summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--.gitlab-ci.yml3
-rw-r--r--.gitlab/CODEOWNERS14
-rw-r--r--.gitlab/ci/build-images.gitlab-ci.yml31
-rw-r--r--.gitlab/ci/docs.gitlab-ci.yml9
-rw-r--r--.gitlab/ci/frontend.gitlab-ci.yml49
-rw-r--r--.gitlab/ci/global.gitlab-ci.yml21
-rw-r--r--.gitlab/ci/qa.gitlab-ci.yml1
-rw-r--r--.gitlab/ci/rails.gitlab-ci.yml362
-rw-r--r--.gitlab/ci/reports.gitlab-ci.yml13
-rw-r--r--.gitlab/ci/review.gitlab-ci.yml321
-rw-r--r--.gitlab/ci/rules.gitlab-ci.yml199
-rw-r--r--.gitlab/ci/setup.gitlab-ci.yml1
-rw-r--r--.gitlab/ci/yaml.gitlab-ci.yml4
-rw-r--r--.gitlab/issue_templates/Feature proposal.md9
-rw-r--r--.gitlab/issue_templates/Security developer workflow.md12
-rw-r--r--.gitlab/merge_request_templates/Documentation.md6
-rw-r--r--.gitlab/merge_request_templates/Security Release.md22
-rw-r--r--.haml-lint.yml7
-rw-r--r--.haml-lint_todo.yml5
-rw-r--r--.markdownlint.json1
-rw-r--r--.prettierignore1
-rw-r--r--.rubocop.yml22
-rw-r--r--.rubocop_todo.yml419
-rw-r--r--CHANGELOG.md559
-rw-r--r--Dangerfile1
-rw-r--r--GITALY_SERVER_VERSION2
-rw-r--r--GITLAB_ELASTICSEARCH_INDEXER_VERSION2
-rw-r--r--GITLAB_PAGES_VERSION2
-rw-r--r--GITLAB_WORKHORSE_VERSION2
-rw-r--r--Gemfile32
-rw-r--r--Gemfile.lock107
-rw-r--r--app/assets/images/bot_avatars/alert-bot.pngbin0 -> 9362 bytes
-rw-r--r--app/assets/images/bot_avatars/security-bot.pngbin0 -> 9561 bytes
-rw-r--r--app/assets/images/bot_avatars/support-bot.pngbin0 -> 9806 bytes
-rw-r--r--app/assets/images/confluence.svg1
-rw-r--r--app/assets/images/logos/jira-gray.svg1
-rw-r--r--app/assets/javascripts/alert_management/components/alert_details.vue54
-rw-r--r--app/assets/javascripts/alert_management/components/alert_management_empty_state.vue90
-rw-r--r--app/assets/javascripts/alert_management/components/alert_management_list.vue461
-rw-r--r--app/assets/javascripts/alert_management/components/alert_management_list_wrapper.vue75
-rw-r--r--app/assets/javascripts/alert_management/components/alert_management_table.vue453
-rw-r--r--app/assets/javascripts/alert_management/components/alert_metrics.vue56
-rw-r--r--app/assets/javascripts/alert_management/components/alert_sidebar.vue48
-rw-r--r--app/assets/javascripts/alert_management/components/alert_status.vue129
-rw-r--r--app/assets/javascripts/alert_management/components/sidebar/sidebar_assignees.vue64
-rw-r--r--app/assets/javascripts/alert_management/components/sidebar/sidebar_header.vue27
-rw-r--r--app/assets/javascripts/alert_management/components/sidebar/sidebar_status.vue99
-rw-r--r--app/assets/javascripts/alert_management/components/sidebar/sidebar_todo.vue106
-rw-r--r--app/assets/javascripts/alert_management/components/system_notes/system_note.vue2
-rw-r--r--app/assets/javascripts/alert_management/details.js54
-rw-r--r--app/assets/javascripts/alert_management/graphql/fragments/alert_note.fragment.graphql23
-rw-r--r--app/assets/javascripts/alert_management/graphql/fragments/detail_item.fragment.graphql2
-rw-r--r--app/assets/javascripts/alert_management/graphql/fragments/list_item.fragment.graphql1
-rw-r--r--app/assets/javascripts/alert_management/graphql/mutations/alert_set_assignees.graphql15
-rw-r--r--app/assets/javascripts/alert_management/graphql/mutations/alert_set_assignees.mutation.graphql22
-rw-r--r--app/assets/javascripts/alert_management/graphql/mutations/alert_todo_create.graphql11
-rw-r--r--app/assets/javascripts/alert_management/graphql/mutations/create_issue_from_alert.graphql8
-rw-r--r--app/assets/javascripts/alert_management/graphql/mutations/create_issue_from_alert.mutation.graphql8
-rw-r--r--app/assets/javascripts/alert_management/graphql/mutations/toggle_sidebar_status.mutation.graphql3
-rw-r--r--app/assets/javascripts/alert_management/graphql/mutations/update_alert_status.graphql10
-rw-r--r--app/assets/javascripts/alert_management/graphql/mutations/update_alert_status.mutation.graphql17
-rw-r--r--app/assets/javascripts/alert_management/graphql/queries/details.query.graphql12
-rw-r--r--app/assets/javascripts/alert_management/graphql/queries/get_alerts.query.graphql54
-rw-r--r--app/assets/javascripts/alert_management/graphql/queries/get_count_by_status.query.graphql18
-rw-r--r--app/assets/javascripts/alert_management/graphql/queries/sidebar_status.query.graphql3
-rw-r--r--app/assets/javascripts/alert_management/list.js16
-rw-r--r--app/assets/javascripts/alerts_service_settings/components/alerts_service_form.vue23
-rw-r--r--app/assets/javascripts/alerts_service_settings/index.js4
-rw-r--r--app/assets/javascripts/alerts_settings/components/alerts_settings_form.vue563
-rw-r--r--app/assets/javascripts/alerts_settings/constants.js50
-rw-r--r--app/assets/javascripts/alerts_settings/index.js67
-rw-r--r--app/assets/javascripts/alerts_settings/services/index.js36
-rw-r--r--app/assets/javascripts/api.js43
-rw-r--r--app/assets/javascripts/awards_handler.js8
-rw-r--r--app/assets/javascripts/badges/components/badge_form.vue2
-rw-r--r--app/assets/javascripts/batch_comments/components/draft_note.vue11
-rw-r--r--app/assets/javascripts/batch_comments/components/parallel_draft_comment_row.vue8
-rw-r--r--app/assets/javascripts/batch_comments/components/preview_dropdown.vue2
-rw-r--r--app/assets/javascripts/batch_comments/components/preview_item.vue15
-rw-r--r--app/assets/javascripts/behaviors/collapse_sidebar_on_window_resize.js41
-rw-r--r--app/assets/javascripts/behaviors/gl_emoji.js92
-rw-r--r--app/assets/javascripts/behaviors/index.js4
-rw-r--r--app/assets/javascripts/behaviors/markdown/copy_as_gfm.js4
-rw-r--r--app/assets/javascripts/behaviors/markdown/render_mermaid.js2
-rw-r--r--app/assets/javascripts/behaviors/select2.js23
-rw-r--r--app/assets/javascripts/behaviors/shortcuts/shortcuts_toggle.vue28
-rw-r--r--app/assets/javascripts/blob/components/blob_header_viewer_switcher.vue12
-rw-r--r--app/assets/javascripts/blob/components/constants.js2
-rw-r--r--app/assets/javascripts/blob/notebook/notebook_viewer.vue4
-rw-r--r--app/assets/javascripts/blob/pdf/pdf_viewer.vue2
-rw-r--r--app/assets/javascripts/blob/sketch/index.js2
-rw-r--r--app/assets/javascripts/blob/suggest_gitlab_ci_yml/components/popover.vue31
-rw-r--r--app/assets/javascripts/blob/viewer/index.js5
-rw-r--r--app/assets/javascripts/blob_edit/blob_bundle.js7
-rw-r--r--app/assets/javascripts/blob_edit/constants.js4
-rw-r--r--app/assets/javascripts/blob_edit/edit_blob.js83
-rw-r--r--app/assets/javascripts/boards/components/board.js192
-rw-r--r--app/assets/javascripts/boards/components/board_content.vue3
-rw-r--r--app/assets/javascripts/boards/components/board_list_header.vue78
-rw-r--r--app/assets/javascripts/boards/components/board_new_issue.vue19
-rw-r--r--app/assets/javascripts/boards/components/boards_selector.vue2
-rw-r--r--app/assets/javascripts/boards/components/issue_card_inner.vue4
-rw-r--r--app/assets/javascripts/boards/components/modal/header.vue2
-rw-r--r--app/assets/javascripts/boards/index.js12
-rw-r--r--app/assets/javascripts/boards/models/list.js21
-rw-r--r--app/assets/javascripts/boards/queries/board.fragment.graphql2
-rw-r--r--app/assets/javascripts/boards/queries/board_list_shared.fragment.graphql20
-rw-r--r--app/assets/javascripts/boards/stores/boards_store.js24
-rw-r--r--app/assets/javascripts/boards/toggle_focus.js2
-rw-r--r--app/assets/javascripts/ci_variable_list/components/ci_key_field.vue169
-rw-r--r--app/assets/javascripts/ci_variable_list/components/ci_variable_autocomplete_tokens.js14
-rw-r--r--app/assets/javascripts/ci_variable_list/components/ci_variable_modal.vue81
-rw-r--r--app/assets/javascripts/ci_variable_list/store/actions.js28
-rw-r--r--app/assets/javascripts/ci_variable_list/store/mutation_types.js6
-rw-r--r--app/assets/javascripts/ci_variable_list/store/mutations.js35
-rw-r--r--app/assets/javascripts/ci_variable_list/store/state.js4
-rw-r--r--app/assets/javascripts/ci_variable_list/store/utils.js4
-rw-r--r--app/assets/javascripts/close_reopen_report_toggle.js7
-rw-r--r--app/assets/javascripts/clusters/clusters_bundle.js3
-rw-r--r--app/assets/javascripts/clusters/components/ingress_modsecurity_settings.vue8
-rw-r--r--app/assets/javascripts/clusters/components/remove_cluster_confirmation.vue19
-rw-r--r--app/assets/javascripts/clusters_list/components/ancestor_notice.vue34
-rw-r--r--app/assets/javascripts/clusters_list/components/clusters.vue39
-rw-r--r--app/assets/javascripts/clusters_list/store/actions.js30
-rw-r--r--app/assets/javascripts/clusters_list/store/mutation_types.js3
-rw-r--r--app/assets/javascripts/clusters_list/store/mutations.js7
-rw-r--r--app/assets/javascripts/clusters_list/store/state.js4
-rw-r--r--app/assets/javascripts/code_navigation/components/popover.vue92
-rw-r--r--app/assets/javascripts/code_navigation/store/actions.js6
-rw-r--r--app/assets/javascripts/code_navigation/utils/index.js1
-rw-r--r--app/assets/javascripts/commit_merge_requests.js4
-rw-r--r--app/assets/javascripts/commons/polyfills.js45
-rw-r--r--app/assets/javascripts/commons/polyfills/custom_event.js21
-rw-r--r--app/assets/javascripts/commons/polyfills/element.js74
-rw-r--r--app/assets/javascripts/commons/polyfills/event.js22
-rw-r--r--app/assets/javascripts/commons/polyfills/nodelist.js14
-rw-r--r--app/assets/javascripts/commons/polyfills/request_idle_callback.js24
-rw-r--r--app/assets/javascripts/commons/polyfills/svg.js11
-rw-r--r--app/assets/javascripts/cycle_analytics/components/stage_nav_item.vue5
-rw-r--r--app/assets/javascripts/deploy_keys/components/app.vue8
-rw-r--r--app/assets/javascripts/design_management/components/design_destroyer.vue2
-rw-r--r--app/assets/javascripts/design_management/components/design_notes/design_discussion.vue4
-rw-r--r--app/assets/javascripts/design_management/components/design_notes/design_reply_form.vue5
-rw-r--r--app/assets/javascripts/design_management/components/toolbar/index.vue2
-rw-r--r--app/assets/javascripts/design_management/components/upload/design_dropzone.vue2
-rw-r--r--app/assets/javascripts/design_management/graphql/fragments/design.fragment.graphql6
-rw-r--r--app/assets/javascripts/design_management/graphql/fragments/designNote.fragment.graphql29
-rw-r--r--app/assets/javascripts/design_management/graphql/fragments/design_list.fragment.graphql (renamed from app/assets/javascripts/design_management/graphql/fragments/designList.fragment.graphql)0
-rw-r--r--app/assets/javascripts/design_management/graphql/fragments/design_note.fragment.graphql29
-rw-r--r--app/assets/javascripts/design_management/graphql/fragments/diff_refs.fragment.graphql (renamed from app/assets/javascripts/design_management/graphql/fragments/diffRefs.fragment.graphql)0
-rw-r--r--app/assets/javascripts/design_management/graphql/mutations/createImageDiffNote.mutation.graphql21
-rw-r--r--app/assets/javascripts/design_management/graphql/mutations/createNote.mutation.graphql10
-rw-r--r--app/assets/javascripts/design_management/graphql/mutations/create_image_diff_note.mutation.graphql21
-rw-r--r--app/assets/javascripts/design_management/graphql/mutations/create_note.mutation.graphql10
-rw-r--r--app/assets/javascripts/design_management/graphql/mutations/destroy_design.mutation.graphql (renamed from app/assets/javascripts/design_management/graphql/mutations/destroyDesign.mutation.graphql)0
-rw-r--r--app/assets/javascripts/design_management/graphql/mutations/toggle_resolve_discussion.mutation.graphql2
-rw-r--r--app/assets/javascripts/design_management/graphql/mutations/updateImageDiffNote.mutation.graphql10
-rw-r--r--app/assets/javascripts/design_management/graphql/mutations/update_active_discussion.mutation.graphql2
-rw-r--r--app/assets/javascripts/design_management/graphql/mutations/update_image_diff_note.mutation.graphql10
-rw-r--r--app/assets/javascripts/design_management/graphql/mutations/update_note.mutation.graphql2
-rw-r--r--app/assets/javascripts/design_management/graphql/mutations/uploadDesign.mutation.graphql21
-rw-r--r--app/assets/javascripts/design_management/graphql/mutations/upload_design.mutation.graphql21
-rw-r--r--app/assets/javascripts/design_management/graphql/queries/app_data.query.graphql (renamed from app/assets/javascripts/design_management/graphql/queries/appData.query.graphql)0
-rw-r--r--app/assets/javascripts/design_management/graphql/queries/get_design.query.graphql (renamed from app/assets/javascripts/design_management/graphql/queries/getDesign.query.graphql)0
-rw-r--r--app/assets/javascripts/design_management/graphql/queries/get_design_list.query.graphql2
-rw-r--r--app/assets/javascripts/design_management/index.js3
-rw-r--r--app/assets/javascripts/design_management/mixins/all_versions.js2
-rw-r--r--app/assets/javascripts/design_management/pages/design/index.vue12
-rw-r--r--app/assets/javascripts/design_management/pages/index.vue2
-rw-r--r--app/assets/javascripts/design_management/utils/tracking.js27
-rw-r--r--app/assets/javascripts/design_management_new/components/app.vue3
-rw-r--r--app/assets/javascripts/design_management_new/components/delete_button.vue81
-rw-r--r--app/assets/javascripts/design_management_new/components/design_destroyer.vue67
-rw-r--r--app/assets/javascripts/design_management_new/components/design_note_pin.vue61
-rw-r--r--app/assets/javascripts/design_management_new/components/design_notes/design_discussion.vue297
-rw-r--r--app/assets/javascripts/design_management_new/components/design_notes/design_note.vue156
-rw-r--r--app/assets/javascripts/design_management_new/components/design_notes/design_reply_form.vue141
-rw-r--r--app/assets/javascripts/design_management_new/components/design_notes/toggle_replies_widget.vue70
-rw-r--r--app/assets/javascripts/design_management_new/components/design_overlay.vue287
-rw-r--r--app/assets/javascripts/design_management_new/components/design_presentation.vue322
-rw-r--r--app/assets/javascripts/design_management_new/components/design_scaler.vue65
-rw-r--r--app/assets/javascripts/design_management_new/components/design_sidebar.vue178
-rw-r--r--app/assets/javascripts/design_management_new/components/image.vue110
-rw-r--r--app/assets/javascripts/design_management_new/components/list/item.vue174
-rw-r--r--app/assets/javascripts/design_management_new/components/toolbar/index.vue124
-rw-r--r--app/assets/javascripts/design_management_new/components/toolbar/pagination.vue83
-rw-r--r--app/assets/javascripts/design_management_new/components/toolbar/pagination_button.vue48
-rw-r--r--app/assets/javascripts/design_management_new/components/upload/button.vue59
-rw-r--r--app/assets/javascripts/design_management_new/components/upload/design_dropzone.vue136
-rw-r--r--app/assets/javascripts/design_management_new/components/upload/design_version_dropdown.vue76
-rw-r--r--app/assets/javascripts/design_management_new/constants.js16
-rw-r--r--app/assets/javascripts/design_management_new/graphql.js45
-rw-r--r--app/assets/javascripts/design_management_new/graphql/fragments/design.fragment.graphql24
-rw-r--r--app/assets/javascripts/design_management_new/graphql/fragments/design_list.fragment.graphql8
-rw-r--r--app/assets/javascripts/design_management_new/graphql/fragments/design_note.fragment.graphql29
-rw-r--r--app/assets/javascripts/design_management_new/graphql/fragments/diff_refs.fragment.graphql5
-rw-r--r--app/assets/javascripts/design_management_new/graphql/fragments/discussion_resolved_status.fragment.graphql9
-rw-r--r--app/assets/javascripts/design_management_new/graphql/fragments/note_permissions.fragment.graphql3
-rw-r--r--app/assets/javascripts/design_management_new/graphql/fragments/version.fragment.graphql4
-rw-r--r--app/assets/javascripts/design_management_new/graphql/mutations/create_image_diff_note.mutation.graphql21
-rw-r--r--app/assets/javascripts/design_management_new/graphql/mutations/create_note.mutation.graphql10
-rw-r--r--app/assets/javascripts/design_management_new/graphql/mutations/destroy_design.mutation.graphql10
-rw-r--r--app/assets/javascripts/design_management_new/graphql/mutations/toggle_resolve_discussion.mutation.graphql17
-rw-r--r--app/assets/javascripts/design_management_new/graphql/mutations/update_active_discussion.mutation.graphql3
-rw-r--r--app/assets/javascripts/design_management_new/graphql/mutations/update_image_diff_note.mutation.graphql10
-rw-r--r--app/assets/javascripts/design_management_new/graphql/mutations/update_note.mutation.graphql10
-rw-r--r--app/assets/javascripts/design_management_new/graphql/mutations/upload_design.mutation.graphql21
-rw-r--r--app/assets/javascripts/design_management_new/graphql/queries/active_discussion.query.graphql6
-rw-r--r--app/assets/javascripts/design_management_new/graphql/queries/design_permissions.query.graphql10
-rw-r--r--app/assets/javascripts/design_management_new/graphql/queries/get_design.query.graphql31
-rw-r--r--app/assets/javascripts/design_management_new/graphql/queries/get_design_list.query.graphql26
-rw-r--r--app/assets/javascripts/design_management_new/graphql/typedefs.graphql12
-rw-r--r--app/assets/javascripts/design_management_new/index.js33
-rw-r--r--app/assets/javascripts/design_management_new/mixins/all_designs.js49
-rw-r--r--app/assets/javascripts/design_management_new/mixins/all_versions.js59
-rw-r--r--app/assets/javascripts/design_management_new/pages/design/index.vue367
-rw-r--r--app/assets/javascripts/design_management_new/pages/index.vue346
-rw-r--r--app/assets/javascripts/design_management_new/router/constants.js2
-rw-r--r--app/assets/javascripts/design_management_new/router/index.js32
-rw-r--r--app/assets/javascripts/design_management_new/router/routes.js29
-rw-r--r--app/assets/javascripts/design_management_new/utils/cache_update.js276
-rw-r--r--app/assets/javascripts/design_management_new/utils/design_management_utils.js128
-rw-r--r--app/assets/javascripts/design_management_new/utils/error_messages.js95
-rw-r--r--app/assets/javascripts/design_management_new/utils/tracking.js27
-rw-r--r--app/assets/javascripts/diffs/components/app.vue56
-rw-r--r--app/assets/javascripts/diffs/components/diff_file.vue5
-rw-r--r--app/assets/javascripts/diffs/components/diff_file_header.vue24
-rw-r--r--app/assets/javascripts/diffs/components/diff_file_row.vue13
-rw-r--r--app/assets/javascripts/diffs/components/diff_line_note_form.vue35
-rw-r--r--app/assets/javascripts/diffs/components/diff_table_cell.vue107
-rw-r--r--app/assets/javascripts/diffs/components/inline_diff_table_row.vue16
-rw-r--r--app/assets/javascripts/diffs/components/inline_diff_view.vue15
-rw-r--r--app/assets/javascripts/diffs/components/no_changes.vue8
-rw-r--r--app/assets/javascripts/diffs/components/parallel_diff_table_row.vue13
-rw-r--r--app/assets/javascripts/diffs/components/parallel_diff_view.vue15
-rw-r--r--app/assets/javascripts/diffs/components/tree_list.vue3
-rw-r--r--app/assets/javascripts/diffs/constants.js6
-rw-r--r--app/assets/javascripts/diffs/index.js16
-rw-r--r--app/assets/javascripts/diffs/store/actions.js68
-rw-r--r--app/assets/javascripts/diffs/store/modules/diff_state.js11
-rw-r--r--app/assets/javascripts/diffs/store/utils.js9
-rw-r--r--app/assets/javascripts/editor/editor_lite.js25
-rw-r--r--app/assets/javascripts/editor/editor_markdown_ext.js99
-rw-r--r--app/assets/javascripts/emoji/index.js88
-rw-r--r--app/assets/javascripts/environments/components/container.vue7
-rw-r--r--app/assets/javascripts/environments/components/empty_state.vue20
-rw-r--r--app/assets/javascripts/environments/components/environments_app.vue6
-rw-r--r--app/assets/javascripts/environments/components/environments_table.vue2
-rw-r--r--app/assets/javascripts/environments/mixins/environments_mixin.js4
-rw-r--r--app/assets/javascripts/environments/mount_show.js2
-rw-r--r--app/assets/javascripts/error_tracking/components/error_details.vue47
-rw-r--r--app/assets/javascripts/error_tracking/components/stacktrace_entry.vue9
-rw-r--r--app/assets/javascripts/error_tracking/queries/details.query.graphql50
-rw-r--r--app/assets/javascripts/error_tracking_settings/components/error_tracking_form.vue4
-rw-r--r--app/assets/javascripts/filtered_search/constants.js2
-rw-r--r--app/assets/javascripts/filtered_search/dropdown_emoji.js2
-rw-r--r--app/assets/javascripts/filtered_search/dropdown_user.js2
-rw-r--r--app/assets/javascripts/filtered_search/filtered_search_manager.js8
-rw-r--r--app/assets/javascripts/filtered_search/filtered_search_tokenizer.js8
-rw-r--r--app/assets/javascripts/filtered_search/stores/recent_searches_store.js12
-rw-r--r--app/assets/javascripts/filtered_search/visual_token_value.js20
-rw-r--r--app/assets/javascripts/gfm_auto_complete.js22
-rw-r--r--app/assets/javascripts/gl_field_error.js2
-rw-r--r--app/assets/javascripts/gl_field_errors.js23
-rw-r--r--app/assets/javascripts/gl_form.js13
-rw-r--r--app/assets/javascripts/global_search_input.js425
-rw-r--r--app/assets/javascripts/graphql_shared/fragments/user.fragment.graphql7
-rw-r--r--app/assets/javascripts/groups/components/group_item.vue4
-rw-r--r--app/assets/javascripts/groups/components/groups.vue2
-rw-r--r--app/assets/javascripts/groups/components/item_caret.vue2
-rw-r--r--app/assets/javascripts/header.js5
-rw-r--r--app/assets/javascripts/helpers/event_hub_factory.js109
-rw-r--r--app/assets/javascripts/helpers/monitor_helper.js22
-rw-r--r--app/assets/javascripts/ide/components/branches/item.vue2
-rw-r--r--app/assets/javascripts/ide/components/commit_sidebar/actions.vue2
-rw-r--r--app/assets/javascripts/ide/components/commit_sidebar/empty_state.vue2
-rw-r--r--app/assets/javascripts/ide/components/commit_sidebar/list_collapsed.vue2
-rw-r--r--app/assets/javascripts/ide/components/commit_sidebar/message_field.vue2
-rw-r--r--app/assets/javascripts/ide/components/commit_sidebar/new_merge_request_option.vue2
-rw-r--r--app/assets/javascripts/ide/components/commit_sidebar/radio_group.vue2
-rw-r--r--app/assets/javascripts/ide/components/commit_sidebar/success_message.vue2
-rw-r--r--app/assets/javascripts/ide/components/file_row_extra.vue2
-rw-r--r--app/assets/javascripts/ide/components/file_templates/bar.vue2
-rw-r--r--app/assets/javascripts/ide/components/ide.vue9
-rw-r--r--app/assets/javascripts/ide/components/ide_review.vue2
-rw-r--r--app/assets/javascripts/ide/components/ide_status_list.vue17
-rw-r--r--app/assets/javascripts/ide/components/jobs/item.vue2
-rw-r--r--app/assets/javascripts/ide/components/jobs/list.vue2
-rw-r--r--app/assets/javascripts/ide/components/jobs/stage.vue2
-rw-r--r--app/assets/javascripts/ide/components/merge_requests/item.vue2
-rw-r--r--app/assets/javascripts/ide/components/merge_requests/list.vue2
-rw-r--r--app/assets/javascripts/ide/components/new_dropdown/index.vue2
-rw-r--r--app/assets/javascripts/ide/components/new_dropdown/modal.vue5
-rw-r--r--app/assets/javascripts/ide/components/pipelines/list.vue4
-rw-r--r--app/assets/javascripts/ide/components/repo_editor.vue3
-rw-r--r--app/assets/javascripts/ide/components/terminal/empty_state.vue2
-rw-r--r--app/assets/javascripts/ide/lib/editor.js7
-rw-r--r--app/assets/javascripts/ide/lib/schemas/index.js4
-rw-r--r--app/assets/javascripts/ide/lib/schemas/json/index.js8
-rw-r--r--app/assets/javascripts/ide/lib/schemas/yaml/gitlab_ci.js4
-rw-r--r--app/assets/javascripts/ide/lib/schemas/yaml/index.js12
-rw-r--r--app/assets/javascripts/ide/queries/getUserPermissions.query.graphql4
-rw-r--r--app/assets/javascripts/ide/services/gql.js23
-rw-r--r--app/assets/javascripts/ide/services/index.js12
-rw-r--r--app/assets/javascripts/ide/stores/actions/file.js85
-rw-r--r--app/assets/javascripts/ide/stores/actions/merge_request.js1
-rw-r--r--app/assets/javascripts/ide/stores/mutation_types.js1
-rw-r--r--app/assets/javascripts/ide/stores/mutations.js9
-rw-r--r--app/assets/javascripts/ide/stores/utils.js7
-rw-r--r--app/assets/javascripts/ide/utils.js15
-rw-r--r--app/assets/javascripts/import_projects/components/bitbucket_status_table.vue2
-rw-r--r--app/assets/javascripts/import_projects/store/actions.js15
-rw-r--r--app/assets/javascripts/importer_status.js6
-rw-r--r--app/assets/javascripts/incidents_settings/components/alerts_form.vue139
-rw-r--r--app/assets/javascripts/incidents_settings/components/incidents_settings_tabs.vue61
-rw-r--r--app/assets/javascripts/incidents_settings/components/pagerduty_form.vue183
-rw-r--r--app/assets/javascripts/incidents_settings/constants.js83
-rw-r--r--app/assets/javascripts/incidents_settings/incidents_settings_service.js32
-rw-r--r--app/assets/javascripts/incidents_settings/index.js46
-rw-r--r--app/assets/javascripts/integrations/edit/components/active_toggle.vue12
-rw-r--r--app/assets/javascripts/integrations/edit/components/dynamic_field.vue22
-rw-r--r--app/assets/javascripts/integrations/edit/components/integration_form.vue82
-rw-r--r--app/assets/javascripts/integrations/edit/components/jira_issues_fields.vue151
-rw-r--r--app/assets/javascripts/integrations/edit/components/jira_trigger_fields.vue58
-rw-r--r--app/assets/javascripts/integrations/edit/components/override_dropdown.vue63
-rw-r--r--app/assets/javascripts/integrations/edit/components/trigger_fields.vue7
-rw-r--r--app/assets/javascripts/integrations/edit/index.js97
-rw-r--r--app/assets/javascripts/integrations/edit/store/actions.js4
-rw-r--r--app/assets/javascripts/integrations/edit/store/getters.js6
-rw-r--r--app/assets/javascripts/integrations/edit/store/index.js17
-rw-r--r--app/assets/javascripts/integrations/edit/store/mutation_types.js2
-rw-r--r--app/assets/javascripts/integrations/edit/store/mutations.js7
-rw-r--r--app/assets/javascripts/integrations/edit/store/state.js9
-rw-r--r--app/assets/javascripts/integrations/integration_settings_form.js5
-rw-r--r--app/assets/javascripts/issuable_bulk_update_actions.js81
-rw-r--r--app/assets/javascripts/issuable_bulk_update_sidebar.js16
-rw-r--r--app/assets/javascripts/issuable_suggestions/components/app.vue2
-rw-r--r--app/assets/javascripts/issuable_suggestions/components/item.vue6
-rw-r--r--app/assets/javascripts/issuables_list/components/issuable.vue121
-rw-r--r--app/assets/javascripts/issuables_list/components/issuable_list_root_app.vue52
-rw-r--r--app/assets/javascripts/issuables_list/components/issuables_list_app.vue187
-rw-r--r--app/assets/javascripts/issuables_list/constants.js23
-rw-r--r--app/assets/javascripts/issuables_list/index.js2
-rw-r--r--app/assets/javascripts/issuables_list/queries/get_issues_list_details.query.graphql5
-rw-r--r--app/assets/javascripts/issue.js10
-rw-r--r--app/assets/javascripts/issue_show/components/app.vue2
-rw-r--r--app/assets/javascripts/issue_show/components/edit_actions.vue4
-rw-r--r--app/assets/javascripts/issue_show/components/fields/description.vue2
-rw-r--r--app/assets/javascripts/issue_show/components/issuable_header_warnings.vue28
-rw-r--r--app/assets/javascripts/issue_show/components/pinned_links.vue65
-rw-r--r--app/assets/javascripts/issue_show/constants.js3
-rw-r--r--app/assets/javascripts/issue_show/index.js12
-rw-r--r--app/assets/javascripts/jira_import/components/jira_import_app.vue59
-rw-r--r--app/assets/javascripts/jira_import/components/jira_import_form.vue184
-rw-r--r--app/assets/javascripts/jira_import/index.js1
-rw-r--r--app/assets/javascripts/jira_import/queries/get_jira_user_mapping.mutation.graphql11
-rw-r--r--app/assets/javascripts/jira_import/utils/jira_import_utils.js34
-rw-r--r--app/assets/javascripts/jobs/components/commit_block.vue2
-rw-r--r--app/assets/javascripts/jobs/components/environments_block.vue2
-rw-r--r--app/assets/javascripts/jobs/components/erased_block.vue2
-rw-r--r--app/assets/javascripts/jobs/components/job_app.vue8
-rw-r--r--app/assets/javascripts/jobs/components/job_log.vue59
-rw-r--r--app/assets/javascripts/jobs/components/job_log_controllers.vue2
-rw-r--r--app/assets/javascripts/jobs/components/log/collapsible_section.vue2
-rw-r--r--app/assets/javascripts/jobs/components/log/line.vue40
-rw-r--r--app/assets/javascripts/jobs/components/log/line_number.vue57
-rw-r--r--app/assets/javascripts/jobs/components/log/log.vue6
-rw-r--r--app/assets/javascripts/jobs/components/manual_variables_form.vue10
-rw-r--r--app/assets/javascripts/jobs/components/stuck_block.vue2
-rw-r--r--app/assets/javascripts/jobs/components/trigger_block.vue2
-rw-r--r--app/assets/javascripts/jobs/store/actions.js2
-rw-r--r--app/assets/javascripts/jobs/store/mutations.js16
-rw-r--r--app/assets/javascripts/jobs/store/state.js4
-rw-r--r--app/assets/javascripts/jobs/store/utils.js6
-rw-r--r--app/assets/javascripts/labels_select.js51
-rw-r--r--app/assets/javascripts/lazy_loader.js29
-rw-r--r--app/assets/javascripts/lib/utils/axios_startup_calls.js52
-rw-r--r--app/assets/javascripts/lib/utils/axios_utils.js3
-rw-r--r--app/assets/javascripts/lib/utils/common_utils.js97
-rw-r--r--app/assets/javascripts/lib/utils/constants.js2
-rw-r--r--app/assets/javascripts/lib/utils/datetime_utility.js7
-rw-r--r--app/assets/javascripts/lib/utils/dom_utils.js22
-rw-r--r--app/assets/javascripts/lib/utils/grammar.js18
-rw-r--r--app/assets/javascripts/lib/utils/text_markdown.js43
-rw-r--r--app/assets/javascripts/lib/utils/text_utility.js81
-rw-r--r--app/assets/javascripts/lib/utils/url_utility.js13
-rw-r--r--app/assets/javascripts/logs/components/environment_logs.vue41
-rw-r--r--app/assets/javascripts/logs/components/log_control_buttons.vue37
-rw-r--r--app/assets/javascripts/logs/constants.js6
-rw-r--r--app/assets/javascripts/logs/stores/actions.js38
-rw-r--r--app/assets/javascripts/logs/stores/mutation_types.js4
-rw-r--r--app/assets/javascripts/logs/stores/mutations.js23
-rw-r--r--app/assets/javascripts/logs/stores/state.js10
-rw-r--r--app/assets/javascripts/main.js98
-rw-r--r--app/assets/javascripts/members.js3
-rw-r--r--app/assets/javascripts/merge_request.js11
-rw-r--r--app/assets/javascripts/merge_request_tabs.js2
-rw-r--r--app/assets/javascripts/monitoring/components/charts/anomaly.vue2
-rw-r--r--app/assets/javascripts/monitoring/components/charts/heatmap.vue5
-rw-r--r--app/assets/javascripts/monitoring/components/charts/options.js18
-rw-r--r--app/assets/javascripts/monitoring/components/charts/single_stat.vue11
-rw-r--r--app/assets/javascripts/monitoring/components/charts/stacked_column.vue4
-rw-r--r--app/assets/javascripts/monitoring/components/charts/time_series.vue2
-rw-r--r--app/assets/javascripts/monitoring/components/create_dashboard_modal.vue66
-rw-r--r--app/assets/javascripts/monitoring/components/dashboard.vue121
-rw-r--r--app/assets/javascripts/monitoring/components/dashboard_header.vue168
-rw-r--r--app/assets/javascripts/monitoring/components/dashboard_panel.vue67
-rw-r--r--app/assets/javascripts/monitoring/components/dashboards_dropdown.vue85
-rw-r--r--app/assets/javascripts/monitoring/components/duplicate_dashboard_modal.vue95
-rw-r--r--app/assets/javascripts/monitoring/components/empty_state.vue74
-rw-r--r--app/assets/javascripts/monitoring/components/graph_group.vue19
-rw-r--r--app/assets/javascripts/monitoring/components/refresh_button.vue163
-rw-r--r--app/assets/javascripts/monitoring/components/variables/custom_variable.vue50
-rw-r--r--app/assets/javascripts/monitoring/components/variables/dropdown_field.vue53
-rw-r--r--app/assets/javascripts/monitoring/components/variables/text_field.vue39
-rw-r--r--app/assets/javascripts/monitoring/components/variables/text_variable.vue39
-rw-r--r--app/assets/javascripts/monitoring/components/variables_section.vue33
-rw-r--r--app/assets/javascripts/monitoring/constants.js30
-rw-r--r--app/assets/javascripts/monitoring/format_date.js1
-rw-r--r--app/assets/javascripts/monitoring/monitoring_app.js35
-rw-r--r--app/assets/javascripts/monitoring/pages/dashboard_page.vue11
-rw-r--r--app/assets/javascripts/monitoring/queries/getDashboardValidationWarnings.query.graphql18
-rw-r--r--app/assets/javascripts/monitoring/router/constants.js1
-rw-r--r--app/assets/javascripts/monitoring/router/routes.js9
-rw-r--r--app/assets/javascripts/monitoring/stores/actions.js124
-rw-r--r--app/assets/javascripts/monitoring/stores/getters.js35
-rw-r--r--app/assets/javascripts/monitoring/stores/mutation_types.js13
-rw-r--r--app/assets/javascripts/monitoring/stores/mutations.js67
-rw-r--r--app/assets/javascripts/monitoring/stores/state.js31
-rw-r--r--app/assets/javascripts/monitoring/stores/utils.js198
-rw-r--r--app/assets/javascripts/monitoring/stores/variable_mapping.js158
-rw-r--r--app/assets/javascripts/monitoring/utils.js51
-rw-r--r--app/assets/javascripts/namespace_storage_limit_alert.js20
-rw-r--r--app/assets/javascripts/notes.js2
-rw-r--r--app/assets/javascripts/notes/components/comment_form.vue42
-rw-r--r--app/assets/javascripts/notes/components/discussion_notes.vue10
-rw-r--r--app/assets/javascripts/notes/components/multiline_comment_form.vue45
-rw-r--r--app/assets/javascripts/notes/components/multiline_comment_utils.js78
-rw-r--r--app/assets/javascripts/notes/components/note_actions.vue21
-rw-r--r--app/assets/javascripts/notes/components/note_form.vue18
-rw-r--r--app/assets/javascripts/notes/components/noteable_note.vue79
-rw-r--r--app/assets/javascripts/notes/components/sort_discussion.vue5
-rw-r--r--app/assets/javascripts/notes/mixins/diff_line_note_form.js5
-rw-r--r--app/assets/javascripts/notes/mixins/discussion_navigation.js13
-rw-r--r--app/assets/javascripts/notes/stores/actions.js46
-rw-r--r--app/assets/javascripts/notes/stores/modules/index.js10
-rw-r--r--app/assets/javascripts/notes/stores/mutation_types.js3
-rw-r--r--app/assets/javascripts/notes/stores/mutations.js12
-rw-r--r--app/assets/javascripts/notes/stores/utils.js8
-rw-r--r--app/assets/javascripts/pages/admin/abuse_reports/abuse_reports.js2
-rw-r--r--app/assets/javascripts/pages/admin/clusters/show/index.js2
-rw-r--r--app/assets/javascripts/pages/admin/groups/show/index.js24
-rw-r--r--app/assets/javascripts/pages/admin/projects/index.js22
-rw-r--r--app/assets/javascripts/pages/constants.js1
-rw-r--r--app/assets/javascripts/pages/groups/clusters/show/index.js2
-rw-r--r--app/assets/javascripts/pages/groups/group_members/index.js30
-rw-r--r--app/assets/javascripts/pages/groups/group_members/index/index.js14
-rw-r--r--app/assets/javascripts/pages/groups/new/group_path_validator.js10
-rw-r--r--app/assets/javascripts/pages/groups/settings/ci_cd/show/index.js9
-rw-r--r--app/assets/javascripts/pages/groups/shared/group_details.js3
-rw-r--r--app/assets/javascripts/pages/profiles/show/index.js5
-rw-r--r--app/assets/javascripts/pages/projects/clusters/show/cluster_health.js18
-rw-r--r--app/assets/javascripts/pages/projects/clusters/show/index.js2
-rw-r--r--app/assets/javascripts/pages/projects/commit/pipelines/index.js2
-rw-r--r--app/assets/javascripts/pages/projects/edit/index.js7
-rw-r--r--app/assets/javascripts/pages/projects/forks/new/components/fork_groups_list.vue91
-rw-r--r--app/assets/javascripts/pages/projects/forks/new/components/fork_groups_list_item.vue147
-rw-r--r--app/assets/javascripts/pages/projects/graphs/components/code_coverage.vue20
-rw-r--r--app/assets/javascripts/pages/projects/integrations/jira/issues/index/index.js5
-rw-r--r--app/assets/javascripts/pages/projects/issues/service_desk/filtered_search.js30
-rw-r--r--app/assets/javascripts/pages/projects/issues/service_desk/index.js11
-rw-r--r--app/assets/javascripts/pages/projects/issues/show.js20
-rw-r--r--app/assets/javascripts/pages/projects/metrics_dashboard/index.js3
-rw-r--r--app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/interval_pattern_input.vue215
-rw-r--r--app/assets/javascripts/pages/projects/pipelines/index/index.js3
-rw-r--r--app/assets/javascripts/pages/projects/project_members/index.js26
-rw-r--r--app/assets/javascripts/pages/projects/releases/new/index.js7
-rw-r--r--app/assets/javascripts/pages/projects/settings/operations/show/index.js4
-rw-r--r--app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue2
-rw-r--r--app/assets/javascripts/pages/projects/show/index.js27
-rw-r--r--app/assets/javascripts/pages/projects/tree/show/index.js45
-rw-r--r--app/assets/javascripts/pages/search/init_filtered_search.js2
-rw-r--r--app/assets/javascripts/pages/sessions/new/length_validator.js23
-rw-r--r--app/assets/javascripts/pages/sessions/new/username_validator.js2
-rw-r--r--app/assets/javascripts/pages/shared/wikis/components/delete_wiki_modal.vue15
-rw-r--r--app/assets/javascripts/pages/shared/wikis/wikis.js19
-rw-r--r--app/assets/javascripts/performance_bar/components/request_selector.vue2
-rw-r--r--app/assets/javascripts/persistent_user_callout.js35
-rw-r--r--app/assets/javascripts/persistent_user_callouts.js3
-rw-r--r--app/assets/javascripts/pipelines/components/dag/constants.js5
-rw-r--r--app/assets/javascripts/pipelines/components/dag/dag.vue117
-rw-r--r--app/assets/javascripts/pipelines/components/dag/dag_annotations.vue73
-rw-r--r--app/assets/javascripts/pipelines/components/dag/dag_graph.vue70
-rw-r--r--app/assets/javascripts/pipelines/components/dag/interactions.js50
-rw-r--r--app/assets/javascripts/pipelines/components/graph/graph_component.vue16
-rw-r--r--app/assets/javascripts/pipelines/components/graph/job_item.vue14
-rw-r--r--app/assets/javascripts/pipelines/components/graph/linked_pipeline.vue53
-rw-r--r--app/assets/javascripts/pipelines/components/graph/linked_pipelines_column.vue6
-rw-r--r--app/assets/javascripts/pipelines/components/graph/stage_column_component.vue6
-rw-r--r--app/assets/javascripts/pipelines/components/header_component.vue2
-rw-r--r--app/assets/javascripts/pipelines/components/nav_controls.vue66
-rw-r--r--app/assets/javascripts/pipelines/components/pipeline_triggerer.vue35
-rw-r--r--app/assets/javascripts/pipelines/components/pipeline_url.vue118
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines.vue374
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_actions.vue112
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_list/blank_state.vue (renamed from app/assets/javascripts/pipelines/components/blank_state.vue)0
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_list/empty_state.vue (renamed from app/assets/javascripts/pipelines/components/empty_state.vue)0
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_list/nav_controls.vue66
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_list/pipeline_stop_modal.vue (renamed from app/assets/javascripts/pipelines/components/pipeline_stop_modal.vue)0
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_list/pipeline_triggerer.vue35
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_list/pipeline_url.vue146
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_list/pipelines.vue362
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_list/pipelines_actions.vue112
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_list/pipelines_artifacts.vue (renamed from app/assets/javascripts/pipelines/components/pipelines_artifacts.vue)0
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_list/pipelines_filtered_search.vue (renamed from app/assets/javascripts/pipelines/components/pipelines_filtered_search.vue)0
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_list/pipelines_table.vue107
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_list/pipelines_table_row.vue365
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_list/stage.vue194
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_list/time_ago.vue79
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_list/tokens/pipeline_branch_name_token.vue73
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_list/tokens/pipeline_status_token.vue (renamed from app/assets/javascripts/pipelines/components/tokens/pipeline_status_token.vue)0
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_list/tokens/pipeline_tag_name_token.vue64
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_list/tokens/pipeline_trigger_author_token.vue117
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_table.vue101
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_table_row.vue355
-rw-r--r--app/assets/javascripts/pipelines/components/stage.vue194
-rw-r--r--app/assets/javascripts/pipelines/components/test_reports/test_reports.vue36
-rw-r--r--app/assets/javascripts/pipelines/components/test_reports/test_suite_table.vue30
-rw-r--r--app/assets/javascripts/pipelines/components/test_reports/test_summary.vue4
-rw-r--r--app/assets/javascripts/pipelines/components/test_reports/test_summary_table.vue16
-rw-r--r--app/assets/javascripts/pipelines/components/time_ago.vue79
-rw-r--r--app/assets/javascripts/pipelines/components/tokens/pipeline_branch_name_token.vue73
-rw-r--r--app/assets/javascripts/pipelines/components/tokens/pipeline_tag_name_token.vue64
-rw-r--r--app/assets/javascripts/pipelines/components/tokens/pipeline_trigger_author_token.vue117
-rw-r--r--app/assets/javascripts/pipelines/constants.js1
-rw-r--r--app/assets/javascripts/pipelines/mixins/pipelines.js12
-rw-r--r--app/assets/javascripts/pipelines/pipeline_details_bundle.js56
-rw-r--r--app/assets/javascripts/pipelines/stores/test_reports/actions.js45
-rw-r--r--app/assets/javascripts/pipelines/stores/test_reports/getters.js12
-rw-r--r--app/assets/javascripts/pipelines/stores/test_reports/index.js13
-rw-r--r--app/assets/javascripts/pipelines/stores/test_reports/mutation_types.js4
-rw-r--r--app/assets/javascripts/pipelines/stores/test_reports/mutations.js12
-rw-r--r--app/assets/javascripts/pipelines/stores/test_reports/state.js13
-rw-r--r--app/assets/javascripts/projects/commits/store/actions.js2
-rw-r--r--app/assets/javascripts/projects/components/remove_modal.vue108
-rw-r--r--app/assets/javascripts/projects/pipelines/charts/components/pipelines_area_chart.vue2
-rw-r--r--app/assets/javascripts/projects/project_remove_modal.js24
-rw-r--r--app/assets/javascripts/projects/settings_service_desk/components/service_desk_root.vue160
-rw-r--r--app/assets/javascripts/projects/settings_service_desk/components/service_desk_setting.vue169
-rw-r--r--app/assets/javascripts/projects/settings_service_desk/event_hub.js3
-rw-r--r--app/assets/javascripts/projects/settings_service_desk/index.js41
-rw-r--r--app/assets/javascripts/projects/settings_service_desk/services/service_desk_service.js27
-rw-r--r--app/assets/javascripts/prometheus_alerts/components/reset_key.vue36
-rw-r--r--app/assets/javascripts/prometheus_alerts/index.js3
-rw-r--r--app/assets/javascripts/ref/components/ref_results_section.vue124
-rw-r--r--app/assets/javascripts/ref/components/ref_selector.vue186
-rw-r--r--app/assets/javascripts/ref/constants.js19
-rw-r--r--app/assets/javascripts/ref/stores/actions.js65
-rw-r--r--app/assets/javascripts/ref/stores/getters.js5
-rw-r--r--app/assets/javascripts/ref/stores/index.js16
-rw-r--r--app/assets/javascripts/ref/stores/mutation_types.js16
-rw-r--r--app/assets/javascripts/ref/stores/mutations.js91
-rw-r--r--app/assets/javascripts/ref/stores/state.js24
-rw-r--r--app/assets/javascripts/registry/explorer/components/delete_button.vue56
-rw-r--r--app/assets/javascripts/registry/explorer/components/details_page/details_row.vue26
-rw-r--r--app/assets/javascripts/registry/explorer/components/details_page/tags_list.vue77
-rw-r--r--app/assets/javascripts/registry/explorer/components/details_page/tags_list_row.vue220
-rw-r--r--app/assets/javascripts/registry/explorer/components/details_page/tags_table.vue210
-rw-r--r--app/assets/javascripts/registry/explorer/components/list_item.vue128
-rw-r--r--app/assets/javascripts/registry/explorer/components/list_page/group_empty_state.vue3
-rw-r--r--app/assets/javascripts/registry/explorer/components/list_page/image_list.vue3
-rw-r--r--app/assets/javascripts/registry/explorer/components/list_page/image_list_row.vue120
-rw-r--r--app/assets/javascripts/registry/explorer/components/list_page/project_empty_state.vue60
-rw-r--r--app/assets/javascripts/registry/explorer/constants/details.js36
-rw-r--r--app/assets/javascripts/registry/explorer/pages/details.vue29
-rw-r--r--app/assets/javascripts/registry/explorer/pages/list.vue1
-rw-r--r--app/assets/javascripts/registry/settings/components/registry_settings_app.vue48
-rw-r--r--app/assets/javascripts/registry/settings/components/settings_form.vue57
-rw-r--r--app/assets/javascripts/registry/settings/constants.js14
-rw-r--r--app/assets/javascripts/registry/shared/components/expiration_policy_fields.vue63
-rw-r--r--app/assets/javascripts/registry/shared/constants.js20
-rw-r--r--app/assets/javascripts/registry/shared/utils.js2
-rw-r--r--app/assets/javascripts/related_merge_requests/components/related_merge_requests.vue2
-rw-r--r--app/assets/javascripts/releases/components/app_new.vue9
-rw-r--r--app/assets/javascripts/releases/components/app_show.vue2
-rw-r--r--app/assets/javascripts/releases/components/evidence_block.vue2
-rw-r--r--app/assets/javascripts/releases/components/release_block.vue2
-rw-r--r--app/assets/javascripts/releases/components/release_block_assets.vue10
-rw-r--r--app/assets/javascripts/releases/components/release_block_header.vue2
-rw-r--r--app/assets/javascripts/releases/components/release_block_metadata.vue2
-rw-r--r--app/assets/javascripts/releases/components/release_block_milestone_info.vue4
-rw-r--r--app/assets/javascripts/releases/mount_new.js20
-rw-r--r--app/assets/javascripts/releases/stores/modules/detail/state.js12
-rw-r--r--app/assets/javascripts/reports/accessibility_report/components/accessibility_issue_body.vue10
-rw-r--r--app/assets/javascripts/reports/codequality_report/components/codequality_issue_body.vue42
-rw-r--r--app/assets/javascripts/reports/codequality_report/grouped_codequality_reports_app.vue83
-rw-r--r--app/assets/javascripts/reports/codequality_report/store/actions.js30
-rw-r--r--app/assets/javascripts/reports/codequality_report/store/getters.js58
-rw-r--r--app/assets/javascripts/reports/codequality_report/store/index.js16
-rw-r--r--app/assets/javascripts/reports/codequality_report/store/mutation_types.js5
-rw-r--r--app/assets/javascripts/reports/codequality_report/store/mutations.js24
-rw-r--r--app/assets/javascripts/reports/codequality_report/store/state.js15
-rw-r--r--app/assets/javascripts/reports/codequality_report/store/utils/codequality_comparison.js41
-rw-r--r--app/assets/javascripts/reports/codequality_report/workers/codequality_comparison_worker.js28
-rw-r--r--app/assets/javascripts/reports/components/grouped_test_reports_app.vue25
-rw-r--r--app/assets/javascripts/reports/components/issue_body.js3
-rw-r--r--app/assets/javascripts/reports/components/report_item.vue2
-rw-r--r--app/assets/javascripts/reports/components/report_section.vue10
-rw-r--r--app/assets/javascripts/reports/components/summary_row.vue9
-rw-r--r--app/assets/javascripts/reports/components/test_issue_body.vue4
-rw-r--r--app/assets/javascripts/repository/components/table/index.vue3
-rw-r--r--app/assets/javascripts/repository/components/table/row.vue6
-rw-r--r--app/assets/javascripts/repository/components/tree_content.vue6
-rw-r--r--app/assets/javascripts/repository/components/web_ide_link.vue47
-rw-r--r--app/assets/javascripts/repository/graphql.js2
-rw-r--r--app/assets/javascripts/repository/index.js22
-rw-r--r--app/assets/javascripts/repository/queries/getFiles.query.graphql4
-rw-r--r--app/assets/javascripts/repository/queries/getVueFileListLfsBadge.query.graphql3
-rw-r--r--app/assets/javascripts/search_autocomplete.js500
-rw-r--r--app/assets/javascripts/serverless/components/environment_row.vue2
-rw-r--r--app/assets/javascripts/serverless/components/function_details.vue2
-rw-r--r--app/assets/javascripts/serverless/components/functions.vue12
-rw-r--r--app/assets/javascripts/set_status_modal/set_status_modal_wrapper.vue5
-rw-r--r--app/assets/javascripts/sidebar/components/confidential/confidential_issue_sidebar.vue24
-rw-r--r--app/assets/javascripts/sidebar/components/confidential/edit_form.vue9
-rw-r--r--app/assets/javascripts/sidebar/components/confidential/edit_form_buttons.vue55
-rw-r--r--app/assets/javascripts/sidebar/components/confidential/queries/update_issue_confidential.mutation.graphql7
-rw-r--r--app/assets/javascripts/sidebar/components/lock/edit_form_buttons.vue2
-rw-r--r--app/assets/javascripts/sidebar/mount_sidebar.js29
-rw-r--r--app/assets/javascripts/sidebar/queries/sidebarDetails.query.graphql6
-rw-r--r--app/assets/javascripts/sidebar/queries/sidebarDetailsForHealthStatusFeatureFlag.query.graphql6
-rw-r--r--app/assets/javascripts/snippets/components/edit.vue112
-rw-r--r--app/assets/javascripts/snippets/components/show.vue13
-rw-r--r--app/assets/javascripts/snippets/components/snippet_blob_edit.vue98
-rw-r--r--app/assets/javascripts/snippets/components/snippet_blob_view.vue70
-rw-r--r--app/assets/javascripts/snippets/components/snippet_header.vue9
-rw-r--r--app/assets/javascripts/snippets/constants.js5
-rw-r--r--app/assets/javascripts/snippets/fragments/project.fragment.graphql4
-rw-r--r--app/assets/javascripts/snippets/fragments/snippetBase.fragment.graphql2
-rw-r--r--app/assets/javascripts/snippets/mixins/snippets.js4
-rw-r--r--app/assets/javascripts/snippets/mutations/deleteSnippet.mutation.graphql4
-rw-r--r--app/assets/javascripts/snippets/queries/projectPermissions.query.graphql2
-rw-r--r--app/assets/javascripts/snippets/queries/snippet.query.graphql2
-rw-r--r--app/assets/javascripts/snippets/queries/userPermissions.query.graphql2
-rw-r--r--app/assets/javascripts/static_site_editor/components/edit_area.vue51
-rw-r--r--app/assets/javascripts/static_site_editor/constants.js2
-rw-r--r--app/assets/javascripts/static_site_editor/graphql/mutations/submit_content_changes.mutation.graphql2
-rw-r--r--app/assets/javascripts/static_site_editor/graphql/queries/app_data.query.graphql2
-rw-r--r--app/assets/javascripts/static_site_editor/graphql/queries/source_content.query.graphql2
-rw-r--r--app/assets/javascripts/static_site_editor/graphql/resolvers/submit_content_changes.js4
-rw-r--r--app/assets/javascripts/static_site_editor/graphql/typedefs.graphql2
-rw-r--r--app/assets/javascripts/static_site_editor/image_repository.js20
-rw-r--r--app/assets/javascripts/static_site_editor/pages/home.vue7
-rw-r--r--app/assets/javascripts/static_site_editor/services/image_service.js9
-rw-r--r--app/assets/javascripts/static_site_editor/services/parse_source_file.js40
-rw-r--r--app/assets/javascripts/static_site_editor/services/submit_content_changes.js32
-rw-r--r--app/assets/javascripts/user_popovers.js3
-rw-r--r--app/assets/javascripts/users_select/index.js6
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/approvals/approvals.vue212
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/approvals/approvals_summary.vue70
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/approvals/approvals_summary_optional.vue50
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/approvals/messages.js11
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/loading.vue2
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/mr_collapsible_extension.vue15
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/mr_widget_author.vue4
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/mr_widget_expandable_section.vue72
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/mr_widget_header.vue15
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/mr_widget_icon.vue2
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.vue109
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline_container.vue3
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/mr_widget_status_icon.vue2
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/mr_widget_suggest_pipeline.vue121
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/mr_widget_terraform_plan.vue139
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/commit_edit.vue2
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/commits_header.vue2
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_auto_merge_enabled.vue6
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_checking.vue2
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_pipeline_tour.vue136
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue31
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/squash_before_merge.vue13
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/terraform/mr_widget_terraform_container.vue140
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/terraform/terraform_plan.vue111
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/constants.js1
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/index.js10
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/mixins/approvals.js19
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/mixins/ready_to_merge.js2
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue33
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/services/mr_widget_service.js16
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/stores/get_state_key.js4
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js32
-rw-r--r--app/assets/javascripts/vue_shared/components/file_finder/index.vue2
-rw-r--r--app/assets/javascripts/vue_shared/components/file_finder/item.vue2
-rw-r--r--app/assets/javascripts/vue_shared/components/file_icon.vue24
-rw-r--r--app/assets/javascripts/vue_shared/components/file_row.vue7
-rw-r--r--app/assets/javascripts/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue65
-rw-r--r--app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/author_token.vue16
-rw-r--r--app/assets/javascripts/vue_shared/components/gl_mentions.vue30
-rw-r--r--app/assets/javascripts/vue_shared/components/gl_modal_vuex.vue9
-rw-r--r--app/assets/javascripts/vue_shared/components/issue/issue_warning.vue91
-rw-r--r--app/assets/javascripts/vue_shared/components/issue/related_issuable_item.vue2
-rw-r--r--app/assets/javascripts/vue_shared/components/loading_button.vue2
-rw-r--r--app/assets/javascripts/vue_shared/components/markdown/field.vue33
-rw-r--r--app/assets/javascripts/vue_shared/components/markdown/header.vue3
-rw-r--r--app/assets/javascripts/vue_shared/components/markdown/suggestion_diff.vue1
-rw-r--r--app/assets/javascripts/vue_shared/components/markdown/suggestion_diff_header.vue31
-rw-r--r--app/assets/javascripts/vue_shared/components/markdown/toolbar.vue4
-rw-r--r--app/assets/javascripts/vue_shared/components/markdown/toolbar_button.vue1
-rw-r--r--app/assets/javascripts/vue_shared/components/notes/noteable_warning.vue113
-rw-r--r--app/assets/javascripts/vue_shared/components/notes/system_note.vue2
-rw-r--r--app/assets/javascripts/vue_shared/components/pagination/table_pagination.vue4
-rw-r--r--app/assets/javascripts/vue_shared/components/project_selector/project_list_item.vue31
-rw-r--r--app/assets/javascripts/vue_shared/components/project_selector/project_selector.vue5
-rw-r--r--app/assets/javascripts/vue_shared/components/remove_member_modal.vue78
-rw-r--r--app/assets/javascripts/vue_shared/components/resizable_chart/constants.js6
-rw-r--r--app/assets/javascripts/vue_shared/components/resizable_chart/skeleton_loader.vue95
-rw-r--r--app/assets/javascripts/vue_shared/components/rich_content_editor/constants.js8
-rw-r--r--app/assets/javascripts/vue_shared/components/rich_content_editor/editor_service.js42
-rw-r--r--app/assets/javascripts/vue_shared/components/rich_content_editor/modals/add_image/add_image_modal.vue147
-rw-r--r--app/assets/javascripts/vue_shared/components/rich_content_editor/modals/add_image/upload_image_tab.vue56
-rw-r--r--app/assets/javascripts/vue_shared/components/rich_content_editor/modals/add_image_modal.vue74
-rw-r--r--app/assets/javascripts/vue_shared/components/rich_content_editor/rich_content_editor.vue75
-rw-r--r--app/assets/javascripts/vue_shared/components/rich_content_editor/services/build_custom_renderer.js68
-rw-r--r--app/assets/javascripts/vue_shared/components/rich_content_editor/services/build_html_to_markdown_renderer.js53
-rw-r--r--app/assets/javascripts/vue_shared/components/rich_content_editor/services/editor_service.js56
-rw-r--r--app/assets/javascripts/vue_shared/components/rich_content_editor/services/renderers/build_uneditable_token.js63
-rw-r--r--app/assets/javascripts/vue_shared/components/rich_content_editor/services/renderers/render_embedded_ruby_text.js13
-rw-r--r--app/assets/javascripts/vue_shared/components/rich_content_editor/services/renderers/render_font_awesome_html_inline.js11
-rw-r--r--app/assets/javascripts/vue_shared/components/rich_content_editor/services/renderers/render_html_block.js9
-rw-r--r--app/assets/javascripts/vue_shared/components/rich_content_editor/services/renderers/render_identifier_instance_text.js40
-rw-r--r--app/assets/javascripts/vue_shared/components/rich_content_editor/services/renderers/render_identifier_paragraph.js16
-rw-r--r--app/assets/javascripts/vue_shared/components/rich_content_editor/services/renderers/render_kramdown_list.js27
-rw-r--r--app/assets/javascripts/vue_shared/components/rich_content_editor/services/renderers/render_kramdown_text.js13
-rw-r--r--app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_button.vue10
-rw-r--r--app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_search_input.vue24
-rw-r--r--app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/constants.js1
-rw-r--r--app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/dropdown_button.vue8
-rw-r--r--app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_create_view.vue8
-rw-r--r--app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_labels_view.vue23
-rw-r--r--app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/labels_select_root.vue19
-rw-r--r--app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/store/getters.js9
-rw-r--r--app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/store/state.js1
-rw-r--r--app/assets/javascripts/vue_shared/components/user_popover/user_popover.vue98
-rw-r--r--app/assets/javascripts/vue_shared/constants.js2
-rw-r--r--app/assets/stylesheets/application.scss29
-rw-r--r--app/assets/stylesheets/application_dark.scss4
-rw-r--r--app/assets/stylesheets/behaviors.scss3
-rw-r--r--app/assets/stylesheets/bootstrap_migration.scss19
-rw-r--r--app/assets/stylesheets/components/design_management/design.scss12
-rw-r--r--app/assets/stylesheets/components/design_management/design_list_item.scss5
-rw-r--r--app/assets/stylesheets/components/popover.scss49
-rw-r--r--app/assets/stylesheets/components/ref_selector.scss17
-rw-r--r--app/assets/stylesheets/components/related_items_list.scss29
-rw-r--r--app/assets/stylesheets/components/rich_content_editor.scss53
-rw-r--r--app/assets/stylesheets/disable_animations.scss2
-rw-r--r--app/assets/stylesheets/emoji_sprites.scss1796
-rw-r--r--app/assets/stylesheets/errors.scss5
-rw-r--r--app/assets/stylesheets/framework.scss2
-rw-r--r--app/assets/stylesheets/framework/awards.scss2
-rw-r--r--app/assets/stylesheets/framework/broadcast_messages.scss2
-rw-r--r--app/assets/stylesheets/framework/buttons.scss21
-rw-r--r--app/assets/stylesheets/framework/common.scss54
-rw-r--r--app/assets/stylesheets/framework/contextual_sidebar.scss6
-rw-r--r--app/assets/stylesheets/framework/dropdowns.scss21
-rw-r--r--app/assets/stylesheets/framework/files.scss23
-rw-r--r--app/assets/stylesheets/framework/filters.scss8
-rw-r--r--app/assets/stylesheets/framework/forms.scss6
-rw-r--r--app/assets/stylesheets/framework/gitlab_theme.scss27
-rw-r--r--app/assets/stylesheets/framework/header.scss6
-rw-r--r--app/assets/stylesheets/framework/images.scss2
-rw-r--r--app/assets/stylesheets/framework/issue_box.scss1
-rw-r--r--app/assets/stylesheets/framework/job_log.scss2
-rw-r--r--app/assets/stylesheets/framework/memory_graph.scss2
-rw-r--r--app/assets/stylesheets/framework/mixins.scss3
-rw-r--r--app/assets/stylesheets/framework/secondary_navigation_elements.scss4
-rw-r--r--app/assets/stylesheets/framework/sidebar.scss2
-rw-r--r--app/assets/stylesheets/framework/stacked_progress_bar.scss2
-rw-r--r--app/assets/stylesheets/framework/system_messages.scss3
-rw-r--r--app/assets/stylesheets/framework/timeline.scss8
-rw-r--r--app/assets/stylesheets/framework/typography.scss6
-rw-r--r--app/assets/stylesheets/framework/variables.scss53
-rw-r--r--app/assets/stylesheets/framework/variables_overrides.scss8
-rw-r--r--app/assets/stylesheets/highlight/themes/dark.scss17
-rw-r--r--app/assets/stylesheets/highlight/themes/monokai.scss7
-rw-r--r--app/assets/stylesheets/highlight/themes/none.scss9
-rw-r--r--app/assets/stylesheets/highlight/themes/solarized-dark.scss12
-rw-r--r--app/assets/stylesheets/highlight/themes/solarized-light.scss12
-rw-r--r--app/assets/stylesheets/highlight/themes/white.scss2
-rw-r--r--app/assets/stylesheets/mailer.scss6
-rw-r--r--app/assets/stylesheets/page_bundles/_ide_theme_overrides.scss12
-rw-r--r--app/assets/stylesheets/page_bundles/ide.scss16
-rw-r--r--app/assets/stylesheets/page_bundles/ide_themes/_solarized-dark.scss2
-rw-r--r--app/assets/stylesheets/pages/alert_management/details.scss4
-rw-r--r--app/assets/stylesheets/pages/alert_management/list.scss33
-rw-r--r--app/assets/stylesheets/pages/boards.scss15
-rw-r--r--app/assets/stylesheets/pages/branches.scss4
-rw-r--r--app/assets/stylesheets/pages/builds.scss2
-rw-r--r--app/assets/stylesheets/pages/container_registry.scss47
-rw-r--r--app/assets/stylesheets/pages/diff.scss3
-rw-r--r--app/assets/stylesheets/pages/editor.scss5
-rw-r--r--app/assets/stylesheets/pages/environment_logs.scss4
-rw-r--r--app/assets/stylesheets/pages/issuable.scss20
-rw-r--r--app/assets/stylesheets/pages/issues/issue_count_badge.scss2
-rw-r--r--app/assets/stylesheets/pages/issues/issues_list.scss5
-rw-r--r--app/assets/stylesheets/pages/labels.scss68
-rw-r--r--app/assets/stylesheets/pages/merge_requests.scss42
-rw-r--r--app/assets/stylesheets/pages/note_form.scss16
-rw-r--r--app/assets/stylesheets/pages/notes.scss9
-rw-r--r--app/assets/stylesheets/pages/pipelines.scss13
-rw-r--r--app/assets/stylesheets/pages/profiles/preferences.scss5
-rw-r--r--app/assets/stylesheets/pages/prometheus.scss2
-rw-r--r--app/assets/stylesheets/pages/runners.scss3
-rw-r--r--app/assets/stylesheets/pages/service_desk.scss7
-rw-r--r--app/assets/stylesheets/pages/settings.scss2
-rw-r--r--app/assets/stylesheets/pages/sherlock.scss6
-rw-r--r--app/assets/stylesheets/pages/wiki.scss4
-rw-r--r--app/assets/stylesheets/performance_bar.scss2
-rw-r--r--app/assets/stylesheets/snippets.scss7
-rw-r--r--app/assets/stylesheets/themes/_dark.scss10
-rw-r--r--app/assets/stylesheets/utilities.scss21
-rw-r--r--app/controllers/admin/application_settings_controller.rb1
-rw-r--r--app/controllers/admin/clusters_controller.rb9
-rw-r--r--app/controllers/admin/jobs_controller.rb4
-rw-r--r--app/controllers/admin/services_controller.rb11
-rw-r--r--app/controllers/application_controller.rb12
-rw-r--r--app/controllers/autocomplete_controller.rb16
-rw-r--r--app/controllers/chaos_controller.rb1
-rw-r--r--app/controllers/clusters/clusters_controller.rb25
-rw-r--r--app/controllers/concerns/controller_with_feature_category.rb45
-rw-r--r--app/controllers/concerns/controller_with_feature_category/config.rb38
-rw-r--r--app/controllers/concerns/filters_events.rb14
-rw-r--r--app/controllers/concerns/integrations_actions.rb8
-rw-r--r--app/controllers/concerns/issuable_actions.rb12
-rw-r--r--app/controllers/concerns/issuable_collections.rb61
-rw-r--r--app/controllers/concerns/known_sign_in.rb19
-rw-r--r--app/controllers/concerns/membership_actions.rb5
-rw-r--r--app/controllers/concerns/metrics/dashboard/prometheus_api_proxy.rb53
-rw-r--r--app/controllers/concerns/metrics_dashboard.rb14
-rw-r--r--app/controllers/concerns/notes_actions.rb84
-rw-r--r--app/controllers/concerns/renders_member_access.rb6
-rw-r--r--app/controllers/concerns/renders_projects_list.rb13
-rw-r--r--app/controllers/concerns/service_params.rb4
-rw-r--r--app/controllers/concerns/snippets/blobs_actions.rb53
-rw-r--r--app/controllers/concerns/snippets/send_blob.rb22
-rw-r--r--app/controllers/concerns/snippets_actions.rb19
-rw-r--r--app/controllers/concerns/snippets_sort.rb9
-rw-r--r--app/controllers/concerns/wiki_actions.rb49
-rw-r--r--app/controllers/dashboard/projects_controller.rb3
-rw-r--r--app/controllers/dashboard/snippets_controller.rb3
-rw-r--r--app/controllers/dashboard/todos_controller.rb3
-rw-r--r--app/controllers/dashboard_controller.rb1
-rw-r--r--app/controllers/explore/projects_controller.rb1
-rw-r--r--app/controllers/groups/application_controller.rb8
-rw-r--r--app/controllers/groups/boards_controller.rb1
-rw-r--r--app/controllers/groups/clusters_controller.rb10
-rw-r--r--app/controllers/groups/runners_controller.rb12
-rw-r--r--app/controllers/groups/settings/ci_cd_controller.rb8
-rw-r--r--app/controllers/groups/variables_controller.rb4
-rw-r--r--app/controllers/groups_controller.rb1
-rw-r--r--app/controllers/ide_controller.rb1
-rw-r--r--app/controllers/import/base_controller.rb11
-rw-r--r--app/controllers/import/bitbucket_controller.rb17
-rw-r--r--app/controllers/import/bitbucket_server_controller.rb46
-rw-r--r--app/controllers/import/fogbugz_controller.rb18
-rw-r--r--app/controllers/import/gitea_controller.rb14
-rw-r--r--app/controllers/import/github_controller.rb99
-rw-r--r--app/controllers/import/gitlab_controller.rb20
-rw-r--r--app/controllers/instance_statistics/cohorts_controller.rb4
-rw-r--r--app/controllers/instance_statistics/dev_ops_score_controller.rb4
-rw-r--r--app/controllers/invites_controller.rb19
-rw-r--r--app/controllers/oauth/applications_controller.rb6
-rw-r--r--app/controllers/omniauth_callbacks_controller.rb2
-rw-r--r--app/controllers/profiles/keys_controller.rb21
-rw-r--r--app/controllers/profiles/personal_access_tokens_controller.rb10
-rw-r--r--app/controllers/profiles/preferences_controller.rb1
-rw-r--r--app/controllers/projects/application_controller.rb6
-rw-r--r--app/controllers/projects/blob_controller.rb7
-rw-r--r--app/controllers/projects/boards_controller.rb1
-rw-r--r--app/controllers/projects/ci/lints_controller.rb2
-rw-r--r--app/controllers/projects/clusters_controller.rb11
-rw-r--r--app/controllers/projects/confluences_controller.rb14
-rw-r--r--app/controllers/projects/cycle_analytics_controller.rb3
-rw-r--r--app/controllers/projects/deployments_controller.rb1
-rw-r--r--app/controllers/projects/environments/prometheus_api_controller.rb46
-rw-r--r--app/controllers/projects/environments_controller.rb4
-rw-r--r--app/controllers/projects/forks_controller.rb1
-rw-r--r--app/controllers/projects/graphs_controller.rb3
-rw-r--r--app/controllers/projects/imports_controller.rb7
-rw-r--r--app/controllers/projects/incident_management/pager_duty_incidents_controller.rb35
-rw-r--r--app/controllers/projects/issues_controller.rb31
-rw-r--r--app/controllers/projects/jobs_controller.rb10
-rw-r--r--app/controllers/projects/logs_controller.rb46
-rw-r--r--app/controllers/projects/merge_requests/application_controller.rb9
-rw-r--r--app/controllers/projects/merge_requests/creations_controller.rb4
-rw-r--r--app/controllers/projects/merge_requests/diffs_controller.rb9
-rw-r--r--app/controllers/projects/merge_requests_controller.rb29
-rw-r--r--app/controllers/projects/metrics_dashboard_controller.rb33
-rw-r--r--app/controllers/projects/pipelines/application_controller.rb24
-rw-r--r--app/controllers/projects/pipelines/stages_controller.rb29
-rw-r--r--app/controllers/projects/pipelines/tests_controller.rb59
-rw-r--r--app/controllers/projects/pipelines_controller.rb25
-rw-r--r--app/controllers/projects/refs_controller.rb15
-rw-r--r--app/controllers/projects/releases_controller.rb21
-rw-r--r--app/controllers/projects/service_desk_controller.rb45
-rw-r--r--app/controllers/projects/services_controller.rb9
-rw-r--r--app/controllers/projects/settings/operations_controller.rb29
-rw-r--r--app/controllers/projects/snippets/blobs_controller.rb5
-rw-r--r--app/controllers/projects/snippets_controller.rb6
-rw-r--r--app/controllers/projects/stages_controller.rb25
-rw-r--r--app/controllers/projects/static_site_editor_controller.rb3
-rw-r--r--app/controllers/projects/tree_controller.rb18
-rw-r--r--app/controllers/projects/variables_controller.rb4
-rw-r--r--app/controllers/projects/wikis_controller.rb1
-rw-r--r--app/controllers/projects_controller.rb9
-rw-r--r--app/controllers/registrations/experience_levels_controller.rb9
-rw-r--r--app/controllers/registrations_controller.rb8
-rw-r--r--app/controllers/root_controller.rb5
-rw-r--r--app/controllers/search_controller.rb15
-rw-r--r--app/controllers/snippets/blobs_controller.rb7
-rw-r--r--app/controllers/snippets_controller.rb2
-rw-r--r--app/controllers/users_controller.rb7
-rw-r--r--app/finders/branches_finder.rb26
-rw-r--r--app/finders/ci/pipelines_finder.rb2
-rw-r--r--app/finders/ci/pipelines_for_merge_request_finder.rb44
-rw-r--r--app/finders/ci/runner_jobs_finder.rb2
-rw-r--r--app/finders/ci/variables_finder.rb31
-rw-r--r--app/finders/events_finder.rb9
-rw-r--r--app/finders/group_projects_finder.rb55
-rw-r--r--app/finders/issuable_finder/params.rb4
-rw-r--r--app/finders/issues_finder.rb1
-rw-r--r--app/finders/issues_finder/params.rb17
-rw-r--r--app/finders/notes_finder.rb9
-rw-r--r--app/finders/packages/composer/packages_finder.rb16
-rw-r--r--app/finders/packages/conan/package_file_finder.rb28
-rw-r--r--app/finders/packages/conan/package_finder.rb32
-rw-r--r--app/finders/packages/go/module_finder.rb29
-rw-r--r--app/finders/packages/go/version_finder.rb44
-rw-r--r--app/finders/packages/group_packages_finder.rb70
-rw-r--r--app/finders/packages/maven/package_finder.rb62
-rw-r--r--app/finders/packages/npm/package_finder.rb29
-rw-r--r--app/finders/packages/nuget/package_finder.rb31
-rw-r--r--app/finders/packages/package_file_finder.rb36
-rw-r--r--app/finders/packages/package_finder.rb16
-rw-r--r--app/finders/packages/packages_finder.rb41
-rw-r--r--app/finders/packages/tags_finder.rb26
-rw-r--r--app/finders/personal_access_tokens_finder.rb2
-rw-r--r--app/finders/projects_finder.rb10
-rw-r--r--app/finders/resource_milestone_event_finder.rb69
-rw-r--r--app/finders/resource_state_event_finder.rb30
-rw-r--r--app/finders/snippets_finder.rb23
-rw-r--r--app/finders/todos_finder.rb23
-rw-r--r--app/graphql/mutations/alert_management/alerts/todo/create.rb30
-rw-r--r--app/graphql/mutations/alert_management/base.rb5
-rw-r--r--app/graphql/mutations/alert_management/update_alert_status.rb4
-rw-r--r--app/graphql/mutations/award_emojis/add.rb2
-rw-r--r--app/graphql/mutations/award_emojis/remove.rb2
-rw-r--r--app/graphql/mutations/award_emojis/toggle.rb2
-rw-r--r--app/graphql/mutations/base_mutation.rb2
-rw-r--r--app/graphql/mutations/concerns/mutations/resolves_issuable.rb29
-rw-r--r--app/graphql/mutations/container_expiration_policies/update.rb10
-rw-r--r--app/graphql/mutations/issues/set_locked.rb26
-rw-r--r--app/graphql/mutations/jira_import/start.rb9
-rw-r--r--app/graphql/mutations/merge_requests/update.rb39
-rw-r--r--app/graphql/mutations/notes/create/base.rb8
-rw-r--r--app/graphql/mutations/snippets/create.rb26
-rw-r--r--app/graphql/mutations/snippets/update.rb18
-rw-r--r--app/graphql/mutations/todos/mark_all_done.rb6
-rw-r--r--app/graphql/mutations/todos/restore_many.rb8
-rw-r--r--app/graphql/resolvers/base_resolver.rb5
-rw-r--r--app/graphql/resolvers/ci_configuration/sast_resolver.rb17
-rw-r--r--app/graphql/resolvers/concerns/resolves_merge_requests.rb28
-rw-r--r--app/graphql/resolvers/environments_resolver.rb2
-rw-r--r--app/graphql/resolvers/issues_resolver.rb17
-rw-r--r--app/graphql/resolvers/last_commit_resolver.rb2
-rw-r--r--app/graphql/resolvers/milestone_resolver.rb6
-rw-r--r--app/graphql/resolvers/packages_resolver.rb19
-rw-r--r--app/graphql/resolvers/projects/jira_projects_resolver.rb42
-rw-r--r--app/graphql/resolvers/projects_resolver.rb2
-rw-r--r--app/graphql/resolvers/release_resolver.rb2
-rw-r--r--app/graphql/resolvers/releases_resolver.rb2
-rw-r--r--app/graphql/types/alert_management/alert_sort_enum.rb8
-rw-r--r--app/graphql/types/alert_management/alert_type.rb6
-rw-r--r--app/graphql/types/ci_configuration/sast/analyzers_entity_type.rb25
-rw-r--r--app/graphql/types/ci_configuration/sast/entity_type.rb34
-rw-r--r--app/graphql/types/ci_configuration/sast/options_entity_type.rb19
-rw-r--r--app/graphql/types/ci_configuration/sast/type.rb22
-rw-r--r--app/graphql/types/container_expiration_policy_type.rb4
-rw-r--r--app/graphql/types/deprecated_mutations.rb19
-rw-r--r--app/graphql/types/diff_stats_summary_type.rb25
-rw-r--r--app/graphql/types/diff_stats_type.rb19
-rw-r--r--app/graphql/types/error_tracking/sentry_detailed_error_type.rb8
-rw-r--r--app/graphql/types/error_tracking/sentry_error_collection_type.rb2
-rw-r--r--app/graphql/types/global_id_type.rb64
-rw-r--r--app/graphql/types/issue_connection_type.rb13
-rw-r--r--app/graphql/types/issue_type.rb4
-rw-r--r--app/graphql/types/jira_user_type.rb6
-rw-r--r--app/graphql/types/jira_users_mapping_input_type.rb18
-rw-r--r--app/graphql/types/merge_request_type.rb26
-rw-r--r--app/graphql/types/milestone_stats_type.rb16
-rw-r--r--app/graphql/types/milestone_type.rb11
-rw-r--r--app/graphql/types/mutation_type.rb4
-rw-r--r--app/graphql/types/namespace_type.rb2
-rw-r--r--app/graphql/types/notes/note_type.rb6
-rw-r--r--app/graphql/types/package_type.rb16
-rw-r--r--app/graphql/types/package_type_enum.rb9
-rw-r--r--app/graphql/types/project_statistics_type.rb2
-rw-r--r--app/graphql/types/project_type.rb19
-rw-r--r--app/graphql/types/projects/services/jira_service_type.rb2
-rw-r--r--app/graphql/types/query_type.rb4
-rw-r--r--app/graphql/types/release_asset_link_type.rb21
-rw-r--r--app/graphql/types/release_asset_link_type_enum.rb12
-rw-r--r--app/graphql/types/release_assets_type.rb5
-rw-r--r--app/graphql/types/release_link_type.rb20
-rw-r--r--app/graphql/types/release_link_type_enum.rb12
-rw-r--r--app/graphql/types/release_links_type.rb23
-rw-r--r--app/graphql/types/release_source_type.rb3
-rw-r--r--app/graphql/types/release_type.rb14
-rw-r--r--app/graphql/types/root_storage_statistics_type.rb1
-rw-r--r--app/graphql/types/todo_target_enum.rb1
-rw-r--r--app/graphql/types/tree/blob_type.rb2
-rw-r--r--app/graphql/types/untrusted_regexp.rb22
-rw-r--r--app/helpers/analytics/unique_visits_helper.rb32
-rw-r--r--app/helpers/application_helper.rb9
-rw-r--r--app/helpers/application_settings_helper.rb10
-rw-r--r--app/helpers/auto_devops_helper.rb4
-rw-r--r--app/helpers/blob_helper.rb3
-rw-r--r--app/helpers/builds_helper.rb38
-rw-r--r--app/helpers/ci/builds_helper.rb40
-rw-r--r--app/helpers/ci/jobs_helper.rb23
-rw-r--r--app/helpers/ci/pipeline_schedules_helper.rb15
-rw-r--r--app/helpers/ci/runners_helper.rb45
-rw-r--r--app/helpers/ci/status_helper.rb148
-rw-r--r--app/helpers/ci/variables_helper.rb54
-rw-r--r--app/helpers/ci_status_helper.rb146
-rw-r--r--app/helpers/ci_variables_helper.rb52
-rw-r--r--app/helpers/clusters_helper.rb8
-rw-r--r--app/helpers/commits_helper.rb12
-rw-r--r--app/helpers/cookies_helper.rb16
-rw-r--r--app/helpers/dashboard_helper.rb2
-rw-r--r--app/helpers/diff_helper.rb7
-rw-r--r--app/helpers/dropdowns_helper.rb5
-rw-r--r--app/helpers/environments_helper.rb26
-rw-r--r--app/helpers/events_helper.rb64
-rw-r--r--app/helpers/export_helper.rb2
-rw-r--r--app/helpers/gitlab_routing_helper.rb30
-rw-r--r--app/helpers/groups_helper.rb4
-rw-r--r--app/helpers/icons_helper.rb19
-rw-r--r--app/helpers/ide_helper.rb4
-rw-r--r--app/helpers/import_helper.rb6
-rw-r--r--app/helpers/issuables_helper.rb17
-rw-r--r--app/helpers/issues_helper.rb7
-rw-r--r--app/helpers/jobs_helper.rb19
-rw-r--r--app/helpers/markup_helper.rb1
-rw-r--r--app/helpers/members_helper.rb8
-rw-r--r--app/helpers/merge_requests_helper.rb2
-rw-r--r--app/helpers/namespaces_helper.rb39
-rw-r--r--app/helpers/nav_helper.rb2
-rw-r--r--app/helpers/notify_helper.rb11
-rw-r--r--app/helpers/onboarding_experiment_helper.rb9
-rw-r--r--app/helpers/operations_helper.rb54
-rw-r--r--app/helpers/pipeline_schedules_helper.rb13
-rw-r--r--app/helpers/preferences_helper.rb5
-rw-r--r--app/helpers/projects/alert_management_helper.rb16
-rw-r--r--app/helpers/projects_helper.rb35
-rw-r--r--app/helpers/releases_helper.rb27
-rw-r--r--app/helpers/runners_helper.rb43
-rw-r--r--app/helpers/search_helper.rb109
-rw-r--r--app/helpers/services_helper.rb75
-rw-r--r--app/helpers/storage_helper.rb5
-rw-r--r--app/helpers/system_note_helper.rb4
-rw-r--r--app/helpers/todos_helper.rb20
-rw-r--r--app/helpers/tree_helper.rb2
-rw-r--r--app/helpers/wiki_helper.rb44
-rw-r--r--app/mailers/emails/merge_requests.rb7
-rw-r--r--app/mailers/emails/service_desk.rb92
-rw-r--r--app/mailers/notify.rb1
-rw-r--r--app/mailers/previews/notify_preview.rb16
-rw-r--r--app/models/active_session.rb17
-rw-r--r--app/models/alert_management/alert.rb50
-rw-r--r--app/models/application_record.rb8
-rw-r--r--app/models/application_setting_implementation.rb10
-rw-r--r--app/models/approval.rb11
-rw-r--r--app/models/audit_event.rb18
-rw-r--r--app/models/blob_viewer/image.rb2
-rw-r--r--app/models/blob_viewer/notebook.rb2
-rw-r--r--app/models/blob_viewer/open_api.rb4
-rw-r--r--app/models/blob_viewer/rich.rb2
-rw-r--r--app/models/blob_viewer/svg.rb2
-rw-r--r--app/models/ci/build.rb22
-rw-r--r--app/models/ci/build_metadata.rb1
-rw-r--r--app/models/ci/build_need.rb2
-rw-r--r--app/models/ci/build_trace.rb26
-rw-r--r--app/models/ci/build_trace_chunks/redis.rb5
-rw-r--r--app/models/ci/instance_variable.rb8
-rw-r--r--app/models/ci/job_artifact.rb53
-rw-r--r--app/models/ci/pipeline.rb144
-rw-r--r--app/models/ci/pipeline_enums.rb5
-rw-r--r--app/models/ci/pipeline_message.rb25
-rw-r--r--app/models/ci/ref.rb2
-rw-r--r--app/models/ci/runner.rb4
-rw-r--r--app/models/ci/stage.rb6
-rw-r--r--app/models/ci/variable.rb2
-rw-r--r--app/models/clusters/applications/cilium.rb21
-rw-r--r--app/models/clusters/applications/prometheus.rb3
-rw-r--r--app/models/clusters/applications/runner.rb2
-rw-r--r--app/models/clusters/cluster.rb71
-rw-r--r--app/models/clusters/platforms/kubernetes.rb11
-rw-r--r--app/models/commit.rb6
-rw-r--r--app/models/commit_collection.rb11
-rw-r--r--app/models/commit_status.rb3
-rw-r--r--app/models/concerns/analytics/cycle_analytics/stage.rb2
-rw-r--r--app/models/concerns/approvable_base.rb16
-rw-r--r--app/models/concerns/avatarable.rb6
-rw-r--r--app/models/concerns/bulk_insert_safe.rb8
-rw-r--r--app/models/concerns/ci/contextable.rb2
-rw-r--r--app/models/concerns/ci/has_status.rb168
-rw-r--r--app/models/concerns/ci/metadatable.rb2
-rw-r--r--app/models/concerns/deployment_platform.rb22
-rw-r--r--app/models/concerns/has_repository.rb8
-rw-r--r--app/models/concerns/has_status.rb166
-rw-r--r--app/models/concerns/integration.rb14
-rw-r--r--app/models/concerns/issuable.rb6
-rw-r--r--app/models/concerns/noteable.rb4
-rw-r--r--app/models/concerns/partitioned_table.rb21
-rw-r--r--app/models/concerns/reactive_caching.rb8
-rw-r--r--app/models/concerns/routable.rb9
-rw-r--r--app/models/concerns/update_project_statistics.rb12
-rw-r--r--app/models/custom_emoji.rb22
-rw-r--r--app/models/deploy_keys_project.rb1
-rw-r--r--app/models/diff_viewer/image.rb2
-rw-r--r--app/models/environment.rb23
-rw-r--r--app/models/epic.rb2
-rw-r--r--app/models/event.rb4
-rw-r--r--app/models/event_collection.rb9
-rw-r--r--app/models/group.rb53
-rw-r--r--app/models/incident_management/project_incident_management_setting.rb19
-rw-r--r--app/models/issue.rb6
-rw-r--r--app/models/issue_assignee.rb5
-rw-r--r--app/models/iteration.rb5
-rw-r--r--app/models/label.rb4
-rw-r--r--app/models/lfs_objects_project.rb2
-rw-r--r--app/models/member.rb9
-rw-r--r--app/models/members/group_member.rb9
-rw-r--r--app/models/merge_request.rb71
-rw-r--r--app/models/merge_request_assignee.rb4
-rw-r--r--app/models/merge_request_diff.rb8
-rw-r--r--app/models/namespace.rb17
-rw-r--r--app/models/namespace/root_storage_size.rb31
-rw-r--r--app/models/namespace/root_storage_statistics.rb26
-rw-r--r--app/models/namespace/traversal_hierarchy.rb84
-rw-r--r--app/models/namespace_setting.rb9
-rw-r--r--app/models/note.rb9
-rw-r--r--app/models/packages.rb6
-rw-r--r--app/models/packages/build_info.rb6
-rw-r--r--app/models/packages/composer/metadatum.rb14
-rw-r--r--app/models/packages/conan.rb8
-rw-r--r--app/models/packages/conan/file_metadatum.rb32
-rw-r--r--app/models/packages/conan/metadatum.rb41
-rw-r--r--app/models/packages/dependency.rb47
-rw-r--r--app/models/packages/dependency_link.rb19
-rw-r--r--app/models/packages/go/module.rb93
-rw-r--r--app/models/packages/go/module_version.rb115
-rw-r--r--app/models/packages/maven.rb8
-rw-r--r--app/models/packages/maven/metadatum.rb28
-rw-r--r--app/models/packages/nuget.rb8
-rw-r--r--app/models/packages/nuget/dependency_link_metadatum.rb19
-rw-r--r--app/models/packages/nuget/metadatum.rb27
-rw-r--r--app/models/packages/package.rb195
-rw-r--r--app/models/packages/package_file.rb56
-rw-r--r--app/models/packages/pypi.rb8
-rw-r--r--app/models/packages/pypi/metadatum.rb19
-rw-r--r--app/models/packages/sem_ver.rb54
-rw-r--r--app/models/packages/tag.rb18
-rw-r--r--app/models/performance_monitoring/prometheus_dashboard.rb20
-rw-r--r--app/models/performance_monitoring/prometheus_panel.rb11
-rw-r--r--app/models/performance_monitoring/prometheus_panel_group.rb11
-rw-r--r--app/models/personal_access_token.rb8
-rw-r--r--app/models/plan.rb2
-rw-r--r--app/models/plan_limits.rb33
-rw-r--r--app/models/product_analytics_event.rb22
-rw-r--r--app/models/project.rb100
-rw-r--r--app/models/project_services/alerts_service.rb2
-rw-r--r--app/models/project_services/bugzilla_service.rb4
-rw-r--r--app/models/project_services/confluence_service.rb91
-rw-r--r--app/models/project_services/custom_issue_tracker_service.rb6
-rw-r--r--app/models/project_services/gitlab_issue_tracker_service.rb4
-rw-r--r--app/models/project_services/issue_tracker_service.rb32
-rw-r--r--app/models/project_services/jira_service.rb12
-rw-r--r--app/models/project_services/prometheus_service.rb44
-rw-r--r--app/models/project_services/redmine_service.rb4
-rw-r--r--app/models/project_services/youtrack_service.rb5
-rw-r--r--app/models/project_setting.rb15
-rw-r--r--app/models/project_statistics.rb38
-rw-r--r--app/models/prometheus_alert.rb1
-rw-r--r--app/models/prometheus_metric.rb1
-rw-r--r--app/models/repository.rb21
-rw-r--r--app/models/resource_event.rb1
-rw-r--r--app/models/resource_state_event.rb6
-rw-r--r--app/models/service.rb38
-rw-r--r--app/models/service_desk_setting.rb30
-rw-r--r--app/models/snippet.rb13
-rw-r--r--app/models/snippet_input_action.rb9
-rw-r--r--app/models/snippet_statistics.rb69
-rw-r--r--app/models/state_note.rb34
-rw-r--r--app/models/suggestion.rb21
-rw-r--r--app/models/synthetic_note.rb18
-rw-r--r--app/models/system_note_metadata.rb3
-rw-r--r--app/models/todo.rb24
-rw-r--r--app/models/user.rb47
-rw-r--r--app/models/user_callout_enums.rb3
-rw-r--r--app/models/user_detail.rb26
-rw-r--r--app/models/webauthn_registration.rb11
-rw-r--r--app/models/wiki_page.rb4
-rw-r--r--app/policies/base_policy.rb6
-rw-r--r--app/policies/concerns/find_group_projects.rb4
-rw-r--r--app/policies/concerns/policy_actor.rb4
-rw-r--r--app/policies/global_policy.rb3
-rw-r--r--app/policies/group_policy.rb17
-rw-r--r--app/policies/merge_request_policy.rb4
-rw-r--r--app/policies/packages/package_policy.rb6
-rw-r--r--app/policies/project_member_policy.rb5
-rw-r--r--app/policies/project_policy.rb28
-rw-r--r--app/policies/releases/source_policy.rb6
-rw-r--r--app/presenters/alert_management/alert_presenter.rb101
-rw-r--r--app/presenters/alert_management/prometheus_alert_presenter.rb27
-rw-r--r--app/presenters/ci/pipeline_presenter.rb11
-rw-r--r--app/presenters/clusterable_presenter.rb20
-rw-r--r--app/presenters/clusters/cluster_presenter.rb42
-rw-r--r--app/presenters/group_clusterable_presenter.rb4
-rw-r--r--app/presenters/instance_clusterable_presenter.rb4
-rw-r--r--app/presenters/merge_request_presenter.rb18
-rw-r--r--app/presenters/packages/composer/packages_presenter.rb71
-rw-r--r--app/presenters/packages/conan/package_presenter.rb114
-rw-r--r--app/presenters/packages/detail/package_presenter.rb75
-rw-r--r--app/presenters/packages/go/module_version_presenter.rb19
-rw-r--r--app/presenters/packages/npm/package_presenter.rb87
-rw-r--r--app/presenters/packages/nuget/package_metadata_presenter.rb25
-rw-r--r--app/presenters/packages/nuget/packages_metadata_presenter.rb63
-rw-r--r--app/presenters/packages/nuget/packages_versions_presenter.rb15
-rw-r--r--app/presenters/packages/nuget/presenter_helpers.rb113
-rw-r--r--app/presenters/packages/nuget/search_results_presenter.rb56
-rw-r--r--app/presenters/packages/nuget/service_index_presenter.rb85
-rw-r--r--app/presenters/packages/pypi/package_presenter.rb75
-rw-r--r--app/presenters/project_clusterable_presenter.rb4
-rw-r--r--app/presenters/project_presenter.rb2
-rw-r--r--app/presenters/projects/prometheus/alert_presenter.rb38
-rw-r--r--app/presenters/release_presenter.rb14
-rw-r--r--app/presenters/snippet_blob_presenter.rb14
-rw-r--r--app/serializers/build_trace_entity.rb3
-rw-r--r--app/serializers/ci/group_variable_entity.rb6
-rw-r--r--app/serializers/ci/group_variable_serializer.rb7
-rw-r--r--app/serializers/ci/variable_entity.rb7
-rw-r--r--app/serializers/ci/variable_serializer.rb7
-rw-r--r--app/serializers/cluster_application_entity.rb2
-rw-r--r--app/serializers/cluster_entity.rb4
-rw-r--r--app/serializers/cluster_serializer.rb1
-rw-r--r--app/serializers/deploy_key_entity.rb5
-rw-r--r--app/serializers/diff_file_base_entity.rb8
-rw-r--r--app/serializers/evidences/release_entity.rb2
-rw-r--r--app/serializers/fork_namespace_entity.rb52
-rw-r--r--app/serializers/fork_namespace_serializer.rb5
-rw-r--r--app/serializers/group_variable_entity.rb4
-rw-r--r--app/serializers/group_variable_serializer.rb5
-rw-r--r--app/serializers/merge_request_poll_cached_widget_entity.rb24
-rw-r--r--app/serializers/merge_request_poll_widget_entity.rb16
-rw-r--r--app/serializers/merge_request_widget_entity.rb34
-rw-r--r--app/serializers/pipeline_entity.rb4
-rw-r--r--app/serializers/pipeline_serializer.rb4
-rw-r--r--app/serializers/service_event_entity.rb2
-rw-r--r--app/serializers/service_field_entity.rb2
-rw-r--r--app/serializers/stage_entity.rb4
-rw-r--r--app/serializers/suggestion_entity.rb31
-rw-r--r--app/serializers/test_report_summary_entity.rb7
-rw-r--r--app/serializers/test_report_summary_serializer.rb5
-rw-r--r--app/serializers/test_suite_entity.rb8
-rw-r--r--app/serializers/test_suite_serializer.rb5
-rw-r--r--app/serializers/test_suite_summary_entity.rb7
-rw-r--r--app/serializers/triggered_pipeline_entity.rb8
-rw-r--r--app/serializers/variable_entity.rb5
-rw-r--r--app/serializers/variable_serializer.rb5
-rw-r--r--app/services/access_token_validation_service.rb10
-rw-r--r--app/services/admin/propagate_integration_service.rb18
-rw-r--r--app/services/alert_management/alerts/todo/create_service.rb51
-rw-r--r--app/services/alert_management/alerts/update_service.rb123
-rw-r--r--app/services/alert_management/create_alert_issue_service.rb65
-rw-r--r--app/services/alert_management/process_prometheus_alert_service.rb18
-rw-r--r--app/services/alert_management/update_alert_status_service.rb63
-rw-r--r--app/services/audit_event_service.rb4
-rw-r--r--app/services/authorized_project_update/project_create_service.rb2
-rw-r--r--app/services/authorized_project_update/project_group_link_create_service.rb70
-rw-r--r--app/services/auto_merge/base_service.rb6
-rw-r--r--app/services/auto_merge/merge_when_pipeline_succeeds_service.rb8
-rw-r--r--app/services/branches/delete_service.rb7
-rw-r--r--app/services/ci/authorize_job_artifact_service.rb53
-rw-r--r--app/services/ci/create_job_artifacts_service.rb122
-rw-r--r--app/services/ci/create_pipeline_service.rb29
-rw-r--r--app/services/ci/destroy_expired_job_artifacts_service.rb2
-rw-r--r--app/services/ci/pipeline_processing/atomic_processing_service.rb2
-rw-r--r--app/services/ci/pipeline_processing/atomic_processing_service/status_collection.rb2
-rw-r--r--app/services/ci/pipeline_processing/legacy_processing_service.rb4
-rw-r--r--app/services/ci/process_pipeline_service.rb10
-rw-r--r--app/services/ci/register_job_service.rb19
-rw-r--r--app/services/ci/retry_build_service.rb8
-rw-r--r--app/services/ci/unlock_artifacts_service.rb33
-rw-r--r--app/services/clusters/create_service.rb11
-rw-r--r--app/services/clusters/parse_cluster_applications_artifact_service.rb2
-rw-r--r--app/services/concerns/exclusive_lease_guard.rb2
-rw-r--r--app/services/concerns/incident_management/settings.rb2
-rw-r--r--app/services/deploy_keys/collect_keys_service.rb27
-rw-r--r--app/services/event_create_service.rb58
-rw-r--r--app/services/files/base_service.rb2
-rw-r--r--app/services/git/branch_push_service.rb7
-rw-r--r--app/services/git/tag_push_service.rb18
-rw-r--r--app/services/git/wiki_push_service.rb2
-rw-r--r--app/services/gpg_keys/destroy_service.rb9
-rw-r--r--app/services/groups/create_service.rb10
-rw-r--r--app/services/groups/update_shared_runners_service.rb50
-rw-r--r--app/services/import/bitbucket_server_service.rb104
-rw-r--r--app/services/incident_management/create_incident_label_service.rb40
-rw-r--r--app/services/incident_management/create_issue_service.rb55
-rw-r--r--app/services/incident_management/pager_duty/create_incident_issue_service.rb72
-rw-r--r--app/services/incident_management/pager_duty/process_webhook_service.rb71
-rw-r--r--app/services/issuable/bulk_update_service.rb53
-rw-r--r--app/services/issuable_base_service.rb39
-rw-r--r--app/services/issues/move_service.rb11
-rw-r--r--app/services/jira/requests/base.rb22
-rw-r--r--app/services/jira/requests/projects.rb32
-rw-r--r--app/services/jira/requests/projects/list_service.rb47
-rw-r--r--app/services/jira_import/start_import_service.rb20
-rw-r--r--app/services/jira_import/users_mapper.rb7
-rw-r--r--app/services/labels/available_labels_service.rb2
-rw-r--r--app/services/labels/transfer_service.rb25
-rw-r--r--app/services/members/create_service.rb2
-rw-r--r--app/services/members/destroy_service.rb29
-rw-r--r--app/services/members/unassign_issuables_service.rb23
-rw-r--r--app/services/merge_requests/approval_service.rb56
-rw-r--r--app/services/merge_requests/base_service.rb16
-rw-r--r--app/services/merge_requests/create_pipeline_service.rb20
-rw-r--r--app/services/merge_requests/create_service.rb6
-rw-r--r--app/services/merge_requests/ff_merge_service.rb2
-rw-r--r--app/services/merge_requests/merge_base_service.rb2
-rw-r--r--app/services/merge_requests/merge_service.rb3
-rw-r--r--app/services/merge_requests/post_merge_service.rb1
-rw-r--r--app/services/merge_requests/remove_approval_service.rb43
-rw-r--r--app/services/merge_requests/squash_service.rb7
-rw-r--r--app/services/merge_requests/update_service.rb56
-rw-r--r--app/services/metrics/dashboard/base_service.rb22
-rw-r--r--app/services/metrics/dashboard/clone_dashboard_service.rb66
-rw-r--r--app/services/metrics/dashboard/cluster_dashboard_service.rb40
-rw-r--r--app/services/metrics/dashboard/cluster_metrics_embed_service.rb37
-rw-r--r--app/services/metrics/dashboard/custom_dashboard_service.rb5
-rw-r--r--app/services/metrics/dashboard/gitlab_alert_embed_service.rb2
-rw-r--r--app/services/metrics/dashboard/grafana_metric_embed_service.rb6
-rw-r--r--app/services/metrics/dashboard/pod_dashboard_service.rb9
-rw-r--r--app/services/metrics/dashboard/predefined_dashboard_service.rb15
-rw-r--r--app/services/metrics/dashboard/self_monitoring_dashboard_service.rb15
-rw-r--r--app/services/metrics/dashboard/system_dashboard_service.rb15
-rw-r--r--app/services/metrics/dashboard/transient_embed_service.rb4
-rw-r--r--app/services/namespaces/check_storage_size_service.rb95
-rw-r--r--app/services/notes/post_process_service.rb22
-rw-r--r--app/services/notes/quick_actions_service.rb2
-rw-r--r--app/services/notes/update_service.rb10
-rw-r--r--app/services/notification_service.rb24
-rw-r--r--app/services/packages/composer/composer_json_service.rb31
-rw-r--r--app/services/packages/composer/create_package_service.rb57
-rw-r--r--app/services/packages/composer/version_parser_service.rb33
-rw-r--r--app/services/packages/conan/create_package_file_service.rb31
-rw-r--r--app/services/packages/conan/create_package_service.rb19
-rw-r--r--app/services/packages/conan/search_service.rb58
-rw-r--r--app/services/packages/create_dependency_service.rb82
-rw-r--r--app/services/packages/create_package_file_service.rb22
-rw-r--r--app/services/packages/maven/create_package_service.rb28
-rw-r--r--app/services/packages/maven/find_or_create_package_service.rb41
-rw-r--r--app/services/packages/npm/create_package_service.rb91
-rw-r--r--app/services/packages/npm/create_tag_service.rb34
-rw-r--r--app/services/packages/nuget/create_dependency_service.rb71
-rw-r--r--app/services/packages/nuget/create_package_service.rb23
-rw-r--r--app/services/packages/nuget/metadata_extraction_service.rb106
-rw-r--r--app/services/packages/nuget/search_service.rb101
-rw-r--r--app/services/packages/nuget/sync_metadatum_service.rb50
-rw-r--r--app/services/packages/nuget/update_package_from_metadata_service.rb125
-rw-r--r--app/services/packages/pypi/create_package_service.rb40
-rw-r--r--app/services/packages/remove_tag_service.rb16
-rw-r--r--app/services/packages/update_tags_service.rb41
-rw-r--r--app/services/personal_access_tokens/last_used_service.rb28
-rw-r--r--app/services/post_receive_service.rb15
-rw-r--r--app/services/projects/after_import_service.rb2
-rw-r--r--app/services/projects/alerting/notify_service.rb9
-rw-r--r--app/services/projects/batch_forks_count_service.rb23
-rw-r--r--app/services/projects/container_repository/delete_tags_service.rb25
-rw-r--r--app/services/projects/create_service.rb17
-rw-r--r--app/services/projects/forks_count_service.rb2
-rw-r--r--app/services/projects/group_links/create_service.rb22
-rw-r--r--app/services/projects/operations/update_service.rb13
-rw-r--r--app/services/projects/prometheus/alerts/create_events_service.rb71
-rw-r--r--app/services/projects/prometheus/alerts/notify_service.rb15
-rw-r--r--app/services/projects/propagate_service_template.rb18
-rw-r--r--app/services/projects/update_remote_mirror_service.rb2
-rw-r--r--app/services/projects/update_repository_storage_service.rb22
-rw-r--r--app/services/prometheus/proxy_service.rb10
-rw-r--r--app/services/prometheus/proxy_variable_substitution_service.rb42
-rw-r--r--app/services/releases/create_evidence_service.rb10
-rw-r--r--app/services/repositories/base_service.rb5
-rw-r--r--app/services/repositories/destroy_service.rb11
-rw-r--r--app/services/repositories/shell_destroy_service.rb2
-rw-r--r--app/services/resource_access_tokens/create_service.rb10
-rw-r--r--app/services/resource_access_tokens/revoke_service.rb2
-rw-r--r--app/services/resource_events/base_synthetic_notes_builder_service.rb20
-rw-r--r--app/services/resource_events/change_state_service.rb38
-rw-r--r--app/services/resource_events/synthetic_label_notes_builder_service.rb2
-rw-r--r--app/services/resource_events/synthetic_milestone_notes_builder_service.rb2
-rw-r--r--app/services/resource_events/synthetic_state_notes_builder_service.rb2
-rw-r--r--app/services/service_desk_settings/update_service.rb19
-rw-r--r--app/services/snippets/base_service.rb20
-rw-r--r--app/services/snippets/create_service.rb10
-rw-r--r--app/services/snippets/update_service.rb7
-rw-r--r--app/services/snippets/update_statistics_service.rb28
-rw-r--r--app/services/spam/spam_verdict_service.rb11
-rw-r--r--app/services/system_note_service.rb32
-rw-r--r--app/services/system_notes/alert_management_service.rb37
-rw-r--r--app/services/system_notes/issuables_service.rb25
-rw-r--r--app/services/system_notes/merge_requests_service.rb21
-rw-r--r--app/services/tags/destroy_service.rb8
-rw-r--r--app/services/terraform/remote_state_handler.rb45
-rw-r--r--app/services/todo_service.rb6
-rw-r--r--app/services/update_container_registry_info_service.rb24
-rw-r--r--app/services/users/block_service.rb2
-rw-r--r--app/services/wiki_pages/base_service.rb2
-rw-r--r--app/services/wiki_pages/event_create_service.rb2
-rw-r--r--app/uploaders/object_storage.rb6
-rw-r--r--app/uploaders/packages/package_file_uploader.rb30
-rw-r--r--app/validators/addressable_url_validator.rb6
-rw-r--r--app/validators/array_members_validator.rb21
-rw-r--r--app/validators/json_schemas/build_metadata_secrets.json30
-rw-r--r--app/validators/json_schemas/security_ci_configuration_schemas/sast_ui_schema.json153
-rw-r--r--app/views/admin/appearances/_form.html.haml4
-rw-r--r--app/views/admin/appearances/show.html.haml2
-rw-r--r--app/views/admin/application_settings/_account_and_limit.html.haml4
-rw-r--r--app/views/admin/application_settings/_ci_cd.html.haml2
-rw-r--r--app/views/admin/application_settings/_email.html.haml2
-rw-r--r--app/views/admin/application_settings/_import_export_limits.html.haml34
-rw-r--r--app/views/admin/application_settings/_initial_branch_name.html.haml12
-rw-r--r--app/views/admin/application_settings/_registry.html.haml2
-rw-r--r--app/views/admin/application_settings/_repository_storage.html.haml2
-rw-r--r--app/views/admin/application_settings/_signin.html.haml9
-rw-r--r--app/views/admin/application_settings/_usage.html.haml2
-rw-r--r--app/views/admin/application_settings/ci/_header.html.haml2
-rw-r--r--app/views/admin/application_settings/ci_cd.html.haml2
-rw-r--r--app/views/admin/application_settings/integrations.html.haml2
-rw-r--r--app/views/admin/application_settings/network.html.haml11
-rw-r--r--app/views/admin/application_settings/repository.html.haml12
-rw-r--r--app/views/admin/applications/edit.html.haml4
-rw-r--r--app/views/admin/applications/index.html.haml2
-rw-r--r--app/views/admin/applications/new.html.haml4
-rw-r--r--app/views/admin/applications/show.html.haml4
-rw-r--r--app/views/admin/background_jobs/show.html.haml2
-rw-r--r--app/views/admin/broadcast_messages/edit.html.haml4
-rw-r--r--app/views/admin/broadcast_messages/index.html.haml4
-rw-r--r--app/views/admin/dashboard/index.html.haml5
-rw-r--r--app/views/admin/deploy_keys/new.html.haml2
-rw-r--r--app/views/admin/gitaly_servers/index.html.haml1
-rw-r--r--app/views/admin/groups/index.html.haml2
-rw-r--r--app/views/admin/groups/show.html.haml2
-rw-r--r--app/views/admin/hook_logs/_index.html.haml2
-rw-r--r--app/views/admin/hook_logs/show.html.haml4
-rw-r--r--app/views/admin/hooks/_form.html.haml2
-rw-r--r--app/views/admin/hooks/edit.html.haml4
-rw-r--r--app/views/admin/hooks/index.html.haml4
-rw-r--r--app/views/admin/impersonation_tokens/index.html.haml2
-rw-r--r--app/views/admin/jobs/index.html.haml3
-rw-r--r--app/views/admin/keys/show.html.haml2
-rw-r--r--app/views/admin/projects/show.html.haml89
-rw-r--r--app/views/admin/requests_profiles/index.html.haml4
-rw-r--r--app/views/admin/runners/_runner.html.haml4
-rw-r--r--app/views/admin/runners/_sort_dropdown.html.haml2
-rw-r--r--app/views/admin/runners/index.html.haml1
-rw-r--r--app/views/admin/runners/show.html.haml1
-rw-r--r--app/views/admin/services/_form.html.haml2
-rw-r--r--app/views/admin/services/edit.html.haml5
-rw-r--r--app/views/admin/services/index.html.haml33
-rw-r--r--app/views/admin/sessions/new.html.haml2
-rw-r--r--app/views/admin/spam_logs/index.html.haml2
-rw-r--r--app/views/admin/system_info/show.html.haml6
-rw-r--r--app/views/admin/users/_access_levels.html.haml29
-rw-r--r--app/views/admin/users/_head.html.haml2
-rw-r--r--app/views/admin/users/_user_listing_note.html.haml2
-rw-r--r--app/views/admin/users/edit.html.haml2
-rw-r--r--app/views/admin/users/index.html.haml6
-rw-r--r--app/views/admin/users/keys.html.haml4
-rw-r--r--app/views/admin/users/new.html.haml2
-rw-r--r--app/views/admin/users/projects.html.haml8
-rw-r--r--app/views/admin/users/show.html.haml24
-rw-r--r--app/views/ci/group_variables/_index.html.haml4
-rw-r--r--app/views/ci/group_variables/_variable_header.html.haml4
-rw-r--r--app/views/ci/variables/_content.html.haml2
-rw-r--r--app/views/ci/variables/_environment_scope_header.html.haml2
-rw-r--r--app/views/ci/variables/_header.html.haml2
-rw-r--r--app/views/ci/variables/_index.html.haml4
-rw-r--r--app/views/ci/variables/_variable_header.html.haml6
-rw-r--r--app/views/ci/variables/_variable_row.html.haml6
-rw-r--r--app/views/clusters/clusters/_advanced_settings.html.haml4
-rw-r--r--app/views/clusters/clusters/_gcp_signup_offer_banner.html.haml2
-rw-r--r--app/views/clusters/clusters/_gitlab_integration_form.html.haml15
-rw-r--r--app/views/clusters/clusters/_health.html.haml6
-rw-r--r--app/views/clusters/clusters/_health_tab.html.haml5
-rw-r--r--app/views/clusters/clusters/_multiple_clusters_message.html.haml6
-rw-r--r--app/views/clusters/clusters/_sidebar.html.haml2
-rw-r--r--app/views/clusters/clusters/aws/_new.html.haml4
-rw-r--r--app/views/clusters/clusters/gcp/_form.html.haml13
-rw-r--r--app/views/clusters/clusters/index.html.haml11
-rw-r--r--app/views/clusters/clusters/new.html.haml2
-rw-r--r--app/views/clusters/clusters/show.html.haml4
-rw-r--r--app/views/clusters/clusters/user/_form.html.haml8
-rw-r--r--app/views/dashboard/_projects_head.html.haml4
-rw-r--r--app/views/dashboard/activity.html.haml4
-rw-r--r--app/views/dashboard/groups/index.html.haml4
-rw-r--r--app/views/dashboard/milestones/index.html.haml4
-rw-r--r--app/views/dashboard/projects/index.html.haml4
-rw-r--r--app/views/dashboard/snippets/index.html.haml4
-rw-r--r--app/views/dashboard/todos/_todo.html.haml2
-rw-r--r--app/views/dashboard/todos/index.html.haml6
-rw-r--r--app/views/devise/mailer/_confirmation_instructions_account.html.haml4
-rw-r--r--app/views/devise/mailer/_confirmation_instructions_account.text.erb5
-rw-r--r--app/views/devise/mailer/_confirmation_instructions_secondary.html.haml2
-rw-r--r--app/views/devise/mailer/_confirmation_instructions_secondary.text.erb2
-rw-r--r--app/views/devise/registrations/new.html.haml2
-rw-r--r--app/views/devise/sessions/new.html.haml2
-rw-r--r--app/views/devise/sessions/two_factor.html.haml4
-rw-r--r--app/views/devise/shared/_experimental_separate_sign_up_flow_box.html.haml3
-rw-r--r--app/views/devise/shared/_signup_box.html.haml3
-rw-r--r--app/views/discussions/_discussion.html.haml2
-rw-r--r--app/views/doorkeeper/applications/_form.html.haml2
-rw-r--r--app/views/doorkeeper/applications/index.html.haml6
-rw-r--r--app/views/doorkeeper/applications/show.html.haml2
-rw-r--r--app/views/doorkeeper/authorizations/new.html.haml2
-rw-r--r--app/views/events/_event.html.haml2
-rw-r--r--app/views/events/event/_common.html.haml10
-rw-r--r--app/views/events/event/_created_project.html.haml2
-rw-r--r--app/views/events/event/_design.html.haml11
-rw-r--r--app/views/events/event/_note.html.haml4
-rw-r--r--app/views/events/event/_push.html.haml4
-rw-r--r--app/views/events/event/_wiki.html.haml2
-rw-r--r--app/views/explore/snippets/index.html.haml4
-rw-r--r--app/views/groups/_flash_messages.html.haml2
-rw-r--r--app/views/groups/_home_panel.html.haml10
-rw-r--r--app/views/groups/activity.html.haml2
-rw-r--r--app/views/groups/edit.html.haml1
-rw-r--r--app/views/groups/group_members/index.html.haml3
-rw-r--r--app/views/groups/issues.html.haml2
-rw-r--r--app/views/groups/labels/edit.html.haml2
-rw-r--r--app/views/groups/labels/index.html.haml6
-rw-r--r--app/views/groups/merge_requests.html.haml2
-rw-r--r--app/views/groups/milestones/_form.html.haml6
-rw-r--r--app/views/groups/milestones/index.html.haml4
-rw-r--r--app/views/groups/new.html.haml2
-rw-r--r--app/views/groups/projects.html.haml5
-rw-r--r--app/views/groups/runners/_group_runners.html.haml10
-rw-r--r--app/views/groups/runners/_index.html.haml94
-rw-r--r--app/views/groups/runners/_runner.html.haml103
-rw-r--r--app/views/groups/settings/_general.html.haml2
-rw-r--r--app/views/groups/settings/_lfs.html.haml2
-rw-r--r--app/views/groups/settings/_permissions.html.haml9
-rw-r--r--app/views/groups/settings/ci_cd/_form.html.haml2
-rw-r--r--app/views/groups/settings/ci_cd/show.html.haml4
-rw-r--r--app/views/groups/show.html.haml5
-rw-r--r--app/views/help/_shortcuts.html.haml8
-rw-r--r--app/views/help/index.html.haml4
-rw-r--r--app/views/help/instance_configuration.html.haml2
-rw-r--r--app/views/help/show.html.haml2
-rw-r--r--app/views/help/ui.html.haml2
-rw-r--r--app/views/ide/_show.html.haml2
-rw-r--r--app/views/import/bitbucket/status.html.haml91
-rw-r--r--app/views/import/bitbucket_server/new.html.haml4
-rw-r--r--app/views/import/bitbucket_server/status.html.haml96
-rw-r--r--app/views/import/fogbugz/status.html.haml65
-rw-r--r--app/views/import/gitlab/status.html.haml55
-rw-r--r--app/views/import/gitlab_projects/new.html.haml5
-rw-r--r--app/views/import/manifest/new.html.haml4
-rw-r--r--app/views/import/manifest/status.html.haml4
-rw-r--r--app/views/instance_statistics/cohorts/index.html.haml3
-rw-r--r--app/views/instance_statistics/dev_ops_score/_callout.html.haml2
-rw-r--r--app/views/instance_statistics/dev_ops_score/_disabled.html.haml2
-rw-r--r--app/views/instance_statistics/dev_ops_score/index.html.haml2
-rw-r--r--app/views/invites/show.html.haml10
-rw-r--r--app/views/kaminari/gitlab/_paginator.html.haml2
-rw-r--r--app/views/kaminari/gitlab/_without_count.html.haml2
-rw-r--r--app/views/layouts/_head.html.haml3
-rw-r--r--app/views/layouts/_img_loader.html.haml17
-rw-r--r--app/views/layouts/_page.html.haml1
-rw-r--r--app/views/layouts/_search.html.haml5
-rw-r--r--app/views/layouts/_startup_js.html.haml13
-rw-r--r--app/views/layouts/application.html.haml2
-rw-r--r--app/views/layouts/header/_current_user_dropdown.html.haml1
-rw-r--r--app/views/layouts/header/_help_dropdown.html.haml1
-rw-r--r--app/views/layouts/header/_new_dropdown.haml1
-rw-r--r--app/views/layouts/nav/_dashboard.html.haml6
-rw-r--r--app/views/layouts/nav/sidebar/_admin.html.haml8
-rw-r--r--app/views/layouts/nav/sidebar/_group.html.haml5
-rw-r--r--app/views/layouts/nav/sidebar/_project.html.haml65
-rw-r--r--app/views/layouts/nav/sidebar/_wiki_link.html.haml11
-rw-r--r--app/views/layouts/service_desk.html.haml24
-rw-r--r--app/views/layouts/snippets.html.haml1
-rw-r--r--app/views/notify/closed_merge_request_email.html.haml3
-rw-r--r--app/views/notify/closed_merge_request_email.text.haml2
-rw-r--r--app/views/notify/merge_request_status_email.html.haml3
-rw-r--r--app/views/notify/merge_request_status_email.text.haml2
-rw-r--r--app/views/notify/merge_request_unmergeable_email.html.haml2
-rw-r--r--app/views/notify/merge_request_unmergeable_email.text.haml2
-rw-r--r--app/views/notify/merge_when_pipeline_succeeds_email.html.haml159
-rw-r--r--app/views/notify/merge_when_pipeline_succeeds_email.text.haml8
-rw-r--r--app/views/notify/merged_merge_request_email.html.haml2
-rw-r--r--app/views/notify/new_issue_email.html.haml2
-rw-r--r--app/views/notify/new_mention_in_merge_request_email.html.haml2
-rw-r--r--app/views/notify/push_to_merge_request_email.html.haml2
-rw-r--r--app/views/notify/push_to_merge_request_email.text.haml4
-rw-r--r--app/views/notify/resolved_all_discussions_email.html.haml3
-rw-r--r--app/views/notify/service_desk_new_note_email.html.haml5
-rw-r--r--app/views/notify/service_desk_new_note_email.text.erb6
-rw-r--r--app/views/notify/service_desk_thank_you_email.html.haml2
-rw-r--r--app/views/notify/service_desk_thank_you_email.text.erb6
-rw-r--r--app/views/profiles/_event_table.html.haml2
-rw-r--r--app/views/profiles/accounts/show.html.haml10
-rw-r--r--app/views/profiles/active_sessions/_active_session.html.haml4
-rw-r--r--app/views/profiles/active_sessions/index.html.haml4
-rw-r--r--app/views/profiles/audit_log.html.haml2
-rw-r--r--app/views/profiles/chat_names/index.html.haml2
-rw-r--r--app/views/profiles/chat_names/new.html.haml2
-rw-r--r--app/views/profiles/emails/index.html.haml10
-rw-r--r--app/views/profiles/gpg_keys/_form.html.haml2
-rw-r--r--app/views/profiles/gpg_keys/_key.html.haml6
-rw-r--r--app/views/profiles/gpg_keys/index.html.haml4
-rw-r--r--app/views/profiles/keys/_form.html.haml4
-rw-r--r--app/views/profiles/keys/_key.html.haml8
-rw-r--r--app/views/profiles/keys/_key_details.html.haml2
-rw-r--r--app/views/profiles/keys/index.html.haml6
-rw-r--r--app/views/profiles/notifications/_group_settings.html.haml2
-rw-r--r--app/views/profiles/notifications/_project_settings.html.haml2
-rw-r--r--app/views/profiles/notifications/show.html.haml6
-rw-r--r--app/views/profiles/passwords/edit.html.haml6
-rw-r--r--app/views/profiles/personal_access_tokens/index.html.haml8
-rw-r--r--app/views/profiles/preferences/show.html.haml20
-rw-r--r--app/views/profiles/show.html.haml11
-rw-r--r--app/views/profiles/two_factor_auths/_codes.html.haml2
-rw-r--r--app/views/profiles/two_factor_auths/show.html.haml14
-rw-r--r--app/views/projects/_files.html.haml23
-rw-r--r--app/views/projects/_flash_messages.html.haml2
-rw-r--r--app/views/projects/_home_panel.html.haml10
-rw-r--r--app/views/projects/_import_project_pane.html.haml6
-rw-r--r--app/views/projects/_merge_request_settings.html.haml3
-rw-r--r--app/views/projects/_merge_request_squash_options_settings.html.haml42
-rw-r--r--app/views/projects/_readme.html.haml14
-rw-r--r--app/views/projects/_remove.html.haml7
-rw-r--r--app/views/projects/_service_desk_settings.html.haml19
-rw-r--r--app/views/projects/_wiki.html.haml2
-rw-r--r--app/views/projects/artifacts/browse.html.haml2
-rw-r--r--app/views/projects/artifacts/file.html.haml2
-rw-r--r--app/views/projects/blame/show.html.haml2
-rw-r--r--app/views/projects/blob/_editor.html.haml10
-rw-r--r--app/views/projects/blob/_header_content.html.haml2
-rw-r--r--app/views/projects/blob/_viewer.html.haml2
-rw-r--r--app/views/projects/blob/_viewer_switcher.html.haml4
-rw-r--r--app/views/projects/blob/edit.html.haml9
-rw-r--r--app/views/projects/blob/new.html.haml10
-rw-r--r--app/views/projects/blob/viewers/_license.html.haml2
-rw-r--r--app/views/projects/blob/viewers/_loading.html.haml2
-rw-r--r--app/views/projects/blob/viewers/_metrics_dashboard_yml.html.haml2
-rw-r--r--app/views/projects/blob/viewers/_sketch.html.haml2
-rw-r--r--app/views/projects/blob/viewers/_stl.html.haml4
-rw-r--r--app/views/projects/branches/_branch.html.haml8
-rw-r--r--app/views/projects/branches/new.html.haml2
-rw-r--r--app/views/projects/buttons/_clone.html.haml2
-rw-r--r--app/views/projects/buttons/_download.html.haml8
-rw-r--r--app/views/projects/cleanup/_show.html.haml2
-rw-r--r--app/views/projects/commit/_commit_box.html.haml4
-rw-r--r--app/views/projects/commit/_limit_exceeded_message.html.haml2
-rw-r--r--app/views/projects/commits/show.html.haml10
-rw-r--r--app/views/projects/compare/_form.html.haml6
-rw-r--r--app/views/projects/compare/index.html.haml4
-rw-r--r--app/views/projects/confluences/show.html.haml13
-rw-r--r--app/views/projects/cycle_analytics/show.html.haml2
-rw-r--r--app/views/projects/default_branch/_show.html.haml2
-rw-r--r--app/views/projects/deploy_keys/edit.html.haml2
-rw-r--r--app/views/projects/diffs/_diffs.html.haml2
-rw-r--r--app/views/projects/diffs/_file_header.html.haml2
-rw-r--r--app/views/projects/diffs/_stats.html.haml4
-rw-r--r--app/views/projects/edit.html.haml2
-rw-r--r--app/views/projects/empty.html.haml6
-rw-r--r--app/views/projects/environments/_form.html.haml4
-rw-r--r--app/views/projects/find_file/show.html.haml4
-rw-r--r--app/views/projects/forks/_fork_button.html.haml8
-rw-r--r--app/views/projects/forks/new.html.haml6
-rw-r--r--app/views/projects/hook_logs/_index.html.haml2
-rw-r--r--app/views/projects/hook_logs/show.html.haml4
-rw-r--r--app/views/projects/hooks/edit.html.haml4
-rw-r--r--app/views/projects/hooks/index.html.haml4
-rw-r--r--app/views/projects/import/jira/show.html.haml1
-rw-r--r--app/views/projects/imports/new.html.haml2
-rw-r--r--app/views/projects/issues/_alert_moved_from_service_desk.html.haml10
-rw-r--r--app/views/projects/issues/_by_email_description.html.haml4
-rw-r--r--app/views/projects/issues/_design_management.html.haml38
-rw-r--r--app/views/projects/issues/_discussion.html.haml2
-rw-r--r--app/views/projects/issues/_issue.html.haml2
-rw-r--r--app/views/projects/issues/_issues.html.haml21
-rw-r--r--app/views/projects/issues/_nav_btns.html.haml2
-rw-r--r--app/views/projects/issues/_new_branch.html.haml2
-rw-r--r--app/views/projects/issues/_service_desk_info_content.html.haml39
-rw-r--r--app/views/projects/issues/edit.html.haml2
-rw-r--r--app/views/projects/issues/export_csv/_modal.html.haml2
-rw-r--r--app/views/projects/issues/import_csv/_button.html.haml5
-rw-r--r--app/views/projects/issues/index.html.haml2
-rw-r--r--app/views/projects/issues/service_desk.html.haml21
-rw-r--r--app/views/projects/issues/show.html.haml23
-rw-r--r--app/views/projects/jobs/index.html.haml2
-rw-r--r--app/views/projects/jobs/show.html.haml2
-rw-r--r--app/views/projects/jobs/terminal.html.haml6
-rw-r--r--app/views/projects/labels/edit.html.haml6
-rw-r--r--app/views/projects/labels/index.html.haml4
-rw-r--r--app/views/projects/labels/new.html.haml6
-rw-r--r--app/views/projects/merge_requests/_approvals_count.html.haml13
-rw-r--r--app/views/projects/merge_requests/_discussion.html.haml2
-rw-r--r--app/views/projects/merge_requests/_merge_request.html.haml2
-rw-r--r--app/views/projects/merge_requests/_mr_title.html.haml2
-rw-r--r--app/views/projects/merge_requests/_nav_btns.html.haml2
-rw-r--r--app/views/projects/merge_requests/_widget.html.haml7
-rw-r--r--app/views/projects/merge_requests/conflicts/show.html.haml2
-rw-r--r--app/views/projects/merge_requests/creations/_new_submit.html.haml4
-rw-r--r--app/views/projects/merge_requests/creations/new.html.haml6
-rw-r--r--app/views/projects/merge_requests/diffs/_commit_widget.html.haml2
-rw-r--r--app/views/projects/merge_requests/edit.html.haml2
-rw-r--r--app/views/projects/merge_requests/index.html.haml2
-rw-r--r--app/views/projects/merge_requests/invalid.html.haml2
-rw-r--r--app/views/projects/merge_requests/show.html.haml14
-rw-r--r--app/views/projects/milestones/_form.html.haml6
-rw-r--r--app/views/projects/milestones/index.html.haml2
-rw-r--r--app/views/projects/milestones/show.html.haml4
-rw-r--r--app/views/projects/mirrors/_instructions.html.haml2
-rw-r--r--app/views/projects/mirrors/_ssh_host_keys.html.haml4
-rw-r--r--app/views/projects/network/show.html.haml6
-rw-r--r--app/views/projects/new.html.haml4
-rw-r--r--app/views/projects/no_repo.html.haml3
-rw-r--r--app/views/projects/notes/_actions.html.haml2
-rw-r--r--app/views/projects/notes/_more_actions_dropdown.html.haml4
-rw-r--r--app/views/projects/pages/show.html.haml2
-rw-r--r--app/views/projects/pipeline_schedules/_pipeline_schedule.html.haml4
-rw-r--r--app/views/projects/pipelines/_stage.html.haml2
-rw-r--r--app/views/projects/pipelines/_with_tabs.html.haml8
-rw-r--r--app/views/projects/pipelines/index.html.haml1
-rw-r--r--app/views/projects/pipelines/show.html.haml4
-rw-r--r--app/views/projects/project_members/index.html.haml3
-rw-r--r--app/views/projects/project_templates/_built_in_templates.html.haml4
-rw-r--r--app/views/projects/project_templates/_project_fields_form.html.haml2
-rw-r--r--app/views/projects/protected_branches/shared/_matching_branch.html.haml2
-rw-r--r--app/views/projects/protected_branches/show.html.haml4
-rw-r--r--app/views/projects/protected_tags/_create_protected_tag.html.haml3
-rw-r--r--app/views/projects/protected_tags/shared/_create_protected_tag.html.haml2
-rw-r--r--app/views/projects/protected_tags/shared/_dropdown.html.haml2
-rw-r--r--app/views/projects/protected_tags/shared/_index.html.haml2
-rw-r--r--app/views/projects/protected_tags/shared/_matching_tag.html.haml2
-rw-r--r--app/views/projects/protected_tags/shared/_protected_tag.html.haml2
-rw-r--r--app/views/projects/protected_tags/show.html.haml4
-rw-r--r--app/views/projects/refs/logs_tree.js.haml23
-rw-r--r--app/views/projects/releases/new.html.haml3
-rw-r--r--app/views/projects/serverless/functions/index.html.haml4
-rw-r--r--app/views/projects/services/_form.html.haml11
-rw-r--r--app/views/projects/services/alerts/_help.html.haml7
-rw-r--r--app/views/projects/services/alerts/_top.html.haml8
-rw-r--r--app/views/projects/services/prometheus/_configuration_banner.html.haml4
-rw-r--r--app/views/projects/services/prometheus/_custom_metrics.html.haml2
-rw-r--r--app/views/projects/services/prometheus/_external_alerts.html.haml4
-rw-r--r--app/views/projects/services/prometheus/_help.html.haml2
-rw-r--r--app/views/projects/services/prometheus/_metrics.html.haml2
-rw-r--r--app/views/projects/services/prometheus/_show.html.haml2
-rw-r--r--app/views/projects/services/prometheus/_top.html.haml10
-rw-r--r--app/views/projects/settings/_general.html.haml2
-rw-r--r--app/views/projects/settings/access_tokens/index.html.haml2
-rw-r--r--app/views/projects/settings/ci_cd/_autodevops_form.html.haml6
-rw-r--r--app/views/projects/settings/ci_cd/_form.html.haml4
-rw-r--r--app/views/projects/settings/ci_cd/show.html.haml6
-rw-r--r--app/views/projects/settings/integrations/show.html.haml2
-rw-r--r--app/views/projects/settings/operations/_alert_management.html.haml14
-rw-r--r--app/views/projects/settings/operations/_configuration_banner.html.haml4
-rw-r--r--app/views/projects/settings/operations/_incidents.html.haml33
-rw-r--r--app/views/projects/settings/operations/_prometheus.html.haml2
-rw-r--r--app/views/projects/settings/operations/show.html.haml1
-rw-r--r--app/views/projects/show.html.haml5
-rw-r--r--app/views/projects/sidebar/_issues_service_desk.html.haml3
-rw-r--r--app/views/projects/snippets/_actions.html.haml2
-rw-r--r--app/views/projects/starrers/_starrer.html.haml2
-rw-r--r--app/views/projects/tags/_tag.html.haml4
-rw-r--r--app/views/projects/tags/index.html.haml2
-rw-r--r--app/views/projects/tags/new.html.haml10
-rw-r--r--app/views/projects/tags/releases/edit.html.haml6
-rw-r--r--app/views/projects/tags/show.html.haml8
-rw-r--r--app/views/projects/tree/_readme.html.haml2
-rw-r--r--app/views/projects/tree/_tree_commit_column.html.haml3
-rw-r--r--app/views/projects/tree/_tree_content.html.haml2
-rw-r--r--app/views/projects/tree/_tree_header.html.haml81
-rw-r--r--app/views/projects/tree/_tree_row.html.haml2
-rw-r--r--app/views/projects/tree/show.html.haml4
-rw-r--r--app/views/projects/triggers/_index.html.haml12
-rw-r--r--app/views/projects/triggers/_trigger.html.haml2
-rw-r--r--app/views/projects/triggers/edit.html.haml4
-rw-r--r--app/views/projects/wikis/git_access.html.haml3
-rw-r--r--app/views/search/_category.html.haml4
-rw-r--r--app/views/search/results/_issue.html.haml2
-rw-r--r--app/views/search/results/_merge_request.html.haml4
-rw-r--r--app/views/search/results/_note.html.haml2
-rw-r--r--app/views/search/show.html.haml2
-rw-r--r--app/views/sent_notifications/unsubscribe.html.haml4
-rw-r--r--app/views/shared/_commit_well.html.haml2
-rw-r--r--app/views/shared/_event_filter.html.haml8
-rw-r--r--app/views/shared/_field.html.haml2
-rw-r--r--app/views/shared/_issuable_meta_data.html.haml8
-rw-r--r--app/views/shared/_label_row.html.haml4
-rw-r--r--app/views/shared/_md_preview.html.haml4
-rw-r--r--app/views/shared/_milestone_expired.html.haml6
-rw-r--r--app/views/shared/_milestones_sort_dropdown.html.haml2
-rw-r--r--app/views/shared/_namespace_storage_limit_alert.html.haml26
-rw-r--r--app/views/shared/_service_settings.html.haml25
-rw-r--r--app/views/shared/_sidebar_toggle_button.html.haml4
-rw-r--r--app/views/shared/_zen.html.haml2
-rw-r--r--app/views/shared/access_tokens/_form.html.haml2
-rw-r--r--app/views/shared/access_tokens/_table.html.haml16
-rw-r--r--app/views/shared/boards/_show.html.haml1
-rw-r--r--app/views/shared/boards/components/_board.html.haml82
-rw-r--r--app/views/shared/dashboard/_no_filter_selected.html.haml2
-rw-r--r--app/views/shared/deploy_tokens/_form.html.haml2
-rw-r--r--app/views/shared/deploy_tokens/_new_deploy_token.html.haml4
-rw-r--r--app/views/shared/empty_states/_wikis.html.haml4
-rw-r--r--app/views/shared/empty_states/_wikis_layout.html.haml2
-rw-r--r--app/views/shared/empty_states/icons/_service_desk_callout.svg1
-rw-r--r--app/views/shared/empty_states/icons/_service_desk_empty_state.svg1
-rw-r--r--app/views/shared/empty_states/icons/_service_desk_setup.svg39
-rw-r--r--app/views/shared/file_hooks/_index.html.haml4
-rw-r--r--app/views/shared/form_elements/_description.html.haml12
-rw-r--r--app/views/shared/groups/_dropdown.html.haml2
-rw-r--r--app/views/shared/icons/_icon_service_desk.svg1
-rw-r--r--app/views/shared/integrations/edit.html.haml1
-rw-r--r--app/views/shared/issuable/_board_create_list_dropdown.html.haml2
-rw-r--r--app/views/shared/issuable/_bulk_update_sidebar.html.haml16
-rw-r--r--app/views/shared/issuable/_close_reopen_button.html.haml11
-rw-r--r--app/views/shared/issuable/_close_reopen_report_toggle.html.haml11
-rw-r--r--app/views/shared/issuable/_form.html.haml4
-rw-r--r--app/views/shared/issuable/_search_bar.html.haml4
-rw-r--r--app/views/shared/issuable/_sidebar.html.haml4
-rw-r--r--app/views/shared/issuable/_sort_dropdown.html.haml2
-rw-r--r--app/views/shared/issuable/form/_branch_chooser.html.haml2
-rw-r--r--app/views/shared/issuable/form/_contribution.html.haml2
-rw-r--r--app/views/shared/issuable/form/_default_templates.html.haml2
-rw-r--r--app/views/shared/issuable/form/_merge_params.html.haml23
-rw-r--r--app/views/shared/issuable/form/_title.html.haml4
-rw-r--r--app/views/shared/members/_member.html.haml14
-rw-r--r--app/views/shared/members/_requests.html.haml2
-rw-r--r--app/views/shared/milestones/_deprecation_message.html.haml2
-rw-r--r--app/views/shared/milestones/_description.html.haml5
-rw-r--r--app/views/shared/milestones/_form_dates.html.haml8
-rw-r--r--app/views/shared/milestones/_header.html.haml2
-rw-r--r--app/views/shared/milestones/_issues_tab.html.haml2
-rw-r--r--app/views/shared/milestones/_merge_requests_tab.haml2
-rw-r--r--app/views/shared/milestones/_milestone.html.haml9
-rw-r--r--app/views/shared/milestones/_sidebar.html.haml4
-rw-r--r--app/views/shared/milestones/_tab_loading.html.haml2
-rw-r--r--app/views/shared/milestones/_tabs.html.haml4
-rw-r--r--app/views/shared/milestones/_top.html.haml2
-rw-r--r--app/views/shared/notes/_comment_button.html.haml4
-rw-r--r--app/views/shared/notes/_edit_form.html.haml4
-rw-r--r--app/views/shared/notes/_form.html.haml2
-rw-r--r--app/views/shared/notes/_hints.html.haml15
-rw-r--r--app/views/shared/notes/_note.html.haml4
-rw-r--r--app/views/shared/notes/_notes_with_form.html.haml4
-rw-r--r--app/views/shared/notifications/_new_button.html.haml2
-rw-r--r--app/views/shared/projects/_edit_information.html.haml2
-rw-r--r--app/views/shared/projects/_project.html.haml25
-rw-r--r--app/views/shared/promotions/_promote_servicedesk.html.haml13
-rw-r--r--app/views/shared/runners/_runner_description.html.haml2
-rw-r--r--app/views/shared/runners/show.html.haml2
-rw-r--r--app/views/shared/snippets/_header.html.haml2
-rw-r--r--app/views/shared/snippets/_snippet.html.haml2
-rw-r--r--app/views/shared/web_hooks/_hook.html.haml2
-rw-r--r--app/views/shared/web_hooks/_index.html.haml2
-rw-r--r--app/views/shared/wikis/_form.html.haml4
-rw-r--r--app/views/shared/wikis/_pages_wiki_page.html.haml2
-rw-r--r--app/views/shared/wikis/_sidebar.html.haml8
-rw-r--r--app/views/shared/wikis/_sidebar_wiki_page.html.haml2
-rw-r--r--app/views/shared/wikis/diff.html.haml32
-rw-r--r--app/views/shared/wikis/edit.html.haml10
-rw-r--r--app/views/shared/wikis/history.html.haml57
-rw-r--r--app/views/shared/wikis/pages.html.haml4
-rw-r--r--app/views/shared/wikis/show.html.haml15
-rw-r--r--app/views/sherlock/file_samples/show.html.haml4
-rw-r--r--app/views/sherlock/queries/_backtrace.html.haml2
-rw-r--r--app/views/sherlock/queries/_general.html.haml2
-rw-r--r--app/views/sherlock/transactions/_general.html.haml2
-rw-r--r--app/views/snippets/_actions.html.haml2
-rw-r--r--app/views/snippets/new.html.haml2
-rw-r--r--app/views/snippets/notes/_actions.html.haml2
-rw-r--r--app/views/users/_overview.html.haml6
-rw-r--r--app/views/users/show.html.haml7
-rw-r--r--app/workers/all_queues.yml95
-rw-r--r--app/workers/authorized_project_update/project_group_link_create_worker.rb21
-rw-r--r--app/workers/build_finished_worker.rb1
-rw-r--r--app/workers/ci/pipeline_success_unlock_artifacts_worker.rb20
-rw-r--r--app/workers/ci/ref_delete_unlock_artifacts_worker.rb22
-rw-r--r--app/workers/concerns/project_export_options.rb25
-rw-r--r--app/workers/concerns/reenqueuer.rb6
-rw-r--r--app/workers/concerns/worker_attributes.rb68
-rw-r--r--app/workers/delete_merged_branches_worker.rb1
-rw-r--r--app/workers/gitlab/jira_import/import_issue_worker.rb2
-rw-r--r--app/workers/group_export_worker.rb1
-rw-r--r--app/workers/incident_management/pager_duty/process_incident_worker.rb42
-rw-r--r--app/workers/incident_management/process_alert_worker.rb36
-rw-r--r--app/workers/incident_management/process_prometheus_alert_worker.rb69
-rw-r--r--app/workers/members_destroyer/unassign_issuables_worker.rb32
-rw-r--r--app/workers/new_release_worker.rb18
-rw-r--r--app/workers/packages/nuget/extraction_worker.rb25
-rw-r--r--app/workers/partition_creation_worker.rb15
-rw-r--r--app/workers/pipeline_update_worker.rb4
-rw-r--r--app/workers/post_receive.rb2
-rw-r--r--app/workers/process_commit_worker.rb2
-rw-r--r--app/workers/project_export_worker.rb3
-rw-r--r--app/workers/project_update_repository_storage_worker.rb4
-rw-r--r--app/workers/repository_check/batch_worker.rb4
-rw-r--r--app/workers/repository_check/single_repository_worker.rb2
-rw-r--r--app/workers/repository_import_worker.rb3
-rw-r--r--app/workers/service_desk_email_receiver_worker.rb15
-rw-r--r--app/workers/stuck_import_jobs_worker.rb19
-rw-r--r--app/workers/update_container_registry_info_worker.rb15
-rwxr-xr-xbin/changelog21
-rwxr-xr-xbin/feature-flag291
-rwxr-xr-xbin/rspec-stackprof3
-rw-r--r--changelogs/unreleased/11805-support-first-name-and-last-name-attributes-in-ldap-user-sync.yml5
-rw-r--r--changelogs/unreleased/118613-spam-api-call.yml5
-rw-r--r--changelogs/unreleased/119018-move-alert-settings-to-Vue.yml5
-rw-r--r--changelogs/unreleased/13049-design-view-allow-comment-pins-on-designs-to-be-resolvable.yml5
-rw-r--r--changelogs/unreleased/13049-graphql-resolve-discussion.yml5
-rw-r--r--changelogs/unreleased/15242-wiki-diff.yml5
-rw-r--r--changelogs/unreleased/16239-snippet-onbeforeunload.yml5
-rw-r--r--changelogs/unreleased/16877-quick-actions-on-description-edit.yml5
-rw-r--r--changelogs/unreleased/17137.yml5
-rw-r--r--changelogs/unreleased/17555-custom-text-for-badges.yml5
-rw-r--r--changelogs/unreleased/17613-configurable-defaults-for-squash-commits-option.yml5
-rw-r--r--changelogs/unreleased/191455-add-a-button-to-assign-users-who-have-commented-on-an-issue.yml5
-rw-r--r--changelogs/unreleased/191455-add-a-button-to-quickly-assign-users-who-have-commented-on-an-issu.yml5
-rw-r--r--changelogs/unreleased/195692-too-much-x-axis-padding-on-the-environments-dashboard-content.yml5
-rw-r--r--changelogs/unreleased/195712-empty-stacktrace-on-error-details-page.yml5
-rw-r--r--changelogs/unreleased/196544-nodemetrics-size.yml5
-rw-r--r--changelogs/unreleased/196630-commit-tab.yml5
-rw-r--r--changelogs/unreleased/196784-add-container-expiration-policy-to-graphql-project.yml5
-rw-r--r--changelogs/unreleased/196784-graphql-mutations-for-container-expiration-policies.yml5
-rw-r--r--changelogs/unreleased/197426-error-details-timeago-tooltip.yml5
-rw-r--r--changelogs/unreleased/199245-search-api-seems-to-ignore-ref-returns-spurious-results-from-maste.yml5
-rw-r--r--changelogs/unreleased/199250-expose-release-yaml-as-steps-via-api-2.yml5
-rw-r--r--changelogs/unreleased/199250-release-generation-from-within-gitlab-ci-yml.yml5
-rw-r--r--changelogs/unreleased/199732-show-more-context-to-jump-unresolved-button.yml5
-rw-r--r--changelogs/unreleased/200016-display-downstream-pipeline-errors.yml5
-rw-r--r--changelogs/unreleased/20069-ci-secrets-rake-task.yml5
-rw-r--r--changelogs/unreleased/201840-link-testwidget.yml5
-rw-r--r--changelogs/unreleased/202159-open-fork.yml6
-rw-r--r--changelogs/unreleased/204839-move-update-logic-to-service.yml5
-rw-r--r--changelogs/unreleased/204904-show-clone-button-on-project-page.yml5
-rw-r--r--changelogs/unreleased/204920-add-refresh-rate-btn.yml5
-rw-r--r--changelogs/unreleased/205424-add-api-for-share-groups-with-groups.yml5
-rw-r--r--changelogs/unreleased/207257-specify-asset-types-in-releases-2.yml5
-rw-r--r--changelogs/unreleased/207257-specify-asset-types-in-releases-3.yml5
-rw-r--r--changelogs/unreleased/207257-specify-asset-types-in-releases.yml5
-rw-r--r--changelogs/unreleased/207472-create-confidential-note-api.yml5
-rw-r--r--changelogs/unreleased/207473-create-confidential-notes-graphql.yml5
-rw-r--r--changelogs/unreleased/207990-secret-detection-ci-template.yml5
-rw-r--r--changelogs/unreleased/208193-add-logs-to-container-repository-delete-tags-service.yml5
-rw-r--r--changelogs/unreleased/208412-featurable.yml5
-rw-r--r--changelogs/unreleased/208655-introduce-prepare-keyword-to-environment-action-to-annotate-non-de.yml5
-rw-r--r--changelogs/unreleased/208738-improve-performance-of-branches-list-api-when-under-load.yml5
-rw-r--r--changelogs/unreleased/209025-design-filename-limit-migrations.yml6
-rw-r--r--changelogs/unreleased/209345-add-ds-detect-kotlin-build-file.yml5
-rw-r--r--changelogs/unreleased/209786-improve-performance-of-diffs_batch-and-diffs_metadata-actions-for-.yml5
-rw-r--r--changelogs/unreleased/209786-improve-performance-of-diffs_batch-diffs-metadata-source-branch-ex.yml5
-rw-r--r--changelogs/unreleased/209912-commits-markdown-cache-preload.yml5
-rw-r--r--changelogs/unreleased/209912-memoize-sprites-icon-path.yml5
-rw-r--r--changelogs/unreleased/21002-slack-opened-merge-request-webhook-message-is-malformed.yml5
-rw-r--r--changelogs/unreleased/210281-label-for-pipeline-schedule-active.yml5
-rw-r--r--changelogs/unreleased/210482-update-descriptions-on-the-integrations-and-webhooks-pages-at-the-.yml5
-rw-r--r--changelogs/unreleased/210523-visually-align-system-bots-alert-support-and-security.yml5
-rw-r--r--changelogs/unreleased/210550-conan-export-tgz.yml5
-rw-r--r--changelogs/unreleased/211340-change-chart-legend-format-to-tabular-format.yml5
-rw-r--r--changelogs/unreleased/211443-ide-only-show-open-mrs.yml4
-rw-r--r--changelogs/unreleased/211461-destroy-annotations-graphql-endpoint.yml5
-rw-r--r--changelogs/unreleased/211828-placement-of-add-designs-button-could-be-confusing-with-the-additi.yml5
-rw-r--r--changelogs/unreleased/212063-images-overflow-at-releases-list-panel.yml5
-rw-r--r--changelogs/unreleased/212229-move-features-to-core-multiple-kubernetes-clusters.yml5
-rw-r--r--changelogs/unreleased/212811-adding-new-task-always-shows-error-something-went-wrong-while-fetc.yml5
-rw-r--r--changelogs/unreleased/212848.yml5
-rw-r--r--changelogs/unreleased/212873-fix-can-edit-logic.yml5
-rw-r--r--changelogs/unreleased/212882-add-cpu-mem-charts.yml5
-rw-r--r--changelogs/unreleased/212882-add-instance-variable.yml5
-rw-r--r--changelogs/unreleased/213009-when-filtering-by-groups-icons-are-misaligned.yml6
-rw-r--r--changelogs/unreleased/213587-ide-ipad-scroll-issue.yml5
-rw-r--r--changelogs/unreleased/213597-add-in-this-group-option-to-search-dropdown-where-not-present.yml5
-rw-r--r--changelogs/unreleased/213699-remove-search-results-autocomplete.yml5
-rw-r--r--changelogs/unreleased/213816.yml5
-rw-r--r--changelogs/unreleased/213824-update-red-variables-in-gitlab-scss-to-match-gitlab-ui.yml5
-rw-r--r--changelogs/unreleased/213881-alerts-list-pagination.yml5
-rw-r--r--changelogs/unreleased/213929-move-package-apis-to-core.yml5
-rw-r--r--changelogs/unreleased/213983-use-version-instead-of-shortversion-for-sentry.yml5
-rw-r--r--changelogs/unreleased/214039-display-filter-commit-in-mobile.yml5
-rw-r--r--changelogs/unreleased/214102-move-the-members-section-from-settings-to-the-side-nav-for-project.yml5
-rw-r--r--changelogs/unreleased/214103-remove-the-second-prompt-to-accept-or-decline-an-invitation.yml5
-rw-r--r--changelogs/unreleased/214109-date-time-format-should-be-consistent-in-the-incident.yml5
-rw-r--r--changelogs/unreleased/214250-usage-ping-counts-for-all-search.yml5
-rw-r--r--changelogs/unreleased/214281-modify-dashboard-title.yml5
-rw-r--r--changelogs/unreleased/214370-add-timezone-setting.yml5
-rw-r--r--changelogs/unreleased/214370-extend-metrics-settings.yml5
-rw-r--r--changelogs/unreleased/214493-invaid-uri.yml5
-rw-r--r--changelogs/unreleased/214539-fe-fetch-dynamic-variable-options.yml5
-rw-r--r--changelogs/unreleased/214556-user-defined-alert-identification.yml5
-rw-r--r--changelogs/unreleased/214824-fix-web-ide-open-file-race-condition.yml4
-rw-r--r--changelogs/unreleased/214905-measure-package-adoption.yml5
-rw-r--r--changelogs/unreleased/214921-improve-tabbing-behavior-when-creating-new-projects.yml5
-rw-r--r--changelogs/unreleased/215160-add-intermediate-group-deploy-key-table.yml5
-rw-r--r--changelogs/unreleased/215194-add-section-to-index_approval_rule_name_for_code_owners_rule_type.yml5
-rw-r--r--changelogs/unreleased/215497-add-custom-links-to-panel.yml5
-rw-r--r--changelogs/unreleased/215517-tab-docs-ff.yml5
-rw-r--r--changelogs/unreleased/215618_mutation_to_create_a_commit.yml5
-rw-r--r--changelogs/unreleased/215619_add_mutation_to_create_mr.yml5
-rw-r--r--changelogs/unreleased/215658-add-users-graphql.yml5
-rw-r--r--changelogs/unreleased/215658-graphql-memberships.yml5
-rw-r--r--changelogs/unreleased/215658-root-users-query.yml5
-rw-r--r--changelogs/unreleased/215668-settings-auto-fix.yml5
-rw-r--r--changelogs/unreleased/215711-improve-performance-of-search-api-advanced-users-scope.yml5
-rw-r--r--changelogs/unreleased/215946-add-gitlab-to-do-for-user-when-they-are-assigned-to-an-alert-2.yml5
-rw-r--r--changelogs/unreleased/215946-add-gitlab-to-do-for-user-when-they-are-assigned-to-an-alert.yml5
-rw-r--r--changelogs/unreleased/216022-use-pod-label.yml5
-rw-r--r--changelogs/unreleased/216045-capture-todo-resolution.yml5
-rw-r--r--changelogs/unreleased/216048-misleading-message-displays-when-mr-request-is-first-submitted.yml5
-rw-r--r--changelogs/unreleased/216088-disable-container-expiration-policy-when-invalid-regex-is-present.yml5
-rw-r--r--changelogs/unreleased/216088-regex-validation-on-container-expiration-policy.yml5
-rw-r--r--changelogs/unreleased/216097-add-application-limits-to-ci-instancevariable.yml5
-rw-r--r--changelogs/unreleased/216103-personal-access-token-pat-expiry-is-notifying-a-for-impersonation-.yml5
-rw-r--r--changelogs/unreleased/216142-resolve-alert-when-associated-issue-closes.yml5
-rw-r--r--changelogs/unreleased/216143-resolve-todo-for-user-after-alert-resolve.yml5
-rw-r--r--changelogs/unreleased/216143-show-resolved-state-in-todos-list.yml5
-rw-r--r--changelogs/unreleased/216145-frontend-create-jira-import-user-mapping-form.yml5
-rw-r--r--changelogs/unreleased/216145-graphql-import.yml5
-rw-r--r--changelogs/unreleased/216145-jira-users-import-endpoint.yml5
-rw-r--r--changelogs/unreleased/216145-project-members-graphql.yml5
-rw-r--r--changelogs/unreleased/216160-fix-label-any-with-custom-sorting.yml5
-rw-r--r--changelogs/unreleased/216174-track-mr-usage.yml5
-rw-r--r--changelogs/unreleased/216199-track-wiki-page-views.yml5
-rw-r--r--changelogs/unreleased/216216-add-rake-task-for-external-diffs-cleanup.yml5
-rw-r--r--changelogs/unreleased/216326-send-alerts-to-slack-db-settings.yml5
-rw-r--r--changelogs/unreleased/216385-add-related-dashboard-links-in-metrics-dashboard-mvc1.yml5
-rw-r--r--changelogs/unreleased/216458-migrate-mr-diffs-without-transaction.yml5
-rw-r--r--changelogs/unreleased/216631-cmd-enter-no-clear-comment-text.yml5
-rw-r--r--changelogs/unreleased/216640-insert-image-modal.yml5
-rw-r--r--changelogs/unreleased/216677-track-static-site-editor-initializations.yml5
-rw-r--r--changelogs/unreleased/216678-sse-track-merge-requests.yml5
-rw-r--r--changelogs/unreleased/216735-fix-prometheus-alerts-not-being-created.yml5
-rw-r--r--changelogs/unreleased/216749-improve-the-container-registry-ui-header-section-with-relevant-met.yml5
-rw-r--r--changelogs/unreleased/216757-add-tags-count.yml5
-rw-r--r--changelogs/unreleased/216757-include-tag-count-in-the-image-repository-list-view-of-the-contain.yml5
-rw-r--r--changelogs/unreleased/216785-terraform-plan-developer-access.yml5
-rw-r--r--changelogs/unreleased/216785-use-ci-job-token-for-terraform-state-auth.yml5
-rw-r--r--changelogs/unreleased/216797-style-toastui-menus.yml5
-rw-r--r--changelogs/unreleased/216834-frontmatter-wysiwyg-removal.yml5
-rw-r--r--changelogs/unreleased/216835-instrument-db-calls.yml6
-rw-r--r--changelogs/unreleased/216865-confirm-leave-site.yml5
-rw-r--r--changelogs/unreleased/216871-snippets-author-can-t-be-blank-error.yml5
-rw-r--r--changelogs/unreleased/216880-frontend-add-sticky-issue-titles.yml5
-rw-r--r--changelogs/unreleased/216880-frontend-url-hash-offset.yml5
-rw-r--r--changelogs/unreleased/216908-pass-limit-and-offset-when-searching-for-commits.yml5
-rw-r--r--changelogs/unreleased/216931-convert-the-image-tag-ui-from-a-table-to-a-list-view-component.yml5
-rw-r--r--changelogs/unreleased/216939-remove-async-mr-check-ff.yml5
-rw-r--r--changelogs/unreleased/216962-add-an-expandable-tag-detail-view-to-the-image-repository-detail-v.yml5
-rw-r--r--changelogs/unreleased/217034-auto-creation-of-issues-for-alerts-off-by-default.yml5
-rw-r--r--changelogs/unreleased/217105-remove-FF-hide_token_from_runners_api.yml5
-rw-r--r--changelogs/unreleased/217168-close-open-reply-input-fields-in-the-design-view-sidebar-when-leav.yml6
-rw-r--r--changelogs/unreleased/217170-when-clicking-multiple-times-to-leave-a-single-comment-the-input-f.yml6
-rw-r--r--changelogs/unreleased/217362-move-configure-stage-usage-activity-to-ce.yml5
-rw-r--r--changelogs/unreleased/217362-move-create-stage-usage-activity-to-ce.yml5
-rw-r--r--changelogs/unreleased/217362-move-manage-stage-usage-activity-to-ce.yml5
-rw-r--r--changelogs/unreleased/217362-move-plan-stage-usage-activity-to-ce.yml5
-rw-r--r--changelogs/unreleased/217362-move-release-stage-usage-activity-to-ce.yml5
-rw-r--r--changelogs/unreleased/217362-move-verify-stage-usage-activity-to-ce.yml5
-rw-r--r--changelogs/unreleased/217362-restructure-usage-ping-add-usage-activity-to-ce.yml5
-rw-r--r--changelogs/unreleased/217366-expose-jira-successfully-imported-issues-count-in-graphql.yml5
-rw-r--r--changelogs/unreleased/217392-update-workhorse-version.yml5
-rw-r--r--changelogs/unreleased/217566-add-warning-of-potential-data-loss-on-elastic-stack-upgrade-2.yml5
-rw-r--r--changelogs/unreleased/217570-improve-performance-for-blame-api.yml5
-rw-r--r--changelogs/unreleased/217587-ignore-title-description-from-services.yml5
-rw-r--r--changelogs/unreleased/217616-fix-note-confidential.yml5
-rw-r--r--changelogs/unreleased/217648-add-no-graph-empty-state-for-dag.yml5
-rw-r--r--changelogs/unreleased/217666-prometheus-api-client.yml5
-rw-r--r--changelogs/unreleased/217670-alerts-count.yml5
-rw-r--r--changelogs/unreleased/217673-keyset-paginate-notes-backend-only.yml5
-rw-r--r--changelogs/unreleased/217680-health-metric-instrumentation.yml5
-rw-r--r--changelogs/unreleased/217680-health-metrics-instrumentation.yml5
-rw-r--r--changelogs/unreleased/217692-design-view-highlight-focused-design-pins-follow-up.yml5
-rw-r--r--changelogs/unreleased/217736-add-related-dashboard-links-in-metrics-dashboard-mvc2.yml5
-rw-r--r--changelogs/unreleased/217743-match-commits-filter-author-button-to-spec.yml5
-rw-r--r--changelogs/unreleased/217748-pipeline-index-endpoint-performance.yml5
-rw-r--r--changelogs/unreleased/217758-reduce-metrics-dashboard-loading.yml6
-rw-r--r--changelogs/unreleased/217768-surface-link-to-chart.yml5
-rw-r--r--changelogs/unreleased/217786-snippet-blobs.yml5
-rw-r--r--changelogs/unreleased/217803-follow-up-from-resolve-distribute-daily-cron-schedules-out-over-th.yml5
-rw-r--r--changelogs/unreleased/217816-add-evidence-to-releases-graphql-endpoint.yml5
-rw-r--r--changelogs/unreleased/217834-remove-FF-ci_dependency_variables.yml5
-rw-r--r--changelogs/unreleased/217934-snippet-description-files.yml5
-rw-r--r--changelogs/unreleased/217936-validate-the-size-of-the-value-for-instance-level-variables.yml5
-rw-r--r--changelogs/unreleased/217985-use-glinfinitescroll-s-default-slot-in-the-project-selector-vue-co.yml5
-rw-r--r--changelogs/unreleased/218004-validate-the-not-null-constraint-on-file-store-columns.yml5
-rw-r--r--changelogs/unreleased/218007-fix-incomplete-kubernetes-cluster-status-list.yml5
-rw-r--r--changelogs/unreleased/218025-xff-is-a-400-error.yml5
-rw-r--r--changelogs/unreleased/218026-add-falco-documentation.yml5
-rw-r--r--changelogs/unreleased/218036-cannot-delete-account-on-gitlab-com.yml5
-rw-r--r--changelogs/unreleased/218040-cablett-graphql-issue-id.yml5
-rw-r--r--changelogs/unreleased/218045-feature-flag-remove-feature-flag-for-create-issue-from-alert-detai.yml5
-rw-r--r--changelogs/unreleased/218045-remove-create-issue-feature-flag-FE.yml5
-rw-r--r--changelogs/unreleased/218165-add-note-no-extend-ecs.yml5
-rw-r--r--changelogs/unreleased/218230-bugfix-save-wiki-page-modifications-with-certain-characters.yml5
-rw-r--r--changelogs/unreleased/218250-project-level-integration-implement-project-level-setting-selector.yml5
-rw-r--r--changelogs/unreleased/218257-service-templates-page-disable-service-template-when-instance-leve.yml5
-rw-r--r--changelogs/unreleased/218287-release-evidence-is-not-being-collected-if-release-is-created-via-.yml5
-rw-r--r--changelogs/unreleased/218340-graphql-haspreviouspage-and-hasnextpage.yml5
-rw-r--r--changelogs/unreleased/218414-refine-sast-analyzer-language-detection.yml5
-rw-r--r--changelogs/unreleased/218464-expiration-policy-defaults.yml5
-rw-r--r--changelogs/unreleased/218472-gitlab-ci-linting.yml5
-rw-r--r--changelogs/unreleased/218510-hide-copy-btn-for-rendering-error.yml5
-rw-r--r--changelogs/unreleased/218526_backstage_remove_gitlab_issue_tracker_service_records.yml5
-rw-r--r--changelogs/unreleased/218560-allow-generic-endpoint-to-receive-alerts-from-external-prometheus.yml5
-rw-r--r--changelogs/unreleased/218569-dont-show-import-from-jira-button-for-non-entitled-users.yml5
-rw-r--r--changelogs/unreleased/218582-fix-artifact-downloads-without-new-route.yml5
-rw-r--r--changelogs/unreleased/218648-remove-jira-httperror-non-actionable-exceptions.yml5
-rw-r--r--changelogs/unreleased/218703-fix-api-projects-search-n-plus-1.yml5
-rw-r--r--changelogs/unreleased/218707-search-criteria-for-alert-status-counts.yml5
-rw-r--r--changelogs/unreleased/218716-iterate-on-epic-tree-card-spacing.yml5
-rw-r--r--changelogs/unreleased/218733-add-manual-rollout-resource-group.yml6
-rw-r--r--changelogs/unreleased/218737-rename-container-registry-expiration-policies-to-cleanup-policy-fo.yml5
-rw-r--r--changelogs/unreleased/218756-feature-flag-enable-sectional-codeowner.yml5
-rw-r--r--changelogs/unreleased/218757-fix-polling-for-events.yml5
-rw-r--r--changelogs/unreleased/218841-extend-ecs-for-fargate.yml5
-rw-r--r--changelogs/unreleased/219002-remove-ghost-column.yml5
-rw-r--r--changelogs/unreleased/219022-fix-pipelines-apps-not-loading.yml5
-rw-r--r--changelogs/unreleased/219046-fix-readiness-probe-500-on-db-down.yml5
-rw-r--r--changelogs/unreleased/219074-safe-link-validation.yml5
-rw-r--r--changelogs/unreleased/219145-comment-button-does-not-show-up-on-mr-when-comments-are-set-to-new.yml5
-rw-r--r--changelogs/unreleased/219151-follow-up-from-fallback-to-lowest-visibility-level-in-snippet-visi.yml5
-rw-r--r--changelogs/unreleased/219210-column-date-format.yml5
-rw-r--r--changelogs/unreleased/219210-stacked-column-date-format.yml5
-rw-r--r--changelogs/unreleased/219228-add-web-ide-solarized-dark-theme-support.yml5
-rw-r--r--changelogs/unreleased/219255-vue-update.yml5
-rw-r--r--changelogs/unreleased/219331-cablett-issues-list-reopened-issue.yml5
-rw-r--r--changelogs/unreleased/219385-metrics-dashboard-elements-outline.yml5
-rw-r--r--changelogs/unreleased/219391-follow-up-from-add-type-field-to-asset-links-on-edit-release-page.yml5
-rw-r--r--changelogs/unreleased/219395-metrics-dashboard-validation-fix-500-for-empty-.yml6
-rw-r--r--changelogs/unreleased/219455-fe-inapplicable-tooltip-message.yml5
-rw-r--r--changelogs/unreleased/219539-project-access-tokens-returns-403-forbidden-your-account-has-been-.yml5
-rw-r--r--changelogs/unreleased/219558-improve-confirmation-email-language.yml5
-rw-r--r--changelogs/unreleased/219582-fix-ambiguous_string_concat_on_cleanup_projects_with_missing_names.yml5
-rw-r--r--changelogs/unreleased/219658-add-route-to-ghost-lost-and-found-group.yml5
-rw-r--r--changelogs/unreleased/219956-instrument-last-git-write-operation-per-user.yml5
-rw-r--r--changelogs/unreleased/220014-default-for-SAST_EXCLUDED_PATHS-DS_EXCLUDED_PATHS.yml5
-rw-r--r--changelogs/unreleased/220051-duplicate-issues-created-when-importing-from-csv.yml5
-rw-r--r--changelogs/unreleased/220058.yml5
-rw-r--r--changelogs/unreleased/220124-drop-temp-index-on-audit-events.yml5
-rw-r--r--changelogs/unreleased/220182-skeleton-loading.yml5
-rw-r--r--changelogs/unreleased/220185-mask-key-comments-when-exposing-ssh-deploy-keys-via-the-api.yml5
-rw-r--r--changelogs/unreleased/220192-fix-pagination-pd.yml5
-rw-r--r--changelogs/unreleased/220195-display-epics-on-swimlanes.yml5
-rw-r--r--changelogs/unreleased/220209-optimize-container-repository-query.yml5
-rw-r--r--changelogs/unreleased/220232-get-all-jira-projects.yml5
-rw-r--r--changelogs/unreleased/220300-do-not-create-duplicate-alert-issues.yml5
-rw-r--r--changelogs/unreleased/220316-redis-n-1-in-api-v4-groups-id-projects-forks-count-key.yml5
-rw-r--r--changelogs/unreleased/220318-refactor-author_name-from-auditevent-details.yml5
-rw-r--r--changelogs/unreleased/220324-add-entity-path-to-audit-events.yml5
-rw-r--r--changelogs/unreleased/220342-remove-services-from-import-export.yml5
-rw-r--r--changelogs/unreleased/220361-display-metric-label-value-in-single-stat-panel.yml5
-rw-r--r--changelogs/unreleased/220413-quickly-resolve-issues-with-your-cleanup-policy-with-improved-vali.yml5
-rw-r--r--changelogs/unreleased/220415-feature-flag-enable-alert-slack-notifications.yml5
-rw-r--r--changelogs/unreleased/220477-harden-ci-pipelines-usage-data-queries.yml5
-rw-r--r--changelogs/unreleased/220616-group-code-icons-in-toolbar.yml5
-rw-r--r--changelogs/unreleased/220785-snippet-editing-multi.yml5
-rw-r--r--changelogs/unreleased/220789-10io-add-missing-attributes-to-graphql-container-expiration-policy.yml5
-rw-r--r--changelogs/unreleased/220840-add-machine-sysname-in-topology-usageping.yml5
-rw-r--r--changelogs/unreleased/220934-confluence-wiki-db.yml5
-rw-r--r--changelogs/unreleased/220934-confluence-wiki-icon.yml5
-rw-r--r--changelogs/unreleased/220934-confluence-wiki-remove-flag.yml5
-rw-r--r--changelogs/unreleased/220935.yml5
-rw-r--r--changelogs/unreleased/220944-docs-product-feedback-example-for-set-a-deploy-freeze-seems-wrong.yml5
-rw-r--r--changelogs/unreleased/220954-replace-fa-file-image-o-with-gitlab-media-icon.yml5
-rw-r--r--changelogs/unreleased/221052-fix-custom-slashcommand-receiving-500.yml5
-rw-r--r--changelogs/unreleased/221106-chart-links-url-blocking.yml5
-rw-r--r--changelogs/unreleased/221136-docs-product-feedback-slack-notifications-service-doc-outdated.yml5
-rw-r--r--changelogs/unreleased/221174-graphql-pagination-bug.yml5
-rw-r--r--changelogs/unreleased/221184-optimize-rolling-28-counters-for-.yml5
-rw-r--r--changelogs/unreleased/221184-rolling-28-day-deployments-metrics.yml5
-rw-r--r--changelogs/unreleased/221184-rolling-28-day-snippets-optimization.yml5
-rw-r--r--changelogs/unreleased/221184-rolling-28-day-time-period-for-all-usage-ping-counters.yml5
-rw-r--r--changelogs/unreleased/221189-suppress-progress-on-pulling-image-on-auto-browser-performance-tes.yml5
-rw-r--r--changelogs/unreleased/221211-improve-performance-of-group-search-api-advanced-merge_requests-sc.yml5
-rw-r--r--changelogs/unreleased/221225-dag-basic-annotations.yml5
-rw-r--r--changelogs/unreleased/221242-change-alert-severity-and-status-sort-order.yml5
-rw-r--r--changelogs/unreleased/221242-fix-alerts-list-sorting.yml5
-rw-r--r--changelogs/unreleased/221301-update-gray-200-value-and-usages.yml5
-rw-r--r--changelogs/unreleased/222253-add-prometheus-columns-to-alert-management-alert.yml5
-rw-r--r--changelogs/unreleased/222253-close-issue-on-resolved-alert.yml6
-rw-r--r--changelogs/unreleased/222264-context-menu-single-stat.yml5
-rw-r--r--changelogs/unreleased/222313-update-usage-ping-data-to-track-projects-with-can-override-approva.yml5
-rw-r--r--changelogs/unreleased/222358-explore-removing-dependencies-on-test-stage.yml5
-rw-r--r--changelogs/unreleased/222534-replace-fa-thumbs-up-and-fa-thumbs-down-with-gitlab-svg-thumbs-ico.yml5
-rw-r--r--changelogs/unreleased/222594-enable-the-release_asset_link_type-feature-flag-by-default.yml5
-rw-r--r--changelogs/unreleased/222907-replace-angle-icons-with-chevron.yml5
-rw-r--r--changelogs/unreleased/222964-collapse-button.yml5
-rw-r--r--changelogs/unreleased/223041-lock_version_monkeypatch_removal_unrevert.yml5
-rw-r--r--changelogs/unreleased/223097-support-fenced-code-blocks-in-atlassian-document-format-converter.yml5
-rw-r--r--changelogs/unreleased/223135-add-ref-milestones-and-released_at-to-releaser-cli-and-yml.yml5
-rw-r--r--changelogs/unreleased/223135-expose-ref-milestones-relesed-at-to-cli.yml5
-rw-r--r--changelogs/unreleased/223151-adjust-unique-index-on-alerts.yml5
-rw-r--r--changelogs/unreleased/223151-custom-error-for-fingerprint-collision.yml5
-rw-r--r--changelogs/unreleased/223151-open-new-alert-when-existing-are-resolved.yml5
-rw-r--r--changelogs/unreleased/223159-fix-dahsboard-warning-logic.yml5
-rw-r--r--changelogs/unreleased/223162-enclose-release-cli-steps-in-an-array.yml5
-rw-r--r--changelogs/unreleased/223171_allow_erb_extension_for_sse.yml5
-rw-r--r--changelogs/unreleased/223185-add-project-key-to-jira-tracker-data.yml5
-rw-r--r--changelogs/unreleased/223196-update-design-management-documentation.yml5
-rw-r--r--changelogs/unreleased/223251-edit-the-message-in-the-empty-state-banner-for-dag-visualization-t.yml5
-rw-r--r--changelogs/unreleased/223279-confidential-warning-doesnt-show-the-issuable-type.yml5
-rw-r--r--changelogs/unreleased/223714-harden-ci-pipelines-auto_devops-config_repository-usage-data.yml5
-rw-r--r--changelogs/unreleased/223718-change-rate-limit-for-issues-creation-to-be-disabled-by-default.yml5
-rw-r--r--changelogs/unreleased/223773-correxct-too-large-limit.yml5
-rw-r--r--changelogs/unreleased/223788-improve-query-to-retrieve-job-artifacts-with-files-stored-locally.yml5
-rw-r--r--changelogs/unreleased/223790-nan-or-null-values-should-not-be-removed.yml5
-rw-r--r--changelogs/unreleased/223928-page-title-editorconfig.yml5
-rw-r--r--changelogs/unreleased/224039-jira-issues-integration-doc.yml6
-rw-r--r--changelogs/unreleased/224113-remove-ff-for-generic-alert-fingerprinting.yml5
-rw-r--r--changelogs/unreleased/224117-require-k8s-for-dast-template.yml5
-rw-r--r--changelogs/unreleased/224447-add-default_branch_name-to-applicationsettings-model.yml5
-rw-r--r--changelogs/unreleased/224470-mixed-case-pages-url.yml5
-rw-r--r--changelogs/unreleased/224528-un-assign-issue-to-from-comment-author-action-visibility.yml5
-rw-r--r--changelogs/unreleased/224602-optimize-usage-ping-metrics-based-on-issues-table.yml5
-rw-r--r--changelogs/unreleased/224674-fix-update_routes_for_lost_and_found_group_and_orphaned_projects-c.yml5
-rw-r--r--changelogs/unreleased/22506-webauthn-step-1-migrations.yml5
-rw-r--r--changelogs/unreleased/225187-replace-fa-comment-icons-with-gitlab-svg-comment-icon.yml5
-rw-r--r--changelogs/unreleased/225212-add-fields-to-jira-mutation.yml5
-rw-r--r--changelogs/unreleased/225214-require-username-namespace-to-be-at-least-2-characters-long.yml5
-rw-r--r--changelogs/unreleased/225258-extend-applicationsettings-to-support-default_branch_name.yml5
-rw-r--r--changelogs/unreleased/225295-fix-missing-avatar.yml5
-rw-r--r--changelogs/unreleased/225592-read-default_branch_name-when-initializing-w-readme-md.yml6
-rw-r--r--changelogs/unreleased/225640-move-link-to-file-view-to-bottom-bar-in-web-ide.yml5
-rw-r--r--changelogs/unreleased/225649-add-description-to-alert-issue-body.yml5
-rw-r--r--changelogs/unreleased/225706-add-issues-enabled-to-jira-tracker-data.yml5
-rw-r--r--changelogs/unreleased/225933-fa-play-replacement.yml5
-rw-r--r--changelogs/unreleased/225938-replace-fa-eyes-slash-icons-with-gitlab-svg-eye-slash-icon.yml5
-rw-r--r--changelogs/unreleased/225965-move-counter-on-related-merge-requests-issues-into-a-badge.yml5
-rw-r--r--changelogs/unreleased/225970-dont-display-speech-bubble-on-image-threads-in-overview.yml5
-rw-r--r--changelogs/unreleased/225971-display-speech-bubble-hovering-over-images-on-commits-page.yml5
-rw-r--r--changelogs/unreleased/226874-fix-pages-url-path.yml5
-rw-r--r--changelogs/unreleased/22691-externalize-i18n-strings-from---app-views-shared-milestones-_form_d.yml5
-rw-r--r--changelogs/unreleased/22691-externalize-i18n-strings-from---app-views-shared-milestones-_sideba.yml5
-rw-r--r--changelogs/unreleased/22691-externelize-i18n-strings-from---app-views-shared-_promo-html-haml.yml5
-rw-r--r--changelogs/unreleased/227054-eng-add-db-column-to-indicate-temporarily-increased-storage.yml5
-rw-r--r--changelogs/unreleased/227090-health-status-text-displaying-twice-on-epic-tree.yml5
-rw-r--r--changelogs/unreleased/227190-add-pagerduty-related-columns-to-project_incident_management_setti.yml5
-rw-r--r--changelogs/unreleased/227351-impossible-to-permanently-close-jira-import-success-alert.yml5
-rw-r--r--changelogs/unreleased/227363-project-services-checkboxes-are-not-checked-with-feature-flag-inte.yml5
-rw-r--r--changelogs/unreleased/227558-missing-digest-revision-and-short-revision-in-tags.yml5
-rw-r--r--changelogs/unreleased/227598-list-format-static-site-editor.yml5
-rw-r--r--changelogs/unreleased/227799-enable-batch-suggestions-by-default.yml5
-rw-r--r--changelogs/unreleased/227863-fix-api-error-on-null-bio.yml5
-rw-r--r--changelogs/unreleased/227865-positioning-of-mr-and-issue-counter-on-epic-trees-and-board-header.yml5
-rw-r--r--changelogs/unreleased/22822-ide-paste-images.yml5
-rw-r--r--changelogs/unreleased/22834-ff-merge-msg.yml5
-rw-r--r--changelogs/unreleased/228674-auto-devops-deploy-failure-when-code-quality-enabled.yml5
-rw-r--r--changelogs/unreleased/22868-fix-remove-button-alignment.yml5
-rw-r--r--changelogs/unreleased/229155-clarify-checking-ability-to-merge-automatically-message-on-mr-widg.yml5
-rw-r--r--changelogs/unreleased/23352-editorconfig.yml5
-rw-r--r--changelogs/unreleased/24241-provide-a-label-for-scheduled-pipeline-in-the-pipelines-overview-pa.yml5
-rw-r--r--changelogs/unreleased/24324-remove-button-row-from-environments-empty-state.yml5
-rw-r--r--changelogs/unreleased/25429-fix-disabled-quick-actions-in-notes.yml4
-rw-r--r--changelogs/unreleased/25486-batch-suggestions.yml5
-rw-r--r--changelogs/unreleased/25830-find-file-button.yml5
-rw-r--r--changelogs/unreleased/25953-api-exposes-comment-field-of-user-uploaded-ssh-keys.yml5
-rw-r--r--changelogs/unreleased/28154-move-controllers-outside-ee.yml5
-rw-r--r--changelogs/unreleased/28589-emoji-status-popover-doesn-t-show-emoji-when-it-s-in-the-message.yml5
-rw-r--r--changelogs/unreleased/29279-add-resend-confirmation-link-to-login-failure-message.yml5
-rw-r--r--changelogs/unreleased/30390-duplicates-replacement-labels.yml5
-rw-r--r--changelogs/unreleased/30707-show-outdated-status-of-suggestions.yml5
-rw-r--r--changelogs/unreleased/30769-deploy-keys-push-protected-branches.yml5
-rw-r--r--changelogs/unreleased/30853-footer-system-message-covers-horizontal-scrollbar.yml5
-rw-r--r--changelogs/unreleased/31000-api-for-instance-level-kubernetes-clusters.yml5
-rw-r--r--changelogs/unreleased/31099-MR-API-allow-NOT-params.yml5
-rw-r--r--changelogs/unreleased/32230-reorder-diffs-compare-versions-dropdowns.yml5
-rw-r--r--changelogs/unreleased/32942-add-dart-ci-template.yml5
-rw-r--r--changelogs/unreleased/33040-doc-cicd-yaml-clarify-rules-if-when-behaviour.yml5
-rw-r--r--changelogs/unreleased/33162-add-last-activity-for-personal-access-tokens.yml5
-rw-r--r--changelogs/unreleased/33185-add-missing-install-instruction.yml5
-rw-r--r--changelogs/unreleased/33743-graph-code-coverage-changes-over-time-for-a-project.yml5
-rw-r--r--changelogs/unreleased/34185-custom-renderer-kramdown.yml5
-rw-r--r--changelogs/unreleased/34523-fix-sse-edit-area-sync-bug.yml5
-rw-r--r--changelogs/unreleased/35077-custom-renderer-identifiers.yml5
-rw-r--r--changelogs/unreleased/35261-custom-renderer-embedded-ruby.yml5
-rw-r--r--changelogs/unreleased/35347-styling-of-sse-markdown-mode.yml5
-rw-r--r--changelogs/unreleased/35349-reorder-api.yml5
-rw-r--r--changelogs/unreleased/35775-inline-code-fix-for-custom-renderer-identifiers.yml5
-rw-r--r--changelogs/unreleased/36250-custom-renderer-html.yml5
-rw-r--r--changelogs/unreleased/36319-graphql-mutation-for-changing-locked-status-of-an-issue.yml5
-rw-r--r--changelogs/unreleased/36330-v2-custom-renderer-html.yml5
-rw-r--r--changelogs/unreleased/36361-custom-renderer-font-awesome.yml5
-rw-r--r--changelogs/unreleased/36574-custom-renderer-identifiers-instances.yml5
-rw-r--r--changelogs/unreleased/36720-frontend-provide-option-to-unassign-removed-user-from-issuables.yml5
-rw-r--r--changelogs/unreleased/36720-unassig-user-on-remove-option.yml5
-rw-r--r--changelogs/unreleased/36788-feature-proposal-api-for-import-from-bitbucket-server.yml5
-rw-r--r--changelogs/unreleased/36860-secret-detection-branch-job.yml5
-rw-r--r--changelogs/unreleased/37412-update-error-tracking-list-message.yml5
-rw-r--r--changelogs/unreleased/39545-cleanup-dynamic-milestone-pages.yml5
-rw-r--r--changelogs/unreleased/500-metrics-creation.yml5
-rw-r--r--changelogs/unreleased/502-add-toggles-help-text-int-test-gitlab-ui-integration-test.yml5
-rw-r--r--changelogs/unreleased/9912-api-env-variables-update-with-env-scope.yml5
-rw-r--r--changelogs/unreleased/Fix-spelling-error.yml5
-rw-r--r--changelogs/unreleased/Remove-addAssignee-logic-from-issues-model.yml5
-rw-r--r--changelogs/unreleased/Remove-addLabel-function-logic-from-issue-models.yml5
-rw-r--r--changelogs/unreleased/Remove-addMilestone-logic-from-issue-models.yml5
-rw-r--r--changelogs/unreleased/Remove-clickable-styling-from-loading-row.yml5
-rw-r--r--changelogs/unreleased/Remove-destroy-function-logic-from-list-model.yml5
-rw-r--r--changelogs/unreleased/Remove-findAssignee-logic-from-issues-model.yml5
-rw-r--r--changelogs/unreleased/Remove-findLabel-logic-from-issues-model.yml5
-rw-r--r--changelogs/unreleased/Remove-findissue-logic-from-list-model.yml5
-rw-r--r--changelogs/unreleased/Remove-moveIssue-logic-from-list-model.yml5
-rw-r--r--changelogs/unreleased/Remove-moveMultipleIssues-logic-from-list.yml5
-rw-r--r--changelogs/unreleased/Remove-newIssue-logic-from-list-model.yml5
-rw-r--r--changelogs/unreleased/Remove-nextPage-function-logic-from-listmodel.yml5
-rw-r--r--changelogs/unreleased/Remove-onNewIssueResponse-logic-from-list-model.yml5
-rw-r--r--changelogs/unreleased/Remove-removeAllAssignees-logic-from-issue-model.yml5
-rw-r--r--changelogs/unreleased/Remove-removeAssignee-logic-from-issue-model.yml5
-rw-r--r--changelogs/unreleased/Remove-removeIssue-function-logic-from-list-model.yml5
-rw-r--r--changelogs/unreleased/Remove-removeLabel-logic-from-issues-model.yml5
-rw-r--r--changelogs/unreleased/Remove-removeLabels-logic-from-issues-model.yml5
-rw-r--r--changelogs/unreleased/Remove-removeMilestone-logic-from-issue-model.yml5
-rw-r--r--changelogs/unreleased/Remove-removeMultipleIssues-logic-from-list-model.yml5
-rw-r--r--changelogs/unreleased/Remove-setFetchingState-logic-from-issue-model.yml5
-rw-r--r--changelogs/unreleased/Remove-setLoadingState-logic-From-issue-model.yml5
-rw-r--r--changelogs/unreleased/Remove-update-fuction-logic-from-list-model.yml5
-rw-r--r--changelogs/unreleased/Remove-updateData-logic-from-issue-model.yml5
-rw-r--r--changelogs/unreleased/aalakkad-fix-time-tracking-help-link.yml5
-rw-r--r--changelogs/unreleased/ab-alert-usage-ping.yml5
-rw-r--r--changelogs/unreleased/ab-cleanup-migrations.yml5
-rw-r--r--changelogs/unreleased/ab-hash-partitioning.yml5
-rw-r--r--changelogs/unreleased/ab-monitor-demo-environments-2.yml5
-rw-r--r--changelogs/unreleased/ab-partition-management.yml5
-rw-r--r--changelogs/unreleased/ab-revert-schema-create.yml5
-rw-r--r--changelogs/unreleased/ab-services-partial-indexes.yml5
-rw-r--r--changelogs/unreleased/add-Groups-SharedRunnersService-tests-migrations.yml5
-rw-r--r--changelogs/unreleased/add-alias-expansion-to-tf-docs.yml5
-rw-r--r--changelogs/unreleased/add-api-endpoint-for-resource-milestone-events-pd.yml5
-rw-r--r--changelogs/unreleased/add-doc-custom-validators.yml5
-rw-r--r--changelogs/unreleased/add-dockerfile-path-to-ado-workflow-rules.yml5
-rw-r--r--changelogs/unreleased/add-email-to-oidc-id-token.yml5
-rw-r--r--changelogs/unreleased/add-experience-level-to-user-preferences.yml5
-rw-r--r--changelogs/unreleased/add-global-plans.yml5
-rw-r--r--changelogs/unreleased/add-group-runners-finder.yml5
-rw-r--r--changelogs/unreleased/add-link-to-mrs-references.yml5
-rw-r--r--changelogs/unreleased/add-markdown-support-in-bio.yml5
-rw-r--r--changelogs/unreleased/add-models-for-site-profiles-225404.yml5
-rw-r--r--changelogs/unreleased/add-namespace-settings-table.yml5
-rw-r--r--changelogs/unreleased/add-node-ci-template.yml5
-rw-r--r--changelogs/unreleased/add-state-events-api-pd.yml5
-rw-r--r--changelogs/unreleased/add-static-source-to-ci-pipeline-218685.yml5
-rw-r--r--changelogs/unreleased/add-tags-to-queue-attributes.yml5
-rw-r--r--changelogs/unreleased/add-tiller-log-to-artifacts.yml6
-rw-r--r--changelogs/unreleased/add-unique-vistits-data-to-usage-ping.yml5
-rw-r--r--changelogs/unreleased/add_build_reference.yml5
-rw-r--r--changelogs/unreleased/ajk-GQL-user-mrs.yml5
-rw-r--r--changelogs/unreleased/ajk-declarative-policy-overrides.yml5
-rw-r--r--changelogs/unreleased/ajk-design-activity-c.yml5
-rw-r--r--changelogs/unreleased/ajk-design-ref-filter.yml5
-rw-r--r--changelogs/unreleased/ajk-ff-remove-wiki_events.yml5
-rw-r--r--changelogs/unreleased/ajk-gql-add-mr-author.yml5
-rw-r--r--changelogs/unreleased/ajk-gql-diff-stats-file-count.yml5
-rw-r--r--changelogs/unreleased/ajk-gql-labels.yml5
-rw-r--r--changelogs/unreleased/ajk-gql-lookahead.yml5
-rw-r--r--changelogs/unreleased/ajk-gql-mr-diff-stats.yml5
-rw-r--r--changelogs/unreleased/ajk-gql-mr-resolvers-split.yml5
-rw-r--r--changelogs/unreleased/ajk-gql-mr-update.yml5
-rw-r--r--changelogs/unreleased/ajk-remove-design-events-ff.yml5
-rw-r--r--changelogs/unreleased/ajk-safe-wiki-event-url.yml5
-rw-r--r--changelogs/unreleased/ajk-todos-return-resource.yml5
-rw-r--r--changelogs/unreleased/ak-update-google-auth.yml5
-rw-r--r--changelogs/unreleased/al-214420-pass-hard-delete-on-snippets-destroy.yml5
-rw-r--r--changelogs/unreleased/al-215200-snippets-by-type-usage-counter.yml5
-rw-r--r--changelogs/unreleased/al-217784-add-blobs-field-to-snippets-in-graphql.yml5
-rw-r--r--changelogs/unreleased/alert-assignee-dropdown.yml5
-rw-r--r--changelogs/unreleased/alert-assignee-list-view.yml5
-rw-r--r--changelogs/unreleased/alert-endpoint.yml5
-rw-r--r--changelogs/unreleased/alert-intergration-trigger-test-docs.yml5
-rw-r--r--changelogs/unreleased/alert-list-dropdown-header.yml5
-rw-r--r--changelogs/unreleased/alert-management-mobile-alignment.yml5
-rw-r--r--changelogs/unreleased/alert-settings-link-follow-through.yml5
-rw-r--r--changelogs/unreleased/alert-system-notes-tool-tip.yml5
-rw-r--r--changelogs/unreleased/alert-system-notes.yml5
-rw-r--r--changelogs/unreleased/alert-table-classes-hotfix.yml5
-rw-r--r--changelogs/unreleased/allow-extra-sentry-tags-from-environment.yml5
-rw-r--r--changelogs/unreleased/allow_skipped.yml5
-rw-r--r--changelogs/unreleased/an-throttle-project-update-repository-storage-worker.yml5
-rw-r--r--changelogs/unreleased/andr3-218471-code-review-diff-overlaps-sidebar.yml5
-rw-r--r--changelogs/unreleased/asciidoc-alignment-roles.yml5
-rw-r--r--changelogs/unreleased/assign-alerts-permissions-hotfix.yml5
-rw-r--r--changelogs/unreleased/assign-alerts-sidebar-base.yml5
-rw-r--r--changelogs/unreleased/assign-alerts-sidebar-container-fix.yml5
-rw-r--r--changelogs/unreleased/astoicescu-adaptDateFormatOnTimeSeries.yml5
-rw-r--r--changelogs/unreleased/astoicescu-addMetricsDashboardActionsMenu.yml5
-rw-r--r--changelogs/unreleased/astoicescu-allowOotbDashboardsToBeCloned.yml5
-rw-r--r--changelogs/unreleased/astoicescu-fullWidthCharts.yml5
-rw-r--r--changelogs/unreleased/astoicescu-settingsButton.yml5
-rw-r--r--changelogs/unreleased/autodevops-secrets.yml5
-rw-r--r--changelogs/unreleased/aws-guidance.yml5
-rw-r--r--changelogs/unreleased/backfill-routes-for-users-migration.yml5
-rw-r--r--changelogs/unreleased/background-migration-tracking.yml5
-rw-r--r--changelogs/unreleased/branch-name-default-to-true.yml6
-rw-r--r--changelogs/unreleased/browse-locked-artifact.yml5
-rw-r--r--changelogs/unreleased/bump-gei-version.yml5
-rw-r--r--changelogs/unreleased/bump_ci_auto_deploy_0_16.yml5
-rw-r--r--changelogs/unreleased/bump_cluster_applications_version.yml5
-rw-r--r--changelogs/unreleased/cablett-email-link.yml5
-rw-r--r--changelogs/unreleased/cablett-merge-request-merged-email-ref.yml5
-rw-r--r--changelogs/unreleased/calebw-update-stuck-runner-message.yml5
-rw-r--r--changelogs/unreleased/change-pipeline-widget-when-no-pipeline-ran-for-commit.yml5
-rw-r--r--changelogs/unreleased/change_from_vendor_specific_to_gitlab.yml5
-rw-r--r--changelogs/unreleased/chore-bump-omniauth_openid_connect.yml5
-rw-r--r--changelogs/unreleased/chore-styles-removal.yml5
-rw-r--r--changelogs/unreleased/ci-rust-cargo-test-workspace-option.yml5
-rw-r--r--changelogs/unreleased/ci-secrets-persistence.yml5
-rw-r--r--changelogs/unreleased/cilium-cluster-application-artifact-parsing.yml5
-rw-r--r--changelogs/unreleased/cilium-cluster-application-migration.yml5
-rw-r--r--changelogs/unreleased/cilium-state-metrics.yml5
-rw-r--r--changelogs/unreleased/clean-up-install-from-source-gitlab-shell.yml5
-rw-r--r--changelogs/unreleased/cluster-application-disable-cleanup-managed.yml5
-rw-r--r--changelogs/unreleased/cluster-applications-0-23-0.yml5
-rw-r--r--changelogs/unreleased/cluster-applications-0-24-2.yml5
-rw-r--r--changelogs/unreleased/cngo-add-count-to-imported-jira-issues-message.yml5
-rw-r--r--changelogs/unreleased/cngo-add-link-text-to-collapsed-left-sidebar.yml5
-rw-r--r--changelogs/unreleased/cngo-fix-border-radius-base.yml5
-rw-r--r--changelogs/unreleased/cngo-improve-header-accessibility.yml5
-rw-r--r--changelogs/unreleased/cngo-labels-bug.yml5
-rw-r--r--changelogs/unreleased/cngo-make-markdown-textarea-buttons-tab-accessible.yml5
-rw-r--r--changelogs/unreleased/cngo-make-markdown-textarea-links-tab-accessible.yml5
-rw-r--r--changelogs/unreleased/create-branch-url.yml5
-rw-r--r--changelogs/unreleased/create-ops-ff-issues-table.yml5
-rw-r--r--changelogs/unreleased/create-state-events-pd.yml5
-rw-r--r--changelogs/unreleased/curd-auto-merge-in-transaction.yml5
-rw-r--r--changelogs/unreleased/dag-annotations-sticky.yml5
-rw-r--r--changelogs/unreleased/dashboardValidationWarnings.yml5
-rw-r--r--changelogs/unreleased/data-endpoint-for-dag-visualization-2.yml5
-rw-r--r--changelogs/unreleased/dblessing-new-sign-in-email-beautification.yml5
-rw-r--r--changelogs/unreleased/dblessing-project-bot-prevent-removal.yml5
-rw-r--r--changelogs/unreleased/dblessing_known_sign_in_.yml5
-rw-r--r--changelogs/unreleased/dblessing_known_sign_in_global_setting.yml5
-rw-r--r--changelogs/unreleased/dedup-merge-request-metrics.yml5
-rw-r--r--changelogs/unreleased/defect-pipeline-related-mr-spinner.yml6
-rw-r--r--changelogs/unreleased/dennis-project-templates-add-gitbook-logo.yml5
-rw-r--r--changelogs/unreleased/dennis-project-templates-add-gomicro-logo.yml5
-rw-r--r--changelogs/unreleased/dennis-project-templates-add-hexo-logo.yml5
-rw-r--r--changelogs/unreleased/dennis-project-templates-add-hugo-logo.yml5
-rw-r--r--changelogs/unreleased/dennis-project-templates-add-jekyll-logo.yml5
-rw-r--r--changelogs/unreleased/dennis-update-webhooks-page-from-whitelist-to-allowlist.yml5
-rw-r--r--changelogs/unreleased/design-management-transitions.yml5
-rw-r--r--changelogs/unreleased/design-view-scrolling-issue-bug.yml5
-rw-r--r--changelogs/unreleased/disable_ilm_on_ELK_yaml.yml5
-rw-r--r--changelogs/unreleased/dmishunov-editor-lite-extensions.yml5
-rw-r--r--changelogs/unreleased/docs-auto-build-cnb-custom-builder.yml5
-rw-r--r--changelogs/unreleased/docs-firefox-u2f-api.yml5
-rw-r--r--changelogs/unreleased/docs-fj-update-web-ide-terminal-doc-core.yml5
-rw-r--r--changelogs/unreleased/docs-links-from-ui-2.yml5
-rw-r--r--changelogs/unreleased/docs-links-from-ui.yml5
-rw-r--r--changelogs/unreleased/docs-saml-sso-from-core-at-self-hosted.yml5
-rw-r--r--changelogs/unreleased/docs-u2f-version-history.yml5
-rw-r--r--changelogs/unreleased/dont-include-changes-in-webhook-payload-when-old-associations-is-empty.yml5
-rw-r--r--changelogs/unreleased/downloadable_reports.yml5
-rw-r--r--changelogs/unreleased/downstream-pipeline-ux.yml5
-rw-r--r--changelogs/unreleased/drop-old-non-unique-index-on-mr-metrics.yml5
-rw-r--r--changelogs/unreleased/dz-redirect-unscoped-pipelines-routes.yml5
-rw-r--r--changelogs/unreleased/dz-scope-project-snippet-routes.yml5
-rw-r--r--changelogs/unreleased/dz-scope-snippet-routes.yml5
-rw-r--r--changelogs/unreleased/eb-drop-old-daily-report-results-table.yml5
-rw-r--r--changelogs/unreleased/eb-report-file-size-mechanism.yml5
-rw-r--r--changelogs/unreleased/ee-app-services-1.yml5
-rw-r--r--changelogs/unreleased/emilyring-cluster-list-refactor-provider-icon.yml5
-rw-r--r--changelogs/unreleased/emilyring-remove-tf-plan-name.yml5
-rw-r--r--changelogs/unreleased/emilyring-terraform-translation.yml5
-rw-r--r--changelogs/unreleased/emilyring-tf-widget-multiple.yml5
-rw-r--r--changelogs/unreleased/emoji-api-model.yml5
-rw-r--r--changelogs/unreleased/enable-atomic-processing-by-default.yml5
-rw-r--r--changelogs/unreleased/enable-bulk-insert-for-needs.yml5
-rw-r--r--changelogs/unreleased/exclude-server-fields-from-exceptions-log.yml5
-rw-r--r--changelogs/unreleased/experimentation-cookie-domain.yml5
-rw-r--r--changelogs/unreleased/faster-label-transfer-queries.yml5
-rw-r--r--changelogs/unreleased/feat-admin-pages-show-custom-attributes.yml5
-rw-r--r--changelogs/unreleased/feat-use-new-api-icon.yml5
-rw-r--r--changelogs/unreleased/feature-api-add-bridge-api-endpoint.yml5
-rw-r--r--changelogs/unreleased/feature-bump-cluster-applications-to-0-16-0.yml5
-rw-r--r--changelogs/unreleased/feature-gb-artifacts-exclude-feature-flag.yml5
-rw-r--r--changelogs/unreleased/feature-secure-eslint-to-core.yml5
-rw-r--r--changelogs/unreleased/feature-show-memory-cpu-on-cluster-list.yml5
-rw-r--r--changelogs/unreleased/filter-from-url-query-params-pipeline.yml5
-rw-r--r--changelogs/unreleased/filter-pipelines-by-status.yml5
-rw-r--r--changelogs/unreleased/filter-pipelines-by-tag-name.yml5
-rw-r--r--changelogs/unreleased/fix-atomic-processing-lock-version.yml5
-rw-r--r--changelogs/unreleased/fix-attach-file-icon-margin.yml5
-rw-r--r--changelogs/unreleased/fix-ci-variables-regression.yml5
-rw-r--r--changelogs/unreleased/fix-design-note-border-radius.yml5
-rw-r--r--changelogs/unreleased/fix-design-notes-filename-duplication.yml5
-rw-r--r--changelogs/unreleased/fix-design-todos-filename-duplication.yml5
-rw-r--r--changelogs/unreleased/fix-ecs-detached-branch-pipeline.yml5
-rw-r--r--changelogs/unreleased/fix-failing-dashboard-schema-validation-calls.yml5
-rw-r--r--changelogs/unreleased/fix-gb-pipeline-index-latest.yml5
-rw-r--r--changelogs/unreleased/fix-group-api-archived.yml5
-rw-r--r--changelogs/unreleased/fix-group-transfer-to-subgroup.yml5
-rw-r--r--changelogs/unreleased/fix-invalid-error-tracking-method.yml5
-rw-r--r--changelogs/unreleased/fix-logrotate-su-parameter.yml5
-rw-r--r--changelogs/unreleased/fix-max_import_size.yml5
-rw-r--r--changelogs/unreleased/fix-omniauth-buttons-js.yml5
-rw-r--r--changelogs/unreleased/fix-open-sse-on-subgroups.yml5
-rw-r--r--changelogs/unreleased/fix-pagination-for-resource-milestone-events-api.yml5
-rw-r--r--changelogs/unreleased/fix-pagination-link.yml5
-rw-r--r--changelogs/unreleased/fix-routes-for-internal-users.yml5
-rw-r--r--changelogs/unreleased/fix-runner-hearbeat.yml5
-rw-r--r--changelogs/unreleased/fix-same-family-pipeline-ids.yml5
-rw-r--r--changelogs/unreleased/fix-selecting-status-emoji-twice.yml5
-rw-r--r--changelogs/unreleased/fix-sse-escaping-sequences.yml5
-rw-r--r--changelogs/unreleased/fix-typo-issues-limit-settings-template.yml5
-rw-r--r--changelogs/unreleased/fix-uninitialized-constant.yml5
-rw-r--r--changelogs/unreleased/fix-update-plan-limits-functionality.yml5
-rw-r--r--changelogs/unreleased/fix-vertically-center-action-icon.yml5
-rw-r--r--changelogs/unreleased/fix_back_button_when_switching_mr_tabs.yml5
-rw-r--r--changelogs/unreleased/fix_blocked_issue_warning.yml5
-rw-r--r--changelogs/unreleased/fix_default_path_when_creating_project_from_group_template.yml5
-rw-r--r--changelogs/unreleased/fix_deleting_user_psql_error_on_events_table_v2.yml5
-rw-r--r--changelogs/unreleased/fix_mr_note_label_urls.yml5
-rw-r--r--changelogs/unreleased/fix_nil_class_for_bytesize_error.yml5
-rw-r--r--changelogs/unreleased/fix_preconnect_typo.yml5
-rw-r--r--changelogs/unreleased/fix_shard_move_archive.yml5
-rw-r--r--changelogs/unreleased/fj-218516-add-validation-to-move-action.yml5
-rw-r--r--changelogs/unreleased/fj-219399-add-usage-data-monthly-snippet-by-type.yml5
-rw-r--r--changelogs/unreleased/fj-223696-add-snippet-statistics.yml5
-rw-r--r--changelogs/unreleased/fj-223700-add-count-logic-to-snippet-statistics.yml5
-rw-r--r--changelogs/unreleased/fj-223701-update-snippets-statistics-after-post-receive.yml5
-rw-r--r--changelogs/unreleased/fj-223703-add-snippets-size-to-project-statistics.yml5
-rw-r--r--changelogs/unreleased/fj-223705-add-snippets-size-to-project-statistics-entity.yml5
-rw-r--r--changelogs/unreleased/fj-223706-add-snippets-size-to-project-statistics-type.yml5
-rw-r--r--changelogs/unreleased/fj-223712-include-snippets-size-in-project-statistics.yml5
-rw-r--r--changelogs/unreleased/fj-223731-update-snippet-statistics-on-project-import.yml5
-rw-r--r--changelogs/unreleased/fj-223817-populate-project-snippets-statistics.yml5
-rw-r--r--changelogs/unreleased/fj-224486-add-snippets-size-column-to-root-storage-statistics.yml5
-rw-r--r--changelogs/unreleased/fj-224487-include-snippets-size-in-root-storage-statistics.yml5
-rw-r--r--changelogs/unreleased/fj-224657-include-snippets-size-in-group-statistics.yml5
-rw-r--r--changelogs/unreleased/fj-224658-add-snippets-size-to-root-storage-statistics-type.yml5
-rw-r--r--changelogs/unreleased/fj-225613-add-snippets-quota-information.yml5
-rw-r--r--changelogs/unreleased/fj-225964-include-personal-snippets-in-snippets-size.yml5
-rw-r--r--changelogs/unreleased/fj-227311-optimize-snippets-finder-query.yml5
-rw-r--r--changelogs/unreleased/fj-228825-remove-file-path-validation-in-snippet-create-action.yml5
-rw-r--r--changelogs/unreleased/fj-add-allowed-actions-to-snippet-input-action.yml5
-rw-r--r--changelogs/unreleased/fj-add-snippet-file-input-action.yml5
-rw-r--r--changelogs/unreleased/fj-add-snippet-files-param-to-snippet-create-service.yml5
-rw-r--r--changelogs/unreleased/fj-add-snippet-files-param-to-snippet-update-service.yml5
-rw-r--r--changelogs/unreleased/fj-add-snippet-input-file-action-to-create-mutation.yml5
-rw-r--r--changelogs/unreleased/fj-add-validations-snippet-input-action.yml5
-rw-r--r--changelogs/unreleased/fj-avoid-updating-snippet-content-when-not-present.yml5
-rw-r--r--changelogs/unreleased/fj-backfill-imported-snippet-repositories.yml5
-rw-r--r--changelogs/unreleased/fj-bump-lfs-token-default-expiration-time.yml5
-rw-r--r--changelogs/unreleased/fj-change-snippet-author-nullable-graphql-type.yml5
-rw-r--r--changelogs/unreleased/fj-default-order-snippet-lists.yml5
-rw-r--r--changelogs/unreleased/fj-fix-import-from-different-types-exports.yml5
-rw-r--r--changelogs/unreleased/fj-fix-single-param-update.yml5
-rw-r--r--changelogs/unreleased/fj-fix-snippet-create-mutation-non-activerecord-errors.yml5
-rw-r--r--changelogs/unreleased/fj-fix-snippet-import-from-database.yml5
-rw-r--r--changelogs/unreleased/fj-fix-snippet-import-when-fails.yml5
-rw-r--r--changelogs/unreleased/fj-fj-add-snippet-input-file-action-to-update-mutation.yml5
-rw-r--r--changelogs/unreleased/fj-rethink-snippet-storage-callbacks.yml5
-rw-r--r--changelogs/unreleased/followup-leakyconst-master-check.yml5
-rw-r--r--changelogs/unreleased/fox-comment-icons-commits.yml5
-rw-r--r--changelogs/unreleased/georgekoltsov-emit-bitbucket-server-metrics.yml5
-rw-r--r--changelogs/unreleased/georgekoltsov-fix-profile-applications-page-i18n.yml5
-rw-r--r--changelogs/unreleased/georgekoltsov-fix-services-relation-on-import.yml5
-rw-r--r--changelogs/unreleased/georgekoltsov-import-export-rate-limits-as-application-settings.yml5
-rw-r--r--changelogs/unreleased/georgekoltsov-import-export-tmp-folder-cleanup.yml5
-rw-r--r--changelogs/unreleased/gitaly-version-v13.1.0-rc1.yml5
-rw-r--r--changelogs/unreleased/gitlab-emoji-specs.yml5
-rw-r--r--changelogs/unreleased/gitlab-json-grape.yml5
-rw-r--r--changelogs/unreleased/gitlab-ui-badge-integration-2.yml5
-rw-r--r--changelogs/unreleased/gl-cluster-applications-json.yml6
-rw-r--r--changelogs/unreleased/graphql-bug-fix-get-todo-by-type.yml5
-rw-r--r--changelogs/unreleased/groups_routing_priority.yml5
-rw-r--r--changelogs/unreleased/grp-finder-code-refactor.yml5
-rw-r--r--changelogs/unreleased/hpaluch-master.yml5
-rw-r--r--changelogs/unreleased/id-approval-rules-usage-ping.yml5
-rw-r--r--changelogs/unreleased/id-code-nav-500-error.yml5
-rw-r--r--changelogs/unreleased/id-code-navigation-enable-feature.yml5
-rw-r--r--changelogs/unreleased/id-draft-wip.yml5
-rw-r--r--changelogs/unreleased/id-expose-approvals-endpoints.yml5
-rw-r--r--changelogs/unreleased/id-fix-timeout-query.yml5
-rw-r--r--changelogs/unreleased/id-fix-wip-in-the-middle-of-title.yml5
-rw-r--r--changelogs/unreleased/id-move-approvals-endpoints-to-ce.yml5
-rw-r--r--changelogs/unreleased/id-split-query-for-code-nav-path.yml5
-rw-r--r--changelogs/unreleased/id-update-workhorse.yml5
-rw-r--r--changelogs/unreleased/image-check-disable.yml4
-rw-r--r--changelogs/unreleased/implement_vulnerability_stats_model.yml5
-rw-r--r--changelogs/unreleased/improve_issue_labels_api.yml5
-rw-r--r--changelogs/unreleased/improve_storage_move_workflow.yml5
-rw-r--r--changelogs/unreleased/increase-events-count-for-prometheus-alerts.yml5
-rw-r--r--changelogs/unreleased/insert-project-authorization-directly-when-creating-project.yml5
-rw-r--r--changelogs/unreleased/instance-variables-ui.yml5
-rw-r--r--changelogs/unreleased/instance_auto_devops_enabled_usage_ping.yml5
-rw-r--r--changelogs/unreleased/issue-bulk-update.yml5
-rw-r--r--changelogs/unreleased/issue_22856_fe.yml5
-rw-r--r--changelogs/unreleased/jc-add-seed-to-repository-storages-weighted.yml5
-rw-r--r--changelogs/unreleased/jc-modify-verbiage-in-repository-storages-settings.yml5
-rw-r--r--changelogs/unreleased/jc-pick-weighted-repository.yml5
-rw-r--r--changelogs/unreleased/jc-show-all-storages.yml5
-rw-r--r--changelogs/unreleased/jc-ui-repository-storages-weighted.yml5
-rw-r--r--changelogs/unreleased/jc-weighted-repository-storages.yml5
-rw-r--r--changelogs/unreleased/jcunha-bump-deploy-image-to-0-17-0.yml6
-rw-r--r--changelogs/unreleased/jcunha-bump-helm-version-to-2-16-7.yml5
-rw-r--r--changelogs/unreleased/jcunha-update-helm-version-to-2-16-9.yml5
-rw-r--r--changelogs/unreleased/jdb-fix-multiline-comment-form-reply.yml5
-rw-r--r--changelogs/unreleased/jdb-highlight-commented-rows.yml5
-rw-r--r--changelogs/unreleased/jdb-mutliline-comment-fe.yml5
-rw-r--r--changelogs/unreleased/jdb-save-whitespace-setting.yml5
-rw-r--r--changelogs/unreleased/jej-api-for-root-groups.yml5
-rw-r--r--changelogs/unreleased/jh-drop_jid_null_constraint.yml5
-rw-r--r--changelogs/unreleased/jh-group_import_status.yml5
-rw-r--r--changelogs/unreleased/jh-group_import_ui_frontend.yml5
-rw-r--r--changelogs/unreleased/jh-rate_limit_project_export.yml5
-rw-r--r--changelogs/unreleased/jira-projects-api-wrapper.yml5
-rw-r--r--changelogs/unreleased/jivanvl-add-keyboard-shortcuts-metrics-dashboard.yml5
-rw-r--r--changelogs/unreleased/jivanvl-add-managed-apps-environments-dropdown.yml5
-rw-r--r--changelogs/unreleased/jivanvl-add-snowplow-logs.yml5
-rw-r--r--changelogs/unreleased/jivanvl-make-chart-panels-focusable-keyboard.yml5
-rw-r--r--changelogs/unreleased/john_long-support-multiple-mailbox-email-check.yml5
-rw-r--r--changelogs/unreleased/jsonnet-template.yml5
-rw-r--r--changelogs/unreleased/jsx-analyzer.yml5
-rw-r--r--changelogs/unreleased/justin_ho-change-redirect-path-after-integration-save.yml5
-rw-r--r--changelogs/unreleased/justin_ho-enable-integration_form_refactor-by-default.yml5
-rw-r--r--changelogs/unreleased/justin_ho-refine-ui-of-integration-form.yml5
-rw-r--r--changelogs/unreleased/justin_ho-replace-fa-exchange-icon.yml5
-rw-r--r--changelogs/unreleased/kassio-fix-user-default-language.yml5
-rw-r--r--changelogs/unreleased/kborges-github-import-rake-add-rate-limit-doc.yml5
-rw-r--r--changelogs/unreleased/ld-graphql-aliased-mutations.yml6
-rw-r--r--changelogs/unreleased/leaky-constant-fix-11.yml5
-rw-r--r--changelogs/unreleased/leaky-constant-fix-14.yml5
-rw-r--r--changelogs/unreleased/leaky-constant-fix-22.yml5
-rw-r--r--changelogs/unreleased/leaky-constant-fix-28.yml5
-rw-r--r--changelogs/unreleased/leaky-constant-fix-29.yml5
-rw-r--r--changelogs/unreleased/leaky-constant-fix-3.yml5
-rw-r--r--changelogs/unreleased/leaky-constant-fix-30.yml5
-rw-r--r--changelogs/unreleased/leaky-constant-fix-32.yml5
-rw-r--r--changelogs/unreleased/leaky-constant-fix-33.yml5
-rw-r--r--changelogs/unreleased/leaky-constant-fix-34.yml5
-rw-r--r--changelogs/unreleased/leaky-constant-fix-36.yml5
-rw-r--r--changelogs/unreleased/leaky-constant-fix-37.yml5
-rw-r--r--changelogs/unreleased/leaky-constant-fix-38.yml5
-rw-r--r--changelogs/unreleased/leaky-constant-fix-39.yml5
-rw-r--r--changelogs/unreleased/leaky-constant-fix-5.yml5
-rw-r--r--changelogs/unreleased/leaky-constant-fix-9.yml5
-rw-r--r--changelogs/unreleased/leipert-remove-ie11-polyfills.yml5
-rw-r--r--changelogs/unreleased/limit-alert-assignees.yml5
-rw-r--r--changelogs/unreleased/lm-change-sorting.yml5
-rw-r--r--changelogs/unreleased/lm-follow-up.yml5
-rw-r--r--changelogs/unreleased/lm-hover-state-sort.yml5
-rw-r--r--changelogs/unreleased/lm-remove-counts-and-redirect.yml5
-rw-r--r--changelogs/unreleased/lm-resolve-timeout.yml5
-rw-r--r--changelogs/unreleased/lm-sorting-list.yml5
-rw-r--r--changelogs/unreleased/long-path-mr-bug.yml5
-rw-r--r--changelogs/unreleased/make-fixed-notification-default-enabled.yml5
-rw-r--r--changelogs/unreleased/make_repository_moves_job_idempotent.yml5
-rw-r--r--changelogs/unreleased/markdown-toolbar-list-style.yml5
-rw-r--r--changelogs/unreleased/mattkasa-207510-terraform-state-usage-ping.yml5
-rw-r--r--changelogs/unreleased/mattkasa-207532-add-usage-ping-for-terraform-reports.yml5
-rw-r--r--changelogs/unreleased/merge-cancel-deploy-followup.yml5
-rw-r--r--changelogs/unreleased/merge-cancel-deploy.yml5
-rw-r--r--changelogs/unreleased/merge-ref-diffs-position-updates.yml5
-rw-r--r--changelogs/unreleased/merge-tslint-with-eslint.yml5
-rw-r--r--changelogs/unreleased/mf-codequality-widget-frontend-feature-move.yml5
-rw-r--r--changelogs/unreleased/mg-fix-katex-fonts.yml5
-rw-r--r--changelogs/unreleased/migration-confirm-project-bot-users.yml5
-rw-r--r--changelogs/unreleased/mo-add-build-report-result.yml5
-rw-r--r--changelogs/unreleased/monospace-ci-variable-value.yml5
-rw-r--r--changelogs/unreleased/move-delete-to-bottom-of-list.yml5
-rw-r--r--changelogs/unreleased/move-has-status-concern-to-ci-namespace.yml5
-rw-r--r--changelogs/unreleased/move-profiles-keys-get-keys-to-users.yml5
-rw-r--r--changelogs/unreleased/move_migration_to_post_deployment.yml5
-rw-r--r--changelogs/unreleased/mr-rebase-button-move-to-bottom.yml5
-rw-r--r--changelogs/unreleased/mvanremmerden-master-patch-68859.yml5
-rw-r--r--changelogs/unreleased/mwaw-208224-move-cluster-metrics-dashboard-endpoint-into-gitlab-core-BE.yml5
-rw-r--r--changelogs/unreleased/mwaw-208790-allow-logs-controller-to-use-cluster-id-parameter.yml5
-rw-r--r--changelogs/unreleased/mwaw-210289-add-metrics-dashboard-validation-to-grapql.yml5
-rw-r--r--changelogs/unreleased/mwaw-210289-fix-dashboard-validation-false-positives-warnings.yml5
-rw-r--r--changelogs/unreleased/mwaw-210289-metrics-dashboard-file-validation-mvc-1.yml5
-rw-r--r--changelogs/unreleased/mwaw-211433-metrics-dashboard-annotations-reaper-job-database-layer.yml6
-rw-r--r--changelogs/unreleased/mwaw-211433-metrics-dashboard-annotations-reaper-job-worker.yml5
-rw-r--r--changelogs/unreleased/nfriend-add-CI_ROOT_NAMESPACE-env-variable.yml5
-rw-r--r--changelogs/unreleased/nfriend-add-copy-branch-name-shortcut.yml5
-rw-r--r--changelogs/unreleased/nfriend-add-issue-stats-to-milestones-graphql.yml5
-rw-r--r--changelogs/unreleased/nfriend-enable-graphql_release_data-feature-flag.yml5
-rw-r--r--changelogs/unreleased/nfriend-enable-release_asset_link_type-f.yml5
-rw-r--r--changelogs/unreleased/nfriend-fix-assets-for-guest-users.yml5
-rw-r--r--changelogs/unreleased/nfriend-fix-npm-template.yml5
-rw-r--r--changelogs/unreleased/nfriend-fix-release-button-alignment.yml5
-rw-r--r--changelogs/unreleased/nfriend-fix-theme-alignment.yml5
-rw-r--r--changelogs/unreleased/nfriend-validate-package-name.yml5
-rw-r--r--changelogs/unreleased/nicolasdular-additional-purchased-storage-db.yml5
-rw-r--r--changelogs/unreleased/nicolasdular-broadcast-notification-close-btn.yml5
-rw-r--r--changelogs/unreleased/nicolasdular-expose-storage-limit-in-api.yml5
-rw-r--r--changelogs/unreleased/nicolasdular-fix-graphql-storage-type.yml5
-rw-r--r--changelogs/unreleased/notes-ee-feature.yml5
-rw-r--r--changelogs/unreleased/ntepluhina-fix-note-scrolling.yml5
-rw-r--r--changelogs/unreleased/optimize-milestones-page.yml5
-rw-r--r--changelogs/unreleased/osw-add-redis-metrics-to-sidekiq-job-run.yml5
-rw-r--r--changelogs/unreleased/osw-add-redis-metrics-to-web-requests.yml5
-rw-r--r--changelogs/unreleased/osw-instrument-lazily-consumed-gitaly-streams.yml5
-rw-r--r--changelogs/unreleased/osw-separate-redis-logs.yml6
-rw-r--r--changelogs/unreleased/osw-update-gitlab-pages-to-1-20.yml5
-rw-r--r--changelogs/unreleased/pages-1-21-0.yml5
-rw-r--r--changelogs/unreleased/parameterize-pg-deprecation-notice.yml5
-rw-r--r--changelogs/unreleased/patch-109.yml5
-rw-r--r--changelogs/unreleased/pb-background-migration-tracking-cleanup-on-rollback.yml5
-rw-r--r--changelogs/unreleased/pb-move-merge-requests-users-metric.yml5
-rw-r--r--changelogs/unreleased/perf-use-build-stubbed.yml5
-rw-r--r--changelogs/unreleased/ph-220889-fixMermaidNotRendering.yml5
-rw-r--r--changelogs/unreleased/ph-222790-diffSingleFileView.yml5
-rw-r--r--changelogs/unreleased/ph-28154-moveFrontendBatchCommentsFiles.yml5
-rw-r--r--changelogs/unreleased/ph-approvalsFEToFoss.yml5
-rw-r--r--changelogs/unreleased/ph-codeNavigationUXImprovements.yml5
-rw-r--r--changelogs/unreleased/pl-alert-management-fix-multiple-issue-creation.yml5
-rw-r--r--changelogs/unreleased/pl-remove-feature-flag-alert-integration-dropdown.yml5
-rw-r--r--changelogs/unreleased/pokstad1-gitaly-13-2-0-rc1.yml5
-rw-r--r--changelogs/unreleased/product-analytics-db-migration.yml5
-rw-r--r--changelogs/unreleased/project-soft-delete-setting.yml5
-rw-r--r--changelogs/unreleased/projects-api-sort-by-statistics.yml5
-rw-r--r--changelogs/unreleased/propagate-ds-java-version-in-dependency-scanning.yml5
-rw-r--r--changelogs/unreleased/ps-find-remote-is-storage-scoped.yml5
-rw-r--r--changelogs/unreleased/ps-fix-single-file-editor-long-branch.yml5
-rw-r--r--changelogs/unreleased/psi-dark-mode-issue-fixes.yml5
-rw-r--r--changelogs/unreleased/psi-dark-suggestions.yml5
-rw-r--r--changelogs/unreleased/psi-dark-theme.yml5
-rw-r--r--changelogs/unreleased/psi-funtional-line-component.yml5
-rw-r--r--changelogs/unreleased/psi-iteration-pagination-sensation.yml5
-rw-r--r--changelogs/unreleased/psi-job-log-text.yml5
-rw-r--r--changelogs/unreleased/rails-logger-cop-1.yml5
-rw-r--r--changelogs/unreleased/rails-logger-cop-10.yml5
-rw-r--r--changelogs/unreleased/rails-logger-cop-11.yml5
-rw-r--r--changelogs/unreleased/rails-logger-cop-12.yml5
-rw-r--r--changelogs/unreleased/rails-logger-cop-13.yml5
-rw-r--r--changelogs/unreleased/rails-logger-cop-14.yml5
-rw-r--r--changelogs/unreleased/rails-logger-cop-15.yml5
-rw-r--r--changelogs/unreleased/rails-logger-cop-16.yml5
-rw-r--r--changelogs/unreleased/rails-logger-cop-17.yml5
-rw-r--r--changelogs/unreleased/rails-logger-cop-18.yml5
-rw-r--r--changelogs/unreleased/rails-logger-cop-19.yml5
-rw-r--r--changelogs/unreleased/rails-logger-cop-2.yml5
-rw-r--r--changelogs/unreleased/rails-logger-cop-20.yml5
-rw-r--r--changelogs/unreleased/rails-logger-cop-21.yml5
-rw-r--r--changelogs/unreleased/rails-logger-cop-22.yml5
-rw-r--r--changelogs/unreleased/rails-logger-cop-23.yml5
-rw-r--r--changelogs/unreleased/rails-logger-cop-3.yml5
-rw-r--r--changelogs/unreleased/rails-logger-cop-4.yml5
-rw-r--r--changelogs/unreleased/rails-logger-cop-5.yml5
-rw-r--r--changelogs/unreleased/rails-logger-cop-6.yml5
-rw-r--r--changelogs/unreleased/rails-logger-cop-7.yml5
-rw-r--r--changelogs/unreleased/rails-logger-cop-8.yml5
-rw-r--r--changelogs/unreleased/rails-logger-cop-9.yml5
-rw-r--r--changelogs/unreleased/rails-save-bang-1.yml5
-rw-r--r--changelogs/unreleased/rails-save-bang-2.yml5
-rw-r--r--changelogs/unreleased/rails-save-bang-3.yml5
-rw-r--r--changelogs/unreleased/rc-add_dashboard_timezone.yml5
-rw-r--r--changelogs/unreleased/rc-add_new_dashboard_route.yml5
-rw-r--r--changelogs/unreleased/rc-enforce_unique_metrics_id_across_project.yml5
-rw-r--r--changelogs/unreleased/rc-escape_dashboard_paths.yml5
-rw-r--r--changelogs/unreleased/rc-remove_metric_identifier_index.yml5
-rw-r--r--changelogs/unreleased/rearchitect-fixed-pipelines-notification-v2.yml5
-rw-r--r--changelogs/unreleased/reduce-repo-size.yml5
-rw-r--r--changelogs/unreleased/reduce_pipeline_status_gitaly_call.yml5
-rw-r--r--changelogs/unreleased/refactor-stuck-imports-jobs-worker.yml5
-rw-r--r--changelogs/unreleased/remove-a11y-widget-ff.yml5
-rw-r--r--changelogs/unreleased/remove-dead-elasticsearch-indexing-code.yml5
-rw-r--r--changelogs/unreleased/remove-fa-heart.yml5
-rw-r--r--changelogs/unreleased/remove-ff-in-count-users-by-group.yml5
-rw-r--r--changelogs/unreleased/remove-ff-job-log-json.yml5
-rw-r--r--changelogs/unreleased/remove-group_milestone_descendants.yml5
-rw-r--r--changelogs/unreleased/remove-partial-clone-feature-flag.yml5
-rw-r--r--changelogs/unreleased/remove-prometheus-iap-feature-flag.yml5
-rw-r--r--changelogs/unreleased/remove-redundant-modsecurity-indexes.yml5
-rw-r--r--changelogs/unreleased/remove-some-tabs-from-pipelines-list.yml5
-rw-r--r--changelogs/unreleased/remove_hyperlink_from_close_reopen_button.yml5
-rw-r--r--changelogs/unreleased/remove_old_csrf_generation_monkey_patch.yml5
-rw-r--r--changelogs/unreleased/remove_scoped_approval_rules_feature_flag.yml5
-rw-r--r--changelogs/unreleased/render-job-details-on-downstream-pipelines.yml5
-rw-r--r--changelogs/unreleased/replace-slot-for-vue-3-migration.yml5
-rw-r--r--changelogs/unreleased/replicate_repository_stop_creating_destination.yml5
-rw-r--r--changelogs/unreleased/revert-bc8546a9.yml5
-rw-r--r--changelogs/unreleased/reword-addMultipleToDiscussionWarning.yml5
-rw-r--r--changelogs/unreleased/rf-brakeman-to-core.yml5
-rw-r--r--changelogs/unreleased/rm-25228-pre-receive-error.yml5
-rw-r--r--changelogs/unreleased/rmv-worker-code.yml5
-rw-r--r--changelogs/unreleased/rp-use-gitlab-yaml-loader-blob-viewer.yml5
-rw-r--r--changelogs/unreleased/rp-use-gitlab-yaml-loader-dashboard-stages.yml5
-rw-r--r--changelogs/unreleased/rp-use-stable-sort-in-sorter.yml5
-rw-r--r--changelogs/unreleased/rspec-rails-fast-failure-template.yml5
-rw-r--r--changelogs/unreleased/run-unassign-issuables-worker-when-out-of-transaction.yml5
-rw-r--r--changelogs/unreleased/sast-ci-config.yml5
-rw-r--r--changelogs/unreleased/sav-1566-pat-for-projects-db-changes.yml5
-rw-r--r--changelogs/unreleased/say-no-to-hacks.yml5
-rw-r--r--changelogs/unreleased/schedule_storage_move_api.yml5
-rw-r--r--changelogs/unreleased/secret-detection-remove-extra-job.yml5
-rw-r--r--changelogs/unreleased/services-usage-1.yml5
-rw-r--r--changelogs/unreleased/services-usage-2.yml5
-rw-r--r--changelogs/unreleased/services-usage-3.yml5
-rw-r--r--changelogs/unreleased/services-usage-4.yml5
-rw-r--r--changelogs/unreleased/services-usage-5.yml5
-rw-r--r--changelogs/unreleased/services-usage-6.yml5
-rw-r--r--changelogs/unreleased/services-usage-7.yml5
-rw-r--r--changelogs/unreleased/services-usage-8.yml5
-rw-r--r--changelogs/unreleased/sh-add-partial-index-locked-merge-requests.yml5
-rw-r--r--changelogs/unreleased/sh-avoid-extra-route-reload.yml5
-rw-r--r--changelogs/unreleased/sh-bump-gitaly-rc2.yml5
-rw-r--r--changelogs/unreleased/sh-consolidate-object-storage-config.yml5
-rw-r--r--changelogs/unreleased/sh-disable-gitconfig-write-on-imports-and-forks.yml5
-rw-r--r--changelogs/unreleased/sh-enable-workhorse-s3-client-consolidated.yml5
-rw-r--r--changelogs/unreleased/sh-error-tracking-query-canceled.yml5
-rw-r--r--changelogs/unreleased/sh-extend-remember-me-token.yml5
-rw-r--r--changelogs/unreleased/sh-fix-any-approvals-with-project-rules.yml5
-rw-r--r--changelogs/unreleased/sh-fix-api-error-handling.yml5
-rw-r--r--changelogs/unreleased/sh-fix-auto-merge-after-resolve-discussions.yml5
-rw-r--r--changelogs/unreleased/sh-fix-delete-blob-failure.yml5
-rw-r--r--changelogs/unreleased/sh-fix-gitaly-blob-service-durations.yml5
-rw-r--r--changelogs/unreleased/sh-fix-gitaly-bug-2857-for-dirs.yml5
-rw-r--r--changelogs/unreleased/sh-fix-gitaly-bug-2857.yml5
-rw-r--r--changelogs/unreleased/sh-fix-gitaly-commit-service-durations.yml5
-rw-r--r--changelogs/unreleased/sh-fix-gitaly-conflicts-service-duration.yml5
-rw-r--r--changelogs/unreleased/sh-fix-gitaly-ref-service-durations.yml5
-rw-r--r--changelogs/unreleased/sh-fix-issue-219991.yml5
-rw-r--r--changelogs/unreleased/sh-handle-distant-pat-expiry.yml5
-rw-r--r--changelogs/unreleased/sh-loosen-aws-tokens.yml5
-rw-r--r--changelogs/unreleased/sh-memoize-project-license-name.yml5
-rw-r--r--changelogs/unreleased/sh-requeue-failed-job-register.yml5
-rw-r--r--changelogs/unreleased/sh-reseed-repository-storages.yml5
-rw-r--r--changelogs/unreleased/sh-update-git-config-for-forks.yml5
-rw-r--r--changelogs/unreleased/sh-update-grape-1-4-0.yml5
-rw-r--r--changelogs/unreleased/sh-update-grape-gem.yml5
-rw-r--r--changelogs/unreleased/sh-update-importer-log-error-field.yml5
-rw-r--r--changelogs/unreleased/sh-update-sidekiq-5-2-9.yml5
-rw-r--r--changelogs/unreleased/sh-update-workhorse-8-34-0.yml5
-rw-r--r--changelogs/unreleased/sh-workhorse-direct-access-upload.yml5
-rw-r--r--changelogs/unreleased/short-url-for-custom-metrics-dashboards.yml5
-rw-r--r--changelogs/unreleased/show-redis-instance-in-performance-bar.yml5
-rw-r--r--changelogs/unreleased/show_build_status_in_branch_list.yml5
-rw-r--r--changelogs/unreleased/show_estimate_on_issues_list.yml5
-rw-r--r--changelogs/unreleased/sidekiq-arguments-logging-tokens.yml5
-rw-r--r--changelogs/unreleased/skip-importing-failed-jira-issue-instead-of-entrire-batch.yml5
-rw-r--r--changelogs/unreleased/sort-code-coverage-graph-by-dates.yml5
-rw-r--r--changelogs/unreleased/stackprof.yml5
-rw-r--r--changelogs/unreleased/support-file-names-for-metrics-dashboards.yml5
-rw-r--r--changelogs/unreleased/support-grafana-links-in-metrics-dashboard.yml5
-rw-r--r--changelogs/unreleased/support-warnings-in-pipeline-creation.yml5
-rw-r--r--changelogs/unreleased/suppress-progress-on-pulling-image-in-builtin-templates.yml5
-rw-r--r--changelogs/unreleased/swap-to-oj.yml5
-rw-r--r--changelogs/unreleased/switch-diff-view-fix.yml6
-rw-r--r--changelogs/unreleased/sy-alert-issue-system-notes.yml5
-rw-r--r--changelogs/unreleased/sy-alert-status-system-notes.yml5
-rw-r--r--changelogs/unreleased/sy-metrics-embeds-in-alerts.yml5
-rw-r--r--changelogs/unreleased/sy-publish-command.yml6
-rw-r--r--changelogs/unreleased/sy-publish-status-ui-fe.yml5
-rw-r--r--changelogs/unreleased/symlink-icon-graphql-file-mode.yml5
-rw-r--r--changelogs/unreleased/symlink-icon-ui.yml5
-rw-r--r--changelogs/unreleased/system-notes-broken-css.yml5
-rw-r--r--changelogs/unreleased/tc-fix-plain-text-commit-mails.yml5
-rw-r--r--changelogs/unreleased/templates-current-folder-fix.yml5
-rw-r--r--changelogs/unreleased/test-report-link-fix.yml5
-rw-r--r--changelogs/unreleased/tidy_put_projects_issues_spec.yml5
-rw-r--r--changelogs/unreleased/tr-alert-column-spacing.yml5
-rw-r--r--changelogs/unreleased/tr-alert-issue-link.yml5
-rw-r--r--changelogs/unreleased/tr-alert-text-search.yml5
-rw-r--r--changelogs/unreleased/tr-avoid-alert-refetch.yml5
-rw-r--r--changelogs/unreleased/tr-fix-broken-incident-link.yml5
-rw-r--r--changelogs/unreleased/tr-fix-prometheus-alert-list.yml5
-rw-r--r--changelogs/unreleased/tr-prettify-graphql-files.yml5
-rw-r--r--changelogs/unreleased/tr-reword-alert-service.yml5
-rw-r--r--changelogs/unreleased/tr-show-new-alerts.yml5
-rw-r--r--changelogs/unreleased/track-pod-logs-refresh-action.yml5
-rw-r--r--changelogs/unreleased/traversal-hierarchy.yml5
-rw-r--r--changelogs/unreleased/unconfirm-wrongfully-verified-email-records.yml5
-rw-r--r--changelogs/unreleased/update--variables-to-match-gitlab-ui.yml5
-rw-r--r--changelogs/unreleased/update-ado-deploy-image-to-0-17-2.yml5
-rw-r--r--changelogs/unreleased/update-auto-build-image-to-0-3-0.yml5
-rw-r--r--changelogs/unreleased/update-deprecated-slot-syntax-in---app-assets-javascripts-pages-admin-pro.yml5
-rw-r--r--changelogs/unreleased/update-deprecated-slot-syntax-in---app-assets-javascripts-pages-projects-.yml5
-rw-r--r--changelogs/unreleased/update-deprecated-slot-syntax-in---clusters-applications-vue.yml5
-rw-r--r--changelogs/unreleased/update-deprecated-slot-syntax-in---javascripts-environments_app-vue.yml5
-rw-r--r--changelogs/unreleased/update-deprecated-slot-syntax-in-app-assets-javascripts-reports-component.yml5
-rw-r--r--changelogs/unreleased/update-gitlab-runner-helm-chart-to-0-17-0.yml5
-rw-r--r--changelogs/unreleased/update-gitlab-runner-helm-chart-to-0-17-1.yml5
-rw-r--r--changelogs/unreleased/update-gitlab-runner-helm-chart-to-0-18-0.yml5
-rw-r--r--changelogs/unreleased/update-gitlab-runner-helm-chart-to-0.18.1.yml5
-rw-r--r--changelogs/unreleased/update-green-variables-to-match-gitlab-ui.yml5
-rw-r--r--changelogs/unreleased/update-index-artifacts-expire.yml6
-rw-r--r--changelogs/unreleased/update-lfs-setting-label.yml5
-rw-r--r--changelogs/unreleased/update-rack-timeout.yml5
-rw-r--r--changelogs/unreleased/update-rouge-3-21.yml5
-rw-r--r--changelogs/unreleased/update-secure-smau-metric.yml5
-rw-r--r--changelogs/unreleased/update-validates-hostname-gem.yml5
-rw-r--r--changelogs/unreleased/update-workhorse-version-master.yml5
-rw-r--r--changelogs/unreleased/update_android_ci.yml5
-rw-r--r--changelogs/unreleased/upgrade-codequality-template.yml5
-rw-r--r--changelogs/unreleased/upgrade-pages-to-1-19.yml5
-rw-r--r--changelogs/unreleased/upgrade-renamed-diff-show-full-diff-for-renamed-files.yml5
-rw-r--r--changelogs/unreleased/use-local-tiller-by-default.yml5
-rw-r--r--changelogs/unreleased/vij-raw-snippet-blobs.yml5
-rw-r--r--changelogs/unreleased/vij-update-existing-raw-snippet.yml5
-rw-r--r--changelogs/unreleased/vs-fix-account-delete-plural-msg.yml5
-rw-r--r--changelogs/unreleased/wiki-edit-invalid-page.yml5
-rw-r--r--changelogs/unreleased/xanf-expose-bitbucket-error.yml5
-rw-r--r--changelogs/unreleased/xanf-fix-404-on-import.yml5
-rw-r--r--changelogs/unreleased/xanf-use-new-import-ui-templates.yml5
-rw-r--r--config/application.rb3
-rw-r--r--config/dependency_decisions.yml6
-rw-r--r--config/environments/development.rb2
-rw-r--r--config/environments/production.rb2
-rw-r--r--config/environments/test.rb4
-rw-r--r--config/feature_categories.yml8
-rw-r--r--config/gitlab.yml.example50
-rw-r--r--config/initializers/01_secret_token.rb12
-rw-r--r--config/initializers/0_inject_feature_flags.rb5
-rw-r--r--config/initializers/1_postgresql_only.rb2
-rw-r--r--config/initializers/1_settings.rb55
-rw-r--r--config/initializers/action_cable.rb8
-rw-r--r--config/initializers/action_dispatch_journey_formatter.rb4
-rw-r--r--config/initializers/actionpack_generate_old_csrf_token.rb33
-rw-r--r--config/initializers/active_record_schema_ignore_tables.rb3
-rw-r--r--config/initializers/config_initializers_active_record_locking.rb46
-rw-r--r--config/initializers/doorkeeper_openid_connect.rb4
-rw-r--r--config/initializers/flipper.rb1
-rw-r--r--config/initializers/grape_patch.rb31
-rw-r--r--config/initializers/lograge.rb1
-rw-r--r--config/initializers/multi_json.rb5
-rw-r--r--config/initializers/oj.rb4
-rw-r--r--config/initializers/postgres_partitioning.rb10
-rw-r--r--config/initializers/rack_attack.rb13
-rw-r--r--config/initializers/rack_timeout.rb2
-rw-r--r--config/initializers/stackprof.rb101
-rw-r--r--config/initializers_before_autoloader/000_inflections.rb1
-rw-r--r--config/karma.config.js2
-rw-r--r--config/locales/devise.en.yml2
-rw-r--r--config/locales/en.yml2
-rw-r--r--config/object_store_settings.rb132
-rw-r--r--config/plugins/monaco_webpack.js17
-rw-r--r--config/prometheus/cluster_metrics.yml91
-rw-r--r--config/prometheus/common_metrics.yml24
-rw-r--r--config/prometheus/queries_cluster_metrics.yml65
-rw-r--r--config/routes.rb21
-rw-r--r--config/routes/import.rb4
-rw-r--r--config/routes/issues.rb1
-rw-r--r--config/routes/pipelines.rb10
-rw-r--r--config/routes/project.rb34
-rw-r--r--config/routes/snippets.rb9
-rw-r--r--config/routes/user.rb2
-rw-r--r--config/routes/wiki.rb6
-rw-r--r--config/settings.rb7
-rw-r--r--config/sidekiq_queues.yml8
-rw-r--r--config/webpack.config.js4
-rw-r--r--crowdin.yml4
-rw-r--r--danger/changelog/Dangerfile10
-rw-r--r--danger/documentation/Dangerfile38
-rw-r--r--danger/metadata/Dangerfile6
-rw-r--r--danger/plugins/sidekiq_queues.rb10
-rw-r--r--danger/roulette/Dangerfile35
-rw-r--r--danger/sidekiq_queues/Dangerfile27
-rw-r--r--danger/specs/Dangerfile8
-rw-r--r--db/fixtures/development/24_forks.rb33
-rw-r--r--db/fixtures/development/26_packages.rb159
-rw-r--r--db/fixtures/development/27_product_analytics_events.rb56
-rw-r--r--db/migrate/20190225160300_steal_encrypt_runners_tokens.rb19
-rw-r--r--db/migrate/20190315191339_create_merge_request_assignees_table.rb2
-rw-r--r--db/migrate/20190722144316_create_milestone_releases_table.rb2
-rw-r--r--db/migrate/20190927055500_create_description_versions.rb2
-rw-r--r--db/migrate/20191112212815_create_web_authn_table.rb26
-rw-r--r--db/migrate/20191118053631_add_group_deletion_schedules.rb2
-rw-r--r--db/migrate/20191127151619_create_gitlab_subscription_histories.rb2
-rw-r--r--db/migrate/20200214025454_add_canonical_emails.rb2
-rw-r--r--db/migrate/20200227165129_create_user_details.rb2
-rw-r--r--db/migrate/20200229171700_create_custom_emojis.rb29
-rw-r--r--db/migrate/20200305020458_add_label_restore_table.rb31
-rw-r--r--db/migrate/20200305020459_add_label_restore_foreign_keys.rb35
-rw-r--r--db/migrate/20200311093210_create_user_highest_roles.rb2
-rw-r--r--db/migrate/20200326122700_create_diff_note_positions.rb2
-rw-r--r--db/migrate/20200330203837_recreate_ci_ref.rb2
-rw-r--r--db/migrate/20200331132103_add_project_compliance_framework_settings_table.rb2
-rw-r--r--db/migrate/20200407182205_create_partitioned_foreign_keys.rb2
-rw-r--r--db/migrate/20200407222647_create_project_repository_storage_moves.rb2
-rw-r--r--db/migrate/20200408125046_create_ci_freeze_periods.rb2
-rw-r--r--db/migrate/20200416005331_create_status_page_published_incidents.rb2
-rw-r--r--db/migrate/20200417044453_create_alert_management_alerts.rb2
-rw-r--r--db/migrate/20200420104303_add_group_import_states_table.rb2
-rw-r--r--db/migrate/20200420115948_create_metrics_users_starred_dashboard.rb2
-rw-r--r--db/migrate/20200422091541_create_ci_instance_variables.rb2
-rw-r--r--db/migrate/20200424102023_add_shared_runners_enabled_and_override_to_namespaces.rb21
-rw-r--r--db/migrate/20200424135319_create_nuget_dependency_link_metadata.rb2
-rw-r--r--db/migrate/20200430130048_create_packages_nuget_metadata.rb2
-rw-r--r--db/migrate/20200430174637_create_group_deploy_keys.rb2
-rw-r--r--db/migrate/20200510181937_add_web_authn_xid_to_user_details.rb12
-rw-r--r--db/migrate/20200510182218_add_text_limit_to_user_details_webauthn_xid.rb16
-rw-r--r--db/migrate/20200510182556_add_text_limit_to_webauthn_registrations_name.rb16
-rw-r--r--db/migrate/20200510182824_add_text_limit_to_webauthn_registrations_credential_xid.rb16
-rw-r--r--db/migrate/20200510183128_add_foreign_key_from_webauthn_registrations_to_users.rb21
-rw-r--r--db/migrate/20200521225327_create_alert_management_alert_assignees.rb2
-rw-r--r--db/migrate/20200522205606_create_group_deploy_keys_group.rb25
-rw-r--r--db/migrate/20200524104346_add_source_to_resource_state_event.rb21
-rw-r--r--db/migrate/20200526193555_add_squash_option_to_project.rb9
-rw-r--r--db/migrate/20200526231421_update_index_approval_rule_name_for_code_owners_rule_type.rb116
-rw-r--r--db/migrate/20200527211605_add_locked_to_ci_pipelines.rb19
-rw-r--r--db/migrate/20200604001128_add_secrets_to_ci_builds_metadata.rb19
-rw-r--r--db/migrate/20200604143628_create_project_security_settings.rb2
-rw-r--r--db/migrate/20200604145731_create_board_user_preferences.rb2
-rw-r--r--db/migrate/20200605160806_add_index_on_repository_size_and_project_id_to_project_statistics.rb17
-rw-r--r--db/migrate/20200605160836_add_index_on_storage_size_and_project_id_to_project_statistics.rb17
-rw-r--r--db/migrate/20200605160851_add_index_on_wiki_size_and_project_id_to_project_statistics.rb17
-rw-r--r--db/migrate/20200609012539_add_traversal_ids_to_namespaces.rb19
-rw-r--r--db/migrate/20200610130002_create_vulnerability_statistics.rb30
-rw-r--r--db/migrate/20200613104045_add_compliance_frameworks_to_application_settings.rb19
-rw-r--r--db/migrate/20200615141554_add_closed_by_fields_to_resource_state_events.rb17
-rw-r--r--db/migrate/20200615193524_add_verify_known_sign_in_to_application_settings.rb9
-rw-r--r--db/migrate/20200615234047_create_clusters_applications_cilium.rb14
-rw-r--r--db/migrate/20200616124338_add_plan_limits_for_max_size_per_artifact_type.rb48
-rw-r--r--db/migrate/20200616145031_add_author_id_index_to_audit_events.rb22
-rw-r--r--db/migrate/20200617000757_clean_up_file_store_lfs_objects.rb21
-rw-r--r--db/migrate/20200617001001_clean_up_store_uploads.rb21
-rw-r--r--db/migrate/20200617001118_clean_up_file_store_ci_job_artifacts.rb23
-rw-r--r--db/migrate/20200617150041_create_namespace_limits.rb22
-rw-r--r--db/migrate/20200617205000_add_deploy_key_id_to_push_access_levels.rb22
-rw-r--r--db/migrate/20200618105638_add_index_on_id_and_created_at_to_snippets.rb17
-rw-r--r--db/migrate/20200618134223_restore_previous_schema_without_lock_version_null_constraint.rb20
-rw-r--r--db/migrate/20200618134723_restore_previous_schema_with_lock_version_indices.rb22
-rw-r--r--db/migrate/20200619000316_add_has_confluence_to_project_settings.rb19
-rw-r--r--db/migrate/20200619154527_add_project_key_to_jira_tracker_data.rb12
-rw-r--r--db/migrate/20200619154528_add_text_limit_to_jira_tracker_data_project_key.rb17
-rw-r--r--db/migrate/20200622040750_add_prometheus_alert_id_to_alert_management_alerts.rb16
-rw-r--r--db/migrate/20200622070606_add_vendor_to_vulnerability_scanners.rb23
-rw-r--r--db/migrate/20200622070620_add_limit_to_vulnerability_scanners_vendor.rb17
-rw-r--r--db/migrate/20200622095419_add_snippets_size_to_project_statistics.rb9
-rw-r--r--db/migrate/20200622103836_create_snippet_statistics.rb24
-rw-r--r--db/migrate/20200622104923_create_ci_pipeline_messages_table.rb27
-rw-r--r--db/migrate/20200622235737_remove_index_ci_job_artifacts_file_store_is_null.rb18
-rw-r--r--db/migrate/20200623000148_remove_index_lfs_objects_file_store_is_null.rb18
-rw-r--r--db/migrate/20200623000320_remove_index_uploads_store_is_null.rb18
-rw-r--r--db/migrate/20200623073431_add_source_merge_request_id_to_resource_state_events.rb33
-rw-r--r--db/migrate/20200623090030_add_author_name_to_audit_event.rb22
-rw-r--r--db/migrate/20200623121135_create_dynamic_partitions_schema.rb19
-rw-r--r--db/migrate/20200623141217_add_view_diffs_file_by_file_to_user_preferences.rb21
-rw-r--r--db/migrate/20200623141544_create_elastic_reindexing_task.rb33
-rw-r--r--db/migrate/20200623170000_create_static_partitions_schema.rb19
-rw-r--r--db/migrate/20200623185440_add_product_analytics_table.rb202
-rw-r--r--db/migrate/20200624075411_add_storage_size_limit_to_plan_limit.rb9
-rw-r--r--db/migrate/20200624142107_create_analytics_cycle_analytics_group_value_streams.rb33
-rw-r--r--db/migrate/20200624142207_add_group_value_stream_to_cycle_analytics_group_stages.rb19
-rw-r--r--db/migrate/20200624222443_add_default_branch_name_to_application_settings.rb12
-rw-r--r--db/migrate/20200625045442_add_idx_and_fk_for_prometheus_and_environment_to_alert_management_alerts.rb25
-rw-r--r--db/migrate/20200625082258_add_snippets_size_to_root_storage_statistics.rb19
-rw-r--r--db/migrate/20200625113337_add_last_used_to_personal_access_tokens.rb19
-rw-r--r--db/migrate/20200625174052_add_partial_index_to_locked_pipelines.rb17
-rw-r--r--db/migrate/20200625190458_add_limit_to_default_branch_name_to_application_settings.rb17
-rw-r--r--db/migrate/20200626130220_drop_partitions_dynamic_schema_if_exists.rb14
-rw-r--r--db/migrate/20200628210938_add_maintenance_mode_application_to_settings.rb29
-rw-r--r--db/migrate/20200629192638_add_uniq_index_on_metric_identifier_and_project_id.rb17
-rw-r--r--db/migrate/20200630091656_add_bio_html_to_user_details.rb24
-rw-r--r--db/migrate/20200630110826_add_documents_count_target_to_elastic_reindexing_tasks.rb9
-rw-r--r--db/migrate/20200701064756_add_not_valid_foreign_key_to_cycle_analytics_group_stages.rb22
-rw-r--r--db/migrate/20200701093859_add_import_export_limits_to_application_settings.rb15
-rw-r--r--db/migrate/20200701190523_add_delayed_project_removal_to_namespaces.rb19
-rw-r--r--db/migrate/20200701205710_create_background_migration_jobs.rb29
-rw-r--r--db/migrate/20200702123805_change_project_id_index_to_be_unique_on_vulnerability_statistics_table.rb17
-rw-r--r--db/migrate/20200702201039_change_prometheus_metrics_identifier_index.rb22
-rw-r--r--db/migrate/20200703121557_remove_f_keys_from_ci_daily_report_results_table.rb21
-rw-r--r--db/migrate/20200703124823_create_namespace_settings.rb22
-rw-r--r--db/migrate/20200703154822_add_issues_enabled_to_jira_tracker_data.rb9
-rw-r--r--db/migrate/20200706005325_remove_elastic_batch_project_indexer_worker_queue.rb11
-rw-r--r--db/migrate/20200706035141_adjust_unique_index_alert_management_alerts.rb35
-rw-r--r--db/migrate/20200706170536_add_temporary_storage_increase_to_namespace_limits.rb9
-rw-r--r--db/migrate/20200707071941_drop_old_non_unique_index_on_mr_metrics.rb18
-rw-r--r--db/migrate/20200707094341_add_browser_performance_to_plan_limits.rb9
-rw-r--r--db/migrate/20200707095849_add_load_performance_to_plan_limits.rb9
-rw-r--r--db/migrate/20200708080631_add_pager_duty_integration_columns_to_project_incident_management_settings.rb13
-rw-r--r--db/migrate/20200710105332_change_issues_create_limit_default.rb19
-rw-r--r--db/migrate/20200710130234_add_limit_constraints_to_project_incident_management_settings_token.rb18
-rw-r--r--db/migrate/20200712084655_create_dast_sites.rb27
-rw-r--r--db/migrate/20200712235622_create_dast_site_profiles.rb28
-rw-r--r--db/migrate/20200713152443_add_background_migration_job_index_for_partitioning_migrations.rb18
-rw-r--r--db/migrate/20200716044023_add_entity_path_to_audit_events.rb21
-rw-r--r--db/migrate/20200716120419_add_text_limit_on_entity_path_to_audit_events.rb17
-rw-r--r--db/post_migrate/20200305082754_remove_duplicate_labels_from_project.rb130
-rw-r--r--db/post_migrate/20200305082858_add_uniqueness_index_to_label_title_and_project.rb21
-rw-r--r--db/post_migrate/20200519201128_migrate_vulnerability_dismissal_feedback.rb36
-rw-r--r--db/post_migrate/20200526115436_dedup_mr_metrics.rb65
-rw-r--r--db/post_migrate/20200608195222_set_lock_version_not_null_constraint.rb23
-rw-r--r--db/post_migrate/20200608203426_set_proper_lock_version_indices.rb29
-rw-r--r--db/post_migrate/20200608205813_set_lock_version_to_not_null.rb31
-rw-r--r--db/post_migrate/20200608212030_lock_version_cleanup_for_epics.rb18
-rw-r--r--db/post_migrate/20200608212435_lock_version_cleanup_for_merge_requests.rb18
-rw-r--r--db/post_migrate/20200608212549_lock_version_cleanup_for_issues.rb18
-rw-r--r--db/post_migrate/20200608212652_lock_version_cleanup_for_ci_stages.rb18
-rw-r--r--db/post_migrate/20200608212807_lock_version_cleanup_for_ci_builds.rb18
-rw-r--r--db/post_migrate/20200608212824_lock_version_cleanup_for_ci_pipelines.rb18
-rw-r--r--db/post_migrate/20200609002841_add_partial_index_on_locked_state_id_to_merge_requests.rb2
-rw-r--r--db/post_migrate/20200615111857_unconfirm_wrongfully_verified_emails.rb31
-rw-r--r--db/post_migrate/20200617001637_validate_file_store_not_null_constraint_on_lfs_objects.rb17
-rw-r--r--db/post_migrate/20200617001848_validate_store_not_null_constraint_uploads.rb17
-rw-r--r--db/post_migrate/20200617002030_validate_file_store_not_null_constraint_on_ci_job_artifacts.rb17
-rw-r--r--db/post_migrate/20200618152212_update_secure_smau_index.rb23
-rw-r--r--db/post_migrate/20200623142159_remove_gitlab_issue_tracker_service_records.rb28
-rw-r--r--db/post_migrate/20200626060151_add_disable_overriding_approvers_per_merge_request_indices.rb26
-rw-r--r--db/post_migrate/20200701070435_add_default_value_stream_to_groups_with_group_stages.rb55
-rw-r--r--db/post_migrate/20200701091253_validate_foreign_key_on_cycle_analytics_group_stages.rb19
-rw-r--r--db/post_migrate/20200703064117_generate_missing_routes_for_bots.rb92
-rw-r--r--db/post_migrate/20200703125016_backfill_namespace_settings.rb29
-rw-r--r--db/post_migrate/20200704143633_add_index_on_user_id_and_created_at_where_source_to_ci_pipelines.rb17
-rw-r--r--db/post_migrate/20200704161600_add_index_on_id_and_status_and_created_at_to_deployments.rb19
-rw-r--r--db/post_migrate/20200706154619_drop_ci_daily_report_results_table.rb24
-rw-r--r--db/post_migrate/20200709101408_schedule_populate_project_snippet_statistics.rb30
-rw-r--r--db/post_migrate/20200710102418_delete_user_callout_alerts_moved.rb28
-rw-r--r--db/post_migrate/20200710102846_drop_index_ruby_objects_in_details_on_audit_events.rb18
-rw-r--r--db/post_migrate/20200713071042_confirm_project_bot_users.rb30
-rw-r--r--db/structure.sql10053
-rw-r--r--doc/.vale/gitlab/Acronyms.yml16
-rw-r--r--doc/.vale/gitlab/AlertBoxStyle.yml16
-rw-r--r--doc/.vale/gitlab/BadgeCapitalization.yml2
-rw-r--r--doc/.vale/gitlab/British.yml2
-rw-r--r--doc/.vale/gitlab/CodeblockFences.yml2
-rw-r--r--doc/.vale/gitlab/Contractions.yml14
-rw-r--r--doc/.vale/gitlab/CurlStringsQuoted.yml2
-rw-r--r--doc/.vale/gitlab/CurrentStatus.yml13
-rw-r--r--doc/.vale/gitlab/FirstPerson.yml2
-rw-r--r--doc/.vale/gitlab/FutureTense.yml17
-rw-r--r--doc/.vale/gitlab/InternalLinkExtension.yml2
-rw-r--r--doc/.vale/gitlab/LatinTerms.yml2
-rw-r--r--doc/.vale/gitlab/MeaningfulLinkWords.yml2
-rw-r--r--doc/.vale/gitlab/MergeConflictMarkers.yml2
-rw-r--r--doc/.vale/gitlab/OutdatedVersions.yml21
-rw-r--r--doc/.vale/gitlab/OxfordComma.yml2
-rw-r--r--doc/.vale/gitlab/ReferenceLinks.yml2
-rw-r--r--doc/.vale/gitlab/RelativeLinks.yml2
-rw-r--r--doc/.vale/gitlab/Repetition.yml2
-rw-r--r--doc/.vale/gitlab/SentenceLength.yml2
-rw-r--r--doc/.vale/gitlab/SentenceSpacing.yml4
-rw-r--r--doc/.vale/gitlab/Spelling.yml2
-rw-r--r--doc/.vale/gitlab/SubstitutionWarning.yml5
-rw-r--r--doc/.vale/gitlab/Substitutions.yml2
-rw-r--r--doc/.vale/gitlab/VersionText.yml2
-rw-r--r--doc/.vale/gitlab/spelling-exceptions.txt7
-rw-r--r--doc/README.md94
-rw-r--r--doc/administration/audit_events.md7
-rw-r--r--doc/administration/auth/jwt.md3
-rw-r--r--doc/administration/auth/ldap/google_secure_ldap.md3
-rw-r--r--doc/administration/auth/ldap/index.md22
-rw-r--r--doc/administration/auth/ldap/ldap-troubleshooting.md38
-rw-r--r--doc/administration/auth/okta.md3
-rw-r--r--doc/administration/auth/smartcard.md2
-rw-r--r--doc/administration/feature_flags.md22
-rw-r--r--doc/administration/geo/disaster_recovery/bring_primary_back.md6
-rw-r--r--doc/administration/geo/disaster_recovery/index.md19
-rw-r--r--doc/administration/geo/disaster_recovery/planned_failover.md15
-rw-r--r--doc/administration/geo/replication/database.md6
-rw-r--r--doc/administration/geo/replication/datatypes.md48
-rw-r--r--doc/administration/geo/replication/disable_geo.md93
-rw-r--r--doc/administration/geo/replication/docker_registry.md2
-rw-r--r--doc/administration/geo/replication/external_database.md3
-rw-r--r--doc/administration/geo/replication/geo_validation_tests.md65
-rw-r--r--doc/administration/geo/replication/index.md30
-rw-r--r--doc/administration/geo/replication/location_aware_git_url.md2
-rw-r--r--doc/administration/geo/replication/multiple_servers.md59
-rw-r--r--doc/administration/geo/replication/troubleshooting.md9
-rw-r--r--doc/administration/geo/replication/updating_the_geo_nodes.md5
-rw-r--r--doc/administration/git_annex.md8
-rw-r--r--doc/administration/gitaly/img/praefect_storage_v12_10.pngbin59531 -> 0 bytes
-rw-r--r--doc/administration/gitaly/index.md564
-rw-r--r--doc/administration/gitaly/praefect.md335
-rw-r--r--doc/administration/gitaly/reference.md2
-rw-r--r--doc/administration/high_availability/consul.md4
-rw-r--r--doc/administration/high_availability/database.md19
-rw-r--r--doc/administration/high_availability/gitlab.md20
-rw-r--r--doc/administration/high_availability/monitoring_node.md13
-rw-r--r--doc/administration/high_availability/nfs.md26
-rw-r--r--doc/administration/high_availability/nfs_host_client_setup.md19
-rw-r--r--doc/administration/high_availability/redis.md1007
-rw-r--r--doc/administration/high_availability/redis_source.md370
-rw-r--r--doc/administration/high_availability/sidekiq.md3
-rw-r--r--doc/administration/img/repository_storages_admin_ui_v12_10.pngbin23718 -> 0 bytes
-rw-r--r--doc/administration/img/repository_storages_admin_ui_v13_1.pngbin0 -> 85160 bytes
-rw-r--r--doc/administration/incoming_email.md22
-rw-r--r--doc/administration/instance_limits.md158
-rw-r--r--doc/administration/integration/plantuml.md3
-rw-r--r--doc/administration/integration/terminal.md3
-rw-r--r--doc/administration/job_artifacts.md46
-rw-r--r--doc/administration/job_logs.md2
-rw-r--r--doc/administration/lfs/index.md51
-rw-r--r--doc/administration/logs.md98
-rw-r--r--doc/administration/merge_request_diffs.md68
-rw-r--r--doc/administration/monitoring/gitlab_self_monitoring_project/img/self_monitoring_default_dashboard.pngbin0 -> 51508 bytes
-rw-r--r--doc/administration/monitoring/gitlab_self_monitoring_project/index.md23
-rw-r--r--doc/administration/monitoring/performance/grafana_configuration.md111
-rw-r--r--doc/administration/monitoring/performance/img/performance_bar_configuration_settings.pngbin16455 -> 0 bytes
-rw-r--r--doc/administration/monitoring/performance/img/performance_bar_frontend.pngbin112089 -> 34521 bytes
-rw-r--r--doc/administration/monitoring/performance/img/performance_bar_gitaly_calls.pngbin83212 -> 81321 bytes
-rw-r--r--doc/administration/monitoring/performance/img/performance_bar_gitaly_threshold.pngbin19076 -> 10316 bytes
-rw-r--r--doc/administration/monitoring/performance/img/performance_bar_redis_calls.pngbin70859 -> 17273 bytes
-rw-r--r--doc/administration/monitoring/performance/img/performance_bar_request_selector_warning.pngbin17259 -> 10175 bytes
-rw-r--r--doc/administration/monitoring/performance/img/performance_bar_rugged_calls.pngbin105305 -> 28784 bytes
-rw-r--r--doc/administration/monitoring/performance/performance_bar.md98
-rw-r--r--doc/administration/monitoring/prometheus/gitlab_exporter.md20
-rw-r--r--doc/administration/monitoring/prometheus/gitlab_metrics.md55
-rw-r--r--doc/administration/monitoring/prometheus/index.md102
-rw-r--r--doc/administration/object_storage.md556
-rw-r--r--doc/administration/operations/fast_ssh_key_lookup.md31
-rw-r--r--doc/administration/operations/filesystem_benchmarking.md3
-rw-r--r--doc/administration/operations/puma.md18
-rw-r--r--doc/administration/operations/unicorn.md2
-rw-r--r--doc/administration/packages/container_registry.md103
-rw-r--r--doc/administration/packages/dependency_proxy.md5
-rw-r--r--doc/administration/packages/index.md3
-rw-r--r--doc/administration/pages/index.md119
-rw-r--r--doc/administration/postgresql/index.md36
-rw-r--r--doc/administration/postgresql/replication_and_failover.md234
-rw-r--r--doc/administration/postgresql/standalone.md3
-rw-r--r--doc/administration/raketasks/ldap.md2
-rw-r--r--doc/administration/raketasks/maintenance.md4
-rw-r--r--doc/administration/raketasks/praefect.md18
-rw-r--r--doc/administration/raketasks/project_import_export.md10
-rw-r--r--doc/administration/redis/index.md42
-rw-r--r--doc/administration/redis/replication_and_failover.md741
-rw-r--r--doc/administration/redis/replication_and_failover_external.md376
-rw-r--r--doc/administration/redis/standalone.md63
-rw-r--r--doc/administration/redis/troubleshooting.md158
-rw-r--r--doc/administration/reference_architectures/10k_users.md2
-rw-r--r--doc/administration/reference_architectures/1k_users.md2
-rw-r--r--doc/administration/reference_architectures/25k_users.md2
-rw-r--r--doc/administration/reference_architectures/2k_users.md174
-rw-r--r--doc/administration/reference_architectures/3k_users.md1816
-rw-r--r--doc/administration/reference_architectures/50k_users.md2
-rw-r--r--doc/administration/reference_architectures/5k_users.md1820
-rw-r--r--doc/administration/reference_architectures/index.md10
-rw-r--r--doc/administration/reference_architectures/troubleshooting.md251
-rw-r--r--doc/administration/repository_storage_paths.md15
-rw-r--r--doc/administration/repository_storage_types.md7
-rw-r--r--doc/administration/server_hooks.md237
-rw-r--r--doc/administration/smime_signing_email.md9
-rw-r--r--doc/administration/terraform_state.md19
-rw-r--r--doc/administration/troubleshooting/elasticsearch.md3
-rw-r--r--doc/administration/troubleshooting/gitlab_rails_cheat_sheet.md20
-rw-r--r--doc/administration/troubleshooting/linux_cheat_sheet.md2
-rw-r--r--doc/administration/troubleshooting/navigating_gitlab_via_rails_console.md4
-rw-r--r--doc/administration/troubleshooting/postgresql.md8
-rw-r--r--doc/administration/troubleshooting/sidekiq.md5
-rw-r--r--doc/administration/uploads.md50
-rw-r--r--doc/api/README.md24
-rw-r--r--doc/api/api_resources.md3
-rw-r--r--doc/api/boards.md2
-rw-r--r--doc/api/container_registry.md6
-rw-r--r--doc/api/deploy_keys.md6
-rw-r--r--doc/api/deploy_tokens.md6
-rw-r--r--doc/api/deployments.md7
-rw-r--r--doc/api/discussions.md62
-rw-r--r--doc/api/environments.md8
-rw-r--r--doc/api/epic_issues.md8
-rw-r--r--doc/api/epic_links.md3
-rw-r--r--doc/api/epics.md4
-rw-r--r--doc/api/error_tracking.md6
-rw-r--r--doc/api/feature_flag_specs.md8
-rw-r--r--doc/api/feature_flags.md11
-rw-r--r--doc/api/feature_flags_legacy.md6
-rw-r--r--doc/api/freeze_periods.md9
-rw-r--r--doc/api/geo_nodes.md10
-rw-r--r--doc/api/graphql/getting_started.md2
-rw-r--r--doc/api/graphql/reference/gitlab_schema.graphql1650
-rw-r--r--doc/api/graphql/reference/gitlab_schema.json4660
-rw-r--r--doc/api/graphql/reference/index.md279
-rw-r--r--doc/api/group_clusters.md10
-rw-r--r--doc/api/group_labels.md6
-rw-r--r--doc/api/group_wikis.md196
-rw-r--r--doc/api/groups.md25
-rw-r--r--doc/api/import.md37
-rw-r--r--doc/api/instance_clusters.md293
-rw-r--r--doc/api/instance_level_ci_variables.md24
-rw-r--r--doc/api/issues.md65
-rw-r--r--doc/api/labels.md9
-rw-r--r--doc/api/members.md7
-rw-r--r--doc/api/merge_requests.md7
-rw-r--r--doc/api/metrics_dashboard_annotations.md7
-rw-r--r--doc/api/metrics_user_starred_dashboards.md9
-rw-r--r--doc/api/namespaces.md3
-rw-r--r--doc/api/notes.md1
-rw-r--r--doc/api/packages.md4
-rw-r--r--doc/api/pipelines.md2
-rw-r--r--doc/api/project_clusters.md14
-rw-r--r--doc/api/project_level_variables.md35
-rw-r--r--doc/api/project_repository_storage_moves.md15
-rw-r--r--doc/api/project_snippets.md22
-rw-r--r--doc/api/projects.md59
-rw-r--r--doc/api/protected_environments.md7
-rw-r--r--doc/api/releases/links.md2
-rw-r--r--doc/api/resource_milestone_events.md4
-rw-r--r--doc/api/resource_state_events.md212
-rw-r--r--doc/api/resource_weight_events.md108
-rw-r--r--doc/api/services.md38
-rw-r--r--doc/api/settings.md7
-rw-r--r--doc/api/snippets.md28
-rw-r--r--doc/api/users.md38
-rw-r--r--doc/api/vulnerability_findings.md2
-rw-r--r--doc/ci/README.md15
-rw-r--r--doc/ci/caching/index.md56
-rw-r--r--doc/ci/chatops/README.md78
-rw-r--r--doc/ci/cloud_deployment/index.md7
-rw-r--r--doc/ci/directed_acyclic_graph/index.md16
-rw-r--r--doc/ci/docker/using_docker_build.md44
-rw-r--r--doc/ci/docker/using_docker_images.md89
-rw-r--r--doc/ci/docker/using_kaniko.md2
-rw-r--r--doc/ci/environments/deployment_safety.md6
-rw-r--r--doc/ci/environments/index.md78
-rw-r--r--doc/ci/environments/protected_environments.md2
-rw-r--r--doc/ci/examples/README.md5
-rw-r--r--doc/ci/examples/authenticating-with-hashicorp-vault/index.md6
-rw-r--r--doc/ci/examples/deploy_spring_boot_to_cloud_foundry/index.md16
-rw-r--r--doc/ci/examples/deployment/README.md26
-rw-r--r--doc/ci/examples/deployment/composer-npm-deploy.md2
-rw-r--r--doc/ci/examples/devops_and_game_dev_with_gitlab_ci_cd/index.md196
-rw-r--r--doc/ci/examples/laravel_with_gitlab_and_envoy/index.md2
-rw-r--r--doc/ci/examples/php.md36
-rw-r--r--doc/ci/examples/test-and-deploy-python-application-to-heroku.md32
-rw-r--r--doc/ci/img/metrics_reports_advanced_v13_0.pngbin0 -> 41131 bytes
-rw-r--r--doc/ci/interactive_web_terminal/index.md10
-rw-r--r--doc/ci/introduction/index.md15
-rw-r--r--doc/ci/jenkins/index.md26
-rw-r--r--doc/ci/junit_test_reports.md22
-rw-r--r--doc/ci/merge_request_pipelines/index.md11
-rw-r--r--doc/ci/merge_request_pipelines/pipelines_for_merged_results/index.md3
-rw-r--r--doc/ci/merge_request_pipelines/pipelines_for_merged_results/merge_trains/index.md2
-rw-r--r--doc/ci/metrics_reports.md7
-rw-r--r--doc/ci/migration/circleci.md4
-rw-r--r--doc/ci/pipelines/index.md6
-rw-r--r--doc/ci/pipelines/job_artifacts.md43
-rw-r--r--doc/ci/pipelines/pipeline_architectures.md16
-rw-r--r--doc/ci/pipelines/settings.md14
-rw-r--r--doc/ci/review_apps/index.md4
-rw-r--r--doc/ci/runners/README.md84
-rw-r--r--doc/ci/triggers/README.md28
-rw-r--r--doc/ci/troubleshooting.md38
-rw-r--r--doc/ci/variables/README.md18
-rw-r--r--doc/ci/variables/predefined_variables.md19
-rw-r--r--doc/ci/yaml/README.md805
-rw-r--r--doc/ci/yaml/includes.md2
-rw-r--r--doc/customization/issue_and_merge_request_template.md4
-rw-r--r--doc/development/README.md3
-rw-r--r--doc/development/api_graphql_styleguide.md78
-rw-r--r--doc/development/api_styleguide.md40
-rw-r--r--doc/development/application_limits.md14
-rw-r--r--doc/development/application_secrets.md41
-rw-r--r--doc/development/approval_rules.md280
-rw-r--r--doc/development/architecture.md44
-rw-r--r--doc/development/changelog.md11
-rw-r--r--doc/development/chatops_on_gitlabcom.md4
-rw-r--r--doc/development/cicd/img/ci_template_selection_v13_1.pngbin0 -> 21284 bytes
-rw-r--r--doc/development/cicd/index.md8
-rw-r--r--doc/development/cicd/templates.md66
-rw-r--r--doc/development/code_intelligence/index.md110
-rw-r--r--doc/development/code_review.md18
-rw-r--r--doc/development/contributing/issue_workflow.md31
-rw-r--r--doc/development/contributing/merge_request_workflow.md3
-rw-r--r--doc/development/contributing/style_guides.md12
-rw-r--r--doc/development/dangerbot.md14
-rw-r--r--doc/development/database/add_foreign_key_to_existing_column.md3
-rw-r--r--doc/development/database/database_reviewer_guidelines.md95
-rw-r--r--doc/development/database/index.md8
-rw-r--r--doc/development/database_debugging.md2
-rw-r--r--doc/development/database_review.md7
-rw-r--r--doc/development/distributed_tracing.md2
-rw-r--r--doc/development/documentation/index.md266
-rw-r--r--doc/development/documentation/site_architecture/global_nav.md22
-rw-r--r--doc/development/documentation/site_architecture/index.md8
-rw-r--r--doc/development/documentation/structure.md11
-rw-r--r--doc/development/documentation/styleguide.md155
-rw-r--r--doc/development/ee_features.md88
-rw-r--r--doc/development/elasticsearch.md22
-rw-r--r--doc/development/emails.md4
-rw-r--r--doc/development/experiment_guide/index.md12
-rw-r--r--doc/development/fe_guide/accessibility.md2
-rw-r--r--doc/development/fe_guide/development_process.md4
-rw-r--r--doc/development/fe_guide/frontend_faq.md17
-rw-r--r--doc/development/fe_guide/graphql.md2
-rw-r--r--doc/development/fe_guide/icons.md3
-rw-r--r--doc/development/fe_guide/index.md4
-rw-r--r--doc/development/fe_guide/tooling.md50
-rw-r--r--doc/development/fe_guide/vue.md14
-rw-r--r--doc/development/fe_guide/vuex.md66
-rw-r--r--doc/development/feature_categorization/index.md130
-rw-r--r--doc/development/feature_flags/controls.md13
-rw-r--r--doc/development/feature_flags/development.md4
-rw-r--r--doc/development/feature_flags/index.md2
-rw-r--r--doc/development/feature_flags/process.md23
-rw-r--r--doc/development/foreign_keys.md11
-rw-r--r--doc/development/geo/framework.md158
-rw-r--r--doc/development/gitaly.md16
-rw-r--r--doc/development/go_guide/index.md24
-rw-r--r--doc/development/gotchas.md4
-rw-r--r--doc/development/i18n/externalization.md3
-rw-r--r--doc/development/i18n/proofreader.md1
-rw-r--r--doc/development/import_export.md3
-rw-r--r--doc/development/import_project.md53
-rw-r--r--doc/development/insert_into_tables_in_batches.md6
-rw-r--r--doc/development/integrations/elasticsearch_for_paid_tiers_on_gitlabcom.md28
-rw-r--r--doc/development/integrations/jira_connect.md2
-rw-r--r--doc/development/integrations/secure.md51
-rw-r--r--doc/development/integrations/secure_partner_integration.md9
-rw-r--r--doc/development/licensing.md2
-rw-r--r--doc/development/migration_style_guide.md44
-rw-r--r--doc/development/multi_version_compatibility.md2
-rw-r--r--doc/development/new_fe_guide/development/accessibility.md2
-rw-r--r--doc/development/ordering_table_columns.md12
-rw-r--r--doc/development/packages.md3
-rw-r--r--doc/development/performance.md111
-rw-r--r--doc/development/permissions.md15
-rw-r--r--doc/development/pipelines.md18
-rw-r--r--doc/development/policies.md83
-rw-r--r--doc/development/profiling.md3
-rw-r--r--doc/development/prometheus_metrics.md20
-rw-r--r--doc/development/query_recorder.md2
-rw-r--r--doc/development/rake_tasks.md3
-rw-r--r--doc/development/redis.md7
-rw-r--r--doc/development/scalability.md3
-rw-r--r--doc/development/secure_coding_guidelines.md75
-rw-r--r--doc/development/sidekiq_style_guide.md200
-rw-r--r--doc/development/telemetry/index.md27
-rw-r--r--doc/development/telemetry/snowplow.md176
-rw-r--r--doc/development/telemetry/usage_ping.md657
-rw-r--r--doc/development/testing_guide/best_practices.md39
-rw-r--r--doc/development/testing_guide/end_to_end/beginners_guide.md28
-rw-r--r--doc/development/testing_guide/end_to_end/environment_selection.md54
-rw-r--r--doc/development/testing_guide/end_to_end/feature_flags.md2
-rw-r--r--doc/development/testing_guide/end_to_end/rspec_metadata_tests.md3
-rw-r--r--doc/development/testing_guide/frontend_testing.md96
-rw-r--r--doc/development/testing_guide/review_apps.md38
-rw-r--r--doc/development/testing_guide/testing_levels.md2
-rw-r--r--doc/development/testing_guide/testing_migrations_guide.md2
-rw-r--r--doc/development/what_requires_downtime.md45
-rw-r--r--doc/gitlab-basics/start-using-git.md216
-rw-r--r--doc/install/aws/index.md54
-rw-r--r--doc/install/installation.md17
-rw-r--r--doc/install/openshift_and_gitlab/index.md3
-rw-r--r--doc/install/requirements.md18
-rw-r--r--doc/integration/README.md2
-rw-r--r--doc/integration/elasticsearch.md77
-rw-r--r--doc/integration/github.md2
-rw-r--r--doc/integration/jira_development_panel.md6
-rw-r--r--doc/integration/openid_connect_provider.md6
-rw-r--r--doc/integration/recaptcha.md6
-rw-r--r--doc/integration/saml.md161
-rw-r--r--doc/migrate_ci_to_ce/README.md2
-rw-r--r--doc/operations/README.md5
-rw-r--r--doc/operations/feature_flags.md328
-rw-r--r--doc/operations/index.md20
-rw-r--r--doc/operations/metrics/alerts.md110
-rw-r--r--doc/operations/metrics/dashboards/index.md249
-rw-r--r--doc/operations/metrics/dashboards/panel_types.md262
-rw-r--r--doc/operations/metrics/dashboards/templating_variables.md128
-rw-r--r--doc/operations/metrics/dashboards/variables.md59
-rw-r--r--doc/operations/metrics/dashboards/yaml.md166
-rw-r--r--doc/operations/metrics/dashboards/yaml_number_format.md177
-rw-r--r--doc/operations/metrics/embed.md93
-rw-r--r--doc/operations/metrics/embed_grafana.md65
-rw-r--r--doc/operations/metrics/img/example-dashboard_v13_1.pngbin0 -> 31439 bytes
-rw-r--r--doc/operations/metrics/index.md142
-rw-r--r--doc/operations/tracing.md40
-rw-r--r--doc/policy/maintenance.md50
-rw-r--r--doc/public_access/public_access.md2
-rw-r--r--doc/raketasks/README.md3
-rw-r--r--doc/raketasks/backup_restore.md90
-rw-r--r--doc/raketasks/cleanup.md20
-rw-r--r--doc/security/rack_attack.md6
-rw-r--r--doc/security/rate_limits.md1
-rw-r--r--doc/ssh/README.md2
-rw-r--r--doc/subscriptions/index.md99
-rw-r--r--doc/tools/email.md4
-rw-r--r--doc/topics/autodevops/customize.md16
-rw-r--r--doc/topics/autodevops/index.md75
-rw-r--r--doc/topics/autodevops/quick_start_guide.md20
-rw-r--r--doc/topics/autodevops/requirements.md20
-rw-r--r--doc/topics/autodevops/stages.md64
-rw-r--r--doc/topics/autodevops/upgrading_chart.md72
-rw-r--r--doc/topics/autodevops/upgrading_postgresql.md15
-rw-r--r--doc/topics/git/index.md3
-rw-r--r--doc/topics/git/lfs/index.md3
-rw-r--r--doc/topics/git/lfs/migrate_to_git_lfs.md2
-rw-r--r--doc/topics/git/partial_clone.md5
-rw-r--r--doc/topics/gitlab_flow.md4
-rw-r--r--doc/topics/web_application_firewall/index.md11
-rw-r--r--doc/topics/web_application_firewall/quick_start_guide.md16
-rw-r--r--doc/university/README.md10
-rw-r--r--doc/university/bookclub/index.md2
-rw-r--r--doc/university/training/end-user/README.md2
-rw-r--r--doc/university/training/topics/merge_requests.md2
-rw-r--r--doc/university/training/user_training.md2
-rw-r--r--doc/update/README.md60
-rw-r--r--doc/update/restore_after_failure.md4
-rw-r--r--doc/update/upgrading_from_ce_to_ee.md3
-rw-r--r--doc/user/admin_area/activating_deactivating_users.md2
-rw-r--r--doc/user/admin_area/credentials_inventory.md4
-rw-r--r--doc/user/admin_area/img/credentials_inventory_v12_6.pngbin52974 -> 0 bytes
-rw-r--r--doc/user/admin_area/img/credentials_inventory_v13_2.pngbin0 -> 96526 bytes
-rw-r--r--doc/user/admin_area/img/mr_approval_settings_compliance_project_v13_1.pngbin0 -> 36819 bytes
-rw-r--r--doc/user/admin_area/img/scope_mr_approval_settings_v13_1.pngbin0 -> 69238 bytes
-rw-r--r--doc/user/admin_area/license.md29
-rw-r--r--doc/user/admin_area/merge_requests_approvals.md19
-rw-r--r--doc/user/admin_area/monitoring/health_check.md8
-rw-r--r--doc/user/admin_area/settings/account_and_limit_settings.md42
-rw-r--r--doc/user/admin_area/settings/continuous_integration.md32
-rw-r--r--doc/user/admin_area/settings/img/email_notification_for_unknown_sign_ins_v13_2.pngbin0 -> 12539 bytes
-rw-r--r--doc/user/admin_area/settings/img/import_export_rate_limits_v13_2.pngbin0 -> 54802 bytes
-rw-r--r--doc/user/admin_area/settings/import_export_rate_limits.md32
-rw-r--r--doc/user/admin_area/settings/index.md1
-rw-r--r--doc/user/admin_area/settings/rate_limit_on_issues_creation.md7
-rw-r--r--doc/user/admin_area/settings/sign_in_restrictions.md27
-rw-r--r--doc/user/admin_area/settings/visibility_and_access_controls.md12
-rw-r--r--doc/user/analytics/value_stream_analytics.md6
-rw-r--r--doc/user/application_security/configuration/index.md16
-rw-r--r--doc/user/application_security/container_scanning/img/container_scanning_v13_0.pngbin33010 -> 0 bytes
-rw-r--r--doc/user/application_security/container_scanning/img/container_scanning_v13_2.pngbin0 -> 8658 bytes
-rw-r--r--doc/user/application_security/container_scanning/index.md25
-rw-r--r--doc/user/application_security/coverage_fuzzing/index.md117
-rw-r--r--doc/user/application_security/dast/img/dast_all_v13_0.pngbin32346 -> 0 bytes
-rw-r--r--doc/user/application_security/dast/img/dast_on_demand_v13_2.pngbin0 -> 91775 bytes
-rw-r--r--doc/user/application_security/dast/img/dast_v13_2.pngbin0 -> 6763 bytes
-rw-r--r--doc/user/application_security/dast/index.md190
-rw-r--r--doc/user/application_security/dependency_scanning/analyzers.md7
-rw-r--r--doc/user/application_security/dependency_scanning/img/dependency_scanning_v13_0.pngbin44921 -> 0 bytes
-rw-r--r--doc/user/application_security/dependency_scanning/img/dependency_scanning_v13_2.pngbin0 -> 10289 bytes
-rw-r--r--doc/user/application_security/dependency_scanning/index.md13
-rw-r--r--doc/user/application_security/img/security_configuration_page_v13_1.pngbin199472 -> 0 bytes
-rw-r--r--doc/user/application_security/img/security_configuration_page_v13_2.pngbin0 -> 51691 bytes
-rw-r--r--doc/user/application_security/index.md54
-rw-r--r--doc/user/application_security/sast/analyzers.md37
-rw-r--r--doc/user/application_security/sast/img/sast_v13_0.pngbin29907 -> 0 bytes
-rw-r--r--doc/user/application_security/sast/img/sast_v13_2.pngbin0 -> 7703 bytes
-rw-r--r--doc/user/application_security/sast/index.md104
-rw-r--r--doc/user/application_security/secret_detection/img/secret-detection-merge-request-ui.pngbin100409 -> 0 bytes
-rw-r--r--doc/user/application_security/secret_detection/img/secret_detection_v13_2.pngbin0 -> 5863 bytes
-rw-r--r--doc/user/application_security/secret_detection/index.md21
-rw-r--r--doc/user/application_security/security_dashboard/img/group_security_dashboard_export_csv_v13_1.pngbin536756 -> 105028 bytes
-rw-r--r--doc/user/application_security/security_dashboard/img/group_security_dashboard_v13_0.pngbin69236 -> 0 bytes
-rw-r--r--doc/user/application_security/security_dashboard/img/group_security_dashboard_v13_2_noNav.pngbin0 -> 53913 bytes
-rw-r--r--doc/user/application_security/security_dashboard/img/instance_security_dashboard_with_projects_v13_0.pngbin58505 -> 0 bytes
-rw-r--r--doc/user/application_security/security_dashboard/img/instance_security_dashboard_with_projects_v13_2_sm.pngbin0 -> 58332 bytes
-rw-r--r--doc/user/application_security/security_dashboard/img/pipeline_security_dashboard_v12_6.pngbin59799 -> 0 bytes
-rw-r--r--doc/user/application_security/security_dashboard/img/pipeline_security_dashboard_v13_2.pngbin0 -> 73101 bytes
-rw-r--r--doc/user/application_security/security_dashboard/img/project_security_dashboard_v13_2.pngbin0 -> 78549 bytes
-rw-r--r--doc/user/application_security/security_dashboard/img/standalone_vulnerability_page_v13_1.pngbin0 -> 79341 bytes
-rw-r--r--doc/user/application_security/security_dashboard/img/vulnerability_list_table_v13_1.pngbin0 -> 74381 bytes
-rw-r--r--doc/user/application_security/security_dashboard/index.md129
-rw-r--r--doc/user/application_security/threat_monitoring/index.md11
-rw-r--r--doc/user/application_security/vulnerabilities/img/standalone_vulnerability_page_v12_10.pngbin26548 -> 0 bytes
-rw-r--r--doc/user/application_security/vulnerabilities/img/standalone_vulnerability_page_v13_1.pngbin0 -> 110282 bytes
-rw-r--r--doc/user/application_security/vulnerabilities/index.md8
-rw-r--r--doc/user/asciidoc.md78
-rw-r--r--doc/user/clusters/applications.md333
-rw-r--r--doc/user/clusters/crossplane.md400
-rw-r--r--doc/user/clusters/management_project.md2
-rw-r--r--doc/user/compliance/compliance_dashboard/img/compliance_dashboard_v12_10.pngbin98355 -> 0 bytes
-rw-r--r--doc/user/compliance/compliance_dashboard/img/compliance_dashboard_v13_2.pngbin0 -> 84922 bytes
-rw-r--r--doc/user/compliance/compliance_dashboard/index.md3
-rw-r--r--doc/user/compliance/license_compliance/img/policies_maintainer_add_v13_0.pngbin22079 -> 0 bytes
-rw-r--r--doc/user/compliance/license_compliance/img/policies_maintainer_add_v13_2.pngbin0 -> 13419 bytes
-rw-r--r--doc/user/compliance/license_compliance/img/policies_maintainer_edit_v13_0.pngbin40712 -> 0 bytes
-rw-r--r--doc/user/compliance/license_compliance/img/policies_maintainer_edit_v13_2.pngbin0 -> 20327 bytes
-rw-r--r--doc/user/compliance/license_compliance/index.md149
-rw-r--r--doc/user/discussions/index.md21
-rw-r--r--doc/user/gitlab_com/index.md41
-rw-r--r--doc/user/group/bulk_editing/img/bulk-editing.pngbin99844 -> 0 bytes
-rw-r--r--doc/user/group/bulk_editing/img/bulk-editing_v13_2.pngbin0 -> 123971 bytes
-rw-r--r--doc/user/group/bulk_editing/index.md8
-rw-r--r--doc/user/group/clusters/index.md13
-rw-r--r--doc/user/group/epics/img/epic_activity_sort_order_v13_2.pngbin0 -> 20531 bytes
-rw-r--r--doc/user/group/epics/img/epics_list_view_v12.5.pngbin116123 -> 0 bytes
-rw-r--r--doc/user/group/epics/img/new_epic_form_v13.2.pngbin0 -> 96690 bytes
-rw-r--r--doc/user/group/epics/img/new_epic_from_groups_v13.2.pngbin0 -> 78168 bytes
-rw-r--r--doc/user/group/epics/index.md25
-rw-r--r--doc/user/group/epics/manage_epics.md71
-rw-r--r--doc/user/group/index.md64
-rw-r--r--doc/user/group/iterations/index.md29
-rw-r--r--doc/user/group/roadmap/img/roadmap_view_v13_0.pngbin55012 -> 0 bytes
-rw-r--r--doc/user/group/roadmap/img/roadmap_view_v13_2.pngbin0 -> 55061 bytes
-rw-r--r--doc/user/group/roadmap/index.md14
-rw-r--r--doc/user/group/saml_sso/group_managed_accounts.md116
-rw-r--r--doc/user/group/saml_sso/index.md279
-rw-r--r--doc/user/group/saml_sso/scim_setup.md10
-rw-r--r--doc/user/group/subgroups/index.md18
-rw-r--r--doc/user/incident_management/img/pagerduty_incidents_integration_13_2.pngbin0 -> 34698 bytes
-rw-r--r--doc/user/incident_management/index.md43
-rw-r--r--doc/user/index.md4
-rw-r--r--doc/user/infrastructure/img/terraform_plan_widget_v13_2.pngbin0 -> 33916 bytes
-rw-r--r--doc/user/infrastructure/index.md211
-rw-r--r--doc/user/markdown.md48
-rw-r--r--doc/user/packages/composer_repository/index.md4
-rw-r--r--doc/user/packages/conan_repository/index.md10
-rw-r--r--doc/user/packages/container_registry/img/expiration_policy_app_v13_0.pngbin61601 -> 0 bytes
-rw-r--r--doc/user/packages/container_registry/index.md103
-rw-r--r--doc/user/packages/img/group_packages_list_v13_0.pngbin50889 -> 0 bytes
-rw-r--r--doc/user/packages/img/package_detail_v13_0.pngbin46047 -> 0 bytes
-rw-r--r--doc/user/packages/img/project_packages_list_v13_0.pngbin52752 -> 0 bytes
-rw-r--r--doc/user/packages/index.md128
-rw-r--r--doc/user/packages/maven_repository/index.md12
-rw-r--r--doc/user/packages/npm_registry/index.md9
-rw-r--r--doc/user/packages/nuget_repository/index.md2
-rw-r--r--doc/user/packages/pypi_repository/index.md2
-rw-r--r--doc/user/permissions.md18
-rw-r--r--doc/user/profile/account/delete_account.md3
-rw-r--r--doc/user/profile/account/two_factor_authentication.md6
-rw-r--r--doc/user/profile/index.md2
-rw-r--r--doc/user/profile/notifications.md2
-rw-r--r--doc/user/profile/personal_access_tokens.md5
-rw-r--r--doc/user/profile/preferences.md5
-rw-r--r--doc/user/profile/unknown_sign_in_notification.md12
-rw-r--r--doc/user/project/bulk_editing.md6
-rw-r--r--doc/user/project/clusters/add_remove_clusters.md80
-rw-r--r--doc/user/project/clusters/img/rbac.pngbin15960 -> 0 bytes
-rw-r--r--doc/user/project/clusters/img/rbac_v13_1.pngbin0 -> 10680 bytes
-rw-r--r--doc/user/project/clusters/index.md281
-rw-r--r--doc/user/project/clusters/kubernetes_pod_logs.md4
-rw-r--r--doc/user/project/clusters/runbooks/img/helm-install.pngbin71705 -> 0 bytes
-rw-r--r--doc/user/project/clusters/runbooks/index.md10
-rw-r--r--doc/user/project/clusters/securing.md154
-rw-r--r--doc/user/project/clusters/serverless/aws.md12
-rw-r--r--doc/user/project/clusters/serverless/index.md50
-rw-r--r--doc/user/project/code_intelligence.md54
-rw-r--r--doc/user/project/code_owners.md163
-rw-r--r--doc/user/project/deploy_tokens/index.md27
-rw-r--r--doc/user/project/description_templates.md2
-rw-r--r--doc/user/project/highlighting.md10
-rw-r--r--doc/user/project/img/bulk-editing.pngbin197667 -> 0 bytes
-rw-r--r--doc/user/project/img/bulk-editing_v13_2.pngbin0 -> 132734 bytes
-rw-r--r--doc/user/project/img/code_intelligence_v13_1.pngbin0 -> 83690 bytes
-rw-r--r--doc/user/project/img/sectional_code_owners_v13.2.pngbin0 -> 106361 bytes
-rw-r--r--doc/user/project/import/gemnasium.md2
-rw-r--r--doc/user/project/import/img/jira/import_issues_from_jira_button_v12_10.pngbin8504 -> 8422 bytes
-rw-r--r--doc/user/project/import/img/jira/import_issues_from_jira_form_v12_10.pngbin116641 -> 56306 bytes
-rw-r--r--doc/user/project/import/img/jira/import_issues_from_jira_form_v13_2.pngbin0 -> 108152 bytes
-rw-r--r--doc/user/project/import/img/jira/import_issues_from_jira_projects_v12_10.pngbin521845 -> 0 bytes
-rw-r--r--doc/user/project/import/jira.md33
-rw-r--r--doc/user/project/index.md93
-rw-r--r--doc/user/project/insights/index.md21
-rw-r--r--doc/user/project/integrations/bugzilla.md1
-rw-r--r--doc/user/project/integrations/custom_issue_tracker.md2
-rw-r--r--doc/user/project/integrations/generic_alerts.md63
-rw-r--r--doc/user/project/integrations/github.md2
-rw-r--r--doc/user/project/integrations/img/actions_menu_create_new_dashboard_v13_2.pngbin0 -> 11479 bytes
-rw-r--r--doc/user/project/integrations/img/heatmap_chart_too_much_data_v_13_2.pngbin0 -> 7310 bytes
-rw-r--r--doc/user/project/integrations/img/jira/open_jira_issues_list_v13.2.pngbin0 -> 90251 bytes
-rw-r--r--doc/user/project/integrations/img/jira_service_page_v12_2.pngbin57327 -> 0 bytes
-rw-r--r--doc/user/project/integrations/img/metrics_settings_button_v13_2.pngbin0 -> 1901 bytes
-rw-r--r--doc/user/project/integrations/img/prometheus_manual_configuration_v13_2.pngbin0 -> 15651 bytes
-rw-r--r--doc/user/project/integrations/img/prometheus_service_configuration.pngbin5022 -> 0 bytes
-rw-r--r--doc/user/project/integrations/jira.md95
-rw-r--r--doc/user/project/integrations/jira_cloud_configuration.md2
-rw-r--r--doc/user/project/integrations/jira_server_configuration.md2
-rw-r--r--doc/user/project/integrations/overview.md21
-rw-r--r--doc/user/project/integrations/prometheus.md1124
-rw-r--r--doc/user/project/integrations/prometheus_library/nginx_ingress.md3
-rw-r--r--doc/user/project/integrations/prometheus_library/nginx_ingress_vts.md3
-rw-r--r--doc/user/project/integrations/prometheus_units.md174
-rw-r--r--doc/user/project/integrations/redmine.md1
-rw-r--r--doc/user/project/integrations/services_templates.md27
-rw-r--r--doc/user/project/integrations/slack.md60
-rw-r--r--doc/user/project/integrations/youtrack.md1
-rw-r--r--doc/user/project/issue_board.md24
-rw-r--r--doc/user/project/issues/crosslinking_issues.md3
-rw-r--r--doc/user/project/issues/csv_import.md3
-rw-r--r--doc/user/project/issues/design_management.md71
-rw-r--r--doc/user/project/issues/img/design_drag_and_drop_uploads_v13_2.pngbin0 -> 1260905 bytes
-rw-r--r--doc/user/project/issues/img/design_management_upload_v13.2.pngbin0 -> 62146 bytes
-rw-r--r--doc/user/project/issues/img/design_management_v13_2.pngbin0 -> 1017975 bytes
-rw-r--r--doc/user/project/issues/index.md2
-rw-r--r--doc/user/project/issues/issue_data_and_actions.md4
-rw-r--r--doc/user/project/issues/managing_issues.md5
-rw-r--r--doc/user/project/labels.md17
-rw-r--r--doc/user/project/members/index.md27
-rw-r--r--doc/user/project/merge_requests/accessibility_testing.md8
-rw-r--r--doc/user/project/merge_requests/browser_performance_testing.md164
-rw-r--r--doc/user/project/merge_requests/code_quality.md56
-rw-r--r--doc/user/project/merge_requests/fail_fast_testing.md87
-rw-r--r--doc/user/project/merge_requests/getting_started.md2
-rw-r--r--doc/user/project/merge_requests/img/browser_performance_testing.pngbin52100 -> 95312 bytes
-rw-r--r--doc/user/project/merge_requests/img/draft_blocked_merge_button_v13_2.pngbin0 -> 32770 bytes
-rw-r--r--doc/user/project/merge_requests/img/file_by_file_v13_2.pngbin0 -> 81874 bytes
-rw-r--r--doc/user/project/merge_requests/img/load_performance_testing.pngbin0 -> 60196 bytes
-rw-r--r--doc/user/project/merge_requests/img/merge_when_pipeline_succeeds_only_if_succeeds_msg.pngbin5237 -> 0 bytes
-rw-r--r--doc/user/project/merge_requests/img/wip_blocked_accept_button.pngbin4970 -> 0 bytes
-rw-r--r--doc/user/project/merge_requests/index.md2
-rw-r--r--doc/user/project/merge_requests/load_performance_testing.md197
-rw-r--r--doc/user/project/merge_requests/merge_request_dependencies.md6
-rw-r--r--doc/user/project/merge_requests/merge_when_pipeline_succeeds.md87
-rw-r--r--doc/user/project/merge_requests/reviewing_and_managing_merge_requests.md37
-rw-r--r--doc/user/project/merge_requests/squash_and_merge.md54
-rw-r--r--doc/user/project/merge_requests/test_coverage_visualization.md3
-rw-r--r--doc/user/project/merge_requests/testing_and_reports_in_merge_requests.md8
-rw-r--r--doc/user/project/merge_requests/work_in_progress_merge_requests.md34
-rw-r--r--doc/user/project/milestones/index.md52
-rw-r--r--doc/user/project/operations/alert_management.md130
-rw-r--r--doc/user/project/operations/dashboard_settings.md5
-rw-r--r--doc/user/project/operations/feature_flags.md260
-rw-r--r--doc/user/project/operations/img/alert_detail_metrics_v13_2.pngbin0 -> 27616 bytes
-rw-r--r--doc/user/project/operations/img/alert_list_search_v13_1.pngbin0 -> 12166 bytes
-rw-r--r--doc/user/project/operations/img/alert_list_sort_v13_1.pngbin0 -> 13919 bytes
-rw-r--r--doc/user/project/operations/img/alert_list_v13_1.pngbin0 -> 38265 bytes
-rw-r--r--doc/user/project/operations/img/alert_management_1_v13_0.pngbin19152 -> 0 bytes
-rw-r--r--doc/user/project/operations/img/alert_management_1_v13_1.pngbin40053 -> 0 bytes
-rw-r--r--doc/user/project/operations/index.md16
-rw-r--r--doc/user/project/operations/tracing.md39
-rw-r--r--doc/user/project/pages/custom_domains_ssl_tls_certification/index.md2
-rw-r--r--doc/user/project/pages/getting_started/fork_sample_project.md55
-rw-r--r--doc/user/project/pages/getting_started/new_or_existing_website.md48
-rw-r--r--doc/user/project/pages/getting_started/pages_bundled_template.md33
-rw-r--r--doc/user/project/pages/getting_started/pages_ci_cd_template.md49
-rw-r--r--doc/user/project/pages/getting_started/pages_forked_sample_project.md56
-rw-r--r--doc/user/project/pages/getting_started/pages_from_scratch.md402
-rw-r--r--doc/user/project/pages/getting_started/pages_new_project_template.md34
-rw-r--r--doc/user/project/pages/getting_started_part_four.md401
-rw-r--r--doc/user/project/pages/index.md10
-rw-r--r--doc/user/project/pages/introduction.md20
-rw-r--r--doc/user/project/protected_tags.md2
-rw-r--r--doc/user/project/quick_actions.md15
-rw-r--r--doc/user/project/releases/img/custom_notifications_dropdown_v12_5.pngbin14983 -> 0 bytes
-rw-r--r--doc/user/project/releases/img/custom_notifications_new_release_v12_5.pngbin20811 -> 0 bytes
-rw-r--r--doc/user/project/releases/img/edit_release_page_v13_0.pngbin285708 -> 0 bytes
-rw-r--r--doc/user/project/releases/img/milestone_list_with_releases_v12_5.pngbin45454 -> 105666 bytes
-rw-r--r--doc/user/project/releases/img/milestone_with_releases_v12_5.pngbin67529 -> 0 bytes
-rw-r--r--doc/user/project/releases/img/new_tag_12_5.pngbin41120 -> 0 bytes
-rw-r--r--doc/user/project/releases/img/release_edit_button_v12_6.pngbin25953 -> 0 bytes
-rw-r--r--doc/user/project/releases/img/release_milestone_dropdown_v13_0.pngbin138986 -> 0 bytes
-rw-r--r--doc/user/project/releases/img/release_with_milestone_v12_9.pngbin27783 -> 57689 bytes
-rw-r--r--doc/user/project/releases/img/releases_count_v13_2.pngbin0 -> 27254 bytes
-rw-r--r--doc/user/project/releases/img/releases_v12_9.pngbin51974 -> 0 bytes
-rw-r--r--doc/user/project/releases/img/tags_12_5.pngbin31541 -> 0 bytes
-rw-r--r--doc/user/project/releases/index.md478
-rw-r--r--doc/user/project/repository/branches/index.md50
-rw-r--r--doc/user/project/repository/index.md3
-rw-r--r--doc/user/project/repository/reducing_the_repo_size_using_git.md33
-rw-r--r--doc/user/project/repository/repository_mirroring.md8
-rw-r--r--doc/user/project/repository/x509_signed_commits/index.md6
-rw-r--r--doc/user/project/requirements/img/requirement_archive_view_v12_10.pngbin112233 -> 0 bytes
-rw-r--r--doc/user/project/requirements/img/requirement_create_view_v12_10.pngbin124402 -> 0 bytes
-rw-r--r--doc/user/project/requirements/img/requirement_edit_view_v12_10.pngbin118066 -> 0 bytes
-rw-r--r--doc/user/project/requirements/img/requirements_archived_list_view_v12_10.pngbin68623 -> 0 bytes
-rw-r--r--doc/user/project/requirements/img/requirements_archived_list_view_v13_1.pngbin0 -> 47662 bytes
-rw-r--r--doc/user/project/requirements/img/requirements_list_v13_1.pngbin0 -> 113403 bytes
-rw-r--r--doc/user/project/requirements/img/requirements_list_view_v12_10.pngbin117250 -> 0 bytes
-rw-r--r--doc/user/project/requirements/index.md70
-rw-r--r--doc/user/project/service_desk.md39
-rw-r--r--doc/user/project/settings/import_export.md15
-rw-r--r--doc/user/project/settings/index.md21
-rw-r--r--doc/user/project/static_site_editor/index.md2
-rw-r--r--doc/user/project/status_page/index.md6
-rw-r--r--doc/user/project/web_ide/index.md27
-rw-r--r--doc/user/project/wiki/img/wiki_page_diffs_v13_2.pngbin0 -> 53576 bytes
-rw-r--r--doc/user/project/wiki/img/wiki_page_history.pngbin12101 -> 13456 bytes
-rw-r--r--doc/user/project/wiki/index.md55
-rw-r--r--doc/user/search/advanced_global_search.md7
-rw-r--r--doc/user/search/advanced_search_syntax.md8
-rw-r--r--doc/user/shortcuts.md9
-rw-r--r--doc/user/snippets.md8
-rw-r--r--doc/user/todos.md3
-rw-r--r--haml_lint/linter/documentation_links.rb100
-rw-r--r--jest.config.base.js4
-rw-r--r--lib/api/access_requests.rb2
-rw-r--r--lib/api/admin/ci/variables.rb2
-rw-r--r--lib/api/admin/instance_clusters.rb134
-rw-r--r--lib/api/admin/sidekiq.rb2
-rw-r--r--lib/api/api.rb27
-rw-r--r--lib/api/api_guard.rb12
-rw-r--r--lib/api/appearance.rb2
-rw-r--r--lib/api/applications.rb2
-rw-r--r--lib/api/avatar.rb2
-rw-r--r--lib/api/award_emoji.rb2
-rw-r--r--lib/api/badges.rb2
-rw-r--r--lib/api/boards.rb2
-rw-r--r--lib/api/branches.rb13
-rw-r--r--lib/api/broadcast_messages.rb2
-rw-r--r--lib/api/ci/pipeline_schedules.rb217
-rw-r--r--lib/api/ci/pipelines.rb189
-rw-r--r--lib/api/ci/runner.rb318
-rw-r--r--lib/api/ci/runners.rb289
-rw-r--r--lib/api/commit_statuses.rb4
-rw-r--r--lib/api/commits.rb2
-rw-r--r--lib/api/composer_packages.rb156
-rw-r--r--lib/api/conan_packages.rb309
-rw-r--r--lib/api/container_registry_event.rb2
-rw-r--r--lib/api/deploy_keys.rb2
-rw-r--r--lib/api/deploy_tokens.rb6
-rw-r--r--lib/api/deployments.rb2
-rw-r--r--lib/api/discussions.rb18
-rw-r--r--lib/api/entities/approvals.rb9
-rw-r--r--lib/api/entities/basic_project_details.rb3
-rw-r--r--lib/api/entities/conan_package/conan_package_manifest.rb11
-rw-r--r--lib/api/entities/conan_package/conan_package_snapshot.rb11
-rw-r--r--lib/api/entities/conan_package/conan_recipe_manifest.rb11
-rw-r--r--lib/api/entities/conan_package/conan_recipe_snapshot.rb11
-rw-r--r--lib/api/entities/conan_package/conan_upload_urls.rb11
-rw-r--r--lib/api/entities/entity_helpers.rb19
-rw-r--r--lib/api/entities/go_module_version.rb10
-rw-r--r--lib/api/entities/group.rb1
-rw-r--r--lib/api/entities/group_detail.rb1
-rw-r--r--lib/api/entities/issuable_entity.rb36
-rw-r--r--lib/api/entities/issue_basic.rb8
-rw-r--r--lib/api/entities/merge_request_approvals.rb24
-rw-r--r--lib/api/entities/merge_request_basic.rb21
-rw-r--r--lib/api/entities/npm_package.rb11
-rw-r--r--lib/api/entities/npm_package_tag.rb9
-rw-r--r--lib/api/entities/nuget/dependency.rb14
-rw-r--r--lib/api/entities/nuget/dependency_group.rb14
-rw-r--r--lib/api/entities/nuget/metadatum.rb13
-rw-r--r--lib/api/entities/nuget/package_metadata.rb13
-rw-r--r--lib/api/entities/nuget/package_metadata_catalog_entry.rb19
-rw-r--r--lib/api/entities/nuget/packages_metadata.rb12
-rw-r--r--lib/api/entities/nuget/packages_metadata_item.rb15
-rw-r--r--lib/api/entities/nuget/packages_versions.rb11
-rw-r--r--lib/api/entities/nuget/search_result.rb21
-rw-r--r--lib/api/entities/nuget/search_result_version.rb13
-rw-r--r--lib/api/entities/nuget/search_results.rb12
-rw-r--r--lib/api/entities/nuget/service_index.rb12
-rw-r--r--lib/api/entities/package.rb42
-rw-r--r--lib/api/entities/package/pipeline.rb11
-rw-r--r--lib/api/entities/package_file.rb11
-rw-r--r--lib/api/entities/package_version.rb14
-rw-r--r--lib/api/entities/project.rb2
-rw-r--r--lib/api/entities/project_statistics.rb1
-rw-r--r--lib/api/entities/release.rb9
-rw-r--r--lib/api/entities/resource_state_event.rb18
-rw-r--r--lib/api/entities/snippet.rb12
-rw-r--r--lib/api/entities/user.rb2
-rw-r--r--lib/api/environments.rb2
-rw-r--r--lib/api/error_tracking.rb2
-rw-r--r--lib/api/events.rb2
-rw-r--r--lib/api/features.rb2
-rw-r--r--lib/api/files.rb4
-rw-r--r--lib/api/freeze_periods.rb2
-rwxr-xr-xlib/api/go_proxy.rb135
-rw-r--r--lib/api/group_boards.rb2
-rw-r--r--lib/api/group_clusters.rb18
-rw-r--r--lib/api/group_container_repositories.rb2
-rw-r--r--lib/api/group_export.rb2
-rw-r--r--lib/api/group_import.rb2
-rw-r--r--lib/api/group_labels.rb2
-rw-r--r--lib/api/group_milestones.rb6
-rw-r--r--lib/api/group_packages.rb44
-rw-r--r--lib/api/group_variables.rb6
-rw-r--r--lib/api/groups.rb11
-rw-r--r--lib/api/helpers.rb33
-rw-r--r--lib/api/helpers/common_helpers.rb20
-rw-r--r--lib/api/helpers/internal_helpers.rb4
-rw-r--r--lib/api/helpers/merge_requests_helpers.rb40
-rw-r--r--lib/api/helpers/packages/basic_auth_helpers.rb57
-rw-r--r--lib/api/helpers/packages/conan/api_helpers.rb225
-rw-r--r--lib/api/helpers/packages/dependency_proxy_helpers.rb36
-rw-r--r--lib/api/helpers/packages_helpers.rb52
-rw-r--r--lib/api/helpers/packages_manager_clients_helpers.rb63
-rw-r--r--lib/api/helpers/projects_helpers.rb6
-rw-r--r--lib/api/helpers/runner.rb7
-rw-r--r--lib/api/helpers/services_helpers.rb33
-rw-r--r--lib/api/helpers/snippets_helpers.rb26
-rw-r--r--lib/api/helpers/users_helpers.rb7
-rw-r--r--lib/api/helpers/wikis_helpers.rb35
-rw-r--r--lib/api/import_bitbucket_server.rb44
-rw-r--r--lib/api/import_github.rb2
-rw-r--r--lib/api/internal/base.rb8
-rw-r--r--lib/api/internal/pages.rb2
-rw-r--r--lib/api/issues.rb41
-rw-r--r--lib/api/job_artifacts.rb2
-rw-r--r--lib/api/jobs.rb4
-rw-r--r--lib/api/keys.rb2
-rw-r--r--lib/api/labels.rb2
-rw-r--r--lib/api/lint.rb2
-rw-r--r--lib/api/markdown.rb2
-rw-r--r--lib/api/maven_packages.rb251
-rw-r--r--lib/api/members.rb12
-rw-r--r--lib/api/merge_request_approvals.rb78
-rw-r--r--lib/api/merge_request_diffs.rb2
-rw-r--r--lib/api/merge_requests.rb31
-rw-r--r--lib/api/metrics/dashboard/annotations.rb2
-rw-r--r--lib/api/metrics/user_starred_dashboards.rb2
-rw-r--r--lib/api/milestone_responses.rb2
-rw-r--r--lib/api/namespaces.rb2
-rw-r--r--lib/api/notes.rb4
-rw-r--r--lib/api/notification_settings.rb2
-rw-r--r--lib/api/npm_packages.rb173
-rw-r--r--lib/api/nuget_packages.rb221
-rw-r--r--lib/api/package_files.rb33
-rw-r--r--lib/api/pages.rb2
-rw-r--r--lib/api/pages_domains.rb2
-rw-r--r--lib/api/pagination_params.rb2
-rw-r--r--lib/api/pipeline_schedules.rb215
-rw-r--r--lib/api/pipelines.rb187
-rw-r--r--lib/api/project_clusters.rb18
-rw-r--r--lib/api/project_container_repositories.rb2
-rw-r--r--lib/api/project_events.rb2
-rw-r--r--lib/api/project_export.rb2
-rw-r--r--lib/api/project_hooks.rb2
-rw-r--r--lib/api/project_import.rb2
-rw-r--r--lib/api/project_milestones.rb6
-rw-r--r--lib/api/project_packages.rb71
-rw-r--r--lib/api/project_repository_storage_moves.rb2
-rw-r--r--lib/api/project_snapshots.rb2
-rw-r--r--lib/api/project_snippets.rb23
-rw-r--r--lib/api/project_statistics.rb2
-rw-r--r--lib/api/project_templates.rb2
-rw-r--r--lib/api/projects.rb32
-rw-r--r--lib/api/projects_relation_builder.rb9
-rw-r--r--lib/api/protected_branches.rb2
-rw-r--r--lib/api/protected_tags.rb2
-rw-r--r--lib/api/pypi_packages.rb148
-rw-r--r--lib/api/release/links.rb2
-rw-r--r--lib/api/releases.rb4
-rw-r--r--lib/api/remote_mirrors.rb2
-rw-r--r--lib/api/repositories.rb4
-rw-r--r--lib/api/resource_label_events.rb2
-rw-r--r--lib/api/resource_milestone_events.rb5
-rw-r--r--lib/api/resource_state_events.rb50
-rw-r--r--lib/api/runner.rb297
-rw-r--r--lib/api/runners.rb287
-rw-r--r--lib/api/search.rb5
-rw-r--r--lib/api/services.rb2
-rw-r--r--lib/api/settings.rb11
-rw-r--r--lib/api/sidekiq_metrics.rb2
-rw-r--r--lib/api/snippets.rb28
-rw-r--r--lib/api/statistics.rb2
-rw-r--r--lib/api/submodules.rb2
-rw-r--r--lib/api/subscriptions.rb2
-rw-r--r--lib/api/suggestions.rb4
-rw-r--r--lib/api/system_hooks.rb2
-rw-r--r--lib/api/tags.rb2
-rw-r--r--lib/api/templates.rb2
-rw-r--r--lib/api/terraform/state.rb12
-rw-r--r--lib/api/todos.rb2
-rw-r--r--lib/api/triggers.rb4
-rw-r--r--lib/api/user_counts.rb2
-rw-r--r--lib/api/users.rb35
-rw-r--r--lib/api/validations/types/comma_separated_to_array.rb2
-rw-r--r--lib/api/validations/types/comma_separated_to_integer_array.rb15
-rw-r--r--lib/api/validations/types/labels_list.rb24
-rw-r--r--lib/api/validations/types/safe_file.rb15
-rw-r--r--lib/api/validations/types/workhorse_file.rb13
-rw-r--r--lib/api/variables.rb32
-rw-r--r--lib/api/version.rb2
-rw-r--r--lib/api/wikis.rb206
-rw-r--r--lib/backup/database.rb10
-rw-r--r--lib/banzai/filter/abstract_reference_filter.rb10
-rw-r--r--lib/banzai/filter/commit_trailers_filter.rb5
-rw-r--r--lib/banzai/filter/external_issue_reference_filter.rb6
-rw-r--r--lib/banzai/filter/inline_cluster_metrics_filter.rb40
-rw-r--r--lib/banzai/filter/inline_metrics_redactor_filter.rb4
-rw-r--r--lib/banzai/filter/jira_import/adf_to_commonmark_filter.rb24
-rw-r--r--lib/banzai/filter/project_reference_filter.rb6
-rw-r--r--lib/banzai/filter/reference_filter.rb87
-rw-r--r--lib/banzai/filter/table_of_contents_filter.rb11
-rw-r--r--lib/banzai/filter/user_reference_filter.rb6
-rw-r--r--lib/banzai/pipeline/gfm_pipeline.rb3
-rw-r--r--lib/banzai/pipeline/jira_import/adf_commonmark_pipeline.rb15
-rw-r--r--lib/container_registry/tag.rb7
-rw-r--r--lib/declarative_policy/base.rb25
-rw-r--r--lib/event_filter.rb21
-rw-r--r--lib/feature.rb41
-rw-r--r--lib/feature/definition.rb137
-rw-r--r--lib/feature/shared.rb33
-rw-r--r--lib/gitlab/action_cable/config.rb17
-rw-r--r--lib/gitlab/alert_management/alert_params.rb6
-rw-r--r--lib/gitlab/alert_management/fingerprint.rb16
-rw-r--r--lib/gitlab/alerting/notification_payload_parser.rb11
-rw-r--r--lib/gitlab/analytics/cycle_analytics/records_fetcher.rb4
-rw-r--r--lib/gitlab/analytics/cycle_analytics/stage_events.rb2
-rw-r--r--lib/gitlab/analytics/cycle_analytics/stage_events/code_stage_start.rb5
-rw-r--r--lib/gitlab/analytics/cycle_analytics/stage_events/issue_stage_end.rb9
-rw-r--r--lib/gitlab/analytics/cycle_analytics/stage_events/metrics_based_stage_event.rb5
-rw-r--r--lib/gitlab/analytics/cycle_analytics/stage_events/plan_stage_start.rb9
-rw-r--r--lib/gitlab/analytics/cycle_analytics/stage_events/production_stage_end.rb5
-rw-r--r--lib/gitlab/analytics/cycle_analytics/stage_events/stage_event.rb8
-rw-r--r--lib/gitlab/analytics/cycle_analytics/stage_query_helpers.rb23
-rw-r--r--lib/gitlab/analytics/unique_visits.rb60
-rw-r--r--lib/gitlab/application_rate_limiter.rb24
-rw-r--r--lib/gitlab/auth/auth_finders.rb26
-rw-r--r--lib/gitlab/background_migration.rb1
-rw-r--r--lib/gitlab/background_migration/backfill_namespace_settings.rb18
-rw-r--r--lib/gitlab/background_migration/cleanup_concurrent_schema_change.rb8
-rw-r--r--lib/gitlab/background_migration/digest_column.rb25
-rw-r--r--lib/gitlab/background_migration/encrypt_columns.rb104
-rw-r--r--lib/gitlab/background_migration/encrypt_runners_tokens.rb32
-rw-r--r--lib/gitlab/background_migration/fix_pages_access_level.rb2
-rw-r--r--lib/gitlab/background_migration/mailers/unconfirm_mailer.rb24
-rw-r--r--lib/gitlab/background_migration/mailers/views/unconfirm_mailer/unconfirm_notification_email.html.haml19
-rw-r--r--lib/gitlab/background_migration/mailers/views/unconfirm_mailer/unconfirm_notification_email.text.erb14
-rw-r--r--lib/gitlab/background_migration/models/encrypt_columns/namespace.rb28
-rw-r--r--lib/gitlab/background_migration/models/encrypt_columns/project.rb28
-rw-r--r--lib/gitlab/background_migration/models/encrypt_columns/runner.rb28
-rw-r--r--lib/gitlab/background_migration/models/encrypt_columns/settings.rb37
-rw-r--r--lib/gitlab/background_migration/models/encrypt_columns/web_hook.rb28
-rw-r--r--lib/gitlab/background_migration/populate_project_snippet_statistics.rb61
-rw-r--r--lib/gitlab/background_migration/update_vulnerabilities_from_dismissal_feedback.rb13
-rw-r--r--lib/gitlab/background_migration/wrongfully_confirmed_email_unconfirmer.rb97
-rw-r--r--lib/gitlab/bitbucket_import/importer.rb11
-rw-r--r--lib/gitlab/bitbucket_import/metrics.rb41
-rw-r--r--lib/gitlab/bitbucket_server_import/importer.rb11
-rw-r--r--lib/gitlab/cache/ci/project_pipeline_status.rb11
-rw-r--r--lib/gitlab/ci/build/releaser.rb17
-rw-r--r--lib/gitlab/ci/config.rb6
-rw-r--r--lib/gitlab/ci/config/entry/environment.rb2
-rw-r--r--lib/gitlab/ci/config/entry/job.rb4
-rw-r--r--lib/gitlab/ci/config/entry/processable.rb4
-rw-r--r--lib/gitlab/ci/config/entry/release.rb24
-rw-r--r--lib/gitlab/ci/config/entry/reports.rb9
-rw-r--r--lib/gitlab/ci/features.rb54
-rw-r--r--lib/gitlab/ci/parsers/terraform/tfplan.rb34
-rw-r--r--lib/gitlab/ci/pipeline/chain/build.rb6
-rw-r--r--lib/gitlab/ci/pipeline/chain/command.rb8
-rw-r--r--lib/gitlab/ci/pipeline/chain/config/content.rb1
-rw-r--r--lib/gitlab/ci/pipeline/chain/config/content/parameter.rb30
-rw-r--r--lib/gitlab/ci/pipeline/chain/config/content/source.rb2
-rw-r--r--lib/gitlab/ci/pipeline/chain/config/process.rb12
-rw-r--r--lib/gitlab/ci/pipeline/chain/create.rb4
-rw-r--r--lib/gitlab/ci/pipeline/chain/helpers.rb9
-rw-r--r--lib/gitlab/ci/pipeline/chain/metrics.rb35
-rw-r--r--lib/gitlab/ci/pipeline/chain/validate/abilities.rb2
-rw-r--r--lib/gitlab/ci/pipeline/chain/validate/repository.rb2
-rw-r--r--lib/gitlab/ci/pipeline/metrics.rb42
-rw-r--r--lib/gitlab/ci/pipeline/preloader.rb18
-rw-r--r--lib/gitlab/ci/reports/test_report_summary.rb49
-rw-r--r--lib/gitlab/ci/reports/test_suite.rb14
-rw-r--r--lib/gitlab/ci/reports/test_suite_summary.rb49
-rw-r--r--lib/gitlab/ci/status/composite.rb4
-rw-r--r--lib/gitlab/ci/status/factory.rb2
-rw-r--r--lib/gitlab/ci/status/stage/play_manual.rb2
-rw-r--r--lib/gitlab/ci/templates/Android-Fastlane.gitlab-ci.yml2
-rw-r--r--lib/gitlab/ci/templates/Android.gitlab-ci.yml63
-rw-r--r--lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml3
-rw-r--r--lib/gitlab/ci/templates/Composer.gitlab-ci.yml19
-rw-r--r--lib/gitlab/ci/templates/Dart.gitlab-ci.yml22
-rw-r--r--lib/gitlab/ci/templates/Jobs/Browser-Performance-Testing.gitlab-ci.yml20
-rw-r--r--lib/gitlab/ci/templates/Jobs/Build.gitlab-ci.yml4
-rw-r--r--lib/gitlab/ci/templates/Jobs/Code-Quality.gitlab-ci.yml7
-rw-r--r--lib/gitlab/ci/templates/Jobs/DAST-Default-Branch-Deploy.gitlab-ci.yml3
-rw-r--r--lib/gitlab/ci/templates/Jobs/Deploy.gitlab-ci.yml13
-rw-r--r--lib/gitlab/ci/templates/Jobs/Deploy/ECS.gitlab-ci.yml42
-rw-r--r--lib/gitlab/ci/templates/Jobs/Load-Performance-Testing.gitlab-ci.yml29
-rw-r--r--lib/gitlab/ci/templates/Managed-Cluster-Applications.gitlab-ci.yml2
-rw-r--r--lib/gitlab/ci/templates/Security/Coverage-Fuzzing.gitlab-ci.yml34
-rw-r--r--lib/gitlab/ci/templates/Security/DAST.gitlab-ci.yml7
-rw-r--r--lib/gitlab/ci/templates/Security/Dependency-Scanning.gitlab-ci.yml16
-rw-r--r--lib/gitlab/ci/templates/Security/License-Scanning.gitlab-ci.yml4
-rw-r--r--lib/gitlab/ci/templates/Security/SAST.gitlab-ci.yml48
-rw-r--r--lib/gitlab/ci/templates/Security/Secret-Detection.gitlab-ci.yml28
-rw-r--r--lib/gitlab/ci/templates/Security/Secure-Binaries.gitlab-ci.yml11
-rw-r--r--lib/gitlab/ci/templates/Verify/Browser-Performance.gitlab-ci.yml12
-rw-r--r--lib/gitlab/ci/templates/Verify/Load-Performance-Testing.gitlab-ci.yml23
-rw-r--r--lib/gitlab/ci/templates/index.md3
-rw-r--r--lib/gitlab/ci/templates/npm.gitlab-ci.yml59
-rw-r--r--lib/gitlab/ci/yaml_processor.rb65
-rw-r--r--lib/gitlab/class_attributes.rb30
-rw-r--r--lib/gitlab/code_navigation_path.rb12
-rw-r--r--lib/gitlab/conan_token.rb64
-rw-r--r--lib/gitlab/config/entry/configurable.rb4
-rw-r--r--lib/gitlab/config/entry/node.rb11
-rw-r--r--lib/gitlab/config/loader/yaml.rb3
-rw-r--r--lib/gitlab/config_checker/external_database_checker.rb49
-rw-r--r--lib/gitlab/danger/changelog.rb14
-rw-r--r--lib/gitlab/danger/commit_linter.rb9
-rw-r--r--lib/gitlab/danger/helper.rb67
-rw-r--r--lib/gitlab/danger/roulette.rb45
-rw-r--r--lib/gitlab/danger/sidekiq_queues.rb37
-rw-r--r--lib/gitlab/danger/teammate.rb80
-rw-r--r--lib/gitlab/database.rb63
-rw-r--r--lib/gitlab/database/background_migration_job.rb38
-rw-r--r--lib/gitlab/database/dynamic_model_helpers.rb16
-rw-r--r--lib/gitlab/database/migration_helpers.rb158
-rw-r--r--lib/gitlab/database/migrations/background_migration_helpers.rb157
-rw-r--r--lib/gitlab/database/partitioning/monthly_strategy.rb96
-rw-r--r--lib/gitlab/database/partitioning/partition_creator.rb87
-rw-r--r--lib/gitlab/database/partitioning/time_partition.rb84
-rw-r--r--lib/gitlab/database/partitioning_migration_helpers/backfill_partitioned_table.rb105
-rw-r--r--lib/gitlab/database/partitioning_migration_helpers/foreign_key_helpers.rb2
-rw-r--r--lib/gitlab/database/partitioning_migration_helpers/table_management_helpers.rb165
-rw-r--r--lib/gitlab/database/schema_helpers.rb25
-rw-r--r--lib/gitlab/diff/file.rb8
-rw-r--r--lib/gitlab/diff/file_collection/base.rb27
-rw-r--r--lib/gitlab/diff/file_collection/merge_request_diff_base.rb33
-rw-r--r--lib/gitlab/diff/file_collection/wiki_page.rb24
-rw-r--r--lib/gitlab/diff/position_tracer.rb4
-rw-r--r--lib/gitlab/diff/position_tracer/base_strategy.rb1
-rw-r--r--lib/gitlab/diff/position_tracer/image_strategy.rb10
-rw-r--r--lib/gitlab/diff/position_tracer/line_strategy.rb30
-rw-r--r--lib/gitlab/diff/stats_cache.rb54
-rw-r--r--lib/gitlab/discussions_diff/file_collection.rb4
-rw-r--r--lib/gitlab/discussions_diff/highlight_cache.rb10
-rw-r--r--lib/gitlab/email/handler.rb5
-rw-r--r--lib/gitlab/email/handler/reply_processing.rb15
-rw-r--r--lib/gitlab/email/handler/service_desk_handler.rb152
-rw-r--r--lib/gitlab/email/service_desk_receiver.rb23
-rw-r--r--lib/gitlab/emoji.rb4
-rw-r--r--lib/gitlab/error_tracking.rb15
-rw-r--r--lib/gitlab/error_tracking/detailed_error.rb1
-rw-r--r--lib/gitlab/file_finder.rb2
-rw-r--r--lib/gitlab/git/commit.rb5
-rw-r--r--lib/gitlab/git/diff.rb24
-rw-r--r--lib/gitlab/git/repository.rb18
-rw-r--r--lib/gitlab/git/wiki.rb4
-rw-r--r--lib/gitlab/git_ref_validator.rb4
-rw-r--r--lib/gitlab/gitaly_client.rb32
-rw-r--r--lib/gitlab/gitaly_client/blob_service.rb52
-rw-r--r--lib/gitlab/gitaly_client/call.rb72
-rw-r--r--lib/gitlab/gitaly_client/cleanup_service.rb5
-rw-r--r--lib/gitlab/gitaly_client/commit_service.rb68
-rw-r--r--lib/gitlab/gitaly_client/conflicts_service.rb1
-rw-r--r--lib/gitlab/gitaly_client/operation_service.rb4
-rw-r--r--lib/gitlab/gitaly_client/ref_service.rb31
-rw-r--r--lib/gitlab/gitaly_client/remote_service.rb6
-rw-r--r--lib/gitlab/gitaly_client/repository_service.rb8
-rw-r--r--lib/gitlab/gl_repository.rb6
-rw-r--r--lib/gitlab/gl_repository/identifier.rb94
-rw-r--r--lib/gitlab/global_id.rb17
-rw-r--r--lib/gitlab/graphql/authorize/authorize_resource.rb3
-rw-r--r--lib/gitlab/graphql/lazy.rb19
-rw-r--r--lib/gitlab/graphql/loaders/issuable_loader.rb82
-rw-r--r--lib/gitlab/graphql/mount_mutation.rb8
-rw-r--r--lib/gitlab/graphql/query_analyzers/logger_analyzer.rb3
-rw-r--r--lib/gitlab/health_checks/probes/collection.rb6
-rw-r--r--lib/gitlab/import/metrics.rb87
-rw-r--r--lib/gitlab/import_export/json/streaming_serializer.rb14
-rw-r--r--lib/gitlab/import_export/project/import_export.yml10
-rw-r--r--lib/gitlab/import_export/project/relation_factory.rb11
-rw-r--r--lib/gitlab/import_export/snippet_repo_restorer.rb2
-rw-r--r--lib/gitlab/incident_management/pager_duty/incident_issue_description.rb64
-rw-r--r--lib/gitlab/instrumentation/elasticsearch_transport.rb16
-rw-r--r--lib/gitlab/instrumentation/redis.rb4
-rw-r--r--lib/gitlab/instrumentation/redis_base.rb37
-rw-r--r--lib/gitlab/instrumentation/redis_cluster_validator.rb106
-rw-r--r--lib/gitlab/instrumentation/redis_interceptor.rb23
-rw-r--r--lib/gitlab/issuable_metadata.rb14
-rw-r--r--lib/gitlab/jira_import/issue_serializer.rb4
-rw-r--r--lib/gitlab/jira_import/user_mapper.rb53
-rw-r--r--lib/gitlab/json.rb199
-rw-r--r--lib/gitlab/json_logger.rb2
-rw-r--r--lib/gitlab/kubernetes/helm.rb2
-rw-r--r--lib/gitlab/kubernetes/node.rb78
-rw-r--r--lib/gitlab/lograge/custom_options.rb12
-rw-r--r--lib/gitlab/marginalia/comment.rb4
-rw-r--r--lib/gitlab/markdown_cache/redis/extension.rb22
-rw-r--r--lib/gitlab/markdown_cache/redis/store.rb20
-rw-r--r--lib/gitlab/metrics/background_transaction.rb2
-rw-r--r--lib/gitlab/metrics/dashboard/errors.rb6
-rw-r--r--lib/gitlab/metrics/dashboard/finder.rb45
-rw-r--r--lib/gitlab/metrics/dashboard/service_selector.rb4
-rw-r--r--lib/gitlab/metrics/dashboard/stages/base_stage.rb8
-rw-r--r--lib/gitlab/metrics/dashboard/stages/cluster_endpoint_inserter.rb83
-rw-r--r--lib/gitlab/metrics/dashboard/stages/endpoint_inserter.rb50
-rw-r--r--lib/gitlab/metrics/dashboard/stages/metric_endpoint_inserter.rb54
-rw-r--r--lib/gitlab/metrics/dashboard/stages/sorter.rb4
-rw-r--r--lib/gitlab/metrics/dashboard/stages/url_validator.rb43
-rw-r--r--lib/gitlab/metrics/dashboard/stages/variable_endpoint_inserter.rb34
-rw-r--r--lib/gitlab/metrics/dashboard/url.rb16
-rw-r--r--lib/gitlab/metrics/methods.rb2
-rw-r--r--lib/gitlab/metrics/sidekiq_middleware.rb4
-rw-r--r--lib/gitlab/metrics/subscribers/active_record.rb22
-rw-r--r--lib/gitlab/metrics/transaction.rb8
-rw-r--r--lib/gitlab/metrics/web_transaction.rb11
-rw-r--r--lib/gitlab/middleware/go.rb4
-rw-r--r--lib/gitlab/middleware/multipart.rb19
-rw-r--r--lib/gitlab/project_template.rb1
-rw-r--r--lib/gitlab/prometheus_client.rb20
-rw-r--r--lib/gitlab/regex.rb12
-rw-r--r--lib/gitlab/runtime.rb26
-rw-r--r--lib/gitlab/search_results.rb3
-rw-r--r--lib/gitlab/seeder.rb3
-rw-r--r--lib/gitlab/service_desk.rb16
-rw-r--r--lib/gitlab/service_desk_email.rb22
-rw-r--r--lib/gitlab/set_cache.rb5
-rw-r--r--lib/gitlab/sidekiq_logging/deduplication_logger.rb7
-rw-r--r--lib/gitlab/sidekiq_middleware/duplicate_jobs/strategies/until_executing.rb3
-rw-r--r--lib/gitlab/static_site_editor/config.rb8
-rw-r--r--lib/gitlab/suggestions/file_suggestion.rb53
-rw-r--r--lib/gitlab/suggestions/suggestion_set.rb22
-rw-r--r--lib/gitlab/template/service_desk_template.rb21
-rw-r--r--lib/gitlab/tracking/incident_management.rb3
-rw-r--r--lib/gitlab/tree_summary.rb2
-rw-r--r--lib/gitlab/updated_notes_paginator.rb74
-rw-r--r--lib/gitlab/url_builder.rb14
-rw-r--r--lib/gitlab/usage_data.rb363
-rw-r--r--lib/gitlab/usage_data/topology.rb258
-rw-r--r--lib/gitlab/usage_data_concerns/topology.rb137
-rw-r--r--lib/gitlab/usage_data_counters/track_unique_actions.rb86
-rw-r--r--lib/gitlab/user_access.rb6
-rw-r--r--lib/gitlab/utils.rb12
-rw-r--r--lib/gitlab/utils/markdown.rb19
-rw-r--r--lib/gitlab/utils/usage_data.rb14
-rw-r--r--lib/gitlab/workhorse.rb4
-rw-r--r--lib/gitlab_danger.rb1
-rw-r--r--lib/google_api/auth.rb2
-rw-r--r--lib/kramdown/converter/commonmark.rb109
-rw-r--r--lib/kramdown/parser/atlassian_document_format.rb381
-rw-r--r--lib/learn_gitlab.rb35
-rw-r--r--lib/object_storage/direct_upload.rb25
-rw-r--r--lib/pager_duty/webhook_payload_parser.rb66
-rw-r--r--lib/peek/views/elasticsearch.rb2
-rw-r--r--lib/product_analytics/collector_app.rb40
-rw-r--r--lib/product_analytics/event_params.rb51
-rw-r--r--lib/quality/helm3_client.rb109
-rw-r--r--lib/quality/kubernetes_client.rb85
-rw-r--r--lib/quality/seeders/issues.rb1
-rw-r--r--lib/quality/test_level.rb8
-rw-r--r--lib/rspec_flaky/flaky_examples_collection.rb2
-rw-r--r--lib/sentry/client/issue.rb3
-rw-r--r--lib/support/logrotate/gitlab2
-rw-r--r--lib/system_check/incoming_email/imap_authentication_check.rb11
-rw-r--r--lib/tasks/cache.rake4
-rw-r--r--lib/tasks/gitlab/container_registry.rake16
-rw-r--r--lib/tasks/gitlab/db.rake37
-rw-r--r--lib/tasks/gitlab/external_diffs.rake35
-rw-r--r--lib/tasks/gitlab/packages/migrate.rake23
-rw-r--r--locale/am_ET/gitlab.po1484
-rw-r--r--locale/ar_SA/gitlab.po1530
-rw-r--r--locale/as_IN/gitlab.po27977
-rw-r--r--locale/az_AZ/gitlab.po1482
-rw-r--r--locale/bg/gitlab.po1482
-rw-r--r--locale/bn_BD/gitlab.po1482
-rw-r--r--locale/bn_IN/gitlab.po1482
-rw-r--r--locale/bs_BA/gitlab.po1616
-rw-r--r--locale/ca_ES/gitlab.po1568
-rw-r--r--locale/cs_CZ/gitlab.po1506
-rw-r--r--locale/cy_GB/gitlab.po1618
-rw-r--r--locale/da_DK/gitlab.po1482
-rw-r--r--locale/de/gitlab.po1988
-rw-r--r--locale/el_GR/gitlab.po1482
-rw-r--r--locale/eo/gitlab.po1482
-rw-r--r--locale/es/gitlab.po2738
-rw-r--r--locale/et_EE/gitlab.po1482
-rw-r--r--locale/fa_IR/gitlab.po1482
-rw-r--r--locale/fi_FI/gitlab.po1482
-rw-r--r--locale/fil_PH/gitlab.po1482
-rw-r--r--locale/fr/gitlab.po1490
-rw-r--r--locale/gitlab.pot2307
-rw-r--r--locale/gl_ES/gitlab.po1482
-rw-r--r--locale/he_IL/gitlab.po1506
-rw-r--r--locale/hi_IN/gitlab.po1482
-rw-r--r--locale/hr_HR/gitlab.po1500
-rw-r--r--locale/hu_HU/gitlab.po1482
-rw-r--r--locale/id_ID/gitlab.po1470
-rw-r--r--locale/ig_NG/gitlab.po27832
-rw-r--r--locale/is_IS/gitlab.po1482
-rw-r--r--locale/it/gitlab.po1482
-rw-r--r--locale/ja/gitlab.po1592
-rw-r--r--locale/ka_GE/gitlab.po1482
-rw-r--r--locale/ko/gitlab.po1474
-rw-r--r--locale/ku_TR/gitlab.po1482
-rw-r--r--locale/lt_LT/gitlab.po28267
-rw-r--r--locale/mn_MN/gitlab.po1482
-rw-r--r--locale/nb_NO/gitlab.po1482
-rw-r--r--locale/nl_NL/gitlab.po1482
-rw-r--r--locale/pa_IN/gitlab.po1482
-rw-r--r--locale/pl_PL/gitlab.po1506
-rw-r--r--locale/pt_BR/gitlab.po1514
-rw-r--r--locale/pt_PT/gitlab.po1492
-rw-r--r--locale/ro_RO/gitlab.po1504
-rw-r--r--locale/ru/gitlab.po1774
-rw-r--r--locale/si_LK/gitlab.po27977
-rw-r--r--locale/sk_SK/gitlab.po1506
-rw-r--r--locale/sl_SI/gitlab.po1506
-rw-r--r--locale/sq_AL/gitlab.po1482
-rw-r--r--locale/sr_CS/gitlab.po1494
-rw-r--r--locale/sr_SP/gitlab.po1494
-rw-r--r--locale/sv_SE/gitlab.po1482
-rw-r--r--locale/sw_KE/gitlab.po1482
-rw-r--r--locale/tr_TR/gitlab.po1514
-rw-r--r--locale/uk/gitlab.po1960
-rw-r--r--locale/ur_PK/gitlab.po1482
-rw-r--r--locale/uz_UZ/gitlab.po1482
-rw-r--r--locale/vi_VN/gitlab.po1470
-rw-r--r--locale/zh_CN/gitlab.po1594
-rw-r--r--locale/zh_HK/gitlab.po2372
-rw-r--r--locale/zh_TW/gitlab.po1474
-rw-r--r--package.json35
-rw-r--r--public/robots.txt87
-rw-r--r--qa/Dockerfile11
-rw-r--r--qa/Gemfile1
-rw-r--r--qa/Gemfile.lock2
-rwxr-xr-xqa/bin/rubymine9
-rw-r--r--qa/qa.rb34
-rw-r--r--qa/qa/fixtures/monitored_auto_devops/.gitlab-ci.yml338
-rw-r--r--qa/qa/flow/login.rb4
-rw-r--r--qa/qa/git/repository.rb4
-rw-r--r--qa/qa/page/component/ci_badge_link.rb2
-rw-r--r--qa/qa/page/component/dropdown_filter.rb2
-rw-r--r--qa/qa/page/component/issuable/sidebar.rb129
-rw-r--r--qa/qa/page/component/issue_board/show.rb5
-rw-r--r--qa/qa/page/component/new_snippet.rb80
-rw-r--r--qa/qa/page/component/project_selector.rb28
-rw-r--r--qa/qa/page/component/snippet.rb180
-rw-r--r--qa/qa/page/component/web_ide/modal/create_new_file.rb2
-rw-r--r--qa/qa/page/dashboard/snippet/new.rb65
-rw-r--r--qa/qa/page/dashboard/snippet/show.rb97
-rw-r--r--qa/qa/page/group/menu.rb25
-rw-r--r--qa/qa/page/group/milestone/index.rb19
-rw-r--r--qa/qa/page/group/milestone/new.rb29
-rw-r--r--qa/qa/page/group/sub_menus/members.rb14
-rw-r--r--qa/qa/page/issuable/sidebar.rb27
-rw-r--r--qa/qa/page/main/menu.rb9
-rw-r--r--qa/qa/page/main/two_factor_auth.rb22
-rw-r--r--qa/qa/page/merge_request/show.rb18
-rw-r--r--qa/qa/page/milestone/index.rb21
-rw-r--r--qa/qa/page/milestone/new.rb22
-rw-r--r--qa/qa/page/milestone/show.rb33
-rw-r--r--qa/qa/page/modal/delete_wiki.rb17
-rw-r--r--qa/qa/page/profile/two_factor_auth.rb26
-rw-r--r--qa/qa/page/project/issue/index.rb20
-rw-r--r--qa/qa/page/project/issue/jira_import.rb25
-rw-r--r--qa/qa/page/project/issue/show.rb81
-rw-r--r--qa/qa/page/project/job/show.rb3
-rw-r--r--qa/qa/page/project/menu.rb5
-rw-r--r--qa/qa/page/project/milestone/index.rb20
-rw-r--r--qa/qa/page/project/milestone/new.rb29
-rw-r--r--qa/qa/page/project/operations/kubernetes/show.rb33
-rw-r--r--qa/qa/page/project/operations/metrics/show.rb4
-rw-r--r--qa/qa/page/project/pipeline/index.rb4
-rw-r--r--qa/qa/page/project/pipeline/show.rb5
-rw-r--r--qa/qa/page/project/settings/incidents.rb6
-rw-r--r--qa/qa/page/project/settings/operations.rb2
-rw-r--r--qa/qa/page/project/settings/protected_tags.rb46
-rw-r--r--qa/qa/page/project/settings/repository.rb10
-rw-r--r--qa/qa/page/project/settings/runners.rb2
-rw-r--r--qa/qa/page/project/settings/services/jira.rb18
-rw-r--r--qa/qa/page/project/show.rb5
-rw-r--r--qa/qa/page/project/snippet/new.rb3
-rw-r--r--qa/qa/page/project/snippet/show.rb17
-rw-r--r--qa/qa/page/project/sub_menus/issues.rb8
-rw-r--r--qa/qa/page/project/sub_menus/repository.rb15
-rw-r--r--qa/qa/page/project/tag/index.rb19
-rw-r--r--qa/qa/page/project/tag/new.rb41
-rw-r--r--qa/qa/page/project/tag/show.rb29
-rw-r--r--qa/qa/page/project/web_ide/edit.rb17
-rw-r--r--qa/qa/page/project/wiki/edit.rb13
-rw-r--r--qa/qa/page/project/wiki/list.rb23
-rw-r--r--qa/qa/page/project/wiki/show.rb19
-rw-r--r--qa/qa/page/project/wiki/sidebar.rb42
-rw-r--r--qa/qa/resource/api_fabricator.rb14
-rw-r--r--qa/qa/resource/group.rb13
-rw-r--r--qa/qa/resource/group_milestone.rb57
-rw-r--r--qa/qa/resource/issue.rb1
-rw-r--r--qa/qa/resource/members.rb4
-rw-r--r--qa/qa/resource/project.rb6
-rw-r--r--qa/qa/resource/project_milestone.rb20
-rw-r--r--qa/qa/resource/repository/commit.rb24
-rw-r--r--qa/qa/resource/sandbox.rb1
-rw-r--r--qa/qa/resource/user.rb5
-rw-r--r--qa/qa/resource/wiki/project_page.rb2
-rw-r--r--qa/qa/runtime/env.rb42
-rw-r--r--qa/qa/service/praefect_manager.rb176
-rw-r--r--qa/qa/service/shellout.rb7
-rw-r--r--qa/qa/specs/features/api/1_manage/rate_limits_spec.rb2
-rw-r--r--qa/qa/specs/features/api/1_manage/users_spec.rb9
-rw-r--r--qa/qa/specs/features/api/2_plan/.gitkeep0
-rw-r--r--qa/qa/specs/features/api/2_plan/closes_issue_via_pushing_a_commit_spec.rb2
-rw-r--r--qa/qa/specs/features/api/3_create/repository/changing_repository_storage_spec.rb10
-rw-r--r--qa/qa/specs/features/api/3_create/repository/files_spec.rb2
-rw-r--r--qa/qa/specs/features/api/3_create/repository/praefect_replication_queue_spec.rb64
-rw-r--r--qa/qa/specs/features/api/3_create/repository/project_archive_compare_spec.rb21
-rw-r--r--qa/qa/specs/features/api/4_verify/.gitkeep0
-rw-r--r--qa/qa/specs/features/api/4_verify/pipeline_deletion_spec.rb63
-rw-r--r--qa/qa/specs/features/api/8_monitor/.gitkeep (renamed from qa/qa/specs/features/api/1_manage/.gitkeep)0
-rw-r--r--qa/qa/specs/features/browser_ui/1_manage/group/create_group_with_mattermost_team_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/1_manage/group/transfer_project_spec.rb26
-rw-r--r--qa/qa/specs/features/browser_ui/1_manage/login/log_in_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/1_manage/login/log_in_with_2fa_spec.rb110
-rw-r--r--qa/qa/specs/features/browser_ui/1_manage/login/log_into_gitlab_via_ldap_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/1_manage/login/log_into_mattermost_via_gitlab_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/1_manage/login/login_via_instance_wide_saml_sso_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/1_manage/login/register_spec.rb6
-rw-r--r--qa/qa/specs/features/browser_ui/1_manage/project/add_project_member_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/1_manage/project/create_project_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/1_manage/project/dashboard_images_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/1_manage/project/import_github_repo_spec.rb17
-rw-r--r--qa/qa/specs/features/browser_ui/1_manage/project/protected_tags_spec.rb109
-rw-r--r--qa/qa/specs/features/browser_ui/1_manage/project/view_project_activity_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/2_plan/email/trigger_email_notification_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/2_plan/issue/check_mentions_for_xss_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/2_plan/issue/collapse_comments_in_discussions_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/2_plan/issue/comment_issue_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/2_plan/issue/create_issue_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/2_plan/issue/export_as_csv_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/2_plan/issue/filter_issue_comments_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/2_plan/issue/issue_suggestions_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/2_plan/issue/jira_issue_import_spec.rb77
-rw-r--r--qa/qa/specs/features/browser_ui/2_plan/issue/mentions_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/2_plan/issue_boards/focus_mode_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/2_plan/milestone/assign_milestone_spec.rb85
-rw-r--r--qa/qa/specs/features/browser_ui/2_plan/milestone/create_group_milestone_spec.rb41
-rw-r--r--qa/qa/specs/features/browser_ui/2_plan/milestone/create_project_milestone_spec.rb41
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/gitaly/high_availability_spec.rb14
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/jira/jira_basic_integration_spec.rb23
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/merge_request/create_merge_request_spec.rb45
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/merge_request/merge_merge_request_from_fork_spec.rb15
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/merge_request/rebase_merge_request_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/merge_request/squash_merge_request_spec.rb24
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/merge_request/view_merge_request_diff_patch_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/repository/add_file_template_spec.rb20
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/repository/add_list_delete_branches_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/repository/add_ssh_key_spec.rb30
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/repository/clone_spec.rb16
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/repository/create_edit_delete_file_via_web_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/repository/move_project_create_fork_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/repository/protocol_v2_push_http_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/repository/protocol_v2_push_ssh_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/repository/push_http_private_token_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/repository/push_mirroring_over_http_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/repository/push_over_http_file_size_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/repository/push_over_http_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/repository/push_protected_branch_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/repository/ssh_key_support_spec.rb35
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/repository/use_ssh_key_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/repository/user_views_commit_diff_patch_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/snippet/add_comment_to_snippet_spec.rb81
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/snippet/clone_push_pull_personal_snippet_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/snippet/clone_push_pull_project_snippet_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/snippet/create_personal_snippet_spec.rb15
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/snippet/create_project_snippet_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/web_ide/add_file_template_spec.rb7
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/web_ide/create_first_file_in_web_ide_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/web_ide/review_merge_request_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/wiki/project_based_content_creation_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/wiki/project_based_content_manipulation_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/wiki/project_based_list_spec.rb73
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/wiki/project_based_page_deletion_spec.rb26
-rw-r--r--qa/qa/specs/features/browser_ui/4_verify/ci_variable/add_remove_ci_variable_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/4_verify/pipeline/create_and_process_pipeline_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/4_verify/runner/register_runner_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/4_verify/testing/view_code_coverage_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/6_release/deploy_key/add_deploy_key_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/6_release/deploy_key/clone_using_deploy_key_spec.rb5
-rw-r--r--qa/qa/specs/features/browser_ui/6_release/deploy_token/add_deploy_token_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/6_release/pipeline/parent_child_pipelines_dependent_relationship_spec.rb14
-rw-r--r--qa/qa/specs/features/browser_ui/6_release/pipeline/parent_child_pipelines_independent_relationship_spec.rb14
-rw-r--r--qa/qa/specs/features/browser_ui/7_configure/auto_devops/create_project_with_auto_devops_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/7_configure/kubernetes/kubernetes_integration_spec.rb4
-rw-r--r--qa/qa/specs/features/browser_ui/8_monitor/all_monitor_core_features_spec.rb15
-rw-r--r--qa/qa/specs/features/browser_ui/non_devops/performance_bar_spec.rb4
-rw-r--r--qa/qa/specs/helpers/quarantine.rb34
-rw-r--r--qa/qa/support/dates.rb5
-rw-r--r--qa/qa/support/otp.rb25
-rw-r--r--qa/spec/runtime/api/request_spec.rb6
-rw-r--r--qa/spec/runtime/env_spec.rb42
-rw-r--r--qa/spec/specs/helpers/quarantine_spec.rb123
-rw-r--r--rubocop/cop/api/grape_api_instance.rb42
-rw-r--r--rubocop/cop/api/grape_array_missing_coerce.rb83
-rw-r--r--rubocop/cop/graphql/authorize_types.rb21
-rw-r--r--rubocop/cop/migration/drop_table.rb5
-rw-r--r--rubocop/cop/migration/with_lock_retries_disallowed_method.rb1
-rw-r--r--scripts/frontend/merge_coverage_frontend.js2
-rw-r--r--scripts/frontend/prettier.js2
-rw-r--r--scripts/gitaly_test.rb23
-rwxr-xr-xscripts/lint-doc.sh5
-rwxr-xr-xscripts/merge-simplecov1
-rw-r--r--scripts/prepare_build.sh4
-rwxr-xr-xscripts/review_apps/automated_cleanup.rb16
-rw-r--r--scripts/review_apps/base-config.yaml14
-rwxr-xr-xscripts/review_apps/gcp_cleanup.sh2
-rwxr-xr-xscripts/review_apps/review-apps.sh74
-rw-r--r--scripts/rspec_helpers.sh4
-rwxr-xr-xscripts/trigger-build1
-rw-r--r--spec/bin/feature_flag_spec.rb191
-rw-r--r--spec/config/object_store_settings_spec.rb102
-rw-r--r--spec/config/settings_spec.rb22
-rw-r--r--spec/controllers/admin/application_settings_controller_spec.rb7
-rw-r--r--spec/controllers/admin/clusters_controller_spec.rb33
-rw-r--r--spec/controllers/admin/jobs_controller_spec.rb32
-rw-r--r--spec/controllers/admin/services_controller_spec.rb22
-rw-r--r--spec/controllers/application_controller_spec.rb22
-rw-r--r--spec/controllers/autocomplete_controller_spec.rb50
-rw-r--r--spec/controllers/concerns/controller_with_feature_category/config_spec.rb53
-rw-r--r--spec/controllers/concerns/controller_with_feature_category_spec.rb66
-rw-r--r--spec/controllers/concerns/metrics_dashboard_spec.rb33
-rw-r--r--spec/controllers/concerns/renders_commits_spec.rb2
-rw-r--r--spec/controllers/concerns/sorting_preference_spec.rb4
-rw-r--r--spec/controllers/dashboard/projects_controller_spec.rb42
-rw-r--r--spec/controllers/dashboard/snippets_controller_spec.rb4
-rw-r--r--spec/controllers/dashboard/todos_controller_spec.rb9
-rw-r--r--spec/controllers/dashboard_controller_spec.rb8
-rw-r--r--spec/controllers/every_controller_spec.rb82
-rw-r--r--spec/controllers/groups/clusters_controller_spec.rb40
-rw-r--r--spec/controllers/groups/imports_controller_spec.rb2
-rw-r--r--spec/controllers/groups/runners_controller_spec.rb127
-rw-r--r--spec/controllers/groups/settings/ci_cd_controller_spec.rb15
-rw-r--r--spec/controllers/groups_controller_spec.rb36
-rw-r--r--spec/controllers/import/bitbucket_controller_spec.rb48
-rw-r--r--spec/controllers/import/bitbucket_server_controller_spec.rb81
-rw-r--r--spec/controllers/import/fogbugz_controller_spec.rb23
-rw-r--r--spec/controllers/import/gitlab_controller_spec.rb23
-rw-r--r--spec/controllers/instance_statistics/cohorts_controller_spec.rb7
-rw-r--r--spec/controllers/instance_statistics/dev_ops_score_controller_spec.rb13
-rw-r--r--spec/controllers/invites_controller_spec.rb37
-rw-r--r--spec/controllers/oauth/applications_controller_spec.rb16
-rw-r--r--spec/controllers/profiles/keys_controller_spec.rb65
-rw-r--r--spec/controllers/projects/clusters_controller_spec.rb42
-rw-r--r--spec/controllers/projects/cycle_analytics_controller_spec.rb7
-rw-r--r--spec/controllers/projects/deployments_controller_spec.rb46
-rw-r--r--spec/controllers/projects/discussions_controller_spec.rb3
-rw-r--r--spec/controllers/projects/environments/prometheus_api_controller_spec.rb206
-rw-r--r--spec/controllers/projects/environments_controller_spec.rb45
-rw-r--r--spec/controllers/projects/graphs_controller_spec.rb9
-rw-r--r--spec/controllers/projects/imports_controller_spec.rb171
-rw-r--r--spec/controllers/projects/issues_controller_spec.rb39
-rw-r--r--spec/controllers/projects/jobs_controller_spec.rb103
-rw-r--r--spec/controllers/projects/logs_controller_spec.rb28
-rw-r--r--spec/controllers/projects/merge_requests/diffs_controller_spec.rb12
-rw-r--r--spec/controllers/projects/merge_requests/drafts_controller_spec.rb9
-rw-r--r--spec/controllers/projects/merge_requests_controller_spec.rb4
-rw-r--r--spec/controllers/projects/notes_controller_spec.rb84
-rw-r--r--spec/controllers/projects/pipelines/stages_controller_spec.rb72
-rw-r--r--spec/controllers/projects/pipelines/tests_controller_spec.rb112
-rw-r--r--spec/controllers/projects/pipelines_controller_spec.rb95
-rw-r--r--spec/controllers/projects/project_members_controller_spec.rb23
-rw-r--r--spec/controllers/projects/refs_controller_spec.rb9
-rw-r--r--spec/controllers/projects/releases_controller_spec.rb11
-rw-r--r--spec/controllers/projects/service_desk_controller_spec.rb111
-rw-r--r--spec/controllers/projects/services_controller_spec.rb19
-rw-r--r--spec/controllers/projects/settings/operations_controller_spec.rb100
-rw-r--r--spec/controllers/projects/snippets/blobs_controller_spec.rb85
-rw-r--r--spec/controllers/projects/snippets_controller_spec.rb28
-rw-r--r--spec/controllers/projects/stages_controller_spec.rb72
-rw-r--r--spec/controllers/projects/tree_controller_spec.rb28
-rw-r--r--spec/controllers/projects/wikis_controller_spec.rb2
-rw-r--r--spec/controllers/projects_controller_spec.rb91
-rw-r--r--spec/controllers/registrations/experience_levels_controller_spec.rb47
-rw-r--r--spec/controllers/search_controller_spec.rb5
-rw-r--r--spec/controllers/snippets/blobs_controller_spec.rb61
-rw-r--r--spec/controllers/snippets_controller_spec.rb6
-rw-r--r--spec/controllers/users_controller_spec.rb65
-rw-r--r--spec/db/schema_spec.rb92
-rw-r--r--spec/factories/alert_management/alerts.rb28
-rw-r--r--spec/factories/approvals.rb10
-rw-r--r--spec/factories/background_migration_jobs.rb13
-rw-r--r--spec/factories/ci/builds.rb12
-rw-r--r--spec/factories/ci/pipelines.rb16
-rw-r--r--spec/factories/clusters/applications/helm.rb8
-rw-r--r--spec/factories/clusters/clusters.rb1
-rw-r--r--spec/factories/custom_emoji.rb9
-rw-r--r--spec/factories/deployments.rb2
-rw-r--r--spec/factories/events.rb2
-rw-r--r--spec/factories/go_module_commits.rb82
-rw-r--r--spec/factories/go_module_versions.rb77
-rw-r--r--spec/factories/go_modules.rb13
-rw-r--r--spec/factories/groups.rb8
-rw-r--r--spec/factories/namespaces.rb30
-rw-r--r--spec/factories/notes.rb5
-rw-r--r--spec/factories/packages.rb355
-rw-r--r--spec/factories/product_analytics_event.rb24
-rw-r--r--spec/factories/project_repository_storage_moves.rb8
-rw-r--r--spec/factories/project_statistics.rb1
-rw-r--r--spec/factories/projects.rb26
-rw-r--r--spec/factories/service_desk_settings.rb7
-rw-r--r--spec/factories/services.rb11
-rw-r--r--spec/factories/snippet_statistics.rb25
-rw-r--r--spec/factories/terraform/state.rb6
-rw-r--r--spec/factories/usage_data.rb16
-rw-r--r--spec/features/admin/admin_sees_project_statistics_spec.rb2
-rw-r--r--spec/features/admin/admin_users_spec.rb8
-rw-r--r--spec/features/admin/services/admin_visits_service_templates_spec.rb30
-rw-r--r--spec/features/clusters/cluster_health_dashboard_spec.rb93
-rw-r--r--spec/features/clusters/installing_applications_shared_examples.rb53
-rw-r--r--spec/features/dashboard/projects_spec.rb8
-rw-r--r--spec/features/discussion_comments/commit_spec.rb2
-rw-r--r--spec/features/groups/container_registry_spec.rb2
-rw-r--r--spec/features/groups/empty_states_spec.rb2
-rw-r--r--spec/features/groups/import_export/import_file_spec.rb2
-rw-r--r--spec/features/groups/members/manage_members_spec.rb9
-rw-r--r--spec/features/groups/navbar_spec.rb1
-rw-r--r--spec/features/groups_spec.rb29
-rw-r--r--spec/features/invites_spec.rb260
-rw-r--r--spec/features/issuables/close_reopen_report_toggle_spec.rb22
-rw-r--r--spec/features/issuables/issuable_list_spec.rb10
-rw-r--r--spec/features/issuables/sorting_list_spec.rb4
-rw-r--r--spec/features/issues/bulk_assignment_labels_spec.rb45
-rw-r--r--spec/features/issues/filtered_search/filter_issues_spec.rb8
-rw-r--r--spec/features/issues/filtered_search/search_bar_spec.rb2
-rw-r--r--spec/features/issues/gfm_autocomplete_spec.rb752
-rw-r--r--spec/features/issues/issue_detail_spec.rb2
-rw-r--r--spec/features/issues/issue_sidebar_spec.rb2
-rw-r--r--spec/features/issues/move_spec.rb39
-rw-r--r--spec/features/issues/service_desk_spec.rb163
-rw-r--r--spec/features/issues/update_issues_spec.rb1
-rw-r--r--spec/features/issues/user_creates_branch_and_merge_request_spec.rb8
-rw-r--r--spec/features/issues/user_filters_issues_spec.rb2
-rw-r--r--spec/features/issues/user_interacts_with_awards_spec.rb4
-rw-r--r--spec/features/issues/user_sees_breadcrumb_links_spec.rb2
-rw-r--r--spec/features/issues/user_sees_empty_state_spec.rb4
-rw-r--r--spec/features/issues/user_sorts_issues_spec.rb2
-rw-r--r--spec/features/issues/user_views_issues_spec.rb29
-rw-r--r--spec/features/labels_hierarchy_spec.rb2
-rw-r--r--spec/features/markdown/metrics_spec.rb38
-rw-r--r--spec/features/merge_request/maintainer_edits_fork_spec.rb4
-rw-r--r--spec/features/merge_request/user_approves_spec.rb41
-rw-r--r--spec/features/merge_request/user_closes_merge_request_spec.rb2
-rw-r--r--spec/features/merge_request/user_comments_on_diff_spec.rb53
-rw-r--r--spec/features/merge_request/user_creates_image_diff_notes_spec.rb2
-rw-r--r--spec/features/merge_request/user_edits_merge_request_spec.rb69
-rw-r--r--spec/features/merge_request/user_posts_diff_notes_spec.rb4
-rw-r--r--spec/features/merge_request/user_reopens_merge_request_spec.rb2
-rw-r--r--spec/features/merge_request/user_sees_diff_spec.rb2
-rw-r--r--spec/features/merge_request/user_sees_merge_widget_spec.rb10
-rw-r--r--spec/features/merge_request/user_sees_pipelines_spec.rb113
-rw-r--r--spec/features/merge_request/user_suggests_changes_on_diff_spec.rb5
-rw-r--r--spec/features/merge_request/user_toggles_whitespace_changes_spec.rb2
-rw-r--r--spec/features/merge_request/user_views_diffs_file_by_file_spec.rb33
-rw-r--r--spec/features/participants_autocomplete_spec.rb23
-rw-r--r--spec/features/profiles/user_edit_preferences_spec.rb13
-rw-r--r--spec/features/profiles/user_edit_profile_spec.rb5
-rw-r--r--spec/features/profiles/user_visits_profile_preferences_page_spec.rb2
-rw-r--r--spec/features/projects/activity/user_sees_design_activity_spec.rb78
-rw-r--r--spec/features/projects/blobs/blob_show_spec.rb2
-rw-r--r--spec/features/projects/blobs/edit_spec.rb16
-rw-r--r--spec/features/projects/blobs/user_creates_new_blob_in_new_project_spec.rb3
-rw-r--r--spec/features/projects/blobs/user_follows_pipeline_suggest_nudge_spec.rb2
-rw-r--r--spec/features/projects/clusters/gcp_spec.rb26
-rw-r--r--spec/features/projects/clusters_spec.rb162
-rw-r--r--spec/features/projects/commit/builds_spec.rb25
-rw-r--r--spec/features/projects/commits/user_browses_commits_spec.rb27
-rw-r--r--spec/features/projects/confluence/user_views_confluence_page_spec.rb28
-rw-r--r--spec/features/projects/container_registry_spec.rb2
-rw-r--r--spec/features/projects/environments/environment_metrics_spec.rb11
-rw-r--r--spec/features/projects/environments_pod_logs_spec.rb5
-rw-r--r--spec/features/projects/files/edit_file_soft_wrap_spec.rb24
-rw-r--r--spec/features/projects/files/gitignore_dropdown_spec.rb2
-rw-r--r--spec/features/projects/files/user_browses_files_spec.rb20
-rw-r--r--spec/features/projects/files/user_creates_files_spec.rb16
-rw-r--r--spec/features/projects/files/user_edits_files_spec.rb20
-rw-r--r--spec/features/projects/import_export/test_project_export.tar.gzbin3360 -> 3176 bytes
-rw-r--r--spec/features/projects/issues/design_management/user_paginates_designs_spec.rb56
-rw-r--r--spec/features/projects/issues/design_management/user_permissions_upload_spec.rb29
-rw-r--r--spec/features/projects/issues/design_management/user_uploads_designs_spec.rb62
-rw-r--r--spec/features/projects/issues/design_management/user_views_design_spec.rb39
-rw-r--r--spec/features/projects/issues/design_management/user_views_designs_spec.rb79
-rw-r--r--spec/features/projects/issues/design_management/user_views_designs_with_svg_xss_spec.rb1
-rw-r--r--spec/features/projects/jobs_spec.rb4
-rw-r--r--spec/features/projects/labels/issues_sorted_by_priority_spec.rb4
-rw-r--r--spec/features/projects/members/list_spec.rb9
-rw-r--r--spec/features/projects/members/member_leaves_project_spec.rb2
-rw-r--r--spec/features/projects/navbar_spec.rb2
-rw-r--r--spec/features/projects/pipeline_schedules_spec.rb2
-rw-r--r--spec/features/projects/pipelines/pipeline_spec.rb90
-rw-r--r--spec/features/projects/pipelines/pipelines_spec.rb17
-rw-r--r--spec/features/projects/services/disable_triggers_spec.rb6
-rw-r--r--spec/features/projects/services/user_activates_alerts_spec.rb51
-rw-r--r--spec/features/projects/services/user_activates_issue_tracker_spec.rb6
-rw-r--r--spec/features/projects/services/user_activates_jira_spec.rb30
-rw-r--r--spec/features/projects/services/user_activates_mattermost_slash_command_spec.rb4
-rw-r--r--spec/features/projects/services/user_activates_slack_slash_command_spec.rb4
-rw-r--r--spec/features/projects/settings/operations_settings_spec.rb5
-rw-r--r--spec/features/projects/settings/registry_settings_spec.rb10
-rw-r--r--spec/features/projects/settings/service_desk_setting_spec.rb33
-rw-r--r--spec/features/projects/settings/user_manages_merge_requests_settings_spec.rb68
-rw-r--r--spec/features/projects/settings/user_manages_project_members_spec.rb9
-rw-r--r--spec/features/projects/show/user_sees_git_instructions_spec.rb22
-rw-r--r--spec/features/projects/show/user_sees_setup_shortcut_buttons_spec.rb17
-rw-r--r--spec/features/projects/tree/tree_show_spec.rb26
-rw-r--r--spec/features/projects/user_sees_sidebar_spec.rb4
-rw-r--r--spec/features/projects/user_sees_user_popover_spec.rb2
-rw-r--r--spec/features/projects/wiki/user_views_wiki_empty_spec.rb42
-rw-r--r--spec/features/projects/wiki/user_views_wiki_page_spec.rb84
-rw-r--r--spec/features/promotion_spec.rb53
-rw-r--r--spec/features/runners_spec.rb120
-rw-r--r--spec/features/security/project/snippet/public_access_spec.rb9
-rw-r--r--spec/features/signed_commits_spec.rb7
-rw-r--r--spec/features/task_lists_spec.rb2
-rw-r--r--spec/features/triggers_spec.rb2
-rw-r--r--spec/features/users/login_spec.rb6
-rw-r--r--spec/features/users/signup_spec.rb9
-rw-r--r--spec/finders/alert_management/alerts_finder_spec.rb78
-rw-r--r--spec/finders/branches_finder_spec.rb263
-rw-r--r--spec/finders/ci/pipelines_finder_spec.rb4
-rw-r--r--spec/finders/ci/pipelines_for_merge_request_finder_spec.rb90
-rw-r--r--spec/finders/ci/runner_jobs_finder_spec.rb4
-rw-r--r--spec/finders/ci/variables_finder_spec.rb44
-rw-r--r--spec/finders/events_finder_spec.rb22
-rw-r--r--spec/finders/group_projects_finder_spec.rb44
-rw-r--r--spec/finders/merge_requests_finder_spec.rb39
-rw-r--r--spec/finders/notes_finder_spec.rb6
-rw-r--r--spec/finders/packages/conan/package_file_finder_spec.rb64
-rw-r--r--spec/finders/packages/conan/package_finder_spec.rb22
-rw-r--r--spec/finders/packages/go/module_finder_spec.rb71
-rw-r--r--spec/finders/packages/go/version_finder_spec.rb160
-rw-r--r--spec/finders/packages/group_packages_finder_spec.rb156
-rw-r--r--spec/finders/packages/maven/package_finder_spec.rb57
-rw-r--r--spec/finders/packages/npm/package_finder_spec.rb34
-rw-r--r--spec/finders/packages/nuget/package_finder_spec.rb75
-rw-r--r--spec/finders/packages/package_file_finder_spec.rb44
-rw-r--r--spec/finders/packages/package_finder_spec.rb25
-rw-r--r--spec/finders/packages/packages_finder_spec.rb90
-rw-r--r--spec/finders/packages/tags_finder_spec.rb68
-rw-r--r--spec/finders/personal_access_tokens_finder_spec.rb18
-rw-r--r--spec/finders/projects_finder_spec.rb11
-rw-r--r--spec/finders/resource_milestone_event_finder_spec.rb16
-rw-r--r--spec/finders/resource_state_event_finder_spec.rb76
-rw-r--r--spec/finders/snippets_finder_spec.rb22
-rw-r--r--spec/finders/todos_finder_spec.rb53
-rw-r--r--spec/finders/user_recent_events_finder_spec.rb16
-rw-r--r--spec/fixtures/api/graphql/introspection.graphql16
-rw-r--r--spec/fixtures/api/schemas/entities/dag_job.json10
-rw-r--r--spec/fixtures/api/schemas/entities/dag_job_group.json13
-rw-r--r--spec/fixtures/api/schemas/entities/dag_pipeline.json11
-rw-r--r--spec/fixtures/api/schemas/entities/dag_stage.json11
-rw-r--r--spec/fixtures/api/schemas/evidences/build_artifact.json10
-rw-r--r--spec/fixtures/api/schemas/evidences/release.json4
-rw-r--r--spec/fixtures/api/schemas/public_api/v4/packages/composer/index.json29
-rw-r--r--spec/fixtures/api/schemas/public_api/v4/packages/composer/package.json65
-rw-r--r--spec/fixtures/api/schemas/public_api/v4/packages/composer/provider.json25
-rw-r--r--spec/fixtures/api/schemas/public_api/v4/packages/group_package.json33
-rw-r--r--spec/fixtures/api/schemas/public_api/v4/packages/group_packages.json4
-rw-r--r--spec/fixtures/api/schemas/public_api/v4/packages/npm_package.json8
-rw-r--r--spec/fixtures/api/schemas/public_api/v4/packages/npm_package_tags.json7
-rw-r--r--spec/fixtures/api/schemas/public_api/v4/packages/npm_package_version.json46
-rw-r--r--spec/fixtures/api/schemas/public_api/v4/packages/nuget/dependency_group.json22
-rw-r--r--spec/fixtures/api/schemas/public_api/v4/packages/nuget/download_versions.json10
-rw-r--r--spec/fixtures/api/schemas/public_api/v4/packages/nuget/package_metadata.json28
-rw-r--r--spec/fixtures/api/schemas/public_api/v4/packages/nuget/packages_metadata.json54
-rw-r--r--spec/fixtures/api/schemas/public_api/v4/packages/nuget/search.json39
-rw-r--r--spec/fixtures/api/schemas/public_api/v4/packages/nuget/service_index.json19
-rw-r--r--spec/fixtures/api/schemas/public_api/v4/packages/package.json41
-rw-r--r--spec/fixtures/api/schemas/public_api/v4/packages/package_files.json13
-rw-r--r--spec/fixtures/api/schemas/public_api/v4/packages/package_version.json19
-rw-r--r--spec/fixtures/api/schemas/public_api/v4/packages/package_with_build.json10
-rw-r--r--spec/fixtures/api/schemas/public_api/v4/packages/packages.json4
-rw-r--r--spec/fixtures/api/schemas/public_api/v4/pipeline.json27
-rw-r--r--spec/fixtures/api/schemas/public_api/v4/snippets.json10
-rw-r--r--spec/fixtures/clusters/ca_certificate.pem23
-rw-r--r--spec/fixtures/clusters/chain_certificates.pem100
-rw-r--r--spec/fixtures/clusters/intermediate_certificate.pem28
-rw-r--r--spec/fixtures/clusters/root_certificate.pem49
-rw-r--r--spec/fixtures/emails/service_desk.eml28
-rw-r--r--spec/fixtures/emails/service_desk_custom_address.eml27
-rw-r--r--spec/fixtures/emails/service_desk_forwarded.eml30
-rw-r--r--spec/fixtures/emails/service_desk_forwarded_new_issue.eml29
-rw-r--r--spec/fixtures/emails/service_desk_legacy.eml28
-rw-r--r--spec/fixtures/emails/service_desk_sender_and_from.eml27
-rw-r--r--spec/fixtures/emails/valid_reply_with_quick_actions.eml45
-rw-r--r--spec/fixtures/gitlab/import_export/corrupted_project_export.tar.gzbin4352 -> 3846 bytes
-rw-r--r--spec/fixtures/gitlab/import_export/lightweight_project_export.tar.gzbin3837 -> 3647 bytes
-rw-r--r--spec/fixtures/helm/helm_list_v2_cilium_deployed.json.gzbin0 -> 302 bytes
-rw-r--r--spec/fixtures/helm/helm_list_v2_cilium_failed.json.gzbin0 -> 304 bytes
-rw-r--r--spec/fixtures/helm/helm_list_v2_cilium_missing.json.gzbin0 -> 320 bytes
-rw-r--r--spec/fixtures/lib/gitlab/import_export/complex/project.json389
-rw-r--r--spec/fixtures/lib/gitlab/import_export/complex/tree/project/services.ndjson38
-rw-r--r--spec/fixtures/lib/gitlab/import_export/designs/project.json3
-rw-r--r--spec/fixtures/lib/gitlab/import_export/light/project.json44
-rw-r--r--spec/fixtures/lib/gitlab/import_export/light/tree/project/services.ndjson4
-rw-r--r--spec/fixtures/lib/gitlab/import_export/with_invalid_records/project.json1
-rw-r--r--spec/fixtures/lib/gitlab/metrics/dashboard/sample_dashboard.yml17
-rw-r--r--spec/fixtures/lib/gitlab/metrics/dashboard/schemas/custom_variable_full_syntax.json12
-rw-r--r--spec/fixtures/lib/gitlab/metrics/dashboard/schemas/custom_variable_options.json11
-rw-r--r--spec/fixtures/lib/gitlab/metrics/dashboard/schemas/custom_variable_values.json10
-rw-r--r--spec/fixtures/lib/gitlab/metrics/dashboard/schemas/metric_label_values_variable_full_syntax.json12
-rw-r--r--spec/fixtures/lib/gitlab/metrics/dashboard/schemas/metric_label_values_variable_options.json12
-rw-r--r--spec/fixtures/lib/gitlab/metrics/dashboard/schemas/text_variable_full_syntax.json2
-rw-r--r--spec/fixtures/lib/gitlab/metrics/dashboard/schemas/variables.json8
-rw-r--r--spec/fixtures/lib/kramdown/atlassian_document_format/blockquote.json24
-rw-r--r--spec/fixtures/lib/kramdown/atlassian_document_format/blockquote.md2
-rw-r--r--spec/fixtures/lib/kramdown/atlassian_document_format/bullet_list.json72
-rw-r--r--spec/fixtures/lib/kramdown/atlassian_document_format/bullet_list.md8
-rw-r--r--spec/fixtures/lib/kramdown/atlassian_document_format/code_block.json89
-rw-r--r--spec/fixtures/lib/kramdown/atlassian_document_format/code_block.md38
-rw-r--r--spec/fixtures/lib/kramdown/atlassian_document_format/code_mark.json24
-rw-r--r--spec/fixtures/lib/kramdown/atlassian_document_format/code_mark.md2
-rw-r--r--spec/fixtures/lib/kramdown/atlassian_document_format/complex_document.json964
-rw-r--r--spec/fixtures/lib/kramdown/atlassian_document_format/complex_document.md223
-rw-r--r--spec/fixtures/lib/kramdown/atlassian_document_format/emoji.json66
-rw-r--r--spec/fixtures/lib/kramdown/atlassian_document_format/emoji.md4
-rw-r--r--spec/fixtures/lib/kramdown/atlassian_document_format/hard_break.json22
-rw-r--r--spec/fixtures/lib/kramdown/atlassian_document_format/hard_break.md3
-rw-r--r--spec/fixtures/lib/kramdown/atlassian_document_format/heading.json91
-rw-r--r--spec/fixtures/lib/kramdown/atlassian_document_format/heading.md12
-rw-r--r--spec/fixtures/lib/kramdown/atlassian_document_format/inline_card.json46
-rw-r--r--spec/fixtures/lib/kramdown/atlassian_document_format/inline_card.md13
-rw-r--r--spec/fixtures/lib/kramdown/atlassian_document_format/invalid_json.json16
-rw-r--r--spec/fixtures/lib/kramdown/atlassian_document_format/invalid_no_doc.json13
-rw-r--r--spec/fixtures/lib/kramdown/atlassian_document_format/invalid_node_type.json28
-rw-r--r--spec/fixtures/lib/kramdown/atlassian_document_format/link_mark.json44
-rw-r--r--spec/fixtures/lib/kramdown/atlassian_document_format/link_mark.md6
-rw-r--r--spec/fixtures/lib/kramdown/atlassian_document_format/media_group.json59
-rw-r--r--spec/fixtures/lib/kramdown/atlassian_document_format/media_group.md10
-rw-r--r--spec/fixtures/lib/kramdown/atlassian_document_format/media_single.json42
-rw-r--r--spec/fixtures/lib/kramdown/atlassian_document_format/media_single.md4
-rw-r--r--spec/fixtures/lib/kramdown/atlassian_document_format/mention.json44
-rw-r--r--spec/fixtures/lib/kramdown/atlassian_document_format/mention.md4
-rw-r--r--spec/fixtures/lib/kramdown/atlassian_document_format/ordered_list.json170
-rw-r--r--spec/fixtures/lib/kramdown/atlassian_document_format/ordered_list.md25
-rw-r--r--spec/fixtures/lib/kramdown/atlassian_document_format/panel.json117
-rw-r--r--spec/fixtures/lib/kramdown/atlassian_document_format/panel.md12
-rw-r--r--spec/fixtures/lib/kramdown/atlassian_document_format/paragraph.json28
-rw-r--r--spec/fixtures/lib/kramdown/atlassian_document_format/paragraph.md4
-rw-r--r--spec/fixtures/lib/kramdown/atlassian_document_format/rule.json9
-rw-r--r--spec/fixtures/lib/kramdown/atlassian_document_format/rule.md2
-rw-r--r--spec/fixtures/lib/kramdown/atlassian_document_format/strike_sup_sub_mark.json56
-rw-r--r--spec/fixtures/lib/kramdown/atlassian_document_format/strike_sup_sub_mark.md3
-rw-r--r--spec/fixtures/lib/kramdown/atlassian_document_format/strong_em_mark.json53
-rw-r--r--spec/fixtures/lib/kramdown/atlassian_document_format/strong_em_mark.md2
-rw-r--r--spec/fixtures/lib/kramdown/atlassian_document_format/table.json55
-rw-r--r--spec/fixtures/lib/kramdown/atlassian_document_format/table.md18
-rw-r--r--spec/fixtures/lib/kramdown/atlassian_document_format/underline_text_color_mark.json40
-rw-r--r--spec/fixtures/lib/kramdown/atlassian_document_format/underline_text_color_mark.md2
-rw-r--r--spec/fixtures/pager_duty/webhook_incident_trigger.json239
-rw-r--r--spec/fixtures/product_analytics/event.json16
-rw-r--r--spec/fixtures/sentry/issue_sample_response.json25
-rw-r--r--spec/frontend/__mocks__/@gitlab/ui.js21
-rw-r--r--spec/frontend/__mocks__/document-register-element/index.js1
-rw-r--r--spec/frontend/__mocks__/monaco-editor/index.js2
-rw-r--r--spec/frontend/alert_management/components/alert_management_detail_spec.js31
-rw-r--r--spec/frontend/alert_management/components/alert_management_empty_state_spec.js54
-rw-r--r--spec/frontend/alert_management/components/alert_management_list_spec.js489
-rw-r--r--spec/frontend/alert_management/components/alert_management_list_wrapper_spec.js57
-rw-r--r--spec/frontend/alert_management/components/alert_management_sidebar_todo_spec.js76
-rw-r--r--spec/frontend/alert_management/components/alert_management_system_note_spec.js34
-rw-r--r--spec/frontend/alert_management/components/alert_management_table_spec.js590
-rw-r--r--spec/frontend/alert_management/components/alert_managment_sidebar_assignees_spec.js133
-rw-r--r--spec/frontend/alert_management/components/alert_metrics_spec.js67
-rw-r--r--spec/frontend/alert_management/components/alert_sidebar_spec.js55
-rw-r--r--spec/frontend/alert_management/components/alert_sidebar_status_spec.js107
-rw-r--r--spec/frontend/alert_management/components/sidebar/alert_managment_sidebar_assignees_spec.js154
-rw-r--r--spec/frontend/alert_management/components/sidebar/alert_sidebar_spec.js64
-rw-r--r--spec/frontend/alert_management/components/sidebar/alert_sidebar_status_spec.js129
-rw-r--r--spec/frontend/alert_management/components/system_notes/alert_management_system_note_spec.js38
-rw-r--r--spec/frontend/alert_management/mocks/alerts.json4
-rw-r--r--spec/frontend/alert_settings/__snapshots__/alert_settings_form_spec.js.snap48
-rw-r--r--spec/frontend/alert_settings/alert_settings_form_spec.js233
-rw-r--r--spec/frontend/alerts_service_settings/components/alerts_service_form_spec.js14
-rw-r--r--spec/frontend/api_spec.js95
-rw-r--r--spec/frontend/awards_handler_spec.js292
-rw-r--r--spec/frontend/batch_comments/stores/modules/batch_comments/actions_spec.js28
-rw-r--r--spec/frontend/behaviors/copy_as_gfm_spec.js5
-rw-r--r--spec/frontend/behaviors/gl_emoji_spec.js110
-rw-r--r--spec/frontend/behaviors/shortcuts/shortcuts_issuable_spec.js2
-rw-r--r--spec/frontend/blob/components/__snapshots__/blob_header_filepath_spec.js.snap1
-rw-r--r--spec/frontend/blob/components/blob_content_error_spec.js6
-rw-r--r--spec/frontend/blob/components/blob_header_default_actions_spec.js5
-rw-r--r--spec/frontend/blob/components/blob_header_viewer_switcher_spec.js6
-rw-r--r--spec/frontend/blob/components/mock_data.js14
-rw-r--r--spec/frontend/blob_edit/edit_blob_spec.js31
-rw-r--r--spec/frontend/boards/components/board_form_spec.js5
-rw-r--r--spec/frontend/boards/issue_card_spec.js2
-rw-r--r--spec/frontend/ci_variable_list/components/ci_key_field_spec.js244
-rw-r--r--spec/frontend/ci_variable_list/components/ci_variable_modal_spec.js73
-rw-r--r--spec/frontend/ci_variable_list/services/mock_data.js2
-rw-r--r--spec/frontend/ci_variable_list/store/actions_spec.js68
-rw-r--r--spec/frontend/ci_variable_list/store/mutations_spec.js78
-rw-r--r--spec/frontend/close_reopen_report_toggle_spec.js7
-rw-r--r--spec/frontend/clusters/components/remove_cluster_confirmation_spec.js17
-rw-r--r--spec/frontend/clusters_list/components/ancestor_notice_spec.js51
-rw-r--r--spec/frontend/clusters_list/components/clusters_spec.js59
-rw-r--r--spec/frontend/clusters_list/store/actions_spec.js63
-rw-r--r--spec/frontend/clusters_list/store/mutations_spec.js60
-rw-r--r--spec/frontend/code_navigation/components/__snapshots__/popover_spec.js.snap110
-rw-r--r--spec/frontend/code_navigation/components/popover_spec.js35
-rw-r--r--spec/frontend/code_navigation/store/actions_spec.js24
-rw-r--r--spec/frontend/code_navigation/utils/index_spec.js2
-rw-r--r--spec/frontend/cycle_analytics/stage_nav_item_spec.js3
-rw-r--r--spec/frontend/design_management/components/design_notes/design_discussion_spec.js2
-rw-r--r--spec/frontend/design_management/pages/design/index_spec.js33
-rw-r--r--spec/frontend/design_management/pages/index_spec.js2
-rw-r--r--spec/frontend/design_management/utils/tracking_spec.js28
-rw-r--r--spec/frontend/design_management_new/components/__snapshots__/design_note_pin_spec.js.snap42
-rw-r--r--spec/frontend/design_management_new/components/__snapshots__/design_presentation_spec.js.snap104
-rw-r--r--spec/frontend/design_management_new/components/__snapshots__/design_scaler_spec.js.snap115
-rw-r--r--spec/frontend/design_management_new/components/__snapshots__/image_spec.js.snap68
-rw-r--r--spec/frontend/design_management_new/components/delete_button_spec.js51
-rw-r--r--spec/frontend/design_management_new/components/design_note_pin_spec.js49
-rw-r--r--spec/frontend/design_management_new/components/design_notes/__snapshots__/design_note_spec.js.snap67
-rw-r--r--spec/frontend/design_management_new/components/design_notes/__snapshots__/design_reply_form_spec.js.snap15
-rw-r--r--spec/frontend/design_management_new/components/design_notes/design_discussion_spec.js322
-rw-r--r--spec/frontend/design_management_new/components/design_notes/design_note_spec.js170
-rw-r--r--spec/frontend/design_management_new/components/design_notes/design_reply_form_spec.js184
-rw-r--r--spec/frontend/design_management_new/components/design_notes/toggle_replies_widget_spec.js98
-rw-r--r--spec/frontend/design_management_new/components/design_overlay_spec.js410
-rw-r--r--spec/frontend/design_management_new/components/design_presentation_spec.js553
-rw-r--r--spec/frontend/design_management_new/components/design_scaler_spec.js67
-rw-r--r--spec/frontend/design_management_new/components/design_sidebar_spec.js236
-rw-r--r--spec/frontend/design_management_new/components/image_spec.js133
-rw-r--r--spec/frontend/design_management_new/components/list/__snapshots__/item_spec.js.snap472
-rw-r--r--spec/frontend/design_management_new/components/list/item_spec.js168
-rw-r--r--spec/frontend/design_management_new/components/toolbar/__snapshots__/index_spec.js.snap63
-rw-r--r--spec/frontend/design_management_new/components/toolbar/__snapshots__/pagination_button_spec.js.snap28
-rw-r--r--spec/frontend/design_management_new/components/toolbar/__snapshots__/pagination_spec.js.snap29
-rw-r--r--spec/frontend/design_management_new/components/toolbar/index_spec.js123
-rw-r--r--spec/frontend/design_management_new/components/toolbar/pagination_button_spec.js61
-rw-r--r--spec/frontend/design_management_new/components/toolbar/pagination_spec.js79
-rw-r--r--spec/frontend/design_management_new/components/upload/__snapshots__/button_spec.js.snap85
-rw-r--r--spec/frontend/design_management_new/components/upload/__snapshots__/design_dropzone_spec.js.snap501
-rw-r--r--spec/frontend/design_management_new/components/upload/__snapshots__/design_version_dropdown_spec.js.snap141
-rw-r--r--spec/frontend/design_management_new/components/upload/button_spec.js59
-rw-r--r--spec/frontend/design_management_new/components/upload/design_dropzone_spec.js151
-rw-r--r--spec/frontend/design_management_new/components/upload/design_version_dropdown_spec.js114
-rw-r--r--spec/frontend/design_management_new/components/upload/mock_data/all_versions.js14
-rw-r--r--spec/frontend/design_management_new/mock_data/all_versions.js8
-rw-r--r--spec/frontend/design_management_new/mock_data/design.js74
-rw-r--r--spec/frontend/design_management_new/mock_data/designs.js17
-rw-r--r--spec/frontend/design_management_new/mock_data/no_designs.js11
-rw-r--r--spec/frontend/design_management_new/mock_data/notes.js46
-rw-r--r--spec/frontend/design_management_new/pages/__snapshots__/index_spec.js.snap317
-rw-r--r--spec/frontend/design_management_new/pages/design/__snapshots__/index_spec.js.snap216
-rw-r--r--spec/frontend/design_management_new/pages/design/index_spec.js294
-rw-r--r--spec/frontend/design_management_new/pages/index_spec.js571
-rw-r--r--spec/frontend/design_management_new/router_spec.js70
-rw-r--r--spec/frontend/design_management_new/utils/cache_update_spec.js44
-rw-r--r--spec/frontend/design_management_new/utils/design_management_utils_spec.js176
-rw-r--r--spec/frontend/design_management_new/utils/error_messages_spec.js62
-rw-r--r--spec/frontend/design_management_new/utils/tracking_spec.js59
-rw-r--r--spec/frontend/diffs/components/app_spec.js55
-rw-r--r--spec/frontend/diffs/components/diff_expansion_cell_spec.js4
-rw-r--r--spec/frontend/diffs/components/diff_file_header_spec.js1
-rw-r--r--spec/frontend/diffs/components/diff_file_row_spec.js11
-rw-r--r--spec/frontend/diffs/components/diff_file_spec.js3
-rw-r--r--spec/frontend/diffs/components/diff_gutter_avatars_spec.js2
-rw-r--r--spec/frontend/diffs/components/diff_line_note_form_spec.js16
-rw-r--r--spec/frontend/diffs/components/diff_table_cell_spec.js15
-rw-r--r--spec/frontend/diffs/components/inline_diff_table_row_spec.js53
-rw-r--r--spec/frontend/diffs/components/no_changes_spec.js8
-rw-r--r--spec/frontend/diffs/components/parallel_diff_table_row_spec.js28
-rw-r--r--spec/frontend/diffs/store/actions_spec.js82
-rw-r--r--spec/frontend/diffs/store/utils_spec.js22
-rw-r--r--spec/frontend/editor/editor_lite_spec.js70
-rw-r--r--spec/frontend/editor/editor_markdown_ext_spec.js204
-rw-r--r--spec/frontend/emoji/emoji_spec.js382
-rw-r--r--spec/frontend/emoji/support/unicode_support_map_spec.js (renamed from spec/frontend/behaviors/gl_emoji/unicode_support_map_spec.js)0
-rw-r--r--spec/frontend/emoji_spec.js485
-rw-r--r--spec/frontend/environment.js7
-rw-r--r--spec/frontend/environments/emtpy_state_spec.js16
-rw-r--r--spec/frontend/error_tracking/components/error_details_spec.js12
-rw-r--r--spec/frontend/filtered_search/dropdown_user_spec.js8
-rw-r--r--spec/frontend/filtered_search/filtered_search_manager_spec.js4
-rw-r--r--spec/frontend/filtered_search/stores/recent_searches_store_spec.js9
-rw-r--r--spec/frontend/filtered_search/visual_token_value_spec.js3
-rw-r--r--spec/frontend/fixtures/branches.rb50
-rw-r--r--spec/frontend/fixtures/commit.rb49
-rw-r--r--spec/frontend/fixtures/emojis.rb17
-rw-r--r--spec/frontend/fixtures/metrics_dashboard.rb12
-rw-r--r--spec/frontend/fixtures/services.rb2
-rw-r--r--spec/frontend/fixtures/static/mini_dropdown_graph.html24
-rw-r--r--spec/frontend/fixtures/static/search_autocomplete.html (renamed from spec/frontend/fixtures/static/global_search_input.html)0
-rw-r--r--spec/frontend/fixtures/tags.rb28
-rw-r--r--spec/frontend/gfm_auto_complete_spec.js2
-rw-r--r--spec/frontend/gl_form_spec.js18
-rw-r--r--spec/frontend/global_search_input_spec.js215
-rw-r--r--spec/frontend/helpers/event_hub_factory_spec.js142
-rw-r--r--spec/frontend/helpers/fake_request_animation_frame.js13
-rw-r--r--spec/frontend/helpers/init_vue_mr_page_helper.js46
-rw-r--r--spec/frontend/helpers/monitor_helper_spec.js9
-rw-r--r--spec/frontend/helpers/test_constants.js22
-rw-r--r--spec/frontend/helpers/vue_mock_directive.js20
-rw-r--r--spec/frontend/helpers/wait_using_real_timer.js7
-rw-r--r--spec/frontend/ide/commit_icon_spec.js1
-rw-r--r--spec/frontend/ide/components/ide_status_list_spec.js8
-rw-r--r--spec/frontend/ide/components/jobs/__snapshots__/stage_spec.js.snap2
-rw-r--r--spec/frontend/ide/components/repo_editor_spec.js113
-rw-r--r--spec/frontend/ide/helpers.js1
-rw-r--r--spec/frontend/ide/lib/editor_spec.js22
-rw-r--r--spec/frontend/ide/services/index_spec.js6
-rw-r--r--spec/frontend/ide/stores/actions/file_spec.js361
-rw-r--r--spec/frontend/ide/stores/actions/merge_request_spec.js1
-rw-r--r--spec/frontend/ide/stores/actions/tree_spec.js3
-rw-r--r--spec/frontend/ide/utils_spec.js52
-rw-r--r--spec/frontend/image_diff/helpers/comment_indicator_helper_spec.js3
-rw-r--r--spec/frontend/image_diff/helpers/utils_helper_spec.js3
-rw-r--r--spec/frontend/image_diff/image_diff_spec.js3
-rw-r--r--spec/frontend/image_diff/replaced_image_diff_spec.js7
-rw-r--r--spec/frontend/import_projects/store/actions_spec.js27
-rw-r--r--spec/frontend/incidents_settings/components/__snapshots__/alerts_form_spec.js.snap99
-rw-r--r--spec/frontend/incidents_settings/components/__snapshots__/incidents_settings_tabs_spec.js.snap63
-rw-r--r--spec/frontend/incidents_settings/components/__snapshots__/pagerduty_form_spec.js.snap89
-rw-r--r--spec/frontend/incidents_settings/components/alerts_form_spec.js49
-rw-r--r--spec/frontend/incidents_settings/components/incidents_settings_service_spec.js55
-rw-r--r--spec/frontend/incidents_settings/components/incidents_settings_tabs_spec.js55
-rw-r--r--spec/frontend/incidents_settings/components/pagerduty_form_spec.js67
-rw-r--r--spec/frontend/integrations/edit/components/active_toggle_spec.js18
-rw-r--r--spec/frontend/integrations/edit/components/dynamic_field_spec.js242
-rw-r--r--spec/frontend/integrations/edit/components/integration_form_spec.js94
-rw-r--r--spec/frontend/integrations/edit/components/jira_issues_fields_spec.js96
-rw-r--r--spec/frontend/integrations/edit/components/jira_trigger_fields_spec.js25
-rw-r--r--spec/frontend/integrations/edit/components/trigger_fields_spec.js51
-rw-r--r--spec/frontend/integrations/edit/mock_data.js18
-rw-r--r--spec/frontend/integrations/edit/store/actions_spec.js19
-rw-r--r--spec/frontend/integrations/edit/store/getters_spec.js71
-rw-r--r--spec/frontend/integrations/edit/store/mutations_spec.js19
-rw-r--r--spec/frontend/integrations/edit/store/state_spec.js26
-rw-r--r--spec/frontend/issuable_suggestions/components/app_spec.js4
-rw-r--r--spec/frontend/issuable_suggestions/components/item_spec.js5
-rw-r--r--spec/frontend/issuable_suggestions/mock_data.js8
-rw-r--r--spec/frontend/issuables_list/components/issuable_list_root_app_spec.js24
-rw-r--r--spec/frontend/issuables_list/components/issuable_spec.js167
-rw-r--r--spec/frontend/issuables_list/components/issuables_list_app_spec.js148
-rw-r--r--spec/frontend/issue_show/components/issuable_header_warnings_spec.js79
-rw-r--r--spec/frontend/issue_show/components/pinned_links_spec.js15
-rw-r--r--spec/frontend/jira_import/components/__snapshots__/jira_import_form_spec.js.snap277
-rw-r--r--spec/frontend/jira_import/components/jira_import_app_spec.js208
-rw-r--r--spec/frontend/jira_import/components/jira_import_form_spec.js179
-rw-r--r--spec/frontend/jira_import/components/jira_import_progress_spec.js7
-rw-r--r--spec/frontend/jira_import/components/jira_import_setup_spec.js4
-rw-r--r--spec/frontend/jira_import/mock_data.js53
-rw-r--r--spec/frontend/jira_import/utils/jira_import_utils_spec.js59
-rw-r--r--spec/frontend/jobs/components/job_app_spec.js148
-rw-r--r--spec/frontend/jobs/components/job_log_spec.js65
-rw-r--r--spec/frontend/jobs/components/log/collapsible_section_spec.js4
-rw-r--r--spec/frontend/jobs/store/mutations_spec.js25
-rw-r--r--spec/frontend/jobs/store/utils_spec.js4
-rw-r--r--spec/frontend/lib/utils/common_utils_spec.js47
-rw-r--r--spec/frontend/lib/utils/datetime_utility_spec.js26
-rw-r--r--spec/frontend/lib/utils/dom_utils_spec.js50
-rw-r--r--spec/frontend/lib/utils/grammar_spec.js12
-rw-r--r--spec/frontend/lib/utils/text_markdown_spec.js108
-rw-r--r--spec/frontend/lib/utils/text_utility_spec.js50
-rw-r--r--spec/frontend/lib/utils/url_utility_spec.js8
-rw-r--r--spec/frontend/logs/components/environment_logs_spec.js2
-rw-r--r--spec/frontend/logs/components/log_control_buttons_spec.js8
-rw-r--r--spec/frontend/logs/mock_data.js15
-rw-r--r--spec/frontend/logs/stores/actions_spec.js27
-rw-r--r--spec/frontend/logs/stores/mutations_spec.js35
-rw-r--r--spec/frontend/merge_request_tabs_spec.js2
-rw-r--r--spec/frontend/monitoring/components/__snapshots__/dashboard_template_spec.js.snap55
-rw-r--r--spec/frontend/monitoring/components/__snapshots__/empty_state_spec.js.snap68
-rw-r--r--spec/frontend/monitoring/components/charts/anomaly_spec.js133
-rw-r--r--spec/frontend/monitoring/components/charts/column_spec.js10
-rw-r--r--spec/frontend/monitoring/components/charts/single_stat_spec.js54
-rw-r--r--spec/frontend/monitoring/components/charts/time_series_spec.js95
-rw-r--r--spec/frontend/monitoring/components/create_dashboard_modal_spec.js48
-rw-r--r--spec/frontend/monitoring/components/dashboard_header_spec.js232
-rw-r--r--spec/frontend/monitoring/components/dashboard_panel_spec.js59
-rw-r--r--spec/frontend/monitoring/components/dashboard_spec.js237
-rw-r--r--spec/frontend/monitoring/components/dashboard_template_spec.js4
-rw-r--r--spec/frontend/monitoring/components/dashboard_url_time_spec.js5
-rw-r--r--spec/frontend/monitoring/components/dashboards_dropdown_spec.js184
-rw-r--r--spec/frontend/monitoring/components/duplicate_dashboard_modal_spec.js111
-rw-r--r--spec/frontend/monitoring/components/empty_state_spec.js23
-rw-r--r--spec/frontend/monitoring/components/graph_group_spec.js126
-rw-r--r--spec/frontend/monitoring/components/links_section_spec.js2
-rw-r--r--spec/frontend/monitoring/components/refresh_button_spec.js143
-rw-r--r--spec/frontend/monitoring/components/variables/custom_variable_spec.js52
-rw-r--r--spec/frontend/monitoring/components/variables/dropdown_field_spec.js65
-rw-r--r--spec/frontend/monitoring/components/variables/text_field_spec.js59
-rw-r--r--spec/frontend/monitoring/components/variables/text_variable_spec.js59
-rw-r--r--spec/frontend/monitoring/components/variables_section_spec.js63
-rw-r--r--spec/frontend/monitoring/fixture_data.js40
-rw-r--r--spec/frontend/monitoring/graph_data.js164
-rw-r--r--spec/frontend/monitoring/mock_data.js572
-rw-r--r--spec/frontend/monitoring/pages/dashboard_page_spec.js36
-rw-r--r--spec/frontend/monitoring/router_spec.js81
-rw-r--r--spec/frontend/monitoring/store/actions_spec.js980
-rw-r--r--spec/frontend/monitoring/store/getters_spec.js40
-rw-r--r--spec/frontend/monitoring/store/mutations_spec.js154
-rw-r--r--spec/frontend/monitoring/store/utils_spec.js297
-rw-r--r--spec/frontend/monitoring/store/variable_mapping_spec.js263
-rw-r--r--spec/frontend/monitoring/store_utils.js32
-rw-r--r--spec/frontend/monitoring/utils_spec.js55
-rw-r--r--spec/frontend/namespace_storage_limit_alert_spec.js36
-rw-r--r--spec/frontend/notes/components/multiline_comment_utils_spec.js64
-rw-r--r--spec/frontend/notes/components/note_actions_spec.js64
-rw-r--r--spec/frontend/notes/components/note_form_spec.js18
-rw-r--r--spec/frontend/notes/components/noteable_note_spec.js71
-rw-r--r--spec/frontend/notes/mixins/discussion_navigation_spec.js6
-rw-r--r--spec/frontend/notes/old_notes_spec.js44
-rw-r--r--spec/frontend/notes/stores/actions_spec.js133
-rw-r--r--spec/frontend/notes/stores/mutation_spec.js34
-rw-r--r--spec/frontend/pager_spec.js7
-rw-r--r--spec/frontend/pages/admin/jobs/index/components/stop_jobs_modal_spec.js5
-rw-r--r--spec/frontend/pages/labels/components/promote_label_modal_spec.js5
-rw-r--r--spec/frontend/pages/milestones/shared/components/delete_milestone_modal_spec.js5
-rw-r--r--spec/frontend/pages/milestones/shared/components/promote_milestone_modal_spec.js5
-rw-r--r--spec/frontend/pages/projects/forks/new/components/fork_groups_list_item_spec.js78
-rw-r--r--spec/frontend/pages/projects/forks/new/components/fork_groups_list_spec.js133
-rw-r--r--spec/frontend/pages/projects/graphs/code_coverage_spec.js6
-rw-r--r--spec/frontend/pages/projects/graphs/mock_data.js91
-rw-r--r--spec/frontend/pages/projects/pipeline_schedules/shared/components/interval_pattern_input_spec.js121
-rw-r--r--spec/frontend/persistent_user_callout_spec.js66
-rw-r--r--spec/frontend/pipelines/blank_state_spec.js2
-rw-r--r--spec/frontend/pipelines/components/dag/__snapshots__/dag_graph_spec.js.snap44
-rw-r--r--spec/frontend/pipelines/components/dag/dag_annotations_spec.js112
-rw-r--r--spec/frontend/pipelines/components/dag/dag_graph_spec.js4
-rw-r--r--spec/frontend/pipelines/components/dag/dag_spec.js171
-rw-r--r--spec/frontend/pipelines/components/dag/mock_data.js80
-rw-r--r--spec/frontend/pipelines/components/pipelines_filtered_search_spec.js2
-rw-r--r--spec/frontend/pipelines/empty_state_spec.js2
-rw-r--r--spec/frontend/pipelines/graph/job_item_spec.js13
-rw-r--r--spec/frontend/pipelines/graph/linked_pipeline_spec.js28
-rw-r--r--spec/frontend/pipelines/graph/linked_pipelines_mock_data.js21
-rw-r--r--spec/frontend/pipelines/nav_controls_spec.js2
-rw-r--r--spec/frontend/pipelines/pipeline_triggerer_spec.js2
-rw-r--r--spec/frontend/pipelines/pipeline_url_spec.js116
-rw-r--r--spec/frontend/pipelines/pipelines_actions_spec.js2
-rw-r--r--spec/frontend/pipelines/pipelines_artifacts_spec.js2
-rw-r--r--spec/frontend/pipelines/pipelines_spec.js14
-rw-r--r--spec/frontend/pipelines/pipelines_table_row_spec.js2
-rw-r--r--spec/frontend/pipelines/pipelines_table_spec.js2
-rw-r--r--spec/frontend/pipelines/stage_spec.js2
-rw-r--r--spec/frontend/pipelines/test_reports/stores/actions_spec.js109
-rw-r--r--spec/frontend/pipelines/test_reports/stores/getters_spec.js15
-rw-r--r--spec/frontend/pipelines/test_reports/stores/mutations_spec.js34
-rw-r--r--spec/frontend/pipelines/test_reports/test_reports_spec.js71
-rw-r--r--spec/frontend/pipelines/test_reports/test_suite_table_spec.js11
-rw-r--r--spec/frontend/pipelines/test_reports/test_summary_spec.js2
-rw-r--r--spec/frontend/pipelines/time_ago_spec.js2
-rw-r--r--spec/frontend/pipelines/tokens/pipeline_branch_name_token_spec.js2
-rw-r--r--spec/frontend/pipelines/tokens/pipeline_status_token_spec.js2
-rw-r--r--spec/frontend/pipelines/tokens/pipeline_tag_name_token_spec.js2
-rw-r--r--spec/frontend/pipelines/tokens/pipeline_trigger_author_token_spec.js2
-rw-r--r--spec/frontend/polyfills/element_spec.js46
-rw-r--r--spec/frontend/projects/commits/store/actions_spec.js4
-rw-r--r--spec/frontend/projects/components/__snapshots__/remove_modal_spec.js.snap126
-rw-r--r--spec/frontend/projects/components/remove_modal_spec.js62
-rw-r--r--spec/frontend/projects/pipelines/charts/components/__snapshots__/pipelines_area_chart_spec.js.snap2
-rw-r--r--spec/frontend/projects/project_new_spec.js3
-rw-r--r--spec/frontend/projects/settings_service_desk/components/service_desk_root_spec.js226
-rw-r--r--spec/frontend/projects/settings_service_desk/components/service_desk_setting_spec.js234
-rw-r--r--spec/frontend/projects/settings_service_desk/services/service_desk_service_spec.js129
-rw-r--r--spec/frontend/ref/components/ref_selector_spec.js532
-rw-r--r--spec/frontend/ref/stores/actions_spec.js180
-rw-r--r--spec/frontend/ref/stores/getters_spec.js36
-rw-r--r--spec/frontend/ref/stores/mutations_spec.js274
-rw-r--r--spec/frontend/registry/explorer/components/delete_button_spec.js73
-rw-r--r--spec/frontend/registry/explorer/components/details_page/details_row_spec.js43
-rw-r--r--spec/frontend/registry/explorer/components/details_page/empty_tags_state.js43
-rw-r--r--spec/frontend/registry/explorer/components/details_page/empty_tags_state_spec.js43
-rw-r--r--spec/frontend/registry/explorer/components/details_page/tags_list_row_spec.js330
-rw-r--r--spec/frontend/registry/explorer/components/details_page/tags_list_spec.js146
-rw-r--r--spec/frontend/registry/explorer/components/details_page/tags_table_spec.js286
-rw-r--r--spec/frontend/registry/explorer/components/list_item_spec.js156
-rw-r--r--spec/frontend/registry/explorer/components/list_page/__snapshots__/group_empty_state_spec.js.snap5
-rw-r--r--spec/frontend/registry/explorer/components/list_page/__snapshots__/project_empty_state_spec.js.snap95
-rw-r--r--spec/frontend/registry/explorer/components/list_page/image_list_row_spec.js35
-rw-r--r--spec/frontend/registry/explorer/mock_data.js10
-rw-r--r--spec/frontend/registry/explorer/pages/details_spec.js53
-rw-r--r--spec/frontend/registry/explorer/stubs.js17
-rw-r--r--spec/frontend/registry/settings/components/__snapshots__/registry_settings_app_spec.js.snap18
-rw-r--r--spec/frontend/registry/settings/components/registry_settings_app_spec.js11
-rw-r--r--spec/frontend/registry/settings/components/settings_form_spec.js69
-rw-r--r--spec/frontend/registry/shared/components/__snapshots__/expiration_policy_fields_spec.js.snap20
-rw-r--r--spec/frontend/registry/shared/components/expiration_policy_fields_spec.js75
-rw-r--r--spec/frontend/releases/components/app_new_spec.js26
-rw-r--r--spec/frontend/releases/components/release_block_assets_spec.js32
-rw-r--r--spec/frontend/reports/codequality_report/components/codequality_issue_body_spec.js62
-rw-r--r--spec/frontend/reports/codequality_report/grouped_codequality_reports_app_spec.js146
-rw-r--r--spec/frontend/reports/codequality_report/mock_data.js90
-rw-r--r--spec/frontend/reports/codequality_report/store/actions_spec.js151
-rw-r--r--spec/frontend/reports/codequality_report/store/getters_spec.js95
-rw-r--r--spec/frontend/reports/codequality_report/store/mutations_spec.js80
-rw-r--r--spec/frontend/reports/codequality_report/store/utils/codequality_comparison_spec.js139
-rw-r--r--spec/frontend/reports/components/grouped_test_reports_app_spec.js45
-rw-r--r--spec/frontend/reports/components/report_section_spec.js102
-rw-r--r--spec/frontend/reports/components/summary_row_spec.js43
-rw-r--r--spec/frontend/repository/components/table/__snapshots__/row_spec.js.snap55
-rw-r--r--spec/frontend/repository/components/table/index_spec.js15
-rw-r--r--spec/frontend/repository/components/table/row_spec.js16
-rw-r--r--spec/frontend/repository/components/web_ide_link_spec.js51
-rw-r--r--spec/frontend/repository/utils/dom_spec.js3
-rw-r--r--spec/frontend/search_autocomplete_spec.js284
-rw-r--r--spec/frontend/self_monitor/components/__snapshots__/self_monitor_form_spec.js.snap4
-rw-r--r--spec/frontend/self_monitor/components/self_monitor_form_spec.js3
-rw-r--r--spec/frontend/sidebar/__snapshots__/confidential_issue_sidebar_spec.js.snap6
-rw-r--r--spec/frontend/sidebar/confidential/edit_form_buttons_spec.js130
-rw-r--r--spec/frontend/sidebar/confidential/edit_form_spec.js2
-rw-r--r--spec/frontend/sidebar/confidential_issue_sidebar_spec.js28
-rw-r--r--spec/frontend/snippets/components/edit_spec.js179
-rw-r--r--spec/frontend/snippets/components/show_spec.js35
-rw-r--r--spec/frontend/snippets/components/snippet_blob_edit_spec.js137
-rw-r--r--spec/frontend/snippets/components/snippet_blob_view_spec.js38
-rw-r--r--spec/frontend/snippets/components/snippet_header_spec.js19
-rw-r--r--spec/frontend/static_site_editor/components/edit_area_spec.js41
-rw-r--r--spec/frontend/static_site_editor/mock_data.js7
-rw-r--r--spec/frontend/static_site_editor/services/parse_source_file_spec.js92
-rw-r--r--spec/frontend/static_site_editor/services/submit_content_changes_spec.js48
-rw-r--r--spec/frontend/vue_mr_widget/components/approvals/approvals_spec.js391
-rw-r--r--spec/frontend/vue_mr_widget/components/approvals/approvals_summary_optional_spec.js57
-rw-r--r--spec/frontend/vue_mr_widget/components/approvals/approvals_summary_spec.js93
-rw-r--r--spec/frontend/vue_mr_widget/components/mr_widget_author_spec.js54
-rw-r--r--spec/frontend/vue_mr_widget/components/mr_widget_expandable_section_spec.js65
-rw-r--r--spec/frontend/vue_mr_widget/components/mr_widget_header_spec.js35
-rw-r--r--spec/frontend/vue_mr_widget/components/mr_widget_pipeline_spec.js300
-rw-r--r--spec/frontend/vue_mr_widget/components/mr_widget_suggest_pipeline_spec.js86
-rw-r--r--spec/frontend/vue_mr_widget/components/mr_widget_terraform_plan_spec.js107
-rw-r--r--spec/frontend/vue_mr_widget/components/pipeline_tour_mock_data.js7
-rw-r--r--spec/frontend/vue_mr_widget/components/states/mr_widget_auto_merge_enabled_spec.js9
-rw-r--r--spec/frontend/vue_mr_widget/components/states/mr_widget_checking_spec.js2
-rw-r--r--spec/frontend/vue_mr_widget/components/states/mr_widget_pipeline_tour_spec.js143
-rw-r--r--spec/frontend/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js44
-rw-r--r--spec/frontend/vue_mr_widget/components/states/mr_widget_squash_before_merge_spec.js42
-rw-r--r--spec/frontend/vue_mr_widget/components/states/pipeline_tour_mock_data.js10
-rw-r--r--spec/frontend/vue_mr_widget/components/terraform/mock_data.js31
-rw-r--r--spec/frontend/vue_mr_widget/components/terraform/mr_widget_terraform_container_spec.js172
-rw-r--r--spec/frontend/vue_mr_widget/components/terraform/terraform_plan_spec.js95
-rw-r--r--spec/frontend/vue_mr_widget/mock_data.js15
-rw-r--r--spec/frontend/vue_mr_widget/mr_widget_options_spec.js6
-rw-r--r--spec/frontend/vue_mr_widget/stores/get_state_key_spec.js8
-rw-r--r--spec/frontend/vue_shared/components/__snapshots__/awards_list_spec.js.snap63
-rw-r--r--spec/frontend/vue_shared/components/file_icon_spec.js27
-rw-r--r--spec/frontend/vue_shared/components/filtered_search_bar/filtered_search_bar_root_spec.js60
-rw-r--r--spec/frontend/vue_shared/components/filtered_search_bar/mock_data.js23
-rw-r--r--spec/frontend/vue_shared/components/filtered_search_bar/tokens/author_token_spec.js23
-rw-r--r--spec/frontend/vue_shared/components/gl_modal_vuex_spec.js28
-rw-r--r--spec/frontend/vue_shared/components/issue/__snapshots__/issue_warning_spec.js.snap62
-rw-r--r--spec/frontend/vue_shared/components/issue/issue_assignees_spec.js2
-rw-r--r--spec/frontend/vue_shared/components/issue/issue_milestone_spec.js2
-rw-r--r--spec/frontend/vue_shared/components/issue/issue_warning_spec.js105
-rw-r--r--spec/frontend/vue_shared/components/issue/related_issuable_item_spec.js3
-rw-r--r--spec/frontend/vue_shared/components/issue/related_issuable_mock_data.js18
-rw-r--r--spec/frontend/vue_shared/components/markdown/suggestion_diff_header_spec.js25
-rw-r--r--spec/frontend/vue_shared/components/notes/__snapshots__/noteable_warning_spec.js.snap58
-rw-r--r--spec/frontend/vue_shared/components/notes/noteable_warning_spec.js196
-rw-r--r--spec/frontend/vue_shared/components/project_selector/project_list_item_spec.js10
-rw-r--r--spec/frontend/vue_shared/components/remove_member_modal_spec.js65
-rw-r--r--spec/frontend/vue_shared/components/resizable_chart/__snapshots__/resizable_chart_container_spec.js.snap (renamed from spec/frontend/vue_shared/components/__snapshots__/resizable_chart_container_spec.js.snap)0
-rw-r--r--spec/frontend/vue_shared/components/resizable_chart/__snapshots__/skeleton_loader_spec.js.snap324
-rw-r--r--spec/frontend/vue_shared/components/resizable_chart/resizable_chart_container_spec.js (renamed from spec/frontend/vue_shared/components/resizable_chart_container_spec.js)0
-rw-r--r--spec/frontend/vue_shared/components/resizable_chart/skeleton_loader_spec.js55
-rw-r--r--spec/frontend/vue_shared/components/rich_content_editor/editor_service_spec.js62
-rw-r--r--spec/frontend/vue_shared/components/rich_content_editor/modals/add_image/add_image_modal_spec.js76
-rw-r--r--spec/frontend/vue_shared/components/rich_content_editor/modals/add_image/upload_image_tab_spec.js41
-rw-r--r--spec/frontend/vue_shared/components/rich_content_editor/modals/add_image_modal_spec.js41
-rw-r--r--spec/frontend/vue_shared/components/rich_content_editor/rich_content_editor_spec.js43
-rw-r--r--spec/frontend/vue_shared/components/rich_content_editor/services/build_custom_renderer_spec.js29
-rw-r--r--spec/frontend/vue_shared/components/rich_content_editor/services/build_html_to_markdown_renderer_spec.js50
-rw-r--r--spec/frontend/vue_shared/components/rich_content_editor/services/renderers/build_uneditable_token_spec.js88
-rw-r--r--spec/frontend/vue_shared/components/rich_content_editor/services/renderers/mock_data.js58
-rw-r--r--spec/frontend/vue_shared/components/rich_content_editor/services/renderers/render_embedded_ruby_spec.js30
-rw-r--r--spec/frontend/vue_shared/components/rich_content_editor/services/renderers/render_font_awesome_html_inline_spec.js33
-rw-r--r--spec/frontend/vue_shared/components/rich_content_editor/services/renderers/render_html_block_spec.js38
-rw-r--r--spec/frontend/vue_shared/components/rich_content_editor/services/renderers/render_identifier_instance_text_spec.js55
-rw-r--r--spec/frontend/vue_shared/components/rich_content_editor/services/renderers/render_identifier_paragraph_spec.js65
-rw-r--r--spec/frontend/vue_shared/components/rich_content_editor/services/renderers/render_kramdown_list_spec.js55
-rw-r--r--spec/frontend/vue_shared/components/rich_content_editor/services/renderers/render_kramdown_text_spec.js30
-rw-r--r--spec/frontend/vue_shared/components/sidebar/labels_select/dropdown_button_spec.js3
-rw-r--r--spec/frontend/vue_shared/components/sidebar/labels_select/dropdown_search_input_spec.js6
-rw-r--r--spec/frontend/vue_shared/components/sidebar/labels_select_vue/dropdown_button_spec.js62
-rw-r--r--spec/frontend/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_labels_view_spec.js14
-rw-r--r--spec/frontend/vue_shared/components/sidebar/labels_select_vue/labels_select_root_spec.js29
-rw-r--r--spec/frontend/vue_shared/components/sidebar/labels_select_vue/store/getters_spec.js21
-rw-r--r--spec/frontend/vue_shared/components/user_popover/user_popover_spec.js46
-rw-r--r--spec/frontend/wikis_spec.js30
-rw-r--r--spec/frontend_integration/ide/__snapshots__/ide_integration_spec.js.snap1
-rw-r--r--spec/graphql/mutations/alert_management/alerts/set_assignees_spec.rb2
-rw-r--r--spec/graphql/mutations/alert_management/alerts/todo/create_spec.rb58
-rw-r--r--spec/graphql/mutations/alert_management/update_alert_status_spec.rb2
-rw-r--r--spec/graphql/mutations/concerns/mutations/resolves_issuable_spec.rb13
-rw-r--r--spec/graphql/mutations/container_expiration_policies/update_spec.rb2
-rw-r--r--spec/graphql/mutations/issues/set_locked_spec.rb46
-rw-r--r--spec/graphql/mutations/issues/update_spec.rb2
-rw-r--r--spec/graphql/mutations/merge_requests/create_spec.rb2
-rw-r--r--spec/graphql/mutations/merge_requests/set_assignees_spec.rb2
-rw-r--r--spec/graphql/mutations/merge_requests/set_labels_spec.rb2
-rw-r--r--spec/graphql/mutations/merge_requests/set_locked_spec.rb2
-rw-r--r--spec/graphql/mutations/merge_requests/set_milestone_spec.rb2
-rw-r--r--spec/graphql/mutations/merge_requests/set_subscription_spec.rb2
-rw-r--r--spec/graphql/mutations/merge_requests/set_wip_spec.rb2
-rw-r--r--spec/graphql/mutations/merge_requests/update_spec.rb68
-rw-r--r--spec/graphql/mutations/todos/mark_all_done_spec.rb5
-rw-r--r--spec/graphql/mutations/todos/mark_done_spec.rb2
-rw-r--r--spec/graphql/mutations/todos/restore_many_spec.rb6
-rw-r--r--spec/graphql/mutations/todos/restore_spec.rb2
-rw-r--r--spec/graphql/resolvers/alert_management/alert_resolver_spec.rb2
-rw-r--r--spec/graphql/resolvers/alert_management/alert_status_counts_resolver_spec.rb2
-rw-r--r--spec/graphql/resolvers/base_resolver_spec.rb2
-rw-r--r--spec/graphql/resolvers/board_lists_resolver_spec.rb2
-rw-r--r--spec/graphql/resolvers/boards_resolver_spec.rb2
-rw-r--r--spec/graphql/resolvers/branch_commit_resolver_spec.rb2
-rw-r--r--spec/graphql/resolvers/ci_configuration/sast_resolver_spec.rb28
-rw-r--r--spec/graphql/resolvers/commit_pipelines_resolver_spec.rb2
-rw-r--r--spec/graphql/resolvers/concerns/looks_ahead_spec.rb2
-rw-r--r--spec/graphql/resolvers/concerns/resolves_pipelines_spec.rb2
-rw-r--r--spec/graphql/resolvers/concerns/resolves_project_spec.rb2
-rw-r--r--spec/graphql/resolvers/design_management/design_at_version_resolver_spec.rb2
-rw-r--r--spec/graphql/resolvers/design_management/design_resolver_spec.rb2
-rw-r--r--spec/graphql/resolvers/design_management/designs_resolver_spec.rb2
-rw-r--r--spec/graphql/resolvers/design_management/version/design_at_version_resolver_spec.rb2
-rw-r--r--spec/graphql/resolvers/design_management/version/designs_at_version_resolver_spec.rb2
-rw-r--r--spec/graphql/resolvers/design_management/version_in_collection_resolver_spec.rb2
-rw-r--r--spec/graphql/resolvers/design_management/version_resolver_spec.rb2
-rw-r--r--spec/graphql/resolvers/design_management/versions_resolver_spec.rb2
-rw-r--r--spec/graphql/resolvers/echo_resolver_spec.rb2
-rw-r--r--spec/graphql/resolvers/environments_resolver_spec.rb2
-rw-r--r--spec/graphql/resolvers/error_tracking/sentry_detailed_error_resolver_spec.rb2
-rw-r--r--spec/graphql/resolvers/error_tracking/sentry_error_collection_resolver_spec.rb2
-rw-r--r--spec/graphql/resolvers/error_tracking/sentry_errors_resolver_spec.rb2
-rw-r--r--spec/graphql/resolvers/group_resolver_spec.rb2
-rw-r--r--spec/graphql/resolvers/issues_resolver_spec.rb38
-rw-r--r--spec/graphql/resolvers/last_commit_resolver_spec.rb28
-rw-r--r--spec/graphql/resolvers/merge_request_pipelines_resolver_spec.rb2
-rw-r--r--spec/graphql/resolvers/merge_requests_resolver_spec.rb25
-rw-r--r--spec/graphql/resolvers/metadata_resolver_spec.rb2
-rw-r--r--spec/graphql/resolvers/metrics/dashboard_resolver_spec.rb2
-rw-r--r--spec/graphql/resolvers/metrics/dashboards/annotation_resolver_spec.rb2
-rw-r--r--spec/graphql/resolvers/milestone_resolver_spec.rb2
-rw-r--r--spec/graphql/resolvers/namespace_projects_resolver_spec.rb2
-rw-r--r--spec/graphql/resolvers/packages_resolver_spec.rb17
-rw-r--r--spec/graphql/resolvers/project_members_resolver_spec.rb2
-rw-r--r--spec/graphql/resolvers/project_pipeline_resolver_spec.rb2
-rw-r--r--spec/graphql/resolvers/project_pipelines_resolver_spec.rb2
-rw-r--r--spec/graphql/resolvers/project_resolver_spec.rb2
-rw-r--r--spec/graphql/resolvers/projects/grafana_integration_resolver_spec.rb2
-rw-r--r--spec/graphql/resolvers/projects/jira_imports_resolver_spec.rb2
-rw-r--r--spec/graphql/resolvers/projects/jira_projects_resolver_spec.rb4
-rw-r--r--spec/graphql/resolvers/projects/services_resolver_spec.rb2
-rw-r--r--spec/graphql/resolvers/projects/snippets_resolver_spec.rb2
-rw-r--r--spec/graphql/resolvers/projects_resolver_spec.rb2
-rw-r--r--spec/graphql/resolvers/release_resolver_spec.rb2
-rw-r--r--spec/graphql/resolvers/releases_resolver_spec.rb2
-rw-r--r--spec/graphql/resolvers/snippets_resolver_spec.rb2
-rw-r--r--spec/graphql/resolvers/todo_resolver_spec.rb43
-rw-r--r--spec/graphql/resolvers/tree_resolver_spec.rb2
-rw-r--r--spec/graphql/resolvers/user_resolver_spec.rb2
-rw-r--r--spec/graphql/resolvers/users/snippets_resolver_spec.rb2
-rw-r--r--spec/graphql/resolvers/users_resolver_spec.rb2
-rw-r--r--spec/graphql/types/access_level_enum_spec.rb2
-rw-r--r--spec/graphql/types/access_level_type_spec.rb2
-rw-r--r--spec/graphql/types/alert_management/alert_status_count_type_spec.rb2
-rw-r--r--spec/graphql/types/alert_management/alert_type_spec.rb3
-rw-r--r--spec/graphql/types/alert_management/severity_enum_spec.rb2
-rw-r--r--spec/graphql/types/alert_management/status_enum_spec.rb2
-rw-r--r--spec/graphql/types/award_emojis/award_emoji_type_spec.rb2
-rw-r--r--spec/graphql/types/base_enum_spec.rb2
-rw-r--r--spec/graphql/types/base_field_spec.rb2
-rw-r--r--spec/graphql/types/blob_viewers/type_enum_spec.rb2
-rw-r--r--spec/graphql/types/board_list_type_spec.rb2
-rw-r--r--spec/graphql/types/board_type_spec.rb2
-rw-r--r--spec/graphql/types/branch_type_spec.rb2
-rw-r--r--spec/graphql/types/ci/detailed_status_type_spec.rb2
-rw-r--r--spec/graphql/types/ci/pipeline_type_spec.rb2
-rw-r--r--spec/graphql/types/ci_configuration/sast/analyzers_entity_type_spec.rb11
-rw-r--r--spec/graphql/types/ci_configuration/sast/entity_type_spec.rb11
-rw-r--r--spec/graphql/types/ci_configuration/sast/options_entity_spec.rb11
-rw-r--r--spec/graphql/types/ci_configuration/sast/type_spec.rb11
-rw-r--r--spec/graphql/types/commit_action_mode_enum_spec.rb2
-rw-r--r--spec/graphql/types/commit_encoding_enum_spec.rb2
-rw-r--r--spec/graphql/types/commit_type_spec.rb2
-rw-r--r--spec/graphql/types/container_expiration_policy_cadence_enum_spec.rb2
-rw-r--r--spec/graphql/types/container_expiration_policy_keep_enum_spec.rb2
-rw-r--r--spec/graphql/types/container_expiration_policy_older_than_enum_spec.rb2
-rw-r--r--spec/graphql/types/container_expiration_policy_type_spec.rb18
-rw-r--r--spec/graphql/types/design_management/design_at_version_type_spec.rb2
-rw-r--r--spec/graphql/types/design_management/design_collection_type_spec.rb2
-rw-r--r--spec/graphql/types/design_management/design_type_spec.rb2
-rw-r--r--spec/graphql/types/design_management/design_version_event_enum_spec.rb2
-rw-r--r--spec/graphql/types/design_management/version_type_spec.rb2
-rw-r--r--spec/graphql/types/design_management_type_spec.rb2
-rw-r--r--spec/graphql/types/diff_refs_type_spec.rb2
-rw-r--r--spec/graphql/types/environment_type_spec.rb2
-rw-r--r--spec/graphql/types/error_tracking/sentry_detailed_error_type_spec.rb4
-rw-r--r--spec/graphql/types/error_tracking/sentry_error_collection_type_spec.rb2
-rw-r--r--spec/graphql/types/error_tracking/sentry_error_stack_trace_entry_type_spec.rb2
-rw-r--r--spec/graphql/types/error_tracking/sentry_error_stack_trace_type_spec.rb2
-rw-r--r--spec/graphql/types/error_tracking/sentry_error_type_spec.rb2
-rw-r--r--spec/graphql/types/evidence_type_spec.rb2
-rw-r--r--spec/graphql/types/global_id_type_spec.rb215
-rw-r--r--spec/graphql/types/grafana_integration_type_spec.rb2
-rw-r--r--spec/graphql/types/group_member_type_spec.rb2
-rw-r--r--spec/graphql/types/group_type_spec.rb2
-rw-r--r--spec/graphql/types/issuable_sort_enum_spec.rb2
-rw-r--r--spec/graphql/types/issuable_state_enum_spec.rb2
-rw-r--r--spec/graphql/types/issue_connection_type_spec.rb11
-rw-r--r--spec/graphql/types/issue_sort_enum_spec.rb2
-rw-r--r--spec/graphql/types/issue_state_enum_spec.rb2
-rw-r--r--spec/graphql/types/issue_type_spec.rb102
-rw-r--r--spec/graphql/types/jira_import_type_spec.rb2
-rw-r--r--spec/graphql/types/jira_user_type_spec.rb13
-rw-r--r--spec/graphql/types/label_type_spec.rb2
-rw-r--r--spec/graphql/types/merge_request_state_enum_spec.rb2
-rw-r--r--spec/graphql/types/merge_request_type_spec.rb5
-rw-r--r--spec/graphql/types/metadata_type_spec.rb2
-rw-r--r--spec/graphql/types/metrics/dashboard_type_spec.rb2
-rw-r--r--spec/graphql/types/metrics/dashboards/annotation_type_spec.rb2
-rw-r--r--spec/graphql/types/milestone_stats_type_spec.rb15
-rw-r--r--spec/graphql/types/milestone_type_spec.rb19
-rw-r--r--spec/graphql/types/mutation_type_spec.rb28
-rw-r--r--spec/graphql/types/namespace_type_spec.rb4
-rw-r--r--spec/graphql/types/notes/diff_position_type_spec.rb2
-rw-r--r--spec/graphql/types/notes/discussion_type_spec.rb2
-rw-r--r--spec/graphql/types/notes/note_type_spec.rb3
-rw-r--r--spec/graphql/types/notes/noteable_type_spec.rb2
-rw-r--r--spec/graphql/types/package_type_enum_spec.rb9
-rw-r--r--spec/graphql/types/package_type_spec.rb15
-rw-r--r--spec/graphql/types/permission_types/base_permission_type_spec.rb2
-rw-r--r--spec/graphql/types/permission_types/issue_spec.rb2
-rw-r--r--spec/graphql/types/permission_types/merge_request_spec.rb2
-rw-r--r--spec/graphql/types/permission_types/merge_request_type_spec.rb2
-rw-r--r--spec/graphql/types/permission_types/note_spec.rb2
-rw-r--r--spec/graphql/types/permission_types/project_spec.rb2
-rw-r--r--spec/graphql/types/permission_types/snippet_spec.rb2
-rw-r--r--spec/graphql/types/permission_types/user_spec.rb2
-rw-r--r--spec/graphql/types/project_member_type_spec.rb2
-rw-r--r--spec/graphql/types/project_statistics_type_spec.rb6
-rw-r--r--spec/graphql/types/project_type_spec.rb92
-rw-r--r--spec/graphql/types/projects/base_service_type_spec.rb2
-rw-r--r--spec/graphql/types/projects/jira_project_type_spec.rb2
-rw-r--r--spec/graphql/types/projects/jira_service_type_spec.rb2
-rw-r--r--spec/graphql/types/projects/service_type_spec.rb2
-rw-r--r--spec/graphql/types/projects/services_enum_spec.rb2
-rw-r--r--spec/graphql/types/query_type_spec.rb2
-rw-r--r--spec/graphql/types/release_asset_link_type_spec.rb15
-rw-r--r--spec/graphql/types/release_assets_type_spec.rb6
-rw-r--r--spec/graphql/types/release_links_type_spec.rb9
-rw-r--r--spec/graphql/types/release_source_type_spec.rb4
-rw-r--r--spec/graphql/types/release_type_spec.rb12
-rw-r--r--spec/graphql/types/repository_type_spec.rb2
-rw-r--r--spec/graphql/types/resolvable_interface_spec.rb2
-rw-r--r--spec/graphql/types/root_storage_statistics_type_spec.rb4
-rw-r--r--spec/graphql/types/snippet_type_spec.rb2
-rw-r--r--spec/graphql/types/snippets/blob_type_spec.rb2
-rw-r--r--spec/graphql/types/snippets/blob_viewer_type_spec.rb2
-rw-r--r--spec/graphql/types/snippets/file_input_action_enum_spec.rb2
-rw-r--r--spec/graphql/types/snippets/file_input_type_spec.rb2
-rw-r--r--spec/graphql/types/time_type_spec.rb2
-rw-r--r--spec/graphql/types/todo_type_spec.rb2
-rw-r--r--spec/graphql/types/tree/blob_type_spec.rb4
-rw-r--r--spec/graphql/types/tree/submodule_type_spec.rb2
-rw-r--r--spec/graphql/types/tree/tree_entry_type_spec.rb2
-rw-r--r--spec/graphql/types/tree/tree_type_spec.rb2
-rw-r--r--spec/graphql/types/tree/type_enum_spec.rb2
-rw-r--r--spec/graphql/types/untrusted_regexp_spec.rb52
-rw-r--r--spec/graphql/types/user_type_spec.rb2
-rw-r--r--spec/haml_lint/linter/documentation_links_spec.rb82
-rw-r--r--spec/haml_lint/linter/no_plain_nodes_spec.rb2
-rw-r--r--spec/helpers/access_tokens_helper_spec.rb2
-rw-r--r--spec/helpers/analytics/unique_visits_helper_spec.rb56
-rw-r--r--spec/helpers/appearances_helper_spec.rb2
-rw-r--r--spec/helpers/application_helper_spec.rb12
-rw-r--r--spec/helpers/application_settings_helper_spec.rb2
-rw-r--r--spec/helpers/auth_helper_spec.rb2
-rw-r--r--spec/helpers/auto_devops_helper_spec.rb8
-rw-r--r--spec/helpers/avatars_helper_spec.rb2
-rw-r--r--spec/helpers/award_emoji_helper_spec.rb2
-rw-r--r--spec/helpers/blame_helper_spec.rb2
-rw-r--r--spec/helpers/blob_helper_spec.rb10
-rw-r--r--spec/helpers/boards_helper_spec.rb2
-rw-r--r--spec/helpers/broadcast_messages_helper_spec.rb2
-rw-r--r--spec/helpers/button_helper_spec.rb2
-rw-r--r--spec/helpers/calendar_helper_spec.rb2
-rw-r--r--spec/helpers/ci/builds_helper_spec.rb111
-rw-r--r--spec/helpers/ci/pipeline_schedules_helper_spec.rb24
-rw-r--r--spec/helpers/ci/runners_helper_spec.rb56
-rw-r--r--spec/helpers/ci/status_helper_spec.rb134
-rw-r--r--spec/helpers/ci_status_helper_spec.rb134
-rw-r--r--spec/helpers/clusters_helper_spec.rb26
-rw-r--r--spec/helpers/commits_helper_spec.rb16
-rw-r--r--spec/helpers/components_helper_spec.rb2
-rw-r--r--spec/helpers/container_expiration_policies_helper_spec.rb2
-rw-r--r--spec/helpers/cookies_helper_spec.rb42
-rw-r--r--spec/helpers/dashboard_helper_spec.rb2
-rw-r--r--spec/helpers/defer_script_tag_helper_spec.rb2
-rw-r--r--spec/helpers/diff_helper_spec.rb16
-rw-r--r--spec/helpers/emails_helper_spec.rb2
-rw-r--r--spec/helpers/emoji_helper_spec.rb2
-rw-r--r--spec/helpers/environment_helper_spec.rb2
-rw-r--r--spec/helpers/environments_helper_spec.rb59
-rw-r--r--spec/helpers/events_helper_spec.rb103
-rw-r--r--spec/helpers/explore_helper_spec.rb2
-rw-r--r--spec/helpers/export_helper_spec.rb2
-rw-r--r--spec/helpers/external_link_helper_spec.rb2
-rw-r--r--spec/helpers/form_helper_spec.rb2
-rw-r--r--spec/helpers/git_helper_spec.rb2
-rw-r--r--spec/helpers/gitlab_routing_helper_spec.rb49
-rw-r--r--spec/helpers/graph_helper_spec.rb2
-rw-r--r--spec/helpers/groups/group_members_helper_spec.rb2
-rw-r--r--spec/helpers/groups_helper_spec.rb2
-rw-r--r--spec/helpers/hooks_helper_spec.rb2
-rw-r--r--spec/helpers/icons_helper_spec.rb32
-rw-r--r--spec/helpers/import_helper_spec.rb2
-rw-r--r--spec/helpers/instance_configuration_helper_spec.rb2
-rw-r--r--spec/helpers/issuables_helper_spec.rb26
-rw-r--r--spec/helpers/issues_helper_spec.rb26
-rw-r--r--spec/helpers/labels_helper_spec.rb2
-rw-r--r--spec/helpers/markup_helper_spec.rb2
-rw-r--r--spec/helpers/members_helper_spec.rb2
-rw-r--r--spec/helpers/merge_requests_helper_spec.rb2
-rw-r--r--spec/helpers/namespaces_helper_spec.rb94
-rw-r--r--spec/helpers/nav_helper_spec.rb2
-rw-r--r--spec/helpers/notes_helper_spec.rb2
-rw-r--r--spec/helpers/notifications_helper_spec.rb2
-rw-r--r--spec/helpers/notify_helper_spec.rb33
-rw-r--r--spec/helpers/onboarding_experiment_helper_spec.rb38
-rw-r--r--spec/helpers/operations_helper_spec.rb160
-rw-r--r--spec/helpers/page_layout_helper_spec.rb2
-rw-r--r--spec/helpers/pagination_helper_spec.rb2
-rw-r--r--spec/helpers/preferences_helper_spec.rb9
-rw-r--r--spec/helpers/profiles_helper_spec.rb2
-rw-r--r--spec/helpers/projects/alert_management_helper_spec.rb33
-rw-r--r--spec/helpers/projects/error_tracking_helper_spec.rb2
-rw-r--r--spec/helpers/projects_helper_spec.rb47
-rw-r--r--spec/helpers/recaptcha_experiment_helper_spec.rb2
-rw-r--r--spec/helpers/releases_helper_spec.rb37
-rw-r--r--spec/helpers/rss_helper_spec.rb2
-rw-r--r--spec/helpers/runners_helper_spec.rb56
-rw-r--r--spec/helpers/search_helper_spec.rb95
-rw-r--r--spec/helpers/services_helper_spec.rb27
-rw-r--r--spec/helpers/sessions_helper_spec.rb2
-rw-r--r--spec/helpers/sidekiq_helper_spec.rb2
-rw-r--r--spec/helpers/snippets_helper_spec.rb2
-rw-r--r--spec/helpers/sorting_helper_spec.rb2
-rw-r--r--spec/helpers/sourcegraph_helper_spec.rb2
-rw-r--r--spec/helpers/storage_helper_spec.rb7
-rw-r--r--spec/helpers/submodule_helper_spec.rb2
-rw-r--r--spec/helpers/subscribable_banner_helper_spec.rb2
-rw-r--r--spec/helpers/tab_helper_spec.rb2
-rw-r--r--spec/helpers/time_helper_spec.rb2
-rw-r--r--spec/helpers/timeboxes_helper_spec.rb2
-rw-r--r--spec/helpers/timeboxes_routing_helper_spec.rb2
-rw-r--r--spec/helpers/todos_helper_spec.rb81
-rw-r--r--spec/helpers/tracking_helper_spec.rb2
-rw-r--r--spec/helpers/tree_helper_spec.rb56
-rw-r--r--spec/helpers/user_callouts_helper_spec.rb6
-rw-r--r--spec/helpers/users_helper_spec.rb2
-rw-r--r--spec/helpers/version_check_helper_spec.rb2
-rw-r--r--spec/helpers/visibility_level_helper_spec.rb2
-rw-r--r--spec/helpers/wiki_helper_spec.rb54
-rw-r--r--spec/helpers/x509_helper_spec.rb2
-rw-r--r--spec/initializers/100_patch_omniauth_saml_spec.rb2
-rw-r--r--spec/initializers/6_validations_spec.rb2
-rw-r--r--spec/initializers/action_mailer_hooks_spec.rb2
-rw-r--r--spec/initializers/actionpack_generate_old_csrf_token_spec.rb47
-rw-r--r--spec/initializers/active_record_locking_spec.rb2
-rw-r--r--spec/initializers/asset_proxy_setting_spec.rb2
-rw-r--r--spec/initializers/attr_encrypted_no_db_connection_spec.rb2
-rw-r--r--spec/initializers/attr_encrypted_thread_safe_spec.rb2
-rw-r--r--spec/initializers/database_config_spec.rb2
-rw-r--r--spec/initializers/direct_upload_support_spec.rb2
-rw-r--r--spec/initializers/doorkeeper_spec.rb2
-rw-r--r--spec/initializers/fog_google_https_private_urls_spec.rb2
-rw-r--r--spec/initializers/hangouts_chat_http_override_spec.rb2
-rw-r--r--spec/initializers/lograge_spec.rb32
-rw-r--r--spec/initializers/mail_encoding_patch_spec.rb2
-rw-r--r--spec/initializers/rest-client-hostname_override_spec.rb2
-rw-r--r--spec/initializers/secret_token_spec.rb26
-rw-r--r--spec/initializers/settings_spec.rb2
-rw-r--r--spec/initializers/trusted_proxies_spec.rb2
-rw-r--r--spec/initializers/zz_metrics_spec.rb2
-rw-r--r--spec/javascripts/boards/components/board_spec.js251
-rw-r--r--spec/javascripts/boards/mock_data.js1
-rw-r--r--spec/javascripts/fly_out_nav_browser_spec.js333
-rw-r--r--spec/javascripts/fly_out_nav_spec.js329
-rw-r--r--spec/javascripts/helpers/class_spec_helper.js9
-rw-r--r--spec/javascripts/helpers/filtered_search_spec_helper.js1
-rw-r--r--spec/javascripts/helpers/index.js3
-rw-r--r--spec/javascripts/helpers/init_vue_mr_page_helper.js46
-rw-r--r--spec/javascripts/helpers/locale_helper.js11
-rw-r--r--spec/javascripts/helpers/set_timeout_promise_helper.js4
-rw-r--r--spec/javascripts/helpers/text_helper.js18
-rw-r--r--spec/javascripts/helpers/tracking_helper.js5
-rw-r--r--spec/javascripts/helpers/user_mock_data_helper.js14
-rw-r--r--spec/javascripts/helpers/vue_mount_component_helper.js2
-rw-r--r--spec/javascripts/helpers/vue_test_utils_helper.js5
-rw-r--r--spec/javascripts/helpers/vuex_action_helper.js102
-rw-r--r--spec/javascripts/helpers/wait_for_promises.js1
-rw-r--r--spec/javascripts/jobs/mock_data.js2
-rw-r--r--spec/javascripts/matchers.js55
-rw-r--r--spec/javascripts/monitoring/components/dashboard_resize_browser_spec.js6
-rw-r--r--spec/javascripts/vue_shared/components/tooltip_on_truncate_browser_spec.js240
-rw-r--r--spec/javascripts/vue_shared/components/tooltip_on_truncate_spec.js236
-rw-r--r--spec/lib/after_commit_queue_spec.rb2
-rw-r--r--spec/lib/api/api_spec.rb2
-rw-r--r--spec/lib/api/entities/branch_spec.rb2
-rw-r--r--spec/lib/api/entities/deploy_key_spec.rb2
-rw-r--r--spec/lib/api/entities/deploy_keys_project_spec.rb2
-rw-r--r--spec/lib/api/entities/design_management/design_spec.rb2
-rw-r--r--spec/lib/api/entities/job_request/image_spec.rb2
-rw-r--r--spec/lib/api/entities/job_request/port_spec.rb2
-rw-r--r--spec/lib/api/entities/merge_request_approvals_spec.rb36
-rw-r--r--spec/lib/api/entities/merge_request_basic_spec.rb43
-rw-r--r--spec/lib/api/entities/nuget/dependency_group_spec.rb50
-rw-r--r--spec/lib/api/entities/nuget/dependency_spec.rb28
-rw-r--r--spec/lib/api/entities/nuget/metadatum_spec.rb35
-rw-r--r--spec/lib/api/entities/nuget/package_metadata_catalog_entry_spec.rb43
-rw-r--r--spec/lib/api/entities/nuget/search_result_spec.rb57
-rw-r--r--spec/lib/api/entities/project_import_failed_relation_spec.rb2
-rw-r--r--spec/lib/api/entities/project_import_status_spec.rb2
-rw-r--r--spec/lib/api/entities/project_repository_storage_move_spec.rb2
-rw-r--r--spec/lib/api/entities/release_spec.rb2
-rw-r--r--spec/lib/api/entities/snippet_spec.rb55
-rw-r--r--spec/lib/api/entities/ssh_key_spec.rb2
-rw-r--r--spec/lib/api/entities/user_spec.rb2
-rw-r--r--spec/lib/api/helpers/common_helpers_spec.rb51
-rw-r--r--spec/lib/api/helpers/graphql_helpers_spec.rb2
-rw-r--r--spec/lib/api/helpers/label_helpers_spec.rb2
-rw-r--r--spec/lib/api/helpers/packages/dependency_proxy_helpers_spec.rb72
-rw-r--r--spec/lib/api/helpers/packages_helpers_spec.rb104
-rw-r--r--spec/lib/api/helpers/packages_manager_clients_helpers_spec.rb154
-rw-r--r--spec/lib/api/helpers/pagination_spec.rb2
-rw-r--r--spec/lib/api/helpers/pagination_strategies_spec.rb2
-rw-r--r--spec/lib/api/helpers/related_resources_helpers_spec.rb2
-rw-r--r--spec/lib/api/helpers/version_spec.rb2
-rw-r--r--spec/lib/api/helpers_spec.rb2
-rw-r--r--spec/lib/api/support/git_access_actor_spec.rb2
-rw-r--r--spec/lib/api/validations/validators/absence_spec.rb2
-rw-r--r--spec/lib/api/validations/validators/array_none_any_spec.rb2
-rw-r--r--spec/lib/api/validations/validators/file_path_spec.rb2
-rw-r--r--spec/lib/api/validations/validators/git_ref_spec.rb2
-rw-r--r--spec/lib/api/validations/validators/git_sha_spec.rb2
-rw-r--r--spec/lib/api/validations/validators/integer_none_any_spec.rb2
-rw-r--r--spec/lib/api/validations/validators/limit_spec.rb2
-rw-r--r--spec/lib/api/validations/validators/untrusted_regexp_spec.rb2
-rw-r--r--spec/lib/backup/files_spec.rb2
-rw-r--r--spec/lib/backup/manager_spec.rb2
-rw-r--r--spec/lib/backup/repository_spec.rb2
-rw-r--r--spec/lib/backup/uploads_spec.rb2
-rw-r--r--spec/lib/banzai/color_parser_spec.rb2
-rw-r--r--spec/lib/banzai/commit_renderer_spec.rb2
-rw-r--r--spec/lib/banzai/cross_project_reference_spec.rb2
-rw-r--r--spec/lib/banzai/filter/absolute_link_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/abstract_reference_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/ascii_doc_post_processing_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/asset_proxy_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/audio_link_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/autolink_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/blockquote_fence_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/broadcast_message_placeholders_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/broadcast_message_sanitization_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/color_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/commit_range_reference_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/commit_reference_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/commit_trailers_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/design_reference_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/emoji_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/external_issue_reference_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/external_link_filter_spec.rb4
-rw-r--r--spec/lib/banzai/filter/footnote_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/front_matter_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/gollum_tags_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/html_entity_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/image_lazy_load_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/image_link_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/inline_cluster_metrics_filter_spec.rb25
-rw-r--r--spec/lib/banzai/filter/inline_diff_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/inline_grafana_metrics_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/inline_metrics_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/inline_metrics_redactor_filter_spec.rb22
-rw-r--r--spec/lib/banzai/filter/issuable_state_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/issue_reference_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/jira_import/adf_to_commonmark_filter_spec.rb28
-rw-r--r--spec/lib/banzai/filter/label_reference_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/markdown_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/math_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/merge_request_reference_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/mermaid_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/milestone_reference_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/output_safety_spec.rb2
-rw-r--r--spec/lib/banzai/filter/plantuml_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/project_reference_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/reference_filter_spec.rb247
-rw-r--r--spec/lib/banzai/filter/reference_redactor_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/repository_link_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/sanitization_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/snippet_reference_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/spaced_link_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/suggestion_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/syntax_highlight_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/table_of_contents_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/table_of_contents_tag_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/upload_link_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/user_reference_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/video_link_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/wiki_link_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter_array_spec.rb2
-rw-r--r--spec/lib/banzai/issuable_extractor_spec.rb2
-rw-r--r--spec/lib/banzai/object_renderer_spec.rb2
-rw-r--r--spec/lib/banzai/pipeline/broadcast_message_pipeline_spec.rb2
-rw-r--r--spec/lib/banzai/pipeline/description_pipeline_spec.rb2
-rw-r--r--spec/lib/banzai/pipeline/email_pipeline_spec.rb2
-rw-r--r--spec/lib/banzai/pipeline/emoji_pipeline_spec.rb2
-rw-r--r--spec/lib/banzai/pipeline/full_pipeline_spec.rb2
-rw-r--r--spec/lib/banzai/pipeline/gfm_pipeline_spec.rb53
-rw-r--r--spec/lib/banzai/pipeline/jira_import/adf_commonmark_pipeline_spec.rb15
-rw-r--r--spec/lib/banzai/pipeline/post_process_pipeline_spec.rb2
-rw-r--r--spec/lib/banzai/pipeline/wiki_pipeline_spec.rb2
-rw-r--r--spec/lib/banzai/pipeline_spec.rb2
-rw-r--r--spec/lib/banzai/querying_spec.rb2
-rw-r--r--spec/lib/banzai/reference_parser/base_parser_spec.rb2
-rw-r--r--spec/lib/banzai/reference_parser/commit_parser_spec.rb2
-rw-r--r--spec/lib/banzai/reference_parser/commit_range_parser_spec.rb2
-rw-r--r--spec/lib/banzai/reference_parser/design_parser_spec.rb2
-rw-r--r--spec/lib/banzai/reference_parser/external_issue_parser_spec.rb2
-rw-r--r--spec/lib/banzai/reference_parser/issue_parser_spec.rb2
-rw-r--r--spec/lib/banzai/reference_parser/label_parser_spec.rb2
-rw-r--r--spec/lib/banzai/reference_parser/mentioned_group_parser_spec.rb2
-rw-r--r--spec/lib/banzai/reference_parser/mentioned_project_parser_spec.rb2
-rw-r--r--spec/lib/banzai/reference_parser/mentioned_user_parser_spec.rb2
-rw-r--r--spec/lib/banzai/reference_parser/merge_request_parser_spec.rb2
-rw-r--r--spec/lib/banzai/reference_parser/milestone_parser_spec.rb2
-rw-r--r--spec/lib/banzai/reference_parser/project_parser_spec.rb2
-rw-r--r--spec/lib/banzai/reference_parser/snippet_parser_spec.rb2
-rw-r--r--spec/lib/banzai/reference_parser/user_parser_spec.rb2
-rw-r--r--spec/lib/banzai/reference_redactor_spec.rb2
-rw-r--r--spec/lib/banzai/render_context_spec.rb2
-rw-r--r--spec/lib/banzai/renderer_spec.rb2
-rw-r--r--spec/lib/bitbucket/collection_spec.rb2
-rw-r--r--spec/lib/bitbucket/connection_spec.rb2
-rw-r--r--spec/lib/bitbucket/page_spec.rb2
-rw-r--r--spec/lib/bitbucket/paginator_spec.rb2
-rw-r--r--spec/lib/bitbucket/representation/comment_spec.rb2
-rw-r--r--spec/lib/bitbucket/representation/issue_spec.rb2
-rw-r--r--spec/lib/bitbucket/representation/pull_request_comment_spec.rb2
-rw-r--r--spec/lib/bitbucket/representation/pull_request_spec.rb2
-rw-r--r--spec/lib/bitbucket/representation/repo_spec.rb2
-rw-r--r--spec/lib/bitbucket/representation/user_spec.rb2
-rw-r--r--spec/lib/bitbucket_server/client_spec.rb2
-rw-r--r--spec/lib/bitbucket_server/collection_spec.rb2
-rw-r--r--spec/lib/bitbucket_server/connection_spec.rb2
-rw-r--r--spec/lib/bitbucket_server/page_spec.rb2
-rw-r--r--spec/lib/bitbucket_server/paginator_spec.rb2
-rw-r--r--spec/lib/bitbucket_server/representation/activity_spec.rb2
-rw-r--r--spec/lib/bitbucket_server/representation/comment_spec.rb2
-rw-r--r--spec/lib/bitbucket_server/representation/pull_request_comment_spec.rb2
-rw-r--r--spec/lib/bitbucket_server/representation/pull_request_spec.rb2
-rw-r--r--spec/lib/bitbucket_server/representation/repo_spec.rb2
-rw-r--r--spec/lib/constraints/admin_constrainer_spec.rb2
-rw-r--r--spec/lib/constraints/feature_constrainer_spec.rb2
-rw-r--r--spec/lib/constraints/group_url_constrainer_spec.rb2
-rw-r--r--spec/lib/constraints/project_url_constrainer_spec.rb2
-rw-r--r--spec/lib/constraints/user_url_constrainer_spec.rb2
-rw-r--r--spec/lib/container_registry/blob_spec.rb2
-rw-r--r--spec/lib/container_registry/client_spec.rb2
-rw-r--r--spec/lib/container_registry/path_spec.rb2
-rw-r--r--spec/lib/container_registry/registry_spec.rb2
-rw-r--r--spec/lib/container_registry/tag_spec.rb2
-rw-r--r--spec/lib/csv_builder_spec.rb2
-rw-r--r--spec/lib/declarative_policy/overrides_spec.rb82
-rw-r--r--spec/lib/declarative_policy_spec.rb2
-rw-r--r--spec/lib/event_filter_spec.rb45
-rw-r--r--spec/lib/expand_variables_spec.rb2
-rw-r--r--spec/lib/extracts_path_spec.rb2
-rw-r--r--spec/lib/extracts_ref_spec.rb2
-rw-r--r--spec/lib/feature/definition_spec.rb209
-rw-r--r--spec/lib/feature/gitaly_spec.rb2
-rw-r--r--spec/lib/feature_spec.rb99
-rw-r--r--spec/lib/file_size_validator_spec.rb2
-rw-r--r--spec/lib/forever_spec.rb2
-rw-r--r--spec/lib/gitaly/server_spec.rb2
-rw-r--r--spec/lib/gitlab/access/branch_protection_spec.rb2
-rw-r--r--spec/lib/gitlab/alert_management/alert_params_spec.rb6
-rw-r--r--spec/lib/gitlab/alert_management/alert_status_counts_spec.rb2
-rw-r--r--spec/lib/gitlab/alert_management/fingerprint_spec.rb70
-rw-r--r--spec/lib/gitlab/alerting/alert_spec.rb2
-rw-r--r--spec/lib/gitlab/alerting/notification_payload_parser_spec.rb6
-rw-r--r--spec/lib/gitlab/allowable_spec.rb2
-rw-r--r--spec/lib/gitlab/analytics/cycle_analytics/base_query_builder_spec.rb2
-rw-r--r--spec/lib/gitlab/analytics/cycle_analytics/median_spec.rb2
-rw-r--r--spec/lib/gitlab/analytics/cycle_analytics/records_fetcher_spec.rb2
-rw-r--r--spec/lib/gitlab/analytics/cycle_analytics/stage_events/code_stage_start_spec.rb2
-rw-r--r--spec/lib/gitlab/analytics/cycle_analytics/stage_events/issue_created_spec.rb2
-rw-r--r--spec/lib/gitlab/analytics/cycle_analytics/stage_events/issue_first_mentioned_in_commit_spec.rb2
-rw-r--r--spec/lib/gitlab/analytics/cycle_analytics/stage_events/issue_stage_end_spec.rb2
-rw-r--r--spec/lib/gitlab/analytics/cycle_analytics/stage_events/merge_request_created_spec.rb2
-rw-r--r--spec/lib/gitlab/analytics/cycle_analytics/stage_events/merge_request_first_deployed_to_production_spec.rb2
-rw-r--r--spec/lib/gitlab/analytics/cycle_analytics/stage_events/merge_request_last_build_finished_spec.rb2
-rw-r--r--spec/lib/gitlab/analytics/cycle_analytics/stage_events/merge_request_last_build_started_spec.rb2
-rw-r--r--spec/lib/gitlab/analytics/cycle_analytics/stage_events/merge_request_merged_spec.rb2
-rw-r--r--spec/lib/gitlab/analytics/cycle_analytics/stage_events/plan_stage_start_spec.rb2
-rw-r--r--spec/lib/gitlab/analytics/cycle_analytics/stage_events/stage_event_spec.rb2
-rw-r--r--spec/lib/gitlab/analytics/unique_visits_spec.rb62
-rw-r--r--spec/lib/gitlab/anonymous_session_spec.rb2
-rw-r--r--spec/lib/gitlab/app_json_logger_spec.rb2
-rw-r--r--spec/lib/gitlab/app_logger_spec.rb2
-rw-r--r--spec/lib/gitlab/app_text_logger_spec.rb2
-rw-r--r--spec/lib/gitlab/application_context_spec.rb2
-rw-r--r--spec/lib/gitlab/application_rate_limiter_spec.rb2
-rw-r--r--spec/lib/gitlab/asciidoc/include_processor_spec.rb2
-rw-r--r--spec/lib/gitlab/asciidoc_spec.rb2
-rw-r--r--spec/lib/gitlab/asset_proxy_spec.rb2
-rw-r--r--spec/lib/gitlab/auth/activity_spec.rb2
-rw-r--r--spec/lib/gitlab/auth/auth_finders_spec.rb75
-rw-r--r--spec/lib/gitlab/auth/blocked_user_tracker_spec.rb2
-rw-r--r--spec/lib/gitlab/auth/current_user_mode_spec.rb2
-rw-r--r--spec/lib/gitlab/auth/ip_rate_limiter_spec.rb2
-rw-r--r--spec/lib/gitlab/auth/key_status_checker_spec.rb2
-rw-r--r--spec/lib/gitlab/auth/ldap/access_spec.rb2
-rw-r--r--spec/lib/gitlab/auth/ldap/adapter_spec.rb2
-rw-r--r--spec/lib/gitlab/auth/ldap/auth_hash_spec.rb2
-rw-r--r--spec/lib/gitlab/auth/ldap/authentication_spec.rb2
-rw-r--r--spec/lib/gitlab/auth/ldap/config_spec.rb2
-rw-r--r--spec/lib/gitlab/auth/ldap/dn_spec.rb2
-rw-r--r--spec/lib/gitlab/auth/ldap/person_spec.rb2
-rw-r--r--spec/lib/gitlab/auth/ldap/user_spec.rb2
-rw-r--r--spec/lib/gitlab/auth/o_auth/auth_hash_spec.rb2
-rw-r--r--spec/lib/gitlab/auth/o_auth/identity_linker_spec.rb2
-rw-r--r--spec/lib/gitlab/auth/o_auth/provider_spec.rb2
-rw-r--r--spec/lib/gitlab/auth/o_auth/user_spec.rb2
-rw-r--r--spec/lib/gitlab/auth/request_authenticator_spec.rb2
-rw-r--r--spec/lib/gitlab/auth/saml/auth_hash_spec.rb2
-rw-r--r--spec/lib/gitlab/auth/saml/identity_linker_spec.rb2
-rw-r--r--spec/lib/gitlab/auth/saml/origin_validator_spec.rb2
-rw-r--r--spec/lib/gitlab/auth/saml/user_spec.rb2
-rw-r--r--spec/lib/gitlab/auth/unique_ips_limiter_spec.rb2
-rw-r--r--spec/lib/gitlab/auth/user_access_denied_reason_spec.rb2
-rw-r--r--spec/lib/gitlab/auth_spec.rb4
-rw-r--r--spec/lib/gitlab/authorized_keys_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/add_merge_request_diff_commits_count_spec.rb6
-rw-r--r--spec/lib/gitlab/background_migration/archive_legacy_traces_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/backfill_deployment_clusters_from_deployments_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/backfill_environment_id_deployment_merge_requests_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/backfill_hashed_project_repositories_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/backfill_legacy_project_repositories_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/backfill_namespace_settings_spec.rb23
-rw-r--r--spec/lib/gitlab/background_migration/backfill_project_fullpath_in_repo_config_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/backfill_project_repositories_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/backfill_project_settings_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/backfill_push_rules_id_in_projects_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/backfill_snippet_repositories_spec.rb18
-rw-r--r--spec/lib/gitlab/background_migration/cleanup_concurrent_schema_change_spec.rb28
-rw-r--r--spec/lib/gitlab/background_migration/digest_column_spec.rb46
-rw-r--r--spec/lib/gitlab/background_migration/encrypt_columns_spec.rb96
-rw-r--r--spec/lib/gitlab/background_migration/encrypt_runners_tokens_spec.rb79
-rw-r--r--spec/lib/gitlab/background_migration/fix_cross_project_label_links_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/fix_projects_without_project_feature_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/fix_projects_without_prometheus_service_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/fix_promoted_epics_discussion_ids_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/fix_user_namespace_names_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/fix_user_project_route_names_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/legacy_upload_mover_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/legacy_uploads_migrator_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/link_lfs_objects_projects_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/mailers/unconfirm_mailer_spec.rb12
-rw-r--r--spec/lib/gitlab/background_migration/merge_request_assignees_migration_progress_check_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/migrate_build_stage_spec.rb8
-rw-r--r--spec/lib/gitlab/background_migration/migrate_fingerprint_sha256_within_keys_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/migrate_issue_trackers_sensitive_data_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/migrate_legacy_artifacts_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/migrate_null_private_profile_to_false_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/migrate_pages_metadata_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/migrate_stage_index_spec.rb32
-rw-r--r--spec/lib/gitlab/background_migration/migrate_users_bio_to_user_details_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/populate_canonical_emails_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/populate_cluster_kubernetes_namespace_table_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/populate_merge_request_assignees_table_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/populate_project_snippet_statistics_spec.rb224
-rw-r--r--spec/lib/gitlab/background_migration/populate_untracked_uploads_dependencies/untracked_file_spec.rb3
-rw-r--r--spec/lib/gitlab/background_migration/populate_untracked_uploads_spec.rb3
-rw-r--r--spec/lib/gitlab/background_migration/populate_user_highest_roles_table_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/prepare_untracked_uploads_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/recalculate_project_authorizations_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/recalculate_project_authorizations_with_min_max_user_id_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/remove_restricted_todos_spec.rb14
-rw-r--r--spec/lib/gitlab/background_migration/reset_merge_status_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/schedule_calculate_wiki_sizes_spec.rb64
-rw-r--r--spec/lib/gitlab/background_migration/set_confidential_note_events_on_services_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/set_confidential_note_events_on_webhooks_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/update_existing_subgroup_to_match_visibility_level_of_parent_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/user_mentions/create_resource_user_mention_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/wrongfully_confirmed_email_unconfirmer_spec.rb123
-rw-r--r--spec/lib/gitlab/background_migration_spec.rb21
-rw-r--r--spec/lib/gitlab/backtrace_cleaner_spec.rb2
-rw-r--r--spec/lib/gitlab/badge/coverage/metadata_spec.rb2
-rw-r--r--spec/lib/gitlab/badge/coverage/report_spec.rb2
-rw-r--r--spec/lib/gitlab/badge/coverage/template_spec.rb2
-rw-r--r--spec/lib/gitlab/badge/pipeline/metadata_spec.rb2
-rw-r--r--spec/lib/gitlab/badge/pipeline/status_spec.rb2
-rw-r--r--spec/lib/gitlab/badge/pipeline/template_spec.rb2
-rw-r--r--spec/lib/gitlab/badge/shared/metadata.rb2
-rw-r--r--spec/lib/gitlab/bare_repository_import/importer_spec.rb2
-rw-r--r--spec/lib/gitlab/bare_repository_import/repository_spec.rb2
-rw-r--r--spec/lib/gitlab/batch_pop_queueing_spec.rb2
-rw-r--r--spec/lib/gitlab/batch_worker_context_spec.rb2
-rw-r--r--spec/lib/gitlab/bitbucket_import/importer_spec.rb26
-rw-r--r--spec/lib/gitlab/bitbucket_import/project_creator_spec.rb2
-rw-r--r--spec/lib/gitlab/bitbucket_import/wiki_formatter_spec.rb2
-rw-r--r--spec/lib/gitlab/bitbucket_server_import/importer_spec.rb51
-rw-r--r--spec/lib/gitlab/blame_spec.rb2
-rw-r--r--spec/lib/gitlab/blob_helper_spec.rb2
-rw-r--r--spec/lib/gitlab/branch_push_merge_commit_analyzer_spec.rb2
-rw-r--r--spec/lib/gitlab/build_access_spec.rb2
-rw-r--r--spec/lib/gitlab/cache/ci/project_pipeline_status_spec.rb60
-rw-r--r--spec/lib/gitlab/cache/import/caching_spec.rb2
-rw-r--r--spec/lib/gitlab/cache/request_cache_spec.rb2
-rw-r--r--spec/lib/gitlab/changes_list_spec.rb2
-rw-r--r--spec/lib/gitlab/chat/command_spec.rb2
-rw-r--r--spec/lib/gitlab/chat/output_spec.rb2
-rw-r--r--spec/lib/gitlab/chat/responder/base_spec.rb2
-rw-r--r--spec/lib/gitlab/chat/responder/mattermost_spec.rb2
-rw-r--r--spec/lib/gitlab/chat/responder/slack_spec.rb2
-rw-r--r--spec/lib/gitlab/chat/responder_spec.rb2
-rw-r--r--spec/lib/gitlab/chat_name_token_spec.rb2
-rw-r--r--spec/lib/gitlab/chat_spec.rb2
-rw-r--r--spec/lib/gitlab/checks/branch_check_spec.rb2
-rw-r--r--spec/lib/gitlab/checks/change_access_spec.rb2
-rw-r--r--spec/lib/gitlab/checks/diff_check_spec.rb2
-rw-r--r--spec/lib/gitlab/checks/force_push_spec.rb2
-rw-r--r--spec/lib/gitlab/checks/lfs_check_spec.rb2
-rw-r--r--spec/lib/gitlab/checks/lfs_integrity_spec.rb2
-rw-r--r--spec/lib/gitlab/checks/project_created_spec.rb2
-rw-r--r--spec/lib/gitlab/checks/project_moved_spec.rb2
-rw-r--r--spec/lib/gitlab/checks/push_check_spec.rb2
-rw-r--r--spec/lib/gitlab/checks/push_file_count_check_spec.rb2
-rw-r--r--spec/lib/gitlab/checks/snippet_check_spec.rb2
-rw-r--r--spec/lib/gitlab/checks/tag_check_spec.rb2
-rw-r--r--spec/lib/gitlab/checks/timed_logger_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/ansi2html_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/ansi2json/line_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/ansi2json/parser_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/ansi2json/result_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/ansi2json/style_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/ansi2json_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/artifact_file_reader_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/build/artifacts/adapters/gzip_stream_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/build/artifacts/adapters/raw_stream_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/build/artifacts/metadata/entry_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/build/artifacts/metadata_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/build/artifacts/path_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/build/context/build_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/build/context/global_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/build/credentials/factory_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/build/credentials/registry_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/build/image_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/build/policy/changes_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/build/policy/kubernetes_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/build/policy/refs_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/build/policy/variables_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/build/policy_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/build/port_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/build/prerequisite/factory_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/build/prerequisite/kubernetes_namespace_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/build/releaser_spec.rb20
-rw-r--r--spec/lib/gitlab/ci/build/rules/rule/clause/changes_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/build/rules/rule/clause/exists_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/build/rules/rule_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/build/rules_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/build/step_spec.rb4
-rw-r--r--spec/lib/gitlab/ci/charts_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/edge_stages_injector_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/entry/artifacts_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/entry/bridge_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/entry/cache_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/entry/commands_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/entry/coverage_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/entry/default_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/entry/environment_spec.rb15
-rw-r--r--spec/lib/gitlab/ci/config/entry/files_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/entry/hidden_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/entry/image_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/entry/include_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/entry/inherit/default_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/entry/inherit/variables_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/entry/job_spec.rb4
-rw-r--r--spec/lib/gitlab/ci/config/entry/jobs_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/entry/key_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/entry/kubernetes_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/entry/need_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/entry/needs_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/entry/paths_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/entry/policy_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/entry/port_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/entry/ports_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/entry/prefix_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/entry/processable_spec.rb14
-rw-r--r--spec/lib/gitlab/ci/config/entry/release/assets/link_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/entry/release/assets/links_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/entry/release/assets_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/entry/release_spec.rb215
-rw-r--r--spec/lib/gitlab/ci/config/entry/reports_spec.rb5
-rw-r--r--spec/lib/gitlab/ci/config/entry/retry_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/entry/root_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/entry/rules/rule_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/entry/rules_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/entry/script_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/entry/service_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/entry/services_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/entry/stage_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/entry/stages_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/entry/trigger_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/entry/variables_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/entry/workflow_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/extendable/entry_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/extendable_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/external/context_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/external/file/artifact_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/external/file/base_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/external/file/local_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/external/file/project_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/external/file/remote_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/external/file/template_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/external/mapper_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/external/processor_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/normalizer_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/cron_parser_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/jwt_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/mask_secret_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/parsers/accessibility/pa11y_spec.rb6
-rw-r--r--spec/lib/gitlab/ci/parsers/coverage/cobertura_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/parsers/terraform/tfplan_spec.rb85
-rw-r--r--spec/lib/gitlab/ci/parsers/test/junit_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/parsers_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/pipeline/chain/build/associations_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/pipeline/chain/build_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/pipeline/chain/command_spec.rb27
-rw-r--r--spec/lib/gitlab/ci/pipeline/chain/config/content_spec.rb26
-rw-r--r--spec/lib/gitlab/ci/pipeline/chain/create_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/pipeline/chain/evaluate_workflow_rules_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/pipeline/chain/populate_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/pipeline/chain/remove_unwanted_chat_jobs_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/pipeline/chain/seed_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/pipeline/chain/sequence_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/pipeline/chain/skip_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/pipeline/chain/validate/abilities_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/pipeline/chain/validate/external_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/pipeline/chain/validate/repository_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/pipeline/duration_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/pipeline/expression/lexeme/and_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/pipeline/expression/lexeme/equals_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/pipeline/expression/lexeme/matches_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/pipeline/expression/lexeme/not_equals_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/pipeline/expression/lexeme/not_matches_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/pipeline/expression/lexeme/null_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/pipeline/expression/lexeme/or_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/pipeline/expression/lexeme/pattern_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/pipeline/expression/lexeme/string_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/pipeline/expression/lexeme/variable_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/pipeline/expression/lexer_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/pipeline/expression/parser_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/pipeline/expression/statement_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/pipeline/expression/token_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/pipeline/preloader_spec.rb6
-rw-r--r--spec/lib/gitlab/ci/pipeline/seed/build/cache_spec.rb4
-rw-r--r--spec/lib/gitlab/ci/pipeline/seed/build/resource_group_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/pipeline/seed/build_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/pipeline/seed/deployment_spec.rb15
-rw-r--r--spec/lib/gitlab/ci/pipeline/seed/environment_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/pipeline/seed/stage_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/reports/accessibility_reports_comparer_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/reports/accessibility_reports_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/reports/coverage_reports_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/reports/terraform_reports_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/reports/test_case_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/reports/test_report_summary_spec.rb90
-rw-r--r--spec/lib/gitlab/ci/reports/test_reports_comparer_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/reports/test_reports_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/reports/test_suite_comparer_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/reports/test_suite_spec.rb37
-rw-r--r--spec/lib/gitlab/ci/reports/test_suite_summary_spec.rb89
-rw-r--r--spec/lib/gitlab/ci/status/bridge/factory_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/build/action_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/build/cancelable_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/build/canceled_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/build/common_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/build/created_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/build/erased_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/build/factory_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/build/failed_allowed_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/build/failed_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/build/manual_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/build/pending_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/build/play_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/build/preparing_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/build/retried_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/build/retryable_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/build/scheduled_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/build/skipped_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/build/stop_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/build/unschedule_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/canceled_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/composite_spec.rb10
-rw-r--r--spec/lib/gitlab/ci/status/created_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/extended_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/external/common_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/external/factory_spec.rb4
-rw-r--r--spec/lib/gitlab/ci/status/factory_spec.rb4
-rw-r--r--spec/lib/gitlab/ci/status/failed_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/group/common_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/group/factory_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/manual_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/pending_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/pipeline/blocked_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/pipeline/common_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/pipeline/delayed_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/pipeline/factory_spec.rb4
-rw-r--r--spec/lib/gitlab/ci/status/preparing_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/running_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/scheduled_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/skipped_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/stage/common_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/stage/factory_spec.rb6
-rw-r--r--spec/lib/gitlab/ci/status/stage/play_manual_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/success_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/success_warning_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/waiting_for_resource_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/templates/Jobs/browser_performance_testing_gitlab_ci_yaml_spec.rb85
-rw-r--r--spec/lib/gitlab/ci/templates/Jobs/build_gitlab_ci_yaml_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/templates/Jobs/code_quality_gitlab_ci_yaml_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/templates/Jobs/deploy_gitlab_ci_yaml_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/templates/Jobs/test_gitlab_ci_yaml_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/templates/Verify/load_performance_testing_gitlab_ci_yaml_spec.rb75
-rw-r--r--spec/lib/gitlab/ci/templates/auto_devops_gitlab_ci_yaml_spec.rb34
-rw-r--r--spec/lib/gitlab/ci/templates/managed_cluster_applications_gitlab_ci_yaml_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/templates/templates_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/trace/chunked_io_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/trace/section_parser_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/trace/stream_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/trace_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/variables/collection/item_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/variables/collection_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/yaml_processor_spec.rb164
-rw-r--r--spec/lib/gitlab/ci_access_spec.rb2
-rw-r--r--spec/lib/gitlab/class_attributes_spec.rb41
-rw-r--r--spec/lib/gitlab/cleanup/orphan_job_artifact_files_batch_spec.rb2
-rw-r--r--spec/lib/gitlab/cleanup/orphan_job_artifact_files_spec.rb2
-rw-r--r--spec/lib/gitlab/cleanup/orphan_lfs_file_references_spec.rb2
-rw-r--r--spec/lib/gitlab/cleanup/project_uploads_spec.rb2
-rw-r--r--spec/lib/gitlab/cleanup/remote_uploads_spec.rb2
-rw-r--r--spec/lib/gitlab/closing_issue_extractor_spec.rb2
-rw-r--r--spec/lib/gitlab/cluster/mixins/puma_cluster_spec.rb2
-rw-r--r--spec/lib/gitlab/cluster/mixins/unicorn_http_server_spec.rb2
-rw-r--r--spec/lib/gitlab/cluster/puma_worker_killer_observer_spec.rb2
-rw-r--r--spec/lib/gitlab/cluster/rack_timeout_observer_spec.rb2
-rw-r--r--spec/lib/gitlab/code_navigation_path_spec.rb4
-rw-r--r--spec/lib/gitlab/color_schemes_spec.rb2
-rw-r--r--spec/lib/gitlab/conan_token_spec.rb97
-rw-r--r--spec/lib/gitlab/config/entry/attributable_spec.rb2
-rw-r--r--spec/lib/gitlab/config/entry/boolean_spec.rb2
-rw-r--r--spec/lib/gitlab/config/entry/configurable_spec.rb2
-rw-r--r--spec/lib/gitlab/config/entry/factory_spec.rb2
-rw-r--r--spec/lib/gitlab/config/entry/simplifiable_spec.rb2
-rw-r--r--spec/lib/gitlab/config/entry/undefined_spec.rb2
-rw-r--r--spec/lib/gitlab/config/entry/unspecified_spec.rb2
-rw-r--r--spec/lib/gitlab/config/entry/validatable_spec.rb2
-rw-r--r--spec/lib/gitlab/config/entry/validator_spec.rb2
-rw-r--r--spec/lib/gitlab/config/loader/yaml_spec.rb2
-rw-r--r--spec/lib/gitlab/config_checker/external_database_checker_spec.rb55
-rw-r--r--spec/lib/gitlab/config_checker/puma_rugged_checker_spec.rb2
-rw-r--r--spec/lib/gitlab/conflict/file_collection_spec.rb2
-rw-r--r--spec/lib/gitlab/conflict/file_spec.rb2
-rw-r--r--spec/lib/gitlab/content_security_policy/config_loader_spec.rb2
-rw-r--r--spec/lib/gitlab/contributions_calendar_spec.rb2
-rw-r--r--spec/lib/gitlab/cross_project_access/check_collection_spec.rb2
-rw-r--r--spec/lib/gitlab/cross_project_access/check_info_spec.rb2
-rw-r--r--spec/lib/gitlab/cross_project_access/class_methods_spec.rb2
-rw-r--r--spec/lib/gitlab/cross_project_access_spec.rb2
-rw-r--r--spec/lib/gitlab/crypto_helper_spec.rb2
-rw-r--r--spec/lib/gitlab/current_settings_spec.rb2
-rw-r--r--spec/lib/gitlab/cycle_analytics/base_event_fetcher_spec.rb2
-rw-r--r--spec/lib/gitlab/cycle_analytics/code_event_fetcher_spec.rb2
-rw-r--r--spec/lib/gitlab/cycle_analytics/code_stage_spec.rb2
-rw-r--r--spec/lib/gitlab/cycle_analytics/events_spec.rb2
-rw-r--r--spec/lib/gitlab/cycle_analytics/issue_event_fetcher_spec.rb2
-rw-r--r--spec/lib/gitlab/cycle_analytics/issue_stage_spec.rb2
-rw-r--r--spec/lib/gitlab/cycle_analytics/permissions_spec.rb2
-rw-r--r--spec/lib/gitlab/cycle_analytics/plan_event_fetcher_spec.rb2
-rw-r--r--spec/lib/gitlab/cycle_analytics/plan_stage_spec.rb2
-rw-r--r--spec/lib/gitlab/cycle_analytics/production_event_fetcher_spec.rb2
-rw-r--r--spec/lib/gitlab/cycle_analytics/production_stage_spec.rb2
-rw-r--r--spec/lib/gitlab/cycle_analytics/review_event_fetcher_spec.rb2
-rw-r--r--spec/lib/gitlab/cycle_analytics/review_stage_spec.rb2
-rw-r--r--spec/lib/gitlab/cycle_analytics/stage_summary_spec.rb2
-rw-r--r--spec/lib/gitlab/cycle_analytics/staging_event_fetcher_spec.rb2
-rw-r--r--spec/lib/gitlab/cycle_analytics/staging_stage_spec.rb2
-rw-r--r--spec/lib/gitlab/cycle_analytics/summary/value_spec.rb2
-rw-r--r--spec/lib/gitlab/cycle_analytics/test_event_fetcher_spec.rb2
-rw-r--r--spec/lib/gitlab/cycle_analytics/test_stage_spec.rb2
-rw-r--r--spec/lib/gitlab/cycle_analytics/updater_spec.rb2
-rw-r--r--spec/lib/gitlab/cycle_analytics/usage_data_spec.rb2
-rw-r--r--spec/lib/gitlab/daemon_spec.rb2
-rw-r--r--spec/lib/gitlab/danger/changelog_spec.rb42
-rw-r--r--spec/lib/gitlab/danger/commit_linter_spec.rb14
-rw-r--r--spec/lib/gitlab/danger/emoji_checker_spec.rb2
-rw-r--r--spec/lib/gitlab/danger/helper_spec.rb328
-rw-r--r--spec/lib/gitlab/danger/roulette_spec.rb265
-rw-r--r--spec/lib/gitlab/danger/sidekiq_queues_spec.rb82
-rw-r--r--spec/lib/gitlab/danger/teammate_spec.rb125
-rw-r--r--spec/lib/gitlab/data_builder/alert_spec.rb2
-rw-r--r--spec/lib/gitlab/data_builder/build_spec.rb2
-rw-r--r--spec/lib/gitlab/data_builder/deployment_spec.rb2
-rw-r--r--spec/lib/gitlab/data_builder/note_spec.rb2
-rw-r--r--spec/lib/gitlab/data_builder/pipeline_spec.rb2
-rw-r--r--spec/lib/gitlab/data_builder/push_spec.rb2
-rw-r--r--spec/lib/gitlab/data_builder/wiki_page_spec.rb2
-rw-r--r--spec/lib/gitlab/database/background_migration_job_spec.rb125
-rw-r--r--spec/lib/gitlab/database/batch_count_spec.rb2
-rw-r--r--spec/lib/gitlab/database/connection_timer_spec.rb2
-rw-r--r--spec/lib/gitlab/database/count/exact_count_strategy_spec.rb2
-rw-r--r--spec/lib/gitlab/database/count/reltuples_count_strategy_spec.rb2
-rw-r--r--spec/lib/gitlab/database/count/tablesample_count_strategy_spec.rb2
-rw-r--r--spec/lib/gitlab/database/count_spec.rb2
-rw-r--r--spec/lib/gitlab/database/custom_structure_spec.rb2
-rw-r--r--spec/lib/gitlab/database/dynamic_model_helpers_spec.rb28
-rw-r--r--spec/lib/gitlab/database/grant_spec.rb2
-rw-r--r--spec/lib/gitlab/database/migration_helpers_spec.rb288
-rw-r--r--spec/lib/gitlab/database/migrations/background_migration_helpers_spec.rb276
-rw-r--r--spec/lib/gitlab/database/multi_threaded_migration_spec.rb2
-rw-r--r--spec/lib/gitlab/database/obsolete_ignored_columns_spec.rb2
-rw-r--r--spec/lib/gitlab/database/partitioning/monthly_strategy_spec.rb153
-rw-r--r--spec/lib/gitlab/database/partitioning/partition_creator_spec.rb96
-rw-r--r--spec/lib/gitlab/database/partitioning/time_partition_spec.rb174
-rw-r--r--spec/lib/gitlab/database/partitioning_migration_helpers/backfill_partitioned_table_spec.rb175
-rw-r--r--spec/lib/gitlab/database/partitioning_migration_helpers/foreign_key_helpers_spec.rb2
-rw-r--r--spec/lib/gitlab/database/partitioning_migration_helpers/partitioned_foreign_key_spec.rb2
-rw-r--r--spec/lib/gitlab/database/partitioning_migration_helpers/table_management_helpers_spec.rb259
-rw-r--r--spec/lib/gitlab/database/postgresql_adapter/force_disconnectable_mixin_spec.rb2
-rw-r--r--spec/lib/gitlab/database/postgresql_adapter/schema_versions_copy_mixin_spec.rb2
-rw-r--r--spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base_spec.rb2
-rw-r--r--spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_namespaces_spec.rb2
-rw-r--r--spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_projects_spec.rb2
-rw-r--r--spec/lib/gitlab/database/rename_reserved_paths_migration/v1_spec.rb4
-rw-r--r--spec/lib/gitlab/database/schema_cleaner_spec.rb2
-rw-r--r--spec/lib/gitlab/database/sha_attribute_spec.rb2
-rw-r--r--spec/lib/gitlab/database/with_lock_retries_spec.rb2
-rw-r--r--spec/lib/gitlab/database_importers/common_metrics/importer_spec.rb2
-rw-r--r--spec/lib/gitlab/database_importers/common_metrics/prometheus_metric_spec.rb6
-rw-r--r--spec/lib/gitlab/database_importers/instance_administrators/create_group_spec.rb2
-rw-r--r--spec/lib/gitlab/database_importers/self_monitoring/project/create_service_spec.rb2
-rw-r--r--spec/lib/gitlab/database_importers/self_monitoring/project/delete_service_spec.rb2
-rw-r--r--spec/lib/gitlab/database_spec.rb113
-rw-r--r--spec/lib/gitlab/dependency_linker/base_linker_spec.rb2
-rw-r--r--spec/lib/gitlab/dependency_linker/cargo_toml_linker_spec.rb2
-rw-r--r--spec/lib/gitlab/dependency_linker/cartfile_linker_spec.rb2
-rw-r--r--spec/lib/gitlab/dependency_linker/composer_json_linker_spec.rb2
-rw-r--r--spec/lib/gitlab/dependency_linker/gemfile_linker_spec.rb2
-rw-r--r--spec/lib/gitlab/dependency_linker/gemspec_linker_spec.rb2
-rw-r--r--spec/lib/gitlab/dependency_linker/go_mod_linker_spec.rb2
-rw-r--r--spec/lib/gitlab/dependency_linker/go_sum_linker_spec.rb2
-rw-r--r--spec/lib/gitlab/dependency_linker/godeps_json_linker_spec.rb2
-rw-r--r--spec/lib/gitlab/dependency_linker/package_json_linker_spec.rb2
-rw-r--r--spec/lib/gitlab/dependency_linker/parser/gemfile_spec.rb2
-rw-r--r--spec/lib/gitlab/dependency_linker/podfile_linker_spec.rb2
-rw-r--r--spec/lib/gitlab/dependency_linker/podspec_json_linker_spec.rb2
-rw-r--r--spec/lib/gitlab/dependency_linker/podspec_linker_spec.rb2
-rw-r--r--spec/lib/gitlab/dependency_linker/requirements_txt_linker_spec.rb2
-rw-r--r--spec/lib/gitlab/dependency_linker_spec.rb2
-rw-r--r--spec/lib/gitlab/devise_failure_spec.rb2
-rw-r--r--spec/lib/gitlab/diff/diff_refs_spec.rb2
-rw-r--r--spec/lib/gitlab/diff/file_collection/commit_spec.rb2
-rw-r--r--spec/lib/gitlab/diff/file_collection/compare_spec.rb2
-rw-r--r--spec/lib/gitlab/diff/file_collection/merge_request_diff_batch_spec.rb2
-rw-r--r--spec/lib/gitlab/diff/file_collection/merge_request_diff_spec.rb2
-rw-r--r--spec/lib/gitlab/diff/file_spec.rb2
-rw-r--r--spec/lib/gitlab/diff/formatters/image_formatter_spec.rb2
-rw-r--r--spec/lib/gitlab/diff/formatters/text_formatter_spec.rb2
-rw-r--r--spec/lib/gitlab/diff/highlight_cache_spec.rb2
-rw-r--r--spec/lib/gitlab/diff/highlight_spec.rb2
-rw-r--r--spec/lib/gitlab/diff/inline_diff_markdown_marker_spec.rb2
-rw-r--r--spec/lib/gitlab/diff/inline_diff_marker_spec.rb2
-rw-r--r--spec/lib/gitlab/diff/inline_diff_spec.rb2
-rw-r--r--spec/lib/gitlab/diff/line_mapper_spec.rb2
-rw-r--r--spec/lib/gitlab/diff/line_spec.rb2
-rw-r--r--spec/lib/gitlab/diff/lines_unfolder_spec.rb2
-rw-r--r--spec/lib/gitlab/diff/parallel_diff_spec.rb2
-rw-r--r--spec/lib/gitlab/diff/parser_spec.rb2
-rw-r--r--spec/lib/gitlab/diff/position_collection_spec.rb2
-rw-r--r--spec/lib/gitlab/diff/position_spec.rb2
-rw-r--r--spec/lib/gitlab/diff/position_tracer/image_strategy_spec.rb115
-rw-r--r--spec/lib/gitlab/diff/position_tracer/line_strategy_spec.rb140
-rw-r--r--spec/lib/gitlab/diff/position_tracer_spec.rb2
-rw-r--r--spec/lib/gitlab/diff/stats_cache_spec.rb84
-rw-r--r--spec/lib/gitlab/diff/suggestion_diff_spec.rb2
-rw-r--r--spec/lib/gitlab/diff/suggestion_spec.rb2
-rw-r--r--spec/lib/gitlab/diff/suggestions_parser_spec.rb2
-rw-r--r--spec/lib/gitlab/discussions_diff/file_collection_spec.rb2
-rw-r--r--spec/lib/gitlab/discussions_diff/highlight_cache_spec.rb2
-rw-r--r--spec/lib/gitlab/doctor/secrets_spec.rb2
-rw-r--r--spec/lib/gitlab/downtime_check/message_spec.rb2
-rw-r--r--spec/lib/gitlab/downtime_check_spec.rb2
-rw-r--r--spec/lib/gitlab/elasticsearch/logs/lines_spec.rb2
-rw-r--r--spec/lib/gitlab/elasticsearch/logs/pods_spec.rb2
-rw-r--r--spec/lib/gitlab/email/attachment_uploader_spec.rb2
-rw-r--r--spec/lib/gitlab/email/handler/create_issue_handler_spec.rb2
-rw-r--r--spec/lib/gitlab/email/handler/create_merge_request_handler_spec.rb2
-rw-r--r--spec/lib/gitlab/email/handler/create_note_handler_spec.rb68
-rw-r--r--spec/lib/gitlab/email/handler/service_desk_handler_spec.rb311
-rw-r--r--spec/lib/gitlab/email/handler/unsubscribe_handler_spec.rb2
-rw-r--r--spec/lib/gitlab/email/handler_spec.rb44
-rw-r--r--spec/lib/gitlab/email/hook/additional_headers_interceptor_spec.rb2
-rw-r--r--spec/lib/gitlab/email/hook/delivery_metrics_observer_spec.rb2
-rw-r--r--spec/lib/gitlab/email/hook/disable_email_interceptor_spec.rb2
-rw-r--r--spec/lib/gitlab/email/hook/smime_signature_interceptor_spec.rb2
-rw-r--r--spec/lib/gitlab/email/message/repository_push_spec.rb2
-rw-r--r--spec/lib/gitlab/email/receiver_spec.rb2
-rw-r--r--spec/lib/gitlab/email/reply_parser_spec.rb2
-rw-r--r--spec/lib/gitlab/email/service_desk_receiver_spec.rb37
-rw-r--r--spec/lib/gitlab/email/smime/certificate_spec.rb2
-rw-r--r--spec/lib/gitlab/email/smime/signer_spec.rb2
-rw-r--r--spec/lib/gitlab/emoji_spec.rb125
-rw-r--r--spec/lib/gitlab/encoding_helper_spec.rb2
-rw-r--r--spec/lib/gitlab/error_tracking/stack_trace_highlight_decorator_spec.rb2
-rw-r--r--spec/lib/gitlab/error_tracking_spec.rb86
-rw-r--r--spec/lib/gitlab/etag_caching/middleware_spec.rb2
-rw-r--r--spec/lib/gitlab/etag_caching/router_spec.rb2
-rw-r--r--spec/lib/gitlab/exclusive_lease_helpers/sleeping_lock_spec.rb2
-rw-r--r--spec/lib/gitlab/exclusive_lease_helpers_spec.rb2
-rw-r--r--spec/lib/gitlab/exclusive_lease_spec.rb2
-rw-r--r--spec/lib/gitlab/experimentation_spec.rb2
-rw-r--r--spec/lib/gitlab/external_authorization/access_spec.rb2
-rw-r--r--spec/lib/gitlab/external_authorization/cache_spec.rb2
-rw-r--r--spec/lib/gitlab/external_authorization/client_spec.rb2
-rw-r--r--spec/lib/gitlab/external_authorization/logger_spec.rb2
-rw-r--r--spec/lib/gitlab/external_authorization/response_spec.rb2
-rw-r--r--spec/lib/gitlab/external_authorization_spec.rb2
-rw-r--r--spec/lib/gitlab/fake_application_settings_spec.rb2
-rw-r--r--spec/lib/gitlab/file_detector_spec.rb2
-rw-r--r--spec/lib/gitlab/file_finder_spec.rb2
-rw-r--r--spec/lib/gitlab/file_hook_spec.rb2
-rw-r--r--spec/lib/gitlab/file_markdown_link_builder_spec.rb2
-rw-r--r--spec/lib/gitlab/file_type_detection_spec.rb2
-rw-r--r--spec/lib/gitlab/fogbugz_import/client_spec.rb2
-rw-r--r--spec/lib/gitlab/fogbugz_import/importer_spec.rb2
-rw-r--r--spec/lib/gitlab/fogbugz_import/project_creator_spec.rb2
-rw-r--r--spec/lib/gitlab/gfm/reference_rewriter_spec.rb2
-rw-r--r--spec/lib/gitlab/gfm/uploads_rewriter_spec.rb2
-rw-r--r--spec/lib/gitlab/git/attributes_at_ref_parser_spec.rb2
-rw-r--r--spec/lib/gitlab/git/attributes_parser_spec.rb2
-rw-r--r--spec/lib/gitlab/git/blame_spec.rb2
-rw-r--r--spec/lib/gitlab/git/blob_spec.rb2
-rw-r--r--spec/lib/gitlab/git/branch_spec.rb2
-rw-r--r--spec/lib/gitlab/git/bundle_file_spec.rb2
-rw-r--r--spec/lib/gitlab/git/changes_spec.rb2
-rw-r--r--spec/lib/gitlab/git/commit_spec.rb32
-rw-r--r--spec/lib/gitlab/git/compare_spec.rb2
-rw-r--r--spec/lib/gitlab/git/conflict/file_spec.rb2
-rw-r--r--spec/lib/gitlab/git/conflict/parser_spec.rb2
-rw-r--r--spec/lib/gitlab/git/cross_repo_comparer_spec.rb2
-rw-r--r--spec/lib/gitlab/git/diff_collection_spec.rb2
-rw-r--r--spec/lib/gitlab/git/diff_spec.rb2
-rw-r--r--spec/lib/gitlab/git/diff_stats_collection_spec.rb6
-rw-r--r--spec/lib/gitlab/git/gitmodules_parser_spec.rb2
-rw-r--r--spec/lib/gitlab/git/hook_env_spec.rb2
-rw-r--r--spec/lib/gitlab/git/keep_around_spec.rb2
-rw-r--r--spec/lib/gitlab/git/lfs_changes_spec.rb2
-rw-r--r--spec/lib/gitlab/git/lfs_pointer_file_spec.rb2
-rw-r--r--spec/lib/gitlab/git/merge_base_spec.rb2
-rw-r--r--spec/lib/gitlab/git/object_pool_spec.rb2
-rw-r--r--spec/lib/gitlab/git/patches/collection_spec.rb2
-rw-r--r--spec/lib/gitlab/git/patches/commit_patches_spec.rb2
-rw-r--r--spec/lib/gitlab/git/patches/patch_spec.rb2
-rw-r--r--spec/lib/gitlab/git/pre_receive_error_spec.rb2
-rw-r--r--spec/lib/gitlab/git/push_spec.rb2
-rw-r--r--spec/lib/gitlab/git/raw_diff_change_spec.rb2
-rw-r--r--spec/lib/gitlab/git/remote_mirror_spec.rb2
-rw-r--r--spec/lib/gitlab/git/remote_repository_spec.rb2
-rw-r--r--spec/lib/gitlab/git/repository_cleaner_spec.rb2
-rw-r--r--spec/lib/gitlab/git/repository_spec.rb43
-rw-r--r--spec/lib/gitlab/git/rugged_impl/use_rugged_spec.rb2
-rw-r--r--spec/lib/gitlab/git/tag_spec.rb2
-rw-r--r--spec/lib/gitlab/git/tree_spec.rb2
-rw-r--r--spec/lib/gitlab/git/user_spec.rb2
-rw-r--r--spec/lib/gitlab/git/util_spec.rb2
-rw-r--r--spec/lib/gitlab/git/wiki_spec.rb2
-rw-r--r--spec/lib/gitlab/git/wraps_gitaly_errors_spec.rb2
-rw-r--r--spec/lib/gitlab/git_access_design_spec.rb2
-rw-r--r--spec/lib/gitlab/git_access_project_spec.rb2
-rw-r--r--spec/lib/gitlab/git_access_snippet_spec.rb2
-rw-r--r--spec/lib/gitlab/git_access_spec.rb2
-rw-r--r--spec/lib/gitlab/git_access_wiki_spec.rb2
-rw-r--r--spec/lib/gitlab/git_post_receive_spec.rb2
-rw-r--r--spec/lib/gitlab/git_ref_validator_spec.rb2
-rw-r--r--spec/lib/gitlab/git_spec.rb2
-rw-r--r--spec/lib/gitlab/gitaly_client/blob_service_spec.rb2
-rw-r--r--spec/lib/gitlab/gitaly_client/blobs_stitcher_spec.rb2
-rw-r--r--spec/lib/gitlab/gitaly_client/call_spec.rb122
-rw-r--r--spec/lib/gitlab/gitaly_client/cleanup_service_spec.rb2
-rw-r--r--spec/lib/gitlab/gitaly_client/commit_service_spec.rb29
-rw-r--r--spec/lib/gitlab/gitaly_client/conflict_files_stitcher_spec.rb2
-rw-r--r--spec/lib/gitlab/gitaly_client/conflicts_service_spec.rb2
-rw-r--r--spec/lib/gitlab/gitaly_client/diff_spec.rb2
-rw-r--r--spec/lib/gitlab/gitaly_client/diff_stitcher_spec.rb2
-rw-r--r--spec/lib/gitlab/gitaly_client/health_check_service_spec.rb2
-rw-r--r--spec/lib/gitlab/gitaly_client/object_pool_service_spec.rb2
-rw-r--r--spec/lib/gitlab/gitaly_client/operation_service_spec.rb16
-rw-r--r--spec/lib/gitlab/gitaly_client/praefect_info_service_spec.rb2
-rw-r--r--spec/lib/gitlab/gitaly_client/ref_service_spec.rb47
-rw-r--r--spec/lib/gitlab/gitaly_client/remote_service_spec.rb5
-rw-r--r--spec/lib/gitlab/gitaly_client/repository_service_spec.rb2
-rw-r--r--spec/lib/gitlab/gitaly_client/storage_settings_spec.rb2
-rw-r--r--spec/lib/gitlab/gitaly_client/util_spec.rb2
-rw-r--r--spec/lib/gitlab/gitaly_client/wiki_service_spec.rb2
-rw-r--r--spec/lib/gitlab/gitaly_client_spec.rb4
-rw-r--r--spec/lib/gitlab/github_import/bulk_importing_spec.rb2
-rw-r--r--spec/lib/gitlab/github_import/client_spec.rb2
-rw-r--r--spec/lib/gitlab/github_import/importer/diff_note_importer_spec.rb2
-rw-r--r--spec/lib/gitlab/github_import/importer/diff_notes_importer_spec.rb2
-rw-r--r--spec/lib/gitlab/github_import/importer/issue_and_label_links_importer_spec.rb2
-rw-r--r--spec/lib/gitlab/github_import/importer/issue_importer_spec.rb2
-rw-r--r--spec/lib/gitlab/github_import/importer/issues_importer_spec.rb2
-rw-r--r--spec/lib/gitlab/github_import/importer/label_links_importer_spec.rb2
-rw-r--r--spec/lib/gitlab/github_import/importer/labels_importer_spec.rb2
-rw-r--r--spec/lib/gitlab/github_import/importer/lfs_object_importer_spec.rb2
-rw-r--r--spec/lib/gitlab/github_import/importer/lfs_objects_importer_spec.rb2
-rw-r--r--spec/lib/gitlab/github_import/importer/milestones_importer_spec.rb2
-rw-r--r--spec/lib/gitlab/github_import/importer/note_importer_spec.rb2
-rw-r--r--spec/lib/gitlab/github_import/importer/notes_importer_spec.rb2
-rw-r--r--spec/lib/gitlab/github_import/importer/pull_request_importer_spec.rb2
-rw-r--r--spec/lib/gitlab/github_import/importer/pull_requests_importer_spec.rb2
-rw-r--r--spec/lib/gitlab/github_import/importer/releases_importer_spec.rb2
-rw-r--r--spec/lib/gitlab/github_import/importer/repository_importer_spec.rb2
-rw-r--r--spec/lib/gitlab/github_import/issuable_finder_spec.rb2
-rw-r--r--spec/lib/gitlab/github_import/label_finder_spec.rb2
-rw-r--r--spec/lib/gitlab/github_import/markdown_text_spec.rb2
-rw-r--r--spec/lib/gitlab/github_import/milestone_finder_spec.rb2
-rw-r--r--spec/lib/gitlab/github_import/page_counter_spec.rb2
-rw-r--r--spec/lib/gitlab/github_import/parallel_importer_spec.rb2
-rw-r--r--spec/lib/gitlab/github_import/parallel_scheduling_spec.rb2
-rw-r--r--spec/lib/gitlab/github_import/representation/diff_note_spec.rb2
-rw-r--r--spec/lib/gitlab/github_import/representation/expose_attribute_spec.rb2
-rw-r--r--spec/lib/gitlab/github_import/representation/issue_spec.rb2
-rw-r--r--spec/lib/gitlab/github_import/representation/note_spec.rb2
-rw-r--r--spec/lib/gitlab/github_import/representation/pull_request_spec.rb2
-rw-r--r--spec/lib/gitlab/github_import/representation/to_hash_spec.rb2
-rw-r--r--spec/lib/gitlab/github_import/representation/user_spec.rb2
-rw-r--r--spec/lib/gitlab/github_import/representation_spec.rb2
-rw-r--r--spec/lib/gitlab/github_import/sequential_importer_spec.rb2
-rw-r--r--spec/lib/gitlab/github_import/user_finder_spec.rb2
-rw-r--r--spec/lib/gitlab/github_import_spec.rb2
-rw-r--r--spec/lib/gitlab/gitlab_import/client_spec.rb2
-rw-r--r--spec/lib/gitlab/gitlab_import/importer_spec.rb2
-rw-r--r--spec/lib/gitlab/gitlab_import/project_creator_spec.rb2
-rw-r--r--spec/lib/gitlab/gl_repository/identifier_spec.rb63
-rw-r--r--spec/lib/gitlab/gl_repository/repo_type_spec.rb2
-rw-r--r--spec/lib/gitlab/gl_repository_spec.rb2
-rw-r--r--spec/lib/gitlab/global_id_spec.rb35
-rw-r--r--spec/lib/gitlab/gon_helper_spec.rb2
-rw-r--r--spec/lib/gitlab/google_code_import/client_spec.rb2
-rw-r--r--spec/lib/gitlab/google_code_import/importer_spec.rb2
-rw-r--r--spec/lib/gitlab/google_code_import/project_creator_spec.rb2
-rw-r--r--spec/lib/gitlab/gpg/commit_spec.rb2
-rw-r--r--spec/lib/gitlab/gpg_spec.rb4
-rw-r--r--spec/lib/gitlab/grape_logging/formatters/lograge_with_timestamp_spec.rb2
-rw-r--r--spec/lib/gitlab/grape_logging/loggers/cloudflare_logger_spec.rb2
-rw-r--r--spec/lib/gitlab/grape_logging/loggers/exception_logger_spec.rb2
-rw-r--r--spec/lib/gitlab/grape_logging/loggers/perf_logger_spec.rb2
-rw-r--r--spec/lib/gitlab/grape_logging/loggers/queue_duration_logger_spec.rb2
-rw-r--r--spec/lib/gitlab/graphql/authorize/authorize_field_service_spec.rb2
-rw-r--r--spec/lib/gitlab/graphql/authorize/authorize_resource_spec.rb2
-rw-r--r--spec/lib/gitlab/graphql/calls_gitaly/instrumentation_spec.rb2
-rw-r--r--spec/lib/gitlab/graphql/copy_field_description_spec.rb2
-rw-r--r--spec/lib/gitlab/graphql/docs/renderer_spec.rb2
-rw-r--r--spec/lib/gitlab/graphql/find_argument_in_parent_spec.rb2
-rw-r--r--spec/lib/gitlab/graphql/generic_tracing_spec.rb2
-rw-r--r--spec/lib/gitlab/graphql/loaders/batch_lfs_oid_loader_spec.rb2
-rw-r--r--spec/lib/gitlab/graphql/loaders/batch_model_loader_spec.rb2
-rw-r--r--spec/lib/gitlab/graphql/loaders/batch_project_statistics_loader_spec.rb2
-rw-r--r--spec/lib/gitlab/graphql/loaders/batch_root_storage_statistics_loader_spec.rb2
-rw-r--r--spec/lib/gitlab/graphql/loaders/issuable_loader_spec.rb96
-rw-r--r--spec/lib/gitlab/graphql/markdown_field/resolver_spec.rb2
-rw-r--r--spec/lib/gitlab/graphql/markdown_field_spec.rb2
-rw-r--r--spec/lib/gitlab/graphql/mount_mutation_spec.rb63
-rw-r--r--spec/lib/gitlab/graphql/pagination/externally_paginated_array_connection_spec.rb2
-rw-r--r--spec/lib/gitlab/graphql/pagination/keyset/conditions/not_null_condition_spec.rb2
-rw-r--r--spec/lib/gitlab/graphql/pagination/keyset/conditions/null_condition_spec.rb2
-rw-r--r--spec/lib/gitlab/graphql/pagination/keyset/connection_spec.rb2
-rw-r--r--spec/lib/gitlab/graphql/pagination/keyset/order_info_spec.rb2
-rw-r--r--spec/lib/gitlab/graphql/pagination/keyset/query_builder_spec.rb2
-rw-r--r--spec/lib/gitlab/graphql/pagination/offset_active_record_relation_connection_spec.rb2
-rw-r--r--spec/lib/gitlab/graphql/query_analyzers/logger_analyzer_spec.rb20
-rw-r--r--spec/lib/gitlab/graphql/representation/submodule_tree_entry_spec.rb2
-rw-r--r--spec/lib/gitlab/graphql/representation/tree_entry_spec.rb2
-rw-r--r--spec/lib/gitlab/graphql/timeout_spec.rb2
-rw-r--r--spec/lib/gitlab/graphql_logger_spec.rb2
-rw-r--r--spec/lib/gitlab/graphs/commits_spec.rb2
-rw-r--r--spec/lib/gitlab/group_search_results_spec.rb2
-rw-r--r--spec/lib/gitlab/hashed_storage/migrator_spec.rb2
-rw-r--r--spec/lib/gitlab/health_checks/db_check_spec.rb2
-rw-r--r--spec/lib/gitlab/health_checks/gitaly_check_spec.rb2
-rw-r--r--spec/lib/gitlab/health_checks/master_check_spec.rb2
-rw-r--r--spec/lib/gitlab/health_checks/probes/collection_spec.rb16
-rw-r--r--spec/lib/gitlab/health_checks/puma_check_spec.rb2
-rw-r--r--spec/lib/gitlab/health_checks/redis/cache_check_spec.rb2
-rw-r--r--spec/lib/gitlab/health_checks/redis/queues_check_spec.rb2
-rw-r--r--spec/lib/gitlab/health_checks/redis/redis_check_spec.rb2
-rw-r--r--spec/lib/gitlab/health_checks/redis/shared_state_check_spec.rb2
-rw-r--r--spec/lib/gitlab/health_checks/simple_check_shared.rb2
-rw-r--r--spec/lib/gitlab/health_checks/unicorn_check_spec.rb2
-rw-r--r--spec/lib/gitlab/highlight_spec.rb2
-rw-r--r--spec/lib/gitlab/hook_data/base_builder_spec.rb2
-rw-r--r--spec/lib/gitlab/hook_data/issuable_builder_spec.rb2
-rw-r--r--spec/lib/gitlab/hook_data/issue_builder_spec.rb2
-rw-r--r--spec/lib/gitlab/hook_data/merge_request_builder_spec.rb2
-rw-r--r--spec/lib/gitlab/http_connection_adapter_spec.rb2
-rw-r--r--spec/lib/gitlab/http_io_spec.rb2
-rw-r--r--spec/lib/gitlab/http_spec.rb2
-rw-r--r--spec/lib/gitlab/i18n/metadata_entry_spec.rb2
-rw-r--r--spec/lib/gitlab/i18n/po_linter_spec.rb2
-rw-r--r--spec/lib/gitlab/i18n/translation_entry_spec.rb2
-rw-r--r--spec/lib/gitlab/i18n_spec.rb2
-rw-r--r--spec/lib/gitlab/identifier_spec.rb2
-rw-r--r--spec/lib/gitlab/import/database_helpers_spec.rb2
-rw-r--r--spec/lib/gitlab/import/merge_request_creator_spec.rb2
-rw-r--r--spec/lib/gitlab/import/merge_request_helpers_spec.rb2
-rw-r--r--spec/lib/gitlab/import/metrics_spec.rb70
-rw-r--r--spec/lib/gitlab/import/set_async_jid_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/after_export_strategies/base_after_export_strategy_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/after_export_strategies/web_upload_strategy_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/after_export_strategy_builder_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/all_models.yml15
-rw-r--r--spec/lib/gitlab/import_export/attribute_cleaner_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/attribute_configuration_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/attributes_finder_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/attributes_permitter_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/avatar_restorer_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/avatar_saver_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/base/object_builder_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/base/relation_factory_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/command_line_util_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/config_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/design_repo_restorer_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/design_repo_saver_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/error_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/fast_hash_serializer_spec.rb10
-rw-r--r--spec/lib/gitlab/import_export/file_importer_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/fork_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/group/legacy_tree_restorer_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/group/legacy_tree_saver_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/group/object_builder_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/group/relation_factory_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/group/tree_restorer_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/group/tree_saver_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/hash_util_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/import_export_equivalence_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/import_export_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/import_failure_service_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/import_test_coverage_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/importer_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/json/legacy_reader/file_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/json/legacy_reader/hash_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/json/legacy_writer_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/json/ndjson_reader_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/json/ndjson_writer_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/json/streaming_serializer_spec.rb16
-rw-r--r--spec/lib/gitlab/import_export/legacy_relation_tree_saver_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/lfs_restorer_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/lfs_saver_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/members_mapper_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/merge_request_parser_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/model_configuration_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/project/export_task_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/project/import_task_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/project/object_builder_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/project/relation_factory_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/project/tree_restorer_spec.rb23
-rw-r--r--spec/lib/gitlab/import_export/project/tree_saver_spec.rb15
-rw-r--r--spec/lib/gitlab/import_export/reader_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/references_configuration_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/relation_tree_restorer_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/repo_restorer_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/repo_saver_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/safe_model_attributes.yml32
-rw-r--r--spec/lib/gitlab/import_export/saver_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/shared_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/snippet_repo_restorer_spec.rb26
-rw-r--r--spec/lib/gitlab/import_export/snippet_repo_saver_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/snippets_repo_restorer_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/snippets_repo_saver_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/uploads_manager_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/uploads_restorer_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/uploads_saver_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/version_checker_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/wiki_repo_saver_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/wiki_restorer_spec.rb2
-rw-r--r--spec/lib/gitlab/import_formatter_spec.rb2
-rw-r--r--spec/lib/gitlab/import_sources_spec.rb2
-rw-r--r--spec/lib/gitlab/incident_management/pager_duty/incident_issue_description_spec.rb97
-rw-r--r--spec/lib/gitlab/incoming_email_spec.rb2
-rw-r--r--spec/lib/gitlab/insecure_key_fingerprint_spec.rb2
-rw-r--r--spec/lib/gitlab/instrumentation/redis_base_spec.rb2
-rw-r--r--spec/lib/gitlab/instrumentation/redis_cluster_validator_spec.rb93
-rw-r--r--spec/lib/gitlab/instrumentation/redis_interceptor_spec.rb71
-rw-r--r--spec/lib/gitlab/instrumentation/redis_spec.rb2
-rw-r--r--spec/lib/gitlab/instrumentation_helper_spec.rb2
-rw-r--r--spec/lib/gitlab/internal_post_receive/response_spec.rb2
-rw-r--r--spec/lib/gitlab/issuable_metadata_spec.rb2
-rw-r--r--spec/lib/gitlab/issuable_sorter_spec.rb2
-rw-r--r--spec/lib/gitlab/issuables_count_for_state_spec.rb2
-rw-r--r--spec/lib/gitlab/jira_import/base_importer_spec.rb2
-rw-r--r--spec/lib/gitlab/jira_import/handle_labels_service_spec.rb2
-rw-r--r--spec/lib/gitlab/jira_import/issue_serializer_spec.rb41
-rw-r--r--spec/lib/gitlab/jira_import/issues_importer_spec.rb2
-rw-r--r--spec/lib/gitlab/jira_import/labels_importer_spec.rb2
-rw-r--r--spec/lib/gitlab/jira_import/metadata_collector_spec.rb2
-rw-r--r--spec/lib/gitlab/jira_import/user_mapper_spec.rb80
-rw-r--r--spec/lib/gitlab/jira_import_spec.rb2
-rw-r--r--spec/lib/gitlab/job_waiter_spec.rb2
-rw-r--r--spec/lib/gitlab/json_cache_spec.rb2
-rw-r--r--spec/lib/gitlab/json_logger_spec.rb2
-rw-r--r--spec/lib/gitlab/json_spec.rb429
-rw-r--r--spec/lib/gitlab/jwt_authenticatable_spec.rb2
-rw-r--r--spec/lib/gitlab/kubernetes/cluster_role_binding_spec.rb2
-rw-r--r--spec/lib/gitlab/kubernetes/config_map_spec.rb2
-rw-r--r--spec/lib/gitlab/kubernetes/config_maps/aws_node_auth_spec.rb2
-rw-r--r--spec/lib/gitlab/kubernetes/default_namespace_spec.rb2
-rw-r--r--spec/lib/gitlab/kubernetes/generic_secret_spec.rb2
-rw-r--r--spec/lib/gitlab/kubernetes/helm/api_spec.rb2
-rw-r--r--spec/lib/gitlab/kubernetes/helm/base_command_spec.rb2
-rw-r--r--spec/lib/gitlab/kubernetes/helm/certificate_spec.rb2
-rw-r--r--spec/lib/gitlab/kubernetes/helm/delete_command_spec.rb2
-rw-r--r--spec/lib/gitlab/kubernetes/helm/init_command_spec.rb2
-rw-r--r--spec/lib/gitlab/kubernetes/helm/install_command_spec.rb2
-rw-r--r--spec/lib/gitlab/kubernetes/helm/parsers/list_v2_spec.rb2
-rw-r--r--spec/lib/gitlab/kubernetes/helm/patch_command_spec.rb2
-rw-r--r--spec/lib/gitlab/kubernetes/helm/pod_spec.rb4
-rw-r--r--spec/lib/gitlab/kubernetes/helm/reset_command_spec.rb2
-rw-r--r--spec/lib/gitlab/kubernetes/kube_client_spec.rb2
-rw-r--r--spec/lib/gitlab/kubernetes/kubectl_cmd_spec.rb2
-rw-r--r--spec/lib/gitlab/kubernetes/namespace_spec.rb2
-rw-r--r--spec/lib/gitlab/kubernetes/network_policy_spec.rb2
-rw-r--r--spec/lib/gitlab/kubernetes/node_spec.rb68
-rw-r--r--spec/lib/gitlab/kubernetes/role_binding_spec.rb2
-rw-r--r--spec/lib/gitlab/kubernetes/role_spec.rb2
-rw-r--r--spec/lib/gitlab/kubernetes/service_account_spec.rb2
-rw-r--r--spec/lib/gitlab/kubernetes/service_account_token_spec.rb2
-rw-r--r--spec/lib/gitlab/kubernetes/tls_secret_spec.rb2
-rw-r--r--spec/lib/gitlab/kubernetes_spec.rb2
-rw-r--r--spec/lib/gitlab/language_data_spec.rb2
-rw-r--r--spec/lib/gitlab/language_detection_spec.rb2
-rw-r--r--spec/lib/gitlab/lazy_spec.rb2
-rw-r--r--spec/lib/gitlab/legacy_github_import/branch_formatter_spec.rb2
-rw-r--r--spec/lib/gitlab/legacy_github_import/client_spec.rb2
-rw-r--r--spec/lib/gitlab/legacy_github_import/comment_formatter_spec.rb2
-rw-r--r--spec/lib/gitlab/legacy_github_import/importer_spec.rb2
-rw-r--r--spec/lib/gitlab/legacy_github_import/issuable_formatter_spec.rb2
-rw-r--r--spec/lib/gitlab/legacy_github_import/issue_formatter_spec.rb2
-rw-r--r--spec/lib/gitlab/legacy_github_import/label_formatter_spec.rb2
-rw-r--r--spec/lib/gitlab/legacy_github_import/milestone_formatter_spec.rb2
-rw-r--r--spec/lib/gitlab/legacy_github_import/project_creator_spec.rb2
-rw-r--r--spec/lib/gitlab/legacy_github_import/pull_request_formatter_spec.rb2
-rw-r--r--spec/lib/gitlab/legacy_github_import/release_formatter_spec.rb2
-rw-r--r--spec/lib/gitlab/legacy_github_import/user_formatter_spec.rb2
-rw-r--r--spec/lib/gitlab/legacy_github_import/wiki_formatter_spec.rb2
-rw-r--r--spec/lib/gitlab/lets_encrypt/challenge_spec.rb2
-rw-r--r--spec/lib/gitlab/lets_encrypt/client_spec.rb2
-rw-r--r--spec/lib/gitlab/lets_encrypt/order_spec.rb2
-rw-r--r--spec/lib/gitlab/lets_encrypt_spec.rb2
-rw-r--r--spec/lib/gitlab/lfs_token_spec.rb2
-rw-r--r--spec/lib/gitlab/log_timestamp_formatter_spec.rb2
-rw-r--r--spec/lib/gitlab/logging/cloudflare_helper_spec.rb2
-rw-r--r--spec/lib/gitlab/lograge/custom_options_spec.rb18
-rw-r--r--spec/lib/gitlab/loop_helpers_spec.rb2
-rw-r--r--spec/lib/gitlab/mail_room/mail_room_spec.rb2
-rw-r--r--spec/lib/gitlab/manifest_import/manifest_spec.rb2
-rw-r--r--spec/lib/gitlab/manifest_import/project_creator_spec.rb2
-rw-r--r--spec/lib/gitlab/markdown_cache/active_record/extension_spec.rb2
-rw-r--r--spec/lib/gitlab/markdown_cache/field_data_spec.rb2
-rw-r--r--spec/lib/gitlab/markdown_cache/redis/extension_spec.rb27
-rw-r--r--spec/lib/gitlab/markdown_cache/redis/store_spec.rb24
-rw-r--r--spec/lib/gitlab/markup_helper_spec.rb2
-rw-r--r--spec/lib/gitlab/metrics/background_transaction_spec.rb13
-rw-r--r--spec/lib/gitlab/metrics/dashboard/defaults_spec.rb2
-rw-r--r--spec/lib/gitlab/metrics/dashboard/finder_spec.rb16
-rw-r--r--spec/lib/gitlab/metrics/dashboard/processor_spec.rb31
-rw-r--r--spec/lib/gitlab/metrics/dashboard/service_selector_spec.rb42
-rw-r--r--spec/lib/gitlab/metrics/dashboard/stages/grafana_formatter_spec.rb2
-rw-r--r--spec/lib/gitlab/metrics/dashboard/stages/panel_ids_inserter_spec.rb6
-rw-r--r--spec/lib/gitlab/metrics/dashboard/stages/url_validator_spec.rb101
-rw-r--r--spec/lib/gitlab/metrics/dashboard/stages/variable_endpoint_inserter_spec.rb77
-rw-r--r--spec/lib/gitlab/metrics/dashboard/url_spec.rb31
-rw-r--r--spec/lib/gitlab/metrics/delta_spec.rb2
-rw-r--r--spec/lib/gitlab/metrics/elasticsearch_rack_middleware_spec.rb2
-rw-r--r--spec/lib/gitlab/metrics/exporter/base_exporter_spec.rb2
-rw-r--r--spec/lib/gitlab/metrics/exporter/sidekiq_exporter_spec.rb2
-rw-r--r--spec/lib/gitlab/metrics/exporter/web_exporter_spec.rb2
-rw-r--r--spec/lib/gitlab/metrics/instrumentation_spec.rb2
-rw-r--r--spec/lib/gitlab/metrics/method_call_spec.rb2
-rw-r--r--spec/lib/gitlab/metrics/methods_spec.rb2
-rw-r--r--spec/lib/gitlab/metrics/prometheus_spec.rb2
-rw-r--r--spec/lib/gitlab/metrics/rack_middleware_spec.rb2
-rw-r--r--spec/lib/gitlab/metrics/redis_rack_middleware_spec.rb2
-rw-r--r--spec/lib/gitlab/metrics/requests_rack_middleware_spec.rb2
-rw-r--r--spec/lib/gitlab/metrics/samplers/database_sampler_spec.rb2
-rw-r--r--spec/lib/gitlab/metrics/samplers/puma_sampler_spec.rb2
-rw-r--r--spec/lib/gitlab/metrics/samplers/ruby_sampler_spec.rb2
-rw-r--r--spec/lib/gitlab/metrics/samplers/unicorn_sampler_spec.rb2
-rw-r--r--spec/lib/gitlab/metrics/sidekiq_middleware_spec.rb31
-rw-r--r--spec/lib/gitlab/metrics/subscribers/action_view_spec.rb2
-rw-r--r--spec/lib/gitlab/metrics/subscribers/active_record_spec.rb233
-rw-r--r--spec/lib/gitlab/metrics/subscribers/rails_cache_spec.rb2
-rw-r--r--spec/lib/gitlab/metrics/system_spec.rb2
-rw-r--r--spec/lib/gitlab/metrics/transaction_spec.rb13
-rw-r--r--spec/lib/gitlab/metrics/web_transaction_spec.rb34
-rw-r--r--spec/lib/gitlab/metrics_spec.rb2
-rw-r--r--spec/lib/gitlab/middleware/basic_health_check_spec.rb2
-rw-r--r--spec/lib/gitlab/middleware/go_spec.rb2
-rw-r--r--spec/lib/gitlab/middleware/handle_ip_spoof_attack_error_spec.rb2
-rw-r--r--spec/lib/gitlab/middleware/multipart_spec.rb80
-rw-r--r--spec/lib/gitlab/middleware/rails_queue_duration_spec.rb2
-rw-r--r--spec/lib/gitlab/middleware/read_only_spec.rb2
-rw-r--r--spec/lib/gitlab/middleware/release_env_spec.rb2
-rw-r--r--spec/lib/gitlab/middleware/request_context_spec.rb2
-rw-r--r--spec/lib/gitlab/middleware/same_site_cookies_spec.rb2
-rw-r--r--spec/lib/gitlab/monitor/demo_projects_spec.rb2
-rw-r--r--spec/lib/gitlab/multi_collection_paginator_spec.rb2
-rw-r--r--spec/lib/gitlab/multi_destination_logger_spec.rb2
-rw-r--r--spec/lib/gitlab/namespaced_session_store_spec.rb2
-rw-r--r--spec/lib/gitlab/no_cache_headers_spec.rb2
-rw-r--r--spec/lib/gitlab/noteable_metadata_spec.rb2
-rw-r--r--spec/lib/gitlab/null_request_store_spec.rb2
-rw-r--r--spec/lib/gitlab/object_hierarchy_spec.rb2
-rw-r--r--spec/lib/gitlab/octokit/middleware_spec.rb2
-rw-r--r--spec/lib/gitlab/omniauth_initializer_spec.rb2
-rw-r--r--spec/lib/gitlab/omniauth_logging/json_formatter_spec.rb2
-rw-r--r--spec/lib/gitlab/optimistic_locking_spec.rb2
-rw-r--r--spec/lib/gitlab/other_markup_spec.rb2
-rw-r--r--spec/lib/gitlab/otp_key_rotator_spec.rb2
-rw-r--r--spec/lib/gitlab/pages_spec.rb2
-rw-r--r--spec/lib/gitlab/pagination/keyset/page_spec.rb2
-rw-r--r--spec/lib/gitlab/pagination/keyset/pager_spec.rb2
-rw-r--r--spec/lib/gitlab/pagination/keyset/request_context_spec.rb2
-rw-r--r--spec/lib/gitlab/pagination/keyset_spec.rb2
-rw-r--r--spec/lib/gitlab/pagination/offset_pagination_spec.rb2
-rw-r--r--spec/lib/gitlab/patch/action_dispatch_journey_formatter_spec.rb2
-rw-r--r--spec/lib/gitlab/patch/draw_route_spec.rb2
-rw-r--r--spec/lib/gitlab/patch/prependable_spec.rb2
-rw-r--r--spec/lib/gitlab/path_regex_spec.rb2
-rw-r--r--spec/lib/gitlab/performance_bar/with_top_level_warnings_spec.rb2
-rw-r--r--spec/lib/gitlab/performance_bar_spec.rb2
-rw-r--r--spec/lib/gitlab/phabricator_import/cache/map_spec.rb2
-rw-r--r--spec/lib/gitlab/phabricator_import/conduit/client_spec.rb2
-rw-r--r--spec/lib/gitlab/phabricator_import/conduit/maniphest_spec.rb2
-rw-r--r--spec/lib/gitlab/phabricator_import/conduit/response_spec.rb4
-rw-r--r--spec/lib/gitlab/phabricator_import/conduit/tasks_response_spec.rb2
-rw-r--r--spec/lib/gitlab/phabricator_import/conduit/user_spec.rb2
-rw-r--r--spec/lib/gitlab/phabricator_import/conduit/users_response_spec.rb2
-rw-r--r--spec/lib/gitlab/phabricator_import/importer_spec.rb2
-rw-r--r--spec/lib/gitlab/phabricator_import/issues/importer_spec.rb2
-rw-r--r--spec/lib/gitlab/phabricator_import/issues/task_importer_spec.rb2
-rw-r--r--spec/lib/gitlab/phabricator_import/project_creator_spec.rb2
-rw-r--r--spec/lib/gitlab/phabricator_import/representation/task_spec.rb2
-rw-r--r--spec/lib/gitlab/phabricator_import/representation/user_spec.rb2
-rw-r--r--spec/lib/gitlab/phabricator_import/user_finder_spec.rb2
-rw-r--r--spec/lib/gitlab/phabricator_import/worker_state_spec.rb2
-rw-r--r--spec/lib/gitlab/polling_interval_spec.rb2
-rw-r--r--spec/lib/gitlab/popen/runner_spec.rb2
-rw-r--r--spec/lib/gitlab/popen_spec.rb2
-rw-r--r--spec/lib/gitlab/private_commit_email_spec.rb2
-rw-r--r--spec/lib/gitlab/process_memory_cache/helper_spec.rb2
-rw-r--r--spec/lib/gitlab/profiler_spec.rb3
-rw-r--r--spec/lib/gitlab/project_authorizations_spec.rb2
-rw-r--r--spec/lib/gitlab/project_search_results_spec.rb2
-rw-r--r--spec/lib/gitlab/project_template_spec.rb4
-rw-r--r--spec/lib/gitlab/project_transfer_spec.rb2
-rw-r--r--spec/lib/gitlab/prometheus/adapter_spec.rb2
-rw-r--r--spec/lib/gitlab/prometheus/additional_metrics_parser_spec.rb2
-rw-r--r--spec/lib/gitlab/prometheus/internal_spec.rb2
-rw-r--r--spec/lib/gitlab/prometheus/metric_group_spec.rb2
-rw-r--r--spec/lib/gitlab/prometheus/queries/additional_metrics_deployment_query_spec.rb2
-rw-r--r--spec/lib/gitlab/prometheus/queries/additional_metrics_environment_query_spec.rb2
-rw-r--r--spec/lib/gitlab/prometheus/queries/deployment_query_spec.rb2
-rw-r--r--spec/lib/gitlab/prometheus/queries/knative_invocation_query_spec.rb2
-rw-r--r--spec/lib/gitlab/prometheus/queries/matched_metric_query_spec.rb2
-rw-r--r--spec/lib/gitlab/prometheus/queries/validate_query_spec.rb2
-rw-r--r--spec/lib/gitlab/prometheus/query_variables_spec.rb2
-rw-r--r--spec/lib/gitlab/prometheus_client_spec.rb40
-rw-r--r--spec/lib/gitlab/puma_logging/json_formatter_spec.rb2
-rw-r--r--spec/lib/gitlab/push_options_spec.rb2
-rw-r--r--spec/lib/gitlab/query_limiting/active_support_subscriber_spec.rb2
-rw-r--r--spec/lib/gitlab/query_limiting/middleware_spec.rb2
-rw-r--r--spec/lib/gitlab/query_limiting/transaction_spec.rb2
-rw-r--r--spec/lib/gitlab/query_limiting_spec.rb2
-rw-r--r--spec/lib/gitlab/quick_actions/command_definition_spec.rb2
-rw-r--r--spec/lib/gitlab/quick_actions/dsl_spec.rb2
-rw-r--r--spec/lib/gitlab/quick_actions/extractor_spec.rb2
-rw-r--r--spec/lib/gitlab/quick_actions/spend_time_and_date_separator_spec.rb2
-rw-r--r--spec/lib/gitlab/quick_actions/substitution_definition_spec.rb2
-rw-r--r--spec/lib/gitlab/rate_limit_helpers_spec.rb2
-rw-r--r--spec/lib/gitlab/reactive_cache_set_cache_spec.rb2
-rw-r--r--spec/lib/gitlab/redis/boolean_spec.rb2
-rw-r--r--spec/lib/gitlab/redis/cache_spec.rb2
-rw-r--r--spec/lib/gitlab/redis/queues_spec.rb2
-rw-r--r--spec/lib/gitlab/redis/shared_state_spec.rb2
-rw-r--r--spec/lib/gitlab/redis/wrapper_spec.rb2
-rw-r--r--spec/lib/gitlab/reference_counter_spec.rb2
-rw-r--r--spec/lib/gitlab/reference_extractor_spec.rb2
-rw-r--r--spec/lib/gitlab/regex_spec.rb35
-rw-r--r--spec/lib/gitlab/repo_path_spec.rb2
-rw-r--r--spec/lib/gitlab/repository_cache_adapter_spec.rb2
-rw-r--r--spec/lib/gitlab/repository_cache_spec.rb2
-rw-r--r--spec/lib/gitlab/repository_hash_cache_spec.rb2
-rw-r--r--spec/lib/gitlab/repository_set_cache_spec.rb2
-rw-r--r--spec/lib/gitlab/repository_size_checker_spec.rb2
-rw-r--r--spec/lib/gitlab/repository_size_error_message_spec.rb2
-rw-r--r--spec/lib/gitlab/repository_url_builder_spec.rb2
-rw-r--r--spec/lib/gitlab/request_context_spec.rb2
-rw-r--r--spec/lib/gitlab/request_forgery_protection_spec.rb2
-rw-r--r--spec/lib/gitlab/request_profiler/profile_spec.rb2
-rw-r--r--spec/lib/gitlab/request_profiler_spec.rb2
-rw-r--r--spec/lib/gitlab/route_map_spec.rb2
-rw-r--r--spec/lib/gitlab/routing_spec.rb2
-rw-r--r--spec/lib/gitlab/rugged_instrumentation_spec.rb2
-rw-r--r--spec/lib/gitlab/runtime_spec.rb44
-rw-r--r--spec/lib/gitlab/safe_request_store_spec.rb2
-rw-r--r--spec/lib/gitlab/sanitizers/exif_spec.rb2
-rw-r--r--spec/lib/gitlab/sanitizers/svg_spec.rb2
-rw-r--r--spec/lib/gitlab/search/found_blob_spec.rb2
-rw-r--r--spec/lib/gitlab/search/found_wiki_page_spec.rb2
-rw-r--r--spec/lib/gitlab/search/query_spec.rb2
-rw-r--r--spec/lib/gitlab/search_context/builder_spec.rb2
-rw-r--r--spec/lib/gitlab/search_context/controller_concern_spec.rb2
-rw-r--r--spec/lib/gitlab/search_results_spec.rb2
-rw-r--r--spec/lib/gitlab/serializer/ci/variables_spec.rb2
-rw-r--r--spec/lib/gitlab/serializer/pagination_spec.rb2
-rw-r--r--spec/lib/gitlab/serverless/service_spec.rb2
-rw-r--r--spec/lib/gitlab/service_desk_email_spec.rb59
-rw-r--r--spec/lib/gitlab/service_desk_spec.rb56
-rw-r--r--spec/lib/gitlab/session_spec.rb2
-rw-r--r--spec/lib/gitlab/shard_health_cache_spec.rb2
-rw-r--r--spec/lib/gitlab/shell_spec.rb2
-rw-r--r--spec/lib/gitlab/sherlock/collection_spec.rb2
-rw-r--r--spec/lib/gitlab/sherlock/file_sample_spec.rb2
-rw-r--r--spec/lib/gitlab/sherlock/line_profiler_spec.rb2
-rw-r--r--spec/lib/gitlab/sherlock/line_sample_spec.rb2
-rw-r--r--spec/lib/gitlab/sherlock/location_spec.rb2
-rw-r--r--spec/lib/gitlab/sherlock/middleware_spec.rb2
-rw-r--r--spec/lib/gitlab/sherlock/query_spec.rb2
-rw-r--r--spec/lib/gitlab/sherlock/transaction_spec.rb2
-rw-r--r--spec/lib/gitlab/sidekiq_cluster/cli_spec.rb2
-rw-r--r--spec/lib/gitlab/sidekiq_cluster_spec.rb2
-rw-r--r--spec/lib/gitlab/sidekiq_config/cli_methods_spec.rb2
-rw-r--r--spec/lib/gitlab/sidekiq_config/worker_spec.rb2
-rw-r--r--spec/lib/gitlab/sidekiq_config_spec.rb2
-rw-r--r--spec/lib/gitlab/sidekiq_daemon/memory_killer_spec.rb2
-rw-r--r--spec/lib/gitlab/sidekiq_daemon/monitor_spec.rb2
-rw-r--r--spec/lib/gitlab/sidekiq_logging/deduplication_logger_spec.rb7
-rw-r--r--spec/lib/gitlab/sidekiq_logging/exception_handler_spec.rb2
-rw-r--r--spec/lib/gitlab/sidekiq_logging/json_formatter_spec.rb2
-rw-r--r--spec/lib/gitlab/sidekiq_logging/structured_logger_spec.rb2
-rw-r--r--spec/lib/gitlab/sidekiq_middleware/admin_mode/client_spec.rb2
-rw-r--r--spec/lib/gitlab/sidekiq_middleware/admin_mode/server_spec.rb2
-rw-r--r--spec/lib/gitlab/sidekiq_middleware/client_metrics_spec.rb2
-rw-r--r--spec/lib/gitlab/sidekiq_middleware/duplicate_jobs/client_spec.rb2
-rw-r--r--spec/lib/gitlab/sidekiq_middleware/duplicate_jobs/duplicate_job_spec.rb2
-rw-r--r--spec/lib/gitlab/sidekiq_middleware/duplicate_jobs/server_spec.rb2
-rw-r--r--spec/lib/gitlab/sidekiq_middleware/duplicate_jobs/strategies/until_executing_spec.rb16
-rw-r--r--spec/lib/gitlab/sidekiq_middleware/duplicate_jobs/strategies_spec.rb2
-rw-r--r--spec/lib/gitlab/sidekiq_middleware/extra_done_log_metadata_spec.rb2
-rw-r--r--spec/lib/gitlab/sidekiq_middleware/memory_killer_spec.rb2
-rw-r--r--spec/lib/gitlab/sidekiq_middleware/monitor_spec.rb2
-rw-r--r--spec/lib/gitlab/sidekiq_middleware/server_metrics_spec.rb2
-rw-r--r--spec/lib/gitlab/sidekiq_middleware/worker_context/client_spec.rb2
-rw-r--r--spec/lib/gitlab/sidekiq_middleware/worker_context/server_spec.rb2
-rw-r--r--spec/lib/gitlab/sidekiq_middleware_spec.rb2
-rw-r--r--spec/lib/gitlab/sidekiq_queue_spec.rb2
-rw-r--r--spec/lib/gitlab/sidekiq_signals_spec.rb2
-rw-r--r--spec/lib/gitlab/sidekiq_status/client_middleware_spec.rb2
-rw-r--r--spec/lib/gitlab/sidekiq_status/server_middleware_spec.rb2
-rw-r--r--spec/lib/gitlab/sidekiq_status_spec.rb2
-rw-r--r--spec/lib/gitlab/sidekiq_versioning/manager_spec.rb2
-rw-r--r--spec/lib/gitlab/sidekiq_versioning_spec.rb2
-rw-r--r--spec/lib/gitlab/slash_commands/application_help_spec.rb2
-rw-r--r--spec/lib/gitlab/slash_commands/command_spec.rb2
-rw-r--r--spec/lib/gitlab/slash_commands/deploy_spec.rb2
-rw-r--r--spec/lib/gitlab/slash_commands/issue_close_spec.rb2
-rw-r--r--spec/lib/gitlab/slash_commands/issue_comment_spec.rb2
-rw-r--r--spec/lib/gitlab/slash_commands/issue_move_spec.rb2
-rw-r--r--spec/lib/gitlab/slash_commands/issue_new_spec.rb2
-rw-r--r--spec/lib/gitlab/slash_commands/issue_search_spec.rb2
-rw-r--r--spec/lib/gitlab/slash_commands/issue_show_spec.rb2
-rw-r--r--spec/lib/gitlab/slash_commands/presenters/access_spec.rb2
-rw-r--r--spec/lib/gitlab/slash_commands/presenters/deploy_spec.rb2
-rw-r--r--spec/lib/gitlab/slash_commands/presenters/error_spec.rb2
-rw-r--r--spec/lib/gitlab/slash_commands/presenters/issue_close_spec.rb2
-rw-r--r--spec/lib/gitlab/slash_commands/presenters/issue_comment_spec.rb2
-rw-r--r--spec/lib/gitlab/slash_commands/presenters/issue_move_spec.rb2
-rw-r--r--spec/lib/gitlab/slash_commands/presenters/issue_new_spec.rb2
-rw-r--r--spec/lib/gitlab/slash_commands/presenters/issue_search_spec.rb2
-rw-r--r--spec/lib/gitlab/slash_commands/presenters/issue_show_spec.rb2
-rw-r--r--spec/lib/gitlab/slash_commands/presenters/run_spec.rb2
-rw-r--r--spec/lib/gitlab/slash_commands/run_spec.rb2
-rw-r--r--spec/lib/gitlab/slug/environment_spec.rb2
-rw-r--r--spec/lib/gitlab/snippet_search_results_spec.rb2
-rw-r--r--spec/lib/gitlab/sourcegraph_spec.rb2
-rw-r--r--spec/lib/gitlab/sql/cte_spec.rb2
-rw-r--r--spec/lib/gitlab/sql/glob_spec.rb2
-rw-r--r--spec/lib/gitlab/sql/pattern_spec.rb2
-rw-r--r--spec/lib/gitlab/sql/recursive_cte_spec.rb2
-rw-r--r--spec/lib/gitlab/sql/union_spec.rb2
-rw-r--r--spec/lib/gitlab/ssh_public_key_spec.rb2
-rw-r--r--spec/lib/gitlab/static_site_editor/config_spec.rb33
-rw-r--r--spec/lib/gitlab/string_placeholder_replacer_spec.rb2
-rw-r--r--spec/lib/gitlab/string_range_marker_spec.rb2
-rw-r--r--spec/lib/gitlab/string_regex_marker_spec.rb2
-rw-r--r--spec/lib/gitlab/submodule_links_spec.rb2
-rw-r--r--spec/lib/gitlab/suggestions/commit_message_spec.rb2
-rw-r--r--spec/lib/gitlab/suggestions/file_suggestion_spec.rb339
-rw-r--r--spec/lib/gitlab/suggestions/suggestion_set_spec.rb7
-rw-r--r--spec/lib/gitlab/tab_width_spec.rb2
-rw-r--r--spec/lib/gitlab/tcp_checker_spec.rb2
-rw-r--r--spec/lib/gitlab/template/finders/global_template_finder_spec.rb2
-rw-r--r--spec/lib/gitlab/template/finders/repo_template_finders_spec.rb2
-rw-r--r--spec/lib/gitlab/template/gitignore_template_spec.rb2
-rw-r--r--spec/lib/gitlab/template/gitlab_ci_yml_template_spec.rb2
-rw-r--r--spec/lib/gitlab/template/issue_template_spec.rb2
-rw-r--r--spec/lib/gitlab/template/merge_request_template_spec.rb2
-rw-r--r--spec/lib/gitlab/themes_spec.rb2
-rw-r--r--spec/lib/gitlab/throttle_spec.rb2
-rw-r--r--spec/lib/gitlab/time_tracking_formatter_spec.rb2
-rw-r--r--spec/lib/gitlab/tracking/incident_management_spec.rb2
-rw-r--r--spec/lib/gitlab/tracking_spec.rb2
-rw-r--r--spec/lib/gitlab/tree_summary_spec.rb27
-rw-r--r--spec/lib/gitlab/untrusted_regexp/ruby_syntax_spec.rb2
-rw-r--r--spec/lib/gitlab/untrusted_regexp_spec.rb2
-rw-r--r--spec/lib/gitlab/updated_notes_paginator_spec.rb57
-rw-r--r--spec/lib/gitlab/uploads_transfer_spec.rb2
-rw-r--r--spec/lib/gitlab/url_blocker_spec.rb2
-rw-r--r--spec/lib/gitlab/url_blockers/domain_whitelist_entry_spec.rb2
-rw-r--r--spec/lib/gitlab/url_blockers/ip_whitelist_entry_spec.rb2
-rw-r--r--spec/lib/gitlab/url_blockers/url_whitelist_spec.rb2
-rw-r--r--spec/lib/gitlab/url_builder_spec.rb39
-rw-r--r--spec/lib/gitlab/url_sanitizer_spec.rb2
-rw-r--r--spec/lib/gitlab/usage_data/topology_spec.rb562
-rw-r--r--spec/lib/gitlab/usage_data_concerns/topology_spec.rb220
-rw-r--r--spec/lib/gitlab/usage_data_counters/cycle_analytics_counter_spec.rb2
-rw-r--r--spec/lib/gitlab/usage_data_counters/designs_counter_spec.rb2
-rw-r--r--spec/lib/gitlab/usage_data_counters/merge_request_counter_spec.rb2
-rw-r--r--spec/lib/gitlab/usage_data_counters/note_counter_spec.rb2
-rw-r--r--spec/lib/gitlab/usage_data_counters/productivity_analytics_counter_spec.rb2
-rw-r--r--spec/lib/gitlab/usage_data_counters/redis_counter_spec.rb2
-rw-r--r--spec/lib/gitlab/usage_data_counters/search_counter_spec.rb2
-rw-r--r--spec/lib/gitlab/usage_data_counters/snippet_counter_spec.rb2
-rw-r--r--spec/lib/gitlab/usage_data_counters/source_code_counter_spec.rb2
-rw-r--r--spec/lib/gitlab/usage_data_counters/track_unique_actions_spec.rb82
-rw-r--r--spec/lib/gitlab/usage_data_counters/web_ide_counter_spec.rb2
-rw-r--r--spec/lib/gitlab/usage_data_counters/wiki_page_counter_spec.rb2
-rw-r--r--spec/lib/gitlab/usage_data_spec.rb453
-rw-r--r--spec/lib/gitlab/user_access_snippet_spec.rb2
-rw-r--r--spec/lib/gitlab/user_access_spec.rb2
-rw-r--r--spec/lib/gitlab/utils/deep_size_spec.rb2
-rw-r--r--spec/lib/gitlab/utils/inline_hash_spec.rb2
-rw-r--r--spec/lib/gitlab/utils/json_size_estimator_spec.rb2
-rw-r--r--spec/lib/gitlab/utils/lazy_attributes_spec.rb2
-rw-r--r--spec/lib/gitlab/utils/log_limited_array_spec.rb2
-rw-r--r--spec/lib/gitlab/utils/markdown_spec.rb63
-rw-r--r--spec/lib/gitlab/utils/measuring_spec.rb2
-rw-r--r--spec/lib/gitlab/utils/merge_hash_spec.rb2
-rw-r--r--spec/lib/gitlab/utils/override_spec.rb2
-rw-r--r--spec/lib/gitlab/utils/safe_inline_hash_spec.rb2
-rw-r--r--spec/lib/gitlab/utils/sanitize_node_link_spec.rb2
-rw-r--r--spec/lib/gitlab/utils/strong_memoize_spec.rb2
-rw-r--r--spec/lib/gitlab/utils/usage_data_spec.rb22
-rw-r--r--spec/lib/gitlab/utils_spec.rb38
-rw-r--r--spec/lib/gitlab/verify/job_artifacts_spec.rb2
-rw-r--r--spec/lib/gitlab/verify/lfs_objects_spec.rb2
-rw-r--r--spec/lib/gitlab/verify/uploads_spec.rb2
-rw-r--r--spec/lib/gitlab/version_info_spec.rb2
-rw-r--r--spec/lib/gitlab/view/presenter/base_spec.rb2
-rw-r--r--spec/lib/gitlab/view/presenter/delegated_spec.rb2
-rw-r--r--spec/lib/gitlab/view/presenter/factory_spec.rb2
-rw-r--r--spec/lib/gitlab/view/presenter/simple_spec.rb2
-rw-r--r--spec/lib/gitlab/visibility_level_checker_spec.rb2
-rw-r--r--spec/lib/gitlab/visibility_level_spec.rb2
-rw-r--r--spec/lib/gitlab/web_ide/config/entry/global_spec.rb2
-rw-r--r--spec/lib/gitlab/web_ide/config/entry/terminal_spec.rb2
-rw-r--r--spec/lib/gitlab/web_ide/config_spec.rb2
-rw-r--r--spec/lib/gitlab/wiki_file_finder_spec.rb2
-rw-r--r--spec/lib/gitlab/wiki_pages/front_matter_parser_spec.rb2
-rw-r--r--spec/lib/gitlab/with_request_store_spec.rb2
-rw-r--r--spec/lib/gitlab/workhorse_spec.rb2
-rw-r--r--spec/lib/gitlab/x509/commit_spec.rb2
-rw-r--r--spec/lib/gitlab/x509/signature_spec.rb2
-rw-r--r--spec/lib/gitlab/x509/tag_spec.rb2
-rw-r--r--spec/lib/gitlab/zoom_link_extractor_spec.rb2
-rw-r--r--spec/lib/gitlab_danger_spec.rb2
-rw-r--r--spec/lib/gitlab_spec.rb2
-rw-r--r--spec/lib/google_api/auth_spec.rb2
-rw-r--r--spec/lib/google_api/cloud_platform/client_spec.rb2
-rw-r--r--spec/lib/grafana/client_spec.rb2
-rw-r--r--spec/lib/grafana/time_window_spec.rb6
-rw-r--r--spec/lib/grafana/validator_spec.rb2
-rw-r--r--spec/lib/json_web_token/hmac_token_spec.rb2
-rw-r--r--spec/lib/json_web_token/rsa_token_spec.rb2
-rw-r--r--spec/lib/json_web_token/token_spec.rb2
-rw-r--r--spec/lib/kramdown/parser/atlassian_document_format_spec.rb77
-rw-r--r--spec/lib/learn_gitlab_spec.rb61
-rw-r--r--spec/lib/marginalia_spec.rb2
-rw-r--r--spec/lib/mattermost/client_spec.rb2
-rw-r--r--spec/lib/mattermost/command_spec.rb2
-rw-r--r--spec/lib/mattermost/session_spec.rb2
-rw-r--r--spec/lib/mattermost/team_spec.rb2
-rw-r--r--spec/lib/microsoft_teams/activity_spec.rb2
-rw-r--r--spec/lib/microsoft_teams/notifier_spec.rb2
-rw-r--r--spec/lib/object_storage/direct_upload_spec.rb45
-rw-r--r--spec/lib/omni_auth/strategies/jwt_spec.rb2
-rw-r--r--spec/lib/pager_duty/webhook_payload_parser_spec.rb80
-rw-r--r--spec/lib/peek/views/bullet_detailed_spec.rb2
-rw-r--r--spec/lib/peek/views/detailed_view_spec.rb2
-rw-r--r--spec/lib/peek/views/redis_detailed_spec.rb2
-rw-r--r--spec/lib/peek/views/rugged_spec.rb2
-rw-r--r--spec/lib/product_analytics/event_params_spec.rb54
-rw-r--r--spec/lib/prometheus/cleanup_multiproc_dir_service_spec.rb2
-rw-r--r--spec/lib/prometheus/pid_provider_spec.rb2
-rw-r--r--spec/lib/quality/helm3_client_spec.rb133
-rw-r--r--spec/lib/quality/kubernetes_client_spec.rb111
-rw-r--r--spec/lib/quality/test_level_spec.rb4
-rw-r--r--spec/lib/rspec_flaky/config_spec.rb2
-rw-r--r--spec/lib/rspec_flaky/example_spec.rb2
-rw-r--r--spec/lib/rspec_flaky/flaky_example_spec.rb2
-rw-r--r--spec/lib/rspec_flaky/flaky_examples_collection_spec.rb2
-rw-r--r--spec/lib/rspec_flaky/listener_spec.rb2
-rw-r--r--spec/lib/rspec_flaky/report_spec.rb2
-rw-r--r--spec/lib/safe_zip/entry_spec.rb2
-rw-r--r--spec/lib/safe_zip/extract_params_spec.rb2
-rw-r--r--spec/lib/safe_zip/extract_spec.rb2
-rw-r--r--spec/lib/sentry/api_urls_spec.rb2
-rw-r--r--spec/lib/sentry/client/event_spec.rb2
-rw-r--r--spec/lib/sentry/client/issue_link_spec.rb2
-rw-r--r--spec/lib/sentry/client/issue_spec.rb3
-rw-r--r--spec/lib/sentry/client/projects_spec.rb2
-rw-r--r--spec/lib/sentry/client/repo_spec.rb2
-rw-r--r--spec/lib/sentry/client_spec.rb2
-rw-r--r--spec/lib/sentry/pagination_parser_spec.rb5
-rw-r--r--spec/lib/serializers/json_spec.rb2
-rw-r--r--spec/lib/system_check/app/authorized_keys_permission_check_spec.rb2
-rw-r--r--spec/lib/system_check/app/git_user_default_ssh_config_check_spec.rb2
-rw-r--r--spec/lib/system_check/app/hashed_storage_all_projects_check_spec.rb2
-rw-r--r--spec/lib/system_check/app/hashed_storage_enabled_check_spec.rb2
-rw-r--r--spec/lib/system_check/base_check_spec.rb2
-rw-r--r--spec/lib/system_check/orphans/namespace_check_spec.rb2
-rw-r--r--spec/lib/system_check/orphans/repository_check_spec.rb2
-rw-r--r--spec/lib/system_check/simple_executor_spec.rb2
-rw-r--r--spec/lib/system_check_spec.rb2
-rw-r--r--spec/lib/uploaded_file_spec.rb2
-rw-r--r--spec/mailers/abuse_report_mailer_spec.rb2
-rw-r--r--spec/mailers/devise_mailer_spec.rb38
-rw-r--r--spec/mailers/email_rejection_mailer_spec.rb2
-rw-r--r--spec/mailers/emails/auto_devops_spec.rb2
-rw-r--r--spec/mailers/emails/groups_spec.rb2
-rw-r--r--spec/mailers/emails/issues_spec.rb2
-rw-r--r--spec/mailers/emails/merge_requests_spec.rb18
-rw-r--r--spec/mailers/emails/pages_domains_spec.rb2
-rw-r--r--spec/mailers/emails/pipelines_spec.rb2
-rw-r--r--spec/mailers/emails/profile_spec.rb2
-rw-r--r--spec/mailers/emails/projects_spec.rb2
-rw-r--r--spec/mailers/emails/releases_spec.rb2
-rw-r--r--spec/mailers/emails/service_desk_spec.rb188
-rw-r--r--spec/mailers/notify_spec.rb83
-rw-r--r--spec/mailers/repository_check_mailer_spec.rb2
-rw-r--r--spec/migrations/20190924152703_migrate_issue_trackers_data_spec.rb2
-rw-r--r--spec/migrations/20191015154408_drop_merge_requests_require_code_owner_approval_from_projects_spec.rb2
-rw-r--r--spec/migrations/20191125114345_add_admin_mode_protected_path_spec.rb2
-rw-r--r--spec/migrations/20191204114127_delete_legacy_triggers_spec.rb2
-rw-r--r--spec/migrations/20200107172020_add_timestamp_softwarelicensespolicy_spec.rb2
-rw-r--r--spec/migrations/20200122123016_backfill_project_settings_spec.rb2
-rw-r--r--spec/migrations/20200123155929_remove_invalid_jira_data_spec.rb2
-rw-r--r--spec/migrations/20200127090233_remove_invalid_issue_tracker_data_spec.rb2
-rw-r--r--spec/migrations/20200130145430_reschedule_migrate_issue_trackers_data_spec.rb2
-rw-r--r--spec/migrations/20200313203550_remove_orphaned_chat_names_spec.rb2
-rw-r--r--spec/migrations/20200406102120_backfill_deployment_clusters_from_deployments_spec.rb2
-rw-r--r--spec/migrations/20200511145545_change_variable_interpolation_format_in_common_metrics_spec.rb13
-rw-r--r--spec/migrations/20200526115436_dedup_mr_metrics_spec.rb68
-rw-r--r--spec/migrations/20200526231421_update_index_approval_rule_name_for_code_owners_rule_type_spec.rb175
-rw-r--r--spec/migrations/20200703125016_backfill_namespace_settings_spec.rb30
-rw-r--r--spec/migrations/20200706035141_adjust_unique_index_alert_management_alerts_spec.rb57
-rw-r--r--spec/migrations/active_record/schema_spec.rb4
-rw-r--r--spec/migrations/add_default_and_free_plans_spec.rb2
-rw-r--r--spec/migrations/add_default_value_stream_to_groups_with_group_stages_spec.rb44
-rw-r--r--spec/migrations/add_deploy_token_type_to_deploy_tokens_spec.rb2
-rw-r--r--spec/migrations/add_incident_settings_to_all_existing_projects_spec.rb4
-rw-r--r--spec/migrations/add_temporary_partial_index_on_project_id_to_services_spec.rb2
-rw-r--r--spec/migrations/add_unique_constraint_to_approvals_user_id_and_merge_request_id_spec.rb2
-rw-r--r--spec/migrations/backfill_and_add_not_null_constraint_to_released_at_column_on_releases_table_spec.rb2
-rw-r--r--spec/migrations/backfill_imported_snippet_repositories_spec.rb2
-rw-r--r--spec/migrations/backfill_operations_feature_flags_active_spec.rb2
-rw-r--r--spec/migrations/backfill_operations_feature_flags_iid_spec.rb2
-rw-r--r--spec/migrations/backfill_releases_table_updated_at_and_add_not_null_constraints_to_timestamps_spec.rb2
-rw-r--r--spec/migrations/backfill_snippet_repositories_spec.rb2
-rw-r--r--spec/migrations/backfill_status_page_published_incidents_spec.rb2
-rw-r--r--spec/migrations/backport_enterprise_schema_spec.rb2
-rw-r--r--spec/migrations/cap_designs_filename_length_to_new_limit_spec.rb2
-rw-r--r--spec/migrations/change_outbound_local_requests_whitelist_default_spec.rb2
-rw-r--r--spec/migrations/change_packages_size_defaults_in_project_statistics_spec.rb2
-rw-r--r--spec/migrations/clean_grafana_url_spec.rb2
-rw-r--r--spec/migrations/clean_up_noteable_id_for_notes_on_commits_spec.rb2
-rw-r--r--spec/migrations/cleanup_empty_commit_user_mentions_spec.rb2
-rw-r--r--spec/migrations/cleanup_legacy_artifact_migration_spec.rb2
-rw-r--r--spec/migrations/cleanup_optimistic_locking_nulls_pt2_fixed_spec.rb2
-rw-r--r--spec/migrations/cleanup_optimistic_locking_nulls_spec.rb2
-rw-r--r--spec/migrations/cleanup_projects_with_missing_namespace_spec.rb2
-rw-r--r--spec/migrations/confirm_project_bot_users_spec.rb84
-rw-r--r--spec/migrations/create_environment_for_self_monitoring_project_spec.rb2
-rw-r--r--spec/migrations/delete_internal_ids_where_feature_flags_usage_spec.rb2
-rw-r--r--spec/migrations/delete_template_project_services_spec.rb2
-rw-r--r--spec/migrations/delete_template_services_duplicated_by_type_spec.rb2
-rw-r--r--spec/migrations/delete_user_callout_alerts_moved_spec.rb30
-rw-r--r--spec/migrations/drop_activate_prometheus_services_background_jobs_spec.rb2
-rw-r--r--spec/migrations/drop_background_migration_jobs_spec.rb2
-rw-r--r--spec/migrations/drop_project_ci_cd_settings_merge_trains_enabled_spec.rb2
-rw-r--r--spec/migrations/encrypt_feature_flags_clients_tokens_spec.rb2
-rw-r--r--spec/migrations/encrypt_plaintext_attributes_on_application_settings_spec.rb2
-rw-r--r--spec/migrations/enqueue_reset_merge_status_second_run_spec.rb2
-rw-r--r--spec/migrations/enqueue_reset_merge_status_spec.rb2
-rw-r--r--spec/migrations/fill_file_store_ci_job_artifacts_spec.rb2
-rw-r--r--spec/migrations/fill_file_store_lfs_objects_spec.rb2
-rw-r--r--spec/migrations/fill_productivity_analytics_start_date_spec.rb2
-rw-r--r--spec/migrations/fill_store_uploads_spec.rb2
-rw-r--r--spec/migrations/fix_max_pages_size_spec.rb2
-rw-r--r--spec/migrations/fix_null_type_labels_spec.rb2
-rw-r--r--spec/migrations/fix_pool_repository_source_project_id_spec.rb2
-rw-r--r--spec/migrations/fix_projects_without_project_feature_spec.rb2
-rw-r--r--spec/migrations/fix_projects_without_prometheus_services_spec.rb2
-rw-r--r--spec/migrations/fix_wrong_pages_access_level_spec.rb2
-rw-r--r--spec/migrations/generate_lets_encrypt_private_key_spec.rb2
-rw-r--r--spec/migrations/generate_missing_routes_for_bots_spec.rb80
-rw-r--r--spec/migrations/insert_project_hooks_plan_limits_spec.rb2
-rw-r--r--spec/migrations/migrate_auto_dev_ops_domain_to_cluster_domain_spec.rb2
-rw-r--r--spec/migrations/migrate_bot_type_to_user_type_spec.rb2
-rw-r--r--spec/migrations/migrate_code_owner_approval_status_to_protected_branches_in_batches_spec.rb2
-rw-r--r--spec/migrations/migrate_commit_notes_mentions_to_db_spec.rb2
-rw-r--r--spec/migrations/migrate_create_commit_signature_worker_sidekiq_queue_spec.rb2
-rw-r--r--spec/migrations/migrate_discussion_id_on_promoted_epics_spec.rb2
-rw-r--r--spec/migrations/migrate_k8s_service_integration_spec.rb2
-rw-r--r--spec/migrations/migrate_legacy_managed_clusters_to_unmanaged_spec.rb2
-rw-r--r--spec/migrations/migrate_managed_clusters_with_no_token_to_unmanaged_spec.rb2
-rw-r--r--spec/migrations/migrate_merge_request_mentions_to_db_spec.rb2
-rw-r--r--spec/migrations/migrate_ops_feature_flags_scopes_target_user_ids_spec.rb2
-rw-r--r--spec/migrations/migrate_storage_migrator_sidekiq_queue_spec.rb2
-rw-r--r--spec/migrations/migrate_store_security_reports_sidekiq_queue_spec.rb2
-rw-r--r--spec/migrations/migrate_sync_security_reports_to_report_approval_rules_sidekiq_queue_spec.rb2
-rw-r--r--spec/migrations/move_limits_from_plans_spec.rb2
-rw-r--r--spec/migrations/nullify_users_role_spec.rb2
-rw-r--r--spec/migrations/populate_project_statistics_packages_size_spec.rb2
-rw-r--r--spec/migrations/populate_rule_type_on_approval_merge_request_rules_spec.rb2
-rw-r--r--spec/migrations/remove_additional_application_settings_rows_spec.rb2
-rw-r--r--spec/migrations/remove_deprecated_jenkins_service_records_spec.rb6
-rw-r--r--spec/migrations/remove_duplicate_labels_from_project_spec.rb238
-rw-r--r--spec/migrations/remove_empty_github_service_templates_spec.rb2
-rw-r--r--spec/migrations/remove_gitlab_issue_tracker_service_records_spec.rb19
-rw-r--r--spec/migrations/remove_orphaned_invited_members_spec.rb2
-rw-r--r--spec/migrations/remove_packages_deprecated_dependencies_spec.rb2
-rw-r--r--spec/migrations/remove_security_dashboard_feature_flag_spec.rb2
-rw-r--r--spec/migrations/rename_security_dashboard_feature_flag_to_instance_security_dashboard_spec.rb2
-rw-r--r--spec/migrations/save_instance_administrators_group_id_spec.rb2
-rw-r--r--spec/migrations/schedule_backfill_push_rules_id_in_projects_spec.rb2
-rw-r--r--spec/migrations/schedule_calculate_wiki_sizes_spec.rb60
-rw-r--r--spec/migrations/schedule_fill_valid_time_for_pages_domain_certificates_spec.rb2
-rw-r--r--spec/migrations/schedule_link_lfs_objects_projects_spec.rb2
-rw-r--r--spec/migrations/schedule_merge_request_assignees_migration_progress_check_spec.rb2
-rw-r--r--spec/migrations/schedule_migrate_security_scans_spec.rb2
-rw-r--r--spec/migrations/schedule_pages_metadata_migration_spec.rb2
-rw-r--r--spec/migrations/schedule_populate_merge_request_assignees_table_spec.rb2
-rw-r--r--spec/migrations/schedule_populate_project_snippet_statistics_spec.rb61
-rw-r--r--spec/migrations/schedule_populate_user_highest_roles_table_spec.rb2
-rw-r--r--spec/migrations/schedule_recalculate_project_authorizations_second_run_spec.rb2
-rw-r--r--spec/migrations/schedule_recalculate_project_authorizations_spec.rb2
-rw-r--r--spec/migrations/schedule_recalculate_project_authorizations_third_run_spec.rb2
-rw-r--r--spec/migrations/schedule_sync_issuables_state_id_spec.rb2
-rw-r--r--spec/migrations/schedule_sync_issuables_state_id_where_nil_spec.rb2
-rw-r--r--spec/migrations/schedule_update_existing_subgroup_to_match_visibility_level_of_parent_spec.rb2
-rw-r--r--spec/migrations/seed_repository_storages_weighted_spec.rb2
-rw-r--r--spec/migrations/services_remove_temporary_index_on_project_id_spec.rb2
-rw-r--r--spec/migrations/set_issue_id_for_all_versions_spec.rb2
-rw-r--r--spec/migrations/sync_issuables_state_id_spec.rb2
-rw-r--r--spec/migrations/truncate_user_fullname_spec.rb2
-rw-r--r--spec/migrations/unconfirm_wrongfully_verified_emails_spec.rb21
-rw-r--r--spec/migrations/update_application_setting_npm_package_requests_forwarding_default_spec.rb2
-rw-r--r--spec/migrations/update_fingerprint_sha256_within_keys_spec.rb2
-rw-r--r--spec/migrations/update_minimum_password_length_spec.rb2
-rw-r--r--spec/migrations/update_routes_for_lost_and_found_group_and_orphaned_projects_spec.rb2
-rw-r--r--spec/migrations/update_timestamp_softwarelicensespolicy_spec.rb2
-rw-r--r--spec/models/ability_spec.rb2
-rw-r--r--spec/models/abuse_report_spec.rb2
-rw-r--r--spec/models/alert_management/alert_assignee_spec.rb2
-rw-r--r--spec/models/alert_management/alert_spec.rb119
-rw-r--r--spec/models/alert_management/alert_user_mention_spec.rb2
-rw-r--r--spec/models/alerting/project_alerting_setting_spec.rb2
-rw-r--r--spec/models/analytics/cycle_analytics/project_stage_spec.rb2
-rw-r--r--spec/models/appearance_spec.rb2
-rw-r--r--spec/models/application_record_spec.rb9
-rw-r--r--spec/models/application_setting/term_spec.rb2
-rw-r--r--spec/models/application_setting_spec.rb2
-rw-r--r--spec/models/approval_spec.rb16
-rw-r--r--spec/models/award_emoji_spec.rb2
-rw-r--r--spec/models/aws/role_spec.rb2
-rw-r--r--spec/models/badge_spec.rb2
-rw-r--r--spec/models/badges/group_badge_spec.rb2
-rw-r--r--spec/models/badges/project_badge_spec.rb2
-rw-r--r--spec/models/blob_spec.rb2
-rw-r--r--spec/models/blob_viewer/base_spec.rb2
-rw-r--r--spec/models/blob_viewer/changelog_spec.rb2
-rw-r--r--spec/models/blob_viewer/composer_json_spec.rb2
-rw-r--r--spec/models/blob_viewer/gemspec_spec.rb2
-rw-r--r--spec/models/blob_viewer/gitlab_ci_yml_spec.rb2
-rw-r--r--spec/models/blob_viewer/go_mod_spec.rb2
-rw-r--r--spec/models/blob_viewer/license_spec.rb2
-rw-r--r--spec/models/blob_viewer/metrics_dashboard_yml_spec.rb2
-rw-r--r--spec/models/blob_viewer/package_json_spec.rb2
-rw-r--r--spec/models/blob_viewer/podspec_json_spec.rb2
-rw-r--r--spec/models/blob_viewer/podspec_spec.rb2
-rw-r--r--spec/models/blob_viewer/readme_spec.rb2
-rw-r--r--spec/models/blob_viewer/route_map_spec.rb2
-rw-r--r--spec/models/blob_viewer/server_side_spec.rb2
-rw-r--r--spec/models/board_group_recent_visit_spec.rb2
-rw-r--r--spec/models/board_project_recent_visit_spec.rb2
-rw-r--r--spec/models/board_spec.rb2
-rw-r--r--spec/models/broadcast_message_spec.rb2
-rw-r--r--spec/models/chat_name_spec.rb2
-rw-r--r--spec/models/chat_team_spec.rb2
-rw-r--r--spec/models/ci/artifact_blob_spec.rb2
-rw-r--r--spec/models/ci/bridge_spec.rb6
-rw-r--r--spec/models/ci/build_dependencies_spec.rb2
-rw-r--r--spec/models/ci/build_metadata_spec.rb31
-rw-r--r--spec/models/ci/build_need_spec.rb20
-rw-r--r--spec/models/ci/build_report_result_spec.rb2
-rw-r--r--spec/models/ci/build_runner_session_spec.rb2
-rw-r--r--spec/models/ci/build_spec.rb97
-rw-r--r--spec/models/ci/build_trace_chunk_spec.rb2
-rw-r--r--spec/models/ci/build_trace_chunks/database_spec.rb2
-rw-r--r--spec/models/ci/build_trace_chunks/fog_spec.rb2
-rw-r--r--spec/models/ci/build_trace_chunks/redis_spec.rb2
-rw-r--r--spec/models/ci/build_trace_section_name_spec.rb2
-rw-r--r--spec/models/ci/build_trace_section_spec.rb2
-rw-r--r--spec/models/ci/build_trace_spec.rb32
-rw-r--r--spec/models/ci/daily_build_group_report_result_spec.rb2
-rw-r--r--spec/models/ci/freeze_period_status_spec.rb2
-rw-r--r--spec/models/ci/group_spec.rb2
-rw-r--r--spec/models/ci/group_variable_spec.rb2
-rw-r--r--spec/models/ci/instance_variable_spec.rb17
-rw-r--r--spec/models/ci/job_artifact_spec.rb125
-rw-r--r--spec/models/ci/job_variable_spec.rb2
-rw-r--r--spec/models/ci/legacy_stage_spec.rb2
-rw-r--r--spec/models/ci/persistent_ref_spec.rb2
-rw-r--r--spec/models/ci/pipeline_config_spec.rb2
-rw-r--r--spec/models/ci/pipeline_message_spec.rb53
-rw-r--r--spec/models/ci/pipeline_schedule_spec.rb2
-rw-r--r--spec/models/ci/pipeline_schedule_variable_spec.rb2
-rw-r--r--spec/models/ci/pipeline_spec.rb255
-rw-r--r--spec/models/ci/pipeline_variable_spec.rb2
-rw-r--r--spec/models/ci/processable_spec.rb2
-rw-r--r--spec/models/ci/ref_spec.rb31
-rw-r--r--spec/models/ci/resource_group_spec.rb2
-rw-r--r--spec/models/ci/resource_spec.rb2
-rw-r--r--spec/models/ci/runner_spec.rb42
-rw-r--r--spec/models/ci/sources/pipeline_spec.rb2
-rw-r--r--spec/models/ci/stage_spec.rb4
-rw-r--r--spec/models/ci/trigger_request_spec.rb2
-rw-r--r--spec/models/ci/trigger_spec.rb2
-rw-r--r--spec/models/ci/variable_spec.rb2
-rw-r--r--spec/models/clusters/applications/cert_manager_spec.rb2
-rw-r--r--spec/models/clusters/applications/cilium_spec.rb17
-rw-r--r--spec/models/clusters/applications/crossplane_spec.rb2
-rw-r--r--spec/models/clusters/applications/elastic_stack_spec.rb16
-rw-r--r--spec/models/clusters/applications/fluentd_spec.rb2
-rw-r--r--spec/models/clusters/applications/helm_spec.rb2
-rw-r--r--spec/models/clusters/applications/ingress_spec.rb2
-rw-r--r--spec/models/clusters/applications/jupyter_spec.rb2
-rw-r--r--spec/models/clusters/applications/knative_spec.rb2
-rw-r--r--spec/models/clusters/applications/prometheus_spec.rb2
-rw-r--r--spec/models/clusters/applications/runner_spec.rb2
-rw-r--r--spec/models/clusters/cluster_spec.rb104
-rw-r--r--spec/models/clusters/clusters_hierarchy_spec.rb2
-rw-r--r--spec/models/clusters/group_spec.rb2
-rw-r--r--spec/models/clusters/platforms/kubernetes_spec.rb48
-rw-r--r--spec/models/clusters/project_spec.rb2
-rw-r--r--spec/models/clusters/providers/aws_spec.rb2
-rw-r--r--spec/models/clusters/providers/gcp_spec.rb2
-rw-r--r--spec/models/commit_collection_spec.rb14
-rw-r--r--spec/models/commit_range_spec.rb2
-rw-r--r--spec/models/commit_spec.rb13
-rw-r--r--spec/models/commit_status_spec.rb2
-rw-r--r--spec/models/commit_with_pipeline_spec.rb2
-rw-r--r--spec/models/compare_spec.rb2
-rw-r--r--spec/models/concerns/access_requestable_spec.rb2
-rw-r--r--spec/models/concerns/approvable_base_spec.rb34
-rw-r--r--spec/models/concerns/atomic_internal_id_spec.rb2
-rw-r--r--spec/models/concerns/avatarable_spec.rb2
-rw-r--r--spec/models/concerns/awardable_spec.rb2
-rw-r--r--spec/models/concerns/batch_destroy_dependent_associations_spec.rb2
-rw-r--r--spec/models/concerns/blob_language_from_git_attributes_spec.rb2
-rw-r--r--spec/models/concerns/blocks_json_serialization_spec.rb2
-rw-r--r--spec/models/concerns/bulk_insert_safe_spec.rb179
-rw-r--r--spec/models/concerns/bulk_insertable_associations_spec.rb2
-rw-r--r--spec/models/concerns/cache_markdown_field_spec.rb2
-rw-r--r--spec/models/concerns/cacheable_attributes_spec.rb2
-rw-r--r--spec/models/concerns/case_sensitivity_spec.rb2
-rw-r--r--spec/models/concerns/checksummable_spec.rb2
-rw-r--r--spec/models/concerns/chronic_duration_attribute_spec.rb8
-rw-r--r--spec/models/concerns/ci/has_ref_spec.rb2
-rw-r--r--spec/models/concerns/ci/has_status_spec.rb411
-rw-r--r--spec/models/concerns/ci/has_variable_spec.rb2
-rw-r--r--spec/models/concerns/ci/maskable_spec.rb2
-rw-r--r--spec/models/concerns/delete_with_limit_spec.rb2
-rw-r--r--spec/models/concerns/deployment_platform_spec.rb237
-rw-r--r--spec/models/concerns/deprecated_assignee_spec.rb2
-rw-r--r--spec/models/concerns/discussion_on_diff_spec.rb2
-rw-r--r--spec/models/concerns/each_batch_spec.rb2
-rw-r--r--spec/models/concerns/editable_spec.rb2
-rw-r--r--spec/models/concerns/expirable_spec.rb2
-rw-r--r--spec/models/concerns/faster_cache_keys_spec.rb2
-rw-r--r--spec/models/concerns/featurable_spec.rb2
-rw-r--r--spec/models/concerns/feature_gate_spec.rb2
-rw-r--r--spec/models/concerns/from_union_spec.rb2
-rw-r--r--spec/models/concerns/group_descendant_spec.rb2
-rw-r--r--spec/models/concerns/has_environment_scope_spec.rb2
-rw-r--r--spec/models/concerns/has_status_spec.rb411
-rw-r--r--spec/models/concerns/has_user_type_spec.rb2
-rw-r--r--spec/models/concerns/ignorable_columns_spec.rb2
-rw-r--r--spec/models/concerns/issuable_spec.rb23
-rw-r--r--spec/models/concerns/limitable_spec.rb2
-rw-r--r--spec/models/concerns/loaded_in_group_list_spec.rb2
-rw-r--r--spec/models/concerns/manual_inverse_association_spec.rb2
-rw-r--r--spec/models/concerns/mentionable_spec.rb14
-rw-r--r--spec/models/concerns/milestoneable_spec.rb2
-rw-r--r--spec/models/concerns/milestoneish_spec.rb2
-rw-r--r--spec/models/concerns/noteable_spec.rb42
-rw-r--r--spec/models/concerns/optionally_search_spec.rb2
-rw-r--r--spec/models/concerns/participable_spec.rb2
-rw-r--r--spec/models/concerns/partitioned_table_spec.rb35
-rw-r--r--spec/models/concerns/presentable_spec.rb2
-rw-r--r--spec/models/concerns/project_api_compatibility_spec.rb2
-rw-r--r--spec/models/concerns/project_features_compatibility_spec.rb2
-rw-r--r--spec/models/concerns/prometheus_adapter_spec.rb2
-rw-r--r--spec/models/concerns/protected_ref_access_spec.rb2
-rw-r--r--spec/models/concerns/reactive_caching_spec.rb36
-rw-r--r--spec/models/concerns/redactable_spec.rb2
-rw-r--r--spec/models/concerns/redis_cacheable_spec.rb2
-rw-r--r--spec/models/concerns/resolvable_discussion_spec.rb2
-rw-r--r--spec/models/concerns/resolvable_note_spec.rb2
-rw-r--r--spec/models/concerns/routable_spec.rb4
-rw-r--r--spec/models/concerns/safe_url_spec.rb2
-rw-r--r--spec/models/concerns/schedulable_spec.rb2
-rw-r--r--spec/models/concerns/sha256_attribute_spec.rb2
-rw-r--r--spec/models/concerns/sha_attribute_spec.rb2
-rw-r--r--spec/models/concerns/sortable_spec.rb2
-rw-r--r--spec/models/concerns/spammable_spec.rb2
-rw-r--r--spec/models/concerns/stepable_spec.rb2
-rw-r--r--spec/models/concerns/strip_attribute_spec.rb2
-rw-r--r--spec/models/concerns/subscribable_spec.rb2
-rw-r--r--spec/models/concerns/token_authenticatable_spec.rb10
-rw-r--r--spec/models/concerns/token_authenticatable_strategies/base_spec.rb2
-rw-r--r--spec/models/concerns/token_authenticatable_strategies/encrypted_spec.rb2
-rw-r--r--spec/models/concerns/uniquify_spec.rb2
-rw-r--r--spec/models/concerns/usage_statistics_spec.rb2
-rw-r--r--spec/models/concerns/where_composite_spec.rb2
-rw-r--r--spec/models/concerns/x509_serial_number_attribute_spec.rb2
-rw-r--r--spec/models/container_registry/event_spec.rb2
-rw-r--r--spec/models/container_repository_spec.rb2
-rw-r--r--spec/models/custom_emoji_spec.rb31
-rw-r--r--spec/models/cycle_analytics/code_spec.rb2
-rw-r--r--spec/models/cycle_analytics/issue_spec.rb2
-rw-r--r--spec/models/cycle_analytics/plan_spec.rb2
-rw-r--r--spec/models/cycle_analytics/production_spec.rb2
-rw-r--r--spec/models/cycle_analytics/project_level_spec.rb2
-rw-r--r--spec/models/cycle_analytics/review_spec.rb2
-rw-r--r--spec/models/cycle_analytics/staging_spec.rb2
-rw-r--r--spec/models/cycle_analytics/test_spec.rb2
-rw-r--r--spec/models/deploy_key_spec.rb2
-rw-r--r--spec/models/deploy_keys_project_spec.rb17
-rw-r--r--spec/models/deploy_token_spec.rb2
-rw-r--r--spec/models/deployment_cluster_spec.rb2
-rw-r--r--spec/models/deployment_merge_request_spec.rb2
-rw-r--r--spec/models/deployment_metrics_spec.rb2
-rw-r--r--spec/models/deployment_spec.rb2
-rw-r--r--spec/models/description_version_spec.rb2
-rw-r--r--spec/models/design_management/action_spec.rb2
-rw-r--r--spec/models/design_management/design_action_spec.rb2
-rw-r--r--spec/models/design_management/design_at_version_spec.rb2
-rw-r--r--spec/models/design_management/design_collection_spec.rb2
-rw-r--r--spec/models/design_management/design_spec.rb2
-rw-r--r--spec/models/design_management/repository_spec.rb2
-rw-r--r--spec/models/design_management/version_spec.rb2
-rw-r--r--spec/models/design_user_mention_spec.rb2
-rw-r--r--spec/models/dev_ops_score/metric_spec.rb2
-rw-r--r--spec/models/diff_discussion_spec.rb2
-rw-r--r--spec/models/diff_note_position_spec.rb2
-rw-r--r--spec/models/diff_note_spec.rb2
-rw-r--r--spec/models/diff_viewer/base_spec.rb2
-rw-r--r--spec/models/diff_viewer/server_side_spec.rb2
-rw-r--r--spec/models/discussion_spec.rb2
-rw-r--r--spec/models/draft_note_spec.rb2
-rw-r--r--spec/models/email_spec.rb2
-rw-r--r--spec/models/environment_spec.rb17
-rw-r--r--spec/models/environment_status_spec.rb2
-rw-r--r--spec/models/error_tracking/project_error_tracking_setting_spec.rb2
-rw-r--r--spec/models/event_collection_spec.rb51
-rw-r--r--spec/models/event_spec.rb20
-rw-r--r--spec/models/external_issue_spec.rb2
-rw-r--r--spec/models/external_pull_request_spec.rb2
-rw-r--r--spec/models/fork_network_member_spec.rb2
-rw-r--r--spec/models/fork_network_spec.rb2
-rw-r--r--spec/models/generic_commit_status_spec.rb2
-rw-r--r--spec/models/gpg_key_spec.rb2
-rw-r--r--spec/models/gpg_key_subkey_spec.rb2
-rw-r--r--spec/models/grafana_integration_spec.rb2
-rw-r--r--spec/models/group_custom_attribute_spec.rb2
-rw-r--r--spec/models/group_deploy_key_spec.rb2
-rw-r--r--spec/models/group_group_link_spec.rb2
-rw-r--r--spec/models/group_import_state_spec.rb2
-rw-r--r--spec/models/group_label_spec.rb2
-rw-r--r--spec/models/group_spec.rb229
-rw-r--r--spec/models/guest_spec.rb2
-rw-r--r--spec/models/hooks/active_hook_filter_spec.rb2
-rw-r--r--spec/models/hooks/project_hook_spec.rb2
-rw-r--r--spec/models/hooks/service_hook_spec.rb2
-rw-r--r--spec/models/hooks/system_hook_spec.rb2
-rw-r--r--spec/models/hooks/web_hook_log_spec.rb2
-rw-r--r--spec/models/hooks/web_hook_spec.rb2
-rw-r--r--spec/models/identity_spec.rb2
-rw-r--r--spec/models/import_export_upload_spec.rb2
-rw-r--r--spec/models/import_failure_spec.rb2
-rw-r--r--spec/models/incident_management/project_incident_management_setting_spec.rb40
-rw-r--r--spec/models/instance_configuration_spec.rb2
-rw-r--r--spec/models/integration_spec.rb11
-rw-r--r--spec/models/internal_id_spec.rb2
-rw-r--r--spec/models/issue/metrics_spec.rb2
-rw-r--r--spec/models/issue_assignee_spec.rb35
-rw-r--r--spec/models/issue_collection_spec.rb2
-rw-r--r--spec/models/issue_spec.rb51
-rw-r--r--spec/models/iteration_spec.rb29
-rw-r--r--spec/models/jira_import_state_spec.rb2
-rw-r--r--spec/models/key_spec.rb2
-rw-r--r--spec/models/label_link_spec.rb2
-rw-r--r--spec/models/label_note_spec.rb2
-rw-r--r--spec/models/label_priority_spec.rb2
-rw-r--r--spec/models/label_spec.rb2
-rw-r--r--spec/models/legacy_diff_discussion_spec.rb2
-rw-r--r--spec/models/lfs_download_object_spec.rb2
-rw-r--r--spec/models/lfs_file_lock_spec.rb2
-rw-r--r--spec/models/lfs_object_spec.rb2
-rw-r--r--spec/models/lfs_objects_project_spec.rb2
-rw-r--r--spec/models/license_template_spec.rb2
-rw-r--r--spec/models/list_spec.rb2
-rw-r--r--spec/models/list_user_preference_spec.rb2
-rw-r--r--spec/models/member_spec.rb24
-rw-r--r--spec/models/members/group_member_spec.rb58
-rw-r--r--spec/models/members/project_member_spec.rb2
-rw-r--r--spec/models/merge_request/metrics_spec.rb2
-rw-r--r--spec/models/merge_request_assignee_spec.rb24
-rw-r--r--spec/models/merge_request_context_commit_diff_file_spec.rb2
-rw-r--r--spec/models/merge_request_context_commit_spec.rb2
-rw-r--r--spec/models/merge_request_diff_commit_spec.rb2
-rw-r--r--spec/models/merge_request_diff_file_spec.rb2
-rw-r--r--spec/models/merge_request_diff_spec.rb115
-rw-r--r--spec/models/merge_request_spec.rb122
-rw-r--r--spec/models/metrics/dashboard/annotation_spec.rb2
-rw-r--r--spec/models/metrics/users_starred_dashboard_spec.rb2
-rw-r--r--spec/models/milestone_note_spec.rb6
-rw-r--r--spec/models/milestone_release_spec.rb2
-rw-r--r--spec/models/milestone_spec.rb2
-rw-r--r--spec/models/namespace/root_storage_size_spec.rb67
-rw-r--r--spec/models/namespace/root_storage_statistics_spec.rb47
-rw-r--r--spec/models/namespace/traversal_hierarchy_spec.rb63
-rw-r--r--spec/models/namespace_setting_spec.rb7
-rw-r--r--spec/models/namespace_spec.rb51
-rw-r--r--spec/models/network/graph_spec.rb2
-rw-r--r--spec/models/note_diff_file_spec.rb2
-rw-r--r--spec/models/note_spec.rb2
-rw-r--r--spec/models/notification_recipient_spec.rb2
-rw-r--r--spec/models/oauth_access_grant_spec.rb2
-rw-r--r--spec/models/oauth_access_token_spec.rb2
-rw-r--r--spec/models/packages/composer/metadatum_spec.rb14
-rw-r--r--spec/models/packages/conan/file_metadatum_spec.rb106
-rw-r--r--spec/models/packages/conan/metadatum_spec.rb90
-rw-r--r--spec/models/packages/dependency_link_spec.rb56
-rw-r--r--spec/models/packages/dependency_spec.rb113
-rw-r--r--spec/models/packages/go/module_spec.rb59
-rw-r--r--spec/models/packages/go/module_version_spec.rb114
-rw-r--r--spec/models/packages/maven/metadatum_spec.rb40
-rw-r--r--spec/models/packages/nuget/dependency_link_metadatum_spec.rb32
-rw-r--r--spec/models/packages/nuget/metadatum_spec.rb44
-rw-r--r--spec/models/packages/package_file_spec.rb69
-rw-r--r--spec/models/packages/package_spec.rb485
-rw-r--r--spec/models/packages/pypi/metadatum_spec.rb22
-rw-r--r--spec/models/packages/sem_ver_spec.rb42
-rw-r--r--spec/models/packages/tag_spec.rb62
-rw-r--r--spec/models/pages/lookup_path_spec.rb2
-rw-r--r--spec/models/pages/virtual_domain_spec.rb2
-rw-r--r--spec/models/pages_domain_acme_order_spec.rb2
-rw-r--r--spec/models/pages_domain_spec.rb2
-rw-r--r--spec/models/performance_monitoring/prometheus_dashboard_spec.rb16
-rw-r--r--spec/models/performance_monitoring/prometheus_metric_spec.rb2
-rw-r--r--spec/models/performance_monitoring/prometheus_panel_group_spec.rb2
-rw-r--r--spec/models/performance_monitoring/prometheus_panel_spec.rb2
-rw-r--r--spec/models/personal_access_token_spec.rb19
-rw-r--r--spec/models/personal_snippet_spec.rb2
-rw-r--r--spec/models/plan_limits_spec.rb210
-rw-r--r--spec/models/plan_spec.rb14
-rw-r--r--spec/models/pool_repository_spec.rb2
-rw-r--r--spec/models/postgresql/replication_slot_spec.rb2
-rw-r--r--spec/models/product_analytics_event_spec.rb24
-rw-r--r--spec/models/programming_language_spec.rb2
-rw-r--r--spec/models/project_authorization_spec.rb2
-rw-r--r--spec/models/project_auto_devops_spec.rb2
-rw-r--r--spec/models/project_ci_cd_setting_spec.rb2
-rw-r--r--spec/models/project_custom_attribute_spec.rb2
-rw-r--r--spec/models/project_daily_statistic_spec.rb2
-rw-r--r--spec/models/project_export_job_spec.rb2
-rw-r--r--spec/models/project_feature_spec.rb2
-rw-r--r--spec/models/project_group_link_spec.rb2
-rw-r--r--spec/models/project_import_data_spec.rb2
-rw-r--r--spec/models/project_import_state_spec.rb2
-rw-r--r--spec/models/project_label_spec.rb2
-rw-r--r--spec/models/project_metrics_setting_spec.rb2
-rw-r--r--spec/models/project_repository_spec.rb2
-rw-r--r--spec/models/project_services/alerts_service_spec.rb2
-rw-r--r--spec/models/project_services/asana_service_spec.rb2
-rw-r--r--spec/models/project_services/assembla_service_spec.rb2
-rw-r--r--spec/models/project_services/bamboo_service_spec.rb2
-rw-r--r--spec/models/project_services/bugzilla_service_spec.rb47
-rw-r--r--spec/models/project_services/buildkite_service_spec.rb2
-rw-r--r--spec/models/project_services/campfire_service_spec.rb2
-rw-r--r--spec/models/project_services/chat_message/alert_message_spec.rb2
-rw-r--r--spec/models/project_services/chat_message/base_message_spec.rb2
-rw-r--r--spec/models/project_services/chat_message/deployment_message_spec.rb2
-rw-r--r--spec/models/project_services/chat_message/issue_message_spec.rb2
-rw-r--r--spec/models/project_services/chat_message/merge_message_spec.rb2
-rw-r--r--spec/models/project_services/chat_message/note_message_spec.rb2
-rw-r--r--spec/models/project_services/chat_message/pipeline_message_spec.rb2
-rw-r--r--spec/models/project_services/chat_message/push_message_spec.rb2
-rw-r--r--spec/models/project_services/chat_message/wiki_page_message_spec.rb2
-rw-r--r--spec/models/project_services/chat_notification_service_spec.rb2
-rw-r--r--spec/models/project_services/confluence_service_spec.rb90
-rw-r--r--spec/models/project_services/custom_issue_tracker_service_spec.rb63
-rw-r--r--spec/models/project_services/data_fields_spec.rb2
-rw-r--r--spec/models/project_services/discord_service_spec.rb2
-rw-r--r--spec/models/project_services/drone_ci_service_spec.rb2
-rw-r--r--spec/models/project_services/emails_on_push_service_spec.rb2
-rw-r--r--spec/models/project_services/external_wiki_service_spec.rb2
-rw-r--r--spec/models/project_services/flowdock_service_spec.rb2
-rw-r--r--spec/models/project_services/gitlab_issue_tracker_service_spec.rb47
-rw-r--r--spec/models/project_services/hangouts_chat_service_spec.rb2
-rw-r--r--spec/models/project_services/hipchat_service_spec.rb2
-rw-r--r--spec/models/project_services/irker_service_spec.rb2
-rw-r--r--spec/models/project_services/issue_tracker_data_spec.rb2
-rw-r--r--spec/models/project_services/issue_tracker_service_spec.rb2
-rw-r--r--spec/models/project_services/jira_service_spec.rb94
-rw-r--r--spec/models/project_services/jira_tracker_data_spec.rb4
-rw-r--r--spec/models/project_services/mattermost_service_spec.rb2
-rw-r--r--spec/models/project_services/mattermost_slash_commands_service_spec.rb2
-rw-r--r--spec/models/project_services/microsoft_teams_service_spec.rb2
-rw-r--r--spec/models/project_services/open_project_service_spec.rb2
-rw-r--r--spec/models/project_services/open_project_tracker_data_spec.rb2
-rw-r--r--spec/models/project_services/packagist_service_spec.rb2
-rw-r--r--spec/models/project_services/pipelines_email_service_spec.rb2
-rw-r--r--spec/models/project_services/pivotaltracker_service_spec.rb2
-rw-r--r--spec/models/project_services/prometheus_service_spec.rb18
-rw-r--r--spec/models/project_services/pushover_service_spec.rb2
-rw-r--r--spec/models/project_services/redmine_service_spec.rb47
-rw-r--r--spec/models/project_services/slack_service_spec.rb2
-rw-r--r--spec/models/project_services/slack_slash_commands_service_spec.rb2
-rw-r--r--spec/models/project_services/teamcity_service_spec.rb2
-rw-r--r--spec/models/project_services/unify_circuit_service_spec.rb2
-rw-r--r--spec/models/project_services/webex_teams_service_spec.rb2
-rw-r--r--spec/models/project_services/youtrack_service_spec.rb47
-rw-r--r--spec/models/project_setting_spec.rb2
-rw-r--r--spec/models/project_snippet_spec.rb2
-rw-r--r--spec/models/project_spec.rb242
-rw-r--r--spec/models/project_statistics_spec.rb87
-rw-r--r--spec/models/project_team_spec.rb2
-rw-r--r--spec/models/project_wiki_spec.rb2
-rw-r--r--spec/models/prometheus_alert_event_spec.rb2
-rw-r--r--spec/models/prometheus_alert_spec.rb6
-rw-r--r--spec/models/prometheus_metric_spec.rb3
-rw-r--r--spec/models/protectable_dropdown_spec.rb2
-rw-r--r--spec/models/protected_branch/merge_access_level_spec.rb2
-rw-r--r--spec/models/protected_branch/push_access_level_spec.rb2
-rw-r--r--spec/models/protected_branch_spec.rb2
-rw-r--r--spec/models/protected_tag_spec.rb2
-rw-r--r--spec/models/push_event_payload_spec.rb2
-rw-r--r--spec/models/push_event_spec.rb2
-rw-r--r--spec/models/readme_blob_spec.rb2
-rw-r--r--spec/models/redirect_route_spec.rb2
-rw-r--r--spec/models/releases/evidence_spec.rb2
-rw-r--r--spec/models/releases/link_spec.rb2
-rw-r--r--spec/models/releases/source_spec.rb2
-rw-r--r--spec/models/remote_mirror_spec.rb2
-rw-r--r--spec/models/repository_language_spec.rb2
-rw-r--r--spec/models/repository_spec.rb55
-rw-r--r--spec/models/resource_milestone_event_spec.rb2
-rw-r--r--spec/models/resource_state_event_spec.rb2
-rw-r--r--spec/models/review_spec.rb2
-rw-r--r--spec/models/route_spec.rb2
-rw-r--r--spec/models/sent_notification_spec.rb2
-rw-r--r--spec/models/sentry_issue_spec.rb2
-rw-r--r--spec/models/serverless/domain_cluster_spec.rb2
-rw-r--r--spec/models/serverless/domain_spec.rb2
-rw-r--r--spec/models/serverless/function_spec.rb2
-rw-r--r--spec/models/service_desk_setting_spec.rb37
-rw-r--r--spec/models/service_spec.rb44
-rw-r--r--spec/models/shard_spec.rb2
-rw-r--r--spec/models/snippet_blob_spec.rb2
-rw-r--r--spec/models/snippet_input_action_collection_spec.rb2
-rw-r--r--spec/models/snippet_input_action_spec.rb16
-rw-r--r--spec/models/snippet_repository_spec.rb2
-rw-r--r--spec/models/snippet_spec.rb39
-rw-r--r--spec/models/snippet_statistics_spec.rb149
-rw-r--r--spec/models/spam_log_spec.rb2
-rw-r--r--spec/models/ssh_host_key_spec.rb2
-rw-r--r--spec/models/state_note_spec.rb54
-rw-r--r--spec/models/subscription_spec.rb2
-rw-r--r--spec/models/suggestion_spec.rb106
-rw-r--r--spec/models/system_note_metadata_spec.rb2
-rw-r--r--spec/models/term_agreement_spec.rb2
-rw-r--r--spec/models/terraform/state_spec.rb2
-rw-r--r--spec/models/todo_spec.rb2
-rw-r--r--spec/models/tree_spec.rb2
-rw-r--r--spec/models/trending_project_spec.rb2
-rw-r--r--spec/models/upload_spec.rb2
-rw-r--r--spec/models/uploads/fog_spec.rb2
-rw-r--r--spec/models/uploads/local_spec.rb2
-rw-r--r--spec/models/user_agent_detail_spec.rb2
-rw-r--r--spec/models/user_callout_spec.rb2
-rw-r--r--spec/models/user_canonical_email_spec.rb2
-rw-r--r--spec/models/user_custom_attribute_spec.rb2
-rw-r--r--spec/models/user_detail_spec.rb33
-rw-r--r--spec/models/user_highest_role_spec.rb2
-rw-r--r--spec/models/user_interacted_project_spec.rb2
-rw-r--r--spec/models/user_mentions/commit_user_mention_spec.rb2
-rw-r--r--spec/models/user_mentions/issue_user_mention_spec.rb2
-rw-r--r--spec/models/user_mentions/merge_request_user_mention_spec.rb2
-rw-r--r--spec/models/user_mentions/snippet_user_mention_spec.rb2
-rw-r--r--spec/models/user_preference_spec.rb2
-rw-r--r--spec/models/user_spec.rb125
-rw-r--r--spec/models/user_status_spec.rb2
-rw-r--r--spec/models/users_statistics_spec.rb2
-rw-r--r--spec/models/web_ide_terminal_spec.rb2
-rw-r--r--spec/models/wiki_page/meta_spec.rb2
-rw-r--r--spec/models/wiki_page/slug_spec.rb2
-rw-r--r--spec/models/wiki_page_spec.rb20
-rw-r--r--spec/models/zoom_meeting_spec.rb2
-rw-r--r--spec/policies/alert_management/alert_policy_spec.rb2
-rw-r--r--spec/policies/application_setting/term_policy_spec.rb2
-rw-r--r--spec/policies/award_emoji_policy_spec.rb2
-rw-r--r--spec/policies/base_policy_spec.rb2
-rw-r--r--spec/policies/blob_policy_spec.rb2
-rw-r--r--spec/policies/board_policy_spec.rb2
-rw-r--r--spec/policies/ci/build_policy_spec.rb2
-rw-r--r--spec/policies/ci/pipeline_policy_spec.rb2
-rw-r--r--spec/policies/ci/pipeline_schedule_policy_spec.rb2
-rw-r--r--spec/policies/ci/trigger_policy_spec.rb2
-rw-r--r--spec/policies/clusters/cluster_policy_spec.rb2
-rw-r--r--spec/policies/clusters/instance_policy_spec.rb2
-rw-r--r--spec/policies/commit_policy_spec.rb2
-rw-r--r--spec/policies/concerns/policy_actor_spec.rb2
-rw-r--r--spec/policies/deploy_key_policy_spec.rb2
-rw-r--r--spec/policies/deploy_keys_project_policy_spec.rb2
-rw-r--r--spec/policies/deploy_token_policy_spec.rb2
-rw-r--r--spec/policies/design_management/design_policy_spec.rb2
-rw-r--r--spec/policies/environment_policy_spec.rb2
-rw-r--r--spec/policies/global_policy_spec.rb20
-rw-r--r--spec/policies/group_member_policy_spec.rb2
-rw-r--r--spec/policies/group_policy_spec.rb65
-rw-r--r--spec/policies/identity_provider_policy_spec.rb2
-rw-r--r--spec/policies/issuable_policy_spec.rb2
-rw-r--r--spec/policies/issue_policy_spec.rb2
-rw-r--r--spec/policies/merge_request_policy_spec.rb3
-rw-r--r--spec/policies/metrics/dashboard/annotation_policy_spec.rb2
-rw-r--r--spec/policies/namespace/root_storage_statistics_policy_spec.rb2
-rw-r--r--spec/policies/namespace_policy_spec.rb2
-rw-r--r--spec/policies/note_policy_spec.rb2
-rw-r--r--spec/policies/packages/package_policy_spec.rb27
-rw-r--r--spec/policies/personal_snippet_policy_spec.rb2
-rw-r--r--spec/policies/project_member_policy_spec.rb33
-rw-r--r--spec/policies/project_policy_spec.rb114
-rw-r--r--spec/policies/project_snippet_policy_spec.rb2
-rw-r--r--spec/policies/project_statistics_policy_spec.rb2
-rw-r--r--spec/policies/protected_branch_policy_spec.rb2
-rw-r--r--spec/policies/releases/source_policy_spec.rb88
-rw-r--r--spec/policies/resource_label_event_policy_spec.rb2
-rw-r--r--spec/policies/todo_policy_spec.rb2
-rw-r--r--spec/policies/user_policy_spec.rb2
-rw-r--r--spec/policies/wiki_page_policy_spec.rb2
-rw-r--r--spec/presenters/alert_management/alert_presenter_spec.rb48
-rw-r--r--spec/presenters/alert_management/prometheus_alert_presenter_spec.rb68
-rw-r--r--spec/presenters/award_emoji_presenter_spec.rb2
-rw-r--r--spec/presenters/blob_presenter_spec.rb2
-rw-r--r--spec/presenters/blobs/unfold_presenter_spec.rb2
-rw-r--r--spec/presenters/ci/bridge_presenter_spec.rb2
-rw-r--r--spec/presenters/ci/build_presenter_spec.rb2
-rw-r--r--spec/presenters/ci/build_runner_presenter_spec.rb2
-rw-r--r--spec/presenters/ci/group_variable_presenter_spec.rb2
-rw-r--r--spec/presenters/ci/legacy_stage_presenter_spec.rb2
-rw-r--r--spec/presenters/ci/pipeline_presenter_spec.rb2
-rw-r--r--spec/presenters/ci/trigger_presenter_spec.rb2
-rw-r--r--spec/presenters/ci/variable_presenter_spec.rb2
-rw-r--r--spec/presenters/clusterable_presenter_spec.rb2
-rw-r--r--spec/presenters/clusters/cluster_presenter_spec.rb124
-rw-r--r--spec/presenters/commit_presenter_spec.rb2
-rw-r--r--spec/presenters/commit_status_presenter_spec.rb2
-rw-r--r--spec/presenters/dev_ops_score/metric_presenter_spec.rb2
-rw-r--r--spec/presenters/event_presenter_spec.rb2
-rw-r--r--spec/presenters/gitlab/blame_presenter_spec.rb2
-rw-r--r--spec/presenters/group_clusterable_presenter_spec.rb8
-rw-r--r--spec/presenters/group_member_presenter_spec.rb2
-rw-r--r--spec/presenters/instance_clusterable_presenter_spec.rb8
-rw-r--r--spec/presenters/issue_presenter_spec.rb2
-rw-r--r--spec/presenters/label_presenter_spec.rb2
-rw-r--r--spec/presenters/merge_request_presenter_spec.rb20
-rw-r--r--spec/presenters/milestone_presenter_spec.rb2
-rw-r--r--spec/presenters/packages/composer/packages_presenter_spec.rb78
-rw-r--r--spec/presenters/packages/conan/package_presenter_spec.rb181
-rw-r--r--spec/presenters/packages/detail/package_presenter_spec.rb98
-rw-r--r--spec/presenters/packages/npm/package_presenter_spec.rb65
-rw-r--r--spec/presenters/packages/nuget/package_metadata_presenter_spec.rb52
-rw-r--r--spec/presenters/packages/nuget/packages_metadata_presenter_spec.rb66
-rw-r--r--spec/presenters/packages/nuget/packages_versions_presenter_spec.rb14
-rw-r--r--spec/presenters/packages/nuget/search_results_presenter_spec.rb59
-rw-r--r--spec/presenters/packages/nuget/service_index_presenter_spec.rb28
-rw-r--r--spec/presenters/packages/pypi/package_presenter_spec.rb49
-rw-r--r--spec/presenters/pages_domain_presenter_spec.rb2
-rw-r--r--spec/presenters/project_clusterable_presenter_spec.rb8
-rw-r--r--spec/presenters/project_hook_presenter_spec.rb2
-rw-r--r--spec/presenters/project_member_presenter_spec.rb2
-rw-r--r--spec/presenters/project_presenter_spec.rb2
-rw-r--r--spec/presenters/projects/import_export/project_export_presenter_spec.rb2
-rw-r--r--spec/presenters/projects/prometheus/alert_presenter_spec.rb100
-rw-r--r--spec/presenters/projects/settings/deploy_keys_presenter_spec.rb2
-rw-r--r--spec/presenters/release_presenter_spec.rb34
-rw-r--r--spec/presenters/sentry_error_presenter_spec.rb2
-rw-r--r--spec/presenters/service_hook_presenter_spec.rb2
-rw-r--r--spec/presenters/snippet_blob_presenter_spec.rb40
-rw-r--r--spec/presenters/snippet_presenter_spec.rb2
-rw-r--r--spec/presenters/tree_entry_presenter_spec.rb2
-rw-r--r--spec/presenters/web_hook_log_presenter_spec.rb2
-rw-r--r--spec/rack_servers/puma_spec.rb2
-rw-r--r--spec/rack_servers/unicorn_spec.rb2
-rw-r--r--spec/requests/api/access_requests_spec.rb2
-rw-r--r--spec/requests/api/admin/ci/variables_spec.rb2
-rw-r--r--spec/requests/api/admin/instance_clusters_spec.rb461
-rw-r--r--spec/requests/api/admin/sidekiq_spec.rb2
-rw-r--r--spec/requests/api/api_guard/admin_mode_middleware_spec.rb2
-rw-r--r--spec/requests/api/api_spec.rb10
-rw-r--r--spec/requests/api/appearance_spec.rb2
-rw-r--r--spec/requests/api/applications_spec.rb11
-rw-r--r--spec/requests/api/avatar_spec.rb2
-rw-r--r--spec/requests/api/award_emoji_spec.rb2
-rw-r--r--spec/requests/api/badges_spec.rb2
-rw-r--r--spec/requests/api/boards_spec.rb2
-rw-r--r--spec/requests/api/branches_spec.rb116
-rw-r--r--spec/requests/api/broadcast_messages_spec.rb2
-rw-r--r--spec/requests/api/ci/pipeline_schedules_spec.rb522
-rw-r--r--spec/requests/api/ci/pipelines_spec.rb786
-rw-r--r--spec/requests/api/ci/runner_spec.rb2474
-rw-r--r--spec/requests/api/ci/runners_spec.rb1096
-rw-r--r--spec/requests/api/commit_statuses_spec.rb2
-rw-r--r--spec/requests/api/commits_spec.rb2
-rw-r--r--spec/requests/api/composer_packages_spec.rb302
-rw-r--r--spec/requests/api/conan_packages_spec.rb840
-rw-r--r--spec/requests/api/container_registry_event_spec.rb2
-rw-r--r--spec/requests/api/deploy_keys_spec.rb2
-rw-r--r--spec/requests/api/deploy_tokens_spec.rb2
-rw-r--r--spec/requests/api/deployments_spec.rb2
-rw-r--r--spec/requests/api/discussions_spec.rb2
-rw-r--r--spec/requests/api/doorkeeper_access_spec.rb2
-rw-r--r--spec/requests/api/environments_spec.rb2
-rw-r--r--spec/requests/api/error_tracking_spec.rb2
-rw-r--r--spec/requests/api/events_spec.rb2
-rw-r--r--spec/requests/api/features_spec.rb2
-rw-r--r--spec/requests/api/files_spec.rb24
-rw-r--r--spec/requests/api/freeze_periods_spec.rb2
-rw-r--r--spec/requests/api/go_proxy_spec.rb465
-rw-r--r--spec/requests/api/graphql/boards/board_lists_query_spec.rb2
-rw-r--r--spec/requests/api/graphql/boards/boards_query_spec.rb2
-rw-r--r--spec/requests/api/graphql/current_user/todos_query_spec.rb2
-rw-r--r--spec/requests/api/graphql/current_user_query_spec.rb2
-rw-r--r--spec/requests/api/graphql/gitlab_schema_spec.rb2
-rw-r--r--spec/requests/api/graphql/group/labels_query_spec.rb2
-rw-r--r--spec/requests/api/graphql/group/milestones_spec.rb119
-rw-r--r--spec/requests/api/graphql/group_query_spec.rb2
-rw-r--r--spec/requests/api/graphql/metadata_query_spec.rb2
-rw-r--r--spec/requests/api/graphql/metrics/dashboard/annotations_spec.rb2
-rw-r--r--spec/requests/api/graphql/metrics/dashboard_query_spec.rb8
-rw-r--r--spec/requests/api/graphql/multiplexed_queries_spec.rb2
-rw-r--r--spec/requests/api/graphql/mutations/admin/sidekiq_queues/delete_jobs_spec.rb2
-rw-r--r--spec/requests/api/graphql/mutations/alert_management/alerts/create_alert_issue_spec.rb2
-rw-r--r--spec/requests/api/graphql/mutations/alert_management/alerts/set_assignees_spec.rb2
-rw-r--r--spec/requests/api/graphql/mutations/alert_management/alerts/todo/create_spec.rb55
-rw-r--r--spec/requests/api/graphql/mutations/alert_management/alerts/update_alert_status_spec.rb2
-rw-r--r--spec/requests/api/graphql/mutations/award_emojis/add_spec.rb6
-rw-r--r--spec/requests/api/graphql/mutations/award_emojis/remove_spec.rb6
-rw-r--r--spec/requests/api/graphql/mutations/award_emojis/toggle_spec.rb6
-rw-r--r--spec/requests/api/graphql/mutations/branches/create_spec.rb2
-rw-r--r--spec/requests/api/graphql/mutations/commits/create_spec.rb2
-rw-r--r--spec/requests/api/graphql/mutations/container_expiration_policy/update_spec.rb61
-rw-r--r--spec/requests/api/graphql/mutations/design_management/delete_spec.rb2
-rw-r--r--spec/requests/api/graphql/mutations/design_management/upload_spec.rb2
-rw-r--r--spec/requests/api/graphql/mutations/discussions/toggle_resolve_spec.rb2
-rw-r--r--spec/requests/api/graphql/mutations/issues/set_confidential_spec.rb2
-rw-r--r--spec/requests/api/graphql/mutations/issues/set_due_date_spec.rb2
-rw-r--r--spec/requests/api/graphql/mutations/issues/set_locked_spec.rb55
-rw-r--r--spec/requests/api/graphql/mutations/jira_import/import_users_spec.rb2
-rw-r--r--spec/requests/api/graphql/mutations/jira_import/start_spec.rb5
-rw-r--r--spec/requests/api/graphql/mutations/merge_requests/create_spec.rb2
-rw-r--r--spec/requests/api/graphql/mutations/merge_requests/set_assignees_spec.rb2
-rw-r--r--spec/requests/api/graphql/mutations/merge_requests/set_labels_spec.rb2
-rw-r--r--spec/requests/api/graphql/mutations/merge_requests/set_locked_spec.rb2
-rw-r--r--spec/requests/api/graphql/mutations/merge_requests/set_milestone_spec.rb2
-rw-r--r--spec/requests/api/graphql/mutations/merge_requests/set_subscription_spec.rb2
-rw-r--r--spec/requests/api/graphql/mutations/merge_requests/set_wip_spec.rb24
-rw-r--r--spec/requests/api/graphql/mutations/metrics/dashboard/annotations/create_spec.rb2
-rw-r--r--spec/requests/api/graphql/mutations/metrics/dashboard/annotations/delete_spec.rb2
-rw-r--r--spec/requests/api/graphql/mutations/notes/create/diff_note_spec.rb2
-rw-r--r--spec/requests/api/graphql/mutations/notes/create/image_diff_note_spec.rb2
-rw-r--r--spec/requests/api/graphql/mutations/notes/create/note_spec.rb6
-rw-r--r--spec/requests/api/graphql/mutations/notes/destroy_spec.rb2
-rw-r--r--spec/requests/api/graphql/mutations/notes/update/image_diff_note_spec.rb2
-rw-r--r--spec/requests/api/graphql/mutations/notes/update/note_spec.rb2
-rw-r--r--spec/requests/api/graphql/mutations/snippets/create_spec.rb52
-rw-r--r--spec/requests/api/graphql/mutations/snippets/destroy_spec.rb2
-rw-r--r--spec/requests/api/graphql/mutations/snippets/mark_as_spam_spec.rb2
-rw-r--r--spec/requests/api/graphql/mutations/snippets/update_spec.rb48
-rw-r--r--spec/requests/api/graphql/mutations/todos/mark_all_done_spec.rb2
-rw-r--r--spec/requests/api/graphql/mutations/todos/mark_done_spec.rb2
-rw-r--r--spec/requests/api/graphql/mutations/todos/restore_spec.rb2
-rw-r--r--spec/requests/api/graphql/namespace/projects_spec.rb2
-rw-r--r--spec/requests/api/graphql/namespace/root_storage_statistics_spec.rb2
-rw-r--r--spec/requests/api/graphql/project/alert_management/alert/assignees_spec.rb2
-rw-r--r--spec/requests/api/graphql/project/alert_management/alert/metrics_dashboard_url_spec.rb62
-rw-r--r--spec/requests/api/graphql/project/alert_management/alert/notes_spec.rb34
-rw-r--r--spec/requests/api/graphql/project/alert_management/alert_status_counts_spec.rb2
-rw-r--r--spec/requests/api/graphql/project/alert_management/alerts_spec.rb11
-rw-r--r--spec/requests/api/graphql/project/base_service_spec.rb2
-rw-r--r--spec/requests/api/graphql/project/container_expiration_policy_spec.rb2
-rw-r--r--spec/requests/api/graphql/project/error_tracking/sentry_detailed_error_request_spec.rb2
-rw-r--r--spec/requests/api/graphql/project/error_tracking/sentry_errors_request_spec.rb2
-rw-r--r--spec/requests/api/graphql/project/grafana_integration_spec.rb2
-rw-r--r--spec/requests/api/graphql/project/issue/design_collection/version_spec.rb2
-rw-r--r--spec/requests/api/graphql/project/issue/design_collection/versions_spec.rb2
-rw-r--r--spec/requests/api/graphql/project/issue/designs/designs_spec.rb2
-rw-r--r--spec/requests/api/graphql/project/issue/designs/notes_spec.rb2
-rw-r--r--spec/requests/api/graphql/project/issue/notes_spec.rb2
-rw-r--r--spec/requests/api/graphql/project/issue_spec.rb2
-rw-r--r--spec/requests/api/graphql/project/issues_spec.rb2
-rw-r--r--spec/requests/api/graphql/project/jira_import_spec.rb2
-rw-r--r--spec/requests/api/graphql/project/jira_projects_spec.rb30
-rw-r--r--spec/requests/api/graphql/project/jira_service_spec.rb2
-rw-r--r--spec/requests/api/graphql/project/labels_query_spec.rb2
-rw-r--r--spec/requests/api/graphql/project/merge_request/diff_notes_spec.rb2
-rw-r--r--spec/requests/api/graphql/project/merge_request_spec.rb55
-rw-r--r--spec/requests/api/graphql/project/merge_requests_spec.rb2
-rw-r--r--spec/requests/api/graphql/project/packages_spec.rb69
-rw-r--r--spec/requests/api/graphql/project/pipeline_spec.rb2
-rw-r--r--spec/requests/api/graphql/project/project_statistics_spec.rb2
-rw-r--r--spec/requests/api/graphql/project/release_spec.rb422
-rw-r--r--spec/requests/api/graphql/project/releases_spec.rb284
-rw-r--r--spec/requests/api/graphql/project/repository_spec.rb2
-rw-r--r--spec/requests/api/graphql/project/tree/tree_spec.rb2
-rw-r--r--spec/requests/api/graphql/project_query_spec.rb2
-rw-r--r--spec/requests/api/graphql/query_spec.rb2
-rw-r--r--spec/requests/api/graphql/read_only_spec.rb2
-rw-r--r--spec/requests/api/graphql/tasks/task_completion_status_spec.rb2
-rw-r--r--spec/requests/api/graphql/user/group_member_query_spec.rb2
-rw-r--r--spec/requests/api/graphql/user/project_member_query_spec.rb2
-rw-r--r--spec/requests/api/graphql/user_query_spec.rb2
-rw-r--r--spec/requests/api/graphql/user_spec.rb2
-rw-r--r--spec/requests/api/graphql/users_spec.rb2
-rw-r--r--spec/requests/api/graphql_spec.rb2
-rw-r--r--spec/requests/api/group_boards_spec.rb2
-rw-r--r--spec/requests/api/group_clusters_spec.rb50
-rw-r--r--spec/requests/api/group_container_repositories_spec.rb2
-rw-r--r--spec/requests/api/group_export_spec.rb10
-rw-r--r--spec/requests/api/group_import_spec.rb3
-rw-r--r--spec/requests/api/group_labels_spec.rb2
-rw-r--r--spec/requests/api/group_milestones_spec.rb2
-rw-r--r--spec/requests/api/group_packages_spec.rb147
-rw-r--r--spec/requests/api/group_variables_spec.rb2
-rw-r--r--spec/requests/api/groups_spec.rb57
-rw-r--r--spec/requests/api/helpers_spec.rb2
-rw-r--r--spec/requests/api/import_bitbucket_server_spec.rb218
-rw-r--r--spec/requests/api/import_github_spec.rb6
-rw-r--r--spec/requests/api/internal/base_spec.rb29
-rw-r--r--spec/requests/api/internal/pages_spec.rb2
-rw-r--r--spec/requests/api/issues/get_group_issues_spec.rb2
-rw-r--r--spec/requests/api/issues/get_project_issues_spec.rb2
-rw-r--r--spec/requests/api/issues/issues_spec.rb51
-rw-r--r--spec/requests/api/issues/post_projects_issues_spec.rb2
-rw-r--r--spec/requests/api/issues/put_projects_issues_spec.rb2
-rw-r--r--spec/requests/api/jobs_spec.rb8
-rw-r--r--spec/requests/api/keys_spec.rb2
-rw-r--r--spec/requests/api/labels_spec.rb2
-rw-r--r--spec/requests/api/lint_spec.rb2
-rw-r--r--spec/requests/api/markdown_spec.rb2
-rw-r--r--spec/requests/api/maven_packages_spec.rb569
-rw-r--r--spec/requests/api/members_spec.rb63
-rw-r--r--spec/requests/api/merge_request_approvals_spec.rb84
-rw-r--r--spec/requests/api/merge_request_diffs_spec.rb2
-rw-r--r--spec/requests/api/merge_requests_spec.rb71
-rw-r--r--spec/requests/api/metrics/dashboard/annotations_spec.rb2
-rw-r--r--spec/requests/api/metrics/user_starred_dashboards_spec.rb2
-rw-r--r--spec/requests/api/namespaces_spec.rb2
-rw-r--r--spec/requests/api/notes_spec.rb2
-rw-r--r--spec/requests/api/notification_settings_spec.rb2
-rw-r--r--spec/requests/api/npm_packages_spec.rb550
-rw-r--r--spec/requests/api/nuget_packages_spec.rb482
-rw-r--r--spec/requests/api/oauth_tokens_spec.rb2
-rw-r--r--spec/requests/api/package_files_spec.rb81
-rw-r--r--spec/requests/api/pages/internal_access_spec.rb2
-rw-r--r--spec/requests/api/pages/pages_spec.rb2
-rw-r--r--spec/requests/api/pages/private_access_spec.rb2
-rw-r--r--spec/requests/api/pages/public_access_spec.rb2
-rw-r--r--spec/requests/api/pages_domains_spec.rb2
-rw-r--r--spec/requests/api/pipeline_schedules_spec.rb522
-rw-r--r--spec/requests/api/pipelines_spec.rb786
-rw-r--r--spec/requests/api/project_clusters_spec.rb55
-rw-r--r--spec/requests/api/project_container_repositories_spec.rb2
-rw-r--r--spec/requests/api/project_events_spec.rb2
-rw-r--r--spec/requests/api/project_export_spec.rb6
-rw-r--r--spec/requests/api/project_hooks_spec.rb2
-rw-r--r--spec/requests/api/project_import_spec.rb2
-rw-r--r--spec/requests/api/project_milestones_spec.rb2
-rw-r--r--spec/requests/api/project_packages_spec.rb272
-rw-r--r--spec/requests/api/project_repository_storage_moves_spec.rb2
-rw-r--r--spec/requests/api/project_snapshots_spec.rb2
-rw-r--r--spec/requests/api/project_snippets_spec.rb37
-rw-r--r--spec/requests/api/project_statistics_spec.rb2
-rw-r--r--spec/requests/api/project_templates_spec.rb2
-rw-r--r--spec/requests/api/projects_spec.rb117
-rw-r--r--spec/requests/api/protected_branches_spec.rb2
-rw-r--r--spec/requests/api/protected_tags_spec.rb2
-rw-r--r--spec/requests/api/pypi_packages_spec.rb259
-rw-r--r--spec/requests/api/release/links_spec.rb2
-rw-r--r--spec/requests/api/releases_spec.rb2
-rw-r--r--spec/requests/api/remote_mirrors_spec.rb2
-rw-r--r--spec/requests/api/repositories_spec.rb18
-rw-r--r--spec/requests/api/resource_label_events_spec.rb2
-rw-r--r--spec/requests/api/resource_milestone_events_spec.rb2
-rw-r--r--spec/requests/api/resource_state_events_spec.rb105
-rw-r--r--spec/requests/api/runner_spec.rb2375
-rw-r--r--spec/requests/api/runners_spec.rb1096
-rw-r--r--spec/requests/api/search_spec.rb2
-rw-r--r--spec/requests/api/services_spec.rb2
-rw-r--r--spec/requests/api/settings_spec.rb12
-rw-r--r--spec/requests/api/sidekiq_metrics_spec.rb2
-rw-r--r--spec/requests/api/snippets_spec.rb156
-rw-r--r--spec/requests/api/statistics_spec.rb2
-rw-r--r--spec/requests/api/submodules_spec.rb2
-rw-r--r--spec/requests/api/suggestions_spec.rb2
-rw-r--r--spec/requests/api/system_hooks_spec.rb2
-rw-r--r--spec/requests/api/tags_spec.rb2
-rw-r--r--spec/requests/api/task_completion_status_spec.rb2
-rw-r--r--spec/requests/api/templates_spec.rb2
-rw-r--r--spec/requests/api/terraform/state_spec.rb63
-rw-r--r--spec/requests/api/todos_spec.rb2
-rw-r--r--spec/requests/api/triggers_spec.rb2
-rw-r--r--spec/requests/api/user_counts_spec.rb2
-rw-r--r--spec/requests/api/users_spec.rb10
-rw-r--r--spec/requests/api/variables_spec.rb151
-rw-r--r--spec/requests/api/version_spec.rb2
-rw-r--r--spec/requests/api/wikis_spec.rb306
-rw-r--r--spec/requests/boards/lists_controller_spec.rb2
-rw-r--r--spec/requests/git_http_spec.rb2
-rw-r--r--spec/requests/groups/milestones_controller_spec.rb2
-rw-r--r--spec/requests/groups/registry/repositories_controller_spec.rb2
-rw-r--r--spec/requests/health_controller_spec.rb36
-rw-r--r--spec/requests/import/gitlab_groups_controller_spec.rb4
-rw-r--r--spec/requests/import/gitlab_projects_controller_spec.rb2
-rw-r--r--spec/requests/jwt_controller_spec.rb2
-rw-r--r--spec/requests/lfs_http_spec.rb2
-rw-r--r--spec/requests/lfs_locks_api_spec.rb2
-rw-r--r--spec/requests/oauth_tokens_spec.rb2
-rw-r--r--spec/requests/openid_connect_spec.rb27
-rw-r--r--spec/requests/product_analytics/collector_app_attack_spec.rb41
-rw-r--r--spec/requests/product_analytics/collector_app_spec.rb73
-rw-r--r--spec/requests/profiles/notifications_controller_spec.rb2
-rw-r--r--spec/requests/projects/cycle_analytics_events_spec.rb2
-rw-r--r--spec/requests/projects/incident_management/pagerduty_incidents_spec.rb36
-rw-r--r--spec/requests/projects/merge_requests/creations_spec.rb2
-rw-r--r--spec/requests/projects/merge_requests_discussions_spec.rb2
-rw-r--r--spec/requests/projects/metrics_dashboard_spec.rb85
-rw-r--r--spec/requests/projects/uploads_spec.rb2
-rw-r--r--spec/requests/rack_attack_global_spec.rb2
-rw-r--r--spec/requests/request_profiler_spec.rb2
-rw-r--r--spec/requests/self_monitoring_project_spec.rb2
-rw-r--r--spec/requests/sessions_spec.rb2
-rw-r--r--spec/requests/user_activity_spec.rb2
-rw-r--r--spec/requests/user_avatar_spec.rb2
-rw-r--r--spec/requests/user_spoofs_ip_spec.rb2
-rw-r--r--spec/routing/admin/serverless/domains_controller_routing_spec.rb2
-rw-r--r--spec/routing/admin_routing_spec.rb18
-rw-r--r--spec/routing/environments_spec.rb2
-rw-r--r--spec/routing/git_http_routing_spec.rb2
-rw-r--r--spec/routing/group_routing_spec.rb24
-rw-r--r--spec/routing/import_routing_spec.rb63
-rw-r--r--spec/routing/instance_statistics_routing_spec.rb2
-rw-r--r--spec/routing/notifications_routing_spec.rb2
-rw-r--r--spec/routing/openid_connect_spec.rb4
-rw-r--r--spec/routing/project_routing_spec.rb185
-rw-r--r--spec/routing/routing_spec.rb61
-rw-r--r--spec/routing/uploads_routing_spec.rb2
-rw-r--r--spec/rubocop/code_reuse_helpers_spec.rb38
-rw-r--r--spec/rubocop/cop/active_record_association_reload_spec.rb4
-rw-r--r--spec/rubocop/cop/api/grape_api_instance_spec.rb29
-rw-r--r--spec/rubocop/cop/api/grape_array_missing_coerce_spec.rb62
-rw-r--r--spec/rubocop/cop/avoid_break_from_strong_memoize_spec.rb2
-rw-r--r--spec/rubocop/cop/avoid_keyword_arguments_in_sidekiq_workers_spec.rb4
-rw-r--r--spec/rubocop/cop/avoid_return_from_blocks_spec.rb2
-rw-r--r--spec/rubocop/cop/avoid_route_redirect_leading_slash_spec.rb4
-rw-r--r--spec/rubocop/cop/ban_catch_throw_spec.rb4
-rw-r--r--spec/rubocop/cop/code_reuse/active_record_spec.rb16
-rw-r--r--spec/rubocop/cop/code_reuse/finder_spec.rb4
-rw-r--r--spec/rubocop/cop/code_reuse/presenter_spec.rb4
-rw-r--r--spec/rubocop/cop/code_reuse/serializer_spec.rb4
-rw-r--r--spec/rubocop/cop/code_reuse/service_class_spec.rb4
-rw-r--r--spec/rubocop/cop/code_reuse/worker_spec.rb6
-rw-r--r--spec/rubocop/cop/default_scope_spec.rb4
-rw-r--r--spec/rubocop/cop/destroy_all_spec.rb4
-rw-r--r--spec/rubocop/cop/filename_length_spec.rb2
-rw-r--r--spec/rubocop/cop/gitlab/bulk_insert_spec.rb4
-rw-r--r--spec/rubocop/cop/gitlab/change_timezone_spec.rb4
-rw-r--r--spec/rubocop/cop/gitlab/const_get_inherit_false_spec.rb4
-rw-r--r--spec/rubocop/cop/gitlab/duplicate_spec_location_spec.rb4
-rw-r--r--spec/rubocop/cop/gitlab/finder_with_find_by_spec.rb4
-rw-r--r--spec/rubocop/cop/gitlab/httparty_spec.rb4
-rw-r--r--spec/rubocop/cop/gitlab/json_spec.rb4
-rw-r--r--spec/rubocop/cop/gitlab/module_with_instance_variables_spec.rb4
-rw-r--r--spec/rubocop/cop/gitlab/predicate_memoization_spec.rb4
-rw-r--r--spec/rubocop/cop/gitlab/rails_logger_spec.rb4
-rw-r--r--spec/rubocop/cop/gitlab/union_spec.rb4
-rw-r--r--spec/rubocop/cop/graphql/authorize_types_spec.rb114
-rw-r--r--spec/rubocop/cop/graphql/descriptions_spec.rb4
-rw-r--r--spec/rubocop/cop/group_public_or_visible_to_user_spec.rb4
-rw-r--r--spec/rubocop/cop/ignored_columns_spec.rb4
-rw-r--r--spec/rubocop/cop/include_sidekiq_worker_spec.rb4
-rw-r--r--spec/rubocop/cop/inject_enterprise_edition_module_spec.rb4
-rw-r--r--spec/rubocop/cop/line_break_around_conditional_block_spec.rb4
-rw-r--r--spec/rubocop/cop/migration/add_column_with_default_spec.rb7
-rw-r--r--spec/rubocop/cop/migration/add_columns_to_wide_tables_spec.rb5
-rw-r--r--spec/rubocop/cop/migration/add_concurrent_foreign_key_spec.rb7
-rw-r--r--spec/rubocop/cop/migration/add_concurrent_index_spec.rb7
-rw-r--r--spec/rubocop/cop/migration/add_index_spec.rb7
-rw-r--r--spec/rubocop/cop/migration/add_limit_to_text_columns_spec.rb6
-rw-r--r--spec/rubocop/cop/migration/add_reference_spec.rb7
-rw-r--r--spec/rubocop/cop/migration/add_timestamps_spec.rb7
-rw-r--r--spec/rubocop/cop/migration/datetime_spec.rb7
-rw-r--r--spec/rubocop/cop/migration/drop_table_spec.rb74
-rw-r--r--spec/rubocop/cop/migration/hash_index_spec.rb7
-rw-r--r--spec/rubocop/cop/migration/prevent_strings_spec.rb6
-rw-r--r--spec/rubocop/cop/migration/remove_column_spec.rb7
-rw-r--r--spec/rubocop/cop/migration/remove_concurrent_index_spec.rb7
-rw-r--r--spec/rubocop/cop/migration/remove_index_spec.rb7
-rw-r--r--spec/rubocop/cop/migration/safer_boolean_column_spec.rb7
-rw-r--r--spec/rubocop/cop/migration/schedule_async_spec.rb2
-rw-r--r--spec/rubocop/cop/migration/timestamps_spec.rb7
-rw-r--r--spec/rubocop/cop/migration/update_column_in_batches_spec.rb2
-rw-r--r--spec/rubocop/cop/migration/with_lock_retries_disallowed_method_spec.rb7
-rw-r--r--spec/rubocop/cop/migration/with_lock_retries_with_change_spec.rb7
-rw-r--r--spec/rubocop/cop/performance/ar_count_each_spec.rb4
-rw-r--r--spec/rubocop/cop/performance/ar_exists_and_present_blank_spec.rb4
-rw-r--r--spec/rubocop/cop/performance/readlines_each_spec.rb4
-rw-r--r--spec/rubocop/cop/prefer_class_methods_over_module_spec.rb4
-rw-r--r--spec/rubocop/cop/project_path_helper_spec.rb4
-rw-r--r--spec/rubocop/cop/put_group_routes_under_scope_spec.rb4
-rw-r--r--spec/rubocop/cop/put_project_routes_under_scope_spec.rb4
-rw-r--r--spec/rubocop/cop/qa/ambiguous_page_object_name_spec.rb4
-rw-r--r--spec/rubocop/cop/qa/element_with_pattern_spec.rb4
-rw-r--r--spec/rubocop/cop/rspec/any_instance_of_spec.rb4
-rw-r--r--spec/rubocop/cop/rspec/be_success_matcher_spec.rb4
-rw-r--r--spec/rubocop/cop/rspec/env_assignment_spec.rb4
-rw-r--r--spec/rubocop/cop/rspec/factories_in_migration_specs_spec.rb4
-rw-r--r--spec/rubocop/cop/rspec/have_gitlab_http_status_spec.rb2
-rw-r--r--spec/rubocop/cop/rspec/modify_sidekiq_middleware_spec.rb3
-rw-r--r--spec/rubocop/cop/rspec/top_level_describe_path_spec.rb6
-rw-r--r--spec/rubocop/cop/ruby_interpolation_in_translation_spec.rb4
-rw-r--r--spec/rubocop/cop/safe_params_spec.rb4
-rw-r--r--spec/rubocop/cop/scalability/bulk_perform_with_context_spec.rb3
-rw-r--r--spec/rubocop/cop/scalability/cron_worker_context_spec.rb3
-rw-r--r--spec/rubocop/cop/scalability/file_uploads_spec.rb3
-rw-r--r--spec/rubocop/cop/scalability/idempotent_worker_spec.rb3
-rw-r--r--spec/rubocop/cop/sidekiq_options_queue_spec.rb4
-rw-r--r--spec/rubocop/cop/static_translation_definition_spec.rb6
-rw-r--r--spec/rubocop/migration_helpers_spec.rb2
-rw-r--r--spec/rubocop/qa_helpers_spec.rb9
-rw-r--r--spec/serializers/accessibility_error_entity_spec.rb2
-rw-r--r--spec/serializers/accessibility_reports_comparer_entity_spec.rb2
-rw-r--r--spec/serializers/accessibility_reports_comparer_serializer_spec.rb2
-rw-r--r--spec/serializers/analytics_build_entity_spec.rb2
-rw-r--r--spec/serializers/analytics_build_serializer_spec.rb2
-rw-r--r--spec/serializers/analytics_issue_entity_spec.rb2
-rw-r--r--spec/serializers/analytics_issue_serializer_spec.rb2
-rw-r--r--spec/serializers/analytics_merge_request_serializer_spec.rb2
-rw-r--r--spec/serializers/analytics_stage_serializer_spec.rb2
-rw-r--r--spec/serializers/analytics_summary_serializer_spec.rb2
-rw-r--r--spec/serializers/blob_entity_spec.rb2
-rw-r--r--spec/serializers/board_serializer_spec.rb2
-rw-r--r--spec/serializers/build_action_entity_spec.rb2
-rw-r--r--spec/serializers/build_artifact_entity_spec.rb2
-rw-r--r--spec/serializers/build_details_entity_spec.rb2
-rw-r--r--spec/serializers/build_serializer_spec.rb2
-rw-r--r--spec/serializers/build_trace_entity_spec.rb52
-rw-r--r--spec/serializers/ci/dag_job_entity_spec.rb18
-rw-r--r--spec/serializers/ci/dag_job_group_entity_spec.rb10
-rw-r--r--spec/serializers/ci/dag_pipeline_entity_spec.rb14
-rw-r--r--spec/serializers/ci/dag_pipeline_serializer_spec.rb6
-rw-r--r--spec/serializers/ci/dag_stage_entity_spec.rb6
-rw-r--r--spec/serializers/ci/daily_build_group_report_result_entity_spec.rb2
-rw-r--r--spec/serializers/ci/daily_build_group_report_result_serializer_spec.rb2
-rw-r--r--spec/serializers/ci/group_variable_entity_spec.rb16
-rw-r--r--spec/serializers/ci/variable_entity_spec.rb16
-rw-r--r--spec/serializers/cluster_application_entity_spec.rb2
-rw-r--r--spec/serializers/cluster_entity_spec.rb34
-rw-r--r--spec/serializers/cluster_serializer_spec.rb7
-rw-r--r--spec/serializers/commit_entity_spec.rb2
-rw-r--r--spec/serializers/container_repositories_serializer_spec.rb2
-rw-r--r--spec/serializers/container_repository_entity_spec.rb2
-rw-r--r--spec/serializers/container_tag_entity_spec.rb2
-rw-r--r--spec/serializers/deploy_key_entity_spec.rb43
-rw-r--r--spec/serializers/deployment_cluster_entity_spec.rb2
-rw-r--r--spec/serializers/deployment_entity_spec.rb2
-rw-r--r--spec/serializers/deployment_serializer_spec.rb2
-rw-r--r--spec/serializers/detailed_status_entity_spec.rb2
-rw-r--r--spec/serializers/diff_file_base_entity_spec.rb6
-rw-r--r--spec/serializers/diff_file_entity_spec.rb2
-rw-r--r--spec/serializers/diff_line_entity_spec.rb2
-rw-r--r--spec/serializers/diff_line_serializer_spec.rb2
-rw-r--r--spec/serializers/diff_viewer_entity_spec.rb2
-rw-r--r--spec/serializers/diffs_entity_spec.rb2
-rw-r--r--spec/serializers/diffs_metadata_entity_spec.rb2
-rw-r--r--spec/serializers/discussion_diff_file_entity_spec.rb2
-rw-r--r--spec/serializers/discussion_entity_spec.rb2
-rw-r--r--spec/serializers/entity_date_helper_spec.rb2
-rw-r--r--spec/serializers/entity_request_spec.rb2
-rw-r--r--spec/serializers/environment_entity_spec.rb2
-rw-r--r--spec/serializers/environment_serializer_spec.rb2
-rw-r--r--spec/serializers/environment_status_entity_spec.rb2
-rw-r--r--spec/serializers/evidences/evidence_entity_spec.rb2
-rw-r--r--spec/serializers/evidences/evidence_serializer_spec.rb2
-rw-r--r--spec/serializers/evidences/issue_entity_spec.rb2
-rw-r--r--spec/serializers/evidences/milestone_entity_spec.rb2
-rw-r--r--spec/serializers/evidences/project_entity_spec.rb2
-rw-r--r--spec/serializers/evidences/release_entity_spec.rb2
-rw-r--r--spec/serializers/evidences/release_serializer_spec.rb2
-rw-r--r--spec/serializers/fork_namespace_entity_spec.rb70
-rw-r--r--spec/serializers/fork_namespace_serializer_spec.rb9
-rw-r--r--spec/serializers/group_child_entity_spec.rb2
-rw-r--r--spec/serializers/group_child_serializer_spec.rb2
-rw-r--r--spec/serializers/group_variable_entity_spec.rb16
-rw-r--r--spec/serializers/import/bitbucket_provider_repo_entity_spec.rb2
-rw-r--r--spec/serializers/import/bitbucket_server_provider_repo_entity_spec.rb2
-rw-r--r--spec/serializers/import/fogbugz_provider_repo_entity_spec.rb2
-rw-r--r--spec/serializers/import/githubish_provider_repo_entity_spec.rb2
-rw-r--r--spec/serializers/import/gitlab_provider_repo_entity_spec.rb2
-rw-r--r--spec/serializers/import/provider_repo_serializer_spec.rb2
-rw-r--r--spec/serializers/issuable_sidebar_extras_entity_spec.rb2
-rw-r--r--spec/serializers/issue_board_entity_spec.rb2
-rw-r--r--spec/serializers/issue_entity_spec.rb2
-rw-r--r--spec/serializers/issue_serializer_spec.rb2
-rw-r--r--spec/serializers/job_artifact_report_entity_spec.rb2
-rw-r--r--spec/serializers/job_entity_spec.rb2
-rw-r--r--spec/serializers/label_serializer_spec.rb2
-rw-r--r--spec/serializers/lfs_file_lock_entity_spec.rb2
-rw-r--r--spec/serializers/merge_request_basic_entity_spec.rb2
-rw-r--r--spec/serializers/merge_request_diff_entity_spec.rb2
-rw-r--r--spec/serializers/merge_request_for_pipeline_entity_spec.rb2
-rw-r--r--spec/serializers/merge_request_poll_cached_widget_entity_spec.rb24
-rw-r--r--spec/serializers/merge_request_poll_widget_entity_spec.rb24
-rw-r--r--spec/serializers/merge_request_serializer_spec.rb2
-rw-r--r--spec/serializers/merge_request_sidebar_basic_entity_spec.rb2
-rw-r--r--spec/serializers/merge_request_user_entity_spec.rb2
-rw-r--r--spec/serializers/merge_request_widget_commit_entity_spec.rb2
-rw-r--r--spec/serializers/merge_request_widget_entity_spec.rb69
-rw-r--r--spec/serializers/move_to_project_entity_spec.rb2
-rw-r--r--spec/serializers/move_to_project_serializer_spec.rb2
-rw-r--r--spec/serializers/namespace_basic_entity_spec.rb2
-rw-r--r--spec/serializers/namespace_serializer_spec.rb2
-rw-r--r--spec/serializers/note_entity_spec.rb2
-rw-r--r--spec/serializers/paginated_diff_entity_spec.rb2
-rw-r--r--spec/serializers/pipeline_details_entity_spec.rb20
-rw-r--r--spec/serializers/pipeline_entity_spec.rb26
-rw-r--r--spec/serializers/pipeline_serializer_spec.rb61
-rw-r--r--spec/serializers/project_import_entity_spec.rb2
-rw-r--r--spec/serializers/project_mirror_entity_spec.rb2
-rw-r--r--spec/serializers/project_mirror_serializer_spec.rb2
-rw-r--r--spec/serializers/project_note_entity_spec.rb2
-rw-r--r--spec/serializers/project_serializer_spec.rb2
-rw-r--r--spec/serializers/prometheus_alert_entity_spec.rb2
-rw-r--r--spec/serializers/remote_mirror_entity_spec.rb2
-rw-r--r--spec/serializers/request_aware_entity_spec.rb2
-rw-r--r--spec/serializers/review_app_setup_entity_spec.rb2
-rw-r--r--spec/serializers/runner_entity_spec.rb2
-rw-r--r--spec/serializers/serverless/domain_entity_spec.rb2
-rw-r--r--spec/serializers/service_event_entity_spec.rb2
-rw-r--r--spec/serializers/service_field_entity_spec.rb8
-rw-r--r--spec/serializers/stage_entity_spec.rb2
-rw-r--r--spec/serializers/stage_serializer_spec.rb2
-rw-r--r--spec/serializers/suggestion_entity_spec.rb113
-rw-r--r--spec/serializers/test_case_entity_spec.rb2
-rw-r--r--spec/serializers/test_report_entity_spec.rb2
-rw-r--r--spec/serializers/test_report_summary_entity_spec.rb31
-rw-r--r--spec/serializers/test_reports_comparer_entity_spec.rb2
-rw-r--r--spec/serializers/test_reports_comparer_serializer_spec.rb2
-rw-r--r--spec/serializers/test_suite_comparer_entity_spec.rb2
-rw-r--r--spec/serializers/test_suite_entity_spec.rb56
-rw-r--r--spec/serializers/test_suite_summary_entity_spec.rb24
-rw-r--r--spec/serializers/trigger_variable_entity_spec.rb2
-rw-r--r--spec/serializers/user_entity_spec.rb2
-rw-r--r--spec/serializers/user_serializer_spec.rb2
-rw-r--r--spec/serializers/variable_entity_spec.rb16
-rw-r--r--spec/serializers/web_ide_terminal_entity_spec.rb2
-rw-r--r--spec/serializers/web_ide_terminal_serializer_spec.rb2
-rw-r--r--spec/services/access_token_validation_service_spec.rb2
-rw-r--r--spec/services/alert_management/alerts/todo/create_service_spec.rb84
-rw-r--r--spec/services/alert_management/alerts/update_service_spec.rb227
-rw-r--r--spec/services/alert_management/create_alert_issue_service_spec.rb47
-rw-r--r--spec/services/alert_management/process_prometheus_alert_service_spec.rb97
-rw-r--r--spec/services/alert_management/update_alert_status_service_spec.rb66
-rw-r--r--spec/services/application_settings/update_service_spec.rb2
-rw-r--r--spec/services/applications/create_service_spec.rb2
-rw-r--r--spec/services/audit_event_service_spec.rb5
-rw-r--r--spec/services/auth/container_registry_authentication_service_spec.rb2
-rw-r--r--spec/services/authorized_project_update/periodic_recalculate_service_spec.rb2
-rw-r--r--spec/services/authorized_project_update/project_create_service_spec.rb2
-rw-r--r--spec/services/authorized_project_update/project_group_link_create_service_spec.rb190
-rw-r--r--spec/services/authorized_project_update/recalculate_for_user_range_service_spec.rb2
-rw-r--r--spec/services/auto_merge/base_service_spec.rb4
-rw-r--r--spec/services/auto_merge/merge_when_pipeline_succeeds_service_spec.rb16
-rw-r--r--spec/services/auto_merge_service_spec.rb2
-rw-r--r--spec/services/award_emojis/add_service_spec.rb2
-rw-r--r--spec/services/award_emojis/collect_user_emoji_service_spec.rb2
-rw-r--r--spec/services/award_emojis/destroy_service_spec.rb2
-rw-r--r--spec/services/award_emojis/toggle_service_spec.rb2
-rw-r--r--spec/services/base_container_service_spec.rb2
-rw-r--r--spec/services/base_count_service_spec.rb2
-rw-r--r--spec/services/boards/create_service_spec.rb2
-rw-r--r--spec/services/boards/issues/create_service_spec.rb2
-rw-r--r--spec/services/boards/issues/list_service_spec.rb2
-rw-r--r--spec/services/boards/issues/move_service_spec.rb2
-rw-r--r--spec/services/boards/list_service_spec.rb2
-rw-r--r--spec/services/boards/lists/create_service_spec.rb2
-rw-r--r--spec/services/boards/lists/destroy_service_spec.rb2
-rw-r--r--spec/services/boards/lists/generate_service_spec.rb2
-rw-r--r--spec/services/boards/lists/list_service_spec.rb2
-rw-r--r--spec/services/boards/lists/move_service_spec.rb2
-rw-r--r--spec/services/boards/lists/update_service_spec.rb2
-rw-r--r--spec/services/boards/visits/create_service_spec.rb2
-rw-r--r--spec/services/branches/create_service_spec.rb2
-rw-r--r--spec/services/branches/delete_merged_service_spec.rb2
-rw-r--r--spec/services/branches/delete_service_spec.rb12
-rw-r--r--spec/services/branches/diverging_commit_counts_service_spec.rb2
-rw-r--r--spec/services/branches/validate_new_service_spec.rb2
-rw-r--r--spec/services/bulk_push_event_payload_service_spec.rb2
-rw-r--r--spec/services/chat_names/authorize_user_service_spec.rb2
-rw-r--r--spec/services/chat_names/find_user_service_spec.rb2
-rw-r--r--spec/services/ci/archive_trace_service_spec.rb2
-rw-r--r--spec/services/ci/build_report_result_service_spec.rb2
-rw-r--r--spec/services/ci/cancel_user_pipelines_service_spec.rb2
-rw-r--r--spec/services/ci/compare_accessibility_reports_service_spec.rb2
-rw-r--r--spec/services/ci/compare_test_reports_service_spec.rb2
-rw-r--r--spec/services/ci/create_cross_project_pipeline_service_spec.rb2
-rw-r--r--spec/services/ci/create_job_artifacts_service_spec.rb32
-rw-r--r--spec/services/ci/create_pipeline_service/cache_spec.rb2
-rw-r--r--spec/services/ci/create_pipeline_service/creation_errors_and_warnings_spec.rb114
-rw-r--r--spec/services/ci/create_pipeline_service/custom_config_content_spec.rb2
-rw-r--r--spec/services/ci/create_pipeline_service/needs_spec.rb2
-rw-r--r--spec/services/ci/create_pipeline_service/parameter_content_spec.rb62
-rw-r--r--spec/services/ci/create_pipeline_service/parent_child_pipeline_spec.rb2
-rw-r--r--spec/services/ci/create_pipeline_service/pre_post_stages_spec.rb2
-rw-r--r--spec/services/ci/create_pipeline_service/rules_spec.rb2
-rw-r--r--spec/services/ci/create_pipeline_service_spec.rb92
-rw-r--r--spec/services/ci/create_web_ide_terminal_service_spec.rb2
-rw-r--r--spec/services/ci/daily_build_group_report_result_service_spec.rb2
-rw-r--r--spec/services/ci/destroy_expired_job_artifacts_service_spec.rb6
-rw-r--r--spec/services/ci/destroy_pipeline_service_spec.rb2
-rw-r--r--spec/services/ci/ensure_stage_service_spec.rb2
-rw-r--r--spec/services/ci/expire_pipeline_cache_service_spec.rb2
-rw-r--r--spec/services/ci/external_pull_requests/create_pipeline_service_spec.rb2
-rw-r--r--spec/services/ci/extract_sections_from_build_trace_service_spec.rb2
-rw-r--r--spec/services/ci/find_exposed_artifacts_service_spec.rb2
-rw-r--r--spec/services/ci/generate_coverage_reports_service_spec.rb2
-rw-r--r--spec/services/ci/generate_terraform_reports_service_spec.rb25
-rw-r--r--spec/services/ci/parse_dotenv_artifact_service_spec.rb2
-rw-r--r--spec/services/ci/pipeline_bridge_status_service_spec.rb2
-rw-r--r--spec/services/ci/pipeline_processing/atomic_processing_service/status_collection_spec.rb2
-rw-r--r--spec/services/ci/pipeline_processing/atomic_processing_service_spec.rb2
-rw-r--r--spec/services/ci/pipeline_processing/legacy_processing_service_spec.rb2
-rw-r--r--spec/services/ci/pipeline_processing/shared_processing_service.rb2
-rw-r--r--spec/services/ci/pipeline_processing/shared_processing_service_tests_with_yaml.rb2
-rw-r--r--spec/services/ci/pipeline_schedule_service_spec.rb2
-rw-r--r--spec/services/ci/pipeline_trigger_service_spec.rb2
-rw-r--r--spec/services/ci/play_build_service_spec.rb2
-rw-r--r--spec/services/ci/play_manual_stage_service_spec.rb2
-rw-r--r--spec/services/ci/prepare_build_service_spec.rb2
-rw-r--r--spec/services/ci/process_build_service_spec.rb2
-rw-r--r--spec/services/ci/process_pipeline_service_spec.rb44
-rw-r--r--spec/services/ci/register_job_service_spec.rb17
-rw-r--r--spec/services/ci/resource_groups/assign_resource_from_resource_group_service_spec.rb2
-rw-r--r--spec/services/ci/retry_build_service_spec.rb29
-rw-r--r--spec/services/ci/retry_pipeline_service_spec.rb2
-rw-r--r--spec/services/ci/run_scheduled_build_service_spec.rb2
-rw-r--r--spec/services/ci/stop_environments_service_spec.rb2
-rw-r--r--spec/services/ci/unlock_artifacts_service_spec.rb97
-rw-r--r--spec/services/ci/update_build_queue_service_spec.rb2
-rw-r--r--spec/services/ci/update_instance_variables_service_spec.rb2
-rw-r--r--spec/services/ci/update_runner_service_spec.rb2
-rw-r--r--spec/services/ci/web_ide_config_service_spec.rb2
-rw-r--r--spec/services/clusters/applications/check_ingress_ip_address_service_spec.rb2
-rw-r--r--spec/services/clusters/applications/check_installation_progress_service_spec.rb2
-rw-r--r--spec/services/clusters/applications/check_uninstall_progress_service_spec.rb2
-rw-r--r--spec/services/clusters/applications/check_upgrade_progress_service_spec.rb2
-rw-r--r--spec/services/clusters/applications/create_service_spec.rb2
-rw-r--r--spec/services/clusters/applications/destroy_service_spec.rb2
-rw-r--r--spec/services/clusters/applications/install_service_spec.rb2
-rw-r--r--spec/services/clusters/applications/patch_service_spec.rb2
-rw-r--r--spec/services/clusters/applications/prometheus_config_service_spec.rb2
-rw-r--r--spec/services/clusters/applications/prometheus_health_check_service_spec.rb2
-rw-r--r--spec/services/clusters/applications/prometheus_update_service_spec.rb2
-rw-r--r--spec/services/clusters/applications/schedule_update_service_spec.rb2
-rw-r--r--spec/services/clusters/applications/uninstall_service_spec.rb2
-rw-r--r--spec/services/clusters/applications/update_service_spec.rb2
-rw-r--r--spec/services/clusters/applications/upgrade_service_spec.rb2
-rw-r--r--spec/services/clusters/aws/authorize_role_service_spec.rb2
-rw-r--r--spec/services/clusters/aws/fetch_credentials_service_spec.rb2
-rw-r--r--spec/services/clusters/aws/finalize_creation_service_spec.rb2
-rw-r--r--spec/services/clusters/aws/provision_service_spec.rb2
-rw-r--r--spec/services/clusters/aws/verify_provision_status_service_spec.rb2
-rw-r--r--spec/services/clusters/build_kubernetes_namespace_service_spec.rb2
-rw-r--r--spec/services/clusters/build_service_spec.rb2
-rw-r--r--spec/services/clusters/cleanup/app_service_spec.rb2
-rw-r--r--spec/services/clusters/cleanup/project_namespace_service_spec.rb2
-rw-r--r--spec/services/clusters/cleanup/service_account_service_spec.rb2
-rw-r--r--spec/services/clusters/create_service_spec.rb49
-rw-r--r--spec/services/clusters/destroy_service_spec.rb2
-rw-r--r--spec/services/clusters/gcp/fetch_operation_service_spec.rb2
-rw-r--r--spec/services/clusters/gcp/finalize_creation_service_spec.rb2
-rw-r--r--spec/services/clusters/gcp/provision_service_spec.rb2
-rw-r--r--spec/services/clusters/gcp/verify_provision_status_service_spec.rb2
-rw-r--r--spec/services/clusters/kubernetes/configure_istio_ingress_service_spec.rb2
-rw-r--r--spec/services/clusters/kubernetes/create_or_update_namespace_service_spec.rb2
-rw-r--r--spec/services/clusters/kubernetes/create_or_update_service_account_service_spec.rb2
-rw-r--r--spec/services/clusters/kubernetes/fetch_kubernetes_token_service_spec.rb2
-rw-r--r--spec/services/clusters/kubernetes_spec.rb2
-rw-r--r--spec/services/clusters/management/create_project_service_spec.rb2
-rw-r--r--spec/services/clusters/management/validate_management_project_permissions_service_spec.rb2
-rw-r--r--spec/services/clusters/parse_cluster_applications_artifact_service_spec.rb89
-rw-r--r--spec/services/clusters/update_service_spec.rb2
-rw-r--r--spec/services/cohorts_service_spec.rb2
-rw-r--r--spec/services/commits/cherry_pick_service_spec.rb2
-rw-r--r--spec/services/commits/commit_patch_service_spec.rb2
-rw-r--r--spec/services/commits/tag_service_spec.rb2
-rw-r--r--spec/services/compare_service_spec.rb2
-rw-r--r--spec/services/concerns/exclusive_lease_guard_spec.rb2
-rw-r--r--spec/services/concerns/merge_requests/assigns_merge_params_spec.rb2
-rw-r--r--spec/services/container_expiration_policies/update_service_spec.rb2
-rw-r--r--spec/services/container_expiration_policy_service_spec.rb2
-rw-r--r--spec/services/deploy_keys/collect_keys_service_spec.rb58
-rw-r--r--spec/services/deploy_keys/create_service_spec.rb2
-rw-r--r--spec/services/deployments/after_create_service_spec.rb2
-rw-r--r--spec/services/deployments/create_service_spec.rb2
-rw-r--r--spec/services/deployments/link_merge_requests_service_spec.rb2
-rw-r--r--spec/services/deployments/older_deployments_drop_service_spec.rb2
-rw-r--r--spec/services/deployments/update_service_spec.rb2
-rw-r--r--spec/services/design_management/delete_designs_service_spec.rb2
-rw-r--r--spec/services/design_management/design_user_notes_count_service_spec.rb2
-rw-r--r--spec/services/design_management/generate_image_versions_service_spec.rb2
-rw-r--r--spec/services/design_management/save_designs_service_spec.rb2
-rw-r--r--spec/services/discussions/capture_diff_note_position_service_spec.rb2
-rw-r--r--spec/services/discussions/capture_diff_note_positions_service_spec.rb2
-rw-r--r--spec/services/discussions/resolve_service_spec.rb2
-rw-r--r--spec/services/discussions/update_diff_position_service_spec.rb2
-rw-r--r--spec/services/draft_notes/create_service_spec.rb2
-rw-r--r--spec/services/draft_notes/destroy_service_spec.rb2
-rw-r--r--spec/services/draft_notes/publish_service_spec.rb5
-rw-r--r--spec/services/emails/confirm_service_spec.rb2
-rw-r--r--spec/services/emails/create_service_spec.rb2
-rw-r--r--spec/services/emails/destroy_service_spec.rb2
-rw-r--r--spec/services/environments/auto_stop_service_spec.rb2
-rw-r--r--spec/services/environments/reset_auto_stop_service_spec.rb2
-rw-r--r--spec/services/error_tracking/base_service_spec.rb2
-rw-r--r--spec/services/error_tracking/issue_details_service_spec.rb2
-rw-r--r--spec/services/error_tracking/issue_latest_event_service_spec.rb2
-rw-r--r--spec/services/error_tracking/issue_update_service_spec.rb2
-rw-r--r--spec/services/error_tracking/list_issues_service_spec.rb2
-rw-r--r--spec/services/error_tracking/list_projects_service_spec.rb2
-rw-r--r--spec/services/event_create_service_spec.rb106
-rw-r--r--spec/services/events/render_service_spec.rb2
-rw-r--r--spec/services/files/create_service_spec.rb2
-rw-r--r--spec/services/files/delete_service_spec.rb2
-rw-r--r--spec/services/files/multi_service_spec.rb2
-rw-r--r--spec/services/files/update_service_spec.rb2
-rw-r--r--spec/services/git/base_hooks_service_spec.rb2
-rw-r--r--spec/services/git/branch_hooks_service_spec.rb2
-rw-r--r--spec/services/git/branch_push_service_spec.rb33
-rw-r--r--spec/services/git/process_ref_changes_service_spec.rb2
-rw-r--r--spec/services/git/tag_hooks_service_spec.rb2
-rw-r--r--spec/services/git/tag_push_service_spec.rb39
-rw-r--r--spec/services/git/wiki_push_service/change_spec.rb2
-rw-r--r--spec/services/git/wiki_push_service_spec.rb10
-rw-r--r--spec/services/gpg_keys/create_service_spec.rb2
-rw-r--r--spec/services/gpg_keys/destroy_service_spec.rb15
-rw-r--r--spec/services/grafana/proxy_service_spec.rb2
-rw-r--r--spec/services/gravatar_service_spec.rb2
-rw-r--r--spec/services/groups/auto_devops_service_spec.rb2
-rw-r--r--spec/services/groups/create_service_spec.rb11
-rw-r--r--spec/services/groups/deploy_tokens/create_service_spec.rb2
-rw-r--r--spec/services/groups/deploy_tokens/destroy_service_spec.rb2
-rw-r--r--spec/services/groups/destroy_service_spec.rb2
-rw-r--r--spec/services/groups/group_links/create_service_spec.rb2
-rw-r--r--spec/services/groups/group_links/destroy_service_spec.rb2
-rw-r--r--spec/services/groups/group_links/update_service_spec.rb2
-rw-r--r--spec/services/groups/import_export/export_service_spec.rb2
-rw-r--r--spec/services/groups/import_export/import_service_spec.rb2
-rw-r--r--spec/services/groups/nested_create_service_spec.rb2
-rw-r--r--spec/services/groups/transfer_service_spec.rb2
-rw-r--r--spec/services/groups/update_service_spec.rb2
-rw-r--r--spec/services/groups/update_shared_runners_service_spec.rb230
-rw-r--r--spec/services/import/bitbucket_server_service_spec.rb113
-rw-r--r--spec/services/import/github_service_spec.rb2
-rw-r--r--spec/services/import_export_clean_up_service_spec.rb2
-rw-r--r--spec/services/incident_management/create_incident_label_service_spec.rb58
-rw-r--r--spec/services/incident_management/create_issue_service_spec.rb97
-rw-r--r--spec/services/incident_management/pager_duty/create_incident_issue_service_spec.rb104
-rw-r--r--spec/services/incident_management/pager_duty/process_webhook_service_spec.rb148
-rw-r--r--spec/services/integrations/test/project_service_spec.rb2
-rw-r--r--spec/services/issuable/bulk_update_service_spec.rb30
-rw-r--r--spec/services/issuable/clone/attributes_rewriter_spec.rb2
-rw-r--r--spec/services/issuable/clone/content_rewriter_spec.rb2
-rw-r--r--spec/services/issuable/common_system_notes_service_spec.rb18
-rw-r--r--spec/services/issuable/destroy_service_spec.rb2
-rw-r--r--spec/services/issues/build_service_spec.rb2
-rw-r--r--spec/services/issues/close_service_spec.rb2
-rw-r--r--spec/services/issues/create_service_spec.rb6
-rw-r--r--spec/services/issues/duplicate_service_spec.rb2
-rw-r--r--spec/services/issues/export_csv_service_spec.rb2
-rw-r--r--spec/services/issues/import_csv_service_spec.rb2
-rw-r--r--spec/services/issues/move_service_spec.rb47
-rw-r--r--spec/services/issues/referenced_merge_requests_service_spec.rb2
-rw-r--r--spec/services/issues/related_branches_service_spec.rb2
-rw-r--r--spec/services/issues/reopen_service_spec.rb2
-rw-r--r--spec/services/issues/reorder_service_spec.rb2
-rw-r--r--spec/services/issues/resolve_discussions_spec.rb2
-rw-r--r--spec/services/issues/update_service_spec.rb7
-rw-r--r--spec/services/issues/zoom_link_service_spec.rb2
-rw-r--r--spec/services/jira/requests/projects/list_service_spec.rb83
-rw-r--r--spec/services/jira/requests/projects_spec.rb95
-rw-r--r--spec/services/jira_import/start_import_service_spec.rb86
-rw-r--r--spec/services/jira_import/users_importer_spec.rb2
-rw-r--r--spec/services/jira_import/users_mapper_spec.rb8
-rw-r--r--spec/services/keys/create_service_spec.rb2
-rw-r--r--spec/services/keys/destroy_service_spec.rb2
-rw-r--r--spec/services/keys/last_used_service_spec.rb2
-rw-r--r--spec/services/labels/available_labels_service_spec.rb2
-rw-r--r--spec/services/labels/create_service_spec.rb2
-rw-r--r--spec/services/labels/find_or_create_service_spec.rb2
-rw-r--r--spec/services/labels/promote_service_spec.rb2
-rw-r--r--spec/services/labels/transfer_service_spec.rb2
-rw-r--r--spec/services/labels/update_service_spec.rb2
-rw-r--r--spec/services/lfs/file_transformer_spec.rb2
-rw-r--r--spec/services/lfs/lock_file_service_spec.rb2
-rw-r--r--spec/services/lfs/locks_finder_service_spec.rb2
-rw-r--r--spec/services/lfs/unlock_file_service_spec.rb2
-rw-r--r--spec/services/members/approve_access_request_service_spec.rb2
-rw-r--r--spec/services/members/create_service_spec.rb2
-rw-r--r--spec/services/members/destroy_service_spec.rb51
-rw-r--r--spec/services/members/request_access_service_spec.rb2
-rw-r--r--spec/services/members/unassign_issuables_service_spec.rb66
-rw-r--r--spec/services/members/update_service_spec.rb2
-rw-r--r--spec/services/merge_requests/add_context_service_spec.rb2
-rw-r--r--spec/services/merge_requests/add_todo_when_build_fails_service_spec.rb2
-rw-r--r--spec/services/merge_requests/after_create_service_spec.rb2
-rw-r--r--spec/services/merge_requests/approval_service_spec.rb74
-rw-r--r--spec/services/merge_requests/assign_issues_service_spec.rb2
-rw-r--r--spec/services/merge_requests/build_service_spec.rb6
-rw-r--r--spec/services/merge_requests/close_service_spec.rb2
-rw-r--r--spec/services/merge_requests/conflicts/list_service_spec.rb3
-rw-r--r--spec/services/merge_requests/conflicts/resolve_service_spec.rb2
-rw-r--r--spec/services/merge_requests/create_from_issue_service_spec.rb10
-rw-r--r--spec/services/merge_requests/create_pipeline_service_spec.rb55
-rw-r--r--spec/services/merge_requests/create_service_spec.rb12
-rw-r--r--spec/services/merge_requests/delete_non_latest_diffs_service_spec.rb2
-rw-r--r--spec/services/merge_requests/ff_merge_service_spec.rb2
-rw-r--r--spec/services/merge_requests/get_urls_service_spec.rb2
-rw-r--r--spec/services/merge_requests/link_lfs_objects_service_spec.rb2
-rw-r--r--spec/services/merge_requests/merge_orchestration_service_spec.rb2
-rw-r--r--spec/services/merge_requests/merge_service_spec.rb21
-rw-r--r--spec/services/merge_requests/merge_to_ref_service_spec.rb2
-rw-r--r--spec/services/merge_requests/mergeability_check_service_spec.rb2
-rw-r--r--spec/services/merge_requests/migrate_external_diffs_service_spec.rb2
-rw-r--r--spec/services/merge_requests/post_merge_service_spec.rb67
-rw-r--r--spec/services/merge_requests/push_options_handler_service_spec.rb2
-rw-r--r--spec/services/merge_requests/pushed_branches_service_spec.rb2
-rw-r--r--spec/services/merge_requests/rebase_service_spec.rb2
-rw-r--r--spec/services/merge_requests/refresh_service_spec.rb9
-rw-r--r--spec/services/merge_requests/reload_diffs_service_spec.rb8
-rw-r--r--spec/services/merge_requests/remove_approval_service_spec.rb46
-rw-r--r--spec/services/merge_requests/reopen_service_spec.rb2
-rw-r--r--spec/services/merge_requests/resolved_discussion_notification_service_spec.rb2
-rw-r--r--spec/services/merge_requests/squash_service_spec.rb38
-rw-r--r--spec/services/merge_requests/update_service_spec.rb7
-rw-r--r--spec/services/metrics/dashboard/annotations/create_service_spec.rb2
-rw-r--r--spec/services/metrics/dashboard/annotations/delete_service_spec.rb2
-rw-r--r--spec/services/metrics/dashboard/clone_dashboard_service_spec.rb19
-rw-r--r--spec/services/metrics/dashboard/cluster_dashboard_service_spec.rb59
-rw-r--r--spec/services/metrics/dashboard/cluster_metrics_embed_service_spec.rb72
-rw-r--r--spec/services/metrics/dashboard/custom_dashboard_service_spec.rb20
-rw-r--r--spec/services/metrics/dashboard/custom_metric_embed_service_spec.rb2
-rw-r--r--spec/services/metrics/dashboard/default_embed_service_spec.rb2
-rw-r--r--spec/services/metrics/dashboard/dynamic_embed_service_spec.rb2
-rw-r--r--spec/services/metrics/dashboard/gitlab_alert_embed_service_spec.rb2
-rw-r--r--spec/services/metrics/dashboard/grafana_metric_embed_service_spec.rb6
-rw-r--r--spec/services/metrics/dashboard/pod_dashboard_service_spec.rb20
-rw-r--r--spec/services/metrics/dashboard/self_monitoring_dashboard_service_spec.rb22
-rw-r--r--spec/services/metrics/dashboard/system_dashboard_service_spec.rb23
-rw-r--r--spec/services/metrics/dashboard/transient_embed_service_spec.rb2
-rw-r--r--spec/services/metrics/dashboard/update_dashboard_service_spec.rb2
-rw-r--r--spec/services/metrics/sample_metrics_service_spec.rb2
-rw-r--r--spec/services/metrics/users_starred_dashboards/create_service_spec.rb2
-rw-r--r--spec/services/metrics/users_starred_dashboards/delete_service_spec.rb2
-rw-r--r--spec/services/milestones/close_service_spec.rb2
-rw-r--r--spec/services/milestones/closed_issues_count_service_spec.rb2
-rw-r--r--spec/services/milestones/create_service_spec.rb2
-rw-r--r--spec/services/milestones/destroy_service_spec.rb2
-rw-r--r--spec/services/milestones/find_or_create_service_spec.rb2
-rw-r--r--spec/services/milestones/issues_count_service_spec.rb2
-rw-r--r--spec/services/milestones/promote_service_spec.rb2
-rw-r--r--spec/services/milestones/transfer_service_spec.rb2
-rw-r--r--spec/services/milestones/update_service_spec.rb2
-rw-r--r--spec/services/namespaces/check_storage_size_service_spec.rb165
-rw-r--r--spec/services/namespaces/statistics_refresher_service_spec.rb2
-rw-r--r--spec/services/note_summary_spec.rb2
-rw-r--r--spec/services/notes/build_service_spec.rb2
-rw-r--r--spec/services/notes/create_service_spec.rb4
-rw-r--r--spec/services/notes/destroy_service_spec.rb2
-rw-r--r--spec/services/notes/post_process_service_spec.rb2
-rw-r--r--spec/services/notes/quick_actions_service_spec.rb2
-rw-r--r--spec/services/notes/render_service_spec.rb2
-rw-r--r--spec/services/notes/resolve_service_spec.rb2
-rw-r--r--spec/services/notes/update_service_spec.rb41
-rw-r--r--spec/services/notification_recipients/build_service_spec.rb2
-rw-r--r--spec/services/notification_recipients/builder/default_spec.rb2
-rw-r--r--spec/services/notification_recipients/builder/new_note_spec.rb2
-rw-r--r--spec/services/notification_service_spec.rb95
-rw-r--r--spec/services/packages/composer/composer_json_service_spec.rb39
-rw-r--r--spec/services/packages/composer/create_package_service_spec.rb97
-rw-r--r--spec/services/packages/composer/version_parser_service_spec.rb31
-rw-r--r--spec/services/packages/conan/create_package_file_service_spec.rb130
-rw-r--r--spec/services/packages/conan/create_package_service_spec.rb48
-rw-r--r--spec/services/packages/conan/search_service_spec.rb74
-rw-r--r--spec/services/packages/create_dependency_service_spec.rb113
-rw-r--r--spec/services/packages/create_package_file_service_spec.rb38
-rw-r--r--spec/services/packages/maven/create_package_service_spec.rb77
-rw-r--r--spec/services/packages/maven/find_or_create_package_service_spec.rb38
-rw-r--r--spec/services/packages/npm/create_package_service_spec.rb96
-rw-r--r--spec/services/packages/npm/create_tag_service_spec.rb53
-rw-r--r--spec/services/packages/nuget/create_dependency_service_spec.rb76
-rw-r--r--spec/services/packages/nuget/create_package_service_spec.rb34
-rw-r--r--spec/services/packages/nuget/metadata_extraction_service_spec.rb106
-rw-r--r--spec/services/packages/nuget/search_service_spec.rb116
-rw-r--r--spec/services/packages/nuget/sync_metadatum_service_spec.rb57
-rw-r--r--spec/services/packages/nuget/update_package_from_metadata_service_spec.rb237
-rw-r--r--spec/services/packages/pypi/create_package_service_spec.rb83
-rw-r--r--spec/services/packages/remove_tag_service_spec.rb20
-rw-r--r--spec/services/packages/update_tags_service_spec.rb59
-rw-r--r--spec/services/pages/delete_services_spec.rb2
-rw-r--r--spec/services/pages_domains/create_acme_order_service_spec.rb2
-rw-r--r--spec/services/pages_domains/obtain_lets_encrypt_certificate_service_spec.rb2
-rw-r--r--spec/services/pages_domains/retry_acme_order_service_spec.rb2
-rw-r--r--spec/services/personal_access_tokens/create_service_spec.rb2
-rw-r--r--spec/services/personal_access_tokens/last_used_service_spec.rb47
-rw-r--r--spec/services/pod_logs/base_service_spec.rb2
-rw-r--r--spec/services/pod_logs/elasticsearch_service_spec.rb2
-rw-r--r--spec/services/pod_logs/kubernetes_service_spec.rb2
-rw-r--r--spec/services/post_receive_service_spec.rb37
-rw-r--r--spec/services/preview_markdown_service_spec.rb2
-rw-r--r--spec/services/projects/after_import_service_spec.rb22
-rw-r--r--spec/services/projects/after_rename_service_spec.rb2
-rw-r--r--spec/services/projects/alerting/notify_service_spec.rb110
-rw-r--r--spec/services/projects/auto_devops/disable_service_spec.rb2
-rw-r--r--spec/services/projects/autocomplete_service_spec.rb2
-rw-r--r--spec/services/projects/batch_open_issues_count_service_spec.rb2
-rw-r--r--spec/services/projects/cleanup_service_spec.rb2
-rw-r--r--spec/services/projects/container_repository/cleanup_tags_service_spec.rb2
-rw-r--r--spec/services/projects/container_repository/delete_tags_service_spec.rb118
-rw-r--r--spec/services/projects/container_repository/destroy_service_spec.rb2
-rw-r--r--spec/services/projects/count_service_spec.rb2
-rw-r--r--spec/services/projects/create_from_template_service_spec.rb2
-rw-r--r--spec/services/projects/create_service_spec.rb61
-rw-r--r--spec/services/projects/deploy_tokens/create_service_spec.rb2
-rw-r--r--spec/services/projects/deploy_tokens/destroy_service_spec.rb2
-rw-r--r--spec/services/projects/destroy_rollback_service_spec.rb2
-rw-r--r--spec/services/projects/destroy_service_spec.rb2
-rw-r--r--spec/services/projects/detect_repository_languages_service_spec.rb2
-rw-r--r--spec/services/projects/download_service_spec.rb2
-rw-r--r--spec/services/projects/enable_deploy_key_service_spec.rb2
-rw-r--r--spec/services/projects/fetch_statistics_increment_service_spec.rb2
-rw-r--r--spec/services/projects/fork_service_spec.rb6
-rw-r--r--spec/services/projects/forks_count_service_spec.rb2
-rw-r--r--spec/services/projects/git_deduplication_service_spec.rb4
-rw-r--r--spec/services/projects/gitlab_projects_import_service_spec.rb2
-rw-r--r--spec/services/projects/group_links/create_service_spec.rb50
-rw-r--r--spec/services/projects/group_links/destroy_service_spec.rb2
-rw-r--r--spec/services/projects/group_links/update_service_spec.rb2
-rw-r--r--spec/services/projects/hashed_storage/base_attachment_service_spec.rb2
-rw-r--r--spec/services/projects/hashed_storage/migrate_attachments_service_spec.rb2
-rw-r--r--spec/services/projects/hashed_storage/migrate_repository_service_spec.rb2
-rw-r--r--spec/services/projects/hashed_storage/migration_service_spec.rb2
-rw-r--r--spec/services/projects/hashed_storage/rollback_attachments_service_spec.rb2
-rw-r--r--spec/services/projects/hashed_storage/rollback_repository_service_spec.rb2
-rw-r--r--spec/services/projects/hashed_storage/rollback_service_spec.rb2
-rw-r--r--spec/services/projects/housekeeping_service_spec.rb2
-rw-r--r--spec/services/projects/import_error_filter_spec.rb2
-rw-r--r--spec/services/projects/import_export/export_service_spec.rb2
-rw-r--r--spec/services/projects/import_service_spec.rb2
-rw-r--r--spec/services/projects/lfs_pointers/lfs_download_link_list_service_spec.rb2
-rw-r--r--spec/services/projects/lfs_pointers/lfs_download_service_spec.rb2
-rw-r--r--spec/services/projects/lfs_pointers/lfs_import_service_spec.rb2
-rw-r--r--spec/services/projects/lfs_pointers/lfs_link_service_spec.rb2
-rw-r--r--spec/services/projects/lfs_pointers/lfs_object_download_list_service_spec.rb2
-rw-r--r--spec/services/projects/move_access_service_spec.rb2
-rw-r--r--spec/services/projects/move_deploy_keys_projects_service_spec.rb2
-rw-r--r--spec/services/projects/move_forks_service_spec.rb2
-rw-r--r--spec/services/projects/move_lfs_objects_projects_service_spec.rb2
-rw-r--r--spec/services/projects/move_notification_settings_service_spec.rb2
-rw-r--r--spec/services/projects/move_project_authorizations_service_spec.rb2
-rw-r--r--spec/services/projects/move_project_group_links_service_spec.rb2
-rw-r--r--spec/services/projects/move_project_members_service_spec.rb2
-rw-r--r--spec/services/projects/move_users_star_projects_service_spec.rb2
-rw-r--r--spec/services/projects/open_issues_count_service_spec.rb2
-rw-r--r--spec/services/projects/open_merge_requests_count_service_spec.rb2
-rw-r--r--spec/services/projects/operations/update_service_spec.rb2
-rw-r--r--spec/services/projects/overwrite_project_service_spec.rb2
-rw-r--r--spec/services/projects/participants_service_spec.rb2
-rw-r--r--spec/services/projects/prometheus/alerts/create_events_service_spec.rb312
-rw-r--r--spec/services/projects/prometheus/alerts/create_service_spec.rb2
-rw-r--r--spec/services/projects/prometheus/alerts/destroy_service_spec.rb2
-rw-r--r--spec/services/projects/prometheus/alerts/notify_service_spec.rb127
-rw-r--r--spec/services/projects/prometheus/alerts/update_service_spec.rb2
-rw-r--r--spec/services/projects/prometheus/metrics/destroy_service_spec.rb2
-rw-r--r--spec/services/projects/prometheus/metrics/update_service_spec.rb2
-rw-r--r--spec/services/projects/propagate_service_template_spec.rb2
-rw-r--r--spec/services/projects/protect_default_branch_service_spec.rb2
-rw-r--r--spec/services/projects/repository_languages_service_spec.rb2
-rw-r--r--spec/services/projects/transfer_service_spec.rb2
-rw-r--r--spec/services/projects/unlink_fork_service_spec.rb5
-rw-r--r--spec/services/projects/update_pages_configuration_service_spec.rb2
-rw-r--r--spec/services/projects/update_pages_service_spec.rb2
-rw-r--r--spec/services/projects/update_remote_mirror_service_spec.rb2
-rw-r--r--spec/services/projects/update_repository_storage_service_spec.rb38
-rw-r--r--spec/services/projects/update_service_spec.rb2
-rw-r--r--spec/services/projects/update_statistics_service_spec.rb2
-rw-r--r--spec/services/prometheus/create_default_alerts_service_spec.rb2
-rw-r--r--spec/services/prometheus/proxy_service_spec.rb2
-rw-r--r--spec/services/prometheus/proxy_variable_substitution_service_spec.rb2
-rw-r--r--spec/services/protected_branches/create_service_spec.rb2
-rw-r--r--spec/services/protected_branches/destroy_service_spec.rb2
-rw-r--r--spec/services/protected_branches/update_service_spec.rb2
-rw-r--r--spec/services/protected_tags/create_service_spec.rb2
-rw-r--r--spec/services/protected_tags/destroy_service_spec.rb2
-rw-r--r--spec/services/protected_tags/update_service_spec.rb2
-rw-r--r--spec/services/push_event_payload_service_spec.rb2
-rw-r--r--spec/services/quick_actions/interpret_service_spec.rb4
-rw-r--r--spec/services/quick_actions/target_service_spec.rb2
-rw-r--r--spec/services/releases/create_evidence_service_spec.rb2
-rw-r--r--spec/services/releases/create_service_spec.rb2
-rw-r--r--spec/services/releases/destroy_service_spec.rb2
-rw-r--r--spec/services/releases/update_service_spec.rb2
-rw-r--r--spec/services/repositories/destroy_rollback_service_spec.rb2
-rw-r--r--spec/services/repositories/destroy_service_spec.rb17
-rw-r--r--spec/services/repositories/shell_destroy_service_spec.rb2
-rw-r--r--spec/services/repository_archive_clean_up_service_spec.rb2
-rw-r--r--spec/services/reset_project_cache_service_spec.rb2
-rw-r--r--spec/services/resource_access_tokens/create_service_spec.rb23
-rw-r--r--spec/services/resource_access_tokens/revoke_service_spec.rb2
-rw-r--r--spec/services/resource_events/change_labels_service_spec.rb2
-rw-r--r--spec/services/resource_events/change_milestone_service_spec.rb2
-rw-r--r--spec/services/resource_events/change_state_service_spec.rb91
-rw-r--r--spec/services/resource_events/merge_into_notes_service_spec.rb4
-rw-r--r--spec/services/resource_events/synthetic_label_notes_builder_service_spec.rb2
-rw-r--r--spec/services/resource_events/synthetic_milestone_notes_builder_service_spec.rb2
-rw-r--r--spec/services/search/global_service_spec.rb2
-rw-r--r--spec/services/search/group_service_spec.rb2
-rw-r--r--spec/services/search/snippet_service_spec.rb2
-rw-r--r--spec/services/search_service_spec.rb2
-rw-r--r--spec/services/serverless/associate_domain_service_spec.rb2
-rw-r--r--spec/services/service_desk_settings/update_service_spec.rb46
-rw-r--r--spec/services/service_response_spec.rb2
-rw-r--r--spec/services/snippets/bulk_destroy_service_spec.rb2
-rw-r--r--spec/services/snippets/count_service_spec.rb2
-rw-r--r--spec/services/snippets/create_service_spec.rb54
-rw-r--r--spec/services/snippets/destroy_service_spec.rb28
-rw-r--r--spec/services/snippets/repository_validation_service_spec.rb2
-rw-r--r--spec/services/snippets/update_service_spec.rb256
-rw-r--r--spec/services/snippets/update_statistics_service_spec.rb86
-rw-r--r--spec/services/spam/akismet_service_spec.rb2
-rw-r--r--spec/services/spam/ham_service_spec.rb2
-rw-r--r--spec/services/spam/mark_as_spam_service_spec.rb2
-rw-r--r--spec/services/spam/spam_action_service_spec.rb2
-rw-r--r--spec/services/spam/spam_verdict_service_spec.rb31
-rw-r--r--spec/services/submit_usage_ping_service_spec.rb2
-rw-r--r--spec/services/submodules/update_service_spec.rb2
-rw-r--r--spec/services/suggestions/apply_service_spec.rb2
-rw-r--r--spec/services/suggestions/create_service_spec.rb2
-rw-r--r--spec/services/suggestions/outdate_service_spec.rb2
-rw-r--r--spec/services/system_hooks_service_spec.rb2
-rw-r--r--spec/services/system_note_service_spec.rb46
-rw-r--r--spec/services/system_notes/alert_management_service_spec.rb35
-rw-r--r--spec/services/system_notes/base_service_spec.rb2
-rw-r--r--spec/services/system_notes/commit_service_spec.rb2
-rw-r--r--spec/services/system_notes/design_management_service_spec.rb2
-rw-r--r--spec/services/system_notes/issuables_service_spec.rb64
-rw-r--r--spec/services/system_notes/merge_requests_service_spec.rb20
-rw-r--r--spec/services/system_notes/time_tracking_service_spec.rb2
-rw-r--r--spec/services/system_notes/zoom_service_spec.rb2
-rw-r--r--spec/services/tags/create_service_spec.rb2
-rw-r--r--spec/services/tags/destroy_service_spec.rb12
-rw-r--r--spec/services/task_list_toggle_service_spec.rb2
-rw-r--r--spec/services/terraform/remote_state_handler_spec.rb123
-rw-r--r--spec/services/test_hooks/project_service_spec.rb2
-rw-r--r--spec/services/test_hooks/system_service_spec.rb2
-rw-r--r--spec/services/todo_service_spec.rb2
-rw-r--r--spec/services/todos/destroy/confidential_issue_service_spec.rb2
-rw-r--r--spec/services/todos/destroy/entity_leave_service_spec.rb2
-rw-r--r--spec/services/todos/destroy/group_private_service_spec.rb2
-rw-r--r--spec/services/todos/destroy/private_features_service_spec.rb2
-rw-r--r--spec/services/todos/destroy/project_private_service_spec.rb2
-rw-r--r--spec/services/update_container_registry_info_service_spec.rb115
-rw-r--r--spec/services/update_merge_request_metrics_service_spec.rb2
-rw-r--r--spec/services/upload_service_spec.rb2
-rw-r--r--spec/services/user_project_access_changed_service_spec.rb2
-rw-r--r--spec/services/users/activity_service_spec.rb2
-rw-r--r--spec/services/users/block_service_spec.rb2
-rw-r--r--spec/services/users/build_service_spec.rb2
-rw-r--r--spec/services/users/create_service_spec.rb2
-rw-r--r--spec/services/users/destroy_service_spec.rb2
-rw-r--r--spec/services/users/keys_count_service_spec.rb2
-rw-r--r--spec/services/users/last_push_event_service_spec.rb2
-rw-r--r--spec/services/users/migrate_to_ghost_user_service_spec.rb2
-rw-r--r--spec/services/users/refresh_authorized_projects_service_spec.rb2
-rw-r--r--spec/services/users/repair_ldap_blocked_service_spec.rb2
-rw-r--r--spec/services/users/respond_to_terms_service_spec.rb2
-rw-r--r--spec/services/users/set_status_service_spec.rb2
-rw-r--r--spec/services/users/signup_service_spec.rb2
-rw-r--r--spec/services/users/update_canonical_email_service_spec.rb2
-rw-r--r--spec/services/users/update_highest_member_role_service_spec.rb2
-rw-r--r--spec/services/users/update_service_spec.rb2
-rw-r--r--spec/services/verify_pages_domain_service_spec.rb2
-rw-r--r--spec/services/web_hook_service_spec.rb2
-rw-r--r--spec/services/wiki_pages/base_service_spec.rb2
-rw-r--r--spec/services/wiki_pages/create_service_spec.rb2
-rw-r--r--spec/services/wiki_pages/destroy_service_spec.rb2
-rw-r--r--spec/services/wiki_pages/event_create_service_spec.rb17
-rw-r--r--spec/services/wiki_pages/update_service_spec.rb2
-rw-r--r--spec/services/wikis/create_attachment_service_spec.rb2
-rw-r--r--spec/services/x509_certificate_revoke_service_spec.rb2
-rw-r--r--spec/sidekiq/cron/job_gem_dependency_spec.rb2
-rw-r--r--spec/simplecov_env.rb53
-rw-r--r--spec/spec_helper.rb6
-rw-r--r--spec/support/capybara.rb22
-rw-r--r--spec/support/controllers/project_import_rate_limiter_shared_examples.rb2
-rw-r--r--spec/support/helpers/expect_offense.rb22
-rw-r--r--spec/support/helpers/fast_rails_root.rb10
-rw-r--r--spec/support/helpers/filtered_search_helpers.rb5
-rw-r--r--spec/support/helpers/jira_service_helper.rb3
-rw-r--r--spec/support/helpers/metrics_dashboard_helpers.rb14
-rw-r--r--spec/support/helpers/notification_helpers.rb12
-rw-r--r--spec/support/helpers/packages_manager_api_spec_helper.rb46
-rw-r--r--spec/support/helpers/partitioning_helpers.rb46
-rw-r--r--spec/support/helpers/rack_attack_spec_helpers.rb12
-rw-r--r--spec/support/helpers/reference_parser_helpers.rb4
-rw-r--r--spec/support/helpers/snippet_helpers.rb14
-rw-r--r--spec/support/helpers/stub_configuration.rb8
-rw-r--r--spec/support/helpers/stub_object_storage.rb26
-rw-r--r--spec/support/helpers/test_env.rb30
-rw-r--r--spec/support/helpers/trigger_helpers.rb5
-rw-r--r--spec/support/helpers/usage_data_helpers.rb12
-rw-r--r--spec/support/matchers/background_migrations_matchers.rb3
-rw-r--r--spec/support/matchers/jsonb_matchers.rb24
-rw-r--r--spec/support/rspec.rb6
-rw-r--r--spec/support/services/clusters/create_service_shared.rb9
-rw-r--r--spec/support/services/issuable_create_service_slash_commands_shared_examples.rb73
-rw-r--r--spec/support/services/issuable_description_quick_actions_shared_examples.rb62
-rw-r--r--spec/support/shared_contexts/cache_allowed_users_in_namespace_shared_context.rb2
-rw-r--r--spec/support/shared_contexts/design_management_shared_contexts.rb2
-rw-r--r--spec/support/shared_contexts/features/error_tracking_shared_context.rb2
-rw-r--r--spec/support/shared_contexts/finders/merge_requests_finder_shared_contexts.rb10
-rw-r--r--spec/support/shared_contexts/issuable/merge_request_shared_context.rb2
-rw-r--r--spec/support/shared_contexts/issuable/project_shared_context.rb2
-rw-r--r--spec/support/shared_contexts/navbar_structure_context.rb1
-rw-r--r--spec/support/shared_contexts/policies/group_policy_shared_context.rb3
-rw-r--r--spec/support/shared_contexts/presenters/nuget_shared_context.rb41
-rw-r--r--spec/support/shared_contexts/project_service_jira_context.rb15
-rw-r--r--spec/support/shared_contexts/project_service_shared_context.rb2
-rw-r--r--spec/support/shared_contexts/prometheus/alert_shared_context.rb76
-rw-r--r--spec/support/shared_contexts/requests/api/graphql/jira_import/jira_projects_context.rb70
-rw-r--r--spec/support/shared_contexts/sentry_error_tracking_shared_context.rb2
-rw-r--r--spec/support/shared_contexts/services_shared_context.rb2
-rw-r--r--spec/support/shared_contexts/spam_constants.rb2
-rw-r--r--spec/support/shared_examples/controllers/import_controller_new_import_ui_shared_examples.rb36
-rw-r--r--spec/support/shared_examples/controllers/import_controller_status_shared_examples.rb33
-rw-r--r--spec/support/shared_examples/controllers/known_sign_in_shared_examples.rb98
-rw-r--r--spec/support/shared_examples/controllers/metrics/dashboard/prometheus_api_proxy_shared_examples.rb147
-rw-r--r--spec/support/shared_examples/controllers/metrics_dashboard_shared_examples.rb27
-rw-r--r--spec/support/shared_examples/controllers/namespace_storage_limit_alert_shared_examples.rb53
-rw-r--r--spec/support/shared_examples/controllers/snippet_blob_shared_examples.rb49
-rw-r--r--spec/support/shared_examples/controllers/snippets_sort_order_shared_examples.rb41
-rw-r--r--spec/support/shared_examples/controllers/unique_visits_shared_examples.rb29
-rw-r--r--spec/support/shared_examples/controllers/wiki_actions_shared_examples.rb111
-rw-r--r--spec/support/shared_examples/create_alert_issue_shared_examples.rb27
-rw-r--r--spec/support/shared_examples/features/discussion_comments_shared_example.rb2
-rw-r--r--spec/support/shared_examples/features/editable_merge_request_shared_examples.rb8
-rw-r--r--spec/support/shared_examples/features/error_tracking_shared_example.rb6
-rw-r--r--spec/support/shared_examples/features/master_manages_access_requests_shared_example.rb7
-rw-r--r--spec/support/shared_examples/graphql/jira_import/jira_import_resolver_shared_examples.rb4
-rw-r--r--spec/support/shared_examples/graphql/mutation_shared_examples.rb14
-rw-r--r--spec/support/shared_examples/graphql/projects/services_resolver_shared_examples.rb4
-rw-r--r--spec/support/shared_examples/graphql/resolves_issuable_shared_examples.rb41
-rw-r--r--spec/support/shared_examples/helm_commands_shared_examples.rb4
-rw-r--r--spec/support/shared_examples/lib/gitlab/background_migration/mentions_migration_shared_examples.rb6
-rw-r--r--spec/support/shared_examples/lib/gitlab/cycle_analytics/base_stage_shared_examples.rb6
-rw-r--r--spec/support/shared_examples/lib/gitlab/cycle_analytics/default_query_config_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/lib/gitlab/cycle_analytics/event_shared_examples.rb1
-rw-r--r--spec/support/shared_examples/lib/gitlab/diff_file_collections_shared_examples.rb66
-rw-r--r--spec/support/shared_examples/lib/gitlab/gl_repository_shared_examples.rb10
-rw-r--r--spec/support/shared_examples/lib/gitlab/import/stuck_import_job_workers_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/lib/gitlab/jira_import/base_importer_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/lib/wikis_api_examples.rb174
-rw-r--r--spec/support/shared_examples/models/cluster_application_status_shared_examples.rb20
-rw-r--r--spec/support/shared_examples/models/cluster_application_version_shared_examples.rb28
-rw-r--r--spec/support/shared_examples/models/concerns/bulk_insert_safe_shared_examples.rb10
-rw-r--r--spec/support/shared_examples/models/jira_import_state_shared_examples.rb10
-rw-r--r--spec/support/shared_examples/models/note_access_check_shared_examples.rb4
-rw-r--r--spec/support/shared_examples/models/services_fields_shared_examples.rb31
-rw-r--r--spec/support/shared_examples/models/synthetic_note_shared_examples.rb17
-rw-r--r--spec/support/shared_examples/namespaces/hierarchy_examples.rb21
-rw-r--r--spec/support/shared_examples/policies/namespace_policy_shared_examples.rb34
-rw-r--r--spec/support/shared_examples/policies/project_policy_shared_examples.rb28
-rw-r--r--spec/support/shared_examples/quick_actions/issue/create_merge_request_quick_action_shared_examples.rb4
-rw-r--r--spec/support/shared_examples/quick_actions/merge_request/merge_quick_action_shared_examples.rb91
-rw-r--r--spec/support/shared_examples/requests/api/composer_packages_shared_examples.rb138
-rw-r--r--spec/support/shared_examples/requests/api/graphql/projects/services_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/requests/api/notes_shared_examples.rb10
-rw-r--r--spec/support/shared_examples/requests/api/nuget_packages_shared_examples.rb408
-rw-r--r--spec/support/shared_examples/requests/api/packages_shared_examples.rb43
-rw-r--r--spec/support/shared_examples/requests/api/packages_tags_shared_examples.rb185
-rw-r--r--spec/support/shared_examples/requests/api/pypi_packages_shared_examples.rb152
-rw-r--r--spec/support/shared_examples/requests/api/resource_milestone_events_api_shared_examples.rb37
-rw-r--r--spec/support/shared_examples/requests/api/snippets_shared_examples.rb79
-rw-r--r--spec/support/shared_examples/requests/snippet_shared_examples.rb24
-rw-r--r--spec/support/shared_examples/resource_events.rb26
-rw-r--r--spec/support/shared_examples/routing/resource_routing_shared_examples.rb73
-rw-r--r--spec/support/shared_examples/routing/wiki_routing_shared_examples.rb20
-rw-r--r--spec/support/shared_examples/services/alert_management_shared_examples.rb41
-rw-r--r--spec/support/shared_examples/services/clusters/parse_cluster_applications_artifact_shared_examples.rb89
-rw-r--r--spec/support/shared_examples/services/common_system_notes_shared_examples.rb4
-rw-r--r--spec/support/shared_examples/services/jira_import/start_import_service_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/services/metrics/dashboard_shared_examples.rb80
-rw-r--r--spec/support/shared_examples/services/packages_shared_examples.rb193
-rw-r--r--spec/support/shared_examples/services/projects/update_repository_storage_service_shared_examples.rb16
-rw-r--r--spec/support/shared_examples/services/resource_events/change_milestone_service_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/services/wiki_pages/create_service_shared_examples.rb10
-rw-r--r--spec/support/shared_examples/services/wiki_pages/destroy_service_shared_examples.rb10
-rw-r--r--spec/support/shared_examples/services/wiki_pages/update_service_shared_examples.rb10
-rw-r--r--spec/support/shared_examples/snippet_blob_shared_examples.rb24
-rw-r--r--spec/support/shared_examples/uploaders/upload_type_shared_examples.rb6
-rw-r--r--spec/support/shared_examples/views/pipeline_status_changes_email.rb2
-rw-r--r--spec/support/shared_examples/views/plain_text_email.rb9
-rw-r--r--spec/support/shared_examples/workers/gitlab/jira_import/jira_import_workers_shared_examples.rb8
-rw-r--r--spec/support_specs/helpers/active_record/query_recorder_spec.rb2
-rw-r--r--spec/support_specs/helpers/graphql_helpers_spec.rb2
-rw-r--r--spec/support_specs/helpers/stub_feature_flags_spec.rb2
-rw-r--r--spec/support_specs/matchers/exceed_query_limit_helpers_spec.rb2
-rw-r--r--spec/tasks/cache/clear/redis_spec.rb2
-rw-r--r--spec/tasks/config_lint_spec.rb4
-rw-r--r--spec/tasks/gitlab/artifacts/check_rake_spec.rb2
-rw-r--r--spec/tasks/gitlab/artifacts/migrate_rake_spec.rb2
-rw-r--r--spec/tasks/gitlab/backup_rake_spec.rb2
-rw-r--r--spec/tasks/gitlab/check_rake_spec.rb2
-rw-r--r--spec/tasks/gitlab/cleanup_rake_spec.rb2
-rw-r--r--spec/tasks/gitlab/container_registry_rake_spec.rb84
-rw-r--r--spec/tasks/gitlab/db_rake_spec.rb2
-rw-r--r--spec/tasks/gitlab/external_diffs_rake_spec.rb34
-rw-r--r--spec/tasks/gitlab/generate_sample_prometheus_data_spec.rb2
-rw-r--r--spec/tasks/gitlab/git_rake_spec.rb2
-rw-r--r--spec/tasks/gitlab/gitaly_rake_spec.rb2
-rw-r--r--spec/tasks/gitlab/info_rake_spec.rb2
-rw-r--r--spec/tasks/gitlab/ldap_rake_spec.rb2
-rw-r--r--spec/tasks/gitlab/lfs/check_rake_spec.rb2
-rw-r--r--spec/tasks/gitlab/lfs/migrate_rake_spec.rb2
-rw-r--r--spec/tasks/gitlab/packages/migrate_rake_spec.rb39
-rw-r--r--spec/tasks/gitlab/praefect_rake_spec.rb2
-rw-r--r--spec/tasks/gitlab/seed/group_seed_rake_spec.rb2
-rw-r--r--spec/tasks/gitlab/shell_rake_spec.rb2
-rw-r--r--spec/tasks/gitlab/snippets_rake_spec.rb2
-rw-r--r--spec/tasks/gitlab/storage_rake_spec.rb2
-rw-r--r--spec/tasks/gitlab/task_helpers_spec.rb2
-rw-r--r--spec/tasks/gitlab/update_templates_rake_spec.rb2
-rw-r--r--spec/tasks/gitlab/uploads/check_rake_spec.rb2
-rw-r--r--spec/tasks/gitlab/uploads/migrate_rake_spec.rb2
-rw-r--r--spec/tasks/gitlab/web_hook_rake_spec.rb2
-rw-r--r--spec/tasks/gitlab/workhorse_rake_spec.rb2
-rw-r--r--spec/tasks/gitlab/x509/update_rake_spec.rb2
-rw-r--r--spec/tasks/migrate/schema_check_rake_spec.rb2
-rw-r--r--spec/tasks/tokens_spec.rb2
-rw-r--r--spec/tooling/lib/tooling/helm3_client_spec.rb133
-rw-r--r--spec/tooling/lib/tooling/kubernetes_client_spec.rb111
-rw-r--r--spec/tooling/lib/tooling/test_file_finder_spec.rb74
-rw-r--r--spec/uploaders/attachment_uploader_spec.rb2
-rw-r--r--spec/uploaders/avatar_uploader_spec.rb2
-rw-r--r--spec/uploaders/content_type_whitelist_spec.rb2
-rw-r--r--spec/uploaders/design_management/design_v432x230_uploader_spec.rb2
-rw-r--r--spec/uploaders/external_diff_uploader_spec.rb2
-rw-r--r--spec/uploaders/favicon_uploader_spec.rb2
-rw-r--r--spec/uploaders/file_mover_spec.rb2
-rw-r--r--spec/uploaders/file_uploader_spec.rb2
-rw-r--r--spec/uploaders/gitlab_uploader_spec.rb2
-rw-r--r--spec/uploaders/import_export_uploader_spec.rb2
-rw-r--r--spec/uploaders/job_artifact_uploader_spec.rb2
-rw-r--r--spec/uploaders/lfs_object_uploader_spec.rb2
-rw-r--r--spec/uploaders/namespace_file_uploader_spec.rb2
-rw-r--r--spec/uploaders/object_storage_spec.rb2
-rw-r--r--spec/uploaders/packages/package_file_uploader_spec.rb45
-rw-r--r--spec/uploaders/personal_file_uploader_spec.rb2
-rw-r--r--spec/uploaders/records_uploads_spec.rb2
-rw-r--r--spec/uploaders/terraform/state_uploader_spec.rb2
-rw-r--r--spec/uploaders/uploader_helper_spec.rb2
-rw-r--r--spec/uploaders/workers/object_storage/background_move_worker_spec.rb2
-rw-r--r--spec/uploaders/workers/object_storage/migrate_uploads_worker_spec.rb2
-rw-r--r--spec/validators/addressable_url_validator_spec.rb2
-rw-r--r--spec/validators/array_members_validator_spec.rb69
-rw-r--r--spec/validators/branch_filter_validator_spec.rb2
-rw-r--r--spec/validators/color_validator_spec.rb2
-rw-r--r--spec/validators/cron_freeze_period_timezone_validator_spec.rb2
-rw-r--r--spec/validators/cron_validator_spec.rb2
-rw-r--r--spec/validators/devise_email_validator_spec.rb2
-rw-r--r--spec/validators/js_regex_validator_spec.rb2
-rw-r--r--spec/validators/json_schema_validator_spec.rb2
-rw-r--r--spec/validators/named_ecdsa_key_validator_spec.rb2
-rw-r--r--spec/validators/namespace_path_validator_spec.rb2
-rw-r--r--spec/validators/project_path_validator_spec.rb2
-rw-r--r--spec/validators/public_url_validator_spec.rb2
-rw-r--r--spec/validators/qualified_domain_array_validator_spec.rb2
-rw-r--r--spec/validators/sha_validator_spec.rb2
-rw-r--r--spec/validators/system_hook_url_validator_spec.rb2
-rw-r--r--spec/validators/variable_duplicates_validator_spec.rb2
-rw-r--r--spec/validators/x509_certificate_credentials_validator_spec.rb2
-rw-r--r--spec/views/admin/application_settings/_eks.html.haml_spec.rb2
-rw-r--r--spec/views/admin/application_settings/_repository_storage.html.haml_spec.rb2
-rw-r--r--spec/views/admin/application_settings/general.html.haml_spec.rb2
-rw-r--r--spec/views/admin/application_settings/repository.html.haml_spec.rb46
-rw-r--r--spec/views/admin/dashboard/index.html.haml_spec.rb2
-rw-r--r--spec/views/admin/sessions/new.html.haml_spec.rb2
-rw-r--r--spec/views/admin/sessions/two_factor.html.haml_spec.rb2
-rw-r--r--spec/views/admin/users/_user.html.haml_spec.rb2
-rw-r--r--spec/views/ci/status/_badge.html.haml_spec.rb2
-rw-r--r--spec/views/ci/status/_icon.html.haml_spec.rb2
-rw-r--r--spec/views/dashboard/projects/_blank_state_admin_welcome.haml_spec.rb2
-rw-r--r--spec/views/dashboard/projects/_nav.html.haml_spec.rb2
-rw-r--r--spec/views/devise/sessions/new.html.haml_spec.rb2
-rw-r--r--spec/views/devise/shared/_signin_box.html.haml_spec.rb2
-rw-r--r--spec/views/errors/access_denied.html.haml_spec.rb2
-rw-r--r--spec/views/events/event/_push.html.haml_spec.rb2
-rw-r--r--spec/views/groups/_home_panel.html.haml_spec.rb2
-rw-r--r--spec/views/groups/edit.html.haml_spec.rb2
-rw-r--r--spec/views/help/index.html.haml_spec.rb2
-rw-r--r--spec/views/help/instance_configuration.html.haml_spec.rb2
-rw-r--r--spec/views/help/show.html.haml_spec.rb2
-rw-r--r--spec/views/import/gitlab_projects/new.html.haml_spec.rb2
-rw-r--r--spec/views/layouts/_head.html.haml_spec.rb2
-rw-r--r--spec/views/layouts/application.html.haml_spec.rb2
-rw-r--r--spec/views/layouts/header/_new_dropdown.haml_spec.rb2
-rw-r--r--spec/views/layouts/nav/sidebar/_admin.html.haml_spec.rb2
-rw-r--r--spec/views/layouts/nav/sidebar/_group.html.haml_spec.rb2
-rw-r--r--spec/views/layouts/nav/sidebar/_instance_statistics.html.haml_spec.rb2
-rw-r--r--spec/views/layouts/nav/sidebar/_profile.html.haml_spec.rb2
-rw-r--r--spec/views/layouts/nav/sidebar/_project.html.haml_spec.rb36
-rw-r--r--spec/views/notify/changed_milestone_email.html.haml_spec.rb2
-rw-r--r--spec/views/notify/pipeline_failed_email.html.haml_spec.rb2
-rw-r--r--spec/views/notify/pipeline_failed_email.text.erb_spec.rb2
-rw-r--r--spec/views/notify/pipeline_fixed_email.html.haml_spec.rb2
-rw-r--r--spec/views/notify/pipeline_fixed_email.text.erb_spec.rb2
-rw-r--r--spec/views/notify/pipeline_success_email.html.haml_spec.rb2
-rw-r--r--spec/views/notify/pipeline_success_email.text.erb_spec.rb2
-rw-r--r--spec/views/notify/push_to_merge_request_email.text.haml_spec.rb19
-rw-r--r--spec/views/profiles/preferences/show.html.haml_spec.rb2
-rw-r--r--spec/views/profiles/show.html.haml_spec.rb2
-rw-r--r--spec/views/projects/_home_panel.html.haml_spec.rb2
-rw-r--r--spec/views/projects/blob/_viewer.html.haml_spec.rb2
-rw-r--r--spec/views/projects/buttons/_dropdown.html.haml_spec.rb2
-rw-r--r--spec/views/projects/ci/lints/show.html.haml_spec.rb2
-rw-r--r--spec/views/projects/clusters/clusters/gcp/_form.html.haml_spec.rb2
-rw-r--r--spec/views/projects/commit/_commit_box.html.haml_spec.rb2
-rw-r--r--spec/views/projects/commit/branches.html.haml_spec.rb2
-rw-r--r--spec/views/projects/commit/show.html.haml_spec.rb2
-rw-r--r--spec/views/projects/commits/_commit.html.haml_spec.rb2
-rw-r--r--spec/views/projects/deployments/_confirm_rollback_modal_spec.html_spec.rb2
-rw-r--r--spec/views/projects/diffs/_stats.html.haml_spec.rb2
-rw-r--r--spec/views/projects/diffs/_viewer.html.haml_spec.rb2
-rw-r--r--spec/views/projects/edit.html.haml_spec.rb2
-rw-r--r--spec/views/projects/environments/terminal.html.haml_spec.rb2
-rw-r--r--spec/views/projects/imports/new.html.haml_spec.rb2
-rw-r--r--spec/views/projects/issues/_related_branches.html.haml_spec.rb2
-rw-r--r--spec/views/projects/issues/import_csv/_button.html.haml_spec.rb2
-rw-r--r--spec/views/projects/issues/show.html.haml_spec.rb12
-rw-r--r--spec/views/projects/jobs/_build.html.haml_spec.rb2
-rw-r--r--spec/views/projects/jobs/_generic_commit_status.html.haml_spec.rb2
-rw-r--r--spec/views/projects/jobs/show.html.haml_spec.rb2
-rw-r--r--spec/views/projects/merge_requests/_commits.html.haml_spec.rb2
-rw-r--r--spec/views/projects/merge_requests/creations/_new_submit.html.haml_spec.rb2
-rw-r--r--spec/views/projects/merge_requests/diffs/_diffs.html.haml_spec.rb2
-rw-r--r--spec/views/projects/merge_requests/edit.html.haml_spec.rb2
-rw-r--r--spec/views/projects/merge_requests/show.html.haml_spec.rb2
-rw-r--r--spec/views/projects/notes/_more_actions_dropdown.html.haml_spec.rb2
-rw-r--r--spec/views/projects/pages/show.html.haml_spec.rb2
-rw-r--r--spec/views/projects/pages_domains/show.html.haml_spec.rb2
-rw-r--r--spec/views/projects/pipeline_schedules/_pipeline_schedule.html.haml_spec.rb2
-rw-r--r--spec/views/projects/pipelines/_stage.html.haml_spec.rb6
-rw-r--r--spec/views/projects/services/_form.haml_spec.rb2
-rw-r--r--spec/views/projects/services/edit.html.haml_spec.rb2
-rw-r--r--spec/views/projects/settings/ci_cd/_autodevops_form.html.haml_spec.rb2
-rw-r--r--spec/views/projects/settings/operations/show.html.haml_spec.rb17
-rw-r--r--spec/views/projects/show.html.haml_spec.rb41
-rw-r--r--spec/views/projects/tags/index.html.haml_spec.rb2
-rw-r--r--spec/views/projects/tree/_tree_header.html.haml_spec.rb55
-rw-r--r--spec/views/projects/tree/_tree_row.html.haml_spec.rb2
-rw-r--r--spec/views/projects/tree/show.html.haml_spec.rb24
-rw-r--r--spec/views/search/_filter.html.haml_spec.rb2
-rw-r--r--spec/views/search/_form.html.haml_spec.rb2
-rw-r--r--spec/views/search/_results.html.haml_spec.rb2
-rw-r--r--spec/views/search/show.html.haml_spec.rb2
-rw-r--r--spec/views/shared/_label_row.html.haml_spec.rb2
-rw-r--r--spec/views/shared/milestones/_issuable.html.haml_spec.rb2
-rw-r--r--spec/views/shared/milestones/_issuables.html.haml_spec.rb2
-rw-r--r--spec/views/shared/milestones/_top.html.haml_spec.rb2
-rw-r--r--spec/views/shared/notes/_form.html.haml_spec.rb2
-rw-r--r--spec/views/shared/projects/_list.html.haml_spec.rb2
-rw-r--r--spec/views/shared/projects/_project.html.haml_spec.rb2
-rw-r--r--spec/views/shared/runners/show.html.haml_spec.rb2
-rw-r--r--spec/workers/admin_email_worker_spec.rb2
-rw-r--r--spec/workers/archive_trace_worker_spec.rb2
-rw-r--r--spec/workers/authorized_keys_worker_spec.rb2
-rw-r--r--spec/workers/authorized_project_update/periodic_recalculate_worker_spec.rb2
-rw-r--r--spec/workers/authorized_project_update/project_create_worker_spec.rb4
-rw-r--r--spec/workers/authorized_project_update/project_group_link_create_worker_spec.rb52
-rw-r--r--spec/workers/authorized_project_update/user_refresh_over_user_range_worker_spec.rb2
-rw-r--r--spec/workers/authorized_project_update/user_refresh_with_low_urgency_worker_spec.rb2
-rw-r--r--spec/workers/authorized_projects_worker_spec.rb2
-rw-r--r--spec/workers/auto_devops/disable_worker_spec.rb2
-rw-r--r--spec/workers/auto_merge_process_worker_spec.rb2
-rw-r--r--spec/workers/background_migration_worker_spec.rb2
-rw-r--r--spec/workers/build_coverage_worker_spec.rb2
-rw-r--r--spec/workers/build_finished_worker_spec.rb2
-rw-r--r--spec/workers/build_hooks_worker_spec.rb2
-rw-r--r--spec/workers/build_success_worker_spec.rb2
-rw-r--r--spec/workers/build_trace_sections_worker_spec.rb2
-rw-r--r--spec/workers/chat_notification_worker_spec.rb2
-rw-r--r--spec/workers/ci/archive_traces_cron_worker_spec.rb2
-rw-r--r--spec/workers/ci/build_prepare_worker_spec.rb2
-rw-r--r--spec/workers/ci/build_report_result_worker_spec.rb2
-rw-r--r--spec/workers/ci/build_schedule_worker_spec.rb2
-rw-r--r--spec/workers/ci/create_cross_project_pipeline_worker_spec.rb2
-rw-r--r--spec/workers/ci/daily_build_group_report_results_worker_spec.rb2
-rw-r--r--spec/workers/ci/pipeline_bridge_status_worker_spec.rb2
-rw-r--r--spec/workers/ci/pipeline_success_unlock_artifacts_worker_spec.rb67
-rw-r--r--spec/workers/ci/ref_delete_unlock_artifacts_worker_spec.rb78
-rw-r--r--spec/workers/ci/resource_groups/assign_resource_from_resource_group_worker_spec.rb2
-rw-r--r--spec/workers/cleanup_container_repository_worker_spec.rb2
-rw-r--r--spec/workers/cluster_configure_istio_worker_spec.rb2
-rw-r--r--spec/workers/cluster_provision_worker_spec.rb2
-rw-r--r--spec/workers/cluster_update_app_worker_spec.rb20
-rw-r--r--spec/workers/cluster_wait_for_app_update_worker_spec.rb2
-rw-r--r--spec/workers/cluster_wait_for_ingress_ip_address_worker_spec.rb2
-rw-r--r--spec/workers/clusters/applications/activate_service_worker_spec.rb2
-rw-r--r--spec/workers/clusters/applications/check_prometheus_health_worker_spec.rb2
-rw-r--r--spec/workers/clusters/applications/deactivate_service_worker_spec.rb2
-rw-r--r--spec/workers/clusters/applications/wait_for_uninstall_app_worker_spec.rb2
-rw-r--r--spec/workers/clusters/cleanup/app_worker_spec.rb2
-rw-r--r--spec/workers/clusters/cleanup/project_namespace_worker_spec.rb2
-rw-r--r--spec/workers/clusters/cleanup/service_account_worker_spec.rb2
-rw-r--r--spec/workers/concerns/application_worker_spec.rb2
-rw-r--r--spec/workers/concerns/cluster_queue_spec.rb2
-rw-r--r--spec/workers/concerns/cronjob_queue_spec.rb2
-rw-r--r--spec/workers/concerns/gitlab/github_import/object_importer_spec.rb2
-rw-r--r--spec/workers/concerns/gitlab/github_import/queue_spec.rb2
-rw-r--r--spec/workers/concerns/gitlab/github_import/rescheduling_methods_spec.rb2
-rw-r--r--spec/workers/concerns/gitlab/github_import/stage_methods_spec.rb2
-rw-r--r--spec/workers/concerns/gitlab/notify_upon_death_spec.rb2
-rw-r--r--spec/workers/concerns/pipeline_background_queue_spec.rb2
-rw-r--r--spec/workers/concerns/pipeline_queue_spec.rb2
-rw-r--r--spec/workers/concerns/project_export_options_spec.rb41
-rw-r--r--spec/workers/concerns/project_import_options_spec.rb2
-rw-r--r--spec/workers/concerns/reenqueuer_spec.rb4
-rw-r--r--spec/workers/concerns/repository_check_queue_spec.rb2
-rw-r--r--spec/workers/concerns/waitable_worker_spec.rb2
-rw-r--r--spec/workers/concerns/worker_context_spec.rb2
-rw-r--r--spec/workers/container_expiration_policy_worker_spec.rb2
-rw-r--r--spec/workers/create_commit_signature_worker_spec.rb2
-rw-r--r--spec/workers/create_evidence_worker_spec.rb2
-rw-r--r--spec/workers/create_note_diff_file_worker_spec.rb2
-rw-r--r--spec/workers/create_pipeline_worker_spec.rb2
-rw-r--r--spec/workers/delete_container_repository_worker_spec.rb2
-rw-r--r--spec/workers/delete_diff_files_worker_spec.rb2
-rw-r--r--spec/workers/delete_merged_branches_worker_spec.rb2
-rw-r--r--spec/workers/delete_user_worker_spec.rb2
-rw-r--r--spec/workers/deployments/finished_worker_spec.rb2
-rw-r--r--spec/workers/deployments/success_worker_spec.rb2
-rw-r--r--spec/workers/design_management/new_version_worker_spec.rb2
-rw-r--r--spec/workers/detect_repository_languages_worker_spec.rb2
-rw-r--r--spec/workers/email_receiver_worker_spec.rb2
-rw-r--r--spec/workers/emails_on_push_worker_spec.rb2
-rw-r--r--spec/workers/environments/auto_stop_cron_worker_spec.rb2
-rw-r--r--spec/workers/error_tracking_issue_link_worker_spec.rb2
-rw-r--r--spec/workers/every_sidekiq_worker_spec.rb8
-rw-r--r--spec/workers/expire_build_artifacts_worker_spec.rb2
-rw-r--r--spec/workers/expire_build_instance_artifacts_worker_spec.rb4
-rw-r--r--spec/workers/expire_job_cache_worker_spec.rb2
-rw-r--r--spec/workers/expire_pipeline_cache_worker_spec.rb2
-rw-r--r--spec/workers/export_csv_worker_spec.rb2
-rw-r--r--spec/workers/external_service_reactive_caching_worker_spec.rb2
-rw-r--r--spec/workers/file_hook_worker_spec.rb2
-rw-r--r--spec/workers/git_garbage_collect_worker_spec.rb2
-rw-r--r--spec/workers/gitlab/github_import/advance_stage_worker_spec.rb2
-rw-r--r--spec/workers/gitlab/github_import/import_diff_note_worker_spec.rb2
-rw-r--r--spec/workers/gitlab/github_import/import_issue_worker_spec.rb2
-rw-r--r--spec/workers/gitlab/github_import/import_note_worker_spec.rb2
-rw-r--r--spec/workers/gitlab/github_import/import_pull_request_worker_spec.rb2
-rw-r--r--spec/workers/gitlab/github_import/refresh_import_jid_worker_spec.rb2
-rw-r--r--spec/workers/gitlab/github_import/stage/finish_import_worker_spec.rb2
-rw-r--r--spec/workers/gitlab/github_import/stage/import_base_data_worker_spec.rb2
-rw-r--r--spec/workers/gitlab/github_import/stage/import_issues_and_diff_notes_worker_spec.rb2
-rw-r--r--spec/workers/gitlab/github_import/stage/import_lfs_objects_worker_spec.rb2
-rw-r--r--spec/workers/gitlab/github_import/stage/import_notes_worker_spec.rb2
-rw-r--r--spec/workers/gitlab/github_import/stage/import_pull_requests_worker_spec.rb2
-rw-r--r--spec/workers/gitlab/github_import/stage/import_repository_worker_spec.rb2
-rw-r--r--spec/workers/gitlab/import/stuck_project_import_jobs_worker_spec.rb2
-rw-r--r--spec/workers/gitlab/jira_import/import_issue_worker_spec.rb2
-rw-r--r--spec/workers/gitlab/jira_import/stage/finish_import_worker_spec.rb2
-rw-r--r--spec/workers/gitlab/jira_import/stage/import_attachments_worker_spec.rb2
-rw-r--r--spec/workers/gitlab/jira_import/stage/import_issues_worker_spec.rb2
-rw-r--r--spec/workers/gitlab/jira_import/stage/import_labels_worker_spec.rb2
-rw-r--r--spec/workers/gitlab/jira_import/stage/import_notes_worker_spec.rb2
-rw-r--r--spec/workers/gitlab/jira_import/stage/start_import_worker_spec.rb2
-rw-r--r--spec/workers/gitlab/jira_import/stuck_jira_import_jobs_worker_spec.rb2
-rw-r--r--spec/workers/gitlab/phabricator_import/base_worker_spec.rb2
-rw-r--r--spec/workers/gitlab/phabricator_import/import_tasks_worker_spec.rb2
-rw-r--r--spec/workers/gitlab_shell_worker_spec.rb2
-rw-r--r--spec/workers/gitlab_usage_ping_worker_spec.rb2
-rw-r--r--spec/workers/group_destroy_worker_spec.rb2
-rw-r--r--spec/workers/group_export_worker_spec.rb2
-rw-r--r--spec/workers/group_import_worker_spec.rb2
-rw-r--r--spec/workers/hashed_storage/migrator_worker_spec.rb2
-rw-r--r--spec/workers/hashed_storage/project_migrate_worker_spec.rb2
-rw-r--r--spec/workers/hashed_storage/project_rollback_worker_spec.rb2
-rw-r--r--spec/workers/hashed_storage/rollbacker_worker_spec.rb2
-rw-r--r--spec/workers/import_issues_csv_worker_spec.rb2
-rw-r--r--spec/workers/incident_management/pager_duty/process_incident_worker_spec.rb57
-rw-r--r--spec/workers/incident_management/process_alert_worker_spec.rb66
-rw-r--r--spec/workers/incident_management/process_prometheus_alert_worker_spec.rb134
-rw-r--r--spec/workers/invalid_gpg_signature_update_worker_spec.rb2
-rw-r--r--spec/workers/irker_worker_spec.rb2
-rw-r--r--spec/workers/issue_due_scheduler_worker_spec.rb2
-rw-r--r--spec/workers/mail_scheduler/issue_due_worker_spec.rb2
-rw-r--r--spec/workers/mail_scheduler/notification_service_worker_spec.rb2
-rw-r--r--spec/workers/members_destroyer/unassign_issuables_worker_spec.rb27
-rw-r--r--spec/workers/merge_request_mergeability_check_worker_spec.rb2
-rw-r--r--spec/workers/merge_worker_spec.rb2
-rw-r--r--spec/workers/metrics/dashboard/prune_old_annotations_worker_spec.rb2
-rw-r--r--spec/workers/metrics/dashboard/schedule_annotations_prune_worker_spec.rb2
-rw-r--r--spec/workers/migrate_external_diffs_worker_spec.rb2
-rw-r--r--spec/workers/namespaceless_project_destroy_worker_spec.rb2
-rw-r--r--spec/workers/namespaces/prune_aggregation_schedules_worker_spec.rb2
-rw-r--r--spec/workers/namespaces/root_statistics_worker_spec.rb2
-rw-r--r--spec/workers/namespaces/schedule_aggregation_worker_spec.rb2
-rw-r--r--spec/workers/new_issue_worker_spec.rb2
-rw-r--r--spec/workers/new_merge_request_worker_spec.rb2
-rw-r--r--spec/workers/new_note_worker_spec.rb2
-rw-r--r--spec/workers/new_release_worker_spec.rb17
-rw-r--r--spec/workers/object_pool/create_worker_spec.rb2
-rw-r--r--spec/workers/object_pool/destroy_worker_spec.rb2
-rw-r--r--spec/workers/object_pool/join_worker_spec.rb2
-rw-r--r--spec/workers/packages/nuget/extraction_worker_spec.rb94
-rw-r--r--spec/workers/pages_domain_removal_cron_worker_spec.rb2
-rw-r--r--spec/workers/pages_domain_ssl_renewal_cron_worker_spec.rb2
-rw-r--r--spec/workers/pages_domain_ssl_renewal_worker_spec.rb2
-rw-r--r--spec/workers/pages_domain_verification_cron_worker_spec.rb2
-rw-r--r--spec/workers/pages_domain_verification_worker_spec.rb2
-rw-r--r--spec/workers/partition_creation_worker_spec.rb19
-rw-r--r--spec/workers/pipeline_hooks_worker_spec.rb2
-rw-r--r--spec/workers/pipeline_metrics_worker_spec.rb4
-rw-r--r--spec/workers/pipeline_notification_worker_spec.rb2
-rw-r--r--spec/workers/pipeline_process_worker_spec.rb2
-rw-r--r--spec/workers/pipeline_schedule_worker_spec.rb4
-rw-r--r--spec/workers/pipeline_update_ci_ref_status_worker_service_spec.rb2
-rw-r--r--spec/workers/pipeline_update_worker_spec.rb10
-rw-r--r--spec/workers/post_receive_spec.rb9
-rw-r--r--spec/workers/process_commit_worker_spec.rb6
-rw-r--r--spec/workers/project_cache_worker_spec.rb2
-rw-r--r--spec/workers/project_daily_statistics_worker_spec.rb2
-rw-r--r--spec/workers/project_destroy_worker_spec.rb2
-rw-r--r--spec/workers/project_export_worker_spec.rb12
-rw-r--r--spec/workers/project_service_worker_spec.rb2
-rw-r--r--spec/workers/project_update_repository_storage_worker_spec.rb3
-rw-r--r--spec/workers/prometheus/create_default_alerts_worker_spec.rb2
-rw-r--r--spec/workers/propagate_integration_worker_spec.rb2
-rw-r--r--spec/workers/propagate_service_template_worker_spec.rb2
-rw-r--r--spec/workers/prune_old_events_worker_spec.rb2
-rw-r--r--spec/workers/prune_web_hook_logs_worker_spec.rb2
-rw-r--r--spec/workers/reactive_caching_worker_spec.rb2
-rw-r--r--spec/workers/rebase_worker_spec.rb2
-rw-r--r--spec/workers/remote_mirror_notification_worker_spec.rb2
-rw-r--r--spec/workers/remove_expired_group_links_worker_spec.rb2
-rw-r--r--spec/workers/remove_expired_members_worker_spec.rb2
-rw-r--r--spec/workers/remove_unreferenced_lfs_objects_worker_spec.rb2
-rw-r--r--spec/workers/repository_check/batch_worker_spec.rb2
-rw-r--r--spec/workers/repository_check/clear_worker_spec.rb4
-rw-r--r--spec/workers/repository_check/dispatch_worker_spec.rb2
-rw-r--r--spec/workers/repository_check/single_repository_worker_spec.rb2
-rw-r--r--spec/workers/repository_cleanup_worker_spec.rb2
-rw-r--r--spec/workers/repository_fork_worker_spec.rb2
-rw-r--r--spec/workers/repository_import_worker_spec.rb8
-rw-r--r--spec/workers/repository_remove_remote_worker_spec.rb4
-rw-r--r--spec/workers/repository_update_remote_mirror_worker_spec.rb10
-rw-r--r--spec/workers/run_pipeline_schedule_worker_spec.rb2
-rw-r--r--spec/workers/schedule_migrate_external_diffs_worker_spec.rb2
-rw-r--r--spec/workers/self_monitoring_project_create_worker_spec.rb2
-rw-r--r--spec/workers/self_monitoring_project_delete_worker_spec.rb2
-rw-r--r--spec/workers/service_desk_email_receiver_worker_spec.rb53
-rw-r--r--spec/workers/stage_update_worker_spec.rb2
-rw-r--r--spec/workers/stuck_ci_jobs_worker_spec.rb2
-rw-r--r--spec/workers/stuck_export_jobs_worker_spec.rb2
-rw-r--r--spec/workers/stuck_import_jobs_worker_spec.rb27
-rw-r--r--spec/workers/stuck_merge_jobs_worker_spec.rb2
-rw-r--r--spec/workers/system_hook_push_worker_spec.rb2
-rw-r--r--spec/workers/todos_destroyer/confidential_issue_worker_spec.rb2
-rw-r--r--spec/workers/todos_destroyer/entity_leave_worker_spec.rb2
-rw-r--r--spec/workers/todos_destroyer/group_private_worker_spec.rb2
-rw-r--r--spec/workers/todos_destroyer/private_features_worker_spec.rb2
-rw-r--r--spec/workers/todos_destroyer/project_private_worker_spec.rb2
-rw-r--r--spec/workers/trending_projects_worker_spec.rb2
-rw-r--r--spec/workers/update_container_registry_info_worker_spec.rb15
-rw-r--r--spec/workers/update_external_pull_requests_worker_spec.rb2
-rw-r--r--spec/workers/update_head_pipeline_for_merge_request_worker_spec.rb2
-rw-r--r--spec/workers/update_highest_role_worker_spec.rb2
-rw-r--r--spec/workers/update_merge_requests_worker_spec.rb2
-rw-r--r--spec/workers/update_project_statistics_worker_spec.rb2
-rw-r--r--spec/workers/upload_checksum_worker_spec.rb2
-rw-r--r--spec/workers/users/create_statistics_worker_spec.rb2
-rw-r--r--spec/workers/wait_for_cluster_creation_worker_spec.rb2
-rw-r--r--spec/workers/x509_certificate_revoke_worker_spec.rb2
-rw-r--r--spec/workers/x509_issuer_crl_check_worker_spec.rb2
-rw-r--r--symbol/icons.svg1
-rw-r--r--symbol/sprite.symbol.html177
-rw-r--r--tooling/lib/tooling/helm3_client.rb111
-rw-r--r--tooling/lib/tooling/kubernetes_client.rb86
-rw-r--r--tooling/lib/tooling/test_file_finder.rb44
-rw-r--r--tooling/overcommit/Gemfile2
-rw-r--r--tooling/overcommit/Gemfile.lock4
-rw-r--r--vendor/elastic_stack/values.yaml4
-rw-r--r--[-rwxr-xr-x]vendor/gitignore/C++.gitignore0
-rw-r--r--[-rwxr-xr-x]vendor/gitignore/Java.gitignore0
-rw-r--r--vendor/licenses.csv3
-rw-r--r--vendor/project_templates/jsonnet.tar.gzbin0 -> 3857 bytes
-rw-r--r--vendor/project_templates/learn_gitlab.tar.gzbin114701 -> 114312 bytes
-rw-r--r--yarn.lock192
9228 files changed, 358256 insertions, 81616 deletions
diff --git a/.gitignore b/.gitignore
index 29180b76e26..151c75d474a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -17,6 +17,7 @@ eslint-report.html
.rbx/
/.ruby-gemset
/.ruby-version
+/.tool-versions
/.rvmrc
.sass-cache/
/.secret
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 4bbf411c233..a5b80c7ca55 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -20,6 +20,8 @@ default:
- gitlab-org
# All jobs are interruptible by default
interruptible: true
+ # Default job timeout set to 90m https://gitlab.com/gitlab-com/gl-infra/infrastructure/-/issues/10520
+ timeout: 90m
workflow:
rules:
@@ -61,6 +63,7 @@ variables:
DOCKER_VERSION: "19.03.0"
include:
+ - local: .gitlab/ci/build-images.gitlab-ci.yml
- local: .gitlab/ci/cache-repo.gitlab-ci.yml
- local: .gitlab/ci/cng.gitlab-ci.yml
- local: .gitlab/ci/docs.gitlab-ci.yml
diff --git a/.gitlab/CODEOWNERS b/.gitlab/CODEOWNERS
index 6cd316349c8..4e2c4aa5c76 100644
--- a/.gitlab/CODEOWNERS
+++ b/.gitlab/CODEOWNERS
@@ -13,6 +13,7 @@
/doc/development/ @marcia @mjang1
/doc/development/documentation/ @mikelewis
/doc/ci @marcel.amirault @sselhorn
+/doc/operations @aqualls @eread
/doc/user/clusters @aqualls
/doc/user/infrastructure @aqualls
/doc/user/project/clusters @aqualls
@@ -43,17 +44,12 @@
# Feature specific owners
/ee/lib/ee/gitlab/auth/ldap/ @dblessing @mkozono
/lib/gitlab/auth/ldap/ @dblessing @mkozono
-/lib/gitlab/ci/templates/ @nolith @zj
+/lib/gitlab/ci/templates/ @nolith @dosuken123
/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml @DylanGriffith @mayra-cabrera @tkuah
/lib/gitlab/ci/templates/Security/ @plafoucriere @gonzoyumo @twoodham @sethgitlab
/ee/app/models/project_alias.rb @patrickbajao
/ee/lib/api/project_aliases.rb @patrickbajao
-# Code Owners
-#
-/ee/lib/gitlab/code_owners/ @reprazent @kerrizor @garyh
-/doc/user/project/code_owners.md @reprazent @kerrizor @garyh
-
# Quality owned files
/qa/ @gl-quality
@@ -77,3 +73,9 @@ Dangerfile @gl-quality/eng-prod
/lib/gitlab/usage_data.rb @gitlab-org/growth/telemetry
/lib/gitlab/cycle_analytics/usage_data.rb @gitlab-org/growth/telemetry
/lib/gitlab/usage_data_counters/ @gitlab-org/growth/telemetry
+
+[Code Owners]
+/ee/lib/gitlab/code_owners.rb @reprazent @kerrizor @garyh
+/ee/lib/gitlab/code_owners/ @reprazent @kerrizor @garyh
+/ee/spec/lib/gitlab/code_owners/ @reprazent @kerrizor @garyh
+/doc/user/project/code_owners.md @reprazent @kerrizor @garyh
diff --git a/.gitlab/ci/build-images.gitlab-ci.yml b/.gitlab/ci/build-images.gitlab-ci.yml
new file mode 100644
index 00000000000..e6c3e7598d3
--- /dev/null
+++ b/.gitlab/ci/build-images.gitlab-ci.yml
@@ -0,0 +1,31 @@
+# This image is used by the `review-qa-*` jobs. Not currently used by the `omnibus-gitlab` pipelines which rebuild this
+# image, e.g. https://gitlab.com/gitlab-org/build/omnibus-gitlab-mirror/-/jobs/587107399, which we could probably avoid.
+# See https://gitlab.com/gitlab-org/omnibus-gitlab/-/issues/5429.
+build-qa-image:
+ extends:
+ - .use-kaniko
+ - .build-images:rules:build-qa-image
+ stage: build-images
+ needs: []
+ script:
+ - export QA_IMAGE="${CI_REGISTRY}/${CI_PROJECT_PATH}/gitlab-ee-qa:${CI_COMMIT_REF_SLUG}"
+ - /kaniko/executor --context=${CI_PROJECT_DIR} --dockerfile=${CI_PROJECT_DIR}/qa/Dockerfile --destination=${QA_IMAGE} --cache=true
+ retry: 2
+
+# This image is used by:
+# - The `CNG` pipelines (via the `review-build-cng` job): https://gitlab.com/gitlab-org/build/CNG/-/blob/cfc67136d711e1c8c409bf8e57427a644393da2f/.gitlab-ci.yml#L335
+# - The `omnibus-gitlab` pipelines (via the `package-and-qa` job): https://gitlab.com/gitlab-org/omnibus-gitlab/-/blob/dfd1ad475868fc84e91ab7b5706aa03e46dc3a86/.gitlab-ci.yml#L130
+build-assets-image:
+ extends:
+ - .use-kaniko
+ - .build-images:rules:build-assets-image
+ stage: build-images
+ needs: ["compile-production-assets"]
+ variables:
+ GIT_DEPTH: "1"
+ script:
+ # TODO: Change the image tag to be the MD5 of assets files and skip image building if the image exists
+ # We'll also need to pass GITLAB_ASSETS_TAG to the trigerred omnibus-gitlab pipeline similarly to how we do it for trigerred CNG pipelines
+ # https://gitlab.com/gitlab-org/gitlab/issues/208389
+ - run_timed_command "scripts/build_assets_image"
+ retry: 2
diff --git a/.gitlab/ci/docs.gitlab-ci.yml b/.gitlab/ci/docs.gitlab-ci.yml
index 5a6f2aacf93..8745e7d8e9e 100644
--- a/.gitlab/ci/docs.gitlab-ci.yml
+++ b/.gitlab/ci/docs.gitlab-ci.yml
@@ -59,6 +59,15 @@ docs lint:
# Check the internal anchor links
- bundle exec nanoc check internal_anchors
+ui-docs-links lint:
+ extends:
+ - .docs:rules:docs-lint
+ - .static-analysis-base
+ stage: test
+ needs: []
+ script:
+ - bundle exec haml-lint -i DocumentationLinks
+
graphql-reference-verify:
extends:
- .default-retry
diff --git a/.gitlab/ci/frontend.gitlab-ci.yml b/.gitlab/ci/frontend.gitlab-ci.yml
index 4403187d422..084a48a7fc6 100644
--- a/.gitlab/ci/frontend.gitlab-ci.yml
+++ b/.gitlab/ci/frontend.gitlab-ci.yml
@@ -2,16 +2,18 @@
extends:
- .default-retry
- .default-before_script
- - .assets-compile-cache
variables:
SETUP_DB: "false"
# we override the max_old_space_size to prevent OOM errors
NODE_OPTIONS: --max_old_space_size=3584
- WEBPACK_VENDOR_DLL: "true"
.compile-assets-base:
- extends: .frontend-base
+ extends:
+ - .frontend-base
+ - .assets-compile-cache
image: registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.6.6-git-2.27-lfs-2.9-node-12.x-yarn-1.21-graphicsmagick-1.3.34
+ variables:
+ WEBPACK_VENDOR_DLL: "true"
stage: prepare
script:
- node --version
@@ -90,21 +92,6 @@ update-yarn-cache:
cache:
policy: push
-build-assets-image:
- extends:
- - .use-kaniko
- - .frontend:rules:compile-production-assets
- stage: build-images
- needs: ["compile-production-assets"]
- variables:
- GIT_DEPTH: "1"
- script:
- # TODO: Change the image tag to be the MD5 of assets files and skip image building if the image exists
- # We'll also need to pass GITLAB_ASSETS_TAG to the trigerred omnibus-gitlab pipeline similarly to how we do it for trigerred CNG pipelines
- # https://gitlab.com/gitlab-org/gitlab/issues/208389
- - run_timed_command "scripts/build_assets_image"
- retry: 2
-
.frontend-fixtures-base:
extends:
- .frontend-base
@@ -114,6 +101,7 @@ build-assets-image:
needs: ["setup-test-env", "compile-test-assets"]
variables:
SETUP_DB: "true"
+ WEBPACK_VENDOR_DLL: "true"
script:
- run_timed_command "scripts/gitaly-test-build"
- run_timed_command "scripts/gitaly-test-spawn"
@@ -138,22 +126,25 @@ frontend-fixtures-as-if-foss:
.frontend-test-base:
extends:
- - .default-retry
+ - .frontend-base
- .yarn-cache
variables:
USE_BUNDLE_INSTALL: "false"
- SETUP_DB: "false"
stage: test
- before_script:
- - source scripts/utils.sh
+
+eslint-as-if-foss:
+ extends:
+ - .frontend-test-base
+ - .frontend:rules:eslint-as-if-foss
+ - .as-if-foss
+ needs: []
+ script:
+ - run_timed_command "retry yarn install --frozen-lockfile"
+ - yarn run eslint
.karma-base:
extends: .frontend-test-base
- variables:
- # we override the max_old_space_size to prevent OOM errors
- NODE_OPTIONS: --max_old_space_size=3584
script:
- - source scripts/utils.sh
- export BABEL_ENV=coverage CHROME_LOG_FILE=chrome_debug.log
- run_timed_command "retry yarn install --frozen-lockfile"
- run_timed_command "yarn karma"
@@ -174,6 +165,7 @@ karma:
- tmp/tests/frontend/
reports:
junit: junit_karma.xml
+ cobertura: coverage-javascript/cobertura-coverage.xml
karma-as-if-foss:
extends:
@@ -185,7 +177,6 @@ karma-as-if-foss:
.jest-base:
extends: .frontend-test-base
script:
- - source scripts/utils.sh
- run_timed_command "retry yarn install --frozen-lockfile"
- run_timed_command "yarn jest --ci --coverage --testSequencer ./scripts/frontend/parallel_ci_sequencer.js"
@@ -211,7 +202,6 @@ jest-integration:
- .frontend-test-base
- .frontend:rules:default-frontend-jobs
script:
- - source scripts/utils.sh
- run_timed_command "retry yarn install --frozen-lockfile"
- run_timed_command "yarn jest:integration --ci"
needs: ["frontend-fixtures"]
@@ -236,11 +226,14 @@ coverage-frontend:
- run_timed_command "retry yarn install --frozen-lockfile"
script:
- run_timed_command "yarn node scripts/frontend/merge_coverage_frontend.js"
+ coverage: '/^Statements\s*:\s*?(\d+(?:\.\d+)?)%/'
artifacts:
name: coverage-frontend
expire_in: 31d
paths:
- coverage-frontend/
+ reports:
+ cobertura: coverage-frontend/cobertura-coverage.xml
.qa-frontend-node:
extends:
diff --git a/.gitlab/ci/global.gitlab-ci.yml b/.gitlab/ci/global.gitlab-ci.yml
index 30e3abf13be..3101a42c058 100644
--- a/.gitlab/ci/global.gitlab-ci.yml
+++ b/.gitlab/ci/global.gitlab-ci.yml
@@ -18,7 +18,7 @@
.rails-cache:
cache:
- key: "rails-v1"
+ key: "rails-v2"
paths:
- vendor/ruby/
- vendor/gitaly-ruby/
@@ -72,6 +72,15 @@
variables:
POSTGRES_HOST_AUTH_METHOD: trust
+.use-pg12:
+ image: "registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.6.6-golang-1.14-git-2.27-lfs-2.9-chrome-83-node-12.x-yarn-1.21-postgresql-12-graphicsmagick-1.3.34"
+ services:
+ - name: postgres:12
+ command: ["postgres", "-c", "fsync=off", "-c", "synchronous_commit=off", "-c", "full_page_writes=off"]
+ - name: redis:alpine
+ variables:
+ POSTGRES_HOST_AUTH_METHOD: trust
+
.use-pg11-ee:
image: "registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.6.6-golang-1.14-git-2.27-lfs-2.9-chrome-83-node-12.x-yarn-1.21-postgresql-11-graphicsmagick-1.3.34"
services:
@@ -82,6 +91,16 @@
variables:
POSTGRES_HOST_AUTH_METHOD: trust
+.use-pg12-ee:
+ image: "registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.6.6-golang-1.14-git-2.27-lfs-2.9-chrome-83-node-12.x-yarn-1.21-postgresql-12-graphicsmagick-1.3.34"
+ services:
+ - name: postgres:12
+ command: ["postgres", "-c", "fsync=off", "-c", "synchronous_commit=off", "-c", "full_page_writes=off"]
+ - name: redis:alpine
+ - name: elasticsearch:6.4.2
+ variables:
+ POSTGRES_HOST_AUTH_METHOD: trust
+
.use-kaniko:
image:
name: gcr.io/kaniko-project/executor:debug-v0.20.0
diff --git a/.gitlab/ci/qa.gitlab-ci.yml b/.gitlab/ci/qa.gitlab-ci.yml
index 20527b690a7..9a81ea513b7 100644
--- a/.gitlab/ci/qa.gitlab-ci.yml
+++ b/.gitlab/ci/qa.gitlab-ci.yml
@@ -49,7 +49,6 @@ update-qa-cache:
.package-and-qa-base:
image: ruby:2.6-alpine
stage: qa
- dependencies: []
retry: 0
script:
- source scripts/utils.sh
diff --git a/.gitlab/ci/rails.gitlab-ci.yml b/.gitlab/ci/rails.gitlab-ci.yml
index f73e0c1d503..4cef4ee26ff 100644
--- a/.gitlab/ci/rails.gitlab-ci.yml
+++ b/.gitlab/ci/rails.gitlab-ci.yml
@@ -1,9 +1,129 @@
+######################
+# rspec job base specs
.rails-job-base:
extends:
- .default-retry
- .default-before_script
- .rails-cache
+.rspec-base:
+ extends: .rails-job-base
+ stage: test
+ needs: ["setup-test-env", "retrieve-tests-metadata", "compile-test-assets"]
+ script:
+ - run_timed_command "scripts/gitaly-test-build"
+ - run_timed_command "scripts/gitaly-test-spawn"
+ - source scripts/rspec_helpers.sh
+ - rspec_paralellized_job "--tag ~quarantine --tag ~geo --tag ~level:migration"
+ artifacts:
+ expire_in: 31d
+ when: always
+ paths:
+ - coverage/
+ - knapsack/
+ - rspec_flaky/
+ - rspec_profiling/
+ - tmp/capybara/
+ - tmp/memory_test/
+ - log/*.log
+ reports:
+ junit: junit_rspec.xml
+
+.rspec-base-migration:
+ extends: .rails:rules:ee-and-foss-migration
+ script:
+ - run_timed_command "scripts/gitaly-test-build"
+ - run_timed_command "scripts/gitaly-test-spawn"
+ - source scripts/rspec_helpers.sh
+ - rspec_paralellized_job "--tag ~quarantine --tag ~geo --tag level:migration"
+
+.rspec-base-pg11:
+ extends:
+ - .rspec-base
+ - .use-pg11
+
+.rspec-base-pg12:
+ extends:
+ - .rspec-base
+ - .use-pg12
+
+.rspec-base-pg11-as-if-foss:
+ extends:
+ - .rspec-base
+ - .as-if-foss
+ - .use-pg11
+ needs: ["setup-test-env", "retrieve-tests-metadata", "compile-test-assets as-if-foss"]
+
+.rspec-ee-base-pg11:
+ extends:
+ - .rspec-base
+ - .use-pg11-ee
+
+.rspec-ee-base-pg12:
+ extends:
+ - .rspec-base
+ - .use-pg12-ee
+
+.rspec-ee-base-geo:
+ extends: .rspec-base
+ script:
+ - run_timed_command "scripts/gitaly-test-build"
+ - run_timed_command "scripts/gitaly-test-spawn"
+ - source scripts/rspec_helpers.sh
+ - scripts/prepare_postgres_fdw.sh
+ - rspec_paralellized_job "--tag ~quarantine --tag geo"
+
+.rspec-ee-base-geo-pg11:
+ extends:
+ - .rspec-ee-base-geo
+ - .use-pg11-ee
+
+.rspec-ee-base-geo-pg12:
+ extends:
+ - .rspec-ee-base-geo
+ - .use-pg12-ee
+
+.db-job-base:
+ extends:
+ - .rails-job-base
+ - .rails:rules:ee-and-foss-migration
+ - .use-pg11
+ stage: test
+ needs: ["setup-test-env"]
+# rspec job base specs
+######################
+
+############################
+# rspec job parallel configs
+.rspec-migration-parallel:
+ parallel: 5
+
+.rspec-ee-migration-parallel:
+ parallel: 2
+
+.rspec-unit-parallel:
+ parallel: 20
+
+.rspec-ee-unit-parallel:
+ parallel: 10
+
+.rspec-ee-unit-geo-parallel:
+ parallel: 2
+
+.rspec-integration-parallel:
+ parallel: 8
+
+.rspec-ee-integration-parallel:
+ parallel: 4
+
+.rspec-system-parallel:
+ parallel: 24
+
+.rspec-ee-system-parallel:
+ parallel: 6
+# rspec job parallel configs
+############################
+
#######################################################
# EE/FOSS: default refs (MRs, master, schedules) jobs #
setup-test-env:
@@ -86,73 +206,37 @@ downtime_check:
script:
- bundle exec rake downtime_check
-.rspec-base:
- extends: .rails-job-base
- stage: test
- needs: ["setup-test-env", "retrieve-tests-metadata", "compile-test-assets"]
- script:
- - run_timed_command "scripts/gitaly-test-build"
- - run_timed_command "scripts/gitaly-test-spawn"
- - source scripts/rspec_helpers.sh
- - rspec_paralellized_job "--tag ~quarantine --tag ~geo --tag ~level:migration"
- artifacts:
- expire_in: 31d
- when: always
- paths:
- - coverage/
- - knapsack/
- - rspec_flaky/
- - rspec_profiling/
- - tmp/capybara/
- - tmp/memory_test/
- - log/*.log
- reports:
- junit: junit_rspec.xml
-
-.rspec-base-pg11:
- extends:
- - .rspec-base
- - .rails:rules:ee-and-foss
- - .use-pg11
-
-.rspec-base-migration:
- script:
- - run_timed_command "scripts/gitaly-test-build"
- - run_timed_command "scripts/gitaly-test-spawn"
- - source scripts/rspec_helpers.sh
- - rspec_paralellized_job "--tag ~quarantine --tag ~geo --tag level:migration"
-
rspec migration pg11:
extends:
- .rspec-base-pg11
- .rspec-base-migration
- parallel: 5
+ - .rspec-migration-parallel
rspec unit pg11:
- extends: .rspec-base-pg11
- parallel: 20
+ extends:
+ - .rspec-base-pg11
+ - .rails:rules:ee-and-foss-unit
+ - .rspec-unit-parallel
rspec integration pg11:
- extends: .rspec-base-pg11
- parallel: 8
+ extends:
+ - .rspec-base-pg11
+ - .rails:rules:ee-and-foss-integration
+ - .rspec-integration-parallel
rspec system pg11:
- extends: .rspec-base-pg11
- parallel: 24
+ extends:
+ - .rspec-base-pg11
+ - .rails:rules:ee-and-foss-system
+ - .rspec-system-parallel
rspec fast_spec_helper:
- extends: .rspec-base-pg11
+ extends:
+ - .rspec-base-pg11
+ - .rails:rules:ee-and-foss-fast_spec_helper
script:
- bin/rspec spec/fast_spec_helper.rb
-.db-job-base:
- extends:
- - .rails-job-base
- - .rails:rules:ee-and-foss
- - .use-pg11
- stage: test
- needs: ["setup-test-env"]
-
db:migrate:reset:
extends: .db-job-base
script:
@@ -216,7 +300,7 @@ gitlab:setup:
rspec:coverage:
extends:
- .rails-job-base
- - .rails:rules:ee-mr-and-master-only
+ - .rails:rules:rspec-coverage
stage: post-test
# We cannot use needs since it would mean needing 84 jobs (since most are parallelized)
# so we use `dependencies` here.
@@ -248,118 +332,180 @@ rspec:coverage:
- coverage/index.html
- coverage/assets/
- tmp/memory_test/
+ reports:
+ cobertura: coverage/coverage.xml
# EE/FOSS: default refs (MRs, master, schedules) jobs #
#######################################################
##################################################
# EE: default refs (MRs, master, schedules) jobs #
-.rspec-base-ee:
- extends:
- - .rspec-base
- - .rails:rules:ee-only
-
-.rspec-base-pg11-as-if-foss:
- extends:
- - .rspec-base
- - .rails:rules:as-if-foss
- - .as-if-foss
- - .use-pg11
- needs: ["setup-test-env", "retrieve-tests-metadata", "compile-test-assets as-if-foss"]
-
-.rspec-ee-base-pg11:
- extends:
- - .rspec-base-ee
- - .use-pg11-ee
-
rspec migration pg11-as-if-foss:
extends:
- .rspec-base-pg11-as-if-foss
- .rspec-base-migration
- parallel: 5
+ - .rails:rules:as-if-foss-migration
+ - .rspec-migration-parallel
rspec unit pg11-as-if-foss:
- extends: .rspec-base-pg11-as-if-foss
- parallel: 20
+ extends:
+ - .rspec-base-pg11-as-if-foss
+ - .rails:rules:as-if-foss-unit
+ - .rspec-unit-parallel
rspec integration pg11-as-if-foss:
- extends: .rspec-base-pg11-as-if-foss
- parallel: 8
+ extends:
+ - .rspec-base-pg11-as-if-foss
+ - .rails:rules:as-if-foss-integration
+ - .rspec-integration-parallel
rspec system pg11-as-if-foss:
- extends: .rspec-base-pg11-as-if-foss
- parallel: 24
+ extends:
+ - .rspec-base-pg11-as-if-foss
+ - .rails:rules:as-if-foss-system
+ - .rspec-system-parallel
rspec-ee migration pg11:
extends:
- .rspec-ee-base-pg11
- .rspec-base-migration
- parallel: 2
+ - .rails:rules:ee-only-migration
+ - .rspec-ee-migration-parallel
rspec-ee unit pg11:
- extends: .rspec-ee-base-pg11
- parallel: 10
+ extends:
+ - .rspec-ee-base-pg11
+ - .rails:rules:ee-only-unit
+ - .rspec-ee-unit-parallel
rspec-ee integration pg11:
- extends: .rspec-ee-base-pg11
- parallel: 4
+ extends:
+ - .rspec-ee-base-pg11
+ - .rails:rules:ee-only-integration
+ - .rspec-ee-integration-parallel
rspec-ee system pg11:
- extends: .rspec-ee-base-pg11
- parallel: 6
-
-.rspec-ee-base-geo:
- extends: .rspec-base-ee
- script:
- - run_timed_command "scripts/gitaly-test-build"
- - run_timed_command "scripts/gitaly-test-spawn"
- - source scripts/rspec_helpers.sh
- - scripts/prepare_postgres_fdw.sh
- - rspec_paralellized_job "--tag ~quarantine --tag geo"
-
-.rspec-ee-base-geo-pg11:
extends:
- - .rspec-ee-base-geo
- - .use-pg11-ee
+ - .rspec-ee-base-pg11
+ - .rails:rules:ee-only-system
+ - .rspec-ee-system-parallel
rspec-ee unit pg11 geo:
- extends: .rspec-ee-base-geo-pg11
- parallel: 2
+ extends:
+ - .rspec-ee-base-geo-pg11
+ - .rails:rules:ee-only-unit
+ - .rspec-ee-unit-geo-parallel
rspec-ee integration pg11 geo:
- extends: .rspec-ee-base-geo-pg11
+ extends:
+ - .rspec-ee-base-geo-pg11
+ - .rails:rules:ee-only-integration
rspec-ee system pg11 geo:
- extends: .rspec-ee-base-geo-pg11
+ extends:
+ - .rspec-ee-base-geo-pg11
+ - .rails:rules:ee-only-system
db:rollback geo:
extends:
- db:rollback
- - .rails:rules:ee-only
+ - .rails:rules:ee-only-migration
script:
- bundle exec rake geo:db:migrate VERSION=20170627195211
- bundle exec rake geo:db:migrate
# EE: default refs (MRs, master, schedules) jobs #
##################################################
+##########################################
+# EE/FOSS: master nightly scheduled jobs #
+rspec migration pg12:
+ extends:
+ - .rspec-base-pg12
+ - .rspec-base-migration
+ - .rails:rules:master-schedule-nightly--code-backstage
+ - .rspec-migration-parallel
+
+rspec unit pg12:
+ extends:
+ - .rspec-base-pg12
+ - .rails:rules:master-schedule-nightly--code-backstage
+ - .rspec-unit-parallel
+
+rspec integration pg12:
+ extends:
+ - .rspec-base-pg12
+ - .rails:rules:master-schedule-nightly--code-backstage
+ - .rspec-integration-parallel
+
+rspec system pg12:
+ extends:
+ - .rspec-base-pg12
+ - .rails:rules:master-schedule-nightly--code-backstage
+ - .rspec-system-parallel
+# EE/FOSS: master nightly scheduled jobs #
+##########################################
+
+#####################################
+# EE: master nightly scheduled jobs #
+rspec-ee migration pg12:
+ extends:
+ - .rspec-ee-base-pg12
+ - .rspec-base-migration
+ - .rails:rules:master-schedule-nightly--code-backstage-ee-only
+ - .rspec-ee-migration-parallel
+
+rspec-ee unit pg12:
+ extends:
+ - .rspec-ee-base-pg12
+ - .rails:rules:master-schedule-nightly--code-backstage-ee-only
+ - .rspec-ee-unit-parallel
+
+rspec-ee integration pg12:
+ extends:
+ - .rspec-ee-base-pg12
+ - .rails:rules:master-schedule-nightly--code-backstage-ee-only
+ - .rspec-ee-integration-parallel
+
+rspec-ee system pg12:
+ extends:
+ - .rspec-ee-base-pg12
+ - .rails:rules:master-schedule-nightly--code-backstage-ee-only
+ - .rspec-ee-system-parallel
+
+rspec-ee unit pg12 geo:
+ extends:
+ - .rspec-ee-base-geo-pg12
+ - .rails:rules:master-schedule-nightly--code-backstage-ee-only
+ - .rspec-ee-unit-geo-parallel
+
+rspec-ee integration pg12 geo:
+ extends:
+ - .rspec-ee-base-geo-pg12
+ - .rails:rules:master-schedule-nightly--code-backstage-ee-only
+
+rspec-ee system pg12 geo:
+ extends:
+ - .rspec-ee-base-geo-pg12
+ - .rails:rules:master-schedule-nightly--code-backstage-ee-only
+# EE: master nightly scheduled jobs #
+#####################################
+
##################################################
# EE: Canonical MR pipelines
rspec foss-impact:
extends:
- - .rspec-base
- - .as-if-foss
+ - .rspec-base-pg11-as-if-foss
- .rails:rules:ee-mr-only
- - .use-pg11
script:
- install_gitlab_gem
- run_timed_command "scripts/gitaly-test-build"
- run_timed_command "scripts/gitaly-test-spawn"
- source scripts/rspec_helpers.sh
- tooling/bin/find_foss_tests tmp/matching_foss_tests.txt
- - rspec_matched_tests tmp/matching_foss_tests.txt "--tag ~quarantine --tag ~geo --tag ~level:migration"
+ - rspec_matched_tests tmp/matching_foss_tests.txt "--tag ~quarantine"
artifacts:
expire_in: 7d
paths:
- tmp/matching_foss_tests.txt
- tmp/capybara/
-# EE: Merge Request pipelines
+# EE: Canonical MR pipelines
##################################################
diff --git a/.gitlab/ci/reports.gitlab-ci.yml b/.gitlab/ci/reports.gitlab-ci.yml
index 65abb6c5cba..228747ae8d3 100644
--- a/.gitlab/ci/reports.gitlab-ci.yml
+++ b/.gitlab/ci/reports.gitlab-ci.yml
@@ -15,7 +15,7 @@ code_quality:
stage: test
needs: []
variables:
- CODE_QUALITY_IMAGE: "registry.gitlab.com/gitlab-org/ci-cd/codequality:0.85.9"
+ CODE_QUALITY_IMAGE: "registry.gitlab.com/gitlab-org/ci-cd/codequality:0.85.10"
script:
- |
if ! docker info &>/dev/null; then
@@ -59,6 +59,7 @@ code_quality:
SAST_ANALYZER_IMAGE_TAG: 2
SAST_BRAKEMAN_LEVEL: 2 # GitLab-specific
SAST_EXCLUDED_PATHS: qa,spec,doc,ee/spec # GitLab-specific
+ SAST_DISABLE_BABEL: "true"
script:
- /analyzer run
@@ -72,11 +73,10 @@ eslint-sast:
image:
name: "$SAST_ANALYZER_IMAGE_PREFIX/eslint:$SAST_ANALYZER_IMAGE_TAG"
-# Temporary disabled as it's constantly failing. See https://gitlab.com/gitlab-org/gitlab/-/issues/213769.
-# nodejs-scan-sast:
-# extends: .sast
-# image:
-# name: "$SAST_ANALYZER_IMAGE_PREFIX/nodejs-scan:$SAST_ANALYZER_IMAGE_TAG"
+nodejs-scan-sast:
+ extends: .sast
+ image:
+ name: "$SAST_ANALYZER_IMAGE_PREFIX/nodejs-scan:$SAST_ANALYZER_IMAGE_TAG"
secrets-sast:
extends: .sast
@@ -172,6 +172,7 @@ dependency_scanning:
# # - 'export DAST_AUTH_URL="${DAST_WEBSITE}/users/sign_in"'
# # - 'export DAST_PASSWORD="${REVIEW_APPS_ROOT_PASSWORD}"'
# - /analyze -t $DAST_WEBSITE
+# timeout: 4h
# artifacts:
# paths:
# - gl-dast-report.json # GitLab-specific
diff --git a/.gitlab/ci/review.gitlab-ci.yml b/.gitlab/ci/review.gitlab-ci.yml
index 6898da95c15..4e3a80372a6 100644
--- a/.gitlab/ci/review.gitlab-ci.yml
+++ b/.gitlab/ci/review.gitlab-ci.yml
@@ -1,14 +1,3 @@
-build-qa-image:
- extends:
- - .use-kaniko
- - .review:rules:build-qa-image
- stage: build-images
- needs: []
- script:
- - export QA_IMAGE="${CI_REGISTRY}/${CI_PROJECT_PATH}/gitlab-ee-qa:${CI_COMMIT_REF_SLUG}"
- - /kaniko/executor --context=${CI_PROJECT_DIR} --dockerfile=${CI_PROJECT_DIR}/qa/Dockerfile --destination=${QA_IMAGE} --cache=true
- retry: 2
-
review-cleanup:
extends:
- .default-retry
@@ -27,25 +16,24 @@ review-cleanup:
- ruby -rrubygems scripts/review_apps/automated_cleanup.rb
- gcp_cleanup
-# Temporarily disabling review apps
-#review-build-cng:
-# extends:
-# - .default-retry
-# - .review:rules:review-build-cng
-# image: ruby:2.6-alpine
-# stage: review-prepare
-# before_script:
-# - source scripts/utils.sh
-# - install_api_client_dependencies_with_apk
-# - install_gitlab_gem
-# needs:
-# - job: compile-production-assets
-# artifacts: false
-# script:
-# - BUILD_TRIGGER_TOKEN=$REVIEW_APPS_BUILD_TRIGGER_TOKEN ./scripts/trigger-build cng
-# # When the job is manual, review-deploy is also manual and we don't want people
-# # to have to manually start the jobs in sequence, so we do it for them.
-# - '[ -z $CI_JOB_MANUAL ] || play_job "review-deploy"'
+review-build-cng:
+ extends:
+ - .default-retry
+ - .review:rules:review-build-cng
+ image: ruby:2.6-alpine
+ stage: review-prepare
+ before_script:
+ - source scripts/utils.sh
+ - install_api_client_dependencies_with_apk
+ - install_gitlab_gem
+ needs:
+ - job: compile-production-assets
+ artifacts: false
+ script:
+ - BUILD_TRIGGER_TOKEN=$REVIEW_APPS_BUILD_TRIGGER_TOKEN ./scripts/trigger-build cng
+ # When the job is manual, review-deploy is also manual and we don't want people
+ # to have to manually start the jobs in sequence, so we do it for them.
+ - '[ -z $CI_JOB_MANUAL ] || play_job "review-deploy"'
.review-workflow-base:
extends:
@@ -53,45 +41,46 @@ review-cleanup:
image: registry.gitlab.com/gitlab-org/gitlab-build-images:gitlab-helm3-kubectl1.14
variables:
HOST_SUFFIX: "${CI_ENVIRONMENT_SLUG}"
+ REVIEW_APPS_DOMAIN: "temp.gitlab-review.app" # FIXME: using temporary domain
DOMAIN: "-${CI_ENVIRONMENT_SLUG}.${REVIEW_APPS_DOMAIN}"
- GITLAB_HELM_CHART_REF: "master"
+ GITLAB_HELM_CHART_REF: "v4.1.3"
environment:
name: review/${CI_COMMIT_REF_NAME}
url: https://gitlab-${CI_ENVIRONMENT_SLUG}.${REVIEW_APPS_DOMAIN}
on_stop: review-stop
auto_stop_in: 48 hours
-# Temporarily disabling review apps
-#review-deploy:
-# extends:
-# - .review-workflow-base
-# - .review:rules:mr-and-schedule-auto-if-frontend-manual-otherwise
-# stage: review
-# dependencies: []
-# resource_group: "review/${CI_COMMIT_REF_NAME}"
-# before_script:
-# - export GITLAB_SHELL_VERSION=$(<GITLAB_SHELL_VERSION)
-# - export GITALY_VERSION=$(<GITALY_SERVER_VERSION)
-# - export GITLAB_WORKHORSE_VERSION=$(<GITLAB_WORKHORSE_VERSION)
-# - echo "${CI_ENVIRONMENT_URL}" > environment_url.txt
-# - source ./scripts/utils.sh
-# - install_api_client_dependencies_with_apk
-# - source scripts/review_apps/review-apps.sh
-# script:
-# - check_kube_domain
-# - ensure_namespace
-# - install_external_dns
-# - download_chart
-# - date
-# - deploy || (display_deployment_debug && exit 1)
-# # When the job is manual, review-qa-smoke is also manual and we don't want people
-# # to have to manually start the jobs in sequence, so we do it for them.
-# - '[ -z $CI_JOB_MANUAL ] || play_job "review-qa-smoke"'
-# - '[ -z $CI_JOB_MANUAL ] || play_job "review-performance"'
-# artifacts:
-# paths: [environment_url.txt]
-# expire_in: 2 days
-# when: always
+review-deploy:
+ extends:
+ - .review-workflow-base
+ - .review:rules:mr-and-schedule-auto-if-frontend-manual-otherwise
+ stage: review
+ dependencies: []
+ resource_group: "review/${CI_COMMIT_REF_NAME}"
+ before_script:
+ - export GITLAB_SHELL_VERSION=$(<GITLAB_SHELL_VERSION)
+ - export GITALY_VERSION=$(<GITALY_SERVER_VERSION)
+ - export GITLAB_WORKHORSE_VERSION=$(<GITLAB_WORKHORSE_VERSION)
+ - echo "${CI_ENVIRONMENT_URL}" > environment_url.txt
+ - source ./scripts/utils.sh
+ - install_api_client_dependencies_with_apk
+ - source scripts/review_apps/review-apps.sh
+ script:
+ - check_kube_domain
+ - ensure_namespace
+ - install_external_dns
+ - download_chart
+ - date
+ - deploy || (display_deployment_debug && exit 1)
+ - disable_sign_ups
+ # When the job is manual, review-qa-smoke is also manual and we don't want people
+ # to have to manually start the jobs in sequence, so we do it for them.
+ - '[ -z $CI_JOB_MANUAL ] || play_job "review-qa-smoke"'
+ - '[ -z $CI_JOB_MANUAL ] || play_job "review-performance"'
+ artifacts:
+ paths: [environment_url.txt]
+ expire_in: 2 days
+ when: always
.review-stop-base:
extends: .review-workflow-base
@@ -124,110 +113,110 @@ review-stop:
script:
- delete_release
-# Temporarily disabling review apps
-#.review-qa-base:
-# extends:
-# - .default-retry
-# - .use-docker-in-docker
-# image: registry.gitlab.com/gitlab-org/gitlab-build-images:gitlab-qa-alpine-ruby-2.6
-# stage: qa
-# # This is needed so that manual jobs with needs don't block the pipeline.
-# # See https://gitlab.com/gitlab-org/gitlab/-/issues/199979.
-# dependencies: ["review-deploy"]
-# variables:
-# QA_ARTIFACTS_DIR: "${CI_PROJECT_DIR}/qa"
-# QA_CAN_TEST_GIT_PROTOCOL_V2: "false"
-# QA_DEBUG: "true"
-# GITLAB_USERNAME: "root"
-# GITLAB_PASSWORD: "${REVIEW_APPS_ROOT_PASSWORD}"
-# GITLAB_ADMIN_USERNAME: "root"
-# GITLAB_ADMIN_PASSWORD: "${REVIEW_APPS_ROOT_PASSWORD}"
-# GITHUB_ACCESS_TOKEN: "${REVIEW_APPS_QA_GITHUB_ACCESS_TOKEN}"
-# EE_LICENSE: "${REVIEW_APPS_EE_LICENSE}"
-# before_script:
-# - export QA_IMAGE="${CI_REGISTRY}/${CI_PROJECT_PATH}/gitlab-ee-qa:${CI_COMMIT_REF_SLUG}"
-# - export CI_ENVIRONMENT_URL="$(cat environment_url.txt)"
-# - echo "${CI_ENVIRONMENT_URL}"
-# - echo "${QA_IMAGE}"
-# - source scripts/utils.sh
-# - install_api_client_dependencies_with_apk
-# - gem install gitlab-qa --no-document ${GITLAB_QA_VERSION:+ --version ${GITLAB_QA_VERSION}}
-# artifacts:
-# paths:
-# - ./qa/gitlab-qa-run-*
-# expire_in: 7 days
-# when: always
-#
-#review-qa-smoke:
-# extends:
-# - .review-qa-base
-# - .review:rules:review-qa-smoke
-# script:
-# - gitlab-qa Test::Instance::Smoke "${QA_IMAGE}" "${CI_ENVIRONMENT_URL}"
-#
-#review-qa-all:
-# extends:
-# - .review-qa-base
-# - .review:rules:mr-only-manual
-# parallel: 5
-# script:
-# - export KNAPSACK_REPORT_PATH=knapsack/master_report.json
-# - export KNAPSACK_TEST_FILE_PATTERN=qa/specs/features/**/*_spec.rb
-# - gitlab-qa Test::Instance::Any "${QA_IMAGE}" "${CI_ENVIRONMENT_URL}" -- --format RspecJunitFormatter --out tmp/rspec-${CI_JOB_ID}.xml --format html --out tmp/rspec.htm --color --format documentation
-#
-#review-performance:
-# extends:
-# - .default-retry
-# - .review:rules:mr-and-schedule-auto-if-frontend-manual-otherwise
-# image:
-# name: sitespeedio/sitespeed.io:6.3.1
-# entrypoint: [""]
-# stage: qa
-# # This is needed so that manual jobs with needs don't block the pipeline.
-# # See https://gitlab.com/gitlab-org/gitlab/-/issues/199979.
-# dependencies: ["review-deploy"]
-# before_script:
-# - export CI_ENVIRONMENT_URL="$(cat environment_url.txt)"
-# - echo "${CI_ENVIRONMENT_URL}"
-# - mkdir -p gitlab-exporter
-# - wget -O ./gitlab-exporter/index.js https://gitlab.com/gitlab-org/gl-performance/raw/master/index.js
-# - mkdir -p sitespeed-results
-# script:
-# - /start.sh --plugins.add ./gitlab-exporter --outputFolder sitespeed-results "${CI_ENVIRONMENT_URL}"
-# after_script:
-# - mv sitespeed-results/data/performance.json performance.json
-# artifacts:
-# paths:
-# - sitespeed-results/
-# reports:
-# performance: performance.json
-# expire_in: 31d
-#
-#parallel-spec-reports:
-# extends:
-# - .review:rules:mr-only-manual
-# image: ruby:2.6-alpine
-# stage: post-qa
-# dependencies: ["review-qa-all"]
-# variables:
-# NEW_PARALLEL_SPECS_REPORT: qa/report-new.html
-# BASE_ARTIFACT_URL: "${CI_PROJECT_URL}/-/jobs/${CI_JOB_ID}/artifacts/file/qa/"
-# script:
-# - apk add --update build-base libxml2-dev libxslt-dev && rm -rf /var/cache/apk/*
-# - gem install nokogiri --no-document
-# - cd qa/gitlab-qa-run-*/gitlab-*
-# - ARTIFACT_DIRS=$(pwd |rev| awk -F / '{print $1,$2}' | rev | sed s_\ _/_)
-# - cd -
-# - '[[ -f $NEW_PARALLEL_SPECS_REPORT ]] || echo "{}" > ${NEW_PARALLEL_SPECS_REPORT}'
-# - scripts/merge-html-reports ${NEW_PARALLEL_SPECS_REPORT} ${BASE_ARTIFACT_URL}${ARTIFACT_DIRS} qa/gitlab-qa-run-*/**/rspec.htm
-# artifacts:
-# when: always
-# paths:
-# - qa/report-new.html
-# - qa/gitlab-qa-run-*
-# reports:
-# junit: qa/gitlab-qa-run-*/**/rspec-*.xml
-# expire_in: 31d
+.review-qa-base:
+ extends:
+ - .default-retry
+ - .use-docker-in-docker
+ image: registry.gitlab.com/gitlab-org/gitlab-build-images:gitlab-qa-alpine-ruby-2.6
+ stage: qa
+ # This is needed so that manual jobs with needs don't block the pipeline.
+ # See https://gitlab.com/gitlab-org/gitlab/-/issues/199979.
+ dependencies: ["review-deploy"]
+ variables:
+ QA_ARTIFACTS_DIR: "${CI_PROJECT_DIR}/qa"
+ QA_CAN_TEST_GIT_PROTOCOL_V2: "false"
+ QA_DEBUG: "true"
+ GITLAB_USERNAME: "root"
+ GITLAB_PASSWORD: "${REVIEW_APPS_ROOT_PASSWORD}"
+ GITLAB_ADMIN_USERNAME: "root"
+ GITLAB_ADMIN_PASSWORD: "${REVIEW_APPS_ROOT_PASSWORD}"
+ GITHUB_ACCESS_TOKEN: "${REVIEW_APPS_QA_GITHUB_ACCESS_TOKEN}"
+ EE_LICENSE: "${REVIEW_APPS_EE_LICENSE}"
+ SIGNUP_DISABLED: "true"
+ before_script:
+ - export QA_IMAGE="${CI_REGISTRY}/${CI_PROJECT_PATH}/gitlab-ee-qa:${CI_COMMIT_REF_SLUG}"
+ - export CI_ENVIRONMENT_URL="$(cat environment_url.txt)"
+ - echo "${CI_ENVIRONMENT_URL}"
+ - echo "${QA_IMAGE}"
+ - source scripts/utils.sh
+ - install_api_client_dependencies_with_apk
+ - gem install gitlab-qa --no-document ${GITLAB_QA_VERSION:+ --version ${GITLAB_QA_VERSION}}
+ artifacts:
+ paths:
+ - ./qa/gitlab-qa-run-*
+ expire_in: 7 days
+ when: always
+
+review-qa-smoke:
+ extends:
+ - .review-qa-base
+ - .review:rules:review-qa-smoke
+ script:
+ - gitlab-qa Test::Instance::Smoke "${QA_IMAGE}" "${CI_ENVIRONMENT_URL}"
+
+review-qa-all:
+ extends:
+ - .review-qa-base
+ - .review:rules:mr-only-manual
+ parallel: 5
+ script:
+ - export KNAPSACK_REPORT_PATH=knapsack/master_report.json
+ - export KNAPSACK_TEST_FILE_PATTERN=qa/specs/features/**/*_spec.rb
+ - gitlab-qa Test::Instance::Any "${QA_IMAGE}" "${CI_ENVIRONMENT_URL}" -- --format RspecJunitFormatter --out tmp/rspec-${CI_JOB_ID}.xml --format html --out tmp/rspec.htm --color --format documentation
+
+review-performance:
+ extends:
+ - .default-retry
+ - .review:rules:mr-and-schedule-auto-if-frontend-manual-otherwise
+ image:
+ name: sitespeedio/sitespeed.io:6.3.1
+ entrypoint: [""]
+ stage: qa
+ # This is needed so that manual jobs with needs don't block the pipeline.
+ # See https://gitlab.com/gitlab-org/gitlab/-/issues/199979.
+ dependencies: ["review-deploy"]
+ before_script:
+ - export CI_ENVIRONMENT_URL="$(cat environment_url.txt)"
+ - echo "${CI_ENVIRONMENT_URL}"
+ - mkdir -p gitlab-exporter
+ - wget -O ./gitlab-exporter/index.js https://gitlab.com/gitlab-org/gl-performance/raw/master/index.js
+ - mkdir -p sitespeed-results
+ script:
+ - /start.sh --plugins.add ./gitlab-exporter --outputFolder sitespeed-results "${CI_ENVIRONMENT_URL}"
+ after_script:
+ - mv sitespeed-results/data/performance.json performance.json
+ artifacts:
+ paths:
+ - sitespeed-results/
+ reports:
+ performance: performance.json
+ expire_in: 31d
+
+parallel-spec-reports:
+ extends:
+ - .review:rules:mr-only-manual
+ image: ruby:2.6-alpine
+ stage: post-qa
+ dependencies: ["review-qa-all"]
+ variables:
+ NEW_PARALLEL_SPECS_REPORT: qa/report-new.html
+ BASE_ARTIFACT_URL: "${CI_PROJECT_URL}/-/jobs/${CI_JOB_ID}/artifacts/file/qa/"
+ script:
+ - apk add --update build-base libxml2-dev libxslt-dev && rm -rf /var/cache/apk/*
+ - gem install nokogiri --no-document
+ - cd qa/gitlab-qa-run-*/gitlab-*
+ - ARTIFACT_DIRS=$(pwd |rev| awk -F / '{print $1,$2}' | rev | sed s_\ _/_)
+ - cd -
+ - '[[ -f $NEW_PARALLEL_SPECS_REPORT ]] || echo "{}" > ${NEW_PARALLEL_SPECS_REPORT}'
+ - scripts/merge-html-reports ${NEW_PARALLEL_SPECS_REPORT} ${BASE_ARTIFACT_URL}${ARTIFACT_DIRS} qa/gitlab-qa-run-*/**/rspec.htm
+ artifacts:
+ when: always
+ paths:
+ - qa/report-new.html
+ - qa/gitlab-qa-run-*
+ reports:
+ junit: qa/gitlab-qa-run-*/**/rspec-*.xml
+ expire_in: 31d
danger-review:
extends:
diff --git a/.gitlab/ci/rules.gitlab-ci.yml b/.gitlab/ci/rules.gitlab-ci.yml
index fbbb0391ec5..f508bfa1465 100644
--- a/.gitlab/ci/rules.gitlab-ci.yml
+++ b/.gitlab/ci/rules.gitlab-ci.yml
@@ -11,7 +11,7 @@
if: '$CI_PROJECT_NAME != "gitlab-foss" && $CI_PROJECT_NAME != "gitlab-ce" && $CI_PROJECT_NAME != "gitlabhq"'
.if-default-refs: &if-default-refs
- if: '$CI_COMMIT_REF_NAME == "master" || $CI_COMMIT_REF_NAME =~ /^[\d-]+-stable(-ee)?$/ || $CI_COMMIT_REF_NAME =~ /^\d+-\d+-auto-deploy-\d+$/ || $CI_COMMIT_REF_NAME =~ /^security\// || $CI_MERGE_REQUEST_IID || $CI_COMMIT_TAG'
+ if: '$CI_COMMIT_REF_NAME == "master" || $CI_COMMIT_REF_NAME =~ /^[\d-]+-stable(-ee)?$/ || $CI_COMMIT_REF_NAME =~ /^\d+-\d+-auto-deploy-\d+$/ || $CI_COMMIT_REF_NAME =~ /^security\// || $CI_MERGE_REQUEST_IID || $CI_COMMIT_TAG || $FORCE_GITLAB_CI'
.if-master-refs: &if-master-refs
if: '$CI_COMMIT_REF_NAME == "master"'
@@ -40,6 +40,9 @@
.if-merge-request-title-update-caches: &if-merge-request-title-update-caches
if: '$CI_MERGE_REQUEST_TITLE =~ /UPDATE CACHE/'
+.if-merge-request-title-run-all-rspec: &if-merge-request-title-run-all-rspec
+ if: '$CI_MERGE_REQUEST_TITLE =~ /RUN ALL RSPEC/'
+
.if-security-merge-request: &if-security-merge-request
if: '$CI_PROJECT_NAMESPACE == "gitlab-org/security" && $CI_MERGE_REQUEST_IID'
@@ -71,6 +74,22 @@
- ".gitlab-ci.yml"
- ".gitlab/ci/**/*"
+.ci-build-images-patterns: &ci-build-images-patterns
+ - ".gitlab-ci.yml"
+ - ".gitlab/ci/build-images.gitlab-ci.yml"
+
+.ci-review-patterns: &ci-review-patterns
+ - ".gitlab-ci.yml"
+ - ".gitlab/ci/frontend.gitlab-ci.yml"
+ - ".gitlab/ci/build-images.gitlab-ci.yml"
+ - ".gitlab/ci/review.gitlab-ci.yml"
+
+.ci-qa-patterns: &ci-qa-patterns
+ - ".gitlab-ci.yml"
+ - ".gitlab/ci/frontend.gitlab-ci.yml"
+ - ".gitlab/ci/build-images.gitlab-ci.yml"
+ - ".gitlab/ci/qa.gitlab-ci.yml"
+
.yaml-patterns: &yaml-patterns
- "**/*.yml"
@@ -92,6 +111,21 @@
- "vendor/assets/**/*"
- "{,ee/}{app/assets,app/helpers,app/presenters,app/views,locale,public,symbol}/**/*"
+.backend-patterns: &backend-patterns
+ - "Gemfile{,.lock}"
+ - "Rakefile"
+ - "config.ru"
+ # List explicitly all the app/ dirs that are backend (i.e. all except app/assets).
+ - "{,ee/}{app/channels,app/controllers,app/finders,app/graphql,app/helpers,app/mailers,app/models,app/policies,app/presenters,app/serializers,app/services,app/uploaders,app/validators,app/views,app/workers}/**/*"
+ - "{,ee/}{bin,cable,config,db,lib}/**/*"
+ - "{,ee/}spec/**/*.rb"
+ - ".gitlab-ci.yml"
+ - ".gitlab/ci/**/*"
+
+.db-patterns: &db-patterns
+ - "{,ee/}{,spec/}{db,migrations}/**/*"
+ - "{,ee/}{,spec/}lib/{,ee/}gitlab/background_migration/**/*"
+
.backstage-patterns: &backstage-patterns
- "Dangerfile"
- "danger/**/*"
@@ -197,6 +231,26 @@
- <<: *if-master-schedule-2-hourly
- <<: *if-merge-request-title-update-caches
+######################
+# Build images rules #
+######################
+.build-images:rules:build-qa-image:
+ rules:
+ - <<: *if-not-ee
+ when: never
+ - <<: *if-dot-com-gitlab-org-and-security-merge-request
+ changes: *ci-build-images-patterns
+ - <<: *if-dot-com-gitlab-org-and-security-merge-request
+ changes: *code-qa-patterns
+ - <<: *if-dot-com-gitlab-org-schedule
+
+.build-images:rules:build-assets-image:
+ rules:
+ - <<: *if-not-canonical-namespace
+ when: never
+ - changes: *ci-build-images-patterns
+ - changes: *code-qa-patterns
+
####################
# Cache repo rules #
####################
@@ -263,7 +317,7 @@
- <<: *if-not-canonical-namespace
when: never
- <<: *if-default-refs
- changes: *code-backstage-qa-patterns
+ changes: *code-qa-patterns
.frontend:rules:compile-test-assets:
rules:
@@ -273,11 +327,8 @@
rules:
- <<: *if-not-ee
when: never
- - <<: *if-security-merge-request
+ - <<: *if-merge-request # Always run for MRs since `compile-test-assets as-if-foss` is either needed by `rspec foss-impact` or the `rspec * as-if-foss` jobs.
changes: *code-backstage-qa-patterns
- - <<: *if-merge-request-title-as-if-foss
- - <<: *if-merge-request
- changes: *ci-patterns
.frontend:rules:default-frontend-jobs:
rules:
@@ -294,6 +345,15 @@
- <<: *if-merge-request
changes: *ci-patterns
+.frontend:rules:eslint-as-if-foss:
+ rules:
+ - <<: *if-not-ee
+ when: never
+ - <<: *if-merge-request-title-as-if-foss
+ when: never
+ - <<: *if-merge-request
+ changes: *frontend-patterns
+
.frontend:rules:ee-mr-and-master-only:
rules:
- <<: *if-not-ee
@@ -341,9 +401,7 @@
rules:
- <<: *if-not-ee
when: never
- - <<: *if-dot-com-gitlab-org-master
- changes: *code-backstage-qa-patterns
- when: on_success
+ - <<: *if-master-schedule-2-hourly
############
# QA rules #
@@ -367,7 +425,7 @@
.qa:rules:package-and-qa:
rules:
- <<: *if-dot-com-gitlab-org-and-security-merge-request
- changes: *ci-patterns
+ changes: *ci-qa-patterns
allow_failure: true
- <<: *if-dot-com-gitlab-org-and-security-merge-request
changes: *qa-patterns
@@ -382,24 +440,95 @@
###############
# Rails rules #
###############
-.rails:rules:ee-and-foss:
+.rails:rules:ee-and-foss-migration:
rules:
- - <<: *if-default-refs
- changes: *code-backstage-patterns
+ - changes: *db-patterns
+ - <<: *if-merge-request-title-run-all-rspec
+
+.rails:rules:ee-and-foss-unit:
+ rules:
+ - changes: *backend-patterns
+ - <<: *if-merge-request-title-run-all-rspec
+
+.rails:rules:ee-and-foss-integration:
+ rules:
+ - changes: *backend-patterns
+ - <<: *if-merge-request-title-run-all-rspec
+
+.rails:rules:ee-and-foss-system:
+ rules:
+ - changes: *code-backstage-patterns
+ - <<: *if-merge-request-title-run-all-rspec
+
+.rails:rules:ee-and-foss-fast_spec_helper:
+ rules:
+ - changes: ["config/**/*"]
+ - <<: *if-merge-request-title-run-all-rspec
.rails:rules:default-refs-code-backstage-qa:
rules:
- <<: *if-default-refs
changes: *code-backstage-qa-patterns
-.rails:rules:ee-only:
+.rails:rules:ee-only-migration:
rules:
- <<: *if-not-ee
when: never
- - <<: *if-default-refs
- changes: *code-backstage-patterns
+ - changes: *db-patterns
+ - <<: *if-merge-request-title-run-all-rspec
-.rails:rules:as-if-foss:
+.rails:rules:ee-only-unit:
+ rules:
+ - <<: *if-not-ee
+ when: never
+ - changes: *backend-patterns
+ - <<: *if-merge-request-title-run-all-rspec
+
+.rails:rules:ee-only-integration:
+ rules:
+ - <<: *if-not-ee
+ when: never
+ - changes: *backend-patterns
+ - <<: *if-merge-request-title-run-all-rspec
+
+.rails:rules:ee-only-system:
+ rules:
+ - <<: *if-not-ee
+ when: never
+ - changes: *code-backstage-patterns
+ - <<: *if-merge-request-title-run-all-rspec
+
+.rails:rules:as-if-foss-migration:
+ rules:
+ - <<: *if-not-ee
+ when: never
+ - <<: *if-security-merge-request
+ changes: *db-patterns
+ - <<: *if-merge-request-title-as-if-foss
+ - <<: *if-merge-request
+ changes: *ci-patterns
+
+.rails:rules:as-if-foss-unit:
+ rules:
+ - <<: *if-not-ee
+ when: never
+ - <<: *if-security-merge-request
+ changes: *backend-patterns
+ - <<: *if-merge-request-title-as-if-foss
+ - <<: *if-merge-request
+ changes: *ci-patterns
+
+.rails:rules:as-if-foss-integration:
+ rules:
+ - <<: *if-not-ee
+ when: never
+ - <<: *if-security-merge-request
+ changes: *backend-patterns
+ - <<: *if-merge-request-title-as-if-foss
+ - <<: *if-merge-request
+ changes: *ci-patterns
+
+.rails:rules:as-if-foss-system:
rules:
- <<: *if-not-ee
when: never
@@ -413,6 +542,7 @@
rules:
- <<: *if-not-ee
when: never
+ - <<: *if-merge-request-title-run-all-rspec
- <<: *if-merge-request
changes: *code-backstage-patterns
- <<: *if-master-refs
@@ -434,6 +564,27 @@
- <<: *if-merge-request
changes: *code-backstage-patterns
+.rails:rules:rspec-coverage:
+ rules:
+ - <<: *if-not-ee
+ when: never
+ - <<: *if-master-schedule-2-hourly
+ - <<: *if-merge-request-title-run-all-rspec
+
+.rails:rules:master-schedule-nightly--code-backstage:
+ rules:
+ - <<: *if-master-schedule-nightly
+ - <<: *if-merge-request
+ changes: [".gitlab/ci/rails.gitlab-ci.yml"]
+
+.rails:rules:master-schedule-nightly--code-backstage-ee-only:
+ rules:
+ - <<: *if-not-ee
+ when: never
+ - <<: *if-master-schedule-nightly
+ - <<: *if-merge-request
+ changes: [".gitlab/ci/rails.gitlab-ci.yml"]
+
##################
# Releases rules #
##################
@@ -496,18 +647,12 @@
################
# Review rules #
################
-.review:rules:build-qa-image:
+.review:rules:review-build-cng:
rules:
- <<: *if-not-ee
when: never
- - <<: *if-dot-com-gitlab-org-and-security-merge-request
- changes: *code-qa-patterns
- - <<: *if-dot-com-gitlab-org-schedule
-
-.review:rules:review-build-cng:
- rules:
- <<: *if-dot-com-gitlab-org-merge-request
- changes: *ci-patterns
+ changes: *ci-review-patterns
- <<: *if-dot-com-gitlab-org-merge-request
changes: *frontend-patterns
- <<: *if-dot-com-gitlab-org-merge-request
@@ -521,7 +666,7 @@
- <<: *if-not-ee
when: never
- <<: *if-dot-com-gitlab-org-merge-request
- changes: *ci-patterns
+ changes: *ci-review-patterns
- <<: *if-dot-com-gitlab-org-merge-request
changes: *frontend-patterns
allow_failure: true
@@ -544,7 +689,7 @@
- <<: *if-not-ee
when: never
- <<: *if-dot-com-gitlab-org-merge-request
- changes: *ci-patterns
+ changes: *ci-review-patterns
- <<: *if-dot-com-gitlab-org-merge-request
changes: *frontend-patterns
allow_failure: true
diff --git a/.gitlab/ci/setup.gitlab-ci.yml b/.gitlab/ci/setup.gitlab-ci.yml
index b878bec3751..26c7a2194cc 100644
--- a/.gitlab/ci/setup.gitlab-ci.yml
+++ b/.gitlab/ci/setup.gitlab-ci.yml
@@ -9,6 +9,7 @@ cache gems:
stage: test
needs: ["setup-test-env"]
variables:
+ BUNDLE_INSTALL_FLAGS: --with=production --with=development --with=test --jobs=2 --path=vendor --retry=3 --quiet
SETUP_DB: "false"
script:
- bundle package --all --all-platforms
diff --git a/.gitlab/ci/yaml.gitlab-ci.yml b/.gitlab/ci/yaml.gitlab-ci.yml
index ab31dd59299..a650ee7e4b4 100644
--- a/.gitlab/ci/yaml.gitlab-ci.yml
+++ b/.gitlab/ci/yaml.gitlab-ci.yml
@@ -4,11 +4,11 @@ lint-ci-gitlab:
extends:
- .default-retry
- .yaml:rules
- image: sdesbure/yamllint:latest
+ image: pipelinecomponents/yamllint:latest
stage: test
needs: []
variables:
LINT_PATHS: .gitlab-ci.yml .gitlab/ci lib/gitlab/ci/templates changelogs
script:
- '[[ ! -d "ee/" ]] || export LINT_PATHS="$LINT_PATHS ee/changelogs"'
- - yamllint $LINT_PATHS
+ - yamllint -f colored $LINT_PATHS
diff --git a/.gitlab/issue_templates/Feature proposal.md b/.gitlab/issue_templates/Feature proposal.md
index 45b5fc85cd1..589310b4cef 100644
--- a/.gitlab/issue_templates/Feature proposal.md
+++ b/.gitlab/issue_templates/Feature proposal.md
@@ -43,7 +43,14 @@ https://about.gitlab.com/handbook/engineering/ux/ux-research-training/user-story
### Permissions and Security
-<!-- What permissions are required to perform the described actions? Are they consistent with the existing permissions as documented for users, groups, and projects as appropriate? Is the proposed behavior consistent between the UI, API, and other access methods (e.g. email replies)?-->
+<!-- What permissions are required to perform the described actions? Are they consistent with the existing permissions as documented for users, groups, and projects as appropriate? Is the proposed behavior consistent between the UI, API, and other access methods (e.g. email replies)?
+Consider adding checkboxes and expectations of users with certain levels of membership https://docs.gitlab.com/ee/user/permissions.html
+* [ ] Add expected impact to members with no access (0)
+* [ ] Add expected impact to Guest (10) members
+* [ ] Add expected impact to Reporter (20) members
+* [ ] Add expected impact to Developer (30) members
+* [ ] Add expected impact to Maintainer (40) members
+* [ ] Add expected impact to Owner (50) members -->
### Documentation
diff --git a/.gitlab/issue_templates/Security developer workflow.md b/.gitlab/issue_templates/Security developer workflow.md
index 695f0167ad4..7de137bd2e2 100644
--- a/.gitlab/issue_templates/Security developer workflow.md
+++ b/.gitlab/issue_templates/Security developer workflow.md
@@ -9,19 +9,17 @@ Set the title to: `Description of the original issue`
## Prior to starting the security release work
- [ ] Read the [security process for developers] if you are not familiar with it.
-- [ ] Mark this [issue as related] to the Security Release tracking issue. You can find it on the topic of the `#releases` Slack channel.
-- [ ] Run `scripts/security-harness` in your local repository to prevent accidentally pushing to any remote besides `gitlab.com/gitlab-org/security`.
+- [ ] Mark this [issue as related] to the Security Release Tracking Issue. You can find it on the topic of the `#releases` Slack channel.
- Fill out the [Links section](#links):
- [ ] Next to **Issue on GitLab**, add a link to the `gitlab-org/gitlab` issue that describes the security vulnerability.
- - [ ] Next to **Security Release tracking issue**, add a link to the security release issue that will include this security issue.
## Development
+- [ ] Run `scripts/security-harness` in your local repository to prevent accidentally pushing to any remote besides `gitlab.com/gitlab-org/security`.
- [ ] Create a new branch prefixing it with `security-`.
- [ ] Create a merge request targeting `master` on `gitlab.com/gitlab-org/security` and use the [Security Release merge request template].
-- [ ] Follow the same [code review process]: Assign to a reviewer, then to a maintainer.
-After your merge request has been approved according to our [approval guidelines], you're ready to prepare the backports
+After your merge request has been approved according to our [approval guidelines] and by a team member of the AppSec team, you're ready to prepare the backports
## Backports
@@ -41,7 +39,6 @@ After your merge request has been approved according to our [approval guidelines
- [ ] Fill in any upgrade notes that users may need to take into account in the [details section](#details)
- [ ] Add Yes/No and further details if needed to the migration and settings columns in the [details section](#details)
- [ ] Add the nickname of the external user who found the issue (and/or HackerOne profile) to the Thanks row in the [details section](#details)
-- [ ] Once your `master` MR is merged, comment on the original security issue with a link to that MR indicating the issue is fixed.
## Summary
@@ -50,7 +47,6 @@ After your merge request has been approved according to our [approval guidelines
| Description | Link |
| -------- | -------- |
| Issue on [GitLab](https://gitlab.com/gitlab-org/gitlab/issues) | #TODO |
-| Security Release tracking issue | #TODO |
### Details
@@ -64,7 +60,7 @@ After your merge request has been approved according to our [approval guidelines
| Thanks | | |
[security process for developers]: https://gitlab.com/gitlab-org/release/docs/blob/master/general/security/developer.md
-[secpick documentation]: https://gitlab.com/gitlab-org/release/docs/blob/master/general/security/developer.md#secpick-script
+[secpick documentation]: https://gitlab.com/gitlab-org/release/docs/-/blob/master/general/security/utilities/secpick_script.md
[security Release merge request template]: https://gitlab.com/gitlab-org/security/gitlab/blob/master/.gitlab/merge_request_templates/Security%20Release.md
[code review process]: https://docs.gitlab.com/ee/development/code_review.html
[approval guidelines]: https://docs.gitlab.com/ee/development/code_review.html#approval-guidelines
diff --git a/.gitlab/merge_request_templates/Documentation.md b/.gitlab/merge_request_templates/Documentation.md
index 282e80d700a..fb828b995b1 100644
--- a/.gitlab/merge_request_templates/Documentation.md
+++ b/.gitlab/merge_request_templates/Documentation.md
@@ -45,9 +45,11 @@ All reviewers can help ensure accuracy, clarity, completeness, and adherence to
**2. Technical Writer**
-- [ ] Optional: Technical writer review. If not requested for this MR, must be scheduled post-merge. To request for this MR, assign the writer listed for the applicable [DevOps stage](https://about.gitlab.com/handbook/product/categories/#devops-stages).
- - [ ] Add ~"Technical Writing" and `docs::` workflow label.
+- [ ] Technical writer review. If not requested for this MR, must be scheduled post-merge. To request for this MR, assign the writer listed for the applicable [DevOps stage](https://about.gitlab.com/handbook/product/product-categories/#devops-stages).
+ - [ ] Ensure ~"Technical Writing", ~"documentation", and a `docs::` scoped label are added.
- [ ] Add ~docs-only when the only files changed are under `doc/*`.
+ - [ ] Add ~"tw::doing" when starting work on the MR.
+ - [ ] Add ~"tw::finished" if Technical Writing team work on the MR is complete but it remains open.
**3. Maintainer**
diff --git a/.gitlab/merge_request_templates/Security Release.md b/.gitlab/merge_request_templates/Security Release.md
index f852bebae95..bdf26041e62 100644
--- a/.gitlab/merge_request_templates/Security Release.md
+++ b/.gitlab/merge_request_templates/Security Release.md
@@ -13,25 +13,33 @@ See [the general developer security release guidelines](https://gitlab.com/gitla
## Developer checklist
- [ ] **On "Related issues" section, write down the [GitLab Security] issue it belongs to (i.e. `Related to <issue_id>`).**
-- [ ] Merge request targets `master`, or `X-Y-stable` for backports.
+- [ ] Merge request targets `master`, or a versioned stable branch (`X-Y-stable-ee`).
- [ ] Milestone is set for the version this merge request applies to. A closed milestone can be assigned via [quick actions].
- [ ] Title of this merge request is the same as for all backports.
-- [ ] A [CHANGELOG entry](https://docs.gitlab.com/ee/development/changelog.html) is added without a `merge_request` value, with `type` set to `security`
-- [ ] Assign to a reviewer and maintainer, per our [Code Review process].
+- [ ] A [CHANGELOG entry] is added without a `merge_request` value, with `type` set to `security`
- [ ] For the MR targeting `master`:
- - [ ] Ask for a non-blocking review from the AppSec team member associated to the issue in the [Canonical repository](https://gitlab.com/gitlab-org/gitlab). If you're unsure who to ping, ask on `#sec-appsec` Slack channel.
+ - [ ] Assign to a reviewer and maintainer, per our [Code Review process].
- [ ] Ensure it's approved according to our [Approval Guidelines].
-- [ ] Merge request _must not_ close the corresponding security issue, _unless_ it targets `master`.
+ - [ ] Ensure it's approved by an AppSec engineer.
+ - If you're unsure who should approve, find the AppSec engineer associated to the issue in the [Canonical repository], or ask #sec-appsec on Slack.
+ - Trigger the [`package-and-qa` build]. The docker image generated will be used by the AppSec engineer to validate the security vulnerability has been remediated.
+ - [ ] Merge request _must_ close the corresponding security issue.
+- [ ] For a backport MR targeting a versioned stable branch (`X-Y-stable-ee`)
+ - [ ] Ensure it's approved by a maintainer.
**Note:** Reviewer/maintainer should not be a Release Manager
## Maintainer checklist
+
- [ ] Correct milestone is applied and the title is matching across all backports
- [ ] Assigned to `@gitlab-release-tools-bot` with passing CI pipelines and **when all backports including the MR targeting master are ready.**
/label ~security
[GitLab Security]: https://gitlab.com/gitlab-org/security/gitlab
-[approval guidelines]: https://docs.gitlab.com/ee/development/code_review.html#approval-guidelines
-[Code Review process]: https://docs.gitlab.com/ee/development/code_review.html
[quick actions]: https://docs.gitlab.com/ee/user/project/quick_actions.html#quick-actions-for-issues-merge-requests-and-epics
+[CHANGELOG entry]: https://docs.gitlab.com/ee/development/changelog.html
+[Code Review process]: https://docs.gitlab.com/ee/development/code_review.html
+[Approval Guidelines]: https://docs.gitlab.com/ee/development/code_review.html#approval-guidelines
+[Canonical repository]: https://gitlab.com/gitlab-org/gitlab
+[`package-and-qa` build]: https://docs.gitlab.com/ee/development/testing_guide/end_to_end/#using-the-package-and-qa-job
diff --git a/.haml-lint.yml b/.haml-lint.yml
index 33a960f7efe..4adb5e62f88 100644
--- a/.haml-lint.yml
+++ b/.haml-lint.yml
@@ -8,6 +8,7 @@ exclude:
- 'spec/**/*'
require:
- './haml_lint/linter/no_plain_nodes.rb'
+ - './haml_lint/linter/documentation_links.rb'
linters:
AltText:
@@ -26,6 +27,12 @@ linters:
enabled: false
max_consecutive: 2
+ DocumentationLinks:
+ enabled: true
+ include:
+ - 'app/views/**/*.haml'
+ - 'ee/app/views/**/*.haml'
+
EmptyObjectReference:
enabled: true
diff --git a/.haml-lint_todo.yml b/.haml-lint_todo.yml
index 5bd7dc18831..df515607d36 100644
--- a/.haml-lint_todo.yml
+++ b/.haml-lint_todo.yml
@@ -47,7 +47,6 @@ linters:
- "app/views/admin/hooks/edit.html.haml"
- "app/views/admin/logs/show.html.haml"
- "app/views/admin/projects/_projects.html.haml"
- - "app/views/admin/projects/show.html.haml"
- "app/views/admin/requests_profiles/index.html.haml"
- "app/views/admin/runners/_runner.html.haml"
- "app/views/admin/runners/index.html.haml"
@@ -57,7 +56,6 @@ linters:
- "app/views/admin/spam_logs/_spam_log.html.haml"
- "app/views/admin/spam_logs/index.html.haml"
- "app/views/admin/system_info/show.html.haml"
- - "app/views/admin/users/_access_levels.html.haml"
- "app/views/admin/users/_form.html.haml"
- "app/views/admin/users/_head.html.haml"
- "app/views/admin/users/_profile.html.haml"
@@ -280,7 +278,6 @@ linters:
- "app/views/shared/_no_password.html.haml"
- "app/views/shared/_ping_consent.html.haml"
- "app/views/shared/_project_limit.html.haml"
- - "app/views/shared/boards/components/_board.html.haml"
- "app/views/shared/boards/components/_sidebar.html.haml"
- "app/views/shared/boards/components/sidebar/_due_date.html.haml"
- "app/views/shared/boards/components/sidebar/_labels.html.haml"
@@ -358,7 +355,6 @@ linters:
- "ee/app/views/notify/unapproved_merge_request_email.html.haml"
- "ee/app/views/oauth/geo_auth/error.html.haml"
- "ee/app/views/projects/commits/_mirror_status.html.haml"
- - "ee/app/views/projects/jobs/_shared_runner_limit_warning.html.haml"
- "ee/app/views/projects/merge_requests/_approvals_count.html.haml"
- "ee/app/views/projects/merge_requests/widget/open/_geo.html.haml"
- "ee/app/views/projects/mirrors/_mirrored_repositories_count.html.haml"
@@ -372,7 +368,6 @@ linters:
- "ee/app/views/projects/services/gitlab_slack_application/_slack_integration_form.html.haml"
- "ee/app/views/projects/settings/slacks/edit.html.haml"
- "ee/app/views/shared/_mirror_update_button.html.haml"
- - "ee/app/views/shared/boards/components/_list_weight.html.haml"
- "ee/app/views/shared/epic/_search_bar.html.haml"
- "ee/app/views/shared/issuable/_approvals.html.haml"
- "ee/app/views/shared/issuable/_board_create_list_dropdown.html.haml"
diff --git a/.markdownlint.json b/.markdownlint.json
index b4da68989f3..253b7bd0c92 100644
--- a/.markdownlint.json
+++ b/.markdownlint.json
@@ -79,6 +79,7 @@
"Jira Cloud",
"Jira Server",
"jQuery",
+ "JSON",
"JupyterHub",
"Karma",
"Kerberos",
diff --git a/.prettierignore b/.prettierignore
index c9b945ac96d..ff8188bbda4 100644
--- a/.prettierignore
+++ b/.prettierignore
@@ -3,6 +3,7 @@
/public/
/vendor/
/tmp/
+doc/api/graphql/reference/gitlab_schema.graphql
# ignore stylesheets for now as this clashes with our linter
*.css
diff --git a/.rubocop.yml b/.rubocop.yml
index ed17799478a..bff2b7a32b1 100644
--- a/.rubocop.yml
+++ b/.rubocop.yml
@@ -308,6 +308,18 @@ Gitlab/Union:
- 'spec/**/*'
- 'ee/spec/**/*'
+API/GrapeAPIInstance:
+ Enabled: true
+ Include:
+ - 'lib/**/api/**/*.rb'
+ - 'ee/**/api/**/*.rb'
+
+API/GrapeArrayMissingCoerce:
+ Enabled: true
+ Include:
+ - 'lib/**/api/**/*.rb'
+ - 'ee/**/api/**/*.rb'
+
Cop/SidekiqOptionsQueue:
Enabled: true
Exclude:
@@ -316,6 +328,9 @@ Cop/SidekiqOptionsQueue:
Graphql/AuthorizeTypes:
Enabled: true
+ Include:
+ - 'app/graphql/types/**/*'
+ - 'ee/app/graphql/types/**/*'
Exclude:
- 'spec/**/*.rb'
- 'ee/spec/**/*.rb'
@@ -450,10 +465,17 @@ Rails/TimeZone:
- 'spec/models/**/*'
- 'ee/app/models/**/*'
- 'ee/spec/models/**/*'
+ - 'app/workers/**/*'
+ - 'spec/workers/**/*'
+ - 'ee/app/workers/**/*'
+ - 'ee/spec/workers/**/*'
+
# WIP: See https://gitlab.com/gitlab-org/gitlab/-/issues/220040
Rails/SaveBang:
Enabled: true
+ AllowImplicitReturn: false
+ AllowedReceivers: ['ActionDispatch::TestRequest']
Include:
- 'spec/**/*.rb'
- 'ee/spec/**/*.rb'
diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml
index 9e3a54fa7e9..e54d970c671 100644
--- a/.rubocop_todo.yml
+++ b/.rubocop_todo.yml
@@ -303,6 +303,11 @@ Performance/Detect:
RSpec/ContextWording:
Enabled: false
+# Offense count: 626
+# Cop supports --auto-correct.
+RSpec/EmptyLineAfterLetBlock:
+ Enabled: false
+
# Offense count: 1121
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle.
@@ -560,30 +565,6 @@ Style/GuardClause:
Style/HashEachMethods:
Enabled: false
-# Offense count: 6
-# Cop supports --auto-correct.
-Style/HashTransformKeys:
- Exclude:
- - 'ee/app/models/vulnerabilities/occurrence.rb'
- - 'ee/spec/lib/gitlab/ci/templates/dependency_scanning_gitlab_ci_yaml_spec.rb'
- - 'lib/banzai/filter/commit_trailers_filter.rb'
- - 'lib/gitlab/analytics/cycle_analytics/stage_events.rb'
-
-# Offense count: 10
-# Cop supports --auto-correct.
-Style/HashTransformValues:
- Exclude:
- - 'app/validators/addressable_url_validator.rb'
- - 'config/initializers/action_dispatch_journey_formatter.rb'
- - 'ee/app/helpers/compliance_management/compliance_framework/project_settings_helper.rb'
- - 'ee/app/services/packages/nuget/metadata_extraction_service.rb'
- - 'lib/gitlab/config/entry/configurable.rb'
- - 'lib/gitlab/config/entry/node.rb'
- - 'lib/gitlab/discussions_diff/file_collection.rb'
- - 'lib/gitlab/error_tracking.rb'
- - 'lib/rspec_flaky/flaky_examples_collection.rb'
- - 'spec/lib/gitlab/database_importers/common_metrics/prometheus_metric_spec.rb'
-
# Offense count: 31
# Configuration parameters: AllowIfModifier.
Style/IfInsideElse:
@@ -639,12 +620,122 @@ Style/Next:
Style/NumericLiteralPrefix:
Enabled: false
-# Offense count: 255
+# Offense count: 130
# Cop supports --auto-correct.
# Configuration parameters: AutoCorrect, EnforcedStyle, IgnoredMethods.
# SupportedStyles: predicate, comparison
+# We use EnforcedStyle of comparison here due to it being better
+# performing code as seen https://gitlab.com/gitlab-org/gitlab/-/merge_requests/36221#note_375659681
Style/NumericPredicate:
- Enabled: false
+ EnforcedStyle: comparison
+ Exclude:
+ - 'spec/**/*'
+ - 'app/views/**/*'
+ - 'ee/app/views/**/*'
+ - 'app/controllers/concerns/issuable_collections.rb'
+ - 'app/controllers/concerns/paginated_collection.rb'
+ - 'app/helpers/graph_helper.rb'
+ - 'app/helpers/timeboxes_helper.rb'
+ - 'app/models/ci/pipeline.rb'
+ - 'app/models/ci/stage.rb'
+ - 'app/models/concerns/update_project_statistics.rb'
+ - 'app/models/merge_request_diff.rb'
+ - 'app/models/milestone.rb'
+ - 'app/models/network/graph.rb'
+ - 'app/models/postgresql/replication_slot.rb'
+ - 'app/models/project.rb'
+ - 'app/models/suggestion.rb'
+ - 'app/models/user.rb'
+ - 'app/serializers/merge_request_widget_entity.rb'
+ - 'app/services/boards/issues/move_service.rb'
+ - 'app/services/cohorts_service.rb'
+ - 'app/services/discussions/resolve_service.rb'
+ - 'app/services/issues/reorder_service.rb'
+ - 'app/services/notes/create_service.rb'
+ - 'app/services/packages/nuget/metadata_extraction_service.rb'
+ - 'app/services/packages/nuget/search_service.rb'
+ - 'app/services/projects/auto_devops/disable_service.rb'
+ - 'app/services/projects/update_pages_service.rb'
+ - 'app/services/search_service.rb'
+ - 'app/workers/admin_email_worker.rb'
+ - 'app/workers/gitlab/import/advance_stage.rb'
+ - 'config/initializers/validate_puma.rb'
+ - 'ee/app/controllers/security/projects_controller.rb'
+ - 'ee/app/graphql/mutations/instance_security_dashboard/remove_project.rb'
+ - 'ee/app/helpers/ee/timeboxes_helper.rb'
+ - 'ee/app/models/ci/minutes/quota.rb'
+ - 'ee/app/models/ee/ci/runner.rb'
+ - 'ee/app/models/geo_node_status.rb'
+ - 'ee/app/models/license.rb'
+ - 'ee/app/models/namespace_statistics.rb'
+ - 'ee/app/services/ee/issues/base_service.rb'
+ - 'ee/app/services/ee/merge_requests/approval_service.rb'
+ - 'ee/app/services/ee/quick_actions/target_service.rb'
+ - 'ee/app/services/elastic/indexing_control_service.rb'
+ - 'ee/app/services/geo/hashed_storage_migration_service.rb'
+ - 'ee/app/services/geo/prune_event_log_service.rb'
+ - 'ee/app/services/security/waf_anomaly_summary_service.rb'
+ - 'ee/app/services/update_build_minutes_service.rb'
+ - 'ee/app/workers/geo/container_repository_sync_dispatch_worker.rb'
+ - 'ee/app/workers/geo/file_download_dispatch_worker.rb'
+ - 'ee/app/workers/geo/registry_sync_worker.rb'
+ - 'ee/app/workers/geo/repository_shard_sync_worker.rb'
+ - 'ee/app/workers/geo/repository_verification/primary/shard_worker.rb'
+ - 'ee/lib/api/helpers/packages/conan/api_helpers.rb'
+ - 'ee/lib/ee/gitlab/auth/ldap/person.rb'
+ - 'ee/lib/ee/gitlab/background_migration/prune_orphaned_geo_events.rb'
+ - 'ee/lib/ee/gitlab/checks/push_rules/file_size_check.rb'
+ - 'ee/lib/ee/gitlab/geo_git_access.rb'
+ - 'ee/lib/gitlab/geo/fdw.rb'
+ - 'ee/lib/gitlab/geo/log_cursor/lease.rb'
+ - 'ee/lib/tasks/gitlab/elastic.rake'
+ - 'lib/api/entities/feature.rb'
+ - 'lib/api/helpers/pagination_strategies.rb'
+ - 'lib/backup/files.rb'
+ - 'lib/banzai/filter/gollum_tags_filter.rb'
+ - 'lib/bitbucket_server/paginator.rb'
+ - 'lib/declarative_policy/runner.rb'
+ - 'lib/gitlab/auth/ldap/adapter.rb'
+ - 'lib/gitlab/bare_repository_import/importer.rb'
+ - 'lib/gitlab/ci/config/external/context.rb'
+ - 'lib/gitlab/ci/reports/accessibility_reports_comparer.rb'
+ - 'lib/gitlab/cycle_analytics/summary/value.rb'
+ - 'lib/gitlab/cycle_analytics/summary_helper.rb'
+ - 'lib/gitlab/danger/teammate.rb'
+ - 'lib/gitlab/database.rb'
+ - 'lib/gitlab/database/connection_timer.rb'
+ - 'lib/gitlab/database/migration_helpers.rb'
+ - 'lib/gitlab/exclusive_lease.rb'
+ - 'lib/gitlab/exclusive_lease_helpers/sleeping_lock.rb'
+ - 'lib/gitlab/experimentation.rb'
+ - 'lib/gitlab/file_hook.rb'
+ - 'lib/gitlab/git/commit.rb'
+ - 'lib/gitlab/git/repository.rb'
+ - 'lib/gitlab/git/rugged_impl/blob.rb'
+ - 'lib/gitlab/gitaly_client.rb'
+ - 'lib/gitlab/github_import/user_finder.rb'
+ - 'lib/gitlab/hashed_storage/migrator.rb'
+ - 'lib/gitlab/import_export/command_line_util.rb'
+ - 'lib/gitlab/multi_collection_paginator.rb'
+ - 'lib/gitlab/polling_interval.rb'
+ - 'lib/gitlab/project_search_results.rb'
+ - 'lib/gitlab/seeder.rb'
+ - 'lib/gitlab/sidekiq_cluster.rb'
+ - 'lib/gitlab/sidekiq_daemon/memory_killer.rb'
+ - 'lib/gitlab/sidekiq_middleware/memory_killer.rb'
+ - 'lib/gitlab/sidekiq_status.rb'
+ - 'lib/gitlab/slash_commands/presenters/issue_show.rb'
+ - 'lib/gitlab/task_helpers.rb'
+ - 'lib/gitlab/untrusted_regexp.rb'
+ - 'lib/gitlab/utils.rb'
+ - 'lib/system_check/sidekiq_check.rb'
+ - 'lib/tasks/gitlab/gitaly.rake'
+ - 'lib/tasks/gitlab/snippets.rake'
+ - 'lib/tasks/gitlab/workhorse.rake'
+ - 'qa/qa/git/repository.rb'
+ - 'qa/qa/support/wait_for_requests.rb'
+ - 'ee/app/models/ee/project.rb'
+ - 'lib/gitlab/usage_data/topology.rb'
# Offense count: 117
# Cop supports --auto-correct.
@@ -690,18 +781,6 @@ Style/RedundantFreeze:
Style/RedundantInterpolation:
Enabled: false
-# Offense count: 6
-# Cop supports --auto-correct.
-Style/RedundantParentheses:
- Exclude:
- - 'ee/app/models/ee/merge_request.rb'
-
-# Offense count: 33
-# Cop supports --auto-correct.
-# Configuration parameters: AllowMultipleReturnValues.
-Style/RedundantReturn:
- Enabled: false
-
# Offense count: 801
# Cop supports --auto-correct.
Style/RedundantSelf:
@@ -711,8 +790,8 @@ Style/RedundantSelf:
# Cop supports --auto-correct.
Style/RedundantSort:
Exclude:
- - 'ee/app/presenters/packages/nuget/search_results_presenter.rb'
- - 'ee/spec/presenters/packages/nuget/search_results_presenter_spec.rb'
+ - 'app/presenters/packages/nuget/search_results_presenter.rb'
+ - 'spec/presenters/packages/nuget/search_results_presenter_spec.rb'
# Offense count: 120
# Cop supports --auto-correct.
@@ -769,25 +848,20 @@ Style/StringLiteralsInInterpolation:
Style/SymbolProc:
Enabled: false
-# Offense count: 1478
+# Offense count: 2362
# Cop supports --auto-correct.
# Configuration parameters: AllowImplicitReturn, AllowedReceivers.
Rails/SaveBang:
Exclude:
- - 'ee/spec/controllers/groups/epic_issues_controller_spec.rb'
- - 'ee/spec/controllers/groups/epic_links_controller_spec.rb'
- - 'ee/spec/controllers/groups/epics_controller_spec.rb'
- - 'ee/spec/controllers/groups/roadmap_controller_spec.rb'
- - 'ee/spec/controllers/projects/environments_controller_spec.rb'
- - 'ee/spec/controllers/projects/issues_controller_spec.rb'
- - 'ee/spec/controllers/projects/merge_requests/creations_controller_spec.rb'
- 'ee/spec/controllers/projects/merge_requests_controller_spec.rb'
- - 'ee/spec/controllers/projects/service_desk_controller_spec.rb'
- - 'ee/spec/controllers/projects/subscriptions_controller_spec.rb'
- - 'ee/spec/controllers/projects/vulnerability_feedback_controller_spec.rb'
- 'ee/spec/controllers/subscriptions_controller_spec.rb'
+ - 'ee/spec/factories/ci/job_artifacts.rb'
+ - 'ee/spec/factories/epics.rb'
+ - 'ee/spec/factories/licenses.rb'
- 'ee/spec/factories/merge_requests.rb'
+ - 'ee/spec/features/admin/admin_users_spec.rb'
- 'ee/spec/features/admin/geo/admin_geo_nodes_spec.rb'
+ - 'ee/spec/features/admin/licenses/admin_views_license_spec.rb'
- 'ee/spec/features/boards/scoped_issue_board_spec.rb'
- 'ee/spec/features/ci_shared_runner_warnings_spec.rb'
- 'ee/spec/features/dashboards/operations_spec.rb'
@@ -796,13 +870,19 @@ Rails/SaveBang:
- 'ee/spec/features/merge_requests/user_views_all_merge_requests_spec.rb'
- 'ee/spec/features/projects/members/invite_group_and_members_spec.rb'
- 'ee/spec/features/projects/merge_requests/user_approves_merge_request_spec.rb'
+ - 'ee/spec/features/projects/mirror_spec.rb'
- 'ee/spec/features/projects/new_project_spec.rb'
- 'ee/spec/features/projects/settings/user_manages_approval_settings_spec.rb'
- 'ee/spec/features/projects/settings/user_manages_members_spec.rb'
- 'ee/spec/features/search/elastic/global_search_spec.rb'
+ - 'ee/spec/features/security/project/internal_access_spec.rb'
+ - 'ee/spec/features/security/project/public_access_spec.rb'
- 'ee/spec/finders/epics_finder_spec.rb'
+ - 'ee/spec/finders/security/vulnerabilities_finder_spec.rb'
- 'ee/spec/frontend/fixtures/analytics.rb'
+ - 'ee/spec/graphql/resolvers/vulnerabilities_resolver_spec.rb'
- 'ee/spec/helpers/application_helper_spec.rb'
+ - 'ee/spec/helpers/ee/dashboard_helper_spec.rb'
- 'ee/spec/helpers/ee/issues_helper_spec.rb'
- 'ee/spec/initializers/fog_google_https_private_urls_spec.rb'
- 'ee/spec/lib/analytics/merge_request_metrics_calculator_spec.rb'
@@ -811,60 +891,84 @@ Rails/SaveBang:
- 'ee/spec/lib/ee/gitlab/background_migration/move_epic_issues_after_epics_spec.rb'
- 'ee/spec/lib/ee/gitlab/background_migration/populate_any_approval_rule_for_merge_requests_spec.rb'
- 'ee/spec/lib/ee/gitlab/background_migration/populate_any_approval_rule_for_projects_spec.rb'
+ - 'ee/spec/lib/ee/gitlab/background_migration/prune_orphaned_geo_events_spec.rb'
- 'ee/spec/lib/ee/gitlab/checks/push_rules/commit_check_spec.rb'
- 'ee/spec/lib/ee/gitlab/ci/pipeline/quota/activity_spec.rb'
- 'ee/spec/lib/gitlab/auth/ldap/access_spec.rb'
- 'ee/spec/lib/gitlab/auth/o_auth/user_spec.rb'
- 'ee/spec/lib/gitlab/auth/saml/user_spec.rb'
+ - 'ee/spec/lib/gitlab/background_migration/fix_orphan_promoted_issues_spec.rb'
- 'ee/spec/lib/gitlab/elastic/search_results_spec.rb'
- 'ee/spec/lib/gitlab/email/handler/ee/service_desk_handler_spec.rb'
+ - 'ee/spec/lib/gitlab/geo/cron_manager_spec.rb'
- 'ee/spec/lib/gitlab/geo/jwt_request_decoder_spec.rb'
- 'ee/spec/lib/gitlab/geo/oauth/session_spec.rb'
- 'ee/spec/lib/gitlab/geo_spec.rb'
- 'ee/spec/lib/gitlab/git_access_spec.rb'
+ - 'ee/spec/lib/gitlab/import_export/group/relation_factory_spec.rb'
+ - 'ee/spec/lib/gitlab/mirror_spec.rb'
- 'ee/spec/mailers/notify_spec.rb'
+ - 'ee/spec/migrations/fix_any_approver_rule_for_projects_spec.rb'
- 'ee/spec/migrations/geo/migrate_ci_job_artifacts_to_separate_registry_spec.rb'
- 'ee/spec/migrations/geo/migrate_lfs_objects_to_separate_registry_spec.rb'
+ - 'ee/spec/migrations/schedule_merge_request_any_approval_rule_migration_spec.rb'
- 'ee/spec/migrations/schedule_project_any_approval_rule_migration_spec.rb'
- 'ee/spec/models/application_setting_spec.rb'
- 'ee/spec/models/approval_merge_request_rule_spec.rb'
+ - 'ee/spec/models/approval_project_rule_spec.rb'
- 'ee/spec/models/approval_state_spec.rb'
- 'ee/spec/models/burndown_spec.rb'
- 'ee/spec/models/ci/build_spec.rb'
- 'ee/spec/models/ci/pipeline_spec.rb'
- 'ee/spec/models/ci/subscriptions/project_spec.rb'
+ - 'ee/spec/models/concerns/approver_migrate_hook_spec.rb'
- 'ee/spec/models/concerns/deprecated_approvals_before_merge_spec.rb'
- 'ee/spec/models/concerns/elastic/note_spec.rb'
+ - 'ee/spec/models/ee/appearance_spec.rb'
+ - 'ee/spec/models/ee/ci/job_artifact_spec.rb'
- 'ee/spec/models/ee/protected_branch_spec.rb'
- 'ee/spec/models/ee/protected_ref_access_spec.rb'
- 'ee/spec/models/ee/protected_ref_spec.rb'
+ - 'ee/spec/models/elasticsearch_indexed_namespace_spec.rb'
+ - 'ee/spec/models/environment_spec.rb'
- 'ee/spec/models/epic_spec.rb'
- 'ee/spec/models/geo/project_registry_spec.rb'
- 'ee/spec/models/geo_node_spec.rb'
- 'ee/spec/models/geo_node_status_spec.rb'
+ - 'ee/spec/models/gitlab_subscription_spec.rb'
- 'ee/spec/models/group_spec.rb'
- 'ee/spec/models/issue_spec.rb'
- 'ee/spec/models/label_note_spec.rb'
+ - 'ee/spec/models/lfs_object_spec.rb'
- 'ee/spec/models/license_spec.rb'
- 'ee/spec/models/merge_request_spec.rb'
+ - 'ee/spec/models/merge_train_spec.rb'
- 'ee/spec/models/operations/feature_flag_scope_spec.rb'
- 'ee/spec/models/operations/feature_flag_spec.rb'
- 'ee/spec/models/operations/feature_flags/strategy_spec.rb'
- 'ee/spec/models/operations/feature_flags/user_list_spec.rb'
+ - 'spec/models/packages/package_spec.rb'
+ - 'ee/spec/models/project_ci_cd_setting_spec.rb'
- 'ee/spec/models/project_services/github_service_spec.rb'
- 'ee/spec/models/project_services/jenkins_service_spec.rb'
- 'ee/spec/models/project_spec.rb'
+ - 'ee/spec/models/protected_environment_spec.rb'
+ - 'ee/spec/models/repository_spec.rb'
- 'ee/spec/models/scim_identity_spec.rb'
- 'ee/spec/models/scim_oauth_access_token_spec.rb'
+ - 'ee/spec/models/upload_spec.rb'
- 'ee/spec/models/user_preference_spec.rb'
- 'ee/spec/models/user_spec.rb'
- 'ee/spec/models/visible_approvable_spec.rb'
- 'ee/spec/models/vulnerabilities/feedback_spec.rb'
- 'ee/spec/models/vulnerabilities/issue_link_spec.rb'
+ - 'ee/spec/policies/group_policy_spec.rb'
+ - 'ee/spec/policies/note_policy_spec.rb'
+ - 'ee/spec/policies/project_policy_spec.rb'
- 'ee/spec/policies/protected_branch_policy_spec.rb'
+ - 'ee/spec/policies/vulnerabilities/feedback_policy_spec.rb'
- 'ee/spec/presenters/audit_event_presenter_spec.rb'
- 'ee/spec/presenters/epic_presenter_spec.rb'
- - 'ee/spec/presenters/packages/conan/package_presenter_spec.rb'
- 'ee/spec/requests/api/boards_spec.rb'
- 'ee/spec/requests/api/epic_issues_spec.rb'
- 'ee/spec/requests/api/epic_links_spec.rb'
@@ -876,7 +980,6 @@ Rails/SaveBang:
- 'ee/spec/requests/api/groups_spec.rb'
- 'ee/spec/requests/api/issues_spec.rb'
- 'ee/spec/requests/api/ldap_group_links_spec.rb'
- - 'ee/spec/requests/api/maven_packages_spec.rb'
- 'ee/spec/requests/api/merge_request_approval_rules_spec.rb'
- 'ee/spec/requests/api/merge_request_approvals_spec.rb'
- 'ee/spec/requests/api/merge_requests_spec.rb'
@@ -885,9 +988,15 @@ Rails/SaveBang:
- 'ee/spec/requests/api/protected_branches_spec.rb'
- 'ee/spec/requests/api/scim_spec.rb'
- 'ee/spec/requests/api/todos_spec.rb'
+ - 'ee/spec/requests/lfs_http_spec.rb'
- 'ee/spec/services/approval_rules/finalize_service_spec.rb'
+ - 'ee/spec/services/approval_rules/update_service_spec.rb'
+ - 'ee/spec/services/ci/minutes/email_notification_service_spec.rb'
+ - 'ee/spec/services/ci/process_build_service_spec.rb'
- 'ee/spec/services/ci/register_job_service_spec.rb'
- 'ee/spec/services/ee/boards/issues/create_service_spec.rb'
+ - 'ee/spec/services/ee/boards/issues/list_service_spec.rb'
+ - 'ee/spec/services/ee/boards/lists/list_service_spec.rb'
- 'ee/spec/services/ee/issuable/clone/attributes_rewriter_spec.rb'
- 'ee/spec/services/ee/issuable/common_system_notes_service_spec.rb'
- 'ee/spec/services/ee/issues/update_service_spec.rb'
@@ -896,13 +1005,15 @@ Rails/SaveBang:
- 'ee/spec/services/ee/notes/quick_actions_service_spec.rb'
- 'ee/spec/services/ee/notification_service_spec.rb'
- 'ee/spec/services/ee/resource_events/change_weight_service_spec.rb'
- - 'ee/spec/services/elastic/index_record_service_spec.rb'
- 'ee/spec/services/epic_links/create_service_spec.rb'
+ - 'ee/spec/services/epics/close_service_spec.rb'
- 'ee/spec/services/epics/issue_promote_service_spec.rb'
+ - 'ee/spec/services/epics/reopen_service_spec.rb'
- 'ee/spec/services/epics/tree_reorder_service_spec.rb'
- 'ee/spec/services/epics/update_dates_service_spec.rb'
- 'ee/spec/services/epics/update_service_spec.rb'
- 'ee/spec/services/geo/blob_verification_secondary_service_spec.rb'
+ - 'ee/spec/services/geo/files_expire_service_spec.rb'
- 'ee/spec/services/geo/metrics_update_service_spec.rb'
- 'ee/spec/services/geo/registry_consistency_service_spec.rb'
- 'ee/spec/services/geo/repository_verification_secondary_service_spec.rb'
@@ -911,8 +1022,11 @@ Rails/SaveBang:
- 'ee/spec/services/lfs/unlock_file_service_spec.rb'
- 'ee/spec/services/merge_requests/approval_service_spec.rb'
- 'ee/spec/services/merge_requests/remove_approval_service_spec.rb'
+ - 'ee/spec/services/merge_requests/update_blocks_service_spec.rb'
- 'ee/spec/services/merge_trains/refresh_merge_request_service_spec.rb'
- 'ee/spec/services/projects/after_rename_service_spec.rb'
+ - 'ee/spec/services/projects/import_export/export_service_spec.rb'
+ - 'ee/spec/services/projects/update_mirror_service_spec.rb'
- 'ee/spec/services/projects/update_service_spec.rb'
- 'ee/spec/services/quick_actions/interpret_service_spec.rb'
- 'ee/spec/services/slash_commands/global_slack_handler_spec.rb'
@@ -920,50 +1034,92 @@ Rails/SaveBang:
- 'ee/spec/services/status_page/trigger_publish_service_spec.rb'
- 'ee/spec/services/todo_service_spec.rb'
- 'ee/spec/services/update_build_minutes_service_spec.rb'
+ - 'ee/spec/services/vulnerability_feedback/create_service_spec.rb'
+ - 'ee/spec/support/helpers/ee/geo_helpers.rb'
+ - 'ee/spec/support/protected_tags/access_control_shared_examples.rb'
+ - 'ee/spec/support/shared_examples/features/protected_branches_access_control_shared_examples.rb'
+ - 'ee/spec/support/shared_examples/finders/geo/framework_registry_finder_shared_examples.rb'
+ - 'ee/spec/support/shared_examples/graphql/geo/geo_registries_resolver_shared_examples.rb'
- 'ee/spec/support/shared_examples/lib/analytics/common_merge_request_metrics_refresh_shared_examples.rb'
- 'ee/spec/support/shared_examples/models/concerns/replicator_shared_examples.rb'
+ - 'ee/spec/support/shared_examples/models/elasticsearch_indexed_container_shared_examples.rb'
+ - 'ee/spec/support/shared_examples/models/geo_framework_registry_shared_examples.rb'
- 'ee/spec/support/shared_examples/models/member_shared_examples.rb'
- 'ee/spec/support/shared_examples/models/mentionable_shared_examples.rb'
- 'ee/spec/support/shared_examples/policies/protected_environments_shared_examples.rb'
+ - 'ee/spec/support/shared_examples/requests/api/graphql/geo/registries_shared_examples.rb'
- 'ee/spec/support/shared_examples/requests/api/project_approval_rules_api_shared_examples.rb'
+ - 'ee/spec/support/shared_examples/services/build_execute_shared_examples.rb'
- 'ee/spec/support/shared_examples/services/issue_epic_shared_examples.rb'
- 'ee/spec/views/layouts/nav/sidebar/_project.html.haml_spec.rb'
- 'ee/spec/workers/adjourned_project_deletion_worker_spec.rb'
+ - 'ee/spec/workers/clear_shared_runners_minutes_worker_spec.rb'
- 'ee/spec/workers/create_github_webhook_worker_spec.rb'
- - 'ee/spec/workers/elastic_indexer_worker_spec.rb'
+ - 'ee/spec/workers/elastic_namespace_rollout_worker_spec.rb'
- 'ee/spec/workers/geo/container_repository_sync_dispatch_worker_spec.rb'
- 'ee/spec/workers/geo/file_download_dispatch_worker_spec.rb'
+ - 'ee/spec/workers/geo/prune_event_log_worker_spec.rb'
+ - 'ee/spec/workers/geo/registry_sync_worker_spec.rb'
- 'ee/spec/workers/geo/repository_shard_sync_worker_spec.rb'
- 'ee/spec/workers/repository_import_worker_spec.rb'
- 'ee/spec/workers/update_all_mirrors_worker_spec.rb'
- 'qa/qa/specs/features/browser_ui/3_create/repository/push_mirroring_over_http_spec.rb'
- 'qa/qa/specs/features/ee/browser_ui/3_create/repository/pull_mirroring_over_http_spec.rb'
+ - 'qa/qa/specs/features/ee/browser_ui/3_create/repository/pull_mirroring_over_ssh_with_key_spec.rb'
- 'spec/controllers/abuse_reports_controller_spec.rb'
- 'spec/controllers/admin/impersonations_controller_spec.rb'
- 'spec/controllers/admin/runners_controller_spec.rb'
+ - 'spec/controllers/admin/services_controller_spec.rb'
- 'spec/controllers/boards/issues_controller_spec.rb'
+ - 'spec/controllers/groups/milestones_controller_spec.rb'
- 'spec/controllers/groups/runners_controller_spec.rb'
+ - 'spec/controllers/groups/uploads_controller_spec.rb'
+ - 'spec/controllers/groups_controller_spec.rb'
- 'spec/controllers/oauth/authorizations_controller_spec.rb'
- 'spec/controllers/omniauth_callbacks_controller_spec.rb'
- 'spec/controllers/profiles/emails_controller_spec.rb'
- 'spec/controllers/profiles/notifications_controller_spec.rb'
- 'spec/controllers/projects/artifacts_controller_spec.rb'
+ - 'spec/controllers/projects/cycle_analytics/events_controller_spec.rb'
- 'spec/controllers/projects/cycle_analytics_controller_spec.rb'
+ - 'spec/controllers/projects/discussions_controller_spec.rb'
+ - 'spec/controllers/projects/forks_controller_spec.rb'
+ - 'spec/controllers/projects/group_links_controller_spec.rb'
+ - 'spec/controllers/projects/imports_controller_spec.rb'
- 'spec/controllers/projects/issues_controller_spec.rb'
- 'spec/controllers/projects/labels_controller_spec.rb'
- 'spec/controllers/projects/merge_requests_controller_spec.rb'
- 'spec/controllers/projects/milestones_controller_spec.rb'
- 'spec/controllers/projects/notes_controller_spec.rb'
- 'spec/controllers/projects/pipelines_controller_spec.rb'
+ - 'spec/controllers/projects/releases/evidences_controller_spec.rb'
- 'spec/controllers/projects/runners_controller_spec.rb'
+ - 'spec/controllers/projects/starrers_controller_spec.rb'
+ - 'spec/controllers/projects/uploads_controller_spec.rb'
- 'spec/controllers/projects_controller_spec.rb'
+ - 'spec/controllers/sent_notifications_controller_spec.rb'
+ - 'spec/controllers/sessions_controller_spec.rb'
+ - 'spec/controllers/users_controller_spec.rb'
+ - 'spec/factories/alert_management/alerts.rb'
+ - 'spec/factories/boards.rb'
- 'spec/factories/ci/pipelines.rb'
- 'spec/factories/design_management/designs.rb'
- 'spec/factories/design_management/versions.rb'
+ - 'spec/factories/emails.rb'
+ - 'spec/factories/issues.rb'
- 'spec/factories/labels.rb'
+ - 'spec/factories/merge_requests.rb'
+ - 'spec/factories/plans.rb'
- 'spec/factories/projects.rb'
+ - 'spec/factories/services.rb'
+ - 'spec/factories/wiki_pages.rb'
+ - 'spec/factories_spec.rb'
+ - 'spec/features/admin/admin_appearance_spec.rb'
+ - 'spec/features/admin/admin_labels_spec.rb'
- 'spec/features/admin/admin_mode/login_spec.rb'
- 'spec/features/admin/admin_runners_spec.rb'
+ - 'spec/features/admin/admin_sees_project_statistics_spec.rb'
+ - 'spec/features/admin/admin_sees_projects_statistics_spec.rb'
- 'spec/features/admin/admin_users_impersonation_tokens_spec.rb'
- 'spec/features/admin/admin_users_spec.rb'
- 'spec/features/boards/sidebar_spec.rb'
@@ -975,6 +1131,7 @@ Rails/SaveBang:
- 'spec/features/dashboard/projects_spec.rb'
- 'spec/features/error_tracking/user_sees_error_index_spec.rb'
- 'spec/features/groups/members/request_access_spec.rb'
+ - 'spec/features/issuables/close_reopen_report_toggle_spec.rb'
- 'spec/features/issues/bulk_assignment_labels_spec.rb'
- 'spec/features/issues/gfm_autocomplete_spec.rb'
- 'spec/features/issues/issue_sidebar_spec.rb'
@@ -989,30 +1146,45 @@ Rails/SaveBang:
- 'spec/features/merge_request/user_posts_diff_notes_spec.rb'
- 'spec/features/merge_request/user_resolves_diff_notes_and_discussions_resolve_spec.rb'
- 'spec/features/merge_request/user_sees_cherry_pick_modal_spec.rb'
+ - 'spec/features/merge_request/user_sees_discussions_spec.rb'
- 'spec/features/merge_request/user_sees_merge_widget_spec.rb'
+ - 'spec/features/merge_request/user_sees_versions_spec.rb'
- 'spec/features/merge_requests/user_mass_updates_spec.rb'
- 'spec/features/profiles/emails_spec.rb'
- 'spec/features/profiles/password_spec.rb'
- 'spec/features/profiles/personal_access_tokens_spec.rb'
- 'spec/features/projects/features_visibility_spec.rb'
+ - 'spec/features/projects/fork_spec.rb'
+ - 'spec/features/projects/jobs/permissions_spec.rb'
- 'spec/features/projects/jobs_spec.rb'
- 'spec/features/projects/members/user_requests_access_spec.rb'
- 'spec/features/projects/pages_lets_encrypt_spec.rb'
- 'spec/features/projects/pages_spec.rb'
+ - 'spec/features/projects/pipelines/pipeline_spec.rb'
+ - 'spec/features/projects/pipelines/pipelines_spec.rb'
- 'spec/features/projects/remote_mirror_spec.rb'
- 'spec/features/projects/services/user_activates_slack_notifications_spec.rb'
- 'spec/features/projects/settings/access_tokens_spec.rb'
- 'spec/features/projects/show/user_sees_deletion_failure_message_spec.rb'
- 'spec/features/projects/user_sees_sidebar_spec.rb'
- 'spec/features/projects/wiki/user_updates_wiki_page_spec.rb'
+ - 'spec/features/projects/wiki/user_views_wiki_page_spec.rb'
- 'spec/features/projects/wiki/users_views_asciidoc_page_with_includes_spec.rb'
+ - 'spec/features/runners_spec.rb'
+ - 'spec/features/security/project/internal_access_spec.rb'
- 'spec/features/security/project/private_access_spec.rb'
+ - 'spec/features/security/project/public_access_spec.rb'
- 'spec/features/users/login_spec.rb'
- 'spec/features/users/show_spec.rb'
+ - 'spec/finders/admin/projects_finder_spec.rb'
- 'spec/finders/autocomplete/move_to_project_finder_spec.rb'
+ - 'spec/finders/ci/pipelines_for_merge_request_finder_spec.rb'
- 'spec/finders/group_descendants_finder_spec.rb'
+ - 'spec/finders/group_projects_finder_spec.rb'
- 'spec/finders/issues_finder_spec.rb'
+ - 'spec/finders/joined_groups_finder_spec.rb'
- 'spec/finders/merge_requests_finder_spec.rb'
+ - 'spec/finders/personal_projects_finder_spec.rb'
- 'spec/finders/projects_finder_spec.rb'
- 'spec/finders/uploader_finder_spec.rb'
- 'spec/frontend/fixtures/issues.rb'
@@ -1021,6 +1193,11 @@ Rails/SaveBang:
- 'spec/graphql/mutations/merge_requests/set_wip_spec.rb'
- 'spec/graphql/resolvers/boards_resolver_spec.rb'
- 'spec/helpers/appearances_helper_spec.rb'
+ - 'spec/helpers/auto_devops_helper_spec.rb'
+ - 'spec/helpers/issuables_helper_spec.rb'
+ - 'spec/helpers/issues_helper_spec.rb'
+ - 'spec/helpers/members_helper_spec.rb'
+ - 'spec/helpers/notes_helper_spec.rb'
- 'spec/helpers/profiles_helper_spec.rb'
- 'spec/helpers/projects/alert_management_helper_spec.rb'
- 'spec/helpers/projects_helper_spec.rb'
@@ -1030,18 +1207,23 @@ Rails/SaveBang:
- 'spec/lib/after_commit_queue_spec.rb'
- 'spec/lib/backup/manager_spec.rb'
- 'spec/lib/banzai/reference_parser/external_issue_parser_spec.rb'
+ - 'spec/lib/banzai/reference_redactor_spec.rb'
+ - 'spec/lib/gitlab/alerting/alert_spec.rb'
- 'spec/lib/gitlab/analytics/cycle_analytics/records_fetcher_spec.rb'
- 'spec/lib/gitlab/auth/ldap/user_spec.rb'
- 'spec/lib/gitlab/auth/o_auth/user_spec.rb'
- 'spec/lib/gitlab/auth/saml/user_spec.rb'
- 'spec/lib/gitlab/auth_spec.rb'
+ - 'spec/lib/gitlab/authorized_keys_spec.rb'
- 'spec/lib/gitlab/background_migration/backfill_deployment_clusters_from_deployments_spec.rb'
- 'spec/lib/gitlab/background_migration/backfill_project_repositories_spec.rb'
- 'spec/lib/gitlab/background_migration/backfill_project_settings_spec.rb'
- 'spec/lib/gitlab/background_migration/backfill_push_rules_id_in_projects_spec.rb'
+ - 'spec/lib/gitlab/background_migration/backfill_snippet_repositories_spec.rb'
- 'spec/lib/gitlab/background_migration/digest_column_spec.rb'
- 'spec/lib/gitlab/background_migration/encrypt_columns_spec.rb'
- 'spec/lib/gitlab/background_migration/fix_cross_project_label_links_spec.rb'
+ - 'spec/lib/gitlab/background_migration/fix_projects_without_project_feature_spec.rb'
- 'spec/lib/gitlab/background_migration/fix_projects_without_prometheus_service_spec.rb'
- 'spec/lib/gitlab/background_migration/fix_user_namespace_names_spec.rb'
- 'spec/lib/gitlab/background_migration/fix_user_project_route_names_spec.rb'
@@ -1056,82 +1238,130 @@ Rails/SaveBang:
- 'spec/lib/gitlab/background_migration/populate_user_highest_roles_table_spec.rb'
- 'spec/lib/gitlab/background_migration/recalculate_project_authorizations_spec.rb'
- 'spec/lib/gitlab/background_migration/remove_restricted_todos_spec.rb'
+ - 'spec/lib/gitlab/background_migration/reset_merge_status_spec.rb'
- 'spec/lib/gitlab/background_migration/set_confidential_note_events_on_services_spec.rb'
- 'spec/lib/gitlab/background_migration/set_confidential_note_events_on_webhooks_spec.rb'
- 'spec/lib/gitlab/bitbucket_server_import/importer_spec.rb'
- 'spec/lib/gitlab/ci/ansi2json/style_spec.rb'
+ - 'spec/lib/gitlab/ci/status/build/common_spec.rb'
- 'spec/lib/gitlab/cycle_analytics/base_event_fetcher_spec.rb'
- 'spec/lib/gitlab/cycle_analytics/events_spec.rb'
- 'spec/lib/gitlab/database/custom_structure_spec.rb'
- 'spec/lib/gitlab/database/partitioning_migration_helpers/table_management_helpers_spec.rb'
- 'spec/lib/gitlab/database_importers/self_monitoring/project/create_service_spec.rb'
+ - 'spec/lib/gitlab/email/handler/create_note_handler_spec.rb'
+ - 'spec/lib/gitlab/email/handler/unsubscribe_handler_spec.rb'
+ - 'spec/lib/gitlab/gfm/reference_rewriter_spec.rb'
+ - 'spec/lib/gitlab/git/object_pool_spec.rb'
- 'spec/lib/gitlab/git/remote_mirror_spec.rb'
+ - 'spec/lib/gitlab/git/repository_spec.rb'
- 'spec/lib/gitlab/git_access_spec.rb'
+ - 'spec/lib/gitlab/gitaly_client/object_pool_service_spec.rb'
- 'spec/lib/gitlab/gitaly_client/repository_service_spec.rb'
- 'spec/lib/gitlab/import_export/avatar_saver_spec.rb'
+ - 'spec/lib/gitlab/import_export/base/relation_factory_spec.rb'
- 'spec/lib/gitlab/import_export/design_repo_restorer_spec.rb'
- 'spec/lib/gitlab/import_export/fast_hash_serializer_spec.rb'
- 'spec/lib/gitlab/import_export/fork_spec.rb'
- 'spec/lib/gitlab/import_export/group/legacy_tree_saver_spec.rb'
+ - 'spec/lib/gitlab/import_export/group/relation_factory_spec.rb'
+ - 'spec/lib/gitlab/import_export/group/tree_saver_spec.rb'
- 'spec/lib/gitlab/import_export/importer_spec.rb'
- 'spec/lib/gitlab/import_export/lfs_restorer_spec.rb'
- 'spec/lib/gitlab/import_export/lfs_saver_spec.rb'
- 'spec/lib/gitlab/import_export/members_mapper_spec.rb'
+ - 'spec/lib/gitlab/import_export/project/relation_factory_spec.rb'
- 'spec/lib/gitlab/import_export/project/tree_restorer_spec.rb'
- 'spec/lib/gitlab/import_export/project/tree_saver_spec.rb'
- 'spec/lib/gitlab/import_export/repo_restorer_spec.rb'
- 'spec/lib/gitlab/import_export/saver_spec.rb'
+ - 'spec/lib/gitlab/import_export/snippet_repo_restorer_spec.rb'
- 'spec/lib/gitlab/import_export/snippet_repo_saver_spec.rb'
- 'spec/lib/gitlab/import_export/snippets_repo_restorer_spec.rb'
- 'spec/lib/gitlab/import_export/snippets_repo_saver_spec.rb'
- 'spec/lib/gitlab/import_export/uploads_manager_spec.rb'
- 'spec/lib/gitlab/import_export/uploads_saver_spec.rb'
- 'spec/lib/gitlab/import_export/wiki_restorer_spec.rb'
+ - 'spec/lib/gitlab/legacy_github_import/importer_spec.rb'
+ - 'spec/lib/gitlab/legacy_github_import/issue_formatter_spec.rb'
+ - 'spec/lib/gitlab/legacy_github_import/milestone_formatter_spec.rb'
+ - 'spec/lib/gitlab/legacy_github_import/pull_request_formatter_spec.rb'
- 'spec/lib/gitlab/lets_encrypt/client_spec.rb'
- 'spec/lib/gitlab/markdown_cache/active_record/extension_spec.rb'
- 'spec/lib/gitlab/markdown_cache/redis/store_spec.rb'
+ - 'spec/lib/gitlab/middleware/go_spec.rb'
- 'spec/lib/gitlab/shard_health_cache_spec.rb'
+ - 'spec/lib/mattermost/command_spec.rb'
+ - 'spec/lib/mattermost/session_spec.rb'
+ - 'spec/lib/mattermost/team_spec.rb'
- 'spec/mailers/notify_spec.rb'
+ - 'spec/migrations/20190924152703_migrate_issue_trackers_data_spec.rb'
- 'spec/migrations/20200122123016_backfill_project_settings_spec.rb'
- 'spec/migrations/20200123155929_remove_invalid_jira_data_spec.rb'
- 'spec/migrations/20200127090233_remove_invalid_issue_tracker_data_spec.rb'
+ - 'spec/migrations/20200130145430_reschedule_migrate_issue_trackers_data_spec.rb'
+ - 'spec/migrations/20200313203550_remove_orphaned_chat_names_spec.rb'
- 'spec/migrations/20200406102120_backfill_deployment_clusters_from_deployments_spec.rb'
+ - 'spec/migrations/20200526115436_dedup_mr_metrics_spec.rb'
+ - 'spec/migrations/add_deploy_token_type_to_deploy_tokens_spec.rb'
+ - 'spec/migrations/add_incident_settings_to_all_existing_projects_spec.rb'
- 'spec/migrations/add_unique_constraint_to_approvals_user_id_and_merge_request_id_spec.rb'
- 'spec/migrations/backfill_and_add_not_null_constraint_to_released_at_column_on_releases_table_spec.rb'
+ - 'spec/migrations/backfill_imported_snippet_repositories_spec.rb'
- 'spec/migrations/backfill_releases_table_updated_at_and_add_not_null_constraints_to_timestamps_spec.rb'
+ - 'spec/migrations/backfill_snippet_repositories_spec.rb'
- 'spec/migrations/encrypt_plaintext_attributes_on_application_settings_spec.rb'
+ - 'spec/migrations/enqueue_reset_merge_status_second_run_spec.rb'
+ - 'spec/migrations/enqueue_reset_merge_status_spec.rb'
- 'spec/migrations/fill_file_store_lfs_objects_spec.rb'
- 'spec/migrations/fill_store_uploads_spec.rb'
- 'spec/migrations/fix_null_type_labels_spec.rb'
- 'spec/migrations/fix_pool_repository_source_project_id_spec.rb'
+ - 'spec/migrations/fix_projects_without_project_feature_spec.rb'
+ - 'spec/migrations/fix_projects_without_prometheus_services_spec.rb'
- 'spec/migrations/fix_wrong_pages_access_level_spec.rb'
- 'spec/migrations/insert_project_hooks_plan_limits_spec.rb'
- 'spec/migrations/migrate_auto_dev_ops_domain_to_cluster_domain_spec.rb'
+ - 'spec/migrations/move_limits_from_plans_spec.rb'
+ - 'spec/migrations/populate_project_statistics_packages_size_spec.rb'
- 'spec/migrations/schedule_link_lfs_objects_projects_spec.rb'
+ - 'spec/migrations/schedule_populate_merge_request_assignees_table_spec.rb'
+ - 'spec/migrations/seed_repository_storages_weighted_spec.rb'
- 'spec/models/appearance_spec.rb'
+ - 'spec/models/application_record_spec.rb'
- 'spec/models/application_setting_spec.rb'
+ - 'spec/models/ci/build_metadata_spec.rb'
- 'spec/models/ci/build_spec.rb'
+ - 'spec/models/ci/build_trace_chunk_spec.rb'
- 'spec/models/ci/instance_variable_spec.rb'
+ - 'spec/models/ci/legacy_stage_spec.rb'
+ - 'spec/models/ci/persistent_ref_spec.rb'
- 'spec/models/ci/pipeline_spec.rb'
- 'spec/models/ci/runner_spec.rb'
- 'spec/models/clusters/applications/helm_spec.rb'
+ - 'spec/models/commit_spec.rb'
+ - 'spec/models/commit_status_spec.rb'
- 'spec/models/concerns/avatarable_spec.rb'
- 'spec/models/concerns/bulk_insertable_associations_spec.rb'
- 'spec/models/concerns/cache_markdown_field_spec.rb'
+ - 'spec/models/concerns/case_sensitivity_spec.rb'
- 'spec/models/concerns/featurable_spec.rb'
- 'spec/models/concerns/issuable_spec.rb'
- 'spec/models/concerns/mentionable_spec.rb'
- 'spec/models/concerns/milestoneable_spec.rb'
+ - 'spec/models/concerns/milestoneish_spec.rb'
- 'spec/models/concerns/routable_spec.rb'
- 'spec/models/concerns/subscribable_spec.rb'
- 'spec/models/concerns/token_authenticatable_spec.rb'
- 'spec/models/container_repository_spec.rb'
- 'spec/models/cycle_analytics/issue_spec.rb'
- 'spec/models/cycle_analytics/plan_spec.rb'
+ - 'spec/models/cycle_analytics/production_spec.rb'
- 'spec/models/deploy_keys_project_spec.rb'
- 'spec/models/deploy_token_spec.rb'
- 'spec/models/deployment_spec.rb'
- 'spec/models/design_management/version_spec.rb'
+ - 'spec/models/diff_discussion_spec.rb'
- 'spec/models/diff_note_spec.rb'
- 'spec/models/email_spec.rb'
- 'spec/models/environment_spec.rb'
@@ -1143,7 +1373,9 @@ Rails/SaveBang:
- 'spec/models/hooks/system_hook_spec.rb'
- 'spec/models/hooks/web_hook_spec.rb'
- 'spec/models/identity_spec.rb'
+ - 'spec/models/issue/metrics_spec.rb'
- 'spec/models/issue_spec.rb'
+ - 'spec/models/jira_import_state_spec.rb'
- 'spec/models/key_spec.rb'
- 'spec/models/lfs_objects_project_spec.rb'
- 'spec/models/member_spec.rb'
@@ -1155,12 +1387,18 @@ Rails/SaveBang:
- 'spec/models/note_spec.rb'
- 'spec/models/notification_setting_spec.rb'
- 'spec/models/pages_domain_spec.rb'
+ - 'spec/models/project_auto_devops_spec.rb'
+ - 'spec/models/project_feature_spec.rb'
- 'spec/models/project_services/bamboo_service_spec.rb'
+ - 'spec/models/project_services/buildkite_service_spec.rb'
- 'spec/models/project_services/jira_service_spec.rb'
+ - 'spec/models/project_services/packagist_service_spec.rb'
- 'spec/models/project_services/pipelines_email_service_spec.rb'
- 'spec/models/project_services/teamcity_service_spec.rb'
- 'spec/models/project_spec.rb'
- 'spec/models/project_team_spec.rb'
+ - 'spec/models/protectable_dropdown_spec.rb'
+ - 'spec/models/redirect_route_spec.rb'
- 'spec/models/release_spec.rb'
- 'spec/models/remote_mirror_spec.rb'
- 'spec/models/resource_milestone_event_spec.rb'
@@ -1171,47 +1409,77 @@ Rails/SaveBang:
- 'spec/models/upload_spec.rb'
- 'spec/models/user_preference_spec.rb'
- 'spec/models/user_spec.rb'
+ - 'spec/models/user_status_spec.rb'
- 'spec/models/wiki_page/meta_spec.rb'
- 'spec/models/wiki_page_spec.rb'
- 'spec/policies/ci/build_policy_spec.rb'
- 'spec/policies/ci/pipeline_policy_spec.rb'
- 'spec/policies/ci/pipeline_schedule_policy_spec.rb'
+ - 'spec/policies/group_policy_spec.rb'
+ - 'spec/policies/issue_policy_spec.rb'
- 'spec/policies/merge_request_policy_spec.rb'
- 'spec/policies/project_policy_spec.rb'
+ - 'spec/presenters/ci/build_runner_presenter_spec.rb'
+ - 'spec/presenters/ci/trigger_presenter_spec.rb'
+ - 'spec/presenters/packages/conan/package_presenter_spec.rb'
+ - 'spec/requests/api/access_requests_spec.rb'
- 'spec/requests/api/boards_spec.rb'
+ - 'spec/requests/api/branches_spec.rb'
+ - 'spec/requests/api/ci/runner_spec.rb'
+ - 'spec/requests/api/commit_statuses_spec.rb'
+ - 'spec/requests/api/conan_packages_spec.rb'
- 'spec/requests/api/deployments_spec.rb'
- 'spec/requests/api/environments_spec.rb'
+ - 'spec/requests/api/go_proxy_spec.rb'
- 'spec/requests/api/graphql/mutations/merge_requests/set_labels_spec.rb'
- 'spec/requests/api/graphql/user_query_spec.rb'
- 'spec/requests/api/graphql_spec.rb'
+ - 'spec/requests/api/group_import_spec.rb'
- 'spec/requests/api/group_milestones_spec.rb'
- 'spec/requests/api/internal/base_spec.rb'
- 'spec/requests/api/issues/get_group_issues_spec.rb'
+ - 'spec/requests/api/issues/post_projects_issues_spec.rb'
- 'spec/requests/api/jobs_spec.rb'
- 'spec/requests/api/labels_spec.rb'
+ - 'spec/requests/api/maven_packages_spec.rb'
- 'spec/requests/api/members_spec.rb'
- 'spec/requests/api/merge_request_diffs_spec.rb'
- 'spec/requests/api/merge_requests_spec.rb'
+ - 'spec/requests/api/notes_spec.rb'
+ - 'spec/requests/api/pages/internal_access_spec.rb'
+ - 'spec/requests/api/pages/private_access_spec.rb'
+ - 'spec/requests/api/pages/public_access_spec.rb'
- 'spec/requests/api/pipeline_schedules_spec.rb'
- 'spec/requests/api/project_import_spec.rb'
+ - 'spec/requests/api/project_milestones_spec.rb'
- 'spec/requests/api/projects_spec.rb'
- - 'spec/requests/api/runners_spec.rb'
- 'spec/requests/api/snippets_spec.rb'
- 'spec/requests/git_http_spec.rb'
+ - 'spec/requests/lfs_http_spec.rb'
- 'spec/requests/profiles/notifications_controller_spec.rb'
- 'spec/requests/projects/cycle_analytics_events_spec.rb'
- 'spec/serializers/environment_status_entity_spec.rb'
+ - 'spec/serializers/issue_entity_spec.rb'
+ - 'spec/serializers/job_entity_spec.rb'
- 'spec/serializers/merge_request_poll_widget_entity_spec.rb'
- 'spec/serializers/merge_request_widget_entity_spec.rb'
+ - 'spec/services/auth/container_registry_authentication_service_spec.rb'
+ - 'spec/services/auto_merge/base_service_spec.rb'
+ - 'spec/services/auto_merge_service_spec.rb'
+ - 'spec/services/ci/create_cross_project_pipeline_service_spec.rb'
- 'spec/services/ci/create_pipeline_service_spec.rb'
- 'spec/services/ci/register_job_service_spec.rb'
- 'spec/services/ci/retry_build_service_spec.rb'
+ - 'spec/services/ci/update_runner_service_spec.rb'
+ - 'spec/services/clusters/update_service_spec.rb'
- 'spec/services/deployments/after_create_service_spec.rb'
- 'spec/services/design_management/generate_image_versions_service_spec.rb'
+ - 'spec/services/discussions/resolve_service_spec.rb'
- 'spec/services/draft_notes/destroy_service_spec.rb'
- 'spec/services/emails/confirm_service_spec.rb'
- 'spec/services/groups/destroy_service_spec.rb'
- 'spec/services/groups/import_export/import_service_spec.rb'
+ - 'spec/services/issuable/bulk_update_service_spec.rb'
- 'spec/services/issuable/clone/attributes_rewriter_spec.rb'
- 'spec/services/issuable/common_system_notes_service_spec.rb'
- 'spec/services/issues/close_service_spec.rb'
@@ -1221,6 +1489,7 @@ Rails/SaveBang:
- 'spec/services/issues/update_service_spec.rb'
- 'spec/services/labels/promote_service_spec.rb'
- 'spec/services/members/destroy_service_spec.rb'
+ - 'spec/services/merge_requests/build_service_spec.rb'
- 'spec/services/merge_requests/conflicts/list_service_spec.rb'
- 'spec/services/merge_requests/create_service_spec.rb'
- 'spec/services/merge_requests/merge_service_spec.rb'
@@ -1230,11 +1499,16 @@ Rails/SaveBang:
- 'spec/services/milestones/destroy_service_spec.rb'
- 'spec/services/milestones/promote_service_spec.rb'
- 'spec/services/milestones/transfer_service_spec.rb'
+ - 'spec/services/notes/create_service_spec.rb'
- 'spec/services/notification_recipients/build_service_spec.rb'
- 'spec/services/notification_service_spec.rb'
+ - 'spec/services/packages/conan/create_package_file_service_spec.rb'
- 'spec/services/projects/after_rename_service_spec.rb'
+ - 'spec/services/projects/autocomplete_service_spec.rb'
- 'spec/services/projects/create_service_spec.rb'
+ - 'spec/services/projects/destroy_service_spec.rb'
- 'spec/services/projects/fork_service_spec.rb'
+ - 'spec/services/projects/hashed_storage/base_attachment_service_spec.rb'
- 'spec/services/projects/move_access_service_spec.rb'
- 'spec/services/projects/move_project_group_links_service_spec.rb'
- 'spec/services/projects/overwrite_project_service_spec.rb'
@@ -1243,6 +1517,8 @@ Rails/SaveBang:
- 'spec/services/projects/update_pages_service_spec.rb'
- 'spec/services/projects/update_service_spec.rb'
- 'spec/services/quick_actions/interpret_service_spec.rb'
+ - 'spec/services/reset_project_cache_service_spec.rb'
+ - 'spec/services/resource_events/change_milestone_service_spec.rb'
- 'spec/services/system_hooks_service_spec.rb'
- 'spec/services/system_note_service_spec.rb'
- 'spec/services/system_notes/issuables_service_spec.rb'
@@ -1250,42 +1526,71 @@ Rails/SaveBang:
- 'spec/services/todos/destroy/confidential_issue_service_spec.rb'
- 'spec/services/users/destroy_service_spec.rb'
- 'spec/services/users/repair_ldap_blocked_service_spec.rb'
+ - 'spec/services/verify_pages_domain_service_spec.rb'
+ - 'spec/sidekiq/cron/job_gem_dependency_spec.rb'
- 'spec/support/helpers/cycle_analytics_helpers.rb'
+ - 'spec/support/helpers/design_management_test_helpers.rb'
- 'spec/support/helpers/jira_service_helper.rb'
- 'spec/support/helpers/login_helpers.rb'
- 'spec/support/helpers/notification_helpers.rb'
- - 'spec/support/helpers/stub_action_cable_connection.rb'
- 'spec/support/helpers/stub_object_storage.rb'
- 'spec/support/migrations_helpers/cluster_helpers.rb'
+ - 'spec/support/migrations_helpers/namespaces_helper.rb'
- 'spec/support/migrations_helpers/track_untracked_uploads_helpers.rb'
+ - 'spec/support/shared_contexts/email_shared_context.rb'
- 'spec/support/shared_contexts/finders/group_projects_finder_shared_contexts.rb'
- 'spec/support/shared_contexts/mailers/notify_shared_context.rb'
+ - 'spec/support/shared_examples/controllers/cache_control_shared_examples.rb'
- 'spec/support/shared_examples/controllers/githubish_import_controller_shared_examples.rb'
- 'spec/support/shared_examples/controllers/sessionless_auth_controller_shared_examples.rb'
- 'spec/support/shared_examples/features/editable_merge_request_shared_examples.rb'
- 'spec/support/shared_examples/lib/gitlab/ci/ci_trace_shared_examples.rb'
+ - 'spec/support/shared_examples/models/chat_slash_commands_shared_examples.rb'
+ - 'spec/support/shared_examples/models/cluster_application_helm_cert_shared_examples.rb'
+ - 'spec/support/shared_examples/models/concerns/limitable_shared_examples.rb'
+ - 'spec/support/shared_examples/models/concerns/timebox_shared_examples.rb'
- 'spec/support/shared_examples/models/diff_note_after_commit_shared_examples.rb'
- 'spec/support/shared_examples/models/member_shared_examples.rb'
- 'spec/support/shared_examples/models/members_notifications_shared_example.rb'
- 'spec/support/shared_examples/models/mentionable_shared_examples.rb'
+ - 'spec/support/shared_examples/models/project_latest_successful_build_for_shared_examples.rb'
- 'spec/support/shared_examples/models/relative_positioning_shared_examples.rb'
- 'spec/support/shared_examples/models/slack_mattermost_notifications_shared_examples.rb'
- 'spec/support/shared_examples/models/update_project_statistics_shared_examples.rb'
- 'spec/support/shared_examples/models/with_uploads_shared_examples.rb'
+ - 'spec/support/shared_examples/policies/project_policy_shared_examples.rb'
- 'spec/support/shared_examples/quick_actions/issuable/issuable_quick_actions_shared_examples.rb'
- 'spec/support/shared_examples/quick_actions/merge_request/merge_quick_action_shared_examples.rb'
+ - 'spec/support/shared_examples/requests/api/award_emoji_todo_shared_examples.rb'
- 'spec/support/shared_examples/requests/api/boards_shared_examples.rb'
+ - 'spec/support/shared_examples/requests/api/custom_attributes_shared_examples.rb'
+ - 'spec/support/shared_examples/requests/api/nuget_packages_shared_examples.rb'
+ - 'spec/support/shared_examples/requests/api/pypi_packages_shared_examples.rb'
+ - 'spec/support/shared_examples/serializers/note_entity_shared_examples.rb'
- 'spec/support/shared_examples/services/common_system_notes_shared_examples.rb'
- 'spec/support/shared_examples/services/issuable_shared_examples.rb'
+ - 'spec/support/shared_examples/services/wiki_pages/destroy_service_shared_examples.rb'
- 'spec/tasks/gitlab/web_hook_rake_spec.rb'
+ - 'spec/uploaders/file_uploader_spec.rb'
+ - 'spec/uploaders/object_storage_spec.rb'
+ - 'spec/views/notify/changed_milestone_email.html.haml_spec.rb'
- 'spec/views/projects/imports/new.html.haml_spec.rb'
- 'spec/views/projects/merge_requests/show.html.haml_spec.rb'
- 'spec/views/shared/_label_row.html.haml_spec.rb'
+ - 'spec/workers/concerns/project_export_options_spec.rb'
+ - 'spec/workers/gitlab/import/stuck_project_import_jobs_worker_spec.rb'
+ - 'spec/workers/gitlab/jira_import/stuck_jira_import_jobs_worker_spec.rb'
- 'spec/workers/migrate_external_diffs_worker_spec.rb'
- 'spec/workers/namespaceless_project_destroy_worker_spec.rb'
+ - 'spec/workers/namespaces/root_statistics_worker_spec.rb'
- 'spec/workers/pages_domain_verification_worker_spec.rb'
+ - 'spec/workers/process_commit_worker_spec.rb'
+ - 'spec/workers/propagate_integration_worker_spec.rb'
- 'spec/workers/propagate_service_template_worker_spec.rb'
- 'spec/workers/remove_unreferenced_lfs_objects_worker_spec.rb'
+ - 'spec/workers/repository_check/single_repository_worker_spec.rb'
- 'spec/workers/repository_cleanup_worker_spec.rb'
- 'spec/workers/repository_import_worker_spec.rb'
- 'spec/workers/repository_update_remote_mirror_worker_spec.rb'
+ - 'spec/workers/stuck_ci_jobs_worker_spec.rb'
+ - 'spec/workers/update_head_pipeline_for_merge_request_worker_spec.rb'
diff --git a/CHANGELOG.md b/CHANGELOG.md
index ed1b71a90de..ac1050362ae 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,16 +2,6 @@
documentation](doc/development/changelog.md) for instructions on adding your own
entry.
-## 13.1.4 (2020-07-09)
-
-### Fixed (4 changes)
-
-- Fix path conflict for Ghost on UpdateRoutesForLostAndFoundGroupAndOrphanedProjects. !35425
-- Fix existing repository_storages_weighted migrations. !35814
-- Fix error 500s creating new projects due to empty weights. !35829
-- Fix gitlab:*:check Rake tasks. !35944
-
-
## 13.1.3 (2020-07-06)
- No changes.
@@ -54,492 +44,57 @@ entry.
- Periodically recompute project authorizations. !34071
-## 13.1.0 (2020-06-22)
+## 13.0.10 (2020-07-09)
+
+### Fixed (1 change)
+
+- Fix gitlab:*:check Rake tasks. !35944
+
+
+## 13.0.9 (2020-07-06)
+
+- No changes.
+
+## 13.0.8 (2020-07-01)
+
+### Security (18 changes)
+
+- Update xterm js dependency to latest stable 3.x version.
+- Do not show activity for users with private profiles.
+- Fix stored XSS in markdown renderer.
+- Upgrade swagger-ui to solve XSS issues.
+- Fix group deploy token API authorizations.
+- Check access when sending TODOs related to merge requests.
+- Change from hybrid to JSON cookies serializer.
+- Prevent XSS in group name validations.
+- Disable caching for wiki attachments.
+- Disable Github Importer API by settings.
+- Fix null byte error in upload path.
+- Update permissions for time tracking endpoints.
+- Add snippet repository validation after bundle import.
+- Update Kaminari gem.
+- Fix note author name rendering.
+- Sanitize bitbucket repo urls to mitigate XSS.
+- Stored XSS on the Error Tracking page.
+- Fix security issue when rendering issuable.
-### Removed (4 changes, 2 of them are from the community)
-- Remove deprecated dashboard & group milestone pages. !13237
-- Removed UltraAuth integration for OmniAuth. !29330 (Kartikey Tanna)
-- Remove all search autocomplete for groups/projects/other. !31187
-- Remove temporary datepicker position fix as it is no longer required. !31836 (Arun Kumar Mohan)
+## 13.0.7 (2020-06-25)
-### Fixed (154 changes, 57 of them are from the community)
+### Fixed (7 changes)
-- Fix 'Active' checkbox text in Pipeline Schedule form to be a label. !27054 (Jonston Chan)
-- Fix back button when switching MR tabs. !29862 (Lee Tickett)
-- Remove ability to scroll Issue while in Design View. !29881
-- Fix merge request note label URLs. !30428 (Lee Tickett)
-- Fix default path when creating project from group template. !30597 (Lee Tickett)
- Group authorization refresh to consider shared groups. !31204
-- Fix group transfer service to deny moving group to its subgroup. !31495 (Abhisek Datta)
-- Fix issuable listings with any label filter. !31729
-- Move prepend to last in ee-app-services. !31838 (Rajendra Kadam)
-- Fallback to lowest visibility level in snippet visibility radio. !31847 (Jacopo Beschi @jacopo-beschi)
-- Add class stubs and fix leaky constant alert in query limit helper spec. !31949 (Rajendra Kadam)
-- Remove usage of spam constants in spec. !31959 (Rajendra Kadam)
-- Fix leaky constant issue in uninstall progress service check. !32036 (Rajendra Kadam)
-- Fix leaky constant issue in commit entity spec. !32039 (Rajendra Kadam)
-- Fix leaky constant issue in task completion status spec. !32043 (Rajendra Kadam)
-- Fix leaky constant issue in admin mode migration spec. !32074 (Rajendra Kadam)
-- Fix leaky constant issue in sidekiq middleware server metric spec. !32104 (Rajendra Kadam)
-- Fix leaky constant issue in sidekiq middleware client metric spec. !32108 (Rajendra Kadam)
-- Fix leaky constant issue in path regex spec. !32115 (Rajendra Kadam)
-- Fix leaky constant issue importer and cache headers spec. !32122 (Rajendra Kadam)
-- Fix leaky constant issue in relation factory spec. !32129 (Rajendra Kadam)
-- Fix leaky constant issue in test coverage spec. !32134 (Rajendra Kadam)
-- Prevent emails to user on expiry of impersonation token. !32140
-- Fix leaky constant issue in diff collection spec. !32163 (Rajendra Kadam)
-- Fix leaky constant issue in migration helpers, with lock retries and ignored cols spec. !32170 (Rajendra Kadam)
-- Fix leaky constant issue in factory spec. !32174 (Rajendra Kadam)
-- Fix leaky constant issue in creds factory spec. !32176 (Rajendra Kadam)
-- Use applogger in project import state file. !32182 (Rajendra Kadam)
-- Use applogger in project.rb. !32183 (Rajendra Kadam)
-- Use applogger in chat_team.rb. !32184 (Rajendra Kadam)
-- Use applogger in repository model. !32185 (Rajendra Kadam)
-- Use applogger in build and ssh host key. !32187 (Rajendra Kadam)
-- Use applogger in cache attrs and highest role ruby files. !32189 (Rajendra Kadam)
-- Use applogger in legacy project and namespace. !32190 (Rajendra Kadam)
-- Use applogger in base.rb. !32191 (Rajendra Kadam)
-- Use applogger in usage ping and webhook service. !32192 (Rajendra Kadam)
-- Use applogger in exclusive_lease_guard. !32194 (Rajendra Kadam)
-- Use applogger in groups destroy service and label create service. !32195 (Rajendra Kadam)
-- Use applogger in merge_service.rb. !32196 (Rajendra Kadam)
-- Use applogger in project create service and after import service. !32198 (Rajendra Kadam)
-- Use applogger in update stats service. !32200 (Rajendra Kadam)
-- Use applogger in base attachment service. !32201 (Rajendra Kadam)
-- Use applogger in export service. !32203 (Rajendra Kadam)
-- Use applogger in akismet service. !32205 (Rajendra Kadam)
-- Use applogger in file mover file. !32206 (Rajendra Kadam)
-- Use applogger in commit signature worker. !32207 (Rajendra Kadam)
-- Use applogger in delete user worker. !32209 (Rajendra Kadam)
-- Use applogger in email receiver worker. !32211 (Rajendra Kadam)
-- Use applogger in artifact worker. !32212 (Rajendra Kadam)
-- Use applogger in new note worker. !32213 (Rajendra Kadam)
-- Fix duplicate filename displayed in design todos. !32274 (Arun Kumar Mohan)
-- Add value length validations for instance level variable. !32303
-- Resolve image overflow at releases list panel. !32307
-- Clean up shared/tmp folder after Import/Export. !32326
-- Fix creating release evidence if release is created via UI. !32441
-- GraphQL hasNextPage and hasPreviousPage return correct values. !32476
-- Fix loading and empty state styling for alerts list. !32531
-- Resolve incorrect x-axis padding on the Environments Dashboard. !32533
-- Fix time_tracking help link. !32552
-- Don't display confidential note icon on confidential issue public notes. !32571
-- Update container expiration policy database defaults. !32600
-- Fix rendering of emojis in status tooltips. !32604
-- Hid copy contents button when blob has rendering error. !32632
-- Avoid refresh to show endedAt after mutation. !32636
-- Fix for metrics creation when saving MR. !32668
-- Skip the individual JIRA issues if failed to import vs failing the whole batch. !32673
-- Hide "Import from Jira" option from non-entitled users. !32685
-- Fix broken help link on operations settings page. !32722
-- Allow different in bulk editing issues. !32734
-- Fix whitespace changes overgrowing the diff container. !32774
-- Improve spacing and wrapping of group actions buttons and stats in group list view. !32786
-- Fix "Broadcast Messages" table overflow and button alignment. !32801
-- Fix 404 when downloading a non-archive artifact. !32811
-- Make commits author button confirm to Pajamas specs. !32821
-- Fix filename duplication in design notes in activity feeds. !32823 (Arun Kumar Mohan)
-- Prevent multiple Auto DevOps deployment jobs running concurrently when using manual rollout. !32824
-- Implement displaying downstream pipeline error details. !32844
-- Fix Runner heartbeats that results in considering them offline. !32851
-- Conan package registry support for the conan_export.tgz file. !32866
-- Fix plural message in account deletion section. !32868
-- Fix atomic processing bumping a lock_version. !32914
-- AsciiDoc: Add support for built-in alignment roles. !32928 (mnrvwl)
-- Fix a bug where some Vue apps would be unable to load when DAG tab is disabled. !32966
-- Fix undefined error in Gitlab::Git::Diff. !32967
-- Fix spelling error on Ci::RunnersFinder. !32985 (Arthur de Lapertosa Lisboa)
-- Fix polling for resource events. !33025
-- Fix broken CSS classes inside alert management list. !33038
-- Fix bug in snippet create mutation with non ActiveRecord errors. !33085
-- Fix overflow issue in MR and Issue comments. !33100
-- Fix alignment of button text on the Edit Release page. !33104
-- Deduplicate URL parameters when requesting merge request diffs which causes diffs load to fail. !33117
-- Fix tabbing through form fields in projects/new flow. !33209
-- Fix incorrect commit search results returned when searching with ref. !33216
-- Fix NoMethodError by using the correct method to report exceptions to Sentry. !33260
-- Fix KaTeX font paths. !33338
-- Resolve Fix Incomplete Kubernetes Cluster Status List. !33344
-- Fix auto-merge not running after discussions resolved. !33371
-- Fix bug in snippets updating only file_name or content. !33375
-- Fix invisible emoji modal on Set Status form when clicked the second time. !33398
-- vertically center action icon in the CI pipeline. !33427 (Nathanael Weber)
-- Wrap auto merge parameters update in database transaction. !33471
-- Return 404 response when redirecting request with invalid url. !33492
-- Fix ambiguous string concatenation on CleanupProjectsWithMissingNamespace. !33497
-- Fix snippet repository import edge cases. !33506
-- Rust CI template: Replace --all with --workspace on cargo test. !33517 (Markus Becker)
-- Make markdown textarea links tab-accessible. !33518
-- Pass hard delete option to snippets bulk destroy. !33520
-- Fix CI rules for ECS related jobs. !33527
-- Update GitLab Workhorse to v8.34.0. !33543
-- Fix snippet repository import fail with older export files. !33584
-- Web IDE: Create template files in the folder from which new file request was made. !33585 (Ashesh Vidyut)
-- Improve header acccessibility. !33603
-- Remove non migrated snippets from failed imports. !33621
-- Prevent duplicate issues when importing from CSV. !33626
-- Fix sidebar spacing for alert details. !33630
-- Fix linking alerts to created issues for the Generic alerts intergration. !33647
-- Resolve spacing ux debt on Release assets form field. !33684
-- Fix pagination link header. !33714 (Max Wittig)
- Fix Value Stream Analytics summary when using non-english locale. !33717
- Fix bug with variable substitution in alerts. !33772
-- Allow wiki pages with +<> characters in their title to be saved. !33803
-- Fix force_remove_source_branch not working in API. !33804
-- Fix prometheus alerts not being automatically created. !33806
-- Fix pagination for resource label events. !33821
- Fix relative URL root in wiki_base_path. !33841
-- Return code navigation path for nil diff_refs. !33850
-- Record audit event when an admin creates a new SSH Key for a user via the API. !33859 (Rajendra Kadam)
-- Do not create duplicate issues for exising Alert Management alerts. !33860
-- Add link text to collapsed left sidebar links for screen readers. !33866
-- Update text in error tracking list error message. !33872
- Adjust wrong column reference for ResetMergeStatus (background job). !33899
-- Fixed dashboard YAML file validaiton for files which do not contain object as root element. !33935
-- Fix design note scrolling. !33939
-- Update validates_hostname gem with support for more TLDs. !34010
-- Update wording of addMultipleToDiscussionWarning. !34088
-- Show all storages in settings. !34093
-- Set author as nullable in snippet GraphQL Type. !34135
-- Fix rendering of very long paths in merge request file tree. !34153
-- Remove not null constraint from events tables. !34190
-- Ensure we always generate a valid wiki event URL. !34191
-- Send information about attached files to the GraphQL mutation. !34221
-- Update issue limits template to use minutes. !34254
-- Add route for the lost-and-found group and update the route of orphaned projects. !34285
-- GraphQL - properly handle pagination of millisecond-precision timestamps. !34352
-- Fix 500 error in BlobController#delete. !34367
-- Updated Auto DevOps with a fix to delete PostgreSQL PVC on environment cleanup, a fix for multiline K8S_SECRET variables, updated Helm to 2.16.7 and glibc to 2.31. !34399 (verenion)
-- Fix issues with scroll on iOS / iPad OS. !34486
-- Fix order of integrations to be sorted alphabetically. !34501
-- Fix undefined method error. !34522
-- Use Keys::DestroyService for deleting an SSH key when an admin deletes a key via the API. !34535 (Rajendra Kadam)
-- Removed default artifact name for Terraform template. !34557
-- Footer system message fix.
-- Set experiementation cookie for GitLab domain only.
-- Add DS detection of build.gradle.kts.
-
-### Changed (76 changes, 5 of them are from the community)
-
-- Add a GraphQL endpoint to fetch Jira projects through its REST API. !28190
-- Change legends in monitor dashboards to tabular layout. !30131
-- Move pipelines routing under /-/ scope. !30730
-- Set markdown toolbar to use hyphens for lists. !31426
-- Use sprites for comment icons on Commits. !31696
-- Rate limit project export by user. !31719
-- Reorder diffs compare versions dropdowns. !31770 (Gilang Gumilar)
-- Enable the `in this group` action in the Search dropdown. !31939
-- Externalize i18n strings from ./app/views/shared/_promo.html.haml. !32109 (Gilang Gumilar)
-- Add Usage Ping count for all searches. !32111
-- Add tags_count to container registry api and controller. !32141
-- Externalize i18n strings from ./app/views/shared/milestones/_sidebar.html.haml. !32150 (Gilang Gumilar)
-- Externalize i18n strings from ./app/views/shared/milestones/_form_dates.html.haml. !32162 (Gilang Gumilar)
-- Improve Container Registry UI header. !32424
-- Added node size to cluster index. !32435
-- Update operations metrics settings title and description to make them general. !32494
-- Track merge_requests_users usage data. !32562
-- Adds cluster CPU and Memory to cluster index. !32601
-- Allow the snippet create service to accept an array of files. !32649
-- Move review related controllers/workers outside EE. !32663
-- Move the Members section from settings to the side nav for projects. !32667
-- Show more context in unresolved jump button. !32737
-- Exclude extra.server fields from exceptions_json.log. !32770
-- Improve new/unknown sign-in email styling. !32808
-- Allow the snippet update service to accept an array of files. !32832
-- Add new issue link to email notification header. !32833
-- Bump cluster-applications to 0.17.0, which updates Runner to 0.17.0 and Cilium to 1.7.4. !32931
-- Update artifacts section to show when an artifact is locked. !32992
-- Include tag count in the image repository list. !33027
-- Clean up gitlab-shell install-from-source path. !33057
-- Increase LFS token default time to 2 hours. !33140
-- Add explicit mention of Merge request in Slack message. !33152
-- Expose `release_links.type` via API. !33154
-- Add link_type column to release_links table. !33156
-- Move broadcast notification dismiss button to the top. !33174
-- Remove null constraint for JID in GroupImportState. !33181
-- Added provider type icon to cluster list. !33196
-- Remove search icon from Project find file button. !33198
-- Refine SAST language detection by frameworks. !33226
-- Render Merge request reference as link. !33248
-- Upgrade to Gitaly v13.1.0-rc1. !33302
-- Show disabled suggestion button with tooltip message. !33357
-- Add update validations to SnippetInputAction. !33379
-- Add snippet DB visibility check in spec. !33388 (Jacopo Beschi @jacopo-beschi)
-- Add Hugo logo to project templates. !33402
-- Add GitBook logo to project templates. !33403
-- Add GoMicro logo to project templates. !33404
-- Add Jekyll logo to project templates. !33405
-- Add Hexo logo to project templates. !33406
-- Rename Add Designs button. !33491
-- Add CPU, memory usage charts to self monitoring default dashboard. !33532
-- Add database migrations to design_management_designs.filename to enforce a 255 character limit, and modify any filenames that exceed that limit. !33565
-- Track Sentry error status updates with dedicated actions. !33623
-- Alert Managament: Change sorting order to have newest alerts first. !33642
-- Add blobs field to SnippetType in GraphQL. !33657
-- Format metrics column chart x axis dates. !33681
-- Style ToastUI contextual menus. !33719
-- Update Auto deploy image to v0.16.1, introducing support for AUTO_DEVOPS_DEPLOY_DEBUG. !33799
-- Add whether instance has Auto DevOps enabled to usage ping. !33811
-- Update local IP address and domain name allow list input label. !33812
-- Add date time format to the monitor stacked-column chart. !33814
-- Allow Tf Plan to genrate multiple reports. !33867
-- Remove async_merge_request_check_mergeability feature flag. !33917
-- Filter potentially-sensitive Sidekiq arguments from logs and Sentry. !33967
-- Update Static Site Editor toolbar to group inline-code and code-block buttons together. !34006
-- Set default values for SAST_EXCLUDED_PATHS and DS_EXCLUDED_PATHS. !34076
-- Add ability to filter self monitoring resource usage charts by instance name. !34084
-- Pick repository storage based on weight. !34095
-- Display error for YAML files that are too large. !34199
-- Change copy of webhooks / integration help text. !34301
-- Update board header icons. !34366
-- Show Redis instance in performance bar. !34377
-- Add secret detection template to Auto DevOps. !34467
-- Add allowed actions to snippet input action. !34499
-- Change from vendor specific to Gitlab. !34576
-- Assign alerts sidebar base.
-
-### Performance (19 changes, 1 of them is from the community)
-
-- Improve performance of commit search by limiting the number of results requested. !32260
-- Add GraphQL lookahead support. !32373
-- Update index_ci_builds_on_commit_id_and_artifacts_expireatandidpartial index for secret_detection. !32584
-- Add index on id and type for Snippets. !32885
-- Use build_stubbed to avoid interacting with the DB in todos helper specs. !32906 (Arun Kumar Mohan)
-- Optimize SQL queries on Milestone index page. !32953
-- Add build report results data model. !32991
-- Adjust condition for partial indexes on services table. !33044
-- Add index to issues and epics on last_edited_by_id. !33075
-- Fix preconnect typo in rel link. !33255
-- Add project_id, user_id, status, ref index to ci_pipelines. !33290
-- Move migration related to ci_builds to post_deployment. !33416
-- Reduce redundant queries for Search API users scope. !33795
-- Speed up boot time in production. !33929
-- Harden CI pipelines usage data queries with an index. !34045
-- Add partial index on locked merge requets. !34127
-- Lazy load commit_date and authored_date on Commit. !34181
-- Optimize container repository for groups query. !34364
-- Enable CI Atomic Processing by default.
-
-### Added (149 changes, 14 of them are from the community)
-
-- Add rake task to verify encrypted data through secrets. !21851
-- User can apply multiple suggestions at the same time. !22439 (Jesse Hall)
-- Resolve Add a button to assign users who have commented on an issue. !23883
-- Resolve Graph code coverage changes over time for a project. !26174
-- Add doc for custom validators in api styleguide. !26734 (Rajendra Kadam)
-- Add Scheduled Job for Monitoring Monitor Group Demo Environments. !27360
-- Add setting to allow merge on skipped pipeline. !27490 (Mathieu Parent)
-- Add dark theme (alpha). !28252
-- Show estimate on issues list. !28271 (Lee Tickett)
-- Make Fixed Email Notification Generally Available. !28338 (jacopo-beschi)
-- Add a link to the `renamed` viewer to fully expand the renamed file (if it's text). !28448
-- Focus and toggle metrics dashboard panels via keyboard. !28603
-- Remove `scoped_approval_rules` feature flag. !28864 (Lee Tickett)
-- Create Group import UI for creating new Groups. !29271
-- Add finder for group-level runners. !29283 (Arthur de Lapertosa Lisboa)
-- Allow customization of badge key_text and key_width. !29381 (Fabian Schneider @fabsrc)
-- Support Workhorse directly uploading files to S3. !29389
-- Add frontend support for multiline comments. !29516
-- Support first_name and last_name attributes in LDAP user sync. !29542
-- Add link to status page detail view for status page published issues. !30249
-- Add metrics dashboard name to document title. !30392
-- Backfill StatusPage::Published incidents and enable a publish quick action for EE. !30906
-- Add missing Merge Request fields. !30935
-- Show build status on branch list. !30948 (Lee Tickett)
-- Add mutation to create commits in GraphQL. !31102
-- Add GraphQL support for authored and assigned Merge Requests. !31227
-- Add usage data metrics for terraform states. !31280
-- Add usage data metrics for terraform reports. !31281
-- Add API endpoint for listing bridge jobs. !31370 (Abhijith Sivarajan)
-- SpamVerdictService can call external spam check endpoint. !31449
-- Move Admin note feature to GitLab Core. !31457 (Rajendra)
-- Add DAG serializer for pipelines controller. !31583
-- Save repository storages in application settings with weights. !31645
-- Add API endpoint for resource milestone events. !31720
-- Show import in progress screen for group imports. !31731
-- Add Verify/FailFast CI template. !31812
-- Improve Add/Remove Issue Labels API. !31864 (Lee Tickett)
-- Add mutation to create a merge request in GraphQL. !31867
-- Add warning popup for Elastic Stack update. !31972
-- Add API support for sharing groups with groups. !32008
-- Add the container expiration policy attribute to the project GraphQL type. !32100
-- Add GraphQL support for project and group labels. !32113
-- Add number of database calls to Prometheus metrics and logs for sidekiq and request. !32131
-- Filter pipelines by status. !32151
-- Filter pipelines based on url query params. !32230
-- Add metrics for Redis usage during Sidekiq job execution. !32265
-- Add filters to merge request fields. !32328
-- Support reading .editorconfig files inside of the Web IDE. !32378
-- [Frontend] Resolvable design discussions. !32399
-- Table index added to `metrics_dashboard_annotations` for future pruning of stale metrics Annotations for metrics dashboards are now checked for valid start and end dates. !32433
-- Enable GitLab-Flavored Markdown processing for design links. !32446
-- Filter Pipelines by Tag Name. !32470
-- Adds sorting by column to alert management list. !32478
-- Add project specific repository storage API. !32493
-- Adapt Limitable for system-wide features. !32574
-- Add application limits to instance level CI/CD variables. !32575
-- Add model for project level security auto-fix settings. !32577
-- Expose Jira imported issues count in GraphQL. !32580
-- Organize alerts by status tabs. !32582
-- Add note to ECS CI template. !32597
-- Add metrics for Redis usage during web requests. !32605
-- Add database and GraphQL support for alert assignees. !32609
-- Set fingerprints and increment events count for Alert Management alerts. !32613
-- Process stuck jira import jobs. !32643
-- Allow user to add custom links to their metrics dashboard panels. !32646
-- Add tags to experimental queue selector attributes. !32651
-- Allow generic endpoint to receive alerts from external Prometheus. !32676
-- Customize the Cloud Native Buildpack builder used with Auto Build. !32691
-- Add timezone display to alert based issue start time. !32702
-- Display dates on metrics dashboards in UTC time zone. !32746
-- Store Todo resolution method. !32753
-- Add experience_level to user_preferences. !32784
-- Remove metrics dashboard annotations attached to time periods older than two weeks. !32838
-- Monitor:Health metrics instrumenation. !32846
-- Adds PostHog as a CI/CD Managed Application. !32856
-- Groups API has top_level_only option to exclude subgroups. !32870
-- Create operations_feature_flags_issues table. !32876
-- Add api.js methods to update issues and merge requests. !32893
-- Render user-defined links in dashboard yml file on metrics dashboard. !32895
-- Add accessibility report MR widget. !32902
-- Add a GraphQL mutation for toggling the resolved state of a Discussion. !32934
-- Add container expiration policy objects to the GraphQL API. !32944
-- Don't hide Commit tab in Web IDE when there are no changes yet. !32979
-- Add column for alert slack notifications. !33017
-- Add ability to insert an image via SSE. !33029
-- Add user root query to GraphQL API. !33041
-- Adds groupMembership and projectMembership to GraphQL API. !33049
-- Alerts list pagination. !33073
-- Add ApplicationSetting ui changes for repository_storages_weighted. !33096
-- Display confirmation modal when user exits SSE and there are unsaved changes. !33103
-- Add column dashboard_timezone to project_metrics_setting. !33120
-- Allow the assignment of alerts to users from the alert detail view. !33122
-- Add solarized dark for Web IDE. !33148
-- Add support for artifacts/exclude configuration. !33170
-- Add root users query to GraphQL API. !33195
-- Added validation for YAML files with metrics dashboard definitions. !33202
-- Create issue from alert. !33213
-- Add max import file size option. !33215 (Roger Meier)
-- Add system note when assigning user to alert. !33217
-- Add count of alerts from all sources to usage ping. !33220
-- Add button to create an issue from an alert management alert. !33221
-- Add more detail to alert integration settings description. !33244
-- Add Evidence to Releases GraphQL endpoint. !33254
-- Add support for pasting images in the Web IDE. !33256
-- Add ProjectAccessToken table. !33272
-- Automatically resolve alert when associated issue closes. !33278
-- Add `link_type` to `ReleaseLink` GraphQL type. !33386
-- Add members to project graphQL endpoint. !33418
-- Update Static Site Editor WYSIWYG mode to hide front matter. !33441
-- Added delete action for Dashboard Annotations in GraphQL. !33468
-- Create graphQL endpoint for Jira users import. !33501
-- Support IAP protected prometheus installations. !33508
-- New instance-level variables UI. !33510
+- Updated Auto DevOps with a fix to delete PostgreSQL PVC on environment cleanup. !34657
+- Load user before logging git http-requests. !34923
+
+### Added (2 changes)
+
- Provide `__range` variable for Prometheus queries. !33521
-- Add support for `git filter-repo` to repository cleanup. !33576
-- Close open reply input fields in the design view sidebar when leaving a new comment. !33587
-- Add dashboard schema validation warnings as metrics dashboard GraphQL field. !33592
-- Add time range to user-defined links in metrics dashboard. !33663
-- Increase events count for Prometheus alerts. !33706
-- Track pod logs refresh action. !33802
-- Add secret detection template. !33869
-- Add DAG visualization MVC. !33958
-- Introduce a feature flag for Vue-based UI for all import providers. !33980
-- Add sticky title on Issue pages. !33983
-- Allow Release asset links to be associated with a type. !33998
-- Support user-defined Grafana links in metrics dashboard. !34003
-- Adds AWS guidance to CI/CD > Add Variable modal. !34009
-- Show custom attributes within Admin Pages. !34017 (Roger Meier)
-- Enable Slack notifications for alerts. !34038
-- Container expiration policy regular expressions are now validated. !34063
-- Add todo when alert is assigned to a user. !34104
-- Track merge requests submitted by Static Site Editor. !34105
-- Turn off alert issue creation by default. !34107
-- Add detailed logs of each Redis instance usage during job execution and web requests. !34110
-- Add API to schedule project repository storage moves. !34119
-- Add validation step on backend for metrics dashboard links. !34204
-- Track when Static Site Editor is initialized. !34215
-- Bring SAST to Core - brakeman. !34217
-- Mask key comments when exposing SSH/Deploy Keys via the API. !34255
-- Convert `:release` yaml to `release-cli` commands. !34261
-- Validate regex before sending them to CleanupContainerRepositoryWorker. !34282
-- Add secret_detection to DOWNLOADABLE_TYPES. !34313
-- Enable ability to assign alerts to users with corresponding system notes and todos. !34360
-- Enable CI Inheriting Env Variables feature. !34495
-- Show tooltip on error detail page when hovering over dates. !34506
-- Add native code intelligence. !34542
-- Bump cluster-applications version to v0.20.0. !34569
-- Add search argument for AlertStatusCountsResolver. !34596
-- Allow CI_JOB_TOKEN for authenticating to the Terraform state API. !34618
-
-### Other (65 changes, 36 of them are from the community)
-
-- Improve fast-forward merge is not possible message. !22834 (Ben Bodenmiller)
-- Remove unused WAF indexes from CI variables. !30021
-- Update the visual design of badges in some areas. !31646
-- Extract featurable concern from ProjectFeature. !31700 (Alexander Randa)
-- Remove update function logic from list model. !31900 (nuwe1)
-- Remove nextpage function logic from list model. !31904 (nuwe1)
-- Squash database migrations prior to 2019 into one. !31936
-- Update deprecated slot syntax in app/assets/javascripts/reports/components/grouped_test_reports_app.vue. !31975 (Gilang Gumilar)
-- Replace slot syntax for Vue 3 migration. !31987 (gaslan)
-- Update deprecated slot syntax in ./app/assets/javascripts/pages/admin/projects/index/components/delete_project_modal.vue. !31994 (Gilang Gumilar)
-- Update deprecated slot syntax in ./app/assets/javascripts/pages/projects/labels/components/promote_label_modal.vue. !31995 (Gilang Gumilar)
-- Update deprecated slot syntax in ./app/assets/javascripts/clusters/components/remove_cluster_confirmation.vue. !32010 (Gilang Gumilar)
-- Update deprecated slot syntax in ./app/assets/javascripts/environments/components/environments_app.vue. !32011 (Gilang Gumilar)
-- Remove setLoadingState logic from issue model. !32226 (nuwe1)
-- Remove addAssignee logic from issue model. !32231 (nuwe1)
-- Remove addLabel Logic from issue models. !32233 (nuwe1)
-- Remove addMilestone logic from issue model. !32235 (nuwe1)
-- Remove destroy function logic from list model. !32237 (nuwe1)
-- Remove findAssignee logic from issue model. !32238 (nuwe1)
-- Remove findLabel logic from issue model. !32239 (nuwe1)
-- Remove findIssue logic from list model. !32241 (nuwe1)
-- Remove moveIssue logic from list model. !32242 (nuwe1)
-- Remove newIssue logic from list model. !32244 (nuwe1)
-- Remove removeAllAssignees logic from issue model. !32247 (nuwe1)
-- Remove removeAssignee logic from issue model. !32248 (nuwe1)
-- Clarify verbiage for stuck job messages. !32250
-- Remove removeLabel logic from issue model. !32251 (nuwe1)
-- Remove removeLabels logic from issue model. !32252 (nuwe1)
-- Remove removeMilestone logic from issue model. !32253 (nuwe1)
-- Remove removeMultipleIssues logic from list model. !32254 (nuwe1)
-- Remove setFetchingState logic from issue model. !32255 (nuwe1)
-- Remove updateData logic from issue model. !32256 (nuwe1)
-- Update U2F docs for Firefox 67+. !32289 (Takuya Noguchi)
-- Update alert management mobile table alignment. !32295
-- Include available instance memory in usage ping. !32315
-- Moves merge request reviews into Core. !32558
-- Update GitLab Runner Helm Chart to 0.17.0. !32634
-- Add snowplow tracking for logs page. !32704
-- Extend "Remember me" token after each login. !32730
-- Assign alerts sidebar container fix. !32743
-- Add anchor for creating a branch. !32745
-- Tidy. !32759 (Lee Tickett)
-- Less verbose JiraService error logs. !32847
-- Reduced padding and increased emphasis of titles within the epic tree. !32873
-- Remove obsolete users.ghost column. !32957
-- Move NoPrimary table def to last context in spec. !33015 (Rajendra Kadam)
-- Document github rate limit behavior. !33090
-- Added build_id column to requirements_management_test_reports table. !33184
-- Add version history information on U2F support. !33229 (Takuya Noguchi)
-- Convert IP spoofing errors into client errors. !33280
-- Update docs to reflect move web IDE Terminal and file sync to Core. !33419
-- Add hovering icon for sorting columns on alert management list. !33429
-- Avoid javascript for omniauth logins. !33459 (Diego Louzán)
-- Add opacity transition to active design discussion pins. !33493
-- Update GitLab Runner Helm Chart to 0.17.1. !33504
-- Make project selector in various dashboard more translatable. !33771
-- Update Workhorse to v8.35.0. !33817
-- Remove FF hide_token_from_runners_api. !33947
-- Bump omniauth_openid_connect to 0.3.5. !34030 (Roger Meier)
-- Specify tiers for SAML SSO at self-hosted plans. !34040 (Takuya Noguchi)
-- Backfill failed imported snippet repositories. !34052
-- Use GitLab SVG icon for file attacher action. !34196
-- Add GraphQL snippet FileInputType. !34442
-- Update red hex values to match GitLab UI. !34544
-- Remove removeIssue logic from list model. (nuwe1)
+- Periodically recompute project authorizations. !34071
## 13.0.6 (2020-06-10)
@@ -1154,6 +709,38 @@ entry.
- Use visitUrl in Alert management. !32414
+## 12.10.14 (2020-07-06)
+
+- No changes.
+
+## 12.10.13 (2020-07-01)
+
+### Security (15 changes)
+
+- Do not show activity for users with private profiles.
+- Fix stored XSS in markdown renderer.
+- Upgrade swagger-ui to solve XSS issues.
+- Fix group deploy token API authorizations.
+- Check access when sending TODOs related to merge requests.
+- Change from hybrid to JSON cookies serializer.
+- Prevent XSS in group name validations.
+- Disable caching for wiki attachments.
+- Fix null byte error in upload path.
+- Update permissions for time tracking endpoints.
+- Update Kaminari gem.
+- Fix note author name rendering.
+- Sanitize bitbucket repo urls to mitigate XSS.
+- Stored XSS on the Error Tracking page.
+- Fix security issue when rendering issuable.
+
+
+## 12.10.12 (2020-06-24)
+
+### Fixed (1 change)
+
+- Correctly count wiki pages in sidebar. !30508
+
+
## 12.10.11 (2020-06-10)
- No changes.
diff --git a/Dangerfile b/Dangerfile
index cc6ebc27d4e..cba7226d4b9 100644
--- a/Dangerfile
+++ b/Dangerfile
@@ -6,6 +6,7 @@ require_relative 'lib/gitlab/danger/request_helper'
danger.import_plugin('danger/plugins/helper.rb')
danger.import_plugin('danger/plugins/roulette.rb')
danger.import_plugin('danger/plugins/changelog.rb')
+danger.import_plugin('danger/plugins/sidekiq_queues.rb')
return if helper.release_automation?
diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION
index b9f7bfcfa33..cfe2f8cbb3f 100644
--- a/GITALY_SERVER_VERSION
+++ b/GITALY_SERVER_VERSION
@@ -1 +1 @@
-13.1.4
+9e6f5f40e6eb44655b6acfd5dc222af04333a4f2
diff --git a/GITLAB_ELASTICSEARCH_INDEXER_VERSION b/GITLAB_ELASTICSEARCH_INDEXER_VERSION
index 276cbf9e285..197c4d5c2d7 100644
--- a/GITLAB_ELASTICSEARCH_INDEXER_VERSION
+++ b/GITLAB_ELASTICSEARCH_INDEXER_VERSION
@@ -1 +1 @@
-2.3.0
+2.4.0
diff --git a/GITLAB_PAGES_VERSION b/GITLAB_PAGES_VERSION
index 84cc529467b..3500250a4b0 100644
--- a/GITLAB_PAGES_VERSION
+++ b/GITLAB_PAGES_VERSION
@@ -1 +1 @@
-1.18.0
+1.21.0
diff --git a/GITLAB_WORKHORSE_VERSION b/GITLAB_WORKHORSE_VERSION
index 13afb01d493..2554e8ae98f 100644
--- a/GITLAB_WORKHORSE_VERSION
+++ b/GITLAB_WORKHORSE_VERSION
@@ -1 +1 @@
-8.35.0
+8.37.0
diff --git a/Gemfile b/Gemfile
index 33d4ebb7f22..e0820947158 100644
--- a/Gemfile
+++ b/Gemfile
@@ -19,7 +19,7 @@ gem 'default_value_for', '~> 3.3.0'
gem 'pg', '~> 1.1'
gem 'rugged', '~> 0.28'
-gem 'grape-path-helpers', '~> 1.2'
+gem 'grape-path-helpers', '~> 1.3'
gem 'faraday', '~> 0.12'
gem 'marginalia', '~> 1.8.0'
@@ -66,7 +66,7 @@ gem 'u2f', '~> 0.2.1'
gem 'validates_hostname', '~> 1.0.10'
gem 'rubyzip', '~> 2.0.0', require: 'zip'
# GitLab Pages letsencrypt support
-gem 'acme-client', '~> 2.0.5'
+gem 'acme-client', '~> 2.0', '>= 2.0.6'
# Browser detection
gem 'browser', '~> 2.5'
@@ -81,7 +81,9 @@ gem 'gitlab_omniauth-ldap', '~> 2.1.1', require: 'omniauth-ldap'
gem 'net-ldap'
# API
-gem 'grape', '~> 1.1.0'
+# Locked at Grape v1.4.0 until https://github.com/ruby-grape/grape/pull/2088 is merged
+# Remove config/initializers/grape_patch.rb
+gem 'grape', '= 1.4.0'
gem 'grape-entity', '~> 0.7.1'
gem 'rack-cors', '~> 1.0.6', require: 'rack/cors'
@@ -140,6 +142,7 @@ gem 'deckar01-task_list', '2.3.1'
gem 'gitlab-markup', '~> 1.7.1'
gem 'github-markup', '~> 1.7.0', require: 'github/markup'
gem 'commonmarker', '~> 0.20'
+gem 'kramdown', '~> 2.2.1'
gem 'RedCloth', '~> 4.3.2'
gem 'rdoc', '~> 6.1.2'
gem 'org-ruby', '~> 0.9.12'
@@ -148,7 +151,7 @@ gem 'wikicloth', '0.8.1'
gem 'asciidoctor', '~> 2.0.10'
gem 'asciidoctor-include-ext', '~> 0.3.1', require: false
gem 'asciidoctor-plantuml', '~> 0.0.12'
-gem 'rouge', '~> 3.19.0'
+gem 'rouge', '~> 3.21.0'
gem 'truncato', '~> 0.7.11'
gem 'bootstrap_form', '~> 4.2.0'
gem 'nokogiri', '~> 1.10.9'
@@ -163,6 +166,8 @@ gem 'diff_match_patch', '~> 0.1.0'
# Application server
gem 'rack', '~> 2.0.9'
+# https://github.com/sharpstone/rack-timeout/blob/master/README.md#rails-apps-manually
+gem 'rack-timeout', '~> 0.5.1', require: 'rack/timeout/base'
group :unicorn do
gem 'unicorn', '~> 5.5'
@@ -172,7 +177,6 @@ end
group :puma do
gem 'gitlab-puma', '~> 4.3.3.gitlab.2', require: false
gem 'gitlab-puma_worker_killer', '~> 0.1.1.gitlab.1', require: false
- gem 'rack-timeout', require: false
end
# State machine
@@ -242,7 +246,9 @@ gem 'slack-messenger', '~> 2.3.3'
gem 'hangouts-chat', '~> 0.0.5'
# Asana integration
-gem 'asana', '~> 0.9'
+# asana 0.10.1 needs faraday 1.0
+# https://gitlab.com/gitlab-org/gitlab/-/issues/224296
+gem 'asana', '0.10.0'
# FogBugz integration
gem 'ruby-fogbugz', '~> 0.2.1'
@@ -300,7 +306,7 @@ gem 'sentry-raven', '~> 2.9'
gem 'premailer-rails', '~> 1.10.3'
# LabKit: Tracing and Correlation
-gem 'gitlab-labkit', '0.12.0'
+gem 'gitlab-labkit', '0.12.1'
# I18n
gem 'ruby_parser', '~> 3.8', require: false
@@ -331,10 +337,9 @@ group :development do
gem 'danger', '~> 6.0', require: false
gem 'letter_opener_web', '~> 1.3.4'
- gem 'rblineprof', '~> 0.3.6', platform: :mri, require: false
# Better errors handler
- gem 'better_errors', '~> 2.5.0'
+ gem 'better_errors', '~> 2.7.1'
gem 'binding_of_caller', '~> 0.8.0'
# thin instead webrick
@@ -361,7 +366,7 @@ group :development, :test do
gem 'spring', '~> 2.0.0'
gem 'spring-commands-rspec', '~> 1.0.4'
- gem 'gitlab-styles', '~> 4.2.0', require: false
+ gem 'gitlab-styles', '~> 4.3.0', require: false
# Pin these dependencies, otherwise a new rule could break the CI pipelines
gem 'rubocop', '~> 0.82.0'
gem 'rubocop-performance', '~> 1.5.2'
@@ -370,6 +375,7 @@ group :development, :test do
gem 'scss_lint', '~> 0.56.0', require: false
gem 'haml_lint', '~> 0.34.0', require: false
gem 'simplecov', '~> 0.18.5', require: false
+ gem 'simplecov-cobertura', '~> 1.3.1', require: false
gem 'bundler-audit', '~> 0.6.1', require: false
gem 'benchmark-ips', '~> 2.3.0', require: false
@@ -383,6 +389,8 @@ group :development, :test do
gem 'png_quantizator', '~> 0.2.1', require: false
gem 'parallel', '~> 1.19', require: false
+
+ gem 'rblineprof', '~> 0.3.6', platform: :mri, require: false
end
# Gems required in omnibus-gitlab pipeline
@@ -452,7 +460,7 @@ group :ed25519 do
end
# Gitaly GRPC protocol definitions
-gem 'gitaly', '~> 13.1.0.pre.rc1'
+gem 'gitaly', '~> 13.2.0.pre.rc2'
gem 'grpc', '~> 1.24.0'
@@ -497,3 +505,5 @@ gem 'valid_email', '~> 0.1'
# JSON
gem 'json', '~> 2.3.0'
gem 'json-schema', '~> 2.8.0'
+gem 'oj', '~> 3.10.6'
+gem 'multi_json', '~> 1.14.1'
diff --git a/Gemfile.lock b/Gemfile.lock
index 69bd5235472..fbe5cfff1f1 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -4,8 +4,8 @@ GEM
RedCloth (4.3.2)
abstract_type (0.0.7)
ace-rails-ap (4.1.2)
- acme-client (2.0.5)
- faraday (~> 0.9, >= 0.9.1)
+ acme-client (2.0.6)
+ faraday (>= 0.17, < 2.0.0)
actioncable (6.0.3.1)
actionpack (= 6.0.3.1)
nio4r (~> 2.0)
@@ -76,7 +76,7 @@ GEM
apollo_upload_server (2.0.0.beta.3)
graphql (>= 1.8)
rails (>= 4.2)
- asana (0.9.3)
+ asana (0.10.0)
faraday (~> 0.9)
faraday_middleware (~> 0.9)
faraday_middleware-multi_json (~> 0.0)
@@ -103,10 +103,6 @@ GEM
aws-sdk-core (= 2.11.374)
aws-sigv4 (1.1.0)
aws-eventstream (~> 1.0, >= 1.0.2)
- axiom-types (0.1.1)
- descendants_tracker (~> 0.0.4)
- ice_nine (~> 0.11.0)
- thread_safe (~> 0.3, >= 0.3.1)
babosa (1.0.2)
base32 (0.3.2)
batch-loader (1.4.0)
@@ -115,7 +111,7 @@ GEM
benchmark-ips (2.3.0)
benchmark-memory (0.1.2)
memory_profiler (~> 0.9)
- better_errors (2.5.0)
+ better_errors (2.7.1)
coderay (>= 1.0.0)
erubi (>= 1.0.0)
rack (>= 0.9.0)
@@ -164,8 +160,6 @@ GEM
nap
open4 (~> 1.3)
coderay (1.1.2)
- coercible (1.0.0)
- descendants_tracker (~> 0.0.1)
colored2 (3.1.2)
commonmarker (0.20.1)
ruby-enum (~> 0.5)
@@ -221,8 +215,6 @@ GEM
ruby-statistics (>= 2.1)
thor (>= 0.19, < 2)
unicode_plot (>= 0.0.4, < 1.0.0)
- descendants_tracker (0.0.4)
- thread_safe (~> 0.3, >= 0.3.1)
device_detector (1.0.0)
devise (4.7.1)
bcrypt (~> 3.0)
@@ -249,6 +241,28 @@ GEM
doorkeeper-openid_connect (1.6.3)
doorkeeper (>= 5.0, < 5.2)
json-jwt (~> 1.6)
+ dry-configurable (0.11.5)
+ concurrent-ruby (~> 1.0)
+ dry-core (~> 0.4, >= 0.4.7)
+ dry-equalizer (~> 0.2)
+ dry-container (0.7.2)
+ concurrent-ruby (~> 1.0)
+ dry-configurable (~> 0.1, >= 0.1.3)
+ dry-core (0.4.9)
+ concurrent-ruby (~> 1.0)
+ dry-equalizer (0.3.0)
+ dry-inflector (0.2.0)
+ dry-logic (1.0.6)
+ concurrent-ruby (~> 1.0)
+ dry-core (~> 0.2)
+ dry-equalizer (~> 0.2)
+ dry-types (1.4.0)
+ concurrent-ruby (~> 1.0)
+ dry-container (~> 0.3)
+ dry-core (~> 0.4, >= 0.4.4)
+ dry-equalizer (~> 0.3)
+ dry-inflector (~> 0.1, >= 0.1.2)
+ dry-logic (~> 1.0, >= 1.0.2)
ed25519 (1.2.4)
elasticsearch (6.8.0)
elasticsearch-api (= 6.8.0)
@@ -290,7 +304,7 @@ GEM
multipart-post (>= 1.2, < 3)
faraday-http-cache (2.0.0)
faraday (~> 0.8)
- faraday_middleware (0.12.2)
+ faraday_middleware (0.14.0)
faraday (>= 0.7.4, < 1.0)
faraday_middleware-aws-signers-v4 (0.1.7)
aws-sdk-resources (~> 2)
@@ -377,12 +391,12 @@ GEM
po_to_json (>= 1.0.0)
rails (>= 3.2.0)
git (1.5.0)
- gitaly (13.1.0.pre.rc1)
+ gitaly (13.2.0.pre.rc2)
grpc (~> 1.0)
github-markup (1.7.0)
gitlab-chronic (0.10.5)
numerizer (~> 0.2)
- gitlab-labkit (0.12.0)
+ gitlab-labkit (0.12.1)
actionpack (>= 5.0.0, < 6.1.0)
activesupport (>= 5.0.0, < 6.1.0)
grpc (~> 1.19)
@@ -400,7 +414,7 @@ GEM
gitlab-puma (>= 2.7, < 5)
gitlab-sidekiq-fetcher (0.5.2)
sidekiq (~> 5)
- gitlab-styles (4.2.0)
+ gitlab-styles (4.3.0)
rubocop (~> 0.82.0)
rubocop-gitlab-security (~> 0.1.0)
rubocop-performance (~> 1.5.2)
@@ -439,19 +453,19 @@ GEM
signet (~> 0.14)
gpgme (2.0.20)
mini_portile2 (~> 2.3)
- grape (1.1.0)
+ grape (1.4.0)
activesupport
builder
+ dry-types (>= 1.1)
mustermann-grape (~> 1.0.0)
rack (>= 1.3.0)
rack-accept
- virtus (>= 1.0.0)
grape-entity (0.7.1)
activesupport (>= 4.0)
multi_json (>= 1.3.2)
- grape-path-helpers (1.2.0)
+ grape-path-helpers (1.3.0)
activesupport
- grape (~> 1.0)
+ grape (~> 1.3)
rake (~> 12)
grape_logging (1.8.3)
grape
@@ -575,7 +589,8 @@ GEM
kgio (2.11.3)
knapsack (1.17.0)
rake
- kramdown (2.1.0)
+ kramdown (2.2.1)
+ rexml
kramdown-parser-gfm (1.1.0)
kramdown (~> 2.0)
kubeclient (4.6.0)
@@ -641,9 +656,10 @@ GEM
multi_xml (0.6.0)
multipart-post (2.1.1)
murmurhash3 (0.1.6)
- mustermann (1.0.3)
- mustermann-grape (1.0.0)
- mustermann (~> 1.0.0)
+ mustermann (1.1.1)
+ ruby2_keywords (~> 0.0.1)
+ mustermann-grape (1.0.1)
+ mustermann (>= 1.0.0)
nakayoshi_fork (0.0.4)
nap (1.1.0)
nenv (0.3.0)
@@ -671,6 +687,7 @@ GEM
octokit (4.15.0)
faraday (>= 0.9)
sawyer (~> 0.8.0, >= 0.5.3)
+ oj (3.10.6)
omniauth (1.9.0)
hashie (>= 3.4.6, < 3.7.0)
rack (>= 1.6.2, < 3)
@@ -801,7 +818,7 @@ GEM
rack
rack-test (1.1.0)
rack (>= 1.0, < 3)
- rack-timeout (0.5.1)
+ rack-timeout (0.5.2)
rails (6.0.3.1)
actioncable (= 6.0.3.1)
actionmailbox (= 6.0.3.1)
@@ -890,7 +907,7 @@ GEM
rexml (3.2.4)
rinku (2.0.0)
rotp (2.1.2)
- rouge (3.19.0)
+ rouge (3.21.0)
rqrcode (0.7.0)
chunky_png
rqrcode-rails3 (0.1.7)
@@ -958,6 +975,7 @@ GEM
ruby-saml (1.7.2)
nokogiri (>= 1.5.10)
ruby-statistics (2.1.2)
+ ruby2_keywords (0.0.2)
ruby_dep (1.5.0)
ruby_parser (3.13.1)
sexp_processor (~> 4.9)
@@ -1003,11 +1021,11 @@ GEM
shellany (0.0.1)
shoulda-matchers (4.0.1)
activesupport (>= 4.2.0)
- sidekiq (5.2.7)
+ sidekiq (5.2.9)
connection_pool (~> 2.2, >= 2.2.2)
- rack (>= 1.5.0)
+ rack (~> 2.0)
rack-protection (>= 1.5.0)
- redis (>= 3.3.5, < 5)
+ redis (>= 3.3.5, < 4.2)
sidekiq-cron (1.0.4)
fugit (~> 1.1)
sidekiq (>= 4.2.1)
@@ -1020,6 +1038,8 @@ GEM
simplecov (0.18.5)
docile (~> 1.1)
simplecov-html (~> 0.11)
+ simplecov-cobertura (1.3.1)
+ simplecov (~> 0.8)
simplecov-html (0.12.2)
sixarm_ruby_unaccent (1.2.0)
slack-messenger (2.3.3)
@@ -1119,11 +1139,6 @@ GEM
activerecord (>= 3.0)
activesupport (>= 3.0)
version_sorter (2.2.4)
- virtus (1.0.5)
- axiom-types (~> 0.1)
- coercible (~> 1.0)
- descendants_tracker (~> 0.0, >= 0.0.3)
- equalizer (~> 0.0, >= 0.0.9)
vmstat (2.3.0)
warden (1.2.8)
rack (>= 2.0.6)
@@ -1155,13 +1170,13 @@ PLATFORMS
DEPENDENCIES
RedCloth (~> 4.3.2)
ace-rails-ap (~> 4.1.0)
- acme-client (~> 2.0.5)
+ acme-client (~> 2.0, >= 2.0.6)
activerecord-explain-analyze (~> 0.1)
acts-as-taggable-on (~> 6.0)
addressable (~> 2.7)
akismet (~> 3.0)
apollo_upload_server (~> 2.0.0.beta3)
- asana (~> 0.9)
+ asana (= 0.10.0)
asciidoctor (~> 2.0.10)
asciidoctor-include-ext (~> 0.3.1)
asciidoctor-plantuml (~> 0.0.12)
@@ -1175,7 +1190,7 @@ DEPENDENCIES
bcrypt_pbkdf (~> 1.0)
benchmark-ips (~> 2.3.0)
benchmark-memory (~> 0.1)
- better_errors (~> 2.5.0)
+ better_errors (~> 2.7.1)
binding_of_caller (~> 0.8.0)
bootsnap (~> 1.4.6)
bootstrap_form (~> 4.2.0)
@@ -1236,10 +1251,10 @@ DEPENDENCIES
gettext (~> 3.2.2)
gettext_i18n_rails (~> 1.8.0)
gettext_i18n_rails_js (~> 1.3)
- gitaly (~> 13.1.0.pre.rc1)
+ gitaly (~> 13.2.0.pre.rc2)
github-markup (~> 1.7.0)
gitlab-chronic (~> 0.10.5)
- gitlab-labkit (= 0.12.0)
+ gitlab-labkit (= 0.12.1)
gitlab-license (~> 1.0)
gitlab-mail_room (~> 0.0.6)
gitlab-markup (~> 1.7.1)
@@ -1247,16 +1262,16 @@ DEPENDENCIES
gitlab-puma (~> 4.3.3.gitlab.2)
gitlab-puma_worker_killer (~> 0.1.1.gitlab.1)
gitlab-sidekiq-fetcher (= 0.5.2)
- gitlab-styles (~> 4.2.0)
+ gitlab-styles (~> 4.3.0)
gitlab_chronic_duration (~> 0.10.6.2)
gitlab_omniauth-ldap (~> 2.1.1)
gon (~> 6.2)
google-api-client (~> 0.33)
google-protobuf (~> 3.8.0)
gpgme (~> 2.0.19)
- grape (~> 1.1.0)
+ grape (= 1.4.0)
grape-entity (~> 0.7.1)
- grape-path-helpers (~> 1.2)
+ grape-path-helpers (~> 1.3)
grape_logging (~> 1.7)
graphiql-rails (~> 1.4.10)
graphql (~> 1.10.5)
@@ -1282,6 +1297,7 @@ DEPENDENCIES
jwt (~> 2.1.0)
kaminari (~> 1.0)
knapsack (~> 1.17)
+ kramdown (~> 2.2.1)
kubeclient (~> 4.6.0)
letter_opener_web (~> 1.3.4)
license_finder (~> 5.4)
@@ -1297,6 +1313,7 @@ DEPENDENCIES
mimemagic (~> 0.3.2)
mini_magick
minitest (~> 5.11.0)
+ multi_json (~> 1.14.1)
nakayoshi_fork (~> 0.0.4)
net-ldap
net-ntp
@@ -1304,6 +1321,7 @@ DEPENDENCIES
nokogiri (~> 1.10.9)
oauth2 (~> 1.4)
octokit (~> 4.15)
+ oj (~> 3.10.6)
omniauth (~> 1.8)
omniauth-auth0 (~> 2.0.0)
omniauth-authentiq (~> 0.3.3)
@@ -1335,7 +1353,7 @@ DEPENDENCIES
rack-cors (~> 1.0.6)
rack-oauth2 (~> 1.9.3)
rack-proxy (~> 0.6.0)
- rack-timeout
+ rack-timeout (~> 0.5.1)
rails (~> 6.0.3.1)
rails-controller-testing
rails-i18n (~> 6.0)
@@ -1352,7 +1370,7 @@ DEPENDENCIES
request_store (~> 1.5)
responders (~> 3.0)
retriable (~> 3.1.2)
- rouge (~> 3.19.0)
+ rouge (~> 3.21.0)
rqrcode-rails3 (~> 0.1.7)
rspec-parameterized
rspec-rails (~> 4.0.0)
@@ -1380,6 +1398,7 @@ DEPENDENCIES
sidekiq-cron (~> 1.0)
simple_po_parser (~> 1.1.2)
simplecov (~> 0.18.5)
+ simplecov-cobertura (~> 1.3.1)
slack-messenger (~> 2.3.3)
snowplow-tracker (~> 0.6.1)
spring (~> 2.0.0)
diff --git a/app/assets/images/bot_avatars/alert-bot.png b/app/assets/images/bot_avatars/alert-bot.png
new file mode 100644
index 00000000000..985d67d6179
--- /dev/null
+++ b/app/assets/images/bot_avatars/alert-bot.png
Binary files differ
diff --git a/app/assets/images/bot_avatars/security-bot.png b/app/assets/images/bot_avatars/security-bot.png
new file mode 100644
index 00000000000..0709f62f07b
--- /dev/null
+++ b/app/assets/images/bot_avatars/security-bot.png
Binary files differ
diff --git a/app/assets/images/bot_avatars/support-bot.png b/app/assets/images/bot_avatars/support-bot.png
new file mode 100644
index 00000000000..1335205c191
--- /dev/null
+++ b/app/assets/images/bot_avatars/support-bot.png
Binary files differ
diff --git a/app/assets/images/confluence.svg b/app/assets/images/confluence.svg
new file mode 100644
index 00000000000..f51d4318b6b
--- /dev/null
+++ b/app/assets/images/confluence.svg
@@ -0,0 +1 @@
+<svg height="16" width="16" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><linearGradient id="a"><stop offset="0" stop-color="#344563"/><stop offset=".68" stop-color="#637088"/><stop offset="1" stop-color="#7a869a"/></linearGradient><linearGradient id="b" gradientUnits="userSpaceOnUse" x1="14.873" x2="5.739" xlink:href="#a" y1="15.883" y2="10.625"/><linearGradient id="c" gradientUnits="userSpaceOnUse" x1="-168376" x2="-168177" xlink:href="#a" y1="-6722.4" y2="-6493.53"/><path d="m1.517 11.68c-.15.243-.32.53-.453.757a.462.462 0 0 0 .155.63l3.013 1.863a.466.466 0 0 0 .645-.158l.445-.735c1.197-1.97 2.402-1.732 4.571-.703l2.995 1.424a.468.468 0 0 0 .626-.232l1.448-3.24a.466.466 0 0 0 -.229-.606c-.633-.298-1.89-.89-3.016-1.434-4.089-2.004-7.551-1.86-10.2 2.434z" fill="url(#b)"/><path d="m14.479 4.315c.15-.243.324-.53.456-.758a.46.46 0 0 0 -.158-.63l-3.025-1.857a.464.464 0 0 0 -.644.158 22.81 22.81 0 0 1 -.446.736c-1.196 1.972-2.4 1.733-4.567.703l-2.993-1.424a.468.468 0 0 0 -.625.231l-1.437 3.246a.46.46 0 0 0 .225.607c.633.298 1.892.89 3.014 1.435 4.097 1.99 7.556 1.858 10.199-2.446z" fill="url(#c)"/></svg> \ No newline at end of file
diff --git a/app/assets/images/logos/jira-gray.svg b/app/assets/images/logos/jira-gray.svg
new file mode 100644
index 00000000000..0e7069f2bd2
--- /dev/null
+++ b/app/assets/images/logos/jira-gray.svg
@@ -0,0 +1 @@
+<svg id="Logos" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="80" height="80" viewBox="0 0 80 80"><defs><style>.cls-1{fill:#7a869a;}.cls-2{fill:url(#linear-gradient);}.cls-3{fill:url(#linear-gradient-2);}</style><linearGradient id="linear-gradient" x1="38.11" y1="18.54" x2="23.17" y2="33.48" gradientUnits="userSpaceOnUse"><stop offset="0.18" stop-color="#344563"/><stop offset="1" stop-color="#7a869a"/></linearGradient><linearGradient id="linear-gradient-2" x1="42.07" y1="61.47" x2="56.98" y2="46.55" xlink:href="#linear-gradient"/></defs><title>jira software-icon-gradient-neutral</title><path class="cls-1" d="M74.18,38,43,6.9l-3-3h0L16.58,27.32h0L5.86,38a2.86,2.86,0,0,0,0,4.05L27.28,63.51,40,76.25,63.47,52.81l.36-.36L74.18,42.09A2.86,2.86,0,0,0,74.18,38ZM40,50.77l-10.7-10.7L40,29.37l10.7,10.7Z"/><path class="cls-2" d="M40,29.37A18,18,0,0,1,40,4L16.54,27.37,29.28,40.11,40,29.37Z"/><path class="cls-3" d="M50.75,40,40,50.77a18,18,0,0,1,0,25.48h0L63.5,52.78Z"/></svg>
diff --git a/app/assets/javascripts/alert_management/components/alert_details.vue b/app/assets/javascripts/alert_management/components/alert_details.vue
index ed6b4b7fdb2..0731349630c 100644
--- a/app/assets/javascripts/alert_management/components/alert_details.vue
+++ b/app/assets/javascripts/alert_management/components/alert_details.vue
@@ -12,18 +12,21 @@ import {
GlTable,
} from '@gitlab/ui';
import { s__ } from '~/locale';
-import query from '../graphql/queries/details.query.graphql';
+import alertQuery from '../graphql/queries/details.query.graphql';
+import sidebarStatusQuery from '../graphql/queries/sidebar_status.query.graphql';
import { fetchPolicies } from '~/lib/graphql';
import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
import highlightCurrentUser from '~/behaviors/markdown/highlight_current_user';
import initUserPopovers from '~/user_popovers';
import { ALERTS_SEVERITY_LABELS, trackAlertsDetailsViewsOptions } from '../constants';
-import createIssueQuery from '../graphql/mutations/create_issue_from_alert.graphql';
+import createIssueMutation from '../graphql/mutations/create_issue_from_alert.mutation.graphql';
+import toggleSidebarStatusMutation from '../graphql/mutations/toggle_sidebar_status.mutation.graphql';
import { visitUrl, joinPaths } from '~/lib/utils/url_utility';
import Tracking from '~/tracking';
import { toggleContainerClasses } from '~/lib/utils/dom_utils';
import SystemNote from './system_notes/system_note.vue';
import AlertSidebar from './alert_sidebar.vue';
+import AlertMetrics from './alert_metrics.vue';
const containerEl = document.querySelector('.page-with-contextual-sidebar');
@@ -34,6 +37,7 @@ export default {
),
fullAlertDetailsTitle: s__('AlertManagement|Alert details'),
overviewTitle: s__('AlertManagement|Overview'),
+ metricsTitle: s__('AlertManagement|Metrics'),
reportedAt: s__('AlertManagement|Reported %{when}'),
reportedAtWithTool: s__('AlertManagement|Reported %{when} by %{tool}'),
},
@@ -51,25 +55,29 @@ export default {
TimeAgoTooltip,
AlertSidebar,
SystemNote,
+ AlertMetrics,
},
- props: {
+ inject: {
+ projectPath: {
+ default: '',
+ },
alertId: {
type: String,
- required: true,
+ default: '',
},
- projectPath: {
+ projectId: {
type: String,
- required: true,
+ default: '',
},
projectIssuesPath: {
type: String,
- required: true,
+ default: '',
},
},
apollo: {
alert: {
fetchPolicy: fetchPolicies.CACHE_AND_NETWORK,
- query,
+ query: alertQuery,
variables() {
return {
fullPath: this.projectPath,
@@ -84,15 +92,18 @@ export default {
Sentry.captureException(error);
},
},
+ sidebarStatus: {
+ query: sidebarStatusQuery,
+ },
},
data() {
return {
alert: null,
errored: false,
+ sidebarStatus: false,
isErrorDismissed: false,
createIssueError: '',
issueCreationInProgress: false,
- sidebarCollapsed: false,
sidebarErrorMessage: '',
};
},
@@ -128,10 +139,10 @@ export default {
this.sidebarErrorMessage = '';
},
toggleSidebar() {
- this.sidebarCollapsed = !this.sidebarCollapsed;
+ this.$apollo.mutate({ mutation: toggleSidebarStatusMutation });
toggleContainerClasses(containerEl, {
- 'right-sidebar-collapsed': this.sidebarCollapsed,
- 'right-sidebar-expanded': !this.sidebarCollapsed,
+ 'right-sidebar-collapsed': !this.sidebarStatus,
+ 'right-sidebar-expanded': this.sidebarStatus,
});
},
handleAlertSidebarError(errorMessage) {
@@ -143,7 +154,7 @@ export default {
this.$apollo
.mutate({
- mutation: createIssueQuery,
+ mutation: createIssueMutation,
variables: {
iid: this.alert.iid,
projectPath: this.projectPath,
@@ -169,9 +180,6 @@ export default {
const { category, action } = trackAlertsDetailsViewsOptions;
Tracking.event(category, action);
},
- alertRefresh() {
- this.$apollo.queries.alert.refetch();
- },
},
};
</script>
@@ -179,7 +187,7 @@ export default {
<template>
<div>
<gl-alert v-if="showErrorMsg" variant="danger" @dismiss="dismissError">
- {{ sidebarErrorMessage || $options.i18n.errorMsg }}
+ <p v-html="sidebarErrorMessage || $options.i18n.errorMsg"></p>
</gl-alert>
<gl-alert
v-if="createIssueError"
@@ -193,10 +201,10 @@ export default {
<div
v-if="alert"
class="alert-management-details gl-relative"
- :class="{ 'pr-sm-8': sidebarCollapsed }"
+ :class="{ 'pr-sm-8': sidebarStatus }"
>
<div
- class="gl-display-flex gl-justify-content-space-between gl-align-items-baseline gl-px-1 py-3 py-md-4 gl-border-b-1 gl-border-b-gray-200 gl-border-b-solid flex-column flex-sm-row"
+ class="gl-display-flex gl-justify-content-space-between gl-align-items-baseline gl-px-1 py-3 py-md-4 gl-border-b-1 gl-border-b-gray-100 gl-border-b-solid flex-column flex-sm-row"
>
<div
data-testid="alert-header"
@@ -324,14 +332,14 @@ export default {
</template>
</gl-table>
</gl-tab>
+ <gl-tab data-testId="metricsTab" :title="$options.i18n.metricsTitle">
+ <alert-metrics :dashboard-url="alert.metricsDashboardUrl" />
+ </gl-tab>
</gl-tabs>
<alert-sidebar
- :project-path="projectPath"
:alert="alert"
- :sidebar-collapsed="sidebarCollapsed"
- @alert-refresh="alertRefresh"
@toggle-sidebar="toggleSidebar"
- @alert-sidebar-error="handleAlertSidebarError"
+ @alert-error="handleAlertSidebarError"
/>
</div>
</div>
diff --git a/app/assets/javascripts/alert_management/components/alert_management_empty_state.vue b/app/assets/javascripts/alert_management/components/alert_management_empty_state.vue
new file mode 100644
index 00000000000..13b6a8e6653
--- /dev/null
+++ b/app/assets/javascripts/alert_management/components/alert_management_empty_state.vue
@@ -0,0 +1,90 @@
+<script>
+import { GlEmptyState, GlButton } from '@gitlab/ui';
+import { s__ } from '~/locale';
+
+export default {
+ i18n: {
+ emptyState: {
+ opsgenie: {
+ title: s__('AlertManagement|Opsgenie is enabled'),
+ info: s__(
+ 'AlertManagement|You have enabled the Opsgenie integration. Your alerts will be visible directly in Opsgenie.',
+ ),
+ buttonText: s__('AlertManagement|View alerts in Opsgenie'),
+ },
+ gitlab: {
+ title: s__('AlertManagement|Surface alerts in GitLab'),
+ info: s__(
+ 'AlertManagement|Display alerts from all your monitoring tools directly within GitLab. Streamline the investigation of your alerts and the escalation of alerts to incidents.',
+ ),
+ buttonText: s__('AlertManagement|Authorize external service'),
+ },
+ },
+ moreInformation: s__('AlertManagement|More information'),
+ },
+ components: {
+ GlEmptyState,
+ GlButton,
+ },
+ props: {
+ enableAlertManagementPath: {
+ type: String,
+ required: true,
+ },
+ userCanEnableAlertManagement: {
+ type: Boolean,
+ required: true,
+ },
+ emptyAlertSvgPath: {
+ type: String,
+ required: true,
+ },
+ opsgenieMvcEnabled: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ opsgenieMvcTargetUrl: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ },
+ computed: {
+ emptyState() {
+ return {
+ ...(this.opsgenieMvcEnabled
+ ? this.$options.i18n.emptyState.opsgenie
+ : this.$options.i18n.emptyState.gitlab),
+ link: this.opsgenieMvcEnabled ? this.opsgenieMvcTargetUrl : this.enableAlertManagementPath,
+ };
+ },
+ alertsCanBeEnabled() {
+ return this.userCanEnableAlertManagement || this.opsgenieMvcEnabled;
+ },
+ },
+};
+</script>
+<template>
+ <div>
+ <gl-empty-state :title="emptyState.title" :svg-path="emptyAlertSvgPath">
+ <template #description>
+ <div class="gl-display-block">
+ <span>{{ emptyState.info }}</span>
+ <a
+ v-if="!opsgenieMvcEnabled"
+ href="/help/user/project/operations/alert_management.html"
+ target="_blank"
+ >
+ {{ $options.i18n.moreInformation }}
+ </a>
+ </div>
+ <div v-if="alertsCanBeEnabled" class="gl-display-block center gl-pt-4">
+ <gl-button category="primary" variant="success" :href="emptyState.link">
+ {{ emptyState.buttonText }}
+ </gl-button>
+ </div>
+ </template>
+ </gl-empty-state>
+ </div>
+</template>
diff --git a/app/assets/javascripts/alert_management/components/alert_management_list.vue b/app/assets/javascripts/alert_management/components/alert_management_list.vue
deleted file mode 100644
index 37901c21f9b..00000000000
--- a/app/assets/javascripts/alert_management/components/alert_management_list.vue
+++ /dev/null
@@ -1,461 +0,0 @@
-<script>
-import {
- GlEmptyState,
- GlDeprecatedButton,
- GlLoadingIcon,
- GlTable,
- GlAlert,
- GlIcon,
- GlDropdown,
- GlDropdownItem,
- GlTabs,
- GlTab,
- GlBadge,
- GlPagination,
-} from '@gitlab/ui';
-import createFlash from '~/flash';
-import { s__ } from '~/locale';
-import { joinPaths, visitUrl } from '~/lib/utils/url_utility';
-import { fetchPolicies } from '~/lib/graphql';
-import TimeAgo from '~/vue_shared/components/time_ago_tooltip.vue';
-import getAlerts from '../graphql/queries/get_alerts.query.graphql';
-import getAlertsCountByStatus from '../graphql/queries/get_count_by_status.query.graphql';
-import {
- ALERTS_STATUS_TABS,
- ALERTS_SEVERITY_LABELS,
- DEFAULT_PAGE_SIZE,
- trackAlertListViewsOptions,
- trackAlertStatusUpdateOptions,
-} from '../constants';
-import updateAlertStatus from '../graphql/mutations/update_alert_status.graphql';
-import { convertToSnakeCase } from '~/lib/utils/text_utility';
-import Tracking from '~/tracking';
-
-const tdClass = 'table-col gl-display-flex d-md-table-cell gl-align-items-center';
-const thClass = 'gl-hover-bg-blue-50';
-const bodyTrClass =
- 'gl-border-1 gl-border-t-solid gl-border-gray-100 gl-hover-bg-blue-50 gl-hover-cursor-pointer gl-hover-border-b-solid gl-hover-border-blue-200';
-
-const initialPaginationState = {
- currentPage: 1,
- prevPageCursor: '',
- nextPageCursor: '',
- firstPageSize: DEFAULT_PAGE_SIZE,
- lastPageSize: null,
-};
-
-export default {
- i18n: {
- noAlertsMsg: s__(
- "AlertManagement|No alerts available to display. If you think you're seeing this message in error, refresh the page.",
- ),
- errorMsg: s__(
- "AlertManagement|There was an error displaying the alerts. Confirm your endpoint's configuration details to ensure alerts appear.",
- ),
- },
- fields: [
- {
- key: 'severity',
- label: s__('AlertManagement|Severity'),
- tdClass: `${tdClass} rounded-top text-capitalize`,
- thClass,
- sortable: true,
- },
- {
- key: 'startedAt',
- label: s__('AlertManagement|Start time'),
- thClass: `${thClass} js-started-at`,
- tdClass,
- sortable: true,
- },
- {
- key: 'endedAt',
- label: s__('AlertManagement|End time'),
- thClass,
- tdClass,
- sortable: true,
- },
- {
- key: 'title',
- label: s__('AlertManagement|Alert'),
- thClass: `${thClass} w-30p gl-pointer-events-none`,
- tdClass,
- sortable: false,
- },
- {
- key: 'eventCount',
- label: s__('AlertManagement|Events'),
- thClass: `${thClass} text-right gl-pr-9 w-3rem`,
- tdClass: `${tdClass} text-md-right`,
- sortable: true,
- },
- {
- key: 'assignees',
- label: s__('AlertManagement|Assignees'),
- tdClass,
- },
- {
- key: 'status',
- thClass: `${thClass} w-15p`,
- label: s__('AlertManagement|Status'),
- tdClass: `${tdClass} rounded-bottom`,
- sortable: true,
- },
- ],
- statuses: {
- TRIGGERED: s__('AlertManagement|Triggered'),
- ACKNOWLEDGED: s__('AlertManagement|Acknowledged'),
- RESOLVED: s__('AlertManagement|Resolved'),
- },
- severityLabels: ALERTS_SEVERITY_LABELS,
- statusTabs: ALERTS_STATUS_TABS,
- components: {
- GlEmptyState,
- GlLoadingIcon,
- GlTable,
- GlAlert,
- GlDeprecatedButton,
- TimeAgo,
- GlDropdown,
- GlDropdownItem,
- GlIcon,
- GlTabs,
- GlTab,
- GlBadge,
- GlPagination,
- },
- props: {
- projectPath: {
- type: String,
- required: true,
- },
- alertManagementEnabled: {
- type: Boolean,
- required: true,
- },
- enableAlertManagementPath: {
- type: String,
- required: true,
- },
- userCanEnableAlertManagement: {
- type: Boolean,
- required: true,
- },
- emptyAlertSvgPath: {
- type: String,
- required: true,
- },
- },
- apollo: {
- alerts: {
- fetchPolicy: fetchPolicies.CACHE_AND_NETWORK,
- query: getAlerts,
- variables() {
- return {
- projectPath: this.projectPath,
- statuses: this.statusFilter,
- sort: this.sort,
- firstPageSize: this.pagination.firstPageSize,
- lastPageSize: this.pagination.lastPageSize,
- prevPageCursor: this.pagination.prevPageCursor,
- nextPageCursor: this.pagination.nextPageCursor,
- };
- },
- update(data) {
- const { alertManagementAlerts: { nodes: list = [], pageInfo = {} } = {} } =
- data.project || {};
-
- return {
- list,
- pageInfo,
- };
- },
- error() {
- this.errored = true;
- },
- },
- alertsCount: {
- query: getAlertsCountByStatus,
- variables() {
- return {
- projectPath: this.projectPath,
- };
- },
- update(data) {
- return data.project?.alertManagementAlertStatusCounts;
- },
- },
- },
- data() {
- return {
- errored: false,
- isAlertDismissed: false,
- isErrorAlertDismissed: false,
- sort: 'STARTED_AT_DESC',
- statusFilter: [],
- filteredByStatus: '',
- pagination: initialPaginationState,
- sortBy: 'startedAt',
- sortDesc: true,
- sortDirection: 'desc',
- };
- },
- computed: {
- showNoAlertsMsg() {
- return (
- !this.errored && !this.loading && this.alertsCount?.all === 0 && !this.isAlertDismissed
- );
- },
- showErrorMsg() {
- return this.errored && !this.isErrorAlertDismissed;
- },
- loading() {
- return this.$apollo.queries.alerts.loading;
- },
- hasAlerts() {
- return this.alerts?.list?.length;
- },
- tbodyTrClass() {
- return !this.loading && this.hasAlerts ? bodyTrClass : '';
- },
- showPaginationControls() {
- return Boolean(this.prevPage || this.nextPage);
- },
- alertsForCurrentTab() {
- return this.alertsCount ? this.alertsCount[this.filteredByStatus.toLowerCase()] : 0;
- },
- prevPage() {
- return Math.max(this.pagination.currentPage - 1, 0);
- },
- nextPage() {
- const nextPage = this.pagination.currentPage + 1;
- return nextPage > Math.ceil(this.alertsForCurrentTab / DEFAULT_PAGE_SIZE) ? null : nextPage;
- },
- },
- mounted() {
- this.trackPageViews();
- },
- methods: {
- filterAlertsByStatus(tabIndex) {
- this.resetPagination();
- const { filters, status } = this.$options.statusTabs[tabIndex];
- this.statusFilter = filters;
- this.filteredByStatus = status;
- },
- fetchSortedData({ sortBy, sortDesc }) {
- const sortingDirection = sortDesc ? 'DESC' : 'ASC';
- const sortingColumn = convertToSnakeCase(sortBy).toUpperCase();
-
- this.resetPagination();
- this.sort = `${sortingColumn}_${sortingDirection}`;
- },
- updateAlertStatus(status, iid) {
- this.$apollo
- .mutate({
- mutation: updateAlertStatus,
- variables: {
- iid,
- status: status.toUpperCase(),
- projectPath: this.projectPath,
- },
- })
- .then(() => {
- this.trackStatusUpdate(status);
- this.$apollo.queries.alerts.refetch();
- this.$apollo.queries.alertsCount.refetch();
- this.resetPagination();
- })
- .catch(() => {
- createFlash(
- s__(
- 'AlertManagement|There was an error while updating the status of the alert. Please try again.',
- ),
- );
- });
- },
- navigateToAlertDetails({ iid }) {
- return visitUrl(joinPaths(window.location.pathname, iid, 'details'));
- },
- trackPageViews() {
- const { category, action } = trackAlertListViewsOptions;
- Tracking.event(category, action);
- },
- trackStatusUpdate(status) {
- const { category, action, label } = trackAlertStatusUpdateOptions;
- Tracking.event(category, action, { label, property: status });
- },
- getAssignees(assignees) {
- // TODO: Update to show list of assignee(s) after https://gitlab.com/gitlab-org/gitlab/-/issues/218405
- return assignees.nodes?.length > 0
- ? assignees.nodes[0]?.username
- : s__('AlertManagement|Unassigned');
- },
- handlePageChange(page) {
- const { startCursor, endCursor } = this.alerts.pageInfo;
-
- if (page > this.pagination.currentPage) {
- this.pagination = {
- ...initialPaginationState,
- nextPageCursor: endCursor,
- currentPage: page,
- };
- } else {
- this.pagination = {
- lastPageSize: DEFAULT_PAGE_SIZE,
- firstPageSize: null,
- prevPageCursor: startCursor,
- nextPageCursor: '',
- currentPage: page,
- };
- }
- },
- resetPagination() {
- this.pagination = initialPaginationState;
- },
- },
-};
-</script>
-<template>
- <div>
- <div v-if="alertManagementEnabled" class="alert-management-list">
- <gl-alert v-if="showNoAlertsMsg" @dismiss="isAlertDismissed = true">
- {{ $options.i18n.noAlertsMsg }}
- </gl-alert>
- <gl-alert v-if="showErrorMsg" variant="danger" @dismiss="isErrorAlertDismissed = true">
- {{ $options.i18n.errorMsg }}
- </gl-alert>
-
- <gl-tabs @input="filterAlertsByStatus">
- <gl-tab v-for="tab in $options.statusTabs" :key="tab.status">
- <template slot="title">
- <span>{{ tab.title }}</span>
- <gl-badge v-if="alertsCount" pill size="sm" class="gl-tab-counter-badge">
- {{ alertsCount[tab.status.toLowerCase()] }}
- </gl-badge>
- </template>
- </gl-tab>
- </gl-tabs>
-
- <h4 class="d-block d-md-none my-3">
- {{ s__('AlertManagement|Alerts') }}
- </h4>
- <gl-table
- class="alert-management-table mt-3"
- :items="alerts ? alerts.list : []"
- :fields="$options.fields"
- :show-empty="true"
- :busy="loading"
- stacked="md"
- :tbody-tr-class="tbodyTrClass"
- :no-local-sorting="true"
- :sort-direction="sortDirection"
- :sort-desc.sync="sortDesc"
- :sort-by.sync="sortBy"
- sort-icon-left
- @row-clicked="navigateToAlertDetails"
- @sort-changed="fetchSortedData"
- >
- <template #cell(severity)="{ item }">
- <div
- class="d-inline-flex align-items-center justify-content-between"
- data-testid="severityField"
- >
- <gl-icon
- class="mr-2"
- :size="12"
- :name="`severity-${item.severity.toLowerCase()}`"
- :class="`icon-${item.severity.toLowerCase()}`"
- />
- {{ $options.severityLabels[item.severity] }}
- </div>
- </template>
-
- <template #cell(startedAt)="{ item }">
- <time-ago v-if="item.startedAt" :time="item.startedAt" />
- </template>
-
- <template #cell(endedAt)="{ item }">
- <time-ago v-if="item.endedAt" :time="item.endedAt" />
- </template>
-
- <template #cell(eventCount)="{ item }">
- {{ item.eventCount }}
- </template>
-
- <template #cell(title)="{ item }">
- <div class="gl-max-w-full text-truncate">{{ item.title }}</div>
- </template>
-
- <template #cell(assignees)="{ item }">
- <div class="gl-max-w-full text-truncate" data-testid="assigneesField">
- {{ getAssignees(item.assignees) }}
- </div>
- </template>
-
- <template #cell(status)="{ item }">
- <gl-dropdown :text="$options.statuses[item.status]" class="w-100" right>
- <gl-dropdown-item
- v-for="(label, field) in $options.statuses"
- :key="field"
- @click="updateAlertStatus(label, item.iid)"
- >
- <span class="d-flex">
- <gl-icon
- class="flex-shrink-0 append-right-4"
- :class="{ invisible: label.toUpperCase() !== item.status }"
- name="mobile-issue-close"
- />
- {{ label }}
- </span>
- </gl-dropdown-item>
- </gl-dropdown>
- </template>
-
- <template #empty>
- {{ s__('AlertManagement|No alerts to display.') }}
- </template>
-
- <template #table-busy>
- <gl-loading-icon size="lg" color="dark" class="mt-3" />
- </template>
- </gl-table>
-
- <gl-pagination
- v-if="showPaginationControls"
- :value="pagination.currentPage"
- :prev-page="prevPage"
- :next-page="nextPage"
- align="center"
- class="gl-pagination prepend-top-default"
- @input="handlePageChange"
- />
- </div>
- <gl-empty-state
- v-else
- :title="s__('AlertManagement|Surface alerts in GitLab')"
- :svg-path="emptyAlertSvgPath"
- >
- <template #description>
- <div class="d-block">
- <span>{{
- s__(
- 'AlertManagement|Display alerts from all your monitoring tools directly within GitLab. Streamline the investigation of your alerts and the escalation of alerts to incidents.',
- )
- }}</span>
- <a href="/help/user/project/operations/alert_management.html" target="_blank">
- {{ s__('AlertManagement|More information') }}
- </a>
- </div>
- <div v-if="userCanEnableAlertManagement" class="d-block center pt-4">
- <gl-deprecated-button
- category="primary"
- variant="success"
- :href="enableAlertManagementPath"
- >
- {{ s__('AlertManagement|Authorize external service') }}
- </gl-deprecated-button>
- </div>
- </template>
- </gl-empty-state>
- </div>
-</template>
diff --git a/app/assets/javascripts/alert_management/components/alert_management_list_wrapper.vue b/app/assets/javascripts/alert_management/components/alert_management_list_wrapper.vue
new file mode 100644
index 00000000000..094f33fed3b
--- /dev/null
+++ b/app/assets/javascripts/alert_management/components/alert_management_list_wrapper.vue
@@ -0,0 +1,75 @@
+<script>
+import Tracking from '~/tracking';
+import { trackAlertListViewsOptions } from '../constants';
+import AlertManagementEmptyState from './alert_management_empty_state.vue';
+import AlertManagementTable from './alert_management_table.vue';
+
+export default {
+ components: {
+ AlertManagementEmptyState,
+ AlertManagementTable,
+ },
+ props: {
+ projectPath: {
+ type: String,
+ required: true,
+ },
+ alertManagementEnabled: {
+ type: Boolean,
+ required: true,
+ },
+ enableAlertManagementPath: {
+ type: String,
+ required: true,
+ },
+ populatingAlertsHelpUrl: {
+ type: String,
+ required: true,
+ },
+ userCanEnableAlertManagement: {
+ type: Boolean,
+ required: true,
+ },
+ emptyAlertSvgPath: {
+ type: String,
+ required: true,
+ },
+ opsgenieMvcEnabled: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ opsgenieMvcTargetUrl: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ },
+ mounted() {
+ this.trackPageViews();
+ },
+ methods: {
+ trackPageViews() {
+ const { category, action } = trackAlertListViewsOptions;
+ Tracking.event(category, action);
+ },
+ },
+};
+</script>
+<template>
+ <div>
+ <alert-management-table
+ v-if="alertManagementEnabled"
+ :populating-alerts-help-url="populatingAlertsHelpUrl"
+ :project-path="projectPath"
+ />
+ <alert-management-empty-state
+ v-else
+ :empty-alert-svg-path="emptyAlertSvgPath"
+ :enable-alert-management-path="enableAlertManagementPath"
+ :user-can-enable-alert-management="userCanEnableAlertManagement"
+ :opsgenie-mvc-enabled="opsgenieMvcEnabled"
+ :opsgenie-mvc-target-url="opsgenieMvcTargetUrl"
+ />
+ </div>
+</template>
diff --git a/app/assets/javascripts/alert_management/components/alert_management_table.vue b/app/assets/javascripts/alert_management/components/alert_management_table.vue
new file mode 100644
index 00000000000..7dd3d7b5dc3
--- /dev/null
+++ b/app/assets/javascripts/alert_management/components/alert_management_table.vue
@@ -0,0 +1,453 @@
+<script>
+import {
+ GlLoadingIcon,
+ GlTable,
+ GlAlert,
+ GlIcon,
+ GlLink,
+ GlTabs,
+ GlTab,
+ GlBadge,
+ GlPagination,
+ GlSearchBoxByType,
+ GlSprintf,
+} from '@gitlab/ui';
+import { __, s__ } from '~/locale';
+import { debounce, trim } from 'lodash';
+import { joinPaths, visitUrl } from '~/lib/utils/url_utility';
+import { fetchPolicies } from '~/lib/graphql';
+import TimeAgo from '~/vue_shared/components/time_ago_tooltip.vue';
+import { convertToSnakeCase } from '~/lib/utils/text_utility';
+import Tracking from '~/tracking';
+import getAlerts from '../graphql/queries/get_alerts.query.graphql';
+import getAlertsCountByStatus from '../graphql/queries/get_count_by_status.query.graphql';
+import {
+ ALERTS_STATUS_TABS,
+ ALERTS_SEVERITY_LABELS,
+ DEFAULT_PAGE_SIZE,
+ trackAlertListViewsOptions,
+ trackAlertStatusUpdateOptions,
+} from '../constants';
+import AlertStatus from './alert_status.vue';
+
+const tdClass =
+ 'table-col gl-display-flex d-md-table-cell gl-align-items-center gl-white-space-nowrap';
+const thClass = 'gl-hover-bg-blue-50';
+const bodyTrClass =
+ 'gl-border-1 gl-border-t-solid gl-border-gray-100 gl-hover-bg-blue-50 gl-hover-cursor-pointer gl-hover-border-b-solid gl-hover-border-blue-200';
+
+const initialPaginationState = {
+ currentPage: 1,
+ prevPageCursor: '',
+ nextPageCursor: '',
+ firstPageSize: DEFAULT_PAGE_SIZE,
+ lastPageSize: null,
+};
+
+const TWELVE_HOURS_IN_MS = 12 * 60 * 60 * 1000;
+
+export default {
+ i18n: {
+ noAlertsMsg: s__(
+ 'AlertManagement|No alerts available to display. See %{linkStart}enabling alert management%{linkEnd} for more information on adding alerts to the list.',
+ ),
+ errorMsg: s__(
+ "AlertManagement|There was an error displaying the alerts. Confirm your endpoint's configuration details to ensure alerts appear.",
+ ),
+ searchPlaceholder: __('Search or filter results...'),
+ },
+ fields: [
+ {
+ key: 'severity',
+ label: s__('AlertManagement|Severity'),
+ tdClass: `${tdClass} rounded-top text-capitalize`,
+ thClass: `${thClass} gl-w-eighth`,
+ sortable: true,
+ },
+ {
+ key: 'startedAt',
+ label: s__('AlertManagement|Start time'),
+ thClass: `${thClass} js-started-at w-15p`,
+ tdClass,
+ sortable: true,
+ },
+ {
+ key: 'title',
+ label: s__('AlertManagement|Alert'),
+ thClass: `gl-pointer-events-none`,
+ tdClass,
+ },
+ {
+ key: 'eventCount',
+ label: s__('AlertManagement|Events'),
+ thClass: `${thClass} text-right gl-w-12`,
+ tdClass: `${tdClass} text-md-right`,
+ sortable: true,
+ },
+ {
+ key: 'issue',
+ label: s__('AlertManagement|Issue'),
+ thClass: 'gl-w-12 gl-pointer-events-none',
+ tdClass,
+ sortable: false,
+ },
+ {
+ key: 'assignees',
+ label: s__('AlertManagement|Assignees'),
+ thClass: 'gl-w-eighth gl-pointer-events-none',
+ tdClass,
+ },
+ {
+ key: 'status',
+ thClass: `${thClass} w-15p`,
+ label: s__('AlertManagement|Status'),
+ tdClass: `${tdClass} rounded-bottom`,
+ sortable: true,
+ },
+ ],
+ severityLabels: ALERTS_SEVERITY_LABELS,
+ statusTabs: ALERTS_STATUS_TABS,
+ components: {
+ GlLoadingIcon,
+ GlTable,
+ GlAlert,
+ TimeAgo,
+ GlIcon,
+ GlLink,
+ GlTabs,
+ GlTab,
+ GlBadge,
+ GlPagination,
+ GlSearchBoxByType,
+ GlSprintf,
+ AlertStatus,
+ },
+ props: {
+ projectPath: {
+ type: String,
+ required: true,
+ },
+ populatingAlertsHelpUrl: {
+ type: String,
+ required: true,
+ },
+ },
+ apollo: {
+ alerts: {
+ fetchPolicy: fetchPolicies.CACHE_AND_NETWORK,
+ query: getAlerts,
+ variables() {
+ return {
+ searchTerm: this.searchTerm,
+ projectPath: this.projectPath,
+ statuses: this.statusFilter,
+ sort: this.sort,
+ firstPageSize: this.pagination.firstPageSize,
+ lastPageSize: this.pagination.lastPageSize,
+ prevPageCursor: this.pagination.prevPageCursor,
+ nextPageCursor: this.pagination.nextPageCursor,
+ };
+ },
+ update(data) {
+ const { alertManagementAlerts: { nodes: list = [], pageInfo = {} } = {} } =
+ data.project || {};
+ const now = new Date();
+
+ const listWithData = list.map(alert => {
+ const then = new Date(alert.startedAt);
+ const diff = now - then;
+
+ return {
+ ...alert,
+ isNew: diff < TWELVE_HOURS_IN_MS,
+ };
+ });
+
+ return {
+ list: listWithData,
+ pageInfo,
+ };
+ },
+ error() {
+ this.errored = true;
+ },
+ },
+ alertsCount: {
+ query: getAlertsCountByStatus,
+ variables() {
+ return {
+ searchTerm: this.searchTerm,
+ projectPath: this.projectPath,
+ };
+ },
+ update(data) {
+ return data.project?.alertManagementAlertStatusCounts;
+ },
+ },
+ },
+ data() {
+ return {
+ searchTerm: '',
+ errored: false,
+ errorMessage: '',
+ isAlertDismissed: false,
+ isErrorAlertDismissed: false,
+ sort: 'STARTED_AT_DESC',
+ statusFilter: [],
+ filteredByStatus: '',
+ pagination: initialPaginationState,
+ sortBy: 'startedAt',
+ sortDesc: true,
+ sortDirection: 'desc',
+ };
+ },
+ computed: {
+ showNoAlertsMsg() {
+ return (
+ !this.errored &&
+ !this.loading &&
+ this.alertsCount?.all === 0 &&
+ !this.searchTerm &&
+ !this.isAlertDismissed
+ );
+ },
+ showErrorMsg() {
+ return this.errored && !this.isErrorAlertDismissed;
+ },
+ loading() {
+ return this.$apollo.queries.alerts.loading;
+ },
+ hasAlerts() {
+ return this.alerts?.list?.length;
+ },
+ showPaginationControls() {
+ return Boolean(this.prevPage || this.nextPage);
+ },
+ alertsForCurrentTab() {
+ return this.alertsCount ? this.alertsCount[this.filteredByStatus.toLowerCase()] : 0;
+ },
+ prevPage() {
+ return Math.max(this.pagination.currentPage - 1, 0);
+ },
+ nextPage() {
+ const nextPage = this.pagination.currentPage + 1;
+ return nextPage > Math.ceil(this.alertsForCurrentTab / DEFAULT_PAGE_SIZE) ? null : nextPage;
+ },
+ },
+ mounted() {
+ this.trackPageViews();
+ },
+ methods: {
+ filterAlertsByStatus(tabIndex) {
+ this.resetPagination();
+ const { filters, status } = this.$options.statusTabs[tabIndex];
+ this.statusFilter = filters;
+ this.filteredByStatus = status;
+ },
+ fetchSortedData({ sortBy, sortDesc }) {
+ const sortingDirection = sortDesc ? 'DESC' : 'ASC';
+ const sortingColumn = convertToSnakeCase(sortBy).toUpperCase();
+
+ this.resetPagination();
+ this.sort = `${sortingColumn}_${sortingDirection}`;
+ },
+ onInputChange: debounce(function debounceSearch(input) {
+ const trimmedInput = trim(input);
+ if (trimmedInput !== this.searchTerm) {
+ this.resetPagination();
+ this.searchTerm = trimmedInput;
+ }
+ }, 500),
+ navigateToAlertDetails({ iid }) {
+ return visitUrl(joinPaths(window.location.pathname, iid, 'details'));
+ },
+ trackPageViews() {
+ const { category, action } = trackAlertListViewsOptions;
+ Tracking.event(category, action);
+ },
+ trackStatusUpdate(status) {
+ const { category, action, label } = trackAlertStatusUpdateOptions;
+ Tracking.event(category, action, { label, property: status });
+ },
+ getAssignees(assignees) {
+ // TODO: Update to show list of assignee(s) after https://gitlab.com/gitlab-org/gitlab/-/issues/218405
+ return assignees.nodes?.length > 0
+ ? assignees.nodes[0]?.username
+ : s__('AlertManagement|Unassigned');
+ },
+ getIssueLink(item) {
+ return joinPaths('/', this.projectPath, '-', 'issues', item.issueIid);
+ },
+ handlePageChange(page) {
+ const { startCursor, endCursor } = this.alerts.pageInfo;
+
+ if (page > this.pagination.currentPage) {
+ this.pagination = {
+ ...initialPaginationState,
+ nextPageCursor: endCursor,
+ currentPage: page,
+ };
+ } else {
+ this.pagination = {
+ lastPageSize: DEFAULT_PAGE_SIZE,
+ firstPageSize: null,
+ prevPageCursor: startCursor,
+ nextPageCursor: '',
+ currentPage: page,
+ };
+ }
+ },
+ resetPagination() {
+ this.pagination = initialPaginationState;
+ },
+ tbodyTrClass(item) {
+ return {
+ [bodyTrClass]: !this.loading && this.hasAlerts,
+ 'new-alert': item?.isNew,
+ };
+ },
+ handleAlertError(errorMessage) {
+ this.errored = true;
+ this.errorMessage = errorMessage;
+ },
+ dismissError() {
+ this.isErrorAlertDismissed = true;
+ this.errorMessage = '';
+ },
+ },
+};
+</script>
+<template>
+ <div>
+ <div class="alert-management-list">
+ <gl-alert v-if="showNoAlertsMsg" @dismiss="isAlertDismissed = true">
+ <gl-sprintf :message="$options.i18n.noAlertsMsg">
+ <template #link="{ content }">
+ <gl-link
+ class="gl-display-inline-block"
+ :href="populatingAlertsHelpUrl"
+ target="_blank"
+ >
+ {{ content }}
+ </gl-link>
+ </template>
+ </gl-sprintf>
+ </gl-alert>
+ <gl-alert
+ v-if="showErrorMsg"
+ variant="danger"
+ data-testid="alert-error"
+ @dismiss="dismissError"
+ >
+ <p v-html="errorMessage || $options.i18n.errorMsg"></p>
+ </gl-alert>
+
+ <gl-tabs content-class="gl-p-0" @input="filterAlertsByStatus">
+ <gl-tab v-for="tab in $options.statusTabs" :key="tab.status">
+ <template slot="title">
+ <span>{{ tab.title }}</span>
+ <gl-badge v-if="alertsCount" pill size="sm" class="gl-tab-counter-badge">
+ {{ alertsCount[tab.status.toLowerCase()] }}
+ </gl-badge>
+ </template>
+ </gl-tab>
+ </gl-tabs>
+
+ <div class="gl-bg-gray-10 gl-p-5 gl-border-b-solid gl-border-b-1 gl-border-gray-100">
+ <gl-search-box-by-type
+ class="gl-bg-white"
+ :placeholder="$options.i18n.searchPlaceholder"
+ @input="onInputChange"
+ />
+ </div>
+
+ <h4 class="d-block d-md-none my-3">
+ {{ s__('AlertManagement|Alerts') }}
+ </h4>
+ <gl-table
+ class="alert-management-table"
+ :items="alerts ? alerts.list : []"
+ :fields="$options.fields"
+ :show-empty="true"
+ :busy="loading"
+ stacked="md"
+ :tbody-tr-class="tbodyTrClass"
+ :no-local-sorting="true"
+ :sort-direction="sortDirection"
+ :sort-desc.sync="sortDesc"
+ :sort-by.sync="sortBy"
+ sort-icon-left
+ fixed
+ @row-clicked="navigateToAlertDetails"
+ @sort-changed="fetchSortedData"
+ >
+ <template #cell(severity)="{ item }">
+ <div
+ class="d-inline-flex align-items-center justify-content-between"
+ data-testid="severityField"
+ >
+ <gl-icon
+ class="mr-2"
+ :size="12"
+ :name="`severity-${item.severity.toLowerCase()}`"
+ :class="`icon-${item.severity.toLowerCase()}`"
+ />
+ {{ $options.severityLabels[item.severity] }}
+ </div>
+ </template>
+
+ <template #cell(startedAt)="{ item }">
+ <time-ago v-if="item.startedAt" :time="item.startedAt" />
+ </template>
+
+ <template #cell(eventCount)="{ item }">
+ {{ item.eventCount }}
+ </template>
+
+ <template #cell(title)="{ item }">
+ <div class="gl-max-w-full text-truncate" :title="item.title">{{ item.title }}</div>
+ </template>
+
+ <template #cell(issue)="{ item }">
+ <gl-link v-if="item.issueIid" data-testid="issueField" :href="getIssueLink(item)">
+ #{{ item.issueIid }}
+ </gl-link>
+ <div v-else data-testid="issueField">{{ s__('AlertManagement|None') }}</div>
+ </template>
+
+ <template #cell(assignees)="{ item }">
+ <div class="gl-max-w-full text-truncate" data-testid="assigneesField">
+ {{ getAssignees(item.assignees) }}
+ </div>
+ </template>
+
+ <template #cell(status)="{ item }">
+ <alert-status
+ :alert="item"
+ :project-path="projectPath"
+ :is-sidebar="false"
+ @alert-error="handleAlertError"
+ />
+ </template>
+
+ <template #empty>
+ {{ s__('AlertManagement|No alerts to display.') }}
+ </template>
+
+ <template #table-busy>
+ <gl-loading-icon size="lg" color="dark" class="mt-3" />
+ </template>
+ </gl-table>
+
+ <gl-pagination
+ v-if="showPaginationControls"
+ :value="pagination.currentPage"
+ :prev-page="prevPage"
+ :next-page="nextPage"
+ align="center"
+ class="gl-pagination gl-mt-3"
+ @input="handlePageChange"
+ />
+ </div>
+ </div>
+</template>
diff --git a/app/assets/javascripts/alert_management/components/alert_metrics.vue b/app/assets/javascripts/alert_management/components/alert_metrics.vue
new file mode 100644
index 00000000000..c5b40edc672
--- /dev/null
+++ b/app/assets/javascripts/alert_management/components/alert_metrics.vue
@@ -0,0 +1,56 @@
+<script>
+import Vue from 'vue';
+import Vuex from 'vuex';
+import * as Sentry from '@sentry/browser';
+
+Vue.use(Vuex);
+
+export default {
+ props: {
+ dashboardUrl: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ },
+ data() {
+ return {
+ metricEmbedComponent: null,
+ namespace: 'alertMetrics',
+ };
+ },
+ mounted() {
+ if (this.dashboardUrl) {
+ Promise.all([
+ import('~/monitoring/components/embeds/metric_embed.vue'),
+ import('~/monitoring/stores'),
+ ])
+ .then(([{ default: MetricEmbed }, { monitoringDashboard }]) => {
+ this.$store = new Vuex.Store({
+ modules: {
+ [this.namespace]: monitoringDashboard,
+ },
+ });
+ this.metricEmbedComponent = MetricEmbed;
+ })
+ .catch(e => Sentry.captureException(e));
+ }
+ },
+};
+</script>
+
+<template>
+ <div class="gl-py-3">
+ <div v-if="dashboardUrl" ref="metricsChart">
+ <component
+ :is="metricEmbedComponent"
+ v-if="metricEmbedComponent"
+ :dashboard-url="dashboardUrl"
+ :namespace="namespace"
+ />
+ </div>
+ <div v-else ref="emptyState">
+ {{ s__("AlertManagement|Metrics weren't available in the alerts payload.") }}
+ </div>
+ </div>
+</template>
diff --git a/app/assets/javascripts/alert_management/components/alert_sidebar.vue b/app/assets/javascripts/alert_management/components/alert_sidebar.vue
index dcd22e2062e..64e4089c85a 100644
--- a/app/assets/javascripts/alert_management/components/alert_sidebar.vue
+++ b/app/assets/javascripts/alert_management/components/alert_sidebar.vue
@@ -4,6 +4,8 @@ import SidebarTodo from './sidebar/sidebar_todo.vue';
import SidebarStatus from './sidebar/sidebar_status.vue';
import SidebarAssignees from './sidebar/sidebar_assignees.vue';
+import sidebarStatusQuery from '../graphql/queries/sidebar_status.query.graphql';
+
export default {
components: {
SidebarAssignees,
@@ -11,23 +13,34 @@ export default {
SidebarTodo,
SidebarStatus,
},
- props: {
- sidebarCollapsed: {
- type: Boolean,
- required: true,
- },
+ inject: {
projectPath: {
+ default: '',
+ },
+ projectId: {
type: String,
- required: true,
+ default: '',
},
+ },
+ props: {
alert: {
type: Object,
required: true,
},
},
+ apollo: {
+ sidebarStatus: {
+ query: sidebarStatusQuery,
+ },
+ },
+ data() {
+ return {
+ sidebarStatus: false,
+ };
+ },
computed: {
sidebarCollapsedClass() {
- return this.sidebarCollapsed ? 'right-sidebar-collapsed' : 'right-sidebar-expanded';
+ return this.sidebarStatus ? 'right-sidebar-collapsed' : 'right-sidebar-expanded';
},
},
};
@@ -37,23 +50,32 @@ export default {
<aside :class="sidebarCollapsedClass" class="right-sidebar alert-sidebar">
<div class="issuable-sidebar js-issuable-update">
<sidebar-header
- :sidebar-collapsed="sidebarCollapsed"
+ :sidebar-collapsed="sidebarStatus"
+ :project-path="projectPath"
+ :alert="alert"
@toggle-sidebar="$emit('toggle-sidebar')"
+ @alert-error="$emit('alert-error', $event)"
+ />
+ <sidebar-todo
+ v-if="sidebarStatus"
+ :project-path="projectPath"
+ :alert="alert"
+ :sidebar-collapsed="sidebarStatus"
+ @alert-error="$emit('alert-error', $event)"
/>
- <sidebar-todo v-if="sidebarCollapsed" :sidebar-collapsed="sidebarCollapsed" />
<sidebar-status
:project-path="projectPath"
:alert="alert"
@toggle-sidebar="$emit('toggle-sidebar')"
- @alert-sidebar-error="$emit('alert-sidebar-error', $event)"
+ @alert-error="$emit('alert-error', $event)"
/>
<sidebar-assignees
:project-path="projectPath"
+ :project-id="projectId"
:alert="alert"
- :sidebar-collapsed="sidebarCollapsed"
- @alert-refresh="$emit('alert-refresh')"
+ :sidebar-collapsed="sidebarStatus"
@toggle-sidebar="$emit('toggle-sidebar')"
- @alert-sidebar-error="$emit('alert-sidebar-error', $event)"
+ @alert-error="$emit('alert-error', $event)"
/>
<div class="block"></div>
</div>
diff --git a/app/assets/javascripts/alert_management/components/alert_status.vue b/app/assets/javascripts/alert_management/components/alert_status.vue
new file mode 100644
index 00000000000..9b726fe2944
--- /dev/null
+++ b/app/assets/javascripts/alert_management/components/alert_status.vue
@@ -0,0 +1,129 @@
+<script>
+import { GlDropdown, GlDropdownItem, GlButton } from '@gitlab/ui';
+import { s__ } from '~/locale';
+import Tracking from '~/tracking';
+import { trackAlertStatusUpdateOptions } from '../constants';
+import updateAlertStatus from '../graphql/mutations/update_alert_status.mutation.graphql';
+
+export default {
+ i18n: {
+ UPDATE_ALERT_STATUS_ERROR: s__(
+ 'AlertManagement|There was an error while updating the status of the alert.',
+ ),
+ UPDATE_ALERT_STATUS_INSTRUCTION: s__('AlertManagement|Please try again.'),
+ },
+ statuses: {
+ TRIGGERED: s__('AlertManagement|Triggered'),
+ ACKNOWLEDGED: s__('AlertManagement|Acknowledged'),
+ RESOLVED: s__('AlertManagement|Resolved'),
+ },
+ components: {
+ GlDropdown,
+ GlDropdownItem,
+ GlButton,
+ },
+ props: {
+ projectPath: {
+ type: String,
+ required: true,
+ },
+ alert: {
+ type: Object,
+ required: true,
+ },
+ isDropdownShowing: {
+ type: Boolean,
+ required: false,
+ },
+ isSidebar: {
+ type: Boolean,
+ required: true,
+ },
+ },
+ computed: {
+ dropdownClass() {
+ // eslint-disable-next-line no-nested-ternary
+ return this.isSidebar ? (this.isDropdownShowing ? 'show' : 'gl-display-none') : '';
+ },
+ },
+ methods: {
+ updateAlertStatus(status) {
+ this.$emit('handle-updating', true);
+ this.$apollo
+ .mutate({
+ mutation: updateAlertStatus,
+ variables: {
+ iid: this.alert.iid,
+ status: status.toUpperCase(),
+ projectPath: this.projectPath,
+ },
+ })
+ .then(resp => {
+ this.trackStatusUpdate(status);
+ this.$emit('hide-dropdown');
+
+ const errors = resp.data?.updateAlertStatus?.errors || [];
+
+ if (errors[0]) {
+ this.$emit(
+ 'alert-error',
+ `${this.$options.i18n.UPDATE_ALERT_STATUS_ERROR} ${errors[0]}`,
+ );
+ }
+ })
+ .catch(() => {
+ this.$emit(
+ 'alert-error',
+ `${this.$options.i18n.UPDATE_ALERT_STATUS_ERROR} ${this.$options.i18n.UPDATE_ALERT_STATUS_INSTRUCTION}`,
+ );
+ })
+ .finally(() => {
+ this.$emit('handle-updating', false);
+ });
+ },
+ trackStatusUpdate(status) {
+ const { category, action, label } = trackAlertStatusUpdateOptions;
+ Tracking.event(category, action, { label, property: status });
+ },
+ },
+};
+</script>
+
+<template>
+ <div class="dropdown dropdown-menu-selectable" :class="dropdownClass">
+ <gl-dropdown
+ ref="dropdown"
+ right
+ :text="$options.statuses[alert.status]"
+ class="w-100"
+ toggle-class="dropdown-menu-toggle"
+ variant="outline-default"
+ @keydown.esc.native="$emit('hide-dropdown')"
+ @hide="$emit('hide-dropdown')"
+ >
+ <div v-if="isSidebar" class="dropdown-title text-center">
+ <span class="alert-title">{{ s__('AlertManagement|Assign status') }}</span>
+ <gl-button
+ :aria-label="__('Close')"
+ variant="link"
+ class="dropdown-title-button dropdown-menu-close"
+ icon="close"
+ @click="$emit('hide-dropdown')"
+ />
+ </div>
+ <div class="dropdown-content dropdown-body">
+ <gl-dropdown-item
+ v-for="(label, field) in $options.statuses"
+ :key="field"
+ data-testid="statusDropdownItem"
+ class="gl-vertical-align-middle"
+ :active="label.toUpperCase() === alert.status"
+ :active-class="'is-active'"
+ @click="updateAlertStatus(label)"
+ >
+ {{ label }}
+ </gl-dropdown-item>
+ </div>
+ </gl-dropdown>
+ </div>
+</template>
diff --git a/app/assets/javascripts/alert_management/components/sidebar/sidebar_assignees.vue b/app/assets/javascripts/alert_management/components/sidebar/sidebar_assignees.vue
index 453a3901665..cb32a5ffd4f 100644
--- a/app/assets/javascripts/alert_management/components/sidebar/sidebar_assignees.vue
+++ b/app/assets/javascripts/alert_management/components/sidebar/sidebar_assignees.vue
@@ -11,20 +11,26 @@ import {
GlSprintf,
} from '@gitlab/ui';
import axios from '~/lib/utils/axios_utils';
-import { s__ } from '~/locale';
-import alertSetAssignees from '../../graphql/mutations/alert_set_assignees.graphql';
+import { s__, __ } from '~/locale';
+import alertSetAssignees from '../../graphql/mutations/alert_set_assignees.mutation.graphql';
import SidebarAssignee from './sidebar_assignee.vue';
import { debounce } from 'lodash';
const DATA_REFETCH_DELAY = 250;
export default {
- FETCH_USERS_ERROR: s__(
- 'AlertManagement|There was an error while updating the assignee(s) list. Please try again.',
- ),
- UPDATE_ALERT_ASSIGNEES_ERROR: s__(
- 'AlertManagement|There was an error while updating the assignee(s) of the alert. Please try again.',
- ),
+ i18n: {
+ FETCH_USERS_ERROR: s__(
+ 'AlertManagement|There was an error while updating the assignee(s) list. Please try again.',
+ ),
+ UPDATE_ALERT_ASSIGNEES_ERROR: s__(
+ 'AlertManagement|There was an error while updating the assignee(s) of the alert. Please try again.',
+ ),
+ UPDATE_ALERT_ASSIGNEES_GRAPHQL_ERROR: s__(
+ 'AlertManagement|This assignee cannot be assigned to this alert.',
+ ),
+ ASSIGNEES_BLOCK: s__('AlertManagement|Alert assignee(s): %{assignees}'),
+ },
components: {
GlIcon,
GlDropdown,
@@ -38,6 +44,10 @@ export default {
SidebarAssignee,
},
props: {
+ projectId: {
+ type: String,
+ required: true,
+ },
projectPath: {
type: String,
required: true,
@@ -73,7 +83,7 @@ export default {
return this.alert?.assignees?.nodes[0]?.username;
},
assignedUser() {
- return this.userName || s__('AlertManagement|None');
+ return this.userName || __('None');
},
sortedUsers() {
return this.users
@@ -122,20 +132,20 @@ export default {
updateAssigneesDropdown() {
this.isDropdownSearching = true;
return axios
- .get(this.buildUrl(gon.relative_url_root, '/autocomplete/users.json'), {
+ .get(this.buildUrl(gon.relative_url_root, '/-/autocomplete/users.json'), {
params: {
search: this.search,
per_page: 20,
active: true,
current_user: true,
- project_id: gon?.current_project_id,
+ project_id: this.projectId,
},
})
.then(({ data }) => {
this.users = data;
})
.catch(() => {
- this.$emit('alert-sidebar-error', this.$options.FETCH_USERS_ERROR);
+ this.$emit('alert-error', this.$options.i18n.FETCH_USERS_ERROR);
})
.finally(() => {
this.isDropdownSearching = false;
@@ -152,12 +162,18 @@ export default {
projectPath: this.projectPath,
},
})
- .then(() => {
+ .then(({ data: { alertSetAssignees: { errors } = [] } = {} } = {}) => {
this.hideDropdown();
- this.$emit('alert-refresh');
+
+ if (errors[0]) {
+ this.$emit(
+ 'alert-error',
+ `${this.$options.i18n.UPDATE_ALERT_ASSIGNEES_GRAPHQL_ERROR} ${errors[0]}.`,
+ );
+ }
})
.catch(() => {
- this.$emit('alert-sidebar-error', this.$options.UPDATE_ALERT_ASSIGNEES_ERROR);
+ this.$emit('alert-error', this.$options.i18n.UPDATE_ALERT_ASSIGNEES_ERROR);
})
.finally(() => {
this.isUpdating = false;
@@ -174,7 +190,7 @@ export default {
<gl-loading-icon v-if="isUpdating" />
</div>
<gl-tooltip :target="() => $refs.status" boundary="viewport" placement="left">
- <gl-sprintf :message="s__('AlertManagement|Alert assignee(s): %{assignees}')">
+ <gl-sprintf :message="$options.i18n.ASSIGNEES_BLOCK">
<template #assignees>
{{ assignedUser }}
</template>
@@ -183,7 +199,7 @@ export default {
<div class="hide-collapsed">
<p class="title gl-display-flex gl-justify-content-space-between">
- {{ s__('AlertManagement|Assignee') }}
+ {{ __('Assignee') }}
<a
v-if="isEditable"
ref="editButton"
@@ -192,7 +208,7 @@ export default {
@click="toggleFormDropdown"
@keydown.esc="hideDropdown"
>
- {{ s__('AlertManagement|Edit') }}
+ {{ __('Edit') }}
</a>
</p>
@@ -207,7 +223,7 @@ export default {
@hide="hideDropdown"
>
<div class="dropdown-title">
- <span class="alert-title">{{ s__('AlertManagement|Assign To') }}</span>
+ <span class="alert-title">{{ __('Assign To') }}</span>
<gl-button
:aria-label="__('Close')"
variant="link"
@@ -232,12 +248,12 @@ export default {
active-class="is-active"
@click="updateAlertAssignees('')"
>
- {{ s__('AlertManagement|Unassigned') }}
+ {{ __('Unassigned') }}
</gl-dropdown-item>
<gl-dropdown-divider />
<gl-dropdown-header class="mt-0">
- {{ s__('AlertManagement|Assignee') }}
+ {{ __('Assignee') }}
</gl-dropdown-header>
<sidebar-assignee
v-for="user in sortedUsers"
@@ -248,7 +264,7 @@ export default {
/>
</template>
<gl-dropdown-item v-else-if="userListEmpty">
- {{ s__('AlertManagement|No Matching Results') }}
+ {{ __('No Matching Results') }}
</gl-dropdown-item>
<gl-loading-icon v-else />
</div>
@@ -261,7 +277,7 @@ export default {
assignedUser
}}</span>
<span v-else class="gl-display-flex gl-align-items-center">
- {{ s__('AlertManagement|None -') }}
+ {{ __('None') }} -
<gl-button
class="gl-pl-2"
href="#"
@@ -269,7 +285,7 @@ export default {
data-testid="unassigned-users"
@click="updateAlertAssignees(currentUser)"
>
- {{ s__('AlertManagement| assign yourself') }}
+ {{ __('assign yourself') }}
</gl-button>
</span>
</p>
diff --git a/app/assets/javascripts/alert_management/components/sidebar/sidebar_header.vue b/app/assets/javascripts/alert_management/components/sidebar/sidebar_header.vue
index 047793d8cee..fd40b5d9f65 100644
--- a/app/assets/javascripts/alert_management/components/sidebar/sidebar_header.vue
+++ b/app/assets/javascripts/alert_management/components/sidebar/sidebar_header.vue
@@ -8,6 +8,14 @@ export default {
SidebarTodo,
},
props: {
+ alert: {
+ type: Object,
+ required: true,
+ },
+ projectPath: {
+ type: String,
+ required: true,
+ },
sidebarCollapsed: {
type: Boolean,
required: true,
@@ -17,18 +25,17 @@ export default {
</script>
<template>
- <div class="block d-flex justify-content-between">
+ <div class="block gl-display-flex gl-justify-content-space-between">
<span class="issuable-header-text hide-collapsed">
- {{ __('Quick actions') }}
+ {{ __('To Do') }}
</span>
- <toggle-sidebar
- :collapsed="sidebarCollapsed"
- css-classes="ml-auto"
- @toggle="$emit('toggle-sidebar')"
+ <sidebar-todo
+ v-if="!sidebarCollapsed"
+ :project-path="projectPath"
+ :alert="alert"
+ :sidebar-collapsed="sidebarCollapsed"
+ @alert-error="$emit('alert-error', $event)"
/>
- <!-- TODO: Implement after or as part of: https://gitlab.com/gitlab-org/gitlab/-/issues/215946 -->
- <template v-if="false">
- <sidebar-todo v-if="!sidebarCollapsed" :sidebar-collapsed="sidebarCollapsed" />
- </template>
+ <toggle-sidebar :collapsed="sidebarCollapsed" @toggle="$emit('toggle-sidebar')" />
</div>
</template>
diff --git a/app/assets/javascripts/alert_management/components/sidebar/sidebar_status.vue b/app/assets/javascripts/alert_management/components/sidebar/sidebar_status.vue
index 89dbbedd9c1..44a81aba828 100644
--- a/app/assets/javascripts/alert_management/components/sidebar/sidebar_status.vue
+++ b/app/assets/javascripts/alert_management/components/sidebar/sidebar_status.vue
@@ -1,17 +1,7 @@
<script>
-import {
- GlIcon,
- GlDropdown,
- GlDropdownItem,
- GlLoadingIcon,
- GlTooltip,
- GlButton,
- GlSprintf,
-} from '@gitlab/ui';
+import { GlIcon, GlLoadingIcon, GlTooltip, GlSprintf } from '@gitlab/ui';
import { s__ } from '~/locale';
-import Tracking from '~/tracking';
-import { trackAlertStatusUpdateOptions } from '../../constants';
-import updateAlertStatus from '../../graphql/mutations/update_alert_status.graphql';
+import AlertStatus from '../alert_status.vue';
export default {
statuses: {
@@ -21,12 +11,10 @@ export default {
},
components: {
GlIcon,
- GlDropdown,
- GlDropdownItem,
GlLoadingIcon,
GlTooltip,
- GlButton,
GlSprintf,
+ AlertStatus,
},
props: {
projectPath: {
@@ -60,44 +48,13 @@ export default {
},
toggleFormDropdown() {
this.isDropdownShowing = !this.isDropdownShowing;
- const { dropdown } = this.$refs.dropdown.$refs;
+ const { dropdown } = this.$children[2].$refs.dropdown.$refs;
if (dropdown && this.isDropdownShowing) {
dropdown.show();
}
},
- isSelected(status) {
- return this.alert.status === status;
- },
- updateAlertStatus(status) {
- this.isUpdating = true;
- this.$apollo
- .mutate({
- mutation: updateAlertStatus,
- variables: {
- iid: this.alert.iid,
- status: status.toUpperCase(),
- projectPath: this.projectPath,
- },
- })
- .then(() => {
- this.trackStatusUpdate(status);
- this.hideDropdown();
- })
- .catch(() => {
- this.$emit(
- 'alert-sidebar-error',
- s__(
- 'AlertManagement|There was an error while updating the status of the alert. Please try again.',
- ),
- );
- })
- .finally(() => {
- this.isUpdating = false;
- });
- },
- trackStatusUpdate(status) {
- const { category, action, label } = trackAlertStatusUpdateOptions;
- Tracking.event(category, action, { label, property: status });
+ handleUpdating(updating) {
+ this.isUpdating = updating;
},
},
};
@@ -132,41 +89,15 @@ export default {
</a>
</p>
- <div class="dropdown dropdown-menu-selectable" :class="dropdownClass">
- <gl-dropdown
- ref="dropdown"
- :text="$options.statuses[alert.status]"
- class="w-100"
- toggle-class="dropdown-menu-toggle"
- variant="outline-default"
- @keydown.esc.native="hideDropdown"
- @hide="hideDropdown"
- >
- <div class="dropdown-title">
- <span class="alert-title">{{ s__('AlertManagement|Assign status') }}</span>
- <gl-button
- :aria-label="__('Close')"
- variant="link"
- class="dropdown-title-button dropdown-menu-close"
- icon="close"
- @click="hideDropdown"
- />
- </div>
- <div class="dropdown-content dropdown-body">
- <gl-dropdown-item
- v-for="(label, field) in $options.statuses"
- :key="field"
- data-testid="statusDropdownItem"
- class="gl-vertical-align-middle"
- :active="label.toUpperCase() === alert.status"
- :active-class="'is-active'"
- @click="updateAlertStatus(label)"
- >
- {{ label }}
- </gl-dropdown-item>
- </div>
- </gl-dropdown>
- </div>
+ <alert-status
+ :alert="alert"
+ :project-path="projectPath"
+ :is-dropdown-showing="isDropdownShowing"
+ :is-sidebar="true"
+ @alert-error="$emit('alert-error', $event)"
+ @hide-dropdown="hideDropdown"
+ @handle-updating="handleUpdating"
+ />
<gl-loading-icon v-if="isUpdating" :inline="true" />
<p
diff --git a/app/assets/javascripts/alert_management/components/sidebar/sidebar_todo.vue b/app/assets/javascripts/alert_management/components/sidebar/sidebar_todo.vue
index 87090165f82..7d3135ad50d 100644
--- a/app/assets/javascripts/alert_management/components/sidebar/sidebar_todo.vue
+++ b/app/assets/javascripts/alert_management/components/sidebar/sidebar_todo.vue
@@ -1,29 +1,123 @@
<script>
+import { s__ } from '~/locale';
import Todo from '~/sidebar/components/todo_toggle/todo.vue';
+import axios from '~/lib/utils/axios_utils';
+import createAlertTodo from '../../graphql/mutations/alert_todo_create.graphql';
export default {
+ i18n: {
+ UPDATE_ALERT_TODO_ERROR: s__(
+ 'AlertManagement|There was an error while updating the To Do of the alert.',
+ ),
+ },
components: {
Todo,
},
props: {
+ alert: {
+ type: Object,
+ required: true,
+ },
+ projectPath: {
+ type: String,
+ required: true,
+ },
sidebarCollapsed: {
type: Boolean,
required: true,
},
},
+ data() {
+ return {
+ isUpdating: false,
+ isTodo: false,
+ todo: '',
+ };
+ },
+ computed: {
+ alertID() {
+ return parseInt(this.alert.iid, 10);
+ },
+ },
+ methods: {
+ updateToDoCount(add) {
+ const oldCount = parseInt(document.querySelector('.todos-count').innerText, 10);
+ const count = add ? oldCount + 1 : oldCount - 1;
+ const headerTodoEvent = new CustomEvent('todo:toggle', {
+ detail: {
+ count,
+ },
+ });
+
+ return document.dispatchEvent(headerTodoEvent);
+ },
+ toggleTodo() {
+ if (this.todo) {
+ return this.markAsDone();
+ }
+
+ this.isUpdating = true;
+ return this.$apollo
+ .mutate({
+ mutation: createAlertTodo,
+ variables: {
+ iid: this.alert.iid,
+ projectPath: this.projectPath,
+ },
+ })
+ .then(({ data: { alertTodoCreate: { todo = {}, errors = [] } } = {} } = {}) => {
+ if (errors[0]) {
+ return this.$emit(
+ 'alert-error',
+ `${this.$options.i18n.UPDATE_ALERT_TODO_ERROR} ${errors[0]}.`,
+ );
+ }
+
+ this.todo = todo.id;
+ return this.updateToDoCount(true);
+ })
+ .catch(() => {
+ this.$emit(
+ 'alert-error',
+ `${this.$options.i18n.UPDATE_ALERT_TODO_ERROR} ${s__(
+ 'AlertManagement|Please try again.',
+ )}`,
+ );
+ })
+ .finally(() => {
+ this.isUpdating = false;
+ });
+ },
+ markAsDone() {
+ this.isUpdating = true;
+
+ return axios
+ .delete(`/dashboard/todos/${this.todo.split('/').pop()}`)
+ .then(() => {
+ this.todo = '';
+ return this.updateToDoCount(false);
+ })
+ .catch(() => {
+ this.$emit('alert-error', this.$options.i18n.UPDATE_ALERT_TODO_ERROR);
+ })
+ .finally(() => {
+ this.isUpdating = false;
+ });
+ },
+ },
};
</script>
-<!-- TODO: Implement after or as part of: https://gitlab.com/gitlab-org/gitlab/-/issues/215946 -->
<template>
- <div v-if="false" :class="{ 'block todo': sidebarCollapsed }">
+ <div :class="{ 'block todo': sidebarCollapsed, 'gl-ml-auto': !sidebarCollapsed }">
<todo
+ data-testid="alert-todo-button"
:collapsed="sidebarCollapsed"
- :issuable-id="1"
- :is-todo="false"
- :is-action-active="false"
+ :issuable-id="alertID"
+ :is-todo="todo !== ''"
+ :is-action-active="isUpdating"
issuable-type="alert"
- @toggleTodo="() => {}"
+ @toggleTodo="toggleTodo"
/>
</div>
</template>
diff --git a/app/assets/javascripts/alert_management/components/system_notes/system_note.vue b/app/assets/javascripts/alert_management/components/system_notes/system_note.vue
index 9042d51aecf..39717ab609f 100644
--- a/app/assets/javascripts/alert_management/components/system_notes/system_note.vue
+++ b/app/assets/javascripts/alert_management/components/system_notes/system_note.vue
@@ -24,7 +24,7 @@ export default {
return { ...author, id: id?.split('/').pop() };
},
iconHtml() {
- return spriteIcon('user');
+ return spriteIcon(this.note?.systemNoteIconName);
},
},
};
diff --git a/app/assets/javascripts/alert_management/details.js b/app/assets/javascripts/alert_management/details.js
index aa8a839ea3f..2820bcb9665 100644
--- a/app/assets/javascripts/alert_management/details.js
+++ b/app/assets/javascripts/alert_management/details.js
@@ -3,45 +3,59 @@ import VueApollo from 'vue-apollo';
import createDefaultClient from '~/lib/graphql';
import { defaultDataIdFromObject } from 'apollo-cache-inmemory';
import AlertDetails from './components/alert_details.vue';
+import sidebarStatusQuery from './graphql/queries/sidebar_status.query.graphql';
Vue.use(VueApollo);
export default selector => {
const domEl = document.querySelector(selector);
- const { alertId, projectPath, projectIssuesPath } = domEl.dataset;
+ const { alertId, projectPath, projectIssuesPath, projectId } = domEl.dataset;
+
+ const resolvers = {
+ Mutation: {
+ toggleSidebarStatus: (_, __, { cache }) => {
+ const data = cache.readQuery({ query: sidebarStatusQuery });
+ data.sidebarStatus = !data.sidebarStatus;
+ cache.writeQuery({ query: sidebarStatusQuery, data });
+ },
+ },
+ };
const apolloProvider = new VueApollo({
- defaultClient: createDefaultClient(
- {},
- {
- cacheConfig: {
- dataIdFromObject: object => {
- // eslint-disable-next-line no-underscore-dangle
- if (object.__typename === 'AlertManagementAlert') {
- return object.iid;
- }
- return defaultDataIdFromObject(object);
- },
+ defaultClient: createDefaultClient(resolvers, {
+ cacheConfig: {
+ dataIdFromObject: object => {
+ // eslint-disable-next-line no-underscore-dangle
+ if (object.__typename === 'AlertManagementAlert') {
+ return object.iid;
+ }
+ return defaultDataIdFromObject(object);
},
},
- ),
+ }),
+ });
+
+ apolloProvider.clients.defaultClient.cache.writeData({
+ data: {
+ sidebarStatus: false,
+ },
});
// eslint-disable-next-line no-new
new Vue({
el: selector,
+ provide: {
+ projectPath,
+ alertId,
+ projectIssuesPath,
+ projectId,
+ },
apolloProvider,
components: {
AlertDetails,
},
render(createElement) {
- return createElement('alert-details', {
- props: {
- alertId,
- projectPath,
- projectIssuesPath,
- },
- });
+ return createElement('alert-details', {});
},
});
};
diff --git a/app/assets/javascripts/alert_management/graphql/fragments/alert_note.fragment.graphql b/app/assets/javascripts/alert_management/graphql/fragments/alert_note.fragment.graphql
index c72300e9757..74b425717a0 100644
--- a/app/assets/javascripts/alert_management/graphql/fragments/alert_note.fragment.graphql
+++ b/app/assets/javascripts/alert_management/graphql/fragments/alert_note.fragment.graphql
@@ -1,16 +1,17 @@
#import "~/graphql_shared/fragments/author.fragment.graphql"
fragment AlertNote on Note {
+ id
+ author {
id
- author {
- id
- state
- ...Author
- }
- body
- bodyHtml
- createdAt
- discussion {
- id
- }
+ state
+ ...Author
+ }
+ body
+ bodyHtml
+ createdAt
+ discussion {
+ id
+ }
+ systemNoteIconName
}
diff --git a/app/assets/javascripts/alert_management/graphql/fragments/detail_item.fragment.graphql b/app/assets/javascripts/alert_management/graphql/fragments/detail_item.fragment.graphql
index cbe7e169be3..18fab429164 100644
--- a/app/assets/javascripts/alert_management/graphql/fragments/detail_item.fragment.graphql
+++ b/app/assets/javascripts/alert_management/graphql/fragments/detail_item.fragment.graphql
@@ -5,9 +5,11 @@ fragment AlertDetailItem on AlertManagementAlert {
...AlertListItem
createdAt
monitoringTool
+ metricsDashboardUrl
service
description
updatedAt
+ endedAt
details
notes {
nodes {
diff --git a/app/assets/javascripts/alert_management/graphql/fragments/list_item.fragment.graphql b/app/assets/javascripts/alert_management/graphql/fragments/list_item.fragment.graphql
index 746c4435f38..c37f29c74fc 100644
--- a/app/assets/javascripts/alert_management/graphql/fragments/list_item.fragment.graphql
+++ b/app/assets/javascripts/alert_management/graphql/fragments/list_item.fragment.graphql
@@ -4,7 +4,6 @@ fragment AlertListItem on AlertManagementAlert {
severity
status
startedAt
- endedAt
eventCount
issueIid
assignees {
diff --git a/app/assets/javascripts/alert_management/graphql/mutations/alert_set_assignees.graphql b/app/assets/javascripts/alert_management/graphql/mutations/alert_set_assignees.graphql
deleted file mode 100644
index efeaf8fa372..00000000000
--- a/app/assets/javascripts/alert_management/graphql/mutations/alert_set_assignees.graphql
+++ /dev/null
@@ -1,15 +0,0 @@
-mutation($projectPath: ID!, $assigneeUsernames: [String!]!, $iid: String!) {
- alertSetAssignees(
- input: { iid: $iid, assigneeUsernames: $assigneeUsernames, projectPath: $projectPath }
- ) {
- errors
- alert {
- iid
- assignees {
- nodes {
- username
- }
- }
- }
- }
-}
diff --git a/app/assets/javascripts/alert_management/graphql/mutations/alert_set_assignees.mutation.graphql b/app/assets/javascripts/alert_management/graphql/mutations/alert_set_assignees.mutation.graphql
new file mode 100644
index 00000000000..40b4b6ae854
--- /dev/null
+++ b/app/assets/javascripts/alert_management/graphql/mutations/alert_set_assignees.mutation.graphql
@@ -0,0 +1,22 @@
+#import "../fragments/alert_note.fragment.graphql"
+
+mutation alertSetAssignees($projectPath: ID!, $assigneeUsernames: [String!]!, $iid: String!) {
+ alertSetAssignees(
+ input: { iid: $iid, assigneeUsernames: $assigneeUsernames, projectPath: $projectPath }
+ ) {
+ errors
+ alert {
+ iid
+ assignees {
+ nodes {
+ username
+ }
+ }
+ notes {
+ nodes {
+ ...AlertNote
+ }
+ }
+ }
+ }
+}
diff --git a/app/assets/javascripts/alert_management/graphql/mutations/alert_todo_create.graphql b/app/assets/javascripts/alert_management/graphql/mutations/alert_todo_create.graphql
new file mode 100644
index 00000000000..cdf3d763302
--- /dev/null
+++ b/app/assets/javascripts/alert_management/graphql/mutations/alert_todo_create.graphql
@@ -0,0 +1,11 @@
+mutation($projectPath: ID!, $iid: String!) {
+ alertTodoCreate(input: { iid: $iid, projectPath: $projectPath }) {
+ errors
+ alert {
+ iid
+ }
+ todo {
+ id
+ }
+ }
+}
diff --git a/app/assets/javascripts/alert_management/graphql/mutations/create_issue_from_alert.graphql b/app/assets/javascripts/alert_management/graphql/mutations/create_issue_from_alert.graphql
deleted file mode 100644
index 664596ab88f..00000000000
--- a/app/assets/javascripts/alert_management/graphql/mutations/create_issue_from_alert.graphql
+++ /dev/null
@@ -1,8 +0,0 @@
-mutation ($projectPath: ID!, $iid: String!) {
- createAlertIssue(input: { iid: $iid, projectPath: $projectPath }) {
- errors
- issue {
- iid
- }
- }
-}
diff --git a/app/assets/javascripts/alert_management/graphql/mutations/create_issue_from_alert.mutation.graphql b/app/assets/javascripts/alert_management/graphql/mutations/create_issue_from_alert.mutation.graphql
new file mode 100644
index 00000000000..bc4d91a51d1
--- /dev/null
+++ b/app/assets/javascripts/alert_management/graphql/mutations/create_issue_from_alert.mutation.graphql
@@ -0,0 +1,8 @@
+mutation createAlertIssue($projectPath: ID!, $iid: String!) {
+ createAlertIssue(input: { iid: $iid, projectPath: $projectPath }) {
+ errors
+ issue {
+ iid
+ }
+ }
+}
diff --git a/app/assets/javascripts/alert_management/graphql/mutations/toggle_sidebar_status.mutation.graphql b/app/assets/javascripts/alert_management/graphql/mutations/toggle_sidebar_status.mutation.graphql
new file mode 100644
index 00000000000..f666fcd6782
--- /dev/null
+++ b/app/assets/javascripts/alert_management/graphql/mutations/toggle_sidebar_status.mutation.graphql
@@ -0,0 +1,3 @@
+mutation toggleSidebarStatus {
+ toggleSidebarStatus @client
+}
diff --git a/app/assets/javascripts/alert_management/graphql/mutations/update_alert_status.graphql b/app/assets/javascripts/alert_management/graphql/mutations/update_alert_status.graphql
deleted file mode 100644
index 09151f233f5..00000000000
--- a/app/assets/javascripts/alert_management/graphql/mutations/update_alert_status.graphql
+++ /dev/null
@@ -1,10 +0,0 @@
-mutation ($projectPath: ID!, $status: AlertManagementStatus!, $iid: String!) {
- updateAlertStatus(input: { iid: $iid, status: $status, projectPath: $projectPath }) {
- errors
- alert {
- iid,
- status,
- endedAt
- }
- }
-}
diff --git a/app/assets/javascripts/alert_management/graphql/mutations/update_alert_status.mutation.graphql b/app/assets/javascripts/alert_management/graphql/mutations/update_alert_status.mutation.graphql
new file mode 100644
index 00000000000..ba1e607bc10
--- /dev/null
+++ b/app/assets/javascripts/alert_management/graphql/mutations/update_alert_status.mutation.graphql
@@ -0,0 +1,17 @@
+#import "../fragments/alert_note.fragment.graphql"
+
+mutation updateAlertStatus($projectPath: ID!, $status: AlertManagementStatus!, $iid: String!) {
+ updateAlertStatus(input: { iid: $iid, status: $status, projectPath: $projectPath }) {
+ errors
+ alert {
+ iid
+ status
+ endedAt
+ notes {
+ nodes {
+ ...AlertNote
+ }
+ }
+ }
+ }
+}
diff --git a/app/assets/javascripts/alert_management/graphql/queries/details.query.graphql b/app/assets/javascripts/alert_management/graphql/queries/details.query.graphql
index c02b8accdd1..8881f49b689 100644
--- a/app/assets/javascripts/alert_management/graphql/queries/details.query.graphql
+++ b/app/assets/javascripts/alert_management/graphql/queries/details.query.graphql
@@ -1,11 +1,11 @@
#import "../fragments/detail_item.fragment.graphql"
query alertDetails($fullPath: ID!, $alertId: String) {
- project(fullPath: $fullPath) {
- alertManagementAlerts(iid: $alertId) {
- nodes {
- ...AlertDetailItem
- }
- }
+ project(fullPath: $fullPath) {
+ alertManagementAlerts(iid: $alertId) {
+ nodes {
+ ...AlertDetailItem
+ }
}
+ }
}
diff --git a/app/assets/javascripts/alert_management/graphql/queries/get_alerts.query.graphql b/app/assets/javascripts/alert_management/graphql/queries/get_alerts.query.graphql
index 1d3c3c83cc1..8ac00bbc6b5 100644
--- a/app/assets/javascripts/alert_management/graphql/queries/get_alerts.query.graphql
+++ b/app/assets/javascripts/alert_management/graphql/queries/get_alerts.query.graphql
@@ -1,32 +1,34 @@
#import "../fragments/list_item.fragment.graphql"
query getAlerts(
- $projectPath: ID!,
- $statuses: [AlertManagementStatus!],
- $sort: AlertManagementAlertSort,
- $firstPageSize: Int,
- $lastPageSize: Int,
- $prevPageCursor: String = ""
- $nextPageCursor: String = ""
+ $searchTerm: String
+ $projectPath: ID!
+ $statuses: [AlertManagementStatus!]
+ $sort: AlertManagementAlertSort
+ $firstPageSize: Int
+ $lastPageSize: Int
+ $prevPageCursor: String = ""
+ $nextPageCursor: String = ""
) {
- project(fullPath: $projectPath, ) {
- alertManagementAlerts(
- statuses: $statuses,
- sort: $sort,
- first: $firstPageSize
- last: $lastPageSize,
- after: $nextPageCursor,
- before: $prevPageCursor
- ) {
- nodes {
- ...AlertListItem
- },
- pageInfo {
- hasNextPage
- endCursor
- hasPreviousPage
- startCursor
- }
- }
+ project(fullPath: $projectPath) {
+ alertManagementAlerts(
+ search: $searchTerm
+ statuses: $statuses
+ sort: $sort
+ first: $firstPageSize
+ last: $lastPageSize
+ after: $nextPageCursor
+ before: $prevPageCursor
+ ) {
+ nodes {
+ ...AlertListItem
+ }
+ pageInfo {
+ hasNextPage
+ endCursor
+ hasPreviousPage
+ startCursor
+ }
}
+ }
}
diff --git a/app/assets/javascripts/alert_management/graphql/queries/get_count_by_status.query.graphql b/app/assets/javascripts/alert_management/graphql/queries/get_count_by_status.query.graphql
index 1143050200c..5a6faea5cd8 100644
--- a/app/assets/javascripts/alert_management/graphql/queries/get_count_by_status.query.graphql
+++ b/app/assets/javascripts/alert_management/graphql/queries/get_count_by_status.query.graphql
@@ -1,11 +1,11 @@
-query getAlertsCount($projectPath: ID!) {
- project(fullPath: $projectPath) {
- alertManagementAlertStatusCounts {
- all
- open
- acknowledged
- resolved
- triggered
- }
+query getAlertsCount($searchTerm: String, $projectPath: ID!) {
+ project(fullPath: $projectPath) {
+ alertManagementAlertStatusCounts(search: $searchTerm) {
+ all
+ open
+ acknowledged
+ resolved
+ triggered
}
+ }
}
diff --git a/app/assets/javascripts/alert_management/graphql/queries/sidebar_status.query.graphql b/app/assets/javascripts/alert_management/graphql/queries/sidebar_status.query.graphql
new file mode 100644
index 00000000000..61c570c5cd0
--- /dev/null
+++ b/app/assets/javascripts/alert_management/graphql/queries/sidebar_status.query.graphql
@@ -0,0 +1,3 @@
+query sidebarStatus {
+ sidebarStatus @client
+}
diff --git a/app/assets/javascripts/alert_management/list.js b/app/assets/javascripts/alert_management/list.js
index cae6a536b56..3f78ca66a59 100644
--- a/app/assets/javascripts/alert_management/list.js
+++ b/app/assets/javascripts/alert_management/list.js
@@ -3,7 +3,7 @@ import VueApollo from 'vue-apollo';
import createDefaultClient from '~/lib/graphql';
import { defaultDataIdFromObject } from 'apollo-cache-inmemory';
import { parseBoolean } from '~/lib/utils/common_utils';
-import AlertManagementList from './components/alert_management_list.vue';
+import AlertManagementList from './components/alert_management_list_wrapper.vue';
Vue.use(VueApollo);
@@ -11,11 +11,18 @@ export default () => {
const selector = '#js-alert_management';
const domEl = document.querySelector(selector);
- const { projectPath, enableAlertManagementPath, emptyAlertSvgPath } = domEl.dataset;
- let { alertManagementEnabled, userCanEnableAlertManagement } = domEl.dataset;
+ const {
+ projectPath,
+ enableAlertManagementPath,
+ emptyAlertSvgPath,
+ populatingAlertsHelpUrl,
+ opsgenieMvcTargetUrl,
+ } = domEl.dataset;
+ let { alertManagementEnabled, userCanEnableAlertManagement, opsgenieMvcEnabled } = domEl.dataset;
alertManagementEnabled = parseBoolean(alertManagementEnabled);
userCanEnableAlertManagement = parseBoolean(userCanEnableAlertManagement);
+ opsgenieMvcEnabled = parseBoolean(opsgenieMvcEnabled);
const apolloProvider = new VueApollo({
defaultClient: createDefaultClient(
@@ -45,9 +52,12 @@ export default () => {
props: {
projectPath,
enableAlertManagementPath,
+ populatingAlertsHelpUrl,
emptyAlertSvgPath,
alertManagementEnabled,
userCanEnableAlertManagement,
+ opsgenieMvcTargetUrl,
+ opsgenieMvcEnabled,
},
});
},
diff --git a/app/assets/javascripts/alerts_service_settings/components/alerts_service_form.vue b/app/assets/javascripts/alerts_service_settings/components/alerts_service_form.vue
index ac30b086875..a2d94fb8083 100644
--- a/app/assets/javascripts/alerts_service_settings/components/alerts_service_form.vue
+++ b/app/assets/javascripts/alerts_service_settings/components/alerts_service_form.vue
@@ -64,6 +64,11 @@ export default {
type: Boolean,
required: true,
},
+ isDisabled: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
},
data() {
return {
@@ -142,7 +147,7 @@ export default {
<gl-form-group :label="__('Active')" label-for="activated" label-class="label-bold">
<toggle-button
id="activated"
- :disabled-input="loadingActivated"
+ :disabled-input="loadingActivated || isDisabled"
:is-loading="loadingActivated"
:value="activated"
@change="toggleActivated"
@@ -152,7 +157,11 @@ export default {
<div class="input-group">
<gl-form-input id="url" :readonly="true" :value="url" />
<span class="input-group-append">
- <clipboard-button :text="url" :title="$options.COPY_TO_CLIPBOARD" />
+ <clipboard-button
+ :text="url"
+ :title="$options.COPY_TO_CLIPBOARD"
+ :disabled="isDisabled"
+ />
</span>
</div>
</gl-form-group>
@@ -164,10 +173,16 @@ export default {
<div class="input-group">
<gl-form-input id="authorization-key" :readonly="true" :value="authorizationKey" />
<span class="input-group-append">
- <clipboard-button :text="authorizationKey" :title="$options.COPY_TO_CLIPBOARD" />
+ <clipboard-button
+ :text="authorizationKey"
+ :title="$options.COPY_TO_CLIPBOARD"
+ :disabled="isDisabled"
+ />
</span>
</div>
- <gl-button v-gl-modal.authKeyModal class="mt-2">{{ $options.RESET_KEY }}</gl-button>
+ <gl-button v-gl-modal.authKeyModal class="mt-2" :disabled="isDisabled">{{
+ $options.RESET_KEY
+ }}</gl-button>
<gl-modal
modal-id="authKeyModal"
:title="$options.RESET_KEY"
diff --git a/app/assets/javascripts/alerts_service_settings/index.js b/app/assets/javascripts/alerts_service_settings/index.js
index c26adf24a7f..fe83ced2ee7 100644
--- a/app/assets/javascripts/alerts_service_settings/index.js
+++ b/app/assets/javascripts/alerts_service_settings/index.js
@@ -14,8 +14,11 @@ export default el => {
formPath,
authorizationKey,
url,
+ disabled,
} = el.dataset;
+
const activated = parseBoolean(activatedStr);
+ const isDisabled = parseBoolean(disabled);
return new Vue({
el,
@@ -28,6 +31,7 @@ export default el => {
formPath,
initialAuthorizationKey: authorizationKey,
url,
+ isDisabled,
},
});
},
diff --git a/app/assets/javascripts/alerts_settings/components/alerts_settings_form.vue b/app/assets/javascripts/alerts_settings/components/alerts_settings_form.vue
new file mode 100644
index 00000000000..18c9f82f052
--- /dev/null
+++ b/app/assets/javascripts/alerts_settings/components/alerts_settings_form.vue
@@ -0,0 +1,563 @@
+<script>
+import {
+ GlAlert,
+ GlButton,
+ GlForm,
+ GlFormGroup,
+ GlFormInput,
+ GlFormInputGroup,
+ GlFormTextarea,
+ GlLink,
+ GlModal,
+ GlModalDirective,
+ GlSprintf,
+ GlFormSelect,
+} from '@gitlab/ui';
+import { debounce } from 'lodash';
+import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
+import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
+import ToggleButton from '~/vue_shared/components/toggle_button.vue';
+import csrf from '~/lib/utils/csrf';
+import service from '../services';
+import {
+ i18n,
+ serviceOptions,
+ JSON_VALIDATE_DELAY,
+ targetPrometheusUrlPlaceholder,
+ targetOpsgenieUrlPlaceholder,
+} from '../constants';
+
+export default {
+ i18n,
+ csrf,
+ targetOpsgenieUrlPlaceholder,
+ targetPrometheusUrlPlaceholder,
+ components: {
+ GlAlert,
+ GlButton,
+ GlForm,
+ GlFormGroup,
+ GlFormInput,
+ GlFormInputGroup,
+ GlFormSelect,
+ GlFormTextarea,
+ GlLink,
+ GlModal,
+ GlSprintf,
+ ClipboardButton,
+ ToggleButton,
+ },
+ directives: {
+ 'gl-modal': GlModalDirective,
+ },
+ mixins: [glFeatureFlagsMixin()],
+ props: {
+ prometheus: {
+ type: Object,
+ required: true,
+ validator: ({ activated }) => {
+ return activated !== undefined;
+ },
+ },
+ generic: {
+ type: Object,
+ required: true,
+ validator: ({ formPath }) => {
+ return formPath !== undefined;
+ },
+ },
+ opsgenie: {
+ type: Object,
+ required: true,
+ },
+ },
+ data() {
+ return {
+ activated: {
+ generic: this.generic.activated,
+ prometheus: this.prometheus.activated,
+ opsgenie: this.opsgenie?.activated,
+ },
+ loading: false,
+ authorizationKey: {
+ generic: this.generic.initialAuthorizationKey,
+ prometheus: this.prometheus.prometheusAuthorizationKey,
+ },
+ selectedEndpoint: serviceOptions[0].value,
+ options: serviceOptions,
+ targetUrl: null,
+ feedback: {
+ variant: 'danger',
+ feedbackMessage: null,
+ isFeedbackDismissed: false,
+ },
+ serverError: null,
+ testAlert: {
+ json: null,
+ error: null,
+ },
+ canSaveForm: false,
+ };
+ },
+ computed: {
+ sections() {
+ return [
+ {
+ text: this.$options.i18n.usageSection,
+ url: this.generic.alertsUsageUrl,
+ },
+ {
+ text: this.$options.i18n.setupSection,
+ url: this.generic.alertsSetupUrl,
+ },
+ ];
+ },
+ isPrometheus() {
+ return this.selectedEndpoint === 'prometheus';
+ },
+ isOpsgenie() {
+ return this.selectedEndpoint === 'opsgenie';
+ },
+ selectedService() {
+ switch (this.selectedEndpoint) {
+ case 'generic': {
+ return {
+ url: this.generic.url,
+ authKey: this.authorizationKey.generic,
+ active: this.activated.generic,
+ resetKey: this.resetGenericKey.bind(this),
+ };
+ }
+ case 'prometheus': {
+ return {
+ url: this.prometheus.prometheusUrl,
+ authKey: this.authorizationKey.prometheus,
+ active: this.activated.prometheus,
+ resetKey: this.resetPrometheusKey.bind(this),
+ targetUrl: this.prometheus.prometheusApiUrl,
+ };
+ }
+ case 'opsgenie': {
+ return {
+ targetUrl: this.opsgenie.opsgenieMvcTargetUrl,
+ active: this.activated.opsgenie,
+ };
+ }
+ default: {
+ return {};
+ }
+ }
+ },
+ showFeedbackMsg() {
+ return this.feedback.feedbackMessage && !this.isFeedbackDismissed;
+ },
+ showAlertSave() {
+ return (
+ this.feedback.feedbackMessage === this.$options.i18n.testAlertFailed &&
+ !this.isFeedbackDismissed
+ );
+ },
+ prometheusInfo() {
+ return this.isPrometheus ? this.$options.i18n.prometheusInfo : '';
+ },
+ jsonIsValid() {
+ return this.testAlert.error === null;
+ },
+ canTestAlert() {
+ return this.selectedService.active && this.testAlert.json !== null;
+ },
+ canSaveConfig() {
+ return !this.loading && this.canSaveForm;
+ },
+ baseUrlPlaceholder() {
+ return this.isOpsgenie
+ ? this.$options.targetOpsgenieUrlPlaceholder
+ : this.$options.targetPrometheusUrlPlaceholder;
+ },
+ },
+ watch: {
+ 'testAlert.json': debounce(function debouncedJsonValidate() {
+ this.validateJson();
+ }, JSON_VALIDATE_DELAY),
+ targetUrl(oldVal, newVal) {
+ if (newVal && oldVal !== this.selectedService.targetUrl) {
+ this.canSaveForm = true;
+ }
+ },
+ },
+ mounted() {
+ if (
+ this.activated.prometheus ||
+ this.activated.generic ||
+ !this.opsgenie.opsgenieMvcIsAvailable
+ ) {
+ this.removeOpsGenieOption();
+ } else if (this.activated.opsgenie) {
+ this.setOpsgenieAsDefault();
+ }
+ },
+ methods: {
+ createUserErrorMessage(errors) {
+ // eslint-disable-next-line prefer-destructuring
+ this.serverError = Object.values(errors)[0][0];
+ },
+ setOpsgenieAsDefault() {
+ this.options = this.options.map(el => {
+ if (el.value !== 'opsgenie') {
+ return { ...el, disabled: true };
+ }
+ return { ...el, disabled: false };
+ });
+ this.selectedEndpoint = this.options.find(({ value }) => value === 'opsgenie').value;
+ if (this.targetUrl === null) {
+ this.targetUrl = this.selectedService.targetUrl;
+ }
+ },
+ removeOpsGenieOption() {
+ this.options = this.options.map(el => {
+ if (el.value !== 'opsgenie') {
+ return { ...el, disabled: false };
+ }
+ return { ...el, disabled: true };
+ });
+ },
+ resetFormValues() {
+ this.testAlert.json = null;
+ this.targetUrl = this.selectedService.targetUrl;
+ },
+ dismissFeedback() {
+ this.serverError = null;
+ this.feedback = { ...this.feedback, feedbackMessage: null };
+ this.isFeedbackDismissed = false;
+ },
+ resetGenericKey() {
+ return service
+ .updateGenericKey({ endpoint: this.generic.formPath, params: { service: { token: '' } } })
+ .then(({ data: { token } }) => {
+ this.authorizationKey.generic = token;
+ this.setFeedback({ feedbackMessage: this.$options.i18n.authKeyRest, variant: 'success' });
+ })
+ .catch(() => {
+ this.setFeedback({ feedbackMessage: this.$options.i18n.errorKeyMsg, variant: 'danger' });
+ });
+ },
+ resetPrometheusKey() {
+ return service
+ .updatePrometheusKey({ endpoint: this.prometheus.prometheusResetKeyPath })
+ .then(({ data: { token } }) => {
+ this.authorizationKey.prometheus = token;
+ this.setFeedback({ feedbackMessage: this.$options.i18n.authKeyRest, variant: 'success' });
+ })
+ .catch(() => {
+ this.setFeedback({ feedbackMessage: this.$options.i18n.errorKeyMsg, variant: 'danger' });
+ });
+ },
+ toggleService(value) {
+ this.canSaveForm = true;
+ if (this.isPrometheus) {
+ this.activated.prometheus = value;
+ } else {
+ this.activated[this.selectedEndpoint] = value;
+ }
+ },
+ toggle(value) {
+ return this.isPrometheus ? this.togglePrometheusActive(value) : this.toggleActivated(value);
+ },
+ toggleActivated(value) {
+ this.loading = true;
+ return service
+ .updateGenericActive({
+ endpoint: this[this.selectedEndpoint].formPath,
+ params: this.isOpsgenie
+ ? { service: { opsgenie_mvc_target_url: this.targetUrl, opsgenie_mvc_enabled: value } }
+ : { service: { active: value } },
+ })
+ .then(() => {
+ this.activated[this.selectedEndpoint] = value;
+ this.toggleSuccess(value);
+
+ if (!this.isOpsgenie && value) {
+ if (!this.selectedService.authKey) {
+ return window.location.reload();
+ }
+
+ return this.removeOpsGenieOption();
+ }
+
+ if (this.isOpsgenie && value) {
+ return this.setOpsgenieAsDefault();
+ }
+
+ // eslint-disable-next-line no-return-assign
+ return (this.options = serviceOptions);
+ })
+ .catch(({ response: { data: { errors } = {} } = {} }) => {
+ this.createUserErrorMessage(errors);
+ this.setFeedback({
+ feedbackMessage: `${this.$options.i18n.errorMsg}.`,
+ variant: 'danger',
+ });
+ })
+ .finally(() => {
+ this.loading = false;
+ this.canSaveForm = false;
+ });
+ },
+ togglePrometheusActive(value) {
+ this.loading = true;
+ return service
+ .updatePrometheusActive({
+ endpoint: this.prometheus.prometheusFormPath,
+ params: {
+ token: this.$options.csrf.token,
+ config: value,
+ url: this.targetUrl,
+ redirect: window.location,
+ },
+ })
+ .then(() => {
+ this.activated.prometheus = value;
+ this.toggleSuccess(value);
+ this.removeOpsGenieOption();
+ })
+ .catch(({ response: { data: { errors } = {} } = {} }) => {
+ this.createUserErrorMessage(errors);
+ this.setFeedback({
+ feedbackMessage: `${this.$options.i18n.errorMsg}.`,
+ variant: 'danger',
+ });
+ })
+ .finally(() => {
+ this.loading = false;
+ this.canSaveForm = false;
+ });
+ },
+ toggleSuccess(value) {
+ if (value) {
+ this.setFeedback({
+ feedbackMessage: this.$options.i18n.endPointActivated,
+ variant: 'info',
+ });
+ } else {
+ this.setFeedback({
+ feedbackMessage: this.$options.i18n.changesSaved,
+ variant: 'info',
+ });
+ }
+ },
+ setFeedback({ feedbackMessage, variant }) {
+ this.feedback = { feedbackMessage, variant };
+ },
+ validateJson() {
+ this.testAlert.error = null;
+ try {
+ JSON.parse(this.testAlert.json);
+ } catch (e) {
+ this.testAlert.error = JSON.stringify(e.message);
+ }
+ },
+ validateTestAlert() {
+ this.loading = true;
+ this.validateJson();
+ return service
+ .updateTestAlert({
+ endpoint: this.selectedService.url,
+ data: this.testAlert.json,
+ authKey: this.selectedService.authKey,
+ })
+ .then(() => {
+ this.setFeedback({
+ feedbackMessage: this.$options.i18n.testAlertSuccess,
+ variant: 'success',
+ });
+ })
+ .catch(() => {
+ this.setFeedback({
+ feedbackMessage: this.$options.i18n.testAlertFailed,
+ variant: 'danger',
+ });
+ })
+ .finally(() => {
+ this.loading = false;
+ });
+ },
+ onSubmit() {
+ this.toggle(this.selectedService.active);
+ },
+ onReset() {
+ this.testAlert.json = null;
+ this.dismissFeedback();
+ this.targetUrl = this.selectedService.targetUrl;
+
+ if (this.canSaveForm) {
+ this.canSaveForm = false;
+ this.activated[this.selectedEndpoint] = this[this.selectedEndpoint].activated;
+ }
+ },
+ },
+};
+</script>
+
+<template>
+ <div>
+ <gl-alert v-if="showFeedbackMsg" :variant="feedback.variant" @dismiss="dismissFeedback">
+ {{ feedback.feedbackMessage }}
+ <br />
+ <i v-if="serverError">{{ __('Error message:') }} {{ serverError }}</i>
+ <gl-button
+ v-if="showAlertSave"
+ variant="danger"
+ category="primary"
+ class="gl-display-block gl-mt-3"
+ @click="toggle(selectedService.active)"
+ >
+ {{ __('Save anyway') }}
+ </gl-button>
+ </gl-alert>
+ <div data-testid="alert-settings-description" class="gl-mt-5">
+ <p v-for="section in sections" :key="section.text">
+ <gl-sprintf :message="section.text">
+ <template #link="{ content }">
+ <gl-link :href="section.url" target="_blank">{{ content }}</gl-link>
+ </template>
+ </gl-sprintf>
+ </p>
+ </div>
+ <gl-form @submit.prevent="onSubmit" @reset.prevent="onReset">
+ <gl-form-group
+ :label="$options.i18n.integrationsLabel"
+ label-for="integrations"
+ label-class="label-bold"
+ >
+ <gl-form-select
+ v-model="selectedEndpoint"
+ :options="options"
+ data-testid="alert-settings-select"
+ @change="resetFormValues"
+ />
+ <span class="gl-text-gray-400">
+ <gl-sprintf :message="$options.i18n.integrationsInfo">
+ <template #link="{ content }">
+ <gl-link
+ class="gl-display-inline-block"
+ href="https://gitlab.com/groups/gitlab-org/-/epics/3362"
+ target="_blank"
+ >{{ content }}</gl-link
+ >
+ </template>
+ </gl-sprintf>
+ </span>
+ </gl-form-group>
+ <gl-form-group
+ :label="$options.i18n.activeLabel"
+ label-for="activated"
+ label-class="label-bold"
+ >
+ <toggle-button
+ id="activated"
+ :disabled-input="loading"
+ :is-loading="loading"
+ :value="selectedService.active"
+ @change="toggleService"
+ />
+ </gl-form-group>
+ <gl-form-group
+ v-if="isOpsgenie || isPrometheus"
+ :label="$options.i18n.apiBaseUrlLabel"
+ label-for="api-url"
+ label-class="label-bold"
+ >
+ <gl-form-input
+ id="api-url"
+ v-model="targetUrl"
+ type="url"
+ :placeholder="baseUrlPlaceholder"
+ :disabled="!selectedService.active"
+ />
+ <span class="gl-text-gray-400">
+ {{ $options.i18n.apiBaseUrlHelpText }}
+ </span>
+ </gl-form-group>
+ <template v-if="!isOpsgenie">
+ <gl-form-group :label="$options.i18n.urlLabel" label-for="url" label-class="label-bold">
+ <gl-form-input-group id="url" readonly :value="selectedService.url">
+ <template #append>
+ <clipboard-button
+ :text="selectedService.url"
+ :title="$options.i18n.copyToClipboard"
+ class="gl-m-0!"
+ />
+ </template>
+ </gl-form-input-group>
+ <span class="gl-text-gray-400">
+ {{ prometheusInfo }}
+ </span>
+ </gl-form-group>
+ <gl-form-group
+ :label="$options.i18n.authKeyLabel"
+ label-for="authorization-key"
+ label-class="label-bold"
+ >
+ <gl-form-input-group
+ id="authorization-key"
+ class="gl-mb-2"
+ readonly
+ :value="selectedService.authKey"
+ >
+ <template #append>
+ <clipboard-button
+ :text="selectedService.authKey || ''"
+ :title="$options.i18n.copyToClipboard"
+ class="gl-m-0!"
+ />
+ </template>
+ </gl-form-input-group>
+ <gl-button v-gl-modal.authKeyModal :disabled="!selectedService.active" class="gl-mt-3">{{
+ $options.i18n.resetKey
+ }}</gl-button>
+ <gl-modal
+ modal-id="authKeyModal"
+ :title="$options.i18n.resetKey"
+ :ok-title="$options.i18n.resetKey"
+ ok-variant="danger"
+ @ok="selectedService.resetKey"
+ >
+ {{ $options.i18n.restKeyInfo }}
+ </gl-modal>
+ </gl-form-group>
+ <gl-form-group
+ :label="$options.i18n.alertJson"
+ label-for="alert-json"
+ label-class="label-bold"
+ :invalid-feedback="testAlert.error"
+ >
+ <gl-form-textarea
+ id="alert-json"
+ v-model.trim="testAlert.json"
+ :disabled="!selectedService.active"
+ :state="jsonIsValid"
+ :placeholder="$options.i18n.alertJsonPlaceholder"
+ rows="6"
+ max-rows="10"
+ />
+ </gl-form-group>
+ <gl-button :disabled="!canTestAlert" @click="validateTestAlert">{{
+ $options.i18n.testAlertInfo
+ }}</gl-button>
+ </template>
+ <div class="footer-block row-content-block gl-display-flex gl-justify-content-space-between">
+ <gl-button
+ variant="success"
+ category="primary"
+ :disabled="!canSaveConfig"
+ @click="onSubmit"
+ >
+ {{ __('Save changes') }}
+ </gl-button>
+ <gl-button variant="default" category="primary" :disabled="!canSaveConfig" @click="onReset">
+ {{ __('Cancel') }}
+ </gl-button>
+ </div>
+ </gl-form>
+ </div>
+</template>
diff --git a/app/assets/javascripts/alerts_settings/constants.js b/app/assets/javascripts/alerts_settings/constants.js
new file mode 100644
index 00000000000..d15e8619df4
--- /dev/null
+++ b/app/assets/javascripts/alerts_settings/constants.js
@@ -0,0 +1,50 @@
+import { s__ } from '~/locale';
+
+export const i18n = {
+ usageSection: s__(
+ 'AlertSettings|You must provide this URL and authorization key to authorize an external service to send alerts to GitLab. You can provide this URL and key to multiple services. After configuring an external service, alerts from your service will display on the GitLab %{linkStart}Alerts%{linkEnd} page.',
+ ),
+ setupSection: s__(
+ "AlertSettings|Review your external service's documentation to learn where to provide this information to your external service, and the %{linkStart}GitLab documentation%{linkEnd} to learn more about configuring your endpoint.",
+ ),
+ errorMsg: s__('AlertSettings|There was an error updating the alert settings'),
+ errorKeyMsg: s__(
+ 'AlertSettings|There was an error while trying to reset the key. Please refresh the page to try again.',
+ ),
+ restKeyInfo: s__(
+ 'AlertSettings|Resetting the authorization key for this project will require updating the authorization key in every alert source it is enabled in.',
+ ),
+ endPointActivated: s__('AlertSettings|Alerts endpoint successfully activated.'),
+ changesSaved: s__('AlertSettings|Your changes were successfully updated.'),
+ prometheusInfo: s__('AlertSettings|Add URL and auth key to your Prometheus config file'),
+ integrationsInfo: s__(
+ 'AlertSettings|Learn more about our %{linkStart}upcoming integrations%{linkEnd}',
+ ),
+ resetKey: s__('AlertSettings|Reset key'),
+ copyToClipboard: s__('AlertSettings|Copy'),
+ integrationsLabel: s__('AlertSettings|Integrations'),
+ apiBaseUrlLabel: s__('AlertSettings|API URL'),
+ authKeyLabel: s__('AlertSettings|Authorization key'),
+ urlLabel: s__('AlertSettings|Webhook URL'),
+ activeLabel: s__('AlertSettings|Active'),
+ apiBaseUrlHelpText: s__('AlertSettings|URL cannot be blank and must start with http or https'),
+ testAlertInfo: s__('AlertSettings|Test alert payload'),
+ alertJson: s__('AlertSettings|Alert test payload'),
+ alertJsonPlaceholder: s__('AlertSettings|Enter test alert JSON....'),
+ testAlertFailed: s__('AlertSettings|Test failed. Do you still want to save your changes anyway?'),
+ testAlertSuccess: s__(
+ 'AlertSettings|Test alert sent successfully. If you have made other changes, please save them now.',
+ ),
+ authKeyRest: s__('AlertSettings|Authorization key has been successfully reset'),
+};
+
+export const serviceOptions = [
+ { value: 'generic', text: s__('AlertSettings|Generic') },
+ { value: 'prometheus', text: s__('AlertSettings|External Prometheus') },
+ { value: 'opsgenie', text: s__('AlertSettings|Opsgenie') },
+];
+
+export const JSON_VALIDATE_DELAY = 250;
+
+export const targetPrometheusUrlPlaceholder = 'http://prometheus.example.com/';
+export const targetOpsgenieUrlPlaceholder = 'https://app.opsgenie.com/alert/list/';
diff --git a/app/assets/javascripts/alerts_settings/index.js b/app/assets/javascripts/alerts_settings/index.js
new file mode 100644
index 00000000000..a4c2bf6b18e
--- /dev/null
+++ b/app/assets/javascripts/alerts_settings/index.js
@@ -0,0 +1,67 @@
+import Vue from 'vue';
+import { parseBoolean } from '~/lib/utils/common_utils';
+import AlertSettingsForm from './components/alerts_settings_form.vue';
+
+export default el => {
+ if (!el) {
+ return null;
+ }
+
+ const {
+ prometheusActivated,
+ prometheusUrl,
+ prometheusAuthorizationKey,
+ prometheusFormPath,
+ prometheusResetKeyPath,
+ prometheusApiUrl,
+ activated: activatedStr,
+ alertsSetupUrl,
+ alertsUsageUrl,
+ formPath,
+ authorizationKey,
+ url,
+ opsgenieMvcAvailable,
+ opsgenieMvcFormPath,
+ opsgenieMvcEnabled,
+ opsgenieMvcTargetUrl,
+ } = el.dataset;
+
+ const genericActivated = parseBoolean(activatedStr);
+ const prometheusIsActivated = parseBoolean(prometheusActivated);
+ const opsgenieMvcActivated = parseBoolean(opsgenieMvcEnabled);
+ const opsgenieMvcIsAvailable = parseBoolean(opsgenieMvcAvailable);
+
+ const props = {
+ prometheus: {
+ activated: prometheusIsActivated,
+ prometheusUrl,
+ prometheusAuthorizationKey,
+ prometheusFormPath,
+ prometheusResetKeyPath,
+ prometheusApiUrl,
+ },
+ generic: {
+ alertsSetupUrl,
+ alertsUsageUrl,
+ activated: genericActivated,
+ formPath,
+ initialAuthorizationKey: authorizationKey,
+ url,
+ },
+ opsgenie: {
+ formPath: opsgenieMvcFormPath,
+ activated: opsgenieMvcActivated,
+ opsgenieMvcTargetUrl,
+ opsgenieMvcIsAvailable,
+ },
+ };
+
+ return new Vue({
+ el,
+ render(createElement) {
+ return createElement(AlertSettingsForm, {
+ props,
+ });
+ },
+ });
+};
diff --git a/app/assets/javascripts/alerts_settings/services/index.js b/app/assets/javascripts/alerts_settings/services/index.js
new file mode 100644
index 00000000000..c49992d4f57
--- /dev/null
+++ b/app/assets/javascripts/alerts_settings/services/index.js
@@ -0,0 +1,36 @@
+/* eslint-disable @gitlab/require-i18n-strings */
+import axios from '~/lib/utils/axios_utils';
+
+export default {
+ updateGenericKey({ endpoint, params }) {
+ return axios.put(endpoint, params);
+ },
+ updatePrometheusKey({ endpoint }) {
+ return axios.post(endpoint);
+ },
+ updateGenericActive({ endpoint, params }) {
+ return axios.put(endpoint, params);
+ },
+ updatePrometheusActive({ endpoint, params: { token, config, url, redirect } }) {
+ const data = new FormData();
+ data.set('_method', 'put');
+ data.set('authenticity_token', token);
+ data.set('service[manual_configuration]', config);
+ data.set('service[api_url]', url);
+ data.set('redirect_to', redirect);
+
+ return axios.post(endpoint, data, {
+ headers: {
+ 'Content-Type': 'application/x-www-form-urlencoded',
+ },
+ });
+ },
+ updateTestAlert({ endpoint, data, authKey }) {
+ return axios.post(endpoint, data, {
+ headers: {
+ 'Content-Type': 'application/json',
+ Authorization: `Bearer ${authKey}`,
+ },
+ });
+ },
+};
diff --git a/app/assets/javascripts/api.js b/app/assets/javascripts/api.js
index 94d155840ea..c84e73ccdb4 100644
--- a/app/assets/javascripts/api.js
+++ b/app/assets/javascripts/api.js
@@ -11,6 +11,9 @@ const Api = {
groupMembersPath: '/api/:version/groups/:id/members',
subgroupsPath: '/api/:version/groups/:id/subgroups',
namespacesPath: '/api/:version/namespaces.json',
+ groupPackagesPath: '/api/:version/groups/:id/packages',
+ projectPackagesPath: '/api/:version/projects/:id/packages',
+ projectPackagePath: '/api/:version/projects/:id/packages/:package_id',
groupProjectsPath: '/api/:version/groups/:id/projects.json',
projectsPath: '/api/:version/projects.json',
projectPath: '/api/:version/projects/:id',
@@ -36,7 +39,9 @@ const Api = {
userStatusPath: '/api/:version/users/:id/status',
userProjectsPath: '/api/:version/users/:id/projects',
userPostStatusPath: '/api/:version/user/status',
- commitPath: '/api/:version/projects/:id/repository/commits',
+ commitPath: '/api/:version/projects/:id/repository/commits/:sha',
+ commitsPath: '/api/:version/projects/:id/repository/commits',
+
applySuggestionPath: '/api/:version/suggestions/:id/apply',
applySuggestionBatchPath: '/api/:version/suggestions/batch_apply',
commitPipelinesPath: '/:project_id/commit/:sha/pipelines',
@@ -64,6 +69,32 @@ const Api = {
});
},
+ groupPackages(id, options = {}) {
+ const url = Api.buildUrl(this.groupPackagesPath).replace(':id', id);
+ return axios.get(url, options);
+ },
+
+ projectPackages(id, options = {}) {
+ const url = Api.buildUrl(this.projectPackagesPath).replace(':id', id);
+ return axios.get(url, options);
+ },
+
+ buildProjectPackageUrl(projectId, packageId) {
+ return Api.buildUrl(this.projectPackagePath)
+ .replace(':id', projectId)
+ .replace(':package_id', packageId);
+ },
+
+ projectPackage(projectId, packageId) {
+ const url = this.buildProjectPackageUrl(projectId, packageId);
+ return axios.get(url);
+ },
+
+ deleteProjectPackage(projectId, packageId) {
+ const url = this.buildProjectPackageUrl(projectId, packageId);
+ return axios.delete(url);
+ },
+
groupMembers(id, options) {
const url = Api.buildUrl(this.groupMembersPath).replace(':id', encodeURIComponent(id));
@@ -308,9 +339,17 @@ const Api = {
.catch(() => flash(__('Something went wrong while fetching projects')));
},
+ commit(id, sha, params = {}) {
+ const url = Api.buildUrl(this.commitPath)
+ .replace(':id', encodeURIComponent(id))
+ .replace(':sha', encodeURIComponent(sha));
+
+ return axios.get(url, { params });
+ },
+
commitMultiple(id, data) {
// see https://docs.gitlab.com/ce/api/commits.html#create-a-commit-with-multiple-files-and-actions
- const url = Api.buildUrl(Api.commitPath).replace(':id', encodeURIComponent(id));
+ const url = Api.buildUrl(Api.commitsPath).replace(':id', encodeURIComponent(id));
return axios.post(url, JSON.stringify(data), {
headers: {
'Content-Type': 'application/json; charset=utf-8',
diff --git a/app/assets/javascripts/awards_handler.js b/app/assets/javascripts/awards_handler.js
index 8381b050900..0e83ba3d528 100644
--- a/app/assets/javascripts/awards_handler.js
+++ b/app/assets/javascripts/awards_handler.js
@@ -9,14 +9,10 @@ import { updateTooltipTitle } from './lib/utils/common_utils';
import { isInVueNoteablePage } from './lib/utils/dom_utils';
import flash from './flash';
import axios from './lib/utils/axios_utils';
+import * as Emoji from '~/emoji';
const animationEndEventString = 'animationend webkitAnimationEnd MSAnimationEnd oAnimationEnd';
const transitionEndEventString = 'transitionend webkitTransitionEnd oTransitionEnd MSTransitionEnd';
-const requestAnimationFrame =
- window.requestAnimationFrame ||
- window.webkitRequestAnimationFrame ||
- window.mozRequestAnimationFrame ||
- window.setTimeout;
const FROM_SENTENCE_REGEX = /(?:, and | and |, )/; // For separating lists produced by ruby's Array#toSentence
@@ -619,7 +615,7 @@ export class AwardsHandler {
let awardsHandlerPromise = null;
export default function loadAwardsHandler(reload = false) {
if (!awardsHandlerPromise || reload) {
- awardsHandlerPromise = import(/* webpackChunkName: 'emoji' */ './emoji').then(Emoji => {
+ awardsHandlerPromise = Emoji.initEmojiMap().then(() => {
const awardsHandler = new AwardsHandler(Emoji);
awardsHandler.bindEvents();
return awardsHandler;
diff --git a/app/assets/javascripts/badges/components/badge_form.vue b/app/assets/javascripts/badges/components/badge_form.vue
index dccc0b024ba..4145a4a4145 100644
--- a/app/assets/javascripts/badges/components/badge_form.vue
+++ b/app/assets/javascripts/badges/components/badge_form.vue
@@ -164,7 +164,7 @@ export default {
<template>
<form
:class="{ 'was-validated': wasValidated }"
- class="prepend-top-default append-bottom-default needs-validation"
+ class="gl-mt-3 gl-mb-3 needs-validation"
novalidate
@submit.prevent.stop="onSubmit"
>
diff --git a/app/assets/javascripts/batch_comments/components/draft_note.vue b/app/assets/javascripts/batch_comments/components/draft_note.vue
index 963d104b6b3..4c100ec7335 100644
--- a/app/assets/javascripts/batch_comments/components/draft_note.vue
+++ b/app/assets/javascripts/batch_comments/components/draft_note.vue
@@ -51,6 +51,7 @@ export default {
'scrollToDraft',
'toggleResolveDiscussion',
]),
+ ...mapActions(['setSelectedCommentPositionHover']),
update(data) {
this.updateDraft(data);
},
@@ -67,12 +68,16 @@ export default {
};
</script>
<template>
- <article class="draft-note-component note-wrapper">
+ <article
+ class="draft-note-component note-wrapper"
+ @mouseenter="setSelectedCommentPositionHover(draft.position.line_range)"
+ @mouseleave="setSelectedCommentPositionHover()"
+ >
<ul class="notes draft-notes">
<noteable-note
:note="draft"
- :diff-lines="diffFile.highlighted_diff_lines"
:line="line"
+ :discussion-root="true"
class="draft-note"
@handleEdit="handleEditing"
@cancelForm="handleNotEditing"
@@ -81,7 +86,7 @@ export default {
@handleUpdateNote="update"
@toggleResolveStatus="toggleResolveDiscussion(draft.id)"
>
- <strong slot="note-header-info" class="badge draft-pending-label append-right-4">
+ <strong slot="note-header-info" class="badge draft-pending-label gl-mr-2">
{{ __('Pending') }}
</strong>
</noteable-note>
diff --git a/app/assets/javascripts/batch_comments/components/parallel_draft_comment_row.vue b/app/assets/javascripts/batch_comments/components/parallel_draft_comment_row.vue
index 68fd20e56bc..b0916623cd2 100644
--- a/app/assets/javascripts/batch_comments/components/parallel_draft_comment_row.vue
+++ b/app/assets/javascripts/batch_comments/components/parallel_draft_comment_row.vue
@@ -35,11 +35,15 @@ export default {
<tr :class="className" class="notes_holder">
<td class="notes_line old"></td>
<td class="notes-content parallel old" colspan="2">
- <div v-if="leftDraft.isDraft" class="content"><draft-note :draft="leftDraft" /></div>
+ <div v-if="leftDraft.isDraft" class="content">
+ <draft-note :draft="leftDraft" :line="line.left" />
+ </div>
</td>
<td class="notes_line new"></td>
<td class="notes-content parallel new" colspan="2">
- <div v-if="rightDraft.isDraft" class="content"><draft-note :draft="rightDraft" /></div>
+ <div v-if="rightDraft.isDraft" class="content">
+ <draft-note :draft="rightDraft" :line="line.right" />
+ </div>
</td>
</tr>
</template>
diff --git a/app/assets/javascripts/batch_comments/components/preview_dropdown.vue b/app/assets/javascripts/batch_comments/components/preview_dropdown.vue
index 195e1b7ec5c..7520cc2401b 100644
--- a/app/assets/javascripts/batch_comments/components/preview_dropdown.vue
+++ b/app/assets/javascripts/batch_comments/components/preview_dropdown.vue
@@ -96,7 +96,7 @@ export default {
<preview-item :draft="draft" :is-last="isLast(index)" />
</li>
</ul>
- <gl-loading-icon v-else size="lg" class="prepend-top-default append-bottom-default" />
+ <gl-loading-icon v-else size="lg" class="gl-mt-3 gl-mb-3" />
</div>
<div class="dropdown-footer">
<publish-button
diff --git a/app/assets/javascripts/batch_comments/components/preview_item.vue b/app/assets/javascripts/batch_comments/components/preview_item.vue
index 22495eb4d7d..3162a83f099 100644
--- a/app/assets/javascripts/batch_comments/components/preview_item.vue
+++ b/app/assets/javascripts/batch_comments/components/preview_item.vue
@@ -52,14 +52,12 @@ export default {
});
},
linePosition() {
- if (this.draft.position && this.draft.position.position_type === IMAGE_DIFF_POSITION_TYPE) {
+ if (this.position?.position_type === IMAGE_DIFF_POSITION_TYPE) {
// eslint-disable-next-line @gitlab/require-i18n-strings
- return `${this.draft.position.x}x ${this.draft.position.y}y`;
+ return `${this.position.x}x ${this.position.y}y`;
}
- const position = this.discussion ? this.discussion.position : this.draft.position;
-
- return position?.new_line || position?.old_line;
+ return this.position?.new_line || this.position?.old_line;
},
content() {
const el = document.createElement('div');
@@ -70,11 +68,14 @@ export default {
showLinePosition() {
return this.draft.file_hash || this.isDiffDiscussion;
},
+ position() {
+ return this.draft.position || this.discussion.position;
+ },
startLineNumber() {
- return getStartLineNumber(this.draft.position?.line_range);
+ return getStartLineNumber(this.position?.line_range);
},
endLineNumber() {
- return getEndLineNumber(this.draft.position?.line_range);
+ return getEndLineNumber(this.position?.line_range);
},
},
methods: {
diff --git a/app/assets/javascripts/behaviors/collapse_sidebar_on_window_resize.js b/app/assets/javascripts/behaviors/collapse_sidebar_on_window_resize.js
new file mode 100644
index 00000000000..d9164f6204a
--- /dev/null
+++ b/app/assets/javascripts/behaviors/collapse_sidebar_on_window_resize.js
@@ -0,0 +1,41 @@
+import $ from 'jquery';
+import { GlBreakpointInstance as bp } from '@gitlab/ui/dist/utils';
+
+/**
+ * This behavior collapses the right sidebar
+ * if the window size changes
+ *
+ * @sentrify
+ */
+export default () => {
+ const $sidebarGutterToggle = $('.js-sidebar-toggle');
+ let bootstrapBreakpoint = bp.getBreakpointSize();
+
+ $(window).on('resize.app', () => {
+ const oldBootstrapBreakpoint = bootstrapBreakpoint;
+ bootstrapBreakpoint = bp.getBreakpointSize();
+
+ if (bootstrapBreakpoint !== oldBootstrapBreakpoint) {
+ const breakpointSizes = ['md', 'sm', 'xs'];
+
+ if (breakpointSizes.includes(bootstrapBreakpoint)) {
+ const $gutterIcon = $sidebarGutterToggle.find('i');
+ if ($gutterIcon.hasClass('fa-angle-double-right')) {
+ $sidebarGutterToggle.trigger('click');
+ }
+
+ const sidebarGutterVueToggleEl = document.querySelector('.js-sidebar-vue-toggle');
+
+ // Sidebar has an icon which corresponds to collapsing the sidebar
+ // only then trigger the click.
+ if (sidebarGutterVueToggleEl) {
+ const collapseIcon = sidebarGutterVueToggleEl.querySelector('i.fa-angle-double-right');
+
+ if (collapseIcon) {
+ collapseIcon.click();
+ }
+ }
+ }
+ }
+ });
+};
diff --git a/app/assets/javascripts/behaviors/gl_emoji.js b/app/assets/javascripts/behaviors/gl_emoji.js
index d1d75658181..bcf732e9522 100644
--- a/app/assets/javascripts/behaviors/gl_emoji.js
+++ b/app/assets/javascripts/behaviors/gl_emoji.js
@@ -1,47 +1,69 @@
import 'document-register-element';
import isEmojiUnicodeSupported from '../emoji/support';
+import { initEmojiMap, getEmojiInfo, emojiFallbackImageSrc, emojiImageTag } from '../emoji';
class GlEmoji extends HTMLElement {
constructor() {
super();
- const emojiUnicode = this.textContent.trim();
- const { name, unicodeVersion, fallbackSrc, fallbackSpriteClass } = this.dataset;
-
- const isEmojiUnicode =
- this.childNodes &&
- Array.prototype.every.call(this.childNodes, childNode => childNode.nodeType === 3);
- const hasImageFallback = fallbackSrc && fallbackSrc.length > 0;
- const hasCssSpriteFalback = fallbackSpriteClass && fallbackSpriteClass.length > 0;
-
- if (emojiUnicode && isEmojiUnicode && !isEmojiUnicodeSupported(emojiUnicode, unicodeVersion)) {
- // CSS sprite fallback takes precedence over image fallback
- if (hasCssSpriteFalback) {
- if (!gon.emoji_sprites_css_added && gon.emoji_sprites_css_path) {
- const emojiSpriteLinkTag = document.createElement('link');
- emojiSpriteLinkTag.setAttribute('rel', 'stylesheet');
- emojiSpriteLinkTag.setAttribute('href', gon.emoji_sprites_css_path);
- document.head.appendChild(emojiSpriteLinkTag);
- gon.emoji_sprites_css_added = true;
+ this.initialize();
+ }
+ initialize() {
+ let emojiUnicode = this.textContent.trim();
+ const { fallbackSpriteClass, fallbackSrc } = this.dataset;
+ let { name, unicodeVersion } = this.dataset;
+
+ return initEmojiMap().then(() => {
+ if (!unicodeVersion) {
+ const emojiInfo = getEmojiInfo(name);
+
+ if (emojiInfo) {
+ if (name !== emojiInfo.name) {
+ ({ name } = emojiInfo);
+ this.dataset.name = emojiInfo.name;
+ }
+ unicodeVersion = emojiInfo.u;
+ this.dataset.unicodeVersion = unicodeVersion;
+
+ emojiUnicode = emojiInfo.e;
+ this.innerHTML = emojiInfo.e;
+
+ this.title = emojiInfo.d;
+ }
+ }
+
+ const isEmojiUnicode =
+ this.childNodes &&
+ Array.prototype.every.call(this.childNodes, childNode => childNode.nodeType === 3);
+
+ if (
+ emojiUnicode &&
+ isEmojiUnicode &&
+ !isEmojiUnicodeSupported(emojiUnicode, unicodeVersion)
+ ) {
+ const hasImageFallback = fallbackSrc && fallbackSrc.length > 0;
+ const hasCssSpriteFallback = fallbackSpriteClass && fallbackSpriteClass.length > 0;
+
+ // CSS sprite fallback takes precedence over image fallback
+ if (hasCssSpriteFallback) {
+ if (!gon.emoji_sprites_css_added && gon.emoji_sprites_css_path) {
+ const emojiSpriteLinkTag = document.createElement('link');
+ emojiSpriteLinkTag.setAttribute('rel', 'stylesheet');
+ emojiSpriteLinkTag.setAttribute('href', gon.emoji_sprites_css_path);
+ document.head.appendChild(emojiSpriteLinkTag);
+ gon.emoji_sprites_css_added = true;
+ }
+ // IE 11 doesn't like adding multiple at once :(
+ this.classList.add('emoji-icon');
+ this.classList.add(fallbackSpriteClass);
+ } else if (hasImageFallback) {
+ this.innerHTML = emojiImageTag(name, fallbackSrc);
+ } else {
+ const src = emojiFallbackImageSrc(name);
+ this.innerHTML = emojiImageTag(name, src);
}
- // IE 11 doesn't like adding multiple at once :(
- this.classList.add('emoji-icon');
- this.classList.add(fallbackSpriteClass);
- } else {
- import(/* webpackChunkName: 'emoji' */ '../emoji')
- .then(({ emojiImageTag, emojiFallbackImageSrc }) => {
- if (hasImageFallback) {
- this.innerHTML = emojiImageTag(name, fallbackSrc);
- } else {
- const src = emojiFallbackImageSrc(name);
- this.innerHTML = emojiImageTag(name, src);
- }
- })
- .catch(() => {
- // do nothing
- });
}
- }
+ });
}
}
diff --git a/app/assets/javascripts/behaviors/index.js b/app/assets/javascripts/behaviors/index.js
index 8c4eccc34a3..8060938c72a 100644
--- a/app/assets/javascripts/behaviors/index.js
+++ b/app/assets/javascripts/behaviors/index.js
@@ -11,9 +11,13 @@ import './requires_input';
import initPageShortcuts from './shortcuts';
import './toggler_behavior';
import './preview_markdown';
+import initCollapseSidebarOnWindowResize from './collapse_sidebar_on_window_resize';
+import initSelect2Dropdowns from './select2';
installGlEmojiElement();
initGFMInput();
initCopyAsGFM();
initCopyToClipboard();
initPageShortcuts();
+initCollapseSidebarOnWindowResize();
+initSelect2Dropdowns();
diff --git a/app/assets/javascripts/behaviors/markdown/copy_as_gfm.js b/app/assets/javascripts/behaviors/markdown/copy_as_gfm.js
index 03c1b5a0169..bbcfa50ba35 100644
--- a/app/assets/javascripts/behaviors/markdown/copy_as_gfm.js
+++ b/app/assets/javascripts/behaviors/markdown/copy_as_gfm.js
@@ -1,5 +1,5 @@
import $ from 'jquery';
-import { getSelectedFragment } from '~/lib/utils/common_utils';
+import { getSelectedFragment, insertText } from '~/lib/utils/common_utils';
export class CopyAsGFM {
constructor() {
@@ -79,7 +79,7 @@ export class CopyAsGFM {
}
static insertPastedText(target, text, gfm) {
- window.gl.utils.insertText(target, textBefore => {
+ insertText(target, textBefore => {
// If the text before the cursor contains an odd number of backticks,
// we are either inside an inline code span that starts with 1 backtick
// or a code block that starts with 3 backticks.
diff --git a/app/assets/javascripts/behaviors/markdown/render_mermaid.js b/app/assets/javascripts/behaviors/markdown/render_mermaid.js
index e4c69a114e0..94033e914ef 100644
--- a/app/assets/javascripts/behaviors/markdown/render_mermaid.js
+++ b/app/assets/javascripts/behaviors/markdown/render_mermaid.js
@@ -174,7 +174,7 @@ export default function renderMermaid($els) {
if (!$els.length) return;
const visibleMermaids = $els.filter(function filter() {
- return $(this).closest('details').length === 0;
+ return $(this).closest('details').length === 0 && $(this).is(':visible');
});
renderMermaids(visibleMermaids);
diff --git a/app/assets/javascripts/behaviors/select2.js b/app/assets/javascripts/behaviors/select2.js
new file mode 100644
index 00000000000..37b75bb5e56
--- /dev/null
+++ b/app/assets/javascripts/behaviors/select2.js
@@ -0,0 +1,23 @@
+import $ from 'jquery';
+
+export default () => {
+ if ($('select.select2').length) {
+ import(/* webpackChunkName: 'select2' */ 'select2/select2')
+ .then(() => {
+ $('select.select2').select2({
+ width: 'resolve',
+ minimumResultsForSearch: 10,
+ dropdownAutoWidth: true,
+ });
+
+ // Close select2 on escape
+ $('.js-select2').on('select2-close', () => {
+ setTimeout(() => {
+ $('.select2-container-active').removeClass('select2-container-active');
+ $(':focus').blur();
+ }, 1);
+ });
+ })
+ .catch(() => {});
+ }
+};
diff --git a/app/assets/javascripts/behaviors/shortcuts/shortcuts_toggle.vue b/app/assets/javascripts/behaviors/shortcuts/shortcuts_toggle.vue
index a53b1b06be9..8418c0f66ac 100644
--- a/app/assets/javascripts/behaviors/shortcuts/shortcuts_toggle.vue
+++ b/app/assets/javascripts/behaviors/shortcuts/shortcuts_toggle.vue
@@ -1,11 +1,10 @@
<script>
-import { GlToggle, GlSprintf } from '@gitlab/ui';
+import { GlToggle } from '@gitlab/ui';
import AccessorUtilities from '~/lib/utils/accessor';
import { disableShortcuts, enableShortcuts, shouldDisableShortcuts } from './shortcuts_toggle';
export default {
components: {
- GlSprintf,
GlToggle,
},
data() {
@@ -32,29 +31,10 @@ export default {
<gl-toggle
v-model="shortcutsEnabled"
aria-describedby="shortcutsToggle"
- class="prepend-left-10 mb-0"
- label-position="right"
+ label="Keyboard shortcuts"
+ label-position="left"
@change="onChange"
- >
- <template #labelOn>
- <gl-sprintf
- :message="__('%{screenreaderOnlyStart}Keyboard shorcuts%{screenreaderOnlyEnd} Enabled')"
- >
- <template #screenreaderOnly="{ content }">
- <span class="sr-only">{{ content }}</span>
- </template>
- </gl-sprintf>
- </template>
- <template #labelOff>
- <gl-sprintf
- :message="__('%{screenreaderOnlyStart}Keyboard shorcuts%{screenreaderOnlyEnd} Disabled')"
- >
- <template #screenreaderOnly="{ content }">
- <span class="sr-only">{{ content }}</span>
- </template>
- </gl-sprintf>
- </template>
- </gl-toggle>
+ />
<div id="shortcutsToggle" class="sr-only">{{ __('Enable or disable keyboard shortcuts') }}</div>
</div>
</template>
diff --git a/app/assets/javascripts/blob/components/blob_header_viewer_switcher.vue b/app/assets/javascripts/blob/components/blob_header_viewer_switcher.vue
index ed03213d7cf..5b15fe2d7cc 100644
--- a/app/assets/javascripts/blob/components/blob_header_viewer_switcher.vue
+++ b/app/assets/javascripts/blob/components/blob_header_viewer_switcher.vue
@@ -1,5 +1,5 @@
<script>
-import { GlDeprecatedButton, GlButtonGroup, GlIcon, GlTooltipDirective } from '@gitlab/ui';
+import { GlButton, GlButtonGroup, GlIcon, GlTooltipDirective } from '@gitlab/ui';
import {
RICH_BLOB_VIEWER,
RICH_BLOB_VIEWER_TITLE,
@@ -11,7 +11,7 @@ export default {
components: {
GlIcon,
GlButtonGroup,
- GlDeprecatedButton,
+ GlButton,
},
directives: {
GlTooltip: GlTooltipDirective,
@@ -46,7 +46,7 @@ export default {
</script>
<template>
<gl-button-group class="js-blob-viewer-switcher mx-2">
- <gl-deprecated-button
+ <gl-button
v-gl-tooltip.hover
:aria-label="$options.SIMPLE_BLOB_VIEWER_TITLE"
:title="$options.SIMPLE_BLOB_VIEWER_TITLE"
@@ -55,8 +55,8 @@ export default {
@click="switchToViewer($options.SIMPLE_BLOB_VIEWER)"
>
<gl-icon name="code" :size="14" />
- </gl-deprecated-button>
- <gl-deprecated-button
+ </gl-button>
+ <gl-button
v-gl-tooltip.hover
:aria-label="$options.RICH_BLOB_VIEWER_TITLE"
:title="$options.RICH_BLOB_VIEWER_TITLE"
@@ -65,6 +65,6 @@ export default {
@click="switchToViewer($options.RICH_BLOB_VIEWER)"
>
<gl-icon name="document" :size="14" />
- </gl-deprecated-button>
+ </gl-button>
</gl-button-group>
</template>
diff --git a/app/assets/javascripts/blob/components/constants.js b/app/assets/javascripts/blob/components/constants.js
index 93dceacabdd..0137bd38d28 100644
--- a/app/assets/javascripts/blob/components/constants.js
+++ b/app/assets/javascripts/blob/components/constants.js
@@ -25,7 +25,7 @@ export const BLOB_RENDER_ERRORS = {
TOO_LARGE: {
id: 'too_large',
text: sprintf(__('it is larger than %{limit}'), {
- limit: numberToHumanSize(104857600), // 100MB in bytes
+ limit: numberToHumanSize(10485760), // 10MB in bytes
}),
},
EXTERNAL: {
diff --git a/app/assets/javascripts/blob/notebook/notebook_viewer.vue b/app/assets/javascripts/blob/notebook/notebook_viewer.vue
index 401fe9beb62..b1713989997 100644
--- a/app/assets/javascripts/blob/notebook/notebook_viewer.vue
+++ b/app/assets/javascripts/blob/notebook/notebook_viewer.vue
@@ -62,9 +62,7 @@ export default {
</script>
<template>
- <div
- class="js-notebook-viewer-mounted container-fluid md prepend-top-default append-bottom-default"
- >
+ <div class="js-notebook-viewer-mounted container-fluid md gl-mt-3 gl-mb-3">
<div v-if="loading && !error" class="text-center loading">
<gl-loading-icon class="mt-5" size="lg" />
</div>
diff --git a/app/assets/javascripts/blob/pdf/pdf_viewer.vue b/app/assets/javascripts/blob/pdf/pdf_viewer.vue
index 5eaddfc099a..64fc832ee54 100644
--- a/app/assets/javascripts/blob/pdf/pdf_viewer.vue
+++ b/app/assets/javascripts/blob/pdf/pdf_viewer.vue
@@ -34,7 +34,7 @@ export default {
</script>
<template>
- <div class="js-pdf-viewer container-fluid md prepend-top-default append-bottom-default">
+ <div class="js-pdf-viewer container-fluid md gl-mt-3 gl-mb-3">
<div v-if="loading && !error" class="text-center loading">
<gl-loading-icon class="mt-5" size="lg" />
</div>
diff --git a/app/assets/javascripts/blob/sketch/index.js b/app/assets/javascripts/blob/sketch/index.js
index dbff03dc734..767e205fcaa 100644
--- a/app/assets/javascripts/blob/sketch/index.js
+++ b/app/assets/javascripts/blob/sketch/index.js
@@ -56,7 +56,7 @@ export default class SketchLoader {
error() {
const errorMsg = document.createElement('p');
- errorMsg.className = 'prepend-top-default append-bottom-default text-center';
+ errorMsg.className = 'gl-mt-3 gl-mb-3 text-center';
errorMsg.textContent = __(`
Cannot show preview. For previews on sketch files, they must have the file format
introduced by Sketch version 43 and above.
diff --git a/app/assets/javascripts/blob/suggest_gitlab_ci_yml/components/popover.vue b/app/assets/javascripts/blob/suggest_gitlab_ci_yml/components/popover.vue
index 1e9e36feecc..932b6e8a0f7 100644
--- a/app/assets/javascripts/blob/suggest_gitlab_ci_yml/components/popover.vue
+++ b/app/assets/javascripts/blob/suggest_gitlab_ci_yml/components/popover.vue
@@ -2,7 +2,6 @@
import { GlPopover, GlSprintf, GlDeprecatedButton, GlIcon } from '@gitlab/ui';
import { parseBoolean, scrollToElement, setCookie, getCookie } from '~/lib/utils/common_utils';
import { s__ } from '~/locale';
-import { glEmojiTag } from '~/emoji';
import Tracking from '~/tracking';
const trackingMixin = Tracking.mixin();
@@ -11,14 +10,16 @@ const popoverStates = {
suggest_gitlab_ci_yml: {
title: s__(`suggestPipeline|1/2: Choose a template`),
content: s__(
- `suggestPipeline|We recommend the %{boldStart}Code Quality%{boldEnd} template, which will add a report widget to your Merge Requests. This way you’ll learn about code quality degradations much sooner. %{footerStart} Goodbye technical debt! %{footerEnd}`,
+ `suggestPipeline|We’re adding a GitLab CI configuration file to add a pipeline to the project. You could create it manually, but we recommend that you start with a GitLab template that works out of the box.`,
+ ),
+ footer: s__(
+ `suggestPipeline|Choose %{boldStart}Code Quality%{boldEnd} to add a pipeline that tests the quality of your code.`,
),
- emoji: glEmojiTag('wave'),
},
suggest_commit_first_project_gitlab_ci_yml: {
title: s__(`suggestPipeline|2/2: Commit your changes`),
content: s__(
- `suggestPipeline|Commit the changes and your pipeline will automatically run for the first time.`,
+ `suggestPipeline|The template is ready! You can now commit it to create your first pipeline.`,
),
},
};
@@ -66,6 +67,9 @@ export default {
suggestContent() {
return popoverStates[this.trackLabel].content || '';
},
+ suggestFooter() {
+ return popoverStates[this.trackLabel].footer || '';
+ },
emoji() {
return popoverStates[this.trackLabel].emoji || '';
},
@@ -123,16 +127,13 @@ export default {
</span>
</template>
- <gl-sprintf :message="suggestContent">
- <template #bold="{content}">
- <strong> {{ content }} </strong>
- </template>
- <template #footer="{content}">
- <div class="mt-3">
- {{ content }}
- <span v-html="emoji"></span>
- </div>
- </template>
- </gl-sprintf>
+ <gl-sprintf :message="suggestContent" />
+ <div class="mt-3">
+ <gl-sprintf :message="suggestFooter">
+ <template #bold="{ content }">
+ <strong> {{ content }} </strong>
+ </template>
+ </gl-sprintf>
+ </div>
</gl-popover>
</template>
diff --git a/app/assets/javascripts/blob/viewer/index.js b/app/assets/javascripts/blob/viewer/index.js
index 3ac419557eb..b18faea628a 100644
--- a/app/assets/javascripts/blob/viewer/index.js
+++ b/app/assets/javascripts/blob/viewer/index.js
@@ -3,6 +3,7 @@ import '~/behaviors/markdown/render_gfm';
import Flash from '../../flash';
import { handleLocationHash } from '../../lib/utils/common_utils';
import axios from '../../lib/utils/axios_utils';
+import eventHub from '../../notes/event_hub';
import { __ } from '~/locale';
const loadRichBlobViewer = type => {
@@ -178,6 +179,10 @@ export default class BlobViewer {
viewer.innerHTML = data.html;
viewer.setAttribute('data-loaded', 'true');
+ if (window.gon?.features?.codeNavigation) {
+ eventHub.$emit('showBlobInteractionZones', viewer.dataset.path);
+ }
+
return viewer;
});
}
diff --git a/app/assets/javascripts/blob_edit/blob_bundle.js b/app/assets/javascripts/blob_edit/blob_bundle.js
index 95b84497de3..9b9ade28623 100644
--- a/app/assets/javascripts/blob_edit/blob_bundle.js
+++ b/app/assets/javascripts/blob_edit/blob_bundle.js
@@ -5,7 +5,7 @@ import NewCommitForm from '../new_commit_form';
import EditBlob from './edit_blob';
import BlobFileDropzone from '../blob/blob_file_dropzone';
import initPopover from '~/blob/suggest_gitlab_ci_yml';
-import { setCookie } from '~/lib/utils/common_utils';
+import { disableButtonIfEmptyField, setCookie } from '~/lib/utils/common_utils';
import Tracking from '~/tracking';
export default () => {
@@ -51,10 +51,7 @@ export default () => {
new BlobFileDropzone(uploadBlobForm, method);
new NewCommitForm(uploadBlobForm);
- window.gl.utils.disableButtonIfEmptyField(
- uploadBlobForm.find('.js-commit-message'),
- '.btn-upload-file',
- );
+ disableButtonIfEmptyField(uploadBlobForm.find('.js-commit-message'), '.btn-upload-file');
}
if (deleteBlobForm.length) {
diff --git a/app/assets/javascripts/blob_edit/constants.js b/app/assets/javascripts/blob_edit/constants.js
new file mode 100644
index 00000000000..a19da2098cf
--- /dev/null
+++ b/app/assets/javascripts/blob_edit/constants.js
@@ -0,0 +1,4 @@
+import { __ } from '~/locale';
+
+export const BLOB_EDITOR_ERROR = __('An error occurred while rendering the editor');
+export const BLOB_PREVIEW_ERROR = __('An error occurred previewing the blob');
diff --git a/app/assets/javascripts/blob_edit/edit_blob.js b/app/assets/javascripts/blob_edit/edit_blob.js
index 011898a5e7a..7e5be8454fe 100644
--- a/app/assets/javascripts/blob_edit/edit_blob.js
+++ b/app/assets/javascripts/blob_edit/edit_blob.js
@@ -3,39 +3,87 @@
import $ from 'jquery';
import axios from '~/lib/utils/axios_utils';
import createFlash from '~/flash';
-import { __ } from '~/locale';
+import { BLOB_EDITOR_ERROR, BLOB_PREVIEW_ERROR } from './constants';
import TemplateSelectorMediator from '../blob/file_template_mediator';
import getModeByFileExtension from '~/lib/utils/ace_utils';
import { addEditorMarkdownListeners } from '~/lib/utils/text_markdown';
+const monacoEnabledGlobally = window.gon.features?.monacoBlobs;
+
export default class EditBlob {
// The options object has:
// assetsPath, filePath, currentAction, projectId, isMarkdown
constructor(options) {
this.options = options;
- this.configureAceEditor();
- this.initModePanesAndLinks();
- this.initSoftWrap();
- this.initFileSelectors();
+ this.options.monacoEnabled = this.options.monacoEnabled ?? monacoEnabledGlobally;
+ const { isMarkdown, monacoEnabled } = this.options;
+ return Promise.resolve()
+ .then(() => {
+ return monacoEnabled ? this.configureMonacoEditor() : this.configureAceEditor();
+ })
+ .then(() => {
+ this.initModePanesAndLinks();
+ this.initFileSelectors();
+ this.initSoftWrap();
+ if (isMarkdown) {
+ addEditorMarkdownListeners(this.editor);
+ }
+ this.editor.focus();
+ })
+ .catch(() => createFlash(BLOB_EDITOR_ERROR));
+ }
+
+ configureMonacoEditor() {
+ const EditorPromise = import(
+ /* webpackChunkName: 'monaco_editor_lite' */ '~/editor/editor_lite'
+ );
+ const MarkdownExtensionPromise = this.options.isMarkdown
+ ? import('~/editor/editor_markdown_ext')
+ : Promise.resolve(false);
+
+ return Promise.all([EditorPromise, MarkdownExtensionPromise])
+ .then(([EditorModule, MarkdownExtension]) => {
+ const EditorLite = EditorModule.default;
+ const editorEl = document.getElementById('editor');
+ const fileNameEl =
+ document.getElementById('file_path') || document.getElementById('file_name');
+ const fileContentEl = document.getElementById('file-content');
+ const form = document.querySelector('.js-edit-blob-form');
+
+ this.editor = new EditorLite();
+
+ if (MarkdownExtension) {
+ this.editor.use(MarkdownExtension.default);
+ }
+
+ this.editor.createInstance({
+ el: editorEl,
+ blobPath: fileNameEl.value,
+ blobContent: editorEl.innerText,
+ });
+
+ fileNameEl.addEventListener('change', () => {
+ this.editor.updateModelLanguage(fileNameEl.value);
+ });
+
+ form.addEventListener('submit', () => {
+ fileContentEl.value = this.editor.getValue();
+ });
+ })
+ .catch(() => createFlash(BLOB_EDITOR_ERROR));
}
configureAceEditor() {
- const { filePath, assetsPath, isMarkdown } = this.options;
+ const { filePath, assetsPath } = this.options;
ace.config.set('modePath', `${assetsPath}/ace`);
ace.config.loadModule('ace/ext/searchbox');
ace.config.loadModule('ace/ext/modelist');
this.editor = ace.edit('editor');
- if (isMarkdown) {
- addEditorMarkdownListeners(this.editor);
- }
-
// This prevents warnings re: automatic scrolling being logged
this.editor.$blockScrolling = Infinity;
- this.editor.focus();
-
if (filePath) {
this.editor.getSession().setMode(getModeByFileExtension(filePath));
}
@@ -81,7 +129,7 @@ export default class EditBlob {
currentPane.empty().append(data);
currentPane.renderGFM();
})
- .catch(() => createFlash(__('An error occurred previewing the blob')));
+ .catch(() => createFlash(BLOB_PREVIEW_ERROR));
}
this.$toggleButton.show();
@@ -90,14 +138,19 @@ export default class EditBlob {
}
initSoftWrap() {
- this.isSoftWrapped = false;
+ this.isSoftWrapped = Boolean(this.options.monacoEnabled);
this.$toggleButton = $('.soft-wrap-toggle');
+ this.$toggleButton.toggleClass('soft-wrap-active', this.isSoftWrapped);
this.$toggleButton.on('click', () => this.toggleSoftWrap());
}
toggleSoftWrap() {
this.isSoftWrapped = !this.isSoftWrapped;
this.$toggleButton.toggleClass('soft-wrap-active', this.isSoftWrapped);
- this.editor.getSession().setUseWrapMode(this.isSoftWrapped);
+ if (this.options.monacoEnabled) {
+ this.editor.updateOptions({ wordWrap: this.isSoftWrapped ? 'on' : 'off' });
+ } else {
+ this.editor.getSession().setUseWrapMode(this.isSoftWrapped);
+ }
}
}
diff --git a/app/assets/javascripts/boards/components/board.js b/app/assets/javascripts/boards/components/board.js
deleted file mode 100644
index 517a13ceb27..00000000000
--- a/app/assets/javascripts/boards/components/board.js
+++ /dev/null
@@ -1,192 +0,0 @@
-import $ from 'jquery';
-import Sortable from 'sortablejs';
-import Vue from 'vue';
-import { GlButtonGroup, GlDeprecatedButton, GlLabel, GlTooltip } from '@gitlab/ui';
-import isWipLimitsOn from 'ee_else_ce/boards/mixins/is_wip_limits';
-import { s__, __, sprintf } from '~/locale';
-import Icon from '~/vue_shared/components/icon.vue';
-import Tooltip from '~/vue_shared/directives/tooltip';
-import AccessorUtilities from '../../lib/utils/accessor';
-import BoardBlankState from './board_blank_state.vue';
-import BoardDelete from './board_delete';
-import BoardList from './board_list.vue';
-import IssueCount from './issue_count.vue';
-import boardsStore from '../stores/boards_store';
-import { getBoardSortableDefaultOptions, sortableEnd } from '../mixins/sortable_default_options';
-import { ListType } from '../constants';
-import { isScopedLabel } from '~/lib/utils/common_utils';
-
-/**
- * Please don't edit this file, have a look at:
- * ./board_column.vue
- * https://gitlab.com/gitlab-org/gitlab/-/issues/212300
- *
- * This file here will be deleted soon
- * @deprecated
- */
-export default Vue.extend({
- components: {
- BoardBlankState,
- BoardDelete,
- BoardList,
- Icon,
- GlButtonGroup,
- IssueCount,
- GlDeprecatedButton,
- GlLabel,
- GlTooltip,
- },
- directives: {
- Tooltip,
- },
- mixins: [isWipLimitsOn],
- props: {
- list: {
- type: Object,
- default: () => ({}),
- required: false,
- },
- disabled: {
- type: Boolean,
- required: true,
- },
- issueLinkBase: {
- type: String,
- required: true,
- },
- rootPath: {
- type: String,
- required: true,
- },
- boardId: {
- type: String,
- required: true,
- },
- // Does not do anything but is used
- // to support the API of the new board_column.vue
- canAdminList: {
- type: Boolean,
- required: false,
- default: false,
- },
- },
- data() {
- return {
- detailIssue: boardsStore.detail,
- filter: boardsStore.filter,
- };
- },
- computed: {
- isLoggedIn() {
- return Boolean(gon.current_user_id);
- },
- showListHeaderButton() {
- return (
- !this.disabled && this.list.type !== ListType.closed && this.list.type !== ListType.blank
- );
- },
- issuesTooltip() {
- const { issuesSize } = this.list;
-
- return sprintf(__('%{issuesSize} issues'), { issuesSize });
- },
- // Only needed to make karma pass.
- weightCountToolTip() {}, // eslint-disable-line vue/return-in-computed-property
- caretTooltip() {
- return this.list.isExpanded ? s__('Boards|Collapse') : s__('Boards|Expand');
- },
- isNewIssueShown() {
- return this.list.type === ListType.backlog || this.showListHeaderButton;
- },
- isSettingsShown() {
- return (
- this.list.type !== ListType.backlog &&
- this.showListHeaderButton &&
- this.list.isExpanded &&
- this.isWipLimitsOn
- );
- },
- showBoardListAndBoardInfo() {
- return this.list.type !== ListType.blank && this.list.type !== ListType.promotion;
- },
- uniqueKey() {
- // eslint-disable-next-line @gitlab/require-i18n-strings
- return `boards.${this.boardId}.${this.list.type}.${this.list.id}`;
- },
- },
- watch: {
- filter: {
- handler() {
- this.list.page = 1;
- this.list.getIssues(true).catch(() => {
- // TODO: handle request error
- });
- },
- deep: true,
- },
- },
- mounted() {
- const instance = this;
-
- const sortableOptions = getBoardSortableDefaultOptions({
- disabled: this.disabled,
- group: 'boards',
- draggable: '.is-draggable',
- handle: '.js-board-handle',
- onEnd(e) {
- sortableEnd();
-
- const sortable = this;
-
- if (e.newIndex !== undefined && e.oldIndex !== e.newIndex) {
- const order = sortable.toArray();
- const list = boardsStore.findList('id', parseInt(e.item.dataset.id, 10));
-
- instance.$nextTick(() => {
- boardsStore.moveList(list, order);
- });
- }
- },
- });
-
- Sortable.create(this.$el.parentNode, sortableOptions);
- },
- created() {
- if (
- this.list.isExpandable &&
- AccessorUtilities.isLocalStorageAccessSafe() &&
- !this.isLoggedIn
- ) {
- const isCollapsed = localStorage.getItem(`${this.uniqueKey}.expanded`) === 'false';
-
- this.list.isExpanded = !isCollapsed;
- }
- },
- methods: {
- showScopedLabels(label) {
- return boardsStore.scopedLabels.enabled && isScopedLabel(label);
- },
-
- showNewIssueForm() {
- this.$refs['board-list'].showIssueForm = !this.$refs['board-list'].showIssueForm;
- },
- toggleExpanded() {
- if (this.list.isExpandable) {
- this.list.isExpanded = !this.list.isExpanded;
-
- if (AccessorUtilities.isLocalStorageAccessSafe() && !this.isLoggedIn) {
- localStorage.setItem(`${this.uniqueKey}.expanded`, this.list.isExpanded);
- }
-
- if (this.isLoggedIn) {
- this.list.update();
- }
-
- // When expanding/collapsing, the tooltip on the caret button sometimes stays open.
- // Close all tooltips manually to prevent dangling tooltips.
- $('.tooltip').tooltip('hide');
- }
- },
- },
- template: '#js-board-template',
-});
diff --git a/app/assets/javascripts/boards/components/board_content.vue b/app/assets/javascripts/boards/components/board_content.vue
index f0497ea0b64..6ac7fdce6a7 100644
--- a/app/assets/javascripts/boards/components/board_content.vue
+++ b/app/assets/javascripts/boards/components/board_content.vue
@@ -54,7 +54,7 @@ export default {
<div>
<div
v-if="!isSwimlanesOn"
- class="boards-list w-100 py-3 px-2 text-nowrap"
+ class="boards-list gl-w-full gl-py-5 gl-px-3 gl-white-space-nowrap"
data-qa-selector="boards_list"
>
<board-column
@@ -77,6 +77,7 @@ export default {
:can-admin-list="canAdminList"
:disabled="disabled"
:board-id="boardId"
+ :group-id="groupId"
/>
</div>
</template>
diff --git a/app/assets/javascripts/boards/components/board_list_header.vue b/app/assets/javascripts/boards/components/board_list_header.vue
index eb12617a66e..02a04cb4e46 100644
--- a/app/assets/javascripts/boards/components/board_list_header.vue
+++ b/app/assets/javascripts/boards/components/board_list_header.vue
@@ -5,10 +5,11 @@ import {
GlLabel,
GlTooltip,
GlIcon,
+ GlSprintf,
GlTooltipDirective,
} from '@gitlab/ui';
import isWipLimitsOn from 'ee_else_ce/boards/mixins/is_wip_limits';
-import { s__, __, sprintf } from '~/locale';
+import { n__, s__ } from '~/locale';
import AccessorUtilities from '../../lib/utils/accessor';
import BoardDelete from './board_delete';
import IssueCount from './issue_count.vue';
@@ -25,6 +26,7 @@ export default {
GlLabel,
GlTooltip,
GlIcon,
+ GlSprintf,
IssueCount,
},
directives: {
@@ -82,10 +84,20 @@ export default {
this.listType !== ListType.promotion
);
},
- issuesTooltip() {
+ showMilestoneListDetails() {
+ return (
+ this.list.type === 'milestone' &&
+ this.list.milestone &&
+ (this.list.isExpanded || !this.isSwimlanesHeader)
+ );
+ },
+ showAssigneeListDetails() {
+ return this.list.type === 'assignee' && (this.list.isExpanded || !this.isSwimlanesHeader);
+ },
+ issuesTooltipLabel() {
const { issuesSize } = this.list;
- return sprintf(__('%{issuesSize} issues'), { issuesSize });
+ return n__(`%d issue`, `%d issues`, issuesSize);
},
chevronTooltip() {
return this.list.isExpanded ? s__('Boards|Collapse') : s__('Boards|Expand');
@@ -111,6 +123,9 @@ export default {
// eslint-disable-next-line @gitlab/require-i18n-strings
return `boards.${this.boardId}.${this.listType}.${this.list.id}`;
},
+ collapsedTooltipTitle() {
+ return this.listTitle || this.listAssignee;
+ },
},
methods: {
showScopedLabels(label) {
@@ -147,7 +162,7 @@ export default {
'has-border': list.label && list.label.color,
'gl-relative': list.isExpanded,
'gl-h-full': !list.isExpanded,
- 'board-inner gl-rounded-base gl-border-b-0': isSwimlanesHeader,
+ 'board-inner gl-rounded-top-left-base gl-rounded-top-right-base': isSwimlanesHeader,
}"
:style="{ borderTopColor: list.label && list.label.color ? list.label.color : null }"
class="board-header gl-relative"
@@ -157,7 +172,9 @@ export default {
<h3
:class="{
'user-can-drag': !disabled && !list.preset,
- 'gl-border-b-0': !list.isExpanded,
+ 'gl-py-3': !list.isExpanded && !isSwimlanesHeader,
+ 'gl-border-b-0': !list.isExpanded || isSwimlanesHeader,
+ 'gl-py-2': !list.isExpanded && isSwimlanesHeader,
}"
class="board-title gl-m-0 gl-display-flex js-board-handle"
>
@@ -167,21 +184,17 @@ export default {
:aria-label="chevronTooltip"
:title="chevronTooltip"
:icon="chevronIcon"
- class="board-title-caret no-drag"
+ class="board-title-caret no-drag gl-cursor-pointer"
variant="link"
@click="toggleExpanded"
/>
<!-- The following is only true in EE and if it is a milestone -->
- <span
- v-if="list.type === 'milestone' && list.milestone"
- aria-hidden="true"
- class="gl-mr-2 milestone-icon"
- >
+ <span v-if="showMilestoneListDetails" aria-hidden="true" class="gl-mr-2 milestone-icon">
<gl-icon name="timer" />
</span>
<a
- v-if="list.type === 'assignee'"
+ v-if="showAssigneeListDetails"
:href="list.assignee.path"
class="user-avatar-link js-no-trigger"
>
@@ -195,7 +208,10 @@ export default {
width="20"
/>
</a>
- <div class="board-title-text">
+ <div
+ class="board-title-text"
+ :class="{ 'gl-display-none': !list.isExpanded && isSwimlanesHeader }"
+ >
<span
v-if="list.type !== 'label'"
v-gl-tooltip.hover
@@ -208,7 +224,7 @@ export default {
{{ list.title }}
</span>
<span v-if="list.type === 'assignee'" class="board-title-sub-text gl-ml-2">
- @{{ list.assignee.username }}
+ @{{ listAssignee }}
</span>
<gl-label
v-if="list.type === 'label'"
@@ -220,6 +236,33 @@ export default {
:title="list.label.title"
/>
</div>
+
+ <span
+ v-if="isSwimlanesHeader && !list.isExpanded"
+ ref="collapsedInfo"
+ aria-hidden="true"
+ class="board-header-collapsed-info-icon gl-mt-2 gl-cursor-pointer gl-text-gray-700"
+ >
+ <gl-icon name="information" />
+ </span>
+ <gl-tooltip v-if="isSwimlanesHeader && !list.isExpanded" :target="() => $refs.collapsedInfo">
+ <div class="gl-font-weight-bold gl-pb-2">{{ collapsedTooltipTitle }}</div>
+ <div v-if="list.maxIssueCount !== 0">
+ &#8226;
+ <gl-sprintf :message="__('%{issuesSize} with a limit of %{maxIssueCount}')">
+ <template #issuesSize>{{ issuesTooltipLabel }}</template>
+ <template #maxIssueCount>{{ list.maxIssueCount }}</template>
+ </gl-sprintf>
+ </div>
+ <div v-else>&#8226; {{ issuesTooltipLabel }}</div>
+ <div v-if="weightFeatureAvailable">
+ &#8226;
+ <gl-sprintf :message="__('%{totalWeight} total weight')">
+ <template #totalWeight>{{ list.totalWeight }}</template>
+ </gl-sprintf>
+ </div>
+ </gl-tooltip>
+
<board-delete
v-if="canAdminList && !list.preset && list.id"
:list="list"
@@ -229,7 +272,7 @@ export default {
v-gl-tooltip.hover.bottom
:class="{ 'gl-display-none': !list.isExpanded }"
:aria-label="__('Delete list')"
- class="board-delete no-drag gl-pr-0 gl-shadow-none gl-mr-3"
+ class="board-delete no-drag gl-pr-0 gl-shadow-none! gl-mr-3"
:title="__('Delete list')"
icon="remove"
size="small"
@@ -238,10 +281,11 @@ export default {
</board-delete>
<div
v-if="showBoardListAndBoardInfo"
- class="issue-count-badge gl-pr-0 no-drag text-secondary"
+ class="issue-count-badge gl-display-inline-flex gl-pr-0 no-drag text-secondary"
+ :class="{ 'gl-display-none': !list.isExpanded && isSwimlanesHeader }"
>
<span class="gl-display-inline-flex">
- <gl-tooltip :target="() => $refs.issueCount" :title="issuesTooltip" />
+ <gl-tooltip :target="() => $refs.issueCount" :title="issuesTooltipLabel" />
<span ref="issueCount" class="issue-count-badge-count">
<gl-icon class="gl-mr-2" name="issues" />
<issue-count :issues-size="list.issuesSize" :max-issue-count="list.maxIssueCount" />
diff --git a/app/assets/javascripts/boards/components/board_new_issue.vue b/app/assets/javascripts/boards/components/board_new_issue.vue
index c72fb7b30f9..02ac45f8ef9 100644
--- a/app/assets/javascripts/boards/components/board_new_issue.vue
+++ b/app/assets/javascripts/boards/components/board_new_issue.vue
@@ -1,6 +1,6 @@
<script>
import $ from 'jquery';
-import { GlDeprecatedButton } from '@gitlab/ui';
+import { GlButton } from '@gitlab/ui';
import { getMilestone } from 'ee_else_ce/boards/boards_util';
import ListIssue from 'ee_else_ce/boards/models/issue';
import eventHub from '../eventhub';
@@ -11,7 +11,7 @@ export default {
name: 'BoardNewIssue',
components: {
ProjectSelect,
- GlDeprecatedButton,
+ GlButton,
},
props: {
groupId: {
@@ -120,21 +120,18 @@ export default {
/>
<project-select v-if="groupId" :group-id="groupId" :list="list" />
<div class="clearfix prepend-top-10">
- <gl-deprecated-button
+ <gl-button
ref="submit-button"
:disabled="disabled"
class="float-left"
variant="success"
+ category="primary"
type="submit"
- >{{ __('Submit issue') }}</gl-deprecated-button
- >
- <gl-deprecated-button
- class="float-right"
- type="button"
- variant="default"
- @click="cancel"
- >{{ __('Cancel') }}</gl-deprecated-button
+ >{{ __('Submit issue') }}</gl-button
>
+ <gl-button class="float-right" type="button" variant="default" @click="cancel">{{
+ __('Cancel')
+ }}</gl-button>
</div>
</form>
</div>
diff --git a/app/assets/javascripts/boards/components/boards_selector.vue b/app/assets/javascripts/boards/components/boards_selector.vue
index 80db9930259..dbe3e0790f6 100644
--- a/app/assets/javascripts/boards/components/boards_selector.vue
+++ b/app/assets/javascripts/boards/components/boards_selector.vue
@@ -233,7 +233,7 @@ export default {
</script>
<template>
- <div class="boards-switcher js-boards-selector append-right-10">
+ <div class="boards-switcher js-boards-selector gl-mr-3">
<span class="boards-selector-wrapper js-boards-selector-wrapper">
<gl-dropdown
data-qa-selector="boards_dropdown"
diff --git a/app/assets/javascripts/boards/components/issue_card_inner.vue b/app/assets/javascripts/boards/components/issue_card_inner.vue
index f2e198eaedb..d90928f35b6 100644
--- a/app/assets/javascripts/boards/components/issue_card_inner.vue
+++ b/app/assets/javascripts/boards/components/issue_card_inner.vue
@@ -153,7 +153,7 @@ export default {
v-gl-tooltip
name="issue-block"
:title="__('Blocked issue')"
- class="issue-blocked-icon append-right-4"
+ class="issue-blocked-icon gl-mr-2"
:aria-label="__('Blocked issue')"
/>
<icon
@@ -161,7 +161,7 @@ export default {
v-gl-tooltip
name="eye-slash"
:title="__('Confidential')"
- class="confidential-icon append-right-4"
+ class="confidential-icon gl-mr-2"
:aria-label="__('Confidential')"
/>
<a :href="issue.path" :title="issue.title" class="js-no-trigger" @mousemove.stop>{{
diff --git a/app/assets/javascripts/boards/components/modal/header.vue b/app/assets/javascripts/boards/components/modal/header.vue
index a42e691dcf3..8eae8e4726f 100644
--- a/app/assets/javascripts/boards/components/modal/header.vue
+++ b/app/assets/javascripts/boards/components/modal/header.vue
@@ -72,7 +72,7 @@ export default {
<button
ref="selectAllBtn"
type="button"
- class="btn btn-success btn-inverted prepend-left-10"
+ class="btn btn-success btn-inverted gl-ml-3"
@click="toggleAll"
>
{{ selectAllText }}
diff --git a/app/assets/javascripts/boards/index.js b/app/assets/javascripts/boards/index.js
index a882cd1cdfa..5b4a1d262dd 100644
--- a/app/assets/javascripts/boards/index.js
+++ b/app/assets/javascripts/boards/index.js
@@ -80,15 +80,7 @@ export default () => {
el: $boardApp,
components: {
BoardContent,
- Board: () =>
- window?.gon?.features?.sfcIssueBoards
- ? import('ee_else_ce/boards/components/board_column.vue')
- : /**
- * Please have a look at, we are moving to the SFC soon:
- * https://gitlab.com/gitlab-org/gitlab/-/issues/212300
- * @deprecated
- */
- import('ee_else_ce/boards/components/board'),
+ Board: () => import('ee_else_ce/boards/components/board_column.vue'),
BoardSidebar,
BoardAddIssuesModal,
BoardSettingsSidebar: () =>
@@ -360,7 +352,7 @@ export default () => {
template: `
<div class="board-extra-actions">
<button
- class="btn btn-success prepend-left-10"
+ class="btn btn-success gl-ml-3"
type="button"
data-placement="bottom"
ref="addIssuesButton"
diff --git a/app/assets/javascripts/boards/models/list.js b/app/assets/javascripts/boards/models/list.js
index 0bd606c6297..2aa92f86125 100644
--- a/app/assets/javascripts/boards/models/list.js
+++ b/app/assets/javascripts/boards/models/list.js
@@ -119,16 +119,12 @@ class List {
}
moveMultipleIssues({ issues, oldIndicies, newIndex, moveBeforeId, moveAfterId }) {
- oldIndicies.reverse().forEach(index => {
- this.issues.splice(index, 1);
- });
- this.issues.splice(newIndex, 0, ...issues);
-
boardsStore
- .moveMultipleIssues({
- ids: issues.map(issue => issue.id),
- fromListId: null,
- toListId: null,
+ .moveListMultipleIssues({
+ list: this,
+ issues,
+ oldIndicies,
+ newIndex,
moveBeforeId,
moveAfterId,
})
@@ -170,12 +166,7 @@ class List {
}
onNewIssueResponse(issue, data) {
- issue.refreshData(data);
-
- if (this.issuesSize > 1) {
- const moveBeforeId = this.issues[1].id;
- boardsStore.moveIssue(issue.id, null, null, null, moveBeforeId);
- }
+ boardsStore.onNewListIssueResponse(this, issue, data);
}
}
diff --git a/app/assets/javascripts/boards/queries/board.fragment.graphql b/app/assets/javascripts/boards/queries/board.fragment.graphql
index 48f55e899bf..872a4c4afbc 100644
--- a/app/assets/javascripts/boards/queries/board.fragment.graphql
+++ b/app/assets/javascripts/boards/queries/board.fragment.graphql
@@ -1,4 +1,4 @@
fragment BoardFragment on Board {
- id,
+ id
name
}
diff --git a/app/assets/javascripts/boards/queries/board_list_shared.fragment.graphql b/app/assets/javascripts/boards/queries/board_list_shared.fragment.graphql
index 6ba6c05d6d9..5b532906f6a 100644
--- a/app/assets/javascripts/boards/queries/board_list_shared.fragment.graphql
+++ b/app/assets/javascripts/boards/queries/board_list_shared.fragment.graphql
@@ -1,15 +1,15 @@
fragment BoardListShared on BoardList {
- id,
- title,
- position,
- listType,
- collapsed,
+ id
+ title
+ position
+ listType
+ collapsed
label {
- id,
- title,
- color,
- textColor,
- description,
+ id
+ title
+ color
+ textColor
+ description
descriptionHtml
}
}
diff --git a/app/assets/javascripts/boards/stores/boards_store.js b/app/assets/javascripts/boards/stores/boards_store.js
index a930f39189e..da7d2e19ec1 100644
--- a/app/assets/javascripts/boards/stores/boards_store.js
+++ b/app/assets/javascripts/boards/stores/boards_store.js
@@ -296,6 +296,15 @@ const boardsStore = {
Object.assign(this.moving, { list, issue });
},
+ onNewListIssueResponse(list, issue, data) {
+ issue.refreshData(data);
+
+ if (list.issuesSize > 1) {
+ const moveBeforeId = list.issues[1].id;
+ this.moveIssue(issue.id, null, null, null, moveBeforeId);
+ }
+ },
+
moveMultipleIssuesToList({ listFrom, listTo, issues, newIndex }) {
const issueTo = issues.map(issue => listTo.findIssue(issue.id));
const issueLists = issues.map(issue => issue.getLists()).flat();
@@ -675,6 +684,21 @@ const boardsStore = {
});
},
+ moveListMultipleIssues({ list, issues, oldIndicies, newIndex, moveBeforeId, moveAfterId }) {
+ oldIndicies.reverse().forEach(index => {
+ list.issues.splice(index, 1);
+ });
+ list.issues.splice(newIndex, 0, ...issues);
+
+ return this.moveMultipleIssues({
+ ids: issues.map(issue => issue.id),
+ fromListId: null,
+ toListId: null,
+ moveBeforeId,
+ moveAfterId,
+ });
+ },
+
newIssue(id, issue) {
return axios.post(this.generateIssuesPath(id), {
issue,
diff --git a/app/assets/javascripts/boards/toggle_focus.js b/app/assets/javascripts/boards/toggle_focus.js
index a437a34c948..e60e7059192 100644
--- a/app/assets/javascripts/boards/toggle_focus.js
+++ b/app/assets/javascripts/boards/toggle_focus.js
@@ -25,7 +25,7 @@ export default (ModalStore, boardsStore) => {
<div class="board-extra-actions">
<a
href="#"
- class="btn btn-default has-tooltip prepend-left-10 js-focus-mode-btn"
+ class="btn btn-default has-tooltip gl-ml-3 js-focus-mode-btn"
data-qa-selector="focus_mode_button"
role="button"
aria-label="Toggle focus mode"
diff --git a/app/assets/javascripts/ci_variable_list/components/ci_key_field.vue b/app/assets/javascripts/ci_variable_list/components/ci_key_field.vue
deleted file mode 100644
index c15d638d92b..00000000000
--- a/app/assets/javascripts/ci_variable_list/components/ci_key_field.vue
+++ /dev/null
@@ -1,169 +0,0 @@
-<script>
-import { uniqueId } from 'lodash';
-import { GlButton, GlFormGroup, GlFormInput } from '@gitlab/ui';
-
-export default {
- name: 'CiKeyField',
- components: {
- GlButton,
- GlFormGroup,
- GlFormInput,
- },
- model: {
- prop: 'value',
- event: 'input',
- },
- props: {
- tokenList: {
- type: Array,
- required: true,
- },
- value: {
- type: String,
- required: true,
- },
- },
- data() {
- return {
- results: [],
- arrowCounter: -1,
- userDismissedResults: false,
- suggestionsId: uniqueId('token-suggestions-'),
- };
- },
- computed: {
- showAutocomplete() {
- return this.showSuggestions ? 'off' : 'on';
- },
- showSuggestions() {
- return this.results.length > 0;
- },
- },
- mounted() {
- document.addEventListener('click', this.handleClickOutside);
- },
- destroyed() {
- document.removeEventListener('click', this.handleClickOutside);
- },
- methods: {
- closeSuggestions() {
- this.results = [];
- this.arrowCounter = -1;
- },
- handleClickOutside(event) {
- if (!this.$el.contains(event.target)) {
- this.closeSuggestions();
- }
- },
- onArrowDown() {
- const newCount = this.arrowCounter + 1;
-
- if (newCount >= this.results.length) {
- this.arrowCounter = 0;
- return;
- }
-
- this.arrowCounter = newCount;
- },
- onArrowUp() {
- const newCount = this.arrowCounter - 1;
-
- if (newCount < 0) {
- this.arrowCounter = this.results.length - 1;
- return;
- }
-
- this.arrowCounter = newCount;
- },
- onEnter() {
- const currentToken = this.results[this.arrowCounter] || this.value;
- this.selectToken(currentToken);
- },
- onEsc() {
- if (!this.showSuggestions) {
- this.$emit('input', '');
- }
- this.closeSuggestions();
- this.userDismissedResults = true;
- },
- onEntry(value) {
- this.$emit('input', value);
- this.userDismissedResults = false;
-
- // short circuit so that we don't false match on empty string
- if (value.length < 1) {
- this.closeSuggestions();
- return;
- }
-
- const filteredTokens = this.tokenList.filter(token =>
- token.toLowerCase().includes(value.toLowerCase()),
- );
-
- if (filteredTokens.length) {
- this.openSuggestions(filteredTokens);
- } else {
- this.closeSuggestions();
- }
- },
- openSuggestions(filteredResults) {
- this.results = filteredResults;
- },
- selectToken(value) {
- this.$emit('input', value);
- this.closeSuggestions();
- this.$emit('key-selected');
- },
- },
-};
-</script>
-<template>
- <div>
- <div class="dropdown position-relative" role="combobox" aria-owns="token-suggestions">
- <gl-form-group :label="__('Key')" label-for="ci-variable-key">
- <gl-form-input
- id="ci-variable-key"
- :value="value"
- type="text"
- role="searchbox"
- class="form-control pl-2 js-env-input"
- :autocomplete="showAutocomplete"
- aria-autocomplete="list"
- aria-controls="token-suggestions"
- aria-haspopup="listbox"
- :aria-expanded="showSuggestions"
- data-qa-selector="ci_variable_key_field"
- @input="onEntry"
- @keydown.down="onArrowDown"
- @keydown.up="onArrowUp"
- @keydown.enter.prevent="onEnter"
- @keydown.esc.stop="onEsc"
- @keydown.tab="closeSuggestions"
- />
- </gl-form-group>
-
- <div
- v-show="showSuggestions && !userDismissedResults"
- id="ci-variable-dropdown"
- class="dropdown-menu dropdown-menu-selectable dropdown-menu-full-width"
- :class="{ 'd-block': showSuggestions }"
- >
- <div class="dropdown-content">
- <ul :id="suggestionsId">
- <li
- v-for="(result, i) in results"
- :key="i"
- role="option"
- :class="{ 'gl-bg-gray-50': i === arrowCounter }"
- :aria-selected="i === arrowCounter"
- >
- <gl-button tabindex="-1" class="btn-transparent pl-2" @click="selectToken(result)">{{
- result
- }}</gl-button>
- </li>
- </ul>
- </div>
- </div>
- </div>
- </div>
-</template>
diff --git a/app/assets/javascripts/ci_variable_list/components/ci_variable_autocomplete_tokens.js b/app/assets/javascripts/ci_variable_list/components/ci_variable_autocomplete_tokens.js
index 9022bf51514..3f25e3df305 100644
--- a/app/assets/javascripts/ci_variable_list/components/ci_variable_autocomplete_tokens.js
+++ b/app/assets/javascripts/ci_variable_list/components/ci_variable_autocomplete_tokens.js
@@ -1,28 +1,14 @@
-import { __ } from '~/locale';
-
import { AWS_ACCESS_KEY_ID, AWS_DEFAULT_REGION, AWS_SECRET_ACCESS_KEY } from '../constants';
export const awsTokens = {
[AWS_ACCESS_KEY_ID]: {
name: AWS_ACCESS_KEY_ID,
- /* Checks for exactly twenty characters that match key.
- Based on greps suggested by Amazon at:
- https://aws.amazon.com/blogs/security/a-safer-way-to-distribute-aws-credentials-to-ec2/
- */
- validation: val => /^[A-Za-z0-9]{20}$/.test(val),
- invalidMessage: __('This variable does not match the expected pattern.'),
},
[AWS_DEFAULT_REGION]: {
name: AWS_DEFAULT_REGION,
},
[AWS_SECRET_ACCESS_KEY]: {
name: AWS_SECRET_ACCESS_KEY,
- /* Checks for exactly forty characters that match secret.
- Based on greps suggested by Amazon at:
- https://aws.amazon.com/blogs/security/a-safer-way-to-distribute-aws-credentials-to-ec2/
- */
- validation: val => /^[A-Za-z0-9/+=]{40}$/.test(val),
- invalidMessage: __('This variable does not match the expected pattern.'),
},
};
diff --git a/app/assets/javascripts/ci_variable_list/components/ci_variable_modal.vue b/app/assets/javascripts/ci_variable_list/components/ci_variable_modal.vue
index 6531b945212..0ba58430de1 100644
--- a/app/assets/javascripts/ci_variable_list/components/ci_variable_modal.vue
+++ b/app/assets/javascripts/ci_variable_list/components/ci_variable_modal.vue
@@ -5,6 +5,7 @@ import {
GlCollapse,
GlDeprecatedButton,
GlFormCheckbox,
+ GlFormCombobox,
GlFormGroup,
GlFormInput,
GlFormSelect,
@@ -16,6 +17,7 @@ import {
} from '@gitlab/ui';
import Cookies from 'js-cookie';
import { mapActions, mapState } from 'vuex';
+import { mapComputed } from '~/vuex_shared/bindings';
import { __ } from '~/locale';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import {
@@ -25,19 +27,21 @@ import {
AWS_TIP_MESSAGE,
} from '../constants';
import { awsTokens, awsTokenList } from './ci_variable_autocomplete_tokens';
-import CiKeyField from './ci_key_field.vue';
import CiEnvironmentsDropdown from './ci_environments_dropdown.vue';
export default {
modalId: ADD_CI_VARIABLE_MODAL_ID,
+ tokens: awsTokens,
+ tokenList: awsTokenList,
+ awsTipMessage: AWS_TIP_MESSAGE,
components: {
CiEnvironmentsDropdown,
- CiKeyField,
GlAlert,
GlButton,
GlCollapse,
GlDeprecatedButton,
GlFormCheckbox,
+ GlFormCombobox,
GlFormGroup,
GlFormInput,
GlFormSelect,
@@ -48,9 +52,6 @@ export default {
GlSprintf,
},
mixins: [glFeatureFlagsMixin()],
- tokens: awsTokens,
- tokenList: awsTokenList,
- awsTipMessage: AWS_TIP_MESSAGE,
data() {
return {
isTipDismissed: Cookies.get(AWS_TIP_DISMISSED_COOKIE_NAME) === 'true',
@@ -74,22 +75,34 @@ export default {
'protectedEnvironmentVariablesLink',
'maskedEnvironmentVariablesLink',
]),
+ ...mapComputed(
+ [
+ { key: 'key', updateFn: 'updateVariableKey' },
+ { key: 'secret_value', updateFn: 'updateVariableValue' },
+ { key: 'variable_type', updateFn: 'updateVariableType' },
+ { key: 'environment_scope', updateFn: 'setEnvironmentScope' },
+ { key: 'protected_variable', updateFn: 'updateVariableProtected' },
+ { key: 'masked', updateFn: 'updateVariableMasked' },
+ ],
+ false,
+ 'variable',
+ ),
isTipVisible() {
- return !this.isTipDismissed && AWS_TOKEN_CONSTANTS.includes(this.variableData.key);
+ return !this.isTipDismissed && AWS_TOKEN_CONSTANTS.includes(this.variable.key);
},
canSubmit() {
return (
this.variableValidationState &&
- this.variableData.key !== '' &&
- this.variableData.secret_value !== ''
+ this.variable.key !== '' &&
+ this.variable.secret_value !== ''
);
},
canMask() {
const regex = RegExp(this.maskableRegex);
- return regex.test(this.variableData.secret_value);
+ return regex.test(this.variable.secret_value);
},
displayMaskedError() {
- return !this.canMask && this.variableData.masked;
+ return !this.canMask && this.variable.masked;
},
maskedState() {
if (this.displayMaskedError) {
@@ -97,9 +110,6 @@ export default {
}
return true;
},
- variableData() {
- return this.variableBeingEdited || this.variable;
- },
modalActionText() {
return this.variableBeingEdited ? __('Update variable') : __('Add variable');
},
@@ -107,7 +117,7 @@ export default {
return this.displayMaskedError ? __('This variable can not be masked.') : '';
},
tokenValidationFeedback() {
- const tokenSpecificFeedback = this.$options.tokens?.[this.variableData.key]?.invalidMessage;
+ const tokenSpecificFeedback = this.$options.tokens?.[this.variable.key]?.invalidMessage;
if (!this.tokenValidationState && tokenSpecificFeedback) {
return tokenSpecificFeedback;
}
@@ -119,10 +129,10 @@ export default {
return true;
}
- const validator = this.$options.tokens?.[this.variableData.key]?.validation;
+ const validator = this.$options.tokens?.[this.variable.key]?.validation;
if (validator) {
- return validator(this.variableData.secret_value);
+ return validator(this.variable.secret_value);
}
return true;
@@ -131,14 +141,7 @@ export default {
return `${this.tokenValidationFeedback} ${this.maskedFeedback}`;
},
variableValidationState() {
- if (
- this.variableData.secret_value === '' ||
- (this.tokenValidationState && this.maskedState)
- ) {
- return true;
- }
-
- return false;
+ return this.variable.secret_value === '' || (this.tokenValidationState && this.maskedState);
},
},
methods: {
@@ -160,7 +163,7 @@ export default {
this.isTipDismissed = true;
},
deleteVarAndClose() {
- this.deleteVariable(this.variableBeingEdited);
+ this.deleteVariable();
this.hideModal();
},
hideModal() {
@@ -169,14 +172,14 @@ export default {
resetModalHandler() {
if (this.variableBeingEdited) {
this.resetEditing();
- } else {
- this.clearModal();
}
+
+ this.clearModal();
this.resetSelectedEnvironment();
},
updateOrAddVariable() {
if (this.variableBeingEdited) {
- this.updateVariable(this.variableBeingEdited);
+ this.updateVariable();
} else {
this.addVariable();
}
@@ -202,16 +205,17 @@ export default {
@shown="setVariableProtectedByDefault"
>
<form>
- <ci-key-field
+ <gl-form-combobox
v-if="glFeatures.ciKeyAutocomplete"
- v-model="variableData.key"
+ v-model="key"
:token-list="$options.tokenList"
+ :label-text="__('Key')"
/>
<gl-form-group v-else :label="__('Key')" label-for="ci-variable-key">
<gl-form-input
id="ci-variable-key"
- v-model="variableData.key"
+ v-model="key"
data-qa-selector="ci_variable_key_field"
/>
</gl-form-group>
@@ -225,11 +229,12 @@ export default {
<gl-form-textarea
id="ci-variable-value"
ref="valueField"
- v-model="variableData.secret_value"
+ v-model="secret_value"
:state="variableValidationState"
rows="3"
max-rows="6"
data-qa-selector="ci_variable_value_field"
+ class="gl-font-monospace!"
/>
</gl-form-group>
@@ -240,11 +245,7 @@ export default {
class="w-50 append-right-15"
:class="{ 'w-100': isGroup }"
>
- <gl-form-select
- id="ci-variable-type"
- v-model="variableData.variable_type"
- :options="typeOptions"
- />
+ <gl-form-select id="ci-variable-type" v-model="variable_type" :options="typeOptions" />
</gl-form-group>
<gl-form-group
@@ -255,7 +256,7 @@ export default {
>
<ci-environments-dropdown
class="w-100"
- :value="variableData.environment_scope"
+ :value="environment_scope"
@selectEnvironment="setEnvironmentScope"
@createClicked="addWildCardScope"
/>
@@ -263,7 +264,7 @@ export default {
</div>
<gl-form-group :label="__('Flags')" label-for="ci-variable-flags">
- <gl-form-checkbox v-model="variableData.protected" class="mb-0">
+ <gl-form-checkbox v-model="protected_variable" class="mb-0">
{{ __('Protect variable') }}
<gl-link target="_blank" :href="protectedEnvironmentVariablesLink">
<gl-icon name="question" :size="12" />
@@ -275,7 +276,7 @@ export default {
<gl-form-checkbox
ref="masked-ci-variable"
- v-model="variableData.masked"
+ v-model="masked"
data-qa-selector="ci_variable_masked_checkbox"
>
{{ __('Mask variable') }}
diff --git a/app/assets/javascripts/ci_variable_list/store/actions.js b/app/assets/javascripts/ci_variable_list/store/actions.js
index d9129c919f8..60c7a480769 100644
--- a/app/assets/javascripts/ci_variable_list/store/actions.js
+++ b/app/assets/javascripts/ci_variable_list/store/actions.js
@@ -65,10 +65,10 @@ export const receiveUpdateVariableError = ({ commit }, error) => {
commit(types.RECEIVE_UPDATE_VARIABLE_ERROR, error);
};
-export const updateVariable = ({ state, dispatch }, variable) => {
+export const updateVariable = ({ state, dispatch }) => {
dispatch('requestUpdateVariable');
- const updatedVariable = prepareDataForApi(variable);
+ const updatedVariable = prepareDataForApi(state.variable);
updatedVariable.secrect_value = updateVariable.value;
return axios
@@ -121,13 +121,13 @@ export const receiveDeleteVariableError = ({ commit }, error) => {
commit(types.RECEIVE_DELETE_VARIABLE_ERROR, error);
};
-export const deleteVariable = ({ dispatch, state }, variable) => {
+export const deleteVariable = ({ dispatch, state }) => {
dispatch('requestDeleteVariable');
const destroy = true;
return axios
- .patch(state.endpoint, { variables_attributes: [prepareDataForApi(variable, destroy)] })
+ .patch(state.endpoint, { variables_attributes: [prepareDataForApi(state.variable, destroy)] })
.then(() => {
dispatch('receiveDeleteVariableSuccess');
dispatch('fetchVariables');
@@ -176,3 +176,23 @@ export const resetSelectedEnvironment = ({ commit }) => {
export const setSelectedEnvironment = ({ commit }, environment) => {
commit(types.SET_SELECTED_ENVIRONMENT, environment);
};
+
+export const updateVariableKey = ({ commit }, { key }) => {
+ commit(types.UPDATE_VARIABLE_KEY, key);
+};
+
+export const updateVariableValue = ({ commit }, { secret_value }) => {
+ commit(types.UPDATE_VARIABLE_VALUE, secret_value);
+};
+
+export const updateVariableType = ({ commit }, { variable_type }) => {
+ commit(types.UPDATE_VARIABLE_TYPE, variable_type);
+};
+
+export const updateVariableProtected = ({ commit }, { protected_variable }) => {
+ commit(types.UPDATE_VARIABLE_PROTECTED, protected_variable);
+};
+
+export const updateVariableMasked = ({ commit }, { masked }) => {
+ commit(types.UPDATE_VARIABLE_MASKED, masked);
+};
diff --git a/app/assets/javascripts/ci_variable_list/store/mutation_types.js b/app/assets/javascripts/ci_variable_list/store/mutation_types.js
index ccf8fbd3cb5..5db8f610192 100644
--- a/app/assets/javascripts/ci_variable_list/store/mutation_types.js
+++ b/app/assets/javascripts/ci_variable_list/store/mutation_types.js
@@ -25,3 +25,9 @@ export const SET_ENVIRONMENT_SCOPE = 'SET_ENVIRONMENT_SCOPE';
export const ADD_WILD_CARD_SCOPE = 'ADD_WILD_CARD_SCOPE';
export const RESET_SELECTED_ENVIRONMENT = 'RESET_SELECTED_ENVIRONMENT';
export const SET_SELECTED_ENVIRONMENT = 'SET_SELECTED_ENVIRONMENT';
+
+export const UPDATE_VARIABLE_KEY = 'UPDATE_VARIABLE_KEY';
+export const UPDATE_VARIABLE_VALUE = 'UPDATE_VARIABLE_VALUE';
+export const UPDATE_VARIABLE_TYPE = 'UPDATE_VARIABLE_TYPE';
+export const UPDATE_VARIABLE_PROTECTED = 'UPDATE_VARIABLE_PROTECTED';
+export const UPDATE_VARIABLE_MASKED = 'UPDATE_VARIABLE_MASKED';
diff --git a/app/assets/javascripts/ci_variable_list/store/mutations.js b/app/assets/javascripts/ci_variable_list/store/mutations.js
index 7d9cd0dd727..961cecee298 100644
--- a/app/assets/javascripts/ci_variable_list/store/mutations.js
+++ b/app/assets/javascripts/ci_variable_list/store/mutations.js
@@ -65,7 +65,8 @@ export default {
},
[types.VARIABLE_BEING_EDITED](state, variable) {
- state.variableBeingEdited = variable;
+ state.variableBeingEdited = true;
+ state.variable = variable;
},
[types.CLEAR_MODAL](state) {
@@ -73,23 +74,19 @@ export default {
variable_type: displayText.variableText,
key: '',
secret_value: '',
- protected: false,
+ protected_variable: false,
masked: false,
environment_scope: displayText.allEnvironmentsText,
};
},
[types.RESET_EDITING](state) {
- state.variableBeingEdited = null;
+ state.variableBeingEdited = false;
state.showInputValue = false;
},
[types.SET_ENVIRONMENT_SCOPE](state, environment) {
- if (state.variableBeingEdited) {
- state.variableBeingEdited.environment_scope = environment;
- } else {
- state.variable.environment_scope = environment;
- }
+ state.variable.environment_scope = environment;
},
[types.ADD_WILD_CARD_SCOPE](state, environment) {
@@ -106,6 +103,26 @@ export default {
},
[types.SET_VARIABLE_PROTECTED](state) {
- state.variable.protected = true;
+ state.variable.protected_variable = true;
+ },
+
+ [types.UPDATE_VARIABLE_KEY](state, key) {
+ state.variable.key = key;
+ },
+
+ [types.UPDATE_VARIABLE_VALUE](state, value) {
+ state.variable.secret_value = value;
+ },
+
+ [types.UPDATE_VARIABLE_TYPE](state, type) {
+ state.variable.variable_type = type;
+ },
+
+ [types.UPDATE_VARIABLE_PROTECTED](state, bool) {
+ state.variable.protected_variable = bool;
+ },
+
+ [types.UPDATE_VARIABLE_MASKED](state, bool) {
+ state.variable.masked = bool;
},
};
diff --git a/app/assets/javascripts/ci_variable_list/store/state.js b/app/assets/javascripts/ci_variable_list/store/state.js
index 2fffd115589..96b27792664 100644
--- a/app/assets/javascripts/ci_variable_list/store/state.js
+++ b/app/assets/javascripts/ci_variable_list/store/state.js
@@ -12,7 +12,7 @@ export default () => ({
variable_type: displayText.variableText,
key: '',
secret_value: '',
- protected: false,
+ protected_variable: false,
masked: false,
environment_scope: displayText.allEnvironmentsText,
},
@@ -21,6 +21,6 @@ export default () => ({
error: null,
environments: [],
typeOptions: [displayText.variableText, displayText.fileText],
- variableBeingEdited: null,
+ variableBeingEdited: false,
selectedEnvironment: '',
});
diff --git a/app/assets/javascripts/ci_variable_list/store/utils.js b/app/assets/javascripts/ci_variable_list/store/utils.js
index 3cd8c85024b..f04530359e7 100644
--- a/app/assets/javascripts/ci_variable_list/store/utils.js
+++ b/app/assets/javascripts/ci_variable_list/store/utils.js
@@ -18,6 +18,7 @@ export const prepareDataForDisplay = variables => {
if (variableCopy.environment_scope === types.allEnvironmentsType) {
variableCopy.environment_scope = displayText.allEnvironmentsText;
}
+ variableCopy.protected_variable = variableCopy.protected;
variablesToDisplay.push(variableCopy);
});
return variablesToDisplay;
@@ -25,7 +26,8 @@ export const prepareDataForDisplay = variables => {
export const prepareDataForApi = (variable, destroy = false) => {
const variableCopy = cloneDeep(variable);
- variableCopy.protected = variableCopy.protected.toString();
+ variableCopy.protected = variableCopy.protected_variable.toString();
+ delete variableCopy.protected_variable;
variableCopy.masked = variableCopy.masked.toString();
variableCopy.variable_type = variableTypeHandler(variableCopy.variable_type);
if (variableCopy.environment_scope === displayText.allEnvironmentsText) {
diff --git a/app/assets/javascripts/close_reopen_report_toggle.js b/app/assets/javascripts/close_reopen_report_toggle.js
index bcddce6e727..9bbbe07e7a1 100644
--- a/app/assets/javascripts/close_reopen_report_toggle.js
+++ b/app/assets/javascripts/close_reopen_report_toggle.js
@@ -80,12 +80,7 @@ class CloseReopenReportToggle {
{
input: this.button,
valueAttribute: 'data-url',
- inputAttribute: 'href',
- },
- {
- input: this.button,
- valueAttribute: 'data-method',
- inputAttribute: 'data-method',
+ inputAttribute: 'data-endpoint',
},
],
};
diff --git a/app/assets/javascripts/clusters/clusters_bundle.js b/app/assets/javascripts/clusters/clusters_bundle.js
index f15efb2fdeb..83bdea15e62 100644
--- a/app/assets/javascripts/clusters/clusters_bundle.js
+++ b/app/assets/javascripts/clusters/clusters_bundle.js
@@ -222,7 +222,7 @@ export default class Clusters {
initRemoveClusterActions() {
const el = document.querySelector('#js-cluster-remove-actions');
if (el && el.dataset) {
- const { clusterName, clusterPath } = el.dataset;
+ const { clusterName, clusterPath, hasManagementProject } = el.dataset;
this.removeClusterAction = new Vue({
el,
@@ -231,6 +231,7 @@ export default class Clusters {
props: {
clusterName,
clusterPath,
+ hasManagementProject,
},
});
},
diff --git a/app/assets/javascripts/clusters/components/ingress_modsecurity_settings.vue b/app/assets/javascripts/clusters/components/ingress_modsecurity_settings.vue
index 54f5468bdd0..87c3225085f 100644
--- a/app/assets/javascripts/clusters/components/ingress_modsecurity_settings.vue
+++ b/app/assets/javascripts/clusters/components/ingress_modsecurity_settings.vue
@@ -198,13 +198,7 @@ export default {
</strong>
</p>
<div class="form-check form-check-inline mt-3">
- <gl-toggle
- v-model="modSecurityEnabled"
- :label-on="__('Enabled')"
- :label-off="__('Disabled')"
- :disabled="saveButtonDisabled"
- label-position="right"
- />
+ <gl-toggle v-model="modSecurityEnabled" :disabled="saveButtonDisabled" />
</div>
<div
v-if="ingress.modsecurity_enabled"
diff --git a/app/assets/javascripts/clusters/components/remove_cluster_confirmation.vue b/app/assets/javascripts/clusters/components/remove_cluster_confirmation.vue
index c5375cbfbdc..45f2dd48961 100644
--- a/app/assets/javascripts/clusters/components/remove_cluster_confirmation.vue
+++ b/app/assets/javascripts/clusters/components/remove_cluster_confirmation.vue
@@ -1,7 +1,7 @@
<script>
import { escape } from 'lodash';
import SplitButton from '~/vue_shared/components/split_button.vue';
-import { GlModal, GlDeprecatedButton, GlFormInput } from '@gitlab/ui';
+import { GlModal, GlButton, GlDeprecatedButton, GlFormInput } from '@gitlab/ui';
import { s__, sprintf } from '~/locale';
import csrf from '~/lib/utils/csrf';
@@ -27,6 +27,7 @@ export default {
components: {
SplitButton,
GlModal,
+ GlButton,
GlDeprecatedButton,
GlFormInput,
},
@@ -39,6 +40,10 @@ export default {
type: String,
required: true,
},
+ hasManagementProject: {
+ type: Boolean,
+ required: false,
+ },
},
data() {
return {
@@ -90,6 +95,9 @@ export default {
canSubmit() {
return this.enteredClusterName === this.clusterName;
},
+ canCleanupResources() {
+ return !this.hasManagementProject;
+ },
},
methods: {
handleClickRemoveCluster(cleanup = false) {
@@ -112,12 +120,21 @@ export default {
<template>
<div>
<split-button
+ v-if="canCleanupResources"
:action-items="$options.splitButtonActionItems"
menu-class="dropdown-menu-large"
variant="danger"
@remove-cluster="handleClickRemoveCluster(false)"
@remove-cluster-and-cleanup="handleClickRemoveCluster(true)"
/>
+ <gl-button
+ v-else
+ variant="danger"
+ data-testid="btnRemove"
+ @click="handleClickRemoveCluster(false)"
+ >
+ {{ s__('ClusterIntegration|Remove integration') }}
+ </gl-button>
<gl-modal
ref="modal"
size="lg"
diff --git a/app/assets/javascripts/clusters_list/components/ancestor_notice.vue b/app/assets/javascripts/clusters_list/components/ancestor_notice.vue
new file mode 100644
index 00000000000..7954fc61785
--- /dev/null
+++ b/app/assets/javascripts/clusters_list/components/ancestor_notice.vue
@@ -0,0 +1,34 @@
+<script>
+import { GlLink, GlSprintf } from '@gitlab/ui';
+import { mapState } from 'vuex';
+
+export default {
+ components: {
+ GlLink,
+ GlSprintf,
+ },
+ computed: {
+ ...mapState(['ancestorHelperPath', 'hasAncestorClusters']),
+ },
+};
+</script>
+
+<template>
+ <div v-if="hasAncestorClusters" class="bs-callout bs-callout-info">
+ <p>
+ <gl-sprintf
+ :message="
+ s__(
+ 'ClusterIntegration|Clusters are utilized by selecting the nearest ancestor with a matching environment scope. For example, project clusters will override group clusters. %{linkStart}More information%{linkEnd}',
+ )
+ "
+ >
+ <template #link="{ content }">
+ <gl-link :href="ancestorHelperPath">
+ <strong>{{ content }}</strong>
+ </gl-link>
+ </template>
+ </gl-sprintf>
+ </p>
+ </div>
+</template>
diff --git a/app/assets/javascripts/clusters_list/components/clusters.vue b/app/assets/javascripts/clusters_list/components/clusters.vue
index a3104038c17..7e9b720d269 100644
--- a/app/assets/javascripts/clusters_list/components/clusters.vue
+++ b/app/assets/javascripts/clusters_list/components/clusters.vue
@@ -1,14 +1,15 @@
<script>
-import * as Sentry from '@sentry/browser';
import { mapState, mapActions } from 'vuex';
import {
GlDeprecatedBadge as GlBadge,
GlLink,
GlLoadingIcon,
GlPagination,
+ GlSkeletonLoading,
GlSprintf,
GlTable,
} from '@gitlab/ui';
+import AncestorNotice from './ancestor_notice.vue';
import tooltip from '~/vue_shared/directives/tooltip';
import { CLUSTER_TYPES, STATUSES } from '../constants';
import { __, sprintf } from '~/locale';
@@ -17,10 +18,12 @@ export default {
nodeMemoryText: __('%{totalMemory} (%{freeSpacePercentage}%{percentSymbol} free)'),
nodeCpuText: __('%{totalCpu} (%{freeSpacePercentage}%{percentSymbol} free)'),
components: {
+ AncestorNotice,
GlBadge,
GlLink,
GlLoadingIcon,
GlPagination,
+ GlSkeletonLoading,
GlSprintf,
GlTable,
},
@@ -28,7 +31,18 @@ export default {
tooltip,
},
computed: {
- ...mapState(['clusters', 'clustersPerPage', 'loading', 'page', 'providers', 'totalCulsters']),
+ ...mapState([
+ 'clusters',
+ 'clustersPerPage',
+ 'loadingClusters',
+ 'loadingNodes',
+ 'page',
+ 'providers',
+ 'totalCulsters',
+ ]),
+ contentAlignClasses() {
+ return 'gl-display-flex gl-align-items-center gl-justify-content-end gl-justify-content-md-start';
+ },
currentPage: {
get() {
return this.page;
@@ -75,7 +89,7 @@ export default {
this.fetchClusters();
},
methods: {
- ...mapActions(['fetchClusters', 'setPage']),
+ ...mapActions(['fetchClusters', 'reportSentryError', 'setPage']),
k8sQuantityToGb(quantity) {
if (!quantity) {
return 0;
@@ -137,7 +151,7 @@ export default {
};
}
} catch (error) {
- Sentry.captureException(error);
+ this.reportSentryError({ error, tag: 'totalMemoryAndUsageError' });
}
return { totalMemory: null, freeSpacePercentage: null };
@@ -170,7 +184,7 @@ export default {
};
}
} catch (error) {
- Sentry.captureException(error);
+ this.reportSentryError({ error, tag: 'totalCpuAndUsageError' });
}
return { totalCpu: null, freeSpacePercentage: null };
@@ -180,14 +194,14 @@ export default {
</script>
<template>
- <gl-loading-icon v-if="loading" size="md" class="mt-3" />
+ <gl-loading-icon v-if="loadingClusters" size="md" class="gl-mt-3" />
<section v-else>
+ <ancestor-notice />
+
<gl-table :items="clusters" :fields="fields" stacked="md" class="qa-clusters-table">
<template #cell(name)="{ item }">
- <div
- class="gl-display-flex gl-align-items-center gl-justify-content-end gl-justify-content-md-start js-status"
- >
+ <div :class="[contentAlignClasses, 'js-status']">
<img
:src="selectedProvider(item.provider_type).path"
:alt="selectedProvider(item.provider_type).text"
@@ -214,6 +228,9 @@ export default {
<template #cell(node_size)="{ item }">
<span v-if="item.nodes">{{ item.nodes.length }}</span>
+
+ <gl-skeleton-loading v-else-if="loadingNodes" :lines="1" :class="contentAlignClasses" />
+
<small v-else class="gl-font-sm gl-font-style-italic gl-text-gray-400">{{
__('Unknown')
}}</small>
@@ -231,6 +248,8 @@ export default {
>
</gl-sprintf>
</span>
+
+ <gl-skeleton-loading v-else-if="loadingNodes" :lines="1" :class="contentAlignClasses" />
</template>
<template #cell(total_memory)="{ item }">
@@ -245,6 +264,8 @@ export default {
>
</gl-sprintf>
</span>
+
+ <gl-skeleton-loading v-else-if="loadingNodes" :lines="1" :class="contentAlignClasses" />
</template>
<template #cell(cluster_type)="{value}">
diff --git a/app/assets/javascripts/clusters_list/store/actions.js b/app/assets/javascripts/clusters_list/store/actions.js
index 5245c307c8c..dddcfb3d975 100644
--- a/app/assets/javascripts/clusters_list/store/actions.js
+++ b/app/assets/javascripts/clusters_list/store/actions.js
@@ -16,9 +16,18 @@ const allNodesPresent = (clusters, retryCount) => {
return retryCount > MAX_REQUESTS || clusters.every(cluster => cluster.nodes != null);
};
-export const fetchClusters = ({ state, commit }) => {
+export const reportSentryError = (_store, { error, tag }) => {
+ Sentry.withScope(scope => {
+ scope.setTag('javascript_clusters_list', tag);
+ Sentry.captureException(error);
+ });
+};
+
+export const fetchClusters = ({ state, commit, dispatch }) => {
let retryCount = 0;
+ commit(types.SET_LOADING_NODES, true);
+
const poll = new Poll({
resource: {
fetchClusters: paginatedEndPoint => axios.get(paginatedEndPoint),
@@ -34,31 +43,30 @@ export const fetchClusters = ({ state, commit }) => {
const paginationInformation = parseIntPagination(normalizedHeaders);
commit(types.SET_CLUSTERS_DATA, { data, paginationInformation });
- commit(types.SET_LOADING_STATE, false);
+ commit(types.SET_LOADING_CLUSTERS, false);
if (allNodesPresent(data.clusters, retryCount)) {
poll.stop();
+ commit(types.SET_LOADING_NODES, false);
}
}
} catch (error) {
poll.stop();
- Sentry.withScope(scope => {
- scope.setTag('javascript_clusters_list', 'fetchClustersSuccessCallback');
- Sentry.captureException(error);
- });
+ commit(types.SET_LOADING_CLUSTERS, false);
+ commit(types.SET_LOADING_NODES, false);
+
+ dispatch('reportSentryError', { error, tag: 'fetchClustersSuccessCallback' });
}
},
errorCallback: response => {
poll.stop();
- commit(types.SET_LOADING_STATE, false);
+ commit(types.SET_LOADING_CLUSTERS, false);
+ commit(types.SET_LOADING_NODES, false);
flash(__('Clusters|An error occurred while loading clusters'));
- Sentry.withScope(scope => {
- scope.setTag('javascript_clusters_list', 'fetchClustersErrorCallback');
- Sentry.captureException(response);
- });
+ dispatch('reportSentryError', { error: response, tag: 'fetchClustersErrorCallback' });
},
});
diff --git a/app/assets/javascripts/clusters_list/store/mutation_types.js b/app/assets/javascripts/clusters_list/store/mutation_types.js
index a5275f28c13..beb4388c93e 100644
--- a/app/assets/javascripts/clusters_list/store/mutation_types.js
+++ b/app/assets/javascripts/clusters_list/store/mutation_types.js
@@ -1,3 +1,4 @@
export const SET_CLUSTERS_DATA = 'SET_CLUSTERS_DATA';
-export const SET_LOADING_STATE = 'SET_LOADING_STATE';
+export const SET_LOADING_CLUSTERS = 'SET_LOADING_CLUSTERS';
+export const SET_LOADING_NODES = 'SET_LOADING_NODES';
export const SET_PAGE = 'SET_PAGE';
diff --git a/app/assets/javascripts/clusters_list/store/mutations.js b/app/assets/javascripts/clusters_list/store/mutations.js
index 2a9df9f38f0..5b462928518 100644
--- a/app/assets/javascripts/clusters_list/store/mutations.js
+++ b/app/assets/javascripts/clusters_list/store/mutations.js
@@ -1,8 +1,11 @@
import * as types from './mutation_types';
export default {
- [types.SET_LOADING_STATE](state, value) {
- state.loading = value;
+ [types.SET_LOADING_CLUSTERS](state, value) {
+ state.loadingClusters = value;
+ },
+ [types.SET_LOADING_NODES](state, value) {
+ state.loadingNodes = value;
},
[types.SET_CLUSTERS_DATA](state, { data, paginationInformation }) {
Object.assign(state, {
diff --git a/app/assets/javascripts/clusters_list/store/state.js b/app/assets/javascripts/clusters_list/store/state.js
index 0023b43ed92..51fafd49479 100644
--- a/app/assets/javascripts/clusters_list/store/state.js
+++ b/app/assets/javascripts/clusters_list/store/state.js
@@ -1,9 +1,11 @@
export default (initialState = {}) => ({
+ ancestorHelperPath: initialState.ancestorHelpPath,
endpoint: initialState.endpoint,
hasAncestorClusters: false,
- loading: true,
clusters: [],
clustersPerPage: 0,
+ loadingClusters: true,
+ loadingNodes: true,
page: 1,
providers: {
aws: { path: initialState.imgTagsAwsPath, text: initialState.imgTagsAwsText },
diff --git a/app/assets/javascripts/code_navigation/components/popover.vue b/app/assets/javascripts/code_navigation/components/popover.vue
index df5f89e4faf..b7fa3242fbf 100644
--- a/app/assets/javascripts/code_navigation/components/popover.vue
+++ b/app/assets/javascripts/code_navigation/components/popover.vue
@@ -1,10 +1,14 @@
<script>
-import { GlButton } from '@gitlab/ui';
+import { GlButton, GlTabs, GlTab, GlLink, GlBadge } from '@gitlab/ui';
import DocLine from './doc_line.vue';
export default {
components: {
GlButton,
+ GlTabs,
+ GlTab,
+ GlLink,
+ GlBadge,
DocLine,
},
props: {
@@ -31,6 +35,9 @@ export default {
};
},
computed: {
+ isCurrentDefinition() {
+ return this.data.definitionLineNumber - 1 === this.position.lineIndex;
+ },
positionStyles() {
return {
left: `${this.position.x - this.offsetLeft}px`,
@@ -43,7 +50,7 @@ export default {
}
if (this.isDefinitionCurrentBlob) {
- return `#${this.data.definition_path.split('#').pop()}`;
+ return `#L${this.data.definitionLineNumber}`;
}
return `${this.definitionPathPrefix}/${this.data.definition_path}`;
@@ -51,6 +58,9 @@ export default {
isDefinitionCurrentBlob() {
return this.data.definition_path.indexOf(this.blobPath) === 0;
},
+ references() {
+ return this.data.references || [];
+ },
},
watch: {
position: {
@@ -79,27 +89,61 @@ export default {
class="popover code-navigation-popover popover-font-size-normal gl-popover bs-popover-bottom show"
>
<div :style="{ left: `${offsetLeft}px` }" class="arrow"></div>
- <div v-for="(hover, index) in data.hover" :key="index" class="border-bottom">
- <pre
- v-if="hover.language"
- ref="code-output"
- :class="$options.colorScheme"
- class="border-0 bg-transparent m-0 code highlight"
- ><doc-line v-for="(tokens, tokenIndex) in hover.tokens" :key="tokenIndex" :language="hover.language" :tokens="tokens"/></pre>
- <p v-else ref="doc-output" class="p-3 m-0">
- {{ hover.value }}
- </p>
- </div>
- <div v-if="definitionPath" class="popover-body">
- <gl-button
- :href="definitionPath"
- :target="isDefinitionCurrentBlob ? null : '_blank'"
- class="w-100"
- variant="default"
- data-testid="go-to-definition-btn"
- >
- {{ __('Go to definition') }}
- </gl-button>
- </div>
+ <gl-tabs nav-class="gl-hidden" content-class="gl-py-0">
+ <gl-tab :title="__('Definition')">
+ <div class="overflow-auto code-navigation-popover-container">
+ <div
+ v-for="(hover, index) in data.hover"
+ :key="index"
+ :class="{ 'border-bottom': index !== data.hover.length - 1 }"
+ >
+ <pre
+ v-if="hover.language"
+ ref="code-output"
+ :class="$options.colorScheme"
+ class="border-0 bg-transparent m-0 code highlight text-wrap"
+ ><doc-line v-for="(tokens, tokenIndex) in hover.tokens" :key="tokenIndex" :language="hover.language" :tokens="tokens"/></pre>
+ <p v-else ref="doc-output" class="p-3 m-0">
+ {{ hover.value }}
+ </p>
+ </div>
+ </div>
+ <div v-if="definitionPath || isCurrentDefinition" class="popover-body border-top">
+ <span v-if="isCurrentDefinition" class="gl-font-weight-bold gl-font-base">
+ {{ s__('CodeIntelligence|This is the definition') }}
+ </span>
+ <gl-button
+ v-else
+ :href="definitionPath"
+ :target="isDefinitionCurrentBlob ? null : '_blank'"
+ class="w-100"
+ variant="default"
+ data-testid="go-to-definition-btn"
+ >
+ {{ __('Go to definition') }}
+ </gl-button>
+ </div>
+ </gl-tab>
+ <gl-tab data-testid="references-tab" class="py-2">
+ <template #title>
+ {{ __('References') }}
+ <gl-badge size="sm" class="gl-tab-counter-badge">{{ references.length }}</gl-badge>
+ </template>
+ <template v-if="references.length">
+ <div v-for="(reference, index) in references" :key="index" class="gl-dropdown-item">
+ <gl-link
+ :href="`${definitionPathPrefix}/${reference.path}`"
+ class="dropdown-item"
+ data-testid="reference-link"
+ >
+ {{ reference.path }}
+ </gl-link>
+ </div>
+ </template>
+ <p v-else class="gl-my-4 gl-px-4">
+ {{ s__('CodeNavigation|No references found') }}
+ </p>
+ </gl-tab>
+ </gl-tabs>
</div>
</template>
diff --git a/app/assets/javascripts/code_navigation/store/actions.js b/app/assets/javascripts/code_navigation/store/actions.js
index 7b2669691bd..9a472ca014f 100644
--- a/app/assets/javascripts/code_navigation/store/actions.js
+++ b/app/assets/javascripts/code_navigation/store/actions.js
@@ -18,7 +18,10 @@ export default {
.then(({ data }) => {
const normalizedData = data.reduce((acc, d) => {
if (d.hover) {
- acc[`${d.start_line}:${d.start_char}`] = d;
+ acc[`${d.start_line}:${d.start_char}`] = {
+ ...d,
+ definitionLineNumber: parseInt(d.definition_path?.split('#L').pop() || 0, 10),
+ };
addInteractionClass(path, d);
}
return acc;
@@ -67,6 +70,7 @@ export default {
x: x || 0,
y: y + window.scrollY || 0,
height: el.offsetHeight,
+ lineIndex: parseInt(lineIndex, 10),
};
definition = data[`${lineIndex}:${charIndex}`];
diff --git a/app/assets/javascripts/code_navigation/utils/index.js b/app/assets/javascripts/code_navigation/utils/index.js
index 4d118852a94..bb33bc556af 100644
--- a/app/assets/javascripts/code_navigation/utils/index.js
+++ b/app/assets/javascripts/code_navigation/utils/index.js
@@ -22,6 +22,7 @@ export const addInteractionClass = (path, d) => {
el.setAttribute('data-char-index', d.start_char);
el.setAttribute('data-line-index', d.start_line);
el.classList.add('cursor-pointer', 'code-navigation', 'js-code-navigation');
+ el.closest('.line').classList.add('code-navigation-line');
}
});
};
diff --git a/app/assets/javascripts/commit_merge_requests.js b/app/assets/javascripts/commit_merge_requests.js
index 3a0ab119df6..3cdb1587a3b 100644
--- a/app/assets/javascripts/commit_merge_requests.js
+++ b/app/assets/javascripts/commit_merge_requests.js
@@ -15,14 +15,14 @@ export function createHeader(childElementCount, mergeRequestCount) {
const headerText = getHeaderText(childElementCount, mergeRequestCount);
return $('<span />', {
- class: 'append-right-5',
+ class: 'gl-mr-2',
text: headerText,
});
}
export function createLink(mergeRequest) {
return $('<a />', {
- class: 'append-right-5',
+ class: 'gl-mr-2',
href: mergeRequest.path,
text: `!${mergeRequest.iid}`,
});
diff --git a/app/assets/javascripts/commons/polyfills.js b/app/assets/javascripts/commons/polyfills.js
index fdeb64a7644..655109bad9a 100644
--- a/app/assets/javascripts/commons/polyfills.js
+++ b/app/assets/javascripts/commons/polyfills.js
@@ -1,27 +1,24 @@
-// Browser polyfills
-
-/**
- * Polyfill: fetch
- * @what https://fetch.spec.whatwg.org/
- * @why Because Apollo GraphQL client relies on fetch
- * @browsers Internet Explorer 11
- * @see https://caniuse.com/#feat=fetch
- */
-import 'unfetch/polyfill/index';
-
/**
- * Polyfill: FormData APIs
- * @what delete(), get(), getAll(), has(), set(), entries(), keys(), values(),
- * and support for for...of
- * @why Because Apollo GraphQL client relies on fetch
- * @browsers Internet Explorer 11, Edge < 18
- * @see https://caniuse.com/#feat=mdn-api_formdata and subfeatures
+ * Polyfill
+ * @what requestIdleCallback
+ * @why To align browser features
+ * @browsers Safari (all versions)
+ * @see https://caniuse.com/#feat=requestidlecallback
*/
-import 'formdata-polyfill';
+window.requestIdleCallback =
+ window.requestIdleCallback ||
+ function requestShim(cb) {
+ const start = Date.now();
+ return setTimeout(() => {
+ cb({
+ didTimeout: false,
+ timeRemaining: () => Math.max(0, 50 - (Date.now() - start)),
+ });
+ }, 1);
+ };
-import './polyfills/custom_event';
-import './polyfills/element';
-import './polyfills/event';
-import './polyfills/nodelist';
-import './polyfills/request_idle_callback';
-import './polyfills/svg';
+window.cancelIdleCallback =
+ window.cancelIdleCallback ||
+ function cancelShim(id) {
+ clearTimeout(id);
+ };
diff --git a/app/assets/javascripts/commons/polyfills/custom_event.js b/app/assets/javascripts/commons/polyfills/custom_event.js
deleted file mode 100644
index 6b14eff6f05..00000000000
--- a/app/assets/javascripts/commons/polyfills/custom_event.js
+++ /dev/null
@@ -1,21 +0,0 @@
-/**
- * Polyfill: CustomEvent constructor
- * @what new CustomEvent()
- * @why Certain features, e.g. notes utilize this
- * @browsers Internet Explorer 11
- * @see https://caniuse.com/#feat=customevent
- */
-if (typeof window.CustomEvent !== 'function') {
- window.CustomEvent = function CustomEvent(event, params) {
- const evt = document.createEvent('CustomEvent');
- const evtParams = {
- bubbles: false,
- cancelable: false,
- detail: undefined,
- ...params,
- };
- evt.initCustomEvent(event, evtParams.bubbles, evtParams.cancelable, evtParams.detail);
- return evt;
- };
- window.CustomEvent.prototype = Event;
-}
diff --git a/app/assets/javascripts/commons/polyfills/element.js b/app/assets/javascripts/commons/polyfills/element.js
deleted file mode 100644
index b13ceccf511..00000000000
--- a/app/assets/javascripts/commons/polyfills/element.js
+++ /dev/null
@@ -1,74 +0,0 @@
-/**
- * Polyfill
- * @what Element.classList
- * @why In order to align browser features
- * @browsers Internet Explorer 11
- * @see https://caniuse.com/#feat=classlist
- */
-import 'classlist-polyfill';
-
-/**
- * Polyfill
- * @what Element.closest
- * @why In order to align browser features
- * @browsers Internet Explorer 11
- * @see https://caniuse.com/#feat=element-closest
- */
-Element.prototype.closest =
- Element.prototype.closest ||
- function closest(selector, selectedElement = this) {
- if (!selectedElement) return null;
- return selectedElement.matches(selector)
- ? selectedElement
- : Element.prototype.closest(selector, selectedElement.parentElement);
- };
-
-/**
- * Polyfill
- * @what Element.matches
- * @why In order to align browser features
- * @browsers Internet Explorer 11
- * @see https://caniuse.com/#feat=mdn-api_element_matches
- */
-Element.prototype.matches =
- Element.prototype.matches ||
- Element.prototype.matchesSelector ||
- Element.prototype.mozMatchesSelector ||
- Element.prototype.msMatchesSelector ||
- Element.prototype.oMatchesSelector ||
- Element.prototype.webkitMatchesSelector ||
- function matches(selector) {
- const elms = (this.document || this.ownerDocument).querySelectorAll(selector);
- let i = elms.length - 1;
- while (i >= 0 && elms.item(i) !== this) {
- i -= 1;
- }
- return i > -1;
- };
-
-/**
- * Polyfill
- * @what ChildNode.remove, Element.remove, CharacterData.remove, DocumentType.remove
- * @why In order to align browser features
- * @browsers Internet Explorer 11
- * @see https://caniuse.com/#feat=childnode-remove
- *
- * From the polyfill on MDN, https://developer.mozilla.org/en-US/docs/Web/API/ChildNode/remove#Polyfill
- */
-(arr => {
- arr.forEach(item => {
- if (Object.prototype.hasOwnProperty.call(item, 'remove')) {
- return;
- }
- Object.defineProperty(item, 'remove', {
- configurable: true,
- enumerable: true,
- writable: true,
- value: function remove() {
- if (this.parentNode !== null) {
- this.parentNode.removeChild(this);
- }
- },
- });
- });
-})([Element.prototype, CharacterData.prototype, DocumentType.prototype]);
diff --git a/app/assets/javascripts/commons/polyfills/event.js b/app/assets/javascripts/commons/polyfills/event.js
deleted file mode 100644
index 543dd5f9a93..00000000000
--- a/app/assets/javascripts/commons/polyfills/event.js
+++ /dev/null
@@ -1,22 +0,0 @@
-/**
- * Polyfill: Event constructor
- * @what new Event()
- * @why To align browser support
- * @browsers Internet Explorer 11
- * @see https://caniuse.com/#feat=mdn-api_event_event
- *
- * Although `initEvent` is deprecated for modern browsers it is the one supported by IE
- */
-if (typeof window.Event !== 'function') {
- window.Event = function Event(event, params) {
- const evt = document.createEvent('Event');
- const evtParams = {
- bubbles: false,
- cancelable: false,
- ...params,
- };
- evt.initEvent(event, evtParams.bubbles, evtParams.cancelable);
- return evt;
- };
- window.Event.prototype = Event;
-}
diff --git a/app/assets/javascripts/commons/polyfills/nodelist.js b/app/assets/javascripts/commons/polyfills/nodelist.js
deleted file mode 100644
index 3a9111e64f8..00000000000
--- a/app/assets/javascripts/commons/polyfills/nodelist.js
+++ /dev/null
@@ -1,14 +0,0 @@
-/**
- * Polyfill
- * @what NodeList.forEach
- * @why To align browser support
- * @browsers Internet Explorer 11
- * @see https://caniuse.com/#feat=mdn-api_nodelist_foreach
- */
-if (window.NodeList && !NodeList.prototype.forEach) {
- NodeList.prototype.forEach = function forEach(callback, thisArg = window) {
- for (let i = 0; i < this.length; i += 1) {
- callback.call(thisArg, this[i], i, this);
- }
- };
-}
diff --git a/app/assets/javascripts/commons/polyfills/request_idle_callback.js b/app/assets/javascripts/commons/polyfills/request_idle_callback.js
deleted file mode 100644
index 51dc82e593a..00000000000
--- a/app/assets/javascripts/commons/polyfills/request_idle_callback.js
+++ /dev/null
@@ -1,24 +0,0 @@
-/**
- * Polyfill
- * @what requestIdleCallback
- * @why To align browser features
- * @browsers Safari (all versions), Internet Explorer 11
- * @see https://caniuse.com/#feat=requestidlecallback
- */
-window.requestIdleCallback =
- window.requestIdleCallback ||
- function requestShim(cb) {
- const start = Date.now();
- return setTimeout(() => {
- cb({
- didTimeout: false,
- timeRemaining: () => Math.max(0, 50 - (Date.now() - start)),
- });
- }, 1);
- };
-
-window.cancelIdleCallback =
- window.cancelIdleCallback ||
- function cancelShim(id) {
- clearTimeout(id);
- };
diff --git a/app/assets/javascripts/commons/polyfills/svg.js b/app/assets/javascripts/commons/polyfills/svg.js
deleted file mode 100644
index 92a8b03fbb4..00000000000
--- a/app/assets/javascripts/commons/polyfills/svg.js
+++ /dev/null
@@ -1,11 +0,0 @@
-/**
- * polyfill support for external SVG file references via <use xlink:href>
- * @what polyfill support for external SVG file references via <use xlink:href>
- * @why This is used in our GitLab SVG icon library
- * @browsers Internet Explorer 11
- * @see https://caniuse.com/#feat=mdn-svg_elements_use_external_uri
- * @see https//css-tricks.com/svg-use-external-source/
- */
-import svg4everybody from 'svg4everybody';
-
-svg4everybody();
diff --git a/app/assets/javascripts/cycle_analytics/components/stage_nav_item.vue b/app/assets/javascripts/cycle_analytics/components/stage_nav_item.vue
index 3c18608eb75..4b15bd55cbd 100644
--- a/app/assets/javascripts/cycle_analytics/components/stage_nav_item.vue
+++ b/app/assets/javascripts/cycle_analytics/components/stage_nav_item.vue
@@ -25,11 +25,6 @@ export default {
default: '',
required: false,
},
- canEdit: {
- type: Boolean,
- default: false,
- required: false,
- },
},
computed: {
hasValue() {
diff --git a/app/assets/javascripts/deploy_keys/components/app.vue b/app/assets/javascripts/deploy_keys/components/app.vue
index 5505704f430..1b8668b533e 100644
--- a/app/assets/javascripts/deploy_keys/components/app.vue
+++ b/app/assets/javascripts/deploy_keys/components/app.vue
@@ -7,12 +7,14 @@ import eventHub from '../eventhub';
import DeployKeysService from '../service';
import DeployKeysStore from '../store';
import KeysPanel from './keys_panel.vue';
+import Icon from '~/vue_shared/components/icon.vue';
export default {
components: {
KeysPanel,
NavigationTabs,
GlLoadingIcon,
+ Icon,
},
props: {
endpoint: {
@@ -115,7 +117,7 @@ export default {
</script>
<template>
- <div class="append-bottom-default deploy-keys">
+ <div class="gl-mb-3 deploy-keys">
<gl-loading-icon
v-if="isLoading && !hasKeys"
:label="s__('DeployKeys|Loading deploy keys')"
@@ -123,8 +125,8 @@ export default {
/>
<template v-else-if="hasKeys">
<div class="top-area scrolling-tabs-container inner-page-scroll-tabs">
- <div class="fade-left"><i class="fa fa-angle-left" aria-hidden="true"> </i></div>
- <div class="fade-right"><i class="fa fa-angle-right" aria-hidden="true"> </i></div>
+ <div class="fade-left"><icon name="chevron-lg-left" :size="12" /></div>
+ <div class="fade-right"><icon name="chevron-lg-right" :size="12" /></div>
<navigation-tabs :tabs="tabs" scope="deployKeys" @onChangeTab="onChangeTab" />
</div>
diff --git a/app/assets/javascripts/design_management/components/design_destroyer.vue b/app/assets/javascripts/design_management/components/design_destroyer.vue
index ad3f2736c4a..62460ca551c 100644
--- a/app/assets/javascripts/design_management/components/design_destroyer.vue
+++ b/app/assets/javascripts/design_management/components/design_destroyer.vue
@@ -1,7 +1,7 @@
<script>
import { ApolloMutation } from 'vue-apollo';
import getDesignListQuery from '../graphql/queries/get_design_list.query.graphql';
-import destroyDesignMutation from '../graphql/mutations/destroyDesign.mutation.graphql';
+import destroyDesignMutation from '../graphql/mutations/destroy_design.mutation.graphql';
import { updateStoreAfterDesignsDelete } from '../utils/cache_update';
export default {
diff --git a/app/assets/javascripts/design_management/components/design_notes/design_discussion.vue b/app/assets/javascripts/design_management/components/design_notes/design_discussion.vue
index 7e442bb295f..4aaf43e3a5b 100644
--- a/app/assets/javascripts/design_management/components/design_notes/design_discussion.vue
+++ b/app/assets/javascripts/design_management/components/design_notes/design_discussion.vue
@@ -5,9 +5,9 @@ import { s__ } from '~/locale';
import ReplyPlaceholder from '~/notes/components/discussion_reply_placeholder.vue';
import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
import allVersionsMixin from '../../mixins/all_versions';
-import createNoteMutation from '../../graphql/mutations/createNote.mutation.graphql';
+import createNoteMutation from '../../graphql/mutations/create_note.mutation.graphql';
import toggleResolveDiscussionMutation from '../../graphql/mutations/toggle_resolve_discussion.mutation.graphql';
-import getDesignQuery from '../../graphql/queries/getDesign.query.graphql';
+import getDesignQuery from '../../graphql/queries/get_design.query.graphql';
import activeDiscussionQuery from '../../graphql/queries/active_discussion.query.graphql';
import DesignNote from './design_note.vue';
import DesignReplyForm from './design_reply_form.vue';
diff --git a/app/assets/javascripts/design_management/components/design_notes/design_reply_form.vue b/app/assets/javascripts/design_management/components/design_notes/design_reply_form.vue
index 756da7f55aa..969034909f2 100644
--- a/app/assets/javascripts/design_management/components/design_notes/design_reply_form.vue
+++ b/app/assets/javascripts/design_management/components/design_notes/design_reply_form.vue
@@ -62,7 +62,7 @@ export default {
},
},
mounted() {
- this.$refs.textarea.focus();
+ this.focusInput();
},
methods: {
submitForm() {
@@ -75,6 +75,9 @@ export default {
this.$emit('cancelForm');
}
},
+ focusInput() {
+ this.$refs.textarea.focus();
+ },
},
};
</script>
diff --git a/app/assets/javascripts/design_management/components/toolbar/index.vue b/app/assets/javascripts/design_management/components/toolbar/index.vue
index ea9f7300981..b998dfc47b8 100644
--- a/app/assets/javascripts/design_management/components/toolbar/index.vue
+++ b/app/assets/javascripts/design_management/components/toolbar/index.vue
@@ -6,7 +6,7 @@ import timeagoMixin from '~/vue_shared/mixins/timeago';
import Pagination from './pagination.vue';
import DeleteButton from '../delete_button.vue';
import permissionsQuery from '../../graphql/queries/design_permissions.query.graphql';
-import appDataQuery from '../../graphql/queries/appData.query.graphql';
+import appDataQuery from '../../graphql/queries/app_data.query.graphql';
import { DESIGNS_ROUTE_NAME } from '../../router/constants';
export default {
diff --git a/app/assets/javascripts/design_management/components/upload/design_dropzone.vue b/app/assets/javascripts/design_management/components/upload/design_dropzone.vue
index e2e1fc8bfad..33261134c15 100644
--- a/app/assets/javascripts/design_management/components/upload/design_dropzone.vue
+++ b/app/assets/javascripts/design_management/components/upload/design_dropzone.vue
@@ -1,7 +1,7 @@
<script>
import { GlIcon, GlLink, GlSprintf } from '@gitlab/ui';
import createFlash from '~/flash';
-import uploadDesignMutation from '../../graphql/mutations/uploadDesign.mutation.graphql';
+import uploadDesignMutation from '../../graphql/mutations/upload_design.mutation.graphql';
import { UPLOAD_DESIGN_INVALID_FILETYPE_ERROR } from '../../utils/error_messages';
import { isValidDesignFile } from '../../utils/design_management_utils';
import { VALID_DATA_TRANSFER_TYPE, VALID_DESIGN_FILE_MIMETYPE } from '../../constants';
diff --git a/app/assets/javascripts/design_management/graphql/fragments/design.fragment.graphql b/app/assets/javascripts/design_management/graphql/fragments/design.fragment.graphql
index c1439c56ff5..4b1703e41c3 100644
--- a/app/assets/javascripts/design_management/graphql/fragments/design.fragment.graphql
+++ b/app/assets/javascripts/design_management/graphql/fragments/design.fragment.graphql
@@ -1,6 +1,6 @@
-#import "./designNote.fragment.graphql"
-#import "./designList.fragment.graphql"
-#import "./diffRefs.fragment.graphql"
+#import "./design_note.fragment.graphql"
+#import "./design_list.fragment.graphql"
+#import "./diff_refs.fragment.graphql"
#import "./discussion_resolved_status.fragment.graphql"
fragment DesignItem on Design {
diff --git a/app/assets/javascripts/design_management/graphql/fragments/designNote.fragment.graphql b/app/assets/javascripts/design_management/graphql/fragments/designNote.fragment.graphql
deleted file mode 100644
index cb7cfd89abf..00000000000
--- a/app/assets/javascripts/design_management/graphql/fragments/designNote.fragment.graphql
+++ /dev/null
@@ -1,29 +0,0 @@
-#import "./diffRefs.fragment.graphql"
-#import "~/graphql_shared/fragments/author.fragment.graphql"
-#import "./note_permissions.fragment.graphql"
-
-fragment DesignNote on Note {
- id
- author {
- ...Author
- }
- body
- bodyHtml
- createdAt
- resolved
- position {
- diffRefs {
- ...DesignDiffRefs
- }
- x
- y
- height
- width
- }
- userPermissions {
- ...DesignNotePermissions
- }
- discussion {
- id
- }
-}
diff --git a/app/assets/javascripts/design_management/graphql/fragments/designList.fragment.graphql b/app/assets/javascripts/design_management/graphql/fragments/design_list.fragment.graphql
index bc3132f9b42..bc3132f9b42 100644
--- a/app/assets/javascripts/design_management/graphql/fragments/designList.fragment.graphql
+++ b/app/assets/javascripts/design_management/graphql/fragments/design_list.fragment.graphql
diff --git a/app/assets/javascripts/design_management/graphql/fragments/design_note.fragment.graphql b/app/assets/javascripts/design_management/graphql/fragments/design_note.fragment.graphql
new file mode 100644
index 00000000000..26edd2c0be1
--- /dev/null
+++ b/app/assets/javascripts/design_management/graphql/fragments/design_note.fragment.graphql
@@ -0,0 +1,29 @@
+#import "./diff_refs.fragment.graphql"
+#import "~/graphql_shared/fragments/author.fragment.graphql"
+#import "./note_permissions.fragment.graphql"
+
+fragment DesignNote on Note {
+ id
+ author {
+ ...Author
+ }
+ body
+ bodyHtml
+ createdAt
+ resolved
+ position {
+ diffRefs {
+ ...DesignDiffRefs
+ }
+ x
+ y
+ height
+ width
+ }
+ userPermissions {
+ ...DesignNotePermissions
+ }
+ discussion {
+ id
+ }
+}
diff --git a/app/assets/javascripts/design_management/graphql/fragments/diffRefs.fragment.graphql b/app/assets/javascripts/design_management/graphql/fragments/diff_refs.fragment.graphql
index 984a55814b0..984a55814b0 100644
--- a/app/assets/javascripts/design_management/graphql/fragments/diffRefs.fragment.graphql
+++ b/app/assets/javascripts/design_management/graphql/fragments/diff_refs.fragment.graphql
diff --git a/app/assets/javascripts/design_management/graphql/mutations/createImageDiffNote.mutation.graphql b/app/assets/javascripts/design_management/graphql/mutations/createImageDiffNote.mutation.graphql
deleted file mode 100644
index 9e2931b23f2..00000000000
--- a/app/assets/javascripts/design_management/graphql/mutations/createImageDiffNote.mutation.graphql
+++ /dev/null
@@ -1,21 +0,0 @@
-#import "../fragments/designNote.fragment.graphql"
-
-mutation createImageDiffNote($input: CreateImageDiffNoteInput!) {
- createImageDiffNote(input: $input) {
- note {
- ...DesignNote
- discussion {
- id
- replyId
- notes {
- edges {
- node {
- ...DesignNote
- }
- }
- }
- }
- }
- errors
- }
-}
diff --git a/app/assets/javascripts/design_management/graphql/mutations/createNote.mutation.graphql b/app/assets/javascripts/design_management/graphql/mutations/createNote.mutation.graphql
deleted file mode 100644
index 3ae478d658e..00000000000
--- a/app/assets/javascripts/design_management/graphql/mutations/createNote.mutation.graphql
+++ /dev/null
@@ -1,10 +0,0 @@
-#import "../fragments/designNote.fragment.graphql"
-
-mutation createNote($input: CreateNoteInput!) {
- createNote(input: $input) {
- note {
- ...DesignNote
- }
- errors
- }
-}
diff --git a/app/assets/javascripts/design_management/graphql/mutations/create_image_diff_note.mutation.graphql b/app/assets/javascripts/design_management/graphql/mutations/create_image_diff_note.mutation.graphql
new file mode 100644
index 00000000000..c8ade328120
--- /dev/null
+++ b/app/assets/javascripts/design_management/graphql/mutations/create_image_diff_note.mutation.graphql
@@ -0,0 +1,21 @@
+#import "../fragments/design_note.fragment.graphql"
+
+mutation createImageDiffNote($input: CreateImageDiffNoteInput!) {
+ createImageDiffNote(input: $input) {
+ note {
+ ...DesignNote
+ discussion {
+ id
+ replyId
+ notes {
+ edges {
+ node {
+ ...DesignNote
+ }
+ }
+ }
+ }
+ }
+ errors
+ }
+}
diff --git a/app/assets/javascripts/design_management/graphql/mutations/create_note.mutation.graphql b/app/assets/javascripts/design_management/graphql/mutations/create_note.mutation.graphql
new file mode 100644
index 00000000000..184ee6955dc
--- /dev/null
+++ b/app/assets/javascripts/design_management/graphql/mutations/create_note.mutation.graphql
@@ -0,0 +1,10 @@
+#import "../fragments/design_note.fragment.graphql"
+
+mutation createNote($input: CreateNoteInput!) {
+ createNote(input: $input) {
+ note {
+ ...DesignNote
+ }
+ errors
+ }
+}
diff --git a/app/assets/javascripts/design_management/graphql/mutations/destroyDesign.mutation.graphql b/app/assets/javascripts/design_management/graphql/mutations/destroy_design.mutation.graphql
index 0b3cf636cdb..0b3cf636cdb 100644
--- a/app/assets/javascripts/design_management/graphql/mutations/destroyDesign.mutation.graphql
+++ b/app/assets/javascripts/design_management/graphql/mutations/destroy_design.mutation.graphql
diff --git a/app/assets/javascripts/design_management/graphql/mutations/toggle_resolve_discussion.mutation.graphql b/app/assets/javascripts/design_management/graphql/mutations/toggle_resolve_discussion.mutation.graphql
index d5f54ec9b58..1157fc05d5f 100644
--- a/app/assets/javascripts/design_management/graphql/mutations/toggle_resolve_discussion.mutation.graphql
+++ b/app/assets/javascripts/design_management/graphql/mutations/toggle_resolve_discussion.mutation.graphql
@@ -1,4 +1,4 @@
-#import "../fragments/designNote.fragment.graphql"
+#import "../fragments/design_note.fragment.graphql"
#import "../fragments/discussion_resolved_status.fragment.graphql"
mutation toggleResolveDiscussion($id: ID!, $resolve: Boolean!) {
diff --git a/app/assets/javascripts/design_management/graphql/mutations/updateImageDiffNote.mutation.graphql b/app/assets/javascripts/design_management/graphql/mutations/updateImageDiffNote.mutation.graphql
deleted file mode 100644
index cdb2264d233..00000000000
--- a/app/assets/javascripts/design_management/graphql/mutations/updateImageDiffNote.mutation.graphql
+++ /dev/null
@@ -1,10 +0,0 @@
-#import "../fragments/designNote.fragment.graphql"
-
-mutation updateImageDiffNote($input: UpdateImageDiffNoteInput!) {
- updateImageDiffNote(input: $input) {
- errors
- note {
- ...DesignNote
- }
- }
-}
diff --git a/app/assets/javascripts/design_management/graphql/mutations/update_active_discussion.mutation.graphql b/app/assets/javascripts/design_management/graphql/mutations/update_active_discussion.mutation.graphql
index 343de4e3025..a24b6737159 100644
--- a/app/assets/javascripts/design_management/graphql/mutations/update_active_discussion.mutation.graphql
+++ b/app/assets/javascripts/design_management/graphql/mutations/update_active_discussion.mutation.graphql
@@ -1,3 +1,3 @@
mutation updateActiveDiscussion($id: String, $source: String) {
- updateActiveDiscussion (id: $id, source: $source ) @client
+ updateActiveDiscussion(id: $id, source: $source) @client
}
diff --git a/app/assets/javascripts/design_management/graphql/mutations/update_image_diff_note.mutation.graphql b/app/assets/javascripts/design_management/graphql/mutations/update_image_diff_note.mutation.graphql
new file mode 100644
index 00000000000..5562ca9d89f
--- /dev/null
+++ b/app/assets/javascripts/design_management/graphql/mutations/update_image_diff_note.mutation.graphql
@@ -0,0 +1,10 @@
+#import "../fragments/design_note.fragment.graphql"
+
+mutation updateImageDiffNote($input: UpdateImageDiffNoteInput!) {
+ updateImageDiffNote(input: $input) {
+ errors
+ note {
+ ...DesignNote
+ }
+ }
+}
diff --git a/app/assets/javascripts/design_management/graphql/mutations/update_note.mutation.graphql b/app/assets/javascripts/design_management/graphql/mutations/update_note.mutation.graphql
index d96b2f3934a..b995e99fb6a 100644
--- a/app/assets/javascripts/design_management/graphql/mutations/update_note.mutation.graphql
+++ b/app/assets/javascripts/design_management/graphql/mutations/update_note.mutation.graphql
@@ -1,4 +1,4 @@
-#import "../fragments/designNote.fragment.graphql"
+#import "../fragments/design_note.fragment.graphql"
mutation updateNote($input: UpdateNoteInput!) {
updateNote(input: $input) {
diff --git a/app/assets/javascripts/design_management/graphql/mutations/uploadDesign.mutation.graphql b/app/assets/javascripts/design_management/graphql/mutations/uploadDesign.mutation.graphql
deleted file mode 100644
index 904acef599b..00000000000
--- a/app/assets/javascripts/design_management/graphql/mutations/uploadDesign.mutation.graphql
+++ /dev/null
@@ -1,21 +0,0 @@
-#import "../fragments/design.fragment.graphql"
-
-mutation uploadDesign($files: [Upload!]!, $projectPath: ID!, $iid: ID!) {
- designManagementUpload(input: { projectPath: $projectPath, iid: $iid, files: $files }) {
- designs {
- ...DesignItem
- versions {
- edges {
- node {
- id
- sha
- }
- }
- },
- }
- skippedDesigns {
- filename
- }
- errors
- }
-}
diff --git a/app/assets/javascripts/design_management/graphql/mutations/upload_design.mutation.graphql b/app/assets/javascripts/design_management/graphql/mutations/upload_design.mutation.graphql
new file mode 100644
index 00000000000..d694e6558a0
--- /dev/null
+++ b/app/assets/javascripts/design_management/graphql/mutations/upload_design.mutation.graphql
@@ -0,0 +1,21 @@
+#import "../fragments/design.fragment.graphql"
+
+mutation uploadDesign($files: [Upload!]!, $projectPath: ID!, $iid: ID!) {
+ designManagementUpload(input: { projectPath: $projectPath, iid: $iid, files: $files }) {
+ designs {
+ ...DesignItem
+ versions {
+ edges {
+ node {
+ id
+ sha
+ }
+ }
+ }
+ }
+ skippedDesigns {
+ filename
+ }
+ errors
+ }
+}
diff --git a/app/assets/javascripts/design_management/graphql/queries/appData.query.graphql b/app/assets/javascripts/design_management/graphql/queries/app_data.query.graphql
index e1269761206..e1269761206 100644
--- a/app/assets/javascripts/design_management/graphql/queries/appData.query.graphql
+++ b/app/assets/javascripts/design_management/graphql/queries/app_data.query.graphql
diff --git a/app/assets/javascripts/design_management/graphql/queries/getDesign.query.graphql b/app/assets/javascripts/design_management/graphql/queries/get_design.query.graphql
index 07a9af55787..07a9af55787 100644
--- a/app/assets/javascripts/design_management/graphql/queries/getDesign.query.graphql
+++ b/app/assets/javascripts/design_management/graphql/queries/get_design.query.graphql
diff --git a/app/assets/javascripts/design_management/graphql/queries/get_design_list.query.graphql b/app/assets/javascripts/design_management/graphql/queries/get_design_list.query.graphql
index 857f205ab07..121a50555b3 100644
--- a/app/assets/javascripts/design_management/graphql/queries/get_design_list.query.graphql
+++ b/app/assets/javascripts/design_management/graphql/queries/get_design_list.query.graphql
@@ -1,4 +1,4 @@
-#import "../fragments/designList.fragment.graphql"
+#import "../fragments/design_list.fragment.graphql"
#import "../fragments/version.fragment.graphql"
query getDesignList($fullPath: ID!, $iid: String!, $atVersion: ID) {
diff --git a/app/assets/javascripts/design_management/index.js b/app/assets/javascripts/design_management/index.js
index eb00e1742ea..1fc5779515a 100644
--- a/app/assets/javascripts/design_management/index.js
+++ b/app/assets/javascripts/design_management/index.js
@@ -1,3 +1,6 @@
+// This application is being moved, please do not touch this files
+// Please see https://gitlab.com/gitlab-org/gitlab/-/issues/14744#note_364468096 for details
+
import $ from 'jquery';
import Vue from 'vue';
import createRouter from './router';
diff --git a/app/assets/javascripts/design_management/mixins/all_versions.js b/app/assets/javascripts/design_management/mixins/all_versions.js
index 41c93064c26..3966fe71732 100644
--- a/app/assets/javascripts/design_management/mixins/all_versions.js
+++ b/app/assets/javascripts/design_management/mixins/all_versions.js
@@ -1,5 +1,5 @@
import getDesignListQuery from '../graphql/queries/get_design_list.query.graphql';
-import appDataQuery from '../graphql/queries/appData.query.graphql';
+import appDataQuery from '../graphql/queries/app_data.query.graphql';
import { findVersionId } from '../utils/design_management_utils';
export default {
diff --git a/app/assets/javascripts/design_management/pages/design/index.vue b/app/assets/javascripts/design_management/pages/design/index.vue
index fe121b6530a..9a959222e22 100644
--- a/app/assets/javascripts/design_management/pages/design/index.vue
+++ b/app/assets/javascripts/design_management/pages/design/index.vue
@@ -11,10 +11,10 @@ import DesignScaler from '../../components/design_scaler.vue';
import DesignPresentation from '../../components/design_presentation.vue';
import DesignReplyForm from '../../components/design_notes/design_reply_form.vue';
import DesignSidebar from '../../components/design_sidebar.vue';
-import getDesignQuery from '../../graphql/queries/getDesign.query.graphql';
-import appDataQuery from '../../graphql/queries/appData.query.graphql';
-import createImageDiffNoteMutation from '../../graphql/mutations/createImageDiffNote.mutation.graphql';
-import updateImageDiffNoteMutation from '../../graphql/mutations/updateImageDiffNote.mutation.graphql';
+import getDesignQuery from '../../graphql/queries/get_design.query.graphql';
+import appDataQuery from '../../graphql/queries/app_data.query.graphql';
+import createImageDiffNoteMutation from '../../graphql/mutations/create_image_diff_note.mutation.graphql';
+import updateImageDiffNoteMutation from '../../graphql/mutations/update_image_diff_note.mutation.graphql';
import updateActiveDiscussionMutation from '../../graphql/mutations/update_active_discussion.mutation.graphql';
import {
extractDiscussions,
@@ -254,6 +254,9 @@ export default {
},
openCommentForm(annotationCoordinates) {
this.annotationCoordinates = annotationCoordinates;
+ if (this.$refs.newDiscussionForm) {
+ this.$refs.newDiscussionForm.focusInput();
+ }
},
closeCommentForm() {
this.comment = '';
@@ -361,6 +364,7 @@ export default {
@error="onCreateImageDiffNoteError"
>
<design-reply-form
+ ref="newDiscussionForm"
v-model="comment"
:is-saving="loading"
:markdown-preview-path="markdownPreviewPath"
diff --git a/app/assets/javascripts/design_management/pages/index.vue b/app/assets/javascripts/design_management/pages/index.vue
index 922c800009f..d14a1fc8c1c 100644
--- a/app/assets/javascripts/design_management/pages/index.vue
+++ b/app/assets/javascripts/design_management/pages/index.vue
@@ -8,7 +8,7 @@ import Design from '../components/list/item.vue';
import DesignDestroyer from '../components/design_destroyer.vue';
import DesignVersionDropdown from '../components/upload/design_version_dropdown.vue';
import DesignDropzone from '../components/upload/design_dropzone.vue';
-import uploadDesignMutation from '../graphql/mutations/uploadDesign.mutation.graphql';
+import uploadDesignMutation from '../graphql/mutations/upload_design.mutation.graphql';
import permissionsQuery from '../graphql/queries/design_permissions.query.graphql';
import getDesignListQuery from '../graphql/queries/get_design_list.query.graphql';
import allDesignsMixin from '../mixins/all_designs';
diff --git a/app/assets/javascripts/design_management/utils/tracking.js b/app/assets/javascripts/design_management/utils/tracking.js
index 39c20376271..b3ecc1453a6 100644
--- a/app/assets/javascripts/design_management/utils/tracking.js
+++ b/app/assets/javascripts/design_management/utils/tracking.js
@@ -1,18 +1,9 @@
import Tracking from '~/tracking';
-function assembleDesignPayload(payloadArr) {
- return {
- value: {
- 'internal-object-refrerer': payloadArr[0],
- 'design-collection-owner': payloadArr[1],
- 'design-version-number': payloadArr[2],
- 'design-is-current-version': payloadArr[3],
- },
- };
-}
-
// Tracking Constants
+const DESIGN_TRACKING_CONTEXT_SCHEMA = 'iglu:com.gitlab/design_management_context/jsonschema/1-0-0';
const DESIGN_TRACKING_PAGE_NAME = 'projects:issues:design';
+const DESIGN_TRACKING_EVENT_NAME = 'view_design';
// eslint-disable-next-line import/prefer-default-export
export function trackDesignDetailView(
@@ -21,8 +12,16 @@ export function trackDesignDetailView(
designVersion = 1,
latestVersion = false,
) {
- Tracking.event(DESIGN_TRACKING_PAGE_NAME, 'design_viewed', {
- label: 'design_viewed',
- ...assembleDesignPayload([referer, owner, designVersion, latestVersion]),
+ Tracking.event(DESIGN_TRACKING_PAGE_NAME, DESIGN_TRACKING_EVENT_NAME, {
+ label: DESIGN_TRACKING_EVENT_NAME,
+ context: {
+ schema: DESIGN_TRACKING_CONTEXT_SCHEMA,
+ data: {
+ 'design-version-number': designVersion,
+ 'design-is-current-version': latestVersion,
+ 'internal-object-referrer': referer,
+ 'design-collection-owner': owner,
+ },
+ },
});
}
diff --git a/app/assets/javascripts/design_management_new/components/app.vue b/app/assets/javascripts/design_management_new/components/app.vue
new file mode 100644
index 00000000000..98240aef810
--- /dev/null
+++ b/app/assets/javascripts/design_management_new/components/app.vue
@@ -0,0 +1,3 @@
+<template>
+ <router-view />
+</template>
diff --git a/app/assets/javascripts/design_management_new/components/delete_button.vue b/app/assets/javascripts/design_management_new/components/delete_button.vue
new file mode 100644
index 00000000000..77e1b97a227
--- /dev/null
+++ b/app/assets/javascripts/design_management_new/components/delete_button.vue
@@ -0,0 +1,81 @@
+<script>
+import { GlButton, GlModal, GlModalDirective } from '@gitlab/ui';
+import { uniqueId } from 'lodash';
+import { s__ } from '~/locale';
+
+export default {
+ name: 'DeleteButton',
+ components: {
+ GlButton,
+ GlModal,
+ },
+ directives: {
+ GlModalDirective,
+ },
+ props: {
+ isDeleting: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ buttonClass: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ buttonVariant: {
+ type: String,
+ required: false,
+ default: 'info',
+ },
+ buttonSize: {
+ type: String,
+ required: false,
+ default: 'medium',
+ },
+ hasSelectedDesigns: {
+ type: Boolean,
+ required: false,
+ default: true,
+ },
+ },
+ data() {
+ return {
+ modalId: uniqueId('design-deletion-confirmation-'),
+ };
+ },
+ modal: {
+ title: s__('DesignManagement|Delete designs confirmation'),
+ actionPrimary: {
+ text: s__('Delete'),
+ attributes: { variant: 'danger' },
+ },
+ actionCancel: {
+ text: s__('Cancel'),
+ },
+ },
+};
+</script>
+
+<template>
+ <div class="gl-display-flex gl-align-items-center gl-h-full">
+ <gl-modal
+ :modal-id="modalId"
+ :title="$options.modal.title"
+ :action-primary="$options.modal.actionPrimary"
+ :action-cancel="$options.modal.actionCancel"
+ @ok="$emit('deleteSelectedDesigns')"
+ >
+ <p>{{ s__('DesignManagement|Are you sure you want to delete the selected designs?') }}</p>
+ </gl-modal>
+ <gl-button
+ v-gl-modal-directive="modalId"
+ :variant="buttonVariant"
+ :size="buttonSize"
+ :class="buttonClass"
+ :disabled="isDeleting || !hasSelectedDesigns"
+ >
+ <slot></slot>
+ </gl-button>
+ </div>
+</template>
diff --git a/app/assets/javascripts/design_management_new/components/design_destroyer.vue b/app/assets/javascripts/design_management_new/components/design_destroyer.vue
new file mode 100644
index 00000000000..7ae569216f0
--- /dev/null
+++ b/app/assets/javascripts/design_management_new/components/design_destroyer.vue
@@ -0,0 +1,67 @@
+<script>
+import { ApolloMutation } from 'vue-apollo';
+import getDesignListQuery from '../graphql/queries/get_design_list.query.graphql';
+import destroyDesignMutation from '../graphql/mutations/destroy_design.mutation.graphql';
+import { updateStoreAfterDesignsDelete } from '../utils/cache_update';
+
+export default {
+ components: {
+ ApolloMutation,
+ },
+ props: {
+ filenames: {
+ type: Array,
+ required: true,
+ },
+ },
+ inject: {
+ projectPath: {
+ default: '',
+ },
+ iid: {
+ from: 'issueIid',
+ defaut: '',
+ },
+ },
+ computed: {
+ projectQueryBody() {
+ return {
+ query: getDesignListQuery,
+ variables: { fullPath: this.projectPath, iid: this.iid, atVersion: null },
+ };
+ },
+ },
+ methods: {
+ updateStoreAfterDelete(
+ store,
+ {
+ data: { designManagementDelete },
+ },
+ ) {
+ updateStoreAfterDesignsDelete(
+ store,
+ designManagementDelete,
+ this.projectQueryBody,
+ this.filenames,
+ );
+ },
+ },
+ destroyDesignMutation,
+};
+</script>
+
+<template>
+ <apollo-mutation
+ #default="{ mutate, loading, error }"
+ :mutation="$options.destroyDesignMutation"
+ :variables="{
+ filenames,
+ projectPath,
+ iid,
+ }"
+ :update="updateStoreAfterDelete"
+ v-on="$listeners"
+ >
+ <slot v-bind="{ mutate, loading, error }"></slot>
+ </apollo-mutation>
+</template>
diff --git a/app/assets/javascripts/design_management_new/components/design_note_pin.vue b/app/assets/javascripts/design_management_new/components/design_note_pin.vue
new file mode 100644
index 00000000000..0811397fbad
--- /dev/null
+++ b/app/assets/javascripts/design_management_new/components/design_note_pin.vue
@@ -0,0 +1,61 @@
+<script>
+import { __, sprintf } from '~/locale';
+import Icon from '~/vue_shared/components/icon.vue';
+
+export default {
+ name: 'DesignNotePin',
+ components: {
+ Icon,
+ },
+ props: {
+ position: {
+ type: Object,
+ required: true,
+ },
+ label: {
+ type: Number,
+ required: false,
+ default: null,
+ },
+ repositioning: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ },
+ computed: {
+ isNewNote() {
+ return this.label === null;
+ },
+ pinStyle() {
+ return this.repositioning ? { ...this.position, cursor: 'move' } : this.position;
+ },
+ pinLabel() {
+ return this.isNewNote
+ ? __('Comment form position')
+ : sprintf(__("Comment '%{label}' position"), { label: this.label });
+ },
+ },
+};
+</script>
+
+<template>
+ <button
+ :style="pinStyle"
+ :aria-label="pinLabel"
+ :class="{
+ 'btn-transparent comment-indicator': isNewNote,
+ 'js-image-badge badge badge-pill': !isNewNote,
+ }"
+ class="design-pin gl-absolute gl-display-flex gl-align-items-center gl-justify-content-center"
+ type="button"
+ @mousedown="$emit('mousedown', $event)"
+ @mouseup="$emit('mouseup', $event)"
+ @click="$emit('click', $event)"
+ >
+ <icon v-if="isNewNote" name="image-comment-dark" />
+ <template v-else>
+ {{ label }}
+ </template>
+ </button>
+</template>
diff --git a/app/assets/javascripts/design_management_new/components/design_notes/design_discussion.vue b/app/assets/javascripts/design_management_new/components/design_notes/design_discussion.vue
new file mode 100644
index 00000000000..4aaf43e3a5b
--- /dev/null
+++ b/app/assets/javascripts/design_management_new/components/design_notes/design_discussion.vue
@@ -0,0 +1,297 @@
+<script>
+import { ApolloMutation } from 'vue-apollo';
+import { GlTooltipDirective, GlIcon, GlLoadingIcon, GlLink } from '@gitlab/ui';
+import { s__ } from '~/locale';
+import ReplyPlaceholder from '~/notes/components/discussion_reply_placeholder.vue';
+import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
+import allVersionsMixin from '../../mixins/all_versions';
+import createNoteMutation from '../../graphql/mutations/create_note.mutation.graphql';
+import toggleResolveDiscussionMutation from '../../graphql/mutations/toggle_resolve_discussion.mutation.graphql';
+import getDesignQuery from '../../graphql/queries/get_design.query.graphql';
+import activeDiscussionQuery from '../../graphql/queries/active_discussion.query.graphql';
+import DesignNote from './design_note.vue';
+import DesignReplyForm from './design_reply_form.vue';
+import { updateStoreAfterAddDiscussionComment } from '../../utils/cache_update';
+import { ACTIVE_DISCUSSION_SOURCE_TYPES } from '../../constants';
+import ToggleRepliesWidget from './toggle_replies_widget.vue';
+
+export default {
+ components: {
+ ApolloMutation,
+ DesignNote,
+ ReplyPlaceholder,
+ DesignReplyForm,
+ GlIcon,
+ GlLoadingIcon,
+ GlLink,
+ ToggleRepliesWidget,
+ TimeAgoTooltip,
+ },
+ directives: {
+ GlTooltip: GlTooltipDirective,
+ },
+ mixins: [allVersionsMixin],
+ props: {
+ discussion: {
+ type: Object,
+ required: true,
+ },
+ noteableId: {
+ type: String,
+ required: true,
+ },
+ designId: {
+ type: String,
+ required: true,
+ },
+ markdownPreviewPath: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ resolvedDiscussionsExpanded: {
+ type: Boolean,
+ required: true,
+ },
+ discussionWithOpenForm: {
+ type: String,
+ required: true,
+ },
+ },
+ apollo: {
+ activeDiscussion: {
+ query: activeDiscussionQuery,
+ result({ data }) {
+ const discussionId = data.activeDiscussion.id;
+ if (this.discussion.resolved && !this.resolvedDiscussionsExpanded) {
+ return;
+ }
+ // We watch any changes to the active discussion from the design pins and scroll to this discussion if it exists
+ // We don't want scrollIntoView to be triggered from the discussion click itself
+ if (
+ discussionId &&
+ data.activeDiscussion.source === ACTIVE_DISCUSSION_SOURCE_TYPES.pin &&
+ discussionId === this.discussion.notes[0].id
+ ) {
+ this.$el.scrollIntoView({
+ behavior: 'smooth',
+ inline: 'start',
+ });
+ }
+ },
+ },
+ },
+ data() {
+ return {
+ discussionComment: '',
+ isFormRendered: false,
+ activeDiscussion: {},
+ isResolving: false,
+ shouldChangeResolvedStatus: false,
+ areRepliesCollapsed: this.discussion.resolved,
+ };
+ },
+ computed: {
+ mutationPayload() {
+ return {
+ noteableId: this.noteableId,
+ body: this.discussionComment,
+ discussionId: this.discussion.id,
+ };
+ },
+ designVariables() {
+ return {
+ fullPath: this.projectPath,
+ iid: this.issueIid,
+ filenames: [this.$route.params.id],
+ atVersion: this.designsVersion,
+ };
+ },
+ isDiscussionHighlighted() {
+ return this.discussion.notes[0].id === this.activeDiscussion.id;
+ },
+ resolveCheckboxText() {
+ return this.discussion.resolved
+ ? s__('DesignManagement|Unresolve thread')
+ : s__('DesignManagement|Resolve thread');
+ },
+ firstNote() {
+ return this.discussion.notes[0];
+ },
+ discussionReplies() {
+ return this.discussion.notes.slice(1);
+ },
+ areRepliesShown() {
+ return !this.discussion.resolved || !this.areRepliesCollapsed;
+ },
+ resolveIconName() {
+ return this.discussion.resolved ? 'check-circle-filled' : 'check-circle';
+ },
+ isRepliesWidgetVisible() {
+ return this.discussion.resolved && this.discussionReplies.length > 0;
+ },
+ isReplyPlaceholderVisible() {
+ return this.areRepliesShown || !this.discussionReplies.length;
+ },
+ isFormVisible() {
+ return this.isFormRendered && this.discussionWithOpenForm === this.discussion.id;
+ },
+ },
+ methods: {
+ addDiscussionComment(
+ store,
+ {
+ data: { createNote },
+ },
+ ) {
+ updateStoreAfterAddDiscussionComment(
+ store,
+ createNote,
+ getDesignQuery,
+ this.designVariables,
+ this.discussion.id,
+ );
+ },
+ onDone() {
+ this.discussionComment = '';
+ this.hideForm();
+ if (this.shouldChangeResolvedStatus) {
+ this.toggleResolvedStatus();
+ }
+ },
+ onCreateNoteError(err) {
+ this.$emit('createNoteError', err);
+ },
+ hideForm() {
+ this.isFormRendered = false;
+ this.discussionComment = '';
+ },
+ showForm() {
+ this.$emit('openForm', this.discussion.id);
+ this.isFormRendered = true;
+ },
+ toggleResolvedStatus() {
+ this.isResolving = true;
+ this.$apollo
+ .mutate({
+ mutation: toggleResolveDiscussionMutation,
+ variables: { id: this.discussion.id, resolve: !this.discussion.resolved },
+ })
+ .then(({ data }) => {
+ if (data.errors?.length > 0) {
+ this.$emit('resolveDiscussionError', data.errors[0]);
+ }
+ })
+ .catch(err => {
+ this.$emit('resolveDiscussionError', err);
+ })
+ .finally(() => {
+ this.isResolving = false;
+ });
+ },
+ },
+ createNoteMutation,
+};
+</script>
+
+<template>
+ <div class="design-discussion-wrapper">
+ <div
+ class="badge badge-pill gl-display-flex gl-align-items-center gl-justify-content-center"
+ :class="{ resolved: discussion.resolved }"
+ type="button"
+ >
+ {{ discussion.index }}
+ </div>
+ <ul
+ class="design-discussion bordered-box gl-relative gl-p-0 gl-list-style-none"
+ data-qa-selector="design_discussion_content"
+ >
+ <design-note
+ :note="firstNote"
+ :markdown-preview-path="markdownPreviewPath"
+ :is-resolving="isResolving"
+ :class="{ 'gl-bg-blue-50': isDiscussionHighlighted }"
+ @error="$emit('updateNoteError', $event)"
+ >
+ <template v-if="discussion.resolvable" #resolveDiscussion>
+ <button
+ v-gl-tooltip
+ :class="{ 'is-active': discussion.resolved }"
+ :title="resolveCheckboxText"
+ :aria-label="resolveCheckboxText"
+ type="button"
+ class="line-resolve-btn note-action-button gl-mr-3"
+ data-testid="resolve-button"
+ @click.stop="toggleResolvedStatus"
+ >
+ <gl-icon v-if="!isResolving" :name="resolveIconName" data-testid="resolve-icon" />
+ <gl-loading-icon v-else inline />
+ </button>
+ </template>
+ <template v-if="discussion.resolved" #resolvedStatus>
+ <p class="gl-text-gray-700 gl-font-sm gl-m-0 gl-mt-5" data-testid="resolved-message">
+ {{ __('Resolved by') }}
+ <gl-link
+ class="gl-text-gray-700 gl-text-decoration-none gl-font-sm link-inherit-color"
+ :href="discussion.resolvedBy.webUrl"
+ target="_blank"
+ >{{ discussion.resolvedBy.name }}</gl-link
+ >
+ <time-ago-tooltip :time="discussion.resolvedAt" tooltip-placement="bottom" />
+ </p>
+ </template>
+ </design-note>
+ <toggle-replies-widget
+ v-if="isRepliesWidgetVisible"
+ :collapsed="areRepliesCollapsed"
+ :replies="discussionReplies"
+ @toggle="areRepliesCollapsed = !areRepliesCollapsed"
+ />
+ <design-note
+ v-for="note in discussionReplies"
+ v-show="areRepliesShown"
+ :key="note.id"
+ :note="note"
+ :markdown-preview-path="markdownPreviewPath"
+ :is-resolving="isResolving"
+ :class="{ 'gl-bg-blue-50': isDiscussionHighlighted }"
+ @error="$emit('updateNoteError', $event)"
+ />
+ <li v-show="isReplyPlaceholderVisible" class="reply-wrapper">
+ <reply-placeholder
+ v-if="!isFormVisible"
+ class="qa-discussion-reply"
+ :button-text="__('Reply...')"
+ @onClick="showForm"
+ />
+ <apollo-mutation
+ v-else
+ #default="{ mutate, loading }"
+ :mutation="$options.createNoteMutation"
+ :variables="{
+ input: mutationPayload,
+ }"
+ :update="addDiscussionComment"
+ @done="onDone"
+ @error="onCreateNoteError"
+ >
+ <design-reply-form
+ v-model="discussionComment"
+ :is-saving="loading"
+ :markdown-preview-path="markdownPreviewPath"
+ @submitForm="mutate"
+ @cancelForm="hideForm"
+ >
+ <template v-if="discussion.resolvable" #resolveCheckbox>
+ <label data-testid="resolve-checkbox">
+ <input v-model="shouldChangeResolvedStatus" type="checkbox" />
+ {{ resolveCheckboxText }}
+ </label>
+ </template>
+ </design-reply-form>
+ </apollo-mutation>
+ </li>
+ </ul>
+ </div>
+</template>
diff --git a/app/assets/javascripts/design_management_new/components/design_notes/design_note.vue b/app/assets/javascripts/design_management_new/components/design_notes/design_note.vue
new file mode 100644
index 00000000000..172e61920ef
--- /dev/null
+++ b/app/assets/javascripts/design_management_new/components/design_notes/design_note.vue
@@ -0,0 +1,156 @@
+<script>
+import { ApolloMutation } from 'vue-apollo';
+import { GlTooltipDirective, GlIcon } from '@gitlab/ui';
+import updateNoteMutation from '../../graphql/mutations/update_note.mutation.graphql';
+import UserAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link.vue';
+import TimelineEntryItem from '~/vue_shared/components/notes/timeline_entry_item.vue';
+import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
+import DesignReplyForm from './design_reply_form.vue';
+import { findNoteId } from '../../utils/design_management_utils';
+import { hasErrors } from '../../utils/cache_update';
+
+export default {
+ components: {
+ UserAvatarLink,
+ TimelineEntryItem,
+ TimeAgoTooltip,
+ DesignReplyForm,
+ ApolloMutation,
+ GlIcon,
+ },
+ directives: {
+ GlTooltip: GlTooltipDirective,
+ },
+ props: {
+ note: {
+ type: Object,
+ required: true,
+ },
+ markdownPreviewPath: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ },
+ data() {
+ return {
+ noteText: this.note.body,
+ isEditing: false,
+ };
+ },
+ computed: {
+ author() {
+ return this.note.author;
+ },
+ noteAnchorId() {
+ return findNoteId(this.note.id);
+ },
+ isNoteLinked() {
+ return this.$route.hash === `#note_${this.noteAnchorId}`;
+ },
+ mutationPayload() {
+ return {
+ id: this.note.id,
+ body: this.noteText,
+ };
+ },
+ isEditButtonVisible() {
+ return !this.isEditing && this.note.userPermissions.adminNote;
+ },
+ },
+ mounted() {
+ if (this.isNoteLinked) {
+ this.$el.scrollIntoView({ behavior: 'smooth', inline: 'start' });
+ }
+ },
+ methods: {
+ hideForm() {
+ this.isEditing = false;
+ this.noteText = this.note.body;
+ },
+ onDone({ data }) {
+ this.hideForm();
+ if (hasErrors(data.updateNote)) {
+ this.$emit('error', data.errors[0]);
+ }
+ },
+ },
+ updateNoteMutation,
+};
+</script>
+
+<template>
+ <timeline-entry-item :id="`note_${noteAnchorId}`" class="design-note note-form">
+ <user-avatar-link
+ :link-href="author.webUrl"
+ :img-src="author.avatarUrl"
+ :img-alt="author.username"
+ :img-size="40"
+ />
+ <div class="d-flex justify-content-between">
+ <div>
+ <a
+ v-once
+ :href="author.webUrl"
+ class="js-user-link"
+ :data-user-id="author.id"
+ :data-username="author.username"
+ >
+ <span class="note-header-author-name bold">{{ author.name }}</span>
+ <span v-if="author.status_tooltip_html" v-html="author.status_tooltip_html"></span>
+ <span class="note-headline-light">@{{ author.username }}</span>
+ </a>
+ <span class="note-headline-light note-headline-meta">
+ <span class="system-note-message"> <slot></slot> </span>
+ <template v-if="note.createdAt">
+ <span class="system-note-separator"></span>
+ <a class="note-timestamp system-note-separator" :href="`#note_${noteAnchorId}`">
+ <time-ago-tooltip :time="note.createdAt" tooltip-placement="bottom" />
+ </a>
+ </template>
+ </span>
+ </div>
+ <div class="gl-display-flex">
+ <slot name="resolveDiscussion"></slot>
+ <button
+ v-if="isEditButtonVisible"
+ v-gl-tooltip
+ type="button"
+ :title="__('Edit comment')"
+ class="note-action-button js-note-edit btn btn-transparent qa-note-edit-button"
+ @click="isEditing = true"
+ >
+ <gl-icon name="pencil" class="link-highlight" />
+ </button>
+ </div>
+ </div>
+ <template v-if="!isEditing">
+ <div
+ class="note-text js-note-text md"
+ data-qa-selector="note_content"
+ v-html="note.bodyHtml"
+ ></div>
+ <slot name="resolvedStatus"></slot>
+ </template>
+ <apollo-mutation
+ v-else
+ #default="{ mutate, loading }"
+ :mutation="$options.updateNoteMutation"
+ :variables="{
+ input: mutationPayload,
+ }"
+ @error="$emit('error', $event)"
+ @done="onDone"
+ >
+ <design-reply-form
+ v-model="noteText"
+ :is-saving="loading"
+ :markdown-preview-path="markdownPreviewPath"
+ :is-new-comment="false"
+ class="mt-5"
+ @submitForm="mutate"
+ @cancelForm="hideForm"
+ />
+ </apollo-mutation>
+ </timeline-entry-item>
+</template>
diff --git a/app/assets/javascripts/design_management_new/components/design_notes/design_reply_form.vue b/app/assets/javascripts/design_management_new/components/design_notes/design_reply_form.vue
new file mode 100644
index 00000000000..969034909f2
--- /dev/null
+++ b/app/assets/javascripts/design_management_new/components/design_notes/design_reply_form.vue
@@ -0,0 +1,141 @@
+<script>
+import { GlDeprecatedButton, GlModal } from '@gitlab/ui';
+import MarkdownField from '~/vue_shared/components/markdown/field.vue';
+import { s__ } from '~/locale';
+
+export default {
+ name: 'DesignReplyForm',
+ components: {
+ MarkdownField,
+ GlDeprecatedButton,
+ GlModal,
+ },
+ props: {
+ markdownPreviewPath: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ value: {
+ type: String,
+ required: true,
+ },
+ isSaving: {
+ type: Boolean,
+ required: true,
+ },
+ isNewComment: {
+ type: Boolean,
+ required: false,
+ default: true,
+ },
+ },
+ data() {
+ return {
+ formText: this.value,
+ };
+ },
+ computed: {
+ hasValue() {
+ return this.value.trim().length > 0;
+ },
+ modalSettings() {
+ if (this.isNewComment) {
+ return {
+ title: s__('DesignManagement|Cancel comment confirmation'),
+ okTitle: s__('DesignManagement|Discard comment'),
+ cancelTitle: s__('DesignManagement|Keep comment'),
+ content: s__('DesignManagement|Are you sure you want to cancel creating this comment?'),
+ };
+ }
+ return {
+ title: s__('DesignManagement|Cancel comment update confirmation'),
+ okTitle: s__('DesignManagement|Cancel changes'),
+ cancelTitle: s__('DesignManagement|Keep changes'),
+ content: s__('DesignManagement|Are you sure you want to cancel changes to this comment?'),
+ };
+ },
+ buttonText() {
+ return this.isNewComment
+ ? s__('DesignManagement|Comment')
+ : s__('DesignManagement|Save comment');
+ },
+ },
+ mounted() {
+ this.focusInput();
+ },
+ methods: {
+ submitForm() {
+ if (this.hasValue) this.$emit('submitForm');
+ },
+ cancelComment() {
+ if (this.hasValue && this.formText !== this.value) {
+ this.$refs.cancelCommentModal.show();
+ } else {
+ this.$emit('cancelForm');
+ }
+ },
+ focusInput() {
+ this.$refs.textarea.focus();
+ },
+ },
+};
+</script>
+
+<template>
+ <form class="new-note common-note-form" @submit.prevent>
+ <markdown-field
+ :markdown-preview-path="markdownPreviewPath"
+ :can-attach-file="false"
+ :enable-autocomplete="true"
+ :textarea-value="value"
+ markdown-docs-path="/help/user/markdown"
+ class="bordered-box"
+ >
+ <template #textarea>
+ <textarea
+ ref="textarea"
+ :value="value"
+ class="note-textarea js-gfm-input js-autosize markdown-area"
+ dir="auto"
+ data-supports-quick-actions="false"
+ data-qa-selector="note_textarea"
+ :aria-label="__('Description')"
+ :placeholder="__('Write a comment…')"
+ @input="$emit('input', $event.target.value)"
+ @keydown.meta.enter="submitForm"
+ @keydown.ctrl.enter="submitForm"
+ @keyup.esc.stop="cancelComment"
+ >
+ </textarea>
+ </template>
+ </markdown-field>
+ <slot name="resolveCheckbox"></slot>
+ <div class="note-form-actions gl-display-flex gl-justify-content-space-between">
+ <gl-deprecated-button
+ ref="submitButton"
+ :disabled="!hasValue || isSaving"
+ variant="success"
+ type="submit"
+ data-track-event="click_button"
+ data-qa-selector="save_comment_button"
+ @click="$emit('submitForm')"
+ >
+ {{ buttonText }}
+ </gl-deprecated-button>
+ <gl-deprecated-button ref="cancelButton" @click="cancelComment">{{
+ __('Cancel')
+ }}</gl-deprecated-button>
+ </div>
+ <gl-modal
+ ref="cancelCommentModal"
+ ok-variant="danger"
+ :title="modalSettings.title"
+ :ok-title="modalSettings.okTitle"
+ :cancel-title="modalSettings.cancelTitle"
+ modal-id="cancel-comment-modal"
+ @ok="$emit('cancelForm')"
+ >{{ modalSettings.content }}
+ </gl-modal>
+ </form>
+</template>
diff --git a/app/assets/javascripts/design_management_new/components/design_notes/toggle_replies_widget.vue b/app/assets/javascripts/design_management_new/components/design_notes/toggle_replies_widget.vue
new file mode 100644
index 00000000000..46c73e3eea8
--- /dev/null
+++ b/app/assets/javascripts/design_management_new/components/design_notes/toggle_replies_widget.vue
@@ -0,0 +1,70 @@
+<script>
+import { GlIcon, GlButton, GlLink } from '@gitlab/ui';
+import { __, n__ } from '~/locale';
+import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
+
+export default {
+ name: 'ToggleNotesWidget',
+ components: {
+ GlIcon,
+ GlButton,
+ GlLink,
+ TimeAgoTooltip,
+ },
+ props: {
+ collapsed: {
+ type: Boolean,
+ required: true,
+ },
+ replies: {
+ type: Array,
+ required: true,
+ },
+ },
+ computed: {
+ lastReply() {
+ return this.replies[this.replies.length - 1];
+ },
+ iconName() {
+ return this.collapsed ? 'chevron-right' : 'chevron-down';
+ },
+ toggleText() {
+ return this.collapsed
+ ? `${this.replies.length} ${n__('reply', 'replies', this.replies.length)}`
+ : __('Collapse replies');
+ },
+ },
+};
+</script>
+
+<template>
+ <li
+ class="toggle-comments gl-bg-gray-50 gl-display-flex gl-align-items-center gl-py-3"
+ :class="{ expanded: !collapsed }"
+ data-testid="toggle-comments-wrapper"
+ >
+ <gl-icon :name="iconName" class="gl-ml-3" @click.stop="$emit('toggle')" />
+ <gl-button
+ variant="link"
+ class="toggle-comments-button gl-ml-2 gl-mr-2"
+ @click.stop="$emit('toggle')"
+ >
+ {{ toggleText }}
+ </gl-button>
+ <template v-if="collapsed">
+ <span class="gl-text-gray-700">{{ __('Last reply by') }}</span>
+ <gl-link
+ :href="lastReply.author.webUrl"
+ target="_blank"
+ class="link-inherit-color gl-text-black-normal gl-text-decoration-none gl-font-weight-bold gl-ml-2 gl-mr-2"
+ >
+ {{ lastReply.author.name }}
+ </gl-link>
+ <time-ago-tooltip
+ :time="lastReply.createdAt"
+ tooltip-placement="bottom"
+ class="gl-text-gray-700"
+ />
+ </template>
+ </li>
+</template>
diff --git a/app/assets/javascripts/design_management_new/components/design_overlay.vue b/app/assets/javascripts/design_management_new/components/design_overlay.vue
new file mode 100644
index 00000000000..926e7c74802
--- /dev/null
+++ b/app/assets/javascripts/design_management_new/components/design_overlay.vue
@@ -0,0 +1,287 @@
+<script>
+import activeDiscussionQuery from '../graphql/queries/active_discussion.query.graphql';
+import updateActiveDiscussionMutation from '../graphql/mutations/update_active_discussion.mutation.graphql';
+import DesignNotePin from './design_note_pin.vue';
+import { ACTIVE_DISCUSSION_SOURCE_TYPES } from '../constants';
+
+export default {
+ name: 'DesignOverlay',
+ components: {
+ DesignNotePin,
+ },
+ props: {
+ dimensions: {
+ type: Object,
+ required: true,
+ },
+ position: {
+ type: Object,
+ required: true,
+ },
+ notes: {
+ type: Array,
+ required: false,
+ default: () => [],
+ },
+ currentCommentForm: {
+ type: Object,
+ required: false,
+ default: null,
+ },
+ disableCommenting: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ resolvedDiscussionsExpanded: {
+ type: Boolean,
+ required: true,
+ },
+ },
+ apollo: {
+ activeDiscussion: {
+ query: activeDiscussionQuery,
+ },
+ },
+ data() {
+ return {
+ movingNoteNewPosition: null,
+ movingNoteStartPosition: null,
+ activeDiscussion: {},
+ };
+ },
+ computed: {
+ overlayStyle() {
+ const cursor = this.disableCommenting ? 'unset' : undefined;
+
+ return {
+ cursor,
+ width: `${this.dimensions.width}px`,
+ height: `${this.dimensions.height}px`,
+ ...this.position,
+ };
+ },
+ isMovingCurrentComment() {
+ return Boolean(this.movingNoteStartPosition && !this.movingNoteStartPosition.noteId);
+ },
+ currentCommentPositionStyle() {
+ return this.isMovingCurrentComment && this.movingNoteNewPosition
+ ? this.getNotePositionStyle(this.movingNoteNewPosition)
+ : this.getNotePositionStyle(this.currentCommentForm);
+ },
+ },
+ methods: {
+ setNewNoteCoordinates({ x, y }) {
+ this.$emit('openCommentForm', { x, y });
+ },
+ getNoteRelativePosition(position) {
+ const { x, y, width, height } = position;
+ const widthRatio = this.dimensions.width / width;
+ const heightRatio = this.dimensions.height / height;
+ return {
+ left: Math.round(x * widthRatio),
+ top: Math.round(y * heightRatio),
+ };
+ },
+ getNotePositionStyle(position) {
+ const { left, top } = this.getNoteRelativePosition(position);
+ return {
+ left: `${left}px`,
+ top: `${top}px`,
+ };
+ },
+ getMovingNotePositionDelta(e) {
+ let deltaX = 0;
+ let deltaY = 0;
+
+ if (this.movingNoteStartPosition) {
+ const { clientX, clientY } = this.movingNoteStartPosition;
+ deltaX = e.clientX - clientX;
+ deltaY = e.clientY - clientY;
+ }
+
+ return {
+ deltaX,
+ deltaY,
+ };
+ },
+ isMovingNote(noteId) {
+ const movingNoteId = this.movingNoteStartPosition?.noteId;
+ return Boolean(movingNoteId && movingNoteId === noteId);
+ },
+ canMoveNote(note) {
+ const { userPermissions } = note;
+ const { adminNote } = userPermissions || {};
+
+ return Boolean(adminNote);
+ },
+ isPositionInOverlay(position) {
+ const { top, left } = this.getNoteRelativePosition(position);
+ const { height, width } = this.dimensions;
+
+ return top >= 0 && top <= height && left >= 0 && left <= width;
+ },
+ onNewNoteMove(e) {
+ if (!this.isMovingCurrentComment) return;
+
+ const { deltaX, deltaY } = this.getMovingNotePositionDelta(e);
+ const x = this.currentCommentForm.x + deltaX;
+ const y = this.currentCommentForm.y + deltaY;
+
+ const movingNoteNewPosition = {
+ x,
+ y,
+ width: this.dimensions.width,
+ height: this.dimensions.height,
+ };
+
+ if (!this.isPositionInOverlay(movingNoteNewPosition)) {
+ this.onNewNoteMouseup();
+ return;
+ }
+
+ this.movingNoteNewPosition = movingNoteNewPosition;
+ },
+ onExistingNoteMove(e) {
+ const note = this.notes.find(({ id }) => id === this.movingNoteStartPosition.noteId);
+ if (!note || !this.canMoveNote(note)) return;
+
+ const { position } = note;
+ const { width, height } = position;
+ const widthRatio = this.dimensions.width / width;
+ const heightRatio = this.dimensions.height / height;
+
+ const { deltaX, deltaY } = this.getMovingNotePositionDelta(e);
+ const x = position.x * widthRatio + deltaX;
+ const y = position.y * heightRatio + deltaY;
+
+ const movingNoteNewPosition = {
+ x,
+ y,
+ width: this.dimensions.width,
+ height: this.dimensions.height,
+ };
+
+ if (!this.isPositionInOverlay(movingNoteNewPosition)) {
+ this.onExistingNoteMouseup();
+ return;
+ }
+
+ this.movingNoteNewPosition = movingNoteNewPosition;
+ },
+ onNewNoteMouseup() {
+ if (!this.movingNoteNewPosition) return;
+
+ const { x, y } = this.movingNoteNewPosition;
+ this.setNewNoteCoordinates({ x, y });
+ },
+ onExistingNoteMouseup(note) {
+ if (!this.movingNoteStartPosition || !this.movingNoteNewPosition) {
+ this.updateActiveDiscussion(note.id);
+ this.$emit('closeCommentForm');
+ return;
+ }
+
+ const { x, y } = this.movingNoteNewPosition;
+ this.$emit('moveNote', {
+ noteId: this.movingNoteStartPosition.noteId,
+ discussionId: this.movingNoteStartPosition.discussionId,
+ coordinates: { x, y },
+ });
+ },
+ onNoteMousedown({ clientX, clientY }, note) {
+ this.movingNoteStartPosition = {
+ noteId: note?.id,
+ discussionId: note?.discussion.id,
+ clientX,
+ clientY,
+ };
+ },
+ onOverlayMousemove(e) {
+ if (!this.movingNoteStartPosition) return;
+
+ if (this.isMovingCurrentComment) {
+ this.onNewNoteMove(e);
+ } else {
+ this.onExistingNoteMove(e);
+ }
+ },
+ onNoteMouseup(note) {
+ if (!this.movingNoteStartPosition) return;
+
+ if (this.isMovingCurrentComment) {
+ this.onNewNoteMouseup();
+ } else {
+ this.onExistingNoteMouseup(note);
+ }
+
+ this.movingNoteStartPosition = null;
+ this.movingNoteNewPosition = null;
+ },
+ onAddCommentMouseup({ offsetX, offsetY }) {
+ if (this.disableCommenting) return;
+ if (this.activeDiscussion.id) {
+ this.updateActiveDiscussion();
+ }
+
+ this.setNewNoteCoordinates({ x: offsetX, y: offsetY });
+ },
+ updateActiveDiscussion(id) {
+ this.$apollo.mutate({
+ mutation: updateActiveDiscussionMutation,
+ variables: {
+ id,
+ source: ACTIVE_DISCUSSION_SOURCE_TYPES.pin,
+ },
+ });
+ },
+ isNoteInactive(note) {
+ return this.activeDiscussion.id && this.activeDiscussion.id !== note.id;
+ },
+ designPinClass(note) {
+ return { inactive: this.isNoteInactive(note), resolved: note.resolved };
+ },
+ },
+};
+</script>
+
+<template>
+ <div
+ class="position-absolute image-diff-overlay frame"
+ :style="overlayStyle"
+ @mousemove="onOverlayMousemove"
+ @mouseleave="onNoteMouseup"
+ >
+ <button
+ v-show="!disableCommenting"
+ type="button"
+ class="btn-transparent position-absolute image-diff-overlay-add-comment w-100 h-100 js-add-image-diff-note-button"
+ data-qa-selector="design_image_button"
+ @mouseup="onAddCommentMouseup"
+ ></button>
+ <template v-for="note in notes">
+ <design-note-pin
+ v-if="resolvedDiscussionsExpanded || !note.resolved"
+ :key="note.id"
+ :label="note.index"
+ :repositioning="isMovingNote(note.id)"
+ :position="
+ isMovingNote(note.id) && movingNoteNewPosition
+ ? getNotePositionStyle(movingNoteNewPosition)
+ : getNotePositionStyle(note.position)
+ "
+ :class="designPinClass(note)"
+ @mousedown.stop="onNoteMousedown($event, note)"
+ @mouseup.stop="onNoteMouseup(note)"
+ />
+ </template>
+
+ <design-note-pin
+ v-if="currentCommentForm"
+ :position="currentCommentPositionStyle"
+ :repositioning="isMovingCurrentComment"
+ @mousedown.stop="onNoteMousedown"
+ @mouseup.stop="onNoteMouseup"
+ />
+ </div>
+</template>
diff --git a/app/assets/javascripts/design_management_new/components/design_presentation.vue b/app/assets/javascripts/design_management_new/components/design_presentation.vue
new file mode 100644
index 00000000000..84dbb2809d9
--- /dev/null
+++ b/app/assets/javascripts/design_management_new/components/design_presentation.vue
@@ -0,0 +1,322 @@
+<script>
+import { throttle } from 'lodash';
+import DesignImage from './image.vue';
+import DesignOverlay from './design_overlay.vue';
+
+const CLICK_DRAG_BUFFER_PX = 2;
+
+export default {
+ components: {
+ DesignImage,
+ DesignOverlay,
+ },
+ props: {
+ image: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ imageName: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ discussions: {
+ type: Array,
+ required: true,
+ },
+ isAnnotating: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ scale: {
+ type: Number,
+ required: false,
+ default: 1,
+ },
+ resolvedDiscussionsExpanded: {
+ type: Boolean,
+ required: true,
+ },
+ },
+ data() {
+ return {
+ overlayDimensions: null,
+ overlayPosition: null,
+ currentAnnotationPosition: null,
+ zoomFocalPoint: {
+ x: 0,
+ y: 0,
+ width: 0,
+ height: 0,
+ },
+ initialLoad: true,
+ lastDragPosition: null,
+ isDraggingDesign: false,
+ };
+ },
+ computed: {
+ discussionStartingNotes() {
+ return this.discussions.map(discussion => ({
+ ...discussion.notes[0],
+ index: discussion.index,
+ }));
+ },
+ currentCommentForm() {
+ return (this.isAnnotating && this.currentAnnotationPosition) || null;
+ },
+ presentationStyle() {
+ return {
+ cursor: this.isDraggingDesign ? 'grabbing' : undefined,
+ };
+ },
+ },
+ beforeDestroy() {
+ const { presentationViewport } = this.$refs;
+ if (!presentationViewport) return;
+
+ presentationViewport.removeEventListener('scroll', this.scrollThrottled, false);
+ },
+ mounted() {
+ const { presentationViewport } = this.$refs;
+ if (!presentationViewport) return;
+
+ this.scrollThrottled = throttle(() => {
+ this.shiftZoomFocalPoint();
+ }, 400);
+
+ presentationViewport.addEventListener('scroll', this.scrollThrottled, false);
+ },
+ methods: {
+ syncCurrentAnnotationPosition() {
+ if (!this.currentAnnotationPosition) return;
+
+ const widthRatio = this.overlayDimensions.width / this.currentAnnotationPosition.width;
+ const heightRatio = this.overlayDimensions.height / this.currentAnnotationPosition.height;
+ const x = this.currentAnnotationPosition.x * widthRatio;
+ const y = this.currentAnnotationPosition.y * heightRatio;
+
+ this.currentAnnotationPosition = this.getAnnotationPositon({ x, y });
+ },
+ setOverlayDimensions(overlayDimensions) {
+ this.overlayDimensions = overlayDimensions;
+
+ // every time we set overlay dimensions, we need to
+ // update the current annotation as well
+ this.syncCurrentAnnotationPosition();
+ },
+ setOverlayPosition() {
+ if (!this.overlayDimensions) {
+ this.overlayPosition = {};
+ }
+
+ const { presentationViewport } = this.$refs;
+ if (!presentationViewport) return;
+
+ // default to center
+ this.overlayPosition = {
+ left: `calc(50% - ${this.overlayDimensions.width / 2}px)`,
+ top: `calc(50% - ${this.overlayDimensions.height / 2}px)`,
+ };
+
+ // if the overlay overflows, then don't center
+ if (this.overlayDimensions.width > presentationViewport.offsetWidth) {
+ this.overlayPosition.left = '0';
+ }
+ if (this.overlayDimensions.height > presentationViewport.offsetHeight) {
+ this.overlayPosition.top = '0';
+ }
+ },
+ /**
+ * Return a point that represents the center of an
+ * overflowing child element w.r.t it's parent
+ */
+ getViewportCenter() {
+ const { presentationViewport } = this.$refs;
+ if (!presentationViewport) return {};
+
+ // get height of scroll bars (i.e. the max values for scrollTop, scrollLeft)
+ const scrollBarWidth = presentationViewport.scrollWidth - presentationViewport.offsetWidth;
+ const scrollBarHeight = presentationViewport.scrollHeight - presentationViewport.offsetHeight;
+
+ // determine how many child pixels have been scrolled
+ const xScrollRatio =
+ presentationViewport.scrollLeft > 0 ? presentationViewport.scrollLeft / scrollBarWidth : 0;
+ const yScrollRatio =
+ presentationViewport.scrollTop > 0 ? presentationViewport.scrollTop / scrollBarHeight : 0;
+ const xScrollOffset =
+ (presentationViewport.scrollWidth - presentationViewport.offsetWidth - 0) * xScrollRatio;
+ const yScrollOffset =
+ (presentationViewport.scrollHeight - presentationViewport.offsetHeight - 0) * yScrollRatio;
+
+ const viewportCenterX = presentationViewport.offsetWidth / 2;
+ const viewportCenterY = presentationViewport.offsetHeight / 2;
+ const focalPointX = viewportCenterX + xScrollOffset;
+ const focalPointY = viewportCenterY + yScrollOffset;
+
+ return {
+ x: focalPointX,
+ y: focalPointY,
+ };
+ },
+ /**
+ * Scroll the viewport such that the focal point is positioned centrally
+ */
+ scrollToFocalPoint() {
+ const { presentationViewport } = this.$refs;
+ if (!presentationViewport) return;
+
+ const scrollX = this.zoomFocalPoint.x - presentationViewport.offsetWidth / 2;
+ const scrollY = this.zoomFocalPoint.y - presentationViewport.offsetHeight / 2;
+
+ presentationViewport.scrollTo(scrollX, scrollY);
+ },
+ scaleZoomFocalPoint() {
+ const { x, y, width, height } = this.zoomFocalPoint;
+ const widthRatio = this.overlayDimensions.width / width;
+ const heightRatio = this.overlayDimensions.height / height;
+
+ this.zoomFocalPoint = {
+ x: Math.round(x * widthRatio * 100) / 100,
+ y: Math.round(y * heightRatio * 100) / 100,
+ ...this.overlayDimensions,
+ };
+ },
+ shiftZoomFocalPoint() {
+ this.zoomFocalPoint = {
+ ...this.getViewportCenter(),
+ ...this.overlayDimensions,
+ };
+ },
+ onImageResize(imageDimensions) {
+ this.setOverlayDimensions(imageDimensions);
+ this.setOverlayPosition();
+
+ this.$nextTick(() => {
+ if (this.initialLoad) {
+ // set focal point on initial load
+ this.shiftZoomFocalPoint();
+ this.initialLoad = false;
+ } else {
+ this.scaleZoomFocalPoint();
+ this.scrollToFocalPoint();
+ }
+ });
+ },
+ getAnnotationPositon(coordinates) {
+ const { x, y } = coordinates;
+ const { width, height } = this.overlayDimensions;
+ return {
+ x: Math.round(x),
+ y: Math.round(y),
+ width: Math.round(width),
+ height: Math.round(height),
+ };
+ },
+ openCommentForm(coordinates) {
+ this.currentAnnotationPosition = this.getAnnotationPositon(coordinates);
+ this.$emit('openCommentForm', this.currentAnnotationPosition);
+ },
+ closeCommentForm() {
+ this.currentAnnotationPosition = null;
+ this.$emit('closeCommentForm');
+ },
+ moveNote({ noteId, discussionId, coordinates }) {
+ const position = this.getAnnotationPositon(coordinates);
+ this.$emit('moveNote', { noteId, discussionId, position });
+ },
+ onPresentationMousedown({ clientX, clientY }) {
+ if (!this.isDesignOverflowing()) return;
+
+ this.lastDragPosition = {
+ x: clientX,
+ y: clientY,
+ };
+ },
+ getDragDelta(clientX, clientY) {
+ return {
+ deltaX: this.lastDragPosition.x - clientX,
+ deltaY: this.lastDragPosition.y - clientY,
+ };
+ },
+ exceedsDragThreshold(clientX, clientY) {
+ const { deltaX, deltaY } = this.getDragDelta(clientX, clientY);
+
+ return Math.abs(deltaX) > CLICK_DRAG_BUFFER_PX || Math.abs(deltaY) > CLICK_DRAG_BUFFER_PX;
+ },
+ shouldDragDesign(clientX, clientY) {
+ return (
+ this.lastDragPosition &&
+ (this.isDraggingDesign || this.exceedsDragThreshold(clientX, clientY))
+ );
+ },
+ onPresentationMousemove({ clientX, clientY }) {
+ const { presentationViewport } = this.$refs;
+ if (!presentationViewport || !this.shouldDragDesign(clientX, clientY)) return;
+
+ this.isDraggingDesign = true;
+
+ const { scrollLeft, scrollTop } = presentationViewport;
+ const { deltaX, deltaY } = this.getDragDelta(clientX, clientY);
+ presentationViewport.scrollTo(scrollLeft + deltaX, scrollTop + deltaY);
+
+ this.lastDragPosition = {
+ x: clientX,
+ y: clientY,
+ };
+ },
+ onPresentationMouseup() {
+ this.lastDragPosition = null;
+ this.isDraggingDesign = false;
+ },
+ isDesignOverflowing() {
+ const { presentationViewport } = this.$refs;
+ if (!presentationViewport) return false;
+
+ return (
+ presentationViewport.scrollWidth > presentationViewport.offsetWidth ||
+ presentationViewport.scrollHeight > presentationViewport.offsetHeight
+ );
+ },
+ },
+};
+</script>
+
+<template>
+ <div
+ ref="presentationViewport"
+ class="h-100 w-100 p-3 overflow-auto position-relative"
+ :style="presentationStyle"
+ @mousedown="onPresentationMousedown"
+ @mousemove="onPresentationMousemove"
+ @mouseup="onPresentationMouseup"
+ @mouseleave="onPresentationMouseup"
+ @touchstart="onPresentationMousedown"
+ @touchmove="onPresentationMousemove"
+ @touchend="onPresentationMouseup"
+ @touchcancel="onPresentationMouseup"
+ >
+ <div class="h-100 w-100 d-flex align-items-center position-relative">
+ <design-image
+ v-if="image"
+ :image="image"
+ :name="imageName"
+ :scale="scale"
+ @resize="onImageResize"
+ />
+ <design-overlay
+ v-if="overlayDimensions && overlayPosition"
+ :dimensions="overlayDimensions"
+ :position="overlayPosition"
+ :notes="discussionStartingNotes"
+ :current-comment-form="currentCommentForm"
+ :disable-commenting="isDraggingDesign"
+ :resolved-discussions-expanded="resolvedDiscussionsExpanded"
+ @openCommentForm="openCommentForm"
+ @closeCommentForm="closeCommentForm"
+ @moveNote="moveNote"
+ />
+ </div>
+ </div>
+</template>
diff --git a/app/assets/javascripts/design_management_new/components/design_scaler.vue b/app/assets/javascripts/design_management_new/components/design_scaler.vue
new file mode 100644
index 00000000000..55dee74bef5
--- /dev/null
+++ b/app/assets/javascripts/design_management_new/components/design_scaler.vue
@@ -0,0 +1,65 @@
+<script>
+import { GlIcon } from '@gitlab/ui';
+
+const SCALE_STEP_SIZE = 0.2;
+const DEFAULT_SCALE = 1;
+const MIN_SCALE = 1;
+const MAX_SCALE = 2;
+
+export default {
+ components: {
+ GlIcon,
+ },
+ data() {
+ return {
+ scale: DEFAULT_SCALE,
+ };
+ },
+ computed: {
+ disableReset() {
+ return this.scale <= MIN_SCALE;
+ },
+ disableDecrease() {
+ return this.scale === DEFAULT_SCALE;
+ },
+ disableIncrease() {
+ return this.scale >= MAX_SCALE;
+ },
+ },
+ methods: {
+ setScale(scale) {
+ if (scale < MIN_SCALE) {
+ return;
+ }
+
+ this.scale = Math.round(scale * 100) / 100;
+ this.$emit('scale', this.scale);
+ },
+ incrementScale() {
+ this.setScale(this.scale + SCALE_STEP_SIZE);
+ },
+ decrementScale() {
+ this.setScale(this.scale - SCALE_STEP_SIZE);
+ },
+ resetScale() {
+ this.setScale(DEFAULT_SCALE);
+ },
+ },
+};
+</script>
+
+<template>
+ <div class="design-scaler btn-group" role="group">
+ <button class="btn" :disabled="disableDecrease" @click="decrementScale">
+ <span class="d-flex-center gl-icon s16">
+ –
+ </span>
+ </button>
+ <button class="btn" :disabled="disableReset" @click="resetScale">
+ <gl-icon name="redo" />
+ </button>
+ <button class="btn" :disabled="disableIncrease" @click="incrementScale">
+ <gl-icon name="plus" />
+ </button>
+ </div>
+</template>
diff --git a/app/assets/javascripts/design_management_new/components/design_sidebar.vue b/app/assets/javascripts/design_management_new/components/design_sidebar.vue
new file mode 100644
index 00000000000..333ad2557e8
--- /dev/null
+++ b/app/assets/javascripts/design_management_new/components/design_sidebar.vue
@@ -0,0 +1,178 @@
+<script>
+import { s__ } from '~/locale';
+import Cookies from 'js-cookie';
+import { parseBoolean } from '~/lib/utils/common_utils';
+import { GlCollapse, GlButton, GlPopover } from '@gitlab/ui';
+import updateActiveDiscussionMutation from '../graphql/mutations/update_active_discussion.mutation.graphql';
+import { extractDiscussions, extractParticipants } from '../utils/design_management_utils';
+import { ACTIVE_DISCUSSION_SOURCE_TYPES } from '../constants';
+import DesignDiscussion from './design_notes/design_discussion.vue';
+import Participants from '~/sidebar/components/participants/participants.vue';
+
+export default {
+ components: {
+ DesignDiscussion,
+ Participants,
+ GlCollapse,
+ GlButton,
+ GlPopover,
+ },
+ props: {
+ design: {
+ type: Object,
+ required: true,
+ },
+ resolvedDiscussionsExpanded: {
+ type: Boolean,
+ required: true,
+ },
+ markdownPreviewPath: {
+ type: String,
+ required: true,
+ },
+ },
+ data() {
+ return {
+ isResolvedCommentsPopoverHidden: parseBoolean(Cookies.get(this.$options.cookieKey)),
+ discussionWithOpenForm: '',
+ };
+ },
+ computed: {
+ discussions() {
+ return extractDiscussions(this.design.discussions);
+ },
+ issue() {
+ return {
+ ...this.design.issue,
+ webPath: this.design.issue.webPath.substr(1),
+ };
+ },
+ discussionParticipants() {
+ return extractParticipants(this.issue.participants);
+ },
+ resolvedDiscussions() {
+ return this.discussions.filter(discussion => discussion.resolved);
+ },
+ unresolvedDiscussions() {
+ return this.discussions.filter(discussion => !discussion.resolved);
+ },
+ resolvedCommentsToggleIcon() {
+ return this.resolvedDiscussionsExpanded ? 'chevron-down' : 'chevron-right';
+ },
+ },
+ methods: {
+ handleSidebarClick() {
+ this.isResolvedCommentsPopoverHidden = true;
+ Cookies.set(this.$options.cookieKey, 'true', { expires: 365 * 10 });
+ this.updateActiveDiscussion();
+ },
+ updateActiveDiscussion(id) {
+ this.$apollo.mutate({
+ mutation: updateActiveDiscussionMutation,
+ variables: {
+ id,
+ source: ACTIVE_DISCUSSION_SOURCE_TYPES.discussion,
+ },
+ });
+ },
+ closeCommentForm() {
+ this.comment = '';
+ this.$emit('closeCommentForm');
+ },
+ updateDiscussionWithOpenForm(id) {
+ this.discussionWithOpenForm = id;
+ },
+ },
+ resolveCommentsToggleText: s__('DesignManagement|Resolved Comments'),
+ cookieKey: 'hide_design_resolved_comments_popover',
+};
+</script>
+
+<template>
+ <div class="image-notes" @click="handleSidebarClick">
+ <h2 class="gl-font-weight-bold gl-mt-0">
+ {{ issue.title }}
+ </h2>
+ <a
+ class="gl-text-gray-600 gl-text-decoration-none gl-mb-6 gl-display-block"
+ :href="issue.webUrl"
+ >{{ issue.webPath }}</a
+ >
+ <participants
+ :participants="discussionParticipants"
+ :show-participant-label="false"
+ class="gl-mb-4"
+ />
+ <h2
+ v-if="unresolvedDiscussions.length === 0"
+ class="new-discussion-disclaimer gl-font-base gl-m-0 gl-mb-4"
+ data-testid="new-discussion-disclaimer"
+ >
+ {{ s__("DesignManagement|Click the image where you'd like to start a new discussion") }}
+ </h2>
+ <design-discussion
+ v-for="discussion in unresolvedDiscussions"
+ :key="discussion.id"
+ :discussion="discussion"
+ :design-id="$route.params.id"
+ :noteable-id="design.id"
+ :markdown-preview-path="markdownPreviewPath"
+ :resolved-discussions-expanded="resolvedDiscussionsExpanded"
+ :discussion-with-open-form="discussionWithOpenForm"
+ data-testid="unresolved-discussion"
+ @createNoteError="$emit('onDesignDiscussionError', $event)"
+ @updateNoteError="$emit('updateNoteError', $event)"
+ @resolveDiscussionError="$emit('resolveDiscussionError', $event)"
+ @click.native.stop="updateActiveDiscussion(discussion.notes[0].id)"
+ @openForm="updateDiscussionWithOpenForm"
+ />
+ <template v-if="resolvedDiscussions.length > 0">
+ <gl-button
+ id="resolved-comments"
+ data-testid="resolved-comments"
+ :icon="resolvedCommentsToggleIcon"
+ variant="link"
+ class="link-inherit-color gl-text-black-normal gl-text-decoration-none gl-font-weight-bold gl-mb-4"
+ @click="$emit('toggleResolvedComments')"
+ >{{ $options.resolveCommentsToggleText }} ({{ resolvedDiscussions.length }})
+ </gl-button>
+ <gl-popover
+ v-if="!isResolvedCommentsPopoverHidden"
+ :show="!isResolvedCommentsPopoverHidden"
+ target="resolved-comments"
+ container="popovercontainer"
+ placement="top"
+ :title="s__('DesignManagement|Resolved Comments')"
+ >
+ <p>
+ {{
+ s__(
+ 'DesignManagement|Comments you resolve can be viewed and unresolved by going to the "Resolved Comments" section below',
+ )
+ }}
+ </p>
+ <a href="#" rel="noopener noreferrer" target="_blank">{{
+ s__('DesignManagement|Learn more about resolving comments')
+ }}</a>
+ </gl-popover>
+ <gl-collapse :visible="resolvedDiscussionsExpanded" class="gl-mt-3">
+ <design-discussion
+ v-for="discussion in resolvedDiscussions"
+ :key="discussion.id"
+ :discussion="discussion"
+ :design-id="$route.params.id"
+ :noteable-id="design.id"
+ :markdown-preview-path="markdownPreviewPath"
+ :resolved-discussions-expanded="resolvedDiscussionsExpanded"
+ :discussion-with-open-form="discussionWithOpenForm"
+ data-testid="resolved-discussion"
+ @error="$emit('onDesignDiscussionError', $event)"
+ @updateNoteError="$emit('updateNoteError', $event)"
+ @openForm="updateDiscussionWithOpenForm"
+ @click.native.stop="updateActiveDiscussion(discussion.notes[0].id)"
+ />
+ </gl-collapse>
+ </template>
+ <slot name="replyForm"></slot>
+ </div>
+</template>
diff --git a/app/assets/javascripts/design_management_new/components/image.vue b/app/assets/javascripts/design_management_new/components/image.vue
new file mode 100644
index 00000000000..91b7b576e0c
--- /dev/null
+++ b/app/assets/javascripts/design_management_new/components/image.vue
@@ -0,0 +1,110 @@
+<script>
+import { throttle } from 'lodash';
+import { GlIcon } from '@gitlab/ui';
+
+export default {
+ components: {
+ GlIcon,
+ },
+ props: {
+ image: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ name: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ scale: {
+ type: Number,
+ required: false,
+ default: 1,
+ },
+ },
+ data() {
+ return {
+ baseImageSize: null,
+ imageStyle: null,
+ imageError: false,
+ };
+ },
+ watch: {
+ scale(val) {
+ this.zoom(val);
+ },
+ },
+ beforeDestroy() {
+ window.removeEventListener('resize', this.resizeThrottled, false);
+ },
+ mounted() {
+ this.onImgLoad();
+
+ this.resizeThrottled = throttle(() => {
+ // NOTE: if imageStyle is set, then baseImageSize
+ // won't change due to resize. We must still emit a
+ // `resize` event so that the parent can handle
+ // resizes appropriately (e.g. for design_overlay)
+ this.setBaseImageSize();
+ }, 400);
+ window.addEventListener('resize', this.resizeThrottled, false);
+ },
+ methods: {
+ onImgLoad() {
+ requestIdleCallback(this.setBaseImageSize, { timeout: 1000 });
+ },
+ onImgError() {
+ this.imageError = true;
+ },
+ setBaseImageSize() {
+ const { contentImg } = this.$refs;
+ if (!contentImg || contentImg.offsetHeight === 0 || contentImg.offsetWidth === 0) return;
+
+ this.baseImageSize = {
+ height: contentImg.offsetHeight,
+ width: contentImg.offsetWidth,
+ };
+ this.onResize({ width: this.baseImageSize.width, height: this.baseImageSize.height });
+ },
+ onResize({ width, height }) {
+ this.$emit('resize', { width, height });
+ },
+ zoom(amount) {
+ if (amount === 1) {
+ this.imageStyle = null;
+ this.$nextTick(() => {
+ this.setBaseImageSize();
+ });
+ return;
+ }
+ const width = this.baseImageSize.width * amount;
+ const height = this.baseImageSize.height * amount;
+
+ this.imageStyle = {
+ width: `${width}px`,
+ height: `${height}px`,
+ };
+
+ this.onResize({ width, height });
+ },
+ },
+};
+</script>
+
+<template>
+ <div class="m-auto js-design-image">
+ <gl-icon v-if="imageError" class="text-secondary-100" name="media-broken" :size="48" />
+ <img
+ v-show="!imageError"
+ ref="contentImg"
+ class="mh-100"
+ :src="image"
+ :alt="name"
+ :style="imageStyle"
+ :class="{ 'img-fluid': !imageStyle }"
+ @error="onImgError"
+ @load="onImgLoad"
+ />
+ </div>
+</template>
diff --git a/app/assets/javascripts/design_management_new/components/list/item.vue b/app/assets/javascripts/design_management_new/components/list/item.vue
new file mode 100644
index 00000000000..b19aef9c22d
--- /dev/null
+++ b/app/assets/javascripts/design_management_new/components/list/item.vue
@@ -0,0 +1,174 @@
+<script>
+import { GlLoadingIcon, GlIcon, GlIntersectionObserver } from '@gitlab/ui';
+import Icon from '~/vue_shared/components/icon.vue';
+import Timeago from '~/vue_shared/components/time_ago_tooltip.vue';
+import { n__, __ } from '~/locale';
+import { DESIGN_ROUTE_NAME } from '../../router/constants';
+
+export default {
+ components: {
+ GlLoadingIcon,
+ GlIntersectionObserver,
+ GlIcon,
+ Icon,
+ Timeago,
+ },
+ props: {
+ id: {
+ type: [Number, String],
+ required: true,
+ },
+ event: {
+ type: String,
+ required: true,
+ },
+ notesCount: {
+ type: Number,
+ required: true,
+ },
+ image: {
+ type: String,
+ required: true,
+ },
+ filename: {
+ type: String,
+ required: true,
+ },
+ updatedAt: {
+ type: String,
+ required: false,
+ default: null,
+ },
+ isUploading: {
+ type: Boolean,
+ required: false,
+ default: true,
+ },
+ imageV432x230: {
+ type: String,
+ required: false,
+ default: null,
+ },
+ },
+ data() {
+ return {
+ imageLoading: true,
+ imageError: false,
+ wasInView: false,
+ };
+ },
+ computed: {
+ icon() {
+ const normalizedEvent = this.event.toLowerCase();
+ const icons = {
+ creation: {
+ name: 'file-addition-solid',
+ classes: 'text-success-500',
+ tooltip: __('Added in this version'),
+ },
+ modification: {
+ name: 'file-modified-solid',
+ classes: 'text-primary-500',
+ tooltip: __('Modified in this version'),
+ },
+ deletion: {
+ name: 'file-deletion-solid',
+ classes: 'text-danger-500',
+ tooltip: __('Deleted in this version'),
+ },
+ };
+
+ return icons[normalizedEvent] ? icons[normalizedEvent] : {};
+ },
+ notesLabel() {
+ return n__('%d comment', '%d comments', this.notesCount);
+ },
+ imageLink() {
+ return this.wasInView ? this.imageV432x230 || this.image : '';
+ },
+ showLoadingSpinner() {
+ return this.imageLoading || this.isUploading;
+ },
+ showImageErrorIcon() {
+ return this.wasInView && this.imageError;
+ },
+ showImage() {
+ return !this.showLoadingSpinner && !this.showImageErrorIcon;
+ },
+ },
+ methods: {
+ onImageLoad() {
+ this.imageLoading = false;
+ this.imageError = false;
+ },
+ onImageError() {
+ this.imageLoading = false;
+ this.imageError = true;
+ },
+ onAppear() {
+ // do nothing if image has previously
+ // been in view
+ if (this.wasInView) {
+ return;
+ }
+
+ this.wasInView = true;
+ this.imageLoading = true;
+ },
+ },
+ DESIGN_ROUTE_NAME,
+};
+</script>
+
+<template>
+ <router-link
+ :to="{
+ name: $options.DESIGN_ROUTE_NAME,
+ params: { id: filename },
+ query: $route.query,
+ }"
+ class="card cursor-pointer text-plain js-design-list-item design-list-item design-list-item-new"
+ >
+ <div class="card-body p-0 d-flex-center overflow-hidden position-relative">
+ <div v-if="icon.name" class="design-event position-absolute">
+ <span :title="icon.tooltip" :aria-label="icon.tooltip">
+ <icon :name="icon.name" :size="18" :class="icon.classes" />
+ </span>
+ </div>
+ <gl-intersection-observer @appear="onAppear">
+ <gl-loading-icon v-if="showLoadingSpinner" size="md" />
+ <gl-icon
+ v-else-if="showImageErrorIcon"
+ name="media-broken"
+ class="text-secondary"
+ :size="32"
+ />
+ <img
+ v-show="showImage"
+ :src="imageLink"
+ :alt="filename"
+ class="block mx-auto mw-100 mh-100 design-img"
+ data-qa-selector="design_image"
+ @load="onImageLoad"
+ @error="onImageError"
+ />
+ </gl-intersection-observer>
+ </div>
+ <div class="card-footer d-flex w-100">
+ <div class="d-flex flex-column str-truncated-100">
+ <span class="bold str-truncated-100" data-qa-selector="design_file_name">{{
+ filename
+ }}</span>
+ <span v-if="updatedAt" class="str-truncated-100">
+ {{ __('Updated') }} <timeago :time="updatedAt" tooltip-placement="bottom" />
+ </span>
+ </div>
+ <div v-if="notesCount" class="ml-auto d-flex align-items-center text-secondary">
+ <icon name="comments" class="ml-1" />
+ <span :aria-label="notesLabel" class="ml-1">
+ {{ notesCount }}
+ </span>
+ </div>
+ </div>
+ </router-link>
+</template>
diff --git a/app/assets/javascripts/design_management_new/components/toolbar/index.vue b/app/assets/javascripts/design_management_new/components/toolbar/index.vue
new file mode 100644
index 00000000000..0b51035e83e
--- /dev/null
+++ b/app/assets/javascripts/design_management_new/components/toolbar/index.vue
@@ -0,0 +1,124 @@
+<script>
+import { GlDeprecatedButton } from '@gitlab/ui';
+import { __, sprintf } from '~/locale';
+import Icon from '~/vue_shared/components/icon.vue';
+import timeagoMixin from '~/vue_shared/mixins/timeago';
+import Pagination from './pagination.vue';
+import DeleteButton from '../delete_button.vue';
+import permissionsQuery from '../../graphql/queries/design_permissions.query.graphql';
+import { DESIGNS_ROUTE_NAME } from '../../router/constants';
+
+export default {
+ components: {
+ Icon,
+ Pagination,
+ DeleteButton,
+ GlDeprecatedButton,
+ },
+ mixins: [timeagoMixin],
+ props: {
+ id: {
+ type: String,
+ required: true,
+ },
+ isDeleting: {
+ type: Boolean,
+ required: true,
+ },
+ filename: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ updatedAt: {
+ type: String,
+ required: false,
+ default: null,
+ },
+ updatedBy: {
+ type: Object,
+ required: false,
+ default: () => ({}),
+ },
+ isLatestVersion: {
+ type: Boolean,
+ required: true,
+ },
+ image: {
+ type: String,
+ required: true,
+ },
+ },
+ data() {
+ return {
+ permissions: {
+ createDesign: false,
+ },
+ };
+ },
+ inject: {
+ projectPath: {
+ default: '',
+ },
+ issueIid: {
+ default: '',
+ },
+ },
+ apollo: {
+ permissions: {
+ query: permissionsQuery,
+ variables() {
+ return {
+ fullPath: this.projectPath,
+ iid: this.issueIid,
+ };
+ },
+ update: data => data.project.issue.userPermissions,
+ },
+ },
+ computed: {
+ updatedText() {
+ return sprintf(__('Updated %{updated_at} by %{updated_by}'), {
+ updated_at: this.timeFormatted(this.updatedAt),
+ updated_by: this.updatedBy.name,
+ });
+ },
+ canDeleteDesign() {
+ return this.permissions.createDesign;
+ },
+ },
+ DESIGNS_ROUTE_NAME,
+};
+</script>
+
+<template>
+ <header class="d-flex p-2 bg-white align-items-center js-design-header">
+ <router-link
+ :to="{
+ name: $options.DESIGNS_ROUTE_NAME,
+ query: $route.query,
+ }"
+ :aria-label="s__('DesignManagement|Go back to designs')"
+ data-testid="close-design"
+ class="mr-3 text-plain d-flex justify-content-center align-items-center"
+ >
+ <icon :size="18" name="close" />
+ </router-link>
+ <div class="overflow-hidden d-flex align-items-center">
+ <h2 class="m-0 str-truncated-100 gl-font-base">{{ filename }}</h2>
+ <small v-if="updatedAt" class="text-secondary">{{ updatedText }}</small>
+ </div>
+ <pagination :id="id" class="ml-auto flex-shrink-0" />
+ <gl-deprecated-button :href="image" class="mr-2">
+ <icon :size="18" name="download" />
+ </gl-deprecated-button>
+ <delete-button
+ v-if="isLatestVersion && canDeleteDesign"
+ :is-deleting="isDeleting"
+ button-variant="danger"
+ @deleteSelectedDesigns="$emit('delete')"
+ >
+ <icon :size="18" name="remove" />
+ </delete-button>
+ </header>
+</template>
diff --git a/app/assets/javascripts/design_management_new/components/toolbar/pagination.vue b/app/assets/javascripts/design_management_new/components/toolbar/pagination.vue
new file mode 100644
index 00000000000..bf62a8f66a6
--- /dev/null
+++ b/app/assets/javascripts/design_management_new/components/toolbar/pagination.vue
@@ -0,0 +1,83 @@
+<script>
+/* global Mousetrap */
+import 'mousetrap';
+import { s__, sprintf } from '~/locale';
+import PaginationButton from './pagination_button.vue';
+import allDesignsMixin from '../../mixins/all_designs';
+import { DESIGN_ROUTE_NAME } from '../../router/constants';
+
+export default {
+ components: {
+ PaginationButton,
+ },
+ mixins: [allDesignsMixin],
+ props: {
+ id: {
+ type: String,
+ required: true,
+ },
+ },
+ computed: {
+ designsCount() {
+ return this.designs.length;
+ },
+ currentIndex() {
+ return this.designs.findIndex(design => design.filename === this.id);
+ },
+ paginationText() {
+ return sprintf(s__('DesignManagement|%{current_design} of %{designs_count}'), {
+ current_design: this.currentIndex + 1,
+ designs_count: this.designsCount,
+ });
+ },
+ previousDesign() {
+ if (!this.designsCount) return null;
+
+ return this.designs[this.currentIndex - 1];
+ },
+ nextDesign() {
+ if (!this.designsCount) return null;
+
+ return this.designs[this.currentIndex + 1];
+ },
+ },
+ mounted() {
+ Mousetrap.bind('left', () => this.navigateToDesign(this.previousDesign));
+ Mousetrap.bind('right', () => this.navigateToDesign(this.nextDesign));
+ },
+ beforeDestroy() {
+ Mousetrap.unbind(['left', 'right'], this.navigateToDesign);
+ },
+ methods: {
+ navigateToDesign(design) {
+ if (design) {
+ this.$router.push({
+ name: DESIGN_ROUTE_NAME,
+ params: { id: design.filename },
+ query: this.$route.query,
+ });
+ }
+ },
+ },
+};
+</script>
+
+<template>
+ <div v-if="designsCount" class="d-flex align-items-center">
+ {{ paginationText }}
+ <div class="btn-group ml-3 mr-3">
+ <pagination-button
+ :design="previousDesign"
+ :title="s__('DesignManagement|Go to previous design')"
+ icon-name="angle-left"
+ class="js-previous-design"
+ />
+ <pagination-button
+ :design="nextDesign"
+ :title="s__('DesignManagement|Go to next design')"
+ icon-name="angle-right"
+ class="js-next-design"
+ />
+ </div>
+ </div>
+</template>
diff --git a/app/assets/javascripts/design_management_new/components/toolbar/pagination_button.vue b/app/assets/javascripts/design_management_new/components/toolbar/pagination_button.vue
new file mode 100644
index 00000000000..f00ecefca01
--- /dev/null
+++ b/app/assets/javascripts/design_management_new/components/toolbar/pagination_button.vue
@@ -0,0 +1,48 @@
+<script>
+import Icon from '~/vue_shared/components/icon.vue';
+import { DESIGN_ROUTE_NAME } from '../../router/constants';
+
+export default {
+ components: {
+ Icon,
+ },
+ props: {
+ design: {
+ type: Object,
+ required: false,
+ default: null,
+ },
+ title: {
+ type: String,
+ required: true,
+ },
+ iconName: {
+ type: String,
+ required: true,
+ },
+ },
+ computed: {
+ designLink() {
+ if (!this.design) return {};
+
+ return {
+ name: DESIGN_ROUTE_NAME,
+ params: { id: this.design.filename },
+ query: this.$route.query,
+ };
+ },
+ },
+};
+</script>
+
+<template>
+ <router-link
+ :to="designLink"
+ :disabled="!design"
+ :class="{ disabled: !design }"
+ :aria-label="title"
+ class="btn btn-default"
+ >
+ <icon :name="iconName" />
+ </router-link>
+</template>
diff --git a/app/assets/javascripts/design_management_new/components/upload/button.vue b/app/assets/javascripts/design_management_new/components/upload/button.vue
new file mode 100644
index 00000000000..de8a38334ac
--- /dev/null
+++ b/app/assets/javascripts/design_management_new/components/upload/button.vue
@@ -0,0 +1,59 @@
+<script>
+import { GlButton, GlLoadingIcon, GlTooltipDirective } from '@gitlab/ui';
+import { VALID_DESIGN_FILE_MIMETYPE } from '../../constants';
+
+export default {
+ components: {
+ GlButton,
+ GlLoadingIcon,
+ },
+ directives: {
+ GlTooltip: GlTooltipDirective,
+ },
+ props: {
+ isSaving: {
+ type: Boolean,
+ required: true,
+ },
+ },
+ methods: {
+ openFileUpload() {
+ this.$refs.fileUpload.click();
+ },
+ onFileUploadChange(e) {
+ this.$emit('upload', e.target.files);
+ },
+ },
+ VALID_DESIGN_FILE_MIMETYPE,
+};
+</script>
+
+<template>
+ <div>
+ <gl-button
+ v-gl-tooltip.hover
+ :title="
+ s__(
+ 'DesignManagement|Adding a design with the same filename replaces the file in a new version.',
+ )
+ "
+ :disabled="isSaving"
+ variant="success"
+ size="small"
+ @click="openFileUpload"
+ >
+ {{ s__('DesignManagement|Upload designs') }}
+ <gl-loading-icon v-if="isSaving" inline class="ml-1" />
+ </gl-button>
+
+ <input
+ ref="fileUpload"
+ type="file"
+ name="design_file"
+ :accept="$options.VALID_DESIGN_FILE_MIMETYPE.mimetype"
+ class="hide"
+ multiple
+ @change="onFileUploadChange"
+ />
+ </div>
+</template>
diff --git a/app/assets/javascripts/design_management_new/components/upload/design_dropzone.vue b/app/assets/javascripts/design_management_new/components/upload/design_dropzone.vue
new file mode 100644
index 00000000000..7b829d63330
--- /dev/null
+++ b/app/assets/javascripts/design_management_new/components/upload/design_dropzone.vue
@@ -0,0 +1,136 @@
+<script>
+import { GlIcon, GlLink, GlSprintf } from '@gitlab/ui';
+import createFlash from '~/flash';
+import uploadDesignMutation from '../../graphql/mutations/upload_design.mutation.graphql';
+import { UPLOAD_DESIGN_INVALID_FILETYPE_ERROR } from '../../utils/error_messages';
+import { isValidDesignFile } from '../../utils/design_management_utils';
+import { VALID_DATA_TRANSFER_TYPE, VALID_DESIGN_FILE_MIMETYPE } from '../../constants';
+
+export default {
+ components: {
+ GlIcon,
+ GlLink,
+ GlSprintf,
+ },
+ props: {
+ hasDesigns: {
+ type: Boolean,
+ required: true,
+ },
+ },
+ data() {
+ return {
+ dragCounter: 0,
+ isDragDataValid: false,
+ };
+ },
+ computed: {
+ dragging() {
+ return this.dragCounter !== 0;
+ },
+ },
+ methods: {
+ isValidUpload(files) {
+ return files.every(isValidDesignFile);
+ },
+ isValidDragDataType({ dataTransfer }) {
+ return Boolean(dataTransfer && dataTransfer.types.some(t => t === VALID_DATA_TRANSFER_TYPE));
+ },
+ ondrop({ dataTransfer = {} }) {
+ this.dragCounter = 0;
+ // User already had feedback when dropzone was active, so bail here
+ if (!this.isDragDataValid) {
+ return;
+ }
+
+ const { files } = dataTransfer;
+ if (!this.isValidUpload(Array.from(files))) {
+ createFlash(UPLOAD_DESIGN_INVALID_FILETYPE_ERROR);
+ return;
+ }
+
+ this.$emit('change', files);
+ },
+ ondragenter(e) {
+ this.dragCounter += 1;
+ this.isDragDataValid = this.isValidDragDataType(e);
+ },
+ ondragleave() {
+ this.dragCounter -= 1;
+ },
+ openFileUpload() {
+ this.$refs.fileUpload.click();
+ },
+ onDesignInputChange(e) {
+ this.$emit('change', e.target.files);
+ },
+ },
+ uploadDesignMutation,
+ VALID_DESIGN_FILE_MIMETYPE,
+};
+</script>
+
+<template>
+ <div
+ class="w-100 position-relative"
+ @dragstart.prevent.stop
+ @dragend.prevent.stop
+ @dragover.prevent.stop
+ @dragenter.prevent.stop="ondragenter"
+ @dragleave.prevent.stop="ondragleave"
+ @drop.prevent.stop="ondrop"
+ >
+ <slot>
+ <button
+ class="card design-dropzone-card design-dropzone-border w-100 h-100 gl-align-items-center gl-justify-content-center gl-p-3"
+ @click="openFileUpload"
+ >
+ <div
+ :class="{ 'gl-flex-direction-column': hasDesigns }"
+ class="gl-display-flex gl-align-items-center gl-justify-content-center gl-text-center"
+ data-testid="dropzone-area"
+ >
+ <gl-icon name="upload" :size="24" :class="hasDesigns ? 'gl-mb-2' : 'gl-mr-4'" />
+ <p class="gl-font-weight-bold gl-mb-0">
+ <gl-sprintf :message="__('Drop or %{linkStart}upload%{linkEnd} Designs to attach')">
+ <template #link="{ content }">
+ <gl-link class="gl-font-weight-normal" @click.stop="openFileUpload">
+ {{ content }}
+ </gl-link>
+ </template>
+ </gl-sprintf>
+ </p>
+ </div>
+ </button>
+
+ <input
+ ref="fileUpload"
+ type="file"
+ name="design_file"
+ :accept="$options.VALID_DESIGN_FILE_MIMETYPE.mimetype"
+ class="hide"
+ multiple
+ @change="onDesignInputChange"
+ />
+ </slot>
+ <transition name="design-dropzone-fade">
+ <div
+ v-show="dragging"
+ class="card design-dropzone-border design-dropzone-overlay w-100 h-100 position-absolute d-flex-center p-3 bg-white"
+ >
+ <div v-show="!isDragDataValid" class="mw-50 text-center">
+ <h3 :class="{ 'gl-font-base gl-display-inline': !hasDesigns }">{{ __('Oh no!') }}</h3>
+ <span>{{
+ __(
+ 'You are trying to upload something other than an image. Please upload a .png, .jpg, .jpeg, .gif, .bmp, .tiff or .ico.',
+ )
+ }}</span>
+ </div>
+ <div v-show="isDragDataValid" class="mw-50 text-center">
+ <h3 :class="{ 'gl-font-base gl-display-inline': !hasDesigns }">{{ __('Incoming!') }}</h3>
+ <span>{{ __('Drop your designs to start your upload.') }}</span>
+ </div>
+ </div>
+ </transition>
+ </div>
+</template>
diff --git a/app/assets/javascripts/design_management_new/components/upload/design_version_dropdown.vue b/app/assets/javascripts/design_management_new/components/upload/design_version_dropdown.vue
new file mode 100644
index 00000000000..5299d0ce09e
--- /dev/null
+++ b/app/assets/javascripts/design_management_new/components/upload/design_version_dropdown.vue
@@ -0,0 +1,76 @@
+<script>
+import { GlNewDropdown, GlNewDropdownItem } from '@gitlab/ui';
+import { __, sprintf } from '~/locale';
+import allVersionsMixin from '../../mixins/all_versions';
+import { findVersionId } from '../../utils/design_management_utils';
+
+export default {
+ components: {
+ GlNewDropdown,
+ GlNewDropdownItem,
+ },
+ mixins: [allVersionsMixin],
+ computed: {
+ queryVersion() {
+ return this.$route.query.version;
+ },
+ currentVersionIdx() {
+ if (!this.queryVersion) return 0;
+
+ const idx = this.allVersions.findIndex(
+ version => this.findVersionId(version.node.id) === this.queryVersion,
+ );
+
+ // if the currentVersionId isn't a valid version (i.e. not in allVersions)
+ // then return the latest version (index 0)
+ return idx !== -1 ? idx : 0;
+ },
+ currentVersionId() {
+ if (this.queryVersion) return this.queryVersion;
+
+ const currentVersion = this.allVersions[this.currentVersionIdx];
+ return this.findVersionId(currentVersion.node.id);
+ },
+ dropdownText() {
+ if (this.isLatestVersion) {
+ return __('Showing Latest Version');
+ }
+ // allVersions is sorted in reverse chronological order (latest first)
+ const currentVersionNumber = this.allVersions.length - this.currentVersionIdx;
+
+ return sprintf(__('Showing Version #%{versionNumber}'), {
+ versionNumber: currentVersionNumber,
+ });
+ },
+ },
+ methods: {
+ findVersionId,
+ },
+};
+</script>
+
+<template>
+ <gl-new-dropdown :text="dropdownText" size="small" class="design-version-dropdown">
+ <gl-new-dropdown-item v-for="(version, index) in allVersions" :key="version.node.id">
+ <router-link
+ class="d-flex js-version-link"
+ :to="{ path: $route.path, query: { version: findVersionId(version.node.id) } }"
+ >
+ <div class="flex-grow-1 ml-2">
+ <div>
+ <strong
+ >{{ __('Version') }} {{ allVersions.length - index }}
+ <span v-if="findVersionId(version.node.id) === latestVersionId"
+ >({{ __('latest') }})</span
+ >
+ </strong>
+ </div>
+ </div>
+ <i
+ v-if="findVersionId(version.node.id) === currentVersionId"
+ class="fa fa-check pull-right"
+ ></i>
+ </router-link>
+ </gl-new-dropdown-item>
+ </gl-new-dropdown>
+</template>
diff --git a/app/assets/javascripts/design_management_new/constants.js b/app/assets/javascripts/design_management_new/constants.js
new file mode 100644
index 00000000000..21ff361a277
--- /dev/null
+++ b/app/assets/javascripts/design_management_new/constants.js
@@ -0,0 +1,16 @@
+// WARNING: replace this with something
+// more sensical as per https://gitlab.com/gitlab-org/gitlab/issues/118611
+export const VALID_DESIGN_FILE_MIMETYPE = {
+ mimetype: 'image/*',
+ regex: /image\/.+/,
+};
+
+// https://developer.mozilla.org/en-US/docs/Web/API/DataTransfer/types
+export const VALID_DATA_TRANSFER_TYPE = 'Files';
+
+export const ACTIVE_DISCUSSION_SOURCE_TYPES = {
+ pin: 'pin',
+ discussion: 'discussion',
+};
+
+export const DESIGN_DETAIL_LAYOUT_CLASSLIST = ['design-detail-layout', 'overflow-hidden', 'm-0'];
diff --git a/app/assets/javascripts/design_management_new/graphql.js b/app/assets/javascripts/design_management_new/graphql.js
new file mode 100644
index 00000000000..fae337aa75b
--- /dev/null
+++ b/app/assets/javascripts/design_management_new/graphql.js
@@ -0,0 +1,45 @@
+import Vue from 'vue';
+import VueApollo from 'vue-apollo';
+import { uniqueId } from 'lodash';
+import { defaultDataIdFromObject } from 'apollo-cache-inmemory';
+import createDefaultClient from '~/lib/graphql';
+import activeDiscussionQuery from './graphql/queries/active_discussion.query.graphql';
+import typeDefs from './graphql/typedefs.graphql';
+
+Vue.use(VueApollo);
+
+const resolvers = {
+ Mutation: {
+ updateActiveDiscussion: (_, { id = null, source }, { cache }) => {
+ const data = cache.readQuery({ query: activeDiscussionQuery });
+ data.activeDiscussion = {
+ __typename: 'ActiveDiscussion',
+ id,
+ source,
+ };
+ cache.writeQuery({ query: activeDiscussionQuery, data });
+ },
+ },
+};
+
+const defaultClient = createDefaultClient(
+ resolvers,
+ // This config is added temporarily to resolve an issue with duplicate design IDs.
+ // Should be removed as soon as https://gitlab.com/gitlab-org/gitlab/issues/13495 is resolved
+ {
+ cacheConfig: {
+ dataIdFromObject: object => {
+ // eslint-disable-next-line no-underscore-dangle, @gitlab/require-i18n-strings
+ if (object.__typename === 'Design') {
+ return object.id && object.image ? `${object.id}-${object.image}` : uniqueId();
+ }
+ return defaultDataIdFromObject(object);
+ },
+ },
+ typeDefs,
+ },
+);
+
+export default new VueApollo({
+ defaultClient,
+});
diff --git a/app/assets/javascripts/design_management_new/graphql/fragments/design.fragment.graphql b/app/assets/javascripts/design_management_new/graphql/fragments/design.fragment.graphql
new file mode 100644
index 00000000000..4b1703e41c3
--- /dev/null
+++ b/app/assets/javascripts/design_management_new/graphql/fragments/design.fragment.graphql
@@ -0,0 +1,24 @@
+#import "./design_note.fragment.graphql"
+#import "./design_list.fragment.graphql"
+#import "./diff_refs.fragment.graphql"
+#import "./discussion_resolved_status.fragment.graphql"
+
+fragment DesignItem on Design {
+ ...DesignListItem
+ fullPath
+ diffRefs {
+ ...DesignDiffRefs
+ }
+ discussions {
+ nodes {
+ id
+ replyId
+ ...ResolvedStatus
+ notes {
+ nodes {
+ ...DesignNote
+ }
+ }
+ }
+ }
+}
diff --git a/app/assets/javascripts/design_management_new/graphql/fragments/design_list.fragment.graphql b/app/assets/javascripts/design_management_new/graphql/fragments/design_list.fragment.graphql
new file mode 100644
index 00000000000..bc3132f9b42
--- /dev/null
+++ b/app/assets/javascripts/design_management_new/graphql/fragments/design_list.fragment.graphql
@@ -0,0 +1,8 @@
+fragment DesignListItem on Design {
+ id
+ event
+ filename
+ notesCount
+ image
+ imageV432x230
+}
diff --git a/app/assets/javascripts/design_management_new/graphql/fragments/design_note.fragment.graphql b/app/assets/javascripts/design_management_new/graphql/fragments/design_note.fragment.graphql
new file mode 100644
index 00000000000..26edd2c0be1
--- /dev/null
+++ b/app/assets/javascripts/design_management_new/graphql/fragments/design_note.fragment.graphql
@@ -0,0 +1,29 @@
+#import "./diff_refs.fragment.graphql"
+#import "~/graphql_shared/fragments/author.fragment.graphql"
+#import "./note_permissions.fragment.graphql"
+
+fragment DesignNote on Note {
+ id
+ author {
+ ...Author
+ }
+ body
+ bodyHtml
+ createdAt
+ resolved
+ position {
+ diffRefs {
+ ...DesignDiffRefs
+ }
+ x
+ y
+ height
+ width
+ }
+ userPermissions {
+ ...DesignNotePermissions
+ }
+ discussion {
+ id
+ }
+}
diff --git a/app/assets/javascripts/design_management_new/graphql/fragments/diff_refs.fragment.graphql b/app/assets/javascripts/design_management_new/graphql/fragments/diff_refs.fragment.graphql
new file mode 100644
index 00000000000..984a55814b0
--- /dev/null
+++ b/app/assets/javascripts/design_management_new/graphql/fragments/diff_refs.fragment.graphql
@@ -0,0 +1,5 @@
+fragment DesignDiffRefs on DiffRefs {
+ baseSha
+ startSha
+ headSha
+}
diff --git a/app/assets/javascripts/design_management_new/graphql/fragments/discussion_resolved_status.fragment.graphql b/app/assets/javascripts/design_management_new/graphql/fragments/discussion_resolved_status.fragment.graphql
new file mode 100644
index 00000000000..7483b508721
--- /dev/null
+++ b/app/assets/javascripts/design_management_new/graphql/fragments/discussion_resolved_status.fragment.graphql
@@ -0,0 +1,9 @@
+fragment ResolvedStatus on Discussion {
+ resolvable
+ resolved
+ resolvedAt
+ resolvedBy {
+ name
+ webUrl
+ }
+}
diff --git a/app/assets/javascripts/design_management_new/graphql/fragments/note_permissions.fragment.graphql b/app/assets/javascripts/design_management_new/graphql/fragments/note_permissions.fragment.graphql
new file mode 100644
index 00000000000..c243e39f3d3
--- /dev/null
+++ b/app/assets/javascripts/design_management_new/graphql/fragments/note_permissions.fragment.graphql
@@ -0,0 +1,3 @@
+fragment DesignNotePermissions on NotePermissions {
+ adminNote
+}
diff --git a/app/assets/javascripts/design_management_new/graphql/fragments/version.fragment.graphql b/app/assets/javascripts/design_management_new/graphql/fragments/version.fragment.graphql
new file mode 100644
index 00000000000..7eb40b12f51
--- /dev/null
+++ b/app/assets/javascripts/design_management_new/graphql/fragments/version.fragment.graphql
@@ -0,0 +1,4 @@
+fragment VersionListItem on DesignVersion {
+ id
+ sha
+}
diff --git a/app/assets/javascripts/design_management_new/graphql/mutations/create_image_diff_note.mutation.graphql b/app/assets/javascripts/design_management_new/graphql/mutations/create_image_diff_note.mutation.graphql
new file mode 100644
index 00000000000..c8ade328120
--- /dev/null
+++ b/app/assets/javascripts/design_management_new/graphql/mutations/create_image_diff_note.mutation.graphql
@@ -0,0 +1,21 @@
+#import "../fragments/design_note.fragment.graphql"
+
+mutation createImageDiffNote($input: CreateImageDiffNoteInput!) {
+ createImageDiffNote(input: $input) {
+ note {
+ ...DesignNote
+ discussion {
+ id
+ replyId
+ notes {
+ edges {
+ node {
+ ...DesignNote
+ }
+ }
+ }
+ }
+ }
+ errors
+ }
+}
diff --git a/app/assets/javascripts/design_management_new/graphql/mutations/create_note.mutation.graphql b/app/assets/javascripts/design_management_new/graphql/mutations/create_note.mutation.graphql
new file mode 100644
index 00000000000..184ee6955dc
--- /dev/null
+++ b/app/assets/javascripts/design_management_new/graphql/mutations/create_note.mutation.graphql
@@ -0,0 +1,10 @@
+#import "../fragments/design_note.fragment.graphql"
+
+mutation createNote($input: CreateNoteInput!) {
+ createNote(input: $input) {
+ note {
+ ...DesignNote
+ }
+ errors
+ }
+}
diff --git a/app/assets/javascripts/design_management_new/graphql/mutations/destroy_design.mutation.graphql b/app/assets/javascripts/design_management_new/graphql/mutations/destroy_design.mutation.graphql
new file mode 100644
index 00000000000..0b3cf636cdb
--- /dev/null
+++ b/app/assets/javascripts/design_management_new/graphql/mutations/destroy_design.mutation.graphql
@@ -0,0 +1,10 @@
+#import "../fragments/version.fragment.graphql"
+
+mutation destroyDesign($filenames: [String!]!, $projectPath: ID!, $iid: ID!) {
+ designManagementDelete(input: { projectPath: $projectPath, iid: $iid, filenames: $filenames }) {
+ version {
+ ...VersionListItem
+ }
+ errors
+ }
+}
diff --git a/app/assets/javascripts/design_management_new/graphql/mutations/toggle_resolve_discussion.mutation.graphql b/app/assets/javascripts/design_management_new/graphql/mutations/toggle_resolve_discussion.mutation.graphql
new file mode 100644
index 00000000000..1157fc05d5f
--- /dev/null
+++ b/app/assets/javascripts/design_management_new/graphql/mutations/toggle_resolve_discussion.mutation.graphql
@@ -0,0 +1,17 @@
+#import "../fragments/design_note.fragment.graphql"
+#import "../fragments/discussion_resolved_status.fragment.graphql"
+
+mutation toggleResolveDiscussion($id: ID!, $resolve: Boolean!) {
+ discussionToggleResolve(input: { id: $id, resolve: $resolve }) {
+ discussion {
+ id
+ ...ResolvedStatus
+ notes {
+ nodes {
+ ...DesignNote
+ }
+ }
+ }
+ errors
+ }
+}
diff --git a/app/assets/javascripts/design_management_new/graphql/mutations/update_active_discussion.mutation.graphql b/app/assets/javascripts/design_management_new/graphql/mutations/update_active_discussion.mutation.graphql
new file mode 100644
index 00000000000..a24b6737159
--- /dev/null
+++ b/app/assets/javascripts/design_management_new/graphql/mutations/update_active_discussion.mutation.graphql
@@ -0,0 +1,3 @@
+mutation updateActiveDiscussion($id: String, $source: String) {
+ updateActiveDiscussion(id: $id, source: $source) @client
+}
diff --git a/app/assets/javascripts/design_management_new/graphql/mutations/update_image_diff_note.mutation.graphql b/app/assets/javascripts/design_management_new/graphql/mutations/update_image_diff_note.mutation.graphql
new file mode 100644
index 00000000000..5562ca9d89f
--- /dev/null
+++ b/app/assets/javascripts/design_management_new/graphql/mutations/update_image_diff_note.mutation.graphql
@@ -0,0 +1,10 @@
+#import "../fragments/design_note.fragment.graphql"
+
+mutation updateImageDiffNote($input: UpdateImageDiffNoteInput!) {
+ updateImageDiffNote(input: $input) {
+ errors
+ note {
+ ...DesignNote
+ }
+ }
+}
diff --git a/app/assets/javascripts/design_management_new/graphql/mutations/update_note.mutation.graphql b/app/assets/javascripts/design_management_new/graphql/mutations/update_note.mutation.graphql
new file mode 100644
index 00000000000..b995e99fb6a
--- /dev/null
+++ b/app/assets/javascripts/design_management_new/graphql/mutations/update_note.mutation.graphql
@@ -0,0 +1,10 @@
+#import "../fragments/design_note.fragment.graphql"
+
+mutation updateNote($input: UpdateNoteInput!) {
+ updateNote(input: $input) {
+ note {
+ ...DesignNote
+ }
+ errors
+ }
+}
diff --git a/app/assets/javascripts/design_management_new/graphql/mutations/upload_design.mutation.graphql b/app/assets/javascripts/design_management_new/graphql/mutations/upload_design.mutation.graphql
new file mode 100644
index 00000000000..d694e6558a0
--- /dev/null
+++ b/app/assets/javascripts/design_management_new/graphql/mutations/upload_design.mutation.graphql
@@ -0,0 +1,21 @@
+#import "../fragments/design.fragment.graphql"
+
+mutation uploadDesign($files: [Upload!]!, $projectPath: ID!, $iid: ID!) {
+ designManagementUpload(input: { projectPath: $projectPath, iid: $iid, files: $files }) {
+ designs {
+ ...DesignItem
+ versions {
+ edges {
+ node {
+ id
+ sha
+ }
+ }
+ }
+ }
+ skippedDesigns {
+ filename
+ }
+ errors
+ }
+}
diff --git a/app/assets/javascripts/design_management_new/graphql/queries/active_discussion.query.graphql b/app/assets/javascripts/design_management_new/graphql/queries/active_discussion.query.graphql
new file mode 100644
index 00000000000..111023cea68
--- /dev/null
+++ b/app/assets/javascripts/design_management_new/graphql/queries/active_discussion.query.graphql
@@ -0,0 +1,6 @@
+query activeDiscussion {
+ activeDiscussion @client {
+ id
+ source
+ }
+}
diff --git a/app/assets/javascripts/design_management_new/graphql/queries/design_permissions.query.graphql b/app/assets/javascripts/design_management_new/graphql/queries/design_permissions.query.graphql
new file mode 100644
index 00000000000..a87b256dc95
--- /dev/null
+++ b/app/assets/javascripts/design_management_new/graphql/queries/design_permissions.query.graphql
@@ -0,0 +1,10 @@
+query permissions($fullPath: ID!, $iid: String!) {
+ project(fullPath: $fullPath) {
+ id
+ issue(iid: $iid) {
+ userPermissions {
+ createDesign
+ }
+ }
+ }
+}
diff --git a/app/assets/javascripts/design_management_new/graphql/queries/get_design.query.graphql b/app/assets/javascripts/design_management_new/graphql/queries/get_design.query.graphql
new file mode 100644
index 00000000000..07a9af55787
--- /dev/null
+++ b/app/assets/javascripts/design_management_new/graphql/queries/get_design.query.graphql
@@ -0,0 +1,31 @@
+#import "../fragments/design.fragment.graphql"
+#import "~/graphql_shared/fragments/author.fragment.graphql"
+
+query getDesign($fullPath: ID!, $iid: String!, $atVersion: ID, $filenames: [String!]) {
+ project(fullPath: $fullPath) {
+ id
+ issue(iid: $iid) {
+ designCollection {
+ designs(atVersion: $atVersion, filenames: $filenames) {
+ edges {
+ node {
+ ...DesignItem
+ issue {
+ title
+ webPath
+ webUrl
+ participants {
+ edges {
+ node {
+ ...Author
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/app/assets/javascripts/design_management_new/graphql/queries/get_design_list.query.graphql b/app/assets/javascripts/design_management_new/graphql/queries/get_design_list.query.graphql
new file mode 100644
index 00000000000..121a50555b3
--- /dev/null
+++ b/app/assets/javascripts/design_management_new/graphql/queries/get_design_list.query.graphql
@@ -0,0 +1,26 @@
+#import "../fragments/design_list.fragment.graphql"
+#import "../fragments/version.fragment.graphql"
+
+query getDesignList($fullPath: ID!, $iid: String!, $atVersion: ID) {
+ project(fullPath: $fullPath) {
+ id
+ issue(iid: $iid) {
+ designCollection {
+ designs(atVersion: $atVersion) {
+ edges {
+ node {
+ ...DesignListItem
+ }
+ }
+ }
+ versions {
+ edges {
+ node {
+ ...VersionListItem
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/app/assets/javascripts/design_management_new/graphql/typedefs.graphql b/app/assets/javascripts/design_management_new/graphql/typedefs.graphql
new file mode 100644
index 00000000000..fdbad4a90e0
--- /dev/null
+++ b/app/assets/javascripts/design_management_new/graphql/typedefs.graphql
@@ -0,0 +1,12 @@
+type ActiveDiscussion {
+ id: ID
+ source: String
+}
+
+extend type Query {
+ activeDiscussion: ActiveDiscussion
+}
+
+extend type Mutation {
+ updateActiveDiscussion(id: ID!, source: String!): Boolean
+}
diff --git a/app/assets/javascripts/design_management_new/index.js b/app/assets/javascripts/design_management_new/index.js
new file mode 100644
index 00000000000..20c9cacf83f
--- /dev/null
+++ b/app/assets/javascripts/design_management_new/index.js
@@ -0,0 +1,33 @@
+import Vue from 'vue';
+import createRouter from './router';
+import App from './components/app.vue';
+import apolloProvider from './graphql';
+
+export default () => {
+ const el = document.querySelector('.js-design-management-new');
+ const { issueIid, projectPath, issuePath } = el.dataset;
+ const router = createRouter(issuePath);
+
+ apolloProvider.clients.defaultClient.cache.writeData({
+ data: {
+ activeDiscussion: {
+ __typename: 'ActiveDiscussion',
+ id: null,
+ source: null,
+ },
+ },
+ });
+
+ return new Vue({
+ el,
+ router,
+ apolloProvider,
+ provide: {
+ projectPath,
+ issueIid,
+ },
+ render(createElement) {
+ return createElement(App);
+ },
+ });
+};
diff --git a/app/assets/javascripts/design_management_new/mixins/all_designs.js b/app/assets/javascripts/design_management_new/mixins/all_designs.js
new file mode 100644
index 00000000000..f7d6551c46c
--- /dev/null
+++ b/app/assets/javascripts/design_management_new/mixins/all_designs.js
@@ -0,0 +1,49 @@
+import { propertyOf } from 'lodash';
+import createFlash from '~/flash';
+import { s__ } from '~/locale';
+import getDesignListQuery from '../graphql/queries/get_design_list.query.graphql';
+import { extractNodes } from '../utils/design_management_utils';
+import allVersionsMixin from './all_versions';
+import { DESIGNS_ROUTE_NAME } from '../router/constants';
+
+export default {
+ mixins: [allVersionsMixin],
+ apollo: {
+ designs: {
+ query: getDesignListQuery,
+ variables() {
+ return {
+ fullPath: this.projectPath,
+ iid: this.issueIid,
+ atVersion: this.designsVersion,
+ };
+ },
+ update: data => {
+ const designEdges = propertyOf(data)(['project', 'issue', 'designCollection', 'designs']);
+ if (designEdges) {
+ return extractNodes(designEdges);
+ }
+ return [];
+ },
+ error() {
+ this.error = true;
+ },
+ result() {
+ if (this.$route.query.version && !this.hasValidVersion) {
+ createFlash(
+ s__(
+ 'DesignManagement|Requested design version does not exist. Showing latest version instead',
+ ),
+ );
+ this.$router.replace({ name: DESIGNS_ROUTE_NAME, query: { version: undefined } });
+ }
+ },
+ },
+ },
+ data() {
+ return {
+ designs: [],
+ error: false,
+ };
+ },
+};
diff --git a/app/assets/javascripts/design_management_new/mixins/all_versions.js b/app/assets/javascripts/design_management_new/mixins/all_versions.js
new file mode 100644
index 00000000000..99e2ee9561c
--- /dev/null
+++ b/app/assets/javascripts/design_management_new/mixins/all_versions.js
@@ -0,0 +1,59 @@
+import getDesignListQuery from '../graphql/queries/get_design_list.query.graphql';
+import { findVersionId } from '../utils/design_management_utils';
+
+export default {
+ apollo: {
+ allVersions: {
+ query: getDesignListQuery,
+ variables() {
+ return {
+ fullPath: this.projectPath,
+ iid: this.issueIid,
+ atVersion: null,
+ };
+ },
+ update: data => data.project.issue.designCollection.versions.edges,
+ },
+ },
+ inject: {
+ projectPath: {
+ default: '',
+ },
+ issueIid: {
+ default: '',
+ },
+ },
+ computed: {
+ hasValidVersion() {
+ return (
+ this.$route.query.version &&
+ this.allVersions &&
+ this.allVersions.some(version => version.node.id.endsWith(this.$route.query.version))
+ );
+ },
+ designsVersion() {
+ return this.hasValidVersion
+ ? `gid://gitlab/DesignManagement::Version/${this.$route.query.version}`
+ : null;
+ },
+ latestVersionId() {
+ const latestVersion = this.allVersions[0];
+ return latestVersion && findVersionId(latestVersion.node.id);
+ },
+ isLatestVersion() {
+ if (this.allVersions.length > 0) {
+ return (
+ !this.$route.query.version ||
+ !this.latestVersionId ||
+ this.$route.query.version === this.latestVersionId
+ );
+ }
+ return true;
+ },
+ },
+ data() {
+ return {
+ allVersions: [],
+ };
+ },
+};
diff --git a/app/assets/javascripts/design_management_new/pages/design/index.vue b/app/assets/javascripts/design_management_new/pages/design/index.vue
new file mode 100644
index 00000000000..47f5e3a786f
--- /dev/null
+++ b/app/assets/javascripts/design_management_new/pages/design/index.vue
@@ -0,0 +1,367 @@
+<script>
+import Mousetrap from 'mousetrap';
+import { GlLoadingIcon, GlAlert } from '@gitlab/ui';
+import { ApolloMutation } from 'vue-apollo';
+import createFlash from '~/flash';
+import { fetchPolicies } from '~/lib/graphql';
+import allVersionsMixin from '../../mixins/all_versions';
+import Toolbar from '../../components/toolbar/index.vue';
+import DesignDestroyer from '../../components/design_destroyer.vue';
+import DesignScaler from '../../components/design_scaler.vue';
+import DesignPresentation from '../../components/design_presentation.vue';
+import DesignReplyForm from '../../components/design_notes/design_reply_form.vue';
+import DesignSidebar from '../../components/design_sidebar.vue';
+import getDesignQuery from '../../graphql/queries/get_design.query.graphql';
+import createImageDiffNoteMutation from '../../graphql/mutations/create_image_diff_note.mutation.graphql';
+import updateImageDiffNoteMutation from '../../graphql/mutations/update_image_diff_note.mutation.graphql';
+import updateActiveDiscussionMutation from '../../graphql/mutations/update_active_discussion.mutation.graphql';
+import {
+ extractDiscussions,
+ extractDesign,
+ updateImageDiffNoteOptimisticResponse,
+} from '../../utils/design_management_utils';
+import {
+ updateStoreAfterAddImageDiffNote,
+ updateStoreAfterUpdateImageDiffNote,
+} from '../../utils/cache_update';
+import {
+ ADD_DISCUSSION_COMMENT_ERROR,
+ ADD_IMAGE_DIFF_NOTE_ERROR,
+ UPDATE_IMAGE_DIFF_NOTE_ERROR,
+ DESIGN_NOT_FOUND_ERROR,
+ DESIGN_VERSION_NOT_EXIST_ERROR,
+ UPDATE_NOTE_ERROR,
+ designDeletionError,
+} from '../../utils/error_messages';
+import { trackDesignDetailView } from '../../utils/tracking';
+import { DESIGNS_ROUTE_NAME } from '../../router/constants';
+import { ACTIVE_DISCUSSION_SOURCE_TYPES } from '../../constants';
+
+export default {
+ components: {
+ ApolloMutation,
+ DesignReplyForm,
+ DesignPresentation,
+ DesignScaler,
+ DesignDestroyer,
+ Toolbar,
+ GlLoadingIcon,
+ GlAlert,
+ DesignSidebar,
+ },
+ mixins: [allVersionsMixin],
+ props: {
+ id: {
+ type: String,
+ required: true,
+ },
+ },
+ data() {
+ return {
+ design: {},
+ comment: '',
+ annotationCoordinates: null,
+ errorMessage: '',
+ scale: 1,
+ resolvedDiscussionsExpanded: false,
+ };
+ },
+ apollo: {
+ design: {
+ query: getDesignQuery,
+ // We want to see cached design version if we have one, and fetch newer version on the background to update discussions
+ fetchPolicy: fetchPolicies.CACHE_AND_NETWORK,
+ variables() {
+ return this.designVariables;
+ },
+ update: data => extractDesign(data),
+ result(res) {
+ this.onDesignQueryResult(res);
+ },
+ error() {
+ this.onQueryError(DESIGN_NOT_FOUND_ERROR);
+ },
+ },
+ },
+ computed: {
+ isFirstLoading() {
+ // We only want to show spinner on initial design load (when opened from a deep link to design)
+ // If we already have cached a design, loading shouldn't be indicated to user
+ return this.$apollo.queries.design.loading && !this.design.filename;
+ },
+ discussions() {
+ if (!this.design.discussions) {
+ return [];
+ }
+ return extractDiscussions(this.design.discussions);
+ },
+ markdownPreviewPath() {
+ return `/${this.projectPath}/preview_markdown?target_type=Issue`;
+ },
+ isSubmitButtonDisabled() {
+ return this.comment.trim().length === 0;
+ },
+ designVariables() {
+ return {
+ fullPath: this.projectPath,
+ iid: this.issueIid,
+ filenames: [this.$route.params.id],
+ atVersion: this.designsVersion,
+ };
+ },
+ mutationPayload() {
+ const { x, y, width, height } = this.annotationCoordinates;
+ return {
+ noteableId: this.design.id,
+ body: this.comment,
+ position: {
+ headSha: this.design.diffRefs.headSha,
+ baseSha: this.design.diffRefs.baseSha,
+ startSha: this.design.diffRefs.startSha,
+ x,
+ y,
+ width,
+ height,
+ paths: {
+ newPath: this.design.fullPath,
+ },
+ },
+ };
+ },
+ isAnnotating() {
+ return Boolean(this.annotationCoordinates);
+ },
+ resolvedDiscussions() {
+ return this.discussions.filter(discussion => discussion.resolved);
+ },
+ },
+ watch: {
+ resolvedDiscussions(val) {
+ if (!val.length) {
+ this.resolvedDiscussionsExpanded = false;
+ }
+ },
+ },
+ mounted() {
+ Mousetrap.bind('esc', this.closeDesign);
+ this.trackEvent();
+ // We need to reset the active discussion when opening a new design
+ this.updateActiveDiscussion();
+ },
+ beforeDestroy() {
+ Mousetrap.unbind('esc', this.closeDesign);
+ },
+ methods: {
+ addImageDiffNoteToStore(
+ store,
+ {
+ data: { createImageDiffNote },
+ },
+ ) {
+ updateStoreAfterAddImageDiffNote(
+ store,
+ createImageDiffNote,
+ getDesignQuery,
+ this.designVariables,
+ );
+ },
+ updateImageDiffNoteInStore(
+ store,
+ {
+ data: { updateImageDiffNote },
+ },
+ ) {
+ return updateStoreAfterUpdateImageDiffNote(
+ store,
+ updateImageDiffNote,
+ getDesignQuery,
+ this.designVariables,
+ );
+ },
+ onMoveNote({ noteId, discussionId, position }) {
+ const discussion = this.discussions.find(({ id }) => id === discussionId);
+ const note = discussion.notes.find(
+ ({ discussion: noteDiscussion }) => noteDiscussion.id === discussionId,
+ );
+
+ const mutationPayload = {
+ optimisticResponse: updateImageDiffNoteOptimisticResponse(note, {
+ position,
+ }),
+ variables: {
+ input: {
+ id: noteId,
+ position,
+ },
+ },
+ mutation: updateImageDiffNoteMutation,
+ update: this.updateImageDiffNoteInStore,
+ };
+
+ return this.$apollo.mutate(mutationPayload).catch(e => this.onUpdateImageDiffNoteError(e));
+ },
+ onDesignQueryResult({ data, loading }) {
+ // On the initial load with cache-and-network policy data is undefined while loading is true
+ // To prevent throwing an error, we don't perform any logic until loading is false
+ if (loading) {
+ return;
+ }
+
+ if (!data || !extractDesign(data)) {
+ this.onQueryError(DESIGN_NOT_FOUND_ERROR);
+ } else if (this.$route.query.version && !this.hasValidVersion) {
+ this.onQueryError(DESIGN_VERSION_NOT_EXIST_ERROR);
+ }
+ },
+ onQueryError(message) {
+ // because we redirect user to /designs (the issue page),
+ // we want to create these flashes on the issue page
+ createFlash(message);
+ this.$router.push({ name: this.$options.DESIGNS_ROUTE_NAME });
+ },
+ onError(message, e) {
+ this.errorMessage = message;
+ throw e;
+ },
+ onCreateImageDiffNoteError(e) {
+ this.onError(ADD_IMAGE_DIFF_NOTE_ERROR, e);
+ },
+ onUpdateNoteError(e) {
+ this.onError(UPDATE_NOTE_ERROR, e);
+ },
+ onDesignDiscussionError(e) {
+ this.onError(ADD_DISCUSSION_COMMENT_ERROR, e);
+ },
+ onUpdateImageDiffNoteError(e) {
+ this.onError(UPDATE_IMAGE_DIFF_NOTE_ERROR, e);
+ },
+ onDesignDeleteError(e) {
+ this.onError(designDeletionError({ singular: true }), e);
+ },
+ onResolveDiscussionError(e) {
+ this.onError(UPDATE_IMAGE_DIFF_NOTE_ERROR, e);
+ },
+ openCommentForm(annotationCoordinates) {
+ this.annotationCoordinates = annotationCoordinates;
+ if (this.$refs.newDiscussionForm) {
+ this.$refs.newDiscussionForm.focusInput();
+ }
+ },
+ closeCommentForm() {
+ this.comment = '';
+ this.annotationCoordinates = null;
+ },
+ closeDesign() {
+ this.$router.push({
+ name: this.$options.DESIGNS_ROUTE_NAME,
+ query: this.$route.query,
+ });
+ },
+ trackEvent() {
+ // TODO: This needs to be made aware of referers, or if it's rendered in a different context than a Issue
+ trackDesignDetailView(
+ 'issue-design-collection',
+ 'issue',
+ this.$route.query.version || this.latestVersionId,
+ this.isLatestVersion,
+ );
+ },
+ updateActiveDiscussion(id) {
+ this.$apollo.mutate({
+ mutation: updateActiveDiscussionMutation,
+ variables: {
+ id,
+ source: ACTIVE_DISCUSSION_SOURCE_TYPES.discussion,
+ },
+ });
+ },
+ toggleResolvedComments() {
+ this.resolvedDiscussionsExpanded = !this.resolvedDiscussionsExpanded;
+ },
+ },
+ createImageDiffNoteMutation,
+ DESIGNS_ROUTE_NAME,
+};
+</script>
+
+<template>
+ <div
+ class="design-detail js-design-detail fixed-top w-100 position-bottom-0 d-flex justify-content-center flex-column flex-lg-row"
+ >
+ <gl-loading-icon v-if="isFirstLoading" size="xl" class="align-self-center" />
+ <template v-else>
+ <div class="d-flex overflow-hidden flex-grow-1 flex-column position-relative">
+ <design-destroyer
+ :filenames="[design.filename]"
+ :project-path="projectPath"
+ :iid="issueIid"
+ @done="$router.push({ name: $options.DESIGNS_ROUTE_NAME })"
+ @error="onDesignDeleteError"
+ >
+ <template #default="{ mutate, loading }">
+ <toolbar
+ :id="id"
+ :is-deleting="loading"
+ :is-latest-version="isLatestVersion"
+ v-bind="design"
+ @delete="mutate"
+ />
+ </template>
+ </design-destroyer>
+
+ <div v-if="errorMessage" class="p-3">
+ <gl-alert variant="danger" @dismiss="errorMessage = null">
+ {{ errorMessage }}
+ </gl-alert>
+ </div>
+ <design-presentation
+ :image="design.image"
+ :image-name="design.filename"
+ :discussions="discussions"
+ :is-annotating="isAnnotating"
+ :scale="scale"
+ :resolved-discussions-expanded="resolvedDiscussionsExpanded"
+ @openCommentForm="openCommentForm"
+ @closeCommentForm="closeCommentForm"
+ @moveNote="onMoveNote"
+ />
+
+ <div class="design-scaler-wrapper position-absolute mb-4 d-flex-center">
+ <design-scaler @scale="scale = $event" />
+ </div>
+ </div>
+ <design-sidebar
+ :design="design"
+ :resolved-discussions-expanded="resolvedDiscussionsExpanded"
+ :markdown-preview-path="markdownPreviewPath"
+ @onDesignDiscussionError="onDesignDiscussionError"
+ @onCreateImageDiffNoteError="onCreateImageDiffNoteError"
+ @updateNoteError="onUpdateNoteError"
+ @resolveDiscussionError="onResolveDiscussionError"
+ @toggleResolvedComments="toggleResolvedComments"
+ >
+ <template #replyForm>
+ <apollo-mutation
+ v-if="isAnnotating"
+ #default="{ mutate, loading }"
+ :mutation="$options.createImageDiffNoteMutation"
+ :variables="{
+ input: mutationPayload,
+ }"
+ :update="addImageDiffNoteToStore"
+ @done="closeCommentForm"
+ @error="onCreateImageDiffNoteError"
+ >
+ <design-reply-form
+ ref="newDiscussionForm"
+ v-model="comment"
+ :is-saving="loading"
+ :markdown-preview-path="markdownPreviewPath"
+ @submitForm="mutate"
+ @cancelForm="closeCommentForm"
+ /> </apollo-mutation
+ ></template>
+ </design-sidebar>
+ </template>
+ </div>
+</template>
diff --git a/app/assets/javascripts/design_management_new/pages/index.vue b/app/assets/javascripts/design_management_new/pages/index.vue
new file mode 100644
index 00000000000..2a100fae280
--- /dev/null
+++ b/app/assets/javascripts/design_management_new/pages/index.vue
@@ -0,0 +1,346 @@
+<script>
+import { GlLoadingIcon, GlButton, GlAlert } from '@gitlab/ui';
+import createFlash from '~/flash';
+import { s__, sprintf } from '~/locale';
+import UploadButton from '../components/upload/button.vue';
+import DeleteButton from '../components/delete_button.vue';
+import Design from '../components/list/item.vue';
+import DesignDestroyer from '../components/design_destroyer.vue';
+import DesignVersionDropdown from '../components/upload/design_version_dropdown.vue';
+import DesignDropzone from '../components/upload/design_dropzone.vue';
+import uploadDesignMutation from '../graphql/mutations/upload_design.mutation.graphql';
+import permissionsQuery from '../graphql/queries/design_permissions.query.graphql';
+import getDesignListQuery from '../graphql/queries/get_design_list.query.graphql';
+import allDesignsMixin from '../mixins/all_designs';
+import {
+ UPLOAD_DESIGN_ERROR,
+ EXISTING_DESIGN_DROP_MANY_FILES_MESSAGE,
+ EXISTING_DESIGN_DROP_INVALID_FILENAME_MESSAGE,
+ designUploadSkippedWarning,
+ designDeletionError,
+} from '../utils/error_messages';
+import { updateStoreAfterUploadDesign } from '../utils/cache_update';
+import {
+ designUploadOptimisticResponse,
+ isValidDesignFile,
+} from '../utils/design_management_utils';
+import { getFilename } from '~/lib/utils/file_upload';
+import { DESIGNS_ROUTE_NAME } from '../router/constants';
+
+const MAXIMUM_FILE_UPLOAD_LIMIT = 10;
+
+export default {
+ components: {
+ GlLoadingIcon,
+ GlAlert,
+ GlButton,
+ UploadButton,
+ Design,
+ DesignDestroyer,
+ DesignVersionDropdown,
+ DeleteButton,
+ DesignDropzone,
+ },
+ mixins: [allDesignsMixin],
+ apollo: {
+ permissions: {
+ query: permissionsQuery,
+ variables() {
+ return {
+ fullPath: this.projectPath,
+ iid: this.issueIid,
+ };
+ },
+ update: data => data.project.issue.userPermissions,
+ },
+ },
+ data() {
+ return {
+ permissions: {
+ createDesign: false,
+ },
+ filesToBeSaved: [],
+ selectedDesigns: [],
+ };
+ },
+ computed: {
+ isLoading() {
+ return this.$apollo.queries.designs.loading || this.$apollo.queries.permissions.loading;
+ },
+ isSaving() {
+ return this.filesToBeSaved.length > 0;
+ },
+ canCreateDesign() {
+ return this.permissions.createDesign;
+ },
+ showToolbar() {
+ return this.canCreateDesign && this.allVersions.length > 0;
+ },
+ hasDesigns() {
+ return this.designs.length > 0;
+ },
+ hasSelectedDesigns() {
+ return this.selectedDesigns.length > 0;
+ },
+ canDeleteDesigns() {
+ return this.isLatestVersion && this.hasSelectedDesigns;
+ },
+ projectQueryBody() {
+ return {
+ query: getDesignListQuery,
+ variables: { fullPath: this.projectPath, iid: this.issueIid, atVersion: null },
+ };
+ },
+ selectAllButtonText() {
+ return this.hasSelectedDesigns
+ ? s__('DesignManagement|Deselect all')
+ : s__('DesignManagement|Select all');
+ },
+ isDesignListEmpty() {
+ return !this.isSaving && !this.hasDesigns;
+ },
+ designDropzoneWrapperClass() {
+ return this.isDesignListEmpty
+ ? 'col-12'
+ : 'gl-flex-direction-column col-md-6 col-lg-3 gl-mb-3';
+ },
+ },
+ mounted() {
+ this.toggleOnPasteListener(this.$route.name);
+ },
+ methods: {
+ resetFilesToBeSaved() {
+ this.filesToBeSaved = [];
+ },
+ /**
+ * Determine if a design upload is valid, given [files]
+ * @param {Array<File>} files
+ */
+ isValidDesignUpload(files) {
+ if (!this.canCreateDesign) return false;
+
+ if (files.length > MAXIMUM_FILE_UPLOAD_LIMIT) {
+ createFlash(
+ sprintf(
+ s__(
+ 'DesignManagement|The maximum number of designs allowed to be uploaded is %{upload_limit}. Please try again.',
+ ),
+ {
+ upload_limit: MAXIMUM_FILE_UPLOAD_LIMIT,
+ },
+ ),
+ );
+
+ return false;
+ }
+ return true;
+ },
+ onUploadDesign(files) {
+ // convert to Array so that we have Array methods (.map, .some, etc.)
+ this.filesToBeSaved = Array.from(files);
+ if (!this.isValidDesignUpload(this.filesToBeSaved)) return null;
+
+ const mutationPayload = {
+ optimisticResponse: designUploadOptimisticResponse(this.filesToBeSaved),
+ variables: {
+ files: this.filesToBeSaved,
+ projectPath: this.projectPath,
+ iid: this.issueIid,
+ },
+ context: {
+ hasUpload: true,
+ },
+ mutation: uploadDesignMutation,
+ update: this.afterUploadDesign,
+ };
+
+ return this.$apollo
+ .mutate(mutationPayload)
+ .then(res => this.onUploadDesignDone(res))
+ .catch(() => this.onUploadDesignError());
+ },
+ afterUploadDesign(
+ store,
+ {
+ data: { designManagementUpload },
+ },
+ ) {
+ updateStoreAfterUploadDesign(store, designManagementUpload, this.projectQueryBody);
+ },
+ onUploadDesignDone(res) {
+ const skippedFiles = res?.data?.designManagementUpload?.skippedDesigns || [];
+ const skippedWarningMessage = designUploadSkippedWarning(this.filesToBeSaved, skippedFiles);
+ if (skippedWarningMessage) {
+ createFlash(skippedWarningMessage, 'warning');
+ }
+
+ // if this upload resulted in a new version being created, redirect user to the latest version
+ if (!this.isLatestVersion) {
+ this.$router.push({ name: DESIGNS_ROUTE_NAME });
+ }
+ this.resetFilesToBeSaved();
+ },
+ onUploadDesignError() {
+ this.resetFilesToBeSaved();
+ createFlash(UPLOAD_DESIGN_ERROR);
+ },
+ changeSelectedDesigns(filename) {
+ if (this.isDesignSelected(filename)) {
+ this.selectedDesigns = this.selectedDesigns.filter(design => design !== filename);
+ } else {
+ this.selectedDesigns.push(filename);
+ }
+ },
+ toggleDesignsSelection() {
+ if (this.hasSelectedDesigns) {
+ this.selectedDesigns = [];
+ } else {
+ this.selectedDesigns = this.designs.map(design => design.filename);
+ }
+ },
+ isDesignSelected(filename) {
+ return this.selectedDesigns.includes(filename);
+ },
+ isDesignToBeSaved(filename) {
+ return this.filesToBeSaved.some(file => file.name === filename);
+ },
+ canSelectDesign(filename) {
+ return this.isLatestVersion && this.canCreateDesign && !this.isDesignToBeSaved(filename);
+ },
+ onDesignDelete() {
+ this.selectedDesigns = [];
+ if (this.$route.query.version) this.$router.push({ name: DESIGNS_ROUTE_NAME });
+ },
+ onDesignDeleteError() {
+ const errorMessage = designDeletionError({ singular: this.selectedDesigns.length === 1 });
+ createFlash(errorMessage);
+ },
+ onExistingDesignDropzoneChange(files, existingDesignFilename) {
+ const filesArr = Array.from(files);
+
+ if (filesArr.length > 1) {
+ createFlash(EXISTING_DESIGN_DROP_MANY_FILES_MESSAGE);
+ return;
+ }
+
+ if (!filesArr.some(({ name }) => existingDesignFilename === name)) {
+ createFlash(EXISTING_DESIGN_DROP_INVALID_FILENAME_MESSAGE);
+ return;
+ }
+
+ this.onUploadDesign(files);
+ },
+ onDesignPaste(event) {
+ const { clipboardData } = event;
+ const files = Array.from(clipboardData.files);
+ if (clipboardData && files.length > 0) {
+ if (!files.some(isValidDesignFile)) {
+ return;
+ }
+ event.preventDefault();
+ let filename = getFilename(event);
+ if (!filename || filename === 'image.png') {
+ filename = `design_${Date.now()}.png`;
+ }
+ const newFile = new File([files[0]], filename);
+ this.onUploadDesign([newFile]);
+ }
+ },
+ toggleOnPasteListener(route) {
+ if (route === DESIGNS_ROUTE_NAME) {
+ document.addEventListener('paste', this.onDesignPaste);
+ } else {
+ document.removeEventListener('paste', this.onDesignPaste);
+ }
+ },
+ },
+ beforeRouteUpdate(to, from, next) {
+ this.toggleOnPasteListener(to.name);
+ this.selectedDesigns = [];
+ next();
+ },
+ beforeRouteLeave(to, from, next) {
+ this.toggleOnPasteListener(to.name);
+ next();
+ },
+};
+</script>
+
+<template>
+ <div data-testid="designs-root" class="gl-mt-5">
+ <header v-if="showToolbar" class="row-content-block border-top-0 p-2 d-flex">
+ <div class="gl-display-flex gl-justify-content-space-between gl-align-items-center gl-w-full">
+ <div>
+ <span class="gl-font-weight-bold gl-mr-3">{{ s__('DesignManagement|Designs') }}</span>
+ <design-version-dropdown />
+ </div>
+ <div v-show="hasDesigns" class="qa-selector-toolbar gl-display-flex">
+ <gl-button
+ v-if="isLatestVersion"
+ variant="link"
+ size="small"
+ class="gl-mr-2 js-select-all"
+ @click="toggleDesignsSelection"
+ >{{ selectAllButtonText }}
+ </gl-button>
+ <design-destroyer
+ #default="{ mutate, loading }"
+ :filenames="selectedDesigns"
+ @done="onDesignDelete"
+ @error="onDesignDeleteError"
+ >
+ <delete-button
+ v-if="isLatestVersion"
+ :is-deleting="loading"
+ button-variant="danger"
+ button-class="gl-mr-4"
+ button-size="small"
+ :has-selected-designs="hasSelectedDesigns"
+ @deleteSelectedDesigns="mutate()"
+ >
+ {{ s__('DesignManagement|Delete selected') }}
+ <gl-loading-icon v-if="loading" inline class="ml-1" />
+ </delete-button>
+ </design-destroyer>
+ <upload-button v-if="canCreateDesign" :is-saving="isSaving" @upload="onUploadDesign" />
+ </div>
+ </div>
+ </header>
+ <div class="mt-4">
+ <gl-loading-icon v-if="isLoading" size="md" />
+ <gl-alert v-else-if="error" variant="danger" :dismissible="false">
+ {{ __('An error occurred while loading designs. Please try again.') }}
+ </gl-alert>
+ <ol v-else class="list-unstyled row">
+ <span
+ v-if="isDesignListEmpty && !allVersions.length"
+ class="gl-font-weight-bold gl-font-weight-bold gl-ml-5 gl-mb-4"
+ >{{ s__('DesignManagement|Designs') }}</span
+ >
+ <li :class="designDropzoneWrapperClass" data-testid="design-dropzone-wrapper">
+ <design-dropzone
+ :class="{ 'design-list-item design-list-item-new': !isDesignListEmpty }"
+ :has-designs="hasDesigns"
+ @change="onUploadDesign"
+ />
+ </li>
+ <li v-for="design in designs" :key="design.id" class="col-md-6 col-lg-3 gl-mb-3">
+ <design-dropzone
+ :has-designs="hasDesigns"
+ @change="onExistingDesignDropzoneChange($event, design.filename)"
+ ><design v-bind="design" :is-uploading="isDesignToBeSaved(design.filename)"
+ /></design-dropzone>
+
+ <input
+ v-if="canSelectDesign(design.filename)"
+ :checked="isDesignSelected(design.filename)"
+ type="checkbox"
+ class="design-checkbox"
+ @change="changeSelectedDesigns(design.filename)"
+ />
+ </li>
+ </ol>
+ </div>
+ <router-view :key="$route.fullPath" />
+ </div>
+</template>
diff --git a/app/assets/javascripts/design_management_new/router/constants.js b/app/assets/javascripts/design_management_new/router/constants.js
new file mode 100644
index 00000000000..dd2ee8d8689
--- /dev/null
+++ b/app/assets/javascripts/design_management_new/router/constants.js
@@ -0,0 +1,2 @@
+export const DESIGNS_ROUTE_NAME = 'designs';
+export const DESIGN_ROUTE_NAME = 'design';
diff --git a/app/assets/javascripts/design_management_new/router/index.js b/app/assets/javascripts/design_management_new/router/index.js
new file mode 100644
index 00000000000..40e2d35bc40
--- /dev/null
+++ b/app/assets/javascripts/design_management_new/router/index.js
@@ -0,0 +1,32 @@
+import Vue from 'vue';
+import VueRouter from 'vue-router';
+import routes from './routes';
+import { DESIGN_ROUTE_NAME } from './constants';
+import { getPageLayoutElement } from '~/design_management_new/utils/design_management_utils';
+import { DESIGN_DETAIL_LAYOUT_CLASSLIST } from '../constants';
+
+Vue.use(VueRouter);
+
+export default function createRouter(base) {
+ const router = new VueRouter({
+ base,
+ mode: 'history',
+ routes,
+ });
+ const pageEl = getPageLayoutElement();
+
+ router.beforeEach(({ name }, _, next) => {
+ // apply a fullscreen layout style in Design View (a.k.a design detail)
+ if (pageEl) {
+ if (name === DESIGN_ROUTE_NAME) {
+ pageEl.classList.add(...DESIGN_DETAIL_LAYOUT_CLASSLIST);
+ } else {
+ pageEl.classList.remove(...DESIGN_DETAIL_LAYOUT_CLASSLIST);
+ }
+ }
+
+ next();
+ });
+
+ return router;
+}
diff --git a/app/assets/javascripts/design_management_new/router/routes.js b/app/assets/javascripts/design_management_new/router/routes.js
new file mode 100644
index 00000000000..d888b856611
--- /dev/null
+++ b/app/assets/javascripts/design_management_new/router/routes.js
@@ -0,0 +1,29 @@
+import Home from '../pages/index.vue';
+import DesignDetail from '../pages/design/index.vue';
+import { DESIGNS_ROUTE_NAME, DESIGN_ROUTE_NAME } from './constants';
+
+export default [
+ {
+ name: DESIGNS_ROUTE_NAME,
+ path: '/',
+ component: Home,
+ alias: '/designs',
+ },
+ {
+ name: DESIGN_ROUTE_NAME,
+ path: '/designs/:id',
+ component: DesignDetail,
+ beforeEnter(
+ {
+ params: { id },
+ },
+ _,
+ next,
+ ) {
+ if (typeof id === 'string') {
+ next();
+ }
+ },
+ props: ({ params: { id } }) => ({ id }),
+ },
+];
diff --git a/app/assets/javascripts/design_management_new/utils/cache_update.js b/app/assets/javascripts/design_management_new/utils/cache_update.js
new file mode 100644
index 00000000000..24b374b79fd
--- /dev/null
+++ b/app/assets/javascripts/design_management_new/utils/cache_update.js
@@ -0,0 +1,276 @@
+/* eslint-disable @gitlab/require-i18n-strings */
+
+import createFlash from '~/flash';
+import { extractCurrentDiscussion, extractDesign } from './design_management_utils';
+import {
+ ADD_IMAGE_DIFF_NOTE_ERROR,
+ UPDATE_IMAGE_DIFF_NOTE_ERROR,
+ ADD_DISCUSSION_COMMENT_ERROR,
+ designDeletionError,
+} from './error_messages';
+
+const deleteDesignsFromStore = (store, query, selectedDesigns) => {
+ const data = store.readQuery(query);
+
+ const changedDesigns = data.project.issue.designCollection.designs.edges.filter(
+ ({ node }) => !selectedDesigns.includes(node.filename),
+ );
+ data.project.issue.designCollection.designs.edges = [...changedDesigns];
+
+ store.writeQuery({
+ ...query,
+ data,
+ });
+};
+
+/**
+ * Adds a new version of designs to store
+ *
+ * @param {Object} store
+ * @param {Object} query
+ * @param {Object} version
+ */
+const addNewVersionToStore = (store, query, version) => {
+ if (!version) return;
+
+ const data = store.readQuery(query);
+ const newEdge = { node: version, __typename: 'DesignVersionEdge' };
+
+ data.project.issue.designCollection.versions.edges = [
+ newEdge,
+ ...data.project.issue.designCollection.versions.edges,
+ ];
+
+ store.writeQuery({
+ ...query,
+ data,
+ });
+};
+
+const addDiscussionCommentToStore = (store, createNote, query, queryVariables, discussionId) => {
+ const data = store.readQuery({
+ query,
+ variables: queryVariables,
+ });
+
+ const design = extractDesign(data);
+ const currentDiscussion = extractCurrentDiscussion(design.discussions, discussionId);
+ currentDiscussion.notes.nodes = [...currentDiscussion.notes.nodes, createNote.note];
+
+ design.notesCount += 1;
+ if (
+ !design.issue.participants.edges.some(
+ participant => participant.node.username === createNote.note.author.username,
+ )
+ ) {
+ design.issue.participants.edges = [
+ ...design.issue.participants.edges,
+ {
+ __typename: 'UserEdge',
+ node: {
+ __typename: 'User',
+ ...createNote.note.author,
+ },
+ },
+ ];
+ }
+ store.writeQuery({
+ query,
+ variables: queryVariables,
+ data: {
+ ...data,
+ design: {
+ ...design,
+ },
+ },
+ });
+};
+
+const addImageDiffNoteToStore = (store, createImageDiffNote, query, variables) => {
+ const data = store.readQuery({
+ query,
+ variables,
+ });
+ const newDiscussion = {
+ __typename: 'Discussion',
+ id: createImageDiffNote.note.discussion.id,
+ replyId: createImageDiffNote.note.discussion.replyId,
+ resolvable: true,
+ resolved: false,
+ resolvedAt: null,
+ resolvedBy: null,
+ notes: {
+ __typename: 'NoteConnection',
+ nodes: [createImageDiffNote.note],
+ },
+ };
+ const design = extractDesign(data);
+ const notesCount = design.notesCount + 1;
+ design.discussions.nodes = [...design.discussions.nodes, newDiscussion];
+ if (
+ !design.issue.participants.edges.some(
+ participant => participant.node.username === createImageDiffNote.note.author.username,
+ )
+ ) {
+ design.issue.participants.edges = [
+ ...design.issue.participants.edges,
+ {
+ __typename: 'UserEdge',
+ node: {
+ __typename: 'User',
+ ...createImageDiffNote.note.author,
+ },
+ },
+ ];
+ }
+ store.writeQuery({
+ query,
+ variables,
+ data: {
+ ...data,
+ design: {
+ ...design,
+ notesCount,
+ },
+ },
+ });
+};
+
+const updateImageDiffNoteInStore = (store, updateImageDiffNote, query, variables) => {
+ const data = store.readQuery({
+ query,
+ variables,
+ });
+
+ const design = extractDesign(data);
+ const discussion = extractCurrentDiscussion(
+ design.discussions,
+ updateImageDiffNote.note.discussion.id,
+ );
+
+ discussion.notes = {
+ ...discussion.notes,
+ nodes: [updateImageDiffNote.note, ...discussion.notes.nodes.slice(1)],
+ };
+
+ store.writeQuery({
+ query,
+ variables,
+ data: {
+ ...data,
+ design,
+ },
+ });
+};
+
+const addNewDesignToStore = (store, designManagementUpload, query) => {
+ const data = store.readQuery(query);
+
+ const newDesigns = data.project.issue.designCollection.designs.edges.reduce((acc, design) => {
+ if (!acc.find(d => d.filename === design.node.filename)) {
+ acc.push(design.node);
+ }
+
+ return acc;
+ }, designManagementUpload.designs);
+
+ let newVersionNode;
+ const findNewVersions = designManagementUpload.designs.find(design => design.versions);
+
+ if (findNewVersions) {
+ const findNewVersionsEdges = findNewVersions.versions.edges;
+
+ if (findNewVersionsEdges && findNewVersionsEdges.length) {
+ newVersionNode = [findNewVersionsEdges[0]];
+ }
+ }
+
+ const newVersions = [
+ ...(newVersionNode || []),
+ ...data.project.issue.designCollection.versions.edges,
+ ];
+
+ const updatedDesigns = {
+ __typename: 'DesignCollection',
+ designs: {
+ __typename: 'DesignConnection',
+ edges: newDesigns.map(design => ({
+ __typename: 'DesignEdge',
+ node: design,
+ })),
+ },
+ versions: {
+ __typename: 'DesignVersionConnection',
+ edges: newVersions,
+ },
+ };
+
+ data.project.issue.designCollection = updatedDesigns;
+
+ store.writeQuery({
+ ...query,
+ data,
+ });
+};
+
+const onError = (data, message) => {
+ createFlash(message);
+ throw new Error(data.errors);
+};
+
+export const hasErrors = ({ errors = [] }) => errors?.length;
+
+/**
+ * Updates a store after design deletion
+ *
+ * @param {Object} store
+ * @param {Object} data
+ * @param {Object} query
+ * @param {Array} designs
+ */
+export const updateStoreAfterDesignsDelete = (store, data, query, designs) => {
+ if (hasErrors(data)) {
+ onError(data, designDeletionError({ singular: designs.length === 1 }));
+ } else {
+ deleteDesignsFromStore(store, query, designs);
+ addNewVersionToStore(store, query, data.version);
+ }
+};
+
+export const updateStoreAfterAddDiscussionComment = (
+ store,
+ data,
+ query,
+ queryVariables,
+ discussionId,
+) => {
+ if (hasErrors(data)) {
+ onError(data, ADD_DISCUSSION_COMMENT_ERROR);
+ } else {
+ addDiscussionCommentToStore(store, data, query, queryVariables, discussionId);
+ }
+};
+
+export const updateStoreAfterAddImageDiffNote = (store, data, query, queryVariables) => {
+ if (hasErrors(data)) {
+ onError(data, ADD_IMAGE_DIFF_NOTE_ERROR);
+ } else {
+ addImageDiffNoteToStore(store, data, query, queryVariables);
+ }
+};
+
+export const updateStoreAfterUpdateImageDiffNote = (store, data, query, queryVariables) => {
+ if (hasErrors(data)) {
+ onError(data, UPDATE_IMAGE_DIFF_NOTE_ERROR);
+ } else {
+ updateImageDiffNoteInStore(store, data, query, queryVariables);
+ }
+};
+
+export const updateStoreAfterUploadDesign = (store, data, query) => {
+ if (hasErrors(data)) {
+ onError(data, data.errors[0]);
+ } else {
+ addNewDesignToStore(store, data, query);
+ }
+};
diff --git a/app/assets/javascripts/design_management_new/utils/design_management_utils.js b/app/assets/javascripts/design_management_new/utils/design_management_utils.js
new file mode 100644
index 00000000000..22705cf67a1
--- /dev/null
+++ b/app/assets/javascripts/design_management_new/utils/design_management_utils.js
@@ -0,0 +1,128 @@
+import { uniqueId } from 'lodash';
+import { VALID_DESIGN_FILE_MIMETYPE } from '../constants';
+
+export const isValidDesignFile = ({ type }) =>
+ (type.match(VALID_DESIGN_FILE_MIMETYPE.regex) || []).length > 0;
+
+/**
+ * Returns formatted array that doesn't contain
+ * `edges`->`node` nesting
+ *
+ * @param {Array} elements
+ */
+
+export const extractNodes = elements => elements.edges.map(({ node }) => node);
+
+/**
+ * Returns formatted array of discussions that doesn't contain
+ * `edges`->`node` nesting for child notes
+ *
+ * @param {Array} discussions
+ */
+
+export const extractDiscussions = discussions =>
+ discussions.nodes.map((discussion, index) => ({
+ ...discussion,
+ index: index + 1,
+ notes: discussion.notes.nodes,
+ }));
+
+/**
+ * Returns a discussion with the given id from discussions array
+ *
+ * @param {Array} discussions
+ */
+
+export const extractCurrentDiscussion = (discussions, id) =>
+ discussions.nodes.find(discussion => discussion.id === id);
+
+export const findVersionId = id => (id.match('::Version/(.+$)') || [])[1];
+
+export const findNoteId = id => (id.match('DiffNote/(.+$)') || [])[1];
+
+export const extractDesigns = data => data.project.issue.designCollection.designs.edges;
+
+export const extractDesign = data => (extractDesigns(data) || [])[0]?.node;
+
+/**
+ * Generates optimistic response for a design upload mutation
+ * @param {Array<File>} files
+ */
+export const designUploadOptimisticResponse = files => {
+ const designs = files.map(file => ({
+ // False positive i18n lint: https://gitlab.com/gitlab-org/frontend/eslint-plugin-i18n/issues/26
+ // eslint-disable-next-line @gitlab/require-i18n-strings
+ __typename: 'Design',
+ id: -uniqueId(),
+ image: '',
+ imageV432x230: '',
+ filename: file.name,
+ fullPath: '',
+ notesCount: 0,
+ event: 'NONE',
+ diffRefs: {
+ __typename: 'DiffRefs',
+ baseSha: '',
+ startSha: '',
+ headSha: '',
+ },
+ discussions: {
+ __typename: 'DesignDiscussion',
+ nodes: [],
+ },
+ versions: {
+ __typename: 'DesignVersionConnection',
+ edges: {
+ __typename: 'DesignVersionEdge',
+ node: {
+ __typename: 'DesignVersion',
+ id: -uniqueId(),
+ sha: -uniqueId(),
+ },
+ },
+ },
+ }));
+
+ return {
+ // False positive i18n lint: https://gitlab.com/gitlab-org/frontend/eslint-plugin-i18n/issues/26
+ // eslint-disable-next-line @gitlab/require-i18n-strings
+ __typename: 'Mutation',
+ designManagementUpload: {
+ __typename: 'DesignManagementUploadPayload',
+ designs,
+ skippedDesigns: [],
+ errors: [],
+ },
+ };
+};
+
+/**
+ * Generates optimistic response for a design upload mutation
+ * @param {Array<File>} files
+ */
+export const updateImageDiffNoteOptimisticResponse = (note, { position }) => ({
+ // False positive i18n lint: https://gitlab.com/gitlab-org/frontend/eslint-plugin-i18n/issues/26
+ // eslint-disable-next-line @gitlab/require-i18n-strings
+ __typename: 'Mutation',
+ updateImageDiffNote: {
+ __typename: 'UpdateImageDiffNotePayload',
+ note: {
+ ...note,
+ position: {
+ ...note.position,
+ ...position,
+ },
+ },
+ errors: [],
+ },
+});
+
+const normalizeAuthor = author => ({
+ ...author,
+ web_url: author.webUrl,
+ avatar_url: author.avatarUrl,
+});
+
+export const extractParticipants = users => users.edges.map(({ node }) => normalizeAuthor(node));
+
+export const getPageLayoutElement = () => document.querySelector('.layout-page');
diff --git a/app/assets/javascripts/design_management_new/utils/error_messages.js b/app/assets/javascripts/design_management_new/utils/error_messages.js
new file mode 100644
index 00000000000..7666c726c2f
--- /dev/null
+++ b/app/assets/javascripts/design_management_new/utils/error_messages.js
@@ -0,0 +1,95 @@
+import { __, s__, n__, sprintf } from '~/locale';
+
+export const ADD_DISCUSSION_COMMENT_ERROR = s__(
+ 'DesignManagement|Could not add a new comment. Please try again.',
+);
+
+export const ADD_IMAGE_DIFF_NOTE_ERROR = s__(
+ 'DesignManagement|Could not create new discussion. Please try again.',
+);
+
+export const UPDATE_IMAGE_DIFF_NOTE_ERROR = s__(
+ 'DesignManagement|Could not update discussion. Please try again.',
+);
+
+export const UPDATE_NOTE_ERROR = s__('DesignManagement|Could not update note. Please try again.');
+
+export const UPLOAD_DESIGN_ERROR = s__(
+ 'DesignManagement|Error uploading a new design. Please try again.',
+);
+
+export const UPLOAD_DESIGN_INVALID_FILETYPE_ERROR = __(
+ 'Could not upload your designs as one or more files uploaded are not supported.',
+);
+
+export const DESIGN_NOT_FOUND_ERROR = __('Could not find design.');
+
+export const DESIGN_VERSION_NOT_EXIST_ERROR = __('Requested design version does not exist.');
+
+const DESIGN_UPLOAD_SKIPPED_MESSAGE = s__('DesignManagement|Upload skipped.');
+
+const ALL_DESIGNS_SKIPPED_MESSAGE = `${DESIGN_UPLOAD_SKIPPED_MESSAGE} ${s__(
+ 'The designs you tried uploading did not change.',
+)}`;
+
+export const EXISTING_DESIGN_DROP_MANY_FILES_MESSAGE = __(
+ 'You can only upload one design when dropping onto an existing design.',
+);
+
+export const EXISTING_DESIGN_DROP_INVALID_FILENAME_MESSAGE = __(
+ 'You must upload a file with the same file name when dropping onto an existing design.',
+);
+
+const MAX_SKIPPED_FILES_LISTINGS = 5;
+
+const oneDesignSkippedMessage = filename =>
+ `${DESIGN_UPLOAD_SKIPPED_MESSAGE} ${sprintf(s__('DesignManagement|%{filename} did not change.'), {
+ filename,
+ })}`;
+
+/**
+ * Return warning message indicating that some (but not all) uploaded
+ * files were skipped.
+ * @param {Array<{ filename }>} skippedFiles
+ */
+const someDesignsSkippedMessage = skippedFiles => {
+ const designsSkippedMessage = `${DESIGN_UPLOAD_SKIPPED_MESSAGE} ${s__(
+ 'Some of the designs you tried uploading did not change:',
+ )}`;
+
+ const moreText = sprintf(s__(`DesignManagement|and %{moreCount} more.`), {
+ moreCount: skippedFiles.length - MAX_SKIPPED_FILES_LISTINGS,
+ });
+
+ return `${designsSkippedMessage} ${skippedFiles
+ .slice(0, MAX_SKIPPED_FILES_LISTINGS)
+ .map(({ filename }) => filename)
+ .join(', ')}${skippedFiles.length > MAX_SKIPPED_FILES_LISTINGS ? `, ${moreText}` : '.'}`;
+};
+
+export const designDeletionError = ({ singular = true } = {}) => {
+ const design = singular ? __('a design') : __('designs');
+ return sprintf(s__('Could not delete %{design}. Please try again.'), {
+ design,
+ });
+};
+
+/**
+ * Return warning message, if applicable, that one, some or all uploaded
+ * files were skipped.
+ * @param {Array<{ filename }>} uploadedDesigns
+ * @param {Array<{ filename }>} skippedFiles
+ */
+export const designUploadSkippedWarning = (uploadedDesigns, skippedFiles) => {
+ if (skippedFiles.length === 0) {
+ return null;
+ }
+
+ if (skippedFiles.length === uploadedDesigns.length) {
+ const { filename } = skippedFiles[0];
+
+ return n__(oneDesignSkippedMessage(filename), ALL_DESIGNS_SKIPPED_MESSAGE, skippedFiles.length);
+ }
+
+ return someDesignsSkippedMessage(skippedFiles);
+};
diff --git a/app/assets/javascripts/design_management_new/utils/tracking.js b/app/assets/javascripts/design_management_new/utils/tracking.js
new file mode 100644
index 00000000000..b3ecc1453a6
--- /dev/null
+++ b/app/assets/javascripts/design_management_new/utils/tracking.js
@@ -0,0 +1,27 @@
+import Tracking from '~/tracking';
+
+// Tracking Constants
+const DESIGN_TRACKING_CONTEXT_SCHEMA = 'iglu:com.gitlab/design_management_context/jsonschema/1-0-0';
+const DESIGN_TRACKING_PAGE_NAME = 'projects:issues:design';
+const DESIGN_TRACKING_EVENT_NAME = 'view_design';
+
+// eslint-disable-next-line import/prefer-default-export
+export function trackDesignDetailView(
+ referer = '',
+ owner = '',
+ designVersion = 1,
+ latestVersion = false,
+) {
+ Tracking.event(DESIGN_TRACKING_PAGE_NAME, DESIGN_TRACKING_EVENT_NAME, {
+ label: DESIGN_TRACKING_EVENT_NAME,
+ context: {
+ schema: DESIGN_TRACKING_CONTEXT_SCHEMA,
+ data: {
+ 'design-version-number': designVersion,
+ 'design-is-current-version': latestVersion,
+ 'internal-object-referrer': referer,
+ 'design-collection-owner': owner,
+ },
+ },
+ });
+}
diff --git a/app/assets/javascripts/diffs/components/app.vue b/app/assets/javascripts/diffs/components/app.vue
index 941365d9d1d..1e524882d5f 100644
--- a/app/assets/javascripts/diffs/components/app.vue
+++ b/app/assets/javascripts/diffs/components/app.vue
@@ -1,6 +1,6 @@
<script>
import { mapState, mapGetters, mapActions } from 'vuex';
-import { GlLoadingIcon } from '@gitlab/ui';
+import { GlLoadingIcon, GlButtonGroup, GlButton } from '@gitlab/ui';
import Mousetrap from 'mousetrap';
import { __ } from '~/locale';
import createFlash from '~/flash';
@@ -36,6 +36,8 @@ export default {
TreeList,
GlLoadingIcon,
PanelResizer,
+ GlButtonGroup,
+ GlButton,
},
mixins: [glFeatureFlagsMixin()],
props: {
@@ -94,6 +96,11 @@ export default {
required: false,
default: false,
},
+ viewDiffsFileByFile: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
},
data() {
const treeWidth =
@@ -120,9 +127,18 @@ export default {
emailPatchPath: state => state.diffs.emailPatchPath,
retrievingBatches: state => state.diffs.retrievingBatches,
}),
- ...mapState('diffs', ['showTreeList', 'isLoading', 'startVersion']),
+ ...mapState('diffs', ['showTreeList', 'isLoading', 'startVersion', 'currentDiffFileId']),
...mapGetters('diffs', ['isParallelView', 'currentDiffIndex']),
...mapGetters(['isNotesFetched', 'getNoteableData']),
+ diffs() {
+ if (!this.viewDiffsFileByFile) {
+ return this.diffFiles;
+ }
+
+ return this.diffFiles.filter((file, i) => {
+ return file.file_hash === this.currentDiffFileId || (i === 0 && !this.currentDiffFileId);
+ });
+ },
canCurrentUserFork() {
return this.currentUser.can_fork === true && this.currentUser.can_create_merge_request;
},
@@ -183,16 +199,22 @@ export default {
dismissEndpoint: this.dismissEndpoint,
showSuggestPopover: this.showSuggestPopover,
useSingleDiffStyle: this.glFeatures.singleMrDiffView,
+ viewDiffsFileByFile: this.viewDiffsFileByFile,
});
if (this.shouldShow) {
this.fetchData();
}
- const id = window && window.location && window.location.hash;
+ const id = window?.location?.hash;
- if (id) {
- this.setHighlightedRow(id.slice(1));
+ if (id && id.indexOf('#note') !== 0) {
+ this.setHighlightedRow(
+ id
+ .split('diff-content')
+ .pop()
+ .slice(1),
+ );
}
},
created() {
@@ -236,6 +258,7 @@ export default {
'cacheTreeListWidth',
'scrollToFile',
'toggleShowTreeList',
+ 'navigateToDiffFileIndex',
]),
refetchDiffData() {
this.fetchData(false);
@@ -398,7 +421,7 @@ export default {
class="files d-flex"
>
<div
- v-show="showTreeList"
+ v-if="showTreeList"
:style="{ width: `${treeWidth}px` }"
class="diff-tree-list js-diff-tree-list mr-3"
>
@@ -422,12 +445,31 @@ export default {
<div v-if="isBatchLoading" class="loading"><gl-loading-icon size="lg" /></div>
<template v-else-if="renderDiffFiles">
<diff-file
- v-for="file in diffFiles"
+ v-for="file in diffs"
:key="file.newPath"
:file="file"
:help-page-path="helpPagePath"
:can-current-user-fork="canCurrentUserFork"
+ :view-diffs-file-by-file="viewDiffsFileByFile"
/>
+ <div v-if="viewDiffsFileByFile" class="d-flex gl-justify-content-center">
+ <gl-button-group>
+ <gl-button
+ :disabled="currentDiffIndex === 0"
+ data-testid="singleFilePrevious"
+ @click="navigateToDiffFileIndex(currentDiffIndex - 1)"
+ >
+ {{ __('Prev') }}
+ </gl-button>
+ <gl-button
+ :disabled="currentDiffIndex === diffFiles.length - 1"
+ data-testid="singleFileNext"
+ @click="navigateToDiffFileIndex(currentDiffIndex + 1)"
+ >
+ {{ __('Next') }}
+ </gl-button>
+ </gl-button-group>
+ </div>
</template>
<no-changes v-else :changes-empty-state-illustration="changesEmptyStateIllustration" />
</div>
diff --git a/app/assets/javascripts/diffs/components/diff_file.vue b/app/assets/javascripts/diffs/components/diff_file.vue
index 54852b113ae..00d36c0b978 100644
--- a/app/assets/javascripts/diffs/components/diff_file.vue
+++ b/app/assets/javascripts/diffs/components/diff_file.vue
@@ -30,6 +30,10 @@ export default {
required: false,
default: '',
},
+ viewDiffsFileByFile: {
+ type: Boolean,
+ required: true,
+ },
},
data() {
return {
@@ -154,6 +158,7 @@ export default {
:collapsible="true"
:expanded="!isCollapsed"
:add-merge-request-buttons="true"
+ :view-diffs-file-by-file="viewDiffsFileByFile"
class="js-file-title file-title"
@toggleFile="handleToggle"
@showForkMessage="showForkMessage"
diff --git a/app/assets/javascripts/diffs/components/diff_file_header.vue b/app/assets/javascripts/diffs/components/diff_file_header.vue
index 61bbf13aa53..5727fbaaf68 100644
--- a/app/assets/javascripts/diffs/components/diff_file_header.vue
+++ b/app/assets/javascripts/diffs/components/diff_file_header.vue
@@ -2,7 +2,6 @@
import { escape } from 'lodash';
import { mapActions, mapGetters } from 'vuex';
import { GlDeprecatedButton, GlTooltipDirective, GlLoadingIcon } from '@gitlab/ui';
-import { polyfillSticky } from '~/lib/utils/sticky';
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
import Icon from '~/vue_shared/components/icon.vue';
import FileIcon from '~/vue_shared/components/file_icon.vue';
@@ -55,6 +54,11 @@ export default {
type: Boolean,
required: true,
},
+ viewDiffsFileByFile: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
},
computed: {
...mapGetters('diffs', ['diffHasExpandedDiscussions', 'diffHasDiscussions']),
@@ -124,9 +128,6 @@ export default {
return s__('MRDiff|Show full file');
},
},
- mounted() {
- polyfillSticky(this.$refs.header);
- },
methods: {
...mapActions('diffs', [
'toggleFileDiscussions',
@@ -167,22 +168,17 @@ export default {
:name="collapseIcon"
:size="16"
aria-hidden="true"
- class="diff-toggle-caret append-right-5"
+ class="diff-toggle-caret gl-mr-2"
@click.stop="handleToggleFile"
/>
<a
- v-once
ref="titleWrapper"
- class="append-right-4"
+ :v-once="!viewDiffsFileByFile"
+ class="gl-mr-2"
:href="titleLink"
@click="handleFileNameClick"
>
- <file-icon
- :file-name="filePath"
- :size="18"
- aria-hidden="true"
- css-classes="append-right-5"
- />
+ <file-icon :file-name="filePath" :size="18" aria-hidden="true" css-classes="gl-mr-2" />
<span v-if="isFileRenamed">
<strong
v-gl-tooltip
@@ -218,7 +214,7 @@ export default {
{{ diffFile.a_mode }} → {{ diffFile.b_mode }}
</small>
- <span v-if="isUsingLfs" class="label label-lfs append-right-5"> {{ __('LFS') }} </span>
+ <span v-if="isUsingLfs" class="label label-lfs gl-mr-2"> {{ __('LFS') }} </span>
</div>
<div
diff --git a/app/assets/javascripts/diffs/components/diff_file_row.vue b/app/assets/javascripts/diffs/components/diff_file_row.vue
index c8ba8d6040e..43b669625f4 100644
--- a/app/assets/javascripts/diffs/components/diff_file_row.vue
+++ b/app/assets/javascripts/diffs/components/diff_file_row.vue
@@ -23,6 +23,11 @@ export default {
type: Boolean,
required: true,
},
+ currentDiffFileId: {
+ type: String,
+ required: false,
+ default: null,
+ },
},
computed: {
showFileRowStats() {
@@ -33,7 +38,13 @@ export default {
</script>
<template>
- <file-row :file="file" v-bind="$attrs" v-on="$listeners">
+ <file-row
+ :file="file"
+ v-bind="$attrs"
+ :class="{ 'is-active': currentDiffFileId === file.fileHash }"
+ class="diff-file-row"
+ v-on="$listeners"
+ >
<file-row-stats v-if="showFileRowStats" :file="file" class="mr-1" />
<changed-file-icon :file="file" :size="16" :show-tooltip="true" />
</file-row>
diff --git a/app/assets/javascripts/diffs/components/diff_line_note_form.vue b/app/assets/javascripts/diffs/components/diff_line_note_form.vue
index 74305ee69bc..d2f49bd0020 100644
--- a/app/assets/javascripts/diffs/components/diff_line_note_form.vue
+++ b/app/assets/javascripts/diffs/components/diff_line_note_form.vue
@@ -8,7 +8,10 @@ import MultilineCommentForm from '../../notes/components/multiline_comment_form.
import autosave from '../../notes/mixins/autosave';
import userAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue';
import { DIFF_NOTE_TYPE } from '../constants';
-import { commentLineOptions } from '../../notes/components/multiline_comment_utils';
+import {
+ commentLineOptions,
+ formatLineRange,
+} from '../../notes/components/multiline_comment_utils';
export default {
components: {
@@ -44,8 +47,10 @@ export default {
data() {
return {
commentLineStart: {
- lineCode: this.line.line_code,
+ line_code: this.line.line_code,
type: this.line.type,
+ old_line: this.line.old_line,
+ new_line: this.line.new_line,
},
};
},
@@ -74,19 +79,26 @@ export default {
diffViewType: this.diffViewType,
diffFile: this.diffFile,
linePosition: this.linePosition,
- lineRange: {
- start_line_code: this.commentLineStart.lineCode,
- start_line_type: this.commentLineStart.type,
- end_line_code: this.line.line_code,
- end_line_type: this.line.type,
- },
+ lineRange: formatLineRange(this.commentLineStart, this.line),
};
},
diffFile() {
return this.getDiffFileByHash(this.diffFileHash);
},
commentLineOptions() {
- return commentLineOptions(this.diffFile.highlighted_diff_lines, this.line.line_code);
+ const combineSides = (acc, { left, right }) => {
+ // ignore null values match lines
+ if (left && left.type !== 'match') acc.push(left);
+ // if the line_codes are identically, return to avoid duplicates
+ if (left?.line_code === right?.line_code) return acc;
+ if (right && right.type !== 'match') acc.push(right);
+ return acc;
+ };
+ const side = this.line.type === 'new' ? 'right' : 'left';
+ const lines = this.diffFile.highlighted_diff_lines.length
+ ? this.diffFile.highlighted_diff_lines
+ : this.diffFile.parallel_diff_lines.reduce(combineSides, []);
+ return commentLineOptions(lines, this.line, this.line.line_code, side);
},
},
mounted() {
@@ -136,10 +148,7 @@ export default {
<template>
<div class="content discussion-form discussion-form-container discussion-notes">
- <div
- v-if="glFeatures.multilineComments"
- class="gl-mb-3 gl-text-gray-700 gl-border-gray-200 gl-border-b-solid gl-border-b-1 gl-pb-3"
- >
+ <div v-if="glFeatures.multilineComments" class="gl-mb-3 gl-text-gray-700 gl-pb-3">
<multiline-comment-form
v-model="commentLineStart"
:line="line"
diff --git a/app/assets/javascripts/diffs/components/diff_table_cell.vue b/app/assets/javascripts/diffs/components/diff_table_cell.vue
index 514d26862a3..198113e330a 100644
--- a/app/assets/javascripts/diffs/components/diff_table_cell.vue
+++ b/app/assets/javascripts/diffs/components/diff_table_cell.vue
@@ -4,15 +4,13 @@ import { GlIcon } from '@gitlab/ui';
import { getParameterByName, parseBoolean } from '~/lib/utils/common_utils';
import DiffGutterAvatars from './diff_gutter_avatars.vue';
import {
- MATCH_LINE_TYPE,
CONTEXT_LINE_TYPE,
LINE_POSITION_RIGHT,
EMPTY_CELL_TYPE,
- OLD_LINE_TYPE,
OLD_NO_NEW_LINE_TYPE,
+ OLD_LINE_TYPE,
NEW_NO_NEW_LINE_TYPE,
LINE_HOVER_CLASS_NAME,
- LINE_UNFOLD_CLASS_NAME,
} from '../constants';
export default {
@@ -29,10 +27,6 @@ export default {
type: String,
required: true,
},
- contextLinesPath: {
- type: String,
- required: true,
- },
isHighlighted: {
type: Boolean,
required: true,
@@ -52,11 +46,6 @@ export default {
required: false,
default: '',
},
- isContentLine: {
- type: Boolean,
- required: false,
- default: false,
- },
isBottom: {
type: Boolean,
required: false,
@@ -68,6 +57,11 @@ export default {
default: false,
},
},
+ data() {
+ return {
+ isCommentButtonRendered: false,
+ };
+ },
computed: {
...mapGetters(['isLoggedIn']),
lineCode() {
@@ -81,13 +75,7 @@ export default {
return `#${this.line.line_code || ''}`;
},
shouldShowCommentButton() {
- return (
- this.isHover &&
- !this.isMatchLine &&
- !this.isContextLine &&
- !this.isMetaLine &&
- !this.hasDiscussions
- );
+ return this.isHover && !this.isContextLine && !this.isMetaLine && !this.hasDiscussions;
},
hasDiscussions() {
return this.line.discussions && this.line.discussions.length > 0;
@@ -99,6 +87,10 @@ export default {
return this.showCommentButton && this.hasDiscussions;
},
shouldRenderCommentButton() {
+ if (!this.isCommentButtonRendered) {
+ return false;
+ }
+
if (this.isLoggedIn && this.showCommentButton) {
const isDiffHead = parseBoolean(getParameterByName('diff_head'));
return !isDiffHead || gon.features?.mergeRefHeadComments;
@@ -106,9 +98,6 @@ export default {
return false;
},
- isMatchLine() {
- return this.line.type === MATCH_LINE_TYPE;
- },
isContextLine() {
return this.line.type === CONTEXT_LINE_TYPE;
},
@@ -126,13 +115,8 @@ export default {
type,
{
hll: this.isHighlighted,
- [LINE_UNFOLD_CLASS_NAME]: this.isMatchLine,
[LINE_HOVER_CLASS_NAME]:
- this.isLoggedIn &&
- this.isHover &&
- !this.isMatchLine &&
- !this.isContextLine &&
- !this.isMetaLine,
+ this.isLoggedIn && this.isHover && !this.isContextLine && !this.isMetaLine,
},
];
},
@@ -140,6 +124,17 @@ export default {
return this.lineType === OLD_LINE_TYPE ? this.line.old_line : this.line.new_line;
},
},
+ mounted() {
+ this.unwatchShouldShowCommentButton = this.$watch('shouldShowCommentButton', newVal => {
+ if (newVal) {
+ this.isCommentButtonRendered = true;
+ this.unwatchShouldShowCommentButton();
+ }
+ });
+ },
+ beforeDestroy() {
+ this.unwatchShouldShowCommentButton();
+ },
methods: {
...mapActions('diffs', ['showCommentForm', 'setHighlightedRow', 'toggleLineDiscussions']),
handleCommentButton() {
@@ -151,34 +146,32 @@ export default {
<template>
<td ref="td" :class="classNameMap">
- <div>
- <button
- v-if="shouldRenderCommentButton"
- v-show="shouldShowCommentButton"
- ref="addDiffNoteButton"
- type="button"
- class="add-diff-note js-add-diff-note-button qa-diff-comment"
- title="Add a comment to this line"
- @click="handleCommentButton"
- >
- <gl-icon :size="12" name="comment" />
- </button>
- <a
- v-if="lineNumber"
- ref="lineNumberRef"
- :data-linenumber="lineNumber"
- :href="lineHref"
- @click="setHighlightedRow(lineCode)"
- >
- </a>
- <diff-gutter-avatars
- v-if="shouldShowAvatarsOnGutter"
- :discussions="line.discussions"
- :discussions-expanded="line.discussionsExpanded"
- @toggleLineDiscussions="
- toggleLineDiscussions({ lineCode, fileHash, expanded: !line.discussionsExpanded })
- "
- />
- </div>
+ <button
+ v-if="shouldRenderCommentButton"
+ v-show="shouldShowCommentButton"
+ ref="addDiffNoteButton"
+ type="button"
+ class="add-diff-note js-add-diff-note-button qa-diff-comment"
+ title="Add a comment to this line"
+ @click="handleCommentButton"
+ >
+ <gl-icon :size="12" name="comment" />
+ </button>
+ <a
+ v-if="lineNumber"
+ ref="lineNumberRef"
+ :data-linenumber="lineNumber"
+ :href="lineHref"
+ @click="setHighlightedRow(lineCode)"
+ >
+ </a>
+ <diff-gutter-avatars
+ v-if="shouldShowAvatarsOnGutter"
+ :discussions="line.discussions"
+ :discussions-expanded="line.discussionsExpanded"
+ @toggleLineDiscussions="
+ toggleLineDiscussions({ lineCode, fileHash, expanded: !line.discussionsExpanded })
+ "
+ />
</td>
</template>
diff --git a/app/assets/javascripts/diffs/components/inline_diff_table_row.vue b/app/assets/javascripts/diffs/components/inline_diff_table_row.vue
index bd99fcb71b8..168e8c6c14e 100644
--- a/app/assets/javascripts/diffs/components/inline_diff_table_row.vue
+++ b/app/assets/javascripts/diffs/components/inline_diff_table_row.vue
@@ -28,10 +28,6 @@ export default {
type: String,
required: true,
},
- contextLinesPath: {
- type: String,
- required: true,
- },
line: {
type: Object,
required: true,
@@ -41,6 +37,11 @@ export default {
required: false,
default: false,
},
+ isCommented: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
},
data() {
return {
@@ -51,7 +52,10 @@ export default {
...mapGetters('diffs', ['fileLineCoverage']),
...mapState({
isHighlighted(state) {
- return this.line.line_code !== null && this.line.line_code === state.diffs.highlightedRow;
+ if (this.isCommented) return true;
+
+ const lineCode = this.line.line_code;
+ return lineCode ? lineCode === state.diffs.highlightedRow : false;
},
}),
isContextLine() {
@@ -106,7 +110,6 @@ export default {
>
<diff-table-cell
:file-hash="fileHash"
- :context-lines-path="contextLinesPath"
:line="line"
:line-type="oldLineType"
:is-bottom="isBottom"
@@ -117,7 +120,6 @@ export default {
/>
<diff-table-cell
:file-hash="fileHash"
- :context-lines-path="contextLinesPath"
:line="line"
:line-type="newLineType"
:is-bottom="isBottom"
diff --git a/app/assets/javascripts/diffs/components/inline_diff_view.vue b/app/assets/javascripts/diffs/components/inline_diff_view.vue
index ad72016f03b..e82d06ee385 100644
--- a/app/assets/javascripts/diffs/components/inline_diff_view.vue
+++ b/app/assets/javascripts/diffs/components/inline_diff_view.vue
@@ -1,10 +1,11 @@
<script>
-import { mapGetters } from 'vuex';
+import { mapGetters, mapState } from 'vuex';
import draftCommentsMixin from '~/diffs/mixins/draft_comments';
import InlineDraftCommentRow from '~/batch_comments/components/inline_draft_comment_row.vue';
import inlineDiffTableRow from './inline_diff_table_row.vue';
import inlineDiffCommentRow from './inline_diff_comment_row.vue';
import inlineDiffExpansionRow from './inline_diff_expansion_row.vue';
+import { getCommentedLines } from '~/notes/components/multiline_comment_utils';
export default {
components: {
@@ -31,9 +32,19 @@ export default {
},
computed: {
...mapGetters('diffs', ['commitId']),
+ ...mapState({
+ selectedCommentPosition: ({ notes }) => notes.selectedCommentPosition,
+ selectedCommentPositionHover: ({ notes }) => notes.selectedCommentPositionHover,
+ }),
diffLinesLength() {
return this.diffLines.length;
},
+ commentedLines() {
+ return getCommentedLines(
+ this.selectedCommentPosition || this.selectedCommentPositionHover,
+ this.diffLines,
+ );
+ },
},
userColorScheme: window.gon.user_color_scheme,
};
@@ -65,9 +76,9 @@ export default {
:key="`${line.line_code || index}`"
:file-hash="diffFile.file_hash"
:file-path="diffFile.file_path"
- :context-lines-path="diffFile.context_lines_path"
:line="line"
:is-bottom="index + 1 === diffLinesLength"
+ :is-commented="index >= commentedLines.startLine && index <= commentedLines.endLine"
/>
<inline-diff-comment-row
:key="`icr-${line.line_code || index}`"
diff --git a/app/assets/javascripts/diffs/components/no_changes.vue b/app/assets/javascripts/diffs/components/no_changes.vue
index 94c2695a945..93afa978862 100644
--- a/app/assets/javascripts/diffs/components/no_changes.vue
+++ b/app/assets/javascripts/diffs/components/no_changes.vue
@@ -1,12 +1,12 @@
<script>
import { mapGetters } from 'vuex';
import { escape } from 'lodash';
-import { GlDeprecatedButton } from '@gitlab/ui';
+import { GlButton } from '@gitlab/ui';
import { __, sprintf } from '~/locale';
export default {
components: {
- GlDeprecatedButton,
+ GlButton,
},
props: {
changesEmptyStateIllustration: {
@@ -43,9 +43,9 @@ export default {
<div class="text-content text-center">
<span v-html="emptyStateText"></span>
<div class="text-center">
- <gl-deprecated-button :href="getNoteableData.new_blob_path" variant="success">{{
+ <gl-button :href="getNoteableData.new_blob_path" variant="success" category="primary">{{
__('Create commit')
- }}</gl-deprecated-button>
+ }}</gl-button>
</div>
</div>
</div>
diff --git a/app/assets/javascripts/diffs/components/parallel_diff_table_row.vue b/app/assets/javascripts/diffs/components/parallel_diff_table_row.vue
index 83d803f42b1..ccb32a2a745 100644
--- a/app/assets/javascripts/diffs/components/parallel_diff_table_row.vue
+++ b/app/assets/javascripts/diffs/components/parallel_diff_table_row.vue
@@ -31,10 +31,6 @@ export default {
type: String,
required: true,
},
- contextLinesPath: {
- type: String,
- required: true,
- },
line: {
type: Object,
required: true,
@@ -44,6 +40,11 @@ export default {
required: false,
default: false,
},
+ isCommented: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
},
data() {
return {
@@ -55,6 +56,8 @@ export default {
...mapGetters('diffs', ['fileLineCoverage']),
...mapState({
isHighlighted(state) {
+ if (this.isCommented) return true;
+
const lineCode =
(this.line.left && this.line.left.line_code) ||
(this.line.right && this.line.right.line_code);
@@ -144,7 +147,6 @@ export default {
<template v-if="line.left && !isMatchLineLeft">
<diff-table-cell
:file-hash="fileHash"
- :context-lines-path="contextLinesPath"
:line="line.left"
:line-type="oldLineType"
:is-bottom="isBottom"
@@ -172,7 +174,6 @@ export default {
<template v-if="line.right && !isMatchLineRight">
<diff-table-cell
:file-hash="fileHash"
- :context-lines-path="contextLinesPath"
:line="line.right"
:line-type="newLineType"
:is-bottom="isBottom"
diff --git a/app/assets/javascripts/diffs/components/parallel_diff_view.vue b/app/assets/javascripts/diffs/components/parallel_diff_view.vue
index b5fcc50ce26..46a691ad22d 100644
--- a/app/assets/javascripts/diffs/components/parallel_diff_view.vue
+++ b/app/assets/javascripts/diffs/components/parallel_diff_view.vue
@@ -1,10 +1,11 @@
<script>
-import { mapGetters } from 'vuex';
+import { mapGetters, mapState } from 'vuex';
import draftCommentsMixin from '~/diffs/mixins/draft_comments';
import ParallelDraftCommentRow from '~/batch_comments/components/parallel_draft_comment_row.vue';
import parallelDiffTableRow from './parallel_diff_table_row.vue';
import parallelDiffCommentRow from './parallel_diff_comment_row.vue';
import parallelDiffExpansionRow from './parallel_diff_expansion_row.vue';
+import { getCommentedLines } from '~/notes/components/multiline_comment_utils';
export default {
components: {
@@ -31,9 +32,19 @@ export default {
},
computed: {
...mapGetters('diffs', ['commitId']),
+ ...mapState({
+ selectedCommentPosition: ({ notes }) => notes.selectedCommentPosition,
+ selectedCommentPositionHover: ({ notes }) => notes.selectedCommentPositionHover,
+ }),
diffLinesLength() {
return this.diffLines.length;
},
+ commentedLines() {
+ return getCommentedLines(
+ this.selectedCommentPosition || this.selectedCommentPositionHover,
+ this.diffLines,
+ );
+ },
},
userColorScheme: window.gon.user_color_scheme,
};
@@ -67,9 +78,9 @@ export default {
:key="line.line_code"
:file-hash="diffFile.file_hash"
:file-path="diffFile.file_path"
- :context-lines-path="diffFile.context_lines_path"
:line="line"
:is-bottom="index + 1 === diffLinesLength"
+ :is-commented="index >= commentedLines.startLine && index <= commentedLines.endLine"
/>
<parallel-diff-comment-row
:key="`dcr-${line.line_code || index}`"
diff --git a/app/assets/javascripts/diffs/components/tree_list.vue b/app/assets/javascripts/diffs/components/tree_list.vue
index 52611f3c82a..38fbd8e61d4 100644
--- a/app/assets/javascripts/diffs/components/tree_list.vue
+++ b/app/assets/javascripts/diffs/components/tree_list.vue
@@ -26,7 +26,7 @@ export default {
};
},
computed: {
- ...mapState('diffs', ['tree', 'renderTreeList']),
+ ...mapState('diffs', ['tree', 'renderTreeList', 'currentDiffFileId']),
...mapGetters('diffs', ['allBlobs']),
filteredTreeList() {
const search = this.search.toLowerCase().trim();
@@ -96,6 +96,7 @@ export default {
:level="0"
:hide-file-stats="hideFileStats"
:file-row-component="$options.DiffFileRow"
+ :current-diff-file-id="currentDiffFileId"
@toggleTreeOpen="toggleTreeOpen"
@clickFile="scrollToFile"
/>
diff --git a/app/assets/javascripts/diffs/constants.js b/app/assets/javascripts/diffs/constants.js
index 9269dacd582..e3dd882f3dc 100644
--- a/app/assets/javascripts/diffs/constants.js
+++ b/app/assets/javascripts/diffs/constants.js
@@ -1,3 +1,7 @@
+// The backend actually uses "hide_whitespace" while the frontend
+// uses "show whitspace" so these values are opposite what you might expect
+export const NO_SHOW_WHITESPACE = '1';
+export const SHOW_WHITESPACE = '0';
export const INLINE_DIFF_VIEW_TYPE = 'inline';
export const PARALLEL_DIFF_VIEW_TYPE = 'parallel';
export const MATCH_LINE_TYPE = 'match';
@@ -20,6 +24,7 @@ export const LINE_SIDE_LEFT = 'left-side';
export const LINE_SIDE_RIGHT = 'right-side';
export const DIFF_VIEW_COOKIE_NAME = 'diff_view';
+export const DIFF_WHITESPACE_COOKIE_NAME = 'diff_whitespace';
export const LINE_HOVER_CLASS_NAME = 'is-over';
export const LINE_UNFOLD_CLASS_NAME = 'unfold js-unfold';
export const CONTEXT_LINE_CLASS_NAME = 'diff-expanded';
@@ -35,7 +40,6 @@ export const MR_TREE_SHOW_KEY = 'mr_tree_show';
export const TREE_TYPE = 'tree';
export const TREE_LIST_STORAGE_KEY = 'mr_diff_tree_list';
-export const WHITESPACE_STORAGE_KEY = 'mr_show_whitespace';
export const TREE_LIST_WIDTH_STORAGE_KEY = 'mr_tree_list_width';
export const INITIAL_TREE_WIDTH = 320;
diff --git a/app/assets/javascripts/diffs/index.js b/app/assets/javascripts/diffs/index.js
index ce48e36bfd7..76ff67ab861 100644
--- a/app/assets/javascripts/diffs/index.js
+++ b/app/assets/javascripts/diffs/index.js
@@ -1,11 +1,11 @@
import Vue from 'vue';
import { mapActions, mapState, mapGetters } from 'vuex';
import { parseBoolean } from '~/lib/utils/common_utils';
-import { getParameterValues } from '~/lib/utils/url_utility';
import FindFile from '~/vue_shared/components/file_finder/index.vue';
import eventHub from '../notes/event_hub';
import diffsApp from './components/app.vue';
-import { TREE_LIST_STORAGE_KEY } from './constants';
+import { TREE_LIST_STORAGE_KEY, DIFF_WHITESPACE_COOKIE_NAME } from './constants';
+import Cookies from 'js-cookie';
export default function initDiffsApp(store) {
const fileFinderEl = document.getElementById('js-diff-file-finder');
@@ -78,6 +78,7 @@ export default function initDiffsApp(store) {
dismissEndpoint: dataset.dismissEndpoint,
showSuggestPopover: parseBoolean(dataset.showSuggestPopover),
showWhitespaceDefault: parseBoolean(dataset.showWhitespaceDefault),
+ viewDiffsFileByFile: parseBoolean(dataset.fileByFileDefault),
};
},
computed: {
@@ -86,15 +87,16 @@ export default function initDiffsApp(store) {
}),
},
created() {
- let hideWhitespace = getParameterValues('w')[0];
const treeListStored = localStorage.getItem(TREE_LIST_STORAGE_KEY);
const renderTreeList = treeListStored !== null ? parseBoolean(treeListStored) : true;
this.setRenderTreeList(renderTreeList);
- if (!hideWhitespace) {
- hideWhitespace = this.showWhitespaceDefault ? '0' : '1';
+
+ // Set whitespace default as per user preferences unless cookie is already set
+ if (!Cookies.get(DIFF_WHITESPACE_COOKIE_NAME)) {
+ const hideWhitespace = this.showWhitespaceDefault ? '0' : '1';
+ this.setShowWhitespace({ showWhitespace: hideWhitespace !== '1' });
}
- this.setShowWhitespace({ showWhitespace: hideWhitespace !== '1' });
},
methods: {
...mapActions('diffs', ['setRenderTreeList', 'setShowWhitespace']),
@@ -114,7 +116,7 @@ export default function initDiffsApp(store) {
isFluidLayout: this.isFluidLayout,
dismissEndpoint: this.dismissEndpoint,
showSuggestPopover: this.showSuggestPopover,
- showWhitespaceDefault: this.showWhitespaceDefault,
+ viewDiffsFileByFile: this.viewDiffsFileByFile,
},
});
},
diff --git a/app/assets/javascripts/diffs/store/actions.js b/app/assets/javascripts/diffs/store/actions.js
index a8d348e1836..d469ed8ee82 100644
--- a/app/assets/javascripts/diffs/store/actions.js
+++ b/app/assets/javascripts/diffs/store/actions.js
@@ -25,7 +25,6 @@ import {
DIFF_VIEW_COOKIE_NAME,
MR_TREE_SHOW_KEY,
TREE_LIST_STORAGE_KEY,
- WHITESPACE_STORAGE_KEY,
TREE_LIST_WIDTH_STORAGE_KEY,
OLD_LINE_KEY,
NEW_LINE_KEY,
@@ -38,6 +37,9 @@ import {
INLINE_DIFF_LINES_KEY,
PARALLEL_DIFF_LINES_KEY,
DIFFS_PER_PAGE,
+ DIFF_WHITESPACE_COOKIE_NAME,
+ SHOW_WHITESPACE,
+ NO_SHOW_WHITESPACE,
} from '../constants';
import { diffViewerModes } from '~/ide/constants';
@@ -103,7 +105,9 @@ export const fetchDiffFiles = ({ state, commit }) => {
.catch(() => worker.terminate());
};
-export const fetchDiffFilesBatch = ({ commit, state }) => {
+export const fetchDiffFilesBatch = ({ commit, state, dispatch }) => {
+ const id = window?.location?.hash;
+ const isNoteLink = id.indexOf('#note') === 0;
const urlParams = {
per_page: DIFFS_PER_PAGE,
w: state.showWhitespace ? '0' : '1',
@@ -123,16 +127,36 @@ export const fetchDiffFilesBatch = ({ commit, state }) => {
commit(types.SET_DIFF_DATA_BATCH, { diff_files });
commit(types.SET_BATCH_LOADING, false);
+ if (!isNoteLink && !state.currentDiffFileId) {
+ commit(types.UPDATE_CURRENT_DIFF_FILE_ID, diff_files[0].file_hash);
+ }
+
+ if (isNoteLink) {
+ dispatch('setCurrentDiffFileIdFromNote', id.split('_').pop());
+ }
+
if (!pagination.next_page) {
commit(types.SET_RETRIEVING_BATCHES, false);
+
+ // We need to check that the currentDiffFileId points to a file that exists
+ if (
+ state.currentDiffFileId &&
+ !state.diffFiles.some(f => f.file_hash === state.currentDiffFileId) &&
+ !isNoteLink
+ ) {
+ commit(types.UPDATE_CURRENT_DIFF_FILE_ID, state.diffFiles[0].file_hash);
+ }
+
if (gon.features?.codeNavigation) {
// eslint-disable-next-line promise/catch-or-return,promise/no-nesting
import('~/code_navigation').then(m =>
m.default({
- blobs: state.diffFiles.map(f => ({
- path: f.new_path,
- codeNavigationPath: f.code_navigation_path,
- })),
+ blobs: state.diffFiles
+ .filter(f => f.code_navigation_path)
+ .map(f => ({
+ path: f.new_path,
+ codeNavigationPath: f.code_navigation_path,
+ })),
definitionPathPrefix: state.definitionPathPrefix,
}),
);
@@ -211,9 +235,11 @@ export const setHighlightedRow = ({ commit }, lineCode) => {
// This is adding line discussions to the actual lines in the diff tree
// once for parallel and once for inline mode
export const assignDiscussionsToDiff = (
- { commit, state, rootState },
+ { commit, state, rootState, dispatch },
discussions = rootState.notes.discussions,
) => {
+ const id = window?.location?.hash;
+ const isNoteLink = id.indexOf('#note') === 0;
const diffPositionByLineCode = getDiffPositionByLineCode(
state.diffFiles,
state.useSingleDiffStyle,
@@ -230,6 +256,10 @@ export const assignDiscussionsToDiff = (
});
});
+ if (isNoteLink) {
+ dispatch('setCurrentDiffFileIdFromNote', id.split('_').pop());
+ }
+
Vue.nextTick(() => {
eventHub.$emit('scrollToDiscussion');
});
@@ -448,6 +478,8 @@ export const toggleTreeOpen = ({ commit }, path) => {
};
export const scrollToFile = ({ state, commit }, path) => {
+ if (!state.treeEntries[path]) return;
+
const { fileHash } = state.treeEntries[path];
document.location.hash = fileHash;
@@ -484,11 +516,12 @@ export const setRenderTreeList = ({ commit }, renderTreeList) => {
export const setShowWhitespace = ({ commit }, { showWhitespace, pushState = false }) => {
commit(types.SET_SHOW_WHITESPACE, showWhitespace);
+ const w = showWhitespace ? SHOW_WHITESPACE : NO_SHOW_WHITESPACE;
- localStorage.setItem(WHITESPACE_STORAGE_KEY, showWhitespace);
+ Cookies.set(DIFF_WHITESPACE_COOKIE_NAME, w);
if (pushState) {
- historyPushState(mergeUrlParams({ w: showWhitespace ? '0' : '1' }, window.location.href));
+ historyPushState(mergeUrlParams({ w }, window.location.href));
}
eventHub.$emit('refetchDiffData');
@@ -710,5 +743,22 @@ export function moveToNeighboringCommit({ dispatch, state }, { direction }) {
}
}
+export const setCurrentDiffFileIdFromNote = ({ commit, rootGetters }, noteId) => {
+ const note = rootGetters.notesById[noteId];
+
+ if (!note) return;
+
+ const fileHash = rootGetters.getDiscussion(note.discussion_id).diff_file.file_hash;
+
+ commit(types.UPDATE_CURRENT_DIFF_FILE_ID, fileHash);
+};
+
+export const navigateToDiffFileIndex = ({ commit, state }, index) => {
+ const fileHash = state.diffFiles[index].file_hash;
+ document.location.hash = fileHash;
+
+ commit(types.UPDATE_CURRENT_DIFF_FILE_ID, fileHash);
+};
+
// prevent babel-plugin-rewire from generating an invalid default during karma tests
export default () => {};
diff --git a/app/assets/javascripts/diffs/store/modules/diff_state.js b/app/assets/javascripts/diffs/store/modules/diff_state.js
index 87938ababed..1f165dd4971 100644
--- a/app/assets/javascripts/diffs/store/modules/diff_state.js
+++ b/app/assets/javascripts/diffs/store/modules/diff_state.js
@@ -1,10 +1,17 @@
import Cookies from 'js-cookie';
import { getParameterValues } from '~/lib/utils/url_utility';
-import { INLINE_DIFF_VIEW_TYPE, DIFF_VIEW_COOKIE_NAME } from '../../constants';
+import {
+ INLINE_DIFF_VIEW_TYPE,
+ DIFF_VIEW_COOKIE_NAME,
+ DIFF_WHITESPACE_COOKIE_NAME,
+} from '../../constants';
+import { getDefaultWhitespace } from '../utils';
const viewTypeFromQueryString = getParameterValues('view')[0];
const viewTypeFromCookie = Cookies.get(DIFF_VIEW_COOKIE_NAME);
const defaultViewType = INLINE_DIFF_VIEW_TYPE;
+const whiteSpaceFromQueryString = getParameterValues('w')[0];
+const whiteSpaceFromCookie = Cookies.get(DIFF_WHITESPACE_COOKIE_NAME);
export default () => ({
isLoading: true,
@@ -29,7 +36,7 @@ export default () => ({
commentForms: [],
highlightedRow: null,
renderTreeList: true,
- showWhitespace: true,
+ showWhitespace: getDefaultWhitespace(whiteSpaceFromQueryString, whiteSpaceFromCookie),
fileFinderVisible: false,
dismissEndpoint: '',
showSuggestPopover: true,
diff --git a/app/assets/javascripts/diffs/store/utils.js b/app/assets/javascripts/diffs/store/utils.js
index d261be1b550..bc85dd0a1d4 100644
--- a/app/assets/javascripts/diffs/store/utils.js
+++ b/app/assets/javascripts/diffs/store/utils.js
@@ -15,6 +15,8 @@ import {
TREE_TYPE,
INLINE_DIFF_VIEW_TYPE,
PARALLEL_DIFF_VIEW_TYPE,
+ SHOW_WHITESPACE,
+ NO_SHOW_WHITESPACE,
} from '../constants';
export function findDiffFile(files, match, matchKey = 'file_hash') {
@@ -701,3 +703,10 @@ export const allDiscussionWrappersExpanded = diff => {
return discussionsExpanded;
};
+
+export const getDefaultWhitespace = (queryString, cookie) => {
+ // Querystring should override stored cookie value
+ if (queryString) return queryString === SHOW_WHITESPACE;
+ if (cookie === NO_SHOW_WHITESPACE) return false;
+ return true;
+};
diff --git a/app/assets/javascripts/editor/editor_lite.js b/app/assets/javascripts/editor/editor_lite.js
index 020ed6dc867..551ffbabaef 100644
--- a/app/assets/javascripts/editor/editor_lite.js
+++ b/app/assets/javascripts/editor/editor_lite.js
@@ -1,4 +1,4 @@
-import { editor as monacoEditor, languages as monacoLanguages, Uri } from 'monaco-editor';
+import { editor as monacoEditor, languages as monacoLanguages, Position, Uri } from 'monaco-editor';
import { DEFAULT_THEME, themes } from '~/ide/lib/themes';
import languages from '~/ide/lib/languages';
import { defaultEditorOptions } from '~/ide/lib/editor_options';
@@ -70,6 +70,27 @@ export default class Editor {
}
getValue() {
- return this.model.getValue();
+ return this.instance.getValue();
+ }
+
+ setValue(val) {
+ this.instance.setValue(val);
+ }
+
+ focus() {
+ this.instance.focus();
+ }
+
+ navigateFileStart() {
+ this.instance.setPosition(new Position(1, 1));
+ }
+
+ updateOptions(options = {}) {
+ this.instance.updateOptions(options);
+ }
+
+ use(exts = []) {
+ const extensions = Array.isArray(exts) ? exts : [exts];
+ Object.assign(this, ...extensions);
}
}
diff --git a/app/assets/javascripts/editor/editor_markdown_ext.js b/app/assets/javascripts/editor/editor_markdown_ext.js
new file mode 100644
index 00000000000..9d09663e643
--- /dev/null
+++ b/app/assets/javascripts/editor/editor_markdown_ext.js
@@ -0,0 +1,99 @@
+export default {
+ getSelectedText(selection = this.getSelection()) {
+ const { startLineNumber, endLineNumber, startColumn, endColumn } = selection;
+ const valArray = this.instance.getValue().split('\n');
+ let text = '';
+ if (startLineNumber === endLineNumber) {
+ text = valArray[startLineNumber - 1].slice(startColumn - 1, endColumn - 1);
+ } else {
+ const startLineText = valArray[startLineNumber - 1].slice(startColumn - 1);
+ const endLineText = valArray[endLineNumber - 1].slice(0, endColumn - 1);
+
+ for (let i = startLineNumber, k = endLineNumber - 1; i < k; i += 1) {
+ text += `${valArray[i]}`;
+ if (i !== k - 1) text += `\n`;
+ }
+ text = text
+ ? [startLineText, text, endLineText].join('\n')
+ : [startLineText, endLineText].join('\n');
+ }
+ return text;
+ },
+
+ getSelection() {
+ return this.instance.getSelection();
+ },
+
+ replaceSelectedText(text, select = undefined) {
+ const forceMoveMarkers = !select;
+ this.instance.executeEdits('', [{ range: this.getSelection(), text, forceMoveMarkers }]);
+ },
+
+ moveCursor(dx = 0, dy = 0) {
+ const pos = this.instance.getPosition();
+ pos.column += dx;
+ pos.lineNumber += dy;
+ this.instance.setPosition(pos);
+ },
+
+ /**
+ * Adjust existing selection to select text within the original selection.
+ * - If `selectedText` is not supplied, we fetch selected text with
+ *
+ * ALGORITHM:
+ *
+ * MULTI-LINE SELECTION
+ * 1. Find line that contains `toSelect` text.
+ * 2. Using the index of this line and the position of `toSelect` text in it,
+ * construct:
+ * * newStartLineNumber
+ * * newStartColumn
+ *
+ * SINGLE-LINE SELECTION
+ * 1. Use `startLineNumber` from the current selection as `newStartLineNumber`
+ * 2. Find the position of `toSelect` text in it to get `newStartColumn`
+ *
+ * 3. `newEndLineNumber` — Since this method is supposed to be used with
+ * markdown decorators that are pretty short, the `newEndLineNumber` is
+ * suggested to be assumed the same as the startLine.
+ * 4. `newEndColumn` — pretty obvious
+ * 5. Adjust the start and end positions of the current selection
+ * 6. Re-set selection on the instance
+ *
+ * @param {string} toSelect - New text to select within current selection.
+ * @param {string} selectedText - Currently selected text. It's just a
+ * shortcut: If it's not supplied, we fetch selected text from the instance
+ */
+ selectWithinSelection(toSelect, selectedText) {
+ const currentSelection = this.getSelection();
+ if (currentSelection.isEmpty() || !toSelect) {
+ return;
+ }
+ const text = selectedText || this.getSelectedText(currentSelection);
+ let lineShift;
+ let newStartLineNumber;
+ let newStartColumn;
+
+ const textLines = text.split('\n');
+
+ if (textLines.length > 1) {
+ // Multi-line selection
+ lineShift = textLines.findIndex(line => line.indexOf(toSelect) !== -1);
+ newStartLineNumber = currentSelection.startLineNumber + lineShift;
+ newStartColumn = textLines[lineShift].indexOf(toSelect) + 1;
+ } else {
+ // Single-line selection
+ newStartLineNumber = currentSelection.startLineNumber;
+ newStartColumn = currentSelection.startColumn + text.indexOf(toSelect);
+ }
+
+ const newEndLineNumber = newStartLineNumber;
+ const newEndColumn = newStartColumn + toSelect.length;
+
+ const newSelection = currentSelection
+ .setStartPosition(newStartLineNumber, newStartColumn)
+ .setEndPosition(newEndLineNumber, newEndColumn);
+
+ this.instance.setSelection(newSelection);
+ },
+};
diff --git a/app/assets/javascripts/emoji/index.js b/app/assets/javascripts/emoji/index.js
index 27dff8cf9aa..4567c807c40 100644
--- a/app/assets/javascripts/emoji/index.js
+++ b/app/assets/javascripts/emoji/index.js
@@ -1,13 +1,63 @@
import { uniq } from 'lodash';
-import emojiMap from 'emojis/digests.json';
import emojiAliases from 'emojis/aliases.json';
+import axios from '../lib/utils/axios_utils';
-export const validEmojiNames = [...Object.keys(emojiMap), ...Object.keys(emojiAliases)];
+import AccessorUtilities from '../lib/utils/accessor';
+
+let emojiMap = null;
+let emojiPromise = null;
+let validEmojiNames = null;
+
+export const EMOJI_VERSION = '1';
+
+const isLocalStorageAvailable = AccessorUtilities.isLocalStorageAccessSafe();
+
+export function initEmojiMap() {
+ emojiPromise =
+ emojiPromise ||
+ new Promise((resolve, reject) => {
+ if (emojiMap) {
+ resolve(emojiMap);
+ } else if (
+ isLocalStorageAvailable &&
+ window.localStorage.getItem('gl-emoji-map-version') === EMOJI_VERSION &&
+ window.localStorage.getItem('gl-emoji-map')
+ ) {
+ emojiMap = JSON.parse(window.localStorage.getItem('gl-emoji-map'));
+ validEmojiNames = [...Object.keys(emojiMap), ...Object.keys(emojiAliases)];
+ resolve(emojiMap);
+ } else {
+ // We load the JSON file direct from the server
+ // because it can't be loaded from a CDN due to
+ // cross domain problems with JSON
+ axios
+ .get(`${gon.relative_url_root || ''}/-/emojis/${EMOJI_VERSION}/emojis.json`)
+ .then(({ data }) => {
+ emojiMap = data;
+ validEmojiNames = [...Object.keys(emojiMap), ...Object.keys(emojiAliases)];
+ resolve(emojiMap);
+ if (isLocalStorageAvailable) {
+ window.localStorage.setItem('gl-emoji-map-version', EMOJI_VERSION);
+ window.localStorage.setItem('gl-emoji-map', JSON.stringify(emojiMap));
+ }
+ })
+ .catch(err => {
+ reject(err);
+ });
+ }
+ });
+
+ return emojiPromise;
+}
export function normalizeEmojiName(name) {
return Object.prototype.hasOwnProperty.call(emojiAliases, name) ? emojiAliases[name] : name;
}
+export function getValidEmojiNames() {
+ return validEmojiNames;
+}
+
export function isEmojiNameValid(name) {
return validEmojiNames.indexOf(name) >= 0;
}
@@ -36,8 +86,8 @@ export function getEmojiCategoryMap() {
};
Object.keys(emojiMap).forEach(name => {
const emoji = emojiMap[name];
- if (emojiCategoryMap[emoji.category]) {
- emojiCategoryMap[emoji.category].push(name);
+ if (emojiCategoryMap[emoji.c]) {
+ emojiCategoryMap[emoji.c].push(name);
}
});
}
@@ -58,8 +108,9 @@ export function getEmojiInfo(query) {
}
export function emojiFallbackImageSrc(inputName) {
- const { name, digest } = getEmojiInfo(inputName);
- return `${gon.asset_host || ''}${gon.relative_url_root || ''}/assets/emoji/${name}-${digest}.png`;
+ const { name } = getEmojiInfo(inputName);
+ return `${gon.asset_host || ''}${gon.relative_url_root ||
+ ''}/-/emojis/${EMOJI_VERSION}/${name}.png`;
}
export function emojiImageTag(name, src) {
@@ -67,36 +118,17 @@ export function emojiImageTag(name, src) {
}
export function glEmojiTag(inputName, options) {
- const opts = { sprite: false, forceFallback: false, ...options };
- const { name, ...emojiInfo } = getEmojiInfo(inputName);
-
- const fallbackImageSrc = emojiFallbackImageSrc(name);
+ const opts = { sprite: false, ...options };
+ const name = normalizeEmojiName(inputName);
const fallbackSpriteClass = `emoji-${name}`;
- const classList = [];
- if (opts.forceFallback && opts.sprite) {
- classList.push('emoji-icon');
- classList.push(fallbackSpriteClass);
- }
- const classAttribute = classList.length > 0 ? `class="${classList.join(' ')}"` : '';
const fallbackSpriteAttribute = opts.sprite
? `data-fallback-sprite-class="${fallbackSpriteClass}"`
: '';
- let contents = emojiInfo.moji;
- if (opts.forceFallback && !opts.sprite) {
- contents = emojiImageTag(name, fallbackImageSrc);
- }
return `
<gl-emoji
- ${classAttribute}
- data-name="${name}"
- data-fallback-src="${fallbackImageSrc}"
${fallbackSpriteAttribute}
- data-unicode-version="${emojiInfo.unicodeVersion}"
- title="${emojiInfo.description}"
- >
- ${contents}
- </gl-emoji>
+ data-name="${name}"></gl-emoji>
`;
}
diff --git a/app/assets/javascripts/environments/components/container.vue b/app/assets/javascripts/environments/components/container.vue
index 899d7ec8521..4c6d233c4d2 100644
--- a/app/assets/javascripts/environments/components/container.vue
+++ b/app/assets/javascripts/environments/components/container.vue
@@ -67,12 +67,7 @@ export default {
<template>
<div class="environments-container">
- <gl-loading-icon
- v-if="isLoading"
- size="md"
- class="prepend-top-default"
- label="Loading environments"
- />
+ <gl-loading-icon v-if="isLoading" size="md" class="gl-mt-3" label="Loading environments" />
<slot name="emptyState"></slot>
diff --git a/app/assets/javascripts/environments/components/empty_state.vue b/app/assets/javascripts/environments/components/empty_state.vue
index ca2ac4c3c53..977da12e8a9 100644
--- a/app/assets/javascripts/environments/components/empty_state.vue
+++ b/app/assets/javascripts/environments/components/empty_state.vue
@@ -2,14 +2,6 @@
export default {
name: 'EnvironmentsEmptyState',
props: {
- newPath: {
- type: String,
- required: true,
- },
- canCreateEnvironment: {
- type: Boolean,
- required: true,
- },
helpPath: {
type: String,
required: true,
@@ -28,18 +20,8 @@ export default {
s__(`Environments|Environments are places where
code gets deployed, such as staging or production.`)
}}
- <a :href="helpPath"> {{ s__('Environments|Read more about environments') }} </a>
+ <a :href="helpPath"> {{ s__('Environments|More information') }} </a>
</p>
-
- <div class="text-center">
- <a
- v-if="canCreateEnvironment"
- :href="newPath"
- class="btn btn-success js-new-environment-button"
- >
- {{ s__('Environments|New environment') }}
- </a>
- </div>
</div>
</div>
</template>
diff --git a/app/assets/javascripts/environments/components/environments_app.vue b/app/assets/javascripts/environments/components/environments_app.vue
index b8bcca814cd..d26bd14a937 100644
--- a/app/assets/javascripts/environments/components/environments_app.vue
+++ b/app/assets/javascripts/environments/components/environments_app.vue
@@ -159,11 +159,7 @@ export default {
@onChangePage="onChangePage"
>
<template v-if="!isLoading && state.environments.length === 0" #emptyState>
- <empty-state
- :new-path="newEnvironmentPath"
- :help-path="helpPagePath"
- :can-create-environment="canCreateEnvironment"
- />
+ <empty-state :help-path="helpPagePath" />
</template>
</container>
</div>
diff --git a/app/assets/javascripts/environments/components/environments_table.vue b/app/assets/javascripts/environments/components/environments_table.vue
index 380e16c7b71..ab1818e61fa 100644
--- a/app/assets/javascripts/environments/components/environments_table.vue
+++ b/app/assets/javascripts/environments/components/environments_table.vue
@@ -188,7 +188,7 @@ export default {
<template v-if="shouldRenderFolderContent(model)">
<div v-if="model.isLoadingFolderContent" :key="`loading-item-${i}`">
- <gl-loading-icon size="md" class="prepend-top-16" />
+ <gl-loading-icon size="md" class="gl-mt-5" />
</div>
<template v-else>
diff --git a/app/assets/javascripts/environments/mixins/environments_mixin.js b/app/assets/javascripts/environments/mixins/environments_mixin.js
index 73dc8c02485..f2b464464e9 100644
--- a/app/assets/javascripts/environments/mixins/environments_mixin.js
+++ b/app/assets/javascripts/environments/mixins/environments_mixin.js
@@ -145,7 +145,7 @@ export default {
deleteEnvironment(environment) {
const endpoint = environment.delete_path;
- const mountedToShow = environment.mounted_to_show;
+ const { onSingleEnvironmentPage } = environment;
const errorMessage = s__(
'Environments|An error occurred while deleting the environment. Check if the environment stopped; if not, stop it and try again.',
);
@@ -153,7 +153,7 @@ export default {
this.service
.deleteAction(endpoint)
.then(() => {
- if (!mountedToShow) {
+ if (!onSingleEnvironmentPage) {
// Reload as a first solution to bust the ETag cache
window.location.reload();
return;
diff --git a/app/assets/javascripts/environments/mount_show.js b/app/assets/javascripts/environments/mount_show.js
index 1929ed080a1..d0b68b0c14f 100644
--- a/app/assets/javascripts/environments/mount_show.js
+++ b/app/assets/javascripts/environments/mount_show.js
@@ -15,7 +15,7 @@ export default () => {
data() {
const environment = JSON.parse(JSON.stringify(container.dataset));
environment.delete_path = environment.deletePath;
- environment.mounted_to_show = true;
+ environment.onSingleEnvironmentPage = true;
return {
environment,
diff --git a/app/assets/javascripts/error_tracking/components/error_details.vue b/app/assets/javascripts/error_tracking/components/error_details.vue
index 1e8f5a26125..52444d2c493 100644
--- a/app/assets/javascripts/error_tracking/components/error_details.vue
+++ b/app/assets/javascripts/error_tracking/components/error_details.vue
@@ -2,7 +2,7 @@
import { mapActions, mapGetters, mapState } from 'vuex';
import createFlash from '~/flash';
import {
- GlDeprecatedButton,
+ GlButton,
GlFormInput,
GlLink,
GlLoadingIcon,
@@ -33,7 +33,7 @@ const SENTRY_TIMEOUT = 10000;
export default {
components: {
- GlDeprecatedButton,
+ GlButton,
GlFormInput,
GlLink,
GlLoadingIcon,
@@ -106,6 +106,7 @@ export default {
errorPollTimeout: 0,
issueCreationInProgress: false,
isAlertVisible: false,
+ isStacktraceEmptyAlertVisible: true,
closedIssueId: null,
};
},
@@ -119,10 +120,10 @@ export default {
]),
...mapGetters('details', ['stacktrace']),
firstReleaseLink() {
- return `${this.error.externalBaseUrl}/releases/${this.error.firstReleaseShortVersion}`;
+ return `${this.error.externalBaseUrl}/releases/${this.error.firstReleaseVersion}`;
},
lastReleaseLink() {
- return `${this.error.externalBaseUrl}/releases/${this.error.lastReleaseShortVersion}`;
+ return `${this.error.externalBaseUrl}/releases/${this.error.lastReleaseVersion}`;
},
showStacktrace() {
return Boolean(this.stacktrace?.length);
@@ -167,6 +168,9 @@ export default {
resolveBtnLabel() {
return this.errorStatus !== errorStatus.RESOLVED ? __('Resolve') : __('Unresolve');
},
+ showEmptyStacktraceAlert() {
+ return !this.loadingStacktrace && !this.showStacktrace && this.isStacktraceEmptyAlertVisible;
+ },
},
watch: {
error(val) {
@@ -254,6 +258,10 @@ export default {
</gl-sprintf>
</gl-alert>
+ <gl-alert v-if="showEmptyStacktraceAlert" @dismiss="isStacktraceEmptyAlertVisible = false">
+ {{ __('No stack trace for this error') }}
+ </gl-alert>
+
<div class="error-details-header d-flex py-2 justify-content-between">
<div
v-if="!loadingStacktrace && stacktrace"
@@ -271,22 +279,24 @@ export default {
</div>
<div class="error-details-actions">
<div class="d-inline-flex bv-d-sm-down-none">
- <gl-deprecated-button
+ <gl-button
:loading="updatingIgnoreStatus"
data-testid="update-ignore-status-btn"
@click="onIgnoreStatusUpdate"
>
{{ ignoreBtnLabel }}
- </gl-deprecated-button>
- <gl-deprecated-button
- class="btn-outline-info ml-2"
+ </gl-button>
+ <gl-button
+ class="ml-2"
+ category="secondary"
+ variant="info"
:loading="updatingResolveStatus"
data-testid="update-resolve-status-btn"
@click="onResolveStatusUpdate"
>
{{ resolveBtnLabel }}
- </gl-deprecated-button>
- <gl-deprecated-button
+ </gl-button>
+ <gl-button
v-if="error.gitlabIssuePath"
class="ml-2"
data-testid="view_issue_button"
@@ -294,7 +304,7 @@ export default {
variant="success"
>
{{ __('View issue') }}
- </gl-deprecated-button>
+ </gl-button>
<form
ref="sentryIssueForm"
:action="projectIssuesPath"
@@ -309,15 +319,16 @@ export default {
name="issue[sentry_issue_attributes][sentry_issue_identifier]"
/>
<gl-form-input :value="csrfToken" class="hidden" name="authenticity_token" />
- <gl-deprecated-button
+ <gl-button
v-if="!error.gitlabIssuePath"
- class="btn-success"
+ category="primary"
+ variant="success"
:loading="issueCreationInProgress"
data-qa-selector="create_issue_button"
@click="createIssue"
>
{{ __('Create issue') }}
- </gl-deprecated-button>
+ </gl-button>
</form>
</div>
<gl-dropdown
@@ -389,18 +400,18 @@ export default {
<icon name="external-link" class="ml-1 flex-shrink-0" />
</gl-link>
</li>
- <li v-if="error.firstReleaseShortVersion">
+ <li v-if="error.firstReleaseVersion">
<strong class="bold">{{ __('First seen') }}:</strong>
<time-ago-tooltip :time="error.firstSeen" />
<gl-link :href="firstReleaseLink" target="_blank">
- <span>{{ __('Release') }}: {{ error.firstReleaseShortVersion.substr(0, 10) }}</span>
+ <span>{{ __('Release') }}: {{ error.firstReleaseVersion }}</span>
</gl-link>
</li>
- <li v-if="error.lastReleaseShortVersion">
+ <li v-if="error.lastReleaseVersion">
<strong class="bold">{{ __('Last seen') }}:</strong>
<time-ago-tooltip :time="error.lastSeen" />
<gl-link :href="lastReleaseLink" target="_blank">
- <span>{{ __('Release') }}: {{ error.lastReleaseShortVersion.substr(0, 10) }}</span>
+ <span>{{ __('Release') }}: {{ error.lastReleaseVersion }}</span>
</gl-link>
</li>
<li>
diff --git a/app/assets/javascripts/error_tracking/components/stacktrace_entry.vue b/app/assets/javascripts/error_tracking/components/stacktrace_entry.vue
index d806c6934a3..c22f34b5a8d 100644
--- a/app/assets/javascripts/error_tracking/components/stacktrace_entry.vue
+++ b/app/assets/javascripts/error_tracking/components/stacktrace_entry.vue
@@ -80,14 +80,9 @@ export default {
<div ref="header" class="file-title file-title-flex-parent">
<div class="file-header-content d-flex align-content-center">
<div v-if="hasCode" class="d-inline-block cursor-pointer" @click="toggle()">
- <icon :name="collapseIcon" :size="16" aria-hidden="true" class="append-right-5" />
+ <icon :name="collapseIcon" :size="16" aria-hidden="true" class="gl-mr-2" />
</div>
- <file-icon
- :file-name="filePath"
- :size="18"
- aria-hidden="true"
- css-classes="append-right-5"
- />
+ <file-icon :file-name="filePath" :size="18" aria-hidden="true" css-classes="gl-mr-2" />
<strong
v-gl-tooltip
:title="filePath"
diff --git a/app/assets/javascripts/error_tracking/queries/details.query.graphql b/app/assets/javascripts/error_tracking/queries/details.query.graphql
index fa579c94257..593cbf2ae52 100644
--- a/app/assets/javascripts/error_tracking/queries/details.query.graphql
+++ b/app/assets/javascripts/error_tracking/queries/details.query.graphql
@@ -1,29 +1,29 @@
query errorDetails($fullPath: ID!, $errorId: ID!) {
- project(fullPath: $fullPath) {
- sentryErrors {
- detailedError(id: $errorId) {
- id
- sentryId
- title
- userCount
- count
- status
- firstSeen
- lastSeen
- message
- culprit
- tags {
- level
- logger
- }
- externalUrl
- externalBaseUrl
- firstReleaseShortVersion
- lastReleaseShortVersion
- gitlabCommit
- gitlabCommitPath
- gitlabIssuePath
- }
+ project(fullPath: $fullPath) {
+ sentryErrors {
+ detailedError(id: $errorId) {
+ id
+ sentryId
+ title
+ userCount
+ count
+ status
+ firstSeen
+ lastSeen
+ message
+ culprit
+ tags {
+ level
+ logger
}
+ externalUrl
+ externalBaseUrl
+ firstReleaseVersion
+ lastReleaseVersion
+ gitlabCommit
+ gitlabCommitPath
+ gitlabIssuePath
+ }
}
+ }
}
diff --git a/app/assets/javascripts/error_tracking_settings/components/error_tracking_form.vue b/app/assets/javascripts/error_tracking_settings/components/error_tracking_form.vue
index 0be42519092..0de67a8bcc7 100644
--- a/app/assets/javascripts/error_tracking_settings/components/error_tracking_form.vue
+++ b/app/assets/javascripts/error_tracking_settings/components/error_tracking_form.vue
@@ -59,14 +59,14 @@ export default {
</div>
<div class="col-4 col-md-3 gl-pl-0">
<loading-button
- class="js-error-tracking-connect prepend-left-5 d-inline-flex"
+ class="js-error-tracking-connect gl-ml-2 d-inline-flex"
:label="isLoadingProjects ? __('Connecting') : __('Connect')"
:loading="isLoadingProjects"
@click="fetchProjects"
/>
<icon
v-show="connectSuccessful"
- class="js-error-tracking-connect-success prepend-left-5 text-success align-middle"
+ class="js-error-tracking-connect-success gl-ml-2 text-success align-middle"
:aria-label="__('Projects Successfully Retrieved')"
name="check-circle"
/>
diff --git a/app/assets/javascripts/filtered_search/constants.js b/app/assets/javascripts/filtered_search/constants.js
index 7e7a2588951..0b9fe969da1 100644
--- a/app/assets/javascripts/filtered_search/constants.js
+++ b/app/assets/javascripts/filtered_search/constants.js
@@ -9,3 +9,5 @@ export const FILTER_TYPE = {
none: 'none',
any: 'any',
};
+
+export const MAX_HISTORY_SIZE = 5;
diff --git a/app/assets/javascripts/filtered_search/dropdown_emoji.js b/app/assets/javascripts/filtered_search/dropdown_emoji.js
index dad188f6f98..adeea0ed5f6 100644
--- a/app/assets/javascripts/filtered_search/dropdown_emoji.js
+++ b/app/assets/javascripts/filtered_search/dropdown_emoji.js
@@ -10,7 +10,7 @@ export default class DropdownEmoji extends FilteredSearchDropdown {
super(options);
this.config = {
Ajax: {
- endpoint: `${gon.relative_url_root || ''}/autocomplete/award_emojis`,
+ endpoint: `${gon.relative_url_root || ''}/-/autocomplete/award_emojis`,
method: 'setData',
loadingTemplate: this.loadingTemplate,
onError() {
diff --git a/app/assets/javascripts/filtered_search/dropdown_user.js b/app/assets/javascripts/filtered_search/dropdown_user.js
index a65c0012b4d..0fb1828fc98 100644
--- a/app/assets/javascripts/filtered_search/dropdown_user.js
+++ b/app/assets/javascripts/filtered_search/dropdown_user.js
@@ -5,7 +5,7 @@ export default class DropdownUser extends DropdownAjaxFilter {
constructor(options = {}) {
super({
...options,
- endpoint: '/autocomplete/users.json',
+ endpoint: '/-/autocomplete/users.json',
symbol: '@',
});
}
diff --git a/app/assets/javascripts/filtered_search/filtered_search_manager.js b/app/assets/javascripts/filtered_search/filtered_search_manager.js
index 55a0e91b0f3..108cc8d3a78 100644
--- a/app/assets/javascripts/filtered_search/filtered_search_manager.js
+++ b/app/assets/javascripts/filtered_search/filtered_search_manager.js
@@ -32,6 +32,7 @@ export default class FilteredSearchManager {
filteredSearchTokenKeys = IssuableFilteredSearchTokenKeys,
stateFiltersSelector = '.issues-state-filters',
placeholder = __('Search or filter results...'),
+ anchor = null,
}) {
this.isGroup = isGroup;
this.isGroupAncestor = isGroupAncestor;
@@ -47,6 +48,7 @@ export default class FilteredSearchManager {
this.filteredSearchTokenKeys = filteredSearchTokenKeys;
this.stateFiltersSelector = stateFiltersSelector;
this.placeholder = placeholder;
+ this.anchor = anchor;
const { multipleAssignees } = this.filteredSearchInput.dataset;
if (multipleAssignees && this.filteredSearchTokenKeys.enableMultipleAssignees) {
@@ -779,7 +781,11 @@ export default class FilteredSearchManager {
paths.push(`search=${sanitized}`);
}
- const parameterizedUrl = `?scope=all&utf8=%E2%9C%93&${paths.join('&')}`;
+ let parameterizedUrl = `?scope=all&utf8=%E2%9C%93&${paths.join('&')}`;
+
+ if (this.anchor) {
+ parameterizedUrl += `#${this.anchor}`;
+ }
if (this.updateObject) {
this.updateObject(parameterizedUrl);
diff --git a/app/assets/javascripts/filtered_search/filtered_search_tokenizer.js b/app/assets/javascripts/filtered_search/filtered_search_tokenizer.js
index 963e8fe5df5..be0fb5cac13 100644
--- a/app/assets/javascripts/filtered_search/filtered_search_tokenizer.js
+++ b/app/assets/javascripts/filtered_search/filtered_search_tokenizer.js
@@ -6,7 +6,7 @@ export default class FilteredSearchTokenizer {
// Values that start with a double quote must end in a double quote (same for single)
const tokenRegex = new RegExp(
- `(${allowedKeys.join('|')}):(=|!=)?([~%@]?)(?:('[^']*'{0,1})|("[^"]*"{0,1})|(\\S+))`,
+ `(${allowedKeys.join('|')}):(=|!=)?([~%@&]?)(?:('[^']*'{0,1})|("[^"]*"{0,1})|(\\S+))`,
'g',
);
const tokens = [];
@@ -15,17 +15,19 @@ export default class FilteredSearchTokenizer {
const searchToken =
input
.replace(tokenRegex, (match, key, operator, symbol, v1, v2, v3) => {
+ const prefixedTokens = ['~', '%', '@', '&'];
+ const comparisonTokens = ['!=', '='];
let tokenValue = v1 || v2 || v3;
let tokenSymbol = symbol;
let tokenIndex = '';
let tokenOperator = operator;
- if (tokenValue === '~' || tokenValue === '%' || tokenValue === '@') {
+ if (prefixedTokens.includes(tokenValue)) {
tokenSymbol = tokenValue;
tokenValue = '';
}
- if (tokenValue === '!=' || tokenValue === '=') {
+ if (comparisonTokens.includes(tokenValue)) {
tokenOperator = tokenValue;
tokenValue = '';
}
diff --git a/app/assets/javascripts/filtered_search/stores/recent_searches_store.js b/app/assets/javascripts/filtered_search/stores/recent_searches_store.js
index cdbc9ec84bd..423f123f71c 100644
--- a/app/assets/javascripts/filtered_search/stores/recent_searches_store.js
+++ b/app/assets/javascripts/filtered_search/stores/recent_searches_store.js
@@ -1,4 +1,6 @@
-import { uniq } from 'lodash';
+import { uniqWith, isEqual } from 'lodash';
+
+import { MAX_HISTORY_SIZE } from '../constants';
class RecentSearchesStore {
constructor(initialState = {}, allowedKeys) {
@@ -17,8 +19,12 @@ class RecentSearchesStore {
}
setRecentSearches(searches = []) {
- const trimmedSearches = searches.map(search => search.trim());
- this.state.recentSearches = uniq(trimmedSearches).slice(0, 5);
+ const trimmedSearches = searches.map(search =>
+ typeof search === 'string' ? search.trim() : search,
+ );
+
+ // Do object equality check to remove duplicates.
+ this.state.recentSearches = uniqWith(trimmedSearches, isEqual).slice(0, MAX_HISTORY_SIZE);
return this.state.recentSearches;
}
}
diff --git a/app/assets/javascripts/filtered_search/visual_token_value.js b/app/assets/javascripts/filtered_search/visual_token_value.js
index 02caf0851af..b1e6c4142e9 100644
--- a/app/assets/javascripts/filtered_search/visual_token_value.js
+++ b/app/assets/javascripts/filtered_search/visual_token_value.js
@@ -7,6 +7,7 @@ import DropdownUtils from '~/filtered_search/dropdown_utils';
import Flash from '~/flash';
import UsersCache from '~/lib/utils/users_cache';
import { __ } from '~/locale';
+import * as Emoji from '~/emoji';
export default class VisualTokenValue {
constructor(tokenValue, tokenType, tokenOperator) {
@@ -137,18 +138,13 @@ export default class VisualTokenValue {
const element = tokenValueElement;
const value = this.tokenValue;
- return (
- import(/* webpackChunkName: 'emoji' */ '../emoji')
- .then(Emoji => {
- if (!Emoji.isEmojiNameValid(value)) {
- return;
- }
+ return Emoji.initEmojiMap().then(() => {
+ if (!Emoji.isEmojiNameValid(value)) {
+ return;
+ }
- container.dataset.originalValue = value;
- element.innerHTML = Emoji.glEmojiTag(value);
- })
- // ignore error and leave emoji name in the search bar
- .catch(() => {})
- );
+ container.dataset.originalValue = value;
+ element.innerHTML = Emoji.glEmojiTag(value);
+ });
}
}
diff --git a/app/assets/javascripts/gfm_auto_complete.js b/app/assets/javascripts/gfm_auto_complete.js
index f3ce30c942f..36c586ddfd2 100644
--- a/app/assets/javascripts/gfm_auto_complete.js
+++ b/app/assets/javascripts/gfm_auto_complete.js
@@ -5,6 +5,7 @@ import SidebarMediator from '~/sidebar/sidebar_mediator';
import glRegexp from './lib/utils/regexp';
import AjaxCache from './lib/utils/ajax_cache';
import { spriteIcon } from './lib/utils/common_utils';
+import * as Emoji from '~/emoji';
function sanitize(str) {
return str.replace(/<(?:.|\n)*?>/gm, '');
@@ -29,7 +30,7 @@ export function membersBeforeSave(members) {
const imgAvatar = `<img src="${member.avatar_url}" alt="${member.username}" class="avatar ${rectAvatarClass} avatar-inline center s26"/>`;
const txtAvatar = `<div class="avatar ${rectAvatarClass} center avatar-inline s26">${autoCompleteAvatar}</div>`;
const avatarIcon = member.mentionsDisabled
- ? spriteIcon('notifications-off', 's16 vertical-align-middle prepend-left-5')
+ ? spriteIcon('notifications-off', 's16 vertical-align-middle gl-ml-2')
: '';
return {
@@ -88,7 +89,6 @@ class GfmAutoComplete {
if (this.enableMap.labels) this.setupLabels($input);
if (this.enableMap.snippets) this.setupSnippets($input);
- // We don't instantiate the quick actions autocomplete for note and issue/MR edit forms
$input.filter('[data-supports-quick-actions="true"]').atwho({
at: '/',
alias: 'commands',
@@ -109,8 +109,10 @@ class GfmAutoComplete {
tpl += ' <small class="params"><%- params.join(" ") %></small>';
}
if (value.warning && value.icon && value.icon === 'confidential') {
- tpl +=
- '<small class="description"><em><i class="fa fa-eye-slash" aria-hidden="true"/><%- warning %></em></small>';
+ tpl += `<small class="description gl-display-flex gl-align-items-center">${spriteIcon(
+ 'eye-slash',
+ 's16 gl-mr-2',
+ )}<em><%- warning %></em></small>`;
} else if (value.warning) {
tpl += '<small class="description"><em><%- warning %></em></small>';
} else if (value.description !== '') {
@@ -587,14 +589,12 @@ class GfmAutoComplete {
if (this.cachedData[at]) {
this.loadData($input, at, this.cachedData[at]);
} else if (GfmAutoComplete.atTypeMap[at] === 'emojis') {
- import(/* webpackChunkName: 'emoji' */ './emoji')
- .then(({ validEmojiNames, glEmojiTag }) => {
- this.loadData($input, at, validEmojiNames);
- GfmAutoComplete.glEmojiTag = glEmojiTag;
+ Emoji.initEmojiMap()
+ .then(() => {
+ this.loadData($input, at, Emoji.getValidEmojiNames());
+ GfmAutoComplete.glEmojiTag = Emoji.glEmojiTag;
})
- .catch(() => {
- this.isLoadingData[at] = false;
- });
+ .catch(() => {});
} else if (dataSource) {
AjaxCache.retrieve(dataSource, true)
.then(data => {
diff --git a/app/assets/javascripts/gl_field_error.js b/app/assets/javascripts/gl_field_error.js
index 04301c9ce12..ac4c8d28ee4 100644
--- a/app/assets/javascripts/gl_field_error.js
+++ b/app/assets/javascripts/gl_field_error.js
@@ -114,7 +114,7 @@ export default class GlFieldError {
this.state.empty = currentValue === '';
this.state.submitted = true;
this.renderValidity();
- this.form.focusOnFirstInvalid.apply(this.form);
+ this.form.focusInvalid.apply(this.form);
// For UX, wait til after first invalid submission to check each keyup
this.inputElement
diff --git a/app/assets/javascripts/gl_field_errors.js b/app/assets/javascripts/gl_field_errors.js
index c4fd719c8d0..ad79483d5ec 100644
--- a/app/assets/javascripts/gl_field_errors.js
+++ b/app/assets/javascripts/gl_field_errors.js
@@ -52,10 +52,23 @@ export default class GlFieldErrors {
});
}
- focusOnFirstInvalid() {
- const firstInvalid = this.state.inputs.filter(
- input => !input.inputDomElement.validity.valid,
- )[0];
- firstInvalid.inputElement.focus();
+ get invalidInputs() {
+ return this.state.inputs.filter(
+ ({
+ inputDomElement: {
+ validity: { valid },
+ },
+ }) => !valid,
+ );
+ }
+
+ get focusedInvalidInput() {
+ return this.invalidInputs.find(({ inputElement }) => inputElement.is(':focus'));
+ }
+
+ focusInvalid() {
+ if (this.focusedInvalidInput) return;
+
+ this.invalidInputs[0].inputElement.focus();
}
}
diff --git a/app/assets/javascripts/gl_form.js b/app/assets/javascripts/gl_form.js
index 0b7735a7db9..0a1e5490237 100644
--- a/app/assets/javascripts/gl_form.js
+++ b/app/assets/javascripts/gl_form.js
@@ -3,19 +3,22 @@ import autosize from 'autosize';
import GfmAutoComplete, { defaultAutocompleteConfig } from 'ee_else_ce/gfm_auto_complete';
import dropzoneInput from './dropzone_input';
import { addMarkdownListeners, removeMarkdownListeners } from './lib/utils/text_markdown';
+import { disableButtonIfEmptyField } from '~/lib/utils/common_utils';
export default class GLForm {
constructor(form, enableGFM = {}) {
this.form = form;
this.textarea = this.form.find('textarea.js-gfm-input');
this.enableGFM = { ...defaultAutocompleteConfig, ...enableGFM };
+
// Disable autocomplete for keywords which do not have dataSources available
const dataSources = (gl.GfmAutoComplete && gl.GfmAutoComplete.dataSources) || {};
Object.keys(this.enableGFM).forEach(item => {
- if (item !== 'emojis') {
- this.enableGFM[item] = Boolean(dataSources[item]);
+ if (item !== 'emojis' && !dataSources[item]) {
+ this.enableGFM[item] = false;
}
});
+
// Before we start, we should clean up any previous data for this form
this.destroy();
// Set up the form
@@ -43,7 +46,7 @@ export default class GLForm {
this.form.find('.div-dropzone').remove();
this.form.addClass('gfm-form');
// remove notify commit author checkbox for non-commit notes
- gl.utils.disableButtonIfEmptyField(
+ disableButtonIfEmptyField(
this.form.find('.js-note-text'),
this.form.find('.js-comment-button, .js-note-new-discussion'),
);
@@ -104,4 +107,8 @@ export default class GLForm {
.removeClass('is-focused');
});
}
+
+ get supportsQuickActions() {
+ return Boolean(this.textarea.data('supports-quick-actions'));
+ }
}
diff --git a/app/assets/javascripts/global_search_input.js b/app/assets/javascripts/global_search_input.js
deleted file mode 100644
index a7c121259d4..00000000000
--- a/app/assets/javascripts/global_search_input.js
+++ /dev/null
@@ -1,425 +0,0 @@
-/* eslint-disable no-return-assign, consistent-return, class-methods-use-this */
-
-import $ from 'jquery';
-import { throttle } from 'lodash';
-import { s__, __, sprintf } from '~/locale';
-import {
- isInGroupsPage,
- isInProjectPage,
- getGroupSlug,
- getProjectSlug,
- spriteIcon,
-} from './lib/utils/common_utils';
-
-/**
- * Search input in top navigation bar.
- * On click, opens a dropdown
- * As the user types it filters the results
- * When the user clicks `x` button it cleans the input and closes the dropdown.
- */
-
-const KEYCODE = {
- ESCAPE: 27,
- BACKSPACE: 8,
- ENTER: 13,
- UP: 38,
- DOWN: 40,
-};
-
-function setSearchOptions() {
- const $projectOptionsDataEl = $('.js-search-project-options');
- const $groupOptionsDataEl = $('.js-search-group-options');
- const $dashboardOptionsDataEl = $('.js-search-dashboard-options');
-
- if ($projectOptionsDataEl.length) {
- gl.projectOptions = gl.projectOptions || {};
-
- const projectPath = $projectOptionsDataEl.data('projectPath');
-
- gl.projectOptions[projectPath] = {
- name: $projectOptionsDataEl.data('name'),
- issuesPath: $projectOptionsDataEl.data('issuesPath'),
- issuesDisabled: $projectOptionsDataEl.data('issuesDisabled'),
- mrPath: $projectOptionsDataEl.data('mrPath'),
- };
- }
-
- if ($groupOptionsDataEl.length) {
- gl.groupOptions = gl.groupOptions || {};
-
- const groupPath = $groupOptionsDataEl.data('groupPath');
-
- gl.groupOptions[groupPath] = {
- name: $groupOptionsDataEl.data('name'),
- issuesPath: $groupOptionsDataEl.data('issuesPath'),
- mrPath: $groupOptionsDataEl.data('mrPath'),
- };
- }
-
- if ($dashboardOptionsDataEl.length) {
- gl.dashboardOptions = {
- name: s__('SearchAutocomplete|All GitLab'),
- issuesPath: $dashboardOptionsDataEl.data('issuesPath'),
- mrPath: $dashboardOptionsDataEl.data('mrPath'),
- };
- }
-}
-
-export class GlobalSearchInput {
- constructor({ wrap } = {}) {
- setSearchOptions();
- this.bindEventContext();
- this.wrap = wrap || $('.search');
- this.dropdown = this.wrap.find('.dropdown');
- this.dropdownToggle = this.wrap.find('.js-dropdown-search-toggle');
- this.dropdownMenu = this.dropdown.find('.dropdown-menu');
- this.dropdownContent = this.dropdown.find('.dropdown-content');
- this.scopeInputEl = this.getElement('#scope');
- this.searchInput = this.getElement('.search-input');
- this.projectInputEl = this.getElement('#search_project_id');
- this.groupInputEl = this.getElement('#group_id');
- this.searchCodeInputEl = this.getElement('#search_code');
- this.repositoryInputEl = this.getElement('#repository_ref');
- this.clearInput = this.getElement('.js-clear-input');
- this.scrollFadeInitialized = false;
- this.saveOriginalState();
-
- // Only when user is logged in
- if (gon.current_user_id) {
- this.createGlobalSearchInput();
- }
-
- this.bindEvents();
- this.dropdownToggle.dropdown();
- this.searchInput.addClass('js-autocomplete-disabled');
- }
-
- // Finds an element inside wrapper element
- bindEventContext() {
- this.onSearchInputBlur = this.onSearchInputBlur.bind(this);
- this.onClearInputClick = this.onClearInputClick.bind(this);
- this.onSearchInputFocus = this.onSearchInputFocus.bind(this);
- this.onSearchInputKeyUp = this.onSearchInputKeyUp.bind(this);
- this.onSearchInputChange = this.onSearchInputChange.bind(this);
- this.setScrollFade = this.setScrollFade.bind(this);
- }
- getElement(selector) {
- return this.wrap.find(selector);
- }
-
- saveOriginalState() {
- return (this.originalState = this.serializeState());
- }
-
- createGlobalSearchInput() {
- return this.searchInput.glDropdown({
- filterInputBlur: false,
- filterable: true,
- filterRemote: true,
- highlight: true,
- icon: true,
- enterCallback: false,
- filterInput: 'input#search',
- search: {
- fields: ['text'],
- },
- id: this.getSearchText,
- data: this.getData.bind(this),
- selectable: true,
- clicked: this.onClick.bind(this),
- });
- }
-
- getSearchText(selectedObject) {
- return selectedObject.id ? selectedObject.text : '';
- }
-
- getData(term, callback) {
- if (!term) {
- const contents = this.getCategoryContents();
- if (contents) {
- const glDropdownInstance = this.searchInput.data('glDropdown');
-
- if (glDropdownInstance) {
- glDropdownInstance.filter.options.callback(contents);
- }
- this.enableDropdown();
- }
- return;
- }
-
- const options = this.scopedSearchOptions(term);
-
- callback(options);
-
- this.highlightFirstRow();
- this.setScrollFade();
- }
-
- // Add option to proceed with the search for each
- // scope that is currently available, namely:
- //
- // - Search in this project
- // - Search in this group (or project's group)
- // - Search in all GitLab
- scopedSearchOptions(term) {
- const icon = spriteIcon('search', 's16 inline-search-icon');
- const projectId = this.projectInputEl.val();
- const groupId = this.groupInputEl.val();
- const options = [];
-
- if (projectId) {
- const projectOptions = gl.projectOptions[getProjectSlug()];
- const url = groupId
- ? `${gon.relative_url_root}/search?search=${term}&project_id=${projectId}&group_id=${groupId}`
- : `${gon.relative_url_root}/search?search=${term}&project_id=${projectId}`;
-
- options.push({
- icon,
- text: term,
- template: sprintf(
- s__(`SearchAutocomplete|in project %{projectName}`),
- {
- projectName: `<i>${projectOptions.name}</i>`,
- },
- false,
- ),
- url,
- });
- }
-
- if (groupId) {
- const groupOptions = gl.groupOptions[getGroupSlug()];
- options.push({
- icon,
- text: term,
- template: sprintf(
- s__(`SearchAutocomplete|in group %{groupName}`),
- {
- groupName: `<i>${groupOptions.name}</i>`,
- },
- false,
- ),
- url: `${gon.relative_url_root}/search?search=${term}&group_id=${groupId}`,
- });
- }
-
- options.push({
- icon,
- text: term,
- template: s__('SearchAutocomplete|in all GitLab'),
- url: `${gon.relative_url_root}/search?search=${term}`,
- });
-
- return options;
- }
-
- serializeState() {
- return {
- // Search Criteria
- search_project_id: this.projectInputEl.val(),
- group_id: this.groupInputEl.val(),
- search_code: this.searchCodeInputEl.val(),
- repository_ref: this.repositoryInputEl.val(),
- scope: this.scopeInputEl.val(),
- };
- }
-
- bindEvents() {
- this.searchInput.on('input', this.onSearchInputChange);
- this.searchInput.on('keyup', this.onSearchInputKeyUp);
- this.searchInput.on('focus', this.onSearchInputFocus);
- this.searchInput.on('blur', this.onSearchInputBlur);
- this.clearInput.on('click', this.onClearInputClick);
- this.dropdownContent.on('scroll', throttle(this.setScrollFade, 250));
-
- this.searchInput.on('click', e => {
- e.stopPropagation();
- });
- }
-
- enableDropdown() {
- this.setScrollFade();
-
- // No need to enable anything if user is not logged in
- if (!gon.current_user_id) {
- return;
- }
-
- // If the dropdown is closed, we'll open it
- if (!this.dropdown.hasClass('show')) {
- this.loadingSuggestions = false;
- this.dropdownToggle.dropdown('toggle');
- return this.searchInput.removeClass('js-autocomplete-disabled');
- }
- }
-
- onSearchInputChange() {
- this.enableDropdown();
- }
-
- onSearchInputKeyUp(e) {
- switch (e.keyCode) {
- case KEYCODE.ESCAPE:
- this.restoreOriginalState();
- break;
- case KEYCODE.ENTER:
- this.disableDropdown();
- break;
- default:
- }
- this.wrap.toggleClass('has-value', Boolean(e.target.value));
- }
-
- onSearchInputFocus() {
- this.isFocused = true;
- this.wrap.addClass('search-active');
- if (this.getValue() === '') {
- return this.getData();
- }
- }
-
- getValue() {
- return this.searchInput.val();
- }
-
- onClearInputClick(e) {
- e.preventDefault();
- this.wrap.toggleClass('has-value', Boolean(e.target.value));
- return this.searchInput.val('').focus();
- }
-
- onSearchInputBlur() {
- this.isFocused = false;
- this.wrap.removeClass('search-active');
- // If input is blank then restore state
- if (this.searchInput.val() === '') {
- this.restoreOriginalState();
- }
- this.dropdownMenu.removeClass('show');
- }
-
- restoreOriginalState() {
- const inputs = Object.keys(this.originalState);
- for (let i = 0, len = inputs.length; i < len; i += 1) {
- const input = inputs[i];
- this.getElement(`#${input}`).val(this.originalState[input]);
- }
- }
-
- resetSearchState() {
- const inputs = Object.keys(this.originalState);
- const results = [];
- for (let i = 0, len = inputs.length; i < len; i += 1) {
- const input = inputs[i];
- results.push(this.getElement(`#${input}`).val(''));
- }
- return results;
- }
-
- disableDropdown() {
- if (!this.searchInput.hasClass('js-autocomplete-disabled') && this.dropdown.hasClass('show')) {
- this.searchInput.addClass('js-autocomplete-disabled');
- this.dropdownToggle.dropdown('toggle');
- this.restoreMenu();
- }
- }
-
- restoreMenu() {
- const html = `<ul><li class="dropdown-menu-empty-item"><a>${__('Loading...')}</a></li></ul>`;
- return this.dropdownContent.html(html);
- }
-
- onClick(item, $el, e) {
- if (window.location.pathname.indexOf(item.url) !== -1) {
- if (!e.metaKey) e.preventDefault();
- $el.removeClass('is-active');
- this.disableDropdown();
- return this.searchInput.val('').focus();
- }
- }
-
- highlightFirstRow() {
- this.searchInput.data('glDropdown').highlightRowAtIndex(null, 0);
- }
-
- getCategoryContents() {
- const userName = gon.current_username;
- const { projectOptions, groupOptions, dashboardOptions } = gl;
-
- // Get options
- let options;
- if (isInProjectPage() && projectOptions) {
- options = projectOptions[getProjectSlug()];
- } else if (isInGroupsPage() && groupOptions) {
- options = groupOptions[getGroupSlug()];
- } else if (dashboardOptions) {
- options = dashboardOptions;
- }
-
- const { issuesPath, mrPath, name, issuesDisabled } = options;
- const baseItems = [];
-
- if (name) {
- baseItems.push({
- type: 'header',
- content: `${name}`,
- });
- }
-
- const issueItems = [
- {
- text: s__('SearchAutocomplete|Issues assigned to me'),
- url: `${issuesPath}/?assignee_username=${userName}`,
- },
- {
- text: s__("SearchAutocomplete|Issues I've created"),
- url: `${issuesPath}/?author_username=${userName}`,
- },
- ];
- const mergeRequestItems = [
- {
- text: s__('SearchAutocomplete|Merge requests assigned to me'),
- url: `${mrPath}/?assignee_username=${userName}`,
- },
- {
- text: s__("SearchAutocomplete|Merge requests I've created"),
- url: `${mrPath}/?author_username=${userName}`,
- },
- ];
-
- let items;
- if (issuesDisabled) {
- items = baseItems.concat(mergeRequestItems);
- } else {
- items = baseItems.concat(...issueItems, ...mergeRequestItems);
- }
- return items;
- }
-
- isScrolledUp() {
- const el = this.dropdownContent[0];
- const currentPosition = this.contentClientHeight + el.scrollTop;
-
- return currentPosition < this.maxPosition;
- }
-
- initScrollFade() {
- const el = this.dropdownContent[0];
- this.scrollFadeInitialized = true;
-
- this.contentClientHeight = el.clientHeight;
- this.maxPosition = el.scrollHeight;
- this.dropdownMenu.addClass('dropdown-content-faded-mask');
- }
-
- setScrollFade() {
- this.initScrollFade();
-
- this.dropdownMenu.toggleClass('fade-out', !this.isScrolledUp());
- }
-}
-
-export default function initGlobalSearchInput(opts) {
- return new GlobalSearchInput(opts);
-}
diff --git a/app/assets/javascripts/graphql_shared/fragments/user.fragment.graphql b/app/assets/javascripts/graphql_shared/fragments/user.fragment.graphql
new file mode 100644
index 00000000000..096bb77ee49
--- /dev/null
+++ b/app/assets/javascripts/graphql_shared/fragments/user.fragment.graphql
@@ -0,0 +1,7 @@
+fragment User on User {
+ id
+ avatarUrl
+ name
+ username
+ webUrl
+}
diff --git a/app/assets/javascripts/groups/components/group_item.vue b/app/assets/javascripts/groups/components/group_item.vue
index 6b9748bb725..be90ba12678 100644
--- a/app/assets/javascripts/groups/components/group_item.vue
+++ b/app/assets/javascripts/groups/components/group_item.vue
@@ -104,7 +104,7 @@ export default {
:class="{ 'project-row-contents': !isGroup }"
class="group-row-contents d-flex align-items-center py-2 pr-3"
>
- <div class="folder-toggle-wrap append-right-4 d-flex align-items-center">
+ <div class="folder-toggle-wrap gl-mr-2 d-flex align-items-center">
<item-caret :is-group-open="group.isOpen" />
<item-type-icon :item-type="group.type" :is-group-open="group.isOpen" />
</div>
@@ -140,7 +140,7 @@ export default {
<item-stats-value
:icon-name="visibilityIcon"
:title="visibilityTooltip"
- css-class="item-visibility d-inline-flex align-items-center gl-mt-3 append-right-4 text-secondary"
+ css-class="item-visibility d-inline-flex align-items-center gl-mt-3 gl-mr-2 text-secondary"
/>
<span v-if="group.permission" class="user-access-role gl-mt-3">
{{ group.permission }}
diff --git a/app/assets/javascripts/groups/components/groups.vue b/app/assets/javascripts/groups/components/groups.vue
index c7acc21378b..c7713cbfafc 100644
--- a/app/assets/javascripts/groups/components/groups.vue
+++ b/app/assets/javascripts/groups/components/groups.vue
@@ -49,7 +49,7 @@ export default {
<pagination-links
:change="change"
:page-info="pageInfo"
- class="d-flex justify-content-center prepend-top-default"
+ class="d-flex justify-content-center gl-mt-3"
/>
</template>
</div>
diff --git a/app/assets/javascripts/groups/components/item_caret.vue b/app/assets/javascripts/groups/components/item_caret.vue
index 18ea4819878..cd3e3de4cb4 100644
--- a/app/assets/javascripts/groups/components/item_caret.vue
+++ b/app/assets/javascripts/groups/components/item_caret.vue
@@ -21,5 +21,5 @@ export default {
</script>
<template>
- <span class="folder-caret append-right-4"> <icon :size="10" :name="iconClass" /> </span>
+ <span class="folder-caret gl-mr-2"> <icon :size="10" :name="iconClass" /> </span>
</template>
diff --git a/app/assets/javascripts/header.js b/app/assets/javascripts/header.js
index d151cecf5be..3f9163e924d 100644
--- a/app/assets/javascripts/header.js
+++ b/app/assets/javascripts/header.js
@@ -16,10 +16,11 @@ import Tracking from '~/tracking';
*/
export default function initTodoToggle() {
$(document).on('todo:toggle', (e, count) => {
+ const updatedCount = count || e?.detail?.count || 0;
const $todoPendingCount = $('.todos-count');
- $todoPendingCount.text(highCountTrim(count));
- $todoPendingCount.toggleClass('hidden', count === 0);
+ $todoPendingCount.text(highCountTrim(updatedCount));
+ $todoPendingCount.toggleClass('hidden', updatedCount === 0);
});
}
diff --git a/app/assets/javascripts/helpers/event_hub_factory.js b/app/assets/javascripts/helpers/event_hub_factory.js
index 4d7f7550a94..a9c301e3a93 100644
--- a/app/assets/javascripts/helpers/event_hub_factory.js
+++ b/app/assets/javascripts/helpers/event_hub_factory.js
@@ -1,20 +1,101 @@
-import mitt from 'mitt';
+/**
+ * An event hub with a Vue instance like API
+ *
+ * NOTE: There's an [issue open][4] to eventually remove this when some
+ * coupling in our codebase has been fixed.
+ *
+ * NOTE: This is a derivative work from [mitt][1] v1.2.0 which is licensed by
+ * [MIT License][2] © [Jason Miller][3]
+ *
+ * [1]: https://github.com/developit/mitt
+ * [2]: https://opensource.org/licenses/MIT
+ * [3]: https://jasonformat.com/
+ * [4]: https://gitlab.com/gitlab-org/gitlab/-/issues/223864
+ */
+class EventHub {
+ constructor() {
+ this.$_all = new Map();
+ }
-export default () => {
- const emitter = mitt();
+ dispose() {
+ this.$_all.clear();
+ }
+
+ /**
+ * Register an event handler for the given type.
+ *
+ * @param {string|symbol} type Type of event to listen for
+ * @param {Function} handler Function to call in response to given event
+ */
+ $on(type, handler) {
+ const handlers = this.$_all.get(type);
+ const added = handlers && handlers.push(handler);
+
+ if (!added) {
+ this.$_all.set(type, [handler]);
+ }
+ }
+
+ /**
+ * Remove an event handler or all handlers for the given type.
+ *
+ * @param {string|symbol} type Type of event to unregister `handler`
+ * @param {Function} handler Handler function to remove
+ */
+ $off(type, handler) {
+ const handlers = this.$_all.get(type) || [];
- emitter.once = (event, handler) => {
- const wrappedHandler = evt => {
- handler(evt);
- emitter.off(event, wrappedHandler);
+ const newHandlers = handler ? handlers.filter(x => x !== handler) : [];
+
+ if (newHandlers.length) {
+ this.$_all.set(type, newHandlers);
+ } else {
+ this.$_all.delete(type);
+ }
+ }
+
+ /**
+ * Add an event listener to type but only trigger it once
+ *
+ * @param {string|symbol} type Type of event to listen for
+ * @param {Function} handler Handler function to call in response to event
+ */
+ $once(type, handler) {
+ const wrapHandler = (...args) => {
+ this.$off(type, wrapHandler);
+ handler(...args);
};
- emitter.on(event, wrappedHandler);
- };
+ this.$on(type, wrapHandler);
+ }
- emitter.$on = emitter.on;
- emitter.$once = emitter.once;
- emitter.$off = emitter.off;
- emitter.$emit = emitter.emit;
+ /**
+ * Invoke all handlers for the given type.
+ *
+ * @param {string|symbol} type The event type to invoke
+ * @param {Any} [evt] Any value passed to each handler
+ */
+ $emit(type, ...args) {
+ const handlers = this.$_all.get(type) || [];
- return emitter;
+ handlers.forEach(handler => {
+ handler(...args);
+ });
+ }
+}
+
+/**
+ * Return a Vue like event hub
+ *
+ * - $on
+ * - $off
+ * - $once
+ * - $emit
+ *
+ * Please note, this was once implemented with `mitt`, but since then has been reverted
+ * because of some API issues. https://gitlab.com/gitlab-org/gitlab/-/merge_requests/35074
+ *
+ * We'd like to shy away from using a full fledged Vue instance from this in the future.
+ */
+export default () => {
+ return new EventHub();
};
diff --git a/app/assets/javascripts/helpers/monitor_helper.js b/app/assets/javascripts/helpers/monitor_helper.js
index 94a0d38f05f..5f85ee58779 100644
--- a/app/assets/javascripts/helpers/monitor_helper.js
+++ b/app/assets/javascripts/helpers/monitor_helper.js
@@ -65,18 +65,10 @@ const getSeriesLabel = (queryLabel, metricAttributes) => {
*/
// eslint-disable-next-line import/prefer-default-export
export const makeDataSeries = (queryResults, defaultConfig) =>
- queryResults
- .map(result => {
- // NaN values may disrupt avg., max. & min. calculations in the legend, filter them out
- const data = result.values.filter(([, value]) => !Number.isNaN(value));
- if (!data.length) {
- return null;
- }
- const series = { data };
- return {
- ...defaultConfig,
- ...series,
- name: getSeriesLabel(defaultConfig.name, result.metric),
- };
- })
- .filter(series => series !== null);
+ queryResults.map(result => {
+ return {
+ ...defaultConfig,
+ data: result.values,
+ name: getSeriesLabel(defaultConfig.name, result.metric),
+ };
+ });
diff --git a/app/assets/javascripts/ide/components/branches/item.vue b/app/assets/javascripts/ide/components/branches/item.vue
index e7f4cd796b5..49744d573da 100644
--- a/app/assets/javascripts/ide/components/branches/item.vue
+++ b/app/assets/javascripts/ide/components/branches/item.vue
@@ -33,7 +33,7 @@ export default {
<template>
<a :href="branchHref" class="btn-link d-flex align-items-center">
- <span class="d-flex append-right-default ide-search-list-current-icon">
+ <span class="d-flex gl-mr-3 ide-search-list-current-icon">
<icon v-if="isActive" :size="18" name="mobile-issue-close" />
</span>
<span>
diff --git a/app/assets/javascripts/ide/components/commit_sidebar/actions.vue b/app/assets/javascripts/ide/components/commit_sidebar/actions.vue
index 6c563776533..407e4c57cd8 100644
--- a/app/assets/javascripts/ide/components/commit_sidebar/actions.vue
+++ b/app/assets/javascripts/ide/components/commit_sidebar/actions.vue
@@ -70,7 +70,7 @@ export default {
</script>
<template>
- <div class="append-bottom-15 ide-commit-options">
+ <div class="gl-mb-5 ide-commit-options">
<radio-group
:value="$options.commitToCurrentBranch"
:disabled="!canPushToBranch"
diff --git a/app/assets/javascripts/ide/components/commit_sidebar/empty_state.vue b/app/assets/javascripts/ide/components/commit_sidebar/empty_state.vue
index a13ca0cd138..3ffbcbf99e8 100644
--- a/app/assets/javascripts/ide/components/commit_sidebar/empty_state.vue
+++ b/app/assets/javascripts/ide/components/commit_sidebar/empty_state.vue
@@ -12,7 +12,7 @@ export default {
<div v-if="!lastCommitMsg" class="multi-file-commit-panel-section ide-commit-empty-state">
<div class="ide-commit-empty-state-container">
<div class="svg-content svg-80"><img :src="noChangesStateSvgPath" /></div>
- <div class="append-right-default prepend-left-default">
+ <div class="gl-mr-3 gl-ml-3">
<div class="text-content text-center">
<h4>{{ __('No changes') }}</h4>
<p>{{ __('Edit files in the editor and commit changes here') }}</p>
diff --git a/app/assets/javascripts/ide/components/commit_sidebar/list_collapsed.vue b/app/assets/javascripts/ide/components/commit_sidebar/list_collapsed.vue
index b6fc567f8cc..03304337839 100644
--- a/app/assets/javascripts/ide/components/commit_sidebar/list_collapsed.vue
+++ b/app/assets/javascripts/ide/components/commit_sidebar/list_collapsed.vue
@@ -75,7 +75,7 @@ export default {
:title="titleTooltip"
data-container="body"
data-placement="left"
- class="append-bottom-15"
+ class="gl-mb-5"
>
<icon v-once :name="iconName" :size="18" />
</div>
diff --git a/app/assets/javascripts/ide/components/commit_sidebar/message_field.vue b/app/assets/javascripts/ide/components/commit_sidebar/message_field.vue
index 6b0aa5b2b2b..b37c7280a30 100644
--- a/app/assets/javascripts/ide/components/commit_sidebar/message_field.vue
+++ b/app/assets/javascripts/ide/components/commit_sidebar/message_field.vue
@@ -83,7 +83,7 @@ export default {
<ul class="nav-links">
<li>
{{ __('Commit Message') }}
- <span v-popover="$options.popoverOptions" class="form-text text-muted prepend-left-10">
+ <span v-popover="$options.popoverOptions" class="form-text text-muted gl-ml-3">
<icon name="question" />
</span>
</li>
diff --git a/app/assets/javascripts/ide/components/commit_sidebar/new_merge_request_option.vue b/app/assets/javascripts/ide/components/commit_sidebar/new_merge_request_option.vue
index 0812599c25c..cdf49866982 100644
--- a/app/assets/javascripts/ide/components/commit_sidebar/new_merge_request_option.vue
+++ b/app/assets/javascripts/ide/components/commit_sidebar/new_merge_request_option.vue
@@ -44,7 +44,7 @@ export default {
data-qa-selector="start_new_mr_checkbox"
@change="toggleShouldCreateMR"
/>
- <span class="prepend-left-10 ide-option-label">
+ <span class="gl-ml-3 ide-option-label">
{{ __('Start a new merge request') }}
</span>
</label>
diff --git a/app/assets/javascripts/ide/components/commit_sidebar/radio_group.vue b/app/assets/javascripts/ide/components/commit_sidebar/radio_group.vue
index a9591805261..aed7b792902 100644
--- a/app/assets/javascripts/ide/components/commit_sidebar/radio_group.vue
+++ b/app/assets/javascripts/ide/components/commit_sidebar/radio_group.vue
@@ -66,7 +66,7 @@ export default {
name="commit-action"
@change="updateCommitAction($event.target.value)"
/>
- <span class="prepend-left-10">
+ <span class="gl-ml-3">
<span v-if="label" class="ide-option-label"> {{ label }} </span> <slot v-else></slot>
</span>
</label>
diff --git a/app/assets/javascripts/ide/components/commit_sidebar/success_message.vue b/app/assets/javascripts/ide/components/commit_sidebar/success_message.vue
index 137f8bb18c7..327b0b8172f 100644
--- a/app/assets/javascripts/ide/components/commit_sidebar/success_message.vue
+++ b/app/assets/javascripts/ide/components/commit_sidebar/success_message.vue
@@ -13,7 +13,7 @@ export default {
<div class="svg-content svg-80">
<img :src="committedStateSvgPath" :alt="s__('IDE|Successful commit')" />
</div>
- <div class="append-right-default prepend-left-default">
+ <div class="gl-mr-3 gl-ml-3">
<div class="text-content text-center">
<h4>{{ __('All changes are committed') }}</h4>
<p v-html="lastCommitMsg"></p>
diff --git a/app/assets/javascripts/ide/components/file_row_extra.vue b/app/assets/javascripts/ide/components/file_row_extra.vue
index 51509cd5fe6..f7cf7a5b251 100644
--- a/app/assets/javascripts/ide/components/file_row_extra.vue
+++ b/app/assets/javascripts/ide/components/file_row_extra.vue
@@ -76,7 +76,7 @@ export default {
data-container="body"
data-placement="right"
name="file-modified"
- class="prepend-left-5 ide-file-modified"
+ class="gl-ml-2 ide-file-modified"
/>
</span>
<changed-file-icon
diff --git a/app/assets/javascripts/ide/components/file_templates/bar.vue b/app/assets/javascripts/ide/components/file_templates/bar.vue
index d459e3b43d3..b6a57d1b6e6 100644
--- a/app/assets/javascripts/ide/components/file_templates/bar.vue
+++ b/app/assets/javascripts/ide/components/file_templates/bar.vue
@@ -48,7 +48,7 @@ export default {
<template>
<div class="d-flex align-items-center ide-file-templates qa-file-templates-bar">
- <strong class="append-right-default"> {{ __('File templates') }} </strong>
+ <strong class="gl-mr-3"> {{ __('File templates') }} </strong>
<dropdown
:data="templateTypes"
:label="selectedTemplateType.name || __('Choose a type...')"
diff --git a/app/assets/javascripts/ide/components/ide.vue b/app/assets/javascripts/ide/components/ide.vue
index e9f84eb8648..55b3eaf9737 100644
--- a/app/assets/javascripts/ide/components/ide.vue
+++ b/app/assets/javascripts/ide/components/ide.vue
@@ -1,6 +1,6 @@
<script>
import { mapActions, mapGetters, mapState } from 'vuex';
-import { GlDeprecatedButton, GlLoadingIcon } from '@gitlab/ui';
+import { GlButton, GlLoadingIcon } from '@gitlab/ui';
import { __ } from '~/locale';
import { modalTypes } from '../constants';
import FindFile from '~/vue_shared/components/file_finder/index.vue';
@@ -24,7 +24,7 @@ export default {
FindFile,
ErrorMessage,
CommitEditorHeader,
- GlDeprecatedButton,
+ GlButton,
GlLoadingIcon,
RightPane,
},
@@ -121,15 +121,16 @@ export default {
)
}}
</p>
- <gl-deprecated-button
+ <gl-button
variant="success"
+ category="primary"
:title="__('New file')"
:aria-label="__('New file')"
data-qa-selector="first_file_button"
@click="createNewFile()"
>
{{ __('New file') }}
- </gl-deprecated-button>
+ </gl-button>
</template>
<gl-loading-icon v-else-if="!currentTree || currentTree.loading" size="md" />
<p v-else>
diff --git a/app/assets/javascripts/ide/components/ide_review.vue b/app/assets/javascripts/ide/components/ide_review.vue
index 62dbfea2088..95348711e1d 100644
--- a/app/assets/javascripts/ide/components/ide_review.vue
+++ b/app/assets/javascripts/ide/components/ide_review.vue
@@ -53,7 +53,7 @@ export default {
@click="updateViewer"
/>
</div>
- <div class="prepend-top-5 ide-review-sub-header">
+ <div class="gl-mt-2 ide-review-sub-header">
<template v-if="showLatestChangesText">
{{ __('Latest changes') }}
</template>
diff --git a/app/assets/javascripts/ide/components/ide_status_list.vue b/app/assets/javascripts/ide/components/ide_status_list.vue
index 92d25709bd5..1354fdc3d98 100644
--- a/app/assets/javascripts/ide/components/ide_status_list.vue
+++ b/app/assets/javascripts/ide/components/ide_status_list.vue
@@ -1,12 +1,17 @@
<script>
import { mapGetters } from 'vuex';
+import { GlLink, GlTooltipDirective } from '@gitlab/ui';
import TerminalSyncStatusSafe from './terminal_sync/terminal_sync_status_safe.vue';
import { getFileEOL } from '../utils';
export default {
components: {
+ GlLink,
TerminalSyncStatusSafe,
},
+ directives: {
+ GlTooltip: GlTooltipDirective,
+ },
computed: {
...mapGetters(['activeFile']),
activeFileEOL() {
@@ -19,12 +24,14 @@ export default {
<template>
<div class="ide-status-list d-flex">
<template v-if="activeFile">
- <div class="ide-status-file">{{ activeFile.name }}</div>
- <div class="ide-status-file">{{ activeFileEOL }}</div>
- <div v-if="!activeFile.binary" class="ide-status-file">
- {{ activeFile.editorRow }}:{{ activeFile.editorColumn }}
+ <div>
+ <gl-link v-gl-tooltip.hover :href="activeFile.permalink" :title="__('Open in file view')">
+ {{ activeFile.name }}
+ </gl-link>
</div>
- <div class="ide-status-file">{{ activeFile.fileLanguage }}</div>
+ <div>{{ activeFileEOL }}</div>
+ <div v-if="!activeFile.binary">{{ activeFile.editorRow }}:{{ activeFile.editorColumn }}</div>
+ <div>{{ activeFile.fileLanguage }}</div>
</template>
<terminal-sync-status-safe />
</div>
diff --git a/app/assets/javascripts/ide/components/jobs/item.vue b/app/assets/javascripts/ide/components/jobs/item.vue
index be8bf77bba0..db3630bc1d1 100644
--- a/app/assets/javascripts/ide/components/jobs/item.vue
+++ b/app/assets/javascripts/ide/components/jobs/item.vue
@@ -26,7 +26,7 @@ export default {
<template>
<div class="ide-job-item">
- <job-description :job="job" class="append-right-default" />
+ <job-description :job="job" class="gl-mr-3" />
<div class="ml-auto align-self-center">
<button v-if="job.started" type="button" class="btn btn-default btn-sm" @click="clickViewLog">
{{ __('View log') }}
diff --git a/app/assets/javascripts/ide/components/jobs/list.vue b/app/assets/javascripts/ide/components/jobs/list.vue
index b97b7289886..4e0912f3f44 100644
--- a/app/assets/javascripts/ide/components/jobs/list.vue
+++ b/app/assets/javascripts/ide/components/jobs/list.vue
@@ -26,7 +26,7 @@ export default {
<template>
<div>
- <gl-loading-icon v-if="loading && !stages.length" size="lg" class="prepend-top-default" />
+ <gl-loading-icon v-if="loading && !stages.length" size="lg" class="gl-mt-3" />
<template v-else>
<stage
v-for="stage in stages"
diff --git a/app/assets/javascripts/ide/components/jobs/stage.vue b/app/assets/javascripts/ide/components/jobs/stage.vue
index 169a948c2da..75441e8c1c8 100644
--- a/app/assets/javascripts/ide/components/jobs/stage.vue
+++ b/app/assets/javascripts/ide/components/jobs/stage.vue
@@ -56,7 +56,7 @@ export default {
</script>
<template>
- <div class="ide-stage card prepend-top-default">
+ <div class="ide-stage card gl-mt-3">
<div
ref="cardHeader"
:class="{
diff --git a/app/assets/javascripts/ide/components/merge_requests/item.vue b/app/assets/javascripts/ide/components/merge_requests/item.vue
index 3f060392686..8b7b8d5a91c 100644
--- a/app/assets/javascripts/ide/components/merge_requests/item.vue
+++ b/app/assets/javascripts/ide/components/merge_requests/item.vue
@@ -40,7 +40,7 @@ export default {
<template>
<a :href="mergeRequestHref" class="btn-link d-flex align-items-center">
- <span class="d-flex append-right-default ide-search-list-current-icon">
+ <span class="d-flex gl-mr-3 ide-search-list-current-icon">
<icon v-if="isActive" :size="18" name="mobile-issue-close" />
</span>
<span>
diff --git a/app/assets/javascripts/ide/components/merge_requests/list.vue b/app/assets/javascripts/ide/components/merge_requests/list.vue
index bf2a33be653..af45d88b84a 100644
--- a/app/assets/javascripts/ide/components/merge_requests/list.vue
+++ b/app/assets/javascripts/ide/components/merge_requests/list.vue
@@ -102,7 +102,7 @@ export default {
class="btn-link d-flex align-items-center"
@click.stop="setSearchType(searchType)"
>
- <span class="d-flex append-right-default ide-search-list-current-icon">
+ <span class="d-flex gl-mr-3 ide-search-list-current-icon">
<icon :size="18" name="search" />
</span>
<span>{{ searchType.label }}</span>
diff --git a/app/assets/javascripts/ide/components/new_dropdown/index.vue b/app/assets/javascripts/ide/components/new_dropdown/index.vue
index 2798ede5341..b656e35f150 100644
--- a/app/assets/javascripts/ide/components/new_dropdown/index.vue
+++ b/app/assets/javascripts/ide/components/new_dropdown/index.vue
@@ -64,6 +64,7 @@ export default {
:aria-label="__('Create new file or directory')"
type="button"
class="rounded border-0 d-flex ide-entry-dropdown-toggle"
+ data-qa-selector="dropdown_button"
@click.stop="openDropdown()"
>
<icon name="ellipsis_v" /> <icon name="chevron-down" />
@@ -97,6 +98,7 @@ export default {
class="d-flex"
icon="pencil"
icon-classes="mr-2"
+ data-qa-selector="rename_move_button"
@click="createNewItem($options.modalTypes.rename)"
/>
</li>
diff --git a/app/assets/javascripts/ide/components/new_dropdown/modal.vue b/app/assets/javascripts/ide/components/new_dropdown/modal.vue
index 586d6867ab4..fe0167942b8 100644
--- a/app/assets/javascripts/ide/components/new_dropdown/modal.vue
+++ b/app/assets/javascripts/ide/components/new_dropdown/modal.vue
@@ -154,10 +154,7 @@ export default {
data-qa-selector="file_name_field"
:placeholder="placeholder"
/>
- <ul
- v-if="isCreatingNewFile"
- class="file-templates prepend-top-default list-inline qa-template-list"
- >
+ <ul v-if="isCreatingNewFile" class="file-templates gl-mt-3 list-inline qa-template-list">
<li v-for="(template, index) in templateTypes" :key="index" class="list-inline-item">
<button
type="button"
diff --git a/app/assets/javascripts/ide/components/pipelines/list.vue b/app/assets/javascripts/ide/components/pipelines/list.vue
index 6958a5d2526..6038e92f254 100644
--- a/app/assets/javascripts/ide/components/pipelines/list.vue
+++ b/app/assets/javascripts/ide/components/pipelines/list.vue
@@ -7,7 +7,7 @@ import Icon from '../../../vue_shared/components/icon.vue';
import CiIcon from '../../../vue_shared/components/ci_icon.vue';
import Tabs from '../../../vue_shared/components/tabs/tabs';
import Tab from '../../../vue_shared/components/tabs/tab.vue';
-import EmptyState from '../../../pipelines/components/empty_state.vue';
+import EmptyState from '../../../pipelines/components/pipelines_list/empty_state.vue';
import JobsList from '../jobs/list.vue';
import IDEServices from '~/ide/services';
@@ -59,7 +59,7 @@ export default {
<template>
<div class="ide-pipeline">
- <gl-loading-icon v-if="showLoadingIcon" size="lg" class="prepend-top-default" />
+ <gl-loading-icon v-if="showLoadingIcon" size="lg" class="gl-mt-3" />
<template v-else-if="hasLoadedPipeline">
<header v-if="latestPipeline" class="ide-tree-header ide-pipeline-header">
<ci-icon :status="latestPipeline.details.status" :size="24" class="d-flex" />
diff --git a/app/assets/javascripts/ide/components/repo_editor.vue b/app/assets/javascripts/ide/components/repo_editor.vue
index a7646083428..ac445a1d9f1 100644
--- a/app/assets/javascripts/ide/components/repo_editor.vue
+++ b/app/assets/javascripts/ide/components/repo_editor.vue
@@ -185,7 +185,6 @@ export default {
'setFileLanguage',
'setEditorPosition',
'setFileViewMode',
- 'updateViewer',
'removePendingTab',
'triggerFilesChange',
'addTempImage',
@@ -241,7 +240,7 @@ export default {
});
},
setupEditor() {
- if (!this.file || !this.editor.instance) return;
+ if (!this.file || !this.editor.instance || this.file.loading) return;
const head = this.getStagedFile(this.file.path);
diff --git a/app/assets/javascripts/ide/components/terminal/empty_state.vue b/app/assets/javascripts/ide/components/terminal/empty_state.vue
index 9841f1ece48..5dd12e62820 100644
--- a/app/assets/javascripts/ide/components/terminal/empty_state.vue
+++ b/app/assets/javascripts/ide/components/terminal/empty_state.vue
@@ -43,7 +43,7 @@ export default {
<div class="text-center p-3">
<div v-if="illustrationPath" class="svg-content svg-130"><img :src="illustrationPath" /></div>
<h4>{{ __('Web Terminal') }}</h4>
- <gl-loading-icon v-if="isLoading" size="lg" class="prepend-top-default" />
+ <gl-loading-icon v-if="isLoading" size="lg" class="gl-mt-3" />
<template v-else>
<p>{{ __('Run tests against your code live using the Web Terminal') }}</p>
<p>
diff --git a/app/assets/javascripts/ide/lib/editor.js b/app/assets/javascripts/ide/lib/editor.js
index 4dfc27117c0..6e90968f008 100644
--- a/app/assets/javascripts/ide/lib/editor.js
+++ b/app/assets/javascripts/ide/lib/editor.js
@@ -8,9 +8,10 @@ import ModelManager from './common/model_manager';
import { editorOptions, defaultEditorOptions, defaultDiffEditorOptions } from './editor_options';
import { themes } from './themes';
import languages from './languages';
+import schemas from './schemas';
import keymap from './keymap.json';
import { clearDomElement } from '~/editor/utils';
-import { registerLanguages } from '../utils';
+import { registerLanguages, registerSchemas } from '../utils';
function setupThemes() {
themes.forEach(theme => {
@@ -45,6 +46,10 @@ export default class Editor {
setupThemes();
registerLanguages(...languages);
+ if (gon.features?.schemaLinting) {
+ registerSchemas(...schemas);
+ }
+
this.debouncedUpdate = debounce(() => {
this.updateDimensions();
}, 200);
diff --git a/app/assets/javascripts/ide/lib/schemas/index.js b/app/assets/javascripts/ide/lib/schemas/index.js
new file mode 100644
index 00000000000..38a2f81921b
--- /dev/null
+++ b/app/assets/javascripts/ide/lib/schemas/index.js
@@ -0,0 +1,4 @@
+import json from './json';
+import yaml from './yaml';
+
+export default [json, yaml];
diff --git a/app/assets/javascripts/ide/lib/schemas/json/index.js b/app/assets/javascripts/ide/lib/schemas/json/index.js
new file mode 100644
index 00000000000..900d5442bec
--- /dev/null
+++ b/app/assets/javascripts/ide/lib/schemas/json/index.js
@@ -0,0 +1,8 @@
+export default {
+ language: 'json',
+ options: {
+ validate: true,
+ enableSchemaRequest: true,
+ schemas: [],
+ },
+};
diff --git a/app/assets/javascripts/ide/lib/schemas/yaml/gitlab_ci.js b/app/assets/javascripts/ide/lib/schemas/yaml/gitlab_ci.js
new file mode 100644
index 00000000000..af20744abb3
--- /dev/null
+++ b/app/assets/javascripts/ide/lib/schemas/yaml/gitlab_ci.js
@@ -0,0 +1,4 @@
+export default {
+ uri: 'https://json.schemastore.org/gitlab-ci',
+ fileMatch: ['*.gitlab-ci.yml'],
+};
diff --git a/app/assets/javascripts/ide/lib/schemas/yaml/index.js b/app/assets/javascripts/ide/lib/schemas/yaml/index.js
new file mode 100644
index 00000000000..e3fc406df4b
--- /dev/null
+++ b/app/assets/javascripts/ide/lib/schemas/yaml/index.js
@@ -0,0 +1,12 @@
+import gitlabCi from './gitlab_ci';
+
+export default {
+ language: 'yaml',
+ options: {
+ validate: true,
+ enableSchemaRequest: true,
+ hover: true,
+ completion: true,
+ schemas: [gitlabCi],
+ },
+};
diff --git a/app/assets/javascripts/ide/queries/getUserPermissions.query.graphql b/app/assets/javascripts/ide/queries/getUserPermissions.query.graphql
index 2c9013ffa9c..f0b50793226 100644
--- a/app/assets/javascripts/ide/queries/getUserPermissions.query.graphql
+++ b/app/assets/javascripts/ide/queries/getUserPermissions.query.graphql
@@ -1,8 +1,8 @@
query getUserPermissions($projectPath: ID!) {
project(fullPath: $projectPath) {
userPermissions {
- createMergeRequestIn,
- readMergeRequest,
+ createMergeRequestIn
+ readMergeRequest
pushCode
}
}
diff --git a/app/assets/javascripts/ide/services/gql.js b/app/assets/javascripts/ide/services/gql.js
index 8a7f27328ba..211cc78bd99 100644
--- a/app/assets/javascripts/ide/services/gql.js
+++ b/app/assets/javascripts/ide/services/gql.js
@@ -1,8 +1,21 @@
+import { memoize } from 'lodash';
import createGqClient, { fetchPolicies } from '~/lib/graphql';
-export default createGqClient(
- {},
- {
- fetchPolicy: fetchPolicies.NO_CACHE,
- },
+/**
+ * Returns a memoized client
+ *
+ * We defer creating the client so that importing this module does not cause any side-effects.
+ * Creating the client immediately caused issues with miragejs where the gql client uses the
+ * real fetch() instead of the shimmed one.
+ */
+const getClient = memoize(() =>
+ createGqClient(
+ {},
+ {
+ fetchPolicy: fetchPolicies.NO_CACHE,
+ },
+ ),
);
+
+// eslint-disable-next-line import/prefer-default-export
+export const query = (...args) => getClient().query(...args);
diff --git a/app/assets/javascripts/ide/services/index.js b/app/assets/javascripts/ide/services/index.js
index 1767d961259..ae4a1ba3db5 100644
--- a/app/assets/javascripts/ide/services/index.js
+++ b/app/assets/javascripts/ide/services/index.js
@@ -2,17 +2,15 @@ import axios from '~/lib/utils/axios_utils';
import { joinPaths, escapeFileUrl } from '~/lib/utils/url_utility';
import Api from '~/api';
import getUserPermissions from '../queries/getUserPermissions.query.graphql';
-import gqClient from './gql';
+import { query } from './gql';
const fetchApiProjectData = projectPath => Api.project(projectPath).then(({ data }) => data);
const fetchGqlProjectData = projectPath =>
- gqClient
- .query({
- query: getUserPermissions,
- variables: { projectPath },
- })
- .then(({ data }) => data.project);
+ query({
+ query: getUserPermissions,
+ variables: { projectPath },
+ }).then(({ data }) => data.project);
export default {
getFileData(endpoint) {
diff --git a/app/assets/javascripts/ide/stores/actions/file.js b/app/assets/javascripts/ide/stores/actions/file.js
index 47f9337a288..c0cb924e749 100644
--- a/app/assets/javascripts/ide/stores/actions/file.js
+++ b/app/assets/javascripts/ide/stores/actions/file.js
@@ -65,7 +65,7 @@ export const getFileData = (
if (file.raw || (file.tempFile && !file.prevPath && !fileDeletedAndReadded))
return Promise.resolve();
- commit(types.TOGGLE_LOADING, { entry: file });
+ commit(types.TOGGLE_LOADING, { entry: file, forceValue: true });
const url = joinPaths(
gon.relative_url_root || '/',
@@ -79,15 +79,15 @@ export const getFileData = (
return service
.getFileData(url)
.then(({ data }) => {
- setPageTitleForFile(state, file);
-
if (data) commit(types.SET_FILE_DATA, { data, file });
if (openFile) commit(types.TOGGLE_FILE_OPEN, path);
- if (makeFileActive) dispatch('setFileActive', path);
- commit(types.TOGGLE_LOADING, { entry: file });
+
+ if (makeFileActive) {
+ setPageTitleForFile(state, file);
+ dispatch('setFileActive', path);
+ }
})
.catch(() => {
- commit(types.TOGGLE_LOADING, { entry: file });
dispatch('setErrorMessage', {
text: __('An error occurred while loading the file.'),
action: payload =>
@@ -95,6 +95,9 @@ export const getFileData = (
actionText: __('Please try again'),
actionPayload: { path, makeFileActive },
});
+ })
+ .finally(() => {
+ commit(types.TOGGLE_LOADING, { entry: file, forceValue: false });
});
};
@@ -106,45 +109,41 @@ export const getRawFileData = ({ state, commit, dispatch, getters }, { path }) =
const file = state.entries[path];
const stagedFile = state.stagedFiles.find(f => f.path === path);
- return new Promise((resolve, reject) => {
- const fileDeletedAndReadded = getters.isFileDeletedAndReadded(path);
- service
- .getRawFileData(fileDeletedAndReadded ? stagedFile : file)
- .then(raw => {
- if (!(file.tempFile && !file.prevPath && !fileDeletedAndReadded))
- commit(types.SET_FILE_RAW_DATA, { file, raw, fileDeletedAndReadded });
-
- if (file.mrChange && file.mrChange.new_file === false) {
- const baseSha =
- (getters.currentMergeRequest && getters.currentMergeRequest.baseCommitSha) || '';
-
- service
- .getBaseRawFileData(file, baseSha)
- .then(baseRaw => {
- commit(types.SET_FILE_BASE_RAW_DATA, {
- file,
- baseRaw,
- });
- resolve(raw);
- })
- .catch(e => {
- reject(e);
- });
- } else {
- resolve(raw);
- }
- })
- .catch(() => {
- dispatch('setErrorMessage', {
- text: __('An error occurred while loading the file content.'),
- action: payload =>
- dispatch('getRawFileData', payload).then(() => dispatch('setErrorMessage', null)),
- actionText: __('Please try again'),
- actionPayload: { path },
+ const fileDeletedAndReadded = getters.isFileDeletedAndReadded(path);
+ commit(types.TOGGLE_LOADING, { entry: file, forceValue: true });
+ return service
+ .getRawFileData(fileDeletedAndReadded ? stagedFile : file)
+ .then(raw => {
+ if (!(file.tempFile && !file.prevPath && !fileDeletedAndReadded))
+ commit(types.SET_FILE_RAW_DATA, { file, raw, fileDeletedAndReadded });
+
+ if (file.mrChange && file.mrChange.new_file === false) {
+ const baseSha =
+ (getters.currentMergeRequest && getters.currentMergeRequest.baseCommitSha) || '';
+
+ return service.getBaseRawFileData(file, baseSha).then(baseRaw => {
+ commit(types.SET_FILE_BASE_RAW_DATA, {
+ file,
+ baseRaw,
+ });
+ return raw;
});
- reject();
+ }
+ return raw;
+ })
+ .catch(e => {
+ dispatch('setErrorMessage', {
+ text: __('An error occurred while loading the file content.'),
+ action: payload =>
+ dispatch('getRawFileData', payload).then(() => dispatch('setErrorMessage', null)),
+ actionText: __('Please try again'),
+ actionPayload: { path },
});
- });
+ throw e;
+ })
+ .finally(() => {
+ commit(types.TOGGLE_LOADING, { entry: file, forceValue: false });
+ });
};
export const changeFileContent = ({ commit, state, getters }, { path, content }) => {
diff --git a/app/assets/javascripts/ide/stores/actions/merge_request.js b/app/assets/javascripts/ide/stores/actions/merge_request.js
index fcaf060ef09..3fdfdc5422b 100644
--- a/app/assets/javascripts/ide/stores/actions/merge_request.js
+++ b/app/assets/javascripts/ide/stores/actions/merge_request.js
@@ -16,6 +16,7 @@ export const getMergeRequestsForBranch = (
.getProjectMergeRequests(`${projectId}`, {
source_branch: branchId,
source_project_id: state.projects[projectId].id,
+ state: 'opened',
order_by: 'created_at',
per_page: 1,
})
diff --git a/app/assets/javascripts/ide/stores/mutation_types.js b/app/assets/javascripts/ide/stores/mutation_types.js
index d94adc3760f..ae119c2b1fd 100644
--- a/app/assets/javascripts/ide/stores/mutation_types.js
+++ b/app/assets/javascripts/ide/stores/mutation_types.js
@@ -1,6 +1,5 @@
export const SET_INITIAL_DATA = 'SET_INITIAL_DATA';
export const TOGGLE_LOADING = 'TOGGLE_LOADING';
-export const SET_LAST_COMMIT_DATA = 'SET_LAST_COMMIT_DATA';
export const SET_LAST_COMMIT_MSG = 'SET_LAST_COMMIT_MSG';
export const SET_RESIZING_STATUS = 'SET_RESIZING_STATUS';
export const SET_EMPTY_STATE_SVGS = 'SET_EMPTY_STATE_SVGS';
diff --git a/app/assets/javascripts/ide/stores/mutations.js b/app/assets/javascripts/ide/stores/mutations.js
index e827aacac13..c64839e5019 100644
--- a/app/assets/javascripts/ide/stores/mutations.js
+++ b/app/assets/javascripts/ide/stores/mutations.js
@@ -34,15 +34,6 @@ export default {
panelResizing: resizing,
});
},
- [types.SET_LAST_COMMIT_DATA](state, { entry, lastCommit }) {
- Object.assign(entry.lastCommit, {
- id: lastCommit.commit.id,
- url: lastCommit.commit_path,
- message: lastCommit.commit.message,
- author: lastCommit.commit.author_name,
- updatedAt: lastCommit.commit.authored_date,
- });
- },
[types.SET_LAST_COMMIT_MSG](state, lastCommitMsg) {
Object.assign(state, {
lastCommitMsg,
diff --git a/app/assets/javascripts/ide/stores/utils.js b/app/assets/javascripts/ide/stores/utils.js
index 1c5fe9fe9a5..f074e6880d0 100644
--- a/app/assets/javascripts/ide/stores/utils.js
+++ b/app/assets/javascripts/ide/stores/utils.js
@@ -25,13 +25,6 @@ export const dataStructure = () => ({
changed: false,
staged: false,
lastCommitSha: '',
- lastCommit: {
- id: '',
- url: '',
- message: '',
- updatedAt: '',
- author: '',
- },
rawPath: '',
binary: false,
raw: '',
diff --git a/app/assets/javascripts/ide/utils.js b/app/assets/javascripts/ide/utils.js
index c28a2bd9f1d..9ec7b2c06ce 100644
--- a/app/assets/javascripts/ide/utils.js
+++ b/app/assets/javascripts/ide/utils.js
@@ -66,7 +66,7 @@ export const trimPathComponents = path =>
.join('/');
export function registerLanguages(def, ...defs) {
- if (defs.length) defs.forEach(lang => registerLanguages(lang));
+ defs.forEach(lang => registerLanguages(lang));
const languageId = def.id;
@@ -75,6 +75,19 @@ export function registerLanguages(def, ...defs) {
languages.setLanguageConfiguration(languageId, def.conf);
}
+export function registerSchemas({ language, options }, ...schemas) {
+ schemas.forEach(schema => registerSchemas(schema));
+
+ const defaults = {
+ json: languages.json.jsonDefaults,
+ yaml: languages.yaml.yamlDefaults,
+ };
+
+ if (defaults[language]) {
+ defaults[language].setDiagnosticsOptions(options);
+ }
+}
+
export const otherSide = side => (side === SIDE_RIGHT ? SIDE_LEFT : SIDE_RIGHT);
export function trimTrailingWhitespace(content) {
diff --git a/app/assets/javascripts/import_projects/components/bitbucket_status_table.vue b/app/assets/javascripts/import_projects/components/bitbucket_status_table.vue
index 1a9974db727..f673a0e42dc 100644
--- a/app/assets/javascripts/import_projects/components/bitbucket_status_table.vue
+++ b/app/assets/javascripts/import_projects/components/bitbucket_status_table.vue
@@ -28,7 +28,7 @@ export default {
};
</script>
<template>
- <import-projects-table provider-title="providerTitle">
+ <import-projects-table :provider-title="providerTitle">
<template #actions>
<slot name="actions"></slot>
</template>
diff --git a/app/assets/javascripts/import_projects/store/actions.js b/app/assets/javascripts/import_projects/store/actions.js
index 2422a1ed2e4..8d8d33f5972 100644
--- a/app/assets/javascripts/import_projects/store/actions.js
+++ b/app/assets/javascripts/import_projects/store/actions.js
@@ -70,8 +70,19 @@ export const fetchImport = ({ state, commit }, { newName, targetNamespace, repo
repoId: repo.id,
}),
)
- .catch(() => {
- createFlash(s__('ImportProjects|Importing the project failed'));
+ .catch(e => {
+ const serverErrorMessage = e?.response?.data?.errors;
+ const flashMessage = serverErrorMessage
+ ? sprintf(
+ s__('ImportProjects|Importing the project failed: %{reason}'),
+ {
+ reason: serverErrorMessage,
+ },
+ false,
+ )
+ : s__('ImportProjects|Importing the project failed');
+
+ createFlash(flashMessage);
commit(types.RECEIVE_IMPORT_ERROR, repo.id);
});
diff --git a/app/assets/javascripts/importer_status.js b/app/assets/javascripts/importer_status.js
index d6b519f7eac..f44c5c3d289 100644
--- a/app/assets/javascripts/importer_status.js
+++ b/app/assets/javascripts/importer_status.js
@@ -35,8 +35,8 @@ class ImporterStatus {
const $tr = $btn.closest('tr');
const $targetField = $tr.find('.import-target');
const $namespaceInput = $targetField.find('.js-select-namespace option:selected');
- const id = $tr.attr('id').replace('repo_', '');
const repoData = $tr.data();
+ const id = repoData.id || $tr.attr('id').replace('repo_', '');
let targetNamespace;
let newName;
@@ -63,7 +63,7 @@ class ImporterStatus {
return axios
.post(this.importUrl, attributes)
.then(({ data }) => {
- const job = $(`tr#repo_${id}`);
+ const job = $tr;
job.attr('id', `project_${data.id}`);
job.find('.import-target').html(`<a href="${data.full_path}">${data.full_path}</a>`);
@@ -86,7 +86,7 @@ class ImporterStatus {
.catch(error => {
let details = error;
- const $statusField = $(`#repo_${this.id} .job-status`);
+ const $statusField = $tr.find('.job-status');
$statusField.text(__('Failed'));
if (error.response && error.response.data && error.response.data.errors) {
diff --git a/app/assets/javascripts/incidents_settings/components/alerts_form.vue b/app/assets/javascripts/incidents_settings/components/alerts_form.vue
new file mode 100644
index 00000000000..a394f404ee1
--- /dev/null
+++ b/app/assets/javascripts/incidents_settings/components/alerts_form.vue
@@ -0,0 +1,139 @@
+<script>
+import {
+ GlButton,
+ GlSprintf,
+ GlLink,
+ GlIcon,
+ GlFormGroup,
+ GlFormCheckbox,
+ GlNewDropdown,
+ GlNewDropdownItem,
+} from '@gitlab/ui';
+import {
+ I18N_ALERT_SETTINGS_FORM,
+ NO_ISSUE_TEMPLATE_SELECTED,
+ TAKING_INCIDENT_ACTION_DOCS_LINK,
+ ISSUE_TEMPLATES_DOCS_LINK,
+} from '../constants';
+
+export default {
+ components: {
+ GlButton,
+ GlSprintf,
+ GlLink,
+ GlFormGroup,
+ GlIcon,
+ GlFormCheckbox,
+ GlNewDropdown,
+ GlNewDropdownItem,
+ },
+ inject: ['service', 'alertSettings'],
+ data() {
+ return {
+ templates: [NO_ISSUE_TEMPLATE_SELECTED, ...this.alertSettings.templates],
+ createIssueEnabled: this.alertSettings.createIssue,
+ issueTemplate: this.alertSettings.issueTemplateKey,
+ sendEmailEnabled: this.alertSettings.sendEmail,
+ loading: false,
+ };
+ },
+ i18n: I18N_ALERT_SETTINGS_FORM,
+ TAKING_INCIDENT_ACTION_DOCS_LINK,
+ ISSUE_TEMPLATES_DOCS_LINK,
+ computed: {
+ issueTemplateHeader() {
+ return this.issueTemplate || NO_ISSUE_TEMPLATE_SELECTED.name;
+ },
+ formData() {
+ return {
+ create_issue: this.createIssueEnabled,
+ issue_template_key: this.issueTemplate,
+ send_email: this.sendEmailEnabled,
+ };
+ },
+ },
+ methods: {
+ selectIssueTemplate(templateKey) {
+ this.issueTemplate = templateKey;
+ },
+ isTemplateSelected(templateKey) {
+ return templateKey === this.issueTemplate;
+ },
+ updateAlertsIntegrationSettings() {
+ this.loading = true;
+
+ this.service.updateSettings(this.formData).catch(() => {
+ this.loading = false;
+ });
+ },
+ },
+};
+</script>
+
+<template>
+ <div>
+ <p>
+ <gl-sprintf :message="$options.i18n.introText">
+ <template #docsLink>
+ <gl-link :href="$options.TAKING_INCIDENT_ACTION_DOCS_LINK" target="_blank">
+ <span>{{ $options.i18n.introLinkText }}</span>
+ </gl-link>
+ </template>
+ </gl-sprintf>
+ </p>
+ <form ref="settingsForm" @submit.prevent="updateAlertsIntegrationSettings">
+ <gl-form-group class="gl-pl-0">
+ <gl-form-checkbox v-model="createIssueEnabled" data-qa-selector="create_issue_checkbox">
+ <span>{{ $options.i18n.createIssue.label }}</span>
+ </gl-form-checkbox>
+ </gl-form-group>
+
+ <gl-form-group
+ label-size="sm"
+ label-for="alert-integration-settings-issue-template"
+ class="col-8 col-md-9 gl-px-6"
+ >
+ <label class="gl-display-inline-flex" for="alert-integration-settings-issue-template">
+ {{ $options.i18n.issueTemplate.label }}
+ <gl-link :href="$options.ISSUE_TEMPLATES_DOCS_LINK" target="_blank">
+ <gl-icon name="question" :size="12" />
+ </gl-link>
+ </label>
+ <gl-new-dropdown
+ id="alert-integration-settings-issue-template"
+ data-qa-selector="incident_templates_dropdown"
+ :text="issueTemplateHeader"
+ :block="true"
+ >
+ <gl-new-dropdown-item
+ v-for="template in templates"
+ :key="template.key"
+ data-qa-selector="incident_templates_item"
+ :is-check-item="true"
+ :is-checked="isTemplateSelected(template.key)"
+ @click="selectIssueTemplate(template.key)"
+ >
+ {{ template.name }}
+ </gl-new-dropdown-item>
+ </gl-new-dropdown>
+ </gl-form-group>
+
+ <gl-form-group class="gl-pl-0 gl-mb-5">
+ <gl-form-checkbox v-model="sendEmailEnabled">
+ <span>{{ $options.i18n.sendEmail.label }}</span>
+ </gl-form-checkbox>
+ </gl-form-group>
+
+ <gl-button
+ ref="submitBtn"
+ data-qa-selector="save_changes_button"
+ :disabled="loading"
+ variant="success"
+ type="submit"
+ class="js-no-auto-disable"
+ >
+ {{ $options.i18n.saveBtnLabel }}
+ </gl-button>
+ </form>
+ </div>
+</template>
diff --git a/app/assets/javascripts/incidents_settings/components/incidents_settings_tabs.vue b/app/assets/javascripts/incidents_settings/components/incidents_settings_tabs.vue
new file mode 100644
index 00000000000..0623c275c5a
--- /dev/null
+++ b/app/assets/javascripts/incidents_settings/components/incidents_settings_tabs.vue
@@ -0,0 +1,61 @@
+<script>
+import { GlButton, GlTabs, GlTab } from '@gitlab/ui';
+import AlertsSettingsForm from './alerts_form.vue';
+import PagerDutySettingsForm from './pagerduty_form.vue';
+import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
+import { INTEGRATION_TABS_CONFIG, I18N_INTEGRATION_TABS } from '../constants';
+
+export default {
+ components: {
+ GlButton,
+ GlTabs,
+ GlTab,
+ AlertsSettingsForm,
+ PagerDutySettingsForm,
+ },
+ mixins: [glFeatureFlagMixin()],
+ tabs: INTEGRATION_TABS_CONFIG,
+ i18n: I18N_INTEGRATION_TABS,
+ methods: {
+ isFeatureFlagEnabled(tab) {
+ if (tab.featureFlag) {
+ return this.glFeatures[tab.featureFlag];
+ }
+ return true;
+ },
+ },
+};
+</script>
+
+<template>
+ <section
+ id="incident-management-settings"
+ data-qa-selector="incidents_settings_content"
+ class="settings no-animate qa-incident-management-settings"
+ >
+ <div class="settings-header">
+ <h3 ref="sectionHeader" class="h4">
+ {{ $options.i18n.headerText }}
+ </h3>
+ <gl-button ref="toggleBtn" class="js-settings-toggle">{{
+ $options.i18n.expandBtnLabel
+ }}</gl-button>
+ <p ref="sectionSubHeader">
+ {{ $options.i18n.subHeaderText }}
+ </p>
+ </div>
+
+ <div class="settings-content">
+ <gl-tabs>
+ <gl-tab
+ v-for="(tab, index) in $options.tabs"
+ v-if="tab.active && isFeatureFlagEnabled(tab)"
+ :key="`${tab.title}_${index}`"
+ :title="tab.title"
+ >
+ <component :is="tab.component" class="gl-pt-3" :data-testid="`${tab.component}-tab`" />
+ </gl-tab>
+ </gl-tabs>
+ </div>
+ </section>
+</template>
diff --git a/app/assets/javascripts/incidents_settings/components/pagerduty_form.vue b/app/assets/javascripts/incidents_settings/components/pagerduty_form.vue
new file mode 100644
index 00000000000..027848db6e9
--- /dev/null
+++ b/app/assets/javascripts/incidents_settings/components/pagerduty_form.vue
@@ -0,0 +1,183 @@
+<script>
+import {
+ GlAlert,
+ GlButton,
+ GlSprintf,
+ GlLink,
+ GlIcon,
+ GlFormGroup,
+ GlFormInputGroup,
+ GlToggle,
+ GlModal,
+ GlModalDirective,
+} from '@gitlab/ui';
+import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
+import { I18N_PAGERDUTY_SETTINGS_FORM, CONFIGURE_PAGERDUTY_WEBHOOK_DOCS_LINK } from '../constants';
+import { isEqual } from 'lodash';
+
+export default {
+ components: {
+ GlAlert,
+ GlButton,
+ GlSprintf,
+ GlLink,
+ GlIcon,
+ GlFormGroup,
+ GlFormInputGroup,
+ GlToggle,
+ GlModal,
+ ClipboardButton,
+ },
+ directives: {
+ 'gl-modal': GlModalDirective,
+ },
+ inject: ['service', 'pagerDutySettings'],
+ data() {
+ return {
+ active: this.pagerDutySettings.active,
+ webhookUrl: this.pagerDutySettings.webhookUrl,
+ loading: false,
+ resettingWebhook: false,
+ webhookUpdateFailed: false,
+ showAlert: false,
+ };
+ },
+ i18n: I18N_PAGERDUTY_SETTINGS_FORM,
+ CONFIGURE_PAGERDUTY_WEBHOOK_DOCS_LINK,
+ computed: {
+ formData() {
+ return {
+ pagerduty_active: this.active,
+ };
+ },
+ isFormUpdated() {
+ return isEqual(this.pagerDutySettings, {
+ active: this.active,
+ webhookUrl: this.webhookUrl,
+ });
+ },
+ isSaveDisabled() {
+ return this.isFormUpdated || this.loading || this.resettingWebhook;
+ },
+ webhookUpdateAlertMsg() {
+ return this.webhookUpdateFailed
+ ? this.$options.i18n.webhookUrl.updateErrMsg
+ : this.$options.i18n.webhookUrl.updateSuccessMsg;
+ },
+ webhookUpdateAlertVariant() {
+ return this.webhookUpdateFailed ? 'danger' : 'success';
+ },
+ },
+ methods: {
+ updatePagerDutyIntegrationSettings() {
+ this.loading = true;
+
+ this.service.updateSettings(this.formData).catch(() => {
+ this.loading = false;
+ });
+ },
+ resetWebhookUrl() {
+ this.resettingWebhook = true;
+
+ this.service
+ .resetWebhookUrl()
+ .then(({ data: { pagerduty_webhook_url: url } }) => {
+ this.webhookUrl = url;
+ this.showAlert = true;
+ this.webhookUpdateFailed = false;
+ })
+ .catch(() => {
+ this.showAlert = true;
+ this.webhookUpdateFailed = true;
+ })
+ .finally(() => {
+ this.resettingWebhook = false;
+ });
+ },
+ },
+};
+</script>
+
+<template>
+ <div>
+ <gl-alert
+ v-if="showAlert"
+ class="gl-mb-3"
+ :variant="webhookUpdateAlertVariant"
+ @dismiss="showAlert = false"
+ >
+ {{ webhookUpdateAlertMsg }}
+ </gl-alert>
+
+ <p>{{ $options.i18n.introText }}</p>
+ <form ref="settingsForm" @submit.prevent="updatePagerDutyIntegrationSettings">
+ <gl-form-group class="col-8 col-md-9 gl-p-0">
+ <gl-toggle
+ id="active"
+ v-model="active"
+ :is-loading="loading"
+ :label="$options.i18n.activeToggle.label"
+ />
+ </gl-form-group>
+
+ <gl-form-group
+ class="col-8 col-md-9 gl-p-0"
+ :label="$options.i18n.webhookUrl.label"
+ label-for="url"
+ label-class="label-bold"
+ >
+ <gl-form-input-group id="url" data-testid="webhook-url" readonly :value="webhookUrl">
+ <template #append>
+ <clipboard-button
+ :text="webhookUrl"
+ :title="$options.i18n.webhookUrl.copyToClipboard"
+ />
+ </template>
+ </gl-form-input-group>
+
+ <div class="gl-text-gray-400 gl-pt-2">
+ <gl-sprintf :message="$options.i18n.webhookUrl.helpText">
+ <template #docsLink>
+ <gl-link
+ :href="$options.CONFIGURE_PAGERDUTY_WEBHOOK_DOCS_LINK"
+ target="_blank"
+ class="gl-display-inline-flex"
+ >
+ <span>{{ $options.i18n.webhookUrl.helpDocsLink }}</span>
+ <gl-icon name="external-link" />
+ </gl-link>
+ </template>
+ </gl-sprintf>
+ </div>
+ <gl-button
+ v-gl-modal.resetWebhookModal
+ class="gl-mt-3"
+ :disabled="loading"
+ :loading="resettingWebhook"
+ data-testid="webhook-reset-btn"
+ >
+ {{ $options.i18n.webhookUrl.resetWebhookUrl }}
+ </gl-button>
+ <gl-modal
+ modal-id="resetWebhookModal"
+ :title="$options.i18n.webhookUrl.resetWebhookUrl"
+ :ok-title="$options.i18n.webhookUrl.resetWebhookUrl"
+ ok-variant="danger"
+ @ok="resetWebhookUrl"
+ >
+ {{ $options.i18n.webhookUrl.restKeyInfo }}
+ </gl-modal>
+ </gl-form-group>
+
+ <gl-button
+ ref="submitBtn"
+ :disabled="isSaveDisabled"
+ variant="success"
+ type="submit"
+ class="js-no-auto-disable"
+ >
+ {{ $options.i18n.saveBtnLabel }}
+ </gl-button>
+ </form>
+ </div>
+</template>
diff --git a/app/assets/javascripts/incidents_settings/constants.js b/app/assets/javascripts/incidents_settings/constants.js
new file mode 100644
index 00000000000..b443c237f0f
--- /dev/null
+++ b/app/assets/javascripts/incidents_settings/constants.js
@@ -0,0 +1,83 @@
+import { __, s__ } from '~/locale';
+
+/* Integration tabs constants */
+export const INTEGRATION_TABS_CONFIG = [
+ {
+ title: s__('IncidentSettings|Alert integration'),
+ component: 'AlertsSettingsForm',
+ active: true,
+ },
+ {
+ title: s__('IncidentSettings|PagerDuty integration'),
+ component: 'PagerDutySettingsForm',
+ active: true,
+ featureFlag: 'pagerdutyWebhook',
+ },
+ {
+ title: s__('IncidentSettings|Grafana integration'),
+ component: '',
+ active: false,
+ },
+];
+
+export const I18N_INTEGRATION_TABS = {
+ headerText: s__('IncidentSettings|Incidents'),
+ expandBtnLabel: __('Expand'),
+ subHeaderText: s__(
+ 'IncidentSettings|Set up integrations with external tools to help better manage incidents.',
+ ),
+};
+
+/* Alerts integration settings constants */
+
+export const I18N_ALERT_SETTINGS_FORM = {
+ saveBtnLabel: __('Save changes'),
+ introText: __('Action to take when receiving an alert. %{docsLink}'),
+ introLinkText: __('More information.'),
+ createIssue: {
+ label: __('Create an issue. Issues are created for each alert triggered.'),
+ },
+ issueTemplate: {
+ label: __('Issue template (optional)'),
+ },
+ sendEmail: {
+ label: __('Send a separate email notification to Developers.'),
+ },
+};
+
+export const NO_ISSUE_TEMPLATE_SELECTED = { key: '', name: __('No template selected') };
+export const TAKING_INCIDENT_ACTION_DOCS_LINK =
+ '/help/user/project/integrations/prometheus#taking-action-on-incidents-ultimate';
+export const ISSUE_TEMPLATES_DOCS_LINK =
+ '/help/user/project/description_templates#creating-issue-templates';
+
+/* PagerDuty integration settings constants */
+
+export const I18N_PAGERDUTY_SETTINGS_FORM = {
+ introText: s__(
+ 'PagerDutySettings|Setting up a webhook with PagerDuty will automatically create a GitLab issue for each PagerDuty incident.',
+ ),
+ activeToggle: {
+ label: s__('PagerDutySettings|Active'),
+ },
+ webhookUrl: {
+ label: s__('PagerDutySettings|Webhook URL'),
+ helpText: s__(
+ 'PagerDutySettings|Create a GitLab issue for each PagerDuty incident by %{docsLink}',
+ ),
+ helpDocsLink: s__('PagerDutySettings|configuring a webhook in PagerDuty'),
+ resetWebhookUrl: s__('PagerDutySettings|Reset webhook URL'),
+ copyToClipboard: __('Copy'),
+ updateErrMsg: s__('PagerDutySettings|Failed to update Webhook URL'),
+ updateSuccessMsg: s__('PagerDutySettings|Webhook URL update was successful'),
+ restKeyInfo: s__(
+ "PagerDutySettings|Resetting the webhook URL for this project will require updating this integration's settings in PagerDuty.",
+ ),
+ },
+ saveBtnLabel: __('Save changes'),
+};
+
+export const CONFIGURE_PAGERDUTY_WEBHOOK_DOCS_LINK = 'https://support.pagerduty.com/docs/webhooks';
+
+/* common constants */
+export const ERROR_MSG = __('There was an error saving your changes.');
diff --git a/app/assets/javascripts/incidents_settings/incidents_settings_service.js b/app/assets/javascripts/incidents_settings/incidents_settings_service.js
new file mode 100644
index 00000000000..bd4f5bb8820
--- /dev/null
+++ b/app/assets/javascripts/incidents_settings/incidents_settings_service.js
@@ -0,0 +1,32 @@
+import axios from '~/lib/utils/axios_utils';
+import { refreshCurrentPage } from '~/lib/utils/url_utility';
+import createFlash from '~/flash';
+import { ERROR_MSG } from './constants';
+
+export default class IncidentsSettingsService {
+ constructor(settingsEndpoint, webhookUpdateEndpoint) {
+ this.settingsEndpoint = settingsEndpoint;
+ this.webhookUpdateEndpoint = webhookUpdateEndpoint;
+ }
+
+ updateSettings(data) {
+ return axios
+ .patch(this.settingsEndpoint, {
+ project: {
+ incident_management_setting_attributes: data,
+ },
+ })
+ .then(() => {
+ refreshCurrentPage();
+ })
+ .catch(({ response }) => {
+ const message = response?.data?.message || '';
+
+ createFlash(`${ERROR_MSG} ${message}`, 'alert');
+ });
+ }
+
+ resetWebhookUrl() {
+ return axios.post(this.webhookUpdateEndpoint);
+ }
+}
diff --git a/app/assets/javascripts/incidents_settings/index.js b/app/assets/javascripts/incidents_settings/index.js
new file mode 100644
index 00000000000..80e7d07feca
--- /dev/null
+++ b/app/assets/javascripts/incidents_settings/index.js
@@ -0,0 +1,46 @@
+import Vue from 'vue';
+import { parseBoolean } from '~/lib/utils/common_utils';
+import SettingsTabs from './components/incidents_settings_tabs.vue';
+import IncidentsSettingsService from './incidents_settings_service';
+
+export default () => {
+ const el = document.querySelector('.js-incidents-settings');
+
+ if (!el) {
+ return null;
+ }
+
+ const {
+ dataset: {
+ operationsSettingsEndpoint,
+ templates,
+ createIssue,
+ issueTemplateKey,
+ sendEmail,
+ pagerdutyActive,
+ pagerdutyWebhookUrl,
+ pagerdutyResetKeyPath,
+ },
+ } = el;
+
+ const service = new IncidentsSettingsService(operationsSettingsEndpoint, pagerdutyResetKeyPath);
+ return new Vue({
+ el,
+ provide: {
+ service,
+ alertSettings: {
+ templates: JSON.parse(templates),
+ createIssue: parseBoolean(createIssue),
+ issueTemplateKey,
+ sendEmail: parseBoolean(sendEmail),
+ },
+ pagerDutySettings: {
+ active: parseBoolean(pagerdutyActive),
+ webhookUrl: pagerdutyWebhookUrl,
+ },
+ },
+ render(createElement) {
+ return createElement(SettingsTabs);
+ },
+ });
+};
diff --git a/app/assets/javascripts/integrations/edit/components/active_toggle.vue b/app/assets/javascripts/integrations/edit/components/active_toggle.vue
index dc89e139320..a3087c8958e 100644
--- a/app/assets/javascripts/integrations/edit/components/active_toggle.vue
+++ b/app/assets/javascripts/integrations/edit/components/active_toggle.vue
@@ -1,4 +1,5 @@
<script>
+import { mapGetters } from 'vuex';
import eventHub from '../event_hub';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import { GlFormGroup, GlToggle } from '@gitlab/ui';
@@ -21,6 +22,9 @@ export default {
activated: this.initialActivated,
};
},
+ computed: {
+ ...mapGetters(['isInheriting']),
+ },
mounted() {
// Initialize view
this.$nextTick(() => {
@@ -42,6 +46,7 @@ export default {
v-model="activated"
name="service[active]"
class="gl-display-block gl-line-height-0"
+ :disabled="isInheriting"
@change="onToggle"
/>
</gl-form-group>
@@ -50,7 +55,12 @@ export default {
<div class="form-group row" role="group">
<label for="service[active]" class="col-form-label col-sm-2">{{ __('Active') }}</label>
<div class="col-sm-10 pt-1">
- <gl-toggle v-model="activated" name="service[active]" @change="onToggle" />
+ <gl-toggle
+ v-model="activated"
+ name="service[active]"
+ :disabled="isInheriting"
+ @change="onToggle"
+ />
</div>
</div>
</div>
diff --git a/app/assets/javascripts/integrations/edit/components/dynamic_field.vue b/app/assets/javascripts/integrations/edit/components/dynamic_field.vue
index 29318d6aaa8..6053d11e6da 100644
--- a/app/assets/javascripts/integrations/edit/components/dynamic_field.vue
+++ b/app/assets/javascripts/integrations/edit/components/dynamic_field.vue
@@ -1,4 +1,5 @@
<script>
+import { mapGetters } from 'vuex';
import eventHub from '../event_hub';
import { capitalize, lowerCase, isEmpty } from 'lodash';
import { __, sprintf } from '~/locale';
@@ -59,6 +60,7 @@ export default {
};
},
computed: {
+ ...mapGetters(['isInheriting']),
isCheckbox() {
return this.type === 'checkbox';
},
@@ -106,10 +108,12 @@ export default {
return {
id: this.fieldId,
name: this.fieldName,
+ state: this.valid,
+ readonly: this.isInheriting,
};
},
valid() {
- return !this.required || !isEmpty(this.model) || !this.validated;
+ return !this.required || !isEmpty(this.model) || this.isNonEmptyPassword || !this.validated;
},
},
created() {
@@ -135,15 +139,21 @@ export default {
:label-for="fieldId"
:invalid-feedback="__('This field is required.')"
:state="valid"
- :description="help"
>
+ <template #description>
+ <span v-html="help"></span>
+ </template>
+
<template v-if="isCheckbox">
- <input :name="fieldName" type="hidden" value="false" />
- <gl-form-checkbox v-model="model" v-bind="sharedProps">
+ <input :name="fieldName" type="hidden" :value="model || false" />
+ <gl-form-checkbox :id="fieldId" v-model="model" :disabled="isInheriting">
{{ humanizedTitle }}
</gl-form-checkbox>
</template>
- <gl-form-select v-else-if="isSelect" v-model="model" v-bind="sharedProps" :options="options" />
+ <template v-else-if="isSelect">
+ <input type="hidden" :name="fieldName" :value="model" />
+ <gl-form-select :id="fieldId" v-model="model" :options="options" :disabled="isInheriting" />
+ </template>
<gl-form-textarea
v-else-if="isTextarea"
v-model="model"
@@ -159,6 +169,7 @@ export default {
autocomplete="new-password"
:placeholder="placeholder"
:required="passwordRequired"
+ :data-qa-selector="`${fieldId}_field`"
/>
<gl-form-input
v-else
@@ -167,6 +178,7 @@ export default {
:type="type"
:placeholder="placeholder"
:required="required"
+ :data-qa-selector="`${fieldId}_field`"
/>
</gl-form-group>
</template>
diff --git a/app/assets/javascripts/integrations/edit/components/integration_form.vue b/app/assets/javascripts/integrations/edit/components/integration_form.vue
index ef7a4d44b20..5088664c3bd 100644
--- a/app/assets/javascripts/integrations/edit/components/integration_form.vue
+++ b/app/assets/javascripts/integrations/edit/components/integration_form.vue
@@ -1,58 +1,74 @@
<script>
+import { mapState, mapActions, mapGetters } from 'vuex';
+import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
+
+import OverrideDropdown from './override_dropdown.vue';
import ActiveToggle from './active_toggle.vue';
import JiraTriggerFields from './jira_trigger_fields.vue';
+import JiraIssuesFields from './jira_issues_fields.vue';
import TriggerFields from './trigger_fields.vue';
import DynamicField from './dynamic_field.vue';
export default {
name: 'IntegrationForm',
components: {
+ OverrideDropdown,
ActiveToggle,
JiraTriggerFields,
+ JiraIssuesFields,
TriggerFields,
DynamicField,
},
- props: {
- activeToggleProps: {
- type: Object,
- required: true,
- },
- showActive: {
- type: Boolean,
- required: true,
- },
- triggerFieldsProps: {
- type: Object,
- required: true,
- },
- triggerEvents: {
- type: Array,
- required: false,
- default: () => [],
- },
- fields: {
- type: Array,
- required: false,
- default: () => [],
- },
- type: {
- type: String,
- required: true,
- },
- },
+ mixins: [glFeatureFlagsMixin()],
computed: {
+ ...mapGetters(['currentKey', 'propsSource']),
+ ...mapState(['adminState', 'override']),
isJira() {
- return this.type === 'jira';
+ return this.propsSource.type === 'jira';
},
+ showJiraIssuesFields() {
+ return this.isJira && this.glFeatures.jiraIssuesIntegration;
+ },
+ },
+ methods: {
+ ...mapActions(['setOverride']),
},
};
</script>
<template>
<div>
- <active-toggle v-if="showActive" v-bind="activeToggleProps" />
- <jira-trigger-fields v-if="isJira" v-bind="triggerFieldsProps" />
- <trigger-fields v-else-if="triggerEvents.length" :events="triggerEvents" :type="type" />
- <dynamic-field v-for="field in fields" :key="field.name" v-bind="field" />
+ <override-dropdown
+ v-if="adminState !== null"
+ :inherit-from-id="adminState.id"
+ :override="override"
+ @change="setOverride"
+ />
+ <active-toggle
+ v-if="propsSource.showActive"
+ :key="`${currentKey}-active-toggle`"
+ v-bind="propsSource.activeToggleProps"
+ />
+ <jira-trigger-fields
+ v-if="isJira"
+ :key="`${currentKey}-jira-trigger-fields`"
+ v-bind="propsSource.triggerFieldsProps"
+ />
+ <trigger-fields
+ v-else-if="propsSource.triggerEvents.length"
+ :key="`${currentKey}-trigger-fields`"
+ :events="propsSource.triggerEvents"
+ :type="propsSource.type"
+ />
+ <dynamic-field
+ v-for="field in propsSource.fields"
+ :key="`${currentKey}-${field.name}`"
+ v-bind="field"
+ />
+ <jira-issues-fields
+ v-if="showJiraIssuesFields"
+ :key="`${currentKey}-jira-issues-fields`"
+ v-bind="propsSource.jiraIssuesProps"
+ />
</div>
</template>
diff --git a/app/assets/javascripts/integrations/edit/components/jira_issues_fields.vue b/app/assets/javascripts/integrations/edit/components/jira_issues_fields.vue
new file mode 100644
index 00000000000..5444cd5a712
--- /dev/null
+++ b/app/assets/javascripts/integrations/edit/components/jira_issues_fields.vue
@@ -0,0 +1,151 @@
+<script>
+import eventHub from '../event_hub';
+import {
+ GlFormGroup,
+ GlFormCheckbox,
+ GlFormInput,
+ GlSprintf,
+ GlLink,
+ GlButton,
+ GlCard,
+} from '@gitlab/ui';
+
+export default {
+ name: 'JiraIssuesFields',
+ components: {
+ GlFormGroup,
+ GlFormCheckbox,
+ GlFormInput,
+ GlSprintf,
+ GlLink,
+ GlButton,
+ GlCard,
+ },
+ props: {
+ showJiraIssuesIntegration: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ initialEnableJiraIssues: {
+ type: Boolean,
+ required: false,
+ default: null,
+ },
+ initialProjectKey: {
+ type: String,
+ required: false,
+ default: null,
+ },
+ upgradePlanPath: {
+ type: String,
+ required: false,
+ default: null,
+ },
+ editProjectPath: {
+ type: String,
+ required: false,
+ default: null,
+ },
+ },
+ data() {
+ return {
+ enableJiraIssues: this.initialEnableJiraIssues,
+ projectKey: this.initialProjectKey,
+ validated: false,
+ };
+ },
+ computed: {
+ validProjectKey() {
+ return !this.enableJiraIssues || Boolean(this.projectKey) || !this.validated;
+ },
+ },
+ created() {
+ eventHub.$on('validateForm', this.validateForm);
+ },
+ beforeDestroy() {
+ eventHub.$off('validateForm', this.validateForm);
+ },
+ methods: {
+ validateForm() {
+ this.validated = true;
+ },
+ },
+};
+</script>
+
+<template>
+ <div>
+ <gl-form-group
+ :label="s__('JiraService|View Jira issues in GitLab')"
+ label-for="jira-issue-settings"
+ >
+ <div id="jira-issue-settings">
+ <p>
+ {{
+ s__(
+ 'JiraService|Work on Jira issues without leaving GitLab. Adds a Jira menu to access your list of Jira issues and view any issue as read-only.',
+ )
+ }}
+ </p>
+ <template v-if="showJiraIssuesIntegration">
+ <input name="service[issues_enabled]" type="hidden" :value="enableJiraIssues || false" />
+ <gl-form-checkbox v-model="enableJiraIssues">
+ {{ s__('JiraService|Enable Jira issues') }}
+ <template #help>
+ {{
+ s__(
+ 'JiraService|Warning: All GitLab users that have access to this GitLab project will be able to view all issues from the Jira project specified below.',
+ )
+ }}
+ </template>
+ </gl-form-checkbox>
+ </template>
+ <gl-card v-else class="gl-mt-7">
+ <strong>{{ __('This is a Premium feature') }}</strong>
+ <p>{{ __('Upgrade your plan to enable this feature of the Jira Integration.') }}</p>
+ <gl-button
+ v-if="upgradePlanPath"
+ category="primary"
+ variant="info"
+ :href="upgradePlanPath"
+ target="_blank"
+ >
+ {{ __('Upgrade your plan') }}
+ </gl-button>
+ </gl-card>
+ </div>
+ </gl-form-group>
+ <template v-if="showJiraIssuesIntegration">
+ <gl-form-group
+ :label="s__('JiraService|Jira project key')"
+ label-for="service_project_key"
+ :invalid-feedback="__('This field is required.')"
+ :state="validProjectKey"
+ >
+ <gl-form-input
+ id="service_project_key"
+ v-model="projectKey"
+ name="service[project_key]"
+ :placeholder="s__('JiraService|e.g. AB')"
+ :required="enableJiraIssues"
+ :state="validProjectKey"
+ :disabled="!enableJiraIssues"
+ />
+ </gl-form-group>
+ <p>
+ <gl-sprintf
+ :message="
+ s__(
+ 'JiraService|Displaying Jira issues while leaving the GitLab issue functionality enabled might be confusing. Consider %{linkStart}disabling GitLab issues%{linkEnd} if they won’t otherwise be used.',
+ )
+ "
+ >
+ <template #link="{ content }">
+ <gl-link :href="editProjectPath" target="_blank">{{ content }}</gl-link>
+ </template>
+ </gl-sprintf>
+ </p>
+ </template>
+ </div>
+</template>
diff --git a/app/assets/javascripts/integrations/edit/components/jira_trigger_fields.vue b/app/assets/javascripts/integrations/edit/components/jira_trigger_fields.vue
index 64e5789764f..1d3354c6651 100644
--- a/app/assets/javascripts/integrations/edit/components/jira_trigger_fields.vue
+++ b/app/assets/javascripts/integrations/edit/components/jira_trigger_fields.vue
@@ -1,5 +1,6 @@
<script>
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
+import { mapGetters } from 'vuex';
import { s__ } from '~/locale';
import { GlFormGroup, GlFormCheckbox, GlFormRadio } from '@gitlab/ui';
@@ -55,6 +56,7 @@ export default {
};
},
computed: {
+ ...mapGetters(['isInheriting']),
showEnableComments() {
return this.triggerCommit || this.triggerMergeRequest;
},
@@ -73,13 +75,17 @@ export default {
)
"
>
- <input name="service[commit_events]" type="hidden" value="false" />
- <gl-form-checkbox v-model="triggerCommit" name="service[commit_events]">
+ <input name="service[commit_events]" type="hidden" :value="triggerCommit || false" />
+ <gl-form-checkbox v-model="triggerCommit" :disabled="isInheriting">
{{ __('Commit') }}
</gl-form-checkbox>
- <input name="service[merge_requests_events]" type="hidden" value="false" />
- <gl-form-checkbox v-model="triggerMergeRequest" name="service[merge_requests_events]">
+ <input
+ name="service[merge_requests_events]"
+ type="hidden"
+ :value="triggerMergeRequest || false"
+ />
+ <gl-form-checkbox v-model="triggerMergeRequest" :disabled="isInheriting">
{{ __('Merge request') }}
</gl-form-checkbox>
</gl-form-group>
@@ -89,8 +95,12 @@ export default {
:label="s__('Integrations|Comment settings:')"
data-testid="comment-settings"
>
- <input name="service[comment_on_event_enabled]" type="hidden" value="false" />
- <gl-form-checkbox v-model="enableComments" name="service[comment_on_event_enabled]">
+ <input
+ name="service[comment_on_event_enabled]"
+ type="hidden"
+ :value="enableComments || false"
+ />
+ <gl-form-checkbox v-model="enableComments" :disabled="isInheriting">
{{ s__('Integrations|Enable comments') }}
</gl-form-checkbox>
</gl-form-group>
@@ -100,12 +110,18 @@ export default {
:label="s__('Integrations|Comment detail:')"
data-testid="comment-detail"
>
+ <input
+ v-if="isInheriting"
+ name="service[comment_detail]"
+ type="hidden"
+ :value="commentDetail"
+ />
<gl-form-radio
v-for="commentDetailOption in commentDetailOptions"
:key="commentDetailOption.value"
v-model="commentDetail"
:value="commentDetailOption.value"
- name="service[comment_detail]"
+ :disabled="isInheriting"
>
{{ commentDetailOption.label }}
<template #help>
@@ -126,13 +142,17 @@ export default {
}}
</label>
- <input name="service[commit_events]" type="hidden" value="false" />
- <gl-form-checkbox v-model="triggerCommit" name="service[commit_events]">
+ <input name="service[commit_events]" type="hidden" :value="triggerCommit || false" />
+ <gl-form-checkbox v-model="triggerCommit" :disabled="isInheriting">
{{ __('Commit') }}
</gl-form-checkbox>
- <input name="service[merge_requests_events]" type="hidden" value="false" />
- <gl-form-checkbox v-model="triggerMergeRequest" name="service[merge_requests_events]">
+ <input
+ name="service[merge_requests_events]"
+ type="hidden"
+ :value="triggerMergeRequest || false"
+ />
+ <gl-form-checkbox v-model="triggerMergeRequest" :disabled="isInheriting">
{{ __('Merge request') }}
</gl-form-checkbox>
@@ -144,8 +164,12 @@ export default {
<label>
{{ s__('Integrations|Comment settings:') }}
</label>
- <input name="service[comment_on_event_enabled]" type="hidden" value="false" />
- <gl-form-checkbox v-model="enableComments" name="service[comment_on_event_enabled]">
+ <input
+ name="service[comment_on_event_enabled]"
+ type="hidden"
+ :value="enableComments || false"
+ />
+ <gl-form-checkbox v-model="enableComments" :disabled="isInheriting">
{{ s__('Integrations|Enable comments') }}
</gl-form-checkbox>
@@ -153,12 +177,18 @@ export default {
<label>
{{ s__('Integrations|Comment detail:') }}
</label>
+ <input
+ v-if="isInheriting"
+ name="service[comment_detail]"
+ type="hidden"
+ :value="commentDetail"
+ />
<gl-form-radio
v-for="commentDetailOption in commentDetailOptions"
:key="commentDetailOption.value"
v-model="commentDetail"
:value="commentDetailOption.value"
- name="service[comment_detail]"
+ :disabled="isInheriting"
>
{{ commentDetailOption.label }}
<template #help>
diff --git a/app/assets/javascripts/integrations/edit/components/override_dropdown.vue b/app/assets/javascripts/integrations/edit/components/override_dropdown.vue
new file mode 100644
index 00000000000..0ae2f267434
--- /dev/null
+++ b/app/assets/javascripts/integrations/edit/components/override_dropdown.vue
@@ -0,0 +1,63 @@
+<script>
+import { s__ } from '~/locale';
+import { GlNewDropdown, GlNewDropdownItem } from '@gitlab/ui';
+
+const dropdownOptions = [
+ {
+ value: false,
+ text: s__('Integrations|Use instance level settings'),
+ },
+ {
+ value: true,
+ text: s__('Integrations|Use custom settings'),
+ },
+];
+
+export default {
+ dropdownOptions,
+ name: 'OverrideDropdown',
+ components: {
+ GlNewDropdown,
+ GlNewDropdownItem,
+ },
+ props: {
+ inheritFromId: {
+ type: Number,
+ required: true,
+ },
+ override: {
+ type: Boolean,
+ required: true,
+ },
+ },
+ data() {
+ return {
+ selected: dropdownOptions.find(x => x.value === this.override),
+ };
+ },
+ methods: {
+ onClick(option) {
+ this.selected = option;
+ this.$emit('change', option.value);
+ },
+ },
+};
+</script>
+
+<template>
+ <div
+ class="gl-display-flex gl-justify-content-space-between gl-align-items-baseline gl-py-4 gl-mt-5 gl-mb-6 gl-border-t-1 gl-border-t-solid gl-border-b-1 gl-border-b-solid gl-border-gray-100"
+ >
+ <span>{{ s__('Integrations|This integration has multiple settings available.') }}</span>
+ <input name="service[inherit_from_id]" :value="override ? '' : inheritFromId" type="hidden" />
+ <gl-new-dropdown :text="selected.text">
+ <gl-new-dropdown-item
+ v-for="option in $options.dropdownOptions"
+ :key="option.value"
+ @click="onClick(option)"
+ >
+ {{ option.text }}
+ </gl-new-dropdown-item>
+ </gl-new-dropdown>
+ </div>
+</template>
diff --git a/app/assets/javascripts/integrations/edit/components/trigger_fields.vue b/app/assets/javascripts/integrations/edit/components/trigger_fields.vue
index 531490ae40c..bb1e0d9d360 100644
--- a/app/assets/javascripts/integrations/edit/components/trigger_fields.vue
+++ b/app/assets/javascripts/integrations/edit/components/trigger_fields.vue
@@ -1,4 +1,5 @@
<script>
+import { mapGetters } from 'vuex';
import { startCase } from 'lodash';
import { __ } from '~/locale';
import { GlFormGroup, GlFormCheckbox, GlFormInput } from '@gitlab/ui';
@@ -32,6 +33,7 @@ export default {
},
},
computed: {
+ ...mapGetters(['isInheriting']),
placeholder() {
return placeholderForType[this.type];
},
@@ -57,8 +59,8 @@ export default {
>
<div id="trigger-fields" class="gl-pt-3">
<gl-form-group v-for="event in events" :key="event.title" :description="event.description">
- <input :name="checkboxName(event.name)" type="hidden" value="false" />
- <gl-form-checkbox v-model="event.value" :name="checkboxName(event.name)">
+ <input :name="checkboxName(event.name)" type="hidden" :value="event.value || false" />
+ <gl-form-checkbox v-model="event.value" :disabled="isInheriting">
{{ startCase(event.title) }}
</gl-form-checkbox>
<gl-form-input
@@ -66,6 +68,7 @@ export default {
v-model="event.field.value"
:name="fieldName(event.field.name)"
:placeholder="placeholder"
+ :readonly="isInheriting"
/>
</gl-form-group>
</div>
diff --git a/app/assets/javascripts/integrations/edit/index.js b/app/assets/javascripts/integrations/edit/index.js
index 21b5ca17951..ea5463832ce 100644
--- a/app/assets/javascripts/integrations/edit/index.js
+++ b/app/assets/javascripts/integrations/edit/index.js
@@ -1,49 +1,86 @@
import Vue from 'vue';
+import { createStore } from './store';
import { parseBoolean } from '~/lib/utils/common_utils';
import IntegrationForm from './components/integration_form.vue';
-export default el => {
- if (!el) {
- return null;
- }
-
- function parseBooleanInData(data) {
- const result = {};
- Object.entries(data).forEach(([key, value]) => {
- result[key] = parseBoolean(value);
- });
- return result;
- }
+function parseBooleanInData(data) {
+ const result = {};
+ Object.entries(data).forEach(([key, value]) => {
+ result[key] = parseBoolean(value);
+ });
+ return result;
+}
- const { type, commentDetail, triggerEvents, fields, ...booleanAttributes } = el.dataset;
+function parseDatasetToProps(data) {
+ const {
+ id,
+ type,
+ commentDetail,
+ projectKey,
+ upgradePlanPath,
+ editProjectPath,
+ triggerEvents,
+ fields,
+ inheritFromId,
+ ...booleanAttributes
+ } = data;
const {
showActive,
activated,
commitEvents,
mergeRequestEvents,
enableComments,
+ showJiraIssuesIntegration,
+ enableJiraIssues,
} = parseBooleanInData(booleanAttributes);
+ return {
+ activeToggleProps: {
+ initialActivated: activated,
+ },
+ showActive,
+ type,
+ triggerFieldsProps: {
+ initialTriggerCommit: commitEvents,
+ initialTriggerMergeRequest: mergeRequestEvents,
+ initialEnableComments: enableComments,
+ initialCommentDetail: commentDetail,
+ },
+ jiraIssuesProps: {
+ showJiraIssuesIntegration,
+ initialEnableJiraIssues: enableJiraIssues,
+ initialProjectKey: projectKey,
+ upgradePlanPath,
+ editProjectPath,
+ },
+ triggerEvents: JSON.parse(triggerEvents),
+ fields: JSON.parse(fields),
+ inheritFromId: parseInt(inheritFromId, 10),
+ id: parseInt(id, 10),
+ };
+}
+
+export default (el, adminEl) => {
+ if (!el) {
+ return null;
+ }
+
+ const props = parseDatasetToProps(el.dataset);
+
+ const initialState = {
+ adminState: null,
+ customState: props,
+ };
+
+ if (adminEl) {
+ initialState.adminState = Object.freeze(parseDatasetToProps(adminEl.dataset));
+ }
+
return new Vue({
el,
+ store: createStore(initialState),
render(createElement) {
- return createElement(IntegrationForm, {
- props: {
- activeToggleProps: {
- initialActivated: activated,
- },
- showActive,
- type,
- triggerFieldsProps: {
- initialTriggerCommit: commitEvents,
- initialTriggerMergeRequest: mergeRequestEvents,
- initialEnableComments: enableComments,
- initialCommentDetail: commentDetail,
- },
- triggerEvents: JSON.parse(triggerEvents),
- fields: JSON.parse(fields),
- },
- });
+ return createElement(IntegrationForm);
},
});
};
diff --git a/app/assets/javascripts/integrations/edit/store/actions.js b/app/assets/javascripts/integrations/edit/store/actions.js
new file mode 100644
index 00000000000..3decdaab55d
--- /dev/null
+++ b/app/assets/javascripts/integrations/edit/store/actions.js
@@ -0,0 +1,4 @@
+import * as types from './mutation_types';
+
+// eslint-disable-next-line import/prefer-default-export
+export const setOverride = ({ commit }, override) => commit(types.SET_OVERRIDE, override);
diff --git a/app/assets/javascripts/integrations/edit/store/getters.js b/app/assets/javascripts/integrations/edit/store/getters.js
new file mode 100644
index 00000000000..b68bd668980
--- /dev/null
+++ b/app/assets/javascripts/integrations/edit/store/getters.js
@@ -0,0 +1,6 @@
+export const isInheriting = state => (state.adminState === null ? false : !state.override);
+
+export const propsSource = (state, getters) =>
+ getters.isInheriting ? state.adminState : state.customState;
+
+export const currentKey = (state, getters) => (getters.isInheriting ? 'admin' : 'custom');
diff --git a/app/assets/javascripts/integrations/edit/store/index.js b/app/assets/javascripts/integrations/edit/store/index.js
new file mode 100644
index 00000000000..eea5e48780d
--- /dev/null
+++ b/app/assets/javascripts/integrations/edit/store/index.js
@@ -0,0 +1,17 @@
+import Vue from 'vue';
+import Vuex from 'vuex';
+import * as actions from './actions';
+import * as getters from './getters';
+import mutations from './mutations';
+import createState from './state';
+
+Vue.use(Vuex);
+
+// eslint-disable-next-line import/prefer-default-export
+export const createStore = (initialState = {}) =>
+ new Vuex.Store({
+ actions,
+ getters,
+ mutations,
+ state: createState(initialState),
+ });
diff --git a/app/assets/javascripts/integrations/edit/store/mutation_types.js b/app/assets/javascripts/integrations/edit/store/mutation_types.js
new file mode 100644
index 00000000000..274afe3fb49
--- /dev/null
+++ b/app/assets/javascripts/integrations/edit/store/mutation_types.js
@@ -0,0 +1,2 @@
+// eslint-disable-next-line import/prefer-default-export
+export const SET_OVERRIDE = 'SET_OVERRIDE';
diff --git a/app/assets/javascripts/integrations/edit/store/mutations.js b/app/assets/javascripts/integrations/edit/store/mutations.js
new file mode 100644
index 00000000000..8757d415197
--- /dev/null
+++ b/app/assets/javascripts/integrations/edit/store/mutations.js
@@ -0,0 +1,7 @@
+import * as types from './mutation_types';
+
+export default {
+ [types.SET_OVERRIDE](state, override) {
+ state.override = override;
+ },
+};
diff --git a/app/assets/javascripts/integrations/edit/store/state.js b/app/assets/javascripts/integrations/edit/store/state.js
new file mode 100644
index 00000000000..95c1a2be500
--- /dev/null
+++ b/app/assets/javascripts/integrations/edit/store/state.js
@@ -0,0 +1,9 @@
+export default ({ adminState = null, customState = {} } = {}) => {
+ const override = adminState !== null ? adminState.id !== customState.inheritFromId : false;
+
+ return {
+ override,
+ adminState,
+ customState,
+ };
+};
diff --git a/app/assets/javascripts/integrations/integration_settings_form.js b/app/assets/javascripts/integrations/integration_settings_form.js
index 8844cbebe85..837409a91ca 100644
--- a/app/assets/javascripts/integrations/integration_settings_form.js
+++ b/app/assets/javascripts/integrations/integration_settings_form.js
@@ -22,7 +22,10 @@ export default class IntegrationSettingsForm {
init() {
// Init Vue component
- initForm(document.querySelector('.js-vue-integration-settings'));
+ initForm(
+ document.querySelector('.js-vue-integration-settings'),
+ document.querySelector('.js-vue-admin-integration-settings'),
+ );
eventHub.$on('toggle', active => {
this.formActive = active;
this.handleServiceToggle();
diff --git a/app/assets/javascripts/issuable_bulk_update_actions.js b/app/assets/javascripts/issuable_bulk_update_actions.js
index 01ea3eee16e..d968e9e5235 100644
--- a/app/assets/javascripts/issuable_bulk_update_actions.js
+++ b/app/assets/javascripts/issuable_bulk_update_actions.js
@@ -1,7 +1,5 @@
-/* eslint-disable consistent-return, func-names, array-callback-return */
-
import $ from 'jquery';
-import { intersection } from 'lodash';
+import { difference, intersection, union } from 'lodash';
import axios from './lib/utils/axios_utils';
import Flash from './flash';
import { __ } from './locale';
@@ -36,43 +34,6 @@ export default {
return new Flash(__('Issue update failed'));
},
- getSelectedIssues() {
- return this.issues.has('.selected-issuable:checked');
- },
-
- getLabelsFromSelection() {
- const labels = [];
- this.getSelectedIssues().map(function() {
- const labelsData = $(this).data('labels');
- if (labelsData) {
- return labelsData.map(labelId => {
- if (labels.indexOf(labelId) === -1) {
- return labels.push(labelId);
- }
- });
- }
- });
- return labels;
- },
-
- /**
- * Will return only labels that were marked previously and the user has unmarked
- * @return {Array} Label IDs
- */
-
- getUnmarkedIndeterminedLabels() {
- const result = [];
- const labelsToKeep = this.$labelDropdown.data('indeterminate');
-
- this.getLabelsFromSelection().forEach(id => {
- if (labelsToKeep.indexOf(id) === -1) {
- result.push(id);
- }
- });
-
- return result;
- },
-
/**
* Simple form serialization, it will return just what we need
* Returns key/value pairs from form data
@@ -86,40 +47,44 @@ export default {
milestone_id: this.form.find('input[name="update[milestone_id]"]').val(),
issuable_ids: this.form.find('input[name="update[issuable_ids]"]').val(),
subscription_event: this.form.find('input[name="update[subscription_event]"]').val(),
+ health_status: this.form.find('input[name="update[health_status]"]').val(),
+ epic_id: this.form.find('input[name="update[epic_id]"]').val(),
add_label_ids: [],
remove_label_ids: [],
},
};
if (this.willUpdateLabels) {
- formData.update.add_label_ids = this.$labelDropdown.data('marked');
- formData.update.remove_label_ids = this.$labelDropdown.data('unmarked');
+ formData.update.add_label_ids = this.$labelDropdown.data('user-checked');
+ formData.update.remove_label_ids = this.$labelDropdown.data('user-unchecked');
}
return formData;
},
setOriginalDropdownData() {
const $labelSelect = $('.bulk-update .js-label-select');
- const dirtyLabelIds = $labelSelect.data('marked') || [];
- const chosenLabelIds = [...this.getOriginalMarkedIds(), ...dirtyLabelIds];
-
- $labelSelect.data('common', this.getOriginalCommonIds());
- $labelSelect.data('marked', chosenLabelIds);
- $labelSelect.data('indeterminate', this.getOriginalIndeterminateIds());
+ const userCheckedIds = $labelSelect.data('user-checked') || [];
+ const userUncheckedIds = $labelSelect.data('user-unchecked') || [];
+
+ // Common labels plus user checked labels minus user unchecked labels
+ const checkedIdsToShow = difference(
+ union(this.getOriginalCommonIds(), userCheckedIds),
+ userUncheckedIds,
+ );
+
+ // Indeterminate labels minus user checked labels minus user unchecked labels
+ const indeterminateIdsToShow = difference(
+ this.getOriginalIndeterminateIds(),
+ userCheckedIds,
+ userUncheckedIds,
+ );
+
+ $labelSelect.data('marked', checkedIdsToShow);
+ $labelSelect.data('indeterminate', indeterminateIdsToShow);
},
// From issuable's initial bulk selection
getOriginalCommonIds() {
const labelIds = [];
-
- this.getElement('.selected-issuable:checked').each((i, el) => {
- labelIds.push(this.getElement(`#${this.prefixId}${el.dataset.id}`).data('labels'));
- });
- return intersection.apply(this, labelIds);
- },
-
- // From issuable's initial bulk selection
- getOriginalMarkedIds() {
- const labelIds = [];
this.getElement('.selected-issuable:checked').each((i, el) => {
labelIds.push(this.getElement(`#${this.prefixId}${el.dataset.id}`).data('labels'));
});
diff --git a/app/assets/javascripts/issuable_bulk_update_sidebar.js b/app/assets/javascripts/issuable_bulk_update_sidebar.js
index 50562688c53..85c2a370ff3 100644
--- a/app/assets/javascripts/issuable_bulk_update_sidebar.js
+++ b/app/assets/javascripts/issuable_bulk_update_sidebar.js
@@ -63,6 +63,22 @@ export default class IssuableBulkUpdateSidebar {
new MilestoneSelect();
issueStatusSelect();
subscriptionSelect();
+
+ if (IS_EE) {
+ import('ee/vue_shared/components/sidebar/health_status_select/health_status_bundle')
+ .then(({ default: HealthStatusSelect }) => {
+ HealthStatusSelect();
+ })
+ .catch(() => {});
+ }
+
+ if (IS_EE) {
+ import('ee/vue_shared/components/sidebar/epics_select/epics_select_bundle')
+ .then(({ default: EpicSelect }) => {
+ EpicSelect();
+ })
+ .catch(() => {});
+ }
}
setupBulkUpdateActions() {
diff --git a/app/assets/javascripts/issuable_suggestions/components/app.vue b/app/assets/javascripts/issuable_suggestions/components/app.vue
index 67d10b797fb..810ca7ac1bd 100644
--- a/app/assets/javascripts/issuable_suggestions/components/app.vue
+++ b/app/assets/javascripts/issuable_suggestions/components/app.vue
@@ -84,7 +84,7 @@ export default {
v-for="(suggestion, index) in issues"
:key="suggestion.id"
:class="{
- 'append-bottom-default': index !== issues.length - 1,
+ 'gl-mb-3': index !== issues.length - 1,
}"
>
<suggestion :suggestion="suggestion" />
diff --git a/app/assets/javascripts/issuable_suggestions/components/item.vue b/app/assets/javascripts/issuable_suggestions/components/item.vue
index 51904c64085..dfadb9d2b24 100644
--- a/app/assets/javascripts/issuable_suggestions/components/item.vue
+++ b/app/assets/javascripts/issuable_suggestions/components/item.vue
@@ -75,7 +75,11 @@ export default {
name="eye-slash"
class="suggestion-help-hover mr-1 suggestion-confidential"
/>
- <gl-link :href="suggestion.webUrl" target="_blank" class="suggestion bold str-truncated-100">
+ <gl-link
+ :href="suggestion.webUrl"
+ target="_blank"
+ class="suggestion bold str-truncated-100 gl-text-gray-900!"
+ >
{{ suggestion.title }}
</gl-link>
</div>
diff --git a/app/assets/javascripts/issuables_list/components/issuable.vue b/app/assets/javascripts/issuables_list/components/issuable.vue
index 947c7518289..b7f4292a126 100644
--- a/app/assets/javascripts/issuables_list/components/issuable.vue
+++ b/app/assets/javascripts/issuables_list/components/issuable.vue
@@ -3,8 +3,11 @@
* This is tightly coupled to projects/issues/_issue.html.haml,
* any changes done to the haml need to be reflected here.
*/
+
+// TODO: need to move this component to graphql - https://gitlab.com/gitlab-org/gitlab/-/issues/221246
import { escape, isNumber } from 'lodash';
-import { GlLink, GlTooltipDirective as GlTooltip, GlSprintf } from '@gitlab/ui';
+import { GlLink, GlTooltipDirective as GlTooltip, GlSprintf, GlLabel, GlIcon } from '@gitlab/ui';
+import jiraLogo from '@gitlab/svgs/dist/illustrations/logos/jira.svg';
import {
dateInWords,
formatDate,
@@ -16,22 +19,26 @@ import {
import { sprintf, __ } from '~/locale';
import initUserPopovers from '~/user_popovers';
import { mergeUrlParams } from '~/lib/utils/url_utility';
-import Icon from '~/vue_shared/components/icon.vue';
import IssueAssignees from '~/vue_shared/components/issue/issue_assignees.vue';
+import { isScopedLabel } from '~/lib/utils/common_utils';
+import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
export default {
i18n: {
openedAgo: __('opened %{timeAgoString} by %{user}'),
+ openedAgoJira: __('opened %{timeAgoString} by %{user} in Jira'),
},
components: {
- Icon,
IssueAssignees,
GlLink,
+ GlLabel,
+ GlIcon,
GlSprintf,
},
directives: {
GlTooltip,
},
+ mixins: [glFeatureFlagsMixin()],
props: {
issuable: {
type: Object,
@@ -55,14 +62,19 @@ export default {
},
},
},
+ data() {
+ return {
+ jiraLogo,
+ };
+ },
computed: {
milestoneLink() {
const { title } = this.issuable.milestone;
return this.issuableLink({ milestone_title: title });
},
- hasLabels() {
- return Boolean(this.issuable.labels && this.issuable.labels.length);
+ scopedLabelsAvailable() {
+ return this.glFeatures.scopedLabels;
},
hasWeight() {
return isNumber(this.issuable.weight);
@@ -82,6 +94,12 @@ export default {
isClosed() {
return this.issuable.state === 'closed';
},
+ isJiraIssue() {
+ return this.issuable.external_tracker === 'jira';
+ },
+ linkTarget() {
+ return this.isJiraIssue ? '_blank' : null;
+ },
issueCreatedToday() {
return getDayDifference(new Date(this.issuable.created_at), new Date()) < 1;
},
@@ -147,14 +165,14 @@ export default {
value: this.issuable.upvotes,
title: __('Upvotes'),
class: 'js-upvotes',
- faicon: 'fa-thumbs-up',
+ icon: 'thumb-up',
},
{
key: 'downvotes',
value: this.issuable.downvotes,
title: __('Downvotes'),
class: 'js-downvotes',
- faicon: 'fa-thumbs-down',
+ icon: 'thumb-down',
},
];
},
@@ -165,16 +183,17 @@ export default {
initUserPopovers([this.$refs.openedAgoByContainer.$el]);
},
methods: {
- labelStyle(label) {
- return {
- backgroundColor: label.color,
- color: label.text_color,
- };
- },
issuableLink(params) {
return mergeUrlParams(params, this.baseUrl);
},
+ isScoped({ name }) {
+ return isScopedLabel({ title: name }) && this.scopedLabelsAvailable;
+ },
labelHref({ name }) {
+ if (this.isJiraIssue) {
+ return this.issuableLink({ 'labels[]': name });
+ }
+
return this.issuableLink({ 'label_name[]': name });
},
onSelect(ev) {
@@ -214,14 +233,23 @@ export default {
<div class="flex-grow-1">
<div class="title">
<span class="issue-title-text">
- <i
+ <gl-icon
v-if="issuable.confidential"
v-gl-tooltip
- class="fa fa-eye-slash"
+ name="eye-slash"
+ class="gl-vertical-align-text-bottom"
+ :size="16"
:title="$options.confidentialTooltipText"
:aria-label="$options.confidentialTooltipText"
- ></i>
- <gl-link :href="issuable.web_url">{{ issuable.title }}</gl-link>
+ />
+ <gl-link :href="issuable.web_url" :target="linkTarget" data-testid="issuable-title">
+ {{ issuable.title }}
+ <gl-icon
+ v-if="isJiraIssue"
+ name="external-link"
+ class="gl-vertical-align-text-bottom"
+ />
+ </gl-link>
</span>
<span v-if="issuable.has_tasks" class="ml-1 task-status d-none d-sm-inline-block">
{{ issuable.task_status }}
@@ -229,11 +257,21 @@ export default {
</div>
<div class="issuable-info">
- <span class="js-ref-path">{{ referencePath }}</span>
+ <span class="js-ref-path">
+ <span
+ v-if="isJiraIssue"
+ class="svg-container jira-logo-container"
+ data-testid="jira-logo"
+ v-html="jiraLogo"
+ ></span>
+ {{ referencePath }}
+ </span>
<span data-testid="openedByMessage" class="d-none d-sm-inline-block mr-1">
&middot;
- <gl-sprintf :message="$options.i18n.openedAgo">
+ <gl-sprintf
+ :message="isJiraIssue ? $options.i18n.openedAgoJira : $options.i18n.openedAgo"
+ >
<template #timeAgoString>
<span>{{ issuableCreatedAt }}</span>
</template>
@@ -242,6 +280,7 @@ export default {
ref="openedAgoByContainer"
v-bind="popoverDataAttrs"
:href="issuableAuthor.web_url"
+ :target="linkTarget"
>
{{ issuableAuthor.name }}
</gl-link>
@@ -271,30 +310,29 @@ export default {
{{ dueDateWords }}
</span>
- <span v-if="hasLabels" class="js-labels">
- <gl-link
- v-for="label in issuable.labels"
- :key="label.id"
- class="label-link mr-1"
- :href="labelHref(label)"
- >
- <span
- v-gl-tooltip
- class="badge color-label"
- :style="labelStyle(label)"
- :title="label.description"
- >{{ label.name }}</span
- >
- </gl-link>
- </span>
+ <gl-label
+ v-for="label in issuable.labels"
+ :key="label.id"
+ data-qa-selector="issuable-label"
+ :target="labelHref(label)"
+ :background-color="label.color"
+ :description="label.description"
+ :color="label.text_color"
+ :title="label.name"
+ :scoped="isScoped(label)"
+ size="sm"
+ class="mr-1"
+ >{{ label.name }}</gl-label
+ >
<span
v-if="hasWeight"
v-gl-tooltip
:title="__('Weight')"
class="d-none d-sm-inline-block js-weight"
+ data-testid="weight"
>
- <icon name="weight" class="align-text-bottom" />
+ <gl-icon name="weight" class="align-text-bottom" />
{{ issuable.weight }}
</span>
</div>
@@ -303,7 +341,8 @@ export default {
<!-- Issuable meta -->
<div class="flex-shrink-0 d-flex flex-column align-items-end justify-content-center">
<div class="controls d-flex">
- <span v-if="isClosed" class="issuable-status">{{ __('CLOSED') }}</span>
+ <span v-if="isJiraIssue" data-testid="issuable-status">{{ issuable.status }}</span>
+ <span v-else-if="isClosed" class="issuable-status">{{ __('CLOSED') }}</span>
<issue-assignees
:assignees="issuable.assignees"
@@ -318,23 +357,23 @@ export default {
v-if="meta.value"
:key="meta.key"
v-gl-tooltip
- :class="['d-none d-sm-inline-block ml-2', meta.class]"
+ :class="['d-none d-sm-inline-block ml-2 vertical-align-middle', meta.class]"
:title="meta.title"
>
- <icon v-if="meta.icon" :name="meta.icon" />
- <i v-else :class="['fa', meta.faicon]"></i>
+ <gl-icon v-if="meta.icon" :name="meta.icon" />
{{ meta.value }}
</span>
</template>
<gl-link
+ v-if="!isJiraIssue"
v-gl-tooltip
class="ml-2 js-notes"
:href="`${issuable.web_url}#notes`"
:title="__('Comments')"
:class="{ 'no-comments': hasNoComments }"
>
- <i class="fa fa-comments"></i>
+ <gl-icon name="comments" class="gl-vertical-align-text-bottom" />
{{ userNotesCount }}
</gl-link>
</div>
diff --git a/app/assets/javascripts/issuables_list/components/issuable_list_root_app.vue b/app/assets/javascripts/issuables_list/components/issuable_list_root_app.vue
index 49a89d15c35..cc90d23eda7 100644
--- a/app/assets/javascripts/issuables_list/components/issuable_list_root_app.vue
+++ b/app/assets/javascripts/issuables_list/components/issuable_list_root_app.vue
@@ -1,10 +1,13 @@
<script>
import { GlAlert, GlLabel } from '@gitlab/ui';
+import { last } from 'lodash';
+import { n__ } from '~/locale';
import getIssuesListDetailsQuery from '../queries/get_issues_list_details.query.graphql';
import {
calculateJiraImportLabel,
- isFinished,
isInProgress,
+ setFinishedAlertHideMap,
+ shouldShowFinishedAlert,
} from '~/jira_import/utils/jira_import_utils';
export default {
@@ -33,8 +36,6 @@ export default {
},
data() {
return {
- isFinishedAlertShowing: true,
- isInProgressAlertShowing: true,
jiraImport: {},
};
},
@@ -46,36 +47,42 @@ export default {
fullPath: this.projectPath,
};
},
- update: ({ project }) => ({
- isInProgress: isInProgress(project.jiraImportStatus),
- isFinished: isFinished(project.jiraImportStatus),
- label: calculateJiraImportLabel(
+ update: ({ project }) => {
+ const label = calculateJiraImportLabel(
project.jiraImports.nodes,
project.issues.nodes.flatMap(({ labels }) => labels.nodes),
- ),
- }),
+ );
+ return {
+ importedIssuesCount: last(project.jiraImports.nodes)?.importedIssuesCount,
+ label,
+ shouldShowFinishedAlert: shouldShowFinishedAlert(label.title, project.jiraImportStatus),
+ shouldShowInProgressAlert: isInProgress(project.jiraImportStatus),
+ };
+ },
skip() {
return !this.isJiraConfigured || !this.canEdit;
},
},
},
computed: {
+ finishedMessage() {
+ return n__(
+ '%d issue successfully imported with the label',
+ '%d issues successfully imported with the label',
+ this.jiraImport.importedIssuesCount,
+ );
+ },
labelTarget() {
return `${this.issuesPath}?label_name[]=${encodeURIComponent(this.jiraImport.label.title)}`;
},
- shouldShowFinishedAlert() {
- return this.isFinishedAlertShowing && this.jiraImport.isFinished;
- },
- shouldShowInProgressAlert() {
- return this.isInProgressAlertShowing && this.jiraImport.isInProgress;
- },
},
methods: {
hideFinishedAlert() {
- this.isFinishedAlertShowing = false;
+ setFinishedAlertHideMap(this.jiraImport.label.title);
+ this.jiraImport.shouldShowFinishedAlert = false;
},
hideInProgressAlert() {
- this.isInProgressAlertShowing = false;
+ this.jiraImport.shouldShowInProgressAlert = false;
},
},
};
@@ -83,11 +90,16 @@ export default {
<template>
<div class="issuable-list-root">
- <gl-alert v-if="shouldShowInProgressAlert" @dismiss="hideInProgressAlert">
+ <gl-alert v-if="jiraImport.shouldShowInProgressAlert" @dismiss="hideInProgressAlert">
{{ __('Import in progress. Refresh page to see newly added issues.') }}
</gl-alert>
- <gl-alert v-if="shouldShowFinishedAlert" variant="success" @dismiss="hideFinishedAlert">
- {{ __('Issues successfully imported with the label') }}
+
+ <gl-alert
+ v-if="jiraImport.shouldShowFinishedAlert"
+ variant="success"
+ @dismiss="hideFinishedAlert"
+ >
+ {{ finishedMessage }}
<gl-label
:background-color="jiraImport.label.color"
scoped
diff --git a/app/assets/javascripts/issuables_list/components/issuables_list_app.vue b/app/assets/javascripts/issuables_list/components/issuables_list_app.vue
index 1c395fd9795..21aeb2ca143 100644
--- a/app/assets/javascripts/issuables_list/components/issuables_list_app.vue
+++ b/app/assets/javascripts/issuables_list/components/issuables_list_app.vue
@@ -12,8 +12,10 @@ import {
import { __ } from '~/locale';
import initManualOrdering from '~/manual_ordering';
import Issuable from './issuable.vue';
+import FilteredSearchBar from '~/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue';
import {
sortOrderMap,
+ availableSortOptionsJira,
RELATIVE_POSITION,
PAGE_SIZE,
PAGE_SIZE_MANUAL,
@@ -29,6 +31,7 @@ export default {
GlPagination,
GlSkeletonLoading,
Issuable,
+ FilteredSearchBar,
},
props: {
canBulkEdit: {
@@ -50,14 +53,25 @@ export default {
type: String,
required: true,
},
+ projectPath: {
+ type: String,
+ required: false,
+ default: '',
+ },
sortKey: {
type: String,
required: false,
default: '',
},
+ type: {
+ type: String,
+ required: false,
+ default: '',
+ },
},
data() {
return {
+ availableSortOptionsJira,
filters: {},
isBulkEditing: false,
issuables: [],
@@ -118,6 +132,45 @@ export default {
baseUrl() {
return window.location.href.replace(/(\?.*)?(#.*)?$/, '');
},
+ paginationNext() {
+ return this.page + 1;
+ },
+ paginationPrev() {
+ return this.page - 1;
+ },
+ paginationProps() {
+ const paginationProps = { value: this.page };
+
+ if (this.totalItems) {
+ return {
+ ...paginationProps,
+ perPage: this.itemsPerPage,
+ totalItems: this.totalItems,
+ };
+ }
+
+ return {
+ ...paginationProps,
+ prevPage: this.paginationPrev,
+ nextPage: this.paginationNext,
+ };
+ },
+ isJira() {
+ return this.type === 'jira';
+ },
+ initialFilterValue() {
+ const value = [];
+ const { search } = this.getQueryObject();
+
+ if (search) {
+ value.push(search);
+ }
+ return value;
+ },
+ initialSortBy() {
+ const { sort } = this.getQueryObject();
+ return sort || 'created_desc';
+ },
},
watch: {
selection() {
@@ -222,9 +275,13 @@ export default {
const {
label_name: labels,
milestone_title: milestoneTitle,
+ 'not[label_name]': excludedLabels,
+ 'not[milestone_title]': excludedMilestone,
...filters
} = this.getQueryObject();
+ // TODO: https://gitlab.com/gitlab-org/gitlab/-/issues/227880
+
if (milestoneTitle) {
filters.milestone = milestoneTitle;
}
@@ -235,58 +292,104 @@ export default {
filters.state = 'opened';
}
+ if (excludedLabels) {
+ filters['not[labels]'] = excludedLabels;
+ }
+
+ if (excludedMilestone) {
+ filters['not[milestone]'] = excludedMilestone;
+ }
+
Object.assign(filters, sortOrderMap[this.sortKey]);
this.filters = filters;
},
+ refetchIssuables() {
+ const ignored = ['utf8'];
+ const params = omit(this.filters, ignored);
+
+ historyPushState(setUrlParams(params, window.location.href, true, true));
+ this.fetchIssuables();
+ },
+ handleFilter(filters) {
+ let search = null;
+
+ filters.forEach(filter => {
+ if (typeof filter === 'string') {
+ search = filter;
+ }
+ });
+
+ this.filters.search = search;
+ this.page = 1;
+
+ this.refetchIssuables();
+ },
+ handleSort(sort) {
+ this.filters.sort = sort;
+ this.page = 1;
+
+ this.refetchIssuables();
+ },
},
};
</script>
<template>
- <ul v-if="loading" class="content-list">
- <li v-for="n in $options.LOADING_LIST_ITEMS_LENGTH" :key="n" class="issue gl-px-5! gl-py-5!">
- <gl-skeleton-loading />
- </li>
- </ul>
- <div v-else-if="issuables.length">
- <div v-if="isBulkEditing" class="issue px-3 py-3 border-bottom border-light">
- <input type="checkbox" :checked="allIssuablesSelected" class="mr-2" @click="onSelectAll" />
- <strong>{{ __('Select all') }}</strong>
- </div>
- <ul
- class="content-list issuable-list issues-list"
- :class="{ 'manual-ordering': isManualOrdering }"
- >
- <issuable
- v-for="issuable in issuables"
- :key="issuable.id"
- class="pr-3"
- :class="{ 'user-can-drag': isManualOrdering }"
- :issuable="issuable"
- :is-bulk-editing="isBulkEditing"
- :selected="isSelected(issuable.id)"
- :base-url="baseUrl"
- @select="onSelectIssuable"
- />
+ <div>
+ <filtered-search-bar
+ v-if="isJira"
+ :namespace="projectPath"
+ :search-input-placeholder="__('Search Jira issues')"
+ :tokens="[]"
+ :sort-options="availableSortOptionsJira"
+ :initial-filter-value="initialFilterValue"
+ :initial-sort-by="initialSortBy"
+ class="row-content-block"
+ @onFilter="handleFilter"
+ @onSort="handleSort"
+ />
+ <ul v-if="loading" class="content-list">
+ <li v-for="n in $options.LOADING_LIST_ITEMS_LENGTH" :key="n" class="issue gl-px-5! gl-py-5!">
+ <gl-skeleton-loading />
+ </li>
</ul>
- <div class="mt-3">
- <gl-pagination
- v-if="totalItems"
- :value="page"
- :per-page="itemsPerPage"
- :total-items="totalItems"
- class="justify-content-center"
- @input="onPaginate"
- />
+ <div v-else-if="issuables.length">
+ <div v-if="isBulkEditing" class="issue px-3 py-3 border-bottom border-light">
+ <input type="checkbox" :checked="allIssuablesSelected" class="mr-2" @click="onSelectAll" />
+ <strong>{{ __('Select all') }}</strong>
+ </div>
+ <ul
+ class="content-list issuable-list issues-list"
+ :class="{ 'manual-ordering': isManualOrdering }"
+ >
+ <issuable
+ v-for="issuable in issuables"
+ :key="issuable.id"
+ class="pr-3"
+ :class="{ 'user-can-drag': isManualOrdering }"
+ :issuable="issuable"
+ :is-bulk-editing="isBulkEditing"
+ :selected="isSelected(issuable.id)"
+ :base-url="baseUrl"
+ @select="onSelectIssuable"
+ />
+ </ul>
+ <div class="mt-3">
+ <gl-pagination
+ v-bind="paginationProps"
+ class="gl-justify-content-center"
+ @input="onPaginate"
+ />
+ </div>
</div>
+ <gl-empty-state
+ v-else
+ :title="emptyState.title"
+ :description="emptyState.description"
+ :svg-path="emptySvgPath"
+ :primary-button-link="emptyState.primaryLink"
+ :primary-button-text="emptyState.primaryText"
+ />
</div>
- <gl-empty-state
- v-else
- :title="emptyState.title"
- :description="emptyState.description"
- :svg-path="emptySvgPath"
- :primary-button-link="emptyState.primaryLink"
- :primary-button-text="emptyState.primaryText"
- />
</template>
diff --git a/app/assets/javascripts/issuables_list/constants.js b/app/assets/javascripts/issuables_list/constants.js
index 71b9c52c703..f008ba1bf4a 100644
--- a/app/assets/javascripts/issuables_list/constants.js
+++ b/app/assets/javascripts/issuables_list/constants.js
@@ -1,3 +1,5 @@
+import { __ } from '~/locale';
+
// Maps sort order as it appears in the URL query to API `order_by` and `sort` params.
const PRIORITY = 'priority';
const ASC = 'asc';
@@ -31,3 +33,24 @@ export const sortOrderMap = {
weight_desc: { order_by: WEIGHT, sort: DESC },
weight: { order_by: WEIGHT, sort: ASC },
};
+
+export const availableSortOptionsJira = [
+ {
+ id: 1,
+ title: __('Created date'),
+ sortDirection: {
+ descending: 'created_desc',
+ ascending: 'created_asc',
+ },
+ },
+ {
+ id: 2,
+ title: __('Last updated'),
+ sortDirection: {
+ descending: 'updated_desc',
+ ascending: 'updated_asc',
+ },
+ },
+];
+
+export const JIRA_IMPORT_SUCCESS_ALERT_HIDE_MAP_KEY = 'jira-import-success-alert-hide-map';
diff --git a/app/assets/javascripts/issuables_list/index.js b/app/assets/javascripts/issuables_list/index.js
index 6bfb885a8af..40252c10d5f 100644
--- a/app/assets/javascripts/issuables_list/index.js
+++ b/app/assets/javascripts/issuables_list/index.js
@@ -36,7 +36,7 @@ function mountIssuableListRootApp() {
}
function mountIssuablesListApp() {
- if (!gon.features?.vueIssuablesList) {
+ if (!gon.features?.vueIssuablesList && !gon.features?.jiraIssuesIntegration) {
return;
}
diff --git a/app/assets/javascripts/issuables_list/queries/get_issues_list_details.query.graphql b/app/assets/javascripts/issuables_list/queries/get_issues_list_details.query.graphql
index b62b9b2af60..8f9b888d19b 100644
--- a/app/assets/javascripts/issuables_list/queries/get_issues_list_details.query.graphql
+++ b/app/assets/javascripts/issuables_list/queries/get_issues_list_details.query.graphql
@@ -1,5 +1,3 @@
-#import "~/jira_import/queries/jira_import.fragment.graphql"
-
query($fullPath: ID!) {
project(fullPath: $fullPath) {
issues {
@@ -15,7 +13,8 @@ query($fullPath: ID!) {
jiraImportStatus
jiraImports {
nodes {
- ...JiraImport
+ importedIssuesCount
+ jiraProjectKey
}
}
}
diff --git a/app/assets/javascripts/issue.js b/app/assets/javascripts/issue.js
index 252e8e92f5e..a01faeb1c8d 100644
--- a/app/assets/javascripts/issue.js
+++ b/app/assets/javascripts/issue.js
@@ -11,7 +11,7 @@ import { __ } from './locale';
export default class Issue {
constructor() {
- if ($('a.btn-close').length) this.initIssueBtnEventListeners();
+ if ($('.btn-close, .btn-reopen').length) this.initIssueBtnEventListeners();
if ($('.js-close-blocked-issue-warning').length) this.initIssueWarningBtnEventListener();
@@ -32,8 +32,8 @@ export default class Issue {
Issue.initRelatedBranches();
}
- this.closeButtons = $('a.btn-close');
- this.reopenButtons = $('a.btn-reopen');
+ this.closeButtons = $('.btn-close');
+ this.reopenButtons = $('.btn-reopen');
this.initCloseReopenReport();
@@ -103,7 +103,7 @@ export default class Issue {
// NOTE: data attribute seems unnecessary but is actually necessary
return $('.js-issuable-buttons[data-action="close-reopen"]').on(
'click',
- 'a.btn-close, a.btn-reopen, a.btn-close-anyway',
+ '.btn-close, .btn-reopen, .btn-close-anyway',
e => {
e.preventDefault();
e.stopImmediatePropagation();
@@ -120,7 +120,7 @@ export default class Issue {
} else {
this.disableCloseReopenButton($button);
- const url = $button.attr('href');
+ const url = $button.data('endpoint');
return axios
.put(url)
diff --git a/app/assets/javascripts/issue_show/components/app.vue b/app/assets/javascripts/issue_show/components/app.vue
index 09acfd1cfae..bcf5dc2aaaf 100644
--- a/app/assets/javascripts/issue_show/components/app.vue
+++ b/app/assets/javascripts/issue_show/components/app.vue
@@ -420,7 +420,7 @@ export default {
<transition name="issuable-header-slide">
<div
v-if="shouldShowStickyHeader"
- class="issue-sticky-header gl-fixed gl-z-index-2 gl-bg-white gl-border-1 gl-border-b-solid gl-border-b-gray-200 gl-py-3"
+ class="issue-sticky-header gl-fixed gl-z-index-3 gl-bg-white gl-border-1 gl-border-b-solid gl-border-b-gray-100 gl-py-3"
data-testid="issue-sticky-header"
>
<div
diff --git a/app/assets/javascripts/issue_show/components/edit_actions.vue b/app/assets/javascripts/issue_show/components/edit_actions.vue
index 588ae655de4..4ee44e50d2f 100644
--- a/app/assets/javascripts/issue_show/components/edit_actions.vue
+++ b/app/assets/javascripts/issue_show/components/edit_actions.vue
@@ -63,7 +63,7 @@ export default {
</script>
<template>
- <div class="prepend-top-default append-bottom-default clearfix">
+ <div class="gl-mt-3 gl-mb-3 clearfix">
<button
:class="{ disabled: formState.updateLoading || !isSubmitEnabled }"
:disabled="formState.updateLoading || !isSubmitEnabled"
@@ -81,7 +81,7 @@ export default {
v-if="shouldShowDeleteButton"
:class="{ disabled: deleteLoading }"
:disabled="deleteLoading"
- class="btn btn-danger float-right append-right-default qa-delete-button"
+ class="btn btn-danger float-right gl-mr-3 qa-delete-button"
type="button"
@click="deleteIssuable"
>
diff --git a/app/assets/javascripts/issue_show/components/fields/description.vue b/app/assets/javascripts/issue_show/components/fields/description.vue
index 35165c9b481..0de0060615b 100644
--- a/app/assets/javascripts/issue_show/components/fields/description.vue
+++ b/app/assets/javascripts/issue_show/components/fields/description.vue
@@ -55,7 +55,7 @@ export default {
class="note-textarea js-gfm-input js-autosize markdown-area
qa-description-textarea"
dir="auto"
- data-supports-quick-actions="false"
+ data-supports-quick-actions="true"
:aria-label="__('Description')"
:placeholder="__('Write a comment or drag your files here…')"
@keydown.meta.enter="updateIssuable"
diff --git a/app/assets/javascripts/issue_show/components/issuable_header_warnings.vue b/app/assets/javascripts/issue_show/components/issuable_header_warnings.vue
new file mode 100644
index 00000000000..b6816be9eb8
--- /dev/null
+++ b/app/assets/javascripts/issue_show/components/issuable_header_warnings.vue
@@ -0,0 +1,28 @@
+<script>
+import { mapState } from 'vuex';
+import Icon from '~/vue_shared/components/icon.vue';
+
+export default {
+ components: {
+ Icon,
+ },
+ computed: {
+ ...mapState({
+ confidential: ({ noteableData }) => noteableData.confidential,
+ dicussionLocked: ({ noteableData }) => noteableData.discussion_locked,
+ }),
+ },
+};
+</script>
+
+<template>
+ <div class="gl-display-inline-block">
+ <div v-if="confidential" class="issuable-warning-icon inline">
+ <icon class="icon" name="eye-slash" data-testid="confidential" />
+ </div>
+
+ <div v-if="dicussionLocked" class="issuable-warning-icon inline">
+ <icon class="icon" name="lock" data-testid="locked" />
+ </div>
+ </div>
+</template>
diff --git a/app/assets/javascripts/issue_show/components/pinned_links.vue b/app/assets/javascripts/issue_show/components/pinned_links.vue
index 4b50acceb62..a877aa2ac96 100644
--- a/app/assets/javascripts/issue_show/components/pinned_links.vue
+++ b/app/assets/javascripts/issue_show/components/pinned_links.vue
@@ -1,11 +1,10 @@
<script>
-import { GlLink } from '@gitlab/ui';
-import Icon from '~/vue_shared/components/icon.vue';
+import { GlButton } from '@gitlab/ui';
+import { STATUS_PAGE_PUBLISHED, JOIN_ZOOM_MEETING } from '../constants';
export default {
components: {
- Icon,
- GlLink,
+ GlButton,
},
props: {
zoomMeetingUrl: {
@@ -19,32 +18,46 @@ export default {
default: '',
},
},
+ computed: {
+ pinnedLinks() {
+ return [
+ {
+ id: 'publishedIncidentUrl',
+ url: this.publishedIncidentUrl,
+ text: STATUS_PAGE_PUBLISHED,
+ icon: 'tanuki',
+ },
+ {
+ id: 'zoomMeetingUrl',
+ url: this.zoomMeetingUrl,
+ text: JOIN_ZOOM_MEETING,
+ icon: 'brand-zoom',
+ },
+ ];
+ },
+ },
+ methods: {
+ needsPaddingClass(i) {
+ return i < this.pinnedLinks.length - 1;
+ },
+ },
};
</script>
<template>
<div class="border-bottom gl-mb-6 gl-display-flex gl-justify-content-start">
- <div v-if="publishedIncidentUrl" class="gl-pr-3">
- <gl-link
- :href="publishedIncidentUrl"
- target="_blank"
- class="btn btn-inverted btn-secondary btn-sm text-dark mb-3"
- data-testid="publishedIncidentUrl"
- >
- <icon name="tanuki" :size="14" />
- <strong class="vertical-align-top">{{ __('Published on status page') }}</strong>
- </gl-link>
- </div>
- <div v-if="zoomMeetingUrl">
- <gl-link
- :href="zoomMeetingUrl"
- target="_blank"
- class="btn btn-inverted btn-secondary btn-sm text-dark mb-3"
- data-testid="zoomMeetingUrl"
- >
- <icon name="brand-zoom" :size="14" />
- <strong class="vertical-align-top">{{ __('Join Zoom meeting') }}</strong>
- </gl-link>
- </div>
+ <template v-for="(link, i) in pinnedLinks">
+ <div v-if="link.url" :key="link.id" :class="{ 'gl-pr-3': needsPaddingClass(i) }">
+ <gl-button
+ :href="link.url"
+ target="_blank"
+ :icon="link.icon"
+ size="small"
+ class="gl-font-weight-bold gl-mb-5"
+ :data-testid="link.id"
+ >{{ link.text }}</gl-button
+ >
+ </div>
+ </template>
</div>
</template>
diff --git a/app/assets/javascripts/issue_show/constants.js b/app/assets/javascripts/issue_show/constants.js
index d73cc8cf007..6bc6ed2b372 100644
--- a/app/assets/javascripts/issue_show/constants.js
+++ b/app/assets/javascripts/issue_show/constants.js
@@ -15,3 +15,6 @@ export const IssuableType = {
Epic: 'epic',
MergeRequest: 'merge_request',
};
+
+export const STATUS_PAGE_PUBLISHED = __('Published on status page');
+export const JOIN_ZOOM_MEETING = __('Join Zoom meeting');
diff --git a/app/assets/javascripts/issue_show/index.js b/app/assets/javascripts/issue_show/index.js
index e170d338408..fe4ff133145 100644
--- a/app/assets/javascripts/issue_show/index.js
+++ b/app/assets/javascripts/issue_show/index.js
@@ -1,6 +1,8 @@
import Vue from 'vue';
import issuableApp from './components/app.vue';
+import IssuableHeaderWarnings from './components/issuable_header_warnings.vue';
import { parseIssuableData } from './utils/parse_data';
+import { store } from '~/notes/stores';
export default function initIssueableApp() {
return new Vue({
@@ -15,3 +17,13 @@ export default function initIssueableApp() {
},
});
}
+
+export function issuableHeaderWarnings() {
+ return new Vue({
+ el: document.getElementById('js-issuable-header-warnings'),
+ store,
+ render(createElement) {
+ return createElement(IssuableHeaderWarnings);
+ },
+ });
+}
diff --git a/app/assets/javascripts/jira_import/components/jira_import_app.vue b/app/assets/javascripts/jira_import/components/jira_import_app.vue
index ef0fc4716dd..6222bd28c9d 100644
--- a/app/assets/javascripts/jira_import/components/jira_import_app.vue
+++ b/app/assets/javascripts/jira_import/components/jira_import_app.vue
@@ -3,6 +3,7 @@ import { GlAlert, GlLoadingIcon, GlSprintf } from '@gitlab/ui';
import { last } from 'lodash';
import { __ } from '~/locale';
import getJiraImportDetailsQuery from '../queries/get_jira_import_details.query.graphql';
+import getJiraUserMappingMutation from '../queries/get_jira_user_mapping.mutation.graphql';
import initiateJiraImportMutation from '../queries/initiate_jira_import.mutation.graphql';
import { addInProgressImportToStore } from '../utils/cache_update';
import { isInProgress, extractJiraProjectsOptions } from '../utils/jira_import_utils';
@@ -37,6 +38,10 @@ export default {
type: String,
required: true,
},
+ projectId: {
+ type: String,
+ required: true,
+ },
projectPath: {
type: String,
required: true,
@@ -48,10 +53,12 @@ export default {
},
data() {
return {
+ isSubmitting: false,
jiraImportDetails: {},
+ selectedProject: undefined,
+ userMappings: [],
errorMessage: '',
showAlert: false,
- selectedProject: undefined,
};
},
apollo: {
@@ -89,15 +96,43 @@ export default {
: 'jira-import::KEY-1';
},
},
+ mounted() {
+ if (this.isJiraConfigured) {
+ this.$apollo
+ .mutate({
+ mutation: getJiraUserMappingMutation,
+ variables: {
+ input: {
+ projectPath: this.projectPath,
+ startAt: 1,
+ },
+ },
+ })
+ .then(({ data }) => {
+ if (data.jiraImportUsers.errors.length) {
+ this.setAlertMessage(data.jiraImportUsers.errors.join('. '));
+ } else {
+ this.userMappings = data.jiraImportUsers.jiraUsers;
+ }
+ })
+ .catch(() => this.setAlertMessage(__('There was an error retrieving the Jira users.')));
+ }
+ },
methods: {
initiateJiraImport(project) {
+ this.isSubmitting = true;
+
this.$apollo
.mutate({
mutation: initiateJiraImportMutation,
variables: {
input: {
- projectPath: this.projectPath,
jiraProjectKey: project,
+ projectPath: this.projectPath,
+ usersMapping: this.userMappings.map(({ gitlabId, jiraAccountId }) => ({
+ gitlabId,
+ jiraAccountId,
+ })),
},
},
update: (store, { data }) =>
@@ -110,7 +145,21 @@ export default {
this.selectedProject = undefined;
}
})
- .catch(() => this.setAlertMessage(__('There was an error importing the Jira project.')));
+ .catch(() => this.setAlertMessage(__('There was an error importing the Jira project.')))
+ .finally(() => {
+ this.isSubmitting = false;
+ });
+ },
+ updateMapping(jiraAccountId, gitlabId, gitlabUsername) {
+ this.userMappings = this.userMappings.map(userMapping =>
+ userMapping.jiraAccountId === jiraAccountId
+ ? {
+ ...userMapping,
+ gitlabId,
+ gitlabUsername,
+ }
+ : userMapping,
+ );
},
setAlertMessage(message) {
this.errorMessage = message;
@@ -155,9 +204,13 @@ export default {
v-else
v-model="selectedProject"
:import-label="importLabel"
+ :is-submitting="isSubmitting"
:issues-path="issuesPath"
:jira-projects="jiraImportDetails.projects"
+ :project-id="projectId"
+ :user-mappings="userMappings"
@initiateJiraImport="initiateJiraImport"
+ @updateMapping="updateMapping"
/>
</div>
</template>
diff --git a/app/assets/javascripts/jira_import/components/jira_import_form.vue b/app/assets/javascripts/jira_import/components/jira_import_form.vue
index c2fe7b29c28..24bfb49a7d1 100644
--- a/app/assets/javascripts/jira_import/components/jira_import_form.vue
+++ b/app/assets/javascripts/jira_import/components/jira_import_form.vue
@@ -1,22 +1,61 @@
<script>
-import { GlAvatar, GlButton, GlFormGroup, GlFormSelect, GlLabel } from '@gitlab/ui';
+import {
+ GlButton,
+ GlNewDropdown,
+ GlNewDropdownItem,
+ GlNewDropdownText,
+ GlFormGroup,
+ GlFormSelect,
+ GlIcon,
+ GlLabel,
+ GlLoadingIcon,
+ GlSearchBoxByType,
+ GlTable,
+} from '@gitlab/ui';
+import { debounce } from 'lodash';
+import axios from '~/lib/utils/axios_utils';
+import { __ } from '~/locale';
export default {
name: 'JiraImportForm',
components: {
- GlAvatar,
GlButton,
+ GlNewDropdown,
+ GlNewDropdownItem,
+ GlNewDropdownText,
GlFormGroup,
GlFormSelect,
+ GlIcon,
GlLabel,
+ GlLoadingIcon,
+ GlSearchBoxByType,
+ GlTable,
},
- currentUserAvatarUrl: gon.current_user_avatar_url,
currentUsername: gon.current_username,
+ dropdownLabel: __('The GitLab user to which the Jira user %{jiraDisplayName} will be mapped'),
+ tableConfig: [
+ {
+ key: 'jiraDisplayName',
+ label: __('Jira display name'),
+ },
+ {
+ key: 'arrow',
+ label: '',
+ },
+ {
+ key: 'gitlabUsername',
+ label: __('GitLab username'),
+ },
+ ],
props: {
importLabel: {
type: String,
required: true,
},
+ isSubmitting: {
+ type: Boolean,
+ required: true,
+ },
issuesPath: {
type: String,
required: true,
@@ -25,6 +64,14 @@ export default {
type: Array,
required: true,
},
+ projectId: {
+ type: String,
+ required: true,
+ },
+ userMappings: {
+ type: Array,
+ required: true,
+ },
value: {
type: String,
required: false,
@@ -33,10 +80,53 @@ export default {
},
data() {
return {
+ isFetching: false,
+ searchTerm: '',
selectState: null,
+ users: [],
};
},
+ computed: {
+ shouldShowNoMatchesFoundText() {
+ return !this.isFetching && this.users.length === 0;
+ },
+ },
+ watch: {
+ searchTerm: debounce(function debouncedUserSearch() {
+ this.searchUsers();
+ }, 500),
+ },
+ mounted() {
+ this.searchUsers()
+ .then(data => {
+ this.initialUsers = data;
+ })
+ .catch(() => {});
+ },
methods: {
+ searchUsers() {
+ const params = {
+ active: true,
+ project_id: this.projectId,
+ search: this.searchTerm,
+ };
+
+ this.isFetching = true;
+
+ return axios
+ .get('/-/autocomplete/users.json', { params })
+ .then(({ data }) => {
+ this.users = data;
+ return data;
+ })
+ .finally(() => {
+ this.isFetching = false;
+ });
+ },
+ resetDropdown() {
+ this.searchTerm = '';
+ this.users = this.initialUsers;
+ },
initiateJiraImport(event) {
event.preventDefault();
if (this.value) {
@@ -70,6 +160,7 @@ export default {
>
<gl-form-select
id="jira-project-select"
+ data-qa-selector="jira_project_dropdown"
class="mb-2"
:options="jiraProjects"
:state="selectState"
@@ -79,7 +170,7 @@ export default {
</gl-form-group>
<gl-form-group
- class="row align-items-center"
+ class="row gl-align-items-center gl-mb-6"
:label="__('Issue label')"
label-cols-sm="2"
label-for="jira-project-label"
@@ -93,50 +184,65 @@ export default {
/>
</gl-form-group>
- <hr />
+ <h4 class="gl-mb-4">{{ __('Jira-GitLab user mapping template') }}</h4>
- <p class="offset-md-1">
+ <p>
{{
__(
- "For each Jira issue successfully imported, we'll create a new GitLab issue with the following data:",
+ `Jira users have been matched with similar GitLab users.
+ This can be overwritten by selecting a GitLab user from the dropdown in the "GitLab
+ username" column.
+ If it wasn't possible to match a Jira user with a GitLab user, the dropdown defaults to
+ the user conducting the import.`,
)
}}
</p>
- <gl-form-group
- class="row align-items-center mb-1"
- :label="__('Title')"
- label-cols-sm="2"
- label-for="jira-project-title"
- >
- <p id="jira-project-title" class="mb-2">{{ __('jira.issue.summary') }}</p>
- </gl-form-group>
- <gl-form-group
- class="row align-items-center mb-1"
- :label="__('Reporter')"
- label-cols-sm="2"
- label-for="jira-project-reporter"
- >
- <gl-avatar
- id="jira-project-reporter"
- class="mb-2"
- :src="$options.currentUserAvatarUrl"
- :size="24"
- :aria-label="$options.currentUsername"
- />
- </gl-form-group>
- <gl-form-group
- class="row align-items-center mb-1"
- :label="__('Description')"
- label-cols-sm="2"
- label-for="jira-project-description"
- >
- <p id="jira-project-description" class="mb-2">{{ __('jira.issue.description.content') }}</p>
- </gl-form-group>
+ <gl-table :fields="$options.tableConfig" :items="userMappings" fixed>
+ <template #cell(arrow)>
+ <gl-icon name="arrow-right" :aria-label="__('Will be mapped to')" />
+ </template>
+ <template #cell(gitlabUsername)="data">
+ <gl-new-dropdown
+ :text="data.value || $options.currentUsername"
+ class="w-100"
+ :aria-label="
+ sprintf($options.dropdownLabel, { jiraDisplayName: data.item.jiraDisplayName })
+ "
+ @hide="resetDropdown"
+ >
+ <gl-search-box-by-type v-model.trim="searchTerm" class="m-2" />
+
+ <div v-if="isFetching" class="gl-text-center">
+ <gl-loading-icon />
+ </div>
+
+ <gl-new-dropdown-item
+ v-for="user in users"
+ v-else
+ :key="user.id"
+ @click="$emit('updateMapping', data.item.jiraAccountId, user.id, user.username)"
+ >
+ {{ user.username }} ({{ user.name }})
+ </gl-new-dropdown-item>
+
+ <gl-new-dropdown-text v-show="shouldShowNoMatchesFoundText" class="text-secondary">
+ {{ __('No matches found') }}
+ </gl-new-dropdown-text>
+ </gl-new-dropdown>
+ </template>
+ </gl-table>
<div class="footer-block row-content-block d-flex justify-content-between">
- <gl-button type="submit" category="primary" variant="success" class="js-no-auto-disable">
- {{ __('Next') }}
+ <gl-button
+ type="submit"
+ category="primary"
+ variant="success"
+ class="js-no-auto-disable"
+ :loading="isSubmitting"
+ data-qa-selector="jira_issues_import_button"
+ >
+ {{ __('Continue') }}
</gl-button>
<gl-button :href="issuesPath">{{ __('Cancel') }}</gl-button>
</div>
diff --git a/app/assets/javascripts/jira_import/index.js b/app/assets/javascripts/jira_import/index.js
index 924cc7e6864..695a237bf50 100644
--- a/app/assets/javascripts/jira_import/index.js
+++ b/app/assets/javascripts/jira_import/index.js
@@ -28,6 +28,7 @@ export default function mountJiraImportApp() {
isJiraConfigured: parseBoolean(el.dataset.isJiraConfigured),
issuesPath: el.dataset.issuesPath,
jiraIntegrationPath: el.dataset.jiraIntegrationPath,
+ projectId: el.dataset.projectId,
projectPath: el.dataset.projectPath,
setupIllustration: el.dataset.setupIllustration,
},
diff --git a/app/assets/javascripts/jira_import/queries/get_jira_user_mapping.mutation.graphql b/app/assets/javascripts/jira_import/queries/get_jira_user_mapping.mutation.graphql
new file mode 100644
index 00000000000..1f7c52eec58
--- /dev/null
+++ b/app/assets/javascripts/jira_import/queries/get_jira_user_mapping.mutation.graphql
@@ -0,0 +1,11 @@
+mutation($input: JiraImportUsersInput!) {
+ jiraImportUsers(input: $input) {
+ jiraUsers {
+ jiraAccountId
+ jiraDisplayName
+ jiraEmail
+ gitlabId
+ }
+ errors
+ }
+}
diff --git a/app/assets/javascripts/jira_import/utils/jira_import_utils.js b/app/assets/javascripts/jira_import/utils/jira_import_utils.js
index e82a3f44a29..a1186b087e1 100644
--- a/app/assets/javascripts/jira_import/utils/jira_import_utils.js
+++ b/app/assets/javascripts/jira_import/utils/jira_import_utils.js
@@ -1,4 +1,5 @@
import { last } from 'lodash';
+import { JIRA_IMPORT_SUCCESS_ALERT_HIDE_MAP_KEY } from '~/issuables_list/constants';
export const IMPORT_STATE = {
FAILED: 'failed',
@@ -68,3 +69,36 @@ export const calculateJiraImportLabel = (jiraImports, labels) => {
title,
};
};
+
+/**
+ * Calculates whether the Jira import success alert should be shown.
+ *
+ * @param {string} labelTitle - Jira import label, for checking localStorage
+ * @param {string} importStatus - Jira import status
+ * @returns {boolean} - A boolean indicating whether to show the success alert
+ */
+export const shouldShowFinishedAlert = (labelTitle, importStatus) => {
+ const finishedAlertHideMap =
+ JSON.parse(localStorage.getItem(JIRA_IMPORT_SUCCESS_ALERT_HIDE_MAP_KEY)) || {};
+
+ const shouldHide = finishedAlertHideMap[labelTitle];
+
+ return !shouldHide && isFinished(importStatus);
+};
+
+/**
+ * Updates the localStorage map to permanently hide the Jira import success alert
+ *
+ * @param {string} labelTitle - Jira import label, for checking localStorage
+ */
+export const setFinishedAlertHideMap = labelTitle => {
+ const finishedAlertHideMap =
+ JSON.parse(localStorage.getItem(JIRA_IMPORT_SUCCESS_ALERT_HIDE_MAP_KEY)) || {};
+
+ finishedAlertHideMap[labelTitle] = true;
+
+ localStorage.setItem(
+ JIRA_IMPORT_SUCCESS_ALERT_HIDE_MAP_KEY,
+ JSON.stringify(finishedAlertHideMap),
+ );
+};
diff --git a/app/assets/javascripts/jobs/components/commit_block.vue b/app/assets/javascripts/jobs/components/commit_block.vue
index 72a5ff01672..c4f180f200c 100644
--- a/app/assets/javascripts/jobs/components/commit_block.vue
+++ b/app/assets/javascripts/jobs/components/commit_block.vue
@@ -32,7 +32,7 @@ export default {
block: !isLastBlock,
}"
>
- <p class="append-bottom-5">
+ <p class="gl-mb-2">
<span class="font-weight-bold">{{ __('Commit') }}</span>
<gl-link :href="commit.commit_path" class="js-commit-sha commit-sha link-commit">
diff --git a/app/assets/javascripts/jobs/components/environments_block.vue b/app/assets/javascripts/jobs/components/environments_block.vue
index c34a3488dbd..c78738221f1 100644
--- a/app/assets/javascripts/jobs/components/environments_block.vue
+++ b/app/assets/javascripts/jobs/components/environments_block.vue
@@ -274,7 +274,7 @@ export default {
};
</script>
<template>
- <div class="prepend-top-default append-bottom-default js-environment-container">
+ <div class="gl-mt-3 gl-mb-3 js-environment-container">
<div class="environment-information">
<ci-icon :status="iconStatus" />
<p class="inline gl-mb-0" v-html="environment"></p>
diff --git a/app/assets/javascripts/jobs/components/erased_block.vue b/app/assets/javascripts/jobs/components/erased_block.vue
index fc5e022f44a..a6d1b41c275 100644
--- a/app/assets/javascripts/jobs/components/erased_block.vue
+++ b/app/assets/javascripts/jobs/components/erased_block.vue
@@ -27,7 +27,7 @@ export default {
};
</script>
<template>
- <div class="prepend-top-default js-build-erased">
+ <div class="gl-mt-3 js-build-erased">
<div class="erased alert alert-warning">
<template v-if="isErasedByUser">
{{ s__('Job|Job has been erased by') }}
diff --git a/app/assets/javascripts/jobs/components/job_app.vue b/app/assets/javascripts/jobs/components/job_app.vue
index 0783d1157be..f43a058b5f8 100644
--- a/app/assets/javascripts/jobs/components/job_app.vue
+++ b/app/assets/javascripts/jobs/components/job_app.vue
@@ -17,7 +17,7 @@ import UnmetPrerequisitesBlock from './unmet_prerequisites_block.vue';
import Sidebar from './sidebar.vue';
import { sprintf } from '~/locale';
import delayedJobMixin from '../mixins/delayed_job_mixin';
-import { isNewJobLogActive } from '../store/utils';
+import Log from './log/log.vue';
export default {
name: 'JobPageApp',
@@ -28,7 +28,7 @@ export default {
EnvironmentsBlock,
ErasedBlock,
Icon,
- Log: () => (isNewJobLogActive() ? import('./log/log.vue') : import('./job_log.vue')),
+ Log,
LogTopBar,
StuckBlock,
UnmetPrerequisitesBlock,
@@ -270,7 +270,7 @@ export default {
<div
v-if="job.archived"
ref="sticky"
- class="js-archived-job prepend-top-default archived-job"
+ class="js-archived-job gl-mt-3 archived-job"
:class="{ 'sticky-top border-bottom-0': hasTrace }"
>
<icon name="lock" class="align-text-bottom" />
@@ -280,7 +280,7 @@ export default {
<div
v-if="hasTrace"
class="build-trace-container position-relative"
- :class="{ 'prepend-top-default': !job.archived }"
+ :class="{ 'gl-mt-3': !job.archived }"
>
<log-top-bar
:class="{
diff --git a/app/assets/javascripts/jobs/components/job_log.vue b/app/assets/javascripts/jobs/components/job_log.vue
deleted file mode 100644
index 20888c0af42..00000000000
--- a/app/assets/javascripts/jobs/components/job_log.vue
+++ /dev/null
@@ -1,59 +0,0 @@
-<script>
-import { mapState, mapActions } from 'vuex';
-
-export default {
- name: 'JobLog',
- props: {
- trace: {
- type: String,
- required: true,
- },
- isComplete: {
- type: Boolean,
- required: true,
- },
- },
- computed: {
- ...mapState(['isScrolledToBottomBeforeReceivingTrace']),
- },
- updated() {
- this.$nextTick(() => {
- this.handleScrollDown();
- });
- },
- mounted() {
- this.$nextTick(() => {
- this.handleScrollDown();
- });
- },
- methods: {
- ...mapActions(['scrollBottom']),
- /**
- * The job log is sent in HTML, which means we need to use `v-html` to render it
- * Using the updated hook with $nextTick is not enough to wait for the DOM to be updated
- * in this case because it runs before `v-html` has finished running, since there's no
- * Vue binding.
- * In order to scroll the page down after `v-html` has finished, we need to use setTimeout
- */
- handleScrollDown() {
- if (this.isScrolledToBottomBeforeReceivingTrace) {
- setTimeout(() => {
- this.scrollBottom();
- }, 0);
- }
- },
- },
-};
-</script>
-<template>
- <pre class="js-build-trace build-trace qa-build-trace">
- <code class="bash" v-html="trace">
- </code>
-
- <div v-if="!isComplete" class="js-log-animation build-loader-animation">
- <div class="dot"></div>
- <div class="dot"></div>
- <div class="dot"></div>
- </div>
- </pre>
-</template>
diff --git a/app/assets/javascripts/jobs/components/job_log_controllers.vue b/app/assets/javascripts/jobs/components/job_log_controllers.vue
index bcec83a7aee..a68174d8e1d 100644
--- a/app/assets/javascripts/jobs/components/job_log_controllers.vue
+++ b/app/assets/javascripts/jobs/components/job_log_controllers.vue
@@ -77,7 +77,7 @@ export default {
<gl-link
v-if="rawPath"
:href="rawPath"
- class="js-raw-link text-plain text-underline prepend-left-5"
+ class="js-raw-link text-plain text-underline gl-ml-2"
>{{ s__('Job|Complete Raw') }}</gl-link
>
</template>
diff --git a/app/assets/javascripts/jobs/components/log/collapsible_section.vue b/app/assets/javascripts/jobs/components/log/collapsible_section.vue
index 0c7b78a3da7..55cdfb691f4 100644
--- a/app/assets/javascripts/jobs/components/log/collapsible_section.vue
+++ b/app/assets/javascripts/jobs/components/log/collapsible_section.vue
@@ -3,7 +3,7 @@ import LogLine from './line.vue';
import LogLineHeader from './line_header.vue';
export default {
- name: 'CollpasibleLogSection',
+ name: 'CollapsibleLogSection',
components: {
LogLine,
LogLineHeader,
diff --git a/app/assets/javascripts/jobs/components/log/line.vue b/app/assets/javascripts/jobs/components/log/line.vue
index 33ee84bd4ee..48f669ae8ed 100644
--- a/app/assets/javascripts/jobs/components/log/line.vue
+++ b/app/assets/javascripts/jobs/components/log/line.vue
@@ -2,9 +2,7 @@
import LineNumber from './line_number.vue';
export default {
- components: {
- LineNumber,
- },
+ functional: true,
props: {
line: {
type: Object,
@@ -15,18 +13,28 @@ export default {
required: true,
},
},
+ render(h, { props }) {
+ const { line, path } = props;
+
+ const chars = line.content.map(content => {
+ return h(
+ 'span',
+ {
+ class: ['ws-pre-wrap', content.style],
+ },
+ content.text,
+ );
+ });
+
+ return h('div', { class: 'js-line log-line' }, [
+ h(LineNumber, {
+ props: {
+ lineNumber: line.lineNumber,
+ path,
+ },
+ }),
+ ...chars,
+ ]);
+ },
};
</script>
-
-<template>
- <div class="js-line log-line">
- <line-number :line-number="line.lineNumber" :path="path" />
- <span
- v-for="(content, i) in line.content"
- :key="i"
- :class="content.style"
- class="ws-pre-wrap"
- >{{ content.text }}</span
- >
- </div>
-</template>
diff --git a/app/assets/javascripts/jobs/components/log/line_number.vue b/app/assets/javascripts/jobs/components/log/line_number.vue
index ae96c32874b..7ca9154d2fe 100644
--- a/app/assets/javascripts/jobs/components/log/line_number.vue
+++ b/app/assets/javascripts/jobs/components/log/line_number.vue
@@ -1,10 +1,6 @@
<script>
-import { GlLink } from '@gitlab/ui';
-
export default {
- components: {
- GlLink,
- },
+ functional: true,
props: {
lineNumber: {
type: Number,
@@ -15,41 +11,24 @@ export default {
required: true,
},
},
- computed: {
- /**
- * Builds the url for each line number
- *
- * @returns {String}
- */
- buildLineNumber() {
- return `${this.path}#${this.lineNumberId}`;
- },
- /**
- * Array indexes start with 0, so we add 1
- * to create the line number
- *
- * @returns {Number} the line number
- */
- parsedLineNumber() {
- return this.lineNumber + 1;
- },
+ render(h, { props }) {
+ const { lineNumber, path } = props;
- /**
- * Creates the anchor for each link
- *
- * @returns {String}
- */
- lineNumberId() {
- return `L${this.parsedLineNumber}`;
- },
+ const parsedLineNumber = lineNumber + 1;
+ const lineId = `L${parsedLineNumber}`;
+ const lineHref = `${path}#${lineId}`;
+
+ return h(
+ 'a',
+ {
+ class: 'gl-link d-inline-block text-right line-number flex-shrink-0',
+ attrs: {
+ id: lineId,
+ href: lineHref,
+ },
+ },
+ parsedLineNumber,
+ );
},
};
</script>
-<template>
- <gl-link
- :id="lineNumberId"
- class="d-inline-block text-right line-number flex-shrink-0"
- :href="buildLineNumber"
- >{{ parsedLineNumber }}</gl-link
- >
-</template>
diff --git a/app/assets/javascripts/jobs/components/log/log.vue b/app/assets/javascripts/jobs/components/log/log.vue
index f0bdbde0602..0134e5dafe8 100644
--- a/app/assets/javascripts/jobs/components/log/log.vue
+++ b/app/assets/javascripts/jobs/components/log/log.vue
@@ -1,11 +1,11 @@
<script>
import { mapState, mapActions } from 'vuex';
-import CollpasibleLogSection from './collapsible_section.vue';
+import CollapsibleLogSection from './collapsible_section.vue';
import LogLine from './line.vue';
export default {
components: {
- CollpasibleLogSection,
+ CollapsibleLogSection,
LogLine,
},
computed: {
@@ -51,7 +51,7 @@ export default {
<template>
<code class="job-log d-block" data-qa-selector="job_log_content">
<template v-for="(section, index) in trace">
- <collpasible-log-section
+ <collapsible-log-section
v-if="section.isHeader"
:key="`collapsible-${index}`"
:section="section"
diff --git a/app/assets/javascripts/jobs/components/manual_variables_form.vue b/app/assets/javascripts/jobs/components/manual_variables_form.vue
index d4aab5c7faf..d83c598dd48 100644
--- a/app/assets/javascripts/jobs/components/manual_variables_form.vue
+++ b/app/assets/javascripts/jobs/components/manual_variables_form.vue
@@ -112,7 +112,7 @@ export default {
<div v-for="variable in variables" :key="variable.id" class="gl-responsive-table-row">
<div class="table-section section-50">
<div class="table-mobile-header" role="rowheader">{{ s__('Pipeline|Key') }}</div>
- <div class="table-mobile-content append-right-10">
+ <div class="table-mobile-content gl-mr-3">
<input
:ref="`${$options.inputTypes.key}-${variable.id}`"
v-model="variable.key"
@@ -124,7 +124,7 @@ export default {
<div class="table-section section-50">
<div class="table-mobile-header" role="rowheader">{{ s__('Pipeline|Value') }}</div>
- <div class="table-mobile-content append-right-10">
+ <div class="table-mobile-content gl-mr-3">
<input
:ref="`${$options.inputTypes.value}-${variable.id}`"
v-model="variable.secret_value"
@@ -149,7 +149,7 @@ export default {
<div class="gl-responsive-table-row">
<div class="table-section section-50">
<div class="table-mobile-header" role="rowheader">{{ s__('Pipeline|Key') }}</div>
- <div class="table-mobile-content append-right-10">
+ <div class="table-mobile-content gl-mr-3">
<input
ref="inputKey"
v-model="key"
@@ -161,7 +161,7 @@ export default {
<div class="table-section section-50">
<div class="table-mobile-header" role="rowheader">{{ s__('Pipeline|Value') }}</div>
- <div class="table-mobile-content append-right-10">
+ <div class="table-mobile-content gl-mr-3">
<input
ref="inputSecretValue"
v-model="secretValue"
@@ -172,7 +172,7 @@ export default {
</div>
</div>
</div>
- <div class="d-flex prepend-top-default justify-content-center">
+ <div class="d-flex gl-mt-3 justify-content-center">
<p class="text-muted" v-html="helpText"></p>
</div>
<div class="d-flex justify-content-center">
diff --git a/app/assets/javascripts/jobs/components/stuck_block.vue b/app/assets/javascripts/jobs/components/stuck_block.vue
index da01269a50c..b69e6f9686f 100644
--- a/app/assets/javascripts/jobs/components/stuck_block.vue
+++ b/app/assets/javascripts/jobs/components/stuck_block.vue
@@ -31,7 +31,7 @@ export default {
s__(`This job is stuck because you don't have
any active runners online or available with any of these tags assigned to them:`)
}}
- <span v-for="(tag, index) in tags" :key="index" class="badge badge-primary append-right-4">
+ <span v-for="(tag, index) in tags" :key="index" class="badge badge-primary gl-mr-2">
{{ tag }}
</span>
</p>
diff --git a/app/assets/javascripts/jobs/components/trigger_block.vue b/app/assets/javascripts/jobs/components/trigger_block.vue
index 1a076249fe7..f55429ecdae 100644
--- a/app/assets/javascripts/jobs/components/trigger_block.vue
+++ b/app/assets/javascripts/jobs/components/trigger_block.vue
@@ -46,7 +46,7 @@ export default {
<p
v-if="trigger.short_token"
class="js-short-token"
- :class="{ 'append-bottom-5': hasVariables, 'gl-mb-0': !hasVariables }"
+ :class="{ 'gl-mb-2': hasVariables, 'gl-mb-0': !hasVariables }"
>
<span class="font-weight-bold">{{ __('Trigger token:') }}</span> {{ trigger.short_token }}
</p>
diff --git a/app/assets/javascripts/jobs/store/actions.js b/app/assets/javascripts/jobs/store/actions.js
index 0ce8dfe4442..4bd8d6f58a6 100644
--- a/app/assets/javascripts/jobs/store/actions.js
+++ b/app/assets/javascripts/jobs/store/actions.js
@@ -195,7 +195,7 @@ export const receiveTraceError = ({ dispatch }) => {
flash(__('An error occurred while fetching the job log.'));
};
/**
- * When the user clicks a collpasible line in the job
+ * When the user clicks a collapsible line in the job
* log, we commit a mutation to update the state
*
* @param {Object} section
diff --git a/app/assets/javascripts/jobs/store/mutations.js b/app/assets/javascripts/jobs/store/mutations.js
index 6193d8d34ab..924b811d0d6 100644
--- a/app/assets/javascripts/jobs/store/mutations.js
+++ b/app/assets/javascripts/jobs/store/mutations.js
@@ -1,6 +1,6 @@
import Vue from 'vue';
import * as types from './mutation_types';
-import { logLinesParser, updateIncrementalTrace, isNewJobLogActive } from './utils';
+import { logLinesParser, updateIncrementalTrace } from './utils';
export default {
[types.SET_JOB_ENDPOINT](state, endpoint) {
@@ -25,22 +25,16 @@ export default {
}
if (log.append) {
- if (isNewJobLogActive()) {
- state.trace = log.lines ? updateIncrementalTrace(log.lines, state.trace) : state.trace;
- } else {
- state.trace += log.html;
- }
+ state.trace = log.lines ? updateIncrementalTrace(log.lines, state.trace) : state.trace;
+
state.traceSize += log.size;
} else {
// When the job still does not have a trace
// the trace response will not have a defined
// html or size. We keep the old value otherwise these
// will be set to `null`
- if (isNewJobLogActive()) {
- state.trace = log.lines ? logLinesParser(log.lines) : state.trace;
- } else {
- state.trace = log.html || state.trace;
- }
+ state.trace = log.lines ? logLinesParser(log.lines) : state.trace;
+
state.traceSize = log.size || state.traceSize;
}
diff --git a/app/assets/javascripts/jobs/store/state.js b/app/assets/javascripts/jobs/store/state.js
index d76828ad19b..2fe945b2985 100644
--- a/app/assets/javascripts/jobs/store/state.js
+++ b/app/assets/javascripts/jobs/store/state.js
@@ -1,5 +1,3 @@
-import { isNewJobLogActive } from './utils';
-
export default () => ({
jobEndpoint: null,
traceEndpoint: null,
@@ -18,7 +16,7 @@ export default () => ({
// Used to check if we should keep the automatic scroll
isScrolledToBottomBeforeReceivingTrace: true,
- trace: isNewJobLogActive() ? [] : '',
+ trace: [],
isTraceComplete: false,
traceSize: 0,
isTraceSizeVisible: false,
diff --git a/app/assets/javascripts/jobs/store/utils.js b/app/assets/javascripts/jobs/store/utils.js
index 0b28c52a78f..8d6e5aac566 100644
--- a/app/assets/javascripts/jobs/store/utils.js
+++ b/app/assets/javascripts/jobs/store/utils.js
@@ -11,7 +11,7 @@ export const parseLine = (line = {}, lineNumber) => ({
/**
* When a line has `section_header` set to true, we create a new
* structure to allow to nest the lines that belong to the
- * collpasible section
+ * collapsible section
*
* @param Object line
* @param Number lineNumber
@@ -91,7 +91,7 @@ export const getIncrementalLineNumber = acc => {
* Parses the job log content into a structure usable by the template
*
* For collaspible lines (section_header = true):
- * - creates a new array to hold the lines that are collpasible,
+ * - creates a new array to hold the lines that are collapsible,
* - adds a isClosed property to handle toggle
* - adds a isHeader property to handle template logic
* - adds the section_duration
@@ -177,5 +177,3 @@ export const updateIncrementalTrace = (newLog = [], oldParsed = []) => {
return logLinesParser(newLog, parsedLog);
};
-
-export const isNewJobLogActive = () => gon && gon.features && gon.features.jobLogJson;
diff --git a/app/assets/javascripts/labels_select.js b/app/assets/javascripts/labels_select.js
index 65d8866fcc3..63c4ad3c410 100644
--- a/app/assets/javascripts/labels_select.js
+++ b/app/assets/javascripts/labels_select.js
@@ -3,7 +3,7 @@
/* global ListLabel */
import $ from 'jquery';
-import { isEqual, escape, sortBy, template } from 'lodash';
+import { difference, isEqual, escape, sortBy, template } from 'lodash';
import { sprintf, s__, __ } from './locale';
import axios from './lib/utils/axios_utils';
import IssuableBulkUpdateActions from './issuable_bulk_update_actions';
@@ -497,7 +497,7 @@ export default class LabelsSelect {
const scopedLabelTemplate = template(
[
- '<span class="gl-label gl-label-scoped" style="color: <%= escapeStr(label.color) %>;">',
+ '<span class="gl-label gl-label-scoped" style="color: <%= escapeStr(label.color) %>; --label-inset-border: inset 0 0 0 2px <%= escapeStr(label.color) %>;">',
linkOpenTag,
spanOpenTag,
'<%- label.title.slice(0, label.title.lastIndexOf("::")) %>',
@@ -526,9 +526,7 @@ export default class LabelsSelect {
[
'<% labels.forEach(function(label){ %>',
'<% if (isScopedLabel(label) && enableScopedLabels) { %>',
- '<span class="d-inline-block position-relative scoped-label-wrapper">',
'<%= scopedLabelTemplate({ label, issueUpdateURL, isScopedLabel, enableScopedLabels, rightLabelTextColor, tooltipTitleTemplate, escapeStr, linkAttrs: \'data-html="true"\' }) %>',
- '</span>',
'<% } else { %>',
'<%= labelTemplate({ label, issueUpdateURL, isScopedLabel, enableScopedLabels, tooltipTitleTemplate, escapeStr, linkAttrs: "" }) %>',
'<% } %>',
@@ -562,45 +560,20 @@ export default class LabelsSelect {
IssuableBulkUpdateActions.willUpdateLabels = true;
}
// eslint-disable-next-line class-methods-use-this
- setDropdownData($dropdown, isMarking, value) {
- const markedIds = $dropdown.data('marked') || [];
- const unmarkedIds = $dropdown.data('unmarked') || [];
- const indeterminateIds = $dropdown.data('indeterminate') || [];
-
- if (isMarking) {
- markedIds.push(value);
+ setDropdownData($dropdown, isChecking, labelId) {
+ let userCheckedIds = $dropdown.data('user-checked') || [];
+ let userUncheckedIds = $dropdown.data('user-unchecked') || [];
- let i = indeterminateIds.indexOf(value);
- if (i > -1) {
- indeterminateIds.splice(i, 1);
- }
-
- i = unmarkedIds.indexOf(value);
- if (i > -1) {
- unmarkedIds.splice(i, 1);
- }
+ if (isChecking) {
+ userCheckedIds = userCheckedIds.concat(labelId);
+ userUncheckedIds = difference(userUncheckedIds, [labelId]);
} else {
- // If marked item (not common) is unmarked
- const i = markedIds.indexOf(value);
- if (i > -1) {
- markedIds.splice(i, 1);
- }
-
- // If an indeterminate item is being unmarked
- if (IssuableBulkUpdateActions.getOriginalIndeterminateIds().indexOf(value) > -1) {
- unmarkedIds.push(value);
- }
-
- // If a marked item is being unmarked
- // (a marked item could also be a label that is present in all selection)
- if (IssuableBulkUpdateActions.getOriginalCommonIds().indexOf(value) > -1) {
- unmarkedIds.push(value);
- }
+ userUncheckedIds = userUncheckedIds.concat(labelId);
+ userCheckedIds = difference(userCheckedIds, [labelId]);
}
- $dropdown.data('marked', markedIds);
- $dropdown.data('unmarked', unmarkedIds);
- $dropdown.data('indeterminate', indeterminateIds);
+ $dropdown.data('user-checked', userCheckedIds);
+ $dropdown.data('user-unchecked', userUncheckedIds);
}
// eslint-disable-next-line class-methods-use-this
setOriginalDropdownData($container, $dropdown) {
diff --git a/app/assets/javascripts/lazy_loader.js b/app/assets/javascripts/lazy_loader.js
index 75542267f37..aa7fe087678 100644
--- a/app/assets/javascripts/lazy_loader.js
+++ b/app/assets/javascripts/lazy_loader.js
@@ -14,6 +14,10 @@ export default class LazyLoader {
scrollContainer.addEventListener('load', () => this.register());
}
+ static supportsNativeLazyLoading() {
+ return 'loading' in HTMLImageElement.prototype;
+ }
+
static supportsIntersectionObserver() {
return Boolean(window.IntersectionObserver);
}
@@ -23,7 +27,9 @@ export default class LazyLoader {
() => {
const lazyImages = [].slice.call(document.querySelectorAll('.lazy'));
- if (LazyLoader.supportsIntersectionObserver()) {
+ if (LazyLoader.supportsNativeLazyLoading()) {
+ lazyImages.forEach(img => LazyLoader.loadImage(img));
+ } else if (LazyLoader.supportsIntersectionObserver()) {
if (this.intersectionObserver) {
lazyImages.forEach(img => this.intersectionObserver.observe(img));
}
@@ -72,11 +78,14 @@ export default class LazyLoader {
}
register() {
- if (LazyLoader.supportsIntersectionObserver()) {
- this.startIntersectionObserver();
- } else {
- this.startLegacyObserver();
+ if (!LazyLoader.supportsNativeLazyLoading()) {
+ if (LazyLoader.supportsIntersectionObserver()) {
+ this.startIntersectionObserver();
+ } else {
+ this.startLegacyObserver();
+ }
}
+
this.startContentObserver();
this.searchLazyImages();
}
@@ -148,16 +157,12 @@ export default class LazyLoader {
static loadImage(img) {
if (img.getAttribute('data-src')) {
+ img.setAttribute('loading', 'lazy');
let imgUrl = img.getAttribute('data-src');
// Only adding width + height for avatars for now
if (imgUrl.indexOf('/avatar/') > -1 && imgUrl.indexOf('?') === -1) {
- let targetWidth = null;
- if (img.getAttribute('width')) {
- targetWidth = img.getAttribute('width');
- } else {
- targetWidth = img.width;
- }
- if (targetWidth) imgUrl += `?width=${targetWidth}`;
+ const targetWidth = img.getAttribute('width') || img.width;
+ imgUrl += `?width=${targetWidth}`;
}
img.setAttribute('src', imgUrl);
img.removeAttribute('data-src');
diff --git a/app/assets/javascripts/lib/utils/axios_startup_calls.js b/app/assets/javascripts/lib/utils/axios_startup_calls.js
new file mode 100644
index 00000000000..cb2e8a76c08
--- /dev/null
+++ b/app/assets/javascripts/lib/utils/axios_startup_calls.js
@@ -0,0 +1,52 @@
+import { isEmpty } from 'lodash';
+import { mergeUrlParams } from './url_utility';
+
+// We should probably not couple this utility to `gon.gitlab_url`
+// Also, this would replace occurrences that aren't at the beginning of the string
+const removeGitLabUrl = url => url.replace(gon.gitlab_url, '');
+
+const getFullUrl = req => {
+ const url = removeGitLabUrl(req.url);
+ return mergeUrlParams(req.params || {}, url);
+};
+
+const setupAxiosStartupCalls = axios => {
+ const { startup_calls: startupCalls } = window.gl || {};
+
+ if (!startupCalls || isEmpty(startupCalls)) {
+ return;
+ }
+
+ // TODO: To save performance of future axios calls, we can
+ // remove this interceptor once the "startupCalls" have been loaded
+ axios.interceptors.request.use(req => {
+ const fullUrl = getFullUrl(req);
+
+ const existing = startupCalls[fullUrl];
+
+ if (existing) {
+ // eslint-disable-next-line no-param-reassign
+ req.adapter = () =>
+ existing.fetchCall.then(res => {
+ const fetchHeaders = {};
+ res.headers.forEach((val, key) => {
+ fetchHeaders[key] = val;
+ });
+
+ // eslint-disable-next-line promise/no-nesting
+ return res.json().then(data => ({
+ data,
+ status: res.status,
+ statusText: res.statusText,
+ headers: fetchHeaders,
+ config: req,
+ request: req,
+ }));
+ });
+ }
+
+ return req;
+ });
+};
+
+export default setupAxiosStartupCalls;
diff --git a/app/assets/javascripts/lib/utils/axios_utils.js b/app/assets/javascripts/lib/utils/axios_utils.js
index 4eec5bffc66..9d517f45caa 100644
--- a/app/assets/javascripts/lib/utils/axios_utils.js
+++ b/app/assets/javascripts/lib/utils/axios_utils.js
@@ -1,6 +1,7 @@
import axios from 'axios';
import csrf from './csrf';
import suppressAjaxErrorsDuringNavigation from './suppress_ajax_errors_during_navigation';
+import setupAxiosStartupCalls from './axios_startup_calls';
axios.defaults.headers.common[csrf.headerKey] = csrf.token;
// Used by Rails to check if it is a valid XHR request
@@ -14,6 +15,8 @@ axios.interceptors.request.use(config => {
return config;
});
+setupAxiosStartupCalls(axios);
+
// Remove the global counter
axios.interceptors.response.use(
response => {
diff --git a/app/assets/javascripts/lib/utils/common_utils.js b/app/assets/javascripts/lib/utils/common_utils.js
index a60748215ab..8bf9a281151 100644
--- a/app/assets/javascripts/lib/utils/common_utils.js
+++ b/app/assets/javascripts/lib/utils/common_utils.js
@@ -53,16 +53,6 @@ export const getCspNonceValue = () => {
return metaTag && metaTag.content;
};
-export const ajaxGet = url =>
- axios
- .get(url, {
- params: { format: 'js' },
- responseType: 'text',
- })
- .then(({ data }) => {
- $.globalEval(data, { nonce: getCspNonceValue() });
- });
-
export const rstrip = val => {
if (val) {
return val.replace(/\s+$/, '');
@@ -105,6 +95,7 @@ export const handleLocationHash = () => {
const topPadding = 8;
const diffFileHeader = document.querySelector('.js-file-title');
const versionMenusContainer = document.querySelector('.mr-version-menus-container');
+ const fixedIssuableTitle = document.querySelector('.issue-sticky-header');
let adjustment = 0;
if (fixedNav) adjustment -= fixedNav.offsetHeight;
@@ -133,6 +124,10 @@ export const handleLocationHash = () => {
adjustment -= versionMenusContainer.offsetHeight;
}
+ if (isInIssuePage()) {
+ adjustment -= fixedIssuableTitle.offsetHeight;
+ }
+
if (isInMRPage()) {
adjustment -= topPadding;
}
@@ -370,34 +365,6 @@ export const insertText = (target, text) => {
target.dispatchEvent(event);
};
-export const nodeMatchesSelector = (node, selector) => {
- const matches =
- Element.prototype.matches ||
- Element.prototype.matchesSelector ||
- Element.prototype.mozMatchesSelector ||
- Element.prototype.msMatchesSelector ||
- Element.prototype.oMatchesSelector ||
- Element.prototype.webkitMatchesSelector;
-
- if (matches) {
- return matches.call(node, selector);
- }
-
- // IE11 doesn't support `node.matches(selector)`
-
- let { parentNode } = node;
-
- if (!parentNode) {
- parentNode = document.createElement('div');
- // eslint-disable-next-line no-param-reassign
- node = node.cloneNode(true);
- parentNode.appendChild(node);
- }
-
- const matchingNodes = parentNode.querySelectorAll(selector);
- return Array.prototype.indexOf.call(matchingNodes, node) !== -1;
-};
-
/**
this will take in the headers from an API response and normalize them
this way we don't run into production issues when nginx gives us lowercased header keys
@@ -413,24 +380,6 @@ export const normalizeHeaders = headers => {
};
/**
- this will take in the getAllResponseHeaders result and normalize them
- this way we don't run into production issues when nginx gives us lowercased header keys
-*/
-export const normalizeCRLFHeaders = headers => {
- const headersObject = {};
- const headersArray = headers.split('\n');
-
- headersArray.forEach(header => {
- const keyValue = header.split(': ');
-
- // eslint-disable-next-line prefer-destructuring
- headersObject[keyValue[0]] = keyValue[1];
- });
-
- return normalizeHeaders(headersObject);
-};
-
-/**
* Parses pagination object string values into numbers.
*
* @param {Object} paginationInformation
@@ -638,13 +587,6 @@ export const setFaviconOverlay = overlayPath => {
);
};
-export const setFavicon = faviconPath => {
- const faviconEl = document.getElementById('favicon');
- if (faviconEl && faviconPath) {
- faviconEl.setAttribute('href', faviconPath);
- }
-};
-
export const resetFavicon = () => {
const faviconEl = document.getElementById('favicon');
@@ -883,35 +825,6 @@ export const searchBy = (query = '', searchSpace = {}) => {
*/
export const isScopedLabel = ({ title = '' }) => title.indexOf('::') !== -1;
-window.gl = window.gl || {};
-window.gl.utils = {
- ...(window.gl.utils || {}),
- getPagePath,
- isInGroupsPage,
- isInProjectPage,
- getProjectSlug,
- getGroupSlug,
- isInIssuePage,
- ajaxGet,
- rstrip,
- updateTooltipTitle,
- disableButtonIfEmptyField,
- handleLocationHash,
- isInViewport,
- parseUrl,
- parseUrlPathname,
- getUrlParamsArray,
- isMetaKey,
- isMetaClick,
- scrollToElement,
- getParameterByName,
- getSelectedFragment,
- insertText,
- nodeMatchesSelector,
- spriteIcon,
- imagePath,
-};
-
// Methods to set and get Cookie
export const setCookie = (name, value) => Cookies.set(name, value, { expires: 365 });
diff --git a/app/assets/javascripts/lib/utils/constants.js b/app/assets/javascripts/lib/utils/constants.js
index eb6c9bf7eb6..993d51370ec 100644
--- a/app/assets/javascripts/lib/utils/constants.js
+++ b/app/assets/javascripts/lib/utils/constants.js
@@ -1,5 +1,7 @@
export const BYTES_IN_KIB = 1024;
export const HIDDEN_CLASS = 'hidden';
+export const TRUNCATE_WIDTH_DEFAULT_WIDTH = 80;
+export const TRUNCATE_WIDTH_DEFAULT_FONT_SIZE = 12;
export const DATETIME_RANGE_TYPES = {
fixed: 'fixed',
diff --git a/app/assets/javascripts/lib/utils/datetime_utility.js b/app/assets/javascripts/lib/utils/datetime_utility.js
index 6b69d2febe0..6e02fc1eb91 100644
--- a/app/assets/javascripts/lib/utils/datetime_utility.js
+++ b/app/assets/javascripts/lib/utils/datetime_utility.js
@@ -89,13 +89,15 @@ export const getDayName = date =>
* @example
* dateFormat('2017-12-05','mmm d, yyyy h:MMtt Z' ) -> "Dec 5, 2017 12:00am GMT+0000"
* @param {date} datetime
+ * @param {String} format
+ * @param {Boolean} UTC convert local time to UTC
* @returns {String}
*/
-export const formatDate = (datetime, format = 'mmm d, yyyy h:MMtt Z') => {
+export const formatDate = (datetime, format = 'mmm d, yyyy h:MMtt Z', utc = false) => {
if (isString(datetime) && datetime.match(/\d+-\d+\d+ /)) {
throw new Error(__('Invalid date'));
}
- return dateFormat(datetime, format);
+ return dateFormat(datetime, format, utc);
};
/**
@@ -425,7 +427,6 @@ export const dayInQuarter = (date, quarter) => {
window.gl = window.gl || {};
window.gl.utils = {
...(window.gl.utils || {}),
- getTimeago,
localTimeAgo,
};
diff --git a/app/assets/javascripts/lib/utils/dom_utils.js b/app/assets/javascripts/lib/utils/dom_utils.js
index 8fa235f8afb..d9b0e8c4476 100644
--- a/app/assets/javascripts/lib/utils/dom_utils.js
+++ b/app/assets/javascripts/lib/utils/dom_utils.js
@@ -1,3 +1,4 @@
+import { has } from 'lodash';
import { isInIssuePage, isInMRPage, isInEpicPage } from './common_utils';
export const addClassIfElementExists = (element, className) => {
@@ -25,3 +26,24 @@ export const toggleContainerClasses = (containerEl, classList) => {
});
}
};
+
+/**
+ * Return a object mapping element dataset names to booleans.
+ *
+ * This is useful for data- attributes whose presense represent
+ * a truthiness, no matter the value of the attribute. The absense of the
+ * attribute represents falsiness.
+ *
+ * This can be useful when Rails-provided boolean-like values are passed
+ * directly to the HAML template, rather than cast to a string.
+ *
+ * @param {HTMLElement} element - The DOM element to inspect
+ * @param {string[]} names - The dataset (i.e., camelCase) names to inspect
+ * @returns {Object.<string, boolean>}
+ */
+export const parseBooleanDataAttributes = ({ dataset }, names) =>
+ names.reduce((acc, name) => {
+ acc[name] = has(dataset, name);
+
+ return acc;
+ }, {});
diff --git a/app/assets/javascripts/lib/utils/grammar.js b/app/assets/javascripts/lib/utils/grammar.js
index 18f9e2ed846..b1f38429369 100644
--- a/app/assets/javascripts/lib/utils/grammar.js
+++ b/app/assets/javascripts/lib/utils/grammar.js
@@ -20,18 +20,22 @@ export const toNounSeriesText = items => {
if (items.length === 0) {
return '';
} else if (items.length === 1) {
- return items[0];
+ return sprintf(s__(`nounSeries|%{item}`), { item: items[0] }, false);
} else if (items.length === 2) {
- return sprintf(s__('nounSeries|%{firstItem} and %{lastItem}'), {
- firstItem: items[0],
- lastItem: items[1],
- });
+ return sprintf(
+ s__('nounSeries|%{firstItem} and %{lastItem}'),
+ {
+ firstItem: items[0],
+ lastItem: items[1],
+ },
+ false,
+ );
}
return items.reduce((item, nextItem, idx) =>
idx === items.length - 1
- ? sprintf(s__('nounSeries|%{item}, and %{lastItem}'), { item, lastItem: nextItem })
- : sprintf(s__('nounSeries|%{item}, %{nextItem}'), { item, nextItem }),
+ ? sprintf(s__('nounSeries|%{item}, and %{lastItem}'), { item, lastItem: nextItem }, false)
+ : sprintf(s__('nounSeries|%{item}, %{nextItem}'), { item, nextItem }, false),
);
};
diff --git a/app/assets/javascripts/lib/utils/text_markdown.js b/app/assets/javascripts/lib/utils/text_markdown.js
index 0dfc144c363..4d25ee9e4bd 100644
--- a/app/assets/javascripts/lib/utils/text_markdown.js
+++ b/app/assets/javascripts/lib/utils/text_markdown.js
@@ -27,9 +27,28 @@ function lineAfter(text, textarea) {
.split('\n')[0];
}
+function convertMonacoSelectionToAceFormat(sel) {
+ return {
+ start: {
+ row: sel.startLineNumber,
+ column: sel.startColumn,
+ },
+ end: {
+ row: sel.endLineNumber,
+ column: sel.endColumn,
+ },
+ };
+}
+
+function getEditorSelectionRange(editor) {
+ return window.gon.features?.monacoBlobs
+ ? convertMonacoSelectionToAceFormat(editor.getSelection())
+ : editor.getSelectionRange();
+}
+
function editorBlockTagText(text, blockTag, selected, editor) {
const lines = text.split('\n');
- const selectionRange = editor.getSelectionRange();
+ const selectionRange = getEditorSelectionRange(editor);
const shouldRemoveBlock =
lines[selectionRange.start.row - 1] === blockTag &&
lines[selectionRange.end.row + 1] === blockTag;
@@ -90,8 +109,12 @@ function moveCursor({
const endPosition = startPosition + select.length;
return textArea.setSelectionRange(startPosition, endPosition);
} else if (editor) {
- editor.navigateLeft(tag.length - tag.indexOf(select));
- editor.getSelection().selectAWord();
+ if (window.gon.features?.monacoBlobs) {
+ editor.selectWithinSelection(select, tag);
+ } else {
+ editor.navigateLeft(tag.length - tag.indexOf(select));
+ editor.getSelection().selectAWord();
+ }
return;
}
}
@@ -115,7 +138,11 @@ function moveCursor({
}
} else if (editor && editorSelectionStart.row === editorSelectionEnd.row) {
if (positionBetweenTags) {
- editor.navigateLeft(tag.length);
+ if (window.gon.features?.monacoBlobs) {
+ editor.moveCursor(tag.length * -1);
+ } else {
+ editor.navigateLeft(tag.length);
+ }
}
}
}
@@ -140,7 +167,7 @@ export function insertMarkdownText({
let textToInsert;
if (editor) {
- const selectionRange = editor.getSelectionRange();
+ const selectionRange = getEditorSelectionRange(editor);
editorSelectionStart = selectionRange.start;
editorSelectionEnd = selectionRange.end;
@@ -237,7 +264,11 @@ export function insertMarkdownText({
}
if (editor) {
- editor.insert(textToInsert);
+ if (window.gon.features?.monacoBlobs) {
+ editor.replaceSelectedText(textToInsert, select);
+ } else {
+ editor.insert(textToInsert);
+ }
} else {
insertText(textArea, textToInsert);
}
diff --git a/app/assets/javascripts/lib/utils/text_utility.js b/app/assets/javascripts/lib/utils/text_utility.js
index be3fe1ed620..e2953ce330c 100644
--- a/app/assets/javascripts/lib/utils/text_utility.js
+++ b/app/assets/javascripts/lib/utils/text_utility.js
@@ -1,4 +1,9 @@
-import { isString } from 'lodash';
+import { isString, memoize } from 'lodash';
+
+import {
+ TRUNCATE_WIDTH_DEFAULT_WIDTH,
+ TRUNCATE_WIDTH_DEFAULT_FONT_SIZE,
+} from '~/lib/utils/constants';
/**
* Adds a , to a string composed by numbers, at every 3 chars.
@@ -73,7 +78,79 @@ export const slugifyWithUnderscore = str => slugify(str, '_');
* @param {Number} maxLength
* @returns {String}
*/
-export const truncate = (string, maxLength) => `${string.substr(0, maxLength - 3)}...`;
+export const truncate = (string, maxLength) => {
+ if (string.length - 1 > maxLength) {
+ return `${string.substr(0, maxLength - 1)}…`;
+ }
+
+ return string;
+};
+
+/**
+ * This function calculates the average char width. It does so by placing a string in the DOM and measuring the width.
+ * NOTE: This will cause a reflow and should be used sparsely!
+ * The default fontFamily is 'sans-serif' and 12px in ECharts, so that is the default basis for calculating the average with.
+ * https://echarts.apache.org/en/option.html#xAxis.nameTextStyle.fontFamily
+ * https://echarts.apache.org/en/option.html#xAxis.nameTextStyle.fontSize
+ * @param {Object} options
+ * @param {Number} options.fontSize style to size the text for measurement
+ * @param {String} options.fontFamily style of font family to measure the text with
+ * @param {String} options.chars string of chars to use as a basis for calculating average width
+ * @return {Number}
+ */
+const getAverageCharWidth = memoize(function getAverageCharWidth(options = {}) {
+ const {
+ fontSize = 12,
+ fontFamily = 'sans-serif',
+ // eslint-disable-next-line @gitlab/require-i18n-strings
+ chars = ' ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789',
+ } = options;
+ const div = document.createElement('div');
+
+ div.style.fontFamily = fontFamily;
+ div.style.fontSize = `${fontSize}px`;
+ // Place outside of view
+ div.style.position = 'absolute';
+ div.style.left = -1000;
+ div.style.top = -1000;
+
+ div.innerHTML = chars;
+
+ document.body.appendChild(div);
+ const width = div.clientWidth;
+ document.body.removeChild(div);
+
+ return width / chars.length / fontSize;
+});
+
+/**
+ * This function returns a truncated version of `string` if its estimated rendered width is longer than `maxWidth`,
+ * otherwise it will return the original `string`
+ * Inspired by https://bl.ocks.org/tophtucker/62f93a4658387bb61e4510c37e2e97cf
+ * @param {String} string text to truncate
+ * @param {Object} options
+ * @param {Number} options.maxWidth largest rendered width the text may have
+ * @param {Number} options.fontSize size of the font used to render the text
+ * @return {String} either the original string or a truncated version
+ */
+export const truncateWidth = (string, options = {}) => {
+ const {
+ maxWidth = TRUNCATE_WIDTH_DEFAULT_WIDTH,
+ fontSize = TRUNCATE_WIDTH_DEFAULT_FONT_SIZE,
+ } = options;
+ const { truncateIndex } = string.split('').reduce(
+ (memo, char, index) => {
+ let newIndex = index;
+ if (memo.width > maxWidth) {
+ newIndex = memo.truncateIndex;
+ }
+ return { width: memo.width + getAverageCharWidth() * fontSize, truncateIndex: newIndex };
+ },
+ { width: 0, truncateIndex: 0 },
+ );
+
+ return truncate(string, truncateIndex);
+};
/**
* Truncate SHA to 8 characters
diff --git a/app/assets/javascripts/lib/utils/url_utility.js b/app/assets/javascripts/lib/utils/url_utility.js
index 0472b8cf51f..c6c34b831ee 100644
--- a/app/assets/javascripts/lib/utils/url_utility.js
+++ b/app/assets/javascripts/lib/utils/url_utility.js
@@ -344,9 +344,15 @@ export function objectToQuery(obj) {
* @param {Object} params The query params to be set/updated
* @param {String} url The url to be operated on
* @param {Boolean} clearParams Indicates whether existing query params should be removed or not
+ * @param {Boolean} railsArraySyntax When enabled, changes the array syntax from `keys=` to `keys[]=` according to Rails conventions
* @returns {String} A copy of the original with the updated query params
*/
-export const setUrlParams = (params, url = window.location.href, clearParams = false) => {
+export const setUrlParams = (
+ params,
+ url = window.location.href,
+ clearParams = false,
+ railsArraySyntax = false,
+) => {
const urlObj = new URL(url);
const queryString = urlObj.search;
const searchParams = clearParams ? new URLSearchParams('') : new URLSearchParams(queryString);
@@ -355,11 +361,12 @@ export const setUrlParams = (params, url = window.location.href, clearParams = f
if (params[key] === null || params[key] === undefined) {
searchParams.delete(key);
} else if (Array.isArray(params[key])) {
+ const keyName = railsArraySyntax ? `${key}[]` : key;
params[key].forEach((val, idx) => {
if (idx === 0) {
- searchParams.set(key, val);
+ searchParams.set(keyName, val);
} else {
- searchParams.append(key, val);
+ searchParams.append(keyName, val);
}
});
} else {
diff --git a/app/assets/javascripts/logs/components/environment_logs.vue b/app/assets/javascripts/logs/components/environment_logs.vue
index 01a4cbd41f6..f37f48aa431 100644
--- a/app/assets/javascripts/logs/components/environment_logs.vue
+++ b/app/assets/javascripts/logs/components/environment_logs.vue
@@ -8,6 +8,7 @@ import {
GlDropdown,
GlDropdownHeader,
GlDropdownItem,
+ GlDropdownDivider,
GlInfiniteScroll,
} from '@gitlab/ui';
@@ -27,6 +28,7 @@ export default {
GlDropdown,
GlDropdownHeader,
GlDropdownItem,
+ GlDropdownDivider,
GlInfiniteScroll,
LogSimpleFilters,
LogAdvancedFilters,
@@ -55,6 +57,10 @@ export default {
type: String,
required: true,
},
+ clustersPath: {
+ type: String,
+ required: true,
+ },
},
data() {
return {
@@ -63,7 +69,7 @@ export default {
};
},
computed: {
- ...mapState('environmentLogs', ['environments', 'timeRange', 'logs', 'pods']),
+ ...mapState('environmentLogs', ['environments', 'timeRange', 'logs', 'pods', 'managedApps']),
...mapGetters('environmentLogs', ['trace', 'showAdvancedFilters']),
showLoader() {
@@ -85,12 +91,15 @@ export default {
});
this.fetchEnvironments(this.environmentsPath);
+ this.fetchManagedApps(this.clustersPath);
},
methods: {
...mapActions('environmentLogs', [
'setInitData',
'showEnvironment',
+ 'showManagedApp',
'fetchEnvironments',
+ 'fetchManagedApps',
'refreshPodLogs',
'fetchMoreLogsPrepend',
'dismissRequestEnvironmentsError',
@@ -101,6 +110,9 @@ export default {
isCurrentEnvironment(envName) {
return envName === this.environments.current;
},
+ isCurrentManagedApp(appName) {
+ return appName === this.managedApps.current;
+ },
topReached() {
if (!this.logs.isLoading) {
this.fetchMoreLogsPrepend();
@@ -164,12 +176,12 @@ export default {
<div class="flex-grow-0">
<gl-dropdown
id="environments-dropdown"
- :text="environments.current"
+ :text="environments.current || managedApps.current"
:disabled="environments.isLoading"
class="mb-2 gl-h-32 pr-2 d-flex d-md-block js-environments-dropdown"
>
- <gl-dropdown-header class="text-center">
- {{ s__('Environments|Select environment') }}
+ <gl-dropdown-header class="gl-text-center">
+ {{ s__('Environments|Environments') }}
</gl-dropdown-header>
<gl-dropdown-item
v-for="env in environments.options"
@@ -181,7 +193,24 @@ export default {
:class="{ invisible: !isCurrentEnvironment(env.name) }"
name="status_success_borderless"
/>
- <div class="flex-grow-1">{{ env.name }}</div>
+ <div class="gl-flex-grow-1">{{ env.name }}</div>
+ </div>
+ </gl-dropdown-item>
+ <gl-dropdown-divider />
+ <gl-dropdown-header class="gl-text-center">
+ {{ s__('Environments|Managed apps') }}
+ </gl-dropdown-header>
+ <gl-dropdown-item
+ v-for="app in managedApps.options"
+ :key="app.id"
+ @click="showManagedApp(app.name)"
+ >
+ <div class="gl-display-flex">
+ <gl-icon
+ :class="{ invisible: !isCurrentManagedApp(app.name) }"
+ name="status_success_borderless"
+ />
+ <div class="gl-flex-grow-1">{{ app.name }}</div>
</div>
</gl-dropdown-item>
</gl-dropdown>
@@ -202,7 +231,7 @@ export default {
<log-control-buttons
ref="scrollButtons"
- class="flex-grow-0 pr-2 mb-2 controllers"
+ class="flex-grow-0 pr-2 mb-2 controllers gl-display-inline-flex"
:scroll-down-button-disabled="scrollDownButtonDisabled"
@refresh="refreshPodLogs()"
@scrollDown="scrollDown"
diff --git a/app/assets/javascripts/logs/components/log_control_buttons.vue b/app/assets/javascripts/logs/components/log_control_buttons.vue
index 3f5de4c22e0..e44b5394fa1 100644
--- a/app/assets/javascripts/logs/components/log_control_buttons.vue
+++ b/app/assets/javascripts/logs/components/log_control_buttons.vue
@@ -1,11 +1,9 @@
<script>
-import { GlDeprecatedButton, GlTooltipDirective } from '@gitlab/ui';
-import Icon from '~/vue_shared/components/icon.vue';
+import { GlButton, GlTooltipDirective } from '@gitlab/ui';
export default {
components: {
- Icon,
- GlDeprecatedButton,
+ GlButton,
},
directives: {
GlTooltip: GlTooltipDirective,
@@ -51,14 +49,16 @@ export default {
:title="__('Scroll to top')"
aria-labelledby="scroll-to-top"
>
- <gl-deprecated-button
+ <gl-button
id="scroll-to-top"
- class="btn-blank js-scroll-to-top"
+ class="js-scroll-to-top gl-mr-2 btn-blank"
:aria-label="__('Scroll to top')"
:disabled="scrollUpButtonDisabled"
+ icon="scroll_up"
+ category="primary"
+ variant="default"
@click="handleScrollUp()"
- ><icon name="scroll_up"
- /></gl-deprecated-button>
+ />
</div>
<div
v-if="scrollDownAvailable"
@@ -68,25 +68,28 @@ export default {
:title="__('Scroll to bottom')"
aria-labelledby="scroll-to-bottom"
>
- <gl-deprecated-button
+ <gl-button
id="scroll-to-bottom"
- class="btn-blank js-scroll-to-bottom"
+ class="js-scroll-to-bottom gl-mr-2 btn-blank"
:aria-label="__('Scroll to bottom')"
:v-if="scrollDownAvailable"
:disabled="scrollDownButtonDisabled"
+ icon="scroll_down"
+ category="primary"
+ variant="default"
@click="handleScrollDown()"
- ><icon name="scroll_down"
- /></gl-deprecated-button>
+ />
</div>
- <gl-deprecated-button
+ <gl-button
id="refresh-log"
v-gl-tooltip
- class="ml-1 px-2 js-refresh-log"
+ class="js-refresh-log"
:title="__('Refresh')"
:aria-label="__('Refresh')"
+ icon="retry"
+ category="primary"
+ variant="default"
@click="handleRefreshClick"
- >
- <icon name="retry" />
- </gl-deprecated-button>
+ />
</div>
</template>
diff --git a/app/assets/javascripts/logs/constants.js b/app/assets/javascripts/logs/constants.js
index f83d369c6b8..0cdef53df34 100644
--- a/app/assets/javascripts/logs/constants.js
+++ b/app/assets/javascripts/logs/constants.js
@@ -8,4 +8,10 @@ export const tracking = {
TIME_RANGE_SET: 'time_range_set',
ENVIRONMENT_SELECTED: 'environment_selected',
REFRESH_POD_LOGS: 'refresh_pod_logs',
+ MANAGED_APP_SELECTED: 'managed_app_selected',
+};
+
+export const logExplorerOptions = {
+ environments: 'environments',
+ managedApps: 'managedApps',
};
diff --git a/app/assets/javascripts/logs/stores/actions.js b/app/assets/javascripts/logs/stores/actions.js
index d828e8f8a3e..0edd825b6e9 100644
--- a/app/assets/javascripts/logs/stores/actions.js
+++ b/app/assets/javascripts/logs/stores/actions.js
@@ -2,7 +2,7 @@ import { backOff } from '~/lib/utils/common_utils';
import httpStatusCodes from '~/lib/utils/http_status';
import axios from '~/lib/utils/axios_utils';
import { convertToFixedRange } from '~/lib/utils/datetime_range';
-import { TOKEN_TYPE_POD_NAME, tracking } from '../constants';
+import { TOKEN_TYPE_POD_NAME, tracking, logExplorerOptions } from '../constants';
import trackLogs from '../logs_tracking_helper';
import * as types from './mutation_types';
@@ -25,9 +25,15 @@ const requestUntilData = (url, params) =>
const requestLogsUntilData = ({ commit, state }) => {
const params = {};
- const { logs_api_path } = state.environments.options.find(
- ({ name }) => name === state.environments.current,
- );
+ const type = state.environments.current
+ ? logExplorerOptions.environments
+ : logExplorerOptions.managedApps;
+ const selectedObj = state[type].options.find(({ name }) => name === state[type].current);
+
+ const path =
+ type === logExplorerOptions.environments
+ ? selectedObj.logs_api_path
+ : selectedObj.gitlab_managed_apps_logs_path;
if (state.pods.current) {
params.pod_name = state.pods.current;
@@ -48,7 +54,7 @@ const requestLogsUntilData = ({ commit, state }) => {
params.cursor = state.logs.cursor;
}
- return requestUntilData(logs_api_path, params);
+ return requestUntilData(path, params);
};
/**
@@ -100,6 +106,11 @@ export const showEnvironment = ({ dispatch, commit }, environmentName) => {
dispatch('fetchLogs', tracking.ENVIRONMENT_SELECTED);
};
+export const showManagedApp = ({ dispatch, commit }, managedApp) => {
+ commit(types.SET_MANAGED_APP, managedApp);
+ dispatch('fetchLogs', tracking.MANAGED_APP_SELECTED);
+};
+
export const refreshPodLogs = ({ dispatch, commit }) => {
commit(types.REFRESH_POD_LOGS);
dispatch('fetchLogs', tracking.REFRESH_POD_LOGS);
@@ -124,6 +135,23 @@ export const fetchEnvironments = ({ commit, dispatch }, environmentsPath) => {
});
};
+/**
+ * Fetch managed apps data
+ * @param {Object} store
+ * @param {String} clustersPath
+ */
+
+export const fetchManagedApps = ({ commit }, clustersPath) => {
+ return axios
+ .get(clustersPath)
+ .then(({ data }) => {
+ commit(types.RECEIVE_MANAGED_APPS_DATA_SUCCESS, data.clusters);
+ })
+ .catch(() => {
+ commit(types.RECEIVE_MANAGED_APPS_DATA_ERROR);
+ });
+};
+
export const fetchLogs = ({ commit, state }, trackingLabel) => {
commit(types.REQUEST_LOGS_DATA);
diff --git a/app/assets/javascripts/logs/stores/mutation_types.js b/app/assets/javascripts/logs/stores/mutation_types.js
index 9010ec51817..eaa4b13f8bd 100644
--- a/app/assets/javascripts/logs/stores/mutation_types.js
+++ b/app/assets/javascripts/logs/stores/mutation_types.js
@@ -1,5 +1,6 @@
export const SET_PROJECT_ENVIRONMENT = 'SET_PROJECT_ENVIRONMENT';
export const SET_SEARCH = 'SET_SEARCH';
+export const SET_MANAGED_APP = 'SET_MANAGED_APP';
export const SET_TIME_RANGE = 'SET_TIME_RANGE';
export const SHOW_TIME_RANGE_INVALID_WARNING = 'SHOW_TIME_RANGE_INVALID_WARNING';
@@ -12,6 +13,9 @@ export const RECEIVE_ENVIRONMENTS_DATA_SUCCESS = 'RECEIVE_ENVIRONMENTS_DATA_SUCC
export const RECEIVE_ENVIRONMENTS_DATA_ERROR = 'RECEIVE_ENVIRONMENTS_DATA_ERROR';
export const HIDE_REQUEST_ENVIRONMENTS_ERROR = 'HIDE_REQUEST_ENVIRONMENTS_ERROR';
+export const RECEIVE_MANAGED_APPS_DATA_SUCCESS = 'RECEIVE_MANAGED_APPS_DATA_SUCCESS';
+export const RECEIVE_MANAGED_APPS_DATA_ERROR = 'RECEIVE_MANAGED_APPS_DATA_ERROR';
+
export const REQUEST_LOGS_DATA = 'REQUEST_LOGS_DATA';
export const RECEIVE_LOGS_DATA_SUCCESS = 'RECEIVE_LOGS_DATA_SUCCESS';
export const RECEIVE_LOGS_DATA_ERROR = 'RECEIVE_LOGS_DATA_ERROR';
diff --git a/app/assets/javascripts/logs/stores/mutations.js b/app/assets/javascripts/logs/stores/mutations.js
index 5e1c794c3a9..be22204d88d 100644
--- a/app/assets/javascripts/logs/stores/mutations.js
+++ b/app/assets/javascripts/logs/stores/mutations.js
@@ -32,6 +32,9 @@ export default {
// Clear current pod options
state.pods.current = null;
state.pods.options = [];
+
+ // Clear current managedApps options
+ state.managedApps.current = null;
},
[types.REQUEST_ENVIRONMENTS_DATA](state) {
state.environments.options = [];
@@ -107,4 +110,24 @@ export default {
[types.RECEIVE_PODS_DATA_ERROR](state) {
state.pods.options = [];
},
+ // Managed apps data
+ [types.RECEIVE_MANAGED_APPS_DATA_SUCCESS](state, apps) {
+ state.managedApps.options = apps;
+ state.managedApps.isLoading = false;
+ },
+ [types.RECEIVE_MANAGED_APPS_DATA_ERROR](state) {
+ state.managedApps.options = [];
+ state.managedApps.isLoading = false;
+ state.managedApps.fetchError = true;
+ },
+ [types.SET_MANAGED_APP](state, managedApp) {
+ state.managedApps.current = managedApp;
+
+ // Clear current pod options
+ state.pods.current = null;
+ state.pods.options = [];
+
+ // Clear current environment options
+ state.environments.current = null;
+ },
};
diff --git a/app/assets/javascripts/logs/stores/state.js b/app/assets/javascripts/logs/stores/state.js
index 11185c9ccf1..fbe6589dd84 100644
--- a/app/assets/javascripts/logs/stores/state.js
+++ b/app/assets/javascripts/logs/stores/state.js
@@ -31,6 +31,16 @@ export default () => ({
},
/**
+ * Managed apps list information
+ */
+ managedApps: {
+ options: [],
+ isLoading: false,
+ current: null,
+ fetchError: false,
+ },
+
+ /**
* Logs including trace
*/
logs: {
diff --git a/app/assets/javascripts/main.js b/app/assets/javascripts/main.js
index 5f5fd790f67..3f85295a5ed 100644
--- a/app/assets/javascripts/main.js
+++ b/app/assets/javascripts/main.js
@@ -19,6 +19,7 @@ import { getLocationHash, visitUrl } from './lib/utils/url_utility';
// everything else
import loadAwardsHandler from './awards_handler';
+import applyGitLabUIConfig from '@gitlab/ui/dist/config';
import { GlBreakpointInstance as bp } from '@gitlab/ui/dist/utils';
import Flash, { removeFlashClickListener } from './flash';
import './gl_dropdown';
@@ -32,7 +33,7 @@ import initFrequentItemDropdowns from './frequent_items';
import initBreadcrumbs from './breadcrumb';
import initUsagePingConsent from './usage_ping_consent';
import initPerformanceBar from './performance_bar';
-import initGlobalSearchInput from './global_search_input';
+import initSearchAutocomplete from './search_autocomplete';
import GlFieldErrors from './gl_field_errors';
import initUserPopovers from './user_popovers';
import initBroadcastNotifications from './broadcast_notification';
@@ -42,6 +43,8 @@ import { __ } from './locale';
import 'ee_else_ce/main_ee';
+applyGitLabUIConfig();
+
// expose jQuery as global (TODO: remove these)
window.jQuery = jQuery;
window.$ = jQuery;
@@ -62,12 +65,12 @@ function disableJQueryAnimations() {
}
// Disable jQuery animations
-if (gon && gon.disable_animations) {
+if (gon?.disable_animations) {
disableJQueryAnimations();
}
// inject test utilities if necessary
-if (process.env.NODE_ENV !== 'production' && gon && gon.test_env) {
+if (process.env.NODE_ENV !== 'production' && gon?.test_env) {
disableJQueryAnimations();
import(/* webpackMode: "eager" */ './test_utils/'); // eslint-disable-line no-unused-expressions
}
@@ -110,7 +113,7 @@ function deferredInitialisation() {
initFrequentItemDropdowns();
initPersistentUserCallouts();
- if (document.querySelector('.search')) initGlobalSearchInput();
+ if (document.querySelector('.search')) initSearchAutocomplete();
addSelectOnFocusBehaviour('.js-select-on-focus');
@@ -132,27 +135,6 @@ function deferredInitialisation() {
.fadeOut();
});
- // Initialize select2 selects
- if ($('select.select2').length) {
- import(/* webpackChunkName: 'select2' */ 'select2/select2')
- .then(() => {
- $('select.select2').select2({
- width: 'resolve',
- minimumResultsForSearch: 10,
- dropdownAutoWidth: true,
- });
-
- // Close select2 on escape
- $('.js-select2').on('select2-close', () => {
- setTimeout(() => {
- $('.select2-container-active').removeClass('select2-container-active');
- $(':focus').blur();
- }, 1);
- });
- })
- .catch(() => {});
- }
-
const glTooltipDelay = localStorage.getItem('gl-tooltip-delay');
const delay = glTooltipDelay ? JSON.parse(glTooltipDelay) : 0;
@@ -179,9 +161,7 @@ function deferredInitialisation() {
document.addEventListener('DOMContentLoaded', () => {
const $body = $('body');
const $document = $(document);
- const $window = $(window);
- const $sidebarGutterToggle = $('.js-sidebar-toggle');
- let bootstrapBreakpoint = bp.getBreakpointSize();
+ const bootstrapBreakpoint = bp.getBreakpointSize();
if (document.querySelector('#js-peek')) initPerformanceBar({ container: '#js-peek' });
@@ -199,6 +179,15 @@ document.addEventListener('DOMContentLoaded', () => {
}
});
+ /**
+ * TODO: Apparently we are collapsing the right sidebar on certain screensizes per default
+ * except on issue board pages. Why can't we do it with CSS?
+ *
+ * Proposal: Expose a global sidebar API, which we could import wherever we are manipulating
+ * the visibility of the sidebar.
+ *
+ * Quick fix: Get rid of jQuery for this implementation
+ */
const isBoardsPage = /(projects|groups):boards:show/.test(document.body.dataset.page);
if (!isBoardsPage && (bootstrapBreakpoint === 'sm' || bootstrapBreakpoint === 'xs')) {
const $rightSidebar = $('aside.right-sidebar');
@@ -225,14 +214,12 @@ document.addEventListener('DOMContentLoaded', () => {
localTimeAgo($('abbr.timeago, .js-timeago'), true);
- // Form submitter
- $('.trigger-submit').on('change', function triggerSubmitCallback() {
- $(this)
- .parents('form')
- .submit();
- });
-
- // Disable form buttons while a form is submitting
+ /**
+ * This disables form buttons while a form is submitting
+ * We do not difinitively know all of the places where this is used
+ *
+ * TODO: Defer execution, migrate to behaviors, and add sentry logging
+ */
$body.on('ajax:complete, ajax:beforeSend, submit', 'form', function ajaxCompleteCallback(e) {
const $buttons = $('[type="submit"], .js-disable-on-submit', this).not('.js-no-auto-disable');
switch (e.type) {
@@ -259,7 +246,11 @@ document.addEventListener('DOMContentLoaded', () => {
$('.header-content').toggleClass('menu-expanded');
});
- // Commit show suppressed diff
+ /**
+ * Show suppressed commit diff
+ *
+ * TODO: Move to commit diff pages
+ */
$document.on('click', '.diff-content .js-show-suppressed-diff', function showDiffCallback() {
const $container = $(this).parent();
$container.next('table').show();
@@ -290,39 +281,6 @@ document.addEventListener('DOMContentLoaded', () => {
$(document).trigger('toggle.comments');
});
- $document.on('breakpoint:change', (e, breakpoint) => {
- const breakpointSizes = ['md', 'sm', 'xs'];
- if (breakpointSizes.includes(breakpoint)) {
- const $gutterIcon = $sidebarGutterToggle.find('i');
- if ($gutterIcon.hasClass('fa-angle-double-right')) {
- $sidebarGutterToggle.trigger('click');
- }
-
- const sidebarGutterVueToggleEl = document.querySelector('.js-sidebar-vue-toggle');
-
- // Sidebar has an icon which corresponds to collapsing the sidebar
- // only then trigger the click.
- if (sidebarGutterVueToggleEl) {
- const collapseIcon = sidebarGutterVueToggleEl.querySelector('i.fa-angle-double-right');
-
- if (collapseIcon) {
- collapseIcon.click();
- }
- }
- }
- });
-
- function fitSidebarForSize() {
- const oldBootstrapBreakpoint = bootstrapBreakpoint;
- bootstrapBreakpoint = bp.getBreakpointSize();
-
- if (bootstrapBreakpoint !== oldBootstrapBreakpoint) {
- $document.trigger('breakpoint:change', [bootstrapBreakpoint]);
- }
- }
-
- $window.on('resize.app', fitSidebarForSize);
-
$('form.filter-form').on('submit', function filterFormSubmitCallback(event) {
const link = document.createElement('a');
link.href = this.action;
diff --git a/app/assets/javascripts/members.js b/app/assets/javascripts/members.js
index d719fd8748d..f220e9e0192 100644
--- a/app/assets/javascripts/members.js
+++ b/app/assets/javascripts/members.js
@@ -1,4 +1,5 @@
import $ from 'jquery';
+import { disableButtonIfEmptyField } from '~/lib/utils/common_utils';
export default class Members {
constructor() {
@@ -13,7 +14,7 @@ export default class Members {
$('.js-edit-member-form')
.off('ajax:success')
.on('ajax:success', this.formSuccess.bind(this));
- gl.utils.disableButtonIfEmptyField('#user_ids', 'input[name=commit]', 'change');
+ disableButtonIfEmptyField('#user_ids', 'input[name=commit]', 'change');
}
dropdownClicked(options) {
diff --git a/app/assets/javascripts/merge_request.js b/app/assets/javascripts/merge_request.js
index 6c794c1d324..a90e4e32d34 100644
--- a/app/assets/javascripts/merge_request.js
+++ b/app/assets/javascripts/merge_request.js
@@ -1,6 +1,7 @@
/* eslint-disable func-names, no-underscore-dangle, consistent-return */
import $ from 'jquery';
+import axios from './lib/utils/axios_utils';
import { __ } from '~/locale';
import createFlash from '~/flash';
import TaskList from './task_list';
@@ -65,9 +66,17 @@ MergeRequest.prototype.showAllCommits = function() {
MergeRequest.prototype.initMRBtnListeners = function() {
const _this = this;
- return $('a.btn-close, a.btn-reopen').on('click', function(e) {
+ return $('.btn-close, .btn-reopen').on('click', function(e) {
const $this = $(this);
const shouldSubmit = $this.hasClass('btn-comment');
+ if ($this.hasClass('js-btn-issue-action')) {
+ const url = $this.data('endpoint');
+ return axios
+ .put(url)
+ .then(() => window.location.reload())
+ .catch(() => createFlash(__('Something went wrong.')));
+ }
+
if (shouldSubmit && $this.data('submitted')) {
return;
}
diff --git a/app/assets/javascripts/merge_request_tabs.js b/app/assets/javascripts/merge_request_tabs.js
index 6c63ab7cf95..e9b7b56a160 100644
--- a/app/assets/javascripts/merge_request_tabs.js
+++ b/app/assets/javascripts/merge_request_tabs.js
@@ -226,6 +226,8 @@ export default class MergeRequestTabs {
this.resetViewContainer();
this.destroyPipelinesView();
}
+
+ $('.detail-page-description').renderGFM();
} else if (action === this.currentAction) {
// ContentTop is used to handle anything at the top of the page before the main content
const mainContentContainer = document.querySelector('.content-wrapper');
diff --git a/app/assets/javascripts/monitoring/components/charts/anomaly.vue b/app/assets/javascripts/monitoring/components/charts/anomaly.vue
index 34da5885c97..ac401c6e381 100644
--- a/app/assets/javascripts/monitoring/components/charts/anomaly.vue
+++ b/app/assets/javascripts/monitoring/components/charts/anomaly.vue
@@ -218,7 +218,7 @@ export default {
<gl-chart-series-label :color="content.color">
{{ content.name }}
</gl-chart-series-label>
- <div class="prepend-left-32">
+ <div class="gl-ml-7">
{{ yValueFormatted(seriesIndex, content.dataIndex) }}
</div>
</div>
diff --git a/app/assets/javascripts/monitoring/components/charts/heatmap.vue b/app/assets/javascripts/monitoring/components/charts/heatmap.vue
index f6f266dacf3..ddb44f7b1be 100644
--- a/app/assets/javascripts/monitoring/components/charts/heatmap.vue
+++ b/app/assets/javascripts/monitoring/components/charts/heatmap.vue
@@ -48,7 +48,10 @@ export default {
return this.result.values.map(val => {
const [yLabel] = val;
- return formatDate(new Date(yLabel), { format: formats.shortTime, timezone: this.timezone });
+ return formatDate(new Date(yLabel), {
+ format: formats.shortTime,
+ timezone: this.timezone,
+ });
});
},
result() {
diff --git a/app/assets/javascripts/monitoring/components/charts/options.js b/app/assets/javascripts/monitoring/components/charts/options.js
index f7822e69b1d..42252dd5897 100644
--- a/app/assets/javascripts/monitoring/components/charts/options.js
+++ b/app/assets/javascripts/monitoring/components/charts/options.js
@@ -17,7 +17,9 @@ const defaultTooltipFormat = defaultFormat;
const defaultTooltipPrecision = 3;
// Give enough space for y-axis with units and name.
-const chartGridLeft = 75;
+const chartGridLeft = 63; // larger gap than gitlab-ui's default to fit formatted numbers
+const chartGridRight = 10; // half of the scroll-handle icon for data zoom
+const yAxisNameGap = chartGridLeft - 12; // offset the axis label line-height
// Axis options
@@ -62,7 +64,7 @@ export const getYAxisOptions = ({
precision = defaultYAxisPrecision,
} = {}) => {
return {
- nameGap: 63, // larger gap than gitlab-ui's default to fit with formatted numbers
+ nameGap: yAxisNameGap,
scale: true,
boundaryGap: yAxisBoundaryGap,
@@ -74,11 +76,14 @@ export const getYAxisOptions = ({
};
};
-export const getTimeAxisOptions = ({ timezone = timezones.LOCAL } = {}) => ({
+export const getTimeAxisOptions = ({
+ timezone = timezones.LOCAL,
+ format = formats.shortDateTime,
+} = {}) => ({
name: __('Time'),
type: axisTypes.time,
axisLabel: {
- formatter: date => formatDate(date, { format: formats.shortTime, timezone }),
+ formatter: date => formatDate(date, { format, timezone }),
},
axisPointer: {
snap: false,
@@ -90,7 +95,10 @@ export const getTimeAxisOptions = ({ timezone = timezones.LOCAL } = {}) => ({
/**
* Grid with enough room to display chart.
*/
-export const getChartGrid = ({ left = chartGridLeft } = {}) => ({ left });
+export const getChartGrid = ({ left = chartGridLeft, right = chartGridRight } = {}) => ({
+ left,
+ right,
+});
// Tooltip options
diff --git a/app/assets/javascripts/monitoring/components/charts/single_stat.vue b/app/assets/javascripts/monitoring/components/charts/single_stat.vue
index eee5eaa5eca..106c76a97dc 100644
--- a/app/assets/javascripts/monitoring/components/charts/single_stat.vue
+++ b/app/assets/javascripts/monitoring/components/charts/single_stat.vue
@@ -1,9 +1,11 @@
<script>
import { GlSingleStat } from '@gitlab/ui/dist/charts';
+import { __ } from '~/locale';
import { SUPPORTED_FORMATS, getFormatter } from '~/lib/utils/unit_format';
import { graphDataValidatorForValues } from '../../utils';
const defaultPrecision = 2;
+const emptyStateMsg = __('No data to display');
export default {
components: {
@@ -21,6 +23,9 @@ export default {
queryInfo() {
return this.graphData.metrics[0];
},
+ queryMetric() {
+ return this.queryInfo.result[0]?.metric;
+ },
queryResult() {
return this.queryInfo.result[0]?.value[1];
},
@@ -33,6 +38,12 @@ export default {
statValue() {
let formatter;
+ // if field is present the metric value is not displayed. Hence
+ // the early exit without formatting.
+ if (this.graphData?.field) {
+ return this.queryMetric?.[this.graphData.field] ?? emptyStateMsg;
+ }
+
if (this.graphData?.maxValue) {
formatter = getFormatter(SUPPORTED_FORMATS.percent);
return formatter(this.queryResult / Number(this.graphData.maxValue), defaultPrecision);
diff --git a/app/assets/javascripts/monitoring/components/charts/stacked_column.vue b/app/assets/javascripts/monitoring/components/charts/stacked_column.vue
index ac31d107e63..9bcd4419a14 100644
--- a/app/assets/javascripts/monitoring/components/charts/stacked_column.vue
+++ b/app/assets/javascripts/monitoring/components/charts/stacked_column.vue
@@ -6,7 +6,7 @@ import { chartHeight, legendLayoutTypes } from '../../constants';
import { s__ } from '~/locale';
import { graphDataValidatorForValues } from '../../utils';
import { getTimeAxisOptions, axisTypes } from './options';
-import { timezones } from '../../format_date';
+import { formats, timezones } from '../../format_date';
export default {
components: {
@@ -97,7 +97,7 @@ export default {
chartOptions() {
return {
xAxis: {
- ...getTimeAxisOptions({ timezone: this.timezone }),
+ ...getTimeAxisOptions({ timezone: this.timezone, format: formats.shortTime }),
type: this.xAxisType,
},
dataZoom: [this.dataZoomConfig],
diff --git a/app/assets/javascripts/monitoring/components/charts/time_series.vue b/app/assets/javascripts/monitoring/components/charts/time_series.vue
index 28af2d8ba77..f2add429a80 100644
--- a/app/assets/javascripts/monitoring/components/charts/time_series.vue
+++ b/app/assets/javascripts/monitoring/components/charts/time_series.vue
@@ -415,7 +415,7 @@ export default {
<gl-chart-series-label :color="isMultiSeries ? content.color : ''">
{{ content.name }}
</gl-chart-series-label>
- <div class="prepend-left-32">
+ <div class="gl-ml-7">
{{ content.value }}
</div>
</div>
diff --git a/app/assets/javascripts/monitoring/components/create_dashboard_modal.vue b/app/assets/javascripts/monitoring/components/create_dashboard_modal.vue
new file mode 100644
index 00000000000..74799002b17
--- /dev/null
+++ b/app/assets/javascripts/monitoring/components/create_dashboard_modal.vue
@@ -0,0 +1,66 @@
+<script>
+import { GlButton, GlModal, GlSprintf } from '@gitlab/ui';
+import { s__ } from '~/locale';
+import { isSafeURL } from '~/lib/utils/url_utility';
+
+export default {
+ components: { GlButton, GlModal, GlSprintf },
+ props: {
+ modalId: {
+ type: String,
+ required: true,
+ },
+ projectPath: {
+ type: String,
+ required: true,
+ validator: isSafeURL,
+ },
+ addDashboardDocumentationPath: {
+ type: String,
+ required: true,
+ },
+ },
+ methods: {
+ cancelHandler() {
+ this.$refs.modal.hide();
+ },
+ },
+ i18n: {
+ titleText: s__('Metrics|Create your dashboard configuration file'),
+ mainText: s__(
+ 'Metrics|To create a new dashboard, add a new YAML file to %{codeStart}.gitlab/dashboards%{codeEnd} at the root of this project.',
+ ),
+ },
+};
+</script>
+
+<template>
+ <gl-modal ref="modal" :modal-id="modalId" :title="$options.i18n.titleText">
+ <p>
+ <gl-sprintf :message="$options.i18n.mainText">
+ <template #code="{ content }">
+ <code>{{ content }}</code>
+ </template>
+ </gl-sprintf>
+ </p>
+ <template #modal-footer>
+ <gl-button category="secondary" @click="cancelHandler">{{ s__('Metrics|Cancel') }}</gl-button>
+ <gl-button
+ category="secondary"
+ variant="info"
+ target="_blank"
+ :href="addDashboardDocumentationPath"
+ data-testid="create-dashboard-modal-docs-button"
+ >
+ {{ s__('Metrics|View documentation') }}
+ </gl-button>
+ <gl-button
+ variant="success"
+ data-testid="create-dashboard-modal-repo-button"
+ :href="projectPath"
+ >
+ {{ s__('Metrics|Open repository') }}
+ </gl-button>
+ </template>
+ </gl-modal>
+</template>
diff --git a/app/assets/javascripts/monitoring/components/dashboard.vue b/app/assets/javascripts/monitoring/components/dashboard.vue
index f54319d283e..bde62275797 100644
--- a/app/assets/javascripts/monitoring/components/dashboard.vue
+++ b/app/assets/javascripts/monitoring/components/dashboard.vue
@@ -1,6 +1,7 @@
<script>
import { mapActions, mapState, mapGetters } from 'vuex';
import VueDraggable from 'vuedraggable';
+import Mousetrap from 'mousetrap';
import { GlIcon, GlButton, GlModalDirective, GlTooltipDirective } from '@gitlab/ui';
import DashboardHeader from './dashboard_header.vue';
import DashboardPanel from './dashboard_panel.vue';
@@ -24,7 +25,7 @@ import {
expandedPanelPayloadFromUrl,
convertVariablesForURL,
} from '../utils';
-import { metricStates } from '../constants';
+import { metricStates, keyboardShortcutKeys } from '../constants';
import { defaultTimeRange } from '~/vue_shared/constants';
export default {
@@ -71,6 +72,10 @@ export default {
type: String,
required: true,
},
+ addDashboardDocumentationPath: {
+ type: String,
+ required: true,
+ },
settingsPath: {
type: String,
required: true,
@@ -149,21 +154,25 @@ export default {
selectedTimeRange: timeRangeFromUrl() || defaultTimeRange,
isRearrangingPanels: false,
originalDocumentTitle: document.title,
+ hoveredPanel: '',
};
},
computed: {
...mapState('monitoringDashboard', [
'dashboard',
'emptyState',
- 'showEmptyState',
'expandedPanel',
'variables',
'links',
'currentDashboard',
+ 'hasDashboardValidationWarnings',
]),
...mapGetters('monitoringDashboard', ['selectedDashboard', 'getMetricStates']),
+ shouldShowEmptyState() {
+ return Boolean(this.emptyState);
+ },
shouldShowVariablesSection() {
- return Object.keys(this.variables).length > 0;
+ return Boolean(this.variables.length);
},
shouldShowLinksSection() {
return Object.keys(this.links).length > 0;
@@ -197,12 +206,29 @@ export default {
selectedDashboard(dashboard) {
this.prependToDocumentTitle(dashboard?.display_name);
},
+ hasDashboardValidationWarnings(hasWarnings) {
+ /**
+ * This watcher is set for future SPA behaviour of the dashboard
+ */
+ if (hasWarnings) {
+ createFlash(
+ s__(
+ 'Metrics|Your dashboard schema is invalid. Edit the dashboard to correct the YAML schema.',
+ ),
+ 'warning',
+ );
+ }
+ },
},
created() {
window.addEventListener('keyup', this.onKeyup);
+
+ Mousetrap.bind(Object.values(keyboardShortcutKeys), this.runShortcut);
},
destroyed() {
window.removeEventListener('keyup', this.onKeyup);
+
+ Mousetrap.unbind(Object.values(keyboardShortcutKeys));
},
mounted() {
if (!this.hasMetrics) {
@@ -254,6 +280,14 @@ export default {
return null;
},
/**
+ * Return true if the entire group is loading.
+ * @param {String} groupKey - Identifier for group
+ * @returns {boolean}
+ */
+ isGroupLoading(groupKey) {
+ return this.groupSingleEmptyState(groupKey) === metricStates.LOADING;
+ },
+ /**
* A group should be not collapsed if any metric is loaded (OK)
*
* @param {String} groupKey - Identifier for group
@@ -302,6 +336,66 @@ export default {
// As a fallback, switch to default time range instead
this.selectedTimeRange = defaultTimeRange;
},
+ isPanelHalfWidth(panelIndex, totalPanels) {
+ /**
+ * A single panel on a row should take the full width of its parent.
+ * All others should have half the width their parent.
+ */
+ const isNumberOfPanelsEven = totalPanels % 2 === 0;
+ const isLastPanel = panelIndex === totalPanels - 1;
+
+ return isNumberOfPanelsEven || !isLastPanel;
+ },
+ /**
+ * TODO: Investigate this to utilize the eventBus from Vue
+ * The intentation behind this cleanup is to allow for better tests
+ * as well as use the correct eventBus facilities that are compatible
+ * with Vue 3
+ * https://gitlab.com/gitlab-org/gitlab/-/issues/225583
+ */
+ //
+ runShortcut(e) {
+ const panel = this.$refs[this.hoveredPanel];
+
+ if (!panel) return;
+
+ const [panelInstance] = panel;
+ let actionToRun = '';
+
+ switch (e.key) {
+ case keyboardShortcutKeys.EXPAND:
+ actionToRun = 'onExpandFromKeyboardShortcut';
+ break;
+
+ case keyboardShortcutKeys.VISIT_LOGS:
+ actionToRun = 'visitLogsPageFromKeyboardShortcut';
+ break;
+
+ case keyboardShortcutKeys.SHOW_ALERT:
+ actionToRun = 'showAlertModalFromKeyboardShortcut';
+ break;
+
+ case keyboardShortcutKeys.DOWNLOAD_CSV:
+ actionToRun = 'downloadCsvFromKeyboardShortcut';
+ break;
+
+ case keyboardShortcutKeys.CHART_COPY:
+ actionToRun = 'copyChartLinkFromKeyboardShotcut';
+ break;
+
+ default:
+ actionToRun = 'onExpandFromKeyboardShortcut';
+ break;
+ }
+
+ panelInstance[actionToRun]();
+ },
+ setHoveredPanel(groupKey, graphIndex) {
+ this.hoveredPanel = `dashboard-panel-${groupKey}-${graphIndex}`;
+ },
+ clearHoveredPanel() {
+ this.hoveredPanel = '';
+ },
},
i18n: {
goBackLabel: s__('Metrics|Go back (Esc)'),
@@ -315,6 +409,7 @@ export default {
v-if="showHeader"
ref="prometheusGraphsHeader"
class="prometheus-graphs-header d-sm-flex flex-sm-wrap pt-2 pr-1 pb-0 pl-2 border-bottom bg-gray-light"
+ :add-dashboard-documentation-path="addDashboardDocumentationPath"
:default-branch="defaultBranch"
:rearrange-panels-available="rearrangePanelsAvailable"
:custom-metrics-available="customMetricsAvailable"
@@ -327,9 +422,9 @@ export default {
@dateTimePickerInvalid="onDateTimePickerInvalid"
@setRearrangingPanels="onSetRearrangingPanels"
/>
- <variables-section v-if="shouldShowVariablesSection && !showEmptyState" />
- <links-section v-if="shouldShowLinksSection && !showEmptyState" />
- <div v-if="!showEmptyState">
+ <template v-if="!shouldShowEmptyState">
+ <variables-section v-if="shouldShowVariablesSection" />
+ <links-section v-if="shouldShowLinksSection" />
<dashboard-panel
v-show="expandedPanel.panel"
ref="expandedPanel"
@@ -364,6 +459,7 @@ export default {
:key="`${groupData.group}.${groupData.priority}`"
:name="groupData.group"
:show-panels="showPanels"
+ :is-loading="isGroupLoading(groupData.key)"
:collapse-group="collapseGroup(groupData.key)"
>
<vue-draggable
@@ -377,8 +473,14 @@ export default {
<div
v-for="(graphData, graphIndex) in groupData.panels"
:key="`dashboard-panel-${graphIndex}`"
- class="col-12 col-lg-6 px-2 mb-2 draggable"
- :class="{ 'draggable-enabled': isRearrangingPanels }"
+ data-testid="dashboard-panel-layout-wrapper"
+ class="col-12 px-2 mb-2 draggable"
+ :class="{
+ 'draggable-enabled': isRearrangingPanels,
+ 'col-lg-6': isPanelHalfWidth(graphIndex, groupData.panels.length),
+ }"
+ @mouseover="setHoveredPanel(groupData.key, graphIndex)"
+ @mouseout="clearHoveredPanel"
>
<div class="position-relative draggable-panel js-draggable-panel">
<div
@@ -392,6 +494,7 @@ export default {
</div>
<dashboard-panel
+ :ref="`dashboard-panel-${groupData.key}-${graphIndex}`"
:settings-path="settingsPath"
:clipboard-text="generatePanelUrl(groupData.group, graphData)"
:graph-data="graphData"
@@ -414,7 +517,7 @@ export default {
</div>
</graph-group>
</div>
- </div>
+ </template>
<empty-state
v-else
:selected-state="emptyState"
diff --git a/app/assets/javascripts/monitoring/components/dashboard_header.vue b/app/assets/javascripts/monitoring/components/dashboard_header.vue
index 16a21ae0d3c..fe6ca3a2a07 100644
--- a/app/assets/javascripts/monitoring/components/dashboard_header.vue
+++ b/app/assets/javascripts/monitoring/components/dashboard_header.vue
@@ -2,12 +2,16 @@
import { debounce } from 'lodash';
import { mapActions, mapState, mapGetters } from 'vuex';
import {
+ GlButton,
GlIcon,
GlDeprecatedButton,
GlDropdown,
GlDropdownItem,
GlDropdownHeader,
GlDropdownDivider,
+ GlNewDropdown,
+ GlNewDropdownDivider,
+ GlNewDropdownItem,
GlModal,
GlLoadingIcon,
GlSearchBoxByType,
@@ -22,6 +26,9 @@ import Icon from '~/vue_shared/components/icon.vue';
import DateTimePicker from '~/vue_shared/components/date_time_picker/date_time_picker.vue';
import DashboardsDropdown from './dashboards_dropdown.vue';
+import RefreshButton from './refresh_button.vue';
+import CreateDashboardModal from './create_dashboard_modal.vue';
+import DuplicateDashboardModal from './duplicate_dashboard_modal.vue';
import TrackEventDirective from '~/vue_shared/directives/track_event';
import { getAddMetricTrackingOptions, timeRangeToUrl } from '../utils';
@@ -31,6 +38,7 @@ import { timezones } from '../format_date';
export default {
components: {
Icon,
+ GlButton,
GlIcon,
GlDeprecatedButton,
GlDropdown,
@@ -38,12 +46,18 @@ export default {
GlDropdownItem,
GlDropdownHeader,
GlDropdownDivider,
+ GlNewDropdown,
+ GlNewDropdownDivider,
+ GlNewDropdownItem,
GlSearchBoxByType,
GlModal,
CustomMetricsFormFields,
DateTimePicker,
DashboardsDropdown,
+ RefreshButton,
+ DuplicateDashboardModal,
+ CreateDashboardModal,
},
directives: {
GlModal: GlModalDirective,
@@ -93,6 +107,10 @@ export default {
type: Object,
required: true,
},
+ addDashboardDocumentationPath: {
+ type: String,
+ required: true,
+ },
},
data() {
return {
@@ -101,20 +119,30 @@ export default {
},
computed: {
...mapState('monitoringDashboard', [
+ 'emptyState',
'environmentsLoading',
'currentEnvironmentName',
'isUpdatingStarredValue',
- 'showEmptyState',
'dashboardTimezone',
+ 'projectPath',
+ 'canAccessOperationsSettings',
+ 'operationsSettingsPath',
+ 'currentDashboard',
]),
...mapGetters('monitoringDashboard', ['selectedDashboard', 'filteredEnvironments']),
+ isOutOfTheBoxDashboard() {
+ return this.selectedDashboard?.out_of_the_box_dashboard;
+ },
+ shouldShowEmptyState() {
+ return Boolean(this.emptyState);
+ },
shouldShowEnvironmentsDropdownNoMatchedMsg() {
return !this.environmentsLoading && this.filteredEnvironments.length === 0;
},
addingMetricsAvailable() {
return (
this.customMetricsAvailable &&
- !this.showEmptyState &&
+ !this.shouldShowEmptyState &&
// Custom metrics only avaialble on system dashboards because
// they are stored in the database. This can be improved. See:
// https://gitlab.com/gitlab-org/gitlab/-/issues/28241
@@ -122,23 +150,29 @@ export default {
);
},
showRearrangePanelsBtn() {
- return !this.showEmptyState && this.rearrangePanelsAvailable;
+ return !this.shouldShowEmptyState && this.rearrangePanelsAvailable;
},
displayUtc() {
return this.dashboardTimezone === timezones.UTC;
},
+ shouldShowActionsMenu() {
+ return Boolean(this.projectPath);
+ },
+ shouldShowSettingsButton() {
+ return this.canAccessOperationsSettings && this.operationsSettingsPath;
+ },
},
methods: {
- ...mapActions('monitoringDashboard', [
- 'filterEnvironments',
- 'fetchDashboardData',
- 'toggleStarredValue',
- ]),
+ ...mapActions('monitoringDashboard', ['filterEnvironments', 'toggleStarredValue']),
selectDashboard(dashboard) {
- const params = {
- dashboard: dashboard.path,
- };
- redirectTo(mergeUrlParams(params, window.location.href));
+ // Once the sidebar See metrics link is updated to the new URL,
+ // this sort of hardcoding will not be necessary.
+ // https://gitlab.com/gitlab-org/gitlab/-/issues/229277
+ const baseURL = `${this.projectPath}/-/metrics`;
+ const dashboardPath = encodeURIComponent(
+ dashboard.out_of_the_box_dashboard ? dashboard.path : dashboard.display_name,
+ );
+ redirectTo(`${baseURL}/${dashboardPath}`);
},
debouncedEnvironmentsSearch: debounce(function environmentsSearchOnInput(searchTerm) {
this.filterEnvironments(searchTerm);
@@ -149,9 +183,6 @@ export default {
onDateTimePickerInvalid() {
this.$emit('dateTimePickerInvalid');
},
- refreshDashboard() {
- this.fetchDashboardData();
- },
toggleRearrangingPanels() {
this.$emit('setRearrangingPanels', !this.isRearrangingPanels);
@@ -166,14 +197,27 @@ export default {
submitCustomMetricsForm() {
this.$refs.customMetricsForm.submit();
},
+ getEnvironmentPath(environment) {
+ // Once the sidebar See metrics link is updated to the new URL,
+ // this sort of hardcoding will not be necessary.
+ // https://gitlab.com/gitlab-org/gitlab/-/issues/229277
+ const baseURL = `${this.projectPath}/-/metrics`;
+ const dashboardPath = encodeURIComponent(this.currentDashboard || '');
+ // The environment_metrics_spec.rb requires the URL to not have
+ // slashes. Hence, this additional check.
+ const url = dashboardPath ? `${baseURL}/${dashboardPath}` : baseURL;
+ return mergeUrlParams({ environment }, url);
+ },
},
- addMetric: {
- title: s__('Metrics|Add metric'),
- modalId: 'add-metric',
+ modalIds: {
+ addMetric: 'addMetric',
+ createDashboard: 'createDashboard',
+ duplicateDashboard: 'duplicateDashboard',
},
i18n: {
starDashboard: s__('Metrics|Star dashboard'),
unstarDashboard: s__('Metrics|Unstar dashboard'),
+ addMetric: s__('Metrics|Add metric'),
},
timeRanges,
};
@@ -181,17 +225,20 @@ export default {
<template>
<div ref="prometheusGraphsHeader">
- <div class="mb-2 pr-2 d-flex d-sm-block">
+ <div class="mb-2 mr-2 d-flex d-sm-block">
<dashboards-dropdown
id="monitor-dashboards-dropdown"
data-qa-selector="dashboards_filter_dropdown"
class="flex-grow-1"
toggle-class="dropdown-menu-toggle"
:default-branch="defaultBranch"
+ :modal-id="$options.modalIds.duplicateDashboard"
@selectDashboard="selectDashboard"
/>
</div>
+ <span aria-hidden="true" class="gl-pl-3 border-left gl-mb-3 d-none d-sm-block"></span>
+
<div class="mb-2 pr-2 d-flex d-sm-block">
<gl-dropdown
id="monitor-environments-dropdown"
@@ -223,7 +270,7 @@ export default {
:key="environment.id"
:active="environment.name === currentEnvironmentName"
active-class="is-active"
- :href="environment.metrics_path"
+ :href="getEnvironmentPath(environment.id)"
>{{ environment.name }}</gl-dropdown-item
>
</div>
@@ -252,16 +299,7 @@ export default {
</div>
<div class="mb-2 pr-2 d-flex d-sm-block">
- <gl-deprecated-button
- ref="refreshDashboardBtn"
- v-gl-tooltip
- class="flex-grow-1"
- variant="default"
- :title="s__('Metrics|Refresh dashboard')"
- @click="refreshDashboard"
- >
- <icon name="retry" />
- </gl-deprecated-button>
+ <refresh-button />
</div>
<div class="flex-grow-1"></div>
@@ -304,17 +342,17 @@ export default {
<div v-if="addingMetricsAvailable" class="mb-2 mr-2 d-flex d-sm-block">
<gl-deprecated-button
ref="addMetricBtn"
- v-gl-modal="$options.addMetric.modalId"
+ v-gl-modal="$options.modalIds.addMetric"
variant="outline-success"
data-qa-selector="add_metric_button"
class="flex-grow-1"
>
- {{ $options.addMetric.title }}
+ {{ $options.i18n.addMetric }}
</gl-deprecated-button>
<gl-modal
ref="addMetricModal"
- :modal-id="$options.addMetric.modalId"
- :title="$options.addMetric.title"
+ :modal-id="$options.modalIds.addMetric"
+ :title="$options.i18n.addMetric"
>
<form ref="customMetricsForm" :action="customMetricsPath" method="post">
<custom-metrics-form-fields
@@ -353,7 +391,10 @@ export default {
</gl-deprecated-button>
</div>
- <div v-if="externalDashboardUrl.length" class="mb-2 mr-2 d-flex d-sm-block">
+ <div
+ v-if="externalDashboardUrl && externalDashboardUrl.length"
+ class="mb-2 mr-2 d-flex d-sm-block"
+ >
<gl-deprecated-button
class="flex-grow-1 js-external-dashboard-link"
variant="primary"
@@ -364,6 +405,63 @@ export default {
{{ __('View full dashboard') }} <icon name="external-link" />
</gl-deprecated-button>
</div>
+
+ <!-- This separator should be displayed only if at least one of the action menu or settings button are displayed -->
+ <span
+ v-if="shouldShowActionsMenu || shouldShowSettingsButton"
+ aria-hidden="true"
+ class="gl-pl-3 border-left gl-mb-3 d-none d-sm-block"
+ ></span>
+
+ <div v-if="shouldShowActionsMenu" class="gl-mb-3 gl-mr-3 d-flex d-sm-block">
+ <gl-new-dropdown
+ v-gl-tooltip
+ right
+ class="gl-flex-grow-1"
+ data-testid="actions-menu"
+ :title="s__('Metrics|Create dashboard')"
+ :icon="'plus-square'"
+ >
+ <gl-new-dropdown-item
+ v-gl-modal="$options.modalIds.createDashboard"
+ data-testid="action-create-dashboard"
+ >{{ s__('Metrics|Create new dashboard') }}</gl-new-dropdown-item
+ >
+
+ <create-dashboard-modal
+ data-testid="create-dashboard-modal"
+ :add-dashboard-documentation-path="addDashboardDocumentationPath"
+ :modal-id="$options.modalIds.createDashboard"
+ :project-path="projectPath"
+ />
+
+ <template v-if="isOutOfTheBoxDashboard">
+ <gl-new-dropdown-divider />
+ <gl-new-dropdown-item
+ ref="duplicateDashboardItem"
+ v-gl-modal="$options.modalIds.duplicateDashboard"
+ data-testid="action-duplicate-dashboard"
+ >
+ {{ s__('Metrics|Duplicate current dashboard') }}
+ </gl-new-dropdown-item>
+ </template>
+ </gl-new-dropdown>
+ </div>
+
+ <div v-if="shouldShowSettingsButton" class="mb-2 mr-2 d-flex d-sm-block">
+ <gl-button
+ v-gl-tooltip
+ data-testid="metrics-settings-button"
+ icon="settings"
+ :href="operationsSettingsPath"
+ :title="s__('Metrics|Metrics Settings')"
+ />
+ </div>
</div>
+ <duplicate-dashboard-modal
+ :default-branch="defaultBranch"
+ :modal-id="$options.modalIds.duplicateDashboard"
+ @dashboardDuplicated="selectDashboard"
+ />
</div>
</template>
diff --git a/app/assets/javascripts/monitoring/components/dashboard_panel.vue b/app/assets/javascripts/monitoring/components/dashboard_panel.vue
index 9545a211bbd..3e3c8408de3 100644
--- a/app/assets/javascripts/monitoring/components/dashboard_panel.vue
+++ b/app/assets/javascripts/monitoring/components/dashboard_panel.vue
@@ -2,6 +2,7 @@
import { mapState } from 'vuex';
import { pickBy } from 'lodash';
import invalidUrl from '~/lib/utils/invalid_url';
+import { relativePathToAbsolute, getBaseURL, visitUrl, isSafeURL } from '~/lib/utils/url_utility';
import {
GlResizeObserverDirective,
GlIcon,
@@ -29,7 +30,6 @@ import MonitorStackedColumnChart from './charts/stacked_column.vue';
import TrackEventDirective from '~/vue_shared/directives/track_event';
import AlertWidget from './alert_widget.vue';
import { timeRangeToUrl, downloadCSVOptions, generateLinkToChartOptions } from '../utils';
-import { isSafeURL } from '~/lib/utils/url_utility';
const events = {
timeRangeZoom: 'timerangezoom',
@@ -132,7 +132,8 @@ export default {
return this.graphData?.title || '';
},
graphDataHasResult() {
- return this.graphData?.metrics?.[0]?.result?.length > 0;
+ const metrics = this.graphData?.metrics || [];
+ return metrics.some(({ result }) => result?.length > 0);
},
graphDataIsLoading() {
const metrics = this.graphData?.metrics || [];
@@ -207,7 +208,17 @@ export default {
return MonitorTimeSeriesChart;
},
isContextualMenuShown() {
- return Boolean(this.graphDataHasResult && !this.basicChartComponent);
+ if (!this.graphDataHasResult) {
+ return false;
+ }
+ // Only a few charts have a contextual menu, support
+ // for more chart types planned at:
+ // https://gitlab.com/groups/gitlab-org/-/epics/3573
+ return (
+ this.isPanelType(panelTypes.AREA_CHART) ||
+ this.isPanelType(panelTypes.LINE_CHART) ||
+ this.isPanelType(panelTypes.SINGLE_STAT)
+ );
},
editCustomMetricLink() {
if (this.graphData.metrics.length > 1) {
@@ -223,13 +234,19 @@ export default {
return metrics.some(({ metricId }) => this.metricsSavedToDb.includes(metricId));
},
alertWidgetAvailable() {
+ const supportsAlerts =
+ this.isPanelType(panelTypes.AREA_CHART) || this.isPanelType(panelTypes.LINE_CHART);
return (
+ supportsAlerts &&
this.prometheusAlertsAvailable &&
this.alertsEndpoint &&
this.graphData &&
this.hasMetricsInDb
);
},
+ alertModalId() {
+ return `alert-modal-${this.graphData.id}`;
+ },
},
mounted() {
this.refreshTitleTooltip();
@@ -268,6 +285,11 @@ export default {
onExpand() {
this.$emit(events.expand);
},
+ onExpandFromKeyboardShortcut() {
+ if (this.isContextualMenuShown) {
+ this.onExpand();
+ }
+ },
setAlerts(alertPath, alertAttributes) {
if (alertAttributes) {
this.$set(this.allAlerts, alertPath, alertAttributes);
@@ -278,18 +300,45 @@ export default {
safeUrl(url) {
return isSafeURL(url) ? url : '#';
},
+ showAlertModal() {
+ this.$root.$emit('bv::show::modal', this.alertModalId);
+ },
+ showAlertModalFromKeyboardShortcut() {
+ if (this.isContextualMenuShown) {
+ this.showAlertModal();
+ }
+ },
+ visitLogsPage() {
+ if (this.logsPathWithTimeRange) {
+ visitUrl(relativePathToAbsolute(this.logsPathWithTimeRange, getBaseURL()));
+ }
+ },
+ visitLogsPageFromKeyboardShortcut() {
+ if (this.isContextualMenuShown) {
+ this.visitLogsPage();
+ }
+ },
+ downloadCsvFromKeyboardShortcut() {
+ if (this.csvText && this.isContextualMenuShown) {
+ this.$refs.downloadCsvLink.$el.firstChild.click();
+ }
+ },
+ copyChartLinkFromKeyboardShotcut() {
+ if (this.clipboardText && this.isContextualMenuShown) {
+ this.$refs.copyChartLink.$el.firstChild.click();
+ }
+ },
},
panelTypes,
};
</script>
<template>
<div v-gl-resize-observer="onResize" class="prometheus-graph">
- <div class="d-flex align-items-center mr-3">
+ <div class="d-flex align-items-center">
<slot name="topLeft"></slot>
<h5
ref="graphTitle"
class="prometheus-graph-title gl-font-lg font-weight-bold text-truncate gl-mr-3"
- tabindex="0"
>
{{ title }}
</h5>
@@ -299,7 +348,7 @@ export default {
<alert-widget
v-if="isContextualMenuShown && alertWidgetAvailable"
class="mx-1"
- :modal-id="`alert-modal-${graphData.id}`"
+ :modal-id="alertModalId"
:alerts-endpoint="alertsEndpoint"
:relevant-queries="graphData.metrics"
:alerts-to-manage="getGraphAlerts(graphData.metrics)"
@@ -314,7 +363,7 @@ export default {
ref="contextualMenu"
data-qa-selector="prometheus_graph_widgets"
>
- <div class="d-flex align-items-center">
+ <div data-testid="dropdown-wrapper" class="d-flex align-items-center">
<gl-dropdown
v-gl-tooltip
toggle-class="shadow-none border-0"
@@ -369,13 +418,13 @@ export default {
</gl-dropdown-item>
<gl-dropdown-item
v-if="alertWidgetAvailable"
- v-gl-modal="`alert-modal-${graphData.id}`"
+ v-gl-modal="alertModalId"
data-qa-selector="alert_widget_menu_item"
>
{{ __('Alerts') }}
</gl-dropdown-item>
- <template v-if="graphData.links.length">
+ <template v-if="graphData.links && graphData.links.length">
<gl-dropdown-divider />
<gl-dropdown-item
v-for="(link, index) in graphData.links"
diff --git a/app/assets/javascripts/monitoring/components/dashboards_dropdown.vue b/app/assets/javascripts/monitoring/components/dashboards_dropdown.vue
index 8b86890715f..574f48a72fe 100644
--- a/app/assets/javascripts/monitoring/components/dashboards_dropdown.vue
+++ b/app/assets/javascripts/monitoring/components/dashboards_dropdown.vue
@@ -1,19 +1,14 @@
<script>
import { mapState, mapActions, mapGetters } from 'vuex';
import {
- GlAlert,
GlIcon,
GlDropdown,
GlDropdownItem,
GlDropdownHeader,
GlDropdownDivider,
GlSearchBoxByType,
- GlModal,
- GlLoadingIcon,
GlModalDirective,
} from '@gitlab/ui';
-import { s__ } from '~/locale';
-import DuplicateDashboardForm from './duplicate_dashboard_form.vue';
const events = {
selectDashboard: 'selectDashboard',
@@ -21,16 +16,12 @@ const events = {
export default {
components: {
- GlAlert,
GlIcon,
GlDropdown,
GlDropdownItem,
GlDropdownHeader,
GlDropdownDivider,
GlSearchBoxByType,
- GlModal,
- GlLoadingIcon,
- DuplicateDashboardForm,
},
directives: {
GlModal: GlModalDirective,
@@ -40,20 +31,21 @@ export default {
type: String,
required: true,
},
+ modalId: {
+ type: String,
+ required: true,
+ },
},
data() {
return {
- alert: null,
- loading: false,
- form: {},
searchTerm: '',
};
},
computed: {
...mapState('monitoringDashboard', ['allDashboards']),
...mapGetters('monitoringDashboard', ['selectedDashboard']),
- isSystemDashboard() {
- return this.selectedDashboard?.system_dashboard;
+ isOutOfTheBoxDashboard() {
+ return this.selectedDashboard?.out_of_the_box_dashboard;
},
selectedDashboardText() {
return this.selectedDashboard?.display_name;
@@ -76,10 +68,6 @@ export default {
nonStarredDashboards() {
return this.filteredDashboards.filter(({ starred }) => !starred);
},
-
- okButtonText() {
- return this.loading ? s__('Metrics|Duplicating...') : s__('Metrics|Duplicate');
- },
},
methods: {
...mapActions('monitoringDashboard', ['duplicateSystemDashboard']),
@@ -89,37 +77,6 @@ export default {
selectDashboard(dashboard) {
this.$emit(events.selectDashboard, dashboard);
},
- ok(bvModalEvt) {
- // Prevent modal from hiding in case submit fails
- bvModalEvt.preventDefault();
-
- this.loading = true;
- this.alert = null;
- this.duplicateSystemDashboard(this.form)
- .then(createdDashboard => {
- this.loading = false;
- this.alert = null;
-
- // Trigger hide modal as submit is successful
- this.$refs.duplicateDashboardModal.hide();
-
- // Dashboards in the default branch become available immediately.
- // Not so in other branches, so we refresh the current dashboard
- const dashboard =
- this.form.branch === this.defaultBranch ? createdDashboard : this.selectedDashboard;
- this.$emit(events.selectDashboard, dashboard);
- })
- .catch(error => {
- this.loading = false;
- this.alert = error;
- });
- },
- hide() {
- this.alert = null;
- },
- formChange(form) {
- this.form = form;
- },
},
};
</script>
@@ -178,32 +135,14 @@ export default {
{{ __('No matching results') }}
</div>
- <template v-if="isSystemDashboard">
+ <!--
+ This Duplicate Dashboard item will be removed from the dashboards dropdown
+ in https://gitlab.com/gitlab-org/gitlab/-/issues/223223
+ -->
+ <template v-if="isOutOfTheBoxDashboard">
<gl-dropdown-divider />
- <gl-modal
- ref="duplicateDashboardModal"
- modal-id="duplicateDashboardModal"
- :title="s__('Metrics|Duplicate dashboard')"
- ok-variant="success"
- @ok="ok"
- @hide="hide"
- >
- <gl-alert v-if="alert" class="mb-3" variant="danger" @dismiss="alert = null">
- {{ alert }}
- </gl-alert>
- <duplicate-dashboard-form
- :dashboard="selectedDashboard"
- :default-branch="defaultBranch"
- @change="formChange"
- />
- <template #modal-ok>
- <gl-loading-icon v-if="loading" inline color="light" />
- {{ okButtonText }}
- </template>
- </gl-modal>
-
- <gl-dropdown-item ref="duplicateDashboardItem" v-gl-modal="'duplicateDashboardModal'">
+ <gl-dropdown-item v-gl-modal="modalId" data-testid="duplicateDashboardItem">
{{ s__('Metrics|Duplicate dashboard') }}
</gl-dropdown-item>
</template>
diff --git a/app/assets/javascripts/monitoring/components/duplicate_dashboard_modal.vue b/app/assets/javascripts/monitoring/components/duplicate_dashboard_modal.vue
new file mode 100644
index 00000000000..e64afc01fd9
--- /dev/null
+++ b/app/assets/javascripts/monitoring/components/duplicate_dashboard_modal.vue
@@ -0,0 +1,95 @@
+<script>
+import { mapActions, mapGetters } from 'vuex';
+import { GlAlert, GlLoadingIcon, GlModal } from '@gitlab/ui';
+import { s__ } from '~/locale';
+import DuplicateDashboardForm from './duplicate_dashboard_form.vue';
+
+const events = {
+ dashboardDuplicated: 'dashboardDuplicated',
+};
+
+export default {
+ components: { GlAlert, GlLoadingIcon, GlModal, DuplicateDashboardForm },
+ props: {
+ defaultBranch: {
+ type: String,
+ required: true,
+ },
+ modalId: {
+ type: String,
+ required: true,
+ },
+ },
+ data() {
+ return {
+ alert: null,
+ loading: false,
+ form: {},
+ };
+ },
+ computed: {
+ ...mapGetters('monitoringDashboard', ['selectedDashboard']),
+ okButtonText() {
+ return this.loading ? s__('Metrics|Duplicating...') : s__('Metrics|Duplicate');
+ },
+ },
+ methods: {
+ ...mapActions('monitoringDashboard', ['duplicateSystemDashboard']),
+ ok(bvModalEvt) {
+ // Prevent modal from hiding in case submit fails
+ bvModalEvt.preventDefault();
+
+ this.loading = true;
+ this.alert = null;
+ this.duplicateSystemDashboard(this.form)
+ .then(createdDashboard => {
+ this.loading = false;
+ this.alert = null;
+
+ // Trigger hide modal as submit is successful
+ this.$refs.duplicateDashboardModal.hide();
+
+ // Dashboards in the default branch become available immediately.
+ // Not so in other branches, so we refresh the current dashboard
+ const dashboard =
+ this.form.branch === this.defaultBranch ? createdDashboard : this.selectedDashboard;
+ this.$emit(events.dashboardDuplicated, dashboard);
+ })
+ .catch(error => {
+ this.loading = false;
+ this.alert = error;
+ });
+ },
+ hide() {
+ this.alert = null;
+ },
+ formChange(form) {
+ this.form = form;
+ },
+ },
+};
+</script>
+
+<template>
+ <gl-modal
+ ref="duplicateDashboardModal"
+ :modal-id="modalId"
+ :title="s__('Metrics|Duplicate dashboard')"
+ ok-variant="success"
+ @ok="ok"
+ @hide="hide"
+ >
+ <gl-alert v-if="alert" class="mb-3" variant="danger" @dismiss="alert = null">
+ {{ alert }}
+ </gl-alert>
+ <duplicate-dashboard-form
+ :dashboard="selectedDashboard"
+ :default-branch="defaultBranch"
+ @change="formChange"
+ />
+ <template #modal-ok>
+ <gl-loading-icon v-if="loading" inline color="light" />
+ {{ okButtonText }}
+ </template>
+ </gl-modal>
+</template>
diff --git a/app/assets/javascripts/monitoring/components/empty_state.vue b/app/assets/javascripts/monitoring/components/empty_state.vue
index d3157b731b2..5e7c9b5d906 100644
--- a/app/assets/javascripts/monitoring/components/empty_state.vue
+++ b/app/assets/javascripts/monitoring/components/empty_state.vue
@@ -1,12 +1,19 @@
<script>
-import { GlEmptyState } from '@gitlab/ui';
+import { GlLoadingIcon, GlEmptyState } from '@gitlab/ui';
import { __ } from '~/locale';
+import { dashboardEmptyStates } from '../constants';
export default {
components: {
+ GlLoadingIcon,
GlEmptyState,
},
props: {
+ selectedState: {
+ type: String,
+ required: true,
+ validator: state => Object.values(dashboardEmptyStates).includes(state),
+ },
documentationPath: {
type: String,
required: true,
@@ -21,10 +28,6 @@ export default {
required: false,
default: '',
},
- selectedState: {
- type: String,
- required: true,
- },
emptyGettingStartedSvgPath: {
type: String,
required: true,
@@ -53,52 +56,49 @@ export default {
},
data() {
return {
+ /**
+ * Possible empty states.
+ * Keys in each state must match GlEmptyState props
+ */
states: {
- gettingStarted: {
- svgUrl: this.emptyGettingStartedSvgPath,
+ [dashboardEmptyStates.GETTING_STARTED]: {
+ svgPath: this.emptyGettingStartedSvgPath,
title: __('Get started with performance monitoring'),
description: __(`Stay updated about the performance and health
of your environment by configuring Prometheus to monitor your deployments.`),
- buttonText: __('Install on clusters'),
- buttonPath: this.clustersPath,
+ primaryButtonText: __('Install on clusters'),
+ primaryButtonLink: this.clustersPath,
secondaryButtonText: __('Configure existing installation'),
- secondaryButtonPath: this.settingsPath,
+ secondaryButtonLink: this.settingsPath,
},
- loading: {
- svgUrl: this.emptyLoadingSvgPath,
- title: __('Waiting for performance data'),
- description: __(`Creating graphs uses the data from the Prometheus server.
- If this takes a long time, ensure that data is available.`),
- buttonText: __('View documentation'),
- buttonPath: this.documentationPath,
- secondaryButtonText: '',
- secondaryButtonPath: '',
- },
- noData: {
- svgUrl: this.emptyNoDataSvgPath,
+ [dashboardEmptyStates.NO_DATA]: {
+ svgPath: this.emptyNoDataSvgPath,
title: __('No data found'),
description: __(`You are connected to the Prometheus server, but there is currently
no data to display.`),
- buttonText: __('Configure Prometheus'),
- buttonPath: this.settingsPath,
+ primaryButtonText: __('Configure Prometheus'),
+ primaryButtonLink: this.settingsPath,
secondaryButtonText: '',
- secondaryButtonPath: '',
+ secondaryButtonLink: '',
},
- unableToConnect: {
- svgUrl: this.emptyUnableToConnectSvgPath,
+ [dashboardEmptyStates.UNABLE_TO_CONNECT]: {
+ svgPath: this.emptyUnableToConnectSvgPath,
title: __('Unable to connect to Prometheus server'),
description: __(
'Ensure connectivity is available from the GitLab server to the Prometheus server',
),
- buttonText: __('View documentation'),
- buttonPath: this.documentationPath,
+ primaryButtonText: __('View documentation'),
+ primaryButtonLink: this.documentationPath,
secondaryButtonText: __('Configure Prometheus'),
- secondaryButtonPath: this.settingsPath,
+ secondaryButtonLink: this.settingsPath,
},
},
};
},
computed: {
+ isLoading() {
+ return this.selectedState === dashboardEmptyStates.LOADING;
+ },
currentState() {
return this.states[this.selectedState];
},
@@ -107,14 +107,8 @@ export default {
</script>
<template>
- <gl-empty-state
- :title="currentState.title"
- :description="currentState.description"
- :primary-button-text="currentState.buttonText"
- :primary-button-link="currentState.buttonPath"
- :secondary-button-text="currentState.secondaryButtonText"
- :secondary-button-link="currentState.secondaryButtonPath"
- :svg-path="currentState.svgUrl"
- :compact="compact"
- />
+ <div>
+ <gl-loading-icon v-if="isLoading" size="xl" class="gl-my-9" />
+ <gl-empty-state v-if="currentState" v-bind="currentState" :compact="compact" />
+ </div>
</template>
diff --git a/app/assets/javascripts/monitoring/components/graph_group.vue b/app/assets/javascripts/monitoring/components/graph_group.vue
index 08fcfa3bc56..ecb8ef4a0d0 100644
--- a/app/assets/javascripts/monitoring/components/graph_group.vue
+++ b/app/assets/javascripts/monitoring/components/graph_group.vue
@@ -1,9 +1,10 @@
<script>
-import Icon from '~/vue_shared/components/icon.vue';
+import { GlLoadingIcon, GlIcon } from '@gitlab/ui';
export default {
components: {
- Icon,
+ GlLoadingIcon,
+ GlIcon,
},
props: {
name: {
@@ -15,6 +16,11 @@ export default {
required: false,
default: true,
},
+ isLoading: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
/**
* Initial value of collapse on mount.
*/
@@ -52,18 +58,21 @@ export default {
</script>
<template>
- <div v-if="showPanels" ref="graph-group" class="card prometheus-panel" tabindex="0">
+ <div v-if="showPanels" ref="graph-group" class="card prometheus-panel">
<div class="card-header d-flex align-items-center">
<h4 class="flex-grow-1">{{ name }}</h4>
+ <gl-loading-icon v-if="isLoading" name="loading" />
<a
data-testid="group-toggle-button"
+ :aria-label="__('Toggle collapse')"
+ :icon="caretIcon"
role="button"
- class="js-graph-group-toggle gl-text-gray-900"
+ class="js-graph-group-toggle gl-display-flex gl-ml-2 gl-text-gray-900"
tabindex="0"
@click="collapse"
@keyup.enter="collapse"
>
- <icon :size="16" :aria-label="__('Toggle collapse')" :name="caretIcon" />
+ <gl-icon :name="caretIcon" />
</a>
</div>
<div
diff --git a/app/assets/javascripts/monitoring/components/refresh_button.vue b/app/assets/javascripts/monitoring/components/refresh_button.vue
new file mode 100644
index 00000000000..5481806c3e0
--- /dev/null
+++ b/app/assets/javascripts/monitoring/components/refresh_button.vue
@@ -0,0 +1,163 @@
+<script>
+import { n__, __ } from '~/locale';
+import { mapActions } from 'vuex';
+
+import {
+ GlButtonGroup,
+ GlButton,
+ GlNewDropdown,
+ GlNewDropdownItem,
+ GlNewDropdownDivider,
+ GlTooltipDirective,
+} from '@gitlab/ui';
+
+const makeInterval = (length = 0, unit = 's') => {
+ const shortLabel = `${length}${unit}`;
+ switch (unit) {
+ case 'd':
+ return {
+ interval: length * 24 * 60 * 60 * 1000,
+ shortLabel,
+ label: n__('%d day', '%d days', length),
+ };
+ case 'h':
+ return {
+ interval: length * 60 * 60 * 1000,
+ shortLabel,
+ label: n__('%d hour', '%d hours', length),
+ };
+ case 'm':
+ return {
+ interval: length * 60 * 1000,
+ shortLabel,
+ label: n__('%d minute', '%d minutes', length),
+ };
+ case 's':
+ default:
+ return {
+ interval: length * 1000,
+ shortLabel,
+ label: n__('%d second', '%d seconds', length),
+ };
+ }
+};
+
+export default {
+ components: {
+ GlButtonGroup,
+ GlButton,
+ GlNewDropdown,
+ GlNewDropdownItem,
+ GlNewDropdownDivider,
+ },
+ directives: {
+ GlTooltip: GlTooltipDirective,
+ },
+ data() {
+ return {
+ refreshInterval: null,
+ timeoutId: null,
+ };
+ },
+ computed: {
+ dropdownText() {
+ return this.refreshInterval?.shortLabel ?? __('Off');
+ },
+ },
+ watch: {
+ refreshInterval() {
+ if (this.refreshInterval !== null) {
+ this.startAutoRefresh();
+ } else {
+ this.stopAutoRefresh();
+ }
+ },
+ },
+ destroyed() {
+ this.stopAutoRefresh();
+ },
+ methods: {
+ ...mapActions('monitoringDashboard', ['fetchDashboardData']),
+
+ refresh() {
+ this.fetchDashboardData();
+ },
+ startAutoRefresh() {
+ const schedule = () => {
+ if (this.refreshInterval) {
+ this.timeoutId = setTimeout(this.startAutoRefresh, this.refreshInterval.interval);
+ }
+ };
+
+ this.stopAutoRefresh();
+ if (document.hidden) {
+ // Inactive tab? Skip fetch and schedule again
+ schedule();
+ } else {
+ // Active tab! Fetch data and then schedule when settled
+ // eslint-disable-next-line promise/catch-or-return
+ this.fetchDashboardData().finally(schedule);
+ }
+ },
+ stopAutoRefresh() {
+ clearTimeout(this.timeoutId);
+ this.timeoutId = null;
+ },
+
+ setRefreshInterval(option) {
+ this.refreshInterval = option;
+ },
+ removeRefreshInterval() {
+ this.refreshInterval = null;
+ },
+ isChecked(option) {
+ if (this.refreshInterval) {
+ return option.interval === this.refreshInterval.interval;
+ }
+ return false;
+ },
+ },
+
+ refreshIntervals: [
+ makeInterval(5),
+ makeInterval(10),
+ makeInterval(30),
+ makeInterval(5, 'm'),
+ makeInterval(30, 'm'),
+ makeInterval(1, 'h'),
+ makeInterval(2, 'h'),
+ makeInterval(12, 'h'),
+ makeInterval(1, 'd'),
+ ],
+};
+</script>
+
+<template>
+ <gl-button-group>
+ <gl-button
+ v-gl-tooltip
+ class="gl-flex-grow-1"
+ variant="default"
+ :title="s__('Metrics|Refresh dashboard')"
+ icon="retry"
+ @click="refresh"
+ />
+ <gl-new-dropdown v-gl-tooltip :title="s__('Metrics|Set refresh rate')" :text="dropdownText">
+ <gl-new-dropdown-item
+ :is-check-item="true"
+ :is-checked="refreshInterval === null"
+ @click="removeRefreshInterval()"
+ >{{ __('Off') }}</gl-new-dropdown-item
+ >
+ <gl-new-dropdown-divider />
+ <gl-new-dropdown-item
+ v-for="(option, i) in $options.refreshIntervals"
+ :key="i"
+ :is-check-item="true"
+ :is-checked="isChecked(option)"
+ @click="setRefreshInterval(option)"
+ >{{ option.label }}</gl-new-dropdown-item
+ >
+ </gl-new-dropdown>
+ </gl-button-group>
+</template>
diff --git a/app/assets/javascripts/monitoring/components/variables/custom_variable.vue b/app/assets/javascripts/monitoring/components/variables/custom_variable.vue
deleted file mode 100644
index 0ac7c0b80df..00000000000
--- a/app/assets/javascripts/monitoring/components/variables/custom_variable.vue
+++ /dev/null
@@ -1,50 +0,0 @@
-<script>
-import { GlFormGroup, GlDropdown, GlDropdownItem } from '@gitlab/ui';
-
-export default {
- components: {
- GlFormGroup,
- GlDropdown,
- GlDropdownItem,
- },
- props: {
- name: {
- type: String,
- required: true,
- },
- label: {
- type: String,
- required: true,
- },
- value: {
- type: String,
- required: false,
- default: '',
- },
- options: {
- type: Array,
- required: true,
- },
- },
- computed: {
- defaultText() {
- const selectedOpt = this.options.find(opt => opt.value === this.value);
- return selectedOpt?.text || this.value;
- },
- },
- methods: {
- onUpdate(value) {
- this.$emit('onUpdate', this.name, value);
- },
- },
-};
-</script>
-<template>
- <gl-form-group :label="label">
- <gl-dropdown toggle-class="dropdown-menu-toggle" :text="defaultText">
- <gl-dropdown-item v-for="(opt, key) in options" :key="key" @click="onUpdate(opt.value)">{{
- opt.text
- }}</gl-dropdown-item>
- </gl-dropdown>
- </gl-form-group>
-</template>
diff --git a/app/assets/javascripts/monitoring/components/variables/dropdown_field.vue b/app/assets/javascripts/monitoring/components/variables/dropdown_field.vue
new file mode 100644
index 00000000000..4e48292c48d
--- /dev/null
+++ b/app/assets/javascripts/monitoring/components/variables/dropdown_field.vue
@@ -0,0 +1,53 @@
+<script>
+import { GlFormGroup, GlDropdown, GlDropdownItem } from '@gitlab/ui';
+
+export default {
+ components: {
+ GlFormGroup,
+ GlDropdown,
+ GlDropdownItem,
+ },
+ props: {
+ name: {
+ type: String,
+ required: true,
+ },
+ label: {
+ type: String,
+ required: true,
+ },
+ value: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ options: {
+ type: Object,
+ required: true,
+ },
+ },
+ computed: {
+ text() {
+ const selectedOpt = this.options.values?.find(opt => opt.value === this.value);
+ return selectedOpt?.text || this.value;
+ },
+ },
+ methods: {
+ onUpdate(value) {
+ this.$emit('input', value);
+ },
+ },
+};
+</script>
+<template>
+ <gl-form-group :label="label">
+ <gl-dropdown toggle-class="dropdown-menu-toggle" :text="text || s__('Metrics|Select a value')">
+ <gl-dropdown-item
+ v-for="val in options.values"
+ :key="val.value"
+ @click="onUpdate(val.value)"
+ >{{ val.text }}</gl-dropdown-item
+ >
+ </gl-dropdown>
+ </gl-form-group>
+</template>
diff --git a/app/assets/javascripts/monitoring/components/variables/text_field.vue b/app/assets/javascripts/monitoring/components/variables/text_field.vue
new file mode 100644
index 00000000000..a0418806e5f
--- /dev/null
+++ b/app/assets/javascripts/monitoring/components/variables/text_field.vue
@@ -0,0 +1,39 @@
+<script>
+import { GlFormGroup, GlFormInput } from '@gitlab/ui';
+
+export default {
+ components: {
+ GlFormGroup,
+ GlFormInput,
+ },
+ props: {
+ name: {
+ type: String,
+ required: true,
+ },
+ label: {
+ type: String,
+ required: true,
+ },
+ value: {
+ type: String,
+ required: true,
+ },
+ },
+ methods: {
+ onUpdate(event) {
+ this.$emit('input', event.target.value);
+ },
+ },
+};
+</script>
+<template>
+ <gl-form-group :label="label">
+ <gl-form-input
+ :value="value"
+ :name="name"
+ @keyup.native.enter="onUpdate"
+ @blur.native="onUpdate"
+ />
+ </gl-form-group>
+</template>
diff --git a/app/assets/javascripts/monitoring/components/variables/text_variable.vue b/app/assets/javascripts/monitoring/components/variables/text_variable.vue
deleted file mode 100644
index ce0d19760e2..00000000000
--- a/app/assets/javascripts/monitoring/components/variables/text_variable.vue
+++ /dev/null
@@ -1,39 +0,0 @@
-<script>
-import { GlFormGroup, GlFormInput } from '@gitlab/ui';
-
-export default {
- components: {
- GlFormGroup,
- GlFormInput,
- },
- props: {
- name: {
- type: String,
- required: true,
- },
- label: {
- type: String,
- required: true,
- },
- value: {
- type: String,
- required: true,
- },
- },
- methods: {
- onUpdate(event) {
- this.$emit('onUpdate', this.name, event.target.value);
- },
- },
-};
-</script>
-<template>
- <gl-form-group :label="label">
- <gl-form-input
- :value="value"
- :name="name"
- @keyup.native.enter="onUpdate"
- @blur.native="onUpdate"
- />
- </gl-form-group>
-</template>
diff --git a/app/assets/javascripts/monitoring/components/variables_section.vue b/app/assets/javascripts/monitoring/components/variables_section.vue
index 3d1d111d5b3..25d900b07ad 100644
--- a/app/assets/javascripts/monitoring/components/variables_section.vue
+++ b/app/assets/javascripts/monitoring/components/variables_section.vue
@@ -1,13 +1,14 @@
<script>
import { mapState, mapActions } from 'vuex';
-import CustomVariable from './variables/custom_variable.vue';
-import TextVariable from './variables/text_variable.vue';
+import DropdownField from './variables/dropdown_field.vue';
+import TextField from './variables/text_field.vue';
import { setCustomVariablesFromUrl } from '../utils';
+import { VARIABLE_TYPES } from '../constants';
export default {
components: {
- CustomVariable,
- TextVariable,
+ DropdownField,
+ TextField,
},
computed: {
...mapState('monitoringDashboard', ['variables']),
@@ -15,10 +16,9 @@ export default {
methods: {
...mapActions('monitoringDashboard', ['updateVariablesAndFetchData']),
refreshDashboard(variable, value) {
- if (this.variables[variable].value !== value) {
- const changedVariable = { key: variable, value };
+ if (variable.value !== value) {
+ this.updateVariablesAndFetchData({ name: variable.name, value });
// update the Vuex store
- this.updateVariablesAndFetchData(changedVariable);
// the below calls can ideally be moved out of the
// component and into the actions and let the
// mutation respond directly.
@@ -27,27 +27,26 @@ export default {
setCustomVariablesFromUrl(this.variables);
}
},
- variableComponent(type) {
- const types = {
- text: TextVariable,
- custom: CustomVariable,
- };
- return types[type] || TextVariable;
+ variableField(type) {
+ if (type === VARIABLE_TYPES.custom || type === VARIABLE_TYPES.metric_label_values) {
+ return DropdownField;
+ }
+ return TextField;
},
},
};
</script>
<template>
<div ref="variablesSection" class="d-sm-flex flex-sm-wrap pt-2 pr-1 pb-0 pl-2 variables-section">
- <div v-for="(variable, key) in variables" :key="key" class="mb-1 pr-2 d-flex d-sm-block">
+ <div v-for="variable in variables" :key="variable.name" class="mb-1 pr-2 d-flex d-sm-block">
<component
- :is="variableComponent(variable.type)"
+ :is="variableField(variable.type)"
class="mb-0 flex-grow-1"
:label="variable.label"
:value="variable.value"
- :name="key"
+ :name="variable.name"
:options="variable.options"
- @onUpdate="refreshDashboard"
+ @input="refreshDashboard(variable, $event)"
/>
</div>
</div>
diff --git a/app/assets/javascripts/monitoring/constants.js b/app/assets/javascripts/monitoring/constants.js
index 50330046c99..afeb3318eb9 100644
--- a/app/assets/javascripts/monitoring/constants.js
+++ b/app/assets/javascripts/monitoring/constants.js
@@ -1,5 +1,12 @@
export const PROMETHEUS_TIMEOUT = 120000; // TWO_MINUTES
+export const dashboardEmptyStates = {
+ GETTING_STARTED: 'gettingStarted',
+ LOADING: 'loading',
+ NO_DATA: 'noData',
+ UNABLE_TO_CONNECT: 'unableToConnect',
+};
+
/**
* States and error states in Prometheus Queries (PromQL) for metrics
*/
@@ -208,6 +215,14 @@ export const annotationsSymbolIcon = 'path://m5 229 5 8h-10z';
*/
export const DEFAULT_DASHBOARD_PATH = 'config/prometheus/common_metrics.yml';
+/**
+ * GitLab provide metrics dashboards that are available to a user once
+ * the Prometheus managed app has been installed, without any extra setup
+ * required. These "out of the box" dashboards are defined under the
+ * `config/prometheus` path.
+ */
+export const OUT_OF_THE_BOX_DASHBOARDS_PATH_PREFIX = 'config/prometheus/';
+
export const OPERATORS = {
greaterThan: '>',
equalTo: '==',
@@ -230,6 +245,7 @@ export const OPERATORS = {
export const VARIABLE_TYPES = {
custom: 'custom',
text: 'text',
+ metric_label_values: 'metric_label_values',
};
/**
@@ -242,3 +258,17 @@ export const VARIABLE_TYPES = {
* before passing the data to the backend.
*/
export const VARIABLE_PREFIX = 'var-';
+
+/**
+ * All of the actions inside each panel dropdown can be accessed
+ * via keyboard shortcuts than can be activated via mouse hovers
+ * and or focus via tabs.
+ */
+
+export const keyboardShortcutKeys = {
+ EXPAND: 'e',
+ VISIT_LOGS: 'l',
+ SHOW_ALERT: 'a',
+ DOWNLOAD_CSV: 'd',
+ CHART_COPY: 'c',
+};
diff --git a/app/assets/javascripts/monitoring/format_date.js b/app/assets/javascripts/monitoring/format_date.js
index a50d441a09e..c7bc626eb11 100644
--- a/app/assets/javascripts/monitoring/format_date.js
+++ b/app/assets/javascripts/monitoring/format_date.js
@@ -14,6 +14,7 @@ export const timezones = {
export const formats = {
shortTime: 'h:MM TT',
+ shortDateTime: 'm/d h:MM TT',
default: 'dd mmm yyyy, h:MMTT (Z)',
};
diff --git a/app/assets/javascripts/monitoring/monitoring_app.js b/app/assets/javascripts/monitoring/monitoring_app.js
index 08543fa6eb3..307154c9a84 100644
--- a/app/assets/javascripts/monitoring/monitoring_app.js
+++ b/app/assets/javascripts/monitoring/monitoring_app.js
@@ -1,9 +1,8 @@
import Vue from 'vue';
import { GlToast } from '@gitlab/ui';
-import { parseBoolean } from '~/lib/utils/common_utils';
-import { getParameterValues } from '~/lib/utils/url_utility';
import { createStore } from './stores';
import createRouter from './router';
+import { stateAndPropsFromDataset } from './utils';
Vue.use(GlToast);
@@ -11,36 +10,10 @@ export default (props = {}) => {
const el = document.getElementById('prometheus-graphs');
if (el && el.dataset) {
- const [currentDashboard] = getParameterValues('dashboard');
-
- const {
- deploymentsEndpoint,
- dashboardEndpoint,
- dashboardsEndpoint,
- projectPath,
- logsPath,
- currentEnvironmentName,
- dashboardTimezone,
- metricsDashboardBasePath,
- ...dataProps
- } = el.dataset;
-
- const store = createStore({
- currentDashboard,
- deploymentsEndpoint,
- dashboardEndpoint,
- dashboardsEndpoint,
- dashboardTimezone,
- projectPath,
- logsPath,
- currentEnvironmentName,
- });
-
- // HTML attributes are always strings, parse other types.
- dataProps.hasMetrics = parseBoolean(dataProps.hasMetrics);
- dataProps.customMetricsAvailable = parseBoolean(dataProps.customMetricsAvailable);
- dataProps.prometheusAlertsAvailable = parseBoolean(dataProps.prometheusAlertsAvailable);
+ const { metricsDashboardBasePath, ...dataset } = el.dataset;
+ const { initState, dataProps } = stateAndPropsFromDataset(dataset);
+ const store = createStore(initState);
const router = createRouter(metricsDashboardBasePath);
// eslint-disable-next-line no-new
diff --git a/app/assets/javascripts/monitoring/pages/dashboard_page.vue b/app/assets/javascripts/monitoring/pages/dashboard_page.vue
index 519a20d7be3..df0e2d7f8f6 100644
--- a/app/assets/javascripts/monitoring/pages/dashboard_page.vue
+++ b/app/assets/javascripts/monitoring/pages/dashboard_page.vue
@@ -1,4 +1,5 @@
<script>
+import { mapActions } from 'vuex';
import Dashboard from '../components/dashboard.vue';
export default {
@@ -11,6 +12,16 @@ export default {
required: true,
},
},
+ created() {
+ // This is to support the older URL <project>/-/environments/:env_id/metrics?dashboard=:path
+ // and the new format <project>/-/metrics/:dashboardPath
+ const encodedDashboard = this.$route.query.dashboard || this.$route.params.dashboard;
+ const currentDashboard = encodedDashboard ? decodeURIComponent(encodedDashboard) : null;
+ this.setCurrentDashboard({ currentDashboard });
+ },
+ methods: {
+ ...mapActions('monitoringDashboard', ['setCurrentDashboard']),
+ },
};
</script>
<template>
diff --git a/app/assets/javascripts/monitoring/queries/getDashboardValidationWarnings.query.graphql b/app/assets/javascripts/monitoring/queries/getDashboardValidationWarnings.query.graphql
new file mode 100644
index 00000000000..302383512d3
--- /dev/null
+++ b/app/assets/javascripts/monitoring/queries/getDashboardValidationWarnings.query.graphql
@@ -0,0 +1,18 @@
+query getDashboardValidationWarnings(
+ $projectPath: ID!
+ $environmentName: String
+ $dashboardPath: String!
+) {
+ project(fullPath: $projectPath) {
+ id
+ environments(name: $environmentName) {
+ nodes {
+ name
+ metricsDashboard(path: $dashboardPath) {
+ path
+ schemaValidationWarnings
+ }
+ }
+ }
+ }
+}
diff --git a/app/assets/javascripts/monitoring/router/constants.js b/app/assets/javascripts/monitoring/router/constants.js
index acfcd03f928..fedfebe33e9 100644
--- a/app/assets/javascripts/monitoring/router/constants.js
+++ b/app/assets/javascripts/monitoring/router/constants.js
@@ -1,3 +1,4 @@
export const BASE_DASHBOARD_PAGE = 'dashboard';
+export const CUSTOM_DASHBOARD_PAGE = 'custom_dashboard';
export default {};
diff --git a/app/assets/javascripts/monitoring/router/routes.js b/app/assets/javascripts/monitoring/router/routes.js
index 1e0cc1715a7..4b82791178a 100644
--- a/app/assets/javascripts/monitoring/router/routes.js
+++ b/app/assets/javascripts/monitoring/router/routes.js
@@ -1,6 +1,6 @@
import DashboardPage from '../pages/dashboard_page.vue';
-import { BASE_DASHBOARD_PAGE } from './constants';
+import { BASE_DASHBOARD_PAGE, CUSTOM_DASHBOARD_PAGE } from './constants';
/**
* Because the cluster health page uses the dashboard
@@ -12,7 +12,12 @@ import { BASE_DASHBOARD_PAGE } from './constants';
export default [
{
name: BASE_DASHBOARD_PAGE,
- path: '*',
+ path: '/',
+ component: DashboardPage,
+ },
+ {
+ name: CUSTOM_DASHBOARD_PAGE,
+ path: '/:dashboard(.*)',
component: DashboardPage,
},
];
diff --git a/app/assets/javascripts/monitoring/stores/actions.js b/app/assets/javascripts/monitoring/stores/actions.js
index 3a9cccec438..a441882a47d 100644
--- a/app/assets/javascripts/monitoring/stores/actions.js
+++ b/app/assets/javascripts/monitoring/stores/actions.js
@@ -12,6 +12,7 @@ import {
import trackDashboardLoad from '../monitoring_tracking_helper';
import getEnvironments from '../queries/getEnvironments.query.graphql';
import getAnnotations from '../queries/getAnnotations.query.graphql';
+import getDashboardValidationWarnings from '../queries/getDashboardValidationWarnings.query.graphql';
import statusCodes from '../../lib/utils/http_status';
import { backOff, convertObjectPropsToCamelCase } from '../../lib/utils/common_utils';
import { s__, sprintf } from '../../locale';
@@ -20,6 +21,7 @@ import {
PROMETHEUS_TIMEOUT,
ENVIRONMENT_AVAILABLE_STATE,
DEFAULT_DASHBOARD_PATH,
+ VARIABLE_TYPES,
} from '../constants';
function prometheusMetricQueryParams(timeRange) {
@@ -50,15 +52,14 @@ function backOffRequest(makeRequestCallback) {
}, PROMETHEUS_TIMEOUT);
}
-function getPrometheusMetricResult(prometheusEndpoint, params) {
+function getPrometheusQueryData(prometheusEndpoint, params) {
return backOffRequest(() => axios.get(prometheusEndpoint, { params }))
.then(res => res.data)
.then(response => {
if (response.status === 'error') {
throw new Error(response.error);
}
-
- return response.data.result;
+ return response.data;
});
}
@@ -76,10 +77,6 @@ export const setTimeRange = ({ commit }, timeRange) => {
commit(types.SET_TIME_RANGE, timeRange);
};
-export const setVariables = ({ commit }, variables) => {
- commit(types.SET_VARIABLES, variables);
-};
-
export const filterEnvironments = ({ commit, dispatch }, searchTerm) => {
commit(types.SET_ENVIRONMENTS_FILTER, searchTerm);
dispatch('fetchEnvironmentsData');
@@ -100,6 +97,10 @@ export const clearExpandedPanel = ({ commit }) => {
});
};
+export const setCurrentDashboard = ({ commit }, { currentDashboard }) => {
+ commit(types.SET_CURRENT_DASHBOARD, currentDashboard);
+};
+
// All Data
/**
@@ -117,17 +118,27 @@ export const fetchData = ({ dispatch }) => {
// Metrics dashboard
-export const fetchDashboard = ({ state, commit, dispatch }) => {
+export const fetchDashboard = ({ state, commit, dispatch, getters }) => {
dispatch('requestMetricsDashboard');
const params = {};
- if (state.currentDashboard) {
- params.dashboard = state.currentDashboard;
+ if (getters.fullDashboardPath) {
+ params.dashboard = getters.fullDashboardPath;
}
return backOffRequest(() => axios.get(state.dashboardEndpoint, { params }))
.then(resp => resp.data)
- .then(response => dispatch('receiveMetricsDashboardSuccess', { response }))
+ .then(response => {
+ dispatch('receiveMetricsDashboardSuccess', { response });
+ /**
+ * After the dashboard is fetched, there can be non-blocking invalid syntax
+ * in the dashboard file. This call will fetch such syntax warnings
+ * and surface a warning on the UI. If the invalid syntax is blocking,
+ * the `fetchDashboard` returns a 404 with error messages that are displayed
+ * on the UI.
+ */
+ dispatch('fetchDashboardValidationWarnings');
+ })
.catch(error => {
Sentry.captureException(error);
@@ -181,8 +192,12 @@ export const fetchDashboardData = ({ state, dispatch, getters }) => {
return Promise.reject();
}
+ // Time range params must be pre-calculated once for all metrics and options
+ // A subsequent call, may calculate a different time range
const defaultQueryParams = prometheusMetricQueryParams(state.timeRange);
+ dispatch('fetchVariableMetricLabelValues', { defaultQueryParams });
+
const promises = [];
state.dashboard.panelGroups.forEach(group => {
group.panels.forEach(panel => {
@@ -194,7 +209,7 @@ export const fetchDashboardData = ({ state, dispatch, getters }) => {
return Promise.all(promises)
.then(() => {
- const dashboardType = state.currentDashboard === '' ? 'default' : 'custom';
+ const dashboardType = getters.fullDashboardPath === '' ? 'default' : 'custom';
trackDashboardLoad({
label: `${dashboardType}_metrics_dashboard`,
value: getters.metricsWithData().length,
@@ -220,7 +235,7 @@ export const fetchPrometheusMetric = (
queryParams.step = metric.step;
}
- if (Object.keys(state.variables).length > 0) {
+ if (state.variables.length > 0) {
queryParams = {
...queryParams,
...getters.getCustomVariablesParams,
@@ -229,9 +244,9 @@ export const fetchPrometheusMetric = (
commit(types.REQUEST_METRIC_RESULT, { metricId: metric.metricId });
- return getPrometheusMetricResult(metric.prometheusEndpointPath, queryParams)
- .then(result => {
- commit(types.RECEIVE_METRIC_RESULT_SUCCESS, { metricId: metric.metricId, result });
+ return getPrometheusQueryData(metric.prometheusEndpointPath, queryParams)
+ .then(data => {
+ commit(types.RECEIVE_METRIC_RESULT_SUCCESS, { metricId: metric.metricId, data });
})
.catch(error => {
Sentry.captureException(error);
@@ -312,9 +327,9 @@ export const receiveEnvironmentsDataFailure = ({ commit }) => {
commit(types.RECEIVE_ENVIRONMENTS_DATA_FAILURE);
};
-export const fetchAnnotations = ({ state, dispatch }) => {
+export const fetchAnnotations = ({ state, dispatch, getters }) => {
const { start } = convertToFixedRange(state.timeRange);
- const dashboardPath = state.currentDashboard || DEFAULT_DASHBOARD_PATH;
+ const dashboardPath = getters.fullDashboardPath || DEFAULT_DASHBOARD_PATH;
return gqClient
.mutate({
mutation: getAnnotations,
@@ -345,6 +360,46 @@ export const receiveAnnotationsSuccess = ({ commit }, data) =>
commit(types.RECEIVE_ANNOTATIONS_SUCCESS, data);
export const receiveAnnotationsFailure = ({ commit }) => commit(types.RECEIVE_ANNOTATIONS_FAILURE);
+export const fetchDashboardValidationWarnings = ({ state, dispatch, getters }) => {
+ /**
+ * Normally, the default dashboard won't throw any validation warnings.
+ *
+ * However, if a bug sneaks into the default dashboard making it invalid,
+ * this might come handy for our clients
+ */
+ const dashboardPath = getters.fullDashboardPath || DEFAULT_DASHBOARD_PATH;
+ return gqClient
+ .mutate({
+ mutation: getDashboardValidationWarnings,
+ variables: {
+ projectPath: removeLeadingSlash(state.projectPath),
+ environmentName: state.currentEnvironmentName,
+ dashboardPath,
+ },
+ })
+ .then(resp => resp.data?.project?.environments?.nodes?.[0]?.metricsDashboard)
+ .then(({ schemaValidationWarnings } = {}) => {
+ const hasWarnings = schemaValidationWarnings && schemaValidationWarnings.length !== 0;
+ /**
+ * The payload of the dispatch is a boolean, because at the moment a standard
+ * warning message is shown instead of the warnings the BE returns
+ */
+ dispatch('receiveDashboardValidationWarningsSuccess', hasWarnings || false);
+ })
+ .catch(err => {
+ Sentry.captureException(err);
+ dispatch('receiveDashboardValidationWarningsFailure');
+ createFlash(
+ s__('Metrics|There was an error getting dashboard validation warnings information.'),
+ );
+ });
+};
+
+export const receiveDashboardValidationWarningsSuccess = ({ commit }, hasWarnings) =>
+ commit(types.RECEIVE_DASHBOARD_VALIDATION_WARNINGS_SUCCESS, hasWarnings);
+export const receiveDashboardValidationWarningsFailure = ({ commit }) =>
+ commit(types.RECEIVE_DASHBOARD_VALIDATION_WARNINGS_FAILURE);
+
// Dashboard manipulation
export const toggleStarredValue = ({ commit, state, getters }) => {
@@ -416,10 +471,41 @@ export const duplicateSystemDashboard = ({ state }, payload) => {
// Variables manipulation
export const updateVariablesAndFetchData = ({ commit, dispatch }, updatedVariable) => {
- commit(types.UPDATE_VARIABLES, updatedVariable);
+ commit(types.UPDATE_VARIABLE_VALUE, updatedVariable);
return dispatch('fetchDashboardData');
};
+export const fetchVariableMetricLabelValues = ({ state, commit }, { defaultQueryParams }) => {
+ const { start_time, end_time } = defaultQueryParams;
+ const optionsRequests = [];
+
+ state.variables.forEach(variable => {
+ if (variable.type === VARIABLE_TYPES.metric_label_values) {
+ const { prometheusEndpointPath, label } = variable.options;
+
+ const optionsRequest = backOffRequest(() =>
+ axios.get(prometheusEndpointPath, {
+ params: { start_time, end_time },
+ }),
+ )
+ .then(({ data }) => data.data)
+ .then(data => {
+ commit(types.UPDATE_VARIABLE_METRIC_LABEL_VALUES, { variable, label, data });
+ })
+ .catch(() => {
+ createFlash(
+ sprintf(s__('Metrics|There was an error getting options for variable "%{name}".'), {
+ name: variable.name,
+ }),
+ );
+ });
+ optionsRequests.push(optionsRequest);
+ }
+ });
+
+ return Promise.all(optionsRequests);
+};
+
// prevent babel-plugin-rewire from generating an invalid default during karma tests
export default () => {};
diff --git a/app/assets/javascripts/monitoring/stores/getters.js b/app/assets/javascripts/monitoring/stores/getters.js
index b7681012472..3aa711a0509 100644
--- a/app/assets/javascripts/monitoring/stores/getters.js
+++ b/app/assets/javascripts/monitoring/stores/getters.js
@@ -1,5 +1,9 @@
import { NOT_IN_DB_PREFIX } from '../constants';
-import { addPrefixToCustomVariableParams, addDashboardMetaDataToLink } from './utils';
+import {
+ addPrefixToCustomVariableParams,
+ addDashboardMetaDataToLink,
+ normalizeCustomDashboardPath,
+} from './utils';
const metricsIdsInPanel = panel =>
panel.metrics.filter(metric => metric.metricId && metric.result).map(metric => metric.metricId);
@@ -10,10 +14,10 @@ const metricsIdsInPanel = panel =>
*
* @param {Object} state
*/
-export const selectedDashboard = state => {
+export const selectedDashboard = (state, getters) => {
const { allDashboards } = state;
return (
- allDashboards.find(d => d.path === state.currentDashboard) ||
+ allDashboards.find(d => d.path === getters.fullDashboardPath) ||
allDashboards.find(d => d.default) ||
null
);
@@ -129,8 +133,8 @@ export const linksWithMetadata = state => {
};
/**
- * Maps an variables object to an array along with stripping
- * the variable prefix.
+ * Maps a variables array to an object for replacement in
+ * prometheus queries.
*
* This method outputs an object in the below format
*
@@ -143,16 +147,29 @@ export const linksWithMetadata = state => {
* user-defined variables coming through the URL and differentiate
* from other variables used for Prometheus API endpoint.
*
- * @param {Object} variables - Custom variables provided by the user
- * @returns {Array} The custom variables array to be send to the API
+ * @param {Object} state - State containing variables provided by the user
+ * @returns {Array} The custom variables object to be send to the API
* in the format of {variables[key1]=value1, variables[key2]=value2}
*/
export const getCustomVariablesParams = state =>
- Object.keys(state.variables).reduce((acc, variable) => {
- acc[addPrefixToCustomVariableParams(variable)] = state.variables[variable]?.value;
+ state.variables.reduce((acc, variable) => {
+ const { name, value } = variable;
+ if (value !== null) {
+ acc[addPrefixToCustomVariableParams(name)] = value;
+ }
return acc;
}, {});
+/**
+ * For a given custom dashboard file name, this method
+ * returns the full file path.
+ *
+ * @param {Object} state
+ * @returns {String} full dashboard path
+ */
+export const fullDashboardPath = state =>
+ normalizeCustomDashboardPath(state.currentDashboard, state.customDashboardBasePath);
+
// prevent babel-plugin-rewire from generating an invalid default during karma tests
export default () => {};
diff --git a/app/assets/javascripts/monitoring/stores/mutation_types.js b/app/assets/javascripts/monitoring/stores/mutation_types.js
index 4593461776b..d408628fc4d 100644
--- a/app/assets/javascripts/monitoring/stores/mutation_types.js
+++ b/app/assets/javascripts/monitoring/stores/mutation_types.js
@@ -2,17 +2,25 @@
export const REQUEST_METRICS_DASHBOARD = 'REQUEST_METRICS_DASHBOARD';
export const RECEIVE_METRICS_DASHBOARD_SUCCESS = 'RECEIVE_METRICS_DASHBOARD_SUCCESS';
export const RECEIVE_METRICS_DASHBOARD_FAILURE = 'RECEIVE_METRICS_DASHBOARD_FAILURE';
-export const SET_VARIABLES = 'SET_VARIABLES';
-export const UPDATE_VARIABLES = 'UPDATE_VARIABLES';
+export const UPDATE_VARIABLE_VALUE = 'UPDATE_VARIABLE_VALUE';
+export const UPDATE_VARIABLE_METRIC_LABEL_VALUES = 'UPDATE_VARIABLE_METRIC_LABEL_VALUES';
export const REQUEST_DASHBOARD_STARRING = 'REQUEST_DASHBOARD_STARRING';
export const RECEIVE_DASHBOARD_STARRING_SUCCESS = 'RECEIVE_DASHBOARD_STARRING_SUCCESS';
export const RECEIVE_DASHBOARD_STARRING_FAILURE = 'RECEIVE_DASHBOARD_STARRING_FAILURE';
+export const SET_CURRENT_DASHBOARD = 'SET_CURRENT_DASHBOARD';
+
// Annotations
export const RECEIVE_ANNOTATIONS_SUCCESS = 'RECEIVE_ANNOTATIONS_SUCCESS';
export const RECEIVE_ANNOTATIONS_FAILURE = 'RECEIVE_ANNOTATIONS_FAILURE';
+// Dashboard validation warnings
+export const RECEIVE_DASHBOARD_VALIDATION_WARNINGS_SUCCESS =
+ 'RECEIVE_DASHBOARD_VALIDATION_WARNINGS_SUCCESS';
+export const RECEIVE_DASHBOARD_VALIDATION_WARNINGS_FAILURE =
+ 'RECEIVE_DASHBOARD_VALIDATION_WARNINGS_FAILURE';
+
// Git project deployments
export const REQUEST_DEPLOYMENTS_DATA = 'REQUEST_DEPLOYMENTS_DATA';
export const RECEIVE_DEPLOYMENTS_DATA_SUCCESS = 'RECEIVE_DEPLOYMENTS_DATA_SUCCESS';
@@ -34,7 +42,6 @@ export const SET_ALL_DASHBOARDS = 'SET_ALL_DASHBOARDS';
export const SET_ENDPOINTS = 'SET_ENDPOINTS';
export const SET_INITIAL_STATE = 'SET_INITIAL_STATE';
export const SET_GETTING_STARTED_EMPTY_STATE = 'SET_GETTING_STARTED_EMPTY_STATE';
-export const SET_NO_DATA_EMPTY_STATE = 'SET_NO_DATA_EMPTY_STATE';
export const SET_SHOW_ERROR_BANNER = 'SET_SHOW_ERROR_BANNER';
export const SET_PANEL_GROUP_METRICS = 'SET_PANEL_GROUP_METRICS';
export const SET_ENVIRONMENTS_FILTER = 'SET_ENVIRONMENTS_FILTER';
diff --git a/app/assets/javascripts/monitoring/stores/mutations.js b/app/assets/javascripts/monitoring/stores/mutations.js
index 2d63fdd6e34..744441c8935 100644
--- a/app/assets/javascripts/monitoring/stores/mutations.js
+++ b/app/assets/javascripts/monitoring/stores/mutations.js
@@ -1,10 +1,11 @@
import Vue from 'vue';
import { pick } from 'lodash';
import * as types from './mutation_types';
-import { mapToDashboardViewModel, normalizeQueryResult } from './utils';
-import { BACKOFF_TIMEOUT } from '../../lib/utils/common_utils';
-import { endpointKeys, initialStateKeys, metricStates } from '../constants';
+import { mapToDashboardViewModel, normalizeQueryResponseData } from './utils';
import httpStatusCodes from '~/lib/utils/http_status';
+import { BACKOFF_TIMEOUT } from '../../lib/utils/common_utils';
+import { dashboardEmptyStates, endpointKeys, initialStateKeys, metricStates } from '../constants';
+import { optionsFromSeriesData } from './variable_mapping';
/**
* Locate and return a metric in the dashboard by its id
@@ -57,8 +58,7 @@ export default {
* Dashboard panels structure and global state
*/
[types.REQUEST_METRICS_DASHBOARD](state) {
- state.emptyState = 'loading';
- state.showEmptyState = true;
+ state.emptyState = dashboardEmptyStates.LOADING;
},
[types.RECEIVE_METRICS_DASHBOARD_SUCCESS](state, dashboardYML) {
const { dashboard, panelGroups, variables, links } = mapToDashboardViewModel(dashboardYML);
@@ -70,12 +70,15 @@ export default {
state.links = links;
if (!state.dashboard.panelGroups.length) {
- state.emptyState = 'noData';
+ state.emptyState = dashboardEmptyStates.NO_DATA;
+ } else {
+ state.emptyState = null;
}
},
[types.RECEIVE_METRICS_DASHBOARD_FAILURE](state, error) {
- state.emptyState = error ? 'unableToConnect' : 'noData';
- state.showEmptyState = true;
+ state.emptyState = error
+ ? dashboardEmptyStates.UNABLE_TO_CONNECT
+ : dashboardEmptyStates.NO_DATA;
},
[types.REQUEST_DASHBOARD_STARRING](state) {
@@ -94,6 +97,10 @@ export default {
state.isUpdatingStarredValue = false;
},
+ [types.SET_CURRENT_DASHBOARD](state, currentDashboard) {
+ state.currentDashboard = currentDashboard;
+ },
+
/**
* Deployments and environments
*/
@@ -126,6 +133,16 @@ export default {
},
/**
+ * Dashboard Validation Warnings
+ */
+ [types.RECEIVE_DASHBOARD_VALIDATION_WARNINGS_SUCCESS](state, hasDashboardValidationWarnings) {
+ state.hasDashboardValidationWarnings = hasDashboardValidationWarnings;
+ },
+ [types.RECEIVE_DASHBOARD_VALIDATION_WARNINGS_FAILURE](state) {
+ state.hasDashboardValidationWarnings = false;
+ },
+
+ /**
* Individual panel/metric results
*/
[types.REQUEST_METRIC_RESULT](state, { metricId }) {
@@ -135,19 +152,18 @@ export default {
metric.state = metricStates.LOADING;
}
},
- [types.RECEIVE_METRIC_RESULT_SUCCESS](state, { metricId, result }) {
+ [types.RECEIVE_METRIC_RESULT_SUCCESS](state, { metricId, data }) {
const metric = findMetricInDashboard(metricId, state.dashboard);
metric.loading = false;
- state.showEmptyState = false;
- if (!result || result.length === 0) {
+ if (!data.result || data.result.length === 0) {
metric.state = metricStates.NO_DATA;
metric.result = null;
} else {
- const normalizedResults = result.map(normalizeQueryResult);
+ const result = normalizeQueryResponseData(data);
metric.state = metricStates.OK;
- metric.result = Object.freeze(normalizedResults);
+ metric.result = Object.freeze(result);
}
},
[types.RECEIVE_METRIC_RESULT_FAILURE](state, { metricId, error }) {
@@ -169,11 +185,7 @@ export default {
state.timeRange = timeRange;
},
[types.SET_GETTING_STARTED_EMPTY_STATE](state) {
- state.emptyState = 'gettingStarted';
- },
- [types.SET_NO_DATA_EMPTY_STATE](state) {
- state.showEmptyState = true;
- state.emptyState = 'noData';
+ state.emptyState = dashboardEmptyStates.GETTING_STARTED;
},
[types.SET_ALL_DASHBOARDS](state, dashboards) {
state.allDashboards = dashboards || [];
@@ -192,13 +204,18 @@ export default {
state.expandedPanel.group = group;
state.expandedPanel.panel = panel;
},
- [types.SET_VARIABLES](state, variables) {
- state.variables = variables;
+ [types.UPDATE_VARIABLE_VALUE](state, { name, value }) {
+ const variable = state.variables.find(v => v.name === name);
+ if (variable) {
+ Object.assign(variable, {
+ value,
+ });
+ }
},
- [types.UPDATE_VARIABLES](state, updatedVariable) {
- Object.assign(state.variables[updatedVariable.key], {
- ...state.variables[updatedVariable.key],
- value: updatedVariable.value,
- });
+ [types.UPDATE_VARIABLE_METRIC_LABEL_VALUES](state, { variable, label, data = [] }) {
+ const values = optionsFromSeriesData({ label, data });
+
+ // Add new options with assign to ensure Vue reactivity
+ Object.assign(variable.options, { values });
},
};
diff --git a/app/assets/javascripts/monitoring/stores/state.js b/app/assets/javascripts/monitoring/stores/state.js
index 8000f27c0d5..89738756ffe 100644
--- a/app/assets/javascripts/monitoring/stores/state.js
+++ b/app/assets/javascripts/monitoring/stores/state.js
@@ -1,5 +1,6 @@
import invalidUrl from '~/lib/utils/invalid_url';
import { timezones } from '../format_date';
+import { dashboardEmptyStates } from '../constants';
export default () => ({
// API endpoints
@@ -9,11 +10,24 @@ export default () => ({
// Dashboard request parameters
timeRange: null,
+ /**
+ * Currently selected dashboard. For custom dashboards,
+ * this could be the filename or the file path.
+ *
+ * If this is the filename and full path is required,
+ * getters.fullDashboardPath should be used.
+ */
currentDashboard: null,
// Dashboard data
- emptyState: 'gettingStarted',
- showEmptyState: true,
+ hasDashboardValidationWarnings: false,
+
+ /**
+ * {?String} If set, dashboard should display a global
+ * empty state, there is no way to interact (yet)
+ * with the dashboard.
+ */
+ emptyState: dashboardEmptyStates.GETTING_STARTED,
showErrorBanner: true,
isUpdatingStarredValue: false,
dashboard: {
@@ -39,7 +53,7 @@ export default () => ({
* User-defined custom variables are passed
* via the dashboard yml file.
*/
- variables: {},
+ variables: [],
/**
* User-defined custom links are passed
* via the dashboard yml file.
@@ -56,5 +70,16 @@ export default () => ({
// GitLab paths to other pages
projectPath: null,
+ operationsSettingsPath: '',
logsPath: invalidUrl,
+
+ // static paths
+ customDashboardBasePath: '',
+
+ // current user data
+ /**
+ * Flag that denotes if the currently logged user can access
+ * the project Settings -> Operations
+ */
+ canAccessOperationsSettings: false,
});
diff --git a/app/assets/javascripts/monitoring/stores/utils.js b/app/assets/javascripts/monitoring/stores/utils.js
index 5795e756282..51562593ee8 100644
--- a/app/assets/javascripts/monitoring/stores/utils.js
+++ b/app/assets/javascripts/monitoring/stores/utils.js
@@ -2,11 +2,11 @@ import { slugify } from '~/lib/utils/text_utility';
import createGqClient, { fetchPolicies } from '~/lib/graphql';
import { SUPPORTED_FORMATS } from '~/lib/utils/unit_format';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
-import { NOT_IN_DB_PREFIX, linkTypes } from '../constants';
import { mergeURLVariables, parseTemplatingVariables } from './variable_mapping';
import { DATETIME_RANGE_TYPES } from '~/lib/utils/constants';
import { timeRangeToParams, getRangeType } from '~/lib/utils/datetime_range';
import { isSafeURL, mergeUrlParams } from '~/lib/utils/url_utility';
+import { NOT_IN_DB_PREFIX, linkTypes, OUT_OF_THE_BOX_DASHBOARDS_PATH_PREFIX } from '../constants';
export const gqClient = createGqClient(
{},
@@ -165,7 +165,7 @@ const mapLinksToViewModel = ({ url = null, title = '', type } = {}) => {
* @param {Object} panel - Metrics panel
* @returns {Object}
*/
-const mapPanelToViewModel = ({
+export const mapPanelToViewModel = ({
id = null,
title = '',
type,
@@ -173,6 +173,7 @@ const mapPanelToViewModel = ({
x_label,
y_label,
y_axis = {},
+ field,
metrics = [],
links = [],
max_value,
@@ -193,6 +194,7 @@ const mapPanelToViewModel = ({
y_label: yAxis.name, // Changing y_label to yLabel is pending https://gitlab.com/gitlab-org/gitlab/issues/207198
yAxis,
xAxis,
+ field,
maxValue: max_value,
links: links.map(mapLinksToViewModel),
metrics: mapToMetricsViewModel(metrics),
@@ -289,49 +291,157 @@ export const mapToDashboardViewModel = ({
}) => {
return {
dashboard,
- variables: mergeURLVariables(parseTemplatingVariables(templating)),
+ variables: mergeURLVariables(parseTemplatingVariables(templating.variables)),
links: links.map(mapLinksToViewModel),
panelGroups: panel_groups.map(mapToPanelGroupViewModel),
};
};
+// Prometheus Results Parsing
+
+const dateTimeFromUnixTime = unixTime => new Date(unixTime * 1000).toISOString();
+
+const mapScalarValue = ([unixTime, value]) => [dateTimeFromUnixTime(unixTime), Number(value)];
+
+// Note: `string` value type is unused as of prometheus 2.19.
+const mapStringValue = ([unixTime, value]) => [dateTimeFromUnixTime(unixTime), value];
+
+/**
+ * Processes a scalar result.
+ *
+ * The corresponding result property has the following format:
+ *
+ * [ <unix_time>, "<scalar_value>" ]
+ *
+ * @param {array} result
+ * @returns {array}
+ */
+const normalizeScalarResult = result => [
+ {
+ metric: {},
+ value: mapScalarValue(result),
+ values: [mapScalarValue(result)],
+ },
+];
+
+/**
+ * Processes a string result.
+ *
+ * The corresponding result property has the following format:
+ *
+ * [ <unix_time>, "<string_value>" ]
+ *
+ * Note: This value type is unused as of prometheus 2.19.
+ *
+ * @param {array} result
+ * @returns {array}
+ */
+const normalizeStringResult = result => [
+ {
+ metric: {},
+ value: mapStringValue(result),
+ values: [mapStringValue(result)],
+ },
+];
+
+/**
+ * Proccesses an instant vector.
+ *
+ * Instant vectors are returned as result type `vector`.
+ *
+ * The corresponding result property has the following format:
+ *
+ * [
+ * {
+ * "metric": { "<label_name>": "<label_value>", ... },
+ * "value": [ <unix_time>, "<sample_value>" ],
+ * "values": [ [ <unix_time>, "<sample_value>" ] ]
+ * },
+ * ...
+ * ]
+ *
+ * `metric` - Key-value pairs object representing metric measured
+ * `value` - The vector result
+ * `values` - An array with a single value representing the result
+ *
+ * This method also adds the matrix version of the vector
+ * by introducing a `values` array with a single element. This
+ * allows charts to default to `values` if needed.
+ *
+ * @param {array} result
+ * @returns {array}
+ */
+const normalizeVectorResult = result =>
+ result.map(({ metric, value }) => {
+ const scalar = mapScalarValue(value);
+ // Add a single element to `values`, to support matrix
+ // style charts.
+ return { metric, value: scalar, values: [scalar] };
+ });
+
/**
- * Processes a single Range vector, part of the result
- * of type `matrix` in the form:
+ * Range vectors are returned as result type matrix.
+ *
+ * The corresponding result property has the following format:
*
* {
* "metric": { "<label_name>": "<label_value>", ... },
+ * "value": [ <unix_time>, "<sample_value>" ],
* "values": [ [ <unix_time>, "<sample_value>" ], ... ]
* },
*
+ * `metric` - Key-value pairs object representing metric measured
+ * `value` - The last (more recent) result
+ * `values` - A range of results for the metric
+ *
* See https://prometheus.io/docs/prometheus/latest/querying/api/#range-vectors
*
- * @param {*} timeSeries
+ * @param {array} result
+ * @returns {object} Normalized result.
*/
-export const normalizeQueryResult = timeSeries => {
- let normalizedResult = {};
-
- if (timeSeries.values) {
- normalizedResult = {
- ...timeSeries,
- values: timeSeries.values.map(([timestamp, value]) => [
- new Date(timestamp * 1000).toISOString(),
- Number(value),
- ]),
- };
- // Check result for empty data
- normalizedResult.values = normalizedResult.values.filter(series => {
- const hasValue = d => !Number.isNaN(d[1]) && (d[1] !== null || d[1] !== undefined);
- return series.find(hasValue);
- });
- } else if (timeSeries.value) {
- normalizedResult = {
- ...timeSeries,
- value: [new Date(timeSeries.value[0] * 1000).toISOString(), Number(timeSeries.value[1])],
+const normalizeResultMatrix = result =>
+ result.map(({ metric, values }) => {
+ const mappedValues = values.map(mapScalarValue);
+ return {
+ metric,
+ value: mappedValues[mappedValues.length - 1],
+ values: mappedValues,
};
- }
+ });
- return normalizedResult;
+/**
+ * Parse response data from a Prometheus Query that comes
+ * in the format:
+ *
+ * {
+ * "resultType": "matrix" | "vector" | "scalar" | "string",
+ * "result": <value>
+ * }
+ *
+ * @see https://prometheus.io/docs/prometheus/latest/querying/api/#expression-query-result-formats
+ *
+ * @param {object} data - Data containing results and result type.
+ * @returns {object} - A result array of metric results:
+ * [
+ * {
+ * metric: { ... },
+ * value: ['2015-07-01T20:10:51.781Z', '1'],
+ * values: [['2015-07-01T20:10:51.781Z', '1'] , ... ],
+ * },
+ * ...
+ * ]
+ *
+ */
+export const normalizeQueryResponseData = data => {
+ const { resultType, result } = data;
+ if (resultType === 'vector') {
+ return normalizeVectorResult(result);
+ } else if (resultType === 'scalar') {
+ return normalizeScalarResult(result);
+ } else if (resultType === 'string') {
+ return normalizeStringResult(result);
+ }
+ return normalizeResultMatrix(result);
};
/**
@@ -345,7 +455,35 @@ export const normalizeQueryResult = timeSeries => {
*
* This is currently only used by getters/getCustomVariablesParams
*
- * @param {String} key Variable key that needs to be prefixed
+ * @param {String} name Variable key that needs to be prefixed
* @returns {String}
*/
-export const addPrefixToCustomVariableParams = key => `variables[${key}]`;
+export const addPrefixToCustomVariableParams = name => `variables[${name}]`;
+
+/**
+ * Normalize custom dashboard paths. This method helps support
+ * metrics dashboard to work with custom dashboard file names instead
+ * of the entire path.
+ *
+ * If dashboard is empty, it is the default dashboard.
+ * If dashboard is set, it usually is a custom dashboard unless
+ * explicitly it is set to default dashboard path.
+ *
+ * @param {String} dashboard dashboard path
+ * @param {String} dashboardPrefix custom dashboard directory prefix
+ * @returns {String} normalized dashboard path
+ */
+export const normalizeCustomDashboardPath = (dashboard, dashboardPrefix = '') => {
+ const currDashboard = dashboard || '';
+ let dashboardPath = `${dashboardPrefix}/${currDashboard}`;
+
+ if (!currDashboard) {
+ dashboardPath = '';
+ } else if (
+ currDashboard.startsWith(dashboardPrefix) ||
+ currDashboard.startsWith(OUT_OF_THE_BOX_DASHBOARDS_PATH_PREFIX)
+ ) {
+ dashboardPath = currDashboard;
+ }
+ return dashboardPath;
+};
diff --git a/app/assets/javascripts/monitoring/stores/variable_mapping.js b/app/assets/javascripts/monitoring/stores/variable_mapping.js
index c0a8150063b..9245ffdb3b9 100644
--- a/app/assets/javascripts/monitoring/stores/variable_mapping.js
+++ b/app/assets/javascripts/monitoring/stores/variable_mapping.js
@@ -46,7 +46,7 @@ const textAdvancedVariableParser = advTextVar => ({
* @param {Object} custom variable option
* @returns {Object} normalized custom variable options
*/
-const normalizeCustomVariableOptions = ({ default: defaultOpt = false, text, value }) => ({
+const normalizeVariableValues = ({ default: defaultOpt = false, text, value = null }) => ({
default: defaultOpt,
text: text || value,
value,
@@ -59,17 +59,19 @@ const normalizeCustomVariableOptions = ({ default: defaultOpt = false, text, val
* The default value is the option with default set to true or the first option
* if none of the options have default prop true.
*
- * @param {Object} advVariable advance custom variable
+ * @param {Object} advVariable advanced custom variable
* @returns {Object}
*/
const customAdvancedVariableParser = advVariable => {
- const options = (advVariable?.options?.values ?? []).map(normalizeCustomVariableOptions);
- const defaultOpt = options.find(opt => opt.default === true) || options[0];
+ const values = (advVariable?.options?.values ?? []).map(normalizeVariableValues);
+ const defaultValue = values.find(opt => opt.default === true) || values[0];
return {
type: VARIABLE_TYPES.custom,
label: advVariable.label,
- value: defaultOpt?.value,
- options,
+ options: {
+ values,
+ },
+ value: defaultValue?.value || null,
};
};
@@ -80,7 +82,7 @@ const customAdvancedVariableParser = advVariable => {
* @param {String} opt option from simple custom variable
* @returns {Object}
*/
-const parseSimpleCustomOptions = opt => ({ text: opt, value: opt });
+export const parseSimpleCustomValues = opt => ({ text: opt, value: opt });
/**
* Custom simple variables are rendered as dropdown elements in the dashboard
@@ -95,15 +97,28 @@ const parseSimpleCustomOptions = opt => ({ text: opt, value: opt });
* @returns {Object}
*/
const customSimpleVariableParser = simpleVar => {
- const options = (simpleVar || []).map(parseSimpleCustomOptions);
+ const values = (simpleVar || []).map(parseSimpleCustomValues);
return {
type: VARIABLE_TYPES.custom,
- value: options[0].value,
label: null,
- options: options.map(normalizeCustomVariableOptions),
+ value: values[0].value || null,
+ options: {
+ values: values.map(normalizeVariableValues),
+ },
};
};
+const metricLabelValuesVariableParser = ({ label, options = {} }) => ({
+ type: VARIABLE_TYPES.metric_label_values,
+ label,
+ value: null,
+ options: {
+ prometheusEndpointPath: options.prometheus_endpoint_path || '',
+ label: options.label || null,
+ values: [], // values are initially empty
+ },
+});
+
/**
* Utility method to determine if a custom variable is
* simple or not. If its not simple, it is advanced.
@@ -123,14 +138,16 @@ const isSimpleCustomVariable = customVar => Array.isArray(customVar);
* @return {Function} parser method
*/
const getVariableParser = variable => {
- if (isSimpleCustomVariable(variable)) {
+ if (isString(variable)) {
+ return textSimpleVariableParser;
+ } else if (isSimpleCustomVariable(variable)) {
return customSimpleVariableParser;
- } else if (variable.type === VARIABLE_TYPES.custom) {
- return customAdvancedVariableParser;
} else if (variable.type === VARIABLE_TYPES.text) {
return textAdvancedVariableParser;
- } else if (isString(variable)) {
- return textSimpleVariableParser;
+ } else if (variable.type === VARIABLE_TYPES.custom) {
+ return customAdvancedVariableParser;
+ } else if (variable.type === VARIABLE_TYPES.metric_label_values) {
+ return metricLabelValuesVariableParser;
}
return () => null;
};
@@ -141,29 +158,26 @@ const getVariableParser = variable => {
* for the user to edit. The values from input elements are relayed to
* backend and eventually Prometheus API.
*
- * This method currently is not used anywhere. Once the issue
- * https://gitlab.com/gitlab-org/gitlab/-/issues/214536 is completed,
- * this method will have been used by the monitoring dashboard.
- *
- * @param {Object} templating templating variables from the dashboard yml file
- * @returns {Object} a map of processed templating variables
+ * @param {Object} templating variables from the dashboard yml file
+ * @returns {array} An array of variables to display as inputs
*/
-export const parseTemplatingVariables = ({ variables = {} } = {}) =>
- Object.entries(variables).reduce((acc, [key, variable]) => {
+export const parseTemplatingVariables = (ymlVariables = {}) =>
+ Object.entries(ymlVariables).reduce((acc, [name, ymlVariable]) => {
// get the parser
- const parser = getVariableParser(variable);
+ const parser = getVariableParser(ymlVariable);
// parse the variable
- const parsedVar = parser(variable);
+ const variable = parser(ymlVariable);
// for simple custom variable label is null and it should be
// replace with key instead
- if (parsedVar) {
- acc[key] = {
- ...parsedVar,
- label: parsedVar.label || key,
- };
+ if (variable) {
+ acc.push({
+ ...variable,
+ name,
+ label: variable.label || name,
+ });
}
return acc;
- }, {});
+ }, []);
/**
* Custom variables are defined in the dashboard yml file
@@ -181,23 +195,81 @@ export const parseTemplatingVariables = ({ variables = {} } = {}) =>
* This method can be improved further. See the below issue
* https://gitlab.com/gitlab-org/gitlab/-/issues/217713
*
- * @param {Object} varsFromYML template variables from yml file
+ * @param {array} parsedYmlVariables - template variables from yml file
* @returns {Object}
*/
-export const mergeURLVariables = (varsFromYML = {}) => {
+export const mergeURLVariables = (parsedYmlVariables = []) => {
const varsFromURL = templatingVariablesFromUrl();
- const variables = {};
- Object.keys(varsFromYML).forEach(key => {
- if (Object.prototype.hasOwnProperty.call(varsFromURL, key)) {
- variables[key] = {
- ...varsFromYML[key],
- value: varsFromURL[key],
- };
- } else {
- variables[key] = varsFromYML[key];
+ parsedYmlVariables.forEach(variable => {
+ const { name } = variable;
+ if (Object.prototype.hasOwnProperty.call(varsFromURL, name)) {
+ Object.assign(variable, { value: varsFromURL[name] });
}
});
- return variables;
+ return parsedYmlVariables;
+};
+
+/**
+ * Converts series data to options that can be added to a
+ * variable. Series data is returned from the Prometheus API
+ * `/api/v1/series`.
+ *
+ * Finds a `label` in the series data, so it can be used as
+ * a filter.
+ *
+ * For example, for the arguments:
+ *
+ * {
+ * "label": "job"
+ * "data" : [
+ * {
+ * "__name__" : "up",
+ * "job" : "prometheus",
+ * "instance" : "localhost:9090"
+ * },
+ * {
+ * "__name__" : "up",
+ * "job" : "node",
+ * "instance" : "localhost:9091"
+ * },
+ * {
+ * "__name__" : "process_start_time_seconds",
+ * "job" : "prometheus",
+ * "instance" : "localhost:9090"
+ * }
+ * ]
+ * }
+ *
+ * It returns all the different "job" values:
+ *
+ * [
+ * {
+ * "label": "node",
+ * "value": "node"
+ * },
+ * {
+ * "label": "prometheus",
+ * "value": "prometheus"
+ * }
+ * ]
+ *
+ * @param {options} options object
+ * @param {options.seriesLabel} name of the searched series label
+ * @param {options.data} series data from the series API
+ * @return {array} Options objects with the shape `{ label, value }`
+ *
+ * @see https://prometheus.io/docs/prometheus/latest/querying/api/#finding-series-by-label-matchers
+ */
+export const optionsFromSeriesData = ({ label, data = [] }) => {
+ const optionsSet = data.reduce((set, seriesObject) => {
+ // Use `new Set` to deduplicate options
+ if (seriesObject[label]) {
+ set.add(seriesObject[label]);
+ }
+ return set;
+ }, new Set());
+
+ return [...optionsSet].map(parseSimpleCustomValues);
};
export default {};
diff --git a/app/assets/javascripts/monitoring/utils.js b/app/assets/javascripts/monitoring/utils.js
index 4d2927a066e..0c6fcad9dd0 100644
--- a/app/assets/javascripts/monitoring/utils.js
+++ b/app/assets/javascripts/monitoring/utils.js
@@ -5,6 +5,7 @@ import {
removeParams,
updateHistory,
} from '~/lib/utils/url_utility';
+import { parseBoolean } from '~/lib/utils/common_utils';
import {
timeRangeParamNames,
timeRangeFromParams,
@@ -13,6 +14,50 @@ import {
import { VARIABLE_PREFIX } from './constants';
/**
+ * Extracts the initial state and props from HTML dataset
+ * and places them in separate objects to setup bundle.
+ * @param {*} dataset
+ */
+export const stateAndPropsFromDataset = (dataset = {}) => {
+ const {
+ currentDashboard,
+ deploymentsEndpoint,
+ dashboardEndpoint,
+ dashboardsEndpoint,
+ dashboardTimezone,
+ canAccessOperationsSettings,
+ operationsSettingsPath,
+ projectPath,
+ logsPath,
+ currentEnvironmentName,
+ customDashboardBasePath,
+ ...dataProps
+ } = dataset;
+
+ // HTML attributes are always strings, parse other types.
+ dataProps.hasMetrics = parseBoolean(dataProps.hasMetrics);
+ dataProps.customMetricsAvailable = parseBoolean(dataProps.customMetricsAvailable);
+ dataProps.prometheusAlertsAvailable = parseBoolean(dataProps.prometheusAlertsAvailable);
+
+ return {
+ initState: {
+ currentDashboard,
+ deploymentsEndpoint,
+ dashboardEndpoint,
+ dashboardsEndpoint,
+ dashboardTimezone,
+ canAccessOperationsSettings,
+ operationsSettingsPath,
+ projectPath,
+ logsPath,
+ currentEnvironmentName,
+ customDashboardBasePath,
+ },
+ dataProps,
+ };
+};
+
+/**
* List of non time range url parameters
* This will be removed once we add support for free text variables
* via the dashboard yaml files in https://gitlab.com/gitlab-org/gitlab/-/issues/215689
@@ -160,8 +205,10 @@ export const removePrefixFromLabel = label =>
* @returns {Object}
*/
export const convertVariablesForURL = variables =>
- Object.keys(variables || {}).reduce((acc, key) => {
- acc[addPrefixToLabel(key)] = variables[key]?.value;
+ variables.reduce((acc, { name, value }) => {
+ if (value !== null) {
+ acc[addPrefixToLabel(name)] = value;
+ }
return acc;
}, {});
diff --git a/app/assets/javascripts/namespace_storage_limit_alert.js b/app/assets/javascripts/namespace_storage_limit_alert.js
deleted file mode 100644
index 34ad93c127d..00000000000
--- a/app/assets/javascripts/namespace_storage_limit_alert.js
+++ /dev/null
@@ -1,20 +0,0 @@
-import Cookies from 'js-cookie';
-
-const handleOnDismiss = ({ currentTarget }) => {
- const {
- dataset: { id, level },
- } = currentTarget;
-
- Cookies.set(`hide_storage_limit_alert_${id}_${level}`, true, { expires: 365 });
-
- const notification = document.querySelector('.js-namespace-storage-alert');
- notification.parentNode.removeChild(notification);
-};
-
-export default () => {
- const alert = document.querySelector('.js-namespace-storage-alert-dismiss');
-
- if (alert) {
- alert.addEventListener('click', handleOnDismiss);
- }
-};
diff --git a/app/assets/javascripts/notes.js b/app/assets/javascripts/notes.js
index 6e695de447d..f4982507adb 100644
--- a/app/assets/javascripts/notes.js
+++ b/app/assets/javascripts/notes.js
@@ -1617,7 +1617,7 @@ export default class Notes {
}
tempFormContent = formContent;
- if (this.hasQuickActions(formContent)) {
+ if (this.glForm.supportsQuickActions && this.hasQuickActions(formContent)) {
tempFormContent = this.stripQuickActions(formContent);
hasQuickActions = true;
}
diff --git a/app/assets/javascripts/notes/components/comment_form.vue b/app/assets/javascripts/notes/components/comment_form.vue
index 16dcde46262..ac93d3df654 100644
--- a/app/assets/javascripts/notes/components/comment_form.vue
+++ b/app/assets/javascripts/notes/components/comment_form.vue
@@ -17,7 +17,7 @@ import {
import { refreshUserMergeRequestCounts } from '~/commons/nav/user_merge_requests';
import * as constants from '../constants';
import eventHub from '../event_hub';
-import issueWarning from '../../vue_shared/components/issue/issue_warning.vue';
+import NoteableWarning from '../../vue_shared/components/notes/noteable_warning.vue';
import markdownField from '../../vue_shared/components/markdown/field.vue';
import userAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue';
import loadingButton from '../../vue_shared/components/loading_button.vue';
@@ -28,8 +28,7 @@ import issuableStateMixin from '../mixins/issuable_state';
export default {
name: 'CommentForm',
components: {
- issueWarning,
- epicWarning: () => import('ee_component/vue_shared/components/epic/epic_warning.vue'),
+ NoteableWarning,
noteSignedOutWidget,
discussionLockedWidget,
markdownField,
@@ -126,9 +125,13 @@ export default {
canToggleIssueState() {
return (
this.getNoteableData.current_user.can_update &&
- this.getNoteableData.state !== constants.MERGED
+ this.getNoteableData.state !== constants.MERGED &&
+ !this.closedAndLocked
);
},
+ closedAndLocked() {
+ return !this.isOpen && this.isLocked(this.getNoteableData);
+ },
endpoint() {
return this.getNoteableData.create_note_path;
},
@@ -350,14 +353,15 @@ export default {
<form ref="commentForm" class="new-note common-note-form gfm-form js-main-target-form">
<div class="error-alert"></div>
- <issue-warning
- v-if="hasWarning(getNoteableData) && isIssueType"
+ <noteable-warning
+ v-if="hasWarning(getNoteableData)"
:is-locked="isLocked(getNoteableData)"
:is-confidential="isConfidential(getNoteableData)"
- :locked-issue-docs-path="lockedIssueDocsPath"
- :confidential-issue-docs-path="confidentialIssueDocsPath"
+ :noteable-type="noteableType"
+ :locked-noteable-docs-path="lockedIssueDocsPath"
+ :confidential-noteable-docs-path="confidentialIssueDocsPath"
/>
- <epic-warning :is-confidential="isConfidential(getNoteableData)" />
+
<markdown-field
ref="markdownField"
:is-submitting="isSubmitting"
@@ -374,20 +378,18 @@ export default {
dir="auto"
:disabled="isSubmitting"
name="note[note]"
- class="note-textarea js-vue-comment-form js-note-text
-js-gfm-input js-autosize markdown-area js-vue-textarea qa-comment-input"
+ class="note-textarea js-vue-comment-form js-note-text js-gfm-input js-autosize markdown-area js-vue-textarea qa-comment-input"
data-supports-quick-actions="true"
:aria-label="__('Description')"
:placeholder="__('Write a comment or drag your files here…')"
@keydown.up="editCurrentUserLastNote()"
@keydown.meta.enter="handleSave()"
@keydown.ctrl.enter="handleSave()"
- >
- </textarea>
+ ></textarea>
</markdown-field>
<gl-alert
v-if="isToggleBlockedIssueWarning"
- class="prepend-top-16"
+ class="gl-mt-5"
:title="__('Are you sure you want to close this blocked issue?')"
:primary-button-text="__('Yes, close issue')"
:secondary-button-text="__('Cancel')"
@@ -417,13 +419,11 @@ js-gfm-input js-autosize markdown-area js-vue-textarea qa-comment-input"
</gl-alert>
<div class="note-form-actions">
<div
- class="btn-group
-append-right-10 comment-type-dropdown js-comment-type-dropdown droplab-dropdown"
+ class="btn-group gl-mr-3 comment-type-dropdown js-comment-type-dropdown droplab-dropdown"
>
<button
:disabled="isSubmitButtonDisabled"
- class="btn btn-success js-comment-button js-comment-submit-button
- qa-comment-button"
+ class="btn btn-success js-comment-button js-comment-submit-button qa-comment-button"
type="submit"
:data-track-label="trackingLabel"
data-track-event="click_button"
@@ -440,7 +440,7 @@ append-right-10 comment-type-dropdown js-comment-type-dropdown droplab-dropdown"
data-toggle="dropdown"
:aria-label="__('Open comment type dropdown')"
>
- <i aria-hidden="true" class="fa fa-caret-down toggle-icon"> </i>
+ <i aria-hidden="true" class="fa fa-caret-down toggle-icon"></i>
</button>
<ul class="note-type-dropdown dropdown-open-top dropdown-menu">
@@ -450,7 +450,7 @@ append-right-10 comment-type-dropdown js-comment-type-dropdown droplab-dropdown"
class="btn btn-transparent"
@click.prevent="setNoteType('comment')"
>
- <i aria-hidden="true" class="fa fa-check icon"> </i>
+ <i aria-hidden="true" class="fa fa-check icon"></i>
<div class="description">
<strong>{{ __('Comment') }}</strong>
<p>
@@ -470,7 +470,7 @@ append-right-10 comment-type-dropdown js-comment-type-dropdown droplab-dropdown"
class="btn btn-transparent qa-discussion-option"
@click.prevent="setNoteType('discussion')"
>
- <i aria-hidden="true" class="fa fa-check icon"> </i>
+ <i aria-hidden="true" class="fa fa-check icon"></i>
<div class="description">
<strong>{{ __('Start thread') }}</strong>
<p>{{ startDiscussionDescription }}</p>
diff --git a/app/assets/javascripts/notes/components/discussion_notes.vue b/app/assets/javascripts/notes/components/discussion_notes.vue
index 0b136549c14..458da5cf67f 100644
--- a/app/assets/javascripts/notes/components/discussion_notes.vue
+++ b/app/assets/javascripts/notes/components/discussion_notes.vue
@@ -74,7 +74,7 @@ export default {
},
},
methods: {
- ...mapActions(['toggleDiscussion']),
+ ...mapActions(['toggleDiscussion', 'setSelectedCommentPositionHover']),
componentName(note) {
if (note.isPlaceholderNote) {
if (note.placeholderType === SYSTEM_NOTE) {
@@ -99,7 +99,11 @@ export default {
<template>
<div class="discussion-notes">
- <ul class="notes">
+ <ul
+ class="notes"
+ @mouseenter="setSelectedCommentPositionHover(discussion.position.line_range)"
+ @mouseleave="setSelectedCommentPositionHover()"
+ >
<template v-if="shouldGroupReplies">
<component
:is="componentName(firstNote)"
@@ -108,6 +112,7 @@ export default {
:commit="commit"
:help-page-path="helpPagePath"
:show-reply-button="userCanReply"
+ :discussion-root="true"
@handleDeleteNote="$emit('deleteNote')"
@startReplying="$emit('startReplying')"
>
@@ -151,6 +156,7 @@ export default {
:note="componentData(note)"
:help-page-path="helpPagePath"
:line="diffLine"
+ :discussion-root="index === 0"
@handleDeleteNote="$emit('deleteNote')"
>
<slot v-if="index === 0" slot="avatar-badge" name="avatar-badge"></slot>
diff --git a/app/assets/javascripts/notes/components/multiline_comment_form.vue b/app/assets/javascripts/notes/components/multiline_comment_form.vue
index 5fba011a153..bb13eb87af7 100644
--- a/app/assets/javascripts/notes/components/multiline_comment_form.vue
+++ b/app/assets/javascripts/notes/components/multiline_comment_form.vue
@@ -1,4 +1,5 @@
<script>
+import { mapActions } from 'vuex';
import { GlFormSelect, GlSprintf } from '@gitlab/ui';
import { getSymbol, getLineClasses } from './multiline_comment_utils';
@@ -21,19 +22,51 @@ export default {
},
data() {
return {
- commentLineStart: {
- lineCode: this.lineRange ? this.lineRange.start_line_code : this.line.line_code,
- type: this.lineRange ? this.lineRange.start_line_type : this.line.type,
- },
+ commentLineStart: {},
+ commentLineEndType: this.lineRange?.end?.line_type || this.line.type,
};
},
+ computed: {
+ lineNumber() {
+ return this.commentLineOptions[this.commentLineOptions.length - 1].text;
+ },
+ },
+ created() {
+ const line = this.lineRange?.start || this.line;
+
+ this.commentLineStart = {
+ line_code: line.line_code,
+ type: line.type,
+ old_line: line.old_line,
+ new_line: line.new_line,
+ };
+ this.highlightSelection();
+ },
+ destroyed() {
+ this.setSelectedCommentPosition();
+ },
methods: {
+ ...mapActions(['setSelectedCommentPosition']),
getSymbol({ type }) {
return getSymbol(type);
},
getLineClasses(line) {
return getLineClasses(line);
},
+ updateCommentLineStart(value) {
+ this.commentLineStart = value;
+ this.$emit('input', value);
+ this.highlightSelection();
+ },
+ highlightSelection() {
+ const { line_code, new_line, old_line, type } = this.line;
+ const updatedLineRange = {
+ start: { ...this.commentLineStart },
+ end: { line_code, new_line, old_line, type },
+ };
+
+ this.setSelectedCommentPosition(updatedLineRange);
+ },
},
};
</script>
@@ -55,12 +88,12 @@ export default {
:options="commentLineOptions"
size="sm"
class="gl-w-auto gl-vertical-align-baseline"
- @change="$emit('input', $event)"
+ @change="updateCommentLineStart"
/>
</template>
<template #end>
<span :class="getLineClasses(line)">
- {{ getSymbol(line) + (line.new_line || line.old_line) }}
+ {{ lineNumber }}
</span>
</template>
</gl-sprintf>
diff --git a/app/assets/javascripts/notes/components/multiline_comment_utils.js b/app/assets/javascripts/notes/components/multiline_comment_utils.js
index dc9c55e9b30..dbae10c8f6c 100644
--- a/app/assets/javascripts/notes/components/multiline_comment_utils.js
+++ b/app/assets/javascripts/notes/components/multiline_comment_utils.js
@@ -7,11 +7,19 @@ export function getSymbol(type) {
}
function getLineNumber(lineRange, key) {
- if (!lineRange || !key) return '';
- const lineCode = lineRange[`${key}_line_code`] || '';
- const lineType = lineRange[`${key}_line_type`] || '';
- const lines = lineCode.split('_') || [];
- const lineNumber = lineType === 'old' ? lines[1] : lines[2];
+ if (!lineRange || !key || !lineRange[key]) return '';
+ const { new_line: newLine, old_line: oldLine, type } = lineRange[key];
+ const otherKey = key === 'start' ? 'end' : 'start';
+
+ // By default we want to see the "old" or "left side" line number
+ // The exception is if the "end" line is on the "right" side
+ // `otherLineType` is only used if `type` is null to make sure the line
+ // number relfects the "right" side number, if that is the side
+ // the comment form is located on
+ const otherLineType = !type ? lineRange[otherKey]?.type : null;
+ const lineType = type || '';
+ let lineNumber = oldLine;
+ if (lineType === 'new' || otherLineType === 'new') lineNumber = newLine;
return (lineNumber && getSymbol(lineType) + lineNumber) || '';
}
@@ -37,21 +45,67 @@ export function getLineClasses(line) {
];
}
-export function commentLineOptions(diffLines, lineCode) {
- const selectedIndex = diffLines.findIndex(line => line.line_code === lineCode);
+export function commentLineOptions(diffLines, startingLine, lineCode, side = 'left') {
+ const preferredSide = side === 'left' ? 'old_line' : 'new_line';
+ const fallbackSide = preferredSide === 'new_line' ? 'old_line' : 'new_line';
const notMatchType = l => l.type !== 'match';
+ const linesCopy = [...diffLines]; // don't mutate the argument
+ const startingLineCode = startingLine.line_code;
+
+ const currentIndex = linesCopy.findIndex(line => line.line_code === lineCode);
// We're limiting adding comments to only lines above the current line
// to make rendering simpler. Future interations will use a more
// intuitive dragging interface that will make this unnecessary
- const upToSelected = diffLines.slice(0, selectedIndex + 1);
+ const upToSelected = linesCopy.slice(0, currentIndex + 1);
// Only include the lines up to the first "Show unchanged lines" block
// i.e. not a "match" type
const lines = takeRightWhile(upToSelected, notMatchType);
- return lines.map(l => ({
- value: { lineCode: l.line_code, type: l.type },
- text: `${getSymbol(l.type)}${l.new_line || l.old_line}`,
- }));
+ // If the selected line is "hidden" in an unchanged line block
+ // or "above" the current group of lines add it to the array so
+ // that the drop down is not defaulted to empty
+ const selectedIndex = lines.findIndex(line => line.line_code === startingLineCode);
+ if (selectedIndex < 0) lines.unshift(startingLine);
+
+ return lines.map(l => {
+ const { line_code, type, old_line, new_line } = l;
+ return {
+ value: { line_code, type, old_line, new_line },
+ text: `${getSymbol(type)}${l[preferredSide] || l[fallbackSide]}`,
+ };
+ });
+}
+
+export function formatLineRange(start, end) {
+ const extractProps = ({ line_code, type, old_line, new_line }) => ({
+ line_code,
+ type,
+ old_line,
+ new_line,
+ });
+ return {
+ start: extractProps(start),
+ end: extractProps(end),
+ };
+}
+
+export function getCommentedLines(selectedCommentPosition, diffLines) {
+ if (!selectedCommentPosition) {
+ // This structure simplifies the logic that consumes this result
+ // by keeping the returned shape the same and adjusting the bounds
+ // to something unreachable. This way our component logic stays:
+ // "if index between start and end"
+ return {
+ startLine: diffLines.length + 1,
+ endLine: diffLines.length + 1,
+ };
+ }
+
+ const { start, end } = selectedCommentPosition;
+ const startLine = diffLines.findIndex(l => l.line_code === start.line_code);
+ const endLine = diffLines.findIndex(l => l.line_code === end.line_code);
+
+ return { startLine, endLine };
}
diff --git a/app/assets/javascripts/notes/components/note_actions.vue b/app/assets/javascripts/notes/components/note_actions.vue
index f1af8be590a..7615b0518b7 100644
--- a/app/assets/javascripts/notes/components/note_actions.vue
+++ b/app/assets/javascripts/notes/components/note_actions.vue
@@ -128,6 +128,9 @@ export default {
isIssue() {
return this.targetType === 'issue';
},
+ canAssign() {
+ return this.getNoteableData.current_user?.can_update && this.isIssue;
+ },
},
methods: {
onEdit() {
@@ -257,23 +260,23 @@ export default {
{{ __('Copy link') }}
</button>
</li>
- <li v-if="canEdit">
+ <li v-if="canAssign">
<button
- class="btn btn-transparent js-note-delete js-note-delete"
+ class="btn-default btn-transparent"
+ data-testid="assign-user"
type="button"
- @click.prevent="onDelete"
+ @click="assignUser"
>
- <span class="text-danger">{{ __('Delete comment') }}</span>
+ {{ displayAssignUserText }}
</button>
</li>
- <li v-if="isIssue">
+ <li v-if="canEdit">
<button
- class="btn-default btn-transparent"
- data-testid="assign-user"
+ class="btn btn-transparent js-note-delete js-note-delete"
type="button"
- @click="assignUser"
+ @click.prevent="onDelete"
>
- {{ displayAssignUserText }}
+ <span class="text-danger">{{ __('Delete comment') }}</span>
</button>
</li>
</ul>
diff --git a/app/assets/javascripts/notes/components/note_form.vue b/app/assets/javascripts/notes/components/note_form.vue
index 795ee10ca0f..24227d55ebf 100644
--- a/app/assets/javascripts/notes/components/note_form.vue
+++ b/app/assets/javascripts/notes/components/note_form.vue
@@ -2,7 +2,7 @@
import { mapGetters, mapActions, mapState } from 'vuex';
import { mergeUrlParams } from '~/lib/utils/url_utility';
import eventHub from '../event_hub';
-import issueWarning from '../../vue_shared/components/issue/issue_warning.vue';
+import NoteableWarning from '../../vue_shared/components/notes/noteable_warning.vue';
import markdownField from '../../vue_shared/components/markdown/field.vue';
import issuableStateMixin from '../mixins/issuable_state';
import resolvable from '../mixins/resolvable';
@@ -12,7 +12,7 @@ import { getDraft, updateDraft } from '~/lib/utils/autosave';
export default {
name: 'NoteForm',
components: {
- issueWarning,
+ NoteableWarning,
markdownField,
},
mixins: [issuableStateMixin, resolvable],
@@ -101,6 +101,7 @@ export default {
isResolving: this.resolveDiscussion,
isUnresolving: !this.resolveDiscussion,
resolveAsThread: true,
+ isSubmittingWithKeydown: false,
};
},
computed: {
@@ -241,6 +242,10 @@ export default {
this.$emit('cancelForm', shouldConfirm, this.noteBody !== this.updatedNoteBody);
},
onInput() {
+ if (this.isSubmittingWithKeydown) {
+ return;
+ }
+
if (this.autosaveKey) {
const { autosaveKey, updatedNoteBody: text } = this;
updateDraft(autosaveKey, text);
@@ -250,6 +255,7 @@ export default {
if (this.showBatchCommentsActions) {
this.handleAddToReview();
} else {
+ this.isSubmittingWithKeydown = true;
this.handleUpdate();
}
},
@@ -303,12 +309,12 @@ export default {
></div>
<div class="flash-container timeline-content"></div>
<form :data-line-code="lineCode" class="edit-note common-note-form js-quick-submit gfm-form">
- <issue-warning
+ <noteable-warning
v-if="hasWarning(getNoteableData)"
:is-locked="isLocked(getNoteableData)"
:is-confidential="isConfidential(getNoteableData)"
- :locked-issue-docs-path="lockedIssueDocsPath"
- :confidential-issue-docs-path="confidentialIssueDocsPath"
+ :locked-noteable-docs-path="lockedIssueDocsPath"
+ :confidential-noteable-docs-path="confidentialIssueDocsPath"
/>
<markdown-field
@@ -404,7 +410,7 @@ export default {
</button>
<button
v-if="discussion.resolvable"
- class="btn btn-nr btn-default append-right-10 js-comment-resolve-button"
+ class="btn btn-nr btn-default gl-mr-3 js-comment-resolve-button"
@click.prevent="handleUpdate(true)"
>
{{ resolveButtonTitle }}
diff --git a/app/assets/javascripts/notes/components/noteable_note.vue b/app/assets/javascripts/notes/components/noteable_note.vue
index 0e4dd1b9c84..9bf8cffe940 100644
--- a/app/assets/javascripts/notes/components/noteable_note.vue
+++ b/app/assets/javascripts/notes/components/noteable_note.vue
@@ -21,6 +21,7 @@ import {
getEndLineNumber,
getLineClasses,
commentLineOptions,
+ formatLineRange,
} from './multiline_comment_utils';
import MultilineCommentForm from './multiline_comment_form.vue';
@@ -62,10 +63,15 @@ export default {
default: false,
},
diffLines: {
- type: Object,
+ type: Array,
required: false,
default: null,
},
+ discussionRoot: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
},
data() {
return {
@@ -73,10 +79,7 @@ export default {
isDeleting: false,
isRequesting: false,
isResolving: false,
- commentLineStart: {
- line_code: this.line?.line_code,
- type: this.line?.type,
- },
+ commentLineStart: {},
};
},
computed: {
@@ -144,28 +147,46 @@ export default {
return getEndLineNumber(this.lineRange);
},
showMultiLineComment() {
- return (
- this.glFeatures.multilineComments &&
- this.startLineNumber &&
- this.endLineNumber &&
- (this.startLineNumber !== this.endLineNumber || this.isEditing)
- );
+ if (!this.glFeatures.multilineComments || !this.discussionRoot) return false;
+ if (this.isEditing) return true;
+
+ return this.line && this.startLineNumber !== this.endLineNumber;
},
commentLineOptions() {
- if (this.diffLines) {
- return commentLineOptions(this.diffLines, this.line.line_code);
+ if (!this.diffFile || !this.line) return [];
+
+ const sideA = this.line.type === 'new' ? 'right' : 'left';
+ const sideB = sideA === 'left' ? 'right' : 'left';
+ const lines = this.diffFile.highlighted_diff_lines.length
+ ? this.diffFile.highlighted_diff_lines
+ : this.diffFile.parallel_diff_lines.map(l => l[sideA] || l[sideB]);
+ return commentLineOptions(lines, this.commentLineStart, this.line.line_code, sideA);
+ },
+ diffFile() {
+ if (this.commentLineStart.line_code) {
+ const lineCode = this.commentLineStart.line_code.split('_')[0];
+ return this.getDiffFileByHash(lineCode);
}
- const diffFile = this.diffFile || this.getDiffFileByHash(this.targetNoteHash);
- if (!diffFile) return null;
- return commentLineOptions(diffFile.highlighted_diff_lines, this.line.line_code);
+ return null;
},
},
-
created() {
+ const line = this.note.position?.line_range?.start || this.line;
+
+ this.commentLineStart = line
+ ? {
+ line_code: line.line_code,
+ type: line.type,
+ old_line: line.old_line,
+ new_line: line.new_line,
+ }
+ : {};
+
eventHub.$on('enterEditMode', ({ noteId }) => {
if (noteId === this.note.id) {
this.isEditing = true;
+ this.setSelectedCommentPositionHover();
this.scrollToNoteIfNeeded($(this.$el));
}
});
@@ -185,9 +206,11 @@ export default {
'toggleResolveNote',
'scrollToNoteIfNeeded',
'updateAssignees',
+ 'setSelectedCommentPositionHover',
]),
editHandler() {
this.isEditing = true;
+ this.setSelectedCommentPositionHover();
this.$emit('handleEdit');
},
deleteHandler() {
@@ -224,13 +247,11 @@ export default {
formUpdateHandler(noteText, parentElement, callback, resolveDiscussion) {
const position = {
...this.note.position,
- line_range: {
- start_line_code: this.commentLineStart?.lineCode,
- start_line_type: this.commentLineStart?.type,
- end_line_code: this.line?.line_code,
- end_line_type: this.line?.type,
- },
};
+
+ if (this.commentLineStart && this.line)
+ position.line_range = formatLineRange(this.commentLineStart, this.line);
+
this.$emit('handleUpdateNote', {
note: this.note,
noteText,
@@ -246,7 +267,7 @@ export default {
note: {
target_type: this.getNoteableData.targetType,
target_id: this.note.noteable_id,
- note: { note: noteText },
+ note: { note: noteText, position: JSON.stringify(position) },
},
};
this.isRequesting = true;
@@ -266,6 +287,7 @@ export default {
} else {
this.isRequesting = false;
this.isEditing = true;
+ this.setSelectedCommentPositionHover();
this.$nextTick(() => {
const msg = __('Something went wrong while editing your comment. Please try again.');
Flash(msg, 'alert', this.$el);
@@ -317,14 +339,17 @@ export default {
>
<div v-if="showMultiLineComment" data-testid="multiline-comment">
<multiline-comment-form
- v-if="isEditing && commentLineOptions && line"
+ v-if="isEditing && note.position"
v-model="commentLineStart"
:line="line"
:comment-line-options="commentLineOptions"
:line-range="note.position.line_range"
- class="gl-mb-3 gl-text-gray-700 gl-border-gray-200 gl-border-b-solid gl-border-b-1 gl-pb-3"
+ class="gl-mb-3 gl-text-gray-700 gl-pb-3"
/>
- <div v-else class="gl-mb-3 gl-text-gray-700">
+ <div
+ v-else
+ class="gl-mb-3 gl-text-gray-700 gl-border-gray-200 gl-border-b-solid gl-border-b-1 gl-pb-3"
+ >
<gl-sprintf :message="__('Comment on lines %{startLine} to %{endLine}')">
<template #startLine>
<span :class="getLineClasses(startLineNumber)">{{ startLineNumber }}</span>
diff --git a/app/assets/javascripts/notes/components/sort_discussion.vue b/app/assets/javascripts/notes/components/sort_discussion.vue
index 4a7543819eb..60b531d7597 100644
--- a/app/assets/javascripts/notes/components/sort_discussion.vue
+++ b/app/assets/javascripts/notes/components/sort_discussion.vue
@@ -49,7 +49,10 @@ export default {
</script>
<template>
- <div class="mr-2 d-inline-block align-bottom full-width-mobile">
+ <div
+ data-testid="sort-discussion-filter"
+ class="gl-mr-2 gl-display-inline-block gl-vertical-align-bottom full-width-mobile"
+ >
<local-storage-sync
:value="sortDirection"
:storage-key="storageKey"
diff --git a/app/assets/javascripts/notes/mixins/diff_line_note_form.js b/app/assets/javascripts/notes/mixins/diff_line_note_form.js
index 5930b5f3321..9a2e86aeed2 100644
--- a/app/assets/javascripts/notes/mixins/diff_line_note_form.js
+++ b/app/assets/javascripts/notes/mixins/diff_line_note_form.js
@@ -4,6 +4,7 @@ import { TEXT_DIFF_POSITION_TYPE, IMAGE_DIFF_POSITION_TYPE } from '~/diffs/const
import createFlash from '~/flash';
import { s__ } from '~/locale';
import { clearDraft } from '~/lib/utils/autosave';
+import { formatLineRange } from '~/notes/components/multiline_comment_utils';
export default {
computed: {
@@ -45,6 +46,9 @@ export default {
});
},
addToReview(note) {
+ const lineRange =
+ (this.line && this.commentLineStart && formatLineRange(this.commentLineStart, this.line)) ||
+ {};
const positionType = this.diffFileCommentForm
? IMAGE_DIFF_POSITION_TYPE
: TEXT_DIFF_POSITION_TYPE;
@@ -60,6 +64,7 @@ export default {
linePosition: this.position,
positionType,
...this.diffFileCommentForm,
+ lineRange,
});
const diffFileHeadSha = this.commit && this?.diffFile?.diff_refs?.head_sha;
diff --git a/app/assets/javascripts/notes/mixins/discussion_navigation.js b/app/assets/javascripts/notes/mixins/discussion_navigation.js
index 9281149d9d3..889883a23d0 100644
--- a/app/assets/javascripts/notes/mixins/discussion_navigation.js
+++ b/app/assets/javascripts/notes/mixins/discussion_navigation.js
@@ -78,8 +78,16 @@ function handleDiscussionJump(self, fn, discussionId = self.currentDiscussionId)
const isDiffView = window.mrTabs.currentAction === 'diffs';
const targetId = fn(discussionId, isDiffView);
const discussion = self.getDiscussion(targetId);
- jumpToDiscussion(self, discussion);
- self.setCurrentDiscussionId(targetId);
+ const discussionFilePath = discussion.diff_file?.file_path;
+
+ if (discussionFilePath) {
+ self.scrollToFile(discussionFilePath);
+ }
+
+ self.$nextTick(() => {
+ jumpToDiscussion(self, discussion);
+ self.setCurrentDiscussionId(targetId);
+ });
}
export default {
@@ -95,6 +103,7 @@ export default {
},
methods: {
...mapActions(['expandDiscussion', 'setCurrentDiscussionId']),
+ ...mapActions('diffs', ['scrollToFile']),
jumpToNextDiscussion() {
handleDiscussionJump(this, this.nextUnresolvedDiscussionId);
diff --git a/app/assets/javascripts/notes/stores/actions.js b/app/assets/javascripts/notes/stores/actions.js
index a5b006fc301..5b2ab255557 100644
--- a/app/assets/javascripts/notes/stores/actions.js
+++ b/app/assets/javascripts/notes/stores/actions.js
@@ -13,11 +13,35 @@ import sidebarTimeTrackingEventHub from '../../sidebar/event_hub';
import { isInViewport, scrollToElement, isInMRPage } from '../../lib/utils/common_utils';
import { mergeUrlParams } from '../../lib/utils/url_utility';
import mrWidgetEventHub from '../../vue_merge_request_widget/event_hub';
+import updateIssueConfidentialMutation from '~/sidebar/components/confidential/queries/update_issue_confidential.mutation.graphql';
import { __, sprintf } from '~/locale';
import Api from '~/api';
let eTagPoll;
+export const updateConfidentialityOnIssue = ({ commit, getters }, { confidential, fullPath }) => {
+ const { iid } = getters.getNoteableData;
+
+ return utils.gqClient
+ .mutate({
+ mutation: updateIssueConfidentialMutation,
+ variables: {
+ input: {
+ projectPath: fullPath,
+ iid: String(iid),
+ confidential,
+ },
+ },
+ })
+ .then(({ data }) => {
+ const {
+ issueSetConfidential: { issue },
+ } = data;
+
+ commit(types.SET_ISSUE_CONFIDENTIAL, issue.confidential);
+ });
+};
+
export const expandDiscussion = ({ commit, dispatch }, data) => {
if (data.discussionId) {
dispatch('diffs/renderFileForDiscussionId', data.discussionId, { root: true });
@@ -32,6 +56,8 @@ export const setNotesData = ({ commit }, data) => commit(types.SET_NOTES_DATA, d
export const setNoteableData = ({ commit }, data) => commit(types.SET_NOTEABLE_DATA, data);
+export const setConfidentiality = ({ commit }, data) => commit(types.SET_ISSUE_CONFIDENTIAL, data);
+
export const setUserData = ({ commit }, data) => commit(types.SET_USER_DATA, data);
export const setLastFetchedAt = ({ commit }, data) => commit(types.SET_LAST_FETCHED_AT, data);
@@ -73,6 +99,14 @@ export const setDiscussionSortDirection = ({ commit }, direction) => {
commit(types.SET_DISCUSSIONS_SORT, direction);
};
+export const setSelectedCommentPosition = ({ commit }, position) => {
+ commit(types.SET_SELECTED_COMMENT_POSITION, position);
+};
+
+export const setSelectedCommentPositionHover = ({ commit }, position) => {
+ commit(types.SET_SELECTED_COMMENT_POSITION_HOVER, position);
+};
+
export const removeNote = ({ commit, dispatch, state }, note) => {
const discussion = state.discussions.find(({ id }) => id === note.discussion_id);
@@ -205,7 +239,6 @@ export const closeIssue = ({ commit, dispatch, state }) => {
commit(types.CLOSE_ISSUE);
dispatch('emitStateChangedEvent', data);
dispatch('toggleStateButtonLoading', false);
- dispatch('toggleBlockedIssueWarning', false);
});
};
@@ -377,9 +410,8 @@ export const saveNote = ({ commit, dispatch }, noteData) => {
};
const pollSuccessCallBack = (resp, commit, state, getters, dispatch) => {
- if (resp.notes && resp.notes.length) {
- updateOrCreateNotes({ commit, state, getters, dispatch }, resp.notes);
-
+ if (resp.notes?.length) {
+ dispatch('updateOrCreateNotes', resp.notes);
dispatch('startTaskList');
}
@@ -399,12 +431,12 @@ const getFetchDataParams = state => {
return { endpoint, options };
};
-export const fetchData = ({ commit, state, getters }) => {
+export const fetchData = ({ commit, state, getters, dispatch }) => {
const { endpoint, options } = getFetchDataParams(state);
axios
.get(endpoint, options)
- .then(({ data }) => pollSuccessCallBack(data, commit, state, getters))
+ .then(({ data }) => pollSuccessCallBack(data, commit, state, getters, dispatch))
.catch(() => Flash(__('Something went wrong while fetching latest comments.')));
};
@@ -424,7 +456,7 @@ export const poll = ({ commit, state, getters, dispatch }) => {
if (!Visibility.hidden()) {
eTagPoll.makeRequest();
} else {
- fetchData({ commit, state, getters });
+ dispatch('fetchData');
}
Visibility.change(() => {
diff --git a/app/assets/javascripts/notes/stores/modules/index.js b/app/assets/javascripts/notes/stores/modules/index.js
index 329bf5e147e..1649e63c61f 100644
--- a/app/assets/javascripts/notes/stores/modules/index.js
+++ b/app/assets/javascripts/notes/stores/modules/index.js
@@ -12,6 +12,15 @@ export default () => ({
lastFetchedAt: null,
currentDiscussionId: null,
batchSuggestionsInfo: [],
+ /**
+ * selectedCommentPosition & selectedCommentPosition structures are the same as `position.line_range`:
+ * {
+ * start: { line_code: string, new_line: number, old_line:number, type: string },
+ * end: { line_code: string, new_line: number, old_line:number, type: string },
+ * }
+ */
+ selectedCommentPosition: null,
+ selectedCommentPositionHover: null,
// View layer
isToggleStateButtonLoading: false,
@@ -26,6 +35,7 @@ export default () => ({
},
userData: {},
noteableData: {
+ discussion_locked: false,
confidential: false, // TODO: Move data like this to Issue Store, should not be apart of notes.
current_user: {},
preview_note_path: 'path/to/preview',
diff --git a/app/assets/javascripts/notes/stores/mutation_types.js b/app/assets/javascripts/notes/stores/mutation_types.js
index 538774ee467..f2236b18beb 100644
--- a/app/assets/javascripts/notes/stores/mutation_types.js
+++ b/app/assets/javascripts/notes/stores/mutation_types.js
@@ -33,12 +33,15 @@ export const SET_EXPAND_DISCUSSIONS = 'SET_EXPAND_DISCUSSIONS';
export const UPDATE_RESOLVABLE_DISCUSSIONS_COUNTS = 'UPDATE_RESOLVABLE_DISCUSSIONS_COUNTS';
export const SET_CURRENT_DISCUSSION_ID = 'SET_CURRENT_DISCUSSION_ID';
export const SET_DISCUSSIONS_SORT = 'SET_DISCUSSIONS_SORT';
+export const SET_SELECTED_COMMENT_POSITION = 'SET_SELECTED_COMMENT_POSITION';
+export const SET_SELECTED_COMMENT_POSITION_HOVER = 'SET_SELECTED_COMMENT_POSITION_HOVER';
// Issue
export const CLOSE_ISSUE = 'CLOSE_ISSUE';
export const REOPEN_ISSUE = 'REOPEN_ISSUE';
export const TOGGLE_STATE_BUTTON_LOADING = 'TOGGLE_STATE_BUTTON_LOADING';
export const TOGGLE_BLOCKED_ISSUE_WARNING = 'TOGGLE_BLOCKED_ISSUE_WARNING';
+export const SET_ISSUE_CONFIDENTIAL = 'SET_ISSUE_CONFIDENTIAL';
// Description version
export const REQUEST_DESCRIPTION_VERSION = 'REQUEST_DESCRIPTION_VERSION';
diff --git a/app/assets/javascripts/notes/stores/mutations.js b/app/assets/javascripts/notes/stores/mutations.js
index 2aeadcb2da1..e5f1c11fb35 100644
--- a/app/assets/javascripts/notes/stores/mutations.js
+++ b/app/assets/javascripts/notes/stores/mutations.js
@@ -95,6 +95,10 @@ export default {
Object.assign(state, { noteableData: data });
},
+ [types.SET_ISSUE_CONFIDENTIAL](state, data) {
+ state.noteableData.confidential = data;
+ },
+
[types.SET_USER_DATA](state, data) {
Object.assign(state, { userData: data });
},
@@ -304,6 +308,14 @@ export default {
state.discussionSortOrder = sort;
},
+ [types.SET_SELECTED_COMMENT_POSITION](state, position) {
+ state.selectedCommentPosition = position;
+ },
+
+ [types.SET_SELECTED_COMMENT_POSITION_HOVER](state, position) {
+ state.selectedCommentPositionHover = position;
+ },
+
[types.DISABLE_COMMENTS](state, value) {
state.commentsDisabled = value;
},
diff --git a/app/assets/javascripts/notes/stores/utils.js b/app/assets/javascripts/notes/stores/utils.js
index 97dcd54fe88..10faac0c32b 100644
--- a/app/assets/javascripts/notes/stores/utils.js
+++ b/app/assets/javascripts/notes/stores/utils.js
@@ -1,6 +1,7 @@
import AjaxCache from '~/lib/utils/ajax_cache';
import { trimFirstCharOfLineContent } from '~/diffs/store/utils';
import { sprintf, __ } from '~/locale';
+import createGqClient, { fetchPolicies } from '~/lib/graphql';
// factory function because global flag makes RegExp stateful
const createQuickActionsRegex = () => /^\/\w+.*$/gm;
@@ -34,3 +35,10 @@ export const stripQuickActions = note => note.replace(createQuickActionsRegex(),
export const prepareDiffLines = diffLines =>
diffLines.map(line => ({ ...trimFirstCharOfLineContent(line) }));
+
+export const gqClient = createGqClient(
+ {},
+ {
+ fetchPolicy: fetchPolicies.NO_CACHE,
+ },
+);
diff --git a/app/assets/javascripts/pages/admin/abuse_reports/abuse_reports.js b/app/assets/javascripts/pages/admin/abuse_reports/abuse_reports.js
index 674b807edbe..da7f81759ea 100644
--- a/app/assets/javascripts/pages/admin/abuse_reports/abuse_reports.js
+++ b/app/assets/javascripts/pages/admin/abuse_reports/abuse_reports.js
@@ -32,7 +32,7 @@ export default class AbuseReports {
$messageCellElement.text(originalMessage);
} else {
$messageCellElement.data('messageTruncated', 'true');
- $messageCellElement.text(`${originalMessage.substr(0, MAX_MESSAGE_LENGTH - 3)}...`);
+ $messageCellElement.text(truncate(originalMessage, MAX_MESSAGE_LENGTH));
}
}
}
diff --git a/app/assets/javascripts/pages/admin/clusters/show/index.js b/app/assets/javascripts/pages/admin/clusters/show/index.js
index 8001d2dd1da..ccf631b2c53 100644
--- a/app/assets/javascripts/pages/admin/clusters/show/index.js
+++ b/app/assets/javascripts/pages/admin/clusters/show/index.js
@@ -1,5 +1,7 @@
import ClustersBundle from '~/clusters/clusters_bundle';
+import initClusterHealth from '~/pages/projects/clusters/show/cluster_health';
document.addEventListener('DOMContentLoaded', () => {
new ClustersBundle(); // eslint-disable-line no-new
+ initClusterHealth();
});
diff --git a/app/assets/javascripts/pages/admin/groups/show/index.js b/app/assets/javascripts/pages/admin/groups/show/index.js
index b0cdad627a6..69d219d29f7 100644
--- a/app/assets/javascripts/pages/admin/groups/show/index.js
+++ b/app/assets/javascripts/pages/admin/groups/show/index.js
@@ -1,3 +1,23 @@
-import UsersSelect from '../../../../users_select';
+import Vue from 'vue';
+import UsersSelect from '~/users_select';
+import RemoveMemberModal from '~/vue_shared/components/remove_member_modal.vue';
-document.addEventListener('DOMContentLoaded', () => new UsersSelect());
+function mountRemoveMemberModal() {
+ const el = document.querySelector('.js-remove-member-modal');
+ if (!el) {
+ return false;
+ }
+
+ return new Vue({
+ el,
+ render(createComponent) {
+ return createComponent(RemoveMemberModal);
+ },
+ });
+}
+
+document.addEventListener('DOMContentLoaded', () => {
+ mountRemoveMemberModal();
+
+ new UsersSelect(); // eslint-disable-line no-new
+});
diff --git a/app/assets/javascripts/pages/admin/projects/index.js b/app/assets/javascripts/pages/admin/projects/index.js
index d6b1e747aec..d86c5e2ddb8 100644
--- a/app/assets/javascripts/pages/admin/projects/index.js
+++ b/app/assets/javascripts/pages/admin/projects/index.js
@@ -1,7 +1,25 @@
-import ProjectsList from '../../../projects_list';
-import NamespaceSelect from '../../../namespace_select';
+import Vue from 'vue';
+import ProjectsList from '~/projects_list';
+import NamespaceSelect from '~/namespace_select';
+import RemoveMemberModal from '~/vue_shared/components/remove_member_modal.vue';
+
+function mountRemoveMemberModal() {
+ const el = document.querySelector('.js-remove-member-modal');
+ if (!el) {
+ return false;
+ }
+
+ return new Vue({
+ el,
+ render(createComponent) {
+ return createComponent(RemoveMemberModal);
+ },
+ });
+}
document.addEventListener('DOMContentLoaded', () => {
+ mountRemoveMemberModal();
+
new ProjectsList(); // eslint-disable-line no-new
document
diff --git a/app/assets/javascripts/pages/constants.js b/app/assets/javascripts/pages/constants.js
index 5e119454ce1..35c67190b62 100644
--- a/app/assets/javascripts/pages/constants.js
+++ b/app/assets/javascripts/pages/constants.js
@@ -4,4 +4,5 @@ export const FILTERED_SEARCH = {
MERGE_REQUESTS: 'merge_requests',
ISSUES: 'issues',
ADMIN_RUNNERS: 'admin/runners',
+ GROUP_RUNNERS_ANCHOR: 'runners-settings',
};
diff --git a/app/assets/javascripts/pages/groups/clusters/show/index.js b/app/assets/javascripts/pages/groups/clusters/show/index.js
index 8001d2dd1da..ccf631b2c53 100644
--- a/app/assets/javascripts/pages/groups/clusters/show/index.js
+++ b/app/assets/javascripts/pages/groups/clusters/show/index.js
@@ -1,5 +1,7 @@
import ClustersBundle from '~/clusters/clusters_bundle';
+import initClusterHealth from '~/pages/projects/clusters/show/cluster_health';
document.addEventListener('DOMContentLoaded', () => {
new ClustersBundle(); // eslint-disable-line no-new
+ initClusterHealth();
});
diff --git a/app/assets/javascripts/pages/groups/group_members/index.js b/app/assets/javascripts/pages/groups/group_members/index.js
new file mode 100644
index 00000000000..e146592e134
--- /dev/null
+++ b/app/assets/javascripts/pages/groups/group_members/index.js
@@ -0,0 +1,30 @@
+import Vue from 'vue';
+import Members from 'ee_else_ce/members';
+import memberExpirationDate from '~/member_expiration_date';
+import UsersSelect from '~/users_select';
+import groupsSelect from '~/groups_select';
+import RemoveMemberModal from '~/vue_shared/components/remove_member_modal.vue';
+
+function mountRemoveMemberModal() {
+ const el = document.querySelector('.js-remove-member-modal');
+ if (!el) {
+ return false;
+ }
+
+ return new Vue({
+ el,
+ render(createComponent) {
+ return createComponent(RemoveMemberModal);
+ },
+ });
+}
+
+document.addEventListener('DOMContentLoaded', () => {
+ groupsSelect();
+ memberExpirationDate();
+ memberExpirationDate('.js-access-expiration-date-groups');
+ mountRemoveMemberModal();
+
+ new Members(); // eslint-disable-line no-new
+ new UsersSelect(); // eslint-disable-line no-new
+});
diff --git a/app/assets/javascripts/pages/groups/group_members/index/index.js b/app/assets/javascripts/pages/groups/group_members/index/index.js
deleted file mode 100644
index 0c732922e81..00000000000
--- a/app/assets/javascripts/pages/groups/group_members/index/index.js
+++ /dev/null
@@ -1,14 +0,0 @@
-/* eslint-disable no-new */
-
-import Members from 'ee_else_ce/members';
-import memberExpirationDate from '~/member_expiration_date';
-import UsersSelect from '~/users_select';
-import groupsSelect from '~/groups_select';
-
-document.addEventListener('DOMContentLoaded', () => {
- memberExpirationDate();
- memberExpirationDate('.js-access-expiration-date-groups');
- new Members();
- groupsSelect();
- new UsersSelect();
-});
diff --git a/app/assets/javascripts/pages/groups/new/group_path_validator.js b/app/assets/javascripts/pages/groups/new/group_path_validator.js
index eeaa6527431..d2684b6af59 100644
--- a/app/assets/javascripts/pages/groups/new/group_path_validator.js
+++ b/app/assets/javascripts/pages/groups/new/group_path_validator.js
@@ -12,6 +12,7 @@ const successMessageSelector = '.validation-success';
const pendingMessageSelector = '.validation-pending';
const unavailableMessageSelector = '.validation-error';
const suggestionsMessageSelector = '.gl-path-suggestions';
+const inputGroupSelector = '.input-group';
export default class GroupPathValidator extends InputValidator {
constructor(opts = {}) {
@@ -39,7 +40,7 @@ export default class GroupPathValidator extends InputValidator {
static validateGroupPathInput(inputDomElement) {
const groupPath = inputDomElement.value;
- if (inputDomElement.checkValidity() && groupPath.length > 0) {
+ if (inputDomElement.checkValidity() && groupPath.length > 1) {
GroupPathValidator.setMessageVisibility(inputDomElement, pendingMessageSelector);
fetchGroupPathAvailability(groupPath)
@@ -69,9 +70,10 @@ export default class GroupPathValidator extends InputValidator {
}
static setMessageVisibility(inputDomElement, messageSelector, isVisible = true) {
- const messageElement = inputDomElement.parentElement.parentElement.querySelector(
- messageSelector,
- );
+ const messageElement = inputDomElement
+ .closest(inputGroupSelector)
+ .parentElement.querySelector(messageSelector);
+
messageElement.classList.toggle('hide', !isVisible);
}
diff --git a/app/assets/javascripts/pages/groups/settings/ci_cd/show/index.js b/app/assets/javascripts/pages/groups/settings/ci_cd/show/index.js
index 479c82265f2..23283f46a5d 100644
--- a/app/assets/javascripts/pages/groups/settings/ci_cd/show/index.js
+++ b/app/assets/javascripts/pages/groups/settings/ci_cd/show/index.js
@@ -1,11 +1,20 @@
import initSettingsPanels from '~/settings_panels';
import AjaxVariableList from '~/ci_variable_list/ajax_variable_list';
import initVariableList from '~/ci_variable_list';
+import initFilteredSearch from '~/pages/search/init_filtered_search';
+import AdminRunnersFilteredSearchTokenKeys from '~/filtered_search/admin_runners_filtered_search_token_keys';
+import { FILTERED_SEARCH } from '~/pages/constants';
document.addEventListener('DOMContentLoaded', () => {
// Initialize expandable settings panels
initSettingsPanels();
+ initFilteredSearch({
+ page: FILTERED_SEARCH.ADMIN_RUNNERS,
+ filteredSearchTokenKeys: AdminRunnersFilteredSearchTokenKeys,
+ anchor: FILTERED_SEARCH.GROUP_RUNNERS_ANCHOR,
+ });
+
if (gon.features.newVariablesUi) {
initVariableList();
} else {
diff --git a/app/assets/javascripts/pages/groups/shared/group_details.js b/app/assets/javascripts/pages/groups/shared/group_details.js
index 85daff3f60f..37b253d7c48 100644
--- a/app/assets/javascripts/pages/groups/shared/group_details.js
+++ b/app/assets/javascripts/pages/groups/shared/group_details.js
@@ -8,7 +8,6 @@ import NotificationsForm from '~/notifications_form';
import ProjectsList from '~/projects_list';
import ShortcutsNavigation from '~/behaviors/shortcuts/shortcuts_navigation';
import GroupTabs from './group_tabs';
-import initNamespaceStorageLimitAlert from '~/namespace_storage_limit_alert';
export default function initGroupDetails(actionName = 'show') {
const newGroupChildWrapper = document.querySelector('.js-new-project-subgroup');
@@ -28,6 +27,4 @@ export default function initGroupDetails(actionName = 'show') {
if (newGroupChildWrapper) {
new NewGroupChild(newGroupChildWrapper);
}
-
- initNamespaceStorageLimitAlert();
}
diff --git a/app/assets/javascripts/pages/profiles/show/index.js b/app/assets/javascripts/pages/profiles/show/index.js
index ad003181728..74ab1bc13a9 100644
--- a/app/assets/javascripts/pages/profiles/show/index.js
+++ b/app/assets/javascripts/pages/profiles/show/index.js
@@ -4,6 +4,7 @@ import emojiRegex from 'emoji-regex';
import createFlash from '~/flash';
import EmojiMenu from './emoji_menu';
import { __ } from '~/locale';
+import * as Emoji from '~/emoji';
const defaultStatusEmoji = 'speech_balloon';
@@ -55,8 +56,8 @@ document.addEventListener('DOMContentLoaded', () => {
}
});
- import(/* webpackChunkName: 'emoji' */ '~/emoji')
- .then(Emoji => {
+ Emoji.initEmojiMap()
+ .then(() => {
const emojiMenu = new EmojiMenu(
Emoji,
toggleEmojiMenuButtonSelector,
diff --git a/app/assets/javascripts/pages/projects/clusters/show/cluster_health.js b/app/assets/javascripts/pages/projects/clusters/show/cluster_health.js
new file mode 100644
index 00000000000..382d39645a9
--- /dev/null
+++ b/app/assets/javascripts/pages/projects/clusters/show/cluster_health.js
@@ -0,0 +1,18 @@
+import monitoringApp from '~/monitoring/monitoring_app';
+
+export default () => {
+ const el = document.getElementById('prometheus-graphs');
+
+ if (el && el.dataset) {
+ monitoringApp({
+ ...el.dataset,
+ showLegend: false,
+ showHeader: false,
+ showPanels: false,
+ forceSmallGraph: true,
+ smallEmptyState: true,
+ currentEnvironmentName: '',
+ hasMetrics: true,
+ });
+ }
+};
diff --git a/app/assets/javascripts/pages/projects/clusters/show/index.js b/app/assets/javascripts/pages/projects/clusters/show/index.js
index 397f9faf6fe..d20e2c19583 100644
--- a/app/assets/javascripts/pages/projects/clusters/show/index.js
+++ b/app/assets/javascripts/pages/projects/clusters/show/index.js
@@ -1,7 +1,9 @@
import ClustersBundle from '~/clusters/clusters_bundle';
import initGkeNamespace from '~/create_cluster/gke_cluster_namespace';
+import initClusterHealth from './cluster_health';
document.addEventListener('DOMContentLoaded', () => {
new ClustersBundle(); // eslint-disable-line no-new
initGkeNamespace();
+ initClusterHealth();
});
diff --git a/app/assets/javascripts/pages/projects/commit/pipelines/index.js b/app/assets/javascripts/pages/projects/commit/pipelines/index.js
index 9f08260c3d6..1415a6f60c8 100644
--- a/app/assets/javascripts/pages/projects/commit/pipelines/index.js
+++ b/app/assets/javascripts/pages/projects/commit/pipelines/index.js
@@ -1,6 +1,7 @@
import $ from 'jquery';
import MiniPipelineGraph from '~/mini_pipeline_graph_dropdown';
import initPipelines from '~/commit/pipelines/pipelines_bundle';
+import { fetchCommitMergeRequests } from '~/commit_merge_requests';
document.addEventListener('DOMContentLoaded', () => {
new MiniPipelineGraph({
@@ -8,5 +9,6 @@ document.addEventListener('DOMContentLoaded', () => {
}).bindEvents();
// eslint-disable-next-line no-jquery/no-load
$('.commit-info.branches').load(document.querySelector('.js-commit-box').dataset.commitPath);
+ fetchCommitMergeRequests();
initPipelines();
});
diff --git a/app/assets/javascripts/pages/projects/edit/index.js b/app/assets/javascripts/pages/projects/edit/index.js
index 9fb07917f9b..e65c18c07a9 100644
--- a/app/assets/javascripts/pages/projects/edit/index.js
+++ b/app/assets/javascripts/pages/projects/edit/index.js
@@ -7,13 +7,20 @@ import dirtySubmitFactory from '~/dirty_submit/dirty_submit_factory';
import initFilePickers from '~/file_pickers';
import initProjectLoadingSpinner from '../shared/save_project_loader';
import initProjectPermissionsSettings from '../shared/permissions';
+import initProjectRemoveModal from '~/projects/project_remove_modal';
+import UserCallout from '~/user_callout';
+import initServiceDesk from '~/projects/settings_service_desk';
document.addEventListener('DOMContentLoaded', () => {
initFilePickers();
initConfirmDangerModal();
initSettingsPanels();
+ initProjectRemoveModal();
mountBadgeSettings(PROJECT_BADGE);
+ new UserCallout({ className: 'js-service-desk-callout' }); // eslint-disable-line no-new
+ initServiceDesk();
+
initProjectLoadingSpinner();
initProjectPermissionsSettings();
setupTransferEdit('.js-project-transfer-form', 'select.select2');
diff --git a/app/assets/javascripts/pages/projects/forks/new/components/fork_groups_list.vue b/app/assets/javascripts/pages/projects/forks/new/components/fork_groups_list.vue
new file mode 100644
index 00000000000..77753521342
--- /dev/null
+++ b/app/assets/javascripts/pages/projects/forks/new/components/fork_groups_list.vue
@@ -0,0 +1,91 @@
+<script>
+import { GlTabs, GlTab, GlLoadingIcon, GlSearchBoxByType } from '@gitlab/ui';
+import axios from '~/lib/utils/axios_utils';
+import { __ } from '~/locale';
+import createFlash from '~/flash';
+import ForkGroupsListItem from './fork_groups_list_item.vue';
+
+export default {
+ components: {
+ GlTabs,
+ GlTab,
+ GlLoadingIcon,
+ GlSearchBoxByType,
+ ForkGroupsListItem,
+ },
+ props: {
+ hasReachedProjectLimit: {
+ type: Boolean,
+ required: true,
+ },
+ endpoint: {
+ type: String,
+ required: true,
+ },
+ },
+ data() {
+ return {
+ namespaces: null,
+ filter: '',
+ };
+ },
+ computed: {
+ filteredNamespaces() {
+ return this.namespaces.filter(n => n.name.toLowerCase().includes(this.filter.toLowerCase()));
+ },
+ },
+
+ mounted() {
+ this.loadGroups();
+ },
+
+ methods: {
+ loadGroups() {
+ axios
+ .get(this.endpoint)
+ .then(response => {
+ this.namespaces = response.data.namespaces;
+ })
+ .catch(() => createFlash(__('There was a problem fetching groups.')));
+ },
+ },
+
+ i18n: {
+ searchPlaceholder: __('Search by name'),
+ },
+};
+</script>
+<template>
+ <gl-tabs class="fork-groups">
+ <gl-tab :title="__('Groups and subgroups')">
+ <gl-loading-icon v-if="!namespaces" size="md" class="gl-mt-3" />
+ <template v-else-if="namespaces.length === 0">
+ <div class="gl-text-center">
+ <div class="h5">{{ __('No available groups to fork the project.') }}</div>
+ <p class="gl-mt-5">
+ {{ __('You must have permission to create a project in a group before forking.') }}
+ </p>
+ </div>
+ </template>
+ <div v-else-if="filteredNamespaces.length === 0" class="gl-text-center gl-mt-3">
+ {{ s__('GroupsTree|No groups matched your search') }}
+ </div>
+ <ul v-else class="groups-list group-list-tree">
+ <fork-groups-list-item
+ v-for="(namespace, index) in filteredNamespaces"
+ :key="index"
+ :group="namespace"
+ :has-reached-project-limit="hasReachedProjectLimit"
+ />
+ </ul>
+ </gl-tab>
+ <template #tabs-end>
+ <gl-search-box-by-type
+ v-if="namespaces && namespaces.length"
+ v-model="filter"
+ :placeholder="$options.i18n.searchPlaceholder"
+ class="gl-align-self-center gl-ml-auto fork-filtered-search"
+ />
+ </template>
+ </gl-tabs>
+</template>
diff --git a/app/assets/javascripts/pages/projects/forks/new/components/fork_groups_list_item.vue b/app/assets/javascripts/pages/projects/forks/new/components/fork_groups_list_item.vue
new file mode 100644
index 00000000000..792c2f3db34
--- /dev/null
+++ b/app/assets/javascripts/pages/projects/forks/new/components/fork_groups_list_item.vue
@@ -0,0 +1,147 @@
+<script>
+import {
+ GlLink,
+ GlButton,
+ GlIcon,
+ GlAvatar,
+ GlTooltipDirective,
+ GlTooltip,
+ GlBadge,
+} from '@gitlab/ui';
+import { VISIBILITY_TYPE_ICON, GROUP_VISIBILITY_TYPE } from '~/groups/constants';
+import { __ } from '~/locale';
+import csrf from '~/lib/utils/csrf';
+
+export default {
+ components: {
+ GlIcon,
+ GlAvatar,
+ GlBadge,
+ GlButton,
+ GlTooltip,
+ GlLink,
+ },
+ directives: {
+ GlTooltip: GlTooltipDirective,
+ },
+ props: {
+ group: {
+ type: Object,
+ required: true,
+ },
+ hasReachedProjectLimit: {
+ type: Boolean,
+ required: true,
+ },
+ },
+ data() {
+ return { namespaces: null };
+ },
+
+ computed: {
+ rowClass() {
+ return {
+ 'has-description': this.group.description,
+ 'being-removed': this.isGroupPendingRemoval,
+ };
+ },
+ isGroupPendingRemoval() {
+ return this.group.marked_for_deletion;
+ },
+ hasForkedProject() {
+ return Boolean(this.group.forked_project_path);
+ },
+ visibilityIcon() {
+ return VISIBILITY_TYPE_ICON[this.group.visibility];
+ },
+ visibilityTooltip() {
+ return GROUP_VISIBILITY_TYPE[this.group.visibility];
+ },
+ isSelectButtonDisabled() {
+ return this.hasReachedProjectLimit || !this.group.can_create_project;
+ },
+ selectButtonDisabledTooltip() {
+ return this.hasReachedProjectLimit
+ ? this.$options.i18n.hasReachedProjectLimitMessage
+ : this.$options.i18n.insufficientPermissionsMessage;
+ },
+ },
+
+ i18n: {
+ hasReachedProjectLimitMessage: __('You have reached your project limit'),
+ insufficientPermissionsMessage: __(
+ 'You must have permission to create a project in a namespace before forking.',
+ ),
+ },
+
+ csrf,
+};
+</script>
+<template>
+ <li :class="rowClass" class="group-row">
+ <div class="group-row-contents gl-display-flex gl-align-items-center gl-py-3 gl-pr-5">
+ <div class="folder-toggle-wrap gl-mr-2 gl-display-flex gl-align-items-center">
+ <gl-icon name="folder-o" />
+ </div>
+ <gl-link
+ :href="group.relative_path"
+ class="gl-display-none gl-flex-shrink-0 gl-display-sm-flex gl-mr-3"
+ >
+ <gl-avatar :size="32" shape="rect" :entity-name="group.name" :src="group.avatarUrl" />
+ </gl-link>
+ <div class="gl-min-w-0 gl-display-flex gl-flex-grow-1 gl-flex-shrink-1 gl-align-items-center">
+ <div class="gl-min-w-0 gl-flex-grow-1 flex-shrink-1">
+ <div class="title gl-display-flex gl-align-items-center gl-flex-wrap gl-mr-3">
+ <gl-link :href="group.relative_path" class="gl-mt-3 gl-mr-3 gl-text-gray-900!">{{
+ group.full_name
+ }}</gl-link>
+ <gl-icon
+ v-gl-tooltip.hover.bottom
+ class="gl-mr-0 gl-inline-flex gl-mt-3 text-secondary"
+ :name="visibilityIcon"
+ :title="visibilityTooltip"
+ />
+ <gl-badge
+ v-if="isGroupPendingRemoval"
+ variant="warning"
+ class="gl-display-none gl-display-sm-flex gl-mt-3 gl-mr-1"
+ >{{ __('pending removal') }}</gl-badge
+ >
+ <span v-if="group.permission" class="user-access-role gl-mt-3">
+ {{ group.permission }}
+ </span>
+ </div>
+ <div v-if="group.description" class="description">
+ <span v-html="group.markdown_description"> </span>
+ </div>
+ </div>
+ <div class="gl-display-flex gl-flex-shrink-0">
+ <gl-button
+ v-if="hasForkedProject"
+ class="gl-h-7 gl-text-decoration-none!"
+ :href="group.forked_project_path"
+ >{{ __('Go to fork') }}</gl-button
+ >
+ <template v-else>
+ <div ref="selectButtonWrapper">
+ <form method="POST" :action="group.fork_path">
+ <input type="hidden" name="authenticity_token" :value="$options.csrf.token" />
+ <gl-button
+ type="submit"
+ class="gl-h-7 gl-text-decoration-none!"
+ :data-qa-name="group.full_name"
+ variant="success"
+ :disabled="isSelectButtonDisabled"
+ >{{ __('Select') }}</gl-button
+ >
+ </form>
+ </div>
+ <gl-tooltip v-if="isSelectButtonDisabled" :target="() => $refs.selectButtonWrapper">
+ {{ selectButtonDisabledTooltip }}
+ </gl-tooltip>
+ </template>
+ </div>
+ </div>
+ </div>
+ </li>
+</template>
diff --git a/app/assets/javascripts/pages/projects/graphs/components/code_coverage.vue b/app/assets/javascripts/pages/projects/graphs/components/code_coverage.vue
index af8fb032c22..39d6df33a85 100644
--- a/app/assets/javascripts/pages/projects/graphs/components/code_coverage.vue
+++ b/app/assets/javascripts/pages/projects/graphs/components/code_coverage.vue
@@ -63,17 +63,19 @@ export default {
selectedDailyCoverageName() {
return this.selectedDailyCoverage?.group_name;
},
- formattedData() {
- if (this.selectedDailyCoverage?.data) {
- return this.selectedDailyCoverage.data.map(value => [
- dateFormat(value.date, 'mmm dd'),
- value.coverage,
- ]);
- }
-
+ sortedData() {
// If the fetching failed, we return an empty array which
// allow the graph to render while empty
- return [];
+ if (!this.selectedDailyCoverage?.data) {
+ return [];
+ }
+
+ return [...this.selectedDailyCoverage.data].sort(
+ (a, b) => new Date(a.date) - new Date(b.date),
+ );
+ },
+ formattedData() {
+ return this.sortedData.map(value => [dateFormat(value.date, 'mmm dd'), value.coverage]);
},
chartData() {
return [
diff --git a/app/assets/javascripts/pages/projects/integrations/jira/issues/index/index.js b/app/assets/javascripts/pages/projects/integrations/jira/issues/index/index.js
new file mode 100644
index 00000000000..260ba69b4bc
--- /dev/null
+++ b/app/assets/javascripts/pages/projects/integrations/jira/issues/index/index.js
@@ -0,0 +1,5 @@
+import initIssuablesList from '~/issuables_list';
+
+document.addEventListener('DOMContentLoaded', () => {
+ initIssuablesList();
+});
diff --git a/app/assets/javascripts/pages/projects/issues/service_desk/filtered_search.js b/app/assets/javascripts/pages/projects/issues/service_desk/filtered_search.js
new file mode 100644
index 00000000000..72003b61c8a
--- /dev/null
+++ b/app/assets/javascripts/pages/projects/issues/service_desk/filtered_search.js
@@ -0,0 +1,30 @@
+/* eslint-disable class-methods-use-this */
+import IssuableFilteredSearchTokenKeys from 'ee_else_ce/filtered_search/issuable_filtered_search_token_keys';
+import FilteredSearchManager from 'ee_else_ce/filtered_search/filtered_search_manager';
+
+const AUTHOR_PARAM_KEY = 'author_username';
+
+export default class FilteredSearchServiceDesk extends FilteredSearchManager {
+ constructor(supportBotData) {
+ super({
+ page: 'service_desk',
+ filteredSearchTokenKeys: IssuableFilteredSearchTokenKeys,
+ });
+
+ this.supportBotData = supportBotData;
+ }
+
+ canEdit(tokenName) {
+ return tokenName !== 'author';
+ }
+
+ modifyUrlParams(paramsArray) {
+ const supportBotParamPair = `${AUTHOR_PARAM_KEY}=${this.supportBotData.username}`;
+ const onlyValidParams = paramsArray.filter(param => param.indexOf(AUTHOR_PARAM_KEY) === -1);
+
+ // unshift ensures author param is always first token element
+ onlyValidParams.unshift(supportBotParamPair);
+
+ return onlyValidParams;
+ }
+}
diff --git a/app/assets/javascripts/pages/projects/issues/service_desk/index.js b/app/assets/javascripts/pages/projects/issues/service_desk/index.js
new file mode 100644
index 00000000000..56054f5fc80
--- /dev/null
+++ b/app/assets/javascripts/pages/projects/issues/service_desk/index.js
@@ -0,0 +1,11 @@
+import FilteredSearchServiceDesk from './filtered_search';
+
+document.addEventListener('DOMContentLoaded', () => {
+ const supportBotData = JSON.parse(
+ document.querySelector('.js-service-desk-issues').dataset.supportBot,
+ );
+
+ const filteredSearchManager = new FilteredSearchServiceDesk(supportBotData);
+
+ filteredSearchManager.setup();
+});
diff --git a/app/assets/javascripts/pages/projects/issues/show.js b/app/assets/javascripts/pages/projects/issues/show.js
index 46c9b2fe0af..32f77465347 100644
--- a/app/assets/javascripts/pages/projects/issues/show.js
+++ b/app/assets/javascripts/pages/projects/issues/show.js
@@ -3,7 +3,7 @@ import Issue from '~/issue';
import ShortcutsIssuable from '~/behaviors/shortcuts/shortcuts_issuable';
import ZenMode from '~/zen_mode';
import '~/notes/index';
-import initIssueableApp from '~/issue_show';
+import initIssueableApp, { issuableHeaderWarnings } from '~/issue_show';
import initSentryErrorStackTraceApp from '~/sentry_error_stack_trace';
import initRelatedMergeRequestsApp from '~/related_merge_requests';
import initVueIssuableSidebarApp from '~/issuable_sidebar/sidebar_bundle';
@@ -12,15 +12,17 @@ export default function() {
initIssueableApp();
initSentryErrorStackTraceApp();
initRelatedMergeRequestsApp();
+ issuableHeaderWarnings();
- // .js-design-management is currently EE-only.
- // This will be moved to CE as part of https://gitlab.com/gitlab-org/gitlab/-/issues/212566#frontend
- // at which point this conditional can be removed.
- if (document.querySelector('.js-design-management')) {
- import(/* webpackChunkName: 'design_management' */ '~/design_management')
- .then(module => module.default())
- .catch(() => {});
- }
+ import(/* webpackChunkName: 'design_management' */ '~/design_management')
+ .then(module => module.default())
+ .catch(() => {});
+
+ // This will be removed when we remove the `design_management_moved` feature flag
+ // See https://gitlab.com/gitlab-org/gitlab/-/issues/223197
+ import(/* webpackChunkName: 'design_management' */ '~/design_management_new')
+ .then(module => module.default())
+ .catch(() => {});
new Issue(); // eslint-disable-line no-new
new ShortcutsIssuable(); // eslint-disable-line no-new
diff --git a/app/assets/javascripts/pages/projects/metrics_dashboard/index.js b/app/assets/javascripts/pages/projects/metrics_dashboard/index.js
new file mode 100644
index 00000000000..d3028aec313
--- /dev/null
+++ b/app/assets/javascripts/pages/projects/metrics_dashboard/index.js
@@ -0,0 +1,3 @@
+import monitoringApp from '~/monitoring/monitoring_app';
+
+document.addEventListener('DOMContentLoaded', monitoringApp);
diff --git a/app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/interval_pattern_input.vue b/app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/interval_pattern_input.vue
index 4efabcb7df3..5ef1f959b2c 100644
--- a/app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/interval_pattern_input.vue
+++ b/app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/interval_pattern_input.vue
@@ -1,12 +1,19 @@
<script>
-import { GlSprintf, GlLink } from '@gitlab/ui';
+import { GlFormRadio, GlFormRadioGroup, GlLink, GlSprintf } from '@gitlab/ui';
import { s__, sprintf } from '~/locale';
import { getWeekdayNames } from '~/lib/utils/datetime_utility';
+const KEY_EVERY_DAY = 'everyDay';
+const KEY_EVERY_WEEK = 'everyWeek';
+const KEY_EVERY_MONTH = 'everyMonth';
+const KEY_CUSTOM = 'custom';
+
export default {
components: {
- GlSprintf,
+ GlFormRadio,
+ GlFormRadioGroup,
GlLink,
+ GlSprintf,
},
props: {
initialCronInterval: {
@@ -22,6 +29,7 @@ export default {
randomWeekDayIndex: this.generateRandomWeekDayIndex(),
randomDay: this.generateRandomDay(),
inputNameAttribute: 'schedule[cron]',
+ radioValue: this.initialCronInterval ? KEY_CUSTOM : KEY_EVERY_DAY,
cronInterval: this.initialCronInterval,
cronSyntaxUrl: 'https://en.wikipedia.org/wiki/Cron',
};
@@ -29,14 +37,11 @@ export default {
computed: {
cronIntervalPresets() {
return {
- everyDay: `0 ${this.randomHour} * * *`,
- everyWeek: `0 ${this.randomHour} * * ${this.randomWeekDayIndex}`,
- everyMonth: `0 ${this.randomHour} ${this.randomDay} * *`,
+ [KEY_EVERY_DAY]: `0 ${this.randomHour} * * *`,
+ [KEY_EVERY_WEEK]: `0 ${this.randomHour} * * ${this.randomWeekDayIndex}`,
+ [KEY_EVERY_MONTH]: `0 ${this.randomHour} ${this.randomDay} * *`,
};
},
- intervalIsPreset() {
- return Object.values(this.cronIntervalPresets).includes(this.cronInterval);
- },
formattedTime() {
if (this.randomHour > 12) {
return `${this.randomHour - 12}:00pm`;
@@ -45,24 +50,36 @@ export default {
}
return `${this.randomHour}:00am`;
},
+ radioOptions() {
+ return [
+ {
+ value: KEY_EVERY_DAY,
+ text: sprintf(s__(`Every day (at %{time})`), { time: this.formattedTime }),
+ },
+ {
+ value: KEY_EVERY_WEEK,
+ text: sprintf(s__('Every week (%{weekday} at %{time})'), {
+ weekday: this.weekday,
+ time: this.formattedTime,
+ }),
+ },
+ {
+ value: KEY_EVERY_MONTH,
+ text: sprintf(s__('Every month (Day %{day} at %{time})'), {
+ day: this.randomDay,
+ time: this.formattedTime,
+ }),
+ },
+ {
+ value: KEY_CUSTOM,
+ text: s__('PipelineScheduleIntervalPattern|Custom (%{linkStart}Cron syntax%{linkEnd})'),
+ link: this.cronSyntaxUrl,
+ },
+ ];
+ },
weekday() {
return getWeekdayNames()[this.randomWeekDayIndex];
},
- everyDayText() {
- return sprintf(s__(`Every day (at %{time})`), { time: this.formattedTime });
- },
- everyWeekText() {
- return sprintf(s__('Every week (%{weekday} at %{time})'), {
- weekday: this.weekday,
- time: this.formattedTime,
- });
- },
- everyMonthText() {
- return sprintf(s__('Every month (Day %{day} at %{time})'), {
- day: this.randomDay,
- time: this.formattedTime,
- });
- },
},
watch: {
cronInterval() {
@@ -72,38 +89,18 @@ export default {
gl.pipelineScheduleFieldErrors.updateFormValidityState();
});
},
- },
- // If at the mounting stage the default is still an empty string, we
- // know we are not editing an existing field so we update it so
- // that the default is the first radio option
- mounted() {
- if (this.cronInterval === '') {
- this.cronInterval = this.cronIntervalPresets.everyDay;
- }
+ radioValue: {
+ immediate: true,
+ handler(val) {
+ if (val !== KEY_CUSTOM) {
+ this.cronInterval = this.cronIntervalPresets[val];
+ }
+ },
+ },
},
methods: {
- setCustomInput(e) {
- if (!this.isEditingCustom) {
- this.isEditingCustom = true;
- this.$refs.customInput.click();
- // Because we need to manually trigger the click on the radio btn,
- // it will add a space to update the v-model. If the user is typing
- // and the space is added, it will feel very unituitive so we reset
- // the value to the original
- this.cronInterval = e.target.value;
- }
- if (this.intervalIsPreset) {
- this.isEditingCustom = false;
- }
- },
- toggleCustomInput(shouldEnable) {
- this.isEditingCustom = shouldEnable;
-
- if (shouldEnable) {
- // We need to change the value so other radios don't remain selected
- // because the model (cronInterval) hasn't changed. The server trims it.
- this.cronInterval = `${this.cronInterval} `;
- }
+ onCustomInput() {
+ this.radioValue = KEY_CUSTOM;
},
generateRandomHour() {
return Math.floor(Math.random() * 23);
@@ -119,89 +116,33 @@ export default {
</script>
<template>
- <div class="interval-pattern-form-group">
- <div class="cron-preset-radio-input">
- <input
- id="every-day"
- v-model="cronInterval"
- :name="inputNameAttribute"
- :value="cronIntervalPresets.everyDay"
- class="label-bold"
- type="radio"
- @click="toggleCustomInput(false)"
- />
-
- <label class="label-bold" for="every-day">
- {{ everyDayText }}
- </label>
- </div>
-
- <div class="cron-preset-radio-input">
- <input
- id="every-week"
- v-model="cronInterval"
- :name="inputNameAttribute"
- :value="cronIntervalPresets.everyWeek"
- class="label-bold"
- type="radio"
- @click="toggleCustomInput(false)"
- />
-
- <label class="label-bold" for="every-week">
- {{ everyWeekText }}
- </label>
- </div>
-
- <div class="cron-preset-radio-input">
- <input
- id="every-month"
- v-model="cronInterval"
- :name="inputNameAttribute"
- :value="cronIntervalPresets.everyMonth"
- class="label-bold"
- type="radio"
- @click="toggleCustomInput(false)"
- />
-
- <label class="label-bold" for="every-month">
- {{ everyMonthText }}
- </label>
- </div>
-
- <div class="cron-preset-radio-input">
- <input
- id="custom"
- ref="customInput"
- v-model="cronInterval"
- :name="inputNameAttribute"
- :value="cronInterval"
- class="label-bold"
- type="radio"
- @click="toggleCustomInput(true)"
- />
-
- <label for="custom"> {{ s__('PipelineSheduleIntervalPattern|Custom') }} </label>
-
- <gl-sprintf :message="__('(%{linkStart}Cron syntax%{linkEnd})')">
- <template #link="{content}">
- <gl-link :href="cronSyntaxUrl" target="_blank" class="gl-font-sm">
- {{ content }}
- </gl-link>
- </template>
- </gl-sprintf>
- </div>
-
- <div class="cron-interval-input-wrapper">
- <input
- id="schedule_cron"
- v-model="cronInterval"
- :placeholder="__('Define a custom pattern with cron syntax')"
- :name="inputNameAttribute"
- class="form-control inline cron-interval-input"
- type="text"
- required="true"
- @input="setCustomInput"
- />
- </div>
+ <div>
+ <gl-form-radio-group v-model="radioValue" :name="inputNameAttribute">
+ <gl-form-radio
+ v-for="option in radioOptions"
+ :key="option.value"
+ :value="option.value"
+ :data-testid="option.value"
+ >
+ <gl-sprintf v-if="option.link" :message="option.text">
+ <template #link="{content}">
+ <gl-link :href="option.link" target="_blank" class="gl-font-sm">
+ {{ content }}
+ </gl-link>
+ </template>
+ </gl-sprintf>
+ <template v-else>{{ option.text }}</template>
+ </gl-form-radio>
+ </gl-form-radio-group>
+ <input
+ id="schedule_cron"
+ v-model="cronInterval"
+ :placeholder="__('Define a custom pattern with cron syntax')"
+ :name="inputNameAttribute"
+ class="form-control inline cron-interval-input"
+ type="text"
+ required="true"
+ @input="onCustomInput"
+ />
</div>
</template>
diff --git a/app/assets/javascripts/pages/projects/pipelines/index/index.js b/app/assets/javascripts/pages/projects/pipelines/index/index.js
index 2c37d7da4a7..bed9a751d4c 100644
--- a/app/assets/javascripts/pages/projects/pipelines/index/index.js
+++ b/app/assets/javascripts/pages/projects/pipelines/index/index.js
@@ -8,7 +8,7 @@ import {
} from '~/lib/utils/common_utils';
import { __ } from '~/locale';
import PipelinesStore from '../../../../pipelines/stores/pipelines_store';
-import pipelinesComponent from '../../../../pipelines/components/pipelines.vue';
+import pipelinesComponent from '../../../../pipelines/components/pipelines_list/pipelines.vue';
import Translate from '../../../../vue_shared/translate';
Vue.use(Translate);
@@ -40,6 +40,7 @@ document.addEventListener(
props: {
store: this.store,
endpoint: this.dataset.endpoint,
+ pipelineScheduleUrl: this.dataset.pipelineScheduleUrl,
helpPagePath: this.dataset.helpPagePath,
emptyStateSvgPath: this.dataset.emptyStateSvgPath,
errorStateSvgPath: this.dataset.errorStateSvgPath,
diff --git a/app/assets/javascripts/pages/projects/project_members/index.js b/app/assets/javascripts/pages/projects/project_members/index.js
index f39765818e7..e146592e134 100644
--- a/app/assets/javascripts/pages/projects/project_members/index.js
+++ b/app/assets/javascripts/pages/projects/project_members/index.js
@@ -1,12 +1,30 @@
+import Vue from 'vue';
import Members from 'ee_else_ce/members';
-import memberExpirationDate from '../../../member_expiration_date';
-import UsersSelect from '../../../users_select';
-import groupsSelect from '../../../groups_select';
+import memberExpirationDate from '~/member_expiration_date';
+import UsersSelect from '~/users_select';
+import groupsSelect from '~/groups_select';
+import RemoveMemberModal from '~/vue_shared/components/remove_member_modal.vue';
+
+function mountRemoveMemberModal() {
+ const el = document.querySelector('.js-remove-member-modal');
+ if (!el) {
+ return false;
+ }
+
+ return new Vue({
+ el,
+ render(createComponent) {
+ return createComponent(RemoveMemberModal);
+ },
+ });
+}
document.addEventListener('DOMContentLoaded', () => {
- memberExpirationDate('.js-access-expiration-date-groups');
groupsSelect();
memberExpirationDate();
+ memberExpirationDate('.js-access-expiration-date-groups');
+ mountRemoveMemberModal();
+
new Members(); // eslint-disable-line no-new
new UsersSelect(); // eslint-disable-line no-new
});
diff --git a/app/assets/javascripts/pages/projects/releases/new/index.js b/app/assets/javascripts/pages/projects/releases/new/index.js
new file mode 100644
index 00000000000..0e314aacf8a
--- /dev/null
+++ b/app/assets/javascripts/pages/projects/releases/new/index.js
@@ -0,0 +1,7 @@
+import ZenMode from '~/zen_mode';
+import initNewRelease from '~/releases/mount_new';
+
+document.addEventListener('DOMContentLoaded', () => {
+ new ZenMode(); // eslint-disable-line no-new
+ initNewRelease();
+});
diff --git a/app/assets/javascripts/pages/projects/settings/operations/show/index.js b/app/assets/javascripts/pages/projects/settings/operations/show/index.js
index 721d4a31fe4..1b9ec44ed4a 100644
--- a/app/assets/javascripts/pages/projects/settings/operations/show/index.js
+++ b/app/assets/javascripts/pages/projects/settings/operations/show/index.js
@@ -1,13 +1,17 @@
import mountErrorTrackingForm from '~/error_tracking_settings';
+import mountAlertsSettings from '~/alerts_settings';
import mountOperationSettings from '~/operation_settings';
import mountGrafanaIntegration from '~/grafana_integration';
import initSettingsPanels from '~/settings_panels';
+import initIncidentsSettings from '~/incidents_settings';
document.addEventListener('DOMContentLoaded', () => {
+ initIncidentsSettings();
mountErrorTrackingForm();
mountOperationSettings();
mountGrafanaIntegration();
if (!IS_EE) {
initSettingsPanels();
}
+ mountAlertsSettings(document.querySelector('.js-alerts-settings'));
});
diff --git a/app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue b/app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue
index 7181332a1d6..a95f0af46cd 100644
--- a/app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue
+++ b/app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue
@@ -426,7 +426,7 @@ export default {
v-if="lfsAvailable"
ref="git-lfs-settings"
:help-path="lfsHelpPath"
- :label="s__('ProjectSettings|Git Large File Storage')"
+ :label="s__('ProjectSettings|Git Large File Storage (LFS)')"
:help-text="
s__('ProjectSettings|Manages large files such as audio, video, and graphics files')
"
diff --git a/app/assets/javascripts/pages/projects/show/index.js b/app/assets/javascripts/pages/projects/show/index.js
index 3c44053e2b2..c65cc3e4c57 100644
--- a/app/assets/javascripts/pages/projects/show/index.js
+++ b/app/assets/javascripts/pages/projects/show/index.js
@@ -1,25 +1,18 @@
-import $ from 'jquery';
-import 'jquery.waitforimages';
-
import initBlob from '~/blob_edit/blob_bundle';
import ShortcutsNavigation from '~/behaviors/shortcuts/shortcuts_navigation';
import NotificationsForm from '~/notifications_form';
import UserCallout from '~/user_callout';
-import TreeView from '~/tree';
import BlobViewer from '~/blob/viewer/index';
import Activities from '~/activities';
-import { ajaxGet } from '~/lib/utils/common_utils';
-import GpgBadges from '~/gpg_badges';
import initReadMore from '~/read_more';
import leaveByUrl from '~/namespaces/leave_by_url';
import Star from '../../../star';
import notificationsDropdown from '../../../notifications_dropdown';
-import initNamespaceStorageLimitAlert from '~/namespace_storage_limit_alert';
import { showLearnGitLabProjectPopover } from '~/onboarding_issues';
+import initTree from 'ee_else_ce/repository';
document.addEventListener('DOMContentLoaded', () => {
initReadMore();
- initNamespaceStorageLimitAlert();
new Star(); // eslint-disable-line no-new
notificationsDropdown();
new ShortcutsNavigation(); // eslint-disable-line no-new
@@ -31,10 +24,10 @@ document.addEventListener('DOMContentLoaded', () => {
});
// Project show page loads different overview content based on user preferences
- const treeSlider = document.querySelector('#tree-slider');
+ const treeSlider = document.getElementById('js-tree-list');
if (treeSlider) {
- new TreeView(); // eslint-disable-line no-new
initBlob();
+ initTree();
}
if (document.querySelector('.blob-viewer')) {
@@ -45,21 +38,7 @@ document.addEventListener('DOMContentLoaded', () => {
new Activities(); // eslint-disable-line no-new
}
- $(treeSlider).waitForImages(() => {
- ajaxGet(document.querySelector('.js-tree-content').dataset.logsPath);
- });
-
- GpgBadges.fetch();
leaveByUrl('project');
- if (document.getElementById('js-tree-list')) {
- initBlob();
- import('ee_else_ce/repository')
- .then(m => m.default())
- .catch(e => {
- throw e;
- });
- }
-
showLearnGitLabProjectPopover();
});
diff --git a/app/assets/javascripts/pages/projects/tree/show/index.js b/app/assets/javascripts/pages/projects/tree/show/index.js
index 0d1d32317fe..78a4ea23f1a 100644
--- a/app/assets/javascripts/pages/projects/tree/show/index.js
+++ b/app/assets/javascripts/pages/projects/tree/show/index.js
@@ -1,53 +1,12 @@
import $ from 'jquery';
-import 'jquery.waitforimages';
-
-import Vue from 'vue';
import initBlob from '~/blob_edit/blob_bundle';
-import commitPipelineStatus from '~/projects/tree/components/commit_pipeline_status_component.vue';
-import GpgBadges from '~/gpg_badges';
-import TreeView from '../../../../tree';
import ShortcutsNavigation from '../../../../behaviors/shortcuts/shortcuts_navigation';
-import BlobViewer from '../../../../blob/viewer';
import NewCommitForm from '../../../../new_commit_form';
-import { ajaxGet } from '../../../../lib/utils/common_utils';
+import initTree from 'ee_else_ce/repository';
document.addEventListener('DOMContentLoaded', () => {
new ShortcutsNavigation(); // eslint-disable-line no-new
- new TreeView(); // eslint-disable-line no-new
- new BlobViewer(); // eslint-disable-line no-new
new NewCommitForm($('.js-create-dir-form')); // eslint-disable-line no-new
- $('#tree-slider').waitForImages(() =>
- ajaxGet(document.querySelector('.js-tree-content').dataset.logsPath),
- );
-
initBlob();
- const commitPipelineStatusEl = document.querySelector('.js-commit-pipeline-status');
- const statusLink = document.querySelector('.commit-actions .ci-status-link');
- if (statusLink != null) {
- statusLink.remove();
- // eslint-disable-next-line no-new
- new Vue({
- el: commitPipelineStatusEl,
- components: {
- commitPipelineStatus,
- },
- render(createElement) {
- return createElement('commit-pipeline-status', {
- props: {
- endpoint: commitPipelineStatusEl.dataset.endpoint,
- },
- });
- },
- });
- }
-
- GpgBadges.fetch();
-
- if (document.getElementById('js-tree-list')) {
- import('ee_else_ce/repository')
- .then(m => m.default())
- .catch(e => {
- throw e;
- });
- }
+ initTree();
});
diff --git a/app/assets/javascripts/pages/search/init_filtered_search.js b/app/assets/javascripts/pages/search/init_filtered_search.js
index e54e32199f0..b331a2bee6a 100644
--- a/app/assets/javascripts/pages/search/init_filtered_search.js
+++ b/app/assets/javascripts/pages/search/init_filtered_search.js
@@ -7,6 +7,7 @@ export default ({
isGroupAncestor,
isGroupDecendent,
stateFiltersSelector,
+ anchor,
}) => {
const filteredSearchEnabled = FilteredSearchManager && document.querySelector('.filtered-search');
if (filteredSearchEnabled) {
@@ -17,6 +18,7 @@ export default ({
isGroupDecendent,
filteredSearchTokenKeys,
stateFiltersSelector,
+ anchor,
});
filteredSearchManager.setup();
}
diff --git a/app/assets/javascripts/pages/sessions/new/length_validator.js b/app/assets/javascripts/pages/sessions/new/length_validator.js
index 3d687ca08cc..92482c81f3c 100644
--- a/app/assets/javascripts/pages/sessions/new/length_validator.js
+++ b/app/assets/javascripts/pages/sessions/new/length_validator.js
@@ -21,11 +21,24 @@ export default class LengthValidator extends InputValidator {
);
const { value } = this.inputDomElement;
- const { maxLengthMessage, maxLength } = this.inputDomElement.dataset;
-
- this.errorMessage = maxLengthMessage;
-
- this.invalidInput = value.length > parseInt(maxLength, 10);
+ const {
+ minLength,
+ minLengthMessage,
+ maxLengthMessage,
+ maxLength,
+ } = this.inputDomElement.dataset;
+
+ this.invalidInput = false;
+
+ if (value.length > parseInt(maxLength, 10)) {
+ this.invalidInput = true;
+ this.errorMessage = maxLengthMessage;
+ }
+
+ if (value.length < parseInt(minLength, 10)) {
+ this.invalidInput = true;
+ this.errorMessage = minLengthMessage;
+ }
this.setValidationStateAndMessage();
}
diff --git a/app/assets/javascripts/pages/sessions/new/username_validator.js b/app/assets/javascripts/pages/sessions/new/username_validator.js
index 1048e3b4548..ecb5e677290 100644
--- a/app/assets/javascripts/pages/sessions/new/username_validator.js
+++ b/app/assets/javascripts/pages/sessions/new/username_validator.js
@@ -39,7 +39,7 @@ export default class UsernameValidator extends InputValidator {
static validateUsernameInput(inputDomElement) {
const username = inputDomElement.value;
- if (inputDomElement.checkValidity() && username.length > 0) {
+ if (inputDomElement.checkValidity() && username.length > 1) {
UsernameValidator.setMessageVisibility(inputDomElement, pendingMessageSelector);
UsernameValidator.fetchUsernameAvailability(username)
.then(usernameTaken => {
diff --git a/app/assets/javascripts/pages/shared/wikis/components/delete_wiki_modal.vue b/app/assets/javascripts/pages/shared/wikis/components/delete_wiki_modal.vue
index 580cca49b5e..a7b7d597fb7 100644
--- a/app/assets/javascripts/pages/shared/wikis/components/delete_wiki_modal.vue
+++ b/app/assets/javascripts/pages/shared/wikis/components/delete_wiki_modal.vue
@@ -55,13 +55,22 @@ export default {
<template>
<div class="d-inline-block">
- <button v-gl-modal="modalId" type="button" class="btn btn-danger">{{ __('Delete') }}</button>
+ <button
+ v-gl-modal="modalId"
+ type="button"
+ class="btn btn-danger"
+ data-qa-selector="delete_button"
+ >
+ {{ __('Delete') }}
+ </button>
<gl-modal
:title="title"
- :ok-title="s__('WikiPageConfirmDelete|Delete page')"
+ :action-primary="{
+ text: s__('WikiPageConfirmDelete|Delete page'),
+ attributes: { variant: 'danger', 'data-qa-selector': 'confirm_deletion_button' },
+ }"
:modal-id="modalId"
title-tag="h4"
- ok-variant="danger"
@ok="onSubmit"
>
{{ message }}
diff --git a/app/assets/javascripts/pages/shared/wikis/wikis.js b/app/assets/javascripts/pages/shared/wikis/wikis.js
index ed67219383b..41d43812b5d 100644
--- a/app/assets/javascripts/pages/shared/wikis/wikis.js
+++ b/app/assets/javascripts/pages/shared/wikis/wikis.js
@@ -1,5 +1,6 @@
import { GlBreakpointInstance as bp } from '@gitlab/ui/dist/utils';
import { s__, sprintf } from '~/locale';
+import Tracking from '~/tracking';
const MARKDOWN_LINK_TEXT = {
markdown: '[Link Title](page-slug)',
@@ -8,6 +9,9 @@ const MARKDOWN_LINK_TEXT = {
org: '[[page-slug]]',
};
+const TRACKING_EVENT_NAME = 'view_wiki_page';
+const TRACKING_CONTEXT_SCHEMA = 'iglu:com.gitlab/wiki_page_context/jsonschema/1-0-0';
+
export default class Wikis {
constructor() {
this.sidebarEl = document.querySelector('.js-wiki-sidebar');
@@ -57,6 +61,8 @@ export default class Wikis {
window.onbeforeunload = null;
});
}
+
+ Wikis.trackPageView();
}
handleWikiTitleChange(e) {
@@ -97,4 +103,17 @@ export default class Wikis {
classList.remove('right-sidebar-expanded');
}
}
+
+ static trackPageView() {
+ const wikiPageContent = document.querySelector('.js-wiki-page-content[data-tracking-context]');
+ if (!wikiPageContent) return;
+
+ Tracking.event(document.body.dataset.page, TRACKING_EVENT_NAME, {
+ label: TRACKING_EVENT_NAME,
+ context: {
+ schema: TRACKING_CONTEXT_SCHEMA,
+ data: JSON.parse(wikiPageContent.dataset.trackingContext),
+ },
+ });
+ }
}
diff --git a/app/assets/javascripts/performance_bar/components/request_selector.vue b/app/assets/javascripts/performance_bar/components/request_selector.vue
index 115b2ff08ac..c22a648d17f 100644
--- a/app/assets/javascripts/performance_bar/components/request_selector.vue
+++ b/app/assets/javascripts/performance_bar/components/request_selector.vue
@@ -45,7 +45,7 @@ export default {
};
</script>
<template>
- <div id="peek-request-selector" data-qa-selector="request_dropdown">
+ <div id="peek-request-selector" data-qa-selector="request_dropdown" class="view">
<select v-model="currentRequestId">
<option
v-for="request in requests"
diff --git a/app/assets/javascripts/persistent_user_callout.js b/app/assets/javascripts/persistent_user_callout.js
index b3068c46bcb..b8a1397d8f6 100644
--- a/app/assets/javascripts/persistent_user_callout.js
+++ b/app/assets/javascripts/persistent_user_callout.js
@@ -18,17 +18,21 @@ export default class PersistentUserCallout {
init() {
const closeButton = this.container.querySelector('.js-close');
+ const followLink = this.container.querySelector('.js-follow-link');
- if (!closeButton) {
- return;
+ if (closeButton) {
+ this.handleCloseButtonCallout(closeButton);
+ } else if (followLink) {
+ this.handleFollowLinkCallout(followLink);
}
+ }
+ handleCloseButtonCallout(closeButton) {
closeButton.addEventListener('click', event => this.dismiss(event));
if (this.deferLinks) {
this.container.addEventListener('click', event => {
const isDeferredLink = event.target.classList.contains(DEFERRED_LINK_CLASS);
-
if (isDeferredLink) {
const { href, target } = event.target;
@@ -38,6 +42,10 @@ export default class PersistentUserCallout {
}
}
+ handleFollowLinkCallout(followLink) {
+ followLink.addEventListener('click', event => this.registerCalloutWithLink(event));
+ }
+
dismiss(event, deferredLinkOptions = null) {
event.preventDefault();
@@ -58,6 +66,27 @@ export default class PersistentUserCallout {
});
}
+ registerCalloutWithLink(event) {
+ event.preventDefault();
+
+ const { href } = event.currentTarget;
+
+ axios
+ .post(this.dismissEndpoint, {
+ feature_name: this.featureId,
+ })
+ .then(() => {
+ window.location.assign(href);
+ })
+ .catch(() => {
+ Flash(
+ __(
+ 'An error occurred while acknowledging the notification. Refresh the page and try again.',
+ ),
+ );
+ });
+ }
+
static factory(container, options) {
if (!container) {
return undefined;
diff --git a/app/assets/javascripts/persistent_user_callouts.js b/app/assets/javascripts/persistent_user_callouts.js
index 6e292299778..f4fe605f0a2 100644
--- a/app/assets/javascripts/persistent_user_callouts.js
+++ b/app/assets/javascripts/persistent_user_callouts.js
@@ -4,6 +4,9 @@ const PERSISTENT_USER_CALLOUTS = [
'.js-recovery-settings-callout',
'.js-users-over-license-callout',
'.js-admin-licensed-user-count-threshold',
+ '.js-buy-pipeline-minutes-notification-callout',
+ '.js-alerts-moved-alert',
+ '.js-token-expiry-callout',
];
const initCallouts = () => {
diff --git a/app/assets/javascripts/pipelines/components/dag/constants.js b/app/assets/javascripts/pipelines/components/dag/constants.js
index 51b1fb4f4cc..b6a98fdc488 100644
--- a/app/assets/javascripts/pipelines/components/dag/constants.js
+++ b/app/assets/javascripts/pipelines/components/dag/constants.js
@@ -8,3 +8,8 @@ export const DEFAULT = 'default';
export const IS_HIGHLIGHTED = 'dag-highlighted';
export const LINK_SELECTOR = 'dag-link';
export const NODE_SELECTOR = 'dag-node';
+
+/* Annotation types */
+export const ADD_NOTE = 'add';
+export const REMOVE_NOTE = 'remove';
+export const REPLACE_NOTES = 'replace';
diff --git a/app/assets/javascripts/pipelines/components/dag/dag.vue b/app/assets/javascripts/pipelines/components/dag/dag.vue
index 6e0d23ef87f..85163a666e2 100644
--- a/app/assets/javascripts/pipelines/components/dag/dag.vue
+++ b/app/assets/javascripts/pipelines/components/dag/dag.vue
@@ -1,19 +1,32 @@
<script>
-import { GlAlert, GlLink, GlSprintf } from '@gitlab/ui';
+import { GlAlert, GlButton, GlEmptyState, GlLink, GlSprintf } from '@gitlab/ui';
+import { isEmpty } from 'lodash';
import axios from '~/lib/utils/axios_utils';
import { __ } from '~/locale';
import DagGraph from './dag_graph.vue';
-import { DEFAULT, PARSE_FAILURE, LOAD_FAILURE, UNSUPPORTED_DATA } from './constants';
+import DagAnnotations from './dag_annotations.vue';
+import {
+ DEFAULT,
+ PARSE_FAILURE,
+ LOAD_FAILURE,
+ UNSUPPORTED_DATA,
+ ADD_NOTE,
+ REMOVE_NOTE,
+ REPLACE_NOTES,
+} from './constants';
import { parseData } from './parsing_utils';
export default {
// eslint-disable-next-line @gitlab/require-i18n-strings
name: 'Dag',
components: {
+ DagAnnotations,
DagGraph,
GlAlert,
GlLink,
GlSprintf,
+ GlEmptyState,
+ GlButton,
},
props: {
graphUrl: {
@@ -21,21 +34,43 @@ export default {
required: false,
default: '',
},
+ emptySvgPath: {
+ type: String,
+ required: true,
+ default: '',
+ },
+ dagDocPath: {
+ type: String,
+ required: true,
+ default: '',
+ },
},
data() {
return {
- showFailureAlert: false,
- showBetaInfo: true,
+ annotationsMap: {},
failureType: null,
graphData: null,
+ showFailureAlert: false,
+ showBetaInfo: true,
+ hasNoDependentJobs: false,
};
},
errorTexts: {
[LOAD_FAILURE]: __('We are currently unable to fetch data for this graph.'),
[PARSE_FAILURE]: __('There was an error parsing the data for this graph.'),
- [UNSUPPORTED_DATA]: __('A DAG must have two dependent jobs to be visualized on this tab.'),
+ [UNSUPPORTED_DATA]: __('DAG visualization requires at least 3 dependent jobs.'),
[DEFAULT]: __('An unknown error occurred while loading this graph.'),
},
+ emptyStateTexts: {
+ title: __('Start using Directed Acyclic Graphs (DAG)'),
+ firstDescription: __(
+ "This pipeline does not use the %{codeStart}needs%{codeEnd} keyword and can't be represented as a directed acyclic graph.",
+ ),
+ secondDescription: __(
+ 'Using %{codeStart}needs%{codeEnd} allows jobs to run before their stage is reached, as soon as their individual dependencies are met, which speeds up your pipelines.',
+ ),
+ button: __('Learn more about job dependencies'),
+ },
computed: {
betaMessage() {
return __(
@@ -66,6 +101,9 @@ export default {
};
}
},
+ shouldDisplayAnnotations() {
+ return !isEmpty(this.annotationsMap);
+ },
shouldDisplayGraph() {
return Boolean(!this.showFailureAlert && this.graphData);
},
@@ -86,6 +124,9 @@ export default {
.catch(() => reportFailure(LOAD_FAILURE));
},
methods: {
+ addAnnotationToMap({ uid, source, target }) {
+ this.$set(this.annotationsMap, uid, { source, target });
+ },
processGraphData(data) {
let parsed;
@@ -96,11 +137,18 @@ export default {
return;
}
- if (parsed.links.length < 2) {
+ if (parsed.links.length === 1) {
this.reportFailure(UNSUPPORTED_DATA);
return;
}
+ // If there are no links, we don't report failure
+ // as it simply means the user does not use job dependencies
+ if (parsed.links.length === 0) {
+ this.hasNoDependentJobs = true;
+ return;
+ }
+
this.graphData = parsed;
},
hideAlert() {
@@ -109,10 +157,28 @@ export default {
hideBetaInfo() {
this.showBetaInfo = false;
},
+ removeAnnotationFromMap({ uid }) {
+ this.$delete(this.annotationsMap, uid);
+ },
reportFailure(type) {
this.showFailureAlert = true;
this.failureType = type;
},
+ updateAnnotation({ type, data }) {
+ switch (type) {
+ case ADD_NOTE:
+ this.addAnnotationToMap(data);
+ break;
+ case REMOVE_NOTE:
+ this.removeAnnotationFromMap(data);
+ break;
+ case REPLACE_NOTES:
+ this.annotationsMap = data;
+ break;
+ default:
+ break;
+ }
+ },
},
};
</script>
@@ -131,6 +197,43 @@ export default {
</template>
</gl-sprintf>
</gl-alert>
- <dag-graph v-if="shouldDisplayGraph" :graph-data="graphData" @onFailure="reportFailure" />
+ <div class="gl-relative">
+ <dag-annotations v-if="shouldDisplayAnnotations" :annotations="annotationsMap" />
+ <dag-graph
+ v-if="shouldDisplayGraph"
+ :graph-data="graphData"
+ @onFailure="reportFailure"
+ @update-annotation="updateAnnotation"
+ />
+ <gl-empty-state
+ v-else-if="hasNoDependentJobs"
+ :svg-path="emptySvgPath"
+ :title="$options.emptyStateTexts.title"
+ >
+ <template #description>
+ <div class="gl-text-left">
+ <p>
+ <gl-sprintf :message="$options.emptyStateTexts.firstDescription">
+ <template #code="{ content }">
+ <code>{{ content }}</code>
+ </template>
+ </gl-sprintf>
+ </p>
+ <p>
+ <gl-sprintf :message="$options.emptyStateTexts.secondDescription">
+ <template #code="{ content }">
+ <code>{{ content }}</code>
+ </template>
+ </gl-sprintf>
+ </p>
+ </div>
+ </template>
+ <template #actions>
+ <gl-button :href="dagDocPath" target="__blank" variant="success">
+ {{ $options.emptyStateTexts.button }}
+ </gl-button>
+ </template>
+ </gl-empty-state>
+ </div>
</div>
</template>
diff --git a/app/assets/javascripts/pipelines/components/dag/dag_annotations.vue b/app/assets/javascripts/pipelines/components/dag/dag_annotations.vue
new file mode 100644
index 00000000000..a1500166cdc
--- /dev/null
+++ b/app/assets/javascripts/pipelines/components/dag/dag_annotations.vue
@@ -0,0 +1,73 @@
+<script>
+import { GlButton } from '@gitlab/ui';
+import { __ } from '~/locale';
+
+export default {
+ name: 'DagAnnotations',
+ components: {
+ GlButton,
+ },
+ props: {
+ annotations: {
+ type: Object,
+ required: true,
+ },
+ },
+ data() {
+ return {
+ showList: true,
+ };
+ },
+ computed: {
+ linkText() {
+ return this.showList ? __('Hide list') : __('Show list');
+ },
+ shouldShowLink() {
+ return Object.keys(this.annotations).length > 1;
+ },
+ wrapperClasses() {
+ return [
+ 'gl-display-flex',
+ 'gl-flex-direction-column',
+ 'gl-fixed',
+ 'gl-right-1',
+ 'gl-top-66vh',
+ 'gl-w-max-content',
+ 'gl-px-5',
+ 'gl-py-4',
+ 'gl-rounded-base',
+ 'gl-bg-white',
+ ].join(' ');
+ },
+ },
+ methods: {
+ toggleList() {
+ this.showList = !this.showList;
+ },
+ },
+};
+</script>
+<template>
+ <div :class="wrapperClasses">
+ <div v-if="showList">
+ <div
+ v-for="note in annotations"
+ :key="note.uid"
+ class="gl-display-flex gl-align-items-center"
+ >
+ <div
+ data-testid="dag-color-block"
+ class="gl-w-6 gl-h-5"
+ :style="{
+ background: `linear-gradient(0.25turn, ${note.source.color} 40%, ${note.target.color} 60%)`,
+ }"
+ ></div>
+ <div data-testid="dag-note-text" class="gl-px-2 gl-font-base gl-align-items-center">
+ {{ note.source.name }} → {{ note.target.name }}
+ </div>
+ </div>
+ </div>
+
+ <gl-button v-if="shouldShowLink" variant="link" @click="toggleList">{{ linkText }}</gl-button>
+ </div>
+</template>
diff --git a/app/assets/javascripts/pipelines/components/dag/dag_graph.vue b/app/assets/javascripts/pipelines/components/dag/dag_graph.vue
index 063ec091e4d..d12baa9617e 100644
--- a/app/assets/javascripts/pipelines/components/dag/dag_graph.vue
+++ b/app/assets/javascripts/pipelines/components/dag/dag_graph.vue
@@ -1,8 +1,17 @@
<script>
import * as d3 from 'd3';
import { uniqueId } from 'lodash';
-import { LINK_SELECTOR, NODE_SELECTOR, PARSE_FAILURE } from './constants';
import {
+ LINK_SELECTOR,
+ NODE_SELECTOR,
+ PARSE_FAILURE,
+ ADD_NOTE,
+ REMOVE_NOTE,
+ REPLACE_NOTES,
+} from './constants';
+import {
+ currentIsLive,
+ getLiveLinksAsDict,
highlightLinks,
restoreLinks,
toggleLinkHighlight,
@@ -25,6 +34,11 @@ export default {
containerClasses: ['dag-graph-container', 'gl-display-flex', 'gl-flex-direction-column'].join(
' ',
),
+ hoverFadeClasses: [
+ 'gl-cursor-pointer',
+ 'gl-transition-duration-slow',
+ 'gl-transition-timing-function-ease',
+ ].join(' '),
},
gitLabColorRotation: [
'#e17223',
@@ -50,8 +64,8 @@ export default {
data() {
return {
color: () => {},
- width: 0,
height: 0,
+ width: 0,
};
},
mounted() {
@@ -60,7 +74,7 @@ export default {
try {
countedAndTransformed = this.transformData(this.graphData);
} catch {
- this.$emit('onFailure', PARSE_FAILURE);
+ this.$emit('on-failure', PARSE_FAILURE);
return;
}
@@ -90,17 +104,33 @@ export default {
},
appendLinkInteractions(link) {
+ const { baseOpacity } = this.$options.viewOptions;
return link
- .on('mouseover', highlightLinks)
- .on('mouseout', restoreLinks.bind(null, this.$options.viewOptions.baseOpacity))
- .on('click', toggleLinkHighlight.bind(null, this.$options.viewOptions.baseOpacity));
+ .on('mouseover', (d, idx, collection) => {
+ if (currentIsLive(idx, collection)) {
+ return;
+ }
+ this.$emit('update-annotation', { type: ADD_NOTE, data: d });
+ highlightLinks(d, idx, collection);
+ })
+ .on('mouseout', (d, idx, collection) => {
+ if (currentIsLive(idx, collection)) {
+ return;
+ }
+ this.$emit('update-annotation', { type: REMOVE_NOTE, data: d });
+ restoreLinks(baseOpacity);
+ })
+ .on('click', (d, idx, collection) => {
+ toggleLinkHighlight(baseOpacity, d, idx, collection);
+ this.$emit('update-annotation', { type: REPLACE_NOTES, data: getLiveLinksAsDict() });
+ });
},
appendNodeInteractions(node) {
- return node.on(
- 'click',
- togglePathHighlights.bind(null, this.$options.viewOptions.baseOpacity),
- );
+ return node.on('click', (d, idx, collection) => {
+ togglePathHighlights(this.$options.viewOptions.baseOpacity, d, idx, collection);
+ this.$emit('update-annotation', { type: REPLACE_NOTES, data: getLiveLinksAsDict() });
+ });
},
appendLabelAsForeignObject(d, i, n) {
@@ -230,7 +260,10 @@ export default {
.attr('id', d => {
return this.createAndAssignId(d, 'uid', LINK_SELECTOR);
})
- .classed(`${LINK_SELECTOR} gl-cursor-pointer`, true);
+ .classed(
+ `${LINK_SELECTOR} gl-transition-property-stroke-opacity ${this.$options.viewOptions.hoverFadeClasses}`,
+ true,
+ );
},
generateNodes(svg, nodeData) {
@@ -242,7 +275,10 @@ export default {
.data(nodeData)
.enter()
.append('line')
- .classed(`${NODE_SELECTOR} gl-cursor-pointer`, true)
+ .classed(
+ `${NODE_SELECTOR} gl-transition-property-stroke ${this.$options.viewOptions.hoverFadeClasses}`,
+ true,
+ )
.attr('id', d => {
return this.createAndAssignId(d, 'uid', NODE_SELECTOR);
})
@@ -260,6 +296,11 @@ export default {
.attr('y2', d => d.y1 - 4);
},
+ initColors() {
+ const colorFn = d3.scaleOrdinal(this.$options.gitLabColorRotation);
+ return ({ name }) => colorFn(name);
+ },
+
labelNodes(svg, nodeData) {
return svg
.append('g')
@@ -271,11 +312,6 @@ export default {
.each(this.appendLabelAsForeignObject);
},
- initColors() {
- const colorFn = d3.scaleOrdinal(this.$options.gitLabColorRotation);
- return ({ name }) => colorFn(name);
- },
-
transformData(parsed) {
const baseLayout = createSankey()(parsed);
const cleanedNodes = removeOrphanNodes(baseLayout.nodes);
diff --git a/app/assets/javascripts/pipelines/components/dag/interactions.js b/app/assets/javascripts/pipelines/components/dag/interactions.js
index c9008730c90..e9f3e9f0e2c 100644
--- a/app/assets/javascripts/pipelines/components/dag/interactions.js
+++ b/app/assets/javascripts/pipelines/components/dag/interactions.js
@@ -5,10 +5,20 @@ export const highlightIn = 1;
export const highlightOut = 0.2;
const getCurrent = (idx, collection) => d3.select(collection[idx]);
-const currentIsLive = (idx, collection) => getCurrent(idx, collection).classed(IS_HIGHLIGHTED);
+const getLiveLinks = () => d3.selectAll(`.${LINK_SELECTOR}.${IS_HIGHLIGHTED}`);
const getOtherLinks = () => d3.selectAll(`.${LINK_SELECTOR}:not(.${IS_HIGHLIGHTED})`);
const getNodesNotLive = () => d3.selectAll(`.${NODE_SELECTOR}:not(.${IS_HIGHLIGHTED})`);
+export const getLiveLinksAsDict = () => {
+ return Object.fromEntries(
+ getLiveLinks()
+ .data()
+ .map(d => [d.uid, d]),
+ );
+};
+export const currentIsLive = (idx, collection) =>
+ getCurrent(idx, collection).classed(IS_HIGHLIGHTED);
+
const backgroundLinks = selection => selection.style('stroke-opacity', highlightOut);
const backgroundNodes = selection => selection.attr('stroke', '#f2f2f2');
const foregroundLinks = selection => selection.style('stroke-opacity', highlightIn);
@@ -16,10 +26,10 @@ const foregroundNodes = selection => selection.attr('stroke', d => d.color);
const renewLinks = (selection, baseOpacity) => selection.style('stroke-opacity', baseOpacity);
const renewNodes = selection => selection.attr('stroke', d => d.color);
-const getAllLinkAncestors = node => {
+export const getAllLinkAncestors = node => {
if (node.targetLinks) {
return node.targetLinks.flatMap(n => {
- return [n.uid, ...getAllLinkAncestors(n.source)];
+ return [n, ...getAllLinkAncestors(n.source)];
});
}
@@ -59,8 +69,8 @@ const highlightPath = (parentLinks, parentNodes) => {
backgroundNodes(getNodesNotLive());
/* highlight correct links */
- parentLinks.forEach(id => {
- foregroundLinks(d3.select(`#${id}`)).classed(IS_HIGHLIGHTED, true);
+ parentLinks.forEach(({ uid }) => {
+ foregroundLinks(d3.select(`#${uid}`)).classed(IS_HIGHLIGHTED, true);
});
/* highlight correct nodes */
@@ -69,9 +79,22 @@ const highlightPath = (parentLinks, parentNodes) => {
});
};
+const restoreNodes = () => {
+ /*
+ When paths are unclicked, they can take down nodes that
+ are still in use for other paths. This checks the live paths and
+ rehighlights their nodes.
+ */
+
+ getLiveLinks().each(d => {
+ foregroundNodes(d3.select(`#${d.source.uid}`)).classed(IS_HIGHLIGHTED, true);
+ foregroundNodes(d3.select(`#${d.target.uid}`)).classed(IS_HIGHLIGHTED, true);
+ });
+};
+
const restorePath = (parentLinks, parentNodes, baseOpacity) => {
- parentLinks.forEach(id => {
- renewLinks(d3.select(`#${id}`), baseOpacity).classed(IS_HIGHLIGHTED, false);
+ parentLinks.forEach(({ uid }) => {
+ renewLinks(d3.select(`#${uid}`), baseOpacity).classed(IS_HIGHLIGHTED, false);
});
parentNodes.forEach(id => {
@@ -86,14 +109,10 @@ const restorePath = (parentLinks, parentNodes, baseOpacity) => {
backgroundLinks(getOtherLinks());
backgroundNodes(getNodesNotLive());
+ restoreNodes();
};
-export const restoreLinks = (baseOpacity, d, idx, collection) => {
- /* in this case, it has just been clicked */
- if (currentIsLive(idx, collection)) {
- return;
- }
-
+export const restoreLinks = baseOpacity => {
/*
if there exist live links, reset to highlight out / pale
otherwise, reset to base
@@ -111,11 +130,12 @@ export const restoreLinks = (baseOpacity, d, idx, collection) => {
export const toggleLinkHighlight = (baseOpacity, d, idx, collection) => {
if (currentIsLive(idx, collection)) {
- restorePath([d.uid], [d.source.uid, d.target.uid], baseOpacity);
+ restorePath([d], [d.source.uid, d.target.uid], baseOpacity);
+ restoreNodes();
return;
}
- highlightPath([d.uid], [d.source.uid, d.target.uid]);
+ highlightPath([d], [d.source.uid, d.target.uid]);
};
export const togglePathHighlights = (baseOpacity, d, idx, collection) => {
diff --git a/app/assets/javascripts/pipelines/components/graph/graph_component.vue b/app/assets/javascripts/pipelines/components/graph/graph_component.vue
index 1ff5b662d18..6b890688a48 100644
--- a/app/assets/javascripts/pipelines/components/graph/graph_component.vue
+++ b/app/assets/javascripts/pipelines/components/graph/graph_component.vue
@@ -43,6 +43,7 @@ export default {
data() {
return {
downstreamMarginTop: null,
+ jobName: null,
};
},
computed: {
@@ -91,13 +92,9 @@ export default {
/**
* Calculates the margin top of the clicked downstream pipeline by
* subtracting the clicked downstream pipelines offsetTop by it's parent's
- * offsetTop and then subtracting either 15 (if child) or 30 (if not a child)
- * due to the height of node and stage name margin bottom.
+ * offsetTop and then subtracting 15
*/
- this.downstreamMarginTop = this.calculateMarginTop(
- downstreamNode,
- downstreamNode.classList.contains('child-pipeline') ? 15 : 30,
- );
+ this.downstreamMarginTop = this.calculateMarginTop(downstreamNode, 15);
/**
* If the expanded trigger is defined and the id is different than the
@@ -120,6 +117,9 @@ export default {
hasUpstream(index) {
return index === 0 && this.hasTriggeredBy;
},
+ setJob(jobName) {
+ this.jobName = jobName;
+ },
},
};
</script>
@@ -172,7 +172,7 @@ export default {
:class="{
'has-upstream prepend-left-64': hasUpstream(index),
'has-only-one-job': hasOnlyOneJob(stage),
- 'append-right-46': shouldAddRightMargin(index),
+ 'gl-mr-26': shouldAddRightMargin(index),
}"
:title="capitalizeStageName(stage.name)"
:groups="stage.groups"
@@ -180,6 +180,7 @@ export default {
:is-first-column="isFirstColumn(index)"
:has-triggered-by="hasTriggeredBy"
:action="stage.status.action"
+ :job-hovered="jobName"
@refreshPipelineGraph="refreshPipelineGraph"
/>
</ul>
@@ -191,6 +192,7 @@ export default {
:project-id="pipelineProjectId"
graph-position="right"
@linkedPipelineClick="handleClickedDownstream"
+ @downstreamHovered="setJob"
/>
<pipeline-graph
diff --git a/app/assets/javascripts/pipelines/components/graph/job_item.vue b/app/assets/javascripts/pipelines/components/graph/job_item.vue
index bfd314e0439..4d72cc55b34 100644
--- a/app/assets/javascripts/pipelines/components/graph/job_item.vue
+++ b/app/assets/javascripts/pipelines/components/graph/job_item.vue
@@ -31,6 +31,7 @@ import delayedJobMixin from '~/jobs/mixins/delayed_job_mixin';
*/
export default {
+ hoverClass: 'gl-inset-border-1-blue-500',
components: {
ActionComponent,
JobNameComponent,
@@ -55,6 +56,11 @@ export default {
required: false,
default: Infinity,
},
+ jobHovered: {
+ type: String,
+ required: false,
+ default: '',
+ },
},
computed: {
boundary() {
@@ -95,6 +101,11 @@ export default {
hasAction() {
return this.job.status && this.job.status.action && this.job.status.action.path;
},
+ jobClasses() {
+ return this.job.name === this.jobHovered
+ ? `${this.$options.hoverClass} ${this.cssClassJobName}`
+ : this.cssClassJobName;
+ },
},
methods: {
pipelineActionRequestComplete() {
@@ -120,8 +131,9 @@ export default {
v-else
v-gl-tooltip="{ boundary, placement: 'bottom' }"
:title="tooltipText"
- :class="cssClassJobName"
+ :class="jobClasses"
class="js-job-component-tooltip non-details-job-component"
+ data-testid="job-without-link"
>
<job-name-component :name="job.name" :status="job.status" />
</div>
diff --git a/app/assets/javascripts/pipelines/components/graph/linked_pipeline.vue b/app/assets/javascripts/pipelines/components/graph/linked_pipeline.vue
index 550b9daa521..733553e02c0 100644
--- a/app/assets/javascripts/pipelines/components/graph/linked_pipeline.vue
+++ b/app/assets/javascripts/pipelines/components/graph/linked_pipeline.vue
@@ -1,7 +1,7 @@
<script>
import { GlLoadingIcon, GlTooltipDirective, GlDeprecatedButton } from '@gitlab/ui';
import CiStatus from '~/vue_shared/components/ci_icon.vue';
-import { __ } from '~/locale';
+import { __, sprintf } from '~/locale';
export default {
directives: {
@@ -28,7 +28,8 @@ export default {
},
computed: {
tooltipText() {
- return `${this.projectName} - ${this.pipelineStatus.label}`;
+ return `${this.downstreamTitle} #${this.pipeline.id} - ${this.pipelineStatus.label}
+ ${this.sourceJobInfo}`;
},
buttonId() {
return `js-linked-pipeline-${this.pipeline.id}`;
@@ -39,25 +40,32 @@ export default {
projectName() {
return this.pipeline.project.name;
},
+ downstreamTitle() {
+ return this.childPipeline ? __('child-pipeline') : this.pipeline.project.name;
+ },
parentPipeline() {
// Refactor string match when BE returns Upstream/Downstream indicators
return this.projectId === this.pipeline.project.id && this.columnTitle === __('Upstream');
},
childPipeline() {
// Refactor string match when BE returns Upstream/Downstream indicators
- return this.projectId === this.pipeline.project.id && this.columnTitle === __('Downstream');
+ return this.projectId === this.pipeline.project.id && this.isDownstream;
},
label() {
- return this.parentPipeline ? __('Parent') : __('Child');
- },
- childTooltipText() {
- return __('This pipeline was triggered by a parent pipeline');
+ if (this.parentPipeline) {
+ return __('Parent');
+ } else if (this.childPipeline) {
+ return __('Child');
+ }
+ return __('Multi-project');
},
- parentTooltipText() {
- return __('This pipeline triggered a child pipeline');
+ isDownstream() {
+ return this.columnTitle === __('Downstream');
},
- labelToolTipText() {
- return this.label === __('Parent') ? this.parentTooltipText : this.childTooltipText;
+ sourceJobInfo() {
+ return this.isDownstream
+ ? sprintf(__('Created by %{job}'), { job: this.pipeline.source_job.name })
+ : '';
},
},
methods: {
@@ -68,6 +76,12 @@ export default {
hideTooltips() {
this.$root.$emit('bv::hide::tooltip');
},
+ onDownstreamHovered() {
+ this.$emit('downstreamHovered', this.pipeline.source_job.name);
+ },
+ onDownstreamHoverLeave() {
+ this.$emit('downstreamHovered', '');
+ },
},
};
</script>
@@ -76,7 +90,10 @@ export default {
<li
ref="linkedPipeline"
class="linked-pipeline build"
- :class="{ 'child-pipeline': childPipeline }"
+ :class="{ 'downstream-pipeline': isDownstream }"
+ data-qa-selector="child_pipeline"
+ @mouseover="onDownstreamHovered"
+ @mouseleave="onDownstreamHoverLeave"
>
<gl-deprecated-button
:id="buttonId"
@@ -94,15 +111,9 @@ export default {
css-classes="position-top-0"
class="js-linked-pipeline-status"
/>
- <span class="str-truncated align-bottom"> {{ projectName }} &#8226; #{{ pipeline.id }} </span>
- <div v-if="parentPipeline || childPipeline" class="parent-child-label-container">
- <span
- v-gl-tooltip.bottom
- :title="labelToolTipText"
- class="badge badge-primary"
- @mouseover="hideTooltips"
- >{{ label }}</span
- >
+ <span class="str-truncated"> {{ downstreamTitle }} &#8226; #{{ pipeline.id }} </span>
+ <div class="gl-pt-2">
+ <span class="badge badge-primary" data-testid="downstream-pipeline-label">{{ label }}</span>
</div>
</gl-deprecated-button>
</li>
diff --git a/app/assets/javascripts/pipelines/components/graph/linked_pipelines_column.vue b/app/assets/javascripts/pipelines/components/graph/linked_pipelines_column.vue
index e3429184c05..c4dfd3382a2 100644
--- a/app/assets/javascripts/pipelines/components/graph/linked_pipelines_column.vue
+++ b/app/assets/javascripts/pipelines/components/graph/linked_pipelines_column.vue
@@ -28,7 +28,7 @@ export default {
columnClass() {
const positionValues = {
right: 'prepend-left-64',
- left: 'append-right-32',
+ left: 'gl-mr-7',
};
return `graph-position-${this.graphPosition} ${positionValues[this.graphPosition]}`;
},
@@ -41,6 +41,9 @@ export default {
onPipelineClick(downstreamNode, pipeline, index) {
this.$emit('linkedPipelineClick', pipeline, index, downstreamNode);
},
+ onDownstreamHovered(jobName) {
+ this.$emit('downstreamHovered', jobName);
+ },
},
};
</script>
@@ -61,6 +64,7 @@ export default {
:column-title="columnTitle"
:project-id="projectId"
@pipelineClicked="onPipelineClick($event, pipeline, index)"
+ @downstreamHovered="onDownstreamHovered"
/>
</ul>
</div>
diff --git a/app/assets/javascripts/pipelines/components/graph/stage_column_component.vue b/app/assets/javascripts/pipelines/components/graph/stage_column_component.vue
index bed0ed51d5f..9de6ba819c2 100644
--- a/app/assets/javascripts/pipelines/components/graph/stage_column_component.vue
+++ b/app/assets/javascripts/pipelines/components/graph/stage_column_component.vue
@@ -36,6 +36,11 @@ export default {
required: false,
default: () => ({}),
},
+ jobHovered: {
+ type: String,
+ required: false,
+ default: '',
+ },
},
computed: {
hasAction() {
@@ -80,6 +85,7 @@ export default {
<job-item
v-if="group.size === 1"
:job="group.jobs[0]"
+ :job-hovered="jobHovered"
css-class-job-name="build-content"
@pipelineActionRequestComplete="pipelineActionRequestComplete"
/>
diff --git a/app/assets/javascripts/pipelines/components/header_component.vue b/app/assets/javascripts/pipelines/components/header_component.vue
index e7777d0d3af..dff642161db 100644
--- a/app/assets/javascripts/pipelines/components/header_component.vue
+++ b/app/assets/javascripts/pipelines/components/header_component.vue
@@ -108,7 +108,7 @@ export default {
/>
</ci-header>
- <gl-loading-icon v-if="isLoading" size="lg" class="prepend-top-default append-bottom-default" />
+ <gl-loading-icon v-if="isLoading" size="lg" class="gl-mt-3 gl-mb-3" />
<gl-modal
:modal-id="$options.DELETE_MODAL_ID"
diff --git a/app/assets/javascripts/pipelines/components/nav_controls.vue b/app/assets/javascripts/pipelines/components/nav_controls.vue
deleted file mode 100644
index 4f6c9d2bd90..00000000000
--- a/app/assets/javascripts/pipelines/components/nav_controls.vue
+++ /dev/null
@@ -1,66 +0,0 @@
-<script>
-import { GlDeprecatedButton } from '@gitlab/ui';
-import LoadingButton from '../../vue_shared/components/loading_button.vue';
-
-export default {
- name: 'PipelineNavControls',
- components: {
- LoadingButton,
- GlDeprecatedButton,
- },
- props: {
- newPipelinePath: {
- type: String,
- required: false,
- default: null,
- },
-
- resetCachePath: {
- type: String,
- required: false,
- default: null,
- },
-
- ciLintPath: {
- type: String,
- required: false,
- default: null,
- },
-
- isResetCacheButtonLoading: {
- type: Boolean,
- required: false,
- default: false,
- },
- },
- methods: {
- onClickResetCache() {
- this.$emit('resetRunnersCache', this.resetCachePath);
- },
- },
-};
-</script>
-<template>
- <div class="nav-controls">
- <gl-deprecated-button
- v-if="newPipelinePath"
- :href="newPipelinePath"
- variant="success"
- class="js-run-pipeline"
- >
- {{ s__('Pipelines|Run Pipeline') }}
- </gl-deprecated-button>
-
- <loading-button
- v-if="resetCachePath"
- :loading="isResetCacheButtonLoading"
- :label="s__('Pipelines|Clear Runner Caches')"
- class="js-clear-cache"
- @click="onClickResetCache"
- />
-
- <gl-deprecated-button v-if="ciLintPath" :href="ciLintPath" class="js-ci-lint">
- {{ s__('Pipelines|CI Lint') }}
- </gl-deprecated-button>
- </div>
-</template>
diff --git a/app/assets/javascripts/pipelines/components/pipeline_triggerer.vue b/app/assets/javascripts/pipelines/components/pipeline_triggerer.vue
deleted file mode 100644
index 740b54cd8e0..00000000000
--- a/app/assets/javascripts/pipelines/components/pipeline_triggerer.vue
+++ /dev/null
@@ -1,35 +0,0 @@
-<script>
-import UserAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link.vue';
-
-export default {
- components: {
- UserAvatarLink,
- },
- props: {
- pipeline: {
- type: Object,
- required: true,
- },
- },
- computed: {
- user() {
- return this.pipeline.user;
- },
- },
-};
-</script>
-<template>
- <div class="table-section section-10 d-none d-sm-none d-md-block pipeline-triggerer">
- <user-avatar-link
- v-if="user"
- :link-href="user.path"
- :img-src="user.avatar_url"
- :img-size="26"
- :tooltip-text="user.name"
- class="prepend-left-default js-pipeline-url-user"
- />
- <span v-else class="prepend-left-default js-pipeline-url-api api">
- {{ s__('Pipelines|API') }}
- </span>
- </div>
-</template>
diff --git a/app/assets/javascripts/pipelines/components/pipeline_url.vue b/app/assets/javascripts/pipelines/components/pipeline_url.vue
deleted file mode 100644
index 6c977b841af..00000000000
--- a/app/assets/javascripts/pipelines/components/pipeline_url.vue
+++ /dev/null
@@ -1,118 +0,0 @@
-<script>
-import { GlLink, GlTooltipDirective } from '@gitlab/ui';
-import { escape } from 'lodash';
-import { __, sprintf } from '~/locale';
-import popover from '~/vue_shared/directives/popover';
-
-const popoverTitle = sprintf(
- escape(
- __(
- `This pipeline makes use of a predefined CI/CD configuration enabled by %{strongStart}Auto DevOps.%{strongEnd}`,
- ),
- ),
- { strongStart: '<b>', strongEnd: '</b>' },
- false,
-);
-
-export default {
- components: {
- GlLink,
- },
- directives: {
- GlTooltip: GlTooltipDirective,
- popover,
- },
- props: {
- pipeline: {
- type: Object,
- required: true,
- },
- autoDevopsHelpPath: {
- type: String,
- required: true,
- },
- },
- computed: {
- user() {
- return this.pipeline.user;
- },
- popoverOptions() {
- return {
- html: true,
- trigger: 'focus',
- placement: 'top',
- title: `<div class="autodevops-title">
- ${popoverTitle}
- </div>`,
- content: `<a
- class="autodevops-link"
- href="${this.autoDevopsHelpPath}"
- target="_blank"
- rel="noopener noreferrer nofollow">
- ${escape(__('Learn more about Auto DevOps'))}
- </a>`,
- };
- },
- },
-};
-</script>
-<template>
- <div class="table-section section-10 d-none d-sm-none d-md-block pipeline-tags">
- <gl-link
- :href="pipeline.path"
- class="js-pipeline-url-link js-onboarding-pipeline-item"
- data-qa-selector="pipeline_url_link"
- >
- <span class="pipeline-id">#{{ pipeline.id }}</span>
- </gl-link>
- <div class="label-container">
- <span
- v-if="pipeline.flags.latest"
- v-gl-tooltip
- :title="__('Latest pipeline for the most recent commit on this branch')"
- class="js-pipeline-url-latest badge badge-success"
- >
- {{ __('latest') }}
- </span>
- <span
- v-if="pipeline.flags.yaml_errors"
- v-gl-tooltip
- :title="pipeline.yaml_errors"
- class="js-pipeline-url-yaml badge badge-danger"
- >
- {{ __('yaml invalid') }}
- </span>
- <span
- v-if="pipeline.flags.failure_reason"
- v-gl-tooltip
- :title="pipeline.failure_reason"
- class="js-pipeline-url-failure badge badge-danger"
- >
- {{ __('error') }}
- </span>
- <gl-link
- v-if="pipeline.flags.auto_devops"
- v-popover="popoverOptions"
- tabindex="0"
- class="js-pipeline-url-autodevops badge badge-info autodevops-badge"
- role="button"
- >{{ __('Auto DevOps') }}</gl-link
- >
- <span v-if="pipeline.flags.stuck" class="js-pipeline-url-stuck badge badge-warning">
- {{ __('stuck') }}
- </span>
- <span
- v-if="pipeline.flags.detached_merge_request_pipeline"
- v-gl-tooltip
- :title="
- __(
- 'Pipelines for merge requests are configured. A detached pipeline runs in the context of the merge request, and not against the merged result. Learn more in the documentation for Pipelines for Merged Results.',
- )
- "
- class="js-pipeline-url-detached badge badge-info"
- >
- {{ __('detached') }}
- </span>
- </div>
- </div>
-</template>
diff --git a/app/assets/javascripts/pipelines/components/pipelines.vue b/app/assets/javascripts/pipelines/components/pipelines.vue
deleted file mode 100644
index dbf29b0c29c..00000000000
--- a/app/assets/javascripts/pipelines/components/pipelines.vue
+++ /dev/null
@@ -1,374 +0,0 @@
-<script>
-import { isEqual } from 'lodash';
-import { __, sprintf, s__ } from '../../locale';
-import createFlash from '../../flash';
-import PipelinesService from '../services/pipelines_service';
-import pipelinesMixin from '../mixins/pipelines';
-import TablePagination from '../../vue_shared/components/pagination/table_pagination.vue';
-import NavigationTabs from '../../vue_shared/components/navigation_tabs.vue';
-import NavigationControls from './nav_controls.vue';
-import { getParameterByName } from '../../lib/utils/common_utils';
-import CIPaginationMixin from '../../vue_shared/mixins/ci_pagination_api_mixin';
-import PipelinesFilteredSearch from './pipelines_filtered_search.vue';
-import { validateParams } from '../utils';
-import { ANY_TRIGGER_AUTHOR, RAW_TEXT_WARNING, FILTER_TAG_IDENTIFIER } from '../constants';
-import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
-
-export default {
- components: {
- TablePagination,
- NavigationTabs,
- NavigationControls,
- PipelinesFilteredSearch,
- },
- mixins: [pipelinesMixin, CIPaginationMixin, glFeatureFlagsMixin()],
- props: {
- store: {
- type: Object,
- required: true,
- },
- // Can be rendered in 3 different places, with some visual differences
- // Accepts root | child
- // `root` -> main view
- // `child` -> rendered inside MR or Commit View
- viewType: {
- type: String,
- required: false,
- default: 'root',
- },
- endpoint: {
- type: String,
- required: true,
- },
- helpPagePath: {
- type: String,
- required: true,
- },
- emptyStateSvgPath: {
- type: String,
- required: true,
- },
- errorStateSvgPath: {
- type: String,
- required: true,
- },
- noPipelinesSvgPath: {
- type: String,
- required: true,
- },
- autoDevopsPath: {
- type: String,
- required: true,
- },
- hasGitlabCi: {
- type: Boolean,
- required: true,
- },
- canCreatePipeline: {
- type: Boolean,
- required: true,
- },
- ciLintPath: {
- type: String,
- required: false,
- default: null,
- },
- resetCachePath: {
- type: String,
- required: false,
- default: null,
- },
- newPipelinePath: {
- type: String,
- required: false,
- default: null,
- },
- projectId: {
- type: String,
- required: true,
- },
- params: {
- type: Object,
- required: true,
- },
- },
- data() {
- return {
- // Start with loading state to avoid a glitch when the empty state will be rendered
- isLoading: true,
- state: this.store.state,
- scope: getParameterByName('scope') || 'all',
- page: getParameterByName('page') || '1',
- requestData: {},
- isResetCacheButtonLoading: false,
- };
- },
- stateMap: {
- // with tabs
- loading: 'loading',
- tableList: 'tableList',
- error: 'error',
- emptyTab: 'emptyTab',
-
- // without tabs
- emptyState: 'emptyState',
- },
- scopes: {
- all: 'all',
- pending: 'pending',
- running: 'running',
- finished: 'finished',
- branches: 'branches',
- tags: 'tags',
- },
- computed: {
- /**
- * `hasGitlabCi` handles both internal and external CI.
- * The order on which the checks are made in this method is
- * important to guarantee we handle all the corner cases.
- */
- stateToRender() {
- const { stateMap } = this.$options;
-
- if (this.isLoading) {
- return stateMap.loading;
- }
-
- if (this.hasError) {
- return stateMap.error;
- }
-
- if (this.state.pipelines.length) {
- return stateMap.tableList;
- }
-
- if ((this.scope !== 'all' && this.scope !== null) || this.hasGitlabCi) {
- return stateMap.emptyTab;
- }
-
- return stateMap.emptyState;
- },
- /**
- * Tabs are rendered in all states except empty state.
- * They are not rendered before the first request to avoid a flicker on first load.
- */
- shouldRenderTabs() {
- const { stateMap } = this.$options;
- return (
- this.hasMadeRequest &&
- [stateMap.loading, stateMap.tableList, stateMap.error, stateMap.emptyTab].includes(
- this.stateToRender,
- )
- );
- },
-
- shouldRenderButtons() {
- return (
- (this.newPipelinePath || this.resetCachePath || this.ciLintPath) && this.shouldRenderTabs
- );
- },
-
- emptyTabMessage() {
- const { scopes } = this.$options;
- const possibleScopes = [scopes.pending, scopes.running, scopes.finished];
-
- if (possibleScopes.includes(this.scope)) {
- return sprintf(s__('Pipelines|There are currently no %{scope} pipelines.'), {
- scope: this.scope,
- });
- }
-
- return s__('Pipelines|There are currently no pipelines.');
- },
-
- tabs() {
- const { count } = this.state;
- const { scopes } = this.$options;
-
- return [
- {
- name: __('All'),
- scope: scopes.all,
- count: count.all,
- isActive: this.scope === 'all',
- },
- {
- name: __('Pending'),
- scope: scopes.pending,
- count: count.pending,
- isActive: this.scope === 'pending',
- },
- {
- name: __('Running'),
- scope: scopes.running,
- count: count.running,
- isActive: this.scope === 'running',
- },
- {
- name: __('Finished'),
- scope: scopes.finished,
- count: count.finished,
- isActive: this.scope === 'finished',
- },
- {
- name: __('Branches'),
- scope: scopes.branches,
- isActive: this.scope === 'branches',
- },
- {
- name: __('Tags'),
- scope: scopes.tags,
- isActive: this.scope === 'tags',
- },
- ];
- },
- canFilterPipelines() {
- return this.glFeatures.filterPipelinesSearch;
- },
- validatedParams() {
- return validateParams(this.params);
- },
- },
- created() {
- this.service = new PipelinesService(this.endpoint);
- this.requestData = { page: this.page, scope: this.scope, ...this.validatedParams };
- },
- methods: {
- successCallback(resp) {
- // Because we are polling & the user is interacting verify if the response received
- // matches the last request made
- if (isEqual(resp.config.params, this.requestData)) {
- this.store.storeCount(resp.data.count);
- this.store.storePagination(resp.headers);
- this.setCommonData(resp.data.pipelines);
- }
- },
- handleResetRunnersCache(endpoint) {
- this.isResetCacheButtonLoading = true;
-
- this.service
- .postAction(endpoint)
- .then(() => {
- this.isResetCacheButtonLoading = false;
- createFlash(s__('Pipelines|Project cache successfully reset.'), 'notice');
- })
- .catch(() => {
- this.isResetCacheButtonLoading = false;
- createFlash(s__('Pipelines|Something went wrong while cleaning runners cache.'));
- });
- },
- resetRequestData() {
- this.requestData = { page: this.page, scope: this.scope };
- },
- filterPipelines(filters) {
- this.resetRequestData();
-
- filters.forEach(filter => {
- // do not add Any for username query param, so we
- // can fetch all trigger authors
- if (
- filter.type &&
- filter.value.data !== ANY_TRIGGER_AUTHOR &&
- filter.type !== FILTER_TAG_IDENTIFIER
- ) {
- this.requestData[filter.type] = filter.value.data;
- }
-
- if (filter.type === FILTER_TAG_IDENTIFIER) {
- this.requestData.ref = filter.value.data;
- }
-
- if (!filter.type) {
- createFlash(RAW_TEXT_WARNING, 'warning');
- }
- });
-
- if (filters.length === 0) {
- this.resetRequestData();
- }
-
- this.updateContent(this.requestData);
- },
- },
-};
-</script>
-<template>
- <div class="pipelines-container">
- <div
- v-if="shouldRenderTabs || shouldRenderButtons"
- class="top-area scrolling-tabs-container inner-page-scroll-tabs"
- >
- <div class="fade-left"><i class="fa fa-angle-left" aria-hidden="true"> </i></div>
- <div class="fade-right"><i class="fa fa-angle-right" aria-hidden="true"> </i></div>
-
- <navigation-tabs
- v-if="shouldRenderTabs"
- :tabs="tabs"
- scope="pipelines"
- @onChangeTab="onChangeTab"
- />
-
- <navigation-controls
- v-if="shouldRenderButtons"
- :new-pipeline-path="newPipelinePath"
- :reset-cache-path="resetCachePath"
- :ci-lint-path="ciLintPath"
- :is-reset-cache-button-loading="isResetCacheButtonLoading"
- @resetRunnersCache="handleResetRunnersCache"
- />
- </div>
-
- <pipelines-filtered-search
- v-if="canFilterPipelines"
- :project-id="projectId"
- :params="validatedParams"
- @filterPipelines="filterPipelines"
- />
-
- <div class="content-list pipelines">
- <gl-loading-icon
- v-if="stateToRender === $options.stateMap.loading"
- :label="s__('Pipelines|Loading Pipelines')"
- size="lg"
- class="prepend-top-20"
- />
-
- <empty-state
- v-else-if="stateToRender === $options.stateMap.emptyState"
- :help-page-path="helpPagePath"
- :empty-state-svg-path="emptyStateSvgPath"
- :can-set-ci="canCreatePipeline"
- />
-
- <svg-blank-state
- v-else-if="stateToRender === $options.stateMap.error"
- :svg-path="errorStateSvgPath"
- :message="
- s__(`Pipelines|There was an error fetching the pipelines.
- Try again in a few moments or contact your support team.`)
- "
- />
-
- <svg-blank-state
- v-else-if="stateToRender === $options.stateMap.emptyTab"
- :svg-path="noPipelinesSvgPath"
- :message="emptyTabMessage"
- />
-
- <div v-else-if="stateToRender === $options.stateMap.tableList" class="table-holder">
- <pipelines-table-component
- :pipelines="state.pipelines"
- :update-graph-dropdown="updateGraphDropdown"
- :auto-devops-help-path="autoDevopsPath"
- :view-type="viewType"
- />
- </div>
-
- <table-pagination
- v-if="shouldRenderPagination"
- :change="onChangePage"
- :page-info="state.pageInfo"
- />
- </div>
- </div>
-</template>
diff --git a/app/assets/javascripts/pipelines/components/pipelines_actions.vue b/app/assets/javascripts/pipelines/components/pipelines_actions.vue
deleted file mode 100644
index 7d4276e8d2e..00000000000
--- a/app/assets/javascripts/pipelines/components/pipelines_actions.vue
+++ /dev/null
@@ -1,112 +0,0 @@
-<script>
-import { GlDeprecatedButton, GlTooltipDirective, GlLoadingIcon } from '@gitlab/ui';
-import axios from '~/lib/utils/axios_utils';
-import flash from '~/flash';
-import { s__, __, sprintf } from '~/locale';
-import GlCountdown from '~/vue_shared/components/gl_countdown.vue';
-import Icon from '~/vue_shared/components/icon.vue';
-import eventHub from '../event_hub';
-
-export default {
- directives: {
- GlTooltip: GlTooltipDirective,
- },
- components: {
- Icon,
- GlCountdown,
- GlDeprecatedButton,
- GlLoadingIcon,
- },
- props: {
- actions: {
- type: Array,
- required: true,
- },
- },
- data() {
- return {
- isLoading: false,
- };
- },
- methods: {
- onClickAction(action) {
- if (action.scheduled_at) {
- const confirmationMessage = sprintf(
- s__(
- "DelayedJobs|Are you sure you want to run %{jobName} immediately? Otherwise this job will run automatically after it's timer finishes.",
- ),
- { jobName: action.name },
- );
- // https://gitlab.com/gitlab-org/gitlab-foss/issues/52156
- // eslint-disable-next-line no-alert
- if (!window.confirm(confirmationMessage)) {
- return;
- }
- }
-
- this.isLoading = true;
-
- /**
- * Ideally, the component would not make an api call directly.
- * However, in order to use the eventhub and know when to
- * toggle back the `isLoading` property we'd need an ID
- * to track the request with a wacther - since this component
- * is rendered at least 20 times in the same page, moving the
- * api call directly here is the most performant solution
- */
- axios
- .post(`${action.path}.json`)
- .then(() => {
- this.isLoading = false;
- eventHub.$emit('updateTable');
- })
- .catch(() => {
- this.isLoading = false;
- flash(__('An error occurred while making the request.'));
- });
- },
-
- isActionDisabled(action) {
- if (action.playable === undefined) {
- return false;
- }
-
- return !action.playable;
- },
- },
-};
-</script>
-<template>
- <div class="btn-group">
- <button
- v-gl-tooltip
- type="button"
- :disabled="isLoading"
- class="dropdown-new btn btn-default js-pipeline-dropdown-manual-actions"
- :title="__('Manual job')"
- data-toggle="dropdown"
- :aria-label="__('Manual job')"
- >
- <icon name="play" class="icon-play" />
- <i class="fa fa-caret-down" aria-hidden="true"></i>
- <gl-loading-icon v-if="isLoading" />
- </button>
-
- <ul class="dropdown-menu dropdown-menu-right">
- <li v-for="action in actions" :key="action.path">
- <gl-deprecated-button
- :class="{ disabled: isActionDisabled(action) }"
- :disabled="isActionDisabled(action)"
- class="js-pipeline-action-link no-btn btn d-flex align-items-center justify-content-between flex-wrap"
- @click="onClickAction(action)"
- >
- {{ action.name }}
- <span v-if="action.scheduled_at">
- <icon name="clock" />
- <gl-countdown :end-date-string="action.scheduled_at" />
- </span>
- </gl-deprecated-button>
- </li>
- </ul>
- </div>
-</template>
diff --git a/app/assets/javascripts/pipelines/components/blank_state.vue b/app/assets/javascripts/pipelines/components/pipelines_list/blank_state.vue
index 6c3a4a27606..6c3a4a27606 100644
--- a/app/assets/javascripts/pipelines/components/blank_state.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines_list/blank_state.vue
diff --git a/app/assets/javascripts/pipelines/components/empty_state.vue b/app/assets/javascripts/pipelines/components/pipelines_list/empty_state.vue
index 74ada6a4d15..74ada6a4d15 100644
--- a/app/assets/javascripts/pipelines/components/empty_state.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines_list/empty_state.vue
diff --git a/app/assets/javascripts/pipelines/components/pipelines_list/nav_controls.vue b/app/assets/javascripts/pipelines/components/pipelines_list/nav_controls.vue
new file mode 100644
index 00000000000..a66bbb7e5ba
--- /dev/null
+++ b/app/assets/javascripts/pipelines/components/pipelines_list/nav_controls.vue
@@ -0,0 +1,66 @@
+<script>
+import { GlDeprecatedButton } from '@gitlab/ui';
+import LoadingButton from '~/vue_shared/components/loading_button.vue';
+
+export default {
+ name: 'PipelineNavControls',
+ components: {
+ LoadingButton,
+ GlDeprecatedButton,
+ },
+ props: {
+ newPipelinePath: {
+ type: String,
+ required: false,
+ default: null,
+ },
+
+ resetCachePath: {
+ type: String,
+ required: false,
+ default: null,
+ },
+
+ ciLintPath: {
+ type: String,
+ required: false,
+ default: null,
+ },
+
+ isResetCacheButtonLoading: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ },
+ methods: {
+ onClickResetCache() {
+ this.$emit('resetRunnersCache', this.resetCachePath);
+ },
+ },
+};
+</script>
+<template>
+ <div class="nav-controls">
+ <gl-deprecated-button
+ v-if="newPipelinePath"
+ :href="newPipelinePath"
+ variant="success"
+ class="js-run-pipeline"
+ >
+ {{ s__('Pipelines|Run Pipeline') }}
+ </gl-deprecated-button>
+
+ <loading-button
+ v-if="resetCachePath"
+ :loading="isResetCacheButtonLoading"
+ :label="s__('Pipelines|Clear Runner Caches')"
+ class="js-clear-cache"
+ @click="onClickResetCache"
+ />
+
+ <gl-deprecated-button v-if="ciLintPath" :href="ciLintPath" class="js-ci-lint">
+ {{ s__('Pipelines|CI Lint') }}
+ </gl-deprecated-button>
+ </div>
+</template>
diff --git a/app/assets/javascripts/pipelines/components/pipeline_stop_modal.vue b/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_stop_modal.vue
index f604edd8859..f604edd8859 100644
--- a/app/assets/javascripts/pipelines/components/pipeline_stop_modal.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_stop_modal.vue
diff --git a/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_triggerer.vue b/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_triggerer.vue
new file mode 100644
index 00000000000..35fd9837b3e
--- /dev/null
+++ b/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_triggerer.vue
@@ -0,0 +1,35 @@
+<script>
+import UserAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link.vue';
+
+export default {
+ components: {
+ UserAvatarLink,
+ },
+ props: {
+ pipeline: {
+ type: Object,
+ required: true,
+ },
+ },
+ computed: {
+ user() {
+ return this.pipeline.user;
+ },
+ },
+};
+</script>
+<template>
+ <div class="table-section section-10 d-none d-sm-none d-md-block pipeline-triggerer">
+ <user-avatar-link
+ v-if="user"
+ :link-href="user.path"
+ :img-src="user.avatar_url"
+ :img-size="26"
+ :tooltip-text="user.name"
+ class="gl-ml-3 js-pipeline-url-user"
+ />
+ <span v-else class="gl-ml-3 js-pipeline-url-api api">
+ {{ s__('Pipelines|API') }}
+ </span>
+ </div>
+</template>
diff --git a/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_url.vue b/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_url.vue
new file mode 100644
index 00000000000..2905b2ca26f
--- /dev/null
+++ b/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_url.vue
@@ -0,0 +1,146 @@
+<script>
+import { GlLink, GlTooltipDirective } from '@gitlab/ui';
+import { escape } from 'lodash';
+import { SCHEDULE_ORIGIN } from '../../constants';
+import { __, sprintf } from '~/locale';
+import popover from '~/vue_shared/directives/popover';
+
+const popoverTitle = sprintf(
+ escape(
+ __(
+ `This pipeline makes use of a predefined CI/CD configuration enabled by %{strongStart}Auto DevOps.%{strongEnd}`,
+ ),
+ ),
+ { strongStart: '<b>', strongEnd: '</b>' },
+ false,
+);
+
+export default {
+ components: {
+ GlLink,
+ },
+ directives: {
+ GlTooltip: GlTooltipDirective,
+ popover,
+ },
+ props: {
+ pipeline: {
+ type: Object,
+ required: true,
+ },
+ pipelineScheduleUrl: {
+ type: String,
+ required: true,
+ },
+ autoDevopsHelpPath: {
+ type: String,
+ required: true,
+ },
+ },
+ computed: {
+ user() {
+ return this.pipeline.user;
+ },
+ isScheduled() {
+ return this.pipeline.source === SCHEDULE_ORIGIN;
+ },
+ popoverOptions() {
+ return {
+ html: true,
+ trigger: 'focus',
+ placement: 'top',
+ title: `<div class="autodevops-title">
+ ${popoverTitle}
+ </div>`,
+ content: `<a
+ class="autodevops-link"
+ href="${this.autoDevopsHelpPath}"
+ target="_blank"
+ rel="noopener noreferrer nofollow">
+ ${escape(__('Learn more about Auto DevOps'))}
+ </a>`,
+ };
+ },
+ },
+};
+</script>
+<template>
+ <div class="table-section section-10 d-none d-sm-none d-md-block pipeline-tags">
+ <gl-link
+ :href="pipeline.path"
+ class="js-pipeline-url-link js-onboarding-pipeline-item"
+ data-testid="pipeline-url-link"
+ data-qa-selector="pipeline_url_link"
+ >
+ <span class="pipeline-id">#{{ pipeline.id }}</span>
+ </gl-link>
+ <div class="label-container">
+ <gl-link v-if="isScheduled" :href="pipelineScheduleUrl" target="__blank">
+ <span
+ v-gl-tooltip
+ :title="__('This pipeline was triggered by a schedule.')"
+ class="badge badge-info"
+ data-testid="pipeline-url-scheduled"
+ >
+ {{ __('Scheduled') }}
+ </span>
+ </gl-link>
+ <span
+ v-if="pipeline.flags.latest"
+ v-gl-tooltip
+ :title="__('Latest pipeline for the most recent commit on this branch')"
+ class="js-pipeline-url-latest badge badge-success"
+ data-testid="pipeline-url-latest"
+ >
+ {{ __('latest') }}
+ </span>
+ <span
+ v-if="pipeline.flags.yaml_errors"
+ v-gl-tooltip
+ :title="pipeline.yaml_errors"
+ class="js-pipeline-url-yaml badge badge-danger"
+ data-testid="pipeline-url-yaml"
+ >
+ {{ __('yaml invalid') }}
+ </span>
+ <span
+ v-if="pipeline.flags.failure_reason"
+ v-gl-tooltip
+ :title="pipeline.failure_reason"
+ class="js-pipeline-url-failure badge badge-danger"
+ data-testid="pipeline-url-failure"
+ >
+ {{ __('error') }}
+ </span>
+ <gl-link
+ v-if="pipeline.flags.auto_devops"
+ v-popover="popoverOptions"
+ tabindex="0"
+ class="js-pipeline-url-autodevops badge badge-info autodevops-badge"
+ data-testid="pipeline-url-autodevops"
+ role="button"
+ >{{ __('Auto DevOps') }}</gl-link
+ >
+ <span
+ v-if="pipeline.flags.stuck"
+ class="js-pipeline-url-stuck badge badge-warning"
+ data-testid="pipeline-url-stuck"
+ >
+ {{ __('stuck') }}
+ </span>
+ <span
+ v-if="pipeline.flags.detached_merge_request_pipeline"
+ v-gl-tooltip
+ :title="
+ __(
+ 'Pipelines for merge requests are configured. A detached pipeline runs in the context of the merge request, and not against the merged result. Learn more in the documentation for Pipelines for Merged Results.',
+ )
+ "
+ class="js-pipeline-url-detached badge badge-info"
+ data-testid="pipeline-url-detached"
+ >
+ {{ __('detached') }}
+ </span>
+ </div>
+ </div>
+</template>
diff --git a/app/assets/javascripts/pipelines/components/pipelines_list/pipelines.vue b/app/assets/javascripts/pipelines/components/pipelines_list/pipelines.vue
new file mode 100644
index 00000000000..0c531650fd2
--- /dev/null
+++ b/app/assets/javascripts/pipelines/components/pipelines_list/pipelines.vue
@@ -0,0 +1,362 @@
+<script>
+import { isEqual } from 'lodash';
+import { __, s__ } from '~/locale';
+import createFlash from '~/flash';
+import PipelinesService from '../../services/pipelines_service';
+import pipelinesMixin from '../../mixins/pipelines';
+import TablePagination from '~/vue_shared/components/pagination/table_pagination.vue';
+import NavigationTabs from '~/vue_shared/components/navigation_tabs.vue';
+import NavigationControls from './nav_controls.vue';
+import { getParameterByName } from '~/lib/utils/common_utils';
+import CIPaginationMixin from '~/vue_shared/mixins/ci_pagination_api_mixin';
+import Icon from '~/vue_shared/components/icon.vue';
+import PipelinesFilteredSearch from './pipelines_filtered_search.vue';
+import { validateParams } from '../../utils';
+import { ANY_TRIGGER_AUTHOR, RAW_TEXT_WARNING, FILTER_TAG_IDENTIFIER } from '../../constants';
+import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
+
+export default {
+ components: {
+ TablePagination,
+ NavigationTabs,
+ NavigationControls,
+ PipelinesFilteredSearch,
+ Icon,
+ },
+ mixins: [pipelinesMixin, CIPaginationMixin, glFeatureFlagsMixin()],
+ props: {
+ store: {
+ type: Object,
+ required: true,
+ },
+ // Can be rendered in 3 different places, with some visual differences
+ // Accepts root | child
+ // `root` -> main view
+ // `child` -> rendered inside MR or Commit View
+ viewType: {
+ type: String,
+ required: false,
+ default: 'root',
+ },
+ endpoint: {
+ type: String,
+ required: true,
+ },
+ pipelineScheduleUrl: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ helpPagePath: {
+ type: String,
+ required: true,
+ },
+ emptyStateSvgPath: {
+ type: String,
+ required: true,
+ },
+ errorStateSvgPath: {
+ type: String,
+ required: true,
+ },
+ noPipelinesSvgPath: {
+ type: String,
+ required: true,
+ },
+ autoDevopsPath: {
+ type: String,
+ required: true,
+ },
+ hasGitlabCi: {
+ type: Boolean,
+ required: true,
+ },
+ canCreatePipeline: {
+ type: Boolean,
+ required: true,
+ },
+ ciLintPath: {
+ type: String,
+ required: false,
+ default: null,
+ },
+ resetCachePath: {
+ type: String,
+ required: false,
+ default: null,
+ },
+ newPipelinePath: {
+ type: String,
+ required: false,
+ default: null,
+ },
+ projectId: {
+ type: String,
+ required: true,
+ },
+ params: {
+ type: Object,
+ required: true,
+ },
+ },
+ data() {
+ return {
+ // Start with loading state to avoid a glitch when the empty state will be rendered
+ isLoading: true,
+ state: this.store.state,
+ scope: getParameterByName('scope') || 'all',
+ page: getParameterByName('page') || '1',
+ requestData: {},
+ isResetCacheButtonLoading: false,
+ };
+ },
+ stateMap: {
+ // with tabs
+ loading: 'loading',
+ tableList: 'tableList',
+ error: 'error',
+ emptyTab: 'emptyTab',
+
+ // without tabs
+ emptyState: 'emptyState',
+ },
+ scopes: {
+ all: 'all',
+ finished: 'finished',
+ branches: 'branches',
+ tags: 'tags',
+ },
+ computed: {
+ /**
+ * `hasGitlabCi` handles both internal and external CI.
+ * The order on which the checks are made in this method is
+ * important to guarantee we handle all the corner cases.
+ */
+ stateToRender() {
+ const { stateMap } = this.$options;
+
+ if (this.isLoading) {
+ return stateMap.loading;
+ }
+
+ if (this.hasError) {
+ return stateMap.error;
+ }
+
+ if (this.state.pipelines.length) {
+ return stateMap.tableList;
+ }
+
+ if ((this.scope !== 'all' && this.scope !== null) || this.hasGitlabCi) {
+ return stateMap.emptyTab;
+ }
+
+ return stateMap.emptyState;
+ },
+ /**
+ * Tabs are rendered in all states except empty state.
+ * They are not rendered before the first request to avoid a flicker on first load.
+ */
+ shouldRenderTabs() {
+ const { stateMap } = this.$options;
+ return (
+ this.hasMadeRequest &&
+ [stateMap.loading, stateMap.tableList, stateMap.error, stateMap.emptyTab].includes(
+ this.stateToRender,
+ )
+ );
+ },
+
+ shouldRenderButtons() {
+ return (
+ (this.newPipelinePath || this.resetCachePath || this.ciLintPath) && this.shouldRenderTabs
+ );
+ },
+
+ emptyTabMessage() {
+ if (this.scope === this.$options.scopes.finished) {
+ return s__('Pipelines|There are currently no finished pipelines.');
+ }
+
+ return s__('Pipelines|There are currently no pipelines.');
+ },
+
+ tabs() {
+ const { count } = this.state;
+ const { scopes } = this.$options;
+
+ return [
+ {
+ name: __('All'),
+ scope: scopes.all,
+ count: count.all,
+ isActive: this.scope === 'all',
+ },
+ {
+ name: __('Finished'),
+ scope: scopes.finished,
+ isActive: this.scope === 'finished',
+ },
+ {
+ name: __('Branches'),
+ scope: scopes.branches,
+ isActive: this.scope === 'branches',
+ },
+ {
+ name: __('Tags'),
+ scope: scopes.tags,
+ isActive: this.scope === 'tags',
+ },
+ ];
+ },
+ canFilterPipelines() {
+ return this.glFeatures.filterPipelinesSearch;
+ },
+ validatedParams() {
+ return validateParams(this.params);
+ },
+ },
+ created() {
+ this.service = new PipelinesService(this.endpoint);
+ this.requestData = { page: this.page, scope: this.scope, ...this.validatedParams };
+ },
+ methods: {
+ successCallback(resp) {
+ // Because we are polling & the user is interacting verify if the response received
+ // matches the last request made
+ if (isEqual(resp.config.params, this.requestData)) {
+ this.store.storeCount(resp.data.count);
+ this.store.storePagination(resp.headers);
+ this.setCommonData(resp.data.pipelines);
+ }
+ },
+ handleResetRunnersCache(endpoint) {
+ this.isResetCacheButtonLoading = true;
+
+ this.service
+ .postAction(endpoint)
+ .then(() => {
+ this.isResetCacheButtonLoading = false;
+ createFlash(s__('Pipelines|Project cache successfully reset.'), 'notice');
+ })
+ .catch(() => {
+ this.isResetCacheButtonLoading = false;
+ createFlash(s__('Pipelines|Something went wrong while cleaning runners cache.'));
+ });
+ },
+ resetRequestData() {
+ this.requestData = { page: this.page, scope: this.scope };
+ },
+ filterPipelines(filters) {
+ this.resetRequestData();
+
+ filters.forEach(filter => {
+ // do not add Any for username query param, so we
+ // can fetch all trigger authors
+ if (
+ filter.type &&
+ filter.value.data !== ANY_TRIGGER_AUTHOR &&
+ filter.type !== FILTER_TAG_IDENTIFIER
+ ) {
+ this.requestData[filter.type] = filter.value.data;
+ }
+
+ if (filter.type === FILTER_TAG_IDENTIFIER) {
+ this.requestData.ref = filter.value.data;
+ }
+
+ if (!filter.type) {
+ createFlash(RAW_TEXT_WARNING, 'warning');
+ }
+ });
+
+ if (filters.length === 0) {
+ this.resetRequestData();
+ }
+
+ this.updateContent(this.requestData);
+ },
+ },
+};
+</script>
+<template>
+ <div class="pipelines-container">
+ <div
+ v-if="shouldRenderTabs || shouldRenderButtons"
+ class="top-area scrolling-tabs-container inner-page-scroll-tabs"
+ >
+ <div class="fade-left"><icon name="chevron-lg-left" :size="12" /></div>
+ <div class="fade-right"><icon name="chevron-lg-right" :size="12" /></div>
+
+ <navigation-tabs
+ v-if="shouldRenderTabs"
+ :tabs="tabs"
+ scope="pipelines"
+ @onChangeTab="onChangeTab"
+ />
+
+ <navigation-controls
+ v-if="shouldRenderButtons"
+ :new-pipeline-path="newPipelinePath"
+ :reset-cache-path="resetCachePath"
+ :ci-lint-path="ciLintPath"
+ :is-reset-cache-button-loading="isResetCacheButtonLoading"
+ @resetRunnersCache="handleResetRunnersCache"
+ />
+ </div>
+
+ <pipelines-filtered-search
+ v-if="canFilterPipelines"
+ :project-id="projectId"
+ :params="validatedParams"
+ @filterPipelines="filterPipelines"
+ />
+
+ <div class="content-list pipelines">
+ <gl-loading-icon
+ v-if="stateToRender === $options.stateMap.loading"
+ :label="s__('Pipelines|Loading Pipelines')"
+ size="lg"
+ class="prepend-top-20"
+ />
+
+ <empty-state
+ v-else-if="stateToRender === $options.stateMap.emptyState"
+ :help-page-path="helpPagePath"
+ :empty-state-svg-path="emptyStateSvgPath"
+ :can-set-ci="canCreatePipeline"
+ />
+
+ <svg-blank-state
+ v-else-if="stateToRender === $options.stateMap.error"
+ :svg-path="errorStateSvgPath"
+ :message="
+ s__(`Pipelines|There was an error fetching the pipelines.
+ Try again in a few moments or contact your support team.`)
+ "
+ />
+
+ <svg-blank-state
+ v-else-if="stateToRender === $options.stateMap.emptyTab"
+ :svg-path="noPipelinesSvgPath"
+ :message="emptyTabMessage"
+ />
+
+ <div v-else-if="stateToRender === $options.stateMap.tableList" class="table-holder">
+ <pipelines-table-component
+ :pipelines="state.pipelines"
+ :pipeline-schedule-url="pipelineScheduleUrl"
+ :update-graph-dropdown="updateGraphDropdown"
+ :auto-devops-help-path="autoDevopsPath"
+ :view-type="viewType"
+ />
+ </div>
+
+ <table-pagination
+ v-if="shouldRenderPagination"
+ :change="onChangePage"
+ :page-info="state.pageInfo"
+ />
+ </div>
+ </div>
+</template>
diff --git a/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_actions.vue b/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_actions.vue
new file mode 100644
index 00000000000..3009ca7a775
--- /dev/null
+++ b/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_actions.vue
@@ -0,0 +1,112 @@
+<script>
+import { GlDeprecatedButton, GlTooltipDirective, GlLoadingIcon } from '@gitlab/ui';
+import axios from '~/lib/utils/axios_utils';
+import flash from '~/flash';
+import { s__, __, sprintf } from '~/locale';
+import GlCountdown from '~/vue_shared/components/gl_countdown.vue';
+import Icon from '~/vue_shared/components/icon.vue';
+import eventHub from '../../event_hub';
+
+export default {
+ directives: {
+ GlTooltip: GlTooltipDirective,
+ },
+ components: {
+ Icon,
+ GlCountdown,
+ GlDeprecatedButton,
+ GlLoadingIcon,
+ },
+ props: {
+ actions: {
+ type: Array,
+ required: true,
+ },
+ },
+ data() {
+ return {
+ isLoading: false,
+ };
+ },
+ methods: {
+ onClickAction(action) {
+ if (action.scheduled_at) {
+ const confirmationMessage = sprintf(
+ s__(
+ "DelayedJobs|Are you sure you want to run %{jobName} immediately? Otherwise this job will run automatically after it's timer finishes.",
+ ),
+ { jobName: action.name },
+ );
+ // https://gitlab.com/gitlab-org/gitlab-foss/issues/52156
+ // eslint-disable-next-line no-alert
+ if (!window.confirm(confirmationMessage)) {
+ return;
+ }
+ }
+
+ this.isLoading = true;
+
+ /**
+ * Ideally, the component would not make an api call directly.
+ * However, in order to use the eventhub and know when to
+ * toggle back the `isLoading` property we'd need an ID
+ * to track the request with a wacther - since this component
+ * is rendered at least 20 times in the same page, moving the
+ * api call directly here is the most performant solution
+ */
+ axios
+ .post(`${action.path}.json`)
+ .then(() => {
+ this.isLoading = false;
+ eventHub.$emit('updateTable');
+ })
+ .catch(() => {
+ this.isLoading = false;
+ flash(__('An error occurred while making the request.'));
+ });
+ },
+
+ isActionDisabled(action) {
+ if (action.playable === undefined) {
+ return false;
+ }
+
+ return !action.playable;
+ },
+ },
+};
+</script>
+<template>
+ <div class="btn-group">
+ <button
+ v-gl-tooltip
+ type="button"
+ :disabled="isLoading"
+ class="dropdown-new btn btn-default js-pipeline-dropdown-manual-actions"
+ :title="__('Manual job')"
+ data-toggle="dropdown"
+ :aria-label="__('Manual job')"
+ >
+ <icon name="play" class="icon-play" />
+ <i class="fa fa-caret-down" aria-hidden="true"></i>
+ <gl-loading-icon v-if="isLoading" />
+ </button>
+
+ <ul class="dropdown-menu dropdown-menu-right">
+ <li v-for="action in actions" :key="action.path">
+ <gl-deprecated-button
+ :class="{ disabled: isActionDisabled(action) }"
+ :disabled="isActionDisabled(action)"
+ class="js-pipeline-action-link no-btn btn d-flex align-items-center justify-content-between flex-wrap"
+ @click="onClickAction(action)"
+ >
+ {{ action.name }}
+ <span v-if="action.scheduled_at">
+ <icon name="clock" />
+ <gl-countdown :end-date-string="action.scheduled_at" />
+ </span>
+ </gl-deprecated-button>
+ </li>
+ </ul>
+ </div>
+</template>
diff --git a/app/assets/javascripts/pipelines/components/pipelines_artifacts.vue b/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_artifacts.vue
index 59c066b2683..59c066b2683 100644
--- a/app/assets/javascripts/pipelines/components/pipelines_artifacts.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_artifacts.vue
diff --git a/app/assets/javascripts/pipelines/components/pipelines_filtered_search.vue b/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_filtered_search.vue
index 0505a8668d1..0505a8668d1 100644
--- a/app/assets/javascripts/pipelines/components/pipelines_filtered_search.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_filtered_search.vue
diff --git a/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_table.vue b/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_table.vue
new file mode 100644
index 00000000000..b8112149778
--- /dev/null
+++ b/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_table.vue
@@ -0,0 +1,107 @@
+<script>
+import { GlTooltipDirective } from '@gitlab/ui';
+import PipelinesTableRowComponent from './pipelines_table_row.vue';
+import PipelineStopModal from './pipeline_stop_modal.vue';
+import eventHub from '../../event_hub';
+
+/**
+ * Pipelines Table Component.
+ *
+ * Given an array of objects, renders a table.
+ */
+export default {
+ components: {
+ PipelinesTableRowComponent,
+ PipelineStopModal,
+ },
+ directives: {
+ GlTooltip: GlTooltipDirective,
+ },
+ props: {
+ pipelines: {
+ type: Array,
+ required: true,
+ },
+ pipelineScheduleUrl: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ updateGraphDropdown: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ autoDevopsHelpPath: {
+ type: String,
+ required: true,
+ },
+ viewType: {
+ type: String,
+ required: true,
+ },
+ },
+ data() {
+ return {
+ pipelineId: 0,
+ pipeline: {},
+ endpoint: '',
+ cancelingPipeline: null,
+ };
+ },
+ watch: {
+ pipelines() {
+ this.cancelingPipeline = null;
+ },
+ },
+ created() {
+ eventHub.$on('openConfirmationModal', this.setModalData);
+ },
+ beforeDestroy() {
+ eventHub.$off('openConfirmationModal', this.setModalData);
+ },
+ methods: {
+ setModalData(data) {
+ this.pipelineId = data.pipeline.id;
+ this.pipeline = data.pipeline;
+ this.endpoint = data.endpoint;
+ },
+ onSubmit() {
+ eventHub.$emit('postAction', this.endpoint);
+ this.cancelingPipeline = this.pipelineId;
+ },
+ },
+};
+</script>
+<template>
+ <div class="ci-table">
+ <div class="gl-responsive-table-row table-row-header" role="row">
+ <div class="table-section section-10 js-pipeline-status" role="rowheader">
+ {{ s__('Pipeline|Status') }}
+ </div>
+ <div class="table-section section-10 js-pipeline-info pipeline-info" role="rowheader">
+ {{ s__('Pipeline|Pipeline') }}
+ </div>
+ <div class="table-section section-10 js-triggerer-info triggerer-info" role="rowheader">
+ {{ s__('Pipeline|Triggerer') }}
+ </div>
+ <div class="table-section section-20 js-pipeline-commit pipeline-commit" role="rowheader">
+ {{ s__('Pipeline|Commit') }}
+ </div>
+ <div class="table-section section-15 js-pipeline-stages pipeline-stages" role="rowheader">
+ {{ s__('Pipeline|Stages') }}
+ </div>
+ </div>
+ <pipelines-table-row-component
+ v-for="model in pipelines"
+ :key="model.id"
+ :pipeline="model"
+ :pipeline-schedule-url="pipelineScheduleUrl"
+ :update-graph-dropdown="updateGraphDropdown"
+ :auto-devops-help-path="autoDevopsHelpPath"
+ :view-type="viewType"
+ :canceling-pipeline="cancelingPipeline"
+ />
+ <pipeline-stop-modal :pipeline="pipeline" @submit="onSubmit" />
+ </div>
+</template>
diff --git a/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_table_row.vue b/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_table_row.vue
new file mode 100644
index 00000000000..f25994a7506
--- /dev/null
+++ b/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_table_row.vue
@@ -0,0 +1,365 @@
+<script>
+import eventHub from '../../event_hub';
+import PipelinesActionsComponent from './pipelines_actions.vue';
+import PipelinesArtifactsComponent from './pipelines_artifacts.vue';
+import CiBadge from '~/vue_shared/components/ci_badge_link.vue';
+import PipelineStage from './stage.vue';
+import PipelineUrl from './pipeline_url.vue';
+import PipelineTriggerer from './pipeline_triggerer.vue';
+import PipelinesTimeago from './time_ago.vue';
+import CommitComponent from '~/vue_shared/components/commit.vue';
+import LoadingButton from '~/vue_shared/components/loading_button.vue';
+import Icon from '~/vue_shared/components/icon.vue';
+import { PIPELINES_TABLE } from '../../constants';
+
+/**
+ * Pipeline table row.
+ *
+ * Given the received object renders a table row in the pipelines' table.
+ */
+export default {
+ components: {
+ PipelinesActionsComponent,
+ PipelinesArtifactsComponent,
+ CommitComponent,
+ PipelineStage,
+ PipelineUrl,
+ PipelineTriggerer,
+ CiBadge,
+ PipelinesTimeago,
+ LoadingButton,
+ Icon,
+ },
+ props: {
+ pipeline: {
+ type: Object,
+ required: true,
+ },
+ pipelineScheduleUrl: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ updateGraphDropdown: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ autoDevopsHelpPath: {
+ type: String,
+ required: true,
+ },
+ viewType: {
+ type: String,
+ required: true,
+ },
+ cancelingPipeline: {
+ type: Number,
+ required: false,
+ default: null,
+ },
+ },
+ pipelinesTable: PIPELINES_TABLE,
+ data() {
+ return {
+ isRetrying: false,
+ };
+ },
+ computed: {
+ actions() {
+ if (!this.pipeline || !this.pipeline.details) {
+ return [];
+ }
+ const { details } = this.pipeline;
+ return [...(details.manual_actions || []), ...(details.scheduled_actions || [])];
+ },
+ /**
+ * If provided, returns the commit tag.
+ * Needed to render the commit component column.
+ *
+ * This field needs a lot of verification, because of different possible cases:
+ *
+ * 1. person who is an author of a commit might be a GitLab user
+ * 2. if person who is an author of a commit is a GitLab user, they can have a GitLab avatar
+ * 3. If GitLab user does not have avatar they might have a Gravatar
+ * 4. If committer is not a GitLab User they can have a Gravatar
+ * 5. We do not have consistent API object in this case
+ * 6. We should improve API and the code
+ *
+ * @returns {Object|Undefined}
+ */
+ commitAuthor() {
+ let commitAuthorInformation;
+
+ if (!this.pipeline || !this.pipeline.commit) {
+ return null;
+ }
+
+ // 1. person who is an author of a commit might be a GitLab user
+ if (this.pipeline.commit.author) {
+ // 2. if person who is an author of a commit is a GitLab user
+ // they can have a GitLab avatar
+ if (this.pipeline.commit.author.avatar_url) {
+ commitAuthorInformation = this.pipeline.commit.author;
+
+ // 3. If GitLab user does not have avatar, they might have a Gravatar
+ } else if (this.pipeline.commit.author_gravatar_url) {
+ commitAuthorInformation = {
+ ...this.pipeline.commit.author,
+ avatar_url: this.pipeline.commit.author_gravatar_url,
+ };
+ }
+ // 4. If committer is not a GitLab User, they can have a Gravatar
+ } else {
+ commitAuthorInformation = {
+ avatar_url: this.pipeline.commit.author_gravatar_url,
+ path: `mailto:${this.pipeline.commit.author_email}`,
+ username: this.pipeline.commit.author_name,
+ };
+ }
+
+ return commitAuthorInformation;
+ },
+
+ /**
+ * If provided, returns the commit tag.
+ * Needed to render the commit component column.
+ *
+ * @returns {String|Undefined}
+ */
+ commitTag() {
+ if (this.pipeline.ref && this.pipeline.ref.tag) {
+ return this.pipeline.ref.tag;
+ }
+ return undefined;
+ },
+
+ /**
+ * If provided, returns the commit ref.
+ * Needed to render the commit component column.
+ *
+ * Matches `path` prop sent in the API to `ref_url` prop needed
+ * in the commit component.
+ *
+ * @returns {Object|Undefined}
+ */
+ commitRef() {
+ if (this.pipeline.ref) {
+ return Object.keys(this.pipeline.ref).reduce((accumulator, prop) => {
+ if (prop === 'path') {
+ accumulator.ref_url = this.pipeline.ref[prop];
+ } else {
+ accumulator[prop] = this.pipeline.ref[prop];
+ }
+ return accumulator;
+ }, {});
+ }
+
+ return undefined;
+ },
+
+ /**
+ * If provided, returns the commit url.
+ * Needed to render the commit component column.
+ *
+ * @returns {String|Undefined}
+ */
+ commitUrl() {
+ if (this.pipeline.commit && this.pipeline.commit.commit_path) {
+ return this.pipeline.commit.commit_path;
+ }
+ return undefined;
+ },
+
+ /**
+ * If provided, returns the commit short sha.
+ * Needed to render the commit component column.
+ *
+ * @returns {String|Undefined}
+ */
+ commitShortSha() {
+ if (this.pipeline.commit && this.pipeline.commit.short_id) {
+ return this.pipeline.commit.short_id;
+ }
+ return undefined;
+ },
+
+ /**
+ * If provided, returns the commit title.
+ * Needed to render the commit component column.
+ *
+ * @returns {String|Undefined}
+ */
+ commitTitle() {
+ if (this.pipeline.commit && this.pipeline.commit.title) {
+ return this.pipeline.commit.title;
+ }
+ return undefined;
+ },
+
+ /**
+ * Timeago components expects a number
+ *
+ * @return {type} description
+ */
+ pipelineDuration() {
+ if (this.pipeline.details && this.pipeline.details.duration) {
+ return this.pipeline.details.duration;
+ }
+
+ return 0;
+ },
+
+ /**
+ * Timeago component expects a String.
+ *
+ * @return {String}
+ */
+ pipelineFinishedAt() {
+ if (this.pipeline.details && this.pipeline.details.finished_at) {
+ return this.pipeline.details.finished_at;
+ }
+
+ return '';
+ },
+
+ pipelineStatus() {
+ if (this.pipeline.details && this.pipeline.details.status) {
+ return this.pipeline.details.status;
+ }
+ return {};
+ },
+
+ displayPipelineActions() {
+ return (
+ this.pipeline.flags.retryable ||
+ this.pipeline.flags.cancelable ||
+ this.pipeline.details.manual_actions.length ||
+ this.pipeline.details.artifacts.length
+ );
+ },
+
+ isChildView() {
+ return this.viewType === 'child';
+ },
+
+ isCancelling() {
+ return this.cancelingPipeline === this.pipeline.id;
+ },
+ },
+ watch: {
+ pipeline() {
+ this.isRetrying = false;
+ },
+ },
+ methods: {
+ handleCancelClick() {
+ eventHub.$emit('openConfirmationModal', {
+ pipeline: this.pipeline,
+ endpoint: this.pipeline.cancel_path,
+ });
+ },
+ handleRetryClick() {
+ this.isRetrying = true;
+ eventHub.$emit('retryPipeline', this.pipeline.retry_path);
+ },
+ },
+};
+</script>
+<template>
+ <div class="commit gl-responsive-table-row">
+ <div class="table-section section-10 commit-link">
+ <div class="table-mobile-header" role="rowheader">{{ s__('Pipeline|Status') }}</div>
+ <div class="table-mobile-content">
+ <ci-badge
+ :status="pipelineStatus"
+ :show-text="!isChildView"
+ data-qa-selector="pipeline_commit_status"
+ />
+ </div>
+ </div>
+
+ <pipeline-url
+ :pipeline="pipeline"
+ :pipeline-schedule-url="pipelineScheduleUrl"
+ :auto-devops-help-path="autoDevopsHelpPath"
+ />
+ <pipeline-triggerer :pipeline="pipeline" />
+
+ <div class="table-section section-wrap section-20">
+ <div class="table-mobile-header" role="rowheader">{{ s__('Pipeline|Commit') }}</div>
+ <div class="table-mobile-content">
+ <commit-component
+ :tag="commitTag"
+ :commit-ref="commitRef"
+ :commit-url="commitUrl"
+ :merge-request-ref="pipeline.merge_request"
+ :short-sha="commitShortSha"
+ :title="commitTitle"
+ :author="commitAuthor"
+ :show-ref-info="!isChildView"
+ />
+ </div>
+ </div>
+
+ <div class="table-section section-wrap section-15 stage-cell">
+ <div class="table-mobile-header" role="rowheader">{{ s__('Pipeline|Stages') }}</div>
+ <div class="table-mobile-content">
+ <template v-if="pipeline.details.stages.length > 0">
+ <div
+ v-for="(stage, index) in pipeline.details.stages"
+ :key="index"
+ class="stage-container dropdown"
+ data-testid="widget-mini-pipeline-graph"
+ >
+ <pipeline-stage
+ :type="$options.pipelinesTable"
+ :stage="stage"
+ :update-dropdown="updateGraphDropdown"
+ />
+ </div>
+ </template>
+ </div>
+ </div>
+
+ <pipelines-timeago :duration="pipelineDuration" :finished-time="pipelineFinishedAt" />
+
+ <div
+ v-if="displayPipelineActions"
+ class="table-section section-20 table-button-footer pipeline-actions"
+ >
+ <div class="btn-group table-action-buttons">
+ <pipelines-actions-component v-if="actions.length > 0" :actions="actions" />
+
+ <pipelines-artifacts-component
+ v-if="pipeline.details.artifacts.length"
+ :artifacts="pipeline.details.artifacts"
+ class="d-md-block"
+ />
+
+ <loading-button
+ v-if="pipeline.flags.retryable"
+ :loading="isRetrying"
+ :disabled="isRetrying"
+ container-class="js-pipelines-retry-button btn btn-default btn-retry"
+ data-qa-selector="pipeline_retry_button"
+ @click="handleRetryClick"
+ >
+ <icon name="repeat" />
+ </loading-button>
+
+ <loading-button
+ v-if="pipeline.flags.cancelable"
+ :loading="isCancelling"
+ :disabled="isCancelling"
+ data-toggle="modal"
+ data-target="#confirmation-modal"
+ container-class="js-pipelines-cancel-button btn btn-remove"
+ @click="handleCancelClick"
+ >
+ <icon name="close" />
+ </loading-button>
+ </div>
+ </div>
+ </div>
+</template>
diff --git a/app/assets/javascripts/pipelines/components/pipelines_list/stage.vue b/app/assets/javascripts/pipelines/components/pipelines_list/stage.vue
new file mode 100644
index 00000000000..99492bd8357
--- /dev/null
+++ b/app/assets/javascripts/pipelines/components/pipelines_list/stage.vue
@@ -0,0 +1,194 @@
+<script>
+/**
+ * Renders each stage of the pipeline mini graph.
+ *
+ * Given the provided endpoint will make a request to
+ * fetch the dropdown data when the stage is clicked.
+ *
+ * Request is made inside this component to make it reusable between:
+ * 1. Pipelines main table
+ * 2. Pipelines table in commit and Merge request views
+ * 3. Merge request widget
+ * 4. Commit widget
+ */
+
+import $ from 'jquery';
+import { GlLoadingIcon, GlTooltipDirective } from '@gitlab/ui';
+import { __ } from '~/locale';
+import Flash from '~/flash';
+import axios from '~/lib/utils/axios_utils';
+import eventHub from '../../event_hub';
+import Icon from '~/vue_shared/components/icon.vue';
+import JobItem from '../graph/job_item.vue';
+import { PIPELINES_TABLE } from '../../constants';
+
+export default {
+ components: {
+ Icon,
+ JobItem,
+ GlLoadingIcon,
+ },
+
+ directives: {
+ GlTooltip: GlTooltipDirective,
+ },
+
+ props: {
+ stage: {
+ type: Object,
+ required: true,
+ },
+
+ updateDropdown: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+
+ type: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ },
+
+ data() {
+ return {
+ isLoading: false,
+ dropdownContent: '',
+ };
+ },
+
+ computed: {
+ dropdownClass() {
+ return this.dropdownContent.length > 0
+ ? 'js-builds-dropdown-container'
+ : 'js-builds-dropdown-loading';
+ },
+
+ triggerButtonClass() {
+ return `ci-status-icon-${this.stage.status.group}`;
+ },
+
+ borderlessIcon() {
+ return `${this.stage.status.icon}_borderless`;
+ },
+ },
+
+ watch: {
+ updateDropdown() {
+ if (this.updateDropdown && this.isDropdownOpen() && !this.isLoading) {
+ this.fetchJobs();
+ }
+ },
+ },
+
+ updated() {
+ if (this.dropdownContent.length > 0) {
+ this.stopDropdownClickPropagation();
+ }
+ },
+
+ methods: {
+ onClickStage() {
+ if (!this.isDropdownOpen()) {
+ eventHub.$emit('clickedDropdown');
+ this.isLoading = true;
+ this.fetchJobs();
+ }
+ },
+
+ fetchJobs() {
+ axios
+ .get(this.stage.dropdown_path)
+ .then(({ data }) => {
+ this.dropdownContent = data.latest_statuses;
+ this.isLoading = false;
+ })
+ .catch(() => {
+ this.closeDropdown();
+ this.isLoading = false;
+
+ Flash(__('Something went wrong on our end.'));
+ });
+ },
+
+ /**
+ * When the user right clicks or cmd/ctrl + click in the job name
+ * the dropdown should not be closed and the link should open in another tab,
+ * so we stop propagation of the click event inside the dropdown.
+ *
+ * Since this component is rendered multiple times per page we need to guarantee we only
+ * target the click event of this component.
+ */
+ stopDropdownClickPropagation() {
+ $(
+ '.js-builds-dropdown-list button, .js-builds-dropdown-list a.mini-pipeline-graph-dropdown-item',
+ this.$el,
+ ).on('click', e => {
+ e.stopPropagation();
+ });
+ },
+
+ closeDropdown() {
+ if (this.isDropdownOpen()) {
+ $(this.$refs.dropdown).dropdown('toggle');
+ }
+ },
+
+ isDropdownOpen() {
+ return this.$el.classList.contains('show');
+ },
+
+ pipelineActionRequestComplete() {
+ if (this.type === PIPELINES_TABLE) {
+ // warn the table to update
+ eventHub.$emit('refreshPipelinesTable');
+ } else {
+ // close the dropdown in mr widget
+ $(this.$refs.dropdown).dropdown('toggle');
+ }
+ },
+ },
+};
+</script>
+
+<template>
+ <div class="dropdown">
+ <button
+ id="stageDropdown"
+ ref="dropdown"
+ v-gl-tooltip.hover
+ :class="triggerButtonClass"
+ :title="stage.title"
+ class="mini-pipeline-graph-dropdown-toggle js-builds-dropdown-button"
+ data-toggle="dropdown"
+ data-display="static"
+ type="button"
+ aria-haspopup="true"
+ aria-expanded="false"
+ @click="onClickStage"
+ >
+ <span :aria-label="stage.title" aria-hidden="true" class="no-pointer-events">
+ <icon :name="borderlessIcon" />
+ </span>
+ </button>
+
+ <div
+ class="dropdown-menu mini-pipeline-graph-dropdown-menu js-builds-dropdown-container"
+ aria-labelledby="stageDropdown"
+ >
+ <gl-loading-icon v-if="isLoading" />
+ <ul v-else class="js-builds-dropdown-list scrollable-menu">
+ <li v-for="job in dropdownContent" :key="job.id">
+ <job-item
+ :dropdown-length="dropdownContent.length"
+ :job="job"
+ css-class-job-name="mini-pipeline-graph-dropdown-item"
+ @pipelineActionRequestComplete="pipelineActionRequestComplete"
+ />
+ </li>
+ </ul>
+ </div>
+ </div>
+</template>
diff --git a/app/assets/javascripts/pipelines/components/pipelines_list/time_ago.vue b/app/assets/javascripts/pipelines/components/pipelines_list/time_ago.vue
new file mode 100644
index 00000000000..8a01e1fe3f5
--- /dev/null
+++ b/app/assets/javascripts/pipelines/components/pipelines_list/time_ago.vue
@@ -0,0 +1,79 @@
+<script>
+import iconTimerSvg from 'icons/_icon_timer.svg';
+import '~/lib/utils/datetime_utility';
+import tooltip from '~/vue_shared/directives/tooltip';
+import timeagoMixin from '~/vue_shared/mixins/timeago';
+
+export default {
+ directives: {
+ tooltip,
+ },
+ mixins: [timeagoMixin],
+ props: {
+ finishedTime: {
+ type: String,
+ required: true,
+ },
+ duration: {
+ type: Number,
+ required: true,
+ },
+ },
+ data() {
+ return {
+ iconTimerSvg,
+ };
+ },
+ computed: {
+ hasDuration() {
+ return this.duration > 0;
+ },
+ hasFinishedTime() {
+ return this.finishedTime !== '';
+ },
+ durationFormatted() {
+ const date = new Date(this.duration * 1000);
+
+ let hh = date.getUTCHours();
+ let mm = date.getUTCMinutes();
+ let ss = date.getSeconds();
+
+ // left pad
+ if (hh < 10) {
+ hh = `0${hh}`;
+ }
+ if (mm < 10) {
+ mm = `0${mm}`;
+ }
+ if (ss < 10) {
+ ss = `0${ss}`;
+ }
+
+ return `${hh}:${mm}:${ss}`;
+ },
+ },
+};
+</script>
+<template>
+ <div class="table-section section-15 pipelines-time-ago">
+ <div class="table-mobile-header" role="rowheader">{{ s__('Pipeline|Duration') }}</div>
+ <div class="table-mobile-content">
+ <p v-if="hasDuration" class="duration">
+ <span v-html="iconTimerSvg"> </span> {{ durationFormatted }}
+ </p>
+
+ <p v-if="hasFinishedTime" class="finished-at d-none d-sm-none d-md-block">
+ <i class="fa fa-calendar" aria-hidden="true"> </i>
+
+ <time
+ v-tooltip
+ :title="tooltipTitle(finishedTime)"
+ data-placement="top"
+ data-container="body"
+ >
+ {{ timeFormatted(finishedTime) }}
+ </time>
+ </p>
+ </div>
+ </div>
+</template>
diff --git a/app/assets/javascripts/pipelines/components/pipelines_list/tokens/pipeline_branch_name_token.vue b/app/assets/javascripts/pipelines/components/pipelines_list/tokens/pipeline_branch_name_token.vue
new file mode 100644
index 00000000000..b6eff2931d3
--- /dev/null
+++ b/app/assets/javascripts/pipelines/components/pipelines_list/tokens/pipeline_branch_name_token.vue
@@ -0,0 +1,73 @@
+<script>
+import { GlFilteredSearchToken, GlFilteredSearchSuggestion, GlLoadingIcon } from '@gitlab/ui';
+import Api from '~/api';
+import { FETCH_BRANCH_ERROR_MESSAGE, FILTER_PIPELINES_SEARCH_DELAY } from '../../../constants';
+import createFlash from '~/flash';
+import { debounce } from 'lodash';
+
+export default {
+ components: {
+ GlFilteredSearchToken,
+ GlFilteredSearchSuggestion,
+ GlLoadingIcon,
+ },
+ props: {
+ config: {
+ type: Object,
+ required: true,
+ },
+ value: {
+ type: Object,
+ required: true,
+ },
+ },
+ data() {
+ return {
+ branches: null,
+ loading: true,
+ };
+ },
+ created() {
+ this.fetchBranches();
+ },
+ methods: {
+ fetchBranches(searchterm) {
+ Api.branches(this.config.projectId, searchterm)
+ .then(({ data }) => {
+ this.branches = data.map(branch => branch.name);
+ this.loading = false;
+ })
+ .catch(err => {
+ createFlash(FETCH_BRANCH_ERROR_MESSAGE);
+ this.loading = false;
+ throw err;
+ });
+ },
+ searchBranches: debounce(function debounceSearch({ data }) {
+ this.fetchBranches(data);
+ }, FILTER_PIPELINES_SEARCH_DELAY),
+ },
+};
+</script>
+
+<template>
+ <gl-filtered-search-token
+ :config="config"
+ v-bind="{ ...$props, ...$attrs }"
+ v-on="$listeners"
+ @input="searchBranches"
+ >
+ <template #suggestions>
+ <gl-loading-icon v-if="loading" />
+ <template v-else>
+ <gl-filtered-search-suggestion
+ v-for="(branch, index) in branches"
+ :key="index"
+ :value="branch"
+ >
+ {{ branch }}
+ </gl-filtered-search-suggestion>
+ </template>
+ </template>
+ </gl-filtered-search-token>
+</template>
diff --git a/app/assets/javascripts/pipelines/components/tokens/pipeline_status_token.vue b/app/assets/javascripts/pipelines/components/pipelines_list/tokens/pipeline_status_token.vue
index dc43d94f4fd..dc43d94f4fd 100644
--- a/app/assets/javascripts/pipelines/components/tokens/pipeline_status_token.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines_list/tokens/pipeline_status_token.vue
diff --git a/app/assets/javascripts/pipelines/components/pipelines_list/tokens/pipeline_tag_name_token.vue b/app/assets/javascripts/pipelines/components/pipelines_list/tokens/pipeline_tag_name_token.vue
new file mode 100644
index 00000000000..64de6d2a053
--- /dev/null
+++ b/app/assets/javascripts/pipelines/components/pipelines_list/tokens/pipeline_tag_name_token.vue
@@ -0,0 +1,64 @@
+<script>
+import { GlFilteredSearchToken, GlFilteredSearchSuggestion, GlLoadingIcon } from '@gitlab/ui';
+import Api from '~/api';
+import { FETCH_TAG_ERROR_MESSAGE, FILTER_PIPELINES_SEARCH_DELAY } from '../../../constants';
+import createFlash from '~/flash';
+import { debounce } from 'lodash';
+
+export default {
+ components: {
+ GlFilteredSearchToken,
+ GlFilteredSearchSuggestion,
+ GlLoadingIcon,
+ },
+ props: {
+ config: {
+ type: Object,
+ required: true,
+ },
+ value: {
+ type: Object,
+ required: true,
+ },
+ },
+ data() {
+ return {
+ tags: null,
+ loading: true,
+ };
+ },
+ created() {
+ this.fetchTags();
+ },
+ methods: {
+ fetchTags(searchTerm) {
+ Api.tags(this.config.projectId, searchTerm)
+ .then(({ data }) => {
+ this.tags = data.map(tag => tag.name);
+ this.loading = false;
+ })
+ .catch(err => {
+ createFlash(FETCH_TAG_ERROR_MESSAGE);
+ this.loading = false;
+ throw err;
+ });
+ },
+ searchTags: debounce(function debounceSearch({ data }) {
+ this.fetchTags(data);
+ }, FILTER_PIPELINES_SEARCH_DELAY),
+ },
+};
+</script>
+
+<template>
+ <gl-filtered-search-token v-bind="{ ...$props, ...$attrs }" v-on="$listeners" @input="searchTags">
+ <template #suggestions>
+ <gl-loading-icon v-if="loading" />
+ <template v-else>
+ <gl-filtered-search-suggestion v-for="(tag, index) in tags" :key="index" :value="tag">
+ {{ tag }}
+ </gl-filtered-search-suggestion>
+ </template>
+ </template>
+ </gl-filtered-search-token>
+</template>
diff --git a/app/assets/javascripts/pipelines/components/pipelines_list/tokens/pipeline_trigger_author_token.vue b/app/assets/javascripts/pipelines/components/pipelines_list/tokens/pipeline_trigger_author_token.vue
new file mode 100644
index 00000000000..b5aeb3fe9e0
--- /dev/null
+++ b/app/assets/javascripts/pipelines/components/pipelines_list/tokens/pipeline_trigger_author_token.vue
@@ -0,0 +1,117 @@
+<script>
+import {
+ GlFilteredSearchToken,
+ GlAvatar,
+ GlFilteredSearchSuggestion,
+ GlDropdownDivider,
+ GlLoadingIcon,
+} from '@gitlab/ui';
+import Api from '~/api';
+import createFlash from '~/flash';
+import { debounce } from 'lodash';
+import {
+ ANY_TRIGGER_AUTHOR,
+ FETCH_AUTHOR_ERROR_MESSAGE,
+ FILTER_PIPELINES_SEARCH_DELAY,
+} from '../../../constants';
+
+export default {
+ anyTriggerAuthor: ANY_TRIGGER_AUTHOR,
+ components: {
+ GlFilteredSearchToken,
+ GlAvatar,
+ GlFilteredSearchSuggestion,
+ GlDropdownDivider,
+ GlLoadingIcon,
+ },
+ props: {
+ config: {
+ type: Object,
+ required: true,
+ },
+ value: {
+ type: Object,
+ required: true,
+ },
+ },
+ data() {
+ return {
+ users: [],
+ loading: true,
+ };
+ },
+ computed: {
+ currentValue() {
+ return this.value.data.toLowerCase();
+ },
+ activeUser() {
+ return this.users.find(user => {
+ return user.username.toLowerCase() === this.currentValue;
+ });
+ },
+ },
+ created() {
+ this.fetchProjectUsers();
+ },
+ methods: {
+ fetchProjectUsers(searchTerm) {
+ Api.projectUsers(this.config.projectId, searchTerm)
+ .then(users => {
+ this.users = users;
+ this.loading = false;
+ })
+ .catch(err => {
+ createFlash(FETCH_AUTHOR_ERROR_MESSAGE);
+ this.loading = false;
+ throw err;
+ });
+ },
+ searchAuthors: debounce(function debounceSearch({ data }) {
+ this.fetchProjectUsers(data);
+ }, FILTER_PIPELINES_SEARCH_DELAY),
+ },
+};
+</script>
+
+<template>
+ <gl-filtered-search-token
+ :config="config"
+ v-bind="{ ...$props, ...$attrs }"
+ v-on="$listeners"
+ @input="searchAuthors"
+ >
+ <template #view="{inputValue}">
+ <gl-avatar
+ v-if="activeUser"
+ :size="16"
+ :src="activeUser.avatar_url"
+ shape="circle"
+ class="gl-mr-2"
+ />
+ <span>{{ activeUser ? activeUser.name : inputValue }}</span>
+ </template>
+ <template #suggestions>
+ <gl-filtered-search-suggestion :value="$options.anyTriggerAuthor">{{
+ $options.anyTriggerAuthor
+ }}</gl-filtered-search-suggestion>
+ <gl-dropdown-divider />
+
+ <gl-loading-icon v-if="loading" />
+ <template v-else>
+ <gl-filtered-search-suggestion
+ v-for="user in users"
+ :key="user.username"
+ :value="user.username"
+ >
+ <div class="d-flex">
+ <gl-avatar :size="32" :src="user.avatar_url" />
+ <div>
+ <div>{{ user.name }}</div>
+ <div>@{{ user.username }}</div>
+ </div>
+ </div>
+ </gl-filtered-search-suggestion>
+ </template>
+ </template>
+ </gl-filtered-search-token>
+</template>
diff --git a/app/assets/javascripts/pipelines/components/pipelines_table.vue b/app/assets/javascripts/pipelines/components/pipelines_table.vue
deleted file mode 100644
index d3ba0c97f6b..00000000000
--- a/app/assets/javascripts/pipelines/components/pipelines_table.vue
+++ /dev/null
@@ -1,101 +0,0 @@
-<script>
-import { GlTooltipDirective } from '@gitlab/ui';
-import PipelinesTableRowComponent from './pipelines_table_row.vue';
-import PipelineStopModal from './pipeline_stop_modal.vue';
-import eventHub from '../event_hub';
-
-/**
- * Pipelines Table Component.
- *
- * Given an array of objects, renders a table.
- */
-export default {
- components: {
- PipelinesTableRowComponent,
- PipelineStopModal,
- },
- directives: {
- GlTooltip: GlTooltipDirective,
- },
- props: {
- pipelines: {
- type: Array,
- required: true,
- },
- updateGraphDropdown: {
- type: Boolean,
- required: false,
- default: false,
- },
- autoDevopsHelpPath: {
- type: String,
- required: true,
- },
- viewType: {
- type: String,
- required: true,
- },
- },
- data() {
- return {
- pipelineId: 0,
- pipeline: {},
- endpoint: '',
- cancelingPipeline: null,
- };
- },
- watch: {
- pipelines() {
- this.cancelingPipeline = null;
- },
- },
- created() {
- eventHub.$on('openConfirmationModal', this.setModalData);
- },
- beforeDestroy() {
- eventHub.$off('openConfirmationModal', this.setModalData);
- },
- methods: {
- setModalData(data) {
- this.pipelineId = data.pipeline.id;
- this.pipeline = data.pipeline;
- this.endpoint = data.endpoint;
- },
- onSubmit() {
- eventHub.$emit('postAction', this.endpoint);
- this.cancelingPipeline = this.pipelineId;
- },
- },
-};
-</script>
-<template>
- <div class="ci-table">
- <div class="gl-responsive-table-row table-row-header" role="row">
- <div class="table-section section-10 js-pipeline-status" role="rowheader">
- {{ s__('Pipeline|Status') }}
- </div>
- <div class="table-section section-10 js-pipeline-info pipeline-info" role="rowheader">
- {{ s__('Pipeline|Pipeline') }}
- </div>
- <div class="table-section section-10 js-triggerer-info triggerer-info" role="rowheader">
- {{ s__('Pipeline|Triggerer') }}
- </div>
- <div class="table-section section-20 js-pipeline-commit pipeline-commit" role="rowheader">
- {{ s__('Pipeline|Commit') }}
- </div>
- <div class="table-section section-15 js-pipeline-stages pipeline-stages" role="rowheader">
- {{ s__('Pipeline|Stages') }}
- </div>
- </div>
- <pipelines-table-row-component
- v-for="model in pipelines"
- :key="model.id"
- :pipeline="model"
- :update-graph-dropdown="updateGraphDropdown"
- :auto-devops-help-path="autoDevopsHelpPath"
- :view-type="viewType"
- :canceling-pipeline="cancelingPipeline"
- />
- <pipeline-stop-modal :pipeline="pipeline" @submit="onSubmit" />
- </div>
-</template>
diff --git a/app/assets/javascripts/pipelines/components/pipelines_table_row.vue b/app/assets/javascripts/pipelines/components/pipelines_table_row.vue
deleted file mode 100644
index 981914dd046..00000000000
--- a/app/assets/javascripts/pipelines/components/pipelines_table_row.vue
+++ /dev/null
@@ -1,355 +0,0 @@
-<script>
-import eventHub from '../event_hub';
-import PipelinesActionsComponent from './pipelines_actions.vue';
-import PipelinesArtifactsComponent from './pipelines_artifacts.vue';
-import CiBadge from '../../vue_shared/components/ci_badge_link.vue';
-import PipelineStage from './stage.vue';
-import PipelineUrl from './pipeline_url.vue';
-import PipelineTriggerer from './pipeline_triggerer.vue';
-import PipelinesTimeago from './time_ago.vue';
-import CommitComponent from '../../vue_shared/components/commit.vue';
-import LoadingButton from '../../vue_shared/components/loading_button.vue';
-import Icon from '../../vue_shared/components/icon.vue';
-import { PIPELINES_TABLE } from '../constants';
-
-/**
- * Pipeline table row.
- *
- * Given the received object renders a table row in the pipelines' table.
- */
-export default {
- components: {
- PipelinesActionsComponent,
- PipelinesArtifactsComponent,
- CommitComponent,
- PipelineStage,
- PipelineUrl,
- PipelineTriggerer,
- CiBadge,
- PipelinesTimeago,
- LoadingButton,
- Icon,
- },
- props: {
- pipeline: {
- type: Object,
- required: true,
- },
- updateGraphDropdown: {
- type: Boolean,
- required: false,
- default: false,
- },
- autoDevopsHelpPath: {
- type: String,
- required: true,
- },
- viewType: {
- type: String,
- required: true,
- },
- cancelingPipeline: {
- type: Number,
- required: false,
- default: null,
- },
- },
- pipelinesTable: PIPELINES_TABLE,
- data() {
- return {
- isRetrying: false,
- };
- },
- computed: {
- actions() {
- if (!this.pipeline || !this.pipeline.details) {
- return [];
- }
- const { details } = this.pipeline;
- return [...(details.manual_actions || []), ...(details.scheduled_actions || [])];
- },
- /**
- * If provided, returns the commit tag.
- * Needed to render the commit component column.
- *
- * This field needs a lot of verification, because of different possible cases:
- *
- * 1. person who is an author of a commit might be a GitLab user
- * 2. if person who is an author of a commit is a GitLab user, they can have a GitLab avatar
- * 3. If GitLab user does not have avatar they might have a Gravatar
- * 4. If committer is not a GitLab User they can have a Gravatar
- * 5. We do not have consistent API object in this case
- * 6. We should improve API and the code
- *
- * @returns {Object|Undefined}
- */
- commitAuthor() {
- let commitAuthorInformation;
-
- if (!this.pipeline || !this.pipeline.commit) {
- return null;
- }
-
- // 1. person who is an author of a commit might be a GitLab user
- if (this.pipeline.commit.author) {
- // 2. if person who is an author of a commit is a GitLab user
- // they can have a GitLab avatar
- if (this.pipeline.commit.author.avatar_url) {
- commitAuthorInformation = this.pipeline.commit.author;
-
- // 3. If GitLab user does not have avatar, they might have a Gravatar
- } else if (this.pipeline.commit.author_gravatar_url) {
- commitAuthorInformation = {
- ...this.pipeline.commit.author,
- avatar_url: this.pipeline.commit.author_gravatar_url,
- };
- }
- // 4. If committer is not a GitLab User, they can have a Gravatar
- } else {
- commitAuthorInformation = {
- avatar_url: this.pipeline.commit.author_gravatar_url,
- path: `mailto:${this.pipeline.commit.author_email}`,
- username: this.pipeline.commit.author_name,
- };
- }
-
- return commitAuthorInformation;
- },
-
- /**
- * If provided, returns the commit tag.
- * Needed to render the commit component column.
- *
- * @returns {String|Undefined}
- */
- commitTag() {
- if (this.pipeline.ref && this.pipeline.ref.tag) {
- return this.pipeline.ref.tag;
- }
- return undefined;
- },
-
- /**
- * If provided, returns the commit ref.
- * Needed to render the commit component column.
- *
- * Matches `path` prop sent in the API to `ref_url` prop needed
- * in the commit component.
- *
- * @returns {Object|Undefined}
- */
- commitRef() {
- if (this.pipeline.ref) {
- return Object.keys(this.pipeline.ref).reduce((accumulator, prop) => {
- if (prop === 'path') {
- accumulator.ref_url = this.pipeline.ref[prop];
- } else {
- accumulator[prop] = this.pipeline.ref[prop];
- }
- return accumulator;
- }, {});
- }
-
- return undefined;
- },
-
- /**
- * If provided, returns the commit url.
- * Needed to render the commit component column.
- *
- * @returns {String|Undefined}
- */
- commitUrl() {
- if (this.pipeline.commit && this.pipeline.commit.commit_path) {
- return this.pipeline.commit.commit_path;
- }
- return undefined;
- },
-
- /**
- * If provided, returns the commit short sha.
- * Needed to render the commit component column.
- *
- * @returns {String|Undefined}
- */
- commitShortSha() {
- if (this.pipeline.commit && this.pipeline.commit.short_id) {
- return this.pipeline.commit.short_id;
- }
- return undefined;
- },
-
- /**
- * If provided, returns the commit title.
- * Needed to render the commit component column.
- *
- * @returns {String|Undefined}
- */
- commitTitle() {
- if (this.pipeline.commit && this.pipeline.commit.title) {
- return this.pipeline.commit.title;
- }
- return undefined;
- },
-
- /**
- * Timeago components expects a number
- *
- * @return {type} description
- */
- pipelineDuration() {
- if (this.pipeline.details && this.pipeline.details.duration) {
- return this.pipeline.details.duration;
- }
-
- return 0;
- },
-
- /**
- * Timeago component expects a String.
- *
- * @return {String}
- */
- pipelineFinishedAt() {
- if (this.pipeline.details && this.pipeline.details.finished_at) {
- return this.pipeline.details.finished_at;
- }
-
- return '';
- },
-
- pipelineStatus() {
- if (this.pipeline.details && this.pipeline.details.status) {
- return this.pipeline.details.status;
- }
- return {};
- },
-
- displayPipelineActions() {
- return (
- this.pipeline.flags.retryable ||
- this.pipeline.flags.cancelable ||
- this.pipeline.details.manual_actions.length ||
- this.pipeline.details.artifacts.length
- );
- },
-
- isChildView() {
- return this.viewType === 'child';
- },
-
- isCancelling() {
- return this.cancelingPipeline === this.pipeline.id;
- },
- },
- watch: {
- pipeline() {
- this.isRetrying = false;
- },
- },
- methods: {
- handleCancelClick() {
- eventHub.$emit('openConfirmationModal', {
- pipeline: this.pipeline,
- endpoint: this.pipeline.cancel_path,
- });
- },
- handleRetryClick() {
- this.isRetrying = true;
- eventHub.$emit('retryPipeline', this.pipeline.retry_path);
- },
- },
-};
-</script>
-<template>
- <div class="commit gl-responsive-table-row">
- <div class="table-section section-10 commit-link">
- <div class="table-mobile-header" role="rowheader">{{ s__('Pipeline|Status') }}</div>
- <div class="table-mobile-content">
- <ci-badge
- :status="pipelineStatus"
- :show-text="!isChildView"
- data-qa-selector="pipeline_commit_status"
- />
- </div>
- </div>
-
- <pipeline-url :pipeline="pipeline" :auto-devops-help-path="autoDevopsHelpPath" />
- <pipeline-triggerer :pipeline="pipeline" />
-
- <div class="table-section section-wrap section-20">
- <div class="table-mobile-header" role="rowheader">{{ s__('Pipeline|Commit') }}</div>
- <div class="table-mobile-content">
- <commit-component
- :tag="commitTag"
- :commit-ref="commitRef"
- :commit-url="commitUrl"
- :merge-request-ref="pipeline.merge_request"
- :short-sha="commitShortSha"
- :title="commitTitle"
- :author="commitAuthor"
- :show-ref-info="!isChildView"
- />
- </div>
- </div>
-
- <div class="table-section section-wrap section-15 stage-cell">
- <div class="table-mobile-header" role="rowheader">{{ s__('Pipeline|Stages') }}</div>
- <div class="table-mobile-content">
- <template v-if="pipeline.details.stages.length > 0">
- <div
- v-for="(stage, index) in pipeline.details.stages"
- :key="index"
- class="stage-container dropdown js-mini-pipeline-graph"
- >
- <pipeline-stage
- :type="$options.pipelinesTable"
- :stage="stage"
- :update-dropdown="updateGraphDropdown"
- />
- </div>
- </template>
- </div>
- </div>
-
- <pipelines-timeago :duration="pipelineDuration" :finished-time="pipelineFinishedAt" />
-
- <div
- v-if="displayPipelineActions"
- class="table-section section-20 table-button-footer pipeline-actions"
- >
- <div class="btn-group table-action-buttons">
- <pipelines-actions-component v-if="actions.length > 0" :actions="actions" />
-
- <pipelines-artifacts-component
- v-if="pipeline.details.artifacts.length"
- :artifacts="pipeline.details.artifacts"
- class="d-md-block"
- />
-
- <loading-button
- v-if="pipeline.flags.retryable"
- :loading="isRetrying"
- :disabled="isRetrying"
- container-class="js-pipelines-retry-button btn btn-default btn-retry"
- data-qa-selector="pipeline_retry_button"
- @click="handleRetryClick"
- >
- <icon name="repeat" />
- </loading-button>
-
- <loading-button
- v-if="pipeline.flags.cancelable"
- :loading="isCancelling"
- :disabled="isCancelling"
- data-toggle="modal"
- data-target="#confirmation-modal"
- container-class="js-pipelines-cancel-button btn btn-remove"
- @click="handleCancelClick"
- >
- <icon name="close" />
- </loading-button>
- </div>
- </div>
- </div>
-</template>
diff --git a/app/assets/javascripts/pipelines/components/stage.vue b/app/assets/javascripts/pipelines/components/stage.vue
deleted file mode 100644
index 569920a4f31..00000000000
--- a/app/assets/javascripts/pipelines/components/stage.vue
+++ /dev/null
@@ -1,194 +0,0 @@
-<script>
-/**
- * Renders each stage of the pipeline mini graph.
- *
- * Given the provided endpoint will make a request to
- * fetch the dropdown data when the stage is clicked.
- *
- * Request is made inside this component to make it reusable between:
- * 1. Pipelines main table
- * 2. Pipelines table in commit and Merge request views
- * 3. Merge request widget
- * 4. Commit widget
- */
-
-import $ from 'jquery';
-import { GlLoadingIcon, GlTooltipDirective } from '@gitlab/ui';
-import { __ } from '../../locale';
-import Flash from '../../flash';
-import axios from '../../lib/utils/axios_utils';
-import eventHub from '../event_hub';
-import Icon from '../../vue_shared/components/icon.vue';
-import JobItem from './graph/job_item.vue';
-import { PIPELINES_TABLE } from '../constants';
-
-export default {
- components: {
- Icon,
- JobItem,
- GlLoadingIcon,
- },
-
- directives: {
- GlTooltip: GlTooltipDirective,
- },
-
- props: {
- stage: {
- type: Object,
- required: true,
- },
-
- updateDropdown: {
- type: Boolean,
- required: false,
- default: false,
- },
-
- type: {
- type: String,
- required: false,
- default: '',
- },
- },
-
- data() {
- return {
- isLoading: false,
- dropdownContent: '',
- };
- },
-
- computed: {
- dropdownClass() {
- return this.dropdownContent.length > 0
- ? 'js-builds-dropdown-container'
- : 'js-builds-dropdown-loading';
- },
-
- triggerButtonClass() {
- return `ci-status-icon-${this.stage.status.group}`;
- },
-
- borderlessIcon() {
- return `${this.stage.status.icon}_borderless`;
- },
- },
-
- watch: {
- updateDropdown() {
- if (this.updateDropdown && this.isDropdownOpen() && !this.isLoading) {
- this.fetchJobs();
- }
- },
- },
-
- updated() {
- if (this.dropdownContent.length > 0) {
- this.stopDropdownClickPropagation();
- }
- },
-
- methods: {
- onClickStage() {
- if (!this.isDropdownOpen()) {
- eventHub.$emit('clickedDropdown');
- this.isLoading = true;
- this.fetchJobs();
- }
- },
-
- fetchJobs() {
- axios
- .get(this.stage.dropdown_path)
- .then(({ data }) => {
- this.dropdownContent = data.latest_statuses;
- this.isLoading = false;
- })
- .catch(() => {
- this.closeDropdown();
- this.isLoading = false;
-
- Flash(__('Something went wrong on our end.'));
- });
- },
-
- /**
- * When the user right clicks or cmd/ctrl + click in the job name
- * the dropdown should not be closed and the link should open in another tab,
- * so we stop propagation of the click event inside the dropdown.
- *
- * Since this component is rendered multiple times per page we need to guarantee we only
- * target the click event of this component.
- */
- stopDropdownClickPropagation() {
- $(
- '.js-builds-dropdown-list button, .js-builds-dropdown-list a.mini-pipeline-graph-dropdown-item',
- this.$el,
- ).on('click', e => {
- e.stopPropagation();
- });
- },
-
- closeDropdown() {
- if (this.isDropdownOpen()) {
- $(this.$refs.dropdown).dropdown('toggle');
- }
- },
-
- isDropdownOpen() {
- return this.$el.classList.contains('show');
- },
-
- pipelineActionRequestComplete() {
- if (this.type === PIPELINES_TABLE) {
- // warn the table to update
- eventHub.$emit('refreshPipelinesTable');
- } else {
- // close the dropdown in mr widget
- $(this.$refs.dropdown).dropdown('toggle');
- }
- },
- },
-};
-</script>
-
-<template>
- <div class="dropdown">
- <button
- id="stageDropdown"
- ref="dropdown"
- v-gl-tooltip.hover
- :class="triggerButtonClass"
- :title="stage.title"
- class="mini-pipeline-graph-dropdown-toggle js-builds-dropdown-button"
- data-toggle="dropdown"
- data-display="static"
- type="button"
- aria-haspopup="true"
- aria-expanded="false"
- @click="onClickStage"
- >
- <span :aria-label="stage.title" aria-hidden="true" class="no-pointer-events">
- <icon :name="borderlessIcon" />
- </span>
- </button>
-
- <div
- class="dropdown-menu mini-pipeline-graph-dropdown-menu js-builds-dropdown-container"
- aria-labelledby="stageDropdown"
- >
- <gl-loading-icon v-if="isLoading" />
- <ul v-else class="js-builds-dropdown-list scrollable-menu">
- <li v-for="job in dropdownContent" :key="job.id">
- <job-item
- :dropdown-length="dropdownContent.length"
- :job="job"
- css-class-job-name="mini-pipeline-graph-dropdown-item"
- @pipelineActionRequestComplete="pipelineActionRequestComplete"
- />
- </li>
- </ul>
- </div>
- </div>
-</template>
diff --git a/app/assets/javascripts/pipelines/components/test_reports/test_reports.vue b/app/assets/javascripts/pipelines/components/test_reports/test_reports.vue
index 06ab45adf80..8746784aa57 100644
--- a/app/assets/javascripts/pipelines/components/test_reports/test_reports.vue
+++ b/app/assets/javascripts/pipelines/components/test_reports/test_reports.vue
@@ -1,10 +1,9 @@
<script>
-import { mapActions, mapState } from 'vuex';
+import { mapActions, mapGetters, mapState } from 'vuex';
import { GlLoadingIcon } from '@gitlab/ui';
import TestSuiteTable from './test_suite_table.vue';
import TestSummary from './test_summary.vue';
import TestSummaryTable from './test_summary_table.vue';
-import store from '~/pipelines/stores/test_reports';
export default {
name: 'TestReports',
@@ -14,24 +13,37 @@ export default {
TestSummary,
TestSummaryTable,
},
- store,
computed: {
- ...mapState(['isLoading', 'selectedSuite', 'testReports']),
+ ...mapState(['hasFullReport', 'isLoading', 'selectedSuiteIndex', 'testReports']),
+ ...mapGetters(['getSelectedSuite']),
showSuite() {
- return this.selectedSuite.total_count > 0;
+ return this.selectedSuiteIndex !== null;
},
showTests() {
const { test_suites: testSuites = [] } = this.testReports;
return testSuites.length > 0;
},
},
+ created() {
+ this.fetchSummary();
+ },
methods: {
- ...mapActions(['setSelectedSuite', 'removeSelectedSuite']),
+ ...mapActions([
+ 'fetchFullReport',
+ 'fetchSummary',
+ 'setSelectedSuiteIndex',
+ 'removeSelectedSuiteIndex',
+ ]),
summaryBackClick() {
- this.removeSelectedSuite();
+ this.removeSelectedSuiteIndex();
},
- summaryTableRowClick(suite) {
- this.setSelectedSuite(suite);
+ summaryTableRowClick(index) {
+ this.setSelectedSuiteIndex(index);
+
+ // Fetch the full report when the user clicks to see more details
+ if (!this.hasFullReport) {
+ this.fetchFullReport();
+ }
},
beforeEnterTransition() {
document.documentElement.style.overflowX = 'hidden';
@@ -45,7 +57,7 @@ export default {
<template>
<div v-if="isLoading">
- <gl-loading-icon size="lg" class="prepend-top-default js-loading-spinner" />
+ <gl-loading-icon size="lg" class="gl-mt-3 js-loading-spinner" />
</div>
<div
@@ -59,7 +71,7 @@ export default {
@after-leave="afterLeaveTransition"
>
<div v-if="showSuite" key="detail" class="w-100 position-absolute slide-enter-to-element">
- <test-summary :report="selectedSuite" show-back @on-back-click="summaryBackClick" />
+ <test-summary :report="getSelectedSuite" show-back @on-back-click="summaryBackClick" />
<test-suite-table />
</div>
@@ -73,7 +85,7 @@ export default {
</div>
<div v-else>
- <div class="row prepend-top-default">
+ <div class="row gl-mt-3">
<div class="col-12">
<p class="js-no-tests-to-show">{{ s__('TestReports|There are no tests to show.') }}</p>
</div>
diff --git a/app/assets/javascripts/pipelines/components/test_reports/test_suite_table.vue b/app/assets/javascripts/pipelines/components/test_reports/test_suite_table.vue
index be7f27f210d..d57b1466177 100644
--- a/app/assets/javascripts/pipelines/components/test_reports/test_suite_table.vue
+++ b/app/assets/javascripts/pipelines/components/test_reports/test_suite_table.vue
@@ -1,8 +1,8 @@
<script>
import { mapGetters } from 'vuex';
import Icon from '~/vue_shared/components/icon.vue';
-import store from '~/pipelines/stores/test_reports';
import { __ } from '~/locale';
+import { GlTooltipDirective } from '@gitlab/ui';
import SmartVirtualList from '~/vue_shared/components/smart_virtual_list.vue';
export default {
@@ -11,7 +11,9 @@ export default {
Icon,
SmartVirtualList,
},
- store,
+ directives: {
+ GlTooltip: GlTooltipDirective,
+ },
props: {
heading: {
type: String,
@@ -32,16 +34,16 @@ export default {
<template>
<div>
- <div class="row prepend-top-default">
+ <div class="row gl-mt-3">
<div class="col-12">
<h4>{{ heading }}</h4>
</div>
</div>
- <div v-if="hasSuites" class="test-reports-table append-bottom-default js-test-cases-table">
+ <div v-if="hasSuites" class="test-reports-table gl-mb-3 js-test-cases-table">
<div role="row" class="gl-responsive-table-row table-row-header font-weight-bold fgray">
<div role="rowheader" class="table-section section-20">
- {{ __('Class') }}
+ {{ __('Suite') }}
</div>
<div role="rowheader" class="table-section section-20">
{{ __('Name') }}
@@ -68,13 +70,25 @@ export default {
class="gl-responsive-table-row rounded align-items-md-start mt-xs-3 js-case-row"
>
<div class="table-section section-20 section-wrap">
- <div role="rowheader" class="table-mobile-header">{{ __('Class') }}</div>
- <div class="table-mobile-content pr-md-1 text-truncate">{{ testCase.classname }}</div>
+ <div role="rowheader" class="table-mobile-header">{{ __('Suite') }}</div>
+ <div
+ v-gl-tooltip
+ :title="testCase.classname"
+ class="table-mobile-content pr-md-1 text-truncate"
+ >
+ {{ testCase.classname }}
+ </div>
</div>
<div class="table-section section-20 section-wrap">
<div role="rowheader" class="table-mobile-header">{{ __('Name') }}</div>
- <div class="table-mobile-content pr-md-1 text-truncate">{{ testCase.name }}</div>
+ <div
+ v-gl-tooltip
+ :title="testCase.name"
+ class="table-mobile-content pr-md-1 text-truncate"
+ >
+ {{ testCase.name }}
+ </div>
</div>
<div class="table-section section-10 section-wrap">
diff --git a/app/assets/javascripts/pipelines/components/test_reports/test_summary.vue b/app/assets/javascripts/pipelines/components/test_reports/test_summary.vue
index 67646c537bd..712ac5eb0e5 100644
--- a/app/assets/javascripts/pipelines/components/test_reports/test_summary.vue
+++ b/app/assets/javascripts/pipelines/components/test_reports/test_summary.vue
@@ -72,7 +72,7 @@ export default {
<gl-deprecated-button
v-if="showBack"
size="sm"
- class="append-right-default js-back-button"
+ class="gl-mr-3 js-back-button"
@click="onBackClick"
>
<icon name="angle-left" />
@@ -85,7 +85,7 @@ export default {
<div class="row mt-2">
<div class="col-4 col-md">
<span class="js-total-tests">{{
- sprintf(s__('TestReports|%{count} jobs'), { count: report.total_count })
+ sprintf(s__('TestReports|%{count} tests'), { count: report.total_count })
}}</span>
</div>
diff --git a/app/assets/javascripts/pipelines/components/test_reports/test_summary_table.vue b/app/assets/javascripts/pipelines/components/test_reports/test_summary_table.vue
index 4dfb67dd8e8..6cfb795595d 100644
--- a/app/assets/javascripts/pipelines/components/test_reports/test_summary_table.vue
+++ b/app/assets/javascripts/pipelines/components/test_reports/test_summary_table.vue
@@ -2,7 +2,6 @@
import { mapGetters } from 'vuex';
import { s__ } from '~/locale';
import { GlIcon, GlTooltipDirective } from '@gitlab/ui';
-import store from '~/pipelines/stores/test_reports';
import SmartVirtualList from '~/vue_shared/components/smart_virtual_list.vue';
export default {
@@ -14,12 +13,11 @@ export default {
directives: {
GlTooltip: GlTooltipDirective,
},
- store,
props: {
heading: {
type: String,
required: false,
- default: s__('TestReports|Test suites'),
+ default: s__('TestReports|Jobs'),
},
},
computed: {
@@ -29,8 +27,8 @@ export default {
},
},
methods: {
- tableRowClick(suite) {
- this.$emit('row-click', suite);
+ tableRowClick(index) {
+ this.$emit('row-click', index);
},
},
maxShownRows: 20,
@@ -40,16 +38,16 @@ export default {
<template>
<div>
- <div class="row prepend-top-default">
+ <div class="row gl-mt-3">
<div class="col-12">
<h4>{{ heading }}</h4>
</div>
</div>
- <div v-if="hasSuites" class="test-reports-table append-bottom-default js-test-suites-table">
+ <div v-if="hasSuites" class="test-reports-table gl-mb-3 js-test-suites-table">
<div role="row" class="gl-responsive-table-row table-row-header font-weight-bold">
<div role="rowheader" class="table-section section-25 pl-3">
- {{ __('Suite') }}
+ {{ __('Job') }}
</div>
<div role="rowheader" class="table-section section-25">
{{ __('Duration') }}
@@ -84,7 +82,7 @@ export default {
:class="{
'gl-responsive-table-row-clickable cursor-pointer': !testSuite.suite_error,
}"
- @click="tableRowClick(testSuite)"
+ @click="tableRowClick(index)"
>
<div class="table-section section-25">
<div role="rowheader" class="table-mobile-header font-weight-bold">
diff --git a/app/assets/javascripts/pipelines/components/time_ago.vue b/app/assets/javascripts/pipelines/components/time_ago.vue
deleted file mode 100644
index 2a23a0f6744..00000000000
--- a/app/assets/javascripts/pipelines/components/time_ago.vue
+++ /dev/null
@@ -1,79 +0,0 @@
-<script>
-import iconTimerSvg from 'icons/_icon_timer.svg';
-import '../../lib/utils/datetime_utility';
-import tooltip from '../../vue_shared/directives/tooltip';
-import timeagoMixin from '../../vue_shared/mixins/timeago';
-
-export default {
- directives: {
- tooltip,
- },
- mixins: [timeagoMixin],
- props: {
- finishedTime: {
- type: String,
- required: true,
- },
- duration: {
- type: Number,
- required: true,
- },
- },
- data() {
- return {
- iconTimerSvg,
- };
- },
- computed: {
- hasDuration() {
- return this.duration > 0;
- },
- hasFinishedTime() {
- return this.finishedTime !== '';
- },
- durationFormatted() {
- const date = new Date(this.duration * 1000);
-
- let hh = date.getUTCHours();
- let mm = date.getUTCMinutes();
- let ss = date.getSeconds();
-
- // left pad
- if (hh < 10) {
- hh = `0${hh}`;
- }
- if (mm < 10) {
- mm = `0${mm}`;
- }
- if (ss < 10) {
- ss = `0${ss}`;
- }
-
- return `${hh}:${mm}:${ss}`;
- },
- },
-};
-</script>
-<template>
- <div class="table-section section-15 pipelines-time-ago">
- <div class="table-mobile-header" role="rowheader">{{ s__('Pipeline|Duration') }}</div>
- <div class="table-mobile-content">
- <p v-if="hasDuration" class="duration">
- <span v-html="iconTimerSvg"> </span> {{ durationFormatted }}
- </p>
-
- <p v-if="hasFinishedTime" class="finished-at d-none d-sm-none d-md-block">
- <i class="fa fa-calendar" aria-hidden="true"> </i>
-
- <time
- v-tooltip
- :title="tooltipTitle(finishedTime)"
- data-placement="top"
- data-container="body"
- >
- {{ timeFormatted(finishedTime) }}
- </time>
- </p>
- </div>
- </div>
-</template>
diff --git a/app/assets/javascripts/pipelines/components/tokens/pipeline_branch_name_token.vue b/app/assets/javascripts/pipelines/components/tokens/pipeline_branch_name_token.vue
deleted file mode 100644
index da14bb2d308..00000000000
--- a/app/assets/javascripts/pipelines/components/tokens/pipeline_branch_name_token.vue
+++ /dev/null
@@ -1,73 +0,0 @@
-<script>
-import { GlFilteredSearchToken, GlFilteredSearchSuggestion, GlLoadingIcon } from '@gitlab/ui';
-import Api from '~/api';
-import { FETCH_BRANCH_ERROR_MESSAGE, FILTER_PIPELINES_SEARCH_DELAY } from '../../constants';
-import createFlash from '~/flash';
-import { debounce } from 'lodash';
-
-export default {
- components: {
- GlFilteredSearchToken,
- GlFilteredSearchSuggestion,
- GlLoadingIcon,
- },
- props: {
- config: {
- type: Object,
- required: true,
- },
- value: {
- type: Object,
- required: true,
- },
- },
- data() {
- return {
- branches: null,
- loading: true,
- };
- },
- created() {
- this.fetchBranches();
- },
- methods: {
- fetchBranches(searchterm) {
- Api.branches(this.config.projectId, searchterm)
- .then(({ data }) => {
- this.branches = data.map(branch => branch.name);
- this.loading = false;
- })
- .catch(err => {
- createFlash(FETCH_BRANCH_ERROR_MESSAGE);
- this.loading = false;
- throw err;
- });
- },
- searchBranches: debounce(function debounceSearch({ data }) {
- this.fetchBranches(data);
- }, FILTER_PIPELINES_SEARCH_DELAY),
- },
-};
-</script>
-
-<template>
- <gl-filtered-search-token
- :config="config"
- v-bind="{ ...$props, ...$attrs }"
- v-on="$listeners"
- @input="searchBranches"
- >
- <template #suggestions>
- <gl-loading-icon v-if="loading" />
- <template v-else>
- <gl-filtered-search-suggestion
- v-for="(branch, index) in branches"
- :key="index"
- :value="branch"
- >
- {{ branch }}
- </gl-filtered-search-suggestion>
- </template>
- </template>
- </gl-filtered-search-token>
-</template>
diff --git a/app/assets/javascripts/pipelines/components/tokens/pipeline_tag_name_token.vue b/app/assets/javascripts/pipelines/components/tokens/pipeline_tag_name_token.vue
deleted file mode 100644
index 7b209c5fa12..00000000000
--- a/app/assets/javascripts/pipelines/components/tokens/pipeline_tag_name_token.vue
+++ /dev/null
@@ -1,64 +0,0 @@
-<script>
-import { GlFilteredSearchToken, GlFilteredSearchSuggestion, GlLoadingIcon } from '@gitlab/ui';
-import Api from '~/api';
-import { FETCH_TAG_ERROR_MESSAGE, FILTER_PIPELINES_SEARCH_DELAY } from '../../constants';
-import createFlash from '~/flash';
-import { debounce } from 'lodash';
-
-export default {
- components: {
- GlFilteredSearchToken,
- GlFilteredSearchSuggestion,
- GlLoadingIcon,
- },
- props: {
- config: {
- type: Object,
- required: true,
- },
- value: {
- type: Object,
- required: true,
- },
- },
- data() {
- return {
- tags: null,
- loading: true,
- };
- },
- created() {
- this.fetchTags();
- },
- methods: {
- fetchTags(searchTerm) {
- Api.tags(this.config.projectId, searchTerm)
- .then(({ data }) => {
- this.tags = data.map(tag => tag.name);
- this.loading = false;
- })
- .catch(err => {
- createFlash(FETCH_TAG_ERROR_MESSAGE);
- this.loading = false;
- throw err;
- });
- },
- searchTags: debounce(function debounceSearch({ data }) {
- this.fetchTags(data);
- }, FILTER_PIPELINES_SEARCH_DELAY),
- },
-};
-</script>
-
-<template>
- <gl-filtered-search-token v-bind="{ ...$props, ...$attrs }" v-on="$listeners" @input="searchTags">
- <template #suggestions>
- <gl-loading-icon v-if="loading" />
- <template v-else>
- <gl-filtered-search-suggestion v-for="(tag, index) in tags" :key="index" :value="tag">
- {{ tag }}
- </gl-filtered-search-suggestion>
- </template>
- </template>
- </gl-filtered-search-token>
-</template>
diff --git a/app/assets/javascripts/pipelines/components/tokens/pipeline_trigger_author_token.vue b/app/assets/javascripts/pipelines/components/tokens/pipeline_trigger_author_token.vue
deleted file mode 100644
index 4062a3b11bb..00000000000
--- a/app/assets/javascripts/pipelines/components/tokens/pipeline_trigger_author_token.vue
+++ /dev/null
@@ -1,117 +0,0 @@
-<script>
-import {
- GlFilteredSearchToken,
- GlAvatar,
- GlFilteredSearchSuggestion,
- GlDropdownDivider,
- GlLoadingIcon,
-} from '@gitlab/ui';
-import Api from '~/api';
-import createFlash from '~/flash';
-import { debounce } from 'lodash';
-import {
- ANY_TRIGGER_AUTHOR,
- FETCH_AUTHOR_ERROR_MESSAGE,
- FILTER_PIPELINES_SEARCH_DELAY,
-} from '../../constants';
-
-export default {
- anyTriggerAuthor: ANY_TRIGGER_AUTHOR,
- components: {
- GlFilteredSearchToken,
- GlAvatar,
- GlFilteredSearchSuggestion,
- GlDropdownDivider,
- GlLoadingIcon,
- },
- props: {
- config: {
- type: Object,
- required: true,
- },
- value: {
- type: Object,
- required: true,
- },
- },
- data() {
- return {
- users: [],
- loading: true,
- };
- },
- computed: {
- currentValue() {
- return this.value.data.toLowerCase();
- },
- activeUser() {
- return this.users.find(user => {
- return user.username.toLowerCase() === this.currentValue;
- });
- },
- },
- created() {
- this.fetchProjectUsers();
- },
- methods: {
- fetchProjectUsers(searchTerm) {
- Api.projectUsers(this.config.projectId, searchTerm)
- .then(users => {
- this.users = users;
- this.loading = false;
- })
- .catch(err => {
- createFlash(FETCH_AUTHOR_ERROR_MESSAGE);
- this.loading = false;
- throw err;
- });
- },
- searchAuthors: debounce(function debounceSearch({ data }) {
- this.fetchProjectUsers(data);
- }, FILTER_PIPELINES_SEARCH_DELAY),
- },
-};
-</script>
-
-<template>
- <gl-filtered-search-token
- :config="config"
- v-bind="{ ...$props, ...$attrs }"
- v-on="$listeners"
- @input="searchAuthors"
- >
- <template #view="{inputValue}">
- <gl-avatar
- v-if="activeUser"
- :size="16"
- :src="activeUser.avatar_url"
- shape="circle"
- class="gl-mr-2"
- />
- <span>{{ activeUser ? activeUser.name : inputValue }}</span>
- </template>
- <template #suggestions>
- <gl-filtered-search-suggestion :value="$options.anyTriggerAuthor">{{
- $options.anyTriggerAuthor
- }}</gl-filtered-search-suggestion>
- <gl-dropdown-divider />
-
- <gl-loading-icon v-if="loading" />
- <template v-else>
- <gl-filtered-search-suggestion
- v-for="user in users"
- :key="user.username"
- :value="user.username"
- >
- <div class="d-flex">
- <gl-avatar :size="32" :src="user.avatar_url" />
- <div>
- <div>{{ user.name }}</div>
- <div>@{{ user.username }}</div>
- </div>
- </div>
- </gl-filtered-search-suggestion>
- </template>
- </template>
- </gl-filtered-search-token>
-</template>
diff --git a/app/assets/javascripts/pipelines/constants.js b/app/assets/javascripts/pipelines/constants.js
index c709f329728..abe5e1060c8 100644
--- a/app/assets/javascripts/pipelines/constants.js
+++ b/app/assets/javascripts/pipelines/constants.js
@@ -7,6 +7,7 @@ export const FILTER_PIPELINES_SEARCH_DELAY = 200;
export const ANY_TRIGGER_AUTHOR = 'Any';
export const SUPPORTED_FILTER_PARAMETERS = ['username', 'ref', 'status'];
export const FILTER_TAG_IDENTIFIER = 'tag';
+export const SCHEDULE_ORIGIN = 'schedule';
export const TestStatus = {
FAILED: 'failed',
diff --git a/app/assets/javascripts/pipelines/mixins/pipelines.js b/app/assets/javascripts/pipelines/mixins/pipelines.js
index 876b30299fb..7710a96e5fb 100644
--- a/app/assets/javascripts/pipelines/mixins/pipelines.js
+++ b/app/assets/javascripts/pipelines/mixins/pipelines.js
@@ -1,11 +1,11 @@
import Visibility from 'visibilityjs';
import { GlLoadingIcon } from '@gitlab/ui';
-import { __ } from '../../locale';
-import createFlash from '../../flash';
-import Poll from '../../lib/utils/poll';
-import EmptyState from '../components/empty_state.vue';
-import SvgBlankState from '../components/blank_state.vue';
-import PipelinesTableComponent from '../components/pipelines_table.vue';
+import { __ } from '~/locale';
+import createFlash from '~/flash';
+import Poll from '~/lib/utils/poll';
+import EmptyState from '../components/pipelines_list/empty_state.vue';
+import SvgBlankState from '../components/pipelines_list/blank_state.vue';
+import PipelinesTableComponent from '../components/pipelines_list/pipelines_table.vue';
import eventHub from '../event_hub';
import { CANCEL_REQUEST } from '../constants';
diff --git a/app/assets/javascripts/pipelines/pipeline_details_bundle.js b/app/assets/javascripts/pipelines/pipeline_details_bundle.js
index 90109598542..f1102a9bddf 100644
--- a/app/assets/javascripts/pipelines/pipeline_details_bundle.js
+++ b/app/assets/javascripts/pipelines/pipeline_details_bundle.js
@@ -10,8 +10,7 @@ import PipelinesMediator from './pipeline_details_mediator';
import pipelineHeader from './components/header_component.vue';
import eventHub from './event_hub';
import TestReports from './components/test_reports/test_reports.vue';
-import testReportsStore from './stores/test_reports';
-import axios from '~/lib/utils/axios_utils';
+import createTestReportsStore from './stores/test_reports';
Vue.use(Translate);
@@ -93,15 +92,11 @@ const createPipelineHeaderApp = mediator => {
});
};
-const createPipelinesTabs = dataset => {
+const createPipelinesTabs = testReportsStore => {
const tabsElement = document.querySelector('.pipelines-tabs');
- const testReportsEnabled =
- window.gon && window.gon.features && window.gon.features.junitPipelineView;
-
- if (tabsElement && testReportsEnabled) {
- const fetchReportsAction = 'fetchReports';
- testReportsStore.dispatch('setEndpoint', dataset.testReportEndpoint);
+ if (tabsElement) {
+ const fetchReportsAction = 'fetchFullReport';
const isTestTabActive = Boolean(
document.querySelector('.pipelines-tabs > li > a.test-tab.active'),
);
@@ -121,28 +116,35 @@ const createPipelinesTabs = dataset => {
}
};
-const createTestDetails = detailsEndpoint => {
+const createTestDetails = () => {
+ if (!window.gon?.features?.junitPipelineView) {
+ return;
+ }
+
+ const el = document.querySelector('#js-pipeline-tests-detail');
+ const { fullReportEndpoint, summaryEndpoint, countEndpoint } = el?.dataset || {};
+
+ const testReportsStore = createTestReportsStore({
+ fullReportEndpoint,
+ summaryEndpoint: summaryEndpoint || countEndpoint,
+ useBuildSummaryReport: window.gon?.features?.buildReportSummary,
+ });
+
+ if (!window.gon?.features?.buildReportSummary) {
+ createPipelinesTabs(testReportsStore);
+ }
+
// eslint-disable-next-line no-new
new Vue({
- el: '#js-pipeline-tests-detail',
+ el,
components: {
TestReports,
},
+ store: testReportsStore,
render(createElement) {
return createElement('test-reports');
},
});
-
- axios
- .get(detailsEndpoint)
- .then(({ data }) => {
- if (!data.total_count) {
- return;
- }
-
- document.querySelector('.js-test-report-badge-counter').innerHTML = data.total_count;
- })
- .catch(() => {});
};
const createDagApp = () => {
@@ -151,7 +153,8 @@ const createDagApp = () => {
}
const el = document.querySelector('#js-pipeline-dag-vue');
- const graphUrl = el?.dataset?.pipelineDataPath;
+ const { pipelineDataPath, emptySvgPath, dagDocPath } = el?.dataset;
+
// eslint-disable-next-line no-new
new Vue({
el,
@@ -161,7 +164,9 @@ const createDagApp = () => {
render(createElement) {
return createElement('dag', {
props: {
- graphUrl,
+ graphUrl: pipelineDataPath,
+ emptySvgPath,
+ dagDocPath,
},
});
},
@@ -175,7 +180,6 @@ export default () => {
createPipelinesDetailApp(mediator);
createPipelineHeaderApp(mediator);
- createPipelinesTabs(dataset);
- createTestDetails(dataset.testReportsCountEndpoint);
+ createTestDetails();
createDagApp();
};
diff --git a/app/assets/javascripts/pipelines/stores/test_reports/actions.js b/app/assets/javascripts/pipelines/stores/test_reports/actions.js
index 71d875c1a83..ccacb9f7e97 100644
--- a/app/assets/javascripts/pipelines/stores/test_reports/actions.js
+++ b/app/assets/javascripts/pipelines/stores/test_reports/actions.js
@@ -3,17 +3,42 @@ import * as types from './mutation_types';
import createFlash from '~/flash';
import { s__ } from '~/locale';
-export const setEndpoint = ({ commit }, data) => commit(types.SET_ENDPOINT, data);
+export const fetchSummary = ({ state, commit, dispatch }) => {
+ // If we do this without the build_report_summary feature flag enabled
+ // it causes a race condition for toggleLoading and ruins the loading
+ // state in the application
+ if (state.useBuildSummaryReport) {
+ dispatch('toggleLoading');
+ }
-export const fetchReports = ({ state, commit, dispatch }) => {
+ return axios
+ .get(state.summaryEndpoint)
+ .then(({ data }) => {
+ commit(types.SET_SUMMARY, data);
+
+ if (!state.useBuildSummaryReport) {
+ // Set the tab counter badge to total_count
+ // This is temporary until we can server-side render that count number
+ // (see https://gitlab.com/gitlab-org/gitlab/-/issues/223134)
+ document.querySelector('.js-test-report-badge-counter').innerHTML = data.total_count || 0;
+ }
+ })
+ .catch(() => {
+ createFlash(s__('TestReports|There was an error fetching the summary.'));
+ })
+ .finally(() => {
+ if (state.useBuildSummaryReport) {
+ dispatch('toggleLoading');
+ }
+ });
+};
+
+export const fetchFullReport = ({ state, commit, dispatch }) => {
dispatch('toggleLoading');
return axios
- .get(state.endpoint)
- .then(response => {
- const { data } = response;
- commit(types.SET_REPORTS, data);
- })
+ .get(state.fullReportEndpoint)
+ .then(({ data }) => commit(types.SET_REPORTS, data))
.catch(() => {
createFlash(s__('TestReports|There was an error fetching the test reports.'));
})
@@ -22,8 +47,10 @@ export const fetchReports = ({ state, commit, dispatch }) => {
});
};
-export const setSelectedSuite = ({ commit }, data) => commit(types.SET_SELECTED_SUITE, data);
-export const removeSelectedSuite = ({ commit }) => commit(types.SET_SELECTED_SUITE, {});
+export const setSelectedSuiteIndex = ({ commit }, data) =>
+ commit(types.SET_SELECTED_SUITE_INDEX, data);
+export const removeSelectedSuiteIndex = ({ commit }) =>
+ commit(types.SET_SELECTED_SUITE_INDEX, null);
export const toggleLoading = ({ commit }) => commit(types.TOGGLE_LOADING);
// prevent babel-plugin-rewire from generating an invalid default during karma tests
diff --git a/app/assets/javascripts/pipelines/stores/test_reports/getters.js b/app/assets/javascripts/pipelines/stores/test_reports/getters.js
index 788c1d32987..877762b77c9 100644
--- a/app/assets/javascripts/pipelines/stores/test_reports/getters.js
+++ b/app/assets/javascripts/pipelines/stores/test_reports/getters.js
@@ -9,14 +9,12 @@ export const getTestSuites = state => {
}));
};
-export const getSuiteTests = state => {
- const { selectedSuite } = state;
-
- if (selectedSuite.test_cases) {
- return selectedSuite.test_cases.sort(sortTestCases).map(addIconStatus);
- }
+export const getSelectedSuite = state =>
+ state.testReports?.test_suites?.[state.selectedSuiteIndex] || {};
- return [];
+export const getSuiteTests = state => {
+ const { test_cases: testCases = [] } = getSelectedSuite(state);
+ return testCases.sort(sortTestCases).map(addIconStatus);
};
// prevent babel-plugin-rewire from generating an invalid default during karma tests
diff --git a/app/assets/javascripts/pipelines/stores/test_reports/index.js b/app/assets/javascripts/pipelines/stores/test_reports/index.js
index 318dff5bcb2..88f61b09025 100644
--- a/app/assets/javascripts/pipelines/stores/test_reports/index.js
+++ b/app/assets/javascripts/pipelines/stores/test_reports/index.js
@@ -7,9 +7,10 @@ import mutations from './mutations';
Vue.use(Vuex);
-export default new Vuex.Store({
- actions,
- getters,
- mutations,
- state,
-});
+export default initialState =>
+ new Vuex.Store({
+ actions,
+ getters,
+ mutations,
+ state: state(initialState),
+ });
diff --git a/app/assets/javascripts/pipelines/stores/test_reports/mutation_types.js b/app/assets/javascripts/pipelines/stores/test_reports/mutation_types.js
index 832e45cf7a1..76405557b51 100644
--- a/app/assets/javascripts/pipelines/stores/test_reports/mutation_types.js
+++ b/app/assets/javascripts/pipelines/stores/test_reports/mutation_types.js
@@ -1,4 +1,4 @@
-export const SET_ENDPOINT = 'SET_ENDPOINT';
export const SET_REPORTS = 'SET_REPORTS';
-export const SET_SELECTED_SUITE = 'SET_SELECTED_SUITE';
+export const SET_SELECTED_SUITE_INDEX = 'SET_SELECTED_SUITE_INDEX';
+export const SET_SUMMARY = 'SET_SUMMARY';
export const TOGGLE_LOADING = 'TOGGLE_LOADING';
diff --git a/app/assets/javascripts/pipelines/stores/test_reports/mutations.js b/app/assets/javascripts/pipelines/stores/test_reports/mutations.js
index 349e6ec0469..2531ab1e87c 100644
--- a/app/assets/javascripts/pipelines/stores/test_reports/mutations.js
+++ b/app/assets/javascripts/pipelines/stores/test_reports/mutations.js
@@ -1,16 +1,16 @@
import * as types from './mutation_types';
export default {
- [types.SET_ENDPOINT](state, endpoint) {
- Object.assign(state, { endpoint });
+ [types.SET_REPORTS](state, testReports) {
+ Object.assign(state, { testReports, hasFullReport: true });
},
- [types.SET_REPORTS](state, testReports) {
- Object.assign(state, { testReports });
+ [types.SET_SELECTED_SUITE_INDEX](state, selectedSuiteIndex) {
+ Object.assign(state, { selectedSuiteIndex });
},
- [types.SET_SELECTED_SUITE](state, selectedSuite) {
- Object.assign(state, { selectedSuite });
+ [types.SET_SUMMARY](state, summary) {
+ Object.assign(state, { testReports: { ...state.testReports, ...summary } });
},
[types.TOGGLE_LOADING](state) {
diff --git a/app/assets/javascripts/pipelines/stores/test_reports/state.js b/app/assets/javascripts/pipelines/stores/test_reports/state.js
index 80a0c2a46a0..bcf5c147916 100644
--- a/app/assets/javascripts/pipelines/stores/test_reports/state.js
+++ b/app/assets/javascripts/pipelines/stores/test_reports/state.js
@@ -1,6 +1,13 @@
-export default () => ({
- endpoint: '',
+export default ({
+ fullReportEndpoint = '',
+ summaryEndpoint = '',
+ useBuildSummaryReport = false,
+}) => ({
+ summaryEndpoint,
+ fullReportEndpoint,
testReports: {},
- selectedSuite: {},
+ selectedSuiteIndex: null,
+ hasFullReport: false,
isLoading: false,
+ useBuildSummaryReport,
});
diff --git a/app/assets/javascripts/projects/commits/store/actions.js b/app/assets/javascripts/projects/commits/store/actions.js
index 0a52a92ae9d..f0832bd36a5 100644
--- a/app/assets/javascripts/projects/commits/store/actions.js
+++ b/app/assets/javascripts/projects/commits/store/actions.js
@@ -18,7 +18,7 @@ export default {
fetchAuthors({ dispatch, state }, author = null) {
const { projectId } = state;
return axios
- .get(joinPaths(gon.relative_url_root || '', '/autocomplete/users.json'), {
+ .get(joinPaths(gon.relative_url_root || '', '/-/autocomplete/users.json'), {
params: {
project_id: projectId,
active: true,
diff --git a/app/assets/javascripts/projects/components/remove_modal.vue b/app/assets/javascripts/projects/components/remove_modal.vue
new file mode 100644
index 00000000000..37f58efcb30
--- /dev/null
+++ b/app/assets/javascripts/projects/components/remove_modal.vue
@@ -0,0 +1,108 @@
+<script>
+import { GlModal, GlModalDirective, GlSprintf, GlFormInput, GlButton } from '@gitlab/ui';
+import { __ } from '~/locale';
+import { rstrip } from '~/lib/utils/common_utils';
+import csrf from '~/lib/utils/csrf';
+
+export default {
+ components: {
+ GlModal,
+ GlSprintf,
+ GlFormInput,
+ GlButton,
+ },
+ directives: {
+ GlModal: GlModalDirective,
+ },
+ props: {
+ confirmPhrase: {
+ type: String,
+ required: true,
+ },
+ warningMessage: {
+ type: String,
+ required: true,
+ },
+ formPath: {
+ type: String,
+ required: true,
+ },
+ },
+ data() {
+ return {
+ userInput: null,
+ };
+ },
+ computed: {
+ buttonDisabled() {
+ return rstrip(this.userInput) !== this.confirmPhrase;
+ },
+ csrfToken() {
+ return csrf.token;
+ },
+ },
+ methods: {
+ submitForm() {
+ this.$refs.form.submit();
+ },
+ },
+ strings: {
+ removeProject: __('Remove project'),
+ title: __('Confirmation required'),
+ confirm: __('Confirm'),
+ dataLoss: __(
+ 'This action can lead to data loss. To prevent accidental actions we ask you to confirm your intention.',
+ ),
+ confirmText: __('Please type %{phrase_code} to proceed or close this modal to cancel.'),
+ },
+ modalId: 'remove-project-modal',
+};
+</script>
+
+<template>
+ <form ref="form" :action="formPath" method="post">
+ <input type="hidden" name="_method" value="delete" />
+ <input :value="csrfToken" type="hidden" name="authenticity_token" />
+ <gl-button v-gl-modal="$options.modalId" category="primary" variant="danger">{{
+ $options.strings.removeProject
+ }}</gl-button>
+ <gl-modal
+ ref="removeModal"
+ :modal-id="$options.modalId"
+ size="sm"
+ ok-variant="danger"
+ footer-class="bg-gray-light gl-p-5"
+ >
+ <template #modal-title>{{ $options.strings.title }}</template>
+ <template #modal-footer>
+ <div class="gl-w-full gl-display-flex gl-just-content-start gl-m-0">
+ <gl-button
+ :disabled="buttonDisabled"
+ category="primary"
+ variant="danger"
+ @click="submitForm"
+ >
+ {{ $options.strings.confirm }}
+ </gl-button>
+ </div>
+ </template>
+ <div>
+ <p class="gl-text-red-500 gl-font-weight-bold">{{ warningMessage }}</p>
+ <p class="gl-mb-0">{{ $options.strings.dataLoss }}</p>
+ <p>
+ <gl-sprintf :message="$options.strings.confirmText">
+ <template #phrase_code>
+ <code>{{ confirmPhrase }}</code>
+ </template>
+ </gl-sprintf>
+ </p>
+ <gl-form-input
+ id="confirm_name_input"
+ v-model="userInput"
+ name="confirm_name_input"
+ type="text"
+ />
+ </div>
+ </gl-modal>
+ </form>
+</template>
diff --git a/app/assets/javascripts/projects/pipelines/charts/components/pipelines_area_chart.vue b/app/assets/javascripts/projects/pipelines/charts/components/pipelines_area_chart.vue
index d701f238a2e..d726196aadf 100644
--- a/app/assets/javascripts/projects/pipelines/charts/components/pipelines_area_chart.vue
+++ b/app/assets/javascripts/projects/pipelines/charts/components/pipelines_area_chart.vue
@@ -28,7 +28,7 @@ export default {
};
</script>
<template>
- <div class="prepend-top-default">
+ <div class="gl-mt-3">
<p>
<slot></slot>
</p>
diff --git a/app/assets/javascripts/projects/project_remove_modal.js b/app/assets/javascripts/projects/project_remove_modal.js
new file mode 100644
index 00000000000..dbdad1bf6f1
--- /dev/null
+++ b/app/assets/javascripts/projects/project_remove_modal.js
@@ -0,0 +1,24 @@
+import Vue from 'vue';
+import RemoveProjectModal from './components/remove_modal.vue';
+
+export default (selector = '#js-confirm-project-remove') => {
+ const el = document.querySelector(selector);
+
+ if (!el) return;
+
+ const { formPath, confirmPhrase, warningMessage } = el.dataset;
+
+ // eslint-disable-next-line no-new
+ new Vue({
+ el,
+ render(createElement) {
+ return createElement(RemoveProjectModal, {
+ props: {
+ confirmPhrase,
+ warningMessage,
+ formPath,
+ },
+ });
+ },
+ });
+};
diff --git a/app/assets/javascripts/projects/settings_service_desk/components/service_desk_root.vue b/app/assets/javascripts/projects/settings_service_desk/components/service_desk_root.vue
new file mode 100644
index 00000000000..d61569fcd6e
--- /dev/null
+++ b/app/assets/javascripts/projects/settings_service_desk/components/service_desk_root.vue
@@ -0,0 +1,160 @@
+<script>
+import { GlAlert } from '@gitlab/ui';
+import { __ } from '~/locale';
+import ServiceDeskSetting from './service_desk_setting.vue';
+import ServiceDeskService from '../services/service_desk_service';
+import eventHub from '../event_hub';
+
+export default {
+ name: 'ServiceDeskRoot',
+ components: {
+ GlAlert,
+ ServiceDeskSetting,
+ },
+ props: {
+ initialIsEnabled: {
+ type: Boolean,
+ required: true,
+ },
+ endpoint: {
+ type: String,
+ required: true,
+ },
+ initialIncomingEmail: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ selectedTemplate: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ outgoingName: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ projectKey: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ templates: {
+ type: Array,
+ required: false,
+ default: () => [],
+ },
+ },
+
+ data() {
+ return {
+ isEnabled: this.initialIsEnabled,
+ incomingEmail: this.initialIncomingEmail,
+ isTemplateSaving: false,
+ isAlertShowing: false,
+ alertVariant: 'danger',
+ alertMessage: '',
+ };
+ },
+
+ created() {
+ eventHub.$on('serviceDeskEnabledCheckboxToggled', this.onEnableToggled);
+ eventHub.$on('serviceDeskTemplateSave', this.onSaveTemplate);
+
+ this.service = new ServiceDeskService(this.endpoint);
+
+ if (this.isEnabled && !this.incomingEmail) {
+ this.fetchIncomingEmail();
+ }
+ },
+
+ beforeDestroy() {
+ eventHub.$off('serviceDeskEnabledCheckboxToggled', this.onEnableToggled);
+ eventHub.$off('serviceDeskTemplateSave', this.onSaveTemplate);
+ },
+
+ methods: {
+ fetchIncomingEmail() {
+ this.service
+ .fetchIncomingEmail()
+ .then(({ data }) => {
+ const email = data.service_desk_address;
+ if (!email) {
+ throw new Error(__("Response didn't include `service_desk_address`"));
+ }
+
+ this.incomingEmail = email;
+ })
+ .catch(() =>
+ this.showAlert(__('An error occurred while fetching the Service Desk address.')),
+ );
+ },
+
+ onEnableToggled(isChecked) {
+ this.isEnabled = isChecked;
+ this.incomingEmail = '';
+
+ this.service
+ .toggleServiceDesk(isChecked)
+ .then(({ data }) => {
+ const email = data.service_desk_address;
+ if (isChecked && !email) {
+ throw new Error(__("Response didn't include `service_desk_address`"));
+ }
+
+ this.incomingEmail = email;
+ })
+ .catch(() => {
+ const message = isChecked
+ ? __('An error occurred while enabling Service Desk.')
+ : __('An error occurred while disabling Service Desk.');
+
+ this.showAlert(message);
+ });
+ },
+
+ onSaveTemplate({ selectedTemplate, outgoingName, projectKey }) {
+ this.isTemplateSaving = true;
+ this.service
+ .updateTemplate({ selectedTemplate, outgoingName, projectKey }, this.isEnabled)
+ .then(() => this.showAlert(__('Template was successfully saved.'), 'success'))
+ .catch(() =>
+ this.showAlert(
+ __('An error occurred while saving the template. Please check if the template exists.'),
+ ),
+ )
+ .finally(() => {
+ this.isTemplateSaving = false;
+ });
+ },
+
+ showAlert(message, variant = 'danger') {
+ this.isAlertShowing = true;
+ this.alertMessage = message;
+ this.alertVariant = variant;
+ },
+
+ onDismiss() {
+ this.isAlertShowing = false;
+ },
+ },
+};
+</script>
+
+<template>
+ <div>
+ <gl-alert v-if="isAlertShowing" class="mb-3" :variant="alertVariant" @dismiss="onDismiss">
+ {{ alertMessage }}
+ </gl-alert>
+ <service-desk-setting
+ :is-enabled="isEnabled"
+ :incoming-email="incomingEmail"
+ :initial-selected-template="selectedTemplate"
+ :initial-outgoing-name="outgoingName"
+ :initial-project-key="projectKey"
+ :templates="templates"
+ :is-template-saving="isTemplateSaving"
+ />
+ </div>
+</template>
diff --git a/app/assets/javascripts/projects/settings_service_desk/components/service_desk_setting.vue b/app/assets/javascripts/projects/settings_service_desk/components/service_desk_setting.vue
new file mode 100644
index 00000000000..43c20fea43e
--- /dev/null
+++ b/app/assets/javascripts/projects/settings_service_desk/components/service_desk_setting.vue
@@ -0,0 +1,169 @@
+<script>
+import { GlDeprecatedButton, GlFormSelect, GlToggle, GlLoadingIcon } from '@gitlab/ui';
+import { __ } from '~/locale';
+import tooltip from '~/vue_shared/directives/tooltip';
+import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
+import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
+import eventHub from '../event_hub';
+
+export default {
+ name: 'ServiceDeskSetting',
+ directives: {
+ tooltip,
+ },
+ components: {
+ ClipboardButton,
+ GlDeprecatedButton,
+ GlFormSelect,
+ GlToggle,
+ GlLoadingIcon,
+ },
+ mixins: [glFeatureFlagsMixin()],
+ props: {
+ isEnabled: {
+ type: Boolean,
+ required: true,
+ },
+ incomingEmail: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ initialSelectedTemplate: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ initialOutgoingName: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ initialProjectKey: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ templates: {
+ type: Array,
+ required: false,
+ default: () => [],
+ },
+ isTemplateSaving: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ },
+ data() {
+ return {
+ selectedTemplate: this.initialSelectedTemplate,
+ outgoingName: this.initialOutgoingName || __('GitLab Support Bot'),
+ projectKey: this.initialProjectKey,
+ };
+ },
+ computed: {
+ templateOptions() {
+ return [''].concat(this.templates);
+ },
+ hasProjectKeySupport() {
+ return Boolean(this.glFeatures.serviceDeskCustomAddress);
+ },
+ },
+ methods: {
+ onCheckboxToggle(isChecked) {
+ eventHub.$emit('serviceDeskEnabledCheckboxToggled', isChecked);
+ },
+ onSaveTemplate() {
+ eventHub.$emit('serviceDeskTemplateSave', {
+ selectedTemplate: this.selectedTemplate,
+ outgoingName: this.outgoingName,
+ projectKey: this.projectKey,
+ });
+ },
+ },
+};
+</script>
+
+<template>
+ <div>
+ <gl-toggle
+ id="service-desk-checkbox"
+ :value="isEnabled"
+ class="d-inline-block align-middle mr-1"
+ label="Service desk"
+ label-position="left"
+ @change="onCheckboxToggle"
+ />
+ <label class="align-middle" for="service-desk-checkbox">
+ {{ __('Activate Service Desk') }}
+ </label>
+ <div v-if="isEnabled" class="row mt-3">
+ <div class="col-md-9 mb-0">
+ <strong id="incoming-email-describer" class="d-block mb-1">
+ {{ __('Forward external support email address to') }}
+ </strong>
+ <template v-if="incomingEmail">
+ <div class="input-group">
+ <input
+ ref="service-desk-incoming-email"
+ type="text"
+ class="form-control incoming-email h-auto"
+ :placeholder="__('Incoming email')"
+ :aria-label="__('Incoming email')"
+ aria-describedby="incoming-email-describer"
+ :value="incomingEmail"
+ disabled="true"
+ />
+ <div class="input-group-append">
+ <clipboard-button
+ :title="__('Copy')"
+ :text="incomingEmail"
+ css-class="btn qa-clipboard-button"
+ />
+ </div>
+ </div>
+ </template>
+ <template v-else>
+ <gl-loading-icon :inline="true" />
+ <span class="sr-only">{{ __('Fetching incoming email') }}</span>
+ </template>
+
+ <label for="service-desk-template-select" class="mt-3">
+ {{ __('Template to append to all Service Desk issues') }}
+ </label>
+ <gl-form-select
+ id="service-desk-template-select"
+ v-model="selectedTemplate"
+ :options="templateOptions"
+ />
+ <label for="service-desk-email-from-name" class="mt-3">
+ {{ __('Email display name') }}
+ </label>
+ <input id="service-desk-email-from-name" v-model.trim="outgoingName" class="form-control" />
+ <span class="form-text text-muted">
+ {{ __('Emails sent from Service Desk will have this name') }}
+ </span>
+ <template v-if="hasProjectKeySupport">
+ <label for="service-desk-project-suffix" class="mt-3">
+ {{ __('Project name suffix') }}
+ </label>
+ <input id="service-desk-project-suffix" v-model.trim="projectKey" class="form-control" />
+ <span class="form-text text-muted mb-3">
+ {{
+ __(
+ 'Project name suffix is a user-defined string which will be appended to the project path, and will form the Service Desk email address.',
+ )
+ }}
+ </span>
+ </template>
+ <gl-deprecated-button
+ variant="success"
+ :disabled="isTemplateSaving"
+ @click="onSaveTemplate"
+ >{{ __('Save template') }}</gl-deprecated-button
+ >
+ </div>
+ </div>
+ </div>
+</template>
diff --git a/app/assets/javascripts/projects/settings_service_desk/event_hub.js b/app/assets/javascripts/projects/settings_service_desk/event_hub.js
new file mode 100644
index 00000000000..e31806ad199
--- /dev/null
+++ b/app/assets/javascripts/projects/settings_service_desk/event_hub.js
@@ -0,0 +1,3 @@
+import createEventHub from '~/helpers/event_hub_factory';
+
+export default createEventHub();
diff --git a/app/assets/javascripts/projects/settings_service_desk/index.js b/app/assets/javascripts/projects/settings_service_desk/index.js
new file mode 100644
index 00000000000..15c077de72e
--- /dev/null
+++ b/app/assets/javascripts/projects/settings_service_desk/index.js
@@ -0,0 +1,41 @@
+import Vue from 'vue';
+import { parseBoolean } from '~/lib/utils/common_utils';
+import ServiceDeskRoot from './components/service_desk_root.vue';
+
+export default () => {
+ const serviceDeskRootElement = document.querySelector('.js-service-desk-setting-root');
+ if (serviceDeskRootElement) {
+ // eslint-disable-next-line no-new
+ new Vue({
+ el: serviceDeskRootElement,
+ components: {
+ ServiceDeskRoot,
+ },
+ data() {
+ const { dataset } = serviceDeskRootElement;
+ return {
+ initialIsEnabled: parseBoolean(dataset.enabled),
+ endpoint: dataset.endpoint,
+ incomingEmail: dataset.incomingEmail,
+ selectedTemplate: dataset.selectedTemplate,
+ outgoingName: dataset.outgoingName,
+ projectKey: dataset.projectKey,
+ templates: JSON.parse(dataset.templates),
+ };
+ },
+ render(createElement) {
+ return createElement('service-desk-root', {
+ props: {
+ initialIsEnabled: this.initialIsEnabled,
+ endpoint: this.endpoint,
+ initialIncomingEmail: this.incomingEmail,
+ selectedTemplate: this.selectedTemplate,
+ outgoingName: this.outgoingName,
+ projectKey: this.projectKey,
+ templates: this.templates,
+ },
+ });
+ },
+ });
+ }
+};
diff --git a/app/assets/javascripts/projects/settings_service_desk/services/service_desk_service.js b/app/assets/javascripts/projects/settings_service_desk/services/service_desk_service.js
new file mode 100644
index 00000000000..d707763c64e
--- /dev/null
+++ b/app/assets/javascripts/projects/settings_service_desk/services/service_desk_service.js
@@ -0,0 +1,27 @@
+import axios from '~/lib/utils/axios_utils';
+
+class ServiceDeskService {
+ constructor(endpoint) {
+ this.endpoint = endpoint;
+ }
+
+ fetchIncomingEmail() {
+ return axios.get(this.endpoint);
+ }
+
+ toggleServiceDesk(enable) {
+ return axios.put(this.endpoint, { service_desk_enabled: enable });
+ }
+
+ updateTemplate({ selectedTemplate, outgoingName, projectKey = '' }, isEnabled) {
+ const body = {
+ issue_template_key: selectedTemplate,
+ outgoing_name: outgoingName,
+ project_key: projectKey,
+ service_desk_enabled: isEnabled,
+ };
+ return axios.put(this.endpoint, body);
+ }
+}
+
+export default ServiceDeskService;
diff --git a/app/assets/javascripts/prometheus_alerts/components/reset_key.vue b/app/assets/javascripts/prometheus_alerts/components/reset_key.vue
index 15b6a29e5cf..941a05583ad 100644
--- a/app/assets/javascripts/prometheus_alerts/components/reset_key.vue
+++ b/app/assets/javascripts/prometheus_alerts/components/reset_key.vue
@@ -41,6 +41,11 @@ export default {
type: String,
required: true,
},
+ disabled: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
},
data() {
return {
@@ -88,7 +93,11 @@ export default {
<div class="input-group">
<gl-form-input id="notify-url" :readonly="true" :value="notifyUrl" />
<span class="input-group-append">
- <clipboard-button :text="notifyUrl" :title="$options.copyToClipboard" />
+ <clipboard-button
+ :text="notifyUrl"
+ :title="$options.copyToClipboard"
+ :disabled="disabled"
+ />
</span>
</div>
</gl-form-group>
@@ -100,7 +109,11 @@ export default {
<div class="input-group">
<gl-form-input id="authorization-key" :readonly="true" :value="authorizationKey" />
<span class="input-group-append">
- <clipboard-button :text="authorizationKey" :title="$options.copyToClipboard" />
+ <clipboard-button
+ :text="authorizationKey"
+ :title="$options.copyToClipboard"
+ :disabled="disabled"
+ />
</span>
</div>
</gl-form-group>
@@ -118,13 +131,20 @@ export default {
)
}}
</gl-modal>
- <gl-deprecated-button v-gl-modal.authKeyModal class="js-reset-auth-key">{{
- __('Reset key')
- }}</gl-deprecated-button>
+ <gl-deprecated-button
+ v-gl-modal.authKeyModal
+ class="js-reset-auth-key"
+ :disabled="disabled"
+ >{{ __('Reset key') }}</gl-deprecated-button
+ >
</template>
- <gl-deprecated-button v-else class="js-reset-auth-key" @click="resetKey">{{
- __('Generate key')
- }}</gl-deprecated-button>
+ <gl-deprecated-button
+ v-else
+ :disabled="disabled"
+ class="js-reset-auth-key"
+ @click="resetKey"
+ >{{ __('Generate key') }}</gl-deprecated-button
+ >
</div>
</div>
</template>
diff --git a/app/assets/javascripts/prometheus_alerts/index.js b/app/assets/javascripts/prometheus_alerts/index.js
index a42f19e5245..7efe6ed186b 100644
--- a/app/assets/javascripts/prometheus_alerts/index.js
+++ b/app/assets/javascripts/prometheus_alerts/index.js
@@ -8,7 +8,7 @@ export default () => {
return;
}
- const { authorizationKey, changeKeyUrl, notifyUrl, learnMoreUrl } = el.dataset;
+ const { authorizationKey, changeKeyUrl, notifyUrl, learnMoreUrl, disabled } = el.dataset;
// eslint-disable-next-line no-new
new Vue({
@@ -20,6 +20,7 @@ export default () => {
changeKeyUrl,
notifyUrl,
learnMoreUrl,
+ disabled,
},
});
},
diff --git a/app/assets/javascripts/ref/components/ref_results_section.vue b/app/assets/javascripts/ref/components/ref_results_section.vue
new file mode 100644
index 00000000000..32e916052c4
--- /dev/null
+++ b/app/assets/javascripts/ref/components/ref_results_section.vue
@@ -0,0 +1,124 @@
+<script>
+import { GlNewDropdownHeader, GlNewDropdownItem, GlBadge, GlIcon } from '@gitlab/ui';
+import { s__ } from '~/locale';
+
+export default {
+ name: 'RefResultsSection',
+ components: {
+ GlNewDropdownHeader,
+ GlNewDropdownItem,
+ GlBadge,
+ GlIcon,
+ },
+ props: {
+ sectionTitle: {
+ type: String,
+ required: true,
+ },
+
+ totalCount: {
+ type: Number,
+ required: true,
+ },
+
+ /**
+ * An array of object that have the following properties:
+ *
+ * - name (String, required): The name of the ref that will be displayed
+ * - value (String, optional): The value that will be selected when the ref
+ * is selected. If not provided, `name` will be used as the value.
+ * For example, commits use the short SHA for `name`
+ * and long SHA for `value`.
+ * - subtitle (String, optional): Text to render underneath the name.
+ * For example, used to render the commit's title underneath its SHA.
+ * - default (Boolean, optional): Whether or not to render a "default"
+ * indicator next to the item. Used to indicate
+ * the project's default branch.
+ *
+ */
+ items: {
+ type: Array,
+ required: true,
+ validator: items => Array.isArray(items) && items.every(item => item.name),
+ },
+
+ /**
+ * The currently selected ref.
+ * Used to render a check mark by the selected item.
+ * */
+ selectedRef: {
+ type: String,
+ required: false,
+ default: '',
+ },
+
+ /**
+ * An error object that indicates that an error
+ * occurred while fetching items for this section
+ */
+ error: {
+ type: Error,
+ required: false,
+ default: null,
+ },
+
+ /** The message to display if an error occurs */
+ errorMessage: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ },
+ computed: {
+ totalCountText() {
+ return this.totalCount > 999 ? s__('TotalRefCountIndicator|1000+') : `${this.totalCount}`;
+ },
+ },
+ methods: {
+ showCheck(item) {
+ return item.name === this.selectedRef || item.value === this.selectedRef;
+ },
+ },
+};
+</script>
+
+<template>
+ <div>
+ <gl-new-dropdown-header>
+ <div class="gl-display-flex align-items-center" data-testid="section-header">
+ <span class="gl-mr-2 gl-mb-1">{{ sectionTitle }}</span>
+ <gl-badge variant="neutral">{{ totalCountText }}</gl-badge>
+ </div>
+ </gl-new-dropdown-header>
+ <template v-if="error">
+ <div class="gl-display-flex align-items-start text-danger gl-ml-4 gl-mr-4 gl-mb-3">
+ <gl-icon name="error" class="gl-mr-2 gl-mt-2 gl-flex-shrink-0" />
+ <span>{{ errorMessage }}</span>
+ </div>
+ </template>
+ <template v-else>
+ <gl-new-dropdown-item
+ v-for="item in items"
+ :key="item.name"
+ @click="$emit('selected', item.value || item.name)"
+ >
+ <div class="gl-display-flex align-items-start">
+ <gl-icon
+ name="mobile-issue-close"
+ class="gl-mr-2 gl-flex-shrink-0"
+ :class="{ 'gl-visibility-hidden': !showCheck(item) }"
+ />
+
+ <div class="gl-flex-grow-1 gl-display-flex gl-flex-direction-column">
+ <span class="gl-font-monospace">{{ item.name }}</span>
+ <span class="gl-text-gray-600">{{ item.subtitle }}</span>
+ </div>
+
+ <gl-badge v-if="item.default" size="sm" variant="info">{{
+ s__('DefaultBranchLabel|default')
+ }}</gl-badge>
+ </div>
+ </gl-new-dropdown-item>
+ </template>
+ </div>
+</template>
diff --git a/app/assets/javascripts/ref/components/ref_selector.vue b/app/assets/javascripts/ref/components/ref_selector.vue
new file mode 100644
index 00000000000..012a391a3da
--- /dev/null
+++ b/app/assets/javascripts/ref/components/ref_selector.vue
@@ -0,0 +1,186 @@
+<script>
+import { mapActions, mapGetters, mapState } from 'vuex';
+import {
+ GlNewDropdown,
+ GlNewDropdownDivider,
+ GlNewDropdownHeader,
+ GlSearchBoxByType,
+ GlSprintf,
+ GlIcon,
+ GlLoadingIcon,
+} from '@gitlab/ui';
+import { debounce } from 'lodash';
+import createStore from '../stores';
+import { SEARCH_DEBOUNCE_MS, DEFAULT_I18N } from '../constants';
+import RefResultsSection from './ref_results_section.vue';
+
+export default {
+ name: 'RefSelector',
+ store: createStore(),
+ components: {
+ GlNewDropdown,
+ GlNewDropdownDivider,
+ GlNewDropdownHeader,
+ GlSearchBoxByType,
+ GlSprintf,
+ GlIcon,
+ GlLoadingIcon,
+ RefResultsSection,
+ },
+ props: {
+ value: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ projectId: {
+ type: String,
+ required: true,
+ },
+ translations: {
+ type: Object,
+ required: false,
+ default: () => ({}),
+ },
+ },
+ data() {
+ return {
+ query: '',
+ };
+ },
+ computed: {
+ ...mapState({
+ matches: state => state.matches,
+ lastQuery: state => state.query,
+ selectedRef: state => state.selectedRef,
+ }),
+ ...mapGetters(['isLoading', 'isQueryPossiblyASha']),
+ i18n() {
+ return {
+ ...DEFAULT_I18N,
+ ...this.translations,
+ };
+ },
+ showBranchesSection() {
+ return Boolean(this.matches.branches.totalCount > 0 || this.matches.branches.error);
+ },
+ showTagsSection() {
+ return Boolean(this.matches.tags.totalCount > 0 || this.matches.tags.error);
+ },
+ showCommitsSection() {
+ return Boolean(this.matches.commits.totalCount > 0 || this.matches.commits.error);
+ },
+ showNoResults() {
+ return !this.showBranchesSection && !this.showTagsSection && !this.showCommitsSection;
+ },
+ },
+ created() {
+ this.setProjectId(this.projectId);
+ this.search(this.query);
+ },
+ methods: {
+ ...mapActions(['setProjectId', 'setSelectedRef', 'search']),
+ focusSearchBox() {
+ this.$refs.searchBox.$el.querySelector('input').focus();
+ },
+ onSearchBoxInput: debounce(function search() {
+ this.search(this.query);
+ }, SEARCH_DEBOUNCE_MS),
+ selectRef(ref) {
+ this.setSelectedRef(ref);
+ this.$emit('input', this.selectedRef);
+ },
+ },
+};
+</script>
+
+<template>
+ <gl-new-dropdown class="ref-selector" @shown="focusSearchBox">
+ <template slot="button-content">
+ <span class="gl-flex-grow-1 gl-ml-2 gl-text-gray-600" data-testid="button-content">
+ <span v-if="selectedRef" class="gl-font-monospace">{{ selectedRef }}</span>
+ <span v-else>{{ i18n.noRefSelected }}</span>
+ </span>
+ <gl-icon name="chevron-down" />
+ </template>
+
+ <div class="gl-display-flex gl-flex-direction-column ref-selector-dropdown-content">
+ <gl-new-dropdown-header>
+ <span class="gl-text-center gl-display-block">{{ i18n.dropdownHeader }}</span>
+ </gl-new-dropdown-header>
+
+ <gl-new-dropdown-divider />
+
+ <gl-search-box-by-type
+ ref="searchBox"
+ v-model.trim="query"
+ class="gl-m-3"
+ :placeholder="i18n.searchPlaceholder"
+ @input="onSearchBoxInput"
+ />
+
+ <div class="gl-flex-grow-1 gl-overflow-y-auto">
+ <gl-loading-icon v-if="isLoading" size="lg" class="gl-my-3" />
+
+ <div
+ v-else-if="showNoResults"
+ class="gl-text-center gl-mx-3 gl-py-3"
+ data-testid="no-results"
+ >
+ <gl-sprintf v-if="lastQuery" :message="i18n.noResultsWithQuery">
+ <template #query>
+ <b class="gl-word-break-all">{{ lastQuery }}</b>
+ </template>
+ </gl-sprintf>
+
+ <span v-else>{{ i18n.noResults }}</span>
+ </div>
+
+ <template v-else>
+ <template v-if="showBranchesSection">
+ <ref-results-section
+ :section-title="i18n.branches"
+ :total-count="matches.branches.totalCount"
+ :items="matches.branches.list"
+ :selected-ref="selectedRef"
+ :error="matches.branches.error"
+ :error-message="i18n.branchesErrorMessage"
+ data-testid="branches-section"
+ @selected="selectRef($event)"
+ />
+
+ <gl-new-dropdown-divider v-if="showTagsSection || showCommitsSection" />
+ </template>
+
+ <template v-if="showTagsSection">
+ <ref-results-section
+ :section-title="i18n.tags"
+ :total-count="matches.tags.totalCount"
+ :items="matches.tags.list"
+ :selected-ref="selectedRef"
+ :error="matches.tags.error"
+ :error-message="i18n.tagsErrorMessage"
+ data-testid="tags-section"
+ @selected="selectRef($event)"
+ />
+
+ <gl-new-dropdown-divider v-if="showCommitsSection" />
+ </template>
+
+ <template v-if="showCommitsSection">
+ <ref-results-section
+ :section-title="i18n.commits"
+ :total-count="matches.commits.totalCount"
+ :items="matches.commits.list"
+ :selected-ref="selectedRef"
+ :error="matches.commits.error"
+ :error-message="i18n.commitsErrorMessage"
+ data-testid="commits-section"
+ @selected="selectRef($event)"
+ />
+ </template>
+ </template>
+ </div>
+ </div>
+ </gl-new-dropdown>
+</template>
diff --git a/app/assets/javascripts/ref/constants.js b/app/assets/javascripts/ref/constants.js
new file mode 100644
index 00000000000..ca82b951377
--- /dev/null
+++ b/app/assets/javascripts/ref/constants.js
@@ -0,0 +1,19 @@
+import { __ } from '~/locale';
+
+export const X_TOTAL_HEADER = 'x-total';
+
+export const SEARCH_DEBOUNCE_MS = 250;
+
+export const DEFAULT_I18N = Object.freeze({
+ dropdownHeader: __('Select Git revision'),
+ searchPlaceholder: __('Search by Git revision'),
+ noResultsWithQuery: __('No matching results for "%{query}"'),
+ noResults: __('No matching results'),
+ branchesErrorMessage: __('An error occurred while fetching branches. Retry the search.'),
+ tagsErrorMessage: __('An error occurred while fetching tags. Retry the search.'),
+ commitsErrorMessage: __('An error occurred while fetching commits. Retry the search.'),
+ branches: __('Branches'),
+ tags: __('Tags'),
+ commits: __('Commits'),
+ noRefSelected: __('No ref selected'),
+});
diff --git a/app/assets/javascripts/ref/stores/actions.js b/app/assets/javascripts/ref/stores/actions.js
new file mode 100644
index 00000000000..8fcc99cef38
--- /dev/null
+++ b/app/assets/javascripts/ref/stores/actions.js
@@ -0,0 +1,65 @@
+import Api from '~/api';
+import * as types from './mutation_types';
+
+export const setProjectId = ({ commit }, projectId) => commit(types.SET_PROJECT_ID, projectId);
+
+export const setSelectedRef = ({ commit }, selectedRef) =>
+ commit(types.SET_SELECTED_REF, selectedRef);
+
+export const search = ({ dispatch, commit }, query) => {
+ commit(types.SET_QUERY, query);
+
+ dispatch('searchBranches');
+ dispatch('searchTags');
+ dispatch('searchCommits');
+};
+
+export const searchBranches = ({ commit, state }) => {
+ commit(types.REQUEST_START);
+
+ Api.branches(state.projectId, state.query)
+ .then(response => {
+ commit(types.RECEIVE_BRANCHES_SUCCESS, response);
+ })
+ .catch(error => {
+ commit(types.RECEIVE_BRANCHES_ERROR, error);
+ })
+ .finally(() => {
+ commit(types.REQUEST_FINISH);
+ });
+};
+
+export const searchTags = ({ commit, state }) => {
+ commit(types.REQUEST_START);
+
+ Api.tags(state.projectId, state.query)
+ .then(response => {
+ commit(types.RECEIVE_TAGS_SUCCESS, response);
+ })
+ .catch(error => {
+ commit(types.RECEIVE_TAGS_ERROR, error);
+ })
+ .finally(() => {
+ commit(types.REQUEST_FINISH);
+ });
+};
+
+export const searchCommits = ({ commit, state, getters }) => {
+ // Only query the Commit API if the search query looks like a commit SHA
+ if (getters.isQueryPossiblyASha) {
+ commit(types.REQUEST_START);
+
+ Api.commit(state.projectId, state.query)
+ .then(response => {
+ commit(types.RECEIVE_COMMITS_SUCCESS, response);
+ })
+ .catch(error => {
+ commit(types.RECEIVE_COMMITS_ERROR, error);
+ })
+ .finally(() => {
+ commit(types.REQUEST_FINISH);
+ });
+ } else {
+ commit(types.RESET_COMMIT_MATCHES);
+ }
+};
diff --git a/app/assets/javascripts/ref/stores/getters.js b/app/assets/javascripts/ref/stores/getters.js
new file mode 100644
index 00000000000..02d4ae8ff91
--- /dev/null
+++ b/app/assets/javascripts/ref/stores/getters.js
@@ -0,0 +1,5 @@
+/** Returns `true` if the query string looks like it could be a commit SHA */
+export const isQueryPossiblyASha = ({ query }) => /^[0-9a-f]{4,40}$/i.test(query);
+
+/** Returns `true` if there is at least one in-progress request */
+export const isLoading = ({ requestCount }) => requestCount > 0;
diff --git a/app/assets/javascripts/ref/stores/index.js b/app/assets/javascripts/ref/stores/index.js
new file mode 100644
index 00000000000..2bebffc19ab
--- /dev/null
+++ b/app/assets/javascripts/ref/stores/index.js
@@ -0,0 +1,16 @@
+import Vue from 'vue';
+import Vuex from 'vuex';
+import * as actions from './actions';
+import * as getters from './getters';
+import mutations from './mutations';
+import createState from './state';
+
+Vue.use(Vuex);
+
+export default () =>
+ new Vuex.Store({
+ actions,
+ getters,
+ mutations,
+ state: createState(),
+ });
diff --git a/app/assets/javascripts/ref/stores/mutation_types.js b/app/assets/javascripts/ref/stores/mutation_types.js
new file mode 100644
index 00000000000..9f6195f5f3f
--- /dev/null
+++ b/app/assets/javascripts/ref/stores/mutation_types.js
@@ -0,0 +1,16 @@
+export const SET_PROJECT_ID = 'SET_PROJECT_ID';
+export const SET_SELECTED_REF = 'SET_SELECTED_REF';
+export const SET_QUERY = 'SET_QUERY';
+
+export const REQUEST_START = 'REQUEST_START';
+export const REQUEST_FINISH = 'REQUEST_FINISH';
+
+export const RECEIVE_BRANCHES_SUCCESS = 'RECEIVE_BRANCHES_SUCCESS';
+export const RECEIVE_BRANCHES_ERROR = 'RECEIVE_BRANCHES_ERROR';
+
+export const RECEIVE_TAGS_SUCCESS = 'RECEIVE_TAGS_SUCCESS';
+export const RECEIVE_TAGS_ERROR = 'RECEIVE_TAGS_ERROR';
+
+export const RECEIVE_COMMITS_SUCCESS = 'RECEIVE_COMMITS_SUCCESS';
+export const RECEIVE_COMMITS_ERROR = 'RECEIVE_COMMITS_ERROR';
+export const RESET_COMMIT_MATCHES = 'RESET_COMMIT_MATCHES';
diff --git a/app/assets/javascripts/ref/stores/mutations.js b/app/assets/javascripts/ref/stores/mutations.js
new file mode 100644
index 00000000000..73f9d7ee487
--- /dev/null
+++ b/app/assets/javascripts/ref/stores/mutations.js
@@ -0,0 +1,91 @@
+import * as types from './mutation_types';
+import { X_TOTAL_HEADER } from '../constants';
+import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
+import httpStatusCodes from '~/lib/utils/http_status';
+
+export default {
+ [types.SET_PROJECT_ID](state, projectId) {
+ state.projectId = projectId;
+ },
+ [types.SET_SELECTED_REF](state, selectedRef) {
+ state.selectedRef = selectedRef;
+ },
+ [types.SET_QUERY](state, query) {
+ state.query = query;
+ },
+
+ [types.REQUEST_START](state) {
+ state.requestCount += 1;
+ },
+ [types.REQUEST_FINISH](state) {
+ state.requestCount -= 1;
+ },
+
+ [types.RECEIVE_BRANCHES_SUCCESS](state, response) {
+ state.matches.branches = {
+ list: convertObjectPropsToCamelCase(response.data).map(b => ({
+ name: b.name,
+ default: b.default,
+ })),
+ totalCount: parseInt(response.headers[X_TOTAL_HEADER], 10),
+ error: null,
+ };
+ },
+ [types.RECEIVE_BRANCHES_ERROR](state, error) {
+ state.matches.branches = {
+ list: [],
+ totalCount: 0,
+ error,
+ };
+ },
+
+ [types.RECEIVE_TAGS_SUCCESS](state, response) {
+ state.matches.tags = {
+ list: convertObjectPropsToCamelCase(response.data).map(b => ({
+ name: b.name,
+ })),
+ totalCount: parseInt(response.headers[X_TOTAL_HEADER], 10),
+ error: null,
+ };
+ },
+ [types.RECEIVE_TAGS_ERROR](state, error) {
+ state.matches.tags = {
+ list: [],
+ totalCount: 0,
+ error,
+ };
+ },
+
+ [types.RECEIVE_COMMITS_SUCCESS](state, response) {
+ const commit = convertObjectPropsToCamelCase(response.data);
+
+ state.matches.commits = {
+ list: [
+ {
+ name: commit.shortId,
+ value: commit.id,
+ subtitle: commit.title,
+ },
+ ],
+ totalCount: 1,
+ error: null,
+ };
+ },
+ [types.RECEIVE_COMMITS_ERROR](state, error) {
+ state.matches.commits = {
+ list: [],
+ totalCount: 0,
+
+ // 404's are expected when the search query doesn't match any commits
+ // and shouldn't be treated as an actual error
+ error: error.response?.status !== httpStatusCodes.NOT_FOUND ? error : null,
+ };
+ },
+ [types.RESET_COMMIT_MATCHES](state) {
+ state.matches.commits = {
+ list: [],
+ totalCount: 0,
+ error: null,
+ };
+ },
+};
diff --git a/app/assets/javascripts/ref/stores/state.js b/app/assets/javascripts/ref/stores/state.js
new file mode 100644
index 00000000000..65b9d6449d7
--- /dev/null
+++ b/app/assets/javascripts/ref/stores/state.js
@@ -0,0 +1,24 @@
+export default () => ({
+ projectId: null,
+
+ query: '',
+ matches: {
+ branches: {
+ list: [],
+ totalCount: 0,
+ error: null,
+ },
+ tags: {
+ list: [],
+ totalCount: 0,
+ error: null,
+ },
+ commits: {
+ list: [],
+ totalCount: 0,
+ error: null,
+ },
+ },
+ selectedRef: null,
+ requestCount: 0,
+});
diff --git a/app/assets/javascripts/registry/explorer/components/delete_button.vue b/app/assets/javascripts/registry/explorer/components/delete_button.vue
new file mode 100644
index 00000000000..dab6a26ea16
--- /dev/null
+++ b/app/assets/javascripts/registry/explorer/components/delete_button.vue
@@ -0,0 +1,56 @@
+<script>
+import { GlTooltipDirective, GlButton } from '@gitlab/ui';
+
+export default {
+ name: 'DeleteButton',
+ components: {
+ GlButton,
+ },
+ directives: {
+ GlTooltip: GlTooltipDirective,
+ },
+ props: {
+ title: {
+ type: String,
+ required: true,
+ },
+ tooltipTitle: {
+ type: String,
+ required: true,
+ },
+ disabled: {
+ type: Boolean,
+ default: false,
+ required: false,
+ },
+ tooltipDisabled: {
+ type: Boolean,
+ default: false,
+ required: false,
+ },
+ },
+ computed: {
+ tooltipConfiguration() {
+ return {
+ disabled: this.tooltipDisabled,
+ title: this.tooltipTitle,
+ };
+ },
+ },
+};
+</script>
+
+<template>
+ <div v-gl-tooltip="tooltipConfiguration">
+ <gl-button
+ v-gl-tooltip
+ :disabled="disabled"
+ :title="title"
+ :aria-label="title"
+ category="secondary"
+ variant="danger"
+ icon="remove"
+ @click="$emit('delete')"
+ />
+ </div>
+</template>
diff --git a/app/assets/javascripts/registry/explorer/components/details_page/details_row.vue b/app/assets/javascripts/registry/explorer/components/details_page/details_row.vue
new file mode 100644
index 00000000000..c4358b83e23
--- /dev/null
+++ b/app/assets/javascripts/registry/explorer/components/details_page/details_row.vue
@@ -0,0 +1,26 @@
+<script>
+import { GlIcon } from '@gitlab/ui';
+
+export default {
+ components: {
+ GlIcon,
+ },
+ props: {
+ icon: {
+ type: String,
+ required: true,
+ },
+ },
+};
+</script>
+
+<template>
+ <div
+ class="gl-display-flex gl-align-items-center gl-font-monospace gl-font-sm gl-py-2 gl-word-break-all"
+ >
+ <gl-icon :name="icon" class="gl-mr-4" />
+ <span>
+ <slot></slot>
+ </span>
+ </div>
+</template>
diff --git a/app/assets/javascripts/registry/explorer/components/details_page/tags_list.vue b/app/assets/javascripts/registry/explorer/components/details_page/tags_list.vue
new file mode 100644
index 00000000000..8494967ab57
--- /dev/null
+++ b/app/assets/javascripts/registry/explorer/components/details_page/tags_list.vue
@@ -0,0 +1,77 @@
+<script>
+import { GlButton } from '@gitlab/ui';
+import TagsListRow from './tags_list_row.vue';
+import { REMOVE_TAGS_BUTTON_TITLE, TAGS_LIST_TITLE } from '../../constants/index';
+
+export default {
+ components: {
+ GlButton,
+ TagsListRow,
+ },
+ props: {
+ tags: {
+ type: Array,
+ required: false,
+ default: () => [],
+ },
+ isDesktop: {
+ type: Boolean,
+ default: false,
+ required: false,
+ },
+ },
+ i18n: {
+ REMOVE_TAGS_BUTTON_TITLE,
+ TAGS_LIST_TITLE,
+ },
+ data() {
+ return {
+ selectedItems: {},
+ };
+ },
+ computed: {
+ hasSelectedItems() {
+ return this.tags.some(tag => this.selectedItems[tag.name]);
+ },
+ showMultiDeleteButton() {
+ return this.tags.some(tag => tag.destroy_path) && this.isDesktop;
+ },
+ },
+ methods: {
+ updateSelectedItems(name) {
+ this.$set(this.selectedItems, name, !this.selectedItems[name]);
+ },
+ },
+};
+</script>
+
+<template>
+ <div>
+ <div class="gl-display-flex gl-justify-content-space-between gl-mb-3">
+ <h5 data-testid="list-title">
+ {{ $options.i18n.TAGS_LIST_TITLE }}
+ </h5>
+
+ <gl-button
+ v-if="showMultiDeleteButton"
+ :disabled="!hasSelectedItems"
+ category="secondary"
+ variant="danger"
+ @click="$emit('delete', selectedItems)"
+ >
+ {{ $options.i18n.REMOVE_TAGS_BUTTON_TITLE }}
+ </gl-button>
+ </div>
+ <tags-list-row
+ v-for="(tag, index) in tags"
+ :key="tag.path"
+ :tag="tag"
+ :first="index === 0"
+ :last="index === tags.length - 1"
+ :selected="selectedItems[tag.name]"
+ :is-desktop="isDesktop"
+ @select="updateSelectedItems(tag.name)"
+ @delete="$emit('delete', { [tag.name]: true })"
+ />
+ </div>
+</template>
diff --git a/app/assets/javascripts/registry/explorer/components/details_page/tags_list_row.vue b/app/assets/javascripts/registry/explorer/components/details_page/tags_list_row.vue
new file mode 100644
index 00000000000..51ba2337db6
--- /dev/null
+++ b/app/assets/javascripts/registry/explorer/components/details_page/tags_list_row.vue
@@ -0,0 +1,220 @@
+<script>
+import { GlFormCheckbox, GlTooltipDirective, GlSprintf, GlIcon } from '@gitlab/ui';
+import { n__ } from '~/locale';
+import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
+import { numberToHumanSize } from '~/lib/utils/number_utils';
+import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
+import { formatDate } from '~/lib/utils/datetime_utility';
+import DeleteButton from '../delete_button.vue';
+import ListItem from '../list_item.vue';
+import DetailsRow from './details_row.vue';
+import {
+ REMOVE_TAG_BUTTON_TITLE,
+ DIGEST_LABEL,
+ CREATED_AT_LABEL,
+ REMOVE_TAG_BUTTON_DISABLE_TOOLTIP,
+ PUBLISHED_DETAILS_ROW_TEXT,
+ MANIFEST_DETAILS_ROW_TEST,
+ CONFIGURATION_DETAILS_ROW_TEST,
+ MISSING_MANIFEST_WARNING_TOOLTIP,
+ NOT_AVAILABLE_TEXT,
+ NOT_AVAILABLE_SIZE,
+} from '../../constants/index';
+
+export default {
+ components: {
+ GlSprintf,
+ GlFormCheckbox,
+ GlIcon,
+ DeleteButton,
+ ListItem,
+ ClipboardButton,
+ TimeAgoTooltip,
+ DetailsRow,
+ },
+ directives: {
+ GlTooltip: GlTooltipDirective,
+ },
+ props: {
+ tag: {
+ type: Object,
+ required: true,
+ },
+ isDesktop: {
+ type: Boolean,
+ default: false,
+ required: false,
+ },
+ selected: {
+ type: Boolean,
+ default: false,
+ required: false,
+ },
+ },
+ i18n: {
+ REMOVE_TAG_BUTTON_TITLE,
+ DIGEST_LABEL,
+ CREATED_AT_LABEL,
+ REMOVE_TAG_BUTTON_DISABLE_TOOLTIP,
+ PUBLISHED_DETAILS_ROW_TEXT,
+ MANIFEST_DETAILS_ROW_TEST,
+ CONFIGURATION_DETAILS_ROW_TEST,
+ MISSING_MANIFEST_WARNING_TOOLTIP,
+ },
+ computed: {
+ formattedSize() {
+ return this.tag.total_size ? numberToHumanSize(this.tag.total_size) : NOT_AVAILABLE_SIZE;
+ },
+ layers() {
+ return this.tag.layers ? n__('%d layer', '%d layers', this.tag.layers) : '';
+ },
+ mobileClasses() {
+ return this.isDesktop ? '' : 'mw-s';
+ },
+ shortDigest() {
+ // remove sha256: from the string, and show only the first 7 char
+ return this.tag.digest?.substring(7, 14) ?? NOT_AVAILABLE_TEXT;
+ },
+ publishedDate() {
+ return formatDate(this.tag.created_at, 'isoDate');
+ },
+ publishedTime() {
+ return formatDate(this.tag.created_at, 'hh:MM Z');
+ },
+ formattedRevision() {
+ // to be removed when API response is adjusted
+ // see https://gitlab.com/gitlab-org/gitlab/-/issues/225324
+ // eslint-disable-next-line @gitlab/require-i18n-strings
+ return `sha256:${this.tag.revision}`;
+ },
+ tagLocation() {
+ return this.tag.path?.replace(`:${this.tag.name}`, '');
+ },
+ invalidTag() {
+ return !this.tag.digest;
+ },
+ },
+};
+</script>
+
+<template>
+ <list-item v-bind="$attrs" :selected="selected">
+ <template #left-action>
+ <gl-form-checkbox
+ v-if="Boolean(tag.destroy_path)"
+ :disabled="invalidTag"
+ class="gl-m-0"
+ :checked="selected"
+ @change="$emit('select')"
+ />
+ </template>
+ <template #left-primary>
+ <div class="gl-display-flex gl-align-items-center">
+ <div
+ v-gl-tooltip="{ title: tag.name }"
+ data-testid="name"
+ class="gl-text-overflow-ellipsis gl-overflow-hidden gl-white-space-nowrap"
+ :class="mobileClasses"
+ >
+ {{ tag.name }}
+ </div>
+
+ <clipboard-button
+ v-if="tag.location"
+ :title="tag.location"
+ :text="tag.location"
+ css-class="btn-default btn-transparent btn-clipboard"
+ />
+
+ <gl-icon
+ v-if="invalidTag"
+ v-gl-tooltip="{ title: $options.i18n.MISSING_MANIFEST_WARNING_TOOLTIP }"
+ name="warning"
+ class="gl-text-orange-500 gl-mb-2 gl-ml-2"
+ />
+ </div>
+ </template>
+
+ <template #left-secondary>
+ <span data-testid="size">
+ {{ formattedSize }}
+ <template v-if="formattedSize && layers"
+ >&middot;</template
+ >
+ {{ layers }}
+ </span>
+ </template>
+ <template #right-primary>
+ <span data-testid="time">
+ <gl-sprintf :message="$options.i18n.CREATED_AT_LABEL">
+ <template #timeInfo>
+ <time-ago-tooltip :time="tag.created_at" />
+ </template>
+ </gl-sprintf>
+ </span>
+ </template>
+ <template #right-secondary>
+ <span data-testid="digest">
+ <gl-sprintf :message="$options.i18n.DIGEST_LABEL">
+ <template #imageId>{{ shortDigest }}</template>
+ </gl-sprintf>
+ </span>
+ </template>
+ <template #right-action>
+ <delete-button
+ :disabled="!tag.destroy_path || invalidTag"
+ :title="$options.i18n.REMOVE_TAG_BUTTON_TITLE"
+ :tooltip-title="$options.i18n.REMOVE_TAG_BUTTON_DISABLE_TOOLTIP"
+ :tooltip-disabled="Boolean(tag.destroy_path)"
+ data-testid="single-delete-button"
+ @delete="$emit('delete')"
+ />
+ </template>
+
+ <template v-if="!invalidTag" #details_published>
+ <details-row icon="clock" data-testid="published-date-detail">
+ <gl-sprintf :message="$options.i18n.PUBLISHED_DETAILS_ROW_TEXT">
+ <template #repositoryPath>
+ <i>{{ tagLocation }}</i>
+ </template>
+ <template #time>
+ {{ publishedTime }}
+ </template>
+ <template #date>
+ {{ publishedDate }}
+ </template>
+ </gl-sprintf>
+ </details-row>
+ </template>
+ <template v-if="!invalidTag" #details_manifest_digest>
+ <details-row icon="log" data-testid="manifest-detail">
+ <gl-sprintf :message="$options.i18n.MANIFEST_DETAILS_ROW_TEST">
+ <template #digest>
+ {{ tag.digest }}
+ </template>
+ </gl-sprintf>
+ <clipboard-button
+ v-if="tag.digest"
+ :title="tag.digest"
+ :text="tag.digest"
+ css-class="btn-default btn-transparent btn-clipboard gl-p-0"
+ />
+ </details-row>
+ </template>
+ <template v-if="!invalidTag" #details_configuration_digest>
+ <details-row icon="cloud-gear" data-testid="configuration-detail">
+ <gl-sprintf :message="$options.i18n.CONFIGURATION_DETAILS_ROW_TEST">
+ <template #digest>
+ {{ formattedRevision }}
+ </template>
+ </gl-sprintf>
+ <clipboard-button
+ v-if="formattedRevision"
+ :title="formattedRevision"
+ :text="formattedRevision"
+ css-class="btn-default btn-transparent btn-clipboard gl-p-0"
+ />
+ </details-row>
+ </template>
+ </list-item>
+</template>
diff --git a/app/assets/javascripts/registry/explorer/components/details_page/tags_table.vue b/app/assets/javascripts/registry/explorer/components/details_page/tags_table.vue
deleted file mode 100644
index 81be778e1e5..00000000000
--- a/app/assets/javascripts/registry/explorer/components/details_page/tags_table.vue
+++ /dev/null
@@ -1,210 +0,0 @@
-<script>
-import { GlTable, GlFormCheckbox, GlButton, GlTooltipDirective } from '@gitlab/ui';
-import { n__ } from '~/locale';
-import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
-import { numberToHumanSize } from '~/lib/utils/number_utils';
-import timeagoMixin from '~/vue_shared/mixins/timeago';
-import {
- LIST_KEY_TAG,
- LIST_KEY_IMAGE_ID,
- LIST_KEY_SIZE,
- LIST_KEY_LAST_UPDATED,
- LIST_KEY_ACTIONS,
- LIST_KEY_CHECKBOX,
- LIST_LABEL_TAG,
- LIST_LABEL_IMAGE_ID,
- LIST_LABEL_SIZE,
- LIST_LABEL_LAST_UPDATED,
- REMOVE_TAGS_BUTTON_TITLE,
- REMOVE_TAG_BUTTON_TITLE,
-} from '../../constants/index';
-
-export default {
- components: {
- GlTable,
- GlFormCheckbox,
- GlButton,
- ClipboardButton,
- },
- directives: {
- GlTooltip: GlTooltipDirective,
- },
- mixins: [timeagoMixin],
- props: {
- tags: {
- type: Array,
- required: false,
- default: () => [],
- },
- isLoading: {
- type: Boolean,
- required: false,
- default: false,
- },
- isDesktop: {
- type: Boolean,
- required: false,
- default: false,
- },
- },
- i18n: {
- REMOVE_TAGS_BUTTON_TITLE,
- REMOVE_TAG_BUTTON_TITLE,
- },
- data() {
- return {
- selectedItems: [],
- };
- },
- computed: {
- fields() {
- const tagClass = this.isDesktop ? 'w-25' : '';
- const tagInnerClass = this.isDesktop ? 'mw-m' : 'gl-justify-content-end';
- return [
- { key: LIST_KEY_CHECKBOX, label: '', class: 'gl-w-16' },
- {
- key: LIST_KEY_TAG,
- label: LIST_LABEL_TAG,
- class: `${tagClass} js-tag-column`,
- innerClass: tagInnerClass,
- },
- { key: LIST_KEY_IMAGE_ID, label: LIST_LABEL_IMAGE_ID },
- { key: LIST_KEY_SIZE, label: LIST_LABEL_SIZE },
- { key: LIST_KEY_LAST_UPDATED, label: LIST_LABEL_LAST_UPDATED },
- { key: LIST_KEY_ACTIONS, label: '' },
- ].filter(f => f.key !== LIST_KEY_CHECKBOX || this.isDesktop);
- },
- tagsNames() {
- return this.tags.map(t => t.name);
- },
- selectAllChecked() {
- return this.selectedItems.length === this.tags.length && this.tags.length > 0;
- },
- },
- watch: {
- tagsNames: {
- immediate: false,
- handler(tagsNames) {
- this.selectedItems = this.selectedItems.filter(t => tagsNames.includes(t));
- },
- },
- },
- methods: {
- formatSize(size) {
- return numberToHumanSize(size);
- },
- layers(layers) {
- return layers ? n__('%d layer', '%d layers', layers) : '';
- },
- onSelectAllChange() {
- if (this.selectAllChecked) {
- this.selectedItems = [];
- } else {
- this.selectedItems = this.tags.map(x => x.name);
- }
- },
- updateSelectedItems(name) {
- const delIndex = this.selectedItems.findIndex(x => x === name);
-
- if (delIndex > -1) {
- this.selectedItems.splice(delIndex, 1);
- } else {
- this.selectedItems.push(name);
- }
- },
- },
-};
-</script>
-
-<template>
- <gl-table :items="tags" :fields="fields" :stacked="!isDesktop" show-empty :busy="isLoading">
- <template v-if="isDesktop" #head(checkbox)>
- <gl-form-checkbox
- data-testid="mainCheckbox"
- :checked="selectAllChecked"
- @change="onSelectAllChange"
- />
- </template>
- <template #head(actions)>
- <span class="gl-display-flex gl-justify-content-end">
- <gl-button
- v-gl-tooltip
- data-testid="bulkDeleteButton"
- :disabled="!selectedItems || selectedItems.length === 0"
- icon="remove"
- variant="danger"
- :title="$options.i18n.REMOVE_TAGS_BUTTON_TITLE"
- :aria-label="$options.i18n.REMOVE_TAGS_BUTTON_TITLE"
- @click="$emit('delete', selectedItems)"
- />
- </span>
- </template>
-
- <template #cell(checkbox)="{item}">
- <gl-form-checkbox
- data-testid="rowCheckbox"
- :checked="selectedItems.includes(item.name)"
- @change="updateSelectedItems(item.name)"
- />
- </template>
- <template #cell(name)="{item, field}">
- <div data-testid="rowName" :class="[field.innerClass, 'gl-display-flex']">
- <span
- v-gl-tooltip
- data-testid="rowNameText"
- :title="item.name"
- class="gl-text-overflow-ellipsis gl-overflow-hidden gl-white-space-nowrap"
- >
- {{ item.name }}
- </span>
- <clipboard-button
- v-if="item.location"
- data-testid="rowClipboardButton"
- :title="item.location"
- :text="item.location"
- css-class="btn-default btn-transparent btn-clipboard"
- />
- </div>
- </template>
- <template #cell(short_revision)="{value}">
- <span data-testid="rowShortRevision">
- {{ value }}
- </span>
- </template>
- <template #cell(total_size)="{item}">
- <span data-testid="rowSize">
- {{ formatSize(item.total_size) }}
- <template v-if="item.total_size && item.layers">
- &middot;
- </template>
- {{ layers(item.layers) }}
- </span>
- </template>
- <template #cell(created_at)="{value}">
- <span v-gl-tooltip data-testid="rowTime" :title="tooltipTitle(value)">
- {{ timeFormatted(value) }}
- </span>
- </template>
- <template #cell(actions)="{item}">
- <span class="gl-display-flex gl-justify-content-end">
- <gl-button
- data-testid="singleDeleteButton"
- :title="$options.i18n.REMOVE_TAG_BUTTON_TITLE"
- :aria-label="$options.i18n.REMOVE_TAG_BUTTON_TITLE"
- :disabled="!item.destroy_path"
- variant="danger"
- icon="remove"
- category="secondary"
- @click="$emit('delete', [item.name])"
- />
- </span>
- </template>
-
- <template #empty>
- <slot name="empty"></slot>
- </template>
- <template #table-busy>
- <slot name="loader"></slot>
- </template>
- </gl-table>
-</template>
diff --git a/app/assets/javascripts/registry/explorer/components/list_item.vue b/app/assets/javascripts/registry/explorer/components/list_item.vue
new file mode 100644
index 00000000000..7b5afe8fd9d
--- /dev/null
+++ b/app/assets/javascripts/registry/explorer/components/list_item.vue
@@ -0,0 +1,128 @@
+<script>
+import { GlButton } from '@gitlab/ui';
+
+export default {
+ name: 'ListItem',
+ components: { GlButton },
+ props: {
+ first: {
+ type: Boolean,
+ default: false,
+ required: false,
+ },
+ last: {
+ type: Boolean,
+ default: false,
+ required: false,
+ },
+ disabled: {
+ type: Boolean,
+ default: false,
+ required: false,
+ },
+ selected: {
+ type: Boolean,
+ default: false,
+ required: false,
+ },
+ },
+ data() {
+ return {
+ isDetailsShown: false,
+ detailsSlots: [],
+ };
+ },
+ computed: {
+ optionalClasses() {
+ return {
+ 'gl-border-t-1': !this.first,
+ 'gl-border-t-2': this.first,
+ 'gl-border-b-1': !this.last,
+ 'gl-border-b-2': this.last,
+ 'disabled-content': this.disabled,
+ 'gl-border-gray-100': !this.selected,
+ 'gl-bg-blue-50 gl-border-blue-200': this.selected,
+ };
+ },
+ },
+ mounted() {
+ this.detailsSlots = Object.keys(this.$slots).filter(k => k.startsWith('details_'));
+ },
+ methods: {
+ toggleDetails() {
+ this.isDetailsShown = !this.isDetailsShown;
+ },
+ },
+};
+</script>
+
+<template>
+ <div
+ class="gl-display-flex gl-flex-direction-column gl-border-b-solid gl-border-t-solid"
+ :class="optionalClasses"
+ >
+ <div class="gl-display-flex gl-align-items-center gl-py-4 gl-px-2">
+ <div
+ v-if="$slots['left-action']"
+ class="gl-w-7 gl-display-none gl-display-sm-flex gl-justify-content-start gl-pl-2"
+ >
+ <slot name="left-action"></slot>
+ </div>
+ <div class="gl-display-flex gl-flex-direction-column gl-flex-fill-1">
+ <div
+ class="gl-display-flex gl-align-items-center gl-justify-content-space-between gl-text-black-normal gl-font-weight-bold"
+ >
+ <div class="gl-display-flex gl-align-items-center">
+ <slot name="left-primary"></slot>
+ <gl-button
+ v-if="detailsSlots.length > 0"
+ :selected="isDetailsShown"
+ icon="ellipsis_h"
+ size="small"
+ class="gl-ml-2 gl-display-none gl-display-sm-block"
+ @click="toggleDetails"
+ />
+ </div>
+ <div>
+ <slot name="right-primary"></slot>
+ </div>
+ </div>
+ <div
+ class="gl-display-flex gl-align-items-center gl-justify-content-space-between gl-font-sm gl-text-gray-500"
+ >
+ <div>
+ <slot name="left-secondary"></slot>
+ </div>
+ <div>
+ <slot name="right-secondary"></slot>
+ </div>
+ </div>
+ </div>
+ <div
+ v-if="$slots['right-action']"
+ class="gl-w-9 gl-display-none gl-display-sm-flex gl-justify-content-end gl-pr-2"
+ >
+ <slot name="right-action"></slot>
+ </div>
+ </div>
+ <div class="gl-display-flex">
+ <div class="gl-w-7"></div>
+ <div
+ v-if="isDetailsShown"
+ class="gl-display-flex gl-flex-direction-column gl-flex-fill-1 gl-bg-gray-10 gl-rounded-base gl-inset-border-1-gray-100 gl-mb-3"
+ >
+ <div
+ v-for="(row, detailIndex) in detailsSlots"
+ :key="detailIndex"
+ class="gl-px-5 gl-py-2"
+ :class="{
+ 'gl-border-gray-100 gl-border-t-solid gl-border-t-1': detailIndex !== 0,
+ }"
+ >
+ <slot :name="row"></slot>
+ </div>
+ </div>
+ <div class="gl-w-9"></div>
+ </div>
+ </div>
+</template>
diff --git a/app/assets/javascripts/registry/explorer/components/list_page/group_empty_state.vue b/app/assets/javascripts/registry/explorer/components/list_page/group_empty_state.vue
index a29a9bd23c3..80cc392f86a 100644
--- a/app/assets/javascripts/registry/explorer/components/list_page/group_empty_state.vue
+++ b/app/assets/javascripts/registry/explorer/components/list_page/group_empty_state.vue
@@ -18,10 +18,9 @@ export default {
<gl-empty-state
:title="s__('ContainerRegistry|There are no container images available in this group')"
:svg-path="config.noContainersImage"
- class="container-message"
>
<template #description>
- <p class="js-no-container-images-text">
+ <p>
<gl-sprintf
:message="
s__(
diff --git a/app/assets/javascripts/registry/explorer/components/list_page/image_list.vue b/app/assets/javascripts/registry/explorer/components/list_page/image_list.vue
index 9d48769cbad..65cf51fd1d1 100644
--- a/app/assets/javascripts/registry/explorer/components/list_page/image_list.vue
+++ b/app/assets/javascripts/registry/explorer/components/list_page/image_list.vue
@@ -37,7 +37,8 @@ export default {
v-for="(listItem, index) in images"
:key="index"
:item="listItem"
- :show-top-border="index === 0"
+ :first="index === 0"
+ :last="index === images.length - 1"
@delete="$emit('delete', $event)"
/>
diff --git a/app/assets/javascripts/registry/explorer/components/list_page/image_list_row.vue b/app/assets/javascripts/registry/explorer/components/list_page/image_list_row.vue
index cd878c38081..2874d89d913 100644
--- a/app/assets/javascripts/registry/explorer/components/list_page/image_list_row.vue
+++ b/app/assets/javascripts/registry/explorer/components/list_page/image_list_row.vue
@@ -1,7 +1,9 @@
<script>
-import { GlTooltipDirective, GlButton, GlIcon, GlSprintf } from '@gitlab/ui';
+import { GlTooltipDirective, GlIcon, GlSprintf } from '@gitlab/ui';
import { n__ } from '~/locale';
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
+import ListItem from '../list_item.vue';
+import DeleteButton from '../delete_button.vue';
import {
ASYNC_DELETE_IMAGE_ERROR_MESSAGE,
@@ -14,9 +16,10 @@ export default {
name: 'ImageListrow',
components: {
ClipboardButton,
- GlButton,
+ DeleteButton,
GlSprintf,
GlIcon,
+ ListItem,
},
directives: {
GlTooltip: GlTooltipDirective,
@@ -26,11 +29,6 @@ export default {
type: Object,
required: true,
},
- showTopBorder: {
- type: Boolean,
- default: false,
- required: false,
- },
},
i18n: {
LIST_DELETE_BUTTON_DISABLED,
@@ -62,75 +60,55 @@ export default {
</script>
<template>
- <div
+ <list-item
v-gl-tooltip="{
placement: 'left',
disabled: !item.deleting,
title: $options.i18n.ROW_SCHEDULED_FOR_DELETION,
}"
+ v-bind="$attrs"
+ :disabled="item.deleting"
>
- <div
- class="gl-display-flex gl-justify-content-space-between gl-align-items-center gl-py-2 gl-px-1 gl-border-gray-200 gl-border-b-solid gl-border-b-1 gl-py-4 "
- :class="{
- 'gl-border-t-solid gl-border-t-1': showTopBorder,
- 'disabled-content': item.deleting,
- }"
- >
- <div class="gl-display-flex gl-flex-direction-column">
- <div class="gl-display-flex gl-align-items-center">
- <router-link
- class="gl-text-black-normal gl-font-weight-bold"
- data-testid="detailsLink"
- :to="{ name: 'details', params: { id: encodedItem } }"
- >
- {{ item.path }}
- </router-link>
- <clipboard-button
- v-if="item.location"
- :disabled="item.deleting"
- :text="item.location"
- :title="item.location"
- css-class="btn-default btn-transparent btn-clipboard gl-text-gray-500"
- />
- <gl-icon
- v-if="item.failedDelete"
- v-gl-tooltip
- :title="$options.i18n.ASYNC_DELETE_IMAGE_ERROR_MESSAGE"
- name="warning"
- class="text-warning"
- />
- </div>
- <div class="gl-font-sm gl-text-gray-500">
- <span class="gl-display-flex gl-align-items-center" data-testid="tagsCount">
- <gl-icon name="tag" class="gl-mr-2" />
- <gl-sprintf :message="tagsCountText">
- <template #count>
- {{ item.tags_count }}
- </template>
- </gl-sprintf>
- </span>
- </div>
- </div>
- <div
- v-gl-tooltip="{
- disabled: item.destroy_path,
- title: $options.i18n.LIST_DELETE_BUTTON_DISABLED,
- }"
- class="d-none d-sm-block"
- data-testid="deleteButtonWrapper"
+ <template #left-primary>
+ <router-link
+ class="gl-text-black-normal gl-font-weight-bold"
+ data-testid="detailsLink"
+ :to="{ name: 'details', params: { id: encodedItem } }"
>
- <gl-button
- v-gl-tooltip
- data-testid="deleteImageButton"
- :disabled="disabledDelete"
- :title="$options.i18n.REMOVE_REPOSITORY_LABEL"
- :aria-label="$options.i18n.REMOVE_REPOSITORY_LABEL"
- category="secondary"
- variant="danger"
- icon="remove"
- @click="$emit('delete', item)"
- />
- </div>
- </div>
- </div>
+ {{ item.path }}
+ </router-link>
+ <clipboard-button
+ v-if="item.location"
+ :disabled="item.deleting"
+ :text="item.location"
+ :title="item.location"
+ css-class="btn-default btn-transparent btn-clipboard gl-text-gray-500"
+ />
+ <gl-icon
+ v-if="item.failedDelete"
+ v-gl-tooltip="{ title: $options.i18n.ASYNC_DELETE_IMAGE_ERROR_MESSAGE }"
+ name="warning"
+ class="gl-text-orange-500"
+ />
+ </template>
+ <template #left-secondary>
+ <span class="gl-display-flex gl-align-items-center" data-testid="tagsCount">
+ <gl-icon name="tag" class="gl-mr-2" />
+ <gl-sprintf :message="tagsCountText">
+ <template #count>
+ {{ item.tags_count }}
+ </template>
+ </gl-sprintf>
+ </span>
+ </template>
+ <template #right-action>
+ <delete-button
+ :title="$options.i18n.REMOVE_REPOSITORY_LABEL"
+ :disabled="disabledDelete"
+ :tooltip-disabled="Boolean(item.destroy_path)"
+ :tooltip-title="$options.i18n.LIST_DELETE_BUTTON_DISABLED"
+ @delete="$emit('delete', item)"
+ />
+ </template>
+ </list-item>
</template>
diff --git a/app/assets/javascripts/registry/explorer/components/list_page/project_empty_state.vue b/app/assets/javascripts/registry/explorer/components/list_page/project_empty_state.vue
index c27d53f4351..35eb0b11e40 100644
--- a/app/assets/javascripts/registry/explorer/components/list_page/project_empty_state.vue
+++ b/app/assets/javascripts/registry/explorer/components/list_page/project_empty_state.vue
@@ -1,5 +1,5 @@
<script>
-import { GlEmptyState, GlSprintf, GlLink } from '@gitlab/ui';
+import { GlEmptyState, GlSprintf, GlLink, GlFormInputGroup, GlFormInput } from '@gitlab/ui';
import { mapState, mapGetters } from 'vuex';
import { s__ } from '~/locale';
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
@@ -17,6 +17,8 @@ export default {
GlEmptyState,
GlSprintf,
GlLink,
+ GlFormInputGroup,
+ GlFormInput,
},
i18n: {
quickStart: QUICK_START,
@@ -43,10 +45,9 @@ export default {
<gl-empty-state
:title="s__('ContainerRegistry|There are no container images stored for this project')"
:svg-path="config.noContainersImage"
- class="container-message"
>
<template #description>
- <p class="js-no-container-images-text">
+ <p>
<gl-sprintf :message="$options.i18n.introText">
<template #docLink="{content}">
<gl-link :href="config.helpPagePath" target="_blank">{{ content }}</gl-link>
@@ -54,7 +55,7 @@ export default {
</gl-sprintf>
</p>
<h5>{{ $options.i18n.quickStart }}</h5>
- <p class="js-not-logged-in-to-registry-text">
+ <p>
<gl-sprintf :message="$options.i18n.notLoggedInMessage">
<template #twofaDocLink="{content}">
<gl-link :href="config.twoFactorAuthHelpLink" target="_blank">{{ content }}</gl-link>
@@ -66,42 +67,49 @@ export default {
</template>
</gl-sprintf>
</p>
- <div class="input-group append-bottom-10">
- <input :value="dockerLoginCommand" type="text" class="form-control monospace" readonly />
- <span class="input-group-append">
+ <gl-form-input-group class="gl-mb-4">
+ <gl-form-input
+ :value="dockerLoginCommand"
+ readonly
+ type="text"
+ class="gl-font-monospace!"
+ />
+ <template #append>
<clipboard-button
:text="dockerLoginCommand"
:title="$options.i18n.copyLoginTitle"
- class="input-group-text"
+ class="gl-m-0!"
/>
- </span>
- </div>
- <p></p>
- <p>
+ </template>
+ </gl-form-input-group>
+ <p class="gl-mb-4">
{{ $options.i18n.addImageText }}
</p>
-
- <div class="input-group append-bottom-10">
- <input :value="dockerBuildCommand" type="text" class="form-control monospace" readonly />
- <span class="input-group-append">
+ <gl-form-input-group class="gl-mb-4 ">
+ <gl-form-input
+ :value="dockerBuildCommand"
+ readonly
+ type="text"
+ class="gl-font-monospace!"
+ />
+ <template #append>
<clipboard-button
:text="dockerBuildCommand"
:title="$options.i18n.copyBuildTitle"
- class="input-group-text"
+ class="gl-m-0!"
/>
- </span>
- </div>
-
- <div class="input-group">
- <input :value="dockerPushCommand" type="text" class="form-control monospace" readonly />
- <span class="input-group-append">
+ </template>
+ </gl-form-input-group>
+ <gl-form-input-group>
+ <gl-form-input :value="dockerPushCommand" readonly type="text" class="gl-font-monospace!" />
+ <template #append>
<clipboard-button
:text="dockerPushCommand"
:title="$options.i18n.copyPushTitle"
- class="input-group-text"
+ class="gl-m-0!"
/>
- </span>
- </div>
+ </template>
+ </gl-form-input-group>
</template>
</gl-empty-state>
</template>
diff --git a/app/assets/javascripts/registry/explorer/constants/details.js b/app/assets/javascripts/registry/explorer/constants/details.js
index a1fa995c17f..1dc5882d415 100644
--- a/app/assets/javascripts/registry/explorer/constants/details.js
+++ b/app/assets/javascripts/registry/explorer/constants/details.js
@@ -1,4 +1,4 @@
-import { s__ } from '~/locale';
+import { s__, __ } from '~/locale';
// Translations strings
export const DETAILS_PAGE_TITLE = s__('ContainerRegistry|%{imageName} tags');
@@ -14,12 +14,20 @@ export const DELETE_TAGS_ERROR_MESSAGE = s__(
export const DELETE_TAGS_SUCCESS_MESSAGE = s__(
'ContainerRegistry|Tags successfully marked for deletion.',
);
-export const LIST_LABEL_TAG = s__('ContainerRegistry|Tag');
-export const LIST_LABEL_IMAGE_ID = s__('ContainerRegistry|Image ID');
-export const LIST_LABEL_SIZE = s__('ContainerRegistry|Compressed Size');
-export const LIST_LABEL_LAST_UPDATED = s__('ContainerRegistry|Last Updated');
+
+export const TAGS_LIST_TITLE = s__('ContainerRegistry|Image tags');
+export const DIGEST_LABEL = s__('ContainerRegistry|Digest: %{imageId}');
+export const CREATED_AT_LABEL = s__('ContainerRegistry|Published %{timeInfo}');
+export const PUBLISHED_DETAILS_ROW_TEXT = s__(
+ 'ContainerRegistry|Published to the %{repositoryPath} image repository at %{time} on %{date}',
+);
+export const MANIFEST_DETAILS_ROW_TEST = s__('ContainerRegistry|Manifest digest: %{digest}');
+export const CONFIGURATION_DETAILS_ROW_TEST = s__(
+ 'ContainerRegistry|Configuration digest: %{digest}',
+);
+
export const REMOVE_TAG_BUTTON_TITLE = s__('ContainerRegistry|Remove tag');
-export const REMOVE_TAGS_BUTTON_TITLE = s__('ContainerRegistry|Remove selected tags');
+export const REMOVE_TAGS_BUTTON_TITLE = s__('ContainerRegistry|Delete selected');
export const REMOVE_TAG_CONFIRMATION_TEXT = s__(
`ContainerRegistry|You are about to remove %{item}. Are you sure?`,
);
@@ -36,17 +44,21 @@ export const ADMIN_GARBAGE_COLLECTION_TIP = s__(
'ContainerRegistry|Remember to run %{docLinkStart}garbage collection%{docLinkEnd} to remove the stale data from storage.',
);
+export const REMOVE_TAG_BUTTON_DISABLE_TOOLTIP = s__(
+ 'ContainerRegistry|Deletion disabled due to missing or insufficient permissions.',
+);
+
+export const MISSING_MANIFEST_WARNING_TOOLTIP = s__(
+ 'ContainerRegistry|Invalid tag: missing manifest digest',
+);
+
+export const NOT_AVAILABLE_TEXT = __('N/A');
+export const NOT_AVAILABLE_SIZE = __('0 bytes');
// Parameters
export const DEFAULT_PAGE = 1;
export const DEFAULT_PAGE_SIZE = 10;
export const GROUP_PAGE_TYPE = 'groups';
-export const LIST_KEY_TAG = 'name';
-export const LIST_KEY_IMAGE_ID = 'short_revision';
-export const LIST_KEY_SIZE = 'total_size';
-export const LIST_KEY_LAST_UPDATED = 'created_at';
-export const LIST_KEY_ACTIONS = 'actions';
-export const LIST_KEY_CHECKBOX = 'checkbox';
export const ALERT_SUCCESS_TAG = 'success_tag';
export const ALERT_DANGER_TAG = 'danger_tag';
export const ALERT_SUCCESS_TAGS = 'success_tags';
diff --git a/app/assets/javascripts/registry/explorer/pages/details.vue b/app/assets/javascripts/registry/explorer/pages/details.vue
index 598e643ce1a..cf811156704 100644
--- a/app/assets/javascripts/registry/explorer/pages/details.vue
+++ b/app/assets/javascripts/registry/explorer/pages/details.vue
@@ -6,7 +6,7 @@ import Tracking from '~/tracking';
import DeleteAlert from '../components/details_page/delete_alert.vue';
import DeleteModal from '../components/details_page/delete_modal.vue';
import DetailsHeader from '../components/details_page/details_header.vue';
-import TagsTable from '../components/details_page/tags_table.vue';
+import TagsList from '../components/details_page/tags_list.vue';
import TagsLoader from '../components/details_page/tags_loader.vue';
import EmptyTagsState from '../components/details_page/empty_tags_state.vue';
@@ -24,7 +24,7 @@ export default {
DetailsHeader,
GlPagination,
DeleteModal,
- TagsTable,
+ TagsList,
TagsLoader,
EmptyTagsState,
},
@@ -65,10 +65,8 @@ export default {
},
methods: {
...mapActions(['requestTagsList', 'requestDeleteTag', 'requestDeleteTags']),
- deleteTags(toBeDeletedList) {
- this.itemsToBeDeleted = toBeDeletedList.map(name => ({
- ...this.tags.find(t => t.name === name),
- }));
+ deleteTags(toBeDeleted) {
+ this.itemsToBeDeleted = this.tags.filter(tag => toBeDeleted[tag.name]);
this.track('click_button');
this.$refs.deleteModal.show();
},
@@ -114,24 +112,21 @@ export default {
</script>
<template>
- <div v-gl-resize-observer="handleResize" class="my-3 w-100 slide-enter-to-element">
+ <div v-gl-resize-observer="handleResize" class="gl-my-3 gl-w-full slide-enter-to-element">
<delete-alert
v-model="deleteAlertType"
:garbage-collection-help-page-path="config.garbageCollectionHelpPagePath"
:is-admin="config.isAdmin"
- class="my-2"
+ class="gl-my-2"
/>
<details-header :image-name="imageName" />
- <tags-table :tags="tags" :is-loading="isLoading" :is-desktop="isDesktop" @delete="deleteTags">
- <template #empty>
- <empty-tags-state :no-containers-image="config.noContainersImage" />
- </template>
- <template #loader>
- <tags-loader v-once />
- </template>
- </tags-table>
+ <tags-loader v-if="isLoading" />
+ <template v-else>
+ <empty-tags-state v-if="tags.length === 0" :no-containers-image="config.noContainersImage" />
+ <tags-list v-else :tags="tags" :is-desktop="isDesktop" @delete="deleteTags" />
+ </template>
<gl-pagination
v-if="!isLoading"
@@ -140,7 +135,7 @@ export default {
:per-page="tagsPagination.perPage"
:total-items="tagsPagination.total"
align="center"
- class="w-100"
+ class="gl-w-full gl-mt-3"
/>
<delete-modal
diff --git a/app/assets/javascripts/registry/explorer/pages/list.vue b/app/assets/javascripts/registry/explorer/pages/list.vue
index e8a26dc58f2..1d353651c38 100644
--- a/app/assets/javascripts/registry/explorer/pages/list.vue
+++ b/app/assets/javascripts/registry/explorer/pages/list.vue
@@ -217,7 +217,6 @@ export default {
:svg-path="config.noContainersImage"
data-testid="emptySearch"
:title="$options.i18n.EMPTY_RESULT_TITLE"
- class="container-message"
>
<template #description>
{{ $options.i18n.EMPTY_RESULT_MESSAGE }}
diff --git a/app/assets/javascripts/registry/settings/components/registry_settings_app.vue b/app/assets/javascripts/registry/settings/components/registry_settings_app.vue
index b4a59fd0178..2ee7bbef4c6 100644
--- a/app/assets/javascripts/registry/settings/components/registry_settings_app.vue
+++ b/app/assets/javascripts/registry/settings/components/registry_settings_app.vue
@@ -1,11 +1,16 @@
<script>
import { mapActions, mapGetters, mapState } from 'vuex';
import { GlAlert, GlSprintf, GlLink } from '@gitlab/ui';
-import { s__ } from '~/locale';
import { FETCH_SETTINGS_ERROR_MESSAGE } from '../../shared/constants';
import SettingsForm from './settings_form.vue';
+import {
+ UNAVAILABLE_FEATURE_TITLE,
+ UNAVAILABLE_FEATURE_INTRO_TEXT,
+ UNAVAILABLE_USER_FEATURE_TEXT,
+ UNAVAILABLE_ADMIN_FEATURE_TEXT,
+} from '../constants';
export default {
components: {
@@ -15,17 +20,9 @@ export default {
GlLink,
},
i18n: {
- unavailableFeatureTitle: s__(
- `ContainerRegistry|Container Registry tag expiration and retention policy is disabled`,
- ),
- unavailableFeatureIntroText: s__(
- `ContainerRegistry|The Container Registry tag expiration and retention policies for this project have not been enabled.`,
- ),
- unavailableUserFeatureText: s__(`ContainerRegistry|Please contact your administrator.`),
- unavailableAdminFeatureText: s__(
- `ContainerRegistry| Please visit the %{linkStart}administration settings%{linkEnd} to enable this feature.`,
- ),
- fetchSettingsErrorText: FETCH_SETTINGS_ERROR_MESSAGE,
+ UNAVAILABLE_FEATURE_TITLE,
+ UNAVAILABLE_FEATURE_INTRO_TEXT,
+ FETCH_SETTINGS_ERROR_MESSAGE,
},
data() {
return {
@@ -42,9 +39,7 @@ export default {
return this.isDisabled && !this.fetchSettingsError;
},
unavailableFeatureMessage() {
- return this.isAdmin
- ? this.$options.i18n.unavailableAdminFeatureText
- : this.$options.i18n.unavailableUserFeatureText;
+ return this.isAdmin ? UNAVAILABLE_ADMIN_FEATURE_TEXT : UNAVAILABLE_USER_FEATURE_TEXT;
},
},
mounted() {
@@ -60,39 +55,24 @@ export default {
<template>
<div>
- <p>
- {{ s__('ContainerRegistry|Tag expiration policy is designed to:') }}
- </p>
- <ul>
- <li>{{ s__('ContainerRegistry|Keep and protect the images that matter most.') }}</li>
- <li>
- {{
- s__(
- "ContainerRegistry|Automatically remove extra images that aren't designed to be kept.",
- )
- }}
- </li>
- </ul>
<settings-form v-if="showSettingForm" />
<template v-else>
<gl-alert
v-if="showDisabledFormMessage"
:dismissible="false"
- :title="$options.i18n.unavailableFeatureTitle"
+ :title="$options.i18n.UNAVAILABLE_FEATURE_TITLE"
variant="tip"
>
- {{ $options.i18n.unavailableFeatureIntroText }}
+ {{ $options.i18n.UNAVAILABLE_FEATURE_INTRO_TEXT }}
<gl-sprintf :message="unavailableFeatureMessage">
<template #link="{ content }">
- <gl-link :href="adminSettingsPath" target="_blank">
- {{ content }}
- </gl-link>
+ <gl-link :href="adminSettingsPath" target="_blank">{{ content }}</gl-link>
</template>
</gl-sprintf>
</gl-alert>
<gl-alert v-else-if="fetchSettingsError" variant="warning" :dismissible="false">
- <gl-sprintf :message="$options.i18n.fetchSettingsErrorText" />
+ <gl-sprintf :message="$options.i18n.FETCH_SETTINGS_ERROR_MESSAGE" />
</gl-alert>
</template>
</div>
diff --git a/app/assets/javascripts/registry/settings/components/settings_form.vue b/app/assets/javascripts/registry/settings/components/settings_form.vue
index afd502109bf..f129922c1d2 100644
--- a/app/assets/javascripts/registry/settings/components/settings_form.vue
+++ b/app/assets/javascripts/registry/settings/components/settings_form.vue
@@ -1,13 +1,15 @@
<script>
+import { get } from 'lodash';
import { mapActions, mapState, mapGetters } from 'vuex';
import { GlCard, GlDeprecatedButton, GlLoadingIcon } from '@gitlab/ui';
import Tracking from '~/tracking';
+import { mapComputed } from '~/vuex_shared/bindings';
import {
UPDATE_SETTINGS_ERROR_MESSAGE,
UPDATE_SETTINGS_SUCCESS_MESSAGE,
} from '../../shared/constants';
-import { mapComputed } from '~/vuex_shared/bindings';
import ExpirationPolicyFields from '../../shared/components/expiration_policy_fields.vue';
+import { SET_CLEANUP_POLICY_BUTTON, CLEANUP_POLICY_CARD_HEADER } from '../constants';
export default {
components: {
@@ -21,12 +23,17 @@ export default {
cols: 3,
align: 'right',
},
+ i18n: {
+ CLEANUP_POLICY_CARD_HEADER,
+ SET_CLEANUP_POLICY_BUTTON,
+ },
data() {
return {
tracking: {
label: 'docker_container_retention_and_expiration_policies',
},
- formIsValid: true,
+ fieldsAreValid: true,
+ apiErrors: null,
};
},
computed: {
@@ -34,7 +41,7 @@ export default {
...mapGetters({ isEdited: 'getIsEdited' }),
...mapComputed([{ key: 'settings', getter: 'getSettings' }], 'updateSettings'),
isSubmitButtonDisabled() {
- return !this.formIsValid || this.isLoading;
+ return !this.fieldsAreValid || this.isLoading;
},
isCancelButtonDisabled() {
return !this.isEdited || this.isLoading;
@@ -44,13 +51,35 @@ export default {
...mapActions(['resetSettings', 'saveSettings']),
reset() {
this.track('reset_form');
+ this.apiErrors = null;
this.resetSettings();
},
+ setApiErrors(response) {
+ const messages = get(response, 'data.message', []);
+
+ this.apiErrors = Object.keys(messages).reduce((acc, curr) => {
+ if (curr.startsWith('container_expiration_policy.')) {
+ const key = curr.replace('container_expiration_policy.', '');
+ acc[key] = get(messages, [curr, 0], '');
+ }
+ return acc;
+ }, {});
+ },
submit() {
this.track('submit_form');
+ this.apiErrors = null;
this.saveSettings()
.then(() => this.$toast.show(UPDATE_SETTINGS_SUCCESS_MESSAGE, { type: 'success' }))
- .catch(() => this.$toast.show(UPDATE_SETTINGS_ERROR_MESSAGE, { type: 'error' }));
+ .catch(({ response }) => {
+ this.setApiErrors(response);
+ this.$toast.show(UPDATE_SETTINGS_ERROR_MESSAGE, { type: 'error' });
+ });
+ },
+ onModelChange(changePayload) {
+ this.settings = changePayload.newValue;
+ if (this.apiErrors) {
+ this.apiErrors[changePayload.modified] = undefined;
+ }
},
},
};
@@ -60,23 +89,25 @@ export default {
<form ref="form-element" @submit.prevent="submit" @reset.prevent="reset">
<gl-card>
<template #header>
- {{ s__('ContainerRegistry|Tag expiration policy') }}
+ {{ $options.i18n.CLEANUP_POLICY_CARD_HEADER }}
</template>
<template #default>
<expiration-policy-fields
- v-model="settings"
+ :value="settings"
:form-options="formOptions"
:is-loading="isLoading"
- @validated="formIsValid = true"
- @invalidated="formIsValid = false"
+ :api-errors="apiErrors"
+ @validated="fieldsAreValid = true"
+ @invalidated="fieldsAreValid = false"
+ @input="onModelChange"
/>
</template>
<template #footer>
- <div class="d-flex justify-content-end">
+ <div class="gl-display-flex gl-justify-content-end">
<gl-deprecated-button
ref="cancel-button"
type="reset"
- class="mr-2 d-block"
+ class="gl-mr-3 gl-display-block"
:disabled="isCancelButtonDisabled"
>
{{ __('Cancel') }}
@@ -86,10 +117,10 @@ export default {
type="submit"
:disabled="isSubmitButtonDisabled"
variant="success"
- class="d-flex justify-content-center align-items-center js-no-auto-disable"
+ class="gl-display-flex gl-justify-content-center gl-align-items-center js-no-auto-disable"
>
- {{ __('Save expiration policy') }}
- <gl-loading-icon v-if="isLoading" class="ml-2" />
+ {{ $options.i18n.SET_CLEANUP_POLICY_BUTTON }}
+ <gl-loading-icon v-if="isLoading" class="gl-ml-3" />
</gl-deprecated-button>
</div>
</template>
diff --git a/app/assets/javascripts/registry/settings/constants.js b/app/assets/javascripts/registry/settings/constants.js
new file mode 100644
index 00000000000..e790658f491
--- /dev/null
+++ b/app/assets/javascripts/registry/settings/constants.js
@@ -0,0 +1,14 @@
+import { s__, __ } from '~/locale';
+
+export const SET_CLEANUP_POLICY_BUTTON = s__('ContainerRegistry|Set cleanup policy');
+export const CLEANUP_POLICY_CARD_HEADER = s__('ContainerRegistry|Tag expiration policy');
+export const UNAVAILABLE_FEATURE_TITLE = s__(
+ `ContainerRegistry|Cleanup policy for tags is disabled`,
+);
+export const UNAVAILABLE_FEATURE_INTRO_TEXT = s__(
+ `ContainerRegistry|This project's cleanup policy for tags is not enabled.`,
+);
+export const UNAVAILABLE_USER_FEATURE_TEXT = __(`Please contact your administrator.`);
+export const UNAVAILABLE_ADMIN_FEATURE_TEXT = s__(
+ `ContainerRegistry| Please visit the %{linkStart}administration settings%{linkEnd} to enable this feature.`,
+);
diff --git a/app/assets/javascripts/registry/shared/components/expiration_policy_fields.vue b/app/assets/javascripts/registry/shared/components/expiration_policy_fields.vue
index 04a547db07e..1ff2f6f99e5 100644
--- a/app/assets/javascripts/registry/shared/components/expiration_policy_fields.vue
+++ b/app/assets/javascripts/registry/shared/components/expiration_policy_fields.vue
@@ -34,6 +34,11 @@ export default {
required: false,
default: () => ({}),
},
+ apiErrors: {
+ type: Object,
+ required: false,
+ default: null,
+ },
isLoading: {
type: Boolean,
required: false,
@@ -56,9 +61,8 @@ export default {
},
},
i18n: {
- textAreaInvalidFeedback: TEXT_AREA_INVALID_FEEDBACK,
- enableToggleLabel: ENABLE_TOGGLE_LABEL,
- enableToggleDescription: ENABLE_TOGGLE_DESCRIPTION,
+ ENABLE_TOGGLE_LABEL,
+ ENABLE_TOGGLE_DESCRIPTION,
},
selectList: [
{
@@ -86,7 +90,6 @@ export default {
label: NAME_REGEX_LABEL,
model: 'name_regex',
placeholder: NAME_REGEX_PLACEHOLDER,
- stateVariable: 'nameRegexState',
description: NAME_REGEX_DESCRIPTION,
},
{
@@ -94,7 +97,6 @@ export default {
label: NAME_REGEX_KEEP_LABEL,
model: 'name_regex_keep',
placeholder: NAME_REGEX_KEEP_PLACEHOLDER,
- stateVariable: 'nameKeepRegexState',
description: NAME_REGEX_KEEP_DESCRIPTION,
},
],
@@ -111,16 +113,34 @@ export default {
policyEnabledText() {
return this.enabled ? ENABLED_TEXT : DISABLED_TEXT;
},
- textAreaState() {
+ textAreaValidation() {
+ const nameRegexErrors =
+ this.apiErrors?.name_regex || this.validateRegexLength(this.name_regex);
+ const nameKeepRegexErrors =
+ this.apiErrors?.name_regex_keep || this.validateRegexLength(this.name_regex_keep);
+
return {
- nameRegexState: this.validateNameRegex(this.name_regex),
- nameKeepRegexState: this.validateNameRegex(this.name_regex_keep),
+ /*
+ * The state has this form:
+ * null: gray border, no message
+ * true: green border, no message ( because none is configured)
+ * false: red border, error message
+ * So in this function we keep null if the are no message otherwise we 'invert' the error message
+ */
+ name_regex: {
+ state: nameRegexErrors === null ? null : !nameRegexErrors,
+ message: nameRegexErrors,
+ },
+ name_regex_keep: {
+ state: nameKeepRegexErrors === null ? null : !nameKeepRegexErrors,
+ message: nameKeepRegexErrors,
+ },
};
},
fieldsValidity() {
return (
- this.textAreaState.nameRegexState !== false &&
- this.textAreaState.nameKeepRegexState !== false
+ this.textAreaValidation.name_regex.state !== false &&
+ this.textAreaValidation.name_regex_keep.state !== false
);
},
isFormElementDisabled() {
@@ -140,8 +160,11 @@ export default {
},
},
methods: {
- validateNameRegex(value) {
- return value ? value.length <= NAME_REGEX_LENGTH : null;
+ validateRegexLength(value) {
+ if (!value) {
+ return null;
+ }
+ return value.length <= NAME_REGEX_LENGTH ? '' : TEXT_AREA_INVALID_FEEDBACK;
},
idGenerator(id) {
return `${id}_${this.uniqueId}`;
@@ -154,22 +177,22 @@ export default {
</script>
<template>
- <div ref="form-elements" class="lh-2">
+ <div ref="form-elements" class="gl-line-height-20">
<gl-form-group
:id="idGenerator('expiration-policy-toggle-group')"
:label-cols="labelCols"
:label-align="labelAlign"
:label-for="idGenerator('expiration-policy-toggle')"
- :label="$options.i18n.enableToggleLabel"
+ :label="$options.i18n.ENABLE_TOGGLE_LABEL"
>
- <div class="d-flex align-items-start">
+ <div class="gl-display-flex">
<gl-toggle
:id="idGenerator('expiration-policy-toggle')"
v-model="enabled"
:disabled="isLoading"
/>
- <span class="mb-2 ml-1 lh-2">
- <gl-sprintf :message="$options.i18n.enableToggleDescription">
+ <span class="gl-mb-3 gl-ml-3 gl-line-height-20">
+ <gl-sprintf :message="$options.i18n.ENABLE_TOGGLE_DESCRIPTION">
<template #toggleStatus>
<strong>{{ policyEnabledText }}</strong>
</template>
@@ -210,8 +233,8 @@ export default {
:label-cols="labelCols"
:label-align="labelAlign"
:label-for="idGenerator(textarea.name)"
- :state="textAreaState[textarea.stateVariable]"
- :invalid-feedback="$options.i18n.textAreaInvalidFeedback"
+ :state="textAreaValidation[textarea.model].state"
+ :invalid-feedback="textAreaValidation[textarea.model].message"
>
<template #label>
<gl-sprintf :message="textarea.label">
@@ -224,7 +247,7 @@ export default {
:id="idGenerator(textarea.name)"
:value="value[textarea.model]"
:placeholder="textarea.placeholder"
- :state="textAreaState[textarea.stateVariable]"
+ :state="textAreaValidation[textarea.model].state"
:disabled="isFormElementDisabled"
trim
@input="updateModel($event, textarea.model)"
diff --git a/app/assets/javascripts/registry/shared/constants.js b/app/assets/javascripts/registry/shared/constants.js
index 4689d01b1c8..36d55c7610e 100644
--- a/app/assets/javascripts/registry/shared/constants.js
+++ b/app/assets/javascripts/registry/shared/constants.js
@@ -1,29 +1,29 @@
import { s__, __ } from '~/locale';
export const FETCH_SETTINGS_ERROR_MESSAGE = s__(
- 'ContainerRegistry|Something went wrong while fetching the expiration policy.',
+ 'ContainerRegistry|Something went wrong while fetching the cleanup policy.',
);
export const UPDATE_SETTINGS_ERROR_MESSAGE = s__(
- 'ContainerRegistry|Something went wrong while updating the expiration policy.',
+ 'ContainerRegistry|Something went wrong while updating the cleanup policy.',
);
export const UPDATE_SETTINGS_SUCCESS_MESSAGE = s__(
- 'ContainerRegistry|Expiration policy successfully saved.',
+ 'ContainerRegistry|Cleanup policy successfully saved.',
);
export const NAME_REGEX_LENGTH = 255;
-export const ENABLED_TEXT = __('enabled');
-export const DISABLED_TEXT = __('disabled');
+export const ENABLED_TEXT = __('Enabled');
+export const DISABLED_TEXT = __('Disabled');
-export const ENABLE_TOGGLE_LABEL = s__('ContainerRegistry|Expiration policy:');
+export const ENABLE_TOGGLE_LABEL = s__('ContainerRegistry|Cleanup policy:');
export const ENABLE_TOGGLE_DESCRIPTION = s__(
- 'ContainerRegistry|Docker tag expiration policy is %{toggleStatus}',
+ 'ContainerRegistry|%{toggleStatus} - Tags matching the patterns defined below will be scheduled for deletion',
);
export const TEXT_AREA_INVALID_FEEDBACK = s__(
- 'ContainerRegistry|The value of this input should be less than 255 characters',
+ 'ContainerRegistry|The value of this input should be less than 256 characters',
);
export const EXPIRATION_INTERVAL_LABEL = s__('ContainerRegistry|Expiration interval:');
@@ -34,12 +34,12 @@ export const NAME_REGEX_LABEL = s__(
);
export const NAME_REGEX_PLACEHOLDER = '.*';
export const NAME_REGEX_DESCRIPTION = s__(
- 'ContainerRegistry|Regular expressions such as %{codeStart}.*-test%{codeEnd} or %{codeStart}dev-.*%{codeEnd} are supported. To select all tags, use %{codeStart}.*%{codeEnd}',
+ 'ContainerRegistry|Wildcards such as %{codeStart}.*-test%{codeEnd} or %{codeStart}dev-.*%{codeEnd} are supported. To select all tags, use %{codeStart}.*%{codeEnd}',
);
export const NAME_REGEX_KEEP_LABEL = s__(
'ContainerRegistry|Tags with names matching this regex pattern will %{italicStart}be preserved:%{italicEnd}',
);
export const NAME_REGEX_KEEP_PLACEHOLDER = '';
export const NAME_REGEX_KEEP_DESCRIPTION = s__(
- 'ContainerRegistry|Regular expressions such as %{codeStart}.*-master%{codeEnd} or %{codeStart}release-.*%{codeEnd} are supported',
+ 'ContainerRegistry|Wildcards such as %{codeStart}.*-master%{codeEnd} or %{codeStart}release-.*%{codeEnd} are supported',
);
diff --git a/app/assets/javascripts/registry/shared/utils.js b/app/assets/javascripts/registry/shared/utils.js
index d85a3ad28c2..a7377773842 100644
--- a/app/assets/javascripts/registry/shared/utils.js
+++ b/app/assets/javascripts/registry/shared/utils.js
@@ -11,7 +11,7 @@ export const mapComputedToEvent = (list, root) => {
return this[root][e];
},
set(value) {
- this.$emit('input', { ...this[root], [e]: value });
+ this.$emit('input', { newValue: { ...this[root], [e]: value }, modified: e });
},
};
});
diff --git a/app/assets/javascripts/related_merge_requests/components/related_merge_requests.vue b/app/assets/javascripts/related_merge_requests/components/related_merge_requests.vue
index 05803ba09ab..15e9b8559d4 100644
--- a/app/assets/javascripts/related_merge_requests/components/related_merge_requests.vue
+++ b/app/assets/javascripts/related_merge_requests/components/related_merge_requests.vue
@@ -82,7 +82,7 @@ export default {
{{ __('Related merge requests') }}
</span>
<div v-if="totalCount" class="d-inline-flex lh-100 align-middle">
- <div class="mr-count-badge border-width-1px border-style-solid border-color-default">
+ <div class="mr-count-badge gl-display-inline-flex">
<div class="mr-count-badge-count">
<svg class="s16 mr-1 text-secondary">
<icon name="merge-request" class="mr-1 text-secondary" />
diff --git a/app/assets/javascripts/releases/components/app_new.vue b/app/assets/javascripts/releases/components/app_new.vue
new file mode 100644
index 00000000000..563f76b3281
--- /dev/null
+++ b/app/assets/javascripts/releases/components/app_new.vue
@@ -0,0 +1,9 @@
+<script>
+export default {
+ name: 'ReleaseNewApp',
+ components: {},
+};
+</script>
+<template>
+ <div></div>
+</template>
diff --git a/app/assets/javascripts/releases/components/app_show.vue b/app/assets/javascripts/releases/components/app_show.vue
index d521edcc361..0e65d722952 100644
--- a/app/assets/javascripts/releases/components/app_show.vue
+++ b/app/assets/javascripts/releases/components/app_show.vue
@@ -21,7 +21,7 @@ export default {
};
</script>
<template>
- <div class="prepend-top-default">
+ <div class="gl-mt-3">
<gl-skeleton-loading v-if="isFetchingRelease" />
<release-block v-else-if="!fetchError" :release="release" />
diff --git a/app/assets/javascripts/releases/components/evidence_block.vue b/app/assets/javascripts/releases/components/evidence_block.vue
index 2cc15777343..6468e2ded62 100644
--- a/app/assets/javascripts/releases/components/evidence_block.vue
+++ b/app/assets/javascripts/releases/components/evidence_block.vue
@@ -59,7 +59,7 @@ export default {
<template>
<div>
- <div class="card-text prepend-top-default">
+ <div class="card-text gl-mt-3">
<b>{{ __('Evidence collection') }}</b>
</div>
<div v-for="(evidence, index) in evidences" :key="evidenceTitle(index)" class="mb-2">
diff --git a/app/assets/javascripts/releases/components/release_block.vue b/app/assets/javascripts/releases/components/release_block.vue
index adb0e69b786..e0061d88ccb 100644
--- a/app/assets/javascripts/releases/components/release_block.vue
+++ b/app/assets/javascripts/releases/components/release_block.vue
@@ -108,7 +108,7 @@ export default {
<release-block-assets v-if="shouldRenderAssets" :assets="assets" />
<evidence-block v-if="hasEvidence && shouldShowEvidence" :release="release" />
- <div ref="gfm-content" class="card-text prepend-top-default">
+ <div ref="gfm-content" class="card-text gl-mt-3">
<div class="md" v-html="release.descriptionHtml"></div>
</div>
</div>
diff --git a/app/assets/javascripts/releases/components/release_block_assets.vue b/app/assets/javascripts/releases/components/release_block_assets.vue
index e07646e9a9f..ab29ceb0ce6 100644
--- a/app/assets/javascripts/releases/components/release_block_assets.vue
+++ b/app/assets/javascripts/releases/components/release_block_assets.vue
@@ -4,7 +4,7 @@ import Icon from '~/vue_shared/components/icon.vue';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import { ASSET_LINK_TYPE } from '../constants';
import { __, s__, sprintf } from '~/locale';
-import { difference } from 'lodash';
+import { difference, get } from 'lodash';
export default {
name: 'ReleaseBlockAssets',
@@ -54,7 +54,7 @@ export default {
sections() {
return [
{
- links: this.assets.sources.map(s => ({
+ links: get(this.assets, 'sources', []).map(s => ({
url: s.url,
name: sprintf(__('Source code (%{fileExtension})'), { fileExtension: s.format }),
})),
@@ -96,7 +96,7 @@ export default {
</script>
<template>
- <div class="card-text prepend-top-default">
+ <div class="card-text gl-mt-3">
<template v-if="glFeatures.releaseAssetLinkType">
<gl-button
data-testid="accordion-button"
@@ -157,7 +157,7 @@ export default {
<ul v-if="assets.links.length" class="pl-0 mb-0 gl-mt-3 list-unstyled js-assets-list">
<li v-for="link in assets.links" :key="link.name" class="gl-mb-3">
<gl-link v-gl-tooltip.bottom :title="__('Download asset')" :href="link.directAssetUrl">
- <icon name="package" class="align-middle append-right-4 align-text-bottom" />
+ <icon name="package" class="align-middle gl-mr-2 align-text-bottom" />
{{ link.name }}
<span v-if="link.external" data-testid="external-link-indicator">{{
__('(external source)')
@@ -174,7 +174,7 @@ export default {
aria-haspopup="true"
aria-expanded="false"
>
- <icon name="doc-code" class="align-top append-right-4" />
+ <icon name="doc-code" class="align-top gl-mr-2" />
{{ __('Source code') }}
<icon name="chevron-down" />
</button>
diff --git a/app/assets/javascripts/releases/components/release_block_header.vue b/app/assets/javascripts/releases/components/release_block_header.vue
index ed49841757a..310fba0fe76 100644
--- a/app/assets/javascripts/releases/components/release_block_header.vue
+++ b/app/assets/javascripts/releases/components/release_block_header.vue
@@ -56,7 +56,7 @@ export default {
v-gl-tooltip
category="primary"
variant="default"
- class="append-right-10 js-edit-button ml-2 pb-2"
+ class="gl-mr-3 js-edit-button ml-2 pb-2"
:title="__('Edit this release')"
:href="editLink"
>
diff --git a/app/assets/javascripts/releases/components/release_block_metadata.vue b/app/assets/javascripts/releases/components/release_block_metadata.vue
index a3377ce044a..861c2e11798 100644
--- a/app/assets/javascripts/releases/components/release_block_metadata.vue
+++ b/app/assets/javascripts/releases/components/release_block_metadata.vue
@@ -75,7 +75,7 @@ export default {
<release-block-milestones v-if="shouldRenderMilestones" :milestones="release.milestones" />
- <div class="append-right-4">
+ <div class="gl-mr-2">
&bull;
<span
v-gl-tooltip.bottom
diff --git a/app/assets/javascripts/releases/components/release_block_milestone_info.vue b/app/assets/javascripts/releases/components/release_block_milestone_info.vue
index 4f75e15a149..b16ae400d6b 100644
--- a/app/assets/javascripts/releases/components/release_block_milestone_info.vue
+++ b/app/assets/javascripts/releases/components/release_block_milestone_info.vue
@@ -126,12 +126,12 @@ export default {
v-gl-tooltip
:title="milestone.description"
:href="milestone.webUrl"
- class="append-right-4"
+ class="gl-mr-2"
>
{{ milestone.title }}
</gl-link>
<template v-if="shouldRenderBullet(index)">
- <span :key="'bullet-' + milestone.id" class="append-right-4">&bull;</span>
+ <span :key="'bullet-' + milestone.id" class="gl-mr-2">&bull;</span>
</template>
<template v-if="shouldRenderShowMoreLink(index)">
<gl-button :key="'more-button-' + milestone.id" variant="link" @click="toggleShowAll">
diff --git a/app/assets/javascripts/releases/mount_new.js b/app/assets/javascripts/releases/mount_new.js
new file mode 100644
index 00000000000..eb02c194c59
--- /dev/null
+++ b/app/assets/javascripts/releases/mount_new.js
@@ -0,0 +1,20 @@
+import Vue from 'vue';
+import ReleaseNewApp from './components/app_new.vue';
+import createStore from './stores';
+import createDetailModule from './stores/modules/detail';
+
+export default () => {
+ const el = document.getElementById('js-new-release-page');
+
+ const store = createStore({
+ modules: {
+ detail: createDetailModule(el.dataset),
+ },
+ });
+
+ return new Vue({
+ el,
+ store,
+ render: h => h(ReleaseNewApp),
+ });
+};
diff --git a/app/assets/javascripts/releases/stores/modules/detail/state.js b/app/assets/javascripts/releases/stores/modules/detail/state.js
index 6d0d102c719..966c1c00ef5 100644
--- a/app/assets/javascripts/releases/stores/modules/detail/state.js
+++ b/app/assets/javascripts/releases/stores/modules/detail/state.js
@@ -1,17 +1,17 @@
export default ({
projectId,
- tagName,
- releasesPagePath,
markdownDocsPath,
markdownPreviewPath,
updateReleaseApiDocsPath,
releaseAssetsDocsPath,
manageMilestonesPath,
newMilestonePath,
+
+ tagName = null,
+ releasesPagePath = null,
+ defaultBranch = null,
}) => ({
projectId,
- tagName,
- releasesPagePath,
markdownDocsPath,
markdownPreviewPath,
updateReleaseApiDocsPath,
@@ -19,6 +19,10 @@ export default ({
manageMilestonesPath,
newMilestonePath,
+ tagName,
+ releasesPagePath,
+ defaultBranch,
+
/** The Release object */
release: null,
diff --git a/app/assets/javascripts/reports/accessibility_report/components/accessibility_issue_body.vue b/app/assets/javascripts/reports/accessibility_report/components/accessibility_issue_body.vue
index 653dcced98b..ed4f3c4e0fe 100644
--- a/app/assets/javascripts/reports/accessibility_report/components/accessibility_issue_body.vue
+++ b/app/assets/javascripts/reports/accessibility_report/components/accessibility_issue_body.vue
@@ -36,13 +36,9 @@ export default {
};
</script>
<template>
- <div class="report-block-list-issue-description prepend-top-5 append-bottom-5">
+ <div class="report-block-list-issue-description gl-mt-2 gl-mb-2">
<div ref="accessibility-issue-description" class="report-block-list-issue-description-text">
- <div
- v-if="isNew"
- ref="accessibility-issue-is-new-badge"
- class="badge badge-danger append-right-5"
- >
+ <div v-if="isNew" ref="accessibility-issue-is-new-badge" class="badge badge-danger gl-mr-2">
{{ s__('AccessibilityReport|New') }}
</div>
<div>
@@ -55,7 +51,7 @@ export default {
)
}}
<gl-link ref="accessibility-issue-learn-more" :href="learnMoreUrl" target="_blank">{{
- s__('AccessibilityReport|Learn More')
+ s__('AccessibilityReport|Learn more')
}}</gl-link>
</div>
{{ sprintf(s__('AccessibilityReport|Message: %{message}'), { message: issue.message }) }}
diff --git a/app/assets/javascripts/reports/codequality_report/components/codequality_issue_body.vue b/app/assets/javascripts/reports/codequality_report/components/codequality_issue_body.vue
new file mode 100644
index 00000000000..0c758ee2b5c
--- /dev/null
+++ b/app/assets/javascripts/reports/codequality_report/components/codequality_issue_body.vue
@@ -0,0 +1,42 @@
+<script>
+/**
+ * Renders Code quality body text
+ * Fixed: [name] in [link]:[line]
+ */
+import ReportLink from '~/reports/components/report_link.vue';
+import { STATUS_SUCCESS } from '~/reports/constants';
+
+export default {
+ name: 'CodequalityIssueBody',
+
+ components: {
+ ReportLink,
+ },
+ props: {
+ status: {
+ type: String,
+ required: true,
+ },
+ issue: {
+ type: Object,
+ required: true,
+ },
+ },
+ computed: {
+ isStatusSuccess() {
+ return this.status === STATUS_SUCCESS;
+ },
+ },
+};
+</script>
+<template>
+ <div class="report-block-list-issue-description gl-mt-2 gl-mb-2">
+ <div class="report-block-list-issue-description-text">
+ <template v-if="isStatusSuccess">{{ s__('ciReport|Fixed:') }}</template>
+
+ {{ issue.name }}
+ </div>
+
+ <report-link v-if="issue.path" :issue="issue" />
+ </div>
+</template>
diff --git a/app/assets/javascripts/reports/codequality_report/grouped_codequality_reports_app.vue b/app/assets/javascripts/reports/codequality_report/grouped_codequality_reports_app.vue
new file mode 100644
index 00000000000..f3d5b1a80f8
--- /dev/null
+++ b/app/assets/javascripts/reports/codequality_report/grouped_codequality_reports_app.vue
@@ -0,0 +1,83 @@
+<script>
+import { mapState, mapActions, mapGetters } from 'vuex';
+import { componentNames } from '~/reports/components/issue_body';
+import { s__, sprintf } from '~/locale';
+import ReportSection from '~/reports/components/report_section.vue';
+import createStore from './store';
+
+export default {
+ name: 'GroupedCodequalityReportsApp',
+ store: createStore(),
+ components: {
+ ReportSection,
+ },
+ props: {
+ headPath: {
+ type: String,
+ required: true,
+ },
+ headBlobPath: {
+ type: String,
+ required: true,
+ },
+ basePath: {
+ type: String,
+ required: false,
+ default: null,
+ },
+ baseBlobPath: {
+ type: String,
+ required: false,
+ default: null,
+ },
+ codequalityHelpPath: {
+ type: String,
+ required: true,
+ },
+ },
+ componentNames,
+ computed: {
+ ...mapState(['newIssues', 'resolvedIssues']),
+ ...mapGetters([
+ 'hasCodequalityIssues',
+ 'codequalityStatus',
+ 'codequalityText',
+ 'codequalityPopover',
+ ]),
+ },
+ created() {
+ this.setPaths({
+ basePath: this.basePath,
+ headPath: this.headPath,
+ baseBlobPath: this.baseBlobPath,
+ headBlobPath: this.headBlobPath,
+ helpPath: this.codequalityHelpPath,
+ });
+
+ this.fetchReports();
+ },
+ methods: {
+ ...mapActions(['fetchReports', 'setPaths']),
+ },
+ loadingText: sprintf(s__('ciReport|Loading %{reportName} report'), {
+ reportName: 'codeclimate',
+ }),
+ errorText: sprintf(s__('ciReport|Failed to load %{reportName} report'), {
+ reportName: 'codeclimate',
+ }),
+};
+</script>
+<template>
+ <report-section
+ :status="codequalityStatus"
+ :loading-text="$options.loadingText"
+ :error-text="$options.errorText"
+ :success-text="codequalityText"
+ :unresolved-issues="newIssues"
+ :resolved-issues="resolvedIssues"
+ :has-issues="hasCodequalityIssues"
+ :component="$options.componentNames.CodequalityIssueBody"
+ :popover-options="codequalityPopover"
+ class="js-codequality-widget mr-widget-border-top mr-report"
+ />
+</template>
diff --git a/app/assets/javascripts/reports/codequality_report/store/actions.js b/app/assets/javascripts/reports/codequality_report/store/actions.js
new file mode 100644
index 00000000000..bf84d27b5ea
--- /dev/null
+++ b/app/assets/javascripts/reports/codequality_report/store/actions.js
@@ -0,0 +1,30 @@
+import axios from '~/lib/utils/axios_utils';
+import * as types from './mutation_types';
+import { parseCodeclimateMetrics, doCodeClimateComparison } from './utils/codequality_comparison';
+
+export const setPaths = ({ commit }, paths) => commit(types.SET_PATHS, paths);
+
+export const fetchReports = ({ state, dispatch, commit }) => {
+ commit(types.REQUEST_REPORTS);
+
+ if (!state.basePath) {
+ return dispatch('receiveReportsError');
+ }
+ return Promise.all([axios.get(state.headPath), axios.get(state.basePath)])
+ .then(results =>
+ doCodeClimateComparison(
+ parseCodeclimateMetrics(results[0].data, state.headBlobPath),
+ parseCodeclimateMetrics(results[1].data, state.baseBlobPath),
+ ),
+ )
+ .then(data => dispatch('receiveReportsSuccess', data))
+ .catch(() => dispatch('receiveReportsError'));
+};
+
+export const receiveReportsSuccess = ({ commit }, data) => {
+ commit(types.RECEIVE_REPORTS_SUCCESS, data);
+};
+
+export const receiveReportsError = ({ commit }) => {
+ commit(types.RECEIVE_REPORTS_ERROR);
+};
diff --git a/app/assets/javascripts/reports/codequality_report/store/getters.js b/app/assets/javascripts/reports/codequality_report/store/getters.js
new file mode 100644
index 00000000000..5df58c7f85f
--- /dev/null
+++ b/app/assets/javascripts/reports/codequality_report/store/getters.js
@@ -0,0 +1,58 @@
+import { LOADING, ERROR, SUCCESS } from '../../constants';
+import { sprintf, __, s__, n__ } from '~/locale';
+
+export const hasCodequalityIssues = state =>
+ Boolean(state.newIssues?.length || state.resolvedIssues?.length);
+
+export const codequalityStatus = state => {
+ if (state.isLoading) {
+ return LOADING;
+ }
+ if (state.hasError) {
+ return ERROR;
+ }
+
+ return SUCCESS;
+};
+
+export const codequalityText = state => {
+ const { newIssues, resolvedIssues } = state;
+ const text = [];
+
+ if (!newIssues.length && !resolvedIssues.length) {
+ text.push(s__('ciReport|No changes to code quality'));
+ } else {
+ text.push(s__('ciReport|Code quality'));
+
+ if (resolvedIssues.length) {
+ text.push(n__(' improved on %d point', ' improved on %d points', resolvedIssues.length));
+ }
+
+ if (newIssues.length && resolvedIssues.length) {
+ text.push(__(' and'));
+ }
+
+ if (newIssues.length) {
+ text.push(n__(' degraded on %d point', ' degraded on %d points', newIssues.length));
+ }
+ }
+
+ return text.join('');
+};
+
+export const codequalityPopover = state => {
+ if (state.headPath && !state.basePath) {
+ return {
+ title: s__('ciReport|Base pipeline codequality artifact not found'),
+ content: sprintf(
+ s__('ciReport|%{linkStartTag}Learn more about codequality reports %{linkEndTag}'),
+ {
+ linkStartTag: `<a href="${state.helpPath}" target="_blank" rel="noopener noreferrer">`,
+ linkEndTag: '<i class="fa fa-external-link" aria-hidden="true"></i></a>',
+ },
+ false,
+ ),
+ };
+ }
+ return {};
+};
diff --git a/app/assets/javascripts/reports/codequality_report/store/index.js b/app/assets/javascripts/reports/codequality_report/store/index.js
new file mode 100644
index 00000000000..047964260ad
--- /dev/null
+++ b/app/assets/javascripts/reports/codequality_report/store/index.js
@@ -0,0 +1,16 @@
+import Vue from 'vue';
+import Vuex from 'vuex';
+import * as actions from './actions';
+import * as getters from './getters';
+import mutations from './mutations';
+import state from './state';
+
+Vue.use(Vuex);
+
+export default initialState =>
+ new Vuex.Store({
+ actions,
+ getters,
+ mutations,
+ state: state(initialState),
+ });
diff --git a/app/assets/javascripts/reports/codequality_report/store/mutation_types.js b/app/assets/javascripts/reports/codequality_report/store/mutation_types.js
new file mode 100644
index 00000000000..c362c973ae1
--- /dev/null
+++ b/app/assets/javascripts/reports/codequality_report/store/mutation_types.js
@@ -0,0 +1,5 @@
+export const SET_PATHS = 'SET_PATHS';
+
+export const REQUEST_REPORTS = 'REQUEST_REPORTS';
+export const RECEIVE_REPORTS_SUCCESS = 'RECEIVE_REPORTS_SUCCESS';
+export const RECEIVE_REPORTS_ERROR = 'RECEIVE_REPORTS_ERROR';
diff --git a/app/assets/javascripts/reports/codequality_report/store/mutations.js b/app/assets/javascripts/reports/codequality_report/store/mutations.js
new file mode 100644
index 00000000000..7ef4f3ce2db
--- /dev/null
+++ b/app/assets/javascripts/reports/codequality_report/store/mutations.js
@@ -0,0 +1,24 @@
+import * as types from './mutation_types';
+
+export default {
+ [types.SET_PATHS](state, paths) {
+ state.basePath = paths.basePath;
+ state.headPath = paths.headPath;
+ state.baseBlobPath = paths.baseBlobPath;
+ state.headBlobPath = paths.headBlobPath;
+ state.helpPath = paths.helpPath;
+ },
+ [types.REQUEST_REPORTS](state) {
+ state.isLoading = true;
+ },
+ [types.RECEIVE_REPORTS_SUCCESS](state, data) {
+ state.hasError = false;
+ state.isLoading = false;
+ state.newIssues = data.newIssues;
+ state.resolvedIssues = data.resolvedIssues;
+ },
+ [types.RECEIVE_REPORTS_ERROR](state) {
+ state.isLoading = false;
+ state.hasError = true;
+ },
+};
diff --git a/app/assets/javascripts/reports/codequality_report/store/state.js b/app/assets/javascripts/reports/codequality_report/store/state.js
new file mode 100644
index 00000000000..38ab53b432e
--- /dev/null
+++ b/app/assets/javascripts/reports/codequality_report/store/state.js
@@ -0,0 +1,15 @@
+export default () => ({
+ basePath: null,
+ headPath: null,
+
+ baseBlobPath: null,
+ headBlobPath: null,
+
+ isLoading: false,
+ hasError: false,
+
+ newIssues: [],
+ resolvedIssues: [],
+
+ helpPath: null,
+});
diff --git a/app/assets/javascripts/reports/codequality_report/store/utils/codequality_comparison.js b/app/assets/javascripts/reports/codequality_report/store/utils/codequality_comparison.js
new file mode 100644
index 00000000000..eba9e340c4e
--- /dev/null
+++ b/app/assets/javascripts/reports/codequality_report/store/utils/codequality_comparison.js
@@ -0,0 +1,41 @@
+import CodeQualityComparisonWorker from '../../workers/codequality_comparison_worker';
+
+export const parseCodeclimateMetrics = (issues = [], path = '') => {
+ return issues.map(issue => {
+ const parsedIssue = {
+ ...issue,
+ name: issue.description,
+ };
+
+ if (issue?.location?.path) {
+ let parseCodeQualityUrl = `${path}/${issue.location.path}`;
+ parsedIssue.path = issue.location.path;
+
+ if (issue?.location?.lines?.begin) {
+ parsedIssue.line = issue.location.lines.begin;
+ parseCodeQualityUrl += `#L${issue.location.lines.begin}`;
+ } else if (issue?.location?.positions?.begin?.line) {
+ parsedIssue.line = issue.location.positions.begin.line;
+ parseCodeQualityUrl += `#L${issue.location.positions.begin.line}`;
+ }
+
+ parsedIssue.urlPath = parseCodeQualityUrl;
+ }
+
+ return parsedIssue;
+ });
+};
+
+export const doCodeClimateComparison = (headIssues, baseIssues) => {
+ // Do these comparisons in worker threads to avoid blocking the main thread
+ return new Promise((resolve, reject) => {
+ const worker = new CodeQualityComparisonWorker();
+ worker.addEventListener('message', ({ data }) =>
+ data.newIssues && data.resolvedIssues ? resolve(data) : reject(data),
+ );
+ worker.postMessage({
+ headIssues,
+ baseIssues,
+ });
+ });
+};
diff --git a/app/assets/javascripts/reports/codequality_report/workers/codequality_comparison_worker.js b/app/assets/javascripts/reports/codequality_report/workers/codequality_comparison_worker.js
new file mode 100644
index 00000000000..fc55602f95c
--- /dev/null
+++ b/app/assets/javascripts/reports/codequality_report/workers/codequality_comparison_worker.js
@@ -0,0 +1,28 @@
+import { differenceBy } from 'lodash';
+
+const KEY_TO_FILTER_BY = 'fingerprint';
+
+// eslint-disable-next-line no-restricted-globals
+self.addEventListener('message', e => {
+ const { data } = e;
+
+ if (data === undefined) {
+ return null;
+ }
+
+ const { headIssues, baseIssues } = data;
+
+ if (!headIssues || !baseIssues) {
+ // eslint-disable-next-line no-restricted-globals
+ return self.postMessage({});
+ }
+
+ // eslint-disable-next-line no-restricted-globals
+ self.postMessage({
+ newIssues: differenceBy(headIssues, baseIssues, KEY_TO_FILTER_BY),
+ resolvedIssues: differenceBy(baseIssues, headIssues, KEY_TO_FILTER_BY),
+ });
+
+ // eslint-disable-next-line no-restricted-globals
+ return self.close();
+});
diff --git a/app/assets/javascripts/reports/components/grouped_test_reports_app.vue b/app/assets/javascripts/reports/components/grouped_test_reports_app.vue
index a670cad5f9f..b8a8cb940e7 100644
--- a/app/assets/javascripts/reports/components/grouped_test_reports_app.vue
+++ b/app/assets/javascripts/reports/components/grouped_test_reports_app.vue
@@ -6,7 +6,9 @@ import ReportSection from './report_section.vue';
import SummaryRow from './summary_row.vue';
import IssuesList from './issues_list.vue';
import Modal from './modal.vue';
+import { GlButton } from '@gitlab/ui';
import createStore from '../store';
+import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import { summaryTextBuilder, reportTextBuilder, statusIcon } from '../store/utils';
export default {
@@ -17,12 +19,19 @@ export default {
SummaryRow,
IssuesList,
Modal,
+ GlButton,
},
+ mixins: [glFeatureFlagsMixin()],
props: {
endpoint: {
type: String,
required: true,
},
+ pipelinePath: {
+ type: String,
+ required: false,
+ default: '',
+ },
},
componentNames,
computed: {
@@ -43,6 +52,12 @@ export default {
return summaryTextBuilder(s__('Reports|Test summary'), this.summary);
},
+ testTabURL() {
+ return `${this.pipelinePath}/test_report`;
+ },
+ showViewFullReport() {
+ return Boolean(this.glFeatures.junitPipelineView) && this.pipelinePath.length;
+ },
},
created() {
this.setEndpoint(this.endpoint);
@@ -98,6 +113,16 @@ export default {
:has-issues="reports.length > 0"
class="mr-widget-section grouped-security-reports mr-report"
>
+ <template v-if="showViewFullReport" #actionButtons>
+ <gl-button
+ :href="testTabURL"
+ icon="external-link"
+ data-testid="group-test-reports-full-link"
+ class="gl-mr-3"
+ >
+ {{ s__('ciReport|View full report') }}
+ </gl-button>
+ </template>
<template #body>
<div class="mr-widget-grouped-section report-block">
<template v-for="(report, i) in reports">
diff --git a/app/assets/javascripts/reports/components/issue_body.js b/app/assets/javascripts/reports/components/issue_body.js
index e106e60951b..1e6dc4f8b78 100644
--- a/app/assets/javascripts/reports/components/issue_body.js
+++ b/app/assets/javascripts/reports/components/issue_body.js
@@ -1,12 +1,15 @@
import TestIssueBody from './test_issue_body.vue';
import AccessibilityIssueBody from '../accessibility_report/components/accessibility_issue_body.vue';
+import CodequalityIssueBody from '../codequality_report/components/codequality_issue_body.vue';
export const components = {
AccessibilityIssueBody,
+ CodequalityIssueBody,
TestIssueBody,
};
export const componentNames = {
AccessibilityIssueBody: AccessibilityIssueBody.name,
+ CodequalityIssueBody: CodequalityIssueBody.name,
TestIssueBody: TestIssueBody.name,
};
diff --git a/app/assets/javascripts/reports/components/report_item.vue b/app/assets/javascripts/reports/components/report_item.vue
index 51062cd7928..1b47d03aa01 100644
--- a/app/assets/javascripts/reports/components/report_item.vue
+++ b/app/assets/javascripts/reports/components/report_item.vue
@@ -52,7 +52,7 @@ export default {
v-if="showReportSectionStatusIcon"
:status="status"
:status-icon-size="statusIconSize"
- class="append-right-default"
+ class="gl-mr-3"
/>
<component :is="component" v-if="component" :issue="issue" :status="status" :is-new="isNew" />
diff --git a/app/assets/javascripts/reports/components/report_section.vue b/app/assets/javascripts/reports/components/report_section.vue
index 68956fc6d2b..63af8a5a9ac 100644
--- a/app/assets/javascripts/reports/components/report_section.vue
+++ b/app/assets/javascripts/reports/components/report_section.vue
@@ -91,6 +91,11 @@ export default {
required: false,
default: undefined,
},
+ shouldEmitToggleEvent: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
},
data() {
@@ -157,6 +162,9 @@ export default {
},
methods: {
toggleCollapsed() {
+ if (this.shouldEmitToggleEvent) {
+ this.$emit('toggleEvent');
+ }
this.isCollapsed = !this.isCollapsed;
},
},
@@ -171,7 +179,7 @@ export default {
<div>
{{ headerText }}
<slot :name="slotName"></slot>
- <popover v-if="hasPopover" :options="popoverOptions" class="prepend-left-5" />
+ <popover v-if="hasPopover" :options="popoverOptions" class="gl-ml-2" />
</div>
<slot name="subHeading"></slot>
</div>
diff --git a/app/assets/javascripts/reports/components/summary_row.vue b/app/assets/javascripts/reports/components/summary_row.vue
index b9fc902cd3a..3232c0edf96 100644
--- a/app/assets/javascripts/reports/components/summary_row.vue
+++ b/app/assets/javascripts/reports/components/summary_row.vue
@@ -21,7 +21,8 @@ export default {
props: {
summary: {
type: String,
- required: true,
+ required: false,
+ default: '',
},
statusIcon: {
type: String,
@@ -45,7 +46,7 @@ export default {
</script>
<template>
<div class="report-block-list-issue report-block-list-issue-parent align-items-center">
- <div class="report-block-list-icon append-right-default">
+ <div class="report-block-list-icon gl-mr-3">
<gl-loading-icon
v-if="statusIcon === 'loading'"
css-class="report-block-list-loading-icon"
@@ -58,8 +59,8 @@ export default {
class="report-block-list-issue-description-text"
data-testid="test-summary-row-description"
>
- {{ summary
- }}<span v-if="popoverOptions" class="text-nowrap"
+ <slot name="summary">{{ summary }}</slot
+ ><span v-if="popoverOptions" class="text-nowrap"
>&nbsp;<popover v-if="popoverOptions" :options="popoverOptions" class="align-top" />
</span>
</div>
diff --git a/app/assets/javascripts/reports/components/test_issue_body.vue b/app/assets/javascripts/reports/components/test_issue_body.vue
index c41238070b1..4e0631740d8 100644
--- a/app/assets/javascripts/reports/components/test_issue_body.vue
+++ b/app/assets/javascripts/reports/components/test_issue_body.vue
@@ -25,14 +25,14 @@ export default {
};
</script>
<template>
- <div class="report-block-list-issue-description prepend-top-5 append-bottom-5">
+ <div class="report-block-list-issue-description gl-mt-2 gl-mb-2">
<div class="report-block-list-issue-description-text" data-testid="test-issue-body-description">
<button
type="button"
class="btn-link btn-blank text-left break-link vulnerability-name-button"
@click="openModal({ issue })"
>
- <div v-if="isNew" class="badge badge-danger append-right-5">{{ s__('New') }}</div>
+ <div v-if="isNew" class="badge badge-danger gl-mr-2">{{ s__('New') }}</div>
{{ issue.name }}
</button>
</div>
diff --git a/app/assets/javascripts/repository/components/table/index.vue b/app/assets/javascripts/repository/components/table/index.vue
index c8549180a25..5e0ad7acdfd 100644
--- a/app/assets/javascripts/repository/components/table/index.vue
+++ b/app/assets/javascripts/repository/components/table/index.vue
@@ -80,7 +80,7 @@ export default {
<table-header v-once />
<tbody>
<parent-row
- v-show="showParentRow"
+ v-if="showParentRow"
:commit-ref="escapedRef"
:path="path"
:loading-path="loadingPath"
@@ -97,6 +97,7 @@ export default {
:path="entry.flatPath"
:type="entry.type"
:url="entry.webUrl"
+ :mode="entry.mode"
:submodule-tree-url="entry.treeUrl"
:lfs-oid="entry.lfsOid"
:loading-path="loadingPath"
diff --git a/app/assets/javascripts/repository/components/table/row.vue b/app/assets/javascripts/repository/components/table/row.vue
index d5363016335..615e329f415 100644
--- a/app/assets/javascripts/repository/components/table/row.vue
+++ b/app/assets/javascripts/repository/components/table/row.vue
@@ -66,6 +66,11 @@ export default {
type: String,
required: true,
},
+ mode: {
+ type: String,
+ required: false,
+ default: '',
+ },
type: {
type: String,
required: true,
@@ -140,6 +145,7 @@ export default {
>
<file-icon
:file-name="fullPath"
+ :file-mode="mode"
:folder="isFolder"
:submodule="isSubmodule"
:loading="path === loadingPath"
diff --git a/app/assets/javascripts/repository/components/tree_content.vue b/app/assets/javascripts/repository/components/tree_content.vue
index 7b34e9ef60d..59ba1caa8c9 100644
--- a/app/assets/javascripts/repository/components/tree_content.vue
+++ b/app/assets/javascripts/repository/components/tree_content.vue
@@ -5,7 +5,6 @@ import FileTable from './table/index.vue';
import getRefMixin from '../mixins/get_ref';
import getFiles from '../queries/getFiles.query.graphql';
import getProjectPath from '../queries/getProjectPath.query.graphql';
-import getVueFileListLfsBadge from '../queries/getVueFileListLfsBadge.query.graphql';
import FilePreview from './preview/index.vue';
import { readmeFile } from '../utils/readme';
@@ -21,9 +20,6 @@ export default {
projectPath: {
query: getProjectPath,
},
- vueFileListLfsBadge: {
- query: getVueFileListLfsBadge,
- },
},
props: {
path: {
@@ -47,7 +43,6 @@ export default {
blobs: [],
},
isLoadingFiles: false,
- vueFileListLfsBadge: false,
};
},
computed: {
@@ -82,7 +77,6 @@ export default {
path: this.path || '/',
nextPageCursor: this.nextPageCursor,
pageSize: PAGE_SIZE,
- vueLfsEnabled: this.vueFileListLfsBadge,
},
})
.then(({ data }) => {
diff --git a/app/assets/javascripts/repository/components/web_ide_link.vue b/app/assets/javascripts/repository/components/web_ide_link.vue
new file mode 100644
index 00000000000..6549d5a3878
--- /dev/null
+++ b/app/assets/javascripts/repository/components/web_ide_link.vue
@@ -0,0 +1,47 @@
+<script>
+import TreeActionLink from './tree_action_link.vue';
+import { __ } from '~/locale';
+import { webIDEUrl } from '~/lib/utils/url_utility';
+
+export default {
+ components: {
+ TreeActionLink,
+ },
+ props: {
+ projectPath: {
+ type: String,
+ required: true,
+ },
+ refSha: {
+ type: String,
+ required: true,
+ },
+ canPushCode: {
+ type: Boolean,
+ required: false,
+ default: true,
+ },
+ forkPath: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ },
+ computed: {
+ showLinkToFork() {
+ return !this.canPushCode && this.forkPath;
+ },
+ text() {
+ return this.showLinkToFork ? __('Edit fork in Web IDE') : __('Web IDE');
+ },
+ path() {
+ const path = this.showLinkToFork ? this.forkPath : this.projectPath;
+ return webIDEUrl(`/${path}/edit/${this.refSha}/-/${this.$route.params.path || ''}`);
+ },
+ },
+};
+</script>
+
+<template>
+ <tree-action-link :path="path" :text="text" data-qa-selector="web_ide_button" />
+</template>
diff --git a/app/assets/javascripts/repository/graphql.js b/app/assets/javascripts/repository/graphql.js
index 6640b636597..450a1571165 100644
--- a/app/assets/javascripts/repository/graphql.js
+++ b/app/assets/javascripts/repository/graphql.js
@@ -30,7 +30,7 @@ const defaultClient = createDefaultClient(
},
readme(_, { url }) {
return axios
- .get(url, { params: { viewer: 'rich', format: 'json' } })
+ .get(url, { params: { format: 'json', viewer: 'rich' } })
.then(({ data }) => ({ ...data, __typename: 'ReadmeFile' }));
},
},
diff --git a/app/assets/javascripts/repository/index.js b/app/assets/javascripts/repository/index.js
index 6528e283372..4f80ab4ff5d 100644
--- a/app/assets/javascripts/repository/index.js
+++ b/app/assets/javascripts/repository/index.js
@@ -4,18 +4,26 @@ import App from './components/app.vue';
import Breadcrumbs from './components/breadcrumbs.vue';
import LastCommit from './components/last_commit.vue';
import TreeActionLink from './components/tree_action_link.vue';
+import WebIdeLink from './components/web_ide_link.vue';
import DirectoryDownloadLinks from './components/directory_download_links.vue';
import apolloProvider from './graphql';
import { setTitle } from './utils/title';
import { updateFormAction } from './utils/dom';
import { parseBoolean } from '../lib/utils/common_utils';
-import { webIDEUrl } from '../lib/utils/url_utility';
import { __ } from '../locale';
export default function setupVueRepositoryList() {
const el = document.getElementById('js-tree-list');
const { dataset } = el;
- const { projectPath, projectShortPath, ref, escapedRef, fullName } = dataset;
+ const {
+ canPushCode,
+ projectPath,
+ projectShortPath,
+ forkPath,
+ ref,
+ escapedRef,
+ fullName,
+ } = dataset;
const router = createRouter(projectPath, escapedRef);
apolloProvider.clients.defaultClient.cache.writeData({
@@ -24,7 +32,6 @@ export default function setupVueRepositoryList() {
projectShortPath,
ref,
escapedRef,
- vueFileListLfsBadge: gon.features?.vueFileListLfsBadge || false,
commits: [],
},
});
@@ -118,11 +125,12 @@ export default function setupVueRepositoryList() {
el: webIdeLinkEl,
router,
render(h) {
- return h(TreeActionLink, {
+ return h(WebIdeLink, {
props: {
- path: webIDEUrl(`/${projectPath}/edit/${ref}/-/${this.$route.params.path || ''}`),
- text: __('Web IDE'),
- cssClass: 'qa-web-ide-button',
+ projectPath,
+ refSha: ref,
+ forkPath,
+ canPushCode: parseBoolean(canPushCode),
},
});
},
diff --git a/app/assets/javascripts/repository/queries/getFiles.query.graphql b/app/assets/javascripts/repository/queries/getFiles.query.graphql
index 01ad72ef752..feb89df0492 100644
--- a/app/assets/javascripts/repository/queries/getFiles.query.graphql
+++ b/app/assets/javascripts/repository/queries/getFiles.query.graphql
@@ -14,7 +14,6 @@ query getFiles(
$ref: String!
$pageSize: Int!
$nextPageCursor: String
- $vueLfsEnabled: Boolean = false
) {
project(fullPath: $projectPath) {
repository {
@@ -46,8 +45,9 @@ query getFiles(
edges {
node {
...TreeEntry
+ mode
webUrl
- lfsOid @include(if: $vueLfsEnabled)
+ lfsOid
}
}
pageInfo {
diff --git a/app/assets/javascripts/repository/queries/getVueFileListLfsBadge.query.graphql b/app/assets/javascripts/repository/queries/getVueFileListLfsBadge.query.graphql
deleted file mode 100644
index eb21c1e73d8..00000000000
--- a/app/assets/javascripts/repository/queries/getVueFileListLfsBadge.query.graphql
+++ /dev/null
@@ -1,3 +0,0 @@
-query getVueFileListLfsBadge {
- vueFileListLfsBadge @client
-}
diff --git a/app/assets/javascripts/search_autocomplete.js b/app/assets/javascripts/search_autocomplete.js
new file mode 100644
index 00000000000..05e0b9e7089
--- /dev/null
+++ b/app/assets/javascripts/search_autocomplete.js
@@ -0,0 +1,500 @@
+/* eslint-disable no-return-assign, consistent-return, class-methods-use-this */
+
+import $ from 'jquery';
+import { escape, throttle } from 'lodash';
+import { s__, __, sprintf } from '~/locale';
+import { getIdenticonBackgroundClass, getIdenticonTitle } from '~/helpers/avatar_helper';
+import axios from './lib/utils/axios_utils';
+import {
+ isInGroupsPage,
+ isInProjectPage,
+ getGroupSlug,
+ getProjectSlug,
+ spriteIcon,
+} from './lib/utils/common_utils';
+
+/**
+ * Search input in top navigation bar.
+ * On click, opens a dropdown
+ * As the user types it filters the results
+ * When the user clicks `x` button it cleans the input and closes the dropdown.
+ */
+
+const KEYCODE = {
+ ESCAPE: 27,
+ BACKSPACE: 8,
+ ENTER: 13,
+ UP: 38,
+ DOWN: 40,
+};
+
+function setSearchOptions() {
+ const $projectOptionsDataEl = $('.js-search-project-options');
+ const $groupOptionsDataEl = $('.js-search-group-options');
+ const $dashboardOptionsDataEl = $('.js-search-dashboard-options');
+
+ if ($projectOptionsDataEl.length) {
+ gl.projectOptions = gl.projectOptions || {};
+
+ const projectPath = $projectOptionsDataEl.data('projectPath');
+
+ gl.projectOptions[projectPath] = {
+ name: $projectOptionsDataEl.data('name'),
+ issuesPath: $projectOptionsDataEl.data('issuesPath'),
+ issuesDisabled: $projectOptionsDataEl.data('issuesDisabled'),
+ mrPath: $projectOptionsDataEl.data('mrPath'),
+ };
+ }
+
+ if ($groupOptionsDataEl.length) {
+ gl.groupOptions = gl.groupOptions || {};
+
+ const groupPath = $groupOptionsDataEl.data('groupPath');
+
+ gl.groupOptions[groupPath] = {
+ name: $groupOptionsDataEl.data('name'),
+ issuesPath: $groupOptionsDataEl.data('issuesPath'),
+ mrPath: $groupOptionsDataEl.data('mrPath'),
+ };
+ }
+
+ if ($dashboardOptionsDataEl.length) {
+ gl.dashboardOptions = {
+ name: s__('SearchAutocomplete|All GitLab'),
+ issuesPath: $dashboardOptionsDataEl.data('issuesPath'),
+ mrPath: $dashboardOptionsDataEl.data('mrPath'),
+ };
+ }
+}
+
+export class SearchAutocomplete {
+ constructor({ wrap, optsEl, autocompletePath, projectId, projectRef } = {}) {
+ setSearchOptions();
+ this.bindEventContext();
+ this.wrap = wrap || $('.search');
+ this.optsEl = optsEl || this.wrap.find('.search-autocomplete-opts');
+ this.autocompletePath = autocompletePath || this.optsEl.data('autocompletePath');
+ this.projectId = projectId || (this.optsEl.data('autocompleteProjectId') || '');
+ this.projectRef = projectRef || (this.optsEl.data('autocompleteProjectRef') || '');
+ this.dropdown = this.wrap.find('.dropdown');
+ this.dropdownToggle = this.wrap.find('.js-dropdown-search-toggle');
+ this.dropdownMenu = this.dropdown.find('.dropdown-menu');
+ this.dropdownContent = this.dropdown.find('.dropdown-content');
+ this.scopeInputEl = this.getElement('#scope');
+ this.searchInput = this.getElement('.search-input');
+ this.projectInputEl = this.getElement('#search_project_id');
+ this.groupInputEl = this.getElement('#group_id');
+ this.searchCodeInputEl = this.getElement('#search_code');
+ this.repositoryInputEl = this.getElement('#repository_ref');
+ this.clearInput = this.getElement('.js-clear-input');
+ this.scrollFadeInitialized = false;
+ this.saveOriginalState();
+
+ // Only when user is logged in
+ if (gon.current_user_id) {
+ this.createAutocomplete();
+ }
+
+ this.bindEvents();
+ this.dropdownToggle.dropdown();
+ this.searchInput.addClass('js-autocomplete-disabled');
+ }
+
+ // Finds an element inside wrapper element
+ bindEventContext() {
+ this.onSearchInputBlur = this.onSearchInputBlur.bind(this);
+ this.onClearInputClick = this.onClearInputClick.bind(this);
+ this.onSearchInputFocus = this.onSearchInputFocus.bind(this);
+ this.onSearchInputKeyUp = this.onSearchInputKeyUp.bind(this);
+ this.onSearchInputChange = this.onSearchInputChange.bind(this);
+ this.setScrollFade = this.setScrollFade.bind(this);
+ }
+ getElement(selector) {
+ return this.wrap.find(selector);
+ }
+
+ saveOriginalState() {
+ return (this.originalState = this.serializeState());
+ }
+
+ createAutocomplete() {
+ return this.searchInput.glDropdown({
+ filterInputBlur: false,
+ filterable: true,
+ filterRemote: true,
+ highlight: true,
+ icon: true,
+ enterCallback: false,
+ filterInput: 'input#search',
+ search: {
+ fields: ['text'],
+ },
+ id: this.getSearchText,
+ data: this.getData.bind(this),
+ selectable: true,
+ clicked: this.onClick.bind(this),
+ });
+ }
+
+ getSearchText(selectedObject) {
+ return selectedObject.id ? selectedObject.text : '';
+ }
+
+ getData(term, callback) {
+ if (!term) {
+ const contents = this.getCategoryContents();
+ if (contents) {
+ const glDropdownInstance = this.searchInput.data('glDropdown');
+
+ if (glDropdownInstance) {
+ glDropdownInstance.filter.options.callback(contents);
+ }
+ this.enableAutocomplete();
+ }
+ return;
+ }
+
+ // Prevent multiple ajax calls
+ if (this.loadingSuggestions) {
+ return;
+ }
+
+ this.loadingSuggestions = true;
+
+ return axios
+ .get(this.autocompletePath, {
+ params: {
+ project_id: this.projectId,
+ project_ref: this.projectRef,
+ term,
+ },
+ })
+ .then(response => {
+ const options = this.scopedSearchOptions(term);
+
+ // List results
+ let lastCategory = null;
+ for (let i = 0, len = response.data.length; i < len; i += 1) {
+ const suggestion = response.data[i];
+ // Add group header before list each group
+ if (lastCategory !== suggestion.category) {
+ options.push({ type: 'separator' });
+ options.push({
+ type: 'header',
+ content: suggestion.category,
+ });
+ lastCategory = suggestion.category;
+ }
+
+ // Add the suggestion
+ options.push({
+ id: `${suggestion.category.toLowerCase()}-${suggestion.id}`,
+ icon: this.getAvatar(suggestion),
+ category: suggestion.category,
+ text: suggestion.label,
+ url: suggestion.url,
+ });
+ }
+
+ callback(options);
+
+ this.loadingSuggestions = false;
+ this.highlightFirstRow();
+ this.setScrollFade();
+ })
+ .catch(() => {
+ this.loadingSuggestions = false;
+ });
+ }
+
+ getCategoryContents() {
+ const userName = gon.current_username;
+ const { projectOptions, groupOptions, dashboardOptions } = gl;
+
+ // Get options
+ let options;
+ if (isInProjectPage() && projectOptions) {
+ options = projectOptions[getProjectSlug()];
+ } else if (isInGroupsPage() && groupOptions) {
+ options = groupOptions[getGroupSlug()];
+ } else if (dashboardOptions) {
+ options = dashboardOptions;
+ }
+
+ const { issuesPath, mrPath, name, issuesDisabled } = options;
+ const baseItems = [];
+
+ if (name) {
+ baseItems.push({
+ type: 'header',
+ content: `${name}`,
+ });
+ }
+
+ const issueItems = [
+ {
+ text: s__('SearchAutocomplete|Issues assigned to me'),
+ url: `${issuesPath}/?assignee_username=${userName}`,
+ },
+ {
+ text: s__("SearchAutocomplete|Issues I've created"),
+ url: `${issuesPath}/?author_username=${userName}`,
+ },
+ ];
+ const mergeRequestItems = [
+ {
+ text: s__('SearchAutocomplete|Merge requests assigned to me'),
+ url: `${mrPath}/?assignee_username=${userName}`,
+ },
+ {
+ text: s__("SearchAutocomplete|Merge requests I've created"),
+ url: `${mrPath}/?author_username=${userName}`,
+ },
+ ];
+
+ let items;
+ if (issuesDisabled) {
+ items = baseItems.concat(mergeRequestItems);
+ } else {
+ items = baseItems.concat(...issueItems, ...mergeRequestItems);
+ }
+ return items;
+ }
+
+ // Add option to proceed with the search for each
+ // scope that is currently available, namely:
+ //
+ // - Search in this project
+ // - Search in this group (or project's group)
+ // - Search in all GitLab
+ scopedSearchOptions(term) {
+ const icon = spriteIcon('search', 's16 inline-search-icon');
+ const projectId = this.projectInputEl.val();
+ const groupId = this.groupInputEl.val();
+ const options = [];
+
+ if (projectId) {
+ const projectOptions = gl.projectOptions[getProjectSlug()];
+ const url = groupId
+ ? `${gon.relative_url_root}/search?search=${term}&project_id=${projectId}&group_id=${groupId}`
+ : `${gon.relative_url_root}/search?search=${term}&project_id=${projectId}`;
+
+ options.push({
+ icon,
+ text: term,
+ template: sprintf(
+ s__(`SearchAutocomplete|in project %{projectName}`),
+ {
+ projectName: `<i>${projectOptions.name}</i>`,
+ },
+ false,
+ ),
+ url,
+ });
+ }
+
+ if (groupId) {
+ const groupOptions = gl.groupOptions[getGroupSlug()];
+ options.push({
+ icon,
+ text: term,
+ template: sprintf(
+ s__(`SearchAutocomplete|in group %{groupName}`),
+ {
+ groupName: `<i>${groupOptions.name}</i>`,
+ },
+ false,
+ ),
+ url: `${gon.relative_url_root}/search?search=${term}&group_id=${groupId}`,
+ });
+ }
+
+ options.push({
+ icon,
+ text: term,
+ template: s__('SearchAutocomplete|in all GitLab'),
+ url: `${gon.relative_url_root}/search?search=${term}`,
+ });
+
+ return options;
+ }
+
+ serializeState() {
+ return {
+ // Search Criteria
+ search_project_id: this.projectInputEl.val(),
+ group_id: this.groupInputEl.val(),
+ search_code: this.searchCodeInputEl.val(),
+ repository_ref: this.repositoryInputEl.val(),
+ scope: this.scopeInputEl.val(),
+ };
+ }
+
+ bindEvents() {
+ this.searchInput.on('input', this.onSearchInputChange);
+ this.searchInput.on('keyup', this.onSearchInputKeyUp);
+ this.searchInput.on('focus', this.onSearchInputFocus);
+ this.searchInput.on('blur', this.onSearchInputBlur);
+ this.clearInput.on('click', this.onClearInputClick);
+ this.dropdownContent.on('scroll', throttle(this.setScrollFade, 250));
+
+ this.searchInput.on('click', e => {
+ e.stopPropagation();
+ });
+ }
+
+ enableAutocomplete() {
+ this.setScrollFade();
+
+ // No need to enable anything if user is not logged in
+ if (!gon.current_user_id) {
+ return;
+ }
+
+ // If the dropdown is closed, we'll open it
+ if (!this.dropdown.hasClass('show')) {
+ this.loadingSuggestions = false;
+ this.dropdownToggle.dropdown('toggle');
+ return this.searchInput.removeClass('js-autocomplete-disabled');
+ }
+ }
+
+ onSearchInputChange() {
+ this.enableAutocomplete();
+ }
+
+ onSearchInputKeyUp(e) {
+ switch (e.keyCode) {
+ case KEYCODE.ESCAPE:
+ this.restoreOriginalState();
+ break;
+ case KEYCODE.ENTER:
+ this.disableAutocomplete();
+ break;
+ default:
+ }
+ this.wrap.toggleClass('has-value', Boolean(e.target.value));
+ }
+
+ onSearchInputFocus() {
+ this.isFocused = true;
+ this.wrap.addClass('search-active');
+ if (this.getValue() === '') {
+ return this.getData();
+ }
+ }
+
+ getValue() {
+ return this.searchInput.val();
+ }
+
+ onClearInputClick(e) {
+ e.preventDefault();
+ this.wrap.toggleClass('has-value', Boolean(e.target.value));
+ return this.searchInput.val('').focus();
+ }
+
+ onSearchInputBlur() {
+ this.isFocused = false;
+ this.wrap.removeClass('search-active');
+ // If input is blank then restore state
+ if (this.searchInput.val() === '') {
+ this.restoreOriginalState();
+ }
+ this.dropdownMenu.removeClass('show');
+ }
+
+ restoreOriginalState() {
+ const inputs = Object.keys(this.originalState);
+ for (let i = 0, len = inputs.length; i < len; i += 1) {
+ const input = inputs[i];
+ this.getElement(`#${input}`).val(this.originalState[input]);
+ }
+ }
+
+ resetSearchState() {
+ const inputs = Object.keys(this.originalState);
+ const results = [];
+ for (let i = 0, len = inputs.length; i < len; i += 1) {
+ const input = inputs[i];
+ results.push(this.getElement(`#${input}`).val(''));
+ }
+ return results;
+ }
+
+ disableAutocomplete() {
+ if (!this.searchInput.hasClass('js-autocomplete-disabled') && this.dropdown.hasClass('show')) {
+ this.searchInput.addClass('js-autocomplete-disabled');
+ this.dropdownToggle.dropdown('toggle');
+ this.restoreMenu();
+ }
+ }
+
+ restoreMenu() {
+ const html = `<ul><li class="dropdown-menu-empty-item"><a>${__('Loading...')}</a></li></ul>`;
+ return this.dropdownContent.html(html);
+ }
+
+ onClick(item, $el, e) {
+ if (window.location.pathname.indexOf(item.url) !== -1) {
+ if (!e.metaKey) e.preventDefault();
+ /* eslint-disable-next-line @gitlab/require-i18n-strings */
+ if (item.category === 'Projects') {
+ this.projectInputEl.val(item.id);
+ }
+ // eslint-disable-next-line @gitlab/require-i18n-strings
+ if (item.category === 'Groups') {
+ this.groupInputEl.val(item.id);
+ }
+ $el.removeClass('is-active');
+ this.disableAutocomplete();
+ return this.searchInput.val('').focus();
+ }
+ }
+
+ highlightFirstRow() {
+ this.searchInput.data('glDropdown').highlightRowAtIndex(null, 0);
+ }
+
+ getAvatar(item) {
+ if (!Object.hasOwnProperty.call(item, 'avatar_url')) {
+ return false;
+ }
+
+ const { label, id } = item;
+ const avatarUrl = item.avatar_url;
+ const avatar = avatarUrl
+ ? `<img class="search-item-avatar" src="${avatarUrl}" />`
+ : `<div class="s16 avatar identicon ${getIdenticonBackgroundClass(id)}">${getIdenticonTitle(
+ escape(label),
+ )}</div>`;
+
+ return avatar;
+ }
+
+ isScrolledUp() {
+ const el = this.dropdownContent[0];
+ const currentPosition = this.contentClientHeight + el.scrollTop;
+
+ return currentPosition < this.maxPosition;
+ }
+
+ initScrollFade() {
+ const el = this.dropdownContent[0];
+ this.scrollFadeInitialized = true;
+
+ this.contentClientHeight = el.clientHeight;
+ this.maxPosition = el.scrollHeight;
+ this.dropdownMenu.addClass('dropdown-content-faded-mask');
+ }
+
+ setScrollFade() {
+ this.initScrollFade();
+
+ this.dropdownMenu.toggleClass('fade-out', !this.isScrolledUp());
+ }
+}
+
+export default function initSearchAutocomplete(opts) {
+ return new SearchAutocomplete(opts);
+}
diff --git a/app/assets/javascripts/serverless/components/environment_row.vue b/app/assets/javascripts/serverless/components/environment_row.vue
index 089e0550583..c46dfb66afe 100644
--- a/app/assets/javascripts/serverless/components/environment_row.vue
+++ b/app/assets/javascripts/serverless/components/environment_row.vue
@@ -54,7 +54,7 @@ export default {
<div class="folder-toggle-wrap d-flex align-items-center">
<item-caret :is-group-open="isOpen" />
</div>
- <div class="group-text flex-grow title namespace-title prepend-left-default">
+ <div class="group-text flex-grow title namespace-title gl-ml-3">
{{ envName }}
</div>
</div>
diff --git a/app/assets/javascripts/serverless/components/function_details.vue b/app/assets/javascripts/serverless/components/function_details.vue
index 2ac57ac5bcb..53c78b93254 100644
--- a/app/assets/javascripts/serverless/components/function_details.vue
+++ b/app/assets/javascripts/serverless/components/function_details.vue
@@ -71,7 +71,7 @@ export default {
<template>
<section id="serverless-function-details">
<h3 class="serverless-function-name">{{ name }}</h3>
- <div class="append-bottom-default serverless-function-description">
+ <div class="gl-mb-3 serverless-function-description">
<div v-for="(line, index) in description.split('\n')" :key="index">{{ line }}</div>
</div>
<url :uri="funcUrl" />
diff --git a/app/assets/javascripts/serverless/components/functions.vue b/app/assets/javascripts/serverless/components/functions.vue
index 2b1291ac70f..8fa48134f1f 100644
--- a/app/assets/javascripts/serverless/components/functions.vue
+++ b/app/assets/javascripts/serverless/components/functions.vue
@@ -75,11 +75,7 @@ export default {
<template>
<section id="serverless-functions" class="flex-grow">
- <gl-loading-icon
- v-if="checkingInstalled"
- size="lg"
- class="prepend-top-default append-bottom-default"
- />
+ <gl-loading-icon v-if="checkingInstalled" size="lg" class="gl-mt-3 gl-mb-3" />
<div v-else-if="isInstalled">
<div v-if="hasFunctionData">
@@ -95,11 +91,7 @@ export default {
</ul>
</div>
</template>
- <gl-loading-icon
- v-if="isLoading"
- size="lg"
- class="prepend-top-default append-bottom-default js-functions-loader"
- />
+ <gl-loading-icon v-if="isLoading" size="lg" class="gl-mt-3 gl-mb-3 js-functions-loader" />
</div>
<div v-else class="empty-state js-empty-state">
<div class="text-content">
diff --git a/app/assets/javascripts/set_status_modal/set_status_modal_wrapper.vue b/app/assets/javascripts/set_status_modal/set_status_modal_wrapper.vue
index fd1f9eae152..d5ae9b04090 100644
--- a/app/assets/javascripts/set_status_modal/set_status_modal_wrapper.vue
+++ b/app/assets/javascripts/set_status_modal/set_status_modal_wrapper.vue
@@ -8,6 +8,7 @@ import { __, s__ } from '~/locale';
import Api from '~/api';
import eventHub from './event_hub';
import EmojiMenuInModal from './emoji_menu_in_modal';
+import * as Emoji from '~/emoji';
const emojiMenuClass = 'js-modal-status-emoji-menu';
@@ -64,8 +65,8 @@ export default {
const emojiAutocomplete = new GfmAutoComplete();
emojiAutocomplete.setup($(this.$refs.statusMessageField), { emojis: true });
- import(/* webpackChunkName: 'emoji' */ '~/emoji')
- .then(Emoji => {
+ Emoji.initEmojiMap()
+ .then(() => {
if (this.emoji) {
this.emojiTag = Emoji.glEmojiTag(this.emoji);
}
diff --git a/app/assets/javascripts/sidebar/components/confidential/confidential_issue_sidebar.vue b/app/assets/javascripts/sidebar/components/confidential/confidential_issue_sidebar.vue
index 550a1be1e64..0987603cafd 100644
--- a/app/assets/javascripts/sidebar/components/confidential/confidential_issue_sidebar.vue
+++ b/app/assets/javascripts/sidebar/components/confidential/confidential_issue_sidebar.vue
@@ -1,5 +1,5 @@
<script>
-import { mapState } from 'vuex';
+import { mapState, mapActions } from 'vuex';
import { __ } from '~/locale';
import Flash from '~/flash';
import tooltip from '~/vue_shared/directives/tooltip';
@@ -18,6 +18,10 @@ export default {
},
mixins: [recaptchaModalImplementor],
props: {
+ fullPath: {
+ required: true,
+ type: String,
+ },
isEditable: {
required: true,
type: Boolean,
@@ -42,16 +46,24 @@ export default {
},
},
created() {
+ eventHub.$on('updateConfidentialAttribute', this.updateConfidentialAttribute);
eventHub.$on('closeConfidentialityForm', this.toggleForm);
},
beforeDestroy() {
+ eventHub.$off('updateConfidentialAttribute', this.updateConfidentialAttribute);
eventHub.$off('closeConfidentialityForm', this.toggleForm);
},
methods: {
+ ...mapActions(['setConfidentiality']),
toggleForm() {
this.edit = !this.edit;
},
- updateConfidentialAttribute(confidential) {
+ closeForm() {
+ this.edit = false;
+ },
+ updateConfidentialAttribute() {
+ // TODO: rm when FF is defaulted to on.
+ const confidential = !this.confidential;
this.service
.update('issue', { confidential })
.then(({ data }) => this.checkForSpam(data))
@@ -97,12 +109,8 @@ export default {
>
</div>
<div class="value sidebar-item-value hide-collapsed">
- <edit-form
- v-if="edit"
- :is-confidential="confidential"
- :update-confidential-attribute="updateConfidentialAttribute"
- />
- <div v-if="!confidential" class="no-value sidebar-item-value">
+ <edit-form v-if="edit" :is-confidential="confidential" :full-path="fullPath" />
+ <div v-if="!confidential" class="no-value sidebar-item-value" data-testid="not-confidential">
<icon :size="16" name="eye" aria-hidden="true" class="sidebar-item-icon inline" />
{{ __('Not confidential') }}
</div>
diff --git a/app/assets/javascripts/sidebar/components/confidential/edit_form.vue b/app/assets/javascripts/sidebar/components/confidential/edit_form.vue
index 0ecbf934c25..9dd4f04acdb 100644
--- a/app/assets/javascripts/sidebar/components/confidential/edit_form.vue
+++ b/app/assets/javascripts/sidebar/components/confidential/edit_form.vue
@@ -11,9 +11,9 @@ export default {
required: true,
type: Boolean,
},
- updateConfidentialAttribute: {
+ fullPath: {
required: true,
- type: Function,
+ type: String,
},
},
computed: {
@@ -37,10 +37,7 @@ export default {
<div>
<p v-if="!isConfidential" v-html="confidentialityOnWarning"></p>
<p v-else v-html="confidentialityOffWarning"></p>
- <edit-form-buttons
- :is-confidential="isConfidential"
- :update-confidential-attribute="updateConfidentialAttribute"
- />
+ <edit-form-buttons :full-path="fullPath" />
</div>
</div>
</div>
diff --git a/app/assets/javascripts/sidebar/components/confidential/edit_form_buttons.vue b/app/assets/javascripts/sidebar/components/confidential/edit_form_buttons.vue
index e106afea9f5..80928649a03 100644
--- a/app/assets/javascripts/sidebar/components/confidential/edit_form_buttons.vue
+++ b/app/assets/javascripts/sidebar/components/confidential/edit_form_buttons.vue
@@ -1,35 +1,60 @@
<script>
import $ from 'jquery';
-import eventHub from '../../event_hub';
+import { GlLoadingIcon } from '@gitlab/ui';
+import { mapActions, mapState } from 'vuex';
import { __ } from '~/locale';
+import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
+import Flash from '~/flash';
+import eventHub from '../../event_hub';
export default {
+ components: {
+ GlLoadingIcon,
+ },
+ mixins: [glFeatureFlagsMixin()],
props: {
- isConfidential: {
- required: true,
- type: Boolean,
- },
- updateConfidentialAttribute: {
+ fullPath: {
required: true,
- type: Function,
+ type: String,
},
},
+ data() {
+ return {
+ isLoading: false,
+ };
+ },
computed: {
+ ...mapState({ confidential: ({ noteableData }) => noteableData.confidential }),
toggleButtonText() {
- return this.isConfidential ? __('Turn Off') : __('Turn On');
- },
- updateConfidentialBool() {
- return !this.isConfidential;
+ if (this.isLoading) {
+ return __('Applying');
+ }
+
+ return this.confidential ? __('Turn Off') : __('Turn On');
},
},
methods: {
+ ...mapActions(['updateConfidentialityOnIssue']),
closeForm() {
eventHub.$emit('closeConfidentialityForm');
$(this.$el).trigger('hidden.gl.dropdown');
},
submitForm() {
- this.closeForm();
- this.updateConfidentialAttribute(this.updateConfidentialBool);
+ this.isLoading = true;
+ const confidential = !this.confidential;
+
+ if (this.glFeatures.confidentialApolloSidebar) {
+ this.updateConfidentialityOnIssue({ confidential, fullPath: this.fullPath })
+ .catch(() => {
+ Flash(__('Something went wrong trying to change the confidentiality of this issue'));
+ })
+ .finally(() => {
+ this.closeForm();
+ this.isLoading = false;
+ });
+ } else {
+ eventHub.$emit('updateConfidentialAttribute');
+ }
},
},
};
@@ -37,15 +62,17 @@ export default {
<template>
<div class="sidebar-item-warning-message-actions">
- <button type="button" class="btn btn-default append-right-10" @click="closeForm">
+ <button type="button" class="btn btn-default gl-mr-3" @click="closeForm">
{{ __('Cancel') }}
</button>
<button
type="button"
class="btn btn-close"
data-testid="confidential-toggle"
+ :disabled="isLoading"
@click.prevent="submitForm"
>
+ <gl-loading-icon v-if="isLoading" inline />
{{ toggleButtonText }}
</button>
</div>
diff --git a/app/assets/javascripts/sidebar/components/confidential/queries/update_issue_confidential.mutation.graphql b/app/assets/javascripts/sidebar/components/confidential/queries/update_issue_confidential.mutation.graphql
new file mode 100644
index 00000000000..2459aa346c9
--- /dev/null
+++ b/app/assets/javascripts/sidebar/components/confidential/queries/update_issue_confidential.mutation.graphql
@@ -0,0 +1,7 @@
+mutation updateIssueConfidential($input: IssueSetConfidentialInput!) {
+ issueSetConfidential(input: $input) {
+ issue {
+ confidential
+ }
+ }
+}
diff --git a/app/assets/javascripts/sidebar/components/lock/edit_form_buttons.vue b/app/assets/javascripts/sidebar/components/lock/edit_form_buttons.vue
index f88bde624b4..2e85ded8ade 100644
--- a/app/assets/javascripts/sidebar/components/lock/edit_form_buttons.vue
+++ b/app/assets/javascripts/sidebar/components/lock/edit_form_buttons.vue
@@ -41,7 +41,7 @@ export default {
<template>
<div class="sidebar-item-warning-message-actions">
- <button type="button" class="btn btn-default append-right-10" @click="closeForm">
+ <button type="button" class="btn btn-default gl-mr-3" @click="closeForm">
{{ __('Cancel') }}
</button>
diff --git a/app/assets/javascripts/sidebar/mount_sidebar.js b/app/assets/javascripts/sidebar/mount_sidebar.js
index e371091fc53..2c108835c36 100644
--- a/app/assets/javascripts/sidebar/mount_sidebar.js
+++ b/app/assets/javascripts/sidebar/mount_sidebar.js
@@ -11,6 +11,7 @@ import sidebarSubscriptions from './components/subscriptions/sidebar_subscriptio
import Translate from '../vue_shared/translate';
import createDefaultClient from '~/lib/graphql';
import { store } from '~/notes/stores';
+import { isInIssuePage } from '~/lib/utils/common_utils';
Vue.use(Translate);
Vue.use(VueApollo);
@@ -43,7 +44,7 @@ function mountAssigneesComponent(mediator) {
projectPath: fullPath,
field: el.dataset.field,
signedIn: el.hasAttribute('data-signed-in'),
- issuableType: gl.utils.isInIssuePage() ? 'issue' : 'merge_request',
+ issuableType: isInIssuePage() ? 'issue' : 'merge_request',
},
}),
});
@@ -52,20 +53,30 @@ function mountAssigneesComponent(mediator) {
function mountConfidentialComponent(mediator) {
const el = document.getElementById('js-confidential-entry-point');
+ const { fullPath, iid } = getSidebarOptions();
+
if (!el) return;
const dataNode = document.getElementById('js-confidential-issue-data');
const initialData = JSON.parse(dataNode.innerHTML);
- const ConfidentialComp = Vue.extend(ConfidentialIssueSidebar);
-
- new ConfidentialComp({
+ // eslint-disable-next-line no-new
+ new Vue({
+ el,
store,
- propsData: {
- isEditable: initialData.is_editable,
- service: mediator.service,
+ components: {
+ ConfidentialIssueSidebar,
},
- }).$mount(el);
+ render: createElement =>
+ createElement('confidential-issue-sidebar', {
+ props: {
+ iid: String(iid),
+ fullPath,
+ isEditable: initialData.is_editable,
+ service: mediator.service,
+ },
+ }),
+ });
}
function mountLockComponent(mediator) {
@@ -83,7 +94,7 @@ function mountLockComponent(mediator) {
isLocked: initialData.is_locked,
isEditable: initialData.is_editable,
mediator,
- issuableType: gl.utils.isInIssuePage() ? 'issue' : 'merge_request',
+ issuableType: isInIssuePage() ? 'issue' : 'merge_request',
},
}).$mount(el);
}
diff --git a/app/assets/javascripts/sidebar/queries/sidebarDetails.query.graphql b/app/assets/javascripts/sidebar/queries/sidebarDetails.query.graphql
index 8cc68f6ea9a..2aff7da4605 100644
--- a/app/assets/javascripts/sidebar/queries/sidebarDetails.query.graphql
+++ b/app/assets/javascripts/sidebar/queries/sidebarDetails.query.graphql
@@ -1,6 +1,6 @@
-query ($fullPath: ID!, $iid: String!) {
- project (fullPath: $fullPath) {
- issue (iid: $iid) {
+query($fullPath: ID!, $iid: String!) {
+ project(fullPath: $fullPath) {
+ issue(iid: $iid) {
iid
}
}
diff --git a/app/assets/javascripts/sidebar/queries/sidebarDetailsForHealthStatusFeatureFlag.query.graphql b/app/assets/javascripts/sidebar/queries/sidebarDetailsForHealthStatusFeatureFlag.query.graphql
index 8cc68f6ea9a..2aff7da4605 100644
--- a/app/assets/javascripts/sidebar/queries/sidebarDetailsForHealthStatusFeatureFlag.query.graphql
+++ b/app/assets/javascripts/sidebar/queries/sidebarDetailsForHealthStatusFeatureFlag.query.graphql
@@ -1,6 +1,6 @@
-query ($fullPath: ID!, $iid: String!) {
- project (fullPath: $fullPath) {
- issue (iid: $iid) {
+query($fullPath: ID!, $iid: String!) {
+ project(fullPath: $fullPath) {
+ issue(iid: $iid) {
iid
}
}
diff --git a/app/assets/javascripts/snippets/components/edit.vue b/app/assets/javascripts/snippets/components/edit.vue
index a6651515e47..c01f9524ca8 100644
--- a/app/assets/javascripts/snippets/components/edit.vue
+++ b/app/assets/javascripts/snippets/components/edit.vue
@@ -3,9 +3,8 @@ import { GlButton, GlLoadingIcon } from '@gitlab/ui';
import Flash from '~/flash';
import { __, sprintf } from '~/locale';
-import axios from '~/lib/utils/axios_utils';
import TitleField from '~/vue_shared/components/form/title.vue';
-import { getBaseURL, joinPaths, redirectTo } from '~/lib/utils/url_utility';
+import { redirectTo } from '~/lib/utils/url_utility';
import FormFooterActions from '~/vue_shared/components/form/form_footer_actions.vue';
import UpdateSnippetMutation from '../mutations/updateSnippet.mutation.graphql';
@@ -15,6 +14,9 @@ import {
SNIPPET_VISIBILITY_PRIVATE,
SNIPPET_CREATE_MUTATION_ERROR,
SNIPPET_UPDATE_MUTATION_ERROR,
+ SNIPPET_BLOB_ACTION_CREATE,
+ SNIPPET_BLOB_ACTION_UPDATE,
+ SNIPPET_BLOB_ACTION_MOVE,
} from '../constants';
import SnippetBlobEdit from './snippet_blob_edit.vue';
import SnippetVisibilityEdit from './snippet_visibility_edit.vue';
@@ -53,17 +55,25 @@ export default {
},
data() {
return {
- blob: {},
- fileName: '',
- content: '',
- isContentLoading: true,
+ blobsActions: {},
isUpdating: false,
newSnippet: false,
};
},
computed: {
+ getActionsEntries() {
+ return Object.values(this.blobsActions);
+ },
+ allBlobsHaveContent() {
+ const entries = this.getActionsEntries;
+ return entries.length > 0 && !entries.find(action => !action.content);
+ },
+ allBlobChangesRegistered() {
+ const entries = this.getActionsEntries;
+ return entries.length > 0 && !entries.find(action => action.action === '');
+ },
updatePrevented() {
- return this.snippet.title === '' || this.content === '' || this.isUpdating;
+ return this.snippet.title === '' || !this.allBlobsHaveContent || this.isUpdating;
},
isProjectSnippet() {
return Boolean(this.projectPath);
@@ -74,8 +84,7 @@ export default {
title: this.snippet.title,
description: this.snippet.description,
visibilityLevel: this.snippet.visibilityLevel,
- fileName: this.fileName,
- content: this.content,
+ files: this.getActionsEntries.filter(entry => entry.action !== ''),
};
},
saveButtonLabel() {
@@ -97,9 +106,57 @@ export default {
return `${this.isProjectSnippet ? 'project' : 'personal'}_snippet_description`;
},
},
+ created() {
+ window.addEventListener('beforeunload', this.onBeforeUnload);
+ },
+ destroyed() {
+ window.removeEventListener('beforeunload', this.onBeforeUnload);
+ },
methods: {
- updateFileName(newName) {
- this.fileName = newName;
+ onBeforeUnload(e = {}) {
+ const returnValue = __('Are you sure you want to lose unsaved changes?');
+
+ if (!this.allBlobChangesRegistered) return undefined;
+
+ Object.assign(e, { returnValue });
+ return returnValue;
+ },
+ updateBlobActions(args = {}) {
+ // `_constants` is the internal prop that
+ // should not be sent to the mutation. Hence we filter it out from
+ // the argsToUpdateAction that is the data-basis for the mutation.
+ const { _constants: blobConstants, ...argsToUpdateAction } = args;
+ const { previousPath, filePath, content } = argsToUpdateAction;
+ let actionEntry = this.blobsActions[blobConstants.id] || {};
+ let tunedActions = {
+ action: '',
+ previousPath,
+ };
+
+ if (this.newSnippet) {
+ // new snippet, hence new blob
+ tunedActions = {
+ action: SNIPPET_BLOB_ACTION_CREATE,
+ previousPath: '',
+ };
+ } else if (previousPath && filePath) {
+ // renaming of a blob + renaming & content update
+ const renamedToOriginal = filePath === blobConstants.originalPath;
+ tunedActions = {
+ action: renamedToOriginal ? SNIPPET_BLOB_ACTION_UPDATE : SNIPPET_BLOB_ACTION_MOVE,
+ previousPath: !renamedToOriginal ? blobConstants.originalPath : '',
+ };
+ } else if (content !== blobConstants.originalContent) {
+ // content update only
+ tunedActions = {
+ action: SNIPPET_BLOB_ACTION_UPDATE,
+ previousPath: '',
+ };
+ }
+
+ actionEntry = { ...actionEntry, ...argsToUpdateAction, ...tunedActions };
+
+ this.$set(this.blobsActions, blobConstants.id, actionEntry);
},
flashAPIFailure(err) {
const defaultErrorMsg = this.newSnippet
@@ -111,24 +168,9 @@ export default {
onNewSnippetFetched() {
this.newSnippet = true;
this.snippet = this.$options.newSnippetSchema;
- this.blob = this.snippet.blob;
- this.isContentLoading = false;
},
onExistingSnippetFetched() {
this.newSnippet = false;
- const { blob } = this.snippet;
- this.blob = blob;
- this.fileName = blob.name;
- const baseUrl = getBaseURL();
- const url = joinPaths(baseUrl, blob.rawPath);
-
- axios
- .get(url)
- .then(res => {
- this.content = res.data;
- this.isContentLoading = false;
- })
- .catch(e => this.flashAPIFailure(e));
},
onSnippetFetch(snippetRes) {
if (snippetRes.data.snippets.edges.length === 0) {
@@ -172,6 +214,7 @@ export default {
if (errors.length) {
this.flashAPIFailure(errors[0]);
} else {
+ this.originalContent = this.content;
redirectTo(baseObj.snippet.webUrl);
}
})
@@ -184,7 +227,6 @@ export default {
title: '',
description: '',
visibilityLevel: SNIPPET_VISIBILITY_PRIVATE,
- blob: {},
},
};
</script>
@@ -215,12 +257,16 @@ export default {
:markdown-preview-path="markdownPreviewPath"
:markdown-docs-path="markdownDocsPath"
/>
- <snippet-blob-edit
- v-model="content"
- :file-name="fileName"
- :is-loading="isContentLoading"
- @name-change="updateFileName"
- />
+ <template v-if="blobs.length">
+ <snippet-blob-edit
+ v-for="blob in blobs"
+ :key="blob.name"
+ :blob="blob"
+ @blob-updated="updateBlobActions"
+ />
+ </template>
+ <snippet-blob-edit v-else @blob-updated="updateBlobActions" />
+
<snippet-visibility-edit
v-model="snippet.visibilityLevel"
:help-link="visibilityHelpLink"
diff --git a/app/assets/javascripts/snippets/components/show.vue b/app/assets/javascripts/snippets/components/show.vue
index bc0034d397e..0779e87e6b6 100644
--- a/app/assets/javascripts/snippets/components/show.vue
+++ b/app/assets/javascripts/snippets/components/show.vue
@@ -1,19 +1,27 @@
<script>
+import BlobEmbeddable from '~/blob/components/blob_embeddable.vue';
import SnippetHeader from './snippet_header.vue';
import SnippetTitle from './snippet_title.vue';
import SnippetBlob from './snippet_blob_view.vue';
import { GlLoadingIcon } from '@gitlab/ui';
import { getSnippetMixin } from '../mixins/snippets';
+import { SNIPPET_VISIBILITY_PUBLIC } from '~/snippets/constants';
export default {
components: {
+ BlobEmbeddable,
SnippetHeader,
SnippetTitle,
GlLoadingIcon,
SnippetBlob,
},
mixins: [getSnippetMixin],
+ computed: {
+ embeddable() {
+ return this.snippet.visibilityLevel === SNIPPET_VISIBILITY_PUBLIC;
+ },
+ },
};
</script>
<template>
@@ -27,7 +35,10 @@ export default {
<template v-else>
<snippet-header :snippet="snippet" />
<snippet-title :snippet="snippet" />
- <snippet-blob :snippet="snippet" />
+ <blob-embeddable v-if="embeddable" class="gl-mb-5" :url="snippet.webUrl" />
+ <div v-for="blob in blobs" :key="blob.path">
+ <snippet-blob :snippet="snippet" :blob="blob" />
+ </div>
</template>
</div>
</template>
diff --git a/app/assets/javascripts/snippets/components/snippet_blob_edit.vue b/app/assets/javascripts/snippets/components/snippet_blob_edit.vue
index 62c29b0c7cd..3c2dbfff6e1 100644
--- a/app/assets/javascripts/snippets/components/snippet_blob_edit.vue
+++ b/app/assets/javascripts/snippets/components/snippet_blob_edit.vue
@@ -2,6 +2,17 @@
import BlobHeaderEdit from '~/blob/components/blob_edit_header.vue';
import BlobContentEdit from '~/blob/components/blob_edit_content.vue';
import { GlLoadingIcon } from '@gitlab/ui';
+import { getBaseURL, joinPaths } from '~/lib/utils/url_utility';
+import axios from '~/lib/utils/axios_utils';
+import { SNIPPET_BLOB_CONTENT_FETCH_ERROR } from '~/snippets/constants';
+import Flash from '~/flash';
+import { sprintf } from '~/locale';
+
+function localId() {
+ return Math.floor((1 + Math.random()) * 0x10000)
+ .toString(16)
+ .substring(1);
+}
export default {
components: {
@@ -11,20 +22,70 @@ export default {
},
inheritAttrs: false,
props: {
- value: {
- type: String,
+ blob: {
+ type: Object,
required: false,
- default: '',
+ default: null,
+ validator: ({ rawPath }) => Boolean(rawPath),
},
- fileName: {
- type: String,
- required: false,
- default: '',
+ },
+ data() {
+ return {
+ id: localId(),
+ filePath: this.blob?.path || '',
+ previousPath: '',
+ originalPath: this.blob?.path || '',
+ content: this.blob?.content || '',
+ originalContent: '',
+ isContentLoading: this.blob,
+ };
+ },
+ watch: {
+ filePath(filePath, previousPath) {
+ this.previousPath = previousPath;
+ this.notifyAboutUpdates({ previousPath });
},
- isLoading: {
- type: Boolean,
- required: false,
- default: true,
+ content() {
+ this.notifyAboutUpdates();
+ },
+ },
+ mounted() {
+ if (this.blob) {
+ this.fetchBlobContent();
+ }
+ },
+ methods: {
+ notifyAboutUpdates(args = {}) {
+ const { filePath, previousPath } = args;
+ this.$emit('blob-updated', {
+ filePath: filePath || this.filePath,
+ previousPath: previousPath || this.previousPath,
+ content: this.content,
+ _constants: {
+ originalPath: this.originalPath,
+ originalContent: this.originalContent,
+ id: this.id,
+ },
+ });
+ },
+ fetchBlobContent() {
+ const baseUrl = getBaseURL();
+ const url = joinPaths(baseUrl, this.blob.rawPath);
+
+ axios
+ .get(url)
+ .then(res => {
+ this.originalContent = res.data;
+ this.content = res.data;
+ })
+ .catch(e => this.flashAPIFailure(e))
+ .finally(() => {
+ this.isContentLoading = false;
+ });
+ },
+ flashAPIFailure(err) {
+ Flash(sprintf(SNIPPET_BLOB_CONTENT_FETCH_ERROR, { err }));
+ this.isContentLoading = false;
},
},
};
@@ -33,23 +94,14 @@ export default {
<div class="form-group file-editor">
<label>{{ s__('Snippets|File') }}</label>
<div class="file-holder snippet">
- <blob-header-edit
- :value="fileName"
- data-qa-selector="file_name_field"
- @input="$emit('name-change', $event)"
- />
+ <blob-header-edit v-model="filePath" data-qa-selector="file_name_field" />
<gl-loading-icon
- v-if="isLoading"
+ v-if="isContentLoading"
:label="__('Loading snippet')"
size="lg"
class="loading-animation prepend-top-20 append-bottom-20"
/>
- <blob-content-edit
- v-else
- :value="value"
- :file-name="fileName"
- @input="$emit('input', $event)"
- />
+ <blob-content-edit v-else v-model="content" :file-name="filePath" />
</div>
</div>
</template>
diff --git a/app/assets/javascripts/snippets/components/snippet_blob_view.vue b/app/assets/javascripts/snippets/components/snippet_blob_view.vue
index 7472aff3318..afd038eef58 100644
--- a/app/assets/javascripts/snippets/components/snippet_blob_view.vue
+++ b/app/assets/javascripts/snippets/components/snippet_blob_view.vue
@@ -1,6 +1,4 @@
<script>
-import BlobEmbeddable from '~/blob/components/blob_embeddable.vue';
-import { SNIPPET_VISIBILITY_PUBLIC } from '../constants';
import BlobHeader from '~/blob/components/blob_header.vue';
import BlobContent from '~/blob/components/blob_content.vue';
import CloneDropdownButton from '~/vue_shared/components/clone_dropdown.vue';
@@ -16,7 +14,6 @@ import {
export default {
components: {
- BlobEmbeddable,
BlobHeader,
BlobContent,
CloneDropdownButton,
@@ -49,21 +46,19 @@ export default {
type: Object,
required: true,
},
+ blob: {
+ type: Object,
+ required: true,
+ },
},
data() {
return {
- blob: this.snippet.blob,
blobContent: '',
activeViewerType:
- this.snippet.blob?.richViewer && !window.location.hash
- ? RICH_BLOB_VIEWER
- : SIMPLE_BLOB_VIEWER,
+ this.blob?.richViewer && !window.location.hash ? RICH_BLOB_VIEWER : SIMPLE_BLOB_VIEWER,
};
},
computed: {
- embeddable() {
- return this.snippet.visibilityLevel === SNIPPET_VISIBILITY_PUBLIC;
- },
isContentLoading() {
return this.$apollo.queries.blobContent.loading;
},
@@ -92,33 +87,30 @@ export default {
};
</script>
<template>
- <div>
- <blob-embeddable v-if="embeddable" class="mb-3" :url="snippet.webUrl" />
- <article class="file-holder snippet-file-content">
- <blob-header
- :blob="blob"
- :active-viewer-type="viewer.type"
- :has-render-error="hasRenderError"
- @viewer-changed="switchViewer"
- >
- <template #actions>
- <clone-dropdown-button
- v-if="canBeCloned"
- class="mr-2"
- :ssh-link="snippet.sshUrlToRepo"
- :http-link="snippet.httpUrlToRepo"
- data-qa-selector="clone_button"
- />
- </template>
- </blob-header>
- <blob-content
- :loading="isContentLoading"
- :content="blobContent"
- :active-viewer="viewer"
- :blob="blob"
- @[$options.BLOB_RENDER_EVENT_LOAD]="forceQuery"
- @[$options.BLOB_RENDER_EVENT_SHOW_SOURCE]="switchViewer"
- />
- </article>
- </div>
+ <article class="file-holder snippet-file-content">
+ <blob-header
+ :blob="blob"
+ :active-viewer-type="viewer.type"
+ :has-render-error="hasRenderError"
+ @viewer-changed="switchViewer"
+ >
+ <template #actions>
+ <clone-dropdown-button
+ v-if="canBeCloned"
+ class="gl-mr-3"
+ :ssh-link="snippet.sshUrlToRepo"
+ :http-link="snippet.httpUrlToRepo"
+ data-qa-selector="clone_button"
+ />
+ </template>
+ </blob-header>
+ <blob-content
+ :loading="isContentLoading"
+ :content="blobContent"
+ :active-viewer="viewer"
+ :blob="blob"
+ @[$options.BLOB_RENDER_EVENT_LOAD]="forceQuery"
+ @[$options.BLOB_RENDER_EVENT_SHOW_SOURCE]="switchViewer"
+ />
+ </article>
</template>
diff --git a/app/assets/javascripts/snippets/components/snippet_header.vue b/app/assets/javascripts/snippets/components/snippet_header.vue
index 2a06296cb15..707e2b0ea30 100644
--- a/app/assets/javascripts/snippets/components/snippet_header.vue
+++ b/app/assets/javascripts/snippets/components/snippet_header.vue
@@ -65,14 +65,17 @@ export default {
};
},
computed: {
+ snippetHasBinary() {
+ return Boolean(this.snippet.blobs.find(blob => blob.binary));
+ },
personalSnippetActions() {
return [
{
condition: this.snippet.userPermissions.updateSnippet,
text: __('Edit'),
href: this.editLink,
- disabled: this.snippet.blob.binary,
- title: this.snippet.blob.binary
+ disabled: this.snippetHasBinary,
+ title: this.snippetHasBinary
? __('Snippets with non-text files can only be edited via Git.')
: undefined,
},
@@ -163,7 +166,7 @@ export default {
<div class="detail-page-header">
<div class="detail-page-header-body">
<div
- class="snippet-box has-tooltip d-flex align-items-center append-right-5 mb-1"
+ class="snippet-box has-tooltip d-flex align-items-center gl-mr-2 mb-1"
data-qa-selector="snippet_container"
:title="snippetVisibilityLevelDescription"
data-container="body"
diff --git a/app/assets/javascripts/snippets/constants.js b/app/assets/javascripts/snippets/constants.js
index b3abc73557c..99ee698408d 100644
--- a/app/assets/javascripts/snippets/constants.js
+++ b/app/assets/javascripts/snippets/constants.js
@@ -25,3 +25,8 @@ export const SNIPPET_VISIBILITY = {
export const SNIPPET_CREATE_MUTATION_ERROR = __("Can't create snippet: %{err}");
export const SNIPPET_UPDATE_MUTATION_ERROR = __("Can't update snippet: %{err}");
+export const SNIPPET_BLOB_CONTENT_FETCH_ERROR = __("Can't fetch content for the blob: %{err}");
+
+export const SNIPPET_BLOB_ACTION_CREATE = 'create';
+export const SNIPPET_BLOB_ACTION_UPDATE = 'update';
+export const SNIPPET_BLOB_ACTION_MOVE = 'move';
diff --git a/app/assets/javascripts/snippets/fragments/project.fragment.graphql b/app/assets/javascripts/snippets/fragments/project.fragment.graphql
index 7d65789c67b..64bb2315c1b 100644
--- a/app/assets/javascripts/snippets/fragments/project.fragment.graphql
+++ b/app/assets/javascripts/snippets/fragments/project.fragment.graphql
@@ -1,6 +1,6 @@
-fragment Project on Snippet {
+fragment SnippetProject on Snippet {
project {
fullPath
webUrl
}
-} \ No newline at end of file
+}
diff --git a/app/assets/javascripts/snippets/fragments/snippetBase.fragment.graphql b/app/assets/javascripts/snippets/fragments/snippetBase.fragment.graphql
index e7765dfd8ba..2cca71708ca 100644
--- a/app/assets/javascripts/snippets/fragments/snippetBase.fragment.graphql
+++ b/app/assets/javascripts/snippets/fragments/snippetBase.fragment.graphql
@@ -11,7 +11,7 @@ fragment SnippetBase on Snippet {
webUrl
httpUrlToRepo
sshUrlToRepo
- blob {
+ blobs {
binary
name
path
diff --git a/app/assets/javascripts/snippets/mixins/snippets.js b/app/assets/javascripts/snippets/mixins/snippets.js
index 837c41cdf6b..91331cdf339 100644
--- a/app/assets/javascripts/snippets/mixins/snippets.js
+++ b/app/assets/javascripts/snippets/mixins/snippets.js
@@ -1,5 +1,7 @@
import GetSnippetQuery from '../queries/snippet.query.graphql';
+const blobsDefault = [];
+
export const getSnippetMixin = {
apollo: {
snippet: {
@@ -11,6 +13,7 @@ export const getSnippetMixin = {
},
update: data => data.snippets.edges[0]?.node,
result(res) {
+ this.blobs = res.data.snippets.edges[0]?.node?.blobs || blobsDefault;
if (this.onSnippetFetch) {
this.onSnippetFetch(res);
}
@@ -27,6 +30,7 @@ export const getSnippetMixin = {
return {
snippet: {},
newSnippet: false,
+ blobs: blobsDefault,
};
},
computed: {
diff --git a/app/assets/javascripts/snippets/mutations/deleteSnippet.mutation.graphql b/app/assets/javascripts/snippets/mutations/deleteSnippet.mutation.graphql
index 0c829cbdee6..f43d53661f4 100644
--- a/app/assets/javascripts/snippets/mutations/deleteSnippet.mutation.graphql
+++ b/app/assets/javascripts/snippets/mutations/deleteSnippet.mutation.graphql
@@ -1,5 +1,5 @@
mutation DeleteSnippet($id: ID!) {
- destroySnippet(input: {id: $id}) {
+ destroySnippet(input: { id: $id }) {
errors
}
-} \ No newline at end of file
+}
diff --git a/app/assets/javascripts/snippets/queries/projectPermissions.query.graphql b/app/assets/javascripts/snippets/queries/projectPermissions.query.graphql
index 288bd0889bf..03c81460fb5 100644
--- a/app/assets/javascripts/snippets/queries/projectPermissions.query.graphql
+++ b/app/assets/javascripts/snippets/queries/projectPermissions.query.graphql
@@ -4,4 +4,4 @@ query CanCreateProjectSnippet($fullPath: ID!) {
createSnippet
}
}
-} \ No newline at end of file
+}
diff --git a/app/assets/javascripts/snippets/queries/snippet.query.graphql b/app/assets/javascripts/snippets/queries/snippet.query.graphql
index c58a5168ba3..b23ab862439 100644
--- a/app/assets/javascripts/snippets/queries/snippet.query.graphql
+++ b/app/assets/javascripts/snippets/queries/snippet.query.graphql
@@ -7,7 +7,7 @@ query GetSnippetQuery($ids: [ID!]) {
edges {
node {
...SnippetBase
- ...Project
+ ...SnippetProject
author {
...Author
}
diff --git a/app/assets/javascripts/snippets/queries/userPermissions.query.graphql b/app/assets/javascripts/snippets/queries/userPermissions.query.graphql
index f5b97b3d0f0..c3e5519e266 100644
--- a/app/assets/javascripts/snippets/queries/userPermissions.query.graphql
+++ b/app/assets/javascripts/snippets/queries/userPermissions.query.graphql
@@ -4,4 +4,4 @@ query CanCreatePersonalSnippet {
createSnippet
}
}
-} \ No newline at end of file
+}
diff --git a/app/assets/javascripts/static_site_editor/components/edit_area.vue b/app/assets/javascripts/static_site_editor/components/edit_area.vue
index e9efef40632..84a16f327d9 100644
--- a/app/assets/javascripts/static_site_editor/components/edit_area.vue
+++ b/app/assets/javascripts/static_site_editor/components/edit_area.vue
@@ -5,6 +5,8 @@ import EditHeader from './edit_header.vue';
import UnsavedChangesConfirmDialog from './unsaved_changes_confirm_dialog.vue';
import parseSourceFile from '~/static_site_editor/services/parse_source_file';
import { EDITOR_TYPES } from '~/vue_shared/components/rich_content_editor/constants';
+import { DEFAULT_IMAGE_UPLOAD_PATH } from '../constants';
+import imageRepository from '../image_repository';
export default {
components: {
@@ -31,46 +33,47 @@ export default {
required: false,
default: '',
},
+ imageRoot: {
+ type: String,
+ required: false,
+ default: DEFAULT_IMAGE_UPLOAD_PATH,
+ validator: prop => prop.endsWith('/'),
+ },
},
data() {
return {
saveable: false,
parsedSource: parseSourceFile(this.content),
editorMode: EDITOR_TYPES.wysiwyg,
+ isModified: false,
};
},
+ imageRepository: imageRepository(),
computed: {
editableContent() {
- return this.parsedSource.editable;
- },
- editableKey() {
- return this.isWysiwygMode ? 'body' : 'raw';
+ return this.parsedSource.content(this.isWysiwygMode);
},
isWysiwygMode() {
return this.editorMode === EDITOR_TYPES.wysiwyg;
},
- modified() {
- return this.isWysiwygMode
- ? this.parsedSource.isModifiedBody()
- : this.parsedSource.isModifiedRaw();
- },
},
methods: {
- syncSource() {
- if (this.isWysiwygMode) {
- this.parsedSource.syncBody();
- return;
- }
-
- this.parsedSource.syncRaw();
+ onInputChange(newVal) {
+ this.parsedSource.sync(newVal, this.isWysiwygMode);
+ this.isModified = this.parsedSource.isModified();
},
onModeChange(mode) {
this.editorMode = mode;
- this.syncSource();
+ this.$refs.editor.resetInitialValue(this.editableContent);
+ },
+ onUploadImage({ file, imageUrl }) {
+ this.$options.imageRepository.add(file, imageUrl);
},
onSubmit() {
- this.syncSource();
- this.$emit('submit', { content: this.editableContent.raw });
+ this.$emit('submit', {
+ content: this.parsedSource.content(),
+ images: this.$options.imageRepository.getAll(),
+ });
},
},
};
@@ -79,16 +82,20 @@ export default {
<div class="d-flex flex-grow-1 flex-column h-100">
<edit-header class="py-2" :title="title" />
<rich-content-editor
- v-model="editableContent[editableKey]"
+ ref="editor"
+ :content="editableContent"
:initial-edit-type="editorMode"
+ :image-root="imageRoot"
class="mb-9 h-100"
@modeChange="onModeChange"
+ @input="onInputChange"
+ @uploadImage="onUploadImage"
/>
- <unsaved-changes-confirm-dialog :modified="modified" />
+ <unsaved-changes-confirm-dialog :modified="isModified" />
<publish-toolbar
class="gl-fixed gl-left-0 gl-bottom-0 gl-w-full"
:return-url="returnUrl"
- :saveable="modified"
+ :saveable="isModified"
:saving-changes="savingChanges"
@submit="onSubmit"
/>
diff --git a/app/assets/javascripts/static_site_editor/constants.js b/app/assets/javascripts/static_site_editor/constants.js
index 947347922f2..49db9ab7ca5 100644
--- a/app/assets/javascripts/static_site_editor/constants.js
+++ b/app/assets/javascripts/static_site_editor/constants.js
@@ -19,3 +19,5 @@ export const DEFAULT_HEADING = s__('StaticSiteEditor|Static site editor');
export const TRACKING_ACTION_CREATE_COMMIT = 'create_commit';
export const TRACKING_ACTION_CREATE_MERGE_REQUEST = 'create_merge_request';
export const TRACKING_ACTION_INITIALIZE_EDITOR = 'initialize_editor';
+
+export const DEFAULT_IMAGE_UPLOAD_PATH = 'source/images/uploads/';
diff --git a/app/assets/javascripts/static_site_editor/graphql/mutations/submit_content_changes.mutation.graphql b/app/assets/javascripts/static_site_editor/graphql/mutations/submit_content_changes.mutation.graphql
index 2840d419966..cd130aa7dbb 100644
--- a/app/assets/javascripts/static_site_editor/graphql/mutations/submit_content_changes.mutation.graphql
+++ b/app/assets/javascripts/static_site_editor/graphql/mutations/submit_content_changes.mutation.graphql
@@ -1,5 +1,5 @@
mutation submitContentChanges($input: SubmitContentChangesInput) {
- submitContentChanges(input: $input) @client {
+ submitContentChanges(input: $input) @client {
branch
commit
mergeRequest
diff --git a/app/assets/javascripts/static_site_editor/graphql/queries/app_data.query.graphql b/app/assets/javascripts/static_site_editor/graphql/queries/app_data.query.graphql
index fdbf4459aee..946d80efff0 100644
--- a/app/assets/javascripts/static_site_editor/graphql/queries/app_data.query.graphql
+++ b/app/assets/javascripts/static_site_editor/graphql/queries/app_data.query.graphql
@@ -3,7 +3,7 @@ query appData {
isSupportedContent
project
sourcePath
- username,
+ username
returnUrl
}
}
diff --git a/app/assets/javascripts/static_site_editor/graphql/queries/source_content.query.graphql b/app/assets/javascripts/static_site_editor/graphql/queries/source_content.query.graphql
index e36d244ae57..cfe30c601ed 100644
--- a/app/assets/javascripts/static_site_editor/graphql/queries/source_content.query.graphql
+++ b/app/assets/javascripts/static_site_editor/graphql/queries/source_content.query.graphql
@@ -1,6 +1,6 @@
query sourceContent($project: ID!, $sourcePath: String!) {
project(fullPath: $project) {
- fullPath,
+ fullPath
file(path: $sourcePath) @client {
title
content
diff --git a/app/assets/javascripts/static_site_editor/graphql/resolvers/submit_content_changes.js b/app/assets/javascripts/static_site_editor/graphql/resolvers/submit_content_changes.js
index 6c4e3a4d973..0cb26f88785 100644
--- a/app/assets/javascripts/static_site_editor/graphql/resolvers/submit_content_changes.js
+++ b/app/assets/javascripts/static_site_editor/graphql/resolvers/submit_content_changes.js
@@ -3,10 +3,10 @@ import savedContentMetaQuery from '../queries/saved_content_meta.query.graphql';
const submitContentChangesResolver = (
_,
- { input: { project: projectId, username, sourcePath, content } },
+ { input: { project: projectId, username, sourcePath, content, images } },
{ cache },
) => {
- return submitContentChanges({ projectId, username, sourcePath, content }).then(
+ return submitContentChanges({ projectId, username, sourcePath, content, images }).then(
savedContentMeta => {
cache.writeQuery({
query: savedContentMetaQuery,
diff --git a/app/assets/javascripts/static_site_editor/graphql/typedefs.graphql b/app/assets/javascripts/static_site_editor/graphql/typedefs.graphql
index 59da2e27144..78cc1746cdb 100644
--- a/app/assets/javascripts/static_site_editor/graphql/typedefs.graphql
+++ b/app/assets/javascripts/static_site_editor/graphql/typedefs.graphql
@@ -22,7 +22,7 @@ type AppData {
username: String!
}
-type SubmitContentChangesInput {
+input SubmitContentChangesInput {
project: String!
sourcePath: String!
content: String!
diff --git a/app/assets/javascripts/static_site_editor/image_repository.js b/app/assets/javascripts/static_site_editor/image_repository.js
new file mode 100644
index 00000000000..541d581bda8
--- /dev/null
+++ b/app/assets/javascripts/static_site_editor/image_repository.js
@@ -0,0 +1,20 @@
+import { __ } from '~/locale';
+import Flash from '~/flash';
+import { getBinary } from './services/image_service';
+
+const imageRepository = () => {
+ const images = new Map();
+ const flash = message => new Flash(message);
+
+ const add = (file, url) => {
+ getBinary(file)
+ .then(content => images.set(url, content))
+ .catch(() => flash(__('Something went wrong while inserting your image. Please try again.')));
+ };
+
+ const getAll = () => images;
+
+ return { add, getAll };
+};
+
+export default imageRepository;
diff --git a/app/assets/javascripts/static_site_editor/pages/home.vue b/app/assets/javascripts/static_site_editor/pages/home.vue
index a1314c8a478..156b815e07a 100644
--- a/app/assets/javascripts/static_site_editor/pages/home.vue
+++ b/app/assets/javascripts/static_site_editor/pages/home.vue
@@ -67,11 +67,11 @@ export default {
onDismissError() {
this.submitChangesError = null;
},
- onSubmit({ content }) {
+ onSubmit({ content, images }) {
this.content = content;
- this.submitChanges();
+ this.submitChanges(images);
},
- submitChanges() {
+ submitChanges(images) {
this.isSavingChanges = true;
this.$apollo
@@ -83,6 +83,7 @@ export default {
username: this.appData.username,
sourcePath: this.appData.sourcePath,
content: this.content,
+ images,
},
},
})
diff --git a/app/assets/javascripts/static_site_editor/services/image_service.js b/app/assets/javascripts/static_site_editor/services/image_service.js
new file mode 100644
index 00000000000..edc69d0579a
--- /dev/null
+++ b/app/assets/javascripts/static_site_editor/services/image_service.js
@@ -0,0 +1,9 @@
+// eslint-disable-next-line import/prefer-default-export
+export const getBinary = file => {
+ return new Promise((resolve, reject) => {
+ const reader = new FileReader();
+ reader.readAsDataURL(file);
+ reader.onload = () => resolve(reader.result.split(',')[1]);
+ reader.onerror = error => reject(error);
+ });
+};
diff --git a/app/assets/javascripts/static_site_editor/services/parse_source_file.js b/app/assets/javascripts/static_site_editor/services/parse_source_file.js
index f32c693411f..126dfe81b90 100644
--- a/app/assets/javascripts/static_site_editor/services/parse_source_file.js
+++ b/app/assets/javascripts/static_site_editor/services/parse_source_file.js
@@ -22,33 +22,43 @@ const parseSourceFile = raw => {
return buildPayload(source, '', '', source);
};
- const computedRaw = () => `${editable.header}${editable.spacing}${editable.body}`;
-
- const syncBody = () => {
+ const syncEditable = () => {
/*
We re-parse as markdown editing could have added non-body changes (preFrontMatter, frontMatter, or spacing).
- Re-parsing additionally gets us the desired body that was extracted from the mutated editable.raw
- Additionally we intentionally mutate the existing editable's key values as opposed to reassigning the object itself so consumers of the potentially reactive property stay in sync.
+ Re-parsing additionally gets us the desired body that was extracted from the potentially mutated editable.raw
*/
- Object.assign(editable, parse(editable.raw));
+ editable = parse(editable.raw);
+ };
+
+ const syncBodyToRaw = () => {
+ editable.raw = `${editable.header}${editable.spacing}${editable.body}`;
+ };
+
+ const sync = (newVal, isBodyToRaw) => {
+ const editableKey = isBodyToRaw ? 'body' : 'raw';
+ editable[editableKey] = newVal;
+
+ if (isBodyToRaw) {
+ syncBodyToRaw();
+ }
+
+ syncEditable();
};
- const syncRaw = () => {
- editable.raw = computedRaw();
+ const content = (isBody = false) => {
+ const editableKey = isBody ? 'body' : 'raw';
+ return editable[editableKey];
};
- const isModifiedRaw = () => initial.raw !== editable.raw;
- const isModifiedBody = () => initial.raw !== computedRaw();
+ const isModified = () => initial.raw !== editable.raw;
initial = parse(raw);
editable = parse(raw);
return {
- editable,
- isModifiedRaw,
- isModifiedBody,
- syncRaw,
- syncBody,
+ content,
+ isModified,
+ sync,
};
};
diff --git a/app/assets/javascripts/static_site_editor/services/submit_content_changes.js b/app/assets/javascripts/static_site_editor/services/submit_content_changes.js
index fce7c1f918f..da62d3fa4fc 100644
--- a/app/assets/javascripts/static_site_editor/services/submit_content_changes.js
+++ b/app/assets/javascripts/static_site_editor/services/submit_content_changes.js
@@ -21,7 +21,32 @@ const createBranch = (projectId, branch) =>
throw new Error(SUBMIT_CHANGES_BRANCH_ERROR);
});
-const commitContent = (projectId, message, branch, sourcePath, content) => {
+const createImageActions = (images, markdown) => {
+ const actions = [];
+
+ if (!markdown) {
+ return actions;
+ }
+
+ images.forEach((imageContent, filePath) => {
+ const imageExistsInMarkdown = path => new RegExp(`!\\[([^[\\]\\n]*)\\](\\(${path})\\)`); // matches the image markdown syntax: ![<any-string-except-newline>](<path>)
+
+ if (imageExistsInMarkdown(filePath).test(markdown)) {
+ actions.push(
+ convertObjectPropsToSnakeCase({
+ encoding: 'base64',
+ action: 'create',
+ content: imageContent,
+ filePath,
+ }),
+ );
+ }
+ });
+
+ return actions;
+};
+
+const commitContent = (projectId, message, branch, sourcePath, content, images) => {
Tracking.event(document.body.dataset.page, TRACKING_ACTION_CREATE_COMMIT);
return Api.commitMultiple(
@@ -35,6 +60,7 @@ const commitContent = (projectId, message, branch, sourcePath, content) => {
filePath: sourcePath,
content,
}),
+ ...createImageActions(images, content),
],
}),
).catch(() => {
@@ -62,7 +88,7 @@ const createMergeRequest = (
});
};
-const submitContentChanges = ({ username, projectId, sourcePath, content }) => {
+const submitContentChanges = ({ username, projectId, sourcePath, content, images }) => {
const branch = generateBranchName(username);
const mergeRequestTitle = sprintf(s__(`StaticSiteEditor|Update %{sourcePath} file`), {
sourcePath,
@@ -73,7 +99,7 @@ const submitContentChanges = ({ username, projectId, sourcePath, content }) => {
.then(({ data: { web_url: url } }) => {
Object.assign(meta, { branch: { label: branch, url } });
- return commitContent(projectId, mergeRequestTitle, branch, sourcePath, content);
+ return commitContent(projectId, mergeRequestTitle, branch, sourcePath, content, images);
})
.then(({ data: { short_id: label, web_url: url } }) => {
Object.assign(meta, { commit: { label, url } });
diff --git a/app/assets/javascripts/user_popovers.js b/app/assets/javascripts/user_popovers.js
index bde00d72620..290de55e6f9 100644
--- a/app/assets/javascripts/user_popovers.js
+++ b/app/assets/javascripts/user_popovers.js
@@ -1,5 +1,7 @@
import Vue from 'vue';
+import sanitize from 'sanitize-html';
+
import UsersCache from './lib/utils/users_cache';
import UserPopover from './vue_shared/components/user_popover/user_popover.vue';
@@ -38,6 +40,7 @@ const populateUserInfo = user => {
name: userData.name,
location: userData.location,
bio: userData.bio,
+ bioHtml: sanitize(userData.bio_html),
workInformation: userData.work_information,
loaded: true,
});
diff --git a/app/assets/javascripts/users_select/index.js b/app/assets/javascripts/users_select/index.js
index 2dbe5a8171e..f72de8c2f4d 100644
--- a/app/assets/javascripts/users_select/index.js
+++ b/app/assets/javascripts/users_select/index.js
@@ -21,8 +21,8 @@ function UsersSelect(currentUser, els, options = {}) {
const $els = $(els || '.js-user-search');
this.users = this.users.bind(this);
this.user = this.user.bind(this);
- this.usersPath = '/autocomplete/users.json';
- this.userPath = '/autocomplete/users/:id.json';
+ this.usersPath = '/-/autocomplete/users.json';
+ this.userPath = '/-/autocomplete/users/:id.json';
if (currentUser != null) {
if (typeof currentUser === 'object') {
this.currentUser = currentUser;
@@ -263,7 +263,7 @@ function UsersSelect(currentUser, els, options = {}) {
const userId = parseInt(input.value, 10);
const { avatarUrl, avatar_url, name, username, canMerge } = input.dataset;
return {
- avatar_url: avatarUrl || avatar_url,
+ avatar_url: avatarUrl || avatar_url || gon.default_avatar_url,
id: userId,
name,
username,
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/approvals/approvals.vue b/app/assets/javascripts/vue_merge_request_widget/components/approvals/approvals.vue
new file mode 100644
index 00000000000..0f9d1b8395b
--- /dev/null
+++ b/app/assets/javascripts/vue_merge_request_widget/components/approvals/approvals.vue
@@ -0,0 +1,212 @@
+<script>
+import { GlButton } from '@gitlab/ui';
+import createFlash from '~/flash';
+import { s__ } from '~/locale';
+import eventHub from '../../event_hub';
+import approvalsMixin from '../../mixins/approvals';
+import MrWidgetContainer from '../mr_widget_container.vue';
+import MrWidgetIcon from '../mr_widget_icon.vue';
+import ApprovalsSummary from './approvals_summary.vue';
+import ApprovalsSummaryOptional from './approvals_summary_optional.vue';
+import { FETCH_LOADING, FETCH_ERROR, APPROVE_ERROR, UNAPPROVE_ERROR } from './messages';
+
+export default {
+ name: 'MRWidgetApprovals',
+ components: {
+ MrWidgetContainer,
+ MrWidgetIcon,
+ ApprovalsSummary,
+ ApprovalsSummaryOptional,
+ GlButton,
+ },
+ mixins: [approvalsMixin],
+ props: {
+ mr: {
+ type: Object,
+ required: true,
+ },
+ service: {
+ type: Object,
+ required: true,
+ },
+ isOptionalDefault: {
+ type: Boolean,
+ required: false,
+ default: null,
+ },
+ approveDefault: {
+ type: Function,
+ required: false,
+ default: null,
+ },
+ modalId: {
+ type: String,
+ required: false,
+ default: null,
+ },
+ requirePasswordToApprove: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ },
+ data() {
+ return {
+ fetchingApprovals: true,
+ hasApprovalAuthError: false,
+ isApproving: false,
+ };
+ },
+ computed: {
+ isBasic() {
+ return this.mr.approvalsWidgetType === 'base';
+ },
+ isApproved() {
+ return Boolean(this.approvals.approved);
+ },
+ isOptional() {
+ return this.isOptionalDefault !== null ? this.isOptionalDefault : !this.approvedBy.length;
+ },
+ hasAction() {
+ return Boolean(this.action);
+ },
+ approvals() {
+ return this.mr.approvals || {};
+ },
+ approvedBy() {
+ return this.approvals.approved_by ? this.approvals.approved_by.map(x => x.user) : [];
+ },
+ userHasApproved() {
+ return Boolean(this.approvals.user_has_approved);
+ },
+ userCanApprove() {
+ return Boolean(this.approvals.user_can_approve);
+ },
+ showApprove() {
+ return !this.userHasApproved && this.userCanApprove && this.mr.isOpen;
+ },
+ showUnapprove() {
+ return this.userHasApproved && !this.userCanApprove && this.mr.state !== 'merged';
+ },
+ approvalText() {
+ return this.isApproved && this.approvedBy.length > 0
+ ? s__('mrWidget|Approve additionally')
+ : s__('mrWidget|Approve');
+ },
+ action() {
+ // Use the default approve action, only if we aren't using the auth component for it
+ if (this.showApprove) {
+ return {
+ text: this.approvalText,
+ category: this.isApproved ? 'secondary' : 'primary',
+ variant: 'info',
+ action: () => this.approve(),
+ };
+ } else if (this.showUnapprove) {
+ return {
+ text: s__('mrWidget|Revoke approval'),
+ variant: 'warning',
+ category: 'secondary',
+ action: () => this.unapprove(),
+ };
+ }
+
+ return null;
+ },
+ },
+ created() {
+ this.refreshApprovals()
+ .then(() => {
+ this.fetchingApprovals = false;
+ })
+ .catch(() => createFlash(FETCH_ERROR));
+ },
+ methods: {
+ approve() {
+ if (this.requirePasswordToApprove) {
+ this.$root.$emit('bv::show::modal', this.modalId);
+ return;
+ }
+
+ this.updateApproval(
+ () => this.service.approveMergeRequest(),
+ () => createFlash(APPROVE_ERROR),
+ );
+ },
+ approveWithAuth(data) {
+ this.updateApproval(
+ () => this.service.approveMergeRequestWithAuth(data),
+ error => {
+ if (error && error.response && error.response.status === 401) {
+ this.hasApprovalAuthError = true;
+ return;
+ }
+ createFlash(APPROVE_ERROR);
+ },
+ );
+ },
+ unapprove() {
+ this.updateApproval(
+ () => this.service.unapproveMergeRequest(),
+ () => createFlash(UNAPPROVE_ERROR),
+ );
+ },
+ updateApproval(serviceFn, errFn) {
+ this.isApproving = true;
+ this.clearError();
+ return serviceFn()
+ .then(data => {
+ this.mr.setApprovals(data);
+ eventHub.$emit('MRWidgetUpdateRequested');
+ this.$emit('updated');
+ })
+ .catch(errFn)
+ .then(() => {
+ this.isApproving = false;
+ });
+ },
+ },
+ FETCH_LOADING,
+};
+</script>
+<template>
+ <mr-widget-container>
+ <div class="js-mr-approvals d-flex align-items-start align-items-md-center">
+ <mr-widget-icon name="approval" />
+ <div v-if="fetchingApprovals">{{ $options.FETCH_LOADING }}</div>
+ <template v-else>
+ <gl-button
+ v-if="action"
+ :variant="action.variant"
+ :category="action.category"
+ :loading="isApproving"
+ class="mr-3"
+ data-qa-selector="approve_button"
+ @click="action.action"
+ >
+ {{ action.text }}
+ </gl-button>
+ <approvals-summary-optional
+ v-if="isOptional"
+ :can-approve="hasAction"
+ :help-path="mr.approvalsHelpPath"
+ />
+ <approvals-summary
+ v-else
+ :approved="isApproved"
+ :approvals-left="approvals.approvals_left || 0"
+ :rules-left="approvals.approvalRuleNamesLeft"
+ :approvers="approvedBy"
+ />
+ <slot
+ :is-approving="isApproving"
+ :approve-with-auth="approveWithAuth"
+ :hasApproval-auth-error="hasApprovalAuthError"
+ ></slot>
+ </template>
+ </div>
+ <template #footer>
+ <slot name="footer"></slot>
+ </template>
+ </mr-widget-container>
+</template>
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/approvals/approvals_summary.vue b/app/assets/javascripts/vue_merge_request_widget/components/approvals/approvals_summary.vue
new file mode 100644
index 00000000000..fb342a5d340
--- /dev/null
+++ b/app/assets/javascripts/vue_merge_request_widget/components/approvals/approvals_summary.vue
@@ -0,0 +1,70 @@
+<script>
+import { n__, sprintf } from '~/locale';
+import { toNounSeriesText } from '~/lib/utils/grammar';
+import UserAvatarList from '~/vue_shared/components/user_avatar/user_avatar_list.vue';
+import { APPROVED_MESSAGE } from '~/vue_merge_request_widget/components/approvals/messages';
+
+export default {
+ components: {
+ UserAvatarList,
+ },
+ props: {
+ approved: {
+ type: Boolean,
+ required: true,
+ },
+ approvalsLeft: {
+ type: Number,
+ required: true,
+ },
+ rulesLeft: {
+ type: Array,
+ required: false,
+ default: () => [],
+ },
+ approvers: {
+ type: Array,
+ required: false,
+ default: () => [],
+ },
+ },
+ computed: {
+ message() {
+ if (this.approved) {
+ return APPROVED_MESSAGE;
+ }
+
+ if (!this.rulesLeft.length) {
+ return n__('Requires approval.', 'Requires %d more approvals.', this.approvalsLeft);
+ }
+
+ return sprintf(
+ n__(
+ 'Requires approval from %{names}.',
+ 'Requires %{count} more approvals from %{names}.',
+ this.approvalsLeft,
+ ),
+ {
+ names: toNounSeriesText(this.rulesLeft),
+ count: this.approvalsLeft,
+ },
+ false,
+ );
+ },
+ hasApprovers() {
+ return Boolean(this.approvers.length);
+ },
+ },
+ APPROVED_MESSAGE,
+};
+</script>
+
+<template>
+ <div data-qa-selector="approvals_summary_content">
+ <strong>{{ message }}</strong>
+ <template v-if="hasApprovers">
+ <span>{{ s__('mrWidget|Approved by') }}</span>
+ <user-avatar-list class="d-inline-block align-middle" :items="approvers" />
+ </template>
+ </div>
+</template>
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/approvals/approvals_summary_optional.vue b/app/assets/javascripts/vue_merge_request_widget/components/approvals/approvals_summary_optional.vue
new file mode 100644
index 00000000000..66af0c5a83e
--- /dev/null
+++ b/app/assets/javascripts/vue_merge_request_widget/components/approvals/approvals_summary_optional.vue
@@ -0,0 +1,50 @@
+<script>
+import { GlTooltipDirective, GlLink } from '@gitlab/ui';
+import Icon from '~/vue_shared/components/icon.vue';
+import {
+ OPTIONAL,
+ OPTIONAL_CAN_APPROVE,
+} from '~/vue_merge_request_widget/components/approvals/messages';
+
+export default {
+ components: {
+ GlLink,
+ Icon,
+ },
+ directives: {
+ GlTooltip: GlTooltipDirective,
+ },
+ props: {
+ canApprove: {
+ type: Boolean,
+ required: true,
+ },
+ helpPath: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ },
+ computed: {
+ message() {
+ return this.canApprove ? OPTIONAL_CAN_APPROVE : OPTIONAL;
+ },
+ },
+};
+</script>
+
+<template>
+ <div class="d-flex align-items-center">
+ <span class="text-muted">{{ message }}</span>
+ <gl-link
+ v-if="canApprove && helpPath"
+ v-gl-tooltip
+ :href="helpPath"
+ :title="__('About this feature')"
+ target="_blank"
+ class="d-flex-center pl-1"
+ >
+ <icon name="question" />
+ </gl-link>
+ </div>
+</template>
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/approvals/messages.js b/app/assets/javascripts/vue_merge_request_widget/components/approvals/messages.js
new file mode 100644
index 00000000000..1d9368f71aa
--- /dev/null
+++ b/app/assets/javascripts/vue_merge_request_widget/components/approvals/messages.js
@@ -0,0 +1,11 @@
+import { __, s__ } from '~/locale';
+
+export const FETCH_LOADING = __('Checking approval status');
+export const FETCH_ERROR = s__(
+ 'mrWidget|An error occurred while retrieving approval data for this merge request.',
+);
+export const APPROVE_ERROR = s__('mrWidget|An error occurred while submitting your approval.');
+export const UNAPPROVE_ERROR = s__('mrWidget|An error occurred while removing your approval.');
+export const APPROVED_MESSAGE = s__('mrWidget|Merge request approved.');
+export const OPTIONAL_CAN_APPROVE = s__('mrWidget|No approval required; you can still approve');
+export const OPTIONAL = s__('mrWidget|No approval required');
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/loading.vue b/app/assets/javascripts/vue_merge_request_widget/components/loading.vue
index 78dc28ee92b..cd4e31e0dae 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/loading.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/loading.vue
@@ -9,7 +9,7 @@ export default {
</script>
<template>
- <div class="prepend-top-default">
+ <div class="gl-mt-3">
<div class="mr-widget-heading p-3">
<gl-skeleton-loader :width="577" :height="12">
<rect width="86" height="12" rx="2" />
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_collapsible_extension.vue b/app/assets/javascripts/vue_merge_request_widget/components/mr_collapsible_extension.vue
index 294871ca5c2..24174c29d51 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/mr_collapsible_extension.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_collapsible_extension.vue
@@ -1,11 +1,11 @@
<script>
-import { GlDeprecatedButton, GlLoadingIcon } from '@gitlab/ui';
+import { GlButton, GlLoadingIcon } from '@gitlab/ui';
import { __ } from '~/locale';
import Icon from '~/vue_shared/components/icon.vue';
export default {
components: {
- GlDeprecatedButton,
+ GlButton,
GlLoadingIcon,
Icon,
},
@@ -58,16 +58,17 @@ export default {
</div>
<template v-else>
- <gl-deprecated-button
- class="btn-blank btn s32 square append-right-default"
+ <button
+ class="btn-blank btn s32 square gl-mr-3"
+ type="button"
:aria-label="ariaLabel"
:disabled="isLoading"
@click="toggleCollapsed"
>
<gl-loading-icon v-if="isLoading" />
<icon v-else :name="arrowIconName" class="js-icon" />
- </gl-deprecated-button>
- <gl-deprecated-button
+ </button>
+ <gl-button
variant="link"
class="js-title"
:disabled="isLoading"
@@ -76,7 +77,7 @@ export default {
>
<template v-if="isCollapsed">{{ title }}</template>
<template v-else>{{ __('Collapse') }}</template>
- </gl-deprecated-button>
+ </gl-button>
</template>
</div>
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_author.vue b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_author.vue
index 84937aa9510..598b08f4c16 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_author.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_author.vue
@@ -27,7 +27,7 @@ export default {
return this.author.webUrl || this.author.web_url;
},
avatarUrl() {
- return this.author.avatarUrl || this.author.avatar_url;
+ return this.author.avatarUrl || this.author.avatar_url || gl.mrWidgetData.defaultAvatarUrl;
},
},
};
@@ -40,6 +40,6 @@ export default {
class="author-link inline"
>
<img :src="avatarUrl" class="avatar avatar-inline s16" />
- <span v-if="showAuthorName" class="author"> {{ author.name }} </span>
+ <span v-if="showAuthorName" class="author">{{ author.name }}</span>
</a>
</template>
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_expandable_section.vue b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_expandable_section.vue
new file mode 100644
index 00000000000..fd999540f4a
--- /dev/null
+++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_expandable_section.vue
@@ -0,0 +1,72 @@
+<script>
+import { __ } from '~/locale';
+import { GlButton, GlCollapse, GlIcon } from '@gitlab/ui';
+
+/**
+ * Renders header section with icon and expand button
+ * Renders expanable content section with grey background
+ */
+export default {
+ name: 'MrWidgetExpanableSection',
+ components: {
+ GlButton,
+ GlCollapse,
+ GlIcon,
+ },
+ props: {
+ iconName: {
+ type: String,
+ required: false,
+ default: 'status_warning',
+ },
+ },
+ data() {
+ return {
+ contentIsVisible: false,
+ };
+ },
+ computed: {
+ collapseButtonText() {
+ if (this.contentIsVisible) {
+ return __('Collapse');
+ }
+
+ return __('Expand');
+ },
+ },
+ methods: {
+ updateContentVisibility() {
+ this.contentIsVisible = !this.contentIsVisible;
+ },
+ },
+};
+</script>
+
+<template>
+ <div>
+ <div class="mr-widget-body gl-display-flex">
+ <span
+ class="gl-display-flex gl-align-items-center gl-justify-content-center gl-mr-3 gl-align-self-start gl-mt-1"
+ >
+ <gl-icon :name="iconName" :size="24" />
+ </span>
+
+ <div class="gl-display-flex gl-flex-fill-1 gl-flex-direction-column gl-md-flex-direction-row">
+ <slot name="header"></slot>
+
+ <div>
+ <gl-button @click="updateContentVisibility">
+ {{ collapseButtonText }}
+ </gl-button>
+ </div>
+ </div>
+ </div>
+
+ <gl-collapse
+ :visible="contentIsVisible"
+ class="gl-bg-gray-10 gl-border-t-solid gl-border-gray-100 gl-border-1"
+ >
+ <slot name="content"></slot>
+ </gl-collapse>
+ </div>
+</template>
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_header.vue b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_header.vue
index 0464c4b9c15..897f706290d 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_header.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_header.vue
@@ -1,4 +1,5 @@
<script>
+import Mousetrap from 'mousetrap';
import { escape } from 'lodash';
import { n__, s__, sprintf } from '~/locale';
import { mergeUrlParams, webIDEUrl } from '~/lib/utils/url_utility';
@@ -74,10 +75,21 @@ export default {
: '';
},
},
+ mounted() {
+ Mousetrap.bind('b', this.copyBranchName);
+ },
+ beforeDestroy() {
+ Mousetrap.unbind('b');
+ },
+ methods: {
+ copyBranchName() {
+ this.$refs.copyBranchNameButton.$el.click();
+ },
+ },
};
</script>
<template>
- <div class="d-flex mr-source-target append-bottom-default">
+ <div class="d-flex mr-source-target gl-mb-3">
<mr-widget-icon name="git-merge" />
<div class="git-merge-container d-flex">
<div class="normal">
@@ -89,6 +101,7 @@ export default {
class="label-branch label-truncate js-source-branch"
v-html="mr.sourceBranchLink"
/><clipboard-button
+ ref="copyBranchNameButton"
:text="branchNameClipboardData"
:title="__('Copy branch name')"
css-class="btn-default btn-transparent btn-clipboard"
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_icon.vue b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_icon.vue
index 57d4d8b7ae6..e1659d9a167 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_icon.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_icon.vue
@@ -13,7 +13,7 @@ export default {
</script>
<template>
- <div class="circle-icon-container append-right-default align-self-start align-self-lg-center">
+ <div class="circle-icon-container gl-mr-3 align-self-start align-self-lg-center">
<icon :name="name" :size="24" />
</div>
</template>
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.vue b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.vue
index 6df53311ef0..a096eb1a1fe 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.vue
@@ -1,21 +1,22 @@
<script>
/* eslint-disable vue/require-default-prop */
-import { GlTooltipDirective, GlLink } from '@gitlab/ui';
+import { GlIcon, GlLink, GlLoadingIcon, GlSprintf, GlTooltipDirective } from '@gitlab/ui';
import mrWidgetPipelineMixin from 'ee_else_ce/vue_merge_request_widget/mixins/mr_widget_pipeline';
-import { sprintf, s__ } from '~/locale';
-import PipelineStage from '~/pipelines/components/stage.vue';
+import { s__ } from '~/locale';
+import PipelineStage from '~/pipelines/components/pipelines_list/stage.vue';
import CiIcon from '~/vue_shared/components/ci_icon.vue';
-import Icon from '~/vue_shared/components/icon.vue';
import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate.vue';
export default {
name: 'MRWidgetPipeline',
components: {
- PipelineStage,
CiIcon,
- Icon,
- TooltipOnTruncate,
GlLink,
+ GlLoadingIcon,
+ GlIcon,
+ GlSprintf,
+ PipelineStage,
+ TooltipOnTruncate,
LinkedPipelinesMiniList: () =>
import('ee_component/vue_shared/components/linked_pipelines_mini_list.vue'),
},
@@ -54,7 +55,11 @@ export default {
type: String,
required: false,
},
- troubleshootingDocsPath: {
+ mrTroubleshootingDocsPath: {
+ type: String,
+ required: true,
+ },
+ ciTroubleshootingDocsPath: {
type: String,
required: true,
},
@@ -64,10 +69,7 @@ export default {
return this.pipeline && Object.keys(this.pipeline).length > 0;
},
hasCIError() {
- return (this.hasCi && !this.ciStatus) || this.hasPipelineMustSucceedConflict;
- },
- hasPipelineMustSucceedConflict() {
- return !this.hasCi && this.pipelineMustSucceed;
+ return this.hasPipeline && !this.ciStatus;
},
status() {
return this.pipeline.details && this.pipeline.details.status
@@ -82,22 +84,6 @@ export default {
hasCommitInfo() {
return this.pipeline.commit && Object.keys(this.pipeline.commit).length > 0;
},
- errorText() {
- if (this.hasPipelineMustSucceedConflict) {
- return s__('Pipeline|No pipeline has been run for this commit.');
- }
-
- return sprintf(
- s__(
- 'Pipeline|Could not retrieve the pipeline status. For troubleshooting steps, read the %{linkStart}documentation%{linkEnd}.',
- ),
- {
- linkStart: `<a href="${this.troubleshootingDocsPath}">`,
- linkEnd: '</a>',
- },
- false,
- );
- },
isTriggeredByMergeRequest() {
return Boolean(this.pipeline.merge_request);
},
@@ -118,31 +104,69 @@ export default {
return '';
},
},
+ errorText: s__(
+ 'Pipeline|Could not retrieve the pipeline status. For troubleshooting steps, read the %{linkStart}documentation%{linkEnd}.',
+ ),
+ monitoringPipelineText: s__('Pipeline|Checking pipeline status.'),
};
</script>
<template>
- <div class="ci-widget media js-ci-widget">
- <template v-if="!hasPipeline || hasCIError">
- <div class="add-border ci-status-icon ci-status-icon-failed ci-error js-ci-error">
- <icon :size="24" name="status_failed_borderless" />
+ <div class="ci-widget media">
+ <template v-if="hasCIError">
+ <gl-icon name="status_failed" class="gl-text-red-500" :size="24" />
+ <div
+ class="gl-flex-fill-1 gl-ml-5"
+ tabindex="0"
+ role="text"
+ :aria-label="$options.errorText"
+ data-testid="ci-error-message"
+ >
+ <gl-sprintf :message="$options.errorText">
+ <template #link="{content}">
+ <gl-link :href="mrTroubleshootingDocsPath">{{ content }}</gl-link>
+ </template>
+ </gl-sprintf>
+ </div>
+ </template>
+ <template v-else-if="!hasPipeline">
+ <gl-loading-icon size="md" />
+ <div class="gl-flex-fill-1 gl-display-flex gl-ml-5" data-testid="monitoring-pipeline-message">
+ <span tabindex="0" role="text" :aria-label="$options.monitoringPipelineText">
+ <gl-sprintf :message="$options.monitoringPipelineText" />
+ </span>
+ <gl-link
+ :href="ciTroubleshootingDocsPath"
+ target="_blank"
+ class="gl-display-flex gl-align-items-center gl-ml-2"
+ tabindex="0"
+ >
+ <gl-icon
+ name="question"
+ :small="12"
+ tabindex="0"
+ role="text"
+ :aria-label="__('Link to go to GitLab pipeline documentation')"
+ />
+ </gl-link>
</div>
- <div class="media-body prepend-left-default" v-html="errorText"></div>
</template>
<template v-else-if="hasPipeline">
- <a :href="status.details_path" class="align-self-start append-right-default">
+ <a :href="status.details_path" class="align-self-start gl-mr-3">
<ci-icon :status="status" :size="24" :borderless="true" class="add-border" />
</a>
<div class="ci-widget-container d-flex">
<div class="ci-widget-content">
<div class="media-body">
<div
- class="font-weight-bold js-pipeline-info-container"
+ class="gl-font-weight-bold"
+ data-testid="pipeline-info-container"
data-qa-selector="merge_request_pipeline_info_content"
>
{{ pipeline.details.name }}
<gl-link
:href="pipeline.path"
- class="pipeline-id font-weight-normal pipeline-number"
+ class="pipeline-id gl-font-weight-normal pipeline-number"
+ data-testid="pipeline-id"
data-qa-selector="pipeline_link"
>#{{ pipeline.id }}</gl-link
>
@@ -151,7 +175,8 @@ export default {
{{ s__('Pipeline|for') }}
<gl-link
:href="pipeline.commit.commit_path"
- class="commit-sha js-commit-link font-weight-normal"
+ class="commit-sha gl-font-weight-normal"
+ data-testid="commit-link"
>{{ pipeline.commit.short_id }}</gl-link
>
</template>
@@ -160,18 +185,18 @@ export default {
<tooltip-on-truncate
:title="sourceBranch"
truncate-target="child"
- class="label-branch label-truncate font-weight-normal"
+ class="label-branch label-truncate gl-font-weight-normal"
v-html="sourceBranchLink"
/>
</template>
</div>
- <div v-if="pipeline.coverage" class="coverage">
+ <div v-if="pipeline.coverage" class="coverage" data-testid="pipeline-coverage">
{{ s__('Pipeline|Coverage') }} {{ pipeline.coverage }}%
<span
v-if="pipelineCoverageDelta"
- class="js-pipeline-coverage-delta"
:class="coverageDeltaClass"
+ data-testid="pipeline-coverage-delta"
>
({{ pipelineCoverageDelta }}%)
</span>
@@ -189,13 +214,13 @@ export default {
:class="{
'has-downstream': hasDownstream(i),
}"
- class="stage-container dropdown js-mini-pipeline-graph mr-widget-pipeline-stages"
+ class="stage-container dropdown mr-widget-pipeline-stages"
+ data-testid="widget-mini-pipeline-graph"
>
<pipeline-stage :stage="stage" />
</div>
</template>
</span>
-
<linked-pipelines-mini-list v-if="triggered.length" :triggered="triggered" />
</span>
</div>
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline_container.vue b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline_container.vue
index 8fba0e2981f..5c307b5ff0c 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline_container.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline_container.vue
@@ -82,7 +82,8 @@ export default {
:pipeline-must-succeed="mr.onlyAllowMergeIfPipelineSucceeds"
:source-branch="branch"
:source-branch-link="branchLink"
- :troubleshooting-docs-path="mr.troubleshootingDocsPath"
+ :mr-troubleshooting-docs-path="mr.mrTroubleshootingDocsPath"
+ :ci-troubleshooting-docs-path="mr.ciTroubleshootingDocsPath"
/>
<template #footer>
<div v-if="mr.exposedArtifactsPath" class="js-exposed-artifacts">
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_status_icon.vue b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_status_icon.vue
index d0df8309dc7..82566682bca 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_status_icon.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_status_icon.vue
@@ -33,7 +33,7 @@ export default {
</script>
<template>
<div class="d-flex align-self-start">
- <div class="square s24 h-auto d-flex-center append-right-default">
+ <div class="square s24 h-auto d-flex-center gl-mr-3">
<div v-if="isLoading" class="mr-widget-icon d-inline-flex">
<gl-loading-icon size="md" class="mr-loading-icon d-inline-flex" />
</div>
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_suggest_pipeline.vue b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_suggest_pipeline.vue
index 9942861d9e4..de01821a292 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_suggest_pipeline.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_suggest_pipeline.vue
@@ -1,22 +1,31 @@
<script>
-import { GlLink, GlSprintf } from '@gitlab/ui';
+import { GlLink, GlSprintf, GlButton } from '@gitlab/ui';
import MrWidgetIcon from './mr_widget_icon.vue';
-import PipelineTourState from './states/mr_widget_pipeline_tour.vue';
+import Tracking from '~/tracking';
+import { s__ } from '~/locale';
+
+const trackingMixin = Tracking.mixin();
+const TRACK_LABEL = 'no_pipeline_noticed';
export default {
name: 'MRWidgetSuggestPipeline',
iconName: 'status_notfound',
- popoverTarget: 'suggest-popover',
- popoverContainer: 'suggest-pipeline',
- trackLabel: 'no_pipeline_noticed',
+ trackLabel: TRACK_LABEL,
linkTrackValue: 30,
linkTrackEvent: 'click_link',
+ showTrackValue: 10,
+ showTrackEvent: 'click_button',
+ helpContent: s__(
+ `mrWidget|Use %{linkStart}CI pipelines to test your code%{linkEnd} by simply adding a GitLab CI configuration file to your project. It only takes a minute to make your code more secure and robust.`,
+ ),
+ helpURL: 'https://about.gitlab.com/blog/2019/07/12/guide-to-ci-cd-pipelines/',
components: {
GlLink,
GlSprintf,
+ GlButton,
MrWidgetIcon,
- PipelineTourState,
},
+ mixins: [trackingMixin],
props: {
pipelinePath: {
type: String,
@@ -31,45 +40,89 @@ export default {
required: true,
},
},
+ computed: {
+ tracking() {
+ return {
+ label: TRACK_LABEL,
+ property: this.humanAccess,
+ };
+ },
+ },
+ mounted() {
+ this.track();
+ },
};
</script>
<template>
- <div :id="$options.popoverContainer" class="d-flex mr-pipeline-suggest append-bottom-default">
- <mr-widget-icon :name="$options.iconName" />
- <div :id="$options.popoverTarget">
- <gl-sprintf
- :message="
- s__(`mrWidget|%{prefixToLinkStart}No pipeline%{prefixToLinkEnd}
+ <div class="mr-widget-body mr-pipeline-suggest gl-mb-3">
+ <div class="gl-display-flex gl-align-items-center">
+ <mr-widget-icon :name="$options.iconName" />
+ <div>
+ <gl-sprintf
+ :message="
+ s__(`mrWidget|%{prefixToLinkStart}No pipeline%{prefixToLinkEnd}
%{addPipelineLinkStart}Add the .gitlab-ci.yml file%{addPipelineLinkEnd}
to create one.`)
- "
- >
- <template #prefixToLink="{content}">
+ "
+ >
+ <template #prefixToLink="{content}">
+ <strong>
+ {{ content }}
+ </strong>
+ </template>
+ <template #addPipelineLink="{content}">
+ <gl-link
+ :href="pipelinePath"
+ class="gl-ml-1"
+ data-testid="add-pipeline-link"
+ :data-track-property="humanAccess"
+ :data-track-value="$options.linkTrackValue"
+ :data-track-event="$options.linkTrackEvent"
+ :data-track-label="$options.trackLabel"
+ >
+ {{ content }}
+ </gl-link>
+ </template>
+ </gl-sprintf>
+ </div>
+ </div>
+ <div class="row">
+ <div class="col-md-5 order-md-last col-12 gl-mt-5 mt-md-n3 svg-content svg-225">
+ <img data-testid="pipeline-image" :src="pipelineSvgPath" />
+ </div>
+ <div class="col-md-7 order-md-first col-12">
+ <div class="ml-6 gl-pt-5">
<strong>
- {{ content }}
+ {{ s__('mrWidget|Are you adding technical debt or code vulnerabilities?') }}
</strong>
- </template>
- <template #addPipelineLink="{content}">
- <gl-link
+ <p class="gl-mt-2">
+ <gl-sprintf :message="$options.helpContent">
+ <template #link="{ content }">
+ <gl-link
+ data-testid="help"
+ :href="$options.helpURL"
+ target="_blank"
+ class="font-size-inherit"
+ >{{ content }}
+ </gl-link>
+ </template>
+ </gl-sprintf>
+ </p>
+ <gl-button
+ data-testid="ok"
+ category="primary"
+ class="gl-mt-2"
+ variant="info"
:href="pipelinePath"
- class="ml-2 js-add-pipeline-path"
:data-track-property="humanAccess"
- :data-track-value="$options.linkTrackValue"
- :data-track-event="$options.linkTrackEvent"
+ :data-track-value="$options.showTrackValue"
+ :data-track-event="$options.showTrackEvent"
:data-track-label="$options.trackLabel"
>
- {{ content }}
- </gl-link>
- </template>
- </gl-sprintf>
- <pipeline-tour-state
- :pipeline-path="pipelinePath"
- :pipeline-svg-path="pipelineSvgPath"
- :human-access="humanAccess"
- :popover-target="$options.popoverTarget"
- :popover-container="$options.popoverContainer"
- :track-label="$options.trackLabel"
- />
+ {{ __('Show me how to add a pipeline') }}
+ </gl-button>
+ </div>
+ </div>
</div>
</div>
</template>
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_terraform_plan.vue b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_terraform_plan.vue
deleted file mode 100644
index 2ef5e81b36b..00000000000
--- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_terraform_plan.vue
+++ /dev/null
@@ -1,139 +0,0 @@
-<script>
-import { __ } from '~/locale';
-import { GlIcon, GlLink, GlLoadingIcon, GlSprintf } from '@gitlab/ui';
-import axios from '~/lib/utils/axios_utils';
-import flash from '~/flash';
-import Poll from '~/lib/utils/poll';
-
-export default {
- name: 'MRWidgetTerraformPlan',
- components: {
- GlIcon,
- GlLink,
- GlLoadingIcon,
- GlSprintf,
- },
- props: {
- endpoint: {
- type: String,
- required: true,
- },
- },
- data() {
- return {
- loading: true,
- plans: {},
- };
- },
- computed: {
- addNum() {
- return Number(this.plan.create);
- },
- changeNum() {
- return Number(this.plan.update);
- },
- deleteNum() {
- return Number(this.plan.delete);
- },
- logUrl() {
- return this.plan.job_path;
- },
- plan() {
- const firstPlanKey = Object.keys(this.plans)[0];
- return this.plans[firstPlanKey] ?? {};
- },
- validPlanValues() {
- return this.addNum + this.changeNum + this.deleteNum >= 0;
- },
- },
- created() {
- this.fetchPlans();
- },
- methods: {
- fetchPlans() {
- this.loading = true;
-
- const poll = new Poll({
- resource: {
- fetchPlans: () => axios.get(this.endpoint),
- },
- data: this.endpoint,
- method: 'fetchPlans',
- successCallback: ({ data }) => {
- this.plans = data;
-
- if (Object.keys(this.plan).length) {
- this.loading = false;
- poll.stop();
- }
- },
- errorCallback: () => {
- this.plans = {};
- this.loading = false;
- flash(__('An error occurred while loading terraform report'));
- },
- });
-
- poll.makeRequest();
- },
- },
-};
-</script>
-
-<template>
- <section class="mr-widget-section">
- <div class="mr-widget-body media d-flex flex-row">
- <span class="append-right-default align-self-start align-self-lg-center">
- <gl-icon name="status_warning" :size="24" />
- </span>
-
- <div class="d-flex flex-fill flex-column flex-md-row">
- <div class="terraform-mr-plan-text normal d-flex flex-column flex-lg-row">
- <p class="m-0 pr-1">{{ __('A terraform report was generated in your pipelines.') }}</p>
-
- <gl-loading-icon v-if="loading" size="md" />
-
- <p v-else-if="validPlanValues" class="m-0">
- <gl-sprintf
- :message="
- __(
- 'Reported Resource Changes: %{addNum} to add, %{changeNum} to change, %{deleteNum} to delete',
- )
- "
- >
- <template #addNum>
- <strong>{{ addNum }}</strong>
- </template>
-
- <template #changeNum>
- <strong>{{ changeNum }}</strong>
- </template>
-
- <template #deleteNum>
- <strong>{{ deleteNum }}</strong>
- </template>
- </gl-sprintf>
- </p>
-
- <p v-else class="m-0">{{ __('Changes are unknown') }}</p>
- </div>
-
- <div class="terraform-mr-plan-actions">
- <gl-link
- v-if="logUrl"
- :href="logUrl"
- target="_blank"
- data-track-event="click_terraform_mr_plan_button"
- data-track-label="mr_widget_terraform_mr_plan_button"
- data-track-property="terraform_mr_plan_button"
- class="btn btn-sm js-terraform-report-link"
- rel="noopener"
- >
- {{ __('View full log') }}
- <gl-icon name="external-link" />
- </gl-link>
- </div>
- </div>
- </div>
- </section>
-</template>
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/commit_edit.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/commit_edit.vue
index acd8037cfb2..44bdc4a3be8 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/commit_edit.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/commit_edit.vue
@@ -29,7 +29,7 @@ export default {
<textarea
:id="inputId"
:value="value"
- class="form-control js-gfm-input append-bottom-default commit-message-edit"
+ class="form-control js-gfm-input gl-mb-3 commit-message-edit"
dir="auto"
required="required"
rows="7"
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/commits_header.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/commits_header.vue
index e4f4032776b..d52e6d38ac6 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/commits_header.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/commits_header.vue
@@ -83,7 +83,7 @@ export default {
<gl-deprecated-button
:aria-label="ariaLabel"
variant="blank"
- class="commit-edit-toggle square s24 append-right-default"
+ class="commit-edit-toggle square s24 gl-mr-3"
@click.stop="toggle()"
>
<icon :name="collapseIcon" :size="16" />
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_auto_merge_enabled.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_auto_merge_enabled.vue
index 92848e86e76..f02e0ac84da 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_auto_merge_enabled.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_auto_merge_enabled.vue
@@ -87,7 +87,7 @@ export default {
<status-icon status="success" />
<div class="media-body">
<h4 class="d-flex align-items-start">
- <span class="append-right-10">
+ <span class="gl-mr-3">
<span class="js-status-text-before-author">{{ statusTextBeforeAuthor }}</span>
<mr-widget-author :author="mr.setToAutoMergeBy" />
<span class="js-status-text-after-author">{{ statusTextAfterAuthor }}</span>
@@ -113,9 +113,7 @@ export default {
{{ s__('mrWidget|The source branch will be deleted') }}
</p>
<p v-else class="d-flex align-items-start">
- <span class="append-right-10">{{
- s__('mrWidget|The source branch will not be deleted')
- }}</span>
+ <span class="gl-mr-3">{{ s__('mrWidget|The source branch will not be deleted') }}</span>
<a
v-if="canRemoveSourceBranch"
:disabled="isRemovingSourceBranch"
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_checking.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_checking.vue
index a5e3115397a..e02be6dc2f7 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_checking.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_checking.vue
@@ -12,7 +12,7 @@ export default {
<div class="mr-widget-body media">
<status-icon :show-disabled-button="true" status="loading" />
<div class="media-body space-children">
- <span class="bold"> {{ s__('mrWidget|Checking ability to merge automatically…') }} </span>
+ <span class="bold"> {{ s__('mrWidget|Checking if merge request can be merged…') }} </span>
</div>
</div>
</template>
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_pipeline_tour.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_pipeline_tour.vue
deleted file mode 100644
index f6bfb178437..00000000000
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_pipeline_tour.vue
+++ /dev/null
@@ -1,136 +0,0 @@
-<script>
-import { GlPopover, GlDeprecatedButton } from '@gitlab/ui';
-import Icon from '~/vue_shared/components/icon.vue';
-import Cookies from 'js-cookie';
-import { parseBoolean } from '~/lib/utils/common_utils';
-import Tracking from '~/tracking';
-
-const trackingMixin = Tracking.mixin();
-
-const cookieKey = 'suggest_pipeline_dismissed';
-
-export default {
- name: 'MRWidgetPipelineTour',
- dismissTrackValue: 20,
- showTrackValue: 10,
- trackEvent: 'click_button',
- components: {
- GlPopover,
- GlDeprecatedButton,
- Icon,
- },
- mixins: [trackingMixin],
- props: {
- pipelinePath: {
- type: String,
- required: true,
- },
- pipelineSvgPath: {
- type: String,
- required: true,
- },
- humanAccess: {
- type: String,
- required: true,
- },
- popoverTarget: {
- type: String,
- required: true,
- },
- popoverContainer: {
- type: String,
- required: true,
- },
- trackLabel: {
- type: String,
- required: true,
- },
- },
- data() {
- return {
- popoverDismissed: parseBoolean(Cookies.get(cookieKey)),
- tracking: {
- label: this.trackLabel,
- property: this.humanAccess,
- },
- };
- },
- mounted() {
- this.trackOnShow();
- },
- methods: {
- trackOnShow() {
- if (!this.popoverDismissed) {
- this.track();
- }
- },
- dismissPopover() {
- this.popoverDismissed = true;
- Cookies.set(cookieKey, this.popoverDismissed, { expires: 365 });
- },
- },
-};
-</script>
-<template>
- <gl-popover
- v-if="!popoverDismissed"
- show
- :target="popoverTarget"
- :container="popoverContainer"
- placement="rightbottom"
- >
- <template #title>
- <button
- class="btn-blank float-right mt-1"
- type="button"
- :aria-label="__('Close')"
- :data-track-property="humanAccess"
- :data-track-value="$options.dismissTrackValue"
- :data-track-event="$options.trackEvent"
- :data-track-label="trackLabel"
- @click="dismissPopover"
- >
- <icon name="close" aria-hidden="true" />
- </button>
- {{ s__('mrWidget|Are you adding technical debt or code vulnerabilities?') }}
- </template>
- <div class="svg-content svg-150 pt-1">
- <img :src="pipelineSvgPath" />
- </div>
- <p>
- {{
- s__(
- 'mrWidget|Detect issues before deployment with a CI pipeline that continuously tests your code. We created a quick guide that will show you how to create one. Make your code more secure and more robust in just a minute.',
- )
- }}
- </p>
- <gl-deprecated-button
- ref="ok"
- category="primary"
- class="mt-2 mb-0"
- variant="info"
- block
- :href="pipelinePath"
- :data-track-property="humanAccess"
- :data-track-value="$options.showTrackValue"
- :data-track-event="$options.trackEvent"
- :data-track-label="trackLabel"
- >
- {{ __('Show me how') }}
- </gl-deprecated-button>
- <gl-deprecated-button
- ref="no-thanks"
- category="secondary"
- class="mt-2 mb-0"
- variant="info"
- block
- :data-track-property="humanAccess"
- :data-track-value="$options.dismissTrackValue"
- :data-track-event="$options.trackEvent"
- :data-track-label="trackLabel"
- @click="dismissPopover"
- >
- {{ __("No thanks, don't show this again") }}
- </gl-deprecated-button>
- </gl-popover>
-</template>
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue
index 82be5eeb5ff..cc43135f50a 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue
@@ -45,7 +45,8 @@ export default {
isMakingRequest: false,
isMergingImmediately: false,
commitMessage: this.mr.commitMessage,
- squashBeforeMerge: this.mr.squash,
+ squashBeforeMerge: this.mr.squashIsSelected,
+ isSquashReadOnly: this.mr.squashIsReadonly,
successSvg,
warningSvg,
squashCommitMessage: this.mr.squashCommitMessage,
@@ -106,7 +107,12 @@ export default {
return this.isMergeButtonDisabled;
},
shouldShowSquashBeforeMerge() {
- const { commitsCount, enableSquashBeforeMerge } = this.mr;
+ const { commitsCount, enableSquashBeforeMerge, squashIsReadonly, squashIsSelected } = this.mr;
+
+ if (squashIsReadonly && !squashIsSelected) {
+ return false;
+ }
+
return enableSquashBeforeMerge && commitsCount > 1;
},
shouldShowMergeControls() {
@@ -344,21 +350,24 @@ export default {
v-if="shouldShowSquashBeforeMerge"
v-model="squashBeforeMerge"
:help-path="mr.squashBeforeMergeHelpPath"
- :is-disabled="isMergeButtonDisabled"
+ :is-disabled="isSquashReadOnly"
/>
</template>
<template v-else>
<div class="bold js-resolve-mr-widget-items-message">
- <gl-sprintf
+ <div
v-if="hasPipelineMustSucceedConflict"
- :message="pipelineMustSucceedConflictText"
+ class="gl-display-flex gl-align-items-center"
>
- <template #link="{ content }">
- <gl-link :href="mr.pipelineMustSucceedDocsPath" target="_blank">
- {{ content }}
- </gl-link>
- </template>
- </gl-sprintf>
+ <gl-sprintf :message="pipelineMustSucceedConflictText" />
+ <gl-link
+ :href="mr.pipelineMustSucceedDocsPath"
+ target="_blank"
+ class="gl-display-flex gl-ml-2"
+ >
+ <gl-icon name="question" />
+ </gl-link>
+ </div>
<gl-sprintf v-else :message="mergeDisabledText" />
</div>
</template>
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/squash_before_merge.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/squash_before_merge.vue
index 5305894873f..efd58341a2d 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/squash_before_merge.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/squash_before_merge.vue
@@ -1,6 +1,7 @@
<script>
import Icon from '~/vue_shared/components/icon.vue';
import tooltip from '~/vue_shared/directives/tooltip';
+import { __ } from '~/locale';
export default {
components: {
@@ -25,12 +26,22 @@ export default {
default: false,
},
},
+ computed: {
+ tooltipTitle() {
+ return this.isDisabled ? __('Required in this project.') : false;
+ },
+ },
};
</script>
<template>
<div class="inline">
- <label>
+ <label
+ v-tooltip
+ :class="{ 'gl-text-gray-600': isDisabled }"
+ data-testid="squashLabel"
+ :data-title="tooltipTitle"
+ >
<input
:checked="value"
:disabled="isDisabled"
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/terraform/mr_widget_terraform_container.vue b/app/assets/javascripts/vue_merge_request_widget/components/terraform/mr_widget_terraform_container.vue
new file mode 100644
index 00000000000..f6e21dc1ec1
--- /dev/null
+++ b/app/assets/javascripts/vue_merge_request_widget/components/terraform/mr_widget_terraform_container.vue
@@ -0,0 +1,140 @@
+<script>
+import { n__ } from '~/locale';
+import { GlSkeletonLoading, GlSprintf } from '@gitlab/ui';
+import axios from '~/lib/utils/axios_utils';
+import MrWidgetExpanableSection from '../mr_widget_expandable_section.vue';
+import Poll from '~/lib/utils/poll';
+import TerraformPlan from './terraform_plan.vue';
+
+export default {
+ name: 'MRWidgetTerraformContainer',
+ components: {
+ GlSkeletonLoading,
+ GlSprintf,
+ MrWidgetExpanableSection,
+ TerraformPlan,
+ },
+ props: {
+ endpoint: {
+ type: String,
+ required: true,
+ },
+ },
+ data() {
+ return {
+ loading: true,
+ plansObject: {},
+ poll: null,
+ };
+ },
+ computed: {
+ inValidPlanCountText() {
+ if (this.numberOfInvalidPlans === 0) {
+ return null;
+ }
+
+ return n__(
+ 'Terraform|%{number} Terraform report failed to generate',
+ 'Terraform|%{number} Terraform reports failed to generate',
+ this.numberOfInvalidPlans,
+ );
+ },
+ numberOfInvalidPlans() {
+ return Object.values(this.plansObject).filter(plan => plan.tf_report_error).length;
+ },
+ numberOfPlans() {
+ return Object.keys(this.plansObject).length;
+ },
+ numberOfValidPlans() {
+ return this.numberOfPlans - this.numberOfInvalidPlans;
+ },
+ validPlanCountText() {
+ if (this.numberOfValidPlans === 0) {
+ return null;
+ }
+
+ return n__(
+ 'Terraform|%{number} Terraform report was generated in your pipelines',
+ 'Terraform|%{number} Terraform reports were generated in your pipelines',
+ this.numberOfValidPlans,
+ );
+ },
+ },
+ created() {
+ this.fetchPlans();
+ },
+ beforeDestroy() {
+ this.poll.stop();
+ },
+ methods: {
+ fetchPlans() {
+ this.loading = true;
+
+ this.poll = new Poll({
+ resource: {
+ fetchPlans: () => axios.get(this.endpoint),
+ },
+ data: this.endpoint,
+ method: 'fetchPlans',
+ successCallback: ({ data }) => {
+ this.plansObject = data;
+
+ if (this.numberOfPlans > 0) {
+ this.loading = false;
+ this.poll.stop();
+ }
+ },
+ errorCallback: () => {
+ this.plansObject = { bad_plan: { tf_report_error: 'api_error' } };
+ this.loading = false;
+ this.poll.stop();
+ },
+ });
+
+ this.poll.makeRequest();
+ },
+ },
+};
+</script>
+
+<template>
+ <section class="mr-widget-section">
+ <div v-if="loading" class="mr-widget-body">
+ <gl-skeleton-loading />
+ </div>
+
+ <mr-widget-expanable-section v-else>
+ <template #header>
+ <div
+ data-testid="terraform-header-text"
+ class="gl-flex-fill-1 gl-display-flex gl-flex-direction-column"
+ >
+ <p v-if="validPlanCountText" class="gl-m-0">
+ <gl-sprintf :message="validPlanCountText">
+ <template #number>
+ <strong>{{ numberOfValidPlans }}</strong>
+ </template>
+ </gl-sprintf>
+ </p>
+
+ <p v-if="inValidPlanCountText" class="gl-m-0">
+ <gl-sprintf :message="inValidPlanCountText">
+ <template #number>
+ <strong>{{ numberOfInvalidPlans }}</strong>
+ </template>
+ </gl-sprintf>
+ </p>
+ </div>
+ </template>
+
+ <template #content>
+ <terraform-plan
+ v-for="(plan, key) in plansObject"
+ :key="key"
+ :plan="plan"
+ class="mr-widget-body"
+ />
+ </template>
+ </mr-widget-expanable-section>
+ </section>
+</template>
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/terraform/terraform_plan.vue b/app/assets/javascripts/vue_merge_request_widget/components/terraform/terraform_plan.vue
new file mode 100644
index 00000000000..dc16d46dd8e
--- /dev/null
+++ b/app/assets/javascripts/vue_merge_request_widget/components/terraform/terraform_plan.vue
@@ -0,0 +1,111 @@
+<script>
+import { s__ } from '~/locale';
+import { GlIcon, GlLink, GlSprintf } from '@gitlab/ui';
+
+export default {
+ name: 'TerraformPlan',
+ components: {
+ GlIcon,
+ GlLink,
+ GlSprintf,
+ },
+ props: {
+ plan: {
+ required: true,
+ type: Object,
+ },
+ },
+ computed: {
+ addNum() {
+ return Number(this.plan.create);
+ },
+ changeNum() {
+ return Number(this.plan.update);
+ },
+ deleteNum() {
+ return Number(this.plan.delete);
+ },
+ iconType() {
+ return this.validPlanValues ? 'doc-changes' : 'warning';
+ },
+ reportChangeText() {
+ if (this.validPlanValues) {
+ return s__(
+ 'Terraform|Reported Resource Changes: %{addNum} to add, %{changeNum} to change, %{deleteNum} to delete',
+ );
+ }
+
+ return s__('Terraform|Generating the report caused an error.');
+ },
+ reportHeaderText() {
+ if (this.validPlanValues) {
+ return this.plan.job_name
+ ? s__('Terraform|The Terraform report %{name} was generated in your pipelines.')
+ : s__('Terraform|A Terraform report was generated in your pipelines.');
+ }
+
+ return this.plan.job_name
+ ? s__('Terraform|The Terraform report %{name} failed to generate.')
+ : s__('Terraform|A Terraform report failed to generate.');
+ },
+ validPlanValues() {
+ return this.addNum + this.changeNum + this.deleteNum >= 0;
+ },
+ },
+};
+</script>
+
+<template>
+ <div class="gl-display-flex">
+ <span
+ class="gl-display-flex gl-align-items-center gl-justify-content-center gl-mr-3 gl-align-self-start gl-mt-1"
+ >
+ <gl-icon :name="iconType" :size="18" data-testid="change-type-icon" />
+ </span>
+
+ <div class="gl-display-flex gl-flex-fill-1 gl-flex-direction-column flex-md-row">
+ <div class="gl-flex-fill-1 gl-display-flex gl-flex-direction-column">
+ <p class="gl-m-0 gl-pr-1">
+ <gl-sprintf :message="reportHeaderText">
+ <template #name>
+ <strong>{{ plan.job_name }}</strong>
+ </template>
+ </gl-sprintf>
+ </p>
+
+ <p class="gl-m-0">
+ <gl-sprintf :message="reportChangeText">
+ <template #addNum>
+ <strong>{{ addNum }}</strong>
+ </template>
+
+ <template #changeNum>
+ <strong>{{ changeNum }}</strong>
+ </template>
+
+ <template #deleteNum>
+ <strong>{{ deleteNum }}</strong>
+ </template>
+ </gl-sprintf>
+ </p>
+ </div>
+
+ <div>
+ <gl-link
+ v-if="plan.job_path"
+ :href="plan.job_path"
+ target="_blank"
+ data-testid="terraform-report-link"
+ data-track-event="click_terraform_mr_plan_button"
+ data-track-label="mr_widget_terraform_mr_plan_button"
+ data-track-property="terraform_mr_plan_button"
+ class="btn btn-sm"
+ rel="noopener"
+ >
+ {{ __('View full log') }}
+ <gl-icon name="external-link" />
+ </gl-link>
+ </div>
+ </div>
+ </div>
+</template>
diff --git a/app/assets/javascripts/vue_merge_request_widget/constants.js b/app/assets/javascripts/vue_merge_request_widget/constants.js
index 6f6d145815e..1002bb728a0 100644
--- a/app/assets/javascripts/vue_merge_request_widget/constants.js
+++ b/app/assets/javascripts/vue_merge_request_widget/constants.js
@@ -1,3 +1,4 @@
+export const SUCCESS = 'success';
export const WARNING = 'warning';
export const DANGER = 'danger';
diff --git a/app/assets/javascripts/vue_merge_request_widget/index.js b/app/assets/javascripts/vue_merge_request_widget/index.js
index 7a9ef7e496e..068829912bf 100644
--- a/app/assets/javascripts/vue_merge_request_widget/index.js
+++ b/app/assets/javascripts/vue_merge_request_widget/index.js
@@ -1,15 +1,23 @@
import Vue from 'vue';
import MrWidgetOptions from 'ee_else_ce/vue_merge_request_widget/mr_widget_options.vue';
import Translate from '../vue_shared/translate';
+import VueApollo from 'vue-apollo';
+import createDefaultClient from '~/lib/graphql';
Vue.use(Translate);
+Vue.use(VueApollo);
+
+const apolloProvider = new VueApollo({
+ defaultClient: createDefaultClient(),
+});
export default () => {
if (gl.mrWidget) return;
gl.mrWidgetData.gitlabLogo = gon.gitlab_logo;
+ gl.mrWidgetData.defaultAvatarUrl = gon.default_avatar_url;
- const vm = new Vue(MrWidgetOptions);
+ const vm = new Vue({ ...MrWidgetOptions, apolloProvider });
window.gl.mrWidget = {
checkStatus: vm.checkStatus,
diff --git a/app/assets/javascripts/vue_merge_request_widget/mixins/approvals.js b/app/assets/javascripts/vue_merge_request_widget/mixins/approvals.js
new file mode 100644
index 00000000000..e50555ca875
--- /dev/null
+++ b/app/assets/javascripts/vue_merge_request_widget/mixins/approvals.js
@@ -0,0 +1,19 @@
+import { hideFlash } from '~/flash';
+
+export default {
+ methods: {
+ clearError() {
+ this.$emit('clearError');
+ this.hasApprovalAuthError = false;
+ const flashEl = document.querySelector('.flash-alert');
+ if (flashEl) {
+ hideFlash(flashEl);
+ }
+ },
+ refreshApprovals() {
+ return this.service.fetchApprovals().then(data => {
+ this.mr.setApprovals(data);
+ });
+ },
+ },
+};
diff --git a/app/assets/javascripts/vue_merge_request_widget/mixins/ready_to_merge.js b/app/assets/javascripts/vue_merge_request_widget/mixins/ready_to_merge.js
index 39fa5e465b8..319b6c333f4 100644
--- a/app/assets/javascripts/vue_merge_request_widget/mixins/ready_to_merge.js
+++ b/app/assets/javascripts/vue_merge_request_widget/mixins/ready_to_merge.js
@@ -2,7 +2,7 @@ import { __ } from '~/locale';
export const MERGE_DISABLED_TEXT = __('You can only merge once the items above are resolved.');
export const PIPELINE_MUST_SUCCEED_CONFLICT_TEXT = __(
- 'Pipelines must succeed for merge requests to be eligible to merge. Please enable pipelines for this project to continue. For more information, see the %{linkStart}documentation.%{linkEnd}',
+ 'A CI/CD pipeline must run and be successful before merge.',
);
export default {
diff --git a/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue b/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue
index 265ff81f39f..cff85fe232d 100644
--- a/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue
@@ -2,6 +2,7 @@
import { isEmpty } from 'lodash';
import MRWidgetStore from 'ee_else_ce/vue_merge_request_widget/stores/mr_widget_store';
import MRWidgetService from 'ee_else_ce/vue_merge_request_widget/services/mr_widget_service';
+import MrWidgetApprovals from 'ee_else_ce/vue_merge_request_widget/components/approvals/approvals.vue';
import stateMaps from 'ee_else_ce/vue_merge_request_widget/stores/state_maps';
import { sprintf, s__, __ } from '~/locale';
import Project from '~/pages/projects/project';
@@ -36,7 +37,8 @@ import CheckingState from './components/states/mr_widget_checking.vue';
import eventHub from './event_hub';
import notify from '~/lib/utils/notify';
import SourceBranchRemovalStatus from './components/source_branch_removal_status.vue';
-import TerraformPlan from './components/mr_widget_terraform_plan.vue';
+import TerraformPlan from './components/terraform/mr_widget_terraform_container.vue';
+import GroupedCodequalityReportsApp from '../reports/codequality_report/grouped_codequality_reports_app.vue';
import GroupedTestReportsApp from '../reports/components/grouped_test_reports_app.vue';
import { setFaviconOverlay } from '../lib/utils/common_utils';
import GroupedAccessibilityReportsApp from '../reports/accessibility_report/grouped_accessibility_reports_app.vue';
@@ -75,9 +77,11 @@ export default {
'mr-widget-auto-merge-failed': AutoMergeFailed,
'mr-widget-rebase': RebaseState,
SourceBranchRemovalStatus,
+ GroupedCodequalityReportsApp,
GroupedTestReportsApp,
TerraformPlan,
GroupedAccessibilityReportsApp,
+ MrWidgetApprovals,
},
props: {
mrData: {
@@ -96,6 +100,9 @@ export default {
};
},
computed: {
+ shouldRenderApprovals() {
+ return this.mr.state !== 'nothingToMerge';
+ },
componentName() {
return stateMaps.stateToComponentMap[this.mr.state];
},
@@ -111,6 +118,9 @@ export default {
shouldSuggestPipelines() {
return gon.features?.suggestPipeline && !this.mr.hasCI && this.mr.mergeRequestAddCiConfigPath;
},
+ shouldRenderCodeQuality() {
+ return this.mr?.codeclimate?.head_path;
+ },
shouldRenderRelatedLinks() {
return Boolean(this.mr.relatedLinks) && !this.mr.isNothingToMergeState;
},
@@ -216,6 +226,9 @@ export default {
mergeRequestCachedWidgetPath: store.mergeRequestCachedWidgetPath,
mergeActionsContentPath: store.mergeActionsContentPath,
rebasePath: store.rebasePath,
+ apiApprovalsPath: store.apiApprovalsPath,
+ apiApprovePath: store.apiApprovePath,
+ apiUnapprovePath: store.apiUnapprovePath,
};
},
createService(store) {
@@ -365,7 +378,7 @@ export default {
};
</script>
<template>
- <div v-if="mr" class="mr-state-widget prepend-top-default">
+ <div v-if="mr" class="mr-state-widget gl-mt-3">
<mr-widget-header :mr="mr" />
<mr-widget-suggest-pipeline
v-if="shouldSuggestPipelines"
@@ -379,11 +392,27 @@ export default {
class="mr-widget-workflow"
:mr="mr"
/>
+ <mr-widget-approvals
+ v-if="shouldRenderApprovals"
+ class="mr-widget-workflow"
+ :mr="mr"
+ :service="service"
+ />
<div class="mr-section-container mr-widget-workflow">
+ <grouped-codequality-reports-app
+ v-if="shouldRenderCodeQuality"
+ :base-path="mr.codeclimate.base_path"
+ :head-path="mr.codeclimate.head_path"
+ :head-blob-path="mr.headBlobPath"
+ :base-blob-path="mr.baseBlobPath"
+ :codequality-help-path="mr.codequalityHelpPath"
+ />
+
<grouped-test-reports-app
v-if="mr.testResultsPath"
class="js-reports-container"
:endpoint="mr.testResultsPath"
+ :pipeline-path="mr.pipeline.path"
/>
<terraform-plan v-if="mr.terraformReportsPath" :endpoint="mr.terraformReportsPath" />
diff --git a/app/assets/javascripts/vue_merge_request_widget/services/mr_widget_service.js b/app/assets/javascripts/vue_merge_request_widget/services/mr_widget_service.js
index c620023a6d6..ee9e3cc6d08 100644
--- a/app/assets/javascripts/vue_merge_request_widget/services/mr_widget_service.js
+++ b/app/assets/javascripts/vue_merge_request_widget/services/mr_widget_service.js
@@ -3,6 +3,10 @@ import axios from '../../lib/utils/axios_utils';
export default class MRWidgetService {
constructor(endpoints) {
this.endpoints = endpoints;
+
+ this.apiApprovalsPath = endpoints.apiApprovalsPath;
+ this.apiApprovePath = endpoints.apiApprovePath;
+ this.apiUnapprovePath = endpoints.apiUnapprovePath;
}
merge(data) {
@@ -54,6 +58,18 @@ export default class MRWidgetService {
return axios.post(this.endpoints.rebasePath);
}
+ fetchApprovals() {
+ return axios.get(this.apiApprovalsPath).then(res => res.data);
+ }
+
+ approveMergeRequest() {
+ return axios.post(this.apiApprovePath).then(res => res.data);
+ }
+
+ unapproveMergeRequest() {
+ return axios.post(this.apiUnapprovePath).then(res => res.data);
+ }
+
static executeInlineAction(url) {
return axios.post(url);
}
diff --git a/app/assets/javascripts/vue_merge_request_widget/stores/get_state_key.js b/app/assets/javascripts/vue_merge_request_widget/stores/get_state_key.js
index a2ee0bc3ca1..44e8167d6a3 100644
--- a/app/assets/javascripts/vue_merge_request_widget/stores/get_state_key.js
+++ b/app/assets/javascripts/vue_merge_request_widget/stores/get_state_key.js
@@ -11,12 +11,12 @@ export default function deviseState(data) {
return stateKey.checking;
} else if (data.has_conflicts) {
return stateKey.conflicts;
- } else if (data.work_in_progress) {
- return stateKey.workInProgress;
} else if (this.shouldBeRebased) {
return stateKey.rebase;
} else if (this.onlyAllowMergeIfPipelineSucceeds && this.isPipelineFailed) {
return stateKey.pipelineFailed;
+ } else if (data.work_in_progress) {
+ return stateKey.workInProgress;
} else if (this.hasMergeableDiscussionsState) {
return stateKey.unresolvedDiscussions;
} else if (this.isPipelineBlocked) {
diff --git a/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js b/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js
index d61e122d612..8b9799d9775 100644
--- a/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js
+++ b/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js
@@ -9,12 +9,19 @@ export default class MergeRequestStore {
this.sha = data.diff_head_sha;
this.gitlabLogo = data.gitlabLogo;
+ this.apiApprovalsPath = data.api_approvals_path;
+ this.apiApprovePath = data.api_approve_path;
+ this.apiUnapprovePath = data.api_unapprove_path;
+ this.hasApprovalsAvailable = data.has_approvals_available;
+
this.setPaths(data);
this.setData(data);
}
setData(data, isRebased) {
+ this.initApprovals();
+
if (isRebased) {
this.sha = data.diff_head_sha;
}
@@ -22,7 +29,10 @@ export default class MergeRequestStore {
const pipelineStatus = data.pipeline ? data.pipeline.details.status : null;
this.squash = data.squash;
+ this.squashIsEnabledByDefault = data.squash_enabled_by_default;
+ this.squashIsReadonly = data.squash_readonly;
this.enableSquashBeforeMerge = this.enableSquashBeforeMerge || true;
+ this.squashIsSelected = data.squash_readonly ? data.squash_on_merge : data.squash;
this.iid = data.iid;
this.title = data.title;
@@ -49,6 +59,7 @@ export default class MergeRequestStore {
this.squashCommitMessage = data.default_squash_commit_message;
this.rebaseInProgress = data.rebase_in_progress;
this.mergeRequestDiffsPath = data.diffs_path;
+ this.approvalsWidgetType = data.approvals_widget_type;
if (data.issues_links) {
const links = data.issues_links;
@@ -160,7 +171,8 @@ export default class MergeRequestStore {
setPaths(data) {
// Paths are set on the first load of the page and not auto-refreshed
this.squashBeforeMergeHelpPath = data.squash_before_merge_help_path;
- this.troubleshootingDocsPath = data.troubleshooting_docs_path;
+ this.mrTroubleshootingDocsPath = data.mr_troubleshooting_docs_path;
+ this.ciTroubleshootingDocsPath = data.ci_troubleshooting_docs_path;
this.pipelineMustSucceedDocsPath = data.pipeline_must_succeed_docs_path;
this.mergeRequestBasicPath = data.merge_request_basic_path;
this.mergeRequestWidgetPath = data.merge_request_widget_path;
@@ -177,10 +189,18 @@ export default class MergeRequestStore {
this.securityApprovalsHelpPagePath = data.security_approvals_help_page_path;
this.eligibleApproversDocsPath = data.eligible_approvers_docs_path;
this.mergeImmediatelyDocsPath = data.merge_immediately_docs_path;
+ this.approvalsHelpPath = data.approvals_help_path;
this.mergeRequestAddCiConfigPath = data.merge_request_add_ci_config_path;
this.pipelinesEmptySvgPath = data.pipelines_empty_svg_path;
this.humanAccess = data.human_access;
this.newPipelinePath = data.new_project_pipeline_path;
+
+ // codeclimate
+ const blobPath = data.blob_path || {};
+ this.headBlobPath = blobPath.head_path || '';
+ this.baseBlobPath = blobPath.base_path || '';
+ this.codequalityHelpPath = data.codequality_help_path;
+ this.codeclimate = data.codeclimate;
}
get isNothingToMergeState() {
@@ -240,4 +260,14 @@ export default class MergeRequestStore {
return undefined;
}
+
+ initApprovals() {
+ this.isApproved = this.isApproved || false;
+ this.approvals = this.approvals || null;
+ }
+
+ setApprovals(data) {
+ this.approvals = data;
+ this.isApproved = data.approved || false;
+ }
}
diff --git a/app/assets/javascripts/vue_shared/components/file_finder/index.vue b/app/assets/javascripts/vue_shared/components/file_finder/index.vue
index 9f6f3d2d63a..d6f591ccca1 100644
--- a/app/assets/javascripts/vue_shared/components/file_finder/index.vue
+++ b/app/assets/javascripts/vue_shared/components/file_finder/index.vue
@@ -261,7 +261,7 @@ export default {
</li>
</template>
<li v-else class="dropdown-menu-empty-item">
- <div class="append-right-default prepend-left-default gl-mt-3 gl-mb-3">
+ <div class="gl-mr-3 gl-ml-3 gl-mt-3 gl-mb-3">
<template v-if="loading">
{{ __('Loading...') }}
</template>
diff --git a/app/assets/javascripts/vue_shared/components/file_finder/item.vue b/app/assets/javascripts/vue_shared/components/file_finder/item.vue
index 590501a975a..79c62cd9938 100644
--- a/app/assets/javascripts/vue_shared/components/file_finder/item.vue
+++ b/app/assets/javascripts/vue_shared/components/file_finder/item.vue
@@ -88,7 +88,7 @@ export default {
>
</span>
</strong>
- <span class="diff-changed-file-path prepend-top-5">
+ <span class="diff-changed-file-path gl-mt-2">
<span
v-for="(char, charIndex) in pathWithEllipsis.split('')"
:key="charIndex + char"
diff --git a/app/assets/javascripts/vue_shared/components/file_icon.vue b/app/assets/javascripts/vue_shared/components/file_icon.vue
index b084ebdf774..7484486d6b4 100644
--- a/app/assets/javascripts/vue_shared/components/file_icon.vue
+++ b/app/assets/javascripts/vue_shared/components/file_icon.vue
@@ -1,6 +1,7 @@
<script>
import { GlLoadingIcon, GlIcon } from '@gitlab/ui';
import getIconForFile from './file_icon/file_icon_map';
+import { FILE_SYMLINK_MODE } from '../constants';
/* This is a re-usable vue component for rendering a svg sprite
icon
@@ -24,6 +25,11 @@ export default {
type: String,
required: true,
},
+ fileMode: {
+ type: String,
+ required: false,
+ default: '',
+ },
folder: {
type: Boolean,
@@ -60,8 +66,12 @@ export default {
},
},
computed: {
+ isSymlink() {
+ return this.fileMode === FILE_SYMLINK_MODE;
+ },
spriteHref() {
const iconName = this.submodule ? 'folder-git' : getIconForFile(this.fileName) || 'file';
+
return `${gon.sprite_file_icons}#${iconName}`;
},
folderIconName() {
@@ -75,13 +85,11 @@ export default {
</script>
<template>
<span>
- <svg v-if="!loading && !folder" :class="[iconSizeClass, cssClasses]">
- <use v-bind="{ 'xlink:href': spriteHref }" /></svg
- ><gl-icon
- v-if="!loading && folder"
- :name="folderIconName"
- :size="size"
- class="folder-icon"
- /><gl-loading-icon v-if="loading" :inline="true" />
+ <gl-loading-icon v-if="loading" :inline="true" />
+ <gl-icon v-else-if="isSymlink" name="symlink" :size="size" />
+ <svg v-else-if="!folder" :class="[iconSizeClass, cssClasses]">
+ <use v-bind="{ 'xlink:href': spriteHref }" />
+ </svg>
+ <gl-icon v-else :name="folderIconName" :size="size" class="folder-icon" />
</span>
</template>
diff --git a/app/assets/javascripts/vue_shared/components/file_row.vue b/app/assets/javascripts/vue_shared/components/file_row.vue
index 0cc96309a92..0952e37e46e 100644
--- a/app/assets/javascripts/vue_shared/components/file_row.vue
+++ b/app/assets/javascripts/vue_shared/components/file_row.vue
@@ -118,7 +118,12 @@ export default {
@mouseleave="$emit('mouseleave', $event)"
>
<div class="file-row-name-container">
- <span ref="textOutput" :style="levelIndentation" class="file-row-name str-truncated">
+ <span
+ ref="textOutput"
+ :style="levelIndentation"
+ class="file-row-name str-truncated"
+ data-qa-selector="file_name_content"
+ >
<file-icon
class="file-row-icon"
:class="{ 'text-secondary': file.type === 'tree' }"
diff --git a/app/assets/javascripts/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue b/app/assets/javascripts/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue
index a858ffdbed5..04090213218 100644
--- a/app/assets/javascripts/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue
+++ b/app/assets/javascripts/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue
@@ -83,6 +83,7 @@ export default {
return {
initialRender: true,
recentSearchesPromise: null,
+ recentSearches: [],
filterValue: this.initialFilterValue,
selectedSortOption,
selectedSortDirection,
@@ -98,6 +99,15 @@ export default {
{},
);
},
+ tokenTitles() {
+ return this.tokens.reduce(
+ (tokenSymbols, token) => ({
+ ...tokenSymbols,
+ [token.type]: token.title,
+ }),
+ {},
+ );
+ },
sortDirectionIcon() {
return this.selectedSortDirection === SortDirection.ascending
? 'sort-lowest'
@@ -112,11 +122,10 @@ export default {
watch: {
/**
* GlFilteredSearch currently doesn't emit any event when
- * search field is cleared, but we still want our parent
- * component to know that filters were cleared and do
- * necessary data refetch, so this watcher is basically
- * a dirty hack/workaround to identify if filter input
- * was cleared. :(
+ * tokens are manually removed from search field so we'd
+ * never know when user actually clears all the tokens.
+ * This watcher listens for updates to `filterValue` on
+ * such instances. :(
*/
filterValue(value) {
const [firstVal] = value;
@@ -172,11 +181,9 @@ export default {
this.recentSearchesStore.state.recentSearches.concat(searches),
);
this.recentSearchesService.save(resultantSearches);
+ this.recentSearches = resultantSearches;
});
},
- getRecentSearches() {
- return this.recentSearchesStore?.state.recentSearches;
- },
handleSortOptionClick(sortBy) {
this.selectedSortOption = sortBy;
this.$emit('onSort', sortBy.sortDirection[this.selectedSortDirection]);
@@ -188,26 +195,22 @@ export default {
: SortDirection.ascending;
this.$emit('onSort', this.selectedSortOption.sortDirection[this.selectedSortDirection]);
},
+ handleHistoryItemSelected(filters) {
+ this.$emit('onFilter', filters);
+ },
+ handleClearHistory() {
+ const resultantSearches = this.recentSearchesStore.setRecentSearches([]);
+ this.recentSearchesService.save(resultantSearches);
+ this.recentSearches = [];
+ },
handleFilterSubmit(filters) {
if (this.recentSearchesStorageKey) {
this.recentSearchesPromise
.then(() => {
if (filters.length) {
- const searchTokens = filters.map(filter => {
- // check filter was plain text search
- if (typeof filter === 'string') {
- return filter;
- }
- // filter was a token.
- return `${filter.type}:${filter.value.operator}${this.tokenSymbols[filter.type]}${
- filter.value.data
- }`;
- });
-
- const resultantSearches = this.recentSearchesStore.addRecentSearch(
- searchTokens.join(' '),
- );
+ const resultantSearches = this.recentSearchesStore.addRecentSearch(filters);
this.recentSearchesService.save(resultantSearches);
+ this.recentSearches = resultantSearches;
}
})
.catch(() => {
@@ -226,10 +229,24 @@ export default {
v-model="filterValue"
:placeholder="searchInputPlaceholder"
:available-tokens="tokens"
- :history-items="getRecentSearches()"
+ :history-items="recentSearches"
class="flex-grow-1"
+ @history-item-selected="handleHistoryItemSelected"
+ @clear-history="handleClearHistory"
@submit="handleFilterSubmit"
- />
+ >
+ <template #history-item="{ historyItem }">
+ <template v-for="(token, index) in historyItem">
+ <span v-if="typeof token === 'string'" :key="index" class="gl-px-1">"{{ token }}"</span>
+ <span v-else :key="`${token.type}-${token.value.data}`" class="gl-px-1">
+ <span v-if="tokenTitles[token.type]"
+ >{{ tokenTitles[token.type] }} :{{ token.value.operator }}</span
+ >
+ <strong>{{ tokenSymbols[token.type] }}{{ token.value.data }}</strong>
+ </span>
+ </template>
+ </template>
+ </gl-filtered-search>
<gl-button-group class="sort-dropdown-container d-flex">
<gl-dropdown :text="selectedSortOption.title" :right="true" class="w-100">
<gl-dropdown-item
diff --git a/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/author_token.vue b/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/author_token.vue
index 412bfa5aa7f..d50649d2581 100644
--- a/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/author_token.vue
+++ b/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/author_token.vue
@@ -46,6 +46,16 @@ export default {
return this.authors.find(author => author.username.toLowerCase() === this.currentValue);
},
},
+ watch: {
+ active: {
+ immediate: true,
+ handler(newValue) {
+ if (!newValue && !this.authors.length) {
+ this.fetchAuthorBySearchTerm(this.value.data);
+ }
+ },
+ },
+ },
methods: {
fetchAuthorBySearchTerm(searchTerm) {
const fetchPromise = this.config.fetchPath
@@ -89,9 +99,9 @@ export default {
<span>{{ activeAuthor ? activeAuthor.name : inputValue }}</span>
</template>
<template #suggestions>
- <gl-filtered-search-suggestion :value="$options.anyAuthor">{{
- __('Any')
- }}</gl-filtered-search-suggestion>
+ <gl-filtered-search-suggestion :value="$options.anyAuthor">
+ {{ __('Any') }}
+ </gl-filtered-search-suggestion>
<gl-dropdown-divider />
<gl-loading-icon v-if="loading" />
<template v-else>
diff --git a/app/assets/javascripts/vue_shared/components/gl_mentions.vue b/app/assets/javascripts/vue_shared/components/gl_mentions.vue
index a7fba5e760b..0ef4f1eda27 100644
--- a/app/assets/javascripts/vue_shared/components/gl_mentions.vue
+++ b/app/assets/javascripts/vue_shared/components/gl_mentions.vue
@@ -3,18 +3,19 @@ import { escape } from 'lodash';
import Tribute from 'tributejs';
import axios from '~/lib/utils/axios_utils';
import { spriteIcon } from '~/lib/utils/common_utils';
+import SidebarMediator from '~/sidebar/sidebar_mediator';
/**
* Creates the HTML template for each row of the mentions dropdown.
*
- * @param original An object from the array returned from the `autocomplete_sources/members` API
- * @returns {string} An HTML template
+ * @param original - An object from the array returned from the `autocomplete_sources/members` API
+ * @returns {string} - An HTML template
*/
function menuItemTemplate({ original }) {
const rectAvatarClass = original.type === 'Group' ? 'rect-avatar' : '';
const avatarClasses = `avatar avatar-inline center s26 ${rectAvatarClass}
- gl-display-inline-flex gl-align-items-center gl-justify-content-center`;
+ gl-display-inline-flex! gl-align-items-center gl-justify-content-center`;
const avatarTag = original.avatar_url
? `<img
@@ -48,6 +49,7 @@ export default {
},
data() {
return {
+ assignees: undefined,
members: undefined,
};
},
@@ -76,19 +78,37 @@ export default {
*/
getMembers(inputText, processValues) {
if (this.members) {
- processValues(this.members);
+ processValues(this.getFilteredMembers());
} else if (this.dataSources.members) {
axios
.get(this.dataSources.members)
.then(response => {
this.members = response.data;
- processValues(response.data);
+ processValues(this.getFilteredMembers());
})
.catch(() => {});
} else {
processValues([]);
}
},
+ getFilteredMembers() {
+ const fullText = this.$slots.default[0].elm.value;
+
+ if (!this.assignees) {
+ this.assignees =
+ SidebarMediator.singleton?.store?.assignees?.map(assignee => assignee.username) || [];
+ }
+
+ if (fullText.startsWith('/assign @')) {
+ return this.members.filter(member => !this.assignees.includes(member.username));
+ }
+
+ if (fullText.startsWith('/unassign @')) {
+ return this.members.filter(member => this.assignees.includes(member.username));
+ }
+
+ return this.members;
+ },
},
render(createElement) {
return createElement('div', this.$slots.default);
diff --git a/app/assets/javascripts/vue_shared/components/gl_modal_vuex.vue b/app/assets/javascripts/vue_shared/components/gl_modal_vuex.vue
index df6fadf10cd..e14f6a04d3c 100644
--- a/app/assets/javascripts/vue_shared/components/gl_modal_vuex.vue
+++ b/app/assets/javascripts/vue_shared/components/gl_modal_vuex.vue
@@ -52,6 +52,14 @@ export default {
// $root.$emit is a workaround because other b-modal approaches don't work yet with gl-modal
this.$root.$emit('bv::hide::modal', this.modalId);
},
+ cancel() {
+ this.$emit('cancel');
+ this.syncHide();
+ },
+ ok() {
+ this.$emit('ok');
+ this.syncHide();
+ },
},
};
</script>
@@ -65,5 +73,6 @@ export default {
@hidden="syncHide"
>
<slot></slot>
+ <slot slot="modal-footer" name="modal-footer" :ok="ok" :cancel="cancel"></slot>
</gl-modal>
</template>
diff --git a/app/assets/javascripts/vue_shared/components/issue/issue_warning.vue b/app/assets/javascripts/vue_shared/components/issue/issue_warning.vue
deleted file mode 100644
index cb3cd18e5a7..00000000000
--- a/app/assets/javascripts/vue_shared/components/issue/issue_warning.vue
+++ /dev/null
@@ -1,91 +0,0 @@
-<script>
-import { GlLink } from '@gitlab/ui';
-import { escape } from 'lodash';
-import { __, sprintf } from '~/locale';
-import icon from '../icon.vue';
-
-function buildDocsLinkStart(path) {
- return `<a href="${escape(path)}" target="_blank" rel="noopener noreferrer">`;
-}
-
-export default {
- components: {
- icon,
- GlLink,
- },
- props: {
- isLocked: {
- type: Boolean,
- default: false,
- required: false,
- },
- isConfidential: {
- type: Boolean,
- default: false,
- required: false,
- },
- lockedIssueDocsPath: {
- type: String,
- required: false,
- default: '',
- },
- confidentialIssueDocsPath: {
- type: String,
- required: false,
- default: '',
- },
- },
- computed: {
- warningIcon() {
- if (this.isConfidential) return 'eye-slash';
- if (this.isLocked) return 'lock';
-
- return '';
- },
- isLockedAndConfidential() {
- return this.isConfidential && this.isLocked;
- },
- confidentialAndLockedDiscussionText() {
- return sprintf(
- __(
- 'This issue is %{confidentialLinkStart}confidential%{linkEnd} and %{lockedLinkStart}locked%{linkEnd}.',
- ),
- {
- confidentialLinkStart: buildDocsLinkStart(this.confidentialIssueDocsPath),
- lockedLinkStart: buildDocsLinkStart(this.lockedIssueDocsPath),
- linkEnd: '</a>',
- },
- false,
- );
- },
- },
-};
-</script>
-<template>
- <div class="issuable-note-warning">
- <icon v-if="!isLockedAndConfidential" :name="warningIcon" :size="16" class="icon inline" />
-
- <span v-if="isLockedAndConfidential" ref="lockedAndConfidential">
- <span v-html="confidentialAndLockedDiscussionText"></span>
- {{
- __("People without permission will never get a notification and won't be able to comment.")
- }}
- </span>
-
- <span v-else-if="isConfidential" ref="confidential">
- {{ __('This is a confidential issue.') }}
- {{ __('People without permission will never get a notification.') }}
- <gl-link :href="confidentialIssueDocsPath" target="_blank">
- {{ __('Learn more') }}
- </gl-link>
- </span>
-
- <span v-else-if="isLocked" ref="locked">
- {{ __('This issue is locked.') }}
- {{ __('Only project members can comment.') }}
- <gl-link :href="lockedIssueDocsPath" target="_blank">
- {{ __('Learn more') }}
- </gl-link>
- </span>
- </div>
-</template>
diff --git a/app/assets/javascripts/vue_shared/components/issue/related_issuable_item.vue b/app/assets/javascripts/vue_shared/components/issue/related_issuable_item.vue
index 63de1e009fd..caf13bc898b 100644
--- a/app/assets/javascripts/vue_shared/components/issue/related_issuable_item.vue
+++ b/app/assets/javascripts/vue_shared/components/issue/related_issuable_item.vue
@@ -82,7 +82,7 @@ export default {
v-gl-tooltip
name="eye-slash"
:title="__('Confidential')"
- class="confidential-icon append-right-4 align-self-baseline align-self-md-auto mt-xl-0"
+ class="confidential-icon gl-mr-2 align-self-baseline align-self-md-auto mt-xl-0"
:aria-label="__('Confidential')"
/>
<a :href="computedPath" class="sortable-link gl-font-weight-normal">{{ title }}</a>
diff --git a/app/assets/javascripts/vue_shared/components/loading_button.vue b/app/assets/javascripts/vue_shared/components/loading_button.vue
index 3508c557289..59ce632c4a2 100644
--- a/app/assets/javascripts/vue_shared/components/loading_button.vue
+++ b/app/assets/javascripts/vue_shared/components/loading_button.vue
@@ -47,7 +47,7 @@ export default {
v-if="loading"
:inline="true"
:class="{
- 'append-right-5': label,
+ 'gl-mr-2': label,
}"
class="js-loading-button-icon"
/>
diff --git a/app/assets/javascripts/vue_shared/components/markdown/field.vue b/app/assets/javascripts/vue_shared/components/markdown/field.vue
index 0e05f4a4622..f954b8eb4f4 100644
--- a/app/assets/javascripts/vue_shared/components/markdown/field.vue
+++ b/app/assets/javascripts/vue_shared/components/markdown/field.vue
@@ -4,21 +4,25 @@ import '~/behaviors/markdown/render_gfm';
import { unescape } from 'lodash';
import { __, sprintf } from '~/locale';
import { stripHtml } from '~/lib/utils/text_utility';
-import Flash from '../../../flash';
-import GLForm from '../../../gl_form';
-import markdownHeader from './header.vue';
-import markdownToolbar from './toolbar.vue';
-import icon from '../icon.vue';
+import Flash from '~/flash';
+import GLForm from '~/gl_form';
+import MarkdownHeader from './header.vue';
+import MarkdownToolbar from './toolbar.vue';
+import Icon from '../icon.vue';
+import GlMentions from '~/vue_shared/components/gl_mentions.vue';
import Suggestions from '~/vue_shared/components/markdown/suggestions.vue';
+import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import axios from '~/lib/utils/axios_utils';
export default {
components: {
- markdownHeader,
- markdownToolbar,
- icon,
+ GlMentions,
+ MarkdownHeader,
+ MarkdownToolbar,
+ Icon,
Suggestions,
},
+ mixins: [glFeatureFlagsMixin()],
props: {
isSubmitting: {
type: Boolean,
@@ -159,12 +163,10 @@ export default {
},
},
mounted() {
- /*
- GLForm class handles all the toolbar buttons
- */
+ // GLForm class handles all the toolbar buttons
return new GLForm($(this.$refs['gl-form']), {
emojis: this.enableAutocomplete,
- members: this.enableAutocomplete,
+ members: this.enableAutocomplete && !this.glFeatures.tributeAutocomplete,
issues: this.enableAutocomplete,
mergeRequests: this.enableAutocomplete,
epics: this.enableAutocomplete,
@@ -229,7 +231,7 @@ export default {
<template>
<div
ref="gl-form"
- :class="{ 'prepend-top-default append-bottom-default': addSpacingClasses }"
+ :class="{ 'gl-mt-3 gl-mb-3': addSpacingClasses }"
class="js-vue-markdown-field md-area position-relative"
>
<markdown-header
@@ -243,7 +245,10 @@ export default {
/>
<div v-show="!previewMarkdown" class="md-write-holder">
<div class="zen-backdrop">
- <slot name="textarea"></slot>
+ <gl-mentions v-if="glFeatures.tributeAutocomplete">
+ <slot name="textarea"></slot>
+ </gl-mentions>
+ <slot v-else name="textarea"></slot>
<a
class="zen-control zen-control-leave js-zen-leave gl-text-gray-700"
href="#"
diff --git a/app/assets/javascripts/vue_shared/components/markdown/header.vue b/app/assets/javascripts/vue_shared/components/markdown/header.vue
index aa1abb5adb6..049f5e71849 100644
--- a/app/assets/javascripts/vue_shared/components/markdown/header.vue
+++ b/app/assets/javascripts/vue_shared/components/markdown/header.vue
@@ -89,14 +89,13 @@ export default {
<div class="md-header">
<ul class="nav-links clearfix">
<li :class="{ active: !previewMarkdown }" class="md-header-tab">
- <button class="js-write-link" tabindex="-1" type="button" @click="writeMarkdownTab($event)">
+ <button class="js-write-link" type="button" @click="writeMarkdownTab($event)">
{{ __('Write') }}
</button>
</li>
<li :class="{ active: previewMarkdown }" class="md-header-tab">
<button
class="js-preview-link js-md-preview-button"
- tabindex="-1"
type="button"
@click="previewMarkdownTab($event)"
>
diff --git a/app/assets/javascripts/vue_shared/components/markdown/suggestion_diff.vue b/app/assets/javascripts/vue_shared/components/markdown/suggestion_diff.vue
index 6dac448d5de..13c42d35b04 100644
--- a/app/assets/javascripts/vue_shared/components/markdown/suggestion_diff.vue
+++ b/app/assets/javascripts/vue_shared/components/markdown/suggestion_diff.vue
@@ -68,6 +68,7 @@ export default {
:is-applying-batch="suggestion.is_applying_batch"
:batch-suggestions-count="batchSuggestionsCount"
:help-page-path="helpPagePath"
+ :inapplicable-reason="suggestion.inapplicable_reason"
@apply="applySuggestion"
@applyBatch="applySuggestionBatch"
@addToBatch="addSuggestionToBatch"
diff --git a/app/assets/javascripts/vue_shared/components/markdown/suggestion_diff_header.vue b/app/assets/javascripts/vue_shared/components/markdown/suggestion_diff_header.vue
index e26ff51e01e..4de80e9b4c2 100644
--- a/app/assets/javascripts/vue_shared/components/markdown/suggestion_diff_header.vue
+++ b/app/assets/javascripts/vue_shared/components/markdown/suggestion_diff_header.vue
@@ -38,6 +38,11 @@ export default {
type: String,
required: true,
},
+ inapplicableReason: {
+ type: String,
+ required: false,
+ default: null,
+ },
},
data() {
return {
@@ -52,14 +57,7 @@ export default {
return this.isApplyingSingle || this.isApplyingBatch;
},
tooltipMessage() {
- return this.canApply
- ? __('This also resolves the discussion')
- : __("Can't apply as this line has changed or the suggestion already matches its content.");
- },
- tooltipMessageBatch() {
- return !this.canBeBatched
- ? __("Suggestions that change line count can't be added to batches, yet.")
- : this.tooltipMessage;
+ return this.canApply ? __('This also resolves this thread') : this.inapplicableReason;
},
isDisableButton() {
return this.isApplying || !this.canApply;
@@ -129,15 +127,14 @@ export default {
</gl-deprecated-button>
</div>
<div v-else class="d-flex align-items-center">
- <span v-if="canBeBatched" v-gl-tooltip.viewport="tooltipMessageBatch" tabindex="0">
- <gl-deprecated-button
- class="btn-inverted js-add-to-batch-btn btn-grouped"
- :disabled="isDisableButton"
- @click="addSuggestionToBatch"
- >
- {{ __('Add suggestion to batch') }}
- </gl-deprecated-button>
- </span>
+ <gl-deprecated-button
+ v-if="canBeBatched && !isDisableButton"
+ class="btn-inverted js-add-to-batch-btn btn-grouped"
+ :disabled="isDisableButton"
+ @click="addSuggestionToBatch"
+ >
+ {{ __('Add suggestion to batch') }}
+ </gl-deprecated-button>
<span v-gl-tooltip.viewport="tooltipMessage" tabindex="0">
<gl-deprecated-button
class="btn-inverted js-apply-btn btn-grouped"
diff --git a/app/assets/javascripts/vue_shared/components/markdown/toolbar.vue b/app/assets/javascripts/vue_shared/components/markdown/toolbar.vue
index 330785c9319..5d47aed9643 100644
--- a/app/assets/javascripts/vue_shared/components/markdown/toolbar.vue
+++ b/app/assets/javascripts/vue_shared/components/markdown/toolbar.vue
@@ -61,7 +61,7 @@ export default {
<span v-if="canAttachFile" class="uploading-container">
<span class="uploading-progress-container hide">
<template>
- <gl-icon name="media" :size="16" />
+ <gl-icon name="media" :size="16" class="gl-vertical-align-text-bottom" />
</template>
<span class="attaching-file-message"></span>
<!-- eslint-disable-next-line @gitlab/vue-require-i18n-strings -->
@@ -71,7 +71,7 @@ export default {
<span class="uploading-error-container hide">
<span class="uploading-error-icon">
<template>
- <gl-icon name="media" :size="16" />
+ <gl-icon name="media" :size="16" class="gl-vertical-align-text-bottom" />
</template>
</span>
<span class="uploading-error-message"></span>
diff --git a/app/assets/javascripts/vue_shared/components/markdown/toolbar_button.vue b/app/assets/javascripts/vue_shared/components/markdown/toolbar_button.vue
index 94f78c0c085..f37dd9e171c 100644
--- a/app/assets/javascripts/vue_shared/components/markdown/toolbar_button.vue
+++ b/app/assets/javascripts/vue_shared/components/markdown/toolbar_button.vue
@@ -64,7 +64,6 @@ export default {
:aria-label="buttonTitle"
type="button"
class="toolbar-btn js-md"
- tabindex="-1"
data-container="body"
@click="() => $emit('click')"
>
diff --git a/app/assets/javascripts/vue_shared/components/notes/noteable_warning.vue b/app/assets/javascripts/vue_shared/components/notes/noteable_warning.vue
new file mode 100644
index 00000000000..f986b105f20
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/notes/noteable_warning.vue
@@ -0,0 +1,113 @@
+<script>
+import { GlLink } from '@gitlab/ui';
+import { escape } from 'lodash';
+import { __, sprintf } from '~/locale';
+import icon from '../icon.vue';
+
+function buildDocsLinkStart(path) {
+ return `<a href="${escape(path)}" target="_blank" rel="noopener noreferrer">`;
+}
+
+const NoteableTypeText = {
+ Issue: __('issue'),
+ Epic: __('epic'),
+ MergeRequest: __('merge request'),
+};
+
+export default {
+ components: {
+ icon,
+ GlLink,
+ },
+ props: {
+ isLocked: {
+ type: Boolean,
+ default: false,
+ required: false,
+ },
+ isConfidential: {
+ type: Boolean,
+ default: false,
+ required: false,
+ },
+ noteableType: {
+ type: String,
+ required: false,
+ // eslint-disable-next-line @gitlab/require-i18n-strings
+ default: 'Issue',
+ },
+ lockedNoteableDocsPath: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ confidentialNoteableDocsPath: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ },
+ computed: {
+ warningIcon() {
+ if (this.isConfidential) return 'eye-slash';
+ if (this.isLocked) return 'lock';
+
+ return '';
+ },
+ isLockedAndConfidential() {
+ return this.isConfidential && this.isLocked;
+ },
+ noteableTypeText() {
+ return NoteableTypeText[this.noteableType];
+ },
+ confidentialAndLockedDiscussionText() {
+ return sprintf(
+ __(
+ 'This %{noteableTypeText} is %{confidentialLinkStart}confidential%{linkEnd} and %{lockedLinkStart}locked%{linkEnd}.',
+ ),
+ {
+ noteableTypeText: this.noteableTypeText,
+ confidentialLinkStart: buildDocsLinkStart(this.confidentialNoteableDocsPath),
+ lockedLinkStart: buildDocsLinkStart(this.lockedNoteableDocsPath),
+ linkEnd: '</a>',
+ },
+ false,
+ );
+ },
+ confidentialContextText() {
+ return sprintf(__('This is a confidential %{noteableTypeText}.'), {
+ noteableTypeText: this.noteableTypeText,
+ });
+ },
+ lockedContextText() {
+ return sprintf(__('This %{noteableTypeText} is locked.'), {
+ noteableTypeText: this.noteableTypeText,
+ });
+ },
+ },
+};
+</script>
+<template>
+ <div class="issuable-note-warning">
+ <icon v-if="!isLockedAndConfidential" :name="warningIcon" :size="16" class="icon inline" />
+
+ <span v-if="isLockedAndConfidential" ref="lockedAndConfidential">
+ <span v-html="confidentialAndLockedDiscussionText"></span>
+ {{
+ __("People without permission will never get a notification and won't be able to comment.")
+ }}
+ </span>
+
+ <span v-else-if="isConfidential" ref="confidential">
+ {{ confidentialContextText }}
+ {{ __('People without permission will never get a notification.') }}
+ <gl-link :href="confidentialNoteableDocsPath" target="_blank">{{ __('Learn more') }}</gl-link>
+ </span>
+
+ <span v-else-if="isLocked" ref="locked">
+ {{ lockedContextText }}
+ {{ __('Only project members can comment.') }}
+ <gl-link :href="lockedNoteableDocsPath" target="_blank">{{ __('Learn more') }}</gl-link>
+ </span>
+ </div>
+</template>
diff --git a/app/assets/javascripts/vue_shared/components/notes/system_note.vue b/app/assets/javascripts/vue_shared/components/notes/system_note.vue
index b6271a95008..fe57d4f29ca 100644
--- a/app/assets/javascripts/vue_shared/components/notes/system_note.vue
+++ b/app/assets/javascripts/vue_shared/components/notes/system_note.vue
@@ -122,7 +122,7 @@ export default {
></div>
<div v-if="hasMoreCommits" class="flex-list">
<div class="system-note-commit-list-toggler flex-row" @click="expanded = !expanded">
- <icon :name="toggleIcon" :size="8" class="append-right-5" />
+ <icon :name="toggleIcon" :size="8" class="gl-mr-2" />
<span>{{ __('Toggle commit list') }}</span>
</div>
</div>
diff --git a/app/assets/javascripts/vue_shared/components/pagination/table_pagination.vue b/app/assets/javascripts/vue_shared/components/pagination/table_pagination.vue
index 29a4a90a59f..5f2a66ee0b7 100644
--- a/app/assets/javascripts/vue_shared/components/pagination/table_pagination.vue
+++ b/app/assets/javascripts/vue_shared/components/pagination/table_pagination.vue
@@ -20,7 +20,7 @@ export default {
Here is an example `change` method:
change(pagenum) {
- gl.utils.visitUrl(`?page=${pagenum}`);
+ visitUrl(`?page=${pagenum}`);
},
*/
change: {
@@ -64,7 +64,7 @@ export default {
<template>
<gl-pagination
v-if="showPagination"
- class="justify-content-center prepend-top-default"
+ class="justify-content-center gl-mt-3"
v-bind="$attrs"
:value="pageInfo.page"
:per-page="pageInfo.perPage"
diff --git a/app/assets/javascripts/vue_shared/components/project_selector/project_list_item.vue b/app/assets/javascripts/vue_shared/components/project_selector/project_list_item.vue
index 3d52f4176db..e053a9ddaa6 100644
--- a/app/assets/javascripts/vue_shared/components/project_selector/project_list_item.vue
+++ b/app/assets/javascripts/vue_shared/components/project_selector/project_list_item.vue
@@ -8,30 +8,25 @@ import { truncateNamespace } from '~/lib/utils/text_utility';
export default {
name: 'ProjectListItem',
- components: {
- Icon,
- ProjectAvatar,
- GlDeprecatedButton,
- },
+ components: { Icon, ProjectAvatar, GlDeprecatedButton },
props: {
project: {
type: Object,
required: true,
- validator: p => Number.isFinite(p.id) && isString(p.name) && isString(p.name_with_namespace),
- },
- selected: {
- type: Boolean,
- required: true,
- },
- matcher: {
- type: String,
- required: false,
- default: '',
+ validator: p =>
+ (Number.isFinite(p.id) || isString(p.id)) &&
+ isString(p.name) &&
+ (isString(p.name_with_namespace) || isString(p.nameWithNamespace)),
},
+ selected: { type: Boolean, required: true },
+ matcher: { type: String, required: false, default: '' },
},
computed: {
+ projectNameWithNamespace() {
+ return this.project.nameWithNamespace || this.project.name_with_namespace;
+ },
truncatedNamespace() {
- return truncateNamespace(this.project.name_with_namespace);
+ return truncateNamespace(this.projectNameWithNamespace);
},
highlightedProjectName() {
return highlight(this.project.name, this.matcher);
@@ -50,7 +45,7 @@ export default {
@click="onClick"
>
<icon
- class="prepend-left-10 append-right-10 flex-shrink-0 position-top-0 js-selected-icon"
+ class="gl-ml-3 gl-mr-3 flex-shrink-0 position-top-0 js-selected-icon"
:class="{ 'js-selected visible': selected, 'js-unselected invisible': !selected }"
name="mobile-issue-close"
/>
@@ -58,7 +53,7 @@ export default {
<div class="d-flex flex-wrap project-namespace-name-container">
<div
v-if="truncatedNamespace"
- :title="project.name_with_namespace"
+ :title="projectNameWithNamespace"
class="text-secondary text-truncate js-project-namespace"
>
{{ truncatedNamespace }}
diff --git a/app/assets/javascripts/vue_shared/components/project_selector/project_selector.vue b/app/assets/javascripts/vue_shared/components/project_selector/project_selector.vue
index 15a5ce85046..0b91588a006 100644
--- a/app/assets/javascripts/vue_shared/components/project_selector/project_selector.vue
+++ b/app/assets/javascripts/vue_shared/components/project_selector/project_selector.vue
@@ -41,7 +41,8 @@ export default {
},
totalResults: {
type: Number,
- required: true,
+ required: false,
+ default: 0,
},
},
data() {
@@ -87,6 +88,7 @@ export default {
type="search"
class="mb-3"
autofocus
+ data-qa-selector="project_search_field"
@input="onInput"
/>
<div class="d-flex flex-column">
@@ -106,6 +108,7 @@ export default {
:project="project"
:matcher="searchQuery"
class="js-project-list-item"
+ data-qa-selector="project_list_item"
@click="projectClicked(project)"
/>
</div>
diff --git a/app/assets/javascripts/vue_shared/components/remove_member_modal.vue b/app/assets/javascripts/vue_shared/components/remove_member_modal.vue
new file mode 100644
index 00000000000..88d1b15aee3
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/remove_member_modal.vue
@@ -0,0 +1,78 @@
+<script>
+import { GlFormCheckbox, GlModal } from '@gitlab/ui';
+import { parseBoolean } from '~/lib/utils/common_utils';
+import csrf from '~/lib/utils/csrf';
+import { __ } from '~/locale';
+
+export default {
+ actionCancel: {
+ text: __('Cancel'),
+ },
+ csrf,
+ components: {
+ GlFormCheckbox,
+ GlModal,
+ },
+ data() {
+ return {
+ modalData: {},
+ };
+ },
+ computed: {
+ isAccessRequest() {
+ return parseBoolean(this.modalData.isAccessRequest);
+ },
+ actionText() {
+ return this.isAccessRequest ? __('Deny access request') : __('Remove member');
+ },
+ actionPrimary() {
+ return {
+ text: this.actionText,
+ attributes: {
+ variant: 'danger',
+ },
+ };
+ },
+ },
+ mounted() {
+ document.addEventListener('click', this.handleClick);
+ },
+ beforeDestroy() {
+ document.removeEventListener('click', this.handleClick);
+ },
+ methods: {
+ handleClick(event) {
+ const removeButton = event.target.closest('.js-remove-member-button');
+ if (removeButton) {
+ this.modalData = removeButton.dataset;
+ this.$refs.modal.show();
+ }
+ },
+ submitForm() {
+ this.$refs.form.submit();
+ },
+ },
+};
+</script>
+
+<template>
+ <gl-modal
+ ref="modal"
+ modal-id="remove-member-modal"
+ :action-cancel="$options.actionCancel"
+ :action-primary="actionPrimary"
+ :title="actionText"
+ data-qa-selector="remove_member_modal_content"
+ @primary="submitForm"
+ >
+ <form ref="form" :action="modalData.memberPath" method="post">
+ <p data-testid="modal-message">{{ modalData.message }}</p>
+
+ <input ref="method" type="hidden" name="_method" value="delete" />
+ <input :value="$options.csrf.token" type="hidden" name="authenticity_token" />
+ <gl-form-checkbox v-if="!isAccessRequest" name="unassign_issuables">
+ {{ __('Also unassign this user from related issues and merge requests') }}
+ </gl-form-checkbox>
+ </form>
+ </gl-modal>
+</template>
diff --git a/app/assets/javascripts/vue_shared/components/resizable_chart/constants.js b/app/assets/javascripts/vue_shared/components/resizable_chart/constants.js
new file mode 100644
index 00000000000..edc5ffb7b77
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/resizable_chart/constants.js
@@ -0,0 +1,6 @@
+export const DEFAULT_RX = 0.4;
+export const DEFAULT_BAR_WIDTH = 6;
+export const DEFAULT_LABEL_WIDTH = 4;
+export const DEFAULT_LABEL_HEIGHT = 5;
+export const BAR_HEIGHTS = [5, 7, 9, 14, 21, 35, 50, 80];
+export const GRID_YS = [30, 60, 90];
diff --git a/app/assets/javascripts/vue_shared/components/resizable_chart/skeleton_loader.vue b/app/assets/javascripts/vue_shared/components/resizable_chart/skeleton_loader.vue
new file mode 100644
index 00000000000..306fa61780f
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/resizable_chart/skeleton_loader.vue
@@ -0,0 +1,95 @@
+<script>
+import { GlSkeletonLoader } from '@gitlab/ui';
+
+import {
+ DEFAULT_RX,
+ DEFAULT_BAR_WIDTH,
+ DEFAULT_LABEL_WIDTH,
+ DEFAULT_LABEL_HEIGHT,
+ BAR_HEIGHTS,
+ GRID_YS,
+} from './constants';
+
+export default {
+ components: {
+ GlSkeletonLoader,
+ },
+ props: {
+ barWidth: {
+ type: Number,
+ default: DEFAULT_BAR_WIDTH,
+ required: false,
+ },
+ labelWidth: {
+ type: Number,
+ default: DEFAULT_LABEL_WIDTH,
+ required: false,
+ },
+ labelHeight: {
+ type: Number,
+ default: DEFAULT_LABEL_HEIGHT,
+ required: false,
+ },
+ rx: {
+ type: Number,
+ default: DEFAULT_RX,
+ required: false,
+ },
+ // skeleton-loader will generate a unique key if not defined
+ uniqueKey: {
+ type: String,
+ default: undefined,
+ required: false,
+ },
+ },
+ computed: {
+ labelCentering() {
+ return (this.barWidth - this.labelWidth) / 2;
+ },
+ },
+ methods: {
+ getBarXPosition(index) {
+ const numberOfBars = this.$options.BAR_HEIGHTS.length;
+ const numberOfSpaces = numberOfBars + 1;
+ const spaceBetweenBars = (100 - numberOfSpaces * this.barWidth) / numberOfBars;
+
+ return (0.5 + index) * (this.barWidth + spaceBetweenBars);
+ },
+ },
+ BAR_HEIGHTS,
+ GRID_YS,
+};
+</script>
+<template>
+ <gl-skeleton-loader :unique-key="uniqueKey">
+ <rect
+ v-for="(y, index) in $options.GRID_YS"
+ :key="`grid-${index}`"
+ data-testid="skeleton-chart-grid"
+ x="0"
+ :y="`${y}%`"
+ width="100%"
+ height="1px"
+ />
+ <rect
+ v-for="(height, index) in $options.BAR_HEIGHTS"
+ :key="`bar-${index}`"
+ data-testid="skeleton-chart-bar"
+ :x="`${getBarXPosition(index)}%`"
+ :y="`${90 - height}%`"
+ :width="`${barWidth}%`"
+ :height="`${height}%`"
+ :rx="`${rx}%`"
+ />
+ <rect
+ v-for="(height, index) in $options.BAR_HEIGHTS"
+ :key="`label-${index}`"
+ data-testid="skeleton-chart-label"
+ :x="`${labelCentering + getBarXPosition(index)}%`"
+ :y="`${100 - labelHeight}%`"
+ :width="`${labelWidth}%`"
+ :height="`${labelHeight}%`"
+ :rx="`${rx}%`"
+ />
+ </gl-skeleton-loader>
+</template>
diff --git a/app/assets/javascripts/vue_shared/components/rich_content_editor/constants.js b/app/assets/javascripts/vue_shared/components/rich_content_editor/constants.js
index 1566c2c784b..dd1da847001 100644
--- a/app/assets/javascripts/vue_shared/components/rich_content_editor/constants.js
+++ b/app/assets/javascripts/vue_shared/components/rich_content_editor/constants.js
@@ -1,5 +1,6 @@
import { __ } from '~/locale';
-import { generateToolbarItem } from './editor_service';
+import { generateToolbarItem } from './services/editor_service';
+import buildCustomHTMLRenderer from './services/build_custom_renderer';
export const CUSTOM_EVENTS = {
openAddImageModal: 'gl_openAddImageModal',
@@ -31,6 +32,7 @@ const TOOLBAR_ITEM_CONFIGS = [
export const EDITOR_OPTIONS = {
toolbarItems: TOOLBAR_ITEM_CONFIGS.map(config => generateToolbarItem(config)),
+ customHTMLRenderer: buildCustomHTMLRenderer(),
};
export const EDITOR_TYPES = {
@@ -41,3 +43,7 @@ export const EDITOR_TYPES = {
export const EDITOR_HEIGHT = '100%';
export const EDITOR_PREVIEW_STYLE = 'horizontal';
+
+export const IMAGE_TABS = { UPLOAD_TAB: 0, URL_TAB: 1 };
+
+export const MAX_FILE_SIZE = 2097152; // 2Mb
diff --git a/app/assets/javascripts/vue_shared/components/rich_content_editor/editor_service.js b/app/assets/javascripts/vue_shared/components/rich_content_editor/editor_service.js
deleted file mode 100644
index 278cd50a947..00000000000
--- a/app/assets/javascripts/vue_shared/components/rich_content_editor/editor_service.js
+++ /dev/null
@@ -1,42 +0,0 @@
-import Vue from 'vue';
-import ToolbarItem from './toolbar_item.vue';
-
-const buildWrapper = propsData => {
- const instance = new Vue({
- render(createElement) {
- return createElement(ToolbarItem, propsData);
- },
- });
-
- instance.$mount();
- return instance.$el;
-};
-
-export const generateToolbarItem = config => {
- const { icon, classes, event, command, tooltip, isDivider } = config;
-
- if (isDivider) {
- return 'divider';
- }
-
- return {
- type: 'button',
- options: {
- el: buildWrapper({ props: { icon, tooltip }, class: classes }),
- event,
- command,
- },
- };
-};
-
-export const addCustomEventListener = (editorApi, event, handler) => {
- editorApi.eventManager.addEventType(event);
- editorApi.eventManager.listen(event, handler);
-};
-
-export const removeCustomEventListener = (editorApi, event, handler) =>
- editorApi.eventManager.removeEventHandler(event, handler);
-
-export const addImage = ({ editor }, image) => editor.exec('AddImage', image);
-
-export const getMarkdown = editorInstance => editorInstance.invoke('getMarkdown');
diff --git a/app/assets/javascripts/vue_shared/components/rich_content_editor/modals/add_image/add_image_modal.vue b/app/assets/javascripts/vue_shared/components/rich_content_editor/modals/add_image/add_image_modal.vue
new file mode 100644
index 00000000000..0a444b2295d
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/rich_content_editor/modals/add_image/add_image_modal.vue
@@ -0,0 +1,147 @@
+<script>
+import { isSafeURL } from '~/lib/utils/url_utility';
+import { GlModal, GlFormGroup, GlFormInput, GlTabs, GlTab } from '@gitlab/ui';
+import { __ } from '~/locale';
+import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
+import { IMAGE_TABS } from '../../constants';
+import UploadImageTab from './upload_image_tab.vue';
+
+export default {
+ components: {
+ UploadImageTab,
+ GlModal,
+ GlFormGroup,
+ GlFormInput,
+ GlTabs,
+ GlTab,
+ },
+ mixins: [glFeatureFlagMixin()],
+ props: {
+ imageRoot: {
+ type: String,
+ required: true,
+ },
+ },
+ data() {
+ return {
+ file: null,
+ urlError: null,
+ imageUrl: null,
+ description: null,
+ tabIndex: IMAGE_TABS.UPLOAD_TAB,
+ uploadImageTab: null,
+ };
+ },
+ modalTitle: __('Image Details'),
+ okTitle: __('Insert'),
+ urlTabTitle: __('By URL'),
+ urlLabel: __('Image URL'),
+ descriptionLabel: __('Description'),
+ uploadTabTitle: __('Upload file'),
+ computed: {
+ altText() {
+ return this.description;
+ },
+ },
+ methods: {
+ show() {
+ this.file = null;
+ this.urlError = null;
+ this.imageUrl = null;
+ this.description = null;
+ this.tabIndex = IMAGE_TABS.UPLOAD_TAB;
+
+ this.$refs.modal.show();
+ },
+ onOk(event) {
+ if (this.glFeatures.sseImageUploads && this.tabIndex === IMAGE_TABS.UPLOAD_TAB) {
+ this.submitFile(event);
+ return;
+ }
+ this.submitURL(event);
+ },
+ setFile(file) {
+ this.file = file;
+ },
+ submitFile(event) {
+ const { file, altText } = this;
+ const { uploadImageTab } = this.$refs;
+
+ uploadImageTab.validateFile();
+
+ if (uploadImageTab.fileError) {
+ event.preventDefault();
+ return;
+ }
+
+ const imageUrl = `${this.imageRoot}${file.name}`;
+
+ this.$emit('addImage', { imageUrl, file, altText: altText || file.name });
+ },
+ submitURL(event) {
+ if (!this.validateUrl()) {
+ event.preventDefault();
+ return;
+ }
+
+ const { imageUrl, altText } = this;
+
+ this.$emit('addImage', { imageUrl, altText: altText || imageUrl });
+ },
+ validateUrl() {
+ if (!isSafeURL(this.imageUrl)) {
+ this.urlError = __('Please provide a valid URL');
+ this.$refs.urlInput.$el.focus();
+ return false;
+ }
+
+ return true;
+ },
+ },
+};
+</script>
+<template>
+ <gl-modal
+ ref="modal"
+ modal-id="add-image-modal"
+ :title="$options.modalTitle"
+ :ok-title="$options.okTitle"
+ @ok="onOk"
+ >
+ <gl-tabs v-if="glFeatures.sseImageUploads" v-model="tabIndex">
+ <!-- Upload file Tab -->
+ <gl-tab :title="$options.uploadTabTitle">
+ <upload-image-tab ref="uploadImageTab" @input="setFile" />
+ </gl-tab>
+
+ <!-- By URL Tab -->
+ <gl-tab :title="$options.urlTabTitle">
+ <gl-form-group
+ class="gl-mt-5 gl-mb-3"
+ :label="$options.urlLabel"
+ label-for="url-input"
+ :state="!Boolean(urlError)"
+ :invalid-feedback="urlError"
+ >
+ <gl-form-input id="url-input" ref="urlInput" v-model="imageUrl" />
+ </gl-form-group>
+ </gl-tab>
+ </gl-tabs>
+
+ <gl-form-group
+ v-else
+ class="gl-mt-5 gl-mb-3"
+ :label="$options.urlLabel"
+ label-for="url-input"
+ :state="!Boolean(urlError)"
+ :invalid-feedback="urlError"
+ >
+ <gl-form-input id="url-input" ref="urlInput" v-model="imageUrl" />
+ </gl-form-group>
+
+ <!-- Description Input -->
+ <gl-form-group :label="$options.descriptionLabel" label-for="description-input">
+ <gl-form-input id="description-input" ref="descriptionInput" v-model="description" />
+ </gl-form-group>
+ </gl-modal>
+</template>
diff --git a/app/assets/javascripts/vue_shared/components/rich_content_editor/modals/add_image/upload_image_tab.vue b/app/assets/javascripts/vue_shared/components/rich_content_editor/modals/add_image/upload_image_tab.vue
new file mode 100644
index 00000000000..739f8b502c9
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/rich_content_editor/modals/add_image/upload_image_tab.vue
@@ -0,0 +1,56 @@
+<script>
+import { __ } from '~/locale';
+import { GlFormGroup } from '@gitlab/ui';
+import { MAX_FILE_SIZE } from '../../constants';
+
+export default {
+ components: {
+ GlFormGroup,
+ },
+ data() {
+ return {
+ file: null,
+ fileError: null,
+ };
+ },
+ fileLabel: __('Select file'),
+ methods: {
+ onInput(event) {
+ [this.file] = event.target.files;
+
+ this.validateFile();
+
+ if (!this.fileError) {
+ this.$emit('input', this.file);
+ }
+ },
+ validateFile() {
+ this.fileError = null;
+
+ if (!this.file) {
+ this.fileError = __('Please choose a file');
+ } else if (this.file.size > MAX_FILE_SIZE) {
+ this.fileError = __('Maximum file size is 2MB. Please select a smaller file.');
+ }
+ },
+ },
+};
+</script>
+<template>
+ <gl-form-group
+ class="gl-mt-5 gl-mb-3"
+ :label="$options.fileLabel"
+ label-for="file-input"
+ :state="!Boolean(fileError)"
+ :invalid-feedback="fileError"
+ >
+ <input
+ id="file-input"
+ ref="fileInput"
+ class="gl-mt-3 gl-mb-2"
+ type="file"
+ accept="image/*"
+ @input="onInput"
+ />
+ </gl-form-group>
+</template>
diff --git a/app/assets/javascripts/vue_shared/components/rich_content_editor/modals/add_image_modal.vue b/app/assets/javascripts/vue_shared/components/rich_content_editor/modals/add_image_modal.vue
deleted file mode 100644
index 40063065926..00000000000
--- a/app/assets/javascripts/vue_shared/components/rich_content_editor/modals/add_image_modal.vue
+++ /dev/null
@@ -1,74 +0,0 @@
-<script>
-import { isSafeURL } from '~/lib/utils/url_utility';
-import { GlModal, GlFormGroup, GlFormInput } from '@gitlab/ui';
-import { __ } from '~/locale';
-
-export default {
- components: {
- GlModal,
- GlFormGroup,
- GlFormInput,
- },
- data() {
- return {
- error: null,
- imageUrl: null,
- altText: null,
- modalTitle: __('Image Details'),
- okTitle: __('Insert'),
- urlLabel: __('Image URL'),
- descriptionLabel: __('Description'),
- };
- },
- methods: {
- show() {
- this.error = null;
- this.imageUrl = null;
- this.altText = null;
-
- this.$refs.modal.show();
- },
- onOk(event) {
- if (!this.isValid()) {
- event.preventDefault();
- return;
- }
-
- const { imageUrl, altText } = this;
-
- this.$emit('addImage', { imageUrl, altText: altText || __('image') });
- },
- isValid() {
- if (!isSafeURL(this.imageUrl)) {
- this.error = __('Please provide a valid URL');
- this.$refs.urlInput.$el.focus();
- return false;
- }
-
- return true;
- },
- },
-};
-</script>
-<template>
- <gl-modal
- ref="modal"
- modal-id="add-image-modal"
- :title="modalTitle"
- :ok-title="okTitle"
- @ok="onOk"
- >
- <gl-form-group
- :label="urlLabel"
- label-for="url-input"
- :state="!Boolean(error)"
- :invalid-feedback="error"
- >
- <gl-form-input id="url-input" ref="urlInput" v-model="imageUrl" />
- </gl-form-group>
-
- <gl-form-group :label="descriptionLabel" label-for="description-input">
- <gl-form-input id="description-input" ref="descriptionInput" v-model="altText" />
- </gl-form-group>
- </gl-modal>
-</template>
diff --git a/app/assets/javascripts/vue_shared/components/rich_content_editor/rich_content_editor.vue b/app/assets/javascripts/vue_shared/components/rich_content_editor/rich_content_editor.vue
index 5c310fc059b..baeb98bec75 100644
--- a/app/assets/javascripts/vue_shared/components/rich_content_editor/rich_content_editor.vue
+++ b/app/assets/javascripts/vue_shared/components/rich_content_editor/rich_content_editor.vue
@@ -2,7 +2,7 @@
import 'codemirror/lib/codemirror.css';
import '@toast-ui/editor/dist/toastui-editor.css';
-import AddImageModal from './modals/add_image_modal.vue';
+import AddImageModal from './modals/add_image/add_image_modal.vue';
import {
EDITOR_OPTIONS,
EDITOR_TYPES,
@@ -12,11 +12,12 @@ import {
} from './constants';
import {
+ registerHTMLToMarkdownRenderer,
addCustomEventListener,
removeCustomEventListener,
addImage,
getMarkdown,
-} from './editor_service';
+} from './services/editor_service';
export default {
components: {
@@ -27,7 +28,7 @@ export default {
AddImageModal,
},
props: {
- value: {
+ content: {
type: String,
required: true,
},
@@ -51,6 +52,11 @@ export default {
required: false,
default: EDITOR_PREVIEW_STYLE,
},
+ imageRoot: {
+ type: String,
+ required: true,
+ validator: prop => prop.endsWith('/'),
+ },
},
data() {
return {
@@ -66,51 +72,48 @@ export default {
return this.$refs.editor;
},
},
- watch: {
- value(newVal) {
- const isSameMode = this.previousMode === this.editorApi.currentMode;
- if (!isSameMode) {
- /*
- The ToastUI Editor consumes its content via the `initial-value` prop and then internally
- manages changes. If we desire the `v-model` to work as expected, we need to manually call
- `setMarkdown`. However, if we do this in each v-model change we'll continually prevent
- the editor from internally managing changes. Thus we use the `previousMode` flag as
- confirmation to actually update its internals. This is initially designed so that front
- matter is excluded from editing in wysiwyg mode, but included in markdown mode.
- */
- this.editorInstance.invoke('setMarkdown', newVal);
- this.previousMode = this.editorApi.currentMode;
- }
- },
- },
beforeDestroy() {
- removeCustomEventListener(
- this.editorApi,
- CUSTOM_EVENTS.openAddImageModal,
- this.onOpenAddImageModal,
- );
-
- this.editorApi.eventManager.removeEventHandler('changeMode', this.onChangeMode);
+ this.removeListeners();
},
methods: {
+ addListeners(editorApi) {
+ addCustomEventListener(editorApi, CUSTOM_EVENTS.openAddImageModal, this.onOpenAddImageModal);
+
+ editorApi.eventManager.listen('changeMode', this.onChangeMode);
+ },
+ removeListeners() {
+ removeCustomEventListener(
+ this.editorApi,
+ CUSTOM_EVENTS.openAddImageModal,
+ this.onOpenAddImageModal,
+ );
+
+ this.editorApi.eventManager.removeEventHandler('changeMode', this.onChangeMode);
+ },
+ resetInitialValue(newVal) {
+ this.editorInstance.invoke('setMarkdown', newVal);
+ },
onContentChanged() {
this.$emit('input', getMarkdown(this.editorInstance));
},
onLoad(editorApi) {
this.editorApi = editorApi;
- addCustomEventListener(
- this.editorApi,
- CUSTOM_EVENTS.openAddImageModal,
- this.onOpenAddImageModal,
- );
+ registerHTMLToMarkdownRenderer(editorApi);
- this.editorApi.eventManager.listen('changeMode', this.onChangeMode);
+ this.addListeners(editorApi);
},
onOpenAddImageModal() {
this.$refs.addImageModal.show();
},
- onAddImage(image) {
+ onAddImage({ imageUrl, altText, file }) {
+ const image = { imageUrl, altText };
+
+ if (file) {
+ this.$emit('uploadImage', { file, imageUrl });
+ // TODO - ensure that the actual repo URL for the image is used in Markdown mode
+ }
+
addImage(this.editorInstance, image);
},
onChangeMode(newMode) {
@@ -123,7 +126,7 @@ export default {
<div>
<toast-editor
ref="editor"
- :initial-value="value"
+ :initial-value="content"
:options="editorOptions"
:preview-style="previewStyle"
:initial-edit-type="initialEditType"
@@ -131,6 +134,6 @@ export default {
@change="onContentChanged"
@load="onLoad"
/>
- <add-image-modal ref="addImageModal" @addImage="onAddImage" />
+ <add-image-modal ref="addImageModal" :image-root="imageRoot" @addImage="onAddImage" />
</div>
</template>
diff --git a/app/assets/javascripts/vue_shared/components/rich_content_editor/services/build_custom_renderer.js b/app/assets/javascripts/vue_shared/components/rich_content_editor/services/build_custom_renderer.js
new file mode 100644
index 00000000000..70d29b5b3df
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/rich_content_editor/services/build_custom_renderer.js
@@ -0,0 +1,68 @@
+import renderBlockHtml from './renderers/render_html_block';
+import renderKramdownList from './renderers/render_kramdown_list';
+import renderKramdownText from './renderers/render_kramdown_text';
+import renderIdentifierInstanceText from './renderers/render_identifier_instance_text';
+import renderIdentifierParagraph from './renderers/render_identifier_paragraph';
+import renderEmbeddedRubyText from './renderers/render_embedded_ruby_text';
+import renderFontAwesomeHtmlInline from './renderers/render_font_awesome_html_inline';
+
+const htmlInlineRenderers = [renderFontAwesomeHtmlInline];
+const htmlBlockRenderers = [renderBlockHtml];
+const listRenderers = [renderKramdownList];
+const paragraphRenderers = [renderIdentifierParagraph];
+const textRenderers = [renderKramdownText, renderEmbeddedRubyText, renderIdentifierInstanceText];
+
+const executeRenderer = (renderers, node, context) => {
+ const availableRenderer = renderers.find(renderer => renderer.canRender(node, context));
+
+ return availableRenderer ? availableRenderer.render(node, context) : context.origin();
+};
+
+const buildCustomRendererFunctions = (customRenderers, defaults) => {
+ const customTypes = Object.keys(customRenderers).filter(type => !defaults[type]);
+ const customEntries = customTypes.map(type => {
+ const fn = (node, context) => executeRenderer(customRenderers[type], node, context);
+ return [type, fn];
+ });
+
+ return Object.fromEntries(customEntries);
+};
+
+const buildCustomHTMLRenderer = (
+ customRenderers = { htmlBlock: [], htmlInline: [], list: [], paragraph: [], text: [] },
+) => {
+ const defaults = {
+ htmlBlock(node, context) {
+ const allHtmlBlockRenderers = [...customRenderers.htmlBlock, ...htmlBlockRenderers];
+
+ return executeRenderer(allHtmlBlockRenderers, node, context);
+ },
+ htmlInline(node, context) {
+ const allHtmlInlineRenderers = [...customRenderers.htmlInline, ...htmlInlineRenderers];
+
+ return executeRenderer(allHtmlInlineRenderers, node, context);
+ },
+ list(node, context) {
+ const allListRenderers = [...customRenderers.list, ...listRenderers];
+
+ return executeRenderer(allListRenderers, node, context);
+ },
+ paragraph(node, context) {
+ const allParagraphRenderers = [...customRenderers.paragraph, ...paragraphRenderers];
+
+ return executeRenderer(allParagraphRenderers, node, context);
+ },
+ text(node, context) {
+ const allTextRenderers = [...customRenderers.text, ...textRenderers];
+
+ return executeRenderer(allTextRenderers, node, context);
+ },
+ };
+
+ return {
+ ...buildCustomRendererFunctions(customRenderers, defaults),
+ ...defaults,
+ };
+};
+
+export default buildCustomHTMLRenderer;
diff --git a/app/assets/javascripts/vue_shared/components/rich_content_editor/services/build_html_to_markdown_renderer.js b/app/assets/javascripts/vue_shared/components/rich_content_editor/services/build_html_to_markdown_renderer.js
new file mode 100644
index 00000000000..ed04765c871
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/rich_content_editor/services/build_html_to_markdown_renderer.js
@@ -0,0 +1,53 @@
+import { defaults, repeat } from 'lodash';
+
+const DEFAULTS = {
+ subListIndentSpaces: 4,
+};
+
+const countIndentSpaces = text => {
+ const matches = text.match(/^\s+/m);
+
+ return matches ? matches[0].length : 0;
+};
+
+const buildHTMLToMarkdownRender = (baseRenderer, formattingPreferences = {}) => {
+ const { subListIndentSpaces } = defaults(formattingPreferences, DEFAULTS);
+ // eslint-disable-next-line @gitlab/require-i18n-strings
+ const sublistNode = 'LI OL, LI UL';
+
+ return {
+ TEXT_NODE(node) {
+ return baseRenderer.getSpaceControlled(
+ baseRenderer.trim(baseRenderer.getSpaceCollapsedText(node.nodeValue)),
+ node,
+ );
+ },
+ /*
+ * This converter overwrites the default indented list converter
+ * to allow us to parameterize the number of indent spaces for
+ * sublists.
+ *
+ * See the original implementation in
+ * https://github.com/nhn/tui.editor/blob/master/libs/to-mark/src/renderer.basic.js#L161
+ */
+ [sublistNode](node, subContent) {
+ const baseResult = baseRenderer.convert(node, subContent);
+ // Default to 1 to prevent possible divide by 0
+ const firstLevelIndentSpacesCount = countIndentSpaces(baseResult) || 1;
+ const reindentedList = baseResult
+ .split('\n')
+ .map(line => {
+ const itemIndentSpacesCount = countIndentSpaces(line);
+ const nestingLevel = Math.ceil(itemIndentSpacesCount / firstLevelIndentSpacesCount);
+ const indentSpaces = repeat(' ', subListIndentSpaces * nestingLevel);
+
+ return line.replace(/^ +/, indentSpaces);
+ })
+ .join('\n');
+
+ return reindentedList;
+ },
+ };
+};
+
+export default buildHTMLToMarkdownRender;
diff --git a/app/assets/javascripts/vue_shared/components/rich_content_editor/services/editor_service.js b/app/assets/javascripts/vue_shared/components/rich_content_editor/services/editor_service.js
new file mode 100644
index 00000000000..6436dcaae64
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/rich_content_editor/services/editor_service.js
@@ -0,0 +1,56 @@
+import Vue from 'vue';
+import ToolbarItem from '../toolbar_item.vue';
+import buildHtmlToMarkdownRenderer from './build_html_to_markdown_renderer';
+
+const buildWrapper = propsData => {
+ const instance = new Vue({
+ render(createElement) {
+ return createElement(ToolbarItem, propsData);
+ },
+ });
+
+ instance.$mount();
+ return instance.$el;
+};
+
+export const generateToolbarItem = config => {
+ const { icon, classes, event, command, tooltip, isDivider } = config;
+
+ if (isDivider) {
+ return 'divider';
+ }
+
+ return {
+ type: 'button',
+ options: {
+ el: buildWrapper({ props: { icon, tooltip }, class: classes }),
+ event,
+ command,
+ },
+ };
+};
+
+export const addCustomEventListener = (editorApi, event, handler) => {
+ editorApi.eventManager.addEventType(event);
+ editorApi.eventManager.listen(event, handler);
+};
+
+export const removeCustomEventListener = (editorApi, event, handler) =>
+ editorApi.eventManager.removeEventHandler(event, handler);
+
+export const addImage = ({ editor }, image) => editor.exec('AddImage', image);
+
+export const getMarkdown = editorInstance => editorInstance.invoke('getMarkdown');
+
+/**
+ * This function allow us to extend Toast UI HTML to Markdown renderer. It is
+ * a temporary measure because Toast UI does not provide an API
+ * to achieve this goal.
+ */
+export const registerHTMLToMarkdownRenderer = editorApi => {
+ const { renderer } = editorApi.toMarkOptions;
+
+ Object.assign(editorApi.toMarkOptions, {
+ renderer: renderer.constructor.factory(renderer, buildHtmlToMarkdownRenderer(renderer)),
+ });
+};
diff --git a/app/assets/javascripts/vue_shared/components/rich_content_editor/services/renderers/build_uneditable_token.js b/app/assets/javascripts/vue_shared/components/rich_content_editor/services/renderers/build_uneditable_token.js
new file mode 100644
index 00000000000..d96cadafdbb
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/rich_content_editor/services/renderers/build_uneditable_token.js
@@ -0,0 +1,63 @@
+const buildToken = (type, tagName, props) => {
+ return { type, tagName, ...props };
+};
+
+const TAG_TYPES = {
+ block: 'div',
+ inline: 'a',
+};
+
+// Open helpers (singular and multiple)
+
+const buildUneditableOpenToken = (tagType = TAG_TYPES.block) =>
+ buildToken('openTag', tagType, {
+ attributes: { contenteditable: false },
+ classNames: [
+ 'gl-px-4 gl-py-2 gl-my-5 gl-opacity-5 gl-bg-gray-100 gl-user-select-none gl-cursor-not-allowed',
+ ],
+ });
+
+export const buildUneditableOpenTokens = (token, tagType = TAG_TYPES.block) => {
+ return [buildUneditableOpenToken(tagType), token];
+};
+
+// Close helpers (singular and multiple)
+
+export const buildUneditableCloseToken = (tagType = TAG_TYPES.block) =>
+ buildToken('closeTag', tagType);
+
+export const buildUneditableCloseTokens = (token, tagType = TAG_TYPES.block) => {
+ return [token, buildUneditableCloseToken(tagType)];
+};
+
+// Complete helpers (open plus close)
+
+export const buildTextToken = content => buildToken('text', null, { content });
+
+export const buildUneditableTokens = token => {
+ return [...buildUneditableOpenTokens(token), buildUneditableCloseToken()];
+};
+
+export const buildUneditableInlineTokens = token => {
+ return [
+ ...buildUneditableOpenTokens(token, TAG_TYPES.inline),
+ buildUneditableCloseToken(TAG_TYPES.inline),
+ ];
+};
+
+export const buildUneditableHtmlAsTextTokens = node => {
+ /*
+ Toast UI internally appends ' data-tomark-pass ' attribute flags so it can target certain
+ nested nodes for internal use during Markdown <=> WYSIWYG conversions. In our case, we want
+ to prevent HTML being rendered completely in WYSIWYG mode and thus we use a `text` vs. `html`
+ type when building the token. However, in doing so, we need to strip out the ` data-tomark-pass `
+ to prevent their persistence within the `text` content as the user did not intend these as edits.
+
+ https://github.com/nhn/tui.editor/blob/cc54ec224fc3a4b6e5a2b19a71650959f41adc0e/apps/editor/src/js/convertor.js#L72
+ */
+ const regex = / data-tomark-pass /gm;
+ const content = node.literal.replace(regex, '');
+ const htmlAsTextToken = buildToken('text', null, { content });
+
+ return [buildUneditableOpenToken(), htmlAsTextToken, buildUneditableCloseToken()];
+};
diff --git a/app/assets/javascripts/vue_shared/components/rich_content_editor/services/renderers/render_embedded_ruby_text.js b/app/assets/javascripts/vue_shared/components/rich_content_editor/services/renderers/render_embedded_ruby_text.js
new file mode 100644
index 00000000000..494057fc75b
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/rich_content_editor/services/renderers/render_embedded_ruby_text.js
@@ -0,0 +1,13 @@
+import { buildUneditableTokens } from './build_uneditable_token';
+
+const embeddedRubyRegex = /(^<%.+%>$)/;
+
+const canRender = ({ literal }) => {
+ return embeddedRubyRegex.test(literal);
+};
+
+const render = (_, { origin }) => {
+ return buildUneditableTokens(origin());
+};
+
+export default { canRender, render };
diff --git a/app/assets/javascripts/vue_shared/components/rich_content_editor/services/renderers/render_font_awesome_html_inline.js b/app/assets/javascripts/vue_shared/components/rich_content_editor/services/renderers/render_font_awesome_html_inline.js
new file mode 100644
index 00000000000..572f6e3cf9d
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/rich_content_editor/services/renderers/render_font_awesome_html_inline.js
@@ -0,0 +1,11 @@
+import { buildUneditableInlineTokens } from './build_uneditable_token';
+
+const fontAwesomeRegexOpen = /<i class="fa.+>/;
+
+const canRender = ({ literal }) => {
+ return fontAwesomeRegexOpen.test(literal);
+};
+
+const render = (_, { origin }) => buildUneditableInlineTokens(origin());
+
+export default { canRender, render };
diff --git a/app/assets/javascripts/vue_shared/components/rich_content_editor/services/renderers/render_html_block.js b/app/assets/javascripts/vue_shared/components/rich_content_editor/services/renderers/render_html_block.js
new file mode 100644
index 00000000000..b179ca61dba
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/rich_content_editor/services/renderers/render_html_block.js
@@ -0,0 +1,9 @@
+import { buildUneditableHtmlAsTextTokens } from './build_uneditable_token';
+
+const canRender = ({ type }) => {
+ return type === 'htmlBlock';
+};
+
+const render = node => buildUneditableHtmlAsTextTokens(node);
+
+export default { canRender, render };
diff --git a/app/assets/javascripts/vue_shared/components/rich_content_editor/services/renderers/render_identifier_instance_text.js b/app/assets/javascripts/vue_shared/components/rich_content_editor/services/renderers/render_identifier_instance_text.js
new file mode 100644
index 00000000000..a9c3dfcd728
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/rich_content_editor/services/renderers/render_identifier_instance_text.js
@@ -0,0 +1,40 @@
+import { buildTextToken, buildUneditableInlineTokens } from './build_uneditable_token';
+
+/*
+Use case examples:
+- Majority: two bracket pairs, back-to-back, each with content (including spaces)
+ - `[environment terraform plans][terraform]`
+ - `[an issue labelled `~"master:broken"`][broken-master-issues]`
+- Minority: two bracket pairs the latter being empty or only one pair with content (including spaces)
+ - `[this link][]`
+ - `[this link]`
+
+Regexp notes:
+ - `(?:\[.+?\]){1}`: Always one bracket pair with content (including spaces)
+ - `(?:\[\]|\[.+?\])?`: Optional second pair that may or may not contain content (including spaces)
+ - `(?!:)`: Never followed by a `:` which is reserved for identifier definition syntax (`[identifier]: /the-link`)
+ - Each of the three parts is non-captured, but the match as a whole is captured
+*/
+const identifierInstanceRegex = /((?:\[.+?\]){1}(?:\[\]|\[.+?\])?(?!:))/g;
+
+const isIdentifierInstance = literal => {
+ // Reset lastIndex as global flag in regexp are stateful (https://stackoverflow.com/a/11477448)
+ identifierInstanceRegex.lastIndex = 0;
+ return identifierInstanceRegex.test(literal);
+};
+
+const canRender = ({ literal }) => isIdentifierInstance(literal);
+
+const tokenize = text => {
+ const matches = text.split(identifierInstanceRegex);
+ const tokens = matches.map(match => {
+ const token = buildTextToken(match);
+ return isIdentifierInstance(match) ? buildUneditableInlineTokens(token) : token;
+ });
+
+ return tokens.flat();
+};
+
+const render = (_, { origin }) => tokenize(origin().content);
+
+export default { canRender, render };
diff --git a/app/assets/javascripts/vue_shared/components/rich_content_editor/services/renderers/render_identifier_paragraph.js b/app/assets/javascripts/vue_shared/components/rich_content_editor/services/renderers/render_identifier_paragraph.js
new file mode 100644
index 00000000000..f5b4502ea3c
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/rich_content_editor/services/renderers/render_identifier_paragraph.js
@@ -0,0 +1,16 @@
+import { buildUneditableOpenTokens, buildUneditableCloseToken } from './build_uneditable_token';
+
+const identifierRegex = /(^\[.+\]: .+)/;
+
+const isIdentifier = text => {
+ return identifierRegex.test(text);
+};
+
+const canRender = (node, context) => {
+ return isIdentifier(context.getChildrenText(node));
+};
+
+const render = (_, { entering, origin }) =>
+ entering ? buildUneditableOpenTokens(origin()) : buildUneditableCloseToken();
+
+export default { canRender, render };
diff --git a/app/assets/javascripts/vue_shared/components/rich_content_editor/services/renderers/render_kramdown_list.js b/app/assets/javascripts/vue_shared/components/rich_content_editor/services/renderers/render_kramdown_list.js
new file mode 100644
index 00000000000..491a26c81d0
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/rich_content_editor/services/renderers/render_kramdown_list.js
@@ -0,0 +1,27 @@
+import { buildUneditableOpenTokens, buildUneditableCloseToken } from './build_uneditable_token';
+
+const isKramdownTOC = ({ type, literal }) => type === 'text' && literal === 'TOC';
+
+const canRender = node => {
+ let targetNode = node;
+ while (targetNode !== null) {
+ const { firstChild } = targetNode;
+ const isLeaf = firstChild === null;
+ if (isLeaf) {
+ if (isKramdownTOC(targetNode)) {
+ return true;
+ }
+
+ break;
+ }
+
+ targetNode = targetNode.firstChild;
+ }
+
+ return false;
+};
+
+const render = (_, { entering, origin }) =>
+ entering ? buildUneditableOpenTokens(origin()) : buildUneditableCloseToken();
+
+export default { canRender, render };
diff --git a/app/assets/javascripts/vue_shared/components/rich_content_editor/services/renderers/render_kramdown_text.js b/app/assets/javascripts/vue_shared/components/rich_content_editor/services/renderers/render_kramdown_text.js
new file mode 100644
index 00000000000..01384699e4f
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/rich_content_editor/services/renderers/render_kramdown_text.js
@@ -0,0 +1,13 @@
+import { buildUneditableTokens } from './build_uneditable_token';
+
+const kramdownRegex = /(^{:.+}$)/;
+
+const canRender = ({ literal }) => {
+ return kramdownRegex.test(literal);
+};
+
+const render = (_, { origin }) => {
+ return buildUneditableTokens(origin());
+};
+
+export default { canRender, render };
diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_button.vue b/app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_button.vue
index 30f7e6a5980..1be5284fa9c 100644
--- a/app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_button.vue
+++ b/app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_button.vue
@@ -1,7 +1,11 @@
<script>
import { __, s__, sprintf } from '~/locale';
+import { GlIcon } from '@gitlab/ui';
export default {
+ components: {
+ GlIcon,
+ },
props: {
abilityName: {
type: String,
@@ -72,6 +76,10 @@ export default {
data-toggle="dropdown"
>
<span class="dropdown-toggle-text"> {{ dropdownToggleText }} </span>
- <i aria-hidden="true" class="fa fa-chevron-down" data-hidden="true"> </i>
+ <gl-icon
+ name="chevron-down"
+ class="gl-absolute gl-top-3 gl-right-3 gl-text-gray-700"
+ :size="16"
+ />
</button>
</template>
diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_search_input.vue b/app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_search_input.vue
index bf51fa3dc38..f0a846c4924 100644
--- a/app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_search_input.vue
+++ b/app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_search_input.vue
@@ -1,5 +1,11 @@
<script>
-export default {};
+import { GlIcon } from '@gitlab/ui';
+
+export default {
+ components: {
+ GlIcon,
+ },
+};
</script>
<template>
@@ -10,13 +16,13 @@ export default {};
class="dropdown-input-field"
type="search"
/>
- <i aria-hidden="true" class="fa fa-search dropdown-input-search" data-hidden="true"> </i>
- <i
- aria-hidden="true"
- class="fa fa-times dropdown-input-clear js-dropdown-input-clear"
- data-hidden="true"
- role="button"
- >
- </i>
+ <gl-icon
+ name="search"
+ class="dropdown-input-search gl-absolute gl-top-3 gl-right-5 gl-text-gray-500 gl-pointer-events-none"
+ />
+ <gl-icon
+ name="close"
+ class="dropdown-input-clear js-dropdown-input-clear gl-absolute gl-top-3 gl-right-5 gl-text-gray-700"
+ />
</div>
</template>
diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/constants.js b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/constants.js
index e94e7d46f85..746e38e98e8 100644
--- a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/constants.js
+++ b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/constants.js
@@ -1,6 +1,7 @@
export const DropdownVariant = {
Sidebar: 'sidebar',
Standalone: 'standalone',
+ Embedded: 'embedded',
};
export const LIST_BUFFER_SIZE = 5;
diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/dropdown_button.vue b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/dropdown_button.vue
index f45c14f8344..cf77aa37d14 100644
--- a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/dropdown_button.vue
+++ b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/dropdown_button.vue
@@ -8,12 +8,16 @@ export default {
GlIcon,
},
computed: {
- ...mapGetters(['dropdownButtonText', 'isDropdownVariantStandalone']),
+ ...mapGetters([
+ 'dropdownButtonText',
+ 'isDropdownVariantStandalone',
+ 'isDropdownVariantEmbedded',
+ ]),
},
methods: {
...mapActions(['toggleDropdownContents']),
handleButtonClick(e) {
- if (this.isDropdownVariantStandalone) {
+ if (this.isDropdownVariantStandalone || this.isDropdownVariantEmbedded) {
this.toggleDropdownContents();
e.stopPropagation();
}
diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_create_view.vue b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_create_view.vue
index ba8d8391952..94671f8a109 100644
--- a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_create_view.vue
+++ b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_create_view.vue
@@ -88,12 +88,16 @@ export default {
@click.prevent="handleColorClick(color)"
/>
</div>
- <div class="color-input-container d-flex">
+ <div class="color-input-container gl-display-flex">
<span
class="dropdown-label-color-preview position-relative position-relative d-inline-block"
:style="{ backgroundColor: selectedColor }"
></span>
- <gl-form-input v-model.trim="selectedColor" :placeholder="__('Use custom color #FF0000')" />
+ <gl-form-input
+ v-model.trim="selectedColor"
+ class="gl-rounded-top-left-none gl-rounded-bottom-left-none"
+ :placeholder="__('Use custom color #FF0000')"
+ />
</div>
</div>
<div class="dropdown-actions clearfix pt-2 px-2">
diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_labels_view.vue b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_labels_view.vue
index af16088b6b9..ef506d00d9a 100644
--- a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_labels_view.vue
+++ b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_labels_view.vue
@@ -36,7 +36,7 @@ export default {
'footerCreateLabelTitle',
'footerManageLabelTitle',
]),
- ...mapGetters(['selectedLabelsList', 'isDropdownVariantSidebar']),
+ ...mapGetters(['selectedLabelsList', 'isDropdownVariantSidebar', 'isDropdownVariantEmbedded']),
visibleLabels() {
if (this.searchKey) {
return this.labels.filter(label =>
@@ -126,16 +126,19 @@ export default {
<div class="labels-select-contents-list js-labels-list" @keydown="handleKeyDown">
<gl-loading-icon
v-if="labelsFetchInProgress"
- class="labels-fetch-loading position-absolute d-flex align-items-center w-100 h-100"
+ class="labels-fetch-loading position-absolute gl-display-flex gl-align-items-center w-100 h-100"
size="md"
/>
- <div v-if="isDropdownVariantSidebar" class="dropdown-title d-flex align-items-center pt-0 pb-2">
+ <div
+ v-if="isDropdownVariantSidebar || isDropdownVariantEmbedded"
+ class="dropdown-title gl-display-flex gl-align-items-center gl-pt-0 gl-pb-3!"
+ >
<span class="flex-grow-1">{{ labelsListTitle }}</span>
<gl-button
:aria-label="__('Close')"
variant="link"
size="small"
- class="dropdown-header-button p-0"
+ class="dropdown-header-button gl-p-0!"
icon="close"
@click="toggleDropdownContents"
/>
@@ -165,17 +168,21 @@ export default {
</li>
</smart-virtual-list>
</div>
- <div v-if="isDropdownVariantSidebar" class="dropdown-footer">
+ <div v-if="isDropdownVariantSidebar || isDropdownVariantEmbedded" class="dropdown-footer">
<ul class="list-unstyled">
<li v-if="allowLabelCreate">
<gl-link
- class="d-flex w-100 flex-row text-break-word label-item"
+ class="gl-display-flex w-100 flex-row text-break-word label-item"
@click="toggleDropdownContentsCreateView"
- >{{ footerCreateLabelTitle }}</gl-link
>
+ {{ footerCreateLabelTitle }}
+ </gl-link>
</li>
<li>
- <gl-link :href="labelsManagePath" class="d-flex flex-row text-break-word label-item">
+ <gl-link
+ :href="labelsManagePath"
+ class="gl-display-flex flex-row text-break-word label-item"
+ >
{{ footerManageLabelTitle }}
</gl-link>
</li>
diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/labels_select_root.vue b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/labels_select_root.vue
index f38b66fdfdf..258a87e62b9 100644
--- a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/labels_select_root.vue
+++ b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/labels_select_root.vue
@@ -74,6 +74,11 @@ export default {
required: false,
default: '',
},
+ dropdownButtonText: {
+ type: String,
+ required: false,
+ default: __('Label'),
+ },
labelsListTitle: {
type: String,
required: false,
@@ -97,7 +102,11 @@ export default {
},
computed: {
...mapState(['showDropdownButton', 'showDropdownContents']),
- ...mapGetters(['isDropdownVariantSidebar', 'isDropdownVariantStandalone']),
+ ...mapGetters([
+ 'isDropdownVariantSidebar',
+ 'isDropdownVariantStandalone',
+ 'isDropdownVariantEmbedded',
+ ]),
dropdownButtonVisible() {
return this.isDropdownVariantSidebar ? this.showDropdownButton : true;
},
@@ -116,6 +125,7 @@ export default {
allowLabelCreate: this.allowLabelCreate,
allowMultiselect: this.allowMultiselect,
allowScopedLabels: this.allowScopedLabels,
+ dropdownButtonText: this.dropdownButtonText,
selectedLabels: this.selectedLabels,
labelsFetchPath: this.labelsFetchPath,
labelsManagePath: this.labelsManagePath,
@@ -200,7 +210,10 @@ export default {
<template>
<div
class="labels-select-wrapper position-relative"
- :class="{ 'is-standalone': isDropdownVariantStandalone }"
+ :class="{
+ 'is-standalone': isDropdownVariantStandalone,
+ 'is-embedded': isDropdownVariantEmbedded,
+ }"
>
<template v-if="isDropdownVariantSidebar">
<dropdown-value-collapsed
@@ -221,7 +234,7 @@ export default {
ref="dropdownContents"
/>
</template>
- <template v-if="isDropdownVariantStandalone">
+ <template v-if="isDropdownVariantStandalone || isDropdownVariantEmbedded">
<dropdown-button v-show="dropdownButtonVisible" />
<dropdown-contents
v-if="dropdownButtonVisible && showDropdownContents"
diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/store/getters.js b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/store/getters.js
index c39222959a9..e035a866048 100644
--- a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/store/getters.js
+++ b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/store/getters.js
@@ -13,7 +13,7 @@ export const dropdownButtonText = (state, getters) => {
: state.selectedLabels;
if (!selectedLabels.length) {
- return __('Label');
+ return state.dropdownButtonText || __('Label');
} else if (selectedLabels.length > 1) {
return sprintf(s__('LabelSelect|%{firstLabelName} +%{remainingLabelCount} more'), {
firstLabelName: selectedLabels[0].title,
@@ -44,5 +44,12 @@ export const isDropdownVariantSidebar = state => state.variant === DropdownVaria
*/
export const isDropdownVariantStandalone = state => state.variant === DropdownVariant.Standalone;
+/**
+ * Returns boolean representing whether dropdown variant
+ * is `embedded`
+ * @param {object} state
+ */
+export const isDropdownVariantEmbedded = state => state.variant === DropdownVariant.Embedded;
+
// prevent babel-plugin-rewire from generating an invalid default during karma tests
export default () => {};
diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/store/state.js b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/store/state.js
index 6a6c0b4c0ee..3f3358d4805 100644
--- a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/store/state.js
+++ b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/store/state.js
@@ -6,6 +6,7 @@ export default () => ({
labelsCreateTitle: '',
footerCreateLabelTitle: '',
footerManageLabelTitle: '',
+ dropdownButtonText: '',
// Paths
namespace: '',
diff --git a/app/assets/javascripts/vue_shared/components/user_popover/user_popover.vue b/app/assets/javascripts/vue_shared/components/user_popover/user_popover.vue
index 595baeeb14f..bd35d3fead9 100644
--- a/app/assets/javascripts/vue_shared/components/user_popover/user_popover.vue
+++ b/app/assets/javascripts/vue_shared/components/user_popover/user_popover.vue
@@ -4,8 +4,11 @@ import Icon from '~/vue_shared/components/icon.vue';
import UserAvatarImage from '../user_avatar/user_avatar_image.vue';
import { glEmojiTag } from '../../../emoji';
+const MAX_SKELETON_LINES = 4;
+
export default {
name: 'UserPopover',
+ maxSkeletonLines: MAX_SKELETON_LINES,
components: {
Icon,
GlPopover,
@@ -22,11 +25,6 @@ export default {
required: true,
default: null,
},
- loaded: {
- type: Boolean,
- required: false,
- default: false,
- },
},
computed: {
statusHtml() {
@@ -42,14 +40,8 @@ export default {
return '';
},
- nameIsLoading() {
- return !this.user.name;
- },
- workInformationIsLoading() {
- return !this.user.loaded && this.user.workInformation === null;
- },
- locationIsLoading() {
- return !this.user.loaded && this.user.location === null;
+ userIsLoading() {
+ return !this.user?.loaded;
},
},
};
@@ -58,54 +50,46 @@ export default {
<template>
<!-- 200ms delay so not every mouseover triggers Popover -->
<gl-popover :target="target" :delay="200" boundary="viewport" triggers="hover" placement="top">
- <div class="user-popover d-flex">
- <div class="p-1 flex-shrink-1">
- <user-avatar-image :img-src="user.avatarUrl" :size="60" css-classes="mr-2" />
+ <div class="gl-p-3 gl-line-height-normal gl-display-flex" data-testid="user-popover">
+ <div class="gl-p-2 flex-shrink-1">
+ <user-avatar-image :img-src="user.avatarUrl" :size="60" css-classes="gl-mr-3!" />
</div>
- <div class="p-1 w-100">
- <h5 class="m-0">
- <span v-if="user.name">{{ user.name }}</span>
- <gl-skeleton-loading v-else :lines="1" class="animation-container-small mb-1" />
- </h5>
- <div class="text-secondary mb-2">
- <span v-if="user.username">@{{ user.username }}</span>
- <gl-skeleton-loading v-else :lines="1" class="animation-container-small mb-1" />
- </div>
- <div class="text-secondary">
- <div v-if="user.bio" class="d-flex mb-1">
- <icon name="profile" class="category-icon flex-shrink-0" />
- <span ref="bio" class="ml-1">{{ user.bio }}</span>
- </div>
- <div v-if="user.workInformation" class="d-flex mb-1">
- <icon
- v-show="!workInformationIsLoading"
- name="work"
- class="category-icon flex-shrink-0"
- />
- <span ref="workInformation" class="ml-1">{{ user.workInformation }}</span>
- </div>
- <gl-skeleton-loading
- v-if="workInformationIsLoading"
- :lines="1"
- class="animation-container-small mb-1"
- />
- </div>
- <div class="js-location text-secondary d-flex">
- <icon
- v-show="!locationIsLoading && user.location"
- name="location"
- class="category-icon flex-shrink-0"
- />
- <span v-if="user.location" class="ml-1">{{ user.location }}</span>
+ <div class="gl-p-2 gl-w-full">
+ <template v-if="userIsLoading">
+ <!-- `gl-skeleton-loading` does not support equal length lines -->
+ <!-- This can be migrated to `gl-skeleton-loader` when https://gitlab.com/gitlab-org/gitlab-ui/-/issues/872 is completed -->
<gl-skeleton-loading
- v-if="locationIsLoading"
+ v-for="n in $options.maxSkeletonLines"
+ :key="n"
:lines="1"
- class="animation-container-small mb-1"
+ class="animation-container-small gl-mb-2"
/>
- </div>
- <div v-if="statusHtml" class="js-user-status mt-2">
- <span v-html="statusHtml"></span>
- </div>
+ </template>
+ <template v-else>
+ <div class="gl-mb-3">
+ <h5 class="gl-m-0">
+ {{ user.name }}
+ </h5>
+ <span class="gl-text-gray-700">@{{ user.username }}</span>
+ </div>
+ <div class="gl-text-gray-700">
+ <div v-if="user.bio" class="gl-display-flex gl-mb-2">
+ <icon name="profile" class="gl-text-gray-600 gl-flex-shrink-0" />
+ <span ref="bio" class="ml-1" v-html="user.bioHtml"></span>
+ </div>
+ <div v-if="user.workInformation" class="gl-display-flex gl-mb-2">
+ <icon name="work" class="gl-text-gray-600 gl-flex-shrink-0" />
+ <span ref="workInformation" class="gl-ml-2">{{ user.workInformation }}</span>
+ </div>
+ </div>
+ <div v-if="user.location" class="js-location gl-text-gray-700 gl-display-flex">
+ <icon name="location" class="gl-text-gray-600 flex-shrink-0" />
+ <span class="gl-ml-2">{{ user.location }}</span>
+ </div>
+ <div v-if="statusHtml" class="js-user-status gl-mt-3">
+ <span v-html="statusHtml"></span>
+ </div>
+ </template>
</div>
</div>
</gl-popover>
diff --git a/app/assets/javascripts/vue_shared/constants.js b/app/assets/javascripts/vue_shared/constants.js
index 63ce4212717..235beb1f22d 100644
--- a/app/assets/javascripts/vue_shared/constants.js
+++ b/app/assets/javascripts/vue_shared/constants.js
@@ -6,6 +6,8 @@ const INTERVALS = {
day: 'day',
};
+export const FILE_SYMLINK_MODE = '120000';
+
export const timeRanges = [
{
label: __('30 minutes'),
diff --git a/app/assets/stylesheets/application.scss b/app/assets/stylesheets/application.scss
index cc4d13db150..41fb62c28e6 100644
--- a/app/assets/stylesheets/application.scss
+++ b/app/assets/stylesheets/application.scss
@@ -11,30 +11,30 @@
// like a table or typography then make changes in the framework/ directory.
// If you need to add unique style that should affect only one page - use pages/
// directory.
-@import "@gitlab/at.js/dist/css/jquery.atwho";
-@import "dropzone/dist/basic";
-@import "select2/select2";
+@import '@gitlab/at.js/dist/css/jquery.atwho';
+@import 'dropzone/dist/basic';
+@import 'select2/select2';
// GitLab UI framework
-@import "framework";
+@import 'framework';
// Font icons
-@import "font-awesome";
+@import 'font-awesome';
// Page specific styles (issues, projects etc):
-@import "pages/**/*";
+@import 'pages/**/*';
// Component specific styles, will be moved to gitlab-ui
-@import "components/**/*";
+@import 'components/**/*';
// Vendors specific styles
-@import "vendors/**/*";
+@import 'vendors/**/*';
// Styles for JS behaviors.
-@import "behaviors";
+@import 'behaviors';
// EE-only stylesheets
-@import "application_ee";
+@import 'application_ee';
// CSS util classes
/**
@@ -42,7 +42,12 @@
Please check https://unpkg.com/browse/@gitlab/ui/src/scss/utilities.scss
to see the available utility classes.
**/
-@import "utilities";
+@import 'utilities';
// Gitlab UI util classes
-@import "@gitlab/ui/src/scss/utilities";
+@import '@gitlab/ui/src/scss/utilities';
+
+/* print styles */
+@media print {
+ @import 'print';
+}
diff --git a/app/assets/stylesheets/application_dark.scss b/app/assets/stylesheets/application_dark.scss
index 72196d71969..e55141e15df 100644
--- a/app/assets/stylesheets/application_dark.scss
+++ b/app/assets/stylesheets/application_dark.scss
@@ -1,3 +1,3 @@
-@import "./themes/dark";
+@import './themes/dark';
-@import "./application";
+@import './application';
diff --git a/app/assets/stylesheets/behaviors.scss b/app/assets/stylesheets/behaviors.scss
index e3ca7f6373a..120a139ff3d 100644
--- a/app/assets/stylesheets/behaviors.scss
+++ b/app/assets/stylesheets/behaviors.scss
@@ -16,6 +16,7 @@
.js-toggler-container {
.turn-on { display: block; }
.turn-off { display: none; }
+
&.on {
.turn-on { display: none; }
.turn-off { display: block; }
@@ -23,6 +24,6 @@
}
// Hide element if Vue is still working on rendering it fully.
-[v-cloak="true"] {
+[v-cloak='true'] {
display: none !important;
}
diff --git a/app/assets/stylesheets/bootstrap_migration.scss b/app/assets/stylesheets/bootstrap_migration.scss
index a6d56819140..aac32e7fb2d 100644
--- a/app/assets/stylesheets/bootstrap_migration.scss
+++ b/app/assets/stylesheets/bootstrap_migration.scss
@@ -6,7 +6,7 @@ $brand-info: $blue-500;
$brand-warning: $orange-500;
$brand-danger: $red-500;
-$border-radius-base: 3px !default;
+$border-radius-base: $gl-border-radius-base;
$modal-body-bg: $white;
$input-border: $border-color;
@@ -23,7 +23,7 @@ body,
// Override default font size used in non-csslab UI
// Use rem to keep default font-size at 14px on body so 1rem still
// fits 8px grid, but also allow users to change browser font size
- font-size: .875rem;
+ font-size: 0.875rem;
}
legend {
@@ -32,11 +32,12 @@ legend {
}
button,
-html [type="button"],
-[type="reset"],
-[type="submit"],
-[role="button"] {
+html [type='button'],
+[type='reset'],
+[type='submit'],
+[role='button'] {
// Override bootstrap reboot
+ /* stylelint-disable-next-line property-no-vendor-prefix */
-webkit-appearance: inherit;
cursor: pointer;
}
@@ -77,7 +78,7 @@ h5,
font-size: $gl-font-size;
}
-input[type="file"] {
+input[type='file'] {
// Bootstrap 4 file input height is taller by default
// which makes them look ugly
line-height: 1;
@@ -314,8 +315,8 @@ input[type=color].form-control {
.toggle-sidebar-button {
.collapse-text,
- .icon-angle-double-left,
- .icon-angle-double-right {
+ .icon-chevron-double-lg-left,
+ .icon-chevron-double-lg-right {
color: $gl-text-color-secondary;
}
}
diff --git a/app/assets/stylesheets/components/design_management/design.scss b/app/assets/stylesheets/components/design_management/design.scss
index 380b2280490..33f03fb5949 100644
--- a/app/assets/stylesheets/components/design_management/design.scss
+++ b/app/assets/stylesheets/components/design_management/design.scss
@@ -10,7 +10,7 @@
}
.design-pin {
- transition: opacity 0.5s ease;
+ transition: opacity $gl-transition-duration-medium $general-hover-transition-curve;
&.inactive {
@include gl-opacity-5;
@@ -98,7 +98,7 @@
&::before {
content: '';
- border-left: 1px solid $gray-200;
+ border-left: 1px solid $gray-100;
position: absolute;
left: 28px;
top: -18px;
@@ -108,6 +108,9 @@
.design-note {
padding: $gl-padding;
list-style: none;
+ transition: background $gl-transition-duration-medium $general-hover-transition-curve;
+ border-top-left-radius: $border-radius-default; // same border radius used by .bordered-box
+ border-top-right-radius: $border-radius-default;
a {
color: inherit;
@@ -146,11 +149,12 @@
}
.design-dropzone-border {
- border: 2px dashed $gray-200;
+ border: 2px dashed $gray-100;
}
.design-dropzone-card {
- transition: border $general-hover-transition-duration $general-hover-transition-curve;
+ transition: border $gl-transition-duration-medium $general-hover-transition-curve;
+ color: $gl-text-color;
&:focus,
&:active {
diff --git a/app/assets/stylesheets/components/design_management/design_list_item.scss b/app/assets/stylesheets/components/design_management/design_list_item.scss
index aacb1f91e59..b7f6b2026fe 100644
--- a/app/assets/stylesheets/components/design_management/design_list_item.scss
+++ b/app/assets/stylesheets/components/design_management/design_list_item.scss
@@ -17,3 +17,8 @@
height: 230px;
}
}
+
+// This is temporary class to be removed after feature flag removal: https://gitlab.com/gitlab-org/gitlab/-/issues/223197
+.design-list-item-new {
+ height: 210px;
+}
diff --git a/app/assets/stylesheets/components/popover.scss b/app/assets/stylesheets/components/popover.scss
index 1e78781f4b8..f870948cc4f 100644
--- a/app/assets/stylesheets/components/popover.scss
+++ b/app/assets/stylesheets/components/popover.scss
@@ -1,6 +1,6 @@
.popover {
max-width: $popover-max-width;
- border: 1px solid $gray-200;
+ border: 1px solid $gray-100;
box-shadow: $popover-box-shadow;
font-size: $gl-font-size-small;
@@ -50,7 +50,7 @@
* due to the box-shadow include in our custom styles.
*/
> .arrow::before {
- border-top-color: $gray-200;
+ border-top-color: $gray-100;
bottom: 1px;
}
@@ -61,7 +61,7 @@
.bs-popover-bottom {
> .arrow::before {
- border-bottom-color: $gray-200;
+ border-bottom-color: $gray-100;
}
> .popover-header::before {
@@ -70,11 +70,11 @@
}
.bs-popover-right > .arrow::before {
- border-right-color: $gray-200;
+ border-right-color: $gray-100;
}
.bs-popover-left > .arrow::before {
- border-left-color: $gray-200;
+ border-left-color: $gray-100;
}
.popover-header {
@@ -100,45 +100,6 @@
}
}
-.onboarding-popover {
- box-shadow: 0 2px 4px $dropdown-shadow-color;
- max-width: 280px;
-
- .popover-body {
- font-size: $gl-font-size;
- line-height: $gl-line-height;
- padding: $gl-padding;
- }
-
- .popover-header {
- display: none;
- }
-
- .accept-mr-label {
- background-color: $accepting-mr-label-color;
- color: $white;
- }
-}
-
-/**
-* user_popover component
-*/
-.user-popover {
- padding: $gl-padding-8;
- line-height: $gl-line-height;
-
- .category-icon {
- color: $gray-600;
- }
-}
-
-.onboarding-welcome-page {
- .popover {
- min-width: auto;
- max-width: 40%;
- }
-}
-
.suggest-gitlab-ci-yml {
margin-top: -1em;
diff --git a/app/assets/stylesheets/components/ref_selector.scss b/app/assets/stylesheets/components/ref_selector.scss
new file mode 100644
index 00000000000..970a7b967ee
--- /dev/null
+++ b/app/assets/stylesheets/components/ref_selector.scss
@@ -0,0 +1,17 @@
+.ref-selector {
+ & &-dropdown-content {
+ // Setting a max height is necessary to allow the dropdown's content
+ // to control where and how scrollbars appear.
+ // This content is limited to the max-height of the dropdown
+ // ($dropdown-max-height-lg) minus the additional padding
+ // on the top and bottom (2 * $gl-padding-8)
+ max-height: $dropdown-max-height-lg - 2 * $gl-padding-8;
+ }
+
+ .dropdown-menu.show {
+ // Make the dropdown a little wider and longer than usual
+ // since it contains quite a bit of content.
+ width: 20rem;
+ max-height: $dropdown-max-height-lg;
+ }
+}
diff --git a/app/assets/stylesheets/components/related_items_list.scss b/app/assets/stylesheets/components/related_items_list.scss
index 956f34f7a8b..dd749b4df1a 100644
--- a/app/assets/stylesheets/components/related_items_list.scss
+++ b/app/assets/stylesheets/components/related_items_list.scss
@@ -59,10 +59,6 @@ $item-remove-button-space: 42px;
flex-basis: 100%;
font-size: $gl-font-size-small;
- &.mr-title {
- font-weight: $gl-font-weight-bold;
- }
-
.sortable-link {
color: $gray-900;
}
@@ -77,10 +73,6 @@ $item-remove-button-space: 42px;
overflow: hidden;
white-space: nowrap;
}
-
- .health-label-short {
- display: none;
- }
}
.item-body,
@@ -89,10 +81,6 @@ $item-remove-button-space: 42px;
max-width: 0;
}
- .health-label-long {
- display: none;
- }
-
.status {
&-at-risk {
color: $red-500;
@@ -158,19 +146,16 @@ $item-remove-button-space: 42px;
max-width: $item-milestone-max-width;
.ic-clock {
- color: $gl-text-color-secondary;
margin-right: $gl-padding-4;
}
}
.item-weight {
max-width: $item-weight-max-width;
-
- .ic-weight {
- color: $gl-text-color-secondary;
- }
}
+ .item-milestone .ic-clock,
+ .item-weight .ic-weight,
.item-due-date .ic-calendar {
color: $gl-text-color-secondary;
}
@@ -314,10 +299,6 @@ $item-remove-button-space: 42px;
max-width: 100px;
}
}
-
- .health-label-long {
- display: none;
- }
}
/* Large devices (large desktops, 1200px and up) */
@@ -331,10 +312,6 @@ $item-remove-button-space: 42px;
}
}
- .health-label-long {
- display: none;
- }
-
.item-contents {
overflow: hidden;
}
@@ -376,7 +353,7 @@ $item-remove-button-space: 42px;
}
.health-label-long {
- display: initial;
+ display: block;
}
}
}
diff --git a/app/assets/stylesheets/components/rich_content_editor.scss b/app/assets/stylesheets/components/rich_content_editor.scss
index bedd06ec9a1..8d31b386d9e 100644
--- a/app/assets/stylesheets/components/rich_content_editor.scss
+++ b/app/assets/stylesheets/components/rich_content_editor.scss
@@ -2,30 +2,45 @@
* Overrides styles from ToastUI editor
*/
-// Toolbar buttons
-.tui-editor-defaultUI-toolbar .toolbar-button {
- color: $gl-gray-600;
- border: 0;
-
- &:hover,
- &:active {
- color: $blue-500;
+.tui-editor-defaultUI {
+
+ // Toolbar buttons
+ .tui-editor-defaultUI-toolbar .toolbar-button {
+ color: $gl-gray-600;
border: 0;
+
+ &:hover,
+ &:active {
+ color: $blue-500;
+ border: 0;
+ }
}
-}
-// Contextual menu's & popups
-.tui-editor-defaultUI .tui-popup-wrapper {
- @include gl-overflow-hidden;
- @include gl-rounded-base;
- @include gl-border-gray-400;
+ // Contextual menu's & popups
+ .tui-popup-wrapper {
+ @include gl-overflow-hidden;
+ @include gl-rounded-base;
+ @include gl-border-gray-400;
- hr {
- @include gl-m-0;
- @include gl-bg-gray-400;
+ hr {
+ @include gl-m-0;
+ @include gl-bg-gray-400;
+ }
+
+ button {
+ @include gl-text-gray-800;
+ }
}
- button {
- @include gl-text-gray-800;
+ /**
+ * Overrides styles from ToastUI's Code Mirror (markdown mode) editor.
+ * Toast UI internally overrides some of these using the `.tui-md-` prefix.
+ * https://codemirror.net/doc/manual.html#styling
+ */
+
+ .te-md-container .CodeMirror * {
+ @include gl-font-monospace;
+ @include gl-font-size-monospace;
+ @include gl-line-height-20;
}
}
diff --git a/app/assets/stylesheets/disable_animations.scss b/app/assets/stylesheets/disable_animations.scss
index e65b49c36f3..799c6e80ec9 100644
--- a/app/assets/stylesheets/disable_animations.scss
+++ b/app/assets/stylesheets/disable_animations.scss
@@ -1,4 +1,5 @@
* {
+ /* stylelint-disable property-no-vendor-prefix */
-o-transition: none !important;
-moz-transition: none !important;
-ms-transition: none !important;
@@ -9,6 +10,7 @@
-o-animation: none !important;
-ms-animation: none !important;
animation: none !important;
+ /* stylelint-enable property-no-vendor-prefix */
}
// Disable sticky changes bar for tests
diff --git a/app/assets/stylesheets/emoji_sprites.scss b/app/assets/stylesheets/emoji_sprites.scss
index 8f6134c474b..01d13b30d2b 100644
--- a/app/assets/stylesheets/emoji_sprites.scss
+++ b/app/assets/stylesheets/emoji_sprites.scss
@@ -1,5384 +1,7176 @@
// Automatic Prettier Formatting for this big file
-// scss-lint:disable EmptyLineBetweenBlocks
.emoji-zzz {
background-position: 0 0;
}
+
.emoji-1234 {
background-position: -20px 0;
}
+
.emoji-1F627 {
background-position: 0 -20px;
}
+
.emoji-8ball {
background-position: -20px -20px;
}
+
.emoji-a {
background-position: -40px 0;
}
+
.emoji-ab {
background-position: -40px -20px;
}
+
.emoji-abc {
background-position: 0 -40px;
}
+
.emoji-abcd {
background-position: -20px -40px;
}
+
.emoji-accept {
background-position: -40px -40px;
}
+
.emoji-aerial_tramway {
background-position: -60px 0;
}
+
.emoji-airplane {
background-position: -60px -20px;
}
+
.emoji-airplane_arriving {
background-position: -60px -40px;
}
+
.emoji-airplane_departure {
background-position: 0 -60px;
}
+
.emoji-airplane_small {
background-position: -20px -60px;
}
+
.emoji-alarm_clock {
background-position: -40px -60px;
}
+
.emoji-alembic {
background-position: -60px -60px;
}
+
.emoji-alien {
background-position: -80px 0;
}
+
.emoji-ambulance {
background-position: -80px -20px;
}
+
.emoji-amphora {
background-position: -80px -40px;
}
+
.emoji-anchor {
background-position: -80px -60px;
}
+
.emoji-angel {
background-position: 0 -80px;
}
+
.emoji-angel_tone1 {
background-position: -20px -80px;
}
+
.emoji-angel_tone2 {
background-position: -40px -80px;
}
+
.emoji-angel_tone3 {
background-position: -60px -80px;
}
+
.emoji-angel_tone4 {
background-position: -80px -80px;
}
+
.emoji-angel_tone5 {
background-position: -100px 0;
}
+
.emoji-anger {
background-position: -100px -20px;
}
+
.emoji-anger_right {
background-position: -100px -40px;
}
+
.emoji-angry {
background-position: -100px -60px;
}
+
.emoji-ant {
background-position: -100px -80px;
}
+
.emoji-apple {
background-position: 0 -100px;
}
+
.emoji-aquarius {
background-position: -20px -100px;
}
+
.emoji-aries {
background-position: -40px -100px;
}
+
.emoji-arrow_backward {
background-position: -60px -100px;
}
+
.emoji-arrow_double_down {
background-position: -80px -100px;
}
+
.emoji-arrow_double_up {
background-position: -100px -100px;
}
+
.emoji-arrow_down {
background-position: -120px 0;
}
+
.emoji-arrow_down_small {
background-position: -120px -20px;
}
+
.emoji-arrow_forward {
background-position: -120px -40px;
}
+
.emoji-arrow_heading_down {
background-position: -120px -60px;
}
+
.emoji-arrow_heading_up {
background-position: -120px -80px;
}
+
.emoji-arrow_left {
background-position: -120px -100px;
}
+
.emoji-arrow_lower_left {
background-position: 0 -120px;
}
+
.emoji-arrow_lower_right {
background-position: -20px -120px;
}
+
.emoji-arrow_right {
background-position: -40px -120px;
}
+
.emoji-arrow_right_hook {
background-position: -60px -120px;
}
+
.emoji-arrow_up {
background-position: -80px -120px;
}
+
.emoji-arrow_up_down {
background-position: -100px -120px;
}
+
.emoji-arrow_up_small {
background-position: -120px -120px;
}
+
.emoji-arrow_upper_left {
background-position: -140px 0;
}
+
.emoji-arrow_upper_right {
background-position: -140px -20px;
}
+
.emoji-arrows_clockwise {
background-position: -140px -40px;
}
+
.emoji-arrows_counterclockwise {
background-position: -140px -60px;
}
+
.emoji-art {
background-position: -140px -80px;
}
+
.emoji-articulated_lorry {
background-position: -140px -100px;
}
+
.emoji-asterisk {
background-position: -140px -120px;
}
+
.emoji-astonished {
background-position: 0 -140px;
}
+
.emoji-athletic_shoe {
background-position: -20px -140px;
}
+
.emoji-atm {
background-position: -40px -140px;
}
+
.emoji-atom {
background-position: -60px -140px;
}
+
.emoji-avocado {
background-position: -80px -140px;
}
+
.emoji-b {
background-position: -100px -140px;
}
+
.emoji-baby {
background-position: -120px -140px;
}
+
.emoji-baby_bottle {
background-position: -140px -140px;
}
+
.emoji-baby_chick {
background-position: -160px 0;
}
+
.emoji-baby_symbol {
background-position: -160px -20px;
}
+
.emoji-baby_tone1 {
background-position: -160px -40px;
}
+
.emoji-baby_tone2 {
background-position: -160px -60px;
}
+
.emoji-baby_tone3 {
background-position: -160px -80px;
}
+
.emoji-baby_tone4 {
background-position: -160px -100px;
}
+
.emoji-baby_tone5 {
background-position: -160px -120px;
}
+
.emoji-back {
background-position: -160px -140px;
}
+
.emoji-bacon {
background-position: 0 -160px;
}
+
.emoji-badminton {
background-position: -20px -160px;
}
+
.emoji-baggage_claim {
background-position: -40px -160px;
}
+
.emoji-balloon {
background-position: -60px -160px;
}
+
.emoji-ballot_box {
background-position: -80px -160px;
}
+
.emoji-ballot_box_with_check {
background-position: -100px -160px;
}
+
.emoji-bamboo {
background-position: -120px -160px;
}
+
.emoji-banana {
background-position: -140px -160px;
}
+
.emoji-bangbang {
background-position: -160px -160px;
}
+
.emoji-bank {
background-position: -180px 0;
}
+
.emoji-bar_chart {
background-position: -180px -20px;
}
+
.emoji-barber {
background-position: -180px -40px;
}
+
.emoji-baseball {
background-position: -180px -60px;
}
+
.emoji-basketball {
background-position: -180px -80px;
}
+
.emoji-basketball_player {
background-position: -180px -100px;
}
+
.emoji-basketball_player_tone1 {
background-position: -180px -120px;
}
+
.emoji-basketball_player_tone2 {
background-position: -180px -140px;
}
+
.emoji-basketball_player_tone3 {
background-position: -180px -160px;
}
+
.emoji-basketball_player_tone4 {
background-position: 0 -180px;
}
+
.emoji-basketball_player_tone5 {
background-position: -20px -180px;
}
+
.emoji-bat {
background-position: -40px -180px;
}
+
.emoji-bath {
background-position: -60px -180px;
}
+
.emoji-bath_tone1 {
background-position: -80px -180px;
}
+
.emoji-bath_tone2 {
background-position: -100px -180px;
}
+
.emoji-bath_tone3 {
background-position: -120px -180px;
}
+
.emoji-bath_tone4 {
background-position: -140px -180px;
}
+
.emoji-bath_tone5 {
background-position: -160px -180px;
}
+
.emoji-bathtub {
background-position: -180px -180px;
}
+
.emoji-battery {
background-position: -200px 0;
}
+
.emoji-beach {
background-position: -200px -20px;
}
+
.emoji-beach_umbrella {
background-position: -200px -40px;
}
+
.emoji-bear {
background-position: -200px -60px;
}
+
.emoji-bed {
background-position: -200px -80px;
}
+
.emoji-bee {
background-position: -200px -100px;
}
+
.emoji-beer {
background-position: -200px -120px;
}
+
.emoji-beers {
background-position: -200px -140px;
}
+
.emoji-beetle {
background-position: -200px -160px;
}
+
.emoji-beginner {
background-position: -200px -180px;
}
+
.emoji-bell {
background-position: 0 -200px;
}
+
.emoji-bellhop {
background-position: -20px -200px;
}
+
.emoji-bento {
background-position: -40px -200px;
}
+
.emoji-bicyclist {
background-position: -60px -200px;
}
+
.emoji-bicyclist_tone1 {
background-position: -80px -200px;
}
+
.emoji-bicyclist_tone2 {
background-position: -100px -200px;
}
+
.emoji-bicyclist_tone3 {
background-position: -120px -200px;
}
+
.emoji-bicyclist_tone4 {
background-position: -140px -200px;
}
+
.emoji-bicyclist_tone5 {
background-position: -160px -200px;
}
+
.emoji-bike {
background-position: -180px -200px;
}
+
.emoji-bikini {
background-position: -200px -200px;
}
+
.emoji-biohazard {
background-position: -220px 0;
}
+
.emoji-bird {
background-position: -220px -20px;
}
+
.emoji-birthday {
background-position: -220px -40px;
}
+
.emoji-black_circle {
background-position: -220px -60px;
}
+
.emoji-black_heart {
background-position: -220px -80px;
}
+
.emoji-black_joker {
background-position: -220px -100px;
}
+
.emoji-black_large_square {
background-position: -220px -120px;
}
+
.emoji-black_medium_small_square {
background-position: -220px -140px;
}
+
.emoji-black_medium_square {
background-position: -220px -160px;
}
+
.emoji-black_nib {
background-position: -220px -180px;
}
+
.emoji-black_small_square {
background-position: -220px -200px;
}
+
.emoji-black_square_button {
background-position: 0 -220px;
}
+
.emoji-blossom {
background-position: -20px -220px;
}
+
.emoji-blowfish {
background-position: -40px -220px;
}
+
.emoji-blue_book {
background-position: -60px -220px;
}
+
.emoji-blue_car {
background-position: -80px -220px;
}
+
.emoji-blue_heart {
background-position: -100px -220px;
}
+
.emoji-blush {
background-position: -120px -220px;
}
+
.emoji-boar {
background-position: -140px -220px;
}
+
.emoji-bomb {
background-position: -160px -220px;
}
+
.emoji-book {
background-position: -180px -220px;
}
+
.emoji-bookmark {
background-position: -200px -220px;
}
+
.emoji-bookmark_tabs {
background-position: -220px -220px;
}
+
.emoji-books {
background-position: -240px 0;
}
+
.emoji-boom {
background-position: -240px -20px;
}
+
.emoji-boot {
background-position: -240px -40px;
}
+
.emoji-bouquet {
background-position: -240px -60px;
}
+
.emoji-bow {
background-position: -240px -80px;
}
+
.emoji-bow_and_arrow {
background-position: -240px -100px;
}
+
.emoji-bow_tone1 {
background-position: -240px -120px;
}
+
.emoji-bow_tone2 {
background-position: -240px -140px;
}
+
.emoji-bow_tone3 {
background-position: -240px -160px;
}
+
.emoji-bow_tone4 {
background-position: -240px -180px;
}
+
.emoji-bow_tone5 {
background-position: -240px -200px;
}
+
.emoji-bowling {
background-position: -240px -220px;
}
+
.emoji-boxing_glove {
background-position: 0 -240px;
}
+
.emoji-boy {
background-position: -20px -240px;
}
+
.emoji-boy_tone1 {
background-position: -40px -240px;
}
+
.emoji-boy_tone2 {
background-position: -60px -240px;
}
+
.emoji-boy_tone3 {
background-position: -80px -240px;
}
+
.emoji-boy_tone4 {
background-position: -100px -240px;
}
+
.emoji-boy_tone5 {
background-position: -120px -240px;
}
+
.emoji-bread {
background-position: -140px -240px;
}
+
.emoji-bride_with_veil {
background-position: -160px -240px;
}
+
.emoji-bride_with_veil_tone1 {
background-position: -180px -240px;
}
+
.emoji-bride_with_veil_tone2 {
background-position: -200px -240px;
}
+
.emoji-bride_with_veil_tone3 {
background-position: -220px -240px;
}
+
.emoji-bride_with_veil_tone4 {
background-position: -240px -240px;
}
+
.emoji-bride_with_veil_tone5 {
background-position: -260px 0;
}
+
.emoji-bridge_at_night {
background-position: -260px -20px;
}
+
.emoji-briefcase {
background-position: -260px -40px;
}
+
.emoji-broken_heart {
background-position: -260px -60px;
}
+
.emoji-bug {
background-position: -260px -80px;
}
+
.emoji-bulb {
background-position: -260px -100px;
}
+
.emoji-bullettrain_front {
background-position: -260px -120px;
}
+
.emoji-bullettrain_side {
background-position: -260px -140px;
}
+
.emoji-burrito {
background-position: -260px -160px;
}
+
.emoji-bus {
background-position: -260px -180px;
}
+
.emoji-busstop {
background-position: -260px -200px;
}
+
.emoji-bust_in_silhouette {
background-position: -260px -220px;
}
+
.emoji-busts_in_silhouette {
background-position: -260px -240px;
}
+
.emoji-butterfly {
background-position: 0 -260px;
}
+
.emoji-cactus {
background-position: -20px -260px;
}
+
.emoji-cake {
background-position: -40px -260px;
}
+
.emoji-calendar {
background-position: -60px -260px;
}
+
.emoji-calendar_spiral {
background-position: -80px -260px;
}
+
.emoji-call_me {
background-position: -100px -260px;
}
+
.emoji-call_me_tone1 {
background-position: -120px -260px;
}
+
.emoji-call_me_tone2 {
background-position: -140px -260px;
}
+
.emoji-call_me_tone3 {
background-position: -160px -260px;
}
+
.emoji-call_me_tone4 {
background-position: -180px -260px;
}
+
.emoji-call_me_tone5 {
background-position: -200px -260px;
}
+
.emoji-calling {
background-position: -220px -260px;
}
+
.emoji-camel {
background-position: -240px -260px;
}
+
.emoji-camera {
background-position: -260px -260px;
}
+
.emoji-camera_with_flash {
background-position: -280px 0;
}
+
.emoji-camping {
background-position: -280px -20px;
}
+
.emoji-cancer {
background-position: -280px -40px;
}
+
.emoji-candle {
background-position: -280px -60px;
}
+
.emoji-candy {
background-position: -280px -80px;
}
+
.emoji-canoe {
background-position: -280px -100px;
}
+
.emoji-capital_abcd {
background-position: -280px -120px;
}
+
.emoji-capricorn {
background-position: -280px -140px;
}
+
.emoji-card_box {
background-position: -280px -160px;
}
+
.emoji-card_index {
background-position: -280px -180px;
}
+
.emoji-carousel_horse {
background-position: -280px -200px;
}
+
.emoji-carrot {
background-position: -280px -220px;
}
+
.emoji-cartwheel {
background-position: -280px -240px;
}
+
.emoji-cartwheel_tone1 {
background-position: -280px -260px;
}
+
.emoji-cartwheel_tone2 {
background-position: 0 -280px;
}
+
.emoji-cartwheel_tone3 {
background-position: -20px -280px;
}
+
.emoji-cartwheel_tone4 {
background-position: -40px -280px;
}
+
.emoji-cartwheel_tone5 {
background-position: -60px -280px;
}
+
.emoji-cat {
background-position: -80px -280px;
}
+
.emoji-cat2 {
background-position: -100px -280px;
}
+
.emoji-cd {
background-position: -120px -280px;
}
+
.emoji-chains {
background-position: -140px -280px;
}
+
.emoji-champagne {
background-position: -160px -280px;
}
+
.emoji-champagne_glass {
background-position: -180px -280px;
}
+
.emoji-chart {
background-position: -200px -280px;
}
+
.emoji-chart_with_downwards_trend {
background-position: -220px -280px;
}
+
.emoji-chart_with_upwards_trend {
background-position: -240px -280px;
}
+
.emoji-checkered_flag {
background-position: -260px -280px;
}
+
.emoji-cheese {
background-position: -280px -280px;
}
+
.emoji-cherries {
background-position: -300px 0;
}
+
.emoji-cherry_blossom {
background-position: -300px -20px;
}
+
.emoji-chestnut {
background-position: -300px -40px;
}
+
.emoji-chicken {
background-position: -300px -60px;
}
+
.emoji-children_crossing {
background-position: -300px -80px;
}
+
.emoji-chipmunk {
background-position: -300px -100px;
}
+
.emoji-chocolate_bar {
background-position: -300px -120px;
}
+
.emoji-christmas_tree {
background-position: -300px -140px;
}
+
.emoji-church {
background-position: -300px -160px;
}
+
.emoji-cinema {
background-position: -300px -180px;
}
+
.emoji-circus_tent {
background-position: -300px -200px;
}
+
.emoji-city_dusk {
background-position: -300px -220px;
}
+
.emoji-city_sunset {
background-position: -300px -240px;
}
+
.emoji-cityscape {
background-position: -300px -260px;
}
+
.emoji-cl {
background-position: -300px -280px;
}
+
.emoji-clap {
background-position: 0 -300px;
}
+
.emoji-clap_tone1 {
background-position: -20px -300px;
}
+
.emoji-clap_tone2 {
background-position: -40px -300px;
}
+
.emoji-clap_tone3 {
background-position: -60px -300px;
}
+
.emoji-clap_tone4 {
background-position: -80px -300px;
}
+
.emoji-clap_tone5 {
background-position: -100px -300px;
}
+
.emoji-clapper {
background-position: -120px -300px;
}
+
.emoji-classical_building {
background-position: -140px -300px;
}
+
.emoji-clipboard {
background-position: -160px -300px;
}
+
.emoji-clock {
background-position: -180px -300px;
}
+
.emoji-clock1 {
background-position: -200px -300px;
}
+
.emoji-clock10 {
background-position: -220px -300px;
}
+
.emoji-clock1030 {
background-position: -240px -300px;
}
+
.emoji-clock11 {
background-position: -260px -300px;
}
+
.emoji-clock1130 {
background-position: -280px -300px;
}
+
.emoji-clock12 {
background-position: -300px -300px;
}
+
.emoji-clock1230 {
background-position: -320px 0;
}
+
.emoji-clock130 {
background-position: -320px -20px;
}
+
.emoji-clock2 {
background-position: -320px -40px;
}
+
.emoji-clock230 {
background-position: -320px -60px;
}
+
.emoji-clock3 {
background-position: -320px -80px;
}
+
.emoji-clock330 {
background-position: -320px -100px;
}
+
.emoji-clock4 {
background-position: -320px -120px;
}
+
.emoji-clock430 {
background-position: -320px -140px;
}
+
.emoji-clock5 {
background-position: -320px -160px;
}
+
.emoji-clock530 {
background-position: -320px -180px;
}
+
.emoji-clock6 {
background-position: -320px -200px;
}
+
.emoji-clock630 {
background-position: -320px -220px;
}
+
.emoji-clock7 {
background-position: -320px -240px;
}
+
.emoji-clock730 {
background-position: -320px -260px;
}
+
.emoji-clock8 {
background-position: -320px -280px;
}
+
.emoji-clock830 {
background-position: -320px -300px;
}
+
.emoji-clock9 {
background-position: 0 -320px;
}
+
.emoji-clock930 {
background-position: -20px -320px;
}
+
.emoji-closed_book {
background-position: -40px -320px;
}
+
.emoji-closed_lock_with_key {
background-position: -60px -320px;
}
+
.emoji-closed_umbrella {
background-position: -80px -320px;
}
+
.emoji-cloud {
background-position: -100px -320px;
}
+
.emoji-cloud_lightning {
background-position: -120px -320px;
}
+
.emoji-cloud_rain {
background-position: -140px -320px;
}
+
.emoji-cloud_snow {
background-position: -160px -320px;
}
+
.emoji-cloud_tornado {
background-position: -180px -320px;
}
+
.emoji-clown {
background-position: -200px -320px;
}
+
.emoji-clubs {
background-position: -220px -320px;
}
+
.emoji-cocktail {
background-position: -240px -320px;
}
+
.emoji-coffee {
background-position: -260px -320px;
}
+
.emoji-coffin {
background-position: -280px -320px;
}
+
.emoji-cold_sweat {
background-position: -300px -320px;
}
+
.emoji-comet {
background-position: -320px -320px;
}
+
.emoji-compression {
background-position: -340px 0;
}
+
.emoji-computer {
background-position: -340px -20px;
}
+
.emoji-confetti_ball {
background-position: -340px -40px;
}
+
.emoji-confounded {
background-position: -340px -60px;
}
+
.emoji-confused {
background-position: -340px -80px;
}
+
.emoji-congratulations {
background-position: -340px -100px;
}
+
.emoji-construction {
background-position: -340px -120px;
}
+
.emoji-construction_site {
background-position: -340px -140px;
}
+
.emoji-construction_worker {
background-position: -340px -160px;
}
+
.emoji-construction_worker_tone1 {
background-position: -340px -180px;
}
+
.emoji-construction_worker_tone2 {
background-position: -340px -200px;
}
+
.emoji-construction_worker_tone3 {
background-position: -340px -220px;
}
+
.emoji-construction_worker_tone4 {
background-position: -340px -240px;
}
+
.emoji-construction_worker_tone5 {
background-position: -340px -260px;
}
+
.emoji-control_knobs {
background-position: -340px -280px;
}
+
.emoji-convenience_store {
background-position: -340px -300px;
}
+
.emoji-cookie {
background-position: -340px -320px;
}
+
.emoji-cooking {
background-position: 0 -340px;
}
+
.emoji-cool {
background-position: -20px -340px;
}
+
.emoji-cop {
background-position: -40px -340px;
}
+
.emoji-cop_tone1 {
background-position: -60px -340px;
}
+
.emoji-cop_tone2 {
background-position: -80px -340px;
}
+
.emoji-cop_tone3 {
background-position: -100px -340px;
}
+
.emoji-cop_tone4 {
background-position: -120px -340px;
}
+
.emoji-cop_tone5 {
background-position: -140px -340px;
}
+
.emoji-copyright {
background-position: -160px -340px;
}
+
.emoji-corn {
background-position: -180px -340px;
}
+
.emoji-couch {
background-position: -200px -340px;
}
+
.emoji-couple {
background-position: -220px -340px;
}
+
.emoji-couple_mm {
background-position: -240px -340px;
}
+
.emoji-couple_with_heart {
background-position: -260px -340px;
}
+
.emoji-couple_ww {
background-position: -280px -340px;
}
+
.emoji-couplekiss {
background-position: -300px -340px;
}
+
.emoji-cow {
background-position: -320px -340px;
}
+
.emoji-cow2 {
background-position: -340px -340px;
}
+
.emoji-cowboy {
background-position: -360px 0;
}
+
.emoji-crab {
background-position: -360px -20px;
}
+
.emoji-crayon {
background-position: -360px -40px;
}
+
.emoji-credit_card {
background-position: -360px -60px;
}
+
.emoji-crescent_moon {
background-position: -360px -80px;
}
+
.emoji-cricket {
background-position: -360px -100px;
}
+
.emoji-crocodile {
background-position: -360px -120px;
}
+
.emoji-croissant {
background-position: -360px -140px;
}
+
.emoji-cross {
background-position: -360px -160px;
}
+
.emoji-crossed_flags {
background-position: -360px -180px;
}
+
.emoji-crossed_swords {
background-position: -360px -200px;
}
+
.emoji-crown {
background-position: -360px -220px;
}
+
.emoji-cruise_ship {
background-position: -360px -240px;
}
+
.emoji-cry {
background-position: -360px -260px;
}
+
.emoji-crying_cat_face {
background-position: -360px -280px;
}
+
.emoji-crystal_ball {
background-position: -360px -300px;
}
+
.emoji-cucumber {
background-position: -360px -320px;
}
+
.emoji-cupid {
background-position: -360px -340px;
}
+
.emoji-curly_loop {
background-position: 0 -360px;
}
+
.emoji-currency_exchange {
background-position: -20px -360px;
}
+
.emoji-curry {
background-position: -40px -360px;
}
+
.emoji-custard {
background-position: -60px -360px;
}
+
.emoji-customs {
background-position: -80px -360px;
}
+
.emoji-cyclone {
background-position: -100px -360px;
}
+
.emoji-dagger {
background-position: -120px -360px;
}
+
.emoji-dancer {
background-position: -140px -360px;
}
+
.emoji-dancer_tone1 {
background-position: -160px -360px;
}
+
.emoji-dancer_tone2 {
background-position: -180px -360px;
}
+
.emoji-dancer_tone3 {
background-position: -200px -360px;
}
+
.emoji-dancer_tone4 {
background-position: -220px -360px;
}
+
.emoji-dancer_tone5 {
background-position: -240px -360px;
}
+
.emoji-dancers {
background-position: -260px -360px;
}
+
.emoji-dango {
background-position: -280px -360px;
}
+
.emoji-dark_sunglasses {
background-position: -300px -360px;
}
+
.emoji-dart {
background-position: -320px -360px;
}
+
.emoji-dash {
background-position: -340px -360px;
}
+
.emoji-date {
background-position: -360px -360px;
}
+
.emoji-deciduous_tree {
background-position: -380px 0;
}
+
.emoji-deer {
background-position: -380px -20px;
}
+
.emoji-department_store {
background-position: -380px -40px;
}
+
.emoji-desert {
background-position: -380px -60px;
}
+
.emoji-desktop {
background-position: -380px -80px;
}
+
.emoji-diamond_shape_with_a_dot_inside {
background-position: -380px -100px;
}
+
.emoji-diamonds {
background-position: -380px -120px;
}
+
.emoji-disappointed {
background-position: -380px -140px;
}
+
.emoji-disappointed_relieved {
background-position: -380px -160px;
}
+
.emoji-dividers {
background-position: -380px -180px;
}
+
.emoji-dizzy {
background-position: -380px -200px;
}
+
.emoji-dizzy_face {
background-position: -380px -220px;
}
+
.emoji-do_not_litter {
background-position: -380px -240px;
}
+
.emoji-dog {
background-position: -380px -260px;
}
+
.emoji-dog2 {
background-position: -380px -280px;
}
+
.emoji-dollar {
background-position: -380px -300px;
}
+
.emoji-dolls {
background-position: -380px -320px;
}
+
.emoji-dolphin {
background-position: -380px -340px;
}
+
.emoji-door {
background-position: -380px -360px;
}
+
.emoji-doughnut {
background-position: 0 -380px;
}
+
.emoji-dove {
background-position: -20px -380px;
}
+
.emoji-dragon {
background-position: -40px -380px;
}
+
.emoji-dragon_face {
background-position: -60px -380px;
}
+
.emoji-dress {
background-position: -80px -380px;
}
+
.emoji-dromedary_camel {
background-position: -100px -380px;
}
+
.emoji-drooling_face {
background-position: -120px -380px;
}
+
.emoji-droplet {
background-position: -140px -380px;
}
+
.emoji-drum {
background-position: -160px -380px;
}
+
.emoji-duck {
background-position: -180px -380px;
}
+
.emoji-dvd {
background-position: -200px -380px;
}
+
.emoji-e-mail {
background-position: -220px -380px;
}
+
.emoji-eagle {
background-position: -240px -380px;
}
+
.emoji-ear {
background-position: -260px -380px;
}
+
.emoji-ear_of_rice {
background-position: -280px -380px;
}
+
.emoji-ear_tone1 {
background-position: -300px -380px;
}
+
.emoji-ear_tone2 {
background-position: -320px -380px;
}
+
.emoji-ear_tone3 {
background-position: -340px -380px;
}
+
.emoji-ear_tone4 {
background-position: -360px -380px;
}
+
.emoji-ear_tone5 {
background-position: -380px -380px;
}
+
.emoji-earth_africa {
background-position: -400px 0;
}
+
.emoji-earth_americas {
background-position: -400px -20px;
}
+
.emoji-earth_asia {
background-position: -400px -40px;
}
+
.emoji-egg {
background-position: -400px -60px;
}
+
.emoji-eggplant {
background-position: -400px -80px;
}
+
.emoji-eight {
background-position: -400px -100px;
}
+
.emoji-eight_pointed_black_star {
background-position: -400px -120px;
}
+
.emoji-eight_spoked_asterisk {
background-position: -400px -140px;
}
+
.emoji-eject {
background-position: -400px -160px;
}
+
.emoji-electric_plug {
background-position: -400px -180px;
}
+
.emoji-elephant {
background-position: -400px -200px;
}
+
.emoji-end {
background-position: -400px -220px;
}
+
.emoji-envelope {
background-position: -400px -240px;
}
+
.emoji-envelope_with_arrow {
background-position: -400px -260px;
}
+
.emoji-euro {
background-position: -400px -280px;
}
+
.emoji-european_castle {
background-position: -400px -300px;
}
+
.emoji-european_post_office {
background-position: -400px -320px;
}
+
.emoji-evergreen_tree {
background-position: -400px -340px;
}
+
.emoji-exclamation {
background-position: -400px -360px;
}
+
.emoji-expressionless {
background-position: -400px -380px;
}
+
.emoji-eye {
background-position: 0 -400px;
}
+
.emoji-eye_in_speech_bubble {
background-position: -20px -400px;
}
+
.emoji-eyeglasses {
background-position: -40px -400px;
}
+
.emoji-eyes {
background-position: -60px -400px;
}
+
.emoji-face_palm {
background-position: -80px -400px;
}
+
.emoji-face_palm_tone1 {
background-position: -100px -400px;
}
+
.emoji-face_palm_tone2 {
background-position: -120px -400px;
}
+
.emoji-face_palm_tone3 {
background-position: -140px -400px;
}
+
.emoji-face_palm_tone4 {
background-position: -160px -400px;
}
+
.emoji-face_palm_tone5 {
background-position: -180px -400px;
}
+
.emoji-factory {
background-position: -200px -400px;
}
+
.emoji-fallen_leaf {
background-position: -220px -400px;
}
+
.emoji-family {
background-position: -240px -400px;
}
+
.emoji-family_mmb {
background-position: -260px -400px;
}
+
.emoji-family_mmbb {
background-position: -280px -400px;
}
+
.emoji-family_mmg {
background-position: -300px -400px;
}
+
.emoji-family_mmgb {
background-position: -320px -400px;
}
+
.emoji-family_mmgg {
background-position: -340px -400px;
}
+
.emoji-family_mwbb {
background-position: -360px -400px;
}
+
.emoji-family_mwg {
background-position: -380px -400px;
}
+
.emoji-family_mwgb {
background-position: -400px -400px;
}
+
.emoji-family_mwgg {
background-position: -420px 0;
}
+
.emoji-family_wwb {
background-position: -420px -20px;
}
+
.emoji-family_wwbb {
background-position: -420px -40px;
}
+
.emoji-family_wwg {
background-position: -420px -60px;
}
+
.emoji-family_wwgb {
background-position: -420px -80px;
}
+
.emoji-family_wwgg {
background-position: -420px -100px;
}
+
.emoji-fast_forward {
background-position: -420px -120px;
}
+
.emoji-fax {
background-position: -420px -140px;
}
+
.emoji-fearful {
background-position: -420px -160px;
}
+
.emoji-feet {
background-position: -420px -180px;
}
+
.emoji-fencer {
background-position: -420px -200px;
}
+
.emoji-ferris_wheel {
background-position: -420px -220px;
}
+
.emoji-ferry {
background-position: -420px -240px;
}
+
.emoji-field_hockey {
background-position: -420px -260px;
}
+
.emoji-file_cabinet {
background-position: -420px -280px;
}
+
.emoji-file_folder {
background-position: -420px -300px;
}
+
.emoji-film_frames {
background-position: -420px -320px;
}
+
.emoji-fingers_crossed {
background-position: -420px -340px;
}
+
.emoji-fingers_crossed_tone1 {
background-position: -420px -360px;
}
+
.emoji-fingers_crossed_tone2 {
background-position: -420px -380px;
}
+
.emoji-fingers_crossed_tone3 {
background-position: -420px -400px;
}
+
.emoji-fingers_crossed_tone4 {
background-position: 0 -420px;
}
+
.emoji-fingers_crossed_tone5 {
background-position: -20px -420px;
}
+
.emoji-fire {
background-position: -40px -420px;
}
+
.emoji-fire_engine {
background-position: -60px -420px;
}
+
.emoji-fireworks {
background-position: -80px -420px;
}
+
.emoji-first_place {
background-position: -100px -420px;
}
+
.emoji-first_quarter_moon {
background-position: -120px -420px;
}
+
.emoji-first_quarter_moon_with_face {
background-position: -140px -420px;
}
+
.emoji-fish {
background-position: -160px -420px;
}
+
.emoji-fish_cake {
background-position: -180px -420px;
}
+
.emoji-fishing_pole_and_fish {
background-position: -200px -420px;
}
+
.emoji-fist {
background-position: -220px -420px;
}
+
.emoji-fist_tone1 {
background-position: -240px -420px;
}
+
.emoji-fist_tone2 {
background-position: -260px -420px;
}
+
.emoji-fist_tone3 {
background-position: -280px -420px;
}
+
.emoji-fist_tone4 {
background-position: -300px -420px;
}
+
.emoji-fist_tone5 {
background-position: -320px -420px;
}
+
.emoji-five {
background-position: -340px -420px;
}
+
.emoji-flag_ac {
background-position: -360px -420px;
}
+
.emoji-flag_ad {
background-position: -380px -420px;
}
+
.emoji-flag_ae {
background-position: -400px -420px;
}
+
.emoji-flag_af {
background-position: -420px -420px;
}
+
.emoji-flag_ag {
background-position: -440px 0;
}
+
.emoji-flag_ai {
background-position: -440px -20px;
}
+
.emoji-flag_al {
background-position: -440px -40px;
}
+
.emoji-flag_am {
background-position: -440px -60px;
}
+
.emoji-flag_ao {
background-position: -440px -80px;
}
+
.emoji-flag_aq {
background-position: -440px -100px;
}
+
.emoji-flag_ar {
background-position: -440px -120px;
}
+
.emoji-flag_as {
background-position: -440px -140px;
}
+
.emoji-flag_at {
background-position: -440px -160px;
}
+
.emoji-flag_au {
background-position: -440px -180px;
}
+
.emoji-flag_aw {
background-position: -440px -200px;
}
+
.emoji-flag_ax {
background-position: -440px -220px;
}
+
.emoji-flag_az {
background-position: -440px -240px;
}
+
.emoji-flag_ba {
background-position: -440px -260px;
}
+
.emoji-flag_bb {
background-position: -440px -280px;
}
+
.emoji-flag_bd {
background-position: -440px -300px;
}
+
.emoji-flag_be {
background-position: -440px -320px;
}
+
.emoji-flag_bf {
background-position: -440px -340px;
}
+
.emoji-flag_bg {
background-position: -440px -360px;
}
+
.emoji-flag_bh {
background-position: -440px -380px;
}
+
.emoji-flag_bi {
background-position: -440px -400px;
}
+
.emoji-flag_bj {
background-position: -440px -420px;
}
+
.emoji-flag_bl {
background-position: 0 -440px;
}
+
.emoji-flag_black {
background-position: -20px -440px;
}
+
.emoji-flag_bm {
background-position: -40px -440px;
}
+
.emoji-flag_bn {
background-position: -60px -440px;
}
+
.emoji-flag_bo {
background-position: -80px -440px;
}
+
.emoji-flag_bq {
background-position: -100px -440px;
}
+
.emoji-flag_br {
background-position: -120px -440px;
}
+
.emoji-flag_bs {
background-position: -140px -440px;
}
+
.emoji-flag_bt {
background-position: -160px -440px;
}
+
.emoji-flag_bv {
background-position: -180px -440px;
}
+
.emoji-flag_bw {
background-position: -200px -440px;
}
+
.emoji-flag_by {
background-position: -220px -440px;
}
+
.emoji-flag_bz {
background-position: -240px -440px;
}
+
.emoji-flag_ca {
background-position: -260px -440px;
}
+
.emoji-flag_cc {
background-position: -280px -440px;
}
+
.emoji-flag_cd {
background-position: -300px -440px;
}
+
.emoji-flag_cf {
background-position: -320px -440px;
}
+
.emoji-flag_cg {
background-position: -340px -440px;
}
+
.emoji-flag_ch {
background-position: -360px -440px;
}
+
.emoji-flag_ci {
background-position: -380px -440px;
}
+
.emoji-flag_ck {
background-position: -400px -440px;
}
+
.emoji-flag_cl {
background-position: -420px -440px;
}
+
.emoji-flag_cm {
background-position: -440px -440px;
}
+
.emoji-flag_cn {
background-position: -460px 0;
}
+
.emoji-flag_co {
background-position: -460px -20px;
}
+
.emoji-flag_cp {
background-position: -460px -40px;
}
+
.emoji-flag_cr {
background-position: -460px -60px;
}
+
.emoji-flag_cu {
background-position: -460px -80px;
}
+
.emoji-flag_cv {
background-position: -460px -100px;
}
+
.emoji-flag_cw {
background-position: -460px -120px;
}
+
.emoji-flag_cx {
background-position: -460px -140px;
}
+
.emoji-flag_cy {
background-position: -460px -160px;
}
+
.emoji-flag_cz {
background-position: -460px -180px;
}
+
.emoji-flag_de {
background-position: -460px -200px;
}
+
.emoji-flag_dg {
background-position: -460px -220px;
}
+
.emoji-flag_dj {
background-position: -460px -240px;
}
+
.emoji-flag_dk {
background-position: -460px -260px;
}
+
.emoji-flag_dm {
background-position: -460px -280px;
}
+
.emoji-flag_do {
background-position: -460px -300px;
}
+
.emoji-flag_dz {
background-position: -460px -320px;
}
+
.emoji-flag_ea {
background-position: -460px -340px;
}
+
.emoji-flag_ec {
background-position: -460px -360px;
}
+
.emoji-flag_ee {
background-position: -460px -380px;
}
+
.emoji-flag_eg {
background-position: -460px -400px;
}
+
.emoji-flag_eh {
background-position: -460px -420px;
}
+
.emoji-flag_er {
background-position: -460px -440px;
}
+
.emoji-flag_es {
background-position: 0 -460px;
}
+
.emoji-flag_et {
background-position: -20px -460px;
}
+
.emoji-flag_eu {
background-position: -40px -460px;
}
+
.emoji-flag_fi {
background-position: -60px -460px;
}
+
.emoji-flag_fj {
background-position: -80px -460px;
}
+
.emoji-flag_fk {
background-position: -100px -460px;
}
+
.emoji-flag_fm {
background-position: -120px -460px;
}
+
.emoji-flag_fo {
background-position: -140px -460px;
}
+
.emoji-flag_fr {
background-position: -160px -460px;
}
+
.emoji-flag_ga {
background-position: -180px -460px;
}
+
.emoji-flag_gb {
background-position: -200px -460px;
}
+
.emoji-flag_gd {
background-position: -220px -460px;
}
+
.emoji-flag_ge {
background-position: -240px -460px;
}
+
.emoji-flag_gf {
background-position: -260px -460px;
}
+
.emoji-flag_gg {
background-position: -280px -460px;
}
+
.emoji-flag_gh {
background-position: -300px -460px;
}
+
.emoji-flag_gi {
background-position: -320px -460px;
}
+
.emoji-flag_gl {
background-position: -340px -460px;
}
+
.emoji-flag_gm {
background-position: -360px -460px;
}
+
.emoji-flag_gn {
background-position: -380px -460px;
}
+
.emoji-flag_gp {
background-position: -400px -460px;
}
+
.emoji-flag_gq {
background-position: -420px -460px;
}
+
.emoji-flag_gr {
background-position: -440px -460px;
}
+
.emoji-flag_gs {
background-position: -460px -460px;
}
+
.emoji-flag_gt {
background-position: -480px 0;
}
+
.emoji-flag_gu {
background-position: -480px -20px;
}
+
.emoji-flag_gw {
background-position: -480px -40px;
}
+
.emoji-flag_gy {
background-position: -480px -60px;
}
+
.emoji-flag_hk {
background-position: -480px -80px;
}
+
.emoji-flag_hm {
background-position: -480px -100px;
}
+
.emoji-flag_hn {
background-position: -480px -120px;
}
+
.emoji-flag_hr {
background-position: -480px -140px;
}
+
.emoji-flag_ht {
background-position: -480px -160px;
}
+
.emoji-flag_hu {
background-position: -480px -180px;
}
+
.emoji-flag_ic {
background-position: -480px -200px;
}
+
.emoji-flag_id {
background-position: -480px -220px;
}
+
.emoji-flag_ie {
background-position: -480px -240px;
}
+
.emoji-flag_il {
background-position: -480px -260px;
}
+
.emoji-flag_im {
background-position: -480px -280px;
}
+
.emoji-flag_in {
background-position: -480px -300px;
}
+
.emoji-flag_io {
background-position: -480px -320px;
}
+
.emoji-flag_iq {
background-position: -480px -340px;
}
+
.emoji-flag_ir {
background-position: -480px -360px;
}
+
.emoji-flag_is {
background-position: -480px -380px;
}
+
.emoji-flag_it {
background-position: -480px -400px;
}
+
.emoji-flag_je {
background-position: -480px -420px;
}
+
.emoji-flag_jm {
background-position: -480px -440px;
}
+
.emoji-flag_jo {
background-position: -480px -460px;
}
+
.emoji-flag_jp {
background-position: 0 -480px;
}
+
.emoji-flag_ke {
background-position: -20px -480px;
}
+
.emoji-flag_kg {
background-position: -40px -480px;
}
+
.emoji-flag_kh {
background-position: -60px -480px;
}
+
.emoji-flag_ki {
background-position: -80px -480px;
}
+
.emoji-flag_km {
background-position: -100px -480px;
}
+
.emoji-flag_kn {
background-position: -120px -480px;
}
+
.emoji-flag_kp {
background-position: -140px -480px;
}
+
.emoji-flag_kr {
background-position: -160px -480px;
}
+
.emoji-flag_kw {
background-position: -180px -480px;
}
+
.emoji-flag_ky {
background-position: -200px -480px;
}
+
.emoji-flag_kz {
background-position: -220px -480px;
}
+
.emoji-flag_la {
background-position: -240px -480px;
}
+
.emoji-flag_lb {
background-position: -260px -480px;
}
+
.emoji-flag_lc {
background-position: -280px -480px;
}
+
.emoji-flag_li {
background-position: -300px -480px;
}
+
.emoji-flag_lk {
background-position: -320px -480px;
}
+
.emoji-flag_lr {
background-position: -340px -480px;
}
+
.emoji-flag_ls {
background-position: -360px -480px;
}
+
.emoji-flag_lt {
background-position: -380px -480px;
}
+
.emoji-flag_lu {
background-position: -400px -480px;
}
+
.emoji-flag_lv {
background-position: -420px -480px;
}
+
.emoji-flag_ly {
background-position: -440px -480px;
}
+
.emoji-flag_ma {
background-position: -460px -480px;
}
+
.emoji-flag_mc {
background-position: -480px -480px;
}
+
.emoji-flag_md {
background-position: -500px 0;
}
+
.emoji-flag_me {
background-position: -500px -20px;
}
+
.emoji-flag_mf {
background-position: -500px -40px;
}
+
.emoji-flag_mg {
background-position: -500px -60px;
}
+
.emoji-flag_mh {
background-position: -500px -80px;
}
+
.emoji-flag_mk {
background-position: -500px -100px;
}
+
.emoji-flag_ml {
background-position: -500px -120px;
}
+
.emoji-flag_mm {
background-position: -500px -140px;
}
+
.emoji-flag_mn {
background-position: -500px -160px;
}
+
.emoji-flag_mo {
background-position: -500px -180px;
}
+
.emoji-flag_mp {
background-position: -500px -200px;
}
+
.emoji-flag_mq {
background-position: -500px -220px;
}
+
.emoji-flag_mr {
background-position: -500px -240px;
}
+
.emoji-flag_ms {
background-position: -500px -260px;
}
+
.emoji-flag_mt {
background-position: -500px -280px;
}
+
.emoji-flag_mu {
background-position: -500px -300px;
}
+
.emoji-flag_mv {
background-position: -500px -320px;
}
+
.emoji-flag_mw {
background-position: -500px -340px;
}
+
.emoji-flag_mx {
background-position: -500px -360px;
}
+
.emoji-flag_my {
background-position: -500px -380px;
}
+
.emoji-flag_mz {
background-position: -500px -400px;
}
+
.emoji-flag_na {
background-position: -500px -420px;
}
+
.emoji-flag_nc {
background-position: -500px -440px;
}
+
.emoji-flag_ne {
background-position: -500px -460px;
}
+
.emoji-flag_nf {
background-position: -500px -480px;
}
+
.emoji-flag_ng {
background-position: 0 -500px;
}
+
.emoji-flag_ni {
background-position: -20px -500px;
}
+
.emoji-flag_nl {
background-position: -40px -500px;
}
+
.emoji-flag_no {
background-position: -60px -500px;
}
+
.emoji-flag_np {
background-position: -80px -500px;
}
+
.emoji-flag_nr {
background-position: -100px -500px;
}
+
.emoji-flag_nu {
background-position: -120px -500px;
}
+
.emoji-flag_nz {
background-position: -140px -500px;
}
+
.emoji-flag_om {
background-position: -160px -500px;
}
+
.emoji-flag_pa {
background-position: -180px -500px;
}
+
.emoji-flag_pe {
background-position: -200px -500px;
}
+
.emoji-flag_pf {
background-position: -220px -500px;
}
+
.emoji-flag_pg {
background-position: -240px -500px;
}
+
.emoji-flag_ph {
background-position: -260px -500px;
}
+
.emoji-flag_pk {
background-position: -280px -500px;
}
+
.emoji-flag_pl {
background-position: -300px -500px;
}
+
.emoji-flag_pm {
background-position: -320px -500px;
}
+
.emoji-flag_pn {
background-position: -340px -500px;
}
+
.emoji-flag_pr {
background-position: -360px -500px;
}
+
.emoji-flag_ps {
background-position: -380px -500px;
}
+
.emoji-flag_pt {
background-position: -400px -500px;
}
+
.emoji-flag_pw {
background-position: -420px -500px;
}
+
.emoji-flag_py {
background-position: -440px -500px;
}
+
.emoji-flag_qa {
background-position: -460px -500px;
}
+
.emoji-flag_re {
background-position: -480px -500px;
}
+
.emoji-flag_ro {
background-position: -500px -500px;
}
+
.emoji-flag_rs {
background-position: -520px 0;
}
+
.emoji-flag_ru {
background-position: -520px -20px;
}
+
.emoji-flag_rw {
background-position: -520px -40px;
}
+
.emoji-flag_sa {
background-position: -520px -60px;
}
+
.emoji-flag_sb {
background-position: -520px -80px;
}
+
.emoji-flag_sc {
background-position: -520px -100px;
}
+
.emoji-flag_sd {
background-position: -520px -120px;
}
+
.emoji-flag_se {
background-position: -520px -140px;
}
+
.emoji-flag_sg {
background-position: -520px -160px;
}
+
.emoji-flag_sh {
background-position: -520px -180px;
}
+
.emoji-flag_si {
background-position: -520px -200px;
}
+
.emoji-flag_sj {
background-position: -520px -220px;
}
+
.emoji-flag_sk {
background-position: -520px -240px;
}
+
.emoji-flag_sl {
background-position: -520px -260px;
}
+
.emoji-flag_sm {
background-position: -520px -280px;
}
+
.emoji-flag_sn {
background-position: -520px -300px;
}
+
.emoji-flag_so {
background-position: -520px -320px;
}
+
.emoji-flag_sr {
background-position: -520px -340px;
}
+
.emoji-flag_ss {
background-position: -520px -360px;
}
+
.emoji-flag_st {
background-position: -520px -380px;
}
+
.emoji-flag_sv {
background-position: -520px -400px;
}
+
.emoji-flag_sx {
background-position: -520px -420px;
}
+
.emoji-flag_sy {
background-position: -520px -440px;
}
+
.emoji-flag_sz {
background-position: -520px -460px;
}
+
.emoji-flag_ta {
background-position: -520px -480px;
}
+
.emoji-flag_tc {
background-position: -520px -500px;
}
+
.emoji-flag_td {
background-position: 0 -520px;
}
+
.emoji-flag_tf {
background-position: -20px -520px;
}
+
.emoji-flag_tg {
background-position: -40px -520px;
}
+
.emoji-flag_th {
background-position: -60px -520px;
}
+
.emoji-flag_tj {
background-position: -80px -520px;
}
+
.emoji-flag_tk {
background-position: -100px -520px;
}
+
.emoji-flag_tl {
background-position: -120px -520px;
}
+
.emoji-flag_tm {
background-position: -140px -520px;
}
+
.emoji-flag_tn {
background-position: -160px -520px;
}
+
.emoji-flag_to {
background-position: -180px -520px;
}
+
.emoji-flag_tr {
background-position: -200px -520px;
}
+
.emoji-flag_tt {
background-position: -220px -520px;
}
+
.emoji-flag_tv {
background-position: -240px -520px;
}
+
.emoji-flag_tw {
background-position: -260px -520px;
}
+
.emoji-flag_tz {
background-position: -280px -520px;
}
+
.emoji-flag_ua {
background-position: -300px -520px;
}
+
.emoji-flag_ug {
background-position: -320px -520px;
}
+
.emoji-flag_um {
background-position: -340px -520px;
}
+
.emoji-flag_us {
background-position: -360px -520px;
}
+
.emoji-flag_uy {
background-position: -380px -520px;
}
+
.emoji-flag_uz {
background-position: -400px -520px;
}
+
.emoji-flag_va {
background-position: -420px -520px;
}
+
.emoji-flag_vc {
background-position: -440px -520px;
}
+
.emoji-flag_ve {
background-position: -460px -520px;
}
+
.emoji-flag_vg {
background-position: -480px -520px;
}
+
.emoji-flag_vi {
background-position: -500px -520px;
}
+
.emoji-flag_vn {
background-position: -520px -520px;
}
+
.emoji-flag_vu {
background-position: -540px 0;
}
+
.emoji-flag_wf {
background-position: -540px -20px;
}
+
.emoji-flag_white {
background-position: -540px -40px;
}
+
.emoji-flag_ws {
background-position: -540px -60px;
}
+
.emoji-flag_xk {
background-position: -540px -80px;
}
+
.emoji-flag_ye {
background-position: -540px -100px;
}
+
.emoji-flag_yt {
background-position: -540px -120px;
}
+
.emoji-flag_za {
background-position: -540px -140px;
}
+
.emoji-flag_zm {
background-position: -540px -160px;
}
+
.emoji-flag_zw {
background-position: -540px -180px;
}
+
.emoji-flags {
background-position: -540px -200px;
}
+
.emoji-flashlight {
background-position: -540px -220px;
}
+
.emoji-fleur-de-lis {
background-position: -540px -240px;
}
+
.emoji-floppy_disk {
background-position: -540px -260px;
}
+
.emoji-flower_playing_cards {
background-position: -540px -280px;
}
+
.emoji-flushed {
background-position: -540px -300px;
}
+
.emoji-fog {
background-position: -540px -320px;
}
+
.emoji-foggy {
background-position: -540px -340px;
}
+
.emoji-football {
background-position: -540px -360px;
}
+
.emoji-footprints {
background-position: -540px -380px;
}
+
.emoji-fork_and_knife {
background-position: -540px -400px;
}
+
.emoji-fork_knife_plate {
background-position: -540px -420px;
}
+
.emoji-fountain {
background-position: -540px -440px;
}
+
.emoji-four {
background-position: -540px -460px;
}
+
.emoji-four_leaf_clover {
background-position: -540px -480px;
}
+
.emoji-fox {
background-position: -540px -500px;
}
+
.emoji-frame_photo {
background-position: -540px -520px;
}
+
.emoji-free {
background-position: 0 -540px;
}
+
.emoji-french_bread {
background-position: -20px -540px;
}
+
.emoji-fried_shrimp {
background-position: -40px -540px;
}
+
.emoji-fries {
background-position: -60px -540px;
}
+
.emoji-frog {
background-position: -80px -540px;
}
+
.emoji-frowning {
background-position: -100px -540px;
}
+
.emoji-frowning2 {
background-position: -120px -540px;
}
+
.emoji-fuelpump {
background-position: -140px -540px;
}
+
.emoji-full_moon {
background-position: -160px -540px;
}
+
.emoji-full_moon_with_face {
background-position: -180px -540px;
}
+
.emoji-game_die {
background-position: -200px -540px;
}
+
.emoji-gay_pride_flag {
background-position: -220px -540px;
}
+
.emoji-gear {
background-position: -240px -540px;
}
+
.emoji-gem {
background-position: -260px -540px;
}
+
.emoji-gemini {
background-position: -280px -540px;
}
+
.emoji-ghost {
background-position: -300px -540px;
}
+
.emoji-gift {
background-position: -320px -540px;
}
+
.emoji-gift_heart {
background-position: -340px -540px;
}
+
.emoji-girl {
background-position: -360px -540px;
}
+
.emoji-girl_tone1 {
background-position: -380px -540px;
}
+
.emoji-girl_tone2 {
background-position: -400px -540px;
}
+
.emoji-girl_tone3 {
background-position: -420px -540px;
}
+
.emoji-girl_tone4 {
background-position: -440px -540px;
}
+
.emoji-girl_tone5 {
background-position: -460px -540px;
}
+
.emoji-globe_with_meridians {
background-position: -480px -540px;
}
+
.emoji-goal {
background-position: -500px -540px;
}
+
.emoji-goat {
background-position: -520px -540px;
}
+
.emoji-golf {
background-position: -540px -540px;
}
+
.emoji-golfer {
background-position: -560px 0;
}
+
.emoji-gorilla {
background-position: -560px -20px;
}
+
.emoji-grapes {
background-position: -560px -40px;
}
+
.emoji-green_apple {
background-position: -560px -60px;
}
+
.emoji-green_book {
background-position: -560px -80px;
}
+
.emoji-green_heart {
background-position: -560px -100px;
}
+
.emoji-grey_exclamation {
background-position: -560px -120px;
}
+
.emoji-grey_question {
background-position: -560px -140px;
}
+
.emoji-grimacing {
background-position: -560px -160px;
}
+
.emoji-grin {
background-position: -560px -180px;
}
+
.emoji-grinning {
background-position: -560px -200px;
}
+
.emoji-guardsman {
background-position: -560px -220px;
}
+
.emoji-guardsman_tone1 {
background-position: -560px -240px;
}
+
.emoji-guardsman_tone2 {
background-position: -560px -260px;
}
+
.emoji-guardsman_tone3 {
background-position: -560px -280px;
}
+
.emoji-guardsman_tone4 {
background-position: -560px -300px;
}
+
.emoji-guardsman_tone5 {
background-position: -560px -320px;
}
+
.emoji-guitar {
background-position: -560px -340px;
}
+
.emoji-gun {
background-position: -560px -360px;
}
+
.emoji-haircut {
background-position: -560px -380px;
}
+
.emoji-haircut_tone1 {
background-position: -560px -400px;
}
+
.emoji-haircut_tone2 {
background-position: -560px -420px;
}
+
.emoji-haircut_tone3 {
background-position: -560px -440px;
}
+
.emoji-haircut_tone4 {
background-position: -560px -460px;
}
+
.emoji-haircut_tone5 {
background-position: -560px -480px;
}
+
.emoji-hamburger {
background-position: -560px -500px;
}
+
.emoji-hammer {
background-position: -560px -520px;
}
+
.emoji-hammer_pick {
background-position: -560px -540px;
}
+
.emoji-hamster {
background-position: 0 -560px;
}
+
.emoji-hand_splayed {
background-position: -20px -560px;
}
+
.emoji-hand_splayed_tone1 {
background-position: -40px -560px;
}
+
.emoji-hand_splayed_tone2 {
background-position: -60px -560px;
}
+
.emoji-hand_splayed_tone3 {
background-position: -80px -560px;
}
+
.emoji-hand_splayed_tone4 {
background-position: -100px -560px;
}
+
.emoji-hand_splayed_tone5 {
background-position: -120px -560px;
}
+
.emoji-handbag {
background-position: -140px -560px;
}
+
.emoji-handball {
background-position: -160px -560px;
}
+
.emoji-handball_tone1 {
background-position: -180px -560px;
}
+
.emoji-handball_tone2 {
background-position: -200px -560px;
}
+
.emoji-handball_tone3 {
background-position: -220px -560px;
}
+
.emoji-handball_tone4 {
background-position: -240px -560px;
}
+
.emoji-handball_tone5 {
background-position: -260px -560px;
}
+
.emoji-handshake {
background-position: -280px -560px;
}
+
.emoji-handshake_tone1 {
background-position: -300px -560px;
}
+
.emoji-handshake_tone2 {
background-position: -320px -560px;
}
+
.emoji-handshake_tone3 {
background-position: -340px -560px;
}
+
.emoji-handshake_tone4 {
background-position: -360px -560px;
}
+
.emoji-handshake_tone5 {
background-position: -380px -560px;
}
+
.emoji-hash {
background-position: -400px -560px;
}
+
.emoji-hatched_chick {
background-position: -420px -560px;
}
+
.emoji-hatching_chick {
background-position: -440px -560px;
}
+
.emoji-head_bandage {
background-position: -460px -560px;
}
+
.emoji-headphones {
background-position: -480px -560px;
}
+
.emoji-hear_no_evil {
background-position: -500px -560px;
}
+
.emoji-heart {
background-position: -520px -560px;
}
+
.emoji-heart_decoration {
background-position: -540px -560px;
}
+
.emoji-heart_exclamation {
background-position: -560px -560px;
}
+
.emoji-heart_eyes {
background-position: -580px 0;
}
+
.emoji-heart_eyes_cat {
background-position: -580px -20px;
}
+
.emoji-heartbeat {
background-position: -580px -40px;
}
+
.emoji-heartpulse {
background-position: -580px -60px;
}
+
.emoji-hearts {
background-position: -580px -80px;
}
+
.emoji-heavy_check_mark {
background-position: -580px -100px;
}
+
.emoji-heavy_division_sign {
background-position: -580px -120px;
}
+
.emoji-heavy_dollar_sign {
background-position: -580px -140px;
}
+
.emoji-heavy_minus_sign {
background-position: -580px -160px;
}
+
.emoji-heavy_multiplication_x {
background-position: -580px -180px;
}
+
.emoji-heavy_plus_sign {
background-position: -580px -200px;
}
+
.emoji-helicopter {
background-position: -580px -220px;
}
+
.emoji-helmet_with_cross {
background-position: -580px -240px;
}
+
.emoji-herb {
background-position: -580px -260px;
}
+
.emoji-hibiscus {
background-position: -580px -280px;
}
+
.emoji-high_brightness {
background-position: -580px -300px;
}
+
.emoji-high_heel {
background-position: -580px -320px;
}
+
.emoji-hockey {
background-position: -580px -340px;
}
+
.emoji-hole {
background-position: -580px -360px;
}
+
.emoji-homes {
background-position: -580px -380px;
}
+
.emoji-honey_pot {
background-position: -580px -400px;
}
+
.emoji-horse {
background-position: -580px -420px;
}
+
.emoji-horse_racing {
background-position: -580px -440px;
}
+
.emoji-horse_racing_tone1 {
background-position: -580px -460px;
}
+
.emoji-horse_racing_tone2 {
background-position: -580px -480px;
}
+
.emoji-horse_racing_tone3 {
background-position: -580px -500px;
}
+
.emoji-horse_racing_tone4 {
background-position: -580px -520px;
}
+
.emoji-horse_racing_tone5 {
background-position: -580px -540px;
}
+
.emoji-hospital {
background-position: -580px -560px;
}
+
.emoji-hot_pepper {
background-position: 0 -580px;
}
+
.emoji-hotdog {
background-position: -20px -580px;
}
+
.emoji-hotel {
background-position: -40px -580px;
}
+
.emoji-hotsprings {
background-position: -60px -580px;
}
+
.emoji-hourglass {
background-position: -80px -580px;
}
+
.emoji-hourglass_flowing_sand {
background-position: -100px -580px;
}
+
.emoji-house {
background-position: -120px -580px;
}
+
.emoji-house_abandoned {
background-position: -140px -580px;
}
+
.emoji-house_with_garden {
background-position: -160px -580px;
}
+
.emoji-hugging {
background-position: -180px -580px;
}
+
.emoji-hushed {
background-position: -200px -580px;
}
+
.emoji-ice_cream {
background-position: -220px -580px;
}
+
.emoji-ice_skate {
background-position: -240px -580px;
}
+
.emoji-icecream {
background-position: -260px -580px;
}
+
.emoji-id {
background-position: -280px -580px;
}
+
.emoji-ideograph_advantage {
background-position: -300px -580px;
}
+
.emoji-imp {
background-position: -320px -580px;
}
+
.emoji-inbox_tray {
background-position: -340px -580px;
}
+
.emoji-incoming_envelope {
background-position: -360px -580px;
}
+
.emoji-information_desk_person {
background-position: -380px -580px;
}
+
.emoji-information_desk_person_tone1 {
background-position: -400px -580px;
}
+
.emoji-information_desk_person_tone2 {
background-position: -420px -580px;
}
+
.emoji-information_desk_person_tone3 {
background-position: -440px -580px;
}
+
.emoji-information_desk_person_tone4 {
background-position: -460px -580px;
}
+
.emoji-information_desk_person_tone5 {
background-position: -480px -580px;
}
+
.emoji-information_source {
background-position: -500px -580px;
}
+
.emoji-innocent {
background-position: -520px -580px;
}
+
.emoji-interrobang {
background-position: -540px -580px;
}
+
.emoji-iphone {
background-position: -560px -580px;
}
+
.emoji-island {
background-position: -580px -580px;
}
+
.emoji-izakaya_lantern {
background-position: -600px 0;
}
+
.emoji-jack_o_lantern {
background-position: -600px -20px;
}
+
.emoji-japan {
background-position: -600px -40px;
}
+
.emoji-japanese_castle {
background-position: -600px -60px;
}
+
.emoji-japanese_goblin {
background-position: -600px -80px;
}
+
.emoji-japanese_ogre {
background-position: -600px -100px;
}
+
.emoji-jeans {
background-position: -600px -120px;
}
+
.emoji-joy {
background-position: -600px -140px;
}
+
.emoji-joy_cat {
background-position: -600px -160px;
}
+
.emoji-joystick {
background-position: -600px -180px;
}
+
.emoji-juggling {
background-position: -600px -200px;
}
+
.emoji-juggling_tone1 {
background-position: -600px -220px;
}
+
.emoji-juggling_tone2 {
background-position: -600px -240px;
}
+
.emoji-juggling_tone3 {
background-position: -600px -260px;
}
+
.emoji-juggling_tone4 {
background-position: -600px -280px;
}
+
.emoji-juggling_tone5 {
background-position: -600px -300px;
}
+
.emoji-kaaba {
background-position: -600px -320px;
}
+
.emoji-key {
background-position: -600px -340px;
}
+
.emoji-key2 {
background-position: -600px -360px;
}
+
.emoji-keyboard {
background-position: -600px -380px;
}
+
.emoji-kimono {
background-position: -600px -400px;
}
+
.emoji-kiss {
background-position: -600px -420px;
}
+
.emoji-kiss_mm {
background-position: -600px -440px;
}
+
.emoji-kiss_ww {
background-position: -600px -460px;
}
+
.emoji-kissing {
background-position: -600px -480px;
}
+
.emoji-kissing_cat {
background-position: -600px -500px;
}
+
.emoji-kissing_closed_eyes {
background-position: -600px -520px;
}
+
.emoji-kissing_heart {
background-position: -600px -540px;
}
+
.emoji-kissing_smiling_eyes {
background-position: -600px -560px;
}
+
.emoji-kiwi {
background-position: -600px -580px;
}
+
.emoji-knife {
background-position: 0 -600px;
}
+
.emoji-koala {
background-position: -20px -600px;
}
+
.emoji-koko {
background-position: -40px -600px;
}
+
.emoji-label {
background-position: -60px -600px;
}
+
.emoji-large_blue_circle {
background-position: -80px -600px;
}
+
.emoji-large_blue_diamond {
background-position: -100px -600px;
}
+
.emoji-large_orange_diamond {
background-position: -120px -600px;
}
+
.emoji-last_quarter_moon {
background-position: -140px -600px;
}
+
.emoji-last_quarter_moon_with_face {
background-position: -160px -600px;
}
+
.emoji-laughing {
background-position: -180px -600px;
}
+
.emoji-leaves {
background-position: -200px -600px;
}
+
.emoji-ledger {
background-position: -220px -600px;
}
+
.emoji-left_facing_fist {
background-position: -240px -600px;
}
+
.emoji-left_facing_fist_tone1 {
background-position: -260px -600px;
}
+
.emoji-left_facing_fist_tone2 {
background-position: -280px -600px;
}
+
.emoji-left_facing_fist_tone3 {
background-position: -300px -600px;
}
+
.emoji-left_facing_fist_tone4 {
background-position: -320px -600px;
}
+
.emoji-left_facing_fist_tone5 {
background-position: -340px -600px;
}
+
.emoji-left_luggage {
background-position: -360px -600px;
}
+
.emoji-left_right_arrow {
background-position: -380px -600px;
}
+
.emoji-leftwards_arrow_with_hook {
background-position: -400px -600px;
}
+
.emoji-lemon {
background-position: -420px -600px;
}
+
.emoji-leo {
background-position: -440px -600px;
}
+
.emoji-leopard {
background-position: -460px -600px;
}
+
.emoji-level_slider {
background-position: -480px -600px;
}
+
.emoji-levitate {
background-position: -500px -600px;
}
+
.emoji-libra {
background-position: -520px -600px;
}
+
.emoji-lifter {
background-position: -540px -600px;
}
+
.emoji-lifter_tone1 {
background-position: -560px -600px;
}
+
.emoji-lifter_tone2 {
background-position: -580px -600px;
}
+
.emoji-lifter_tone3 {
background-position: -600px -600px;
}
+
.emoji-lifter_tone4 {
background-position: -620px 0;
}
+
.emoji-lifter_tone5 {
background-position: -620px -20px;
}
+
.emoji-light_rail {
background-position: -620px -40px;
}
+
.emoji-link {
background-position: -620px -60px;
}
+
.emoji-lion_face {
background-position: -620px -80px;
}
+
.emoji-lips {
background-position: -620px -100px;
}
+
.emoji-lipstick {
background-position: -620px -120px;
}
+
.emoji-lizard {
background-position: -620px -140px;
}
+
.emoji-lock {
background-position: -620px -160px;
}
+
.emoji-lock_with_ink_pen {
background-position: -620px -180px;
}
+
.emoji-lollipop {
background-position: -620px -200px;
}
+
.emoji-loop {
background-position: -620px -220px;
}
+
.emoji-loud_sound {
background-position: -620px -240px;
}
+
.emoji-loudspeaker {
background-position: -620px -260px;
}
+
.emoji-love_hotel {
background-position: -620px -280px;
}
+
.emoji-love_letter {
background-position: -620px -300px;
}
+
.emoji-low_brightness {
background-position: -620px -320px;
}
+
.emoji-lying_face {
background-position: -620px -340px;
}
+
.emoji-m {
background-position: -620px -360px;
}
+
.emoji-mag {
background-position: -620px -380px;
}
+
.emoji-mag_right {
background-position: -620px -400px;
}
+
.emoji-mahjong {
background-position: -620px -420px;
}
+
.emoji-mailbox {
background-position: -620px -440px;
}
+
.emoji-mailbox_closed {
background-position: -620px -460px;
}
+
.emoji-mailbox_with_mail {
background-position: -620px -480px;
}
+
.emoji-mailbox_with_no_mail {
background-position: -620px -500px;
}
+
.emoji-man {
background-position: -620px -520px;
}
+
.emoji-man_dancing {
background-position: -620px -540px;
}
+
.emoji-man_dancing_tone1 {
background-position: -620px -560px;
}
+
.emoji-man_dancing_tone2 {
background-position: -620px -580px;
}
+
.emoji-man_dancing_tone3 {
background-position: -620px -600px;
}
+
.emoji-man_dancing_tone4 {
background-position: 0 -620px;
}
+
.emoji-man_dancing_tone5 {
background-position: -20px -620px;
}
+
.emoji-man_in_tuxedo {
background-position: -40px -620px;
}
+
.emoji-man_in_tuxedo_tone1 {
background-position: -60px -620px;
}
+
.emoji-man_in_tuxedo_tone2 {
background-position: -80px -620px;
}
+
.emoji-man_in_tuxedo_tone3 {
background-position: -100px -620px;
}
+
.emoji-man_in_tuxedo_tone4 {
background-position: -120px -620px;
}
+
.emoji-man_in_tuxedo_tone5 {
background-position: -140px -620px;
}
+
.emoji-man_tone1 {
background-position: -160px -620px;
}
+
.emoji-man_tone2 {
background-position: -180px -620px;
}
+
.emoji-man_tone3 {
background-position: -200px -620px;
}
+
.emoji-man_tone4 {
background-position: -220px -620px;
}
+
.emoji-man_tone5 {
background-position: -240px -620px;
}
+
.emoji-man_with_gua_pi_mao {
background-position: -260px -620px;
}
+
.emoji-man_with_gua_pi_mao_tone1 {
background-position: -280px -620px;
}
+
.emoji-man_with_gua_pi_mao_tone2 {
background-position: -300px -620px;
}
+
.emoji-man_with_gua_pi_mao_tone3 {
background-position: -320px -620px;
}
+
.emoji-man_with_gua_pi_mao_tone4 {
background-position: -340px -620px;
}
+
.emoji-man_with_gua_pi_mao_tone5 {
background-position: -360px -620px;
}
+
.emoji-man_with_turban {
background-position: -380px -620px;
}
+
.emoji-man_with_turban_tone1 {
background-position: -400px -620px;
}
+
.emoji-man_with_turban_tone2 {
background-position: -420px -620px;
}
+
.emoji-man_with_turban_tone3 {
background-position: -440px -620px;
}
+
.emoji-man_with_turban_tone4 {
background-position: -460px -620px;
}
+
.emoji-man_with_turban_tone5 {
background-position: -480px -620px;
}
+
.emoji-mans_shoe {
background-position: -500px -620px;
}
+
.emoji-map {
background-position: -520px -620px;
}
+
.emoji-maple_leaf {
background-position: -540px -620px;
}
+
.emoji-martial_arts_uniform {
background-position: -560px -620px;
}
+
.emoji-mask {
background-position: -580px -620px;
}
+
.emoji-massage {
background-position: -600px -620px;
}
+
.emoji-massage_tone1 {
background-position: -620px -620px;
}
+
.emoji-massage_tone2 {
background-position: -640px 0;
}
+
.emoji-massage_tone3 {
background-position: -640px -20px;
}
+
.emoji-massage_tone4 {
background-position: -640px -40px;
}
+
.emoji-massage_tone5 {
background-position: -640px -60px;
}
+
.emoji-meat_on_bone {
background-position: -640px -80px;
}
+
.emoji-medal {
background-position: -640px -100px;
}
+
.emoji-mega {
background-position: -640px -120px;
}
+
.emoji-melon {
background-position: -640px -140px;
}
+
.emoji-menorah {
background-position: -640px -160px;
}
+
.emoji-mens {
background-position: -640px -180px;
}
+
.emoji-metal {
background-position: -640px -200px;
}
+
.emoji-metal_tone1 {
background-position: -640px -220px;
}
+
.emoji-metal_tone2 {
background-position: -640px -240px;
}
+
.emoji-metal_tone3 {
background-position: -640px -260px;
}
+
.emoji-metal_tone4 {
background-position: -640px -280px;
}
+
.emoji-metal_tone5 {
background-position: -640px -300px;
}
+
.emoji-metro {
background-position: -640px -320px;
}
+
.emoji-microphone {
background-position: -640px -340px;
}
+
.emoji-microphone2 {
background-position: -640px -360px;
}
+
.emoji-microscope {
background-position: -640px -380px;
}
+
.emoji-middle_finger {
background-position: -640px -400px;
}
+
.emoji-middle_finger_tone1 {
background-position: -640px -420px;
}
+
.emoji-middle_finger_tone2 {
background-position: -640px -440px;
}
+
.emoji-middle_finger_tone3 {
background-position: -640px -460px;
}
+
.emoji-middle_finger_tone4 {
background-position: -640px -480px;
}
+
.emoji-middle_finger_tone5 {
background-position: -640px -500px;
}
+
.emoji-military_medal {
background-position: -640px -520px;
}
+
.emoji-milk {
background-position: -640px -540px;
}
+
.emoji-milky_way {
background-position: -640px -560px;
}
+
.emoji-minibus {
background-position: -640px -580px;
}
+
.emoji-minidisc {
background-position: -640px -600px;
}
+
.emoji-mobile_phone_off {
background-position: -640px -620px;
}
+
.emoji-money_mouth {
background-position: 0 -640px;
}
+
.emoji-money_with_wings {
background-position: -20px -640px;
}
+
.emoji-moneybag {
background-position: -40px -640px;
}
+
.emoji-monkey {
background-position: -60px -640px;
}
+
.emoji-monkey_face {
background-position: -80px -640px;
}
+
.emoji-monorail {
background-position: -100px -640px;
}
+
.emoji-mortar_board {
background-position: -120px -640px;
}
+
.emoji-mosque {
background-position: -140px -640px;
}
+
.emoji-motor_scooter {
background-position: -160px -640px;
}
+
.emoji-motorboat {
background-position: -180px -640px;
}
+
.emoji-motorcycle {
background-position: -200px -640px;
}
+
.emoji-motorway {
background-position: -220px -640px;
}
+
.emoji-mount_fuji {
background-position: -240px -640px;
}
+
.emoji-mountain {
background-position: -260px -640px;
}
+
.emoji-mountain_bicyclist {
background-position: -280px -640px;
}
+
.emoji-mountain_bicyclist_tone1 {
background-position: -300px -640px;
}
+
.emoji-mountain_bicyclist_tone2 {
background-position: -320px -640px;
}
+
.emoji-mountain_bicyclist_tone3 {
background-position: -340px -640px;
}
+
.emoji-mountain_bicyclist_tone4 {
background-position: -360px -640px;
}
+
.emoji-mountain_bicyclist_tone5 {
background-position: -380px -640px;
}
+
.emoji-mountain_cableway {
background-position: -400px -640px;
}
+
.emoji-mountain_railway {
background-position: -420px -640px;
}
+
.emoji-mountain_snow {
background-position: -440px -640px;
}
+
.emoji-mouse {
background-position: -460px -640px;
}
+
.emoji-mouse2 {
background-position: -480px -640px;
}
+
.emoji-mouse_three_button {
background-position: -500px -640px;
}
+
.emoji-movie_camera {
background-position: -520px -640px;
}
+
.emoji-moyai {
background-position: -540px -640px;
}
+
.emoji-mrs_claus {
background-position: -560px -640px;
}
+
.emoji-mrs_claus_tone1 {
background-position: -580px -640px;
}
+
.emoji-mrs_claus_tone2 {
background-position: -600px -640px;
}
+
.emoji-mrs_claus_tone3 {
background-position: -620px -640px;
}
+
.emoji-mrs_claus_tone4 {
background-position: -640px -640px;
}
+
.emoji-mrs_claus_tone5 {
background-position: -660px 0;
}
+
.emoji-muscle {
background-position: -660px -20px;
}
+
.emoji-muscle_tone1 {
background-position: -660px -40px;
}
+
.emoji-muscle_tone2 {
background-position: -660px -60px;
}
+
.emoji-muscle_tone3 {
background-position: -660px -80px;
}
+
.emoji-muscle_tone4 {
background-position: -660px -100px;
}
+
.emoji-muscle_tone5 {
background-position: -660px -120px;
}
+
.emoji-mushroom {
background-position: -660px -140px;
}
+
.emoji-musical_keyboard {
background-position: -660px -160px;
}
+
.emoji-musical_note {
background-position: -660px -180px;
}
+
.emoji-musical_score {
background-position: -660px -200px;
}
+
.emoji-mute {
background-position: -660px -220px;
}
+
.emoji-nail_care {
background-position: -660px -240px;
}
+
.emoji-nail_care_tone1 {
background-position: -660px -260px;
}
+
.emoji-nail_care_tone2 {
background-position: -660px -280px;
}
+
.emoji-nail_care_tone3 {
background-position: -660px -300px;
}
+
.emoji-nail_care_tone4 {
background-position: -660px -320px;
}
+
.emoji-nail_care_tone5 {
background-position: -660px -340px;
}
+
.emoji-name_badge {
background-position: -660px -360px;
}
+
.emoji-nauseated_face {
background-position: -660px -380px;
}
+
.emoji-necktie {
background-position: -660px -400px;
}
+
.emoji-negative_squared_cross_mark {
background-position: -660px -420px;
}
+
.emoji-nerd {
background-position: -660px -440px;
}
+
.emoji-neutral_face {
background-position: -660px -460px;
}
+
.emoji-new {
background-position: -660px -480px;
}
+
.emoji-new_moon {
background-position: -660px -500px;
}
+
.emoji-new_moon_with_face {
background-position: -660px -520px;
}
+
.emoji-newspaper {
background-position: -660px -540px;
}
+
.emoji-newspaper2 {
background-position: -660px -560px;
}
+
.emoji-ng {
background-position: -660px -580px;
}
+
.emoji-night_with_stars {
background-position: -660px -600px;
}
+
.emoji-nine {
background-position: -660px -620px;
}
+
.emoji-no_bell {
background-position: -660px -640px;
}
+
.emoji-no_bicycles {
background-position: 0 -660px;
}
+
.emoji-no_entry {
background-position: -20px -660px;
}
+
.emoji-no_entry_sign {
background-position: -40px -660px;
}
+
.emoji-no_good {
background-position: -60px -660px;
}
+
.emoji-no_good_tone1 {
background-position: -80px -660px;
}
+
.emoji-no_good_tone2 {
background-position: -100px -660px;
}
+
.emoji-no_good_tone3 {
background-position: -120px -660px;
}
+
.emoji-no_good_tone4 {
background-position: -140px -660px;
}
+
.emoji-no_good_tone5 {
background-position: -160px -660px;
}
+
.emoji-no_mobile_phones {
background-position: -180px -660px;
}
+
.emoji-no_mouth {
background-position: -200px -660px;
}
+
.emoji-no_pedestrians {
background-position: -220px -660px;
}
+
.emoji-no_smoking {
background-position: -240px -660px;
}
+
.emoji-non-potable_water {
background-position: -260px -660px;
}
+
.emoji-nose {
background-position: -280px -660px;
}
+
.emoji-nose_tone1 {
background-position: -300px -660px;
}
+
.emoji-nose_tone2 {
background-position: -320px -660px;
}
+
.emoji-nose_tone3 {
background-position: -340px -660px;
}
+
.emoji-nose_tone4 {
background-position: -360px -660px;
}
+
.emoji-nose_tone5 {
background-position: -380px -660px;
}
+
.emoji-notebook {
background-position: -400px -660px;
}
+
.emoji-notebook_with_decorative_cover {
background-position: -420px -660px;
}
+
.emoji-notepad_spiral {
background-position: -440px -660px;
}
+
.emoji-notes {
background-position: -460px -660px;
}
+
.emoji-nut_and_bolt {
background-position: -480px -660px;
}
+
.emoji-o {
background-position: -500px -660px;
}
+
.emoji-o2 {
background-position: -520px -660px;
}
+
.emoji-ocean {
background-position: -540px -660px;
}
+
.emoji-octagonal_sign {
background-position: -560px -660px;
}
+
.emoji-octopus {
background-position: -580px -660px;
}
+
.emoji-oden {
background-position: -600px -660px;
}
+
.emoji-office {
background-position: -620px -660px;
}
+
.emoji-oil {
background-position: -640px -660px;
}
+
.emoji-ok {
background-position: -660px -660px;
}
+
.emoji-ok_hand {
background-position: -680px 0;
}
+
.emoji-ok_hand_tone1 {
background-position: -680px -20px;
}
+
.emoji-ok_hand_tone2 {
background-position: -680px -40px;
}
+
.emoji-ok_hand_tone3 {
background-position: -680px -60px;
}
+
.emoji-ok_hand_tone4 {
background-position: -680px -80px;
}
+
.emoji-ok_hand_tone5 {
background-position: -680px -100px;
}
+
.emoji-ok_woman {
background-position: -680px -120px;
}
+
.emoji-ok_woman_tone1 {
background-position: -680px -140px;
}
+
.emoji-ok_woman_tone2 {
background-position: -680px -160px;
}
+
.emoji-ok_woman_tone3 {
background-position: -680px -180px;
}
+
.emoji-ok_woman_tone4 {
background-position: -680px -200px;
}
+
.emoji-ok_woman_tone5 {
background-position: -680px -220px;
}
+
.emoji-older_man {
background-position: -680px -240px;
}
+
.emoji-older_man_tone1 {
background-position: -680px -260px;
}
+
.emoji-older_man_tone2 {
background-position: -680px -280px;
}
+
.emoji-older_man_tone3 {
background-position: -680px -300px;
}
+
.emoji-older_man_tone4 {
background-position: -680px -320px;
}
+
.emoji-older_man_tone5 {
background-position: -680px -340px;
}
+
.emoji-older_woman {
background-position: -680px -360px;
}
+
.emoji-older_woman_tone1 {
background-position: -680px -380px;
}
+
.emoji-older_woman_tone2 {
background-position: -680px -400px;
}
+
.emoji-older_woman_tone3 {
background-position: -680px -420px;
}
+
.emoji-older_woman_tone4 {
background-position: -680px -440px;
}
+
.emoji-older_woman_tone5 {
background-position: -680px -460px;
}
+
.emoji-om_symbol {
background-position: -680px -480px;
}
+
.emoji-on {
background-position: -680px -500px;
}
+
.emoji-oncoming_automobile {
background-position: -680px -520px;
}
+
.emoji-oncoming_bus {
background-position: -680px -540px;
}
+
.emoji-oncoming_police_car {
background-position: -680px -560px;
}
+
.emoji-oncoming_taxi {
background-position: -680px -580px;
}
+
.emoji-one {
background-position: -680px -600px;
}
+
.emoji-open_file_folder {
background-position: -680px -620px;
}
+
.emoji-open_hands {
background-position: -680px -640px;
}
+
.emoji-open_hands_tone1 {
background-position: -680px -660px;
}
+
.emoji-open_hands_tone2 {
background-position: 0 -680px;
}
+
.emoji-open_hands_tone3 {
background-position: -20px -680px;
}
+
.emoji-open_hands_tone4 {
background-position: -40px -680px;
}
+
.emoji-open_hands_tone5 {
background-position: -60px -680px;
}
+
.emoji-open_mouth {
background-position: -80px -680px;
}
+
.emoji-ophiuchus {
background-position: -100px -680px;
}
+
.emoji-orange_book {
background-position: -120px -680px;
}
+
.emoji-orthodox_cross {
background-position: -140px -680px;
}
+
.emoji-outbox_tray {
background-position: -160px -680px;
}
+
.emoji-owl {
background-position: -180px -680px;
}
+
.emoji-ox {
background-position: -200px -680px;
}
+
.emoji-package {
background-position: -220px -680px;
}
+
.emoji-page_facing_up {
background-position: -240px -680px;
}
+
.emoji-page_with_curl {
background-position: -260px -680px;
}
+
.emoji-pager {
background-position: -280px -680px;
}
+
.emoji-paintbrush {
background-position: -300px -680px;
}
+
.emoji-palm_tree {
background-position: -320px -680px;
}
+
.emoji-pancakes {
background-position: -340px -680px;
}
+
.emoji-panda_face {
background-position: -360px -680px;
}
+
.emoji-paperclip {
background-position: -380px -680px;
}
+
.emoji-paperclips {
background-position: -400px -680px;
}
+
.emoji-park {
background-position: -420px -680px;
}
+
.emoji-parking {
background-position: -440px -680px;
}
+
.emoji-part_alternation_mark {
background-position: -460px -680px;
}
+
.emoji-partly_sunny {
background-position: -480px -680px;
}
+
.emoji-passport_control {
background-position: -500px -680px;
}
+
.emoji-pause_button {
background-position: -520px -680px;
}
+
.emoji-peace {
background-position: -540px -680px;
}
+
.emoji-peach {
background-position: -560px -680px;
}
+
.emoji-peanuts {
background-position: -580px -680px;
}
+
.emoji-pear {
background-position: -600px -680px;
}
+
.emoji-pen_ballpoint {
background-position: -620px -680px;
}
+
.emoji-pen_fountain {
background-position: -640px -680px;
}
+
.emoji-pencil {
background-position: -660px -680px;
}
+
.emoji-pencil2 {
background-position: -680px -680px;
}
+
.emoji-penguin {
background-position: -700px 0;
}
+
.emoji-pensive {
background-position: -700px -20px;
}
+
.emoji-performing_arts {
background-position: -700px -40px;
}
+
.emoji-persevere {
background-position: -700px -60px;
}
+
.emoji-person_frowning {
background-position: -700px -80px;
}
+
.emoji-person_frowning_tone1 {
background-position: -700px -100px;
}
+
.emoji-person_frowning_tone2 {
background-position: -700px -120px;
}
+
.emoji-person_frowning_tone3 {
background-position: -700px -140px;
}
+
.emoji-person_frowning_tone4 {
background-position: -700px -160px;
}
+
.emoji-person_frowning_tone5 {
background-position: -700px -180px;
}
+
.emoji-person_with_blond_hair {
background-position: -700px -200px;
}
+
.emoji-person_with_blond_hair_tone1 {
background-position: -700px -220px;
}
+
.emoji-person_with_blond_hair_tone2 {
background-position: -700px -240px;
}
+
.emoji-person_with_blond_hair_tone3 {
background-position: -700px -260px;
}
+
.emoji-person_with_blond_hair_tone4 {
background-position: -700px -280px;
}
+
.emoji-person_with_blond_hair_tone5 {
background-position: -700px -300px;
}
+
.emoji-person_with_pouting_face {
background-position: -700px -320px;
}
+
.emoji-person_with_pouting_face_tone1 {
background-position: -700px -340px;
}
+
.emoji-person_with_pouting_face_tone2 {
background-position: -700px -360px;
}
+
.emoji-person_with_pouting_face_tone3 {
background-position: -700px -380px;
}
+
.emoji-person_with_pouting_face_tone4 {
background-position: -700px -400px;
}
+
.emoji-person_with_pouting_face_tone5 {
background-position: -700px -420px;
}
+
.emoji-pick {
background-position: -700px -440px;
}
+
.emoji-pig {
background-position: -700px -460px;
}
+
.emoji-pig2 {
background-position: -700px -480px;
}
+
.emoji-pig_nose {
background-position: -700px -500px;
}
+
.emoji-pill {
background-position: -700px -520px;
}
+
.emoji-pineapple {
background-position: -700px -540px;
}
+
.emoji-ping_pong {
background-position: -700px -560px;
}
+
.emoji-pisces {
background-position: -700px -580px;
}
+
.emoji-pizza {
background-position: -700px -600px;
}
+
.emoji-place_of_worship {
background-position: -700px -620px;
}
+
.emoji-play_pause {
background-position: -700px -640px;
}
+
.emoji-point_down {
background-position: -700px -660px;
}
+
.emoji-point_down_tone1 {
background-position: -700px -680px;
}
+
.emoji-point_down_tone2 {
background-position: 0 -700px;
}
+
.emoji-point_down_tone3 {
background-position: -20px -700px;
}
+
.emoji-point_down_tone4 {
background-position: -40px -700px;
}
+
.emoji-point_down_tone5 {
background-position: -60px -700px;
}
+
.emoji-point_left {
background-position: -80px -700px;
}
+
.emoji-point_left_tone1 {
background-position: -100px -700px;
}
+
.emoji-point_left_tone2 {
background-position: -120px -700px;
}
+
.emoji-point_left_tone3 {
background-position: -140px -700px;
}
+
.emoji-point_left_tone4 {
background-position: -160px -700px;
}
+
.emoji-point_left_tone5 {
background-position: -180px -700px;
}
+
.emoji-point_right {
background-position: -200px -700px;
}
+
.emoji-point_right_tone1 {
background-position: -220px -700px;
}
+
.emoji-point_right_tone2 {
background-position: -240px -700px;
}
+
.emoji-point_right_tone3 {
background-position: -260px -700px;
}
+
.emoji-point_right_tone4 {
background-position: -280px -700px;
}
+
.emoji-point_right_tone5 {
background-position: -300px -700px;
}
+
.emoji-point_up {
background-position: -320px -700px;
}
+
.emoji-point_up_2 {
background-position: -340px -700px;
}
+
.emoji-point_up_2_tone1 {
background-position: -360px -700px;
}
+
.emoji-point_up_2_tone2 {
background-position: -380px -700px;
}
+
.emoji-point_up_2_tone3 {
background-position: -400px -700px;
}
+
.emoji-point_up_2_tone4 {
background-position: -420px -700px;
}
+
.emoji-point_up_2_tone5 {
background-position: -440px -700px;
}
+
.emoji-point_up_tone1 {
background-position: -460px -700px;
}
+
.emoji-point_up_tone2 {
background-position: -480px -700px;
}
+
.emoji-point_up_tone3 {
background-position: -500px -700px;
}
+
.emoji-point_up_tone4 {
background-position: -520px -700px;
}
+
.emoji-point_up_tone5 {
background-position: -540px -700px;
}
+
.emoji-police_car {
background-position: -560px -700px;
}
+
.emoji-poodle {
background-position: -580px -700px;
}
+
.emoji-poop {
background-position: -600px -700px;
}
+
.emoji-popcorn {
background-position: -620px -700px;
}
+
.emoji-post_office {
background-position: -640px -700px;
}
+
.emoji-postal_horn {
background-position: -660px -700px;
}
+
.emoji-postbox {
background-position: -680px -700px;
}
+
.emoji-potable_water {
background-position: -700px -700px;
}
+
.emoji-potato {
background-position: -720px 0;
}
+
.emoji-pouch {
background-position: -720px -20px;
}
+
.emoji-poultry_leg {
background-position: -720px -40px;
}
+
.emoji-pound {
background-position: -720px -60px;
}
+
.emoji-pouting_cat {
background-position: -720px -80px;
}
+
.emoji-pray {
background-position: -720px -100px;
}
+
.emoji-pray_tone1 {
background-position: -720px -120px;
}
+
.emoji-pray_tone2 {
background-position: -720px -140px;
}
+
.emoji-pray_tone3 {
background-position: -720px -160px;
}
+
.emoji-pray_tone4 {
background-position: -720px -180px;
}
+
.emoji-pray_tone5 {
background-position: -720px -200px;
}
+
.emoji-prayer_beads {
background-position: -720px -220px;
}
+
.emoji-pregnant_woman {
background-position: -720px -240px;
}
+
.emoji-pregnant_woman_tone1 {
background-position: -720px -260px;
}
+
.emoji-pregnant_woman_tone2 {
background-position: -720px -280px;
}
+
.emoji-pregnant_woman_tone3 {
background-position: -720px -300px;
}
+
.emoji-pregnant_woman_tone4 {
background-position: -720px -320px;
}
+
.emoji-pregnant_woman_tone5 {
background-position: -720px -340px;
}
+
.emoji-prince {
background-position: -720px -360px;
}
+
.emoji-prince_tone1 {
background-position: -720px -380px;
}
+
.emoji-prince_tone2 {
background-position: -720px -400px;
}
+
.emoji-prince_tone3 {
background-position: -720px -420px;
}
+
.emoji-prince_tone4 {
background-position: -720px -440px;
}
+
.emoji-prince_tone5 {
background-position: -720px -460px;
}
+
.emoji-princess {
background-position: -720px -480px;
}
+
.emoji-princess_tone1 {
background-position: -720px -500px;
}
+
.emoji-princess_tone2 {
background-position: -720px -520px;
}
+
.emoji-princess_tone3 {
background-position: -720px -540px;
}
+
.emoji-princess_tone4 {
background-position: -720px -560px;
}
+
.emoji-princess_tone5 {
background-position: -720px -580px;
}
+
.emoji-printer {
background-position: -720px -600px;
}
+
.emoji-projector {
background-position: -720px -620px;
}
+
.emoji-punch {
background-position: -720px -640px;
}
+
.emoji-punch_tone1 {
background-position: -720px -660px;
}
+
.emoji-punch_tone2 {
background-position: -720px -680px;
}
+
.emoji-punch_tone3 {
background-position: -720px -700px;
}
+
.emoji-punch_tone4 {
background-position: 0 -720px;
}
+
.emoji-punch_tone5 {
background-position: -20px -720px;
}
+
.emoji-purple_heart {
background-position: -40px -720px;
}
+
.emoji-purse {
background-position: -60px -720px;
}
+
.emoji-pushpin {
background-position: -80px -720px;
}
+
.emoji-put_litter_in_its_place {
background-position: -100px -720px;
}
+
.emoji-question {
background-position: -120px -720px;
}
+
.emoji-rabbit {
background-position: -140px -720px;
}
+
.emoji-rabbit2 {
background-position: -160px -720px;
}
+
.emoji-race_car {
background-position: -180px -720px;
}
+
.emoji-racehorse {
background-position: -200px -720px;
}
+
.emoji-radio {
background-position: -220px -720px;
}
+
.emoji-radio_button {
background-position: -240px -720px;
}
+
.emoji-radioactive {
background-position: -260px -720px;
}
+
.emoji-rage {
background-position: -280px -720px;
}
+
.emoji-railway_car {
background-position: -300px -720px;
}
+
.emoji-railway_track {
background-position: -320px -720px;
}
+
.emoji-rainbow {
background-position: -340px -720px;
}
+
.emoji-raised_back_of_hand {
background-position: -360px -720px;
}
+
.emoji-raised_back_of_hand_tone1 {
background-position: -380px -720px;
}
+
.emoji-raised_back_of_hand_tone2 {
background-position: -400px -720px;
}
+
.emoji-raised_back_of_hand_tone3 {
background-position: -420px -720px;
}
+
.emoji-raised_back_of_hand_tone4 {
background-position: -440px -720px;
}
+
.emoji-raised_back_of_hand_tone5 {
background-position: -460px -720px;
}
+
.emoji-raised_hand {
background-position: -480px -720px;
}
+
.emoji-raised_hand_tone1 {
background-position: -500px -720px;
}
+
.emoji-raised_hand_tone2 {
background-position: -520px -720px;
}
+
.emoji-raised_hand_tone3 {
background-position: -540px -720px;
}
+
.emoji-raised_hand_tone4 {
background-position: -560px -720px;
}
+
.emoji-raised_hand_tone5 {
background-position: -580px -720px;
}
+
.emoji-raised_hands {
background-position: -600px -720px;
}
+
.emoji-raised_hands_tone1 {
background-position: -620px -720px;
}
+
.emoji-raised_hands_tone2 {
background-position: -640px -720px;
}
+
.emoji-raised_hands_tone3 {
background-position: -660px -720px;
}
+
.emoji-raised_hands_tone4 {
background-position: -680px -720px;
}
+
.emoji-raised_hands_tone5 {
background-position: -700px -720px;
}
+
.emoji-raising_hand {
background-position: -720px -720px;
}
+
.emoji-raising_hand_tone1 {
background-position: -740px 0;
}
+
.emoji-raising_hand_tone2 {
background-position: -740px -20px;
}
+
.emoji-raising_hand_tone3 {
background-position: -740px -40px;
}
+
.emoji-raising_hand_tone4 {
background-position: -740px -60px;
}
+
.emoji-raising_hand_tone5 {
background-position: -740px -80px;
}
+
.emoji-ram {
background-position: -740px -100px;
}
+
.emoji-ramen {
background-position: -740px -120px;
}
+
.emoji-rat {
background-position: -740px -140px;
}
+
.emoji-record_button {
background-position: -740px -160px;
}
+
.emoji-recycle {
background-position: -740px -180px;
}
+
.emoji-red_car {
background-position: -740px -200px;
}
+
.emoji-red_circle {
background-position: -740px -220px;
}
+
.emoji-registered {
background-position: -740px -240px;
}
+
.emoji-relaxed {
background-position: -740px -260px;
}
+
.emoji-relieved {
background-position: -740px -280px;
}
+
.emoji-reminder_ribbon {
background-position: -740px -300px;
}
+
.emoji-repeat {
background-position: -740px -320px;
}
+
.emoji-repeat_one {
background-position: -740px -340px;
}
+
.emoji-restroom {
background-position: -740px -360px;
}
+
.emoji-revolving_hearts {
background-position: -740px -380px;
}
+
.emoji-rewind {
background-position: -740px -400px;
}
+
.emoji-rhino {
background-position: -740px -420px;
}
+
.emoji-ribbon {
background-position: -740px -440px;
}
+
.emoji-rice {
background-position: -740px -460px;
}
+
.emoji-rice_ball {
background-position: -740px -480px;
}
+
.emoji-rice_cracker {
background-position: -740px -500px;
}
+
.emoji-rice_scene {
background-position: -740px -520px;
}
+
.emoji-right_facing_fist {
background-position: -740px -540px;
}
+
.emoji-right_facing_fist_tone1 {
background-position: -740px -560px;
}
+
.emoji-right_facing_fist_tone2 {
background-position: -740px -580px;
}
+
.emoji-right_facing_fist_tone3 {
background-position: -740px -600px;
}
+
.emoji-right_facing_fist_tone4 {
background-position: -740px -620px;
}
+
.emoji-right_facing_fist_tone5 {
background-position: -740px -640px;
}
+
.emoji-ring {
background-position: -740px -660px;
}
+
.emoji-robot {
background-position: -740px -680px;
}
+
.emoji-rocket {
background-position: -740px -700px;
}
+
.emoji-rofl {
background-position: -740px -720px;
}
+
.emoji-roller_coaster {
background-position: 0 -740px;
}
+
.emoji-rolling_eyes {
background-position: -20px -740px;
}
+
.emoji-rooster {
background-position: -40px -740px;
}
+
.emoji-rose {
background-position: -60px -740px;
}
+
.emoji-rosette {
background-position: -80px -740px;
}
+
.emoji-rotating_light {
background-position: -100px -740px;
}
+
.emoji-round_pushpin {
background-position: -120px -740px;
}
+
.emoji-rowboat {
background-position: -140px -740px;
}
+
.emoji-rowboat_tone1 {
background-position: -160px -740px;
}
+
.emoji-rowboat_tone2 {
background-position: -180px -740px;
}
+
.emoji-rowboat_tone3 {
background-position: -200px -740px;
}
+
.emoji-rowboat_tone4 {
background-position: -220px -740px;
}
+
.emoji-rowboat_tone5 {
background-position: -240px -740px;
}
+
.emoji-rugby_football {
background-position: -260px -740px;
}
+
.emoji-runner {
background-position: -280px -740px;
}
+
.emoji-runner_tone1 {
background-position: -300px -740px;
}
+
.emoji-runner_tone2 {
background-position: -320px -740px;
}
+
.emoji-runner_tone3 {
background-position: -340px -740px;
}
+
.emoji-runner_tone4 {
background-position: -360px -740px;
}
+
.emoji-runner_tone5 {
background-position: -380px -740px;
}
+
.emoji-running_shirt_with_sash {
background-position: -400px -740px;
}
+
.emoji-sa {
background-position: -420px -740px;
}
+
.emoji-sagittarius {
background-position: -440px -740px;
}
+
.emoji-sailboat {
background-position: -460px -740px;
}
+
.emoji-sake {
background-position: -480px -740px;
}
+
.emoji-salad {
background-position: -500px -740px;
}
+
.emoji-sandal {
background-position: -520px -740px;
}
+
.emoji-santa {
background-position: -540px -740px;
}
+
.emoji-santa_tone1 {
background-position: -560px -740px;
}
+
.emoji-santa_tone2 {
background-position: -580px -740px;
}
+
.emoji-santa_tone3 {
background-position: -600px -740px;
}
+
.emoji-santa_tone4 {
background-position: -620px -740px;
}
+
.emoji-santa_tone5 {
background-position: -640px -740px;
}
+
.emoji-satellite {
background-position: -660px -740px;
}
+
.emoji-satellite_orbital {
background-position: -680px -740px;
}
+
.emoji-saxophone {
background-position: -700px -740px;
}
+
.emoji-scales {
background-position: -720px -740px;
}
+
.emoji-school {
background-position: -740px -740px;
}
+
.emoji-school_satchel {
background-position: -760px 0;
}
+
.emoji-scissors {
background-position: -760px -20px;
}
+
.emoji-scooter {
background-position: -760px -40px;
}
+
.emoji-scorpion {
background-position: -760px -60px;
}
+
.emoji-scorpius {
background-position: -760px -80px;
}
+
.emoji-scream {
background-position: -760px -100px;
}
+
.emoji-scream_cat {
background-position: -760px -120px;
}
+
.emoji-scroll {
background-position: -760px -140px;
}
+
.emoji-seat {
background-position: -760px -160px;
}
+
.emoji-second_place {
background-position: -760px -180px;
}
+
.emoji-secret {
background-position: -760px -200px;
}
+
.emoji-see_no_evil {
background-position: -760px -220px;
}
+
.emoji-seedling {
background-position: -760px -240px;
}
+
.emoji-selfie {
background-position: -760px -260px;
}
+
.emoji-selfie_tone1 {
background-position: -760px -280px;
}
+
.emoji-selfie_tone2 {
background-position: -760px -300px;
}
+
.emoji-selfie_tone3 {
background-position: -760px -320px;
}
+
.emoji-selfie_tone4 {
background-position: -760px -340px;
}
+
.emoji-selfie_tone5 {
background-position: -760px -360px;
}
+
.emoji-seven {
background-position: -760px -380px;
}
+
.emoji-shallow_pan_of_food {
background-position: -760px -400px;
}
+
.emoji-shamrock {
background-position: -760px -420px;
}
+
.emoji-shark {
background-position: -760px -440px;
}
+
.emoji-shaved_ice {
background-position: -760px -460px;
}
+
.emoji-sheep {
background-position: -760px -480px;
}
+
.emoji-shell {
background-position: -760px -500px;
}
+
.emoji-shield {
background-position: -760px -520px;
}
+
.emoji-shinto_shrine {
background-position: -760px -540px;
}
+
.emoji-ship {
background-position: -760px -560px;
}
+
.emoji-shirt {
background-position: -760px -580px;
}
+
.emoji-shopping_bags {
background-position: -760px -600px;
}
+
.emoji-shopping_cart {
background-position: -760px -620px;
}
+
.emoji-shower {
background-position: -760px -640px;
}
+
.emoji-shrimp {
background-position: -760px -660px;
}
+
.emoji-shrug {
background-position: -760px -680px;
}
+
.emoji-shrug_tone1 {
background-position: -760px -700px;
}
+
.emoji-shrug_tone2 {
background-position: -760px -720px;
}
+
.emoji-shrug_tone3 {
background-position: -760px -740px;
}
+
.emoji-shrug_tone4 {
background-position: 0 -760px;
}
+
.emoji-shrug_tone5 {
background-position: -20px -760px;
}
+
.emoji-signal_strength {
background-position: -40px -760px;
}
+
.emoji-six {
background-position: -60px -760px;
}
+
.emoji-six_pointed_star {
background-position: -80px -760px;
}
+
.emoji-ski {
background-position: -100px -760px;
}
+
.emoji-skier {
background-position: -120px -760px;
}
+
.emoji-skull {
background-position: -140px -760px;
}
+
.emoji-skull_crossbones {
background-position: -160px -760px;
}
+
.emoji-sleeping {
background-position: -180px -760px;
}
+
.emoji-sleeping_accommodation {
background-position: -200px -760px;
}
+
.emoji-sleepy {
background-position: -220px -760px;
}
+
.emoji-slight_frown {
background-position: -240px -760px;
}
+
.emoji-slight_smile {
background-position: -260px -760px;
}
+
.emoji-slot_machine {
background-position: -280px -760px;
}
+
.emoji-small_blue_diamond {
background-position: -300px -760px;
}
+
.emoji-small_orange_diamond {
background-position: -320px -760px;
}
+
.emoji-small_red_triangle {
background-position: -340px -760px;
}
+
.emoji-small_red_triangle_down {
background-position: -360px -760px;
}
+
.emoji-smile {
background-position: -380px -760px;
}
+
.emoji-smile_cat {
background-position: -400px -760px;
}
+
.emoji-smiley {
background-position: -420px -760px;
}
+
.emoji-smiley_cat {
background-position: -440px -760px;
}
+
.emoji-smiling_imp {
background-position: -460px -760px;
}
+
.emoji-smirk {
background-position: -480px -760px;
}
+
.emoji-smirk_cat {
background-position: -500px -760px;
}
+
.emoji-smoking {
background-position: -520px -760px;
}
+
.emoji-snail {
background-position: -540px -760px;
}
+
.emoji-snake {
background-position: -560px -760px;
}
+
.emoji-sneezing_face {
background-position: -580px -760px;
}
+
.emoji-snowboarder {
background-position: -600px -760px;
}
+
.emoji-snowflake {
background-position: -620px -760px;
}
+
.emoji-snowman {
background-position: -640px -760px;
}
+
.emoji-snowman2 {
background-position: -660px -760px;
}
+
.emoji-sob {
background-position: -680px -760px;
}
+
.emoji-soccer {
background-position: -700px -760px;
}
+
.emoji-soon {
background-position: -720px -760px;
}
+
.emoji-sos {
background-position: -740px -760px;
}
+
.emoji-sound {
background-position: -760px -760px;
}
+
.emoji-space_invader {
background-position: -780px 0;
}
+
.emoji-spades {
background-position: -780px -20px;
}
+
.emoji-spaghetti {
background-position: -780px -40px;
}
+
.emoji-sparkle {
background-position: -780px -60px;
}
+
.emoji-sparkler {
background-position: -780px -80px;
}
+
.emoji-sparkles {
background-position: -780px -100px;
}
+
.emoji-sparkling_heart {
background-position: -780px -120px;
}
+
.emoji-speak_no_evil {
background-position: -780px -140px;
}
+
.emoji-speaker {
background-position: -780px -160px;
}
+
.emoji-speaking_head {
background-position: -780px -180px;
}
+
.emoji-speech_balloon {
background-position: -780px -200px;
}
+
.emoji-speech_left {
background-position: -780px -220px;
}
+
.emoji-speedboat {
background-position: -780px -240px;
}
+
.emoji-spider {
background-position: -780px -260px;
}
+
.emoji-spider_web {
background-position: -780px -280px;
}
+
.emoji-spoon {
background-position: -780px -300px;
}
+
.emoji-spy {
background-position: -780px -320px;
}
+
.emoji-spy_tone1 {
background-position: -780px -340px;
}
+
.emoji-spy_tone2 {
background-position: -780px -360px;
}
+
.emoji-spy_tone3 {
background-position: -780px -380px;
}
+
.emoji-spy_tone4 {
background-position: -780px -400px;
}
+
.emoji-spy_tone5 {
background-position: -780px -420px;
}
+
.emoji-squid {
background-position: -780px -440px;
}
+
.emoji-stadium {
background-position: -780px -460px;
}
+
.emoji-star {
background-position: -780px -480px;
}
+
.emoji-star2 {
background-position: -780px -500px;
}
+
.emoji-star_and_crescent {
background-position: -780px -520px;
}
+
.emoji-star_of_david {
background-position: -780px -540px;
}
+
.emoji-stars {
background-position: -780px -560px;
}
+
.emoji-station {
background-position: -780px -580px;
}
+
.emoji-statue_of_liberty {
background-position: -780px -600px;
}
+
.emoji-steam_locomotive {
background-position: -780px -620px;
}
+
.emoji-stew {
background-position: -780px -640px;
}
+
.emoji-stop_button {
background-position: -780px -660px;
}
+
.emoji-stopwatch {
background-position: -780px -680px;
}
+
.emoji-straight_ruler {
background-position: -780px -700px;
}
+
.emoji-strawberry {
background-position: -780px -720px;
}
+
.emoji-stuck_out_tongue {
background-position: -780px -740px;
}
+
.emoji-stuck_out_tongue_closed_eyes {
background-position: -780px -760px;
}
+
.emoji-stuck_out_tongue_winking_eye {
background-position: 0 -780px;
}
+
.emoji-stuffed_flatbread {
background-position: -20px -780px;
}
+
.emoji-sun_with_face {
background-position: -40px -780px;
}
+
.emoji-sunflower {
background-position: -60px -780px;
}
+
.emoji-sunglasses {
background-position: -80px -780px;
}
+
.emoji-sunny {
background-position: -100px -780px;
}
+
.emoji-sunrise {
background-position: -120px -780px;
}
+
.emoji-sunrise_over_mountains {
background-position: -140px -780px;
}
+
.emoji-surfer {
background-position: -160px -780px;
}
+
.emoji-surfer_tone1 {
background-position: -180px -780px;
}
+
.emoji-surfer_tone2 {
background-position: -200px -780px;
}
+
.emoji-surfer_tone3 {
background-position: -220px -780px;
}
+
.emoji-surfer_tone4 {
background-position: -240px -780px;
}
+
.emoji-surfer_tone5 {
background-position: -260px -780px;
}
+
.emoji-sushi {
background-position: -280px -780px;
}
+
.emoji-suspension_railway {
background-position: -300px -780px;
}
+
.emoji-sweat {
background-position: -320px -780px;
}
+
.emoji-sweat_drops {
background-position: -340px -780px;
}
+
.emoji-sweat_smile {
background-position: -360px -780px;
}
+
.emoji-sweet_potato {
background-position: -380px -780px;
}
+
.emoji-swimmer {
background-position: -400px -780px;
}
+
.emoji-swimmer_tone1 {
background-position: -420px -780px;
}
+
.emoji-swimmer_tone2 {
background-position: -440px -780px;
}
+
.emoji-swimmer_tone3 {
background-position: -460px -780px;
}
+
.emoji-swimmer_tone4 {
background-position: -480px -780px;
}
+
.emoji-swimmer_tone5 {
background-position: -500px -780px;
}
+
.emoji-symbols {
background-position: -520px -780px;
}
+
.emoji-synagogue {
background-position: -540px -780px;
}
+
.emoji-syringe {
background-position: -560px -780px;
}
+
.emoji-taco {
background-position: -580px -780px;
}
+
.emoji-tada {
background-position: -600px -780px;
}
+
.emoji-tanabata_tree {
background-position: -620px -780px;
}
+
.emoji-tangerine {
background-position: -640px -780px;
}
+
.emoji-taurus {
background-position: -660px -780px;
}
+
.emoji-taxi {
background-position: -680px -780px;
}
+
.emoji-tea {
background-position: -700px -780px;
}
+
.emoji-telephone {
background-position: -720px -780px;
}
+
.emoji-telephone_receiver {
background-position: -740px -780px;
}
+
.emoji-telescope {
background-position: -760px -780px;
}
+
.emoji-ten {
background-position: -780px -780px;
}
+
.emoji-tennis {
background-position: -800px 0;
}
+
.emoji-tent {
background-position: -800px -20px;
}
+
.emoji-thermometer {
background-position: -800px -40px;
}
+
.emoji-thermometer_face {
background-position: -800px -60px;
}
+
.emoji-thinking {
background-position: -800px -80px;
}
+
.emoji-third_place {
background-position: -800px -100px;
}
+
.emoji-thought_balloon {
background-position: -800px -120px;
}
+
.emoji-three {
background-position: -800px -140px;
}
+
.emoji-thumbsdown {
background-position: -800px -160px;
}
+
.emoji-thumbsdown_tone1 {
background-position: -800px -180px;
}
+
.emoji-thumbsdown_tone2 {
background-position: -800px -200px;
}
+
.emoji-thumbsdown_tone3 {
background-position: -800px -220px;
}
+
.emoji-thumbsdown_tone4 {
background-position: -800px -240px;
}
+
.emoji-thumbsdown_tone5 {
background-position: -800px -260px;
}
+
.emoji-thumbsup {
background-position: -800px -280px;
}
+
.emoji-thumbsup_tone1 {
background-position: -800px -300px;
}
+
.emoji-thumbsup_tone2 {
background-position: -800px -320px;
}
+
.emoji-thumbsup_tone3 {
background-position: -800px -340px;
}
+
.emoji-thumbsup_tone4 {
background-position: -800px -360px;
}
+
.emoji-thumbsup_tone5 {
background-position: -800px -380px;
}
+
.emoji-thunder_cloud_rain {
background-position: -800px -400px;
}
+
.emoji-ticket {
background-position: -800px -420px;
}
+
.emoji-tickets {
background-position: -800px -440px;
}
+
.emoji-tiger {
background-position: -800px -460px;
}
+
.emoji-tiger2 {
background-position: -800px -480px;
}
+
.emoji-timer {
background-position: -800px -500px;
}
+
.emoji-tired_face {
background-position: -800px -520px;
}
+
.emoji-tm {
background-position: -800px -540px;
}
+
.emoji-toilet {
background-position: -800px -560px;
}
+
.emoji-tokyo_tower {
background-position: -800px -580px;
}
+
.emoji-tomato {
background-position: -800px -600px;
}
+
.emoji-tone1 {
background-position: -800px -620px;
}
+
.emoji-tone2 {
background-position: -800px -640px;
}
+
.emoji-tone3 {
background-position: -800px -660px;
}
+
.emoji-tone4 {
background-position: -800px -680px;
}
+
.emoji-tone5 {
background-position: -800px -700px;
}
+
.emoji-tongue {
background-position: -800px -720px;
}
+
.emoji-tools {
background-position: -800px -740px;
}
+
.emoji-top {
background-position: -800px -760px;
}
+
.emoji-tophat {
background-position: -800px -780px;
}
+
.emoji-track_next {
background-position: 0 -800px;
}
+
.emoji-track_previous {
background-position: -20px -800px;
}
+
.emoji-trackball {
background-position: -40px -800px;
}
+
.emoji-tractor {
background-position: -60px -800px;
}
+
.emoji-traffic_light {
background-position: -80px -800px;
}
+
.emoji-train {
background-position: -100px -800px;
}
+
.emoji-train2 {
background-position: -120px -800px;
}
+
.emoji-tram {
background-position: -140px -800px;
}
+
.emoji-triangular_flag_on_post {
background-position: -160px -800px;
}
+
.emoji-triangular_ruler {
background-position: -180px -800px;
}
+
.emoji-trident {
background-position: -200px -800px;
}
+
.emoji-triumph {
background-position: -220px -800px;
}
+
.emoji-trolleybus {
background-position: -240px -800px;
}
+
.emoji-trophy {
background-position: -260px -800px;
}
+
.emoji-tropical_drink {
background-position: -280px -800px;
}
+
.emoji-tropical_fish {
background-position: -300px -800px;
}
+
.emoji-truck {
background-position: -320px -800px;
}
+
.emoji-trumpet {
background-position: -340px -800px;
}
+
.emoji-tulip {
background-position: -360px -800px;
}
+
.emoji-tumbler_glass {
background-position: -380px -800px;
}
+
.emoji-turkey {
background-position: -400px -800px;
}
+
.emoji-turtle {
background-position: -420px -800px;
}
+
.emoji-tv {
background-position: -440px -800px;
}
+
.emoji-twisted_rightwards_arrows {
background-position: -460px -800px;
}
+
.emoji-two {
background-position: -480px -800px;
}
+
.emoji-two_hearts {
background-position: -500px -800px;
}
+
.emoji-two_men_holding_hands {
background-position: -520px -800px;
}
+
.emoji-two_women_holding_hands {
background-position: -540px -800px;
}
+
.emoji-u5272 {
background-position: -560px -800px;
}
+
.emoji-u5408 {
background-position: -580px -800px;
}
+
.emoji-u55b6 {
background-position: -600px -800px;
}
+
.emoji-u6307 {
background-position: -620px -800px;
}
+
.emoji-u6708 {
background-position: -640px -800px;
}
+
.emoji-u6709 {
background-position: -660px -800px;
}
+
.emoji-u6e80 {
background-position: -680px -800px;
}
+
.emoji-u7121 {
background-position: -700px -800px;
}
+
.emoji-u7533 {
background-position: -720px -800px;
}
+
.emoji-u7981 {
background-position: -740px -800px;
}
+
.emoji-u7a7a {
background-position: -760px -800px;
}
+
.emoji-umbrella {
background-position: -780px -800px;
}
+
.emoji-umbrella2 {
background-position: -800px -800px;
}
+
.emoji-unamused {
background-position: -820px 0;
}
+
.emoji-underage {
background-position: -820px -20px;
}
+
.emoji-unicorn {
background-position: -820px -40px;
}
+
.emoji-unlock {
background-position: -820px -60px;
}
+
.emoji-up {
background-position: -820px -80px;
}
+
.emoji-upside_down {
background-position: -820px -100px;
}
+
.emoji-urn {
background-position: -820px -120px;
}
+
.emoji-v {
background-position: -820px -140px;
}
+
.emoji-v_tone1 {
background-position: -820px -160px;
}
+
.emoji-v_tone2 {
background-position: -820px -180px;
}
+
.emoji-v_tone3 {
background-position: -820px -200px;
}
+
.emoji-v_tone4 {
background-position: -820px -220px;
}
+
.emoji-v_tone5 {
background-position: -820px -240px;
}
+
.emoji-vertical_traffic_light {
background-position: -820px -260px;
}
+
.emoji-vhs {
background-position: -820px -280px;
}
+
.emoji-vibration_mode {
background-position: -820px -300px;
}
+
.emoji-video_camera {
background-position: -820px -320px;
}
+
.emoji-video_game {
background-position: -820px -340px;
}
+
.emoji-violin {
background-position: -820px -360px;
}
+
.emoji-virgo {
background-position: -820px -380px;
}
+
.emoji-volcano {
background-position: -820px -400px;
}
+
.emoji-volleyball {
background-position: -820px -420px;
}
+
.emoji-vs {
background-position: -820px -440px;
}
+
.emoji-vulcan {
background-position: -820px -460px;
}
+
.emoji-vulcan_tone1 {
background-position: -820px -480px;
}
+
.emoji-vulcan_tone2 {
background-position: -820px -500px;
}
+
.emoji-vulcan_tone3 {
background-position: -820px -520px;
}
+
.emoji-vulcan_tone4 {
background-position: -820px -540px;
}
+
.emoji-vulcan_tone5 {
background-position: -820px -560px;
}
+
.emoji-walking {
background-position: -820px -580px;
}
+
.emoji-walking_tone1 {
background-position: -820px -600px;
}
+
.emoji-walking_tone2 {
background-position: -820px -620px;
}
+
.emoji-walking_tone3 {
background-position: -820px -640px;
}
+
.emoji-walking_tone4 {
background-position: -820px -660px;
}
+
.emoji-walking_tone5 {
background-position: -820px -680px;
}
+
.emoji-waning_crescent_moon {
background-position: -820px -700px;
}
+
.emoji-waning_gibbous_moon {
background-position: -820px -720px;
}
+
.emoji-warning {
background-position: -820px -740px;
}
+
.emoji-wastebasket {
background-position: -820px -760px;
}
+
.emoji-watch {
background-position: -820px -780px;
}
+
.emoji-water_buffalo {
background-position: -820px -800px;
}
+
.emoji-water_polo {
background-position: 0 -820px;
}
+
.emoji-water_polo_tone1 {
background-position: -20px -820px;
}
+
.emoji-water_polo_tone2 {
background-position: -40px -820px;
}
+
.emoji-water_polo_tone3 {
background-position: -60px -820px;
}
+
.emoji-water_polo_tone4 {
background-position: -80px -820px;
}
+
.emoji-water_polo_tone5 {
background-position: -100px -820px;
}
+
.emoji-watermelon {
background-position: -120px -820px;
}
+
.emoji-wave {
background-position: -140px -820px;
}
+
.emoji-wave_tone1 {
background-position: -160px -820px;
}
+
.emoji-wave_tone2 {
background-position: -180px -820px;
}
+
.emoji-wave_tone3 {
background-position: -200px -820px;
}
+
.emoji-wave_tone4 {
background-position: -220px -820px;
}
+
.emoji-wave_tone5 {
background-position: -240px -820px;
}
+
.emoji-wavy_dash {
background-position: -260px -820px;
}
+
.emoji-waxing_crescent_moon {
background-position: -280px -820px;
}
+
.emoji-waxing_gibbous_moon {
background-position: -300px -820px;
}
+
.emoji-wc {
background-position: -320px -820px;
}
+
.emoji-weary {
background-position: -340px -820px;
}
+
.emoji-wedding {
background-position: -360px -820px;
}
+
.emoji-whale {
background-position: -380px -820px;
}
+
.emoji-whale2 {
background-position: -400px -820px;
}
+
.emoji-wheel_of_dharma {
background-position: -420px -820px;
}
+
.emoji-wheelchair {
background-position: -440px -820px;
}
+
.emoji-white_check_mark {
background-position: -460px -820px;
}
+
.emoji-white_circle {
background-position: -480px -820px;
}
+
.emoji-white_flower {
background-position: -500px -820px;
}
+
.emoji-white_large_square {
background-position: -520px -820px;
}
+
.emoji-white_medium_small_square {
background-position: -540px -820px;
}
+
.emoji-white_medium_square {
background-position: -560px -820px;
}
+
.emoji-white_small_square {
background-position: -580px -820px;
}
+
.emoji-white_square_button {
background-position: -600px -820px;
}
+
.emoji-white_sun_cloud {
background-position: -620px -820px;
}
+
.emoji-white_sun_rain_cloud {
background-position: -640px -820px;
}
+
.emoji-white_sun_small_cloud {
background-position: -660px -820px;
}
+
.emoji-wilted_rose {
background-position: -680px -820px;
}
+
.emoji-wind_blowing_face {
background-position: -700px -820px;
}
+
.emoji-wind_chime {
background-position: -720px -820px;
}
+
.emoji-wine_glass {
background-position: -740px -820px;
}
+
.emoji-wink {
background-position: -760px -820px;
}
+
.emoji-wolf {
background-position: -780px -820px;
}
+
.emoji-woman {
background-position: -800px -820px;
}
+
.emoji-woman_tone1 {
background-position: -820px -820px;
}
+
.emoji-woman_tone2 {
background-position: -840px 0;
}
+
.emoji-woman_tone3 {
background-position: -840px -20px;
}
+
.emoji-woman_tone4 {
background-position: -840px -40px;
}
+
.emoji-woman_tone5 {
background-position: -840px -60px;
}
+
.emoji-womans_clothes {
background-position: -840px -80px;
}
+
.emoji-womans_hat {
background-position: -840px -100px;
}
+
.emoji-womens {
background-position: -840px -120px;
}
+
.emoji-worried {
background-position: -840px -140px;
}
+
.emoji-wrench {
background-position: -840px -160px;
}
+
.emoji-wrestlers {
background-position: -840px -180px;
}
+
.emoji-wrestlers_tone1 {
background-position: -840px -200px;
}
+
.emoji-wrestlers_tone2 {
background-position: -840px -220px;
}
+
.emoji-wrestlers_tone3 {
background-position: -840px -240px;
}
+
.emoji-wrestlers_tone4 {
background-position: -840px -260px;
}
+
.emoji-wrestlers_tone5 {
background-position: -840px -280px;
}
+
.emoji-writing_hand {
background-position: -840px -300px;
}
+
.emoji-writing_hand_tone1 {
background-position: -840px -320px;
}
+
.emoji-writing_hand_tone2 {
background-position: -840px -340px;
}
+
.emoji-writing_hand_tone3 {
background-position: -840px -360px;
}
+
.emoji-writing_hand_tone4 {
background-position: -840px -380px;
}
+
.emoji-writing_hand_tone5 {
background-position: -840px -400px;
}
+
.emoji-x {
background-position: -840px -420px;
}
+
.emoji-yellow_heart {
background-position: -840px -440px;
}
+
.emoji-yen {
background-position: -840px -460px;
}
+
.emoji-yin_yang {
background-position: -840px -480px;
}
+
.emoji-yum {
background-position: -840px -500px;
}
+
.emoji-zap {
background-position: -840px -520px;
}
+
.emoji-zero {
background-position: -840px -540px;
}
+
.emoji-zipper_mouth {
background-position: -840px -560px;
}
+
.emoji-100 {
background-position: -840px -580px;
}
@@ -5391,6 +7183,7 @@
height: 20px;
width: 20px;
+ /* stylelint-disable media-feature-name-no-vendor-prefix */
@media only screen and (-webkit-min-device-pixel-ratio: 2),
only screen and (min--moz-device-pixel-ratio: 2),
only screen and (-o-min-device-pixel-ratio: 2/1),
@@ -5400,4 +7193,5 @@
background-image: image-url('emoji@2x.png');
background-size: 860px 840px;
}
+ /* stylelint-enable media-feature-name-no-vendor-prefix */
}
diff --git a/app/assets/stylesheets/errors.scss b/app/assets/stylesheets/errors.scss
index 89029a58d1e..f4519841ce3 100644
--- a/app/assets/stylesheets/errors.scss
+++ b/app/assets/stylesheets/errors.scss
@@ -15,9 +15,9 @@ $header-color: #456;
body {
color: $body-color;
text-align: center;
- font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
+ font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
margin: auto;
- font-size: .875rem;
+ font-size: 0.875rem;
}
h1 {
@@ -105,7 +105,6 @@ a {
}
@include media-breakpoint-up(sm) {
-
li {
display: inline-block;
padding-bottom: 0;
diff --git a/app/assets/stylesheets/framework.scss b/app/assets/stylesheets/framework.scss
index 338a8c5497c..413e0dde535 100644
--- a/app/assets/stylesheets/framework.scss
+++ b/app/assets/stylesheets/framework.scss
@@ -68,6 +68,6 @@
@import 'framework/read_more';
@import 'framework/flex_grid';
@import 'framework/system_messages';
-@import "framework/spinner";
+@import 'framework/spinner';
@import 'framework/card';
@import 'framework/editor-lite';
diff --git a/app/assets/stylesheets/framework/awards.scss b/app/assets/stylesheets/framework/awards.scss
index 0eab86ff7ea..86e701604b5 100644
--- a/app/assets/stylesheets/framework/awards.scss
+++ b/app/assets/stylesheets/framework/awards.scss
@@ -179,7 +179,7 @@
&.user-authored {
cursor: default;
background-color: $gray-light;
- border-color: $gray-200;
+ border-color: $gray-100;
color: $gl-text-color-disabled;
gl-emoji {
diff --git a/app/assets/stylesheets/framework/broadcast_messages.scss b/app/assets/stylesheets/framework/broadcast_messages.scss
index 534ada08b85..f7836213e5c 100644
--- a/app/assets/stylesheets/framework/broadcast_messages.scss
+++ b/app/assets/stylesheets/framework/broadcast_messages.scss
@@ -28,7 +28,7 @@
max-width: 300px;
width: auto;
background: $white;
- border: 1px solid $gray-200;
+ border: 1px solid $gray-100;
box-shadow: 0 1px 2px 0 rgba($black, 0.1);
border-radius: $border-radius-default;
z-index: 999;
diff --git a/app/assets/stylesheets/framework/buttons.scss b/app/assets/stylesheets/framework/buttons.scss
index f47d0cab31f..fd5b3f74c4a 100644
--- a/app/assets/stylesheets/framework/buttons.scss
+++ b/app/assets/stylesheets/framework/buttons.scss
@@ -171,7 +171,7 @@
@include btn-green;
}
- &.btn-inverted {
+ &.btn-inverted:not(.disabled):not(:disabled) {
&.btn-success {
@include btn-outline($white, $green-600, $green-500, $green-100, $green-700, $green-500, $green-200, $green-600, $green-800);
}
@@ -501,18 +501,19 @@
// All disabled buttons, regardless of color, type, etc
%disabled {
- background-color: $gray-light !important;
- border-color: $gray-200 !important;
- color: $gl-text-color-disabled !important;
- opacity: 1 !important;
- cursor: default !important;
+ background-color: $gray-light;
+ border-color: $gray-100;
+ color: $gl-text-color-disabled;
+ opacity: 1;
+ text-decoration: none;
+ cursor: default;
&.cursor-not-allowed {
- cursor: not-allowed !important;
+ cursor: not-allowed;
}
i {
- color: $gl-text-color-disabled !important;
+ color: $gl-text-color-disabled;
}
}
@@ -526,6 +527,10 @@ fieldset[disabled] .btn,
&:hover {
@extend %disabled;
}
+
+ &.btn-link {
+ background-color: transparent;
+ }
}
[readonly] {
diff --git a/app/assets/stylesheets/framework/common.scss b/app/assets/stylesheets/framework/common.scss
index 849ca4a79f8..1abb7a9c06f 100644
--- a/app/assets/stylesheets/framework/common.scss
+++ b/app/assets/stylesheets/framework/common.scss
@@ -396,35 +396,16 @@ img.emoji {
🚨 Do not use these classes — they are deprecated and being removed. 🚨
See https://gitlab.com/gitlab-org/gitlab/-/issues/217418 for more details.
**/
-.prepend-top-5 { margin-top: 5px; }
.prepend-top-10 { margin-top: 10px; }
.prepend-top-15 { margin-top: 15px; }
-.prepend-top-default { margin-top: $gl-padding !important; }
-.prepend-top-16 { margin-top: 16px; }
.prepend-top-20 { margin-top: 20px; }
-.prepend-left-5 { margin-left: 5px; }
-.prepend-left-10 { margin-left: 10px; }
.prepend-left-15 { margin-left: 15px; }
-.prepend-left-default { margin-left: $gl-padding; }
.prepend-left-20 { margin-left: 20px; }
-.prepend-left-32 { margin-left: 32px; }
.prepend-left-64 { margin-left: 64px; }
-.append-right-2 { margin-right: 2px; }
-.append-right-4 { margin-right: 4px; }
-.append-right-5 { margin-right: 5px; }
-.append-right-10 { margin-right: 10px; }
.append-right-15 { margin-right: 15px; }
-.append-right-default { margin-right: $gl-padding; }
.append-right-20 { margin-right: 20px; }
-.append-right-32 { margin-right: 32px; }
-.append-right-48 { margin-right: 48px; }
-.prepend-right-32 { margin-right: 32px; }
-.append-bottom-5 { margin-bottom: 5px; }
.append-bottom-10 { margin-bottom: 10px; }
-.append-bottom-15 { margin-bottom: 15px; }
.append-bottom-20 { margin-bottom: 20px; }
-.append-bottom-default { margin-bottom: $gl-padding; }
-.prepend-bottom-32 { margin-bottom: 32px; }
.ml-10 { margin-left: 4.5rem; }
.inline { display: inline-block; }
.center { text-align: center; }
@@ -560,41 +541,6 @@ img.emoji {
}
}
-.onboarding-helper-container {
- bottom: 40px;
- right: 40px;
- font-size: $gl-font-size-small;
- background: $gray-50;
- width: 200px;
- border-radius: 24px;
- box-shadow: 0 2px 4px $issue-boards-card-shadow;
- z-index: 10000;
-
- .collapsible {
- max-height: 0;
- transition: max-height 0.5s cubic-bezier(0, 1, 0, 1);
- }
-
- &.expanded {
- border-bottom-right-radius: $border-radius-default;
- border-bottom-left-radius: $border-radius-default;
-
- .collapsible {
- max-height: 1000px;
- transition: max-height 1s ease-in-out;
- }
- }
-
- .avatar {
- border-color: darken($gray-normal, 10%);
-
- img {
- width: 32px;
- height: 32px;
- }
- }
-}
-
.gl-font-sm { font-size: $gl-font-size-small; }
.gl-font-lg { font-size: $gl-font-size-large; }
.gl-font-base { font-size: $gl-font-size-14; }
diff --git a/app/assets/stylesheets/framework/contextual_sidebar.scss b/app/assets/stylesheets/framework/contextual_sidebar.scss
index e4bee01f61f..7004bcc121d 100644
--- a/app/assets/stylesheets/framework/contextual_sidebar.scss
+++ b/app/assets/stylesheets/framework/contextual_sidebar.scss
@@ -98,11 +98,11 @@
width: $contextual-sidebar-collapsed-width - 1px;
.collapse-text,
- .icon-angle-double-left {
+ .icon-chevron-double-lg-left {
display: none;
}
- .icon-angle-double-right {
+ .icon-chevron-double-lg-right {
display: block;
margin: 0;
}
@@ -381,7 +381,7 @@
margin-right: 8px;
}
- .icon-angle-double-right {
+ .icon-chevron-double-lg-right {
display: none;
}
diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss
index 485a4879c43..32c276ea6d2 100644
--- a/app/assets/stylesheets/framework/dropdowns.scss
+++ b/app/assets/stylesheets/framework/dropdowns.scss
@@ -639,9 +639,12 @@
display: none;
cursor: pointer;
pointer-events: all;
- right: 22px;
- top: 9px;
+ top: $gl-padding-8;
font-size: 14px;
+
+ &:not(.gl-icon) {
+ right: 22px;
+ }
}
&.has-value {
@@ -1084,8 +1087,20 @@ header.header-content .dropdown-menu.frequent-items-dropdown-menu {
.color-input-container {
.dropdown-label-color-preview {
- border: 1px solid $gray-200;
+ border: 1px solid $gray-100;
border-right: 0;
+
+ &[style] {
+ border-color: transparent;
+ }
+ }
+ }
+}
+
+.bulk-update {
+ .dropdown-toggle-text {
+ &.is-default {
+ color: $gl-text-color;
}
}
}
diff --git a/app/assets/stylesheets/framework/files.scss b/app/assets/stylesheets/framework/files.scss
index eef6d9031f8..8fd507a45bb 100644
--- a/app/assets/stylesheets/framework/files.scss
+++ b/app/assets/stylesheets/framework/files.scss
@@ -500,16 +500,27 @@ span.idiff {
border: transparent;
}
-.code-navigation {
- border-bottom: 1px $gray-darkest dashed;
+.code-navigation-line:hover {
+ .code-navigation {
+ border-bottom: 1px $gray-darkest dashed;
- &:hover {
- border-bottom-color: $almost-black;
+ &:hover {
+ border-bottom-color: $almost-black;
+ }
}
}
-.code-navigation-popover {
- max-width: 450px;
+.code-navigation-popover.popover {
+ max-width: calc(min(#{px-to-rem(560px)}, calc(100vw - #{$gl-padding-32})));
+}
+
+.code-navigation-popover-container {
+ max-height: px-to-rem(320px);
+}
+
+.code-navigation-popover .code {
+ padding-left: $grid-size * 3;
+ text-indent: -$grid-size * 2;
}
.tree-item-link {
diff --git a/app/assets/stylesheets/framework/filters.scss b/app/assets/stylesheets/framework/filters.scss
index 9bba5c0614a..8f209d2d99a 100644
--- a/app/assets/stylesheets/framework/filters.scss
+++ b/app/assets/stylesheets/framework/filters.scss
@@ -26,6 +26,12 @@
margin-right: 6px;
}
+ .bulk-update {
+ .filter-item {
+ margin-right: 0;
+ }
+ }
+
.sort-filter {
display: inline-block;
float: right;
@@ -152,7 +158,7 @@
.filtered-search-token .selected,
.filtered-search-term .selected {
.name {
- background-color: $gray-200;
+ background-color: $gray-100;
}
.operator {
diff --git a/app/assets/stylesheets/framework/forms.scss b/app/assets/stylesheets/framework/forms.scss
index 44c8ace9040..ec8d5806345 100644
--- a/app/assets/stylesheets/framework/forms.scss
+++ b/app/assets/stylesheets/framework/forms.scss
@@ -4,6 +4,8 @@ textarea {
input {
border-radius: $border-radius-base;
+ color: $gl-text-color;
+ background-color: $input-bg;
}
input[type='text'].danger {
@@ -126,10 +128,6 @@ label {
display: inline;
}
-.wiki-content {
- margin-top: 35px;
-}
-
.form-control::placeholder {
color: $gl-text-color-tertiary;
}
diff --git a/app/assets/stylesheets/framework/gitlab_theme.scss b/app/assets/stylesheets/framework/gitlab_theme.scss
index 8d5afe1d312..288849ba438 100644
--- a/app/assets/stylesheets/framework/gitlab_theme.scss
+++ b/app/assets/stylesheets/framework/gitlab_theme.scss
@@ -74,19 +74,6 @@
}
}
- &:focus:hover,
- &:focus {
- &.header-user-dropdown-toggle .header-user-notification-dot {
- border-color: $white;
- }
- }
-
- &:hover {
- &.header-user-dropdown-toggle .header-user-notification-dot {
- border-color: $nav-svg-color + 33;
- }
- }
-
&:hover,
&:focus {
@include media-breakpoint-up(sm) {
@@ -96,6 +83,10 @@
svg {
fill: currentColor;
}
+
+ &.header-user-dropdown-toggle .header-user-notification-dot {
+ border-color: $nav-svg-color + 33;
+ }
}
}
@@ -109,6 +100,10 @@
fill: $nav-svg-color;
}
}
+
+ &.header-user-dropdown-toggle .header-user-notification-dot {
+ border-color: $white;
+ }
}
.impersonated-user,
@@ -171,7 +166,7 @@
color: $sidebar-text;
}
- svg {
+ .nav-icon-container svg {
fill: $sidebar-text;
}
}
@@ -347,7 +342,7 @@ body {
.navbar-toggler,
.navbar-toggler:hover {
color: $gray-700;
- border-left: 1px solid $gray-200;
+ border-left: 1px solid $gray-100;
}
}
}
@@ -365,7 +360,7 @@ body {
.search-input-wrap {
.search-icon {
- fill: $gray-200;
+ fill: $gray-100;
}
.search-input {
diff --git a/app/assets/stylesheets/framework/header.scss b/app/assets/stylesheets/framework/header.scss
index 2c7e9428ef1..50628c7de82 100644
--- a/app/assets/stylesheets/framework/header.scss
+++ b/app/assets/stylesheets/framework/header.scss
@@ -570,9 +570,9 @@
}
.header-user-notification-dot {
- background-color: $orange-500;
- height: 10px;
- width: 10px;
+ background-color: $orange-300;
+ height: 12px;
+ width: 12px;
right: 8px;
top: -8px;
}
diff --git a/app/assets/stylesheets/framework/images.scss b/app/assets/stylesheets/framework/images.scss
index 2c9397d363c..0fae1c7d235 100644
--- a/app/assets/stylesheets/framework/images.scss
+++ b/app/assets/stylesheets/framework/images.scss
@@ -20,7 +20,7 @@
width: 100%;
}
- $image-widths: 80 130 150 250 306 394 430;
+ $image-widths: 80 130 150 225 250 306 394 430;
@each $width in $image-widths {
&.svg-#{$width} {
img,
diff --git a/app/assets/stylesheets/framework/issue_box.scss b/app/assets/stylesheets/framework/issue_box.scss
index 385b29f8bbe..4d5032ac674 100644
--- a/app/assets/stylesheets/framework/issue_box.scss
+++ b/app/assets/stylesheets/framework/issue_box.scss
@@ -30,6 +30,7 @@
}
&.status-box-issue-closed,
+ &.status-box-alert-resolved,
&.status-box-mr-merged {
background-color: $blue-500;
}
diff --git a/app/assets/stylesheets/framework/job_log.scss b/app/assets/stylesheets/framework/job_log.scss
index 1a26c0283e5..2448be1bca3 100644
--- a/app/assets/stylesheets/framework/job_log.scss
+++ b/app/assets/stylesheets/framework/job_log.scss
@@ -5,7 +5,7 @@
font-size: 13px;
word-break: break-all;
word-wrap: break-word;
- color: $gl-text-color-inverted;
+ color: color-yiq($builds-trace-bg);
border-radius: $border-radius-small;
min-height: 42px;
background-color: $builds-trace-bg;
diff --git a/app/assets/stylesheets/framework/memory_graph.scss b/app/assets/stylesheets/framework/memory_graph.scss
index b0bfc4f47ff..510969e149a 100644
--- a/app/assets/stylesheets/framework/memory_graph.scss
+++ b/app/assets/stylesheets/framework/memory_graph.scss
@@ -1,4 +1,4 @@
.memory-graph-container {
background: $white;
- border: 1px solid $gray-200;
+ border: 1px solid $gray-100;
}
diff --git a/app/assets/stylesheets/framework/mixins.scss b/app/assets/stylesheets/framework/mixins.scss
index 52da1b9abfc..918ca448c21 100644
--- a/app/assets/stylesheets/framework/mixins.scss
+++ b/app/assets/stylesheets/framework/mixins.scss
@@ -137,7 +137,8 @@
transition-duration: 0.3s;
}
- .fa {
+ .fa,
+ svg {
position: relative;
top: 5px;
font-size: 18px;
diff --git a/app/assets/stylesheets/framework/secondary_navigation_elements.scss b/app/assets/stylesheets/framework/secondary_navigation_elements.scss
index bd262b65dc3..f85efc63645 100644
--- a/app/assets/stylesheets/framework/secondary_navigation_elements.scss
+++ b/app/assets/stylesheets/framework/secondary_navigation_elements.scss
@@ -313,7 +313,7 @@
right: 0;
text-align: right;
- .fa {
+ svg {
right: 5px;
}
}
@@ -323,7 +323,7 @@
left: 0;
text-align: left;
- .fa {
+ svg {
left: 5px;
}
}
diff --git a/app/assets/stylesheets/framework/sidebar.scss b/app/assets/stylesheets/framework/sidebar.scss
index 1131248dd3f..9b33ed1b630 100644
--- a/app/assets/stylesheets/framework/sidebar.scss
+++ b/app/assets/stylesheets/framework/sidebar.scss
@@ -214,7 +214,7 @@
.health-status {
.dropdown-body {
.health-divider {
- border-top-color: $gray-200;
+ border-top-color: $gray-100;
}
.dropdown-item:not(.health-dropdown-item) {
diff --git a/app/assets/stylesheets/framework/stacked_progress_bar.scss b/app/assets/stylesheets/framework/stacked_progress_bar.scss
index 0a57a74eafc..2d16fdf4ee7 100644
--- a/app/assets/stylesheets/framework/stacked_progress_bar.scss
+++ b/app/assets/stylesheets/framework/stacked_progress_bar.scss
@@ -36,7 +36,7 @@
}
.status-neutral {
- background-color: $gray-200;
+ background-color: $gray-100;
color: $gl-gray-dark;
&:hover {
diff --git a/app/assets/stylesheets/framework/system_messages.scss b/app/assets/stylesheets/framework/system_messages.scss
index 4f66d6bf354..10796f319bf 100644
--- a/app/assets/stylesheets/framework/system_messages.scss
+++ b/app/assets/stylesheets/framework/system_messages.scss
@@ -94,7 +94,8 @@
margin-bottom: 16px;
}
- .boards-list {
+ .boards-list,
+ .board-swimlanes {
height: calc(100vh - #{$header-height + $breadcrumb-min-height + $performance-bar-height + $system-footer-height + $gl-padding-32});
}
}
diff --git a/app/assets/stylesheets/framework/timeline.scss b/app/assets/stylesheets/framework/timeline.scss
index ff6ac87db76..1504f3ee50f 100644
--- a/app/assets/stylesheets/framework/timeline.scss
+++ b/app/assets/stylesheets/framework/timeline.scss
@@ -27,7 +27,13 @@
.timeline-entry {
color: $gl-text-color;
- background-color: $white;
+
+ // [dark-theme]: only give background color to actual notes
+ // in the timeline, the note form textarea has a background
+ // of it's own
+ &:not(.note-form) {
+ background-color: $white;
+ }
.timeline-entry-inner {
position: relative;
diff --git a/app/assets/stylesheets/framework/typography.scss b/app/assets/stylesheets/framework/typography.scss
index 6e07a2b5de1..b5b86b807a6 100644
--- a/app/assets/stylesheets/framework/typography.scss
+++ b/app/assets/stylesheets/framework/typography.scss
@@ -89,7 +89,7 @@
background-color: $gray-10;
border-width: 1px;
border-style: solid;
- border-color: $gray-200 $gray-200 $gray-400;
+ border-color: $gray-100 $gray-100 $gray-400;
border-image: none;
border-radius: 3px;
box-shadow: 0 -1px 0 $gray-400 inset;
@@ -181,7 +181,7 @@
background-color: $white;
td {
- border-color: $gray-200;
+ border-color: $gray-100;
}
}
@@ -611,7 +611,7 @@ pre {
word-wrap: break-word;
color: $gl-text-color;
background-color: $gray-light;
- border: 1px solid $gray-200;
+ border: 1px solid $gray-100;
border-radius: $border-radius-small;
}
diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss
index 1536c5c3022..265dceb3c61 100644
--- a/app/assets/stylesheets/framework/variables.scss
+++ b/app/assets/stylesheets/framework/variables.scss
@@ -113,29 +113,29 @@ $gl-gray-600: #666 !default;
$gl-gray-700: #555 !default;
$gl-gray-800: #333 !default;
-$green-50: #f1fdf6 !default;
-$green-100: #dcf5e7 !default;
-$green-200: #263a2e !default;
-$green-300: #75d09b !default;
-$green-400: #37b96d !default;
-$green-500: #1aaa55 !default;
-$green-600: #168f48 !default;
-$green-700: #12753a !default;
-$green-800: #0e5a2d !default;
+$green-50: #ecf4ee !default;
+$green-100: #c3e6cd !default;
+$green-200: #91d4a8 !default;
+$green-300: #52b87a !default;
+$green-400: #2da160 !default;
+$green-500: #108548 !default;
+$green-600: #217645 !default;
+$green-700: #24663b !default;
+$green-800: #0d532a !default;
$green-900: #0a4020 !default;
$green-950: #072b15 !default;
-$blue-50: #f6fafe !default;
-$blue-100: #e4f0fb !default;
-$blue-200: #b8d6f4 !default;
-$blue-300: #73afea !default;
-$blue-400: #418cd8 !default;
-$blue-500: #1f78d1 !default;
-$blue-600: #1b69b6 !default;
-$blue-700: #17599c !default;
-$blue-800: #134a81 !default;
-$blue-900: #0f3b66 !default;
-$blue-950: #0a2744 !default;
+$blue-50: #e9f3fc !default;
+$blue-100: #cbe2f9 !default;
+$blue-200: #9dc7f1 !default;
+$blue-300: #63a6e9 !default;
+$blue-400: #428fdc !default;
+$blue-500: #1f75cb !default;
+$blue-600: #1068bf !default;
+$blue-700: #0b5cad !default;
+$blue-800: #064787 !default;
+$blue-900: #033464 !default;
+$blue-950: #002850 !default;
$orange-50: #fffaf4 !default;
$orange-100: #fff1de !default;
@@ -164,14 +164,14 @@ $red-950: #4d0a00 !default;
$gray-10: #fafafa !default;
$gray-50: #f0f0f0 !default;
$gray-100: #dbdbdb !default;
-$gray-200: #dfdfdf !default;
+$gray-200: #bfbfbf !default;
$gray-300: #ccc !default;
$gray-400: #bababa !default;
$gray-500: #a7a7a7 !default;
$gray-600: #919191 !default;
$gray-700: #707070 !default;
$gray-800: #4f4f4f !default;
-$gray-900: #2e2e2e !default;
+$gray-900: #303030 !default;
$gray-950: #1f1f1f !default;
$greens: (
@@ -333,7 +333,7 @@ $border-gray-normal-dashed: darken($gray-normal, $darken-border-dashed-factor);
/*
* UI elements
*/
-$border-color: $gray-200;
+$border-color: $gray-100;
$shadow-color: $t-gray-a-08;
$well-expand-item: #e8f2f7;
$well-inner-border: #eef0f2;
@@ -479,9 +479,9 @@ $gl-btn-active-gradient: inset 0 2px 3px $gl-btn-active-background;
$added: #63c363;
$deleted: #f77;
$line-added: #ecfdf0;
-$line-added-dark: #c7f0d2;
+$line-added-dark: #c7f0d2 !default;
$line-removed: #fbe9eb;
-$line-removed-dark: #fac5cd;
+$line-removed-dark: #fac5cd !default;
$line-number-old: #f9d7dc;
$line-number-new: #ddfbe6;
$line-number-select: #fbf2da;
@@ -711,7 +711,6 @@ $input-lg-width: 320px;
*/
$document-index-color: #888;
$help-shortcut-header-color: #333;
-$accepting-mr-label-color: #69d100;
/*
* Issues
@@ -868,7 +867,7 @@ $priority-label-empty-state-width: 114px;
Popovers
*/
$popover-max-width: 384px;
-$popover-box-shadow: 0 2px 3px 1px $gray-200;
+$popover-box-shadow: 0 2px 3px 1px $gray-100;
/*
Issues Analytics
diff --git a/app/assets/stylesheets/framework/variables_overrides.scss b/app/assets/stylesheets/framework/variables_overrides.scss
index c7a50bdb5a3..acfda718e77 100644
--- a/app/assets/stylesheets/framework/variables_overrides.scss
+++ b/app/assets/stylesheets/framework/variables_overrides.scss
@@ -5,23 +5,23 @@
$secondary: $gray-light;
$input-disabled-bg: $gray-light;
-$input-border-color: $gray-200;
+$input-border-color: $gray-100;
$input-color: $gl-text-color;
$input-font-size: $gl-font-size;
$font-family-sans-serif: $regular-font;
$font-family-monospace: $monospace-font;
$btn-line-height: 20px;
$table-accent-bg: $gray-light;
-$table-border-color: $gray-200;
+$table-border-color: $gray-100;
$card-border-color: $border-color;
-$card-cap-bg: $gray-light;
+$card-cap-bg: $gray-light !default;
$success: $green-500;
$info: $blue-500;
$warning: $orange-500;
$danger: $red-500;
$zindex-modal-backdrop: 1040;
$nav-divider-margin-y: ($grid-size / 2);
-$dropdown-divider-bg: $gray-200;
+$dropdown-divider-bg: $gray-100;
$dropdown-item-padding-y: 8px;
$dropdown-item-padding-x: 12px;
$popover-max-width: 300px;
diff --git a/app/assets/stylesheets/highlight/themes/dark.scss b/app/assets/stylesheets/highlight/themes/dark.scss
index 5ab762a5104..8d965ea4309 100644
--- a/app/assets/stylesheets/highlight/themes/dark.scss
+++ b/app/assets/stylesheets/highlight/themes/dark.scss
@@ -1,6 +1,6 @@
/* https://github.com/MozMorris/tomorrow-pygments */
-@import "../common";
+@import '../common';
/*
* Dark syntax colors
@@ -223,11 +223,20 @@ $dark-il: #de935f;
.cs { color: $dark-cs; } /* Comment.Special */
.gd { color: $dark-gd; } /* Generic.Deleted */
.ge { font-style: italic; } /* Generic.Emph */
- .gh { color: $dark-gh; font-weight: $gl-font-weight-bold; } /* Generic.Heading */
+ .gh { /* Generic.Heading */
+ color: $dark-gh;
+ font-weight: $gl-font-weight-bold;
+ }
.gi { color: $dark-gi; } /* Generic.Inserted */
- .gp { color: $dark-gp; font-weight: $gl-font-weight-bold; } /* Generic.Prompt */
+ .gp { /* Generic.Prompt */
+ color: $dark-gp;
+ font-weight: $gl-font-weight-bold;
+ }
.gs { font-weight: $gl-font-weight-bold; } /* Generic.Strong */
- .gu { color: $dark-gu; font-weight: $gl-font-weight-bold; } /* Generic.Subheading */
+ .gu { /* Generic.Subheading */
+ color: $dark-gu;
+ font-weight: $gl-font-weight-bold;
+ }
.kc { color: $dark-kc; } /* Keyword.Constant */
.kd { color: $dark-kd; } /* Keyword.Declaration */
.kn { color: $dark-kn; } /* Keyword.Namespace */
diff --git a/app/assets/stylesheets/highlight/themes/monokai.scss b/app/assets/stylesheets/highlight/themes/monokai.scss
index 348ef69cc4f..5ef2b9dcc36 100644
--- a/app/assets/stylesheets/highlight/themes/monokai.scss
+++ b/app/assets/stylesheets/highlight/themes/monokai.scss
@@ -1,6 +1,6 @@
/* https://github.com/richleland/pygments-css/blob/master/monokai.css */
-@import "../common";
+@import '../common';
/*
* Monokai Colors
@@ -211,7 +211,10 @@ $monokai-gi: #a6e22e;
.hll { background-color: $monokai-hll; }
.c { color: $monokai-c; } /* Comment */
- .err { color: $monokai-err-color; background-color: $monokai-err-bg; } /* Error */
+ .err { /* Error */
+ color: $monokai-err-color;
+ background-color: $monokai-err-bg;
+ }
.k { color: $monokai-k; } /* Keyword */
.l { color: $monokai-l; } /* Literal */
.n { color: $monokai-n; } /* Name */
diff --git a/app/assets/stylesheets/highlight/themes/none.scss b/app/assets/stylesheets/highlight/themes/none.scss
index 2fc5d7f7a85..fb548a00526 100644
--- a/app/assets/stylesheets/highlight/themes/none.scss
+++ b/app/assets/stylesheets/highlight/themes/none.scss
@@ -2,7 +2,7 @@
* None Syntax Colors
*/
-@import "../common";
+@import '../common';
@mixin match-line {
color: $black-transparent;
@@ -10,7 +10,7 @@
}
.code.none {
- // Line numbers
+ // Line numbers
.line-numbers,
.diff-line-num {
background-color: $gray-light;
@@ -44,7 +44,6 @@
$none-expanded-bg: #e0e0e0;
.line_holder {
-
&.match .line_content,
.new-nonewline.line_content,
.old-nonewline.line_content {
@@ -149,12 +148,12 @@
background-color: $white-normal;
}
- // Search result highlight
+ // Search result highlight
span.highlight_word {
background-color: $white-normal;
}
- // Links to URLs, emails, or dependencies
+ // Links to URLs, emails, or dependencies
.line a {
color: $gl-text-color;
text-decoration: underline;
diff --git a/app/assets/stylesheets/highlight/themes/solarized-dark.scss b/app/assets/stylesheets/highlight/themes/solarized-dark.scss
index f5b36480f18..190a6e6156a 100644
--- a/app/assets/stylesheets/highlight/themes/solarized-dark.scss
+++ b/app/assets/stylesheets/highlight/themes/solarized-dark.scss
@@ -1,6 +1,6 @@
/* https://gist.github.com/qguv/7936275 */
-@import "../common";
+@import '../common';
/*
* Solarized dark colors
@@ -244,13 +244,19 @@ $solarized-dark-il: #2aa198;
.c1 { color: $solarized-dark-c1; } /* Comment.Single */
.cs { color: $solarized-dark-cs; } /* Comment.Special */
.gd { color: $solarized-dark-gd; } /* Generic.Deleted */
- .ge { color: $solarized-dark-ge; font-style: italic; } /* Generic.Emph */
+ .ge { /* Generic.Emph */
+ color: $solarized-dark-ge;
+ font-style: italic;
+ }
.gr { color: $solarized-dark-gr; } /* Generic.Error */
.gh { color: $solarized-dark-gh; } /* Generic.Heading */
.gi { color: $solarized-dark-gi; } /* Generic.Inserted */
.go { color: $solarized-dark-go; } /* Generic.Output */
.gp { color: $solarized-dark-gp; } /* Generic.Prompt */
- .gs { color: $solarized-dark-gs; font-weight: $gl-font-weight-bold; } /* Generic.Strong */
+ .gs { /* Generic.Strong */
+ color: $solarized-dark-gs;
+ font-weight: $gl-font-weight-bold;
+ }
.gu { color: $solarized-dark-gu; } /* Generic.Subheading */
.gt { color: $solarized-dark-gt; } /* Generic.Traceback */
.kc { color: $solarized-dark-kc; } /* Keyword.Constant */
diff --git a/app/assets/stylesheets/highlight/themes/solarized-light.scss b/app/assets/stylesheets/highlight/themes/solarized-light.scss
index 993370642c3..71d8dd06834 100644
--- a/app/assets/stylesheets/highlight/themes/solarized-light.scss
+++ b/app/assets/stylesheets/highlight/themes/solarized-light.scss
@@ -1,6 +1,6 @@
/* https://gist.github.com/qguv/7936275 */
-@import "../common";
+@import '../common';
/*
* Solarized light syntax colors
@@ -252,13 +252,19 @@ $solarized-light-il: #2aa198;
.c1 { color: $solarized-light-c1; } /* Comment.Single */
.cs { color: $solarized-light-cs; } /* Comment.Special */
.gd { color: $solarized-light-gd; } /* Generic.Deleted */
- .ge { color: $solarized-light-ge; font-style: italic; } /* Generic.Emph */
+ .ge { /* Generic.Emph */
+ color: $solarized-light-ge;
+ font-style: italic;
+ }
.gr { color: $solarized-light-gr; } /* Generic.Error */
.gh { color: $solarized-light-gh; } /* Generic.Heading */
.gi { color: $solarized-light-gi; } /* Generic.Inserted */
.go { color: $solarized-light-go; } /* Generic.Output */
.gp { color: $solarized-light-gp; } /* Generic.Prompt */
- .gs { color: $solarized-light-gs; font-weight: $gl-font-weight-bold; } /* Generic.Strong */
+ .gs { /* Generic.Strong */
+ color: $solarized-light-gs;
+ font-weight: $gl-font-weight-bold;
+ }
.gu { color: $solarized-light-gu; } /* Generic.Subheading */
.gt { color: $solarized-light-gt; } /* Generic.Traceback */
.kc { color: $solarized-light-kc; } /* Keyword.Constant */
diff --git a/app/assets/stylesheets/highlight/themes/white.scss b/app/assets/stylesheets/highlight/themes/white.scss
index 7239086f649..6362dd734f6 100644
--- a/app/assets/stylesheets/highlight/themes/white.scss
+++ b/app/assets/stylesheets/highlight/themes/white.scss
@@ -1,3 +1,3 @@
.code.white {
- @import "../white_base";
+ @import '../white_base';
}
diff --git a/app/assets/stylesheets/mailer.scss b/app/assets/stylesheets/mailer.scss
index f7d93870a25..f188b29a113 100644
--- a/app/assets/stylesheets/mailer.scss
+++ b/app/assets/stylesheets/mailer.scss
@@ -6,12 +6,12 @@
// stylelint-disable color-hex-length
$mailer-font: 'Helvetica Neue', Helvetica, Arial, sans-serif;
-$mailer-text-color: #333333;
+$mailer-text-color: #333;
$mailer-bg-color: #fafafa;
$mailer-link-color: #3777b0;
-$mailer-link-muted-color: #333333;
+$mailer-link-muted-color: #333;
$mailer-line-cell-bg-color: #6b4fbb;
-$mailer-wrapper-cell-bg-color: #ffffff;
+$mailer-wrapper-cell-bg-color: #fff;
$mailer-wrapper-cell-border-color: #ededed;
$mailer-header-footer-text-color: #5c5c5c;
diff --git a/app/assets/stylesheets/page_bundles/_ide_theme_overrides.scss b/app/assets/stylesheets/page_bundles/_ide_theme_overrides.scss
index 2b82b2226c6..a8d10ea1a29 100644
--- a/app/assets/stylesheets/page_bundles/_ide_theme_overrides.scss
+++ b/app/assets/stylesheets/page_bundles/_ide_theme_overrides.scss
@@ -146,7 +146,7 @@
}
pre {
- border-color: var(--ide-border-color-alt, $gray-200);
+ border-color: var(--ide-border-color-alt, $gray-100);
code {
background-color: var(--ide-border-color, inherit);
@@ -216,7 +216,7 @@
color: var(--ide-text-color, $gl-text-color);
&:hover {
- background-color: var(--ide-input-border, $gray-200);
+ background-color: var(--ide-input-border, $gray-100);
}
}
@@ -300,8 +300,8 @@
}
.divider {
- background-color: var(--ide-dropdown-hover-background, $gray-200);
- border-color: var(--ide-dropdown-hover-background, $gray-200);
+ background-color: var(--ide-dropdown-hover-background, $gray-100);
+ border-color: var(--ide-dropdown-hover-background, $gray-100);
}
li > a:not(.disable-hover):hover,
@@ -316,7 +316,7 @@
.dropdown-title,
.dropdown-input {
- border-color: var(--ide-dropdown-hover-background, $gray-200) !important;
+ border-color: var(--ide-dropdown-hover-background, $gray-100) !important;
}
.btn-primary,
@@ -356,7 +356,7 @@
.btn[disabled] {
background-color: var(--ide-btn-default-background, $gray-light) !important;
- border: 1px solid var(--ide-btn-disabled-border, $gray-200) !important;
+ border: 1px solid var(--ide-btn-disabled-border, $gray-100) !important;
color: var(--ide-btn-disabled-color, $gl-text-color-disabled) !important;
}
diff --git a/app/assets/stylesheets/page_bundles/ide.scss b/app/assets/stylesheets/page_bundles/ide.scss
index 9c92f891834..a07755724dd 100644
--- a/app/assets/stylesheets/page_bundles/ide.scss
+++ b/app/assets/stylesheets/page_bundles/ide.scss
@@ -145,7 +145,7 @@ $ide-commit-header-height: 48px;
}
&:not([disabled]):hover {
- background-color: var(--ide-input-border, $gray-200);
+ background-color: var(--ide-input-border, $gray-100);
}
&:not([disabled]):focus {
@@ -251,10 +251,6 @@ $ide-commit-header-height: 48px;
padding-left: $gl-padding;
}
}
-
-.ide-status-file {
- text-align: right;
-}
// Not great, but this is to deal with our current output
.multi-file-preview-holder {
height: 100%;
@@ -400,7 +396,7 @@ $ide-commit-header-height: 48px;
}
&:active {
- background: var(--ide-background, $gray-200);
+ background: var(--ide-background, $gray-100);
}
&.is-active {
@@ -571,7 +567,7 @@ $ide-commit-header-height: 48px;
&:focus {
color: var(--ide-text-color, $gl-text-color);
- background-color: var(--ide-background-hover, $gray-200);
+ background-color: var(--ide-background-hover, $gray-100);
}
&.active {
@@ -1050,7 +1046,7 @@ $ide-commit-header-height: 48px;
background-color: var(--ide-background, $gray-50);
&:hover {
- background-color: var(--ide-file-row-btn-hover-background, $gray-200);
+ background-color: var(--ide-file-row-btn-hover-background, $gray-100);
}
&:active,
@@ -1101,7 +1097,7 @@ $ide-commit-header-height: 48px;
&:focus {
outline: 0;
box-shadow: none;
- border-color: var(--ide-border-color, $gray-200);
+ border-color: var(--ide-border-color, $gray-100);
}
}
@@ -1144,7 +1140,7 @@ $ide-commit-header-height: 48px;
}
.file-row:active {
- background: var(--ide-background, $gray-200);
+ background: var(--ide-background, $gray-100);
}
.file-row.is-active {
diff --git a/app/assets/stylesheets/page_bundles/ide_themes/_solarized-dark.scss b/app/assets/stylesheets/page_bundles/ide_themes/_solarized-dark.scss
index a58a0ed9475..0ef0834d8db 100644
--- a/app/assets/stylesheets/page_bundles/ide_themes/_solarized-dark.scss
+++ b/app/assets/stylesheets/page_bundles/ide_themes/_solarized-dark.scss
@@ -47,4 +47,4 @@
--ide-animation-gradient-1: var(--ide-file-row-btn-hover-background);
--ide-animation-gradient-2: var(--ide-dropdown-hover-background);
- }
+}
diff --git a/app/assets/stylesheets/pages/alert_management/details.scss b/app/assets/stylesheets/pages/alert_management/details.scss
index 591a26e5941..73a4af00c5a 100644
--- a/app/assets/stylesheets/pages/alert_management/details.scss
+++ b/app/assets/stylesheets/pages/alert_management/details.scss
@@ -61,13 +61,13 @@
&.is-active {
&:last-child {
- border-bottom: 1px solid $gray-200;
+ border-bottom: 1px solid $gray-100;
}
}
}
}
.note-header-info {
- margin-top: 1px;
+ @include gl-mt-1;
}
}
diff --git a/app/assets/stylesheets/pages/alert_management/list.scss b/app/assets/stylesheets/pages/alert_management/list.scss
index c1ea9b7604a..e420209b1fc 100644
--- a/app/assets/stylesheets/pages/alert_management/list.scss
+++ b/app/assets/stylesheets/pages/alert_management/list.scss
@@ -1,4 +1,8 @@
.alert-management-list {
+ .new-alert {
+ background-color: $issues-today-bg;
+ }
+
// these styles need to be deleted once GlTable component looks in GitLab same as in @gitlab/ui
table {
color: $gray-700;
@@ -8,14 +12,9 @@
outline: none;
}
- > :not([aria-sort='none']).b-table-sort-icon-left:hover::before {
- content: '' !important;
- }
-
td,
th {
- // TODO: There is no gl-pl-9 utlity for this padding, to be done and then removed.
- padding-left: 1.25rem;
+ @include gl-pl-9;
@include gl-py-5;
@include gl-outline-none;
@include gl-relative;
@@ -26,24 +25,8 @@
font-weight: $gl-font-weight-bold;
color: $gl-gray-600;
- &:hover::before {
- left: 3%;
- top: 34%;
- @include gl-absolute;
- content: url("data:image/svg+xml,%3Csvg \
- xmlns='http://www.w3.org/2000/svg' \
- width='14' height='14' viewBox='0 0 16 \
- 16'%3E%3Cpath fill='%23BABABA' fill-rule='evenodd' \
- d='M11.707085,11.7071 L7.999975,15.4142 L4.292875,11.7071 \
- C3.902375,11.3166 3.902375,10.6834 \
- 4.292875,10.2929 C4.683375,9.90237 \
- 5.316575,9.90237 5.707075,10.2929 \
- L6.999975,11.5858 L6.999975,2 C6.999975,1.44771 \
- 7.447695,1 7.999975,1 C8.552255,1 8.999975,1.44771 \
- 8.999975,2 L8.999975,11.5858 L10.292865,10.2929 \
- C10.683395,9.90237 11.316555,9.90237 11.707085,10.2929 \
- C12.097605,10.6834 12.097605,11.3166 11.707085,11.7071 \
- Z'/%3E%3C/svg%3E%0A");
+ &[aria-sort='none']:hover {
+ background-image: url('data:image/svg+xml, %3csvg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="4 0 8 16"%3e %3cpath style="fill: %23BABABA;" fill-rule="evenodd" d="M11.707085,11.7071 L7.999975,15.4142 L4.292875,11.7071 C3.902375,11.3166 3.902375, 10.6834 4.292875,10.2929 C4.683375,9.90237 5.316575,9.90237 5.707075,10.2929 L6.999975, 11.5858 L6.999975,2 C6.999975,1.44771 7.447695,1 7.999975,1 C8.552255,1 8.999975,1.44771 8.999975,2 L8.999975,11.5858 L10.292865,10.2929 C10.683395 ,9.90237 11.316555,9.90237 11.707085,10.2929 C12.097605,10.6834 12.097605,11.3166 11.707085,11.7071 Z"/%3e %3c/svg%3e');
}
}
}
@@ -74,7 +57,7 @@
content: none !important;
}
- div {
+ div:not(.dropdown-title) {
width: 100% !important;
padding: 0 !important;
}
diff --git a/app/assets/stylesheets/pages/boards.scss b/app/assets/stylesheets/pages/boards.scss
index 3e680c59910..049660220df 100644
--- a/app/assets/stylesheets/pages/boards.scss
+++ b/app/assets/stylesheets/pages/boards.scss
@@ -45,7 +45,8 @@
}
}
-.boards-list {
+.boards-list,
+.board-swimlanes {
height: calc(100vh - #{$issue-board-list-difference-xs});
overflow-x: scroll;
min-height: 200px;
@@ -82,7 +83,6 @@
}
.board-title-caret {
- cursor: pointer;
border-radius: $border-radius-default;
line-height: $gl-spacing-scale-5;
height: $gl-spacing-scale-5;
@@ -109,7 +109,6 @@
.board-title {
flex-direction: column;
height: 100%;
- padding: $gl-padding-8 0;
}
.board-title-caret {
@@ -203,8 +202,7 @@
flex-grow: 1;
}
-.board-delete {
- color: $gray-darkest;
+.board-delete.gl-button {
background-color: transparent;
outline: 0;
@@ -579,7 +577,10 @@
}
}
-.board-epics-swimlanes {
+.board-swimlanes {
overflow-x: auto;
- min-height: 600px;
+}
+
+.board-header-collapsed-info-icon:hover {
+ color: $gray-900;
}
diff --git a/app/assets/stylesheets/pages/branches.scss b/app/assets/stylesheets/pages/branches.scss
index e1715b8e1bf..3c49cc54ac4 100644
--- a/app/assets/stylesheets/pages/branches.scss
+++ b/app/assets/stylesheets/pages/branches.scss
@@ -23,7 +23,7 @@
.bar {
height: 4px;
- background-color: $gl-gray-200;
+ background-color: $gl-gray-100;
}
.count {
@@ -34,7 +34,7 @@
.graph-separator {
width: $graph-separator-width;
height: 18px;
- background-color: $gl-gray-200;
+ background-color: $gl-gray-100;
}
}
diff --git a/app/assets/stylesheets/pages/builds.scss b/app/assets/stylesheets/pages/builds.scss
index f50d4bc736e..02c42d5b779 100644
--- a/app/assets/stylesheets/pages/builds.scss
+++ b/app/assets/stylesheets/pages/builds.scss
@@ -236,7 +236,7 @@
.trigger-variables-table-cell {
font-size: $gl-font-size-small;
line-height: $gl-line-height;
- border: 1px solid $gray-200;
+ border: 1px solid $gray-100;
padding: $gl-padding-4 6px;
width: 50%;
vertical-align: top;
diff --git a/app/assets/stylesheets/pages/container_registry.scss b/app/assets/stylesheets/pages/container_registry.scss
deleted file mode 100644
index b88bd78cf3d..00000000000
--- a/app/assets/stylesheets/pages/container_registry.scss
+++ /dev/null
@@ -1,47 +0,0 @@
-/**
- * Container Registry
- */
-
-.container-message {
- span .btn {
- margin: 0;
- }
-}
-
-.container-image {
- border-bottom: 1px solid $white-normal;
-}
-
-.container-image-head {
- padding: 0 16px;
- line-height: 4em;
-
- .btn-link {
- padding: 0;
-
- &:focus {
- outline: none;
- }
- }
-}
-
-.table.tags {
- margin-bottom: 0;
-
- .registry-image-row {
- .check {
- padding-right: $gl-padding;
- width: 5%;
- }
-
- .action-buttons {
- opacity: 0;
- }
-
- &:hover {
- .action-buttons {
- opacity: 1;
- }
- }
- }
-}
diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss
index 98d74a9aaa2..fd5b3ff1dd8 100644
--- a/app/assets/stylesheets/pages/diff.scss
+++ b/app/assets/stylesheets/pages/diff.scss
@@ -935,11 +935,10 @@ table.code {
}
}
-.files:not([data-can-create-note='true']) .frame {
+.files:not([data-can-create-note]) .frame {
cursor: auto;
}
-.frame,
.frame.click-to-comment,
.btn-transparent.image-diff-overlay-add-comment {
position: relative;
diff --git a/app/assets/stylesheets/pages/editor.scss b/app/assets/stylesheets/pages/editor.scss
index eb9684c7b3c..fd11d0e3a69 100644
--- a/app/assets/stylesheets/pages/editor.scss
+++ b/app/assets/stylesheets/pages/editor.scss
@@ -45,6 +45,7 @@
display: block;
float: left;
margin-right: 10px;
+ max-width: 250px;
}
.new-file-name,
@@ -139,10 +140,6 @@
clear: both;
}
}
-
- .editor-ref {
- max-width: 250px;
- }
}
}
diff --git a/app/assets/stylesheets/pages/environment_logs.scss b/app/assets/stylesheets/pages/environment_logs.scss
index 81cec14062f..03993e5321d 100644
--- a/app/assets/stylesheets/pages/environment_logs.scss
+++ b/app/assets/stylesheets/pages/environment_logs.scss
@@ -31,10 +31,6 @@
width: 160px;
}
}
-
- .controllers {
- @include build-controllers(16px, flex-end, false, 2, inline);
- }
}
.log-lines,
diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss
index b1e849143b0..a7d0d4259ea 100644
--- a/app/assets/stylesheets/pages/issuable.scss
+++ b/app/assets/stylesheets/pages/issuable.scss
@@ -115,20 +115,6 @@
font-size: 0;
margin-bottom: -5px;
}
-
- .scoped-label-wrapper {
- > a {
- max-width: 100%;
- }
-
- .color-label {
- padding-right: $gl-padding-24;
- }
-
- .scoped-label {
- right: 12px;
- }
- }
}
.assignee {
@@ -396,7 +382,7 @@
overflow: hidden;
&:hover {
- background-color: $gray-200;
+ background-color: $gray-100;
}
&.issuable-sidebar-header {
@@ -983,10 +969,6 @@
vertical-align: sub;
}
-.suggestion-item a {
- color: initial;
-}
-
.suggestion-confidential {
color: $orange-600;
}
diff --git a/app/assets/stylesheets/pages/issues/issue_count_badge.scss b/app/assets/stylesheets/pages/issues/issue_count_badge.scss
index 569f323abd8..f2283e02ad2 100644
--- a/app/assets/stylesheets/pages/issues/issue_count_badge.scss
+++ b/app/assets/stylesheets/pages/issues/issue_count_badge.scss
@@ -1,7 +1,5 @@
.issue-count-badge,
.mr-count-badge {
- display: inline-flex;
- border-radius: $border-radius-base;
padding: 5px $gl-padding-8;
}
diff --git a/app/assets/stylesheets/pages/issues/issues_list.scss b/app/assets/stylesheets/pages/issues/issues_list.scss
new file mode 100644
index 00000000000..c0af7a6af6d
--- /dev/null
+++ b/app/assets/stylesheets/pages/issues/issues_list.scss
@@ -0,0 +1,5 @@
+.svg-container.jira-logo-container {
+ svg {
+ vertical-align: text-bottom;
+ }
+}
diff --git a/app/assets/stylesheets/pages/labels.scss b/app/assets/stylesheets/pages/labels.scss
index c3bac053a0a..73d2c3ca2f8 100644
--- a/app/assets/stylesheets/pages/labels.scss
+++ b/app/assets/stylesheets/pages/labels.scss
@@ -134,6 +134,11 @@
}
}
+.label-description-wrapper {
+ margin-right: 8px;
+ margin-left: 8px;
+}
+
.prioritized-labels {
margin-bottom: 30px;
@@ -310,7 +315,6 @@
width: 200px;
flex-shrink: 0;
- .scoped-label-wrapper,
.gl-label {
line-height: $gl-line-height;
}
@@ -386,7 +390,7 @@
order: 3;
width: 100%;
- > .append-right-default.prepend-left-default {
+ > .label-description-wrapper {
margin-left: 0;
margin-right: 0;
}
@@ -415,40 +419,6 @@
color: $indigo-300;
}
-.scoped-label-wrapper {
- max-width: 100%;
- vertical-align: top;
-
- .badge {
- text-overflow: ellipsis;
- overflow-x: hidden;
- }
-
- &.label-link .color-label a {
- color: inherit;
- }
-
- .color-label {
- padding-right: $gl-padding-24;
- max-width: 100%;
- }
-
- .scoped-label {
- position: absolute;
- top: 4px;
- right: 8px;
- padding: 0;
- margin: 0;
- line-height: $gl-line-height;
- }
-
- &.board-label {
- .scoped-label {
- top: 1px;
- }
- }
-}
-
.gl-label-scoped {
box-shadow: 0 0 0 2px currentColor inset;
@@ -456,29 +426,3 @@
box-shadow: 0 0 0 1px inset;
}
}
-
-// Label inside title of Delete Label Modal
-.modal-header .page-title {
- .scoped-label-wrapper {
- .scoped-label {
- line-height: 20px;
- }
-
- span.color-label {
- padding-right: $gl-padding-24;
- }
- }
-}
-
-// Don't hide the overflow in system messages
-.system-note-message,
-.issuable-details,
-.md-preview-holder,
-.referenced-commands,
-.note-body {
- .scoped-label-wrapper {
- .badge {
- overflow: initial;
- }
- }
-}
diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss
index 1e5e6da4e6c..5cf2d847405 100644
--- a/app/assets/stylesheets/pages/merge_requests.scss
+++ b/app/assets/stylesheets/pages/merge_requests.scss
@@ -64,7 +64,7 @@ $mr-widget-min-height: 69px;
background-color: $gray-light;
&.clickable:hover {
- background-color: $gl-gray-200;
+ background-color: $gl-gray-100;
cursor: pointer;
}
}
@@ -75,7 +75,7 @@ $mr-widget-min-height: 69px;
&::before {
content: '';
- border-left: 1px solid $gray-200;
+ border-left: 1px solid $gray-100;
position: absolute;
left: 28px;
top: -17px;
@@ -162,10 +162,6 @@ $mr-widget-min-height: 69px;
.btn {
font-size: $gl-font-size;
- &[disabled] {
- opacity: 0.3;
- }
-
&.dropdown-toggle {
.fa {
color: inherit;
@@ -401,6 +397,16 @@ $mr-widget-min-height: 69px;
}
}
}
+
+ &.mr-pipeline-suggest {
+ border-radius: $border-radius-default;
+ line-height: 20px;
+ border: 1px solid $border-color;
+
+ .circle-icon-container {
+ color: $gl-text-color-quaternary;
+ }
+ }
}
.mr-widget-help {
@@ -600,26 +606,6 @@ $mr-widget-min-height: 69px;
}
}
-.mr-pipeline-suggest {
- flex-wrap: wrap;
- border-radius: $border-radius-default;
- padding: $gl-padding;
- border: 1px solid $border-color;
- min-height: $mr-widget-min-height;
-
- @include media-breakpoint-up(md) {
- align-items: center;
- }
-
- .circle-icon-container {
- color: $gl-text-color-quaternary;
- }
-
- .popover {
- z-index: 240;
- }
-}
-
.card-new-merge-request {
.card-header {
padding: 5px 10px;
@@ -1050,3 +1036,7 @@ $mr-widget-min-height: 69px;
}
}
}
+
+.diff-file-row.is-active {
+ background-color: $gray-50;
+}
diff --git a/app/assets/stylesheets/pages/note_form.scss b/app/assets/stylesheets/pages/note_form.scss
index c3f3dbc223b..3a210d66420 100644
--- a/app/assets/stylesheets/pages/note_form.scss
+++ b/app/assets/stylesheets/pages/note_form.scss
@@ -62,7 +62,8 @@
background-color: $white;
&.is-focused {
- @extend .form-control:focus;
+ border-color: $input-focus-border-color;
+ box-shadow: $input-focus-box-shadow;
.comment-toolbar,
.nav-links {
@@ -359,14 +360,6 @@ table {
}
}
-.toolbar-button-icon {
- position: relative;
- top: 1px;
- margin-right: $gl-padding-4;
- color: inherit;
- font-size: 16px;
-}
-
.toolbar-text {
font-size: 14px;
line-height: 16px;
@@ -489,6 +482,7 @@ table {
border: 0;
font-size: 14px;
line-height: 16px;
+ vertical-align: initial;
&:hover,
&:focus {
@@ -498,6 +492,10 @@ table {
text-decoration: underline;
}
}
+
+ .gl-icon:not(:last-child) {
+ margin-right: 0;
+ }
}
.markdown-selector {
diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss
index e8cdfd717c0..40f0104a2bf 100644
--- a/app/assets/stylesheets/pages/notes.scss
+++ b/app/assets/stylesheets/pages/notes.scss
@@ -10,6 +10,7 @@ $note-form-margin-left: 72px;
top: 0;
bottom: 0;
left: $left;
+ height: calc(100% - 20px);
}
}
@@ -185,8 +186,8 @@ $note-form-margin-left: 72px;
padding: $gl-padding;
.dummy-avatar {
- background-color: $gl-gray-200;
- border: 1px solid darken($gl-gray-200, 25%);
+ background-color: $gl-gray-100;
+ border: 1px solid darken($gl-gray-100, 25%);
}
.note-headline-light,
@@ -254,10 +255,6 @@ $note-form-margin-left: 72px;
}
&.is-loading {
- .fa-smile-o {
- display: none;
- }
-
.fa-spinner {
display: inline-block;
}
diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss
index 43d766db9e0..57ad9abef4b 100644
--- a/app/assets/stylesheets/pages/pipelines.scss
+++ b/app/assets/stylesheets/pages/pipelines.scss
@@ -253,13 +253,6 @@
}
.stage-cell {
- &.table-section {
- @include media-breakpoint-up(md) {
- min-width: 160px; /* Hack alert: Without this the mini graph pipeline won't work properly*/
- margin-right: -4px;
- }
- }
-
.mini-pipeline-graph-dropdown-toggle {
svg {
height: $ci-action-icon-size;
@@ -816,7 +809,7 @@
&.ci-status-icon-created,
&.ci-status-icon-skipped {
- @include mini-pipeline-graph-color($white, $gray-200, $gray-300, $gray-500, $gray-600, $gray-700);
+ @include mini-pipeline-graph-color($white, $gray-100, $gray-300, $gray-500, $gray-600, $gray-700);
}
}
@@ -1108,7 +1101,3 @@ button.mini-pipeline-graph-dropdown-toggle {
.progress-bar.bg-primary {
background-color: $blue-500 !important;
}
-
-.parent-child-label-container {
- padding-top: $gl-padding-4;
-}
diff --git a/app/assets/stylesheets/pages/profiles/preferences.scss b/app/assets/stylesheets/pages/profiles/preferences.scss
index 3bab84af492..12386fa66ec 100644
--- a/app/assets/stylesheets/pages/profiles/preferences.scss
+++ b/app/assets/stylesheets/pages/profiles/preferences.scss
@@ -19,11 +19,6 @@
$ui-light-bg: #dfdfdf;
$ui-dark-mode-bg: #1f1f1f;
- label {
- margin: 0 $gl-padding-32 $gl-padding 0;
- text-align: center;
- }
-
.preview {
font-size: 0;
height: 48px;
diff --git a/app/assets/stylesheets/pages/prometheus.scss b/app/assets/stylesheets/pages/prometheus.scss
index 26db1fb9f58..6461d09bb47 100644
--- a/app/assets/stylesheets/pages/prometheus.scss
+++ b/app/assets/stylesheets/pages/prometheus.scss
@@ -34,7 +34,7 @@
.draggable {
&.draggable-enabled {
.draggable-panel {
- border: $gray-200 1px solid;
+ border: $gray-100 1px solid;
border-radius: $border-radius-default;
margin: -1px;
cursor: grab;
diff --git a/app/assets/stylesheets/pages/runners.scss b/app/assets/stylesheets/pages/runners.scss
index dc3811bab65..66d2f76c558 100644
--- a/app/assets/stylesheets/pages/runners.scss
+++ b/app/assets/stylesheets/pages/runners.scss
@@ -45,8 +45,7 @@
color: $gl-text-color-secondary;
}
- .fa-pause,
- .fa-play {
+ .fa-pause {
font-size: 11px;
}
}
diff --git a/app/assets/stylesheets/pages/service_desk.scss b/app/assets/stylesheets/pages/service_desk.scss
new file mode 100644
index 00000000000..34ab5eb1b74
--- /dev/null
+++ b/app/assets/stylesheets/pages/service_desk.scss
@@ -0,0 +1,7 @@
+.service-desk-issues {
+ .non-empty-state {
+ text-align: left;
+ padding-bottom: $gl-padding-top;
+ border-bottom: 1px solid $border-color;
+ }
+}
diff --git a/app/assets/stylesheets/pages/settings.scss b/app/assets/stylesheets/pages/settings.scss
index d26c07ce51b..f1df9099d82 100644
--- a/app/assets/stylesheets/pages/settings.scss
+++ b/app/assets/stylesheets/pages/settings.scss
@@ -347,7 +347,7 @@
.btn-clipboard {
background-color: $white;
- border: 1px solid $gray-200;
+ border: 1px solid $gray-100;
}
.deploy-token-help-block {
diff --git a/app/assets/stylesheets/pages/sherlock.scss b/app/assets/stylesheets/pages/sherlock.scss
index 2bf0bedb1f5..55b0b5295af 100644
--- a/app/assets/stylesheets/pages/sherlock.scss
+++ b/app/assets/stylesheets/pages/sherlock.scss
@@ -13,10 +13,8 @@ table .sherlock-code {
}
.sherlock-line-samples-table {
- margin-bottom: 0 !important;
-
- thead tr th,
- tbody tr td {
+ thead th,
+ tbody td {
font-size: 13px !important;
text-align: right;
padding: 0 10px !important;
diff --git a/app/assets/stylesheets/pages/wiki.scss b/app/assets/stylesheets/pages/wiki.scss
index 640968ff678..8c4bfdf68cc 100644
--- a/app/assets/stylesheets/pages/wiki.scss
+++ b/app/assets/stylesheets/pages/wiki.scss
@@ -147,3 +147,7 @@ ul.wiki-pages-list.content-list {
}
}
}
+
+.empty-state-wiki .text-content {
+ max-width: 490px; // Widen to allow for the Confluence button
+}
diff --git a/app/assets/stylesheets/performance_bar.scss b/app/assets/stylesheets/performance_bar.scss
index 4eef4d361a1..daeab80d373 100644
--- a/app/assets/stylesheets/performance_bar.scss
+++ b/app/assets/stylesheets/performance_bar.scss
@@ -61,7 +61,7 @@
padding: 4px 6px;
font-family: Consolas, 'Liberation Mono', Courier, monospace;
line-height: 1;
- color: $gl-gray-200;
+ color: $gl-gray-100;
border-radius: 3px;
box-shadow: 0 1px 0 $perf-bar-bucket-box-shadow-from,
inset 0 1px 2px $perf-bar-bucket-box-shadow-to;
diff --git a/app/assets/stylesheets/snippets.scss b/app/assets/stylesheets/snippets.scss
index d410a16a1d9..e5d5ed0d48f 100644
--- a/app/assets/stylesheets/snippets.scss
+++ b/app/assets/stylesheets/snippets.scss
@@ -1,8 +1,8 @@
-@import "framework/variables";
+@import 'framework/variables';
.gitlab-embed-snippets {
- @import "highlight/embedded";
- @import "framework/images";
+ @import 'highlight/embedded';
+ @import 'framework/images';
$border-style: 1px solid $border-color;
@@ -15,6 +15,7 @@
.gl-snippet-icon {
display: inline-block;
+ /* stylelint-disable-next-line function-url-quotes */
background: url(asset_path('ext_snippet_icons/ext_snippet_icons.png')) no-repeat;
overflow: hidden;
text-align: left;
diff --git a/app/assets/stylesheets/themes/_dark.scss b/app/assets/stylesheets/themes/_dark.scss
index 1f2a7645495..e2b4d6b8e7a 100644
--- a/app/assets/stylesheets/themes/_dark.scss
+++ b/app/assets/stylesheets/themes/_dark.scss
@@ -103,6 +103,8 @@ $input-focus-bg: $gray-100;
$input-color: $gray-900;
$input-group-addon-bg: $gray-900;
+$card-cap-bg: $gray-50;
+
$tooltip-bg: $gray-800;
$tooltip-color: $gray-10;
@@ -115,6 +117,14 @@ $secondary: $gray-600;
$issues-today-bg: #333838;
$issues-today-border: #333a40;
+$yiq-text-dark: $gray-50;
+$yiq-text-light: $gray-950;
+
+// Commit Diff Colors
+$line-added-dark: $green-200;
+$line-removed-dark: $red-200;
+
+// Misc component overrides that should live elsewhere
.gl-label {
filter: brightness(0.9) contrast(1.1);
}
diff --git a/app/assets/stylesheets/utilities.scss b/app/assets/stylesheets/utilities.scss
index 176d64272c2..38842ec167e 100644
--- a/app/assets/stylesheets/utilities.scss
+++ b/app/assets/stylesheets/utilities.scss
@@ -43,6 +43,7 @@
@for $i from 1 through 12 {
#{'.tab-width-#{$i}'} {
+ /* stylelint-disable-next-line property-no-vendor-prefix */
-moz-tab-size: $i;
tab-size: $i;
}
@@ -100,3 +101,23 @@
.gl-pl-7 {
padding-left: $gl-spacing-scale-7;
}
+
+.gl-transition-property-stroke-opacity {
+ transition-property: stroke-opacity;
+}
+
+.gl-transition-property-stroke {
+ transition-property: stroke;
+}
+
+.gl-top-66vh {
+ top: 66vh;
+}
+
+// Remove when https://gitlab.com/gitlab-org/gitlab-ui/-/issues/871
+// gets fixed on GitLab UI
+.gl-sm-w-auto\! {
+ @media (min-width: $breakpoint-sm) {
+ width: auto !important;
+ }
+}
diff --git a/app/controllers/admin/application_settings_controller.rb b/app/controllers/admin/application_settings_controller.rb
index 94c82c25357..41a6616d10c 100644
--- a/app/controllers/admin/application_settings_controller.rb
+++ b/app/controllers/admin/application_settings_controller.rb
@@ -227,6 +227,7 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
:raw_blob_request_limit,
:namespace_storage_size_limit,
:issues_create_limit,
+ :default_branch_name,
disabled_oauth_sign_in_sources: [],
import_sources: [],
repository_storages: [],
diff --git a/app/controllers/admin/clusters_controller.rb b/app/controllers/admin/clusters_controller.rb
index 5b1902fad51..9a642e53d86 100644
--- a/app/controllers/admin/clusters_controller.rb
+++ b/app/controllers/admin/clusters_controller.rb
@@ -10,6 +10,11 @@ class Admin::ClustersController < Clusters::ClustersController
def clusterable
@clusterable ||= InstanceClusterablePresenter.fabricate(Clusters::Instance.new, current_user: current_user)
end
-end
-Admin::ClustersController.prepend_if_ee('EE::Admin::ClustersController')
+ def metrics_dashboard_params
+ {
+ cluster: cluster,
+ cluster_type: :admin
+ }
+ end
+end
diff --git a/app/controllers/admin/jobs_controller.rb b/app/controllers/admin/jobs_controller.rb
index a3a18a115e9..7b50a45a9cd 100644
--- a/app/controllers/admin/jobs_controller.rb
+++ b/app/controllers/admin/jobs_controller.rb
@@ -1,6 +1,8 @@
# frozen_string_literal: true
class Admin::JobsController < Admin::ApplicationController
+ BUILDS_PER_PAGE = 30
+
def index
# We need all builds for tabs counters
@all_builds = Ci::JobsFinder.new(current_user: current_user).execute
@@ -8,7 +10,7 @@ class Admin::JobsController < Admin::ApplicationController
@scope = params[:scope]
@builds = Ci::JobsFinder.new(current_user: current_user, params: params).execute
@builds = @builds.eager_load_everything
- @builds = @builds.page(params[:page]).per(30)
+ @builds = @builds.page(params[:page]).per(BUILDS_PER_PAGE).without_count
end
def cancel_all
diff --git a/app/controllers/admin/services_controller.rb b/app/controllers/admin/services_controller.rb
index 08ef992e604..e0137accd2d 100644
--- a/app/controllers/admin/services_controller.rb
+++ b/app/controllers/admin/services_controller.rb
@@ -4,13 +4,18 @@ class Admin::ServicesController < Admin::ApplicationController
include ServiceParams
before_action :service, only: [:edit, :update]
+ before_action :whitelist_query_limiting, only: [:index]
+ before_action only: :edit do
+ push_frontend_feature_flag(:integration_form_refactor, default_enabled: true)
+ end
def index
@services = Service.find_or_create_templates.sort_by(&:title)
+ @existing_instance_types = Service.instances.pluck(:type) # rubocop: disable CodeReuse/ActiveRecord
end
def edit
- unless service.present?
+ if service.nil? || Service.instance_exists_for?(service.type)
redirect_to admin_application_settings_services_path,
alert: "Service is unknown or it doesn't exist"
end
@@ -34,4 +39,8 @@ class Admin::ServicesController < Admin::ApplicationController
@service ||= Service.find_by(id: params[:id], template: true)
end
# rubocop: enable CodeReuse/ActiveRecord
+
+ def whitelist_query_limiting
+ Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab/-/issues/220357')
+ end
end
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index 79a164a5574..2595b646964 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -22,6 +22,7 @@ class ApplicationController < ActionController::Base
include Impersonation
include Gitlab::Logging::CloudflareHelper
include Gitlab::Utils::StrongMemoize
+ include ControllerWithFeatureCategory
before_action :authenticate_user!, except: [:route_not_found]
before_action :enforce_terms!, if: :should_enforce_terms?
@@ -305,7 +306,7 @@ class ApplicationController < ActionController::Base
return if session[:impersonator_id] || !current_user&.allow_password_authentication?
if current_user&.password_expired?
- return redirect_to new_profile_password_path
+ redirect_to new_profile_password_path
end
end
@@ -329,13 +330,6 @@ class ApplicationController < ActionController::Base
end
end
- def event_filter
- @event_filter ||=
- EventFilter.new(params[:event_filter].presence || cookies[:event_filter]).tap do |new_event_filter|
- cookies[:event_filter] = new_event_filter.filter
- end
- end
-
# JSON for infinite scroll via Pager object
def pager_json(partial, count, locals = {})
html = render_to_string(
@@ -370,7 +364,7 @@ class ApplicationController < ActionController::Base
def require_email
if current_user && current_user.temp_oauth_email? && session[:impersonator_id].nil?
- return redirect_to profile_path, notice: _('Please complete your profile with email address')
+ redirect_to profile_path, notice: _('Please complete your profile with email address')
end
end
diff --git a/app/controllers/autocomplete_controller.rb b/app/controllers/autocomplete_controller.rb
index 0df201ab506..99fa17e202a 100644
--- a/app/controllers/autocomplete_controller.rb
+++ b/app/controllers/autocomplete_controller.rb
@@ -4,10 +4,6 @@ class AutocompleteController < ApplicationController
skip_before_action :authenticate_user!, only: [:users, :award_emojis, :merge_request_target_branches]
def users
- project = Autocomplete::ProjectFinder
- .new(current_user, params)
- .execute
-
group = Autocomplete::GroupFinder
.new(current_user, project, params)
.execute
@@ -50,8 +46,20 @@ class AutocompleteController < ApplicationController
end
end
+ def deploy_keys_with_owners
+ deploy_keys = DeployKeys::CollectKeysService.new(project, current_user).execute
+
+ render json: DeployKeySerializer.new.represent(deploy_keys, { with_owner: true, user: current_user })
+ end
+
private
+ def project
+ @project ||= Autocomplete::ProjectFinder
+ .new(current_user, params)
+ .execute
+ end
+
def target_branch_params
params.permit(:group_id, :project_id).select { |_, v| v.present? }
end
diff --git a/app/controllers/chaos_controller.rb b/app/controllers/chaos_controller.rb
index ac008165c16..e0d1f313fc7 100644
--- a/app/controllers/chaos_controller.rb
+++ b/app/controllers/chaos_controller.rb
@@ -45,7 +45,6 @@ class ChaosController < ActionController::Base
unless Devise.secure_compare(chaos_secret_configured, chaos_secret_request)
render plain: "To experience chaos, please set a valid `X-Chaos-Secret` header or `token` param",
status: :unauthorized
- return
end
end
diff --git a/app/controllers/clusters/clusters_controller.rb b/app/controllers/clusters/clusters_controller.rb
index 46dec5f3287..2e8b3d764ca 100644
--- a/app/controllers/clusters/clusters_controller.rb
+++ b/app/controllers/clusters/clusters_controller.rb
@@ -2,6 +2,8 @@
class Clusters::ClustersController < Clusters::BaseController
include RoutableActions
+ include Metrics::Dashboard::PrometheusApiProxy
+ include MetricsDashboard
before_action :cluster, only: [:cluster_status, :show, :update, :destroy, :clear_cache]
before_action :generate_gcp_authorize_url, only: [:new]
@@ -290,6 +292,29 @@ class Clusters::ClustersController < Clusters::BaseController
@gcp_cluster = cluster.present(current_user: current_user)
end
+ def proxyable
+ cluster.cluster
+ end
+
+ # During first iteration of dashboard variables implementation
+ # cluster health case was omitted. Existing service for now is tied to
+ # environment, which is not always present for cluster health dashboard.
+ # It is planned to break coupling to environment https://gitlab.com/gitlab-org/gitlab/-/issues/213833.
+ # It is also planned to move cluster health to metrics dashboard section https://gitlab.com/gitlab-org/gitlab/-/issues/220214
+ # but for now I've used dummy class to stub variable substitution service, as there are no variables
+ # in cluster health dashboard
+ def proxy_variable_substitution_service
+ @empty_service ||= Class.new(BaseService) do
+ def initialize(proxyable, params)
+ @proxyable, @params = proxyable, params
+ end
+
+ def execute
+ success(params: @params)
+ end
+ end
+ end
+
def user_cluster
cluster = Clusters::BuildService.new(clusterable.subject).execute
cluster.build_platform_kubernetes
diff --git a/app/controllers/concerns/controller_with_feature_category.rb b/app/controllers/concerns/controller_with_feature_category.rb
new file mode 100644
index 00000000000..f8985cf0950
--- /dev/null
+++ b/app/controllers/concerns/controller_with_feature_category.rb
@@ -0,0 +1,45 @@
+# frozen_string_literal: true
+
+module ControllerWithFeatureCategory
+ extend ActiveSupport::Concern
+ include Gitlab::ClassAttributes
+
+ class_methods do
+ def feature_category(category, config = {})
+ validate_config!(config)
+
+ category_config = Config.new(category, config[:only], config[:except], config[:if], config[:unless])
+ # Add the config to the beginning. That way, the last defined one takes precedence.
+ feature_category_configuration.unshift(category_config)
+ end
+
+ def feature_category_for_action(action)
+ category_config = feature_category_configuration.find { |config| config.matches?(action) }
+
+ category_config&.category || superclass_feature_category_for_action(action)
+ end
+
+ private
+
+ def validate_config!(config)
+ invalid_keys = config.keys - [:only, :except, :if, :unless]
+ if invalid_keys.any?
+ raise ArgumentError, "unknown arguments: #{invalid_keys} "
+ end
+
+ if config.key?(:only) && config.key?(:except)
+ raise ArgumentError, "cannot configure both `only` and `except`"
+ end
+ end
+
+ def feature_category_configuration
+ class_attributes[:feature_category_config] ||= []
+ end
+
+ def superclass_feature_category_for_action(action)
+ return unless superclass.respond_to?(:feature_category_for_action)
+
+ superclass.feature_category_for_action(action)
+ end
+ end
+end
diff --git a/app/controllers/concerns/controller_with_feature_category/config.rb b/app/controllers/concerns/controller_with_feature_category/config.rb
new file mode 100644
index 00000000000..624691ee4f6
--- /dev/null
+++ b/app/controllers/concerns/controller_with_feature_category/config.rb
@@ -0,0 +1,38 @@
+# frozen_string_literal: true
+
+module ControllerWithFeatureCategory
+ class Config
+ attr_reader :category
+
+ def initialize(category, only, except, if_proc, unless_proc)
+ @category = category.to_sym
+ @only, @except = only&.map(&:to_s), except&.map(&:to_s)
+ @if_proc, @unless_proc = if_proc, unless_proc
+ end
+
+ def matches?(action)
+ included?(action) && !excluded?(action) &&
+ if_proc?(action) && !unless_proc?(action)
+ end
+
+ private
+
+ attr_reader :only, :except, :if_proc, :unless_proc
+
+ def if_proc?(action)
+ if_proc.nil? || if_proc.call(action)
+ end
+
+ def unless_proc?(action)
+ unless_proc.present? && unless_proc.call(action)
+ end
+
+ def included?(action)
+ only.nil? || only.include?(action)
+ end
+
+ def excluded?(action)
+ except.present? && except.include?(action)
+ end
+ end
+end
diff --git a/app/controllers/concerns/filters_events.rb b/app/controllers/concerns/filters_events.rb
new file mode 100644
index 00000000000..c82d0318fd3
--- /dev/null
+++ b/app/controllers/concerns/filters_events.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+
+module FiltersEvents
+ def event_filter
+ @event_filter ||= new_event_filter.tap { |ef| cookies[:event_filter] = ef.filter }
+ end
+
+ private
+
+ def new_event_filter
+ active_filter = params[:event_filter].presence || cookies[:event_filter]
+ EventFilter.new(active_filter)
+ end
+end
diff --git a/app/controllers/concerns/integrations_actions.rb b/app/controllers/concerns/integrations_actions.rb
index cc9db7936e8..46febc44807 100644
--- a/app/controllers/concerns/integrations_actions.rb
+++ b/app/controllers/concerns/integrations_actions.rb
@@ -8,6 +8,9 @@ module IntegrationsActions
before_action :not_found, unless: :integrations_enabled?
before_action :integration, only: [:edit, :update, :test]
+ before_action only: :edit do
+ push_frontend_feature_flag(:integration_form_refactor, default_enabled: true)
+ end
end
def edit
@@ -51,9 +54,8 @@ module IntegrationsActions
end
def integration
- # Using instance variable `@service` still required as it's used in ServiceParams
- # and app/views/shared/_service_settings.html.haml. Should be removed once
- # those 2 are refactored to use `@integration`.
+ # Using instance variable `@service` still required as it's used in ServiceParams.
+ # Should be removed once that is refactored to use `@integration`.
@integration = @service ||= find_or_initialize_integration(params[:id]) # rubocop:disable Gitlab/ModuleWithInstanceVariables
end
diff --git a/app/controllers/concerns/issuable_actions.rb b/app/controllers/concerns/issuable_actions.rb
index 98fa8202e25..c4dbce00593 100644
--- a/app/controllers/concerns/issuable_actions.rb
+++ b/app/controllers/concerns/issuable_actions.rb
@@ -110,9 +110,13 @@ module IssuableActions
def bulk_update
result = Issuable::BulkUpdateService.new(parent, current_user, bulk_update_params).execute(resource_name)
- quantity = result[:count]
- render json: { notice: "#{quantity} #{resource_name.pluralize(quantity)} updated" }
+ if result.success?
+ quantity = result.payload[:count]
+ render json: { notice: "#{quantity} #{resource_name.pluralize(quantity)} updated" }
+ elsif result.error?
+ render json: { errors: result.message }, status: result.http_status
+ end
end
# rubocop:disable CodeReuse/ActiveRecord
@@ -193,13 +197,13 @@ module IssuableActions
def authorize_destroy_issuable!
unless can?(current_user, :"destroy_#{issuable.to_ability_name}", issuable)
- return access_denied!
+ access_denied!
end
end
def authorize_admin_issuable!
unless can?(current_user, :"admin_#{resource_name}", parent)
- return access_denied!
+ access_denied!
end
end
diff --git a/app/controllers/concerns/issuable_collections.rb b/app/controllers/concerns/issuable_collections.rb
index 9ef067e8797..4f61e5ed711 100644
--- a/app/controllers/concerns/issuable_collections.rb
+++ b/app/controllers/concerns/issuable_collections.rb
@@ -81,34 +81,36 @@ module IssuableCollections
# rubocop:disable Gitlab/ModuleWithInstanceVariables
def finder_options
- params[:state] = default_state if params[:state].blank?
-
- options = {
- scope: params[:scope],
- state: params[:state],
- confidential: Gitlab::Utils.to_boolean(params[:confidential]),
- sort: set_sort_order
- }
-
- # Used by view to highlight active option
- @sort = options[:sort]
-
- # When a user looks for an exact iid, we do not filter by search but only by iid
- if params[:search] =~ /^#(?<iid>\d+)\z/
- options[:iids] = Regexp.last_match[:iid]
- params[:search] = nil
+ strong_memoize(:finder_options) do
+ params[:state] = default_state if params[:state].blank?
+
+ options = {
+ scope: params[:scope],
+ state: params[:state],
+ confidential: Gitlab::Utils.to_boolean(params[:confidential]),
+ sort: set_sort_order
+ }
+
+ # Used by view to highlight active option
+ @sort = options[:sort]
+
+ # When a user looks for an exact iid, we do not filter by search but only by iid
+ if params[:search] =~ /^#(?<iid>\d+)\z/
+ options[:iids] = Regexp.last_match[:iid]
+ params[:search] = nil
+ end
+
+ if @project
+ options[:project_id] = @project.id
+ options[:attempt_project_search_optimizations] = true
+ elsif @group
+ options[:group_id] = @group.id
+ options[:include_subgroups] = true
+ options[:attempt_group_search_optimizations] = true
+ end
+
+ params.permit(finder_type.valid_params).merge(options)
end
-
- if @project
- options[:project_id] = @project.id
- options[:attempt_project_search_optimizations] = true
- elsif @group
- options[:group_id] = @group.id
- options[:include_subgroups] = true
- options[:attempt_group_search_optimizations] = true
- end
-
- params.permit(finder_type.valid_params).merge(options)
end
# rubocop:enable Gitlab/ModuleWithInstanceVariables
@@ -147,7 +149,10 @@ module IssuableCollections
when 'Issue'
common_attributes + [:project, project: :namespace]
when 'MergeRequest'
- common_attributes + [:target_project, :latest_merge_request_diff, source_project: :route, head_pipeline: :project, target_project: :namespace]
+ common_attributes + [
+ :target_project, :latest_merge_request_diff, :approvals, :approved_by_users,
+ source_project: :route, head_pipeline: :project, target_project: :namespace
+ ]
end
end
# rubocop:enable Gitlab/ModuleWithInstanceVariables
diff --git a/app/controllers/concerns/known_sign_in.rb b/app/controllers/concerns/known_sign_in.rb
index c0b9605de58..cacc7e4628f 100644
--- a/app/controllers/concerns/known_sign_in.rb
+++ b/app/controllers/concerns/known_sign_in.rb
@@ -2,19 +2,34 @@
module KnownSignIn
include Gitlab::Utils::StrongMemoize
+ include CookiesHelper
+
+ KNOWN_SIGN_IN_COOKIE = :known_sign_in
+ KNOWN_SIGN_IN_COOKIE_EXPIRY = 14.days
private
def verify_known_sign_in
- return unless current_user
+ return unless Gitlab::CurrentSettings.notify_on_unknown_sign_in? && current_user
+
+ notify_user unless known_device? || known_remote_ip?
- notify_user unless known_remote_ip?
+ update_cookie
end
def known_remote_ip?
known_ip_addresses.include?(request.remote_ip)
end
+ def known_device?
+ cookies.encrypted[KNOWN_SIGN_IN_COOKIE] == current_user.id
+ end
+
+ def update_cookie
+ set_secure_cookie(KNOWN_SIGN_IN_COOKIE, current_user.id,
+ type: COOKIE_TYPE_ENCRYPTED, httponly: true, expires: KNOWN_SIGN_IN_COOKIE_EXPIRY)
+ end
+
def sessions
strong_memoize(:session) do
ActiveSession.list(current_user).reject(&:is_impersonated)
diff --git a/app/controllers/concerns/membership_actions.rb b/app/controllers/concerns/membership_actions.rb
index 4ab02005b45..8c7f156f7f8 100644
--- a/app/controllers/concerns/membership_actions.rb
+++ b/app/controllers/concerns/membership_actions.rb
@@ -31,7 +31,10 @@ module MembershipActions
def destroy
member = membershipable.members_and_requesters.find(params[:id])
- Members::DestroyService.new(current_user).execute(member)
+ # !! is used in case unassign_issuables contains empty string which would result in nil
+ unassign_issuables = !!ActiveRecord::Type::Boolean.new.cast(params.delete(:unassign_issuables))
+
+ Members::DestroyService.new(current_user).execute(member, unassign_issuables: unassign_issuables)
respond_to do |format|
format.html do
diff --git a/app/controllers/concerns/metrics/dashboard/prometheus_api_proxy.rb b/app/controllers/concerns/metrics/dashboard/prometheus_api_proxy.rb
new file mode 100644
index 00000000000..e0e3f628cc5
--- /dev/null
+++ b/app/controllers/concerns/metrics/dashboard/prometheus_api_proxy.rb
@@ -0,0 +1,53 @@
+# frozen_string_literal: true
+
+module Metrics::Dashboard::PrometheusApiProxy
+ extend ActiveSupport::Concern
+ include RenderServiceResults
+
+ included do
+ before_action :authorize_read_prometheus!, only: [:prometheus_proxy]
+ end
+
+ def prometheus_proxy
+ variable_substitution_result =
+ proxy_variable_substitution_service.new(proxyable, permit_params).execute
+
+ if variable_substitution_result[:status] == :error
+ return error_response(variable_substitution_result)
+ end
+
+ prometheus_result = Prometheus::ProxyService.new(
+ proxyable,
+ proxy_method,
+ proxy_path,
+ variable_substitution_result[:params]
+ ).execute
+
+ return continue_polling_response if prometheus_result.nil?
+ return error_response(prometheus_result) if prometheus_result[:status] == :error
+
+ success_response(prometheus_result)
+ end
+
+ private
+
+ def proxyable
+ raise NotImplementedError, "#{self.class} must implement method: #{__callee__}"
+ end
+
+ def proxy_variable_substitution_service
+ raise NotImplementedError, "#{self.class} must implement method: #{__callee__}"
+ end
+
+ def permit_params
+ params.permit!
+ end
+
+ def proxy_method
+ request.method
+ end
+
+ def proxy_path
+ params[:proxy_path]
+ end
+end
diff --git a/app/controllers/concerns/metrics_dashboard.rb b/app/controllers/concerns/metrics_dashboard.rb
index 1aea0e294a5..28d0692d748 100644
--- a/app/controllers/concerns/metrics_dashboard.rb
+++ b/app/controllers/concerns/metrics_dashboard.rb
@@ -13,7 +13,7 @@ module MetricsDashboard
result = dashboard_finder.find(
project_for_dashboard,
current_user,
- metrics_dashboard_params.to_h.symbolize_keys
+ decoded_params
)
if result
@@ -41,7 +41,7 @@ module MetricsDashboard
end
def amend_dashboard(dashboard)
- project_dashboard = project_for_dashboard && !dashboard[:system_dashboard]
+ project_dashboard = project_for_dashboard && !dashboard[:out_of_the_box_dashboard]
dashboard[:can_edit] = project_dashboard ? can_edit?(dashboard) : false
dashboard[:project_blob_path] = project_dashboard ? dashboard_project_blob_path(dashboard) : nil
@@ -114,4 +114,14 @@ module MetricsDashboard
json: result.slice(:all_dashboards, :message, :status)
}
end
+
+ def decoded_params
+ params = metrics_dashboard_params
+
+ if params[:dashboard_path]
+ params[:dashboard_path] = CGI.unescape(params[:dashboard_path])
+ end
+
+ params
+ end
end
diff --git a/app/controllers/concerns/notes_actions.rb b/app/controllers/concerns/notes_actions.rb
index d3dfb1813e4..f4fc7decb60 100644
--- a/app/controllers/concerns/notes_actions.rb
+++ b/app/controllers/concerns/notes_actions.rb
@@ -5,6 +5,11 @@ module NotesActions
include Gitlab::Utils::StrongMemoize
extend ActiveSupport::Concern
+ # last_fetched_at is an integer number of microseconds, which is the same
+ # precision as PostgreSQL "timestamp" fields. It's important for them to have
+ # identical precision for accurate pagination
+ MICROSECOND = 1_000_000
+
included do
before_action :set_polling_interval_header, only: [:index]
before_action :require_noteable!, only: [:index, :create]
@@ -13,30 +18,20 @@ module NotesActions
end
def index
- notes_json = { notes: [], last_fetched_at: Time.current.to_i }
-
- notes = notes_finder
- .execute
- .inc_relations_for_view
-
- if notes_filter != UserPreference::NOTES_FILTERS[:only_comments]
- notes =
- ResourceEvents::MergeIntoNotesService
- .new(noteable, current_user, last_fetched_at: last_fetched_at)
- .execute(notes)
- end
-
+ notes, meta = gather_notes
notes = prepare_notes_for_rendering(notes)
notes = notes.select { |n| n.readable_by?(current_user) }
-
- notes_json[:notes] =
+ notes =
if use_note_serializer?
note_serializer.represent(notes)
else
notes.map { |note| note_json(note) }
end
- render json: notes_json
+ # We know there's more data, so tell the frontend to poll again after 1ms
+ set_polling_interval_header(interval: 1) if meta[:more]
+
+ render json: meta.merge(notes: notes)
end
# rubocop:disable Gitlab/ModuleWithInstanceVariables
@@ -101,6 +96,48 @@ module NotesActions
private
+ # Lower bound (last_fetched_at as specified in the request) is already set in
+ # the finder. Here, we select between returning all notes since then, or a
+ # page's worth of notes.
+ def gather_notes
+ if Feature.enabled?(:paginated_notes, project)
+ gather_some_notes
+ else
+ gather_all_notes
+ end
+ end
+
+ def gather_all_notes
+ now = Time.current
+ notes = merge_resource_events(notes_finder.execute.inc_relations_for_view)
+
+ [notes, { last_fetched_at: (now.to_i * MICROSECOND) + now.usec }]
+ end
+
+ def gather_some_notes
+ paginator = Gitlab::UpdatedNotesPaginator.new(
+ notes_finder.execute.inc_relations_for_view,
+ last_fetched_at: last_fetched_at
+ )
+
+ notes = paginator.notes
+
+ # Fetch all the synthetic notes in the same time range as the real notes.
+ # Although we don't limit the number, their text is under our control so
+ # should be fairly cheap to process.
+ notes = merge_resource_events(notes, fetch_until: paginator.next_fetched_at)
+
+ [notes, paginator.metadata]
+ end
+
+ def merge_resource_events(notes, fetch_until: nil)
+ return notes if notes_filter == UserPreference::NOTES_FILTERS[:only_comments]
+
+ ResourceEvents::MergeIntoNotesService
+ .new(noteable, current_user, last_fetched_at: last_fetched_at, fetch_until: fetch_until)
+ .execute(notes)
+ end
+
def note_html(note)
render_to_string(
"shared/notes/_note",
@@ -226,11 +263,11 @@ module NotesActions
end
def update_note_params
- params.require(:note).permit(:note)
+ params.require(:note).permit(:note, :position)
end
- def set_polling_interval_header
- Gitlab::PollingInterval.set_header(response, interval: 6_000)
+ def set_polling_interval_header(interval: 6000)
+ Gitlab::PollingInterval.set_header(response, interval: interval)
end
def noteable
@@ -242,7 +279,14 @@ module NotesActions
end
def last_fetched_at
- request.headers['X-Last-Fetched-At']
+ strong_memoize(:last_fetched_at) do
+ microseconds = request.headers['X-Last-Fetched-At'].to_i
+
+ seconds = microseconds / MICROSECOND
+ frac = microseconds % MICROSECOND
+
+ Time.zone.at(seconds, frac)
+ end
end
def notes_filter
diff --git a/app/controllers/concerns/renders_member_access.rb b/app/controllers/concerns/renders_member_access.rb
index 955ac1a1bc8..745830181c1 100644
--- a/app/controllers/concerns/renders_member_access.rb
+++ b/app/controllers/concerns/renders_member_access.rb
@@ -7,12 +7,6 @@ module RendersMemberAccess
groups
end
- def prepare_projects_for_rendering(projects)
- preload_max_member_access_for_collection(Project, projects)
-
- projects
- end
-
private
# rubocop: disable CodeReuse/ActiveRecord
diff --git a/app/controllers/concerns/renders_projects_list.rb b/app/controllers/concerns/renders_projects_list.rb
new file mode 100644
index 00000000000..be45c676ad6
--- /dev/null
+++ b/app/controllers/concerns/renders_projects_list.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+module RendersProjectsList
+ def prepare_projects_for_rendering(projects)
+ preload_max_member_access_for_collection(Project, projects)
+
+ # Call the forks count method on every project, so the BatchLoader would load them all at
+ # once when the entities are rendered
+ projects.each(&:forks_count)
+
+ projects
+ end
+end
diff --git a/app/controllers/concerns/service_params.rb b/app/controllers/concerns/service_params.rb
index e78fa8f8250..a19c43a227a 100644
--- a/app/controllers/concerns/service_params.rb
+++ b/app/controllers/concerns/service_params.rb
@@ -22,8 +22,8 @@ module ServiceParams
:comment_on_event_enabled,
:comment_detail,
:confidential_issues_events,
+ :confluence_url,
:default_irc_uri,
- :description,
:device,
:disable_diffs,
:drone_url,
@@ -31,6 +31,7 @@ module ServiceParams
:external_wiki_url,
:google_iap_service_account_json,
:google_iap_audience_client_id,
+ :inherit_from_id,
# We're using `issues_events` and `merge_requests_events`
# in the view so we still need to explicitly state them
# here. `Service#event_names` would only give
@@ -61,7 +62,6 @@ module ServiceParams
:sound,
:subdomain,
:teamcity_url,
- :title,
:token,
:type,
:url,
diff --git a/app/controllers/concerns/snippets/blobs_actions.rb b/app/controllers/concerns/snippets/blobs_actions.rb
new file mode 100644
index 00000000000..db56ce8f193
--- /dev/null
+++ b/app/controllers/concerns/snippets/blobs_actions.rb
@@ -0,0 +1,53 @@
+# frozen_string_literal: true
+
+module Snippets::BlobsActions
+ extend ActiveSupport::Concern
+
+ include Gitlab::Utils::StrongMemoize
+ include ExtractsRef
+ include Snippets::SendBlob
+
+ included do
+ before_action :authorize_read_snippet!, only: [:raw]
+ before_action :ensure_repository
+ before_action :ensure_blob
+ end
+
+ def raw
+ send_snippet_blob(snippet, blob)
+ end
+
+ private
+
+ def repository_container
+ snippet
+ end
+
+ # rubocop:disable Gitlab/ModuleWithInstanceVariables
+ def blob
+ strong_memoize(:blob) do
+ assign_ref_vars
+
+ next unless @commit
+
+ @repo.blob_at(@commit.id, @path)
+ end
+ end
+ # rubocop:enable Gitlab/ModuleWithInstanceVariables
+
+ def ensure_blob
+ render_404 unless blob
+ end
+
+ def ensure_repository
+ unless snippet.repo_exists?
+ Gitlab::AppLogger.error(message: "Snippet raw blob attempt with no repo", snippet: snippet.id)
+
+ respond_422
+ end
+ end
+
+ def snippet_id
+ params[:snippet_id]
+ end
+end
diff --git a/app/controllers/concerns/snippets/send_blob.rb b/app/controllers/concerns/snippets/send_blob.rb
new file mode 100644
index 00000000000..4f432430aaa
--- /dev/null
+++ b/app/controllers/concerns/snippets/send_blob.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+module Snippets::SendBlob
+ include SendsBlob
+
+ def send_snippet_blob(snippet, blob)
+ workhorse_set_content_type!
+
+ send_blob(
+ snippet.repository,
+ blob,
+ inline: content_disposition == 'inline',
+ allow_caching: snippet.public?
+ )
+ end
+
+ private
+
+ def content_disposition
+ @disposition ||= params[:inline] == 'false' ? 'attachment' : 'inline'
+ end
+end
diff --git a/app/controllers/concerns/snippets_actions.rb b/app/controllers/concerns/snippets_actions.rb
index 51fc12398d9..048b18c5c61 100644
--- a/app/controllers/concerns/snippets_actions.rb
+++ b/app/controllers/concerns/snippets_actions.rb
@@ -2,11 +2,13 @@
module SnippetsActions
extend ActiveSupport::Concern
- include SendsBlob
+
include RendersNotes
include RendersBlob
include PaginatedCollection
include Gitlab::NoteableMetadata
+ include Snippets::SendBlob
+ include SnippetsSort
included do
skip_before_action :verify_authenticity_token,
@@ -25,6 +27,10 @@ module SnippetsActions
render 'edit'
end
+ # This endpoint is being replaced by Snippets::BlobController#raw
+ # Support for old raw links will be maintainted via this action but
+ # it will only return the first blob found,
+ # see: https://gitlab.com/gitlab-org/gitlab/-/issues/217775
def raw
workhorse_set_content_type!
@@ -39,12 +45,7 @@ module SnippetsActions
filename: Snippet.sanitized_file_name(blob.name)
)
else
- send_blob(
- snippet.repository,
- blob,
- inline: content_disposition == 'inline',
- allow_caching: snippet.public?
- )
+ send_snippet_blob(snippet, blob)
end
end
@@ -106,10 +107,6 @@ module SnippetsActions
private
- def content_disposition
- @disposition ||= params[:inline] == 'false' ? 'attachment' : 'inline'
- end
-
# rubocop:disable Gitlab/ModuleWithInstanceVariables
def blob
return unless snippet
diff --git a/app/controllers/concerns/snippets_sort.rb b/app/controllers/concerns/snippets_sort.rb
new file mode 100644
index 00000000000..f122c843af7
--- /dev/null
+++ b/app/controllers/concerns/snippets_sort.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+module SnippetsSort
+ extend ActiveSupport::Concern
+
+ def sort_param
+ params[:sort].presence || 'updated_desc'
+ end
+end
diff --git a/app/controllers/concerns/wiki_actions.rb b/app/controllers/concerns/wiki_actions.rb
index 7eef12fadfe..a5182000f5b 100644
--- a/app/controllers/concerns/wiki_actions.rb
+++ b/app/controllers/concerns/wiki_actions.rb
@@ -1,6 +1,8 @@
# frozen_string_literal: true
module WikiActions
+ include DiffHelper
+ include PreviewMarkdown
include SendsBlob
include Gitlab::Utils::StrongMemoize
extend ActiveSupport::Concern
@@ -11,16 +13,23 @@ module WikiActions
before_action :authorize_admin_wiki!, only: :destroy
before_action :wiki
- before_action :page, only: [:show, :edit, :update, :history, :destroy]
+ before_action :page, only: [:show, :edit, :update, :history, :destroy, :diff]
before_action :load_sidebar, except: [:pages]
+ before_action :set_content_class
before_action only: [:show, :edit, :update] do
@valid_encoding = valid_encoding?
end
before_action only: [:edit, :update], unless: :valid_encoding? do
- redirect_to wiki_page_path(wiki, page)
+ if params[:id].present?
+ redirect_to wiki_page_path(wiki, page || params[:id])
+ else
+ redirect_to wiki_path(wiki)
+ end
end
+
+ helper_method :view_file_button, :diff_file_html_data
end
def new
@@ -133,6 +142,19 @@ module WikiActions
# rubocop:enable Gitlab/ModuleWithInstanceVariables
# rubocop:disable Gitlab/ModuleWithInstanceVariables
+ def diff
+ return render_404 unless page
+
+ apply_diff_view_cookie!
+
+ @diffs = page.diffs(diff_options)
+ @diff_notes_disabled = true
+
+ render 'shared/wikis/diff'
+ end
+ # rubocop:disable Gitlab/ModuleWithInstanceVariables
+
+ # rubocop:disable Gitlab/ModuleWithInstanceVariables
def destroy
WikiPages::DestroyService.new(container: container, current_user: current_user).execute(page)
@@ -203,7 +225,7 @@ module WikiActions
def page_params
keys = [:id]
- keys << :version_id if params[:action] == 'show'
+ keys << :version_id if %w[show diff].include?(params[:action])
params.values_at(*keys)
end
@@ -229,4 +251,25 @@ module WikiActions
wiki.repository.blob_at(commit.id, params[:id])
end
end
+
+ def set_content_class
+ @content_class = 'limit-container-width' unless fluid_layout # rubocop:disable Gitlab/ModuleWithInstanceVariables
+ end
+
+ # Override CommitsHelper#view_file_button
+ def view_file_button(commit_sha, *args)
+ path = wiki_page_path(wiki, page, version_id: page.version.id)
+
+ helpers.link_to(path, class: 'btn') do
+ helpers.raw(_('View page @ ')) + helpers.content_tag(:span, Commit.truncate_sha(commit_sha), class: 'commit-sha')
+ end
+ end
+
+ # Override DiffHelper#diff_file_html_data
+ def diff_file_html_data(_project, _diff_file_path, diff_commit_id)
+ {
+ blob_diff_path: wiki_page_path(wiki, page, action: :diff, version_id: diff_commit_id),
+ view: diff_view
+ }
+ end
end
diff --git a/app/controllers/dashboard/projects_controller.rb b/app/controllers/dashboard/projects_controller.rb
index 25c48fadf49..ad64b6c4f94 100644
--- a/app/controllers/dashboard/projects_controller.rb
+++ b/app/controllers/dashboard/projects_controller.rb
@@ -3,9 +3,10 @@
class Dashboard::ProjectsController < Dashboard::ApplicationController
include ParamsBackwardCompatibility
include RendersMemberAccess
- include OnboardingExperimentHelper
+ include RendersProjectsList
include SortingHelper
include SortingPreference
+ include FiltersEvents
prepend_before_action(only: [:index]) { authenticate_sessionless_user!(:rss) }
before_action :set_non_archived_param
diff --git a/app/controllers/dashboard/snippets_controller.rb b/app/controllers/dashboard/snippets_controller.rb
index aa09fcdbe61..a8ca3dbd0e7 100644
--- a/app/controllers/dashboard/snippets_controller.rb
+++ b/app/controllers/dashboard/snippets_controller.rb
@@ -3,6 +3,7 @@
class Dashboard::SnippetsController < Dashboard::ApplicationController
include PaginatedCollection
include Gitlab::NoteableMetadata
+ include SnippetsSort
skip_cross_project_access_check :index
@@ -11,7 +12,7 @@ class Dashboard::SnippetsController < Dashboard::ApplicationController
.new(current_user, author: current_user)
.execute
- @snippets = SnippetsFinder.new(current_user, author: current_user, scope: params[:scope])
+ @snippets = SnippetsFinder.new(current_user, author: current_user, scope: params[:scope], sort: sort_param)
.execute
.page(params[:page])
.inc_author
diff --git a/app/controllers/dashboard/todos_controller.rb b/app/controllers/dashboard/todos_controller.rb
index 8a8064b24c2..db40b0bed77 100644
--- a/app/controllers/dashboard/todos_controller.rb
+++ b/app/controllers/dashboard/todos_controller.rb
@@ -3,11 +3,14 @@
class Dashboard::TodosController < Dashboard::ApplicationController
include ActionView::Helpers::NumberHelper
include PaginatedCollection
+ include Analytics::UniqueVisitsHelper
before_action :authorize_read_project!, only: :index
before_action :authorize_read_group!, only: :index
before_action :find_todos, only: [:index, :destroy_all]
+ track_unique_visits :index, target_id: 'u_analytics_todos'
+
def index
@sort = params[:sort]
@todos = @todos.page(params[:page])
diff --git a/app/controllers/dashboard_controller.rb b/app/controllers/dashboard_controller.rb
index dd9e6488bc5..07cc31fb7d3 100644
--- a/app/controllers/dashboard_controller.rb
+++ b/app/controllers/dashboard_controller.rb
@@ -2,6 +2,7 @@
class DashboardController < Dashboard::ApplicationController
include IssuableCollectionsAction
+ include FiltersEvents
prepend_before_action(only: [:issues]) { authenticate_sessionless_user!(:rss) }
prepend_before_action(only: [:issues_calendar]) { authenticate_sessionless_user!(:ics) }
diff --git a/app/controllers/explore/projects_controller.rb b/app/controllers/explore/projects_controller.rb
index 705a586d614..f1f41e67a4c 100644
--- a/app/controllers/explore/projects_controller.rb
+++ b/app/controllers/explore/projects_controller.rb
@@ -4,6 +4,7 @@ class Explore::ProjectsController < Explore::ApplicationController
include PageLimiter
include ParamsBackwardCompatibility
include RendersMemberAccess
+ include RendersProjectsList
include SortingHelper
include SortingPreference
diff --git a/app/controllers/groups/application_controller.rb b/app/controllers/groups/application_controller.rb
index 84c8d7ada43..9c2e361e92f 100644
--- a/app/controllers/groups/application_controller.rb
+++ b/app/controllers/groups/application_controller.rb
@@ -30,25 +30,25 @@ class Groups::ApplicationController < ApplicationController
def authorize_admin_group!
unless can?(current_user, :admin_group, group)
- return render_404
+ render_404
end
end
def authorize_create_deploy_token!
unless can?(current_user, :create_deploy_token, group)
- return render_404
+ render_404
end
end
def authorize_destroy_deploy_token!
unless can?(current_user, :destroy_deploy_token, group)
- return render_404
+ render_404
end
end
def authorize_admin_group_member!
unless can?(current_user, :admin_group_member, group)
- return render_403
+ render_403
end
end
diff --git a/app/controllers/groups/boards_controller.rb b/app/controllers/groups/boards_controller.rb
index c618ee8566a..23d4f0d24e9 100644
--- a/app/controllers/groups/boards_controller.rb
+++ b/app/controllers/groups/boards_controller.rb
@@ -8,7 +8,6 @@ class Groups::BoardsController < Groups::ApplicationController
before_action :assign_endpoint_vars
before_action do
push_frontend_feature_flag(:multi_select_board, default_enabled: true)
- push_frontend_feature_flag(:sfc_issue_boards, default_enabled: true)
push_frontend_feature_flag(:boards_with_swimlanes, group, default_enabled: false)
end
diff --git a/app/controllers/groups/clusters_controller.rb b/app/controllers/groups/clusters_controller.rb
index 2165dee45fb..33bfc24885f 100644
--- a/app/controllers/groups/clusters_controller.rb
+++ b/app/controllers/groups/clusters_controller.rb
@@ -17,6 +17,12 @@ class Groups::ClustersController < Clusters::ClustersController
def group
@group ||= find_routable!(Group, params[:group_id] || params[:id])
end
-end
-Groups::ClustersController.prepend_if_ee('EE::Groups::ClustersController')
+ def metrics_dashboard_params
+ {
+ cluster: cluster,
+ cluster_type: :group,
+ group: group
+ }
+ end
+end
diff --git a/app/controllers/groups/runners_controller.rb b/app/controllers/groups/runners_controller.rb
index 635c248024e..edebffe2912 100644
--- a/app/controllers/groups/runners_controller.rb
+++ b/app/controllers/groups/runners_controller.rb
@@ -23,9 +23,13 @@ class Groups::RunnersController < Groups::ApplicationController
end
def destroy
- @runner.destroy
+ if @runner.belongs_to_more_than_one_project?
+ redirect_to group_settings_ci_cd_path(@group, anchor: 'runners-settings'), status: :found, alert: _('Runner was not deleted because it is assigned to multiple projects.')
+ else
+ @runner.destroy
- redirect_to group_settings_ci_cd_path(@group, anchor: 'runners-settings'), status: :found
+ redirect_to group_settings_ci_cd_path(@group, anchor: 'runners-settings'), status: :found
+ end
end
def resume
@@ -47,7 +51,9 @@ class Groups::RunnersController < Groups::ApplicationController
private
def runner
- @runner ||= @group.runners.find(params[:id])
+ @runner ||= Ci::RunnersFinder.new(current_user: current_user, group: @group, params: {}).execute
+ .except(:limit, :offset)
+ .find(params[:id])
end
def runner_params
diff --git a/app/controllers/groups/settings/ci_cd_controller.rb b/app/controllers/groups/settings/ci_cd_controller.rb
index 18f336eae78..bf3a38ce57b 100644
--- a/app/controllers/groups/settings/ci_cd_controller.rb
+++ b/app/controllers/groups/settings/ci_cd_controller.rb
@@ -11,7 +11,15 @@ module Groups
end
before_action :define_variables, only: [:show]
+ NUMBER_OF_RUNNERS_PER_PAGE = 4
+
def show
+ runners_finder = Ci::RunnersFinder.new(current_user: current_user, group: @group, params: params)
+ # We need all runners for count
+ @all_group_runners = runners_finder.execute.except(:limit, :offset)
+ @group_runners = runners_finder.execute.page(params[:page]).per(NUMBER_OF_RUNNERS_PER_PAGE)
+
+ @sort = runners_finder.sort_key
end
def update
diff --git a/app/controllers/groups/variables_controller.rb b/app/controllers/groups/variables_controller.rb
index 11e3cfb01e4..02b015e8e53 100644
--- a/app/controllers/groups/variables_controller.rb
+++ b/app/controllers/groups/variables_controller.rb
@@ -9,7 +9,7 @@ module Groups
def show
respond_to do |format|
format.json do
- render status: :ok, json: { variables: GroupVariableSerializer.new.represent(@group.variables) }
+ render status: :ok, json: { variables: ::Ci::GroupVariableSerializer.new.represent(@group.variables) }
end
end
end
@@ -29,7 +29,7 @@ module Groups
private
def render_group_variables
- render status: :ok, json: { variables: GroupVariableSerializer.new.represent(@group.variables) }
+ render status: :ok, json: { variables: ::Ci::GroupVariableSerializer.new.represent(@group.variables) }
end
def render_error
diff --git a/app/controllers/groups_controller.rb b/app/controllers/groups_controller.rb
index fba374dbb44..2162d397da3 100644
--- a/app/controllers/groups_controller.rb
+++ b/app/controllers/groups_controller.rb
@@ -7,6 +7,7 @@ class GroupsController < Groups::ApplicationController
include PreviewMarkdown
include RecordUserLastActivity
include SendFileUpload
+ include FiltersEvents
extend ::Gitlab::Utils::Override
respond_to :html
diff --git a/app/controllers/ide_controller.rb b/app/controllers/ide_controller.rb
index 2bf7bdd1ae0..2c17f5b5542 100644
--- a/app/controllers/ide_controller.rb
+++ b/app/controllers/ide_controller.rb
@@ -8,6 +8,7 @@ class IdeController < ApplicationController
before_action do
push_frontend_feature_flag(:build_service_proxy)
+ push_frontend_feature_flag(:schema_linting)
end
def index
diff --git a/app/controllers/import/base_controller.rb b/app/controllers/import/base_controller.rb
index afdea4f7c9d..bc05030f8af 100644
--- a/app/controllers/import/base_controller.rb
+++ b/app/controllers/import/base_controller.rb
@@ -30,7 +30,7 @@ class Import::BaseController < ApplicationController
end
def incompatible_repos
- []
+ raise NotImplementedError
end
def provider_name
@@ -87,15 +87,6 @@ class Import::BaseController < ApplicationController
end
# rubocop: enable CodeReuse/ActiveRecord
- # rubocop: disable CodeReuse/ActiveRecord
- def find_jobs(import_type)
- current_user.created_projects
- .with_import_state
- .where(import_type: import_type)
- .to_json(only: [:id], methods: [:import_status])
- end
- # rubocop: enable CodeReuse/ActiveRecord
-
# deprecated: being replaced by app/services/import/base_service.rb
def find_or_create_namespace(names, owner)
names = params[:target_namespace].presence || names
diff --git a/app/controllers/import/bitbucket_controller.rb b/app/controllers/import/bitbucket_controller.rb
index 4886aeb5e3f..0ffd9ef8bdd 100644
--- a/app/controllers/import/bitbucket_controller.rb
+++ b/app/controllers/import/bitbucket_controller.rb
@@ -22,23 +22,8 @@ class Import::BitbucketController < Import::BaseController
redirect_to status_import_bitbucket_url
end
- # rubocop: disable CodeReuse/ActiveRecord
def status
- return super if Feature.enabled?(:new_import_ui)
-
- bitbucket_client = Bitbucket::Client.new(credentials)
- repos = bitbucket_client.repos(filter: sanitized_filter_param)
- @repos, @incompatible_repos = repos.partition { |repo| repo.valid? }
-
- @already_added_projects = find_already_added_projects('bitbucket')
- already_added_projects_names = @already_added_projects.pluck(:import_source)
-
- @repos.to_a.reject! { |repo| already_added_projects_names.include?(repo.full_name) }
- end
- # rubocop: enable CodeReuse/ActiveRecord
-
- def jobs
- render json: find_jobs('bitbucket')
+ super
end
def realtime_changes
diff --git a/app/controllers/import/bitbucket_server_controller.rb b/app/controllers/import/bitbucket_server_controller.rb
index 9aa8110257d..bee78cb3283 100644
--- a/app/controllers/import/bitbucket_server_controller.rb
+++ b/app/controllers/import/bitbucket_server_controller.rb
@@ -7,6 +7,7 @@ class Import::BitbucketServerController < Import::BaseController
before_action :verify_bitbucket_server_import_enabled
before_action :bitbucket_auth, except: [:new, :configure]
+ before_action :normalize_import_params, only: [:create]
before_action :validate_import_params, only: [:create]
rescue_from BitbucketServer::Connection::ConnectionError, with: :bitbucket_connection_error
@@ -34,48 +35,25 @@ class Import::BitbucketServerController < Import::BaseController
return render json: { errors: _("Project %{project_repo} could not be found") % { project_repo: "#{@project_key}/#{@repo_slug}" } }, status: :unprocessable_entity
end
- project_name = params[:new_name].presence || repo.name
- namespace_path = params[:new_namespace].presence || current_user.username
- target_namespace = find_or_create_namespace(namespace_path, current_user)
+ result = Import::BitbucketServerService.new(client, current_user, params).execute(credentials)
- if current_user.can?(:create_projects, target_namespace)
- project = Gitlab::BitbucketServerImport::ProjectCreator.new(@project_key, @repo_slug, repo, project_name, target_namespace, current_user, credentials).execute
-
- if project.persisted?
- render json: ProjectSerializer.new.represent(project, serializer: :import)
- else
- render json: { errors: project_save_error(project) }, status: :unprocessable_entity
- end
+ if result[:status] == :success
+ render json: ProjectSerializer.new.represent(result[:project], serializer: :import)
else
- render json: { errors: _('This namespace has already been taken! Please choose another one.') }, status: :unprocessable_entity
+ render json: { errors: result[:message] }, status: result[:http_status]
end
end
def configure
session[personal_access_token_key] = params[:personal_access_token]
- session[bitbucket_server_username_key] = params[:bitbucket_username]
+ session[bitbucket_server_username_key] = params[:bitbucket_server_username]
session[bitbucket_server_url_key] = params[:bitbucket_server_url]
redirect_to status_import_bitbucket_server_path
end
- # rubocop: disable CodeReuse/ActiveRecord
def status
- return super if Feature.enabled?(:new_import_ui)
-
- @collection = client.repos(page_offset: page_offset, limit: limit_per_page, filter: sanitized_filter_param)
- @repos, @incompatible_repos = @collection.partition { |repo| repo.valid? }
-
- # Use the import URL to filter beyond what BaseService#find_already_added_projects
- @already_added_projects = filter_added_projects('bitbucket_server', @repos.map(&:browse_url))
- already_added_projects_names = @already_added_projects.pluck(:import_source)
-
- @repos.reject! { |repo| already_added_projects_names.include?(repo.browse_url) }
- end
- # rubocop: enable CodeReuse/ActiveRecord
-
- def jobs
- render json: find_jobs('bitbucket_server')
+ super
end
def realtime_changes
@@ -126,9 +104,15 @@ class Import::BitbucketServerController < Import::BaseController
@bitbucket_repos ||= client.repos(page_offset: page_offset, limit: limit_per_page, filter: sanitized_filter_param).to_a
end
+ def normalize_import_params
+ project_key, repo_slug = params[:repo_id].split('/')
+ params[:bitbucket_server_project] = project_key
+ params[:bitbucket_server_repo] = repo_slug
+ end
+
def validate_import_params
- @project_key = params[:project]
- @repo_slug = params[:repository]
+ @project_key = params[:bitbucket_server_project]
+ @repo_slug = params[:bitbucket_server_repo]
return render_validation_error('Missing project key') unless @project_key.present? && @repo_slug.present?
return render_validation_error('Missing repository slug') unless @repo_slug.present?
diff --git a/app/controllers/import/fogbugz_controller.rb b/app/controllers/import/fogbugz_controller.rb
index 91779a5d6cc..a34bc9c953f 100644
--- a/app/controllers/import/fogbugz_controller.rb
+++ b/app/controllers/import/fogbugz_controller.rb
@@ -50,14 +50,7 @@ class Import::FogbugzController < Import::BaseController
return redirect_to new_import_fogbugz_path
end
- return super if Feature.enabled?(:new_import_ui)
-
- @repos = client.repos
-
- @already_added_projects = find_already_added_projects('fogbugz')
- already_added_projects_names = @already_added_projects.pluck(:import_source)
-
- @repos.reject! { |repo| already_added_projects_names.include? repo.name }
+ super
end
# rubocop: enable CodeReuse/ActiveRecord
@@ -65,10 +58,6 @@ class Import::FogbugzController < Import::BaseController
super
end
- def jobs
- render json: find_jobs('fogbugz')
- end
-
def create
repo = client.repo(params[:repo_id])
fb_session = { uri: session[:fogbugz_uri], token: session[:fogbugz_token] }
@@ -96,6 +85,11 @@ class Import::FogbugzController < Import::BaseController
end
# rubocop: enable CodeReuse/ActiveRecord
+ override :incompatible_repos
+ def incompatible_repos
+ []
+ end
+
override :provider_name
def provider_name
:fogbugz
diff --git a/app/controllers/import/gitea_controller.rb b/app/controllers/import/gitea_controller.rb
index 42c23fb29a7..efeff8439e4 100644
--- a/app/controllers/import/gitea_controller.rb
+++ b/app/controllers/import/gitea_controller.rb
@@ -21,15 +21,17 @@ class Import::GiteaController < Import::GithubController
super
end
- private
+ protected
- def host_key
- :"#{provider}_host_url"
+ override :provider_name
+ def provider_name
+ :gitea
end
- override :provider
- def provider
- :gitea
+ private
+
+ def host_key
+ :"#{provider_name}_host_url"
end
override :provider_url
diff --git a/app/controllers/import/github_controller.rb b/app/controllers/import/github_controller.rb
index 097edcd6075..ac6b8c06d66 100644
--- a/app/controllers/import/github_controller.rb
+++ b/app/controllers/import/github_controller.rb
@@ -1,6 +1,8 @@
# frozen_string_literal: true
class Import::GithubController < Import::BaseController
+ extend ::Gitlab::Utils::Override
+
include ImportHelper
include ActionView::Helpers::SanitizeHelper
@@ -34,18 +36,11 @@ class Import::GithubController < Import::BaseController
# Improving in https://gitlab.com/gitlab-org/gitlab-foss/issues/55585
client_repos
- respond_to do |format|
- format.json do
- render json: { imported_projects: serialized_imported_projects,
- provider_repos: serialized_provider_repos,
- namespaces: serialized_namespaces }
- end
- format.html
- end
+ super
end
def create
- result = Import::GithubService.new(client, current_user, import_params).execute(access_params, provider)
+ result = Import::GithubService.new(client, current_user, import_params).execute(access_params, provider_name)
if result[:status] == :success
render json: serialized_imported_projects(result[:project])
@@ -55,44 +50,51 @@ class Import::GithubController < Import::BaseController
end
def realtime_changes
- Gitlab::PollingInterval.set_header(response, interval: 3_000)
-
- render json: already_added_projects.to_json(only: [:id], methods: [:import_status])
+ super
end
- private
+ protected
- def import_params
- params.permit(permitted_import_params)
- end
+ # rubocop: disable CodeReuse/ActiveRecord
+ override :importable_repos
+ def importable_repos
+ already_added_projects_names = already_added_projects.pluck(:import_source)
- def permitted_import_params
- [:repo_id, :new_name, :target_namespace]
+ client_repos.reject { |repo| already_added_projects_names.include?(repo.full_name) }
end
+ # rubocop: enable CodeReuse/ActiveRecord
- def serialized_imported_projects(projects = already_added_projects)
- ProjectSerializer.new.represent(projects, serializer: :import, provider_url: provider_url)
+ override :incompatible_repos
+ def incompatible_repos
+ []
end
- def serialized_provider_repos
- repos = client_repos.reject { |repo| already_added_project_names.include? repo.full_name }
- Import::ProviderRepoSerializer.new(current_user: current_user).represent(repos, provider: provider, provider_url: provider_url)
+ override :provider_name
+ def provider_name
+ :github
end
- def serialized_namespaces
- NamespaceSerializer.new.represent(namespaces)
+ override :provider_url
+ def provider_url
+ strong_memoize(:provider_url) do
+ provider = Gitlab::Auth::OAuth::Provider.config_for('github')
+
+ provider&.dig('url').presence || 'https://github.com'
+ end
end
- def already_added_projects
- @already_added_projects ||= filtered(find_already_added_projects(provider))
+ private
+
+ def import_params
+ params.permit(permitted_import_params)
end
- def already_added_project_names
- @already_added_projects_names ||= already_added_projects.pluck(:import_source) # rubocop:disable CodeReuse/ActiveRecord
+ def permitted_import_params
+ [:repo_id, :new_name, :target_namespace]
end
- def namespaces
- current_user.manageable_groups_with_routes
+ def serialized_imported_projects(projects = already_added_projects)
+ ProjectSerializer.new.represent(projects, serializer: :import, provider_url: provider_url)
end
def expire_etag_cache
@@ -118,29 +120,29 @@ class Import::GithubController < Import::BaseController
end
def import_enabled?
- __send__("#{provider}_import_enabled?") # rubocop:disable GitlabSecurity/PublicSend
+ __send__("#{provider_name}_import_enabled?") # rubocop:disable GitlabSecurity/PublicSend
end
def realtime_changes_path
- public_send("realtime_changes_import_#{provider}_path", format: :json) # rubocop:disable GitlabSecurity/PublicSend
+ public_send("realtime_changes_import_#{provider_name}_path", format: :json) # rubocop:disable GitlabSecurity/PublicSend
end
def new_import_url
- public_send("new_import_#{provider}_url", extra_import_params) # rubocop:disable GitlabSecurity/PublicSend
+ public_send("new_import_#{provider_name}_url", extra_import_params) # rubocop:disable GitlabSecurity/PublicSend
end
def status_import_url
- public_send("status_import_#{provider}_url", extra_import_params) # rubocop:disable GitlabSecurity/PublicSend
+ public_send("status_import_#{provider_name}_url", extra_import_params) # rubocop:disable GitlabSecurity/PublicSend
end
def callback_import_url
- public_send("users_import_#{provider}_callback_url", extra_import_params) # rubocop:disable GitlabSecurity/PublicSend
+ public_send("users_import_#{provider_name}_callback_url", extra_import_params) # rubocop:disable GitlabSecurity/PublicSend
end
def provider_unauthorized
session[access_token_key] = nil
redirect_to new_import_url,
- alert: "Access denied to your #{Gitlab::ImportSources.title(provider.to_s)} account."
+ alert: "Access denied to your #{Gitlab::ImportSources.title(provider_name.to_s)} account."
end
def provider_rate_limit(exception)
@@ -151,29 +153,16 @@ class Import::GithubController < Import::BaseController
end
def access_token_key
- :"#{provider}_access_token"
+ :"#{provider_name}_access_token"
end
def access_params
{ github_access_token: session[access_token_key] }
end
- # The following methods are overridden in subclasses
- def provider
- :github
- end
-
- def provider_url
- strong_memoize(:provider_url) do
- provider = Gitlab::Auth::OAuth::Provider.config_for('github')
-
- provider&.dig('url').presence || 'https://github.com'
- end
- end
-
# rubocop: disable CodeReuse/ActiveRecord
def logged_in_with_provider?
- current_user.identities.exists?(provider: provider)
+ current_user.identities.exists?(provider: provider_name)
end
# rubocop: enable CodeReuse/ActiveRecord
@@ -202,12 +191,6 @@ class Import::GithubController < Import::BaseController
def filter_attribute
:name
end
-
- def filtered(collection)
- return collection unless sanitized_filter_param
-
- collection.select { |item| item[filter_attribute].include?(sanitized_filter_param) }
- end
end
Import::GithubController.prepend_if_ee('EE::Import::GithubController')
diff --git a/app/controllers/import/gitlab_controller.rb b/app/controllers/import/gitlab_controller.rb
index a95a67e208c..cc68eb02741 100644
--- a/app/controllers/import/gitlab_controller.rb
+++ b/app/controllers/import/gitlab_controller.rb
@@ -16,21 +16,8 @@ class Import::GitlabController < Import::BaseController
redirect_to status_import_gitlab_url
end
- # rubocop: disable CodeReuse/ActiveRecord
def status
- return super if Feature.enabled?(:new_import_ui)
-
- @repos = client.projects(starting_page: 1, page_limit: MAX_PROJECT_PAGES, per_page: PER_PAGE_PROJECTS)
-
- @already_added_projects = find_already_added_projects('gitlab')
- already_added_projects_names = @already_added_projects.pluck(:import_source)
-
- @repos = @repos.to_a.reject { |repo| already_added_projects_names.include? repo["path_with_namespace"] }
- end
- # rubocop: enable CodeReuse/ActiveRecord
-
- def jobs
- render json: find_jobs('gitlab')
+ super
end
def create
@@ -63,6 +50,11 @@ class Import::GitlabController < Import::BaseController
end
# rubocop: enable CodeReuse/ActiveRecord
+ override :incompatible_repos
+ def incompatible_repos
+ []
+ end
+
override :provider_name
def provider_name
:gitlab
diff --git a/app/controllers/instance_statistics/cohorts_controller.rb b/app/controllers/instance_statistics/cohorts_controller.rb
index 4b4e39db2e1..0de62a56b01 100644
--- a/app/controllers/instance_statistics/cohorts_controller.rb
+++ b/app/controllers/instance_statistics/cohorts_controller.rb
@@ -1,8 +1,12 @@
# frozen_string_literal: true
class InstanceStatistics::CohortsController < InstanceStatistics::ApplicationController
+ include Analytics::UniqueVisitsHelper
+
before_action :authenticate_usage_ping_enabled_or_admin!
+ track_unique_visits :index, target_id: 'i_analytics_cohorts'
+
def index
if Gitlab::CurrentSettings.usage_ping_enabled
cohorts_results = Rails.cache.fetch('cohorts', expires_in: 1.day) do
diff --git a/app/controllers/instance_statistics/dev_ops_score_controller.rb b/app/controllers/instance_statistics/dev_ops_score_controller.rb
index 238f7fa7707..b98a1bf7f99 100644
--- a/app/controllers/instance_statistics/dev_ops_score_controller.rb
+++ b/app/controllers/instance_statistics/dev_ops_score_controller.rb
@@ -1,6 +1,10 @@
# frozen_string_literal: true
class InstanceStatistics::DevOpsScoreController < InstanceStatistics::ApplicationController
+ include Analytics::UniqueVisitsHelper
+
+ track_unique_visits :index, target_id: 'i_analytics_dev_ops_score'
+
# rubocop: disable CodeReuse/ActiveRecord
def index
@metric = DevOpsScore::Metric.order(:created_at).last&.present
diff --git a/app/controllers/invites_controller.rb b/app/controllers/invites_controller.rb
index a78d87eceea..5bd9ac7f275 100644
--- a/app/controllers/invites_controller.rb
+++ b/app/controllers/invites_controller.rb
@@ -1,12 +1,17 @@
# frozen_string_literal: true
class InvitesController < ApplicationController
+ include Gitlab::Utils::StrongMemoize
+
before_action :member
skip_before_action :authenticate_user!, only: :decline
+ helper_method :member?, :current_user_matches_invite?
+
respond_to :html
def show
+ accept if skip_invitation_prompt?
end
def accept
@@ -38,6 +43,20 @@ class InvitesController < ApplicationController
private
+ def skip_invitation_prompt?
+ !member? && current_user_matches_invite?
+ end
+
+ def current_user_matches_invite?
+ @member.invite_email == current_user.email
+ end
+
+ def member?
+ strong_memoize(:is_member) do
+ @member.source.users.include?(current_user)
+ end
+ end
+
def member
return @member if defined?(@member)
diff --git a/app/controllers/oauth/applications_controller.rb b/app/controllers/oauth/applications_controller.rb
index 2c3e60d12b7..6532501733a 100644
--- a/app/controllers/oauth/applications_controller.rb
+++ b/app/controllers/oauth/applications_controller.rb
@@ -17,6 +17,8 @@ class Oauth::ApplicationsController < Doorkeeper::ApplicationsController
before_action :add_gon_variables
before_action :load_scopes, only: [:index, :create, :edit, :update]
+ around_action :set_locale
+
helper_method :can?
layout 'profile'
@@ -70,4 +72,8 @@ class Oauth::ApplicationsController < Doorkeeper::ApplicationsController
params[:owner] = current_user
end
end
+
+ def set_locale(&block)
+ Gitlab::I18n.with_user_locale(current_user, &block)
+ end
end
diff --git a/app/controllers/omniauth_callbacks_controller.rb b/app/controllers/omniauth_callbacks_controller.rb
index 4c595313cb6..706a4843117 100644
--- a/app/controllers/omniauth_callbacks_controller.rb
+++ b/app/controllers/omniauth_callbacks_controller.rb
@@ -200,7 +200,7 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
def fail_login(user)
error_message = user.errors.full_messages.to_sentence
- return redirect_to omniauth_error_path(oauth['provider'], error: error_message)
+ redirect_to omniauth_error_path(oauth['provider'], error: error_message)
end
def fail_auth0_login
diff --git a/app/controllers/profiles/keys_controller.rb b/app/controllers/profiles/keys_controller.rb
index b9cb71ae89a..99e1b9027fa 100644
--- a/app/controllers/profiles/keys_controller.rb
+++ b/app/controllers/profiles/keys_controller.rb
@@ -1,8 +1,6 @@
# frozen_string_literal: true
class Profiles::KeysController < Profiles::ApplicationController
- skip_before_action :authenticate_user!, only: [:get_keys]
-
def index
@keys = current_user.keys.order_id_desc
@key = Key.new
@@ -33,25 +31,6 @@ class Profiles::KeysController < Profiles::ApplicationController
end
end
- # Get all keys of a user(params[:username]) in a text format
- # Helpful for sysadmins to put in respective servers
- def get_keys
- if params[:username].present?
- begin
- user = UserFinder.new(params[:username]).find_by_username
- if user.present?
- render plain: user.all_ssh_keys.join("\n")
- else
- return render_404
- end
- rescue => e
- render html: e.message
- end
- else
- return render_404
- end
- end
-
private
def key_params
diff --git a/app/controllers/profiles/personal_access_tokens_controller.rb b/app/controllers/profiles/personal_access_tokens_controller.rb
index f1c07cd9a1d..30f25e8fdaa 100644
--- a/app/controllers/profiles/personal_access_tokens_controller.rb
+++ b/app/controllers/profiles/personal_access_tokens_controller.rb
@@ -40,14 +40,18 @@ class Profiles::PersonalAccessTokensController < Profiles::ApplicationController
params.require(:personal_access_token).permit(:name, :expires_at, scopes: [])
end
- # rubocop: disable CodeReuse/ActiveRecord
def set_index_vars
@scopes = Gitlab::Auth.available_scopes_for(current_user)
@inactive_personal_access_tokens = finder(state: 'inactive').execute
- @active_personal_access_tokens = finder(state: 'active').execute.order(:expires_at)
+ @active_personal_access_tokens = active_personal_access_tokens
@new_personal_access_token = PersonalAccessToken.redis_getdel(current_user.id)
end
- # rubocop: enable CodeReuse/ActiveRecord
+
+ def active_personal_access_tokens
+ finder(state: 'active', sort: 'expires_at_asc').execute
+ end
end
+
+Profiles::PersonalAccessTokensController.prepend_if_ee('EE::Profiles::PersonalAccessTokensController')
diff --git a/app/controllers/profiles/preferences_controller.rb b/app/controllers/profiles/preferences_controller.rb
index 1477d79c911..8653fe3b6ed 100644
--- a/app/controllers/profiles/preferences_controller.rb
+++ b/app/controllers/profiles/preferences_controller.rb
@@ -48,6 +48,7 @@ class Profiles::PreferencesController < Profiles::ApplicationController
:time_display_relative,
:time_format_in_24h,
:show_whitespace_in_diffs,
+ :view_diffs_file_by_file,
:tab_width,
:sourcegraph_enabled,
:render_whitespace_in_code
diff --git a/app/controllers/projects/application_controller.rb b/app/controllers/projects/application_controller.rb
index b1f285f76d7..518d414be1b 100644
--- a/app/controllers/projects/application_controller.rb
+++ b/app/controllers/projects/application_controller.rb
@@ -42,7 +42,7 @@ class Projects::ApplicationController < ApplicationController
def authorize_action!(action)
unless can?(current_user, action, project)
- return access_denied!
+ access_denied!
end
end
@@ -81,10 +81,6 @@ class Projects::ApplicationController < ApplicationController
end
end
- def apply_diff_view_cookie!
- set_secure_cookie(:diff_view, params.delete(:view), permanent: true) if params[:view].present?
- end
-
def require_pages_enabled!
not_found unless @project.pages_available?
end
diff --git a/app/controllers/projects/blob_controller.rb b/app/controllers/projects/blob_controller.rb
index 14dca1bdc30..7f14522e61b 100644
--- a/app/controllers/projects/blob_controller.rb
+++ b/app/controllers/projects/blob_controller.rb
@@ -9,6 +9,7 @@ class Projects::BlobController < Projects::ApplicationController
include ActionView::Helpers::SanitizeHelper
include RedirectsForMissingPathOnTree
include SourcegraphDecorator
+ include DiffHelper
prepend_before_action :authenticate_user!, only: [:edit]
@@ -129,7 +130,7 @@ class Projects::BlobController < Projects::ApplicationController
end
end
- return redirect_to_tree_root_for_missing_path(@project, @ref, @path)
+ redirect_to_tree_root_for_missing_path(@project, @ref, @path)
end
end
@@ -207,14 +208,14 @@ class Projects::BlobController < Projects::ApplicationController
def set_last_commit_sha
@last_commit_sha = Gitlab::Git::Commit
- .last_for_path(@repository, @ref, @path).sha
+ .last_for_path(@repository, @ref, @path, literal_pathspec: true).sha
end
def show_html
environment_params = @repository.branch_exists?(@ref) ? { ref: @ref } : { commit: @commit }
environment_params[:find_latest] = true
@environment = EnvironmentsFinder.new(@project, current_user, environment_params).execute.last
- @last_commit = @repository.last_commit_for_path(@commit.id, @blob.path)
+ @last_commit = @repository.last_commit_for_path(@commit.id, @blob.path, literal_pathspec: true)
@code_navigation_path = Gitlab::CodeNavigationPath.new(@project, @blob.commit_id).full_json_path_for(@blob.path)
render 'show'
diff --git a/app/controllers/projects/boards_controller.rb b/app/controllers/projects/boards_controller.rb
index 8fa823e0be1..db05da0bb7f 100644
--- a/app/controllers/projects/boards_controller.rb
+++ b/app/controllers/projects/boards_controller.rb
@@ -9,7 +9,6 @@ class Projects::BoardsController < Projects::ApplicationController
before_action :assign_endpoint_vars
before_action do
push_frontend_feature_flag(:multi_select_board, default_enabled: true)
- push_frontend_feature_flag(:sfc_issue_boards, default_enabled: true)
end
private
diff --git a/app/controllers/projects/ci/lints_controller.rb b/app/controllers/projects/ci/lints_controller.rb
index b50afa12da0..73b3eb9c205 100644
--- a/app/controllers/projects/ci/lints_controller.rb
+++ b/app/controllers/projects/ci/lints_controller.rb
@@ -14,7 +14,7 @@ class Projects::Ci::LintsController < Projects::ApplicationController
@errors = result.errors
if result.valid?
- @config_processor = result.content
+ @config_processor = result.config
@stages = @config_processor.stages
@builds = @config_processor.builds
@jobs = @config_processor.jobs
diff --git a/app/controllers/projects/clusters_controller.rb b/app/controllers/projects/clusters_controller.rb
index 079d30127d6..8acf5235c1a 100644
--- a/app/controllers/projects/clusters_controller.rb
+++ b/app/controllers/projects/clusters_controller.rb
@@ -23,6 +23,13 @@ class Projects::ClustersController < Clusters::ClustersController
def repository
@repository ||= project.repository
end
-end
-Projects::ClustersController.prepend_if_ee('EE::Projects::ClustersController')
+ def metrics_dashboard_params
+ params.permit(:embedded, :group, :title, :y_label).merge(
+ {
+ cluster: cluster,
+ cluster_type: :project
+ }
+ )
+ end
+end
diff --git a/app/controllers/projects/confluences_controller.rb b/app/controllers/projects/confluences_controller.rb
new file mode 100644
index 00000000000..d563b34a362
--- /dev/null
+++ b/app/controllers/projects/confluences_controller.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+
+class Projects::ConfluencesController < Projects::ApplicationController
+ before_action :ensure_confluence
+
+ def show
+ end
+
+ private
+
+ def ensure_confluence
+ render_404 unless project.has_confluence?
+ end
+end
diff --git a/app/controllers/projects/cycle_analytics_controller.rb b/app/controllers/projects/cycle_analytics_controller.rb
index f13c75ac4cc..898d888c978 100644
--- a/app/controllers/projects/cycle_analytics_controller.rb
+++ b/app/controllers/projects/cycle_analytics_controller.rb
@@ -4,10 +4,13 @@ class Projects::CycleAnalyticsController < Projects::ApplicationController
include ActionView::Helpers::DateHelper
include ActionView::Helpers::TextHelper
include CycleAnalyticsParams
+ include Analytics::UniqueVisitsHelper
before_action :whitelist_query_limiting, only: [:show]
before_action :authorize_read_cycle_analytics!
+ track_unique_visits :show, target_id: 'p_analytics_valuestream'
+
def show
@cycle_analytics = ::CycleAnalytics::ProjectLevel.new(@project, options: options(cycle_analytics_project_params))
diff --git a/app/controllers/projects/deployments_controller.rb b/app/controllers/projects/deployments_controller.rb
index 766e2f86ea2..1344cf775e4 100644
--- a/app/controllers/projects/deployments_controller.rb
+++ b/app/controllers/projects/deployments_controller.rb
@@ -1,7 +1,6 @@
# frozen_string_literal: true
class Projects::DeploymentsController < Projects::ApplicationController
- before_action :authorize_read_environment!
before_action :authorize_read_deployment!
# rubocop: disable CodeReuse/ActiveRecord
diff --git a/app/controllers/projects/environments/prometheus_api_controller.rb b/app/controllers/projects/environments/prometheus_api_controller.rb
index 98fcc594d6e..f0bb5360f84 100644
--- a/app/controllers/projects/environments/prometheus_api_controller.rb
+++ b/app/controllers/projects/environments/prometheus_api_controller.rb
@@ -1,51 +1,17 @@
# frozen_string_literal: true
class Projects::Environments::PrometheusApiController < Projects::ApplicationController
- include RenderServiceResults
+ include Metrics::Dashboard::PrometheusApiProxy
- before_action :authorize_read_prometheus!
- before_action :environment
-
- def proxy
- variable_substitution_result =
- variable_substitution_service.new(environment, permit_params).execute
-
- if variable_substitution_result[:status] == :error
- return error_response(variable_substitution_result)
- end
-
- prometheus_result = Prometheus::ProxyService.new(
- environment,
- proxy_method,
- proxy_path,
- variable_substitution_result[:params]
- ).execute
-
- return continue_polling_response if prometheus_result.nil?
- return error_response(prometheus_result) if prometheus_result[:status] == :error
-
- success_response(prometheus_result)
- end
+ before_action :proxyable
private
- def variable_substitution_service
- Prometheus::ProxyVariableSubstitutionService
- end
-
- def permit_params
- params.permit!
- end
-
- def environment
- @environment ||= project.environments.find(params[:id])
+ def proxyable
+ @proxyable ||= project.environments.find(params[:id])
end
- def proxy_method
- request.method
- end
-
- def proxy_path
- params[:proxy_path]
+ def proxy_variable_substitution_service
+ Prometheus::ProxyVariableSubstitutionService
end
end
diff --git a/app/controllers/projects/environments_controller.rb b/app/controllers/projects/environments_controller.rb
index 4d774123ef1..d5da24a76de 100644
--- a/app/controllers/projects/environments_controller.rb
+++ b/app/controllers/projects/environments_controller.rb
@@ -1,6 +1,10 @@
# frozen_string_literal: true
class Projects::EnvironmentsController < Projects::ApplicationController
+ # Metrics dashboard code is getting decoupled from environments and is being moved
+ # into app/controllers/projects/metrics_dashboard_controller.rb
+ # See https://gitlab.com/gitlab-org/gitlab/-/issues/226002 for more details.
+
include MetricsDashboard
layout 'project'
diff --git a/app/controllers/projects/forks_controller.rb b/app/controllers/projects/forks_controller.rb
index ebc81976529..b93f6384e0c 100644
--- a/app/controllers/projects/forks_controller.rb
+++ b/app/controllers/projects/forks_controller.rb
@@ -3,6 +3,7 @@
class Projects::ForksController < Projects::ApplicationController
include ContinueParams
include RendersMemberAccess
+ include RendersProjectsList
include Gitlab::Utils::StrongMemoize
# Authorize
diff --git a/app/controllers/projects/graphs_controller.rb b/app/controllers/projects/graphs_controller.rb
index a8b90f8685f..9b889f9e837 100644
--- a/app/controllers/projects/graphs_controller.rb
+++ b/app/controllers/projects/graphs_controller.rb
@@ -2,12 +2,15 @@
class Projects::GraphsController < Projects::ApplicationController
include ExtractsPath
+ include Analytics::UniqueVisitsHelper
# Authorize
before_action :require_non_empty_project
before_action :assign_ref_vars
before_action :authorize_read_repository_graphs!
+ track_unique_visits :charts, target_id: 'p_analytics_repo'
+
def show
respond_to do |format|
format.html
diff --git a/app/controllers/projects/imports_controller.rb b/app/controllers/projects/imports_controller.rb
index 67a7daf8445..deba71c9dd3 100644
--- a/app/controllers/projects/imports_controller.rb
+++ b/app/controllers/projects/imports_controller.rb
@@ -5,7 +5,8 @@ class Projects::ImportsController < Projects::ApplicationController
include ImportUrlParams
# Authorize
- before_action :authorize_admin_project!
+ before_action :authorize_admin_project!, only: [:new, :create]
+ before_action :require_namespace_project_creation_permission, only: :show
before_action :require_no_repo, only: [:new, :create]
before_action :redirect_if_progress, only: [:new, :create]
before_action :redirect_if_no_import, only: :show
@@ -51,6 +52,10 @@ class Projects::ImportsController < Projects::ApplicationController
end
end
+ def require_namespace_project_creation_permission
+ render_404 unless current_user.can?(:admin_project, @project) || current_user.can?(:create_projects, @project.namespace)
+ end
+
def redirect_if_progress
if @project.import_in_progress?
redirect_to project_import_path(@project)
diff --git a/app/controllers/projects/incident_management/pager_duty_incidents_controller.rb b/app/controllers/projects/incident_management/pager_duty_incidents_controller.rb
new file mode 100644
index 00000000000..dac1640dd08
--- /dev/null
+++ b/app/controllers/projects/incident_management/pager_duty_incidents_controller.rb
@@ -0,0 +1,35 @@
+# frozen_string_literal: true
+
+module Projects
+ module IncidentManagement
+ class PagerDutyIncidentsController < Projects::ApplicationController
+ respond_to :json
+
+ skip_before_action :verify_authenticity_token
+ skip_before_action :project
+
+ prepend_before_action :project_without_auth
+
+ def create
+ result = webhook_processor.execute(params[:token])
+
+ head result.http_status
+ end
+
+ private
+
+ def project_without_auth
+ @project ||= Project
+ .find_by_full_path("#{params[:namespace_id]}/#{params[:project_id]}")
+ end
+
+ def webhook_processor
+ ::IncidentManagement::PagerDuty::ProcessWebhookService.new(project, nil, payload)
+ end
+
+ def payload
+ @payload ||= params.permit![:pager_duty_incident].to_h
+ end
+ end
+ end
+end
diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb
index 693329848de..12b5a538bc9 100644
--- a/app/controllers/projects/issues_controller.rb
+++ b/app/controllers/projects/issues_controller.rb
@@ -11,11 +11,11 @@ class Projects::IssuesController < Projects::ApplicationController
include RecordUserLastActivity
def issue_except_actions
- %i[index calendar new create bulk_update import_csv export_csv]
+ %i[index calendar new create bulk_update import_csv export_csv service_desk]
end
def set_issuables_index_only_actions
- %i[index calendar]
+ %i[index calendar service_desk]
end
prepend_before_action(only: [:index]) { authenticate_sessionless_user!(:rss) }
@@ -46,10 +46,17 @@ class Projects::IssuesController < Projects::ApplicationController
before_action do
push_frontend_feature_flag(:vue_issuable_sidebar, project.group)
+ push_frontend_feature_flag(:tribute_autocomplete, @project)
+ push_frontend_feature_flag(:vue_issuables_list, project)
end
before_action only: :show do
push_frontend_feature_flag(:real_time_issue_sidebar, @project)
+ push_frontend_feature_flag(:confidential_apollo_sidebar, @project)
+ end
+
+ before_action only: :index do
+ push_frontend_feature_flag(:scoped_labels, @project)
end
around_action :allow_gitaly_ref_name_caching, only: [:discussions]
@@ -216,6 +223,11 @@ class Projects::IssuesController < Projects::ApplicationController
redirect_to project_issues_path(project)
end
+ def service_desk
+ @issues = @issuables # rubocop:disable Gitlab/ModuleWithInstanceVariables
+ @users.push(User.support_bot) # rubocop:disable Gitlab/ModuleWithInstanceVariables
+ end
+
protected
def sorting_field
@@ -313,6 +325,17 @@ class Projects::IssuesController < Projects::ApplicationController
private
+ def finder_options
+ options = super
+
+ return options unless service_desk?
+
+ options.reject! { |key| key == 'author_username' || key == 'author_id' }
+ options[:author_id] = User.support_bot
+
+ options
+ end
+
def branch_link(branch)
project_compare_path(project, from: project.default_branch, to: branch[:name])
end
@@ -330,6 +353,10 @@ class Projects::IssuesController < Projects::ApplicationController
def rate_limiter
::Gitlab::ApplicationRateLimiter
end
+
+ def service_desk?
+ action_name == 'service_desk'
+ end
end
Projects::IssuesController.prepend_if_ee('EE::Projects::IssuesController')
diff --git a/app/controllers/projects/jobs_controller.rb b/app/controllers/projects/jobs_controller.rb
index e1f6cbe3dca..3f7f8da3478 100644
--- a/app/controllers/projects/jobs_controller.rb
+++ b/app/controllers/projects/jobs_controller.rb
@@ -11,9 +11,6 @@ class Projects::JobsController < Projects::ApplicationController
before_action :authorize_erase_build!, only: [:erase]
before_action :authorize_use_build_terminal!, only: [:terminal, :terminal_websocket_authorize]
before_action :verify_api_request!, only: :terminal_websocket_authorize
- before_action only: [:show] do
- push_frontend_feature_flag(:job_log_json, project, default_enabled: true)
- end
before_action :authorize_create_proxy_build!, only: :proxy_websocket_authorize
before_action :verify_proxy_request!, only: :proxy_websocket_authorize
@@ -55,15 +52,10 @@ class Projects::JobsController < Projects::ApplicationController
format.json do
build.trace.being_watched!
- # TODO: when the feature flag is removed we should not pass
- # content_format to serialize method.
- content_format = Feature.enabled?(:job_log_json, @project, default_enabled: true) ? :json : :html
-
build_trace = Ci::BuildTrace.new(
build: @build,
stream: stream,
- state: params[:state],
- content_format: content_format)
+ state: params[:state])
render json: BuildTraceSerializer
.new(project: @project, current_user: @current_user)
diff --git a/app/controllers/projects/logs_controller.rb b/app/controllers/projects/logs_controller.rb
index ba509235417..b9027b3a2cb 100644
--- a/app/controllers/projects/logs_controller.rb
+++ b/app/controllers/projects/logs_controller.rb
@@ -2,15 +2,16 @@
module Projects
class LogsController < Projects::ApplicationController
+ include ::Gitlab::Utils::StrongMemoize
+
before_action :authorize_read_pod_logs!
- before_action :environment
before_action :ensure_deployments, only: %i(k8s elasticsearch)
def index
- if environment.nil?
- render :empty_logs
- else
+ if environment || cluster
render :index
+ else
+ render :empty_logs
end
end
@@ -39,8 +40,9 @@ module Projects
end
end
- def index_params
- params.permit(:environment_name)
+ # cluster is selected either via environment or directly by id
+ def cluster_params
+ params.permit(:environment_name, :cluster_id)
end
def k8s_params
@@ -52,22 +54,36 @@ module Projects
end
def environment
- @environment ||= if index_params.key?(:environment_name)
- EnvironmentsFinder.new(project, current_user, name: index_params[:environment_name]).find.first
- else
- project.default_environment
- end
+ strong_memoize(:environment) do
+ if cluster_params.key?(:environment_name)
+ EnvironmentsFinder.new(project, current_user, name: cluster_params[:environment_name]).find.first
+ else
+ project.default_environment
+ end
+ end
end
def cluster
- environment.deployment_platform&.cluster
+ strong_memoize(:cluster) do
+ if gitlab_managed_apps_logs?
+ clusters = ClusterAncestorsFinder.new(project, current_user).execute
+ clusters.find { |cluster| cluster.id == cluster_params[:cluster_id].to_i }
+ else
+ environment&.deployment_platform&.cluster
+ end
+ end
end
def namespace
- environment.deployment_namespace
+ if gitlab_managed_apps_logs?
+ Gitlab::Kubernetes::Helm::NAMESPACE
+ else
+ environment.deployment_namespace
+ end
end
def ensure_deployments
+ return if gitlab_managed_apps_logs?
return if cluster && namespace.present?
render status: :bad_request, json: {
@@ -75,5 +91,9 @@ module Projects
message: _('Environment does not have deployments')
}
end
+
+ def gitlab_managed_apps_logs?
+ cluster_params.key?(:cluster_id)
+ end
end
end
diff --git a/app/controllers/projects/merge_requests/application_controller.rb b/app/controllers/projects/merge_requests/application_controller.rb
index b7e99cb7ed0..0bb4e0fb5ee 100644
--- a/app/controllers/projects/merge_requests/application_controller.rb
+++ b/app/controllers/projects/merge_requests/application_controller.rb
@@ -48,12 +48,9 @@ class Projects::MergeRequests::ApplicationController < Projects::ApplicationCont
end
def set_pipeline_variables
- @pipelines =
- if can?(current_user, :read_pipeline, @merge_request.source_project)
- @merge_request.all_pipelines
- else
- Ci::Pipeline.none
- end
+ @pipelines = Ci::PipelinesForMergeRequestFinder
+ .new(@merge_request, current_user)
+ .execute
end
def close_merge_request_if_no_source_project
diff --git a/app/controllers/projects/merge_requests/creations_controller.rb b/app/controllers/projects/merge_requests/creations_controller.rb
index 28aa1b300aa..3e077c1af37 100644
--- a/app/controllers/projects/merge_requests/creations_controller.rb
+++ b/app/controllers/projects/merge_requests/creations_controller.rb
@@ -32,13 +32,13 @@ class Projects::MergeRequests::CreationsController < Projects::MergeRequests::Ap
end
def pipelines
- @pipelines = @merge_request.all_pipelines
+ @pipelines = Ci::PipelinesForMergeRequestFinder.new(@merge_request, current_user).execute
Gitlab::PollingInterval.set_header(response, interval: 10_000)
render json: {
pipelines: PipelineSerializer
- .new(project: @project, current_user: @current_user)
+ .new(project: @project, current_user: current_user)
.represent(@pipelines)
}
end
diff --git a/app/controllers/projects/merge_requests/diffs_controller.rb b/app/controllers/projects/merge_requests/diffs_controller.rb
index 1bf143c9a91..98b0abc89e9 100644
--- a/app/controllers/projects/merge_requests/diffs_controller.rb
+++ b/app/controllers/projects/merge_requests/diffs_controller.rb
@@ -8,6 +8,7 @@ class Projects::MergeRequests::DiffsController < Projects::MergeRequests::Applic
before_action :commit
before_action :define_diff_vars
before_action :define_diff_comment_vars, except: [:diffs_batch, :diffs_metadata]
+ before_action :update_diff_discussion_positions!
around_action :allow_gitaly_ref_name_caching
@@ -171,4 +172,12 @@ class Projects::MergeRequests::DiffsController < Projects::MergeRequests::Applic
@notes.concat(draft_notes)
end
+
+ def update_diff_discussion_positions!
+ return unless Feature.enabled?(:merge_ref_head_comments, @merge_request.target_project, default_enabled: true)
+ return unless Feature.enabled?(:merge_red_head_comments_position_on_demand, @merge_request.target_project, default_enabled: true)
+ return if @merge_request.has_any_diff_note_positions?
+
+ Discussions::CaptureDiffNotePositionsService.new(@merge_request).execute
+ end
end
diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb
index 6c1ffc35276..e65e5531b88 100644
--- a/app/controllers/projects/merge_requests_controller.rb
+++ b/app/controllers/projects/merge_requests_controller.rb
@@ -35,15 +35,23 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
push_frontend_feature_flag(:mr_commit_neighbor_nav, @project, default_enabled: true)
push_frontend_feature_flag(:multiline_comments, @project)
push_frontend_feature_flag(:file_identifier_hash)
- push_frontend_feature_flag(:batch_suggestions, @project)
+ push_frontend_feature_flag(:batch_suggestions, @project, default_enabled: true)
end
before_action do
push_frontend_feature_flag(:vue_issuable_sidebar, @project.group)
+ push_frontend_feature_flag(:junit_pipeline_view, @project.group)
end
around_action :allow_gitaly_ref_name_caching, only: [:index, :show, :discussions]
+ feature_category :source_code_management,
+ unless: -> (action) { action.ends_with?("_reports") }
+ feature_category :code_testing,
+ only: [:test_reports, :coverage_reports, :terraform_reports]
+ feature_category :accessibility_testing,
+ only: [:accessibility_reports]
+
def index
@merge_requests = @issuables
@@ -76,7 +84,9 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
@issuable_sidebar = serializer.represent(@merge_request, serializer: 'sidebar')
@current_user_data = UserSerializer.new(project: @project).represent(current_user, {}, MergeRequestUserEntity).to_json
@show_whitespace_default = current_user.nil? || current_user.show_whitespace_in_diffs
+ @file_by_file_default = Feature.enabled?(:view_diffs_file_by_file) && current_user&.view_diffs_file_by_file
@coverage_path = coverage_reports_project_merge_request_path(@project, @merge_request, format: :json) if @merge_request.has_coverage_reports?
+ @endpoint_metadata_url = endpoint_metadata_url(@project, @merge_request)
set_pipeline_variables
@@ -108,8 +118,8 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
# or from cache if already merged
@commits =
set_commits_for_rendering(
- @merge_request.recent_commits.with_latest_pipeline(@merge_request.source_branch),
- commits_count: @merge_request.commits_count
+ @merge_request.recent_commits.with_latest_pipeline(@merge_request.source_branch).with_markdown_cache,
+ commits_count: @merge_request.commits_count
)
render json: { html: view_to_html_string('projects/merge_requests/_commits') }
@@ -178,7 +188,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
end
def update
- @merge_request = ::MergeRequests::UpdateService.new(project, current_user, merge_request_params).execute(@merge_request)
+ @merge_request = ::MergeRequests::UpdateService.new(project, current_user, merge_request_update_params).execute(@merge_request)
respond_to do |format|
format.html do
@@ -312,6 +322,10 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
private
+ def merge_request_update_params
+ merge_request_params.merge!(params.permit(:merge_request_diff_head_sha))
+ end
+
def head_pipeline
strong_memoize(:head_pipeline) do
pipeline = @merge_request.head_pipeline
@@ -422,6 +436,13 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
def authorize_read_actual_head_pipeline!
return render_404 unless can?(current_user, :read_build, merge_request.actual_head_pipeline)
end
+
+ def endpoint_metadata_url(project, merge_request)
+ params = request.query_parameters
+ params[:view] = cookies[:diff_view] if params[:view].blank? && cookies[:diff_view].present?
+
+ diffs_metadata_project_json_merge_request_path(project, merge_request, 'json', params)
+ end
end
Projects::MergeRequestsController.prepend_if_ee('EE::Projects::MergeRequestsController')
diff --git a/app/controllers/projects/metrics_dashboard_controller.rb b/app/controllers/projects/metrics_dashboard_controller.rb
new file mode 100644
index 00000000000..235ee1dfbf2
--- /dev/null
+++ b/app/controllers/projects/metrics_dashboard_controller.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+module Projects
+ class MetricsDashboardController < Projects::ApplicationController
+ # Metrics dashboard code is in the process of being decoupled from environments
+ # and is getting moved to this controller. Some code may be duplicated from
+ # app/controllers/projects/environments_controller.rb
+ # See https://gitlab.com/gitlab-org/gitlab/-/issues/226002 for more details.
+
+ before_action :authorize_metrics_dashboard!
+ before_action do
+ push_frontend_feature_flag(:prometheus_computed_alerts)
+ end
+
+ def show
+ if environment
+ render 'projects/environments/metrics'
+ else
+ render_404
+ end
+ end
+
+ private
+
+ def environment
+ @environment ||=
+ if params[:environment]
+ project.environments.find(params[:environment])
+ else
+ project.default_environment
+ end
+ end
+ end
+end
diff --git a/app/controllers/projects/pipelines/application_controller.rb b/app/controllers/projects/pipelines/application_controller.rb
new file mode 100644
index 00000000000..92887750813
--- /dev/null
+++ b/app/controllers/projects/pipelines/application_controller.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+
+# Abstract class encapsulating common logic for creating new controllers in a pipeline context
+
+module Projects
+ module Pipelines
+ class ApplicationController < Projects::ApplicationController
+ include Gitlab::Utils::StrongMemoize
+
+ before_action :pipeline
+ before_action :authorize_read_pipeline!
+
+ private
+
+ def pipeline
+ strong_memoize(:pipeline) do
+ project.all_pipelines.find(params[:pipeline_id]).tap do |pipeline|
+ render_404 unless can?(current_user, :read_pipeline, pipeline)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/app/controllers/projects/pipelines/stages_controller.rb b/app/controllers/projects/pipelines/stages_controller.rb
new file mode 100644
index 00000000000..ce08b49ce9f
--- /dev/null
+++ b/app/controllers/projects/pipelines/stages_controller.rb
@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+
+module Projects
+ module Pipelines
+ class StagesController < Projects::Pipelines::ApplicationController
+ before_action :authorize_update_pipeline!
+
+ def play_manual
+ ::Ci::PlayManualStageService
+ .new(@project, current_user, pipeline: pipeline)
+ .execute(stage)
+
+ respond_to do |format|
+ format.json do
+ render json: StageSerializer
+ .new(project: @project, current_user: @current_user)
+ .represent(stage)
+ end
+ end
+ end
+
+ private
+
+ def stage
+ @pipeline_stage ||= pipeline.find_stage_by_name!(params[:stage_name])
+ end
+ end
+ end
+end
diff --git a/app/controllers/projects/pipelines/tests_controller.rb b/app/controllers/projects/pipelines/tests_controller.rb
new file mode 100644
index 00000000000..f03274bf32e
--- /dev/null
+++ b/app/controllers/projects/pipelines/tests_controller.rb
@@ -0,0 +1,59 @@
+# frozen_string_literal: true
+
+module Projects
+ module Pipelines
+ class TestsController < Projects::Pipelines::ApplicationController
+ before_action :validate_feature_flag!
+ before_action :authorize_read_build!
+ before_action :builds, only: [:show]
+
+ def summary
+ respond_to do |format|
+ format.json do
+ render json: TestReportSummarySerializer
+ .new(project: project, current_user: @current_user)
+ .represent(pipeline.test_report_summary)
+ end
+ end
+ end
+
+ def show
+ respond_to do |format|
+ format.json do
+ render json: TestSuiteSerializer
+ .new(project: project, current_user: @current_user)
+ .represent(test_suite, details: true)
+ end
+ end
+ end
+
+ private
+
+ def validate_feature_flag!
+ render_404 unless Feature.enabled?(:build_report_summary, project)
+ end
+
+ # rubocop: disable CodeReuse/ActiveRecord
+ def builds
+ pipeline.latest_builds.where(id: build_params)
+ end
+
+ def build_params
+ return [] unless params[:build_ids]
+
+ params[:build_ids].split(",")
+ end
+
+ def test_suite
+ if builds.present?
+ builds.map do |build|
+ build.collect_test_reports!(Gitlab::Ci::Reports::TestReports.new)
+ end.sum
+ else
+ render_404
+ end
+ end
+ # rubocop: enable CodeReuse/ActiveRecord
+ end
+ end
+end
diff --git a/app/controllers/projects/pipelines_controller.rb b/app/controllers/projects/pipelines_controller.rb
index 0b6c0db211e..d8e11ddd423 100644
--- a/app/controllers/projects/pipelines_controller.rb
+++ b/app/controllers/projects/pipelines_controller.rb
@@ -2,6 +2,7 @@
class Projects::PipelinesController < Projects::ApplicationController
include ::Gitlab::Utils::StrongMemoize
+ include Analytics::UniqueVisitsHelper
before_action :whitelist_query_limiting, only: [:create, :retry]
before_action :pipeline, except: [:index, :new, :create, :charts]
@@ -12,14 +13,20 @@ class Projects::PipelinesController < Projects::ApplicationController
before_action :authorize_update_pipeline!, only: [:retry, :cancel]
before_action do
push_frontend_feature_flag(:junit_pipeline_view, project)
+ push_frontend_feature_flag(:build_report_summary, project)
push_frontend_feature_flag(:filter_pipelines_search, project, default_enabled: true)
- push_frontend_feature_flag(:dag_pipeline_tab, project, default_enabled: false)
+ push_frontend_feature_flag(:dag_pipeline_tab, project, default_enabled: true)
push_frontend_feature_flag(:pipelines_security_report_summary, project)
end
before_action :ensure_pipeline, only: [:show]
+ # Will be removed with https://gitlab.com/gitlab-org/gitlab/-/issues/225596
+ before_action :redirect_for_legacy_scope_filter, only: [:index], if: -> { request.format.html? }
+
around_action :allow_gitaly_ref_name_caching, only: [:index, :show]
+ track_unique_visits :charts, target_id: 'p_analytics_pipelines'
+
wrap_parameters Ci::Pipeline
POLLING_INTERVAL = 10_000
@@ -31,9 +38,6 @@ class Projects::PipelinesController < Projects::ApplicationController
.page(params[:page])
.per(30)
- @running_count = limited_pipelines_count(project, 'running')
- @pending_count = limited_pipelines_count(project, 'pending')
- @finished_count = limited_pipelines_count(project, 'finished')
@pipelines_count = limited_pipelines_count(project)
respond_to do |format|
@@ -44,10 +48,7 @@ class Projects::PipelinesController < Projects::ApplicationController
render json: {
pipelines: serialize_pipelines,
count: {
- all: @pipelines_count,
- running: @running_count,
- pending: @pending_count,
- finished: @finished_count
+ all: @pipelines_count
}
}
end
@@ -186,7 +187,7 @@ class Projects::PipelinesController < Projects::ApplicationController
format.json do
render json: TestReportSerializer
.new(current_user: @current_user)
- .represent(pipeline_test_report, project: project)
+ .represent(pipeline_test_report, project: project, details: true)
end
end
end
@@ -226,6 +227,12 @@ class Projects::PipelinesController < Projects::ApplicationController
render_404 unless pipeline
end
+ def redirect_for_legacy_scope_filter
+ return unless %w[running pending].include?(params[:scope])
+
+ redirect_to url_for(safe_params.except(:scope).merge(status: safe_params[:scope])), status: :moved_permanently
+ end
+
# rubocop: disable CodeReuse/ActiveRecord
def pipeline
@pipeline ||= if params[:id].blank? && params[:latest]
diff --git a/app/controllers/projects/refs_controller.rb b/app/controllers/projects/refs_controller.rb
index a2581e72257..db770d3e438 100644
--- a/app/controllers/projects/refs_controller.rb
+++ b/app/controllers/projects/refs_controller.rb
@@ -11,10 +11,6 @@ class Projects::RefsController < Projects::ApplicationController
before_action :assign_ref_vars
before_action :authorize_download_code!
- before_action only: [:logs_tree] do
- push_frontend_feature_flag(:vue_file_list_lfs_badge, default_enabled: true)
- end
-
def switch
respond_to do |format|
format.html do
@@ -57,22 +53,11 @@ class Projects::RefsController < Projects::ApplicationController
render json: logs
end
-
- # Deprecated due to https://gitlab.com/gitlab-org/gitlab/-/issues/36863
- # Will be removed soon https://gitlab.com/gitlab-org/gitlab/-/merge_requests/29895
- format.js do
- @logs, _ = tree_summary.summarize
- @more_log_url = more_url(tree_summary.next_offset) if tree_summary.more?
- end
end
end
private
- def more_url(offset)
- logs_file_project_ref_path(@project, @ref, @path, offset: offset)
- end
-
def validate_ref_id
return not_found! if params[:id].present? && params[:id] !~ Gitlab::PathRegex.git_reference_regex
end
diff --git a/app/controllers/projects/releases_controller.rb b/app/controllers/projects/releases_controller.rb
index d3285b64dab..d58755c2655 100644
--- a/app/controllers/projects/releases_controller.rb
+++ b/app/controllers/projects/releases_controller.rb
@@ -13,6 +13,7 @@ class Projects::ReleasesController < Projects::ApplicationController
push_frontend_feature_flag(:release_asset_link_type, project, default_enabled: true)
end
before_action :authorize_update_release!, only: %i[edit update]
+ before_action :authorize_create_release!, only: :new
def index
respond_to do |format|
@@ -25,11 +26,11 @@ class Projects::ReleasesController < Projects::ApplicationController
def show
return render_404 unless Feature.enabled?(:release_show_page, project, default_enabled: true)
+ end
- respond_to do |format|
- format.html do
- render :show
- end
+ def new
+ unless Feature.enabled?(:new_release_page, project)
+ redirect_to(new_project_tag_path(@project))
end
end
@@ -37,22 +38,12 @@ class Projects::ReleasesController < Projects::ApplicationController
redirect_to link.url
end
- protected
+ private
def releases
ReleasesFinder.new(@project, current_user).execute
end
- def edit
- respond_to do |format|
- format.html do
- render :edit
- end
- end
- end
-
- private
-
def authorize_update_release!
access_denied! unless can?(current_user, :update_release, release)
end
diff --git a/app/controllers/projects/service_desk_controller.rb b/app/controllers/projects/service_desk_controller.rb
new file mode 100644
index 00000000000..bcd190bbc2c
--- /dev/null
+++ b/app/controllers/projects/service_desk_controller.rb
@@ -0,0 +1,45 @@
+# frozen_string_literal: true
+
+class Projects::ServiceDeskController < Projects::ApplicationController
+ before_action :authorize_admin_project!
+
+ def show
+ json_response
+ end
+
+ def update
+ Projects::UpdateService.new(project, current_user, { service_desk_enabled: params[:service_desk_enabled] }).execute
+
+ result = ServiceDeskSettings::UpdateService.new(project, current_user, setting_params).execute
+
+ if result[:status] == :success
+ json_response
+ else
+ render json: { message: result[:message] }, status: :unprocessable_entity
+ end
+ end
+
+ private
+
+ def setting_params
+ params.permit(:issue_template_key, :outgoing_name, :project_key)
+ end
+
+ def json_response
+ respond_to do |format|
+ service_desk_settings = project.service_desk_setting
+
+ service_desk_attributes =
+ {
+ service_desk_address: project.service_desk_address,
+ service_desk_enabled: project.service_desk_enabled,
+ issue_template_key: service_desk_settings&.issue_template_key,
+ template_file_missing: service_desk_settings&.issue_template_missing?,
+ outgoing_name: service_desk_settings&.outgoing_name,
+ project_key: service_desk_settings&.project_key
+ }
+
+ format.json { render json: service_desk_attributes }
+ end
+ end
+end
diff --git a/app/controllers/projects/services_controller.rb b/app/controllers/projects/services_controller.rb
index 710ad546e64..6b7e253595c 100644
--- a/app/controllers/projects/services_controller.rb
+++ b/app/controllers/projects/services_controller.rb
@@ -12,7 +12,8 @@ class Projects::ServicesController < Projects::ApplicationController
before_action :set_deprecation_notice_for_prometheus_service, only: [:edit, :update]
before_action :redirect_deprecated_prometheus_service, only: [:update]
before_action only: :edit do
- push_frontend_feature_flag(:integration_form_refactor)
+ push_frontend_feature_flag(:integration_form_refactor, default_enabled: true)
+ push_frontend_feature_flag(:jira_issues_integration, @project, { default_enabled: true })
end
respond_to :html
@@ -20,17 +21,19 @@ class Projects::ServicesController < Projects::ApplicationController
layout "project_settings"
def edit
+ @admin_integration = Service.instance_for(service.type)
end
def update
@service.attributes = service_params[:service]
+ @service.inherit_from_id = nil if service_params[:service][:inherit_from_id].blank?
saved = @service.save(context: :manual_change)
respond_to do |format|
format.html do
if saved
- target_url = safe_redirect_path(params[:redirect_to]).presence || project_settings_integrations_path(@project)
+ target_url = safe_redirect_path(params[:redirect_to]).presence || edit_project_service_path(@project, @service)
redirect_to target_url, notice: success_message
else
render 'edit'
@@ -60,7 +63,7 @@ class Projects::ServicesController < Projects::ApplicationController
return { error: true, message: _('Validations failed.'), service_response: @service.errors.full_messages.join(','), test_failed: false }
end
- result = Integrations::Test::ProjectService.new(@service, current_user, params[:event]).execute
+ result = ::Integrations::Test::ProjectService.new(@service, current_user, params[:event]).execute
unless result[:success]
return { error: true, message: _('Test failed.'), service_response: result[:message].to_s, test_failed: true }
diff --git a/app/controllers/projects/settings/operations_controller.rb b/app/controllers/projects/settings/operations_controller.rb
index c2292511e0f..d7a6f1b0139 100644
--- a/app/controllers/projects/settings/operations_controller.rb
+++ b/app/controllers/projects/settings/operations_controller.rb
@@ -6,13 +6,13 @@ module Projects
before_action :authorize_admin_operations!
before_action :authorize_read_prometheus_alerts!, only: [:reset_alerting_token]
- respond_to :json, only: [:reset_alerting_token]
+ before_action do
+ push_frontend_feature_flag(:pagerduty_webhook, project)
+ end
- helper_method :error_tracking_setting
+ respond_to :json, only: [:reset_alerting_token, :reset_pagerduty_token]
- def show
- render locals: { prometheus_service: prometheus_service }
- end
+ helper_method :error_tracking_setting
def update
result = ::Projects::Operations::UpdateService.new(project, current_user, update_params).execute
@@ -42,14 +42,29 @@ module Projects
end
end
+ def reset_pagerduty_token
+ result = ::Projects::Operations::UpdateService
+ .new(project, current_user, pagerduty_token_params)
+ .execute
+
+ if result[:status] == :success
+ pagerduty_token = project.incident_management_setting&.pagerduty_token
+ webhook_url = project_incidents_pagerduty_url(project, token: pagerduty_token)
+
+ render json: { pagerduty_webhook_url: webhook_url, pagerduty_token: pagerduty_token }
+ else
+ render json: {}, status: :unprocessable_entity
+ end
+ end
+
private
def alerting_params
{ alerting_setting_attributes: { regenerate_token: true } }
end
- def prometheus_service
- project.find_or_initialize_service(::PrometheusService.to_param)
+ def pagerduty_token_params
+ { incident_management_setting_attributes: { regenerate_token: true } }
end
def render_update_response(result)
diff --git a/app/controllers/projects/snippets/blobs_controller.rb b/app/controllers/projects/snippets/blobs_controller.rb
new file mode 100644
index 00000000000..148fc7c96f8
--- /dev/null
+++ b/app/controllers/projects/snippets/blobs_controller.rb
@@ -0,0 +1,5 @@
+# frozen_string_literal: true
+
+class Projects::Snippets::BlobsController < Projects::Snippets::ApplicationController
+ include Snippets::BlobsActions
+end
diff --git a/app/controllers/projects/snippets_controller.rb b/app/controllers/projects/snippets_controller.rb
index 5ee6abef804..49840e847f2 100644
--- a/app/controllers/projects/snippets_controller.rb
+++ b/app/controllers/projects/snippets_controller.rb
@@ -15,11 +15,11 @@ class Projects::SnippetsController < Projects::Snippets::ApplicationController
before_action :authorize_admin_snippet!, only: [:destroy]
def index
- @snippet_counts = Snippets::CountService
+ @snippet_counts = ::Snippets::CountService
.new(current_user, project: @project)
.execute
- @snippets = SnippetsFinder.new(current_user, project: @project, scope: params[:scope])
+ @snippets = SnippetsFinder.new(current_user, project: @project, scope: params[:scope], sort: sort_param)
.execute
.page(params[:page])
.inc_author
@@ -35,7 +35,7 @@ class Projects::SnippetsController < Projects::Snippets::ApplicationController
def create
create_params = snippet_params.merge(spammable_params)
- service_response = Snippets::CreateService.new(project, current_user, create_params).execute
+ service_response = ::Snippets::CreateService.new(project, current_user, create_params).execute
@snippet = service_response.payload[:snippet]
handle_repository_error(:new)
diff --git a/app/controllers/projects/stages_controller.rb b/app/controllers/projects/stages_controller.rb
deleted file mode 100644
index c8db5b1277f..00000000000
--- a/app/controllers/projects/stages_controller.rb
+++ /dev/null
@@ -1,25 +0,0 @@
-# frozen_string_literal: true
-
-class Projects::StagesController < Projects::PipelinesController
- before_action :authorize_update_pipeline!
-
- def play_manual
- ::Ci::PlayManualStageService
- .new(@project, current_user, pipeline: pipeline)
- .execute(stage)
-
- respond_to do |format|
- format.json do
- render json: StageSerializer
- .new(project: @project, current_user: @current_user)
- .represent(stage)
- end
- end
- end
-
- private
-
- def stage
- @pipeline_stage ||= pipeline.find_stage_by_name!(params[:stage_name])
- end
-end
diff --git a/app/controllers/projects/static_site_editor_controller.rb b/app/controllers/projects/static_site_editor_controller.rb
index 74f28c3da67..9ec50ff8196 100644
--- a/app/controllers/projects/static_site_editor_controller.rb
+++ b/app/controllers/projects/static_site_editor_controller.rb
@@ -9,6 +9,9 @@ class Projects::StaticSiteEditorController < Projects::ApplicationController
prepend_before_action :authenticate_user!, only: [:show]
before_action :assign_ref_and_path, only: [:show]
before_action :authorize_edit_tree!, only: [:show]
+ before_action do
+ push_frontend_feature_flag(:sse_image_uploads)
+ end
def show
@config = Gitlab::StaticSiteEditor::Config.new(@repository, @ref, @path, params[:return_url])
diff --git a/app/controllers/projects/tree_controller.rb b/app/controllers/projects/tree_controller.rb
index 9cb345724cc..638e1a05c18 100644
--- a/app/controllers/projects/tree_controller.rb
+++ b/app/controllers/projects/tree_controller.rb
@@ -15,26 +15,14 @@ class Projects::TreeController < Projects::ApplicationController
before_action :authorize_download_code!
before_action :authorize_edit_tree!, only: [:create_dir]
- before_action only: [:show] do
- push_frontend_feature_flag(:vue_file_list_lfs_badge, default_enabled: true)
- end
-
def show
- return render_404 unless @repository.commit(@ref)
+ return render_404 unless @commit
if tree.entries.empty?
if @repository.blob_at(@commit.id, @path)
- return redirect_to project_blob_path(@project, File.join(@ref, @path))
+ redirect_to project_blob_path(@project, File.join(@ref, @path))
elsif @path.present?
- return redirect_to_tree_root_for_missing_path(@project, @ref, @path)
- end
- end
-
- respond_to do |format|
- format.html do
- lfs_blob_ids if Feature.disabled?(:vue_file_list, @project, default_enabled: true)
-
- @last_commit = @repository.last_commit_for_path(@commit.id, @tree.path) || @commit
+ redirect_to_tree_root_for_missing_path(@project, @ref, @path)
end
end
end
diff --git a/app/controllers/projects/variables_controller.rb b/app/controllers/projects/variables_controller.rb
index 1dffc57fcf0..2cc030d18fc 100644
--- a/app/controllers/projects/variables_controller.rb
+++ b/app/controllers/projects/variables_controller.rb
@@ -6,7 +6,7 @@ class Projects::VariablesController < Projects::ApplicationController
def show
respond_to do |format|
format.json do
- render status: :ok, json: { variables: VariableSerializer.new.represent(@project.variables) }
+ render status: :ok, json: { variables: ::Ci::VariableSerializer.new.represent(@project.variables) }
end
end
end
@@ -26,7 +26,7 @@ class Projects::VariablesController < Projects::ApplicationController
private
def render_variables
- render status: :ok, json: { variables: VariableSerializer.new.represent(@project.variables) }
+ render status: :ok, json: { variables: ::Ci::VariableSerializer.new.represent(@project.variables) }
end
def render_error
diff --git a/app/controllers/projects/wikis_controller.rb b/app/controllers/projects/wikis_controller.rb
index 85e643aa212..d0aa733cadb 100644
--- a/app/controllers/projects/wikis_controller.rb
+++ b/app/controllers/projects/wikis_controller.rb
@@ -2,7 +2,6 @@
class Projects::WikisController < Projects::ApplicationController
include WikiActions
- include PreviewMarkdown
alias_method :container, :project
diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb
index f0ddd62e996..a5666cb70ac 100644
--- a/app/controllers/projects_controller.rb
+++ b/app/controllers/projects_controller.rb
@@ -8,6 +8,7 @@ class ProjectsController < Projects::ApplicationController
include SendFileUpload
include RecordUserLastActivity
include ImportUrlParams
+ include FiltersEvents
prepend_before_action(only: [:show]) { authenticate_sessionless_user!(:rss) }
@@ -21,7 +22,6 @@ class ProjectsController < Projects::ApplicationController
before_action :assign_ref_vars, if: -> { action_name == 'show' && repo_exists? }
before_action :tree,
if: -> { action_name == 'show' && repo_exists? && project_view_files? }
- before_action :lfs_blob_ids, if: :show_blob_ids?, only: :show
before_action :project_export_enabled, only: [:export, :download_export, :remove_export, :generate_new_export]
before_action :present_project, only: [:edit]
before_action :authorize_download_code!, only: [:refs]
@@ -38,6 +38,7 @@ class ProjectsController < Projects::ApplicationController
before_action only: [:new, :create] do
frontend_experimentation_tracking_data(:new_create_project_ui, 'click_tab')
push_frontend_feature_flag(:new_create_project_ui) if experiment_enabled?(:new_create_project_ui)
+ push_frontend_feature_flag(:service_desk_custom_address, @project)
end
layout :determine_layout
@@ -301,10 +302,6 @@ class ProjectsController < Projects::ApplicationController
private
- def show_blob_ids?
- repo_exists? && project_view_files? && Feature.disabled?(:vue_file_list, @project, default_enabled: true)
- end
-
# Render project landing depending of which features are available
# So if page is not available in the list it renders the next page
#
@@ -395,6 +392,7 @@ class ProjectsController < Projects::ApplicationController
:initialize_with_readme,
:autoclose_referenced_issues,
:suggestion_commit_message,
+ :service_desk_enabled,
project_feature_attributes: %i[
builds_access_level
@@ -409,6 +407,7 @@ class ProjectsController < Projects::ApplicationController
],
project_setting_attributes: %i[
show_default_award_emojis
+ squash_option
]
]
end
diff --git a/app/controllers/registrations/experience_levels_controller.rb b/app/controllers/registrations/experience_levels_controller.rb
index 515d6b3f9aa..97239b1bbac 100644
--- a/app/controllers/registrations/experience_levels_controller.rb
+++ b/app/controllers/registrations/experience_levels_controller.rb
@@ -33,12 +33,13 @@ module Registrations
def hide_advanced_issues
return unless current_user.user_preference.novice?
+ return unless learn_gitlab.available?
- settings = cookies[:onboarding_issues_settings]
- return unless settings
+ Boards::UpdateService.new(learn_gitlab.project, current_user, label_ids: [learn_gitlab.label.id]).execute(learn_gitlab.board)
+ end
- modified_settings = Gitlab::Json.parse(settings).merge(hideAdvanced: true)
- cookies[:onboarding_issues_settings] = modified_settings.to_json
+ def learn_gitlab
+ @learn_gitlab ||= LearnGitlab.new(current_user)
end
end
end
diff --git a/app/controllers/registrations_controller.rb b/app/controllers/registrations_controller.rb
index 6ab2924a8b5..b1c1fe3ba74 100644
--- a/app/controllers/registrations_controller.rb
+++ b/app/controllers/registrations_controller.rb
@@ -64,8 +64,8 @@ class RegistrationsController < Devise::RegistrationsController
if result[:status] == :success
track_experiment_event(:signup_flow, 'end') # We want this event to be tracked when the user is _in_ the experimental group
- track_experiment_event(:onboarding_issues, 'signed_up') if ::Gitlab.com? && !helpers.in_subscription_flow? && !helpers.in_invitation_flow?
- return redirect_to new_users_sign_up_group_path if experiment_enabled?(:onboarding_issues) && !helpers.in_subscription_flow? && !helpers.in_invitation_flow?
+ track_experiment_event(:onboarding_issues, 'signed_up') if ::Gitlab.com? && show_onboarding_issues_experiment?
+ return redirect_to new_users_sign_up_group_path if experiment_enabled?(:onboarding_issues) && show_onboarding_issues_experiment?
set_flash_message! :notice, :signed_up
redirect_to path_for_signed_in_user(current_user)
@@ -210,6 +210,10 @@ class RegistrationsController < Devise::RegistrationsController
'devise'
end
end
+
+ def show_onboarding_issues_experiment?
+ !helpers.in_subscription_flow? && !helpers.in_invitation_flow? && !helpers.in_oauth_flow?
+ end
end
RegistrationsController.prepend_if_ee('EE::RegistrationsController')
diff --git a/app/controllers/root_controller.rb b/app/controllers/root_controller.rb
index 24452f9a188..14469877e14 100644
--- a/app/controllers/root_controller.rb
+++ b/app/controllers/root_controller.rb
@@ -13,10 +13,15 @@ class RootController < Dashboard::ProjectsController
before_action :redirect_unlogged_user, if: -> { current_user.nil? }
before_action :redirect_logged_user, if: -> { current_user.present? }
+ # We only need to load the projects when the user is logged in but did not
+ # configure a dashboard. In which case we render projects. We can do that straight
+ # from the #index action.
+ skip_before_action :projects
def index
# n+1: https://gitlab.com/gitlab-org/gitlab-foss/issues/40260
Gitlab::GitalyClient.allow_n_plus_1_calls do
+ projects
super
end
end
diff --git a/app/controllers/search_controller.rb b/app/controllers/search_controller.rb
index 217f08dd648..ff6d9350a5c 100644
--- a/app/controllers/search_controller.rb
+++ b/app/controllers/search_controller.rb
@@ -51,6 +51,21 @@ class SearchController < ApplicationController
render json: { count: count }
end
+ # rubocop: disable CodeReuse/ActiveRecord
+ def autocomplete
+ term = params[:term]
+
+ if params[:project_id].present?
+ @project = Project.find_by(id: params[:project_id])
+ @project = nil unless can?(current_user, :read_project, @project)
+ end
+
+ @ref = params[:project_ref] if params[:project_ref].present?
+
+ render json: search_autocomplete_opts(term).to_json
+ end
+ # rubocop: enable CodeReuse/ActiveRecord
+
private
def preload_method
diff --git a/app/controllers/snippets/blobs_controller.rb b/app/controllers/snippets/blobs_controller.rb
new file mode 100644
index 00000000000..d7c4bbcf8f2
--- /dev/null
+++ b/app/controllers/snippets/blobs_controller.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+class Snippets::BlobsController < Snippets::ApplicationController
+ include Snippets::BlobsActions
+
+ skip_before_action :authenticate_user!, only: [:raw]
+end
diff --git a/app/controllers/snippets_controller.rb b/app/controllers/snippets_controller.rb
index 87d87390e57..e68b821459d 100644
--- a/app/controllers/snippets_controller.rb
+++ b/app/controllers/snippets_controller.rb
@@ -21,7 +21,7 @@ class SnippetsController < Snippets::ApplicationController
if params[:username].present?
@user = UserFinder.new(params[:username]).find_by_username!
- @snippets = SnippetsFinder.new(current_user, author: @user, scope: params[:scope])
+ @snippets = SnippetsFinder.new(current_user, author: @user, scope: params[:scope], sort: sort_param)
.execute
.page(params[:page])
.inc_author
diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb
index 5ee97885071..95ea31fa977 100644
--- a/app/controllers/users_controller.rb
+++ b/app/controllers/users_controller.rb
@@ -3,6 +3,7 @@
class UsersController < ApplicationController
include RoutableActions
include RendersMemberAccess
+ include RendersProjectsList
include ControllerWithCrossProjectAccessCheck
include Gitlab::NoteableMetadata
@@ -36,6 +37,12 @@ class UsersController < ApplicationController
end
end
+ # Get all keys of a user(params[:username]) in a text format
+ # Helpful for sysadmins to put in respective servers
+ def ssh_keys
+ render plain: user.all_ssh_keys.join("\n")
+ end
+
def activity
respond_to do |format|
format.html { render 'show' }
diff --git a/app/finders/branches_finder.rb b/app/finders/branches_finder.rb
index 8001c70a9b2..2eee90a512a 100644
--- a/app/finders/branches_finder.rb
+++ b/app/finders/branches_finder.rb
@@ -5,11 +5,15 @@ class BranchesFinder < GitRefsFinder
super(repository, params)
end
- def execute
- branches = repository.branches_sorted_by(sort)
- branches = by_search(branches)
- branches = by_names(branches)
- branches
+ def execute(gitaly_pagination: false)
+ if gitaly_pagination && names.blank? && search.blank?
+ repository.branches_sorted_by(sort, pagination_params)
+ else
+ branches = repository.branches_sorted_by(sort)
+ branches = by_search(branches)
+ branches = by_names(branches)
+ branches
+ end
end
private
@@ -18,6 +22,18 @@ class BranchesFinder < GitRefsFinder
@params[:names].presence
end
+ def per_page
+ @params[:per_page].presence
+ end
+
+ def page_token
+ "#{Gitlab::Git::BRANCH_REF_PREFIX}#{@params[:page_token]}" if @params[:page_token]
+ end
+
+ def pagination_params
+ { limit: per_page, page_token: page_token }
+ end
+
def by_names(branches)
return branches unless names
diff --git a/app/finders/ci/pipelines_finder.rb b/app/finders/ci/pipelines_finder.rb
index 9e71e92b456..7347a83d294 100644
--- a/app/finders/ci/pipelines_finder.rb
+++ b/app/finders/ci/pipelines_finder.rb
@@ -71,7 +71,7 @@ module Ci
# rubocop: disable CodeReuse/ActiveRecord
def by_status(items)
- return items unless HasStatus::AVAILABLE_STATUSES.include?(params[:status])
+ return items unless Ci::HasStatus::AVAILABLE_STATUSES.include?(params[:status])
items.where(status: params[:status])
end
diff --git a/app/finders/ci/pipelines_for_merge_request_finder.rb b/app/finders/ci/pipelines_for_merge_request_finder.rb
index c01a68d6749..93d139652b9 100644
--- a/app/finders/ci/pipelines_for_merge_request_finder.rb
+++ b/app/finders/ci/pipelines_for_merge_request_finder.rb
@@ -7,14 +7,29 @@ module Ci
EVENT = 'merge_request_event'
- def initialize(merge_request)
+ def initialize(merge_request, current_user)
@merge_request = merge_request
+ @current_user = current_user
end
- attr_reader :merge_request
+ attr_reader :merge_request, :current_user
- delegate :commit_shas, :source_project, :source_branch, to: :merge_request
+ delegate :commit_shas, :target_project, :source_project, :source_branch, to: :merge_request
+ # Fetch all pipelines that the user can read.
+ def execute
+ if can_read_pipeline_in_target_project? && can_read_pipeline_in_source_project?
+ all
+ elsif can_read_pipeline_in_source_project?
+ all.for_project(merge_request.source_project)
+ elsif can_read_pipeline_in_target_project?
+ all.for_project(merge_request.target_project)
+ else
+ Ci::Pipeline.none
+ end
+ end
+
+ # Fetch all pipelines without permission check.
def all
strong_memoize(:all_pipelines) do
next Ci::Pipeline.none unless source_project
@@ -35,13 +50,13 @@ module Ci
def pipelines_using_cte
cte = Gitlab::SQL::CTE.new(:shas, merge_request.all_commits.select(:sha))
- source_pipelines_join = cte.table[:sha].eq(Ci::Pipeline.arel_table[:source_sha])
- source_pipelines = filter_by(triggered_by_merge_request, cte, source_pipelines_join)
- detached_pipelines = filter_by_sha(triggered_by_merge_request, cte)
+ source_sha_join = cte.table[:sha].eq(Ci::Pipeline.arel_table[:source_sha])
+ merged_result_pipelines = filter_by(triggered_by_merge_request, cte, source_sha_join)
+ detached_merge_request_pipelines = filter_by_sha(triggered_by_merge_request, cte)
pipelines_for_branch = filter_by_sha(triggered_for_branch, cte)
Ci::Pipeline.with(cte.to_arel) # rubocop: disable CodeReuse/ActiveRecord
- .from_union([source_pipelines, detached_pipelines, pipelines_for_branch])
+ .from_union([merged_result_pipelines, detached_merge_request_pipelines, pipelines_for_branch])
end
def filter_by_sha(pipelines, cte)
@@ -65,8 +80,7 @@ module Ci
# NOTE: this method returns only parent merge request pipelines.
# Child merge request pipelines have a different source.
def triggered_by_merge_request
- source_project.ci_pipelines
- .where(source: :merge_request_event, merge_request: merge_request) # rubocop: disable CodeReuse/ActiveRecord
+ Ci::Pipeline.triggered_by_merge_request(merge_request)
end
def triggered_for_branch
@@ -86,5 +100,17 @@ module Ci
pipelines.order(Arel.sql(query)) # rubocop: disable CodeReuse/ActiveRecord
end
+
+ def can_read_pipeline_in_target_project?
+ strong_memoize(:can_read_pipeline_in_target_project) do
+ Ability.allowed?(current_user, :read_pipeline, target_project)
+ end
+ end
+
+ def can_read_pipeline_in_source_project?
+ strong_memoize(:can_read_pipeline_in_source_project) do
+ Ability.allowed?(current_user, :read_pipeline, source_project)
+ end
+ end
end
end
diff --git a/app/finders/ci/runner_jobs_finder.rb b/app/finders/ci/runner_jobs_finder.rb
index ffcdb407e7e..9dc3c2a2427 100644
--- a/app/finders/ci/runner_jobs_finder.rb
+++ b/app/finders/ci/runner_jobs_finder.rb
@@ -21,7 +21,7 @@ module Ci
# rubocop: disable CodeReuse/ActiveRecord
def by_status(items)
- return items unless HasStatus::AVAILABLE_STATUSES.include?(params[:status])
+ return items unless Ci::HasStatus::AVAILABLE_STATUSES.include?(params[:status])
items.where(status: params[:status])
end
diff --git a/app/finders/ci/variables_finder.rb b/app/finders/ci/variables_finder.rb
new file mode 100644
index 00000000000..d933643ffb2
--- /dev/null
+++ b/app/finders/ci/variables_finder.rb
@@ -0,0 +1,31 @@
+# frozen_string_literal: true
+
+module Ci
+ class VariablesFinder
+ attr_reader :project, :params
+
+ def initialize(project, params)
+ @project, @params = project, params
+
+ raise ArgumentError, 'Please provide params[:key]' if params[:key].blank?
+ end
+
+ def execute
+ variables = project.variables
+ variables = by_key(variables)
+ variables = by_environment_scope(variables)
+ variables
+ end
+
+ private
+
+ def by_key(variables)
+ variables.by_key(params[:key])
+ end
+
+ def by_environment_scope(variables)
+ environment_scope = params.dig(:filter, :environment_scope)
+ environment_scope.present? ? variables.by_environment_scope(environment_scope) : variables
+ end
+ end
+end
diff --git a/app/finders/events_finder.rb b/app/finders/events_finder.rb
index 004fbc4cd22..4c619f3d7ea 100644
--- a/app/finders/events_finder.rb
+++ b/app/finders/events_finder.rb
@@ -54,17 +54,10 @@ class EventsFinder
if current_user && scope == 'all'
EventCollection.new(current_user.authorized_projects).all_project_events
else
- # EventCollection is responsible for applying the feature flag
- apply_feature_flags(source.events)
+ source.events
end
end
- def apply_feature_flags(events)
- return events if ::Feature.enabled?(:wiki_events)
-
- events.not_wiki_page
- end
-
# rubocop: disable CodeReuse/ActiveRecord
def by_current_user_access(events)
events.merge(Project.public_or_visible_to_user(current_user))
diff --git a/app/finders/group_projects_finder.rb b/app/finders/group_projects_finder.rb
index dd8b2f29425..5f24b15156c 100644
--- a/app/finders/group_projects_finder.rb
+++ b/app/finders/group_projects_finder.rb
@@ -19,6 +19,9 @@
# personal: boolean
# search: string
# non_archived: boolean
+# with_issues_enabled: boolean
+# with_merge_requests_enabled: boolean
+# min_access_level: int
#
class GroupProjectsFinder < ProjectsFinder
DEFAULT_PROJECTS_LIMIT = 100
@@ -42,6 +45,12 @@ class GroupProjectsFinder < ProjectsFinder
private
+ def filter_projects(collection)
+ projects = super
+ projects = by_feature_availability(projects)
+ projects
+ end
+
def limit(collection)
limit = options[:limit]
@@ -49,35 +58,37 @@ class GroupProjectsFinder < ProjectsFinder
end
def init_collection
- projects = if current_user
- collection_with_user
- else
- collection_without_user
- end
+ projects =
+ if only_shared?
+ [shared_projects]
+ elsif only_owned?
+ [owned_projects]
+ else
+ [owned_projects, shared_projects]
+ end
+
+ projects.map! do |project_relation|
+ filter_by_visibility(project_relation)
+ end
union(projects)
end
- def collection_with_user
- if only_shared?
- [shared_projects.public_or_visible_to_user(current_user)]
- elsif only_owned?
- [owned_projects.public_or_visible_to_user(current_user)]
- else
- [
- owned_projects.public_or_visible_to_user(current_user),
- shared_projects.public_or_visible_to_user(current_user)
- ]
- end
+ def by_feature_availability(projects)
+ projects = projects.with_issues_available_for_user(current_user) if params[:with_issues_enabled].present?
+ projects = projects.with_merge_requests_available_for_user(current_user) if params[:with_merge_requests_enabled].present?
+ projects
end
- def collection_without_user
- if only_shared?
- [shared_projects.public_only]
- elsif only_owned?
- [owned_projects.public_only]
+ def filter_by_visibility(relation)
+ if current_user
+ if min_access_level?
+ relation.visible_to_user_and_access_level(current_user, params[:min_access_level])
+ else
+ relation.public_or_visible_to_user(current_user)
+ end
else
- [shared_projects.public_only, owned_projects.public_only]
+ relation.public_only
end
end
diff --git a/app/finders/issuable_finder/params.rb b/app/finders/issuable_finder/params.rb
index 5b48d0817e3..8a194f34f74 100644
--- a/app/finders/issuable_finder/params.rb
+++ b/app/finders/issuable_finder/params.rb
@@ -110,7 +110,9 @@ class IssuableFinder
def group
strong_memoize(:group) do
- if params[:group_id].present?
+ if params[:group_id].is_a?(Group)
+ params[:group_id]
+ elsif params[:group_id].present?
Group.find(params[:group_id])
else
nil
diff --git a/app/finders/issues_finder.rb b/app/finders/issues_finder.rb
index 72695a9d501..2b2e6b377b4 100644
--- a/app/finders/issues_finder.rb
+++ b/app/finders/issues_finder.rb
@@ -24,6 +24,7 @@
# created_before: datetime
# updated_after: datetime
# updated_before: datetime
+# confidential: boolean
#
class IssuesFinder < IssuableFinder
CONFIDENTIAL_ACCESS_LEVEL = Gitlab::Access::REPORTER
diff --git a/app/finders/issues_finder/params.rb b/app/finders/issues_finder/params.rb
index cd92b79265d..668d969f7c0 100644
--- a/app/finders/issues_finder/params.rb
+++ b/app/finders/issues_finder/params.rb
@@ -27,19 +27,14 @@ class IssuesFinder
end
def user_can_see_all_confidential_issues?
- return @user_can_see_all_confidential_issues if defined?(@user_can_see_all_confidential_issues)
-
- return @user_can_see_all_confidential_issues = false if current_user.blank?
- return @user_can_see_all_confidential_issues = true if current_user.can_read_all_resources?
-
- @user_can_see_all_confidential_issues =
- if project? && project
- project.team.max_member_access(current_user.id) >= CONFIDENTIAL_ACCESS_LEVEL
- elsif group
- group.max_member_access_for_user(current_user) >= CONFIDENTIAL_ACCESS_LEVEL
+ strong_memoize(:user_can_see_all_confidential_issues) do
+ parent = project? ? project : group
+ if parent
+ Ability.allowed?(current_user, :read_confidential_issues, parent)
else
- false
+ Ability.allowed?(current_user, :read_all_resources)
end
+ end
end
def user_cannot_see_confidential_issues?
diff --git a/app/finders/notes_finder.rb b/app/finders/notes_finder.rb
index 8e57014f66e..1a3f011d9eb 100644
--- a/app/finders/notes_finder.rb
+++ b/app/finders/notes_finder.rb
@@ -158,13 +158,16 @@ class NotesFinder
end
# Notes changed since last fetch
- # Uses overlapping intervals to avoid worrying about race conditions
def since_fetch_at(notes)
return notes unless @params[:last_fetched_at]
# Default to 0 to remain compatible with old clients
- last_fetched_at = Time.at(@params.fetch(:last_fetched_at, 0).to_i)
- notes.updated_after(last_fetched_at - FETCH_OVERLAP)
+ last_fetched_at = @params.fetch(:last_fetched_at, Time.at(0))
+
+ # Use overlapping intervals to avoid worrying about race conditions
+ last_fetched_at -= FETCH_OVERLAP
+
+ notes.updated_after(last_fetched_at)
end
def notes_filter?
diff --git a/app/finders/packages/composer/packages_finder.rb b/app/finders/packages/composer/packages_finder.rb
new file mode 100644
index 00000000000..e63b2ee03fa
--- /dev/null
+++ b/app/finders/packages/composer/packages_finder.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+module Packages
+ module Composer
+ class PackagesFinder < Packages::GroupPackagesFinder
+ def initialize(current_user, group, params = {})
+ @current_user = current_user
+ @group = group
+ @params = params
+ end
+
+ def execute
+ packages_for_group_projects.composer.preload_composer
+ end
+ end
+ end
+end
diff --git a/app/finders/packages/conan/package_file_finder.rb b/app/finders/packages/conan/package_file_finder.rb
new file mode 100644
index 00000000000..edf35388a36
--- /dev/null
+++ b/app/finders/packages/conan/package_file_finder.rb
@@ -0,0 +1,28 @@
+# frozen_string_literal: true
+
+module Packages
+ module Conan
+ class PackageFileFinder < ::Packages::PackageFileFinder
+ private
+
+ def package_files
+ files = super
+ files = by_conan_file_type(files)
+ files = by_conan_package_reference(files)
+ files
+ end
+
+ def by_conan_file_type(files)
+ return files unless params[:conan_file_type]
+
+ files.with_conan_file_type(params[:conan_file_type])
+ end
+
+ def by_conan_package_reference(files)
+ return files unless params[:conan_package_reference]
+
+ files.with_conan_package_reference(params[:conan_package_reference])
+ end
+ end
+ end
+end
diff --git a/app/finders/packages/conan/package_finder.rb b/app/finders/packages/conan/package_finder.rb
new file mode 100644
index 00000000000..26e9182f4e1
--- /dev/null
+++ b/app/finders/packages/conan/package_finder.rb
@@ -0,0 +1,32 @@
+# frozen_string_literal: true
+
+module Packages
+ module Conan
+ class PackageFinder
+ attr_reader :current_user, :query
+
+ def initialize(current_user, params)
+ @current_user = current_user
+ @query = params[:query]
+ end
+
+ def execute
+ packages_for_current_user.with_name_like(query).order_name_asc if query
+ end
+
+ private
+
+ def packages
+ Packages::Package.conan
+ end
+
+ def packages_for_current_user
+ packages.for_projects(projects_visible_to_current_user)
+ end
+
+ def projects_visible_to_current_user
+ ::Project.public_or_visible_to_user(current_user)
+ end
+ end
+ end
+end
diff --git a/app/finders/packages/go/module_finder.rb b/app/finders/packages/go/module_finder.rb
new file mode 100644
index 00000000000..ed8bd5599d9
--- /dev/null
+++ b/app/finders/packages/go/module_finder.rb
@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+
+module Packages
+ module Go
+ class ModuleFinder
+ include Gitlab::Golang
+
+ attr_reader :project, :module_name
+
+ def initialize(project, module_name)
+ module_name = Pathname.new(module_name).cleanpath.to_s
+
+ @project = project
+ @module_name = module_name
+ end
+
+ def execute
+ return if @module_name.blank? || !@module_name.start_with?(local_module_prefix)
+
+ module_path = @module_name[local_module_prefix.length..].split('/')
+ project_path = project.full_path.split('/')
+ module_project_path = module_path.shift(project_path.length)
+ return unless module_project_path == project_path
+
+ Packages::Go::Module.new(@project, @module_name, module_path.join('/'))
+ end
+ end
+ end
+end
diff --git a/app/finders/packages/go/version_finder.rb b/app/finders/packages/go/version_finder.rb
new file mode 100644
index 00000000000..8e2fab8ba35
--- /dev/null
+++ b/app/finders/packages/go/version_finder.rb
@@ -0,0 +1,44 @@
+# frozen_string_literal: true
+
+module Packages
+ module Go
+ class VersionFinder
+ include Gitlab::Golang
+
+ attr_reader :mod
+
+ def initialize(mod)
+ @mod = mod
+ end
+
+ def execute
+ @mod.project.repository.tags
+ .filter { |tag| semver_tag? tag }
+ .map { |tag| @mod.version_by(ref: tag) }
+ .filter { |ver| ver.valid? }
+ end
+
+ def find(target)
+ case target
+ when String
+ if pseudo_version? target
+ semver = parse_semver(target)
+ commit = pseudo_version_commit(@mod.project, semver)
+ Packages::Go::ModuleVersion.new(@mod, :pseudo, commit, name: target, semver: semver)
+ else
+ @mod.version_by(ref: target)
+ end
+
+ when Gitlab::Git::Ref
+ @mod.version_by(ref: target)
+
+ when ::Commit, Gitlab::Git::Commit
+ @mod.version_by(commit: target)
+
+ else
+ raise ArgumentError.new 'not a valid target'
+ end
+ end
+ end
+ end
+end
diff --git a/app/finders/packages/group_packages_finder.rb b/app/finders/packages/group_packages_finder.rb
new file mode 100644
index 00000000000..ffc8c35fbcc
--- /dev/null
+++ b/app/finders/packages/group_packages_finder.rb
@@ -0,0 +1,70 @@
+# frozen_string_literal: true
+
+module Packages
+ class GroupPackagesFinder
+ attr_reader :current_user, :group, :params
+
+ InvalidPackageTypeError = Class.new(StandardError)
+
+ def initialize(current_user, group, params = { exclude_subgroups: false, order_by: 'created_at', sort: 'asc' })
+ @current_user = current_user
+ @group = group
+ @params = params
+ end
+
+ def execute
+ return ::Packages::Package.none unless group
+
+ packages_for_group_projects
+ end
+
+ private
+
+ def packages_for_group_projects
+ packages = ::Packages::Package
+ .for_projects(group_projects_visible_to_current_user)
+ .processed
+ .has_version
+ .sort_by_attribute("#{params[:order_by]}_#{params[:sort]}")
+
+ packages = filter_by_package_type(packages)
+ packages = filter_by_package_name(packages)
+ packages
+ end
+
+ def group_projects_visible_to_current_user
+ ::Project
+ .in_namespace(groups)
+ .public_or_visible_to_user(current_user, Gitlab::Access::REPORTER)
+ .with_project_feature
+ .select { |project| Ability.allowed?(current_user, :read_package, project) }
+ end
+
+ def package_type
+ params[:package_type].presence
+ end
+
+ def groups
+ return [group] if exclude_subgroups?
+
+ group.self_and_descendants
+ end
+
+ def exclude_subgroups?
+ params[:exclude_subgroups]
+ end
+
+ def filter_by_package_type(packages)
+ return packages unless package_type
+ raise InvalidPackageTypeError unless Package.package_types.key?(package_type)
+
+ packages.with_package_type(package_type)
+ end
+
+ def filter_by_package_name(packages)
+ return packages unless params[:package_name].present?
+
+ packages.search_by_name(params[:package_name])
+ end
+ end
+end
diff --git a/app/finders/packages/maven/package_finder.rb b/app/finders/packages/maven/package_finder.rb
new file mode 100644
index 00000000000..775db12adb7
--- /dev/null
+++ b/app/finders/packages/maven/package_finder.rb
@@ -0,0 +1,62 @@
+# frozen_string_literal: true
+module Packages
+ module Maven
+ class PackageFinder
+ attr_reader :path, :current_user, :project, :group
+
+ def initialize(path, current_user, project: nil, group: nil)
+ @path = path
+ @current_user = current_user
+ @project = project
+ @group = group
+ end
+
+ def execute
+ packages_with_path.last
+ end
+
+ def execute!
+ packages_with_path.last!
+ end
+
+ private
+
+ def base
+ if project
+ packages_for_a_single_project
+ elsif group
+ packages_for_multiple_projects
+ else
+ packages
+ end
+ end
+
+ def packages_with_path
+ base.only_maven_packages_with_path(path)
+ end
+
+ # Produces a query that returns all packages.
+ def packages
+ ::Packages::Package.all
+ end
+
+ # Produces a query that retrieves packages from a single project.
+ def packages_for_a_single_project
+ project.packages
+ end
+
+ # Produces a query that retrieves packages from multiple projects that
+ # the current user can view within a group.
+ def packages_for_multiple_projects
+ ::Packages::Package.for_projects(projects_visible_to_current_user)
+ end
+
+ # Returns the projects that the current user can view within a group.
+ def projects_visible_to_current_user
+ ::Project
+ .in_namespace(group.self_and_descendants.select(:id))
+ .public_or_visible_to_user(current_user)
+ end
+ end
+ end
+end
diff --git a/app/finders/packages/npm/package_finder.rb b/app/finders/packages/npm/package_finder.rb
new file mode 100644
index 00000000000..8599fd07e7f
--- /dev/null
+++ b/app/finders/packages/npm/package_finder.rb
@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+module Packages
+ module Npm
+ class PackageFinder
+ attr_reader :project, :package_name
+
+ delegate :find_by_version, to: :execute
+
+ def initialize(project, package_name)
+ @project = project
+ @package_name = package_name
+ end
+
+ def execute
+ packages
+ end
+
+ private
+
+ def packages
+ project.packages
+ .npm
+ .with_name(package_name)
+ .last_of_each_version
+ .preload_files
+ end
+ end
+ end
+end
diff --git a/app/finders/packages/nuget/package_finder.rb b/app/finders/packages/nuget/package_finder.rb
new file mode 100644
index 00000000000..e6fb6712d47
--- /dev/null
+++ b/app/finders/packages/nuget/package_finder.rb
@@ -0,0 +1,31 @@
+# frozen_string_literal: true
+module Packages
+ module Nuget
+ class PackageFinder
+ MAX_PACKAGES_COUNT = 50
+
+ def initialize(project, package_name:, package_version: nil, limit: MAX_PACKAGES_COUNT)
+ @project = project
+ @package_name = package_name
+ @package_version = package_version
+ @limit = limit
+ end
+
+ def execute
+ packages.limit_recent(@limit)
+ end
+
+ private
+
+ def packages
+ result = @project.packages
+ .nuget
+ .has_version
+ .processed
+ .with_name_like(@package_name)
+ result = result.with_version(@package_version) if @package_version.present?
+ result
+ end
+ end
+ end
+end
diff --git a/app/finders/packages/package_file_finder.rb b/app/finders/packages/package_file_finder.rb
new file mode 100644
index 00000000000..d015f4adfa6
--- /dev/null
+++ b/app/finders/packages/package_file_finder.rb
@@ -0,0 +1,36 @@
+# frozen_string_literal: true
+class Packages::PackageFileFinder
+ attr_reader :package, :file_name, :params
+
+ def initialize(package, file_name, params = {})
+ @package = package
+ @file_name = file_name
+ @params = params
+ end
+
+ def execute
+ package_files.last
+ end
+
+ def execute!
+ package_files.last!
+ end
+
+ private
+
+ def package_files
+ files = package.package_files
+
+ files = by_file_name(files)
+
+ files
+ end
+
+ def by_file_name(files)
+ if params[:with_file_name_like]
+ files.with_file_name_like(file_name)
+ else
+ files.with_file_name(file_name)
+ end
+ end
+end
diff --git a/app/finders/packages/package_finder.rb b/app/finders/packages/package_finder.rb
new file mode 100644
index 00000000000..0e911491da2
--- /dev/null
+++ b/app/finders/packages/package_finder.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+module Packages
+ class PackageFinder
+ def initialize(project, package_id)
+ @project = project
+ @package_id = package_id
+ end
+
+ def execute
+ @project
+ .packages
+ .processed
+ .find(@package_id)
+ end
+ end
+end
diff --git a/app/finders/packages/packages_finder.rb b/app/finders/packages/packages_finder.rb
new file mode 100644
index 00000000000..c533cb266a2
--- /dev/null
+++ b/app/finders/packages/packages_finder.rb
@@ -0,0 +1,41 @@
+# frozen_string_literal: true
+
+module Packages
+ class PackagesFinder
+ attr_reader :params, :project
+
+ def initialize(project, params = {})
+ @project = project
+ @params = params
+
+ params[:order_by] ||= 'created_at'
+ params[:sort] ||= 'asc'
+ end
+
+ def execute
+ packages = project.packages.processed.has_version
+ packages = filter_by_package_type(packages)
+ packages = filter_by_package_name(packages)
+ packages = order_packages(packages)
+ packages
+ end
+
+ private
+
+ def filter_by_package_type(packages)
+ return packages unless params[:package_type]
+
+ packages.with_package_type(params[:package_type])
+ end
+
+ def filter_by_package_name(packages)
+ return packages unless params[:package_name]
+
+ packages.search_by_name(params[:package_name])
+ end
+
+ def order_packages(packages)
+ packages.sort_by_attribute("#{params[:order_by]}_#{params[:sort]}")
+ end
+ end
+end
diff --git a/app/finders/packages/tags_finder.rb b/app/finders/packages/tags_finder.rb
new file mode 100644
index 00000000000..020b3d8072a
--- /dev/null
+++ b/app/finders/packages/tags_finder.rb
@@ -0,0 +1,26 @@
+# frozen_string_literal: true
+class Packages::TagsFinder
+ attr_reader :project, :package_name, :params
+
+ delegate :find_by_name, to: :execute
+
+ def initialize(project, package_name, params = {})
+ @project = project
+ @package_name = package_name
+ @params = params
+ end
+
+ def execute
+ packages = project.packages
+ .with_name(package_name)
+ packages = packages.with_package_type(package_type) if package_type.present?
+
+ Packages::Tag.for_packages(packages)
+ end
+
+ private
+
+ def package_type
+ params[:package_type]
+ end
+end
diff --git a/app/finders/personal_access_tokens_finder.rb b/app/finders/personal_access_tokens_finder.rb
index 7b15a3b0c10..e3d5f2ae8de 100644
--- a/app/finders/personal_access_tokens_finder.rb
+++ b/app/finders/personal_access_tokens_finder.rb
@@ -51,6 +51,8 @@ class PersonalAccessTokensFinder
tokens.active
when 'inactive'
tokens.inactive
+ when 'active_or_expired'
+ tokens.not_revoked.expired.or(tokens.active)
else
tokens
end
diff --git a/app/finders/projects_finder.rb b/app/finders/projects_finder.rb
index 8846ff54eb2..7c7cd87a7c1 100644
--- a/app/finders/projects_finder.rb
+++ b/app/finders/projects_finder.rb
@@ -23,6 +23,7 @@
# min_access_level: integer
# last_activity_after: datetime
# last_activity_before: datetime
+# repository_storage: string
#
class ProjectsFinder < UnionFinder
include CustomAttributesFilter
@@ -75,6 +76,7 @@ class ProjectsFinder < UnionFinder
collection = by_deleted_status(collection)
collection = by_last_activity_after(collection)
collection = by_last_activity_before(collection)
+ collection = by_repository_storage(collection)
collection
end
@@ -197,6 +199,14 @@ class ProjectsFinder < UnionFinder
end
end
+ def by_repository_storage(items)
+ if params[:repository_storage].present?
+ items.where(repository_storage: params[:repository_storage]) # rubocop: disable CodeReuse/ActiveRecord
+ else
+ items
+ end
+ end
+
def sort(items)
params[:sort].present? ? items.sort_by_attribute(params[:sort]) : items.projects_order_id_desc
end
diff --git a/app/finders/resource_milestone_event_finder.rb b/app/finders/resource_milestone_event_finder.rb
index 7af34f0a4bc..f3b779c8f77 100644
--- a/app/finders/resource_milestone_event_finder.rb
+++ b/app/finders/resource_milestone_event_finder.rb
@@ -1,69 +1,56 @@
# frozen_string_literal: true
class ResourceMilestoneEventFinder
- include FinderMethods
-
- MAX_PER_PAGE = 100
-
- attr_reader :params, :current_user, :eventable
-
- def initialize(current_user, eventable, params = {})
+ def initialize(current_user, eventable)
@current_user = current_user
@eventable = eventable
- @params = params
end
+ # Returns the ResourceMilestoneEvents of the eventable
+ # visible to the user.
+ #
+ # @return ResourceMilestoneEvent::ActiveRecord_AssociationRelation
def execute
- Kaminari.paginate_array(visible_events)
+ eventable.resource_milestone_events.include_relations
+ .where(milestone_id: readable_milestone_ids) # rubocop: disable CodeReuse/ActiveRecord
end
private
- def visible_events
- @visible_events ||= visible_to_user(events)
- end
+ attr_reader :current_user, :eventable
- def events
- @events ||= eventable.resource_milestone_events.include_relations.page(page).per(per_page)
- end
+ def readable_milestone_ids
+ readable_milestones = events_milestones.select do |milestone|
+ parent_availabilities[key_for_parent(milestone.parent)]
+ end
- def visible_to_user(events)
- events.select { |event| visible_for_user?(event) }
+ readable_milestones.map(&:id).uniq
end
- def visible_for_user?(event)
- milestone = event_milestones[event.milestone_id]
- return if milestone.blank?
+ # rubocop: disable CodeReuse/ActiveRecord
+ def events_milestones
+ @events_milestones ||= Milestone.where(id: unique_milestone_ids_from_events)
+ .includes(:project, :group)
+ end
+ # rubocop: enable CodeReuse/ActiveRecord
- parent = milestone.parent
- parent_availabilities[key_for_parent(parent)]
+ def relevant_milestone_parents
+ events_milestones.map(&:parent).uniq
end
def parent_availabilities
- @parent_availabilities ||= relevant_parents.to_h do |parent|
+ @parent_availabilities ||= relevant_milestone_parents.to_h do |parent|
[key_for_parent(parent), Ability.allowed?(current_user, :read_milestone, parent)]
end
end
- def key_for_parent(parent)
- "#{parent.class.name}_#{parent.id}"
- end
-
- def event_milestones
- @milestones ||= events.map(&:milestone).uniq.to_h do |milestone|
- [milestone.id, milestone]
- end
- end
-
- def relevant_parents
- @relevant_parents ||= event_milestones.map { |_id, milestone| milestone.parent }
+ # rubocop: disable CodeReuse/ActiveRecord
+ def unique_milestone_ids_from_events
+ eventable.resource_milestone_events.select(:milestone_id).distinct
end
+ # rubocop: enable CodeReuse/ActiveRecord
- def per_page
- [params[:per_page], MAX_PER_PAGE].compact.min
- end
-
- def page
- params[:page] || 1
+ def key_for_parent(parent)
+ "#{parent.class.name}_#{parent.id}"
end
end
diff --git a/app/finders/resource_state_event_finder.rb b/app/finders/resource_state_event_finder.rb
new file mode 100644
index 00000000000..7f4ac3332cd
--- /dev/null
+++ b/app/finders/resource_state_event_finder.rb
@@ -0,0 +1,30 @@
+# frozen_string_literal: true
+
+class ResourceStateEventFinder
+ include FinderMethods
+
+ def initialize(current_user, eventable)
+ @current_user = current_user
+ @eventable = eventable
+ end
+
+ def execute
+ return ResourceStateEvent.none unless can_read_eventable?
+
+ eventable.resource_state_events.includes(:user) # rubocop: disable CodeReuse/ActiveRecord
+ end
+
+ def can_read_eventable?
+ return unless eventable
+
+ Ability.allowed?(current_user, read_ability, eventable)
+ end
+
+ private
+
+ attr_reader :current_user, :eventable
+
+ def read_ability
+ :"read_#{eventable.class.to_ability_name}"
+ end
+end
diff --git a/app/finders/snippets_finder.rb b/app/finders/snippets_finder.rb
index 4f63810423b..941abb70400 100644
--- a/app/finders/snippets_finder.rb
+++ b/app/finders/snippets_finder.rb
@@ -43,7 +43,7 @@ class SnippetsFinder < UnionFinder
include Gitlab::Utils::StrongMemoize
attr_accessor :current_user, :params
- delegate :explore, :only_personal, :only_project, :scope, to: :params
+ delegate :explore, :only_personal, :only_project, :scope, :sort, to: :params
def initialize(current_user = nil, params = {})
@current_user = current_user
@@ -69,7 +69,9 @@ class SnippetsFinder < UnionFinder
items = init_collection
items = by_ids(items)
- items.with_optional_visibility(visibility_from_scope).fresh
+ items = items.with_optional_visibility(visibility_from_scope)
+
+ items.order_by(sort_param)
end
private
@@ -115,7 +117,7 @@ class SnippetsFinder < UnionFinder
queries << snippets_of_authorized_projects if current_user
end
- find_union(queries, Snippet)
+ prepared_union(queries)
end
def snippets_for_a_single_project
@@ -202,6 +204,21 @@ class SnippetsFinder < UnionFinder
params[:project].is_a?(Project) ? params[:project] : Project.find_by_id(params[:project])
end
end
+
+ def sort_param
+ sort.presence || 'id_desc'
+ end
+
+ def prepared_union(queries)
+ return Snippet.none if queries.empty?
+ return queries.first if queries.length == 1
+
+ # The queries are going to be part of a global `where`
+ # therefore we only need to retrieve the `id` column
+ # which will speed the query
+ queries.map! { |rel| rel.select(:id) }
+ Snippet.id_in(find_union(queries, Snippet))
+ end
end
SnippetsFinder.prepend_if_ee('EE::SnippetsFinder')
diff --git a/app/finders/todos_finder.rb b/app/finders/todos_finder.rb
index 672bbd52b07..a2054f73c9d 100644
--- a/app/finders/todos_finder.rb
+++ b/app/finders/todos_finder.rb
@@ -11,7 +11,7 @@
# author_id: integer
# project_id; integer
# state: 'pending' (default) or 'done'
-# type: 'Issue' or 'MergeRequest'
+# type: 'Issue' or 'MergeRequest' or ['Issue', 'MergeRequest']
#
class TodosFinder
@@ -40,13 +40,14 @@ class TodosFinder
def execute
return Todo.none if current_user.nil?
+ raise ArgumentError, invalid_type_message unless valid_types?
items = current_user.todos
items = by_action_id(items)
items = by_action(items)
items = by_author(items)
items = by_state(items)
- items = by_type(items)
+ items = by_types(items)
items = by_group(items)
# Filtering by project HAS TO be the last because we use
# the project IDs yielded by the todos query thus far
@@ -123,12 +124,16 @@ class TodosFinder
end
end
- def type?
- type.present? && self.class.todo_types.include?(type)
+ def types
+ @types ||= Array(params[:type]).reject(&:blank?)
end
- def type
- params[:type]
+ def valid_types?
+ types.all? { |type| self.class.todo_types.include?(type) }
+ end
+
+ def invalid_type_message
+ _("Unsupported todo type passed. Supported todo types are: %{todo_types}") % { todo_types: self.class.todo_types.to_a.join(', ') }
end
def sort(items)
@@ -193,9 +198,9 @@ class TodosFinder
items.with_states(params[:state])
end
- def by_type(items)
- if type?
- items.for_type(type)
+ def by_types(items)
+ if types.any?
+ items.for_type(types)
else
items
end
diff --git a/app/graphql/mutations/alert_management/alerts/todo/create.rb b/app/graphql/mutations/alert_management/alerts/todo/create.rb
new file mode 100644
index 00000000000..3dba96e43f1
--- /dev/null
+++ b/app/graphql/mutations/alert_management/alerts/todo/create.rb
@@ -0,0 +1,30 @@
+# frozen_string_literal: true
+
+module Mutations
+ module AlertManagement
+ module Alerts
+ module Todo
+ class Create < Base
+ graphql_name 'AlertTodoCreate'
+
+ def resolve(args)
+ alert = authorized_find!(project_path: args[:project_path], iid: args[:iid])
+ result = ::AlertManagement::Alerts::Todo::CreateService.new(alert, current_user).execute
+
+ prepare_response(result)
+ end
+
+ private
+
+ def prepare_response(result)
+ {
+ alert: result.payload[:alert],
+ todo: result.payload[:todo],
+ errors: result.error? ? [result.message] : []
+ }
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/app/graphql/mutations/alert_management/base.rb b/app/graphql/mutations/alert_management/base.rb
index 7fcca63db51..0de4b9409e4 100644
--- a/app/graphql/mutations/alert_management/base.rb
+++ b/app/graphql/mutations/alert_management/base.rb
@@ -18,6 +18,11 @@ module Mutations
null: true,
description: "The alert after mutation"
+ field :todo,
+ Types::TodoType,
+ null: true,
+ description: "The todo after mutation"
+
field :issue,
Types::IssueType,
null: true,
diff --git a/app/graphql/mutations/alert_management/update_alert_status.rb b/app/graphql/mutations/alert_management/update_alert_status.rb
index d820124d26f..ed61555fbd6 100644
--- a/app/graphql/mutations/alert_management/update_alert_status.rb
+++ b/app/graphql/mutations/alert_management/update_alert_status.rb
@@ -19,8 +19,8 @@ module Mutations
private
def update_status(alert, status)
- ::AlertManagement::UpdateAlertStatusService
- .new(alert, current_user, status)
+ ::AlertManagement::Alerts::UpdateService
+ .new(alert, current_user, status: status)
.execute
end
diff --git a/app/graphql/mutations/award_emojis/add.rb b/app/graphql/mutations/award_emojis/add.rb
index 85f3eb065bb..856fdd5fb14 100644
--- a/app/graphql/mutations/award_emojis/add.rb
+++ b/app/graphql/mutations/award_emojis/add.rb
@@ -3,7 +3,7 @@
module Mutations
module AwardEmojis
class Add < Base
- graphql_name 'AddAwardEmoji'
+ graphql_name 'AwardEmojiAdd'
def resolve(args)
awardable = authorized_find!(id: args[:awardable_id])
diff --git a/app/graphql/mutations/award_emojis/remove.rb b/app/graphql/mutations/award_emojis/remove.rb
index f8a3d0ce390..c654688c6dc 100644
--- a/app/graphql/mutations/award_emojis/remove.rb
+++ b/app/graphql/mutations/award_emojis/remove.rb
@@ -3,7 +3,7 @@
module Mutations
module AwardEmojis
class Remove < Base
- graphql_name 'RemoveAwardEmoji'
+ graphql_name 'AwardEmojiRemove'
def resolve(args)
awardable = authorized_find!(id: args[:awardable_id])
diff --git a/app/graphql/mutations/award_emojis/toggle.rb b/app/graphql/mutations/award_emojis/toggle.rb
index 22eab4812a1..a7714e695d2 100644
--- a/app/graphql/mutations/award_emojis/toggle.rb
+++ b/app/graphql/mutations/award_emojis/toggle.rb
@@ -3,7 +3,7 @@
module Mutations
module AwardEmojis
class Toggle < Base
- graphql_name 'ToggleAwardEmoji'
+ graphql_name 'AwardEmojiToggle'
field :toggledOn, GraphQL::BOOLEAN_TYPE, null: false,
description: 'Indicates the status of the emoji. ' \
diff --git a/app/graphql/mutations/base_mutation.rb b/app/graphql/mutations/base_mutation.rb
index 33f3f33a440..68e7853a9b1 100644
--- a/app/graphql/mutations/base_mutation.rb
+++ b/app/graphql/mutations/base_mutation.rb
@@ -7,6 +7,8 @@ module Mutations
ERROR_MESSAGE = 'You cannot perform write operations on a read-only instance'
+ field_class ::Types::BaseField
+
field :errors, [GraphQL::STRING_TYPE],
null: false,
description: 'Errors encountered during execution of the mutation.'
diff --git a/app/graphql/mutations/concerns/mutations/resolves_issuable.rb b/app/graphql/mutations/concerns/mutations/resolves_issuable.rb
index 13a56f2e709..0fe2d09de6d 100644
--- a/app/graphql/mutations/concerns/mutations/resolves_issuable.rb
+++ b/app/graphql/mutations/concerns/mutations/resolves_issuable.rb
@@ -9,30 +9,31 @@ module Mutations
end
def resolve_issuable(type:, parent_path:, iid:)
- parent = resolve_issuable_parent(type, parent_path)
- key = type == :merge_request ? :iids : :iid
- args = { key => iid.to_s }
+ parent = ::Gitlab::Graphql::Lazy.force(resolve_issuable_parent(type, parent_path))
+ return unless parent.present?
- resolver = issuable_resolver(type, parent, context)
- ready, early_return = resolver.ready?(**args)
-
- return early_return unless ready
-
- resolver.resolve(**args)
+ finder = issuable_finder(type, iids: [iid])
+ Gitlab::Graphql::Loaders::IssuableLoader.new(parent, finder).find_all.first
end
private
- def issuable_resolver(type, parent, context)
- resolver_class = "Resolvers::#{type.to_s.classify.pluralize}Resolver".constantize
-
- resolver_class.single.new(object: parent, context: context, field: nil)
+ def issuable_finder(type, args)
+ case type
+ when :merge_request
+ MergeRequestsFinder.new(current_user, args)
+ when :issue
+ IssuesFinder.new(current_user, args)
+ else
+ raise "Unsupported type: #{type}"
+ end
end
def resolve_issuable_parent(type, parent_path)
+ return unless parent_path.present?
return unless type == :issue || type == :merge_request
- resolve_project(full_path: parent_path) if parent_path.present?
+ resolve_project(full_path: parent_path)
end
end
end
diff --git a/app/graphql/mutations/container_expiration_policies/update.rb b/app/graphql/mutations/container_expiration_policies/update.rb
index c210571c6ca..4bff04bb705 100644
--- a/app/graphql/mutations/container_expiration_policies/update.rb
+++ b/app/graphql/mutations/container_expiration_policies/update.rb
@@ -34,6 +34,16 @@ module Mutations
required: false,
description: copy_field_description(Types::ContainerExpirationPolicyType, :keep_n)
+ argument :name_regex,
+ Types::UntrustedRegexp,
+ required: false,
+ description: copy_field_description(Types::ContainerExpirationPolicyType, :name_regex)
+
+ argument :name_regex_keep,
+ Types::UntrustedRegexp,
+ required: false,
+ description: copy_field_description(Types::ContainerExpirationPolicyType, :name_regex_keep)
+
field :container_expiration_policy,
Types::ContainerExpirationPolicyType,
null: true,
diff --git a/app/graphql/mutations/issues/set_locked.rb b/app/graphql/mutations/issues/set_locked.rb
new file mode 100644
index 00000000000..63a8483067a
--- /dev/null
+++ b/app/graphql/mutations/issues/set_locked.rb
@@ -0,0 +1,26 @@
+# frozen_string_literal: true
+
+module Mutations
+ module Issues
+ class SetLocked < Base
+ graphql_name 'IssueSetLocked'
+
+ argument :locked,
+ GraphQL::BOOLEAN_TYPE,
+ required: true,
+ description: 'Whether or not to lock discussion on the issue'
+
+ def resolve(project_path:, iid:, locked:)
+ issue = authorized_find!(project_path: project_path, iid: iid)
+
+ ::Issues::UpdateService.new(issue.project, current_user, discussion_locked: locked)
+ .execute(issue)
+
+ {
+ issue: issue,
+ errors: errors_on_object(issue)
+ }
+ end
+ end
+ end
+end
diff --git a/app/graphql/mutations/jira_import/start.rb b/app/graphql/mutations/jira_import/start.rb
index 3df26d33711..eda28059272 100644
--- a/app/graphql/mutations/jira_import/start.rb
+++ b/app/graphql/mutations/jira_import/start.rb
@@ -21,12 +21,17 @@ module Mutations
argument :jira_project_name, GraphQL::STRING_TYPE,
required: false,
description: 'Project name of the importer Jira project'
+ argument :users_mapping,
+ [Types::JiraUsersMappingInputType],
+ required: false,
+ description: 'The mapping of Jira to GitLab users'
- def resolve(project_path:, jira_project_key:)
+ def resolve(project_path:, jira_project_key:, users_mapping:)
project = authorized_find!(full_path: project_path)
+ mapping = users_mapping.to_ary.map { |map| map.to_hash }
service_response = ::JiraImport::StartImportService
- .new(context[:current_user], project, jira_project_key)
+ .new(context[:current_user], project, jira_project_key, mapping)
.execute
jira_import = service_response.success? ? service_response.payload[:import_data] : nil
diff --git a/app/graphql/mutations/merge_requests/update.rb b/app/graphql/mutations/merge_requests/update.rb
new file mode 100644
index 00000000000..b583fdfca9b
--- /dev/null
+++ b/app/graphql/mutations/merge_requests/update.rb
@@ -0,0 +1,39 @@
+# frozen_string_literal: true
+
+module Mutations
+ module MergeRequests
+ class Update < Base
+ graphql_name 'MergeRequestUpdate'
+
+ description 'Update attributes of a merge request'
+
+ argument :title, GraphQL::STRING_TYPE,
+ required: false,
+ description: copy_field_description(Types::MergeRequestType, :title)
+
+ argument :target_branch, GraphQL::STRING_TYPE,
+ required: false,
+ description: copy_field_description(Types::MergeRequestType, :target_branch)
+
+ argument :description, GraphQL::STRING_TYPE,
+ required: false,
+ description: copy_field_description(Types::MergeRequestType, :description)
+
+ def resolve(args)
+ merge_request = authorized_find!(args.slice(:project_path, :iid))
+ attributes = args.slice(:title, :description, :target_branch).compact
+
+ ::MergeRequests::UpdateService
+ .new(merge_request.project, current_user, attributes)
+ .execute(merge_request)
+
+ errors = errors_on_object(merge_request)
+
+ {
+ merge_request: merge_request.reset,
+ errors: errors
+ }
+ end
+ end
+ end
+end
diff --git a/app/graphql/mutations/notes/create/base.rb b/app/graphql/mutations/notes/create/base.rb
index cf9f74a63d8..f081eac368e 100644
--- a/app/graphql/mutations/notes/create/base.rb
+++ b/app/graphql/mutations/notes/create/base.rb
@@ -18,6 +18,11 @@ module Mutations
required: true,
description: copy_field_description(Types::Notes::NoteType, :body)
+ argument :confidential,
+ GraphQL::BOOLEAN_TYPE,
+ required: false,
+ description: 'The confidentiality flag of a note. Default is false.'
+
def resolve(args)
noteable = authorized_find!(id: args[:noteable_id])
@@ -40,7 +45,8 @@ module Mutations
def create_note_params(noteable, args)
{
noteable: noteable,
- note: args[:body]
+ note: args[:body],
+ confidential: args[:confidential]
}
end
end
diff --git a/app/graphql/mutations/snippets/create.rb b/app/graphql/mutations/snippets/create.rb
index e1022358c09..89c21486a74 100644
--- a/app/graphql/mutations/snippets/create.rb
+++ b/app/graphql/mutations/snippets/create.rb
@@ -21,7 +21,7 @@ module Mutations
description: 'File name of the snippet'
argument :content, GraphQL::STRING_TYPE,
- required: true,
+ required: false,
description: 'Content of the snippet'
argument :description, GraphQL::STRING_TYPE,
@@ -40,6 +40,10 @@ module Mutations
required: false,
description: 'The paths to files uploaded in the snippet description'
+ argument :files, [Types::Snippets::FileInputType],
+ description: "The snippet files to create",
+ required: false
+
def resolve(args)
project_path = args.delete(:project_path)
@@ -49,13 +53,9 @@ module Mutations
raise_resource_not_available_error!
end
- # We need to rename `uploaded_files` into `files` because
- # it's the expected key param
- args[:files] = args.delete(:uploaded_files)
-
service_response = ::Snippets::CreateService.new(project,
- context[:current_user],
- args).execute
+ context[:current_user],
+ create_params(args)).execute
snippet = service_response.payload[:snippet]
@@ -82,6 +82,18 @@ module Mutations
def can_create_personal_snippet?
Ability.allowed?(context[:current_user], :create_snippet)
end
+
+ def create_params(args)
+ args.tap do |create_args|
+ # We need to rename `files` into `snippet_actions` because
+ # it's the expected key param
+ create_args[:snippet_actions] = create_args.delete(:files)&.map(&:to_h)
+
+ # We need to rename `uploaded_files` into `files` because
+ # it's the expected key param
+ create_args[:files] = create_args.delete(:uploaded_files)
+ end
+ end
end
end
end
diff --git a/app/graphql/mutations/snippets/update.rb b/app/graphql/mutations/snippets/update.rb
index b6bdcb9b67b..8890158b0df 100644
--- a/app/graphql/mutations/snippets/update.rb
+++ b/app/graphql/mutations/snippets/update.rb
@@ -30,12 +30,16 @@ module Mutations
description: 'The visibility level of the snippet',
required: false
+ argument :files, [Types::Snippets::FileInputType],
+ description: 'The snippet files to update',
+ required: false
+
def resolve(args)
snippet = authorized_find!(id: args.delete(:id))
result = ::Snippets::UpdateService.new(snippet.project,
- context[:current_user],
- args).execute(snippet)
+ context[:current_user],
+ update_params(args)).execute(snippet)
snippet = result.payload[:snippet]
{
@@ -47,7 +51,15 @@ module Mutations
private
def ability_name
- "update"
+ 'update'
+ end
+
+ def update_params(args)
+ args.tap do |update_args|
+ # We need to rename `files` into `snippet_actions` because
+ # it's the expected key param
+ update_args[:snippet_actions] = update_args.delete(:files)&.map(&:to_h)
+ end
end
end
end
diff --git a/app/graphql/mutations/todos/mark_all_done.rb b/app/graphql/mutations/todos/mark_all_done.rb
index d30d1bcbcf0..8b53658ddd5 100644
--- a/app/graphql/mutations/todos/mark_all_done.rb
+++ b/app/graphql/mutations/todos/mark_all_done.rb
@@ -10,8 +10,13 @@ module Mutations
field :updated_ids,
[GraphQL::ID_TYPE],
null: false,
+ deprecated: { reason: 'Use todos', milestone: '13.2' },
description: 'Ids of the updated todos'
+ field :todos, [::Types::TodoType],
+ null: false,
+ description: 'Updated todos'
+
def resolve
authorize!(current_user)
@@ -19,6 +24,7 @@ module Mutations
{
updated_ids: map_to_global_ids(updated_ids),
+ todos: Todo.id_in(updated_ids),
errors: []
}
end
diff --git a/app/graphql/mutations/todos/restore_many.rb b/app/graphql/mutations/todos/restore_many.rb
index e95651b232f..c5e2750768c 100644
--- a/app/graphql/mutations/todos/restore_many.rb
+++ b/app/graphql/mutations/todos/restore_many.rb
@@ -14,7 +14,12 @@ module Mutations
field :updated_ids, [GraphQL::ID_TYPE],
null: false,
- description: 'The ids of the updated todo items'
+ description: 'The ids of the updated todo items',
+ deprecated: { reason: 'Use todos', milestone: '13.2' }
+
+ field :todos, [::Types::TodoType],
+ null: false,
+ description: 'Updated todos'
def resolve(ids:)
check_update_amount_limit!(ids)
@@ -24,6 +29,7 @@ module Mutations
{
updated_ids: gids_of(updated_ids),
+ todos: Todo.id_in(updated_ids),
errors: errors_on_objects(todos)
}
end
diff --git a/app/graphql/resolvers/base_resolver.rb b/app/graphql/resolvers/base_resolver.rb
index 7daff68c069..791c6eab42f 100644
--- a/app/graphql/resolvers/base_resolver.rb
+++ b/app/graphql/resolvers/base_resolver.rb
@@ -83,5 +83,10 @@ module Resolvers
def current_user
context[:current_user]
end
+
+ # Overridden in sub-classes (see .single, .last)
+ def select_result(results)
+ results
+ end
end
end
diff --git a/app/graphql/resolvers/ci_configuration/sast_resolver.rb b/app/graphql/resolvers/ci_configuration/sast_resolver.rb
new file mode 100644
index 00000000000..e8c42076ea2
--- /dev/null
+++ b/app/graphql/resolvers/ci_configuration/sast_resolver.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+require "json"
+
+module Resolvers
+ module CiConfiguration
+ class SastResolver < BaseResolver
+ SAST_UI_SCHEMA_PATH = 'app/validators/json_schemas/security_ci_configuration_schemas/sast_ui_schema.json'
+
+ type ::Types::CiConfiguration::Sast::Type, null: true
+
+ def resolve(**args)
+ Gitlab::Json.parse(File.read(Rails.root.join(SAST_UI_SCHEMA_PATH)))
+ end
+ end
+ end
+end
diff --git a/app/graphql/resolvers/concerns/resolves_merge_requests.rb b/app/graphql/resolvers/concerns/resolves_merge_requests.rb
index a2140728a27..7ed88be52b9 100644
--- a/app/graphql/resolvers/concerns/resolves_merge_requests.rb
+++ b/app/graphql/resolvers/concerns/resolves_merge_requests.rb
@@ -11,16 +11,10 @@ module ResolvesMergeRequests
end
def resolve_with_lookahead(**args)
- args[:iids] = Array.wrap(args[:iids]) if args[:iids]
- args.compact!
+ mr_finder = MergeRequestsFinder.new(current_user, args.compact)
+ finder = Gitlab::Graphql::Loaders::IssuableLoader.new(project, mr_finder)
- if project && args.keys == [:iids]
- batch_load_merge_requests(args[:iids])
- else
- args[:project_id] ||= project
-
- apply_lookahead(MergeRequestsFinder.new(current_user, args).execute)
- end.then(&(single? ? :first : :itself))
+ select_result(finder.batching_find_all { |query| apply_lookahead(query) })
end
def ready?(**args)
@@ -35,22 +29,6 @@ module ResolvesMergeRequests
private
- def batch_load_merge_requests(iids)
- iids.map { |iid| batch_load(iid) }.select(&:itself) # .compact doesn't work on BatchLoader
- end
-
- # rubocop: disable CodeReuse/ActiveRecord
- def batch_load(iid)
- BatchLoader::GraphQL.for(iid.to_s).batch(key: project) do |iids, loader, args|
- query = args[:key].merge_requests.where(iid: iids)
-
- apply_lookahead(query).each do |mr|
- loader.call(mr.iid.to_s, mr)
- end
- end
- end
- # rubocop: enable CodeReuse/ActiveRecord
-
def unconditional_includes
[:target_project]
end
diff --git a/app/graphql/resolvers/environments_resolver.rb b/app/graphql/resolvers/environments_resolver.rb
index 4e9a17f1e17..1b916a89796 100644
--- a/app/graphql/resolvers/environments_resolver.rb
+++ b/app/graphql/resolvers/environments_resolver.rb
@@ -8,7 +8,7 @@ module Resolvers
argument :search, GraphQL::STRING_TYPE,
required: false,
- description: 'Search query'
+ description: 'Search query for environment name'
argument :states, [GraphQL::STRING_TYPE],
required: false,
diff --git a/app/graphql/resolvers/issues_resolver.rb b/app/graphql/resolvers/issues_resolver.rb
index f103da07666..9d0535a208f 100644
--- a/app/graphql/resolvers/issues_resolver.rb
+++ b/app/graphql/resolvers/issues_resolver.rb
@@ -44,7 +44,7 @@ module Resolvers
description: 'Issues closed after this date'
argument :search, GraphQL::STRING_TYPE,
required: false,
- description: 'Search query for finding issues by title or description'
+ description: 'Search query for issue title or description'
argument :sort, Types::IssueSortEnum,
description: 'Sort issues by this criteria',
required: false,
@@ -63,18 +63,13 @@ module Resolvers
parent = object.respond_to?(:sync) ? object.sync : object
return Issue.none if parent.nil?
- if parent.is_a?(Group)
- args[:group_id] = parent.id
- else
- args[:project_id] = parent.id
- end
-
# Will need to be be made group & namespace aware with
# https://gitlab.com/gitlab-org/gitlab-foss/issues/54520
- args[:iids] ||= [args[:iid]].compact
- args[:attempt_project_search_optimizations] = args[:search].present?
+ args[:iids] ||= [args.delete(:iid)].compact if args[:iid]
+ args[:attempt_project_search_optimizations] = true if args[:search].present?
- issues = IssuesFinder.new(context[:current_user], args).execute
+ finder = IssuesFinder.new(current_user, args)
+ issues = Gitlab::Graphql::Loaders::IssuableLoader.new(parent, finder).batching_find_all
if non_stable_cursor_sort?(args[:sort])
# Certain complex sorts are not supported by the stable cursor pagination yet.
@@ -97,3 +92,5 @@ module Resolvers
end
end
end
+
+Resolvers::IssuesResolver.prepend_if_ee('::EE::Resolvers::IssuesResolver')
diff --git a/app/graphql/resolvers/last_commit_resolver.rb b/app/graphql/resolvers/last_commit_resolver.rb
index 7a433d6556f..dd89c322617 100644
--- a/app/graphql/resolvers/last_commit_resolver.rb
+++ b/app/graphql/resolvers/last_commit_resolver.rb
@@ -9,7 +9,7 @@ module Resolvers
def resolve(**args)
# Ensure merge commits can be returned by sending nil to Gitaly instead of '/'
path = tree.path == '/' ? nil : tree.path
- commit = Gitlab::Git::Commit.last_for_path(tree.repository, tree.sha, path)
+ commit = Gitlab::Git::Commit.last_for_path(tree.repository, tree.sha, path, literal_pathspec: true)
::Commit.new(commit, tree.repository.project) if commit
end
diff --git a/app/graphql/resolvers/milestone_resolver.rb b/app/graphql/resolvers/milestone_resolver.rb
index 6c6513e0ee4..bcfbc63c31f 100644
--- a/app/graphql/resolvers/milestone_resolver.rb
+++ b/app/graphql/resolvers/milestone_resolver.rb
@@ -52,7 +52,7 @@ module Resolvers
end
def group_parameters(args)
- return { group_ids: parent.id } unless include_descendants?(args)
+ return { group_ids: parent.id } unless args[:include_descendants].present?
{
group_ids: parent.self_and_descendants.public_or_visible_to_user(current_user).select(:id),
@@ -60,10 +60,6 @@ module Resolvers
}
end
- def include_descendants?(args)
- args[:include_descendants].present? && Feature.enabled?(:group_milestone_descendants, parent)
- end
-
def group_projects
GroupProjectsFinder.new(
group: parent,
diff --git a/app/graphql/resolvers/packages_resolver.rb b/app/graphql/resolvers/packages_resolver.rb
new file mode 100644
index 00000000000..519fb87183e
--- /dev/null
+++ b/app/graphql/resolvers/packages_resolver.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+module Resolvers
+ class PackagesResolver < BaseResolver
+ type Types::PackageType, null: true
+
+ def resolve(**args)
+ return unless packages_available?
+
+ ::Packages::PackagesFinder.new(object).execute
+ end
+
+ private
+
+ def packages_available?
+ ::Gitlab.config.packages.enabled
+ end
+ end
+end
diff --git a/app/graphql/resolvers/projects/jira_projects_resolver.rb b/app/graphql/resolvers/projects/jira_projects_resolver.rb
index a8c3768df41..2dc712128cc 100644
--- a/app/graphql/resolvers/projects/jira_projects_resolver.rb
+++ b/app/graphql/resolvers/projects/jira_projects_resolver.rb
@@ -13,11 +13,10 @@ module Resolvers
def resolve(name: nil, **args)
authorize!(project)
- response, start_cursor, end_cursor = jira_projects(name: name, **compute_pagination_params(args))
- end_cursor = nil if !!response.payload[:is_last]
+ response = jira_projects(name: name)
if response.success?
- Gitlab::Graphql::ExternallyPaginatedArray.new(start_cursor, end_cursor, *response.payload[:projects])
+ response.payload[:projects]
else
raise Gitlab::Graphql::Errors::BaseError, response.message
end
@@ -35,41 +34,10 @@ module Resolvers
jira_service&.project
end
- def compute_pagination_params(params)
- after_cursor = Base64.decode64(params[:after].to_s)
- before_cursor = Base64.decode64(params[:before].to_s)
+ def jira_projects(name:)
+ args = { query: name }.compact
- # differentiate between 0 cursor and nil or invalid cursor that decodes into zero.
- after_index = after_cursor.to_i == 0 && after_cursor != "0" ? nil : after_cursor.to_i
- before_index = before_cursor.to_i == 0 && before_cursor != "0" ? nil : before_cursor.to_i
-
- if after_index.present? && before_index.present?
- if after_index >= before_index
- { start_at: 0, limit: 0 }
- else
- { start_at: after_index + 1, limit: before_index - after_index - 1 }
- end
- elsif after_index.present?
- { start_at: after_index + 1, limit: nil }
- elsif before_index.present?
- { start_at: 0, limit: before_index - 1 }
- else
- { start_at: 0, limit: nil }
- end
- end
-
- def jira_projects(name:, start_at:, limit:)
- args = { query: name, start_at: start_at, limit: limit }.compact
-
- response = Jira::Requests::Projects.new(project.jira_service, args).execute
-
- return [response, nil, nil] if response.error?
-
- projects = response.payload[:projects]
- start_cursor = start_at == 0 ? nil : Base64.encode64((start_at - 1).to_s)
- end_cursor = Base64.encode64((start_at + projects.size - 1).to_s)
-
- [response, start_cursor, end_cursor]
+ Jira::Requests::Projects::ListService.new(project.jira_service, args).execute
end
end
end
diff --git a/app/graphql/resolvers/projects_resolver.rb b/app/graphql/resolvers/projects_resolver.rb
index 068546cd39f..f75f591b381 100644
--- a/app/graphql/resolvers/projects_resolver.rb
+++ b/app/graphql/resolvers/projects_resolver.rb
@@ -10,7 +10,7 @@ module Resolvers
argument :search, GraphQL::STRING_TYPE,
required: false,
- description: 'Search criteria'
+ description: 'Search query for project name, path, or description'
def resolve(**args)
ProjectsFinder
diff --git a/app/graphql/resolvers/release_resolver.rb b/app/graphql/resolvers/release_resolver.rb
index 9bae8b8cd13..1edcc8c70b5 100644
--- a/app/graphql/resolvers/release_resolver.rb
+++ b/app/graphql/resolvers/release_resolver.rb
@@ -15,6 +15,8 @@ module Resolvers
end
def resolve(tag_name:)
+ return unless Feature.enabled?(:graphql_release_data, project, default_enabled: true)
+
ReleasesFinder.new(
project,
current_user,
diff --git a/app/graphql/resolvers/releases_resolver.rb b/app/graphql/resolvers/releases_resolver.rb
index b2afbb92684..85892c2abeb 100644
--- a/app/graphql/resolvers/releases_resolver.rb
+++ b/app/graphql/resolvers/releases_resolver.rb
@@ -12,6 +12,8 @@ module Resolvers
end
def resolve(**args)
+ return unless Feature.enabled?(:graphql_release_data, project, default_enabled: true)
+
ReleasesFinder.new(
project,
current_user
diff --git a/app/graphql/types/alert_management/alert_sort_enum.rb b/app/graphql/types/alert_management/alert_sort_enum.rb
index 3faac9ce53c..51e7bef0a7f 100644
--- a/app/graphql/types/alert_management/alert_sort_enum.rb
+++ b/app/graphql/types/alert_management/alert_sort_enum.rb
@@ -16,10 +16,10 @@ module Types
value 'UPDATED_TIME_DESC', 'Created time by descending order', value: :updated_at_desc
value 'EVENT_COUNT_ASC', 'Events count by ascending order', value: :event_count_asc
value 'EVENT_COUNT_DESC', 'Events count by descending order', value: :event_count_desc
- value 'SEVERITY_ASC', 'Severity by ascending order', value: :severity_asc
- value 'SEVERITY_DESC', 'Severity by descending order', value: :severity_desc
- value 'STATUS_ASC', 'Status by ascending order', value: :status_asc
- value 'STATUS_DESC', 'Status by descending order', value: :status_desc
+ value 'SEVERITY_ASC', 'Severity from less critical to more critical', value: :severity_asc
+ value 'SEVERITY_DESC', 'Severity from more critical to less critical', value: :severity_desc
+ value 'STATUS_ASC', 'Status by order: Ignored > Resolved > Acknowledged > Triggered', value: :status_asc
+ value 'STATUS_DESC', 'Status by order: Triggered > Acknowledged > Resolved > Ignored', value: :status_desc
end
end
end
diff --git a/app/graphql/types/alert_management/alert_type.rb b/app/graphql/types/alert_management/alert_type.rb
index 8215ccb152c..089d2426158 100644
--- a/app/graphql/types/alert_management/alert_type.rb
+++ b/app/graphql/types/alert_management/alert_type.rb
@@ -91,6 +91,12 @@ module Types
null: true,
description: 'Assignees of the alert'
+ field :metrics_dashboard_url,
+ GraphQL::STRING_TYPE,
+ null: true,
+ description: 'URL for metrics embed for the alert',
+ resolve: -> (alert, _args, _context) { alert.present.metrics_dashboard_url }
+
def notes
object.ordered_notes
end
diff --git a/app/graphql/types/ci_configuration/sast/analyzers_entity_type.rb b/app/graphql/types/ci_configuration/sast/analyzers_entity_type.rb
new file mode 100644
index 00000000000..ccd1c7dd0eb
--- /dev/null
+++ b/app/graphql/types/ci_configuration/sast/analyzers_entity_type.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+module Types
+ module CiConfiguration
+ module Sast
+ # rubocop: disable Graphql/AuthorizeTypes
+ class AnalyzersEntityType < BaseObject
+ graphql_name 'SastCiConfigurationAnalyzersEntity'
+ description 'Represents an analyzer entity in SAST CI configuration'
+
+ field :name, GraphQL::STRING_TYPE, null: true,
+ description: 'Name of the analyzer.'
+
+ field :label, GraphQL::STRING_TYPE, null: true,
+ description: 'Analyzer label used in the config UI.'
+
+ field :enabled, GraphQL::BOOLEAN_TYPE, null: true,
+ description: 'Indicates whether an analyzer is enabled.'
+
+ field :description, GraphQL::STRING_TYPE, null: true,
+ description: 'Analyzer description that is displayed on the form.'
+ end
+ end
+ end
+end
diff --git a/app/graphql/types/ci_configuration/sast/entity_type.rb b/app/graphql/types/ci_configuration/sast/entity_type.rb
new file mode 100644
index 00000000000..b61b582ad20
--- /dev/null
+++ b/app/graphql/types/ci_configuration/sast/entity_type.rb
@@ -0,0 +1,34 @@
+# frozen_string_literal: true
+
+module Types
+ module CiConfiguration
+ module Sast
+ # rubocop: disable Graphql/AuthorizeTypes
+ class EntityType < BaseObject
+ graphql_name 'SastCiConfigurationEntity'
+ description 'Represents an entity in SAST CI configuration'
+
+ field :field, GraphQL::STRING_TYPE, null: true,
+ description: 'CI keyword of entity.'
+
+ field :label, GraphQL::STRING_TYPE, null: true,
+ description: 'Label for entity used in the form.'
+
+ field :type, GraphQL::STRING_TYPE, null: true,
+ description: 'Type of the field value.'
+
+ field :options, ::Types::CiConfiguration::Sast::OptionsEntityType.connection_type, null: true,
+ description: 'Different possible values of the field.'
+
+ field :default_value, GraphQL::STRING_TYPE, null: true,
+ description: 'Default value that is used if value is empty.'
+
+ field :description, GraphQL::STRING_TYPE, null: true,
+ description: 'Entity description that is displayed on the form.'
+
+ field :value, GraphQL::STRING_TYPE, null: true,
+ description: 'Current value of the entity.'
+ end
+ end
+ end
+end
diff --git a/app/graphql/types/ci_configuration/sast/options_entity_type.rb b/app/graphql/types/ci_configuration/sast/options_entity_type.rb
new file mode 100644
index 00000000000..86d104a7fda
--- /dev/null
+++ b/app/graphql/types/ci_configuration/sast/options_entity_type.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+module Types
+ module CiConfiguration
+ module Sast
+ # rubocop: disable Graphql/AuthorizeTypes
+ class OptionsEntityType < BaseObject
+ graphql_name 'SastCiConfigurationOptionsEntity'
+ description 'Represents an entity for options in SAST CI configuration'
+
+ field :label, GraphQL::STRING_TYPE, null: true,
+ description: 'Label of option entity.'
+
+ field :value, GraphQL::STRING_TYPE, null: true,
+ description: 'Value of option entity.'
+ end
+ end
+ end
+end
diff --git a/app/graphql/types/ci_configuration/sast/type.rb b/app/graphql/types/ci_configuration/sast/type.rb
new file mode 100644
index 00000000000..35d11584ac7
--- /dev/null
+++ b/app/graphql/types/ci_configuration/sast/type.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+module Types
+ module CiConfiguration
+ module Sast
+ # rubocop: disable Graphql/AuthorizeTypes
+ class Type < BaseObject
+ graphql_name 'SastCiConfiguration'
+ description 'Represents a CI configuration of SAST'
+
+ field :global, ::Types::CiConfiguration::Sast::EntityType.connection_type, null: true,
+ description: 'List of global entities related to SAST configuration.'
+
+ field :pipeline, ::Types::CiConfiguration::Sast::EntityType.connection_type, null: true,
+ description: 'List of pipeline entities related to SAST configuration.'
+
+ field :analyzers, ::Types::CiConfiguration::Sast::AnalyzersEntityType.connection_type, null: true,
+ description: 'List of analyzers entities attached to SAST configuration.'
+ end
+ end
+ end
+end
diff --git a/app/graphql/types/container_expiration_policy_type.rb b/app/graphql/types/container_expiration_policy_type.rb
index da53dbcbd39..f19aa964377 100644
--- a/app/graphql/types/container_expiration_policy_type.rb
+++ b/app/graphql/types/container_expiration_policy_type.rb
@@ -14,8 +14,8 @@ module Types
field :older_than, Types::ContainerExpirationPolicyOlderThanEnum, null: true, description: 'Tags older that this will expire'
field :cadence, Types::ContainerExpirationPolicyCadenceEnum, null: false, description: 'This container expiration policy schedule'
field :keep_n, Types::ContainerExpirationPolicyKeepEnum, null: true, description: 'Number of tags to retain'
- field :name_regex, GraphQL::STRING_TYPE, null: true, description: 'Tags with names matching this regex pattern will expire'
- field :name_regex_keep, GraphQL::STRING_TYPE, null: true, description: 'Tags with names matching this regex pattern will be preserved'
+ field :name_regex, Types::UntrustedRegexp, null: true, description: 'Tags with names matching this regex pattern will expire'
+ field :name_regex_keep, Types::UntrustedRegexp, null: true, description: 'Tags with names matching this regex pattern will be preserved'
field :next_run_at, Types::TimeType, null: true, description: 'Next time that this container expiration policy will get executed'
end
end
diff --git a/app/graphql/types/deprecated_mutations.rb b/app/graphql/types/deprecated_mutations.rb
new file mode 100644
index 00000000000..a4336fa3ef3
--- /dev/null
+++ b/app/graphql/types/deprecated_mutations.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+module Types
+ module DeprecatedMutations
+ extend ActiveSupport::Concern
+
+ prepended do
+ mount_aliased_mutation 'AddAwardEmoji',
+ Mutations::AwardEmojis::Add,
+ deprecated: { reason: 'Use awardEmojiAdd', milestone: '13.2' }
+ mount_aliased_mutation 'RemoveAwardEmoji',
+ Mutations::AwardEmojis::Remove,
+ deprecated: { reason: 'Use awardEmojiRemove', milestone: '13.2' }
+ mount_aliased_mutation 'ToggleAwardEmoji',
+ Mutations::AwardEmojis::Toggle,
+ deprecated: { reason: 'Use awardEmojiToggle', milestone: '13.2' }
+ end
+ end
+end
diff --git a/app/graphql/types/diff_stats_summary_type.rb b/app/graphql/types/diff_stats_summary_type.rb
new file mode 100644
index 00000000000..956400fd21b
--- /dev/null
+++ b/app/graphql/types/diff_stats_summary_type.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+module Types
+ # rubocop: disable Graphql/AuthorizeTypes
+ # Types that use DiffStatsType should have their own authorization
+ class DiffStatsSummaryType < BaseObject
+ graphql_name 'DiffStatsSummary'
+
+ description 'Aggregated summary of changes'
+
+ field :additions, GraphQL::INT_TYPE, null: false,
+ description: 'Number of lines added'
+ field :deletions, GraphQL::INT_TYPE, null: false,
+ description: 'Number of lines deleted'
+ field :changes, GraphQL::INT_TYPE, null: false,
+ description: 'Number of lines changed'
+ field :file_count, GraphQL::INT_TYPE, null: false,
+ description: 'Number of files changed'
+
+ def changes
+ object[:additions] + object[:deletions]
+ end
+ end
+ # rubocop: enable Graphql/AuthorizeTypes
+end
diff --git a/app/graphql/types/diff_stats_type.rb b/app/graphql/types/diff_stats_type.rb
new file mode 100644
index 00000000000..6c79a4c389d
--- /dev/null
+++ b/app/graphql/types/diff_stats_type.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+module Types
+ # rubocop: disable Graphql/AuthorizeTypes
+ # Types that use DiffStatsType should have their own authorization
+ class DiffStatsType < BaseObject
+ graphql_name 'DiffStats'
+
+ description 'Changes to a single file'
+
+ field :path, GraphQL::STRING_TYPE, null: false,
+ description: 'File path, relative to repository root'
+ field :additions, GraphQL::INT_TYPE, null: false,
+ description: 'Number of lines added to this file'
+ field :deletions, GraphQL::INT_TYPE, null: false,
+ description: 'Number of lines deleted from this file'
+ end
+ # rubocop: enable Graphql/AuthorizeTypes
+end
diff --git a/app/graphql/types/error_tracking/sentry_detailed_error_type.rb b/app/graphql/types/error_tracking/sentry_detailed_error_type.rb
index 124398f28e7..8bdd8afcbff 100644
--- a/app/graphql/types/error_tracking/sentry_detailed_error_type.rb
+++ b/app/graphql/types/error_tracking/sentry_detailed_error_type.rb
@@ -76,9 +76,15 @@ module Types
description: 'Commit the error was last seen'
field :first_release_short_version, GraphQL::STRING_TYPE,
null: true,
- description: 'Release version the error was first seen'
+ description: 'Release short version the error was first seen'
field :last_release_short_version, GraphQL::STRING_TYPE,
null: true,
+ description: 'Release short version the error was last seen'
+ field :first_release_version, GraphQL::STRING_TYPE,
+ null: true,
+ description: 'Release version the error was first seen'
+ field :last_release_version, GraphQL::STRING_TYPE,
+ null: true,
description: 'Release version the error was last seen'
field :gitlab_commit, GraphQL::STRING_TYPE,
null: true,
diff --git a/app/graphql/types/error_tracking/sentry_error_collection_type.rb b/app/graphql/types/error_tracking/sentry_error_collection_type.rb
index 121146133cb..f423fcb1b9f 100644
--- a/app/graphql/types/error_tracking/sentry_error_collection_type.rb
+++ b/app/graphql/types/error_tracking/sentry_error_collection_type.rb
@@ -17,7 +17,7 @@ module Types
resolver: Resolvers::ErrorTracking::SentryErrorsResolver do
argument :search_term,
String,
- description: 'Search term for the Sentry error.',
+ description: 'Search query for the Sentry error details',
required: false
argument :sort,
String,
diff --git a/app/graphql/types/global_id_type.rb b/app/graphql/types/global_id_type.rb
new file mode 100644
index 00000000000..a3964ba83e1
--- /dev/null
+++ b/app/graphql/types/global_id_type.rb
@@ -0,0 +1,64 @@
+# frozen_string_literal: true
+
+module Types
+ class GlobalIDType < BaseScalar
+ graphql_name 'GlobalID'
+ description 'A global identifier'
+
+ # @param value [GID]
+ # @return [String]
+ def self.coerce_result(value, _ctx)
+ ::Gitlab::GlobalId.as_global_id(value).to_s
+ end
+
+ # @param value [String]
+ # @return [GID]
+ def self.coerce_input(value, _ctx)
+ gid = GlobalID.parse(value)
+ raise GraphQL::CoercionError, "#{value.inspect} is not a valid Global ID" if gid.nil?
+ raise GraphQL::CoercionError, "#{value.inspect} is not a Gitlab Global ID" unless gid.app == GlobalID.app
+
+ gid
+ end
+
+ # Construct a restricted type, that can only be inhabited by an ID of
+ # a given model class.
+ def self.[](model_class)
+ @id_types ||= {}
+
+ @id_types[model_class] ||= Class.new(self) do
+ graphql_name "#{model_class.name.gsub(/::/, '')}ID"
+ description "Identifier of #{model_class.name}"
+
+ self.define_singleton_method(:to_s) do
+ graphql_name
+ end
+
+ self.define_singleton_method(:inspect) do
+ graphql_name
+ end
+
+ self.define_singleton_method(:coerce_result) do |gid, ctx|
+ global_id = ::Gitlab::GlobalId.as_global_id(gid, model_name: model_class.name)
+
+ if suitable?(global_id)
+ global_id.to_s
+ else
+ raise GraphQL::CoercionError, "Expected a #{model_class.name} ID, got #{global_id}"
+ end
+ end
+
+ self.define_singleton_method(:suitable?) do |gid|
+ gid&.model_class&.ancestors&.include?(model_class)
+ end
+
+ self.define_singleton_method(:coerce_input) do |string, ctx|
+ gid = super(string, ctx)
+ raise GraphQL::CoercionError, "#{string.inspect} does not represent an instance of #{model_class.name}" unless suitable?(gid)
+
+ gid
+ end
+ end
+ end
+ end
+end
diff --git a/app/graphql/types/issue_connection_type.rb b/app/graphql/types/issue_connection_type.rb
new file mode 100644
index 00000000000..beed392f01a
--- /dev/null
+++ b/app/graphql/types/issue_connection_type.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+module Types
+ # rubocop: disable Graphql/AuthorizeTypes
+ class IssueConnectionType < GraphQL::Types::Relay::BaseConnection
+ field :count, Integer, null: false,
+ description: 'Total count of collection'
+
+ def count
+ object.items.size
+ end
+ end
+end
diff --git a/app/graphql/types/issue_type.rb b/app/graphql/types/issue_type.rb
index 73219ca9e1e..9baa0018999 100644
--- a/app/graphql/types/issue_type.rb
+++ b/app/graphql/types/issue_type.rb
@@ -4,6 +4,8 @@ module Types
class IssueType < BaseObject
graphql_name 'Issue'
+ connection_type_class(Types::IssueConnectionType)
+
implements(Types::Notes::NoteableType)
authorize :read_issue
@@ -12,6 +14,8 @@ module Types
present_using IssuePresenter
+ field :id, GraphQL::ID_TYPE, null: false,
+ description: "ID of the issue"
field :iid, GraphQL::ID_TYPE, null: false,
description: "Internal ID of the issue"
field :title, GraphQL::STRING_TYPE, null: false,
diff --git a/app/graphql/types/jira_user_type.rb b/app/graphql/types/jira_user_type.rb
index 8aa21ce669b..999526a920e 100644
--- a/app/graphql/types/jira_user_type.rb
+++ b/app/graphql/types/jira_user_type.rb
@@ -13,7 +13,11 @@ module Types
field :jira_email, GraphQL::STRING_TYPE, null: true,
description: 'Email of the Jira user, returned only for users with public emails'
field :gitlab_id, GraphQL::INT_TYPE, null: true,
- description: 'Id of the matched GitLab user'
+ description: 'ID of the matched GitLab user'
+ field :gitlab_username, GraphQL::STRING_TYPE, null: true,
+ description: 'Username of the matched GitLab user'
+ field :gitlab_name, GraphQL::STRING_TYPE, null: true,
+ description: 'Name of the matched GitLab user'
end
# rubocop: enable Graphql/AuthorizeTypes
end
diff --git a/app/graphql/types/jira_users_mapping_input_type.rb b/app/graphql/types/jira_users_mapping_input_type.rb
new file mode 100644
index 00000000000..61cf1474493
--- /dev/null
+++ b/app/graphql/types/jira_users_mapping_input_type.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+module Types
+ # rubocop: disable Graphql/AuthorizeTypes
+ class JiraUsersMappingInputType < BaseInputObject
+ graphql_name 'JiraUsersMappingInputType'
+
+ argument :jira_account_id,
+ GraphQL::STRING_TYPE,
+ required: true,
+ description: 'Jira account id of the user'
+ argument :gitlab_id,
+ GraphQL::INT_TYPE,
+ required: false,
+ description: 'Id of the GitLab user'
+ end
+ # rubocop: enable Graphql/AuthorizeTypes
+end
diff --git a/app/graphql/types/merge_request_type.rb b/app/graphql/types/merge_request_type.rb
index cb4ff7ea0c5..c194b467363 100644
--- a/app/graphql/types/merge_request_type.rb
+++ b/app/graphql/types/merge_request_type.rb
@@ -54,6 +54,13 @@ module Types
description: 'Indicates if the merge has been set to be merged when its pipeline succeeds (MWPS)'
field :diff_head_sha, GraphQL::STRING_TYPE, null: true,
description: 'Diff head SHA of the merge request'
+ field :diff_stats, [Types::DiffStatsType], null: true, calls_gitaly: true,
+ description: 'Details about which files were changed in this merge request' do
+ argument :path, GraphQL::STRING_TYPE, required: false, description: 'A specific file-path'
+ end
+
+ field :diff_stats_summary, Types::DiffStatsSummaryType, null: true, calls_gitaly: true,
+ description: 'Summary of which files were changed in this merge request'
field :merge_commit_sha, GraphQL::STRING_TYPE, null: true,
description: 'SHA of the merge request commit (set once merged)'
field :user_notes_count, GraphQL::INT_TYPE, null: true,
@@ -134,5 +141,24 @@ module Types
end
field :task_completion_status, Types::TaskCompletionStatus, null: false,
description: Types::TaskCompletionStatus.description
+
+ def diff_stats(path: nil)
+ stats = Array.wrap(object.diff_stats&.to_a)
+
+ if path.present?
+ stats.select { |s| s.path == path }
+ else
+ stats
+ end
+ end
+
+ def diff_stats_summary
+ nil_stats = { additions: 0, deletions: 0, file_count: 0 }
+ return nil_stats unless object.diff_stats.present?
+
+ object.diff_stats.each_with_object(nil_stats) do |status, hash|
+ hash.merge!(additions: status.additions, deletions: status.deletions, file_count: 1) { |_, x, y| x + y }
+ end
+ end
end
end
diff --git a/app/graphql/types/milestone_stats_type.rb b/app/graphql/types/milestone_stats_type.rb
new file mode 100644
index 00000000000..ef533af59e7
--- /dev/null
+++ b/app/graphql/types/milestone_stats_type.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+module Types
+ class MilestoneStatsType < BaseObject
+ graphql_name 'MilestoneStats'
+ description 'Contains statistics about a milestone'
+
+ authorize :read_milestone
+
+ field :total_issues_count, GraphQL::INT_TYPE, null: true,
+ description: 'Total number of issues associated with the milestone'
+
+ field :closed_issues_count, GraphQL::INT_TYPE, null: true,
+ description: 'Number of closed issues associated with the milestone'
+ end
+end
diff --git a/app/graphql/types/milestone_type.rb b/app/graphql/types/milestone_type.rb
index 99bd6e819d6..ca606c9da44 100644
--- a/app/graphql/types/milestone_type.rb
+++ b/app/graphql/types/milestone_type.rb
@@ -9,6 +9,8 @@ module Types
authorize :read_milestone
+ alias_method :milestone, :object
+
field :id, GraphQL::ID_TYPE, null: false,
description: 'ID of the milestone'
@@ -47,5 +49,14 @@ module Types
field :subgroup_milestone, GraphQL::BOOLEAN_TYPE, null: false,
description: 'Indicates if milestone is at subgroup level',
method: :subgroup_milestone?
+
+ field :stats, Types::MilestoneStatsType, null: true,
+ description: 'Milestone statistics'
+
+ def stats
+ return unless Feature.enabled?(:graphql_milestone_stats, milestone.project || milestone.group, default_enabled: true)
+
+ milestone
+ end
end
end
diff --git a/app/graphql/types/mutation_type.rb b/app/graphql/types/mutation_type.rb
index 8874c56dfdb..49d51b626b2 100644
--- a/app/graphql/types/mutation_type.rb
+++ b/app/graphql/types/mutation_type.rb
@@ -10,6 +10,7 @@ module Types
mount_mutation Mutations::AlertManagement::CreateAlertIssue
mount_mutation Mutations::AlertManagement::UpdateAlertStatus
mount_mutation Mutations::AlertManagement::Alerts::SetAssignees
+ mount_mutation Mutations::AlertManagement::Alerts::Todo::Create
mount_mutation Mutations::AwardEmojis::Add
mount_mutation Mutations::AwardEmojis::Remove
mount_mutation Mutations::AwardEmojis::Toggle
@@ -17,9 +18,11 @@ module Types
mount_mutation Mutations::Commits::Create, calls_gitaly: true
mount_mutation Mutations::Discussions::ToggleResolve
mount_mutation Mutations::Issues::SetConfidential
+ mount_mutation Mutations::Issues::SetLocked
mount_mutation Mutations::Issues::SetDueDate
mount_mutation Mutations::Issues::Update
mount_mutation Mutations::MergeRequests::Create
+ mount_mutation Mutations::MergeRequests::Update
mount_mutation Mutations::MergeRequests::SetLabels
mount_mutation Mutations::MergeRequests::SetLocked
mount_mutation Mutations::MergeRequests::SetMilestone
@@ -56,4 +59,5 @@ module Types
end
end
+::Types::MutationType.prepend(::Types::DeprecatedMutations)
::Types::MutationType.prepend_if_ee('::EE::Types::MutationType')
diff --git a/app/graphql/types/namespace_type.rb b/app/graphql/types/namespace_type.rb
index 1714284a5cf..fbdf049b755 100644
--- a/app/graphql/types/namespace_type.rb
+++ b/app/graphql/types/namespace_type.rb
@@ -38,3 +38,5 @@ module Types
resolver: ::Resolvers::NamespaceProjectsResolver
end
end
+
+Types::NamespaceType.prepend_if_ee('EE::Types::NamespaceType')
diff --git a/app/graphql/types/notes/note_type.rb b/app/graphql/types/notes/note_type.rb
index 8755b4ccad5..5d41f0032bd 100644
--- a/app/graphql/types/notes/note_type.rb
+++ b/app/graphql/types/notes/note_type.rb
@@ -27,6 +27,8 @@ module Types
field :system, GraphQL::BOOLEAN_TYPE,
null: false,
description: 'Indicates whether this note was created by the system or by a user'
+ field :system_note_icon_name, GraphQL::STRING_TYPE, null: true,
+ description: 'Name of the icon corresponding to a system note'
field :body, GraphQL::STRING_TYPE,
null: false,
@@ -46,6 +48,10 @@ module Types
field :confidential, GraphQL::BOOLEAN_TYPE, null: true,
description: 'Indicates if this note is confidential',
method: :confidential?
+
+ def system_note_icon_name
+ SystemNoteHelper.system_note_icon_name(object) if object.system?
+ end
end
end
end
diff --git a/app/graphql/types/package_type.rb b/app/graphql/types/package_type.rb
new file mode 100644
index 00000000000..0604bf827a5
--- /dev/null
+++ b/app/graphql/types/package_type.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+module Types
+ class PackageType < BaseObject
+ graphql_name 'Package'
+ description 'Represents a package'
+ authorize :read_package
+
+ field :id, GraphQL::ID_TYPE, null: false, description: 'The ID of the package'
+ field :name, GraphQL::STRING_TYPE, null: false, description: 'The name of the package'
+ field :created_at, Types::TimeType, null: false, description: 'The created date'
+ field :updated_at, Types::TimeType, null: false, description: 'The update date'
+ field :version, GraphQL::STRING_TYPE, null: true, description: 'The version of the package'
+ field :package_type, Types::PackageTypeEnum, null: false, description: 'The type of the package'
+ end
+end
diff --git a/app/graphql/types/package_type_enum.rb b/app/graphql/types/package_type_enum.rb
new file mode 100644
index 00000000000..bc03b8f5f8b
--- /dev/null
+++ b/app/graphql/types/package_type_enum.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+module Types
+ class PackageTypeEnum < BaseEnum
+ ::Packages::Package.package_types.keys.each do |package_type|
+ value package_type.to_s.upcase, "Packages from the #{package_type} package manager", value: package_type.to_s
+ end
+ end
+end
diff --git a/app/graphql/types/project_statistics_type.rb b/app/graphql/types/project_statistics_type.rb
index e1546d31e89..b3916e42e92 100644
--- a/app/graphql/types/project_statistics_type.rb
+++ b/app/graphql/types/project_statistics_type.rb
@@ -21,5 +21,7 @@ module Types
description: 'Packages size of the project'
field :wiki_size, GraphQL::FLOAT_TYPE, null: true,
description: 'Wiki size of the project'
+ field :snippets_size, GraphQL::FLOAT_TYPE, null: true,
+ description: 'Snippets size of the project'
end
end
diff --git a/app/graphql/types/project_type.rb b/app/graphql/types/project_type.rb
index bbfb7fc4f20..2251a0f4e0c 100644
--- a/app/graphql/types/project_type.rb
+++ b/app/graphql/types/project_type.rb
@@ -60,6 +60,12 @@ module Types
field :merge_requests_ff_only_enabled, GraphQL::BOOLEAN_TYPE, null: true,
description: 'Indicates if no merge commits should be created and all merges should instead be fast-forwarded, which means that merging is only allowed if the branch could be fast-forwarded.'
+ field :service_desk_enabled, GraphQL::BOOLEAN_TYPE, null: true,
+ description: 'Indicates if the project has service desk enabled.'
+
+ field :service_desk_address, GraphQL::STRING_TYPE, null: true,
+ description: 'E-mail address of the service desk.'
+
field :avatar_url, GraphQL::STRING_TYPE, null: true, calls_gitaly: true,
description: 'URL to avatar image file of the project',
resolve: -> (project, args, ctx) do
@@ -153,12 +159,20 @@ module Types
description: 'Environments of the project',
resolver: Resolvers::EnvironmentsResolver
+ field :sast_ci_configuration, ::Types::CiConfiguration::Sast::Type, null: true,
+ description: 'SAST CI configuration for the project',
+ resolver: ::Resolvers::CiConfiguration::SastResolver
+
field :issue,
Types::IssueType,
null: true,
description: 'A single issue of the project',
resolver: Resolvers::IssuesResolver.single
+ field :packages, Types::PackageType.connection_type, null: true,
+ description: 'Packages of the project',
+ resolver: Resolvers::PackagesResolver
+
field :pipelines,
Types::Ci::PipelineType.connection_type,
null: true,
@@ -243,15 +257,14 @@ module Types
Types::ReleaseType.connection_type,
null: true,
description: 'Releases of the project',
- resolver: Resolvers::ReleasesResolver,
- feature_flag: :graphql_release_data
+ resolver: Resolvers::ReleasesResolver
field :release,
Types::ReleaseType,
null: true,
description: 'A single release of the project',
resolver: Resolvers::ReleasesResolver.single,
- feature_flag: :graphql_release_data
+ authorize: :download_code
field :container_expiration_policy,
Types::ContainerExpirationPolicyType,
diff --git a/app/graphql/types/projects/services/jira_service_type.rb b/app/graphql/types/projects/services/jira_service_type.rb
index e81963f752d..8bf85a14cbf 100644
--- a/app/graphql/types/projects/services/jira_service_type.rb
+++ b/app/graphql/types/projects/services/jira_service_type.rb
@@ -15,7 +15,7 @@ module Types
null: true,
connection: false,
extensions: [Gitlab::Graphql::Extensions::ExternallyPaginatedArrayExtension],
- description: 'List of Jira projects fetched through Jira REST API',
+ description: 'List of all Jira projects fetched through Jira REST API',
resolver: Resolvers::Projects::JiraProjectsResolver
end
end
diff --git a/app/graphql/types/query_type.rb b/app/graphql/types/query_type.rb
index 362e4004b73..b4cbd96bfdb 100644
--- a/app/graphql/types/query_type.rb
+++ b/app/graphql/types/query_type.rb
@@ -61,10 +61,6 @@ module Types
description: 'Text to echo back',
resolver: Resolvers::EchoResolver
- field :user, Types::UserType, null: true,
- description: 'Find a user on this instance',
- resolver: Resolvers::UserResolver
-
def design_management
DesignManagementObject.new(nil)
end
diff --git a/app/graphql/types/release_asset_link_type.rb b/app/graphql/types/release_asset_link_type.rb
new file mode 100644
index 00000000000..21f1bd50cff
--- /dev/null
+++ b/app/graphql/types/release_asset_link_type.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+module Types
+ class ReleaseAssetLinkType < BaseObject
+ graphql_name 'ReleaseAssetLink'
+ description 'Represents an asset link associated with a release'
+
+ authorize :read_release
+
+ field :id, GraphQL::ID_TYPE, null: false,
+ description: 'ID of the link'
+ field :name, GraphQL::STRING_TYPE, null: true,
+ description: 'Name of the link'
+ field :url, GraphQL::STRING_TYPE, null: true,
+ description: 'URL of the link'
+ field :link_type, Types::ReleaseAssetLinkTypeEnum, null: true,
+ description: 'Type of the link: `other`, `runbook`, `image`, `package`; defaults to `other`'
+ field :external, GraphQL::BOOLEAN_TYPE, null: true, method: :external?,
+ description: 'Indicates the link points to an external resource'
+ end
+end
diff --git a/app/graphql/types/release_asset_link_type_enum.rb b/app/graphql/types/release_asset_link_type_enum.rb
new file mode 100644
index 00000000000..01862ada56d
--- /dev/null
+++ b/app/graphql/types/release_asset_link_type_enum.rb
@@ -0,0 +1,12 @@
+# frozen_string_literal: true
+
+module Types
+ class ReleaseAssetLinkTypeEnum < BaseEnum
+ graphql_name 'ReleaseAssetLinkType'
+ description 'Type of the link: `other`, `runbook`, `image`, `package`; defaults to `other`'
+
+ ::Releases::Link.link_types.keys.each do |link_type|
+ value link_type.upcase, value: link_type, description: "#{link_type.titleize} link type"
+ end
+ end
+end
diff --git a/app/graphql/types/release_assets_type.rb b/app/graphql/types/release_assets_type.rb
index 58ad05b5365..d6042bdbc0b 100644
--- a/app/graphql/types/release_assets_type.rb
+++ b/app/graphql/types/release_assets_type.rb
@@ -3,6 +3,7 @@
module Types
class ReleaseAssetsType < BaseObject
graphql_name 'ReleaseAssets'
+ description 'A container for all assets associated with a release'
authorize :read_release
@@ -10,9 +11,9 @@ module Types
present_using ReleasePresenter
- field :assets_count, GraphQL::INT_TYPE, null: true,
+ field :count, GraphQL::INT_TYPE, null: true, method: :assets_count,
description: 'Number of assets of the release'
- field :links, Types::ReleaseLinkType.connection_type, null: true,
+ field :links, Types::ReleaseAssetLinkType.connection_type, null: true,
description: 'Asset links of the release'
field :sources, Types::ReleaseSourceType.connection_type, null: true,
description: 'Sources of the release'
diff --git a/app/graphql/types/release_link_type.rb b/app/graphql/types/release_link_type.rb
deleted file mode 100644
index 070f14a90df..00000000000
--- a/app/graphql/types/release_link_type.rb
+++ /dev/null
@@ -1,20 +0,0 @@
-# frozen_string_literal: true
-
-module Types
- class ReleaseLinkType < BaseObject
- graphql_name 'ReleaseLink'
-
- authorize :read_release
-
- field :id, GraphQL::ID_TYPE, null: false,
- description: 'ID of the link'
- field :name, GraphQL::STRING_TYPE, null: true,
- description: 'Name of the link'
- field :url, GraphQL::STRING_TYPE, null: true,
- description: 'URL of the link'
- field :link_type, Types::ReleaseLinkTypeEnum, null: true,
- description: 'Type of the link: `other`, `runbook`, `image`, `package`; defaults to `other`'
- field :external, GraphQL::BOOLEAN_TYPE, null: true, method: :external?,
- description: 'Indicates the link points to an external resource'
- end
-end
diff --git a/app/graphql/types/release_link_type_enum.rb b/app/graphql/types/release_link_type_enum.rb
deleted file mode 100644
index b364855833f..00000000000
--- a/app/graphql/types/release_link_type_enum.rb
+++ /dev/null
@@ -1,12 +0,0 @@
-# frozen_string_literal: true
-
-module Types
- class ReleaseLinkTypeEnum < BaseEnum
- graphql_name 'ReleaseLinkType'
- description 'Type of the link: `other`, `runbook`, `image`, `package`; defaults to `other`'
-
- ::Releases::Link.link_types.keys.each do |link_type|
- value link_type.upcase, value: link_type, description: "#{link_type.titleize} link type"
- end
- end
-end
diff --git a/app/graphql/types/release_links_type.rb b/app/graphql/types/release_links_type.rb
new file mode 100644
index 00000000000..f61a16f5b67
--- /dev/null
+++ b/app/graphql/types/release_links_type.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+module Types
+ class ReleaseLinksType < BaseObject
+ graphql_name 'ReleaseLinks'
+
+ authorize :download_code
+
+ alias_method :release, :object
+
+ present_using ReleasePresenter
+
+ field :self_url, GraphQL::STRING_TYPE, null: true,
+ description: 'HTTP URL of the release'
+ field :merge_requests_url, GraphQL::STRING_TYPE, null: true,
+ description: 'HTTP URL of the merge request page filtered by this release'
+ field :issues_url, GraphQL::STRING_TYPE, null: true,
+ description: 'HTTP URL of the issues page filtered by this release'
+ field :edit_url, GraphQL::STRING_TYPE, null: true,
+ description: "HTTP URL of the release's edit page",
+ authorize: :update_release
+ end
+end
diff --git a/app/graphql/types/release_source_type.rb b/app/graphql/types/release_source_type.rb
index 0ec1ad85a39..891da472116 100644
--- a/app/graphql/types/release_source_type.rb
+++ b/app/graphql/types/release_source_type.rb
@@ -3,8 +3,9 @@
module Types
class ReleaseSourceType < BaseObject
graphql_name 'ReleaseSource'
+ description 'Represents the source code attached to a release in a particular format'
- authorize :read_release_sources
+ authorize :download_code
field :format, GraphQL::STRING_TYPE, null: true,
description: 'Format of the source'
diff --git a/app/graphql/types/release_type.rb b/app/graphql/types/release_type.rb
index 3d8e5a93c68..a0703b96a36 100644
--- a/app/graphql/types/release_type.rb
+++ b/app/graphql/types/release_type.rb
@@ -3,6 +3,7 @@
module Types
class ReleaseType < BaseObject
graphql_name 'Release'
+ description 'Represents a release'
authorize :read_release
@@ -10,10 +11,12 @@ module Types
present_using ReleasePresenter
- field :tag_name, GraphQL::STRING_TYPE, null: false, method: :tag,
- description: 'Name of the tag associated with the release'
+ field :tag_name, GraphQL::STRING_TYPE, null: true, method: :tag,
+ description: 'Name of the tag associated with the release',
+ authorize: :download_code
field :tag_path, GraphQL::STRING_TYPE, null: true,
- description: 'Relative web path to the tag associated with the release'
+ description: 'Relative web path to the tag associated with the release',
+ authorize: :download_code
field :description, GraphQL::STRING_TYPE, null: true,
description: 'Description (also known as "release notes") of the release'
markdown_field :description_html, null: true
@@ -25,6 +28,8 @@ module Types
description: 'Timestamp of when the release was released'
field :assets, Types::ReleaseAssetsType, null: true, method: :itself,
description: 'Assets of the release'
+ field :links, Types::ReleaseLinksType, null: true, method: :itself,
+ description: 'Links of the release'
field :milestones, Types::MilestoneType.connection_type, null: true,
description: 'Milestones associated to the release'
field :evidences, Types::EvidenceType.connection_type, null: true,
@@ -39,8 +44,7 @@ module Types
field :commit, Types::CommitType, null: true,
complexity: 10, calls_gitaly: true,
- description: 'The commit associated with the release',
- authorize: :reporter_access
+ description: 'The commit associated with the release'
def commit
return if release.sha.nil?
diff --git a/app/graphql/types/root_storage_statistics_type.rb b/app/graphql/types/root_storage_statistics_type.rb
index e2d85aebc48..3acc1d9ca44 100644
--- a/app/graphql/types/root_storage_statistics_type.rb
+++ b/app/graphql/types/root_storage_statistics_type.rb
@@ -12,5 +12,6 @@ module Types
field :build_artifacts_size, GraphQL::FLOAT_TYPE, null: false, description: 'The CI artifacts size in bytes'
field :packages_size, GraphQL::FLOAT_TYPE, null: false, description: 'The packages size in bytes'
field :wiki_size, GraphQL::FLOAT_TYPE, null: false, description: 'The wiki size in bytes'
+ field :snippets_size, GraphQL::FLOAT_TYPE, null: false, description: 'The snippets size in bytes'
end
end
diff --git a/app/graphql/types/todo_target_enum.rb b/app/graphql/types/todo_target_enum.rb
index a377c3aafdc..b797722fef8 100644
--- a/app/graphql/types/todo_target_enum.rb
+++ b/app/graphql/types/todo_target_enum.rb
@@ -6,6 +6,7 @@ module Types
value 'ISSUE', value: 'Issue', description: 'An Issue'
value 'MERGEREQUEST', value: 'MergeRequest', description: 'A MergeRequest'
value 'DESIGN', value: 'DesignManagement::Design', description: 'A Design'
+ value 'ALERT', value: 'AlertManagement::Alert', description: 'An Alert'
end
end
diff --git a/app/graphql/types/tree/blob_type.rb b/app/graphql/types/tree/blob_type.rb
index 22349203519..36cae756a0d 100644
--- a/app/graphql/types/tree/blob_type.rb
+++ b/app/graphql/types/tree/blob_type.rb
@@ -17,6 +17,8 @@ module Types
resolve: -> (blob, args, ctx) do
Gitlab::Graphql::Loaders::BatchLfsOidLoader.new(blob.repository, blob.id).find
end
+ field :mode, GraphQL::STRING_TYPE, null: true,
+ description: 'Blob mode in numeric format'
# rubocop: enable Graphql/AuthorizeTypes
end
end
diff --git a/app/graphql/types/untrusted_regexp.rb b/app/graphql/types/untrusted_regexp.rb
new file mode 100644
index 00000000000..2c715ab4967
--- /dev/null
+++ b/app/graphql/types/untrusted_regexp.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+module Types
+ class UntrustedRegexp < Types::BaseScalar
+ description 'A regexp containing patterns sourced from user input'
+
+ def self.coerce_input(input_value, _)
+ return unless input_value
+
+ Gitlab::UntrustedRegexp.new(input_value)
+
+ input_value
+ rescue RegexpError => e
+ message = "#{input_value} is an invalid regexp: #{e.message}"
+ raise GraphQL::CoercionError, message
+ end
+
+ def self.coerce_result(ruby_value, _)
+ ruby_value.to_s
+ end
+ end
+end
diff --git a/app/helpers/analytics/unique_visits_helper.rb b/app/helpers/analytics/unique_visits_helper.rb
new file mode 100644
index 00000000000..ded7f54e44e
--- /dev/null
+++ b/app/helpers/analytics/unique_visits_helper.rb
@@ -0,0 +1,32 @@
+# frozen_string_literal: true
+
+module Analytics
+ module UniqueVisitsHelper
+ extend ActiveSupport::Concern
+
+ def visitor_id
+ return cookies[:visitor_id] if cookies[:visitor_id].present?
+ return unless current_user
+
+ uuid = SecureRandom.uuid
+ cookies[:visitor_id] = { value: uuid, expires: 24.months }
+ uuid
+ end
+
+ def track_visit(target_id)
+ return unless Feature.enabled?(:track_unique_visits)
+ return unless Gitlab::CurrentSettings.usage_ping_enabled?
+ return unless visitor_id
+
+ Gitlab::Analytics::UniqueVisits.new.track_visit(visitor_id, target_id)
+ end
+
+ class_methods do
+ def track_unique_visits(controller_actions, target_id:)
+ after_action only: controller_actions, if: -> { request.format.html? && request.headers['DNT'] != '1' } do
+ track_visit(target_id)
+ end
+ end
+ end
+ end
+end
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index bdfdf5a69b3..e8bd5ad9b9b 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -335,6 +335,15 @@ module ApplicationHelper
}
end
+ def page_startup_api_calls
+ @api_startup_calls
+ end
+
+ def add_page_startup_api_call(api_path, options: {})
+ @api_startup_calls ||= {}
+ @api_startup_calls[api_path] = options
+ end
+
def autocomplete_data_sources(object, noteable_type)
return {} unless object && noteable_type
diff --git a/app/helpers/application_settings_helper.rb b/app/helpers/application_settings_helper.rb
index e709d15a946..aa118a9bc45 100644
--- a/app/helpers/application_settings_helper.rb
+++ b/app/helpers/application_settings_helper.rb
@@ -190,6 +190,7 @@ module ApplicationSettingsHelper
:container_expiration_policies_enable_historic_entries,
:container_registry_token_expire_delay,
:default_artifacts_expire_in,
+ :default_branch_name,
:default_branch_protection,
:default_ci_config_path,
:default_group_visibility,
@@ -244,6 +245,7 @@ module ApplicationSettingsHelper
:metrics_method_call_threshold,
:minimum_password_length,
:mirror_available,
+ :notify_on_unknown_sign_in,
:pages_domain_verification_enabled,
:password_authentication_enabled_for_web,
:password_authentication_enabled_for_git,
@@ -319,7 +321,13 @@ module ApplicationSettingsHelper
:email_restrictions_enabled,
:email_restrictions,
:issues_create_limit,
- :raw_blob_request_limit
+ :raw_blob_request_limit,
+ :project_import_limit,
+ :project_export_limit,
+ :project_download_export_limit,
+ :group_import_limit,
+ :group_export_limit,
+ :group_download_export_limit
]
end
diff --git a/app/helpers/auto_devops_helper.rb b/app/helpers/auto_devops_helper.rb
index 0f14680607e..c27f5d4ebce 100644
--- a/app/helpers/auto_devops_helper.rb
+++ b/app/helpers/auto_devops_helper.rb
@@ -22,4 +22,8 @@ module AutoDevopsHelper
s_('CICD|instance enabled')
end
end
+
+ def auto_devops_settings_path(project)
+ project_settings_ci_cd_path(project, anchor: 'autodevops-settings')
+ end
end
diff --git a/app/helpers/blob_helper.rb b/app/helpers/blob_helper.rb
index 69fe3303840..f4238e7711a 100644
--- a/app/helpers/blob_helper.rb
+++ b/app/helpers/blob_helper.rb
@@ -52,13 +52,12 @@ module BlobHelper
edit_button_tag(blob,
common_classes,
_('Edit'),
- Feature.enabled?(:web_ide_default) ? ide_edit_path(project, ref, path) : edit_blob_path(project, ref, path, options),
+ edit_blob_path(project, ref, path, options),
project,
ref)
end
def ide_edit_button(project = @project, ref = @ref, path = @path, blob:)
- return if Feature.enabled?(:web_ide_default)
return unless blob
edit_button_tag(blob,
diff --git a/app/helpers/builds_helper.rb b/app/helpers/builds_helper.rb
deleted file mode 100644
index 2def3488184..00000000000
--- a/app/helpers/builds_helper.rb
+++ /dev/null
@@ -1,38 +0,0 @@
-# frozen_string_literal: true
-
-module BuildsHelper
- def build_summary(build, skip: false)
- if build.has_trace?
- if skip
- link_to _("View job log"), pipeline_job_url(build.pipeline, build)
- else
- build.trace.html(last_lines: 10).html_safe
- end
- else
- _("No job log")
- end
- end
-
- def sidebar_build_class(build, current_build)
- build_class = []
- build_class << 'active' if build.id === current_build.id
- build_class << 'retried' if build.retried?
- build_class.join(' ')
- end
-
- def javascript_build_options
- {
- page_path: project_job_path(@project, @build),
- build_status: @build.status,
- build_stage: @build.stage,
- log_state: ''
- }
- end
-
- def build_failed_issue_options
- {
- title: _("Job Failed #%{build_id}") % { build_id: @build.id },
- description: project_job_url(@project, @build)
- }
- end
-end
diff --git a/app/helpers/ci/builds_helper.rb b/app/helpers/ci/builds_helper.rb
new file mode 100644
index 00000000000..bfdb830f2c3
--- /dev/null
+++ b/app/helpers/ci/builds_helper.rb
@@ -0,0 +1,40 @@
+# frozen_string_literal: true
+
+module Ci
+ module BuildsHelper
+ def build_summary(build, skip: false)
+ if build.has_trace?
+ if skip
+ link_to _('View job log'), pipeline_job_url(build.pipeline, build)
+ else
+ build.trace.html(last_lines: 10).html_safe
+ end
+ else
+ _('No job log')
+ end
+ end
+
+ def sidebar_build_class(build, current_build)
+ build_class = []
+ build_class << 'active' if build.id === current_build.id
+ build_class << 'retried' if build.retried?
+ build_class.join(' ')
+ end
+
+ def javascript_build_options
+ {
+ page_path: project_job_path(@project, @build),
+ build_status: @build.status,
+ build_stage: @build.stage,
+ log_state: ''
+ }
+ end
+
+ def build_failed_issue_options
+ {
+ title: _("Job Failed #%{build_id}") % { build_id: @build.id },
+ description: project_job_url(@project, @build)
+ }
+ end
+ end
+end
diff --git a/app/helpers/ci/jobs_helper.rb b/app/helpers/ci/jobs_helper.rb
new file mode 100644
index 00000000000..0344413b849
--- /dev/null
+++ b/app/helpers/ci/jobs_helper.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+module Ci
+ module JobsHelper
+ def jobs_data
+ {
+ "endpoint" => project_job_path(@project, @build, format: :json),
+ "project_path" => @project.full_path,
+ "deployment_help_url" => help_page_path('user/project/clusters/index.html', anchor: 'troubleshooting'),
+ "runner_help_url" => help_page_path('ci/runners/README.html', anchor: 'set-maximum-job-timeout-for-a-runner'),
+ "runner_settings_url" => project_runners_path(@build.project, anchor: 'js-runners-settings'),
+ "variables_settings_url" => project_variables_path(@build.project, anchor: 'js-cicd-variables-settings'),
+ "page_path" => project_job_path(@project, @build),
+ "build_status" => @build.status,
+ "build_stage" => @build.stage,
+ "log_state" => '',
+ "build_options" => javascript_build_options
+ }
+ end
+ end
+end
+
+Ci::JobsHelper.prepend_if_ee('::EE::Ci::JobsHelper')
diff --git a/app/helpers/ci/pipeline_schedules_helper.rb b/app/helpers/ci/pipeline_schedules_helper.rb
new file mode 100644
index 00000000000..20e5c90a60e
--- /dev/null
+++ b/app/helpers/ci/pipeline_schedules_helper.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+module Ci
+ module PipelineSchedulesHelper
+ def timezone_data
+ ActiveSupport::TimeZone.all.map do |timezone|
+ {
+ name: timezone.name,
+ offset: timezone.now.utc_offset,
+ identifier: timezone.tzinfo.identifier
+ }
+ end
+ end
+ end
+end
diff --git a/app/helpers/ci/runners_helper.rb b/app/helpers/ci/runners_helper.rb
new file mode 100644
index 00000000000..8cdb28b2874
--- /dev/null
+++ b/app/helpers/ci/runners_helper.rb
@@ -0,0 +1,45 @@
+# frozen_string_literal: true
+
+module Ci
+ module RunnersHelper
+ def runner_status_icon(runner)
+ status = runner.status
+ case status
+ when :not_connected
+ content_tag :i, nil,
+ class: "fa fa-warning",
+ title: "New runner. Has not connected yet"
+
+ when :online, :offline, :paused
+ content_tag :i, nil,
+ class: "fa fa-circle runner-status-#{status}",
+ title: "Runner is #{status}, last contact was #{time_ago_in_words(runner.contacted_at)} ago"
+ end
+ end
+
+ def runner_link(runner)
+ display_name = truncate(runner.display_name, length: 15)
+ id = "\##{runner.id}"
+
+ if current_user && current_user.admin
+ link_to admin_runner_path(runner) do
+ display_name + id
+ end
+ else
+ display_name + id
+ end
+ end
+
+ # Due to inability of performing sorting of runners by cached "contacted_at" values we have to show uncached values if sorting by "contacted_asc" is requested.
+ # Please refer to the following issue for more details: https://gitlab.com/gitlab-org/gitlab-foss/issues/55920
+ def runner_contacted_at(runner)
+ if params[:sort] == 'contacted_asc'
+ runner.uncached_contacted_at
+ else
+ runner.contacted_at
+ end
+ end
+ end
+end
+
+Ci::RunnersHelper.prepend_if_ee('EE::Ci::RunnersHelper')
diff --git a/app/helpers/ci/status_helper.rb b/app/helpers/ci/status_helper.rb
new file mode 100644
index 00000000000..bca49324a19
--- /dev/null
+++ b/app/helpers/ci/status_helper.rb
@@ -0,0 +1,148 @@
+# frozen_string_literal: true
+
+##
+# DEPRECATED
+#
+# These helpers are deprecated in favor of detailed CI/CD statuses.
+#
+# See 'detailed_status?` method and `Gitlab::Ci::Status` module.
+#
+module Ci
+ module StatusHelper
+ def ci_label_for_status(status)
+ if detailed_status?(status)
+ return status.label
+ end
+
+ label = case status
+ when 'success'
+ 'passed'
+ when 'success-with-warnings'
+ 'passed with warnings'
+ when 'manual'
+ 'waiting for manual action'
+ when 'scheduled'
+ 'waiting for delayed job'
+ else
+ status
+ end
+ translation = "CiStatusLabel|#{label}"
+ s_(translation)
+ end
+
+ def ci_text_for_status(status)
+ if detailed_status?(status)
+ return status.text
+ end
+
+ case status
+ when 'success'
+ s_('CiStatusText|passed')
+ when 'success-with-warnings'
+ s_('CiStatusText|passed')
+ when 'manual'
+ s_('CiStatusText|blocked')
+ when 'scheduled'
+ s_('CiStatusText|delayed')
+ else
+ # All states are already being translated inside the detailed statuses:
+ # :running => Gitlab::Ci::Status::Running
+ # :skipped => Gitlab::Ci::Status::Skipped
+ # :failed => Gitlab::Ci::Status::Failed
+ # :success => Gitlab::Ci::Status::Success
+ # :canceled => Gitlab::Ci::Status::Canceled
+ # The following states are customized above:
+ # :manual => Gitlab::Ci::Status::Manual
+ status_translation = "CiStatusText|#{status}"
+ s_(status_translation)
+ end
+ end
+
+ def ci_status_for_statuseable(subject)
+ status = subject.try(:status) || 'not found'
+ status.humanize
+ end
+
+ # rubocop:disable Metrics/CyclomaticComplexity
+ def ci_icon_for_status(status, size: 16)
+ if detailed_status?(status)
+ return sprite_icon(status.icon, size: size)
+ end
+
+ icon_name =
+ case status
+ when 'success'
+ 'status_success'
+ when 'success-with-warnings'
+ 'status_warning'
+ when 'failed'
+ 'status_failed'
+ when 'pending'
+ 'status_pending'
+ when 'waiting_for_resource'
+ 'status_pending'
+ when 'preparing'
+ 'status_preparing'
+ when 'running'
+ 'status_running'
+ when 'play'
+ 'play'
+ when 'created'
+ 'status_created'
+ when 'skipped'
+ 'status_skipped'
+ when 'manual'
+ 'status_manual'
+ when 'scheduled'
+ 'status_scheduled'
+ else
+ 'status_canceled'
+ end
+
+ sprite_icon(icon_name, size: size)
+ end
+ # rubocop:enable Metrics/CyclomaticComplexity
+
+ def ci_icon_class_for_status(status)
+ group = detailed_status?(status) ? status.group : status.dasherize
+
+ "ci-status-icon-#{group}"
+ end
+
+ def pipeline_status_cache_key(pipeline_status)
+ "pipeline-status/#{pipeline_status.sha}-#{pipeline_status.status}"
+ end
+
+ def render_commit_status(commit, status, ref: nil, tooltip_placement: 'left')
+ project = commit.project
+ path = pipelines_project_commit_path(project, commit, ref: ref)
+
+ render_status_with_link(
+ status,
+ path,
+ tooltip_placement: tooltip_placement,
+ icon_size: 24)
+ end
+
+ def render_status_with_link(status, path = nil, type: _('pipeline'), tooltip_placement: 'left', cssclass: '', container: 'body', icon_size: 16)
+ klass = "ci-status-link #{ci_icon_class_for_status(status)} d-inline-flex #{cssclass}"
+ title = "#{type.titleize}: #{ci_label_for_status(status)}"
+ data = { toggle: 'tooltip', placement: tooltip_placement, container: container }
+
+ if path
+ link_to ci_icon_for_status(status, size: icon_size), path,
+ class: klass, title: title, data: data
+ else
+ content_tag :span, ci_icon_for_status(status, size: icon_size),
+ class: klass, title: title, data: data
+ end
+ end
+
+ def detailed_status?(status)
+ status.respond_to?(:text) &&
+ status.respond_to?(:group) &&
+ status.respond_to?(:label) &&
+ status.respond_to?(:icon)
+ end
+ end
+end
diff --git a/app/helpers/ci/variables_helper.rb b/app/helpers/ci/variables_helper.rb
new file mode 100644
index 00000000000..b20390d58e9
--- /dev/null
+++ b/app/helpers/ci/variables_helper.rb
@@ -0,0 +1,54 @@
+# frozen_string_literal: true
+
+module Ci
+ module VariablesHelper
+ def ci_variable_protected_by_default?
+ Gitlab::CurrentSettings.current_application_settings.protected_ci_variables
+ end
+
+ def create_deploy_token_path(entity, opts = {})
+ if entity.is_a?(::Group)
+ create_deploy_token_group_settings_repository_path(entity, opts)
+ else
+ # TODO: change this path to 'create_deploy_token_project_settings_ci_cd_path'
+ # See MR comment for more detail: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/27059#note_311585356
+ create_deploy_token_project_settings_repository_path(entity, opts)
+ end
+ end
+
+ def revoke_deploy_token_path(entity, token)
+ if entity.is_a?(::Group)
+ revoke_group_deploy_token_path(entity, token)
+ else
+ revoke_project_deploy_token_path(entity, token)
+ end
+ end
+
+ def ci_variable_protected?(variable, only_key_value)
+ if variable && !only_key_value
+ variable.protected
+ else
+ ci_variable_protected_by_default?
+ end
+ end
+
+ def ci_variable_masked?(variable, only_key_value)
+ if variable && !only_key_value
+ variable.masked
+ else
+ false
+ end
+ end
+
+ def ci_variable_type_options
+ [
+ %w(Variable env_var),
+ %w(File file)
+ ]
+ end
+
+ def ci_variable_maskable_regex
+ Ci::Maskable::REGEX.inspect.sub('\\A', '^').sub('\\z', '$').sub(/^\//, '').sub(/\/[a-z]*$/, '').gsub('\/', '/')
+ end
+ end
+end
diff --git a/app/helpers/ci_status_helper.rb b/app/helpers/ci_status_helper.rb
deleted file mode 100644
index 80d1b7e7edb..00000000000
--- a/app/helpers/ci_status_helper.rb
+++ /dev/null
@@ -1,146 +0,0 @@
-# frozen_string_literal: true
-
-##
-# DEPRECATED
-#
-# These helpers are deprecated in favor of detailed CI/CD statuses.
-#
-# See 'detailed_status?` method and `Gitlab::Ci::Status` module.
-#
-module CiStatusHelper
- def ci_label_for_status(status)
- if detailed_status?(status)
- return status.label
- end
-
- label = case status
- when 'success'
- 'passed'
- when 'success-with-warnings'
- 'passed with warnings'
- when 'manual'
- 'waiting for manual action'
- when 'scheduled'
- 'waiting for delayed job'
- else
- status
- end
- translation = "CiStatusLabel|#{label}"
- s_(translation)
- end
-
- def ci_text_for_status(status)
- if detailed_status?(status)
- return status.text
- end
-
- case status
- when 'success'
- s_('CiStatusText|passed')
- when 'success-with-warnings'
- s_('CiStatusText|passed')
- when 'manual'
- s_('CiStatusText|blocked')
- when 'scheduled'
- s_('CiStatusText|delayed')
- else
- # All states are already being translated inside the detailed statuses:
- # :running => Gitlab::Ci::Status::Running
- # :skipped => Gitlab::Ci::Status::Skipped
- # :failed => Gitlab::Ci::Status::Failed
- # :success => Gitlab::Ci::Status::Success
- # :canceled => Gitlab::Ci::Status::Canceled
- # The following states are customized above:
- # :manual => Gitlab::Ci::Status::Manual
- status_translation = "CiStatusText|#{status}"
- s_(status_translation)
- end
- end
-
- def ci_status_for_statuseable(subject)
- status = subject.try(:status) || 'not found'
- status.humanize
- end
-
- # rubocop:disable Metrics/CyclomaticComplexity
- def ci_icon_for_status(status, size: 16)
- if detailed_status?(status)
- return sprite_icon(status.icon, size: size)
- end
-
- icon_name =
- case status
- when 'success'
- 'status_success'
- when 'success-with-warnings'
- 'status_warning'
- when 'failed'
- 'status_failed'
- when 'pending'
- 'status_pending'
- when 'waiting_for_resource'
- 'status_pending'
- when 'preparing'
- 'status_preparing'
- when 'running'
- 'status_running'
- when 'play'
- 'play'
- when 'created'
- 'status_created'
- when 'skipped'
- 'status_skipped'
- when 'manual'
- 'status_manual'
- when 'scheduled'
- 'status_scheduled'
- else
- 'status_canceled'
- end
-
- sprite_icon(icon_name, size: size)
- end
- # rubocop:enable Metrics/CyclomaticComplexity
-
- def ci_icon_class_for_status(status)
- group = detailed_status?(status) ? status.group : status.dasherize
-
- "ci-status-icon-#{group}"
- end
-
- def pipeline_status_cache_key(pipeline_status)
- "pipeline-status/#{pipeline_status.sha}-#{pipeline_status.status}"
- end
-
- def render_commit_status(commit, status, ref: nil, tooltip_placement: 'left')
- project = commit.project
- path = pipelines_project_commit_path(project, commit, ref: ref)
-
- render_status_with_link(
- status,
- path,
- tooltip_placement: tooltip_placement,
- icon_size: 24)
- end
-
- def render_status_with_link(status, path = nil, type: _('pipeline'), tooltip_placement: 'left', cssclass: '', container: 'body', icon_size: 16)
- klass = "ci-status-link #{ci_icon_class_for_status(status)} d-inline-flex #{cssclass}"
- title = "#{type.titleize}: #{ci_label_for_status(status)}"
- data = { toggle: 'tooltip', placement: tooltip_placement, container: container }
-
- if path
- link_to ci_icon_for_status(status, size: icon_size), path,
- class: klass, title: title, data: data
- else
- content_tag :span, ci_icon_for_status(status, size: icon_size),
- class: klass, title: title, data: data
- end
- end
-
- def detailed_status?(status)
- status.respond_to?(:text) &&
- status.respond_to?(:group) &&
- status.respond_to?(:label) &&
- status.respond_to?(:icon)
- end
-end
diff --git a/app/helpers/ci_variables_helper.rb b/app/helpers/ci_variables_helper.rb
deleted file mode 100644
index cd0718c1b82..00000000000
--- a/app/helpers/ci_variables_helper.rb
+++ /dev/null
@@ -1,52 +0,0 @@
-# frozen_string_literal: true
-
-module CiVariablesHelper
- def ci_variable_protected_by_default?
- Gitlab::CurrentSettings.current_application_settings.protected_ci_variables
- end
-
- def create_deploy_token_path(entity, opts = {})
- if entity.is_a?(Group)
- create_deploy_token_group_settings_repository_path(entity, opts)
- else
- # TODO: change this path to 'create_deploy_token_project_settings_ci_cd_path'
- # See MR comment for more detail: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/27059#note_311585356
- create_deploy_token_project_settings_repository_path(entity, opts)
- end
- end
-
- def revoke_deploy_token_path(entity, token)
- if entity.is_a?(Group)
- revoke_group_deploy_token_path(entity, token)
- else
- revoke_project_deploy_token_path(entity, token)
- end
- end
-
- def ci_variable_protected?(variable, only_key_value)
- if variable && !only_key_value
- variable.protected
- else
- ci_variable_protected_by_default?
- end
- end
-
- def ci_variable_masked?(variable, only_key_value)
- if variable && !only_key_value
- variable.masked
- else
- false
- end
- end
-
- def ci_variable_type_options
- [
- %w(Variable env_var),
- %w(File file)
- ]
- end
-
- def ci_variable_maskable_regex
- Ci::Maskable::REGEX.inspect.sub('\\A', '^').sub('\\z', '$').sub(/^\//, '').sub(/\/[a-z]*$/, '').gsub('\/', '/')
- end
-end
diff --git a/app/helpers/clusters_helper.rb b/app/helpers/clusters_helper.rb
index 1204f882707..c85d2a68f14 100644
--- a/app/helpers/clusters_helper.rb
+++ b/app/helpers/clusters_helper.rb
@@ -1,11 +1,6 @@
# frozen_string_literal: true
module ClustersHelper
- # EE overrides this
- def has_multiple_clusters?
- false
- end
-
def create_new_cluster_label(provider: nil)
case provider
when 'aws'
@@ -19,6 +14,7 @@ module ClustersHelper
def js_clusters_list_data(path = nil)
{
+ ancestor_help_path: help_page_path('user/group/clusters/index', anchor: 'cluster-precedence'),
endpoint: path,
img_tags: {
aws: { path: image_path('illustrations/logos/amazon_eks.svg'), text: s_('ClusterIntegration|Amazon EKS') },
@@ -95,5 +91,3 @@ module ClustersHelper
can?(user, :admin_cluster, cluster)
end
end
-
-ClustersHelper.prepend_if_ee('EE::ClustersHelper')
diff --git a/app/helpers/commits_helper.rb b/app/helpers/commits_helper.rb
index 2a0c2e73dd6..f8490d79427 100644
--- a/app/helpers/commits_helper.rb
+++ b/app/helpers/commits_helper.rb
@@ -79,7 +79,7 @@ module CommitsHelper
# Returns a link formatted as a commit tag link
def commit_tag_link(url, text)
link_to(url, class: 'badge badge-gray ref-name') do
- sprite_icon('tag', size: 12, css_class: 'append-right-5 vertical-align-middle') + "#{text}"
+ sprite_icon('tag', size: 12, css_class: 'gl-mr-2 vertical-align-middle') + "#{text}"
end
end
@@ -181,15 +181,11 @@ module CommitsHelper
end
def view_file_button(commit_sha, diff_new_path, project, replaced: false)
+ path = project_blob_path(project, tree_join(commit_sha, diff_new_path))
title = replaced ? _('View replaced file @ ') : _('View file @ ')
- link_to(
- project_blob_path(project,
- tree_join(commit_sha, diff_new_path)),
- class: 'btn view-file js-view-file'
- ) do
- raw(title) + content_tag(:span, Commit.truncate_sha(commit_sha),
- class: 'commit-sha')
+ link_to(path, class: 'btn') do
+ raw(title) + content_tag(:span, truncate_sha(commit_sha), class: 'commit-sha')
end
end
diff --git a/app/helpers/cookies_helper.rb b/app/helpers/cookies_helper.rb
index 3a7e9987190..938379818de 100644
--- a/app/helpers/cookies_helper.rb
+++ b/app/helpers/cookies_helper.rb
@@ -1,9 +1,19 @@
# frozen_string_literal: true
module CookiesHelper
- def set_secure_cookie(key, value, httponly: false, permanent: false)
- cookie_jar = permanent ? cookies.permanent : cookies
+ COOKIE_TYPE_PERMANENT = :permanent
+ COOKIE_TYPE_ENCRYPTED = :encrypted
- cookie_jar[key] = { value: value, secure: Gitlab.config.gitlab.https, httponly: httponly }
+ def set_secure_cookie(key, value, httponly: false, expires: nil, type: nil)
+ cookie_jar = case type
+ when COOKIE_TYPE_PERMANENT
+ cookies.permanent
+ when COOKIE_TYPE_ENCRYPTED
+ cookies.encrypted
+ else
+ cookies
+ end
+
+ cookie_jar[key] = { value: value, secure: Gitlab.config.gitlab.https, httponly: httponly, expires: expires }
end
end
diff --git a/app/helpers/dashboard_helper.rb b/app/helpers/dashboard_helper.rb
index b38feb0fb6c..7bf3795d73a 100644
--- a/app/helpers/dashboard_helper.rb
+++ b/app/helpers/dashboard_helper.rb
@@ -41,7 +41,7 @@ module DashboardHelper
if doc_href.present?
link_to_doc = link_to(sprite_icon('question', size: 16), doc_href,
- class: 'prepend-left-5', title: _('Documentation'),
+ class: 'gl-ml-2', title: _('Documentation'),
target: '_blank', rel: 'noopener noreferrer')
concat(link_to_doc)
diff --git a/app/helpers/diff_helper.rb b/app/helpers/diff_helper.rb
index 4c3c4931387..3b25de521d0 100644
--- a/app/helpers/diff_helper.rb
+++ b/app/helpers/diff_helper.rb
@@ -135,8 +135,7 @@ module DiffHelper
def diff_file_html_data(project, diff_file_path, diff_commit_id)
{
- blob_diff_path: project_blob_diff_path(project,
- tree_join(diff_commit_id, diff_file_path)),
+ blob_diff_path: project_blob_diff_path(project, tree_join(diff_commit_id, diff_file_path)),
view: diff_view
}
end
@@ -175,6 +174,10 @@ module DiffHelper
end
end
+ def apply_diff_view_cookie!
+ set_secure_cookie(:diff_view, params.delete(:view), type: CookiesHelper::COOKIE_TYPE_PERMANENT) if params[:view].present?
+ end
+
private
def diff_btn(title, name, selected)
diff --git a/app/helpers/dropdowns_helper.rb b/app/helpers/dropdowns_helper.rb
index 64c5fae7d96..772a5f79a4d 100644
--- a/app/helpers/dropdowns_helper.rb
+++ b/app/helpers/dropdowns_helper.rb
@@ -15,7 +15,10 @@ module DropdownsHelper
dropdown_output = dropdown_toggle_link(toggle_text, data_attr, options)
end
- dropdown_output << content_tag(:div, class: "dropdown-menu dropdown-select #{options[:dropdown_class] if options.key?(:dropdown_class)}") do
+ content_tag_options = { class: "dropdown-menu dropdown-select #{options[:dropdown_class] if options.key?(:dropdown_class)}" }
+ content_tag_options[:data] = { qa_selector: "#{options[:dropdown_qa_selector]}" } if options[:dropdown_qa_selector]
+
+ dropdown_output << content_tag(:div, content_tag_options) do
output = []
if options.key?(:title)
diff --git a/app/helpers/environments_helper.rb b/app/helpers/environments_helper.rb
index 41a255434af..b522a9dfb4f 100644
--- a/app/helpers/environments_helper.rb
+++ b/app/helpers/environments_helper.rb
@@ -24,7 +24,7 @@ module EnvironmentsHelper
def metrics_data(project, environment)
metrics_data = {}
metrics_data.merge!(project_metrics_data(project)) if project
- metrics_data.merge!(environment_metrics_data(environment)) if environment
+ metrics_data.merge!(environment_metrics_data(environment, project)) if environment
metrics_data.merge!(project_and_environment_metrics_data(project, environment)) if project && environment
metrics_data.merge!(static_metrics_data)
@@ -36,7 +36,8 @@ module EnvironmentsHelper
"environment-name": environment.name,
"environments-path": project_environments_path(project, format: :json),
"environment-id": environment.id,
- "cluster-applications-documentation-path" => help_page_path('user/clusters/applications.md', anchor: 'elastic-stack')
+ "cluster-applications-documentation-path" => help_page_path('user/clusters/applications.md', anchor: 'elastic-stack'),
+ "clusters-path": project_clusters_path(project, format: :json)
}
end
@@ -65,11 +66,11 @@ module EnvironmentsHelper
}
end
- def environment_metrics_data(environment)
+ def environment_metrics_data(environment, project = nil)
return {} unless environment
{
- 'metrics-dashboard-base-path' => environment_metrics_path(environment),
+ 'metrics-dashboard-base-path' => metrics_dashboard_base_path(environment, project),
'current-environment-name' => environment.name,
'has-metrics' => "#{environment.has_metrics?}",
'prometheus-status' => "#{environment.prometheus_status}",
@@ -77,6 +78,17 @@ module EnvironmentsHelper
}
end
+ def metrics_dashboard_base_path(environment, project)
+ # This is needed to support our transition from environment scoped metric paths to project scoped.
+ if project
+ path = project_metrics_dashboard_path(project)
+
+ return path if request.path.include?(path)
+ end
+
+ environment_metrics_path(environment)
+ end
+
def project_and_environment_metrics_data(project, environment)
return {} unless project && environment
@@ -84,14 +96,16 @@ module EnvironmentsHelper
'metrics-endpoint' => additional_metrics_project_environment_path(project, environment, format: :json),
'dashboard-endpoint' => metrics_dashboard_project_environment_path(project, environment, format: :json),
'deployments-endpoint' => project_environment_deployments_path(project, environment, format: :json),
- 'alerts-endpoint' => project_prometheus_alerts_path(project, environment_id: environment.id, format: :json)
-
+ 'alerts-endpoint' => project_prometheus_alerts_path(project, environment_id: environment.id, format: :json),
+ 'operations-settings-path' => project_settings_operations_path(project),
+ 'can-access-operations-settings' => can?(current_user, :admin_operations, project).to_s
}
end
def static_metrics_data
{
'documentation-path' => help_page_path('administration/monitoring/prometheus/index.md'),
+ 'add-dashboard-documentation-path' => help_page_path('user/project/integrations/prometheus.md', anchor: 'adding-a-new-dashboard-to-your-project'),
'empty-getting-started-svg-path' => image_path('illustrations/monitoring/getting_started.svg'),
'empty-loading-svg-path' => image_path('illustrations/monitoring/loading.svg'),
'empty-no-data-svg-path' => image_path('illustrations/monitoring/no_data.svg'),
diff --git a/app/helpers/events_helper.rb b/app/helpers/events_helper.rb
index c1f343edd10..207230fd92e 100644
--- a/app/helpers/events_helper.rb
+++ b/app/helpers/events_helper.rb
@@ -29,7 +29,11 @@ module EventsHelper
def event_action_name(event)
target = if event.target_type
- if event.note?
+ if event.design? || event.design_note?
+ 'design'
+ elsif event.wiki_page?
+ 'wiki page'
+ elsif event.note?
event.note_target_type
else
event.target_type.titleize.downcase
@@ -58,11 +62,28 @@ module EventsHelper
end
def event_filter_visible(feature_key)
+ return designs_visible? if feature_key == :designs
return true unless @project
@project.feature_available?(feature_key, current_user)
end
+ def designs_visible?
+ if @project
+ design_activity_enabled?(@project)
+ elsif @group
+ design_activity_enabled?(@group)
+ elsif @projects
+ @projects.with_namespace.include_project_feature.any? { |p| design_activity_enabled?(p) }
+ else
+ true
+ end
+ end
+
+ def design_activity_enabled?(project)
+ Ability.allowed?(current_user, :read_design_activity, project)
+ end
+
def comments_visible?
event_filter_visible(:repository) ||
event_filter_visible(:merge_requests) ||
@@ -94,6 +115,12 @@ module EventsHelper
elsif event.milestone?
words << "##{event.target_iid}" if event.target_iid
words << "in"
+ elsif event.design?
+ words << event.design.to_reference
+ words << "in"
+ elsif event.wiki_page?
+ words << event.target_title
+ words << "in"
elsif event.target
prefix =
if event.merge_request?
@@ -180,10 +207,19 @@ module EventsHelper
def event_wiki_title_html(event)
capture do
- concat content_tag(:span, _('wiki page'), class: "event-target-type append-right-4")
+ concat content_tag(:span, _('wiki page'), class: "event-target-type gl-mr-2")
concat link_to(event.target_title, event_wiki_page_target_url(event),
title: event.target_title,
- class: 'has-tooltip event-target-link append-right-4')
+ class: 'has-tooltip event-target-link gl-mr-2')
+ end
+ end
+
+ def event_design_title_html(event)
+ capture do
+ concat content_tag(:span, _('design'), class: "event-target-type gl-mr-2")
+ concat link_to(event.design.reference_link_text, design_url(event.design),
+ title: event.target_title,
+ class: 'has-tooltip event-design event-target-link gl-mr-2')
end
end
@@ -194,8 +230,8 @@ module EventsHelper
def event_note_title_html(event)
if event.note_target
capture do
- concat content_tag(:span, event.note_target_type, class: "event-target-type append-right-4")
- concat link_to(event.note_target_reference, event_note_target_url(event), title: event.target_title, class: 'has-tooltip event-target-link append-right-4')
+ concat content_tag(:span, event.note_target_type, class: "event-target-type gl-mr-2")
+ concat link_to(event.note_target_reference, event_note_target_url(event), title: event.target_title, class: 'has-tooltip event-target-link gl-mr-2')
end
else
content_tag(:strong, '(deleted)')
@@ -214,6 +250,18 @@ module EventsHelper
sprite_icon(icon_name, size: size) if icon_name
end
+ DESIGN_ICONS = {
+ 'created' => 'upload',
+ 'updated' => 'pencil',
+ 'destroyed' => ICON_NAMES_BY_EVENT_TYPE['destroyed'],
+ 'archived' => 'archive'
+ }.freeze
+
+ def design_event_icon(action, size: 24)
+ icon_name = DESIGN_ICONS[action]
+ sprite_icon(icon_name, size: size) if icon_name
+ end
+
def icon_for_profile_event(event)
if current_path?('users#show')
content_tag :div, class: "system-note-image #{event.action_name.parameterize}-icon" do
@@ -228,7 +276,9 @@ module EventsHelper
def inline_event_icon(event)
unless current_path?('users#show')
- content_tag :span, class: "system-note-image-inline d-none d-sm-flex append-right-4 #{event.action_name.parameterize}-icon align-self-center" do
+ content_tag :span, class: "system-note-image-inline d-none d-sm-flex gl-mr-2 #{event.action_name.parameterize}-icon align-self-center" do
+ next design_event_icon(event.action, size: 14) if event.design?
+
icon_for_event(event.action_name, size: 14)
end
end
@@ -244,7 +294,7 @@ module EventsHelper
private
- def design_url(design, opts)
+ def design_url(design, opts = {})
designs_project_issue_url(
design.project,
design.issue,
diff --git a/app/helpers/export_helper.rb b/app/helpers/export_helper.rb
index 483b350b99b..38a4f7f1b4b 100644
--- a/app/helpers/export_helper.rb
+++ b/app/helpers/export_helper.rb
@@ -6,7 +6,7 @@ module ExportHelper
[
_('Project and wiki repositories'),
_('Project uploads'),
- _('Project configuration, including services'),
+ _('Project configuration, excluding integrations'),
_('Issues with comments, merge requests with diffs and comments, labels, milestones, snippets, and other project entities'),
_('LFS objects'),
_('Issue Boards'),
diff --git a/app/helpers/gitlab_routing_helper.rb b/app/helpers/gitlab_routing_helper.rb
index 8a9380f4771..04f34f5a3ae 100644
--- a/app/helpers/gitlab_routing_helper.rb
+++ b/app/helpers/gitlab_routing_helper.rb
@@ -271,6 +271,36 @@ module GitlabRoutingHelper
end
end
+ def gitlab_raw_snippet_blob_url(snippet, path, ref = nil)
+ params = {
+ snippet_id: snippet,
+ ref: ref || snippet.repository.root_ref,
+ path: path
+ }
+
+ if snippet.is_a?(ProjectSnippet)
+ project_snippet_blob_raw_url(snippet.project, params)
+ else
+ snippet_blob_raw_url(params)
+ end
+ end
+
+ def gitlab_raw_snippet_blob_path(blob, ref = nil)
+ snippet = blob.container
+
+ params = {
+ snippet_id: snippet,
+ ref: ref || blob.repository.root_ref,
+ path: blob.path
+ }
+
+ if snippet.is_a?(ProjectSnippet)
+ project_snippet_blob_raw_path(snippet.project, params)
+ else
+ snippet_blob_raw_path(params)
+ end
+ end
+
def gitlab_snippet_notes_path(snippet, *args)
new_args = snippet_query_params(snippet, *args)
snippet_notes_path(snippet, *new_args)
diff --git a/app/helpers/groups_helper.rb b/app/helpers/groups_helper.rb
index a6c3c97a873..61c9bd74451 100644
--- a/app/helpers/groups_helper.rb
+++ b/app/helpers/groups_helper.rb
@@ -176,6 +176,10 @@ module GroupsHelper
links << :settings
end
+ if can?(current_user, :read_wiki, @group)
+ links << :wiki
+ end
+
links
end
diff --git a/app/helpers/icons_helper.rb b/app/helpers/icons_helper.rb
index 8a32d3c8a3f..add15cc0d12 100644
--- a/app/helpers/icons_helper.rb
+++ b/app/helpers/icons_helper.rb
@@ -28,10 +28,12 @@ module IconsHelper
end
def sprite_icon_path
- # SVG Sprites currently don't work across domains, so in the case of a CDN
- # we have to set the current path deliberately to prevent addition of asset_host
- sprite_base_url = Gitlab.config.gitlab.url if ActionController::Base.asset_host
- ActionController::Base.helpers.image_path('icons.svg', host: sprite_base_url)
+ @sprite_icon_path ||= begin
+ # SVG Sprites currently don't work across domains, so in the case of a CDN
+ # we have to set the current path deliberately to prevent addition of asset_host
+ sprite_base_url = Gitlab.config.gitlab.url if ActionController::Base.asset_host
+ ActionController::Base.helpers.image_path('icons.svg', host: sprite_base_url)
+ end
end
def sprite_file_icons_path
@@ -53,6 +55,15 @@ module IconsHelper
content_tag(:svg, content_tag(:use, "", { "xlink:href" => "#{sprite_icon_path}##{icon_name}" } ), class: css_classes.empty? ? nil : css_classes.join(' '))
end
+ def loading_icon(container: false, color: 'orange', size: 'sm', css_class: nil)
+ css_classes = ['gl-spinner', "gl-spinner-#{color}", "gl-spinner-#{size}"]
+ css_classes << "#{css_class}" unless css_class.blank?
+
+ spinner = content_tag(:span, "", { class: css_classes.join(' '), aria: { label: _('Loading') } })
+
+ container == true ? content_tag(:div, spinner, { class: 'gl-spinner-container' }) : spinner
+ end
+
def external_snippet_icon(name)
content_tag(:span, "", class: "gl-snippet-icon gl-snippet-icon-#{name}")
end
diff --git a/app/helpers/ide_helper.rb b/app/helpers/ide_helper.rb
index d6145493ba6..93f5ca7258d 100644
--- a/app/helpers/ide_helper.rb
+++ b/app/helpers/ide_helper.rb
@@ -9,10 +9,12 @@ module IdeHelper
"pipelines-empty-state-svg-path": image_path('illustrations/pipelines_empty.svg'),
"promotion-svg-path": image_path('illustrations/web-ide_promotion.svg'),
"ci-help-page-path" => help_page_path('ci/quick_start/README'),
- "web-ide-help-page-path" => help_page_path('user/project/web_ide/index.html'),
+ "web-ide-help-page-path" => help_page_path('user/project/web_ide/index.md'),
"clientside-preview-enabled": Gitlab::CurrentSettings.web_ide_clientside_preview_enabled?.to_s,
"render-whitespace-in-code": current_user.render_whitespace_in_code.to_s,
"codesandbox-bundler-url": Gitlab::CurrentSettings.web_ide_clientside_preview_bundler_url
}
end
end
+
+::IdeHelper.prepend_if_ee('::EE::IdeHelper')
diff --git a/app/helpers/import_helper.rb b/app/helpers/import_helper.rb
index 9122ad5b35a..1ee67211ab0 100644
--- a/app/helpers/import_helper.rb
+++ b/app/helpers/import_helper.rb
@@ -19,7 +19,11 @@ module ImportHelper
end
def provider_project_link_url(provider_url, full_path)
- Gitlab::Utils.append_path(provider_url, full_path)
+ if Gitlab::Utils.parse_url(full_path)&.absolute?
+ full_path
+ else
+ Gitlab::Utils.append_path(provider_url, full_path)
+ end
end
def import_will_timeout_message(_ci_cd_only)
diff --git a/app/helpers/issuables_helper.rb b/app/helpers/issuables_helper.rb
index a848c814742..dccb89eec79 100644
--- a/app/helpers/issuables_helper.rb
+++ b/app/helpers/issuables_helper.rb
@@ -367,15 +367,6 @@ module IssuablesHelper
end
end
- def issuable_close_reopen_button_method(issuable)
- case issuable
- when Issue
- ''
- when MergeRequest
- 'put'
- end
- end
-
def issuable_author_is_current_user(issuable)
issuable.author == current_user
end
@@ -394,6 +385,14 @@ module IssuablesHelper
end
end
+ def issuable_squash_option?(issuable, project)
+ if issuable.persisted?
+ issuable.squash
+ else
+ project.squash_enabled_by_default?
+ end
+ end
+
private
def sidebar_gutter_collapsed?
diff --git a/app/helpers/issues_helper.rb b/app/helpers/issues_helper.rb
index 244b97c7196..61fe075303c 100644
--- a/app/helpers/issues_helper.rb
+++ b/app/helpers/issues_helper.rb
@@ -41,7 +41,7 @@ module IssuesHelper
end
def confidential_icon(issue)
- icon('eye-slash') if issue.confidential?
+ sprite_icon('eye-slash', size: 16, css_class: 'gl-vertical-align-text-bottom') if issue.confidential?
end
def award_user_list(awards, current_user, limit: 10)
@@ -132,7 +132,10 @@ module IssuesHelper
end
def show_moved_service_desk_issue_warning?(issue)
- false
+ return false unless issue.moved_from
+ return false unless issue.from_service_desk?
+
+ issue.moved_from.project.service_desk_enabled? && !issue.project.service_desk_enabled?
end
end
diff --git a/app/helpers/jobs_helper.rb b/app/helpers/jobs_helper.rb
deleted file mode 100644
index 46edba261dd..00000000000
--- a/app/helpers/jobs_helper.rb
+++ /dev/null
@@ -1,19 +0,0 @@
-# frozen_string_literal: true
-
-module JobsHelper
- def jobs_data
- {
- "endpoint" => project_job_path(@project, @build, format: :json),
- "project_path" => @project.full_path,
- "deployment_help_url" => help_page_path('user/project/clusters/index.html', anchor: 'troubleshooting-failed-deployment-jobs'),
- "runner_help_url" => help_page_path('ci/runners/README.html', anchor: 'setting-maximum-job-timeout-for-a-runner'),
- "runner_settings_url" => project_runners_path(@build.project, anchor: 'js-runners-settings'),
- "variables_settings_url" => project_variables_path(@build.project, anchor: 'js-cicd-variables-settings'),
- "page_path" => project_job_path(@project, @build),
- "build_status" => @build.status,
- "build_stage" => @build.stage,
- "log_state" => '',
- "build_options" => javascript_build_options
- }
- end
-end
diff --git a/app/helpers/markup_helper.rb b/app/helpers/markup_helper.rb
index 7ab2b33de8c..ed8931fe0f2 100644
--- a/app/helpers/markup_helper.rb
+++ b/app/helpers/markup_helper.rb
@@ -244,7 +244,6 @@ module MarkupHelper
content_tag :button,
type: 'button',
class: 'toolbar-btn js-md has-tooltip',
- tabindex: -1,
data: data,
title: options[:title],
aria: { label: options[:title] } do
diff --git a/app/helpers/members_helper.rb b/app/helpers/members_helper.rb
index 31995c27fac..d66f67fbb60 100644
--- a/app/helpers/members_helper.rb
+++ b/app/helpers/members_helper.rb
@@ -48,6 +48,14 @@ module MembersHelper
"#{request.path}?#{options.to_param}"
end
+ def member_path(member)
+ if member.is_a?(GroupMember)
+ group_group_member_path(member.source, member)
+ else
+ project_project_member_path(member.source, member)
+ end
+ end
+
private
def source_text(member)
diff --git a/app/helpers/merge_requests_helper.rb b/app/helpers/merge_requests_helper.rb
index 7940ec1162b..caf39741543 100644
--- a/app/helpers/merge_requests_helper.rb
+++ b/app/helpers/merge_requests_helper.rb
@@ -118,7 +118,7 @@ module MergeRequestsHelper
auto_merge_strategy: AutoMergeService::STRATEGY_MERGE_WHEN_PIPELINE_SUCCEEDS,
should_remove_source_branch: true,
sha: merge_request.diff_head_sha,
- squash: merge_request.squash
+ squash: merge_request.squash_on_merge?
}
end
diff --git a/app/helpers/namespaces_helper.rb b/app/helpers/namespaces_helper.rb
index b9f8d81bc4e..81451e398f2 100644
--- a/app/helpers/namespaces_helper.rb
+++ b/app/helpers/namespaces_helper.rb
@@ -56,45 +56,6 @@ module NamespacesHelper
namespaces_options(selected, options)
end
- def namespace_storage_alert(namespace)
- return {} if current_user.nil?
-
- payload = Namespaces::CheckStorageSizeService.new(namespace, current_user).execute.payload
-
- return {} if payload.empty?
-
- alert_level = payload[:alert_level]
- root_namespace = payload[:root_namespace]
-
- return {} if cookies["hide_storage_limit_alert_#{root_namespace.id}_#{alert_level}"] == 'true'
-
- payload
- end
-
- def namespace_storage_alert_style(alert_level)
- if alert_level == :error || alert_level == :alert
- 'danger'
- else
- alert_level.to_s
- end
- end
-
- def namespace_storage_alert_icon(alert_level)
- if alert_level == :error || alert_level == :alert
- 'error'
- elsif alert_level == :info
- 'information-o'
- else
- alert_level.to_s
- end
- end
-
- def namespace_storage_usage_link(namespace)
- # The usage quota page is only available in EE. This will be changed in
- # the future, see https://gitlab.com/gitlab-org/gitlab/-/issues/220042.
- nil
- end
-
private
# Many importers create a temporary Group, so use the real
diff --git a/app/helpers/nav_helper.rb b/app/helpers/nav_helper.rb
index 9ea0b9cb584..d849ed9d076 100644
--- a/app/helpers/nav_helper.rb
+++ b/app/helpers/nav_helper.rb
@@ -27,7 +27,7 @@ module NavHelper
end
elsif current_path?('jobs#show')
%w[page-gutter build-sidebar right-sidebar-expanded]
- elsif current_controller?('wikis') && current_action?('show', 'create', 'edit', 'update', 'history', 'git_access', 'destroy')
+ elsif current_controller?('wikis') && current_action?('show', 'create', 'edit', 'update', 'history', 'git_access', 'destroy', 'diff')
%w[page-gutter wiki-sidebar right-sidebar-expanded]
else
[]
diff --git a/app/helpers/notify_helper.rb b/app/helpers/notify_helper.rb
new file mode 100644
index 00000000000..fb68029928c
--- /dev/null
+++ b/app/helpers/notify_helper.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+module NotifyHelper
+ def merge_request_reference_link(entity, *args)
+ link_to(entity.to_reference, merge_request_url(entity, *args))
+ end
+
+ def issue_reference_link(entity, *args)
+ link_to(entity.to_reference, issue_url(entity, *args))
+ end
+end
diff --git a/app/helpers/onboarding_experiment_helper.rb b/app/helpers/onboarding_experiment_helper.rb
deleted file mode 100644
index 138fc60479d..00000000000
--- a/app/helpers/onboarding_experiment_helper.rb
+++ /dev/null
@@ -1,9 +0,0 @@
-# frozen_string_literal: true
-
-module OnboardingExperimentHelper
- def allow_access_to_onboarding?
- ::Gitlab.dev_env_or_com? && Feature.enabled?(:user_onboarding)
- end
-end
-
-OnboardingExperimentHelper.prepend_if_ee('EE::OnboardingExperimentHelper')
diff --git a/app/helpers/operations_helper.rb b/app/helpers/operations_helper.rb
new file mode 100644
index 00000000000..3444773fe88
--- /dev/null
+++ b/app/helpers/operations_helper.rb
@@ -0,0 +1,54 @@
+# frozen_string_literal: true
+
+module OperationsHelper
+ include Gitlab::Utils::StrongMemoize
+
+ def prometheus_service
+ strong_memoize(:prometheus_service) do
+ @project.find_or_initialize_service(::PrometheusService.to_param)
+ end
+ end
+
+ def alerts_service
+ strong_memoize(:alerts_service) do
+ @project.find_or_initialize_service(::AlertsService.to_param)
+ end
+ end
+
+ def alerts_settings_data(disabled: false)
+ {
+ 'prometheus_activated' => prometheus_service.manual_configuration?.to_s,
+ 'activated' => alerts_service.activated?.to_s,
+ 'prometheus_form_path' => scoped_integration_path(prometheus_service),
+ 'form_path' => scoped_integration_path(alerts_service),
+ 'prometheus_reset_key_path' => reset_alerting_token_project_settings_operations_path(@project),
+ 'prometheus_authorization_key' => @project.alerting_setting&.token,
+ 'prometheus_api_url' => prometheus_service.api_url,
+ 'authorization_key' => alerts_service.token,
+ 'prometheus_url' => notify_project_prometheus_alerts_url(@project, format: :json),
+ 'url' => alerts_service.url,
+ 'alerts_setup_url' => help_page_path('user/project/integrations/generic_alerts.md', anchor: 'setting-up-generic-alerts'),
+ 'alerts_usage_url' => project_alert_management_index_path(@project),
+ 'disabled' => disabled.to_s
+ }
+ end
+
+ def operations_settings_data
+ setting = project_incident_management_setting
+ templates = setting.available_issue_templates.map { |t| { key: t.key, name: t.name } }
+
+ {
+ operations_settings_endpoint: project_settings_operations_path(@project),
+ templates: templates.to_json,
+ create_issue: setting.create_issue.to_s,
+ issue_template_key: setting.issue_template_key.to_s,
+ send_email: setting.send_email.to_s,
+ pagerduty_active: setting.pagerduty_active.to_s,
+ pagerduty_token: setting.pagerduty_token.to_s,
+ pagerduty_webhook_url: project_incidents_pagerduty_url(@project, token: setting.pagerduty_token),
+ pagerduty_reset_key_path: reset_pagerduty_token_project_settings_operations_path(@project)
+ }
+ end
+end
+
+OperationsHelper.prepend_if_ee('EE::OperationsHelper')
diff --git a/app/helpers/pipeline_schedules_helper.rb b/app/helpers/pipeline_schedules_helper.rb
deleted file mode 100644
index 0e166106b32..00000000000
--- a/app/helpers/pipeline_schedules_helper.rb
+++ /dev/null
@@ -1,13 +0,0 @@
-# frozen_string_literal: true
-
-module PipelineSchedulesHelper
- def timezone_data
- ActiveSupport::TimeZone.all.map do |timezone|
- {
- name: timezone.name,
- offset: timezone.now.utc_offset,
- identifier: timezone.tzinfo.identifier
- }
- end
- end
-end
diff --git a/app/helpers/preferences_helper.rb b/app/helpers/preferences_helper.rb
index 7a0462e1b2c..271359fcfd1 100644
--- a/app/helpers/preferences_helper.rb
+++ b/app/helpers/preferences_helper.rb
@@ -70,7 +70,10 @@ module PreferencesHelper
end
def language_choices
- Gitlab::I18n::AVAILABLE_LANGUAGES.map(&:reverse).sort
+ options_for_select(
+ Gitlab::I18n::AVAILABLE_LANGUAGES.map(&:reverse).sort,
+ current_user.preferred_language
+ )
end
private
diff --git a/app/helpers/projects/alert_management_helper.rb b/app/helpers/projects/alert_management_helper.rb
index bc585899591..d6e8e738a1c 100644
--- a/app/helpers/projects/alert_management_helper.rb
+++ b/app/helpers/projects/alert_management_helper.rb
@@ -4,10 +4,11 @@ module Projects::AlertManagementHelper
def alert_management_data(current_user, project)
{
'project-path' => project.full_path,
- 'enable-alert-management-path' => edit_project_service_path(project, AlertsService),
+ 'enable-alert-management-path' => project_settings_operations_path(project, anchor: 'js-alert-management-settings'),
+ 'populating-alerts-help-url' => help_page_url('user/project/operations/alert_management.html', anchor: 'enable-alert-management'),
'empty-alert-svg-path' => image_path('illustrations/alert-management-empty-state.svg'),
- 'user-can-enable-alert-management' => can?(current_user, :admin_project, project).to_s,
- 'alert-management-enabled' => (!!project.alerts_service_activated?).to_s
+ 'user-can-enable-alert-management' => can?(current_user, :admin_operations, project).to_s,
+ 'alert-management-enabled' => alert_management_enabled?(project).to_s
}
end
@@ -15,7 +16,16 @@ module Projects::AlertManagementHelper
{
'alert-id' => alert_id,
'project-path' => project.full_path,
+ 'project-id' => project.id,
'project-issues-path' => project_issues_path(project)
}
end
+
+ private
+
+ def alert_management_enabled?(project)
+ !!(project.alerts_service_activated? || project.prometheus_service_active?)
+ end
end
+
+Projects::AlertManagementHelper.prepend_if_ee('EE::Projects::AlertManagementHelper')
diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb
index bda9a69d71f..840e3ef9daa 100644
--- a/app/helpers/projects_helper.rb
+++ b/app/helpers/projects_helper.rb
@@ -180,7 +180,7 @@ module ProjectsHelper
end
def link_to_autodeploy_doc
- link_to _('About auto deploy'), help_page_path('autodevops/index.md#auto-deploy'), target: '_blank'
+ link_to _('About auto deploy'), help_page_path('topics/autodevops/stages.md', anchor: 'auto-deploy'), target: '_blank'
end
def autodeploy_flash_notice(branch_name)
@@ -384,9 +384,12 @@ module ProjectsHelper
end
def project_license_name(project)
- project.repository.license&.name
+ key = "project:#{project.id}:license_name"
+
+ Gitlab::SafeRequestStore.fetch(key) { project.repository.license&.name }
rescue GRPC::Unavailable, GRPC::DeadlineExceeded, Gitlab::Git::CommandError => e
Gitlab::ErrorTracking.track_exception(e)
+ Gitlab::SafeRequestStore[key] = nil
nil
end
@@ -397,7 +400,7 @@ module ProjectsHelper
nav_tabs = [:home]
unless project.empty_repo?
- nav_tabs << [:files, :commits, :network, :graphs, :forks] if can?(current_user, :download_code, project)
+ nav_tabs += [:files, :commits, :network, :graphs, :forks] if can?(current_user, :download_code, project)
nav_tabs << :releases if can?(current_user, :read_release, project)
end
@@ -418,30 +421,30 @@ module ProjectsHelper
nav_tabs << :operations
end
- if can?(current_user, :read_cycle_analytics, project)
- nav_tabs << :cycle_analytics
- end
-
tab_ability_map.each do |tab, ability|
if can?(current_user, ability, project)
nav_tabs << tab
end
end
- nav_tabs << external_nav_tabs(project)
+ apply_external_nav_tabs(nav_tabs, project)
- nav_tabs.flatten
+ nav_tabs
end
- def external_nav_tabs(project)
- [].tap do |tabs|
- tabs << :external_issue_tracker if project.external_issue_tracker
- tabs << :external_wiki if project.external_wiki
+ def apply_external_nav_tabs(nav_tabs, project)
+ nav_tabs << :external_issue_tracker if project.external_issue_tracker
+ nav_tabs << :external_wiki if project.external_wiki
+
+ if project.has_confluence?
+ nav_tabs.delete(:wiki)
+ nav_tabs << :confluence
end
end
def tab_ability_map
{
+ cycle_analytics: :read_cycle_analytics,
environments: :read_environment,
metrics_dashboards: :metrics_dashboard,
milestones: :read_milestone,
@@ -565,7 +568,7 @@ module ProjectsHelper
end
def project_child_container_class(view_path)
- view_path == "projects/issues/issues" ? "prepend-top-default" : "project-show-#{view_path}"
+ view_path == "projects/issues/issues" ? "gl-mt-3" : "project-show-#{view_path}"
end
def project_issues(project)
@@ -729,10 +732,6 @@ module ProjectsHelper
!project.repository.gitlab_ci_yml
end
- def vue_file_list_enabled?
- Feature.enabled?(:vue_file_list, @project, default_enabled: true)
- end
-
def native_code_navigation_enabled?(project)
Feature.enabled?(:code_navigation, project, default_enabled: true)
end
diff --git a/app/helpers/releases_helper.rb b/app/helpers/releases_helper.rb
index 1238567a4ed..a3d944c64cc 100644
--- a/app/helpers/releases_helper.rb
+++ b/app/helpers/releases_helper.rb
@@ -18,21 +18,40 @@ module ReleasesHelper
illustration_path: illustration,
documentation_path: help_page
}.tap do |data|
- data[:new_release_path] = new_project_tag_path(@project) if can?(current_user, :create_release, @project)
+ if can?(current_user, :create_release, @project)
+ data[:new_release_path] = if Feature.enabled?(:new_release_page, @project)
+ new_project_release_path(@project)
+ else
+ new_project_tag_path(@project)
+ end
+ end
end
end
def data_for_edit_release_page
+ new_edit_pages_shared_data.merge(
+ tag_name: @release.tag,
+ releases_page_path: project_releases_path(@project, anchor: @release.tag)
+ )
+ end
+
+ def data_for_new_release_page
+ new_edit_pages_shared_data.merge(
+ default_branch: @project.default_branch
+ )
+ end
+
+ private
+
+ def new_edit_pages_shared_data
{
project_id: @project.id,
- tag_name: @release.tag,
markdown_preview_path: preview_markdown_path(@project),
markdown_docs_path: help_page_path('user/markdown'),
- releases_page_path: project_releases_path(@project, anchor: @release.tag),
update_release_api_docs_path: help_page_path('api/releases/index.md', anchor: 'update-a-release'),
release_assets_docs_path: help_page(anchor: 'release-assets'),
manage_milestones_path: project_milestones_path(@project),
- new_milestone_path: new_project_milestone_url(@project)
+ new_milestone_path: new_project_milestone_path(@project)
}
end
end
diff --git a/app/helpers/runners_helper.rb b/app/helpers/runners_helper.rb
deleted file mode 100644
index d871aaa9c86..00000000000
--- a/app/helpers/runners_helper.rb
+++ /dev/null
@@ -1,43 +0,0 @@
-# frozen_string_literal: true
-
-module RunnersHelper
- def runner_status_icon(runner)
- status = runner.status
- case status
- when :not_connected
- content_tag :i, nil,
- class: "fa fa-warning",
- title: "New runner. Has not connected yet"
-
- when :online, :offline, :paused
- content_tag :i, nil,
- class: "fa fa-circle runner-status-#{status}",
- title: "Runner is #{status}, last contact was #{time_ago_in_words(runner.contacted_at)} ago"
- end
- end
-
- def runner_link(runner)
- display_name = truncate(runner.display_name, length: 15)
- id = "\##{runner.id}"
-
- if current_user && current_user.admin
- link_to admin_runner_path(runner) do
- display_name + id
- end
- else
- display_name + id
- end
- end
-
- # Due to inability of performing sorting of runners by cached "contacted_at" values we have to show uncached values if sorting by "contacted_asc" is requested.
- # Please refer to the following issue for more details: https://gitlab.com/gitlab-org/gitlab-foss/issues/55920
- def runner_contacted_at(runner)
- if params[:sort] == 'contacted_asc'
- runner.uncached_contacted_at
- else
- runner.contacted_at
- end
- end
-end
-
-RunnersHelper.prepend_if_ee('EE::RunnersHelper')
diff --git a/app/helpers/search_helper.rb b/app/helpers/search_helper.rb
index 4e3b6aad8cc..1b9876b9a6a 100644
--- a/app/helpers/search_helper.rb
+++ b/app/helpers/search_helper.rb
@@ -3,6 +3,28 @@
module SearchHelper
SEARCH_PERMITTED_PARAMS = [:search, :scope, :project_id, :group_id, :repository_ref, :snippets].freeze
+ def search_autocomplete_opts(term)
+ return unless current_user
+
+ resources_results = [
+ groups_autocomplete(term),
+ projects_autocomplete(term)
+ ].flatten
+
+ search_pattern = Regexp.new(Regexp.escape(term), "i")
+
+ generic_results = project_autocomplete + default_autocomplete + help_autocomplete
+ generic_results.concat(default_autocomplete_admin) if current_user.admin?
+ generic_results.select! { |result| result[:label] =~ search_pattern }
+
+ [
+ resources_results,
+ generic_results
+ ].flatten.uniq do |item|
+ item[:label]
+ end
+ end
+
def search_entries_info(collection, scope, term)
return if collection.to_a.empty?
@@ -62,7 +84,7 @@ module SearchHelper
}).html_safe
end
- # Overriden in EE
+ # Overridden in EE
def search_blob_title(project, path)
path
end
@@ -73,6 +95,91 @@ module SearchHelper
private
+ # Autocomplete results for various settings pages
+ def default_autocomplete
+ [
+ { category: "Settings", label: _("User settings"), url: profile_path },
+ { category: "Settings", label: _("SSH Keys"), url: profile_keys_path },
+ { category: "Settings", label: _("Dashboard"), url: root_path }
+ ]
+ end
+
+ # Autocomplete results for settings pages, for admins
+ def default_autocomplete_admin
+ [
+ { category: "Settings", label: _("Admin Section"), url: admin_root_path }
+ ]
+ end
+
+ # Autocomplete results for internal help pages
+ def help_autocomplete
+ [
+ { category: "Help", label: _("API Help"), url: help_page_path("api/README") },
+ { category: "Help", label: _("Markdown Help"), url: help_page_path("user/markdown") },
+ { category: "Help", label: _("Permissions Help"), url: help_page_path("user/permissions") },
+ { category: "Help", label: _("Public Access Help"), url: help_page_path("public_access/public_access") },
+ { category: "Help", label: _("Rake Tasks Help"), url: help_page_path("raketasks/README") },
+ { category: "Help", label: _("SSH Keys Help"), url: help_page_path("ssh/README") },
+ { category: "Help", label: _("System Hooks Help"), url: help_page_path("system_hooks/system_hooks") },
+ { category: "Help", label: _("Webhooks Help"), url: help_page_path("user/project/integrations/webhooks") },
+ { category: "Help", label: _("Workflow Help"), url: help_page_path("workflow/README") }
+ ]
+ end
+
+ # Autocomplete results for the current project, if it's defined
+ def project_autocomplete
+ if @project && @project.repository.root_ref
+ ref = @ref || @project.repository.root_ref
+
+ [
+ { category: "In this project", label: _("Files"), url: project_tree_path(@project, ref) },
+ { category: "In this project", label: _("Commits"), url: project_commits_path(@project, ref) },
+ { category: "In this project", label: _("Network"), url: project_network_path(@project, ref) },
+ { category: "In this project", label: _("Graph"), url: project_graph_path(@project, ref) },
+ { category: "In this project", label: _("Issues"), url: project_issues_path(@project) },
+ { category: "In this project", label: _("Merge Requests"), url: project_merge_requests_path(@project) },
+ { category: "In this project", label: _("Milestones"), url: project_milestones_path(@project) },
+ { category: "In this project", label: _("Snippets"), url: project_snippets_path(@project) },
+ { category: "In this project", label: _("Members"), url: project_project_members_path(@project) },
+ { category: "In this project", label: _("Wiki"), url: project_wikis_path(@project) }
+ ]
+ else
+ []
+ end
+ end
+
+ # Autocomplete results for the current user's groups
+ # rubocop: disable CodeReuse/ActiveRecord
+ def groups_autocomplete(term, limit = 5)
+ current_user.authorized_groups.order_id_desc.search(term).limit(limit).map do |group|
+ {
+ category: "Groups",
+ id: group.id,
+ label: "#{search_result_sanitize(group.full_name)}",
+ url: group_path(group),
+ avatar_url: group.avatar_url || ''
+ }
+ end
+ end
+ # rubocop: enable CodeReuse/ActiveRecord
+
+ # Autocomplete results for the current user's projects
+ # rubocop: disable CodeReuse/ActiveRecord
+ def projects_autocomplete(term, limit = 5)
+ current_user.authorized_projects.order_id_desc.search_by_title(term)
+ .sorted_by_stars_desc.non_archived.limit(limit).map do |p|
+ {
+ category: "Projects",
+ id: p.id,
+ value: "#{search_result_sanitize(p.name)}",
+ label: "#{search_result_sanitize(p.full_name)}",
+ url: project_path(p),
+ avatar_url: p.avatar_url || ''
+ }
+ end
+ end
+ # rubocop: enable CodeReuse/ActiveRecord
+
def search_result_sanitize(str)
Sanitize.clean(str)
end
diff --git a/app/helpers/services_helper.rb b/app/helpers/services_helper.rb
index fe839b92ba6..1f9cce80bed 100644
--- a/app/helpers/services_helper.rb
+++ b/app/helpers/services_helper.rb
@@ -4,25 +4,29 @@ module ServicesHelper
def service_event_description(event)
case event
when "push", "push_events"
- "Event will be triggered by a push to the repository"
+ s_("ProjectService|Event will be triggered by a push to the repository")
when "tag_push", "tag_push_events"
- "Event will be triggered when a new tag is pushed to the repository"
+ s_("ProjectService|Event will be triggered when a new tag is pushed to the repository")
when "note", "note_events"
- "Event will be triggered when someone adds a comment"
+ s_("ProjectService|Event will be triggered when someone adds a comment")
when "confidential_note", "confidential_note_events"
- "Event will be triggered when someone adds a comment on a confidential issue"
+ s_("ProjectService|Event will be triggered when someone adds a comment on a confidential issue")
when "issue", "issue_events"
- "Event will be triggered when an issue is created/updated/closed"
- when "confidential_issue", "confidential_issues_events"
- "Event will be triggered when a confidential issue is created/updated/closed"
+ s_("ProjectService|Event will be triggered when an issue is created/updated/closed")
+ when "confidential_issue", "confidential_issue_events"
+ s_("ProjectService|Event will be triggered when a confidential issue is created/updated/closed")
when "merge_request", "merge_request_events"
- "Event will be triggered when a merge request is created/updated/merged"
+ s_("ProjectService|Event will be triggered when a merge request is created/updated/merged")
when "pipeline", "pipeline_events"
- "Event will be triggered when a pipeline status changes"
+ s_("ProjectService|Event will be triggered when a pipeline status changes")
when "wiki_page", "wiki_page_events"
- "Event will be triggered when a wiki page is created/updated"
+ s_("ProjectService|Event will be triggered when a wiki page is created/updated")
when "commit", "commit_events"
- "Event will be triggered when a commit is created/updated"
+ s_("ProjectService|Event will be triggered when a commit is created/updated")
+ when "deployment"
+ s_("ProjectService|Event will be triggered when a deployment finishes")
+ when "alert"
+ s_("ProjectService|Event will be triggered when a new, unique alert is recorded")
end
end
@@ -44,15 +48,8 @@ module ServicesHelper
end
end
- def event_action_description(action)
- case action
- when "comment"
- s_("ProjectService|Comment will be posted on each event")
- end
- end
-
- def service_save_button
- button_tag(class: 'btn btn-success', type: 'submit', data: { qa_selector: 'save_changes_button' }) do
+ def service_save_button(disabled: false)
+ button_tag(class: 'btn btn-success', type: 'submit', disabled: disabled, data: { qa_selector: 'save_changes_button' }) do
icon('spinner spin', class: 'hidden js-btn-spinner') +
content_tag(:span, 'Save changes', class: 'js-btn-label')
end
@@ -90,7 +87,7 @@ module ServicesHelper
def scoped_test_integration_path(integration)
if @project.present?
- test_project_settings_integration_path(@project, integration)
+ test_project_service_path(@project, integration)
elsif @group.present?
test_group_settings_integration_path(@group, integration)
else
@@ -99,25 +96,45 @@ module ServicesHelper
end
def integration_form_refactor?
- Feature.enabled?(:integration_form_refactor, @project)
+ Feature.enabled?(:integration_form_refactor, @project, default_enabled: true)
end
- def trigger_events_for_service
+ def integration_form_data(integration)
+ {
+ id: integration.id,
+ show_active: integration.show_active_box?.to_s,
+ activated: (integration.active || integration.new_record?).to_s,
+ type: integration.to_param,
+ merge_request_events: integration.merge_requests_events.to_s,
+ commit_events: integration.commit_events.to_s,
+ enable_comments: integration.comment_on_event_enabled.to_s,
+ comment_detail: integration.comment_detail,
+ trigger_events: trigger_events_for_service(integration),
+ fields: fields_for_service(integration),
+ inherit_from_id: integration.inherit_from_id
+ }
+ end
+
+ def trigger_events_for_service(integration)
return [] unless integration_form_refactor?
- ServiceEventSerializer.new(service: @service).represent(@service.configurable_events).to_json
+ ServiceEventSerializer.new(service: integration).represent(integration.configurable_events).to_json
end
- def fields_for_service
+ def fields_for_service(integration)
return [] unless integration_form_refactor?
- ServiceFieldSerializer.new(service: @service).represent(@service.global_fields).to_json
+ ServiceFieldSerializer.new(service: integration).represent(integration.global_fields).to_json
end
- def show_service_trigger_events?
- return false if @service.is_a?(JiraService) || integration_form_refactor?
+ def show_service_trigger_events?(integration)
+ return false if integration.is_a?(JiraService) || integration_form_refactor?
+
+ integration.configurable_events.present?
+ end
- @service.configurable_events.present?
+ def project_jira_issues_integration?
+ false
end
extend self
diff --git a/app/helpers/storage_helper.rb b/app/helpers/storage_helper.rb
index ce810433a3a..13bf9c92d52 100644
--- a/app/helpers/storage_helper.rb
+++ b/app/helpers/storage_helper.rb
@@ -14,9 +14,10 @@ module StorageHelper
counter_repositories: storage_counter(statistics.repository_size),
counter_wikis: storage_counter(statistics.wiki_size),
counter_build_artifacts: storage_counter(statistics.build_artifacts_size),
- counter_lfs_objects: storage_counter(statistics.lfs_objects_size)
+ counter_lfs_objects: storage_counter(statistics.lfs_objects_size),
+ counter_snippets: storage_counter(statistics.snippets_size)
}
- _("Repository: %{counter_repositories} / Wikis: %{counter_wikis} / Build Artifacts: %{counter_build_artifacts} / LFS: %{counter_lfs_objects}") % counters
+ _("Repository: %{counter_repositories} / Wikis: %{counter_wikis} / Build Artifacts: %{counter_build_artifacts} / LFS: %{counter_lfs_objects} / Snippets: %{counter_snippets}") % counters
end
end
diff --git a/app/helpers/system_note_helper.rb b/app/helpers/system_note_helper.rb
index 7baa615d36f..6ea6a33ba5e 100644
--- a/app/helpers/system_note_helper.rb
+++ b/app/helpers/system_note_helper.rb
@@ -31,7 +31,9 @@ module SystemNoteHelper
'designs_added' => 'doc-image',
'designs_modified' => 'doc-image',
'designs_removed' => 'doc-image',
- 'designs_discussion_added' => 'doc-image'
+ 'designs_discussion_added' => 'doc-image',
+ 'status' => 'status',
+ 'alert_issue_added' => 'issues'
}.freeze
def system_note_icon_name(note)
diff --git a/app/helpers/todos_helper.rb b/app/helpers/todos_helper.rb
index 2b4f2f11d1e..b9a6cab07a8 100644
--- a/app/helpers/todos_helper.rb
+++ b/app/helpers/todos_helper.rb
@@ -22,6 +22,7 @@ module TodosHelper
when Todo::APPROVAL_REQUIRED then "set #{todo_action_subject(todo)} as an approver for"
when Todo::UNMERGEABLE then 'Could not merge'
when Todo::DIRECTLY_ADDRESSED then "directly addressed #{todo_action_subject(todo)} on"
+ when Todo::MERGE_TRAIN_REMOVED then "Removed from Merge Train:"
end
end
@@ -97,11 +98,13 @@ module TodosHelper
'mr'
when Issue
'issue'
+ when AlertManagement::Alert
+ 'alert'
end
content_tag(:span, nil, class: 'target-status') do
- content_tag(:span, nil, class: "status-box status-box-#{type}-#{todo.target.state.dasherize}") do
- todo.target.state.capitalize
+ content_tag(:span, nil, class: "status-box status-box-#{type}-#{todo.target.state.to_s.dasherize}") do
+ todo.target.state.to_s.capitalize
end
end
end
@@ -195,6 +198,10 @@ module TodosHelper
"&middot; #{content}".html_safe
end
+ def todo_author_display?(todo)
+ !todo.build_failed? && !todo.unmergeable?
+ end
+
private
def todos_design_path(todo, path_options)
@@ -214,7 +221,14 @@ module TodosHelper
end
def show_todo_state?(todo)
- (todo.target.is_a?(MergeRequest) || todo.target.is_a?(Issue)) && %w(closed merged).include?(todo.target.state)
+ case todo.target
+ when MergeRequest, Issue
+ %w(closed merged).include?(todo.target.state)
+ when AlertManagement::Alert
+ %i(resolved).include?(todo.target.state)
+ else
+ false
+ end
end
def todo_group_options
diff --git a/app/helpers/tree_helper.rb b/app/helpers/tree_helper.rb
index 4dc00581703..90a5b6da4c7 100644
--- a/app/helpers/tree_helper.rb
+++ b/app/helpers/tree_helper.rb
@@ -191,8 +191,10 @@ module TreeHelper
def vue_file_list_data(project, ref)
{
+ can_push_code: current_user&.can?(:push_code, project) && "true",
project_path: project.full_path,
project_short_path: project.path,
+ fork_path: current_user&.fork_of(project)&.full_path,
ref: ref,
escaped_ref: ActionDispatch::Journey::Router::Utils.escape_path(ref),
full_name: project.name_with_namespace
diff --git a/app/helpers/wiki_helper.rb b/app/helpers/wiki_helper.rb
index 3c983606b73..cf2d2d178e1 100644
--- a/app/helpers/wiki_helper.rb
+++ b/app/helpers/wiki_helper.rb
@@ -3,6 +3,30 @@
module WikiHelper
include API::Helpers::RelatedResourcesHelpers
+ def wiki_page_title(page, action = nil)
+ titles = [_('Wiki')]
+
+ if page.persisted?
+ titles << page.human_title
+ breadcrumb_title(page.human_title)
+ wiki_breadcrumb_dropdown_links(page.slug)
+ end
+
+ titles << action if action
+ page_title(*titles.reverse)
+ add_to_breadcrumbs(_('Wiki'), wiki_path(page.wiki))
+ end
+
+ def link_to_wiki_page(page, **options)
+ link_to page.human_title, wiki_page_path(page.wiki, page), **options
+ end
+
+ def wiki_sidebar_toggle_button
+ content_tag :button, class: 'btn btn-default sidebar-toggle js-sidebar-wiki-toggle', role: 'button', type: 'button' do
+ sprite_icon('chevron-double-lg-left')
+ end
+ end
+
# Produces a pure text breadcrumb for a given page.
#
# page_slug - The slug of a WikiPage object.
@@ -71,10 +95,13 @@ module WikiHelper
def wiki_empty_state_messages(wiki)
case wiki.container
when Project
+ writable_body = s_("WikiEmpty|A wiki is where you can store all the details about your project. This can include why you've created it, its principles, how to use it, and so on.")
+ writable_body += s_("WikiEmpty| Have a Confluence wiki already? Use that instead.") if show_enable_confluence_integration?(wiki.container)
+
{
writable: {
title: s_('WikiEmpty|The wiki lets you write documentation for your project'),
- body: s_("WikiEmpty|A wiki is where you can store all the details about your project. This can include why you've created it, its principles, how to use it, and so on.")
+ body: writable_body
},
issuable: {
title: s_('WikiEmpty|This project has no wiki pages'),
@@ -104,4 +131,19 @@ module WikiHelper
raise NotImplementedError, "Unknown wiki container type #{wiki.container.class.name}"
end
end
+
+ def wiki_page_tracking_context(page)
+ {
+ 'wiki-format' => page.format,
+ 'wiki-title-size' => page.title.bytesize,
+ 'wiki-content-size' => page.raw_content.bytesize,
+ 'wiki-directory-nest-level' => page.path.scan('/').count
+ }
+ end
+
+ def show_enable_confluence_integration?(container)
+ container.is_a?(Project) &&
+ current_user&.can?(:admin_project, container) &&
+ !container.has_confluence?
+ end
end
diff --git a/app/mailers/emails/merge_requests.rb b/app/mailers/emails/merge_requests.rb
index 76b1c2d234c..c709c2950d6 100644
--- a/app/mailers/emails/merge_requests.rb
+++ b/app/mailers/emails/merge_requests.rb
@@ -92,6 +92,13 @@ module Emails
mail_answer_thread(@merge_request, merge_request_thread_options(resolved_by_user_id, recipient_id, reason))
end
+ def merge_when_pipeline_succeeds_email(recipient_id, merge_request_id, mwps_set_by_user_id, reason = nil)
+ setup_merge_request_mail(merge_request_id, recipient_id)
+
+ @mwps_set_by = ::User.find(mwps_set_by_user_id)
+ mail_answer_thread(@merge_request, merge_request_thread_options(mwps_set_by_user_id, recipient_id, reason))
+ end
+
private
def setup_merge_request_mail(merge_request_id, recipient_id, present: false)
diff --git a/app/mailers/emails/service_desk.rb b/app/mailers/emails/service_desk.rb
new file mode 100644
index 00000000000..29fe608472d
--- /dev/null
+++ b/app/mailers/emails/service_desk.rb
@@ -0,0 +1,92 @@
+# frozen_string_literal: true
+
+module Emails
+ module ServiceDesk
+ extend ActiveSupport::Concern
+ include MarkupHelper
+
+ included do
+ layout 'service_desk', only: [:service_desk_thank_you_email, :service_desk_new_note_email]
+ end
+
+ def service_desk_thank_you_email(issue_id)
+ setup_service_desk_mail(issue_id)
+
+ email_sender = sender(
+ @support_bot.id,
+ send_from_user_email: false,
+ sender_name: @project.service_desk_setting&.outgoing_name
+ )
+ options = service_desk_options(email_sender, 'thank_you')
+ .merge(subject: "Re: #{subject_base}")
+
+ mail_new_thread(@issue, options)
+ end
+
+ def service_desk_new_note_email(issue_id, note_id)
+ @note = Note.find(note_id)
+ setup_service_desk_mail(issue_id)
+
+ email_sender = sender(@note.author_id)
+ options = service_desk_options(email_sender, 'new_note')
+ .merge(subject: subject_base)
+
+ mail_answer_thread(@issue, options)
+ end
+
+ private
+
+ def setup_service_desk_mail(issue_id)
+ @issue = Issue.find(issue_id)
+ @project = @issue.project
+ @support_bot = User.support_bot
+
+ @sent_notification = SentNotification.record(@issue, @support_bot.id, reply_key)
+ end
+
+ def service_desk_options(email_sender, email_type)
+ {
+ from: email_sender,
+ to: @issue.service_desk_reply_to
+ }.tap do |options|
+ next unless template_body = template_content(email_type)
+
+ options[:body] = template_body
+ options[:content_type] = 'text/html'
+ end
+ end
+
+ def template_content(email_type)
+ template = Gitlab::Template::ServiceDeskTemplate.find(email_type, @project)
+
+ text = substitute_template_replacements(template.content)
+
+ markdown(text, project: @project)
+ rescue Gitlab::Template::Finders::RepoTemplateFinder::FileNotFoundError
+ nil
+ end
+
+ def substitute_template_replacements(template_body)
+ template_body
+ .gsub(/%\{\s*ISSUE_ID\s*\}/, issue_id)
+ .gsub(/%\{\s*ISSUE_PATH\s*\}/, issue_path)
+ .gsub(/%\{\s*NOTE_TEXT\s*\}/, note_text)
+ end
+
+ def issue_id
+ "#{Issue.reference_prefix}#{@issue.iid}"
+ end
+
+ def issue_path
+ @issue.to_reference(full: true)
+ end
+
+ def note_text
+ @note&.note.to_s
+ end
+
+ def subject_base
+ "#{@issue.title} (##{@issue.iid})"
+ end
+ end
+end
diff --git a/app/mailers/notify.rb b/app/mailers/notify.rb
index 2cf72d40635..f9aba3fe4f2 100644
--- a/app/mailers/notify.rb
+++ b/app/mailers/notify.rb
@@ -19,6 +19,7 @@ class Notify < ApplicationMailer
include Emails::Releases
include Emails::Groups
include Emails::Reviews
+ include Emails::ServiceDesk
helper TimeboxesHelper
helper MergeRequestsHelper
diff --git a/app/mailers/previews/notify_preview.rb b/app/mailers/previews/notify_preview.rb
index cb7c6a36c27..c70ac1428cd 100644
--- a/app/mailers/previews/notify_preview.rb
+++ b/app/mailers/previews/notify_preview.rb
@@ -165,6 +165,22 @@ class NotifyPreview < ActionMailer::Preview
Notify.unknown_sign_in_email(user, '127.0.0.1', Time.current).message
end
+ def service_desk_new_note_email
+ cleanup do
+ note = create_note(noteable_type: 'Issue', noteable_id: issue.id, note: 'Issue note content')
+
+ Notify.service_desk_new_note_email(issue.id, note.id).message
+ end
+ end
+
+ def service_desk_thank_you_email
+ Notify.service_desk_thank_you_email(issue.id).message
+ end
+
+ def merge_when_pipeline_succeeds_email
+ Notify.merge_when_pipeline_succeeds_email(user.id, merge_request.id, user.id).message
+ end
+
private
def project
diff --git a/app/models/active_session.rb b/app/models/active_session.rb
index a23190cc8b3..be07c221f32 100644
--- a/app/models/active_session.rb
+++ b/app/models/active_session.rb
@@ -91,8 +91,11 @@ class ActiveSession
key_names = session_ids.map { |session_id| key_name(user.id, session_id.public_id) }
redis.srem(lookup_key_name(user.id), session_ids.map(&:public_id))
- redis.del(key_names)
- redis.del(rack_session_keys(session_ids))
+
+ Gitlab::Instrumentation::RedisClusterValidator.allow_cross_slot_commands do
+ redis.del(key_names)
+ redis.del(rack_session_keys(session_ids))
+ end
end
def self.cleanup(user)
@@ -136,8 +139,10 @@ class ActiveSession
session_keys = rack_session_keys(session_ids)
session_keys.each_slice(SESSION_BATCH_SIZE).flat_map do |session_keys_batch|
- redis.mget(session_keys_batch).compact.map do |raw_session|
- load_raw_session(raw_session)
+ Gitlab::Instrumentation::RedisClusterValidator.allow_cross_slot_commands do
+ redis.mget(session_keys_batch).compact.map do |raw_session|
+ load_raw_session(raw_session)
+ end
end
end
end
@@ -178,7 +183,9 @@ class ActiveSession
entry_keys = session_ids.map { |session_id| key_name(user_id, session_id) }
- redis.mget(entry_keys)
+ Gitlab::Instrumentation::RedisClusterValidator.allow_cross_slot_commands do
+ redis.mget(entry_keys)
+ end
end
def self.active_session_entries(session_ids, user_id, redis)
diff --git a/app/models/alert_management/alert.rb b/app/models/alert_management/alert.rb
index af60ddd6f9a..fb166fb56b7 100644
--- a/app/models/alert_management/alert.rb
+++ b/app/models/alert_management/alert.rb
@@ -10,6 +10,7 @@ module AlertManagement
include Sortable
include Noteable
include Gitlab::SQL::Pattern
+ include Presentable
STATUSES = {
triggered: 0,
@@ -25,8 +26,17 @@ module AlertManagement
ignored: :ignore
}.freeze
+ OPEN_STATUSES = [
+ :triggered,
+ :acknowledged
+ ].freeze
+
+ DETAILS_IGNORED_PARAMS = %w(start_time).freeze
+
belongs_to :project
belongs_to :issue, optional: true
+ belongs_to :prometheus_alert, optional: true
+ belongs_to :environment, optional: true
has_many :alert_assignees, inverse_of: :alert
has_many :assignees, through: :alert_assignees
@@ -50,8 +60,12 @@ module AlertManagement
validates :severity, presence: true
validates :status, presence: true
validates :started_at, presence: true
- validates :fingerprint, uniqueness: { scope: :project }, allow_blank: true
- validate :hosts_length
+ validates :fingerprint, allow_blank: true, uniqueness: {
+ scope: :project,
+ conditions: -> { not_resolved },
+ message: -> (object, data) { _('Cannot have multiple unresolved alerts') }
+ }, unless: :resolved?
+ validate :hosts_length
enum severity: {
critical: 0,
@@ -108,15 +122,30 @@ module AlertManagement
scope :for_iid, -> (iid) { where(iid: iid) }
scope :for_status, -> (status) { where(status: status) }
scope :for_fingerprint, -> (project, fingerprint) { where(project: project, fingerprint: fingerprint) }
+ scope :for_environment, -> (environment) { where(environment: environment) }
scope :search, -> (query) { fuzzy_search(query, [:title, :description, :monitoring_tool, :service]) }
+ scope :open, -> { with_status(OPEN_STATUSES) }
+ scope :not_resolved, -> { where.not(status: STATUSES[:resolved]) }
+ scope :with_prometheus_alert, -> { includes(:prometheus_alert) }
scope :order_start_time, -> (sort_order) { order(started_at: sort_order) }
scope :order_end_time, -> (sort_order) { order(ended_at: sort_order) }
scope :order_event_count, -> (sort_order) { order(events: sort_order) }
- scope :order_severity, -> (sort_order) { order(severity: sort_order) }
- scope :order_status, -> (sort_order) { order(status: sort_order) }
+
+ # Ascending sort order sorts severity from less critical to more critical.
+ # Descending sort order sorts severity from more critical to less critical.
+ # https://gitlab.com/gitlab-org/gitlab/-/issues/221242#what-is-the-expected-correct-behavior
+ scope :order_severity, -> (sort_order) { order(severity: sort_order == :asc ? :desc : :asc) }
+
+ # Ascending sort order sorts statuses: Ignored > Resolved > Acknowledged > Triggered
+ # Descending sort order sorts statuses: Triggered > Acknowledged > Resolved > Ignored
+ # https://gitlab.com/gitlab-org/gitlab/-/issues/221242#what-is-the-expected-correct-behavior
+ scope :order_status, -> (sort_order) { order(status: sort_order == :asc ? :desc : :asc) }
scope :counts_by_status, -> { group(:status).count }
+ scope :counts_by_project_id, -> { group(:project_id).count }
+
+ alias_method :state, :status_name
def self.sort_by_attribute(method)
case method.to_s
@@ -135,8 +164,13 @@ module AlertManagement
end
end
+ def self.last_prometheus_alert_by_project_id
+ ids = select(arel_table[:id].maximum).group(:project_id)
+ with_prometheus_alert.where(id: ids)
+ end
+
def details
- details_payload = payload.except(*attributes.keys)
+ details_payload = payload.except(*attributes.keys, *DETAILS_IGNORED_PARAMS)
Gitlab::Utils::InlineHash.merge_keys(details_payload)
end
@@ -161,6 +195,12 @@ module AlertManagement
project.execute_services(hook_data, :alert_hooks)
end
+ def present
+ return super(presenter_class: AlertManagement::PrometheusAlertPresenter) if prometheus?
+
+ super
+ end
+
private
def hook_data
diff --git a/app/models/application_record.rb b/app/models/application_record.rb
index c7e4d64d3d5..9ec407a10a4 100644
--- a/app/models/application_record.rb
+++ b/app/models/application_record.rb
@@ -13,6 +13,10 @@ class ApplicationRecord < ActiveRecord::Base
where(id: ids)
end
+ def self.iid_in(iids)
+ where(iid: iids)
+ end
+
def self.id_not_in(ids)
where.not(id: ids)
end
@@ -34,6 +38,10 @@ class ApplicationRecord < ActiveRecord::Base
false
end
+ def self.at_most(count)
+ limit(count)
+ end
+
def self.safe_find_or_create_by!(*args)
safe_find_or_create_by(*args).tap do |record|
record.validate! unless record.persisted?
diff --git a/app/models/application_setting_implementation.rb b/app/models/application_setting_implementation.rb
index d24136cc04a..c489d11d462 100644
--- a/app/models/application_setting_implementation.rb
+++ b/app/models/application_setting_implementation.rb
@@ -50,6 +50,7 @@ module ApplicationSettingImplementation
default_artifacts_expire_in: '30 days',
default_branch_protection: Settings.gitlab['default_branch_protection'],
default_ci_config_path: nil,
+ default_branch_name: nil,
default_group_visibility: Settings.gitlab.default_projects_features['visibility_level'],
default_project_creation: Settings.gitlab['default_project_creation'],
default_project_visibility: Settings.gitlab.default_projects_features['visibility_level'],
@@ -88,6 +89,7 @@ module ApplicationSettingImplementation
max_attachment_size: Settings.gitlab['max_attachment_size'],
max_import_size: 50,
mirror_available: true,
+ notify_on_unknown_sign_in: true,
outbound_local_requests_whitelist: [],
password_authentication_enabled_for_git: true,
password_authentication_enabled_for_web: Settings.gitlab['signin_enabled'],
@@ -156,7 +158,13 @@ module ApplicationSettingImplementation
snowplow_iglu_registry_url: nil,
custom_http_clone_url_root: nil,
productivity_analytics_start_date: Time.current,
- snippet_size_limit: 50.megabytes
+ snippet_size_limit: 50.megabytes,
+ project_import_limit: 6,
+ project_export_limit: 6,
+ project_download_export_limit: 1,
+ group_import_limit: 6,
+ group_export_limit: 6,
+ group_download_export_limit: 1
}
end
diff --git a/app/models/approval.rb b/app/models/approval.rb
new file mode 100644
index 00000000000..bc123de0b20
--- /dev/null
+++ b/app/models/approval.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+class Approval < ApplicationRecord
+ belongs_to :user
+ belongs_to :merge_request
+
+ validates :merge_request_id, presence: true
+ validates :user_id, presence: true, uniqueness: { scope: [:merge_request_id] }
+
+ scope :with_user, -> { joins(:user) }
+end
diff --git a/app/models/audit_event.rb b/app/models/audit_event.rb
index 3bbd2e43a51..13fc2514f0c 100644
--- a/app/models/audit_event.rb
+++ b/app/models/audit_event.rb
@@ -3,8 +3,11 @@
class AuditEvent < ApplicationRecord
include CreatedAtFilterable
include IgnorableColumns
+ include BulkInsertSafe
- ignore_column :updated_at, remove_with: '13.3', remove_after: '2020-08-22'
+ PARALLEL_PERSISTENCE_COLUMNS = [:author_name, :entity_path].freeze
+
+ ignore_column :updated_at, remove_with: '13.4', remove_after: '2020-09-22'
serialize :details, Hash # rubocop:disable Cop/ActiveRecordSerialize
@@ -16,8 +19,15 @@ class AuditEvent < ApplicationRecord
scope :by_entity_type, -> (entity_type) { where(entity_type: entity_type) }
scope :by_entity_id, -> (entity_id) { where(entity_id: entity_id) }
+ scope :by_author_id, -> (author_id) { where(author_id: author_id) }
after_initialize :initialize_details
+ # Note: The intention is to remove this once refactoring of AuditEvent
+ # has proceeded further.
+ #
+ # See further details in the epic:
+ # https://gitlab.com/groups/gitlab-org/-/epics/2765
+ after_validation :parallel_persist
def self.order_by(method)
case method.to_s
@@ -51,7 +61,11 @@ class AuditEvent < ApplicationRecord
private
def default_author_value
- ::Gitlab::Audit::NullAuthor.for(author_id, details[:author_name])
+ ::Gitlab::Audit::NullAuthor.for(author_id, (self[:author_name] || details[:author_name]))
+ end
+
+ def parallel_persist
+ PARALLEL_PERSISTENCE_COLUMNS.each { |col| self[col] = details[col] }
end
end
diff --git a/app/models/blob_viewer/image.rb b/app/models/blob_viewer/image.rb
index cbebef46c60..97eb0489158 100644
--- a/app/models/blob_viewer/image.rb
+++ b/app/models/blob_viewer/image.rb
@@ -8,7 +8,7 @@ module BlobViewer
self.partial_name = 'image'
self.extensions = UploaderHelper::SAFE_IMAGE_EXT
self.binary = true
- self.switcher_icon = 'picture-o'
+ self.switcher_icon = 'doc-image'
self.switcher_title = 'image'
end
end
diff --git a/app/models/blob_viewer/notebook.rb b/app/models/blob_viewer/notebook.rb
index 57d6d802db3..351502d451f 100644
--- a/app/models/blob_viewer/notebook.rb
+++ b/app/models/blob_viewer/notebook.rb
@@ -8,7 +8,7 @@ module BlobViewer
self.partial_name = 'notebook'
self.extensions = %w(ipynb)
self.binary = false
- self.switcher_icon = 'file-text-o'
+ self.switcher_icon = 'doc-text'
self.switcher_title = 'notebook'
end
end
diff --git a/app/models/blob_viewer/open_api.rb b/app/models/blob_viewer/open_api.rb
index 963b7336c8d..0551f3bb1e3 100644
--- a/app/models/blob_viewer/open_api.rb
+++ b/app/models/blob_viewer/open_api.rb
@@ -8,8 +8,6 @@ module BlobViewer
self.partial_name = 'openapi'
self.file_types = %i(openapi)
self.binary = false
- # TODO: get an icon for OpenAPI
- self.switcher_icon = 'file-pdf-o'
- self.switcher_title = 'OpenAPI'
+ self.switcher_icon = 'api'
end
end
diff --git a/app/models/blob_viewer/rich.rb b/app/models/blob_viewer/rich.rb
index 0f66a672102..46f36cc2674 100644
--- a/app/models/blob_viewer/rich.rb
+++ b/app/models/blob_viewer/rich.rb
@@ -6,7 +6,7 @@ module BlobViewer
included do
self.type = :rich
- self.switcher_icon = 'file-text-o'
+ self.switcher_icon = 'doc-text'
self.switcher_title = 'rendered file'
end
end
diff --git a/app/models/blob_viewer/svg.rb b/app/models/blob_viewer/svg.rb
index 454c6a57568..60a11fbd97e 100644
--- a/app/models/blob_viewer/svg.rb
+++ b/app/models/blob_viewer/svg.rb
@@ -8,7 +8,7 @@ module BlobViewer
self.partial_name = 'svg'
self.extensions = %w(svg)
self.binary = false
- self.switcher_icon = 'picture-o'
+ self.switcher_icon = 'doc-image'
self.switcher_title = 'image'
end
end
diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb
index b5e68b55f72..6c90645e997 100644
--- a/app/models/ci/build.rb
+++ b/app/models/ci/build.rb
@@ -27,7 +27,7 @@ module Ci
upload_multiple_artifacts: -> (build) { build.publishes_artifacts_reports? },
refspecs: -> (build) { build.merge_request_ref? },
artifacts_exclude: -> (build) { build.supports_artifacts_exclude? },
- release_steps: -> (build) { build.release_steps? }
+ multi_build_steps: -> (build) { build.multi_build_steps? }
}.freeze
DEFAULT_RETRIES = {
@@ -539,7 +539,6 @@ module Ci
.concat(job_variables)
.concat(environment_changed_page_variables)
.concat(persisted_environment_variables)
- .concat(deploy_freeze_variables)
.to_runner_variables
end
end
@@ -595,18 +594,6 @@ module Ci
end
end
- def deploy_freeze_variables
- Gitlab::Ci::Variables::Collection.new.tap do |variables|
- break variables unless freeze_period?
-
- variables.append(key: 'CI_DEPLOY_FREEZE', value: 'true')
- end
- end
-
- def freeze_period?
- Ci::FreezePeriodStatus.new(project: project).execute
- end
-
def dependency_variables
return [] if all_dependencies.empty?
@@ -801,6 +788,11 @@ module Ci
has_expiring_artifacts? && job_artifacts_archive.present?
end
+ def self.keep_artifacts!
+ update_all(artifacts_expire_at: nil)
+ Ci::JobArtifact.where(job: self.select(:id)).update_all(expire_at: nil)
+ end
+
def keep_artifacts!
self.update(artifacts_expire_at: nil)
self.job_artifacts.update_all(expire_at: nil)
@@ -885,7 +877,7 @@ module Ci
Gitlab::Ci::Features.artifacts_exclude_enabled?
end
- def release_steps?
+ def multi_build_steps?
options.dig(:release)&.any? &&
Gitlab::Ci::Features.release_generation_enabled?
end
diff --git a/app/models/ci/build_metadata.rb b/app/models/ci/build_metadata.rb
index 0df5ebfe843..4094bdb26dc 100644
--- a/app/models/ci/build_metadata.rb
+++ b/app/models/ci/build_metadata.rb
@@ -19,6 +19,7 @@ module Ci
before_create :set_build_project
validates :build, presence: true
+ validates :secrets, json_schema: { filename: 'build_metadata_secrets' }
serialize :config_options, Serializers::JSON # rubocop:disable Cop/ActiveRecordSerialize
serialize :config_variables, Serializers::JSON # rubocop:disable Cop/ActiveRecordSerialize
diff --git a/app/models/ci/build_need.rb b/app/models/ci/build_need.rb
index 0b243c20e67..b977a5f4419 100644
--- a/app/models/ci/build_need.rb
+++ b/app/models/ci/build_need.rb
@@ -4,6 +4,8 @@ module Ci
class BuildNeed < ApplicationRecord
extend Gitlab::Ci::Model
+ include BulkInsertSafe
+
belongs_to :build, class_name: "Ci::Build", foreign_key: :build_id, inverse_of: :needs
validates :build, presence: true
diff --git a/app/models/ci/build_trace.rb b/app/models/ci/build_trace.rb
index b9db1559836..f70e1ed69ea 100644
--- a/app/models/ci/build_trace.rb
+++ b/app/models/ci/build_trace.rb
@@ -2,40 +2,22 @@
module Ci
class BuildTrace
- CONVERTERS = {
- html: Gitlab::Ci::Ansi2html,
- json: Gitlab::Ci::Ansi2json
- }.freeze
-
attr_reader :trace, :build
delegate :state, :append, :truncated, :offset, :size, :total, to: :trace, allow_nil: true
delegate :id, :status, :complete?, to: :build, prefix: true
- def initialize(build:, stream:, state:, content_format:)
+ def initialize(build:, stream:, state:)
@build = build
- @content_format = content_format
if stream.valid?
stream.limit
- @trace = CONVERTERS.fetch(content_format).convert(stream.stream, state)
+ @trace = Gitlab::Ci::Ansi2json.convert(stream.stream, state)
end
end
- def json?
- @content_format == :json
- end
-
- def html?
- @content_format == :html
- end
-
- def json_lines
- @trace&.lines if json?
- end
-
- def html_lines
- @trace&.html if html?
+ def lines
+ @trace&.lines
end
end
end
diff --git a/app/models/ci/build_trace_chunks/redis.rb b/app/models/ci/build_trace_chunks/redis.rb
index 813eaf5d839..c3864f78b01 100644
--- a/app/models/ci/build_trace_chunks/redis.rb
+++ b/app/models/ci/build_trace_chunks/redis.rb
@@ -35,7 +35,10 @@ module Ci
keys = keys.map { |key| key_raw(*key) }
Gitlab::Redis::SharedState.with do |redis|
- redis.del(keys)
+ # https://gitlab.com/gitlab-org/gitlab/-/issues/224171
+ Gitlab::Instrumentation::RedisClusterValidator.allow_cross_slot_commands do
+ redis.del(keys)
+ end
end
end
diff --git a/app/models/ci/instance_variable.rb b/app/models/ci/instance_variable.rb
index 8245729a884..628749b32cb 100644
--- a/app/models/ci/instance_variable.rb
+++ b/app/models/ci/instance_variable.rb
@@ -45,13 +45,5 @@ module Ci
end
end
end
-
- private
-
- def validate_plan_limit_not_exceeded
- if Gitlab::Ci::Features.instance_level_variables_limit_enabled?
- super
- end
- end
end
end
diff --git a/app/models/ci/job_artifact.rb b/app/models/ci/job_artifact.rb
index 8aba9356949..dbeba1ece31 100644
--- a/app/models/ci/job_artifact.rb
+++ b/app/models/ci/job_artifact.rb
@@ -7,10 +7,13 @@ module Ci
include UpdateProjectStatistics
include UsageStatistics
include Sortable
+ include IgnorableColumns
extend Gitlab::Ci::Model
NotSupportedAdapterError = Class.new(StandardError)
+ ignore_columns :locked, remove_after: '2020-07-22', remove_with: '13.4'
+
TEST_REPORT_FILE_TYPES = %w[junit].freeze
COVERAGE_REPORT_FILE_TYPES = %w[cobertura].freeze
ACCESSIBILITY_REPORT_FILE_TYPES = %w[accessibility].freeze
@@ -34,13 +37,16 @@ module Ci
license_management: 'gl-license-management-report.json',
license_scanning: 'gl-license-scanning-report.json',
performance: 'performance.json',
+ browser_performance: 'browser-performance.json',
+ load_performance: 'load-performance.json',
metrics: 'metrics.txt',
lsif: 'lsif.json',
dotenv: '.env',
cobertura: 'cobertura-coverage.xml',
terraform: 'tfplan.json',
cluster_applications: 'gl-cluster-applications.json',
- requirements: 'requirements.json'
+ requirements: 'requirements.json',
+ coverage_fuzzing: 'gl-coverage-fuzzing.json'
}.freeze
INTERNAL_TYPES = {
@@ -72,8 +78,11 @@ module Ci
license_management: :raw,
license_scanning: :raw,
performance: :raw,
+ browser_performance: :raw,
+ load_performance: :raw,
terraform: :raw,
- requirements: :raw
+ requirements: :raw,
+ coverage_fuzzing: :raw
}.freeze
DOWNLOADABLE_TYPES = %w[
@@ -91,6 +100,8 @@ module Ci
lsif
metrics
performance
+ browser_performance
+ load_performance
sast
secret_detection
requirements
@@ -98,9 +109,7 @@ module Ci
TYPE_AND_FORMAT_PAIRS = INTERNAL_TYPES.merge(REPORT_TYPES).freeze
- # This is required since we cannot add a default to the database
- # https://gitlab.com/gitlab-org/gitlab/-/issues/215418
- attribute :locked, :boolean, default: false
+ PLAN_LIMIT_PREFIX = 'ci_max_artifact_size_'
belongs_to :project
belongs_to :job, class_name: "Ci::Build", foreign_key: :job_id
@@ -117,10 +126,9 @@ module Ci
after_save :update_file_store, if: :saved_change_to_file?
scope :not_expired, -> { where('expire_at IS NULL OR expire_at > ?', Time.current) }
- scope :with_files_stored_locally, -> { where(file_store: [nil, ::JobArtifactUploader::Store::LOCAL]) }
+ scope :with_files_stored_locally, -> { where(file_store: ::JobArtifactUploader::Store::LOCAL) }
scope :with_files_stored_remotely, -> { where(file_store: ::JobArtifactUploader::Store::REMOTE) }
scope :for_sha, ->(sha, project_id) { joins(job: :pipeline).where(ci_pipelines: { sha: sha, project_id: project_id }) }
- scope :for_ref, ->(ref, project_id) { joins(job: :pipeline).where(ci_pipelines: { ref: ref, project_id: project_id }) }
scope :for_job_name, ->(name) { joins(:job).where(ci_builds: { name: name }) }
scope :with_file_types, -> (file_types) do
@@ -157,8 +165,7 @@ module Ci
scope :expired, -> (limit) { where('expire_at < ?', Time.current).limit(limit) }
scope :downloadable, -> { where(file_type: DOWNLOADABLE_TYPES) }
- scope :locked, -> { where(locked: true) }
- scope :unlocked, -> { where(locked: [false, nil]) }
+ scope :unlocked, -> { joins(job: :pipeline).merge(::Ci::Pipeline.unlocked).order(expire_at: :desc) }
scope :scoped_project, -> { where('ci_job_artifacts.project_id = projects.id') }
@@ -176,7 +183,7 @@ module Ci
codequality: 9, ## EE-specific
license_management: 10, ## EE-specific
license_scanning: 101, ## EE-specific till 13.0
- performance: 11, ## EE-specific
+ performance: 11, ## EE-specific till 13.2
metrics: 12, ## EE-specific
metrics_referee: 13, ## runner referees
network_referee: 14, ## runner referees
@@ -187,7 +194,10 @@ module Ci
accessibility: 19,
cluster_applications: 20,
secret_detection: 21, ## EE-specific
- requirements: 22 ## EE-specific
+ requirements: 22, ## EE-specific
+ coverage_fuzzing: 23, ## EE-specific
+ browser_performance: 24, ## EE-specific
+ load_performance: 25 ## EE-specific
}
enum file_format: {
@@ -235,6 +245,12 @@ module Ci
self.update_column(:file_store, file.object_store)
end
+ def self.associated_file_types_for(file_type)
+ return unless file_types.include?(file_type)
+
+ [file_type]
+ end
+
def self.total_size
self.sum(:size)
end
@@ -286,6 +302,21 @@ module Ci
where(job_id: job_id).trace.take&.file&.file&.exists?
end
+ def self.max_artifact_size(type:, project:)
+ max_size = if Feature.enabled?(:ci_max_artifact_size_per_type, project, default_enabled: false)
+ limit_name = "#{PLAN_LIMIT_PREFIX}#{type}"
+
+ project.actual_limits.limit_for(
+ limit_name,
+ alternate_limit: -> { project.closest_setting(:max_artifacts_size) }
+ )
+ else
+ project.closest_setting(:max_artifacts_size)
+ end
+
+ max_size&.megabytes.to_i
+ end
+
private
def file_format_adapter_class
diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb
index 497e1a4d74a..d4b439d648f 100644
--- a/app/models/ci/pipeline.rb
+++ b/app/models/ci/pipeline.rb
@@ -3,7 +3,7 @@
module Ci
class Pipeline < ApplicationRecord
extend Gitlab::Ci::Model
- include HasStatus
+ include Ci::HasStatus
include Importable
include AfterCommitQueue
include Presentable
@@ -51,6 +51,8 @@ module Ci
has_many :latest_builds, -> { latest }, foreign_key: :commit_id, inverse_of: :pipeline, class_name: 'Ci::Build'
has_many :downloadable_artifacts, -> { not_expired.downloadable }, through: :latest_builds, source: :job_artifacts
+ has_many :messages, class_name: 'Ci::PipelineMessage', inverse_of: :pipeline
+
# Merge requests for which the current pipeline is running against
# the merge request's latest commit.
has_many :merge_requests_as_head_pipeline, foreign_key: "head_pipeline_id", class_name: 'MergeRequest'
@@ -80,6 +82,7 @@ module Ci
has_one :pipeline_config, class_name: 'Ci::PipelineConfig', inverse_of: :pipeline
has_many :daily_build_group_report_results, class_name: 'Ci::DailyBuildGroupReportResult', foreign_key: :last_pipeline_id
+ has_many :latest_builds_report_results, through: :latest_builds, source: :report_results
accepts_nested_attributes_for :variables, reject_if: :persisted?
@@ -110,6 +113,8 @@ module Ci
# extend this `Hash` with new values.
enum failure_reason: ::Ci::PipelineEnums.failure_reasons
+ enum locked: { unlocked: 0, artifacts_locked: 1 }
+
state_machine :status, initial: :created do
event :enqueue do
transition [:created, :manual, :waiting_for_resource, :preparing, :skipped, :scheduled] => :pending
@@ -244,6 +249,14 @@ module Ci
pipeline.run_after_commit { AutoDevops::DisableWorker.perform_async(pipeline.id) }
end
+
+ after_transition any => [:success] do |pipeline|
+ next unless Gitlab::Ci::Features.keep_latest_artifacts_for_ref_enabled?(pipeline.project)
+
+ pipeline.run_after_commit do
+ Ci::PipelineSuccessUnlockArtifactsWorker.perform_async(pipeline.id)
+ end
+ end
end
scope :internal, -> { where(source: internal_sources) }
@@ -256,7 +269,14 @@ module Ci
scope :for_ref, -> (ref) { where(ref: ref) }
scope :for_id, -> (id) { where(id: id) }
scope :for_iid, -> (iid) { where(iid: iid) }
+ scope :for_project, -> (project) { where(project: project) }
scope :created_after, -> (time) { where('ci_pipelines.created_at > ?', time) }
+ scope :created_before_id, -> (id) { where('ci_pipelines.id < ?', id) }
+ scope :before_pipeline, -> (pipeline) { created_before_id(pipeline.id).outside_pipeline_family(pipeline) }
+
+ scope :outside_pipeline_family, ->(pipeline) do
+ where.not(id: pipeline.same_family_pipeline_ids)
+ end
scope :with_reports, -> (reports_scope) do
where('EXISTS (?)', ::Ci::Build.latest.with_reports(reports_scope).where('ci_pipelines.id=ci_builds.commit_id').select(1))
@@ -270,6 +290,15 @@ module Ci
)
end
+ # Returns the pipelines that associated with the given merge request.
+ # In general, please use `Ci::PipelinesForMergeRequestFinder` instead,
+ # for checking permission of the actor.
+ scope :triggered_by_merge_request, -> (merge_request) do
+ ci_sources.where(source: :merge_request_event,
+ merge_request: merge_request,
+ project: [merge_request.source_project, merge_request.target_project])
+ end
+
# Returns the pipelines in descending order (= newest first), optionally
# limited to a number of references.
#
@@ -348,6 +377,10 @@ module Ci
success.group(:project_id).select('max(id) as id')
end
+ def self.last_finished_for_ref_id(ci_ref_id)
+ where(ci_ref_id: ci_ref_id).ci_sources.finished.order(id: :desc).select(:id).take
+ end
+
def self.truncate_sha(sha)
sha[0...8]
end
@@ -440,6 +473,10 @@ module Ci
end
end
+ def triggered_pipelines_with_preloads
+ triggered_pipelines.preload(:source_job)
+ end
+
def legacy_stages
if ::Gitlab::Ci::Features.composite_status?(project)
legacy_stages_using_composite_status
@@ -552,10 +589,28 @@ module Ci
end
end
+ def lazy_ref_commit
+ return unless ::Gitlab::Ci::Features.pipeline_latest?
+
+ BatchLoader.for(ref).batch do |refs, loader|
+ next unless project.repository_exists?
+
+ project.repository.list_commits_by_ref_name(refs).then do |commits|
+ commits.each { |key, commit| loader.call(key, commits[key]) }
+ end
+ end
+ end
+
def latest?
return false unless git_ref && commit.present?
- project.commit(git_ref) == commit
+ unless ::Gitlab::Ci::Features.pipeline_latest?
+ return project.commit(git_ref) == commit
+ end
+
+ return false if lazy_ref_commit.nil?
+
+ lazy_ref_commit.id == commit.id
end
def retried
@@ -569,10 +624,46 @@ module Ci
end
end
+ def batch_lookup_report_artifact_for_file_type(file_type)
+ latest_report_artifacts
+ .values_at(*::Ci::JobArtifact.associated_file_types_for(file_type.to_s))
+ .flatten
+ .compact
+ .last
+ end
+
+ # This batch loads the latest reports for each CI job artifact
+ # type (e.g. sast, dast, etc.) in a single SQL query to eliminate
+ # the need to do N different `job_artifacts.where(file_type:
+ # X).last` calls.
+ #
+ # Return a hash of file type => array of 1 job artifact
+ def latest_report_artifacts
+ ::Gitlab::SafeRequestStore.fetch("pipeline:#{self.id}:latest_report_artifacts") do
+ # Note we use read_attribute(:project_id) to read the project
+ # ID instead of self.project_id. The latter appears to load
+ # the Project model. This extra filter doesn't appear to
+ # affect query plan but included to ensure we don't leak the
+ # wrong informaiton.
+ ::Ci::JobArtifact.where(
+ id: job_artifacts.with_reports
+ .select('max(ci_job_artifacts.id) as id')
+ .where(project_id: self.read_attribute(:project_id))
+ .group(:file_type)
+ )
+ .preload(:job)
+ .group_by(&:file_type)
+ end
+ end
+
def has_kubernetes_active?
project.deployment_platform&.active?
end
+ def freeze_period?
+ Ci::FreezePeriodStatus.new(project: project).execute
+ end
+
def has_warnings?
number_of_warnings.positive?
end
@@ -607,6 +698,25 @@ module Ci
yaml_errors.present?
end
+ def add_error_message(content)
+ add_message(:error, content)
+ end
+
+ def add_warning_message(content)
+ add_message(:warning, content)
+ end
+
+ # We can't use `messages.error` scope here because messages should also be
+ # read when the pipeline is not persisted. Using the scope will return no
+ # results as it would query persisted data.
+ def error_messages
+ messages.select(&:error?)
+ end
+
+ def warning_messages
+ messages.select(&:warning?)
+ end
+
# Manually set the notes for a Ci::Pipeline
# There is no ActiveRecord relation between Ci::Pipeline and notes
# as they are related to a commit sha. This method helps importing
@@ -639,7 +749,7 @@ module Ci
when 'manual' then block
when 'scheduled' then delay
else
- raise HasStatus::UnknownStatusError,
+ raise Ci::HasStatus::UnknownStatusError,
"Unknown status `#{new_status}`"
end
end
@@ -683,6 +793,7 @@ module Ci
end
variables.append(key: 'CI_KUBERNETES_ACTIVE', value: 'true') if has_kubernetes_active?
+ variables.append(key: 'CI_DEPLOY_FREEZE', value: 'true') if freeze_period?
if external_pull_request_event? && external_pull_request
variables.concat(external_pull_request.predefined_variables)
@@ -748,13 +859,10 @@ module Ci
end
# If pipeline is a child of another pipeline, include the parent
- # and the siblings, otherwise return only itself.
+ # and the siblings, otherwise return only itself and children.
def same_family_pipeline_ids
- if (parent = parent_pipeline)
- [parent.id] + parent.child_pipelines.pluck(:id)
- else
- [self.id]
- end
+ parent = parent_pipeline || self
+ [parent.id] + parent.child_pipelines.pluck(:id)
end
def bridge_triggered?
@@ -802,6 +910,10 @@ module Ci
complete? && latest_report_builds(reports_scope).exists?
end
+ def test_report_summary
+ Gitlab::Ci::Reports::TestReportSummary.new(latest_builds_report_results)
+ end
+
def test_reports
Gitlab::Ci::Reports::TestReports.new.tap do |test_reports|
latest_report_builds(Ci::JobArtifact.test_reports).preload(:project).find_each do |build|
@@ -840,6 +952,10 @@ module Ci
end
end
+ def has_archive_artifacts?
+ complete? && builds.latest.with_existing_job_artifacts(Ci::JobArtifact.archive.or(Ci::JobArtifact.metadata)).exists?
+ end
+
def has_exposed_artifacts?
complete? && builds.latest.with_exposed_artifacts.exists?
end
@@ -925,7 +1041,7 @@ module Ci
stages.find_by!(name: name)
end
- def error_messages
+ def full_error_messages
errors ? errors.full_messages.to_sentence : ""
end
@@ -964,8 +1080,6 @@ module Ci
# Set scheduling type of processables if they were created before scheduling_type
# data was deployed (https://gitlab.com/gitlab-org/gitlab/-/merge_requests/22246).
def ensure_scheduling_type!
- return unless ::Gitlab::Ci::Features.ensure_scheduling_type_enabled?
-
processables.populate_scheduling_type!
end
@@ -977,6 +1091,12 @@ module Ci
private
+ def add_message(severity, content)
+ return unless Gitlab::Ci::Features.store_pipeline_messages?(project)
+
+ messages.build(severity: severity, content: content)
+ end
+
def pipeline_data
Gitlab::DataBuilder::Pipeline.build(self)
end
diff --git a/app/models/ci/pipeline_enums.rb b/app/models/ci/pipeline_enums.rb
index 2ccd8445aa8..352dc56aac7 100644
--- a/app/models/ci/pipeline_enums.rb
+++ b/app/models/ci/pipeline_enums.rb
@@ -31,7 +31,7 @@ module Ci
merge_request_event: 10,
external_pull_request_event: 11,
parent_pipeline: 12,
- ondemand_scan: 13
+ ondemand_dast_scan: 13
}
end
@@ -45,7 +45,8 @@ module Ci
webide_source: 3,
remote_source: 4,
external_project_source: 5,
- bridge_source: 6
+ bridge_source: 6,
+ parameter_source: 7
}
end
diff --git a/app/models/ci/pipeline_message.rb b/app/models/ci/pipeline_message.rb
new file mode 100644
index 00000000000..a47ec554462
--- /dev/null
+++ b/app/models/ci/pipeline_message.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+module Ci
+ class PipelineMessage < ApplicationRecord
+ extend Gitlab::Ci::Model
+
+ MAX_CONTENT_LENGTH = 10_000
+
+ belongs_to :pipeline
+
+ validates :content, presence: true
+
+ before_save :truncate_long_content
+
+ enum severity: { error: 0, warning: 1 }
+
+ private
+
+ def truncate_long_content
+ return if content.length <= MAX_CONTENT_LENGTH
+
+ self.content = content.truncate(MAX_CONTENT_LENGTH)
+ end
+ end
+end
diff --git a/app/models/ci/ref.rb b/app/models/ci/ref.rb
index be6062b6e6e..29b44575d65 100644
--- a/app/models/ci/ref.rb
+++ b/app/models/ci/ref.rb
@@ -43,7 +43,7 @@ module Ci
end
def last_finished_pipeline_id
- Ci::Pipeline.where(ci_ref_id: self.id).finished.order(id: :desc).select(:id).take&.id
+ Ci::Pipeline.last_finished_for_ref_id(self.id)&.id
end
def update_status_by!(pipeline)
diff --git a/app/models/ci/runner.rb b/app/models/ci/runner.rb
index 8fc273556f0..1cd6c64841b 100644
--- a/app/models/ci/runner.rb
+++ b/app/models/ci/runner.rb
@@ -239,6 +239,10 @@ module Ci
runner_projects.count == 1
end
+ def belongs_to_more_than_one_project?
+ self.projects.limit(2).count(:all) > 1
+ end
+
def assigned_to_group?
runner_namespaces.any?
end
diff --git a/app/models/ci/stage.rb b/app/models/ci/stage.rb
index a316b4718e0..41215601704 100644
--- a/app/models/ci/stage.rb
+++ b/app/models/ci/stage.rb
@@ -4,10 +4,10 @@ module Ci
class Stage < ApplicationRecord
extend Gitlab::Ci::Model
include Importable
- include HasStatus
+ include Ci::HasStatus
include Gitlab::OptimisticLocking
- enum status: HasStatus::STATUSES_ENUM
+ enum status: Ci::HasStatus::STATUSES_ENUM
belongs_to :project
belongs_to :pipeline
@@ -98,7 +98,7 @@ module Ci
when 'scheduled' then delay
when 'skipped', nil then skip
else
- raise HasStatus::UnknownStatusError,
+ raise Ci::HasStatus::UnknownStatusError,
"Unknown status `#{new_status}`"
end
end
diff --git a/app/models/ci/variable.rb b/app/models/ci/variable.rb
index 08d39595c61..13358b95a47 100644
--- a/app/models/ci/variable.rb
+++ b/app/models/ci/variable.rb
@@ -18,5 +18,7 @@ module Ci
}
scope :unprotected, -> { where(protected: false) }
+ scope :by_key, -> (key) { where(key: key) }
+ scope :by_environment_scope, -> (environment_scope) { where(environment_scope: environment_scope) }
end
end
diff --git a/app/models/clusters/applications/cilium.rb b/app/models/clusters/applications/cilium.rb
new file mode 100644
index 00000000000..7936b0b18de
--- /dev/null
+++ b/app/models/clusters/applications/cilium.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+module Clusters
+ module Applications
+ class Cilium < ApplicationRecord
+ self.table_name = 'clusters_applications_cilium'
+
+ include ::Clusters::Concerns::ApplicationCore
+ include ::Clusters::Concerns::ApplicationStatus
+
+ # Cilium can only be installed and uninstalled through the
+ # cluster-applications project by triggering CI pipeline for a
+ # management project. UI operations are not available for such
+ # applications. More information:
+ # https://docs.gitlab.com/ee/user/clusters/management_project.html
+ def allowed_to_uninstall?
+ false
+ end
+ end
+ end
+end
diff --git a/app/models/clusters/applications/prometheus.rb b/app/models/clusters/applications/prometheus.rb
index 24bb1df6d22..101d782db3a 100644
--- a/app/models/clusters/applications/prometheus.rb
+++ b/app/models/clusters/applications/prometheus.rb
@@ -17,6 +17,9 @@ module Clusters
default_value_for :version, VERSION
+ scope :preload_cluster_platform, -> { preload(cluster: [:platform_kubernetes]) }
+ scope :with_clusters_with_cilium, -> { joins(:cluster).merge(Clusters::Cluster.with_available_cilium) }
+
attr_encrypted :alert_manager_token,
mode: :per_attribute_iv,
key: Settings.attr_encrypted_db_key_base_truncated,
diff --git a/app/models/clusters/applications/runner.rb b/app/models/clusters/applications/runner.rb
index 6d3b6c4ed8f..9ec7c194a26 100644
--- a/app/models/clusters/applications/runner.rb
+++ b/app/models/clusters/applications/runner.rb
@@ -3,7 +3,7 @@
module Clusters
module Applications
class Runner < ApplicationRecord
- VERSION = '0.17.1'
+ VERSION = '0.18.1'
self.table_name = 'clusters_applications_runners'
diff --git a/app/models/clusters/cluster.rb b/app/models/clusters/cluster.rb
index bde7a2104ba..7641b6d2a4b 100644
--- a/app/models/clusters/cluster.rb
+++ b/app/models/clusters/cluster.rb
@@ -2,6 +2,7 @@
module Clusters
class Cluster < ApplicationRecord
+ prepend HasEnvironmentScope
include Presentable
include Gitlab::Utils::StrongMemoize
include FromUnion
@@ -20,7 +21,8 @@ module Clusters
Clusters::Applications::Jupyter.application_name => Clusters::Applications::Jupyter,
Clusters::Applications::Knative.application_name => Clusters::Applications::Knative,
Clusters::Applications::ElasticStack.application_name => Clusters::Applications::ElasticStack,
- Clusters::Applications::Fluentd.application_name => Clusters::Applications::Fluentd
+ Clusters::Applications::Fluentd.application_name => Clusters::Applications::Fluentd,
+ Clusters::Applications::Cilium.application_name => Clusters::Applications::Cilium
}.freeze
DEFAULT_ENVIRONMENT = '*'
KUBE_INGRESS_BASE_DOMAIN = 'KUBE_INGRESS_BASE_DOMAIN'
@@ -64,6 +66,7 @@ module Clusters
has_one_cluster_application :knative
has_one_cluster_application :elastic_stack
has_one_cluster_application :fluentd
+ has_one_cluster_application :cilium
has_many :kubernetes_namespaces
has_many :metrics_dashboard_annotations, class_name: 'Metrics::Dashboard::Annotation', inverse_of: :cluster
@@ -81,6 +84,7 @@ module Clusters
validate :no_groups, unless: :group_type?
validate :no_projects, unless: :project_type?
validate :unique_management_project_environment_scope
+ validate :unique_environment_scope
after_save :clear_reactive_cache!
@@ -129,6 +133,7 @@ module Clusters
scope :with_enabled_modsecurity, -> { joins(:application_ingress).merge(::Clusters::Applications::Ingress.modsecurity_enabled) }
scope :with_available_elasticstack, -> { joins(:application_elastic_stack).merge(::Clusters::Applications::ElasticStack.available) }
+ scope :with_available_cilium, -> { joins(:application_cilium).merge(::Clusters::Applications::Cilium.available) }
scope :distinct_with_deployed_environments, -> { joins(:environments).merge(::Deployment.success).distinct }
scope :preload_elasticstack, -> { preload(:application_elastic_stack) }
scope :preload_environments, -> { preload(:environments) }
@@ -228,7 +233,9 @@ module Clusters
def calculate_reactive_cache
return unless enabled?
- { connection_status: retrieve_connection_status, nodes: retrieve_nodes }
+ gitlab_kubernetes_nodes = Gitlab::Kubernetes::Node.new(self)
+
+ { connection_status: retrieve_connection_status, nodes: gitlab_kubernetes_nodes.all.presence }
end
def persisted_applications
@@ -335,7 +342,11 @@ module Clusters
end
def local_tiller_enabled?
- Feature.enabled?(:managed_apps_local_tiller, clusterable, default_enabled: false)
+ Feature.enabled?(:managed_apps_local_tiller, clusterable, default_enabled: true)
+ end
+
+ def prometheus_adapter
+ application_prometheus
end
private
@@ -352,6 +363,12 @@ module Clusters
end
end
+ def unique_environment_scope
+ if clusterable.present? && clusterable.clusters.where(environment_scope: environment_scope).where.not(id: id).exists?
+ errors.add(:environment_scope, 'cannot add duplicated environment scope')
+ end
+ end
+
def managed_namespace(environment)
Clusters::KubernetesNamespaceFinder.new(
self,
@@ -383,54 +400,6 @@ module Clusters
result[:status]
end
- def retrieve_nodes
- result = ::Gitlab::Kubernetes::KubeClient.graceful_request(id) { kubeclient.get_nodes }
-
- return unless result[:response]
-
- cluster_nodes = result[:response]
-
- result = ::Gitlab::Kubernetes::KubeClient.graceful_request(id) { kubeclient.metrics_client.get_nodes }
- nodes_metrics = result[:response].to_a
-
- cluster_nodes.inject([]) do |memo, node|
- sliced_node = filter_relevant_node_attributes(node)
-
- matched_node_metric = nodes_metrics.find { |node_metric| node_metric.metadata.name == node.metadata.name }
-
- sliced_node_metrics = matched_node_metric ? filter_relevant_node_metrics_attributes(matched_node_metric) : {}
-
- memo << sliced_node.merge(sliced_node_metrics)
- end
- end
-
- def filter_relevant_node_attributes(node)
- {
- 'metadata' => {
- 'name' => node.metadata.name
- },
- 'status' => {
- 'capacity' => {
- 'cpu' => node.status.capacity.cpu,
- 'memory' => node.status.capacity.memory
- },
- 'allocatable' => {
- 'cpu' => node.status.allocatable.cpu,
- 'memory' => node.status.allocatable.memory
- }
- }
- }
- end
-
- def filter_relevant_node_metrics_attributes(node_metrics)
- {
- 'usage' => {
- 'cpu' => node_metrics.usage.cpu,
- 'memory' => node_metrics.usage.memory
- }
- }
- end
-
# To keep backward compatibility with AUTO_DEVOPS_DOMAIN
# environment variable, we need to ensure KUBE_INGRESS_BASE_DOMAIN
# is set if AUTO_DEVOPS_DOMAIN is set on any of the following options:
diff --git a/app/models/clusters/platforms/kubernetes.rb b/app/models/clusters/platforms/kubernetes.rb
index 444368d0ef3..7af78960e35 100644
--- a/app/models/clusters/platforms/kubernetes.rb
+++ b/app/models/clusters/platforms/kubernetes.rb
@@ -159,7 +159,16 @@ module Clusters
if ca_pem.present?
opts[:cert_store] = OpenSSL::X509::Store.new
- opts[:cert_store].add_cert(OpenSSL::X509::Certificate.new(ca_pem))
+
+ file = Tempfile.new('cluster_ca_pem_temp')
+ begin
+ file.write(ca_pem)
+ file.rewind
+ opts[:cert_store].add_file(file.path)
+ ensure
+ file.close
+ file.unlink # deletes the temp file
+ end
end
opts
diff --git a/app/models/commit.rb b/app/models/commit.rb
index 681fe727456..53bcdf8165f 100644
--- a/app/models/commit.rb
+++ b/app/models/commit.rb
@@ -469,10 +469,12 @@ class Commit
# We don't want to do anything for `Commit` model, so this is empty.
end
- WIP_REGEX = /\A\s*(((?i)(\[WIP\]|WIP:|WIP)\s|WIP$))|(fixup!|squash!)\s/.freeze
+ # WIP is deprecated in favor of Draft. Currently both options are supported
+ # https://gitlab.com/gitlab-org/gitlab/-/issues/227426
+ DRAFT_REGEX = /\A\s*#{Regexp.union(Gitlab::Regex.merge_request_wip, Gitlab::Regex.merge_request_draft)}|(fixup!|squash!)\s/.freeze
def work_in_progress?
- !!(title =~ WIP_REGEX)
+ !!(title =~ DRAFT_REGEX)
end
def merged_merge_request?(user)
diff --git a/app/models/commit_collection.rb b/app/models/commit_collection.rb
index 456d32bf403..b8653f47392 100644
--- a/app/models/commit_collection.rb
+++ b/app/models/commit_collection.rb
@@ -53,6 +53,17 @@ class CommitCollection
self
end
+ # Returns the collection with markdown fields preloaded.
+ #
+ # Get the markdown cache from redis using pipeline to prevent n+1 requests
+ # when rendering the markdown of an attribute (e.g. title, full_title,
+ # description).
+ def with_markdown_cache
+ Commit.preload_markdown_cache!(commits)
+
+ self
+ end
+
def unenriched
commits.reject(&:gitaly_commit?)
end
diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb
index 475f82f23ca..c85292feb25 100644
--- a/app/models/commit_status.rb
+++ b/app/models/commit_status.rb
@@ -1,11 +1,12 @@
# frozen_string_literal: true
class CommitStatus < ApplicationRecord
- include HasStatus
+ include Ci::HasStatus
include Importable
include AfterCommitQueue
include Presentable
include EnumWithNil
+ include BulkInsertableAssociations
self.table_name = 'ci_builds'
diff --git a/app/models/concerns/analytics/cycle_analytics/stage.rb b/app/models/concerns/analytics/cycle_analytics/stage.rb
index 39e8408f794..f1c39dda49d 100644
--- a/app/models/concerns/analytics/cycle_analytics/stage.rb
+++ b/app/models/concerns/analytics/cycle_analytics/stage.rb
@@ -125,7 +125,7 @@ module Analytics
def label_available_for_group?(label_id)
LabelsFinder.new(nil, { group_id: group.id, include_ancestor_groups: true, only_group_labels: true })
.execute(skip_authorization: true)
- .by_ids(label_id)
+ .id_in(label_id)
.exists?
end
end
diff --git a/app/models/concerns/approvable_base.rb b/app/models/concerns/approvable_base.rb
new file mode 100644
index 00000000000..6323bd01c58
--- /dev/null
+++ b/app/models/concerns/approvable_base.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+module ApprovableBase
+ extend ActiveSupport::Concern
+
+ included do
+ has_many :approvals, dependent: :delete_all # rubocop:disable Cop/ActiveRecordDependent
+ has_many :approved_by_users, through: :approvals, source: :user
+ end
+
+ def approved_by?(user)
+ return false unless user
+
+ approved_by_users.include?(user)
+ end
+end
diff --git a/app/models/concerns/avatarable.rb b/app/models/concerns/avatarable.rb
index a98baeb0e3d..ac84ef94b1c 100644
--- a/app/models/concerns/avatarable.rb
+++ b/app/models/concerns/avatarable.rb
@@ -36,6 +36,12 @@ module Avatarable
end
end
+ class_methods do
+ def bot_avatar(image:)
+ Rails.root.join('app', 'assets', 'images', 'bot_avatars', image).open
+ end
+ end
+
def avatar_type
unless self.avatar.image?
errors.add :avatar, "file format is not supported. Please try one of the following supported formats: #{AvatarUploader::SAFE_IMAGE_EXT.join(', ')}"
diff --git a/app/models/concerns/bulk_insert_safe.rb b/app/models/concerns/bulk_insert_safe.rb
index e09f44e68dc..f9eb3fb875e 100644
--- a/app/models/concerns/bulk_insert_safe.rb
+++ b/app/models/concerns/bulk_insert_safe.rb
@@ -37,7 +37,7 @@ module BulkInsertSafe
# These are the callbacks we think safe when used on models that are
# written to the database in bulk
- CALLBACK_NAME_WHITELIST = Set[
+ ALLOWED_CALLBACKS = Set[
:initialize,
:validate,
:validation,
@@ -179,16 +179,12 @@ module BulkInsertSafe
end
def _bulk_insert_callback_allowed?(name, args)
- _bulk_insert_whitelisted?(name) || _bulk_insert_saved_from_belongs_to?(name, args)
+ ALLOWED_CALLBACKS.include?(name) || _bulk_insert_saved_from_belongs_to?(name, args)
end
# belongs_to associations will install a before_save hook during class loading
def _bulk_insert_saved_from_belongs_to?(name, args)
args.first == :before && args.second.to_s.start_with?('autosave_associated_records_for_')
end
-
- def _bulk_insert_whitelisted?(name)
- CALLBACK_NAME_WHITELIST.include?(name)
- end
end
end
diff --git a/app/models/concerns/ci/contextable.rb b/app/models/concerns/ci/contextable.rb
index 7ea5382a4fa..10df5e1a8dc 100644
--- a/app/models/concerns/ci/contextable.rb
+++ b/app/models/concerns/ci/contextable.rb
@@ -84,8 +84,6 @@ module Ci
end
def secret_instance_variables
- return [] unless ::Feature.enabled?(:ci_instance_level_variables, project, default_enabled: true)
-
project.ci_instance_variables_for(ref: git_ref)
end
diff --git a/app/models/concerns/ci/has_status.rb b/app/models/concerns/ci/has_status.rb
new file mode 100644
index 00000000000..c52807ec501
--- /dev/null
+++ b/app/models/concerns/ci/has_status.rb
@@ -0,0 +1,168 @@
+# frozen_string_literal: true
+
+module Ci
+ module HasStatus
+ extend ActiveSupport::Concern
+
+ DEFAULT_STATUS = 'created'
+ BLOCKED_STATUS = %w[manual scheduled].freeze
+ AVAILABLE_STATUSES = %w[created waiting_for_resource preparing pending running success failed canceled skipped manual scheduled].freeze
+ STARTED_STATUSES = %w[running success failed skipped manual scheduled].freeze
+ ACTIVE_STATUSES = %w[waiting_for_resource preparing pending running].freeze
+ COMPLETED_STATUSES = %w[success failed canceled skipped].freeze
+ ORDERED_STATUSES = %w[failed preparing pending running waiting_for_resource manual scheduled canceled success skipped created].freeze
+ PASSED_WITH_WARNINGS_STATUSES = %w[failed canceled].to_set.freeze
+ EXCLUDE_IGNORED_STATUSES = %w[manual failed canceled].to_set.freeze
+ STATUSES_ENUM = { created: 0, pending: 1, running: 2, success: 3,
+ failed: 4, canceled: 5, skipped: 6, manual: 7,
+ scheduled: 8, preparing: 9, waiting_for_resource: 10 }.freeze
+
+ UnknownStatusError = Class.new(StandardError)
+
+ class_methods do
+ def legacy_status_sql
+ scope_relevant = respond_to?(:exclude_ignored) ? exclude_ignored : all
+ scope_warnings = respond_to?(:failed_but_allowed) ? failed_but_allowed : none
+
+ builds = scope_relevant.select('count(*)').to_sql
+ created = scope_relevant.created.select('count(*)').to_sql
+ success = scope_relevant.success.select('count(*)').to_sql
+ manual = scope_relevant.manual.select('count(*)').to_sql
+ scheduled = scope_relevant.scheduled.select('count(*)').to_sql
+ preparing = scope_relevant.preparing.select('count(*)').to_sql
+ waiting_for_resource = scope_relevant.waiting_for_resource.select('count(*)').to_sql
+ pending = scope_relevant.pending.select('count(*)').to_sql
+ running = scope_relevant.running.select('count(*)').to_sql
+ skipped = scope_relevant.skipped.select('count(*)').to_sql
+ canceled = scope_relevant.canceled.select('count(*)').to_sql
+ warnings = scope_warnings.select('count(*) > 0').to_sql.presence || 'false'
+
+ Arel.sql(
+ "(CASE
+ WHEN (#{builds})=(#{skipped}) AND (#{warnings}) THEN 'success'
+ WHEN (#{builds})=(#{skipped}) THEN 'skipped'
+ WHEN (#{builds})=(#{success}) THEN 'success'
+ WHEN (#{builds})=(#{created}) THEN 'created'
+ WHEN (#{builds})=(#{preparing}) THEN 'preparing'
+ WHEN (#{builds})=(#{success})+(#{skipped}) THEN 'success'
+ WHEN (#{builds})=(#{success})+(#{skipped})+(#{canceled}) THEN 'canceled'
+ WHEN (#{builds})=(#{created})+(#{skipped})+(#{pending}) THEN 'pending'
+ WHEN (#{running})+(#{pending})>0 THEN 'running'
+ WHEN (#{waiting_for_resource})>0 THEN 'waiting_for_resource'
+ WHEN (#{manual})>0 THEN 'manual'
+ WHEN (#{scheduled})>0 THEN 'scheduled'
+ WHEN (#{preparing})>0 THEN 'preparing'
+ WHEN (#{created})>0 THEN 'running'
+ ELSE 'failed'
+ END)"
+ )
+ end
+
+ def legacy_status
+ all.pluck(legacy_status_sql).first
+ end
+
+ # This method should not be used.
+ # This method performs expensive calculation of status:
+ # 1. By plucking all related objects,
+ # 2. Or executes expensive SQL query
+ def slow_composite_status(project:)
+ if ::Gitlab::Ci::Features.composite_status?(project)
+ Gitlab::Ci::Status::Composite
+ .new(all, with_allow_failure: columns_hash.key?('allow_failure'))
+ .status
+ else
+ legacy_status
+ end
+ end
+
+ def started_at
+ all.minimum(:started_at)
+ end
+
+ def finished_at
+ all.maximum(:finished_at)
+ end
+
+ def all_state_names
+ state_machines.values.flat_map(&:states).flat_map { |s| s.map(&:name) }
+ end
+
+ def completed_statuses
+ COMPLETED_STATUSES.map(&:to_sym)
+ end
+ end
+
+ included do
+ validates :status, inclusion: { in: AVAILABLE_STATUSES }
+
+ state_machine :status, initial: :created do
+ state :created, value: 'created'
+ state :waiting_for_resource, value: 'waiting_for_resource'
+ state :preparing, value: 'preparing'
+ state :pending, value: 'pending'
+ state :running, value: 'running'
+ state :failed, value: 'failed'
+ state :success, value: 'success'
+ state :canceled, value: 'canceled'
+ state :skipped, value: 'skipped'
+ state :manual, value: 'manual'
+ state :scheduled, value: 'scheduled'
+ end
+
+ scope :created, -> { with_status(:created) }
+ scope :waiting_for_resource, -> { with_status(:waiting_for_resource) }
+ scope :preparing, -> { with_status(:preparing) }
+ scope :relevant, -> { without_status(:created) }
+ scope :running, -> { with_status(:running) }
+ scope :pending, -> { with_status(:pending) }
+ scope :success, -> { with_status(:success) }
+ scope :failed, -> { with_status(:failed) }
+ scope :canceled, -> { with_status(:canceled) }
+ scope :skipped, -> { with_status(:skipped) }
+ scope :manual, -> { with_status(:manual) }
+ scope :scheduled, -> { with_status(:scheduled) }
+ scope :alive, -> { with_status(:created, :waiting_for_resource, :preparing, :pending, :running) }
+ scope :alive_or_scheduled, -> { with_status(:created, :waiting_for_resource, :preparing, :pending, :running, :scheduled) }
+ scope :created_or_pending, -> { with_status(:created, :pending) }
+ scope :running_or_pending, -> { with_status(:running, :pending) }
+ scope :finished, -> { with_status(:success, :failed, :canceled) }
+ scope :failed_or_canceled, -> { with_status(:failed, :canceled) }
+ scope :incomplete, -> { without_statuses(completed_statuses) }
+
+ scope :cancelable, -> do
+ where(status: [:running, :waiting_for_resource, :preparing, :pending, :created, :scheduled])
+ end
+
+ scope :without_statuses, -> (names) do
+ with_status(all_state_names - names.to_a)
+ end
+ end
+
+ def started?
+ STARTED_STATUSES.include?(status) && started_at
+ end
+
+ def active?
+ ACTIVE_STATUSES.include?(status)
+ end
+
+ def complete?
+ COMPLETED_STATUSES.include?(status)
+ end
+
+ def blocked?
+ BLOCKED_STATUS.include?(status)
+ end
+
+ private
+
+ def calculate_duration
+ if started_at && finished_at
+ finished_at - started_at
+ elsif started_at
+ Time.current - started_at
+ end
+ end
+ end
+end
diff --git a/app/models/concerns/ci/metadatable.rb b/app/models/concerns/ci/metadatable.rb
index bd40af28bc9..26e644646b4 100644
--- a/app/models/concerns/ci/metadatable.rb
+++ b/app/models/concerns/ci/metadatable.rb
@@ -87,3 +87,5 @@ module Ci
end
end
end
+
+Ci::Metadatable.prepend_if_ee('EE::Ci::Metadatable')
diff --git a/app/models/concerns/deployment_platform.rb b/app/models/concerns/deployment_platform.rb
index 3b893a56bd6..02f7711e927 100644
--- a/app/models/concerns/deployment_platform.rb
+++ b/app/models/concerns/deployment_platform.rb
@@ -1,7 +1,6 @@
# frozen_string_literal: true
module DeploymentPlatform
- # EE would override this and utilize environment argument
# rubocop:disable Gitlab/ModuleWithInstanceVariables
def deployment_platform(environment: nil)
@deployment_platform ||= {}
@@ -20,16 +19,27 @@ module DeploymentPlatform
find_instance_cluster_platform_kubernetes(environment: environment)
end
- # EE would override this and utilize environment argument
- def find_platform_kubernetes_with_cte(_environment)
- Clusters::ClustersHierarchy.new(self, include_management_project: cluster_management_project_enabled?).base_and_ancestors
+ def find_platform_kubernetes_with_cte(environment)
+ if environment
+ ::Clusters::ClustersHierarchy.new(self, include_management_project: cluster_management_project_enabled?)
+ .base_and_ancestors
+ .enabled
+ .on_environment(environment, relevant_only: true)
+ .first&.platform_kubernetes
+ else
+ Clusters::ClustersHierarchy.new(self, include_management_project: cluster_management_project_enabled?).base_and_ancestors
.enabled.default_environment
.first&.platform_kubernetes
+ end
end
- # EE would override this and utilize environment argument
def find_instance_cluster_platform_kubernetes(environment: nil)
- Clusters::Instance.new.clusters.enabled.default_environment
+ if environment
+ ::Clusters::Instance.new.clusters.enabled.on_environment(environment, relevant_only: true)
.first&.platform_kubernetes
+ else
+ Clusters::Instance.new.clusters.enabled.default_environment
+ .first&.platform_kubernetes
+ end
end
end
diff --git a/app/models/concerns/has_repository.rb b/app/models/concerns/has_repository.rb
index 29d31b8bb4f..d909b67d7ba 100644
--- a/app/models/concerns/has_repository.rb
+++ b/app/models/concerns/has_repository.rb
@@ -5,7 +5,7 @@
# of directly having a repository, like project or snippet.
#
# It also includes `Referable`, therefore the method
-# `to_reference` should be overriden in case the object
+# `to_reference` should be overridden in case the object
# needs any special behavior.
module HasRepository
extend ActiveSupport::Concern
@@ -76,7 +76,11 @@ module HasRepository
end
def default_branch
- @default_branch ||= repository.root_ref
+ @default_branch ||= repository.root_ref || default_branch_from_preferences
+ end
+
+ def default_branch_from_preferences
+ empty_repo? ? Gitlab::CurrentSettings.default_branch_name : nil
end
def reload_default_branch
diff --git a/app/models/concerns/has_status.rb b/app/models/concerns/has_status.rb
deleted file mode 100644
index c885dea862f..00000000000
--- a/app/models/concerns/has_status.rb
+++ /dev/null
@@ -1,166 +0,0 @@
-# frozen_string_literal: true
-
-module HasStatus
- extend ActiveSupport::Concern
-
- DEFAULT_STATUS = 'created'
- BLOCKED_STATUS = %w[manual scheduled].freeze
- AVAILABLE_STATUSES = %w[created waiting_for_resource preparing pending running success failed canceled skipped manual scheduled].freeze
- STARTED_STATUSES = %w[running success failed skipped manual scheduled].freeze
- ACTIVE_STATUSES = %w[waiting_for_resource preparing pending running].freeze
- COMPLETED_STATUSES = %w[success failed canceled skipped].freeze
- ORDERED_STATUSES = %w[failed preparing pending running waiting_for_resource manual scheduled canceled success skipped created].freeze
- PASSED_WITH_WARNINGS_STATUSES = %w[failed canceled].to_set.freeze
- EXCLUDE_IGNORED_STATUSES = %w[manual failed canceled].to_set.freeze
- STATUSES_ENUM = { created: 0, pending: 1, running: 2, success: 3,
- failed: 4, canceled: 5, skipped: 6, manual: 7,
- scheduled: 8, preparing: 9, waiting_for_resource: 10 }.freeze
-
- UnknownStatusError = Class.new(StandardError)
-
- class_methods do
- def legacy_status_sql
- scope_relevant = respond_to?(:exclude_ignored) ? exclude_ignored : all
- scope_warnings = respond_to?(:failed_but_allowed) ? failed_but_allowed : none
-
- builds = scope_relevant.select('count(*)').to_sql
- created = scope_relevant.created.select('count(*)').to_sql
- success = scope_relevant.success.select('count(*)').to_sql
- manual = scope_relevant.manual.select('count(*)').to_sql
- scheduled = scope_relevant.scheduled.select('count(*)').to_sql
- preparing = scope_relevant.preparing.select('count(*)').to_sql
- waiting_for_resource = scope_relevant.waiting_for_resource.select('count(*)').to_sql
- pending = scope_relevant.pending.select('count(*)').to_sql
- running = scope_relevant.running.select('count(*)').to_sql
- skipped = scope_relevant.skipped.select('count(*)').to_sql
- canceled = scope_relevant.canceled.select('count(*)').to_sql
- warnings = scope_warnings.select('count(*) > 0').to_sql.presence || 'false'
-
- Arel.sql(
- "(CASE
- WHEN (#{builds})=(#{skipped}) AND (#{warnings}) THEN 'success'
- WHEN (#{builds})=(#{skipped}) THEN 'skipped'
- WHEN (#{builds})=(#{success}) THEN 'success'
- WHEN (#{builds})=(#{created}) THEN 'created'
- WHEN (#{builds})=(#{preparing}) THEN 'preparing'
- WHEN (#{builds})=(#{success})+(#{skipped}) THEN 'success'
- WHEN (#{builds})=(#{success})+(#{skipped})+(#{canceled}) THEN 'canceled'
- WHEN (#{builds})=(#{created})+(#{skipped})+(#{pending}) THEN 'pending'
- WHEN (#{running})+(#{pending})>0 THEN 'running'
- WHEN (#{waiting_for_resource})>0 THEN 'waiting_for_resource'
- WHEN (#{manual})>0 THEN 'manual'
- WHEN (#{scheduled})>0 THEN 'scheduled'
- WHEN (#{preparing})>0 THEN 'preparing'
- WHEN (#{created})>0 THEN 'running'
- ELSE 'failed'
- END)"
- )
- end
-
- def legacy_status
- all.pluck(legacy_status_sql).first
- end
-
- # This method should not be used.
- # This method performs expensive calculation of status:
- # 1. By plucking all related objects,
- # 2. Or executes expensive SQL query
- def slow_composite_status(project:)
- if ::Gitlab::Ci::Features.composite_status?(project)
- Gitlab::Ci::Status::Composite
- .new(all, with_allow_failure: columns_hash.key?('allow_failure'))
- .status
- else
- legacy_status
- end
- end
-
- def started_at
- all.minimum(:started_at)
- end
-
- def finished_at
- all.maximum(:finished_at)
- end
-
- def all_state_names
- state_machines.values.flat_map(&:states).flat_map { |s| s.map(&:name) }
- end
-
- def completed_statuses
- COMPLETED_STATUSES.map(&:to_sym)
- end
- end
-
- included do
- validates :status, inclusion: { in: AVAILABLE_STATUSES }
-
- state_machine :status, initial: :created do
- state :created, value: 'created'
- state :waiting_for_resource, value: 'waiting_for_resource'
- state :preparing, value: 'preparing'
- state :pending, value: 'pending'
- state :running, value: 'running'
- state :failed, value: 'failed'
- state :success, value: 'success'
- state :canceled, value: 'canceled'
- state :skipped, value: 'skipped'
- state :manual, value: 'manual'
- state :scheduled, value: 'scheduled'
- end
-
- scope :created, -> { with_status(:created) }
- scope :waiting_for_resource, -> { with_status(:waiting_for_resource) }
- scope :preparing, -> { with_status(:preparing) }
- scope :relevant, -> { without_status(:created) }
- scope :running, -> { with_status(:running) }
- scope :pending, -> { with_status(:pending) }
- scope :success, -> { with_status(:success) }
- scope :failed, -> { with_status(:failed) }
- scope :canceled, -> { with_status(:canceled) }
- scope :skipped, -> { with_status(:skipped) }
- scope :manual, -> { with_status(:manual) }
- scope :scheduled, -> { with_status(:scheduled) }
- scope :alive, -> { with_status(:created, :waiting_for_resource, :preparing, :pending, :running) }
- scope :alive_or_scheduled, -> { with_status(:created, :waiting_for_resource, :preparing, :pending, :running, :scheduled) }
- scope :created_or_pending, -> { with_status(:created, :pending) }
- scope :running_or_pending, -> { with_status(:running, :pending) }
- scope :finished, -> { with_status(:success, :failed, :canceled) }
- scope :failed_or_canceled, -> { with_status(:failed, :canceled) }
- scope :incomplete, -> { without_statuses(completed_statuses) }
-
- scope :cancelable, -> do
- where(status: [:running, :waiting_for_resource, :preparing, :pending, :created, :scheduled])
- end
-
- scope :without_statuses, -> (names) do
- with_status(all_state_names - names.to_a)
- end
- end
-
- def started?
- STARTED_STATUSES.include?(status) && started_at
- end
-
- def active?
- ACTIVE_STATUSES.include?(status)
- end
-
- def complete?
- COMPLETED_STATUSES.include?(status)
- end
-
- def blocked?
- BLOCKED_STATUS.include?(status)
- end
-
- private
-
- def calculate_duration
- if started_at && finished_at
- finished_at - started_at
- elsif started_at
- Time.current - started_at
- end
- end
-end
diff --git a/app/models/concerns/integration.rb b/app/models/concerns/integration.rb
index 644a0ba1b5e..34ff5bb1195 100644
--- a/app/models/concerns/integration.rb
+++ b/app/models/concerns/integration.rb
@@ -15,5 +15,19 @@ module Integration
Project.where(id: custom_integration_project_ids)
end
+
+ def ids_without_integration(integration, limit)
+ services = Service
+ .select('1')
+ .where('services.project_id = projects.id')
+ .where(type: integration.type)
+
+ Project
+ .where('NOT EXISTS (?)', services)
+ .where(pending_delete: false)
+ .where(archived: false)
+ .limit(limit)
+ .pluck(:id)
+ end
end
end
diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb
index 220af8ab7c7..715cbd15d93 100644
--- a/app/models/concerns/issuable.rb
+++ b/app/models/concerns/issuable.rb
@@ -411,8 +411,8 @@ module Issuable
changes = previous_changes
if old_associations
- old_labels = old_associations.fetch(:labels, [])
- old_assignees = old_associations.fetch(:assignees, [])
+ old_labels = old_associations.fetch(:labels, labels)
+ old_assignees = old_associations.fetch(:assignees, assignees)
if old_labels != labels
changes[:labels] = [old_labels.map(&:hook_attrs), labels.map(&:hook_attrs)]
@@ -423,7 +423,7 @@ module Issuable
end
if self.respond_to?(:total_time_spent)
- old_total_time_spent = old_associations.fetch(:total_time_spent, nil)
+ old_total_time_spent = old_associations.fetch(:total_time_spent, total_time_spent)
if old_total_time_spent != total_time_spent
changes[:total_time_spent] = [old_total_time_spent, total_time_spent]
diff --git a/app/models/concerns/noteable.rb b/app/models/concerns/noteable.rb
index 183b902dd37..2dbe9360d42 100644
--- a/app/models/concerns/noteable.rb
+++ b/app/models/concerns/noteable.rb
@@ -67,6 +67,10 @@ module Noteable
false
end
+ def has_any_diff_note_positions?
+ notes.any? && DiffNotePosition.where(note: notes).exists?
+ end
+
def discussion_notes
notes
end
diff --git a/app/models/concerns/partitioned_table.rb b/app/models/concerns/partitioned_table.rb
new file mode 100644
index 00000000000..9f1cec5d520
--- /dev/null
+++ b/app/models/concerns/partitioned_table.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+module PartitionedTable
+ extend ActiveSupport::Concern
+
+ class_methods do
+ attr_reader :partitioning_strategy
+
+ PARTITIONING_STRATEGIES = {
+ monthly: Gitlab::Database::Partitioning::MonthlyStrategy
+ }.freeze
+
+ def partitioned_by(partitioning_key, strategy:)
+ strategy_class = PARTITIONING_STRATEGIES[strategy.to_sym] || raise(ArgumentError, "Unknown partitioning strategy: #{strategy}")
+
+ @partitioning_strategy = strategy_class.new(self, partitioning_key)
+
+ Gitlab::Database::Partitioning::PartitionCreator.register(self)
+ end
+ end
+end
diff --git a/app/models/concerns/reactive_caching.rb b/app/models/concerns/reactive_caching.rb
index d294563139c..5f30fc0c36c 100644
--- a/app/models/concerns/reactive_caching.rb
+++ b/app/models/concerns/reactive_caching.rb
@@ -29,7 +29,7 @@ module ReactiveCaching
self.reactive_cache_lease_timeout = 2.minutes
self.reactive_cache_refresh_interval = 1.minute
self.reactive_cache_lifetime = 10.minutes
- self.reactive_cache_hard_limit = 1.megabyte
+ self.reactive_cache_hard_limit = nil # this value should be set in megabytes. E.g: 1.megabyte
self.reactive_cache_work_type = :default
self.reactive_cache_worker_finder = ->(id, *_args) do
find_by(primary_key => id)
@@ -159,8 +159,12 @@ module ReactiveCaching
WORK_TYPE.fetch(self.class.reactive_cache_work_type.to_sym)
end
+ def reactive_cache_limit_enabled?
+ !!self.reactive_cache_hard_limit
+ end
+
def check_exceeded_reactive_cache_limit!(data)
- return unless Feature.enabled?(:reactive_cache_limit)
+ return unless reactive_cache_limit_enabled?
data_deep_size = Gitlab::Utils::DeepSize.new(data, max_size: self.class.reactive_cache_hard_limit)
diff --git a/app/models/concerns/routable.rb b/app/models/concerns/routable.rb
index 129d0fbb2c0..c70ce9bebcc 100644
--- a/app/models/concerns/routable.rb
+++ b/app/models/concerns/routable.rb
@@ -17,11 +17,8 @@ module Routable
after_validation :set_path_errors
- before_validation do
- if full_path_changed? || full_name_changed?
- prepare_route
- end
- end
+ before_validation :prepare_route
+ before_save :prepare_route # in case validation is skipped
end
class_methods do
@@ -118,6 +115,8 @@ module Routable
end
def prepare_route
+ return unless full_path_changed? || full_name_changed?
+
route || build_route(source: self)
route.path = build_full_path
route.name = build_full_name
diff --git a/app/models/concerns/update_project_statistics.rb b/app/models/concerns/update_project_statistics.rb
index 6cf012680d8..c0fa14d3369 100644
--- a/app/models/concerns/update_project_statistics.rb
+++ b/app/models/concerns/update_project_statistics.rb
@@ -35,8 +35,8 @@ module UpdateProjectStatistics
@project_statistics_name = project_statistics_name
@statistic_attribute = statistic_attribute
- after_save(:update_project_statistics_after_save, if: :update_project_statistics_attribute_changed?)
- after_destroy(:update_project_statistics_after_destroy, unless: :project_destroyed?)
+ after_save(:update_project_statistics_after_save, if: :update_project_statistics_after_save?)
+ after_destroy(:update_project_statistics_after_destroy, if: :update_project_statistics_after_destroy?)
end
private :update_project_statistics
@@ -45,6 +45,14 @@ module UpdateProjectStatistics
included do
private
+ def update_project_statistics_after_save?
+ update_project_statistics_attribute_changed?
+ end
+
+ def update_project_statistics_after_destroy?
+ !project_destroyed?
+ end
+
def update_project_statistics_after_save
attr = self.class.statistic_attribute
delta = read_attribute(attr).to_i - attribute_before_last_save(attr).to_i
diff --git a/app/models/custom_emoji.rb b/app/models/custom_emoji.rb
new file mode 100644
index 00000000000..643b4060ad6
--- /dev/null
+++ b/app/models/custom_emoji.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+class CustomEmoji < ApplicationRecord
+ belongs_to :namespace, inverse_of: :custom_emoji
+
+ validate :valid_emoji_name
+
+ validates :namespace, presence: true
+ validates :name,
+ uniqueness: { scope: [:namespace_id, :name] },
+ presence: true,
+ length: { maximum: 36 },
+ format: { with: /\A\w+\z/ }
+
+ private
+
+ def valid_emoji_name
+ if Gitlab::Emoji.emoji_exists?(name)
+ errors.add(:name, _('%{name} is already being used for another emoji') % { name: self.name })
+ end
+ end
+end
diff --git a/app/models/deploy_keys_project.rb b/app/models/deploy_keys_project.rb
index 40c66d5bc4c..a9cc56a7246 100644
--- a/app/models/deploy_keys_project.rb
+++ b/app/models/deploy_keys_project.rb
@@ -6,6 +6,7 @@ class DeployKeysProject < ApplicationRecord
scope :without_project_deleted, -> { joins(:project).where(projects: { pending_delete: false }) }
scope :in_project, ->(project) { where(project: project) }
scope :with_write_access, -> { where(can_push: true) }
+ scope :with_deploy_keys, -> { includes(:deploy_key) }
accepts_nested_attributes_for :deploy_key
diff --git a/app/models/diff_viewer/image.rb b/app/models/diff_viewer/image.rb
index cfda0058d81..62a3446a7b6 100644
--- a/app/models/diff_viewer/image.rb
+++ b/app/models/diff_viewer/image.rb
@@ -8,7 +8,7 @@ module DiffViewer
self.partial_name = 'image'
self.extensions = UploaderHelper::SAFE_IMAGE_EXT
self.binary = true
- self.switcher_icon = 'picture-o'
+ self.switcher_icon = 'doc-image'
self.switcher_title = _('image diff')
end
end
diff --git a/app/models/environment.rb b/app/models/environment.rb
index 8dae2d760f5..bddc84f10b5 100644
--- a/app/models/environment.rb
+++ b/app/models/environment.rb
@@ -21,6 +21,7 @@ class Environment < ApplicationRecord
has_many :prometheus_alerts, inverse_of: :environment
has_many :metrics_dashboard_annotations, class_name: 'Metrics::Dashboard::Annotation', inverse_of: :environment
has_many :self_managed_prometheus_alert_events, inverse_of: :environment
+ has_many :alert_management_alerts, class_name: 'AlertManagement::Alert', inverse_of: :environment
has_one :last_deployment, -> { success.order('deployments.id DESC') }, class_name: 'Deployment'
has_one :last_deployable, through: :last_deployment, source: 'deployable', source_type: 'CommitStatus'
@@ -147,7 +148,7 @@ class Environment < ApplicationRecord
Ci::Build.joins(inner_join_stop_actions)
.with(cte.to_arel)
.where(ci_builds[:commit_id].in(pipeline_ids))
- .where(status: HasStatus::BLOCKED_STATUS)
+ .where(status: Ci::HasStatus::BLOCKED_STATUS)
.preload_project_and_pipeline_project
.preload(:user, :metadata, :deployment)
end
@@ -226,6 +227,21 @@ class Environment < ApplicationRecord
available? && stop_action.present?
end
+ def cancel_deployment_jobs!
+ jobs = active_deployments.with_deployable
+ jobs.each do |deployment|
+ # guard against data integrity issues,
+ # for example https://gitlab.com/gitlab-org/gitlab/-/issues/218659#note_348823660
+ next unless deployment.deployable
+
+ Gitlab::OptimisticLocking.retry_lock(deployment.deployable) do |deployable|
+ deployable.cancel! if deployable&.cancelable?
+ end
+ rescue => e
+ Gitlab::ErrorTracking.track_exception(e, environment_id: id, deployment_id: deployment.id)
+ end
+ end
+
def stop_with_action!(current_user)
return unless available?
@@ -362,6 +378,11 @@ class Environment < ApplicationRecord
def generate_slug
self.slug = Gitlab::Slug::Environment.new(name).generate
end
+
+ # Overrides ReactiveCaching default to activate limit checking behind a FF
+ def reactive_cache_limit_enabled?
+ Feature.enabled?(:reactive_caching_limit_environment, project)
+ end
end
Environment.prepend_if_ee('EE::Environment')
diff --git a/app/models/epic.rb b/app/models/epic.rb
index e09dc1080e6..93f286f97d3 100644
--- a/app/models/epic.rb
+++ b/app/models/epic.rb
@@ -5,8 +5,6 @@
class Epic < ApplicationRecord
include IgnorableColumns
- ignore_column :health_status, remove_with: '13.0', remove_after: '2019-05-22'
-
def self.link_reference_pattern
nil
end
diff --git a/app/models/event.rb b/app/models/event.rb
index 9c0fcbb354b..56d7742c51a 100644
--- a/app/models/event.rb
+++ b/app/models/event.rb
@@ -83,10 +83,6 @@ class Event < ApplicationRecord
scope :for_wiki_page, -> { where(target_type: 'WikiPage::Meta') }
scope :for_design, -> { where(target_type: 'DesignManagement::Design') }
- # Needed to implement feature flag: can be removed when feature flag is removed
- scope :not_wiki_page, -> { where('target_type IS NULL or target_type <> ?', 'WikiPage::Meta') }
- scope :not_design, -> { where('target_type IS NULL or target_type <> ?', 'DesignManagement::Design') }
-
scope :with_associations, -> do
# We're using preload for "push_event_payload" as otherwise the association
# is not always available (depending on the query being built).
diff --git a/app/models/event_collection.rb b/app/models/event_collection.rb
index 4c178e27b75..4768506b8fa 100644
--- a/app/models/event_collection.rb
+++ b/app/models/event_collection.rb
@@ -33,23 +33,16 @@ class EventCollection
project_events
end
- relation = apply_feature_flags(relation)
relation = paginate_events(relation)
relation.with_associations.to_a
end
def all_project_events
- apply_feature_flags(Event.from_union([project_events]).recent)
+ Event.from_union([project_events]).recent
end
private
- def apply_feature_flags(events)
- return events if ::Feature.enabled?(:wiki_events)
-
- events.not_wiki_page
- end
-
def project_events
relation_with_join_lateral('project_id', projects)
end
diff --git a/app/models/group.rb b/app/models/group.rb
index 71f58a5fd1a..c38ddbdf6fb 100644
--- a/app/models/group.rb
+++ b/app/models/group.rb
@@ -18,6 +18,8 @@ class Group < Namespace
ACCESS_REQUEST_APPROVERS_TO_BE_NOTIFIED_LIMIT = 10
+ UpdateSharedRunnersError = Class.new(StandardError)
+
has_many :group_members, -> { where(requested_at: nil) }, dependent: :destroy, as: :source # rubocop:disable Cop/ActiveRecordDependent
alias_method :members, :group_members
has_many :users, through: :group_members
@@ -89,6 +91,8 @@ class Group < Namespace
scope :with_users, -> { includes(:users) }
+ scope :by_id, ->(groups) { where(id: groups) }
+
class << self
def sort_by_attribute(method)
if method == 'storage_size_desc'
@@ -504,6 +508,55 @@ class Group < Namespace
preloader.preload(self, shared_with_group_links: [shared_with_group: :route])
end
+ def shared_runners_allowed?
+ shared_runners_enabled? || allow_descendants_override_disabled_shared_runners?
+ end
+
+ def parent_allows_shared_runners?
+ return true unless has_parent?
+
+ parent.shared_runners_allowed?
+ end
+
+ def parent_enabled_shared_runners?
+ return true unless has_parent?
+
+ parent.shared_runners_enabled?
+ end
+
+ def enable_shared_runners!
+ raise UpdateSharedRunnersError, 'Shared Runners disabled for the parent group' unless parent_enabled_shared_runners?
+
+ update_column(:shared_runners_enabled, true)
+ end
+
+ def disable_shared_runners!
+ group_ids = self_and_descendants
+ return if group_ids.empty?
+
+ Group.by_id(group_ids).update_all(shared_runners_enabled: false)
+
+ all_projects.update_all(shared_runners_enabled: false)
+ end
+
+ def allow_descendants_override_disabled_shared_runners!
+ raise UpdateSharedRunnersError, 'Shared Runners enabled' if shared_runners_enabled?
+ raise UpdateSharedRunnersError, 'Group level shared Runners not allowed' unless parent_allows_shared_runners?
+
+ update_column(:allow_descendants_override_disabled_shared_runners, true)
+ end
+
+ def disallow_descendants_override_disabled_shared_runners!
+ raise UpdateSharedRunnersError, 'Shared Runners enabled' if shared_runners_enabled?
+
+ group_ids = self_and_descendants
+ return if group_ids.empty?
+
+ Group.by_id(group_ids).update_all(allow_descendants_override_disabled_shared_runners: false)
+
+ all_projects.update_all(shared_runners_enabled: false)
+ end
+
private
def update_two_factor_requirement
diff --git a/app/models/incident_management/project_incident_management_setting.rb b/app/models/incident_management/project_incident_management_setting.rb
index bf57c5b883f..c79acdb685f 100644
--- a/app/models/incident_management/project_incident_management_setting.rb
+++ b/app/models/incident_management/project_incident_management_setting.rb
@@ -8,6 +8,15 @@ module IncidentManagement
validate :issue_template_exists, if: :create_issue?
+ before_validation :ensure_pagerduty_token
+
+ attr_encrypted :pagerduty_token,
+ mode: :per_attribute_iv,
+ key: ::Settings.attr_encrypted_db_key_base_truncated,
+ algorithm: 'aes-256-gcm',
+ encode: false, # No need to encode for binary column https://github.com/attr-encrypted/attr_encrypted#the-encode-encode_iv-encode_salt-and-default_encoding-options
+ encode_iv: false
+
def available_issue_templates
Gitlab::Template::IssueTemplate.all(project)
end
@@ -30,5 +39,15 @@ module IncidentManagement
Gitlab::Template::IssueTemplate.find(issue_template_key, project)
rescue Gitlab::Template::Finders::RepoTemplateFinder::FileNotFoundError
end
+
+ def ensure_pagerduty_token
+ return unless pagerduty_active
+
+ self.pagerduty_token ||= generate_pagerduty_token
+ end
+
+ def generate_pagerduty_token
+ SecureRandom.hex
+ end
end
end
diff --git a/app/models/issue.rb b/app/models/issue.rb
index 5c5190f88b1..619555f369d 100644
--- a/app/models/issue.rb
+++ b/app/models/issue.rb
@@ -98,6 +98,8 @@ class Issue < ApplicationRecord
scope :counts_by_state, -> { reorder(nil).group(:state_id).count }
+ scope :service_desk, -> { where(author: ::User.support_bot) }
+
# An issue can be uniquely identified by project_id and iid
# Takes one or more sets of composite IDs, expressed as hash-like records of
# `{project_id: x, iid: y}`.
@@ -373,6 +375,10 @@ class Issue < ApplicationRecord
)
end
+ def from_service_desk?
+ author.id == User.support_bot.id
+ end
+
private
def ensure_metrics
diff --git a/app/models/issue_assignee.rb b/app/models/issue_assignee.rb
index 8128b8a538e..e57acbae546 100644
--- a/app/models/issue_assignee.rb
+++ b/app/models/issue_assignee.rb
@@ -2,9 +2,12 @@
class IssueAssignee < ApplicationRecord
belongs_to :issue
- belongs_to :assignee, class_name: "User", foreign_key: :user_id
+ belongs_to :assignee, class_name: "User", foreign_key: :user_id, inverse_of: :issue_assignees
validates :assignee, uniqueness: { scope: :issue_id }
+
+ scope :in_projects, ->(project_ids) { joins(:issue).where("issues.project_id in (?)", project_ids) }
+ scope :on_issues, ->(issue_ids) { where(issue_id: issue_ids) }
end
IssueAssignee.prepend_if_ee('EE::IssueAssignee')
diff --git a/app/models/iteration.rb b/app/models/iteration.rb
index 2bda0725471..0b59cf047f7 100644
--- a/app/models/iteration.rb
+++ b/app/models/iteration.rb
@@ -34,6 +34,9 @@ class Iteration < ApplicationRecord
.where('due_date is NULL or due_date >= ?', start_date)
end
+ scope :start_date_passed, -> { where('start_date <= ?', Date.current).where('due_date > ?', Date.current) }
+ scope :due_date_passed, -> { where('due_date <= ?', Date.current) }
+
state_machine :state_enum, initial: :upcoming do
event :start do
transition upcoming: :started
@@ -93,7 +96,7 @@ class Iteration < ApplicationRecord
# ensure dates do not overlap with other Iterations in the same group/project
def dates_do_not_overlap
- return unless resource_parent.iterations.within_timeframe(start_date, due_date).exists?
+ return unless resource_parent.iterations.where.not(id: self.id).within_timeframe(start_date, due_date).exists?
errors.add(:base, s_("Iteration|Dates cannot overlap with other existing Iterations"))
end
diff --git a/app/models/label.rb b/app/models/label.rb
index 910cc0d68cd..3c70eef9bd5 100644
--- a/app/models/label.rb
+++ b/app/models/label.rb
@@ -149,10 +149,6 @@ class Label < ApplicationRecord
1
end
- def self.by_ids(ids)
- where(id: ids)
- end
-
def self.on_project_board?(project_id, label_id)
return false if label_id.blank?
diff --git a/app/models/lfs_objects_project.rb b/app/models/lfs_objects_project.rb
index e1966eda277..674294f0916 100644
--- a/app/models/lfs_objects_project.rb
+++ b/app/models/lfs_objects_project.rb
@@ -15,7 +15,7 @@ class LfsObjectsProject < ApplicationRecord
enum repository_type: {
project: 0,
wiki: 1,
- design: 2 ## EE-specific
+ design: 2
}
scope :project_id_in, ->(ids) { where(project_id: ids) }
diff --git a/app/models/member.rb b/app/models/member.rb
index f2926d32d47..36f9741ce01 100644
--- a/app/models/member.rb
+++ b/app/models/member.rb
@@ -38,6 +38,11 @@ class Member < ApplicationRecord
scope: [:source_type, :source_id],
allow_nil: true
}
+ validates :user_id,
+ uniqueness: {
+ message: _('project bots cannot be added to other groups / projects')
+ },
+ if: :project_bot?
# This scope encapsulates (most of) the conditions a row in the member table
# must satisfy if it is a valid permission. Of particular note:
@@ -473,6 +478,10 @@ class Member < ApplicationRecord
def update_highest_role_attribute
user_id
end
+
+ def project_bot?
+ user&.project_bot?
+ end
end
Member.prepend_if_ee('EE::Member')
diff --git a/app/models/members/group_member.rb b/app/models/members/group_member.rb
index 9a916cd40ae..8c224dea88f 100644
--- a/app/models/members/group_member.rb
+++ b/app/models/members/group_member.rb
@@ -17,14 +17,7 @@ class GroupMember < Member
scope :of_groups, ->(groups) { where(source_id: groups.select(:id)) }
scope :of_ldap_type, -> { where(ldap: true) }
-
- scope :count_users_by_group_id, -> do
- if Feature.enabled?(:optimized_count_users_by_group_id)
- group(:source_id).count
- else
- joins(:user).group(:source_id).count
- end
- end
+ scope :count_users_by_group_id, -> { group(:source_id).count }
after_create :update_two_factor_requirement, unless: :invite?
after_destroy :update_two_factor_requirement, unless: :invite?
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index a7e0907eb5f..b7885771781 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -20,13 +20,15 @@ class MergeRequest < ApplicationRecord
include IgnorableColumns
include MilestoneEventable
include StateEventable
+ include ApprovableBase
+
+ extend ::Gitlab::Utils::Override
sha_attribute :squash_commit_sha
self.reactive_cache_key = ->(model) { [model.project.id, model.iid] }
self.reactive_cache_refresh_interval = 10.minutes
self.reactive_cache_lifetime = 10.minutes
- self.reactive_cache_hard_limit = 20.megabytes
SORTING_PREFERENCE_FIELD = :merge_requests_sort
@@ -103,6 +105,7 @@ class MergeRequest < ApplicationRecord
after_create :ensure_merge_request_diff
after_update :clear_memoized_shas
+ after_update :clear_memoized_source_branch_exists
after_update :reload_diff_if_branch_changed
after_commit :ensure_metrics, on: [:create, :update], unless: :importing?
after_commit :expire_etag_cache, unless: :importing?
@@ -260,6 +263,7 @@ class MergeRequest < ApplicationRecord
*PROJECT_ROUTE_AND_NAMESPACE_ROUTE,
metrics: [:latest_closed_by, :merged_by])
}
+
scope :by_target_branch_wildcard, ->(wildcard_branch_name) do
where("target_branch LIKE ?", ApplicationRecord.sanitize_sql_like(wildcard_branch_name).tr('*', '%'))
end
@@ -386,25 +390,27 @@ class MergeRequest < ApplicationRecord
end
end
- WIP_REGEX = /\A*(\[WIP\]\s*|WIP:\s*|WIP\s+)+\s*/i.freeze
+ # WIP is deprecated in favor of Draft. Currently both options are supported
+ # https://gitlab.com/gitlab-org/gitlab/-/issues/227426
+ DRAFT_REGEX = /\A*#{Regexp.union(Gitlab::Regex.merge_request_wip, Gitlab::Regex.merge_request_draft)}+\s*/i.freeze
def self.work_in_progress?(title)
- !!(title =~ WIP_REGEX)
+ !!(title =~ DRAFT_REGEX)
end
def self.wipless_title(title)
- title.sub(WIP_REGEX, "")
+ title.sub(DRAFT_REGEX, "")
end
def self.wip_title(title)
- work_in_progress?(title) ? title : "WIP: #{title}"
+ work_in_progress?(title) ? title : "Draft: #{title}"
end
def committers
@committers ||= commits.committers
end
- # Verifies if title has changed not taking into account WIP prefix
+ # Verifies if title has changed not taking into account Draft prefix
# for merge requests.
def wipless_title_changed(old_title)
self.class.wipless_title(old_title) != self.wipless_title
@@ -858,6 +864,10 @@ class MergeRequest < ApplicationRecord
clear_memoization(:target_branch_head)
end
+ def clear_memoized_source_branch_exists
+ clear_memoization(:source_branch_exists)
+ end
+
def reload_diff_if_branch_changed
if (saved_change_to_source_branch? || saved_change_to_target_branch?) &&
(source_branch_head && target_branch_head)
@@ -946,7 +956,8 @@ class MergeRequest < ApplicationRecord
end
def can_remove_source_branch?(current_user)
- !ProtectedBranch.protected?(source_project, source_branch) &&
+ source_project &&
+ !ProtectedBranch.protected?(source_project, source_branch) &&
!source_project.root_ref?(source_branch) &&
Ability.allowed?(current_user, :push_code, source_project) &&
diff_head_sha == source_branch_head.try(:sha)
@@ -1017,6 +1028,10 @@ class MergeRequest < ApplicationRecord
target_project != source_project
end
+ def for_same_project?
+ target_project == source_project
+ end
+
# If the merge request closes any issues, save this information in the
# `MergeRequestsClosingIssues` model. This is a performance optimization.
# Calculating this information for a number of merge requests requires
@@ -1104,9 +1119,11 @@ class MergeRequest < ApplicationRecord
end
def source_branch_exists?
- return false unless self.source_project
+ strong_memoize(:source_branch_exists) do
+ next false unless self.source_project
- self.source_project.repository.branch_exists?(self.source_branch)
+ self.source_project.repository.branch_exists?(self.source_branch)
+ end
end
def target_branch_exists?
@@ -1142,6 +1159,13 @@ class MergeRequest < ApplicationRecord
end
end
+ def squash_on_merge?
+ return true if target_project.squash_always?
+ return false if target_project.squash_never?
+
+ squash?
+ end
+
def has_ci?
return false if has_no_commits?
@@ -1273,7 +1297,7 @@ class MergeRequest < ApplicationRecord
def all_pipelines
strong_memoize(:all_pipelines) do
- Ci::PipelinesForMergeRequestFinder.new(self).all
+ Ci::PipelinesForMergeRequestFinder.new(self, nil).all
end
end
@@ -1374,9 +1398,9 @@ class MergeRequest < ApplicationRecord
# TODO: consider renaming this as with exposed artifacts we generate reports,
# not always compare
# issue: https://gitlab.com/gitlab-org/gitlab/issues/34224
- def compare_reports(service_class, current_user = nil)
- with_reactive_cache(service_class.name, current_user&.id) do |data|
- unless service_class.new(project, current_user, id: id)
+ def compare_reports(service_class, current_user = nil, report_type = nil )
+ with_reactive_cache(service_class.name, current_user&.id, report_type) do |data|
+ unless service_class.new(project, current_user, id: id, report_type: report_type)
.latest?(base_pipeline, actual_head_pipeline, data)
raise InvalidateReactiveCache
end
@@ -1385,7 +1409,7 @@ class MergeRequest < ApplicationRecord
end || { status: :parsing }
end
- def calculate_reactive_cache(identifier, current_user_id = nil, *args)
+ def calculate_reactive_cache(identifier, current_user_id = nil, report_type = nil, *args)
service_class = identifier.constantize
# TODO: the type check should change to something that includes exposed artifacts service
@@ -1393,7 +1417,7 @@ class MergeRequest < ApplicationRecord
raise NameError, service_class unless service_class < Ci::CompareReportsBaseService
current_user = User.find_by(id: current_user_id)
- service_class.new(project, current_user, id: id).execute(base_pipeline, actual_head_pipeline)
+ service_class.new(project, current_user, id: id, report_type: report_type).execute(base_pipeline, actual_head_pipeline)
end
def all_commits
@@ -1582,6 +1606,23 @@ class MergeRequest < ApplicationRecord
super.merge(label_url_method: :project_merge_requests_url)
end
+ override :ensure_metrics
+ def ensure_metrics
+ MergeRequest::Metrics.safe_find_or_create_by(merge_request_id: id).tap do |metrics_record|
+ # Make sure we refresh the loaded association object with the newly created/loaded item.
+ # This is needed in order to have the exact functionality than before.
+ #
+ # Example:
+ #
+ # merge_request.metrics.destroy
+ # merge_request.ensure_metrics
+ # merge_request.metrics # should return the metrics record and not nil
+ # merge_request.metrics.merge_request # should return the same MR record
+ metrics_record.association(:merge_request).target = self
+ association(:metrics).target = metrics_record
+ end
+ end
+
private
def with_rebase_lock
diff --git a/app/models/merge_request_assignee.rb b/app/models/merge_request_assignee.rb
index fe642bee8e2..2ac1de4321a 100644
--- a/app/models/merge_request_assignee.rb
+++ b/app/models/merge_request_assignee.rb
@@ -2,7 +2,9 @@
class MergeRequestAssignee < ApplicationRecord
belongs_to :merge_request
- belongs_to :assignee, class_name: "User", foreign_key: :user_id
+ belongs_to :assignee, class_name: "User", foreign_key: :user_id, inverse_of: :merge_request_assignees
validates :assignee, uniqueness: { scope: :merge_request_id }
+
+ scope :in_projects, ->(project_ids) { joins(:merge_request).where("merge_requests.target_project_id in (?)", project_ids) }
end
diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb
index 66b27aeac91..eb5250d5cf6 100644
--- a/app/models/merge_request_diff.rb
+++ b/app/models/merge_request_diff.rb
@@ -414,10 +414,16 @@ class MergeRequestDiff < ApplicationRecord
return if stored_externally? || !use_external_diff? || merge_request_diff_files.count == 0
rows = build_merge_request_diff_files(merge_request_diff_files)
+ rows = build_external_merge_request_diff_files(rows)
+
+ # Perform carrierwave activity before entering the database transaction.
+ # This is safe as until the `external_diff_store` column is changed, we will
+ # continue to consult the in-database content.
+ self.external_diff.store!
transaction do
MergeRequestDiffFile.where(merge_request_diff_id: id).delete_all
- create_merge_request_diff_files(rows)
+ Gitlab::Database.bulk_insert('merge_request_diff_files', rows) # rubocop:disable Gitlab/BulkInsert
save!
end
diff --git a/app/models/namespace.rb b/app/models/namespace.rb
index 90b4be7a674..e529ba6b486 100644
--- a/app/models/namespace.rb
+++ b/app/models/namespace.rb
@@ -13,9 +13,6 @@ class Namespace < ApplicationRecord
include Gitlab::Utils::StrongMemoize
include IgnorableColumns
- ignore_column :plan_id, remove_with: '13.1', remove_after: '2020-06-22'
- ignore_column :trial_ends_on, remove_with: '13.2', remove_after: '2020-07-22'
-
# Prevent users from creating unreasonably deep level of nesting.
# The number 20 was taken based on maximum nesting level of
# Android repo (15) + some extra backup.
@@ -25,6 +22,7 @@ class Namespace < ApplicationRecord
has_many :projects, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
has_many :project_statistics
+ has_one :namespace_settings, inverse_of: :namespace, class_name: 'NamespaceSetting', autosave: true
has_many :runner_namespaces, inverse_of: :namespace, class_name: 'Ci::RunnerNamespace'
has_many :runners, through: :runner_namespaces, source: :runner, class_name: 'Ci::Runner'
@@ -35,6 +33,7 @@ class Namespace < ApplicationRecord
belongs_to :parent, class_name: "Namespace"
has_many :children, class_name: "Namespace", foreign_key: :parent_id
+ has_many :custom_emoji, inverse_of: :namespace
has_one :chat_team, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
has_one :root_storage_statistics, class_name: 'Namespace::RootStorageStatistics'
has_one :aggregation_schedule, class_name: 'Namespace::AggregationSchedule'
@@ -50,6 +49,13 @@ class Namespace < ApplicationRecord
length: { maximum: 255 },
namespace_path: true
+ # Introduce minimal path length of 2 characters.
+ # Allow change of other attributes without forcing users to
+ # rename their user or group. At the same time prevent changing
+ # the path without complying with new 2 chars requirement.
+ # Issue https://gitlab.com/gitlab-org/gitlab/-/issues/225214
+ validates :path, length: { minimum: 2 }, if: :path_changed?
+
validates :max_artifacts_size, numericality: { only_integer: true, greater_than: 0, allow_nil: true }
validate :nesting_level_allowed
@@ -82,6 +88,7 @@ class Namespace < ApplicationRecord
'COALESCE(SUM(ps.storage_size), 0) AS storage_size',
'COALESCE(SUM(ps.repository_size), 0) AS repository_size',
'COALESCE(SUM(ps.wiki_size), 0) AS wiki_size',
+ 'COALESCE(SUM(ps.snippets_size), 0) AS snippets_size',
'COALESCE(SUM(ps.lfs_objects_size), 0) AS lfs_objects_size',
'COALESCE(SUM(ps.build_artifacts_size), 0) AS build_artifacts_size',
'COALESCE(SUM(ps.packages_size), 0) AS packages_size'
@@ -212,7 +219,7 @@ class Namespace < ApplicationRecord
Gitlab.config.lfs.enabled
end
- def shared_runners_enabled?
+ def any_project_with_shared_runners_enabled?
projects.with_shared_runners.any?
end
@@ -281,6 +288,8 @@ class Namespace < ApplicationRecord
end
def root_ancestor
+ return self if persisted? && parent_id.nil?
+
strong_memoize(:root_ancestor) do
self_and_ancestors.reorder(nil).find_by(parent_id: nil)
end
diff --git a/app/models/namespace/root_storage_size.rb b/app/models/namespace/root_storage_size.rb
deleted file mode 100644
index d61917e468e..00000000000
--- a/app/models/namespace/root_storage_size.rb
+++ /dev/null
@@ -1,31 +0,0 @@
-# frozen_string_literal: true
-
-class Namespace::RootStorageSize
- def initialize(root_namespace)
- @root_namespace = root_namespace
- end
-
- def above_size_limit?
- return false if limit == 0
-
- usage_ratio > 1
- end
-
- def usage_ratio
- return 0 if limit == 0
-
- current_size.to_f / limit.to_f
- end
-
- def current_size
- @current_size ||= root_namespace.root_storage_statistics&.storage_size
- end
-
- def limit
- @limit ||= Gitlab::CurrentSettings.namespace_storage_size_limit.megabytes
- end
-
- private
-
- attr_reader :root_namespace
-end
diff --git a/app/models/namespace/root_storage_statistics.rb b/app/models/namespace/root_storage_statistics.rb
index ae9b2f14343..2ad6ea59588 100644
--- a/app/models/namespace/root_storage_statistics.rb
+++ b/app/models/namespace/root_storage_statistics.rb
@@ -1,7 +1,8 @@
# frozen_string_literal: true
class Namespace::RootStorageStatistics < ApplicationRecord
- STATISTICS_ATTRIBUTES = %w(storage_size repository_size wiki_size lfs_objects_size build_artifacts_size packages_size).freeze
+ SNIPPETS_SIZE_STAT_NAME = 'snippets_size'.freeze
+ STATISTICS_ATTRIBUTES = %W(storage_size repository_size wiki_size lfs_objects_size build_artifacts_size packages_size #{SNIPPETS_SIZE_STAT_NAME}).freeze
self.primary_key = :namespace_id
@@ -13,11 +14,15 @@ class Namespace::RootStorageStatistics < ApplicationRecord
delegate :all_projects, to: :namespace
def recalculate!
- update!(attributes_from_project_statistics)
+ update!(merged_attributes)
end
private
+ def merged_attributes
+ attributes_from_project_statistics.merge!(attributes_from_personal_snippets) { |key, v1, v2| v1 + v2 }
+ end
+
def attributes_from_project_statistics
from_project_statistics
.take
@@ -34,7 +39,22 @@ class Namespace::RootStorageStatistics < ApplicationRecord
'COALESCE(SUM(ps.wiki_size), 0) AS wiki_size',
'COALESCE(SUM(ps.lfs_objects_size), 0) AS lfs_objects_size',
'COALESCE(SUM(ps.build_artifacts_size), 0) AS build_artifacts_size',
- 'COALESCE(SUM(ps.packages_size), 0) AS packages_size'
+ 'COALESCE(SUM(ps.packages_size), 0) AS packages_size',
+ "COALESCE(SUM(ps.snippets_size), 0) AS #{SNIPPETS_SIZE_STAT_NAME}"
)
end
+
+ def attributes_from_personal_snippets
+ # Return if the type of namespace does not belong to a user
+ return {} unless namespace.type.nil?
+
+ from_personal_snippets.take.slice(SNIPPETS_SIZE_STAT_NAME)
+ end
+
+ def from_personal_snippets
+ PersonalSnippet
+ .joins('INNER JOIN snippet_statistics s ON s.snippet_id = snippets.id')
+ .where(author: namespace.owner_id)
+ .select("COALESCE(SUM(s.repository_size), 0) AS #{SNIPPETS_SIZE_STAT_NAME}")
+ end
end
diff --git a/app/models/namespace/traversal_hierarchy.rb b/app/models/namespace/traversal_hierarchy.rb
new file mode 100644
index 00000000000..cfb6cfdde74
--- /dev/null
+++ b/app/models/namespace/traversal_hierarchy.rb
@@ -0,0 +1,84 @@
+# frozen_string_literal: true
+#
+# A Namespace::TraversalHierarchy is the collection of namespaces that descend
+# from a root Namespace as defined by the Namespace#traversal_ids attributes.
+#
+# This class provides operations to be performed on the hierarchy itself,
+# rather than individual namespaces.
+#
+# This includes methods for synchronizing traversal_ids attributes to a correct
+# state. We use recursive methods to determine the correct state so we don't
+# have to depend on the integrity of the traversal_ids attribute values
+# themselves.
+#
+class Namespace
+ class TraversalHierarchy
+ attr_accessor :root
+
+ def self.for_namespace(namespace)
+ new(recursive_root_ancestor(namespace))
+ end
+
+ def initialize(root)
+ raise StandardError.new('Must specify a root node') if root.parent_id
+
+ @root = root
+ end
+
+ # Update all traversal_ids in the current namespace hierarchy.
+ def sync_traversal_ids!
+ # An issue in Rails since 2013 prevents this kind of join based update in
+ # ActiveRecord. https://github.com/rails/rails/issues/13496
+ # Ideally it would be:
+ # `incorrect_traversal_ids.update_all('traversal_ids = cte.traversal_ids')`
+ sql = """
+ UPDATE namespaces
+ SET traversal_ids = cte.traversal_ids
+ FROM (#{recursive_traversal_ids}) as cte
+ WHERE namespaces.id = cte.id
+ AND namespaces.traversal_ids <> cte.traversal_ids
+ """
+ Namespace.connection.exec_query(sql)
+ end
+
+ # Identify all incorrect traversal_ids in the current namespace hierarchy.
+ def incorrect_traversal_ids
+ Namespace
+ .joins("INNER JOIN (#{recursive_traversal_ids}) as cte ON namespaces.id = cte.id")
+ .where('namespaces.traversal_ids <> cte.traversal_ids')
+ end
+
+ private
+
+ # Determine traversal_ids for the namespace hierarchy using recursive methods.
+ # Generate a collection of [id, traversal_ids] rows.
+ #
+ # Note that the traversal_ids represent a calculated traversal path for the
+ # namespace and not the value stored within the traversal_ids attribute.
+ def recursive_traversal_ids
+ root_id = Integer(@root.id)
+
+ """
+ WITH RECURSIVE cte(id, traversal_ids, cycle) AS (
+ VALUES(#{root_id}, ARRAY[#{root_id}], false)
+ UNION ALL
+ SELECT n.id, cte.traversal_ids || n.id, n.id = ANY(cte.traversal_ids)
+ FROM namespaces n, cte
+ WHERE n.parent_id = cte.id AND NOT cycle
+ )
+ SELECT id, traversal_ids FROM cte
+ """
+ end
+
+ # This is essentially Namespace#root_ancestor which will soon be rewritten
+ # to use traversal_ids. We replicate here as a reliable way to find the
+ # root using recursive methods.
+ def self.recursive_root_ancestor(namespace)
+ Gitlab::ObjectHierarchy
+ .new(Namespace.where(id: namespace))
+ .base_and_ancestors
+ .reorder(nil)
+ .find_by(parent_id: nil)
+ end
+ end
+end
diff --git a/app/models/namespace_setting.rb b/app/models/namespace_setting.rb
new file mode 100644
index 00000000000..53bfa3d979e
--- /dev/null
+++ b/app/models/namespace_setting.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+class NamespaceSetting < ApplicationRecord
+ belongs_to :namespace, inverse_of: :namespace_settings
+
+ self.primary_key = :namespace_id
+end
+
+NamespaceSetting.prepend_if_ee('EE::NamespaceSetting')
diff --git a/app/models/note.rb b/app/models/note.rb
index 6b6a7c50b00..2db7e4e406d 100644
--- a/app/models/note.rb
+++ b/app/models/note.rb
@@ -5,6 +5,7 @@
# A note of this type is never resolvable.
class Note < ApplicationRecord
extend ActiveModel::Naming
+ include Gitlab::Utils::StrongMemoize
include Participable
include Mentionable
include Awardable
@@ -122,6 +123,8 @@ class Note < ApplicationRecord
scope :common, -> { where(noteable_type: ["", nil]) }
scope :fresh, -> { order(created_at: :asc, id: :asc) }
scope :updated_after, ->(time) { where('updated_at > ?', time) }
+ scope :with_updated_at, ->(time) { where(updated_at: time) }
+ scope :by_updated_at, -> { reorder(:updated_at, :id) }
scope :inc_author_project, -> { includes(:project, :author) }
scope :inc_author, -> { includes(:author) }
scope :inc_relations_for_view, -> do
@@ -446,8 +449,10 @@ class Note < ApplicationRecord
# Consider using `#to_discussion` if we do not need to render the discussion
# and all its notes and if we don't care about the discussion's resolvability status.
def discussion
- full_discussion = self.noteable.notes.find_discussion(self.discussion_id) if part_of_discussion?
- full_discussion || to_discussion
+ strong_memoize(:discussion) do
+ full_discussion = self.noteable.notes.find_discussion(self.discussion_id) if part_of_discussion?
+ full_discussion || to_discussion
+ end
end
def start_of_discussion?
diff --git a/app/models/packages.rb b/app/models/packages.rb
new file mode 100644
index 00000000000..e14c9290093
--- /dev/null
+++ b/app/models/packages.rb
@@ -0,0 +1,6 @@
+# frozen_string_literal: true
+module Packages
+ def self.table_name_prefix
+ 'packages_'
+ end
+end
diff --git a/app/models/packages/build_info.rb b/app/models/packages/build_info.rb
new file mode 100644
index 00000000000..df8cf68490e
--- /dev/null
+++ b/app/models/packages/build_info.rb
@@ -0,0 +1,6 @@
+# frozen_string_literal: true
+
+class Packages::BuildInfo < ApplicationRecord
+ belongs_to :package, inverse_of: :build_info
+ belongs_to :pipeline, class_name: 'Ci::Pipeline'
+end
diff --git a/app/models/packages/composer/metadatum.rb b/app/models/packages/composer/metadatum.rb
new file mode 100644
index 00000000000..3026f5ea878
--- /dev/null
+++ b/app/models/packages/composer/metadatum.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+
+module Packages
+ module Composer
+ class Metadatum < ApplicationRecord
+ self.table_name = 'packages_composer_metadata'
+ self.primary_key = :package_id
+
+ belongs_to :package, -> { where(package_type: :composer) }, inverse_of: :composer_metadatum
+
+ validates :package, :target_sha, :composer_json, presence: true
+ end
+ end
+end
diff --git a/app/models/packages/conan.rb b/app/models/packages/conan.rb
new file mode 100644
index 00000000000..01007c3fa78
--- /dev/null
+++ b/app/models/packages/conan.rb
@@ -0,0 +1,8 @@
+# frozen_string_literal: true
+module Packages
+ module Conan
+ def self.table_name_prefix
+ 'packages_conan_'
+ end
+ end
+end
diff --git a/app/models/packages/conan/file_metadatum.rb b/app/models/packages/conan/file_metadatum.rb
new file mode 100644
index 00000000000..e1ef62b3959
--- /dev/null
+++ b/app/models/packages/conan/file_metadatum.rb
@@ -0,0 +1,32 @@
+# frozen_string_literal: true
+
+class Packages::Conan::FileMetadatum < ApplicationRecord
+ belongs_to :package_file, inverse_of: :conan_file_metadatum
+
+ validates :package_file, presence: true
+
+ validates :recipe_revision,
+ presence: true,
+ format: { with: Gitlab::Regex.conan_revision_regex }
+
+ validates :package_revision, absence: true, if: :recipe_file?
+ validates :package_revision, format: { with: Gitlab::Regex.conan_revision_regex }, if: :package_file?
+
+ validates :conan_package_reference, absence: true, if: :recipe_file?
+ validates :conan_package_reference, format: { with: Gitlab::Regex.conan_package_reference_regex }, if: :package_file?
+ validate :conan_package_type
+
+ enum conan_file_type: { recipe_file: 1, package_file: 2 }
+
+ RECIPE_FILES = ::Gitlab::Regex::Packages::CONAN_RECIPE_FILES
+ PACKAGE_FILES = ::Gitlab::Regex::Packages::CONAN_PACKAGE_FILES
+ PACKAGE_BINARY = 'conan_package.tgz'
+
+ private
+
+ def conan_package_type
+ unless package_file&.package&.conan?
+ errors.add(:base, _('Package type must be Conan'))
+ end
+ end
+end
diff --git a/app/models/packages/conan/metadatum.rb b/app/models/packages/conan/metadatum.rb
new file mode 100644
index 00000000000..7ec2641177a
--- /dev/null
+++ b/app/models/packages/conan/metadatum.rb
@@ -0,0 +1,41 @@
+# frozen_string_literal: true
+
+class Packages::Conan::Metadatum < ApplicationRecord
+ belongs_to :package, -> { where(package_type: :conan) }, inverse_of: :conan_metadatum
+
+ validates :package, presence: true
+
+ validates :package_username,
+ presence: true,
+ format: { with: Gitlab::Regex.conan_recipe_component_regex }
+
+ validates :package_channel,
+ presence: true,
+ format: { with: Gitlab::Regex.conan_recipe_component_regex }
+
+ validate :conan_package_type
+
+ def recipe
+ "#{package.name}/#{package.version}@#{package_username}/#{package_channel}"
+ end
+
+ def recipe_path
+ recipe.tr('@', '/')
+ end
+
+ def self.package_username_from(full_path:)
+ full_path.tr('/', '+')
+ end
+
+ def self.full_path_from(package_username:)
+ package_username.tr('+', '/')
+ end
+
+ private
+
+ def conan_package_type
+ unless package&.conan?
+ errors.add(:base, _('Package type must be Conan'))
+ end
+ end
+end
diff --git a/app/models/packages/dependency.rb b/app/models/packages/dependency.rb
new file mode 100644
index 00000000000..51b80934827
--- /dev/null
+++ b/app/models/packages/dependency.rb
@@ -0,0 +1,47 @@
+# frozen_string_literal: true
+class Packages::Dependency < ApplicationRecord
+ has_many :dependency_links, class_name: 'Packages::DependencyLink'
+
+ validates :name, :version_pattern, presence: true
+
+ validates :name, uniqueness: { scope: :version_pattern }
+
+ NAME_VERSION_PATTERN_TUPLE_MATCHING = '(name, version_pattern) = (?, ?)'.freeze
+ MAX_STRING_LENGTH = 255.freeze
+ MAX_CHUNKED_QUERIES_COUNT = 10.freeze
+
+ def self.ids_for_package_names_and_version_patterns(names_and_version_patterns = {}, chunk_size = 50, max_rows_limit = 200)
+ names_and_version_patterns.reject! { |key, value| key.size > MAX_STRING_LENGTH || value.size > MAX_STRING_LENGTH }
+ raise ArgumentError, 'Too many names_and_version_patterns' if names_and_version_patterns.size > MAX_CHUNKED_QUERIES_COUNT * chunk_size
+
+ matched_ids = []
+ names_and_version_patterns.each_slice(chunk_size) do |tuples|
+ where_statement = Array.new(tuples.size, NAME_VERSION_PATTERN_TUPLE_MATCHING)
+ .join(' OR ')
+ ids = where(where_statement, *tuples.flatten)
+ .limit(max_rows_limit + 1)
+ .pluck(:id)
+ matched_ids.concat(ids)
+
+ raise ArgumentError, 'Too many Dependencies selected' if matched_ids.size > max_rows_limit
+ end
+
+ matched_ids
+ end
+
+ def self.for_package_names_and_version_patterns(names_and_version_patterns = {}, chunk_size = 50, max_rows_limit = 200)
+ ids = ids_for_package_names_and_version_patterns(names_and_version_patterns, chunk_size, max_rows_limit)
+
+ return none if ids.empty?
+
+ id_in(ids)
+ end
+
+ def self.pluck_ids_and_names
+ pluck(:id, :name)
+ end
+
+ def orphaned?
+ self.dependency_links.empty?
+ end
+end
diff --git a/app/models/packages/dependency_link.rb b/app/models/packages/dependency_link.rb
new file mode 100644
index 00000000000..51018602bdc
--- /dev/null
+++ b/app/models/packages/dependency_link.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+class Packages::DependencyLink < ApplicationRecord
+ belongs_to :package, inverse_of: :dependency_links
+ belongs_to :dependency, inverse_of: :dependency_links, class_name: 'Packages::Dependency'
+ has_one :nuget_metadatum, inverse_of: :dependency_link, class_name: 'Packages::Nuget::DependencyLinkMetadatum'
+
+ validates :package, :dependency, presence: true
+
+ validates :dependency_type,
+ uniqueness: { scope: %i[package_id dependency_id] }
+
+ enum dependency_type: { dependencies: 1, devDependencies: 2, bundleDependencies: 3, peerDependencies: 4 }
+
+ scope :with_dependency_type, ->(dependency_type) { where(dependency_type: dependency_type) }
+ scope :includes_dependency, -> { includes(:dependency) }
+ scope :for_package, ->(package) { where(package_id: package.id) }
+ scope :preload_dependency, -> { preload(:dependency) }
+ scope :preload_nuget_metadatum, -> { preload(:nuget_metadatum) }
+end
diff --git a/app/models/packages/go/module.rb b/app/models/packages/go/module.rb
new file mode 100644
index 00000000000..b38b691ed6c
--- /dev/null
+++ b/app/models/packages/go/module.rb
@@ -0,0 +1,93 @@
+# frozen_string_literal: true
+
+module Packages
+ module Go
+ class Module
+ include Gitlab::Utils::StrongMemoize
+
+ attr_reader :project, :name, :path
+
+ def initialize(project, name, path)
+ @project = project
+ @name = name
+ @path = path
+ end
+
+ def versions
+ strong_memoize(:versions) { Packages::Go::VersionFinder.new(self).execute }
+ end
+
+ def version_by(ref: nil, commit: nil)
+ raise ArgumentError.new 'no filter specified' unless ref || commit
+ raise ArgumentError.new 'ref and commit are mutually exclusive' if ref && commit
+
+ if commit
+ return version_by_sha(commit) if commit.is_a? String
+
+ return version_by_commit(commit)
+ end
+
+ return version_by_name(ref) if ref.is_a? String
+
+ version_by_ref(ref)
+ end
+
+ def path_valid?(major)
+ m = /\/v(\d+)$/i.match(@name)
+
+ case major
+ when 0, 1
+ m.nil?
+ else
+ !m.nil? && m[1].to_i == major
+ end
+ end
+
+ def gomod_valid?(gomod)
+ if Feature.enabled?(:go_proxy_disable_gomod_validation, @project)
+ return gomod&.start_with?("module ")
+ end
+
+ gomod&.split("\n", 2)&.first == "module #{@name}"
+ end
+
+ private
+
+ def version_by_name(name)
+ # avoid a Gitaly call if possible
+ if strong_memoized?(:versions)
+ v = versions.find { |v| v.name == ref }
+ return v if v
+ end
+
+ ref = @project.repository.find_tag(name) || @project.repository.find_branch(name)
+ return unless ref
+
+ version_by_ref(ref)
+ end
+
+ def version_by_ref(ref)
+ # reuse existing versions
+ if strong_memoized?(:versions)
+ v = versions.find { |v| v.ref == ref }
+ return v if v
+ end
+
+ commit = ref.dereferenced_target
+ semver = Packages::SemVer.parse(ref.name, prefixed: true)
+ Packages::Go::ModuleVersion.new(self, :ref, commit, ref: ref, semver: semver)
+ end
+
+ def version_by_sha(sha)
+ commit = @project.commit_by(oid: sha)
+ return unless ref
+
+ version_by_commit(commit)
+ end
+
+ def version_by_commit(commit)
+ Packages::Go::ModuleVersion.new(self, :commit, commit)
+ end
+ end
+ end
+end
diff --git a/app/models/packages/go/module_version.rb b/app/models/packages/go/module_version.rb
new file mode 100644
index 00000000000..a50c78f8e69
--- /dev/null
+++ b/app/models/packages/go/module_version.rb
@@ -0,0 +1,115 @@
+# frozen_string_literal: true
+
+module Packages
+ module Go
+ class ModuleVersion
+ include Gitlab::Utils::StrongMemoize
+
+ VALID_TYPES = %i[ref commit pseudo].freeze
+
+ attr_reader :mod, :type, :ref, :commit
+
+ delegate :major, to: :@semver, allow_nil: true
+ delegate :minor, to: :@semver, allow_nil: true
+ delegate :patch, to: :@semver, allow_nil: true
+ delegate :prerelease, to: :@semver, allow_nil: true
+ delegate :build, to: :@semver, allow_nil: true
+
+ def initialize(mod, type, commit, name: nil, semver: nil, ref: nil)
+ raise ArgumentError.new("invalid type '#{type}'") unless VALID_TYPES.include? type
+ raise ArgumentError.new("mod is required") unless mod
+ raise ArgumentError.new("commit is required") unless commit
+
+ if type == :ref
+ raise ArgumentError.new("ref is required") unless ref
+ elsif type == :pseudo
+ raise ArgumentError.new("name is required") unless name
+ raise ArgumentError.new("semver is required") unless semver
+ end
+
+ @mod = mod
+ @type = type
+ @commit = commit
+ @name = name if name
+ @semver = semver if semver
+ @ref = ref if ref
+ end
+
+ def name
+ @name || @ref&.name
+ end
+
+ def full_name
+ "#{mod.name}@#{name || commit.sha}"
+ end
+
+ def gomod
+ strong_memoize(:gomod) do
+ if strong_memoized?(:blobs)
+ blob_at(@mod.path + '/go.mod')
+ elsif @mod.path.empty?
+ @mod.project.repository.blob_at(@commit.sha, 'go.mod')&.data
+ else
+ @mod.project.repository.blob_at(@commit.sha, @mod.path + '/go.mod')&.data
+ end
+ end
+ end
+
+ def archive
+ suffix_len = @mod.path == '' ? 0 : @mod.path.length + 1
+
+ Zip::OutputStream.write_buffer do |zip|
+ files.each do |file|
+ zip.put_next_entry "#{full_name}/#{file[suffix_len...]}"
+ zip.write blob_at(file)
+ end
+ end
+ end
+
+ def files
+ strong_memoize(:files) do
+ ls_tree.filter { |e| !excluded.any? { |n| e.start_with? n } }
+ end
+ end
+
+ def excluded
+ strong_memoize(:excluded) do
+ ls_tree
+ .filter { |f| f.end_with?('/go.mod') && f != @mod.path + '/go.mod' }
+ .map { |f| f[0..-7] }
+ end
+ end
+
+ def valid?
+ @mod.path_valid?(major) && @mod.gomod_valid?(gomod)
+ end
+
+ private
+
+ def blob_at(path)
+ return if path.nil? || path.empty?
+
+ path = path[1..] if path.start_with? '/'
+
+ blobs.find { |x| x.path == path }&.data
+ end
+
+ def blobs
+ strong_memoize(:blobs) { @mod.project.repository.batch_blobs(files.map { |x| [@commit.sha, x] }) }
+ end
+
+ def ls_tree
+ strong_memoize(:ls_tree) do
+ path =
+ if @mod.path.empty?
+ '.'
+ else
+ @mod.path
+ end
+
+ @mod.project.repository.gitaly_repository_client.search_files_by_name(@commit.sha, path)
+ end
+ end
+ end
+ end
+end
diff --git a/app/models/packages/maven.rb b/app/models/packages/maven.rb
new file mode 100644
index 00000000000..5c1581ce0b7
--- /dev/null
+++ b/app/models/packages/maven.rb
@@ -0,0 +1,8 @@
+# frozen_string_literal: true
+module Packages
+ module Maven
+ def self.table_name_prefix
+ 'packages_maven_'
+ end
+ end
+end
diff --git a/app/models/packages/maven/metadatum.rb b/app/models/packages/maven/metadatum.rb
new file mode 100644
index 00000000000..b7f27fb9e06
--- /dev/null
+++ b/app/models/packages/maven/metadatum.rb
@@ -0,0 +1,28 @@
+# frozen_string_literal: true
+class Packages::Maven::Metadatum < ApplicationRecord
+ belongs_to :package, -> { where(package_type: :maven) }
+
+ validates :package, presence: true
+
+ validates :path,
+ presence: true,
+ format: { with: Gitlab::Regex.maven_path_regex }
+
+ validates :app_group,
+ presence: true,
+ format: { with: Gitlab::Regex.maven_app_group_regex }
+
+ validates :app_name,
+ presence: true,
+ format: { with: Gitlab::Regex.maven_app_name_regex }
+
+ validate :maven_package_type
+
+ private
+
+ def maven_package_type
+ unless package&.maven?
+ errors.add(:base, _('Package type must be Maven'))
+ end
+ end
+end
diff --git a/app/models/packages/nuget.rb b/app/models/packages/nuget.rb
new file mode 100644
index 00000000000..42c167e9b7f
--- /dev/null
+++ b/app/models/packages/nuget.rb
@@ -0,0 +1,8 @@
+# frozen_string_literal: true
+module Packages
+ module Nuget
+ def self.table_name_prefix
+ 'packages_nuget_'
+ end
+ end
+end
diff --git a/app/models/packages/nuget/dependency_link_metadatum.rb b/app/models/packages/nuget/dependency_link_metadatum.rb
new file mode 100644
index 00000000000..b586b55d3f0
--- /dev/null
+++ b/app/models/packages/nuget/dependency_link_metadatum.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+class Packages::Nuget::DependencyLinkMetadatum < ApplicationRecord
+ self.primary_key = :dependency_link_id
+
+ belongs_to :dependency_link, inverse_of: :nuget_metadatum
+
+ validates :dependency_link, :target_framework, presence: true
+
+ validate :ensure_nuget_package_type
+
+ private
+
+ def ensure_nuget_package_type
+ return if dependency_link&.package&.nuget?
+
+ errors.add(:base, _('Package type must be NuGet'))
+ end
+end
diff --git a/app/models/packages/nuget/metadatum.rb b/app/models/packages/nuget/metadatum.rb
new file mode 100644
index 00000000000..1db8c0eddbf
--- /dev/null
+++ b/app/models/packages/nuget/metadatum.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+class Packages::Nuget::Metadatum < ApplicationRecord
+ belongs_to :package, -> { where(package_type: :nuget) }, inverse_of: :nuget_metadatum
+
+ validates :package, presence: true
+ validates :license_url, public_url: { allow_blank: true }
+ validates :project_url, public_url: { allow_blank: true }
+ validates :icon_url, public_url: { allow_blank: true }
+
+ validate :ensure_at_least_one_field_supplied
+ validate :ensure_nuget_package_type
+
+ private
+
+ def ensure_at_least_one_field_supplied
+ return if license_url? || project_url? || icon_url?
+
+ errors.add(:base, _('Nuget metadatum must have at least license_url, project_url or icon_url set'))
+ end
+
+ def ensure_nuget_package_type
+ return if package&.nuget?
+
+ errors.add(:base, _('Package type must be NuGet'))
+ end
+end
diff --git a/app/models/packages/package.rb b/app/models/packages/package.rb
new file mode 100644
index 00000000000..d6633456de4
--- /dev/null
+++ b/app/models/packages/package.rb
@@ -0,0 +1,195 @@
+# frozen_string_literal: true
+class Packages::Package < ApplicationRecord
+ include Sortable
+ include Gitlab::SQL::Pattern
+ include UsageStatistics
+
+ belongs_to :project
+ # package_files must be destroyed by ruby code in order to properly remove carrierwave uploads and update project statistics
+ has_many :package_files, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
+ has_many :dependency_links, inverse_of: :package, class_name: 'Packages::DependencyLink'
+ has_many :tags, inverse_of: :package, class_name: 'Packages::Tag'
+ has_one :conan_metadatum, inverse_of: :package, class_name: 'Packages::Conan::Metadatum'
+ has_one :pypi_metadatum, inverse_of: :package, class_name: 'Packages::Pypi::Metadatum'
+ has_one :maven_metadatum, inverse_of: :package, class_name: 'Packages::Maven::Metadatum'
+ has_one :nuget_metadatum, inverse_of: :package, class_name: 'Packages::Nuget::Metadatum'
+ has_one :composer_metadatum, inverse_of: :package, class_name: 'Packages::Composer::Metadatum'
+ has_one :build_info, inverse_of: :package
+
+ accepts_nested_attributes_for :conan_metadatum
+ accepts_nested_attributes_for :maven_metadatum
+
+ delegate :recipe, :recipe_path, to: :conan_metadatum, prefix: :conan
+
+ validates :project, presence: true
+ validates :name, presence: true
+
+ validates :name, format: { with: Gitlab::Regex.package_name_regex }, unless: :conan?
+
+ validates :name,
+ uniqueness: { scope: %i[project_id version package_type] }, unless: :conan?
+
+ validate :valid_conan_package_recipe, if: :conan?
+ validate :valid_npm_package_name, if: :npm?
+ validate :valid_composer_global_name, if: :composer?
+ validate :package_already_taken, if: :npm?
+ validates :version, format: { with: Gitlab::Regex.semver_regex }, if: -> { npm? || nuget? }
+ validates :name, format: { with: Gitlab::Regex.conan_recipe_component_regex }, if: :conan?
+ validates :version, format: { with: Gitlab::Regex.conan_recipe_component_regex }, if: :conan?
+ validates :version, format: { with: Gitlab::Regex.maven_version_regex }, if: -> { version? && maven? }
+
+ enum package_type: { maven: 1, npm: 2, conan: 3, nuget: 4, pypi: 5, composer: 6 }
+
+ scope :with_name, ->(name) { where(name: name) }
+ scope :with_name_like, ->(name) { where(arel_table[:name].matches(name)) }
+ scope :search_by_name, ->(query) { fuzzy_search(query, [:name], use_minimum_char_limit: false) }
+ scope :with_version, ->(version) { where(version: version) }
+ scope :without_version_like, -> (version) { where.not(arel_table[:version].matches(version)) }
+ scope :with_package_type, ->(package_type) { where(package_type: package_type) }
+
+ scope :with_conan_channel, ->(package_channel) do
+ joins(:conan_metadatum).where(packages_conan_metadata: { package_channel: package_channel })
+ end
+ scope :with_conan_username, ->(package_username) do
+ joins(:conan_metadatum).where(packages_conan_metadata: { package_username: package_username })
+ end
+
+ scope :with_composer_target, -> (target) do
+ includes(:composer_metadatum)
+ .joins(:composer_metadatum)
+ .where(Packages::Composer::Metadatum.table_name => { target_sha: target })
+ end
+ scope :preload_composer, -> { preload(:composer_metadatum) }
+
+ scope :without_nuget_temporary_name, -> { where.not(name: Packages::Nuget::CreatePackageService::TEMPORARY_PACKAGE_NAME) }
+
+ scope :has_version, -> { where.not(version: nil) }
+ scope :processed, -> do
+ where.not(package_type: :nuget).or(
+ where.not(name: Packages::Nuget::CreatePackageService::TEMPORARY_PACKAGE_NAME)
+ )
+ end
+ scope :preload_files, -> { preload(:package_files) }
+ scope :last_of_each_version, -> { where(id: all.select('MAX(id) AS id').group(:version)) }
+ scope :limit_recent, ->(limit) { order_created_desc.limit(limit) }
+ scope :select_distinct_name, -> { select(:name).distinct }
+
+ # Sorting
+ scope :order_created, -> { reorder('created_at ASC') }
+ scope :order_created_desc, -> { reorder('created_at DESC') }
+ scope :order_name, -> { reorder('name ASC') }
+ scope :order_name_desc, -> { reorder('name DESC') }
+ scope :order_version, -> { reorder('version ASC') }
+ scope :order_version_desc, -> { reorder('version DESC') }
+ scope :order_type, -> { reorder('package_type ASC') }
+ scope :order_type_desc, -> { reorder('package_type DESC') }
+ scope :order_project_name, -> { joins(:project).reorder('projects.name ASC') }
+ scope :order_project_name_desc, -> { joins(:project).reorder('projects.name DESC') }
+ scope :order_project_path, -> { joins(:project).reorder('projects.path ASC, id ASC') }
+ scope :order_project_path_desc, -> { joins(:project).reorder('projects.path DESC, id DESC') }
+
+ def self.for_projects(projects)
+ return none unless projects.any?
+
+ where(project_id: projects)
+ end
+
+ def self.only_maven_packages_with_path(path)
+ joins(:maven_metadatum).where(packages_maven_metadata: { path: path })
+ end
+
+ def self.by_name_and_file_name(name, file_name)
+ with_name(name)
+ .joins(:package_files)
+ .where(packages_package_files: { file_name: file_name }).last!
+ end
+
+ def self.by_file_name_and_sha256(file_name, sha256)
+ joins(:package_files)
+ .where(packages_package_files: { file_name: file_name, file_sha256: sha256 }).last!
+ end
+
+ def self.pluck_names
+ pluck(:name)
+ end
+
+ def self.pluck_versions
+ pluck(:version)
+ end
+
+ def self.sort_by_attribute(method)
+ case method.to_s
+ when 'created_asc' then order_created
+ when 'created_at_asc' then order_created
+ when 'name_asc' then order_name
+ when 'name_desc' then order_name_desc
+ when 'version_asc' then order_version
+ when 'version_desc' then order_version_desc
+ when 'type_asc' then order_type
+ when 'type_desc' then order_type_desc
+ when 'project_name_asc' then order_project_name
+ when 'project_name_desc' then order_project_name_desc
+ when 'project_path_asc' then order_project_path
+ when 'project_path_desc' then order_project_path_desc
+ else
+ order_created_desc
+ end
+ end
+
+ def versions
+ project.packages
+ .with_name(name)
+ .where.not(version: version)
+ .with_package_type(package_type)
+ .order(:version)
+ end
+
+ def pipeline
+ build_info&.pipeline
+ end
+
+ def tag_names
+ tags.pluck(:name)
+ end
+
+ private
+
+ def valid_conan_package_recipe
+ recipe_exists = project.packages
+ .conan
+ .includes(:conan_metadatum)
+ .with_name(name)
+ .with_version(version)
+ .with_conan_channel(conan_metadatum.package_channel)
+ .with_conan_username(conan_metadatum.package_username)
+ .id_not_in(id)
+ .exists?
+
+ errors.add(:base, _('Package recipe already exists')) if recipe_exists
+ end
+
+ def valid_composer_global_name
+ # .default_scoped is required here due to a bug in rails that leaks
+ # the scope and adds `self` to the query incorrectly
+ # See https://github.com/rails/rails/pull/35186
+ if Packages::Package.default_scoped.composer.with_name(name).where.not(project_id: project_id).exists?
+ errors.add(:name, 'is already taken by another project')
+ end
+ end
+
+ def valid_npm_package_name
+ return unless project&.root_namespace
+
+ unless name =~ %r{\A@#{project.root_namespace.path}/[^/]+\z}
+ errors.add(:name, 'is not valid')
+ end
+ end
+
+ def package_already_taken
+ return unless project
+
+ if project.package_already_taken?(name)
+ errors.add(:base, _('Package already exists'))
+ end
+ end
+end
diff --git a/app/models/packages/package_file.rb b/app/models/packages/package_file.rb
new file mode 100644
index 00000000000..567b5a14603
--- /dev/null
+++ b/app/models/packages/package_file.rb
@@ -0,0 +1,56 @@
+# frozen_string_literal: true
+class Packages::PackageFile < ApplicationRecord
+ include UpdateProjectStatistics
+
+ delegate :project, :project_id, to: :package
+ delegate :conan_file_type, to: :conan_file_metadatum
+
+ belongs_to :package
+
+ has_one :conan_file_metadatum, inverse_of: :package_file, class_name: 'Packages::Conan::FileMetadatum'
+
+ accepts_nested_attributes_for :conan_file_metadatum
+
+ validates :package, presence: true
+ validates :file, presence: true
+ validates :file_name, presence: true
+
+ scope :recent, -> { order(id: :desc) }
+ scope :with_file_name, ->(file_name) { where(file_name: file_name) }
+ scope :with_file_name_like, ->(file_name) { where(arel_table[:file_name].matches(file_name)) }
+ scope :with_files_stored_locally, -> { where(file_store: ::Packages::PackageFileUploader::Store::LOCAL) }
+ scope :preload_conan_file_metadata, -> { preload(:conan_file_metadatum) }
+
+ scope :with_conan_file_type, ->(file_type) do
+ joins(:conan_file_metadatum)
+ .where(packages_conan_file_metadata: { conan_file_type: ::Packages::Conan::FileMetadatum.conan_file_types[file_type] })
+ end
+
+ scope :with_conan_package_reference, ->(conan_package_reference) do
+ joins(:conan_file_metadatum)
+ .where(packages_conan_file_metadata: { conan_package_reference: conan_package_reference })
+ end
+
+ mount_uploader :file, Packages::PackageFileUploader
+
+ after_save :update_file_metadata, if: :saved_change_to_file?
+
+ update_project_statistics project_statistics_name: :packages_size
+
+ def update_file_metadata
+ # The file.object_store is set during `uploader.store!`
+ # which happens after object is inserted/updated
+ self.update_column(:file_store, file.object_store)
+ self.update_column(:size, file.size) unless file.size == self.size
+ end
+
+ def download_path
+ Gitlab::Routing.url_helpers.download_project_package_file_path(project, self) if ::Gitlab.ee?
+ end
+
+ def local?
+ file_store == ::Packages::PackageFileUploader::Store::LOCAL
+ end
+end
+
+Packages::PackageFile.prepend_if_ee('EE::Packages::PackageFileGeo')
diff --git a/app/models/packages/pypi.rb b/app/models/packages/pypi.rb
new file mode 100644
index 00000000000..fc8a55caa31
--- /dev/null
+++ b/app/models/packages/pypi.rb
@@ -0,0 +1,8 @@
+# frozen_string_literal: true
+module Packages
+ module Pypi
+ def self.table_name_prefix
+ 'packages_pypi_'
+ end
+ end
+end
diff --git a/app/models/packages/pypi/metadatum.rb b/app/models/packages/pypi/metadatum.rb
new file mode 100644
index 00000000000..7e6456ad964
--- /dev/null
+++ b/app/models/packages/pypi/metadatum.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+class Packages::Pypi::Metadatum < ApplicationRecord
+ self.primary_key = :package_id
+
+ belongs_to :package, -> { where(package_type: :pypi) }, inverse_of: :pypi_metadatum
+
+ validates :package, presence: true
+
+ validate :pypi_package_type
+
+ private
+
+ def pypi_package_type
+ unless package&.pypi?
+ errors.add(:base, _('Package type must be PyPi'))
+ end
+ end
+end
diff --git a/app/models/packages/sem_ver.rb b/app/models/packages/sem_ver.rb
new file mode 100644
index 00000000000..b73d51b08b7
--- /dev/null
+++ b/app/models/packages/sem_ver.rb
@@ -0,0 +1,54 @@
+# frozen_string_literal: true
+
+class Packages::SemVer
+ attr_accessor :major, :minor, :patch, :prerelease, :build
+
+ def initialize(major = 0, minor = 0, patch = 0, prerelease = nil, build = nil, prefixed: false)
+ @major = major
+ @minor = minor
+ @patch = patch
+ @prerelease = prerelease
+ @build = build
+ @prefixed = prefixed
+ end
+
+ def prefixed?
+ @prefixed
+ end
+
+ def ==(other)
+ self.class == other.class &&
+ self.major == other.major &&
+ self.minor == other.minor &&
+ self.patch == other.patch &&
+ self.prerelease == other.prerelease &&
+ self.build == other.build
+ end
+
+ def to_s
+ s = "#{prefixed? ? 'v' : ''}#{major || 0}.#{minor || 0}.#{patch || 0}"
+ s += "-#{prerelease}" if prerelease
+ s += "+#{build}" if build
+
+ s
+ end
+
+ def self.match(str, prefixed: false)
+ return unless str&.start_with?('v') == prefixed
+
+ str = str[1..] if prefixed
+
+ Gitlab::Regex.semver_regex.match(str)
+ end
+
+ def self.match?(str, prefixed: false)
+ !match(str, prefixed: prefixed).nil?
+ end
+
+ def self.parse(str, prefixed: false)
+ m = match str, prefixed: prefixed
+ return unless m
+
+ new(m[1].to_i, m[2].to_i, m[3].to_i, m[4], m[5], prefixed: prefixed)
+ end
+end
diff --git a/app/models/packages/tag.rb b/app/models/packages/tag.rb
new file mode 100644
index 00000000000..771d016daed
--- /dev/null
+++ b/app/models/packages/tag.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+class Packages::Tag < ApplicationRecord
+ belongs_to :package, inverse_of: :tags
+
+ validates :package, :name, presence: true
+
+ FOR_PACKAGES_TAGS_LIMIT = 200.freeze
+ NUGET_TAGS_SEPARATOR = ' ' # https://docs.microsoft.com/en-us/nuget/reference/nuspec#tags
+
+ scope :preload_package, -> { preload(:package) }
+ scope :with_name, -> (name) { where(name: name) }
+
+ def self.for_packages(packages)
+ where(package_id: packages.select(:id))
+ .order(updated_at: :desc)
+ .limit(FOR_PACKAGES_TAGS_LIMIT)
+ end
+end
diff --git a/app/models/performance_monitoring/prometheus_dashboard.rb b/app/models/performance_monitoring/prometheus_dashboard.rb
index b04e7e689cd..bf87d2c3916 100644
--- a/app/models/performance_monitoring/prometheus_dashboard.rb
+++ b/app/models/performance_monitoring/prometheus_dashboard.rb
@@ -7,7 +7,7 @@ module PerformanceMonitoring
attr_accessor :dashboard, :panel_groups, :path, :environment, :priority, :templating, :links
validates :dashboard, presence: true
- validates :panel_groups, presence: true
+ validates :panel_groups, array_members: { member_class: PerformanceMonitoring::PrometheusPanelGroup }
class << self
def from_json(json_content)
@@ -35,9 +35,15 @@ module PerformanceMonitoring
new(
dashboard: attributes['dashboard'],
- panel_groups: attributes['panel_groups']&.map { |group| PrometheusPanelGroup.from_json(group) }
+ panel_groups: initialize_children_collection(attributes['panel_groups'])
)
end
+
+ def initialize_children_collection(children)
+ return unless children.is_a?(Array)
+
+ children.map { |group| PerformanceMonitoring::PrometheusPanelGroup.from_json(group) }
+ end
end
def to_yaml
@@ -47,7 +53,7 @@ module PerformanceMonitoring
# This method is planned to be refactored as a part of https://gitlab.com/gitlab-org/gitlab/-/issues/219398
# implementation. For new existing logic was reused to faster deliver MVC
def schema_validation_warnings
- self.class.from_json(self.as_json)
+ self.class.from_json(reload_schema)
nil
rescue ActiveModel::ValidationError => exception
exception.model.errors.map { |attr, error| "#{attr}: #{error}" }
@@ -55,6 +61,14 @@ module PerformanceMonitoring
private
+ # dashboard finder methods are somehow limited, #find includes checking if
+ # user is authorised to view selected dashboard, but modifies schema, which in some cases may
+ # cause false positives returned from validation, and #find_raw does not authorise users
+ def reload_schema
+ project = environment&.project
+ project.nil? ? self.as_json : Gitlab::Metrics::Dashboard::Finder.find_raw(project, dashboard_path: path)
+ end
+
def yaml_valid_attributes
%w(panel_groups panels metrics group priority type title y_label weight id unit label query query_range dashboard)
end
diff --git a/app/models/performance_monitoring/prometheus_panel.rb b/app/models/performance_monitoring/prometheus_panel.rb
index a16a68ba832..b33c09001ae 100644
--- a/app/models/performance_monitoring/prometheus_panel.rb
+++ b/app/models/performance_monitoring/prometheus_panel.rb
@@ -7,7 +7,8 @@ module PerformanceMonitoring
attr_accessor :type, :title, :y_label, :weight, :metrics, :y_axis, :max_value
validates :title, presence: true
- validates :metrics, presence: true
+ validates :metrics, array_members: { member_class: PerformanceMonitoring::PrometheusMetric }
+
class << self
def from_json(json_content)
build_from_hash(json_content).tap(&:validate!)
@@ -23,9 +24,15 @@ module PerformanceMonitoring
title: attributes['title'],
y_label: attributes['y_label'],
weight: attributes['weight'],
- metrics: attributes['metrics']&.map { |metric| PrometheusMetric.from_json(metric) }
+ metrics: initialize_children_collection(attributes['metrics'])
)
end
+
+ def initialize_children_collection(children)
+ return unless children.is_a?(Array)
+
+ children.map { |metrics| PerformanceMonitoring::PrometheusMetric.from_json(metrics) }
+ end
end
def id(group_title)
diff --git a/app/models/performance_monitoring/prometheus_panel_group.rb b/app/models/performance_monitoring/prometheus_panel_group.rb
index f88106f259b..7f3d2a1b8f4 100644
--- a/app/models/performance_monitoring/prometheus_panel_group.rb
+++ b/app/models/performance_monitoring/prometheus_panel_group.rb
@@ -7,7 +7,8 @@ module PerformanceMonitoring
attr_accessor :group, :priority, :panels
validates :group, presence: true
- validates :panels, presence: true
+ validates :panels, array_members: { member_class: PerformanceMonitoring::PrometheusPanel }
+
class << self
def from_json(json_content)
build_from_hash(json_content).tap(&:validate!)
@@ -21,9 +22,15 @@ module PerformanceMonitoring
new(
group: attributes['group'],
priority: attributes['priority'],
- panels: attributes['panels']&.map { |panel| PrometheusPanel.from_json(panel) }
+ panels: initialize_children_collection(attributes['panels'])
)
end
+
+ def initialize_children_collection(children)
+ return unless children.is_a?(Array)
+
+ children.map { |panels| PerformanceMonitoring::PrometheusPanel.from_json(panels) }
+ end
end
end
end
diff --git a/app/models/personal_access_token.rb b/app/models/personal_access_token.rb
index 7afee2a35cb..488ebd531a8 100644
--- a/app/models/personal_access_token.rb
+++ b/app/models/personal_access_token.rb
@@ -17,11 +17,13 @@ class PersonalAccessToken < ApplicationRecord
before_save :ensure_token
- scope :active, -> { where("revoked = false AND (expires_at >= NOW() OR expires_at IS NULL)") }
- scope :expiring_and_not_notified, ->(date) { where(["revoked = false AND expire_notification_delivered = false AND expires_at >= NOW() AND expires_at <= ?", date]) }
- scope :inactive, -> { where("revoked = true OR expires_at < NOW()") }
+ scope :active, -> { where("revoked = false AND (expires_at >= CURRENT_DATE OR expires_at IS NULL)") }
+ scope :expiring_and_not_notified, ->(date) { where(["revoked = false AND expire_notification_delivered = false AND expires_at >= CURRENT_DATE AND expires_at <= ?", date]) }
+ scope :inactive, -> { where("revoked = true OR expires_at < CURRENT_DATE") }
scope :with_impersonation, -> { where(impersonation: true) }
scope :without_impersonation, -> { where(impersonation: false) }
+ scope :revoked, -> { where(revoked: true) }
+ scope :not_revoked, -> { where(revoked: [false, nil]) }
scope :for_user, -> (user) { where(user: user) }
scope :preload_users, -> { preload(:user) }
scope :order_expires_at_asc, -> { reorder(expires_at: :asc) }
diff --git a/app/models/plan.rb b/app/models/plan.rb
index acac5f9aeae..b4091e0a755 100644
--- a/app/models/plan.rb
+++ b/app/models/plan.rb
@@ -27,7 +27,7 @@ class Plan < ApplicationRecord
end
def actual_limits
- self.limits || PlanLimits.new
+ self.limits || self.build_limits
end
def default?
diff --git a/app/models/plan_limits.rb b/app/models/plan_limits.rb
index 575105cfd79..f17078c0cab 100644
--- a/app/models/plan_limits.rb
+++ b/app/models/plan_limits.rb
@@ -1,23 +1,36 @@
# frozen_string_literal: true
class PlanLimits < ApplicationRecord
+ LimitUndefinedError = Class.new(StandardError)
+
belongs_to :plan
- def exceeded?(limit_name, object)
- return false unless enabled?(limit_name)
+ def exceeded?(limit_name, subject, alternate_limit: 0)
+ limit = limit_for(limit_name, alternate_limit: alternate_limit)
+ return false unless limit
- if object.is_a?(Integer)
- object >= read_attribute(limit_name)
- else
- # object.count >= limit value is slower than checking
+ case subject
+ when Integer
+ subject >= limit
+ when ActiveRecord::Relation
+ # We intentionally not accept just plain ApplicationRecord classes to
+ # enforce the subject to be scoped down to a relation first.
+ #
+ # subject.count >= limit value is slower than checking
# if a record exists at the limit value - 1 position.
- object.offset(read_attribute(limit_name) - 1).exists?
+ subject.offset(limit - 1).exists?
+ else
+ raise ArgumentError, "#{subject.class} is not supported as a limit value"
end
end
- private
+ def limit_for(limit_name, alternate_limit: 0)
+ limit = read_attribute(limit_name)
+ raise LimitUndefinedError, "The limit `#{limit_name}` is undefined" if limit.nil?
+
+ alternate_limit = alternate_limit.call if alternate_limit.respond_to?(:call)
- def enabled?(limit_name)
- read_attribute(limit_name) > 0
+ limits = [limit, alternate_limit]
+ limits.map(&:to_i).select(&:positive?).min
end
end
diff --git a/app/models/product_analytics_event.rb b/app/models/product_analytics_event.rb
new file mode 100644
index 00000000000..95a2e7a26c4
--- /dev/null
+++ b/app/models/product_analytics_event.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+class ProductAnalyticsEvent < ApplicationRecord
+ self.table_name = 'product_analytics_events_experimental'
+
+ # Ignore that the partition key :project_id is part of the formal primary key
+ self.primary_key = :id
+
+ belongs_to :project
+
+ validates :event_id, :project_id, :v_collector, :v_etl, presence: true
+
+ # There is no default Rails timestamps in the table.
+ # collector_tstamp is a timestamp when a collector recorded an event.
+ scope :order_by_time, -> { order(collector_tstamp: :desc) }
+
+ # If we decide to change this scope to use date_trunc('day', collector_tstamp),
+ # we should remember that a btree index on collector_tstamp will be no longer effective.
+ scope :timerange, ->(duration, today = Time.zone.today) {
+ where('collector_tstamp BETWEEN ? AND ? ', today - duration + 1, today + 1)
+ }
+end
diff --git a/app/models/project.rb b/app/models/project.rb
index 845e9e83e78..3aa0db56404 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -65,6 +65,7 @@ class Project < ApplicationRecord
cache_markdown_field :description, pipeline: :description
+ default_value_for :packages_enabled, true
default_value_for :archived, false
default_value_for :resolve_outdated_diff_discussions, false
default_value_for :container_registry_enabled, gitlab_config_features.container_registry
@@ -168,6 +169,7 @@ class Project < ApplicationRecord
has_one :custom_issue_tracker_service
has_one :bugzilla_service
has_one :gitlab_issue_tracker_service, inverse_of: :project
+ has_one :confluence_service
has_one :external_wiki_service
has_one :prometheus_service, inverse_of: :project
has_one :mock_ci_service
@@ -190,6 +192,10 @@ class Project < ApplicationRecord
has_many :forks, through: :forked_to_members, source: :project, inverse_of: :forked_from_project
has_many :fork_network_projects, through: :fork_network, source: :projects
+ # Packages
+ has_many :packages, class_name: 'Packages::Package'
+ has_many :package_files, through: :packages, class_name: 'Packages::PackageFile'
+
has_one :import_state, autosave: true, class_name: 'ProjectImportState', inverse_of: :project
has_one :import_export_upload, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
has_many :export_jobs, class_name: 'ProjectExportJob'
@@ -200,6 +206,7 @@ class Project < ApplicationRecord
has_one :grafana_integration, inverse_of: :project
has_one :project_setting, inverse_of: :project, autosave: true
has_one :alerting_setting, inverse_of: :project, class_name: 'Alerting::ProjectAlertingSetting'
+ has_one :service_desk_setting, class_name: 'ServiceDeskSetting'
# Merge Requests for target project should be removed with it
has_many :merge_requests, foreign_key: 'target_project_id', inverse_of: :target_project
@@ -363,6 +370,7 @@ class Project < ApplicationRecord
to: :project_setting, allow_nil: true
delegate :scheduled?, :started?, :in_progress?, :failed?, :finished?,
prefix: :import, to: :import_state, allow_nil: true
+ delegate :squash_always?, :squash_never?, :squash_enabled_by_default?, :squash_readonly?, to: :project_setting
delegate :no_import?, to: :import_state, allow_nil: true
delegate :name, to: :owner, allow_nil: true, prefix: true
delegate :members, to: :team, prefix: true
@@ -376,7 +384,10 @@ class Project < ApplicationRecord
delegate :default_git_depth, :default_git_depth=, to: :ci_cd_settings, prefix: :ci
delegate :forward_deployment_enabled, :forward_deployment_enabled=, :forward_deployment_enabled?, to: :ci_cd_settings
delegate :actual_limits, :actual_plan_name, to: :namespace, allow_nil: true
- delegate :allow_merge_on_skipped_pipeline, :allow_merge_on_skipped_pipeline?, :allow_merge_on_skipped_pipeline=, to: :project_setting
+ delegate :allow_merge_on_skipped_pipeline, :allow_merge_on_skipped_pipeline?,
+ :allow_merge_on_skipped_pipeline=, :has_confluence?,
+ to: :project_setting
+ delegate :active?, to: :prometheus_service, allow_nil: true, prefix: true
# Validations
validates :creator, presence: true, on: :create
@@ -439,6 +450,7 @@ class Project < ApplicationRecord
# Sometimes queries (e.g. using CTEs) require explicit disambiguation with table name
scope :projects_order_id_desc, -> { reorder(self.arel_table['id'].desc) }
+ scope :with_packages, -> { joins(:packages) }
scope :in_namespace, ->(namespace_ids) { where(namespace_id: namespace_ids) }
scope :personal, ->(user) { where(namespace_id: user.namespace_id) }
scope :joined, ->(user) { where('namespace_id != ?', user.namespace_id) }
@@ -454,6 +466,7 @@ class Project < ApplicationRecord
scope :with_statistics, -> { includes(:statistics) }
scope :with_namespace, -> { includes(:namespace) }
scope :with_import_state, -> { includes(:import_state) }
+ scope :include_project_feature, -> { includes(:project_feature) }
scope :with_service, ->(service) { joins(service).eager_load(service) }
scope :with_shared_runners, -> { where(shared_runners_enabled: true) }
scope :with_container_registry, -> { where(container_registry_enabled: true) }
@@ -488,6 +501,7 @@ class Project < ApplicationRecord
.where(repository_languages: { programming_language_id: lang_id_query })
end
+ scope :service_desk_enabled, -> { where(service_desk_enabled: true) }
scope :with_builds_enabled, -> { with_feature_enabled(:builds) }
scope :with_issues_enabled, -> { with_feature_enabled(:issues) }
scope :with_issues_available_for_user, ->(current_user) { with_feature_available_for_user(:issues, current_user) }
@@ -513,9 +527,8 @@ class Project < ApplicationRecord
.where(project_pages_metadata: { project_id: nil })
end
- scope :with_api_entity_associations, -> {
- preload(:project_feature, :route, :tags,
- group: :ip_restrictions, namespace: [:route, :owner])
+ scope :with_api_commit_entity_associations, -> {
+ preload(:project_feature, :route, namespace: [:route, :owner])
}
enum auto_cancel_pending_pipelines: { disabled: 0, enabled: 1 }
@@ -532,6 +545,10 @@ class Project < ApplicationRecord
# Used by Projects::CleanupService to hold a map of rewritten object IDs
mount_uploader :bfg_object_map, AttachmentUploader
+ def self.with_api_entity_associations
+ preload(:project_feature, :route, :tags, :group, namespace: [:route, :owner])
+ end
+
def self.with_web_entity_associations
preload(:project_feature, :route, :creator, :group, namespace: [:route, :owner])
end
@@ -589,6 +606,14 @@ class Project < ApplicationRecord
end
end
+ def self.projects_user_can(projects, user, action)
+ projects = where(id: projects)
+
+ DeclarativePolicy.user_scope do
+ projects.select { |project| Ability.allowed?(user, action, project) }
+ end
+ end
+
# This scope returns projects where user has access to both the project and the feature.
def self.filter_by_feature_visibility(feature, user)
with_feature_available_for_user(feature, user)
@@ -675,10 +700,11 @@ class Project < ApplicationRecord
# '>' or its escaped form ('&gt;') are checked for because '>' is sometimes escaped
# when the reference comes from an external source.
def markdown_reference_pattern
- %r{
- #{reference_pattern}
- (#{reference_postfix}|#{reference_postfix_escaped})
- }x
+ @markdown_reference_pattern ||=
+ %r{
+ #{reference_pattern}
+ (#{reference_postfix}|#{reference_postfix_escaped})
+ }x
end
def trending
@@ -706,6 +732,12 @@ class Project < ApplicationRecord
from_union([with_issues_enabled, with_merge_requests_enabled]).select(:id)
end
+
+ def find_by_service_desk_project_key(key)
+ # project_key is not indexed for now
+ # see https://gitlab.com/gitlab-org/gitlab/-/merge_requests/24063#note_282435524 for details
+ joins(:service_desk_setting).find_by('service_desk_settings.project_key' => key)
+ end
end
def initialize(attributes = nil)
@@ -839,6 +871,15 @@ class Project < ApplicationRecord
end
end
+ # Because we use default_value_for we need to be sure
+ # packages_enabled= method does exist even if we rollback migration.
+ # Otherwise many tests from spec/migrations will fail.
+ def packages_enabled=(value)
+ if has_attribute?(:packages_enabled)
+ write_attribute(:packages_enabled, value)
+ end
+ end
+
def cleanup
@repository = nil
end
@@ -1699,7 +1740,7 @@ class Project < ApplicationRecord
url_path = full_path.partition('/').last
# If the project path is the same as host, we serve it as group page
- return url if url == "#{Settings.pages.protocol}://#{url_path}"
+ return url if url == "#{Settings.pages.protocol}://#{url_path}".downcase
"#{url}/#{url_path}"
end
@@ -1795,6 +1836,7 @@ class Project < ApplicationRecord
after_create_default_branch
join_pool_repository
refresh_markdown_cache!
+ write_repository_config
end
def update_project_counter_caches
@@ -1922,6 +1964,7 @@ class Project < ApplicationRecord
.append(key: 'CI_PROJECT_PATH', value: full_path)
.append(key: 'CI_PROJECT_PATH_SLUG', value: full_path_slug)
.append(key: 'CI_PROJECT_NAMESPACE', value: namespace.full_path)
+ .append(key: 'CI_PROJECT_ROOT_NAMESPACE', value: namespace.root_ancestor.path)
.append(key: 'CI_PROJECT_URL', value: web_url)
.append(key: 'CI_PROJECT_VISIBILITY', value: Gitlab::VisibilityLevel.string_level(visibility_level))
.append(key: 'CI_PROJECT_REPOSITORY_LANGUAGES', value: repository_languages.map(&:name).join(',').downcase)
@@ -2131,7 +2174,13 @@ class Project < ApplicationRecord
# rubocop: disable CodeReuse/ServiceClass
def forks_count
- Projects::ForksCountService.new(self).count
+ BatchLoader.for(self).batch do |projects, loader|
+ fork_count_per_project = ::Projects::BatchForksCountService.new(projects).refresh_cache_and_retrieve_data
+
+ fork_count_per_project.each do |project, count|
+ loader.call(project, count)
+ end
+ end
end
# rubocop: enable CodeReuse/ServiceClass
@@ -2410,6 +2459,37 @@ class Project < ApplicationRecord
super || build_metrics_setting
end
+ def service_desk_enabled
+ Gitlab::ServiceDesk.enabled?(project: self)
+ end
+
+ alias_method :service_desk_enabled?, :service_desk_enabled
+
+ def service_desk_address
+ return unless service_desk_enabled?
+
+ config = Gitlab.config.incoming_email
+ wildcard = Gitlab::IncomingEmail::WILDCARD_PLACEHOLDER
+
+ config.address&.gsub(wildcard, "#{full_path_slug}-#{id}-issue-")
+ end
+
+ def root_namespace
+ if namespace.has_parent?
+ namespace.root_ancestor
+ else
+ namespace
+ end
+ end
+
+ def package_already_taken?(package_name)
+ namespace.root_ancestor.all_projects
+ .joins(:packages)
+ .where.not(id: id)
+ .merge(Packages::Package.with_name(package_name))
+ .exists?
+ end
+
private
def find_service(services, name)
diff --git a/app/models/project_services/alerts_service.rb b/app/models/project_services/alerts_service.rb
index 58c47accfd1..28902114f3c 100644
--- a/app/models/project_services/alerts_service.rb
+++ b/app/models/project_services/alerts_service.rb
@@ -78,3 +78,5 @@ class AlertsService < Service
Gitlab::Routing.url_helpers
end
end
+
+AlertsService.prepend_if_ee('EE::AlertsService')
diff --git a/app/models/project_services/bugzilla_service.rb b/app/models/project_services/bugzilla_service.rb
index 0a498fde95a..4332db3e961 100644
--- a/app/models/project_services/bugzilla_service.rb
+++ b/app/models/project_services/bugzilla_service.rb
@@ -3,11 +3,11 @@
class BugzillaService < IssueTrackerService
validates :project_url, :issues_url, :new_issue_url, presence: true, public_url: true, if: :activated?
- def default_title
+ def title
'Bugzilla'
end
- def default_description
+ def description
s_('IssueTracker|Bugzilla issue tracker')
end
diff --git a/app/models/project_services/confluence_service.rb b/app/models/project_services/confluence_service.rb
new file mode 100644
index 00000000000..dd44a0d1d56
--- /dev/null
+++ b/app/models/project_services/confluence_service.rb
@@ -0,0 +1,91 @@
+# frozen_string_literal: true
+
+class ConfluenceService < Service
+ include ActionView::Helpers::UrlHelper
+
+ VALID_SCHEME_MATCH = %r{\Ahttps?\Z}.freeze
+ VALID_HOST_MATCH = %r{\A.+\.atlassian\.net\Z}.freeze
+ VALID_PATH_MATCH = %r{\A/wiki(/|\Z)}.freeze
+
+ prop_accessor :confluence_url
+
+ validates :confluence_url, presence: true, if: :activated?
+ validate :validate_confluence_url_is_cloud, if: :activated?
+
+ after_commit :cache_project_has_confluence
+
+ def self.to_param
+ 'confluence'
+ end
+
+ def self.supported_events
+ %w()
+ end
+
+ def title
+ s_('ConfluenceService|Confluence Workspace')
+ end
+
+ def description
+ s_('ConfluenceService|Connect a Confluence Cloud Workspace to your GitLab project')
+ end
+
+ def detailed_description
+ return unless project.wiki_enabled?
+
+ if activated?
+ wiki_url = project.wiki.web_url
+
+ s_(
+ 'ConfluenceService|Your GitLab Wiki can be accessed here: %{wiki_link}. To re-enable your GitLab Wiki, disable this integration' %
+ { wiki_link: link_to(wiki_url, wiki_url) }
+ ).html_safe
+ else
+ s_('ConfluenceService|Enabling the Confluence Workspace will disable the default GitLab Wiki. Your GitLab Wiki data will be saved and you can always re-enable it later by turning off this integration').html_safe
+ end
+ end
+
+ def fields
+ [
+ {
+ type: 'text',
+ name: 'confluence_url',
+ title: 'Confluence Cloud Workspace URL',
+ placeholder: s_('ConfluenceService|The URL of the Confluence Workspace'),
+ required: true
+ }
+ ]
+ end
+
+ def can_test?
+ false
+ end
+
+ private
+
+ def validate_confluence_url_is_cloud
+ unless confluence_uri_valid?
+ errors.add(:confluence_url, 'URL must be to a Confluence Cloud Workspace hosted on atlassian.net')
+ end
+ end
+
+ def confluence_uri_valid?
+ return false unless confluence_url
+
+ uri = URI.parse(confluence_url)
+
+ (uri.scheme&.match(VALID_SCHEME_MATCH) &&
+ uri.host&.match(VALID_HOST_MATCH) &&
+ uri.path&.match(VALID_PATH_MATCH)).present?
+
+ rescue URI::InvalidURIError
+ false
+ end
+
+ def cache_project_has_confluence
+ return unless project && !project.destroyed?
+
+ project.project_setting.save! unless project.project_setting.persisted?
+ project.project_setting.update_column(:has_confluence, active?)
+ end
+end
diff --git a/app/models/project_services/custom_issue_tracker_service.rb b/app/models/project_services/custom_issue_tracker_service.rb
index dbc42b1b86d..fc58ba27c3d 100644
--- a/app/models/project_services/custom_issue_tracker_service.rb
+++ b/app/models/project_services/custom_issue_tracker_service.rb
@@ -3,11 +3,11 @@
class CustomIssueTrackerService < IssueTrackerService
validates :project_url, :issues_url, :new_issue_url, presence: true, public_url: true, if: :activated?
- def default_title
+ def title
'Custom Issue Tracker'
end
- def default_description
+ def description
s_('IssueTracker|Custom issue tracker')
end
@@ -17,8 +17,6 @@ class CustomIssueTrackerService < IssueTrackerService
def fields
[
- { type: 'text', name: 'title', placeholder: title },
- { type: 'text', name: 'description', placeholder: description },
{ type: 'text', name: 'project_url', placeholder: 'Project url', required: true },
{ type: 'text', name: 'issues_url', placeholder: 'Issue url', required: true },
{ type: 'text', name: 'new_issue_url', placeholder: 'New Issue url', required: true }
diff --git a/app/models/project_services/gitlab_issue_tracker_service.rb b/app/models/project_services/gitlab_issue_tracker_service.rb
index ec28602b5e6..b3f44e040bc 100644
--- a/app/models/project_services/gitlab_issue_tracker_service.rb
+++ b/app/models/project_services/gitlab_issue_tracker_service.rb
@@ -7,11 +7,11 @@ class GitlabIssueTrackerService < IssueTrackerService
default_value_for :default, true
- def default_title
+ def title
'GitLab'
end
- def default_description
+ def description
s_('IssueTracker|GitLab issue tracker')
end
diff --git a/app/models/project_services/issue_tracker_service.rb b/app/models/project_services/issue_tracker_service.rb
index f5d6ae10469..694374e9548 100644
--- a/app/models/project_services/issue_tracker_service.rb
+++ b/app/models/project_services/issue_tracker_service.rb
@@ -25,28 +25,6 @@ class IssueTrackerService < Service
end
end
- # this will be removed as part of https://gitlab.com/gitlab-org/gitlab/issues/29404
- def title
- if title_attribute = read_attribute(:title)
- title_attribute
- elsif self.properties && self.properties['title'].present?
- self.properties['title']
- else
- default_title
- end
- end
-
- # this will be removed as part of https://gitlab.com/gitlab-org/gitlab/issues/29404
- def description
- if description_attribute = read_attribute(:description)
- description_attribute
- elsif self.properties && self.properties['description'].present?
- self.properties['description']
- else
- default_description
- end
- end
-
def handle_properties
# this has been moved from initialize_properties and should be improved
# as part of https://gitlab.com/gitlab-org/gitlab/issues/29404
@@ -54,13 +32,6 @@ class IssueTrackerService < Service
@legacy_properties_data = properties.dup
data_values = properties.slice!('title', 'description')
- properties.each do |key, _|
- current_value = self.properties.delete(key)
- value = attribute_changed?(key) ? attribute_change(key).last : current_value
-
- write_attribute(key, value)
- end
-
data_values.reject! { |key| data_fields.changed.include?(key) }
data_values.slice!(*data_fields.attributes.keys)
data_fields.assign_attributes(data_values) if data_values.present?
@@ -102,7 +73,6 @@ class IssueTrackerService < Service
def fields
[
- { type: 'text', name: 'description', placeholder: description },
{ type: 'text', name: 'project_url', placeholder: 'Project url', required: true },
{ type: 'text', name: 'issues_url', placeholder: 'Issue url', required: true },
{ type: 'text', name: 'new_issue_url', placeholder: 'New Issue url', required: true }
@@ -117,8 +87,6 @@ class IssueTrackerService < Service
def set_default_data
return unless issues_tracker.present?
- self.title ||= issues_tracker['title']
-
# we don't want to override if we have set something
return if project_url || issues_url || new_issue_url
diff --git a/app/models/project_services/jira_service.rb b/app/models/project_services/jira_service.rb
index bb4d35cad22..4ea2ec10f11 100644
--- a/app/models/project_services/jira_service.rb
+++ b/app/models/project_services/jira_service.rb
@@ -23,7 +23,7 @@ class JiraService < IssueTrackerService
# TODO: we can probably just delegate as part of
# https://gitlab.com/gitlab-org/gitlab/issues/29404
- data_field :username, :password, :url, :api_url, :jira_issue_transition_id
+ data_field :username, :password, :url, :api_url, :jira_issue_transition_id, :project_key, :issues_enabled
before_update :reset_password
@@ -64,8 +64,6 @@ class JiraService < IssueTrackerService
def set_default_data
return unless issues_tracker.present?
- self.title ||= issues_tracker['title']
-
return if url
data_fields.url ||= issues_tracker['url']
@@ -103,11 +101,11 @@ class JiraService < IssueTrackerService
[Jira service documentation](#{help_page_url('user/project/integrations/jira')})."
end
- def default_title
+ def title
'Jira'
end
- def default_description
+ def description
s_('JiraService|Jira issue tracker')
end
@@ -130,7 +128,7 @@ class JiraService < IssueTrackerService
end
def new_issue_url
- "#{url}/secure/CreateIssue.jspa"
+ "#{url}/secure/CreateIssue!default.jspa"
end
alias_method :original_url, :url
@@ -442,3 +440,5 @@ class JiraService < IssueTrackerService
end
end
end
+
+JiraService.prepend_if_ee('EE::JiraService')
diff --git a/app/models/project_services/prometheus_service.rb b/app/models/project_services/prometheus_service.rb
index 44a41969b1c..997c6eba91a 100644
--- a/app/models/project_services/prometheus_service.rb
+++ b/app/models/project_services/prometheus_service.rb
@@ -28,6 +28,9 @@ class PrometheusService < MonitoringService
after_create_commit :create_default_alerts
+ scope :preload_project, -> { preload(:project) }
+ scope :with_clusters_with_cilium, -> { joins(project: [:clusters]).merge(Clusters::Cluster.with_available_cilium) }
+
def initialize_properties
if properties.nil?
self.properties = {}
@@ -51,7 +54,7 @@ class PrometheusService < MonitoringService
end
def fields
- result = [
+ [
{
type: 'checkbox',
name: 'manual_configuration',
@@ -64,30 +67,23 @@ class PrometheusService < MonitoringService
title: 'API URL',
placeholder: s_('PrometheusService|Prometheus API Base URL, like http://prometheus.example.com/'),
required: true
+ },
+ {
+ type: 'text',
+ name: 'google_iap_audience_client_id',
+ title: 'Google IAP Audience Client ID',
+ placeholder: s_('PrometheusService|Client ID of the IAP secured resource (looks like IAP_CLIENT_ID.apps.googleusercontent.com)'),
+ autocomplete: 'off',
+ required: false
+ },
+ {
+ type: 'textarea',
+ name: 'google_iap_service_account_json',
+ title: 'Google IAP Service Account JSON',
+ placeholder: s_('PrometheusService|Contents of the credentials.json file of your service account, like: { "type": "service_account", "project_id": ... }'),
+ required: false
}
]
-
- if Feature.enabled?(:prometheus_service_iap_auth)
- result += [
- {
- type: 'text',
- name: 'google_iap_audience_client_id',
- title: 'Google IAP Audience Client ID',
- placeholder: s_('PrometheusService|Client ID of the IAP secured resource (looks like IAP_CLIENT_ID.apps.googleusercontent.com)'),
- autocomplete: 'off',
- required: false
- },
- {
- type: 'textarea',
- name: 'google_iap_service_account_json',
- title: 'Google IAP Service Account JSON',
- placeholder: s_('PrometheusService|Contents of the credentials.json file of your service account, like: { "type": "service_account", "project_id": ... }'),
- required: false
- }
- ]
- end
-
- result
end
# Check we can connect to the Prometheus API
@@ -103,7 +99,7 @@ class PrometheusService < MonitoringService
options = { allow_local_requests: allow_local_api_url? }
- if Feature.enabled?(:prometheus_service_iap_auth) && behind_iap?
+ if behind_iap?
# Adds the Authorization header
options[:headers] = iap_client.apply({})
end
diff --git a/app/models/project_services/redmine_service.rb b/app/models/project_services/redmine_service.rb
index a4ca0d20669..df78520d65f 100644
--- a/app/models/project_services/redmine_service.rb
+++ b/app/models/project_services/redmine_service.rb
@@ -3,11 +3,11 @@
class RedmineService < IssueTrackerService
validates :project_url, :issues_url, :new_issue_url, presence: true, public_url: true, if: :activated?
- def default_title
+ def title
'Redmine'
end
- def default_description
+ def description
s_('IssueTracker|Redmine issue tracker')
end
diff --git a/app/models/project_services/youtrack_service.rb b/app/models/project_services/youtrack_service.rb
index 40203ad692d..7fb3bde44a5 100644
--- a/app/models/project_services/youtrack_service.rb
+++ b/app/models/project_services/youtrack_service.rb
@@ -12,11 +12,11 @@ class YoutrackService < IssueTrackerService
end
end
- def default_title
+ def title
'YouTrack'
end
- def default_description
+ def description
s_('IssueTracker|YouTrack issue tracker')
end
@@ -26,7 +26,6 @@ class YoutrackService < IssueTrackerService
def fields
[
- { type: 'text', name: 'description', placeholder: description },
{ type: 'text', name: 'project_url', title: 'Project URL', placeholder: 'Project URL', required: true },
{ type: 'text', name: 'issues_url', title: 'Issue URL', placeholder: 'Issue URL', required: true }
]
diff --git a/app/models/project_setting.rb b/app/models/project_setting.rb
index 9022d3e879d..aca7eec3382 100644
--- a/app/models/project_setting.rb
+++ b/app/models/project_setting.rb
@@ -3,7 +3,22 @@
class ProjectSetting < ApplicationRecord
belongs_to :project, inverse_of: :project_setting
+ enum squash_option: {
+ never: 0,
+ always: 1,
+ default_on: 2,
+ default_off: 3
+ }, _prefix: 'squash'
+
self.primary_key = :project_id
+
+ def squash_enabled_by_default?
+ %w[always default_on].include?(squash_option)
+ end
+
+ def squash_readonly?
+ %w[always never].include?(squash_option)
+ end
end
ProjectSetting.prepend_if_ee('EE::ProjectSetting')
diff --git a/app/models/project_statistics.rb b/app/models/project_statistics.rb
index 6f04a36392d..f153bfe3f5b 100644
--- a/app/models/project_statistics.rb
+++ b/app/models/project_statistics.rb
@@ -7,16 +7,12 @@ class ProjectStatistics < ApplicationRecord
belongs_to :namespace
default_value_for :wiki_size, 0
-
- # older migrations fail due to non-existent attribute without this
- def wiki_size
- has_attribute?(:wiki_size) ? super : 0
- end
+ default_value_for :snippets_size, 0
before_save :update_storage_size
- COLUMNS_TO_REFRESH = [:repository_size, :wiki_size, :lfs_objects_size, :commit_count].freeze
- INCREMENTABLE_COLUMNS = { build_artifacts_size: %i[storage_size], packages_size: %i[storage_size] }.freeze
+ COLUMNS_TO_REFRESH = [:repository_size, :wiki_size, :lfs_objects_size, :commit_count, :snippets_size].freeze
+ INCREMENTABLE_COLUMNS = { build_artifacts_size: %i[storage_size], packages_size: %i[storage_size], snippets_size: %i[storage_size] }.freeze
NAMESPACE_RELATABLE_COLUMNS = [:repository_size, :wiki_size, :lfs_objects_size].freeze
scope :for_project_ids, ->(project_ids) { where(project_id: project_ids) }
@@ -54,17 +50,37 @@ class ProjectStatistics < ApplicationRecord
self.wiki_size = project.wiki.repository.size * 1.megabyte
end
+ def update_snippets_size
+ self.snippets_size = project.snippets.with_statistics.sum(:repository_size)
+ end
+
def update_lfs_objects_size
self.lfs_objects_size = project.lfs_objects.sum(:size)
end
- # older migrations fail due to non-existent attribute without this
- def packages_size
- has_attribute?(:packages_size) ? super : 0
+ # `wiki_size` and `snippets_size` have no default value in the database
+ # and the column can be nil.
+ # This means that, when the columns were added, all rows had nil
+ # values on them.
+ # Therefore, any call to any of those methods will return nil instead
+ # of 0, because `default_value_for` works with new records, not existing ones.
+ #
+ # These two methods provide consistency and avoid returning nil.
+ def wiki_size
+ super.to_i
+ end
+
+ def snippets_size
+ super.to_i
end
def update_storage_size
- self.storage_size = repository_size + wiki_size.to_i + lfs_objects_size + build_artifacts_size + packages_size
+ storage_size = repository_size + wiki_size + lfs_objects_size + build_artifacts_size + packages_size
+ # The `snippets_size` column was added on 20200622095419 but db/post_migrate/20190527194900_schedule_calculate_wiki_sizes.rb
+ # might try to update project statistics before the `snippets_size` column has been created.
+ storage_size += snippets_size if self.class.column_names.include?('snippets_size')
+
+ self.storage_size = storage_size
end
# Since this incremental update method does not call update_storage_size above,
diff --git a/app/models/prometheus_alert.rb b/app/models/prometheus_alert.rb
index fbc0281296f..32f9809e538 100644
--- a/app/models/prometheus_alert.rb
+++ b/app/models/prometheus_alert.rb
@@ -16,6 +16,7 @@ class PrometheusAlert < ApplicationRecord
has_many :prometheus_alert_events, inverse_of: :prometheus_alert
has_many :related_issues, through: :prometheus_alert_events
+ has_many :alert_management_alerts, class_name: 'AlertManagement::Alert', inverse_of: :prometheus_alert
after_save :clear_prometheus_adapter_cache!
after_destroy :clear_prometheus_adapter_cache!
diff --git a/app/models/prometheus_metric.rb b/app/models/prometheus_metric.rb
index 571b586056b..bfd23d2a334 100644
--- a/app/models/prometheus_metric.rb
+++ b/app/models/prometheus_metric.rb
@@ -11,6 +11,7 @@ class PrometheusMetric < ApplicationRecord
validates :group, presence: true
validates :y_label, presence: true
validates :unit, presence: true
+ validates :identifier, uniqueness: { scope: :project_id }, allow_nil: true
validates :project, presence: true, unless: :common?
validates :project, absence: true, if: :common?
diff --git a/app/models/repository.rb b/app/models/repository.rb
index 911cfc7db38..48e96d4c193 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -149,7 +149,8 @@ class Repository
before: opts[:before],
all: !!opts[:all],
first_parent: !!opts[:first_parent],
- order: opts[:order]
+ order: opts[:order],
+ literal_pathspec: opts.fetch(:literal_pathspec, true)
}
commits = Gitlab::Git::Commit.where(options)
@@ -676,24 +677,24 @@ class Repository
end
end
- def list_last_commits_for_tree(sha, path, offset: 0, limit: 25)
- commits = raw_repository.list_last_commits_for_tree(sha, path, offset: offset, limit: limit)
+ def list_last_commits_for_tree(sha, path, offset: 0, limit: 25, literal_pathspec: false)
+ commits = raw_repository.list_last_commits_for_tree(sha, path, offset: offset, limit: limit, literal_pathspec: literal_pathspec)
commits.each do |path, commit|
commits[path] = ::Commit.new(commit, container)
end
end
- def last_commit_for_path(sha, path)
- commit = raw_repository.last_commit_for_path(sha, path)
+ def last_commit_for_path(sha, path, literal_pathspec: false)
+ commit = raw_repository.last_commit_for_path(sha, path, literal_pathspec: literal_pathspec)
::Commit.new(commit, container) if commit
end
- def last_commit_id_for_path(sha, path)
+ def last_commit_id_for_path(sha, path, literal_pathspec: false)
key = path.blank? ? "last_commit_id_for_path:#{sha}" : "last_commit_id_for_path:#{sha}:#{Digest::SHA1.hexdigest(path)}"
cache.fetch(key) do
- last_commit_for_path(sha, path)&.id
+ last_commit_for_path(sha, path, literal_pathspec: literal_pathspec)&.id
end
end
@@ -712,8 +713,8 @@ class Repository
"#{name}-#{highest_branch_id + 1}"
end
- def branches_sorted_by(value)
- raw_repository.local_branches(sort_by: value)
+ def branches_sorted_by(sort_by, pagination_params = nil)
+ raw_repository.local_branches(sort_by: sort_by, pagination_params: pagination_params)
end
def tags_sorted_by(value)
@@ -1113,7 +1114,7 @@ class Repository
def project
if repo_type.snippet?
container.project
- else
+ elsif container.is_a?(Project)
container
end
end
diff --git a/app/models/resource_event.rb b/app/models/resource_event.rb
index 86e11c2d568..26dcda2630a 100644
--- a/app/models/resource_event.rb
+++ b/app/models/resource_event.rb
@@ -11,6 +11,7 @@ class ResourceEvent < ApplicationRecord
belongs_to :user
scope :created_after, ->(time) { where('created_at > ?', time) }
+ scope :created_on_or_before, ->(time) { where('created_at <= ?', time) }
def discussion_id
strong_memoize(:discussion_id) do
diff --git a/app/models/resource_state_event.rb b/app/models/resource_state_event.rb
index 1d6573b180f..766b4d7a865 100644
--- a/app/models/resource_state_event.rb
+++ b/app/models/resource_state_event.rb
@@ -6,10 +6,16 @@ class ResourceStateEvent < ResourceEvent
validate :exactly_one_issuable
+ belongs_to :source_merge_request, class_name: 'MergeRequest', foreign_key: :source_merge_request_id
+
# state is used for issue and merge request states.
enum state: Issue.available_states.merge(MergeRequest.available_states).merge(reopened: 5)
def self.issuable_attrs
%i(issue merge_request).freeze
end
+
+ def issuable
+ issue || merge_request
+ end
end
diff --git a/app/models/service.rb b/app/models/service.rb
index 2880526c9de..89bde61bfe1 100644
--- a/app/models/service.rb
+++ b/app/models/service.rb
@@ -7,9 +7,12 @@ class Service < ApplicationRecord
include Importable
include ProjectServicesLoggable
include DataFields
+ include IgnorableColumns
+
+ ignore_columns %i[title description], remove_with: '13.4', remove_after: '2020-09-22'
SERVICE_NAMES = %w[
- alerts asana assembla bamboo bugzilla buildkite campfire custom_issue_tracker discord
+ alerts asana assembla bamboo bugzilla buildkite campfire confluence custom_issue_tracker discord
drone_ci emails_on_push external_wiki flowdock hangouts_chat hipchat irker jira
mattermost mattermost_slash_commands microsoft_teams packagist pipelines_email
pivotaltracker prometheus pushover redmine slack slack_slash_commands teamcity unify_circuit webex_teams youtrack
@@ -357,6 +360,14 @@ class Service < ApplicationRecord
service
end
+ def self.instance_exists_for?(type)
+ exists?(instance: true, type: type)
+ end
+
+ def self.instance_for(type)
+ find_by(instance: true, type: type)
+ end
+
# override if needed
def supports_data_fields?
false
@@ -381,30 +392,7 @@ class Service < ApplicationRecord
end
def self.event_description(event)
- case event
- when "push", "push_events"
- "Event will be triggered by a push to the repository"
- when "tag_push", "tag_push_events"
- "Event will be triggered when a new tag is pushed to the repository"
- when "note", "note_events"
- "Event will be triggered when someone adds a comment"
- when "issue", "issue_events"
- "Event will be triggered when an issue is created/updated/closed"
- when "confidential_issue", "confidential_issue_events"
- "Event will be triggered when a confidential issue is created/updated/closed"
- when "merge_request", "merge_request_events"
- "Event will be triggered when a merge request is created/updated/merged"
- when "pipeline", "pipeline_events"
- "Event will be triggered when a pipeline status changes"
- when "wiki_page", "wiki_page_events"
- "Event will be triggered when a wiki page is created/updated"
- when "commit", "commit_events"
- "Event will be triggered when a commit is created/updated"
- when "deployment"
- "Event will be triggered when a deployment finishes"
- when "alert"
- "Event will be triggered when a new, unique alert is recorded"
- end
+ ServicesHelper.service_event_description(event)
end
def valid_recipients?
diff --git a/app/models/service_desk_setting.rb b/app/models/service_desk_setting.rb
new file mode 100644
index 00000000000..bcc17d32272
--- /dev/null
+++ b/app/models/service_desk_setting.rb
@@ -0,0 +1,30 @@
+# frozen_string_literal: true
+
+class ServiceDeskSetting < ApplicationRecord
+ include Gitlab::Utils::StrongMemoize
+
+ belongs_to :project
+ validates :project_id, presence: true
+ validate :valid_issue_template
+ validates :outgoing_name, length: { maximum: 255 }, allow_blank: true
+ validates :project_key, length: { maximum: 255 }, allow_blank: true, format: { with: /\A[a-z0-9_]+\z/ }
+
+ def issue_template_content
+ strong_memoize(:issue_template_content) do
+ next unless issue_template_key.present?
+
+ Gitlab::Template::IssueTemplate.find(issue_template_key, project).content
+ rescue ::Gitlab::Template::Finders::RepoTemplateFinder::FileNotFoundError
+ end
+ end
+
+ def issue_template_missing?
+ issue_template_key.present? && !issue_template_content.present?
+ end
+
+ def valid_issue_template
+ if issue_template_missing?
+ errors.add(:issue_template_key, 'is empty or does not exist')
+ end
+ end
+end
diff --git a/app/models/snippet.rb b/app/models/snippet.rb
index b63ab003711..eb3960ff12b 100644
--- a/app/models/snippet.rb
+++ b/app/models/snippet.rb
@@ -45,6 +45,9 @@ class Snippet < ApplicationRecord
has_many :user_mentions, class_name: "SnippetUserMention", dependent: :delete_all # rubocop:disable Cop/ActiveRecordDependent
has_one :snippet_repository, inverse_of: :snippet
+ # We need to add the `dependent` in order to call the after_destroy callback
+ has_one :statistics, class_name: 'SnippetStatistics', dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
+
delegate :name, :email, to: :author, prefix: true, allow_nil: true
validates :author, presence: true
@@ -68,6 +71,7 @@ class Snippet < ApplicationRecord
validates :visibility_level, inclusion: { in: Gitlab::VisibilityLevel.values }
after_save :store_mentions!, if: :any_mentionable_attributes_changed?
+ after_create :create_statistics
# Scopes
scope :are_internal, -> { where(visibility_level: Snippet::INTERNAL) }
@@ -77,6 +81,7 @@ class Snippet < ApplicationRecord
scope :fresh, -> { order("created_at DESC") }
scope :inc_author, -> { includes(:author) }
scope :inc_relations_for_view, -> { includes(author: :status) }
+ scope :with_statistics, -> { joins(:statistics) }
attr_mentionable :description
@@ -331,7 +336,13 @@ class Snippet < ApplicationRecord
def file_name_on_repo
return if repository.empty?
- repository.ls_files(repository.root_ref).first
+ list_files(repository.root_ref).first
+ end
+
+ def list_files(ref = nil)
+ return [] if repository.empty?
+
+ repository.ls_files(ref)
end
class << self
diff --git a/app/models/snippet_input_action.rb b/app/models/snippet_input_action.rb
index 7f4ab775ab0..cc6373264cc 100644
--- a/app/models/snippet_input_action.rb
+++ b/app/models/snippet_input_action.rb
@@ -15,9 +15,10 @@ class SnippetInputAction
validates :action, inclusion: { in: ACTIONS, message: "%{value} is not a valid action" }
validates :previous_path, presence: true, if: :move_action?
- validates :file_path, presence: true
+ validates :file_path, presence: true, unless: :create_action?
validates :content, presence: true, if: -> (action) { action.create_action? || action.update_action? }
validate :ensure_same_file_path_and_previous_path, if: :update_action?
+ validate :ensure_different_file_path_and_previous_path, if: :move_action?
validate :ensure_allowed_action
def initialize(action: nil, previous_path: nil, file_path: nil, content: nil, allowed_actions: nil)
@@ -52,6 +53,12 @@ class SnippetInputAction
errors.add(:file_path, "can't be different from the previous_path attribute")
end
+ def ensure_different_file_path_and_previous_path
+ return if previous_path != file_path
+
+ errors.add(:file_path, 'must be different from the previous_path attribute')
+ end
+
def ensure_allowed_action
return if @allowed_actions.empty?
diff --git a/app/models/snippet_statistics.rb b/app/models/snippet_statistics.rb
new file mode 100644
index 00000000000..7439f98d114
--- /dev/null
+++ b/app/models/snippet_statistics.rb
@@ -0,0 +1,69 @@
+# frozen_string_literal: true
+
+class SnippetStatistics < ApplicationRecord
+ include AfterCommitQueue
+ include UpdateProjectStatistics
+
+ belongs_to :snippet
+
+ validates :snippet, presence: true
+
+ update_project_statistics project_statistics_name: :snippets_size, statistic_attribute: :repository_size
+
+ delegate :repository, :project, :project_id, to: :snippet
+
+ after_save :update_author_root_storage_statistics, if: :update_author_root_storage_statistics?
+ after_destroy :update_author_root_storage_statistics, unless: :project_snippet?
+
+ def update_commit_count
+ self.commit_count = repository.commit_count
+ end
+
+ def update_repository_size
+ self.repository_size = repository.size.megabytes
+ end
+
+ def update_file_count
+ count = if snippet.repository_exists?
+ repository.ls_files(repository.root_ref).size
+ else
+ 0
+ end
+
+ self.file_count = count
+ end
+
+ def refresh!
+ update_commit_count
+ update_repository_size
+ update_file_count
+
+ save!
+ end
+
+ private
+
+ alias_method :original_update_project_statistics_after_save?, :update_project_statistics_after_save?
+ def update_project_statistics_after_save?
+ project_snippet? && original_update_project_statistics_after_save?
+ end
+
+ alias_method :original_update_project_statistics_after_destroy?, :update_project_statistics_after_destroy?
+ def update_project_statistics_after_destroy?
+ project_snippet? && original_update_project_statistics_after_destroy?
+ end
+
+ def update_author_root_storage_statistics?
+ !project_snippet? && saved_change_to_repository_size?
+ end
+
+ def update_author_root_storage_statistics
+ run_after_commit do
+ Namespaces::ScheduleAggregationWorker.perform_async(snippet.author.namespace_id)
+ end
+ end
+
+ def project_snippet?
+ snippet.is_a?(ProjectSnippet)
+ end
+end
diff --git a/app/models/state_note.rb b/app/models/state_note.rb
index cbcb1c2b49d..5e35f15aac4 100644
--- a/app/models/state_note.rb
+++ b/app/models/state_note.rb
@@ -1,19 +1,47 @@
# frozen_string_literal: true
class StateNote < SyntheticNote
+ include Gitlab::Utils::StrongMemoize
+
def self.from_event(event, resource: nil, resource_parent: nil)
- attrs = note_attributes(event.state, event, resource, resource_parent)
+ attrs = note_attributes(action_by(event), event, resource, resource_parent)
StateNote.new(attrs)
end
def note_html
- @note_html ||= "<p dir=\"auto\">#{note_text(html: true)}</p>"
+ @note_html ||= Banzai::Renderer.cacheless_render_field(self, :note, { group: group, project: project })
end
private
def note_text(html: false)
- event.state
+ if event.state == 'closed'
+ if event.close_after_error_tracking_resolve
+ return 'resolved the corresponding error and closed the issue.'
+ end
+
+ if event.close_auto_resolve_prometheus_alert
+ return 'automatically closed this issue because the alert resolved.'
+ end
+ end
+
+ body = event.state.dup
+ body << " via #{event_source.gfm_reference(project)}" if event_source
+ body
+ end
+
+ def event_source
+ strong_memoize(:event_source) do
+ if event.source_commit
+ project&.commit(event.source_commit)
+ else
+ event.source_merge_request
+ end
+ end
+ end
+
+ def self.action_by(event)
+ event.state == 'reopened' ? 'opened' : event.state
end
end
diff --git a/app/models/suggestion.rb b/app/models/suggestion.rb
index 96ffec90c00..94f3a140098 100644
--- a/app/models/suggestion.rb
+++ b/app/models/suggestion.rb
@@ -38,11 +38,18 @@ class Suggestion < ApplicationRecord
end
def appliable?(cached: true)
- !applied? &&
- noteable.opened? &&
- !outdated?(cached: cached) &&
- different_content? &&
- note.active?
+ inapplicable_reason(cached: cached).nil?
+ end
+
+ def inapplicable_reason(cached: true)
+ strong_memoize("inapplicable_reason_#{cached}") do
+ next :applied if applied?
+ next :merge_request_merged if noteable.merged?
+ next :merge_request_closed if noteable.closed?
+ next :source_branch_deleted unless noteable.source_branch_exists?
+ next :outdated if outdated?(cached: cached) || !note.active?
+ next :same_content unless different_content?
+ end
end
# Overwrites outdated column
@@ -53,6 +60,10 @@ class Suggestion < ApplicationRecord
from_content != fetch_from_content
end
+ def single_line?
+ lines_above.zero? && lines_below.zero?
+ end
+
def target_line
position.new_line
end
diff --git a/app/models/synthetic_note.rb b/app/models/synthetic_note.rb
index 3017140f871..dea7165af9f 100644
--- a/app/models/synthetic_note.rb
+++ b/app/models/synthetic_note.rb
@@ -3,20 +3,18 @@
class SyntheticNote < Note
attr_accessor :resource_parent, :event
- self.abstract_class = true
-
def self.note_attributes(action, event, resource, resource_parent)
resource ||= event.resource
attrs = {
- system: true,
- author: event.user,
- created_at: event.created_at,
- discussion_id: event.discussion_id,
- noteable: resource,
- event: event,
- system_note_metadata: ::SystemNoteMetadata.new(action: action),
- resource_parent: resource_parent
+ system: true,
+ author: event.user,
+ created_at: event.created_at,
+ discussion_id: event.discussion_id,
+ noteable: resource,
+ event: event,
+ system_note_metadata: ::SystemNoteMetadata.new(action: action),
+ resource_parent: resource_parent
}
if resource_parent.is_a?(Project)
diff --git a/app/models/system_note_metadata.rb b/app/models/system_note_metadata.rb
index 4e14bb4e92c..b6ba96c768e 100644
--- a/app/models/system_note_metadata.rb
+++ b/app/models/system_note_metadata.rb
@@ -18,7 +18,8 @@ class SystemNoteMetadata < ApplicationRecord
designs_added designs_modified designs_removed designs_discussion_added
title time_tracking branch milestone discussion task moved
opened closed merged duplicate locked unlocked outdated
- tag due_date pinned_embed cherry_pick health_status
+ tag due_date pinned_embed cherry_pick health_status approved unapproved
+ status alert_issue_added
].freeze
validates :note, presence: true
diff --git a/app/models/todo.rb b/app/models/todo.rb
index 102f36a991e..f973c1ff1d4 100644
--- a/app/models/todo.rb
+++ b/app/models/todo.rb
@@ -7,15 +7,16 @@ class Todo < ApplicationRecord
# Time to wait for todos being removed when not visible for user anymore.
# Prevents TODOs being removed by mistake, for example, removing access from a user
# and giving it back again.
- WAIT_FOR_DELETE = 1.hour
+ WAIT_FOR_DELETE = 1.hour
- ASSIGNED = 1
- MENTIONED = 2
- BUILD_FAILED = 3
- MARKED = 4
- APPROVAL_REQUIRED = 5 # This is an EE-only feature
- UNMERGEABLE = 6
- DIRECTLY_ADDRESSED = 7
+ ASSIGNED = 1
+ MENTIONED = 2
+ BUILD_FAILED = 3
+ MARKED = 4
+ APPROVAL_REQUIRED = 5 # This is an EE-only feature
+ UNMERGEABLE = 6
+ DIRECTLY_ADDRESSED = 7
+ MERGE_TRAIN_REMOVED = 8 # This is an EE-only feature
ACTION_NAMES = {
ASSIGNED => :assigned,
@@ -24,7 +25,8 @@ class Todo < ApplicationRecord
MARKED => :marked,
APPROVAL_REQUIRED => :approval_required,
UNMERGEABLE => :unmergeable,
- DIRECTLY_ADDRESSED => :directly_addressed
+ DIRECTLY_ADDRESSED => :directly_addressed,
+ MERGE_TRAIN_REMOVED => :merge_train_removed
}.freeze
belongs_to :author, class_name: "User"
@@ -165,6 +167,10 @@ class Todo < ApplicationRecord
action == ASSIGNED
end
+ def merge_train_removed?
+ action == MERGE_TRAIN_REMOVED
+ end
+
def done?
state == 'done'
end
diff --git a/app/models/user.rb b/app/models/user.rb
index 431a5b3a5b7..643b759e6f4 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -69,7 +69,7 @@ class User < ApplicationRecord
MINIMUM_INACTIVE_DAYS = 180
- ignore_column :ghost, remove_with: '13.2', remove_after: '2020-06-22'
+ ignore_column :bio, remove_with: '13.4', remove_after: '2020-09-22'
# Override Devise::Models::Trackable#update_tracked_fields!
# to limit database writes to at most once every hour
@@ -163,9 +163,10 @@ class User < ApplicationRecord
has_many :award_emoji, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
has_many :triggers, dependent: :destroy, class_name: 'Ci::Trigger', foreign_key: :owner_id # rubocop:disable Cop/ActiveRecordDependent
- has_many :issue_assignees
+ has_many :issue_assignees, inverse_of: :assignee
+ has_many :merge_request_assignees, inverse_of: :assignee
has_many :assigned_issues, class_name: "Issue", through: :issue_assignees, source: :issue
- has_many :assigned_merge_requests, dependent: :nullify, foreign_key: :assignee_id, class_name: "MergeRequest" # rubocop:disable Cop/ActiveRecordDependent
+ has_many :assigned_merge_requests, class_name: "MergeRequest", through: :merge_request_assignees, source: :merge_request
has_many :custom_attributes, class_name: 'UserCustomAttribute'
has_many :callouts, class_name: 'UserCallout'
@@ -194,7 +195,6 @@ class User < ApplicationRecord
validates :notification_email, devise_email: true, if: ->(user) { user.notification_email != user.email }
validates :public_email, presence: true, uniqueness: true, devise_email: true, allow_blank: true
validates :commit_email, devise_email: true, allow_nil: true, if: ->(user) { user.commit_email != user.email }
- validates :bio, length: { maximum: 255 }, allow_blank: true
validates :projects_limit,
presence: true,
numericality: { greater_than_or_equal_to: 0, less_than_or_equal_to: Gitlab::Database::MAX_INT_VALUE }
@@ -229,7 +229,6 @@ class User < ApplicationRecord
before_save :check_for_verified_email, if: ->(user) { user.email_changed? && !user.new_record? }
before_validation :ensure_namespace_correct
before_save :ensure_namespace_correct # in case validation is skipped
- before_save :ensure_bio_is_assigned_to_user_details, if: :bio_changed?
after_validation :set_username_errors
after_update :username_changed_hook, if: :saved_change_to_username?
after_destroy :post_destroy_hook
@@ -272,6 +271,7 @@ class User < ApplicationRecord
:time_display_relative, :time_display_relative=,
:time_format_in_24h, :time_format_in_24h=,
:show_whitespace_in_diffs, :show_whitespace_in_diffs=,
+ :view_diffs_file_by_file, :view_diffs_file_by_file=,
:tab_width, :tab_width=,
:sourcegraph_enabled, :sourcegraph_enabled=,
:setup_for_company, :setup_for_company=,
@@ -281,6 +281,7 @@ class User < ApplicationRecord
delegate :path, to: :namespace, allow_nil: true, prefix: true
delegate :job_title, :job_title=, to: :user_detail, allow_nil: true
+ delegate :bio, :bio=, :bio_html, to: :user_detail, allow_nil: true
accepts_nested_attributes_for :user_preference, update_only: true
accepts_nested_attributes_for :user_detail, update_only: true
@@ -619,11 +620,12 @@ class User < ApplicationRecord
# Pattern used to extract `@user` user references from text
def reference_pattern
- %r{
- (?<!\w)
- #{Regexp.escape(reference_prefix)}
- (?<user>#{Gitlab::PathRegex::FULL_NAMESPACE_FORMAT_REGEX})
- }x
+ @reference_pattern ||=
+ %r{
+ (?<!\w)
+ #{Regexp.escape(reference_prefix)}
+ (?<user>#{Gitlab::PathRegex::FULL_NAMESPACE_FORMAT_REGEX})
+ }x
end
# Return (create if necessary) the ghost user. The ghost user
@@ -642,6 +644,7 @@ class User < ApplicationRecord
unique_internal(where(user_type: :alert_bot), 'alert-bot', email_pattern) do |u|
u.bio = 'The GitLab alert bot'
u.name = 'GitLab Alert Bot'
+ u.avatar = bot_avatar(image: 'alert-bot.png')
end
end
@@ -655,6 +658,16 @@ class User < ApplicationRecord
end
end
+ def support_bot
+ email_pattern = "support%s@#{Settings.gitlab.host}"
+
+ unique_internal(where(user_type: :support_bot), 'support-bot', email_pattern) do |u|
+ u.bio = 'The GitLab support bot used for Service Desk'
+ u.name = 'GitLab Support Bot'
+ u.avatar = bot_avatar(image: 'support-bot.png')
+ end
+ end
+
# Return true if there is only single non-internal user in the deployment,
# ghost user is ignored.
def single_user?
@@ -1257,17 +1270,11 @@ class User < ApplicationRecord
namespace.path = username if username_changed?
namespace.name = name if name_changed?
else
- build_namespace(path: username, name: name)
+ namespace = build_namespace(path: username, name: name)
+ namespace.build_namespace_settings
end
end
- # Temporary, will be removed when bio is fully migrated
- def ensure_bio_is_assigned_to_user_details
- return if Feature.disabled?(:migrate_bio_to_user_details, default_enabled: true)
-
- user_detail.bio = bio.to_s[0...255] # bio can be NULL in users, but cannot be NULL in user_details
- end
-
def set_username_errors
namespace_path_errors = self.errors.delete(:"namespace.path")
self.errors[:username].concat(namespace_path_errors) if namespace_path_errors
@@ -1692,6 +1699,10 @@ class User < ApplicationRecord
impersonator.present?
end
+ def created_recently?
+ created_at > Devise.confirm_within.ago
+ end
+
protected
# override, from Devise::Validatable
diff --git a/app/models/user_callout_enums.rb b/app/models/user_callout_enums.rb
index 0a3f597ae27..226c8cd9ab5 100644
--- a/app/models/user_callout_enums.rb
+++ b/app/models/user_callout_enums.rb
@@ -17,7 +17,8 @@ module UserCalloutEnums
suggest_popover_dismissed: 9,
tabs_position_highlight: 10,
webhooks_moved: 13,
- admin_integrations_moved: 15
+ admin_integrations_moved: 15,
+ personal_access_token_expiry: 21 # EE-only
}
end
end
diff --git a/app/models/user_detail.rb b/app/models/user_detail.rb
index 5dc74421705..9674f9a41da 100644
--- a/app/models/user_detail.rb
+++ b/app/models/user_detail.rb
@@ -1,7 +1,33 @@
# frozen_string_literal: true
class UserDetail < ApplicationRecord
+ extend ::Gitlab::Utils::Override
+ include CacheMarkdownField
+
belongs_to :user
validates :job_title, length: { maximum: 200 }
+ validates :bio, length: { maximum: 255 }, allow_blank: true
+
+ before_save :prevent_nil_bio
+
+ cache_markdown_field :bio
+
+ def bio_html
+ read_attribute(:bio_html) || bio
+ end
+
+ # For backward compatibility.
+ # Older migrations (and their tests) reference the `User.migration_bot` where the `bio` attribute is set.
+ # Here we disable writing the markdown cache when the `bio_html` column does not exists.
+ override :invalidated_markdown_cache?
+ def invalidated_markdown_cache?
+ self.class.column_names.include?('bio_html') && super
+ end
+
+ private
+
+ def prevent_nil_bio
+ self.bio = '' if bio_changed? && bio.nil?
+ end
end
diff --git a/app/models/webauthn_registration.rb b/app/models/webauthn_registration.rb
new file mode 100644
index 00000000000..76f8faa11c7
--- /dev/null
+++ b/app/models/webauthn_registration.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+# Registration information for WebAuthn credentials
+
+class WebauthnRegistration < ApplicationRecord
+ belongs_to :user
+
+ validates :credential_xid, :public_key, :name, :counter, presence: true
+ validates :counter,
+ numericality: { only_integer: true, greater_than_or_equal_to: 0, less_than_or_equal_to: 2**32 - 1 }
+end
diff --git a/app/models/wiki_page.rb b/app/models/wiki_page.rb
index 9e4e2f68d38..3dc90edb331 100644
--- a/app/models/wiki_page.rb
+++ b/app/models/wiki_page.rb
@@ -301,6 +301,10 @@ class WikiPage
version&.commit&.committed_date
end
+ def diffs(diff_options = {})
+ Gitlab::Diff::FileCollection::WikiPage.new(self, diff_options: diff_options)
+ end
+
private
def serialize_front_matter(hash)
diff --git a/app/policies/base_policy.rb b/app/policies/base_policy.rb
index 2c26ba565ab..13d732e4edd 100644
--- a/app/policies/base_policy.rb
+++ b/app/policies/base_policy.rb
@@ -21,6 +21,10 @@ class BasePolicy < DeclarativePolicy::Base
with_options scope: :user, score: 0
condition(:deactivated) { @user&.deactivated? }
+ desc "User is support bot"
+ with_options scope: :user, score: 0
+ condition(:support_bot) { @user&.support_bot? }
+
desc "User email is unconfirmed or user account is locked"
with_options scope: :user, score: 0
condition(:inactive) do
@@ -54,6 +58,8 @@ class BasePolicy < DeclarativePolicy::Base
rule { admin }.enable :read_all_resources
rule { default }.enable :read_cross_project
+
+ condition(:is_gitlab_com) { ::Gitlab.dev_env_or_com? }
end
BasePolicy.prepend_if_ee('EE::BasePolicy')
diff --git a/app/policies/concerns/find_group_projects.rb b/app/policies/concerns/find_group_projects.rb
index e2cb90079c7..aad9081bd7d 100644
--- a/app/policies/concerns/find_group_projects.rb
+++ b/app/policies/concerns/find_group_projects.rb
@@ -3,11 +3,11 @@
module FindGroupProjects
extend ActiveSupport::Concern
- def group_projects_for(user:, group:)
+ def group_projects_for(user:, group:, only_owned: true)
GroupProjectsFinder.new(
group: group,
current_user: user,
- options: { include_subgroups: true, only_owned: true }
+ options: { include_subgroups: true, only_owned: only_owned }
).execute
end
end
diff --git a/app/policies/concerns/policy_actor.rb b/app/policies/concerns/policy_actor.rb
index f910e04d015..3073a2e5d10 100644
--- a/app/policies/concerns/policy_actor.rb
+++ b/app/policies/concerns/policy_actor.rb
@@ -45,6 +45,10 @@ module PolicyActor
false
end
+ def support_bot?
+ false
+ end
+
def deactivated?
false
end
diff --git a/app/policies/global_policy.rb b/app/policies/global_policy.rb
index 03f5a863421..c66f0d199b0 100644
--- a/app/policies/global_policy.rb
+++ b/app/policies/global_policy.rb
@@ -105,6 +105,9 @@ class GlobalPolicy < BasePolicy
enable :update_custom_attribute
end
+ # We can't use `read_statistics` because the user may have different permissions for different projects
+ rule { admin }.enable :use_project_statistics_filters
+
rule { external_user }.prevent :create_snippet
end
diff --git a/app/policies/group_policy.rb b/app/policies/group_policy.rb
index b1b52d62b85..62f66093875 100644
--- a/app/policies/group_policy.rb
+++ b/app/policies/group_policy.rb
@@ -42,6 +42,14 @@ class GroupPolicy < BasePolicy
@subject.subgroup_creation_level == ::Gitlab::Access::MAINTAINER_SUBGROUP_ACCESS
end
+ condition(:design_management_enabled) do
+ group_projects_for(user: @user, group: @subject, only_owned: false).any? { |p| p.design_management_enabled? }
+ end
+
+ rule { design_management_enabled }.policy do
+ enable :read_design_activity
+ end
+
rule { public_group }.policy do
enable :read_group
enable :read_package
@@ -59,6 +67,10 @@ class GroupPolicy < BasePolicy
enable :update_max_artifacts_size
end
+ rule { can?(:read_all_resources) }.policy do
+ enable :read_confidential_issues
+ end
+
rule { has_projects }.policy do
enable :read_group
end
@@ -70,6 +82,10 @@ class GroupPolicy < BasePolicy
enable :read_board
end
+ rule { ~can?(:read_group) }.policy do
+ prevent :read_design_activity
+ end
+
rule { has_access }.enable :read_namespace
rule { developer }.policy do
@@ -87,6 +103,7 @@ class GroupPolicy < BasePolicy
enable :admin_list
enable :admin_issue
enable :read_metrics_dashboard_annotation
+ enable :read_prometheus
end
rule { maintainer }.policy do
diff --git a/app/policies/merge_request_policy.rb b/app/policies/merge_request_policy.rb
index e2aca2a37d5..e5ac228b0ee 100644
--- a/app/policies/merge_request_policy.rb
+++ b/app/policies/merge_request_policy.rb
@@ -10,6 +10,10 @@ class MergeRequestPolicy < IssuablePolicy
# it would not be safe to prevent :create_note there, since
# note permissions are shared, and this would apply too broadly.
rule { ~can?(:read_merge_request) }.prevent :create_note
+
+ rule { can?(:update_merge_request) }.policy do
+ enable :approve_merge_request
+ end
end
MergeRequestPolicy.prepend_if_ee('EE::MergeRequestPolicy')
diff --git a/app/policies/packages/package_policy.rb b/app/policies/packages/package_policy.rb
new file mode 100644
index 00000000000..8eef280c640
--- /dev/null
+++ b/app/policies/packages/package_policy.rb
@@ -0,0 +1,6 @@
+# frozen_string_literal: true
+module Packages
+ class PackagePolicy < BasePolicy
+ delegate { @subject.project }
+ end
+end
diff --git a/app/policies/project_member_policy.rb b/app/policies/project_member_policy.rb
index f2f18406bd3..ca33b95e523 100644
--- a/app/policies/project_member_policy.rb
+++ b/app/policies/project_member_policy.rb
@@ -5,14 +5,17 @@ class ProjectMemberPolicy < BasePolicy
condition(:target_is_owner, scope: :subject) { @subject.user == @subject.project.owner }
condition(:target_is_self) { @user && @subject.user == @user }
+ condition(:project_bot) { @subject.user&.project_bot? }
rule { anonymous }.prevent_all
rule { target_is_owner }.prevent_all
- rule { can?(:admin_project_member) }.policy do
+ rule { ~project_bot & can?(:admin_project_member) }.policy do
enable :update_project_member
enable :destroy_project_member
end
+ rule { project_bot & can?(:admin_project_member) }.enable :destroy_project_bot_member
+
rule { target_is_self }.enable :destroy_project_member
end
diff --git a/app/policies/project_policy.rb b/app/policies/project_policy.rb
index f87c72007ec..39b39bd2fce 100644
--- a/app/policies/project_policy.rb
+++ b/app/policies/project_policy.rb
@@ -123,6 +123,9 @@ class ProjectPolicy < BasePolicy
!@subject.design_management_enabled?
end
+ with_scope :subject
+ condition(:service_desk_enabled) { @subject.service_desk_enabled? }
+
# We aren't checking `:read_issue` or `:read_merge_request` in this case
# because it could be possible for a user to see an issuable-iid
# (`:read_issue_iid` or `:read_merge_request_iid`) but then wouldn't be
@@ -151,6 +154,9 @@ class ProjectPolicy < BasePolicy
::Feature.enabled?(:build_service_proxy, @subject)
end
+ with_scope :subject
+ condition(:packages_disabled) { !@subject.packages_enabled }
+
features = %w[
merge_requests
issues
@@ -173,6 +179,7 @@ class ProjectPolicy < BasePolicy
rule { guest | admin }.enable :read_project_for_iids
rule { admin }.enable :update_max_artifacts_size
+ rule { can?(:read_all_resources) }.enable :read_confidential_issues
rule { guest }.enable :guest_access
rule { reporter }.enable :reporter_access
@@ -254,6 +261,8 @@ class ProjectPolicy < BasePolicy
enable :read_prometheus
enable :read_metrics_dashboard_annotation
enable :metrics_dashboard
+ enable :read_confidential_issues
+ enable :read_package
end
# We define `:public_user_access` separately because there are cases in gitlab-ee
@@ -290,12 +299,17 @@ class ProjectPolicy < BasePolicy
enable :read_metrics_user_starred_dashboard
end
+ rule { packages_disabled | repository_disabled }.policy do
+ prevent(*create_read_update_admin_destroy(:package))
+ end
+
rule { owner | admin | guest | group_member }.prevent :request_access
rule { ~request_access_enabled }.prevent :request_access
rule { can?(:developer_access) & can?(:create_issue) }.enable :import_issues
rule { can?(:developer_access) }.policy do
+ enable :create_package
enable :admin_board
enable :admin_merge_request
enable :admin_milestone
@@ -327,6 +341,7 @@ class ProjectPolicy < BasePolicy
enable :update_alert_management_alert
enable :create_design
enable :destroy_design
+ enable :read_terraform_state
end
rule { can?(:developer_access) & user_confirmed? }.policy do
@@ -336,6 +351,7 @@ class ProjectPolicy < BasePolicy
end
rule { can?(:maintainer_access) }.policy do
+ enable :destroy_package
enable :admin_board
enable :push_to_delete_protected_branch
enable :update_snippet
@@ -470,6 +486,7 @@ class ProjectPolicy < BasePolicy
end
rule { can?(:public_access) }.policy do
+ enable :read_package
enable :read_project
enable :read_board
enable :read_list
@@ -545,11 +562,13 @@ class ProjectPolicy < BasePolicy
rule { can?(:read_issue) }.policy do
enable :read_design
+ enable :read_design_activity
end
# Design abilities could also be prevented in the issue policy.
rule { design_management_disabled }.policy do
prevent :read_design
+ prevent :read_design_activity
prevent :create_design
prevent :destroy_design
end
@@ -576,6 +595,12 @@ class ProjectPolicy < BasePolicy
enable :read_build_report_results
end
+ rule { support_bot }.enable :guest_access
+ rule { support_bot & ~service_desk_enabled }.policy do
+ prevent :create_note
+ prevent :read_project
+ end
+
private
def team_member?
@@ -624,6 +649,7 @@ class ProjectPolicy < BasePolicy
def lookup_access_level!
return ::Gitlab::Access::REPORTER if alert_bot?
+ return ::Gitlab::Access::REPORTER if support_bot? && service_desk_enabled?
# NOTE: max_member_access has its own cache
project.team.max_member_access(@user.id)
@@ -636,7 +662,7 @@ class ProjectPolicy < BasePolicy
when ProjectFeature::DISABLED
false
when ProjectFeature::PRIVATE
- admin? || team_access_level >= ProjectFeature.required_minimum_access_level(feature)
+ can?(:read_all_resources) || team_access_level >= ProjectFeature.required_minimum_access_level(feature)
else
true
end
diff --git a/app/policies/releases/source_policy.rb b/app/policies/releases/source_policy.rb
index 8b86b925589..3b11c661237 100644
--- a/app/policies/releases/source_policy.rb
+++ b/app/policies/releases/source_policy.rb
@@ -3,11 +3,5 @@
module Releases
class SourcePolicy < BasePolicy
delegate { @subject.project }
-
- rule { can?(:public_access) | can?(:reporter_access) }.policy do
- enable :read_release_sources
- end
-
- rule { ~can?(:read_release) }.prevent :read_release_sources
end
end
diff --git a/app/presenters/alert_management/alert_presenter.rb b/app/presenters/alert_management/alert_presenter.rb
new file mode 100644
index 00000000000..a515c70152d
--- /dev/null
+++ b/app/presenters/alert_management/alert_presenter.rb
@@ -0,0 +1,101 @@
+# frozen_string_literal: true
+
+module AlertManagement
+ class AlertPresenter < Gitlab::View::Presenter::Delegated
+ include Gitlab::Utils::StrongMemoize
+ include IncidentManagement::Settings
+
+ MARKDOWN_LINE_BREAK = " \n".freeze
+
+ def initialize(alert, _attributes = {})
+ super
+
+ @alert = alert
+ @project = alert.project
+ end
+
+ def issue_description
+ horizontal_line = "\n\n---\n\n"
+
+ [
+ issue_summary_markdown,
+ alert_markdown,
+ incident_management_setting.issue_template_content
+ ].compact.join(horizontal_line)
+ end
+
+ def start_time
+ started_at&.strftime('%d %B %Y, %-l:%M%p (%Z)')
+ end
+
+ def issue_summary_markdown
+ <<~MARKDOWN.chomp
+ #### Summary
+
+ #{metadata_list}
+ #{alert_details}#{metric_embed_for_alert}
+ MARKDOWN
+ end
+
+ def metrics_dashboard_url; end
+
+ private
+
+ attr_reader :alert, :project
+
+ def alerting_alert
+ strong_memoize(:alerting_alert) do
+ Gitlab::Alerting::Alert.new(project: project, payload: alert.payload).present
+ end
+ end
+
+ def alert_markdown; end
+
+ def metadata_list
+ metadata = []
+
+ metadata << list_item('Start time', start_time)
+ metadata << list_item('Severity', severity)
+ metadata << list_item('full_query', backtick(full_query)) if full_query
+ metadata << list_item('Service', service) if service
+ metadata << list_item('Monitoring tool', monitoring_tool) if monitoring_tool
+ metadata << list_item('Hosts', host_links) if hosts.any?
+ metadata << list_item('Description', description) if description.present?
+
+ metadata.join(MARKDOWN_LINE_BREAK)
+ end
+
+ def alert_details
+ if details.present?
+ <<~MARKDOWN.chomp
+
+ #### Alert Details
+
+ #{details_list}
+ MARKDOWN
+ end
+ end
+
+ def details_list
+ alert.details
+ .map { |label, value| list_item(label, value) }
+ .join(MARKDOWN_LINE_BREAK)
+ end
+
+ def metric_embed_for_alert; end
+
+ def full_query; end
+
+ def list_item(key, value)
+ "**#{key}:** #{value}".strip
+ end
+
+ def backtick(value)
+ "`#{value}`"
+ end
+
+ def host_links
+ hosts.join(' ')
+ end
+ end
+end
diff --git a/app/presenters/alert_management/prometheus_alert_presenter.rb b/app/presenters/alert_management/prometheus_alert_presenter.rb
new file mode 100644
index 00000000000..3bcc98e6784
--- /dev/null
+++ b/app/presenters/alert_management/prometheus_alert_presenter.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+module AlertManagement
+ class PrometheusAlertPresenter < AlertManagement::AlertPresenter
+ def metrics_dashboard_url
+ alerting_alert.metrics_dashboard_url
+ end
+
+ private
+
+ def alert_markdown
+ alerting_alert.alert_markdown
+ end
+
+ def details_list
+ alerting_alert.annotation_list
+ end
+
+ def metric_embed_for_alert
+ alerting_alert.metric_embed_for_alert
+ end
+
+ def full_query
+ alerting_alert.full_query
+ end
+ end
+end
diff --git a/app/presenters/ci/pipeline_presenter.rb b/app/presenters/ci/pipeline_presenter.rb
index 395eaeea8de..da610f13899 100644
--- a/app/presenters/ci/pipeline_presenter.rb
+++ b/app/presenters/ci/pipeline_presenter.rb
@@ -110,6 +110,17 @@ module Ci
merge_request_presenter&.target_branch_link
end
+ def downloadable_path_for_report_type(file_type)
+ if (job_artifact = batch_lookup_report_artifact_for_file_type(file_type)) &&
+ can?(current_user, :read_build, job_artifact.job)
+ download_project_job_artifacts_path(
+ job_artifact.project,
+ job_artifact.job,
+ file_type: file_type,
+ proxy: true)
+ end
+ end
+
private
def plain_ref_name
diff --git a/app/presenters/clusterable_presenter.rb b/app/presenters/clusterable_presenter.rb
index 5e669ff2e50..efb3cf7f348 100644
--- a/app/presenters/clusterable_presenter.rb
+++ b/app/presenters/clusterable_presenter.rb
@@ -13,8 +13,7 @@ class ClusterablePresenter < Gitlab::View::Presenter::Delegated
end
def can_add_cluster?
- can?(current_user, :add_cluster, clusterable) &&
- (has_no_clusters? || multiple_clusters_available?)
+ can?(current_user, :add_cluster, clusterable)
end
def can_create_cluster?
@@ -65,7 +64,11 @@ class ClusterablePresenter < Gitlab::View::Presenter::Delegated
raise NotImplementedError
end
- # Will be overidden in EE
+ def metrics_dashboard_path(cluster)
+ raise NotImplementedError
+ end
+
+ # Will be overridden in EE
def environments_cluster_path(cluster)
nil
end
@@ -81,17 +84,6 @@ class ClusterablePresenter < Gitlab::View::Presenter::Delegated
def learn_more_link
raise NotImplementedError
end
-
- private
-
- # Overridden on EE module
- def multiple_clusters_available?
- false
- end
-
- def has_no_clusters?
- clusterable.clusters.empty?
- end
end
ClusterablePresenter.prepend_if_ee('EE::ClusterablePresenter')
diff --git a/app/presenters/clusters/cluster_presenter.rb b/app/presenters/clusters/cluster_presenter.rb
index c4e3393cac9..c0da5310ca4 100644
--- a/app/presenters/clusters/cluster_presenter.rb
+++ b/app/presenters/clusters/cluster_presenter.rb
@@ -2,6 +2,7 @@
module Clusters
class ClusterPresenter < Gitlab::View::Presenter::Delegated
+ include ::Gitlab::Utils::StrongMemoize
include ActionView::Helpers::SanitizeHelper
include ActionView::Helpers::UrlHelper
include IconsHelper
@@ -60,12 +61,53 @@ module Clusters
end
end
+ def gitlab_managed_apps_logs_path
+ return unless logs_project && can_read_cluster?
+
+ if cluster.application_elastic_stack&.available?
+ elasticsearch_project_logs_path(logs_project, cluster_id: cluster.id, format: :json)
+ else
+ k8s_project_logs_path(logs_project, cluster_id: cluster.id, format: :json)
+ end
+ end
+
def read_only_kubernetes_platform_fields?
!cluster.provided_by_user?
end
+ def health_data(clusterable)
+ {
+ 'clusters-path': clusterable.index_path,
+ 'dashboard-endpoint': clusterable.metrics_dashboard_path(cluster),
+ 'documentation-path': help_page_path('user/project/clusters/index', anchor: 'monitoring-your-kubernetes-cluster-ultimate'),
+ 'add-dashboard-documentation-path': help_page_path('user/project/integrations/prometheus.md', anchor: 'adding-a-new-dashboard-to-your-project'),
+ 'empty-getting-started-svg-path': image_path('illustrations/monitoring/getting_started.svg'),
+ 'empty-loading-svg-path': image_path('illustrations/monitoring/loading.svg'),
+ 'empty-no-data-svg-path': image_path('illustrations/monitoring/no_data.svg'),
+ 'empty-no-data-small-svg-path': image_path('illustrations/chart-empty-state-small.svg'),
+ 'empty-unable-to-connect-svg-path': image_path('illustrations/monitoring/unable_to_connect.svg'),
+ 'settings-path': '',
+ 'project-path': '',
+ 'tags-path': ''
+ }
+ end
+
private
+ def image_path(path)
+ ActionController::Base.helpers.image_path(path)
+ end
+
+ # currently log explorer is only available in the scope of the project
+ # for group and instance level cluster selected project does not affects
+ # fetching logs from gitlab managed apps namespace, therefore any project
+ # available to user will be sufficient.
+ def logs_project
+ strong_memoize(:logs_project) do
+ cluster.all_projects.first
+ end
+ end
+
def clusterable
if cluster.group_type?
cluster.group
diff --git a/app/presenters/group_clusterable_presenter.rb b/app/presenters/group_clusterable_presenter.rb
index 21db2f6f96b..dfe8e315f94 100644
--- a/app/presenters/group_clusterable_presenter.rb
+++ b/app/presenters/group_clusterable_presenter.rb
@@ -43,6 +43,10 @@ class GroupClusterablePresenter < ClusterablePresenter
def learn_more_link
link_to(s_('ClusterIntegration|Learn more about group Kubernetes clusters'), help_page_path('user/group/clusters/index'), target: '_blank', rel: 'noopener noreferrer')
end
+
+ def metrics_dashboard_path(cluster)
+ metrics_dashboard_group_cluster_path(clusterable, cluster)
+ end
end
GroupClusterablePresenter.prepend_if_ee('EE::GroupClusterablePresenter')
diff --git a/app/presenters/instance_clusterable_presenter.rb b/app/presenters/instance_clusterable_presenter.rb
index 41071bc7bc7..7704e6b59c1 100644
--- a/app/presenters/instance_clusterable_presenter.rb
+++ b/app/presenters/instance_clusterable_presenter.rb
@@ -81,6 +81,10 @@ class InstanceClusterablePresenter < ClusterablePresenter
def learn_more_link
link_to(s_('ClusterIntegration|Learn more about instance Kubernetes clusters'), help_page_path('user/instance/clusters/index'), target: '_blank', rel: 'noopener noreferrer')
end
+
+ def metrics_dashboard_path(cluster)
+ metrics_dashboard_admin_cluster_path(cluster)
+ end
end
InstanceClusterablePresenter.prepend_if_ee('EE::InstanceClusterablePresenter')
diff --git a/app/presenters/merge_request_presenter.rb b/app/presenters/merge_request_presenter.rb
index af98a6ee36a..bccf0340749 100644
--- a/app/presenters/merge_request_presenter.rb
+++ b/app/presenters/merge_request_presenter.rb
@@ -8,6 +8,8 @@ class MergeRequestPresenter < Gitlab::View::Presenter::Delegated
include ChecksCollaboration
include Gitlab::Utils::StrongMemoize
+ APPROVALS_WIDGET_BASE_TYPE = 'base'
+
presents :merge_request
def ci_status
@@ -224,6 +226,22 @@ class MergeRequestPresenter < Gitlab::View::Presenter::Delegated
end
end
+ def api_approvals_path
+ expose_path(api_v4_projects_merge_requests_approvals_path(id: project.id, merge_request_iid: merge_request.iid))
+ end
+
+ def api_approve_path
+ expose_path(api_v4_projects_merge_requests_approve_path(id: project.id, merge_request_iid: merge_request.iid))
+ end
+
+ def api_unapprove_path
+ expose_path(api_v4_projects_merge_requests_unapprove_path(id: project.id, merge_request_iid: merge_request.iid))
+ end
+
+ def approvals_widget_type
+ APPROVALS_WIDGET_BASE_TYPE
+ end
+
private
def cached_can_be_reverted?
diff --git a/app/presenters/packages/composer/packages_presenter.rb b/app/presenters/packages/composer/packages_presenter.rb
new file mode 100644
index 00000000000..84f266989e9
--- /dev/null
+++ b/app/presenters/packages/composer/packages_presenter.rb
@@ -0,0 +1,71 @@
+# frozen_string_literal: true
+
+module Packages
+ module Composer
+ class PackagesPresenter
+ include API::Helpers::RelatedResourcesHelpers
+
+ def initialize(group, packages)
+ @group = group
+ @packages = packages
+ end
+
+ def root
+ path = api_v4_group___packages_composer_package_name_path({ id: @group.id, package_name: '%package%', format: '.json' }, true)
+ { 'packages' => [], 'provider-includes' => { 'p/%hash%.json' => { 'sha256' => provider_sha } }, 'providers-url' => path }
+ end
+
+ def provider
+ { 'providers' => providers_map }
+ end
+
+ def package_versions(packages = @packages)
+ { 'packages' => { packages.first.name => package_versions_map(packages) } }
+ end
+
+ private
+
+ def package_versions_map(packages)
+ packages.each_with_object({}) do |package, map|
+ map[package.version] = package_metadata(package)
+ end
+ end
+
+ def package_metadata(package)
+ json = package.composer_metadatum.composer_json
+
+ json.merge('dist' => package_dist(package), 'uid' => package.id, 'version' => package.version)
+ end
+
+ def package_dist(package)
+ sha = package.composer_metadatum.target_sha
+ archive_api_path = api_v4_projects_packages_composer_archives_package_name_path({ id: package.project_id, package_name: package.name, format: '.zip' }, true)
+
+ {
+ 'type' => 'zip',
+ 'url' => expose_url(archive_api_path) + "?sha=#{sha}",
+ 'reference' => sha,
+ 'shasum' => ''
+ }
+ end
+
+ def providers_map
+ map = {}
+
+ @packages.group_by(&:name).each_pair do |package_name, packages|
+ map[package_name] = { 'sha256' => package_versions_sha(packages) }
+ end
+
+ map
+ end
+
+ def package_versions_sha(packages)
+ Digest::SHA256.hexdigest(package_versions(packages).to_json)
+ end
+
+ def provider_sha
+ Digest::SHA256.hexdigest(provider.to_json)
+ end
+ end
+ end
+end
diff --git a/app/presenters/packages/conan/package_presenter.rb b/app/presenters/packages/conan/package_presenter.rb
new file mode 100644
index 00000000000..5141c450412
--- /dev/null
+++ b/app/presenters/packages/conan/package_presenter.rb
@@ -0,0 +1,114 @@
+# frozen_string_literal: true
+
+module Packages
+ module Conan
+ class PackagePresenter
+ include API::Helpers::RelatedResourcesHelpers
+ include Gitlab::Utils::StrongMemoize
+
+ attr_reader :params
+
+ def initialize(recipe, user, project, params = {})
+ @recipe = recipe
+ @user = user
+ @project = project
+ @params = params
+ end
+
+ def recipe_urls
+ map_package_files do |package_file|
+ build_recipe_file_url(package_file) if package_file.conan_file_metadatum.recipe_file?
+ end
+ end
+
+ def recipe_snapshot
+ map_package_files do |package_file|
+ package_file.file_md5 if package_file.conan_file_metadatum.recipe_file?
+ end
+ end
+
+ def package_urls
+ map_package_files do |package_file|
+ next unless package_file.conan_file_metadatum.package_file? && matching_reference?(package_file)
+
+ build_package_file_url(package_file)
+ end
+ end
+
+ def package_snapshot
+ map_package_files do |package_file|
+ next unless package_file.conan_file_metadatum.package_file? && matching_reference?(package_file)
+
+ package_file.file_md5
+ end
+ end
+
+ private
+
+ def build_recipe_file_url(package_file)
+ expose_url(
+ api_v4_packages_conan_v1_files_export_path(
+ package_name: package.name,
+ package_version: package.version,
+ package_username: package.conan_metadatum.package_username,
+ package_channel: package.conan_metadatum.package_channel,
+ recipe_revision: package_file.conan_file_metadatum.recipe_revision,
+ file_name: package_file.file_name
+ )
+ )
+ end
+
+ def build_package_file_url(package_file)
+ expose_url(
+ api_v4_packages_conan_v1_files_package_path(
+ package_name: package.name,
+ package_version: package.version,
+ package_username: package.conan_metadatum.package_username,
+ package_channel: package.conan_metadatum.package_channel,
+ recipe_revision: package_file.conan_file_metadatum.recipe_revision,
+ conan_package_reference: package_file.conan_file_metadatum.conan_package_reference,
+ package_revision: package_file.conan_file_metadatum.package_revision,
+ file_name: package_file.file_name
+ )
+ )
+ end
+
+ def map_package_files
+ package_files.to_a.map do |package_file|
+ key = package_file.file_name
+ value = yield(package_file)
+ next unless key && value
+
+ [key, value]
+ end.compact.to_h
+ end
+
+ def package_files
+ return unless package
+
+ @package_files ||= package.package_files.preload_conan_file_metadata
+ end
+
+ def package
+ strong_memoize(:package) do
+ name, version = @recipe.split('@')[0].split('/')
+
+ @project.packages
+ .conan
+ .with_name(name)
+ .with_version(version)
+ .order_created
+ .last
+ end
+ end
+
+ def matching_reference?(package_file)
+ package_file.conan_file_metadatum.conan_package_reference == conan_package_reference
+ end
+
+ def conan_package_reference
+ params[:conan_package_reference]
+ end
+ end
+ end
+end
diff --git a/app/presenters/packages/detail/package_presenter.rb b/app/presenters/packages/detail/package_presenter.rb
new file mode 100644
index 00000000000..f6e068302c1
--- /dev/null
+++ b/app/presenters/packages/detail/package_presenter.rb
@@ -0,0 +1,75 @@
+# frozen_string_literal: true
+
+module Packages
+ module Detail
+ class PackagePresenter
+ def initialize(package)
+ @package = package
+ end
+
+ def detail_view
+ package_detail = {
+ id: @package.id,
+ created_at: @package.created_at,
+ name: @package.name,
+ package_files: @package.package_files.map { |pf| build_package_file_view(pf) },
+ package_type: @package.package_type,
+ project_id: @package.project_id,
+ tags: @package.tags.as_json,
+ updated_at: @package.updated_at,
+ version: @package.version
+ }
+
+ package_detail[:maven_metadatum] = @package.maven_metadatum if @package.maven_metadatum
+ package_detail[:nuget_metadatum] = @package.nuget_metadatum if @package.nuget_metadatum
+ package_detail[:dependency_links] = @package.dependency_links.map(&method(:build_dependency_links))
+ package_detail[:pipeline] = build_pipeline_info(@package.build_info.pipeline) if @package.build_info
+
+ package_detail
+ end
+
+ private
+
+ def build_package_file_view(package_file)
+ {
+ created_at: package_file.created_at,
+ download_path: package_file.download_path,
+ file_name: package_file.file_name,
+ size: package_file.size
+ }
+ end
+
+ def build_pipeline_info(pipeline_info)
+ {
+ created_at: pipeline_info.created_at,
+ id: pipeline_info.id,
+ sha: pipeline_info.sha,
+ ref: pipeline_info.ref,
+ git_commit_message: pipeline_info.git_commit_message,
+ user: build_user_info(pipeline_info.user),
+ project: {
+ name: pipeline_info.project.name,
+ web_url: pipeline_info.project.web_url
+ }
+ }
+ end
+
+ def build_user_info(user)
+ return unless user
+
+ {
+ avatar_url: user.avatar_url,
+ name: user.name
+ }
+ end
+
+ def build_dependency_links(link)
+ {
+ name: link.dependency.name,
+ version_pattern: link.dependency.version_pattern,
+ target_framework: link.nuget_metadatum&.target_framework
+ }.compact
+ end
+ end
+ end
+end
diff --git a/app/presenters/packages/go/module_version_presenter.rb b/app/presenters/packages/go/module_version_presenter.rb
new file mode 100644
index 00000000000..4c86eae46cd
--- /dev/null
+++ b/app/presenters/packages/go/module_version_presenter.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+module Packages
+ module Go
+ class ModuleVersionPresenter
+ def initialize(version)
+ @version = version
+ end
+
+ def name
+ @version.name
+ end
+
+ def time
+ @version.commit.committed_date
+ end
+ end
+ end
+end
diff --git a/app/presenters/packages/npm/package_presenter.rb b/app/presenters/packages/npm/package_presenter.rb
new file mode 100644
index 00000000000..a3ab10d3913
--- /dev/null
+++ b/app/presenters/packages/npm/package_presenter.rb
@@ -0,0 +1,87 @@
+# frozen_string_literal: true
+
+module Packages
+ module Npm
+ class PackagePresenter
+ include API::Helpers::RelatedResourcesHelpers
+
+ attr_reader :name, :packages
+
+ NPM_VALID_DEPENDENCY_TYPES = %i[dependencies devDependencies bundleDependencies peerDependencies].freeze
+
+ def initialize(name, packages)
+ @name = name
+ @packages = packages
+ end
+
+ def versions
+ package_versions = {}
+
+ packages.each do |package|
+ package_file = package.package_files.last
+
+ next unless package_file
+
+ package_versions[package.version] = build_package_version(package, package_file)
+ end
+
+ package_versions
+ end
+
+ def dist_tags
+ build_package_tags.tap { |t| t["latest"] ||= sorted_versions.last }
+ end
+
+ private
+
+ def build_package_tags
+ Hash[
+ package_tags.map { |tag| [tag.name, tag.package.version] }
+ ]
+ end
+
+ def build_package_version(package, package_file)
+ {
+ name: package.name,
+ version: package.version,
+ dist: {
+ shasum: package_file.file_sha1,
+ tarball: tarball_url(package, package_file)
+ }
+ }.tap do |package_version|
+ package_version.merge!(build_package_dependencies(package))
+ end
+ end
+
+ def tarball_url(package, package_file)
+ expose_url "#{api_v4_projects_path(id: package.project_id)}" \
+ "/packages/npm/#{package.name}" \
+ "/-/#{package_file.file_name}"
+ end
+
+ def build_package_dependencies(package)
+ dependencies = Hash.new { |h, key| h[key] = {} }
+ dependency_links = package.dependency_links
+ .with_dependency_type(NPM_VALID_DEPENDENCY_TYPES)
+ .includes_dependency
+
+ dependency_links.find_each do |dependency_link|
+ dependency = dependency_link.dependency
+ dependencies[dependency_link.dependency_type][dependency.name] = dependency.version_pattern
+ end
+
+ dependencies
+ end
+
+ def sorted_versions
+ versions = packages.map(&:version).compact
+ VersionSorter.sort(versions)
+ end
+
+ def package_tags
+ Packages::Tag.for_packages(packages)
+ .preload_package
+ end
+ end
+ end
+end
diff --git a/app/presenters/packages/nuget/package_metadata_presenter.rb b/app/presenters/packages/nuget/package_metadata_presenter.rb
new file mode 100644
index 00000000000..500fc982e11
--- /dev/null
+++ b/app/presenters/packages/nuget/package_metadata_presenter.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+module Packages
+ module Nuget
+ class PackageMetadataPresenter
+ include Packages::Nuget::PresenterHelpers
+
+ def initialize(package)
+ @package = package
+ end
+
+ def json_url
+ json_url_for(@package)
+ end
+
+ def archive_url
+ archive_url_for(@package)
+ end
+
+ def catalog_entry
+ catalog_entry_for(@package)
+ end
+ end
+ end
+end
diff --git a/app/presenters/packages/nuget/packages_metadata_presenter.rb b/app/presenters/packages/nuget/packages_metadata_presenter.rb
new file mode 100644
index 00000000000..5f22d5dd8a1
--- /dev/null
+++ b/app/presenters/packages/nuget/packages_metadata_presenter.rb
@@ -0,0 +1,63 @@
+# frozen_string_literal: true
+
+module Packages
+ module Nuget
+ class PackagesMetadataPresenter
+ include Packages::Nuget::PresenterHelpers
+ include Gitlab::Utils::StrongMemoize
+
+ COUNT = 1.freeze
+
+ def initialize(packages)
+ @packages = packages
+ end
+
+ def count
+ COUNT
+ end
+
+ def items
+ [summary]
+ end
+
+ private
+
+ def summary
+ {
+ json_url: json_url,
+ lower_version: lower_version,
+ upper_version: upper_version,
+ packages_count: @packages.count,
+ packages: @packages.map { |pkg| metadata_for(pkg) }
+ }
+ end
+
+ def metadata_for(package)
+ {
+ json_url: json_url_for(package),
+ archive_url: archive_url_for(package),
+ catalog_entry: catalog_entry_for(package)
+ }
+ end
+
+ def json_url
+ json_url_for(@packages.first)
+ end
+
+ def lower_version
+ sorted_versions.first
+ end
+
+ def upper_version
+ sorted_versions.last
+ end
+
+ def sorted_versions
+ strong_memoize(:sorted_versions) do
+ versions = @packages.map(&:version).compact
+ VersionSorter.sort(versions)
+ end
+ end
+ end
+ end
+end
diff --git a/app/presenters/packages/nuget/packages_versions_presenter.rb b/app/presenters/packages/nuget/packages_versions_presenter.rb
new file mode 100644
index 00000000000..7f4ce4dbb2f
--- /dev/null
+++ b/app/presenters/packages/nuget/packages_versions_presenter.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+module Packages
+ module Nuget
+ class PackagesVersionsPresenter
+ def initialize(packages)
+ @packages = packages
+ end
+
+ def versions
+ @packages.pluck_versions.sort
+ end
+ end
+ end
+end
diff --git a/app/presenters/packages/nuget/presenter_helpers.rb b/app/presenters/packages/nuget/presenter_helpers.rb
new file mode 100644
index 00000000000..cc7e8619220
--- /dev/null
+++ b/app/presenters/packages/nuget/presenter_helpers.rb
@@ -0,0 +1,113 @@
+# frozen_string_literal: true
+
+module Packages
+ module Nuget
+ module PresenterHelpers
+ include ::API::Helpers::RelatedResourcesHelpers
+
+ BLANK_STRING = ''
+ PACKAGE_DEPENDENCY_GROUP = 'PackageDependencyGroup'
+ PACKAGE_DEPENDENCY = 'PackageDependency'
+
+ private
+
+ def json_url_for(package)
+ path = api_v4_projects_packages_nuget_metadata_package_name_package_version_path(
+ {
+ id: package.project_id,
+ package_name: package.name,
+ package_version: package.version,
+ format: '.json'
+ },
+ true
+ )
+
+ expose_url(path)
+ end
+
+ def archive_url_for(package)
+ path = api_v4_projects_packages_nuget_download_package_name_package_version_package_filename_path(
+ {
+ id: package.project_id,
+ package_name: package.name,
+ package_version: package.version,
+ package_filename: package.package_files.last&.file_name
+ },
+ true
+ )
+
+ expose_url(path)
+ end
+
+ def catalog_entry_for(package)
+ {
+ json_url: json_url_for(package),
+ authors: BLANK_STRING,
+ dependency_groups: dependency_groups_for(package),
+ package_name: package.name,
+ package_version: package.version,
+ archive_url: archive_url_for(package),
+ summary: BLANK_STRING,
+ tags: tags_for(package),
+ metadatum: metadatum_for(package)
+ }
+ end
+
+ def dependency_groups_for(package)
+ base_nuget_id = "#{json_url_for(package)}#dependencyGroup"
+
+ dependency_links_grouped_by_target_framework(package).map do |target_framework, dependency_links|
+ nuget_id = target_framework_nuget_id(base_nuget_id, target_framework)
+ {
+ id: nuget_id,
+ type: PACKAGE_DEPENDENCY_GROUP,
+ target_framework: target_framework,
+ dependencies: dependencies_for(nuget_id, dependency_links)
+ }.compact
+ end
+ end
+
+ def dependency_links_grouped_by_target_framework(package)
+ package
+ .dependency_links
+ .includes_dependency
+ .preload_nuget_metadatum
+ .group_by { |dependency_link| dependency_link.nuget_metadatum&.target_framework }
+ end
+
+ def dependencies_for(nuget_id, dependency_links)
+ return [] if dependency_links.empty?
+
+ dependency_links.map do |dependency_link|
+ dependency = dependency_link.dependency
+ {
+ id: "#{nuget_id}/#{dependency.name.downcase}",
+ type: PACKAGE_DEPENDENCY,
+ name: dependency.name,
+ range: dependency.version_pattern
+ }
+ end
+ end
+
+ def target_framework_nuget_id(base_nuget_id, target_framework)
+ target_framework.blank? ? base_nuget_id : "#{base_nuget_id}/#{target_framework.downcase}"
+ end
+
+ def metadatum_for(package)
+ metadatum = package.nuget_metadatum
+ return {} unless metadatum
+
+ metadatum.slice(:project_url, :license_url, :icon_url)
+ .compact
+ end
+
+ def base_path_for(package)
+ api_v4_projects_packages_nuget_path(id: package.project_id)
+ end
+
+ def tags_for(package)
+ package.tag_names.join(::Packages::Tag::NUGET_TAGS_SEPARATOR)
+ end
+ end
+ end
+end
diff --git a/app/presenters/packages/nuget/search_results_presenter.rb b/app/presenters/packages/nuget/search_results_presenter.rb
new file mode 100644
index 00000000000..96c8fe7dd2a
--- /dev/null
+++ b/app/presenters/packages/nuget/search_results_presenter.rb
@@ -0,0 +1,56 @@
+# frozen_string_literal: true
+
+module Packages
+ module Nuget
+ class SearchResultsPresenter
+ include Packages::Nuget::PresenterHelpers
+ include Gitlab::Utils::StrongMemoize
+
+ delegate :total_count, to: :@search
+
+ def initialize(search)
+ @search = search
+ @package_versions = {}
+ end
+
+ def data
+ strong_memoize(:data) do
+ @search.results.group_by(&:name).map do |package_name, packages|
+ latest_version = latest_version(packages)
+ latest_package = packages.find { |pkg| pkg.version == latest_version }
+
+ {
+ type: 'Package',
+ authors: '',
+ name: package_name,
+ version: latest_version,
+ versions: build_package_versions(packages),
+ summary: '',
+ total_downloads: 0,
+ verified: true,
+ tags: tags_for(latest_package),
+ metadatum: metadatum_for(latest_package)
+ }
+ end
+ end
+ end
+
+ private
+
+ def build_package_versions(packages)
+ packages.map do |pkg|
+ {
+ json_url: json_url_for(pkg),
+ downloads: 0,
+ version: pkg.version
+ }
+ end
+ end
+
+ def latest_version(packages)
+ versions = packages.map(&:version).compact
+ VersionSorter.sort(versions).last # rubocop: disable Style/UnneededSort
+ end
+ end
+ end
+end
diff --git a/app/presenters/packages/nuget/service_index_presenter.rb b/app/presenters/packages/nuget/service_index_presenter.rb
new file mode 100644
index 00000000000..ed00b36b362
--- /dev/null
+++ b/app/presenters/packages/nuget/service_index_presenter.rb
@@ -0,0 +1,85 @@
+# frozen_string_literal: true
+
+module Packages
+ module Nuget
+ class ServiceIndexPresenter
+ include API::Helpers::RelatedResourcesHelpers
+
+ SERVICE_VERSIONS = {
+ download: %w[PackageBaseAddress/3.0.0],
+ search: %w[SearchQueryService SearchQueryService/3.0.0-beta SearchQueryService/3.0.0-rc],
+ publish: %w[PackagePublish/2.0.0],
+ metadata: %w[RegistrationsBaseUrl RegistrationsBaseUrl/3.0.0-beta RegistrationsBaseUrl/3.0.0-rc]
+ }.freeze
+
+ SERVICE_COMMENTS = {
+ download: 'Get package content (.nupkg).',
+ search: 'Filter and search for packages by keyword.',
+ publish: 'Push and delete (or unlist) packages.',
+ metadata: 'Get package metadata.'
+ }.freeze
+
+ VERSION = '3.0.0'.freeze
+
+ def initialize(project)
+ @project = project
+ end
+
+ def version
+ VERSION
+ end
+
+ def resources
+ [
+ build_service(:download),
+ build_service(:search),
+ build_service(:publish),
+ build_service(:metadata)
+ ].flatten
+ end
+
+ private
+
+ def build_service(service_type)
+ url = build_service_url(service_type)
+ comment = SERVICE_COMMENTS[service_type]
+
+ SERVICE_VERSIONS[service_type].map do |version|
+ { :@id => url, :@type => version, :comment => comment }
+ end
+ end
+
+ def build_service_url(service_type)
+ base_path = api_v4_projects_packages_nuget_path(id: @project.id)
+
+ full_path = case service_type
+ when :download
+ api_v4_projects_packages_nuget_download_package_name_package_version_package_filename_path(
+ {
+ id: @project.id,
+ package_name: nil,
+ package_version: nil,
+ package_filename: nil
+ },
+ true
+ )
+ when :search
+ "#{base_path}/query"
+ when :metadata
+ api_v4_projects_packages_nuget_metadata_package_name_package_version_path(
+ {
+ id: @project.id,
+ package_name: nil,
+ package_version: nil
+ },
+ true
+ )
+ when :publish
+ base_path
+ end
+
+ expose_url(full_path)
+ end
+ end
+ end
+end
diff --git a/app/presenters/packages/pypi/package_presenter.rb b/app/presenters/packages/pypi/package_presenter.rb
new file mode 100644
index 00000000000..4192e974645
--- /dev/null
+++ b/app/presenters/packages/pypi/package_presenter.rb
@@ -0,0 +1,75 @@
+# frozen_string_literal: true
+
+# Display package version data acording to PyPi
+# Simple API: https://warehouse.pypa.io/api-reference/legacy/#simple-project-api
+module Packages
+ module Pypi
+ class PackagePresenter
+ include API::Helpers::RelatedResourcesHelpers
+
+ def initialize(packages, project)
+ @packages = packages
+ @project = project
+ end
+
+ # Returns the HTML body for PyPi simple API.
+ # Basically a list of package download links for a specific
+ # package
+ def body
+ <<-HTML
+ <!DOCTYPE html>
+ <html>
+ <head>
+ <title>Links for #{escape(name)}</title>
+ </head>
+ <body>
+ <h1>Links for #{escape(name)}</h1>
+ #{links}
+ </body>
+ </html>
+ HTML
+ end
+
+ private
+
+ def links
+ refs = []
+
+ @packages.map do |package|
+ package.package_files.each do |file|
+ url = build_pypi_package_path(file)
+
+ refs << package_link(url, package.pypi_metadatum.required_python, file.file_name)
+ end
+ end
+
+ refs.join
+ end
+
+ def package_link(url, required_python, filename)
+ "<a href=\"#{url}\" data-requires-python=\"#{escape(required_python)}\">#{filename}</a><br>"
+ end
+
+ def build_pypi_package_path(file)
+ expose_url(
+ api_v4_projects_packages_pypi_files_file_identifier_path(
+ {
+ id: @project.id,
+ sha256: file.file_sha256,
+ file_identifier: file.file_name
+ },
+ true
+ )
+ ) + "#sha256=#{file.file_sha256}"
+ end
+
+ def name
+ @packages.first.name
+ end
+
+ def escape(str)
+ ERB::Util.html_escape(str)
+ end
+ end
+ end
+end
diff --git a/app/presenters/project_clusterable_presenter.rb b/app/presenters/project_clusterable_presenter.rb
index 5c56d42ed27..718f653eab1 100644
--- a/app/presenters/project_clusterable_presenter.rb
+++ b/app/presenters/project_clusterable_presenter.rb
@@ -38,6 +38,10 @@ class ProjectClusterablePresenter < ClusterablePresenter
def learn_more_link
link_to(s_('ClusterIntegration|Learn more about Kubernetes'), help_page_path('user/project/clusters/index'), target: '_blank', rel: 'noopener noreferrer')
end
+
+ def metrics_dashboard_path(cluster)
+ metrics_dashboard_project_cluster_path(clusterable, cluster)
+ end
end
ProjectClusterablePresenter.prepend_if_ee('EE::ProjectClusterablePresenter')
diff --git a/app/presenters/project_presenter.rb b/app/presenters/project_presenter.rb
index a663bc555f6..4e8dae1d508 100644
--- a/app/presenters/project_presenter.rb
+++ b/app/presenters/project_presenter.rb
@@ -16,7 +16,7 @@ class ProjectPresenter < Gitlab::View::Presenter::Delegated
MAX_TOPICS_TO_SHOW = 3
def statistic_icon(icon_name = 'plus-square-o')
- sprite_icon(icon_name, size: 16, css_class: 'icon append-right-4')
+ sprite_icon(icon_name, size: 16, css_class: 'icon gl-mr-2')
end
def statistics_anchors(show_auto_devops_callout:)
diff --git a/app/presenters/projects/prometheus/alert_presenter.rb b/app/presenters/projects/prometheus/alert_presenter.rb
index 6009ee4c7be..1cf8b202810 100644
--- a/app/presenters/projects/prometheus/alert_presenter.rb
+++ b/app/presenters/projects/prometheus/alert_presenter.rb
@@ -6,7 +6,7 @@ module Projects
RESERVED_ANNOTATIONS = %w(gitlab_incident_markdown gitlab_y_label title).freeze
GENERIC_ALERT_SUMMARY_ANNOTATIONS = %w(monitoring_tool service hosts).freeze
MARKDOWN_LINE_BREAK = " \n".freeze
- INCIDENT_LABEL_NAME = IncidentManagement::CreateIssueService::INCIDENT_LABEL[:title].freeze
+ INCIDENT_LABEL_NAME = ::IncidentManagement::CreateIncidentLabelService::LABEL_PROPERTIES[:title].freeze
METRIC_TIME_WINDOW = 30.minutes
def full_title
@@ -58,6 +58,25 @@ module Projects
MARKDOWN
end
+ def annotation_list
+ strong_memoize(:annotation_list) do
+ annotations
+ .reject { |annotation| annotation.label.in?(RESERVED_ANNOTATIONS | GENERIC_ALERT_SUMMARY_ANNOTATIONS) }
+ .map { |annotation| list_item(annotation.label, annotation.value) }
+ .join(MARKDOWN_LINE_BREAK)
+ end
+ end
+
+ def metric_embed_for_alert
+ "\n[](#{metrics_dashboard_url})" if metrics_dashboard_url
+ end
+
+ def metrics_dashboard_url
+ strong_memoize(:metrics_dashboard_url) do
+ embed_url_for_gitlab_alert || embed_url_for_self_managed_alert
+ end
+ end
+
private
def alert_title
@@ -93,15 +112,6 @@ module Projects
end
end
- def annotation_list
- strong_memoize(:annotation_list) do
- annotations
- .reject { |annotation| annotation.label.in?(RESERVED_ANNOTATIONS | GENERIC_ALERT_SUMMARY_ANNOTATIONS) }
- .map { |annotation| list_item(annotation.label, annotation.value) }
- .join(MARKDOWN_LINE_BREAK)
- end
- end
-
def list_item(key, value)
"**#{key}:** #{value}".strip
end
@@ -120,12 +130,6 @@ module Projects
Array(hosts.value).join(' ')
end
- def metric_embed_for_alert
- url = embed_url_for_gitlab_alert || embed_url_for_self_managed_alert
-
- "\n[](#{url})" if url
- end
-
def embed_url_for_gitlab_alert
return unless gitlab_alert
@@ -133,6 +137,7 @@ module Projects
project,
gitlab_alert.prometheus_metric_id,
environment_id: environment.id,
+ embedded: true,
**alert_embed_window_params(embed_time)
)
end
@@ -144,6 +149,7 @@ module Projects
project,
environment,
embed_json: dashboard_for_self_managed_alert.to_json,
+ embedded: true,
**alert_embed_window_params(embed_time)
)
end
diff --git a/app/presenters/release_presenter.rb b/app/presenters/release_presenter.rb
index 7b0a3d1e7b9..4393ca05f48 100644
--- a/app/presenters/release_presenter.rb
+++ b/app/presenters/release_presenter.rb
@@ -5,7 +5,7 @@ class ReleasePresenter < Gitlab::View::Presenter::Delegated
presents :release
- delegate :project, :tag, :assets_count, to: :release
+ delegate :project, :tag, to: :release
def commit_path
return unless release.commit && can_download_code?
@@ -43,6 +43,18 @@ class ReleasePresenter < Gitlab::View::Presenter::Delegated
edit_project_release_url(project, release)
end
+ def assets_count
+ if can_download_code?
+ release.assets_count
+ else
+ release.assets_count(except: [:sources])
+ end
+ end
+
+ def name
+ can_download_code? ? release.name : "Release-#{release.id}"
+ end
+
private
def can_download_code?
diff --git a/app/presenters/snippet_blob_presenter.rb b/app/presenters/snippet_blob_presenter.rb
index ed9c28bbc2c..d27fe751ab7 100644
--- a/app/presenters/snippet_blob_presenter.rb
+++ b/app/presenters/snippet_blob_presenter.rb
@@ -1,6 +1,8 @@
# frozen_string_literal: true
class SnippetBlobPresenter < BlobPresenter
+ include GitlabRoutingHelper
+
def rich_data
return if blob.binary?
return unless blob.rich_viewer
@@ -15,15 +17,17 @@ class SnippetBlobPresenter < BlobPresenter
end
def raw_path
- if snippet.is_a?(ProjectSnippet)
- raw_project_snippet_path(snippet.project, snippet)
- else
- raw_snippet_path(snippet)
- end
+ return gitlab_raw_snippet_blob_path(blob) if snippet_multiple_files?
+
+ gitlab_raw_snippet_path(snippet)
end
private
+ def snippet_multiple_files?
+ blob.container.repository_exists? && Feature.enabled?(:snippet_multiple_files, current_user)
+ end
+
def snippet
blob.container
end
diff --git a/app/serializers/build_trace_entity.rb b/app/serializers/build_trace_entity.rb
index b5bac8a5d64..f4c3c7770b2 100644
--- a/app/serializers/build_trace_entity.rb
+++ b/app/serializers/build_trace_entity.rb
@@ -12,6 +12,5 @@ class BuildTraceEntity < Grape::Entity
expose :size
expose :total
- expose :json_lines, as: :lines, if: ->(*) { object.json? }
- expose :html_lines, as: :html, if: ->(*) { object.html? }
+ expose :lines
end
diff --git a/app/serializers/ci/group_variable_entity.rb b/app/serializers/ci/group_variable_entity.rb
new file mode 100644
index 00000000000..e7d0a957082
--- /dev/null
+++ b/app/serializers/ci/group_variable_entity.rb
@@ -0,0 +1,6 @@
+# frozen_string_literal: true
+
+module Ci
+ class GroupVariableEntity < Ci::BasicVariableEntity
+ end
+end
diff --git a/app/serializers/ci/group_variable_serializer.rb b/app/serializers/ci/group_variable_serializer.rb
new file mode 100644
index 00000000000..b100a931620
--- /dev/null
+++ b/app/serializers/ci/group_variable_serializer.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+module Ci
+ class GroupVariableSerializer < BaseSerializer
+ entity ::Ci::GroupVariableEntity
+ end
+end
diff --git a/app/serializers/ci/variable_entity.rb b/app/serializers/ci/variable_entity.rb
new file mode 100644
index 00000000000..715f829a0e1
--- /dev/null
+++ b/app/serializers/ci/variable_entity.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+module Ci
+ class VariableEntity < Ci::BasicVariableEntity
+ expose :environment_scope
+ end
+end
diff --git a/app/serializers/ci/variable_serializer.rb b/app/serializers/ci/variable_serializer.rb
new file mode 100644
index 00000000000..eb47d3b71b5
--- /dev/null
+++ b/app/serializers/ci/variable_serializer.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+module Ci
+ class VariableSerializer < BaseSerializer
+ entity ::Ci::VariableEntity
+ end
+end
diff --git a/app/serializers/cluster_application_entity.rb b/app/serializers/cluster_application_entity.rb
index 32b759b9628..6b9a3ce114b 100644
--- a/app/serializers/cluster_application_entity.rb
+++ b/app/serializers/cluster_application_entity.rb
@@ -4,7 +4,7 @@ class ClusterApplicationEntity < Grape::Entity
expose :name
expose :status_name, as: :status
expose :status_reason
- expose :version
+ expose :version, if: -> (e, _) { e.respond_to?(:version) }
expose :external_ip, if: -> (e, _) { e.respond_to?(:external_ip) }
expose :external_hostname, if: -> (e, _) { e.respond_to?(:external_hostname) }
expose :hostname, if: -> (e, _) { e.respond_to?(:hostname) }
diff --git a/app/serializers/cluster_entity.rb b/app/serializers/cluster_entity.rb
index 8a1d41dbd96..a46f2889a96 100644
--- a/app/serializers/cluster_entity.rb
+++ b/app/serializers/cluster_entity.rb
@@ -16,4 +16,8 @@ class ClusterEntity < Grape::Entity
expose :path do |cluster|
Clusters::ClusterPresenter.new(cluster).show_path # rubocop: disable CodeReuse/Presenter
end
+
+ expose :gitlab_managed_apps_logs_path do |cluster|
+ Clusters::ClusterPresenter.new(cluster, current_user: request.current_user).gitlab_managed_apps_logs_path # rubocop: disable CodeReuse/Presenter
+ end
end
diff --git a/app/serializers/cluster_serializer.rb b/app/serializers/cluster_serializer.rb
index 27156d3178f..92363a4942c 100644
--- a/app/serializers/cluster_serializer.rb
+++ b/app/serializers/cluster_serializer.rb
@@ -10,6 +10,7 @@ class ClusterSerializer < BaseSerializer
:cluster_type,
:enabled,
:environment_scope,
+ :gitlab_managed_apps_logs_path,
:name,
:nodes,
:path,
diff --git a/app/serializers/deploy_key_entity.rb b/app/serializers/deploy_key_entity.rb
index 653316ce4d2..486189b84ca 100644
--- a/app/serializers/deploy_key_entity.rb
+++ b/app/serializers/deploy_key_entity.rb
@@ -16,6 +16,7 @@ class DeployKeyEntity < Grape::Entity
end
end
expose :can_edit
+ expose :user, as: :owner, using: ::API::Entities::UserBasic, if: -> (_, opts) { can_read_owner?(opts) }
private
@@ -24,6 +25,10 @@ class DeployKeyEntity < Grape::Entity
Ability.allowed?(options[:user], :update_deploy_keys_project, object.deploy_keys_project_for(options[:project]))
end
+ def can_read_owner?(opts)
+ opts[:with_owner] && Ability.allowed?(options[:user], :read_user, object.user)
+ end
+
def allowed_to_read_project?(project)
if options[:readable_project_ids]
options[:readable_project_ids].include?(project.id)
diff --git a/app/serializers/diff_file_base_entity.rb b/app/serializers/diff_file_base_entity.rb
index 33eb33d314b..2af14f1eb82 100644
--- a/app/serializers/diff_file_base_entity.rb
+++ b/app/serializers/diff_file_base_entity.rb
@@ -26,13 +26,9 @@ class DiffFileBaseEntity < Grape::Entity
target_project, target_branch = edit_project_branch_options(merge_request)
- if Feature.enabled?(:web_ide_default)
- ide_edit_path(target_project, target_branch, diff_file.new_path)
- else
- options = merge_request.persisted? && merge_request.source_branch_exists? && !merge_request.merged? ? { from_merge_request_iid: merge_request.iid } : {}
+ options = merge_request.persisted? && merge_request.source_branch_exists? && !merge_request.merged? ? { from_merge_request_iid: merge_request.iid } : {}
- project_edit_blob_path(target_project, tree_join(target_branch, diff_file.new_path), options)
- end
+ project_edit_blob_path(target_project, tree_join(target_branch, diff_file.new_path), options)
end
expose :old_path_html do |diff_file|
diff --git a/app/serializers/evidences/release_entity.rb b/app/serializers/evidences/release_entity.rb
index 59e379a3c08..dfc4f52de07 100644
--- a/app/serializers/evidences/release_entity.rb
+++ b/app/serializers/evidences/release_entity.rb
@@ -11,3 +11,5 @@ module Evidences
expose :milestones, using: Evidences::MilestoneEntity
end
end
+
+Evidences::ReleaseEntity.prepend_if_ee('EE::Evidences::ReleaseEntity')
diff --git a/app/serializers/fork_namespace_entity.rb b/app/serializers/fork_namespace_entity.rb
new file mode 100644
index 00000000000..068862e0951
--- /dev/null
+++ b/app/serializers/fork_namespace_entity.rb
@@ -0,0 +1,52 @@
+# frozen_string_literal: true
+
+class ForkNamespaceEntity < Grape::Entity
+ include ActionView::Helpers::NumberHelper
+ include RequestAwareEntity
+ include MarkupHelper
+
+ expose :id, :name, :description, :visibility, :full_name,
+ :created_at, :updated_at, :avatar_url
+
+ expose :fork_path do |namespace, options|
+ project_forks_path(options[:project], namespace_key: namespace.id)
+ end
+
+ expose :forked_project_path do |namespace, options|
+ if forked_project = namespace.find_fork_of(options[:project])
+ project_path(forked_project)
+ end
+ end
+
+ expose :permission do |namespace, options|
+ membership(options[:current_user], namespace)&.human_access
+ end
+
+ expose :relative_path do |namespace|
+ polymorphic_path(namespace)
+ end
+
+ expose :markdown_description do |namespace|
+ markdown_description(namespace)
+ end
+
+ expose :can_create_project do |namespace, options|
+ options[:current_user].can?(:create_projects, namespace)
+ end
+
+ private
+
+ # rubocop: disable CodeReuse/ActiveRecord
+ def membership(user, object)
+ return unless user
+
+ @membership ||= user.members.find_by(source: object)
+ end
+ # rubocop: enable CodeReuse/ActiveRecord
+
+ def markdown_description(namespace)
+ markdown_field(namespace, :description)
+ end
+end
+
+ForkNamespaceEntity.prepend_if_ee('EE::ForkNamespaceEntity')
diff --git a/app/serializers/fork_namespace_serializer.rb b/app/serializers/fork_namespace_serializer.rb
new file mode 100644
index 00000000000..1461938269e
--- /dev/null
+++ b/app/serializers/fork_namespace_serializer.rb
@@ -0,0 +1,5 @@
+# frozen_string_literal: true
+
+class ForkNamespaceSerializer < BaseSerializer
+ entity ForkNamespaceEntity
+end
diff --git a/app/serializers/group_variable_entity.rb b/app/serializers/group_variable_entity.rb
deleted file mode 100644
index 4f44723fefe..00000000000
--- a/app/serializers/group_variable_entity.rb
+++ /dev/null
@@ -1,4 +0,0 @@
-# frozen_string_literal: true
-
-class GroupVariableEntity < Ci::BasicVariableEntity
-end
diff --git a/app/serializers/group_variable_serializer.rb b/app/serializers/group_variable_serializer.rb
deleted file mode 100644
index ed20b240cce..00000000000
--- a/app/serializers/group_variable_serializer.rb
+++ /dev/null
@@ -1,5 +0,0 @@
-# frozen_string_literal: true
-
-class GroupVariableSerializer < BaseSerializer
- entity GroupVariableEntity
-end
diff --git a/app/serializers/merge_request_poll_cached_widget_entity.rb b/app/serializers/merge_request_poll_cached_widget_entity.rb
index 72f629b3507..c51c08ab646 100644
--- a/app/serializers/merge_request_poll_cached_widget_entity.rb
+++ b/app/serializers/merge_request_poll_cached_widget_entity.rb
@@ -74,6 +74,30 @@ class MergeRequestPollCachedWidgetEntity < IssuableEntity
diffs_project_merge_request_path(merge_request.project, merge_request)
end
+ expose :squash_enabled_by_default do |merge_request|
+ presenter(merge_request).project.squash_enabled_by_default?
+ end
+
+ expose :squash_readonly do |merge_request|
+ presenter(merge_request).project.squash_readonly?
+ end
+
+ expose :squash_on_merge do |merge_request|
+ presenter(merge_request).squash_on_merge?
+ end
+
+ expose :api_approvals_path do |merge_request|
+ presenter(merge_request).api_approvals_path
+ end
+
+ expose :api_approve_path do |merge_request|
+ presenter(merge_request).api_approve_path
+ end
+
+ expose :api_unapprove_path do |merge_request|
+ presenter(merge_request).api_unapprove_path
+ end
+
private
delegate :current_user, to: :request
diff --git a/app/serializers/merge_request_poll_widget_entity.rb b/app/serializers/merge_request_poll_widget_entity.rb
index aad607f358a..a365ebc29c9 100644
--- a/app/serializers/merge_request_poll_widget_entity.rb
+++ b/app/serializers/merge_request_poll_widget_entity.rb
@@ -145,6 +145,22 @@ class MergeRequestPollWidgetEntity < Grape::Entity
presenter(merge_request).revert_in_fork_path
end
+ expose :squash_enabled_by_default do |merge_request|
+ presenter(merge_request).project.squash_enabled_by_default?
+ end
+
+ expose :squash_readonly do |merge_request|
+ presenter(merge_request).project.squash_readonly?
+ end
+
+ expose :squash_on_merge do |merge_request|
+ presenter(merge_request).squash_on_merge?
+ end
+
+ expose :approvals_widget_type do |merge_request|
+ presenter(merge_request).approvals_widget_type
+ end
+
private
delegate :current_user, to: :request
diff --git a/app/serializers/merge_request_widget_entity.rb b/app/serializers/merge_request_widget_entity.rb
index 74f29b36209..2a7afb57314 100644
--- a/app/serializers/merge_request_widget_entity.rb
+++ b/app/serializers/merge_request_widget_entity.rb
@@ -85,6 +85,26 @@ class MergeRequestWidgetEntity < Grape::Entity
end
end
+ expose :blob_path do
+ expose :head_path, if: -> (mr, _) { mr.source_branch_sha } do |merge_request|
+ project_blob_path(merge_request.project, merge_request.source_branch_sha)
+ end
+
+ expose :base_path, if: -> (mr, _) { mr.diff_base_sha } do |merge_request|
+ project_blob_path(merge_request.project, merge_request.diff_base_sha)
+ end
+ end
+
+ expose :codeclimate, if: -> (mr, _) { head_pipeline_downloadable_path_for_report_type(:codequality) } do
+ expose :head_path do |merge_request|
+ head_pipeline_downloadable_path_for_report_type(:codequality)
+ end
+
+ expose :base_path do |merge_request|
+ base_pipeline_downloadable_path_for_report_type(:codequality)
+ end
+ end
+
private
delegate :current_user, to: :request
@@ -95,12 +115,24 @@ class MergeRequestWidgetEntity < Grape::Entity
end
def can_add_ci_config_path?(merge_request)
- merge_request.source_project&.uses_default_ci_config? &&
+ merge_request.open? &&
+ merge_request.source_branch_exists? &&
+ merge_request.source_project&.uses_default_ci_config? &&
!merge_request.source_project.has_ci? &&
merge_request.commits_count.positive? &&
can?(current_user, :read_build, merge_request.source_project) &&
can?(current_user, :create_pipeline, merge_request.source_project)
end
+
+ def head_pipeline_downloadable_path_for_report_type(file_type)
+ object.head_pipeline&.present(current_user: current_user)
+ &.downloadable_path_for_report_type(file_type)
+ end
+
+ def base_pipeline_downloadable_path_for_report_type(file_type)
+ object.base_pipeline&.present(current_user: current_user)
+ &.downloadable_path_for_report_type(file_type)
+ end
end
MergeRequestWidgetEntity.prepend_if_ee('EE::MergeRequestWidgetEntity')
diff --git a/app/serializers/pipeline_entity.rb b/app/serializers/pipeline_entity.rb
index c3ddbb88c9c..8333a0bb863 100644
--- a/app/serializers/pipeline_entity.rb
+++ b/app/serializers/pipeline_entity.rb
@@ -85,6 +85,10 @@ class PipelineEntity < Grape::Entity
pipeline.failed_builds
end
+ expose :tests_total_count, if: -> (pipeline, _) { Feature.enabled?(:build_report_summary, pipeline.project) } do |pipeline|
+ pipeline.test_report_summary.total_count
+ end
+
private
alias_method :pipeline, :object
diff --git a/app/serializers/pipeline_serializer.rb b/app/serializers/pipeline_serializer.rb
index 21d49c6c292..bfd6851647f 100644
--- a/app/serializers/pipeline_serializer.rb
+++ b/app/serializers/pipeline_serializer.rb
@@ -60,8 +60,8 @@ class PipelineSerializer < BaseSerializer
},
pending_builds: :project,
project: [:route, { namespace: :route }],
- triggered_by_pipeline: [:project, :user],
- triggered_pipelines: [:project, :user]
+ triggered_by_pipeline: [{ project: [:route, { namespace: :route }] }, :user],
+ triggered_pipelines: [{ project: [:route, { namespace: :route }] }, :user, :source_job]
}
]
end
diff --git a/app/serializers/service_event_entity.rb b/app/serializers/service_event_entity.rb
index fd655dd1ed3..eb4f9c665f2 100644
--- a/app/serializers/service_event_entity.rb
+++ b/app/serializers/service_event_entity.rb
@@ -14,7 +14,7 @@ class ServiceEventEntity < Grape::Entity
end
expose :description do |event|
- service.class.event_description(event)
+ ServicesHelper.service_event_description(event)
end
expose :field, if: -> (_, _) { event_field } do
diff --git a/app/serializers/service_field_entity.rb b/app/serializers/service_field_entity.rb
index 9929d7e2e5a..08e08ae187f 100644
--- a/app/serializers/service_field_entity.rb
+++ b/app/serializers/service_field_entity.rb
@@ -11,6 +11,8 @@ class ServiceFieldEntity < Grape::Entity
if field[:type] == 'password' && value.present?
'true'
+ elsif field[:type] == 'checkbox'
+ ActiveRecord::Type::Boolean.new.deserialize(value).to_s
else
value
end
diff --git a/app/serializers/stage_entity.rb b/app/serializers/stage_entity.rb
index 0b0454c5282..0aadcd01a43 100644
--- a/app/serializers/stage_entity.rb
+++ b/app/serializers/stage_entity.rb
@@ -59,13 +59,13 @@ class StageEntity < Grape::Entity
end
def latest_statuses
- HasStatus::ORDERED_STATUSES.flat_map do |ordered_status|
+ Ci::HasStatus::ORDERED_STATUSES.flat_map do |ordered_status|
grouped_statuses.fetch(ordered_status, [])
end
end
def retried_statuses
- HasStatus::ORDERED_STATUSES.flat_map do |ordered_status|
+ Ci::HasStatus::ORDERED_STATUSES.flat_map do |ordered_status|
grouped_retried_statuses.fetch(ordered_status, [])
end
end
diff --git a/app/serializers/suggestion_entity.rb b/app/serializers/suggestion_entity.rb
index 4fb19fbc074..c9fcbe14f2e 100644
--- a/app/serializers/suggestion_entity.rb
+++ b/app/serializers/suggestion_entity.rb
@@ -2,6 +2,7 @@
class SuggestionEntity < API::Entities::Suggestion
include RequestAwareEntity
+ include Gitlab::Utils::StrongMemoize
unexpose :from_line, :to_line, :from_content, :to_content
expose :diff_lines, using: DiffLineEntity do |suggestion|
@@ -9,7 +10,29 @@ class SuggestionEntity < API::Entities::Suggestion
end
expose :current_user do
expose :can_apply do |suggestion|
- Ability.allowed?(current_user, :apply_suggestion, suggestion)
+ can_apply?(suggestion)
+ end
+ end
+
+ expose :inapplicable_reason do |suggestion|
+ next _("You don't have write access to the source branch.") unless can_apply?(suggestion)
+ next if suggestion.appliable?
+
+ case suggestion.inapplicable_reason
+ when :merge_request_merged
+ _("This merge request was merged. To apply this suggestion, edit this file directly.")
+ when :merge_request_closed
+ _("This merge request is closed. To apply this suggestion, edit this file directly.")
+ when :source_branch_deleted
+ _("Can't apply as the source branch was deleted.")
+ when :outdated
+ phrase = suggestion.single_line? ? 'this line was' : 'these lines were'
+
+ _("Can't apply as %{phrase} changed in a more recent version.") % { phrase: phrase }
+ when :same_content
+ _("This suggestion already matches its content.")
+ else
+ _("Can't apply this suggestion.")
end
end
@@ -18,4 +41,10 @@ class SuggestionEntity < API::Entities::Suggestion
def current_user
request.current_user
end
+
+ def can_apply?(suggestion)
+ strong_memoize(:can_apply) do
+ Ability.allowed?(current_user, :apply_suggestion, suggestion)
+ end
+ end
end
diff --git a/app/serializers/test_report_summary_entity.rb b/app/serializers/test_report_summary_entity.rb
new file mode 100644
index 00000000000..5995ca007d6
--- /dev/null
+++ b/app/serializers/test_report_summary_entity.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+class TestReportSummaryEntity < TestReportEntity
+ expose :test_suites, using: TestSuiteSummaryEntity do |summary|
+ summary.test_suites.values
+ end
+end
diff --git a/app/serializers/test_report_summary_serializer.rb b/app/serializers/test_report_summary_serializer.rb
new file mode 100644
index 00000000000..6077a4e87bb
--- /dev/null
+++ b/app/serializers/test_report_summary_serializer.rb
@@ -0,0 +1,5 @@
+# frozen_string_literal: true
+
+class TestReportSummarySerializer < BaseSerializer
+ entity TestReportSummaryEntity
+end
diff --git a/app/serializers/test_suite_entity.rb b/app/serializers/test_suite_entity.rb
index 53fa830718a..d04fd5f6a84 100644
--- a/app/serializers/test_suite_entity.rb
+++ b/app/serializers/test_suite_entity.rb
@@ -9,9 +9,11 @@ class TestSuiteEntity < Grape::Entity
expose :failed_count
expose :skipped_count
expose :error_count
- expose :suite_error
- expose :test_cases, using: TestCaseEntity do |test_suite|
- test_suite.suite_error ? [] : test_suite.test_cases.values.flat_map(&:values)
+ with_options if: -> (_, opts) { opts[:details] } do |test_suite|
+ expose :suite_error
+ expose :test_cases, using: TestCaseEntity do |test_suite|
+ test_suite.suite_error ? [] : test_suite.test_cases.values.flat_map(&:values)
+ end
end
end
diff --git a/app/serializers/test_suite_serializer.rb b/app/serializers/test_suite_serializer.rb
new file mode 100644
index 00000000000..f11d0fbe7e6
--- /dev/null
+++ b/app/serializers/test_suite_serializer.rb
@@ -0,0 +1,5 @@
+# frozen_string_literal: true
+
+class TestSuiteSerializer < BaseSerializer
+ entity TestSuiteEntity
+end
diff --git a/app/serializers/test_suite_summary_entity.rb b/app/serializers/test_suite_summary_entity.rb
new file mode 100644
index 00000000000..6718b31a7f5
--- /dev/null
+++ b/app/serializers/test_suite_summary_entity.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+class TestSuiteSummaryEntity < TestSuiteEntity
+ expose :build_ids do |summary|
+ summary.build_ids
+ end
+end
diff --git a/app/serializers/triggered_pipeline_entity.rb b/app/serializers/triggered_pipeline_entity.rb
index fd7e4454abf..47f51a6d76a 100644
--- a/app/serializers/triggered_pipeline_entity.rb
+++ b/app/serializers/triggered_pipeline_entity.rb
@@ -11,6 +11,12 @@ class TriggeredPipelineEntity < Grape::Entity
expose :coverage
expose :source
+ expose :source_job do
+ expose :name do |pipeline|
+ pipeline.source_job&.name
+ end
+ end
+
expose :path do |pipeline|
project_pipeline_path(pipeline.project, pipeline)
end
@@ -27,7 +33,7 @@ class TriggeredPipelineEntity < Grape::Entity
as: :triggered_by, with: TriggeredPipelineEntity,
if: -> (_, opts) { can_read_details? && expand_for_path?(opts) }
- expose :triggered_pipelines,
+ expose :triggered_pipelines_with_preloads,
as: :triggered, using: TriggeredPipelineEntity,
if: -> (_, opts) { can_read_details? && expand_for_path?(opts) }
diff --git a/app/serializers/variable_entity.rb b/app/serializers/variable_entity.rb
deleted file mode 100644
index 9b0db371acb..00000000000
--- a/app/serializers/variable_entity.rb
+++ /dev/null
@@ -1,5 +0,0 @@
-# frozen_string_literal: true
-
-class VariableEntity < Ci::BasicVariableEntity
- expose :environment_scope
-end
diff --git a/app/serializers/variable_serializer.rb b/app/serializers/variable_serializer.rb
deleted file mode 100644
index 586666cad8e..00000000000
--- a/app/serializers/variable_serializer.rb
+++ /dev/null
@@ -1,5 +0,0 @@
-# frozen_string_literal: true
-
-class VariableSerializer < BaseSerializer
- entity VariableEntity
-end
diff --git a/app/services/access_token_validation_service.rb b/app/services/access_token_validation_service.rb
index 851d862c0cf..eb2e66a9285 100644
--- a/app/services/access_token_validation_service.rb
+++ b/app/services/access_token_validation_service.rb
@@ -17,21 +17,21 @@ class AccessTokenValidationService
def validate(scopes: [])
if token.expired?
- return EXPIRED
+ EXPIRED
elsif token.revoked?
- return REVOKED
+ REVOKED
elsif !self.include_any_scope?(scopes)
- return INSUFFICIENT_SCOPE
+ INSUFFICIENT_SCOPE
elsif token.respond_to?(:impersonation) &&
token.impersonation &&
!Gitlab.config.gitlab.impersonation_enabled
- return IMPERSONATION_DISABLED
+ IMPERSONATION_DISABLED
else
- return VALID
+ VALID
end
end
diff --git a/app/services/admin/propagate_integration_service.rb b/app/services/admin/propagate_integration_service.rb
index 084b103ee3b..e21bb03ed68 100644
--- a/app/services/admin/propagate_integration_service.rb
+++ b/app/services/admin/propagate_integration_service.rb
@@ -64,7 +64,7 @@ module Admin
def create_integration_for_projects_without_integration
loop do
- batch = Project.uncached { project_ids_without_integration }
+ batch = Project.uncached { Project.ids_without_integration(integration, BATCH_SIZE) }
bulk_create_from_integration(batch) unless batch.empty?
@@ -114,22 +114,6 @@ module Admin
integration.type == 'ExternalWikiService'
end
- # rubocop: disable CodeReuse/ActiveRecord
- def project_ids_without_integration
- services = Service
- .select('1')
- .where('services.project_id = projects.id')
- .where(type: integration.type)
-
- Project
- .where('NOT EXISTS (?)', services)
- .where(pending_delete: false)
- .where(archived: false)
- .limit(BATCH_SIZE)
- .pluck(:id)
- end
- # rubocop: enable CodeReuse/ActiveRecord
-
def service_hash
@service_hash ||= integration.to_service_hash
.tap { |json| json['inherit_from_id'] = integration.id }
diff --git a/app/services/alert_management/alerts/todo/create_service.rb b/app/services/alert_management/alerts/todo/create_service.rb
new file mode 100644
index 00000000000..87af943fdc2
--- /dev/null
+++ b/app/services/alert_management/alerts/todo/create_service.rb
@@ -0,0 +1,51 @@
+# frozen_string_literal: true
+
+module AlertManagement
+ module Alerts
+ module Todo
+ class CreateService
+ # @param alert [AlertManagement::Alert]
+ # @param current_user [User]
+ def initialize(alert, current_user)
+ @alert = alert
+ @current_user = current_user
+ end
+
+ def execute
+ return error_no_permissions unless allowed?
+
+ todos = TodoService.new.mark_todo(alert, current_user)
+ todo = todos&.first
+
+ return error_existing_todo unless todo
+
+ success(todo)
+ end
+
+ private
+
+ attr_reader :alert, :current_user
+
+ def allowed?
+ current_user&.can?(:update_alert_management_alert, alert)
+ end
+
+ def error(message)
+ ServiceResponse.error(payload: { alert: alert, todo: nil }, message: message)
+ end
+
+ def success(todo)
+ ServiceResponse.success(payload: { alert: alert, todo: todo })
+ end
+
+ def error_no_permissions
+ error(_('You have insufficient permissions to create a Todo for this alert'))
+ end
+
+ def error_existing_todo
+ error(_('You already have pending todo for this alert'))
+ end
+ end
+ end
+ end
+end
diff --git a/app/services/alert_management/alerts/update_service.rb b/app/services/alert_management/alerts/update_service.rb
index ffabbb37289..0b7216cd9f8 100644
--- a/app/services/alert_management/alerts/update_service.rb
+++ b/app/services/alert_management/alerts/update_service.rb
@@ -12,17 +12,20 @@ module AlertManagement
@alert = alert
@current_user = current_user
@params = params
+ @param_errors = []
end
def execute
return error_no_permissions unless allowed?
- return error_no_updates if params.empty?
- filter_assignees
+ filter_params
+ return error_invalid_params if param_errors.any?
+
+ # Save old assignees for system notes
old_assignees = alert.assignees.to_a
if alert.update(params)
- process_assignement(old_assignees)
+ handle_changes(old_assignees: old_assignees)
success
else
@@ -32,16 +35,13 @@ module AlertManagement
private
- attr_reader :alert, :current_user, :params
+ attr_reader :alert, :current_user, :params, :param_errors
+ delegate :resolved?, to: :alert
def allowed?
current_user&.can?(:update_alert_management_alert, alert)
end
- def assignee_todo_allowed?
- assignee&.can?(:read_alert_management_alert, alert)
- end
-
def todo_service
strong_memoize(:todo_service) do
TodoService.new
@@ -60,39 +60,122 @@ module AlertManagement
error(_('You have no permissions'))
end
- def error_no_updates
- error(_('Please provide attributes to update'))
+ def error_invalid_params
+ error(param_errors.to_sentence)
+ end
+
+ def add_param_error(message)
+ param_errors << message
+ end
+
+ def filter_params
+ param_errors << _('Please provide attributes to update') if params.empty?
+
+ filter_status
+ filter_assignees
+ filter_duplicate
+ end
+
+ def handle_changes(old_assignees:)
+ handle_assignement(old_assignees) if params[:assignees]
+ handle_status_change if params[:status_event]
end
# ----- Assignee-related behavior ------
def filter_assignees
return if params[:assignees].nil?
- params[:assignees] = Array(assignee)
+ # Always take first assignee while multiple are not currently supported
+ params[:assignees] = Array(params[:assignees].first)
+
+ param_errors << _('Assignee has no permissions') if unauthorized_assignees?
end
- def assignee
- strong_memoize(:assignee) do
- # Take first assignee while multiple are not currently supported
- params[:assignees]&.first
- end
+ def unauthorized_assignees?
+ params[:assignees]&.any? { |user| !user.can?(:read_alert_management_alert, alert) }
end
- def process_assignement(old_assignees)
+ def handle_assignement(old_assignees)
assign_todo
add_assignee_system_note(old_assignees)
end
def assign_todo
- # Remove check in follow-up issue https://gitlab.com/gitlab-org/gitlab/-/issues/222672
- return unless assignee_todo_allowed?
-
todo_service.assign_alert(alert, current_user)
end
def add_assignee_system_note(old_assignees)
SystemNoteService.change_issuable_assignees(alert, alert.project, current_user, old_assignees)
end
+
+ # ------ Status-related behavior -------
+ def filter_status
+ return unless params[:status]
+
+ status_event = AlertManagement::Alert::STATUS_EVENTS[status_key]
+
+ unless status_event
+ param_errors << _('Invalid status')
+ return
+ end
+
+ params[:status_event] = status_event
+ end
+
+ def status_key
+ strong_memoize(:status_key) do
+ status = params.delete(:status)
+ AlertManagement::Alert::STATUSES.key(status)
+ end
+ end
+
+ def handle_status_change
+ add_status_change_system_note
+ resolve_todos if resolved?
+ end
+
+ def add_status_change_system_note
+ SystemNoteService.change_alert_status(alert, current_user)
+ end
+
+ def resolve_todos
+ todo_service.resolve_todos_for_target(alert, current_user)
+ end
+
+ def filter_duplicate
+ # Only need to check if changing to an open status
+ return unless params[:status_event] && AlertManagement::Alert::OPEN_STATUSES.include?(status_key)
+
+ param_errors << unresolved_alert_error if duplicate_alert?
+ end
+
+ def duplicate_alert?
+ return if alert.fingerprint.blank?
+
+ open_alerts.any? && open_alerts.exclude?(alert)
+ end
+
+ def open_alerts
+ strong_memoize(:open_alerts) do
+ AlertManagement::Alert.for_fingerprint(alert.project, alert.fingerprint).open
+ end
+ end
+
+ def unresolved_alert_error
+ _('An %{link_start}alert%{link_end} with the same fingerprint is already open. ' \
+ 'To change the status of this alert, resolve the linked alert.'
+ ) % open_alert_url_params
+ end
+
+ def open_alert_url_params
+ open_alert = open_alerts.first
+ alert_path = Gitlab::Routing.url_helpers.details_project_alert_management_path(alert.project, open_alert)
+
+ {
+ link_start: '<a href="%{url}">'.html_safe % { url: alert_path },
+ link_end: '</a>'.html_safe
+ }
+ end
end
end
end
diff --git a/app/services/alert_management/create_alert_issue_service.rb b/app/services/alert_management/create_alert_issue_service.rb
index beacd240b08..6ea3fd867ef 100644
--- a/app/services/alert_management/create_alert_issue_service.rb
+++ b/app/services/alert_management/create_alert_issue_service.rb
@@ -2,6 +2,8 @@
module AlertManagement
class CreateAlertIssueService
+ include Gitlab::Utils::StrongMemoize
+
# @param alert [AlertManagement::Alert]
# @param user [User]
def initialize(alert, user)
@@ -13,18 +15,20 @@ module AlertManagement
return error_no_permissions unless allowed?
return error_issue_already_exists if alert.issue
- result = create_issue(alert, user, alert_payload)
- @issue = result[:issue]
+ result = create_issue
+ issue = result.payload[:issue]
+
+ return error(result.message, issue) if result.error?
+ return error(object_errors(alert), issue) unless associate_alert_with_issue(issue)
- return error(result[:message]) if result[:status] == :error
- return error(alert.errors.full_messages.to_sentence) unless update_alert_issue_id
+ SystemNoteService.new_alert_issue(alert, issue, user)
- success
+ result
end
private
- attr_reader :alert, :user, :issue
+ attr_reader :alert, :user
delegate :project, to: :alert
@@ -32,29 +36,36 @@ module AlertManagement
user.can?(:create_issue, project)
end
- def create_issue(alert, user, alert_payload)
- ::IncidentManagement::CreateIssueService
- .new(project, alert_payload, user)
- .execute(skip_settings_check: true)
- end
+ def create_issue
+ label_result = find_or_create_incident_label
- def alert_payload
- if alert.prometheus?
- alert.payload
- else
- Gitlab::Alerting::NotificationPayloadParser.call(alert.payload.to_h)
- end
+ # Create an unlabelled issue if we couldn't create the label
+ # due to a race condition.
+ # See https://gitlab.com/gitlab-org/gitlab-foss/issues/65042
+ extra_params = label_result.success? ? { label_ids: [label_result.payload[:label].id] } : {}
+
+ issue = Issues::CreateService.new(
+ project,
+ user,
+ title: alert_presenter.title,
+ description: alert_presenter.issue_description,
+ **extra_params
+ ).execute
+
+ return error(object_errors(issue), issue) unless issue.valid?
+
+ success(issue)
end
- def update_alert_issue_id
+ def associate_alert_with_issue(issue)
alert.update(issue_id: issue.id)
end
- def success
+ def success(issue)
ServiceResponse.success(payload: { issue: issue })
end
- def error(message)
+ def error(message, issue = nil)
ServiceResponse.error(payload: { issue: issue }, message: message)
end
@@ -65,5 +76,19 @@ module AlertManagement
def error_no_permissions
error(_('You have no permissions'))
end
+
+ def alert_presenter
+ strong_memoize(:alert_presenter) do
+ alert.present
+ end
+ end
+
+ def find_or_create_incident_label
+ IncidentManagement::CreateIncidentLabelService.new(project, user).execute
+ end
+
+ def object_errors(object)
+ object.errors.full_messages.to_sentence
+ end
end
end
diff --git a/app/services/alert_management/process_prometheus_alert_service.rb b/app/services/alert_management/process_prometheus_alert_service.rb
index 90fcbd95e4b..573d3914c05 100644
--- a/app/services/alert_management/process_prometheus_alert_service.rb
+++ b/app/services/alert_management/process_prometheus_alert_service.rb
@@ -66,7 +66,11 @@ module AlertManagement
def process_resolved_alert_management_alert
return if am_alert.blank?
- return if am_alert.resolve(ends_at)
+
+ if am_alert.resolve(ends_at)
+ close_issue(am_alert.issue)
+ return
+ end
logger.warn(
message: 'Unable to update AlertManagement::Alert status to resolved',
@@ -75,12 +79,22 @@ module AlertManagement
)
end
+ def close_issue(issue)
+ return if issue.blank? || issue.closed?
+
+ Issues::CloseService
+ .new(project, User.alert_bot)
+ .execute(issue, system_note: false)
+
+ SystemNoteService.auto_resolve_prometheus_alert(issue, project, User.alert_bot) if issue.reset.closed?
+ end
+
def logger
@logger ||= Gitlab::AppLogger
end
def am_alert
- @am_alert ||= AlertManagement::Alert.for_fingerprint(project, gitlab_fingerprint).first
+ @am_alert ||= AlertManagement::Alert.not_resolved.for_fingerprint(project, gitlab_fingerprint).first
end
def bad_request
diff --git a/app/services/alert_management/update_alert_status_service.rb b/app/services/alert_management/update_alert_status_service.rb
deleted file mode 100644
index a7ebddb82e0..00000000000
--- a/app/services/alert_management/update_alert_status_service.rb
+++ /dev/null
@@ -1,63 +0,0 @@
-# frozen_string_literal: true
-
-module AlertManagement
- class UpdateAlertStatusService
- include Gitlab::Utils::StrongMemoize
-
- # @param alert [AlertManagement::Alert]
- # @param user [User]
- # @param status [Integer] Must match a value from AlertManagement::Alert::STATUSES
- def initialize(alert, user, status)
- @alert = alert
- @user = user
- @status = status
- end
-
- def execute
- return error_no_permissions unless allowed?
- return error_invalid_status unless status_key
-
- if alert.update(status_event: status_event)
- success
- else
- error(alert.errors.full_messages.to_sentence)
- end
- end
-
- private
-
- attr_reader :alert, :user, :status
-
- delegate :project, to: :alert
-
- def allowed?
- user.can?(:update_alert_management_alert, project)
- end
-
- def status_key
- strong_memoize(:status_key) do
- AlertManagement::Alert::STATUSES.key(status)
- end
- end
-
- def status_event
- AlertManagement::Alert::STATUS_EVENTS[status_key]
- end
-
- def success
- ServiceResponse.success(payload: { alert: alert })
- end
-
- def error_no_permissions
- error(_('You have no permissions'))
- end
-
- def error_invalid_status
- error(_('Invalid status'))
- end
-
- def error(message)
- ServiceResponse.error(payload: { alert: alert }, message: message)
- end
- end
-end
diff --git a/app/services/audit_event_service.rb b/app/services/audit_event_service.rb
index fb309aed649..fef733a7d09 100644
--- a/app/services/audit_event_service.rb
+++ b/app/services/audit_event_service.rb
@@ -16,6 +16,7 @@ class AuditEventService
@author = build_author(author)
@entity = entity
@details = details
+ @ip_address = (@details[:ip_address].presence || @author.current_sign_in_ip)
end
# Builds the @details attribute for authentication
@@ -49,6 +50,8 @@ class AuditEventService
private
+ attr_reader :ip_address
+
def build_author(author)
case author
when User
@@ -61,6 +64,7 @@ class AuditEventService
def base_payload
{
author_id: @author.id,
+ author_name: @author.name,
entity_id: @entity.id,
entity_type: @entity.class.name
}
diff --git a/app/services/authorized_project_update/project_create_service.rb b/app/services/authorized_project_update/project_create_service.rb
index c17c0a033fe..5809315a066 100644
--- a/app/services/authorized_project_update/project_create_service.rb
+++ b/app/services/authorized_project_update/project_create_service.rb
@@ -21,7 +21,7 @@ module AuthorizedProjectUpdate
{ user_id: member.user_id, project_id: project.id, access_level: member.access_level }
end
- ProjectAuthorization.insert_all(attributes)
+ ProjectAuthorization.insert_all(attributes) unless attributes.empty?
end
ServiceResponse.success
diff --git a/app/services/authorized_project_update/project_group_link_create_service.rb b/app/services/authorized_project_update/project_group_link_create_service.rb
new file mode 100644
index 00000000000..db2db091374
--- /dev/null
+++ b/app/services/authorized_project_update/project_group_link_create_service.rb
@@ -0,0 +1,70 @@
+# frozen_string_literal: true
+
+module AuthorizedProjectUpdate
+ class ProjectGroupLinkCreateService < BaseService
+ include Gitlab::Utils::StrongMemoize
+
+ BATCH_SIZE = 1000
+
+ def initialize(project, group)
+ @project = project
+ @group = group
+ end
+
+ def execute
+ group.members_from_self_and_ancestors_with_effective_access_level
+ .each_batch(of: BATCH_SIZE, column: :user_id) do |members|
+ existing_authorizations = existing_project_authorizations(members)
+ authorizations_to_create = []
+ user_ids_to_delete = []
+
+ members.each do |member|
+ existing_access_level = existing_authorizations[member.user_id]
+
+ if existing_access_level
+ # User might already have access to the project unrelated to the
+ # current project share
+ next if existing_access_level >= member.access_level
+
+ user_ids_to_delete << member.user_id
+ end
+
+ authorizations_to_create << { user_id: member.user_id,
+ project_id: project.id,
+ access_level: member.access_level }
+ end
+
+ update_authorizations(user_ids_to_delete, authorizations_to_create)
+ end
+
+ ServiceResponse.success
+ end
+
+ private
+
+ attr_reader :project, :group
+
+ def existing_project_authorizations(members)
+ user_ids = members.map(&:user_id)
+
+ ProjectAuthorization.where(project_id: project.id, user_id: user_ids) # rubocop: disable CodeReuse/ActiveRecord
+ .select(:user_id, :access_level)
+ .each_with_object({}) do |authorization, hash|
+ hash[authorization.user_id] = authorization.access_level
+ end
+ end
+
+ def update_authorizations(user_ids_to_delete, authorizations_to_create)
+ ProjectAuthorization.transaction do
+ if user_ids_to_delete.any?
+ ProjectAuthorization.where(project_id: project.id, user_id: user_ids_to_delete) # rubocop: disable CodeReuse/ActiveRecord
+ .delete_all
+ end
+
+ if authorizations_to_create.any?
+ ProjectAuthorization.insert_all(authorizations_to_create)
+ end
+ end
+ end
+ end
+end
diff --git a/app/services/auto_merge/base_service.rb b/app/services/auto_merge/base_service.rb
index c4109765a1c..5c63dc34cb1 100644
--- a/app/services/auto_merge/base_service.rb
+++ b/app/services/auto_merge/base_service.rb
@@ -11,7 +11,7 @@ module AutoMerge
yield if block_given?
end
- # Notify the event that auto merge is enabled or merge param is updated
+ notify(merge_request)
AutoMergeProcessWorker.perform_async(merge_request.id)
strategy.to_sym
@@ -62,6 +62,10 @@ module AutoMerge
private
+ # Overridden in child classes
+ def notify(merge_request)
+ end
+
def strategy
strong_memoize(:strategy) do
self.class.name.demodulize.remove('Service').underscore
diff --git a/app/services/auto_merge/merge_when_pipeline_succeeds_service.rb b/app/services/auto_merge/merge_when_pipeline_succeeds_service.rb
index 9ae5bd1b5ec..7e0298432ac 100644
--- a/app/services/auto_merge/merge_when_pipeline_succeeds_service.rb
+++ b/app/services/auto_merge/merge_when_pipeline_succeeds_service.rb
@@ -34,5 +34,13 @@ module AutoMerge
merge_request.actual_head_pipeline&.active?
end
end
+
+ private
+
+ def notify(merge_request)
+ return unless Feature.enabled?(:mwps_notification, project)
+
+ notification_service.async.merge_when_pipeline_succeeds(merge_request, current_user) if merge_request.saved_change_to_auto_merge_enabled?
+ end
end
end
diff --git a/app/services/branches/delete_service.rb b/app/services/branches/delete_service.rb
index ca2b4556b58..9bd5b343448 100644
--- a/app/services/branches/delete_service.rb
+++ b/app/services/branches/delete_service.rb
@@ -19,6 +19,7 @@ module Branches
end
if repository.rm_branch(current_user, branch_name)
+ unlock_artifacts(branch_name)
ServiceResponse.success(message: 'Branch was deleted')
else
ServiceResponse.error(
@@ -28,5 +29,11 @@ module Branches
rescue Gitlab::Git::PreReceiveError => ex
ServiceResponse.error(message: ex.message, http_status: 400)
end
+
+ private
+
+ def unlock_artifacts(branch_name)
+ Ci::RefDeleteUnlockArtifactsWorker.perform_async(project.id, current_user.id, "#{::Gitlab::Git::BRANCH_REF_PREFIX}#{branch_name}")
+ end
end
end
diff --git a/app/services/ci/authorize_job_artifact_service.rb b/app/services/ci/authorize_job_artifact_service.rb
deleted file mode 100644
index 893e92d427c..00000000000
--- a/app/services/ci/authorize_job_artifact_service.rb
+++ /dev/null
@@ -1,53 +0,0 @@
-# frozen_string_literal: true
-
-module Ci
- class AuthorizeJobArtifactService
- include Gitlab::Utils::StrongMemoize
-
- # Max size of the zipped LSIF artifact
- LSIF_ARTIFACT_MAX_SIZE = 20.megabytes
- LSIF_ARTIFACT_TYPE = 'lsif'
-
- def initialize(job, params, max_size:)
- @job = job
- @max_size = max_size
- @size = params[:filesize]
- @type = params[:artifact_type].to_s
- end
-
- def forbidden?
- lsif? && !code_navigation_enabled?
- end
-
- def too_large?
- size && max_size <= size.to_i
- end
-
- def headers
- default_headers = JobArtifactUploader.workhorse_authorize(has_length: false, maximum_size: max_size)
- default_headers.tap do |h|
- h[:ProcessLsif] = true if lsif? && code_navigation_enabled?
- end
- end
-
- private
-
- attr_reader :job, :size, :type
-
- def code_navigation_enabled?
- strong_memoize(:code_navigation_enabled) do
- Feature.enabled?(:code_navigation, job.project, default_enabled: true)
- end
- end
-
- def lsif?
- strong_memoize(:lsif) do
- type == LSIF_ARTIFACT_TYPE
- end
- end
-
- def max_size
- lsif? ? LSIF_ARTIFACT_MAX_SIZE : @max_size.to_i
- end
- end
-end
diff --git a/app/services/ci/create_job_artifacts_service.rb b/app/services/ci/create_job_artifacts_service.rb
index f0ffe67510b..9a6e103e5dd 100644
--- a/app/services/ci/create_job_artifacts_service.rb
+++ b/app/services/ci/create_job_artifacts_service.rb
@@ -3,42 +3,104 @@
module Ci
class CreateJobArtifactsService < ::BaseService
ArtifactsExistError = Class.new(StandardError)
+
+ LSIF_ARTIFACT_TYPE = 'lsif'
+
OBJECT_STORAGE_ERRORS = [
Errno::EIO,
Google::Apis::ServerError,
Signet::RemoteServerError
].freeze
- def execute(job, artifacts_file, params, metadata_file: nil)
- return success if sha256_matches_existing_artifact?(job, params['artifact_type'], artifacts_file)
+ def initialize(job)
+ @job = job
+ @project = job.project
+ end
+
+ def authorize(artifact_type:, filesize: nil)
+ result = validate_requirements(artifact_type: artifact_type, filesize: filesize)
+ return result unless result[:status] == :success
+
+ headers = JobArtifactUploader.workhorse_authorize(has_length: false, maximum_size: max_size(artifact_type))
- artifact, artifact_metadata = build_artifact(job, artifacts_file, params, metadata_file)
- result = parse_artifact(job, artifact)
+ if lsif?(artifact_type)
+ headers[:ProcessLsif] = true
+ headers[:ProcessLsifReferences] = Feature.enabled?(:code_navigation_references, project, default_enabled: false)
+ end
+ success(headers: headers)
+ end
+
+ def execute(artifacts_file, params, metadata_file: nil)
+ result = validate_requirements(artifact_type: params[:artifact_type], filesize: artifacts_file.size)
return result unless result[:status] == :success
- persist_artifact(job, artifact, artifact_metadata)
+ return success if sha256_matches_existing_artifact?(params[:artifact_type], artifacts_file)
+
+ artifact, artifact_metadata = build_artifact(artifacts_file, params, metadata_file)
+ result = parse_artifact(artifact)
+
+ return result unless result[:status] == :success
+
+ persist_artifact(artifact, artifact_metadata, params)
end
private
- def build_artifact(job, artifacts_file, params, metadata_file)
+ attr_reader :job, :project
+
+ def validate_requirements(artifact_type:, filesize:)
+ return forbidden_type_error(artifact_type) if forbidden_type?(artifact_type)
+ return too_large_error if too_large?(artifact_type, filesize)
+
+ success
+ end
+
+ def forbidden_type?(type)
+ lsif?(type) && !code_navigation_enabled?
+ end
+
+ def too_large?(type, size)
+ size > max_size(type) if size
+ end
+
+ def code_navigation_enabled?
+ Feature.enabled?(:code_navigation, project, default_enabled: true)
+ end
+
+ def lsif?(type)
+ type == LSIF_ARTIFACT_TYPE
+ end
+
+ def max_size(type)
+ Ci::JobArtifact.max_artifact_size(type: type, project: project)
+ end
+
+ def forbidden_type_error(type)
+ error("#{type} artifacts are forbidden", :forbidden)
+ end
+
+ def too_large_error
+ error('file size has reached maximum size limit', :payload_too_large)
+ end
+
+ def build_artifact(artifacts_file, params, metadata_file)
expire_in = params['expire_in'] ||
Gitlab::CurrentSettings.current_application_settings.default_artifacts_expire_in
artifact = Ci::JobArtifact.new(
job_id: job.id,
- project: job.project,
+ project: project,
file: artifacts_file,
- file_type: params['artifact_type'],
- file_format: params['artifact_format'],
+ file_type: params[:artifact_type],
+ file_format: params[:artifact_format],
file_sha256: artifacts_file.sha256,
expire_in: expire_in)
artifact_metadata = if metadata_file
Ci::JobArtifact.new(
job_id: job.id,
- project: job.project,
+ project: project,
file: metadata_file,
file_type: :metadata,
file_format: :gzip,
@@ -46,31 +108,25 @@ module Ci
expire_in: expire_in)
end
- if Feature.enabled?(:keep_latest_artifact_for_ref, job.project)
- artifact.locked = true
- artifact_metadata&.locked = true
- end
-
[artifact, artifact_metadata]
end
- def parse_artifact(job, artifact)
- unless Feature.enabled?(:ci_synchronous_artifact_parsing, job.project, default_enabled: true)
+ def parse_artifact(artifact)
+ unless Feature.enabled?(:ci_synchronous_artifact_parsing, project, default_enabled: true)
return success
end
case artifact.file_type
- when 'dotenv' then parse_dotenv_artifact(job, artifact)
- when 'cluster_applications' then parse_cluster_applications_artifact(job, artifact)
+ when 'dotenv' then parse_dotenv_artifact(artifact)
+ when 'cluster_applications' then parse_cluster_applications_artifact(artifact)
else success
end
end
- def persist_artifact(job, artifact, artifact_metadata)
+ def persist_artifact(artifact, artifact_metadata, params)
Ci::JobArtifact.transaction do
artifact.save!
artifact_metadata&.save!
- unlock_previous_artifacts!(artifact)
# NOTE: The `artifacts_expire_at` column is already deprecated and to be removed in the near future.
job.update_column(:artifacts_expire_at, artifact.expire_at)
@@ -78,42 +134,36 @@ module Ci
success
rescue ActiveRecord::RecordNotUnique => error
- track_exception(error, job, params)
+ track_exception(error, params)
error('another artifact of the same type already exists', :bad_request)
rescue *OBJECT_STORAGE_ERRORS => error
- track_exception(error, job, params)
+ track_exception(error, params)
error(error.message, :service_unavailable)
rescue => error
- track_exception(error, job, params)
+ track_exception(error, params)
error(error.message, :bad_request)
end
- def unlock_previous_artifacts!(artifact)
- return unless Feature.enabled?(:keep_latest_artifact_for_ref, artifact.job.project)
-
- Ci::JobArtifact.for_ref(artifact.job.ref, artifact.project_id).locked.update_all(locked: false)
- end
-
- def sha256_matches_existing_artifact?(job, artifact_type, artifacts_file)
+ def sha256_matches_existing_artifact?(artifact_type, artifacts_file)
existing_artifact = job.job_artifacts.find_by_file_type(artifact_type)
return false unless existing_artifact
existing_artifact.file_sha256 == artifacts_file.sha256
end
- def track_exception(error, job, params)
+ def track_exception(error, params)
Gitlab::ErrorTracking.track_exception(error,
job_id: job.id,
project_id: job.project_id,
- uploading_type: params['artifact_type']
+ uploading_type: params[:artifact_type]
)
end
- def parse_dotenv_artifact(job, artifact)
- Ci::ParseDotenvArtifactService.new(job.project, current_user).execute(artifact)
+ def parse_dotenv_artifact(artifact)
+ Ci::ParseDotenvArtifactService.new(project, current_user).execute(artifact)
end
- def parse_cluster_applications_artifact(job, artifact)
+ def parse_cluster_applications_artifact(artifact)
Clusters::ParseClusterApplicationsArtifactService.new(job, job.user).execute(artifact)
end
end
diff --git a/app/services/ci/create_pipeline_service.rb b/app/services/ci/create_pipeline_service.rb
index 922c3556362..2d7f5014aa9 100644
--- a/app/services/ci/create_pipeline_service.rb
+++ b/app/services/ci/create_pipeline_service.rb
@@ -23,6 +23,24 @@ module Ci
Gitlab::Ci::Pipeline::Chain::Limit::Activity,
Gitlab::Ci::Pipeline::Chain::Limit::JobActivity].freeze
+ # Create a new pipeline in the specified project.
+ #
+ # @param [Symbol] source What event (Ci::Pipeline.sources) triggers the pipeline
+ # creation.
+ # @param [Boolean] ignore_skip_ci Whether skipping a pipeline creation when `[skip ci]` comment
+ # is present in the commit body
+ # @param [Boolean] save_on_errors Whether persisting an invalid pipeline when it encounters an
+ # error during creation (e.g. invalid yaml)
+ # @param [Ci::TriggerRequest] trigger_request The pipeline trigger triggers the pipeline creation.
+ # @param [Ci::PipelineSchedule] schedule The pipeline schedule triggers the pipeline creation.
+ # @param [MergeRequest] merge_request The merge request triggers the pipeline creation.
+ # @param [ExternalPullRequest] external_pull_request The external pull request triggers the pipeline creation.
+ # @param [Ci::Bridge] bridge The bridge job that triggers the downstream pipeline creation.
+ # @param [String] content The content of .gitlab-ci.yml to override the default config
+ # contents (e.g. .gitlab-ci.yml in repostiry). Mainly used for
+ # generating a dangling pipeline.
+ #
+ # @return [Ci::Pipeline] The created Ci::Pipeline object.
# rubocop: disable Metrics/ParameterLists
def execute(source, ignore_skip_ci: false, save_on_errors: true, trigger_request: nil, schedule: nil, merge_request: nil, external_pull_request: nil, bridge: nil, **options, &block)
@pipeline = Ci::Pipeline.new
@@ -77,7 +95,7 @@ module Ci
def execute!(*args, &block)
execute(*args, &block).tap do |pipeline|
unless pipeline.persisted?
- raise CreateError, pipeline.error_messages
+ raise CreateError, pipeline.full_error_messages
end
end
end
@@ -122,13 +140,8 @@ module Ci
end
end
- def extra_options(options = {})
- # In Ruby 2.4, even when options is empty, f(**options) doesn't work when f
- # doesn't have any parameters. We reproduce the Ruby 2.5 behavior by
- # checking explicitly that no arguments are given.
- raise ArgumentError if options.any?
-
- {} # overridden in EE
+ def extra_options(content: nil)
+ { content: content }
end
end
end
diff --git a/app/services/ci/destroy_expired_job_artifacts_service.rb b/app/services/ci/destroy_expired_job_artifacts_service.rb
index 5deb84812ac..1fa8926faa1 100644
--- a/app/services/ci/destroy_expired_job_artifacts_service.rb
+++ b/app/services/ci/destroy_expired_job_artifacts_service.rb
@@ -28,7 +28,7 @@ module Ci
private
def destroy_batch
- artifact_batch = if Feature.enabled?(:keep_latest_artifact_for_ref)
+ artifact_batch = if Gitlab::Ci::Features.destroy_only_unlocked_expired_artifacts_enabled?
Ci::JobArtifact.expired(BATCH_SIZE).unlocked
else
Ci::JobArtifact.expired(BATCH_SIZE)
diff --git a/app/services/ci/pipeline_processing/atomic_processing_service.rb b/app/services/ci/pipeline_processing/atomic_processing_service.rb
index b01a9d2e3b8..a23d5d8941a 100644
--- a/app/services/ci/pipeline_processing/atomic_processing_service.rb
+++ b/app/services/ci/pipeline_processing/atomic_processing_service.rb
@@ -77,7 +77,7 @@ module Ci
def update_processable!(processable)
status = processable_status(processable)
- return unless HasStatus::COMPLETED_STATUSES.include?(status)
+ return unless Ci::HasStatus::COMPLETED_STATUSES.include?(status)
# transition status if possible
Gitlab::OptimisticLocking.retry_lock(processable) do |subject|
diff --git a/app/services/ci/pipeline_processing/atomic_processing_service/status_collection.rb b/app/services/ci/pipeline_processing/atomic_processing_service/status_collection.rb
index 2228328882d..d0aa8b04775 100644
--- a/app/services/ci/pipeline_processing/atomic_processing_service/status_collection.rb
+++ b/app/services/ci/pipeline_processing/atomic_processing_service/status_collection.rb
@@ -80,7 +80,7 @@ module Ci
# TODO: This is hack to support
# the same exact behaviour for Atomic and Legacy processing
# that DAG is blocked from executing if dependent is not "complete"
- if dag && statuses.any? { |status| HasStatus::COMPLETED_STATUSES.exclude?(status[:status]) }
+ if dag && statuses.any? { |status| Ci::HasStatus::COMPLETED_STATUSES.exclude?(status[:status]) }
return 'pending'
end
diff --git a/app/services/ci/pipeline_processing/legacy_processing_service.rb b/app/services/ci/pipeline_processing/legacy_processing_service.rb
index c471f7f0011..56fbc7271da 100644
--- a/app/services/ci/pipeline_processing/legacy_processing_service.rb
+++ b/app/services/ci/pipeline_processing/legacy_processing_service.rb
@@ -35,7 +35,7 @@ module Ci
def process_stage_for_stage_scheduling(index)
current_status = status_for_prior_stages(index)
- return unless HasStatus::COMPLETED_STATUSES.include?(current_status)
+ return unless Ci::HasStatus::COMPLETED_STATUSES.include?(current_status)
created_stage_scheduled_processables_in_stage(index).find_each.select do |build|
process_build(build, current_status)
@@ -73,7 +73,7 @@ module Ci
def process_dag_build_with_needs(build)
current_status = status_for_build_needs(build.needs.map(&:name))
- return unless HasStatus::COMPLETED_STATUSES.include?(current_status)
+ return unless Ci::HasStatus::COMPLETED_STATUSES.include?(current_status)
process_build(build, current_status)
end
diff --git a/app/services/ci/process_pipeline_service.rb b/app/services/ci/process_pipeline_service.rb
index 80ebe5f5eb6..1f24dce0458 100644
--- a/app/services/ci/process_pipeline_service.rb
+++ b/app/services/ci/process_pipeline_service.rb
@@ -9,6 +9,8 @@ module Ci
end
def execute(trigger_build_ids = nil, initial_process: false)
+ increment_processing_counter
+
update_retried
if ::Gitlab::Ci::Features.atomic_processing?(pipeline.project)
@@ -22,6 +24,10 @@ module Ci
end
end
+ def metrics
+ @metrics ||= ::Gitlab::Ci::Pipeline::Metrics.new
+ end
+
private
# This method is for compatibility and data consistency and should be removed with 9.3 version of GitLab
@@ -43,5 +49,9 @@ module Ci
.update_all(retried: true) if latest_statuses.any?
end
# rubocop: enable CodeReuse/ActiveRecord
+
+ def increment_processing_counter
+ metrics.pipeline_processing_events_counter.increment
+ end
end
end
diff --git a/app/services/ci/register_job_service.rb b/app/services/ci/register_job_service.rb
index 17b9e56636b..3797ea1d96c 100644
--- a/app/services/ci/register_job_service.rb
+++ b/app/services/ci/register_job_service.rb
@@ -11,7 +11,7 @@ module Ci
METRICS_SHARD_TAG_PREFIX = 'metrics_shard::'.freeze
DEFAULT_METRICS_SHARD = 'default'.freeze
- Result = Struct.new(:build, :valid?)
+ Result = Struct.new(:build, :build_json, :valid?)
def initialize(runner)
@runner = runner
@@ -59,7 +59,7 @@ module Ci
end
register_failure
- Result.new(nil, valid)
+ Result.new(nil, nil, valid)
end
# rubocop: enable CodeReuse/ActiveRecord
@@ -71,7 +71,7 @@ module Ci
# In case when 2 runners try to assign the same build, second runner will be declined
# with StateMachines::InvalidTransition or StaleObjectError when doing run! or save method.
if assign_runner!(build, params)
- Result.new(build, true)
+ present_build!(build)
end
rescue StateMachines::InvalidTransition, ActiveRecord::StaleObjectError
# We are looping to find another build that is not conflicting
@@ -83,8 +83,10 @@ module Ci
# In case we hit the concurrency-access lock,
# we still have to return 409 in the end,
# to make sure that this is properly handled by runner.
- Result.new(nil, false)
+ Result.new(nil, nil, false)
rescue => ex
+ # If an error (e.g. GRPC::DeadlineExceeded) occurred constructing
+ # the result, consider this as a failure to be retried.
scheduler_failure!(build)
track_exception_for_build(ex, build)
@@ -92,6 +94,15 @@ module Ci
nil
end
+ # Force variables evaluation to occur now
+ def present_build!(build)
+ # We need to use the presenter here because Gitaly calls in the presenter
+ # may fail, and we need to ensure the response has been generated.
+ presented_build = ::Ci::BuildRunnerPresenter.new(build) # rubocop:disable CodeReuse/Presenter
+ build_json = ::API::Entities::JobRequest::Response.new(presented_build).to_json
+ Result.new(build, build_json, true)
+ end
+
def assign_runner!(build, params)
build.runner_id = runner.id
build.runner_session_attributes = params[:session] if params[:session].present?
diff --git a/app/services/ci/retry_build_service.rb b/app/services/ci/retry_build_service.rb
index 23507a31c72..60b3d28b0c5 100644
--- a/app/services/ci/retry_build_service.rb
+++ b/app/services/ci/retry_build_service.rb
@@ -34,10 +34,6 @@ module Ci
attributes[:user] = current_user
- # TODO: we can probably remove this logic
- # see: https://gitlab.com/gitlab-org/gitlab/-/issues/217930
- attributes[:scheduling_type] ||= build.find_legacy_scheduling_type
-
Ci::Build.transaction do
# mark all other builds of that name as retried
build.pipeline.builds.latest
@@ -59,7 +55,9 @@ module Ci
build = project.builds.new(attributes)
build.assign_attributes(::Gitlab::Ci::Pipeline::Seed::Build.environment_attributes_for(build))
build.retried = false
- build.save!
+ BulkInsertableAssociations.with_bulk_insert(enabled: ::Gitlab::Ci::Features.bulk_insert_on_create?(project)) do
+ build.save!
+ end
build
end
end
diff --git a/app/services/ci/unlock_artifacts_service.rb b/app/services/ci/unlock_artifacts_service.rb
new file mode 100644
index 00000000000..07faf90dd6d
--- /dev/null
+++ b/app/services/ci/unlock_artifacts_service.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+module Ci
+ class UnlockArtifactsService < ::BaseService
+ BATCH_SIZE = 100
+
+ def execute(ci_ref, before_pipeline = nil)
+ query = <<~SQL.squish
+ UPDATE "ci_pipelines"
+ SET "locked" = #{::Ci::Pipeline.lockeds[:unlocked]}
+ WHERE "ci_pipelines"."id" in (
+ #{collect_pipelines(ci_ref, before_pipeline).select(:id).to_sql}
+ LIMIT #{BATCH_SIZE}
+ FOR UPDATE SKIP LOCKED
+ )
+ RETURNING "ci_pipelines"."id";
+ SQL
+
+ loop do
+ break if ActiveRecord::Base.connection.exec_query(query).empty?
+ end
+ end
+
+ private
+
+ def collect_pipelines(ci_ref, before_pipeline)
+ pipeline_scope = ci_ref.pipelines
+ pipeline_scope = pipeline_scope.before_pipeline(before_pipeline) if before_pipeline
+
+ pipeline_scope.artifacts_locked
+ end
+ end
+end
diff --git a/app/services/clusters/create_service.rb b/app/services/clusters/create_service.rb
index 7b5bf6b32c2..6693a58683f 100644
--- a/app/services/clusters/create_service.rb
+++ b/app/services/clusters/create_service.rb
@@ -19,10 +19,6 @@ module Clusters
cluster = Clusters::Cluster.new(cluster_params)
- unless can_create_cluster?
- cluster.errors.add(:base, _('Instance does not support multiple Kubernetes clusters'))
- end
-
validate_management_project_permissions(cluster)
return cluster if cluster.errors.present?
@@ -55,16 +51,9 @@ module Clusters
end
end
- # EE would override this method
- def can_create_cluster?
- clusterable.clusters.empty?
- end
-
def validate_management_project_permissions(cluster)
Clusters::Management::ValidateManagementProjectPermissionsService.new(current_user)
.execute(cluster, params[:management_project_id])
end
end
end
-
-Clusters::CreateService.prepend_if_ee('EE::Clusters::CreateService')
diff --git a/app/services/clusters/parse_cluster_applications_artifact_service.rb b/app/services/clusters/parse_cluster_applications_artifact_service.rb
index 35fba5f47c7..6a0ca0ef9d0 100644
--- a/app/services/clusters/parse_cluster_applications_artifact_service.rb
+++ b/app/services/clusters/parse_cluster_applications_artifact_service.rb
@@ -5,7 +5,7 @@ module Clusters
include Gitlab::Utils::StrongMemoize
MAX_ACCEPTABLE_ARTIFACT_SIZE = 5.kilobytes
- RELEASE_NAMES = %w[prometheus].freeze
+ RELEASE_NAMES = %w[prometheus cilium].freeze
def initialize(job, current_user)
@job = job
diff --git a/app/services/concerns/exclusive_lease_guard.rb b/app/services/concerns/exclusive_lease_guard.rb
index 4678d051d29..a58e9aefcec 100644
--- a/app/services/concerns/exclusive_lease_guard.rb
+++ b/app/services/concerns/exclusive_lease_guard.rb
@@ -21,7 +21,7 @@ module ExclusiveLeaseGuard
lease = exclusive_lease.try_obtain
unless lease
- log_error('Cannot obtain an exclusive lease. There must be another instance already in execution.')
+ log_error("Cannot obtain an exclusive lease for #{self.class.name}. There must be another instance already in execution.")
return
end
diff --git a/app/services/concerns/incident_management/settings.rb b/app/services/concerns/incident_management/settings.rb
index 5f56d6e7f53..491bd4fa6bf 100644
--- a/app/services/concerns/incident_management/settings.rb
+++ b/app/services/concerns/incident_management/settings.rb
@@ -1,6 +1,8 @@
# frozen_string_literal: true
module IncidentManagement
module Settings
+ include Gitlab::Utils::StrongMemoize
+
def incident_management_setting
strong_memoize(:incident_management_setting) do
project.incident_management_setting ||
diff --git a/app/services/deploy_keys/collect_keys_service.rb b/app/services/deploy_keys/collect_keys_service.rb
new file mode 100644
index 00000000000..2ef49bf0f30
--- /dev/null
+++ b/app/services/deploy_keys/collect_keys_service.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+module DeployKeys
+ class CollectKeysService
+ def initialize(project, current_user)
+ @project = project
+ @current_user = current_user
+ end
+
+ def execute
+ return [] unless current_user && project && user_can_read_project
+
+ project.deploy_keys_projects
+ .with_deploy_keys
+ .with_write_access
+ .map(&:deploy_key)
+ end
+
+ private
+
+ def user_can_read_project
+ Ability.allowed?(current_user, :read_project, project)
+ end
+
+ attr_reader :project, :current_user
+ end
+end
diff --git a/app/services/event_create_service.rb b/app/services/event_create_service.rb
index 89c3225dbcd..ad36fe70b3a 100644
--- a/app/services/event_create_service.rb
+++ b/app/services/event_create_service.rb
@@ -11,44 +11,30 @@ class EventCreateService
IllegalActionError = Class.new(StandardError)
def open_issue(issue, current_user)
- create_resource_event(issue, current_user, :opened)
-
create_record_event(issue, current_user, :created)
end
def close_issue(issue, current_user)
- create_resource_event(issue, current_user, :closed)
-
create_record_event(issue, current_user, :closed)
end
def reopen_issue(issue, current_user)
- create_resource_event(issue, current_user, :reopened)
-
create_record_event(issue, current_user, :reopened)
end
def open_mr(merge_request, current_user)
- create_resource_event(merge_request, current_user, :opened)
-
create_record_event(merge_request, current_user, :created)
end
def close_mr(merge_request, current_user)
- create_resource_event(merge_request, current_user, :closed)
-
create_record_event(merge_request, current_user, :closed)
end
def reopen_mr(merge_request, current_user)
- create_resource_event(merge_request, current_user, :reopened)
-
create_record_event(merge_request, current_user, :reopened)
end
def merge_mr(merge_request, current_user)
- create_resource_event(merge_request, current_user, :merged)
-
create_record_event(merge_request, current_user, :merged)
end
@@ -97,23 +83,13 @@ class EventCreateService
end
def save_designs(current_user, create: [], update: [])
- created = create.group_by(&:project).flat_map do |project, designs|
- Feature.enabled?(:design_activity_events, project) ? designs : []
- end.to_set
- updated = update.group_by(&:project).flat_map do |project, designs|
- Feature.enabled?(:design_activity_events, project) ? designs : []
- end.to_set
- return [] if created.empty? && updated.empty?
-
- records = created.zip([:created].cycle) + updated.zip([:updated].cycle)
+ records = create.zip([:created].cycle) + update.zip([:updated].cycle)
+ return [] if records.empty?
create_record_events(records, current_user)
end
def destroy_designs(designs, current_user)
- designs = designs.select do |design|
- Feature.enabled?(:design_activity_events, design.project)
- end
return [] unless designs.present?
create_record_events(designs.zip([:destroyed].cycle), current_user)
@@ -127,8 +103,6 @@ class EventCreateService
#
# @return a tuple of event and either :found or :created
def wiki_event(wiki_page_meta, author, action)
- return unless Feature.enabled?(:wiki_events)
-
raise IllegalActionError, action unless Event::WIKI_ACTIONS.include?(action)
if duplicate = existing_wiki_event(wiki_page_meta, action)
@@ -142,9 +116,15 @@ class EventCreateService
event.update_columns(updated_at: time_stamp, created_at: time_stamp)
end
+ Gitlab::UsageDataCounters::TrackUniqueActions.track_action(event_action: action, event_target: wiki_page_meta.class, author_id: author.id)
+
event
end
+ def approve_mr(merge_request, current_user)
+ create_record_event(merge_request, current_user, :approved)
+ end
+
private
def existing_wiki_event(wiki_page_meta, action)
@@ -182,7 +162,13 @@ class EventCreateService
.merge(action: action, target_id: record.id, target_type: record.class.name)
end
- Event.insert_all(attribute_sets, returning: %w[id])
+ result = Event.insert_all(attribute_sets, returning: %w[id])
+
+ pairs.each do |record, status|
+ Gitlab::UsageDataCounters::TrackUniqueActions.track_action(event_action: status, event_target: record.class, author_id: current_user.id)
+ end
+
+ result
end
def create_push_event(service_class, project, current_user, push_data)
@@ -197,6 +183,8 @@ class EventCreateService
new_event
end
+ Gitlab::UsageDataCounters::TrackUniqueActions.track_action(event_action: :pushed, event_target: Project, author_id: current_user.id)
+
Users::LastPushEventService.new(current_user)
.cache_last_push_event(event)
@@ -225,18 +213,6 @@ class EventCreateService
{ resource_parent_attr => resource_parent.id }
end
-
- def create_resource_event(issuable, current_user, status)
- return unless state_change_tracking_enabled?(issuable)
-
- ResourceEvents::ChangeStateService.new(resource: issuable, user: current_user)
- .execute(status)
- end
-
- def state_change_tracking_enabled?(issuable)
- issuable&.respond_to?(:resource_state_events) &&
- ::Feature.enabled?(:track_resource_state_change_events, issuable&.project)
- end
end
EventCreateService.prepend_if_ee('EE::EventCreateService')
diff --git a/app/services/files/base_service.rb b/app/services/files/base_service.rb
index 39e614d6569..d42f718a272 100644
--- a/app/services/files/base_service.rb
+++ b/app/services/files/base_service.rb
@@ -25,7 +25,7 @@ module Files
return false unless commit_id
last_commit = Gitlab::Git::Commit
- .last_for_path(@start_project.repository, @start_branch, path)
+ .last_for_path(@start_project.repository, @start_branch, path, literal_pathspec: true)
return false unless last_commit
diff --git a/app/services/git/branch_push_service.rb b/app/services/git/branch_push_service.rb
index 5c1ee981d0c..2ec6ac99ece 100644
--- a/app/services/git/branch_push_service.rb
+++ b/app/services/git/branch_push_service.rb
@@ -29,6 +29,7 @@ module Git
perform_housekeeping
stop_environments
+ unlock_artifacts
true
end
@@ -60,6 +61,12 @@ module Git
Ci::StopEnvironmentsService.new(project, current_user).execute(branch_name)
end
+ def unlock_artifacts
+ return unless removing_branch?
+
+ Ci::RefDeleteUnlockArtifactsWorker.perform_async(project.id, current_user.id, ref)
+ end
+
def execute_related_hooks
BranchHooksService.new(project, current_user, params).execute
end
diff --git a/app/services/git/tag_push_service.rb b/app/services/git/tag_push_service.rb
index 9a266f7d74c..120c4cde94b 100644
--- a/app/services/git/tag_push_service.rb
+++ b/app/services/git/tag_push_service.rb
@@ -10,7 +10,25 @@ module Git
project.repository.before_push_tag
TagHooksService.new(project, current_user, params).execute
+ unlock_artifacts
+
true
end
+
+ private
+
+ def unlock_artifacts
+ return unless removing_tag?
+
+ Ci::RefDeleteUnlockArtifactsWorker.perform_async(project.id, current_user.id, ref)
+ end
+
+ def removing_tag?
+ Gitlab::Git.blank_ref?(newrev)
+ end
+
+ def tag_name
+ Gitlab::Git.ref_name(ref)
+ end
end
end
diff --git a/app/services/git/wiki_push_service.rb b/app/services/git/wiki_push_service.rb
index 8bdbc28f3e8..b3937a10a70 100644
--- a/app/services/git/wiki_push_service.rb
+++ b/app/services/git/wiki_push_service.rb
@@ -23,7 +23,7 @@ module Git
end
def can_process_wiki_events?
- Feature.enabled?(:wiki_events) && Feature.enabled?(:wiki_events_on_git_push, project)
+ Feature.enabled?(:wiki_events_on_git_push, project)
end
def push_changes
diff --git a/app/services/gpg_keys/destroy_service.rb b/app/services/gpg_keys/destroy_service.rb
new file mode 100644
index 00000000000..cecbfe26611
--- /dev/null
+++ b/app/services/gpg_keys/destroy_service.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+module GpgKeys
+ class DestroyService < Keys::BaseService
+ def execute(key)
+ key.destroy
+ end
+ end
+end
diff --git a/app/services/groups/create_service.rb b/app/services/groups/create_service.rb
index eb1b8d4fcc0..ce583095168 100644
--- a/app/services/groups/create_service.rb
+++ b/app/services/groups/create_service.rb
@@ -28,7 +28,11 @@ module Groups
@group.build_chat_team(name: response['name'], team_id: response['id'])
end
- @group.add_owner(current_user) if @group.save
+ if @group.save
+ @group.add_owner(current_user)
+ add_settings_record
+ end
+
@group
end
@@ -79,6 +83,10 @@ module Groups
params[:visibility_level] = Gitlab::CurrentSettings.current_application_settings.default_group_visibility
end
+
+ def add_settings_record
+ @group.create_namespace_settings
+ end
end
end
diff --git a/app/services/groups/update_shared_runners_service.rb b/app/services/groups/update_shared_runners_service.rb
new file mode 100644
index 00000000000..63f57104510
--- /dev/null
+++ b/app/services/groups/update_shared_runners_service.rb
@@ -0,0 +1,50 @@
+# frozen_string_literal: true
+
+module Groups
+ class UpdateSharedRunnersService < Groups::BaseService
+ def execute
+ return error('Operation not allowed', 403) unless can?(current_user, :admin_group, group)
+
+ validate_params
+
+ enable_or_disable_shared_runners!
+ allow_or_disallow_descendants_override_disabled_shared_runners!
+
+ success
+
+ rescue Group::UpdateSharedRunnersError => error
+ error(error.message)
+ end
+
+ private
+
+ def validate_params
+ if Gitlab::Utils.to_boolean(params[:shared_runners_enabled]) && !params[:allow_descendants_override_disabled_shared_runners].nil?
+ raise Group::UpdateSharedRunnersError, 'Cannot set shared_runners_enabled to true and allow_descendants_override_disabled_shared_runners'
+ end
+ end
+
+ def enable_or_disable_shared_runners!
+ return if params[:shared_runners_enabled].nil?
+
+ if Gitlab::Utils.to_boolean(params[:shared_runners_enabled])
+ group.enable_shared_runners!
+ else
+ group.disable_shared_runners!
+ end
+ end
+
+ def allow_or_disallow_descendants_override_disabled_shared_runners!
+ return if params[:allow_descendants_override_disabled_shared_runners].nil?
+
+ # Needs to reset group because if both params are present could result in error
+ group.reset
+
+ if Gitlab::Utils.to_boolean(params[:allow_descendants_override_disabled_shared_runners])
+ group.allow_descendants_override_disabled_shared_runners!
+ else
+ group.disallow_descendants_override_disabled_shared_runners!
+ end
+ end
+ end
+end
diff --git a/app/services/import/bitbucket_server_service.rb b/app/services/import/bitbucket_server_service.rb
new file mode 100644
index 00000000000..86e8215821e
--- /dev/null
+++ b/app/services/import/bitbucket_server_service.rb
@@ -0,0 +1,104 @@
+# frozen_string_literal: true
+
+module Import
+ class BitbucketServerService < Import::BaseService
+ attr_reader :client, :params, :current_user
+
+ def execute(credentials)
+ if blocked_url?
+ return log_and_return_error("Invalid URL: #{url}", :bad_request)
+ end
+
+ unless authorized?
+ return log_and_return_error("You don't have permissions to create this project", :unauthorized)
+ end
+
+ unless repo
+ return log_and_return_error("Project %{project_repo} could not be found" % { project_repo: "#{project_key}/#{repo_slug}" }, :unprocessable_entity)
+ end
+
+ project = create_project(credentials)
+
+ if project.persisted?
+ success(project)
+ else
+ log_and_return_error(project_save_error(project), :unprocessable_entity)
+ end
+ rescue BitbucketServer::Connection::ConnectionError => e
+ log_and_return_error("Import failed due to a BitBucket Server error: #{e}", :bad_request)
+ end
+
+ private
+
+ def create_project(credentials)
+ Gitlab::BitbucketServerImport::ProjectCreator.new(
+ project_key,
+ repo_slug,
+ repo,
+ project_name,
+ target_namespace,
+ current_user,
+ credentials
+ ).execute
+ end
+
+ def repo
+ @repo ||= client.repo(project_key, repo_slug)
+ end
+
+ def project_name
+ @project_name ||= params[:new_name].presence || repo.name
+ end
+
+ def namespace_path
+ @namespace_path ||= params[:new_namespace].presence || current_user.namespace_path
+ end
+
+ def target_namespace
+ @target_namespace ||= find_or_create_namespace(namespace_path, current_user.namespace_path)
+ end
+
+ def repo_slug
+ @repo_slug ||= params[:bitbucket_server_repo]
+ end
+
+ def project_key
+ @project_key ||= params[:bitbucket_server_project]
+ end
+
+ def url
+ @url ||= params[:bitbucket_server_url]
+ end
+
+ def authorized?
+ can?(current_user, :create_projects, target_namespace)
+ end
+
+ def allow_local_requests?
+ Gitlab::CurrentSettings.allow_local_requests_from_web_hooks_and_services?
+ end
+
+ def blocked_url?
+ Gitlab::UrlBlocker.blocked_url?(
+ url,
+ {
+ allow_localhost: allow_local_requests?,
+ allow_local_network: allow_local_requests?,
+ schemes: %w(http https)
+ }
+ )
+ end
+
+ def log_and_return_error(message, error_type)
+ log_error(message)
+ error(_(message), error_type)
+ end
+
+ def log_error(message)
+ Gitlab::Import::Logger.error(
+ message: 'Import failed due to a BitBucket Server error',
+ error: message
+ )
+ end
+ end
+end
diff --git a/app/services/incident_management/create_incident_label_service.rb b/app/services/incident_management/create_incident_label_service.rb
new file mode 100644
index 00000000000..dbd0d78fa3c
--- /dev/null
+++ b/app/services/incident_management/create_incident_label_service.rb
@@ -0,0 +1,40 @@
+# frozen_string_literal: true
+
+module IncidentManagement
+ class CreateIncidentLabelService < BaseService
+ LABEL_PROPERTIES = {
+ title: 'incident',
+ color: '#CC0033',
+ description: <<~DESCRIPTION.chomp
+ Denotes a disruption to IT services and \
+ the associated issues require immediate attention
+ DESCRIPTION
+ }.freeze
+
+ def execute
+ label = Labels::FindOrCreateService
+ .new(current_user, project, **LABEL_PROPERTIES)
+ .execute
+
+ if label.invalid?
+ log_invalid_label_info(label)
+ return ServiceResponse.error(payload: { label: label }, message: full_error_message(label))
+ end
+
+ ServiceResponse.success(payload: { label: label })
+ end
+
+ private
+
+ def log_invalid_label_info(label)
+ log_info <<~TEXT.chomp
+ Cannot create incident label "#{label.title}" \
+ for "#{label.project.full_name}": #{full_error_message(label)}.
+ TEXT
+ end
+
+ def full_error_message(label)
+ label.errors.full_messages.to_sentence
+ end
+ end
+end
diff --git a/app/services/incident_management/create_issue_service.rb b/app/services/incident_management/create_issue_service.rb
index 4b59dc64cec..5e1e0863115 100644
--- a/app/services/incident_management/create_issue_service.rb
+++ b/app/services/incident_management/create_issue_service.rb
@@ -4,21 +4,12 @@ module IncidentManagement
class CreateIssueService < BaseService
include Gitlab::Utils::StrongMemoize
- INCIDENT_LABEL = {
- title: 'incident',
- color: '#CC0033',
- description: <<~DESCRIPTION.chomp
- Denotes a disruption to IT services and \
- the associated issues require immediate attention
- DESCRIPTION
- }.freeze
-
- def initialize(project, params, user = User.alert_bot)
- super(project, user, params)
+ def initialize(project, params)
+ super(project, User.alert_bot, params)
end
- def execute(skip_settings_check: false)
- return error_with('setting disabled') unless skip_settings_check || incident_management_setting.create_issue?
+ def execute
+ return error_with('setting disabled') unless incident_management_setting.create_issue?
return error_with('invalid alert') unless alert.valid?
issue = create_issue
@@ -30,26 +21,19 @@ module IncidentManagement
private
def create_issue
- issue = do_create_issue(label_ids: issue_label_ids)
+ label_result = find_or_create_incident_label
- # Create an unlabelled issue if we couldn't create the issue
- # due to labels errors.
+ # Create an unlabelled issue if we couldn't create the label
+ # due to a race condition.
# See https://gitlab.com/gitlab-org/gitlab-foss/issues/65042
- if issue.errors.include?(:labels)
- log_label_error(issue)
- issue = do_create_issue
- end
-
- issue
- end
+ extra_params = label_result.success? ? { label_ids: [label_result.payload[:label].id] } : {}
- def do_create_issue(**params)
Issues::CreateService.new(
project,
current_user,
title: issue_title,
description: issue_description,
- **params
+ **extra_params
).execute
end
@@ -67,16 +51,8 @@ module IncidentManagement
].compact.join(horizontal_line)
end
- def issue_label_ids
- [
- find_or_create_label(**INCIDENT_LABEL)
- ].compact.map(&:id)
- end
-
- def find_or_create_label(**params)
- Labels::FindOrCreateService
- .new(current_user, project, **params)
- .execute
+ def find_or_create_incident_label
+ IncidentManagement::CreateIncidentLabelService.new(project, current_user).execute
end
def alert_summary
@@ -108,15 +84,6 @@ module IncidentManagement
issue.errors.full_messages.to_sentence
end
- def log_label_error(issue)
- log_info <<~TEXT.chomp
- Cannot create incident issue with labels \
- #{issue.labels.map(&:title).inspect} \
- for "#{project.full_name}": #{issue.errors.full_messages.to_sentence}.
- Retrying without labels.
- TEXT
- end
-
def error_with(message)
log_error(%{Cannot create incident issue for "#{project.full_name}": #{message}})
diff --git a/app/services/incident_management/pager_duty/create_incident_issue_service.rb b/app/services/incident_management/pager_duty/create_incident_issue_service.rb
new file mode 100644
index 00000000000..ee0feb49e0d
--- /dev/null
+++ b/app/services/incident_management/pager_duty/create_incident_issue_service.rb
@@ -0,0 +1,72 @@
+# frozen_string_literal: true
+
+module IncidentManagement
+ module PagerDuty
+ class CreateIncidentIssueService < BaseService
+ include IncidentManagement::Settings
+
+ def initialize(project, incident_payload)
+ super(project, User.alert_bot, incident_payload)
+ end
+
+ def execute
+ return forbidden unless webhook_available?
+
+ issue = create_issue
+ return error(issue.errors.full_messages.to_sentence, issue) unless issue.valid?
+
+ success(issue)
+ end
+
+ private
+
+ alias_method :incident_payload, :params
+
+ def create_issue
+ label_result = find_or_create_incident_label
+
+ # Create an unlabelled issue if we couldn't create the label
+ # due to a race condition.
+ # See https://gitlab.com/gitlab-org/gitlab-foss/issues/65042
+ extra_params = label_result.success? ? { label_ids: [label_result.payload[:label].id] } : {}
+
+ Issues::CreateService.new(
+ project,
+ current_user,
+ title: issue_title,
+ description: issue_description,
+ **extra_params
+ ).execute
+ end
+
+ def webhook_available?
+ Feature.enabled?(:pagerduty_webhook, project) &&
+ incident_management_setting.pagerduty_active?
+ end
+
+ def forbidden
+ ServiceResponse.error(message: 'Forbidden', http_status: :forbidden)
+ end
+
+ def find_or_create_incident_label
+ ::IncidentManagement::CreateIncidentLabelService.new(project, current_user).execute
+ end
+
+ def issue_title
+ incident_payload['title']
+ end
+
+ def issue_description
+ Gitlab::IncidentManagement::PagerDuty::IncidentIssueDescription.new(incident_payload).to_s
+ end
+
+ def success(issue)
+ ServiceResponse.success(payload: { issue: issue })
+ end
+
+ def error(message, issue = nil)
+ ServiceResponse.error(payload: { issue: issue }, message: message)
+ end
+ end
+ end
+end
diff --git a/app/services/incident_management/pager_duty/process_webhook_service.rb b/app/services/incident_management/pager_duty/process_webhook_service.rb
new file mode 100644
index 00000000000..5dd3186694a
--- /dev/null
+++ b/app/services/incident_management/pager_duty/process_webhook_service.rb
@@ -0,0 +1,71 @@
+# frozen_string_literal: true
+
+module IncidentManagement
+ module PagerDuty
+ class ProcessWebhookService < BaseService
+ include Gitlab::Utils::StrongMemoize
+ include IncidentManagement::Settings
+
+ # https://developer.pagerduty.com/docs/webhooks/webhook-behavior/#size-limit
+ PAGER_DUTY_PAYLOAD_SIZE_LIMIT = 55.kilobytes
+
+ # https://developer.pagerduty.com/docs/webhooks/v2-overview/#webhook-types
+ PAGER_DUTY_PROCESSABLE_EVENT_TYPES = %w(incident.trigger).freeze
+
+ def execute(token)
+ return forbidden unless webhook_setting_active?
+ return unauthorized unless valid_token?(token)
+ return bad_request unless valid_payload_size?
+
+ process_incidents
+
+ accepted
+ end
+
+ private
+
+ def process_incidents
+ pager_duty_processable_events.each do |event|
+ ::IncidentManagement::PagerDuty::ProcessIncidentWorker.perform_async(project.id, event['incident'])
+ end
+ end
+
+ def pager_duty_processable_events
+ strong_memoize(:pager_duty_processable_events) do
+ ::PagerDuty::WebhookPayloadParser
+ .call(params.to_h)
+ .filter { |msg| msg['event'].in?(PAGER_DUTY_PROCESSABLE_EVENT_TYPES) }
+ end
+ end
+
+ def webhook_setting_active?
+ Feature.enabled?(:pagerduty_webhook, project) &&
+ incident_management_setting.pagerduty_active?
+ end
+
+ def valid_token?(token)
+ token && incident_management_setting.pagerduty_token == token
+ end
+
+ def valid_payload_size?
+ Gitlab::Utils::DeepSize.new(params, max_size: PAGER_DUTY_PAYLOAD_SIZE_LIMIT).valid?
+ end
+
+ def accepted
+ ServiceResponse.success(http_status: :accepted)
+ end
+
+ def forbidden
+ ServiceResponse.error(message: 'Forbidden', http_status: :forbidden)
+ end
+
+ def unauthorized
+ ServiceResponse.error(message: 'Unauthorized', http_status: :unauthorized)
+ end
+
+ def bad_request
+ ServiceResponse.error(message: 'Bad Request', http_status: :bad_request)
+ end
+ end
+ end
+end
diff --git a/app/services/issuable/bulk_update_service.rb b/app/services/issuable/bulk_update_service.rb
index 2902385da4a..79be771b3fb 100644
--- a/app/services/issuable/bulk_update_service.rb
+++ b/app/services/issuable/bulk_update_service.rb
@@ -11,40 +11,29 @@ module Issuable
end
def execute(type)
- model_class = type.classify.constantize
- update_class = type.classify.pluralize.constantize::UpdateService
-
ids = params.delete(:issuable_ids).split(",")
- items = find_issuables(parent, model_class, ids)
+ set_update_params(type)
+ items = update_issuables(type, ids)
+ response_success(payload: { count: items.count })
+ rescue ArgumentError => e
+ response_error(e.message, 422)
+ end
+
+ private
+
+ def set_update_params(type)
params.slice!(*permitted_attrs(type))
params.delete_if { |k, v| v.blank? }
if params[:assignee_ids] == [IssuableFinder::Params::NONE.to_s]
params[:assignee_ids] = []
end
-
- items.each do |issuable|
- next unless can?(current_user, :"update_#{type}", issuable)
-
- update_class.new(issuable.issuing_parent, current_user, params).execute(issuable)
- end
-
- {
- count: items.count,
- success: !items.count.zero?
- }
end
- private
-
def permitted_attrs(type)
attrs = %i(state_event milestone_id add_label_ids remove_label_ids subscription_event)
- issuable_specific_attrs(type, attrs)
- end
-
- def issuable_specific_attrs(type, attrs)
if type == 'issue' || type == 'merge_request'
attrs.push(:assignee_ids)
else
@@ -52,6 +41,20 @@ module Issuable
end
end
+ def update_issuables(type, ids)
+ model_class = type.classify.constantize
+ update_class = type.classify.pluralize.constantize::UpdateService
+ items = find_issuables(parent, model_class, ids)
+
+ items.each do |issuable|
+ next unless can?(current_user, :"update_#{type}", issuable)
+
+ update_class.new(issuable.issuing_parent, current_user, params).execute(issuable)
+ end
+
+ items
+ end
+
def find_issuables(parent, model_class, ids)
if parent.is_a?(Project)
model_class.id_in(ids).of_projects(parent)
@@ -59,6 +62,14 @@ module Issuable
model_class.id_in(ids).of_projects(parent.all_projects)
end
end
+
+ def response_success(message: nil, payload: nil)
+ ServiceResponse.success(message: message, payload: payload)
+ end
+
+ def response_error(message, http_status)
+ ServiceResponse.error(message: message, http_status: http_status)
+ end
end
end
diff --git a/app/services/issuable_base_service.rb b/app/services/issuable_base_service.rb
index 38b10996f44..65a73dadc2e 100644
--- a/app/services/issuable_base_service.rb
+++ b/app/services/issuable_base_service.rb
@@ -97,29 +97,6 @@ class IssuableBaseService < BaseService
params.delete(label_key) if params[label_key].nil?
end
- def filter_labels_in_param(key)
- return if params[key].to_a.empty?
-
- params[key] = available_labels.id_in(params[key]).pluck_primary_key
- end
-
- def find_or_create_label_ids
- labels = params.delete(:labels)
-
- return unless labels
-
- params[:label_ids] = labels.map do |label_name|
- label = Labels::FindOrCreateService.new(
- current_user,
- parent,
- title: label_name.strip,
- available_labels: available_labels
- ).execute
-
- label.try(:id)
- end.compact
- end
-
def labels_service
@labels_service ||= ::Labels::AvailableLabelsService.new(current_user, parent, params)
end
@@ -138,7 +115,7 @@ class IssuableBaseService < BaseService
new_label_ids.uniq
end
- def handle_quick_actions_on_create(issuable)
+ def handle_quick_actions(issuable)
merge_quick_actions_into_params!(issuable)
end
@@ -146,17 +123,21 @@ class IssuableBaseService < BaseService
original_description = params.fetch(:description, issuable.description)
description, command_params =
- QuickActions::InterpretService.new(project, current_user)
+ QuickActions::InterpretService.new(project, current_user, quick_action_options)
.execute(original_description, issuable, only: only)
# Avoid a description already set on an issuable to be overwritten by a nil
- params[:description] = description if description
+ params[:description] = description if description && description != original_description
params.merge!(command_params)
end
+ def quick_action_options
+ {}
+ end
+
def create(issuable)
- handle_quick_actions_on_create(issuable)
+ handle_quick_actions(issuable)
filter_params(issuable)
params.delete(:state_event)
@@ -200,11 +181,13 @@ class IssuableBaseService < BaseService
end
def update(issuable)
+ handle_quick_actions(issuable)
+ filter_params(issuable)
+
change_state(issuable)
change_subscription(issuable)
change_todo(issuable)
toggle_award(issuable)
- filter_params(issuable)
old_associations = associations_before_update(issuable)
label_ids = process_label_ids(params, existing_label_ids: issuable.label_ids)
diff --git a/app/services/issues/move_service.rb b/app/services/issues/move_service.rb
index 2409396c1ac..ce1466307e1 100644
--- a/app/services/issues/move_service.rb
+++ b/app/services/issues/move_service.rb
@@ -19,11 +19,22 @@ module Issues
notify_participants
+ # Updates old issue sent notifications allowing
+ # to receive service desk emails on the new moved issue.
+ update_service_desk_sent_notifications
+
new_entity
end
private
+ def update_service_desk_sent_notifications
+ return unless original_entity.from_service_desk?
+
+ original_entity
+ .sent_notifications.update_all(project_id: new_entity.project_id, noteable_id: new_entity.id)
+ end
+
def update_old_entity
super
diff --git a/app/services/jira/requests/base.rb b/app/services/jira/requests/base.rb
index 7521c7610cb..7c6db372257 100644
--- a/app/services/jira/requests/base.rb
+++ b/app/services/jira/requests/base.rb
@@ -5,28 +5,32 @@ module Jira
class Base
include ProjectServicesLoggable
- PER_PAGE = 50
+ JIRA_API_VERSION = 2
- attr_reader :jira_service, :project, :limit, :start_at, :query
-
- def initialize(jira_service, limit: PER_PAGE, start_at: 0, query: nil)
+ def initialize(jira_service, params = {})
@project = jira_service&.project
@jira_service = jira_service
-
- @limit = limit
- @start_at = start_at
- @query = query
end
def execute
return ServiceResponse.error(message: _('Jira service not configured.')) unless jira_service&.active?
- return ServiceResponse.success(payload: empty_payload) if limit.to_i <= 0
request
end
+ def base_api_url
+ "/rest/api/#{api_version}"
+ end
+
private
+ attr_reader :jira_service, :project
+
+ # override this method in the specific request class implementation if a differnt API version is required
+ def api_version
+ JIRA_API_VERSION
+ end
+
def client
@client ||= jira_service.client
end
diff --git a/app/services/jira/requests/projects.rb b/app/services/jira/requests/projects.rb
deleted file mode 100644
index da464503211..00000000000
--- a/app/services/jira/requests/projects.rb
+++ /dev/null
@@ -1,32 +0,0 @@
-# frozen_string_literal: true
-
-module Jira
- module Requests
- class Projects < Base
- extend ::Gitlab::Utils::Override
-
- private
-
- override :url
- def url
- '/rest/api/2/project/search?query=%{query}&maxResults=%{limit}&startAt=%{start_at}' %
- { query: CGI.escape(query.to_s), limit: limit.to_i, start_at: start_at.to_i }
- end
-
- override :build_service_response
- def build_service_response(response)
- return ServiceResponse.success(payload: empty_payload) unless response['values'].present?
-
- ServiceResponse.success(payload: { projects: map_projects(response), is_last: response['isLast'] })
- end
-
- def map_projects(response)
- response['values'].map { |v| JIRA::Resource::Project.build(client, v) }
- end
-
- def empty_payload
- { projects: [], is_last: true }
- end
- end
- end
-end
diff --git a/app/services/jira/requests/projects/list_service.rb b/app/services/jira/requests/projects/list_service.rb
new file mode 100644
index 00000000000..8ecfd358ffb
--- /dev/null
+++ b/app/services/jira/requests/projects/list_service.rb
@@ -0,0 +1,47 @@
+# frozen_string_literal: true
+
+module Jira
+ module Requests
+ module Projects
+ class ListService < Base
+ extend ::Gitlab::Utils::Override
+
+ def initialize(jira_service, params: {})
+ super(jira_service, params)
+
+ @query = params[:query]
+ end
+
+ private
+
+ attr_reader :query
+
+ override :url
+ def url
+ "#{base_api_url}/project"
+ end
+
+ override :build_service_response
+ def build_service_response(response)
+ return ServiceResponse.success(payload: empty_payload) unless response.present?
+
+ ServiceResponse.success(payload: { projects: map_projects(response), is_last: true })
+ end
+
+ def map_projects(response)
+ response.map { |v| JIRA::Resource::Project.build(client, v) }.select(&method(:match_query?))
+ end
+
+ def match_query?(jira_project)
+ query = query.to_s.downcase
+
+ jira_project&.key&.downcase&.include?(query) || jira_project&.name&.downcase&.include?(query)
+ end
+
+ def empty_payload
+ { projects: [], is_last: true }
+ end
+ end
+ end
+ end
+end
diff --git a/app/services/jira_import/start_import_service.rb b/app/services/jira_import/start_import_service.rb
index a06cc6df719..f85f686c61a 100644
--- a/app/services/jira_import/start_import_service.rb
+++ b/app/services/jira_import/start_import_service.rb
@@ -2,23 +2,39 @@
module JiraImport
class StartImportService
- attr_reader :user, :project, :jira_project_key
+ attr_reader :user, :project, :jira_project_key, :users_mapping
- def initialize(user, project, jira_project_key)
+ def initialize(user, project, jira_project_key, users_mapping)
@user = user
@project = project
@jira_project_key = jira_project_key
+ @users_mapping = users_mapping
end
def execute
validation_response = validate
return validation_response if validation_response&.error?
+ store_users_mapping
create_and_schedule_import
end
private
+ def store_users_mapping
+ return if users_mapping.blank?
+
+ mapping = users_mapping.map do |map|
+ next if !map[:jira_account_id] || !map[:gitlab_id]
+
+ [map[:jira_account_id], map[:gitlab_id]]
+ end.compact.to_h
+
+ return if mapping.blank?
+
+ Gitlab::JiraImport.cache_users_mapping(project.id, mapping)
+ end
+
def create_and_schedule_import
jira_import = build_jira_import
project.import_type = 'jira'
diff --git a/app/services/jira_import/users_mapper.rb b/app/services/jira_import/users_mapper.rb
index 31a3f721556..c3cbeb157bd 100644
--- a/app/services/jira_import/users_mapper.rb
+++ b/app/services/jira_import/users_mapper.rb
@@ -14,9 +14,8 @@ module JiraImport
{
jira_account_id: jira_user['accountId'],
jira_display_name: jira_user['displayName'],
- jira_email: jira_user['emailAddress'],
- gitlab_id: match_user(jira_user)
- }
+ jira_email: jira_user['emailAddress']
+ }.merge(match_user(jira_user))
end
end
@@ -25,7 +24,7 @@ module JiraImport
# TODO: Matching user by email and displayName will be done as the part
# of follow-up issue: https://gitlab.com/gitlab-org/gitlab/-/issues/219023
def match_user(jira_user)
- nil
+ { gitlab_id: nil, gitlab_username: nil, gitlab_name: nil }
end
end
end
diff --git a/app/services/labels/available_labels_service.rb b/app/services/labels/available_labels_service.rb
index 979964e09fd..3b226f39d04 100644
--- a/app/services/labels/available_labels_service.rb
+++ b/app/services/labels/available_labels_service.rb
@@ -34,7 +34,7 @@ module Labels
return [] if ids.empty?
# rubocop:disable CodeReuse/ActiveRecord
- existing_ids = available_labels.by_ids(ids).pluck(:id)
+ existing_ids = available_labels.id_in(ids).pluck(:id)
# rubocop:enable CodeReuse/ActiveRecord
ids.map(&:to_i) & existing_ids
end
diff --git a/app/services/labels/transfer_service.rb b/app/services/labels/transfer_service.rb
index e6f9cf35fcb..a05090d6bfb 100644
--- a/app/services/labels/transfer_service.rb
+++ b/app/services/labels/transfer_service.rb
@@ -15,14 +15,18 @@ module Labels
def execute
return unless old_group.present?
+ # rubocop: disable CodeReuse/ActiveRecord
+ link_ids = group_labels_applied_to_issues.pluck("label_links.id") +
+ group_labels_applied_to_merge_requests.pluck("label_links.id")
+ # rubocop: disable CodeReuse/ActiveRecord
+
Label.transaction do
labels_to_transfer.find_each do |label|
new_label_id = find_or_create_label!(label)
next if new_label_id == label.id
- update_label_links(group_labels_applied_to_issues, old_label_id: label.id, new_label_id: new_label_id)
- update_label_links(group_labels_applied_to_merge_requests, old_label_id: label.id, new_label_id: new_label_id)
+ update_label_links(link_ids, old_label_id: label.id, new_label_id: new_label_id)
update_label_priorities(old_label_id: label.id, new_label_id: new_label_id)
end
end
@@ -46,20 +50,20 @@ module Labels
# rubocop: disable CodeReuse/ActiveRecord
def group_labels_applied_to_issues
- Label.joins(:issues)
+ @group_labels_applied_to_issues ||= Label.joins(:issues)
.where(
issues: { project_id: project.id },
- labels: { type: 'GroupLabel', group_id: old_group.self_and_ancestors }
+ labels: { group_id: old_group.self_and_ancestors }
)
end
# rubocop: enable CodeReuse/ActiveRecord
# rubocop: disable CodeReuse/ActiveRecord
def group_labels_applied_to_merge_requests
- Label.joins(:merge_requests)
+ @group_labels_applied_to_merge_requests ||= Label.joins(:merge_requests)
.where(
merge_requests: { target_project_id: project.id },
- labels: { type: 'GroupLabel', group_id: old_group.self_and_ancestors }
+ labels: { group_id: old_group.self_and_ancestors }
)
end
# rubocop: enable CodeReuse/ActiveRecord
@@ -72,14 +76,7 @@ module Labels
end
# rubocop: disable CodeReuse/ActiveRecord
- def update_label_links(labels, old_label_id:, new_label_id:)
- # use 'labels' relation to get label_link ids only of issues/MRs
- # in the project being transferred.
- # IDs are fetched in a separate query because MySQL doesn't
- # allow referring of 'label_links' table in UPDATE query:
- # https://gitlab.com/gitlab-org/gitlab-foss/-/jobs/62435068
- link_ids = labels.pluck('label_links.id')
-
+ def update_label_links(link_ids, old_label_id:, new_label_id:)
LabelLink.where(id: link_ids, label_id: old_label_id)
.update_all(label_id: new_label_id)
end
diff --git a/app/services/members/create_service.rb b/app/services/members/create_service.rb
index 0b729981a93..610288c5e76 100644
--- a/app/services/members/create_service.rb
+++ b/app/services/members/create_service.rb
@@ -22,7 +22,7 @@ module Members
errors = []
members.each do |member|
- if member.errors.any?
+ if member.invalid?
current_error =
# Invited users may not have an associated user
if member.user.present?
diff --git a/app/services/members/destroy_service.rb b/app/services/members/destroy_service.rb
index 20f64a99ad7..fdd43260521 100644
--- a/app/services/members/destroy_service.rb
+++ b/app/services/members/destroy_service.rb
@@ -2,8 +2,8 @@
module Members
class DestroyService < Members::BaseService
- def execute(member, skip_authorization: false, skip_subresources: false)
- raise Gitlab::Access::AccessDeniedError unless skip_authorization || can_destroy_member?(member)
+ def execute(member, skip_authorization: false, skip_subresources: false, unassign_issuables: false, destroy_bot: false)
+ raise Gitlab::Access::AccessDeniedError unless skip_authorization || authorized?(member, destroy_bot)
@skip_auth = skip_authorization
@@ -19,6 +19,7 @@ module Members
delete_subresources(member) unless skip_subresources
enqueue_delete_todos(member)
+ enqueue_unassign_issuables(member) if unassign_issuables
after_execute(member: member)
@@ -27,6 +28,12 @@ module Members
private
+ def authorized?(member, destroy_bot)
+ return can_destroy_bot_member?(member) if destroy_bot
+
+ can_destroy_member?(member)
+ end
+
def delete_subresources(member)
return unless member.is_a?(GroupMember) && member.user && member.group
@@ -54,6 +61,10 @@ module Members
can?(current_user, destroy_member_permission(member), member)
end
+ def can_destroy_bot_member?(member)
+ can?(current_user, destroy_bot_member_permission(member), member)
+ end
+
def destroy_member_permission(member)
case member
when GroupMember
@@ -64,6 +75,20 @@ module Members
raise "Unknown member type: #{member}!"
end
end
+
+ def destroy_bot_member_permission(member)
+ raise "Unsupported bot member type: #{member}" unless member.is_a?(ProjectMember)
+
+ :destroy_project_bot_member
+ end
+
+ def enqueue_unassign_issuables(member)
+ source_type = member.is_a?(GroupMember) ? 'Group' : 'Project'
+
+ member.run_after_commit_or_now do
+ MembersDestroyer::UnassignIssuablesWorker.perform_async(member.user_id, member.source_id, source_type)
+ end
+ end
end
end
diff --git a/app/services/members/unassign_issuables_service.rb b/app/services/members/unassign_issuables_service.rb
new file mode 100644
index 00000000000..95e07deb761
--- /dev/null
+++ b/app/services/members/unassign_issuables_service.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+module Members
+ class UnassignIssuablesService
+ attr_reader :user, :entity
+
+ def initialize(user, entity)
+ @user = user
+ @entity = entity
+ end
+
+ def execute
+ return unless entity && user
+
+ project_ids = entity.is_a?(Group) ? entity.all_projects.select(:id) : [entity.id]
+
+ user.issue_assignees.on_issues(Issue.in_projects(project_ids).select(:id)).delete_all
+ user.merge_request_assignees.in_projects(project_ids).delete_all
+
+ user.invalidate_cache_counts
+ end
+ end
+end
diff --git a/app/services/merge_requests/approval_service.rb b/app/services/merge_requests/approval_service.rb
new file mode 100644
index 00000000000..150ec85fca9
--- /dev/null
+++ b/app/services/merge_requests/approval_service.rb
@@ -0,0 +1,56 @@
+# frozen_string_literal: true
+
+module MergeRequests
+ class ApprovalService < MergeRequests::BaseService
+ def execute(merge_request)
+ return unless can_be_approved?(merge_request)
+
+ approval = merge_request.approvals.new(user: current_user)
+
+ return success unless save_approval(approval)
+
+ reset_approvals_cache(merge_request)
+ create_event(merge_request)
+ create_approval_note(merge_request)
+ mark_pending_todos_as_done(merge_request)
+ execute_approval_hooks(merge_request, current_user)
+
+ success
+ end
+
+ private
+
+ def can_be_approved?(merge_request)
+ current_user.can?(:approve_merge_request, merge_request)
+ end
+
+ def reset_approvals_cache(merge_request)
+ merge_request.approvals.reset
+ end
+
+ def execute_approval_hooks(merge_request, current_user)
+ # Only one approval is required for a merge request to be approved
+ execute_hooks(merge_request, 'approved')
+ end
+
+ def save_approval(approval)
+ Approval.safe_ensure_unique do
+ approval.save
+ end
+ end
+
+ def create_approval_note(merge_request)
+ SystemNoteService.approve_mr(merge_request, current_user)
+ end
+
+ def mark_pending_todos_as_done(merge_request)
+ todo_service.resolve_todos_for_target(merge_request, current_user)
+ end
+
+ def create_event(merge_request)
+ event_service.approve_mr(merge_request, current_user)
+ end
+ end
+end
+
+MergeRequests::ApprovalService.prepend_if_ee('EE::MergeRequests::ApprovalService')
diff --git a/app/services/merge_requests/base_service.rb b/app/services/merge_requests/base_service.rb
index 7f7bfa29af7..7e301f311e9 100644
--- a/app/services/merge_requests/base_service.rb
+++ b/app/services/merge_requests/base_service.rb
@@ -2,6 +2,7 @@
module MergeRequests
class BaseService < ::IssuableBaseService
+ extend ::Gitlab::Utils::Override
include MergeRequests::AssignsMergeParams
def create_note(merge_request, state = merge_request.state)
@@ -29,6 +30,11 @@ module MergeRequests
.execute_for_merge_request(merge_request)
end
+ def cancel_review_app_jobs!(merge_request)
+ environments = merge_request.environments.in_review_folder.available
+ environments.each { |environment| environment.cancel_deployment_jobs! }
+ end
+
def source_project
@source_project ||= merge_request.source_project
end
@@ -58,6 +64,12 @@ module MergeRequests
super
end
+ override :handle_quick_actions
+ def handle_quick_actions(merge_request)
+ super
+ handle_wip_event(merge_request)
+ end
+
def handle_wip_event(merge_request)
if wip_event = params.delete(:wip_event)
# We update the title that is provided in the params or we use the mr title
@@ -90,10 +102,6 @@ module MergeRequests
MergeRequests::CreatePipelineService.new(project, user).execute(merge_request)
end
- def can_use_merge_request_ref?(merge_request)
- !merge_request.for_fork?
- end
-
def abort_auto_merge(merge_request, reason)
AutoMergeService.new(project, current_user).abort(merge_request, reason)
end
diff --git a/app/services/merge_requests/create_pipeline_service.rb b/app/services/merge_requests/create_pipeline_service.rb
index f802aa44487..f9352f10fea 100644
--- a/app/services/merge_requests/create_pipeline_service.rb
+++ b/app/services/merge_requests/create_pipeline_service.rb
@@ -9,7 +9,7 @@ module MergeRequests
end
def create_detached_merge_request_pipeline(merge_request)
- Ci::CreatePipelineService.new(merge_request.source_project,
+ Ci::CreatePipelineService.new(pipeline_project(merge_request),
current_user,
ref: pipeline_ref_for_detached_merge_request_pipeline(merge_request))
.execute(:merge_request_event, merge_request: merge_request)
@@ -31,13 +31,29 @@ module MergeRequests
private
+ def pipeline_project(merge_request)
+ if can_create_pipeline_in_target_project?(merge_request)
+ merge_request.target_project
+ else
+ merge_request.source_project
+ end
+ end
+
def pipeline_ref_for_detached_merge_request_pipeline(merge_request)
- if can_use_merge_request_ref?(merge_request)
+ if can_create_pipeline_in_target_project?(merge_request)
merge_request.ref_path
else
merge_request.source_branch
end
end
+
+ def can_create_pipeline_in_target_project?(merge_request)
+ if Gitlab::Ci::Features.allow_to_create_merge_request_pipelines_in_target_project?(merge_request.target_project)
+ can?(current_user, :create_pipeline, merge_request.target_project)
+ else
+ merge_request.for_same_project?
+ end
+ end
end
end
diff --git a/app/services/merge_requests/create_service.rb b/app/services/merge_requests/create_service.rb
index 1cdfba41432..ac84a13f437 100644
--- a/app/services/merge_requests/create_service.rb
+++ b/app/services/merge_requests/create_service.rb
@@ -33,12 +33,6 @@ module MergeRequests
super
end
- # Override from IssuableBaseService
- def handle_quick_actions_on_create(merge_request)
- super
- handle_wip_event(merge_request)
- end
-
private
def set_projects!
diff --git a/app/services/merge_requests/ff_merge_service.rb b/app/services/merge_requests/ff_merge_service.rb
index 6f1fa607ef9..b3896d61a78 100644
--- a/app/services/merge_requests/ff_merge_service.rb
+++ b/app/services/merge_requests/ff_merge_service.rb
@@ -16,7 +16,7 @@ module MergeRequests
merge_request.target_branch,
merge_request: merge_request)
- if merge_request.squash
+ if merge_request.squash_on_merge?
merge_request.update_column(:squash_commit_sha, merge_request.in_progress_merge_commit_sha)
end
diff --git a/app/services/merge_requests/merge_base_service.rb b/app/services/merge_requests/merge_base_service.rb
index 27b5e31faab..fe09c92aab9 100644
--- a/app/services/merge_requests/merge_base_service.rb
+++ b/app/services/merge_requests/merge_base_service.rb
@@ -20,7 +20,7 @@ module MergeRequests
def source
strong_memoize(:source) do
- if merge_request.squash
+ if merge_request.squash_on_merge?
squash_sha!
else
merge_request.diff_head_sha
diff --git a/app/services/merge_requests/merge_service.rb b/app/services/merge_requests/merge_service.rb
index 8d57a76f7d0..961a7cb1ef6 100644
--- a/app/services/merge_requests/merge_service.rb
+++ b/app/services/merge_requests/merge_service.rb
@@ -27,6 +27,7 @@ module MergeRequests
success
end
end
+
log_info("Merge process finished on JID #{merge_jid} with state #{state}")
rescue MergeError => e
handle_merge_error(log_message: e.message, save_message_on_model: true)
@@ -56,6 +57,8 @@ module MergeRequests
'Only fast-forward merge is allowed for your project. Please update your source branch'
elsif !@merge_request.mergeable?
'Merge request is not mergeable'
+ elsif !@merge_request.squash && project.squash_always?
+ 'This project requires squashing commits when merge requests are accepted.'
end
raise_error(error) if error
diff --git a/app/services/merge_requests/post_merge_service.rb b/app/services/merge_requests/post_merge_service.rb
index 0364c0dd479..fdf8f442297 100644
--- a/app/services/merge_requests/post_merge_service.rb
+++ b/app/services/merge_requests/post_merge_service.rb
@@ -18,6 +18,7 @@ module MergeRequests
invalidate_cache_counts(merge_request, users: merge_request.assignees)
merge_request.update_project_counter_caches
delete_non_latest_diffs(merge_request)
+ cancel_review_app_jobs!(merge_request)
cleanup_environments(merge_request)
end
diff --git a/app/services/merge_requests/remove_approval_service.rb b/app/services/merge_requests/remove_approval_service.rb
new file mode 100644
index 00000000000..3164d0b4069
--- /dev/null
+++ b/app/services/merge_requests/remove_approval_service.rb
@@ -0,0 +1,43 @@
+# frozen_string_literal: true
+
+module MergeRequests
+ class RemoveApprovalService < MergeRequests::BaseService
+ # rubocop: disable CodeReuse/ActiveRecord
+ def execute(merge_request)
+ return unless merge_request.approved_by?(current_user)
+
+ # paranoid protection against running wrong deletes
+ return unless merge_request.id && current_user.id
+
+ approval = merge_request.approvals.where(user: current_user)
+
+ trigger_approval_hooks(merge_request) do
+ next unless approval.destroy_all # rubocop: disable Cop/DestroyAll
+
+ reset_approvals_cache(merge_request)
+ create_note(merge_request)
+ end
+
+ success
+ end
+ # rubocop: enable CodeReuse/ActiveRecord
+
+ private
+
+ def reset_approvals_cache(merge_request)
+ merge_request.approvals.reset
+ end
+
+ def trigger_approval_hooks(merge_request)
+ yield
+
+ execute_hooks(merge_request, 'unapproved')
+ end
+
+ def create_note(merge_request)
+ SystemNoteService.unapprove_mr(merge_request, current_user)
+ end
+ end
+end
+
+MergeRequests::RemoveApprovalService.prepend_if_ee('EE::MergeRequests::RemoveApprovalService')
diff --git a/app/services/merge_requests/squash_service.rb b/app/services/merge_requests/squash_service.rb
index 4b04d42b48e..faa2e921581 100644
--- a/app/services/merge_requests/squash_service.rb
+++ b/app/services/merge_requests/squash_service.rb
@@ -11,11 +11,14 @@ module MergeRequests
return success(squash_sha: merge_request.diff_head_sha)
end
+ return error(s_('MergeRequests|This project does not allow squashing commits when merge requests are accepted.')) if squash_forbidden?
+
if squash_in_progress?
return error(s_('MergeRequests|Squash task canceled: another squash is already in progress.'))
end
squash! || error(s_('MergeRequests|Failed to squash. Should be done manually.'))
+
rescue SquashInProgressError
error(s_('MergeRequests|An error occurred while checking whether another squash is in progress.'))
end
@@ -40,6 +43,10 @@ module MergeRequests
raise SquashInProgressError, e.message
end
+ def squash_forbidden?
+ target_project.squash_never?
+ end
+
def repository
target_project.repository
end
diff --git a/app/services/merge_requests/update_service.rb b/app/services/merge_requests/update_service.rb
index 561695baeab..29e0c22b155 100644
--- a/app/services/merge_requests/update_service.rb
+++ b/app/services/merge_requests/update_service.rb
@@ -2,6 +2,8 @@
module MergeRequests
class UpdateService < MergeRequests::BaseService
+ extend ::Gitlab::Utils::Override
+
def execute(merge_request)
# We don't allow change of source/target projects and source branch
# after merge request was created
@@ -9,14 +11,11 @@ module MergeRequests
params.delete(:target_project_id)
params.delete(:source_branch)
- merge_from_quick_action(merge_request) if params[:merge]
-
if merge_request.closed_without_fork?
params.delete(:target_branch)
params.delete(:force_remove_source_branch)
end
- handle_wip_event(merge_request)
update_task_event(merge_request) || update(merge_request)
end
@@ -77,26 +76,6 @@ module MergeRequests
todo_service.update_merge_request(merge_request, current_user)
end
- def merge_from_quick_action(merge_request)
- last_diff_sha = params.delete(:merge)
-
- if Feature.enabled?(:merge_orchestration_service, merge_request.project, default_enabled: true)
- MergeRequests::MergeOrchestrationService
- .new(project, current_user, { sha: last_diff_sha })
- .execute(merge_request)
- else
- return unless merge_request.mergeable_with_quick_action?(current_user, last_diff_sha: last_diff_sha)
-
- merge_request.update(merge_error: nil)
-
- if merge_request.head_pipeline_active?
- AutoMergeService.new(project, current_user, { sha: last_diff_sha }).execute(merge_request, AutoMergeService::STRATEGY_MERGE_WHEN_PIPELINE_SUCCEEDS)
- else
- merge_request.merge_async(current_user.id, { sha: last_diff_sha })
- end
- end
- end
-
def reopen_service
MergeRequests::ReopenService
end
@@ -134,6 +113,37 @@ module MergeRequests
issuable, issuable.project, current_user, branch_type,
old_branch, new_branch)
end
+
+ override :handle_quick_actions
+ def handle_quick_actions(merge_request)
+ super
+ merge_from_quick_action(merge_request) if params[:merge]
+ end
+
+ def merge_from_quick_action(merge_request)
+ last_diff_sha = params.delete(:merge)
+
+ if Feature.enabled?(:merge_orchestration_service, merge_request.project, default_enabled: true)
+ MergeRequests::MergeOrchestrationService
+ .new(project, current_user, { sha: last_diff_sha })
+ .execute(merge_request)
+ else
+ return unless merge_request.mergeable_with_quick_action?(current_user, last_diff_sha: last_diff_sha)
+
+ merge_request.update(merge_error: nil)
+
+ if merge_request.head_pipeline_active?
+ AutoMergeService.new(project, current_user, { sha: last_diff_sha }).execute(merge_request, AutoMergeService::STRATEGY_MERGE_WHEN_PIPELINE_SUCCEEDS)
+ else
+ merge_request.merge_async(current_user.id, { sha: last_diff_sha })
+ end
+ end
+ end
+
+ override :quick_action_options
+ def quick_action_options
+ { merge_request_diff_head_sha: params.delete(:merge_request_diff_head_sha) }
+ end
end
end
diff --git a/app/services/metrics/dashboard/base_service.rb b/app/services/metrics/dashboard/base_service.rb
index c2a0f22e73e..5fa127d64b2 100644
--- a/app/services/metrics/dashboard/base_service.rb
+++ b/app/services/metrics/dashboard/base_service.rb
@@ -10,7 +10,8 @@ module Metrics
STAGES = ::Gitlab::Metrics::Dashboard::Stages
SEQUENCE = [
STAGES::CommonMetricsInserter,
- STAGES::EndpointInserter,
+ STAGES::MetricEndpointInserter,
+ STAGES::VariableEndpointInserter,
STAGES::PanelIdsInserter,
STAGES::Sorter,
STAGES::AlertsInserter,
@@ -36,6 +37,14 @@ module Metrics
Gitlab::Metrics::Dashboard::Cache.fetch(cache_key) { get_raw_dashboard }
end
+ # Should return true if this dashboard service is for an out-of-the-box
+ # dashboard.
+ # This method is overridden in app/services/metrics/dashboard/predefined_dashboard_service.rb.
+ # @return Boolean
+ def self.out_of_the_box_dashboard?
+ false
+ end
+
private
# Determines whether users should be able to view
@@ -83,6 +92,17 @@ module Metrics
params[:dashboard_path]
end
+ def load_yaml(data)
+ ::Gitlab::Config::Loader::Yaml.new(data).load_raw!
+ rescue Gitlab::Config::Loader::Yaml::NotHashError
+ # Raise more informative error in app/models/performance_monitoring/prometheus_dashboard.rb.
+ {}
+ rescue Gitlab::Config::Loader::Yaml::DataTooLargeError => exception
+ raise Gitlab::Metrics::Dashboard::Errors::LayoutError, exception.message
+ rescue Gitlab::Config::Loader::FormatError
+ raise Gitlab::Metrics::Dashboard::Errors::LayoutError, _('Invalid yaml')
+ end
+
# @return [Hash] an unmodified dashboard
def get_raw_dashboard
raise NotImplementedError
diff --git a/app/services/metrics/dashboard/clone_dashboard_service.rb b/app/services/metrics/dashboard/clone_dashboard_service.rb
index 3ca25b3bd9b..a6bece391f2 100644
--- a/app/services/metrics/dashboard/clone_dashboard_service.rb
+++ b/app/services/metrics/dashboard/clone_dashboard_service.rb
@@ -6,30 +6,33 @@ module Metrics
module Dashboard
class CloneDashboardService < ::BaseService
include Stepable
+ include Gitlab::Utils::StrongMemoize
ALLOWED_FILE_TYPE = '.yml'
USER_DASHBOARDS_DIR = ::Metrics::Dashboard::CustomDashboardService::DASHBOARD_ROOT
+ SEQUENCES = {
+ ::Metrics::Dashboard::SystemDashboardService::DASHBOARD_PATH => [
+ ::Gitlab::Metrics::Dashboard::Stages::CommonMetricsInserter,
+ ::Gitlab::Metrics::Dashboard::Stages::CustomMetricsInserter,
+ ::Gitlab::Metrics::Dashboard::Stages::Sorter
+ ].freeze,
+
+ ::Metrics::Dashboard::SelfMonitoringDashboardService::DASHBOARD_PATH => [
+ ::Gitlab::Metrics::Dashboard::Stages::CustomMetricsInserter
+ ].freeze,
+
+ ::Metrics::Dashboard::ClusterDashboardService::DASHBOARD_PATH => [
+ ::Gitlab::Metrics::Dashboard::Stages::CommonMetricsInserter,
+ ::Gitlab::Metrics::Dashboard::Stages::Sorter
+ ].freeze
+ }.freeze
steps :check_push_authorized,
- :check_branch_name,
- :check_file_type,
- :check_dashboard_template,
- :create_file,
- :refresh_repository_method_caches
-
- class << self
- def allowed_dashboard_templates
- @allowed_dashboard_templates ||= Set[::Metrics::Dashboard::SystemDashboardService::DASHBOARD_PATH].freeze
- end
-
- def sequences
- @sequences ||= {
- ::Metrics::Dashboard::SystemDashboardService::DASHBOARD_PATH => [::Gitlab::Metrics::Dashboard::Stages::CommonMetricsInserter,
- ::Gitlab::Metrics::Dashboard::Stages::CustomMetricsInserter,
- ::Gitlab::Metrics::Dashboard::Stages::Sorter].freeze
- }.freeze
- end
- end
+ :check_branch_name,
+ :check_file_type,
+ :check_dashboard_template,
+ :create_file,
+ :refresh_repository_method_caches
def execute
execute_steps
@@ -56,8 +59,12 @@ module Metrics
success(result)
end
+ # Only allow out of the box metrics dashboards to be cloned. This can be
+ # changed to allow cloning of any metrics dashboard, if desired.
+ # However, only metrics dashboards should be allowed. If any file is
+ # allowed to be cloned, this will become a security risk.
def check_dashboard_template(result)
- return error(_('Not found.'), :not_found) unless self.class.allowed_dashboard_templates.include?(params[:dashboard])
+ return error(_('Not found.'), :not_found) unless dashboard_service&.out_of_the_box_dashboard?
success(result)
end
@@ -78,6 +85,12 @@ module Metrics
success(result.merge(http_status: :created, dashboard: dashboard_details))
end
+ def dashboard_service
+ strong_memoize(:dashboard_service) do
+ Gitlab::Metrics::Dashboard::ServiceSelector.call(dashboard_service_options)
+ end
+ end
+
def dashboard_attrs
{
commit_message: params[:commit_message],
@@ -149,14 +162,19 @@ module Metrics
end
def raw_dashboard
- YAML.safe_load(File.read(Rails.root.join(dashboard_template)))
+ dashboard_service.new(project, current_user, dashboard_service_options).raw_dashboard
+ end
+
+ def dashboard_service_options
+ {
+ embedded: false,
+ dashboard_path: dashboard_template
+ }
end
def sequence
- self.class.sequences[dashboard_template]
+ SEQUENCES[dashboard_template] || []
end
end
end
end
-
-Metrics::Dashboard::CloneDashboardService.prepend_if_ee('EE::Metrics::Dashboard::CloneDashboardService')
diff --git a/app/services/metrics/dashboard/cluster_dashboard_service.rb b/app/services/metrics/dashboard/cluster_dashboard_service.rb
new file mode 100644
index 00000000000..bfd5abf1126
--- /dev/null
+++ b/app/services/metrics/dashboard/cluster_dashboard_service.rb
@@ -0,0 +1,40 @@
+# frozen_string_literal: true
+
+# Fetches the system metrics dashboard and formats the output.
+# Use Gitlab::Metrics::Dashboard::Finder to retrive dashboards.
+module Metrics
+ module Dashboard
+ class ClusterDashboardService < ::Metrics::Dashboard::PredefinedDashboardService
+ DASHBOARD_PATH = 'config/prometheus/cluster_metrics.yml'
+ DASHBOARD_NAME = 'Cluster'
+
+ # SHA256 hash of dashboard content
+ DASHBOARD_VERSION = '9349afc1d96329c08ab478ea0b77db94ee5cc2549b8c754fba67a7f424666b22'
+
+ SEQUENCE = [
+ STAGES::ClusterEndpointInserter,
+ STAGES::PanelIdsInserter,
+ STAGES::Sorter
+ ].freeze
+
+ class << self
+ def valid_params?(params)
+ # support selecting this service by cluster id via .find
+ # Use super to support selecting this service by dashboard_path via .find_raw
+ (params[:cluster].present? && params[:embedded] != 'true') || super
+ end
+ end
+
+ # Permissions are handled at the controller level
+ def allowed?
+ true
+ end
+
+ private
+
+ def dashboard_version
+ DASHBOARD_VERSION
+ end
+ end
+ end
+end
diff --git a/app/services/metrics/dashboard/cluster_metrics_embed_service.rb b/app/services/metrics/dashboard/cluster_metrics_embed_service.rb
new file mode 100644
index 00000000000..6fb39ed3004
--- /dev/null
+++ b/app/services/metrics/dashboard/cluster_metrics_embed_service.rb
@@ -0,0 +1,37 @@
+# frozen_string_literal: true
+#
+module Metrics
+ module Dashboard
+ class ClusterMetricsEmbedService < Metrics::Dashboard::DynamicEmbedService
+ class << self
+ def valid_params?(params)
+ [
+ params[:cluster],
+ embedded?(params[:embedded]),
+ params[:group].present?,
+ params[:title].present?,
+ params[:y_label].present?
+ ].all?
+ end
+ end
+
+ private
+
+ # Permissions are handled at the controller level
+ def allowed?
+ true
+ end
+
+ def dashboard_path
+ ::Metrics::Dashboard::ClusterDashboardService::DASHBOARD_PATH
+ end
+
+ def sequence
+ [
+ STAGES::ClusterEndpointInserter,
+ STAGES::PanelIdsInserter
+ ]
+ end
+ end
+ end
+end
diff --git a/app/services/metrics/dashboard/custom_dashboard_service.rb b/app/services/metrics/dashboard/custom_dashboard_service.rb
index 77173813a4f..741738cc3af 100644
--- a/app/services/metrics/dashboard/custom_dashboard_service.rb
+++ b/app/services/metrics/dashboard/custom_dashboard_service.rb
@@ -21,7 +21,8 @@ module Metrics
path: filepath,
display_name: name_for_path(filepath),
default: false,
- system_dashboard: false
+ system_dashboard: false,
+ out_of_the_box_dashboard: out_of_the_box_dashboard?
}
end
end
@@ -42,7 +43,7 @@ module Metrics
def get_raw_dashboard
yml = self.class.file_finder(project).read(dashboard_path)
- YAML.safe_load(yml)
+ load_yaml(yml)
end
def cache_key
diff --git a/app/services/metrics/dashboard/gitlab_alert_embed_service.rb b/app/services/metrics/dashboard/gitlab_alert_embed_service.rb
index 38e89d392ad..08d65413e1d 100644
--- a/app/services/metrics/dashboard/gitlab_alert_embed_service.rb
+++ b/app/services/metrics/dashboard/gitlab_alert_embed_service.rb
@@ -11,7 +11,7 @@ module Metrics
include Gitlab::Utils::StrongMemoize
SEQUENCE = [
- STAGES::EndpointInserter,
+ STAGES::MetricEndpointInserter,
STAGES::PanelIdsInserter
].freeze
diff --git a/app/services/metrics/dashboard/grafana_metric_embed_service.rb b/app/services/metrics/dashboard/grafana_metric_embed_service.rb
index d9ce2c5e905..8e72a185406 100644
--- a/app/services/metrics/dashboard/grafana_metric_embed_service.rb
+++ b/app/services/metrics/dashboard/grafana_metric_embed_service.rb
@@ -80,7 +80,7 @@ module Metrics
def fetch_dashboard
uid = GrafanaUidParser.new(grafana_url, project).parse
- raise DashboardProcessingError.new('Dashboard uid not found') unless uid
+ raise DashboardProcessingError.new(_('Dashboard uid not found')) unless uid
response = client.get_dashboard(uid: uid)
@@ -89,7 +89,7 @@ module Metrics
def fetch_datasource(dashboard)
name = DatasourceNameParser.new(grafana_url, dashboard).parse
- raise DashboardProcessingError.new('Datasource name not found') unless name
+ raise DashboardProcessingError.new(_('Datasource name not found')) unless name
response = client.get_datasource(name: name)
@@ -115,7 +115,7 @@ module Metrics
def parse_json(json)
Gitlab::Json.parse(json, symbolize_names: true)
rescue JSON::ParserError
- raise DashboardProcessingError.new('Grafana response contains invalid json')
+ raise DashboardProcessingError.new(_('Grafana response contains invalid json'))
end
end
diff --git a/app/services/metrics/dashboard/pod_dashboard_service.rb b/app/services/metrics/dashboard/pod_dashboard_service.rb
index 16b87d2d587..8699189deac 100644
--- a/app/services/metrics/dashboard/pod_dashboard_service.rb
+++ b/app/services/metrics/dashboard/pod_dashboard_service.rb
@@ -5,6 +5,15 @@ module Metrics
class PodDashboardService < ::Metrics::Dashboard::PredefinedDashboardService
DASHBOARD_PATH = 'config/prometheus/pod_metrics.yml'
DASHBOARD_NAME = 'Pod Health'
+
+ # SHA256 hash of dashboard content
+ DASHBOARD_VERSION = 'f12f641d2575d5dcb69e2c633ff5231dbd879ad35020567d8fc4e1090bfdb4b4'
+
+ private
+
+ def dashboard_version
+ DASHBOARD_VERSION
+ end
end
end
end
diff --git a/app/services/metrics/dashboard/predefined_dashboard_service.rb b/app/services/metrics/dashboard/predefined_dashboard_service.rb
index f454df63773..c21083475f0 100644
--- a/app/services/metrics/dashboard/predefined_dashboard_service.rb
+++ b/app/services/metrics/dashboard/predefined_dashboard_service.rb
@@ -10,7 +10,8 @@ module Metrics
DASHBOARD_NAME = nil
SEQUENCE = [
- STAGES::EndpointInserter,
+ STAGES::MetricEndpointInserter,
+ STAGES::VariableEndpointInserter,
STAGES::PanelIdsInserter,
STAGES::Sorter
].freeze
@@ -23,12 +24,20 @@ module Metrics
def matching_dashboard?(filepath)
filepath == self::DASHBOARD_PATH
end
+
+ def out_of_the_box_dashboard?
+ true
+ end
end
private
+ def dashboard_version
+ raise NotImplementedError
+ end
+
def cache_key
- "metrics_dashboard_#{dashboard_path}"
+ "metrics_dashboard_#{dashboard_path}_#{dashboard_version}"
end
def dashboard_path
@@ -39,7 +48,7 @@ module Metrics
def get_raw_dashboard
yml = File.read(Rails.root.join(dashboard_path))
- YAML.safe_load(yml)
+ load_yaml(yml)
end
def sequence
diff --git a/app/services/metrics/dashboard/self_monitoring_dashboard_service.rb b/app/services/metrics/dashboard/self_monitoring_dashboard_service.rb
index 8599c23c206..f1f5cd7d77e 100644
--- a/app/services/metrics/dashboard/self_monitoring_dashboard_service.rb
+++ b/app/services/metrics/dashboard/self_monitoring_dashboard_service.rb
@@ -8,9 +8,13 @@ module Metrics
DASHBOARD_PATH = 'config/prometheus/self_monitoring_default.yml'
DASHBOARD_NAME = N_('Default dashboard')
+ # SHA256 hash of dashboard content
+ DASHBOARD_VERSION = '1dff3e3cb76e73c8e368823c98b34c61aec0d141978450dea195a3b3dc2415d6'
+
SEQUENCE = [
STAGES::CustomMetricsInserter,
- STAGES::EndpointInserter,
+ STAGES::MetricEndpointInserter,
+ STAGES::VariableEndpointInserter,
STAGES::PanelIdsInserter,
STAGES::Sorter
].freeze
@@ -25,7 +29,8 @@ module Metrics
path: DASHBOARD_PATH,
display_name: _(DASHBOARD_NAME),
default: true,
- system_dashboard: false
+ system_dashboard: false,
+ out_of_the_box_dashboard: out_of_the_box_dashboard?
}]
end
@@ -33,6 +38,12 @@ module Metrics
params[:dashboard_path].nil? && params[:environment]&.project&.self_monitoring?
end
end
+
+ private
+
+ def dashboard_version
+ DASHBOARD_VERSION
+ end
end
end
end
diff --git a/app/services/metrics/dashboard/system_dashboard_service.rb b/app/services/metrics/dashboard/system_dashboard_service.rb
index db5599b4def..5c3562b8ca0 100644
--- a/app/services/metrics/dashboard/system_dashboard_service.rb
+++ b/app/services/metrics/dashboard/system_dashboard_service.rb
@@ -8,11 +8,15 @@ module Metrics
DASHBOARD_PATH = 'config/prometheus/common_metrics.yml'
DASHBOARD_NAME = N_('Default dashboard')
+ # SHA256 hash of dashboard content
+ DASHBOARD_VERSION = '4685fe386c25b1a786b3be18f79bb2ee9828019003e003816284cdb634fa3e13'
+
SEQUENCE = [
STAGES::CommonMetricsInserter,
STAGES::CustomMetricsInserter,
STAGES::CustomMetricsDetailsInserter,
- STAGES::EndpointInserter,
+ STAGES::MetricEndpointInserter,
+ STAGES::VariableEndpointInserter,
STAGES::PanelIdsInserter,
STAGES::Sorter,
STAGES::AlertsInserter
@@ -24,10 +28,17 @@ module Metrics
path: DASHBOARD_PATH,
display_name: _(DASHBOARD_NAME),
default: true,
- system_dashboard: true
+ system_dashboard: true,
+ out_of_the_box_dashboard: out_of_the_box_dashboard?
}]
end
end
+
+ private
+
+ def dashboard_version
+ DASHBOARD_VERSION
+ end
end
end
end
diff --git a/app/services/metrics/dashboard/transient_embed_service.rb b/app/services/metrics/dashboard/transient_embed_service.rb
index cb6ca215447..0a9c4bc7b86 100644
--- a/app/services/metrics/dashboard/transient_embed_service.rb
+++ b/app/services/metrics/dashboard/transient_embed_service.rb
@@ -30,7 +30,7 @@ module Metrics
override :sequence
def sequence
- [STAGES::EndpointInserter]
+ [STAGES::MetricEndpointInserter]
end
override :identifiers
@@ -39,7 +39,7 @@ module Metrics
end
def invalid_embed_json!(message)
- raise DashboardProcessingError.new("Parsing error for param :embed_json. #{message}")
+ raise DashboardProcessingError.new(_("Parsing error for param :embed_json. %{message}") % { message: message })
end
end
end
diff --git a/app/services/namespaces/check_storage_size_service.rb b/app/services/namespaces/check_storage_size_service.rb
deleted file mode 100644
index 57d2645a0c8..00000000000
--- a/app/services/namespaces/check_storage_size_service.rb
+++ /dev/null
@@ -1,95 +0,0 @@
-# frozen_string_literal: true
-
-module Namespaces
- class CheckStorageSizeService
- include ActiveSupport::NumberHelper
- include Gitlab::Allowable
- include Gitlab::Utils::StrongMemoize
-
- def initialize(namespace, user)
- @root_namespace = namespace.root_ancestor
- @root_storage_size = Namespace::RootStorageSize.new(root_namespace)
- @user = user
- end
-
- def execute
- return ServiceResponse.success unless Feature.enabled?(:namespace_storage_limit, root_namespace)
- return ServiceResponse.success if alert_level == :none
-
- if root_storage_size.above_size_limit?
- ServiceResponse.error(message: above_size_limit_message, payload: payload)
- else
- ServiceResponse.success(payload: payload)
- end
- end
-
- private
-
- attr_reader :root_namespace, :root_storage_size, :user
-
- USAGE_THRESHOLDS = {
- none: 0.0,
- info: 0.5,
- warning: 0.75,
- alert: 0.95,
- error: 1.0
- }.freeze
-
- def payload
- return {} unless can?(user, :admin_namespace, root_namespace)
-
- {
- explanation_message: explanation_message,
- usage_message: usage_message,
- alert_level: alert_level,
- root_namespace: root_namespace
- }
- end
-
- def explanation_message
- root_storage_size.above_size_limit? ? above_size_limit_message : below_size_limit_message
- end
-
- def usage_message
- s_("You reached %{usage_in_percent} of %{namespace_name}'s storage capacity (%{used_storage} of %{storage_limit})" % current_usage_params)
- end
-
- def alert_level
- strong_memoize(:alert_level) do
- usage_ratio = root_storage_size.usage_ratio
- current_level = USAGE_THRESHOLDS.each_key.first
-
- USAGE_THRESHOLDS.each do |level, threshold|
- current_level = level if usage_ratio >= threshold
- end
-
- current_level
- end
- end
-
- def below_size_limit_message
- s_("If you reach 100%% storage capacity, you will not be able to: %{base_message}" % { base_message: base_message } )
- end
-
- def above_size_limit_message
- s_("%{namespace_name} is now read-only. You cannot: %{base_message}" % { namespace_name: root_namespace.name, base_message: base_message })
- end
-
- def base_message
- s_("push to your repository, create pipelines, create issues or add comments. To reduce storage capacity, delete unused repositories, artifacts, wikis, issues, and pipelines.")
- end
-
- def current_usage_params
- {
- usage_in_percent: number_to_percentage(root_storage_size.usage_ratio * 100, precision: 0),
- namespace_name: root_namespace.name,
- used_storage: formatted(root_storage_size.current_size),
- storage_limit: formatted(root_storage_size.limit)
- }
- end
-
- def formatted(number)
- number_to_human_size(number, delimiter: ',', precision: 2)
- end
- end
-end
diff --git a/app/services/notes/post_process_service.rb b/app/services/notes/post_process_service.rb
index 0e455c641ce..4f3b2000e9a 100644
--- a/app/services/notes/post_process_service.rb
+++ b/app/services/notes/post_process_service.rb
@@ -10,13 +10,13 @@ module Notes
def execute
# Skip system notes, like status changes and cross-references and awards
- unless @note.system?
- EventCreateService.new.leave_note(@note, @note.author)
+ unless note.system?
+ EventCreateService.new.leave_note(note, note.author)
- return if @note.for_personal_snippet?
+ return if note.for_personal_snippet?
- @note.create_cross_references!
- ::SystemNoteService.design_discussion_added(@note) if create_design_discussion_system_note?
+ note.create_cross_references!
+ ::SystemNoteService.design_discussion_added(note) if create_design_discussion_system_note?
execute_note_hooks
end
@@ -25,21 +25,21 @@ module Notes
private
def create_design_discussion_system_note?
- @note && @note.for_design? && @note.start_of_discussion?
+ note && note.for_design? && note.start_of_discussion?
end
def hook_data
- Gitlab::DataBuilder::Note.build(@note, @note.author)
+ Gitlab::DataBuilder::Note.build(note, note.author)
end
def execute_note_hooks
- return unless @note.project
+ return unless note.project
note_data = hook_data
- hooks_scope = @note.confidential?(include_noteable: true) ? :confidential_note_hooks : :note_hooks
+ hooks_scope = note.confidential?(include_noteable: true) ? :confidential_note_hooks : :note_hooks
- @note.project.execute_hooks(note_data, hooks_scope)
- @note.project.execute_services(note_data, hooks_scope)
+ note.project.execute_hooks(note_data, hooks_scope)
+ note.project.execute_services(note_data, hooks_scope)
end
end
end
diff --git a/app/services/notes/quick_actions_service.rb b/app/services/notes/quick_actions_service.rb
index 7e6568b5b25..c670f01e502 100644
--- a/app/services/notes/quick_actions_service.rb
+++ b/app/services/notes/quick_actions_service.rb
@@ -41,7 +41,7 @@ module Notes
@interpret_service = QuickActions::InterpretService.new(project, current_user, options)
- @interpret_service.execute(note.note, note.noteable)
+ interpret_service.execute(note.note, note.noteable)
end
# Applies updates extracted to note#noteable
diff --git a/app/services/notes/update_service.rb b/app/services/notes/update_service.rb
index 444656348ed..047848fd1a3 100644
--- a/app/services/notes/update_service.rb
+++ b/app/services/notes/update_service.rb
@@ -10,6 +10,7 @@ module Notes
note.assign_attributes(params.merge(updated_by: current_user))
note.with_transaction_returning_status do
+ update_confidentiality(note)
note.save
end
@@ -79,6 +80,15 @@ module Notes
TodoService.new.update_note(note, current_user, old_mentioned_users)
end
+
+ # This method updates confidentiality of all discussion notes at once
+ def update_confidentiality(note)
+ return unless params.key?(:confidential)
+ return unless note.is_a?(DiscussionNote) # we don't need to do bulk update for single notes
+ return unless note.start_of_discussion? # don't update all notes if a response is being updated
+
+ Note.id_in(note.discussion.notes.map(&:id)).update_all(confidential: params[:confidential])
+ end
end
end
diff --git a/app/services/notification_service.rb b/app/services/notification_service.rb
index 73e60ac8420..a4e935a8cf5 100644
--- a/app/services/notification_service.rb
+++ b/app/services/notification_service.rb
@@ -294,6 +294,7 @@ class NotificationService
return true if note.system_note_with_references?
send_new_note_notifications(note)
+ send_service_desk_notification(note)
end
def send_new_note_notifications(note)
@@ -305,6 +306,21 @@ class NotificationService
end
end
+ def send_service_desk_notification(note)
+ return unless Gitlab::ServiceDesk.supported?
+ return unless note.noteable_type == 'Issue'
+
+ issue = note.noteable
+ support_bot = User.support_bot
+
+ return unless issue.service_desk_reply_to.present?
+ return unless issue.project.service_desk_enabled?
+ return if note.author == support_bot
+ return unless issue.subscribed?(support_bot, issue.project)
+
+ mailer.service_desk_new_note_email(issue.id, note.id).deliver_later
+ end
+
# Notify users when a new release is created
def send_new_release_notifications(release)
recipients = NotificationRecipients::BuildService.build_new_release_recipients(release)
@@ -566,6 +582,14 @@ class NotificationService
end
end
+ def merge_when_pipeline_succeeds(merge_request, current_user)
+ recipients = ::NotificationRecipients::BuildService.build_recipients(merge_request, current_user, action: 'merge_when_pipeline_succeeds')
+
+ recipients.each do |recipient|
+ mailer.merge_when_pipeline_succeeds_email(recipient.user.id, merge_request.id, current_user.id).deliver_later
+ end
+ end
+
protected
def new_resource_email(target, method)
diff --git a/app/services/packages/composer/composer_json_service.rb b/app/services/packages/composer/composer_json_service.rb
new file mode 100644
index 00000000000..6ffb5a77da3
--- /dev/null
+++ b/app/services/packages/composer/composer_json_service.rb
@@ -0,0 +1,31 @@
+# frozen_string_literal: true
+
+module Packages
+ module Composer
+ class ComposerJsonService
+ def initialize(project, target)
+ @project, @target = project, target
+ end
+
+ def execute
+ composer_json
+ end
+
+ private
+
+ def composer_json
+ composer_file = @project.repository.blob_at(@target, 'composer.json')
+
+ composer_file_not_found! unless composer_file
+
+ Gitlab::Json.parse(composer_file.data)
+ rescue JSON::ParserError
+ raise 'Could not parse composer.json file. Invalid JSON.'
+ end
+
+ def composer_file_not_found!
+ raise 'The file composer.json was not found.'
+ end
+ end
+ end
+end
diff --git a/app/services/packages/composer/create_package_service.rb b/app/services/packages/composer/create_package_service.rb
new file mode 100644
index 00000000000..ad5d267698b
--- /dev/null
+++ b/app/services/packages/composer/create_package_service.rb
@@ -0,0 +1,57 @@
+# frozen_string_literal: true
+
+module Packages
+ module Composer
+ class CreatePackageService < BaseService
+ include ::Gitlab::Utils::StrongMemoize
+
+ def execute
+ # fetches json outside of transaction
+ composer_json
+
+ ::Packages::Package.transaction do
+ ::Packages::Composer::Metadatum.upsert(
+ package_id: created_package.id,
+ target_sha: target,
+ composer_json: composer_json
+ )
+ end
+ end
+
+ private
+
+ def created_package
+ project
+ .packages
+ .composer
+ .safe_find_or_create_by!(name: package_name, version: package_version)
+ end
+
+ def composer_json
+ strong_memoize(:composer_json) do
+ ::Packages::Composer::ComposerJsonService.new(project, target).execute
+ end
+ end
+
+ def package_name
+ composer_json['name']
+ end
+
+ def target
+ (branch || tag).target
+ end
+
+ def branch
+ params[:branch]
+ end
+
+ def tag
+ params[:tag]
+ end
+
+ def package_version
+ ::Packages::Composer::VersionParserService.new(tag_name: tag&.name, branch_name: branch&.name).execute
+ end
+ end
+ end
+end
diff --git a/app/services/packages/composer/version_parser_service.rb b/app/services/packages/composer/version_parser_service.rb
new file mode 100644
index 00000000000..76dfd7a14bd
--- /dev/null
+++ b/app/services/packages/composer/version_parser_service.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+module Packages
+ module Composer
+ class VersionParserService
+ def initialize(tag_name: nil, branch_name: nil)
+ @tag_name, @branch_name = tag_name, branch_name
+ end
+
+ def execute
+ if @tag_name.present?
+ @tag_name.match(Gitlab::Regex.composer_package_version_regex).captures[0]
+ elsif @branch_name.present?
+ branch_sufix_or_prefix(@branch_name.match(Gitlab::Regex.composer_package_version_regex))
+ end
+ end
+
+ private
+
+ def branch_sufix_or_prefix(match)
+ if match
+ if match.captures[1] == '.x'
+ match.captures[0] + '-dev'
+ else
+ match.captures[0] + '.x-dev'
+ end
+ else
+ "dev-#{@branch_name}"
+ end
+ end
+ end
+ end
+end
diff --git a/app/services/packages/conan/create_package_file_service.rb b/app/services/packages/conan/create_package_file_service.rb
new file mode 100644
index 00000000000..2db5c4e507b
--- /dev/null
+++ b/app/services/packages/conan/create_package_file_service.rb
@@ -0,0 +1,31 @@
+# frozen_string_literal: true
+
+module Packages
+ module Conan
+ class CreatePackageFileService
+ attr_reader :package, :file, :params
+
+ def initialize(package, file, params)
+ @package = package
+ @file = file
+ @params = params
+ end
+
+ def execute
+ package.package_files.create!(
+ file: file,
+ size: params['file.size'],
+ file_name: params[:file_name],
+ file_sha1: params['file.sha1'],
+ file_md5: params['file.md5'],
+ conan_file_metadatum_attributes: {
+ recipe_revision: params[:recipe_revision],
+ package_revision: params[:package_revision],
+ conan_package_reference: params[:conan_package_reference],
+ conan_file_type: params[:conan_file_type]
+ }
+ )
+ end
+ end
+ end
+end
diff --git a/app/services/packages/conan/create_package_service.rb b/app/services/packages/conan/create_package_service.rb
new file mode 100644
index 00000000000..22a0436c5fb
--- /dev/null
+++ b/app/services/packages/conan/create_package_service.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+module Packages
+ module Conan
+ class CreatePackageService < BaseService
+ def execute
+ project.packages.create!(
+ name: params[:package_name],
+ version: params[:package_version],
+ package_type: :conan,
+ conan_metadatum_attributes: {
+ package_username: params[:package_username],
+ package_channel: params[:package_channel]
+ }
+ )
+ end
+ end
+ end
+end
diff --git a/app/services/packages/conan/search_service.rb b/app/services/packages/conan/search_service.rb
new file mode 100644
index 00000000000..4513616bad2
--- /dev/null
+++ b/app/services/packages/conan/search_service.rb
@@ -0,0 +1,58 @@
+# frozen_string_literal: true
+
+module Packages
+ module Conan
+ class SearchService < BaseService
+ include ActiveRecord::Sanitization::ClassMethods
+
+ WILDCARD = '*'
+ RECIPE_SEPARATOR = '@'
+
+ def initialize(user, params)
+ super(nil, user, params)
+ end
+
+ def execute
+ ServiceResponse.success(payload: { results: search_results })
+ end
+
+ private
+
+ def search_results
+ return [] if wildcard_query?
+
+ return search_for_single_package(sanitized_query) if params[:query].include?(RECIPE_SEPARATOR)
+
+ search_packages(build_query)
+ end
+
+ def wildcard_query?
+ params[:query] == WILDCARD
+ end
+
+ def build_query
+ return "#{sanitized_query}%" if params[:query].end_with?(WILDCARD)
+
+ sanitized_query
+ end
+
+ def search_packages(query)
+ ::Packages::Conan::PackageFinder.new(current_user, query: query).execute.map(&:conan_recipe)
+ end
+
+ def search_for_single_package(query)
+ name, version, username, _ = query.split(/[@\/]/)
+ full_path = Packages::Conan::Metadatum.full_path_from(package_username: username)
+ project = Project.find_by_full_path(full_path)
+ return unless current_user.can?(:read_package, project)
+
+ result = project.packages.with_name(name).with_version(version).order_created.last
+ [result&.conan_recipe].compact
+ end
+
+ def sanitized_query
+ @sanitized_query ||= sanitize_sql_like(params[:query].delete(WILDCARD))
+ end
+ end
+ end
+end
diff --git a/app/services/packages/create_dependency_service.rb b/app/services/packages/create_dependency_service.rb
new file mode 100644
index 00000000000..2999885d55d
--- /dev/null
+++ b/app/services/packages/create_dependency_service.rb
@@ -0,0 +1,82 @@
+# frozen_string_literal: true
+module Packages
+ class CreateDependencyService < BaseService
+ attr_reader :package, :dependencies
+
+ def initialize(package, dependencies)
+ @package = package
+ @dependencies = dependencies
+ end
+
+ def execute
+ Packages::DependencyLink.dependency_types.each_key do |type|
+ create_dependency(type)
+ end
+ end
+
+ private
+
+ def create_dependency(type)
+ return unless dependencies[type].is_a?(Hash)
+
+ names_and_version_patterns = dependencies[type]
+ existing_ids, existing_names = find_existing_ids_and_names(names_and_version_patterns)
+ dependencies_to_insert = names_and_version_patterns
+
+ if existing_names.any?
+ dependencies_to_insert = names_and_version_patterns.reject { |k, _| k.in?(existing_names) }
+ end
+
+ ActiveRecord::Base.transaction do
+ inserted_ids = bulk_insert_package_dependencies(dependencies_to_insert)
+ bulk_insert_package_dependency_links(type, (existing_ids + inserted_ids))
+ end
+ end
+
+ def find_existing_ids_and_names(names_and_version_patterns)
+ ids_and_names = Packages::Dependency.for_package_names_and_version_patterns(names_and_version_patterns)
+ .pluck_ids_and_names
+ ids = ids_and_names.map(&:first) || []
+ names = ids_and_names.map(&:second) || []
+ [ids, names]
+ end
+
+ def bulk_insert_package_dependencies(names_and_version_patterns)
+ return [] if names_and_version_patterns.empty?
+
+ rows = names_and_version_patterns.map do |name, version_pattern|
+ {
+ name: name,
+ version_pattern: version_pattern
+ }
+ end
+
+ ids = database.bulk_insert(Packages::Dependency.table_name, rows, return_ids: true, on_conflict: :do_nothing)
+ return ids if ids.size == names_and_version_patterns.size
+
+ Packages::Dependency.uncached do
+ # The bulk_insert statement above do not dirty the query cache. To make
+ # sure that the results are fresh from the database and not from a stalled
+ # and potentially wrong cache, this query has to be done with the query
+ # chache disabled.
+ Packages::Dependency.ids_for_package_names_and_version_patterns(names_and_version_patterns)
+ end
+ end
+
+ def bulk_insert_package_dependency_links(type, dependency_ids)
+ rows = dependency_ids.map do |dependency_id|
+ {
+ package_id: package.id,
+ dependency_id: dependency_id,
+ dependency_type: Packages::DependencyLink.dependency_types[type.to_s]
+ }
+ end
+
+ database.bulk_insert(Packages::DependencyLink.table_name, rows)
+ end
+
+ def database
+ ::Gitlab::Database
+ end
+ end
+end
diff --git a/app/services/packages/create_package_file_service.rb b/app/services/packages/create_package_file_service.rb
new file mode 100644
index 00000000000..0ebceeee779
--- /dev/null
+++ b/app/services/packages/create_package_file_service.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+module Packages
+ class CreatePackageFileService
+ attr_reader :package, :params
+
+ def initialize(package, params)
+ @package = package
+ @params = params
+ end
+
+ def execute
+ package.package_files.create!(
+ file: params[:file],
+ size: params[:size],
+ file_name: params[:file_name],
+ file_sha1: params[:file_sha1],
+ file_sha256: params[:file_sha256],
+ file_md5: params[:file_md5]
+ )
+ end
+ end
+end
diff --git a/app/services/packages/maven/create_package_service.rb b/app/services/packages/maven/create_package_service.rb
new file mode 100644
index 00000000000..aca5d28ca98
--- /dev/null
+++ b/app/services/packages/maven/create_package_service.rb
@@ -0,0 +1,28 @@
+# frozen_string_literal: true
+module Packages
+ module Maven
+ class CreatePackageService < BaseService
+ def execute
+ app_group, _, app_name = params[:name].rpartition('/')
+ app_group.tr!('/', '.')
+
+ package = project.packages.create!(
+ name: params[:name],
+ version: params[:version],
+ package_type: :maven,
+ maven_metadatum_attributes: {
+ path: params[:path],
+ app_group: app_group,
+ app_name: app_name,
+ app_version: params[:version]
+ }
+ )
+
+ build = params[:build]
+ package.create_build_info!(pipeline: build.pipeline) if build.present?
+
+ package
+ end
+ end
+ end
+end
diff --git a/app/services/packages/maven/find_or_create_package_service.rb b/app/services/packages/maven/find_or_create_package_service.rb
new file mode 100644
index 00000000000..50a008843ad
--- /dev/null
+++ b/app/services/packages/maven/find_or_create_package_service.rb
@@ -0,0 +1,41 @@
+# frozen_string_literal: true
+module Packages
+ module Maven
+ class FindOrCreatePackageService < BaseService
+ MAVEN_METADATA_FILE = 'maven-metadata.xml'.freeze
+
+ def execute
+ package = ::Packages::Maven::PackageFinder
+ .new(params[:path], current_user, project: project).execute
+
+ unless package
+ if params[:file_name] == MAVEN_METADATA_FILE
+ # Maven uploads several files during `mvn deploy` in next order:
+ # - my-company/my-app/1.0-SNAPSHOT/my-app.jar
+ # - my-company/my-app/1.0-SNAPSHOT/my-app.pom
+ # - my-company/my-app/1.0-SNAPSHOT/maven-metadata.xml
+ # - my-company/my-app/maven-metadata.xml
+ #
+ # The last xml file does not have VERSION in URL because it contains
+ # information about all versions.
+ package_name, version = params[:path], nil
+ else
+ package_name, _, version = params[:path].rpartition('/')
+ end
+
+ package_params = {
+ name: package_name,
+ path: params[:path],
+ version: version,
+ build: params[:build]
+ }
+
+ package = ::Packages::Maven::CreatePackageService
+ .new(project, current_user, package_params).execute
+ end
+
+ package
+ end
+ end
+ end
+end
diff --git a/app/services/packages/npm/create_package_service.rb b/app/services/packages/npm/create_package_service.rb
new file mode 100644
index 00000000000..cf927683ce9
--- /dev/null
+++ b/app/services/packages/npm/create_package_service.rb
@@ -0,0 +1,91 @@
+# frozen_string_literal: true
+module Packages
+ module Npm
+ class CreatePackageService < BaseService
+ include Gitlab::Utils::StrongMemoize
+
+ def execute
+ return error('Version is empty.', 400) if version.blank?
+ return error('Package already exists.', 403) if current_package_exists?
+
+ ActiveRecord::Base.transaction { create_package! }
+ end
+
+ private
+
+ def create_package!
+ package = project.packages.create!(
+ name: name,
+ version: version,
+ package_type: 'npm'
+ )
+
+ if build.present?
+ package.create_build_info!(pipeline: build.pipeline)
+ end
+
+ ::Packages::CreatePackageFileService.new(package, file_params).execute
+ ::Packages::CreateDependencyService.new(package, package_dependencies).execute
+ ::Packages::Npm::CreateTagService.new(package, dist_tag).execute
+
+ package
+ end
+
+ def current_package_exists?
+ project.packages
+ .npm
+ .with_name(name)
+ .with_version(version)
+ .exists?
+ end
+
+ def name
+ params[:name]
+ end
+
+ def version
+ strong_memoize(:version) do
+ params[:versions].each_key.first
+ end
+ end
+
+ def version_data
+ params[:versions][version]
+ end
+
+ def build
+ params[:build]
+ end
+
+ def dist_tag
+ params['dist-tags'].each_key.first
+ end
+
+ def package_file_name
+ strong_memoize(:package_file_name) do
+ "#{name}-#{version}.tgz"
+ end
+ end
+
+ def attachment
+ strong_memoize(:attachment) do
+ params['_attachments'][package_file_name]
+ end
+ end
+
+ def file_params
+ {
+ file: CarrierWaveStringFile.new(Base64.decode64(attachment['data'])),
+ size: attachment['length'],
+ file_sha1: version_data[:dist][:shasum],
+ file_name: package_file_name
+ }
+ end
+
+ def package_dependencies
+ _version, versions_data = params[:versions].first
+ versions_data
+ end
+ end
+ end
+end
diff --git a/app/services/packages/npm/create_tag_service.rb b/app/services/packages/npm/create_tag_service.rb
new file mode 100644
index 00000000000..82974d0ca4b
--- /dev/null
+++ b/app/services/packages/npm/create_tag_service.rb
@@ -0,0 +1,34 @@
+# frozen_string_literal: true
+module Packages
+ module Npm
+ class CreateTagService
+ include Gitlab::Utils::StrongMemoize
+
+ attr_reader :package, :tag_name
+
+ def initialize(package, tag_name)
+ @package = package
+ @tag_name = tag_name
+ end
+
+ def execute
+ if existing_tag.present?
+ existing_tag.update_column(:package_id, package.id)
+ existing_tag
+ else
+ package.tags.create!(name: tag_name)
+ end
+ end
+
+ private
+
+ def existing_tag
+ strong_memoize(:existing_tag) do
+ Packages::TagsFinder
+ .new(package.project, package.name, package_type: package.package_type)
+ .find_by_name(tag_name)
+ end
+ end
+ end
+ end
+end
diff --git a/app/services/packages/nuget/create_dependency_service.rb b/app/services/packages/nuget/create_dependency_service.rb
new file mode 100644
index 00000000000..2be5db732f6
--- /dev/null
+++ b/app/services/packages/nuget/create_dependency_service.rb
@@ -0,0 +1,71 @@
+# frozen_string_literal: true
+module Packages
+ module Nuget
+ class CreateDependencyService < BaseService
+ def initialize(package, dependencies = [])
+ @package = package
+ @dependencies = dependencies
+ end
+
+ def execute
+ return if @dependencies.empty?
+
+ @package.transaction do
+ create_dependency_links
+ create_dependency_link_metadata
+ end
+ end
+
+ private
+
+ def create_dependency_links
+ ::Packages::CreateDependencyService
+ .new(@package, dependencies_for_create_dependency_service)
+ .execute
+ end
+
+ def create_dependency_link_metadata
+ inserted_links = ::Packages::DependencyLink.preload_dependency
+ .for_package(@package)
+
+ return if inserted_links.empty?
+
+ rows = inserted_links.map do |dependency_link|
+ raw_dependency = raw_dependency_for(dependency_link.dependency)
+
+ next if raw_dependency[:target_framework].blank?
+
+ {
+ dependency_link_id: dependency_link.id,
+ target_framework: raw_dependency[:target_framework]
+ }
+ end
+
+ ::Gitlab::Database.bulk_insert(::Packages::Nuget::DependencyLinkMetadatum.table_name, rows.compact)
+ end
+
+ def raw_dependency_for(dependency)
+ name = dependency.name
+ version = dependency.version_pattern.presence
+
+ @dependencies.find do |raw_dependency|
+ raw_dependency[:name] == name && raw_dependency[:version] == version
+ end
+ end
+
+ def dependencies_for_create_dependency_service
+ names_and_versions = @dependencies.map do |dependency|
+ [dependency[:name], version_or_empty_string(dependency[:version])]
+ end.to_h
+
+ { 'dependencies' => names_and_versions }
+ end
+
+ def version_or_empty_string(version)
+ return '' if version.blank?
+
+ version
+ end
+ end
+ end
+end
diff --git a/app/services/packages/nuget/create_package_service.rb b/app/services/packages/nuget/create_package_service.rb
new file mode 100644
index 00000000000..68ad7f028e4
--- /dev/null
+++ b/app/services/packages/nuget/create_package_service.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+module Packages
+ module Nuget
+ class CreatePackageService < BaseService
+ TEMPORARY_PACKAGE_NAME = 'NuGet.Temporary.Package'
+ PACKAGE_VERSION = '0.0.0'
+
+ def execute
+ project.packages.nuget.create!(
+ name: TEMPORARY_PACKAGE_NAME,
+ version: "#{PACKAGE_VERSION}-#{uuid}"
+ )
+ end
+
+ private
+
+ def uuid
+ SecureRandom.uuid
+ end
+ end
+ end
+end
diff --git a/app/services/packages/nuget/metadata_extraction_service.rb b/app/services/packages/nuget/metadata_extraction_service.rb
new file mode 100644
index 00000000000..6fec398fab0
--- /dev/null
+++ b/app/services/packages/nuget/metadata_extraction_service.rb
@@ -0,0 +1,106 @@
+# frozen_string_literal: true
+
+module Packages
+ module Nuget
+ class MetadataExtractionService
+ include Gitlab::Utils::StrongMemoize
+
+ ExtractionError = Class.new(StandardError)
+
+ XPATHS = {
+ package_name: '//xmlns:package/xmlns:metadata/xmlns:id',
+ package_version: '//xmlns:package/xmlns:metadata/xmlns:version',
+ license_url: '//xmlns:package/xmlns:metadata/xmlns:licenseUrl',
+ project_url: '//xmlns:package/xmlns:metadata/xmlns:projectUrl',
+ icon_url: '//xmlns:package/xmlns:metadata/xmlns:iconUrl'
+ }.freeze
+
+ XPATH_DEPENDENCIES = '//xmlns:package/xmlns:metadata/xmlns:dependencies/xmlns:dependency'
+ XPATH_DEPENDENCY_GROUPS = '//xmlns:package/xmlns:metadata/xmlns:dependencies/xmlns:group'
+ XPATH_TAGS = '//xmlns:package/xmlns:metadata/xmlns:tags'
+
+ MAX_FILE_SIZE = 4.megabytes.freeze
+
+ def initialize(package_file_id)
+ @package_file_id = package_file_id
+ end
+
+ def execute
+ raise ExtractionError.new('invalid package file') unless valid_package_file?
+
+ extract_metadata(nuspec_file)
+ end
+
+ private
+
+ def package_file
+ strong_memoize(:package_file) do
+ ::Packages::PackageFile.find_by_id(@package_file_id)
+ end
+ end
+
+ def valid_package_file?
+ package_file &&
+ package_file.package&.nuget? &&
+ package_file.file.size.positive?
+ end
+
+ def extract_metadata(file)
+ doc = Nokogiri::XML(file)
+
+ XPATHS.transform_values { |query| doc.xpath(query).text.presence }
+ .compact
+ .tap do |metadata|
+ metadata[:package_dependencies] = extract_dependencies(doc)
+ metadata[:package_tags] = extract_tags(doc)
+ end
+ end
+
+ def extract_dependencies(doc)
+ dependencies = []
+
+ doc.xpath(XPATH_DEPENDENCIES).each do |node|
+ dependencies << extract_dependency(node)
+ end
+
+ doc.xpath(XPATH_DEPENDENCY_GROUPS).each do |group_node|
+ target_framework = group_node.attr("targetFramework")
+
+ group_node.xpath("xmlns:dependency").each do |node|
+ dependencies << extract_dependency(node).merge(target_framework: target_framework)
+ end
+ end
+
+ dependencies
+ end
+
+ def extract_dependency(node)
+ {
+ name: node.attr('id'),
+ version: node.attr('version')
+ }.compact
+ end
+
+ def extract_tags(doc)
+ tags = doc.xpath(XPATH_TAGS).text
+
+ return [] if tags.blank?
+
+ tags.split(::Packages::Tag::NUGET_TAGS_SEPARATOR)
+ end
+
+ def nuspec_file
+ package_file.file.use_file do |file_path|
+ Zip::File.open(file_path) do |zip_file|
+ entry = zip_file.glob('*.nuspec').first
+
+ raise ExtractionError.new('nuspec file not found') unless entry
+ raise ExtractionError.new('nuspec file too big') if entry.size > MAX_FILE_SIZE
+
+ entry.get_input_stream.read
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/app/services/packages/nuget/search_service.rb b/app/services/packages/nuget/search_service.rb
new file mode 100644
index 00000000000..f7e09e11819
--- /dev/null
+++ b/app/services/packages/nuget/search_service.rb
@@ -0,0 +1,101 @@
+# frozen_string_literal: true
+
+module Packages
+ module Nuget
+ class SearchService < BaseService
+ include Gitlab::Utils::StrongMemoize
+ include ActiveRecord::ConnectionAdapters::Quoting
+
+ MAX_PER_PAGE = 30
+ MAX_VERSIONS_PER_PACKAGE = 10
+ PRE_RELEASE_VERSION_MATCHING_TERM = '%-%'
+
+ DEFAULT_OPTIONS = {
+ include_prerelease_versions: true,
+ per_page: Kaminari.config.default_per_page,
+ padding: 0
+ }.freeze
+
+ def initialize(project, search_term, options = {})
+ @project = project
+ @search_term = search_term
+ @options = DEFAULT_OPTIONS.merge(options)
+
+ raise ArgumentError, 'negative per_page' if per_page.negative?
+ raise ArgumentError, 'negative padding' if padding.negative?
+ end
+
+ def execute
+ OpenStruct.new(
+ total_count: package_names.total_count,
+ results: search_packages
+ )
+ end
+
+ private
+
+ def search_packages
+ # custom query to get package names and versions as expected from the nuget search api
+ # See https://gitlab.com/gitlab-org/gitlab/-/merge_requests/24182#technical-notes
+ # and https://docs.microsoft.com/en-us/nuget/api/search-query-service-resource
+ subquery_name = :partition_subquery
+ arel_table = Arel::Table.new(:partition_subquery)
+ column_names = Packages::Package.column_names.map do |cn|
+ "#{subquery_name}.#{quote_column_name(cn)}"
+ end
+
+ # rubocop: disable CodeReuse/ActiveRecord
+ pkgs = Packages::Package.select(column_names.join(','))
+ .from(package_names_partition, subquery_name)
+ .where(arel_table[:row_number].lteq(MAX_VERSIONS_PER_PACKAGE))
+
+ return pkgs if include_prerelease_versions?
+
+ # we can't use pkgs.without_version_like since we have a custom from
+ pkgs.where.not(arel_table[:version].matches(PRE_RELEASE_VERSION_MATCHING_TERM))
+ end
+
+ def package_names_partition
+ table_name = quote_table_name(Packages::Package.table_name)
+ name_column = "#{table_name}.#{quote_column_name('name')}"
+ created_at_column = "#{table_name}.#{quote_column_name('created_at')}"
+ select_sql = "ROW_NUMBER() OVER (PARTITION BY #{name_column} ORDER BY #{created_at_column} DESC) AS row_number, #{table_name}.*"
+
+ @project.packages
+ .select(select_sql)
+ .nuget
+ .has_version
+ .without_nuget_temporary_name
+ .with_name(package_names)
+ end
+
+ def package_names
+ strong_memoize(:package_names) do
+ pkgs = @project.packages
+ .nuget
+ .has_version
+ .without_nuget_temporary_name
+ .order_name
+ .select_distinct_name
+ pkgs = pkgs.without_version_like(PRE_RELEASE_VERSION_MATCHING_TERM) unless include_prerelease_versions?
+ pkgs = pkgs.search_by_name(@search_term) if @search_term.present?
+ pkgs.page(0) # we're using a padding
+ .per(per_page)
+ .padding(padding)
+ end
+ end
+
+ def include_prerelease_versions?
+ @options[:include_prerelease_versions]
+ end
+
+ def padding
+ @options[:padding]
+ end
+
+ def per_page
+ [@options[:per_page], MAX_PER_PAGE].min
+ end
+ end
+ end
+end
diff --git a/app/services/packages/nuget/sync_metadatum_service.rb b/app/services/packages/nuget/sync_metadatum_service.rb
new file mode 100644
index 00000000000..ca9cc4d5b78
--- /dev/null
+++ b/app/services/packages/nuget/sync_metadatum_service.rb
@@ -0,0 +1,50 @@
+# frozen_string_literal: true
+
+module Packages
+ module Nuget
+ class SyncMetadatumService
+ include Gitlab::Utils::StrongMemoize
+
+ def initialize(package, metadata)
+ @package = package
+ @metadata = metadata
+ end
+
+ def execute
+ if blank_metadata?
+ metadatum.destroy! if metadatum.persisted?
+ else
+ metadatum.update!(
+ license_url: license_url,
+ project_url: project_url,
+ icon_url: icon_url
+ )
+ end
+ end
+
+ private
+
+ def metadatum
+ strong_memoize(:metadatum) do
+ @package.nuget_metadatum || @package.build_nuget_metadatum
+ end
+ end
+
+ def blank_metadata?
+ project_url.blank? && license_url.blank? && icon_url.blank?
+ end
+
+ def project_url
+ @metadata[:project_url]
+ end
+
+ def license_url
+ @metadata[:license_url]
+ end
+
+ def icon_url
+ @metadata[:icon_url]
+ end
+ end
+ end
+end
diff --git a/app/services/packages/nuget/update_package_from_metadata_service.rb b/app/services/packages/nuget/update_package_from_metadata_service.rb
new file mode 100644
index 00000000000..f72b1386985
--- /dev/null
+++ b/app/services/packages/nuget/update_package_from_metadata_service.rb
@@ -0,0 +1,125 @@
+# frozen_string_literal: true
+
+module Packages
+ module Nuget
+ class UpdatePackageFromMetadataService
+ include Gitlab::Utils::StrongMemoize
+ include ExclusiveLeaseGuard
+
+ # used by ExclusiveLeaseGuard
+ DEFAULT_LEASE_TIMEOUT = 1.hour.to_i.freeze
+
+ InvalidMetadataError = Class.new(StandardError)
+
+ def initialize(package_file)
+ @package_file = package_file
+ end
+
+ def execute
+ raise InvalidMetadataError.new('package name and/or package version not found in metadata') unless valid_metadata?
+
+ try_obtain_lease do
+ @package_file.transaction do
+ package = existing_package ? link_to_existing_package : update_linked_package
+
+ update_package(package)
+
+ # Updating file_name updates the path where the file is stored.
+ # We must pass the file again so that CarrierWave can handle the update
+ @package_file.update!(
+ file_name: package_filename,
+ file: @package_file.file
+ )
+ end
+ end
+ end
+
+ private
+
+ def update_package(package)
+ ::Packages::Nuget::SyncMetadatumService
+ .new(package, metadata.slice(:project_url, :license_url, :icon_url))
+ .execute
+ ::Packages::UpdateTagsService
+ .new(package, package_tags)
+ .execute
+ rescue => e
+ raise InvalidMetadataError, e.message
+ end
+
+ def valid_metadata?
+ package_name.present? && package_version.present?
+ end
+
+ def link_to_existing_package
+ package_to_destroy = @package_file.package
+ # Updating package_id updates the path where the file is stored.
+ # We must pass the file again so that CarrierWave can handle the update
+ @package_file.update!(
+ package_id: existing_package.id,
+ file: @package_file.file
+ )
+ package_to_destroy.destroy!
+ existing_package
+ end
+
+ def update_linked_package
+ @package_file.package.update!(
+ name: package_name,
+ version: package_version
+ )
+
+ ::Packages::Nuget::CreateDependencyService.new(@package_file.package, package_dependencies)
+ .execute
+ @package_file.package
+ end
+
+ def existing_package
+ strong_memoize(:existing_package) do
+ @package_file.project.packages
+ .nuget
+ .with_name(package_name)
+ .with_version(package_version)
+ .first
+ end
+ end
+
+ def package_name
+ metadata[:package_name]
+ end
+
+ def package_version
+ metadata[:package_version]
+ end
+
+ def package_dependencies
+ metadata.fetch(:package_dependencies, [])
+ end
+
+ def package_tags
+ metadata.fetch(:package_tags, [])
+ end
+
+ def metadata
+ strong_memoize(:metadata) do
+ ::Packages::Nuget::MetadataExtractionService.new(@package_file.id).execute
+ end
+ end
+
+ def package_filename
+ "#{package_name.downcase}.#{package_version.downcase}.nupkg"
+ end
+
+ # used by ExclusiveLeaseGuard
+ def lease_key
+ package_id = existing_package ? existing_package.id : @package_file.package_id
+ "packages:nuget:update_package_from_metadata_service:package:#{package_id}"
+ end
+
+ # used by ExclusiveLeaseGuard
+ def lease_timeout
+ DEFAULT_LEASE_TIMEOUT
+ end
+ end
+ end
+end
diff --git a/app/services/packages/pypi/create_package_service.rb b/app/services/packages/pypi/create_package_service.rb
new file mode 100644
index 00000000000..1313fc80e33
--- /dev/null
+++ b/app/services/packages/pypi/create_package_service.rb
@@ -0,0 +1,40 @@
+# frozen_string_literal: true
+
+module Packages
+ module Pypi
+ class CreatePackageService < BaseService
+ include ::Gitlab::Utils::StrongMemoize
+
+ def execute
+ ::Packages::Package.transaction do
+ Packages::Pypi::Metadatum.upsert(
+ package_id: created_package.id,
+ required_python: params[:requires_python]
+ )
+
+ ::Packages::CreatePackageFileService.new(created_package, file_params).execute
+ end
+ end
+
+ private
+
+ def created_package
+ strong_memoize(:created_package) do
+ project
+ .packages
+ .pypi
+ .safe_find_or_create_by!(name: params[:name], version: params[:version])
+ end
+ end
+
+ def file_params
+ {
+ file: params[:content],
+ file_name: params[:content].original_filename,
+ file_md5: params[:md5_digest],
+ file_sha256: params[:sha256_digest]
+ }
+ end
+ end
+ end
+end
diff --git a/app/services/packages/remove_tag_service.rb b/app/services/packages/remove_tag_service.rb
new file mode 100644
index 00000000000..465b85506a6
--- /dev/null
+++ b/app/services/packages/remove_tag_service.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+module Packages
+ class RemoveTagService < BaseService
+ attr_reader :package_tag
+
+ def initialize(package_tag)
+ raise ArgumentError, "Package tag must be set" if package_tag.blank?
+
+ @package_tag = package_tag
+ end
+
+ def execute
+ package_tag.delete
+ end
+ end
+end
diff --git a/app/services/packages/update_tags_service.rb b/app/services/packages/update_tags_service.rb
new file mode 100644
index 00000000000..da50cd3479e
--- /dev/null
+++ b/app/services/packages/update_tags_service.rb
@@ -0,0 +1,41 @@
+# frozen_string_literal: true
+module Packages
+ class UpdateTagsService
+ include Gitlab::Utils::StrongMemoize
+
+ def initialize(package, tags = [])
+ @package = package
+ @tags = tags
+ end
+
+ def execute
+ return if @tags.empty?
+
+ tags_to_destroy = existing_tags - @tags
+ tags_to_create = @tags - existing_tags
+
+ @package.tags.with_name(tags_to_destroy).delete_all if tags_to_destroy.any?
+ ::Gitlab::Database.bulk_insert(Packages::Tag.table_name, rows(tags_to_create)) if tags_to_create.any?
+ end
+
+ private
+
+ def existing_tags
+ strong_memoize(:existing_tags) do
+ @package.tag_names
+ end
+ end
+
+ def rows(tags)
+ now = Time.zone.now
+ tags.map do |tag|
+ {
+ package_id: @package.id,
+ name: tag,
+ created_at: now,
+ updated_at: now
+ }
+ end
+ end
+ end
+end
diff --git a/app/services/personal_access_tokens/last_used_service.rb b/app/services/personal_access_tokens/last_used_service.rb
new file mode 100644
index 00000000000..9066fd1acdf
--- /dev/null
+++ b/app/services/personal_access_tokens/last_used_service.rb
@@ -0,0 +1,28 @@
+# frozen_string_literal: true
+
+module PersonalAccessTokens
+ class LastUsedService
+ def initialize(personal_access_token)
+ @personal_access_token = personal_access_token
+ end
+
+ def execute
+ # Needed to avoid calling service on Oauth tokens
+ return unless @personal_access_token.has_attribute?(:last_used_at)
+
+ # We _only_ want to update last_used_at and not also updated_at (which
+ # would be updated when using #touch).
+ @personal_access_token.update_column(:last_used_at, Time.zone.now) if update?
+ end
+
+ private
+
+ def update?
+ return false if ::Gitlab::Database.read_only?
+
+ last_used = @personal_access_token.last_used_at
+
+ last_used.nil? || (last_used <= 1.day.ago)
+ end
+ end
+end
diff --git a/app/services/post_receive_service.rb b/app/services/post_receive_service.rb
index 65e6ebc17d2..69c9868c75c 100644
--- a/app/services/post_receive_service.rb
+++ b/app/services/post_receive_service.rb
@@ -29,8 +29,6 @@ class PostReceiveService
response.add_alert_message(message)
end
- response.add_alert_message(storage_size_limit_alert)
-
broadcast_message = BroadcastMessage.current_banner_messages&.last&.message
response.add_alert_message(broadcast_message)
@@ -76,19 +74,6 @@ class PostReceiveService
::MergeRequests::GetUrlsService.new(project).execute(params[:changes])
end
-
- private
-
- def storage_size_limit_alert
- return unless repository&.repo_type&.project?
-
- payload = Namespaces::CheckStorageSizeService.new(project.namespace, user).execute.payload
- return unless payload.present?
-
- alert_level = "##### #{payload[:alert_level].to_s.upcase} #####"
-
- [alert_level, payload[:usage_message], payload[:explanation_message]].join("\n")
- end
end
PostReceiveService.prepend_if_ee('EE::PostReceiveService')
diff --git a/app/services/projects/after_import_service.rb b/app/services/projects/after_import_service.rb
index fad2290a47b..b37ae56ba0f 100644
--- a/app/services/projects/after_import_service.rb
+++ b/app/services/projects/after_import_service.rb
@@ -26,7 +26,7 @@ module Projects
message: 'Project housekeeping failed',
project_full_path: @project.full_path,
project_id: @project.id,
- error: e.message
+ 'error.message' => e.message
)
end
diff --git a/app/services/projects/alerting/notify_service.rb b/app/services/projects/alerting/notify_service.rb
index 86c408aeec8..e08bc8efb15 100644
--- a/app/services/projects/alerting/notify_service.rb
+++ b/app/services/projects/alerting/notify_service.rb
@@ -4,7 +4,7 @@ module Projects
module Alerting
class NotifyService < BaseService
include Gitlab::Utils::StrongMemoize
- include IncidentManagement::Settings
+ include ::IncidentManagement::Settings
def execute(token)
return forbidden unless alerts_service_activated?
@@ -55,7 +55,7 @@ module Projects
def find_alert_by_fingerprint(fingerprint)
return unless fingerprint
- AlertManagement::Alert.for_fingerprint(project, fingerprint).first
+ AlertManagement::Alert.not_resolved.for_fingerprint(project, fingerprint).first
end
def send_email?
@@ -65,8 +65,7 @@ module Projects
def process_incident_issues(alert)
return if alert.issue
- IncidentManagement::ProcessAlertWorker
- .perform_async(project.id, parsed_payload, alert.id)
+ ::IncidentManagement::ProcessAlertWorker.perform_async(nil, nil, alert.id)
end
def send_alert_email
@@ -76,7 +75,7 @@ module Projects
end
def parsed_payload
- Gitlab::Alerting::NotificationPayloadParser.call(params.to_h)
+ Gitlab::Alerting::NotificationPayloadParser.call(params.to_h, project)
end
def valid_token?(token)
diff --git a/app/services/projects/batch_forks_count_service.rb b/app/services/projects/batch_forks_count_service.rb
index 6467744a435..d12772b40ff 100644
--- a/app/services/projects/batch_forks_count_service.rb
+++ b/app/services/projects/batch_forks_count_service.rb
@@ -5,6 +5,21 @@
# because the service use maps to retrieve the project ids
module Projects
class BatchForksCountService < Projects::BatchCountService
+ def refresh_cache_and_retrieve_data
+ count_services = @projects.map { |project| count_service.new(project) }
+
+ values = Gitlab::Instrumentation::RedisClusterValidator.allow_cross_slot_commands do
+ Rails.cache.fetch_multi(*(count_services.map { |ser| ser.cache_key } )) { |key| nil }
+ end
+
+ results_per_service = Hash[count_services.zip(values.values)]
+ projects_to_refresh = results_per_service.select { |_k, value| value.nil? }
+ projects_to_refresh = recreate_cache(projects_to_refresh)
+
+ results_per_service.update(projects_to_refresh)
+ results_per_service.transform_keys { |k| k.project }
+ end
+
# rubocop: disable CodeReuse/ActiveRecord
def global_count
@global_count ||= begin
@@ -18,5 +33,13 @@ module Projects
def count_service
::Projects::ForksCountService
end
+
+ def recreate_cache(projects_to_refresh)
+ projects_to_refresh.each_with_object({}) do |(service, _v), hash|
+ count = global_count[service.project.id].to_i
+ service.refresh_cache { count }
+ hash[service] = count
+ end
+ end
end
end
diff --git a/app/services/projects/container_repository/delete_tags_service.rb b/app/services/projects/container_repository/delete_tags_service.rb
index 21081bd077f..5d4059710bb 100644
--- a/app/services/projects/container_repository/delete_tags_service.rb
+++ b/app/services/projects/container_repository/delete_tags_service.rb
@@ -3,6 +3,8 @@
module Projects
module ContainerRepository
class DeleteTagsService < BaseService
+ LOG_DATA_BASE = { service_class: self.to_s }.freeze
+
def execute(container_repository)
return error('access denied') unless can?(current_user, :destroy_container_image, project)
@@ -51,10 +53,27 @@ module Projects
def smart_delete(container_repository, tag_names)
fast_delete_enabled = Feature.enabled?(:container_registry_fast_tag_delete, default_enabled: true)
- if fast_delete_enabled && container_repository.client.supports_tag_delete?
- fast_delete(container_repository, tag_names)
+ response = if fast_delete_enabled && container_repository.client.supports_tag_delete?
+ fast_delete(container_repository, tag_names)
+ else
+ slow_delete(container_repository, tag_names)
+ end
+
+ response.tap { |r| log_response(r, container_repository) }
+ end
+
+ def log_response(response, container_repository)
+ log_data = LOG_DATA_BASE.merge(
+ container_repository_id: container_repository.id,
+ message: 'deleted tags'
+ )
+
+ if response[:status] == :success
+ log_data[:deleted_tags_count] = response[:deleted].size
+ log_info(log_data)
else
- slow_delete(container_repository, tag_names)
+ log_data[:message] = response[:message]
+ log_error(log_data)
end
end
diff --git a/app/services/projects/create_service.rb b/app/services/projects/create_service.rb
index bffd443c49f..6569277ad9d 100644
--- a/app/services/projects/create_service.rb
+++ b/app/services/projects/create_service.rb
@@ -84,8 +84,12 @@ module Projects
def after_create_actions
log_info("#{@project.owner.name} created a new project \"#{@project.full_name}\"")
+ # Skip writing the config for project imports/forks because it
+ # will always fail since the Git directory doesn't exist until
+ # a background job creates it (see Project#add_import_job).
+ @project.write_repository_config unless @project.import?
+
unless @project.gitlab_project_import?
- @project.write_repository_config
@project.create_wiki unless skip_wiki?
end
@@ -103,12 +107,13 @@ module Projects
create_readme if @initialize_with_readme
end
- # Refresh the current user's authorizations inline (so they can access the
- # project immediately after this request completes), and any other affected
- # users in the background
+ # Add an authorization for the current user authorizations inline
+ # (so they can access the project immediately after this request
+ # completes), and any other affected users in the background
def setup_authorizations
if @project.group
- current_user.refresh_authorized_projects
+ current_user.project_authorizations.create!(project: @project,
+ access_level: @project.group.max_member_access_for_user(current_user))
if Feature.enabled?(:specialized_project_authorization_workers)
AuthorizedProjectUpdate::ProjectCreateWorker.perform_async(@project.id)
@@ -131,7 +136,7 @@ module Projects
def create_readme
commit_attrs = {
- branch_name: 'master',
+ branch_name: Gitlab::CurrentSettings.default_branch_name.presence || 'master',
commit_message: 'Initial commit',
file_path: 'README.md',
file_content: "# #{@project.name}\n\n#{@project.description}"
diff --git a/app/services/projects/forks_count_service.rb b/app/services/projects/forks_count_service.rb
index ca85e2dc281..848d8d54104 100644
--- a/app/services/projects/forks_count_service.rb
+++ b/app/services/projects/forks_count_service.rb
@@ -3,6 +3,8 @@
module Projects
# Service class for getting and caching the number of forks of a project.
class ForksCountService < Projects::CountService
+ attr_reader :project
+
def cache_key_name
'forks_count'
end
diff --git a/app/services/projects/group_links/create_service.rb b/app/services/projects/group_links/create_service.rb
index 2ba3cd6694f..3c3cab26fb5 100644
--- a/app/services/projects/group_links/create_service.rb
+++ b/app/services/projects/group_links/create_service.rb
@@ -13,12 +13,32 @@ module Projects
)
if link.save
- group.refresh_members_authorized_projects
+ setup_authorizations(group)
success(link: link)
else
error(link.errors.full_messages.to_sentence, 409)
end
end
+
+ private
+
+ def setup_authorizations(group)
+ if Feature.enabled?(:specialized_project_authorization_project_share_worker)
+ AuthorizedProjectUpdate::ProjectGroupLinkCreateWorker.perform_async(project.id, group.id)
+
+ # AuthorizedProjectsWorker uses an exclusive lease per user but
+ # specialized workers might have synchronization issues. Until we
+ # compare the inconsistency rates of both approaches, we still run
+ # AuthorizedProjectsWorker but with some delay and lower urgency as a
+ # safety net.
+ group.refresh_members_authorized_projects(
+ blocking: false,
+ priority: UserProjectAccessChangedService::LOW_PRIORITY
+ )
+ else
+ group.refresh_members_authorized_projects(blocking: false)
+ end
+ end
end
end
end
diff --git a/app/services/projects/operations/update_service.rb b/app/services/projects/operations/update_service.rb
index 7aa7ea73639..7af489c3751 100644
--- a/app/services/projects/operations/update_service.rb
+++ b/app/services/projects/operations/update_service.rb
@@ -108,7 +108,18 @@ module Projects
end
def incident_management_setting_params
- params.slice(:incident_management_setting_attributes)
+ attrs = params[:incident_management_setting_attributes]
+ return {} unless attrs
+
+ regenerate_token = attrs.delete(:regenerate_token)
+
+ if regenerate_token
+ attrs[:pagerduty_token] = nil
+ else
+ attrs = attrs.except(:pagerduty_token)
+ end
+
+ { incident_management_setting_attributes: attrs }
end
end
end
diff --git a/app/services/projects/prometheus/alerts/create_events_service.rb b/app/services/projects/prometheus/alerts/create_events_service.rb
deleted file mode 100644
index 4fcf841314b..00000000000
--- a/app/services/projects/prometheus/alerts/create_events_service.rb
+++ /dev/null
@@ -1,71 +0,0 @@
-# frozen_string_literal: true
-
-module Projects
- module Prometheus
- module Alerts
- # Persists a series of Prometheus alert events as list of PrometheusAlertEvent.
- class CreateEventsService < BaseService
- def execute
- create_events_from(alerts)
- end
-
- private
-
- def create_events_from(alerts)
- Array.wrap(alerts).map { |alert| create_event(alert) }.compact
- end
-
- def create_event(payload)
- parsed_alert = Gitlab::Alerting::Alert.new(project: project, payload: payload)
-
- return unless parsed_alert.valid?
-
- if parsed_alert.gitlab_managed?
- create_managed_prometheus_alert_event(parsed_alert)
- else
- create_self_managed_prometheus_alert_event(parsed_alert)
- end
- end
-
- def alerts
- params['alerts']
- end
-
- def find_alert(metric)
- Projects::Prometheus::AlertsFinder
- .new(project: project, metric: metric)
- .execute
- .first
- end
-
- def create_managed_prometheus_alert_event(parsed_alert)
- alert = find_alert(parsed_alert.metric_id)
- event = PrometheusAlertEvent.find_or_initialize_by_payload_key(parsed_alert.project, alert, parsed_alert.gitlab_fingerprint)
-
- set_status(parsed_alert, event)
- end
-
- def create_self_managed_prometheus_alert_event(parsed_alert)
- event = SelfManagedPrometheusAlertEvent.find_or_initialize_by_payload_key(parsed_alert.project, parsed_alert.gitlab_fingerprint) do |event|
- event.environment = parsed_alert.environment
- event.title = parsed_alert.title
- event.query_expression = parsed_alert.full_query
- end
-
- set_status(parsed_alert, event)
- end
-
- def set_status(parsed_alert, event)
- persisted = case parsed_alert.status
- when 'firing'
- event.fire(parsed_alert.starts_at)
- when 'resolved'
- event.resolve(parsed_alert.ends_at)
- end
-
- event if persisted
- end
- end
- end
- end
-end
diff --git a/app/services/projects/prometheus/alerts/notify_service.rb b/app/services/projects/prometheus/alerts/notify_service.rb
index 877a4f99a94..ea557ebe20f 100644
--- a/app/services/projects/prometheus/alerts/notify_service.rb
+++ b/app/services/projects/prometheus/alerts/notify_service.rb
@@ -5,7 +5,7 @@ module Projects
module Alerts
class NotifyService < BaseService
include Gitlab::Utils::StrongMemoize
- include IncidentManagement::Settings
+ include ::IncidentManagement::Settings
# This set of keys identifies a payload as a valid Prometheus
# payload and thus processable by this service. See also
@@ -23,9 +23,7 @@ module Projects
return unauthorized unless valid_alert_manager_token?(token)
process_prometheus_alerts
- persist_events
send_alert_email if send_email?
- process_incident_issues if process_issues?
ServiceResponse.success
end
@@ -132,13 +130,6 @@ module Projects
.prometheus_alerts_fired(project, firings)
end
- def process_incident_issues
- alerts.each do |alert|
- IncidentManagement::ProcessPrometheusAlertWorker
- .perform_async(project.id, alert.to_h)
- end
- end
-
def process_prometheus_alerts
alerts.each do |alert|
AlertManagement::ProcessPrometheusAlertService
@@ -147,10 +138,6 @@ module Projects
end
end
- def persist_events
- CreateEventsService.new(project, nil, params).execute
- end
-
def bad_request
ServiceResponse.error(message: 'Bad Request', http_status: :bad_request)
end
diff --git a/app/services/projects/propagate_service_template.rb b/app/services/projects/propagate_service_template.rb
index 4adcda042d1..b6465810fde 100644
--- a/app/services/projects/propagate_service_template.rb
+++ b/app/services/projects/propagate_service_template.rb
@@ -26,7 +26,7 @@ module Projects
def propagate_projects_with_template
loop do
- batch = Project.uncached { project_ids_without_integration }
+ batch = Project.uncached { Project.ids_without_integration(template, BATCH_SIZE) }
bulk_create_from_template(batch) unless batch.empty?
@@ -50,22 +50,6 @@ module Projects
end
end
- # rubocop: disable CodeReuse/ActiveRecord
- def project_ids_without_integration
- services = Service
- .select('1')
- .where('services.project_id = projects.id')
- .where(type: template.type)
-
- Project
- .where('NOT EXISTS (?)', services)
- .where(pending_delete: false)
- .where(archived: false)
- .limit(BATCH_SIZE)
- .pluck(:id)
- end
- # rubocop: enable CodeReuse/ActiveRecord
-
def bulk_insert(klass, columns, values_array)
items_to_insert = values_array.map { |array| Hash[columns.zip(array)] }
diff --git a/app/services/projects/update_remote_mirror_service.rb b/app/services/projects/update_remote_mirror_service.rb
index 5f8ef75a8d7..d6c0d647468 100644
--- a/app/services/projects/update_remote_mirror_service.rb
+++ b/app/services/projects/update_remote_mirror_service.rb
@@ -29,7 +29,7 @@ module Projects
remote_mirror.ensure_remote!
# https://gitlab.com/gitlab-org/gitaly/-/issues/2670
- if Feature.disabled?(:gitaly_ruby_remote_branches_ls_remote)
+ if Feature.disabled?(:gitaly_ruby_remote_branches_ls_remote, default_enabled: true)
repository.fetch_remote(remote_mirror.remote_name, ssh_auth: remote_mirror, no_tags: true)
end
diff --git a/app/services/projects/update_repository_storage_service.rb b/app/services/projects/update_repository_storage_service.rb
index fa8d4c5aa5f..7b346c09635 100644
--- a/app/services/projects/update_repository_storage_service.rb
+++ b/app/services/projects/update_repository_storage_service.rb
@@ -14,7 +14,11 @@ module Projects
end
def execute
- repository_storage_move.start!
+ repository_storage_move.with_lock do
+ return ServiceResponse.success unless repository_storage_move.scheduled? # rubocop:disable Cop/AvoidReturnFromBlocks
+
+ repository_storage_move.start!
+ end
raise SameFilesystemError if same_filesystem?(repository.storage, destination_storage_name)
@@ -79,8 +83,6 @@ module Projects
full_path
)
- new_repository.create_repository
-
new_repository.replicate(raw_repository)
new_checksum = new_repository.checksum
@@ -93,25 +95,25 @@ module Projects
old_repository_storage = project.repository_storage
new_project_path = moved_path(project.disk_path)
- # Notice that the block passed to `run_after_commit` will run with `project`
+ # Notice that the block passed to `run_after_commit` will run with `repository_storage_move`
# as its context
- project.run_after_commit do
+ repository_storage_move.run_after_commit do
GitlabShellWorker.perform_async(:mv_repository,
old_repository_storage,
- disk_path,
+ project.disk_path,
new_project_path)
- if wiki.repository_exists?
+ if project.wiki.repository_exists?
GitlabShellWorker.perform_async(:mv_repository,
old_repository_storage,
- wiki.disk_path,
+ project.wiki.disk_path,
"#{new_project_path}.wiki")
end
- if design_repository.exists?
+ if project.design_repository.exists?
GitlabShellWorker.perform_async(:mv_repository,
old_repository_storage,
- design_repository.disk_path,
+ project.design_repository.disk_path,
"#{new_project_path}.design")
end
end
diff --git a/app/services/prometheus/proxy_service.rb b/app/services/prometheus/proxy_service.rb
index e0bc5518d30..33635796771 100644
--- a/app/services/prometheus/proxy_service.rb
+++ b/app/services/prometheus/proxy_service.rb
@@ -22,16 +22,20 @@ module Prometheus
attr_accessor :proxyable, :method, :path, :params
+ PROMETHEUS_QUERY_API = 'query'
+ PROMETHEUS_QUERY_RANGE_API = 'query_range'
+ PROMETHEUS_SERIES_API = 'series'
+
PROXY_SUPPORT = {
- 'query' => {
+ PROMETHEUS_QUERY_API => {
method: ['GET'],
params: %w(query time timeout)
},
- 'query_range' => {
+ PROMETHEUS_QUERY_RANGE_API => {
method: ['GET'],
params: %w(query start end step timeout)
},
- 'series' => {
+ PROMETHEUS_SERIES_API => {
method: %w(GET),
params: %w(match start end)
}
diff --git a/app/services/prometheus/proxy_variable_substitution_service.rb b/app/services/prometheus/proxy_variable_substitution_service.rb
index 10fb3a8c1b5..820b551c30a 100644
--- a/app/services/prometheus/proxy_variable_substitution_service.rb
+++ b/app/services/prometheus/proxy_variable_substitution_service.rb
@@ -19,10 +19,52 @@ module Prometheus
:substitute_params,
:substitute_variables
+ # @param environment [Environment]
+ # @param params [Hash<Symbol,Any>]
+ # @param params - query [String] The Prometheus query string.
+ # @param params - start [String] (optional) A time string in the rfc3339 format.
+ # @param params - start_time [String] (optional) A time string in the rfc3339 format.
+ # @param params - end [String] (optional) A time string in the rfc3339 format.
+ # @param params - end_time [String] (optional) A time string in the rfc3339 format.
+ # @param params - variables [ActionController::Parameters] (optional) Variables with their values.
+ # The keys in the Hash should be the name of the variable. The value should be the value of the
+ # variable. Ex: `ActionController::Parameters.new(variable1: 'value 1', variable2: 'value 2').permit!`
+ # @return [Prometheus::ProxyVariableSubstitutionService]
+ #
+ # Example:
+ # Prometheus::ProxyVariableSubstitutionService.new(environment, {
+ # params: {
+ # start_time: '2020-07-03T06:08:36Z',
+ # end_time: '2020-07-03T14:08:52Z',
+ # query: 'up{instance="{{instance}}"}',
+ # variables: { instance: 'srv1' }
+ # }
+ # })
def initialize(environment, params = {})
@environment, @params = environment, params.deep_dup
end
+ # @return - params [Hash<Symbol,Any>] Returns a Hash containing a params key which is
+ # similar to the `params` that is passed to the initialize method with 2 differences:
+ # 1. Variables in the query string are substituted with their values.
+ # If a variable present in the query string has no known value (values
+ # are obtained from the `variables` Hash in `params` or from
+ # `Gitlab::Prometheus::QueryVariables.call`), it will not be substituted.
+ # 2. `start` and `end` keys are added, with their values copied from `start_time`
+ # and `end_time`.
+ #
+ # Example output:
+ #
+ # {
+ # params: {
+ # start_time: '2020-07-03T06:08:36Z',
+ # start: '2020-07-03T06:08:36Z',
+ # end_time: '2020-07-03T14:08:52Z',
+ # end: '2020-07-03T14:08:52Z',
+ # query: 'up{instance="srv1"}',
+ # variables: { instance: 'srv1' }
+ # }
+ # }
def execute
execute_steps
end
diff --git a/app/services/releases/create_evidence_service.rb b/app/services/releases/create_evidence_service.rb
index ac13dce1729..9c370722d2c 100644
--- a/app/services/releases/create_evidence_service.rb
+++ b/app/services/releases/create_evidence_service.rb
@@ -10,7 +10,7 @@ module Releases
def execute
evidence = release.evidences.build
- summary = Evidences::EvidenceSerializer.new.represent(evidence) # rubocop: disable CodeReuse/Serializer
+ summary = ::Evidences::EvidenceSerializer.new.represent(evidence, evidence_options) # rubocop: disable CodeReuse/Serializer
evidence.summary = summary
# TODO: fix the sha generating https://gitlab.com/gitlab-org/gitlab/-/issues/209000
evidence.summary_sha = Gitlab::CryptoHelper.sha256(summary)
@@ -20,6 +20,12 @@ module Releases
private
- attr_reader :release
+ attr_reader :release, :pipeline
+
+ def evidence_options
+ {}
+ end
end
end
+
+Releases::CreateEvidenceService.prepend_if_ee('EE::Releases::CreateEvidenceService')
diff --git a/app/services/repositories/base_service.rb b/app/services/repositories/base_service.rb
index a99a65b7edb..efb6f6de8db 100644
--- a/app/services/repositories/base_service.rb
+++ b/app/services/repositories/base_service.rb
@@ -8,20 +8,19 @@ class Repositories::BaseService < BaseService
attr_reader :repository
delegate :container, :disk_path, :full_path, to: :repository
- delegate :repository_storage, to: :container
def initialize(repository)
@repository = repository
end
def repo_exists?(path)
- gitlab_shell.repository_exists?(repository_storage, path + '.git')
+ gitlab_shell.repository_exists?(repository.shard, path + '.git')
end
def mv_repository(from_path, to_path)
return true unless repo_exists?(from_path)
- gitlab_shell.mv_repository(repository_storage, from_path, to_path)
+ gitlab_shell.mv_repository(repository.shard, from_path, to_path)
end
# Build a path for removing repositories
diff --git a/app/services/repositories/destroy_service.rb b/app/services/repositories/destroy_service.rb
index b12d0744387..1e34dfbe398 100644
--- a/app/services/repositories/destroy_service.rb
+++ b/app/services/repositories/destroy_service.rb
@@ -14,8 +14,17 @@ class Repositories::DestroyService < Repositories::BaseService
log_info(%Q{Repository "#{disk_path}" moved to "#{removal_path}" for repository "#{full_path}"})
current_repository = repository
- container.run_after_commit do
+
+ # Because GitlabShellWorker is inside a run_after_commit callback it will
+ # never be triggered on a read-only instance.
+ #
+ # Issue: https://gitlab.com/gitlab-org/gitlab/-/issues/223272
+ if Gitlab::Database.read_only?
Repositories::ShellDestroyService.new(current_repository).execute
+ else
+ container.run_after_commit do
+ Repositories::ShellDestroyService.new(current_repository).execute
+ end
end
log_info("Repository \"#{full_path}\" was removed")
diff --git a/app/services/repositories/shell_destroy_service.rb b/app/services/repositories/shell_destroy_service.rb
index 2f5af10e24c..d25cb28c6d7 100644
--- a/app/services/repositories/shell_destroy_service.rb
+++ b/app/services/repositories/shell_destroy_service.rb
@@ -9,7 +9,7 @@ class Repositories::ShellDestroyService < Repositories::BaseService
GitlabShellWorker.perform_in(delay,
:remove_repository,
- repository_storage,
+ repository.shard,
removal_path)
end
end
diff --git a/app/services/resource_access_tokens/create_service.rb b/app/services/resource_access_tokens/create_service.rb
index c8e86e68383..2d0a78feb8e 100644
--- a/app/services/resource_access_tokens/create_service.rb
+++ b/app/services/resource_access_tokens/create_service.rb
@@ -13,8 +13,6 @@ module ResourceAccessTokens
return unless feature_enabled?
return error("User does not have permission to create #{resource_type} Access Token") unless has_permission_to_create?
- # We skip authorization by default, since the user creating the bot is not an admin
- # and project/group bot users are not created via sign-up
user = create_user
return error(user.errors.full_messages.to_sentence) unless user.persisted?
@@ -49,6 +47,11 @@ module ResourceAccessTokens
end
def create_user
+ # Even project maintainers can create project access tokens, which in turn
+ # creates a bot user, and so it becomes necessary to have `skip_authorization: true`
+ # since someone like a project maintainer does not inherently have the ability
+ # to create a new user in the system.
+
Users::CreateService.new(current_user, default_user_params).execute(skip_authorization: true)
end
@@ -57,7 +60,8 @@ module ResourceAccessTokens
name: params[:name] || "#{resource.name.to_s.humanize} bot",
email: generate_email,
username: generate_username,
- user_type: "#{resource_type}_bot".to_sym
+ user_type: "#{resource_type}_bot".to_sym,
+ skip_confirmation: true # Bot users should always have their emails confirmed.
}
end
diff --git a/app/services/resource_access_tokens/revoke_service.rb b/app/services/resource_access_tokens/revoke_service.rb
index eea6bff572b..efeb0bfb8d5 100644
--- a/app/services/resource_access_tokens/revoke_service.rb
+++ b/app/services/resource_access_tokens/revoke_service.rb
@@ -35,7 +35,7 @@ module ResourceAccessTokens
attr_reader :current_user, :access_token, :bot_user, :resource
def remove_member
- ::Members::DestroyService.new(current_user).execute(find_member)
+ ::Members::DestroyService.new(current_user).execute(find_member, destroy_bot: true)
end
def migrate_to_ghost_user
diff --git a/app/services/resource_events/base_synthetic_notes_builder_service.rb b/app/services/resource_events/base_synthetic_notes_builder_service.rb
index db8bf6e4b74..a2d78ec67c3 100644
--- a/app/services/resource_events/base_synthetic_notes_builder_service.rb
+++ b/app/services/resource_events/base_synthetic_notes_builder_service.rb
@@ -23,11 +23,25 @@ module ResourceEvents
private
- def since_fetch_at(events)
+ def apply_common_filters(events)
+ events = apply_last_fetched_at(events)
+ events = apply_fetch_until(events)
+
+ events
+ end
+
+ def apply_last_fetched_at(events)
return events unless params[:last_fetched_at].present?
- last_fetched_at = Time.zone.at(params.fetch(:last_fetched_at).to_i)
- events.created_after(last_fetched_at - NotesFinder::FETCH_OVERLAP)
+ last_fetched_at = params[:last_fetched_at] - NotesFinder::FETCH_OVERLAP
+
+ events.created_after(last_fetched_at)
+ end
+
+ def apply_fetch_until(events)
+ return events unless params[:fetch_until].present?
+
+ events.created_on_or_before(params[:fetch_until])
end
def resource_parent
diff --git a/app/services/resource_events/change_state_service.rb b/app/services/resource_events/change_state_service.rb
index 8beb76d8aee..202972c1efd 100644
--- a/app/services/resource_events/change_state_service.rb
+++ b/app/services/resource_events/change_state_service.rb
@@ -8,12 +8,18 @@ module ResourceEvents
@user, @resource = user, resource
end
- def execute(state)
+ def execute(params)
+ @params = params
+
ResourceStateEvent.create(
user: user,
issue: issue,
merge_request: merge_request,
+ source_commit: commit_id_of(mentionable_source),
+ source_merge_request_id: merge_request_id_of(mentionable_source),
state: ResourceStateEvent.states[state],
+ close_after_error_tracking_resolve: close_after_error_tracking_resolve,
+ close_auto_resolve_prometheus_alert: close_auto_resolve_prometheus_alert,
created_at: Time.zone.now)
resource.expire_note_etag_cache
@@ -21,6 +27,36 @@ module ResourceEvents
private
+ attr_reader :params
+
+ def close_auto_resolve_prometheus_alert
+ params[:close_auto_resolve_prometheus_alert] || false
+ end
+
+ def close_after_error_tracking_resolve
+ params[:close_after_error_tracking_resolve] || false
+ end
+
+ def state
+ params[:status]
+ end
+
+ def mentionable_source
+ params[:mentionable_source]
+ end
+
+ def commit_id_of(mentionable_source)
+ return unless mentionable_source.is_a?(Commit)
+
+ mentionable_source.id[0...40]
+ end
+
+ def merge_request_id_of(mentionable_source)
+ return unless mentionable_source.is_a?(MergeRequest)
+
+ mentionable_source.id
+ end
+
def issue
return unless resource.is_a?(Issue)
diff --git a/app/services/resource_events/synthetic_label_notes_builder_service.rb b/app/services/resource_events/synthetic_label_notes_builder_service.rb
index fd128101b49..5915ea938cf 100644
--- a/app/services/resource_events/synthetic_label_notes_builder_service.rb
+++ b/app/services/resource_events/synthetic_label_notes_builder_service.rb
@@ -19,7 +19,7 @@ module ResourceEvents
return [] unless resource.respond_to?(:resource_label_events)
events = resource.resource_label_events.includes(:label, user: :status) # rubocop: disable CodeReuse/ActiveRecord
- events = since_fetch_at(events)
+ events = apply_common_filters(events)
events.group_by { |event| event.discussion_id }
end
diff --git a/app/services/resource_events/synthetic_milestone_notes_builder_service.rb b/app/services/resource_events/synthetic_milestone_notes_builder_service.rb
index cc6383d7083..10acf94e22b 100644
--- a/app/services/resource_events/synthetic_milestone_notes_builder_service.rb
+++ b/app/services/resource_events/synthetic_milestone_notes_builder_service.rb
@@ -19,7 +19,7 @@ module ResourceEvents
return [] unless resource.respond_to?(:resource_milestone_events)
events = resource.resource_milestone_events.includes(user: :status) # rubocop: disable CodeReuse/ActiveRecord
- since_fetch_at(events)
+ apply_common_filters(events)
end
end
end
diff --git a/app/services/resource_events/synthetic_state_notes_builder_service.rb b/app/services/resource_events/synthetic_state_notes_builder_service.rb
index 763134d98d8..71d40200365 100644
--- a/app/services/resource_events/synthetic_state_notes_builder_service.rb
+++ b/app/services/resource_events/synthetic_state_notes_builder_service.rb
@@ -14,7 +14,7 @@ module ResourceEvents
return [] unless resource.respond_to?(:resource_state_events)
events = resource.resource_state_events.includes(user: :status) # rubocop: disable CodeReuse/ActiveRecord
- since_fetch_at(events)
+ apply_common_filters(events)
end
end
end
diff --git a/app/services/service_desk_settings/update_service.rb b/app/services/service_desk_settings/update_service.rb
new file mode 100644
index 00000000000..08106b04d18
--- /dev/null
+++ b/app/services/service_desk_settings/update_service.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+module ServiceDeskSettings
+ class UpdateService < BaseService
+ def execute
+ settings = ServiceDeskSetting.safe_find_or_create_by!(project_id: project.id)
+
+ unless ::Feature.enabled?(:service_desk_custom_address, project)
+ params.delete(:project_key)
+ end
+
+ if settings.update(params)
+ success
+ else
+ error(settings.errors.full_messages.to_sentence)
+ end
+ end
+ end
+end
diff --git a/app/services/snippets/base_service.rb b/app/services/snippets/base_service.rb
index 5d1fe815d83..d9e8326f159 100644
--- a/app/services/snippets/base_service.rb
+++ b/app/services/snippets/base_service.rb
@@ -6,13 +6,15 @@ module Snippets
CreateRepositoryError = Class.new(StandardError)
- attr_reader :uploaded_assets, :snippet_files
+ attr_reader :uploaded_assets, :snippet_actions
def initialize(project, user = nil, params = {})
super
@uploaded_assets = Array(@params.delete(:files).presence)
- @snippet_files = SnippetInputActionCollection.new(Array(@params.delete(:snippet_files).presence))
+
+ input_actions = Array(@params.delete(:snippet_actions).presence)
+ @snippet_actions = SnippetInputActionCollection.new(input_actions, allowed_actions: restricted_files_actions)
filter_spam_check_params
end
@@ -30,18 +32,18 @@ module Snippets
end
def valid_params?
- return true if snippet_files.empty?
+ return true if snippet_actions.empty?
- (params.keys & [:content, :file_name]).none? && snippet_files.valid?
+ (params.keys & [:content, :file_name]).none? && snippet_actions.valid?
end
def invalid_params_error(snippet)
- if snippet_files.valid?
+ if snippet_actions.valid?
[:content, :file_name].each do |key|
snippet.errors.add(key, 'and snippet files cannot be used together') if params.key?(key)
end
else
- snippet.errors.add(:snippet_files, 'have invalid data')
+ snippet.errors.add(:snippet_actions, 'have invalid data')
end
snippet_error_response(snippet, 403)
@@ -73,11 +75,15 @@ module Snippets
end
def files_to_commit(snippet)
- snippet_files.to_commit_actions.presence || build_actions_from_params(snippet)
+ snippet_actions.to_commit_actions.presence || build_actions_from_params(snippet)
end
def build_actions_from_params(snippet)
raise NotImplementedError
end
+
+ def restricted_files_actions
+ nil
+ end
end
end
diff --git a/app/services/snippets/create_service.rb b/app/services/snippets/create_service.rb
index 7b477621da3..dab47de8a36 100644
--- a/app/services/snippets/create_service.rb
+++ b/app/services/snippets/create_service.rb
@@ -37,13 +37,13 @@ module Snippets
end
end
- # If the snippet_files param is present
+ # If the snippet_actions param is present
# we need to fill content and file_name from
# the model
def create_params
- return params if snippet_files.empty?
+ return params if snippet_actions.empty?
- params.merge(content: snippet_files[0].content, file_name: snippet_files[0].file_path)
+ params.merge(content: snippet_actions[0].content, file_name: snippet_actions[0].file_path)
end
def save_and_commit
@@ -100,5 +100,9 @@ module Snippets
def build_actions_from_params(_snippet)
[{ file_path: params[:file_name], content: params[:content] }]
end
+
+ def restricted_files_actions
+ :create
+ end
end
end
diff --git a/app/services/snippets/update_service.rb b/app/services/snippets/update_service.rb
index 6cdc2c374da..00146389e22 100644
--- a/app/services/snippets/update_service.rb
+++ b/app/services/snippets/update_service.rb
@@ -37,8 +37,9 @@ module Snippets
# is implemented.
# Once we can perform different operations through this service
# we won't need to keep track of the `content` and `file_name` fields
- if snippet_files.any?
- params.merge!(content: snippet_files[0].content, file_name: snippet_files[0].file_path)
+ if snippet_actions.any?
+ params[:content] = snippet_actions[0].content if snippet_actions[0].content
+ params[:file_name] = snippet_actions[0].file_path
end
snippet.assign_attributes(params)
@@ -108,7 +109,7 @@ module Snippets
end
def committable_attributes?
- (params.stringify_keys.keys & COMMITTABLE_ATTRIBUTES).present? || snippet_files.any?
+ (params.stringify_keys.keys & COMMITTABLE_ATTRIBUTES).present? || snippet_actions.any?
end
def build_actions_from_params(snippet)
diff --git a/app/services/snippets/update_statistics_service.rb b/app/services/snippets/update_statistics_service.rb
new file mode 100644
index 00000000000..295cb963ccc
--- /dev/null
+++ b/app/services/snippets/update_statistics_service.rb
@@ -0,0 +1,28 @@
+# frozen_string_literal: true
+
+module Snippets
+ class UpdateStatisticsService
+ attr_reader :snippet
+
+ def initialize(snippet)
+ @snippet = snippet
+ end
+
+ def execute
+ unless snippet.repository_exists?
+ return ServiceResponse.error(message: 'Invalid snippet repository', http_status: 400)
+ end
+
+ snippet.repository.expire_statistics_caches
+ statistics.refresh!
+
+ ServiceResponse.success(message: 'Snippet statistics successfully updated.')
+ end
+
+ private
+
+ def statistics
+ @statistics ||= snippet.statistics || snippet.build_statistics
+ end
+ end
+end
diff --git a/app/services/spam/spam_verdict_service.rb b/app/services/spam/spam_verdict_service.rb
index 68f1135ae28..7de3bad607a 100644
--- a/app/services/spam/spam_verdict_service.rb
+++ b/app/services/spam/spam_verdict_service.rb
@@ -14,7 +14,7 @@ module Spam
end
def execute
- external_spam_check_result = spam_verdict
+ external_spam_check_result = external_verdict
akismet_result = akismet_verdict
# filter out anything we don't recognise, including nils.
@@ -38,7 +38,7 @@ module Spam
end
end
- def spam_verdict
+ def external_verdict
return unless Gitlab::CurrentSettings.spam_check_endpoint_enabled
return if endpoint_url.blank?
@@ -50,17 +50,14 @@ module Spam
# @TODO metrics/logging
# Expecting:
# error: (string or nil)
- # result: (string or nil)
- verdict = json_result[:verdict]
- return unless SUPPORTED_VERDICTS.include?(verdict)
-
+ # verdict: (string or nil)
# @TODO log if json_result[:error]
json_result[:verdict]
rescue *Gitlab::HTTP::HTTP_ERRORS => e
# @TODO: log error via try_post https://gitlab.com/gitlab-org/gitlab/-/issues/219223
Gitlab::ErrorTracking.log_exception(e)
- return
+ nil
rescue
# @TODO log
ALLOW
diff --git a/app/services/system_note_service.rb b/app/services/system_note_service.rb
index 6bf04c55415..db5693960b2 100644
--- a/app/services/system_note_service.rb
+++ b/app/services/system_note_service.rb
@@ -273,6 +273,38 @@ module SystemNoteService
::SystemNotes::DesignManagementService.new(noteable: design.issue, project: design.project, author: discussion_note.author).design_discussion_added(discussion_note)
end
+
+ # Called when the merge request is approved by user
+ #
+ # noteable - Noteable object
+ # user - User performing approve
+ #
+ # Example Note text:
+ #
+ # "approved this merge request"
+ #
+ # Returns the created Note object
+ def approve_mr(noteable, user)
+ merge_requests_service(noteable, noteable.project, user).approve_mr
+ end
+
+ def unapprove_mr(noteable, user)
+ merge_requests_service(noteable, noteable.project, user).unapprove_mr
+ end
+
+ def change_alert_status(alert, author)
+ ::SystemNotes::AlertManagementService.new(noteable: alert, project: alert.project, author: author).change_alert_status(alert)
+ end
+
+ def new_alert_issue(alert, issue, author)
+ ::SystemNotes::AlertManagementService.new(noteable: alert, project: alert.project, author: author).new_alert_issue(alert, issue)
+ end
+
+ private
+
+ def merge_requests_service(noteable, project, author)
+ ::SystemNotes::MergeRequestsService.new(noteable: noteable, project: project, author: author)
+ end
end
SystemNoteService.prepend_if_ee('EE::SystemNoteService')
diff --git a/app/services/system_notes/alert_management_service.rb b/app/services/system_notes/alert_management_service.rb
new file mode 100644
index 00000000000..55a6a17bbca
--- /dev/null
+++ b/app/services/system_notes/alert_management_service.rb
@@ -0,0 +1,37 @@
+# frozen_string_literal: true
+
+module SystemNotes
+ class AlertManagementService < ::SystemNotes::BaseService
+ # Called when the status of an AlertManagement::Alert has changed
+ #
+ # alert - AlertManagement::Alert object.
+ #
+ # Example Note text:
+ #
+ # "changed the status to Acknowledged"
+ #
+ # Returns the created Note object
+ def change_alert_status(alert)
+ status = AlertManagement::Alert::STATUSES.key(alert.status).to_s.titleize
+ body = "changed the status to **#{status}**"
+
+ create_note(NoteSummary.new(noteable, project, author, body, action: 'status'))
+ end
+
+ # Called when an issue is created based on an AlertManagement::Alert
+ #
+ # alert - AlertManagement::Alert object.
+ # issue - Issue object.
+ #
+ # Example Note text:
+ #
+ # "created issue #17 for this alert"
+ #
+ # Returns the created Note object
+ def new_alert_issue(alert, issue)
+ body = "created issue #{issue.to_reference(project)} for this alert"
+
+ create_note(NoteSummary.new(noteable, project, author, body, action: 'alert_issue_added'))
+ end
+ end
+end
diff --git a/app/services/system_notes/issuables_service.rb b/app/services/system_notes/issuables_service.rb
index 7d7ee8d829e..76261aa716e 100644
--- a/app/services/system_notes/issuables_service.rb
+++ b/app/services/system_notes/issuables_service.rb
@@ -228,7 +228,9 @@ module SystemNotes
# A state event which results in a synthetic note will be
# created by EventCreateService if change event tracking
# is enabled.
- unless state_change_tracking_enabled?
+ if state_change_tracking_enabled?
+ create_resource_state_event(status: status, mentionable_source: source)
+ else
create_note(NoteSummary.new(noteable, project, author, body, action: action))
end
end
@@ -288,15 +290,23 @@ module SystemNotes
end
def close_after_error_tracking_resolve
- body = _('resolved the corresponding error and closed the issue.')
+ if state_change_tracking_enabled?
+ create_resource_state_event(status: 'closed', close_after_error_tracking_resolve: true)
+ else
+ body = 'resolved the corresponding error and closed the issue.'
- create_note(NoteSummary.new(noteable, project, author, body, action: 'closed'))
+ create_note(NoteSummary.new(noteable, project, author, body, action: 'closed'))
+ end
end
def auto_resolve_prometheus_alert
- body = 'automatically closed this issue because the alert resolved.'
+ if state_change_tracking_enabled?
+ create_resource_state_event(status: 'closed', close_auto_resolve_prometheus_alert: true)
+ else
+ body = 'automatically closed this issue because the alert resolved.'
- create_note(NoteSummary.new(noteable, project, author, body, action: 'closed'))
+ create_note(NoteSummary.new(noteable, project, author, body, action: 'closed'))
+ end
end
private
@@ -324,6 +334,11 @@ module SystemNotes
note_text =~ /\A#{cross_reference_note_prefix}/i
end
+ def create_resource_state_event(params)
+ ResourceEvents::ChangeStateService.new(resource: noteable, user: author)
+ .execute(params)
+ end
+
def state_change_tracking_enabled?
noteable.respond_to?(:resource_state_events) &&
::Feature.enabled?(:track_resource_state_change_events, noteable.project)
diff --git a/app/services/system_notes/merge_requests_service.rb b/app/services/system_notes/merge_requests_service.rb
index baf26245eb9..9b5c9ba20b2 100644
--- a/app/services/system_notes/merge_requests_service.rb
+++ b/app/services/system_notes/merge_requests_service.rb
@@ -150,7 +150,24 @@ module SystemNotes
create_note(summary)
end
+
+ # Called when the merge request is approved by user
+ #
+ # Example Note text:
+ #
+ # "approved this merge request"
+ #
+ # Returns the created Note object
+ def approve_mr
+ body = "approved this merge request"
+
+ create_note(NoteSummary.new(noteable, project, author, body, action: 'approved'))
+ end
+
+ def unapprove_mr
+ body = "unapproved this merge request"
+
+ create_note(NoteSummary.new(noteable, project, author, body, action: 'unapproved'))
+ end
end
end
-
-SystemNotes::MergeRequestsService.prepend_if_ee('::EE::SystemNotes::MergeRequestsService')
diff --git a/app/services/tags/destroy_service.rb b/app/services/tags/destroy_service.rb
index 3a01192487d..4d1f4043b01 100644
--- a/app/services/tags/destroy_service.rb
+++ b/app/services/tags/destroy_service.rb
@@ -18,6 +18,8 @@ module Tags
.new(project, current_user, tag: tag_name)
.execute
+ unlock_artifacts(tag_name)
+
success('Tag was removed')
else
error('Failed to remove tag')
@@ -33,5 +35,11 @@ module Tags
def success(message)
super().merge(message: message)
end
+
+ private
+
+ def unlock_artifacts(tag_name)
+ Ci::RefDeleteUnlockArtifactsWorker.perform_async(project.id, current_user.id, "#{::Gitlab::Git::TAG_REF_PREFIX}#{tag_name}")
+ end
end
end
diff --git a/app/services/terraform/remote_state_handler.rb b/app/services/terraform/remote_state_handler.rb
index d180a3a2432..d2c44d4a265 100644
--- a/app/services/terraform/remote_state_handler.rb
+++ b/app/services/terraform/remote_state_handler.rb
@@ -5,26 +5,17 @@ module Terraform
include Gitlab::OptimisticLocking
StateLockedError = Class.new(StandardError)
+ UnauthorizedError = Class.new(StandardError)
- # rubocop: disable CodeReuse/ActiveRecord
def find_with_lock
- raise ArgumentError unless params[:name].present?
-
- state = Terraform::State.find_by(project: project, name: params[:name])
- raise ActiveRecord::RecordNotFound.new("Couldn't find state") unless state
-
- retry_optimistic_lock(state) { |state| yield state } if state && block_given?
- state
- end
- # rubocop: enable CodeReuse/ActiveRecord
-
- def create_or_find!
- raise ArgumentError unless params[:name].present?
-
- Terraform::State.create_or_find_by(project: project, name: params[:name])
+ retrieve_with_lock(find_only: true) do |state|
+ yield state if block_given?
+ end
end
def handle_with_lock
+ raise UnauthorizedError unless can_modify_state?
+
retrieve_with_lock do |state|
raise StateLockedError unless lock_matches?(state)
@@ -36,6 +27,7 @@ module Terraform
def lock!
raise ArgumentError if params[:lock_id].blank?
+ raise UnauthorizedError unless can_modify_state?
retrieve_with_lock do |state|
raise StateLockedError if state.locked?
@@ -49,6 +41,8 @@ module Terraform
end
def unlock!
+ raise UnauthorizedError unless can_modify_state?
+
retrieve_with_lock do |state|
# force-unlock does not pass ID, so we ignore it if it is missing
raise StateLockedError unless params[:lock_id].nil? || lock_matches?(state)
@@ -63,8 +57,21 @@ module Terraform
private
- def retrieve_with_lock
- create_or_find!.tap { |state| retry_optimistic_lock(state) { |state| yield state } }
+ def retrieve_with_lock(find_only: false)
+ create_or_find!(find_only: find_only).tap { |state| retry_optimistic_lock(state) { |state| yield state } }
+ end
+
+ def create_or_find!(find_only:)
+ raise ArgumentError unless params[:name].present?
+
+ find_params = { project: project, name: params[:name] }
+
+ if find_only
+ Terraform::State.find_by(find_params) || # rubocop: disable CodeReuse/ActiveRecord
+ raise(ActiveRecord::RecordNotFound.new("Couldn't find state"))
+ else
+ Terraform::State.create_or_find_by(find_params)
+ end
end
def lock_matches?(state)
@@ -73,5 +80,9 @@ module Terraform
ActiveSupport::SecurityUtils
.secure_compare(state.lock_xid.to_s, params[:lock_id].to_s)
end
+
+ def can_modify_state?
+ current_user.can?(:admin_terraform_state, project)
+ end
end
end
diff --git a/app/services/todo_service.rb b/app/services/todo_service.rb
index e6fb0d3c72e..ec15bdde8d7 100644
--- a/app/services/todo_service.rb
+++ b/app/services/todo_service.rb
@@ -162,9 +162,9 @@ class TodoService
create_assignment_todo(alert, current_user, [])
end
- # When user marks an issue as todo
- def mark_todo(issuable, current_user)
- attributes = attributes_for_todo(issuable.project, issuable, current_user, Todo::MARKED)
+ # When user marks a target as todo
+ def mark_todo(target, current_user)
+ attributes = attributes_for_todo(target.project, target, current_user, Todo::MARKED)
create_todos(current_user, attributes)
end
diff --git a/app/services/update_container_registry_info_service.rb b/app/services/update_container_registry_info_service.rb
new file mode 100644
index 00000000000..531335839a9
--- /dev/null
+++ b/app/services/update_container_registry_info_service.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+
+class UpdateContainerRegistryInfoService
+ def execute
+ registry_config = Gitlab.config.registry
+ return unless registry_config.enabled && registry_config.api_url.presence
+
+ # registry_info will query the /v2 route of the registry API. This route
+ # requires authentication, but not authorization (the response has no body,
+ # only headers that show the version of the registry). There might be no
+ # associated user when running this (e.g. from a rake task or a cron job),
+ # so we need to generate a valid JWT token with no access permissions to
+ # authenticate as a trusted client.
+ token = Auth::ContainerRegistryAuthenticationService.access_token([], [])
+ client = ContainerRegistry::Client.new(registry_config.api_url, token: token)
+ info = client.registry_info
+
+ Gitlab::CurrentSettings.update!(
+ container_registry_vendor: info[:vendor] || '',
+ container_registry_version: info[:version] || '',
+ container_registry_features: info[:features] || []
+ )
+ end
+end
diff --git a/app/services/users/block_service.rb b/app/services/users/block_service.rb
index 9c393832d8f..041db731875 100644
--- a/app/services/users/block_service.rb
+++ b/app/services/users/block_service.rb
@@ -19,7 +19,7 @@ module Users
private
def after_block_hook(user)
- # overriden by EE module
+ # overridden by EE module
end
end
end
diff --git a/app/services/wiki_pages/base_service.rb b/app/services/wiki_pages/base_service.rb
index a0256ea5e69..2967684f7bc 100644
--- a/app/services/wiki_pages/base_service.rb
+++ b/app/services/wiki_pages/base_service.rb
@@ -44,8 +44,6 @@ module WikiPages
end
def create_wiki_event(page)
- return unless ::Feature.enabled?(:wiki_events)
-
response = WikiPages::EventCreateService.new(current_user).execute(slug_for_page(page), page, event_action)
log_error(response.message) if response.error?
diff --git a/app/services/wiki_pages/event_create_service.rb b/app/services/wiki_pages/event_create_service.rb
index 18a45d057a9..0453c90d693 100644
--- a/app/services/wiki_pages/event_create_service.rb
+++ b/app/services/wiki_pages/event_create_service.rb
@@ -10,8 +10,6 @@ module WikiPages
end
def execute(slug, page, action)
- return ServiceResponse.success(message: 'No event created as `wiki_events` feature is disabled') unless ::Feature.enabled?(:wiki_events)
-
event = Event.transaction do
wiki_page_meta = WikiPage::Meta.find_or_create(slug, page)
diff --git a/app/uploaders/object_storage.rb b/app/uploaders/object_storage.rb
index 5297112eef8..63b6197a04d 100644
--- a/app/uploaders/object_storage.rb
+++ b/app/uploaders/object_storage.rb
@@ -169,6 +169,10 @@ module ObjectStorage
object_store_options.connection.to_hash.deep_symbolize_keys
end
+ def consolidated_settings?
+ object_store_options.fetch('consolidated_settings', false)
+ end
+
def remote_store_path
object_store_options.remote_directory
end
@@ -196,7 +200,7 @@ module ObjectStorage
id = [CarrierWave.generate_cache_id, SecureRandom.hex].join('-')
upload_path = File.join(TMP_UPLOAD_PATH, id)
direct_upload = ObjectStorage::DirectUpload.new(self.object_store_credentials, remote_store_path, upload_path,
- has_length: has_length, maximum_size: maximum_size)
+ has_length: has_length, maximum_size: maximum_size, consolidated_settings: consolidated_settings?)
direct_upload.to_hash.merge(ID: id)
end
diff --git a/app/uploaders/packages/package_file_uploader.rb b/app/uploaders/packages/package_file_uploader.rb
new file mode 100644
index 00000000000..20fcf0a7a32
--- /dev/null
+++ b/app/uploaders/packages/package_file_uploader.rb
@@ -0,0 +1,30 @@
+# frozen_string_literal: true
+class Packages::PackageFileUploader < GitlabUploader
+ extend Workhorse::UploadPath
+ include ObjectStorage::Concern
+
+ storage_options Gitlab.config.packages
+
+ after :store, :schedule_background_upload
+
+ alias_method :upload, :model
+
+ def filename
+ model.file_name
+ end
+
+ def store_dir
+ dynamic_segment
+ end
+
+ private
+
+ def dynamic_segment
+ File.join(disk_hash[0..1], disk_hash[2..3], disk_hash,
+ 'packages', model.package.id.to_s, 'files', model.id.to_s)
+ end
+
+ def disk_hash
+ @disk_hash ||= Digest::SHA2.hexdigest(model.package.project_id.to_s)
+ end
+end
diff --git a/app/validators/addressable_url_validator.rb b/app/validators/addressable_url_validator.rb
index 99f503c3f06..9fa99903e36 100644
--- a/app/validators/addressable_url_validator.rb
+++ b/app/validators/addressable_url_validator.rb
@@ -95,9 +95,9 @@ class AddressableUrlValidator < ActiveModel::EachValidator
end
def current_options
- options.map do |option, value|
- [option, value.is_a?(Proc) ? value.call(record) : value]
- end.to_h
+ options.transform_values do |value|
+ value.is_a?(Proc) ? value.call(record) : value
+ end
end
def blocker_args
diff --git a/app/validators/array_members_validator.rb b/app/validators/array_members_validator.rb
new file mode 100644
index 00000000000..c5d3d25b4d9
--- /dev/null
+++ b/app/validators/array_members_validator.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+# ArrayMembersValidator
+#
+# Custom validator that checks if validated
+# attribute contains non empty array, which every
+# element is an instances of :member_class
+#
+# Example:
+#
+# class Config::Root < ActiveRecord::Base
+# validates :nodes, member_class: Config::Node
+# end
+#
+class ArrayMembersValidator < ActiveModel::EachValidator
+ def validate_each(record, attribute, value)
+ if !value.is_a?(Array) || value.empty? || value.any? { |child| !child.instance_of?(options[:member_class]) }
+ record.errors.add(attribute, _("should be an array of %{object_name} objects") % { object_name: options.fetch(:object_name, attribute) })
+ end
+ end
+end
diff --git a/app/validators/json_schemas/build_metadata_secrets.json b/app/validators/json_schemas/build_metadata_secrets.json
new file mode 100644
index 00000000000..e745a266777
--- /dev/null
+++ b/app/validators/json_schemas/build_metadata_secrets.json
@@ -0,0 +1,30 @@
+{
+ "description": "CI builds metadata secrets",
+ "type": "object",
+ "patternProperties": {
+ ".*": {
+ "type": "object",
+ "patternProperties": {
+ "^vault$": {
+ "type": "object",
+ "required": ["path", "field", "engine"],
+ "properties": {
+ "path": { "type": "string" },
+ "field": { "type": "string" },
+ "engine": {
+ "type": "object",
+ "required": ["name", "path"],
+ "properties": {
+ "path": { "type": "string" },
+ "name": { "type": "string" }
+ },
+ "additionalProperties": false
+ }
+ },
+ "additionalProperties": false
+ }
+ },
+ "additionalProperties": false
+ }
+ }
+}
diff --git a/app/validators/json_schemas/security_ci_configuration_schemas/sast_ui_schema.json b/app/validators/json_schemas/security_ci_configuration_schemas/sast_ui_schema.json
new file mode 100644
index 00000000000..1154a4c45b8
--- /dev/null
+++ b/app/validators/json_schemas/security_ci_configuration_schemas/sast_ui_schema.json
@@ -0,0 +1,153 @@
+{
+ "global": [
+ {
+ "field" : "SECURE_ANALYZERS_PREFIX",
+ "label" : "Image prefix",
+ "type": "string",
+ "default_value": "registry.gitlab.com/gitlab-org/security-products/analyzers",
+ "value": ""
+ },
+ {
+ "field" : "SAST_EXCLUDED_PATHS",
+ "label" : "Excluded Paths",
+ "type": "string",
+ "default_value": "spec, test, tests, tmp",
+ "value": ""
+ },
+ {
+ "field" : "SECURE_ANALYZER_IMAGE_TAG",
+ "label" : "Image tag",
+ "type": "string",
+ "options": [],
+ "default_value": "2",
+ "value": ""
+ },
+ {
+ "field" : "SAST_DISABLED",
+ "label" : "Disable SAST",
+ "type": "options",
+ "options": [
+ {
+ "value" :"true",
+ "label" : "true (disables SAST)"
+ },
+ {
+ "value":"false",
+ "label":"false (enables SAST)"
+ }
+ ],
+ "default_value": "false",
+ "value": ""
+ }
+ ],
+ "pipeline": [
+ {
+ "field" : "stage",
+ "label" : "Stage",
+ "type": "dropdown",
+ "options": [
+ {
+ "value" :"test",
+ "label" : "test"
+ },
+ {
+ "value":"build",
+ "label":"build"
+ }
+ ],
+ "default_value": "test",
+ "value": ""
+ },
+ {
+ "field" : "allow_failure",
+ "label" : "Allow Failure",
+ "type": "options",
+ "options": [
+ {
+ "value" :"true",
+ "label" : "Allows pipeline failure"
+ },
+ {
+ "value": "false",
+ "label": "Does not allow pipeline failure"
+ }
+ ],
+ "default_value": "true",
+ "value": ""
+ },
+ {
+ "field" : "rules",
+ "label" : "Rules",
+ "type": "multiline",
+ "default_value": "",
+ "value": ""
+ }
+ ],
+ "analyzers": [
+ {
+ "name": "brakeman",
+ "label": "Brakeman",
+ "enabled" : true
+ },
+ {
+ "name": "bandit",
+ "label": "Bandit",
+ "enabled" : true
+ },
+ {
+ "name": "eslint",
+ "label": "ESLint",
+ "enabled" : true
+ },
+ {
+ "name": "flawfinder",
+ "label": "Flawfinder",
+ "enabled" : true
+ },
+ {
+ "name": "kubesec",
+ "label": "kubesec",
+ "enabled" : true
+ },
+ {
+ "name": "nodejsscan",
+ "label": "Node.js Scan",
+ "enabled" : true
+ },
+ {
+ "name": "gosec",
+ "label": "Golang Security Checker",
+ "enabled" : true
+ },
+ {
+ "name": "phpcs-security-audit",
+ "label": "PHP Security Audit",
+ "enabled" : true
+ },
+ {
+ "name": "pmd-apex",
+ "label": "PMD APEX",
+ "enabled" : true
+ },
+ {
+ "name": "security-code-scan",
+ "label": "Security Code Scan",
+ "enabled" : true
+ },
+ {
+ "name": "sobelow",
+ "label": "Sobelow",
+ "enabled" : true
+ },
+ {
+ "name": "spotbugs",
+ "label": "Spotbugs",
+ "enabled" : true
+ },
+ {
+ "name": "secrets",
+ "label": "Secrets",
+ "enabled" : true
+ }
+ ]
+}
diff --git a/app/views/admin/appearances/_form.html.haml b/app/views/admin/appearances/_form.html.haml
index aa47daf4a57..fcb1c1a6f3e 100644
--- a/app/views/admin/appearances/_form.html.haml
+++ b/app/views/admin/appearances/_form.html.haml
@@ -1,6 +1,6 @@
- parsed_with_gfm = "Content parsed with #{link_to('GitLab Flavored Markdown', help_page_path('user/markdown'), target: '_blank')}.".html_safe
-= form_for @appearance, url: admin_appearances_path, html: { class: 'prepend-top-default' } do |f|
+= form_for @appearance, url: admin_appearances_path, html: { class: 'gl-mt-3' } do |f|
= form_errors(@appearance)
@@ -100,7 +100,7 @@
.hint
= parsed_with_gfm
- .prepend-top-default.append-bottom-default
+ .gl-mt-3.gl-mb-3
= f.submit 'Update appearance settings', class: 'btn btn-success'
- if @appearance.persisted? || @appearance.updated_at
.mt-4
diff --git a/app/views/admin/appearances/show.html.haml b/app/views/admin/appearances/show.html.haml
index ccf6f960cf2..77a08913666 100644
--- a/app/views/admin/appearances/show.html.haml
+++ b/app/views/admin/appearances/show.html.haml
@@ -1,4 +1,4 @@
-- page_title "Appearance"
+- page_title _("Appearance")
- @content_class = "limit-container-width" unless fluid_layout
= render 'form'
diff --git a/app/views/admin/application_settings/_account_and_limit.html.haml b/app/views/admin/application_settings/_account_and_limit.html.haml
index ceec8901951..65a2f1d42e1 100644
--- a/app/views/admin/application_settings/_account_and_limit.html.haml
+++ b/app/views/admin/application_settings/_account_and_limit.html.haml
@@ -54,10 +54,10 @@
= _('Newly registered users will by default be external')
.prepend-top-10
= _('Internal users')
- = f.text_field :user_default_internal_regex, placeholder: _('Regex pattern'), class: 'form-control prepend-top-5'
+ = f.text_field :user_default_internal_regex, placeholder: _('Regex pattern'), class: 'form-control gl-mt-2'
.help-block
= _('Specify an e-mail address regex pattern to identify default internal users.')
- = link_to _('More information'), help_page_path('user/permissions', anchor: 'external-users-permissions'),
+ = link_to _('More information'), help_page_path('user/permissions', anchor: 'setting-new-users-to-external'),
target: '_blank'
.form-group
= f.label :user_show_add_ssh_key_message, _('Prompt users to upload SSH keys'), class: 'label-bold'
diff --git a/app/views/admin/application_settings/_ci_cd.html.haml b/app/views/admin/application_settings/_ci_cd.html.haml
index c7918881bdf..410820dfb85 100644
--- a/app/views/admin/application_settings/_ci_cd.html.haml
+++ b/app/views/admin/application_settings/_ci_cd.html.haml
@@ -40,7 +40,7 @@
= f.text_field :default_artifacts_expire_in, class: 'form-control'
.form-text.text-muted
= _("Set the default expiration time for each job's artifacts. 0 for unlimited. The default unit is in seconds, but you can define an alternative. For example: <code>4 mins 2 sec</code>, <code>2h42min</code>.").html_safe
- = link_to icon('question-circle'), help_page_path('user/admin_area/settings/continuous_integration', anchor: 'default-artifacts-expiration')
+ = link_to icon('question-circle'), help_page_path('user/admin_area/settings/continuous_integration', anchor: 'default-artifacts-expiration-core-only')
.form-group
= f.label :archive_builds_in_human_readable, _('Archive jobs'), class: 'label-bold'
= f.text_field :archive_builds_in_human_readable, class: 'form-control', placeholder: 'never'
diff --git a/app/views/admin/application_settings/_email.html.haml b/app/views/admin/application_settings/_email.html.haml
index 3dd72909805..49747f2bfd4 100644
--- a/app/views/admin/application_settings/_email.html.haml
+++ b/app/views/admin/application_settings/_email.html.haml
@@ -20,7 +20,7 @@
= f.label :commit_email_hostname, _('Custom hostname (for private commit emails)'), class: 'label-bold'
= f.text_field :commit_email_hostname, class: 'form-control'
.form-text.text-muted
- - commit_email_hostname_docs_link = link_to _('Learn more'), help_page_path('user/admin_area/settings/email', anchor: 'custom-private-commit-email-hostname'), target: '_blank'
+ - commit_email_hostname_docs_link = link_to _('Learn more'), help_page_path('user/admin_area/settings/email.md', anchor: 'custom-hostname-for-private-commit-emails'), target: '_blank'
= _("This setting will update the hostname that is used to generate private commit emails. %{learn_more}").html_safe % { learn_more: commit_email_hostname_docs_link }
= render_if_exists 'admin/application_settings/email_additional_text_setting', form: f
diff --git a/app/views/admin/application_settings/_import_export_limits.html.haml b/app/views/admin/application_settings/_import_export_limits.html.haml
new file mode 100644
index 00000000000..d26c3376391
--- /dev/null
+++ b/app/views/admin/application_settings/_import_export_limits.html.haml
@@ -0,0 +1,34 @@
+= form_for @application_setting, url: network_admin_application_settings_path(anchor: 'js-import-export-limits-settings'), html: { class: 'fieldset-form' } do |f|
+ = form_errors(@application_setting)
+
+ %fieldset
+ .form-group
+ = f.label :project_import_limit, _('Max Project Import requests per minute per user'), class: 'label-bold'
+ = f.number_field :project_import_limit, class: 'form-control'
+
+ %fieldset
+ .form-group
+ = f.label :project_export_limit, _('Max Project Export requests per minute per user'), class: 'label-bold'
+ = f.number_field :project_export_limit, class: 'form-control'
+
+ %fieldset
+ .form-group
+ = f.label :project_download_export_limit, _('Max Project Export Download requests per minute per user'), class: 'label-bold'
+ = f.number_field :project_download_export_limit, class: 'form-control'
+
+ %fieldset
+ .form-group
+ = f.label :group_import_limit, _('Max Group Import requests per minute per user'), class: 'label-bold'
+ = f.number_field :group_import_limit, class: 'form-control'
+
+ %fieldset
+ .form-group
+ = f.label :group_export_limit, _('Max Group Export requests per minute per user'), class: 'label-bold'
+ = f.number_field :group_export_limit, class: 'form-control'
+
+ %fieldset
+ .form-group
+ = f.label :group_download_export_limit, _('Max Group Export Download requests per minute per user'), class: 'label-bold'
+ = f.number_field :group_download_export_limit, class: 'form-control'
+
+ = f.submit 'Save changes', class: "btn btn-success", data: { qa_selector: 'save_changes_button' }
diff --git a/app/views/admin/application_settings/_initial_branch_name.html.haml b/app/views/admin/application_settings/_initial_branch_name.html.haml
new file mode 100644
index 00000000000..e76374e88a8
--- /dev/null
+++ b/app/views/admin/application_settings/_initial_branch_name.html.haml
@@ -0,0 +1,12 @@
+= form_for @application_setting, url: general_admin_application_settings_path(anchor: 'js-default-branch-name'), html: { class: 'fieldset-form' } do |f|
+ = form_errors(@application_setting)
+
+ - fallback_branch_name = '<code>master</code>'
+
+ %fieldset
+ .form-group
+ = f.label :default_branch_name, _('Default initial branch name'), class: 'label-light'
+ = f.text_field :default_branch_name, placeholder: 'master', class: 'form-control'
+ %span.form-text.text-muted
+ = (_("Changes affect new repositories only. If not specified, Git's default name %{branch_name_default} will be used.") % { branch_name_default: fallback_branch_name } ).html_safe
+ = f.submit _('Save changes'), class: 'gl-button btn-success'
diff --git a/app/views/admin/application_settings/_registry.html.haml b/app/views/admin/application_settings/_registry.html.haml
index 0631c024eb8..fea3ff4c3ba 100644
--- a/app/views/admin/application_settings/_registry.html.haml
+++ b/app/views/admin/application_settings/_registry.html.haml
@@ -10,7 +10,7 @@
= f.check_box :container_expiration_policies_enable_historic_entries, class: 'form-check-input'
= f.label :container_expiration_policies_enable_historic_entries, class: 'form-check-label' do
= _("Enable container expiration and retention policies for projects created earlier than GitLab 12.7.")
- = link_to icon('question-circle'), help_page_path('user/packages/container_registry/index', anchor: 'expiration-policy')
+ = link_to icon('question-circle'), help_page_path('user/packages/container_registry/index', anchor: 'cleanup-policy')
.form-text.text-muted
= _("Existing projects will be able to use expiration policies. Avoid enabling this if an external Container Registry is being used, as there is a performance risk if many images exist on one project.")
= link_to icon('question-circle'), help_page_path('user/packages/container_registry/index', anchor: 'use-with-external-container-registries')
diff --git a/app/views/admin/application_settings/_repository_storage.html.haml b/app/views/admin/application_settings/_repository_storage.html.haml
index ed276da08f2..ecae720cd49 100644
--- a/app/views/admin/application_settings/_repository_storage.html.haml
+++ b/app/views/admin/application_settings/_repository_storage.html.haml
@@ -15,7 +15,7 @@
.form-group
.form-text
%p.text-secondary
- = _('Select a weight for the storage new repositories will be placed on.')
+ = _('Enter weights for storages for new repositories.')
= link_to icon('question-circle'), help_page_path('administration/repository_storage_paths')
.form-check
- storage_weights.each do |attribute|
diff --git a/app/views/admin/application_settings/_signin.html.haml b/app/views/admin/application_settings/_signin.html.haml
index 007cd343339..0972e10e12c 100644
--- a/app/views/admin/application_settings/_signin.html.haml
+++ b/app/views/admin/application_settings/_signin.html.haml
@@ -33,6 +33,15 @@
= f.label :require_two_factor_authentication, class: 'form-check-label' do
Require all users to set up Two-factor authentication
.form-group
+ = f.label :unknown_sign_in, _('Email notification for unknown sign-ins'), class: 'label-bold'
+ .form-check
+ = f.check_box :notify_on_unknown_sign_in, class: 'form-check-input'
+ = f.label :notify_on_unknown_sign_in, class: 'form-check-label' do
+ = _('Notify users by email when sign-in location is not recognized')
+ = link_to icon('question-circle'),
+ 'https://docs.gitlab.com/ee/user/profile/unknown_sign_in_notification.html',
+ target: '_blank'
+ .form-group
= f.label :two_factor_authentication, 'Two-factor grace period (hours)', class: 'label-bold'
= f.number_field :two_factor_grace_period, min: 0, class: 'form-control', placeholder: '0'
.form-text.text-muted Amount of time (in hours) that users are allowed to skip forced configuration of two-factor authentication
diff --git a/app/views/admin/application_settings/_usage.html.haml b/app/views/admin/application_settings/_usage.html.haml
index 9421585b70c..d8a4c601b77 100644
--- a/app/views/admin/application_settings/_usage.html.haml
+++ b/app/views/admin/application_settings/_usage.html.haml
@@ -31,7 +31,7 @@
%pre.usage-data.js-usage-ping-payload.js-syntax-highlight.code.highlight.mt-2.d-none{ data: { endpoint: usage_data_admin_application_settings_path(format: :html) } }
- else
= _('The usage ping is disabled, and cannot be configured through this form.')
- - deactivating_usage_ping_path = help_page_path('user/admin_area/settings/usage_statistics', anchor: 'deactivate-the-usage-ping')
+ - deactivating_usage_ping_path = help_page_path('development/telemetry/usage_ping', anchor: 'disable-usage-ping')
- deactivating_usage_ping_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: deactivating_usage_ping_path }
= s_('For more information, see the documentation on %{deactivating_usage_ping_link_start}deactivating the usage ping%{deactivating_usage_ping_link_end}.').html_safe % { deactivating_usage_ping_link_start: deactivating_usage_ping_link_start, deactivating_usage_ping_link_end: '</a>'.html_safe }
.form-group.mt-3
diff --git a/app/views/admin/application_settings/ci/_header.html.haml b/app/views/admin/application_settings/ci/_header.html.haml
index 9f03936f64a..fe86284ba2f 100644
--- a/app/views/admin/application_settings/ci/_header.html.haml
+++ b/app/views/admin/application_settings/ci/_header.html.haml
@@ -2,7 +2,7 @@
%h4
= _('Variables')
- = link_to icon('question-circle'), help_page_path('ci/variables/README', anchor: 'variables'), target: '_blank', rel: 'noopener noreferrer'
+ = link_to icon('question-circle'), help_page_path('ci/variables/README', anchor: 'custom-environment-variables'), target: '_blank', rel: 'noopener noreferrer'
%button.btn.btn-default.js-settings-toggle{ type: 'button' }
= expanded ? _('Collapse') : _('Expand')
diff --git a/app/views/admin/application_settings/ci_cd.html.haml b/app/views/admin/application_settings/ci_cd.html.haml
index 2452ab794fc..cdb69d33b12 100644
--- a/app/views/admin/application_settings/ci_cd.html.haml
+++ b/app/views/admin/application_settings/ci_cd.html.haml
@@ -9,7 +9,7 @@
.settings-content
- if ci_variable_protected_by_default?
%p.settings-message.text-center
- - link_start = '<a href="%{url}">'.html_safe % { url: help_page_path('ci/variables/README', anchor: 'protected-variables') }
+ - link_start = '<a href="%{url}">'.html_safe % { url: help_page_path('ci/variables/README', anchor: 'protect-a-custom-variable') }
= s_('Environment variables on this GitLab instance are configured to be %{link_start}protected%{link_end} by default').html_safe % { link_start: link_start, link_end: '</a>'.html_safe }
#js-instance-variables{ data: { endpoint: admin_ci_variables_path, group: 'true', maskable_regex: ci_variable_maskable_regex, protected_by_default: ci_variable_protected_by_default?.to_s} }
diff --git a/app/views/admin/application_settings/integrations.html.haml b/app/views/admin/application_settings/integrations.html.haml
index a8eff26b94c..cca0240462f 100644
--- a/app/views/admin/application_settings/integrations.html.haml
+++ b/app/views/admin/application_settings/integrations.html.haml
@@ -12,7 +12,7 @@
%h4.gl-alert-title= s_('AdminSettings|Some settings have moved')
= s_('AdminSettings|Elasticsearch, PlantUML, Slack application, Third party offers, Snowplow, Amazon EKS have moved to Settings > General.')
.gl-alert-actions
- = link_to s_('AdminSettings|Go to General Settings'), admin_application_settings_path, class: 'btn gl-alert-action btn-info new-gl-button'
+ = link_to s_('AdminSettings|Go to General Settings'), general_admin_application_settings_path, class: 'btn gl-alert-action btn-info new-gl-button'
%h4= s_('AdminSettings|Apply integration settings to all Projects')
%p
diff --git a/app/views/admin/application_settings/network.html.haml b/app/views/admin/application_settings/network.html.haml
index db4611964b4..15149e46f9c 100644
--- a/app/views/admin/application_settings/network.html.haml
+++ b/app/views/admin/application_settings/network.html.haml
@@ -57,4 +57,15 @@
.settings-content
= render 'issue_limits'
+%section.settings.as-import-export-limits.no-animate#js-import-export-limits-settings{ class: ('expanded' if expanded_by_default?) }
+ .settings-header
+ %h4
+ = _('Import/Export Rate Limits')
+ %button.btn.btn-default.js-settings-toggle{ type: 'button' }
+ = expanded_by_default? ? _('Collapse') : _('Expand')
+ %p
+ = _('Configure limits for Project/Group Import/Export.')
+ .settings-content
+ = render 'import_export_limits'
+
= render_if_exists 'admin/application_settings/ee_network_settings'
diff --git a/app/views/admin/application_settings/repository.html.haml b/app/views/admin/application_settings/repository.html.haml
index b0934a9d9fb..33a6715d424 100644
--- a/app/views/admin/application_settings/repository.html.haml
+++ b/app/views/admin/application_settings/repository.html.haml
@@ -2,6 +2,18 @@
- page_title _("Repository")
- @content_class = "limit-container-width" unless fluid_layout
+- if Feature.enabled?(:global_default_branch_name, default_enabled: true)
+ %section.settings.as-default-branch-name.no-animate#js-default-branch-name{ class: ('expanded' if expanded_by_default?) }
+ .settings-header
+ %h4
+ = _('Default initial branch name')
+ %button.gl-button.js-settings-toggle{ type: 'button' }
+ = expanded_by_default? ? _('Collapse') : _('Expand')
+ %p
+ = _('Set the default name of the initial branch when creating new repositories through the user interface.')
+ .settings-content
+ = render 'initial_branch_name'
+
%section.settings.as-mirror.no-animate#js-mirror-settings{ class: ('expanded' if expanded_by_default?) }
.settings-header
%h4
diff --git a/app/views/admin/applications/edit.html.haml b/app/views/admin/applications/edit.html.haml
index 13c408914bb..4f737a14e12 100644
--- a/app/views/admin/applications/edit.html.haml
+++ b/app/views/admin/applications/edit.html.haml
@@ -1,6 +1,6 @@
-- add_to_breadcrumbs "Applications", admin_applications_path
+- add_to_breadcrumbs _("Applications"), admin_applications_path
- breadcrumb_title @application.name
-- page_title "Edit", @application.name, "Applications"
+- page_title _("Edit"), @application.name, _("Applications")
%h3.page-title Edit application
- @url = admin_application_path(@application)
diff --git a/app/views/admin/applications/index.html.haml b/app/views/admin/applications/index.html.haml
index c3861f335b8..0119cabf1ad 100644
--- a/app/views/admin/applications/index.html.haml
+++ b/app/views/admin/applications/index.html.haml
@@ -1,4 +1,4 @@
-- page_title "Applications"
+- page_title _("Applications")
%h3.page-title
System OAuth applications
%p.light
diff --git a/app/views/admin/applications/new.html.haml b/app/views/admin/applications/new.html.haml
index 346c58877d9..4d4b6b0c994 100644
--- a/app/views/admin/applications/new.html.haml
+++ b/app/views/admin/applications/new.html.haml
@@ -1,5 +1,5 @@
-- breadcrumb_title "Applications"
-- page_title "New Application"
+- breadcrumb_title _("Applications")
+- page_title _("New Application")
%h3.page-title New application
- @url = admin_applications_path
diff --git a/app/views/admin/applications/show.html.haml b/app/views/admin/applications/show.html.haml
index 146674a2fac..5259dd56df5 100644
--- a/app/views/admin/applications/show.html.haml
+++ b/app/views/admin/applications/show.html.haml
@@ -1,4 +1,4 @@
-- page_title @application.name, "Applications"
+- page_title @application.name, _("Applications")
%h3.page-title
Application: #{@application.name}
@@ -46,4 +46,4 @@
.form-actions
= link_to 'Edit', edit_admin_application_path(@application), class: 'btn btn-primary wide float-left'
- = render 'delete_form', application: @application, submit_btn_css: 'btn btn-danger prepend-left-10'
+ = render 'delete_form', application: @application, submit_btn_css: 'btn btn-danger gl-ml-3'
diff --git a/app/views/admin/background_jobs/show.html.haml b/app/views/admin/background_jobs/show.html.haml
index 1001a69b787..bbb47e29bb9 100644
--- a/app/views/admin/background_jobs/show.html.haml
+++ b/app/views/admin/background_jobs/show.html.haml
@@ -1,4 +1,4 @@
-- page_title "Background Jobs"
+- page_title _("Background Jobs")
%h3.page-title Background Jobs
%p.light GitLab uses #{link_to "sidekiq", "http://sidekiq.org/"} library for async job processing
diff --git a/app/views/admin/broadcast_messages/edit.html.haml b/app/views/admin/broadcast_messages/edit.html.haml
index 8cbc4597e32..569aaa29cc4 100644
--- a/app/views/admin/broadcast_messages/edit.html.haml
+++ b/app/views/admin/broadcast_messages/edit.html.haml
@@ -1,4 +1,4 @@
-- breadcrumb_title "Messages"
-- page_title "Broadcast Messages"
+- breadcrumb_title _("Messages")
+- page_title _("Broadcast Messages")
= render 'form'
diff --git a/app/views/admin/broadcast_messages/index.html.haml b/app/views/admin/broadcast_messages/index.html.haml
index e7a7ee96508..bca74f71c5c 100644
--- a/app/views/admin/broadcast_messages/index.html.haml
+++ b/app/views/admin/broadcast_messages/index.html.haml
@@ -1,5 +1,5 @@
-- breadcrumb_title "Messages"
-- page_title "Broadcast Messages"
+- breadcrumb_title _("Messages")
+- page_title _("Broadcast Messages")
%h3.page-title
Broadcast Messages
diff --git a/app/views/admin/dashboard/index.html.haml b/app/views/admin/dashboard/index.html.haml
index 951e5364ad8..7c6c21bc509 100644
--- a/app/views/admin/dashboard/index.html.haml
+++ b/app/views/admin/dashboard/index.html.haml
@@ -1,4 +1,5 @@
-- breadcrumb_title "Dashboard"
+- breadcrumb_title _("Dashboard")
+- page_title _("Dashboard")
- if show_license_breakdown?
= render_if_exists 'admin/licenses/breakdown', license: @license
@@ -9,7 +10,7 @@
dismissible: true.to_s } }
= notice[:message].html_safe
-.admin-dashboard.prepend-top-default
+.admin-dashboard.gl-mt-3
.row
.col-sm-4
.info-well.dark-well
diff --git a/app/views/admin/deploy_keys/new.html.haml b/app/views/admin/deploy_keys/new.html.haml
index 9a563a5bc78..f43c1447f09 100644
--- a/app/views/admin/deploy_keys/new.html.haml
+++ b/app/views/admin/deploy_keys/new.html.haml
@@ -1,4 +1,4 @@
-- page_title 'New Deploy Key'
+- page_title _('New Deploy Key')
%h3.page-title New public deploy key
%hr
diff --git a/app/views/admin/gitaly_servers/index.html.haml b/app/views/admin/gitaly_servers/index.html.haml
index 9b24f411a75..0b06f145687 100644
--- a/app/views/admin/gitaly_servers/index.html.haml
+++ b/app/views/admin/gitaly_servers/index.html.haml
@@ -1,4 +1,5 @@
- breadcrumb_title _("Gitaly Servers")
+- page_title _("Gitaly Servers")
%h3.page-title= _("Gitaly Servers")
%hr
diff --git a/app/views/admin/groups/index.html.haml b/app/views/admin/groups/index.html.haml
index f295e5a06cb..da2b2c60b15 100644
--- a/app/views/admin/groups/index.html.haml
+++ b/app/views/admin/groups/index.html.haml
@@ -1,7 +1,7 @@
- page_title _("Groups")
.top-area
- .prepend-top-default.append-bottom-default
+ .gl-mt-3.gl-mb-3
= form_tag admin_groups_path, method: :get, class: 'js-search-form' do |f|
= hidden_field_tag :sort, @sort
.search-holder
diff --git a/app/views/admin/groups/show.html.haml b/app/views/admin/groups/show.html.haml
index e105091e773..4b0e0b9c697 100644
--- a/app/views/admin/groups/show.html.haml
+++ b/app/views/admin/groups/show.html.haml
@@ -1,6 +1,8 @@
- add_to_breadcrumbs _("Groups"), admin_groups_path
- breadcrumb_title @group.name
- page_title @group.name, _("Groups")
+
+.js-remove-member-modal
%h3.page-title
= _('Group: %{group_name}') % { group_name: @group.full_name }
diff --git a/app/views/admin/hook_logs/_index.html.haml b/app/views/admin/hook_logs/_index.html.haml
index 841640efad2..5e70e80cff7 100644
--- a/app/views/admin/hook_logs/_index.html.haml
+++ b/app/views/admin/hook_logs/_index.html.haml
@@ -1,4 +1,4 @@
-.row.prepend-top-default.append-bottom-default
+.row.gl-mt-3.gl-mb-3
.col-lg-3
%h4.gl-mt-0
Recent Deliveries
diff --git a/app/views/admin/hook_logs/show.html.haml b/app/views/admin/hook_logs/show.html.haml
index 86729dbe7bc..4d534c59c40 100644
--- a/app/views/admin/hook_logs/show.html.haml
+++ b/app/views/admin/hook_logs/show.html.haml
@@ -1,9 +1,9 @@
-- page_title 'Request details'
+- page_title _('Request details')
%h3.page-title
Request details
%hr
-= link_to 'Resend Request', retry_admin_hook_hook_log_path(@hook, @hook_log), method: :post, class: "btn btn-default float-right prepend-left-10"
+= link_to 'Resend Request', retry_admin_hook_hook_log_path(@hook, @hook_log), method: :post, class: "btn btn-default float-right gl-ml-3"
= render partial: 'shared/hook_logs/content', locals: { hook_log: @hook_log }
diff --git a/app/views/admin/hooks/_form.html.haml b/app/views/admin/hooks/_form.html.haml
index 072f80b56b9..17bb054b869 100644
--- a/app/views/admin/hooks/_form.html.haml
+++ b/app/views/admin/hooks/_form.html.haml
@@ -16,7 +16,7 @@
System hook will be triggered on set of events like creating project
or adding ssh key. But you can also enable extra triggers like Push events.
- .prepend-top-default
+ .gl-mt-3
= form.check_box :repository_update_events, class: 'float-left'
.prepend-left-20
= form.label :repository_update_events, class: 'list-label' do
diff --git a/app/views/admin/hooks/edit.html.haml b/app/views/admin/hooks/edit.html.haml
index 636dd6bdfc1..f9faf5b11fa 100644
--- a/app/views/admin/hooks/edit.html.haml
+++ b/app/views/admin/hooks/edit.html.haml
@@ -1,11 +1,11 @@
- add_to_breadcrumbs @hook.pluralized_name, admin_hooks_path
- page_title _('Edit System Hook')
-.row.prepend-top-default
+.row.gl-mt-3
.col-lg-3
= render 'shared/web_hooks/title_and_docs', hook: @hook
- .col-lg-9.append-bottom-default
+ .col-lg-9.gl-mb-3
= form_for @hook, as: :hook, url: admin_hook_path do |f|
= render partial: 'form', locals: { form: f, hook: @hook }
.form-actions
diff --git a/app/views/admin/hooks/index.html.haml b/app/views/admin/hooks/index.html.haml
index 1c14291b58e..d70baa592ea 100644
--- a/app/views/admin/hooks/index.html.haml
+++ b/app/views/admin/hooks/index.html.haml
@@ -1,10 +1,10 @@
- page_title @hook.pluralized_name
-.row.prepend-top-default
+.row.gl-mt-3
.col-lg-4
= render 'shared/web_hooks/title_and_docs', hook: @hook
- .col-lg-8.append-bottom-default
+ .col-lg-8.gl-mb-3
= form_for @hook, as: :hook, url: admin_hooks_path do |f|
= render partial: 'form', locals: { form: f, hook: @hook }
= f.submit _('Add system hook'), class: 'btn btn-success'
diff --git a/app/views/admin/impersonation_tokens/index.html.haml b/app/views/admin/impersonation_tokens/index.html.haml
index 8342507d8a6..ec393fdd794 100644
--- a/app/views/admin/impersonation_tokens/index.html.haml
+++ b/app/views/admin/impersonation_tokens/index.html.haml
@@ -6,7 +6,7 @@
= render 'admin/users/head'
-.row.prepend-top-default
+.row.gl-mt-3
.col-lg-12
- if @new_impersonation_token
= render 'shared/access_tokens/created_container',
diff --git a/app/views/admin/jobs/index.html.haml b/app/views/admin/jobs/index.html.haml
index f1bdd52b399..32c0a801a1d 100644
--- a/app/views/admin/jobs/index.html.haml
+++ b/app/views/admin/jobs/index.html.haml
@@ -1,4 +1,5 @@
-- breadcrumb_title "Jobs"
+- breadcrumb_title _("Jobs")
+- page_title _("Jobs")
.top-area.scrolling-tabs-container.inner-page-scroll-tabs
- build_path_proc = ->(scope) { admin_jobs_path(scope: scope) }
diff --git a/app/views/admin/keys/show.html.haml b/app/views/admin/keys/show.html.haml
index 9ee77c77398..03cc0ae15be 100644
--- a/app/views/admin/keys/show.html.haml
+++ b/app/views/admin/keys/show.html.haml
@@ -1,2 +1,2 @@
-- page_title @key.title, "Keys"
+- page_title @key.title, _("Keys")
= render "profiles/keys/key_details", admin: true
diff --git a/app/views/admin/projects/show.html.haml b/app/views/admin/projects/show.html.haml
index f9d42d3f53b..96337d357eb 100644
--- a/app/views/admin/projects/show.html.haml
+++ b/app/views/admin/projects/show.html.haml
@@ -1,13 +1,14 @@
-- add_to_breadcrumbs "Projects", admin_projects_path
+- add_to_breadcrumbs _("Projects"), admin_projects_path
- breadcrumb_title @project.full_name
-- page_title @project.full_name, "Projects"
+- page_title @project.full_name, _("Projects")
- @content_class = "admin-projects"
+.js-remove-member-modal
%h3.page-title
- Project: #{@project.full_name}
+ = _('Project: %{name}') % { name: @project.full_name }
= link_to edit_project_path(@project), class: "btn btn-nr float-right" do
%i.fa.fa-pencil-square-o
- Edit
+ = _('Edit')
%hr
- if @project.last_repository_check_failed?
.row
@@ -21,57 +22,67 @@
.col-md-6
.card
.card-header
- Project info:
+ = _('Project info:')
%ul.content-list
%li
- %span.light Name:
+ %span.light
+ = _('Name:')
%strong
= link_to @project.name, project_path(@project)
%li
- %span.light Namespace:
+ %span.light
+ = _('Namespace:')
%strong
- if @project.namespace
= link_to @project.namespace.human_name, [:admin, @project.group || @project.owner]
- else
- Global
+ = s_('ProjectSettings|Global')
%li
- %span.light Owned by:
+ %span.light
+ = _('Owned by:')
%strong
- if @project.owner
= link_to @project.owner_name, [:admin, @project.owner]
- else
- (deleted)
+ = _('(deleted)')
%li
- %span.light Created by:
+ %span.light
+ = _('Created by:')
%strong
- = @project.creator.try(:name) || '(deleted)'
+ = @project.creator.try(:name) || _('(deleted)')
%li
- %span.light Created on:
+ %span.light
+ = _('Created on:')
%strong
= @project.created_at.to_s(:medium)
%li
- %span.light ID:
+ %span.light
+ = _('ID:')
%strong
= @project.id
%li
- %span.light http:
+ %span.light
+ = _('http:')
%strong
= link_to @project.http_url_to_repo, project_path(@project)
%li
- %span.light ssh:
+ %span.light
+ = _('ssh:')
%strong
= link_to @project.ssh_url_to_repo, project_path(@project)
- if @project.repository.exists?
%li
- %span.light Gitaly storage name:
+ %span.light
+ = _('Gitaly storage name:')
%strong
= @project.repository.storage
%li
- %span.light Gitaly relative path:
+ %span.light
+ = _('Gitaly relative path:')
%strong
= @project.repository.relative_path
@@ -79,30 +90,36 @@
= render 'shared/storage_counter_statistics', storage_size: @project.statistics&.storage_size, storage_details: @project.statistics
%li
- %span.light last commit:
+ %span.light
+ = _('last commit:')
%strong
= last_commit(@project)
%li
- %span.light Git LFS status:
+ %span.light
+ = _('Git LFS status:')
%strong
= project_lfs_status(@project)
= link_to icon('question-circle'), help_page_path('topics/git/lfs/index')
- else
%li
- %span.light repository:
+ %span.light
+ = _('repository:')
%strong.cred
- does not exist
+ = _('does not exist')
- if @project.archived?
%li
- %span.light archived:
- %strong project is read-only
+ %span.light
+ = _('archived:')
+ %strong
+ = _('project is read-only')
= render_if_exists "shared_runner_status", project: @project
%li
- %span.light access:
+ %span.light
+ = _('access:')
%strong
%span{ class: visibility_level_color(@project.visibility_level) }
= visibility_level_icon(@project.visibility_level)
@@ -114,24 +131,24 @@
.card
.card-header
- Transfer project
+ = s_('ProjectSettings|Transfer project')
.card-body
= form_for @project, url: transfer_admin_project_path(@project), method: :put do |f|
.form-group.row
.col-sm-3.col-form-label
- = f.label :new_namespace_id, "Namespace"
+ = f.label :new_namespace_id, _("Namespace")
.col-sm-9
.dropdown
- = dropdown_toggle('Search for Namespace', { toggle: 'dropdown', field_name: 'new_namespace_id' }, { toggle_class: 'js-namespace-select large' })
+ = dropdown_toggle(_('Search for Namespace'), { toggle: 'dropdown', field_name: 'new_namespace_id' }, { toggle_class: 'js-namespace-select large' })
.dropdown-menu.dropdown-select
- = dropdown_title('Namespaces')
- = dropdown_filter("Search for Namespace")
+ = dropdown_title(_('Namespaces'))
+ = dropdown_filter(_('Search for Namespace'))
= dropdown_content
= dropdown_loading
.form-group.row
.offset-sm-3.col-sm-9
- = f.submit 'Transfer', class: 'btn btn-primary'
+ = f.submit _('Transfer'), class: 'btn btn-primary'
.card.repository-check
.card-header
@@ -151,18 +168,18 @@
= link_to icon('question-circle'), help_page_path('administration/repository_checks')
.form-group
- = f.submit 'Trigger repository check', class: 'btn btn-primary'
+ = f.submit _('Trigger repository check'), class: 'btn btn-primary'
.col-md-6
- if @group
.card
.card-header
%strong= @group.name
- group members
+ = _('group members')
%span.badge.badge-pill= @group_members.size
.float-right
= link_to admin_group_path(@group), class: 'btn btn-sm' do
- = icon('pencil-square-o', text: 'Manage access')
+ = icon('pencil-square-o', text: _('Manage access'))
%ul.content-list.members-list
= render partial: 'shared/members/member', collection: @group_members, as: :member, locals: { show_controls: false }
.card-footer
@@ -173,10 +190,10 @@
.card
.card-header
%strong= @project.name
- project members
+ = _('project members')
%span.badge.badge-pill= @project.users.size
.float-right
- = link_to icon('pencil-square-o', text: 'Manage access'), project_project_members_path(@project), class: "btn btn-sm"
+ = link_to icon('pencil-square-o', text: _('Manage access')), project_project_members_path(@project), class: "btn btn-sm"
%ul.content-list.project_members.members-list
= render partial: 'shared/members/member', collection: @project_members, as: :member, locals: { show_controls: false }
.card-footer
diff --git a/app/views/admin/requests_profiles/index.html.haml b/app/views/admin/requests_profiles/index.html.haml
index efc16bb4d3b..6e1ac452d52 100644
--- a/app/views/admin/requests_profiles/index.html.haml
+++ b/app/views/admin/requests_profiles/index.html.haml
@@ -1,4 +1,4 @@
-- page_title 'Requests Profiles'
+- page_title _('Requests Profiles')
%h3.page-title
= page_title
@@ -9,7 +9,7 @@
to profile the request
- if @profiles.present?
- .prepend-top-default
+ .gl-mt-3
- @profiles.each do |path, profiles|
.card.card-small
.card-header
diff --git a/app/views/admin/runners/_runner.html.haml b/app/views/admin/runners/_runner.html.haml
index 423472324fe..5c834c2125f 100644
--- a/app/views/admin/runners/_runner.html.haml
+++ b/app/views/admin/runners/_runner.html.haml
@@ -72,8 +72,8 @@
= link_to [:pause, :admin, runner], method: :get, class: 'btn btn-default has-tooltip', title: _('Pause'), ref: 'tooltip', aria: { label: _('Pause') }, data: { placement: 'top', container: 'body', confirm: _('Are you sure?') } do
= icon('pause')
- else
- = link_to [:resume, :admin, runner], method: :get, class: 'btn btn-default has-tooltip', title: _('Resume'), ref: 'tooltip', aria: { label: _('Resume') }, data: { placement: 'top', container: 'body'} do
- = icon('play')
+ = link_to [:resume, :admin, runner], method: :get, class: 'btn btn-default has-tooltip gl-px-3', title: _('Resume'), ref: 'tooltip', aria: { label: _('Resume') }, data: { placement: 'top', container: 'body'} do
+ = sprite_icon('play')
.btn-group
= link_to [:admin, runner], method: :delete, class: 'btn btn-danger has-tooltip', title: _('Remove'), ref: 'tooltip', aria: { label: _('Remove') }, data: { placement: 'top', container: 'body', confirm: _('Are you sure?') } do
= icon('remove')
diff --git a/app/views/admin/runners/_sort_dropdown.html.haml b/app/views/admin/runners/_sort_dropdown.html.haml
index 4f4f0a543e0..3b3de042511 100644
--- a/app/views/admin/runners/_sort_dropdown.html.haml
+++ b/app/views/admin/runners/_sort_dropdown.html.haml
@@ -1,6 +1,6 @@
- sorted_by = sort_options_hash[@sort]
-.dropdown.inline.prepend-left-10
+.dropdown.inline.gl-ml-3
%button.dropdown-menu-toggle{ type: 'button', data: { toggle: 'dropdown', display: 'static' } }
= sorted_by
= icon('chevron-down')
diff --git a/app/views/admin/runners/index.html.haml b/app/views/admin/runners/index.html.haml
index 59e28a3b244..08d65819476 100644
--- a/app/views/admin/runners/index.html.haml
+++ b/app/views/admin/runners/index.html.haml
@@ -1,4 +1,5 @@
- breadcrumb_title _('Runners')
+- page_title _('Runners')
.row
.col-sm-6
diff --git a/app/views/admin/runners/show.html.haml b/app/views/admin/runners/show.html.haml
index 0120d4038b9..0c2b9bab357 100644
--- a/app/views/admin/runners/show.html.haml
+++ b/app/views/admin/runners/show.html.haml
@@ -9,6 +9,7 @@
%span.runner-state.runner-state-specific
Specific
+- page_title _("Runners")
- add_to_breadcrumbs _("Runners"), admin_runners_path
- breadcrumb_title "##{@runner.id}"
diff --git a/app/views/admin/services/_form.html.haml b/app/views/admin/services/_form.html.haml
index d18e91c0b14..f2153e503af 100644
--- a/app/views/admin/services/_form.html.haml
+++ b/app/views/admin/services/_form.html.haml
@@ -4,7 +4,7 @@
%p #{@service.description} template.
= form_for :service, url: admin_application_settings_service_path, method: :put, html: { class: 'fieldset-form' } do |form|
- = render 'shared/service_settings', form: form, service: @service
+ = render 'shared/service_settings', form: form, integration: @service
.footer-block.row-content-block
= form.submit 'Save', class: 'btn btn-success'
diff --git a/app/views/admin/services/edit.html.haml b/app/views/admin/services/edit.html.haml
index 00ed5464a44..d13b5a34dac 100644
--- a/app/views/admin/services/edit.html.haml
+++ b/app/views/admin/services/edit.html.haml
@@ -1,5 +1,6 @@
-- add_to_breadcrumbs "Service Templates", admin_application_settings_services_path
+- add_to_breadcrumbs _("Service Templates"), admin_application_settings_services_path
+- page_title @service.title, _("Service Templates")
- breadcrumb_title @service.title
-- page_title @service.title, "Service Templates"
+- @content_class = 'limit-container-width' unless fluid_layout
= render 'form'
diff --git a/app/views/admin/services/index.html.haml b/app/views/admin/services/index.html.haml
index e0a1a3549a5..ec343c38470 100644
--- a/app/views/admin/services/index.html.haml
+++ b/app/views/admin/services/index.html.haml
@@ -1,4 +1,4 @@
-- page_title "Service Templates"
+- page_title _("Service Templates")
%h3.page-title Service templates
%p.light= s_('AdminSettings|Service template allows you to set default values for integrations')
@@ -11,13 +11,24 @@
%th Description
%th Last edit
- @services.each do |service|
- %tr
- %td
- = boolean_to_icon service.activated?
- %td
- = link_to edit_admin_application_settings_service_path(service.id) do
- %strong= service.title
- %td
- = service.description
- %td.light
- = time_ago_with_tooltip service.updated_at
+ - if service.type.in?(@existing_instance_types)
+ %tr
+ %td
+ %td
+ = link_to edit_admin_application_settings_integration_path(service.to_param), class: 'gl-text-blue-300!' do
+ %strong.has-tooltip{ title: s_('AdminSettings|Moved to integrations'), data: { container: 'body' } }
+ = service.title
+ %td.gl-cursor-default.gl-text-gray-600
+ = service.description
+ %td
+ - else
+ %tr
+ %td
+ = boolean_to_icon service.activated?
+ %td
+ = link_to edit_admin_application_settings_service_path(service.id) do
+ %strong= service.title
+ %td
+ = service.description
+ %td.light
+ = time_ago_with_tooltip service.updated_at
diff --git a/app/views/admin/sessions/new.html.haml b/app/views/admin/sessions/new.html.haml
index 4ce1629bb53..67c607270a5 100644
--- a/app/views/admin/sessions/new.html.haml
+++ b/app/views/admin/sessions/new.html.haml
@@ -15,7 +15,7 @@
-# Show a message if none of the mechanisms above are enabled
- if !allow_admin_mode_password_authentication_for_web? && !ldap_sign_in_enabled? && !omniauth_enabled?
- .prepend-top-default.center
+ .gl-mt-3.center
= _('No authentication methods configured.')
- if omniauth_enabled? && button_based_providers_enabled?
diff --git a/app/views/admin/spam_logs/index.html.haml b/app/views/admin/spam_logs/index.html.haml
index b45d3e4823b..40fbc559d72 100644
--- a/app/views/admin/spam_logs/index.html.haml
+++ b/app/views/admin/spam_logs/index.html.haml
@@ -1,4 +1,4 @@
-- page_title "Spam Logs"
+- page_title _("Spam Logs")
%h3.page-title Spam Logs
%hr
- if @spam_logs.present?
diff --git a/app/views/admin/system_info/show.html.haml b/app/views/admin/system_info/show.html.haml
index b7648979edd..312ca62cfdf 100644
--- a/app/views/admin/system_info/show.html.haml
+++ b/app/views/admin/system_info/show.html.haml
@@ -1,6 +1,6 @@
- page_title _('System Info')
-.prepend-top-default
+.gl-mt-3
.row
.col-sm
.bg-light.light-well
@@ -11,7 +11,7 @@
- else
= icon('warning', class: 'text-warning')
= _('Unable to collect CPU info')
- .bg-light.light-well.prepend-top-default
+ .bg-light.light-well.gl-mt-3
%h4= _('Memory Usage')
.data
- if @memory
@@ -19,7 +19,7 @@
- else
= icon('warning', class: 'text-warning')
= _('Unable to collect memory info')
- .bg-light.light-well.prepend-top-default
+ .bg-light.light-well.gl-mt-3
%h4= _('Uptime')
.data
%h2= distance_of_time_in_words_to_now(Rails.application.config.booted_at)
diff --git a/app/views/admin/users/_access_levels.html.haml b/app/views/admin/users/_access_levels.html.haml
index e3ab2e4f9bd..3ba01e8a350 100644
--- a/app/views/admin/users/_access_levels.html.haml
+++ b/app/views/admin/users/_access_levels.html.haml
@@ -1,5 +1,6 @@
%fieldset
- %legend Access
+ %legend
+ = s_('AdminUsers|Access')
.form-group.row
.col-sm-2.col-form-label
= f.label :projects_limit
@@ -7,43 +8,43 @@
= f.number_field :projects_limit, min: 0, max: Gitlab::Database::MAX_INT_VALUE, class: 'form-control'
.form-group.row
- .col-sm-2.col-form-label
+ .col-sm-2.col-form-label.gl-pt-0
= f.label :can_create_group
.col-sm-10
= f.check_box :can_create_group
.form-group.row
- .col-sm-2.col-form-label
+ .col-sm-2.col-form-label.gl-pt-0
= f.label :access_level
.col-sm-10
- editing_current_user = (current_user == @user)
= f.radio_button :access_level, :regular, disabled: editing_current_user
= f.label :access_level_regular, class: 'font-weight-bold' do
- Regular
+ = s_('AdminUsers|Regular')
%p.light
- Regular users have access to their groups and projects
+ = s_('AdminUsers|Regular users have access to their groups and projects')
= render_if_exists 'admin/users/auditor_access_level_radio', f: f, disabled: editing_current_user
= f.radio_button :access_level, :admin, disabled: editing_current_user
= f.label :access_level_admin, class: 'font-weight-bold' do
- Admin
+ = s_('AdminUsers|Admin')
%p.light
- Administrators have access to all groups, projects and users and can manage all features in this installation
+ = s_('AdminUsers|Administrators have access to all groups, projects and users and can manage all features in this installation')
- if editing_current_user
%p.light
- You cannot remove your own admin rights.
+ = s_('AdminUsers|You cannot remove your own admin rights.')
.form-group.row
- .col-sm-2.col-form-label
+ .col-sm-2.col-form-label.gl-pt-0
= f.label :external
.hidden{ data: user_internal_regex_data }
- .col-sm-10
+ .col-sm-10.gl-display-flex.gl-align-items-baseline
= f.check_box :external do
- External
- %p.light
- External users cannot see internal or private projects unless access is explicitly granted. Also, external users cannot create projects, groups, or personal snippets.
+ = s_('AdminUsers|External')
+ %p.light.gl-pl-2
+ = s_('AdminUsers|External users cannot see internal or private projects unless access is explicitly granted. Also, external users cannot create projects, groups, or personal snippets.')
%row.hidden#warning_external_automatically_set.hidden
.badge.badge-warning.text-white
- = _('Automatically marked as default internal user')
+ = s_('AdminUsers|Automatically marked as default internal user')
diff --git a/app/views/admin/users/_head.html.haml b/app/views/admin/users/_head.html.haml
index a218885a00e..3403e9e5abf 100644
--- a/app/views/admin/users/_head.html.haml
+++ b/app/views/admin/users/_head.html.haml
@@ -28,4 +28,4 @@
= link_to "Identities", admin_user_identities_path(@user)
= nav_link(controller: :impersonation_tokens) do
= link_to "Impersonation Tokens", admin_user_impersonation_tokens_path(@user)
-.append-bottom-default
+.gl-mb-3
diff --git a/app/views/admin/users/_user_listing_note.html.haml b/app/views/admin/users/_user_listing_note.html.haml
index df4af009c5c..b6c9bc43339 100644
--- a/app/views/admin/users/_user_listing_note.html.haml
+++ b/app/views/admin/users/_user_listing_note.html.haml
@@ -1,3 +1,3 @@
- if user.note.present?
%span.has-tooltip.user-note{ title: user.note }
- = icon("sticky-note-o cgrey")
+ = sprite_icon('document', size: 16, css_class: 'gl-vertical-align-middle')
diff --git a/app/views/admin/users/edit.html.haml b/app/views/admin/users/edit.html.haml
index 3b6fd71500d..7d10e839cd6 100644
--- a/app/views/admin/users/edit.html.haml
+++ b/app/views/admin/users/edit.html.haml
@@ -1,4 +1,4 @@
-- page_title "Edit", @user.name, "Users"
+- page_title _("Edit"), @user.name, _("Users")
%h3.page-title
Edit user: #{@user.name}
%hr
diff --git a/app/views/admin/users/index.html.haml b/app/views/admin/users/index.html.haml
index ecbabab3e7f..05988c17412 100644
--- a/app/views/admin/users/index.html.haml
+++ b/app/views/admin/users/index.html.haml
@@ -1,10 +1,10 @@
-- page_title "Users"
+- page_title _("Users")
.top-area.scrolling-tabs-container.inner-page-scroll-tabs
.fade-left
- = icon('angle-left')
+ = sprite_icon('chevron-lg-left', size: 12)
.fade-right
- = icon('angle-right')
+ = sprite_icon('chevron-lg-right', size: 12)
%ul.nav-links.nav.nav-tabs.scrolling-tabs
= nav_link(html_options: { class: active_when(params[:filter].nil?) }) do
= link_to admin_users_path do
diff --git a/app/views/admin/users/keys.html.haml b/app/views/admin/users/keys.html.haml
index 103bbb3b063..5f9d11af7c1 100644
--- a/app/views/admin/users/keys.html.haml
+++ b/app/views/admin/users/keys.html.haml
@@ -1,5 +1,5 @@
-- add_to_breadcrumbs "Users", admin_users_path
+- add_to_breadcrumbs _("Users"), admin_users_path
- breadcrumb_title @user.name
-- page_title "SSH Keys", @user.name, "Users"
+- page_title _("SSH Keys"), @user.name, _("Users")
= render 'admin/users/head'
= render 'profiles/keys/key_table', admin: true
diff --git a/app/views/admin/users/new.html.haml b/app/views/admin/users/new.html.haml
index bfc36ed7373..e5e6790b789 100644
--- a/app/views/admin/users/new.html.haml
+++ b/app/views/admin/users/new.html.haml
@@ -1,4 +1,4 @@
-- page_title "New User"
+- page_title _("New User")
%h3.page-title
New user
%hr
diff --git a/app/views/admin/users/projects.html.haml b/app/views/admin/users/projects.html.haml
index e6da81831ab..f66d9b76afc 100644
--- a/app/views/admin/users/projects.html.haml
+++ b/app/views/admin/users/projects.html.haml
@@ -1,6 +1,6 @@
-- add_to_breadcrumbs "Users", admin_users_path
+- add_to_breadcrumbs _("Users"), admin_users_path
- breadcrumb_title @user.name
-- page_title "Groups and projects", @user.name, "Users"
+- page_title _("Groups and projects"), @user.name, _("Users")
= render 'admin/users/head'
- if @user.groups.any?
@@ -16,7 +16,7 @@
.float-right
%span.light.vertical-align-middle= group_member.human_access
- unless group_member.owner?
- = link_to group_group_member_path(group, group_member), data: { confirm: remove_member_message(group_member) }, method: :delete, remote: true, class: "btn-sm btn btn-remove prepend-left-10", title: 'Remove user from group' do
+ = link_to group_group_member_path(group, group_member), data: { confirm: remove_member_message(group_member) }, method: :delete, remote: true, class: "btn-sm btn btn-remove gl-ml-3", title: 'Remove user from group' do
%i.fa.fa-times.fa-inverse
.row
@@ -46,5 +46,5 @@
%span.light.vertical-align-middle= member.human_access
- if member.respond_to? :project
- = link_to project_project_member_path(project, member), data: { confirm: remove_member_message(member) }, remote: true, method: :delete, class: "btn-sm btn btn-remove prepend-left-10", title: 'Remove user from project' do
+ = link_to project_project_member_path(project, member), data: { confirm: remove_member_message(member) }, remote: true, method: :delete, class: "btn-sm btn btn-remove gl-ml-3", title: 'Remove user from project' do
%i.fa.fa-times
diff --git a/app/views/admin/users/show.html.haml b/app/views/admin/users/show.html.haml
index e76f1f6444c..2bc39a23b2d 100644
--- a/app/views/admin/users/show.html.haml
+++ b/app/views/admin/users/show.html.haml
@@ -1,6 +1,6 @@
-- add_to_breadcrumbs "Users", admin_users_path
+- add_to_breadcrumbs _("Users"), admin_users_path
- breadcrumb_title @user.name
-- page_title @user.name, "Users"
+- page_title @user.name, _("Users")
= render 'admin/users/head'
.row
@@ -86,34 +86,22 @@
%li
%span.light Current sign-in IP:
%strong
- - if @user.current_sign_in_ip # rubocop:disable Style/RedundantCondition
- = @user.current_sign_in_ip
- - else
- never
+ = @user.current_sign_in_ip || _('never')
%li
%span.light Current sign-in at:
%strong
- - if @user.current_sign_in_at
- = @user.current_sign_in_at.to_s(:medium)
- - else
- never
+ = @user.current_sign_in_at&.to_s(:medium) || _('never')
%li
%span.light Last sign-in IP:
%strong
- - if @user.last_sign_in_ip # rubocop:disable Style/RedundantCondition
- = @user.last_sign_in_ip
- - else
- never
+ = @user.last_sign_in_ip || _('never')
%li
%span.light Last sign-in at:
%strong
- - if @user.last_sign_in_at
- = @user.last_sign_in_at.to_s(:medium)
- - else
- never
+ = @user.last_sign_in_at&.to_s(:medium) || _('never')
%li
%span.light Sign-in count:
diff --git a/app/views/ci/group_variables/_index.html.haml b/app/views/ci/group_variables/_index.html.haml
index c350ba5caf7..84bcd42e07c 100644
--- a/app/views/ci/group_variables/_index.html.haml
+++ b/app/views/ci/group_variables/_index.html.haml
@@ -6,8 +6,8 @@
= render 'ci/group_variables/variable_header'
- variables.each do |variable|
.group-variable-row.d-flex.w-100.border-bottom.pt-2.pb-2
- .table-section.section-40.append-right-10.key
+ .table-section.section-40.gl-mr-3.key
= variable.key
- .table-section.section-40.append-right-10
+ .table-section.section-40.gl-mr-3
%a.group-origin-link{ href: group_settings_ci_cd_path(variable.group) }
= variable.group.name
diff --git a/app/views/ci/group_variables/_variable_header.html.haml b/app/views/ci/group_variables/_variable_header.html.haml
index 1a3168cf781..a8d533da0e0 100644
--- a/app/views/ci/group_variables/_variable_header.html.haml
+++ b/app/views/ci/group_variables/_variable_header.html.haml
@@ -1,5 +1,5 @@
.group-variable-keys.d-flex.w-100.align-items-center.pb-2.border-bottom
- .bold.table-section.section-40.append-right-10
+ .bold.table-section.section-40.gl-mr-3
= s_('Key')
- .bold.table-section.section-40.append-right-10
+ .bold.table-section.section-40.gl-mr-3
= s_('Origin')
diff --git a/app/views/ci/variables/_content.html.haml b/app/views/ci/variables/_content.html.haml
index 0b5c1a806b2..144d13565b2 100644
--- a/app/views/ci/variables/_content.html.haml
+++ b/app/views/ci/variables/_content.html.haml
@@ -1,3 +1,3 @@
= _('Environment variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. Additionally, they can be masked so they are hidden in job logs, though they must match certain regexp requirements to do so. You can use environment variables for passwords, secret keys, or whatever you want.')
= _('You may also add variables that are made available to the running application by prepending the variable key with <code>K8S_SECRET_</code>.').html_safe
-= link_to _('More information'), help_page_path('ci/variables/README', anchor: 'variables')
+= link_to _('More information'), help_page_path('ci/variables/README', anchor: 'custom-environment-variables')
diff --git a/app/views/ci/variables/_environment_scope_header.html.haml b/app/views/ci/variables/_environment_scope_header.html.haml
index 4ba4ceec16c..fc3b7f925fc 100644
--- a/app/views/ci/variables/_environment_scope_header.html.haml
+++ b/app/views/ci/variables/_environment_scope_header.html.haml
@@ -1,2 +1,2 @@
-.bold.table-section.section-15.append-right-10
+.bold.table-section.section-15.gl-mr-3
= s_('CiVariables|Scope')
diff --git a/app/views/ci/variables/_header.html.haml b/app/views/ci/variables/_header.html.haml
index ce4dd5a4877..d0148e455de 100644
--- a/app/views/ci/variables/_header.html.haml
+++ b/app/views/ci/variables/_header.html.haml
@@ -2,7 +2,7 @@
%h4
= _('Variables')
- = link_to icon('question-circle'), help_page_path('ci/variables/README', anchor: 'variables'), target: '_blank', rel: 'noopener noreferrer'
+ = link_to icon('question-circle'), help_page_path('ci/variables/README', anchor: 'custom-environment-variables'), target: '_blank', rel: 'noopener noreferrer'
%button.btn.btn-default.js-settings-toggle{ type: 'button' }
= expanded ? _('Collapse') : _('Expand')
diff --git a/app/views/ci/variables/_index.html.haml b/app/views/ci/variables/_index.html.haml
index fa5f2c514ae..8d379774719 100644
--- a/app/views/ci/variables/_index.html.haml
+++ b/app/views/ci/variables/_index.html.haml
@@ -2,7 +2,7 @@
- if ci_variable_protected_by_default?
%p.settings-message.text-center
- - link_start = '<a href="%{url}">'.html_safe % { url: help_page_path('ci/variables/README', anchor: 'protected-variables') }
+ - link_start = '<a href="%{url}">'.html_safe % { url: help_page_path('ci/variables/README', anchor: 'protect-a-custom-variable') }
= s_('Environment variables are configured by your administrator to be %{link_start}protected%{link_end} by default').html_safe % { link_start: link_start, link_end: '</a>'.html_safe }
- if Feature.enabled?(:new_variables_ui, @project || @group, default_enabled: true)
@@ -36,7 +36,7 @@
%span.hide.js-ci-variables-save-loading-icon
.spinner.spinner-light.mr-1
= _('Save variables')
- %button.btn.btn-info.btn-inverted.prepend-left-10.js-secret-value-reveal-button{ type: 'button', data: { secret_reveal_status: "#{@variables.size == 0}" } }
+ %button.btn.btn-info.btn-inverted.gl-ml-3.js-secret-value-reveal-button{ type: 'button', data: { secret_reveal_status: "#{@variables.size == 0}" } }
- if @variables.size == 0
= n_('Hide value', 'Hide values', @variables.size)
- else
diff --git a/app/views/ci/variables/_variable_header.html.haml b/app/views/ci/variables/_variable_header.html.haml
index d3b7a5ae883..65cea00a0c4 100644
--- a/app/views/ci/variables/_variable_header.html.haml
+++ b/app/views/ci/variables/_variable_header.html.haml
@@ -2,11 +2,11 @@
%li.ci-variable-row.m-0.d-none.d-sm-block
.d-flex.w-100.align-items-center.pb-2
- .bold.table-section.section-15.append-right-10
+ .bold.table-section.section-15.gl-mr-3
= s_('CiVariables|Type')
- .bold.table-section.section-15.append-right-10
+ .bold.table-section.section-15.gl-mr-3
= s_('CiVariables|Key')
- .bold.table-section.section-15.append-right-10
+ .bold.table-section.section-15.gl-mr-3
= s_('CiVariables|Value')
- unless only_key_value
.bold.table-section.section-20
diff --git a/app/views/ci/variables/_variable_row.html.haml b/app/views/ci/variables/_variable_row.html.haml
index 4244556a24a..c69a3adb0e9 100644
--- a/app/views/ci/variables/_variable_row.html.haml
+++ b/app/views/ci/variables/_variable_row.html.haml
@@ -39,10 +39,10 @@
= value
%p.masking-validation-error.gl-field-error.hide
= s_("CiVariables|Cannot use Masked Variable with current value")
- = link_to icon('question-circle'), help_page_path('ci/variables/README', anchor: 'masked-variables'), target: '_blank', rel: 'noopener noreferrer'
+ = link_to icon('question-circle'), help_page_path('ci/variables/README', anchor: 'mask-a-custom-variable'), target: '_blank', rel: 'noopener noreferrer'
- unless only_key_value
.ci-variable-body-item.ci-variable-protected-item.table-section.section-20.mr-0.border-top-0
- .append-right-default
+ .gl-mr-3
= s_("CiVariable|Protected")
= render "shared/buttons/project_feature_toggle", is_checked: is_protected, label: s_("CiVariable|Toggle protected") do
%input{ type: "hidden",
@@ -51,7 +51,7 @@
value: is_protected,
data: { default: is_protected_default.to_s } }
.ci-variable-body-item.ci-variable-masked-item.table-section.section-20.mr-0.border-top-0
- .append-right-default
+ .gl-mr-3
= s_("CiVariable|Masked")
= render "shared/buttons/project_feature_toggle", is_checked: is_masked, label: s_("CiVariable|Toggle masked"), class_list: "js-project-feature-toggle project-feature-toggle qa-variable-masked" do
%input{ type: "hidden",
diff --git a/app/views/clusters/clusters/_advanced_settings.html.haml b/app/views/clusters/clusters/_advanced_settings.html.haml
index d823cd0412b..d1681409a93 100644
--- a/app/views/clusters/clusters/_advanced_settings.html.haml
+++ b/app/views/clusters/clusters/_advanced_settings.html.haml
@@ -40,4 +40,6 @@
%p
= s_("ClusterIntegration|Remove this Kubernetes cluster's configuration from this project. This will not delete your actual Kubernetes cluster.")
- #js-cluster-remove-actions{ data: { cluster_path: clusterable.cluster_path(@cluster), cluster_name: @cluster.name } }
+ #js-cluster-remove-actions{ data: { cluster_path: clusterable.cluster_path(@cluster),
+ cluster_name: @cluster.name,
+ has_management_project: @cluster.management_project_id? } }
diff --git a/app/views/clusters/clusters/_gcp_signup_offer_banner.html.haml b/app/views/clusters/clusters/_gcp_signup_offer_banner.html.haml
index 486625c790b..3869ca6591c 100644
--- a/app/views/clusters/clusters/_gcp_signup_offer_banner.html.haml
+++ b/app/views/clusters/clusters/_gcp_signup_offer_banner.html.haml
@@ -1,5 +1,5 @@
- link = link_to(s_('ClusterIntegration|sign up'), 'https://console.cloud.google.com/freetrial?utm_campaign=2018_cpanel&utm_source=gitlab&utm_medium=referral', target: '_blank', rel: 'noopener noreferrer')
-.bs-callout.gcp-signup-offer.alert.alert-block.alert-dismissable.prepend-top-default.append-bottom-default{ role: 'alert', data: { feature_id: UserCalloutsHelper::GCP_SIGNUP_OFFER, dismiss_endpoint: user_callouts_path } }
+.bs-callout.gcp-signup-offer.alert.alert-block.alert-dismissable.gl-mt-3.gl-mb-3{ role: 'alert', data: { feature_id: UserCalloutsHelper::GCP_SIGNUP_OFFER, dismiss_endpoint: user_callouts_path } }
%button.close.js-close{ type: "button" } &times;
.gcp-signup-offer--content
.gcp-signup-offer--icon.gl-mr-3
diff --git a/app/views/clusters/clusters/_gitlab_integration_form.html.haml b/app/views/clusters/clusters/_gitlab_integration_form.html.haml
index c5b54997407..160964b532a 100644
--- a/app/views/clusters/clusters/_gitlab_integration_form.html.haml
+++ b/app/views/clusters/clusters/_gitlab_integration_form.html.haml
@@ -10,17 +10,10 @@
.form-group
%h5= s_('ClusterIntegration|Environment scope')
- - if has_multiple_clusters?
- = field.text_field :environment_scope, class: 'col-md-6 form-control js-select-on-focus', placeholder: s_('ClusterIntegration|Environment scope')
- .form-text.text-muted= s_("ClusterIntegration|Choose which of your environments will use this cluster.")
- - else
- = text_field_tag :environment_scope, '*', class: 'col-md-6 form-control disabled', placeholder: s_('ClusterIntegration|Environment scope'), disabled: true
- - environment_scope_url = help_page_path('user/project/clusters/index', anchor: 'base-domain')
- - environment_scope_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: environment_scope_url }
- .form-text.text-muted
- %code
- = _('*')
- = s_("ClusterIntegration| is the default environment scope for this cluster. This means that all jobs, regardless of their environment, will use this cluster. %{environment_scope_start}More information%{environment_scope_end}").html_safe % { environment_scope_start: environment_scope_start, environment_scope_end: '</a>'.html_safe }
+ = field.text_field :environment_scope, class: 'col-md-6 form-control js-select-on-focus', placeholder: s_('ClusterIntegration|Environment scope')
+ - environment_scope_url = help_page_path('user/project/clusters/index', anchor: 'base-domain')
+ - environment_scope_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: environment_scope_url }
+ .form-text.text-muted= s_("ClusterIntegration|Choose which of your environments will use this cluster. %{environment_scope_start}More information%{environment_scope_end}").html_safe % { environment_scope_start: environment_scope_start, environment_scope_end: '</a>'.html_safe }
.form-group
%h5= s_('ClusterIntegration|Base domain')
diff --git a/app/views/clusters/clusters/_health.html.haml b/app/views/clusters/clusters/_health.html.haml
new file mode 100644
index 00000000000..5400bd7f201
--- /dev/null
+++ b/app/views/clusters/clusters/_health.html.haml
@@ -0,0 +1,6 @@
+%section.settings.no-animate.expanded.cluster-health-graphs.qa-cluster-health-section#cluster-health
+ - if @cluster&.application_prometheus_available?
+ #prometheus-graphs{ data: @cluster.health_data(clusterable) }
+
+ - else
+ %p.settings-message.text-center= s_("ClusterIntegration|In order to view the health of your cluster, you must first install Prometheus in the Applications tab.")
diff --git a/app/views/clusters/clusters/_health_tab.html.haml b/app/views/clusters/clusters/_health_tab.html.haml
new file mode 100644
index 00000000000..fda392693f6
--- /dev/null
+++ b/app/views/clusters/clusters/_health_tab.html.haml
@@ -0,0 +1,5 @@
+- active = params[:tab] == 'health'
+
+%li.nav-item{ role: 'presentation' }
+ %a#cluster-health-tab.nav-link.qa-health{ class: active_when(active), href: clusterable.cluster_path(@cluster.id, params: {tab: 'health'}) }
+ %span= _('Health')
diff --git a/app/views/clusters/clusters/_multiple_clusters_message.html.haml b/app/views/clusters/clusters/_multiple_clusters_message.html.haml
new file mode 100644
index 00000000000..da3e128ba32
--- /dev/null
+++ b/app/views/clusters/clusters/_multiple_clusters_message.html.haml
@@ -0,0 +1,6 @@
+- autodevops_help_url = help_page_path('topics/autodevops/index.md', anchor: 'using-multiple-kubernetes-clusters')
+- help_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe
+- help_link_end = '</a>'.html_safe
+
+%p
+ = s_('ClusterIntegration|If you are setting up multiple clusters and are using Auto DevOps, %{help_link_start}read this first%{help_link_end}.').html_safe % { help_link_start: help_link_start % { url: autodevops_help_url }, help_link_end: help_link_end }
diff --git a/app/views/clusters/clusters/_sidebar.html.haml b/app/views/clusters/clusters/_sidebar.html.haml
index 24a74c59b97..31add011bfa 100644
--- a/app/views/clusters/clusters/_sidebar.html.haml
+++ b/app/views/clusters/clusters/_sidebar.html.haml
@@ -5,4 +5,4 @@
%p
= clusterable.learn_more_link
-= render_if_exists 'clusters/multiple_clusters_message'
+= render 'clusters/clusters/multiple_clusters_message'
diff --git a/app/views/clusters/clusters/aws/_new.html.haml b/app/views/clusters/clusters/aws/_new.html.haml
index 5bbdadf83f3..ec604ca83e5 100644
--- a/app/views/clusters/clusters/aws/_new.html.haml
+++ b/app/views/clusters/clusters/aws/_new.html.haml
@@ -1,6 +1,6 @@
- if !Gitlab::CurrentSettings.eks_integration_enabled?
- - documentation_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: help_page_path('user/project/clusters/add_remove_clusters.md',
- anchor: 'additional-requirements-for-self-managed-instances') }
+ - documentation_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: help_page_path('user/project/clusters/add_eks_clusters.md',
+ anchor: 'additional-requirements-for-self-managed-instances-core-only') }
= s_('Amazon authentication is not %{link_start}correctly configured%{link_end}. Ask your GitLab administrator if you want to use this service.').html_safe % { link_start: documentation_link_start, link_end: '<a/>'.html_safe }
- else
.js-create-eks-cluster-form-container{ data: { 'gitlab-managed-cluster-help-path' => help_page_path('user/project/clusters/index.md', anchor: 'gitlab-managed-clusters'),
diff --git a/app/views/clusters/clusters/gcp/_form.html.haml b/app/views/clusters/clusters/gcp/_form.html.haml
index e83bf61ab9b..434c02a5c41 100644
--- a/app/views/clusters/clusters/gcp/_form.html.haml
+++ b/app/views/clusters/clusters/gcp/_form.html.haml
@@ -16,12 +16,11 @@
data: { token: token_in_session } }, url: clusterable.create_gcp_clusters_path, as: :cluster do |field|
= field.text_field :name, required: true, title: s_('ClusterIntegration|Cluster name is required.'),
label: s_('ClusterIntegration|Kubernetes cluster name'), label_class: 'label-bold'
- - if has_multiple_clusters?
- = field.form_group :environment_scope, label: { text: s_('ClusterIntegration|Environment scope'),
- class: 'label-bold' } do
- = field.text_field :environment_scope, required: true, class: 'form-control',
- title: 'Environment scope is required.', wrapper: false
- .form-text.text-muted= s_("ClusterIntegration|Choose which of your environments will use this cluster.")
+ = field.form_group :environment_scope, label: { text: s_('ClusterIntegration|Environment scope'),
+ class: 'label-bold' } do
+ = field.text_field :environment_scope, required: true, class: 'form-control',
+ title: 'Environment scope is required.', wrapper: false
+ .form-text.text-muted= s_("ClusterIntegration|Choose which of your environments will use this cluster.")
= field.fields_for :provider_gcp, @gcp_cluster.provider_gcp do |provider_gcp_field|
.form-group
@@ -70,7 +69,7 @@
label_class: 'label-bold' }
.form-text.text-muted
= s_('ClusterIntegration|Uses the Cloud Run, Istio, and HTTP Load Balancing addons for this cluster.')
- = link_to _('More information'), help_page_path('user/project/clusters/add_remove_clusters.md', anchor: 'cloud-run-for-anthos'), target: '_blank'
+ = link_to _('More information'), help_page_path('user/project/clusters/add_gke_clusters.md', anchor: 'cloud-run-for-anthos'), target: '_blank'
.form-group
= field.check_box :managed, { label: s_('ClusterIntegration|GitLab-managed cluster'),
diff --git a/app/views/clusters/clusters/index.html.haml b/app/views/clusters/clusters/index.html.haml
index a654a8741a4..557ad1bf280 100644
--- a/app/views/clusters/clusters/index.html.haml
+++ b/app/views/clusters/clusters/index.html.haml
@@ -12,15 +12,14 @@
= s_('ClusterIntegration|Kubernetes clusters can be used to deploy applications and to provide Review Apps for this project')
= render 'clusters/clusters/buttons'
- - if @has_ancestor_clusters
- .bs-callout.bs-callout-info
- = s_('ClusterIntegration|Clusters are utilized by selecting the nearest ancestor with a matching environment scope. For example, project clusters will override group clusters.')
- %strong
- = link_to _('More information'), help_page_path('user/group/clusters/index', anchor: 'cluster-precedence')
-
- if Feature.enabled?(:clusters_list_redesign)
#js-clusters-list-app{ data: js_clusters_list_data(clusterable.index_path(format: :json)) }
- else
+ - if @has_ancestor_clusters
+ .bs-callout.bs-callout-info
+ = s_('ClusterIntegration|Clusters are utilized by selecting the nearest ancestor with a matching environment scope. For example, project clusters will override group clusters.')
+ %strong
+ = link_to _('More information'), help_page_path('user/group/clusters/index', anchor: 'cluster-precedence')
.clusters-table.js-clusters-list
.gl-responsive-table-row.table-row-header{ role: "row" }
.table-section.section-60{ role: "rowheader" }
diff --git a/app/views/clusters/clusters/new.html.haml b/app/views/clusters/clusters/new.html.haml
index fae78fbb7f4..0a51d4b2e93 100644
--- a/app/views/clusters/clusters/new.html.haml
+++ b/app/views/clusters/clusters/new.html.haml
@@ -6,7 +6,7 @@
= render_gcp_signup_offer
-.row.prepend-top-default
+.row.gl-mt-3
.col-md-3
= render 'sidebar'
.col-md-9.js-toggle-container
diff --git a/app/views/clusters/clusters/show.html.haml b/app/views/clusters/clusters/show.html.haml
index 83b8092fb48..ffa99f06593 100644
--- a/app/views/clusters/clusters/show.html.haml
+++ b/app/views/clusters/clusters/show.html.haml
@@ -32,7 +32,7 @@
ingress_mod_security_help_path: help_page_path('user/clusters/applications.md', anchor: 'web-application-firewall-modsecurity'),
environments_help_path: help_page_path('ci/environments/index.md', anchor: 'defining-environments'),
clusters_help_path: help_page_path('user/project/clusters/index.md', anchor: 'deploying-to-a-kubernetes-cluster'),
- deploy_boards_help_path: help_page_path('user/project/deploy_boards.html', anchor: 'enabling-deploy-boards'),
+ deploy_boards_help_path: help_page_path('user/project/deploy_boards.md', anchor: 'enabling-deploy-boards'),
cloud_run_help_path: help_page_path('user/project/clusters/add_remove_clusters.md', anchor: 'cloud-run-for-anthos'),
manage_prometheus_path: manage_prometheus_path,
cluster_id: @cluster.id } }
@@ -55,7 +55,7 @@
%ul.nav-links.mobile-separator.nav.nav-tabs{ role: 'tablist' }
= render 'details_tab'
= render_if_exists 'clusters/clusters/environments_tab'
- = render_if_exists 'clusters/clusters/health_tab'
+ = render 'clusters/clusters/health_tab'
= render 'applications_tab'
= render 'advanced_settings_tab'
diff --git a/app/views/clusters/clusters/user/_form.html.haml b/app/views/clusters/clusters/user/_form.html.haml
index ce226d29113..11772107135 100644
--- a/app/views/clusters/clusters/user/_form.html.haml
+++ b/app/views/clusters/clusters/user/_form.html.haml
@@ -13,10 +13,10 @@
url: clusterable.create_user_clusters_path, as: :cluster do |field|
= field.text_field :name, required: true, title: s_('ClusterIntegration|Cluster name is required.'),
label: s_('ClusterIntegration|Kubernetes cluster name'), label_class: 'label-bold'
- - if has_multiple_clusters?
- = field.text_field :environment_scope, required: true, title: 'Environment scope is required.',
- label: s_('ClusterIntegration|Environment scope'), label_class: 'label-bold',
- help: s_("ClusterIntegration|Choose which of your environments will use this cluster.")
+
+ = field.text_field :environment_scope, required: true, title: s_('ClusterIntegration|Environment scope is required.'),
+ label: s_('ClusterIntegration|Environment scope'), label_class: 'label-bold',
+ help: s_('ClusterIntegration|Choose which of your environments will use this cluster.')
= field.fields_for :platform_kubernetes, @user_cluster.platform_kubernetes do |platform_kubernetes_field|
= platform_kubernetes_field.url_field :api_url, required: true,
diff --git a/app/views/dashboard/_projects_head.html.haml b/app/views/dashboard/_projects_head.html.haml
index 97a446dbeec..5e78749fee2 100644
--- a/app/views/dashboard/_projects_head.html.haml
+++ b/app/views/dashboard/_projects_head.html.haml
@@ -12,8 +12,8 @@
= link_to _("New project"), new_project_path, class: "btn btn-success"
.top-area.scrolling-tabs-container.inner-page-scroll-tabs
- .fade-left= icon('angle-left')
- .fade-right= icon('angle-right')
+ .fade-left= sprite_icon('chevron-lg-left', size: 12)
+ .fade-right= sprite_icon('chevron-lg-right', size: 12)
%ul.nav-links.scrolling-tabs.mobile-separator.nav.nav-tabs{ class: ('border-0' if feature_project_list_filter_bar) }
= nav_link(page: [dashboard_projects_path, root_path]) do
= link_to dashboard_projects_path, class: 'shortcuts-activity', data: {placement: 'right'} do
diff --git a/app/views/dashboard/activity.html.haml b/app/views/dashboard/activity.html.haml
index d7306f5932d..1e93613e978 100644
--- a/app/views/dashboard/activity.html.haml
+++ b/app/views/dashboard/activity.html.haml
@@ -5,8 +5,8 @@
= render_dashboard_gold_trial(current_user)
-- page_title "Activity"
-- header_title "Activity", activity_dashboard_path
+- page_title _("Activity")
+- header_title _("Activity"), activity_dashboard_path
= render "projects/last_push"
= render 'dashboard/activity_head'
diff --git a/app/views/dashboard/groups/index.html.haml b/app/views/dashboard/groups/index.html.haml
index d1d8d970b59..9536ff940f5 100644
--- a/app/views/dashboard/groups/index.html.haml
+++ b/app/views/dashboard/groups/index.html.haml
@@ -1,6 +1,6 @@
- @hide_top_links = true
-- page_title "Groups"
-- header_title "Groups", dashboard_groups_path
+- page_title _("Groups")
+- header_title _("Groups"), dashboard_groups_path
= render_dashboard_gold_trial(current_user)
= render 'dashboard/groups_head'
diff --git a/app/views/dashboard/milestones/index.html.haml b/app/views/dashboard/milestones/index.html.haml
index b9be6028b72..a0c1c314a85 100644
--- a/app/views/dashboard/milestones/index.html.haml
+++ b/app/views/dashboard/milestones/index.html.haml
@@ -1,6 +1,6 @@
- @hide_top_links = true
-- page_title 'Milestones'
-- header_title 'Milestones', dashboard_milestones_path
+- page_title _('Milestones')
+- header_title _('Milestones'), dashboard_milestones_path
.page-title-holder.d-flex.align-items-center
%h1.page-title= _('Milestones')
diff --git a/app/views/dashboard/projects/index.html.haml b/app/views/dashboard/projects/index.html.haml
index d2aa07bab22..2e7eab87af3 100644
--- a/app/views/dashboard/projects/index.html.haml
+++ b/app/views/dashboard/projects/index.html.haml
@@ -5,8 +5,8 @@
= render_dashboard_gold_trial(current_user)
-- page_title "Projects"
-- header_title "Projects", dashboard_projects_path
+- page_title _("Projects")
+- header_title _("Projects"), dashboard_projects_path
= render "projects/last_push"
- if show_projects?(@projects, params)
diff --git a/app/views/dashboard/snippets/index.html.haml b/app/views/dashboard/snippets/index.html.haml
index 2f0cc76f2e0..68457ab33f7 100644
--- a/app/views/dashboard/snippets/index.html.haml
+++ b/app/views/dashboard/snippets/index.html.haml
@@ -1,6 +1,6 @@
- @hide_top_links = true
-- page_title "Snippets"
-- header_title "Snippets", dashboard_snippets_path
+- page_title _("Snippets")
+- header_title _("Snippets"), dashboard_snippets_path
- button_path = new_snippet_path if can?(current_user, :create_snippet)
= render 'dashboard/snippets_head'
diff --git a/app/views/dashboard/todos/_todo.html.haml b/app/views/dashboard/todos/_todo.html.haml
index f5ffe8f2e36..82abb9b3b8a 100644
--- a/app/views/dashboard/todos/_todo.html.haml
+++ b/app/views/dashboard/todos/_todo.html.haml
@@ -4,7 +4,7 @@
.todo-item.todo-block.align-self-center
.todo-title
- - unless todo.build_failed? || todo.unmergeable?
+ - if todo_author_display?(todo)
= todo_target_state_pill(todo)
%span.title-item.author-name.bold
diff --git a/app/views/dashboard/todos/index.html.haml b/app/views/dashboard/todos/index.html.haml
index cfc637592d3..9b6150c4be2 100644
--- a/app/views/dashboard/todos/index.html.haml
+++ b/app/views/dashboard/todos/index.html.haml
@@ -1,6 +1,6 @@
- @hide_top_links = true
-- page_title "To-Do List"
-- header_title "To-Do List", dashboard_todos_path
+- page_title _("To-Do List")
+- header_title _("To-Do List"), dashboard_todos_path
= render_dashboard_gold_trial(current_user)
@@ -25,7 +25,7 @@
.nav-controls
- if @todos.any?(&:pending?)
- .append-right-default
+ .gl-mr-3
= link_to destroy_all_dashboard_todos_path(todos_filter_params), class: 'btn btn-loading d-flex align-items-center js-todos-mark-all', method: :delete, data: { href: destroy_all_dashboard_todos_path(todos_filter_params) } do
Mark all as done
%span.spinner.ml-1
diff --git a/app/views/devise/mailer/_confirmation_instructions_account.html.haml b/app/views/devise/mailer/_confirmation_instructions_account.html.haml
index 65565b7b8a8..27ef586d90f 100644
--- a/app/views/devise/mailer/_confirmation_instructions_account.html.haml
+++ b/app/views/devise/mailer/_confirmation_instructions_account.html.haml
@@ -1,7 +1,7 @@
- confirmation_link = confirmation_url(@resource, confirmation_token: @token)
-- if @resource.unconfirmed_email.present?
+- if @resource.unconfirmed_email.present? || !@resource.created_recently?
#content
- = email_default_heading(@resource.unconfirmed_email)
+ = email_default_heading(@resource.unconfirmed_email || @resource.email)
%p Click the link below to confirm your email address.
#cta
= link_to 'Confirm your email address', confirmation_link
diff --git a/app/views/devise/mailer/_confirmation_instructions_account.text.erb b/app/views/devise/mailer/_confirmation_instructions_account.text.erb
index 01f09aa763d..5bccb68bbe2 100644
--- a/app/views/devise/mailer/_confirmation_instructions_account.text.erb
+++ b/app/views/devise/mailer/_confirmation_instructions_account.text.erb
@@ -1,6 +1,5 @@
-<% if @resource.unconfirmed_email.present? %>
-<%= @resource.unconfirmed_email %>,
-
+<% if @resource.unconfirmed_email.present? || !@resource.created_recently? %>
+<%= @resource.unconfirmed_email || @resource.email %>,
Use the link below to confirm your email address.
<% else %>
<% if Gitlab.com? %>
diff --git a/app/views/devise/mailer/_confirmation_instructions_secondary.html.haml b/app/views/devise/mailer/_confirmation_instructions_secondary.html.haml
index ccc3e734276..f14d50eaf71 100644
--- a/app/views/devise/mailer/_confirmation_instructions_secondary.html.haml
+++ b/app/views/devise/mailer/_confirmation_instructions_secondary.html.haml
@@ -1,5 +1,5 @@
#content
- = email_default_heading("#{sanitize_name(@resource.user.name)}, you've added an additional email!")
+ = email_default_heading("#{sanitize_name(@resource.user.name)}, confirm your email address now!")
%p Click the link below to confirm your email address (#{@resource.email})
#cta
= link_to 'Confirm your email address', confirmation_url(@resource, confirmation_token: @token)
diff --git a/app/views/devise/mailer/_confirmation_instructions_secondary.text.erb b/app/views/devise/mailer/_confirmation_instructions_secondary.text.erb
index a3b28cb0b84..b91498ccfae 100644
--- a/app/views/devise/mailer/_confirmation_instructions_secondary.text.erb
+++ b/app/views/devise/mailer/_confirmation_instructions_secondary.text.erb
@@ -1,4 +1,4 @@
-<%= @resource.user.name %>, you've added an additional email!
+<%= @resource.user.name %>, confirm your email address now!
Use the link below to confirm your email address (<%= @resource.email %>)
diff --git a/app/views/devise/registrations/new.html.haml b/app/views/devise/registrations/new.html.haml
index 9fb5e27b692..fb00e1b4384 100644
--- a/app/views/devise/registrations/new.html.haml
+++ b/app/views/devise/registrations/new.html.haml
@@ -1,4 +1,4 @@
-- page_title "Sign up"
+- page_title _("Sign up")
- if experiment_enabled?(:signup_flow)
.row
.col-lg-7
diff --git a/app/views/devise/sessions/new.html.haml b/app/views/devise/sessions/new.html.haml
index fd6d8f3f769..c466d2ce936 100644
--- a/app/views/devise/sessions/new.html.haml
+++ b/app/views/devise/sessions/new.html.haml
@@ -1,4 +1,4 @@
-- page_title "Sign in"
+- page_title _("Sign in")
#signin-container
- if any_form_based_providers_enabled?
diff --git a/app/views/devise/sessions/two_factor.html.haml b/app/views/devise/sessions/two_factor.html.haml
index 126d8450568..115ebc94238 100644
--- a/app/views/devise/sessions/two_factor.html.haml
+++ b/app/views/devise/sessions/two_factor.html.haml
@@ -8,10 +8,10 @@
= f.hidden_field :remember_me, value: resource_params.fetch(:remember_me, 0)
%div
= f.label 'Two-Factor Authentication code', name: :otp_attempt
- = f.text_field :otp_attempt, class: 'form-control', required: true, autofocus: true, autocomplete: 'off', title: 'This field is required.'
+ = f.text_field :otp_attempt, class: 'form-control', required: true, autofocus: true, autocomplete: 'off', title: 'This field is required.', data: { qa_selector: 'two_fa_code_field' }
%p.form-text.text-muted.hint Enter the code from the two-factor app on your mobile device. If you've lost your device, you may enter one of your recovery codes.
.prepend-top-20
- = f.submit "Verify code", class: "btn btn-success"
+ = f.submit "Verify code", class: "btn btn-success", data: { qa_selector: 'verify_code_button' }
- if @user.two_factor_u2f_enabled?
= render "u2f/authenticate", params: params, resource: resource, resource_name: resource_name, render_remember_me: true, target_path: new_user_session_path
diff --git a/app/views/devise/shared/_experimental_separate_sign_up_flow_box.html.haml b/app/views/devise/shared/_experimental_separate_sign_up_flow_box.html.haml
index 7bc3042c94d..61271f4525c 100644
--- a/app/views/devise/shared/_experimental_separate_sign_up_flow_box.html.haml
+++ b/app/views/devise/shared/_experimental_separate_sign_up_flow_box.html.haml
@@ -1,5 +1,6 @@
- max_first_name_length = max_last_name_length = 127
- max_username_length = 255
+- min_username_length = 2
.signup-box.p-3.mb-2
.signup-body
= form_for(resource, as: "new_#{resource_name}", url: registration_path(resource_name), html: { class: "new_new_user gl-show-field-errors", "aria-live" => "assertive" }) do |f|
@@ -16,7 +17,7 @@
= f.text_field :last_name, class: "form-control top js-block-emoji js-validate-length", :data => { :max_length => max_last_name_length, :max_length_message => _("Last Name is too long (maximum is %{max_length} characters).") % { max_length: max_last_name_length }, :qa_selector => 'new_user_lastname_field' }, required: true, title: _("This field is required.")
.username.form-group
= f.label :username, class: 'label-bold'
- = f.text_field :username, class: "form-control middle js-block-emoji js-validate-length js-validate-username", :data => { :max_length => max_username_length, :max_length_message => _("Username is too long (maximum is %{max_length} characters).") % { max_length: max_username_length }, :qa_selector => 'new_user_username_field' }, pattern: Gitlab::PathRegex::NAMESPACE_FORMAT_REGEX_JS, required: true, title: _("Please create a username with only alphanumeric characters.")
+ = f.text_field :username, class: "form-control middle js-block-emoji js-validate-length js-validate-username", :data => { :min_length => min_username_length, :min_length_message => s_("SignUp|Username is too short (minimum is %{min_length} characters).") % { min_length: min_username_length }, :max_length => max_username_length, :max_length_message => _("Username is too long (maximum is %{max_length} characters).") % { max_length: max_username_length }, :qa_selector => 'new_user_username_field' }, pattern: Gitlab::PathRegex::NAMESPACE_FORMAT_REGEX_JS, required: true, title: _("Please create a username with only alphanumeric characters.")
%p.validation-error.gl-field-error-ignore.field-validation.mt-1.hide.cred= _('Username is already taken.')
%p.validation-success.gl-field-error-ignore.field-validation.mt-1.hide.cgreen= _('Username is available.')
%p.validation-pending.gl-field-error-ignore.field-validation.mt-1.hide= _('Checking username availability...')
diff --git a/app/views/devise/shared/_signup_box.html.haml b/app/views/devise/shared/_signup_box.html.haml
index 7c5b85c903c..0735702ae5f 100644
--- a/app/views/devise/shared/_signup_box.html.haml
+++ b/app/views/devise/shared/_signup_box.html.haml
@@ -1,5 +1,6 @@
- max_name_length = 255
- max_username_length = 255
+- min_username_length = 2
#register-pane.tab-pane.login-box{ role: 'tabpanel' }
.login-body
= form_for(resource, as: "new_#{resource_name}", url: registration_path(resource_name), html: { class: "new_new_user gl-show-field-errors", "aria-live" => "assertive" }) do |f|
@@ -12,7 +13,7 @@
= f.text_field :name, class: "form-control top js-block-emoji js-validate-length", :data => { :max_length => max_name_length, :max_length_message => s_("SignUp|Name is too long (maximum is %{max_length} characters).") % { max_length: max_name_length }, :qa_selector => 'new_user_name_field' }, required: true, title: _("This field is required.")
.username.form-group
= f.label :username, class: 'label-bold'
- = f.text_field :username, class: "form-control middle js-block-emoji js-validate-length js-validate-username", :data => { :max_length => max_username_length, :max_length_message => s_("SignUp|Username is too long (maximum is %{max_length} characters).") % { max_length: max_username_length }, :qa_selector => 'new_user_username_field' }, pattern: Gitlab::PathRegex::NAMESPACE_FORMAT_REGEX_JS, required: true, title: _("Please create a username with only alphanumeric characters.")
+ = f.text_field :username, class: "form-control middle js-block-emoji js-validate-length js-validate-username", :data => { :min_length => min_username_length, :min_length_message => s_("SignUp|Username is too short (minimum is %{min_length} characters).") % { min_length: min_username_length }, :max_length => max_username_length, :max_length_message => s_("SignUp|Username is too long (maximum is %{max_length} characters).") % { max_length: max_username_length }, :qa_selector => 'new_user_username_field' }, pattern: Gitlab::PathRegex::NAMESPACE_FORMAT_REGEX_JS, required: true, title: _("Please create a username with only alphanumeric characters.")
%p.validation-error.gl-field-error-ignore.field-validation.hide= _('Username is already taken.')
%p.validation-success.gl-field-error-ignore.field-validation.hide= _('Username is available.')
%p.validation-pending.gl-field-error-ignore.field-validation.hide= _('Checking username availability...')
diff --git a/app/views/discussions/_discussion.html.haml b/app/views/discussions/_discussion.html.haml
index 9659d416a38..4a27284cbae 100644
--- a/app/views/discussions/_discussion.html.haml
+++ b/app/views/discussions/_discussion.html.haml
@@ -37,7 +37,7 @@
an outdated change in
commit
- %span.commit-sha= Commit.truncate_sha(discussion.commit_id)
+ %span.commit-sha= truncate_sha(discussion.commit_id)
- else
- unless discussion.active?
an old version of
diff --git a/app/views/doorkeeper/applications/_form.html.haml b/app/views/doorkeeper/applications/_form.html.haml
index 79abe31a056..d74cba984e8 100644
--- a/app/views/doorkeeper/applications/_form.html.haml
+++ b/app/views/doorkeeper/applications/_form.html.haml
@@ -25,5 +25,5 @@
= f.label :scopes, class: 'label-bold'
= render 'shared/tokens/scopes_form', prefix: 'doorkeeper_application', token: application, scopes: @scopes
- .prepend-top-default
+ .gl-mt-3
= f.submit _('Save application'), class: "btn btn-success"
diff --git a/app/views/doorkeeper/applications/index.html.haml b/app/views/doorkeeper/applications/index.html.haml
index 9aab1556373..051799ca13f 100644
--- a/app/views/doorkeeper/applications/index.html.haml
+++ b/app/views/doorkeeper/applications/index.html.haml
@@ -1,7 +1,7 @@
- page_title _("Applications")
- @content_class = "limit-container-width" unless fluid_layout
-.row.prepend-top-default
+.row.gl-mt-3
.col-lg-4.profile-settings-sidebar
%h4.gl-mt-0
= page_title
@@ -41,7 +41,7 @@
%div= uri
%td= application.access_tokens.count
%td
- = link_to edit_oauth_application_path(application), class: "btn btn-transparent append-right-5" do
+ = link_to edit_oauth_application_path(application), class: "btn btn-transparent gl-mr-2" do
%span.sr-only
= _('Edit')
= icon('pencil')
@@ -49,7 +49,7 @@
- else
.settings-message.text-center
= _("You don't have any applications")
- .oauth-authorized-applications.prepend-top-20.append-bottom-default
+ .oauth-authorized-applications.prepend-top-20.gl-mb-3
- if user_oauth_applications?
%h5
= _("Authorized applications (%{size})") % { size: @authorized_apps.size + @authorized_anonymous_tokens.size }
diff --git a/app/views/doorkeeper/applications/show.html.haml b/app/views/doorkeeper/applications/show.html.haml
index 7b29269dbb1..280b5d90793 100644
--- a/app/views/doorkeeper/applications/show.html.haml
+++ b/app/views/doorkeeper/applications/show.html.haml
@@ -44,4 +44,4 @@
.form-actions
= link_to _('Edit'), edit_oauth_application_path(@application), class: 'btn btn-primary wide float-left'
- = render 'delete_form', application: @application, submit_btn_css: 'btn btn-danger prepend-left-10'
+ = render 'delete_form', application: @application, submit_btn_css: 'btn btn-danger gl-ml-3'
diff --git a/app/views/doorkeeper/authorizations/new.html.haml b/app/views/doorkeeper/authorizations/new.html.haml
index 5d57337a568..70abc1a267a 100644
--- a/app/views/doorkeeper/authorizations/new.html.haml
+++ b/app/views/doorkeeper/authorizations/new.html.haml
@@ -46,4 +46,4 @@
= hidden_field_tag :response_type, @pre_auth.response_type
= hidden_field_tag :scope, @pre_auth.scope
= hidden_field_tag :nonce, @pre_auth.nonce
- = submit_tag _("Authorize"), class: "btn btn-success prepend-left-10", data: { qa_selector: 'authorization_button' }
+ = submit_tag _("Authorize"), class: "btn btn-success gl-ml-3", data: { qa_selector: 'authorization_button' }
diff --git a/app/views/events/_event.html.haml b/app/views/events/_event.html.haml
index c042cd2c3e3..83f7d743755 100644
--- a/app/views/events/_event.html.haml
+++ b/app/views/events/_event.html.haml
@@ -7,6 +7,8 @@
- if event.wiki_page?
= render "events/event/wiki", event: event
+ - elsif event.design?
+ = render 'events/event/design', event: event
- elsif event.created_project_action?
= render "events/event/created_project", event: event
- elsif event.push_action?
diff --git a/app/views/events/event/_common.html.haml b/app/views/events/event/_common.html.haml
index 50c5885c648..dc16c46476e 100644
--- a/app/views/events/event/_common.html.haml
+++ b/app/views/events/event/_common.html.haml
@@ -5,16 +5,16 @@
.event-title.d-flex.flex-wrap
= inline_event_icon(event)
- if event.target
- %span.event-type.d-inline-block.append-right-4{ class: event.action_name }
+ %span.event-type.d-inline-block.gl-mr-2{ class: event.action_name }
= event.action_name
- %span.event-target-type.append-right-4= event.target_type.titleize.downcase
- = link_to event.target_link_options, class: 'has-tooltip event-target-link append-right-4', title: event.target_title do
+ %span.event-target-type.gl-mr-2= event.target_type.titleize.downcase
+ = link_to event.target_link_options, class: 'has-tooltip event-target-link gl-mr-2', title: event.target_title do
= event.target.reference_link_text
- unless event.milestone?
- %span.event-target-title.append-right-4{ dir: "auto" }
+ %span.event-target-title.gl-mr-2{ dir: "auto" }
= "&quot;".html_safe + event.target.title + "&quot".html_safe
- else
- %span.event-type.d-inline-block.append-right-4{ class: event.action_name }
+ %span.event-type.d-inline-block.gl-mr-2{ class: event.action_name }
= event_action_name(event)
= render "events/event_scope", event: event if event.resource_parent.present?
diff --git a/app/views/events/event/_created_project.html.haml b/app/views/events/event/_created_project.html.haml
index 606b0febb57..f0bb07d062c 100644
--- a/app/views/events/event/_created_project.html.haml
+++ b/app/views/events/event/_created_project.html.haml
@@ -4,7 +4,7 @@
.event-title.d-flex.flex-wrap
= inline_event_icon(event)
- %span.event-type.d-inline-block.append-right-4{ class: event.action_name }
+ %span.event-type.d-inline-block.gl-mr-2{ class: event.action_name }
= event_action_name(event)
- if event.project
diff --git a/app/views/events/event/_design.html.haml b/app/views/events/event/_design.html.haml
new file mode 100644
index 00000000000..c1fa1aaca50
--- /dev/null
+++ b/app/views/events/event/_design.html.haml
@@ -0,0 +1,11 @@
+= icon_for_profile_event(event)
+
+= event_user_info(event)
+
+.event-title.d-flex.flex-wrap
+ = inline_event_icon(event)
+ %span.event-type.d-inline-block.gl-mr-2{ class: event.action_name }
+ = event.action_name
+ = event_design_title_html(event)
+ = render "events/event_scope", event: event
+
diff --git a/app/views/events/event/_note.html.haml b/app/views/events/event/_note.html.haml
index 21e8b1401ca..a81b999acba 100644
--- a/app/views/events/event/_note.html.haml
+++ b/app/views/events/event/_note.html.haml
@@ -4,12 +4,12 @@
.event-title.d-flex.flex-wrap
= inline_event_icon(event)
- %span.event-type.d-inline-block.append-right-4{ class: event.action_name }
+ %span.event-type.d-inline-block.gl-mr-2{ class: event.action_name }
= event.action_name
= event_note_title_html(event)
- title = note_target_title(event.target)
- if title.present?
- %span.event-target-title.append-right-4{ dir: "auto" }
+ %span.event-target-title.gl-mr-2{ dir: "auto" }
= "&quot;".html_safe + title + "&quot".html_safe
= render "events/event_scope", event: event
diff --git a/app/views/events/event/_push.html.haml b/app/views/events/event/_push.html.haml
index b9e88f3fc47..4c1ee5fd3b7 100644
--- a/app/views/events/event/_push.html.haml
+++ b/app/views/events/event/_push.html.haml
@@ -7,9 +7,9 @@
.event-title.d-flex.flex-wrap
= inline_event_icon(event)
- many_refs = event.ref_count.to_i > 1
- %span.event-type.d-inline-block.append-right-4.pushed= many_refs ? "#{event.action_name} #{event.ref_count} #{event.ref_type.pluralize}" : "#{event.action_name} #{event.ref_type}"
+ %span.event-type.d-inline-block.gl-mr-2.pushed= many_refs ? "#{event.action_name} #{event.ref_count} #{event.ref_type.pluralize}" : "#{event.action_name} #{event.ref_type}"
- unless many_refs
- %span.append-right-4
+ %span.gl-mr-2
- commits_link = project_commits_path(project, event.ref_name)
- should_link = event.tag? ? project.repository.tag_exists?(event.ref_name) : project.repository.branch_exists?(event.ref_name)
= link_to_if should_link, event.ref_name, commits_link, class: 'ref-name'
diff --git a/app/views/events/event/_wiki.html.haml b/app/views/events/event/_wiki.html.haml
index 7ca98294521..cbd5ebcae12 100644
--- a/app/views/events/event/_wiki.html.haml
+++ b/app/views/events/event/_wiki.html.haml
@@ -4,7 +4,7 @@
.event-title.d-flex.flex-wrap
= inline_event_icon(event)
- %span.event-type.d-inline-block.append-right-4{ class: event.action_name }
+ %span.event-type.d-inline-block.gl-mr-2{ class: event.action_name }
= event.action_name
= event_wiki_title_html(event)
= render "events/event_scope", event: event
diff --git a/app/views/explore/snippets/index.html.haml b/app/views/explore/snippets/index.html.haml
index d23c8301b10..bf861e30b3a 100644
--- a/app/views/explore/snippets/index.html.haml
+++ b/app/views/explore/snippets/index.html.haml
@@ -1,6 +1,6 @@
- @hide_top_links = true
-- page_title "Snippets"
-- header_title "Snippets", snippets_path
+- page_title _("Snippets")
+- header_title _("Snippets"), snippets_path
- if current_user
= render 'dashboard/snippets_head'
diff --git a/app/views/groups/_flash_messages.html.haml b/app/views/groups/_flash_messages.html.haml
index ca951f28fcf..d1fea0e60c6 100644
--- a/app/views/groups/_flash_messages.html.haml
+++ b/app/views/groups/_flash_messages.html.haml
@@ -1,3 +1,3 @@
= content_for :flash_message do
= render_if_exists 'shared/shared_runners_minutes_limit', namespace: @group, classes: [container_class, ("limit-container-width" unless fluid_layout)]
- = render 'shared/namespace_storage_limit_alert', namespace: @group, classes: [container_class, ("limit-container-width" unless fluid_layout)]
+ = render_if_exists 'shared/namespace_storage_limit_alert', namespace: @group, classes: [container_class, ("limit-container-width" unless fluid_layout)]
diff --git a/app/views/groups/_home_panel.html.haml b/app/views/groups/_home_panel.html.haml
index 9bf7ad228d9..2cf94695482 100644
--- a/app/views/groups/_home_panel.html.haml
+++ b/app/views/groups/_home_panel.html.haml
@@ -5,11 +5,11 @@
.group-home-panel
.row.mb-3
.home-panel-title-row.col-md-12.col-lg-6.d-flex
- .avatar-container.rect-avatar.s64.home-panel-avatar.append-right-default.float-none
+ .avatar-container.rect-avatar.s64.home-panel-avatar.gl-mr-3.float-none
= group_icon(@group, class: 'avatar avatar-tile s64', width: 64, height: 64)
.d-flex.flex-column.flex-wrap.align-items-baseline
.d-inline-flex.align-items-baseline
- %h1.home-panel-title.gl-mt-3.append-bottom-5
+ %h1.home-panel-title.gl-mt-3.gl-mb-2
= @group.name
%span.visibility-icon.text-secondary.gl-ml-2.has-tooltip{ data: { container: 'body' }, title: visibility_icon_description(@group) }
= visibility_level_icon(@group.visibility_level, fw: false, options: {class: 'icon'})
@@ -27,7 +27,7 @@
- new_project_label = _("New project")
- new_subgroup_label = _("New subgroup")
- if can_create_projects and can_create_subgroups
- .btn-group.new-project-subgroup.droplab-dropdown.home-panel-action-button.prepend-top-default.js-new-project-subgroup.qa-new-project-or-subgroup-dropdown{ data: { project_path: new_project_path(namespace_id: @group.id), subgroup_path: new_group_path(parent_id: @group.id) } }
+ .btn-group.new-project-subgroup.droplab-dropdown.home-panel-action-button.gl-mt-3.js-new-project-subgroup.qa-new-project-or-subgroup-dropdown{ data: { project_path: new_project_path(namespace_id: @group.id), subgroup_path: new_group_path(parent_id: @group.id) } }
%input.btn.btn-success.dropdown-primary.js-new-group-child.qa-new-in-group-button{ type: "button", value: new_project_label, data: { action: "new-project" } }
%button.btn.btn-success.dropdown-toggle.js-dropdown-toggle.qa-new-project-or-subgroup-dropdown-toggle{ type: "button", data: { "dropdown-trigger" => "#new-project-or-subgroup-dropdown", 'display' => 'static' } }
= sprite_icon("chevron-down", css_class: "icon dropdown-btn-icon")
@@ -48,9 +48,9 @@
%strong= new_subgroup_label
%span= s_("GroupsTree|Create a subgroup in this group.")
- elsif can_create_projects
- = link_to new_project_label, new_project_path(namespace_id: @group.id), class: "btn btn-success prepend-top-default"
+ = link_to new_project_label, new_project_path(namespace_id: @group.id), class: "btn btn-success gl-mt-3"
- elsif can_create_subgroups
- = link_to new_subgroup_label, new_group_path(parent_id: @group.id), class: "btn btn-success prepend-top-default"
+ = link_to new_subgroup_label, new_group_path(parent_id: @group.id), class: "btn btn-success gl-mt-3"
- if @group.description.present?
.group-home-desc.mt-1
diff --git a/app/views/groups/activity.html.haml b/app/views/groups/activity.html.haml
index cb7dab26332..bc75fada937 100644
--- a/app/views/groups/activity.html.haml
+++ b/app/views/groups/activity.html.haml
@@ -1,7 +1,7 @@
= content_for :meta_tags do
= auto_discovery_link_tag(:atom, group_url(@group, rss_url_options), title: "#{@group.name} activity")
-- page_title "Activity"
+- page_title _("Activity")
%section.activities
= render 'activities'
diff --git a/app/views/groups/edit.html.haml b/app/views/groups/edit.html.haml
index 2e58517fdc7..1e04b2761f6 100644
--- a/app/views/groups/edit.html.haml
+++ b/app/views/groups/edit.html.haml
@@ -1,4 +1,5 @@
- breadcrumb_title _("General Settings")
+- page_title _("General Settings")
- @content_class = "limit-container-width" unless fluid_layout
- expanded = expanded_by_default?
diff --git a/app/views/groups/group_members/index.html.haml b/app/views/groups/group_members/index.html.haml
index 1f2fb747c7d..b9ea8316bbc 100644
--- a/app/views/groups/group_members/index.html.haml
+++ b/app/views/groups/group_members/index.html.haml
@@ -4,7 +4,8 @@
- pending_active = params[:search_invited].present?
- total_count = @members.count + @group.shared_with_group_links.count
-.project-members-page.prepend-top-default
+.js-remove-member-modal
+.project-members-page.gl-mt-3
%h4
= _("Group members")
%hr
diff --git a/app/views/groups/issues.html.haml b/app/views/groups/issues.html.haml
index 1cb1cc45bdb..59432e5f015 100644
--- a/app/views/groups/issues.html.haml
+++ b/app/views/groups/issues.html.haml
@@ -1,6 +1,6 @@
- @can_bulk_update = can?(current_user, :admin_issue, @group) && @group.feature_available?(:group_bulk_edit)
-- page_title "Issues"
+- page_title _("Issues")
= content_for :meta_tags do
= auto_discovery_link_tag(:atom, safe_params.merge(rss_url_options).to_h, title: "#{@group.name} issues")
diff --git a/app/views/groups/labels/edit.html.haml b/app/views/groups/labels/edit.html.haml
index 586b0f6ebfa..fbab4f8a250 100644
--- a/app/views/groups/labels/edit.html.haml
+++ b/app/views/groups/labels/edit.html.haml
@@ -1,6 +1,6 @@
- add_to_breadcrumbs _("Labels"), group_labels_path(@group)
- breadcrumb_title _("Edit")
-- page_title "Edit", @label.name, _("Labels")
+- page_title _("Edit"), @label.name, _("Labels")
%h3.page-title
Edit Label
diff --git a/app/views/groups/labels/index.html.haml b/app/views/groups/labels/index.html.haml
index 41c1d3e84b7..3299d127222 100644
--- a/app/views/groups/labels/index.html.haml
+++ b/app/views/groups/labels/index.html.haml
@@ -1,4 +1,4 @@
-- page_title 'Labels'
+- page_title _('Labels')
- can_admin_label = can?(current_user, :admin_label, @group)
- search = params[:search]
- subscribed = params[:subscribed]
@@ -8,7 +8,7 @@
#promote-label-modal
= render 'shared/labels/nav', labels_or_filters: labels_or_filters, can_admin_label: can_admin_label
- .labels-container.prepend-top-5
+ .labels-container.gl-mt-2
- if @labels.any?
.text-muted
= _('Labels can be applied to %{features}. Group labels are available for any project within the group.') % { features: issuable_types.to_sentence }
@@ -27,5 +27,5 @@
= render 'shared/empty_states/labels'
%template#js-badge-item-template
- %li.label-link-item.js-priority-badge.inline.prepend-left-10
+ %li.label-link-item.js-priority-badge.inline.gl-ml-3
.label-badge.label-badge-blue= _('Prioritized label')
diff --git a/app/views/groups/merge_requests.html.haml b/app/views/groups/merge_requests.html.haml
index 0780fab513b..1828f850d35 100644
--- a/app/views/groups/merge_requests.html.haml
+++ b/app/views/groups/merge_requests.html.haml
@@ -1,6 +1,6 @@
- @can_bulk_update = can?(current_user, :admin_merge_request, @group) && @group.feature_available?(:group_bulk_edit)
-- page_title "Merge Requests"
+- page_title _("Merge Requests")
- if group_merge_requests_count(state: 'all').zero?
= render 'shared/empty_states/merge_requests', project_select_button: true
diff --git a/app/views/groups/milestones/_form.html.haml b/app/views/groups/milestones/_form.html.haml
index 7a35bc12eee..df82b264f9a 100644
--- a/app/views/groups/milestones/_form.html.haml
+++ b/app/views/groups/milestones/_form.html.haml
@@ -6,20 +6,20 @@
.col-form-label.col-sm-2
= f.label :title, "Title"
.col-sm-10
- = f.text_field :title, maxlength: 255, class: "form-control", required: true, autofocus: true
+ = f.text_field :title, maxlength: 255, class: "form-control", data: { qa_selector: "milestone_title_field" }, required: true, autofocus: true
.form-group.row.milestone-description
.col-form-label.col-sm-2
= f.label :description, "Description"
.col-sm-10
= render layout: 'shared/md_preview', locals: { url: group_preview_markdown_path } do
- = render 'shared/zen', f: f, attr: :description, classes: 'note-textarea', placeholder: 'Write milestone description...', supports_autocomplete: false
+ = render 'shared/zen', f: f, attr: :description, classes: 'note-textarea', qa_selector: 'milestone_description_field', placeholder: 'Write milestone description...', supports_autocomplete: false
.clearfix
.error-alert
= render "shared/milestones/form_dates", f: f
.form-actions
- if @milestone.new_record?
- = f.submit 'Create milestone', class: "btn-success btn"
+ = f.submit 'Create milestone', class: "btn-success btn", data: { qa_selector: "create_milestone_button" }
= link_to "Cancel", group_milestones_path(@group), class: "btn btn-cancel"
- else
= f.submit 'Update milestone', class: "btn-success btn"
diff --git a/app/views/groups/milestones/index.html.haml b/app/views/groups/milestones/index.html.haml
index 03407adb57d..1685707d457 100644
--- a/app/views/groups/milestones/index.html.haml
+++ b/app/views/groups/milestones/index.html.haml
@@ -1,4 +1,4 @@
-- page_title "Milestones"
+- page_title _("Milestones")
.top-area
= render 'shared/milestones_filter', counts: @milestone_states
@@ -7,7 +7,7 @@
= render 'shared/milestones/search_form'
= render 'shared/milestones_sort_dropdown'
- if can?(current_user, :admin_milestone, @group)
- = link_to "New milestone", new_group_milestone_path(@group), class: "btn btn-success"
+ = link_to "New milestone", new_group_milestone_path(@group), class: "btn btn-success", data: { qa_selector: "new_group_milestone_link" }
.milestones
%ul.content-list
diff --git a/app/views/groups/new.html.haml b/app/views/groups/new.html.haml
index ed016206310..a231702012c 100644
--- a/app/views/groups/new.html.haml
+++ b/app/views/groups/new.html.haml
@@ -4,7 +4,7 @@
- header_title _("Groups"), dashboard_groups_path
- active_tab = local_assigns.fetch(:active_tab, 'create')
-.group-edit-container.prepend-top-default
+.group-edit-container.gl-mt-3
.row
.col-lg-3.group-settings-sidebar
%h4.prepend-top-0
diff --git a/app/views/groups/projects.html.haml b/app/views/groups/projects.html.haml
index 8b01e54474a..bf9d89da24a 100644
--- a/app/views/groups/projects.html.haml
+++ b/app/views/groups/projects.html.haml
@@ -1,6 +1,7 @@
-- breadcrumb_title "Projects"
+- breadcrumb_title _("Projects")
+- page_title _("Projects")
-.card.prepend-top-default
+.card.gl-mt-3
.card-header
%strong= @group.name
projects:
diff --git a/app/views/groups/runners/_group_runners.html.haml b/app/views/groups/runners/_group_runners.html.haml
index f752bc0a702..554240b7aef 100644
--- a/app/views/groups/runners/_group_runners.html.haml
+++ b/app/views/groups/runners/_group_runners.html.haml
@@ -18,13 +18,3 @@
locals: { registration_token: @group.runners_token,
type: 'group',
reset_token_url: reset_registration_token_group_settings_ci_cd_path }
-
-- if @group.runners.empty?
- %h4.underlined-title
- = _('This group does not provide any group Runners yet.')
-
-- else
- %h4.underlined-title
- = _('Available group Runners: %{runners}').html_safe % { runners: @group.runners.count }
- %ul.bordered-list
- = render partial: 'groups/runners/runner', collection: @group.runners, as: :runner
diff --git a/app/views/groups/runners/_index.html.haml b/app/views/groups/runners/_index.html.haml
index 0cf9011b471..51375f50659 100644
--- a/app/views/groups/runners/_index.html.haml
+++ b/app/views/groups/runners/_index.html.haml
@@ -7,3 +7,97 @@
.row
.col-sm-6
= render 'groups/runners/group_runners'
+
+%h4.underlined-title
+ = _('Available Runners: %{runners}').html_safe % { runners: limited_counter_with_delimiter(@all_group_runners) }
+
+-# haml-lint:disable NoPlainNodes
+.row
+ .col-sm-9
+ = form_tag group_settings_ci_cd_path, id: 'runners-search', method: :get, class: 'filter-form js-filter-form' do
+ .filtered-search-wrapper.d-flex
+ .filtered-search-box
+ = dropdown_tag(_('Recent searches'),
+ options: { wrapper_class: 'filtered-search-history-dropdown-wrapper',
+ toggle_class: 'btn filtered-search-history-dropdown-toggle-button',
+ dropdown_class: 'filtered-search-history-dropdown',
+ content_class: 'filtered-search-history-dropdown-content' }) do
+ .js-filtered-search-history-dropdown{ data: { full_path: group_settings_ci_cd_path } }
+ .filtered-search-box-input-container.droplab-dropdown
+ .scroll-container
+ %ul.tokens-container.list-unstyled
+ %li.input-token
+ %input.form-control.filtered-search{ search_filter_input_options('runners') }
+ #js-dropdown-hint.filtered-search-input-dropdown-menu.dropdown-menu.hint-dropdown
+ %ul.filter-dropdown{ data: { dynamic: true, dropdown: true } }
+ %li.filter-dropdown-item{ data: {hint: "#{'{{hint}}'}", tag: "#{'{{tag}}'}", action: "#{'{{hint === \'search\' ? \'submit\' : \'\' }}'}" } }
+ = button_tag class: 'btn btn-link' do
+ -# Encapsulate static class name `{{icon}}` inside #{} to bypass
+ -# haml lint's ClassAttributeWithStaticValue
+ %svg
+ %use{ 'xlink:href': "#{'{{icon}}'}" }
+ %span.js-filter-hint
+ {{formattedKey}}
+ #js-dropdown-operator.filtered-search-input-dropdown-menu.dropdown-menu
+ %ul.filter-dropdown{ data: { dropdown: true, dynamic: true } }
+ %li.filter-dropdown-item{ data: { value: "{{ title }}" } }
+ = button_tag class: 'btn btn-link' do
+ {{ title }}
+ %span.btn-helptext
+ {{ help }}
+ #js-dropdown-admin-runner-status.filtered-search-input-dropdown-menu.dropdown-menu
+ %ul{ data: { dropdown: true } }
+ - Ci::Runner::AVAILABLE_STATUSES.each do |status|
+ %li.filter-dropdown-item{ data: { value: status } }
+ = button_tag class: 'btn btn-link' do
+ = status.titleize
+
+ #js-dropdown-admin-runner-type.filtered-search-input-dropdown-menu.dropdown-menu
+ %ul{ data: { dropdown: true } }
+ - Ci::Runner::AVAILABLE_TYPES.each do |runner_type|
+ - next if runner_type == 'instance_type'
+ %li.filter-dropdown-item{ data: { value: runner_type } }
+ = button_tag class: 'btn btn-link' do
+ = runner_type.titleize
+
+ #js-dropdown-runner-tag.filtered-search-input-dropdown-menu.dropdown-menu
+ %ul{ data: { dropdown: true } }
+ %li.filter-dropdown-item{ data: { value: 'none' } }
+ = button_tag class: 'btn btn-link' do
+ = _('No Tag')
+ %li.divider.droplab-item-ignore
+ %ul.filter-dropdown{ data: { dynamic: true, dropdown: true } }
+ %li.filter-dropdown-item
+ = button_tag class: 'btn btn-link js-data-value' do
+ %span.dropdown-light-content
+ {{name}}
+
+ = button_tag class: 'clear-search hidden' do
+ = icon('times')
+ .filter-dropdown-container
+ = render 'admin/runners/sort_dropdown'
+
+ .col-sm-3.text-right-lg
+ = _('Runners currently online: %{active_runners_count}') % { active_runners_count: limited_counter_with_delimiter(@all_group_runners.online) }
+
+
+- if @group_runners.any?
+ .runners-content.content-list
+ .table-holder
+ .gl-responsive-table-row.table-row-header{ role: 'row' }
+ .table-section.section-10{ role: 'rowheader' }= _('Type/State')
+ .table-section.section-10{ role: 'rowheader' }= _('Runner token')
+ .table-section.section-20{ role: 'rowheader' }= _('Description')
+ .table-section.section-10{ role: 'rowheader' }= _('Version')
+ .table-section.section-10{ role: 'rowheader' }= _('IP Address')
+ .table-section.section-5{ role: 'rowheader' }= _('Projects')
+ .table-section.section-5{ role: 'rowheader' }= _('Jobs')
+ .table-section.section-10{ role: 'rowheader' }= _('Tags')
+ .table-section.section-10{ role: 'rowheader' }= _('Last contact')
+ .table-section.section-10{ role: 'rowheader' }
+
+ - @group_runners.each do |runner|
+ = render 'groups/runners/runner', runner: runner
+ = paginate @group_runners, theme: 'gitlab', :params => { :anchor => 'runners-settings' }
+- else
+ .nothing-here-block= _('No runners found')
diff --git a/app/views/groups/runners/_runner.html.haml b/app/views/groups/runners/_runner.html.haml
index 3f89b04a5fc..df615eb189a 100644
--- a/app/views/groups/runners/_runner.html.haml
+++ b/app/views/groups/runners/_runner.html.haml
@@ -1,27 +1,86 @@
-%li.runner{ id: dom_id(runner) }
- %h4
- = runner_status_icon(runner)
+.gl-responsive-table-row{ id: dom_id(runner) }
+ .table-section.section-10.section-wrap
+ .table-mobile-header{ role: 'rowheader' }= _('Type')
+ .table-mobile-content
+ - if runner.group_type?
+ %span.badge.badge-success
+ = _('group')
+ - else
+ %span.badge.badge-info
+ = _('specific')
+ - if runner.locked?
+ %span.badge.badge-warning
+ = _('locked')
+ - unless runner.active?
+ %span.badge.badge-danger
+ = _('paused')
+
+ .table-section.section-10
+ .table-mobile-header{ role: 'rowheader' }= _('Runner token')
+ .table-mobile-content
+ = link_to runner.short_sha, group_runner_path(@group, runner)
+
+ .table-section.section-20
+ .table-mobile-header{ role: 'rowheader' }= _('Description')
+ .table-mobile-content.str-truncated.has-tooltip{ title: runner.description }
+ = runner.description
- = link_to runner.short_sha, group_runner_path(@group, runner), class: 'commit-sha'
+ .table-section.section-10
+ .table-mobile-header{ role: 'rowheader' }= _('Version')
+ .table-mobile-content.str-truncated.has-tooltip{ title: runner.version }
+ = runner.version
- %small.edit-runner
- = link_to edit_group_runner_path(@group, runner) do
- = icon('edit')
+ .table-section.section-10
+ .table-mobile-header{ role: 'rowheader' }= _('IP Address')
+ .table-mobile-content.str-truncated.has-tooltip{ title: runner.ip_address }
+ = runner.ip_address
- .float-right
- - if runner.active?
- = link_to _('Pause'), pause_group_runner_path(@group, runner), method: :post, class: 'btn btn-sm btn-danger', data: { confirm: _("Are you sure?") }
+ .table-section.section-5
+ .table-mobile-header{ role: 'rowheader' }= _('Projects')
+ .table-mobile-content
+ - if runner.group_type?
+ = _('n/a')
- else
- = link_to _('Resume'), resume_group_runner_path(@group, runner), method: :post, class: 'btn btn-success btn-sm'
- = link_to _('Remove Runner'), group_runner_path(@group, runner), data: { confirm: _("Are you sure?") }, method: :delete, class: 'btn btn-danger btn-sm'
- .float-right
- %small.light
- \##{runner.id}
- - if runner.description.present?
- %p.runner-description
- = runner.description
- - if runner.tag_list.present?
- %p
- - runner.tag_list.sort.each do |tag|
- %span.label.label-primary
+ = runner.projects.count(:all)
+
+ .table-section.section-5
+ .table-mobile-header{ role: 'rowheader' }= _('Jobs')
+ .table-mobile-content
+ = limited_counter_with_delimiter(runner.builds)
+
+ .table-section.section-10.section-wrap
+ .table-mobile-header{ role: 'rowheader' }= _('Tags')
+ .table-mobile-content
+ - runner.tags.map(&:name).sort.each do |tag|
+ %span.badge.badge-primary.str-truncated.has-tooltip{ title: tag }
= tag
+
+ .table-section.section-10
+ .table-mobile-header{ role: 'rowheader' }= _('Last contact')
+ .table-mobile-content
+ - contacted_at = runner_contacted_at(runner)
+ - if contacted_at
+ = time_ago_with_tooltip contacted_at
+ - else
+ = _('Never')
+
+ .table-section.table-button-footer.section-10
+ .btn-group.table-action-buttons
+ .btn-group
+ = link_to edit_group_runner_path(@group, runner), class: 'btn btn-default has-tooltip', title: _('Edit'), ref: 'tooltip', aria: { label: _('Edit') }, data: { placement: 'top', container: 'body'} do
+ = icon('pencil')
+ .btn-group
+ - if runner.active?
+ = link_to pause_group_runner_path(@group, runner), method: :post, class: 'btn btn-default has-tooltip', title: _('Pause'), ref: 'tooltip', aria: { label: _('Pause') }, data: { placement: 'top', container: 'body', confirm: _('Are you sure?') } do
+ = icon('pause')
+ - else
+ = link_to resume_group_runner_path(@group, runner), method: :post, class: 'btn btn-default has-tooltip', title: _('Resume'), ref: 'tooltip', aria: { label: _('Resume') }, data: { placement: 'top', container: 'body'} do
+ = icon('play')
+ - if runner.belongs_to_more_than_one_project?
+ .btn-group
+ .btn.btn-danger.has-tooltip{ 'aria-label' => 'Remove', 'data-container' => 'body', 'data-original-title' => _('Multi-project Runners cannot be removed'), 'data-placement' => 'top', disabled: 'disabled' }
+ = icon('remove')
+ - else
+ .btn-group
+ = link_to group_runner_path(@group, runner), method: :delete, class: 'btn btn-danger has-tooltip', title: _('Remove'), ref: 'tooltip', aria: { label: _('Remove') }, data: { placement: 'top', container: 'body', confirm: _('Are you sure?') } do
+ = icon('remove')
diff --git a/app/views/groups/settings/_general.html.haml b/app/views/groups/settings/_general.html.haml
index 742bf50fb89..0094104e07d 100644
--- a/app/views/groups/settings/_general.html.haml
+++ b/app/views/groups/settings/_general.html.haml
@@ -19,7 +19,7 @@
= render_if_exists 'shared/repository_size_limit_setting', form: f, type: :group
- .form-group.prepend-top-default.append-bottom-20
+ .form-group.gl-mt-3.append-bottom-20
.avatar-container.rect-avatar.s90
= group_icon(@group, alt: '', class: 'avatar group-avatar s90')
= f.label :avatar, _('Group avatar'), class: 'label-bold d-block'
diff --git a/app/views/groups/settings/_lfs.html.haml b/app/views/groups/settings/_lfs.html.haml
index 7970c3c73f6..77c84862316 100644
--- a/app/views/groups/settings/_lfs.html.haml
+++ b/app/views/groups/settings/_lfs.html.haml
@@ -5,7 +5,7 @@
%p= s_('Check the %{docs_link_start}documentation%{docs_link_end}.').html_safe % { docs_link_start: docs_link_start, docs_link_end: '</a>'.html_safe }
-.form-group.append-bottom-default
+.form-group.gl-mb-3
.form-check
= f.check_box :lfs_enabled, checked: @group.lfs_enabled?, class: 'form-check-input', data: { qa_selector: 'lfs_checkbox' }
= f.label :lfs_enabled, class: 'form-check-label' do
diff --git a/app/views/groups/settings/_permissions.html.haml b/app/views/groups/settings/_permissions.html.haml
index e886c99a656..507246d573e 100644
--- a/app/views/groups/settings/_permissions.html.haml
+++ b/app/views/groups/settings/_permissions.html.haml
@@ -7,7 +7,7 @@
.form-group
= render 'shared/allow_request_access', form: f
- .form-group.append-bottom-default
+ .form-group.gl-mb-3
.form-check
= f.check_box :share_with_group_lock, disabled: !can_change_share_with_group_lock?(@group), class: 'form-check-input'
= f.label :share_with_group_lock, class: 'form-check-label' do
@@ -16,20 +16,21 @@
= s_('GroupSettings|Prevent sharing a project within %{group} with other groups').html_safe % { group: group_link }
%span.js-descr.text-muted= share_with_group_lock_help_text(@group)
- .form-group.append-bottom-default
+ .form-group.gl-mb-3
.form-check
= f.check_box :emails_disabled, checked: @group.emails_disabled?, disabled: !can_disable_group_emails?(@group), class: 'form-check-input'
= f.label :emails_disabled, class: 'form-check-label' do
%span.d-block= s_('GroupSettings|Disable email notifications')
%span.text-muted= s_('GroupSettings|This setting will override user notification preferences for all members of the group, subgroups, and projects.')
- .form-group.append-bottom-default
+ .form-group.gl-mb-3
.form-check
= f.check_box :mentions_disabled, checked: @group.mentions_disabled?, class: 'form-check-input'
= f.label :mentions_disabled, class: 'form-check-label' do
%span.d-block= s_('GroupSettings|Disable group mentions')
%span.text-muted= s_('GroupSettings|This setting will prevent group members from being notified if the group is mentioned.')
+ = render_if_exists 'groups/settings/delayed_project_removal', f: f, group: @group
= render_if_exists 'groups/settings/ip_restriction', f: f, group: @group
= render_if_exists 'groups/settings/allowed_email_domain', f: f, group: @group
= render 'groups/settings/lfs', f: f
@@ -40,4 +41,4 @@
= render_if_exists 'groups/personal_access_token_expiration_policy', f: f, group: @group
= render_if_exists 'groups/member_lock_setting', f: f, group: @group
- = f.submit _('Save changes'), class: 'btn btn-success prepend-top-default js-dirty-submit', data: { qa_selector: 'save_permissions_changes_button' }
+ = f.submit _('Save changes'), class: 'btn btn-success gl-mt-3 js-dirty-submit', data: { qa_selector: 'save_permissions_changes_button' }
diff --git a/app/views/groups/settings/ci_cd/_form.html.haml b/app/views/groups/settings/ci_cd/_form.html.haml
index 54e88d11827..139c710fac0 100644
--- a/app/views/groups/settings/ci_cd/_form.html.haml
+++ b/app/views/groups/settings/ci_cd/_form.html.haml
@@ -1,4 +1,4 @@
-.row.prepend-top-default
+.row.gl-mt-3
.col-lg-12
= form_for group, url: group_settings_ci_cd_path(group, anchor: 'js-general-pipeline-settings') do |f|
= form_errors(group)
diff --git a/app/views/groups/settings/ci_cd/show.html.haml b/app/views/groups/settings/ci_cd/show.html.haml
index 8c9b859e127..366d7dd5afe 100644
--- a/app/views/groups/settings/ci_cd/show.html.haml
+++ b/app/views/groups/settings/ci_cd/show.html.haml
@@ -1,5 +1,5 @@
-- breadcrumb_title "CI / CD Settings"
-- page_title "CI / CD"
+- breadcrumb_title _("CI / CD Settings")
+- page_title _("CI / CD")
- expanded = expanded_by_default?
- general_expanded = @group.errors.empty? ? expanded : true
diff --git a/app/views/groups/show.html.haml b/app/views/groups/show.html.haml
index 7e5bf6ddde1..6ad864121d7 100644
--- a/app/views/groups/show.html.haml
+++ b/app/views/groups/show.html.haml
@@ -1,4 +1,5 @@
- breadcrumb_title _("Details")
+- page_title _("Groups")
- @content_class = "limit-container-width" unless fluid_layout
= content_for :meta_tags do
@@ -18,8 +19,8 @@
.groups-listing{ data: { endpoints: { default: group_children_path(@group, format: :json), shared: group_shared_projects_path(@group, format: :json) } } }
.top-area.group-nav-container.justify-content-between
.scrolling-tabs-container.inner-page-scroll-tabs
- .fade-left= icon('angle-left')
- .fade-right= icon('angle-right')
+ .fade-left= sprite_icon('chevron-lg-left', size: 12)
+ .fade-right= sprite_icon('chevron-lg-right', size: 12)
%ul.nav-links.scrolling-tabs.mobile-separator.nav.nav-tabs
%li.js-subgroups_and_projects-tab
= link_to group_path, data: { target: 'div#subgroups_and_projects', action: 'subgroups_and_projects', toggle: 'tab'} do
diff --git a/app/views/help/_shortcuts.html.haml b/app/views/help/_shortcuts.html.haml
index bd5424c30c6..80df8581a9b 100644
--- a/app/views/help/_shortcuts.html.haml
+++ b/app/views/help/_shortcuts.html.haml
@@ -2,10 +2,6 @@
.modal-dialog.modal-lg.modal-1040
.modal-content
.modal-header
- %h4.modal-title
- = _('Keyboard Shortcuts')
- %small
- = link_to _('(Show all)'), '#', class: 'js-more-help-button'
.js-toggle-shortcuts
%button.close{ type: "button", "data-dismiss": "modal", "aria-label" => _('Close') }
%span{ "aria-hidden": true } &times;
@@ -313,6 +309,10 @@
%td.shortcut
%kbd p
%td= _('Previous unresolved discussion')
+ %tr
+ %td.shortcut
+ %kbd b
+ %td= _('Copy source branch name')
%tbody
%tr
%th
diff --git a/app/views/help/index.html.haml b/app/views/help/index.html.haml
index ed904c48ddb..03f8539293b 100644
--- a/app/views/help/index.html.haml
+++ b/app/views/help/index.html.haml
@@ -1,6 +1,6 @@
%div
- if Gitlab::CurrentSettings.help_page_text.present?
- .prepend-top-default.md
+ .gl-mt-3.md
= markdown_field(Gitlab::CurrentSettings.current_application_settings, :help_page_text)
%hr
@@ -28,7 +28,7 @@
%p= link_to 'Check the current instance configuration ', help_instance_configuration_url
%hr
-.row.prepend-top-default
+.row.gl-mt-3
.col-md-8
.documentation-index.md
= markdown(@help_index)
diff --git a/app/views/help/instance_configuration.html.haml b/app/views/help/instance_configuration.html.haml
index 99576d45f76..260566b1441 100644
--- a/app/views/help/instance_configuration.html.haml
+++ b/app/views/help/instance_configuration.html.haml
@@ -1,4 +1,4 @@
-- page_title 'Instance Configuration'
+- page_title _('Instance Configuration')
.documentation.md
%h1 Instance Configuration
diff --git a/app/views/help/show.html.haml b/app/views/help/show.html.haml
index dace8a77736..c41f6ea3ed4 100644
--- a/app/views/help/show.html.haml
+++ b/app/views/help/show.html.haml
@@ -1,5 +1,5 @@
- page_title @path.split("/").reverse.map(&:humanize)
- @content_class = "limit-container-width" unless fluid_layout
-.documentation.md.prepend-top-default
+.documentation.md.gl-mt-3
= markdown @markdown
diff --git a/app/views/help/ui.html.haml b/app/views/help/ui.html.haml
index d71650ae50c..5c216ee1ec0 100644
--- a/app/views/help/ui.html.haml
+++ b/app/views/help/ui.html.haml
@@ -1,4 +1,4 @@
-- page_title "UI Development Kit", "Help"
+- page_title _("UI Development Kit"), _("Help")
- lorem = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed fermentum nisi sapien, non consequat lectus aliquam ultrices. Suspendisse sodales est euismod nunc condimentum, a consectetur diam ornare."
- link_classes = "flex-grow-1 mx-1 "
diff --git a/app/views/ide/_show.html.haml b/app/views/ide/_show.html.haml
index b871f0363f3..d0384fd50bc 100644
--- a/app/views/ide/_show.html.haml
+++ b/app/views/ide/_show.html.haml
@@ -1,5 +1,5 @@
- @body_class = 'ide-layout'
-- page_title 'IDE'
+- page_title _('IDE')
- content_for :page_specific_javascripts do
= stylesheet_link_tag 'page_bundles/ide'
diff --git a/app/views/import/bitbucket/status.html.haml b/app/views/import/bitbucket/status.html.haml
index d405acef75c..9b54cbe577a 100644
--- a/app/views/import/bitbucket/status.html.haml
+++ b/app/views/import/bitbucket/status.html.haml
@@ -5,93 +5,4 @@
%i.fa.fa-bitbucket
= _('Import projects from Bitbucket')
-- if Feature.enabled?(:new_import_ui)
- = render 'import/githubish_status', provider: 'bitbucket'
-- else
- - if @repos.any?
- %p.light
- = _('Select projects you want to import.')
- %p
- - if @incompatible_repos.any?
- = button_tag class: 'btn btn-import btn-success js-import-all' do
- = _('Import all compatible projects')
- = icon('spinner spin', class: 'loading-icon')
- - else
- = button_tag class: 'btn btn-import btn-success js-import-all' do
- = _('Import all projects')
- = icon('spinner spin', class: 'loading-icon')
-
- .position-relative.ms-no-clear.d-flex.flex-fill.float-right.append-bottom-10
- = form_tag status_import_bitbucket_path, method: 'get' do
- = text_field_tag :filter, @filter, class: 'form-control pr-5', placeholder: _('Filter projects'), size: 40, autofocus: true, 'aria-label': _('Search')
- .position-absolute.position-top-0.d-flex.align-items-center.text-muted.position-right-0.h-100
- .border-left
- %button{ class: 'btn btn-transparent btn-secondary', 'aria-label': _('Search Button'), type: 'submit' }
- %i{ class: 'fa fa-search', 'aria-hidden': true }
-
- .table-responsive
- %table.table.import-jobs
- %colgroup.import-jobs-from-col
- %colgroup.import-jobs-to-col
- %colgroup.import-jobs-status-col
- %thead
- %tr
- %th= _('From Bitbucket')
- %th= _('To GitLab')
- %th= _('Status')
- %tbody
- - @already_added_projects.each do |project|
- %tr{ id: "project_#{project.id}", class: "#{project_status_css_class(project.import_status)}" }
- %td
- = link_to project.import_source, "https://bitbucket.org/#{project.import_source}", target: '_blank', rel: 'noopener noreferrer'
- %td
- = link_to project.full_path, [project.namespace.becomes(Namespace), project]
- %td.job-status
- - case project.import_status
- - when 'finished'
- %span
- %i.fa.fa-check
- = _('done')
- - when 'started'
- %i.fa.fa-spinner.fa-spin
- = _('started')
- - else
- = project.human_import_status_name
-
- - @repos.each do |repo|
- %tr{ id: "repo_#{repo.owner}___#{repo.slug}" }
- %td
- = link_to repo.full_name, "https://bitbucket.org/#{repo.full_name}", target: '_blank', rel: 'noopener noreferrer'
- %td.import-target
- %fieldset.row
- .input-group
- .project-path.input-group-prepend
- - if current_user.can_select_namespace?
- - selected = params[:namespace_id] || :current_user
- - opts = current_user.can_create_group? ? { extra_group: Group.new(name: repo.owner, path: repo.owner) } : {}
- = select_tag :namespace_id, namespaces_options(selected, opts.merge({ display_path: true })), { class: 'select2 js-select-namespace', tabindex: 1 }
- - else
- = text_field_tag :path, current_user.namespace_path, class: "input-group-text input-large form-control", tabindex: 1, disabled: true
- %span.input-group-prepend
- .input-group-text /
- = text_field_tag :path, sanitize_project_name(repo.slug), class: "input-mini form-control", tabindex: 2, autofocus: true, required: true
- %td.import-actions.job-status
- = button_tag class: 'btn btn-import js-add-to-import' do
- = _('Import')
- = icon('spinner spin', class: 'loading-icon')
- - @incompatible_repos.each do |repo|
- %tr{ id: "repo_#{repo.owner}___#{repo.slug}" }
- %td
- = link_to repo.full_name, "https://bitbucket.org/#{repo.full_name}", target: '_blank', rel: 'noopener noreferrer'
- %td.import-target
- %td.import-actions-job-status
- = label_tag _('Incompatible Project'), nil, class: 'label badge-danger'
-
- - if @incompatible_repos.any?
- %p
- = _("One or more of your Bitbucket projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git.")
- - link_to_git = link_to(_('Git'), 'https://www.atlassian.com/git/tutorials/migrating-overview')
- - link_to_import_flow = link_to(_('import flow'), status_import_bitbucket_path)
- = _("Please convert them to %{link_to_git}, and go through the %{link_to_import_flow} again.").html_safe % { link_to_git: link_to_git, link_to_import_flow: link_to_import_flow }
-
- .js-importer-status{ data: { jobs_import_path: "#{jobs_import_bitbucket_path}", import_path: "#{import_bitbucket_path}" } }
+= render 'import/githubish_status', provider: 'bitbucket'
diff --git a/app/views/import/bitbucket_server/new.html.haml b/app/views/import/bitbucket_server/new.html.haml
index 2eac8d0c5a1..735535ffc36 100644
--- a/app/views/import/bitbucket_server/new.html.haml
+++ b/app/views/import/bitbucket_server/new.html.haml
@@ -1,7 +1,7 @@
- title = _('Bitbucket Server Import')
- page_title title
- breadcrumb_title title
-- header_title "Projects", root_path
+- header_title _("Projects"), root_path
%h3.page-title
= icon 'bitbucket-square', text: _('Import repositories from Bitbucket Server')
@@ -17,7 +17,7 @@
.form-group.row
= label_tag :bitbucket_server_url, 'Username', class: 'col-form-label col-md-2'
.col-md-4
- = text_field_tag :bitbucket_username, '', class: 'form-control gl-mr-3', placeholder: _('username'), size: 40
+ = text_field_tag :bitbucket_server_username, '', class: 'form-control gl-mr-3', placeholder: _('username'), size: 40
.form-group.row
= label_tag :personal_access_token, 'Password/Personal Access Token', class: 'col-form-label col-md-2'
.col-md-4
diff --git a/app/views/import/bitbucket_server/status.html.haml b/app/views/import/bitbucket_server/status.html.haml
index 3e16f449831..a24a1c1fb05 100644
--- a/app/views/import/bitbucket_server/status.html.haml
+++ b/app/views/import/bitbucket_server/status.html.haml
@@ -1,98 +1,8 @@
-- page_title 'Bitbucket Server import'
-- header_title 'Projects', root_path
+- page_title _('Bitbucket Server import')
+- header_title _('Projects'), root_path
%h3.page-title
%i.fa.fa-bitbucket-square
= _('Import projects from Bitbucket Server')
-- if Feature.enabled?(:new_import_ui)
- = render 'import/githubish_status', provider: 'bitbucket_server', extra_data: { reconfigure_path: configure_import_bitbucket_server_path }
-- else
- - if @repos.any?
- %p.light
- = _('Select projects you want to import.')
- .btn-group
- - if @incompatible_repos.any?
- = button_tag class: 'btn btn-import btn-success js-import-all' do
- = _('Import all compatible projects')
- = icon('spinner spin', class: 'loading-icon')
- - else
- = button_tag class: 'btn btn-import btn-success js-import-all' do
- = _('Import all projects')
- = icon('spinner spin', class: 'loading-icon')
-
- .btn-group
- = link_to('Reconfigure', configure_import_bitbucket_server_path, class: 'btn btn-primary', method: :post)
-
- .input-btn-group.float-right
- = form_tag status_import_bitbucket_server_path, :method => 'get' do
- = text_field_tag :filter, sanitize(params[:filter]), class: 'form-control append-bottom-10', placeholder: _('Filter your projects by name'), size: 40, autoFocus: true
-
- .table-responsive.prepend-top-10
- %table.table.import-jobs
- %colgroup.import-jobs-from-col
- %colgroup.import-jobs-to-col
- %colgroup.import-jobs-status-col
- %thead
- %tr
- %th= _('From Bitbucket Server')
- %th= _('To GitLab')
- %th= _('Status')
- %tbody
- - @already_added_projects.each do |project|
- %tr{ id: "project_#{project.id}", class: "#{project_status_css_class(project.import_status)}" }
- %td
- = link_to project.import_source, project.import_source, target: '_blank', rel: 'noopener noreferrer'
- %td
- = link_to project.full_path, [project.namespace.becomes(Namespace), project]
- %td.job-status
- - case project.import_status
- - when 'finished'
- = icon('check', text: 'Done')
- - when 'started'
- = icon('spin', text: 'started')
- - else
- = project.human_import_status_name
-
- - @repos.each do |repo|
- %tr{ id: "repo_#{repo.project_key}___#{repo.slug}", data: { project: repo.project_key, repository: repo.slug } }
- %td
- = sanitize(link_to(repo.browse_url, repo.browse_url, target: '_blank', rel: 'noopener noreferrer'), attributes: %w(href target rel))
- %td.import-target
- %fieldset.row
- .input-group
- .project-path.input-group-prepend
- - if current_user.can_select_namespace?
- - selected = params[:namespace_id] || :extra_group
- - opts = current_user.can_create_group? ? { extra_group: Group.new(name: sanitize_project_name(repo.project_key), path: sanitize_project_name(repo.project_key)) } : {}
- = select_tag :namespace_id, namespaces_options(selected, opts.merge({ display_path: true })), { class: 'select2 js-select-namespace', tabindex: 1 }
- - else
- = text_field_tag :path, current_user.namespace_path, class: "input-group-text input-large form-control", tabindex: 1, disabled: true
- %span.input-group-prepend
- .input-group-text /
- = text_field_tag :path, sanitize_project_name(repo.slug), class: "input-mini form-control", tabindex: 2, required: true
- %td.import-actions.job-status
- = button_tag class: 'btn btn-import js-add-to-import' do
- Import
- = icon('spinner spin', class: 'loading-icon')
- - @incompatible_repos.each do |repo|
- %tr{ id: "repo_#{repo.project_key}___#{repo.slug}" }
- %td
- = sanitize(link_to(repo.browse_url, repo.browse_url, target: '_blank', rel: 'noopener noreferrer'), attributes: %w(href target rel))
- %td.import-target
- %td.import-actions-job-status
- = label_tag 'Incompatible Project', nil, class: 'label badge-danger'
-
- - if @incompatible_repos.any?
- %p
- One or more of your Bitbucket Server projects cannot be imported into GitLab
- directly because they use Subversion or Mercurial for version control,
- rather than Git. Please convert
- = link_to 'them to Git,', 'https://www.atlassian.com/git/tutorials/migrating-overview'
- and go through the
- = link_to 'import flow', status_import_bitbucket_server_path
- again.
-
- = paginate_without_count(@collection)
-
- .js-importer-status{ data: { jobs_import_path: "#{jobs_import_bitbucket_server_path}", import_path: "#{import_bitbucket_server_path}" } }
+= render 'import/githubish_status', provider: 'bitbucket_server', extra_data: { reconfigure_path: configure_import_bitbucket_server_path }
diff --git a/app/views/import/fogbugz/status.html.haml b/app/views/import/fogbugz/status.html.haml
index 75529487aa4..f201c0e83fe 100644
--- a/app/views/import/fogbugz/status.html.haml
+++ b/app/views/import/fogbugz/status.html.haml
@@ -4,63 +4,8 @@
%i.fa.fa-bug
= _('Import projects from FogBugz')
-- if Feature.enabled?(:new_import_ui)
- %p.light
- - link_to_customize = link_to('customize', new_user_map_import_fogbugz_path)
- = _('Optionally, you can %{link_to_customize} how FogBugz email addresses and usernames are imported into GitLab.').html_safe % { link_to_customize: link_to_customize }
- %hr
- = render 'import/githubish_status', provider: 'fogbugz', filterable: false
-- else
- - if @repos.any?
- %p.light
- = _('Select projects you want to import.')
- %p.light
- - link_to_customize = link_to('customize', new_user_map_import_fogbugz_path)
- = _('Optionally, you can %{link_to_customize} how FogBugz email addresses and usernames are imported into GitLab.').html_safe % { link_to_customize: link_to_customize }
- %hr
- %p
- = button_tag class: 'btn btn-import btn-success js-import-all' do
- = _('Import all projects')
- = icon("spinner spin", class: "loading-icon")
-
- .table-responsive
- %table.table.import-jobs
- %colgroup.import-jobs-from-col
- %colgroup.import-jobs-to-col
- %colgroup.import-jobs-status-col
- %thead
- %tr
- %th= _("From FogBugz")
- %th= _("To GitLab")
- %th= _("Status")
- %tbody
- - @already_added_projects.each do |project|
- %tr{ id: "project_#{project.id}", class: "#{project_status_css_class(project.import_status)}" }
- %td
- = project.import_source
- %td
- = link_to project.full_path, [project.namespace.becomes(Namespace), project]
- %td.job-status
- - case project.import_status
- - when 'finished'
- %span
- %i.fa.fa-check
- = _("done")
- - when 'started'
- %i.fa.fa-spinner.fa-spin
- = _("started")
- - else
- = project.human_import_status_name
-
- - @repos.each do |repo|
- %tr{ id: "repo_#{repo.id}" }
- %td
- = repo.name
- %td.import-target
- #{current_user.username}/#{repo.name}
- %td.import-actions.job-status
- = button_tag class: "btn btn-import js-add-to-import" do
- = _("Import")
- = icon("spinner spin", class: "loading-icon")
-
- .js-importer-status{ data: { jobs_import_path: "#{jobs_import_fogbugz_path}", import_path: "#{import_fogbugz_path}" } }
+%p.light
+ - link_to_customize = link_to('customize', new_user_map_import_fogbugz_path)
+ = _('Optionally, you can %{link_to_customize} how FogBugz email addresses and usernames are imported into GitLab.').html_safe % { link_to_customize: link_to_customize }
+%hr
+= render 'import/githubish_status', provider: 'fogbugz', filterable: false
diff --git a/app/views/import/gitlab/status.html.haml b/app/views/import/gitlab/status.html.haml
index a12b69ae5f9..5513849be3d 100644
--- a/app/views/import/gitlab/status.html.haml
+++ b/app/views/import/gitlab/status.html.haml
@@ -1,58 +1,7 @@
- page_title _("GitLab.com import")
- header_title _("Projects"), root_path
%h3.page-title
- %i.fa.fa-heart
+ = sprite_icon('heart', size: 16, css_class: 'gl-vertical-align-middle')
= _('Import projects from GitLab.com')
-- if Feature.enabled?(:new_import_ui)
- = render 'import/githubish_status', provider: 'gitlab', filterable: false
-- else
- %p.light
- = _('Select projects you want to import.')
- %hr
- %p
- = button_tag class: "btn btn-import btn-success js-import-all" do
- = _('Import all projects')
- = icon("spinner spin", class: "loading-icon")
-
- .table-responsive
- %table.table.import-jobs
- %colgroup.import-jobs-from-col
- %colgroup.import-jobs-to-col
- %colgroup.import-jobs-status-col
- %thead
- %tr
- %th= _('From GitLab.com')
- %th= _('To this GitLab instance')
- %th= _('Status')
- %tbody
- - @already_added_projects.each do |project|
- %tr{ id: "project_#{project.id}", class: "#{project_status_css_class(project.import_status)}" }
- %td
- = link_to project.import_source, "https://gitlab.com/#{project.import_source}", target: "_blank"
- %td
- = link_to project.full_path, [project.namespace.becomes(Namespace), project]
- %td.job-status
- - case project.import_status
- - when 'finished'
- %span
- %i.fa.fa-check
- = _('done')
- - when 'started'
- %i.fa.fa-spinner.fa-spin
- = _('started')
- - else
- = project.human_import_status_name
-
- - @repos.each do |repo|
- %tr{ id: "repo_#{repo["id"]}" }
- %td
- = link_to repo["path_with_namespace"], "https://gitlab.com/#{repo["path_with_namespace"]}", target: "_blank", rel: 'noopener noreferrer'
- %td.import-target
- = import_project_target(repo['namespace']['path'], repo['name'])
- %td.import-actions.job-status
- = button_tag class: "btn btn-import js-add-to-import" do
- = _('Import')
- = icon("spinner spin", class: "loading-icon")
-
- .js-importer-status{ data: { jobs_import_path: "#{jobs_import_gitlab_path}", import_path: "#{import_gitlab_path}" } }
+= render 'import/githubish_status', provider: 'gitlab', filterable: false
diff --git a/app/views/import/gitlab_projects/new.html.haml b/app/views/import/gitlab_projects/new.html.haml
index feebbccf46a..b667d2aa0d7 100644
--- a/app/views/import/gitlab_projects/new.html.haml
+++ b/app/views/import/gitlab_projects/new.html.haml
@@ -1,8 +1,9 @@
- page_title _("GitLab Import")
- header_title _("Projects"), root_path
-%h3.page-title
- = icon('gitlab')
+%h3.page-title.d-flex
+ .gl-display-flex.gl-align-items-center.gl-justify-content-center
+ = sprite_icon('tanuki', size: 16, css_class: 'gl-mr-2')
= _('Import an exported GitLab project')
%hr
diff --git a/app/views/import/manifest/new.html.haml b/app/views/import/manifest/new.html.haml
index df00c4d2179..852f269f2ed 100644
--- a/app/views/import/manifest/new.html.haml
+++ b/app/views/import/manifest/new.html.haml
@@ -1,5 +1,5 @@
-- page_title "Manifest file import"
-- header_title "Projects", root_path
+- page_title _("Manifest file import")
+- header_title _("Projects"), root_path
%h3.page-title
= _('Manifest file import')
diff --git a/app/views/import/manifest/status.html.haml b/app/views/import/manifest/status.html.haml
index 3d4abc32b88..e85162ad1b4 100644
--- a/app/views/import/manifest/status.html.haml
+++ b/app/views/import/manifest/status.html.haml
@@ -1,5 +1,5 @@
-- page_title "Manifest import"
-- header_title "Projects", root_path
+- page_title _("Manifest import")
+- header_title _("Projects"), root_path
- provider = 'manifest'
%h3.page-title
diff --git a/app/views/instance_statistics/cohorts/index.html.haml b/app/views/instance_statistics/cohorts/index.html.haml
index 5333f8b7a1f..a038246bd53 100644
--- a/app/views/instance_statistics/cohorts/index.html.haml
+++ b/app/views/instance_statistics/cohorts/index.html.haml
@@ -1,11 +1,12 @@
- breadcrumb_title _("Cohorts")
+- page_title _("Cohorts")
- if @cohorts
= render 'cohorts_table'
- else
.bs-callout.bs-callout-warning.clearfix
%p
- - usage_ping_path = help_page_path('user/admin_area/settings/usage_statistics', anchor: 'usage-ping')
+ - usage_ping_path = help_page_path('development/telemetry/usage_ping')
- usage_ping_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: usage_ping_path }
= s_('User Cohorts are only shown when the %{usage_ping_link_start}usage ping%{usage_ping_link_end} is enabled.').html_safe % { usage_ping_link_start: usage_ping_link_start, usage_ping_link_end: '</a>'.html_safe }
- if current_user.admin?
diff --git a/app/views/instance_statistics/dev_ops_score/_callout.html.haml b/app/views/instance_statistics/dev_ops_score/_callout.html.haml
index 64eb72c0d8d..31ae7721f5f 100644
--- a/app/views/instance_statistics/dev_ops_score/_callout.html.haml
+++ b/app/views/instance_statistics/dev_ops_score/_callout.html.haml
@@ -1,4 +1,4 @@
-.prepend-top-default
+.gl-mt-3
.user-callout{ data: { uid: 'dev_ops_score_intro_callout_dismissed' } }
.bordered-box.landing.content-block
%button.btn.btn-default.close.js-close-callout{ type: 'button',
diff --git a/app/views/instance_statistics/dev_ops_score/_disabled.html.haml b/app/views/instance_statistics/dev_ops_score/_disabled.html.haml
index da27ea17b61..bd808218f75 100644
--- a/app/views/instance_statistics/dev_ops_score/_disabled.html.haml
+++ b/app/views/instance_statistics/dev_ops_score/_disabled.html.haml
@@ -4,7 +4,7 @@
%h4= _('Usage ping is not enabled')
- if !current_user.admin?
%p
- - usage_ping_path = help_page_path('user/admin_area/settings/usage_statistics', anchor: 'usage-ping')
+ - usage_ping_path = help_page_path('development/telemetry/usage_ping')
- usage_ping_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: usage_ping_path }
= s_('In order to enable instance-level analytics, please ask an admin to enable %{usage_ping_link_start}usage ping%{usage_ping_link_end}.').html_safe % { usage_ping_link_start: usage_ping_link_start, usage_ping_link_end: '</a>'.html_safe }
- if current_user.admin?
diff --git a/app/views/instance_statistics/dev_ops_score/index.html.haml b/app/views/instance_statistics/dev_ops_score/index.html.haml
index 44c6e9664db..215624d27ce 100644
--- a/app/views/instance_statistics/dev_ops_score/index.html.haml
+++ b/app/views/instance_statistics/dev_ops_score/index.html.haml
@@ -5,7 +5,7 @@
- if usage_ping_enabled && show_callout?('dev_ops_score_intro_callout_dismissed')
= render 'callout'
- .prepend-top-default
+ .gl-mt-3
- if !usage_ping_enabled
= render 'disabled'
- elsif @metric.blank?
diff --git a/app/views/invites/show.html.haml b/app/views/invites/show.html.haml
index 30ab5781014..2bcd64d0690 100644
--- a/app/views/invites/show.html.haml
+++ b/app/views/invites/show.html.haml
@@ -20,21 +20,19 @@
= link_to group.name, group_url(group)
as #{@member.human_access}.
-- is_member = @member.source.users.include?(current_user)
-
-- if is_member
+- if member?
%p
- member_source = @member.source.is_a?(Group) ? _("group") : _("project")
= _("However, you are already a member of this %{member_source}. Sign in using a different account to accept the invitation.") % { member_source: member_source }
-- if @member.invite_email != current_user.email
+- if !current_user_matches_invite?
%p
- mail_to_invite_email = mail_to(@member.invite_email)
- mail_to_current_user = mail_to(current_user.email)
- link_to_current_user = link_to(current_user.to_reference, user_url(current_user))
= _("Note that this invitation was sent to %{mail_to_invite_email}, but you are signed in as %{link_to_current_user} with email %{mail_to_current_user}.").html_safe % { mail_to_invite_email: mail_to_invite_email, mail_to_current_user: mail_to_current_user, link_to_current_user: link_to_current_user }
-- unless is_member
+- unless member?
.actions
= link_to _("Accept invitation"), accept_invite_url(@token), method: :post, class: "btn btn-success"
- = link_to _("Decline"), decline_invite_url(@token), method: :post, class: "btn btn-danger prepend-left-10"
+ = link_to _("Decline"), decline_invite_url(@token), method: :post, class: "btn btn-danger gl-ml-3"
diff --git a/app/views/kaminari/gitlab/_paginator.html.haml b/app/views/kaminari/gitlab/_paginator.html.haml
index 1b2edc0ad22..91998147966 100644
--- a/app/views/kaminari/gitlab/_paginator.html.haml
+++ b/app/views/kaminari/gitlab/_paginator.html.haml
@@ -6,7 +6,7 @@
-# remote: data-remote
-# paginator: the paginator that renders the pagination tags inside
= paginator.render do
- .gl-pagination.prepend-top-default
+ .gl-pagination.gl-mt-3
%ul.pagination.justify-content-center
= prev_page_tag
- each_page do |page|
diff --git a/app/views/kaminari/gitlab/_without_count.html.haml b/app/views/kaminari/gitlab/_without_count.html.haml
index d13f6ca5fa8..dc9dcbeed1d 100644
--- a/app/views/kaminari/gitlab/_without_count.html.haml
+++ b/app/views/kaminari/gitlab/_without_count.html.haml
@@ -1,4 +1,4 @@
-.gl-pagination.prepend-top-default
+.gl-pagination.gl-mt-3
%ul.pagination.justify-content-center
- if previous_path
%li.page-item.prev
diff --git a/app/views/layouts/_head.html.haml b/app/views/layouts/_head.html.haml
index 886d4109ff5..d1311f17b72 100644
--- a/app/views/layouts/_head.html.haml
+++ b/app/views/layouts/_head.html.haml
@@ -25,6 +25,8 @@
%meta{ 'http-equiv' => 'X-UA-Compatible', content: 'IE=edge' }
+ = render 'layouts/startup_js'
+
-# Open Graph - http://ogp.me/
%meta{ property: 'og:type', content: "object" }
%meta{ property: 'og:site_name', content: site_name }
@@ -51,7 +53,6 @@
= stylesheet_link_tag "application_dark", media: "all"
- else
= stylesheet_link_tag "application", media: "all"
- = stylesheet_link_tag "print", media: "print"
= stylesheet_link_tag "disable_animations", media: "all" if Rails.env.test? || Gitlab.config.gitlab['disable_animations']
= stylesheet_link_tag 'performance_bar' if performance_bar_enabled?
diff --git a/app/views/layouts/_img_loader.html.haml b/app/views/layouts/_img_loader.html.haml
new file mode 100644
index 00000000000..cddcd6e0af6
--- /dev/null
+++ b/app/views/layouts/_img_loader.html.haml
@@ -0,0 +1,17 @@
+= javascript_tag nonce: true do
+ :plain
+ if ('loading' in HTMLImageElement.prototype) {
+ document.querySelectorAll('img.lazy').forEach(img => {
+ img.loading = 'lazy';
+ let imgUrl = img.dataset.src;
+ // Only adding width + height for avatars for now
+ if (imgUrl.indexOf('/avatar/') > -1 && imgUrl.indexOf('?') === -1) {
+ const targetWidth = img.getAttribute('width') || img.width;
+ imgUrl += `?width=${targetWidth}`;
+ }
+ img.src = imgUrl;
+ img.removeAttribute('data-src');
+ img.classList.remove('lazy');
+ img.classList.add('js-lazy-loaded', 'qa-js-lazy-loaded');
+ });
+ }
diff --git a/app/views/layouts/_page.html.haml b/app/views/layouts/_page.html.haml
index d1cf83b2a9f..72b88fa8f7f 100644
--- a/app/views/layouts/_page.html.haml
+++ b/app/views/layouts/_page.html.haml
@@ -7,6 +7,7 @@
= render 'shared/outdated_browser'
= render_if_exists 'layouts/header/users_over_license_banner'
= render_if_exists "layouts/header/licensed_user_count_threshold"
+ = render_if_exists "layouts/header/token_expiry_notification"
= render "layouts/broadcast"
= render "layouts/header/read_only_banner"
= render "layouts/nav/classification_level_banner"
diff --git a/app/views/layouts/_search.html.haml b/app/views/layouts/_search.html.haml
index 97d00bce11b..81fe0798bd1 100644
--- a/app/views/layouts/_search.html.haml
+++ b/app/views/layouts/_search.html.haml
@@ -2,7 +2,7 @@
= form_tag search_path, method: :get, class: 'form-inline' do |f|
.search-input-container
.search-input-wrap
- .dropdown
+ .dropdown{ data: { url: search_autocomplete_path } }
= search_field_tag 'search', nil, placeholder: _('Search or jump to…'),
class: 'search-input dropdown-menu-toggle no-outline js-search-dashboard-options',
spellcheck: false,
@@ -37,3 +37,6 @@
-# workaround for non-JS feature specs, see spec/support/helpers/search_helpers.rb
- if ENV['RAILS_ENV'] == 'test'
%noscript= button_tag 'Search'
+ .search-autocomplete-opts.hide{ :'data-autocomplete-path' => search_autocomplete_path,
+ :'data-autocomplete-project-id' => search_context.project.try(:id),
+ :'data-autocomplete-project-ref' => search_context.ref }
diff --git a/app/views/layouts/_startup_js.html.haml b/app/views/layouts/_startup_js.html.haml
new file mode 100644
index 00000000000..3eb68df07c6
--- /dev/null
+++ b/app/views/layouts/_startup_js.html.haml
@@ -0,0 +1,13 @@
+- return unless page_startup_api_calls.present?
+
+= javascript_tag nonce: true do
+ :plain
+ var gl = window.gl || {};
+ gl.startup_calls = #{page_startup_api_calls.to_json};
+ if (gl.startup_calls && window.fetch) {
+ Object.keys(gl.startup_calls).forEach(apiCall => {
+ gl.startup_calls[apiCall] = {
+ fetchCall: fetch(apiCall)
+ };
+ });
+ }
diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml
index eb58115451d..58408ec822c 100644
--- a/app/views/layouts/application.html.haml
+++ b/app/views/layouts/application.html.haml
@@ -13,6 +13,6 @@
= render 'layouts/page', sidebar: sidebar, nav: nav
= footer_message
- = render_if_exists "shared/onboarding_guide"
+ = render 'layouts/img_loader'
= yield :scripts_body
diff --git a/app/views/layouts/header/_current_user_dropdown.html.haml b/app/views/layouts/header/_current_user_dropdown.html.haml
index d568086f4a4..4c659241f99 100644
--- a/app/views/layouts/header/_current_user_dropdown.html.haml
+++ b/app/views/layouts/header/_current_user_dropdown.html.haml
@@ -35,7 +35,6 @@
= link_to _("Help"), help_path
%li.d-md-none
= link_to _("Support"), support_url
- = render_if_exists "shared/learn_gitlab_menu_item"
%li.d-md-none
= link_to _("Submit feedback"), "https://about.gitlab.com/submit-feedback"
- if current_user_menu?(:help) || current_user_menu?(:settings) || current_user_menu?(:profile)
diff --git a/app/views/layouts/header/_help_dropdown.html.haml b/app/views/layouts/header/_help_dropdown.html.haml
index 2b3f5d266b0..ad4e0f1f4b2 100644
--- a/app/views/layouts/header/_help_dropdown.html.haml
+++ b/app/views/layouts/header/_help_dropdown.html.haml
@@ -9,7 +9,6 @@
%button.js-shortcuts-modal-trigger{ type: "button" }
= _("Keyboard shortcuts")
%span.text-secondary.float-right{ "aria-hidden": true }= '?'.html_safe
- = render_if_exists "shared/learn_gitlab_menu_item"
%li.divider
%li
= link_to _("Submit feedback"), "https://about.gitlab.com/submit-feedback"
diff --git a/app/views/layouts/header/_new_dropdown.haml b/app/views/layouts/header/_new_dropdown.haml
index 3cbfb24a868..4bfac76ec5b 100644
--- a/app/views/layouts/header/_new_dropdown.haml
+++ b/app/views/layouts/header/_new_dropdown.haml
@@ -15,6 +15,7 @@
%li= link_to _('New project'), new_project_path(namespace_id: @group.id)
- if create_group_subgroup
%li= link_to _('New subgroup'), new_group_path(parent_id: @group.id)
+ = render_if_exists 'layouts/header/create_epic_new_dropdown_item'
%li.divider
%li.dropdown-bold-header GitLab
diff --git a/app/views/layouts/nav/_dashboard.html.haml b/app/views/layouts/nav/_dashboard.html.haml
index 0b23a06f5a9..e6cfd7d56bb 100644
--- a/app/views/layouts/nav/_dashboard.html.haml
+++ b/app/views/layouts/nav/_dashboard.html.haml
@@ -26,16 +26,16 @@
%ul
- if dashboard_nav_link?(:groups)
%li.d-md-none
- = link_to dashboard_groups_path, class: 'dashboard-shortcuts-groups' do
+ = link_to dashboard_groups_path, class: 'dashboard-shortcuts-groups', data: { qa_selector: 'groups_link' } do
= _('Groups')
- if dashboard_nav_link?(:activity)
= nav_link(path: 'dashboard#activity') do
- = link_to activity_dashboard_path, class: 'dashboard-shortcuts-activity' do
+ = link_to activity_dashboard_path, class: 'dashboard-shortcuts-activity', data: { qa_selector: 'activity_link' } do
= _('Activity')
- if dashboard_nav_link?(:milestones)
= nav_link(controller: 'dashboard/milestones') do
- = link_to dashboard_milestones_path, class: 'dashboard-shortcuts-milestones' do
+ = link_to dashboard_milestones_path, class: 'dashboard-shortcuts-milestones', data: { qa_selector: 'milestones_link' } do
= _('Milestones')
- if dashboard_nav_link?(:snippets)
diff --git a/app/views/layouts/nav/sidebar/_admin.html.haml b/app/views/layouts/nav/sidebar/_admin.html.haml
index 28e52dc85db..e72535b8824 100644
--- a/app/views/layouts/nav/sidebar/_admin.html.haml
+++ b/app/views/layouts/nav/sidebar/_admin.html.haml
@@ -8,7 +8,7 @@
= _('Admin Area')
%ul.sidebar-top-level-items{ data: { qa_selector: 'admin_sidebar_overview_submenu_content' } }
= nav_link(controller: %w(dashboard admin admin/projects users groups jobs runners gitaly_servers), html_options: {class: 'home'}) do
- = link_to admin_root_path, class: 'shortcuts-tree' do
+ = link_to admin_root_path do
.nav-icon-container
= sprite_icon('overview')
%span.nav-item-name
@@ -216,7 +216,7 @@
%strong.fly-out-top-item-name
= _('Appearance')
- = nav_link(controller: :application_settings) do
+ = nav_link(controller: [:application_settings, :integrations]) do
= link_to general_admin_application_settings_path do
.nav-icon-container
= sprite_icon('settings')
@@ -224,7 +224,7 @@
= _('Settings')
%ul.sidebar-sub-level-items.qa-admin-sidebar-settings-submenu
- = nav_link(controller: :application_settings, html_options: { class: "fly-out-top-item" } ) do
+ = nav_link(controller: [:application_settings, :integrations], html_options: { class: "fly-out-top-item" } ) do
= link_to general_admin_application_settings_path do
%strong.fly-out-top-item-name
= _('Settings')
@@ -233,7 +233,7 @@
= link_to general_admin_application_settings_path, title: _('General'), class: 'qa-admin-settings-general-item' do
%span
= _('General')
- = nav_link(path: 'application_settings#integrations') do
+ = nav_link(path: ['application_settings#integrations', 'integrations#edit']) do
= link_to integrations_admin_application_settings_path, title: _('Integrations'), data: { qa_selector: 'integration_settings_link' } do
%span
= _('Integrations')
diff --git a/app/views/layouts/nav/sidebar/_group.html.haml b/app/views/layouts/nav/sidebar/_group.html.haml
index cd9765289a4..909d72edb31 100644
--- a/app/views/layouts/nav/sidebar/_group.html.haml
+++ b/app/views/layouts/nav/sidebar/_group.html.haml
@@ -81,7 +81,7 @@
- if group_sidebar_link?(:milestones)
= nav_link(path: 'milestones#index') do
- = link_to group_milestones_path(@group), title: _('Milestones') do
+ = link_to group_milestones_path(@group), title: _('Milestones'), data: { qa_selector: 'group_milestones_link' } do
%span
= _('Milestones')
@@ -123,6 +123,9 @@
= render 'layouts/nav/sidebar/analytics_links', links: group_analytics_navbar_links(@group, current_user)
+ - if group_sidebar_link?(:wiki)
+ = render 'layouts/nav/sidebar/wiki_link', wiki_url: @group.wiki.web_url
+
- if group_sidebar_link?(:group_members)
= nav_link(path: 'group_members#index') do
= link_to group_group_members_path(@group) do
diff --git a/app/views/layouts/nav/sidebar/_project.html.haml b/app/views/layouts/nav/sidebar/_project.html.haml
index 16902ebe1d4..d59c75de6d2 100644
--- a/app/views/layouts/nav/sidebar/_project.html.haml
+++ b/app/views/layouts/nav/sidebar/_project.html.haml
@@ -37,7 +37,7 @@
- if project_nav_tab? :files
= nav_link(controller: sidebar_repository_paths, unless: -> { current_path?('projects/graphs#charts') }) do
- = link_to project_tree_path(@project), class: 'shortcuts-tree qa-project-menu-repo' do
+ = link_to project_tree_path(@project), class: 'shortcuts-tree', data: { qa_selector: "repository_link" } do
.nav-icon-container
= sprite_icon('doc-text')
%span.nav-item-name#js-onboarding-repo-link
@@ -58,11 +58,11 @@
= _('Commits')
= nav_link(html_options: {class: branches_tab_class}) do
- = link_to project_branches_path(@project), class: 'qa-branches-link', id: 'js-onboarding-branches-link' do
+ = link_to project_branches_path(@project), data: { qa_selector: "branches_link" }, id: 'js-onboarding-branches-link' do
= _('Branches')
= nav_link(controller: [:tags]) do
- = link_to project_tags_path(@project) do
+ = link_to project_tags_path(@project), data: { qa_selector: "tags_link" } do
= _('Tags')
= nav_link(path: 'graphs#show') do
@@ -80,7 +80,7 @@
= render_if_exists 'projects/sidebar/repository_locked_files'
- if project_nav_tab? :issues
- = nav_link(controller: @project.issues_enabled? ? [:issues, :labels, :milestones, :boards] : :issues) do
+ = nav_link(controller: @project.issues_enabled? ? ['projects/issues', :labels, :milestones, :boards] : 'projects/issues') do
= link_to project_issues_path(@project), class: 'shortcuts-issues qa-issues-item' do
.nav-icon-container
= sprite_icon('issues')
@@ -91,7 +91,7 @@
= number_with_delimiter(@project.open_issues_count(current_user))
%ul.sidebar-sub-level-items
- = nav_link(controller: :issues, action: :index, html_options: { class: "fly-out-top-item" } ) do
+ = nav_link(controller: 'projects/issues', action: :index, html_options: { class: "fly-out-top-item" } ) do
= link_to project_issues_path(@project) do
%strong.fly-out-top-item-name
= _('Issues')
@@ -114,25 +114,29 @@
%span
= _('Labels')
- = render_if_exists 'projects/sidebar/issues_service_desk'
+ = render 'projects/sidebar/issues_service_desk'
= nav_link(controller: :milestones) do
= link_to project_milestones_path(@project), title: _('Milestones'), class: 'qa-milestones-link' do
%span
= _('Milestones')
- - if project_nav_tab? :external_issue_tracker
- = nav_link do
- - issue_tracker = @project.external_issue_tracker
- = link_to issue_tracker.issue_tracker_path, class: 'shortcuts-external_tracker' do
- .nav-icon-container
- = sprite_icon('external-link')
- %span.nav-item-name
- = issue_tracker.title
- %ul.sidebar-sub-level-items.is-fly-out-only
- = nav_link(html_options: { class: "fly-out-top-item" } ) do
- = link_to issue_tracker.issue_tracker_path do
- %strong.fly-out-top-item-name
- = issue_tracker.title
+
+ - if project_nav_tab?(:external_issue_tracker)
+ - issue_tracker = @project.external_issue_tracker
+ - if issue_tracker.is_a?(JiraService) && project_jira_issues_integration?
+ = render_if_exists 'layouts/nav/sidebar/project_jira_issues_link', issue_tracker: issue_tracker
+ - else
+ = nav_link do
+ = link_to issue_tracker.issue_tracker_path, target: '_blank', rel: 'noopener noreferrer', class: 'shortcuts-external_tracker' do
+ .nav-icon-container
+ = sprite_icon('external-link')
+ %span.nav-item-name
+ = issue_tracker.title
+ %ul.sidebar-sub-level-items.is-fly-out-only
+ = nav_link(html_options: { class: "fly-out-top-item" } ) do
+ = link_to issue_tracker.issue_tracker_path, target: '_blank', rel: 'noopener noreferrer' do
+ %strong.fly-out-top-item-name
+ = issue_tracker.title
- if (project_nav_tab? :labels) && !@project.issues_enabled?
= nav_link(controller: [:labels]) do
@@ -289,19 +293,22 @@
= render 'layouts/nav/sidebar/analytics_links', links: project_analytics_navbar_links(@project, current_user)
- - if project_nav_tab? :wiki
- - wiki_url = wiki_path(@project.wiki)
- = nav_link(controller: :wikis) do
- = link_to wiki_url, class: 'shortcuts-wiki', data: { qa_selector: 'wiki_link' } do
+ - if project_nav_tab?(:confluence)
+ - confluence_url = project_wikis_confluence_path(@project)
+ = nav_link do
+ = link_to confluence_url, class: 'shortcuts-confluence' do
.nav-icon-container
- = sprite_icon('book')
+ = image_tag 'confluence.svg', alt: _('Confluence')
%span.nav-item-name
- = _('Wiki')
+ = _('Confluence')
%ul.sidebar-sub-level-items.is-fly-out-only
- = nav_link(controller: :wikis, html_options: { class: "fly-out-top-item" } ) do
- = link_to wiki_url do
+ = nav_link(html_options: { class: 'fly-out-top-item' } ) do
+ = link_to confluence_url, target: '_blank', rel: 'noopener noreferrer' do
%strong.fly-out-top-item-name
- = _('Wiki')
+ = _('Confluence')
+
+ - if project_nav_tab? :wiki
+ = render 'layouts/nav/sidebar/wiki_link', wiki_url: wiki_path(@project.wiki)
- if project_nav_tab?(:external_wiki)
- external_wiki_url = @project.external_wiki.external_wiki_url
@@ -344,7 +351,7 @@
- if project_nav_tab? :settings
= nav_link(path: sidebar_settings_paths) do
- = link_to edit_project_path(@project), class: 'shortcuts-tree' do
+ = link_to edit_project_path(@project) do
.nav-icon-container
= sprite_icon('settings')
%span.nav-item-name.qa-settings-item#js-onboarding-settings-link
diff --git a/app/views/layouts/nav/sidebar/_wiki_link.html.haml b/app/views/layouts/nav/sidebar/_wiki_link.html.haml
new file mode 100644
index 00000000000..b6b63b75fcc
--- /dev/null
+++ b/app/views/layouts/nav/sidebar/_wiki_link.html.haml
@@ -0,0 +1,11 @@
+= nav_link(controller: :wikis) do
+ = link_to wiki_url, class: 'shortcuts-wiki', data: { qa_selector: 'wiki_link' } do
+ .nav-icon-container
+ = sprite_icon('book')
+ %span.nav-item-name
+ = _('Wiki')
+ %ul.sidebar-sub-level-items.is-fly-out-only
+ = nav_link(controller: :wikis, html_options: { class: "fly-out-top-item" } ) do
+ = link_to wiki_url do
+ %strong.fly-out-top-item-name
+ = _('Wiki')
diff --git a/app/views/layouts/service_desk.html.haml b/app/views/layouts/service_desk.html.haml
new file mode 100644
index 00000000000..26d15a74403
--- /dev/null
+++ b/app/views/layouts/service_desk.html.haml
@@ -0,0 +1,24 @@
+%html{ lang: "en" }
+ %head
+ %meta{ content: "text/html; charset=utf-8", "http-equiv" => "Content-Type" }
+ -# haml-lint:disable NoPlainNodes
+ %title
+ GitLab
+ -# haml-lint:enable NoPlainNodes
+ = stylesheet_link_tag 'notify'
+ = yield :head
+ %body
+ .content
+ = yield
+ .footer{ style: "margin-top: 10px;" }
+ %p
+ &mdash;
+ %br
+ = link_to "Unsubscribe", @unsubscribe_url
+
+ -# EE-specific start
+ - if Gitlab::CurrentSettings.email_additional_text.present?
+ %br
+ %br
+ = Gitlab::Utils.nlbr(Gitlab::CurrentSettings.email_additional_text)
+ -# EE-specific end
diff --git a/app/views/layouts/snippets.html.haml b/app/views/layouts/snippets.html.haml
index cde2b467392..6cc53ba3342 100644
--- a/app/views/layouts/snippets.html.haml
+++ b/app/views/layouts/snippets.html.haml
@@ -1,3 +1,4 @@
+- page_title _("Snippets")
- header_title _("Snippets"), snippets_path
- snippets_upload_path = snippets_upload_path(@snippet, current_user)
diff --git a/app/views/notify/closed_merge_request_email.html.haml b/app/views/notify/closed_merge_request_email.html.haml
index 2aa753e0d55..6caa0e59e8f 100644
--- a/app/views/notify/closed_merge_request_email.html.haml
+++ b/app/views/notify/closed_merge_request_email.html.haml
@@ -1,2 +1,3 @@
%p
- Merge Request #{@merge_request.to_reference} was closed by #{sanitize_name(@updated_by.name)}
+ Merge Request #{merge_request_reference_link(@merge_request)}
+ was closed by #{sanitize_name(@updated_by.name)}
diff --git a/app/views/notify/closed_merge_request_email.text.haml b/app/views/notify/closed_merge_request_email.text.haml
index 6e84f9fb355..8546da2d7f0 100644
--- a/app/views/notify/closed_merge_request_email.text.haml
+++ b/app/views/notify/closed_merge_request_email.text.haml
@@ -1,6 +1,6 @@
Merge Request #{@merge_request.to_reference} was closed by #{sanitize_name(@updated_by.name)}
-Merge Request url: #{project_merge_request_url(@merge_request.target_project, @merge_request)}
+Merge Request URL: #{project_merge_request_url(@merge_request.target_project, @merge_request)}
= merge_path_description(@merge_request, 'to')
diff --git a/app/views/notify/merge_request_status_email.html.haml b/app/views/notify/merge_request_status_email.html.haml
index ffb416abf72..a15c5a752d4 100644
--- a/app/views/notify/merge_request_status_email.html.haml
+++ b/app/views/notify/merge_request_status_email.html.haml
@@ -1,2 +1,3 @@
%p
- Merge Request #{@merge_request.to_reference} was #{@mr_status} by #{sanitize_name(@updated_by.name)}
+ Merge Request #{merge_request_reference_link(@merge_request)}
+ was #{@mr_status} by #{sanitize_name(@updated_by.name)}
diff --git a/app/views/notify/merge_request_status_email.text.haml b/app/views/notify/merge_request_status_email.text.haml
index e3b24bbd405..3d7115856d4 100644
--- a/app/views/notify/merge_request_status_email.text.haml
+++ b/app/views/notify/merge_request_status_email.text.haml
@@ -1,6 +1,6 @@
Merge Request #{@merge_request.to_reference} was #{@mr_status} by #{sanitize_name(@updated_by.name)}
-Merge Request url: #{project_merge_request_url(@merge_request.target_project, @merge_request)}
+Merge Request URL: #{project_merge_request_url(@merge_request.target_project, @merge_request)}
= merge_path_description(@merge_request, 'to')
diff --git a/app/views/notify/merge_request_unmergeable_email.html.haml b/app/views/notify/merge_request_unmergeable_email.html.haml
index 7ec0c1ef390..ee459a26551 100644
--- a/app/views/notify/merge_request_unmergeable_email.html.haml
+++ b/app/views/notify/merge_request_unmergeable_email.html.haml
@@ -1,2 +1,2 @@
%p
- Merge Request #{link_to @merge_request.to_reference, project_merge_request_url(@merge_request.target_project, @merge_request)} can no longer be merged due to conflict.
+ Merge Request #{merge_request_reference_link(@merge_request)} can no longer be merged due to conflict.
diff --git a/app/views/notify/merge_request_unmergeable_email.text.haml b/app/views/notify/merge_request_unmergeable_email.text.haml
index e9708a297d7..412a0887186 100644
--- a/app/views/notify/merge_request_unmergeable_email.text.haml
+++ b/app/views/notify/merge_request_unmergeable_email.text.haml
@@ -1,6 +1,6 @@
Merge Request #{@merge_request.to_reference} can no longer be merged due to conflict.
-Merge Request url: #{project_merge_request_url(@merge_request.target_project, @merge_request)}
+Merge Request URL: #{project_merge_request_url(@merge_request.target_project, @merge_request)}
= merge_path_description(@merge_request, 'to')
diff --git a/app/views/notify/merge_when_pipeline_succeeds_email.html.haml b/app/views/notify/merge_when_pipeline_succeeds_email.html.haml
new file mode 100644
index 00000000000..4db213fb229
--- /dev/null
+++ b/app/views/notify/merge_when_pipeline_succeeds_email.html.haml
@@ -0,0 +1,159 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional //EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+%html{ lang: "en" }
+ %head
+ %meta{ content: "text/html; charset=UTF-8", "http-equiv" => "Content-Type" }
+ %meta{ content: "width=device-width, initial-scale=1", name: "viewport" }
+ %meta{ content: "IE=edge", "http-equiv" => "X-UA-Compatible" }
+ %title= message.subject
+ :css
+ /* CLIENT-SPECIFIC STYLES */
+ body, table, td, a { -webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; }
+ table, td { mso-table-lspace: 0pt; mso-table-rspace: 0pt; }
+ img { -ms-interpolation-mode: bicubic; }
+
+ /* iOS BLUE LINKS */
+ a[x-apple-data-detectors] {
+ color: inherit !important;
+ text-decoration: none !important;
+ font-size: inherit !important;
+ font-family: inherit !important;
+ font-weight: inherit !important;
+ line-height: inherit !important;
+ }
+
+ /* ANDROID MARGIN HACK */
+ body { margin:0 !important; }
+ div[style*="margin: 16px 0"] { margin:0 !important; }
+
+ @media only screen and (max-width: 639px) {
+ body, #body {
+ min-width: 320px !important;
+ }
+ table.wrapper {
+ width: 100% !important;
+ min-width: 320px !important;
+ }
+ table.wrapper > tbody > tr > td {
+ border-left: 0 !important;
+ border-right: 0 !important;
+ border-radius: 0 !important;
+ padding-left: 10px !important;
+ padding-right: 10px !important;
+ }
+ }
+
+ ul.assignees-list {
+ list-style: none;
+ padding: 0px;
+ display: block;
+ margin-top: 0px;
+ }
+ ul.assignees-list li {
+ display: inline-block;
+ padding-right: 12px;
+ padding-top: 8px;
+ }
+
+ %body{ style: "background-color:#fafafa;margin:0;padding:0;text-align:center;min-width:640px;width:100%;height:100%;font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;" }
+ %table#body{ border: "0", cellpadding: "0", cellspacing: "0", style: "background-color:#fafafa;margin:0;padding:0;text-align:center;min-width:640px;width:100%;" }
+ %tbody
+ %tr.line
+ %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;background-color:#6b4fbb;height:4px;font-size:4px;line-height:4px;" }
+ %tr.header
+ %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:25px 0;font-size:13px;line-height:1.6;color:#5c5c5c;" }
+ %img{ alt: "GitLab", height: "50", src: image_url('mailers/ci_pipeline_notif_v1/gitlab-logo.gif'), width: "55" }
+ %tr
+ %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;" }
+ %table.wrapper{ border: "0", cellpadding: "0", cellspacing: "0", style: "width:640px;margin:0 auto;border-collapse:separate;border-spacing:0;" }
+ %tbody
+ %tr
+ %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;background-color:#ffffff;text-align:left;padding:18px 25px;border:1px solid #ededed;border-radius:3px;overflow:hidden;" }
+ %table.content{ border: "0", cellpadding: "0", cellspacing: "0", style: "width:100%;border-collapse:separate;border-spacing:0;" }
+ %tbody
+ %tr.success
+ %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:10px;border-radius:3px;font-size:14px;line-height:1.3;text-align:center;overflow:hidden;color:#ffffff;background-color:#31af64;" }
+ %table.img{ border: "0", cellpadding: "0", cellspacing: "0", style: "border-collapse:collapse;margin:0 auto;" }
+ %tbody
+ %tr
+ %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;vertical-align:middle;color:#ffffff;text-align:center;padding-right:5px;" }
+ %img{ alt: "✓", height: "13", src: image_url('mailers/ci_pipeline_notif_v1/icon-check-green-inverted.gif'), style: "display:block;", width: "13" }
+ %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;vertical-align:middle;color:#ffffff;text-align:center;" }
+ %span= _('Merge request was scheduled to merge after pipeline succeeds')
+ %tr.spacer
+ %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;height:18px;font-size:18px;line-height:18px;" }
+ &nbsp;
+ %tr.section
+ %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;line-height:1.4;text-align:center;padding:0 15px;border:1px solid #ededed;border-radius:3px;overflow:hidden;" }
+ %table.img{ border: "0", cellpadding: "0", cellspacing: "0", style: "border-collapse:collapse;width:100%;" }
+ %tbody
+ %tr{ style: 'width:100%;' }
+ %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;text-align:center;" }
+ %img{ src: image_url('mailers/approval/icon-merge-request-gray.gif'), style: "height:18px;width:18px;margin-bottom:-4px;", alt: "Merge request icon" }
+ %span{ style: "font-weight: 600;color:#333333;" }= _('Merge request')
+ %a{ href: merge_request_url(@merge_request), style: "font-weight: 600;color:#3777b0;text-decoration:none" }= @merge_request.to_reference
+ %span= _('was scheduled to merge after pipeline succeeds by')
+ %img.avatar{ height: "24", src: avatar_icon_for_user(@mwps_set_by, 24, only_path: false), style: "border-radius:12px;margin:-7px 0 -7px 3px;", width: "24", alt: "Avatar" }
+ %a.muted{ href: user_url(@mwps_set_by), style: "color:#333333;text-decoration:none;" }
+ = @mwps_set_by.name
+ %tr.spacer
+ %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;height:18px;font-size:18px;line-height:18px;" }
+ &nbsp;
+ %tr.section
+ %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:0 15px;border:1px solid #ededed;border-radius:3px;overflow:hidden;" }
+ %table.info{ border: "0", cellpadding: "0", cellspacing: "0", style: "width:100%;" }
+ %tbody
+ %tr
+ %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;" }= _('Project')
+ -# haml-lint:disable NoPlainNodes
+ %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;color:#333333;font-weight:400;width:75%;padding-left:5px;" }
+ - namespace_name = @project.group ? @project.group.name : @project.namespace.owner.name
+ - namespace_url = @project.group ? group_url(@project.group) : user_url(@project.namespace.owner)
+ %a.muted{ href: namespace_url, style: "color:#333333;text-decoration:none;" }
+ = namespace_name
+ \/
+ %a.muted{ href: project_url(@project), style: "color:#333333;text-decoration:none;" }
+ = @project.name
+ %tr
+ %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;border-top:1px solid #ededed;" }= _('Branch')
+ %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;color:#333333;font-weight:400;width:75%;padding-left:5px;border-top:1px solid #ededed;" }
+ %table.img{ border: "0", cellpadding: "0", cellspacing: "0", style: "border-collapse:collapse;" }
+ %tbody
+ %tr
+ %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;padding-right:5px;" }
+ %img{ height: "13", src: image_url('mailers/ci_pipeline_notif_v1/icon-branch-gray.gif'), style: "display:block;", width: "13", alt: "Branch icon" }
+ %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;" }
+ %span.muted{ style: "color:#333333;text-decoration:none;" }
+ = @merge_request.source_branch
+ %tr
+ %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;border-top:1px solid #ededed;" }= _('Author')
+ %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;color:#333333;font-weight:400;width:75%;padding-left:5px;border-top:1px solid #ededed;" }
+ %table.img{ border: "0", cellpadding: "0", cellspacing: "0", style: "border-collapse:collapse;" }
+ %tbody
+ %tr
+ %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;padding-right:5px;" }
+ %img.avatar{ height: "24", src: avatar_icon_for_user(@merge_request.author, 24, only_path: false), style: "display:block;border-radius:12px;margin:-2px 0;", width: "24", alt: "Avatar" }
+ %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;" }
+ %a.muted{ href: user_url(@merge_request.author), style: "color:#333333;text-decoration:none;" }
+ = @merge_request.author.name
+
+ - if @merge_request.assignees.any?
+ %tr
+ %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;border-top:1px solid #ededed;" }
+ = assignees_label(@merge_request, include_value: false)
+ %td{ style: "font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; margin: 0; padding: 14px 0 0px 5px; font-size: 15px; line-height: 1.4; color: #333333; font-weight: 400; width: 75%; border-top-style: solid; border-top-color: #ededed; border-top-width: 1px; -webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; mso-table-lspace: 0pt; mso-table-rspace: 0pt;" }
+ %ul.assignees-list{ style: "font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; font-size: 15px; line-height: 1.4; padding-right: 5px; -webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; mso-table-lspace: 0pt; mso-table-rspace: 0pt;" }
+ - @merge_request.assignees.each do |assignee|
+ %li
+ %img.avatar{ alt: "Avatar", height: "24", src: avatar_icon_for_user(assignee, 24, only_path: false), style: "border-radius: 12px; max-width: 100%; height: auto; -ms-interpolation-mode: bicubic; margin: -2px 0;", width: "24" }
+ %a.muted{ href: user_url(assignee), style: "color: #333333; text-decoration: none; -webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; vertical-align: top;" }
+ = assignee.name
+
+ = render_if_exists 'layouts/mailer/additional_text'
+
+ %tr.footer
+ %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:25px 0;font-size:13px;line-height:1.6;color:#5c5c5c;" }
+ %img{ alt: "GitLab", height: "33", src: image_url('mailers/ci_pipeline_notif_v1/gitlab-logo-full-horizontal.gif'), style: "display:block;margin:0 auto 1em;", width: "90" }
+ %div
+ - manage_notifications_link = link_to(_("Manage all notifications"), profile_notifications_url, style: "color:#3777b0;text-decoration:none;")
+ - help_link = link_to(_("Help"), help_url, style: "color:#3777b0;text-decoration:none;")
+ = _("You're receiving this email because of your account on %{host}. %{manage_notifications_link} &middot; %{help_link}").html_safe % { host: Gitlab.config.gitlab.host, manage_notifications_link: manage_notifications_link, help_link: help_link }
diff --git a/app/views/notify/merge_when_pipeline_succeeds_email.text.haml b/app/views/notify/merge_when_pipeline_succeeds_email.text.haml
new file mode 100644
index 00000000000..fdc23a6af0f
--- /dev/null
+++ b/app/views/notify/merge_when_pipeline_succeeds_email.text.haml
@@ -0,0 +1,8 @@
+Merge Request #{@merge_request.to_reference} was scheduled to merge after pipeline succeeds by #{sanitize_name(@mwps_set_by.name)}
+
+Merge Request url: #{project_merge_request_url(@merge_request.target_project, @merge_request)}
+
+= merge_path_description(@merge_request, 'to')
+
+Author: #{sanitize_name(@merge_request.author_name)}
+= assignees_label(@merge_request)
diff --git a/app/views/notify/merged_merge_request_email.html.haml b/app/views/notify/merged_merge_request_email.html.haml
index 341aa6f8103..c84c0d1d14b 100644
--- a/app/views/notify/merged_merge_request_email.html.haml
+++ b/app/views/notify/merged_merge_request_email.html.haml
@@ -1,2 +1,2 @@
%p
- Merge Request #{link_to @merge_request.to_reference, project_merge_request_url(@merge_request.target_project, @merge_request)} was merged
+ Merge Request #{merge_request_reference_link(@merge_request)} was merged
diff --git a/app/views/notify/new_issue_email.html.haml b/app/views/notify/new_issue_email.html.haml
index 52e110a98f6..7f0a50e9248 100644
--- a/app/views/notify/new_issue_email.html.haml
+++ b/app/views/notify/new_issue_email.html.haml
@@ -1,5 +1,5 @@
%p.details
- #{link_to @issue.author_name, user_url(@issue.author)} created an issue #{link_to @issue.to_reference(full: false), issue_url(@issue)}:
+ #{link_to @issue.author_name, user_url(@issue.author)} created an issue #{issue_reference_link(@issue)}:
- if @issue.assignees.any?
%p
diff --git a/app/views/notify/new_mention_in_merge_request_email.html.haml b/app/views/notify/new_mention_in_merge_request_email.html.haml
index b061f9c106e..ddcf287e501 100644
--- a/app/views/notify/new_mention_in_merge_request_email.html.haml
+++ b/app/views/notify/new_mention_in_merge_request_email.html.haml
@@ -1,4 +1,4 @@
%p
- You have been mentioned in Merge Request #{@merge_request.to_reference}
+ You have been mentioned in Merge Request #{merge_request_reference_link(@merge_request)}
= render template: 'notify/new_merge_request_email'
diff --git a/app/views/notify/push_to_merge_request_email.html.haml b/app/views/notify/push_to_merge_request_email.html.haml
index 97258833cfc..3e9f9b442e0 100644
--- a/app/views/notify/push_to_merge_request_email.html.haml
+++ b/app/views/notify/push_to_merge_request_email.html.haml
@@ -1,7 +1,7 @@
%h3
= sanitize_name(@updated_by_user.name)
pushed new commits to merge request
- = link_to(@merge_request.to_reference, project_merge_request_url(@merge_request.target_project, @merge_request))
+ = merge_request_reference_link(@merge_request)
- if @existing_commits.any?
- count = @existing_commits.size
diff --git a/app/views/notify/push_to_merge_request_email.text.haml b/app/views/notify/push_to_merge_request_email.text.haml
index 10c8e158846..5c2005a47e5 100644
--- a/app/views/notify/push_to_merge_request_email.text.haml
+++ b/app/views/notify/push_to_merge_request_email.text.haml
@@ -1,6 +1,6 @@
#{sanitize_name(@updated_by_user.name)} pushed new commits to merge request #{@merge_request.to_reference}
-\
-#{url_for(project_merge_request_url(@merge_request.target_project, @merge_request))}
+
+Merge Request URL: #{project_merge_request_url(@merge_request.target_project, @merge_request)}
\
- if @existing_commits.any?
- count = @existing_commits.size
diff --git a/app/views/notify/resolved_all_discussions_email.html.haml b/app/views/notify/resolved_all_discussions_email.html.haml
index 502b8f21e35..0b3c56c9bd1 100644
--- a/app/views/notify/resolved_all_discussions_email.html.haml
+++ b/app/views/notify/resolved_all_discussions_email.html.haml
@@ -1,2 +1,3 @@
%p
- All discussions on Merge Request #{@merge_request.to_reference} were resolved by #{sanitize_name(@resolved_by.name)}
+ All discussions on Merge Request #{merge_request_reference_link(@merge_request)}
+ were resolved by #{sanitize_name(@resolved_by.name)}
diff --git a/app/views/notify/service_desk_new_note_email.html.haml b/app/views/notify/service_desk_new_note_email.html.haml
new file mode 100644
index 00000000000..7c6be6688d0
--- /dev/null
+++ b/app/views/notify/service_desk_new_note_email.html.haml
@@ -0,0 +1,5 @@
+- if Gitlab::CurrentSettings.email_author_in_body
+ %div
+ #{link_to @note.author_name, user_url(@note.author)} wrote:
+%div
+ = markdown(@note.note, pipeline: :email, author: @note.author)
diff --git a/app/views/notify/service_desk_new_note_email.text.erb b/app/views/notify/service_desk_new_note_email.text.erb
new file mode 100644
index 00000000000..208953a437d
--- /dev/null
+++ b/app/views/notify/service_desk_new_note_email.text.erb
@@ -0,0 +1,6 @@
+New response for issue #<%= @issue.iid %>:
+
+Author: <%= sanitize_name(@note.author_name) %>
+
+<%= @note.note %>
+<%# EE-specific start %><%= render_if_exists 'layouts/mailer/additional_text'%><%# EE-specific end %>
diff --git a/app/views/notify/service_desk_thank_you_email.html.haml b/app/views/notify/service_desk_thank_you_email.html.haml
new file mode 100644
index 00000000000..a3407acd9ba
--- /dev/null
+++ b/app/views/notify/service_desk_thank_you_email.html.haml
@@ -0,0 +1,2 @@
+%p
+ Thank you for your support request! We are tracking your request as ticket ##{@issue.iid}, and will respond as soon as we can.
diff --git a/app/views/notify/service_desk_thank_you_email.text.erb b/app/views/notify/service_desk_thank_you_email.text.erb
new file mode 100644
index 00000000000..8281607a4a8
--- /dev/null
+++ b/app/views/notify/service_desk_thank_you_email.text.erb
@@ -0,0 +1,6 @@
+Thank you for your support request! We are tracking your request as ticket #<%= @issue.iid %>, and will respond as soon as we can.
+
+To unsubscribe from this issue, please paste the following link into your browser:
+
+<%= @unsubscribe_url %>
+<%# EE-specific start %><%= render_if_exists 'layouts/mailer/additional_text' %><%# EE-specific end %>
diff --git a/app/views/profiles/_event_table.html.haml b/app/views/profiles/_event_table.html.haml
index c65c4fd0d81..b952868e4e3 100644
--- a/app/views/profiles/_event_table.html.haml
+++ b/app/views/profiles/_event_table.html.haml
@@ -5,7 +5,7 @@
- events.each do |event|
%li
%span.description
- = audit_icon(event.details[:with], class: "append-right-5")
+ = audit_icon(event.details[:with], class: "gl-mr-2")
= _('Signed in with %{authentication} authentication') % { authentication: event.details[:with]}
%span.float-right= time_ago_with_tooltip(event.created_at)
diff --git a/app/views/profiles/accounts/show.html.haml b/app/views/profiles/accounts/show.html.haml
index f4a97206a19..ea2f888c129 100644
--- a/app/views/profiles/accounts/show.html.haml
+++ b/app/views/profiles/accounts/show.html.haml
@@ -5,7 +5,7 @@
.alert.alert-info
= s_('Profiles|Some options are unavailable for LDAP accounts')
-.row.prepend-top-default
+.row.gl-mt-3
.col-lg-4.profile-settings-sidebar
%h4.gl-mt-0
= s_('Profiles|Two-Factor Authentication')
@@ -22,7 +22,7 @@
%hr
- if display_providers_on_profile?
- .row.prepend-top-default
+ .row.gl-mt-3
.col-lg-4.profile-settings-sidebar
%h4.gl-mt-0
= s_('Profiles|Social sign-in')
@@ -32,7 +32,7 @@
= render 'providers', providers: button_based_providers, group_saml_identities: local_assigns[:group_saml_identities]
%hr
- if current_user.can_change_username?
- .row.prepend-top-default
+ .row.gl-mt-3
.col-lg-4.profile-settings-sidebar
%h4.gl-mt-0.warning-title
= s_('Profiles|Change username')
@@ -45,7 +45,7 @@
#update-username{ data: data }
%hr
-.row.prepend-top-default
+.row.gl-mt-3
.col-lg-4.profile-settings-sidebar
%h4.gl-mt-0.danger-title
= s_('Profiles|Delete account')
@@ -72,4 +72,4 @@
- else
%p
= s_("Profiles|You don't have access to delete this user.")
-.append-bottom-default
+.gl-mb-3
diff --git a/app/views/profiles/active_sessions/_active_session.html.haml b/app/views/profiles/active_sessions/_active_session.html.haml
index f3ad0c4c8ad..9ae75fe6b8e 100644
--- a/app/views/profiles/active_sessions/_active_session.html.haml
+++ b/app/views/profiles/active_sessions/_active_session.html.haml
@@ -1,7 +1,7 @@
- is_current_session = active_session.current?(session)
%li.list-group-item
- .float-left.append-right-10{ data: { toggle: 'tooltip' }, title: active_session.human_device_type }
+ .float-left.gl-mr-3{ data: { toggle: 'tooltip' }, title: active_session.human_device_type }
= active_session_device_type_icon(active_session)
.description.float-left
@@ -27,6 +27,6 @@
- unless is_current_session
.float-right
- = link_to profile_active_session_path(active_session.public_id), data: { confirm: _('Are you sure? The device will be signed out of GitLab.') }, method: :delete, class: "btn btn-danger prepend-left-10" do
+ = link_to profile_active_session_path(active_session.public_id), data: { confirm: _('Are you sure? The device will be signed out of GitLab.') }, method: :delete, class: "btn btn-danger gl-ml-3" do
%span.sr-only= _('Revoke')
= _('Revoke')
diff --git a/app/views/profiles/active_sessions/index.html.haml b/app/views/profiles/active_sessions/index.html.haml
index 6d01d055f0c..f444f236cfc 100644
--- a/app/views/profiles/active_sessions/index.html.haml
+++ b/app/views/profiles/active_sessions/index.html.haml
@@ -1,14 +1,14 @@
- page_title _('Active Sessions')
- @content_class = "limit-container-width" unless fluid_layout
-.row.prepend-top-default
+.row.gl-mt-3
.col-lg-4.profile-settings-sidebar
%h4.gl-mt-0
= page_title
%p
= _('This is a list of devices that have logged into your account. Revoke any sessions that you do not recognize.')
.col-lg-8
- .append-bottom-default
+ .gl-mb-3
.card.border-0
%ul.list-group.list-group-flush
diff --git a/app/views/profiles/audit_log.html.haml b/app/views/profiles/audit_log.html.haml
index 02aadcc5c8b..aec855c790e 100644
--- a/app/views/profiles/audit_log.html.haml
+++ b/app/views/profiles/audit_log.html.haml
@@ -1,7 +1,7 @@
- page_title _('Authentication log')
- @content_class = "limit-container-width" unless fluid_layout
-.row.prepend-top-default
+.row.gl-mt-3
.col-lg-4.profile-settings-sidebar
%h4.gl-mt-0
= page_title
diff --git a/app/views/profiles/chat_names/index.html.haml b/app/views/profiles/chat_names/index.html.haml
index 05870e0e221..e0b0f839455 100644
--- a/app/views/profiles/chat_names/index.html.haml
+++ b/app/views/profiles/chat_names/index.html.haml
@@ -1,7 +1,7 @@
- page_title _('Chat')
- @content_class = "limit-container-width" unless fluid_layout
-.row.prepend-top-default
+.row.gl-mt-3
.col-lg-4.profile-settings-sidebar
%h4.gl-mt-0
= page_title
diff --git a/app/views/profiles/chat_names/new.html.haml b/app/views/profiles/chat_names/new.html.haml
index d86941b7a29..5bed9e0d771 100644
--- a/app/views/profiles/chat_names/new.html.haml
+++ b/app/views/profiles/chat_names/new.html.haml
@@ -12,4 +12,4 @@
= submit_tag "Authorize", class: "btn btn-success wide float-left"
= form_tag deny_profile_chat_names_path, method: :delete do
= hidden_field_tag :token, @chat_name_token.token
- = submit_tag "Deny", class: "btn btn-danger prepend-left-10"
+ = submit_tag "Deny", class: "btn btn-danger gl-ml-3"
diff --git a/app/views/profiles/emails/index.html.haml b/app/views/profiles/emails/index.html.haml
index e90bda0e187..fa7ab0666cc 100644
--- a/app/views/profiles/emails/index.html.haml
+++ b/app/views/profiles/emails/index.html.haml
@@ -1,7 +1,7 @@
- page_title _('Emails')
- @content_class = "limit-container-width" unless fluid_layout
-.row.prepend-top-default
+.row.gl-mt-3
.col-lg-4.profile-settings-sidebar
%h4.gl-mt-0
= page_title
@@ -14,12 +14,12 @@
.form-group
= f.label :email, _('Email'), class: 'label-bold'
= f.text_field :email, class: 'form-control', data: { qa_selector: 'email_address_field' }
- .prepend-top-default
+ .gl-mt-3
= f.submit _('Add email address'), class: 'btn btn-success', data: { qa_selector: 'add_email_address_button' }
%hr
%h4.gl-mt-0
= _('Linked emails (%{email_count})') % { email_count: @emails.load.size + 1 }
- .account-well.append-bottom-default
+ .account-well.gl-mb-3
%ul
%li
= _('Your Primary Email will be used for avatar detection.')
@@ -56,8 +56,8 @@
%span.badge.badge-info= s_('Profiles|Notification email')
- unless email.confirmed?
- confirm_title = "#{email.confirmation_sent_at ? _('Resend confirmation email') : _('Send confirmation email')}"
- = link_to confirm_title, resend_confirmation_instructions_profile_email_path(email), method: :put, class: 'btn btn-sm btn-warning prepend-left-10'
+ = link_to confirm_title, resend_confirmation_instructions_profile_email_path(email), method: :put, class: 'btn btn-sm btn-warning gl-ml-3'
- = link_to profile_email_path(email), data: { confirm: _('Are you sure?'), qa_selector: 'delete_email_link'}, method: :delete, class: 'btn btn-sm btn-danger prepend-left-10' do
+ = link_to profile_email_path(email), data: { confirm: _('Are you sure?'), qa_selector: 'delete_email_link'}, method: :delete, class: 'btn btn-sm btn-danger gl-ml-3' do
%span.sr-only= _('Remove')
= icon('trash')
diff --git a/app/views/profiles/gpg_keys/_form.html.haml b/app/views/profiles/gpg_keys/_form.html.haml
index 225487b2638..2fb07adc006 100644
--- a/app/views/profiles/gpg_keys/_form.html.haml
+++ b/app/views/profiles/gpg_keys/_form.html.haml
@@ -6,5 +6,5 @@
= f.label :key, s_('Profiles|Key'), class: 'label-bold'
= f.text_area :key, class: "form-control", rows: 8, required: true, placeholder: _("Don't paste the private part of the GPG key. Paste the public part which begins with '-----BEGIN PGP PUBLIC KEY BLOCK-----'.")
- .prepend-top-default
+ .gl-mt-3
= f.submit s_('Profiles|Add key'), class: "btn btn-success"
diff --git a/app/views/profiles/gpg_keys/_key.html.haml b/app/views/profiles/gpg_keys/_key.html.haml
index 2de5cf2f506..7bbb0235cd8 100644
--- a/app/views/profiles/gpg_keys/_key.html.haml
+++ b/app/views/profiles/gpg_keys/_key.html.haml
@@ -1,5 +1,5 @@
%li.key-list-item
- .float-left.append-right-10
+ .float-left.gl-mr-3
= icon 'key', class: "settings-list-icon d-none d-sm-block"
.key-list-item-info
- key.emails_with_verified_status.map do |email, verified|
@@ -19,9 +19,9 @@
.float-right
%span.key-created-at
= s_('Profiles|Created %{time_ago}'.html_safe) % { time_ago:time_ago_with_tooltip(key.created_at)}
- = link_to profile_gpg_key_path(key), data: { confirm: _('Are you sure? Removing this GPG key does not affect already signed commits.') }, method: :delete, class: "btn btn-danger prepend-left-10" do
+ = link_to profile_gpg_key_path(key), data: { confirm: _('Are you sure? Removing this GPG key does not affect already signed commits.') }, method: :delete, class: "btn btn-danger gl-ml-3" do
%span.sr-only= _('Remove')
= icon('trash')
- = link_to revoke_profile_gpg_key_path(key), data: { confirm: _('Are you sure? All commits that were signed with this GPG key will be unverified.') }, method: :put, class: "btn btn-danger prepend-left-10" do
+ = link_to revoke_profile_gpg_key_path(key), data: { confirm: _('Are you sure? All commits that were signed with this GPG key will be unverified.') }, method: :put, class: "btn btn-danger gl-ml-3" do
%span.sr-only= _('Revoke')
= _('Revoke')
diff --git a/app/views/profiles/gpg_keys/index.html.haml b/app/views/profiles/gpg_keys/index.html.haml
index 31610e7505b..053cb3547ba 100644
--- a/app/views/profiles/gpg_keys/index.html.haml
+++ b/app/views/profiles/gpg_keys/index.html.haml
@@ -1,7 +1,7 @@
- page_title _('GPG Keys')
- @content_class = "limit-container-width" unless fluid_layout
-.row.prepend-top-default
+.row.gl-mt-3
.col-lg-4.profile-settings-sidebar
%h4.gl-mt-0
= page_title
@@ -17,5 +17,5 @@
%hr
%h5
= _('Your GPG keys (%{count})') % { count:@gpg_keys.count}
- .append-bottom-default
+ .gl-mb-3
= render 'key_table'
diff --git a/app/views/profiles/keys/_form.html.haml b/app/views/profiles/keys/_form.html.haml
index 7709aa8f4b9..078b5907623 100644
--- a/app/views/profiles/keys/_form.html.haml
+++ b/app/views/profiles/keys/_form.html.haml
@@ -10,7 +10,7 @@
.col.form-group
= f.label :title, _('Title'), class: 'label-bold'
= f.text_field :title, class: "form-control input-lg qa-key-title-field", required: true, placeholder: s_('Profiles|e.g. My MacBook key')
- %p.form-text.text-muted= s_('Profiles|Give your individual key a title')
+ %p.form-text.text-muted= s_('Profiles|Give your individual key a title. This will be publically visible.')
.col.form-group
= f.label :expires_at, s_('Profiles|Expires at'), class: 'label-bold'
@@ -23,5 +23,5 @@
%button.btn.btn-success.js-add-ssh-key-validation-confirm-submit= _("Yes, add it")
- .prepend-top-default
+ .gl-mt-3
= f.submit s_('Profiles|Add key'), class: "btn btn-success js-add-ssh-key-validation-original-submit qa-add-key-button"
diff --git a/app/views/profiles/keys/_key.html.haml b/app/views/profiles/keys/_key.html.haml
index b227041c9de..c9ab7b6fbd3 100644
--- a/app/views/profiles/keys/_key.html.haml
+++ b/app/views/profiles/keys/_key.html.haml
@@ -1,5 +1,5 @@
%li.d-flex.align-items-center.key-list-item
- .append-right-10
+ .gl-mr-3
- if key.valid?
- if key.expired?
%span.d-inline-block.has-tooltip{ title: s_('Profiles|Your key has expired') }
@@ -17,15 +17,15 @@
= key.fingerprint
.key-list-item-dates.d-flex.align-items-start.justify-content-between
- %span.last-used-at.append-right-10
+ %span.last-used-at.gl-mr-3
= s_('Profiles|Last used:')
= key.last_used_at ? time_ago_with_tooltip(key.last_used_at) : _('Never')
- %span.expires.append-right-10
+ %span.expires.gl-mr-3
= s_('Profiles|Expires:')
= key.expires_at ? key.expires_at.to_date : _('Never')
%span.key-created-at
= s_('Profiles|Created %{time_ago}'.html_safe) % { time_ago:time_ago_with_tooltip(key.created_at)}
- if key.can_delete?
- = link_to path_to_key(key, is_admin), data: { confirm: _('Are you sure?')}, method: :delete, class: "btn btn-transparent prepend-left-10 align-baseline" do
+ = link_to path_to_key(key, is_admin), data: { confirm: _('Are you sure?')}, method: :delete, class: "btn btn-transparent gl-ml-3 align-baseline" do
%span.sr-only= _('Remove')
= sprite_icon('remove', size: 16)
diff --git a/app/views/profiles/keys/_key_details.html.haml b/app/views/profiles/keys/_key_details.html.haml
index 88deb0f11cb..59d953678e7 100644
--- a/app/views/profiles/keys/_key_details.html.haml
+++ b/app/views/profiles/keys/_key_details.html.haml
@@ -1,5 +1,5 @@
- is_admin = defined?(admin) ? true : false
-.row.prepend-top-default
+.row.gl-mt-3
.col-md-4
.card
.card-header
diff --git a/app/views/profiles/keys/index.html.haml b/app/views/profiles/keys/index.html.haml
index 788c67b3704..7b7c24f3ac8 100644
--- a/app/views/profiles/keys/index.html.haml
+++ b/app/views/profiles/keys/index.html.haml
@@ -1,7 +1,7 @@
- page_title _('SSH Keys')
- @content_class = "limit-container-width" unless fluid_layout
-.row.prepend-top-default
+.row.gl-mt-3
.col-lg-4.profile-settings-sidebar
%h4.gl-mt-0
= page_title
@@ -12,7 +12,7 @@
= _('Add an SSH key')
%p.profile-settings-content
- generate_link_url = help_page_path("ssh/README", anchor: 'generating-a-new-ssh-key-pair')
- - existing_link_url = help_page_path("ssh/README", anchor: 'locating-an-existing-ssh-key-pair')
+ - existing_link_url = help_page_path("ssh/README", anchor: 'review-existing-ssh-keys')
- generate_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: generate_link_url }
- existing_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: existing_link_url }
= _('To add an SSH key you need to %{generate_link_start}generate one%{link_end} or use an %{existing_link_start}existing key%{link_end}.').html_safe % { generate_link_start: generate_link_start, existing_link_start: existing_link_start, link_end: '</a>'.html_safe }
@@ -20,5 +20,5 @@
%hr
%h5
= _('Your SSH keys (%{count})') % { count:@keys.count }
- .append-bottom-default
+ .gl-mb-3
= render 'key_table'
diff --git a/app/views/profiles/notifications/_group_settings.html.haml b/app/views/profiles/notifications/_group_settings.html.haml
index a25cd78fb0b..404bb224655 100644
--- a/app/views/profiles/notifications/_group_settings.html.haml
+++ b/app/views/profiles/notifications/_group_settings.html.haml
@@ -2,7 +2,7 @@
.gl-responsive-table-row.notification-list-item
.table-section.section-40
- %span.notification.fa.fa-holder.append-right-5
+ %span.notification.fa.fa-holder.gl-mr-2
= notification_icon(notification_icon_level(setting, emails_disabled))
%span.str-truncated
diff --git a/app/views/profiles/notifications/_project_settings.html.haml b/app/views/profiles/notifications/_project_settings.html.haml
index 63a77b335b6..f9172ae87aa 100644
--- a/app/views/profiles/notifications/_project_settings.html.haml
+++ b/app/views/profiles/notifications/_project_settings.html.haml
@@ -1,7 +1,7 @@
- emails_disabled = project.emails_disabled?
%li.notification-list-item
- %span.notification.fa.fa-holder.append-right-5
+ %span.notification.fa.fa-holder.gl-mr-2
= notification_icon(notification_icon_level(setting, emails_disabled))
%span.str-truncated
diff --git a/app/views/profiles/notifications/show.html.haml b/app/views/profiles/notifications/show.html.haml
index 498f80aed2b..ab04d977a4d 100644
--- a/app/views/profiles/notifications/show.html.haml
+++ b/app/views/profiles/notifications/show.html.haml
@@ -9,7 +9,7 @@
%li= msg
= hidden_field_tag :notification_type, 'global'
- .row.prepend-top-default
+ .row.gl-mt-3
.col-lg-4.profile-settings-sidebar
%h4.gl-mt-0
= page_title
@@ -21,7 +21,7 @@
%h5.gl-mt-0
= _('Global notification settings')
- = form_for @user, url: profile_notifications_path, method: :put, html: { class: 'update-notifications prepend-top-default' } do |f|
+ = form_for @user, url: profile_notifications_path, method: :put, html: { class: 'update-notifications gl-mt-3' } do |f|
= render_if_exists 'profiles/notifications/email_settings', form: f
= label_tag :global_notification_level, "Global notification level", class: "label-bold"
@@ -47,7 +47,7 @@
= _('Projects (%{count})') % { count: @project_notifications.size }
%p.account-well
= _('To specify the notification level per project of a group you belong to, you need to visit project page and change notification level there.')
- .append-bottom-default
+ .gl-mb-3
%ul.bordered-list
- @project_notifications.each do |setting|
= render 'project_settings', setting: setting, project: setting.source
diff --git a/app/views/profiles/passwords/edit.html.haml b/app/views/profiles/passwords/edit.html.haml
index 9deaf7f84be..fe16c2e2f28 100644
--- a/app/views/profiles/passwords/edit.html.haml
+++ b/app/views/profiles/passwords/edit.html.haml
@@ -2,7 +2,7 @@
- page_title _('Password')
- @content_class = "limit-container-width" unless fluid_layout
-.row.prepend-top-default
+.row.gl-mt-3
.col-lg-4.profile-settings-sidebar
%h4.gl-mt-0
= page_title
@@ -29,7 +29,7 @@
.form-group
= f.label :password_confirmation, _('Password confirmation'), class: 'label-bold'
= f.password_field :password_confirmation, required: true, class: 'form-control', data: { qa_selector: 'confirm_password_field' }
- .prepend-top-default.append-bottom-default
- = f.submit _('Save password'), class: "btn btn-success append-right-10", data: { qa_selector: 'save_password_button' }
+ .gl-mt-3.gl-mb-3
+ = f.submit _('Save password'), class: "btn btn-success gl-mr-3", data: { qa_selector: 'save_password_button' }
- unless @user.password_automatically_set?
= link_to _('I forgot my password'), reset_profile_password_path, method: :put
diff --git a/app/views/profiles/personal_access_tokens/index.html.haml b/app/views/profiles/personal_access_tokens/index.html.haml
index 769502e0026..11750f2a6d5 100644
--- a/app/views/profiles/personal_access_tokens/index.html.haml
+++ b/app/views/profiles/personal_access_tokens/index.html.haml
@@ -4,7 +4,7 @@
- type_plural = _('personal access tokens')
- @content_class = 'limit-container-width' unless fluid_layout
-.row.prepend-top-default
+.row.gl-mt-3
.col-lg-4.profile-settings-sidebar
%h4.gl-mt-0
= page_title
@@ -33,7 +33,7 @@
revoke_route_helper: ->(token) { revoke_profile_personal_access_token_path(token) }
%hr
-.row.prepend-top-default
+.row.gl-mt-3
.col-lg-4.profile-settings-sidebar
%h4.gl-mt-0
= s_('AccessTokens|Feed token')
@@ -51,7 +51,7 @@
- if incoming_email_token_enabled?
%hr
- .row.prepend-top-default
+ .row.gl-mt-3
.col-lg-4.profile-settings-sidebar
%h4.gl-mt-0
= s_('AccessTokens|Incoming email token')
@@ -69,7 +69,7 @@
- if static_objects_external_storage_enabled?
%hr
- .row.prepend-top-default
+ .row.gl-mt-3
.col-lg-4
%h4.gl-mt-0
= s_('AccessTokens|Static object token')
diff --git a/app/views/profiles/preferences/show.html.haml b/app/views/profiles/preferences/show.html.haml
index cc44d137848..659b3066603 100644
--- a/app/views/profiles/preferences/show.html.haml
+++ b/app/views/profiles/preferences/show.html.haml
@@ -1,18 +1,19 @@
- page_title _('Preferences')
- @content_class = "limit-container-width" unless fluid_layout
-= form_for @user, url: profile_preferences_path, remote: true, method: :put, html: { class: 'row prepend-top-default js-preferences-form' } do |f|
+= form_for @user, url: profile_preferences_path, remote: true, method: :put, html: { class: 'row gl-mt-3 js-preferences-form' } do |f|
.col-lg-4.application-theme
%h4.gl-mt-0
= s_('Preferences|Navigation theme')
%p
= s_('Preferences|Customize the appearance of the application header and navigation sidebar.')
.col-lg-8.application-theme
- - Gitlab::Themes.each do |theme|
- = label_tag do
- .preview{ class: theme.css_class }
- = f.radio_button :theme_id, theme.id, checked: Gitlab::Themes.for_user(@user).id == theme.id
- = theme.name
+ .row
+ - Gitlab::Themes.each do |theme|
+ %label.col-6.col-sm-4.col-md-3.gl-mb-5.gl-text-center
+ .preview{ class: theme.css_class }
+ = f.radio_button :theme_id, theme.id, checked: Gitlab::Themes.for_user(@user).id == theme.id
+ = theme.name
.col-sm-12
%hr
@@ -69,6 +70,13 @@
= f.check_box :show_whitespace_in_diffs, class: 'form-check-input'
= f.label :show_whitespace_in_diffs, class: 'form-check-label' do
= s_('Preferences|Show whitespace changes in diffs')
+ - if Feature.enabled?(:view_diffs_file_by_file)
+ .form-group.form-check
+ = f.check_box :view_diffs_file_by_file, class: 'form-check-input'
+ = f.label :view_diffs_file_by_file, class: 'form-check-label' do
+ = s_("Preferences|Show one file at a time on merge request's Changes tab")
+ .form-text.text-muted
+ = s_("Preferences|Instead of all the files changed, show only one file at a time. To switch between files, use the file browser.")
.form-group
= f.label :tab_width, s_('Preferences|Tab width'), class: 'label-bold'
= f.number_field :tab_width,
diff --git a/app/views/profiles/show.html.haml b/app/views/profiles/show.html.haml
index 78fdcdef3c4..f4aa0b98e37 100644
--- a/app/views/profiles/show.html.haml
+++ b/app/views/profiles/show.html.haml
@@ -1,8 +1,9 @@
- breadcrumb_title s_("Profiles|Edit Profile")
+- page_title s_("Profiles|Edit Profile")
- @content_class = "limit-container-width" unless fluid_layout
- gravatar_link = link_to Gitlab.config.gravatar.host, 'https://' + Gitlab.config.gravatar.host
-= bootstrap_form_for @user, url: profile_path, method: :put, html: { multipart: true, class: 'edit-user prepend-top-default js-quick-submit gl-show-field-errors' }, authenticity_token: true do |f|
+= bootstrap_form_for @user, url: profile_path, method: :put, html: { multipart: true, class: 'edit-user gl-mt-3 js-quick-submit gl-show-field-errors' }, authenticity_token: true do |f|
= form_errors(@user)
.row
@@ -24,13 +25,13 @@
.md
= brand_profile_image_guidelines
.col-lg-8
- .clearfix.avatar-image.append-bottom-default
+ .clearfix.avatar-image.gl-mb-3
= link_to avatar_icon_for_user(@user, 400), target: '_blank', rel: 'noopener noreferrer' do
= image_tag avatar_icon_for_user(@user, 160), alt: '', class: 'avatar s160'
%h5.gl-mt-0= s_("Profiles|Upload new avatar")
- .prepend-top-5.append-bottom-10
+ .gl-mt-2.append-bottom-10
%button.btn.js-choose-user-avatar-button{ type: 'button' }= s_("Profiles|Choose file...")
- %span.avatar-file-name.prepend-left-default.js-avatar-filename= s_("Profiles|No file chosen")
+ %span.avatar-file-name.gl-ml-3.js-avatar-filename= s_("Profiles|No file chosen")
= f.file_field_without_bootstrap :avatar, class: 'js-user-avatar-input hidden', accept: 'image/*'
.form-text.text-muted= s_("Profiles|The maximum file size allowed is 200KB.")
- if @user.avatar?
@@ -117,7 +118,7 @@
= f.check_box :include_private_contributions, label: s_('Profiles|Include private contributions on my profile'), wrapper_class: 'mb-2', inline: true
.help-block
= s_("Profiles|Choose to show contributions of private projects on your public profile without any project, repository or organization information")
- .prepend-top-default.append-bottom-default
+ .gl-mt-3.gl-mb-3
= f.submit s_("Profiles|Update profile settings"), class: 'btn btn-success'
= link_to _("Cancel"), user_path(current_user), class: 'btn btn-cancel'
diff --git a/app/views/profiles/two_factor_auths/_codes.html.haml b/app/views/profiles/two_factor_auths/_codes.html.haml
index be0af977011..68cd4875a33 100644
--- a/app/views/profiles/two_factor_auths/_codes.html.haml
+++ b/app/views/profiles/two_factor_auths/_codes.html.haml
@@ -9,5 +9,5 @@
%span.monospace= code
.d-flex
- = link_to _('Proceed'), profile_account_path, class: 'btn btn-success append-right-10'
+ = link_to _('Proceed'), profile_account_path, class: 'btn btn-success gl-mr-3', data: { qa_selector: 'proceed_button' }
= link_to _('Download codes'), "data:text/plain;charset=utf-8,#{CGI.escape(@codes.join("\n"))}", download: "gitlab-recovery-codes.txt", class: 'btn btn-default'
diff --git a/app/views/profiles/two_factor_auths/show.html.haml b/app/views/profiles/two_factor_auths/show.html.haml
index 7e566361848..0fde3e5fb10 100644
--- a/app/views/profiles/two_factor_auths/show.html.haml
+++ b/app/views/profiles/two_factor_auths/show.html.haml
@@ -3,7 +3,7 @@
- @content_class = "limit-container-width" unless fluid_layout
.js-two-factor-auth{ 'data-two-factor-skippable' => "#{two_factor_skippable?}", 'data-two_factor_skip_url' => skip_profile_two_factor_auth_path }
- .row.prepend-top-default
+ .row.gl-mt-3
.col-lg-4
%h4.gl-mt-0
= _('Register Two-Factor Authenticator')
@@ -19,7 +19,7 @@
= link_to _('Disable two-factor authentication'), profile_two_factor_auth_path,
method: :delete,
data: { confirm: _('Are you sure? This will invalidate your registered applications and U2F devices.') },
- class: 'btn btn-danger append-right-10'
+ class: 'btn btn-danger gl-mr-3'
= form_tag codes_profile_two_factor_auth_path, {style: 'display: inline-block', method: :post} do |f|
= submit_tag _('Regenerate recovery codes'), class: 'btn'
@@ -39,7 +39,7 @@
= _('To add the entry manually, provide the following details to the application on your phone.')
%p.gl-mt-0.gl-mb-0
= _('Account: %{account}') % { account: @account_string }
- %p.gl-mt-0.gl-mb-0
+ %p.gl-mt-0.gl-mb-0{ data: { qa_selector: 'otp_secret_content' } }
= _('Key: %{key}') %{ key: current_user.otp_secret.scan(/.{4}/).join(' ') }
%p.two-factor-new-manual-content
= _('Time based: Yes')
@@ -49,13 +49,13 @@
= @error
.form-group
= label_tag :pin_code, _('Pin code'), class: "label-bold"
- = text_field_tag :pin_code, nil, class: "form-control", required: true
- .prepend-top-default
- = submit_tag _('Register with two-factor app'), class: 'btn btn-success'
+ = text_field_tag :pin_code, nil, class: "form-control", required: true, data: { qa_selector: 'pin_code_field' }
+ .gl-mt-3
+ = submit_tag _('Register with two-factor app'), class: 'btn btn-success', data: { qa_selector: 'register_2fa_app_button' }
%hr
- .row.prepend-top-default
+ .row.gl-mt-3
.col-lg-4
%h4.gl-mt-0
= _('Register Universal Two-Factor (U2F) Device')
diff --git a/app/views/projects/_files.html.haml b/app/views/projects/_files.html.haml
index 20d4084f428..1562cc065f1 100644
--- a/app/views/projects/_files.html.haml
+++ b/app/views/projects/_files.html.haml
@@ -1,27 +1,22 @@
- is_project_overview = local_assigns.fetch(:is_project_overview, false)
-- commit = local_assigns.fetch(:commit) { @repository.commit }
- ref = local_assigns.fetch(:ref) { current_ref }
- project = local_assigns.fetch(:project) { @project }
-- content_url = local_assigns.fetch(:content_url) { @tree.readme ? project_blob_path(@project, tree_join(@ref, @tree.readme.path)) : project_tree_path(@project, @ref) }
- show_auto_devops_callout = show_auto_devops_callout?(@project)
+- add_page_startup_api_call logs_file_project_ref_path(@project, ref, @path, format: "json", offset: 0)
+- if @tree.readme
+ - add_page_startup_api_call project_blob_path(@project, tree_join(@ref, @tree.readme.path), viewer: "rich", format: "json")
#tree-holder.tree-holder.clearfix
.nav-block
= render 'projects/tree/tree_header', tree: @tree
- - if vue_file_list_enabled?
- #js-last-commit
- - elsif commit
- = render 'shared/commit_well', commit: commit, ref: ref, project: project
+ #js-last-commit
- if is_project_overview
- .project-buttons.append-bottom-default{ class: ("js-show-on-project-root" if vue_file_list_enabled?) }
+ .project-buttons.gl-mb-3.js-show-on-project-root
= render 'stat_anchor_list', anchors: @project.statistics_buttons(show_auto_devops_callout: show_auto_devops_callout)
- - if vue_file_list_enabled?
- #js-tree-list{ data: vue_file_list_data(project, ref) }
- - if can_edit_tree?
- = render 'projects/blob/upload', title: _('Upload New File'), placeholder: _('Upload New File'), button_title: _('Upload file'), form_path: project_create_blob_path(@project, @id), method: :post
- = render 'projects/blob/new_dir'
- - else
- = render 'projects/tree/tree_content', tree: @tree, content_url: content_url
+ #js-tree-list{ data: vue_file_list_data(project, ref) }
+ - if can_edit_tree?
+ = render 'projects/blob/upload', title: _('Upload New File'), placeholder: _('Upload New File'), button_title: _('Upload file'), form_path: project_create_blob_path(@project, @id), method: :post
+ = render 'projects/blob/new_dir'
diff --git a/app/views/projects/_flash_messages.html.haml b/app/views/projects/_flash_messages.html.haml
index 4739689b419..ab8275ba5e4 100644
--- a/app/views/projects/_flash_messages.html.haml
+++ b/app/views/projects/_flash_messages.html.haml
@@ -9,4 +9,4 @@
= render 'shared/auto_devops_implicitly_enabled_banner', project: project
= render_if_exists 'projects/above_size_limit_warning', project: project
= render_if_exists 'shared/shared_runners_minutes_limit', project: project, classes: [container_class, ("limit-container-width" unless fluid_layout)]
- = render 'shared/namespace_storage_limit_alert', namespace: project.namespace, classes: [container_class, ("limit-container-width" unless fluid_layout)]
+ = render_if_exists 'shared/namespace_storage_limit_alert', namespace: project.namespace, classes: [container_class, ("limit-container-width" unless fluid_layout)]
diff --git a/app/views/projects/_home_panel.html.haml b/app/views/projects/_home_panel.html.haml
index 6f8375f80be..9966baf78f4 100644
--- a/app/views/projects/_home_panel.html.haml
+++ b/app/views/projects/_home_panel.html.haml
@@ -3,14 +3,14 @@
- max_project_topic_length = 15
- emails_disabled = @project.emails_disabled?
-.project-home-panel{ class: [("empty-project" if empty_repo), ("js-show-on-project-root" if vue_file_list_enabled?)] }
+.project-home-panel.js-show-on-project-root{ class: [("empty-project" if empty_repo)] }
.row.gl-mb-3
.home-panel-title-row.col-md-12.col-lg-6.d-flex
- .avatar-container.rect-avatar.s64.home-panel-avatar.append-right-default.float-none
+ .avatar-container.rect-avatar.s64.home-panel-avatar.gl-mr-3.float-none
= project_icon(@project, alt: @project.name, class: 'avatar avatar-tile s64', width: 64, height: 64)
.d-flex.flex-column.flex-wrap.align-items-baseline
.d-inline-flex.align-items-baseline
- %h1.home-panel-title.gl-mt-3.append-bottom-5{ data: { qa_selector: 'project_name_content' } }
+ %h1.home-panel-title.gl-mt-3.gl-mb-2{ data: { qa_selector: 'project_name_content' } }
= @project.name
%span.visibility-icon.text-secondary.gl-ml-2.has-tooltip{ data: { container: 'body' }, title: visibility_icon_description(@project) }
= visibility_level_icon(@project.visibility_level, fw: false, options: {class: 'icon'})
@@ -24,10 +24,10 @@
= render 'shared/members/access_request_links', source: @project
- if @project.tag_list.present?
%span.home-panel-topic-list.mt-2.w-100.d-inline-flex
- = sprite_icon('tag', size: 16, css_class: 'icon append-right-4')
+ = sprite_icon('tag', size: 16, css_class: 'icon gl-mr-2')
- @project.topics_to_show.each do |topic|
- - project_topics_classes = "badge badge-pill badge-secondary append-right-5"
+ - project_topics_classes = "badge badge-pill badge-secondary gl-mr-2"
- explore_project_topic_path = explore_projects_path(tag: topic)
- if topic.length > max_project_topic_length
%a{ class: "#{ project_topics_classes } str-truncated-30 has-tooltip", data: { container: "body" }, title: topic, href: explore_project_topic_path }
diff --git a/app/views/projects/_import_project_pane.html.haml b/app/views/projects/_import_project_pane.html.haml
index 3ae37254e39..bb278fbf311 100644
--- a/app/views/projects/_import_project_pane.html.haml
+++ b/app/views/projects/_import_project_pane.html.haml
@@ -9,7 +9,8 @@
- if gitlab_project_import_enabled?
.import_gitlab_project.has-tooltip{ data: { container: 'body' } }
= link_to new_import_gitlab_project_path, class: 'btn btn_import_gitlab_project project-submit', **tracking_attrs(track_label, 'click_button', 'gitlab_export') do
- = icon('gitlab', text: 'GitLab export')
+ = sprite_icon('tanuki')
+ = _("GitLab export")
- if github_import_enabled?
%div
@@ -32,7 +33,8 @@
%div
= link_to status_import_gitlab_path, class: "btn import_gitlab #{'how_to_import_link' unless gitlab_import_configured?}",
**tracking_attrs(track_label, 'click_button', 'gitlab_com') do
- = icon('gitlab', text: 'GitLab.com')
+ = sprite_icon('tanuki')
+ = _("GitLab.com")
- unless gitlab_import_configured?
= render 'projects/gitlab_import_modal'
diff --git a/app/views/projects/_merge_request_settings.html.haml b/app/views/projects/_merge_request_settings.html.haml
index dc3a3fcc647..5ffdeef3558 100644
--- a/app/views/projects/_merge_request_settings.html.haml
+++ b/app/views/projects/_merge_request_settings.html.haml
@@ -4,6 +4,9 @@
= render 'projects/merge_request_merge_options_settings', project: @project, form: form
+- if Feature.enabled?(:squash_options, @project)
+ = render 'projects/merge_request_squash_options_settings', form: form
+
= render 'projects/merge_request_merge_checks_settings', project: @project, form: form
= render 'projects/merge_request_merge_suggestions_settings', project: @project, form: form
diff --git a/app/views/projects/_merge_request_squash_options_settings.html.haml b/app/views/projects/_merge_request_squash_options_settings.html.haml
new file mode 100644
index 00000000000..a5dbfeb16d8
--- /dev/null
+++ b/app/views/projects/_merge_request_squash_options_settings.html.haml
@@ -0,0 +1,42 @@
+- form = local_assigns.fetch(:form)
+
+= form.fields_for :project_setting do |settings|
+ .form-group
+ %b= s_('ProjectSettings|Squash commits when merging')
+ %p.text-secondary
+ = s_('ProjectSettings|Set the default behavior and availability of this option in merge requests. Changes made are also applied to existing merge requests.')
+ = link_to "What is squashing?",
+ help_page_path('user/project/merge_requests/squash_and_merge.md'),
+ target: '_blank'
+
+ .form-check.gl-mb-2
+ = settings.radio_button :squash_option, :never, class: "form-check-input"
+ = label_tag :project_project_setting_attributes_squash_option_never, class: 'form-check-label' do
+ .gl-font-weight-bold
+ = s_('ProjectSettings|Do not allow')
+ .text-secondary
+ = s_('ProjectSettings|Squashing is never performed and the checkbox is hidden.')
+
+ .form-check.gl-mb-2
+ = settings.radio_button :squash_option, :default_off, class: "form-check-input"
+ = label_tag :project_project_setting_attributes_squash_option_default_off, class: 'form-check-label' do
+ .gl-font-weight-bold
+ = s_('ProjectSettings|Allow')
+ .text-secondary
+ = s_('ProjectSettings|Checkbox is visible and unselected by default.')
+
+ .form-check.gl-mb-2
+ = settings.radio_button :squash_option, :default_on, class: "form-check-input"
+ = label_tag :project_project_setting_attributes_squash_option_default_on, class: 'form-check-label' do
+ .gl-font-weight-bold
+ = s_('ProjectSettings|Encourage')
+ .text-secondary
+ = s_('ProjectSettings|Checkbox is visible and selected by default.')
+
+ .form-check.gl-mb-2
+ = settings.radio_button :squash_option, :always, class: "form-check-input"
+ = label_tag :project_project_setting_attributes_squash_option_always, class: 'form-check-label' do
+ .gl-font-weight-bold
+ = s_('ProjectSettings|Require')
+ .text-secondary
+ = s_('ProjectSettings|Squashing is always performed. Checkbox is visible and selected, and users cannot change it.')
diff --git a/app/views/projects/_readme.html.haml b/app/views/projects/_readme.html.haml
index 32624ac225b..da3133dfe15 100644
--- a/app/views/projects/_readme.html.haml
+++ b/app/views/projects/_readme.html.haml
@@ -1,10 +1,14 @@
- if (readme = @repository.readme) && readme.rich_viewer
+ .tree-holder
+ .nav-block.mt-0
+ = render 'projects/tree/tree_header', tree: @tree
%article.file-holder.readme-holder{ id: 'readme', class: ("limited-width-container" unless fluid_layout) }
- .js-file-title.file-title
- = blob_icon readme.mode, readme.name
- = link_to project_blob_path(@project, tree_join(@ref, readme.path)) do
- %strong
- = readme.name
+ .js-file-title.file-title-flex-parent
+ .file-header-content
+ = blob_icon readme.mode, readme.name
+ = link_to project_blob_path(@project, tree_join(@ref, readme.path)) do
+ %strong
+ = readme.name
= render 'projects/blob/viewer', viewer: readme.rich_viewer, viewer_url: namespace_project_blob_path(@project.namespace, @project, tree_join(@ref, readme.path), viewer: :rich, format: :json)
- else
diff --git a/app/views/projects/_remove.html.haml b/app/views/projects/_remove.html.haml
index 6c84fbfeeb3..528d802261c 100644
--- a/app/views/projects/_remove.html.haml
+++ b/app/views/projects/_remove.html.haml
@@ -4,7 +4,6 @@
%h4.danger-title= _('Remove project')
%p
%strong= _('Removing the project will delete its repository and all related resources including issues, merge requests etc.')
- = form_tag(project_path(project), method: :delete) do
- %p
- %strong= _('Removed projects cannot be restored!')
- = button_to _('Remove project'), '#', class: "btn btn-remove js-confirm-danger", data: { "confirm-danger-message" => remove_project_message(project) }
+ %p
+ %strong= _('Removed projects cannot be restored!')
+ #js-confirm-project-remove{ data: { form_path: project_path(project), confirm_phrase: project.path, warning_message: remove_project_message(project) } }
diff --git a/app/views/projects/_service_desk_settings.html.haml b/app/views/projects/_service_desk_settings.html.haml
new file mode 100644
index 00000000000..e6842bbb939
--- /dev/null
+++ b/app/views/projects/_service_desk_settings.html.haml
@@ -0,0 +1,19 @@
+- expanded = expanded_by_default?
+%section.settings.js-service-desk-setting-wrapper.no-animate#js-service-desk{ class: ('expanded' if expanded) }
+ .settings-header
+ %h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only= _('Service Desk')
+ %button.btn.js-settings-toggle
+ = expanded ? _('Collapse') : _('Expand')
+ - link_start = "<a href='#{help_page_path('user/project/service_desk')}' target='_blank' rel='noopener noreferrer'>".html_safe
+ %p= _('Enable/disable your service desk. %{link_start}Learn more about service desk%{link_end}.').html_safe % { link_start: link_start, link_end: '</a>'.html_safe }
+ .settings-content
+ - if ::Gitlab::ServiceDesk.supported?
+ .js-service-desk-setting-root{ data: { endpoint: project_service_desk_path(@project),
+ enabled: "#{@project.service_desk_enabled}",
+ incoming_email: (@project.service_desk_address if @project.service_desk_enabled),
+ selected_template: "#{@project.service_desk_setting&.issue_template_key}",
+ outgoing_name: "#{@project.service_desk_setting&.outgoing_name}",
+ project_key: "#{@project.service_desk_setting&.project_key}",
+ templates: issuable_templates_names(Issue.new) } }
+ - elsif show_callout?('promote_service_desk_dismissed')
+ = render 'shared/promotions/promote_servicedesk'
diff --git a/app/views/projects/_wiki.html.haml b/app/views/projects/_wiki.html.haml
index 6f90bf50b91..991c95153da 100644
--- a/app/views/projects/_wiki.html.haml
+++ b/app/views/projects/_wiki.html.haml
@@ -1,6 +1,6 @@
- if @wiki_home.present?
%div{ class: container_class }
- .md.prepend-top-default.append-bottom-default
+ .md.gl-mt-3.gl-mb-3
= render_wiki_content(@wiki_home)
- else
- can_create_wiki = can?(current_user, :create_wiki, @project)
diff --git a/app/views/projects/artifacts/browse.html.haml b/app/views/projects/artifacts/browse.html.haml
index 7abac2d14e4..ff56cb53720 100644
--- a/app/views/projects/artifacts/browse.html.haml
+++ b/app/views/projects/artifacts/browse.html.haml
@@ -1,5 +1,5 @@
- breadcrumb_title _('Artifacts')
-- page_title @path.presence, 'Artifacts', "#{@build.name} (##{@build.id})", 'Jobs'
+- page_title @path.presence, _('Artifacts'), "#{@build.name} (##{@build.id})", _('Jobs')
= render "projects/jobs/header"
diff --git a/app/views/projects/artifacts/file.html.haml b/app/views/projects/artifacts/file.html.haml
index 808b4acc8f3..1ad70506be4 100644
--- a/app/views/projects/artifacts/file.html.haml
+++ b/app/views/projects/artifacts/file.html.haml
@@ -1,4 +1,4 @@
-- page_title @path, 'Artifacts', "#{@build.name} (##{@build.id})", 'Jobs'
+- page_title @path, _('Artifacts'), "#{@build.name} (##{@build.id})", _('Jobs')
= render "projects/jobs/header"
diff --git a/app/views/projects/blame/show.html.haml b/app/views/projects/blame/show.html.haml
index 0591c3180ea..a2d6b2e18a9 100644
--- a/app/views/projects/blame/show.html.haml
+++ b/app/views/projects/blame/show.html.haml
@@ -1,4 +1,4 @@
-- page_title "Blame", @blob.path, @ref
+- page_title _("Blame"), @blob.path, @ref
- link_icon = icon("link")
#blob-content-holder.tree-holder
diff --git a/app/views/projects/blob/_editor.html.haml b/app/views/projects/blob/_editor.html.haml
index 032df24a603..b06ae31e73f 100644
--- a/app/views/projects/blob/_editor.html.haml
+++ b/app/views/projects/blob/_editor.html.haml
@@ -2,19 +2,19 @@
- file_name = params[:id].split("/").last ||= ""
- is_markdown = Gitlab::MarkupHelper.gitlab_markdown?(file_name)
-.file-holder-bottom-radius.file-holder.file.append-bottom-default
+.file-holder-bottom-radius.file-holder.file.gl-mb-3
.js-file-title.file-title.align-items-center.clearfix{ data: { current_action: action } }
- .editor-ref.block-truncated
+ .editor-ref.block-truncated.has-tooltip{ title: ref }
= sprite_icon('fork', size: 12)
= ref
- if current_action?(:edit) || current_action?(:update)
- %span.pull-left.append-right-10
+ %span.pull-left.gl-mr-3
= text_field_tag 'file_path', (params[:file_path] || @path),
class: 'form-control new-file-path js-file-path-name-input'
= render 'template_selectors'
- if current_action?(:new) || current_action?(:create)
- %span.pull-left.append-right-10
+ %span.pull-left.gl-mr-3
\/
= text_field_tag 'file_name', params[:file_name], placeholder: "File name",
required: true, class: 'form-control new-file-name js-file-path-name-input', value: params[:file_name] || (should_suggest_gitlab_ci_yml? ? '.gitlab-ci.yml' : '')
@@ -40,7 +40,7 @@
= select_tag :encoding, options_for_select([ "base64", "text" ], "text"), class: 'select2', tabindex: '-1'
.file-editor.code
- %pre.js-edit-mode-pane.qa-editor#editor= params[:content] || local_assigns[:blob_data]
+ %pre.js-edit-mode-pane.qa-editor#editor{ data: { 'editor-loading': true } }= params[:content] || local_assigns[:blob_data]
- if local_assigns[:path]
.js-edit-mode-pane#preview.hide
.center
diff --git a/app/views/projects/blob/_header_content.html.haml b/app/views/projects/blob/_header_content.html.haml
index 6527c6021a0..32adfb320ff 100644
--- a/app/views/projects/blob/_header_content.html.haml
+++ b/app/views/projects/blob/_header_content.html.haml
@@ -10,4 +10,4 @@
= number_to_human_size(blob.raw_size)
- if blob.stored_externally? && blob.external_storage == :lfs
- %span.badge.label-lfs.append-right-5 LFS
+ %span.badge.label-lfs.gl-mr-2 LFS
diff --git a/app/views/projects/blob/_viewer.html.haml b/app/views/projects/blob/_viewer.html.haml
index b9663bbba15..a0d82ffd2c7 100644
--- a/app/views/projects/blob/_viewer.html.haml
+++ b/app/views/projects/blob/_viewer.html.haml
@@ -5,7 +5,7 @@
- external_embed = local_assigns.fetch(:external_embed, false)
- viewer_url = local_assigns.fetch(:viewer_url) { url_for(safe_params.merge(viewer: viewer.type, format: :json)) } if load_async
-.blob-viewer{ data: { type: viewer.type, rich_type: rich_type, url: viewer_url }, class: ('hidden' if hidden) }
+.blob-viewer{ data: { type: viewer.type, rich_type: rich_type, url: viewer_url, path: viewer.blob.path }, class: ('hidden' if hidden) }
- if render_error
= render 'projects/blob/render_error', viewer: viewer
- elsif load_async
diff --git a/app/views/projects/blob/_viewer_switcher.html.haml b/app/views/projects/blob/_viewer_switcher.html.haml
index 5e0d70b2ca9..df81e509c85 100644
--- a/app/views/projects/blob/_viewer_switcher.html.haml
+++ b/app/views/projects/blob/_viewer_switcher.html.haml
@@ -5,8 +5,8 @@
.btn-group.js-blob-viewer-switcher.ml-2{ role: "group" }>
- simple_label = "Display #{simple_viewer.switcher_title}"
%button.btn.btn-default.btn-sm.js-blob-viewer-switch-btn.has-tooltip{ 'aria-label' => simple_label, title: simple_label, data: { viewer: 'simple', container: 'body' } }>
- = icon(simple_viewer.switcher_icon)
+ = sprite_icon(simple_viewer.switcher_icon)
- rich_label = "Display #{rich_viewer.switcher_title}"
%button.btn.btn-default.btn-sm.js-blob-viewer-switch-btn.has-tooltip{ 'aria-label' => rich_label, title: rich_label, data: { viewer: 'rich', container: 'body' } }>
- = icon(rich_viewer.switcher_icon)
+ = sprite_icon(rich_viewer.switcher_icon)
diff --git a/app/views/projects/blob/edit.html.haml b/app/views/projects/blob/edit.html.haml
index 870e37488cf..1319c58eb38 100644
--- a/app/views/projects/blob/edit.html.haml
+++ b/app/views/projects/blob/edit.html.haml
@@ -1,7 +1,8 @@
-- breadcrumb_title "Repository"
-- page_title "Edit", @blob.path, @ref
-- content_for :page_specific_javascripts do
- = page_specific_javascript_tag('lib/ace.js')
+- breadcrumb_title _("Repository")
+- page_title _("Edit"), @blob.path, @ref
+- unless Feature.enabled?(:monaco_blobs)
+ - content_for :page_specific_javascripts do
+ = page_specific_javascript_tag('lib/ace.js')
- if @conflict
.alert.alert-danger
diff --git a/app/views/projects/blob/new.html.haml b/app/views/projects/blob/new.html.haml
index 8f166e9aa16..2420c4a4bd5 100644
--- a/app/views/projects/blob/new.html.haml
+++ b/app/views/projects/blob/new.html.haml
@@ -1,7 +1,9 @@
-- breadcrumb_title "Repository"
-- page_title "New File", @path.presence, @ref
-- content_for :page_specific_javascripts do
- = page_specific_javascript_tag('lib/ace.js')
+- breadcrumb_title _("Repository")
+- page_title _("New File"), @path.presence, @ref
+- unless Feature.enabled?(:monaco_blobs)
+ - content_for :page_specific_javascripts do
+ = page_specific_javascript_tag('lib/ace.js')
+
.editor-title-row
%h3.page-title.blob-new-page-title
New file
diff --git a/app/views/projects/blob/viewers/_license.html.haml b/app/views/projects/blob/viewers/_license.html.haml
index fb9d0b99d09..7ac0e7bb579 100644
--- a/app/views/projects/blob/viewers/_license.html.haml
+++ b/app/views/projects/blob/viewers/_license.html.haml
@@ -1,6 +1,6 @@
- license = viewer.license
-= icon('balance-scale fw')
+= sprite_icon('scale', size: 16)
This project is licensed under the
= succeed '.' do
%strong= license.name
diff --git a/app/views/projects/blob/viewers/_loading.html.haml b/app/views/projects/blob/viewers/_loading.html.haml
index df1f3e4e01b..5fbe9b0df0c 100644
--- a/app/views/projects/blob/viewers/_loading.html.haml
+++ b/app/views/projects/blob/viewers/_loading.html.haml
@@ -1,2 +1,2 @@
-.text-center.prepend-top-default.append-bottom-default
+.text-center.gl-mt-3.gl-mb-3
= icon('spinner spin 2x', 'aria-hidden' => 'true', 'aria-label' => 'Loading content…', class: 'qa-spinner')
diff --git a/app/views/projects/blob/viewers/_metrics_dashboard_yml.html.haml b/app/views/projects/blob/viewers/_metrics_dashboard_yml.html.haml
index fc8683e1d19..ecbf6d9005d 100644
--- a/app/views/projects/blob/viewers/_metrics_dashboard_yml.html.haml
+++ b/app/views/projects/blob/viewers/_metrics_dashboard_yml.html.haml
@@ -8,4 +8,4 @@
- viewer.errors.messages.each do |error|
%li= error.join(': ')
-= link_to _('Learn more'), help_page_path('user/project/integrations/prometheus.md', anchor: 'defining-custom-dashboards-per-project')
+= link_to _('Learn more'), help_page_path('operations/metrics/dashboards/index.md', anchor: 'defining-custom-dashboards-per-project')
diff --git a/app/views/projects/blob/viewers/_sketch.html.haml b/app/views/projects/blob/viewers/_sketch.html.haml
index b4b6492b92f..aa8d1dd326f 100644
--- a/app/views/projects/blob/viewers/_sketch.html.haml
+++ b/app/views/projects/blob/viewers/_sketch.html.haml
@@ -1,3 +1,3 @@
.file-content#js-sketch-viewer{ data: { endpoint: blob_raw_path } }
- .js-loading-icon.text-center.prepend-top-default.append-bottom-default.js-loading-icon{ 'aria-label' => 'Loading Sketch preview' }
+ .js-loading-icon.text-center.gl-mt-3.gl-mb-3.js-loading-icon{ 'aria-label' => 'Loading Sketch preview' }
= icon('spinner spin 2x', 'aria-hidden' => 'true');
diff --git a/app/views/projects/blob/viewers/_stl.html.haml b/app/views/projects/blob/viewers/_stl.html.haml
index 55dd8cba7fe..6983c3cc81b 100644
--- a/app/views/projects/blob/viewers/_stl.html.haml
+++ b/app/views/projects/blob/viewers/_stl.html.haml
@@ -1,7 +1,7 @@
.file-content.is-stl-loading
.text-center#js-stl-viewer{ data: { endpoint: blob_raw_path } }
- = icon('spinner spin 2x', class: 'prepend-top-default append-bottom-default', 'aria-hidden' => 'true', 'aria-label' => 'Loading')
- .text-center.prepend-top-default.append-bottom-default.stl-controls
+ = icon('spinner spin 2x', class: 'gl-mt-3 gl-mb-3', 'aria-hidden' => 'true', 'aria-label' => 'Loading')
+ .text-center.gl-mt-3.gl-mb-3.stl-controls
.btn-group
%button.btn.btn-default.btn-sm.js-material-changer{ data: { type: 'wireframe' } }
Wireframe
diff --git a/app/views/projects/branches/_branch.html.haml b/app/views/projects/branches/_branch.html.haml
index 2e9be28df86..ed7dbdeae93 100644
--- a/app/views/projects/branches/_branch.html.haml
+++ b/app/views/projects/branches/_branch.html.haml
@@ -8,13 +8,13 @@
= link_to project_tree_path(@project, branch.name), class: 'item-title str-truncated-100 ref-name gl-ml-3 qa-branch-name' do
= branch.name
- if branch.name == @repository.root_ref
- %span.badge.badge-primary.prepend-left-5 default
+ %span.badge.badge-primary.gl-ml-2 default
- elsif merged
- %span.badge.badge-info.has-tooltip.prepend-left-5{ title: s_('Branches|Merged into %{default_branch}') % { default_branch: @repository.root_ref } }
+ %span.badge.badge-info.has-tooltip.gl-ml-2{ title: s_('Branches|Merged into %{default_branch}') % { default_branch: @repository.root_ref } }
= s_('Branches|merged')
- if protected_branch?(@project, branch)
- %span.badge.badge-success.prepend-left-5
+ %span.badge.badge-success.gl-ml-2
= s_('Branches|protected')
= render_if_exists 'projects/branches/diverged_from_upstream', branch: branch
@@ -41,7 +41,7 @@
- if branch.name != @repository.root_ref
= link_to project_compare_index_path(@project, from: @repository.root_ref, to: branch.name),
- class: "btn btn-default js-onboarding-compare-branches #{'prepend-left-10' unless merge_project}",
+ class: "btn btn-default js-onboarding-compare-branches #{'gl-ml-3' unless merge_project}",
method: :post,
title: s_('Branches|Compare') do
= s_('Branches|Compare')
diff --git a/app/views/projects/branches/new.html.haml b/app/views/projects/branches/new.html.haml
index af8887b0c39..97e46aaa710 100644
--- a/app/views/projects/branches/new.html.haml
+++ b/app/views/projects/branches/new.html.haml
@@ -1,4 +1,4 @@
-- page_title "New Branch"
+- page_title _("New Branch")
- default_ref = params[:ref] || @project.default_branch
- if @error
diff --git a/app/views/projects/buttons/_clone.html.haml b/app/views/projects/buttons/_clone.html.haml
index b12be8a91d6..7ce143a86b3 100644
--- a/app/views/projects/buttons/_clone.html.haml
+++ b/app/views/projects/buttons/_clone.html.haml
@@ -3,7 +3,7 @@
.git-clone-holder.js-git-clone-holder
%a#clone-dropdown.btn.btn-primary.clone-dropdown-btn.qa-clone-dropdown{ href: '#', data: { toggle: 'dropdown' } }
- %span.append-right-4.js-clone-dropdown-label
+ %span.gl-mr-2.js-clone-dropdown-label
= _('Clone')
= sprite_icon("chevron-down", css_class: "icon")
%ul.p-3.dropdown-menu.dropdown-menu-large.dropdown-menu-selectable.clone-options-dropdown.qa-clone-options{ class: dropdown_class }
diff --git a/app/views/projects/buttons/_download.html.haml b/app/views/projects/buttons/_download.html.haml
index 445752d0a15..1d0ad6dcde6 100644
--- a/app/views/projects/buttons/_download.html.haml
+++ b/app/views/projects/buttons/_download.html.haml
@@ -12,13 +12,7 @@
%h5.m-0.dropdown-bold-header= _('Download source code')
.dropdown-menu-content
= render 'projects/buttons/download_links', project: project, ref: ref, archive_prefix: archive_prefix, path: nil
- - if vue_file_list_enabled?
- #js-directory-downloads{ data: { links: directory_download_links(project, ref, archive_prefix).to_json } }
- - elsif directory?
- %section.border-top.pt-1.mt-1
- %h5.m-0.dropdown-bold-header= _('Download this directory')
- .dropdown-menu-content
- = render 'projects/buttons/download_links', project: project, ref: ref, archive_prefix: archive_prefix, path: @path
+ #js-directory-downloads{ data: { links: directory_download_links(project, ref, archive_prefix).to_json } }
- if pipeline && pipeline.latest_builds_with_artifacts.any?
%section.border-top.pt-1.mt-1
%h5.m-0.dropdown-bold-header= _('Download artifacts')
diff --git a/app/views/projects/cleanup/_show.html.haml b/app/views/projects/cleanup/_show.html.haml
index 02e8bad69b9..52855d7ee12 100644
--- a/app/views/projects/cleanup/_show.html.haml
+++ b/app/views/projects/cleanup/_show.html.haml
@@ -20,7 +20,7 @@
= _("Upload object map")
%button.btn.btn-default.js-choose-file{ type: "button" }
= _("Choose a file")
- %span.prepend-left-default.js-filename
+ %span.gl-ml-3.js-filename
= _("No file selected")
= f.file_field :bfg_object_map, class: "hidden js-object-map-input", required: true
.form-text.text-muted
diff --git a/app/views/projects/commit/_commit_box.html.haml b/app/views/projects/commit/_commit_box.html.haml
index 4442bdcdf1d..71cf6ca6922 100644
--- a/app/views/projects/commit/_commit_box.html.haml
+++ b/app/views/projects/commit/_commit_box.html.haml
@@ -22,10 +22,10 @@
.header-action-buttons
- if defined?(@notes_count) && @notes_count > 0
- %span.btn.disabled.btn-grouped.d-none.d-sm-block.append-right-10.has-tooltip{ title: n_("%d comment on this commit", "%d comments on this commit", @notes_count) % @notes_count }
+ %span.btn.disabled.btn-grouped.d-none.d-sm-block.gl-mr-3.has-tooltip{ title: n_("%d comment on this commit", "%d comments on this commit", @notes_count) % @notes_count }
= sprite_icon('comment')
= @notes_count
- = link_to project_tree_path(@project, @commit), class: "btn btn-default append-right-10 d-none d-sm-none d-md-inline" do
+ = link_to project_tree_path(@project, @commit), class: "btn btn-default gl-mr-3 d-none d-sm-none d-md-inline" do
#{ _('Browse files') }
.dropdown.inline
%a.btn.btn-default.dropdown-toggle.qa-options-button.d-md-inline{ data: { toggle: "dropdown" } }
diff --git a/app/views/projects/commit/_limit_exceeded_message.html.haml b/app/views/projects/commit/_limit_exceeded_message.html.haml
index 7d3c0582d0b..ace1be787fb 100644
--- a/app/views/projects/commit/_limit_exceeded_message.html.haml
+++ b/app/views/projects/commit/_limit_exceeded_message.html.haml
@@ -1,4 +1,4 @@
-.has-tooltip{ class: "limit-box limit-box-#{objects} prepend-left-5", data: { title: _('Project has too many %{label_for_message} to search') % { label_for_message: label_for_message } } }
+.has-tooltip{ class: "limit-box limit-box-#{objects} gl-ml-2", data: { title: _('Project has too many %{label_for_message} to search') % { label_for_message: label_for_message } } }
.limit-icon
- if objects == :branch
= sprite_icon('fork', size: 12)
diff --git a/app/views/projects/commits/show.html.haml b/app/views/projects/commits/show.html.haml
index 7722a3523a1..737e4f66dd2 100644
--- a/app/views/projects/commits/show.html.haml
+++ b/app/views/projects/commits/show.html.haml
@@ -14,18 +14,18 @@
%ul.breadcrumb.repo-breadcrumb
= commits_breadcrumbs
#js-author-dropdown{ data: { 'commits_path': project_commits_path(@project), 'project_id': @project.id } }
- .tree-controls.d-none.d-sm-none.d-md-block
+ .tree-controls
- if @merge_request.present?
- .control
+ .control.d-none.d-md-block
= link_to _("View open merge request"), project_merge_request_path(@project, @merge_request), class: 'btn'
- elsif create_mr_button?(@repository.root_ref, @ref)
- .control
+ .control.d-none.d-md-block
= link_to _("Create merge request"), create_mr_path(@repository.root_ref, @ref), class: 'btn btn-success'
.control
= form_tag(project_commits_path(@project, @id), method: :get, class: 'commits-search-form js-signature-container', data: { 'signatures-path' => namespace_project_signatures_path }) do
- = search_field_tag :search, params[:search], { placeholder: _('Filter by commit message'), id: 'commits-search', class: 'form-control search-text-input input-short', spellcheck: false }
- .control
+ = search_field_tag :search, params[:search], { placeholder: _('Search by message'), id: 'commits-search', class: 'form-control search-text-input input-short gl-mt-3 gl-sm-mt-0 gl-min-w-full', spellcheck: false }
+ .control.d-none.d-md-block
= link_to project_commits_path(@project, @ref, rss_url_options), title: _("Commits feed"), class: 'btn' do
= icon("rss")
diff --git a/app/views/projects/compare/_form.html.haml b/app/views/projects/compare/_form.html.haml
index f5a4889b4bb..d10fa69ff47 100644
--- a/app/views/projects/compare/_form.html.haml
+++ b/app/views/projects/compare/_form.html.haml
@@ -1,7 +1,7 @@
= form_tag project_compare_index_path(@project), method: :post, class: 'form-inline js-requires-input js-signature-container', data: { 'signatures-path' => signatures_namespace_project_compare_index_path } do
- if params[:to] && params[:from]
.compare-switch-container
- = link_to icon('exchange'), { from: params[:to], to: params[:from] }, class: 'commits-compare-switch has-tooltip btn btn-white', title: 'Swap revisions'
+ = link_to sprite_icon('substitute'), { from: params[:to], to: params[:from] }, class: 'commits-compare-switch has-tooltip btn btn-white', title: 'Swap revisions'
.form-group.dropdown.compare-form-group.to.js-compare-to-dropdown
.input-group.inline-input-group
%span.input-group-prepend
@@ -26,6 +26,6 @@
&nbsp;
= button_tag s_("CompareBranches|Compare"), class: "btn btn-success commits-compare-btn"
- if @merge_request.present?
- = link_to _("View open merge request"), project_merge_request_path(@project, @merge_request), class: 'prepend-left-10 btn'
+ = link_to _("View open merge request"), project_merge_request_path(@project, @merge_request), class: 'gl-ml-3 btn'
- elsif create_mr_button?
- = link_to _("Create merge request"), create_mr_path, class: 'prepend-left-10 btn'
+ = link_to _("Create merge request"), create_mr_path, class: 'gl-ml-3 btn'
diff --git a/app/views/projects/compare/index.html.haml b/app/views/projects/compare/index.html.haml
index 02f2b104ce3..93ee1bed809 100644
--- a/app/views/projects/compare/index.html.haml
+++ b/app/views/projects/compare/index.html.haml
@@ -1,5 +1,5 @@
-- breadcrumb_title "Compare Revisions"
-- page_title "Compare"
+- breadcrumb_title _("Compare Revisions")
+- page_title _("Compare")
%h3.page-title
= _("Compare Git revisions")
diff --git a/app/views/projects/confluences/show.html.haml b/app/views/projects/confluences/show.html.haml
new file mode 100644
index 00000000000..b87780db4cd
--- /dev/null
+++ b/app/views/projects/confluences/show.html.haml
@@ -0,0 +1,13 @@
+- breadcrumb_title _('Confluence')
+- page_title _('Confluence')
+= render layout: 'shared/empty_states/wikis_layout', locals: { image_path: 'illustrations/wiki_login_empty.svg' } do
+ %h4
+ = s_('WikiEmpty|Confluence is enabled')
+ %p
+ - wiki_confluence_epic_link_url = 'https://gitlab.com/groups/gitlab-org/-/epics/3629'
+ - wiki_confluence_epic_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: wiki_confluence_epic_link_url }
+ = s_("WikiEmpty|You've enabled the Confluence Workspace integration. Your wiki will be viewable directly within Confluence. We are hard at work integrating Confluence more seamlessly into GitLab. If you'd like to stay up to date, follow our %{wiki_confluence_epic_link_start}Confluence epic%{wiki_confluence_epic_link_end}.").html_safe % { wiki_confluence_epic_link_start: wiki_confluence_epic_link_start, wiki_confluence_epic_link_end: '</a>'.html_safe }
+ = link_to @project.confluence_service.confluence_url, target: '_blank', rel: 'noopener noreferrer', class: 'btn btn-success external-url', title: s_('WikiEmpty|Go to Confluence') do
+ = sprite_icon('external-link')
+ = s_('WikiEmpty|Go to Confluence')
+
diff --git a/app/views/projects/cycle_analytics/show.html.haml b/app/views/projects/cycle_analytics/show.html.haml
index b6c30c680e4..090fc602ebb 100644
--- a/app/views/projects/cycle_analytics/show.html.haml
+++ b/app/views/projects/cycle_analytics/show.html.haml
@@ -1,4 +1,4 @@
-- page_title "Value Stream Analytics"
+- page_title _("Value Stream Analytics")
#cycle-analytics{ "v-cloak" => "true", data: { request_path: project_cycle_analytics_path(@project) } }
- if @cycle_analytics_no_data
diff --git a/app/views/projects/default_branch/_show.html.haml b/app/views/projects/default_branch/_show.html.haml
index 6a09004143e..38bec0361b0 100644
--- a/app/views/projects/default_branch/_show.html.haml
+++ b/app/views/projects/default_branch/_show.html.haml
@@ -26,6 +26,6 @@
%strong= _("Auto-close referenced issues on default branch")
.form-text.text-muted
= _("Issues referenced by merge requests and commits within the default branch will be closed automatically")
- = link_to icon('question-circle'), help_page_path('user/project/issues/managing_issues.html', anchor: 'disabling-automatic-issue-closing'), target: '_blank'
+ = link_to icon('question-circle'), help_page_path('user/project/issues/managing_issues.md', anchor: 'disabling-automatic-issue-closing'), target: '_blank'
= f.submit 'Save changes', class: "btn btn-success"
diff --git a/app/views/projects/deploy_keys/edit.html.haml b/app/views/projects/deploy_keys/edit.html.haml
index 0ce93eef369..7fa7036245c 100644
--- a/app/views/projects/deploy_keys/edit.html.haml
+++ b/app/views/projects/deploy_keys/edit.html.haml
@@ -1,4 +1,4 @@
-- page_title 'Edit Deploy Key'
+- page_title _('Edit Deploy Key')
%h3.page-title= _('Edit Deploy Key')
%hr
diff --git a/app/views/projects/diffs/_diffs.html.haml b/app/views/projects/diffs/_diffs.html.haml
index cf7fe36af9d..4b76dde681e 100644
--- a/app/views/projects/diffs/_diffs.html.haml
+++ b/app/views/projects/diffs/_diffs.html.haml
@@ -16,6 +16,8 @@
= diff_merge_request_whitespace_link(diffs.project, @merge_request, class: 'd-none d-sm-inline-block')
- elsif current_controller?(:compare)
= diff_compare_whitespace_link(diffs.project, params[:from], params[:to], class: 'd-none d-sm-inline-block')
+ - elsif current_controller?(:wikis)
+ = toggle_whitespace_link(url_for(params_with_whitespace), class: 'd-none d-sm-inline-block')
.btn-group
= inline_diff_btn
= parallel_diff_btn
diff --git a/app/views/projects/diffs/_file_header.html.haml b/app/views/projects/diffs/_file_header.html.haml
index 6a1bff8640c..f954b09abee 100644
--- a/app/views/projects/diffs/_file_header.html.haml
+++ b/app/views/projects/diffs/_file_header.html.haml
@@ -37,4 +37,4 @@
#{diff_file.a_mode} → #{diff_file.b_mode}
- if diff_file.stored_externally? && diff_file.external_storage == :lfs
- %span.badge.label-lfs.append-right-5 LFS
+ %span.badge.label-lfs.gl-mr-2 LFS
diff --git a/app/views/projects/diffs/_stats.html.haml b/app/views/projects/diffs/_stats.html.haml
index 17c1764e8a4..0e2a1165ad3 100644
--- a/app/views/projects/diffs/_stats.html.haml
+++ b/app/views/projects/diffs/_stats.html.haml
@@ -4,7 +4,7 @@
Showing
%button.diff-stats-summary-toggler.js-diff-stats-dropdown{ type: "button", data: { toggle: "dropdown", display: "static" } }<
= pluralize(diff_files.size, "changed file")
- = icon("caret-down", class: "prepend-left-5")
+ = icon("caret-down", class: "gl-ml-2")
%span.diff-stats-additions-deletions-expanded#diff-stats
with
%strong.cgreen= pluralize(sum_added_lines, 'addition')
@@ -30,7 +30,7 @@
- else
%strong.diff-changed-blank-file-name
= s_('Diffs|No file name available')
- %span.diff-changed-file-path.prepend-top-5= diff_file_path_text(diff_file)
+ %span.diff-changed-file-path.gl-mt-2= diff_file_path_text(diff_file)
%span.diff-changed-stats
%span.cgreen<
+#{diff_file.added_lines}
diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml
index 3c6fb5b19a4..e63b615115a 100644
--- a/app/views/projects/edit.html.haml
+++ b/app/views/projects/edit.html.haml
@@ -56,7 +56,7 @@
= render_if_exists 'projects/settings/default_issue_template'
-= render_if_exists 'projects/service_desk_settings'
+= render 'projects/service_desk_settings'
%section.qa-advanced-settings.settings.advanced-settings.no-animate#js-project-advanced-settings{ class: ('expanded' if expanded) }
.settings-header
diff --git a/app/views/projects/empty.html.haml b/app/views/projects/empty.html.haml
index 6b1455acd08..bfb22aa8025 100644
--- a/app/views/projects/empty.html.haml
+++ b/app/views/projects/empty.html.haml
@@ -1,5 +1,7 @@
- @content_class = "limit-container-width" unless fluid_layout
+- default_branch_name = Gitlab::CurrentSettings.default_branch_name.presence || "master"
- breadcrumb_title _("Details")
+- page_title _("Details")
= render partial: 'flash_messages', locals: { project: @project }
@@ -46,7 +48,7 @@
git commit -m "add README"
- if @project.can_current_user_push_to_default_branch?
%span><
- git push -u origin master
+ git push -u origin #{ default_branch_name }
%fieldset
%h5= _('Push an existing folder')
@@ -59,7 +61,7 @@
git commit -m "Initial commit"
- if @project.can_current_user_push_to_default_branch?
%span><
- git push -u origin master
+ git push -u origin #{ default_branch_name }
%fieldset
%h5= _('Push an existing Git repository')
diff --git a/app/views/projects/environments/_form.html.haml b/app/views/projects/environments/_form.html.haml
index efe80a4877c..39eda493d69 100644
--- a/app/views/projects/environments/_form.html.haml
+++ b/app/views/projects/environments/_form.html.haml
@@ -1,9 +1,9 @@
-.row.prepend-top-default.append-bottom-default
+.row.gl-mt-3.gl-mb-3
.col-lg-3
%h4.gl-mt-0
= _("Environments")
%p
- - link_to_read_more = link_to(_("Read more about environments"), help_page_path("ci/environments/index.md"))
+ - link_to_read_more = link_to(_("More information"), help_page_path("ci/environments/index.md"))
= _("Environments allow you to track deployments of your application %{link_to_read_more}.").html_safe % { link_to_read_more: link_to_read_more }
= form_for [@project.namespace.becomes(Namespace), @project, @environment], html: { class: 'col-lg-9' } do |f|
diff --git a/app/views/projects/find_file/show.html.haml b/app/views/projects/find_file/show.html.haml
index 971107675ab..786af3714a6 100644
--- a/app/views/projects/find_file/show.html.haml
+++ b/app/views/projects/find_file/show.html.haml
@@ -1,4 +1,4 @@
-- page_title "Find File", @ref
+- page_title _("Find File"), @ref
.file-finder-holder.tree-holder.clearfix.js-file-finder{ 'data-file-find-url': "#{escape_javascript(project_files_path(@project, @ref, format: :json))}", 'data-find-tree-url': escape_javascript(project_tree_path(@project, @ref)), 'data-blob-url-template': escape_javascript(project_blob_path(@project, @id || @commit.id)) }
.nav-block
@@ -23,5 +23,5 @@
= _('There are no matching files')
%p.text-secondary
= _('Try using a different search term to find the file you are looking for.')
- .text-center.prepend-top-default.loading
+ .text-center.gl-mt-3.loading
.spinner.spinner-md
diff --git a/app/views/projects/forks/_fork_button.html.haml b/app/views/projects/forks/_fork_button.html.haml
index 70064722832..eec02a50b85 100644
--- a/app/views/projects/forks/_fork_button.html.haml
+++ b/app/views/projects/forks/_fork_button.html.haml
@@ -2,17 +2,17 @@
- can_create_project = current_user.can?(:create_projects, namespace)
- if forked_project = namespace.find_fork_of(@project)
- .bordered-box.fork-thumbnail.text-center.prepend-left-default.append-right-default.prepend-top-default.append-bottom-default.forked
+ .bordered-box.fork-thumbnail.text-center.gl-ml-3.gl-mr-3.gl-mt-3.gl-mb-3.forked
= link_to project_path(forked_project) do
- if /no_((\w*)_)*avatar/.match(avatar)
= group_icon(namespace, class: "avatar rect-avatar s100 identicon mx-auto")
- else
.avatar-container.s100.mx-auto
= image_tag(avatar, class: "avatar s100")
- %h5.prepend-top-default
+ %h5.gl-mt-3
= namespace.human_name
- else
- .bordered-box.fork-thumbnail.text-center.prepend-left-default.append-right-default.prepend-top-default.append-bottom-default{ class: ("disabled" unless can_create_project) }
+ .bordered-box.fork-thumbnail.text-center.gl-ml-3.gl-mr-3.gl-mt-3.gl-mb-3{ class: ("disabled" unless can_create_project) }
= link_to project_forks_path(@project, namespace_key: namespace.id),
method: "POST",
class: ("disabled has-tooltip" unless can_create_project),
@@ -22,5 +22,5 @@
- else
.avatar-container.s100.mx-auto
= image_tag(avatar, class: "avatar s100")
- %h5.prepend-top-default{ data: { qa_selector: 'fork_namespace_content', qa_name: namespace.human_name } }
+ %h5.gl-mt-3{ data: { qa_selector: 'fork_namespace_content', qa_name: namespace.human_name } }
= namespace.human_name
diff --git a/app/views/projects/forks/new.html.haml b/app/views/projects/forks/new.html.haml
index 763e31c4a8b..887081d0f35 100644
--- a/app/views/projects/forks/new.html.haml
+++ b/app/views/projects/forks/new.html.haml
@@ -1,6 +1,6 @@
- page_title _("Fork project")
-.row.prepend-top-default
+.row.gl-mt-3
.col-lg-3
%h4.gl-mt-0
= _("Fork project")
@@ -9,13 +9,13 @@
.col-lg-9
- if @namespaces.present?
.fork-thumbnail-container.js-fork-content
- %h5.gl-mt-0.gl-mb-0.prepend-left-default.append-right-default
+ %h5.gl-mt-0.gl-mb-0.gl-ml-3.gl-mr-3
= _("Select a namespace to fork the project")
- @namespaces.each do |namespace|
= render 'fork_button', namespace: namespace
- else
%strong
= _("No available namespaces to fork the project.")
- %p.prepend-top-default
+ %p.gl-mt-3
= _("You must have permission to create a project in a namespace before forking.")
diff --git a/app/views/projects/hook_logs/_index.html.haml b/app/views/projects/hook_logs/_index.html.haml
index e7b924c65bf..a8a4eef65b3 100644
--- a/app/views/projects/hook_logs/_index.html.haml
+++ b/app/views/projects/hook_logs/_index.html.haml
@@ -1,4 +1,4 @@
-.row.gl-mt-7.append-bottom-default
+.row.gl-mt-7.gl-mb-3
.col-lg-3
%h4.gl-mt-0
Recent Deliveries
diff --git a/app/views/projects/hook_logs/show.html.haml b/app/views/projects/hook_logs/show.html.haml
index a6a3f56c28c..8a8c396a9e4 100644
--- a/app/views/projects/hook_logs/show.html.haml
+++ b/app/views/projects/hook_logs/show.html.haml
@@ -2,11 +2,11 @@
- add_to_breadcrumbs _('Webhook Settings'), namespace_project_hooks_path
- page_title _('Webhook Logs')
-.row.prepend-top-default.append-bottom-default
+.row.gl-mt-3.gl-mb-3
.col-lg-3
%h4.gl-mt-0
Request details
.col-lg-9
- = link_to 'Resend Request', @hook_log.present.retry_path, method: :post, class: "btn btn-default float-right prepend-left-10"
+ = link_to 'Resend Request', @hook_log.present.retry_path, method: :post, class: "btn btn-default float-right gl-ml-3"
= render partial: 'shared/hook_logs/content', locals: { hook_log: @hook_log }
diff --git a/app/views/projects/hooks/edit.html.haml b/app/views/projects/hooks/edit.html.haml
index 15100840c0a..e0ef0c0d3f9 100644
--- a/app/views/projects/hooks/edit.html.haml
+++ b/app/views/projects/hooks/edit.html.haml
@@ -2,11 +2,11 @@
- add_to_breadcrumbs _('Webhook Settings'), namespace_project_hooks_path
- page_title _('Webhook')
-.row.prepend-top-default
+.row.gl-mt-3
.col-lg-3
= render 'shared/web_hooks/title_and_docs', hook: @hook
- .col-lg-9.append-bottom-default
+ .col-lg-9.gl-mb-3
= form_for [@project.namespace.becomes(Namespace), @project, @hook], as: :hook, url: project_hook_path(@project, @hook) do |f|
= render partial: 'shared/web_hooks/form', locals: { form: f, hook: @hook }
diff --git a/app/views/projects/hooks/index.html.haml b/app/views/projects/hooks/index.html.haml
index 169a5cc9d6b..1845bd190d3 100644
--- a/app/views/projects/hooks/index.html.haml
+++ b/app/views/projects/hooks/index.html.haml
@@ -2,11 +2,11 @@
- breadcrumb_title _('Webhook Settings')
- page_title _('Webhooks')
-.row.prepend-top-default
+.row.gl-mt-3
.col-lg-4
= render 'shared/web_hooks/title_and_docs', hook: @hook
- .col-lg-8.append-bottom-default
+ .col-lg-8.gl-mb-3
= form_for @hook, as: :hook, url: polymorphic_path([@project.namespace.becomes(Namespace), @project, :hooks]) do |f|
= render partial: 'shared/web_hooks/form', locals: { form: f, hook: @hook }
= f.submit 'Add webhook', class: 'btn btn-success'
diff --git a/app/views/projects/import/jira/show.html.haml b/app/views/projects/import/jira/show.html.haml
index fe6cc6fa828..3c0664e4d5f 100644
--- a/app/views/projects/import/jira/show.html.haml
+++ b/app/views/projects/import/jira/show.html.haml
@@ -3,4 +3,5 @@
jira_integration_path: edit_project_service_path(@project, :jira),
is_jira_configured: @project.jira_service&.active? && @project.jira_service&.valid_connection?.to_s,
in_progress_illustration: image_path('illustrations/export-import.svg'),
+ project_id: @project.id,
setup_illustration: image_path('illustrations/manual_action.svg') } }
diff --git a/app/views/projects/imports/new.html.haml b/app/views/projects/imports/new.html.haml
index bd0ab2c19f2..58981ca1556 100644
--- a/app/views/projects/imports/new.html.haml
+++ b/app/views/projects/imports/new.html.haml
@@ -1,4 +1,4 @@
-- page_title "Import repository"
+- page_title _("Import repository")
%h3.page-title
Import repository
diff --git a/app/views/projects/issues/_alert_moved_from_service_desk.html.haml b/app/views/projects/issues/_alert_moved_from_service_desk.html.haml
new file mode 100644
index 00000000000..a6f969f8b10
--- /dev/null
+++ b/app/views/projects/issues/_alert_moved_from_service_desk.html.haml
@@ -0,0 +1,10 @@
+- return unless show_moved_service_desk_issue_warning?(issue)
+- service_desk_link_url = help_page_path('user/project/service_desk')
+- service_desk_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: service_desk_link_url }
+
+.hide.gl-alert.gl-alert-warning.js-alert-moved-from-service-desk-warning.gl-mt-5{ role: 'alert' }
+ = sprite_icon('warning', size: 16, css_class: 'gl-icon gl-alert-icon gl-alert-icon-no-title')
+ %button.js-close.gl-alert-dismiss{ type: 'button', 'aria-label' => _('Dismiss') }
+ = sprite_icon('close', size: 16, css_class: 'gl-icon')
+ .gl-alert-body.gl-mr-3
+ = s_('This project does not have %{service_desk_link_start}Service Desk%{service_desk_link_end} enabled, so the user who created the issue will no longer receive email notifications about new activity.').html_safe % { service_desk_link_start: service_desk_link_start, service_desk_link_end: '</a>'.html_safe }
diff --git a/app/views/projects/issues/_by_email_description.html.haml b/app/views/projects/issues/_by_email_description.html.haml
index f2d58534903..0ff852352e1 100644
--- a/app/views/projects/issues/_by_email_description.html.haml
+++ b/app/views/projects/issues/_by_email_description.html.haml
@@ -1,6 +1,6 @@
The subject will be used as the title of the new issue, and the message will be the description.
-= link_to 'Quick actions', help_page_path('user/project/quick_actions'), target: '_blank', tabindex: -1
+= link_to 'Quick actions', help_page_path('user/project/quick_actions'), target: '_blank'
and styling with
-= link_to 'Markdown', help_page_path('user/markdown'), target: '_blank', tabindex: -1
+= link_to 'Markdown', help_page_path('user/markdown'), target: '_blank'
are supported.
diff --git a/app/views/projects/issues/_design_management.html.haml b/app/views/projects/issues/_design_management.html.haml
index 96f1dc0155c..045f032e6e7 100644
--- a/app/views/projects/issues/_design_management.html.haml
+++ b/app/views/projects/issues/_design_management.html.haml
@@ -1,15 +1,27 @@
- if @project.design_management_enabled?
- .js-design-management{ data: { project_path: @project.full_path, issue_iid: @issue.iid, issue_path: project_issue_path(@project, @issue) } }
+ - if Feature.enabled?(:design_management_moved, @project, default_enabled: true)
+ .js-design-management-new{ data: { project_path: @project.full_path, issue_iid: @issue.iid, issue_path: project_issue_path(@project, @issue) } }
+ - else
+ .js-design-management{ data: { project_path: @project.full_path, issue_iid: @issue.iid, issue_path: project_issue_path(@project, @issue) } }
- else
- .mt-4
- .row.empty-state
- .col-12
- .text-content
- %h4.center
- = _('The one place for your designs')
- %p.center
- - requirements_link_url = help_page_path('user/project/issues/design_management', anchor: 'requirements')
- - requirements_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: requirements_link_url }
- - support_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: support_url }
- - link_end = '</a>'.html_safe
- = s_("DesignManagement|To enable design management, you'll need to %{requirements_link_start}meet the requirements%{requirements_link_end}. If you need help, reach out to our %{support_link_start}support team%{support_link_end} for assistance.").html_safe % { requirements_link_start: requirements_link_start, requirements_link_end: link_end, support_link_start: support_link_start, support_link_end: link_end }
+ - if Feature.enabled?(:design_management_moved, @project, default_enabled: true)
+ .row.empty-state.design-dropzone-border.gl-mt-5
+ .text-content.center.gl-font-weight-bold
+ - requirements_link_url = help_page_path('user/project/issues/design_management', anchor: 'requirements')
+ - requirements_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: requirements_link_url }
+ - support_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: support_url }
+ - link_end = '</a>'.html_safe
+ = s_("DesignManagement|To enable design management, you'll need to %{requirements_link_start}meet the requirements%{requirements_link_end}. If you need help, reach out to our %{support_link_start}support team%{support_link_end} for assistance.").html_safe % { requirements_link_start: requirements_link_start, requirements_link_end: link_end, support_link_start: support_link_start, support_link_end: link_end }
+ - else
+ .mt-4
+ .row.empty-state
+ .col-12
+ .text-content
+ %h4.center
+ = _('The one place for your designs')
+ %p.center
+ - requirements_link_url = help_page_path('user/project/issues/design_management', anchor: 'requirements')
+ - requirements_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: requirements_link_url }
+ - support_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: support_url }
+ - link_end = '</a>'.html_safe
+ = s_("DesignManagement|To enable design management, you'll need to %{requirements_link_start}meet the requirements%{requirements_link_end}. If you need help, reach out to our %{support_link_start}support team%{support_link_end} for assistance.").html_safe % { requirements_link_start: requirements_link_start, requirements_link_end: link_end, support_link_start: support_link_start, support_link_end: link_end }
diff --git a/app/views/projects/issues/_discussion.html.haml b/app/views/projects/issues/_discussion.html.haml
index 9c129fa9ecc..bcc74e8d1d9 100644
--- a/app/views/projects/issues/_discussion.html.haml
+++ b/app/views/projects/issues/_discussion.html.haml
@@ -7,7 +7,7 @@
%section.issuable-discussion.js-vue-notes-event
#js-vue-notes{ data: { notes_data: notes_data(@issue).to_json,
- noteable_data: serialize_issuable(@issue, with_blocking_issues: Feature.enabled?(:prevent_closing_blocked_issues, @issue.project)),
+ noteable_data: serialize_issuable(@issue, with_blocking_issues: true),
noteable_type: 'Issue',
target_type: 'issue',
current_user_data: UserSerializer.new.represent(current_user, {only_path: true}, CurrentUserEntity).to_json } }
diff --git a/app/views/projects/issues/_issue.html.haml b/app/views/projects/issues/_issue.html.haml
index e325d585d0c..e7cd35497e8 100644
--- a/app/views/projects/issues/_issue.html.haml
+++ b/app/views/projects/issues/_issue.html.haml
@@ -47,7 +47,7 @@
.issuable-meta
%ul.controls
- - if issue.moved?
+ - if issue.closed? && issue.moved?
%li.issuable-status
= _('CLOSED (MOVED)')
- elsif issue.closed?
diff --git a/app/views/projects/issues/_issues.html.haml b/app/views/projects/issues/_issues.html.haml
index 7d539c9d749..c0383c57e63 100644
--- a/app/views/projects/issues/_issues.html.haml
+++ b/app/views/projects/issues/_issues.html.haml
@@ -1,9 +1,14 @@
-- empty_state_path = local_assigns.fetch(:empty_state_path, 'shared/empty_states/issues')
+- if Feature.enabled?(:vue_issuables_list, @project)
+ .js-issuables-list{ data: { endpoint: expose_url(api_v4_projects_issues_path(id: @project.id)),
+ 'can-bulk-edit': @can_bulk_update.to_json,
+ 'empty-svg-path': image_path('illustrations/issues.svg'),
+ 'sort-key': @sort } }
+- else
+ - empty_state_path = local_assigns.fetch(:empty_state_path, 'shared/empty_states/issues')
+ %ul.content-list.issues-list.issuable-list{ class: ("manual-ordering" if @sort == 'relative_position') }
+ = render partial: "projects/issues/issue", collection: @issues
+ - if @issues.blank?
+ = render empty_state_path
-%ul.content-list.issues-list.issuable-list{ class: ("manual-ordering" if @sort == 'relative_position') }
- = render partial: "projects/issues/issue", collection: @issues
- - if @issues.blank?
- = render empty_state_path
-
-- if @issues.present?
- = paginate @issues, theme: "gitlab", total_pages: @total_pages
+ - if @issues.present?
+ = paginate @issues, theme: "gitlab", total_pages: @total_pages
diff --git a/app/views/projects/issues/_nav_btns.html.haml b/app/views/projects/issues/_nav_btns.html.haml
index 71c9bb36936..cc6ca4aca4a 100644
--- a/app/views/projects/issues/_nav_btns.html.haml
+++ b/app/views/projects/issues/_nav_btns.html.haml
@@ -14,7 +14,7 @@
= render 'projects/issues/import_csv/button'
- if @can_bulk_update
- = button_tag _("Edit issues"), class: "btn btn-default append-right-10 js-bulk-update-toggle"
+ = button_tag _("Edit issues"), class: "btn btn-default gl-mr-3 js-bulk-update-toggle"
- if show_new_issue_link?(@project)
= link_to _("New issue"), new_project_issue_path(@project,
issue: { assignee_id: finder.assignee.try(:id),
diff --git a/app/views/projects/issues/_new_branch.html.haml b/app/views/projects/issues/_new_branch.html.haml
index 73904354a12..9bbab925f6a 100644
--- a/app/views/projects/issues/_new_branch.html.haml
+++ b/app/views/projects/issues/_new_branch.html.haml
@@ -41,7 +41,7 @@
= _('Create branch')
%li.divider.droplab-item-ignore
- %li.droplab-item-ignore.gl-ml-3.gl-mr-3.prepend-top-16
+ %li.droplab-item-ignore.gl-ml-3.gl-mr-3.gl-mt-5
- if can_create_confidential_merge_request?
#js-forked-project{ data: { namespace_path: @project.namespace.full_path, project_path: @project.full_path, new_fork_path: new_project_fork_path(@project), help_page_path: help_page_path('user/project/merge_requests') } }
.form-group
diff --git a/app/views/projects/issues/_service_desk_info_content.html.haml b/app/views/projects/issues/_service_desk_info_content.html.haml
new file mode 100644
index 00000000000..ddd8e545043
--- /dev/null
+++ b/app/views/projects/issues/_service_desk_info_content.html.haml
@@ -0,0 +1,39 @@
+- is_empty_state = @issues.blank?
+- service_desk_enabled = @project.service_desk_enabled?
+
+- callout_selector = is_empty_state ? 'empty-state' : 'non-empty-state media'
+- svg_path = !is_empty_state ? 'shared/empty_states/icons/service_desk_callout.svg' : 'shared/empty_states/icons/service_desk_empty_state.svg'
+- can_edit_project_settings = can?(current_user, :admin_project, @project)
+- title_text = _("Use Service Desk to connect with your users (e.g. to offer customer support) through email right inside GitLab")
+
+- if Gitlab::ServiceDesk.supported?
+ %div{ class: "#{callout_selector}" }
+ .svg-content
+ = render svg_path
+
+ %div{ class: is_empty_state ? "text-content" : "prepend-top-10 gl-ml-3" }
+ - if is_empty_state
+ %h4= title_text
+ - else
+ %h5= title_text
+
+ - if can_edit_project_settings && service_desk_enabled
+ %p
+ = _("Have your users email")
+ %code= @project.service_desk_address
+
+ %span= _("Those emails automatically become issues (with the comments becoming the email conversation) listed here.")
+ = link_to _('Read more'), help_page_path('user/project/service_desk')
+
+ - if can_edit_project_settings && !service_desk_enabled
+ %div{ class: is_empty_state ? "text-center" : "prepend-top-10" }
+ = link_to _("Turn on Service Desk"), edit_project_path(@project), class: 'btn btn-success'
+- else
+ .empty-state
+ .svg-content
+ = render 'shared/empty_states/icons/service_desk_setup.svg'
+ .text-content
+ %h4= _('Service Desk is enabled but not yet active')
+ %p
+ = _("You must set up incoming email before it becomes active.")
+ = link_to _('More information'), help_page_path('administration/incoming_email', anchor: 'set-it-up')
diff --git a/app/views/projects/issues/edit.html.haml b/app/views/projects/issues/edit.html.haml
index 1b7d878c38c..353ff9c1cc2 100644
--- a/app/views/projects/issues/edit.html.haml
+++ b/app/views/projects/issues/edit.html.haml
@@ -1,4 +1,4 @@
-- page_title "Edit", "#{@issue.title} (#{@issue.to_reference})", "Issues"
+- page_title _("Edit"), "#{@issue.title} (#{@issue.to_reference})", _("Issues")
%h3.page-title
Edit Issue ##{@issue.iid}
diff --git a/app/views/projects/issues/export_csv/_modal.html.haml b/app/views/projects/issues/export_csv/_modal.html.haml
index 9fdeb901b56..342c3ba27bb 100644
--- a/app/views/projects/issues/export_csv/_modal.html.haml
+++ b/app/views/projects/issues/export_csv/_modal.html.haml
@@ -12,7 +12,7 @@
.modal-body
.modal-subheader
= icon('check', { class: 'checkmark' })
- %strong.prepend-left-10
+ %strong.gl-ml-3
- issues_count = issuables_count_for_state(:issues, params[:state])
= n_('%d issue selected', '%d issues selected', issues_count) % issues_count
.modal-text
diff --git a/app/views/projects/issues/import_csv/_button.html.haml b/app/views/projects/issues/import_csv/_button.html.haml
index 7119b22daef..ea8f53f7342 100644
--- a/app/views/projects/issues/import_csv/_button.html.haml
+++ b/app/views/projects/issues/import_csv/_button.html.haml
@@ -3,7 +3,7 @@
.dropdown.btn-group
%button.btn.rounded-right.text-center{ class: ('has-tooltip' if type == :icon), title: (_('Import issues') if type == :icon),
- data: { toggle: 'dropdown' }, 'aria-label' => _('Import issues'), 'aria-haspopup' => 'true', 'aria-expanded' => 'false' }
+ data: { toggle: 'dropdown', qa_selector: 'import_issues_button' }, 'aria-label' => _('Import issues'), 'aria-haspopup' => 'true', 'aria-expanded' => 'false' }
- if type == :icon
= sprite_icon('import')
- else
@@ -13,4 +13,5 @@
%button{ data: { toggle: 'modal', target: '.issues-import-modal' } }
= _('Import CSV')
- if can_edit
- %li= link_to _('Import from Jira'), project_import_jira_path(@project)
+ %li{ data: { qa_selector: 'import_from_jira_link' } }
+ = link_to _('Import from Jira'), project_import_jira_path(@project)
diff --git a/app/views/projects/issues/index.html.haml b/app/views/projects/issues/index.html.haml
index 826a62e39d3..cfc423da57a 100644
--- a/app/views/projects/issues/index.html.haml
+++ b/app/views/projects/issues/index.html.haml
@@ -1,6 +1,6 @@
- @can_bulk_update = can?(current_user, :admin_issue, @project)
-- page_title "Issues"
+- page_title _("Issues")
- new_issue_email = @project.new_issuable_address(current_user, 'issue')
= content_for :meta_tags do
diff --git a/app/views/projects/issues/service_desk.html.haml b/app/views/projects/issues/service_desk.html.haml
new file mode 100644
index 00000000000..9b0b3ebc9e0
--- /dev/null
+++ b/app/views/projects/issues/service_desk.html.haml
@@ -0,0 +1,21 @@
+- @can_bulk_update = false
+
+- page_title _("Service Desk")
+
+- content_for :breadcrumbs_extra do
+ = render "projects/issues/nav_btns", show_export_button: false, show_rss_button: false
+
+- support_bot_attrs = UserSerializer.new.represent(User.support_bot).to_json
+
+%div{ class: "js-service-desk-issues service-desk-issues", data: { support_bot: support_bot_attrs } }
+ .top-area
+ = render 'shared/issuable/nav', type: :issues
+ .nav-controls.d-block.d-sm-none
+ = render "projects/issues/nav_btns", show_feed_buttons: false, show_import_button: false, show_export_button: false
+
+ - if @issues.present?
+ = render 'shared/issuable/search_bar', type: :issues
+ = render 'service_desk_info_content'
+
+ .issues-holder
+ = render 'projects/issues/issues', empty_state_path: 'service_desk_info_content'
diff --git a/app/views/projects/issues/show.html.haml b/app/views/projects/issues/show.html.haml
index 4d24b510267..2a0dc5e30b9 100644
--- a/app/views/projects/issues/show.html.haml
+++ b/app/views/projects/issues/show.html.haml
@@ -11,7 +11,7 @@
- can_create_issue = show_new_issue_link?(@project)
= render_if_exists "projects/issues/alert_blocked", issue: @issue, current_user: current_user
-= render_if_exists "projects/issues/alert_moved_from_service_desk", issue: @issue
+= render "projects/issues/alert_moved_from_service_desk", issue: @issue
.detail-page-header
.detail-page-header-body
@@ -24,14 +24,11 @@
%span.d-none.d-sm-block Open
.issuable-meta
- - if @issue.confidential
- .issuable-warning-icon.inline= sprite_icon('eye-slash', size: 16, css_class: 'icon')
- - if @issue.discussion_locked?
- .issuable-warning-icon.inline= sprite_icon('lock', size: 16, css_class: 'icon')
+ #js-issuable-header-warnings
= issuable_meta(@issue, @project, "Issue")
%a.btn.btn-default.float-right.d-block.d-sm-none.gutter-toggle.issuable-gutter-toggle.js-sidebar-toggle{ href: "#" }
- = icon('angle-double-left')
+ = sprite_icon('chevron-double-lg-left')
.detail-page-header-actions.js-issuable-actions.js-issuable-buttons{ data: { "action": "close-reopen" } }
.clearfix.issue-btn-group.dropdown
@@ -77,6 +74,9 @@
- if @issue.sentry_issue.present?
#js-sentry-error-stack-trace{ data: error_details_data(@project, @issue.sentry_issue.sentry_issue_identifier) }
+ - if Feature.enabled?(:design_management_moved, @project, default_enabled: true)
+ = render 'projects/issues/design_management'
+
= render_if_exists 'projects/issues/related_issues'
#js-related-merge-requests{ data: { endpoint: expose_path(api_v4_projects_issues_related_merge_requests_path(id: @project.id, issue_iid: @issue.iid)), project_namespace: @project.namespace.path, project_path: @project.path } }
@@ -86,14 +86,17 @@
-# This element is filled in using JavaScript.
.content-block.emoji-block.emoji-block-sticky
- .row
- .col-md-12.col-lg-4.js-noteable-awards
+ .row.gl-m-0.gl-justify-content-space-between
+ .js-noteable-awards
= render 'award_emoji/awards_block', awardable: @issue, inline: true
- .col-md-12.col-lg-8.new-branch-col
+ .new-branch-col
#js-vue-sort-issue-discussions
#js-vue-discussion-filter{ data: { default_filter: current_user&.notes_filter_for(@issue), notes_filters: UserPreference.notes_filters.to_json } }
= render 'new_branch' if show_new_branch_button?
- = render 'projects/issues/tabs'
+ - if Feature.enabled?(:design_management_moved, @project, default_enabled: true)
+ = render 'projects/issues/discussion'
+ - else
+ = render 'projects/issues/tabs'
= render 'shared/issuable/sidebar', issuable_sidebar: @issuable_sidebar, assignees: @issue.assignees
diff --git a/app/views/projects/jobs/index.html.haml b/app/views/projects/jobs/index.html.haml
index 5acb2af08e4..4f537ee8014 100644
--- a/app/views/projects/jobs/index.html.haml
+++ b/app/views/projects/jobs/index.html.haml
@@ -1,4 +1,4 @@
-- page_title "Jobs"
+- page_title _("Jobs")
.top-area
- build_path_proc = ->(scope) { project_jobs_path(@project, scope: scope) }
diff --git a/app/views/projects/jobs/show.html.haml b/app/views/projects/jobs/show.html.haml
index 2e322c7db23..df98a1c7cce 100644
--- a/app/views/projects/jobs/show.html.haml
+++ b/app/views/projects/jobs/show.html.haml
@@ -5,4 +5,6 @@
- content_for :page_specific_javascripts do
= stylesheet_link_tag 'page_bundles/xterm'
+= render_if_exists "shared/shared_runners_minutes_limit_flash_message"
+
#js-job-vue-app{ data: jobs_data }
diff --git a/app/views/projects/jobs/terminal.html.haml b/app/views/projects/jobs/terminal.html.haml
index 5439a4b5d5c..01f40543926 100644
--- a/app/views/projects/jobs/terminal.html.haml
+++ b/app/views/projects/jobs/terminal.html.haml
@@ -1,7 +1,7 @@
-- add_to_breadcrumbs 'Jobs', project_jobs_path(@project)
+- add_to_breadcrumbs _('Jobs'), project_jobs_path(@project)
- add_to_breadcrumbs "##{@build.id}", project_job_path(@project, @build)
-- breadcrumb_title 'Terminal'
-- page_title 'Terminal', "#{@build.name} (##{@build.id})", 'Jobs'
+- breadcrumb_title _('Terminal')
+- page_title _('Terminal'), "#{@build.name} (##{@build.id})", _('Jobs')
- content_for :page_specific_javascripts do
= stylesheet_link_tag "xterm.css"
diff --git a/app/views/projects/labels/edit.html.haml b/app/views/projects/labels/edit.html.haml
index b7996f0dad1..343900359b4 100644
--- a/app/views/projects/labels/edit.html.haml
+++ b/app/views/projects/labels/edit.html.haml
@@ -1,6 +1,6 @@
-- add_to_breadcrumbs "Labels", project_labels_path(@project)
-- breadcrumb_title "Edit"
-- page_title "Edit", @label.name, "Labels"
+- add_to_breadcrumbs _("Labels"), project_labels_path(@project)
+- breadcrumb_title _("Edit")
+- page_title _("Edit"), @label.name, _("Labels")
%h3.page-title
Edit Label
diff --git a/app/views/projects/labels/index.html.haml b/app/views/projects/labels/index.html.haml
index 760d81136c6..ba47712211d 100644
--- a/app/views/projects/labels/index.html.haml
+++ b/app/views/projects/labels/index.html.haml
@@ -1,4 +1,4 @@
-- page_title "Labels"
+- page_title _("Labels")
- can_admin_label = can?(current_user, :admin_label, @project)
- search = params[:search]
- subscribed = params[:subscribed]
@@ -52,5 +52,5 @@
= render 'shared/empty_states/labels'
%template#js-badge-item-template
- %li.label-link-item.js-priority-badge.inline.prepend-left-10
+ %li.label-link-item.js-priority-badge.inline.gl-ml-3
.label-badge.label-badge-blue= _('Prioritized label')
diff --git a/app/views/projects/labels/new.html.haml b/app/views/projects/labels/new.html.haml
index 96ce0eba2c6..38bd6102437 100644
--- a/app/views/projects/labels/new.html.haml
+++ b/app/views/projects/labels/new.html.haml
@@ -1,6 +1,6 @@
-- add_to_breadcrumbs "Labels", project_labels_path(@project)
-- breadcrumb_title "New"
-- page_title "New Label"
+- add_to_breadcrumbs _("Labels"), project_labels_path(@project)
+- breadcrumb_title _("New")
+- page_title _("New Label")
%h3.page-title
New Label
diff --git a/app/views/projects/merge_requests/_approvals_count.html.haml b/app/views/projects/merge_requests/_approvals_count.html.haml
new file mode 100644
index 00000000000..464cba1bb2d
--- /dev/null
+++ b/app/views/projects/merge_requests/_approvals_count.html.haml
@@ -0,0 +1,13 @@
+- merge_request = local_assigns.fetch(:merge_request)
+- self_approved = merge_request.approved_by?(current_user)
+- total = merge_request.approvals.size
+
+- if total > 0
+ - final_text = n_("%d approver", "%d approvers", total) % total
+ - final_self_text = n_("%d approver (you've approved)", "%d approvers (you've approved)", total) % total
+
+ - approval_icon = sprite_icon((self_approved ? 'approval-solid' : 'approval'), size: 16, css_class: 'align-middle')
+
+ %li.d-none.d-sm-inline-block.has-tooltip.text-success{ title: self_approved ? final_self_text : final_text }
+ = approval_icon
+ = _("Approved")
diff --git a/app/views/projects/merge_requests/_discussion.html.haml b/app/views/projects/merge_requests/_discussion.html.haml
index 3303aa72604..ecb51aca847 100644
--- a/app/views/projects/merge_requests/_discussion.html.haml
+++ b/app/views/projects/merge_requests/_discussion.html.haml
@@ -5,7 +5,7 @@
- if @merge_request.reopenable?
= link_to 'Reopen merge request', merge_request_path(@merge_request, merge_request: { state_event: :reopen }), method: :put, class: "btn btn-nr btn-comment btn-reopen reopen-mr-link js-note-target-close js-note-target-reopen", title: "Reopen merge request", data: { original_text: "Reopen merge request", alternative_text: "Comment & reopen merge request"}
%comment-and-resolve-btn{ "inline-template" => true }
- %button.btn.btn-nr.btn-default.append-right-10.js-comment-resolve-button{ "v-if" => "showButton", type: "submit", data: { project_path: "#{project_path(@merge_request.project)}" } }
+ %button.btn.btn-nr.btn-default.gl-mr-3.js-comment-resolve-button{ "v-if" => "showButton", type: "submit", data: { project_path: "#{project_path(@merge_request.project)}" } }
{{ buttonText }}
#notes= render "shared/notes/notes_with_form", :autocomplete => true
diff --git a/app/views/projects/merge_requests/_merge_request.html.haml b/app/views/projects/merge_requests/_merge_request.html.haml
index a753ee50c43..d3e98bac7f9 100644
--- a/app/views/projects/merge_requests/_merge_request.html.haml
+++ b/app/views/projects/merge_requests/_merge_request.html.haml
@@ -55,7 +55,7 @@
- if merge_request.assignees.any?
%li.d-flex
= render 'shared/issuable/assignees', project: merge_request.project, issuable: merge_request
- = render_if_exists 'projects/merge_requests/approvals_count', merge_request: merge_request
+ = render 'projects/merge_requests/approvals_count', merge_request: merge_request
= render 'shared/issuable_meta_data', issuable: merge_request
diff --git a/app/views/projects/merge_requests/_mr_title.html.haml b/app/views/projects/merge_requests/_mr_title.html.haml
index d1e8dc3a834..72931448432 100644
--- a/app/views/projects/merge_requests/_mr_title.html.haml
+++ b/app/views/projects/merge_requests/_mr_title.html.haml
@@ -20,7 +20,7 @@
= issuable_meta(@merge_request, @project, "Merge request")
%a.btn.btn-default.float-right.d-block.d-sm-none.gutter-toggle.issuable-gutter-toggle.js-sidebar-toggle{ href: "#" }
- = icon('angle-double-left')
+ = sprite_icon('chevron-double-lg-left')
.detail-page-header-actions.js-issuable-actions
.clearfix.issue-btn-group.dropdown
diff --git a/app/views/projects/merge_requests/_nav_btns.html.haml b/app/views/projects/merge_requests/_nav_btns.html.haml
index b7498216334..2ef10365c18 100644
--- a/app/views/projects/merge_requests/_nav_btns.html.haml
+++ b/app/views/projects/merge_requests/_nav_btns.html.haml
@@ -1,5 +1,5 @@
- if @can_bulk_update
- = button_tag "Edit merge requests", class: "btn append-right-10 js-bulk-update-toggle"
+ = button_tag "Edit merge requests", class: "btn gl-mr-3 js-bulk-update-toggle"
- if merge_project
= link_to new_merge_request_path, class: "btn btn-success", title: "New merge request" do
New merge request
diff --git a/app/views/projects/merge_requests/_widget.html.haml b/app/views/projects/merge_requests/_widget.html.haml
index 6aba5c98d52..16b08cbf648 100644
--- a/app/views/projects/merge_requests/_widget.html.haml
+++ b/app/views/projects/merge_requests/_widget.html.haml
@@ -7,10 +7,13 @@
window.gl.mrWidgetData = #{serialize_issuable(@merge_request, serializer: 'widget', issues_links: true)}
window.gl.mrWidgetData.squash_before_merge_help_path = '#{help_page_path("user/project/merge_requests/squash_and_merge")}';
- window.gl.mrWidgetData.troubleshooting_docs_path = '#{help_page_path('user/project/merge_requests/reviewing_and_managing_merge_requests.md', anchor: 'troubleshooting')}';
+ window.gl.mrWidgetData.ci_troubleshooting_docs_path = '#{help_page_path('ci/troubleshooting.md')}';
+ window.gl.mrWidgetData.mr_troubleshooting_docs_path = '#{help_page_path('user/project/merge_requests/reviewing_and_managing_merge_requests.md', anchor: 'troubleshooting')}';
window.gl.mrWidgetData.pipeline_must_succeed_docs_path = '#{help_page_path('user/project/merge_requests/merge_when_pipeline_succeeds.md', anchor: 'only-allow-merge-requests-to-be-merged-if-the-pipeline-succeeds')}';
- window.gl.mrWidgetData.security_approvals_help_page_path = '#{help_page_path('user/application_security/index.html', anchor: 'security-approvals-in-merge-requests-ultimate')}';
+ window.gl.mrWidgetData.security_approvals_help_page_path = '#{help_page_path('user/application_security/index.md', anchor: 'security-approvals-in-merge-requests-ultimate')}';
window.gl.mrWidgetData.eligible_approvers_docs_path = '#{help_page_path('user/project/merge_requests/merge_request_approvals', anchor: 'eligible-approvers')}';
+ window.gl.mrWidgetData.approvals_help_path = '#{help_page_path("user/project/merge_requests/merge_request_approvals")}';
window.gl.mrWidgetData.pipelines_empty_svg_path = '#{image_path('illustrations/pipelines_empty.svg')}';
+ window.gl.mrWidgetData.codequality_help_path = '#{help_page_path("user/project/merge_requests/code_quality", anchor: "code-quality-reports")}';
#js-vue-mr-widget.mr-widget
diff --git a/app/views/projects/merge_requests/conflicts/show.html.haml b/app/views/projects/merge_requests/conflicts/show.html.haml
index d933675eac5..6c23661fb86 100644
--- a/app/views/projects/merge_requests/conflicts/show.html.haml
+++ b/app/views/projects/merge_requests/conflicts/show.html.haml
@@ -1,4 +1,4 @@
-- page_title "Merge Conflicts", "#{@merge_request.title} (#{@merge_request.to_reference}", "Merge Requests"
+- page_title _("Merge Conflicts"), "#{@merge_request.title} (#{@merge_request.to_reference}", _("Merge Requests")
- content_for :page_specific_javascripts do
= page_specific_javascript_tag('lib/ace.js')
= render "projects/merge_requests/mr_title"
diff --git a/app/views/projects/merge_requests/creations/_new_submit.html.haml b/app/views/projects/merge_requests/creations/_new_submit.html.haml
index 0fb4d9ae70f..fdf0bfe8e50 100644
--- a/app/views/projects/merge_requests/creations/_new_submit.html.haml
+++ b/app/views/projects/merge_requests/creations/_new_submit.html.haml
@@ -20,8 +20,8 @@
.merge-request-tabs-holder{ class: ("js-tabs-affix" unless ENV['RAILS_ENV'] == 'test') }
.merge-request-tabs-container
.scrolling-tabs-container.inner-page-scroll-tabs.is-smaller
- .fade-left= icon('angle-left')
- .fade-right= icon('angle-right')
+ .fade-left= sprite_icon('chevron-lg-left', size: 12)
+ .fade-right= sprite_icon('chevron-lg-right', size: 12)
%ul.merge-request-tabs.nav.nav-tabs.nav-links.no-top.no-bottom.js-tabs-affix
%li.commits-tab.new-tab
= link_to url_for(safe_params), data: {target: 'div#commits', action: 'new', toggle: 'tabvue'} do
diff --git a/app/views/projects/merge_requests/creations/new.html.haml b/app/views/projects/merge_requests/creations/new.html.haml
index 0f618826305..ad4980fa57f 100644
--- a/app/views/projects/merge_requests/creations/new.html.haml
+++ b/app/views/projects/merge_requests/creations/new.html.haml
@@ -1,6 +1,6 @@
-- add_to_breadcrumbs "Merge Requests", project_merge_requests_path(@project)
-- breadcrumb_title "New"
-- page_title "New Merge Request"
+- add_to_breadcrumbs _("Merge Requests"), project_merge_requests_path(@project)
+- breadcrumb_title _("New")
+- page_title _("New Merge Request")
- if @merge_request.can_be_created && !params[:change_branches]
= render 'new_submit'
diff --git a/app/views/projects/merge_requests/diffs/_commit_widget.html.haml b/app/views/projects/merge_requests/diffs/_commit_widget.html.haml
index 066c8d5dba6..efc052ca791 100644
--- a/app/views/projects/merge_requests/diffs/_commit_widget.html.haml
+++ b/app/views/projects/merge_requests/diffs/_commit_widget.html.haml
@@ -3,7 +3,7 @@
- `assets/javascripts/diffs/components/commit_widget.vue`
-#-----------------------------------------------------------------
- if @commit
- .info-well.d-none.d-sm-block.prepend-top-default
+ .info-well.d-none.d-sm-block.gl-mt-3
.well-segment
%ul.blob-commit-info
= render 'projects/commits/commit', commit: @commit, merge_request: @merge_request, view_details: true
diff --git a/app/views/projects/merge_requests/edit.html.haml b/app/views/projects/merge_requests/edit.html.haml
index 318c9d809c1..a4bb790ce0b 100644
--- a/app/views/projects/merge_requests/edit.html.haml
+++ b/app/views/projects/merge_requests/edit.html.haml
@@ -1,4 +1,4 @@
-- page_title "Edit", "#{@merge_request.title} (#{@merge_request.to_reference}", "Merge Requests"
+- page_title _("Edit"), "#{@merge_request.title} (#{@merge_request.to_reference}", _("Merge Requests")
%h3.page-title
Edit Merge Request #{@merge_request.to_reference}
diff --git a/app/views/projects/merge_requests/index.html.haml b/app/views/projects/merge_requests/index.html.haml
index 4e30f09b9a2..36b1cf0796f 100644
--- a/app/views/projects/merge_requests/index.html.haml
+++ b/app/views/projects/merge_requests/index.html.haml
@@ -2,7 +2,7 @@
- merge_project = merge_request_source_project_for_project(@project)
- new_merge_request_path = project_new_merge_request_path(merge_project) if merge_project
-- page_title "Merge Requests"
+- page_title _("Merge Requests")
- new_merge_request_email = @project.new_issuable_address(current_user, 'merge_request')
= render 'projects/last_push'
diff --git a/app/views/projects/merge_requests/invalid.html.haml b/app/views/projects/merge_requests/invalid.html.haml
index 749228a9664..7b831aa2d01 100644
--- a/app/views/projects/merge_requests/invalid.html.haml
+++ b/app/views/projects/merge_requests/invalid.html.haml
@@ -1,4 +1,4 @@
-- page_title "#{@merge_request.title} (#{@merge_request.to_reference}", "Merge Requests"
+- page_title "#{@merge_request.title} (#{@merge_request.to_reference}", _("Merge Requests")
.merge-request
= render "projects/merge_requests/mr_title"
diff --git a/app/views/projects/merge_requests/show.html.haml b/app/views/projects/merge_requests/show.html.haml
index 90bc2504cb4..03fa9758587 100644
--- a/app/views/projects/merge_requests/show.html.haml
+++ b/app/views/projects/merge_requests/show.html.haml
@@ -1,14 +1,15 @@
- @gfm_form = true
- @content_class = "limit-container-width" unless fluid_layout
-- add_to_breadcrumbs "Merge Requests", project_merge_requests_path(@project)
+- add_to_breadcrumbs _("Merge Requests"), project_merge_requests_path(@project)
- breadcrumb_title @merge_request.to_reference
-- page_title "#{@merge_request.title} (#{@merge_request.to_reference})", "Merge Requests"
+- page_title "#{@merge_request.title} (#{@merge_request.to_reference})", _("Merge Requests")
- page_description @merge_request.description
- page_card_attributes @merge_request.card_attributes
- suggest_changes_help_path = help_page_path('user/discussions/index.md', anchor: 'suggest-changes')
- number_of_pipelines = @pipelines.size
+- mr_action = j(params[:tab].presence || 'show')
-.merge-request{ data: { mr_action: j(params[:tab].presence || 'show'), url: merge_request_path(@merge_request, format: :json), project_path: project_path(@merge_request.project), lock_version: @merge_request.lock_version } }
+.merge-request{ data: { mr_action: mr_action, url: merge_request_path(@merge_request, format: :json), project_path: project_path(@merge_request.project), lock_version: @merge_request.lock_version } }
= render "projects/merge_requests/mr_title"
.merge-request-details.issuable-details{ data: { id: @merge_request.project.id } }
@@ -76,9 +77,11 @@
= render "projects/merge_requests/tabs/pane", name: "pipelines", id: "pipelines", class: "pipelines" do
- if number_of_pipelines.nonzero?
= render 'projects/commit/pipelines_list', disable_initialization: true, endpoint: pipelines_project_merge_request_path(@project, @merge_request)
+ - if mr_action === "diffs"
+ - add_page_startup_api_call @endpoint_metadata_url
= render "projects/merge_requests/tabs/pane", name: "diffs", id: "js-diffs-app", class: "diffs", data: { "is-locked": @merge_request.discussion_locked?,
endpoint: diffs_project_merge_request_path(@project, @merge_request, 'json', request.query_parameters),
- endpoint_metadata: diffs_metadata_project_json_merge_request_path(@project, @merge_request, 'json', request.query_parameters),
+ endpoint_metadata: @endpoint_metadata_url,
endpoint_batch: diffs_batch_project_json_merge_request_path(@project, @merge_request, 'json', request.query_parameters),
endpoint_coverage: @coverage_path,
help_page_path: suggest_changes_help_path,
@@ -88,7 +91,8 @@
is_fluid_layout: fluid_layout.to_s,
dismiss_endpoint: user_callouts_path,
show_suggest_popover: show_suggest_popover?.to_s,
- show_whitespace_default: @show_whitespace_default.to_s }
+ show_whitespace_default: @show_whitespace_default.to_s,
+ file_by_file_default: @file_by_file_default.to_s }
.mr-loading-status
.loading.hide
diff --git a/app/views/projects/milestones/_form.html.haml b/app/views/projects/milestones/_form.html.haml
index a3083fa2081..eeff91f631c 100644
--- a/app/views/projects/milestones/_form.html.haml
+++ b/app/views/projects/milestones/_form.html.haml
@@ -7,13 +7,13 @@
.col-form-label.col-sm-2
= f.label :title, _('Title')
.col-sm-10
- = f.text_field :title, maxlength: 255, class: 'qa-milestone-title form-control', required: true, autofocus: true
+ = f.text_field :title, maxlength: 255, class: 'form-control', data: { qa_selector: 'milestone_title_field' }, required: true, autofocus: true
.form-group.row.milestone-description
.col-form-label.col-sm-2
= f.label :description, _('Description')
.col-sm-10
= render layout: 'shared/md_preview', locals: { url: preview_markdown_path(@project) } do
- = render 'shared/zen', f: f, attr: :description, classes: 'qa-milestone-description note-textarea', placeholder: _('Write milestone description...')
+ = render 'shared/zen', f: f, attr: :description, classes: 'note-textarea', qa_selector: 'milestone_description_field', placeholder: _('Write milestone description...')
= render 'shared/notes/hints'
.clearfix
.error-alert
@@ -21,7 +21,7 @@
.form-actions
- if @milestone.new_record?
- = f.submit _('Create milestone'), class: 'btn-success btn qa-milestone-create-button'
+ = f.submit _('Create milestone'), class: 'btn-success btn', data: { qa_selector: 'create_milestone_button' }
= link_to _('Cancel'), project_milestones_path(@project), class: 'btn btn-cancel'
- else
= f.submit _('Save changes'), class: 'btn-success btn'
diff --git a/app/views/projects/milestones/index.html.haml b/app/views/projects/milestones/index.html.haml
index c89566dac90..2bab2a0fb03 100644
--- a/app/views/projects/milestones/index.html.haml
+++ b/app/views/projects/milestones/index.html.haml
@@ -7,7 +7,7 @@
= render 'shared/milestones/search_form'
= render 'shared/milestones_sort_dropdown'
- if can?(current_user, :admin_milestone, @project)
- = link_to new_project_milestone_path(@project), class: 'btn btn-success qa-new-project-milestone', title: _('New milestone') do
+ = link_to new_project_milestone_path(@project), class: 'btn btn-success', data: { qa_selector: "new_project_milestone_link" }, title: _('New milestone') do
= _('New milestone')
.milestones
diff --git a/app/views/projects/milestones/show.html.haml b/app/views/projects/milestones/show.html.haml
index b83204c27e3..5239af82ba6 100644
--- a/app/views/projects/milestones/show.html.haml
+++ b/app/views/projects/milestones/show.html.haml
@@ -9,10 +9,10 @@
= render_if_exists 'shared/milestones/burndown', milestone: @milestone, project: @project
- if can?(current_user, :read_issue, @project) && @milestone.total_issues_count.zero?
- .alert.alert-success.prepend-top-default
+ .alert.alert-success.gl-mt-3
%span= _('Assign some issues to this milestone.')
- elsif @milestone.complete? && @milestone.active?
- .alert.alert-success.prepend-top-default
+ .alert.alert-success.gl-mt-3
%span= _('All issues for this milestone are closed. You may close this milestone now.')
= render 'shared/milestones/tabs', milestone: @milestone
diff --git a/app/views/projects/mirrors/_instructions.html.haml b/app/views/projects/mirrors/_instructions.html.haml
index 7ff6c0a2019..15c9076c1ab 100644
--- a/app/views/projects/mirrors/_instructions.html.haml
+++ b/app/views/projects/mirrors/_instructions.html.haml
@@ -1,4 +1,4 @@
-.account-well.prepend-top-default.append-bottom-default
+.account-well.gl-mt-3.gl-mb-3
%ul
%li
= _('The repository must be accessible over <code>http://</code>,
diff --git a/app/views/projects/mirrors/_ssh_host_keys.html.haml b/app/views/projects/mirrors/_ssh_host_keys.html.haml
index 90236dc0c48..236ede32d31 100644
--- a/app/views/projects/mirrors/_ssh_host_keys.html.haml
+++ b/app/views/projects/mirrors/_ssh_host_keys.html.haml
@@ -3,7 +3,7 @@
- verified_at = mirror.ssh_known_hosts_verified_at
.form-group.js-ssh-host-keys-section{ class: ('collapse' unless mirror.ssh_mirror_url?) }
- %button.btn.btn-inverted.btn-secondary.inline.js-detect-host-keys.append-right-10{ type: 'button', data: { qa_selector: 'detect_host_keys' } }
+ %button.btn.btn-inverted.btn-secondary.inline.js-detect-host-keys.gl-mr-3{ type: 'button', data: { qa_selector: 'detect_host_keys' } }
.js-spinner.d-none.spinner.mr-1
= _('Detect host keys')
.fingerprint-ssh-info.js-fingerprint-ssh-info.prepend-top-10.append-bottom-10{ class: ('collapse' unless mirror.ssh_mirror_url?) }
@@ -28,6 +28,6 @@
= _('Input host keys manually')
%span.label-hide
= _('Hide host keys manual input')
- .js-ssh-known-hosts.collapse.prepend-top-default
+ .js-ssh-known-hosts.collapse.gl-mt-3
= f.label :ssh_known_hosts, _('SSH host keys'), class: 'label-bold'
= f.text_area :ssh_known_hosts, class: 'form-control known-hosts js-known-hosts', rows: '10'
diff --git a/app/views/projects/network/show.html.haml b/app/views/projects/network/show.html.haml
index 6821453cffa..d134bfb488e 100644
--- a/app/views/projects/network/show.html.haml
+++ b/app/views/projects/network/show.html.haml
@@ -1,5 +1,5 @@
-- breadcrumb_title "Graph"
-- page_title "Graph", @ref
+- breadcrumb_title _("Graph")
+- page_title _("Graph"), @ref
= render "head"
%div{ class: container_class }
.project-network
@@ -16,5 +16,5 @@
- if @commit
.network-graph{ data: { url: @url, commit_url: @commit_url, ref: @ref, commit_id: @commit.id } }
- .text-center.prepend-top-default
+ .text-center.gl-mt-3
.spinner.spinner-md
diff --git a/app/views/projects/new.html.haml b/app/views/projects/new.html.haml
index 81a778f76f4..d5099f80ea4 100644
--- a/app/views/projects/new.html.haml
+++ b/app/views/projects/new.html.haml
@@ -4,7 +4,7 @@
- header_title _("Projects"), dashboard_projects_path
- active_tab = local_assigns.fetch(:active_tab, 'blank')
-.project-edit-container.prepend-top-default
+.project-edit-container.gl-mt-3
.project-edit-errors
= render 'projects/errors'
@@ -16,7 +16,7 @@
%h4.gl-mt-0
= _('New project')
%p
- - among_other_things_link = link_to _('among other things'), help_page_path("user/project/index.md", anchor: "projects-features"), target: '_blank'
+ - among_other_things_link = link_to _('among other things'), help_page_path("user/project/index.md", anchor: "project-features"), target: '_blank'
= _('A project is where you house your files (repository), plan your work (issues), and publish your documentation (wiki), %{among_other_things_link}.').html_safe % { among_other_things_link: among_other_things_link }
%p
= _('All features are enabled for blank projects, from templates, or when importing, but you can disable them afterward in the project settings.')
diff --git a/app/views/projects/no_repo.html.haml b/app/views/projects/no_repo.html.haml
index 08772a0188b..d5030a02cdd 100644
--- a/app/views/projects/no_repo.html.haml
+++ b/app/views/projects/no_repo.html.haml
@@ -1,4 +1,5 @@
- breadcrumb_title _("Details")
+- page_title _("Details")
%h2
%i.fa.fa-warning
@@ -14,7 +15,7 @@
= link_to project_repository_path(@project), method: :post, class: 'btn btn-primary' do
#{ _('Create empty repository') }
- %strong.prepend-left-10.append-right-10 or
+ %strong.gl-ml-3.gl-mr-3 or
= link_to new_project_import_path(@project), class: 'btn' do
#{ _('Import repository') }
diff --git a/app/views/projects/notes/_actions.html.haml b/app/views/projects/notes/_actions.html.haml
index 7de7dd3b98b..d725098752d 100644
--- a/app/views/projects/notes/_actions.html.haml
+++ b/app/views/projects/notes/_actions.html.haml
@@ -45,7 +45,7 @@
- if note_editable
.note-actions-item
- = button_tag title: 'Edit comment', class: 'note-action-button js-note-edit has-tooltip btn btn-transparent', data: { container: 'body' } do
+ = button_tag title: 'Edit comment', class: 'note-action-button js-note-edit has-tooltip btn btn-transparent', data: { container: 'body', qa_selector: 'edit_comment_button' } do
%span.link-highlight
= custom_icon('icon_pencil')
diff --git a/app/views/projects/notes/_more_actions_dropdown.html.haml b/app/views/projects/notes/_more_actions_dropdown.html.haml
index 2f0394538bb..8cf1b6b9294 100644
--- a/app/views/projects/notes/_more_actions_dropdown.html.haml
+++ b/app/views/projects/notes/_more_actions_dropdown.html.haml
@@ -2,7 +2,7 @@
- if note_editable || !is_current_user
.dropdown.more-actions.note-actions-item
- = button_tag title: 'More actions', class: 'note-action-button more-actions-toggle has-tooltip btn btn-transparent', data: { toggle: 'dropdown', container: 'body' } do
+ = button_tag title: 'More actions', class: 'note-action-button more-actions-toggle has-tooltip btn btn-transparent', data: { toggle: 'dropdown', container: 'body', qa_selector: 'more_actions_dropdown' } do
%span.icon
= custom_icon('ellipsis_v')
%ul.dropdown-menu.more-actions-dropdown.dropdown-open-left
@@ -14,6 +14,6 @@
= _('Report abuse to admin')
- if note_editable
%li
- = link_to note_url(note), method: :delete, data: { confirm: 'Are you sure you want to delete this comment?' }, remote: true, class: 'js-note-delete' do
+ = link_to note_url(note), method: :delete, data: { confirm: 'Are you sure you want to delete this comment?', qa_selector: 'delete_comment_button' }, remote: true, class: 'js-note-delete' do
%span.text-danger
= _('Delete comment')
diff --git a/app/views/projects/pages/show.html.haml b/app/views/projects/pages/show.html.haml
index 4b7810ea357..fc69b390bde 100644
--- a/app/views/projects/pages/show.html.haml
+++ b/app/views/projects/pages/show.html.haml
@@ -1,4 +1,4 @@
-- page_title 'Pages'
+- page_title _('Pages')
- if @project.pages_enabled?
%h3.page-title.with-button
diff --git a/app/views/projects/pipeline_schedules/_pipeline_schedule.html.haml b/app/views/projects/pipeline_schedules/_pipeline_schedule.html.haml
index 8d88f0be083..f48763cb544 100644
--- a/app/views/projects/pipeline_schedules/_pipeline_schedule.html.haml
+++ b/app/views/projects/pipeline_schedules/_pipeline_schedule.html.haml
@@ -27,8 +27,8 @@
%td
.float-right.btn-group
- if can?(current_user, :play_pipeline_schedule, pipeline_schedule)
- = link_to play_pipeline_schedule_path(pipeline_schedule), method: :post, title: s_('Play'), class: 'btn' do
- = icon('play')
+ = link_to play_pipeline_schedule_path(pipeline_schedule), method: :post, title: s_('Play'), class: 'btn btn-svg gl-display-flex gl-align-items-center gl-justify-content-center' do
+ = sprite_icon('play')
- if can?(current_user, :take_ownership_pipeline_schedule, pipeline_schedule)
= link_to take_ownership_pipeline_schedule_path(pipeline_schedule), method: :post, title: s_('PipelineSchedules|Take ownership'), class: 'btn' do
= s_('PipelineSchedules|Take ownership')
diff --git a/app/views/projects/pipelines/_stage.html.haml b/app/views/projects/pipelines/_stage.html.haml
index 3feb99cfcd7..0651ad6fdb8 100644
--- a/app/views/projects/pipelines/_stage.html.haml
+++ b/app/views/projects/pipelines/_stage.html.haml
@@ -1,5 +1,5 @@
- grouped_statuses = @stage.statuses.latest_ordered.group_by(&:status)
-- HasStatus::ORDERED_STATUSES.each do |ordered_status|
+- Ci::HasStatus::ORDERED_STATUSES.each do |ordered_status|
- grouped_statuses.fetch(ordered_status, []).each do |status|
%li
= render 'ci/status/dropdown_graph_badge', subject: status
diff --git a/app/views/projects/pipelines/_with_tabs.html.haml b/app/views/projects/pipelines/_with_tabs.html.haml
index 92edde034a6..590ae72a2ff 100644
--- a/app/views/projects/pipelines/_with_tabs.html.haml
+++ b/app/views/projects/pipelines/_with_tabs.html.haml
@@ -24,7 +24,7 @@
%li.js-tests-tab-link
= link_to test_report_project_pipeline_path(@project, @pipeline), data: { target: '#js-tab-tests', action: 'test_report', toggle: 'tab' }, class: 'test-tab' do
= s_('TestReports|Tests')
- %span.badge.badge-pill.js-test-report-badge-counter
+ %span.badge.badge-pill.js-test-report-badge-counter= Feature.enabled?(:build_report_summary, @project) ? @pipeline.test_report_summary.total_count : ''
= render_if_exists "projects/pipelines/tabs_holder", pipeline: @pipeline, project: @project
.tab-content
@@ -83,8 +83,10 @@
- if dag_pipeline_tab_enabled
#js-tab-dag.tab-pane
- #js-pipeline-dag-vue{ data: { pipeline_data_path: dag_project_pipeline_path(@project, @pipeline) } }
+ #js-pipeline-dag-vue{ data: { pipeline_data_path: dag_project_pipeline_path(@project, @pipeline), empty_svg_path: image_path('illustrations/empty-state/empty-dag-md.svg'), dag_doc_path: help_page_path('ci/yaml/README.md', anchor: 'needs')} }
#js-tab-tests.tab-pane
- #js-pipeline-tests-detail
+ #js-pipeline-tests-detail{ data: { full_report_endpoint: test_report_project_pipeline_path(@project, @pipeline, format: :json),
+ summary_endpoint: Feature.enabled?(:build_report_summary, @project) ? summary_project_pipeline_tests_path(@project, @pipeline, format: :json) : '',
+ count_endpoint: test_reports_count_project_pipeline_path(@project, @pipeline, format: :json) } }
= render_if_exists "projects/pipelines/tabs_content", pipeline: @pipeline, project: @project
diff --git a/app/views/projects/pipelines/index.html.haml b/app/views/projects/pipelines/index.html.haml
index fa4a77a692a..05f8a126a02 100644
--- a/app/views/projects/pipelines/index.html.haml
+++ b/app/views/projects/pipelines/index.html.haml
@@ -7,6 +7,7 @@
params: params.to_json,
"help-page-path" => help_page_path('ci/quick_start/README'),
"help-auto-devops-path" => help_page_path('topics/autodevops/index.md'),
+ "pipeline-schedule-url" => pipeline_schedules_path(@project),
"empty-state-svg-path" => image_path('illustrations/pipelines_empty.svg'),
"error-state-svg-path" => image_path('illustrations/pipelines_failed.svg'),
"no-pipelines-svg-path" => image_path('illustrations/pipelines_pending.svg'),
diff --git a/app/views/projects/pipelines/show.html.haml b/app/views/projects/pipelines/show.html.haml
index f39968eecef..2b2133b8296 100644
--- a/app/views/projects/pipelines/show.html.haml
+++ b/app/views/projects/pipelines/show.html.haml
@@ -20,6 +20,4 @@
- else
= render "projects/pipelines/with_tabs", pipeline: @pipeline
-.js-pipeline-details-vue{ data: { endpoint: project_pipeline_path(@project, @pipeline, format: :json),
- test_report_endpoint: test_report_project_pipeline_path(@project, @pipeline, format: :json),
- test_reports_count_endpoint: test_reports_count_project_pipeline_path(@project, @pipeline, format: :json) } }
+.js-pipeline-details-vue{ data: { endpoint: project_pipeline_path(@project, @pipeline, format: :json) } }
diff --git a/app/views/projects/project_members/index.html.haml b/app/views/projects/project_members/index.html.haml
index c24a9061146..ba964e5cd37 100644
--- a/app/views/projects/project_members/index.html.haml
+++ b/app/views/projects/project_members/index.html.haml
@@ -1,7 +1,8 @@
- page_title _("Members")
- can_admin_project_members = can?(current_user, :admin_project_member, @project)
-.row.prepend-top-default
+.js-remove-member-modal
+.row.gl-mt-3
.col-lg-12
- if project_can_be_shared?
%h4
diff --git a/app/views/projects/project_templates/_built_in_templates.html.haml b/app/views/projects/project_templates/_built_in_templates.html.haml
index eb41a3e0785..43352952b37 100644
--- a/app/views/projects/project_templates/_built_in_templates.html.haml
+++ b/app/views/projects/project_templates/_built_in_templates.html.haml
@@ -1,6 +1,6 @@
- Gitlab::ProjectTemplate.all.each do |template|
.template-option.d-flex.align-items-center{ data: { qa_selector: 'template_option_row' } }
- .logo.append-right-10.px-1
+ .logo.gl-mr-3.px-1
= image_tag template.logo, size: 32, class: "btn-template-icon icon-#{template.name}"
.description
%strong
@@ -9,7 +9,7 @@
.text-muted
= template.description
.controls.d-flex.align-items-center
- %a.btn.btn-default.append-right-10{ href: template.preview, rel: 'noopener noreferrer', target: '_blank', data: { track_label: "template_preview", track_property: template.name, track_event: "click_button", track_value: "" } }
+ %a.btn.btn-default.gl-mr-3{ href: template.preview, rel: 'noopener noreferrer', target: '_blank', data: { track_label: "template_preview", track_property: template.name, track_event: "click_button", track_value: "" } }
= _("Preview")
%label.btn.btn-success.template-button.choose-template.gl-mb-0{ for: template.name }
%input{ type: "radio", autocomplete: "off", name: "project[template_name]", id: template.name, value: template.name, data: { track_label: "template_use", track_property: template.name, track_event: "click_button", track_value: "" } }
diff --git a/app/views/projects/project_templates/_project_fields_form.html.haml b/app/views/projects/project_templates/_project_fields_form.html.haml
index c96010550d8..201e2d5b5fb 100644
--- a/app/views/projects/project_templates/_project_fields_form.html.haml
+++ b/app/views/projects/project_templates/_project_fields_form.html.haml
@@ -5,7 +5,7 @@
.input-group.template-input-group
.input-group-prepend
.input-group-text
- .selected-icon.append-right-10
+ .selected-icon.gl-mr-3
.selected-template
.input-group-append
%button.btn.btn-default.change-template{ type: "button" }
diff --git a/app/views/projects/protected_branches/shared/_matching_branch.html.haml b/app/views/projects/protected_branches/shared/_matching_branch.html.haml
index 2c76bf87945..9145be5d2f2 100644
--- a/app/views/projects/protected_branches/shared/_matching_branch.html.haml
+++ b/app/views/projects/protected_branches/shared/_matching_branch.html.haml
@@ -3,7 +3,7 @@
= link_to matching_branch.name, project_ref_path(@project, matching_branch.name), class: 'ref-name'
- if @project.root_ref?(matching_branch.name)
- %span.badge.badge-info.prepend-left-5 default
+ %span.badge.badge-info.gl-ml-2 default
%td
- commit = @project.commit(matching_branch.name)
= link_to(commit.short_id, project_commit_path(@project, commit.id), class: 'commit-sha')
diff --git a/app/views/projects/protected_branches/show.html.haml b/app/views/projects/protected_branches/show.html.haml
index ffaf118a5e3..c671757a603 100644
--- a/app/views/projects/protected_branches/show.html.haml
+++ b/app/views/projects/protected_branches/show.html.haml
@@ -1,6 +1,6 @@
-- page_title @protected_ref.name, "Protected Branches"
+- page_title @protected_ref.name, _("Protected Branches")
-.row.prepend-top-default.append-bottom-default
+.row.gl-mt-3.gl-mb-3
.col-lg-3
%h4.gl-mt-0.ref-name
= @protected_ref.name
diff --git a/app/views/projects/protected_tags/_create_protected_tag.html.haml b/app/views/projects/protected_tags/_create_protected_tag.html.haml
index f53b81cada6..d19a6401fc8 100644
--- a/app/views/projects/protected_tags/_create_protected_tag.html.haml
+++ b/app/views/projects/protected_tags/_create_protected_tag.html.haml
@@ -3,6 +3,7 @@
= dropdown_tag('Select',
options: { toggle_class: 'js-allowed-to-create wide',
dropdown_class: 'dropdown-menu-selectable capitalize-header',
- data: { field_name: 'protected_tag[create_access_levels_attributes][0][access_level]', input_id: 'create_access_levels_attributes' }})
+ dropdown_qa_selector: 'access_levels_content',
+ data: { field_name: 'protected_tag[create_access_levels_attributes][0][access_level]', input_id: 'create_access_levels_attributes', qa_selector: 'access_levels_dropdown' }})
= render 'projects/protected_tags/shared/create_protected_tag'
diff --git a/app/views/projects/protected_tags/shared/_create_protected_tag.html.haml b/app/views/projects/protected_tags/shared/_create_protected_tag.html.haml
index 020e6e187a6..8a6ae53a7c4 100644
--- a/app/views/projects/protected_tags/shared/_create_protected_tag.html.haml
+++ b/app/views/projects/protected_tags/shared/_create_protected_tag.html.haml
@@ -25,4 +25,4 @@
= yield :create_access_levels
.card-footer
- = f.submit 'Protect', class: 'btn-success btn', disabled: true
+ = f.submit 'Protect', class: 'btn-success btn', disabled: true, data: { qa_selector: 'protect_tag_button' }
diff --git a/app/views/projects/protected_tags/shared/_dropdown.html.haml b/app/views/projects/protected_tags/shared/_dropdown.html.haml
index 824a8604f6f..9c7f532fa29 100644
--- a/app/views/projects/protected_tags/shared/_dropdown.html.haml
+++ b/app/views/projects/protected_tags/shared/_dropdown.html.haml
@@ -6,7 +6,7 @@
footer_content: true,
data: { show_no: true, show_any: true, show_upcoming: true,
selected: params[:protected_tag_name],
- project_id: @project.try(:id) } }) do
+ project_id: @project.try(:id), qa_selector: 'tags_dropdown' } }) do
%ul.dropdown-footer-list
%li
diff --git a/app/views/projects/protected_tags/shared/_index.html.haml b/app/views/projects/protected_tags/shared/_index.html.haml
index b0c87ac8c17..4bf3ce09fc7 100644
--- a/app/views/projects/protected_tags/shared/_index.html.haml
+++ b/app/views/projects/protected_tags/shared/_index.html.haml
@@ -1,6 +1,6 @@
- expanded = expanded_by_default?
-%section.settings.no-animate#js-protected-tags-settings{ class: ('expanded' if expanded) }
+%section.settings.no-animate#js-protected-tags-settings{ class: ('expanded' if expanded), data: { qa_selector: 'protected_tag_settings_content' } }
.settings-header
%h4
Protected Tags
diff --git a/app/views/projects/protected_tags/shared/_matching_tag.html.haml b/app/views/projects/protected_tags/shared/_matching_tag.html.haml
index 133c76cd2ad..bf030d36cd6 100644
--- a/app/views/projects/protected_tags/shared/_matching_tag.html.haml
+++ b/app/views/projects/protected_tags/shared/_matching_tag.html.haml
@@ -3,7 +3,7 @@
= link_to matching_tag.name, project_ref_path(@project, matching_tag.name), class: 'ref-name'
- if @project.root_ref?(matching_tag.name)
- %span.badge.badge-info.prepend-left-5 default
+ %span.badge.badge-info.gl-ml-2 default
%td
- commit = @project.commit(matching_tag.name)
= link_to(commit.short_id, project_commit_path(@project, commit.id), class: 'commit-sha')
diff --git a/app/views/projects/protected_tags/shared/_protected_tag.html.haml b/app/views/projects/protected_tags/shared/_protected_tag.html.haml
index cc6f0309123..b0563163c9c 100644
--- a/app/views/projects/protected_tags/shared/_protected_tag.html.haml
+++ b/app/views/projects/protected_tags/shared/_protected_tag.html.haml
@@ -3,7 +3,7 @@
%span.ref-name= protected_tag.name
- if @project.root_ref?(protected_tag.name)
- %span.badge.badge-info.prepend-left-5 default
+ %span.badge.badge-info.gl-ml-2 default
%td
- if protected_tag.wildcard?
- matching_tags = protected_tag.matching(repository.tags)
diff --git a/app/views/projects/protected_tags/show.html.haml b/app/views/projects/protected_tags/show.html.haml
index 6f4535a0b3f..c8052e6ae8d 100644
--- a/app/views/projects/protected_tags/show.html.haml
+++ b/app/views/projects/protected_tags/show.html.haml
@@ -1,6 +1,6 @@
-- page_title @protected_ref.name, "Protected Tags"
+- page_title @protected_ref.name, _("Protected Tags")
-.row.prepend-top-default.append-bottom-default
+.row.gl-mt-3.gl-mb-3
.col-lg-3
%h4.gl-mt-0.ref-name
= @protected_ref.name
diff --git a/app/views/projects/refs/logs_tree.js.haml b/app/views/projects/refs/logs_tree.js.haml
deleted file mode 100644
index 506bf54b3f8..00000000000
--- a/app/views/projects/refs/logs_tree.js.haml
+++ /dev/null
@@ -1,23 +0,0 @@
-- @logs.each do |content_data|
- - file_name = content_data[:file_name]
- - commit = content_data[:commit]
- - next unless commit
-
- :plain
- var row = $("table.table_#{@hex_path} tr.file_#{hexdigest(file_name)}");
- row.find("td.tree-time-ago").html('#{escape_javascript time_ago_with_tooltip(commit.committed_date)}');
- row.find("td.tree-commit").html('#{escape_javascript render("projects/tree/tree_commit_column", commit: commit)}');
-
- = render_if_exists 'projects/refs/logs_tree_lock_label', lock_label: content_data[:lock_label]
-
-- if @more_log_url
- :plain
- if($('#tree-slider').length) {
- // Load more commit logs for each file in tree
- // if we still on the same page
- var url = "#{escape_javascript(@more_log_url)}";
- gl.utils.ajaxGet(url);
- }
-
-:plain
- gl.utils.localTimeAgo($('.js-timeago', 'table.table_#{@hex_path} tbody'));
diff --git a/app/views/projects/releases/new.html.haml b/app/views/projects/releases/new.html.haml
new file mode 100644
index 00000000000..4348035a324
--- /dev/null
+++ b/app/views/projects/releases/new.html.haml
@@ -0,0 +1,3 @@
+- page_title s_('Releases|New Release')
+
+#js-new-release-page{ data: data_for_new_release_page }
diff --git a/app/views/projects/serverless/functions/index.html.haml b/app/views/projects/serverless/functions/index.html.haml
index 2f1da453c0a..b21965915a2 100644
--- a/app/views/projects/serverless/functions/index.html.haml
+++ b/app/views/projects/serverless/functions/index.html.haml
@@ -1,6 +1,6 @@
- @content_class = "limit-container-width" unless fluid_layout
-- breadcrumb_title 'Serverless'
-- page_title 'Serverless'
+- breadcrumb_title _('Serverless')
+- page_title _('Serverless')
- status_path = project_serverless_functions_path(@project, format: :json)
- clusters_path = project_clusters_path(@project)
diff --git a/app/views/projects/services/_form.html.haml b/app/views/projects/services/_form.html.haml
index e6761807409..2e49e74a9b3 100644
--- a/app/views/projects/services/_form.html.haml
+++ b/app/views/projects/services/_form.html.haml
@@ -1,4 +1,7 @@
-.row.prepend-top-default.append-bottom-default
+- if lookup_context.template_exists?('top', "projects/services/#{@service.to_param}", true)
+ = render "projects/services/#{@service.to_param}/top"
+
+.row.gl-mt-3.gl-mb-3
.col-lg-4
%h4.gl-mt-0
= @service.title
@@ -11,10 +14,10 @@
%p= @service.detailed_description
.col-lg-8
= form_for(@service, as: :service, url: scoped_integration_path(@service), method: :put, html: { class: 'gl-show-field-errors integration-settings-form js-integration-settings-form', data: { 'can-test' => @service.can_test?, 'test-url' => test_project_service_path(@project, @service) } }) do |form|
- = render 'shared/service_settings', form: form, service: @service
- .footer-block.row-content-block
+ = render 'shared/service_settings', form: form, integration: @service
+ .footer-block.row-content-block{ :class => "#{'gl-display-none' if @service.is_a?(AlertsService)}" }
%input{ id: 'services_redirect_to', type: 'hidden', name: 'redirect_to', value: request.referrer }
- = service_save_button
+ = service_save_button(disabled: @service.is_a?(AlertsService))
&nbsp;
= link_to _('Cancel'), project_settings_integrations_path(@project), class: 'btn btn-cancel'
diff --git a/app/views/projects/services/alerts/_help.html.haml b/app/views/projects/services/alerts/_help.html.haml
index 4b09d1d9d0e..7abd198bea5 100644
--- a/app/views/projects/services/alerts/_help.html.haml
+++ b/app/views/projects/services/alerts/_help.html.haml
@@ -1,6 +1 @@
-.js-alerts-service-settings{ data: { activated: @service.activated?.to_s,
- form_path: scoped_integration_path(@service),
- authorization_key: @service.token,
- url: @service.url || _('<namespace / project>'),
- alerts_setup_url: help_page_path('user/project/integrations/generic_alerts.html', anchor: 'setting-up-generic-alerts'),
- alerts_usage_url: help_page_path('user/project/operations/alert_management.html') } }
+.js-alerts-service-settings{ data: alerts_settings_data(disabled: true) }
diff --git a/app/views/projects/services/alerts/_top.html.haml b/app/views/projects/services/alerts/_top.html.haml
new file mode 100644
index 00000000000..ebc93978832
--- /dev/null
+++ b/app/views/projects/services/alerts/_top.html.haml
@@ -0,0 +1,8 @@
+.row
+ .col-lg-12
+ .gl-alert.gl-alert-info.js-alerts-moved-alert{ role: 'alert' }
+ = sprite_icon('information-o', size: 16, css_class: 'gl-icon gl-alert-icon gl-alert-icon-no-title')
+ .gl-alert-body
+ = _('You can now manage alert endpoint configuration in the Alerts section on the Operations settings page. Fields on this page have been deprecated.')
+ .gl-alert-actions
+ = link_to _('Visit settings page'), project_settings_operations_path(@project, anchor: 'js-alert-management-settings'), class: 'btn gl-alert-action btn-info new-gl-button'
diff --git a/app/views/projects/services/prometheus/_configuration_banner.html.haml b/app/views/projects/services/prometheus/_configuration_banner.html.haml
index dfcb1c5d240..b4e8458d8b9 100644
--- a/app/views/projects/services/prometheus/_configuration_banner.html.haml
+++ b/app/views/projects/services/prometheus/_configuration_banner.html.haml
@@ -12,14 +12,14 @@
.svg-container
= image_tag 'illustrations/monitoring/getting_started.svg'
.col-sm-10
- %p.text-success.prepend-top-default
+ %p.text-success.gl-mt-3
= s_('PrometheusService|Prometheus is being automatically managed on your clusters')
= link_to s_('PrometheusService|Manage clusters'), project_clusters_path(project), class: 'btn'
- else
.col-sm-2
= image_tag 'illustrations/monitoring/loading.svg'
.col-sm-10
- %p.prepend-top-default
+ %p.gl-mt-3
= s_('PrometheusService|Automatically deploy and configure Prometheus on your clusters to monitor your project’s environments')
= link_to s_('PrometheusService|Install Prometheus on clusters'), project_clusters_path(project), class: 'btn btn-success'
diff --git a/app/views/projects/services/prometheus/_custom_metrics.html.haml b/app/views/projects/services/prometheus/_custom_metrics.html.haml
index 210d0f37d65..3642460467b 100644
--- a/app/views/projects/services/prometheus/_custom_metrics.html.haml
+++ b/app/views/projects/services/prometheus/_custom_metrics.html.haml
@@ -3,7 +3,7 @@
.col-lg-3
%p
= s_('PrometheusService|Custom metrics require Prometheus installed on a cluster with environment scope "*" OR a manually configured Prometheus to be available.')
- = link_to s_('PrometheusService|More information'), help_page_path('user/project/integrations/prometheus', anchor: 'adding-custom-metrics'), target: '_blank', rel: "noopener noreferrer"
+ = link_to s_('PrometheusService|More information'), help_page_path('operations/metrics/index.md', anchor: 'adding-custom-metrics'), target: '_blank', rel: "noopener noreferrer"
.col-lg-9
.card.custom-monitored-metrics.js-panel-custom-monitored-metrics{ data: { qa_selector: 'custom_metrics_container', active_custom_metrics: project_prometheus_metrics_path(project), environments_data: environments_list_data, service_active: "#{@service.active}" } }
diff --git a/app/views/projects/services/prometheus/_external_alerts.html.haml b/app/views/projects/services/prometheus/_external_alerts.html.haml
index 24ff0cc88a3..b27b1ab8723 100644
--- a/app/views/projects/services/prometheus/_external_alerts.html.haml
+++ b/app/views/projects/services/prometheus/_external_alerts.html.haml
@@ -3,6 +3,6 @@
- notify_url = notify_project_prometheus_alerts_url(@project, format: :json)
- authorization_key = @project.alerting_setting.try(:token)
-- learn_more_url = help_page_path('user/project/integrations/prometheus', anchor: 'external-prometheus-instances')
+- learn_more_url = help_page_path('operations/metrics/alerts.md', anchor: 'external-prometheus-instances')
-#js-settings-prometheus-alerts{ data: { notify_url: notify_url, authorization_key: authorization_key, change_key_url: reset_alerting_token_project_settings_operations_path(@project), learn_more_url: learn_more_url } }
+#js-settings-prometheus-alerts{ data: { notify_url: notify_url, authorization_key: authorization_key, change_key_url: reset_alerting_token_project_settings_operations_path(@project), learn_more_url: learn_more_url, disabled: true } }
diff --git a/app/views/projects/services/prometheus/_help.html.haml b/app/views/projects/services/prometheus/_help.html.haml
index 1b5b794a7aa..c5b3fd31efa 100644
--- a/app/views/projects/services/prometheus/_help.html.haml
+++ b/app/views/projects/services/prometheus/_help.html.haml
@@ -1,7 +1,7 @@
- if @project
= render 'projects/services/prometheus/configuration_banner', project: @project, service: @service
-%h4.append-bottom-default
+%h4.gl-mb-3
= s_('PrometheusService|Manual configuration')
%p
= s_('PrometheusService|Select the Active checkbox to override the Auto Configuration with custom settings. If unchecked, Auto Configuration settings are used.')
diff --git a/app/views/projects/services/prometheus/_metrics.html.haml b/app/views/projects/services/prometheus/_metrics.html.haml
index 3bd5f69f67e..9f5160f3dd5 100644
--- a/app/views/projects/services/prometheus/_metrics.html.haml
+++ b/app/views/projects/services/prometheus/_metrics.html.haml
@@ -33,5 +33,5 @@
.flash-notice
.flash-text
= s_("PrometheusService|To set up automatic monitoring, add the environment variable %{variable} to exporter's queries." % { variable: "<code>$CI_ENVIRONMENT_SLUG</code>" }).html_safe
- = link_to s_('PrometheusService|More information'), help_page_path('user/project/integrations/prometheus', anchor: 'metrics-and-labels')
+ = link_to s_('PrometheusService|More information'), help_page_path('operations/metrics/dashboards/variables.md', anchor: 'query-variables')
%ul.list-unstyled.metrics-list.js-missing-var-metrics-list
diff --git a/app/views/projects/services/prometheus/_show.html.haml b/app/views/projects/services/prometheus/_show.html.haml
index 728a52f024f..9ce61ed5c13 100644
--- a/app/views/projects/services/prometheus/_show.html.haml
+++ b/app/views/projects/services/prometheus/_show.html.haml
@@ -3,7 +3,7 @@
%h4.gl-mt-0
= s_('PrometheusService|Metrics')
-.row.append-bottom-default.prometheus-metrics-monitoring.js-prometheus-metrics-monitoring
+.row.gl-mb-3.prometheus-metrics-monitoring.js-prometheus-metrics-monitoring
= render 'projects/services/prometheus/metrics', project: @project
= render 'projects/services/prometheus/external_alerts', project: @project
diff --git a/app/views/projects/services/prometheus/_top.html.haml b/app/views/projects/services/prometheus/_top.html.haml
new file mode 100644
index 00000000000..338414be5ab
--- /dev/null
+++ b/app/views/projects/services/prometheus/_top.html.haml
@@ -0,0 +1,10 @@
+- return unless @service.manual_configuration?
+
+.row
+ .col-lg-12
+ .gl-alert.gl-alert-info.js-alerts-moved-alert{ role: 'alert' }
+ = sprite_icon('information-o', size: 16, css_class: 'gl-icon gl-alert-icon gl-alert-icon-no-title')
+ .gl-alert-body
+ = s_('AlertSettings|You can now set up alert endpoints for manually configured Prometheus instances in the Alerts section on the Operations settings page. Alert endpoint fields on this page have been deprecated.')
+ .gl-alert-actions
+ = link_to _('Visit settings page'), project_settings_operations_path(@project, anchor: 'js-alert-management-settings'), class: 'btn gl-alert-action btn-info gl-button'
diff --git a/app/views/projects/settings/_general.html.haml b/app/views/projects/settings/_general.html.haml
index 5eeebe4160f..3ffa029a25d 100644
--- a/app/views/projects/settings/_general.html.haml
+++ b/app/views/projects/settings/_general.html.haml
@@ -31,7 +31,7 @@
= render_if_exists 'shared/repository_size_limit_setting', form: f, type: :project
- .form-group.prepend-top-default.append-bottom-20
+ .form-group.gl-mt-3.append-bottom-20
.avatar-container.s90
= project_icon(@project, alt: _('Project avatar'), class: 'avatar project-avatar s90')
= f.label :avatar, _('Project avatar'), class: 'label-bold d-block'
diff --git a/app/views/projects/settings/access_tokens/index.html.haml b/app/views/projects/settings/access_tokens/index.html.haml
index 092f9c2333c..4992288a8c8 100644
--- a/app/views/projects/settings/access_tokens/index.html.haml
+++ b/app/views/projects/settings/access_tokens/index.html.haml
@@ -4,7 +4,7 @@
- type_plural = _('project access tokens')
- @content_class = 'limit-container-width' unless fluid_layout
-.row.prepend-top-default
+.row.gl-mt-3
.col-lg-4.profile-settings-sidebar
%h4.gl-mt-0
= page_title
diff --git a/app/views/projects/settings/ci_cd/_autodevops_form.html.haml b/app/views/projects/settings/ci_cd/_autodevops_form.html.haml
index 8b84acb67c1..7284b4bb55d 100644
--- a/app/views/projects/settings/ci_cd/_autodevops_form.html.haml
+++ b/app/views/projects/settings/ci_cd/_autodevops_form.html.haml
@@ -40,18 +40,18 @@
= form.radio_button :deploy_strategy, 'continuous', class: 'form-check-input'
= form.label :deploy_strategy_continuous, class: 'form-check-label' do
= s_('CICD|Continuous deployment to production')
- = link_to icon('question-circle'), help_page_path('topics/autodevops/index.md', anchor: 'auto-deploy'), target: '_blank'
+ = link_to icon('question-circle'), help_page_path('topics/autodevops/stages.md', anchor: 'auto-deploy'), target: '_blank'
.form-check
= form.radio_button :deploy_strategy, 'timed_incremental', class: 'form-check-input'
= form.label :deploy_strategy_timed_incremental, class: 'form-check-label' do
= s_('CICD|Continuous deployment to production using timed incremental rollout')
- = link_to icon('question-circle'), help_page_path('topics/autodevops/index.md', anchor: 'timed-incremental-rollout-to-production-premium'), target: '_blank'
+ = link_to icon('question-circle'), help_page_path('topics/autodevops/customize.md', anchor: 'timed-incremental-rollout-to-production-premium'), target: '_blank'
.form-check
= form.radio_button :deploy_strategy, 'manual', class: 'form-check-input'
= form.label :deploy_strategy_manual, class: 'form-check-label' do
= s_('CICD|Automatic deployment to staging, manual deployment to production')
- = link_to icon('question-circle'), help_page_path('topics/autodevops/index.md', anchor: 'incremental-rollout-to-production-premium'), target: '_blank'
+ = link_to icon('question-circle'), help_page_path('topics/autodevops/customize.md', anchor: 'incremental-rollout-to-production-premium'), target: '_blank'
= f.submit _('Save changes'), class: "btn btn-success prepend-top-15", data: { qa_selector: 'save_changes_button' }
diff --git a/app/views/projects/settings/ci_cd/_form.html.haml b/app/views/projects/settings/ci_cd/_form.html.haml
index a1809cecafb..e8e5a5f0256 100644
--- a/app/views/projects/settings/ci_cd/_form.html.haml
+++ b/app/views/projects/settings/ci_cd/_form.html.haml
@@ -1,4 +1,4 @@
-.row.prepend-top-default
+.row.gl-mt-3
.col-lg-12
= form_for @project, url: project_settings_ci_cd_path(@project, anchor: 'js-general-pipeline-settings') do |f|
= form_errors(@project)
@@ -147,5 +147,5 @@
%hr
-.row.prepend-top-default
+.row.gl-mt-3
= render partial: 'badge', collection: @badges
diff --git a/app/views/projects/settings/ci_cd/show.html.haml b/app/views/projects/settings/ci_cd/show.html.haml
index 4e14426a069..b5452fcca55 100644
--- a/app/views/projects/settings/ci_cd/show.html.haml
+++ b/app/views/projects/settings/ci_cd/show.html.haml
@@ -66,11 +66,11 @@
%section.settings.no-animate#js-registry-policies{ class: ('expanded' if expanded) }
.settings-header
%h4
- = _("Container Registry tag expiration policy")
- = link_to icon('question-circle'), help_page_path('user/packages/container_registry/index', anchor: 'expiration-policy'), target: '_blank', rel: 'noopener noreferrer'
+ = _("Cleanup policy for tags")
%button.btn.js-settings-toggle{ type: 'button' }
= expanded ? _('Collapse') : _('Expand')
%p
- = _("Expiration policy for the Container Registry is a perfect solution for keeping the Registry space down while still enjoying the full power of GitLab CI/CD.")
+ = _("Save space and find tags in the Container Registry more easily. Enable the cleanup policy to remove stale tags and keep only the ones you need.")
+ = link_to _('More information'), help_page_path('user/packages/container_registry/index', anchor: 'cleanup-policy', target: '_blank', rel: 'noopener noreferrer')
.settings-content
= render 'projects/registry/settings/index'
diff --git a/app/views/projects/settings/integrations/show.html.haml b/app/views/projects/settings/integrations/show.html.haml
index e7a509abc8b..d9068bde847 100644
--- a/app/views/projects/settings/integrations/show.html.haml
+++ b/app/views/projects/settings/integrations/show.html.haml
@@ -3,7 +3,7 @@
- page_title _('Integrations')
- if show_webhooks_moved_alert?
- .gl-alert.gl-alert-info.js-webhooks-moved-alert.prepend-top-default{ role: 'alert', data: { feature_id: UserCalloutsHelper::WEBHOOKS_MOVED, dismiss_endpoint: user_callouts_path } }
+ .gl-alert.gl-alert-info.js-webhooks-moved-alert.gl-mt-3{ role: 'alert', data: { feature_id: UserCalloutsHelper::WEBHOOKS_MOVED, dismiss_endpoint: user_callouts_path } }
= sprite_icon('information-o', size: 16, css_class: 'gl-icon gl-alert-icon gl-alert-icon-no-title')
%button.js-close.gl-alert-dismiss{ type: 'button', 'aria-label' => _('Dismiss') }
= sprite_icon('close', size: 16, css_class: 'gl-icon')
diff --git a/app/views/projects/settings/operations/_alert_management.html.haml b/app/views/projects/settings/operations/_alert_management.html.haml
new file mode 100644
index 00000000000..f8f3ecb6273
--- /dev/null
+++ b/app/views/projects/settings/operations/_alert_management.html.haml
@@ -0,0 +1,14 @@
+- return unless can?(current_user, :admin_operations, @project)
+- expanded = expanded_by_default?
+
+%section.settings.no-animate#js-alert-management-settings{ class: ('expanded' if expanded) }
+ .settings-header
+ %h3{ :class => "h4" }
+ = _('Alerts')
+ %button.btn.js-settings-toggle{ type: 'button' }
+ = _('Expand')
+ %p
+ = _('Display alerts from all your monitoring tools directly within GitLab.')
+ = link_to _('More information'), help_page_path('user/project/operations/alert_management'), target: '_blank', rel: 'noopener noreferrer'
+ .settings-content
+ .js-alerts-settings{ data: alerts_settings_data }
diff --git a/app/views/projects/settings/operations/_configuration_banner.html.haml b/app/views/projects/settings/operations/_configuration_banner.html.haml
index bdbc9b7d69d..69bbd0edac7 100644
--- a/app/views/projects/settings/operations/_configuration_banner.html.haml
+++ b/app/views/projects/settings/operations/_configuration_banner.html.haml
@@ -12,13 +12,13 @@
.svg-container
= image_tag 'illustrations/monitoring/getting_started.svg'
.col-sm-10
- %p.text-success.prepend-top-default
+ %p.text-success.gl-mt-3
= s_('PrometheusService|Prometheus is being automatically managed on your clusters')
= link_to s_('PrometheusService|Manage clusters'), project_clusters_path(project), class: 'btn'
- else
.col-sm-2
= image_tag 'illustrations/monitoring/loading.svg'
.col-sm-10
- %p.prepend-top-default
+ %p.gl-mt-3
= s_('PrometheusService|Automatically deploy and configure Prometheus on your clusters to monitor your project’s environments')
= link_to s_('PrometheusService|Install Prometheus on clusters'), project_clusters_path(project), class: 'btn btn-success'
diff --git a/app/views/projects/settings/operations/_incidents.html.haml b/app/views/projects/settings/operations/_incidents.html.haml
index 3b1b0a00380..e7236cdec69 100644
--- a/app/views/projects/settings/operations/_incidents.html.haml
+++ b/app/views/projects/settings/operations/_incidents.html.haml
@@ -1,32 +1 @@
-- templates = []
-- setting = project_incident_management_setting
-- templates = setting.available_issue_templates.map { |t| [t.name, t.key] }
-
-%section.settings.no-animate.qa-incident-management-settings{ data: { qa_selector: 'incidents_settings_content' } }
- .settings-header
- %h3{ :class => "h4" }= _('Incidents')
- %button.btn.js-settings-toggle{ type: 'button' }
- = _('Expand')
- %p
- = _('Action to take when receiving an alert.')
- = link_to help_page_path('user/project/integrations/prometheus', anchor: 'taking-action-on-incidents-ultimate') do
- = _('More information')
- .settings-content
- = form_for @project, url: project_settings_operations_path(@project), method: :patch do |f|
- = form_errors(@project.incident_management_setting)
- .form-group
- = f.fields_for :incident_management_setting_attributes, setting do |form|
- .form-group
- = form.check_box :create_issue, data: { qa_selector: 'create_issue_checkbox' }
- = form.label :create_issue, _('Create an issue. Issues are created for each alert triggered.'), class: 'form-check-label'
- .form-group.col-sm-8
- = form.label :issue_template_key, class: 'label-bold' do
- = _('Issue template (optional)')
- = link_to icon('question-circle'), help_page_path('user/project/description_templates', anchor: 'creating-issue-templates'), target: '_blank', rel: 'noopener noreferrer'
- .select-wrapper
- = form.select :issue_template_key, templates, {include_blank: 'No template selected'}, class: "form-control select-control", data: { qa_selector: 'incident_templates_dropdown' }
- = icon('chevron-down')
- .form-group
- = form.check_box :send_email
- = form.label :send_email, _('Send a separate email notification to Developers.'), class: 'form-check-label'
- = f.submit _('Save changes'), class: 'btn btn-success', data: { qa_selector: 'save_changes_button' }
+.js-incidents-settings{ data: operations_settings_data }
diff --git a/app/views/projects/settings/operations/_prometheus.html.haml b/app/views/projects/settings/operations/_prometheus.html.haml
index b0fa750e131..7ccc829662d 100644
--- a/app/views/projects/settings/operations/_prometheus.html.haml
+++ b/app/views/projects/settings/operations/_prometheus.html.haml
@@ -11,7 +11,7 @@
- if @project
= render 'projects/settings/operations/configuration_banner', project: @project, service: service
- %b.append-bottom-default
+ %b.gl-mb-3
= s_('PrometheusService|Manual configuration')
%p
= s_('PrometheusService|Select the Active checkbox to override the Auto Configuration with custom settings. If unchecked, Auto Configuration settings are used.')
diff --git a/app/views/projects/settings/operations/show.html.haml b/app/views/projects/settings/operations/show.html.haml
index 9e4fbf81ca4..103828ee0a0 100644
--- a/app/views/projects/settings/operations/show.html.haml
+++ b/app/views/projects/settings/operations/show.html.haml
@@ -2,6 +2,7 @@
- page_title _('Operations Settings')
- breadcrumb_title _('Operations Settings')
+= render 'projects/settings/operations/alert_management', alerts_service: alerts_service, prometheus_service: prometheus_service
= render 'projects/settings/operations/incidents'
= render 'projects/settings/operations/error_tracking'
= render 'projects/settings/operations/prometheus', service: prometheus_service if Feature.enabled?(:settings_operations_prometheus_service)
diff --git a/app/views/projects/show.html.haml b/app/views/projects/show.html.haml
index 17bc10af58a..4a521f2f46e 100644
--- a/app/views/projects/show.html.haml
+++ b/app/views/projects/show.html.haml
@@ -1,4 +1,5 @@
- breadcrumb_title _("Details")
+- page_title _("Projects")
- @content_class = "limit-container-width" unless fluid_layout
= content_for :meta_tags do
@@ -6,10 +7,6 @@
= render partial: 'flash_messages', locals: { project: @project }
-- if !@project.empty_repo? && can?(current_user, :download_code, @project) && !vue_file_list_enabled?
- - signatures_path = project_signatures_path(@project, @project.default_branch)
- .js-signature-container{ data: { 'signatures-path': signatures_path } }
-
%div{ class: [("limit-container-width" unless fluid_layout)] }
= render "projects/last_push"
diff --git a/app/views/projects/sidebar/_issues_service_desk.html.haml b/app/views/projects/sidebar/_issues_service_desk.html.haml
new file mode 100644
index 00000000000..2730fe37f28
--- /dev/null
+++ b/app/views/projects/sidebar/_issues_service_desk.html.haml
@@ -0,0 +1,3 @@
+= nav_link(controller: :issues, action: :service_desk ) do
+ = link_to service_desk_project_issues_path(@project), title: 'Service Desk' do
+ = _('Service Desk')
diff --git a/app/views/projects/snippets/_actions.html.haml b/app/views/projects/snippets/_actions.html.haml
index 6aedab36e1b..e4645101765 100644
--- a/app/views/projects/snippets/_actions.html.haml
+++ b/app/views/projects/snippets/_actions.html.haml
@@ -14,7 +14,7 @@
= link_to _('Submit as spam'), mark_as_spam_project_snippet_path(@project, @snippet), method: :post, class: 'btn btn-grouped btn-spam', title: _('Submit as spam')
- if can?(current_user, :create_snippet, @project) || can?(current_user, :update_snippet, @snippet)
.d-block.d-sm-none.dropdown
- %button.btn.btn-default.btn-block.gl-mb-0.prepend-top-5{ data: { toggle: "dropdown" } }
+ %button.btn.btn-default.btn-block.gl-mb-0.gl-mt-2{ data: { toggle: "dropdown" } }
= _('Options')
= icon('caret-down')
.dropdown-menu.dropdown-menu-full-width
diff --git a/app/views/projects/starrers/_starrer.html.haml b/app/views/projects/starrers/_starrer.html.haml
index 377d62f8abd..d8a2c72d9ce 100644
--- a/app/views/projects/starrers/_starrer.html.haml
+++ b/app/views/projects/starrers/_starrer.html.haml
@@ -13,7 +13,7 @@
%span.cgray= starrer.user.to_reference
- if starrer.user == current_user
- %span.badge.badge-success.prepend-left-5= _("It's you")
+ %span.badge.badge-success.gl-ml-2= _("It's you")
.block-truncated
= time_ago_with_tooltip(starrer.starred_since)
diff --git a/app/views/projects/tags/_tag.html.haml b/app/views/projects/tags/_tag.html.haml
index 79a00b00fa6..59c7d0401d1 100644
--- a/app/views/projects/tags/_tag.html.haml
+++ b/app/views/projects/tags/_tag.html.haml
@@ -26,7 +26,7 @@
= _("Release")
= link_to release.name, project_releases_path(@project, anchor: release.tag), class: 'tag-release-link'
- if release.description.present?
- .md.prepend-top-default
+ .md.gl-mt-3
= markdown_field(release, :description)
.row-fixed-content.controls.flex-row
@@ -38,5 +38,5 @@
- if can?(current_user, :admin_tag, @project)
= link_to edit_project_tag_release_path(@project, tag.name), class: 'btn btn-edit has-tooltip', title: s_('TagsPage|Edit release notes'), data: { container: "body" } do
= icon("pencil")
- = link_to project_tag_path(@project, tag.name), class: "btn btn-remove remove-row has-tooltip prepend-left-10 #{protected_tag?(@project, tag) ? 'disabled' : ''}", title: s_('TagsPage|Delete tag'), method: :delete, data: { confirm: s_('TagsPage|Deleting the %{tag_name} tag cannot be undone. Are you sure?') % { tag_name: tag.name }, container: 'body' }, remote: true do
+ = link_to project_tag_path(@project, tag.name), class: "btn btn-remove remove-row has-tooltip gl-ml-3 #{protected_tag?(@project, tag) ? 'disabled' : ''}", title: s_('TagsPage|Delete tag'), method: :delete, data: { confirm: s_('TagsPage|Deleting the %{tag_name} tag cannot be undone. Are you sure?') % { tag_name: tag.name }, container: 'body' }, remote: true do
= icon("trash-o")
diff --git a/app/views/projects/tags/index.html.haml b/app/views/projects/tags/index.html.haml
index 6ad7cf1848f..e3d3f2226a8 100644
--- a/app/views/projects/tags/index.html.haml
+++ b/app/views/projects/tags/index.html.haml
@@ -24,7 +24,7 @@
%li
= link_to title, filter_tags_path(sort: value), class: ("is-active" if @sort == value)
- if can?(current_user, :admin_tag, @project)
- = link_to new_project_tag_path(@project), class: 'btn btn-success new-tag-btn' do
+ = link_to new_project_tag_path(@project), class: 'btn btn-success new-tag-btn', data: { qa_selector: "new_tag_button" } do
= s_('TagsPage|New tag')
= link_to project_tags_path(@project, rss_url_options), title: _("Tags feed"), class: 'btn d-none d-sm-inline-block has-tooltip' do
= icon("rss")
diff --git a/app/views/projects/tags/new.html.haml b/app/views/projects/tags/new.html.haml
index 5aabfdd022a..c32318df7cc 100644
--- a/app/views/projects/tags/new.html.haml
+++ b/app/views/projects/tags/new.html.haml
@@ -14,7 +14,7 @@
.form-group.row
= label_tag :tag_name, nil, class: 'col-form-label col-sm-2'
.col-sm-10
- = text_field_tag :tag_name, params[:tag_name], required: true, autofocus: true, class: 'form-control'
+ = text_field_tag :tag_name, params[:tag_name], required: true, autofocus: true, class: 'form-control', data: { qa_selector: "tag_name_field" }
.form-group.row
= label_tag :ref, 'Create from', class: 'col-form-label col-sm-2'
.col-sm-10.create-from
@@ -29,7 +29,7 @@
.form-group.row
= label_tag :message, nil, class: 'col-form-label col-sm-2'
.col-sm-10
- = text_area_tag :message, @message, required: false, class: 'form-control', rows: 5
+ = text_area_tag :message, @message, required: false, class: 'form-control', rows: 5, data: { qa_selector: "tag_message_field" }
.form-text.text-muted
= tag_description_help_text
%hr
@@ -40,17 +40,17 @@
- link_start = '<a href="%{url}" rel="noopener noreferrer" target="_blank">'.html_safe
- releases_page_path = project_releases_path(@project)
- releases_page_link_start = link_start % { url: releases_page_path }
- - docs_url = help_page_path('user/project/releases/index.md', anchor: 'creating-a-release')
+ - docs_url = help_page_path('user/project/releases/index.md', anchor: 'create-a-release')
- docs_link_start = link_start % { url: docs_url }
- link_end = '</a>'.html_safe
- replacements = { releases_page_link_start: releases_page_link_start, docs_link_start: docs_link_start, link_end: link_end }
= s_('TagsPage|Optionally, create a public Release of your project, based on this tag. Release notes are displayed on the %{releases_page_link_start}Releases%{link_end} page. %{docs_link_start}More information%{link_end}').html_safe % replacements
= render layout: 'shared/md_preview', locals: { url: preview_markdown_path(@project), referenced_users: true } do
- = render 'shared/zen', attr: :release_description, classes: 'note-textarea', placeholder: s_('TagsPage|Write your release notes or drag files here…'), current_text: @release_description
+ = render 'shared/zen', attr: :release_description, classes: 'note-textarea', placeholder: s_('TagsPage|Write your release notes or drag files here…'), current_text: @release_description, qa_selector: 'release_notes_field'
= render 'shared/notes/hints'
.form-actions
- = button_tag s_('TagsPage|Create tag'), class: 'btn btn-success'
+ = button_tag s_('TagsPage|Create tag'), class: 'btn btn-success', data: { qa_selector: "create_tag_button" }
= link_to s_('TagsPage|Cancel'), project_tags_path(@project), class: 'btn btn-cancel'
-# haml-lint:disable InlineJavaScript
%script#availableRefs{ type: "application/json" }= @project.repository.ref_names.to_json.html_safe
diff --git a/app/views/projects/tags/releases/edit.html.haml b/app/views/projects/tags/releases/edit.html.haml
index a3746808440..896dbe454e6 100644
--- a/app/views/projects/tags/releases/edit.html.haml
+++ b/app/views/projects/tags/releases/edit.html.haml
@@ -1,6 +1,6 @@
-- add_to_breadcrumbs "Tags", project_tags_path(@project)
+- add_to_breadcrumbs _("Tags"), project_tags_path(@project)
- breadcrumb_title @tag.name
-- page_title "Edit", @tag.name, "Tags"
+- page_title _("Edit"), @tag.name, _("Tags")
.sub-header-block.no-bottom-space
.oneline
@@ -14,6 +14,6 @@
= render 'shared/zen', f: f, attr: :description, classes: 'note-textarea', placeholder: "Write your release notes or drag files here…"
= render 'shared/notes/hints'
.error-alert
- .prepend-top-default
+ .gl-mt-3
= f.submit 'Save changes', class: 'btn btn-success'
= link_to "Cancel", project_tag_path(@project, @tag.name), class: "btn btn-default btn-cancel"
diff --git a/app/views/projects/tags/show.html.haml b/app/views/projects/tags/show.html.haml
index 6f53a687fb9..edb0577cebd 100644
--- a/app/views/projects/tags/show.html.haml
+++ b/app/views/projects/tags/show.html.haml
@@ -9,7 +9,7 @@
.top-area.multi-line.flex-wrap
.nav-text
.title
- %span.item-title.ref-name
+ %span.item-title.ref-name{ data: { qa_selector: 'tag_name_content' } }
= icon('tag')
= @tag.name
- if protected_tag?(@project, @tag)
@@ -56,12 +56,12 @@
%i.fa.fa-trash-o
- if @tag.message.present?
- %pre.wrap
+ %pre.wrap{ data: { qa_selector: 'tag_message_content' } }
= strip_signature(@tag.message)
-.append-bottom-default.prepend-top-default
+.gl-mb-3.gl-mt-3
- if @release.description.present?
- .description.md
+ .description.md{ data: { qa_selector: 'tag_release_notes_content' } }
= markdown_field(@release, :description)
- else
= s_('TagsPage|This tag has no release notes.')
diff --git a/app/views/projects/tree/_readme.html.haml b/app/views/projects/tree/_readme.html.haml
index 3e3804ae204..6d2bdda8254 100644
--- a/app/views/projects/tree/_readme.html.haml
+++ b/app/views/projects/tree/_readme.html.haml
@@ -1,5 +1,5 @@
- if readme.rich_viewer
- %article.file-holder.readme-holder{ id: 'readme', class: [("limited-width-container" unless fluid_layout), ("js-show-on-root" if vue_file_list_enabled?)] }
+ %article.file-holder.readme-holder{ id: 'readme', class: [("limited-width-container" unless fluid_layout)] }
.js-file-title.file-title-flex-parent
.file-header-content
= blob_icon readme.mode, readme.name
diff --git a/app/views/projects/tree/_tree_commit_column.html.haml b/app/views/projects/tree/_tree_commit_column.html.haml
deleted file mode 100644
index 065fef606d5..00000000000
--- a/app/views/projects/tree/_tree_commit_column.html.haml
+++ /dev/null
@@ -1,3 +0,0 @@
-- full_title = markdown_field(commit, :full_title)
-%span.str-truncated
- = link_to_html full_title, project_commit_path(@project, commit.id), title: full_title, class: 'tree-commit-link'
diff --git a/app/views/projects/tree/_tree_content.html.haml b/app/views/projects/tree/_tree_content.html.haml
index c65420d537b..a4427c6eedb 100644
--- a/app/views/projects/tree/_tree_content.html.haml
+++ b/app/views/projects/tree/_tree_content.html.haml
@@ -10,7 +10,7 @@
- if @path.present?
%tr.tree-item
%td.tree-item-file-name
- = link_to "..", project_tree_path(@project, up_dir_path), class: 'prepend-left-10'
+ = link_to "..", project_tree_path(@project, up_dir_path), class: 'gl-ml-3'
%td
%td.d-none.d-sm-table-cell
diff --git a/app/views/projects/tree/_tree_header.html.haml b/app/views/projects/tree/_tree_header.html.haml
index d5f7673488f..eab6d750a02 100644
--- a/app/views/projects/tree/_tree_header.html.haml
+++ b/app/views/projects/tree/_tree_header.html.haml
@@ -5,92 +5,17 @@
.tree-ref-holder
= render 'shared/ref_switcher', destination: 'tree', path: @path, show_create: true
- - if on_top_of_branch?
- - addtotree_toggle_attributes = { 'data-toggle': 'dropdown', 'data-target': '.add-to-tree-dropdown', 'data-boundary': 'window' }
- - else
- - addtotree_toggle_attributes = { title: _("You can only add files when you are on a branch"), data: { container: 'body' }, class: 'disabled has-tooltip' }
-
- - if vue_file_list_enabled?
- #js-repo-breadcrumb{ data: breadcrumb_data_attributes }
- - else
- %ul.breadcrumb.repo-breadcrumb
- %li.breadcrumb-item
- = link_to project_tree_path(@project, @ref) do
- = @project.path
- - path_breadcrumbs do |title, path|
- %li.breadcrumb-item
- = link_to truncate(title, length: 40), project_tree_path(@project, tree_join(@ref, path))
-
- - if can_collaborate || can_create_mr_from_fork
- %li.breadcrumb-item
- %button.btn.add-to-tree.qa-add-to-tree{ addtotree_toggle_attributes, type: 'button' }
- = sprite_icon('plus', size: 16, css_class: 'float-left')
- = sprite_icon('chevron-down', size: 16, css_class: 'float-left')
- - if on_top_of_branch?
- .add-to-tree-dropdown
- %ul.dropdown-menu
- - if can_edit_tree?
- %li.dropdown-header
- #{ _('This directory') }
- %li
- = link_to project_new_blob_path(@project, @id), class: 'qa-new-file-option' do
- #{ _('New file') }
- %li
- = link_to '#modal-upload-blob', { 'data-target' => '#modal-upload-blob', 'data-toggle' => 'modal' } do
- #{ _('Upload file') }
- %li
- = link_to '#modal-create-new-dir', { 'data-target' => '#modal-create-new-dir', 'data-toggle' => 'modal' } do
- #{ _('New directory') }
- - elsif can_create_mr_from_fork
- %li
- - continue_params = { to: project_new_blob_path(@project, @id),
- notice: edit_in_new_fork_notice,
- notice_now: edit_in_new_fork_notice_now }
- - fork_path = project_forks_path(@project, namespace_key: current_user.namespace.id, continue: continue_params)
- = link_to fork_path, method: :post do
- #{ _('New file') }
- %li
- - continue_params = { to: request.fullpath,
- notice: edit_in_new_fork_notice + " Try to upload a file again.",
- notice_now: edit_in_new_fork_notice_now }
- - fork_path = project_forks_path(@project, namespace_key: current_user.namespace.id, continue: continue_params)
- = link_to fork_path, method: :post do
- #{ _('Upload file') }
- %li
- - continue_params = { to: request.fullpath,
- notice: edit_in_new_fork_notice + " Try to create a new directory again.",
- notice_now: edit_in_new_fork_notice_now }
- - fork_path = project_forks_path(@project, namespace_key: current_user.namespace.id, continue: continue_params)
- = link_to fork_path, method: :post do
- #{ _('New directory') }
-
- - if can?(current_user, :push_code, @project)
- %li.divider
- %li.dropdown-header
- #{ _('This repository') }
- %li
- = link_to new_project_branch_path(@project) do
- #{ _('New branch') }
- %li
- = link_to new_project_tag_path(@project) do
- #{ _('New tag') }
+ #js-repo-breadcrumb{ data: breadcrumb_data_attributes }
.tree-controls
.d-block.d-sm-flex.flex-wrap.align-items-start.gl-children-ml-sm-3<
= render_if_exists 'projects/tree/lock_link'
- - if vue_file_list_enabled?
- #js-tree-history-link.d-inline-block{ data: { history_link: project_commits_path(@project, @ref) } }
- - else
- = link_to s_('Commits|History'), project_commits_path(@project, @id), class: 'btn'
+ #js-tree-history-link.d-inline-block{ data: { history_link: project_commits_path(@project, @ref) } }
= render 'projects/find_file_link'
- if can_collaborate || current_user&.already_forked?(@project)
- - if vue_file_list_enabled?
- #js-tree-web-ide-link.d-inline-block
- - else
- = link_to ide_edit_path(@project, @ref, @path), class: 'btn btn-default qa-web-ide-button' do
- = _('Web IDE')
+ #js-tree-web-ide-link.d-inline-block
- elsif can_create_mr_from_fork
= link_to '#modal-confirm-fork', class: 'btn btn-default qa-web-ide-button', data: { target: '#modal-confirm-fork', toggle: 'modal'} do
= _('Web IDE')
diff --git a/app/views/projects/tree/_tree_row.html.haml b/app/views/projects/tree/_tree_row.html.haml
index 8a27ea66523..300cd5423bf 100644
--- a/app/views/projects/tree/_tree_row.html.haml
+++ b/app/views/projects/tree/_tree_row.html.haml
@@ -14,7 +14,7 @@
%a.str-truncated{ href: fast_project_blob_path(@project, tree_join(@id || @commit.id, tree_row_name)), title: tree_row_name }
%span= tree_row_name
- if @lfs_blob_ids.include?(tree_row.id)
- %span.badge.label-lfs.prepend-left-5 LFS
+ %span.badge.label-lfs.gl-ml-2 LFS
- elsif tree_row_type == :commit
= tree_icon('archive', tree_row.mode, tree_row.name)
diff --git a/app/views/projects/tree/show.html.haml b/app/views/projects/tree/show.html.haml
index 65f5bc31d2e..3dd12a7b641 100644
--- a/app/views/projects/tree/show.html.haml
+++ b/app/views/projects/tree/show.html.haml
@@ -1,13 +1,9 @@
- breadcrumb_title _("Repository")
- @content_class = "limit-container-width" unless fluid_layout
-- signatures_path = namespace_project_signatures_path(namespace_id: @project.namespace.full_path, project_id: @project.path, id: @last_commit)
- page_title @path.presence || _("Files"), @ref
= content_for :meta_tags do
= auto_discovery_link_tag(:atom, project_commits_url(@project, @ref, rss_url_options), title: "#{@project.name}:#{@ref} commits")
-- unless vue_file_list_enabled?
- .js-signature-container{ data: { 'signatures-path': signatures_path } }
-
= render 'projects/last_push'
= render 'projects/files', commit: @last_commit, project: @project, ref: @ref, content_url: project_tree_path(@project, @id)
diff --git a/app/views/projects/triggers/_index.html.haml b/app/views/projects/triggers/_index.html.haml
index 4ca070cb162..4e097f345c2 100644
--- a/app/views/projects/triggers/_index.html.haml
+++ b/app/views/projects/triggers/_index.html.haml
@@ -1,4 +1,4 @@
-.row.prepend-top-default.append-bottom-default.triggers-container
+.row.gl-mt-3.gl-mb-3.triggers-container
.col-lg-12
.card
.card-header
@@ -21,7 +21,7 @@
%th
= render partial: 'projects/triggers/trigger', collection: @triggers, as: :trigger
- else
- %p.settings-message.text-center.append-bottom-default
+ %p.settings-message.text-center.gl-mb-3
No triggers have been created yet. Add one using the form above.
.card-footer
@@ -38,7 +38,7 @@
%code REF_NAME
with the trigger token and the branch or tag name respectively.
- %h5.prepend-top-default
+ %h5.gl-mt-3
Use cURL
%p.light
@@ -51,7 +51,7 @@
-F token=TOKEN \
-F ref=REF_NAME \
#{builds_trigger_url(@project.id)}
- %h5.prepend-top-default
+ %h5.gl-mt-3
Use .gitlab-ci.yml
%p.light
@@ -66,7 +66,7 @@
stage: deploy
script:
- "curl -X POST -F token=TOKEN -F ref=REF_NAME #{builds_trigger_url(@project.id)}"
- %h5.prepend-top-default
+ %h5.gl-mt-3
Use webhook
%p.light
@@ -76,7 +76,7 @@
%pre
:plain
#{builds_trigger_url(@project.id, ref: 'REF_NAME')}?token=TOKEN
- %h5.prepend-top-default
+ %h5.gl-mt-3
Pass job variables
%p.light
diff --git a/app/views/projects/triggers/_trigger.html.haml b/app/views/projects/triggers/_trigger.html.haml
index d80248f7e80..3036e918160 100644
--- a/app/views/projects/triggers/_trigger.html.haml
+++ b/app/views/projects/triggers/_trigger.html.haml
@@ -31,7 +31,7 @@
- revoke_trigger_confirmation = "By revoking a trigger you will break any processes making use of it. Are you sure?"
- if can?(current_user, :admin_trigger, trigger)
= link_to edit_project_trigger_path(@project, trigger), method: :get, title: "Edit", class: "btn btn-default btn-sm" do
- %i.fa.fa-pencil
+ = sprite_icon('pencil')
- if can?(current_user, :manage_trigger, trigger)
= link_to project_trigger_path(@project, trigger), data: { confirm: revoke_trigger_confirmation }, method: :delete, title: "Revoke", class: "btn btn-default btn-warning btn-sm btn-trigger-revoke" do
%i.fa.fa-trash
diff --git a/app/views/projects/triggers/edit.html.haml b/app/views/projects/triggers/edit.html.haml
index e287f05fe6a..ee2714e1eb9 100644
--- a/app/views/projects/triggers/edit.html.haml
+++ b/app/views/projects/triggers/edit.html.haml
@@ -1,6 +1,6 @@
-- page_title "Trigger"
+- page_title _("Trigger")
-.row.prepend-top-default.append-bottom-default
+.row.gl-mt-3.gl-mb-3
.col-lg-12
%h4.gl-mt-0
Update trigger
diff --git a/app/views/projects/wikis/git_access.html.haml b/app/views/projects/wikis/git_access.html.haml
index 208dedc988b..1db4554541d 100644
--- a/app/views/projects/wikis/git_access.html.haml
+++ b/app/views/projects/wikis/git_access.html.haml
@@ -2,8 +2,7 @@
- page_title s_("WikiClone|Git Access"), _("Wiki")
.wiki-page-header.top-area.has-sidebar-toggle.py-3.flex-column.flex-lg-row
- %button.btn.btn-default.d-block.d-sm-block.d-md-none.float-right.sidebar-toggle.js-sidebar-wiki-toggle{ role: "button", type: "button" }
- = icon('angle-double-left')
+ = wiki_sidebar_toggle_button
.git-access-header.w-100.d-flex.flex-column.justify-content-center
%span
diff --git a/app/views/search/_category.html.haml b/app/views/search/_category.html.haml
index db7769fa743..d6e38ddd5c6 100644
--- a/app/views/search/_category.html.haml
+++ b/app/views/search/_category.html.haml
@@ -3,8 +3,8 @@
= search_filter_link 'users', _("Users")
.scrolling-tabs-container.inner-page-scroll-tabs.is-smaller
- .fade-left= icon('angle-left')
- .fade-right= icon('angle-right')
+ .fade-left= sprite_icon('chevron-lg-left', size: 12)
+ .fade-right= sprite_icon('chevron-lg-right', size: 12)
%ul.nav-links.search-filter.scrolling-tabs.nav.nav-tabs
- if @project
- if project_search_tabs?(:blobs)
diff --git a/app/views/search/results/_issue.html.haml b/app/views/search/results/_issue.html.haml
index 1f055cdfa31..b88e9a75053 100644
--- a/app/views/search/results/_issue.html.haml
+++ b/app/views/search/results/_issue.html.haml
@@ -4,7 +4,7 @@
= link_to namespace_project_issue_path(issue.project.namespace.becomes(Namespace), issue.project, issue) do
%span.term.str-truncated= issue.title
- if issue.closed?
- %span.badge.badge-danger.prepend-left-5= _("Closed")
+ %span.badge.badge-danger.gl-ml-2= _("Closed")
.float-right ##{issue.iid}
- if issue.description.present?
.description.term
diff --git a/app/views/search/results/_merge_request.html.haml b/app/views/search/results/_merge_request.html.haml
index 074bb9bce8d..45b6cb06753 100644
--- a/app/views/search/results/_merge_request.html.haml
+++ b/app/views/search/results/_merge_request.html.haml
@@ -3,9 +3,9 @@
= link_to namespace_project_merge_request_path(merge_request.target_project.namespace.becomes(Namespace), merge_request.target_project, merge_request) do
%span.term.str-truncated= merge_request.title
- if merge_request.merged?
- %span.badge.badge-primary.prepend-left-5= _("Merged")
+ %span.badge.badge-primary.gl-ml-2= _("Merged")
- elsif merge_request.closed?
- %span.badge.badge-danger.prepend-left-5= _("Closed")
+ %span.badge.badge-danger.gl-ml-2= _("Closed")
.float-right= merge_request.to_reference
- if merge_request.description.present?
.description.term
diff --git a/app/views/search/results/_note.html.haml b/app/views/search/results/_note.html.haml
index 6717939d034..b67bc71941a 100644
--- a/app/views/search/results/_note.html.haml
+++ b/app/views/search/results/_note.html.haml
@@ -4,7 +4,7 @@
.search-result-row
%h5.note-search-caption.str-truncated
- %i.fa.fa-comment
+ = sprite_icon('comment', size: 16, css_class: 'gl-vertical-align-text-bottom')
= link_to_member(project, note.author, avatar: false)
- link_to_project = link_to(project.full_name, project)
= _("commented on %{link_to_project}").html_safe % { link_to_project: link_to_project }
diff --git a/app/views/search/show.html.haml b/app/views/search/show.html.haml
index f300e1d4841..869890cdf31 100644
--- a/app/views/search/show.html.haml
+++ b/app/views/search/show.html.haml
@@ -7,7 +7,7 @@
= _('Search')
= render_if_exists 'search/form_elasticsearch', attrs: { class: 'ml-sm-auto' }
-.prepend-top-default
+.gl-mt-3
= render 'search/form'
- if @search_term
= render 'search/category'
diff --git a/app/views/sent_notifications/unsubscribe.html.haml b/app/views/sent_notifications/unsubscribe.html.haml
index 1eecbe3bc0e..7aeecf26c39 100644
--- a/app/views/sent_notifications/unsubscribe.html.haml
+++ b/app/views/sent_notifications/unsubscribe.html.haml
@@ -15,5 +15,5 @@
%p
= link_to _('Unsubscribe'), unsubscribe_sent_notification_path(@sent_notification, force: true),
- class: 'btn btn-primary append-right-10'
- = link_to _('Cancel'), new_user_session_path, class: 'btn append-right-10'
+ class: 'btn btn-primary gl-mr-3'
+ = link_to _('Cancel'), new_user_session_path, class: 'btn gl-mr-3'
diff --git a/app/views/shared/_commit_well.html.haml b/app/views/shared/_commit_well.html.haml
index 6f1fe9bfdc5..48fe258d01f 100644
--- a/app/views/shared/_commit_well.html.haml
+++ b/app/views/shared/_commit_well.html.haml
@@ -1,4 +1,4 @@
-.info-well.d-none.d-sm-block.project-last-commit.append-bottom-default
+.info-well.d-none.d-sm-block.project-last-commit.gl-mb-3
.well-segment
%ul.blob-commit-info
= render 'projects/commits/commit', commit: commit, ref: ref, project: project
diff --git a/app/views/shared/_event_filter.html.haml b/app/views/shared/_event_filter.html.haml
index 1b2e8d3799d..03534bf78d1 100644
--- a/app/views/shared/_event_filter.html.haml
+++ b/app/views/shared/_event_filter.html.haml
@@ -1,8 +1,8 @@
- show_group_events = local_assigns.fetch(:show_group_events, false)
.scrolling-tabs-container.inner-page-scroll-tabs.is-smaller.flex-fill
- .fade-left= icon('angle-left')
- .fade-right= icon('angle-right')
+ .fade-left= sprite_icon('chevron-lg-left', size: 12)
+ .fade-right= sprite_icon('chevron-lg-right', size: 12)
%ul.nav-links.event-filter.scrolling-tabs.nav.nav-tabs
= event_filter_link EventFilter::ALL, _('All'), s_('EventFilterBy|Filter by all')
- if event_filter_visible(:repository)
@@ -15,6 +15,8 @@
= render_if_exists 'events/epics_filter'
- if comments_visible?
= event_filter_link EventFilter::COMMENTS, _('Comments'), s_('EventFilterBy|Filter by comments')
- - if Feature.enabled?(:wiki_events) && (@project.nil? || @project.has_wiki?)
+ - if @project.nil? || @project.has_wiki?
= event_filter_link EventFilter::WIKI, _('Wiki'), s_('EventFilterBy|Filter by wiki')
+ - if event_filter_visible(:designs)
+ = event_filter_link EventFilter::DESIGNS, _('Designs'), s_('EventFilterBy|Filter by designs')
= event_filter_link EventFilter::TEAM, _('Team'), s_('EventFilterBy|Filter by team')
diff --git a/app/views/shared/_field.html.haml b/app/views/shared/_field.html.haml
index 2480014ea42..076c87400e0 100644
--- a/app/views/shared/_field.html.haml
+++ b/app/views/shared/_field.html.haml
@@ -22,7 +22,7 @@
- elsif type == 'checkbox'
= form.check_box name
- elsif type == 'select'
- = form.select name, options_for_select(choices, value ? value : default_choice), {}, { class: "form-control"} # rubocop:disable Style/RedundantCondition
+ = form.select name, options_for_select(choices, value || default_choice), {}, { class: "form-control"}
- elsif type == 'password'
= form.password_field name, autocomplete: "new-password", placeholder: placeholder, class: "form-control", required: value.blank? && required, data: { qa_selector: "#{name.downcase.gsub('\s', '')}_field" }
- if help
diff --git a/app/views/shared/_issuable_meta_data.html.haml b/app/views/shared/_issuable_meta_data.html.haml
index 7807371285c..d704eae2090 100644
--- a/app/views/shared/_issuable_meta_data.html.haml
+++ b/app/views/shared/_issuable_meta_data.html.haml
@@ -2,7 +2,7 @@
- issue_votes = @issuable_meta_data[issuable.id]
- upvotes, downvotes = issue_votes.upvotes, issue_votes.downvotes
- issuable_path = issuable_path(issuable, anchor: 'notes')
-- issuable_mr = @issuable_meta_data[issuable.id].merge_requests_count(current_user)
+- issuable_mr = @issuable_meta_data[issuable.id].merge_requests_count
- if issuable_mr > 0
%li.issuable-mr.d-none.d-sm-block.has-tooltip{ title: _('Related merge requests') }
@@ -11,15 +11,15 @@
- if upvotes > 0
%li.issuable-upvotes.d-none.d-sm-block.has-tooltip{ title: _('Upvotes') }
- = icon('thumbs-up')
+ = sprite_icon('thumb-up', size: 16, css_class: "vertical-align-middle")
= upvotes
- if downvotes > 0
%li.issuable-downvotes.d-none.d-sm-block.has-tooltip{ title: _('Downvotes') }
- = icon('thumbs-down')
+ = sprite_icon('thumb-down', size: 16, css_class: "vertical-align-middle")
= downvotes
%li.issuable-comments.d-none.d-sm-block
= link_to issuable_path, class: ['has-tooltip', ('no-comments' if note_count.zero?)], title: _('Comments') do
- = icon('comments')
+ = sprite_icon('comments', size: 16, css_class: 'gl-vertical-align-text-bottom')
= note_count
diff --git a/app/views/shared/_label_row.html.haml b/app/views/shared/_label_row.html.haml
index cd303dd7a3d..3d2ae772135 100644
--- a/app/views/shared/_label_row.html.haml
+++ b/app/views/shared/_label_row.html.haml
@@ -6,7 +6,7 @@
.label-name
= render_label(label, tooltip: false)
.label-description
- .append-right-default.prepend-left-default
+ .label-description-wrapper
- if label.description.present?
.description-text
= markdown_field(label, :description)
@@ -20,6 +20,6 @@
= link_to_label(label, type: :merge_request) { _('Merge requests') }
- if force_priority
&middot;
- %li.label-link-item.priority-badge.js-priority-badge.inline.prepend-left-10
+ %li.label-link-item.priority-badge.js-priority-badge.inline.gl-ml-3
.label-badge.label-badge-blue= _('Prioritized label')
= render_if_exists 'shared/label_row_epics_link', label: label
diff --git a/app/views/shared/_md_preview.html.haml b/app/views/shared/_md_preview.html.haml
index f5f24b2f0ce..c3818b9f7ae 100644
--- a/app/views/shared/_md_preview.html.haml
+++ b/app/views/shared/_md_preview.html.haml
@@ -11,10 +11,10 @@
.md-header
%ul.nav.nav-tabs.nav-links.clearfix
%li.md-header-tab.active
- %button.js-md-write-button{ tabindex: -1 }
+ %button.js-md-write-button
= _("Write")
%li.md-header-tab
- %button.js-md-preview-button{ tabindex: -1 }
+ %button.js-md-preview-button
= _("Preview")
%li.md-header-toolbar.active
diff --git a/app/views/shared/_milestone_expired.html.haml b/app/views/shared/_milestone_expired.html.haml
index 48a97a18ca9..2261e9e3121 100644
--- a/app/views/shared/_milestone_expired.html.haml
+++ b/app/views/shared/_milestone_expired.html.haml
@@ -1,6 +1,6 @@
- if milestone.expired? and not milestone.closed?
- .status-box.status-box-expired.append-bottom-5= _('Expired')
+ .status-box.status-box-expired.gl-mb-2= _('Expired')
- if milestone.upcoming?
- .status-box.status-box-mr-merged.append-bottom-5= _('Upcoming')
+ .status-box.status-box-mr-merged.gl-mb-2= _('Upcoming')
- if milestone.closed?
- .status-box.status-box-closed.append-bottom-5= _('Closed')
+ .status-box.status-box-closed.gl-mb-2= _('Closed')
diff --git a/app/views/shared/_milestones_sort_dropdown.html.haml b/app/views/shared/_milestones_sort_dropdown.html.haml
index 9a1db831ad3..06da990e071 100644
--- a/app/views/shared/_milestones_sort_dropdown.html.haml
+++ b/app/views/shared/_milestones_sort_dropdown.html.haml
@@ -1,4 +1,4 @@
-.dropdown.inline.prepend-left-10
+.dropdown.inline.gl-ml-3
%button.dropdown-menu-toggle{ type: 'button', data: { toggle: 'dropdown' } }
%span.light
- if @sort.present?
diff --git a/app/views/shared/_namespace_storage_limit_alert.html.haml b/app/views/shared/_namespace_storage_limit_alert.html.haml
deleted file mode 100644
index 95f27cde15b..00000000000
--- a/app/views/shared/_namespace_storage_limit_alert.html.haml
+++ /dev/null
@@ -1,26 +0,0 @@
-- return unless current_user
-
-- payload = namespace_storage_alert(namespace)
-- return if payload.empty?
-
-- alert_level = payload[:alert_level]
-- root_namespace = payload[:root_namespace]
-
-- style = namespace_storage_alert_style(alert_level)
-- icon = namespace_storage_alert_icon(alert_level)
-- link = namespace_storage_usage_link(root_namespace)
-
-%div{ class: [classes, 'js-namespace-storage-alert'] }
- .gl-pt-5.gl-pb-3
- .gl-alert{ class: "gl-alert-#{style}", role: 'alert' }
- = sprite_icon(icon, css_class: "gl-icon gl-alert-icon")
- .gl-alert-title
- %h4.gl-alert-title= payload[:usage_message]
- - if alert_level != :error
- %button.js-namespace-storage-alert-dismiss.gl-alert-dismiss.gl-cursor-pointer{ type: 'button', 'aria-label' => _('Dismiss'), data: { id: root_namespace.id, level: alert_level } }
- = sprite_icon('close', size: 16, css_class: 'gl-icon')
- .gl-alert-body
- = payload[:explanation_message]
- - if link
- .gl-alert-actions
- = link_to(_('Manage storage usage'), link, class: "btn gl-alert-action btn-md gl-button btn-#{style}")
diff --git a/app/views/shared/_service_settings.html.haml b/app/views/shared/_service_settings.html.haml
index 92b86c6fec1..7d93dca22f5 100644
--- a/app/views/shared/_service_settings.html.haml
+++ b/app/views/shared/_service_settings.html.haml
@@ -1,22 +1,23 @@
-= form_errors(@service)
+= form_errors(integration)
-- if lookup_context.template_exists?('help', "projects/services/#{@service.to_param}", true)
- = render "projects/services/#{@service.to_param}/help", subject: @service
-- elsif @service.help.present?
+- if lookup_context.template_exists?('help', "projects/services/#{integration.to_param}", true)
+ = render "projects/services/#{integration.to_param}/help", subject: integration
+- elsif integration.help.present?
.info-well
.well-segment
- = markdown @service.help
+ = markdown integration.help
.service-settings
- .js-vue-integration-settings{ data: { show_active: @service.show_active_box?.to_s, activated: (@service.active || @service.new_record?).to_s, type: @service.to_param, merge_request_events: @service.merge_requests_events.to_s,
-commit_events: @service.commit_events.to_s, enable_comments: @service.comment_on_event_enabled.to_s, comment_detail: @service.comment_detail, trigger_events: trigger_events_for_service, fields: fields_for_service } }
+ - if @admin_integration
+ .js-vue-admin-integration-settings{ data: integration_form_data(@admin_integration) }
+ .js-vue-integration-settings{ data: integration_form_data(integration) }
- - if show_service_trigger_events?
+ - if show_service_trigger_events?(integration)
.form-group.row
%label.col-form-label.col-sm-2= _('Trigger')
.col-sm-10
- - @service.configurable_events.each do |event|
+ - integration.configurable_events.each do |event|
.form-group
.form-check
= form.check_box service_event_field_name(event), class: 'form-check-input'
@@ -24,14 +25,14 @@ commit_events: @service.commit_events.to_s, enable_comments: @service.comment_on
%strong
= event.humanize
- - field = @service.event_field(event)
+ - field = integration.event_field(event)
- if field
= form.text_field field[:name], class: "form-control", placeholder: field[:placeholder]
%p.text-muted
- = @service.class.event_description(event)
+ = integration.class.event_description(event)
- unless integration_form_refactor?
- - @service.global_fields.each do |field|
+ - integration.global_fields.each do |field|
= render 'shared/field', form: form, field: field
diff --git a/app/views/shared/_sidebar_toggle_button.html.haml b/app/views/shared/_sidebar_toggle_button.html.haml
index c7546073e5c..1431966c83d 100644
--- a/app/views/shared/_sidebar_toggle_button.html.haml
+++ b/app/views/shared/_sidebar_toggle_button.html.haml
@@ -1,6 +1,6 @@
%a.toggle-sidebar-button.js-toggle-sidebar.qa-toggle-sidebar.rspec-toggle-sidebar{ role: "button", type: "button", title: "Toggle sidebar" }
- = sprite_icon('angle-double-left', css_class: 'icon-angle-double-left')
- = sprite_icon('angle-double-right', css_class: 'icon-angle-double-right')
+ = sprite_icon('chevron-double-lg-left', css_class: 'icon-chevron-double-lg-left')
+ = sprite_icon('chevron-double-lg-right', css_class: 'icon-chevron-double-lg-right')
%span.collapse-text= _("Collapse sidebar")
= button_tag class: 'close-nav-button', type: 'button' do
diff --git a/app/views/shared/_zen.html.haml b/app/views/shared/_zen.html.haml
index 8dd0e5a92a7..914409d0e65 100644
--- a/app/views/shared/_zen.html.haml
+++ b/app/views/shared/_zen.html.haml
@@ -14,6 +14,6 @@
supports_autocomplete: supports_autocomplete,
qa_selector: qa_selector }
- else
- = text_area_tag attr, current_text, class: classes, placeholder: placeholder
+ = text_area_tag attr, current_text, data: { qa_selector: qa_selector }, class: classes, placeholder: placeholder
%a.zen-control.zen-control-leave.js-zen-leave.gl-text-gray-700{ href: "#" }
= sprite_icon('compress', size: 16)
diff --git a/app/views/shared/access_tokens/_form.html.haml b/app/views/shared/access_tokens/_form.html.haml
index 680626f7880..820a6cbd15d 100644
--- a/app/views/shared/access_tokens/_form.html.haml
+++ b/app/views/shared/access_tokens/_form.html.haml
@@ -30,5 +30,5 @@
= f.label :scopes, _('Scopes'), class: 'label-bold'
= render 'shared/tokens/scopes_form', prefix: prefix, token: token, scopes: scopes
- .prepend-top-default
+ .gl-mt-3
= f.submit _('Create %{type}') % { type: type }, class: 'btn btn-success', data: { qa_selector: 'create_token_button' }
diff --git a/app/views/shared/access_tokens/_table.html.haml b/app/views/shared/access_tokens/_table.html.haml
index 5518c31cb06..55231cb9429 100644
--- a/app/views/shared/access_tokens/_table.html.haml
+++ b/app/views/shared/access_tokens/_table.html.haml
@@ -16,6 +16,9 @@
%tr
%th= _('Name')
%th= s_('AccessTokens|Created')
+ %th
+ = _('Last Used')
+ = link_to icon('question-circle'), help_page_path('user/profile/personal_access_tokens.md', anchor: 'token-activity'), target: '_blank'
%th= _('Expires')
%th= _('Scopes')
%th
@@ -25,9 +28,18 @@
%td= token.name
%td= token.created_at.to_date.to_s(:medium)
%td
+ - if token.last_used_at?
+ %span.token-last-used-label= _(time_ago_with_tooltip(token.last_used_at))
+ - else
+ %span.token-never-used-label= _('Never')
+ %td
- if token.expires?
- %span{ class: ('text-warning' if token.expires_soon?) }
- = _('In %{time_to_now}') % { time_to_now: distance_of_time_in_words_to_now(token.expires_at) }
+ - if token.expires_at.past? || token.expires_at.today?
+ %span{ class: 'text-danger has-tooltip', title: _('Expiration not enforced') }
+ = _('Expired')
+ - else
+ %span{ class: ('text-warning' if token.expires_soon?) }
+ = _('In %{time_to_now}') % { time_to_now: distance_of_time_in_words_to_now(token.expires_at) }
- else
%span.token-never-expires-label= _('Never')
%td= token.scopes.present? ? token.scopes.join(', ') : _('<no scopes selected>')
diff --git a/app/views/shared/boards/_show.html.haml b/app/views/shared/boards/_show.html.haml
index 902b6d19f82..b68c7cd4d52 100644
--- a/app/views/shared/boards/_show.html.haml
+++ b/app/views/shared/boards/_show.html.haml
@@ -13,7 +13,6 @@
- content_for :page_specific_javascripts do
-# haml-lint:disable InlineJavaScript
- %script#js-board-template{ type: "text/x-template" }= render "shared/boards/components/board"
%script#js-board-modal-filter{ type: "text/x-template" }= render "shared/issuable/search_bar", type: :boards_modal, show_sorting_dropdown: false
%script#js-board-promotion{ type: "text/x-template" }= render_if_exists "shared/promotions/promote_issue_board"
diff --git a/app/views/shared/boards/components/_board.html.haml b/app/views/shared/boards/components/_board.html.haml
deleted file mode 100644
index 2a5b72d478a..00000000000
--- a/app/views/shared/boards/components/_board.html.haml
+++ /dev/null
@@ -1,82 +0,0 @@
--# Please have a look at app/assets/javascripts/boards/components/board_column.vue
- This haml file is deprecated and will be deleted soon, please change the Vue app
- https://gitlab.com/gitlab-org/gitlab/-/issues/212300
-.board.h-100.px-2.align-top.ws-normal{ ":class" => '{ "is-draggable": !list.preset, "is-expandable": list.isExpandable, "is-collapsed": !list.isExpanded, "board-type-assignee": list.type === "assignee" }',
- ":data-id" => "list.id", data: { qa_selector: "board_list" } }
- .board-inner.d-flex.flex-column.position-relative.h-100.rounded
- %header.board-header{ ":class" => '{ "has-border": list.label && list.label.color, "position-relative": list.isExpanded, "position-absolute position-top-0 position-left-0 w-100 h-100": !list.isExpanded }', ":style" => "{ borderTopColor: (list.label && list.label.color ? list.label.color : null) }", data: { qa_selector: "board_list_header" } }
- %h3.board-title.m-0.d-flex.js-board-handle{ ":class" => '{ "user-can-drag": (!disabled && !list.preset), "border-bottom-0": !list.isExpanded }' }
-
- .board-title-caret.no-drag{ "v-if": "list.isExpandable",
- "aria-hidden": "true",
- ":aria-label": "caretTooltip",
- ":title": "caretTooltip",
- "v-tooltip": "",
- data: { placement: "bottom" },
- "@click": "toggleExpanded" }
- %i.fa.fa-fw{ ":class": '{ "fa-caret-right": list.isExpanded, "fa-caret-down": !list.isExpanded }' }
- = render_if_exists "shared/boards/components/list_milestone"
-
- %a.user-avatar-link.js-no-trigger{ "v-if": "list.type === \"assignee\"", ":href": "list.assignee.path" }
- -# haml-lint:disable AltText
- %img.avatar.s20.has-tooltip{ height: "20", width: "20", ":src": "list.assignee.avatar", ":alt": "list.assignee.name" }
-
- .board-title-text
- %span.board-title-main-text.block-truncated{ "v-if": "list.type !== \"label\"",
- ":title" => '((list.label && list.label.description) || list.title || "")',
- data: { container: "body" },
- ":class": "{ 'has-tooltip': !['backlog', 'closed'].includes(list.type), 'd-block': list.type === 'milestone' }" }
- {{ list.title }}
-
- %span.board-title-sub-text.prepend-left-5.has-tooltip{ "v-if": "list.type === \"assignee\"",
- ":title" => '(list.assignee && list.assignee.username || "")' }
- @{{ list.assignee.username }}
-
- %gl-label{ "v-if" => " list.type === \"label\"",
- ":background-color" => "list.label.color",
- ":title" => "list.label.title",
- ":description" => "list.label.description",
- "tooltipPlacement" => "bottom",
- ":size" => '(!list.isExpanded ? "sm" : "")',
- ":scoped" => "showScopedLabels(list.label)" }
-
- - if can?(current_user, :admin_list, current_board_parent)
- %board-delete{ "inline-template" => true,
- ":list" => "list",
- "v-if" => "!list.preset && list.id" }
- %button.board-delete.no-drag.p-0.border-0.has-tooltip.float-right{ type: "button", title: _("Delete list"), ":class": "{ 'd-none': !list.isExpanded }", "aria-label" => _("Delete list"), data: { placement: "bottom" }, "@click.stop" => "deleteBoard" }
- = icon("trash")
-
- .issue-count-badge.pr-0.no-drag.text-secondary{ "v-if" => "showBoardListAndBoardInfo" }
- %span.d-inline-flex
- %gl-tooltip{ ":target" => "() => $refs.issueCount", ":title" => "issuesTooltip" }
- %span.issue-count-badge-count{ "ref" => "issueCount" }
- %icon.mr-1{ name: "issues" }
- %issue-count{ ":maxIssueCount" => "list.maxIssueCount",
- ":issuesSize" => "list.issuesSize" }
- = render_if_exists "shared/boards/components/list_weight"
-
- %gl-button-group.board-list-button-group.pl-2{ "v-if" => "isNewIssueShown || isSettingsShown" }
- %gl-deprecated-button.issue-count-badge-add-button.no-drag{ type: "button",
- "@click" => "showNewIssueForm",
- "v-if" => "isNewIssueShown",
- ":class": "{ 'd-none': !list.isExpanded, 'rounded-right': isNewIssueShown && !isSettingsShown }",
- "aria-label" => _("New issue"),
- "ref" => "newIssueBtn" }
- = icon("plus")
- %gl-tooltip{ ":target" => "() => $refs.newIssueBtn" }
- = _("New Issue")
- = render_if_exists 'shared/boards/components/list_settings'
-
- %board-list{ "v-if" => "showBoardListAndBoardInfo",
- ":list" => "list",
- ":issues" => "list.issues",
- ":loading" => "list.loading",
- ":disabled" => "disabled",
- ":issue-link-base" => "issueLinkBase",
- ":root-path" => "rootPath",
- ":groupId" => ((current_board_parent.id if @group) || 'null'),
- "ref" => "board-list" }
- - if can?(current_user, :admin_list, current_board_parent)
- %board-blank-state{ "v-if" => 'list.id == "blank"' }
- = render_if_exists 'shared/boards/board_promotion_state'
diff --git a/app/views/shared/dashboard/_no_filter_selected.html.haml b/app/views/shared/dashboard/_no_filter_selected.html.haml
index 32246dac4c7..48c844d93e8 100644
--- a/app/views/shared/dashboard/_no_filter_selected.html.haml
+++ b/app/views/shared/dashboard/_no_filter_selected.html.haml
@@ -1,6 +1,6 @@
.row.empty-state.text-center
.col-12
- .svg-130.prepend-top-default
+ .svg-130.gl-mt-3
= image_tag 'illustrations/issue-dashboard_results-without-filter.svg'
.col-12
.text-content
diff --git a/app/views/shared/deploy_tokens/_form.html.haml b/app/views/shared/deploy_tokens/_form.html.haml
index 512644518fa..8d74e12e943 100644
--- a/app/views/shared/deploy_tokens/_form.html.haml
+++ b/app/views/shared/deploy_tokens/_form.html.haml
@@ -45,5 +45,5 @@
= label_tag ("deploy_token_write_package_registry"), 'write_package_registry', class: 'label-bold form-check-label'
.text-secondary= s_('DeployTokens|Allows write access to the package registry')
- .prepend-top-default
+ .gl-mt-3
= f.submit s_('DeployTokens|Create deploy token'), class: 'btn btn-success qa-create-deploy-token'
diff --git a/app/views/shared/deploy_tokens/_new_deploy_token.html.haml b/app/views/shared/deploy_tokens/_new_deploy_token.html.haml
index a9728dc841f..738f2f9db70 100644
--- a/app/views/shared/deploy_tokens/_new_deploy_token.html.haml
+++ b/app/views/shared/deploy_tokens/_new_deploy_token.html.haml
@@ -8,11 +8,11 @@
= text_field_tag 'deploy-token-user', deploy_token.username, readonly: true, class: 'deploy-token-field form-control js-select-on-focus qa-deploy-token-user'
.input-group-append
= clipboard_button(text: deploy_token.username, title: s_('DeployTokens|Copy username'), placement: 'left')
- %span.deploy-token-help-block.prepend-top-5.text-success= s_("DeployTokens|Use this username as a login.")
+ %span.deploy-token-help-block.gl-mt-2.text-success= s_("DeployTokens|Use this username as a login.")
.form-group
.input-group
= text_field_tag 'deploy-token', deploy_token.token, readonly: true, class: 'deploy-token-field form-control js-select-on-focus qa-deploy-token'
.input-group-append
= clipboard_button(text: deploy_token.token, title: s_('DeployTokens|Copy deploy token'), placement: 'left')
- %span.deploy-token-help-block.prepend-top-5.text-danger= s_("DeployTokens|Use this token as a password. Make sure you save it - you won't be able to access it again.")
+ %span.deploy-token-help-block.gl-mt-2.text-danger= s_("DeployTokens|Use this token as a password. Make sure you save it - you won't be able to access it again.")
diff --git a/app/views/shared/empty_states/_wikis.html.haml b/app/views/shared/empty_states/_wikis.html.haml
index ff5ee801969..656acafd416 100644
--- a/app/views/shared/empty_states/_wikis.html.haml
+++ b/app/views/shared/empty_states/_wikis.html.haml
@@ -11,6 +11,10 @@
%p.text-left
= messages.dig(:writable, :body)
= create_link
+ - if show_enable_confluence_integration?(@wiki.container)
+ = link_to s_('WikiEmpty|Enable the Confluence Wiki integration'),
+ edit_project_service_path(@project, :confluence),
+ class: 'btn', title: s_('WikiEmpty|Enable the Confluence Wiki integration')
- elsif @project && can?(current_user, :read_issue, @project)
- issues_link = link_to s_('WikiEmptyIssueMessage|issue tracker'), project_issues_path(@project)
diff --git a/app/views/shared/empty_states/_wikis_layout.html.haml b/app/views/shared/empty_states/_wikis_layout.html.haml
index d44017299b8..3b100f832b2 100644
--- a/app/views/shared/empty_states/_wikis_layout.html.haml
+++ b/app/views/shared/empty_states/_wikis_layout.html.haml
@@ -1,4 +1,4 @@
-.row.empty-state
+.row.empty-state.empty-state-wiki
.col-12
.svg-content.qa-svg-content
= image_tag image_path
diff --git a/app/views/shared/empty_states/icons/_service_desk_callout.svg b/app/views/shared/empty_states/icons/_service_desk_callout.svg
new file mode 100644
index 00000000000..2886388279e
--- /dev/null
+++ b/app/views/shared/empty_states/icons/_service_desk_callout.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="78" height="82" viewBox="0 0 78 82"><g fill="none" fill-rule="evenodd"><path fill="#F9F9F9" d="M2.12 42c-.08.99-.12 1.99-.12 3 0 20.435 16.565 37 37 37s37-16.565 37-37c0-1.01-.04-2.01-.12-3C74.353 61.032 58.425 76 39 76 19.575 76 3.647 61.032 2.12 42z"/><path fill="#EEE" fill-rule="nonzero" d="M39 78C17.46 78 0 60.54 0 39S17.46 0 39 0s39 17.46 39 39-17.46 39-39 39zm0-4c19.33 0 35-15.67 35-35S58.33 4 39 4 4 19.67 4 39s15.67 35 35 35z"/><rect width="7" height="1" x="59" y="38" fill="#E1DBF2" rx=".5"/><path fill="#6B4FBB" d="M60.5 42a3.5 3.5 0 0 0 0-7v7z"/><rect width="7" height="1" x="12" y="38" fill="#E1DBF2" transform="matrix(-1 0 0 1 31 0)" rx=".5"/><path fill="#6B4FBB" d="M17.5 42a3.5 3.5 0 0 1 0-7v7z"/><path fill="#E1DBF1" fill-rule="nonzero" d="M39 58c10.493 0 19-8.507 19-19s-8.507-19-19-19-19 8.507-19 19 8.507 19 19 19zm0 4c-12.703 0-23-10.297-23-23s10.297-23 23-23 23 10.297 23 23-10.297 23-23 23z"/><path fill="#6B4FBB" d="M35 56a1 1 0 1 1 0-2 1 1 0 0 1 0 2zm4 0a1 1 0 1 1 0-2 1 1 0 0 1 0 2zm4 0a1 1 0 1 1 0-2 1 1 0 0 1 0 2z"/><path fill="#E1DBF1" fill-rule="nonzero" d="M26.5 40c0 4.143 3.355 7.5 7.494 7.5h10.012A7.497 7.497 0 0 0 51.5 40c0-4.143-3.355-7.5-7.494-7.5H33.994A7.497 7.497 0 0 0 26.5 40zm-3 0c0-5.799 4.698-10.5 10.494-10.5h10.012C49.802 29.5 54.5 34.2 54.5 40c0 5.799-4.698 10.5-10.494 10.5H33.994C28.198 50.5 23.5 45.8 23.5 40z"/><path fill="#6B4FBB" fill-rule="nonzero" d="M35.255 42.406a1 1 0 1 1 1.872-.703 2.001 2.001 0 0 0 3.76-.038 1 1 0 1 1 1.886.665 4 4 0 0 1-7.518.076zM31.5 40a1.5 1.5 0 1 1 0-3 1.5 1.5 0 0 1 0 3zm15 0a1.5 1.5 0 1 1 0-3 1.5 1.5 0 0 1 0 3z"/><path fill="#6B4FBB" d="M38 22h2a1 1 0 0 1 0 2h-2a1 1 0 0 1 0-2zm0 3h2a1 1 0 0 1 0 2h-2a1 1 0 0 1 0-2z" style="mix-blend-mode:multiply"/></g></svg> \ No newline at end of file
diff --git a/app/views/shared/empty_states/icons/_service_desk_empty_state.svg b/app/views/shared/empty_states/icons/_service_desk_empty_state.svg
new file mode 100644
index 00000000000..04c4870be07
--- /dev/null
+++ b/app/views/shared/empty_states/icons/_service_desk_empty_state.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="226" height="178" viewBox="0 0 226 178"><g fill="none" fill-rule="evenodd"><path fill="#EEE" fill-rule="nonzero" d="M109.496 165.895c2.06.108 4.113.134 6.158.08 1.104-.03 1.975-.95 1.945-2.055-.03-1.104-.95-1.975-2.055-1.945-1.94.053-3.886.028-5.84-.074-1.102-.057-2.043.79-2.1 1.893-.06 1.104.788 2.045 1.89 2.102zm18.408-1.245c2.02-.386 4.023-.853 6-1.4 1.066-.295 1.69-1.396 1.396-2.46-.295-1.066-1.397-1.69-2.46-1.396-1.875.52-3.772.96-5.686 1.327-1.085.208-1.797 1.255-1.59 2.34.207 1.085 1.255 1.797 2.34 1.59zm17.572-5.636c1.865-.86 3.696-1.795 5.486-2.803.962-.54 1.303-1.76.762-2.723-.542-.962-1.762-1.303-2.724-.762-1.697.955-3.43 1.84-5.2 2.656-1.002.464-1.44 1.652-.978 2.655.462 1.003 1.65 1.44 2.654.98zm44.342-74.897c-.142-2.056-.367-4.1-.674-6.127-.165-1.092-1.184-1.844-2.276-1.678-1.092.165-1.844 1.184-1.68 2.276.29 1.92.505 3.857.64 5.805.076 1.102 1.03 1.934 2.133 1.857 1.103-.076 1.934-1.03 1.858-2.133zm-3.505-18.144c-.632-1.956-1.343-3.884-2.13-5.78-.425-1.02-1.595-1.504-2.615-1.08-1.02.424-1.503 1.594-1.08 2.614.747 1.797 1.42 3.624 2.02 5.476.34 1.05 1.467 1.628 2.518 1.288 1.05-.34 1.627-1.466 1.287-2.517zm-7.754-16.73c-1.083-1.745-2.235-3.447-3.454-5.1-.655-.89-1.907-1.08-2.797-.423-.89.655-1.08 1.907-.424 2.796 1.155 1.568 2.247 3.18 3.273 4.835.58.94 1.814 1.23 2.753.647.938-.582 1.228-1.815.646-2.754zm-11.582-14.446c-1.468-1.437-2.993-2.814-4.572-4.128-.85-.708-2.11-.592-2.816.256-.707.85-.592 2.11.257 2.817 1.496 1.246 2.942 2.55 4.334 3.913.79.773 2.057.76 2.83-.03.772-.79.758-2.057-.032-2.83zm-101.422-4.91c-1.6 1.288-3.148 2.64-4.64 4.05-.802.76-.837 2.026-.078 2.828.76.802 2.025.837 2.827.078 1.415-1.338 2.882-2.62 4.4-3.84.86-.692.996-1.95.303-2.812-.692-.86-1.95-.996-2.812-.303zM52.7 43.062c-1.25 1.632-2.433 3.313-3.546 5.04-.6.93-.33 2.167.597 2.765.93.6 2.167.33 2.766-.597 1.055-1.637 2.176-3.23 3.36-4.777.67-.878.504-2.133-.374-2.804-.877-.672-2.132-.505-2.803.372zm-9.373 15.924c-.82 1.882-1.56 3.8-2.226 5.745-.356 1.047.2 2.183 1.247 2.54 1.045.358 2.182-.2 2.54-1.246.63-1.844 1.333-3.66 2.108-5.443.44-1.012-.023-2.19-1.036-2.63-1.014-.44-2.192.023-2.633 1.036zm-5.26 17.74c-.34 2.02-.6 4.058-.777 6.11-.096 1.102.72 2.07 1.82 2.167 1.1.095 2.07-.72 2.165-1.82.17-1.947.415-3.88.737-5.793.183-1.09-.552-2.12-1.64-2.304-1.09-.183-2.122.552-2.305 1.64zM74.87 155.55c1.772 1.038 3.585 2.005 5.437 2.897.995.48 2.19.062 2.67-.933.48-.995.062-2.19-.933-2.67-1.755-.845-3.473-1.76-5.152-2.745-.953-.56-2.178-.24-2.737.714-.558.954-.238 2.18.715 2.738zm16.97 7.34c1.966.578 3.96 1.078 5.975 1.498 1.082.225 2.14-.47 2.366-1.55.226-1.082-.468-2.14-1.55-2.366-1.91-.398-3.798-.872-5.662-1.42-1.06-.312-2.172.294-2.483 1.354-.312 1.06.294 2.17 1.354 2.483z"/><path fill="#F9F9F9" d="M2.12 130c-.08.99-.12 1.99-.12 3 0 20.435 16.565 37 37 37s37-16.565 37-37c0-1.01-.04-2.01-.12-3-1.527 19.032-17.455 34-36.88 34-19.425 0-35.353-14.968-36.88-34z"/><path fill="#EEE" fill-rule="nonzero" d="M39 166c-21.54 0-39-17.46-39-39s17.46-39 39-39 39 17.46 39 39-17.46 39-39 39zm0-4c19.33 0 35-15.67 35-35S58.33 92 39 92 4 107.67 4 127s15.67 35 35 35z"/><path fill="#FDC4A8" fill-rule="nonzero" d="M53.925 116.226c-.277-.144-.59-.226-.925-.226H25c-.323 0-.628.076-.898.212l14.663 13.406c.39.357.99.348 1.37-.02l13.79-13.372zm1.075 4.53L42.92 132.47c-1.898 1.84-4.902 1.885-6.854.1L23 120.624V138c0 1.105.895 2 2 2h28c1.105 0 2-.895 2-2v-17.244zM25 112h28c3.314 0 6 2.686 6 6v20c0 3.314-2.686 6-6 6H25c-3.314 0-6-2.686-6-6v-20c0-3.314 2.686-6 6-6z"/><g><path fill="#F9F9F9" d="M150.12 131c-.08.99-.12 1.99-.12 3 0 20.435 16.565 37 37 37s37-16.565 37-37c0-1.01-.04-2.01-.12-3-1.527 19.032-17.455 34-36.88 34-19.425 0-35.353-14.968-36.88-34z"/><path fill="#EEE" fill-rule="nonzero" d="M187 167c-21.54 0-39-17.46-39-39s17.46-39 39-39 39 17.46 39 39-17.46 39-39 39zm0-4c19.33 0 35-15.67 35-35s-15.67-35-35-35-35 15.67-35 35 15.67 35 35 35z"/><path fill="#E1DBF1" fill-rule="nonzero" d="M180.51 137H199c1.105 0 2-.895 2-2v-16c0-1.105-.895-2-2-2h-24c-1.105 0-2 .895-2 2v22.743l7.51-4.743zm1.157 4l-9.6 6.062c-.32.202-.69.31-1.067.31-1.105 0-2-.896-2-2V119c0-3.314 2.686-6 6-6h24c3.314 0 6 2.686 6 6v16c0 3.314-2.686 6-6 6h-17.333z"/><path fill="#6B4FBB" d="M180 129c-1.105 0-2-.895-2-2s.895-2 2-2 2 .895 2 2-.895 2-2 2zm7 0c-1.105 0-2-.895-2-2s.895-2 2-2 2 .895 2 2-.895 2-2 2zm7 0c-1.105 0-2-.895-2-2s.895-2 2-2 2 .895 2 2-.895 2-2 2z"/></g><g><path fill="#F9F9F9" d="M76.12 42c-.08.99-.12 1.99-.12 3 0 20.435 16.565 37 37 37s37-16.565 37-37c0-1.01-.04-2.01-.12-3-1.527 19.032-17.455 34-36.88 34-19.425 0-35.353-14.968-36.88-34z"/><path fill="#EEE" fill-rule="nonzero" d="M113 78c-21.54 0-39-17.46-39-39S91.46 0 113 0s39 17.46 39 39-17.46 39-39 39zm0-4c19.33 0 35-15.67 35-35S132.33 4 113 4 78 19.67 78 39s15.67 35 35 35z"/><g transform="translate(133 35)"><rect width="7" height="1" y="3" fill="#E1DBF2" rx=".5"/><path fill="#6B4FBB" d="M1.5 7C3.433 7 5 5.433 5 3.5S3.433 0 1.5 0v7z"/></g><g transform="matrix(-1 0 0 1 93 35)"><rect width="7" height="1" y="3" fill="#E1DBF2" rx=".5"/><path fill="#6B4FBB" d="M1.5 7C3.433 7 5 5.433 5 3.5S3.433 0 1.5 0v7z"/></g><path fill="#E1DBF1" fill-rule="nonzero" d="M113 58c10.493 0 19-8.507 19-19s-8.507-19-19-19-19 8.507-19 19 8.507 19 19 19zm0 4c-12.703 0-23-10.297-23-23s10.297-23 23-23 23 10.297 23 23-10.297 23-23 23z"/><path fill="#6B4FBB" d="M109 56c-.552 0-1-.448-1-1s.448-1 1-1 1 .448 1 1-.448 1-1 1zm4 0c-.552 0-1-.448-1-1s.448-1 1-1 1 .448 1 1-.448 1-1 1zm4 0c-.552 0-1-.448-1-1s.448-1 1-1 1 .448 1 1-.448 1-1 1z"/><path fill="#E1DBF1" fill-rule="nonzero" d="M97.5 40c0-5.8 4.698-10.5 10.494-10.5h10.012c5.796 0 10.494 4.7 10.494 10.5s-4.698 10.5-10.494 10.5h-10.012C102.198 50.5 97.5 45.8 97.5 40zm3 0c0 4.143 3.355 7.5 7.494 7.5h10.012c4.14 0 7.494-3.358 7.494-7.5 0-4.143-3.355-7.5-7.494-7.5h-10.012c-4.14 0-7.494 3.358-7.494 7.5z"/><path fill="#6B4FBB" fill-rule="nonzero" d="M109.255 42.406c-.195-.517.067-1.093.584-1.287.516-.196 1.093.066 1.287.583.29.774 1.033 1.297 1.873 1.297.855 0 1.608-.542 1.887-1.335.184-.52.755-.794 1.276-.61.52.183.794.754.61 1.275-.56 1.587-2.063 2.67-3.773 2.67-1.68 0-3.164-1.046-3.745-2.594zM105.5 40c-.828 0-1.5-.672-1.5-1.5s.672-1.5 1.5-1.5 1.5.672 1.5 1.5-.672 1.5-1.5 1.5zm15 0c-.828 0-1.5-.672-1.5-1.5s.672-1.5 1.5-1.5 1.5.672 1.5 1.5-.672 1.5-1.5 1.5z"/><path fill="#6B4FBB" d="M112 22h2c.552 0 1 .448 1 1s-.448 1-1 1h-2c-.552 0-1-.448-1-1s.448-1 1-1zm0 3h2c.552 0 1 .448 1 1s-.448 1-1 1h-2c-.552 0-1-.448-1-1s.448-1 1-1z" style="mix-blend-mode:multiply"/></g></g></svg>
diff --git a/app/views/shared/empty_states/icons/_service_desk_setup.svg b/app/views/shared/empty_states/icons/_service_desk_setup.svg
new file mode 100644
index 00000000000..bb791b58593
--- /dev/null
+++ b/app/views/shared/empty_states/icons/_service_desk_setup.svg
@@ -0,0 +1,39 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="430" height="167" viewBox="0 0 430 167">
+ <defs>
+ <rect id="a" width="81" height="4" x="96" y="88"/>
+ </defs>
+ <g fill="none" fill-rule="evenodd">
+ <g transform="translate(282 2)">
+ <rect width="40" height="4" x="25" y="86" fill="#DFDFDF" rx="2"/>
+ <rect width="22" height="4" y="86" fill="#DFDFDF" rx="2"/>
+ <path stroke="#DFDFDF" stroke-linecap="round" stroke-width="4" d="M63,88 C87.300529,88 107,68.300529 107,44 C107,19.699471 87.300529,0 63,0 C38.699471,0 19,19.699471 19,44 C19,55.4692579 23.3882741,65.9135795 30.5774088,73.7455512"/>
+ <path stroke="#DFDFDF" stroke-linecap="round" stroke-width="4" d="M52,142 L119,142 C133.911688,142 146,129.911688 146,115 C146,100.088312 133.911688,88 119,88 C104.088312,88 92,100.088312 92,115 C92,122.037954 94.6928046,128.446969 99.104319,133.252952" transform="matrix(1 0 0 -1 0 230)"/>
+ <path fill="#A7A7A7" d="M128 106C129.6569 106 131 107.343145 131 109L131 121C131 122.6569 129.6569 124 128 124L114.06641 124 109.250585 126.78325C108.250579 127.3612 107 126.63955 107 125.48455L107 109C107 107.343145 108.343147 106 110 106L128 106zM128 109L110 109 110 122.8852 113.26184 121 128 121 128 109zM114.5 113.5C115.32842 113.5 116 114.17158 116 115 116 115.82842 115.32842 116.5 114.5 116.5 113.67158 116.5 113 115.82842 113 115 113 114.17158 113.67158 113.5 114.5 113.5zM119 113.5C119.82842 113.5 120.5 114.17158 120.5 115 120.5 115.82842 119.82842 116.5 119 116.5 118.17158 116.5 117.5 115.82842 117.5 115 117.5 114.17158 118.17158 113.5 119 113.5zM123.5 113.5C124.32845 113.5 125 114.17158 125 115 125 115.82842 124.32845 116.5 123.5 116.5 122.67155 116.5 122 115.82842 122 115 122 114.17158 122.67155 113.5 123.5 113.5zM47 36C47 33.790862 48.790862 32 51 32L75 32C77.2092 32 79 33.790862 79 36L79 52C79 54.2092 77.2092 56 75 56L51 56C48.790862 56 47 54.2092 47 52L47 36zM51 36L75 36 75 36.0154 63.0079 42.93904 51 36.0063 51 36zM51 40.6251L51 52 75 52 75 40.6342 63.0079 47.55786 51 40.6251z"/>
+ </g>
+ <path stroke="#C2B7E6" stroke-linecap="round" stroke-width="4" d="M276.5,20 L276.5,165"/>
+ <use fill="#6E49CB" xlink:href="#a"/>
+ <use fill="#FFFFFF" fill-opacity=".6" xlink:href="#a"/>
+ <g transform="translate(172 40)">
+ <path fill="#6E49CB" fill-rule="nonzero" d="M64.5083266,2.16939521 C64.5598976,1.31008332 65.1555623,0.580183202 65.9870892,0.357376239 L67.0659897,0.0682857185 C67.8975166,-0.154521245 68.7783275,0.179758436 69.2526452,0.898158883 L71.0838835,3.67168101 C71.8604055,3.69835108 72.6253745,3.80075177 73.3696161,3.97339039 L75.8570965,1.76768551 C76.501214,1.19651341 77.4383928,1.10164098 78.1839968,1.53205032 L79.1513003,2.09052325 C79.8969043,2.52093259 80.2832521,3.38015574 80.1106561,4.22354464 L79.4443144,7.48050479 C79.9657604,8.03872555 80.4370489,8.65007844 80.8482561,9.30920953 L84.1658391,9.50834112 C85.025263,9.55988206 85.7551052,10.1555623 85.9779122,10.9870892 L86.2670027,12.0659897 C86.4898096,12.8975166 86.1555879,13.778312 85.4370754,14.2526597 L82.6635301,16.0839042 C82.6369953,16.86039 82.534498,17.6253848 82.3620332,18.3695798 L84.5676029,20.8570965 C85.1387232,21.5010208 85.2337633,22.4383618 84.8032767,23.1839864 L84.2448038,24.1512899 C83.8142654,24.8967214 82.9552293,25.2832262 82.111821,25.1106354 L78.8547318,24.4441212 C78.2965242,24.9657707 77.6852679,25.4370334 77.0260789,25.8482561 L76.8269473,29.1658391 C76.7754063,30.025263 76.1797261,30.7551052 75.3481992,30.9779122 L74.2692987,31.2670027 C73.4377718,31.4898096 72.5569764,31.1555879 72.0826287,30.4370754 L70.2513842,27.6635301 C69.4749563,27.6369798 68.7098843,27.5345032 67.9657472,27.3620229 L65.478263,29.5677909 C64.8341648,30.1389578 63.89683,30.2337892 63.1512826,29.8032819 L62.1839598,29.2448141 C61.4384642,28.8145 61.0520043,27.9552448 61.2245757,27.1118417 L61.8910899,23.8547525 C61.369479,23.2965346 60.898313,22.6852524 60.486955,22.0260996 L57.1693952,21.8269618 C56.3100833,21.7753908 55.5801832,21.1797261 55.3573762,20.3481992 L55.0682857,19.2692987 C54.8454788,18.4377718 55.1797584,17.5569609 55.8981589,17.0826432 L58.671681,15.2514049 C58.6983614,14.4749215 58.8007311,13.7098367 58.9733555,12.9656196 L56.7676172,10.4781688 C56.1964503,9.83407059 56.1015416,8.89675656 56.5319717,8.15122986 L57.0904394,7.18390704 C57.5208695,6.43838035 58.380086,6.05193078 59.2234504,6.22451259 L62.4805641,6.89104094 C63.0387487,6.36945971 63.6501081,5.89827293 64.3091888,5.48695498 L64.5083266,2.16939521 Z M72.7381966,23.3950508 C77.00585,22.2515365 79.5385651,17.8647453 78.3950508,13.5970918 C77.2515158,9.32936108 72.8647453,6.79672328 68.5970918,7.94023759 C64.3293611,9.0837726 61.7967026,13.4704658 62.9402376,17.7381966 C64.0837519,22.00585 68.4704658,24.5385858 72.7381966,23.3950508 Z"/>
+ <path fill="#EFEDF8" stroke="#6E49CB" stroke-width="4" d="M27.08832,20.735088 C27.63276,19.10172 29.16132,18 30.88304,18 L33.11696,18 C34.83868,18 36.36724,19.10172 36.91168,20.735088 L39.01368,27.04104 C40.5,27.49452 41.9248,28.08832 43.2732,28.80708 L49.2204,25.8336 C50.7604,25.0636 52.62,25.36544 53.8376,26.58288 L55.4172,28.16248 C56.6348,29.37992 56.9364,31.2398 56.1664,32.77976 L53.1932,38.7268 C53.9116,40.07512 54.5056,41.50012 54.9588,42.98632 L61.2648,45.08832 C62.8984,45.63276 64,47.16132 64,48.88304 L64,51.11696 C64,52.83868 62.8984,54.36724 61.2648,54.91168 L54.9588,57.01368 C54.5056,58.5 53.9116,59.9248 53.1932,61.2732 L56.1664,67.2204 C56.9364,68.76 56.6348,70.62 55.4172,71.8376 L53.8376,73.4172 C52.62,74.6344 50.7604,74.9364 49.2204,74.1664 L43.2732,71.1928 C41.9248,71.9116 40.5,72.5056 39.01368,72.9588 L36.91168,79.2648 C36.36724,80.8984 34.83868,82 33.11696,82 L30.88304,82 C29.16132,82 27.63276,80.8984 27.08832,79.2648 L24.98632,72.9588 C23.50012,72.5056 22.07516,71.9116 20.72688,71.1932 L14.77964,74.1668 C13.23968,74.9368 11.3798,74.6348 10.16236,73.4172 L8.58272,71.8376 C7.36528,70.6204 7.06348,68.7604 7.83344,67.2204 L10.80704,61.2732 C10.08832,59.9248 9.49452,58.5 9.04104,57.01368 L2.735088,54.91168 C1.10172,54.36724 0,52.83868 0,51.11696 L0,48.88304 C0,47.16132 1.10172,45.63276 2.735088,45.08832 L9.04104,42.98632 C9.49452,41.50008 10.08832,40.07504 10.80704,38.72668 L7.83348,32.77952 C7.06348,31.23956 7.36532,29.37968 8.58276,28.16224 L10.16236,26.5826 C11.3798,25.36516 13.23972,25.06336 14.77964,25.83332 L20.72688,28.80696 C22.0752,28.08828 23.50016,27.49448 24.98632,27.04104 L27.08832,20.735088 Z M32,66 C40.8364,66 48,58.8364 48,50 C48,41.16344 40.8364,34 32,34 C23.16344,34 16,41.16344 16,50 C16,58.8364 23.16344,66 32,66 Z"/>
+ <circle cx="32" cy="50" r="10" stroke="#6E49CB" stroke-linecap="round" stroke-width="2"/>
+ </g>
+ <g stroke="#FC6D26" transform="translate(123 78)">
+ <circle cx="12" cy="12" r="11" fill="#FFFFFF" stroke-width="2"/>
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="4" d="M8,12.25 C9.8974359,14.0833333 10.8461538,15 10.8461538,15 C10.8461538,15 12.8974359,13 17,9"/>
+ </g>
+ <g transform="translate(0 40)">
+ <circle cx="50" cy="50" r="48" fill="#FFFFFF" stroke="#FC6D26" stroke-width="4"/>
+ <circle cx="21" cy="50" r="4" fill="#6E49CB"/>
+ <circle cx="79" cy="50" r="4" fill="#6E49CB"/>
+ <circle cx="50" cy="50" r="27" fill="#FFFFFF" stroke="#E1DBF1" stroke-width="4"/>
+ <rect width="38" height="24" x="31" y="38" fill="#FFFFFF" stroke="#E1DBF1" stroke-width="2" rx="12"/>
+ <circle cx="50" cy="69" r="2" fill="#6E49CB"/>
+ <circle cx="50" cy="69" r="2" fill="#6E49CB"/>
+ <circle cx="55" cy="69" r="1" fill="#6E49CB"/>
+ <circle cx="45" cy="69" r="1" fill="#6E49CB"/>
+ <path stroke="#6E49CB" stroke-linecap="round" stroke-width="2" d="M48 30L52 30M15 50L19 50M81 50L85 50M48 33.5L52 33.5"/>
+ <path fill="#6E49CB" d="M54.214 52.70154C54.9314 53.11584 55.177 54.0332 54.7628 54.7506 54.2804 55.5856 53.58722 56.2792 52.7524 56.7618 51.91758 57.2442 50.97058 57.4988 50.00632 57.5000085 49.04208 57.5012 48.09448 57.2488 47.25856 56.768 46.42264 56.2874 45.72774 55.5956 45.24358 54.7616 44.8276 54.0452 45.07118 53.12726 45.7876 52.71128 46.4443183 52.3299833 47.2704031 52.5028667 47.7239338 53.0861543L47.83798 53.2553C48.05804 53.63434 48.3739 53.94886 48.75388 54.1674 49.13384 54.3858 49.56456 54.5006 50.00286 54.5 50.44116 54.4994 50.8716 54.3838 51.25108 54.1644 51.554648 53.988944 51.8170384 53.7520992 52.0220822 53.470055L52.16486 53.2503C52.57918 52.53292 53.49658 52.28722 54.214 52.70154zM41 46C42.10456 46 43 46.89544 43 48 43 49.10456 42.10456 50 41 50 39.89544 50 39 49.10456 39 48 39 46.89544 39.89544 46 41 46zM59 46C60.1046 46 61 46.89544 61 48 61 49.10456 60.1046 50 59 50 57.89544 50 57 49.10456 57 48 57 46.89544 57.89544 46 59 46z"/>
+ </g>
+ </g>
+</svg>
diff --git a/app/views/shared/file_hooks/_index.html.haml b/app/views/shared/file_hooks/_index.html.haml
index 436bd305df1..cab0adf159b 100644
--- a/app/views/shared/file_hooks/_index.html.haml
+++ b/app/views/shared/file_hooks/_index.html.haml
@@ -1,6 +1,6 @@
- file_hooks = Gitlab::FileHook.files
-.row.prepend-top-default
+.row.gl-mt-3
.col-lg-4
%h4.gl-mt-0
= _('File Hooks')
@@ -9,7 +9,7 @@
= link_to _('For more information, see the File Hooks documentation.'), help_page_path('administration/file_hooks')
- .col-lg-8.append-bottom-default
+ .col-lg-8.gl-mb-3
- if file_hooks.any?
.card
.card-header
diff --git a/app/views/shared/form_elements/_description.html.haml b/app/views/shared/form_elements/_description.html.haml
index 77af4f09408..413df29da77 100644
--- a/app/views/shared/form_elements/_description.html.haml
+++ b/app/views/shared/form_elements/_description.html.haml
@@ -1,20 +1,16 @@
- project = local_assigns.fetch(:project)
- model = local_assigns.fetch(:model)
-
-
-
- form = local_assigns.fetch(:form)
- placeholder = model.is_a?(MergeRequest) ? _('Describe the goal of the changes and what reviewers should be aware of.') : _('Write a comment or drag your files here…')
-- supports_quick_actions = model.new_record?
-- if supports_quick_actions
- - preview_url = preview_markdown_path(project, target_type: model.class.name)
-- else
- - preview_url = preview_markdown_path(project)
+- supports_quick_actions = true
+- preview_url = preview_markdown_path(project, target_type: model.class.name)
.form-group.row.detail-page-description
= form.label :description, 'Description', class: 'col-form-label col-sm-2'
.col-sm-10
+ - if model.is_a?(MergeRequest)
+ = hidden_field_tag :merge_request_diff_head_sha, model.diff_head_sha
- if model.is_a?(Issuable)
= render 'shared/issuable/form/template_selector', issuable: model
diff --git a/app/views/shared/groups/_dropdown.html.haml b/app/views/shared/groups/_dropdown.html.haml
index f4915440cb2..9d2d3ce20c7 100644
--- a/app/views/shared/groups/_dropdown.html.haml
+++ b/app/views/shared/groups/_dropdown.html.haml
@@ -8,7 +8,7 @@
- else
- default_sort_by = sort_value_recently_created
-.dropdown.inline.js-group-filter-dropdown-wrap.append-right-10
+.dropdown.inline.js-group-filter-dropdown-wrap.gl-mr-3
%button.dropdown-menu-toggle{ type: 'button', 'data-toggle' => 'dropdown' }
%span.dropdown-label
= options_hash[default_sort_by]
diff --git a/app/views/shared/icons/_icon_service_desk.svg b/app/views/shared/icons/_icon_service_desk.svg
new file mode 100644
index 00000000000..2886388279e
--- /dev/null
+++ b/app/views/shared/icons/_icon_service_desk.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="78" height="82" viewBox="0 0 78 82"><g fill="none" fill-rule="evenodd"><path fill="#F9F9F9" d="M2.12 42c-.08.99-.12 1.99-.12 3 0 20.435 16.565 37 37 37s37-16.565 37-37c0-1.01-.04-2.01-.12-3C74.353 61.032 58.425 76 39 76 19.575 76 3.647 61.032 2.12 42z"/><path fill="#EEE" fill-rule="nonzero" d="M39 78C17.46 78 0 60.54 0 39S17.46 0 39 0s39 17.46 39 39-17.46 39-39 39zm0-4c19.33 0 35-15.67 35-35S58.33 4 39 4 4 19.67 4 39s15.67 35 35 35z"/><rect width="7" height="1" x="59" y="38" fill="#E1DBF2" rx=".5"/><path fill="#6B4FBB" d="M60.5 42a3.5 3.5 0 0 0 0-7v7z"/><rect width="7" height="1" x="12" y="38" fill="#E1DBF2" transform="matrix(-1 0 0 1 31 0)" rx=".5"/><path fill="#6B4FBB" d="M17.5 42a3.5 3.5 0 0 1 0-7v7z"/><path fill="#E1DBF1" fill-rule="nonzero" d="M39 58c10.493 0 19-8.507 19-19s-8.507-19-19-19-19 8.507-19 19 8.507 19 19 19zm0 4c-12.703 0-23-10.297-23-23s10.297-23 23-23 23 10.297 23 23-10.297 23-23 23z"/><path fill="#6B4FBB" d="M35 56a1 1 0 1 1 0-2 1 1 0 0 1 0 2zm4 0a1 1 0 1 1 0-2 1 1 0 0 1 0 2zm4 0a1 1 0 1 1 0-2 1 1 0 0 1 0 2z"/><path fill="#E1DBF1" fill-rule="nonzero" d="M26.5 40c0 4.143 3.355 7.5 7.494 7.5h10.012A7.497 7.497 0 0 0 51.5 40c0-4.143-3.355-7.5-7.494-7.5H33.994A7.497 7.497 0 0 0 26.5 40zm-3 0c0-5.799 4.698-10.5 10.494-10.5h10.012C49.802 29.5 54.5 34.2 54.5 40c0 5.799-4.698 10.5-10.494 10.5H33.994C28.198 50.5 23.5 45.8 23.5 40z"/><path fill="#6B4FBB" fill-rule="nonzero" d="M35.255 42.406a1 1 0 1 1 1.872-.703 2.001 2.001 0 0 0 3.76-.038 1 1 0 1 1 1.886.665 4 4 0 0 1-7.518.076zM31.5 40a1.5 1.5 0 1 1 0-3 1.5 1.5 0 0 1 0 3zm15 0a1.5 1.5 0 1 1 0-3 1.5 1.5 0 0 1 0 3z"/><path fill="#6B4FBB" d="M38 22h2a1 1 0 0 1 0 2h-2a1 1 0 0 1 0-2zm0 3h2a1 1 0 0 1 0 2h-2a1 1 0 0 1 0-2z" style="mix-blend-mode:multiply"/></g></svg> \ No newline at end of file
diff --git a/app/views/shared/integrations/edit.html.haml b/app/views/shared/integrations/edit.html.haml
index 927d2410132..a996f72e2f4 100644
--- a/app/views/shared/integrations/edit.html.haml
+++ b/app/views/shared/integrations/edit.html.haml
@@ -1,5 +1,6 @@
- add_to_breadcrumbs _('Integrations'), scoped_integrations_path
- breadcrumb_title @integration.title
- page_title @integration.title, _('Integrations')
+- @content_class = 'limit-container-width' unless fluid_layout
= render 'shared/integrations/form', integration: @integration
diff --git a/app/views/shared/issuable/_board_create_list_dropdown.html.haml b/app/views/shared/issuable/_board_create_list_dropdown.html.haml
index ae0e5e45afe..b6cf23faff8 100644
--- a/app/views/shared/issuable/_board_create_list_dropdown.html.haml
+++ b/app/views/shared/issuable/_board_create_list_dropdown.html.haml
@@ -1,4 +1,4 @@
-.dropdown.prepend-left-10#js-add-list
+.dropdown.gl-ml-3#js-add-list
%button.btn.btn-success.btn-inverted.js-new-board-list{ type: "button", data: board_list_data }
Add list
.dropdown-menu.dropdown-extended-height.dropdown-menu-paging.dropdown-menu-right.dropdown-menu-issues-board-new.dropdown-menu-selectable.js-tab-container-labels
diff --git a/app/views/shared/issuable/_bulk_update_sidebar.html.haml b/app/views/shared/issuable/_bulk_update_sidebar.html.haml
index 4bc6c1dee37..ec7ff127ed5 100644
--- a/app/views/shared/issuable/_bulk_update_sidebar.html.haml
+++ b/app/views/shared/issuable/_bulk_update_sidebar.html.haml
@@ -1,4 +1,6 @@
- type = local_assigns.fetch(:type)
+- bulk_issue_health_status_flag = Feature.enabled?(:bulk_update_health_status, @project&.group, default_enabled: true) && type == :issues && @project&.group&.feature_available?(:issuable_health_status)
+- epic_bulk_edit_flag = @project&.group&.feature_available?(:epics) && type == :issues
%aside.issues-bulk-update.js-right-sidebar.right-sidebar{ "aria-live" => "polite", data: { 'signed-in': current_user.present? } }
.issuable-sidebar.hidden
@@ -26,6 +28,13 @@
- field_name = "update[assignee_ids][]"
= dropdown_tag(_("Select assignee"), options: { toggle_class: "js-user-search js-update-assignee js-filter-submit js-filter-bulk-update", title: _("Assign to"), filter: true, dropdown_class: "dropdown-menu-user dropdown-menu-selectable",
placeholder: _("Search authors"), data: { first_user: (current_user.username if current_user), null_user: true, current_user: true, project_id: @project.id, field_name: field_name } })
+ - if epic_bulk_edit_flag
+ .block
+ .title
+ = _('Epic')
+ .filter-item.epic-bulk-edit
+ #js-epic-select-root{ data: { group_id: @project&.group&.id, show_header: "true" } }
+ %input{ id: 'issue_epic_id', type: 'hidden', name: 'update[epic_id]' }
.block
.title
= _('Milestone')
@@ -36,6 +45,13 @@
= _('Labels')
.filter-item.labels-filter
= render "shared/issuable/label_dropdown", classes: ["js-filter-bulk-update", "js-multiselect"], dropdown_title: _("Apply a label"), show_create: false, show_footer: false, extra_options: false, filter_submit: false, data_options: { persist_when_hide: "true", field_name: "update[label_ids][]", show_no: false, show_any: false, use_id: true, default_label: _("Labels") }, label_name: _("Select labels"), no_default_styles: true
+ - if bulk_issue_health_status_flag
+ .block
+ .title
+ = _('Health status')
+ .filter-item.health-status.health-status-filter
+ #js-bulk-update-health-status-root
+ %input{ id: 'issue_health_status_value', type: 'hidden', name: 'update[health_status]' }
.block
.title
= _('Subscriptions')
diff --git a/app/views/shared/issuable/_close_reopen_button.html.haml b/app/views/shared/issuable/_close_reopen_button.html.haml
index 5f7cfdc9d03..59d0c46b92f 100644
--- a/app/views/shared/issuable/_close_reopen_button.html.haml
+++ b/app/views/shared/issuable/_close_reopen_button.html.haml
@@ -1,6 +1,5 @@
- is_current_user = issuable_author_is_current_user(issuable)
- display_issuable_type = issuable_display_type(issuable)
-- button_method = issuable_close_reopen_button_method(issuable)
- are_close_and_open_buttons_hidden = issuable_button_hidden?(issuable, true) && issuable_button_hidden?(issuable, false)
- add_blocked_class = false
- if defined? warn_before_close
@@ -8,11 +7,13 @@
- if is_current_user
- if can_update
- = link_to _("Close %{display_issuable_type}") % { display_issuable_type: display_issuable_type }, close_issuable_path(issuable), method: button_method,
- class: "d-none d-sm-none d-md-block btn btn-grouped btn-close js-btn-issue-action #{issuable_button_visibility(issuable, true)} #{(add_blocked_class ? 'btn-issue-blocked' : '')}", title: _("Close %{display_issuable_type}") % { display_issuable_type: display_issuable_type }, data: { qa_selector: 'close_issue_button' }
+ %button{ class: "d-none d-sm-none d-md-block btn btn-grouped btn-close js-btn-issue-action #{issuable_button_visibility(issuable, true)} #{(add_blocked_class ? 'btn-issue-blocked' : '')}",
+ data: { remote: 'true', endpoint: close_issuable_path(issuable), qa_selector: 'close_issue_button' } }
+ = _("Close %{display_issuable_type}") % { display_issuable_type: display_issuable_type }
- if can_reopen
- = link_to _("Reopen %{display_issuable_type}") % { display_issuable_type: display_issuable_type }, reopen_issuable_path(issuable), method: button_method,
- class: "d-none d-sm-none d-md-block btn btn-grouped btn-reopen js-btn-issue-action #{issuable_button_visibility(issuable, false)}", title: _("Reopen %{display_issuable_type}") % { display_issuable_type: display_issuable_type }, data: { qa_selector: 'reopen_issue_button' }
+ %button{ class: "d-none d-sm-none d-md-block btn btn-grouped btn-reopen js-btn-issue-action #{issuable_button_visibility(issuable, false)}",
+ data: { remote: 'true', endpoint: reopen_issuable_path(issuable), qa_selector: 'reopen_issue_button' } }
+ = _("Reopen %{display_issuable_type}") % { display_issuable_type: display_issuable_type }
- else
- if can_update && !are_close_and_open_buttons_hidden
= render 'shared/issuable/close_reopen_report_toggle', issuable: issuable, warn_before_close: add_blocked_class
diff --git a/app/views/shared/issuable/_close_reopen_report_toggle.html.haml b/app/views/shared/issuable/_close_reopen_report_toggle.html.haml
index 9d718083d2d..3fc6a3b545b 100644
--- a/app/views/shared/issuable/_close_reopen_report_toggle.html.haml
+++ b/app/views/shared/issuable/_close_reopen_report_toggle.html.haml
@@ -4,14 +4,13 @@
- button_responsive_class = 'd-none d-sm-none d-md-block'
- button_class = "#{button_responsive_class} btn btn-grouped js-issuable-close-button js-btn-issue-action issuable-close-button"
- toggle_class = "#{button_responsive_class} btn btn-nr dropdown-toggle js-issuable-close-toggle"
-- button_method = issuable_close_reopen_button_method(issuable)
- add_blocked_class = false
- if defined? warn_before_close
- add_blocked_class = !issuable.closed? && warn_before_close
-.float-left.btn-group.prepend-left-10.issuable-close-dropdown.droplab-dropdown.js-issuable-close-dropdown
- = link_to "#{display_button_action} #{display_issuable_type}", close_reopen_issuable_path(issuable),
- method: button_method, class: "#{button_class} btn-#{button_action} #{(add_blocked_class ? 'btn-issue-blocked' : '')}", title: "#{display_button_action} #{display_issuable_type}", data: { qa_selector: 'close_issue_button' }
+.float-left.btn-group.gl-ml-3.issuable-close-dropdown.droplab-dropdown.js-issuable-close-dropdown
+ %button{ class: "#{button_class} btn-#{button_action} #{(add_blocked_class ? 'btn-issue-blocked' : '')}", data: { qa_selector: 'close_issue_button', endpoint: close_reopen_issuable_path(issuable) } }
+ #{display_button_action} #{display_issuable_type}
= button_tag type: 'button', class: "#{toggle_class} btn-#{button_action}-color",
data: { 'dropdown-trigger' => '#issuable-close-menu' }, 'aria-label' => _('Toggle dropdown') do
@@ -20,7 +19,7 @@
%ul#issuable-close-menu.js-issuable-close-menu.dropdown-menu{ data: { dropdown: true } }
%li.close-item{ class: "#{issuable_button_visibility(issuable, true) || 'droplab-item-selected'}",
data: { text: _("Close %{display_issuable_type}") % { display_issuable_type: display_issuable_type }, url: close_issuable_path(issuable),
- button_class: "#{button_class} btn-close", toggle_class: "#{toggle_class} btn-close-color", method: button_method } }
+ button_class: "#{button_class} btn-close", toggle_class: "#{toggle_class} btn-close-color" } }
%button.btn.btn-transparent
= icon('check', class: 'icon')
.description
@@ -30,7 +29,7 @@
%li.reopen-item{ class: "#{issuable_button_visibility(issuable, false) || 'droplab-item-selected'}",
data: { text: _("Reopen %{display_issuable_type}") % { display_issuable_type: display_issuable_type }, url: reopen_issuable_path(issuable),
- button_class: "#{button_class} btn-reopen", toggle_class: "#{toggle_class} btn-reopen-color", method: button_method } }
+ button_class: "#{button_class} btn-reopen", toggle_class: "#{toggle_class} btn-reopen-color" } }
%button.btn.btn-transparent
= icon('check', class: 'icon')
.description
diff --git a/app/views/shared/issuable/_form.html.haml b/app/views/shared/issuable/_form.html.haml
index 1b3ad484bcc..f54457b8b33 100644
--- a/app/views/shared/issuable/_form.html.haml
+++ b/app/views/shared/issuable/_form.html.haml
@@ -35,7 +35,7 @@
= render_if_exists 'shared/issuable/approvals', issuable: issuable, presenter: presenter, form: form
-= render 'shared/issuable/form/merge_params', issuable: issuable
+= render 'shared/issuable/form/merge_params', issuable: issuable, project: project
= render 'shared/issuable/form/contribution', issuable: issuable, form: form
@@ -69,7 +69,7 @@
= link_to 'Delete', polymorphic_path([@project.namespace.becomes(Namespace), @project, issuable], params: { destroy_confirm: true }), data: { confirm: "#{issuable.human_class_name} will be removed! Are you sure?" }, method: :delete, class: 'btn btn-danger btn-grouped'
= link_to 'Cancel', polymorphic_path([@project.namespace.becomes(Namespace), @project, issuable]), class: 'btn btn-grouped btn-cancel'
- %span.append-right-10
+ %span.gl-mr-3
- if issuable.new_record?
= form.submit "Submit #{issuable.class.model_name.human.downcase}", class: 'btn btn-success qa-issuable-create-button'
- else
diff --git a/app/views/shared/issuable/_search_bar.html.haml b/app/views/shared/issuable/_search_bar.html.haml
index d53ec4d4eeb..0b5700e5413 100644
--- a/app/views/shared/issuable/_search_bar.html.haml
+++ b/app/views/shared/issuable/_search_bar.html.haml
@@ -135,7 +135,7 @@
%li.filter-dropdown-item
%button.btn.btn-link{ type: 'button' }
%gl-emoji
- %span.js-data-value.prepend-left-10
+ %span.js-data-value.gl-ml-3
{{name}}
#js-dropdown-wip.filtered-search-input-dropdown-menu.dropdown-menu
%ul.filter-dropdown{ data: { dropdown: true } }
@@ -172,7 +172,7 @@
- if user_can_admin_list
= render 'shared/issuable/board_create_list_dropdown', board: board
- if @project
- #js-add-issues-btn.prepend-left-10{ data: { can_admin_list: can?(current_user, :admin_list, @project) } }
+ #js-add-issues-btn.gl-ml-3{ data: { can_admin_list: can?(current_user, :admin_list, @project) } }
- if Feature.enabled?(:boards_with_swimlanes, @group)
#js-board-epics-swimlanes-toggle
#js-toggle-focus-btn
diff --git a/app/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml
index ab4bd88cfe5..00113b2c2c0 100644
--- a/app/views/shared/issuable/_sidebar.html.haml
+++ b/app/views/shared/issuable/_sidebar.html.haml
@@ -42,7 +42,7 @@
= _('Milestone')
= icon('spinner spin', class: 'hidden block-loading', 'aria-hidden': 'true')
- if can_edit_issuable
- = link_to _('Edit'), '#', class: 'js-sidebar-dropdown-toggle edit-link float-right', data: { track_label: "right_sidebar", track_property: "milestone", track_event: "click_edit_button", track_value: "" }
+ = link_to _('Edit'), '#', class: 'js-sidebar-dropdown-toggle edit-link float-right', data: { qa_selector: "edit_milestone_link", track_label: "right_sidebar", track_property: "milestone", track_event: "click_edit_button", track_value: "" }
.value.hide-collapsed
- if milestone.present?
= link_to milestone[:title], milestone[:web_url], class: "bold has-tooltip", title: sidebar_milestone_remaining_days(milestone), data: { container: "body", html: 'true', boundary: 'viewport', qa_selector: 'milestone_link', qa_title: milestone[:title] }
@@ -107,7 +107,7 @@
= _('Labels')
= icon('spinner spin', class: 'hidden block-loading', 'aria-hidden': 'true')
- if can_edit_issuable
- = link_to _('Edit'), '#', class: 'js-sidebar-dropdown-toggle edit-link qa-edit-link-labels float-right', data: { track_label: "right_sidebar", track_property: "labels", track_event: "click_edit_button", track_value: "" }
+ = link_to _('Edit'), '#', class: 'js-sidebar-dropdown-toggle edit-link float-right', data: { qa_selector: "edit_labels_link", track_label: "right_sidebar", track_property: "labels", track_event: "click_edit_button", track_value: "" }
.value.issuable-show-labels.dont-hide.hide-collapsed{ class: ("has-labels" if selected_labels.any?), data: { qa_selector: 'labels_block' } }
- if selected_labels.any?
- selected_labels.each do |label_hash|
diff --git a/app/views/shared/issuable/_sort_dropdown.html.haml b/app/views/shared/issuable/_sort_dropdown.html.haml
index 9c151dc96f3..81dbecb430b 100644
--- a/app/views/shared/issuable/_sort_dropdown.html.haml
+++ b/app/views/shared/issuable/_sort_dropdown.html.haml
@@ -2,7 +2,7 @@
- sort_title = issuable_sort_option_title(sort_value)
- viewing_issues = controller.controller_name == 'issues' || controller.action_name == 'issues'
-.dropdown.inline.prepend-left-10.issue-sort-dropdown
+.dropdown.inline.gl-ml-3.issue-sort-dropdown
.btn-group{ role: 'group' }
.btn-group{ role: 'group' }
%button.dropdown-menu-toggle{ type: 'button', data: { toggle: 'dropdown', display: 'static' }, class: 'btn btn-default' }
diff --git a/app/views/shared/issuable/form/_branch_chooser.html.haml b/app/views/shared/issuable/form/_branch_chooser.html.haml
index 3794a3b3845..1823c5279e5 100644
--- a/app/views/shared/issuable/form/_branch_chooser.html.haml
+++ b/app/views/shared/issuable/form/_branch_chooser.html.haml
@@ -18,7 +18,7 @@
- elsif issuable.for_fork?
%code= issuable.target_project_path + ":"
- unless issuable.new_record?
- %span.dropdown.prepend-left-5.d-inline-block
+ %span.dropdown.gl-ml-2.d-inline-block
= form.hidden_field(:target_branch,
{ class: 'target_branch js-target-branch-select ref-name mw-xl',
data: { placeholder: _('Select branch'), endpoint: refs_project_path(@project, sort: 'updated_desc', find: 'branches') }})
diff --git a/app/views/shared/issuable/form/_contribution.html.haml b/app/views/shared/issuable/form/_contribution.html.haml
index a78231b37ce..dc6abfd2c9e 100644
--- a/app/views/shared/issuable/form/_contribution.html.haml
+++ b/app/views/shared/issuable/form/_contribution.html.haml
@@ -11,7 +11,7 @@
%label.col-form-label.col-sm-2
= _('Contribution')
.col-sm-10
- .form-check.prepend-top-5
+ .form-check.gl-mt-2
= form.check_box :allow_collaboration, disabled: !issuable.can_allow_collaboration?(current_user), class: 'form-check-input'
= form.label :allow_collaboration, class: 'form-check-label' do
= _('Allow commits from members who can merge to the target branch.')
diff --git a/app/views/shared/issuable/form/_default_templates.html.haml b/app/views/shared/issuable/form/_default_templates.html.haml
index 49a5ce926b3..3dc244677e2 100644
--- a/app/views/shared/issuable/form/_default_templates.html.haml
+++ b/app/views/shared/issuable/form/_default_templates.html.haml
@@ -1,4 +1,4 @@
%p.form-text.text-muted
Add
- = link_to 'description templates', help_page_path('user/project/description_templates'), tabindex: -1
+ = link_to 'description templates', help_page_path('user/project/description_templates')
to help your contributors communicate effectively!
diff --git a/app/views/shared/issuable/form/_merge_params.html.haml b/app/views/shared/issuable/form/_merge_params.html.haml
index 1b557214e02..6f1023474a1 100644
--- a/app/views/shared/issuable/form/_merge_params.html.haml
+++ b/app/views/shared/issuable/form/_merge_params.html.haml
@@ -1,4 +1,5 @@
- issuable = local_assigns.fetch(:issuable)
+- project = local_assigns.fetch(:project)
- return unless issuable.is_a?(MergeRequest)
- return if issuable.closed_without_fork?
@@ -9,14 +10,22 @@
= _('Merge options')
.col-sm-10
- if issuable.can_remove_source_branch?(current_user)
- .form-check.append-bottom-default
+ .form-check.gl-mb-3
= hidden_field_tag 'merge_request[force_remove_source_branch]', '0', id: nil
= check_box_tag 'merge_request[force_remove_source_branch]', '1', issuable.force_remove_source_branch?, class: 'form-check-input'
= label_tag 'merge_request[force_remove_source_branch]', class: 'form-check-label' do
Delete source branch when merge request is accepted.
- .form-check
- = hidden_field_tag 'merge_request[squash]', '0', id: nil
- = check_box_tag 'merge_request[squash]', '1', issuable.squash, class: 'form-check-input'
- = label_tag 'merge_request[squash]', class: 'form-check-label' do
- Squash commits when merge request is accepted.
- = link_to icon('question-circle'), help_page_path('user/project/merge_requests/squash_and_merge'), target: '_blank'
+ - if !project.squash_never?
+ .form-check
+ - if project.squash_always?
+ = hidden_field_tag 'merge_request[squash]', '1', id: nil
+ = check_box_tag 'merge_request[squash]', '1', project.squash_enabled_by_default?, class: 'form-check-input', disabled: 'true'
+ - else
+ = hidden_field_tag 'merge_request[squash]', '0', id: nil
+ = check_box_tag 'merge_request[squash]', '1', issuable_squash_option?(issuable, project), class: 'form-check-input'
+ = label_tag 'merge_request[squash]', class: 'form-check-label' do
+ Squash commits when merge request is accepted.
+ = link_to icon('question-circle'), help_page_path('user/project/merge_requests/squash_and_merge'), target: '_blank'
+ - if project.squash_always?
+ .gl-text-gray-600
+ = _('Required in this project.')
diff --git a/app/views/shared/issuable/form/_title.html.haml b/app/views/shared/issuable/form/_title.html.haml
index 75e9ab547ce..355a6627b8f 100644
--- a/app/views/shared/issuable/form/_title.html.haml
+++ b/app/views/shared/issuable/form/_title.html.haml
@@ -11,7 +11,7 @@
- if issuable.respond_to?(:work_in_progress?)
.form-text.text-muted
.js-wip-explanation
- %a.js-toggle-wip{ href: '', tabindex: -1 }
+ %a.js-toggle-wip{ href: '' }
Remove the
%code WIP:
prefix from the title
@@ -22,7 +22,7 @@
- if has_wip_commits
It looks like you have some WIP commits in this branch.
%br
- %a.js-toggle-wip{ href: '', tabindex: -1 }
+ %a.js-toggle-wip{ href: '' }
Start the title with
%code WIP:
to prevent a
diff --git a/app/views/shared/members/_member.html.haml b/app/views/shared/members/_member.html.haml
index f7d90a588c7..79dc3043e8d 100644
--- a/app/views/shared/members/_member.html.haml
+++ b/app/views/shared/members/_member.html.haml
@@ -62,12 +62,12 @@
- if show_controls && member.source == current_resource
- if member.can_resend_invite?
- = link_to icon('paper-plane'), polymorphic_path([:resend_invite, member]),
+ = link_to sprite_icon('paper-airplane', size: 16), polymorphic_path([:resend_invite, member]),
method: :post,
class: 'btn btn-default align-self-center mr-sm-2',
title: _('Resend invite')
- - if user != current_user && member.can_update? && !user&.project_bot?
+ - if user != current_user && member.can_update?
= form_for member, remote: true, html: { class: "js-edit-member-form form-group #{'d-sm-flex' unless force_mobile_view}" } do |f|
= f.hidden_field :access_level
.member-form-control.dropdown{ class: [("mr-sm-2 d-sm-inline-block" unless force_mobile_view)] }
@@ -117,12 +117,10 @@
method: :delete,
data: { confirm: leave_confirmation_message(member.source) },
class: "btn btn-remove align-self-center m-0 #{'ml-sm-2' unless force_mobile_view}"
- - elsif !user&.project_bot?
- = link_to member,
- method: :delete,
- data: { confirm: remove_member_message(member), qa_selector: 'delete_member_button' },
- class: "btn btn-remove align-self-center m-0 #{'ml-sm-2' unless force_mobile_view}",
- title: remove_member_title(member) do
+ - else
+ %button{ data: { member_path: member_path(member.member), message: remove_member_message(member), is_access_request: member.request?.to_s, qa_selector: 'delete_member_button' },
+ class: "js-remove-member-button btn btn-remove align-self-center m-0 #{'ml-sm-2' unless force_mobile_view}",
+ title: remove_member_title(member) }
%span{ class: ('d-block d-sm-none' unless force_mobile_view) }
= _("Delete")
- unless force_mobile_view
diff --git a/app/views/shared/members/_requests.html.haml b/app/views/shared/members/_requests.html.haml
index 1f62c3cbcf4..e1e7aa36a78 100644
--- a/app/views/shared/members/_requests.html.haml
+++ b/app/views/shared/members/_requests.html.haml
@@ -4,7 +4,7 @@
- return if requesters.empty?
-.card.prepend-top-default{ class: ('card-mobile' if force_mobile_view ) }
+.card.gl-mt-3{ class: ('card-mobile' if force_mobile_view ) }
.card-header
= _("Users requesting access to")
%strong= membership_source.name
diff --git a/app/views/shared/milestones/_deprecation_message.html.haml b/app/views/shared/milestones/_deprecation_message.html.haml
index ba5eb54f017..27cd6d75232 100644
--- a/app/views/shared/milestones/_deprecation_message.html.haml
+++ b/app/views/shared/milestones/_deprecation_message.html.haml
@@ -1,6 +1,6 @@
.banner-callout.compact.milestone-deprecation-message.js-milestone-deprecation-message.prepend-top-20
.banner-graphic= image_tag 'illustrations/milestone_removing-page.svg'
- .banner-body.prepend-left-10.append-right-10
+ .banner-body.gl-ml-3.gl-mr-3
%h5.banner-title.gl-mt-0= _('This page will be removed in a future release.')
%p.milestone-banner-text= _('Use group milestones to manage issues from multiple projects in the same milestone.')
= button_tag _('Promote these project milestones into a group milestone.'), class: 'btn btn-link js-popover-link text-align-left milestone-banner-link'
diff --git a/app/views/shared/milestones/_description.html.haml b/app/views/shared/milestones/_description.html.haml
index 5ff110bf94b..76d6c765ed6 100644
--- a/app/views/shared/milestones/_description.html.haml
+++ b/app/views/shared/milestones/_description.html.haml
@@ -1,8 +1,9 @@
.detail-page-description.milestone-detail
- %h2.title
+ %h2{ data: { qa_selector: "milestone_title_content" } }
+ .title
= markdown_field(milestone, :title)
- if milestone.try(:description).present?
- %div
+ %div{ data: { qa_selector: "milestone_description_content" } }
.description.md
= markdown_field(milestone, :description)
diff --git a/app/views/shared/milestones/_form_dates.html.haml b/app/views/shared/milestones/_form_dates.html.haml
index 6dbc460d9bf..e995584309a 100644
--- a/app/views/shared/milestones/_form_dates.html.haml
+++ b/app/views/shared/milestones/_form_dates.html.haml
@@ -3,11 +3,11 @@
.col-form-label.col-sm-2
= f.label :start_date, _('Start Date')
.col-sm-10
- = f.text_field :start_date, class: "datepicker form-control", placeholder: _('Select start date'), autocomplete: 'off'
- %a.inline.float-right.prepend-top-5.js-clear-start-date{ href: "#" }= _('Clear start date')
+ = f.text_field :start_date, class: "datepicker form-control", data: { qa_selector: "start_date_field" }, placeholder: _('Select start date'), autocomplete: 'off'
+ %a.inline.float-right.gl-mt-2.js-clear-start-date{ href: "#" }= _('Clear start date')
.form-group.row
.col-form-label.col-sm-2
= f.label :due_date, _('Due Date')
.col-sm-10
- = f.text_field :due_date, class: "datepicker form-control", placeholder: _('Select due date'), autocomplete: 'off'
- %a.inline.float-right.prepend-top-5.js-clear-due-date{ href: "#" }= _('Clear due date')
+ = f.text_field :due_date, class: "datepicker form-control", data: { qa_selector: "due_date_field" }, placeholder: _('Select due date'), autocomplete: 'off'
+ %a.inline.float-right.gl-mt-2.js-clear-due-date{ href: "#" }= _('Clear due date')
diff --git a/app/views/shared/milestones/_header.html.haml b/app/views/shared/milestones/_header.html.haml
index 99a46f1fb85..ea90b674b34 100644
--- a/app/views/shared/milestones/_header.html.haml
+++ b/app/views/shared/milestones/_header.html.haml
@@ -33,4 +33,4 @@
= render 'shared/milestones/delete_button'
%button.btn.btn-default.btn-grouped.float-right.d-block.d-sm-none.js-sidebar-toggle{ type: 'button' }
- = icon('angle-double-left')
+ = sprite_icon('chevron-double-lg-left')
diff --git a/app/views/shared/milestones/_issues_tab.html.haml b/app/views/shared/milestones/_issues_tab.html.haml
index 6684f6d752a..dc54eefbaa9 100644
--- a/app/views/shared/milestones/_issues_tab.html.haml
+++ b/app/views/shared/milestones/_issues_tab.html.haml
@@ -6,7 +6,7 @@
.flash-warning#milestone-issue-count-warning
= milestone_issues_count_message(@milestone)
-.row.prepend-top-default
+.row.gl-mt-3
.col-md-4
= render 'shared/milestones/issuables', args.merge(title: s_('Milestones|Unstarted Issues (open and unassigned)'), issuables: issues.opened.unassigned, id: 'unassigned', show_counter: true)
.col-md-4
diff --git a/app/views/shared/milestones/_merge_requests_tab.haml b/app/views/shared/milestones/_merge_requests_tab.haml
index 4dba2473efc..0dbf2b27c8d 100644
--- a/app/views/shared/milestones/_merge_requests_tab.haml
+++ b/app/views/shared/milestones/_merge_requests_tab.haml
@@ -1,7 +1,7 @@
- args = { show_project_name: local_assigns.fetch(:show_project_name, false),
show_full_project_name: local_assigns.fetch(:show_full_project_name, false) }
-.row.prepend-top-default
+.row.gl-mt-3
.col-md-3
= render 'shared/milestones/issuables', args.merge(title: _('Work in progress (open and unassigned)'), issuables: merge_requests.opened.unassigned, id: 'unassigned', show_counter: true)
.col-md-3
diff --git a/app/views/shared/milestones/_milestone.html.haml b/app/views/shared/milestones/_milestone.html.haml
index 31505d2d9fb..ae5bf9572bd 100644
--- a/app/views/shared/milestones/_milestone.html.haml
+++ b/app/views/shared/milestones/_milestone.html.haml
@@ -5,17 +5,18 @@
%li{ class: "milestone milestone-#{milestone.closed? ? 'closed' : 'open'}", id: custom_dom_id }
.row
.col-sm-6
- .append-bottom-5
- %strong= link_to truncate(milestone.title, length: 100), milestone_path(milestone)
+ .gl-mb-2
+ %strong{ data: { qa_selector: "milestone_link", qa_milestone_title: milestone.title } }
+ = link_to truncate(milestone.title, length: 100), milestone_path(milestone)
- if @group
= " - #{milestone_type}"
- if milestone.due_date || milestone.start_date
- .text-tertiary.append-bottom-5
+ .text-tertiary.gl-mb-2
= milestone_date_range(milestone)
- recent_releases, total_count, more_count = recent_releases_with_counts(milestone)
- unless total_count.zero?
- .text-tertiary.append-bottom-5.milestone-release-links
+ .text-tertiary.gl-mb-2.milestone-release-links
= sprite_icon("rocket", size: 12)
= n_('Release', 'Releases', total_count)
- recent_releases.each do |release|
diff --git a/app/views/shared/milestones/_sidebar.html.haml b/app/views/shared/milestones/_sidebar.html.haml
index 160f6487439..7fd657ec2dd 100644
--- a/app/views/shared/milestones/_sidebar.html.haml
+++ b/app/views/shared/milestones/_sidebar.html.haml
@@ -24,7 +24,7 @@
- if @project && can?(current_user, :admin_milestone, @project)
= link_to s_('MilestoneSidebar|Edit'), edit_project_milestone_path(@project, @milestone), class: 'js-sidebar-dropdown-toggle edit-link float-right'
.value
- %span.value-content
+ %span.value-content{ data: { qa_selector: 'start_date_content' } }
- if milestone.start_date
%span.bold= milestone.start_date.to_s(:medium)
- else
@@ -60,7 +60,7 @@
- if @project && can?(current_user, :admin_milestone, @project)
= link_to s_('MilestoneSidebar|Edit'), edit_project_milestone_path(@project, @milestone), class: 'js-sidebar-dropdown-toggle edit-link float-right'
.value.hide-collapsed
- %span.value-content
+ %span.value-content{ data: { qa_selector: 'due_date_content' } }
- if milestone.due_date
%span.bold= milestone.due_date.to_s(:medium)
- else
diff --git a/app/views/shared/milestones/_tab_loading.html.haml b/app/views/shared/milestones/_tab_loading.html.haml
index dfca6a184be..fe1184114e9 100644
--- a/app/views/shared/milestones/_tab_loading.html.haml
+++ b/app/views/shared/milestones/_tab_loading.html.haml
@@ -1,2 +1,2 @@
-.text-center.prepend-top-default
+.text-center.gl-mt-3
.spinner.spinner-md
diff --git a/app/views/shared/milestones/_tabs.html.haml b/app/views/shared/milestones/_tabs.html.haml
index 538ebe79641..34f476241c6 100644
--- a/app/views/shared/milestones/_tabs.html.haml
+++ b/app/views/shared/milestones/_tabs.html.haml
@@ -1,6 +1,6 @@
.scrolling-tabs-container.inner-page-scroll-tabs.is-smaller
- .fade-left= icon('angle-left')
- .fade-right= icon('angle-right')
+ .fade-left= sprite_icon('chevron-lg-left', size: 12)
+ .fade-right= sprite_icon('chevron-lg-right', size: 12)
%ul.nav-links.scrolling-tabs.js-milestone-tabs.nav.nav-tabs
%li.nav-item
= link_to '#tab-issues', class: 'nav-link active', data: { toggle: 'tab', show: '.tab-issues-buttons' } do
diff --git a/app/views/shared/milestones/_top.html.haml b/app/views/shared/milestones/_top.html.haml
index 49df00940b7..4d209c30e7b 100644
--- a/app/views/shared/milestones/_top.html.haml
+++ b/app/views/shared/milestones/_top.html.haml
@@ -7,7 +7,7 @@
= render 'shared/milestones/description', milestone: milestone
- if milestone.complete? && milestone.active?
- .alert.alert-success.prepend-top-default
+ .alert.alert-success.gl-mt-3
%span
= _('All issues for this milestone are closed.')
= group ? _('You may close the milestone now.') : _('Navigate to the project to close the milestone.')
diff --git a/app/views/shared/notes/_comment_button.html.haml b/app/views/shared/notes/_comment_button.html.haml
index 8d74eacc7dc..e151e55d0d2 100644
--- a/app/views/shared/notes/_comment_button.html.haml
+++ b/app/views/shared/notes/_comment_button.html.haml
@@ -1,7 +1,7 @@
- noteable_name = @note.noteable.human_class_name
-.float-left.btn-group.append-right-10.droplab-dropdown.comment-type-dropdown.js-comment-type-dropdown
- %input.btn.btn-nr.btn-success.js-comment-button.js-comment-submit-button{ type: 'submit', value: _('Comment') }
+.float-left.btn-group.gl-mr-3.droplab-dropdown.comment-type-dropdown.js-comment-type-dropdown
+ %input.btn.btn-nr.btn-success.js-comment-button.js-comment-submit-button{ type: 'submit', value: _('Comment'), data: { qa_selector: 'comment_button' } }
- if @note.can_be_discussion_note?
= button_tag type: 'button', class: 'btn btn-nr dropdown-toggle btn-success js-note-new-discussion js-disable-on-submit', data: { 'dropdown-trigger' => '#resolvable-comment-menu' }, 'aria-label' => _('Open comment type dropdown') do
diff --git a/app/views/shared/notes/_edit_form.html.haml b/app/views/shared/notes/_edit_form.html.haml
index 244c191af12..79feb12bed5 100644
--- a/app/views/shared/notes/_edit_form.html.haml
+++ b/app/views/shared/notes/_edit_form.html.haml
@@ -3,12 +3,12 @@
= hidden_field_tag :target_id, '', class: 'js-form-target-id'
= hidden_field_tag :target_type, '', class: 'js-form-target-type'
= render layout: 'shared/md_preview', locals: { url: preview_markdown_path(project), referenced_users: true } do
- = render 'shared/zen', attr: 'note[note]', classes: 'note-textarea js-note-text js-task-list-field', placeholder: _("Write a comment or drag your files here…")
+ = render 'shared/zen', attr: 'note[note]', classes: 'note-textarea js-note-text js-task-list-field', qa_selector: 'edit_note_field', placeholder: _("Write a comment or drag your files here…")
= render 'shared/notes/hints'
.note-form-actions.clearfix
.settings-message.note-edit-warning.js-finish-edit-warning
= _("Finish editing this message first!")
- = submit_tag _('Save comment'), class: 'btn btn-nr btn-success js-comment-save-button'
+ = submit_tag _('Save comment'), class: 'btn btn-nr btn-success js-comment-save-button', data: { qa_selector: 'save_comment_button' }
%button.btn.btn-nr.btn-cancel.note-edit-cancel{ type: 'button' }
= _("Cancel")
diff --git a/app/views/shared/notes/_form.html.haml b/app/views/shared/notes/_form.html.haml
index 40e36728642..f1686417f8d 100644
--- a/app/views/shared/notes/_form.html.haml
+++ b/app/views/shared/notes/_form.html.haml
@@ -26,7 +26,7 @@
.discussion-form-container.discussion-with-resolve-btn.flex-column.p-0
= render layout: 'shared/md_preview', locals: { url: preview_url, referenced_users: true } do
- = render 'shared/zen', f: f,
+ = render 'shared/zen', f: f, qa_selector: 'note_field',
attr: :note,
classes: 'note-textarea js-note-text',
placeholder: _("Write a comment or drag your files here…"),
diff --git a/app/views/shared/notes/_hints.html.haml b/app/views/shared/notes/_hints.html.haml
index 902a6e9b363..abd5d8cd9db 100644
--- a/app/views/shared/notes/_hints.html.haml
+++ b/app/views/shared/notes/_hints.html.haml
@@ -1,10 +1,10 @@
- supports_quick_actions = local_assigns.fetch(:supports_quick_actions, false)
.comment-toolbar.clearfix
.toolbar-text
- = link_to _('Markdown'), help_page_path('user/markdown'), target: '_blank', tabindex: -1
+ = link_to _('Markdown'), help_page_path('user/markdown'), target: '_blank'
- if supports_quick_actions
and
- = link_to _('quick actions'), help_page_path('user/project/quick_actions'), target: '_blank', tabindex: -1
+ = link_to _('quick actions'), help_page_path('user/project/quick_actions'), target: '_blank'
are
- else
is
@@ -12,24 +12,23 @@
%span.uploading-container
%span.uploading-progress-container.hide
- = icon('file-image-o', class: 'toolbar-button-icon')
+ = sprite_icon('media', size: 16, css_class: 'gl-icon gl-vertical-align-text-bottom')
%span.attaching-file-message
-# Populated by app/assets/javascripts/dropzone_input.js
%span.uploading-progress 0%
- %span.uploading-spinner
- .toolbar-button-icon.spinner.align-text-top
+ = loading_icon(css_class: 'align-text-bottom gl-mr-2')
%span.uploading-error-container.hide
%span.uploading-error-icon
- = icon('file-image-o', class: 'toolbar-button-icon')
+ = sprite_icon('media', size: 16, css_class: 'gl-icon gl-vertical-align-text-bottom')
%span.uploading-error-message
-# Populated by app/assets/javascripts/dropzone_input.js
%button.retry-uploading-link{ type: 'button' }= _("Try again")
or
%button.attach-new-file.markdown-selector{ type: 'button' }= _("attach a new file")
- %button.markdown-selector.button-attach-file.btn-link{ type: 'button', tabindex: '-1' }
- = icon('file-image-o', class: 'toolbar-button-icon')
+ %button.btn.markdown-selector.button-attach-file.btn-link{ type: 'button', tabindex: '-1' }
+ = sprite_icon('media', size: 16)
%span.text-attach-file<>
= _("Attach a file")
diff --git a/app/views/shared/notes/_note.html.haml b/app/views/shared/notes/_note.html.haml
index e6c8e13c5c1..95450a5df3c 100644
--- a/app/views/shared/notes/_note.html.haml
+++ b/app/views/shared/notes/_note.html.haml
@@ -34,7 +34,7 @@
%span.note-header-author-name.bold
= note.author.name
= user_status(note.author)
- %span.note-headline-light
+ %span.note-headline-light{ data: { qa_selector: 'note_author_content' } }
= note.author.to_reference
%span.note-headline-light.note-headline-meta
- if note.system
@@ -51,7 +51,7 @@
- else
= render 'projects/notes/actions', note: note, note_editable: note_editable
.note-body{ class: note_editable ? 'js-task-list-container' : '' }
- .note-text.md
+ .note-text.md{ data: { qa_selector: 'note_content' } }
= markdown_field(note, :note)
= edited_time_ago_with_tooltip(note, placement: 'bottom', html_class: 'note_edited_ago')
.original-note-content.hidden{ data: { post_url: note_url(note), target_id: note.noteable.id, target_type: note.noteable.class.name.underscore } }
diff --git a/app/views/shared/notes/_notes_with_form.html.haml b/app/views/shared/notes/_notes_with_form.html.haml
index 002189e6ecd..fa103ad447a 100644
--- a/app/views/shared/notes/_notes_with_form.html.haml
+++ b/app/views/shared/notes/_notes_with_form.html.haml
@@ -18,12 +18,12 @@
.timeline-content.timeline-content-form
= render "shared/notes/form", view: diff_view, supports_autocomplete: autocomplete
- elsif !current_user
- .disabled-comment.text-center.prepend-top-default
+ .disabled-comment.text-center.gl-mt-3
- link_to_register = link_to(_("register"), new_session_path(:user, redirect_to_referer: 'yes', anchor: 'register-pane'), class: 'js-register-link')
- link_to_sign_in = link_to(_("sign in"), new_session_path(:user, redirect_to_referer: 'yes'), class: 'js-sign-in-link')
= _("Please %{link_to_register} or %{link_to_sign_in} to comment").html_safe % { link_to_register: link_to_register, link_to_sign_in: link_to_sign_in }
- elsif discussion_locked
- .disabled-comment.text-center.prepend-top-default
+ .disabled-comment.text-center.gl-mt-3
%span.issuable-note-warning
= sprite_icon('lock', size: 16, css_class: 'icon')
%span
diff --git a/app/views/shared/notifications/_new_button.html.haml b/app/views/shared/notifications/_new_button.html.haml
index 796ff095eea..fbcfec5fd96 100644
--- a/app/views/shared/notifications/_new_button.html.haml
+++ b/app/views/shared/notifications/_new_button.html.haml
@@ -8,7 +8,7 @@
- else
- button_title = _("Notification setting - %{notification_title}") % { notification_title: notification_title(notification_setting.level) }
- .js-notification-dropdown.notification-dropdown.home-panel-action-button.prepend-top-default.gl-mr-3.dropdown.inline
+ .js-notification-dropdown.notification-dropdown.home-panel-action-button.gl-mt-3.gl-mr-3.dropdown.inline
= form_for notification_setting, remote: true, html: { class: "inline notification-form no-label" } do |f|
= hidden_setting_source_input(notification_setting)
= hidden_field_tag "hide_label", true
diff --git a/app/views/shared/projects/_edit_information.html.haml b/app/views/shared/projects/_edit_information.html.haml
index 9230e045a81..5a2f4328837 100644
--- a/app/views/shared/projects/_edit_information.html.haml
+++ b/app/views/shared/projects/_edit_information.html.haml
@@ -1,5 +1,5 @@
- unless can?(current_user, :push_code, @project)
- .inline.prepend-left-10
+ .inline.gl-ml-3
- if @project.branch_allows_collaboration?(current_user, selected_branch)
= commit_in_single_accessible_branch
- else
diff --git a/app/views/shared/projects/_project.html.haml b/app/views/shared/projects/_project.html.haml
index fc3f1a8d1c1..626e94e0202 100644
--- a/app/views/shared/projects/_project.html.haml
+++ b/app/views/shared/projects/_project.html.haml
@@ -12,11 +12,10 @@
- css_class += " no-description" if project.description.blank? && !show_last_commit_as_description
- cache_key = project_list_cache_key(project, pipeline_status: pipeline_status)
- updated_tooltip = time_ago_with_tooltip(project.last_activity_date)
-- show_pipeline_status_icon = pipeline_status && can?(current_user, :read_cross_project) && project.pipeline_status.has_status? && can?(current_user, :read_build, project)
+- show_pipeline_status_icon = pipeline_status && can?(current_user, :read_cross_project) && project.pipeline_status.has_status? && can?(current_user, :read_build, project) && project.last_pipeline.present?
- css_controls_class = compact_mode ? [] : ["flex-lg-row", "justify-content-lg-between"]
- css_controls_class << "with-pipeline-status" if show_pipeline_status_icon
- avatar_container_class = project.creator && use_creator_avatar ? '' : 'rect-avatar'
-- license_name = project_license_name(project)
%li.project-row.d-flex{ class: css_class }
= cache(cache_key) do
@@ -40,13 +39,13 @@
%span.project-name<
= project.name
- %span.metadata-info.visibility-icon.append-right-10.gl-mt-3.text-secondary.has-tooltip{ data: { container: 'body', placement: 'top' }, title: visibility_icon_description(project) }
+ %span.metadata-info.visibility-icon.gl-mr-3.gl-mt-3.text-secondary.has-tooltip{ data: { container: 'body', placement: 'top' }, title: visibility_icon_description(project) }
= visibility_level_icon(project.visibility_level, fw: true)
- - if explore_projects_tab? && license_name
- %span.metadata-info.d-inline-flex.align-items-center.append-right-10.gl-mt-3
- = sprite_icon('scale', size: 14, css_class: 'append-right-4')
- = license_name
+ - if explore_projects_tab? && project_license_name(project)
+ %span.metadata-info.d-inline-flex.align-items-center.gl-mr-3.gl-mt-3
+ = sprite_icon('scale', size: 14, css_class: 'gl-mr-2')
+ = project_license_name(project)
- if !explore_projects_tab? && access&.nonzero?
-# haml-lint:disable UnnecessaryStringOutput
@@ -59,10 +58,10 @@
= render_if_exists 'compliance_management/compliance_framework/compliance_framework_badge', project: project
- if show_last_commit_as_description
- .description.d-none.d-sm-block.append-right-default
+ .description.d-none.d-sm-block.gl-mr-3
= link_to_markdown(project.commit.title, project_commit_path(project, project.commit), class: "commit-row-message")
- elsif project.description.present?
- .description.d-none.d-sm-block.append-right-default
+ .description.d-none.d-sm-block.gl-mr-3
= markdown_field(project, :description)
.controls.d-flex.flex-sm-column.align-items-center.align-items-sm-end.flex-wrap.flex-shrink-0.text-secondary{ class: css_controls_class.join(" ") }
@@ -77,25 +76,25 @@
= link_to project_starrers_path(project),
class: "d-flex align-items-center icon-wrapper stars has-tooltip",
title: _('Stars'), data: { container: 'body', placement: 'top' } do
- = sprite_icon('star', size: 14, css_class: 'append-right-4')
+ = sprite_icon('star', size: 14, css_class: 'gl-mr-2')
= number_with_delimiter(project.star_count)
- if forks
= link_to project_forks_path(project),
class: "align-items-center icon-wrapper forks has-tooltip",
title: _('Forks'), data: { container: 'body', placement: 'top' } do
- = sprite_icon('fork', size: 14, css_class: 'append-right-4')
+ = sprite_icon('fork', size: 14, css_class: 'gl-mr-2')
= number_with_delimiter(project.forks_count)
- if show_merge_request_count?(disabled: !merge_requests, compact_mode: compact_mode)
= link_to project_merge_requests_path(project),
class: "d-none d-xl-flex align-items-center icon-wrapper merge-requests has-tooltip",
title: _('Merge Requests'), data: { container: 'body', placement: 'top' } do
- = sprite_icon('git-merge', size: 14, css_class: 'append-right-4')
+ = sprite_icon('git-merge', size: 14, css_class: 'gl-mr-2')
= number_with_delimiter(project.open_merge_requests_count)
- if show_issue_count?(disabled: !issues, compact_mode: compact_mode)
= link_to project_issues_path(project),
class: "d-none d-xl-flex align-items-center icon-wrapper issues has-tooltip",
title: _('Issues'), data: { container: 'body', placement: 'top' } do
- = sprite_icon('issues', size: 14, css_class: 'append-right-4')
+ = sprite_icon('issues', size: 14, css_class: 'gl-mr-2')
= number_with_delimiter(project.open_issues_count)
.updated-note
%span
diff --git a/app/views/shared/promotions/_promote_servicedesk.html.haml b/app/views/shared/promotions/_promote_servicedesk.html.haml
new file mode 100644
index 00000000000..f7f65c34c75
--- /dev/null
+++ b/app/views/shared/promotions/_promote_servicedesk.html.haml
@@ -0,0 +1,13 @@
+.user-callout.promotion-callout.js-service-desk-callout#promote_service_desk{ data: { uid: 'promote_service_desk_dismissed' } }
+ .bordered-box.content-block
+ %button.btn.btn-default.close.js-close-callout{ type: 'button', 'aria-label' => 'Dismiss Service Desk promotion' }
+ = icon('times', class: 'dismiss-icon', 'aria-hidden' => 'true')
+ .svg-container
+ = custom_icon('icon_service_desk')
+ .user-callout-copy
+ -# haml-lint:disable NoPlainNodes
+ %h4
+ Improve customer support with GitLab Service Desk.
+ %p
+ GitLab Service Desk is a simple way to allow people to create issues in your GitLab instance without needing their own user account. It provides a unique email address for end users to create issues in a project, and replies can be sent either through the GitLab interface or by email. End users will only see the thread through email.
+ = link_to 'Read more', help_page_path('user/project/service_desk.md'), target: '_blank'
diff --git a/app/views/shared/runners/_runner_description.html.haml b/app/views/shared/runners/_runner_description.html.haml
index a47bbd55325..d3e50cfe92f 100644
--- a/app/views/shared/runners/_runner_description.html.haml
+++ b/app/views/shared/runners/_runner_description.html.haml
@@ -1,4 +1,4 @@
-.light.prepend-top-default
+.light.gl-mt-3
%p
= _("You can set up as many Runners as you need to run your jobs.")
%br
diff --git a/app/views/shared/runners/show.html.haml b/app/views/shared/runners/show.html.haml
index f62eed694d2..8a78f12bdd8 100644
--- a/app/views/shared/runners/show.html.haml
+++ b/app/views/shared/runners/show.html.haml
@@ -1,4 +1,4 @@
-- page_title "#{@runner.description} ##{@runner.id}", "Runners"
+- page_title "#{@runner.description} ##{@runner.id}", _("Runners")
%h3.page-title
Runner ##{@runner.id}
diff --git a/app/views/shared/snippets/_header.html.haml b/app/views/shared/snippets/_header.html.haml
index 7f213c50de2..36b6bfd061f 100644
--- a/app/views/shared/snippets/_header.html.haml
+++ b/app/views/shared/snippets/_header.html.haml
@@ -1,6 +1,6 @@
.detail-page-header
.detail-page-header-body
- .snippet-box.has-tooltip.inline.append-right-5{ title: snippet_visibility_level_description(@snippet.visibility_level, @snippet), data: { container: "body" } }
+ .snippet-box.has-tooltip.inline.gl-mr-2{ title: snippet_visibility_level_description(@snippet.visibility_level, @snippet), data: { container: "body" } }
%span.sr-only
= visibility_level_label(@snippet.visibility_level)
= visibility_level_icon(@snippet.visibility_level, fw: false)
diff --git a/app/views/shared/snippets/_snippet.html.haml b/app/views/shared/snippets/_snippet.html.haml
index 128ddbb8e8b..b2c9a74b177 100644
--- a/app/views/shared/snippets/_snippet.html.haml
+++ b/app/views/shared/snippets/_snippet.html.haml
@@ -11,7 +11,7 @@
%ul.controls
%li
= link_to gitlab_snippet_path(snippet, anchor: 'notes'), class: ('no-comments' if notes_count.zero?) do
- = icon('comments')
+ = sprite_icon('comments', size: 16, css_class: 'gl-vertical-align-text-bottom')
= notes_count
%li
%span.sr-only
diff --git a/app/views/shared/web_hooks/_hook.html.haml b/app/views/shared/web_hooks/_hook.html.haml
index 470e2f6b904..a957f9f6dfa 100644
--- a/app/views/shared/web_hooks/_hook.html.haml
+++ b/app/views/shared/web_hooks/_hook.html.haml
@@ -10,7 +10,7 @@
= _('SSL Verification:')
= hook.enable_ssl_verification ? _('enabled') : _('disabled')
- .col-md-4.col-lg-5.text-right-md.prepend-top-5
+ .col-md-4.col-lg-5.text-right-md.gl-mt-2
%span>= render 'shared/web_hooks/test_button', hook: hook, button_class: 'btn-sm gl-mr-3'
%span>= link_to _('Edit'), edit_hook_path(hook), class: 'btn btn-sm gl-mr-3'
= link_to _('Delete'), destroy_hook_path(hook), data: { confirm: _('Are you sure?') }, method: :delete, class: 'btn btn-sm'
diff --git a/app/views/shared/web_hooks/_index.html.haml b/app/views/shared/web_hooks/_index.html.haml
index 149f4baeb21..794418b8336 100644
--- a/app/views/shared/web_hooks/_index.html.haml
+++ b/app/views/shared/web_hooks/_index.html.haml
@@ -10,5 +10,5 @@
- hooks.each do |hook|
= render 'shared/web_hooks/hook', hook: hook
- else
- %p.text-center.prepend-top-default.append-bottom-default
+ %p.text-center.gl-mt-3.gl-mb-3
= _('No webhooks found, add one in the form above.')
diff --git a/app/views/shared/wikis/_form.html.haml b/app/views/shared/wikis/_form.html.haml
index 8ea06d4d6c3..92b9207aaa4 100644
--- a/app/views/shared/wikis/_form.html.haml
+++ b/app/views/shared/wikis/_form.html.haml
@@ -1,4 +1,4 @@
-- form_classes = %w[wiki-form common-note-form prepend-top-default js-quick-submit]
+- form_classes = %w[wiki-form common-note-form gl-mt-3 js-quick-submit]
- if @page.persisted?
- form_action = wiki_page_path(@wiki, @page)
@@ -20,7 +20,7 @@
.col-sm-12= f.label :title, class: 'control-label-full-width'
.col-sm-12
= f.text_field :title, class: 'form-control qa-wiki-title-textbox', value: @page.title, required: true, autofocus: !@page.persisted?, placeholder: s_('Wiki|Page title')
- %span.d-inline-block.mw-100.prepend-top-5
+ %span.d-inline-block.mw-100.gl-mt-2
= icon('lightbulb-o')
- if @page.persisted?
= s_("WikiEditPageTip|Tip: You can move this page by adding the path to the beginning of the title.")
diff --git a/app/views/shared/wikis/_pages_wiki_page.html.haml b/app/views/shared/wikis/_pages_wiki_page.html.haml
index 534884eb848..b56ae2bf9b1 100644
--- a/app/views/shared/wikis/_pages_wiki_page.html.haml
+++ b/app/views/shared/wikis/_pages_wiki_page.html.haml
@@ -1,5 +1,5 @@
%li
- = link_to wiki_page.title, wiki_page_path(@wiki, wiki_page)
+ = link_to wiki_page.title, wiki_page_path(@wiki, wiki_page), data: { qa_selector: 'wiki_page_link', qa_page_name: wiki_page.slug }
%small (#{wiki_page.format})
.float-right
- if wiki_page.last_version
diff --git a/app/views/shared/wikis/_sidebar.html.haml b/app/views/shared/wikis/_sidebar.html.haml
index 8cfb95cdcf5..cddf19fbc8e 100644
--- a/app/views/shared/wikis/_sidebar.html.haml
+++ b/app/views/shared/wikis/_sidebar.html.haml
@@ -1,12 +1,12 @@
%aside.right-sidebar.right-sidebar-expanded.wiki-sidebar.js-wiki-sidebar.js-right-sidebar{ data: { "offset-top" => "50", "spy" => "affix" } }
.sidebar-container
- .block.wiki-sidebar-header.append-bottom-default.w-100
+ .block.wiki-sidebar-header.gl-mb-3.w-100
%a.gutter-toggle.float-right.d-block.d-sm-block.d-md-none.js-sidebar-wiki-toggle{ href: "#" }
- = icon('angle-double-right')
+ = sprite_icon('chevron-double-lg-right', size: 16, css_class: 'gl-icon')
- git_access_url = wiki_path(@wiki, action: :git_access)
= link_to git_access_url, class: active_nav_link?(path: 'wikis#git_access') ? 'active' : '', data: { qa_selector: 'clone_repository_link' } do
- = icon('cloud-download', class: 'append-right-5')
+ = sprite_icon('download', size: 16, css_class: 'gl-mr-2')
%span= _("Clone repository")
.blocks-container
@@ -18,5 +18,5 @@
= render @sidebar_wiki_entries, context: 'sidebar'
.block.w-100
- if @sidebar_limited
- = link_to wiki_path(@wiki, action: :pages), class: 'btn btn-block' do
+ = link_to wiki_path(@wiki, action: :pages), class: 'btn btn-block', data: { qa_selector: 'view_all_pages_button' } do
= s_("Wiki|View All Pages")
diff --git a/app/views/shared/wikis/_sidebar_wiki_page.html.haml b/app/views/shared/wikis/_sidebar_wiki_page.html.haml
index 2573471f9f9..4259633280a 100644
--- a/app/views/shared/wikis/_sidebar_wiki_page.html.haml
+++ b/app/views/shared/wikis/_sidebar_wiki_page.html.haml
@@ -1,3 +1,3 @@
%li{ class: active_when(params[:id] == wiki_page.slug) }
- = link_to wiki_page_path(@wiki, wiki_page) do
+ = link_to wiki_page_path(@wiki, wiki_page), data: { qa_selector: 'wiki_page_link', qa_page_name: wiki_page.slug } do
= wiki_page.human_title
diff --git a/app/views/shared/wikis/diff.html.haml b/app/views/shared/wikis/diff.html.haml
new file mode 100644
index 00000000000..6fce3f5894e
--- /dev/null
+++ b/app/views/shared/wikis/diff.html.haml
@@ -0,0 +1,32 @@
+- wiki_page_title @page, _('Changes')
+- commit = @diffs.diffable
+
+.wiki-page-header.top-area.has-sidebar-toggle.flex-column.flex-lg-row
+ = wiki_sidebar_toggle_button
+
+ .nav-text
+ %h2.wiki-page-title
+ = link_to_wiki_page @page
+ %span.light
+ &middot;
+ = _('Changes')
+
+ .nav-controls.pb-md-3.pb-lg-0
+ = link_to wiki_page_path(@wiki, @page, action: :history), class: 'btn', role: 'button', data: { qa_selector: 'page_history_button' } do
+ = s_('Wiki|Page history')
+
+.page-content-header
+ .header-main-content
+ %strong= markdown_field(commit, :title)
+ %span.d-none.d-sm-inline= _('authored')
+ #{time_ago_with_tooltip(commit.authored_date)}
+ %span= s_('ByAuthor|by')
+ = author_avatar(commit, size: 24, has_tooltip: false)
+ %strong
+ = commit_author_link(commit, avatar: true, size: 24)
+ - if commit.description.present?
+ %pre.commit-description<
+ = preserve(markdown_field(commit, :description))
+
+= render 'projects/diffs/diffs', diffs: @diffs
+= render 'shared/wikis/sidebar'
diff --git a/app/views/shared/wikis/edit.html.haml b/app/views/shared/wikis/edit.html.haml
index 5bda8d85627..64a4816def6 100644
--- a/app/views/shared/wikis/edit.html.haml
+++ b/app/views/shared/wikis/edit.html.haml
@@ -1,18 +1,14 @@
-- @content_class = "limit-container-width" unless fluid_layout
-- add_to_breadcrumbs _("Wiki"), wiki_page_path(@wiki, @page)
-- breadcrumb_title @page.persisted? ? _("Edit") : _("New")
-- page_title @page.persisted? ? _("Edit") : _("New"), @page.human_title, _("Wiki")
+- wiki_page_title @page, @page.persisted? ? _('Edit') : _('New')
= wiki_page_errors(@error)
.wiki-page-header.top-area.has-sidebar-toggle.flex-column.flex-lg-row
- %button.btn.btn-default.sidebar-toggle.js-sidebar-wiki-toggle{ role: "button", type: "button" }
- = icon('angle-double-left')
+ = wiki_sidebar_toggle_button
.nav-text
%h2.wiki-page-title
- if @page.persisted?
- = link_to @page.human_title, wiki_page_path(@wiki, @page)
+ = link_to_wiki_page @page
%span.light
&middot;
= s_("Wiki|Edit Page")
diff --git a/app/views/shared/wikis/history.html.haml b/app/views/shared/wikis/history.html.haml
index ec07082bd02..f9d21c8fb57 100644
--- a/app/views/shared/wikis/history.html.haml
+++ b/app/views/shared/wikis/history.html.haml
@@ -1,41 +1,38 @@
-- page_title _("History"), @page.human_title, _("Wiki")
+- wiki_page_title @page, _('History')
.wiki-page-header.top-area.has-sidebar-toggle.flex-column.flex-lg-row
- %button.btn.btn-default.sidebar-toggle.js-sidebar-wiki-toggle{ role: "button", type: "button" }
- = icon('angle-double-left')
+ = wiki_sidebar_toggle_button
.nav-text
%h2.wiki-page-title
- = link_to @page.human_title, wiki_page_path(@wiki, @page)
+ = link_to_wiki_page @page
%span.light
&middot;
- = _("History")
+ = _('History')
-.table-holder
- %table.table
- %thead
- %tr
- %th= s_("Wiki|Page version")
- %th= _("Author")
- %th= _("Commit Message")
- %th= _("Last updated")
- %th= _("Format")
- %tbody
- - @page_versions.each_with_index do |version, index|
- - commit = version
+.prepend-top-default.gl-mb-3
+ .table-holder
+ %table.table.wiki-history
+ %thead
%tr
- %td
- = link_to wiki_page_path(@wiki, @page, version_id: index == 0 ? nil : commit.id) do
- = truncate_sha(commit.id)
- %td
- = commit.author_name
- %td
- = commit.message
- %td
- #{time_ago_with_tooltip(version.authored_date)}
- %td
- %strong
- = version.format
-= paginate @page_versions, theme: 'gitlab'
+ %th= s_('Wiki|Page version')
+ %th= _('Author')
+ %th= _('Changes')
+ %th= _('Last updated')
+ %tbody
+ - @page_versions.each do |commit|
+ %tr
+ %td
+ = link_to wiki_page_path(@wiki, @page, version_id: commit.id) do
+ = truncate_sha(commit.id)
+ %td
+ = commit.author_name
+ %td
+ %span.str-truncated-60
+ = link_to wiki_page_path(@wiki, @page, action: :diff, version_id: commit.id), { title: commit.message } do
+ = commit.message
+ %td
+ = time_ago_with_tooltip(commit.authored_date)
+ = paginate @page_versions, theme: 'gitlab'
= render 'shared/wikis/sidebar'
diff --git a/app/views/shared/wikis/pages.html.haml b/app/views/shared/wikis/pages.html.haml
index 987c696cdfe..35a62ec2bb4 100644
--- a/app/views/shared/wikis/pages.html.haml
+++ b/app/views/shared/wikis/pages.html.haml
@@ -11,7 +11,7 @@
.nav-controls.pb-md-3.pb-lg-0
= link_to wiki_path(@wiki, action: :git_access), class: 'btn' do
- = icon('cloud-download')
+ = sprite_icon('download')
= _("Clone repository")
.dropdown.inline.wiki-sort-dropdown
@@ -19,7 +19,7 @@
.btn-group{ role: 'group' }
%button.dropdown-toggle{ type: 'button', data: { toggle: 'dropdown', display: 'static' }, class: 'btn btn-default' }
= sort_title
- = icon('chevron-down')
+ = sprite_icon('chevron-down')
%ul.dropdown-menu.dropdown-menu-right.dropdown-menu-selectable.dropdown-menu-sort
%li
= sortable_item(s_("Wiki|Title"), wiki_path(@wiki, action: :pages, sort: Wiki::TITLE_ORDER), sort_title)
diff --git a/app/views/shared/wikis/show.html.haml b/app/views/shared/wikis/show.html.haml
index a4f3996e5de..a7c734f5af4 100644
--- a/app/views/shared/wikis/show.html.haml
+++ b/app/views/shared/wikis/show.html.haml
@@ -1,19 +1,14 @@
-- @content_class = "limit-container-width" unless fluid_layout
-- breadcrumb_title @page.human_title
-- wiki_breadcrumb_dropdown_links(@page.slug)
-- page_title @page.human_title, _("Wiki")
-- add_to_breadcrumbs _("Wiki"), wiki_path(@wiki)
+- wiki_page_title @page
.wiki-page-header.top-area.has-sidebar-toggle.flex-column.flex-lg-row
- %button.btn.btn-default.sidebar-toggle.js-sidebar-wiki-toggle{ role: "button", type: "button" }
- = icon('angle-double-left')
+ = wiki_sidebar_toggle_button
.nav-text.flex-fill
%h2.wiki-page-title{ data: { qa_selector: 'wiki_page_title' } }= @page.human_title
%span.wiki-last-edit-by
- if @page.last_version
= (_("Last edited by %{name}") % { name: "<strong>#{@page.last_version.author_name}</strong>" }).html_safe
- #{time_ago_with_tooltip(@page.last_version.authored_date)}
+ = time_ago_with_tooltip(@page.last_version.authored_date)
.nav-controls.pb-md-3.pb-lg-0
= render 'shared/wikis/main_links'
@@ -25,8 +20,8 @@
- history_link = link_to s_("WikiHistoricalPage|history"), wiki_page_path(@wiki, @page, action: :history)
= (s_("WikiHistoricalPage|You can view the %{most_recent_link} or browse the %{history_link}.") % { most_recent_link: most_recent_link, history_link: history_link }).html_safe
-.prepend-top-default.append-bottom-default
- .md{ data: { qa_selector: 'wiki_page_content' } }
+.gl-mt-3.gl-mb-3
+ .js-wiki-page-content.md{ data: { qa_selector: 'wiki_page_content', tracking_context: wiki_page_tracking_context(@page).to_json } }
= render_wiki_content(@page)
= render 'shared/wikis/sidebar'
diff --git a/app/views/sherlock/file_samples/show.html.haml b/app/views/sherlock/file_samples/show.html.haml
index 7255d352775..cc8bdbae55d 100644
--- a/app/views/sherlock/file_samples/show.html.haml
+++ b/app/views/sherlock/file_samples/show.html.haml
@@ -12,7 +12,7 @@
= t('sherlock.file_sample')
= @file_sample.id
-.prepend-top-default
+.gl-mt-3
%p
%span.light
#{t('sherlock.time')}:
@@ -32,7 +32,7 @@
= @file_sample.file
.code.file-content.js-syntax-highlight
.line-numbers
- %table.sherlock-line-samples-table
+ %table.sherlock-line-samples-table.gl-mb-0
%thead
%tr
%th= t('sherlock.line_capitalized')
diff --git a/app/views/sherlock/queries/_backtrace.html.haml b/app/views/sherlock/queries/_backtrace.html.haml
index 38b4d2c6102..ff5a6b73e47 100644
--- a/app/views/sherlock/queries/_backtrace.html.haml
+++ b/app/views/sherlock/queries/_backtrace.html.haml
@@ -1,4 +1,4 @@
-.prepend-top-default
+.gl-mt-3
.card
.card-header
%strong
diff --git a/app/views/sherlock/queries/_general.html.haml b/app/views/sherlock/queries/_general.html.haml
index 1514ad55d71..92f4f16f453 100644
--- a/app/views/sherlock/queries/_general.html.haml
+++ b/app/views/sherlock/queries/_general.html.haml
@@ -1,4 +1,4 @@
-.prepend-top-default
+.gl-mt-3
.card
.card-header
%strong
diff --git a/app/views/sherlock/transactions/_general.html.haml b/app/views/sherlock/transactions/_general.html.haml
index 9c028b5c741..7cf6f27e1af 100644
--- a/app/views/sherlock/transactions/_general.html.haml
+++ b/app/views/sherlock/transactions/_general.html.haml
@@ -1,4 +1,4 @@
-.prepend-top-default
+.gl-mt-3
.card
.card-header
%strong
diff --git a/app/views/snippets/_actions.html.haml b/app/views/snippets/_actions.html.haml
index 2ff174971cc..566395133a1 100644
--- a/app/views/snippets/_actions.html.haml
+++ b/app/views/snippets/_actions.html.haml
@@ -13,7 +13,7 @@
- if @snippet.submittable_as_spam_by?(current_user)
= link_to _('Submit as spam'), mark_as_spam_snippet_path(@snippet), method: :post, class: 'btn btn-grouped btn-spam', title: _('Submit as spam')
.d-block.d-sm-none.dropdown
- %button.btn.btn-default.btn-block.gl-mb-0.prepend-top-5{ data: { toggle: "dropdown" } }
+ %button.btn.btn-default.btn-block.gl-mb-0.gl-mt-2{ data: { toggle: "dropdown" } }
= _("Options")
= icon('caret-down')
.dropdown-menu.dropdown-menu-full-width
diff --git a/app/views/snippets/new.html.haml b/app/views/snippets/new.html.haml
index acc0ce0fff3..2669754cc3a 100644
--- a/app/views/snippets/new.html.haml
+++ b/app/views/snippets/new.html.haml
@@ -6,5 +6,5 @@
.page-title-holder.d-flex.align-items-center
%h1.page-title= _('New Snippet')
-.prepend-top-default
+.gl-mt-3
= render "shared/snippets/form", url: snippets_path(@snippet)
diff --git a/app/views/snippets/notes/_actions.html.haml b/app/views/snippets/notes/_actions.html.haml
index 28fbeaa25f0..310d9946663 100644
--- a/app/views/snippets/notes/_actions.html.haml
+++ b/app/views/snippets/notes/_actions.html.haml
@@ -8,7 +8,7 @@
- if note_editable
.note-actions-item
- = button_tag title: _('Edit comment'), class: 'note-action-button js-note-edit has-tooltip btn btn-transparent', data: { container: 'body' } do
+ = button_tag title: _('Edit comment'), class: 'note-action-button js-note-edit has-tooltip btn btn-transparent', data: { container: 'body', qa_selector: 'edit_comment_button' } do
%span.link-highlight
= custom_icon('icon_pencil')
diff --git a/app/views/users/_overview.html.haml b/app/views/users/_overview.html.haml
index 7bd2d30a35c..5b6d1169b4b 100644
--- a/app/views/users/_overview.html.haml
+++ b/app/views/users/_overview.html.haml
@@ -1,6 +1,6 @@
.row
.col-12
- .calendar-block.prepend-top-default.append-bottom-default
+ .calendar-block.gl-mt-3.gl-mb-3
.user-calendar.d-none.d-sm-block{ data: { calendar_path: user_calendar_path(@user, :json), calendar_activities_path: user_calendar_activities_path, utc_offset: Time.zone.utc_offset } }
%h4.center.light
.spinner.spinner-md
@@ -9,7 +9,7 @@
.col-md-12.col-lg-6
- if can?(current_user, :read_cross_project)
.activities-block
- .prepend-top-16
+ .gl-mt-5
.d-flex.align-items-center.border-bottom
%h4.flex-grow
= s_('UserProfile|Activity')
@@ -20,7 +20,7 @@
.col-md-12.col-lg-6
.projects-block
- .prepend-top-16
+ .gl-mt-5
.d-flex.align-items-center.border-bottom
%h4.flex-grow
= s_('UserProfile|Personal projects')
diff --git a/app/views/users/show.html.haml b/app/views/users/show.html.haml
index dc151a61ee1..d2f7ff91f0d 100644
--- a/app/views/users/show.html.haml
+++ b/app/views/users/show.html.haml
@@ -85,12 +85,13 @@
- if @user.bio.present?
.cover-desc.cgray
%p.profile-user-bio
- = @user.bio
+ = markdown(@user.bio_html)
+
- unless profile_tabs.empty?
.scrolling-tabs-container
- .fade-left= icon('angle-left')
- .fade-right= icon('angle-right')
+ .fade-left= sprite_icon('chevron-lg-left', size: 12)
+ .fade-right= sprite_icon('chevron-lg-right', size: 12)
%ul.nav-links.user-profile-nav.scrolling-tabs.nav.nav-tabs
- if profile_tab?(:overview)
%li.js-overview-tab
diff --git a/app/workers/all_queues.yml b/app/workers/all_queues.yml
index 3baa2166812..5148772c881 100644
--- a/app/workers/all_queues.yml
+++ b/app/workers/all_queues.yml
@@ -11,6 +11,14 @@
:weight: 1
:idempotent: true
:tags: []
+- :name: authorized_project_update:authorized_project_update_project_group_link_create
+ :feature_category: :authentication_and_authorization
+ :has_external_dependencies:
+ :urgency: :low
+ :resource_boundary: :unknown
+ :weight: 1
+ :idempotent: true
+ :tags: []
- :name: authorized_project_update:authorized_project_update_user_refresh_over_user_range
:feature_category: :authentication_and_authorization
:has_external_dependencies:
@@ -227,6 +235,14 @@
:weight: 1
:idempotent:
:tags: []
+- :name: cronjob:partition_creation
+ :feature_category: :database
+ :has_external_dependencies:
+ :urgency: :low
+ :resource_boundary: :unknown
+ :weight: 1
+ :idempotent: true
+ :tags: []
- :name: cronjob:personal_access_tokens_expiring
:feature_category: :authentication_and_authorization
:has_external_dependencies:
@@ -331,15 +347,15 @@
:weight: 1
:idempotent:
:tags: []
-- :name: cronjob:stuck_import_jobs
- :feature_category: :importers
+- :name: cronjob:stuck_merge_jobs
+ :feature_category: :source_code_management
:has_external_dependencies:
:urgency: :low
- :resource_boundary: :cpu
+ :resource_boundary: :unknown
:weight: 1
:idempotent:
:tags: []
-- :name: cronjob:stuck_merge_jobs
+- :name: cronjob:trending_projects
:feature_category: :source_code_management
:has_external_dependencies:
:urgency: :low
@@ -347,13 +363,13 @@
:weight: 1
:idempotent:
:tags: []
-- :name: cronjob:trending_projects
- :feature_category: :source_code_management
+- :name: cronjob:update_container_registry_info
+ :feature_category: :container_registry
:has_external_dependencies:
:urgency: :low
:resource_boundary: :unknown
:weight: 1
- :idempotent:
+ :idempotent: true
:tags: []
- :name: cronjob:users_create_statistics
:feature_category: :users
@@ -675,6 +691,14 @@
:weight: 2
:idempotent: true
:tags: []
+- :name: incident_management:incident_management_pager_duty_process_incident
+ :feature_category: :incident_management
+ :has_external_dependencies:
+ :urgency: :low
+ :resource_boundary: :unknown
+ :weight: 2
+ :idempotent:
+ :tags: []
- :name: incident_management:incident_management_process_alert
:feature_category: :incident_management
:has_external_dependencies:
@@ -771,14 +795,6 @@
:weight: 2
:idempotent:
:tags: []
-- :name: notifications:new_release
- :feature_category: :release_orchestration
- :has_external_dependencies:
- :urgency: :low
- :resource_boundary: :unknown
- :weight: 2
- :idempotent:
- :tags: []
- :name: object_pool:object_pool_create
:feature_category: :gitaly
:has_external_dependencies:
@@ -827,6 +843,14 @@
:weight: 1
:idempotent:
:tags: []
+- :name: package_repositories:packages_nuget_extraction
+ :feature_category: :package_registry
+ :has_external_dependencies:
+ :urgency: :low
+ :resource_boundary: :unknown
+ :weight: 1
+ :idempotent:
+ :tags: []
- :name: pipeline_background:archive_trace
:feature_category: :continuous_integration
:has_external_dependencies:
@@ -859,6 +883,22 @@
:weight: 1
:idempotent: true
:tags: []
+- :name: pipeline_background:ci_pipeline_success_unlock_artifacts
+ :feature_category: :continuous_integration
+ :has_external_dependencies:
+ :urgency: :low
+ :resource_boundary: :unknown
+ :weight: 1
+ :idempotent: true
+ :tags: []
+- :name: pipeline_background:ci_ref_delete_unlock_artifacts
+ :feature_category: :continuous_integration
+ :has_external_dependencies:
+ :urgency: :low
+ :resource_boundary: :unknown
+ :weight: 1
+ :idempotent: true
+ :tags: []
- :name: pipeline_cache:expire_job_cache
:feature_category: :continuous_integration
:has_external_dependencies:
@@ -970,7 +1010,8 @@
:resource_boundary: :cpu
:weight: 5
:idempotent:
- :tags: []
+ :tags:
+ - :requires_disk_io
- :name: pipeline_processing:build_queue
:feature_category: :continuous_integration
:has_external_dependencies:
@@ -1025,7 +1066,7 @@
:urgency: :high
:resource_boundary: :unknown
:weight: 5
- :idempotent:
+ :idempotent: true
:tags: []
- :name: pipeline_processing:stage_update
:feature_category: :continuous_integration
@@ -1107,6 +1148,14 @@
:weight: 1
:idempotent:
:tags: []
+- :name: unassign_issuables:members_destroyer_unassign_issuables
+ :feature_category: :authentication_and_authorization
+ :has_external_dependencies:
+ :urgency: :low
+ :resource_boundary: :unknown
+ :weight: 1
+ :idempotent: true
+ :tags: []
- :name: update_namespace_statistics:namespaces_root_statistics
:feature_category: :source_code_management
:has_external_dependencies:
@@ -1526,10 +1575,10 @@
- :name: project_update_repository_storage
:feature_category: :gitaly
:has_external_dependencies:
- :urgency: :low
+ :urgency: :throttled
:resource_boundary: :unknown
:weight: 1
- :idempotent:
+ :idempotent: true
:tags: []
- :name: prometheus_create_default_alerts
:feature_category: :incident_management
@@ -1635,6 +1684,14 @@
:weight: 2
:idempotent:
:tags: []
+- :name: service_desk_email_receiver
+ :feature_category: :issue_tracking
+ :has_external_dependencies:
+ :urgency: :low
+ :resource_boundary: :unknown
+ :weight: 1
+ :idempotent:
+ :tags: []
- :name: system_hook_push
:feature_category: :source_code_management
:has_external_dependencies:
diff --git a/app/workers/authorized_project_update/project_group_link_create_worker.rb b/app/workers/authorized_project_update/project_group_link_create_worker.rb
new file mode 100644
index 00000000000..5fb59efaacb
--- /dev/null
+++ b/app/workers/authorized_project_update/project_group_link_create_worker.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+module AuthorizedProjectUpdate
+ class ProjectGroupLinkCreateWorker
+ include ApplicationWorker
+
+ feature_category :authentication_and_authorization
+ urgency :low
+ queue_namespace :authorized_project_update
+
+ idempotent!
+
+ def perform(project_id, group_id)
+ project = Project.find(project_id)
+ group = Group.find(group_id)
+
+ AuthorizedProjectUpdate::ProjectGroupLinkCreateService.new(project, group)
+ .execute
+ end
+ end
+end
diff --git a/app/workers/build_finished_worker.rb b/app/workers/build_finished_worker.rb
index d38780dd08d..d0f7d65aed6 100644
--- a/app/workers/build_finished_worker.rb
+++ b/app/workers/build_finished_worker.rb
@@ -7,6 +7,7 @@ class BuildFinishedWorker # rubocop:disable Scalability/IdempotentWorker
queue_namespace :pipeline_processing
urgency :high
worker_resource_boundary :cpu
+ tags :requires_disk_io
# rubocop: disable CodeReuse/ActiveRecord
def perform(build_id)
diff --git a/app/workers/ci/pipeline_success_unlock_artifacts_worker.rb b/app/workers/ci/pipeline_success_unlock_artifacts_worker.rb
new file mode 100644
index 00000000000..bc31876aa1d
--- /dev/null
+++ b/app/workers/ci/pipeline_success_unlock_artifacts_worker.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+
+module Ci
+ class PipelineSuccessUnlockArtifactsWorker
+ include ApplicationWorker
+ include PipelineBackgroundQueue
+
+ idempotent!
+
+ def perform(pipeline_id)
+ ::Ci::Pipeline.find_by_id(pipeline_id).try do |pipeline|
+ break unless pipeline.has_archive_artifacts?
+
+ ::Ci::UnlockArtifactsService
+ .new(pipeline.project, pipeline.user)
+ .execute(pipeline.ci_ref, pipeline)
+ end
+ end
+ end
+end
diff --git a/app/workers/ci/ref_delete_unlock_artifacts_worker.rb b/app/workers/ci/ref_delete_unlock_artifacts_worker.rb
new file mode 100644
index 00000000000..3b4a6fcf630
--- /dev/null
+++ b/app/workers/ci/ref_delete_unlock_artifacts_worker.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+module Ci
+ class RefDeleteUnlockArtifactsWorker
+ include ApplicationWorker
+ include PipelineBackgroundQueue
+
+ idempotent!
+
+ def perform(project_id, user_id, ref_path)
+ ::Project.find_by_id(project_id).try do |project|
+ ::User.find_by_id(user_id).try do |user|
+ ::Ci::Ref.find_by_ref_path(ref_path).try do |ci_ref|
+ ::Ci::UnlockArtifactsService
+ .new(project, user)
+ .execute(ci_ref)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/app/workers/concerns/project_export_options.rb b/app/workers/concerns/project_export_options.rb
deleted file mode 100644
index e9318c1ba43..00000000000
--- a/app/workers/concerns/project_export_options.rb
+++ /dev/null
@@ -1,25 +0,0 @@
-# frozen_string_literal: true
-
-module ProjectExportOptions
- extend ActiveSupport::Concern
-
- EXPORT_RETRY_COUNT = 3
-
- included do
- sidekiq_options retry: EXPORT_RETRY_COUNT, status_expiration: StuckExportJobsWorker::EXPORT_JOBS_EXPIRATION
-
- # We mark the project export as failed once we have exhausted all retries
- sidekiq_retries_exhausted do |job|
- project = Project.find(job['args'][1])
- # rubocop: disable CodeReuse/ActiveRecord
- job = project.export_jobs.find_by(jid: job["jid"])
- # rubocop: enable CodeReuse/ActiveRecord
-
- if job&.fail_op
- Sidekiq.logger.info "Job #{job['jid']} for project #{project.id} has been set to failed state"
- else
- Sidekiq.logger.error "Failed to set Job #{job['jid']} for project #{project.id} to failed state"
- end
- end
- end
-end
diff --git a/app/workers/concerns/reenqueuer.rb b/app/workers/concerns/reenqueuer.rb
index 5cc13e490d8..bf6f6546c03 100644
--- a/app/workers/concerns/reenqueuer.rb
+++ b/app/workers/concerns/reenqueuer.rb
@@ -60,8 +60,6 @@ module Reenqueuer
5.seconds
end
- # We intend to get rid of sleep:
- # https://gitlab.com/gitlab-org/gitlab/issues/121697
module ReenqueuerSleeper
# The block will run, and then sleep until the minimum duration. Returns the
# block's return value.
@@ -73,7 +71,7 @@ module Reenqueuer
# end
#
def ensure_minimum_duration(minimum_duration)
- start_time = Time.now
+ start_time = Time.current
result = yield
@@ -95,7 +93,7 @@ module Reenqueuer
end
def elapsed_time(start_time)
- Time.now - start_time
+ Time.current - start_time
end
end
end
diff --git a/app/workers/concerns/worker_attributes.rb b/app/workers/concerns/worker_attributes.rb
index b19217b15de..bb6192166b4 100644
--- a/app/workers/concerns/worker_attributes.rb
+++ b/app/workers/concerns/worker_attributes.rb
@@ -2,6 +2,7 @@
module WorkerAttributes
extend ActiveSupport::Concern
+ include Gitlab::ClassAttributes
# Resource boundaries that workers can declare through the
# `resource_boundary` attribute
@@ -30,24 +31,24 @@ module WorkerAttributes
}.stringify_keys.freeze
class_methods do
- def feature_category(value)
+ def feature_category(value, *extras)
raise "Invalid category. Use `feature_category_not_owned!` to mark a worker as not owned" if value == :not_owned
- worker_attributes[:feature_category] = value
+ class_attributes[:feature_category] = value
end
# Special case: mark this work as not associated with a feature category
# this should be used for cross-cutting concerns, such as mailer workers.
def feature_category_not_owned!
- worker_attributes[:feature_category] = :not_owned
+ class_attributes[:feature_category] = :not_owned
end
def get_feature_category
- get_worker_attribute(:feature_category)
+ get_class_attribute(:feature_category)
end
def feature_category_not_owned?
- get_worker_attribute(:feature_category) == :not_owned
+ get_feature_category == :not_owned
end
# This should be set to :high for jobs that need to be run
@@ -61,97 +62,76 @@ module WorkerAttributes
def urgency(urgency)
raise "Invalid urgency: #{urgency}" unless VALID_URGENCIES.include?(urgency)
- worker_attributes[:urgency] = urgency
+ class_attributes[:urgency] = urgency
end
def get_urgency
- worker_attributes[:urgency] || :low
+ class_attributes[:urgency] || :low
end
# Set this attribute on a job when it will call to services outside of the
# application, such as 3rd party applications, other k8s clusters etc See
- # doc/development/sidekiq_style_guide.md#Jobs-with-External-Dependencies for
+ # doc/development/sidekiq_style_guide.md#jobs-with-external-dependencies for
# details
def worker_has_external_dependencies!
- worker_attributes[:external_dependencies] = true
+ class_attributes[:external_dependencies] = true
end
# Returns a truthy value if the worker has external dependencies.
- # See doc/development/sidekiq_style_guide.md#Jobs-with-External-Dependencies
+ # See doc/development/sidekiq_style_guide.md#jobs-with-external-dependencies
# for details
def worker_has_external_dependencies?
- worker_attributes[:external_dependencies]
+ class_attributes[:external_dependencies]
end
def worker_resource_boundary(boundary)
raise "Invalid boundary" unless VALID_RESOURCE_BOUNDARIES.include? boundary
- worker_attributes[:resource_boundary] = boundary
+ class_attributes[:resource_boundary] = boundary
end
def get_worker_resource_boundary
- worker_attributes[:resource_boundary] || :unknown
+ class_attributes[:resource_boundary] || :unknown
end
def idempotent!
- worker_attributes[:idempotent] = true
+ class_attributes[:idempotent] = true
end
def idempotent?
- worker_attributes[:idempotent]
+ class_attributes[:idempotent]
end
def weight(value)
- worker_attributes[:weight] = value
+ class_attributes[:weight] = value
end
def get_weight
- worker_attributes[:weight] ||
+ class_attributes[:weight] ||
NAMESPACE_WEIGHTS[queue_namespace] ||
1
end
def tags(*values)
- worker_attributes[:tags] = values
+ class_attributes[:tags] = values
end
def get_tags
- Array(worker_attributes[:tags])
+ Array(class_attributes[:tags])
end
def deduplicate(strategy, options = {})
- worker_attributes[:deduplication_strategy] = strategy
- worker_attributes[:deduplication_options] = options
+ class_attributes[:deduplication_strategy] = strategy
+ class_attributes[:deduplication_options] = options
end
def get_deduplicate_strategy
- worker_attributes[:deduplication_strategy] ||
+ class_attributes[:deduplication_strategy] ||
Gitlab::SidekiqMiddleware::DuplicateJobs::DuplicateJob::DEFAULT_STRATEGY
end
def get_deduplication_options
- worker_attributes[:deduplication_options] || {}
- end
-
- protected
-
- # Returns a worker attribute declared on this class or its parent class.
- # This approach allows declared attributes to be inherited by
- # child classes.
- def get_worker_attribute(name)
- worker_attributes[name] || superclass_worker_attributes(name)
- end
-
- private
-
- def worker_attributes
- @attributes ||= {}
- end
-
- def superclass_worker_attributes(name)
- return unless superclass.include? WorkerAttributes
-
- superclass.get_worker_attribute(name)
+ class_attributes[:deduplication_options] || {}
end
end
end
diff --git a/app/workers/delete_merged_branches_worker.rb b/app/workers/delete_merged_branches_worker.rb
index ab3d42e5384..8d7026e2d1e 100644
--- a/app/workers/delete_merged_branches_worker.rb
+++ b/app/workers/delete_merged_branches_worker.rb
@@ -17,7 +17,6 @@ class DeleteMergedBranchesWorker # rubocop:disable Scalability/IdempotentWorker
begin
::Branches::DeleteMergedService.new(project, user).execute
rescue Gitlab::Access::AccessDeniedError
- return
end
end
end
diff --git a/app/workers/gitlab/jira_import/import_issue_worker.rb b/app/workers/gitlab/jira_import/import_issue_worker.rb
index 7709d2ec31b..d1ceda4fd6a 100644
--- a/app/workers/gitlab/jira_import/import_issue_worker.rb
+++ b/app/workers/gitlab/jira_import/import_issue_worker.rb
@@ -62,7 +62,7 @@ module Gitlab
end
def build_label_attrs(issue_id, label_id)
- time = Time.now
+ time = Time.current
{
label_id: label_id,
target_id: issue_id,
diff --git a/app/workers/group_export_worker.rb b/app/workers/group_export_worker.rb
index 6fd977e43d8..e22b691d35e 100644
--- a/app/workers/group_export_worker.rb
+++ b/app/workers/group_export_worker.rb
@@ -6,6 +6,7 @@ class GroupExportWorker # rubocop:disable Scalability/IdempotentWorker
feature_category :importers
loggable_arguments 2
+ sidekiq_options retry: false
def perform(current_user_id, group_id, params = {})
current_user = User.find(current_user_id)
diff --git a/app/workers/incident_management/pager_duty/process_incident_worker.rb b/app/workers/incident_management/pager_duty/process_incident_worker.rb
new file mode 100644
index 00000000000..3f378b012a1
--- /dev/null
+++ b/app/workers/incident_management/pager_duty/process_incident_worker.rb
@@ -0,0 +1,42 @@
+# frozen_string_literal: true
+
+module IncidentManagement
+ module PagerDuty
+ class ProcessIncidentWorker # rubocop:disable Scalability/IdempotentWorker
+ include ApplicationWorker
+
+ queue_namespace :incident_management
+ feature_category :incident_management
+
+ def perform(project_id, incident_payload)
+ return unless project_id
+
+ project = find_project(project_id)
+ return unless project
+
+ result = create_issue(project, incident_payload)
+
+ log_error(result) if result.error?
+ end
+
+ private
+
+ def find_project(project_id)
+ Project.find_by_id(project_id)
+ end
+
+ def create_issue(project, incident_payload)
+ ::IncidentManagement::PagerDuty::CreateIncidentIssueService
+ .new(project, incident_payload)
+ .execute
+ end
+
+ def log_error(result)
+ Gitlab::AppLogger.warn(
+ message: 'Cannot create issue for PagerDuty incident',
+ issue_errors: result.message
+ )
+ end
+ end
+ end
+end
diff --git a/app/workers/incident_management/process_alert_worker.rb b/app/workers/incident_management/process_alert_worker.rb
index 0af34fa35d5..bc23dbda693 100644
--- a/app/workers/incident_management/process_alert_worker.rb
+++ b/app/workers/incident_management/process_alert_worker.rb
@@ -7,39 +7,45 @@ module IncidentManagement
queue_namespace :incident_management
feature_category :incident_management
- def perform(project_id, alert_payload, am_alert_id = nil)
- project = find_project(project_id)
- return unless project
+ # `project_id` and `alert_payload` are deprecated and can be removed
+ # starting from 14.0 release
+ # https://gitlab.com/gitlab-org/gitlab/-/issues/224500
+ def perform(_project_id = nil, _alert_payload = nil, alert_id = nil)
+ return unless alert_id
- new_issue = create_issue(project, alert_payload)
- return unless am_alert_id && new_issue&.persisted?
+ alert = find_alert(alert_id)
+ return unless alert
+
+ new_issue = create_issue_for(alert)
+ return unless new_issue&.persisted?
- link_issue_with_alert(am_alert_id, new_issue.id)
+ link_issue_with_alert(alert, new_issue.id)
end
private
- def find_project(project_id)
- Project.find_by_id(project_id)
+ def find_alert(alert_id)
+ AlertManagement::Alert.find_by_id(alert_id)
+ end
+
+ def parsed_payload(alert)
+ Gitlab::Alerting::NotificationPayloadParser.call(alert.payload.to_h, alert.project)
end
- def create_issue(project, alert_payload)
+ def create_issue_for(alert)
IncidentManagement::CreateIssueService
- .new(project, alert_payload)
+ .new(alert.project, parsed_payload(alert))
.execute
.dig(:issue)
end
- def link_issue_with_alert(alert_id, issue_id)
- alert = AlertManagement::Alert.find_by_id(alert_id)
- return unless alert
-
+ def link_issue_with_alert(alert, issue_id)
return if alert.update(issue_id: issue_id)
Gitlab::AppLogger.warn(
message: 'Cannot link an Issue with Alert',
issue_id: issue_id,
- alert_id: alert_id,
+ alert_id: alert.id,
alert_errors: alert.errors.messages
)
end
diff --git a/app/workers/incident_management/process_prometheus_alert_worker.rb b/app/workers/incident_management/process_prometheus_alert_worker.rb
index e405bc2c2d2..4b778f6a621 100644
--- a/app/workers/incident_management/process_prometheus_alert_worker.rb
+++ b/app/workers/incident_management/process_prometheus_alert_worker.rb
@@ -9,68 +9,13 @@ module IncidentManagement
worker_resource_boundary :cpu
def perform(project_id, alert_hash)
- project = find_project(project_id)
- return unless project
-
- parsed_alert = Gitlab::Alerting::Alert.new(project: project, payload: alert_hash)
- event = find_prometheus_alert_event(parsed_alert)
-
- if event&.resolved?
- issue = event.related_issues.order_created_at_desc.detect(&:opened?)
-
- close_issue(project, issue)
- else
- issue = create_issue(project, alert_hash)
-
- relate_issue_to_event(event, issue)
- end
- end
-
- private
-
- def find_project(project_id)
- Project.find_by_id(project_id)
- end
-
- def find_prometheus_alert_event(alert)
- if alert.gitlab_managed?
- find_gitlab_managed_event(alert)
- else
- find_self_managed_event(alert)
- end
- end
-
- def find_gitlab_managed_event(alert)
- PrometheusAlertEvent.find_by_payload_key(alert.gitlab_fingerprint)
- end
-
- def find_self_managed_event(alert)
- SelfManagedPrometheusAlertEvent.find_by_payload_key(alert.gitlab_fingerprint)
- end
-
- def create_issue(project, alert)
- IncidentManagement::CreateIssueService
- .new(project, alert)
- .execute
- .dig(:issue)
- end
-
- def close_issue(project, issue)
- return if issue.blank? || issue.closed?
-
- processed_issue = Issues::CloseService
- .new(project, User.alert_bot)
- .execute(issue, system_note: false)
-
- SystemNoteService.auto_resolve_prometheus_alert(issue, project, User.alert_bot) if processed_issue.reset.closed?
- end
-
- def relate_issue_to_event(event, issue)
- return unless event && issue
-
- if event.related_issues.exclude?(issue)
- event.related_issues << issue
- end
+ # no-op
+ #
+ # This worker is not scheduled anymore since
+ # https://gitlab.com/gitlab-org/gitlab/-/merge_requests/35943
+ # and will be removed completely via
+ # https://gitlab.com/gitlab-org/gitlab/-/issues/227146
+ # in 14.0.
end
end
end
diff --git a/app/workers/members_destroyer/unassign_issuables_worker.rb b/app/workers/members_destroyer/unassign_issuables_worker.rb
new file mode 100644
index 00000000000..2c17120bf48
--- /dev/null
+++ b/app/workers/members_destroyer/unassign_issuables_worker.rb
@@ -0,0 +1,32 @@
+# frozen_string_literal: true
+
+module MembersDestroyer
+ class UnassignIssuablesWorker
+ include ApplicationWorker
+
+ ENTITY_TYPES = %w(Group Project).freeze
+
+ queue_namespace :unassign_issuables
+ feature_category :authentication_and_authorization
+
+ idempotent!
+
+ def perform(user_id, entity_id, entity_type)
+ unless ENTITY_TYPES.include?(entity_type)
+ logger.error(
+ message: "#{entity_type} is not a supported entity.",
+ entity_type: entity_type,
+ entity_id: entity_id,
+ user_id: user_id
+ )
+
+ return
+ end
+
+ user = User.find(user_id)
+ entity = entity_type.constantize.find(entity_id)
+
+ ::Members::UnassignIssuablesService.new(user, entity).execute
+ end
+ end
+end
diff --git a/app/workers/new_release_worker.rb b/app/workers/new_release_worker.rb
deleted file mode 100644
index fa4703d10f2..00000000000
--- a/app/workers/new_release_worker.rb
+++ /dev/null
@@ -1,18 +0,0 @@
-# frozen_string_literal: true
-
-# TODO: Worker can be removed in 13.2:
-# https://gitlab.com/gitlab-org/gitlab/-/issues/218231
-class NewReleaseWorker # rubocop:disable Scalability/IdempotentWorker
- include ApplicationWorker
-
- queue_namespace :notifications
- feature_category :release_orchestration
- weight 2
-
- def perform(release_id)
- release = Release.preloaded.find_by_id(release_id)
- return unless release
-
- NotificationService.new.send_new_release_notifications(release)
- end
-end
diff --git a/app/workers/packages/nuget/extraction_worker.rb b/app/workers/packages/nuget/extraction_worker.rb
new file mode 100644
index 00000000000..820304a9f3b
--- /dev/null
+++ b/app/workers/packages/nuget/extraction_worker.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+module Packages
+ module Nuget
+ class ExtractionWorker # rubocop:disable Scalability/IdempotentWorker
+ include ApplicationWorker
+
+ queue_namespace :package_repositories
+ feature_category :package_registry
+
+ def perform(package_file_id)
+ package_file = ::Packages::PackageFile.find_by_id(package_file_id)
+
+ return unless package_file
+
+ ::Packages::Nuget::UpdatePackageFromMetadataService.new(package_file).execute
+
+ rescue ::Packages::Nuget::MetadataExtractionService::ExtractionError,
+ ::Packages::Nuget::UpdatePackageFromMetadataService::InvalidMetadataError => e
+ Gitlab::ErrorTracking.log_exception(e, project_id: package_file.project_id)
+ package_file.package.destroy!
+ end
+ end
+ end
+end
diff --git a/app/workers/partition_creation_worker.rb b/app/workers/partition_creation_worker.rb
new file mode 100644
index 00000000000..9101623d93a
--- /dev/null
+++ b/app/workers/partition_creation_worker.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+class PartitionCreationWorker
+ include ApplicationWorker
+ include CronjobQueue # rubocop:disable Scalability/CronWorkerContext
+
+ feature_category :database
+ idempotent!
+
+ def perform
+ Gitlab::AppLogger.info("Checking state of dynamic postgres partitions")
+
+ Gitlab::Database::Partitioning::PartitionCreator.new.create_partitions
+ end
+end
diff --git a/app/workers/pipeline_update_worker.rb b/app/workers/pipeline_update_worker.rb
index 7f667057af6..267caa5bedd 100644
--- a/app/workers/pipeline_update_worker.rb
+++ b/app/workers/pipeline_update_worker.rb
@@ -1,12 +1,14 @@
# frozen_string_literal: true
-class PipelineUpdateWorker # rubocop:disable Scalability/IdempotentWorker
+class PipelineUpdateWorker
include ApplicationWorker
include PipelineQueue
queue_namespace :pipeline_processing
urgency :high
+ idempotent!
+
def perform(pipeline_id)
Ci::Pipeline.find_by_id(pipeline_id)&.update_legacy_status
end
diff --git a/app/workers/post_receive.rb b/app/workers/post_receive.rb
index 62d76294bc0..8f844bd0b47 100644
--- a/app/workers/post_receive.rb
+++ b/app/workers/post_receive.rb
@@ -79,7 +79,7 @@ class PostReceive # rubocop:disable Scalability/IdempotentWorker
return false unless user
expire_caches(post_received, snippet.repository)
- snippet.repository.expire_statistics_caches
+ Snippets::UpdateStatisticsService.new(snippet).execute
end
# Expire the repository status, branch, and tag cache once per push.
diff --git a/app/workers/process_commit_worker.rb b/app/workers/process_commit_worker.rb
index 5756ebb8358..3c7af641f16 100644
--- a/app/workers/process_commit_worker.rb
+++ b/app/workers/process_commit_worker.rb
@@ -80,7 +80,7 @@ class ProcessCommitWorker
# manually parse these values.
hash.each do |key, value|
if key.to_s.end_with?(date_suffix) && value.is_a?(String)
- hash[key] = Time.parse(value)
+ hash[key] = Time.zone.parse(value)
end
end
diff --git a/app/workers/project_export_worker.rb b/app/workers/project_export_worker.rb
index d29348e85bc..6c8640138a1 100644
--- a/app/workers/project_export_worker.rb
+++ b/app/workers/project_export_worker.rb
@@ -3,12 +3,13 @@
class ProjectExportWorker # rubocop:disable Scalability/IdempotentWorker
include ApplicationWorker
include ExceptionBacktrace
- include ProjectExportOptions
feature_category :importers
worker_resource_boundary :memory
urgency :throttled
loggable_arguments 2, 3
+ sidekiq_options retry: false
+ sidekiq_options status_expiration: StuckExportJobsWorker::EXPORT_JOBS_EXPIRATION
def perform(current_user_id, project_id, after_export_strategy = {}, params = {})
current_user = User.find(current_user_id)
diff --git a/app/workers/project_update_repository_storage_worker.rb b/app/workers/project_update_repository_storage_worker.rb
index 5c1a8062f12..7c0b1ae07fa 100644
--- a/app/workers/project_update_repository_storage_worker.rb
+++ b/app/workers/project_update_repository_storage_worker.rb
@@ -1,9 +1,11 @@
# frozen_string_literal: true
-class ProjectUpdateRepositoryStorageWorker # rubocop:disable Scalability/IdempotentWorker
+class ProjectUpdateRepositoryStorageWorker
include ApplicationWorker
+ idempotent!
feature_category :gitaly
+ urgency :throttled
def perform(project_id, new_repository_storage_key, repository_storage_move_id = nil)
repository_storage_move =
diff --git a/app/workers/repository_check/batch_worker.rb b/app/workers/repository_check/batch_worker.rb
index 1e2cb912598..d47f738ccb0 100644
--- a/app/workers/repository_check/batch_worker.rb
+++ b/app/workers/repository_check/batch_worker.rb
@@ -34,7 +34,7 @@ module RepositoryCheck
end
def perform_repository_checks
- start = Time.now
+ start = Time.current
# This loop will break after a little more than one hour ('a little
# more' because `git fsck` may take a few minutes), or if it runs out of
@@ -42,7 +42,7 @@ module RepositoryCheck
# RepositoryCheckWorker each hour so that as long as there are repositories to
# check, only one (or two) will be checked at a time.
project_ids.each do |project_id|
- break if Time.now - start >= RUN_TIME
+ break if Time.current - start >= RUN_TIME
next unless try_obtain_lease_for_project(project_id)
diff --git a/app/workers/repository_check/single_repository_worker.rb b/app/workers/repository_check/single_repository_worker.rb
index edff7fc31df..d757b87c23a 100644
--- a/app/workers/repository_check/single_repository_worker.rb
+++ b/app/workers/repository_check/single_repository_worker.rb
@@ -17,7 +17,7 @@ module RepositoryCheck
def update_repository_check_status(project, healthy)
project.update_columns(
last_repository_check_failed: !healthy,
- last_repository_check_at: Time.now
+ last_repository_check_at: Time.current
)
end
diff --git a/app/workers/repository_import_worker.rb b/app/workers/repository_import_worker.rb
index 30570a2227e..54052bda675 100644
--- a/app/workers/repository_import_worker.rb
+++ b/app/workers/repository_import_worker.rb
@@ -4,10 +4,11 @@ class RepositoryImportWorker # rubocop:disable Scalability/IdempotentWorker
include ApplicationWorker
include ExceptionBacktrace
include ProjectStartImport
- include ProjectImportOptions
feature_category :importers
worker_has_external_dependencies!
+ sidekiq_options retry: false
+ sidekiq_options status_expiration: Gitlab::Import::StuckImportJob::IMPORT_JOBS_EXPIRATION
# technical debt: https://gitlab.com/gitlab-org/gitlab/issues/33991
sidekiq_options memory_killer_memory_growth_kb: ENV.fetch('MEMORY_KILLER_REPOSITORY_IMPORT_WORKER_MEMORY_GROWTH_KB', 50).to_i
diff --git a/app/workers/service_desk_email_receiver_worker.rb b/app/workers/service_desk_email_receiver_worker.rb
new file mode 100644
index 00000000000..8649034445c
--- /dev/null
+++ b/app/workers/service_desk_email_receiver_worker.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+class ServiceDeskEmailReceiverWorker < EmailReceiverWorker # rubocop:disable Scalability/IdempotentWorker
+ include ApplicationWorker
+
+ def perform(raw)
+ return unless ::Gitlab::ServiceDeskEmail.enabled?
+
+ begin
+ Gitlab::Email::ServiceDeskReceiver.new(raw).execute
+ rescue => e
+ handle_failure(raw, e)
+ end
+ end
+end
diff --git a/app/workers/stuck_import_jobs_worker.rb b/app/workers/stuck_import_jobs_worker.rb
deleted file mode 100644
index ce8d5bf0219..00000000000
--- a/app/workers/stuck_import_jobs_worker.rb
+++ /dev/null
@@ -1,19 +0,0 @@
-# frozen_string_literal: true
-
-class StuckImportJobsWorker # rubocop:disable Scalability/IdempotentWorker
- include Gitlab::Import::StuckImportJob
-
- private
-
- def track_metrics(with_jid_count, without_jid_count)
- Gitlab::Metrics.add_event(
- :stuck_import_jobs,
- projects_without_jid_count: without_jid_count,
- projects_with_jid_count: with_jid_count
- )
- end
-
- def enqueued_import_states
- ProjectImportState.with_status([:scheduled, :started])
- end
-end
diff --git a/app/workers/update_container_registry_info_worker.rb b/app/workers/update_container_registry_info_worker.rb
new file mode 100644
index 00000000000..14a816f25ef
--- /dev/null
+++ b/app/workers/update_container_registry_info_worker.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+class UpdateContainerRegistryInfoWorker
+ include ApplicationWorker
+ include CronjobQueue # rubocop:disable Scalability/CronWorkerContext
+
+ feature_category :container_registry
+ urgency :low
+
+ idempotent!
+
+ def perform
+ UpdateContainerRegistryInfoService.new.execute
+ end
+end
diff --git a/bin/changelog b/bin/changelog
index 45b6295e331..bdf159a0a22 100755
--- a/bin/changelog
+++ b/bin/changelog
@@ -8,16 +8,6 @@
require 'optparse'
require 'yaml'
-Options = Struct.new(
- :amend,
- :author,
- :dry_run,
- :force,
- :merge_request,
- :title,
- :type,
- :ee
-)
INVALID_TYPE = -1
module ChangelogHelpers
@@ -40,6 +30,17 @@ end
class ChangelogOptionParser
extend ChangelogHelpers
+ Options = Struct.new(
+ :amend,
+ :author,
+ :dry_run,
+ :force,
+ :merge_request,
+ :title,
+ :type,
+ :ee
+ )
+
Type = Struct.new(:name, :description)
TYPES = [
Type.new('added', 'New feature'),
diff --git a/bin/feature-flag b/bin/feature-flag
new file mode 100755
index 00000000000..46d93a11ebd
--- /dev/null
+++ b/bin/feature-flag
@@ -0,0 +1,291 @@
+#!/usr/bin/env ruby
+#
+# Generate a feature flag entry file in the correct location.
+#
+# Automatically stages the file and amends the previous commit if the `--amend`
+# argument is used.
+
+require 'optparse'
+require 'yaml'
+require 'fileutils'
+require 'cgi'
+
+require_relative '../lib/feature/shared' unless defined?(Feature::Shared)
+
+module FeatureFlagHelpers
+ Abort = Class.new(StandardError)
+ Done = Class.new(StandardError)
+
+ def capture_stdout(cmd)
+ output = IO.popen(cmd, &:read)
+ fail_with "command failed: #{cmd.join(' ')}" unless $?.success?
+ output
+ end
+
+ def fail_with(message)
+ raise Abort, "\e[31merror\e[0m #{message}"
+ end
+end
+
+class FeatureFlagOptionParser
+ extend FeatureFlagHelpers
+ extend ::Feature::Shared
+
+ Options = Struct.new(
+ :name,
+ :type,
+ :group,
+ :ee,
+ :amend,
+ :dry_run,
+ :force,
+ :introduced_by_url,
+ :rollout_issue_url
+ )
+
+ class << self
+ def parse(argv)
+ options = Options.new
+
+ parser = OptionParser.new do |opts|
+ opts.banner = "Usage: #{__FILE__} [options] <feature-flag>\n\n"
+
+ # Note: We do not provide a shorthand for this in order to match the `git
+ # commit` interface
+ opts.on('--amend', 'Amend the previous commit') do |value|
+ options.amend = value
+ end
+
+ opts.on('-f', '--force', 'Overwrite an existing entry') do |value|
+ options.force = value
+ end
+
+ opts.on('-m', '--introduced-by-url [string]', String, 'URL to Merge Request introducing Feature Flag') do |value|
+ options.introduced_by_url = value
+ end
+
+ opts.on('-i', '--rollout-issue-url [string]', String, 'URL to Issue rolling out Feature Flag') do |value|
+ options.rollout_issue_url = value
+ end
+
+ opts.on('-n', '--dry-run', "Don't actually write anything, just print") do |value|
+ options.dry_run = value
+ end
+
+ opts.on('-g', '--group [string]', String, "The group introducing a feature flag, like: `group::apm`") do |value|
+ options.group = value if value.start_with?('group::')
+ end
+
+ opts.on('-t', '--type [string]', String, "The category of the feature flag, valid options are: #{TYPES.keys.map(&:to_s).join(', ')}") do |value|
+ options.type = value.to_sym if TYPES[value.to_sym]
+ end
+
+ opts.on('-e', '--ee', 'Generate a feature flag entry for GitLab EE') do |value|
+ options.ee = value
+ end
+
+ opts.on('-h', '--help', 'Print help message') do
+ $stdout.puts opts
+ raise Done.new
+ end
+ end
+
+ parser.parse!(argv)
+
+ unless argv.one?
+ $stdout.puts parser.help
+ $stdout.puts
+ raise Abort, 'Feature flag name is required'
+ end
+
+ # Name is a first name
+ options.name = argv.first
+
+ options
+ end
+
+ def read_group
+ $stdout.puts ">> Please specify the group introducing feature flag, like `group::apm`:"
+
+ loop do
+ $stdout.print "\n?> "
+ group = $stdin.gets.strip
+ group = nil if group.empty?
+ return group if group.nil? || group.start_with?('group::')
+
+ $stderr.puts "Group needs to include `group::`"
+ end
+ end
+
+ def read_type
+ $stdout.puts ">> Please specify the type of your feature flag:"
+ $stdout.puts
+ TYPES.each do |type, data|
+ $stdout.puts "#{type.to_s.rjust(15)}#{' '*6}#{data[:description]}"
+ end
+
+ loop do
+ $stdout.print "\n?> "
+
+ type = $stdin.gets.strip.to_sym
+ return type if TYPES[type]
+
+ $stderr.puts "Invalid type specified '#{type}'"
+ end
+ end
+
+ def read_issue_url(options)
+ return unless TYPES.dig(options.type, :rollout_issue)
+
+ url = "https://gitlab.com/gitlab-org/gitlab/-/issues/new"
+ title = "[Feature flag] Rollout of `#{options.name}`"
+ description = File.read('.gitlab/issue_templates/Feature Flag Roll Out.md')
+ description.sub!(':feature_name', options.name)
+
+ issue_new_url = url + "?" +
+ "issue[title]=" + CGI.escape(title) + "&"
+ # TODO: We should be able to pick `issueable_template`
+ # + "issue[description]=" + CGI.escape(description)
+
+ $stdout.puts ">> Open this URL and fill the rest of details:"
+ $stdout.puts issue_new_url
+ $stdout.puts
+
+ $stdout.puts ">> Paste URL here, or enter to skip:"
+
+ loop do
+ $stdout.print "\n?> "
+ created_url = $stdin.gets.strip
+ created_url = nil if created_url.empty?
+ return created_url if created_url.nil? || created_url.start_with?('https://')
+
+ $stderr.puts "URL needs to start with https://"
+ end
+ end
+ end
+end
+
+class FeatureFlagCreator
+ include FeatureFlagHelpers
+
+ attr_reader :options
+
+ def initialize(options)
+ @options = options
+ end
+
+ def execute
+ assert_feature_branch!
+ assert_name!
+ assert_existing_feature_flag!
+
+ # Read type from $stdin unless is already set
+ options.type ||= FeatureFlagOptionParser.read_type
+ options.group ||= FeatureFlagOptionParser.read_group
+ options.rollout_issue_url ||= FeatureFlagOptionParser.read_issue_url(options)
+
+ $stdout.puts "\e[32mcreate\e[0m #{file_path}"
+ $stdout.puts contents
+
+ unless options.dry_run
+ write
+ amend_commit if options.amend
+ end
+
+ if editor
+ system("#{editor} '#{file_path}'")
+ end
+ end
+
+ private
+
+ def contents
+ YAML.dump(
+ 'name' => options.name,
+ 'introduced_by_url' => options.introduced_by_url,
+ 'rollout_issue_url' => options.rollout_issue_url,
+ 'group' => options.group.to_s,
+ 'type' => options.type.to_s,
+ 'default_enabled' => false
+ ).strip
+ end
+
+ def write
+ FileUtils.mkdir_p(File.dirname(file_path))
+ File.write(file_path, contents)
+ end
+
+ def editor
+ ENV['EDITOR']
+ end
+
+ def amend_commit
+ fail_with "git add failed" unless system(*%W[git add #{file_path}])
+
+ Kernel.exec(*%w[git commit --amend])
+ end
+
+ def assert_feature_branch!
+ return unless branch_name == 'master'
+
+ fail_with "Create a branch first!"
+ end
+
+ def assert_existing_feature_flag!
+ existing_path = all_feature_flag_names[options.name]
+ return unless existing_path
+ return if options.force
+
+ fail_with "#{existing_path} already exists! Use `--force` to overwrite."
+ end
+
+ def assert_name!
+ return if options.name.match(/\A[a-z0-9_-]+\Z/)
+
+ fail_with "Provide a name for the feature flag that is [a-z0-9_-]"
+ end
+
+ def file_path
+ feature_flags_paths.last
+ .sub('**', options.type.to_s)
+ .sub('*.yml', options.name + '.yml')
+ end
+
+ def all_feature_flag_names
+ @all_feature_flag_names ||=
+ feature_flags_paths.map do |glob_path|
+ Dir.glob(glob_path).map do |path|
+ [File.basename(path, '.yml'), path]
+ end
+ end.flatten(1).to_h
+ end
+
+ def feature_flags_paths
+ paths = []
+ paths << File.join('config', 'feature_flags', '**', '*.yml')
+ paths << File.join('ee', 'config', 'feature_flags', '**', '*.yml') if ee?
+ paths
+ end
+
+ def ee?
+ options.ee
+ end
+
+ def branch_name
+ @branch_name ||= capture_stdout(%w[git symbolic-ref --short HEAD]).strip
+ end
+end
+
+if $0 == __FILE__
+ begin
+ options = FeatureFlagOptionParser.parse(ARGV)
+ FeatureFlagCreator.new(options).execute
+ rescue FeatureFlagHelpers::Abort => ex
+ $stderr.puts ex.message
+ exit 1
+ rescue FeatureFlagHelpers::Done
+ exit
+ end
+end
+
+# vim: ft=ruby
diff --git a/bin/rspec-stackprof b/bin/rspec-stackprof
index 8058d165196..3bef45c607c 100755
--- a/bin/rspec-stackprof
+++ b/bin/rspec-stackprof
@@ -8,9 +8,10 @@ require 'spec_helper'
filename = ARGV[0].split('/').last
interval = ENV.fetch('INTERVAL', 1000).to_i
limit = ENV.fetch('LIMIT', 20)
+raw = ENV.fetch('RAW', false) == 'true'
output_file = "tmp/#{filename}.dump"
-StackProf.run(mode: :wall, out: output_file, interval: interval) do
+StackProf.run(mode: :wall, out: output_file, interval: interval, raw: raw) do
RSpec::Core::Runner.run(ARGV, $stderr, $stdout)
end
diff --git a/changelogs/unreleased/11805-support-first-name-and-last-name-attributes-in-ldap-user-sync.yml b/changelogs/unreleased/11805-support-first-name-and-last-name-attributes-in-ldap-user-sync.yml
new file mode 100644
index 00000000000..4878eeb26e7
--- /dev/null
+++ b/changelogs/unreleased/11805-support-first-name-and-last-name-attributes-in-ldap-user-sync.yml
@@ -0,0 +1,5 @@
+---
+title: Support first_name and last_name attributes in LDAP user sync
+merge_request: 29542
+author:
+type: added
diff --git a/changelogs/unreleased/118613-spam-api-call.yml b/changelogs/unreleased/118613-spam-api-call.yml
new file mode 100644
index 00000000000..68d4aa27071
--- /dev/null
+++ b/changelogs/unreleased/118613-spam-api-call.yml
@@ -0,0 +1,5 @@
+---
+title: SpamVerdictService can call external spam check endpoint
+merge_request: 31449
+author:
+type: added
diff --git a/changelogs/unreleased/119018-move-alert-settings-to-Vue.yml b/changelogs/unreleased/119018-move-alert-settings-to-Vue.yml
new file mode 100644
index 00000000000..84211b5517f
--- /dev/null
+++ b/changelogs/unreleased/119018-move-alert-settings-to-Vue.yml
@@ -0,0 +1,5 @@
+---
+title: Move alert integrations setting to Vue
+merge_request: 36110
+author:
+type: changed
diff --git a/changelogs/unreleased/13049-design-view-allow-comment-pins-on-designs-to-be-resolvable.yml b/changelogs/unreleased/13049-design-view-allow-comment-pins-on-designs-to-be-resolvable.yml
new file mode 100644
index 00000000000..579485e7488
--- /dev/null
+++ b/changelogs/unreleased/13049-design-view-allow-comment-pins-on-designs-to-be-resolvable.yml
@@ -0,0 +1,5 @@
+---
+title: "[Frontend] Resolvable design discussions"
+merge_request: 32399
+author:
+type: added
diff --git a/changelogs/unreleased/13049-graphql-resolve-discussion.yml b/changelogs/unreleased/13049-graphql-resolve-discussion.yml
new file mode 100644
index 00000000000..850d5b5aac4
--- /dev/null
+++ b/changelogs/unreleased/13049-graphql-resolve-discussion.yml
@@ -0,0 +1,5 @@
+---
+title: Add a GraphQL mutation for toggling the resolved state of a Discussion
+merge_request: 32934
+author:
+type: added
diff --git a/changelogs/unreleased/15242-wiki-diff.yml b/changelogs/unreleased/15242-wiki-diff.yml
new file mode 100644
index 00000000000..1c9891cdcee
--- /dev/null
+++ b/changelogs/unreleased/15242-wiki-diff.yml
@@ -0,0 +1,5 @@
+---
+title: Allow diffing changes in wiki history
+merge_request: 35330
+author: gwhyte, Steve Mokris
+type: added
diff --git a/changelogs/unreleased/16239-snippet-onbeforeunload.yml b/changelogs/unreleased/16239-snippet-onbeforeunload.yml
new file mode 100644
index 00000000000..8701b4ffd5d
--- /dev/null
+++ b/changelogs/unreleased/16239-snippet-onbeforeunload.yml
@@ -0,0 +1,5 @@
+---
+title: Trigger unsaved changes warning in snippets on navigating away
+merge_request: 34640
+author:
+type: added
diff --git a/changelogs/unreleased/16877-quick-actions-on-description-edit.yml b/changelogs/unreleased/16877-quick-actions-on-description-edit.yml
new file mode 100644
index 00000000000..997576127f4
--- /dev/null
+++ b/changelogs/unreleased/16877-quick-actions-on-description-edit.yml
@@ -0,0 +1,5 @@
+---
+title: Support quick actions when editing issue, merge request, and epic descriptions
+merge_request: 31186
+author:
+type: added
diff --git a/changelogs/unreleased/17137.yml b/changelogs/unreleased/17137.yml
new file mode 100644
index 00000000000..8778fefc960
--- /dev/null
+++ b/changelogs/unreleased/17137.yml
@@ -0,0 +1,5 @@
+---
+title: Align "External" access level row in the user admin form
+merge_request: 34455
+author: Eduardo Sanz @esanzgar
+type: fixed
diff --git a/changelogs/unreleased/17555-custom-text-for-badges.yml b/changelogs/unreleased/17555-custom-text-for-badges.yml
new file mode 100644
index 00000000000..61132efa264
--- /dev/null
+++ b/changelogs/unreleased/17555-custom-text-for-badges.yml
@@ -0,0 +1,5 @@
+---
+title: Allow customization of badge key_text and key_width
+merge_request: 29381
+author: Fabian Schneider @fabsrc
+type: added
diff --git a/changelogs/unreleased/17613-configurable-defaults-for-squash-commits-option.yml b/changelogs/unreleased/17613-configurable-defaults-for-squash-commits-option.yml
new file mode 100644
index 00000000000..bb774921361
--- /dev/null
+++ b/changelogs/unreleased/17613-configurable-defaults-for-squash-commits-option.yml
@@ -0,0 +1,5 @@
+---
+title: Add squash commits options as a project setting.
+merge_request: 33099
+author:
+type: added
diff --git a/changelogs/unreleased/191455-add-a-button-to-assign-users-who-have-commented-on-an-issue.yml b/changelogs/unreleased/191455-add-a-button-to-assign-users-who-have-commented-on-an-issue.yml
new file mode 100644
index 00000000000..370d8f8ceb3
--- /dev/null
+++ b/changelogs/unreleased/191455-add-a-button-to-assign-users-who-have-commented-on-an-issue.yml
@@ -0,0 +1,5 @@
+---
+title: Resolve Add a button to assign users who have commented on an issue
+merge_request: 23883
+author:
+type: added
diff --git a/changelogs/unreleased/191455-add-a-button-to-quickly-assign-users-who-have-commented-on-an-issu.yml b/changelogs/unreleased/191455-add-a-button-to-quickly-assign-users-who-have-commented-on-an-issu.yml
new file mode 100644
index 00000000000..df303695865
--- /dev/null
+++ b/changelogs/unreleased/191455-add-a-button-to-quickly-assign-users-who-have-commented-on-an-issu.yml
@@ -0,0 +1,5 @@
+---
+title: Add api.js methods to update issues and merge requests
+merge_request: 32893
+author:
+type: added
diff --git a/changelogs/unreleased/195692-too-much-x-axis-padding-on-the-environments-dashboard-content.yml b/changelogs/unreleased/195692-too-much-x-axis-padding-on-the-environments-dashboard-content.yml
new file mode 100644
index 00000000000..0ee3d01d030
--- /dev/null
+++ b/changelogs/unreleased/195692-too-much-x-axis-padding-on-the-environments-dashboard-content.yml
@@ -0,0 +1,5 @@
+---
+title: Resolve incorrect x-axis padding on the Environments Dashboard
+merge_request: 32533
+author:
+type: fixed
diff --git a/changelogs/unreleased/195712-empty-stacktrace-on-error-details-page.yml b/changelogs/unreleased/195712-empty-stacktrace-on-error-details-page.yml
new file mode 100644
index 00000000000..0c82a04838a
--- /dev/null
+++ b/changelogs/unreleased/195712-empty-stacktrace-on-error-details-page.yml
@@ -0,0 +1,5 @@
+---
+title: Show notification about empty stacktrace
+merge_request: 34517
+author:
+type: added
diff --git a/changelogs/unreleased/196544-nodemetrics-size.yml b/changelogs/unreleased/196544-nodemetrics-size.yml
new file mode 100644
index 00000000000..1319ef2f624
--- /dev/null
+++ b/changelogs/unreleased/196544-nodemetrics-size.yml
@@ -0,0 +1,5 @@
+---
+title: Added node size to cluster index
+merge_request: 32435
+author:
+type: changed
diff --git a/changelogs/unreleased/196630-commit-tab.yml b/changelogs/unreleased/196630-commit-tab.yml
new file mode 100644
index 00000000000..38b158d1520
--- /dev/null
+++ b/changelogs/unreleased/196630-commit-tab.yml
@@ -0,0 +1,5 @@
+---
+title: Don't hide Commit tab in Web IDE when there are no changes yet
+merge_request: 32979
+author:
+type: added
diff --git a/changelogs/unreleased/196784-add-container-expiration-policy-to-graphql-project.yml b/changelogs/unreleased/196784-add-container-expiration-policy-to-graphql-project.yml
new file mode 100644
index 00000000000..dfd80b80241
--- /dev/null
+++ b/changelogs/unreleased/196784-add-container-expiration-policy-to-graphql-project.yml
@@ -0,0 +1,5 @@
+---
+title: Add the container expiration policy attribute to the project GraphQL type
+merge_request: 32100
+author:
+type: added
diff --git a/changelogs/unreleased/196784-graphql-mutations-for-container-expiration-policies.yml b/changelogs/unreleased/196784-graphql-mutations-for-container-expiration-policies.yml
new file mode 100644
index 00000000000..3205401f5db
--- /dev/null
+++ b/changelogs/unreleased/196784-graphql-mutations-for-container-expiration-policies.yml
@@ -0,0 +1,5 @@
+---
+title: Add container expiration policy objects to the GraphQL API
+merge_request: 32944
+author:
+type: added
diff --git a/changelogs/unreleased/197426-error-details-timeago-tooltip.yml b/changelogs/unreleased/197426-error-details-timeago-tooltip.yml
new file mode 100644
index 00000000000..926f22bfa68
--- /dev/null
+++ b/changelogs/unreleased/197426-error-details-timeago-tooltip.yml
@@ -0,0 +1,5 @@
+---
+title: Show tooltip on error detail page when hovering over dates
+merge_request: 34506
+author:
+type: added
diff --git a/changelogs/unreleased/199245-search-api-seems-to-ignore-ref-returns-spurious-results-from-maste.yml b/changelogs/unreleased/199245-search-api-seems-to-ignore-ref-returns-spurious-results-from-maste.yml
new file mode 100644
index 00000000000..8e9f49ad14f
--- /dev/null
+++ b/changelogs/unreleased/199245-search-api-seems-to-ignore-ref-returns-spurious-results-from-maste.yml
@@ -0,0 +1,5 @@
+---
+title: Fix incorrect commit search results returned when searching with ref
+merge_request: 33216
+author:
+type: fixed
diff --git a/changelogs/unreleased/199250-expose-release-yaml-as-steps-via-api-2.yml b/changelogs/unreleased/199250-expose-release-yaml-as-steps-via-api-2.yml
new file mode 100644
index 00000000000..58d02d65b71
--- /dev/null
+++ b/changelogs/unreleased/199250-expose-release-yaml-as-steps-via-api-2.yml
@@ -0,0 +1,5 @@
+---
+title: Convert `:release` yaml to `release-cli` commands
+merge_request: 34261
+author:
+type: added
diff --git a/changelogs/unreleased/199250-release-generation-from-within-gitlab-ci-yml.yml b/changelogs/unreleased/199250-release-generation-from-within-gitlab-ci-yml.yml
new file mode 100644
index 00000000000..86ef117a0e2
--- /dev/null
+++ b/changelogs/unreleased/199250-release-generation-from-within-gitlab-ci-yml.yml
@@ -0,0 +1,5 @@
+---
+title: Release generation via gitlab-ci.yml documentation
+merge_request: 19237
+author:
+type: added
diff --git a/changelogs/unreleased/199732-show-more-context-to-jump-unresolved-button.yml b/changelogs/unreleased/199732-show-more-context-to-jump-unresolved-button.yml
new file mode 100644
index 00000000000..0a654b11442
--- /dev/null
+++ b/changelogs/unreleased/199732-show-more-context-to-jump-unresolved-button.yml
@@ -0,0 +1,5 @@
+---
+title: Show more context in unresolved jump button
+merge_request: 32737
+author:
+type: changed
diff --git a/changelogs/unreleased/200016-display-downstream-pipeline-errors.yml b/changelogs/unreleased/200016-display-downstream-pipeline-errors.yml
new file mode 100644
index 00000000000..4457947639f
--- /dev/null
+++ b/changelogs/unreleased/200016-display-downstream-pipeline-errors.yml
@@ -0,0 +1,5 @@
+---
+title: Implement displaying downstream pipeline error details
+merge_request: 32844
+author:
+type: fixed
diff --git a/changelogs/unreleased/20069-ci-secrets-rake-task.yml b/changelogs/unreleased/20069-ci-secrets-rake-task.yml
new file mode 100644
index 00000000000..6c75d8945e5
--- /dev/null
+++ b/changelogs/unreleased/20069-ci-secrets-rake-task.yml
@@ -0,0 +1,5 @@
+---
+title: Add rake task to verify encrypted data through secrets
+merge_request: 21851
+author:
+type: added
diff --git a/changelogs/unreleased/201840-link-testwidget.yml b/changelogs/unreleased/201840-link-testwidget.yml
new file mode 100644
index 00000000000..d489755b20c
--- /dev/null
+++ b/changelogs/unreleased/201840-link-testwidget.yml
@@ -0,0 +1,5 @@
+---
+title: Link to test reports from MR Widget
+merge_request: 29729
+author:
+type: added
diff --git a/changelogs/unreleased/202159-open-fork.yml b/changelogs/unreleased/202159-open-fork.yml
new file mode 100644
index 00000000000..9be66cc8deb
--- /dev/null
+++ b/changelogs/unreleased/202159-open-fork.yml
@@ -0,0 +1,6 @@
+---
+title: If a user does not have write access to repo, but a fork exists, the Web IDE
+ button should take them to the fork
+merge_request: 36548
+author:
+type: added
diff --git a/changelogs/unreleased/204839-move-update-logic-to-service.yml b/changelogs/unreleased/204839-move-update-logic-to-service.yml
new file mode 100644
index 00000000000..528be73e097
--- /dev/null
+++ b/changelogs/unreleased/204839-move-update-logic-to-service.yml
@@ -0,0 +1,5 @@
+---
+title: Periodically update container registry type settings
+merge_request: 36415
+author:
+type: added
diff --git a/changelogs/unreleased/204904-show-clone-button-on-project-page.yml b/changelogs/unreleased/204904-show-clone-button-on-project-page.yml
new file mode 100644
index 00000000000..53e07431f67
--- /dev/null
+++ b/changelogs/unreleased/204904-show-clone-button-on-project-page.yml
@@ -0,0 +1,5 @@
+---
+title: Show clone button on project page for readme preference
+merge_request: 33023
+author:
+type: changed
diff --git a/changelogs/unreleased/204920-add-refresh-rate-btn.yml b/changelogs/unreleased/204920-add-refresh-rate-btn.yml
new file mode 100644
index 00000000000..e567ddca591
--- /dev/null
+++ b/changelogs/unreleased/204920-add-refresh-rate-btn.yml
@@ -0,0 +1,5 @@
+---
+title: Add refresh rate options to dashboard header
+merge_request: 35238
+author:
+type: added
diff --git a/changelogs/unreleased/205424-add-api-for-share-groups-with-groups.yml b/changelogs/unreleased/205424-add-api-for-share-groups-with-groups.yml
new file mode 100644
index 00000000000..a8d892db2ff
--- /dev/null
+++ b/changelogs/unreleased/205424-add-api-for-share-groups-with-groups.yml
@@ -0,0 +1,5 @@
+---
+title: Add API support for sharing groups with groups
+merge_request: 32008
+author:
+type: added
diff --git a/changelogs/unreleased/207257-specify-asset-types-in-releases-2.yml b/changelogs/unreleased/207257-specify-asset-types-in-releases-2.yml
new file mode 100644
index 00000000000..f897faeeaa2
--- /dev/null
+++ b/changelogs/unreleased/207257-specify-asset-types-in-releases-2.yml
@@ -0,0 +1,5 @@
+---
+title: Expose `release_links.type` via API
+merge_request: 33154
+author:
+type: changed
diff --git a/changelogs/unreleased/207257-specify-asset-types-in-releases-3.yml b/changelogs/unreleased/207257-specify-asset-types-in-releases-3.yml
new file mode 100644
index 00000000000..bf5de47e7ff
--- /dev/null
+++ b/changelogs/unreleased/207257-specify-asset-types-in-releases-3.yml
@@ -0,0 +1,5 @@
+---
+title: Add `link_type` to `ReleaseLink` GraphQL type
+merge_request: 33386
+author:
+type: added
diff --git a/changelogs/unreleased/207257-specify-asset-types-in-releases.yml b/changelogs/unreleased/207257-specify-asset-types-in-releases.yml
new file mode 100644
index 00000000000..ee11eb72389
--- /dev/null
+++ b/changelogs/unreleased/207257-specify-asset-types-in-releases.yml
@@ -0,0 +1,5 @@
+---
+title: Add link_type column to release_links table
+merge_request: 33156
+author:
+type: changed
diff --git a/changelogs/unreleased/207472-create-confidential-note-api.yml b/changelogs/unreleased/207472-create-confidential-note-api.yml
new file mode 100644
index 00000000000..9ea01d24be5
--- /dev/null
+++ b/changelogs/unreleased/207472-create-confidential-note-api.yml
@@ -0,0 +1,5 @@
+---
+title: Add confidential attribute to public API for notes creation
+merge_request: 36793
+author:
+type: added
diff --git a/changelogs/unreleased/207473-create-confidential-notes-graphql.yml b/changelogs/unreleased/207473-create-confidential-notes-graphql.yml
new file mode 100644
index 00000000000..8d9edee07b2
--- /dev/null
+++ b/changelogs/unreleased/207473-create-confidential-notes-graphql.yml
@@ -0,0 +1,5 @@
+---
+title: Add confidential attribute to graphQL for notes creation
+merge_request: 36799
+author:
+type: added
diff --git a/changelogs/unreleased/207990-secret-detection-ci-template.yml b/changelogs/unreleased/207990-secret-detection-ci-template.yml
new file mode 100644
index 00000000000..246ac1c096a
--- /dev/null
+++ b/changelogs/unreleased/207990-secret-detection-ci-template.yml
@@ -0,0 +1,5 @@
+---
+title: Add secret detection template
+merge_request: 33869
+author:
+type: added
diff --git a/changelogs/unreleased/208193-add-logs-to-container-repository-delete-tags-service.yml b/changelogs/unreleased/208193-add-logs-to-container-repository-delete-tags-service.yml
new file mode 100644
index 00000000000..7bef6203075
--- /dev/null
+++ b/changelogs/unreleased/208193-add-logs-to-container-repository-delete-tags-service.yml
@@ -0,0 +1,5 @@
+---
+title: Add log statements to Projects::ContainerRepository::DeleteTagsService
+merge_request: 35539
+author:
+type: added
diff --git a/changelogs/unreleased/208412-featurable.yml b/changelogs/unreleased/208412-featurable.yml
new file mode 100644
index 00000000000..02adcccec0b
--- /dev/null
+++ b/changelogs/unreleased/208412-featurable.yml
@@ -0,0 +1,5 @@
+---
+title: Extract featurable concern from ProjectFeature
+merge_request: 31700
+author: Alexander Randa
+type: other
diff --git a/changelogs/unreleased/208655-introduce-prepare-keyword-to-environment-action-to-annotate-non-de.yml b/changelogs/unreleased/208655-introduce-prepare-keyword-to-environment-action-to-annotate-non-de.yml
new file mode 100644
index 00000000000..efb63195aff
--- /dev/null
+++ b/changelogs/unreleased/208655-introduce-prepare-keyword-to-environment-action-to-annotate-non-de.yml
@@ -0,0 +1,5 @@
+---
+title: Introduce prepare environment action to annotate non-deployment jobs
+merge_request: 35642
+author:
+type: added
diff --git a/changelogs/unreleased/208738-improve-performance-of-branches-list-api-when-under-load.yml b/changelogs/unreleased/208738-improve-performance-of-branches-list-api-when-under-load.yml
new file mode 100644
index 00000000000..90f90e285e8
--- /dev/null
+++ b/changelogs/unreleased/208738-improve-performance-of-branches-list-api-when-under-load.yml
@@ -0,0 +1,5 @@
+---
+title: Use native Gitaly pagination for Branch list API
+merge_request: 35819
+author:
+type: changed
diff --git a/changelogs/unreleased/209025-design-filename-limit-migrations.yml b/changelogs/unreleased/209025-design-filename-limit-migrations.yml
new file mode 100644
index 00000000000..92379b29e34
--- /dev/null
+++ b/changelogs/unreleased/209025-design-filename-limit-migrations.yml
@@ -0,0 +1,6 @@
+---
+title: Add database migrations to design_management_designs.filename to enforce
+ a 255 character limit, and modify any filenames that exceed that limit
+merge_request: 33565
+author:
+type: changed
diff --git a/changelogs/unreleased/209345-add-ds-detect-kotlin-build-file.yml b/changelogs/unreleased/209345-add-ds-detect-kotlin-build-file.yml
new file mode 100644
index 00000000000..a6eca5f5ad6
--- /dev/null
+++ b/changelogs/unreleased/209345-add-ds-detect-kotlin-build-file.yml
@@ -0,0 +1,5 @@
+---
+title: Add DS detection of build.gradle.kts
+merge_request: !32727
+author:
+type: fixed
diff --git a/changelogs/unreleased/209786-improve-performance-of-diffs_batch-and-diffs_metadata-actions-for-.yml b/changelogs/unreleased/209786-improve-performance-of-diffs_batch-and-diffs_metadata-actions-for-.yml
new file mode 100644
index 00000000000..3780e463306
--- /dev/null
+++ b/changelogs/unreleased/209786-improve-performance-of-diffs_batch-and-diffs_metadata-actions-for-.yml
@@ -0,0 +1,5 @@
+---
+title: Improve the performance for loading large diffs on a Merge request
+merge_request: 33037
+author:
+type: performance
diff --git a/changelogs/unreleased/209786-improve-performance-of-diffs_batch-diffs-metadata-source-branch-ex.yml b/changelogs/unreleased/209786-improve-performance-of-diffs_batch-diffs-metadata-source-branch-ex.yml
new file mode 100644
index 00000000000..215d64bbacb
--- /dev/null
+++ b/changelogs/unreleased/209786-improve-performance-of-diffs_batch-diffs-metadata-source-branch-ex.yml
@@ -0,0 +1,5 @@
+---
+title: Further improve the performance for loading large diffs on a Merge request
+merge_request: 34516
+author:
+type: performance
diff --git a/changelogs/unreleased/209912-commits-markdown-cache-preload.yml b/changelogs/unreleased/209912-commits-markdown-cache-preload.yml
new file mode 100644
index 00000000000..4665085d4fe
--- /dev/null
+++ b/changelogs/unreleased/209912-commits-markdown-cache-preload.yml
@@ -0,0 +1,5 @@
+---
+title: Preload commits markdown cache
+merge_request: 35314
+author:
+type: performance
diff --git a/changelogs/unreleased/209912-memoize-sprites-icon-path.yml b/changelogs/unreleased/209912-memoize-sprites-icon-path.yml
new file mode 100644
index 00000000000..fa8dc87d11a
--- /dev/null
+++ b/changelogs/unreleased/209912-memoize-sprites-icon-path.yml
@@ -0,0 +1,5 @@
+---
+title: Avoid N+1 calls for image_path when rendering commits
+merge_request: 36724
+author:
+type: performance
diff --git a/changelogs/unreleased/21002-slack-opened-merge-request-webhook-message-is-malformed.yml b/changelogs/unreleased/21002-slack-opened-merge-request-webhook-message-is-malformed.yml
new file mode 100644
index 00000000000..840f72be481
--- /dev/null
+++ b/changelogs/unreleased/21002-slack-opened-merge-request-webhook-message-is-malformed.yml
@@ -0,0 +1,5 @@
+---
+title: Add explicit mention of Merge request in Slack message
+merge_request: 33152
+author:
+type: changed
diff --git a/changelogs/unreleased/210281-label-for-pipeline-schedule-active.yml b/changelogs/unreleased/210281-label-for-pipeline-schedule-active.yml
new file mode 100644
index 00000000000..7b3ad814382
--- /dev/null
+++ b/changelogs/unreleased/210281-label-for-pipeline-schedule-active.yml
@@ -0,0 +1,5 @@
+---
+title: Fix 'Active' checkbox text in Pipeline Schedule form to be a label
+merge_request: 27054
+author: Jonston Chan
+type: fixed
diff --git a/changelogs/unreleased/210482-update-descriptions-on-the-integrations-and-webhooks-pages-at-the-.yml b/changelogs/unreleased/210482-update-descriptions-on-the-integrations-and-webhooks-pages-at-the-.yml
new file mode 100644
index 00000000000..4e8aa0d7ca5
--- /dev/null
+++ b/changelogs/unreleased/210482-update-descriptions-on-the-integrations-and-webhooks-pages-at-the-.yml
@@ -0,0 +1,5 @@
+---
+title: Change copy of webhooks / integration help text
+merge_request: 34301
+author:
+type: changed
diff --git a/changelogs/unreleased/210523-visually-align-system-bots-alert-support-and-security.yml b/changelogs/unreleased/210523-visually-align-system-bots-alert-support-and-security.yml
new file mode 100644
index 00000000000..48ca49cfafb
--- /dev/null
+++ b/changelogs/unreleased/210523-visually-align-system-bots-alert-support-and-security.yml
@@ -0,0 +1,5 @@
+---
+title: Add custom avatars for Alert and Support Bot
+merge_request: 36269
+author:
+type: added
diff --git a/changelogs/unreleased/210550-conan-export-tgz.yml b/changelogs/unreleased/210550-conan-export-tgz.yml
new file mode 100644
index 00000000000..bffd0b8b644
--- /dev/null
+++ b/changelogs/unreleased/210550-conan-export-tgz.yml
@@ -0,0 +1,5 @@
+---
+title: Conan package registry support for the conan_export.tgz file
+merge_request: 32866
+author:
+type: fixed
diff --git a/changelogs/unreleased/211340-change-chart-legend-format-to-tabular-format.yml b/changelogs/unreleased/211340-change-chart-legend-format-to-tabular-format.yml
new file mode 100644
index 00000000000..9db7b230ded
--- /dev/null
+++ b/changelogs/unreleased/211340-change-chart-legend-format-to-tabular-format.yml
@@ -0,0 +1,5 @@
+---
+title: Change legends in monitor dashboards to tabular layout
+merge_request: 30131
+author:
+type: changed
diff --git a/changelogs/unreleased/211443-ide-only-show-open-mrs.yml b/changelogs/unreleased/211443-ide-only-show-open-mrs.yml
new file mode 100644
index 00000000000..b240216388f
--- /dev/null
+++ b/changelogs/unreleased/211443-ide-only-show-open-mrs.yml
@@ -0,0 +1,4 @@
+---
+title: Only show open Merge Requests in Web IDE
+merge_request: 35514
+type: fixed
diff --git a/changelogs/unreleased/211461-destroy-annotations-graphql-endpoint.yml b/changelogs/unreleased/211461-destroy-annotations-graphql-endpoint.yml
new file mode 100644
index 00000000000..7e9edaad64b
--- /dev/null
+++ b/changelogs/unreleased/211461-destroy-annotations-graphql-endpoint.yml
@@ -0,0 +1,5 @@
+---
+title: Added delete action for Dashboard Annotations in GraphQL
+merge_request: 33468
+author:
+type: added
diff --git a/changelogs/unreleased/211828-placement-of-add-designs-button-could-be-confusing-with-the-additi.yml b/changelogs/unreleased/211828-placement-of-add-designs-button-could-be-confusing-with-the-additi.yml
new file mode 100644
index 00000000000..cb172caf997
--- /dev/null
+++ b/changelogs/unreleased/211828-placement-of-add-designs-button-could-be-confusing-with-the-additi.yml
@@ -0,0 +1,5 @@
+---
+title: Rename Add Designs button
+merge_request: 33491
+author:
+type: changed
diff --git a/changelogs/unreleased/212063-images-overflow-at-releases-list-panel.yml b/changelogs/unreleased/212063-images-overflow-at-releases-list-panel.yml
new file mode 100644
index 00000000000..dcfe1e56491
--- /dev/null
+++ b/changelogs/unreleased/212063-images-overflow-at-releases-list-panel.yml
@@ -0,0 +1,5 @@
+---
+title: Resolve image overflow at releases list panel
+merge_request: 32307
+author:
+type: fixed
diff --git a/changelogs/unreleased/212229-move-features-to-core-multiple-kubernetes-clusters.yml b/changelogs/unreleased/212229-move-features-to-core-multiple-kubernetes-clusters.yml
new file mode 100644
index 00000000000..9898b6449b6
--- /dev/null
+++ b/changelogs/unreleased/212229-move-features-to-core-multiple-kubernetes-clusters.yml
@@ -0,0 +1,5 @@
+---
+title: 'Multiple Kubernetes clusters now available in GitLab core'
+merge_request: 35094
+author:
+type: changed
diff --git a/changelogs/unreleased/212811-adding-new-task-always-shows-error-something-went-wrong-while-fetc.yml b/changelogs/unreleased/212811-adding-new-task-always-shows-error-something-went-wrong-while-fetc.yml
new file mode 100644
index 00000000000..fd33640f42f
--- /dev/null
+++ b/changelogs/unreleased/212811-adding-new-task-always-shows-error-something-went-wrong-while-fetc.yml
@@ -0,0 +1,5 @@
+---
+title: Fix comment loading error in issues and merge requests
+merge_request: 36043
+author:
+type: fixed
diff --git a/changelogs/unreleased/212848.yml b/changelogs/unreleased/212848.yml
new file mode 100644
index 00000000000..c4a33866d62
--- /dev/null
+++ b/changelogs/unreleased/212848.yml
@@ -0,0 +1,5 @@
+---
+title: Removed UltraAuth integration for OmniAuth
+merge_request: 29330
+author: Kartikey Tanna
+type: removed
diff --git a/changelogs/unreleased/212873-fix-can-edit-logic.yml b/changelogs/unreleased/212873-fix-can-edit-logic.yml
new file mode 100644
index 00000000000..f9c2fb92b5c
--- /dev/null
+++ b/changelogs/unreleased/212873-fix-can-edit-logic.yml
@@ -0,0 +1,5 @@
+---
+title: Remove Edit dashboard button from self monitoring dashboard
+merge_request: 35521
+author:
+type: fixed
diff --git a/changelogs/unreleased/212882-add-cpu-mem-charts.yml b/changelogs/unreleased/212882-add-cpu-mem-charts.yml
new file mode 100644
index 00000000000..52e645cead7
--- /dev/null
+++ b/changelogs/unreleased/212882-add-cpu-mem-charts.yml
@@ -0,0 +1,5 @@
+---
+title: Add CPU, memory usage charts to self monitoring default dashboard
+merge_request: 33532
+author:
+type: changed
diff --git a/changelogs/unreleased/212882-add-instance-variable.yml b/changelogs/unreleased/212882-add-instance-variable.yml
new file mode 100644
index 00000000000..19131ff7ab9
--- /dev/null
+++ b/changelogs/unreleased/212882-add-instance-variable.yml
@@ -0,0 +1,5 @@
+---
+title: Add ability to filter self monitoring resource usage charts by instance name
+merge_request: 34084
+author:
+type: changed
diff --git a/changelogs/unreleased/213009-when-filtering-by-groups-icons-are-misaligned.yml b/changelogs/unreleased/213009-when-filtering-by-groups-icons-are-misaligned.yml
new file mode 100644
index 00000000000..e0674b167a2
--- /dev/null
+++ b/changelogs/unreleased/213009-when-filtering-by-groups-icons-are-misaligned.yml
@@ -0,0 +1,6 @@
+---
+title: Improve spacing and wrapping of group actions buttons and stats in group list
+ view
+merge_request: 32786
+author:
+type: fixed
diff --git a/changelogs/unreleased/213587-ide-ipad-scroll-issue.yml b/changelogs/unreleased/213587-ide-ipad-scroll-issue.yml
new file mode 100644
index 00000000000..b542581f0f8
--- /dev/null
+++ b/changelogs/unreleased/213587-ide-ipad-scroll-issue.yml
@@ -0,0 +1,5 @@
+---
+title: Fix issues with scroll on iOS / iPad OS
+merge_request: 34486
+author:
+type: fixed
diff --git a/changelogs/unreleased/213597-add-in-this-group-option-to-search-dropdown-where-not-present.yml b/changelogs/unreleased/213597-add-in-this-group-option-to-search-dropdown-where-not-present.yml
new file mode 100644
index 00000000000..c8d405afc01
--- /dev/null
+++ b/changelogs/unreleased/213597-add-in-this-group-option-to-search-dropdown-where-not-present.yml
@@ -0,0 +1,5 @@
+---
+title: Enable the `in this group` action in the Search dropdown
+merge_request: 31939
+author:
+type: changed
diff --git a/changelogs/unreleased/213699-remove-search-results-autocomplete.yml b/changelogs/unreleased/213699-remove-search-results-autocomplete.yml
new file mode 100644
index 00000000000..539d4695658
--- /dev/null
+++ b/changelogs/unreleased/213699-remove-search-results-autocomplete.yml
@@ -0,0 +1,5 @@
+---
+title: Remove all search autocomplete for groups/projects/other
+merge_request: 31187
+author:
+type: removed
diff --git a/changelogs/unreleased/213816.yml b/changelogs/unreleased/213816.yml
new file mode 100644
index 00000000000..1492dda47b4
--- /dev/null
+++ b/changelogs/unreleased/213816.yml
@@ -0,0 +1,5 @@
+---
+title: Drop deprecated **_ANALYZER_IMAGE_PREFIX
+merge_request: 34325
+author:
+type: removed
diff --git a/changelogs/unreleased/213824-update-red-variables-in-gitlab-scss-to-match-gitlab-ui.yml b/changelogs/unreleased/213824-update-red-variables-in-gitlab-scss-to-match-gitlab-ui.yml
new file mode 100644
index 00000000000..7c446d40128
--- /dev/null
+++ b/changelogs/unreleased/213824-update-red-variables-in-gitlab-scss-to-match-gitlab-ui.yml
@@ -0,0 +1,5 @@
+---
+title: Update red hex values to match GitLab UI
+merge_request: 34544
+author:
+type: other
diff --git a/changelogs/unreleased/213881-alerts-list-pagination.yml b/changelogs/unreleased/213881-alerts-list-pagination.yml
new file mode 100644
index 00000000000..310152722ed
--- /dev/null
+++ b/changelogs/unreleased/213881-alerts-list-pagination.yml
@@ -0,0 +1,5 @@
+---
+title: Alerts list pagination
+merge_request: 33073
+author:
+type: added
diff --git a/changelogs/unreleased/213929-move-package-apis-to-core.yml b/changelogs/unreleased/213929-move-package-apis-to-core.yml
new file mode 100644
index 00000000000..fd8413875ba
--- /dev/null
+++ b/changelogs/unreleased/213929-move-package-apis-to-core.yml
@@ -0,0 +1,5 @@
+---
+title: Package APIs moved to core
+merge_request: 35919
+author:
+type: changed
diff --git a/changelogs/unreleased/213983-use-version-instead-of-shortversion-for-sentry.yml b/changelogs/unreleased/213983-use-version-instead-of-shortversion-for-sentry.yml
new file mode 100644
index 00000000000..b883dcbbf98
--- /dev/null
+++ b/changelogs/unreleased/213983-use-version-instead-of-shortversion-for-sentry.yml
@@ -0,0 +1,5 @@
+---
+title: Use full version instead of short version for Sentry Error Release links.
+merge_request: 35623
+author:
+type: fixed
diff --git a/changelogs/unreleased/214039-display-filter-commit-in-mobile.yml b/changelogs/unreleased/214039-display-filter-commit-in-mobile.yml
new file mode 100644
index 00000000000..a9e1727ac4d
--- /dev/null
+++ b/changelogs/unreleased/214039-display-filter-commit-in-mobile.yml
@@ -0,0 +1,5 @@
+---
+title: Display commits search in mobile & adjust text
+merge_request: 35702
+author:
+type: changed
diff --git a/changelogs/unreleased/214102-move-the-members-section-from-settings-to-the-side-nav-for-project.yml b/changelogs/unreleased/214102-move-the-members-section-from-settings-to-the-side-nav-for-project.yml
new file mode 100644
index 00000000000..2cb3569a38e
--- /dev/null
+++ b/changelogs/unreleased/214102-move-the-members-section-from-settings-to-the-side-nav-for-project.yml
@@ -0,0 +1,5 @@
+---
+title: Move the Members section from settings to the side nav for projects
+merge_request: 32667
+author:
+type: changed
diff --git a/changelogs/unreleased/214103-remove-the-second-prompt-to-accept-or-decline-an-invitation.yml b/changelogs/unreleased/214103-remove-the-second-prompt-to-accept-or-decline-an-invitation.yml
new file mode 100644
index 00000000000..1b16856d178
--- /dev/null
+++ b/changelogs/unreleased/214103-remove-the-second-prompt-to-accept-or-decline-an-invitation.yml
@@ -0,0 +1,5 @@
+---
+title: Remove the second prompt to accept or decline an invitation
+merge_request: 35777
+author:
+type: changed
diff --git a/changelogs/unreleased/214109-date-time-format-should-be-consistent-in-the-incident.yml b/changelogs/unreleased/214109-date-time-format-should-be-consistent-in-the-incident.yml
new file mode 100644
index 00000000000..762c8dc1992
--- /dev/null
+++ b/changelogs/unreleased/214109-date-time-format-should-be-consistent-in-the-incident.yml
@@ -0,0 +1,5 @@
+---
+title: Add timezone display to alert based issue start time
+merge_request: 32702
+author:
+type: added
diff --git a/changelogs/unreleased/214250-usage-ping-counts-for-all-search.yml b/changelogs/unreleased/214250-usage-ping-counts-for-all-search.yml
new file mode 100644
index 00000000000..b313bfc9dcb
--- /dev/null
+++ b/changelogs/unreleased/214250-usage-ping-counts-for-all-search.yml
@@ -0,0 +1,5 @@
+---
+title: Add Usage Ping count for all searches
+merge_request: 32111
+author:
+type: changed
diff --git a/changelogs/unreleased/214281-modify-dashboard-title.yml b/changelogs/unreleased/214281-modify-dashboard-title.yml
new file mode 100644
index 00000000000..19919dd664b
--- /dev/null
+++ b/changelogs/unreleased/214281-modify-dashboard-title.yml
@@ -0,0 +1,5 @@
+---
+title: Add metrics dashboard name to document title
+merge_request: 30392
+author:
+type: added
diff --git a/changelogs/unreleased/214370-add-timezone-setting.yml b/changelogs/unreleased/214370-add-timezone-setting.yml
new file mode 100644
index 00000000000..03c564da54c
--- /dev/null
+++ b/changelogs/unreleased/214370-add-timezone-setting.yml
@@ -0,0 +1,5 @@
+---
+title: Display dates on metrics dashboards in UTC time zone
+merge_request: 32746
+author:
+type: added
diff --git a/changelogs/unreleased/214370-extend-metrics-settings.yml b/changelogs/unreleased/214370-extend-metrics-settings.yml
new file mode 100644
index 00000000000..56045e3cb5d
--- /dev/null
+++ b/changelogs/unreleased/214370-extend-metrics-settings.yml
@@ -0,0 +1,5 @@
+---
+title: Update operations metrics settings title and description to make them general
+merge_request: 32494
+author:
+type: changed
diff --git a/changelogs/unreleased/214493-invaid-uri.yml b/changelogs/unreleased/214493-invaid-uri.yml
new file mode 100644
index 00000000000..13d6e261804
--- /dev/null
+++ b/changelogs/unreleased/214493-invaid-uri.yml
@@ -0,0 +1,5 @@
+---
+title: Return 404 response when redirecting request with invalid url.
+merge_request: 33492
+author:
+type: fixed
diff --git a/changelogs/unreleased/214539-fe-fetch-dynamic-variable-options.yml b/changelogs/unreleased/214539-fe-fetch-dynamic-variable-options.yml
new file mode 100644
index 00000000000..44c49d2c3e3
--- /dev/null
+++ b/changelogs/unreleased/214539-fe-fetch-dynamic-variable-options.yml
@@ -0,0 +1,5 @@
+---
+title: Fetch metrics dashboard templating variable options using a Prometheus query
+merge_request: 34607
+author:
+type: added
diff --git a/changelogs/unreleased/214556-user-defined-alert-identification.yml b/changelogs/unreleased/214556-user-defined-alert-identification.yml
new file mode 100644
index 00000000000..d65f76e97c0
--- /dev/null
+++ b/changelogs/unreleased/214556-user-defined-alert-identification.yml
@@ -0,0 +1,5 @@
+---
+title: Set fingerprints and increment events count for Alert Management alerts
+merge_request: 32613
+author:
+type: added
diff --git a/changelogs/unreleased/214824-fix-web-ide-open-file-race-condition.yml b/changelogs/unreleased/214824-fix-web-ide-open-file-race-condition.yml
new file mode 100644
index 00000000000..c390e805514
--- /dev/null
+++ b/changelogs/unreleased/214824-fix-web-ide-open-file-race-condition.yml
@@ -0,0 +1,4 @@
+---
+title: Resolve "WebIDE displays blank file incorrectly"
+merge_request: 33391
+type: fixed
diff --git a/changelogs/unreleased/214905-measure-package-adoption.yml b/changelogs/unreleased/214905-measure-package-adoption.yml
new file mode 100644
index 00000000000..a502d067712
--- /dev/null
+++ b/changelogs/unreleased/214905-measure-package-adoption.yml
@@ -0,0 +1,5 @@
+---
+title: Measure adoption of package registry
+merge_request: 36514
+author:
+type: added
diff --git a/changelogs/unreleased/214921-improve-tabbing-behavior-when-creating-new-projects.yml b/changelogs/unreleased/214921-improve-tabbing-behavior-when-creating-new-projects.yml
new file mode 100644
index 00000000000..e7d56cf3189
--- /dev/null
+++ b/changelogs/unreleased/214921-improve-tabbing-behavior-when-creating-new-projects.yml
@@ -0,0 +1,5 @@
+---
+title: Fix tabbing through form fields in projects/new flow
+merge_request: 33209
+author:
+type: fixed
diff --git a/changelogs/unreleased/215160-add-intermediate-group-deploy-key-table.yml b/changelogs/unreleased/215160-add-intermediate-group-deploy-key-table.yml
new file mode 100644
index 00000000000..4adfa614830
--- /dev/null
+++ b/changelogs/unreleased/215160-add-intermediate-group-deploy-key-table.yml
@@ -0,0 +1,5 @@
+---
+title: Create group_deploy_keys_groups intermediate table
+merge_request: 32901
+author:
+type: added
diff --git a/changelogs/unreleased/215194-add-section-to-index_approval_rule_name_for_code_owners_rule_type.yml b/changelogs/unreleased/215194-add-section-to-index_approval_rule_name_for_code_owners_rule_type.yml
new file mode 100644
index 00000000000..6a914521437
--- /dev/null
+++ b/changelogs/unreleased/215194-add-section-to-index_approval_rule_name_for_code_owners_rule_type.yml
@@ -0,0 +1,5 @@
+---
+title: Add :section to approval_merge_request_rule unique index
+merge_request: 34680
+author:
+type: other
diff --git a/changelogs/unreleased/215497-add-custom-links-to-panel.yml b/changelogs/unreleased/215497-add-custom-links-to-panel.yml
new file mode 100644
index 00000000000..e542a4dfffc
--- /dev/null
+++ b/changelogs/unreleased/215497-add-custom-links-to-panel.yml
@@ -0,0 +1,5 @@
+---
+title: Allow user to add custom links to their metrics dashboard panels
+merge_request: 32646
+author:
+type: added
diff --git a/changelogs/unreleased/215517-tab-docs-ff.yml b/changelogs/unreleased/215517-tab-docs-ff.yml
new file mode 100644
index 00000000000..9ff5f4802ae
--- /dev/null
+++ b/changelogs/unreleased/215517-tab-docs-ff.yml
@@ -0,0 +1,5 @@
+---
+title: Add DAG visualization MVC
+merge_request: 33958
+author:
+type: added
diff --git a/changelogs/unreleased/215618_mutation_to_create_a_commit.yml b/changelogs/unreleased/215618_mutation_to_create_a_commit.yml
new file mode 100644
index 00000000000..9850681d1ef
--- /dev/null
+++ b/changelogs/unreleased/215618_mutation_to_create_a_commit.yml
@@ -0,0 +1,5 @@
+---
+title: Add mutation to create commits in GraphQL
+merge_request: 31102
+author:
+type: added
diff --git a/changelogs/unreleased/215619_add_mutation_to_create_mr.yml b/changelogs/unreleased/215619_add_mutation_to_create_mr.yml
new file mode 100644
index 00000000000..477f8b60d5e
--- /dev/null
+++ b/changelogs/unreleased/215619_add_mutation_to_create_mr.yml
@@ -0,0 +1,5 @@
+---
+title: Add mutation to create a merge request in GraphQL
+merge_request: 31867
+author:
+type: added
diff --git a/changelogs/unreleased/215658-add-users-graphql.yml b/changelogs/unreleased/215658-add-users-graphql.yml
new file mode 100644
index 00000000000..a28ae9f1a12
--- /dev/null
+++ b/changelogs/unreleased/215658-add-users-graphql.yml
@@ -0,0 +1,5 @@
+---
+title: Add user root query to GraphQL API
+merge_request: 33041
+author:
+type: added
diff --git a/changelogs/unreleased/215658-graphql-memberships.yml b/changelogs/unreleased/215658-graphql-memberships.yml
new file mode 100644
index 00000000000..1dd970dbfe6
--- /dev/null
+++ b/changelogs/unreleased/215658-graphql-memberships.yml
@@ -0,0 +1,5 @@
+---
+title: Adds groupMembership and projectMembership to GraphQL API
+merge_request: 33049
+author:
+type: added
diff --git a/changelogs/unreleased/215658-root-users-query.yml b/changelogs/unreleased/215658-root-users-query.yml
new file mode 100644
index 00000000000..24c388ba826
--- /dev/null
+++ b/changelogs/unreleased/215658-root-users-query.yml
@@ -0,0 +1,5 @@
+---
+title: Add root users query to GraphQL API
+merge_request: 33195
+author:
+type: added
diff --git a/changelogs/unreleased/215668-settings-auto-fix.yml b/changelogs/unreleased/215668-settings-auto-fix.yml
new file mode 100644
index 00000000000..b0ac2c15fcf
--- /dev/null
+++ b/changelogs/unreleased/215668-settings-auto-fix.yml
@@ -0,0 +1,5 @@
+---
+title: Add model for project level security auto-fix settings
+merge_request: 32577
+author:
+type: added
diff --git a/changelogs/unreleased/215711-improve-performance-of-search-api-advanced-users-scope.yml b/changelogs/unreleased/215711-improve-performance-of-search-api-advanced-users-scope.yml
new file mode 100644
index 00000000000..e401c881223
--- /dev/null
+++ b/changelogs/unreleased/215711-improve-performance-of-search-api-advanced-users-scope.yml
@@ -0,0 +1,5 @@
+---
+title: Reduce redundant queries for Search API users scope.
+merge_request: 33795
+author:
+type: performance
diff --git a/changelogs/unreleased/215946-add-gitlab-to-do-for-user-when-they-are-assigned-to-an-alert-2.yml b/changelogs/unreleased/215946-add-gitlab-to-do-for-user-when-they-are-assigned-to-an-alert-2.yml
new file mode 100644
index 00000000000..76e3770c65a
--- /dev/null
+++ b/changelogs/unreleased/215946-add-gitlab-to-do-for-user-when-they-are-assigned-to-an-alert-2.yml
@@ -0,0 +1,5 @@
+---
+title: Add ability for user to manually create a todo for an alert
+merge_request: 34175
+author:
+type: added
diff --git a/changelogs/unreleased/215946-add-gitlab-to-do-for-user-when-they-are-assigned-to-an-alert.yml b/changelogs/unreleased/215946-add-gitlab-to-do-for-user-when-they-are-assigned-to-an-alert.yml
new file mode 100644
index 00000000000..5191aca5caf
--- /dev/null
+++ b/changelogs/unreleased/215946-add-gitlab-to-do-for-user-when-they-are-assigned-to-an-alert.yml
@@ -0,0 +1,5 @@
+---
+title: Add todo when alert is assigned to a user
+merge_request: 34104
+author:
+type: added
diff --git a/changelogs/unreleased/216022-use-pod-label.yml b/changelogs/unreleased/216022-use-pod-label.yml
new file mode 100644
index 00000000000..76ad5bc8430
--- /dev/null
+++ b/changelogs/unreleased/216022-use-pod-label.yml
@@ -0,0 +1,5 @@
+---
+title: 'Fix the default metrics dashboard to work on K8s versions 1.12 to 1.16'
+merge_request: 36863
+author:
+type: fixed
diff --git a/changelogs/unreleased/216045-capture-todo-resolution.yml b/changelogs/unreleased/216045-capture-todo-resolution.yml
new file mode 100644
index 00000000000..cc43635a120
--- /dev/null
+++ b/changelogs/unreleased/216045-capture-todo-resolution.yml
@@ -0,0 +1,5 @@
+---
+title: Store Todo resolution method
+merge_request: 32753
+author:
+type: added
diff --git a/changelogs/unreleased/216048-misleading-message-displays-when-mr-request-is-first-submitted.yml b/changelogs/unreleased/216048-misleading-message-displays-when-mr-request-is-first-submitted.yml
new file mode 100644
index 00000000000..f6999969def
--- /dev/null
+++ b/changelogs/unreleased/216048-misleading-message-displays-when-mr-request-is-first-submitted.yml
@@ -0,0 +1,5 @@
+---
+title: Resolve Misleading message displays when MR request is first submitted
+merge_request: 34958
+author:
+type: fixed
diff --git a/changelogs/unreleased/216088-disable-container-expiration-policy-when-invalid-regex-is-present.yml b/changelogs/unreleased/216088-disable-container-expiration-policy-when-invalid-regex-is-present.yml
new file mode 100644
index 00000000000..0a8df78d0c7
--- /dev/null
+++ b/changelogs/unreleased/216088-disable-container-expiration-policy-when-invalid-regex-is-present.yml
@@ -0,0 +1,5 @@
+---
+title: Validate regex before sending them to CleanupContainerRepositoryWorker
+merge_request: 34282
+author:
+type: added
diff --git a/changelogs/unreleased/216088-regex-validation-on-container-expiration-policy.yml b/changelogs/unreleased/216088-regex-validation-on-container-expiration-policy.yml
new file mode 100644
index 00000000000..c6df3112d90
--- /dev/null
+++ b/changelogs/unreleased/216088-regex-validation-on-container-expiration-policy.yml
@@ -0,0 +1,5 @@
+---
+title: Container expiration policy regular expressions are now validated
+merge_request: 34063
+author:
+type: added
diff --git a/changelogs/unreleased/216097-add-application-limits-to-ci-instancevariable.yml b/changelogs/unreleased/216097-add-application-limits-to-ci-instancevariable.yml
new file mode 100644
index 00000000000..becb89455fd
--- /dev/null
+++ b/changelogs/unreleased/216097-add-application-limits-to-ci-instancevariable.yml
@@ -0,0 +1,5 @@
+---
+title: Add application limits to instance level CI/CD variables
+merge_request: 32575
+author:
+type: added
diff --git a/changelogs/unreleased/216103-personal-access-token-pat-expiry-is-notifying-a-for-impersonation-.yml b/changelogs/unreleased/216103-personal-access-token-pat-expiry-is-notifying-a-for-impersonation-.yml
new file mode 100644
index 00000000000..16884a14a1e
--- /dev/null
+++ b/changelogs/unreleased/216103-personal-access-token-pat-expiry-is-notifying-a-for-impersonation-.yml
@@ -0,0 +1,5 @@
+---
+title: Prevent emails to user on expiry of impersonation token
+merge_request: 32140
+author:
+type: fixed
diff --git a/changelogs/unreleased/216142-resolve-alert-when-associated-issue-closes.yml b/changelogs/unreleased/216142-resolve-alert-when-associated-issue-closes.yml
new file mode 100644
index 00000000000..834fef6f01b
--- /dev/null
+++ b/changelogs/unreleased/216142-resolve-alert-when-associated-issue-closes.yml
@@ -0,0 +1,5 @@
+---
+title: Automatically resolve alert when associated issue closes
+merge_request: 33278
+author:
+type: added
diff --git a/changelogs/unreleased/216143-resolve-todo-for-user-after-alert-resolve.yml b/changelogs/unreleased/216143-resolve-todo-for-user-after-alert-resolve.yml
new file mode 100644
index 00000000000..3a23a6c6b50
--- /dev/null
+++ b/changelogs/unreleased/216143-resolve-todo-for-user-after-alert-resolve.yml
@@ -0,0 +1,5 @@
+---
+title: Resolve user's todo when an alert is resolved
+merge_request: 35700
+author:
+type: added
diff --git a/changelogs/unreleased/216143-show-resolved-state-in-todos-list.yml b/changelogs/unreleased/216143-show-resolved-state-in-todos-list.yml
new file mode 100644
index 00000000000..e88de7b09d1
--- /dev/null
+++ b/changelogs/unreleased/216143-show-resolved-state-in-todos-list.yml
@@ -0,0 +1,5 @@
+---
+title: Add todo pill styling for resolved alert
+merge_request: 35579
+author:
+type: added
diff --git a/changelogs/unreleased/216145-frontend-create-jira-import-user-mapping-form.yml b/changelogs/unreleased/216145-frontend-create-jira-import-user-mapping-form.yml
new file mode 100644
index 00000000000..265c5479373
--- /dev/null
+++ b/changelogs/unreleased/216145-frontend-create-jira-import-user-mapping-form.yml
@@ -0,0 +1,5 @@
+---
+title: Add Jira Importer user mapping form
+merge_request: 33320
+author:
+type: added
diff --git a/changelogs/unreleased/216145-graphql-import.yml b/changelogs/unreleased/216145-graphql-import.yml
new file mode 100644
index 00000000000..a107b95cd4a
--- /dev/null
+++ b/changelogs/unreleased/216145-graphql-import.yml
@@ -0,0 +1,5 @@
+---
+title: Add Jira users mapping to start Jira import mutation
+merge_request: 34609
+author:
+type: added
diff --git a/changelogs/unreleased/216145-jira-users-import-endpoint.yml b/changelogs/unreleased/216145-jira-users-import-endpoint.yml
new file mode 100644
index 00000000000..54c3a130b07
--- /dev/null
+++ b/changelogs/unreleased/216145-jira-users-import-endpoint.yml
@@ -0,0 +1,5 @@
+---
+title: Create graphQL endpoint for Jira users import
+merge_request: 33501
+author:
+type: added
diff --git a/changelogs/unreleased/216145-project-members-graphql.yml b/changelogs/unreleased/216145-project-members-graphql.yml
new file mode 100644
index 00000000000..b64407b8b35
--- /dev/null
+++ b/changelogs/unreleased/216145-project-members-graphql.yml
@@ -0,0 +1,5 @@
+---
+title: Add members to project graphQL endpoint
+merge_request: 33418
+author:
+type: added
diff --git a/changelogs/unreleased/216160-fix-label-any-with-custom-sorting.yml b/changelogs/unreleased/216160-fix-label-any-with-custom-sorting.yml
new file mode 100644
index 00000000000..25b0c302cf2
--- /dev/null
+++ b/changelogs/unreleased/216160-fix-label-any-with-custom-sorting.yml
@@ -0,0 +1,5 @@
+---
+title: Fix issuable listings with any label filter
+merge_request: 31729
+author:
+type: fixed
diff --git a/changelogs/unreleased/216174-track-mr-usage.yml b/changelogs/unreleased/216174-track-mr-usage.yml
new file mode 100644
index 00000000000..90374fa85f7
--- /dev/null
+++ b/changelogs/unreleased/216174-track-mr-usage.yml
@@ -0,0 +1,5 @@
+---
+title: Track merge_requests_users usage data
+merge_request: 32562
+author:
+type: changed
diff --git a/changelogs/unreleased/216199-track-wiki-page-views.yml b/changelogs/unreleased/216199-track-wiki-page-views.yml
new file mode 100644
index 00000000000..aec95777ebf
--- /dev/null
+++ b/changelogs/unreleased/216199-track-wiki-page-views.yml
@@ -0,0 +1,5 @@
+---
+title: Track wiki page views in Snowplow
+merge_request: 35784
+author:
+type: changed
diff --git a/changelogs/unreleased/216216-add-rake-task-for-external-diffs-cleanup.yml b/changelogs/unreleased/216216-add-rake-task-for-external-diffs-cleanup.yml
new file mode 100644
index 00000000000..1cbbc96a730
--- /dev/null
+++ b/changelogs/unreleased/216216-add-rake-task-for-external-diffs-cleanup.yml
@@ -0,0 +1,5 @@
+---
+title: Add a Rake task to fix incorrectly-recorded external diffs
+merge_request: 36353
+author:
+type: fixed
diff --git a/changelogs/unreleased/216326-send-alerts-to-slack-db-settings.yml b/changelogs/unreleased/216326-send-alerts-to-slack-db-settings.yml
new file mode 100644
index 00000000000..ab77ed18444
--- /dev/null
+++ b/changelogs/unreleased/216326-send-alerts-to-slack-db-settings.yml
@@ -0,0 +1,5 @@
+---
+title: Add column for alert slack notifications
+merge_request: 33017
+author:
+type: added
diff --git a/changelogs/unreleased/216385-add-related-dashboard-links-in-metrics-dashboard-mvc1.yml b/changelogs/unreleased/216385-add-related-dashboard-links-in-metrics-dashboard-mvc1.yml
new file mode 100644
index 00000000000..1d211732e04
--- /dev/null
+++ b/changelogs/unreleased/216385-add-related-dashboard-links-in-metrics-dashboard-mvc1.yml
@@ -0,0 +1,5 @@
+---
+title: Render user-defined links in dashboard yml file on metrics dashboard
+merge_request: 32895
+author:
+type: added
diff --git a/changelogs/unreleased/216458-migrate-mr-diffs-without-transaction.yml b/changelogs/unreleased/216458-migrate-mr-diffs-without-transaction.yml
new file mode 100644
index 00000000000..94f0688e78a
--- /dev/null
+++ b/changelogs/unreleased/216458-migrate-mr-diffs-without-transaction.yml
@@ -0,0 +1,5 @@
+---
+title: 'MR diff migration: perform I/O outside of database transaction'
+merge_request: 35734
+author:
+type: performance
diff --git a/changelogs/unreleased/216631-cmd-enter-no-clear-comment-text.yml b/changelogs/unreleased/216631-cmd-enter-no-clear-comment-text.yml
new file mode 100644
index 00000000000..2087ad23402
--- /dev/null
+++ b/changelogs/unreleased/216631-cmd-enter-no-clear-comment-text.yml
@@ -0,0 +1,5 @@
+---
+title: Prevent autosave when reply comment via cmd+enter
+merge_request: 35716
+author:
+type: fixed
diff --git a/changelogs/unreleased/216640-insert-image-modal.yml b/changelogs/unreleased/216640-insert-image-modal.yml
new file mode 100644
index 00000000000..46d43736921
--- /dev/null
+++ b/changelogs/unreleased/216640-insert-image-modal.yml
@@ -0,0 +1,5 @@
+---
+title: Add ability to insert an image via SSE
+merge_request: 33029
+author:
+type: added
diff --git a/changelogs/unreleased/216677-track-static-site-editor-initializations.yml b/changelogs/unreleased/216677-track-static-site-editor-initializations.yml
new file mode 100644
index 00000000000..e8d87d70250
--- /dev/null
+++ b/changelogs/unreleased/216677-track-static-site-editor-initializations.yml
@@ -0,0 +1,5 @@
+---
+title: Track when Static Site Editor is initialized
+merge_request: 34215
+author:
+type: added
diff --git a/changelogs/unreleased/216678-sse-track-merge-requests.yml b/changelogs/unreleased/216678-sse-track-merge-requests.yml
new file mode 100644
index 00000000000..30d3f722b50
--- /dev/null
+++ b/changelogs/unreleased/216678-sse-track-merge-requests.yml
@@ -0,0 +1,5 @@
+---
+title: Track merge requests submitted by Static Site Editor
+merge_request: 34105
+author:
+type: added
diff --git a/changelogs/unreleased/216735-fix-prometheus-alerts-not-being-created.yml b/changelogs/unreleased/216735-fix-prometheus-alerts-not-being-created.yml
new file mode 100644
index 00000000000..cfcd401f6ca
--- /dev/null
+++ b/changelogs/unreleased/216735-fix-prometheus-alerts-not-being-created.yml
@@ -0,0 +1,5 @@
+---
+title: Fix prometheus alerts not being automatically created
+merge_request: 33806
+author:
+type: fixed
diff --git a/changelogs/unreleased/216749-improve-the-container-registry-ui-header-section-with-relevant-met.yml b/changelogs/unreleased/216749-improve-the-container-registry-ui-header-section-with-relevant-met.yml
new file mode 100644
index 00000000000..286453d9575
--- /dev/null
+++ b/changelogs/unreleased/216749-improve-the-container-registry-ui-header-section-with-relevant-met.yml
@@ -0,0 +1,5 @@
+---
+title: Improve Container Registry UI header
+merge_request: 32424
+author:
+type: changed
diff --git a/changelogs/unreleased/216757-add-tags-count.yml b/changelogs/unreleased/216757-add-tags-count.yml
new file mode 100644
index 00000000000..22737f7a914
--- /dev/null
+++ b/changelogs/unreleased/216757-add-tags-count.yml
@@ -0,0 +1,5 @@
+---
+title: Add tags_count to container registry api and controller
+merge_request: 32141
+author:
+type: changed
diff --git a/changelogs/unreleased/216757-include-tag-count-in-the-image-repository-list-view-of-the-contain.yml b/changelogs/unreleased/216757-include-tag-count-in-the-image-repository-list-view-of-the-contain.yml
new file mode 100644
index 00000000000..c9b7a195002
--- /dev/null
+++ b/changelogs/unreleased/216757-include-tag-count-in-the-image-repository-list-view-of-the-contain.yml
@@ -0,0 +1,5 @@
+---
+title: Include tag count in the image repository list
+merge_request: 33027
+author:
+type: changed
diff --git a/changelogs/unreleased/216785-terraform-plan-developer-access.yml b/changelogs/unreleased/216785-terraform-plan-developer-access.yml
new file mode 100644
index 00000000000..d7720cc1c13
--- /dev/null
+++ b/changelogs/unreleased/216785-terraform-plan-developer-access.yml
@@ -0,0 +1,5 @@
+---
+title: Allow developer role read-only access to Terraform state
+merge_request: 33573
+author:
+type: added
diff --git a/changelogs/unreleased/216785-use-ci-job-token-for-terraform-state-auth.yml b/changelogs/unreleased/216785-use-ci-job-token-for-terraform-state-auth.yml
new file mode 100644
index 00000000000..c7fec94b9fb
--- /dev/null
+++ b/changelogs/unreleased/216785-use-ci-job-token-for-terraform-state-auth.yml
@@ -0,0 +1,5 @@
+---
+title: Allow CI_JOB_TOKEN for authenticating to the Terraform state API
+merge_request: 34618
+author:
+type: added
diff --git a/changelogs/unreleased/216797-style-toastui-menus.yml b/changelogs/unreleased/216797-style-toastui-menus.yml
new file mode 100644
index 00000000000..0f87d9cef1e
--- /dev/null
+++ b/changelogs/unreleased/216797-style-toastui-menus.yml
@@ -0,0 +1,5 @@
+---
+title: Style ToastUI contextual menus
+merge_request: 33719
+author:
+type: changed
diff --git a/changelogs/unreleased/216834-frontmatter-wysiwyg-removal.yml b/changelogs/unreleased/216834-frontmatter-wysiwyg-removal.yml
new file mode 100644
index 00000000000..2885d9d5e26
--- /dev/null
+++ b/changelogs/unreleased/216834-frontmatter-wysiwyg-removal.yml
@@ -0,0 +1,5 @@
+---
+title: Update Static Site Editor WYSIWYG mode to hide front matter
+merge_request: 33441
+author:
+type: added
diff --git a/changelogs/unreleased/216835-instrument-db-calls.yml b/changelogs/unreleased/216835-instrument-db-calls.yml
new file mode 100644
index 00000000000..6e1af13b4c7
--- /dev/null
+++ b/changelogs/unreleased/216835-instrument-db-calls.yml
@@ -0,0 +1,6 @@
+---
+title: Add number of database calls to Prometheus metrics and logs for sidekiq and
+ request
+merge_request: 32131
+author:
+type: added
diff --git a/changelogs/unreleased/216865-confirm-leave-site.yml b/changelogs/unreleased/216865-confirm-leave-site.yml
new file mode 100644
index 00000000000..3d6b47db91f
--- /dev/null
+++ b/changelogs/unreleased/216865-confirm-leave-site.yml
@@ -0,0 +1,5 @@
+---
+title: Display confirmation modal when user exits SSE and there are unsaved changes
+merge_request: 33103
+author:
+type: added
diff --git a/changelogs/unreleased/216871-snippets-author-can-t-be-blank-error.yml b/changelogs/unreleased/216871-snippets-author-can-t-be-blank-error.yml
new file mode 100644
index 00000000000..f4a06320e47
--- /dev/null
+++ b/changelogs/unreleased/216871-snippets-author-can-t-be-blank-error.yml
@@ -0,0 +1,5 @@
+---
+title: Fallback to lowest visibility level in snippet visibility radio
+merge_request: 31847
+author: Jacopo Beschi @jacopo-beschi
+type: fixed
diff --git a/changelogs/unreleased/216880-frontend-add-sticky-issue-titles.yml b/changelogs/unreleased/216880-frontend-add-sticky-issue-titles.yml
new file mode 100644
index 00000000000..1194adab7a3
--- /dev/null
+++ b/changelogs/unreleased/216880-frontend-add-sticky-issue-titles.yml
@@ -0,0 +1,5 @@
+---
+title: Add sticky title on Issue pages
+merge_request: 33983
+author:
+type: added
diff --git a/changelogs/unreleased/216880-frontend-url-hash-offset.yml b/changelogs/unreleased/216880-frontend-url-hash-offset.yml
new file mode 100644
index 00000000000..d1e2c0a5396
--- /dev/null
+++ b/changelogs/unreleased/216880-frontend-url-hash-offset.yml
@@ -0,0 +1,5 @@
+---
+title: Fix Issue sticky title URL hash offset
+merge_request: 34764
+author:
+type: fixed
diff --git a/changelogs/unreleased/216908-pass-limit-and-offset-when-searching-for-commits.yml b/changelogs/unreleased/216908-pass-limit-and-offset-when-searching-for-commits.yml
new file mode 100644
index 00000000000..4b2b857cb85
--- /dev/null
+++ b/changelogs/unreleased/216908-pass-limit-and-offset-when-searching-for-commits.yml
@@ -0,0 +1,5 @@
+---
+title: Improve performance of commit search by limiting the number of results requested
+merge_request: 32260
+author:
+type: performance
diff --git a/changelogs/unreleased/216931-convert-the-image-tag-ui-from-a-table-to-a-list-view-component.yml b/changelogs/unreleased/216931-convert-the-image-tag-ui-from-a-table-to-a-list-view-component.yml
new file mode 100644
index 00000000000..75f61bf13cc
--- /dev/null
+++ b/changelogs/unreleased/216931-convert-the-image-tag-ui-from-a-table-to-a-list-view-component.yml
@@ -0,0 +1,5 @@
+---
+title: Convert the Image tag UI from a table to a list view
+merge_request: 35138
+author:
+type: changed
diff --git a/changelogs/unreleased/216939-remove-async-mr-check-ff.yml b/changelogs/unreleased/216939-remove-async-mr-check-ff.yml
new file mode 100644
index 00000000000..b66cb44de4b
--- /dev/null
+++ b/changelogs/unreleased/216939-remove-async-mr-check-ff.yml
@@ -0,0 +1,5 @@
+---
+title: Remove async_merge_request_check_mergeability feature flag
+merge_request: 33917
+author:
+type: changed
diff --git a/changelogs/unreleased/216962-add-an-expandable-tag-detail-view-to-the-image-repository-detail-v.yml b/changelogs/unreleased/216962-add-an-expandable-tag-detail-view-to-the-image-repository-detail-v.yml
new file mode 100644
index 00000000000..62f49534864
--- /dev/null
+++ b/changelogs/unreleased/216962-add-an-expandable-tag-detail-view-to-the-image-repository-detail-v.yml
@@ -0,0 +1,5 @@
+---
+title: Add details rows to Container Registry Tags List
+merge_request: 36036
+author:
+type: changed
diff --git a/changelogs/unreleased/217034-auto-creation-of-issues-for-alerts-off-by-default.yml b/changelogs/unreleased/217034-auto-creation-of-issues-for-alerts-off-by-default.yml
new file mode 100644
index 00000000000..c109a644871
--- /dev/null
+++ b/changelogs/unreleased/217034-auto-creation-of-issues-for-alerts-off-by-default.yml
@@ -0,0 +1,5 @@
+---
+title: Turn off alert issue creation by default
+merge_request: 34107
+author:
+type: added
diff --git a/changelogs/unreleased/217105-remove-FF-hide_token_from_runners_api.yml b/changelogs/unreleased/217105-remove-FF-hide_token_from_runners_api.yml
new file mode 100644
index 00000000000..45aea72ae24
--- /dev/null
+++ b/changelogs/unreleased/217105-remove-FF-hide_token_from_runners_api.yml
@@ -0,0 +1,5 @@
+---
+title: Remove FF hide_token_from_runners_api
+merge_request: 33947
+author:
+type: other
diff --git a/changelogs/unreleased/217168-close-open-reply-input-fields-in-the-design-view-sidebar-when-leav.yml b/changelogs/unreleased/217168-close-open-reply-input-fields-in-the-design-view-sidebar-when-leav.yml
new file mode 100644
index 00000000000..b3f61c924fa
--- /dev/null
+++ b/changelogs/unreleased/217168-close-open-reply-input-fields-in-the-design-view-sidebar-when-leav.yml
@@ -0,0 +1,6 @@
+---
+title: Close open reply input fields in the design view sidebar when leaving a new
+ comment
+merge_request: 33587
+author:
+type: added
diff --git a/changelogs/unreleased/217170-when-clicking-multiple-times-to-leave-a-single-comment-the-input-f.yml b/changelogs/unreleased/217170-when-clicking-multiple-times-to-leave-a-single-comment-the-input-f.yml
new file mode 100644
index 00000000000..353f4910823
--- /dev/null
+++ b/changelogs/unreleased/217170-when-clicking-multiple-times-to-leave-a-single-comment-the-input-f.yml
@@ -0,0 +1,6 @@
+---
+title: When clicking multiple times to leave a single comment, the input field should
+ remain focused
+merge_request: 33742
+author:
+type: fixed
diff --git a/changelogs/unreleased/217362-move-configure-stage-usage-activity-to-ce.yml b/changelogs/unreleased/217362-move-configure-stage-usage-activity-to-ce.yml
new file mode 100644
index 00000000000..909ffa115f0
--- /dev/null
+++ b/changelogs/unreleased/217362-move-configure-stage-usage-activity-to-ce.yml
@@ -0,0 +1,5 @@
+---
+title: Move monitor stage usage activity to CE
+merge_request: 36067
+author:
+type: changed
diff --git a/changelogs/unreleased/217362-move-create-stage-usage-activity-to-ce.yml b/changelogs/unreleased/217362-move-create-stage-usage-activity-to-ce.yml
new file mode 100644
index 00000000000..ce2a40e27ad
--- /dev/null
+++ b/changelogs/unreleased/217362-move-create-stage-usage-activity-to-ce.yml
@@ -0,0 +1,5 @@
+---
+title: Move create stage usage activity to CE
+merge_request: 36086
+author:
+type: changed
diff --git a/changelogs/unreleased/217362-move-manage-stage-usage-activity-to-ce.yml b/changelogs/unreleased/217362-move-manage-stage-usage-activity-to-ce.yml
new file mode 100644
index 00000000000..b31db0b7ccc
--- /dev/null
+++ b/changelogs/unreleased/217362-move-manage-stage-usage-activity-to-ce.yml
@@ -0,0 +1,5 @@
+---
+title: Move manage stage usage activity to CE
+merge_request: 36089
+author:
+type: changed
diff --git a/changelogs/unreleased/217362-move-plan-stage-usage-activity-to-ce.yml b/changelogs/unreleased/217362-move-plan-stage-usage-activity-to-ce.yml
new file mode 100644
index 00000000000..6bfffbc19a3
--- /dev/null
+++ b/changelogs/unreleased/217362-move-plan-stage-usage-activity-to-ce.yml
@@ -0,0 +1,5 @@
+---
+title: Move plan stage usage activity to CE
+merge_request: 36087
+author:
+type: changed
diff --git a/changelogs/unreleased/217362-move-release-stage-usage-activity-to-ce.yml b/changelogs/unreleased/217362-move-release-stage-usage-activity-to-ce.yml
new file mode 100644
index 00000000000..c253c88d762
--- /dev/null
+++ b/changelogs/unreleased/217362-move-release-stage-usage-activity-to-ce.yml
@@ -0,0 +1,5 @@
+---
+title: Move release stage usage activity to CE
+merge_request: 36083
+author:
+type: changed
diff --git a/changelogs/unreleased/217362-move-verify-stage-usage-activity-to-ce.yml b/changelogs/unreleased/217362-move-verify-stage-usage-activity-to-ce.yml
new file mode 100644
index 00000000000..d6117d88a6f
--- /dev/null
+++ b/changelogs/unreleased/217362-move-verify-stage-usage-activity-to-ce.yml
@@ -0,0 +1,5 @@
+---
+title: Move verify stage usage activity to CE
+merge_request: 36090
+author:
+type: changed
diff --git a/changelogs/unreleased/217362-restructure-usage-ping-add-usage-activity-to-ce.yml b/changelogs/unreleased/217362-restructure-usage-ping-add-usage-activity-to-ce.yml
new file mode 100644
index 00000000000..45e767c13e6
--- /dev/null
+++ b/changelogs/unreleased/217362-restructure-usage-ping-add-usage-activity-to-ce.yml
@@ -0,0 +1,5 @@
+---
+title: Move Usage activity by stage for Configure to Core
+merge_request: 33672
+author:
+type: changed
diff --git a/changelogs/unreleased/217366-expose-jira-successfully-imported-issues-count-in-graphql.yml b/changelogs/unreleased/217366-expose-jira-successfully-imported-issues-count-in-graphql.yml
new file mode 100644
index 00000000000..da139df5643
--- /dev/null
+++ b/changelogs/unreleased/217366-expose-jira-successfully-imported-issues-count-in-graphql.yml
@@ -0,0 +1,5 @@
+---
+title: Expose Jira imported issues count in GraphQL
+merge_request: 32580
+author:
+type: added
diff --git a/changelogs/unreleased/217392-update-workhorse-version.yml b/changelogs/unreleased/217392-update-workhorse-version.yml
new file mode 100644
index 00000000000..7e7cf835b7a
--- /dev/null
+++ b/changelogs/unreleased/217392-update-workhorse-version.yml
@@ -0,0 +1,5 @@
+---
+title: Update GITLAB_WORKHORSE_VERSION to 8.37.0
+merge_request: 36988
+author:
+type: other
diff --git a/changelogs/unreleased/217566-add-warning-of-potential-data-loss-on-elastic-stack-upgrade-2.yml b/changelogs/unreleased/217566-add-warning-of-potential-data-loss-on-elastic-stack-upgrade-2.yml
new file mode 100644
index 00000000000..9306f5a9144
--- /dev/null
+++ b/changelogs/unreleased/217566-add-warning-of-potential-data-loss-on-elastic-stack-upgrade-2.yml
@@ -0,0 +1,5 @@
+---
+title: Add warning popup for Elastic Stack update
+merge_request: 31972
+author:
+type: added
diff --git a/changelogs/unreleased/217570-improve-performance-for-blame-api.yml b/changelogs/unreleased/217570-improve-performance-for-blame-api.yml
new file mode 100644
index 00000000000..390e19dc333
--- /dev/null
+++ b/changelogs/unreleased/217570-improve-performance-for-blame-api.yml
@@ -0,0 +1,5 @@
+---
+title: Lazy load commit_date and authored_date on Commit
+merge_request: 34181
+author:
+type: performance
diff --git a/changelogs/unreleased/217587-ignore-title-description-from-services.yml b/changelogs/unreleased/217587-ignore-title-description-from-services.yml
new file mode 100644
index 00000000000..6602b19e979
--- /dev/null
+++ b/changelogs/unreleased/217587-ignore-title-description-from-services.yml
@@ -0,0 +1,5 @@
+---
+title: Remove the ability to customize the title and description of some integrations (Bugzilla, Custom Issue Tracker, Redmine, and YouTrack).
+merge_request: 33298
+author:
+type: removed
diff --git a/changelogs/unreleased/217616-fix-note-confidential.yml b/changelogs/unreleased/217616-fix-note-confidential.yml
new file mode 100644
index 00000000000..d9e1f8a9b26
--- /dev/null
+++ b/changelogs/unreleased/217616-fix-note-confidential.yml
@@ -0,0 +1,5 @@
+---
+title: Don't display confidential note icon on confidential issue public notes
+merge_request: 32571
+author:
+type: fixed
diff --git a/changelogs/unreleased/217648-add-no-graph-empty-state-for-dag.yml b/changelogs/unreleased/217648-add-no-graph-empty-state-for-dag.yml
new file mode 100644
index 00000000000..67032fb5be8
--- /dev/null
+++ b/changelogs/unreleased/217648-add-no-graph-empty-state-for-dag.yml
@@ -0,0 +1,5 @@
+---
+title: Resolve Add no graph empty state for DAG
+merge_request: 35053
+author:
+type: changed
diff --git a/changelogs/unreleased/217666-prometheus-api-client.yml b/changelogs/unreleased/217666-prometheus-api-client.yml
new file mode 100644
index 00000000000..cb1f4eb193b
--- /dev/null
+++ b/changelogs/unreleased/217666-prometheus-api-client.yml
@@ -0,0 +1,5 @@
+---
+title: Include available instance memory in usage ping
+merge_request: 32315
+author:
+type: other
diff --git a/changelogs/unreleased/217670-alerts-count.yml b/changelogs/unreleased/217670-alerts-count.yml
new file mode 100644
index 00000000000..5a3995be0ce
--- /dev/null
+++ b/changelogs/unreleased/217670-alerts-count.yml
@@ -0,0 +1,5 @@
+---
+title: Organize alerts by status tabs
+merge_request: 32582
+author:
+type: added
diff --git a/changelogs/unreleased/217673-keyset-paginate-notes-backend-only.yml b/changelogs/unreleased/217673-keyset-paginate-notes-backend-only.yml
new file mode 100644
index 00000000000..dac9f4cb57b
--- /dev/null
+++ b/changelogs/unreleased/217673-keyset-paginate-notes-backend-only.yml
@@ -0,0 +1,5 @@
+---
+title: Paginate the notes incremental fetch endpoint
+merge_request: 34628
+author:
+type: performance
diff --git a/changelogs/unreleased/217680-health-metric-instrumentation.yml b/changelogs/unreleased/217680-health-metric-instrumentation.yml
new file mode 100644
index 00000000000..b1f55701c08
--- /dev/null
+++ b/changelogs/unreleased/217680-health-metric-instrumentation.yml
@@ -0,0 +1,5 @@
+---
+title: Track Sentry error status updates with dedicated actions
+merge_request: 33623
+author:
+type: changed
diff --git a/changelogs/unreleased/217680-health-metrics-instrumentation.yml b/changelogs/unreleased/217680-health-metrics-instrumentation.yml
new file mode 100644
index 00000000000..77ae44aaf38
--- /dev/null
+++ b/changelogs/unreleased/217680-health-metrics-instrumentation.yml
@@ -0,0 +1,5 @@
+---
+title: Monitor:Health metrics instrumenation
+merge_request: 32846
+author:
+type: added
diff --git a/changelogs/unreleased/217692-design-view-highlight-focused-design-pins-follow-up.yml b/changelogs/unreleased/217692-design-view-highlight-focused-design-pins-follow-up.yml
new file mode 100644
index 00000000000..27b6d0dd58c
--- /dev/null
+++ b/changelogs/unreleased/217692-design-view-highlight-focused-design-pins-follow-up.yml
@@ -0,0 +1,5 @@
+---
+title: Add opacity transition to active design discussion pins
+merge_request: 33493
+author:
+type: other
diff --git a/changelogs/unreleased/217736-add-related-dashboard-links-in-metrics-dashboard-mvc2.yml b/changelogs/unreleased/217736-add-related-dashboard-links-in-metrics-dashboard-mvc2.yml
new file mode 100644
index 00000000000..8369dbc3def
--- /dev/null
+++ b/changelogs/unreleased/217736-add-related-dashboard-links-in-metrics-dashboard-mvc2.yml
@@ -0,0 +1,5 @@
+---
+title: Add time range to user-defined links in metrics dashboard
+merge_request: 33663
+author:
+type: added
diff --git a/changelogs/unreleased/217743-match-commits-filter-author-button-to-spec.yml b/changelogs/unreleased/217743-match-commits-filter-author-button-to-spec.yml
new file mode 100644
index 00000000000..c90ceb3190b
--- /dev/null
+++ b/changelogs/unreleased/217743-match-commits-filter-author-button-to-spec.yml
@@ -0,0 +1,5 @@
+---
+title: Make commits author button confirm to Pajamas specs
+merge_request: 32821
+author:
+type: fixed
diff --git a/changelogs/unreleased/217748-pipeline-index-endpoint-performance.yml b/changelogs/unreleased/217748-pipeline-index-endpoint-performance.yml
new file mode 100644
index 00000000000..d9a68248010
--- /dev/null
+++ b/changelogs/unreleased/217748-pipeline-index-endpoint-performance.yml
@@ -0,0 +1,5 @@
+---
+title: Add project_id, user_id, status, ref index to ci_pipelines
+merge_request: 33290
+author:
+type: performance
diff --git a/changelogs/unreleased/217758-reduce-metrics-dashboard-loading.yml b/changelogs/unreleased/217758-reduce-metrics-dashboard-loading.yml
new file mode 100644
index 00000000000..d018c3bf7bb
--- /dev/null
+++ b/changelogs/unreleased/217758-reduce-metrics-dashboard-loading.yml
@@ -0,0 +1,6 @@
+---
+title: Replace initial dashboard loading state with a loading spinner, show dashboard
+ skeleton earlier with smaller loading indicators
+merge_request: 36399
+author:
+type: changed
diff --git a/changelogs/unreleased/217768-surface-link-to-chart.yml b/changelogs/unreleased/217768-surface-link-to-chart.yml
new file mode 100644
index 00000000000..3ebd03ac861
--- /dev/null
+++ b/changelogs/unreleased/217768-surface-link-to-chart.yml
@@ -0,0 +1,5 @@
+---
+title: Surface metrics charts on the alert detail page
+merge_request: 35044
+author:
+type: added
diff --git a/changelogs/unreleased/217786-snippet-blobs.yml b/changelogs/unreleased/217786-snippet-blobs.yml
new file mode 100644
index 00000000000..3c812eb5704
--- /dev/null
+++ b/changelogs/unreleased/217786-snippet-blobs.yml
@@ -0,0 +1,5 @@
+---
+title: Accept multiple blobs in snippets
+merge_request: 35605
+author:
+type: changed
diff --git a/changelogs/unreleased/217803-follow-up-from-resolve-distribute-daily-cron-schedules-out-over-th.yml b/changelogs/unreleased/217803-follow-up-from-resolve-distribute-daily-cron-schedules-out-over-th.yml
new file mode 100644
index 00000000000..6f2106fdd81
--- /dev/null
+++ b/changelogs/unreleased/217803-follow-up-from-resolve-distribute-daily-cron-schedules-out-over-th.yml
@@ -0,0 +1,5 @@
+---
+title: Fix UI quirks with pipeline schedule cron options
+merge_request: 36471
+author:
+type: changed
diff --git a/changelogs/unreleased/217816-add-evidence-to-releases-graphql-endpoint.yml b/changelogs/unreleased/217816-add-evidence-to-releases-graphql-endpoint.yml
new file mode 100644
index 00000000000..79ffa011f63
--- /dev/null
+++ b/changelogs/unreleased/217816-add-evidence-to-releases-graphql-endpoint.yml
@@ -0,0 +1,5 @@
+---
+title: Add Evidence to Releases GraphQL endpoint
+merge_request: 33254
+author:
+type: added
diff --git a/changelogs/unreleased/217834-remove-FF-ci_dependency_variables.yml b/changelogs/unreleased/217834-remove-FF-ci_dependency_variables.yml
new file mode 100644
index 00000000000..d237974389a
--- /dev/null
+++ b/changelogs/unreleased/217834-remove-FF-ci_dependency_variables.yml
@@ -0,0 +1,5 @@
+---
+title: Enable CI Inheriting Env Variables feature
+merge_request: 34495
+author:
+type: added
diff --git a/changelogs/unreleased/217934-snippet-description-files.yml b/changelogs/unreleased/217934-snippet-description-files.yml
new file mode 100644
index 00000000000..193188beeb8
--- /dev/null
+++ b/changelogs/unreleased/217934-snippet-description-files.yml
@@ -0,0 +1,5 @@
+---
+title: Send information about attached files to the GraphQL mutation
+merge_request: 34221
+author:
+type: fixed
diff --git a/changelogs/unreleased/217936-validate-the-size-of-the-value-for-instance-level-variables.yml b/changelogs/unreleased/217936-validate-the-size-of-the-value-for-instance-level-variables.yml
new file mode 100644
index 00000000000..b62aa0a8c20
--- /dev/null
+++ b/changelogs/unreleased/217936-validate-the-size-of-the-value-for-instance-level-variables.yml
@@ -0,0 +1,5 @@
+---
+title: Add value length validations for instance level variable
+merge_request: 32303
+author:
+type: fixed
diff --git a/changelogs/unreleased/217985-use-glinfinitescroll-s-default-slot-in-the-project-selector-vue-co.yml b/changelogs/unreleased/217985-use-glinfinitescroll-s-default-slot-in-the-project-selector-vue-co.yml
new file mode 100644
index 00000000000..2d06af04985
--- /dev/null
+++ b/changelogs/unreleased/217985-use-glinfinitescroll-s-default-slot-in-the-project-selector-vue-co.yml
@@ -0,0 +1,5 @@
+---
+title: Make project selector in various dashboard more translatable
+merge_request: 33771
+author:
+type: other
diff --git a/changelogs/unreleased/218004-validate-the-not-null-constraint-on-file-store-columns.yml b/changelogs/unreleased/218004-validate-the-not-null-constraint-on-file-store-columns.yml
new file mode 100644
index 00000000000..cc41bcb3abe
--- /dev/null
+++ b/changelogs/unreleased/218004-validate-the-not-null-constraint-on-file-store-columns.yml
@@ -0,0 +1,5 @@
+---
+title: Validate the existing not null constraints on columns for ci_job_artifacts, lfs_objects, and uploads tables.
+merge_request: 34568
+author:
+type: other
diff --git a/changelogs/unreleased/218007-fix-incomplete-kubernetes-cluster-status-list.yml b/changelogs/unreleased/218007-fix-incomplete-kubernetes-cluster-status-list.yml
new file mode 100644
index 00000000000..bdb87ceee3b
--- /dev/null
+++ b/changelogs/unreleased/218007-fix-incomplete-kubernetes-cluster-status-list.yml
@@ -0,0 +1,5 @@
+---
+title: Resolve Fix Incomplete Kubernetes Cluster Status List
+merge_request: 33344
+author:
+type: fixed
diff --git a/changelogs/unreleased/218025-xff-is-a-400-error.yml b/changelogs/unreleased/218025-xff-is-a-400-error.yml
new file mode 100644
index 00000000000..737fc01315e
--- /dev/null
+++ b/changelogs/unreleased/218025-xff-is-a-400-error.yml
@@ -0,0 +1,5 @@
+---
+title: Convert IP spoofing errors into client errors
+merge_request: 33280
+author:
+type: other
diff --git a/changelogs/unreleased/218026-add-falco-documentation.yml b/changelogs/unreleased/218026-add-falco-documentation.yml
new file mode 100644
index 00000000000..15620582d31
--- /dev/null
+++ b/changelogs/unreleased/218026-add-falco-documentation.yml
@@ -0,0 +1,5 @@
+---
+title: Add Falco to the managed cluster apps template
+merge_request: 32779
+author:
+type: added
diff --git a/changelogs/unreleased/218036-cannot-delete-account-on-gitlab-com.yml b/changelogs/unreleased/218036-cannot-delete-account-on-gitlab-com.yml
new file mode 100644
index 00000000000..fa95f5e36c5
--- /dev/null
+++ b/changelogs/unreleased/218036-cannot-delete-account-on-gitlab-com.yml
@@ -0,0 +1,5 @@
+---
+title: Add index to issues and epics on last_edited_by_id
+merge_request: 33075
+author:
+type: performance
diff --git a/changelogs/unreleased/218040-cablett-graphql-issue-id.yml b/changelogs/unreleased/218040-cablett-graphql-issue-id.yml
new file mode 100644
index 00000000000..8d697d554d8
--- /dev/null
+++ b/changelogs/unreleased/218040-cablett-graphql-issue-id.yml
@@ -0,0 +1,5 @@
+---
+title: Expose issue ID via GraphQL
+merge_request: 36412
+author:
+type: changed
diff --git a/changelogs/unreleased/218045-feature-flag-remove-feature-flag-for-create-issue-from-alert-detai.yml b/changelogs/unreleased/218045-feature-flag-remove-feature-flag-for-create-issue-from-alert-detai.yml
new file mode 100644
index 00000000000..65b51037ce6
--- /dev/null
+++ b/changelogs/unreleased/218045-feature-flag-remove-feature-flag-for-create-issue-from-alert-detai.yml
@@ -0,0 +1,5 @@
+---
+title: Add button to create an issue from an alert management alert
+merge_request: 33221
+author:
+type: added
diff --git a/changelogs/unreleased/218045-remove-create-issue-feature-flag-FE.yml b/changelogs/unreleased/218045-remove-create-issue-feature-flag-FE.yml
new file mode 100644
index 00000000000..db500c26f45
--- /dev/null
+++ b/changelogs/unreleased/218045-remove-create-issue-feature-flag-FE.yml
@@ -0,0 +1,5 @@
+---
+title: Create issue from alert
+merge_request: 33213
+author:
+type: added
diff --git a/changelogs/unreleased/218165-add-note-no-extend-ecs.yml b/changelogs/unreleased/218165-add-note-no-extend-ecs.yml
new file mode 100644
index 00000000000..7952028f97e
--- /dev/null
+++ b/changelogs/unreleased/218165-add-note-no-extend-ecs.yml
@@ -0,0 +1,5 @@
+---
+title: Add note to ECS CI template
+merge_request: 32597
+author:
+type: added
diff --git a/changelogs/unreleased/218230-bugfix-save-wiki-page-modifications-with-certain-characters.yml b/changelogs/unreleased/218230-bugfix-save-wiki-page-modifications-with-certain-characters.yml
new file mode 100644
index 00000000000..5be7a069ece
--- /dev/null
+++ b/changelogs/unreleased/218230-bugfix-save-wiki-page-modifications-with-certain-characters.yml
@@ -0,0 +1,5 @@
+---
+title: Allow wiki pages with +<> characters in their title to be saved
+merge_request: 33803
+author:
+type: fixed
diff --git a/changelogs/unreleased/218250-project-level-integration-implement-project-level-setting-selector.yml b/changelogs/unreleased/218250-project-level-integration-implement-project-level-setting-selector.yml
new file mode 100644
index 00000000000..39853524bf5
--- /dev/null
+++ b/changelogs/unreleased/218250-project-level-integration-implement-project-level-setting-selector.yml
@@ -0,0 +1,5 @@
+---
+title: Add override selector for project-level integrations
+merge_request: 34742
+author:
+type: added
diff --git a/changelogs/unreleased/218257-service-templates-page-disable-service-template-when-instance-leve.yml b/changelogs/unreleased/218257-service-templates-page-disable-service-template-when-instance-leve.yml
new file mode 100644
index 00000000000..a00b3c466e7
--- /dev/null
+++ b/changelogs/unreleased/218257-service-templates-page-disable-service-template-when-instance-leve.yml
@@ -0,0 +1,5 @@
+---
+title: Add UI to disable Service template when instance-level integration is active
+merge_request: 33490
+author:
+type: changed
diff --git a/changelogs/unreleased/218287-release-evidence-is-not-being-collected-if-release-is-created-via-.yml b/changelogs/unreleased/218287-release-evidence-is-not-being-collected-if-release-is-created-via-.yml
new file mode 100644
index 00000000000..101b8c60287
--- /dev/null
+++ b/changelogs/unreleased/218287-release-evidence-is-not-being-collected-if-release-is-created-via-.yml
@@ -0,0 +1,5 @@
+---
+title: Fix creating release evidence if release is created via UI
+merge_request: 32441
+author:
+type: fixed
diff --git a/changelogs/unreleased/218340-graphql-haspreviouspage-and-hasnextpage.yml b/changelogs/unreleased/218340-graphql-haspreviouspage-and-hasnextpage.yml
new file mode 100644
index 00000000000..075caecc1c7
--- /dev/null
+++ b/changelogs/unreleased/218340-graphql-haspreviouspage-and-hasnextpage.yml
@@ -0,0 +1,5 @@
+---
+title: GraphQL hasNextPage and hasPreviousPage return correct values
+merge_request: 32476
+author:
+type: fixed
diff --git a/changelogs/unreleased/218414-refine-sast-analyzer-language-detection.yml b/changelogs/unreleased/218414-refine-sast-analyzer-language-detection.yml
new file mode 100644
index 00000000000..b385ee3a25c
--- /dev/null
+++ b/changelogs/unreleased/218414-refine-sast-analyzer-language-detection.yml
@@ -0,0 +1,5 @@
+---
+title: Refine SAST language detection by frameworks
+merge_request: 33226
+author:
+type: changed
diff --git a/changelogs/unreleased/218464-expiration-policy-defaults.yml b/changelogs/unreleased/218464-expiration-policy-defaults.yml
new file mode 100644
index 00000000000..ac25d58b034
--- /dev/null
+++ b/changelogs/unreleased/218464-expiration-policy-defaults.yml
@@ -0,0 +1,5 @@
+---
+title: Update container expiration policy database defaults
+merge_request: 32600
+author:
+type: fixed
diff --git a/changelogs/unreleased/218472-gitlab-ci-linting.yml b/changelogs/unreleased/218472-gitlab-ci-linting.yml
new file mode 100644
index 00000000000..0fc937a0a5d
--- /dev/null
+++ b/changelogs/unreleased/218472-gitlab-ci-linting.yml
@@ -0,0 +1,5 @@
+---
+title: Add support for linting based on schemas in WebIDE
+merge_request: 35838
+author:
+type: added
diff --git a/changelogs/unreleased/218510-hide-copy-btn-for-rendering-error.yml b/changelogs/unreleased/218510-hide-copy-btn-for-rendering-error.yml
new file mode 100644
index 00000000000..fa212bf2121
--- /dev/null
+++ b/changelogs/unreleased/218510-hide-copy-btn-for-rendering-error.yml
@@ -0,0 +1,5 @@
+---
+title: Hid copy contents button when blob has rendering error
+merge_request: 32632
+author:
+type: fixed
diff --git a/changelogs/unreleased/218526_backstage_remove_gitlab_issue_tracker_service_records.yml b/changelogs/unreleased/218526_backstage_remove_gitlab_issue_tracker_service_records.yml
new file mode 100644
index 00000000000..c774e3d5397
--- /dev/null
+++ b/changelogs/unreleased/218526_backstage_remove_gitlab_issue_tracker_service_records.yml
@@ -0,0 +1,5 @@
+---
+title: Clean up GitlabIssueTrackerService database records
+merge_request: 35221
+author:
+type: other
diff --git a/changelogs/unreleased/218560-allow-generic-endpoint-to-receive-alerts-from-external-prometheus.yml b/changelogs/unreleased/218560-allow-generic-endpoint-to-receive-alerts-from-external-prometheus.yml
new file mode 100644
index 00000000000..0713e96b714
--- /dev/null
+++ b/changelogs/unreleased/218560-allow-generic-endpoint-to-receive-alerts-from-external-prometheus.yml
@@ -0,0 +1,5 @@
+---
+title: Allow generic endpoint to receive alerts from external Prometheus
+merge_request: 32676
+author:
+type: added
diff --git a/changelogs/unreleased/218569-dont-show-import-from-jira-button-for-non-entitled-users.yml b/changelogs/unreleased/218569-dont-show-import-from-jira-button-for-non-entitled-users.yml
new file mode 100644
index 00000000000..da712f0aa59
--- /dev/null
+++ b/changelogs/unreleased/218569-dont-show-import-from-jira-button-for-non-entitled-users.yml
@@ -0,0 +1,5 @@
+---
+title: Hide "Import from Jira" option from non-entitled users
+merge_request: 32685
+author:
+type: fixed
diff --git a/changelogs/unreleased/218582-fix-artifact-downloads-without-new-route.yml b/changelogs/unreleased/218582-fix-artifact-downloads-without-new-route.yml
new file mode 100644
index 00000000000..758b08c3a42
--- /dev/null
+++ b/changelogs/unreleased/218582-fix-artifact-downloads-without-new-route.yml
@@ -0,0 +1,5 @@
+---
+title: Fix 404 when downloading a non-archive artifact
+merge_request: 32811
+author:
+type: fixed
diff --git a/changelogs/unreleased/218648-remove-jira-httperror-non-actionable-exceptions.yml b/changelogs/unreleased/218648-remove-jira-httperror-non-actionable-exceptions.yml
new file mode 100644
index 00000000000..5ac63075c1f
--- /dev/null
+++ b/changelogs/unreleased/218648-remove-jira-httperror-non-actionable-exceptions.yml
@@ -0,0 +1,5 @@
+---
+title: Less verbose JiraService error logs
+merge_request: 32847
+author:
+type: other
diff --git a/changelogs/unreleased/218703-fix-api-projects-search-n-plus-1.yml b/changelogs/unreleased/218703-fix-api-projects-search-n-plus-1.yml
new file mode 100644
index 00000000000..c5587a8be8d
--- /dev/null
+++ b/changelogs/unreleased/218703-fix-api-projects-search-n-plus-1.yml
@@ -0,0 +1,5 @@
+---
+title: Resolve N+1 in Search API projects scope
+merge_request: 35833
+author:
+type: performance
diff --git a/changelogs/unreleased/218707-search-criteria-for-alert-status-counts.yml b/changelogs/unreleased/218707-search-criteria-for-alert-status-counts.yml
new file mode 100644
index 00000000000..a556ae4b924
--- /dev/null
+++ b/changelogs/unreleased/218707-search-criteria-for-alert-status-counts.yml
@@ -0,0 +1,5 @@
+---
+title: Add search argument for AlertStatusCountsResolver
+merge_request: 34596
+author:
+type: added
diff --git a/changelogs/unreleased/218716-iterate-on-epic-tree-card-spacing.yml b/changelogs/unreleased/218716-iterate-on-epic-tree-card-spacing.yml
new file mode 100644
index 00000000000..843d27d1cea
--- /dev/null
+++ b/changelogs/unreleased/218716-iterate-on-epic-tree-card-spacing.yml
@@ -0,0 +1,5 @@
+---
+title: Reduced padding and increased emphasis of titles within the epic tree
+merge_request: 32873
+author:
+type: other
diff --git a/changelogs/unreleased/218733-add-manual-rollout-resource-group.yml b/changelogs/unreleased/218733-add-manual-rollout-resource-group.yml
new file mode 100644
index 00000000000..926db344ade
--- /dev/null
+++ b/changelogs/unreleased/218733-add-manual-rollout-resource-group.yml
@@ -0,0 +1,6 @@
+---
+title: Prevent multiple Auto DevOps deployment jobs running concurrently when using
+ manual rollout
+merge_request: 32824
+author:
+type: fixed
diff --git a/changelogs/unreleased/218737-rename-container-registry-expiration-policies-to-cleanup-policy-fo.yml b/changelogs/unreleased/218737-rename-container-registry-expiration-policies-to-cleanup-policy-fo.yml
new file mode 100644
index 00000000000..3262111b17b
--- /dev/null
+++ b/changelogs/unreleased/218737-rename-container-registry-expiration-policies-to-cleanup-policy-fo.yml
@@ -0,0 +1,5 @@
+---
+title: Rename Container Expiration Policies to Cleanup policy for tags
+merge_request: 35315
+author:
+type: changed
diff --git a/changelogs/unreleased/218756-feature-flag-enable-sectional-codeowner.yml b/changelogs/unreleased/218756-feature-flag-enable-sectional-codeowner.yml
new file mode 100644
index 00000000000..788421cb7bd
--- /dev/null
+++ b/changelogs/unreleased/218756-feature-flag-enable-sectional-codeowner.yml
@@ -0,0 +1,5 @@
+---
+title: Enable feature flag 'sectional_codeowners' Sections for Code Owners
+merge_request: 36902
+author:
+type: added
diff --git a/changelogs/unreleased/218757-fix-polling-for-events.yml b/changelogs/unreleased/218757-fix-polling-for-events.yml
new file mode 100644
index 00000000000..10accb9ba4d
--- /dev/null
+++ b/changelogs/unreleased/218757-fix-polling-for-events.yml
@@ -0,0 +1,5 @@
+---
+title: Fix polling for resource events
+merge_request: 33025
+author:
+type: fixed
diff --git a/changelogs/unreleased/218841-extend-ecs-for-fargate.yml b/changelogs/unreleased/218841-extend-ecs-for-fargate.yml
new file mode 100644
index 00000000000..04975ee3f1e
--- /dev/null
+++ b/changelogs/unreleased/218841-extend-ecs-for-fargate.yml
@@ -0,0 +1,5 @@
+---
+title: Extend ECS Deploy template with Fargate jobs
+merge_request: 35173
+author:
+type: added
diff --git a/changelogs/unreleased/219002-remove-ghost-column.yml b/changelogs/unreleased/219002-remove-ghost-column.yml
new file mode 100644
index 00000000000..0af12a67b20
--- /dev/null
+++ b/changelogs/unreleased/219002-remove-ghost-column.yml
@@ -0,0 +1,5 @@
+---
+title: Remove obsolete users.ghost column
+merge_request: 32957
+author:
+type: other
diff --git a/changelogs/unreleased/219022-fix-pipelines-apps-not-loading.yml b/changelogs/unreleased/219022-fix-pipelines-apps-not-loading.yml
new file mode 100644
index 00000000000..a56c63353ad
--- /dev/null
+++ b/changelogs/unreleased/219022-fix-pipelines-apps-not-loading.yml
@@ -0,0 +1,5 @@
+---
+title: Fix a bug where some Vue apps would be unable to load when DAG tab is disabled
+merge_request: 32966
+author:
+type: fixed
diff --git a/changelogs/unreleased/219046-fix-readiness-probe-500-on-db-down.yml b/changelogs/unreleased/219046-fix-readiness-probe-500-on-db-down.yml
new file mode 100644
index 00000000000..9765e2635e4
--- /dev/null
+++ b/changelogs/unreleased/219046-fix-readiness-probe-500-on-db-down.yml
@@ -0,0 +1,5 @@
+---
+title: Expand healtchecks `500`s when DB is not available
+merge_request: 34844
+author:
+type: fixed
diff --git a/changelogs/unreleased/219074-safe-link-validation.yml b/changelogs/unreleased/219074-safe-link-validation.yml
new file mode 100644
index 00000000000..afce591505a
--- /dev/null
+++ b/changelogs/unreleased/219074-safe-link-validation.yml
@@ -0,0 +1,5 @@
+---
+title: Add validation step on backend for metrics dashboard links
+merge_request: 34204
+author:
+type: added
diff --git a/changelogs/unreleased/219145-comment-button-does-not-show-up-on-mr-when-comments-are-set-to-new.yml b/changelogs/unreleased/219145-comment-button-does-not-show-up-on-mr-when-comments-are-set-to-new.yml
new file mode 100644
index 00000000000..232842c3c5a
--- /dev/null
+++ b/changelogs/unreleased/219145-comment-button-does-not-show-up-on-mr-when-comments-are-set-to-new.yml
@@ -0,0 +1,5 @@
+---
+title: Fix overflow issue in MR and Issue comments
+merge_request: 33100
+author:
+type: fixed
diff --git a/changelogs/unreleased/219151-follow-up-from-fallback-to-lowest-visibility-level-in-snippet-visi.yml b/changelogs/unreleased/219151-follow-up-from-fallback-to-lowest-visibility-level-in-snippet-visi.yml
new file mode 100644
index 00000000000..b9f98664dff
--- /dev/null
+++ b/changelogs/unreleased/219151-follow-up-from-fallback-to-lowest-visibility-level-in-snippet-visi.yml
@@ -0,0 +1,5 @@
+---
+title: Add snippet DB visibility check in spec
+merge_request: 33388
+author: Jacopo Beschi @jacopo-beschi
+type: changed
diff --git a/changelogs/unreleased/219210-column-date-format.yml b/changelogs/unreleased/219210-column-date-format.yml
new file mode 100644
index 00000000000..6b71df17130
--- /dev/null
+++ b/changelogs/unreleased/219210-column-date-format.yml
@@ -0,0 +1,5 @@
+---
+title: Format metrics column chart x axis dates
+merge_request: 33681
+author:
+type: changed
diff --git a/changelogs/unreleased/219210-stacked-column-date-format.yml b/changelogs/unreleased/219210-stacked-column-date-format.yml
new file mode 100644
index 00000000000..86ce1103c97
--- /dev/null
+++ b/changelogs/unreleased/219210-stacked-column-date-format.yml
@@ -0,0 +1,5 @@
+---
+title: Add date time format to the monitor stacked-column chart
+merge_request: 33814
+author:
+type: changed
diff --git a/changelogs/unreleased/219228-add-web-ide-solarized-dark-theme-support.yml b/changelogs/unreleased/219228-add-web-ide-solarized-dark-theme-support.yml
new file mode 100644
index 00000000000..469a372e78e
--- /dev/null
+++ b/changelogs/unreleased/219228-add-web-ide-solarized-dark-theme-support.yml
@@ -0,0 +1,5 @@
+---
+title: Add solarized dark for Web IDE
+merge_request: 33148
+author:
+type: added
diff --git a/changelogs/unreleased/219255-vue-update.yml b/changelogs/unreleased/219255-vue-update.yml
new file mode 100644
index 00000000000..07849d07256
--- /dev/null
+++ b/changelogs/unreleased/219255-vue-update.yml
@@ -0,0 +1,5 @@
+---
+title: Display Multiple Terraform Reports in MR Widget
+merge_request: 34392
+author:
+type: added
diff --git a/changelogs/unreleased/219331-cablett-issues-list-reopened-issue.yml b/changelogs/unreleased/219331-cablett-issues-list-reopened-issue.yml
new file mode 100644
index 00000000000..351b81c7aa5
--- /dev/null
+++ b/changelogs/unreleased/219331-cablett-issues-list-reopened-issue.yml
@@ -0,0 +1,5 @@
+---
+title: Issue list page shows correct status for moved re-opened issues
+merge_request: 33238
+author:
+type: fixed
diff --git a/changelogs/unreleased/219385-metrics-dashboard-elements-outline.yml b/changelogs/unreleased/219385-metrics-dashboard-elements-outline.yml
new file mode 100644
index 00000000000..1c681a96f98
--- /dev/null
+++ b/changelogs/unreleased/219385-metrics-dashboard-elements-outline.yml
@@ -0,0 +1,5 @@
+---
+title: Remove dashboard panels' tabindex where is not needed
+merge_request: 36168
+author:
+type: fixed
diff --git a/changelogs/unreleased/219391-follow-up-from-add-type-field-to-asset-links-on-edit-release-page.yml b/changelogs/unreleased/219391-follow-up-from-add-type-field-to-asset-links-on-edit-release-page.yml
new file mode 100644
index 00000000000..fc732222a6f
--- /dev/null
+++ b/changelogs/unreleased/219391-follow-up-from-add-type-field-to-asset-links-on-edit-release-page.yml
@@ -0,0 +1,5 @@
+---
+title: Resolve spacing ux debt on Release assets form field
+merge_request: 33684
+author:
+type: fixed
diff --git a/changelogs/unreleased/219395-metrics-dashboard-validation-fix-500-for-empty-.yml b/changelogs/unreleased/219395-metrics-dashboard-validation-fix-500-for-empty-.yml
new file mode 100644
index 00000000000..4581e4d0566
--- /dev/null
+++ b/changelogs/unreleased/219395-metrics-dashboard-validation-fix-500-for-empty-.yml
@@ -0,0 +1,6 @@
+---
+title: Fixed dashboard YAML file validaiton for files which do not contain object
+ as root element
+merge_request: 33935
+author:
+type: fixed
diff --git a/changelogs/unreleased/219455-fe-inapplicable-tooltip-message.yml b/changelogs/unreleased/219455-fe-inapplicable-tooltip-message.yml
new file mode 100644
index 00000000000..f1200160eb9
--- /dev/null
+++ b/changelogs/unreleased/219455-fe-inapplicable-tooltip-message.yml
@@ -0,0 +1,5 @@
+---
+title: Add inapplicable reason in MR suggestion Tooltip
+merge_request: 35276
+author:
+type: changed
diff --git a/changelogs/unreleased/219539-project-access-tokens-returns-403-forbidden-your-account-has-been-.yml b/changelogs/unreleased/219539-project-access-tokens-returns-403-forbidden-your-account-has-been-.yml
new file mode 100644
index 00000000000..c263f7e9de7
--- /dev/null
+++ b/changelogs/unreleased/219539-project-access-tokens-returns-403-forbidden-your-account-has-been-.yml
@@ -0,0 +1,5 @@
+---
+title: Project bot users should always have their emails confirmed by default
+merge_request: 35498
+author:
+type: fixed
diff --git a/changelogs/unreleased/219558-improve-confirmation-email-language.yml b/changelogs/unreleased/219558-improve-confirmation-email-language.yml
new file mode 100644
index 00000000000..bd7d462c764
--- /dev/null
+++ b/changelogs/unreleased/219558-improve-confirmation-email-language.yml
@@ -0,0 +1,5 @@
+---
+title: Replace misleading text in re-confirmation emails
+merge_request: 36634
+author:
+type: security
diff --git a/changelogs/unreleased/219582-fix-ambiguous_string_concat_on_cleanup_projects_with_missing_names.yml b/changelogs/unreleased/219582-fix-ambiguous_string_concat_on_cleanup_projects_with_missing_names.yml
new file mode 100644
index 00000000000..4a4e268171e
--- /dev/null
+++ b/changelogs/unreleased/219582-fix-ambiguous_string_concat_on_cleanup_projects_with_missing_names.yml
@@ -0,0 +1,5 @@
+---
+title: Fix ambiguous string concatenation on CleanupProjectsWithMissingNamespace
+merge_request: 33497
+author:
+type: fixed
diff --git a/changelogs/unreleased/219658-add-route-to-ghost-lost-and-found-group.yml b/changelogs/unreleased/219658-add-route-to-ghost-lost-and-found-group.yml
new file mode 100644
index 00000000000..d60ab67bee9
--- /dev/null
+++ b/changelogs/unreleased/219658-add-route-to-ghost-lost-and-found-group.yml
@@ -0,0 +1,5 @@
+---
+title: Add route for the lost-and-found group and update the route of orphaned projects
+merge_request: 34285
+author:
+type: fixed
diff --git a/changelogs/unreleased/219956-instrument-last-git-write-operation-per-user.yml b/changelogs/unreleased/219956-instrument-last-git-write-operation-per-user.yml
new file mode 100644
index 00000000000..30913008201
--- /dev/null
+++ b/changelogs/unreleased/219956-instrument-last-git-write-operation-per-user.yml
@@ -0,0 +1,5 @@
+---
+title: Track the number of unique users who push, change wikis and change design managerment
+merge_request:
+author:
+type: other
diff --git a/changelogs/unreleased/220014-default-for-SAST_EXCLUDED_PATHS-DS_EXCLUDED_PATHS.yml b/changelogs/unreleased/220014-default-for-SAST_EXCLUDED_PATHS-DS_EXCLUDED_PATHS.yml
new file mode 100644
index 00000000000..9a3557f0673
--- /dev/null
+++ b/changelogs/unreleased/220014-default-for-SAST_EXCLUDED_PATHS-DS_EXCLUDED_PATHS.yml
@@ -0,0 +1,5 @@
+---
+title: Set default values for SAST_EXCLUDED_PATHS and DS_EXCLUDED_PATHS
+merge_request: 34076
+author:
+type: changed
diff --git a/changelogs/unreleased/220051-duplicate-issues-created-when-importing-from-csv.yml b/changelogs/unreleased/220051-duplicate-issues-created-when-importing-from-csv.yml
new file mode 100644
index 00000000000..1c58bf63840
--- /dev/null
+++ b/changelogs/unreleased/220051-duplicate-issues-created-when-importing-from-csv.yml
@@ -0,0 +1,5 @@
+---
+title: Prevent duplicate issues when importing from CSV
+merge_request: 33626
+author:
+type: fixed
diff --git a/changelogs/unreleased/220058.yml b/changelogs/unreleased/220058.yml
new file mode 100644
index 00000000000..4327732eef7
--- /dev/null
+++ b/changelogs/unreleased/220058.yml
@@ -0,0 +1,5 @@
+---
+title: Fix sidebar spacing for alert details
+merge_request: 33630
+author:
+type: fixed
diff --git a/changelogs/unreleased/220124-drop-temp-index-on-audit-events.yml b/changelogs/unreleased/220124-drop-temp-index-on-audit-events.yml
new file mode 100644
index 00000000000..d2f86aae5ec
--- /dev/null
+++ b/changelogs/unreleased/220124-drop-temp-index-on-audit-events.yml
@@ -0,0 +1,5 @@
+---
+title: Drop index of ruby objects in details on audit_events table
+merge_request: 36547
+author:
+type: other
diff --git a/changelogs/unreleased/220182-skeleton-loading.yml b/changelogs/unreleased/220182-skeleton-loading.yml
new file mode 100644
index 00000000000..2602aafe497
--- /dev/null
+++ b/changelogs/unreleased/220182-skeleton-loading.yml
@@ -0,0 +1,5 @@
+---
+title: Add skeleton loader to cluster list
+merge_request: 34090
+author:
+type: changed
diff --git a/changelogs/unreleased/220185-mask-key-comments-when-exposing-ssh-deploy-keys-via-the-api.yml b/changelogs/unreleased/220185-mask-key-comments-when-exposing-ssh-deploy-keys-via-the-api.yml
new file mode 100644
index 00000000000..7d319fb4f91
--- /dev/null
+++ b/changelogs/unreleased/220185-mask-key-comments-when-exposing-ssh-deploy-keys-via-the-api.yml
@@ -0,0 +1,5 @@
+---
+title: Mask key comments when exposing SSH/Deploy Keys via the API
+merge_request: 34255
+author:
+type: added
diff --git a/changelogs/unreleased/220192-fix-pagination-pd.yml b/changelogs/unreleased/220192-fix-pagination-pd.yml
new file mode 100644
index 00000000000..e0785d09008
--- /dev/null
+++ b/changelogs/unreleased/220192-fix-pagination-pd.yml
@@ -0,0 +1,5 @@
+---
+title: Fix pagination for resource label events
+merge_request: 33821
+author:
+type: fixed
diff --git a/changelogs/unreleased/220195-display-epics-on-swimlanes.yml b/changelogs/unreleased/220195-display-epics-on-swimlanes.yml
new file mode 100644
index 00000000000..46880ee8b8d
--- /dev/null
+++ b/changelogs/unreleased/220195-display-epics-on-swimlanes.yml
@@ -0,0 +1,5 @@
+---
+title: Update board header icons
+merge_request: 34366
+author:
+type: changed
diff --git a/changelogs/unreleased/220209-optimize-container-repository-query.yml b/changelogs/unreleased/220209-optimize-container-repository-query.yml
new file mode 100644
index 00000000000..6376d06cb53
--- /dev/null
+++ b/changelogs/unreleased/220209-optimize-container-repository-query.yml
@@ -0,0 +1,5 @@
+---
+title: Optimize container repository for groups query
+merge_request: 34364
+author:
+type: performance
diff --git a/changelogs/unreleased/220232-get-all-jira-projects.yml b/changelogs/unreleased/220232-get-all-jira-projects.yml
new file mode 100644
index 00000000000..b0620305a02
--- /dev/null
+++ b/changelogs/unreleased/220232-get-all-jira-projects.yml
@@ -0,0 +1,5 @@
+---
+title: Expose all Jira projects endpoint through a GraphQL
+merge_request: 33861
+author:
+type: added
diff --git a/changelogs/unreleased/220300-do-not-create-duplicate-alert-issues.yml b/changelogs/unreleased/220300-do-not-create-duplicate-alert-issues.yml
new file mode 100644
index 00000000000..e6a8a952561
--- /dev/null
+++ b/changelogs/unreleased/220300-do-not-create-duplicate-alert-issues.yml
@@ -0,0 +1,5 @@
+---
+title: Do not create duplicate issues for exising Alert Management alerts
+merge_request: 33860
+author:
+type: fixed
diff --git a/changelogs/unreleased/220316-redis-n-1-in-api-v4-groups-id-projects-forks-count-key.yml b/changelogs/unreleased/220316-redis-n-1-in-api-v4-groups-id-projects-forks-count-key.yml
new file mode 100644
index 00000000000..49ebf8739ab
--- /dev/null
+++ b/changelogs/unreleased/220316-redis-n-1-in-api-v4-groups-id-projects-forks-count-key.yml
@@ -0,0 +1,5 @@
+---
+title: Use BatchLoader for Project.forks_count to limit calls to Redis
+merge_request: 35328
+author:
+type: performance
diff --git a/changelogs/unreleased/220318-refactor-author_name-from-auditevent-details.yml b/changelogs/unreleased/220318-refactor-author_name-from-auditevent-details.yml
new file mode 100644
index 00000000000..af86e7425d3
--- /dev/null
+++ b/changelogs/unreleased/220318-refactor-author_name-from-auditevent-details.yml
@@ -0,0 +1,5 @@
+---
+title: Add parallel persistence for author_name on AuditEvent
+merge_request: 35130
+author:
+type: changed
diff --git a/changelogs/unreleased/220324-add-entity-path-to-audit-events.yml b/changelogs/unreleased/220324-add-entity-path-to-audit-events.yml
new file mode 100644
index 00000000000..1e7f07aa9d4
--- /dev/null
+++ b/changelogs/unreleased/220324-add-entity-path-to-audit-events.yml
@@ -0,0 +1,5 @@
+---
+title: Add entity_path column to audit_events table
+merge_request: 37041
+author:
+type: changed
diff --git a/changelogs/unreleased/220342-remove-services-from-import-export.yml b/changelogs/unreleased/220342-remove-services-from-import-export.yml
new file mode 100644
index 00000000000..1e5af27198b
--- /dev/null
+++ b/changelogs/unreleased/220342-remove-services-from-import-export.yml
@@ -0,0 +1,5 @@
+---
+title: Exclude integrations (services) from Project Import/Export
+merge_request: 35249
+author:
+type: changed
diff --git a/changelogs/unreleased/220361-display-metric-label-value-in-single-stat-panel.yml b/changelogs/unreleased/220361-display-metric-label-value-in-single-stat-panel.yml
new file mode 100644
index 00000000000..95fe11cdcfa
--- /dev/null
+++ b/changelogs/unreleased/220361-display-metric-label-value-in-single-stat-panel.yml
@@ -0,0 +1,5 @@
+---
+title: Display metric label in single stat
+merge_request: 35289
+author:
+type: added
diff --git a/changelogs/unreleased/220413-quickly-resolve-issues-with-your-cleanup-policy-with-improved-vali.yml b/changelogs/unreleased/220413-quickly-resolve-issues-with-your-cleanup-policy-with-improved-vali.yml
new file mode 100644
index 00000000000..486698487c6
--- /dev/null
+++ b/changelogs/unreleased/220413-quickly-resolve-issues-with-your-cleanup-policy-with-improved-vali.yml
@@ -0,0 +1,5 @@
+---
+title: 'Cleanup policies: display API error messages under form field'
+merge_request: 36190
+author:
+type: changed
diff --git a/changelogs/unreleased/220415-feature-flag-enable-alert-slack-notifications.yml b/changelogs/unreleased/220415-feature-flag-enable-alert-slack-notifications.yml
new file mode 100644
index 00000000000..fc1198d8344
--- /dev/null
+++ b/changelogs/unreleased/220415-feature-flag-enable-alert-slack-notifications.yml
@@ -0,0 +1,5 @@
+---
+title: Enable Slack notifications for alerts
+merge_request: 34038
+author:
+type: added
diff --git a/changelogs/unreleased/220477-harden-ci-pipelines-usage-data-queries.yml b/changelogs/unreleased/220477-harden-ci-pipelines-usage-data-queries.yml
new file mode 100644
index 00000000000..39231901fae
--- /dev/null
+++ b/changelogs/unreleased/220477-harden-ci-pipelines-usage-data-queries.yml
@@ -0,0 +1,5 @@
+---
+title: Harden CI pipelines usage data queries with an index
+merge_request: 34045
+author:
+type: performance
diff --git a/changelogs/unreleased/220616-group-code-icons-in-toolbar.yml b/changelogs/unreleased/220616-group-code-icons-in-toolbar.yml
new file mode 100644
index 00000000000..15e671bad56
--- /dev/null
+++ b/changelogs/unreleased/220616-group-code-icons-in-toolbar.yml
@@ -0,0 +1,5 @@
+---
+title: Update Static Site Editor toolbar to group inline-code and code-block buttons together
+merge_request: 34006
+author:
+type: changed
diff --git a/changelogs/unreleased/220785-snippet-editing-multi.yml b/changelogs/unreleased/220785-snippet-editing-multi.yml
new file mode 100644
index 00000000000..b2e857c4901
--- /dev/null
+++ b/changelogs/unreleased/220785-snippet-editing-multi.yml
@@ -0,0 +1,5 @@
+---
+title: Support multiple files when editing snippets
+merge_request: 37079
+author:
+type: changed
diff --git a/changelogs/unreleased/220789-10io-add-missing-attributes-to-graphql-container-expiration-policy.yml b/changelogs/unreleased/220789-10io-add-missing-attributes-to-graphql-container-expiration-policy.yml
new file mode 100644
index 00000000000..8c0c077ae30
--- /dev/null
+++ b/changelogs/unreleased/220789-10io-add-missing-attributes-to-graphql-container-expiration-policy.yml
@@ -0,0 +1,5 @@
+---
+title: Add regex fields to the container expiration policy update mutation
+merge_request: 34389
+author:
+type: added
diff --git a/changelogs/unreleased/220840-add-machine-sysname-in-topology-usageping.yml b/changelogs/unreleased/220840-add-machine-sysname-in-topology-usageping.yml
new file mode 100644
index 00000000000..2e9817fd3ae
--- /dev/null
+++ b/changelogs/unreleased/220840-add-machine-sysname-in-topology-usageping.yml
@@ -0,0 +1,5 @@
+---
+title: Add machine/sysname/release in topology usage ping
+merge_request: 34627
+author:
+type: other
diff --git a/changelogs/unreleased/220934-confluence-wiki-db.yml b/changelogs/unreleased/220934-confluence-wiki-db.yml
new file mode 100644
index 00000000000..6c2c8be64f2
--- /dev/null
+++ b/changelogs/unreleased/220934-confluence-wiki-db.yml
@@ -0,0 +1,5 @@
+---
+title: Database migration to add project_settings.has_confluence
+merge_request: 35485
+author:
+type: other
diff --git a/changelogs/unreleased/220934-confluence-wiki-icon.yml b/changelogs/unreleased/220934-confluence-wiki-icon.yml
new file mode 100644
index 00000000000..b64d29470c8
--- /dev/null
+++ b/changelogs/unreleased/220934-confluence-wiki-icon.yml
@@ -0,0 +1,5 @@
+---
+title: Use a Confluence icon for the project Confluence integration nav item
+merge_request: 36780
+author:
+type: changed
diff --git a/changelogs/unreleased/220934-confluence-wiki-remove-flag.yml b/changelogs/unreleased/220934-confluence-wiki-remove-flag.yml
new file mode 100644
index 00000000000..fe7febc6609
--- /dev/null
+++ b/changelogs/unreleased/220934-confluence-wiki-remove-flag.yml
@@ -0,0 +1,5 @@
+---
+title: Add new Confluence integration for projects
+merge_request: 36781
+author:
+type: added
diff --git a/changelogs/unreleased/220935.yml b/changelogs/unreleased/220935.yml
new file mode 100644
index 00000000000..723b2d6ae3e
--- /dev/null
+++ b/changelogs/unreleased/220935.yml
@@ -0,0 +1,5 @@
+---
+title: Update pinned links to use GlButton
+merge_request: 34620
+author:
+type: other
diff --git a/changelogs/unreleased/220944-docs-product-feedback-example-for-set-a-deploy-freeze-seems-wrong.yml b/changelogs/unreleased/220944-docs-product-feedback-example-for-set-a-deploy-freeze-seems-wrong.yml
new file mode 100644
index 00000000000..8bcc9155e2c
--- /dev/null
+++ b/changelogs/unreleased/220944-docs-product-feedback-example-for-set-a-deploy-freeze-seems-wrong.yml
@@ -0,0 +1,5 @@
+---
+title: Properly set CI_DEPLOY_FREEZE variable in pipelines
+merge_request: 35226
+author:
+type: fixed
diff --git a/changelogs/unreleased/220954-replace-fa-file-image-o-with-gitlab-media-icon.yml b/changelogs/unreleased/220954-replace-fa-file-image-o-with-gitlab-media-icon.yml
new file mode 100644
index 00000000000..9f01694f06a
--- /dev/null
+++ b/changelogs/unreleased/220954-replace-fa-file-image-o-with-gitlab-media-icon.yml
@@ -0,0 +1,5 @@
+---
+title: Use GitLab SVG icon for file attacher action
+merge_request: 34196
+author:
+type: other
diff --git a/changelogs/unreleased/221052-fix-custom-slashcommand-receiving-500.yml b/changelogs/unreleased/221052-fix-custom-slashcommand-receiving-500.yml
new file mode 100644
index 00000000000..58d594fe4f8
--- /dev/null
+++ b/changelogs/unreleased/221052-fix-custom-slashcommand-receiving-500.yml
@@ -0,0 +1,5 @@
+---
+title: Fix undefined method error
+merge_request: 34522
+author:
+type: fixed
diff --git a/changelogs/unreleased/221106-chart-links-url-blocking.yml b/changelogs/unreleased/221106-chart-links-url-blocking.yml
new file mode 100644
index 00000000000..cb506731f93
--- /dev/null
+++ b/changelogs/unreleased/221106-chart-links-url-blocking.yml
@@ -0,0 +1,5 @@
+---
+title: Block invalid URLs in metrics dashboard chart links
+merge_request: 34888
+author:
+type: added
diff --git a/changelogs/unreleased/221136-docs-product-feedback-slack-notifications-service-doc-outdated.yml b/changelogs/unreleased/221136-docs-product-feedback-slack-notifications-service-doc-outdated.yml
new file mode 100644
index 00000000000..7fca5a43e6c
--- /dev/null
+++ b/changelogs/unreleased/221136-docs-product-feedback-slack-notifications-service-doc-outdated.yml
@@ -0,0 +1,5 @@
+---
+title: Fix order of integrations to be sorted alphabetically
+merge_request: 34501
+author:
+type: fixed
diff --git a/changelogs/unreleased/221174-graphql-pagination-bug.yml b/changelogs/unreleased/221174-graphql-pagination-bug.yml
new file mode 100644
index 00000000000..70765ae708b
--- /dev/null
+++ b/changelogs/unreleased/221174-graphql-pagination-bug.yml
@@ -0,0 +1,5 @@
+---
+title: GraphQL - properly handle pagination of millisecond-precision timestamps
+merge_request: 34352
+author:
+type: fixed
diff --git a/changelogs/unreleased/221184-optimize-rolling-28-counters-for-.yml b/changelogs/unreleased/221184-optimize-rolling-28-counters-for-.yml
new file mode 100644
index 00000000000..1d7a07e5cf9
--- /dev/null
+++ b/changelogs/unreleased/221184-optimize-rolling-28-counters-for-.yml
@@ -0,0 +1,5 @@
+---
+title: Optimize deployment counters for last 28 days
+merge_request: 35892
+author:
+type: performance
diff --git a/changelogs/unreleased/221184-rolling-28-day-deployments-metrics.yml b/changelogs/unreleased/221184-rolling-28-day-deployments-metrics.yml
new file mode 100644
index 00000000000..772b40854a3
--- /dev/null
+++ b/changelogs/unreleased/221184-rolling-28-day-deployments-metrics.yml
@@ -0,0 +1,5 @@
+---
+title: Rolling 28 day time period counter for deployments
+merge_request: 35493
+author:
+type: added
diff --git a/changelogs/unreleased/221184-rolling-28-day-snippets-optimization.yml b/changelogs/unreleased/221184-rolling-28-day-snippets-optimization.yml
new file mode 100644
index 00000000000..4e350f3d67f
--- /dev/null
+++ b/changelogs/unreleased/221184-rolling-28-day-snippets-optimization.yml
@@ -0,0 +1,5 @@
+---
+title: Optimize rolling 28 days snippets counter
+merge_request: 34918
+author:
+type: performance
diff --git a/changelogs/unreleased/221184-rolling-28-day-time-period-for-all-usage-ping-counters.yml b/changelogs/unreleased/221184-rolling-28-day-time-period-for-all-usage-ping-counters.yml
new file mode 100644
index 00000000000..0209d516e3f
--- /dev/null
+++ b/changelogs/unreleased/221184-rolling-28-day-time-period-for-all-usage-ping-counters.yml
@@ -0,0 +1,5 @@
+---
+title: Rolling 28 day time period counters for snippets
+merge_request: 34363
+author:
+type: added
diff --git a/changelogs/unreleased/221189-suppress-progress-on-pulling-image-on-auto-browser-performance-tes.yml b/changelogs/unreleased/221189-suppress-progress-on-pulling-image-on-auto-browser-performance-tes.yml
new file mode 100644
index 00000000000..b553b0db1c2
--- /dev/null
+++ b/changelogs/unreleased/221189-suppress-progress-on-pulling-image-on-auto-browser-performance-tes.yml
@@ -0,0 +1,5 @@
+---
+title: Suppress progress on pulling on Performance Test
+merge_request: 34368
+author: Takuya Noguchi
+type: other
diff --git a/changelogs/unreleased/221211-improve-performance-of-group-search-api-advanced-merge_requests-sc.yml b/changelogs/unreleased/221211-improve-performance-of-group-search-api-advanced-merge_requests-sc.yml
new file mode 100644
index 00000000000..f951f88320e
--- /dev/null
+++ b/changelogs/unreleased/221211-improve-performance-of-group-search-api-advanced-merge_requests-sc.yml
@@ -0,0 +1,5 @@
+---
+title: Improve the search performance for merge requests
+merge_request: 36072
+author:
+type: performance
diff --git a/changelogs/unreleased/221225-dag-basic-annotations.yml b/changelogs/unreleased/221225-dag-basic-annotations.yml
new file mode 100644
index 00000000000..0632ca189aa
--- /dev/null
+++ b/changelogs/unreleased/221225-dag-basic-annotations.yml
@@ -0,0 +1,5 @@
+---
+title: Add annotation component for DAG
+merge_request: 35240
+author:
+type: added
diff --git a/changelogs/unreleased/221242-change-alert-severity-and-status-sort-order.yml b/changelogs/unreleased/221242-change-alert-severity-and-status-sort-order.yml
new file mode 100644
index 00000000000..5278ee53798
--- /dev/null
+++ b/changelogs/unreleased/221242-change-alert-severity-and-status-sort-order.yml
@@ -0,0 +1,5 @@
+---
+title: Change the sort order for alert severity and status.
+merge_request: 35774
+author:
+type: fixed
diff --git a/changelogs/unreleased/221242-fix-alerts-list-sorting.yml b/changelogs/unreleased/221242-fix-alerts-list-sorting.yml
new file mode 100644
index 00000000000..0bab4a0e5dc
--- /dev/null
+++ b/changelogs/unreleased/221242-fix-alerts-list-sorting.yml
@@ -0,0 +1,5 @@
+---
+title: Fix alert sort styling issues
+merge_request: 35741
+author:
+type: fixed
diff --git a/changelogs/unreleased/221301-update-gray-200-value-and-usages.yml b/changelogs/unreleased/221301-update-gray-200-value-and-usages.yml
new file mode 100644
index 00000000000..dbf92732837
--- /dev/null
+++ b/changelogs/unreleased/221301-update-gray-200-value-and-usages.yml
@@ -0,0 +1,5 @@
+---
+title: Updating $gray-200 hex value and remapping current instances to $gray-100
+merge_request: 36128
+author:
+type: other
diff --git a/changelogs/unreleased/222253-add-prometheus-columns-to-alert-management-alert.yml b/changelogs/unreleased/222253-add-prometheus-columns-to-alert-management-alert.yml
new file mode 100644
index 00000000000..3ffa0bc63a1
--- /dev/null
+++ b/changelogs/unreleased/222253-add-prometheus-columns-to-alert-management-alert.yml
@@ -0,0 +1,5 @@
+---
+title: Add prometheus_alert_id and environment_id to Alert management alerts
+merge_request: 34995
+author:
+type: added
diff --git a/changelogs/unreleased/222253-close-issue-on-resolved-alert.yml b/changelogs/unreleased/222253-close-issue-on-resolved-alert.yml
new file mode 100644
index 00000000000..d50c696f6d8
--- /dev/null
+++ b/changelogs/unreleased/222253-close-issue-on-resolved-alert.yml
@@ -0,0 +1,6 @@
+---
+title: Automatically close related issue when resolving Alert Management Prometheus
+ Alert
+merge_request: 35208
+author:
+type: added
diff --git a/changelogs/unreleased/222264-context-menu-single-stat.yml b/changelogs/unreleased/222264-context-menu-single-stat.yml
new file mode 100644
index 00000000000..130d2ed8f25
--- /dev/null
+++ b/changelogs/unreleased/222264-context-menu-single-stat.yml
@@ -0,0 +1,5 @@
+---
+title: Add contextual menu to single stat panels
+merge_request: 34497
+author:
+type: changed
diff --git a/changelogs/unreleased/222313-update-usage-ping-data-to-track-projects-with-can-override-approva.yml b/changelogs/unreleased/222313-update-usage-ping-data-to-track-projects-with-can-override-approva.yml
new file mode 100644
index 00000000000..8bf1533680e
--- /dev/null
+++ b/changelogs/unreleased/222313-update-usage-ping-data-to-track-projects-with-can-override-approva.yml
@@ -0,0 +1,5 @@
+---
+title: Add indices for projects with disable_overriding_approvers_per_merge_request
+merge_request: 35224
+author:
+type: added
diff --git a/changelogs/unreleased/222358-explore-removing-dependencies-on-test-stage.yml b/changelogs/unreleased/222358-explore-removing-dependencies-on-test-stage.yml
new file mode 100644
index 00000000000..a65b86378d2
--- /dev/null
+++ b/changelogs/unreleased/222358-explore-removing-dependencies-on-test-stage.yml
@@ -0,0 +1,5 @@
+---
+title: Remove build dependencies on code quality and license scanning
+merge_request: 34659
+author:
+type: other
diff --git a/changelogs/unreleased/222534-replace-fa-thumbs-up-and-fa-thumbs-down-with-gitlab-svg-thumbs-ico.yml b/changelogs/unreleased/222534-replace-fa-thumbs-up-and-fa-thumbs-down-with-gitlab-svg-thumbs-ico.yml
new file mode 100644
index 00000000000..c484417b12a
--- /dev/null
+++ b/changelogs/unreleased/222534-replace-fa-thumbs-up-and-fa-thumbs-down-with-gitlab-svg-thumbs-ico.yml
@@ -0,0 +1,5 @@
+---
+title: Normalize the 'thumb-up', 'thumb-down' icon.
+merge_request: 35988
+author:
+type: other
diff --git a/changelogs/unreleased/222594-enable-the-release_asset_link_type-feature-flag-by-default.yml b/changelogs/unreleased/222594-enable-the-release_asset_link_type-feature-flag-by-default.yml
new file mode 100644
index 00000000000..c814c47f06c
--- /dev/null
+++ b/changelogs/unreleased/222594-enable-the-release_asset_link_type-feature-flag-by-default.yml
@@ -0,0 +1,5 @@
+---
+title: Enable `:ci_release_generation` feature flag by default
+merge_request: 34633
+author:
+type: changed
diff --git a/changelogs/unreleased/222907-replace-angle-icons-with-chevron.yml b/changelogs/unreleased/222907-replace-angle-icons-with-chevron.yml
new file mode 100644
index 00000000000..ea53df18da9
--- /dev/null
+++ b/changelogs/unreleased/222907-replace-angle-icons-with-chevron.yml
@@ -0,0 +1,5 @@
+---
+title: Replace double angle icons with double chevron
+merge_request: 34736
+author:
+type: other
diff --git a/changelogs/unreleased/222964-collapse-button.yml b/changelogs/unreleased/222964-collapse-button.yml
new file mode 100644
index 00000000000..e4720033df2
--- /dev/null
+++ b/changelogs/unreleased/222964-collapse-button.yml
@@ -0,0 +1,5 @@
+---
+title: Add expand/collapse view to Terraform MR widget
+merge_request: 34879
+author:
+type: changed
diff --git a/changelogs/unreleased/223041-lock_version_monkeypatch_removal_unrevert.yml b/changelogs/unreleased/223041-lock_version_monkeypatch_removal_unrevert.yml
new file mode 100644
index 00000000000..6b0915cf06b
--- /dev/null
+++ b/changelogs/unreleased/223041-lock_version_monkeypatch_removal_unrevert.yml
@@ -0,0 +1,5 @@
+---
+title: Remove Rails Optimistic Locking monkeypatch
+merge_request: 36893
+author:
+type: fixed
diff --git a/changelogs/unreleased/223097-support-fenced-code-blocks-in-atlassian-document-format-converter.yml b/changelogs/unreleased/223097-support-fenced-code-blocks-in-atlassian-document-format-converter.yml
new file mode 100644
index 00000000000..0a99851b08a
--- /dev/null
+++ b/changelogs/unreleased/223097-support-fenced-code-blocks-in-atlassian-document-format-converter.yml
@@ -0,0 +1,5 @@
+---
+title: Support fenced code blocks in Atlassian Document Format converter
+merge_request: 35065
+author:
+type: fixed
diff --git a/changelogs/unreleased/223135-add-ref-milestones-and-released_at-to-releaser-cli-and-yml.yml b/changelogs/unreleased/223135-add-ref-milestones-and-released_at-to-releaser-cli-and-yml.yml
new file mode 100644
index 00000000000..d68b63e6241
--- /dev/null
+++ b/changelogs/unreleased/223135-add-ref-milestones-and-released_at-to-releaser-cli-and-yml.yml
@@ -0,0 +1,5 @@
+---
+title: Add ref, released_at, milestones to release yml
+merge_request: 34943
+author:
+type: added
diff --git a/changelogs/unreleased/223135-expose-ref-milestones-relesed-at-to-cli.yml b/changelogs/unreleased/223135-expose-ref-milestones-relesed-at-to-cli.yml
new file mode 100644
index 00000000000..dfbe529baa0
--- /dev/null
+++ b/changelogs/unreleased/223135-expose-ref-milestones-relesed-at-to-cli.yml
@@ -0,0 +1,5 @@
+---
+title: Expose ref, milestones, released_at to releaser-cli
+merge_request: 35115
+author:
+type: added
diff --git a/changelogs/unreleased/223151-adjust-unique-index-on-alerts.yml b/changelogs/unreleased/223151-adjust-unique-index-on-alerts.yml
new file mode 100644
index 00000000000..d0033403da4
--- /dev/null
+++ b/changelogs/unreleased/223151-adjust-unique-index-on-alerts.yml
@@ -0,0 +1,5 @@
+---
+title: Change Alert fingerprint index to run when status is not resolved
+merge_request: 36024
+author:
+type: changed
diff --git a/changelogs/unreleased/223151-custom-error-for-fingerprint-collision.yml b/changelogs/unreleased/223151-custom-error-for-fingerprint-collision.yml
new file mode 100644
index 00000000000..59b0c0cbf56
--- /dev/null
+++ b/changelogs/unreleased/223151-custom-error-for-fingerprint-collision.yml
@@ -0,0 +1,5 @@
+---
+title: Display informative error for status updates on duplicate alerts
+merge_request: 36527
+author:
+type: changed
diff --git a/changelogs/unreleased/223151-open-new-alert-when-existing-are-resolved.yml b/changelogs/unreleased/223151-open-new-alert-when-existing-are-resolved.yml
new file mode 100644
index 00000000000..bd1ddd3e3ae
--- /dev/null
+++ b/changelogs/unreleased/223151-open-new-alert-when-existing-are-resolved.yml
@@ -0,0 +1,5 @@
+---
+title: Open new alert when existing alert is resolved
+merge_request: 36261
+author:
+type: added
diff --git a/changelogs/unreleased/223159-fix-dahsboard-warning-logic.yml b/changelogs/unreleased/223159-fix-dahsboard-warning-logic.yml
new file mode 100644
index 00000000000..ba69c9ac07d
--- /dev/null
+++ b/changelogs/unreleased/223159-fix-dahsboard-warning-logic.yml
@@ -0,0 +1,5 @@
+---
+title: Fix dashboard schema validation issue
+merge_request: 36577
+author:
+type: fixed
diff --git a/changelogs/unreleased/223162-enclose-release-cli-steps-in-an-array.yml b/changelogs/unreleased/223162-enclose-release-cli-steps-in-an-array.yml
new file mode 100644
index 00000000000..c5fc7ee561f
--- /dev/null
+++ b/changelogs/unreleased/223162-enclose-release-cli-steps-in-an-array.yml
@@ -0,0 +1,5 @@
+---
+title: Enclose `release-cli` steps in an array
+merge_request: 34913
+author:
+type: fixed
diff --git a/changelogs/unreleased/223171_allow_erb_extension_for_sse.yml b/changelogs/unreleased/223171_allow_erb_extension_for_sse.yml
new file mode 100644
index 00000000000..7feae278c54
--- /dev/null
+++ b/changelogs/unreleased/223171_allow_erb_extension_for_sse.yml
@@ -0,0 +1,5 @@
+---
+title: Allow files with .md.erb extension for the Static Site Editor
+merge_request: 35136
+author:
+type: added
diff --git a/changelogs/unreleased/223185-add-project-key-to-jira-tracker-data.yml b/changelogs/unreleased/223185-add-project-key-to-jira-tracker-data.yml
new file mode 100644
index 00000000000..ddfcb337c11
--- /dev/null
+++ b/changelogs/unreleased/223185-add-project-key-to-jira-tracker-data.yml
@@ -0,0 +1,5 @@
+---
+title: Add project_key column to jira_tracker_data table
+merge_request: 34949
+author:
+type: other
diff --git a/changelogs/unreleased/223196-update-design-management-documentation.yml b/changelogs/unreleased/223196-update-design-management-documentation.yml
new file mode 100644
index 00000000000..2362a31f1e0
--- /dev/null
+++ b/changelogs/unreleased/223196-update-design-management-documentation.yml
@@ -0,0 +1,5 @@
+---
+title: Make the Design Collection more visible in the Issue UI
+merge_request: 36681
+author:
+type: changed
diff --git a/changelogs/unreleased/223251-edit-the-message-in-the-empty-state-banner-for-dag-visualization-t.yml b/changelogs/unreleased/223251-edit-the-message-in-the-empty-state-banner-for-dag-visualization-t.yml
new file mode 100644
index 00000000000..ef5b984047f
--- /dev/null
+++ b/changelogs/unreleased/223251-edit-the-message-in-the-empty-state-banner-for-dag-visualization-t.yml
@@ -0,0 +1,5 @@
+---
+title: Edit copy of DAG unsupported data alert
+merge_request: 35170
+author:
+type: other
diff --git a/changelogs/unreleased/223279-confidential-warning-doesnt-show-the-issuable-type.yml b/changelogs/unreleased/223279-confidential-warning-doesnt-show-the-issuable-type.yml
new file mode 100644
index 00000000000..48451ea8c4b
--- /dev/null
+++ b/changelogs/unreleased/223279-confidential-warning-doesnt-show-the-issuable-type.yml
@@ -0,0 +1,5 @@
+---
+title: Fix confidential warning not showing the issuable type
+merge_request: 34988
+author:
+type: fixed
diff --git a/changelogs/unreleased/223714-harden-ci-pipelines-auto_devops-config_repository-usage-data.yml b/changelogs/unreleased/223714-harden-ci-pipelines-auto_devops-config_repository-usage-data.yml
new file mode 100644
index 00000000000..b0a95d72a37
--- /dev/null
+++ b/changelogs/unreleased/223714-harden-ci-pipelines-auto_devops-config_repository-usage-data.yml
@@ -0,0 +1,5 @@
+---
+title: Rework hardening CI pipelines usage data queries with an index
+merge_request: 35494
+author:
+type: performance
diff --git a/changelogs/unreleased/223718-change-rate-limit-for-issues-creation-to-be-disabled-by-default.yml b/changelogs/unreleased/223718-change-rate-limit-for-issues-creation-to-be-disabled-by-default.yml
new file mode 100644
index 00000000000..31e08a9ce1c
--- /dev/null
+++ b/changelogs/unreleased/223718-change-rate-limit-for-issues-creation-to-be-disabled-by-default.yml
@@ -0,0 +1,5 @@
+---
+title: Change default value in application_settings.issues_create_limit to be 0
+merge_request: 36558
+author:
+type: changed
diff --git a/changelogs/unreleased/223773-correxct-too-large-limit.yml b/changelogs/unreleased/223773-correxct-too-large-limit.yml
new file mode 100644
index 00000000000..0c09b93b55c
--- /dev/null
+++ b/changelogs/unreleased/223773-correxct-too-large-limit.yml
@@ -0,0 +1,5 @@
+---
+title: Fixed size limit for too large snippets
+merge_request: 35076
+author:
+type: fixed
diff --git a/changelogs/unreleased/223788-improve-query-to-retrieve-job-artifacts-with-files-stored-locally.yml b/changelogs/unreleased/223788-improve-query-to-retrieve-job-artifacts-with-files-stored-locally.yml
new file mode 100644
index 00000000000..0386b89b974
--- /dev/null
+++ b/changelogs/unreleased/223788-improve-query-to-retrieve-job-artifacts-with-files-stored-locally.yml
@@ -0,0 +1,5 @@
+---
+title: Improve query to retrieve job artifacts with files stored locally
+merge_request: 35084
+author:
+type: performance
diff --git a/changelogs/unreleased/223790-nan-or-null-values-should-not-be-removed.yml b/changelogs/unreleased/223790-nan-or-null-values-should-not-be-removed.yml
new file mode 100644
index 00000000000..ed858bc32bb
--- /dev/null
+++ b/changelogs/unreleased/223790-nan-or-null-values-should-not-be-removed.yml
@@ -0,0 +1,5 @@
+---
+title: Stop removing NaN values from monitoring data series
+merge_request: 35086
+author:
+type: changed
diff --git a/changelogs/unreleased/223928-page-title-editorconfig.yml b/changelogs/unreleased/223928-page-title-editorconfig.yml
new file mode 100644
index 00000000000..bbecbe498cf
--- /dev/null
+++ b/changelogs/unreleased/223928-page-title-editorconfig.yml
@@ -0,0 +1,5 @@
+---
+title: 'Web IDE: Page title should not be .editorconfig when the IDE is first loaded.'
+merge_request: 36783
+author:
+type: fixed
diff --git a/changelogs/unreleased/224039-jira-issues-integration-doc.yml b/changelogs/unreleased/224039-jira-issues-integration-doc.yml
new file mode 100644
index 00000000000..0262543c574
--- /dev/null
+++ b/changelogs/unreleased/224039-jira-issues-integration-doc.yml
@@ -0,0 +1,6 @@
+---
+title: Expands Jira integration to allow viewing and searching a list of of Jira issues
+ directly within GitLab
+merge_request: 36435
+author:
+type: added
diff --git a/changelogs/unreleased/224113-remove-ff-for-generic-alert-fingerprinting.yml b/changelogs/unreleased/224113-remove-ff-for-generic-alert-fingerprinting.yml
new file mode 100644
index 00000000000..d3628d53066
--- /dev/null
+++ b/changelogs/unreleased/224113-remove-ff-for-generic-alert-fingerprinting.yml
@@ -0,0 +1,5 @@
+---
+title: Remove generic_alert_fingerprinting feature flag
+merge_request: 36148
+author:
+type: added
diff --git a/changelogs/unreleased/224117-require-k8s-for-dast-template.yml b/changelogs/unreleased/224117-require-k8s-for-dast-template.yml
new file mode 100644
index 00000000000..fee2cd0f024
--- /dev/null
+++ b/changelogs/unreleased/224117-require-k8s-for-dast-template.yml
@@ -0,0 +1,5 @@
+---
+title: Only run DAST job if Kubernetes active
+merge_request: 35259
+author:
+type: fixed
diff --git a/changelogs/unreleased/224447-add-default_branch_name-to-applicationsettings-model.yml b/changelogs/unreleased/224447-add-default_branch_name-to-applicationsettings-model.yml
new file mode 100644
index 00000000000..d903c1ed630
--- /dev/null
+++ b/changelogs/unreleased/224447-add-default_branch_name-to-applicationsettings-model.yml
@@ -0,0 +1,5 @@
+---
+title: Add default_branch_name to application_settings
+merge_request: 35282
+author:
+type: other
diff --git a/changelogs/unreleased/224470-mixed-case-pages-url.yml b/changelogs/unreleased/224470-mixed-case-pages-url.yml
new file mode 100644
index 00000000000..e315661dcfd
--- /dev/null
+++ b/changelogs/unreleased/224470-mixed-case-pages-url.yml
@@ -0,0 +1,5 @@
+---
+title: Fix pages_url for projects with mixed case path
+merge_request: 35300
+author:
+type: fixed
diff --git a/changelogs/unreleased/224528-un-assign-issue-to-from-comment-author-action-visibility.yml b/changelogs/unreleased/224528-un-assign-issue-to-from-comment-author-action-visibility.yml
new file mode 100644
index 00000000000..be139a683c2
--- /dev/null
+++ b/changelogs/unreleased/224528-un-assign-issue-to-from-comment-author-action-visibility.yml
@@ -0,0 +1,5 @@
+---
+title: Resolve [Un]Assign Issue to/from Comment Author Action Visibility
+merge_request: 35459
+author:
+type: fixed
diff --git a/changelogs/unreleased/224602-optimize-usage-ping-metrics-based-on-issues-table.yml b/changelogs/unreleased/224602-optimize-usage-ping-metrics-based-on-issues-table.yml
new file mode 100644
index 00000000000..91d495e8eaf
--- /dev/null
+++ b/changelogs/unreleased/224602-optimize-usage-ping-metrics-based-on-issues-table.yml
@@ -0,0 +1,5 @@
+---
+title: Use memoized start/finish for metrics based on issues table
+merge_request: 37155
+author:
+type: performance
diff --git a/changelogs/unreleased/224674-fix-update_routes_for_lost_and_found_group_and_orphaned_projects-c.yml b/changelogs/unreleased/224674-fix-update_routes_for_lost_and_found_group_and_orphaned_projects-c.yml
new file mode 100644
index 00000000000..39010dbb9c8
--- /dev/null
+++ b/changelogs/unreleased/224674-fix-update_routes_for_lost_and_found_group_and_orphaned_projects-c.yml
@@ -0,0 +1,5 @@
+---
+title: Fix path conflict for Ghost on UpdateRoutesForLostAndFoundGroupAndOrphanedProjects
+merge_request: 35425
+author:
+type: fixed
diff --git a/changelogs/unreleased/22506-webauthn-step-1-migrations.yml b/changelogs/unreleased/22506-webauthn-step-1-migrations.yml
new file mode 100644
index 00000000000..d9bf27590ef
--- /dev/null
+++ b/changelogs/unreleased/22506-webauthn-step-1-migrations.yml
@@ -0,0 +1,5 @@
+---
+title: Prepare database for WebAuthn
+merge_request: 35797
+author: Jan Beckmann
+type: other
diff --git a/changelogs/unreleased/225187-replace-fa-comment-icons-with-gitlab-svg-comment-icon.yml b/changelogs/unreleased/225187-replace-fa-comment-icons-with-gitlab-svg-comment-icon.yml
new file mode 100644
index 00000000000..0a21c5d6622
--- /dev/null
+++ b/changelogs/unreleased/225187-replace-fa-comment-icons-with-gitlab-svg-comment-icon.yml
@@ -0,0 +1,5 @@
+---
+title: Replace fa-comment / fa-comments icons with GitLab SVG
+merge_request: 36206
+author:
+type: changed
diff --git a/changelogs/unreleased/225212-add-fields-to-jira-mutation.yml b/changelogs/unreleased/225212-add-fields-to-jira-mutation.yml
new file mode 100644
index 00000000000..0873b43de80
--- /dev/null
+++ b/changelogs/unreleased/225212-add-fields-to-jira-mutation.yml
@@ -0,0 +1,5 @@
+---
+title: Add GitLab username and name to the import users from Jira mutation response
+merge_request: 35542
+author:
+type: changed
diff --git a/changelogs/unreleased/225214-require-username-namespace-to-be-at-least-2-characters-long.yml b/changelogs/unreleased/225214-require-username-namespace-to-be-at-least-2-characters-long.yml
new file mode 100644
index 00000000000..3781a91adea
--- /dev/null
+++ b/changelogs/unreleased/225214-require-username-namespace-to-be-at-least-2-characters-long.yml
@@ -0,0 +1,5 @@
+---
+title: Require namespace path (and username) to be at least 2 chars long
+merge_request: 35649
+author:
+type: changed
diff --git a/changelogs/unreleased/225258-extend-applicationsettings-to-support-default_branch_name.yml b/changelogs/unreleased/225258-extend-applicationsettings-to-support-default_branch_name.yml
new file mode 100644
index 00000000000..2abaea83845
--- /dev/null
+++ b/changelogs/unreleased/225258-extend-applicationsettings-to-support-default_branch_name.yml
@@ -0,0 +1,5 @@
+---
+title: Add default_branch_name to ApplicationSettings visible attrs
+merge_request: 35681
+author:
+type: other
diff --git a/changelogs/unreleased/225295-fix-missing-avatar.yml b/changelogs/unreleased/225295-fix-missing-avatar.yml
new file mode 100644
index 00000000000..de48f718f97
--- /dev/null
+++ b/changelogs/unreleased/225295-fix-missing-avatar.yml
@@ -0,0 +1,5 @@
+---
+title: Fix missing avatar in MR widget
+merge_request: 36034
+author:
+type: fixed
diff --git a/changelogs/unreleased/225592-read-default_branch_name-when-initializing-w-readme-md.yml b/changelogs/unreleased/225592-read-default_branch_name-when-initializing-w-readme-md.yml
new file mode 100644
index 00000000000..026e4027c53
--- /dev/null
+++ b/changelogs/unreleased/225592-read-default_branch_name-when-initializing-w-readme-md.yml
@@ -0,0 +1,6 @@
+---
+title: Use the application's default_branch_name when available when initializing a new repo with
+ a README
+merge_request: 35801
+author:
+type: changed
diff --git a/changelogs/unreleased/225640-move-link-to-file-view-to-bottom-bar-in-web-ide.yml b/changelogs/unreleased/225640-move-link-to-file-view-to-bottom-bar-in-web-ide.yml
new file mode 100644
index 00000000000..e45513ca16f
--- /dev/null
+++ b/changelogs/unreleased/225640-move-link-to-file-view-to-bottom-bar-in-web-ide.yml
@@ -0,0 +1,5 @@
+---
+title: Move file link to bottom in Web IDE
+merge_request: 35847
+author:
+type: changed
diff --git a/changelogs/unreleased/225649-add-description-to-alert-issue-body.yml b/changelogs/unreleased/225649-add-description-to-alert-issue-body.yml
new file mode 100644
index 00000000000..bfb25c2392c
--- /dev/null
+++ b/changelogs/unreleased/225649-add-description-to-alert-issue-body.yml
@@ -0,0 +1,5 @@
+---
+title: Fix rendering alert issue description field.
+merge_request: 35862
+author:
+type: fixed
diff --git a/changelogs/unreleased/225706-add-issues-enabled-to-jira-tracker-data.yml b/changelogs/unreleased/225706-add-issues-enabled-to-jira-tracker-data.yml
new file mode 100644
index 00000000000..54161c0982c
--- /dev/null
+++ b/changelogs/unreleased/225706-add-issues-enabled-to-jira-tracker-data.yml
@@ -0,0 +1,5 @@
+---
+title: Add issues_enabled column to jira_tracker_data table
+merge_request: 35987
+author:
+type: other
diff --git a/changelogs/unreleased/225933-fa-play-replacement.yml b/changelogs/unreleased/225933-fa-play-replacement.yml
new file mode 100644
index 00000000000..7e850a46144
--- /dev/null
+++ b/changelogs/unreleased/225933-fa-play-replacement.yml
@@ -0,0 +1,5 @@
+---
+title: Replace FA play icon with svg in pipeline schedule and admin runner page
+merge_request: 36379
+author:
+type: other
diff --git a/changelogs/unreleased/225938-replace-fa-eyes-slash-icons-with-gitlab-svg-eye-slash-icon.yml b/changelogs/unreleased/225938-replace-fa-eyes-slash-icons-with-gitlab-svg-eye-slash-icon.yml
new file mode 100644
index 00000000000..e9d768ee271
--- /dev/null
+++ b/changelogs/unreleased/225938-replace-fa-eyes-slash-icons-with-gitlab-svg-eye-slash-icon.yml
@@ -0,0 +1,5 @@
+---
+title: Replace fa-eyes-slash icons with GitLab SVG eye-slash icon
+merge_request: 36602
+author:
+type: performance
diff --git a/changelogs/unreleased/225965-move-counter-on-related-merge-requests-issues-into-a-badge.yml b/changelogs/unreleased/225965-move-counter-on-related-merge-requests-issues-into-a-badge.yml
new file mode 100644
index 00000000000..a38a96a8af1
--- /dev/null
+++ b/changelogs/unreleased/225965-move-counter-on-related-merge-requests-issues-into-a-badge.yml
@@ -0,0 +1,5 @@
+---
+title: Remove border from related merge requests/issues counter
+merge_request: 36272
+author:
+type: fixed
diff --git a/changelogs/unreleased/225970-dont-display-speech-bubble-on-image-threads-in-overview.yml b/changelogs/unreleased/225970-dont-display-speech-bubble-on-image-threads-in-overview.yml
new file mode 100644
index 00000000000..ea3d1393711
--- /dev/null
+++ b/changelogs/unreleased/225970-dont-display-speech-bubble-on-image-threads-in-overview.yml
@@ -0,0 +1,5 @@
+---
+title: Fix to remove speech bubble on hover over image on MR Overview tab
+merge_request: 36474
+author: Adam Alvis @adamalvis
+type: fixed
diff --git a/changelogs/unreleased/225971-display-speech-bubble-hovering-over-images-on-commits-page.yml b/changelogs/unreleased/225971-display-speech-bubble-hovering-over-images-on-commits-page.yml
new file mode 100644
index 00000000000..dd5aaf853ca
--- /dev/null
+++ b/changelogs/unreleased/225971-display-speech-bubble-hovering-over-images-on-commits-page.yml
@@ -0,0 +1,5 @@
+---
+title: Fix to display speech bubble on hover over image on commits page
+merge_request: 36470
+author: Adam Alvis @adamalvis
+type: fixed
diff --git a/changelogs/unreleased/226874-fix-pages-url-path.yml b/changelogs/unreleased/226874-fix-pages-url-path.yml
new file mode 100644
index 00000000000..082edf3de80
--- /dev/null
+++ b/changelogs/unreleased/226874-fix-pages-url-path.yml
@@ -0,0 +1,5 @@
+---
+title: Fix Project#pages_url not to downcase url path
+merge_request: 36183
+author:
+type: fixed
diff --git a/changelogs/unreleased/22691-externalize-i18n-strings-from---app-views-shared-milestones-_form_d.yml b/changelogs/unreleased/22691-externalize-i18n-strings-from---app-views-shared-milestones-_form_d.yml
new file mode 100644
index 00000000000..f6f54617e1a
--- /dev/null
+++ b/changelogs/unreleased/22691-externalize-i18n-strings-from---app-views-shared-milestones-_form_d.yml
@@ -0,0 +1,5 @@
+---
+title: Externalize i18n strings from ./app/views/shared/milestones/_form_dates.html.haml
+merge_request: 32162
+author: Gilang Gumilar
+type: changed
diff --git a/changelogs/unreleased/22691-externalize-i18n-strings-from---app-views-shared-milestones-_sideba.yml b/changelogs/unreleased/22691-externalize-i18n-strings-from---app-views-shared-milestones-_sideba.yml
new file mode 100644
index 00000000000..54168b42538
--- /dev/null
+++ b/changelogs/unreleased/22691-externalize-i18n-strings-from---app-views-shared-milestones-_sideba.yml
@@ -0,0 +1,5 @@
+---
+title: Externalize i18n strings from ./app/views/shared/milestones/_sidebar.html.haml
+merge_request: 32150
+author: Gilang Gumilar
+type: changed
diff --git a/changelogs/unreleased/22691-externelize-i18n-strings-from---app-views-shared-_promo-html-haml.yml b/changelogs/unreleased/22691-externelize-i18n-strings-from---app-views-shared-_promo-html-haml.yml
new file mode 100644
index 00000000000..fb2b87754da
--- /dev/null
+++ b/changelogs/unreleased/22691-externelize-i18n-strings-from---app-views-shared-_promo-html-haml.yml
@@ -0,0 +1,5 @@
+---
+title: Externalize i18n strings from ./app/views/shared/_promo.html.haml
+merge_request: 32109
+author: Gilang Gumilar
+type: changed
diff --git a/changelogs/unreleased/227054-eng-add-db-column-to-indicate-temporarily-increased-storage.yml b/changelogs/unreleased/227054-eng-add-db-column-to-indicate-temporarily-increased-storage.yml
new file mode 100644
index 00000000000..ee94dc596a7
--- /dev/null
+++ b/changelogs/unreleased/227054-eng-add-db-column-to-indicate-temporarily-increased-storage.yml
@@ -0,0 +1,5 @@
+---
+title: Add temporary storage increase column
+merge_request: 36107
+author:
+type: added
diff --git a/changelogs/unreleased/227090-health-status-text-displaying-twice-on-epic-tree.yml b/changelogs/unreleased/227090-health-status-text-displaying-twice-on-epic-tree.yml
new file mode 100644
index 00000000000..45255df0312
--- /dev/null
+++ b/changelogs/unreleased/227090-health-status-text-displaying-twice-on-epic-tree.yml
@@ -0,0 +1,5 @@
+---
+title: Prevent duplicate health status text on epics
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/227190-add-pagerduty-related-columns-to-project_incident_management_setti.yml b/changelogs/unreleased/227190-add-pagerduty-related-columns-to-project_incident_management_setti.yml
new file mode 100644
index 00000000000..5d48397e2e7
--- /dev/null
+++ b/changelogs/unreleased/227190-add-pagerduty-related-columns-to-project_incident_management_setti.yml
@@ -0,0 +1,5 @@
+---
+title: Add PagerDuty integration columns to `project_incident_management_settings` table.
+merge_request: 36277
+author:
+type: added
diff --git a/changelogs/unreleased/227351-impossible-to-permanently-close-jira-import-success-alert.yml b/changelogs/unreleased/227351-impossible-to-permanently-close-jira-import-success-alert.yml
new file mode 100644
index 00000000000..acd8312daba
--- /dev/null
+++ b/changelogs/unreleased/227351-impossible-to-permanently-close-jira-import-success-alert.yml
@@ -0,0 +1,5 @@
+---
+title: Permanently close Jira import success alert
+merge_request: 36571
+author:
+type: fixed
diff --git a/changelogs/unreleased/227363-project-services-checkboxes-are-not-checked-with-feature-flag-inte.yml b/changelogs/unreleased/227363-project-services-checkboxes-are-not-checked-with-feature-flag-inte.yml
new file mode 100644
index 00000000000..a3510c1502d
--- /dev/null
+++ b/changelogs/unreleased/227363-project-services-checkboxes-are-not-checked-with-feature-flag-inte.yml
@@ -0,0 +1,5 @@
+---
+title: Fix wrong value of checkbox in integration form
+merge_request: 36329
+author:
+type: fixed
diff --git a/changelogs/unreleased/227558-missing-digest-revision-and-short-revision-in-tags.yml b/changelogs/unreleased/227558-missing-digest-revision-and-short-revision-in-tags.yml
new file mode 100644
index 00000000000..370d0688734
--- /dev/null
+++ b/changelogs/unreleased/227558-missing-digest-revision-and-short-revision-in-tags.yml
@@ -0,0 +1,5 @@
+---
+title: Add broken tag state to tags list items
+merge_request: 36442
+author:
+type: changed
diff --git a/changelogs/unreleased/227598-list-format-static-site-editor.yml b/changelogs/unreleased/227598-list-format-static-site-editor.yml
new file mode 100644
index 00000000000..fcc7c142f7b
--- /dev/null
+++ b/changelogs/unreleased/227598-list-format-static-site-editor.yml
@@ -0,0 +1,5 @@
+---
+title: 'Static Site Editor: Set default sublist indent spaces to four space characters'
+merge_request: 36756
+author:
+type: changed
diff --git a/changelogs/unreleased/227799-enable-batch-suggestions-by-default.yml b/changelogs/unreleased/227799-enable-batch-suggestions-by-default.yml
new file mode 100644
index 00000000000..86b282d958e
--- /dev/null
+++ b/changelogs/unreleased/227799-enable-batch-suggestions-by-default.yml
@@ -0,0 +1,5 @@
+---
+title: Enable Batch Suggestins feature flag by default
+merge_request: 36561
+author:
+type: added
diff --git a/changelogs/unreleased/227863-fix-api-error-on-null-bio.yml b/changelogs/unreleased/227863-fix-api-error-on-null-bio.yml
new file mode 100644
index 00000000000..75fc9609e19
--- /dev/null
+++ b/changelogs/unreleased/227863-fix-api-error-on-null-bio.yml
@@ -0,0 +1,5 @@
+---
+title: Fix API errors when null value is given for the bio
+merge_request: 36650
+author:
+type: fixed
diff --git a/changelogs/unreleased/227865-positioning-of-mr-and-issue-counter-on-epic-trees-and-board-header.yml b/changelogs/unreleased/227865-positioning-of-mr-and-issue-counter-on-epic-trees-and-board-header.yml
new file mode 100644
index 00000000000..f95f8d98295
--- /dev/null
+++ b/changelogs/unreleased/227865-positioning-of-mr-and-issue-counter-on-epic-trees-and-board-header.yml
@@ -0,0 +1,5 @@
+---
+title: Fix positioning of mr/issue count
+merge_request: 36621
+author:
+type: fixed
diff --git a/changelogs/unreleased/22822-ide-paste-images.yml b/changelogs/unreleased/22822-ide-paste-images.yml
new file mode 100644
index 00000000000..26a3adf70a6
--- /dev/null
+++ b/changelogs/unreleased/22822-ide-paste-images.yml
@@ -0,0 +1,5 @@
+---
+title: Add support for pasting images in the Web IDE
+merge_request: 33256
+author:
+type: added
diff --git a/changelogs/unreleased/22834-ff-merge-msg.yml b/changelogs/unreleased/22834-ff-merge-msg.yml
new file mode 100644
index 00000000000..6e2e09bd654
--- /dev/null
+++ b/changelogs/unreleased/22834-ff-merge-msg.yml
@@ -0,0 +1,5 @@
+---
+title: Improve fast-forward merge is not possible message
+merge_request: 22834
+author: Ben Bodenmiller
+type: other
diff --git a/changelogs/unreleased/228674-auto-devops-deploy-failure-when-code-quality-enabled.yml b/changelogs/unreleased/228674-auto-devops-deploy-failure-when-code-quality-enabled.yml
new file mode 100644
index 00000000000..15145efa46c
--- /dev/null
+++ b/changelogs/unreleased/228674-auto-devops-deploy-failure-when-code-quality-enabled.yml
@@ -0,0 +1,5 @@
+---
+title: Do not depend on artifacts from previous stages in Auto DevOps deployments
+merge_request: 36741
+author:
+type: fixed
diff --git a/changelogs/unreleased/22868-fix-remove-button-alignment.yml b/changelogs/unreleased/22868-fix-remove-button-alignment.yml
new file mode 100644
index 00000000000..63c755d2479
--- /dev/null
+++ b/changelogs/unreleased/22868-fix-remove-button-alignment.yml
@@ -0,0 +1,5 @@
+---
+title: Fix "Broadcast Messages" table overflow and button alignment
+merge_request: 32801
+author:
+type: fixed
diff --git a/changelogs/unreleased/229155-clarify-checking-ability-to-merge-automatically-message-on-mr-widg.yml b/changelogs/unreleased/229155-clarify-checking-ability-to-merge-automatically-message-on-mr-widg.yml
new file mode 100644
index 00000000000..5c043f5be52
--- /dev/null
+++ b/changelogs/unreleased/229155-clarify-checking-ability-to-merge-automatically-message-on-mr-widg.yml
@@ -0,0 +1,5 @@
+---
+title: Change loading MR message wording
+merge_request: 37181
+author:
+type: changed
diff --git a/changelogs/unreleased/23352-editorconfig.yml b/changelogs/unreleased/23352-editorconfig.yml
new file mode 100644
index 00000000000..a80fb3556c8
--- /dev/null
+++ b/changelogs/unreleased/23352-editorconfig.yml
@@ -0,0 +1,5 @@
+---
+title: Support reading .editorconfig files inside of the Web IDE
+merge_request: 32378
+author:
+type: added
diff --git a/changelogs/unreleased/24241-provide-a-label-for-scheduled-pipeline-in-the-pipelines-overview-pa.yml b/changelogs/unreleased/24241-provide-a-label-for-scheduled-pipeline-in-the-pipelines-overview-pa.yml
new file mode 100644
index 00000000000..99862a27edc
--- /dev/null
+++ b/changelogs/unreleased/24241-provide-a-label-for-scheduled-pipeline-in-the-pipelines-overview-pa.yml
@@ -0,0 +1,5 @@
+---
+title: Provide a label for 'Scheduled Pipeline' in the pipelines overview page
+merge_request: 35554
+author:
+type: added
diff --git a/changelogs/unreleased/24324-remove-button-row-from-environments-empty-state.yml b/changelogs/unreleased/24324-remove-button-row-from-environments-empty-state.yml
new file mode 100644
index 00000000000..11c74b2d8ef
--- /dev/null
+++ b/changelogs/unreleased/24324-remove-button-row-from-environments-empty-state.yml
@@ -0,0 +1,5 @@
+---
+title: Resolve Remove button row from environments empty state
+merge_request: 35413
+author:
+type: changed
diff --git a/changelogs/unreleased/25429-fix-disabled-quick-actions-in-notes.yml b/changelogs/unreleased/25429-fix-disabled-quick-actions-in-notes.yml
new file mode 100644
index 00000000000..150d377b465
--- /dev/null
+++ b/changelogs/unreleased/25429-fix-disabled-quick-actions-in-notes.yml
@@ -0,0 +1,4 @@
+---
+title: Snippet comments where any line begins with a slash following an alphabetic character can't be published
+merge_request: 36563
+type: fixed
diff --git a/changelogs/unreleased/25486-batch-suggestions.yml b/changelogs/unreleased/25486-batch-suggestions.yml
new file mode 100644
index 00000000000..157e50c2a15
--- /dev/null
+++ b/changelogs/unreleased/25486-batch-suggestions.yml
@@ -0,0 +1,5 @@
+---
+title: User can apply multiple suggestions at the same time.
+merge_request: 22439
+author: Jesse Hall
+type: added
diff --git a/changelogs/unreleased/25830-find-file-button.yml b/changelogs/unreleased/25830-find-file-button.yml
new file mode 100644
index 00000000000..cd0b2104ac2
--- /dev/null
+++ b/changelogs/unreleased/25830-find-file-button.yml
@@ -0,0 +1,5 @@
+---
+title: Remove search icon from Project find file button
+merge_request: 33198
+author:
+type: changed
diff --git a/changelogs/unreleased/25953-api-exposes-comment-field-of-user-uploaded-ssh-keys.yml b/changelogs/unreleased/25953-api-exposes-comment-field-of-user-uploaded-ssh-keys.yml
new file mode 100644
index 00000000000..17e97151815
--- /dev/null
+++ b/changelogs/unreleased/25953-api-exposes-comment-field-of-user-uploaded-ssh-keys.yml
@@ -0,0 +1,5 @@
+---
+title: Add note about SSH key title being public information
+merge_request: 35574
+author:
+type: added
diff --git a/changelogs/unreleased/28154-move-controllers-outside-ee.yml b/changelogs/unreleased/28154-move-controllers-outside-ee.yml
new file mode 100644
index 00000000000..3fa60392c65
--- /dev/null
+++ b/changelogs/unreleased/28154-move-controllers-outside-ee.yml
@@ -0,0 +1,5 @@
+---
+title: Move review related controllers/workers outside EE
+merge_request: 32663
+author:
+type: changed
diff --git a/changelogs/unreleased/28589-emoji-status-popover-doesn-t-show-emoji-when-it-s-in-the-message.yml b/changelogs/unreleased/28589-emoji-status-popover-doesn-t-show-emoji-when-it-s-in-the-message.yml
new file mode 100644
index 00000000000..85f6aa53840
--- /dev/null
+++ b/changelogs/unreleased/28589-emoji-status-popover-doesn-t-show-emoji-when-it-s-in-the-message.yml
@@ -0,0 +1,5 @@
+---
+title: Fix rendering of emojis in status tooltips
+merge_request: 32604
+author:
+type: fixed
diff --git a/changelogs/unreleased/29279-add-resend-confirmation-link-to-login-failure-message.yml b/changelogs/unreleased/29279-add-resend-confirmation-link-to-login-failure-message.yml
new file mode 100644
index 00000000000..e678a073f91
--- /dev/null
+++ b/changelogs/unreleased/29279-add-resend-confirmation-link-to-login-failure-message.yml
@@ -0,0 +1,5 @@
+---
+title: Improve error message when unconfirmed user tries to log in
+merge_request: 34818
+author:
+type: changed
diff --git a/changelogs/unreleased/30390-duplicates-replacement-labels.yml b/changelogs/unreleased/30390-duplicates-replacement-labels.yml
new file mode 100644
index 00000000000..a0109c277c0
--- /dev/null
+++ b/changelogs/unreleased/30390-duplicates-replacement-labels.yml
@@ -0,0 +1,5 @@
+---
+title: Deduplicate labels with identical title and project
+merge_request: 21384
+author:
+type: changed
diff --git a/changelogs/unreleased/30707-show-outdated-status-of-suggestions.yml b/changelogs/unreleased/30707-show-outdated-status-of-suggestions.yml
new file mode 100644
index 00000000000..bec0d8d3c95
--- /dev/null
+++ b/changelogs/unreleased/30707-show-outdated-status-of-suggestions.yml
@@ -0,0 +1,5 @@
+---
+title: Show disabled suggestion button with tooltip message
+merge_request: 33357
+author:
+type: changed
diff --git a/changelogs/unreleased/30769-deploy-keys-push-protected-branches.yml b/changelogs/unreleased/30769-deploy-keys-push-protected-branches.yml
new file mode 100644
index 00000000000..5c21a1d6496
--- /dev/null
+++ b/changelogs/unreleased/30769-deploy-keys-push-protected-branches.yml
@@ -0,0 +1,5 @@
+---
+title: Expose project deploy keys for autocompletion
+merge_request: 34875
+author:
+type: added
diff --git a/changelogs/unreleased/30853-footer-system-message-covers-horizontal-scrollbar.yml b/changelogs/unreleased/30853-footer-system-message-covers-horizontal-scrollbar.yml
new file mode 100644
index 00000000000..565fed3508e
--- /dev/null
+++ b/changelogs/unreleased/30853-footer-system-message-covers-horizontal-scrollbar.yml
@@ -0,0 +1,5 @@
+---
+title: Footer system message fix
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/31000-api-for-instance-level-kubernetes-clusters.yml b/changelogs/unreleased/31000-api-for-instance-level-kubernetes-clusters.yml
new file mode 100644
index 00000000000..8fe88130c59
--- /dev/null
+++ b/changelogs/unreleased/31000-api-for-instance-level-kubernetes-clusters.yml
@@ -0,0 +1,5 @@
+---
+title: Add API support for instance-level Kubernetes clusters
+merge_request: 36001
+author:
+type: added
diff --git a/changelogs/unreleased/31099-MR-API-allow-NOT-params.yml b/changelogs/unreleased/31099-MR-API-allow-NOT-params.yml
new file mode 100644
index 00000000000..2e7f9994cbe
--- /dev/null
+++ b/changelogs/unreleased/31099-MR-API-allow-NOT-params.yml
@@ -0,0 +1,5 @@
+---
+title: Add 'not' params to MergeRequests API endpoint
+merge_request: 35391
+author:
+type: added
diff --git a/changelogs/unreleased/32230-reorder-diffs-compare-versions-dropdowns.yml b/changelogs/unreleased/32230-reorder-diffs-compare-versions-dropdowns.yml
new file mode 100644
index 00000000000..34bc383ec38
--- /dev/null
+++ b/changelogs/unreleased/32230-reorder-diffs-compare-versions-dropdowns.yml
@@ -0,0 +1,5 @@
+---
+title: Reorder diffs compare versions dropdowns.
+merge_request: 31770
+author: Gilang Gumilar
+type: changed
diff --git a/changelogs/unreleased/32942-add-dart-ci-template.yml b/changelogs/unreleased/32942-add-dart-ci-template.yml
new file mode 100644
index 00000000000..15c8ab78af6
--- /dev/null
+++ b/changelogs/unreleased/32942-add-dart-ci-template.yml
@@ -0,0 +1,5 @@
+---
+title: Added CI template for Dart
+merge_request: 32942
+author: agilob
+type: added
diff --git a/changelogs/unreleased/33040-doc-cicd-yaml-clarify-rules-if-when-behaviour.yml b/changelogs/unreleased/33040-doc-cicd-yaml-clarify-rules-if-when-behaviour.yml
new file mode 100644
index 00000000000..6ed24768729
--- /dev/null
+++ b/changelogs/unreleased/33040-doc-cicd-yaml-clarify-rules-if-when-behaviour.yml
@@ -0,0 +1,5 @@
+---
+doc: "cicd: yaml: clarify rules:if when behaviour"
+merge_request: 33040
+author: Melvin Vermeeren
+type: changed
diff --git a/changelogs/unreleased/33162-add-last-activity-for-personal-access-tokens.yml b/changelogs/unreleased/33162-add-last-activity-for-personal-access-tokens.yml
new file mode 100644
index 00000000000..e377dcb30b3
--- /dev/null
+++ b/changelogs/unreleased/33162-add-last-activity-for-personal-access-tokens.yml
@@ -0,0 +1,5 @@
+---
+title: Track last activity for Personal Access Token
+merge_request: 35471
+author:
+type: changed
diff --git a/changelogs/unreleased/33185-add-missing-install-instruction.yml b/changelogs/unreleased/33185-add-missing-install-instruction.yml
new file mode 100644
index 00000000000..db82588bd04
--- /dev/null
+++ b/changelogs/unreleased/33185-add-missing-install-instruction.yml
@@ -0,0 +1,5 @@
+---
+doc: "doc: install: add missing exiftool dependency"
+merge_request: 33185
+author: Stefan Schrijvers
+type: fixed
diff --git a/changelogs/unreleased/33743-graph-code-coverage-changes-over-time-for-a-project.yml b/changelogs/unreleased/33743-graph-code-coverage-changes-over-time-for-a-project.yml
new file mode 100644
index 00000000000..d2ee2c50d00
--- /dev/null
+++ b/changelogs/unreleased/33743-graph-code-coverage-changes-over-time-for-a-project.yml
@@ -0,0 +1,5 @@
+---
+title: Resolve Graph code coverage changes over time for a project
+merge_request: 26174
+author:
+type: added
diff --git a/changelogs/unreleased/34185-custom-renderer-kramdown.yml b/changelogs/unreleased/34185-custom-renderer-kramdown.yml
new file mode 100644
index 00000000000..2cb4ad07295
--- /dev/null
+++ b/changelogs/unreleased/34185-custom-renderer-kramdown.yml
@@ -0,0 +1,5 @@
+---
+title: "Prevents editing of non-markdown kramdown content in the Static Site Editor's WYSIWYG mode"
+merge_request: 34185
+author:
+type: changed
diff --git a/changelogs/unreleased/34523-fix-sse-edit-area-sync-bug.yml b/changelogs/unreleased/34523-fix-sse-edit-area-sync-bug.yml
new file mode 100644
index 00000000000..bc5891bf7a1
--- /dev/null
+++ b/changelogs/unreleased/34523-fix-sse-edit-area-sync-bug.yml
@@ -0,0 +1,5 @@
+---
+title: Fix static site editor raw (has front matter) <-> body (lacks front matter) content changes sync
+merge_request: 34523
+author:
+type: fixed
diff --git a/changelogs/unreleased/35077-custom-renderer-identifiers.yml b/changelogs/unreleased/35077-custom-renderer-identifiers.yml
new file mode 100644
index 00000000000..909e0edc126
--- /dev/null
+++ b/changelogs/unreleased/35077-custom-renderer-identifiers.yml
@@ -0,0 +1,5 @@
+---
+title: Add a custom HTML renderer to the Static Site Editor for markdown identifier syntax
+merge_request: 35077
+author:
+type: added
diff --git a/changelogs/unreleased/35261-custom-renderer-embedded-ruby.yml b/changelogs/unreleased/35261-custom-renderer-embedded-ruby.yml
new file mode 100644
index 00000000000..b3752568909
--- /dev/null
+++ b/changelogs/unreleased/35261-custom-renderer-embedded-ruby.yml
@@ -0,0 +1,5 @@
+---
+title: Add a custom HTML renderer to the Static Site Editor for embedded ruby (ERB) syntax
+merge_request: 35261
+author:
+type: added
diff --git a/changelogs/unreleased/35347-styling-of-sse-markdown-mode.yml b/changelogs/unreleased/35347-styling-of-sse-markdown-mode.yml
new file mode 100644
index 00000000000..4b3b3bd4205
--- /dev/null
+++ b/changelogs/unreleased/35347-styling-of-sse-markdown-mode.yml
@@ -0,0 +1,5 @@
+---
+title: Update the static site editor's markdown mode text to monospace to better reflect a code-editing experience
+merge_request: 35347
+author: Derek Knox
+type: changed
diff --git a/changelogs/unreleased/35349-reorder-api.yml b/changelogs/unreleased/35349-reorder-api.yml
new file mode 100644
index 00000000000..e45a0b00a9d
--- /dev/null
+++ b/changelogs/unreleased/35349-reorder-api.yml
@@ -0,0 +1,5 @@
+---
+title: "Added support for reordering issues to the v4 API"
+merge_request: 35349
+author: Joel @jjshoe, Lee Tickett @leetickett
+type: added
diff --git a/changelogs/unreleased/35775-inline-code-fix-for-custom-renderer-identifiers.yml b/changelogs/unreleased/35775-inline-code-fix-for-custom-renderer-identifiers.yml
new file mode 100644
index 00000000000..f343d2727cc
--- /dev/null
+++ b/changelogs/unreleased/35775-inline-code-fix-for-custom-renderer-identifiers.yml
@@ -0,0 +1,5 @@
+---
+title: Fix unique case where static site editor's custom renderer for identifier syntax didn't robustly handle inline code
+merge_request: 35775
+author: Derek Knox
+type: fixed
diff --git a/changelogs/unreleased/36250-custom-renderer-html.yml b/changelogs/unreleased/36250-custom-renderer-html.yml
new file mode 100644
index 00000000000..8c4c521b1ca
--- /dev/null
+++ b/changelogs/unreleased/36250-custom-renderer-html.yml
@@ -0,0 +1,5 @@
+---
+title: Add initial custom HTML renderer to the Static Site Editor to prevent editing in WYSIWYG mode
+merge_request: 36250
+author:
+type: added
diff --git a/changelogs/unreleased/36319-graphql-mutation-for-changing-locked-status-of-an-issue.yml b/changelogs/unreleased/36319-graphql-mutation-for-changing-locked-status-of-an-issue.yml
new file mode 100644
index 00000000000..9acf85be180
--- /dev/null
+++ b/changelogs/unreleased/36319-graphql-mutation-for-changing-locked-status-of-an-issue.yml
@@ -0,0 +1,5 @@
+---
+title: 'GraphQL mutation for changing locked status of an issue'
+merge_request: 36866
+author:
+type: added
diff --git a/changelogs/unreleased/36330-v2-custom-renderer-html.yml b/changelogs/unreleased/36330-v2-custom-renderer-html.yml
new file mode 100644
index 00000000000..4d0d2498143
--- /dev/null
+++ b/changelogs/unreleased/36330-v2-custom-renderer-html.yml
@@ -0,0 +1,5 @@
+---
+title: Add a custom HTML renderer to the Static Site Editor for HTML block syntax
+merge_request: 36330
+author:
+type: added
diff --git a/changelogs/unreleased/36361-custom-renderer-font-awesome.yml b/changelogs/unreleased/36361-custom-renderer-font-awesome.yml
new file mode 100644
index 00000000000..c3651f66350
--- /dev/null
+++ b/changelogs/unreleased/36361-custom-renderer-font-awesome.yml
@@ -0,0 +1,5 @@
+---
+title: Add a custom HTML renderer to the Static Site Editor for font awesome inline HTML syntax
+merge_request: 36361
+author:
+type: added
diff --git a/changelogs/unreleased/36574-custom-renderer-identifiers-instances.yml b/changelogs/unreleased/36574-custom-renderer-identifiers-instances.yml
new file mode 100644
index 00000000000..b5173375cf6
--- /dev/null
+++ b/changelogs/unreleased/36574-custom-renderer-identifiers-instances.yml
@@ -0,0 +1,5 @@
+---
+title: Add a custom HTML renderer to the Static Site Editor for markdown identifier instance syntax
+merge_request: 36574
+author:
+type: added
diff --git a/changelogs/unreleased/36720-frontend-provide-option-to-unassign-removed-user-from-issuables.yml b/changelogs/unreleased/36720-frontend-provide-option-to-unassign-removed-user-from-issuables.yml
new file mode 100644
index 00000000000..af4358e573a
--- /dev/null
+++ b/changelogs/unreleased/36720-frontend-provide-option-to-unassign-removed-user-from-issuables.yml
@@ -0,0 +1,5 @@
+---
+title: Add option to unassign member from issuables when removing them from a project
+merge_request: 34946
+author:
+type: added
diff --git a/changelogs/unreleased/36720-unassig-user-on-remove-option.yml b/changelogs/unreleased/36720-unassig-user-on-remove-option.yml
new file mode 100644
index 00000000000..db5a1861053
--- /dev/null
+++ b/changelogs/unreleased/36720-unassig-user-on-remove-option.yml
@@ -0,0 +1,5 @@
+---
+title: Extend members REST API with the option to unassign Issues and Merge Requests when member leaves team
+merge_request: 34388
+author:
+type: changed
diff --git a/changelogs/unreleased/36788-feature-proposal-api-for-import-from-bitbucket-server.yml b/changelogs/unreleased/36788-feature-proposal-api-for-import-from-bitbucket-server.yml
new file mode 100644
index 00000000000..2d82aafd95f
--- /dev/null
+++ b/changelogs/unreleased/36788-feature-proposal-api-for-import-from-bitbucket-server.yml
@@ -0,0 +1,5 @@
+---
+title: 'Resolve Feature proposal: API for import from BitBucket Server'
+merge_request: 33097
+author:
+type: added
diff --git a/changelogs/unreleased/36860-secret-detection-branch-job.yml b/changelogs/unreleased/36860-secret-detection-branch-job.yml
new file mode 100644
index 00000000000..1e180fd4fa5
--- /dev/null
+++ b/changelogs/unreleased/36860-secret-detection-branch-job.yml
@@ -0,0 +1,5 @@
+---
+title: Add default and non-default branch jobs for secret detection
+merge_request: 36570
+author:
+type: added
diff --git a/changelogs/unreleased/37412-update-error-tracking-list-message.yml b/changelogs/unreleased/37412-update-error-tracking-list-message.yml
new file mode 100644
index 00000000000..5c6ece8dd51
--- /dev/null
+++ b/changelogs/unreleased/37412-update-error-tracking-list-message.yml
@@ -0,0 +1,5 @@
+---
+title: Update text in error tracking list error message
+merge_request: 33872
+author:
+type: fixed
diff --git a/changelogs/unreleased/39545-cleanup-dynamic-milestone-pages.yml b/changelogs/unreleased/39545-cleanup-dynamic-milestone-pages.yml
new file mode 100644
index 00000000000..e2ab210a333
--- /dev/null
+++ b/changelogs/unreleased/39545-cleanup-dynamic-milestone-pages.yml
@@ -0,0 +1,5 @@
+---
+title: Remove deprecated dashboard & group milestone pages
+merge_request: 13237
+author:
+type: removed
diff --git a/changelogs/unreleased/500-metrics-creation.yml b/changelogs/unreleased/500-metrics-creation.yml
new file mode 100644
index 00000000000..83a93e2ff04
--- /dev/null
+++ b/changelogs/unreleased/500-metrics-creation.yml
@@ -0,0 +1,5 @@
+---
+title: Fix for metrics creation when saving MR
+merge_request: 32668
+author:
+type: fixed
diff --git a/changelogs/unreleased/502-add-toggles-help-text-int-test-gitlab-ui-integration-test.yml b/changelogs/unreleased/502-add-toggles-help-text-int-test-gitlab-ui-integration-test.yml
new file mode 100644
index 00000000000..24990c85d47
--- /dev/null
+++ b/changelogs/unreleased/502-add-toggles-help-text-int-test-gitlab-ui-integration-test.yml
@@ -0,0 +1,5 @@
+---
+title: Update gl-toggles with deprecated attributes
+merge_request: 34660
+author:
+type: changed
diff --git a/changelogs/unreleased/9912-api-env-variables-update-with-env-scope.yml b/changelogs/unreleased/9912-api-env-variables-update-with-env-scope.yml
new file mode 100644
index 00000000000..2c1a95e57bd
--- /dev/null
+++ b/changelogs/unreleased/9912-api-env-variables-update-with-env-scope.yml
@@ -0,0 +1,5 @@
+---
+title: Add environment_scope filter to ci-variables API
+merge_request: 34490
+author:
+type: fixed
diff --git a/changelogs/unreleased/Fix-spelling-error.yml b/changelogs/unreleased/Fix-spelling-error.yml
new file mode 100644
index 00000000000..c851da131fe
--- /dev/null
+++ b/changelogs/unreleased/Fix-spelling-error.yml
@@ -0,0 +1,5 @@
+---
+title: Fix spelling error on Ci::RunnersFinder
+merge_request: 32985
+author: Arthur de Lapertosa Lisboa
+type: fixed
diff --git a/changelogs/unreleased/Remove-addAssignee-logic-from-issues-model.yml b/changelogs/unreleased/Remove-addAssignee-logic-from-issues-model.yml
new file mode 100644
index 00000000000..27c9dbaf3b6
--- /dev/null
+++ b/changelogs/unreleased/Remove-addAssignee-logic-from-issues-model.yml
@@ -0,0 +1,5 @@
+---
+title: Remove addAssignee logic from issue model
+merge_request: 32231
+author: nuwe1
+type: other
diff --git a/changelogs/unreleased/Remove-addLabel-function-logic-from-issue-models.yml b/changelogs/unreleased/Remove-addLabel-function-logic-from-issue-models.yml
new file mode 100644
index 00000000000..df0e490e1ca
--- /dev/null
+++ b/changelogs/unreleased/Remove-addLabel-function-logic-from-issue-models.yml
@@ -0,0 +1,5 @@
+---
+title: Remove addLabel Logic from issue models
+merge_request: 32233
+author: nuwe1
+type: other
diff --git a/changelogs/unreleased/Remove-addMilestone-logic-from-issue-models.yml b/changelogs/unreleased/Remove-addMilestone-logic-from-issue-models.yml
new file mode 100644
index 00000000000..7c537b04d84
--- /dev/null
+++ b/changelogs/unreleased/Remove-addMilestone-logic-from-issue-models.yml
@@ -0,0 +1,5 @@
+---
+title: Remove addMilestone logic from issue model
+merge_request: 32235
+author: nuwe1
+type: other
diff --git a/changelogs/unreleased/Remove-clickable-styling-from-loading-row.yml b/changelogs/unreleased/Remove-clickable-styling-from-loading-row.yml
new file mode 100644
index 00000000000..a32c3d5f847
--- /dev/null
+++ b/changelogs/unreleased/Remove-clickable-styling-from-loading-row.yml
@@ -0,0 +1,5 @@
+---
+title: Fix loading and empty state styling for alerts list
+merge_request: 32531
+author:
+type: fixed
diff --git a/changelogs/unreleased/Remove-destroy-function-logic-from-list-model.yml b/changelogs/unreleased/Remove-destroy-function-logic-from-list-model.yml
new file mode 100644
index 00000000000..2832db23bed
--- /dev/null
+++ b/changelogs/unreleased/Remove-destroy-function-logic-from-list-model.yml
@@ -0,0 +1,5 @@
+---
+title: Remove destroy function logic from list model
+merge_request: 32237
+author: nuwe1
+type: other
diff --git a/changelogs/unreleased/Remove-findAssignee-logic-from-issues-model.yml b/changelogs/unreleased/Remove-findAssignee-logic-from-issues-model.yml
new file mode 100644
index 00000000000..7e4bfe78cf6
--- /dev/null
+++ b/changelogs/unreleased/Remove-findAssignee-logic-from-issues-model.yml
@@ -0,0 +1,5 @@
+---
+title: Remove findAssignee logic from issue model
+merge_request: 32238
+author: nuwe1
+type: other
diff --git a/changelogs/unreleased/Remove-findLabel-logic-from-issues-model.yml b/changelogs/unreleased/Remove-findLabel-logic-from-issues-model.yml
new file mode 100644
index 00000000000..97fa19ca080
--- /dev/null
+++ b/changelogs/unreleased/Remove-findLabel-logic-from-issues-model.yml
@@ -0,0 +1,5 @@
+---
+title: Remove findLabel logic from issue model
+merge_request: 32239
+author: nuwe1
+type: other
diff --git a/changelogs/unreleased/Remove-findissue-logic-from-list-model.yml b/changelogs/unreleased/Remove-findissue-logic-from-list-model.yml
new file mode 100644
index 00000000000..74d57ff4f14
--- /dev/null
+++ b/changelogs/unreleased/Remove-findissue-logic-from-list-model.yml
@@ -0,0 +1,5 @@
+---
+title: Remove findIssue logic from list model
+merge_request: 32241
+author: nuwe1
+type: other
diff --git a/changelogs/unreleased/Remove-moveIssue-logic-from-list-model.yml b/changelogs/unreleased/Remove-moveIssue-logic-from-list-model.yml
new file mode 100644
index 00000000000..9323405a02c
--- /dev/null
+++ b/changelogs/unreleased/Remove-moveIssue-logic-from-list-model.yml
@@ -0,0 +1,5 @@
+---
+title: Remove moveIssue logic from list model
+merge_request: 32242
+author: nuwe1
+type: other
diff --git a/changelogs/unreleased/Remove-moveMultipleIssues-logic-from-list.yml b/changelogs/unreleased/Remove-moveMultipleIssues-logic-from-list.yml
new file mode 100644
index 00000000000..3eb69cc9e41
--- /dev/null
+++ b/changelogs/unreleased/Remove-moveMultipleIssues-logic-from-list.yml
@@ -0,0 +1,5 @@
+---
+title: Remove moveMultipleIssues logic from issue model
+merge_request: 32243
+author: nuwe1
+type: other
diff --git a/changelogs/unreleased/Remove-newIssue-logic-from-list-model.yml b/changelogs/unreleased/Remove-newIssue-logic-from-list-model.yml
new file mode 100644
index 00000000000..978533e9285
--- /dev/null
+++ b/changelogs/unreleased/Remove-newIssue-logic-from-list-model.yml
@@ -0,0 +1,5 @@
+---
+title: Remove newIssue logic from list model
+merge_request: 32244
+author: nuwe1
+type: other
diff --git a/changelogs/unreleased/Remove-nextPage-function-logic-from-listmodel.yml b/changelogs/unreleased/Remove-nextPage-function-logic-from-listmodel.yml
new file mode 100644
index 00000000000..d548c912513
--- /dev/null
+++ b/changelogs/unreleased/Remove-nextPage-function-logic-from-listmodel.yml
@@ -0,0 +1,5 @@
+---
+title: Remove nextpage function logic from list model
+merge_request: 31904
+author: nuwe1
+type: other
diff --git a/changelogs/unreleased/Remove-onNewIssueResponse-logic-from-list-model.yml b/changelogs/unreleased/Remove-onNewIssueResponse-logic-from-list-model.yml
new file mode 100644
index 00000000000..aa665bd40d3
--- /dev/null
+++ b/changelogs/unreleased/Remove-onNewIssueResponse-logic-from-list-model.yml
@@ -0,0 +1,5 @@
+---
+title: Remove onNewIssueResponse logic from list model
+merge_request: 32245
+author: nuwe1
+type: other
diff --git a/changelogs/unreleased/Remove-removeAllAssignees-logic-from-issue-model.yml b/changelogs/unreleased/Remove-removeAllAssignees-logic-from-issue-model.yml
new file mode 100644
index 00000000000..df0488a3882
--- /dev/null
+++ b/changelogs/unreleased/Remove-removeAllAssignees-logic-from-issue-model.yml
@@ -0,0 +1,5 @@
+---
+title: Remove removeAllAssignees logic from issue model
+merge_request: 32247
+author: nuwe1
+type: other
diff --git a/changelogs/unreleased/Remove-removeAssignee-logic-from-issue-model.yml b/changelogs/unreleased/Remove-removeAssignee-logic-from-issue-model.yml
new file mode 100644
index 00000000000..257f95de712
--- /dev/null
+++ b/changelogs/unreleased/Remove-removeAssignee-logic-from-issue-model.yml
@@ -0,0 +1,5 @@
+---
+title: Remove removeAssignee logic from issue model
+merge_request: 32248
+author: nuwe1
+type: other
diff --git a/changelogs/unreleased/Remove-removeIssue-function-logic-from-list-model.yml b/changelogs/unreleased/Remove-removeIssue-function-logic-from-list-model.yml
new file mode 100644
index 00000000000..8d929b1a448
--- /dev/null
+++ b/changelogs/unreleased/Remove-removeIssue-function-logic-from-list-model.yml
@@ -0,0 +1,5 @@
+---
+title: Remove removeIssue logic from list model
+merge_request:
+author: nuwe1
+type: other
diff --git a/changelogs/unreleased/Remove-removeLabel-logic-from-issues-model.yml b/changelogs/unreleased/Remove-removeLabel-logic-from-issues-model.yml
new file mode 100644
index 00000000000..a04939f68f4
--- /dev/null
+++ b/changelogs/unreleased/Remove-removeLabel-logic-from-issues-model.yml
@@ -0,0 +1,5 @@
+---
+title: Remove removeLabel logic from issue model
+merge_request: 32251
+author: nuwe1
+type: other
diff --git a/changelogs/unreleased/Remove-removeLabels-logic-from-issues-model.yml b/changelogs/unreleased/Remove-removeLabels-logic-from-issues-model.yml
new file mode 100644
index 00000000000..18398999113
--- /dev/null
+++ b/changelogs/unreleased/Remove-removeLabels-logic-from-issues-model.yml
@@ -0,0 +1,5 @@
+---
+title: Remove removeLabels logic from issue model
+merge_request: 32252
+author: nuwe1
+type: other
diff --git a/changelogs/unreleased/Remove-removeMilestone-logic-from-issue-model.yml b/changelogs/unreleased/Remove-removeMilestone-logic-from-issue-model.yml
new file mode 100644
index 00000000000..ff000f33c53
--- /dev/null
+++ b/changelogs/unreleased/Remove-removeMilestone-logic-from-issue-model.yml
@@ -0,0 +1,5 @@
+---
+title: Remove removeMilestone logic from issue model
+merge_request: 32253
+author: nuwe1
+type: other
diff --git a/changelogs/unreleased/Remove-removeMultipleIssues-logic-from-list-model.yml b/changelogs/unreleased/Remove-removeMultipleIssues-logic-from-list-model.yml
new file mode 100644
index 00000000000..ba525900849
--- /dev/null
+++ b/changelogs/unreleased/Remove-removeMultipleIssues-logic-from-list-model.yml
@@ -0,0 +1,5 @@
+---
+title: Remove removeMultipleIssues logic from list model
+merge_request: 32254
+author: nuwe1
+type: other
diff --git a/changelogs/unreleased/Remove-setFetchingState-logic-from-issue-model.yml b/changelogs/unreleased/Remove-setFetchingState-logic-from-issue-model.yml
new file mode 100644
index 00000000000..5295fed6e98
--- /dev/null
+++ b/changelogs/unreleased/Remove-setFetchingState-logic-from-issue-model.yml
@@ -0,0 +1,5 @@
+---
+title: Remove setFetchingState logic from issue model
+merge_request: 32255
+author: nuwe1
+type: other
diff --git a/changelogs/unreleased/Remove-setLoadingState-logic-From-issue-model.yml b/changelogs/unreleased/Remove-setLoadingState-logic-From-issue-model.yml
new file mode 100644
index 00000000000..38c2b29d18f
--- /dev/null
+++ b/changelogs/unreleased/Remove-setLoadingState-logic-From-issue-model.yml
@@ -0,0 +1,5 @@
+---
+title: Remove setLoadingState logic from issue model
+merge_request: 32226
+author: nuwe1
+type: other
diff --git a/changelogs/unreleased/Remove-update-fuction-logic-from-list-model.yml b/changelogs/unreleased/Remove-update-fuction-logic-from-list-model.yml
new file mode 100644
index 00000000000..6b13103b332
--- /dev/null
+++ b/changelogs/unreleased/Remove-update-fuction-logic-from-list-model.yml
@@ -0,0 +1,5 @@
+---
+title: Remove update function logic from list model
+merge_request: 31900
+author: nuwe1
+type: other
diff --git a/changelogs/unreleased/Remove-updateData-logic-from-issue-model.yml b/changelogs/unreleased/Remove-updateData-logic-from-issue-model.yml
new file mode 100644
index 00000000000..27af15c3cc5
--- /dev/null
+++ b/changelogs/unreleased/Remove-updateData-logic-from-issue-model.yml
@@ -0,0 +1,5 @@
+---
+title: Remove updateData logic from issue model
+merge_request: 32256
+author: nuwe1
+type: other
diff --git a/changelogs/unreleased/aalakkad-fix-time-tracking-help-link.yml b/changelogs/unreleased/aalakkad-fix-time-tracking-help-link.yml
new file mode 100644
index 00000000000..48a48e34c69
--- /dev/null
+++ b/changelogs/unreleased/aalakkad-fix-time-tracking-help-link.yml
@@ -0,0 +1,5 @@
+---
+title: Fix time_tracking help link
+merge_request: 32552
+author:
+type: fixed
diff --git a/changelogs/unreleased/ab-alert-usage-ping.yml b/changelogs/unreleased/ab-alert-usage-ping.yml
new file mode 100644
index 00000000000..408e511cd36
--- /dev/null
+++ b/changelogs/unreleased/ab-alert-usage-ping.yml
@@ -0,0 +1,5 @@
+---
+title: Add count of alerts from all sources to usage ping
+merge_request: 33220
+author:
+type: added
diff --git a/changelogs/unreleased/ab-cleanup-migrations.yml b/changelogs/unreleased/ab-cleanup-migrations.yml
new file mode 100644
index 00000000000..803040ab69b
--- /dev/null
+++ b/changelogs/unreleased/ab-cleanup-migrations.yml
@@ -0,0 +1,5 @@
+---
+title: Squash database migrations prior to 2019 into one
+merge_request: 31936
+author:
+type: other
diff --git a/changelogs/unreleased/ab-hash-partitioning.yml b/changelogs/unreleased/ab-hash-partitioning.yml
new file mode 100644
index 00000000000..9de9aafc2e0
--- /dev/null
+++ b/changelogs/unreleased/ab-hash-partitioning.yml
@@ -0,0 +1,5 @@
+---
+title: Create schema for static partitions
+merge_request: 35268
+author:
+type: other
diff --git a/changelogs/unreleased/ab-monitor-demo-environments-2.yml b/changelogs/unreleased/ab-monitor-demo-environments-2.yml
new file mode 100644
index 00000000000..19bb53079d8
--- /dev/null
+++ b/changelogs/unreleased/ab-monitor-demo-environments-2.yml
@@ -0,0 +1,5 @@
+---
+title: Add Scheduled Job for Monitoring Monitor Group Demo Environments
+merge_request: 27360
+author:
+type: added
diff --git a/changelogs/unreleased/ab-partition-management.yml b/changelogs/unreleased/ab-partition-management.yml
new file mode 100644
index 00000000000..c4c9c8ab432
--- /dev/null
+++ b/changelogs/unreleased/ab-partition-management.yml
@@ -0,0 +1,5 @@
+---
+title: Create time-space partitions in separate schema gitlab_partitions_dynamic
+merge_request: 35137
+author:
+type: other
diff --git a/changelogs/unreleased/ab-revert-schema-create.yml b/changelogs/unreleased/ab-revert-schema-create.yml
new file mode 100644
index 00000000000..7deb28f42ce
--- /dev/null
+++ b/changelogs/unreleased/ab-revert-schema-create.yml
@@ -0,0 +1,5 @@
+---
+title: Drop partitions_dynamic schema if it exists
+merge_request: 35426
+author:
+type: other
diff --git a/changelogs/unreleased/ab-services-partial-indexes.yml b/changelogs/unreleased/ab-services-partial-indexes.yml
new file mode 100644
index 00000000000..15dad096f7b
--- /dev/null
+++ b/changelogs/unreleased/ab-services-partial-indexes.yml
@@ -0,0 +1,5 @@
+---
+title: Adjust condition for partial indexes on services table
+merge_request: 33044
+author:
+type: performance
diff --git a/changelogs/unreleased/add-Groups-SharedRunnersService-tests-migrations.yml b/changelogs/unreleased/add-Groups-SharedRunnersService-tests-migrations.yml
new file mode 100644
index 00000000000..308225344b5
--- /dev/null
+++ b/changelogs/unreleased/add-Groups-SharedRunnersService-tests-migrations.yml
@@ -0,0 +1,5 @@
+---
+title: Add setting to enable and disable shared Runners for a group and its descendants
+merge_request: 33411
+author: Arthur de Lapertosa Lisboa
+type: added
diff --git a/changelogs/unreleased/add-alias-expansion-to-tf-docs.yml b/changelogs/unreleased/add-alias-expansion-to-tf-docs.yml
new file mode 100644
index 00000000000..00fc972b152
--- /dev/null
+++ b/changelogs/unreleased/add-alias-expansion-to-tf-docs.yml
@@ -0,0 +1,5 @@
+---
+title: Add alias expansion to Terraform documentation
+merge_request: 35941
+author: zmeggyesi
+type: other
diff --git a/changelogs/unreleased/add-api-endpoint-for-resource-milestone-events-pd.yml b/changelogs/unreleased/add-api-endpoint-for-resource-milestone-events-pd.yml
new file mode 100644
index 00000000000..8f5ef68ba5b
--- /dev/null
+++ b/changelogs/unreleased/add-api-endpoint-for-resource-milestone-events-pd.yml
@@ -0,0 +1,5 @@
+---
+title: Add API endpoint for resource milestone events
+merge_request: 31720
+author:
+type: added
diff --git a/changelogs/unreleased/add-doc-custom-validators.yml b/changelogs/unreleased/add-doc-custom-validators.yml
new file mode 100644
index 00000000000..86e32801bdc
--- /dev/null
+++ b/changelogs/unreleased/add-doc-custom-validators.yml
@@ -0,0 +1,5 @@
+---
+title: Add doc for custom validators in api styleguide
+merge_request: 26734
+author: Rajendra Kadam
+type: added
diff --git a/changelogs/unreleased/add-dockerfile-path-to-ado-workflow-rules.yml b/changelogs/unreleased/add-dockerfile-path-to-ado-workflow-rules.yml
new file mode 100644
index 00000000000..036d6081693
--- /dev/null
+++ b/changelogs/unreleased/add-dockerfile-path-to-ado-workflow-rules.yml
@@ -0,0 +1,5 @@
+---
+title: Add DOCKERFILE_PATH to Auto DevOps workflow:rules
+merge_request: 36475
+author:
+type: fixed
diff --git a/changelogs/unreleased/add-email-to-oidc-id-token.yml b/changelogs/unreleased/add-email-to-oidc-id-token.yml
new file mode 100644
index 00000000000..5be068d7e3f
--- /dev/null
+++ b/changelogs/unreleased/add-email-to-oidc-id-token.yml
@@ -0,0 +1,5 @@
+---
+title: Add email and email_verified claims to OAuth ID token
+merge_request: 35468
+author: André Hänsel
+type: fixed
diff --git a/changelogs/unreleased/add-experience-level-to-user-preferences.yml b/changelogs/unreleased/add-experience-level-to-user-preferences.yml
new file mode 100644
index 00000000000..2029ebc90d5
--- /dev/null
+++ b/changelogs/unreleased/add-experience-level-to-user-preferences.yml
@@ -0,0 +1,5 @@
+---
+title: Add experience_level to user_preferences
+merge_request: 32784
+author:
+type: added
diff --git a/changelogs/unreleased/add-global-plans.yml b/changelogs/unreleased/add-global-plans.yml
new file mode 100644
index 00000000000..cafd6cfc227
--- /dev/null
+++ b/changelogs/unreleased/add-global-plans.yml
@@ -0,0 +1,5 @@
+---
+title: Adapt Limitable for system-wide features
+merge_request: 32574
+author:
+type: added
diff --git a/changelogs/unreleased/add-group-runners-finder.yml b/changelogs/unreleased/add-group-runners-finder.yml
new file mode 100644
index 00000000000..a90eb2c2e42
--- /dev/null
+++ b/changelogs/unreleased/add-group-runners-finder.yml
@@ -0,0 +1,5 @@
+---
+title: Add finder for group-level runners
+merge_request: 29283
+author: Arthur de Lapertosa Lisboa
+type: added
diff --git a/changelogs/unreleased/add-link-to-mrs-references.yml b/changelogs/unreleased/add-link-to-mrs-references.yml
new file mode 100644
index 00000000000..13686060b14
--- /dev/null
+++ b/changelogs/unreleased/add-link-to-mrs-references.yml
@@ -0,0 +1,5 @@
+---
+title: Render Merge request reference as link in email templates
+merge_request: 33316
+author:
+type: changed
diff --git a/changelogs/unreleased/add-markdown-support-in-bio.yml b/changelogs/unreleased/add-markdown-support-in-bio.yml
new file mode 100644
index 00000000000..a22ee193bda
--- /dev/null
+++ b/changelogs/unreleased/add-markdown-support-in-bio.yml
@@ -0,0 +1,5 @@
+---
+title: Add support for Markdown in the user's bio
+merge_request: 35604
+author: Riccardo Padovani
+type: added
diff --git a/changelogs/unreleased/add-models-for-site-profiles-225404.yml b/changelogs/unreleased/add-models-for-site-profiles-225404.yml
new file mode 100644
index 00000000000..4122c8189e2
--- /dev/null
+++ b/changelogs/unreleased/add-models-for-site-profiles-225404.yml
@@ -0,0 +1,5 @@
+---
+title: Add new models for DAST site profiles as part of DAST on-demand scans
+merge_request: 36659
+author:
+type: changed
diff --git a/changelogs/unreleased/add-namespace-settings-table.yml b/changelogs/unreleased/add-namespace-settings-table.yml
new file mode 100644
index 00000000000..7e09b391efb
--- /dev/null
+++ b/changelogs/unreleased/add-namespace-settings-table.yml
@@ -0,0 +1,5 @@
+---
+title: Add namespace settings table
+merge_request: 36321
+author:
+type: added
diff --git a/changelogs/unreleased/add-node-ci-template.yml b/changelogs/unreleased/add-node-ci-template.yml
new file mode 100644
index 00000000000..1ccf68114fc
--- /dev/null
+++ b/changelogs/unreleased/add-node-ci-template.yml
@@ -0,0 +1,5 @@
+---
+title: Add node ci template
+merge_request: 25668
+author:
+type: other
diff --git a/changelogs/unreleased/add-state-events-api-pd.yml b/changelogs/unreleased/add-state-events-api-pd.yml
new file mode 100644
index 00000000000..797b347a689
--- /dev/null
+++ b/changelogs/unreleased/add-state-events-api-pd.yml
@@ -0,0 +1,5 @@
+---
+title: Create API to retrieve resource state events
+merge_request: 35210
+author:
+type: added
diff --git a/changelogs/unreleased/add-static-source-to-ci-pipeline-218685.yml b/changelogs/unreleased/add-static-source-to-ci-pipeline-218685.yml
new file mode 100644
index 00000000000..dfb667d1032
--- /dev/null
+++ b/changelogs/unreleased/add-static-source-to-ci-pipeline-218685.yml
@@ -0,0 +1,5 @@
+---
+title: Ability to use an arbitrary YAML blob to create CI pipelines
+merge_request: 34706
+author:
+type: added
diff --git a/changelogs/unreleased/add-tags-to-queue-attributes.yml b/changelogs/unreleased/add-tags-to-queue-attributes.yml
new file mode 100644
index 00000000000..fd609109a67
--- /dev/null
+++ b/changelogs/unreleased/add-tags-to-queue-attributes.yml
@@ -0,0 +1,5 @@
+---
+title: Add tags to experimental queue selector attributes
+merge_request: 32651
+author:
+type: added
diff --git a/changelogs/unreleased/add-tiller-log-to-artifacts.yml b/changelogs/unreleased/add-tiller-log-to-artifacts.yml
new file mode 100644
index 00000000000..3c00503e929
--- /dev/null
+++ b/changelogs/unreleased/add-tiller-log-to-artifacts.yml
@@ -0,0 +1,6 @@
+---
+title: Add tiller.log to Auto DevOps deployment job artifacts when AUTO_DEVOPS_DEPLOY_DEBUG
+ is set
+merge_request: 35458
+author:
+type: fixed
diff --git a/changelogs/unreleased/add-unique-vistits-data-to-usage-ping.yml b/changelogs/unreleased/add-unique-vistits-data-to-usage-ping.yml
new file mode 100644
index 00000000000..a571cddc39a
--- /dev/null
+++ b/changelogs/unreleased/add-unique-vistits-data-to-usage-ping.yml
@@ -0,0 +1,5 @@
+---
+title: Add the unique visits data to the usage ping
+merge_request: 33146
+author:
+type: changed
diff --git a/changelogs/unreleased/add_build_reference.yml b/changelogs/unreleased/add_build_reference.yml
new file mode 100644
index 00000000000..70db43c810d
--- /dev/null
+++ b/changelogs/unreleased/add_build_reference.yml
@@ -0,0 +1,5 @@
+---
+title: Added build_id column to requirements_management_test_reports table
+merge_request: 33184
+author:
+type: other
diff --git a/changelogs/unreleased/ajk-GQL-user-mrs.yml b/changelogs/unreleased/ajk-GQL-user-mrs.yml
new file mode 100644
index 00000000000..103ae1d33dd
--- /dev/null
+++ b/changelogs/unreleased/ajk-GQL-user-mrs.yml
@@ -0,0 +1,5 @@
+---
+title: Add GraphQL support for authored and assigned Merge Requests
+merge_request: 31227
+author:
+type: added
diff --git a/changelogs/unreleased/ajk-declarative-policy-overrides.yml b/changelogs/unreleased/ajk-declarative-policy-overrides.yml
new file mode 100644
index 00000000000..03f3bd3d7af
--- /dev/null
+++ b/changelogs/unreleased/ajk-declarative-policy-overrides.yml
@@ -0,0 +1,5 @@
+---
+title: Allow policies to override parent rules
+merge_request: 33990
+author:
+type: added
diff --git a/changelogs/unreleased/ajk-design-activity-c.yml b/changelogs/unreleased/ajk-design-activity-c.yml
new file mode 100644
index 00000000000..e9a0ad4b998
--- /dev/null
+++ b/changelogs/unreleased/ajk-design-activity-c.yml
@@ -0,0 +1,5 @@
+---
+title: Add design activity in event streams
+merge_request: 33534
+author:
+type: added
diff --git a/changelogs/unreleased/ajk-design-ref-filter.yml b/changelogs/unreleased/ajk-design-ref-filter.yml
new file mode 100644
index 00000000000..0f77e344d65
--- /dev/null
+++ b/changelogs/unreleased/ajk-design-ref-filter.yml
@@ -0,0 +1,5 @@
+---
+title: Enable GitLab-Flavored Markdown processing for design links
+merge_request: 32446
+author:
+type: added
diff --git a/changelogs/unreleased/ajk-ff-remove-wiki_events.yml b/changelogs/unreleased/ajk-ff-remove-wiki_events.yml
new file mode 100644
index 00000000000..777d16cc45b
--- /dev/null
+++ b/changelogs/unreleased/ajk-ff-remove-wiki_events.yml
@@ -0,0 +1,5 @@
+---
+title: Enable display of wiki events in activity streams
+merge_request: 32475
+author:
+type: changed
diff --git a/changelogs/unreleased/ajk-gql-add-mr-author.yml b/changelogs/unreleased/ajk-gql-add-mr-author.yml
new file mode 100644
index 00000000000..f4d8048f54b
--- /dev/null
+++ b/changelogs/unreleased/ajk-gql-add-mr-author.yml
@@ -0,0 +1,5 @@
+---
+title: Add missing Merge Request fields
+merge_request: 30935
+author:
+type: added
diff --git a/changelogs/unreleased/ajk-gql-diff-stats-file-count.yml b/changelogs/unreleased/ajk-gql-diff-stats-file-count.yml
new file mode 100644
index 00000000000..a5d1c4b0169
--- /dev/null
+++ b/changelogs/unreleased/ajk-gql-diff-stats-file-count.yml
@@ -0,0 +1,5 @@
+---
+title: Add MergeRequest.diffStatsSummary.fileCount to graphql API
+merge_request: 35685
+author:
+type: added
diff --git a/changelogs/unreleased/ajk-gql-labels.yml b/changelogs/unreleased/ajk-gql-labels.yml
new file mode 100644
index 00000000000..8716e536492
--- /dev/null
+++ b/changelogs/unreleased/ajk-gql-labels.yml
@@ -0,0 +1,5 @@
+---
+title: Add GraphQL support for project and group labels
+merge_request: 32113
+author:
+type: added
diff --git a/changelogs/unreleased/ajk-gql-lookahead.yml b/changelogs/unreleased/ajk-gql-lookahead.yml
new file mode 100644
index 00000000000..e1d920ba57a
--- /dev/null
+++ b/changelogs/unreleased/ajk-gql-lookahead.yml
@@ -0,0 +1,5 @@
+---
+title: Add GraphQL lookahead support
+merge_request: 32373
+author:
+type: performance
diff --git a/changelogs/unreleased/ajk-gql-mr-diff-stats.yml b/changelogs/unreleased/ajk-gql-mr-diff-stats.yml
new file mode 100644
index 00000000000..43091947274
--- /dev/null
+++ b/changelogs/unreleased/ajk-gql-mr-diff-stats.yml
@@ -0,0 +1,5 @@
+---
+title: Add diff stats fields to merge request type
+merge_request: 34966
+author:
+type: added
diff --git a/changelogs/unreleased/ajk-gql-mr-resolvers-split.yml b/changelogs/unreleased/ajk-gql-mr-resolvers-split.yml
new file mode 100644
index 00000000000..02c84076fc3
--- /dev/null
+++ b/changelogs/unreleased/ajk-gql-mr-resolvers-split.yml
@@ -0,0 +1,5 @@
+---
+title: Add filters to merge request fields
+merge_request: 32328
+author:
+type: added
diff --git a/changelogs/unreleased/ajk-gql-mr-update.yml b/changelogs/unreleased/ajk-gql-mr-update.yml
new file mode 100644
index 00000000000..7d0b76f4df1
--- /dev/null
+++ b/changelogs/unreleased/ajk-gql-mr-update.yml
@@ -0,0 +1,5 @@
+---
+title: Add mutation to update merge requests
+merge_request: 34748
+author:
+type: added
diff --git a/changelogs/unreleased/ajk-remove-design-events-ff.yml b/changelogs/unreleased/ajk-remove-design-events-ff.yml
new file mode 100644
index 00000000000..15b0142f917
--- /dev/null
+++ b/changelogs/unreleased/ajk-remove-design-events-ff.yml
@@ -0,0 +1,5 @@
+---
+title: Enable design activity events by default
+merge_request: 37107
+author:
+type: added
diff --git a/changelogs/unreleased/ajk-safe-wiki-event-url.yml b/changelogs/unreleased/ajk-safe-wiki-event-url.yml
new file mode 100644
index 00000000000..4e25186e024
--- /dev/null
+++ b/changelogs/unreleased/ajk-safe-wiki-event-url.yml
@@ -0,0 +1,5 @@
+---
+title: Ensure we always generate a valid wiki event URL
+merge_request: 34191
+author:
+type: fixed
diff --git a/changelogs/unreleased/ajk-todos-return-resource.yml b/changelogs/unreleased/ajk-todos-return-resource.yml
new file mode 100644
index 00000000000..d5af4ecdff0
--- /dev/null
+++ b/changelogs/unreleased/ajk-todos-return-resource.yml
@@ -0,0 +1,5 @@
+---
+title: Todo Mutations should return the mutated todos
+merge_request: 35998
+author:
+type: added
diff --git a/changelogs/unreleased/ak-update-google-auth.yml b/changelogs/unreleased/ak-update-google-auth.yml
new file mode 100644
index 00000000000..cb91b9ebe93
--- /dev/null
+++ b/changelogs/unreleased/ak-update-google-auth.yml
@@ -0,0 +1,5 @@
+---
+title: Support IAP protected prometheus installations
+merge_request: 33508
+author:
+type: added
diff --git a/changelogs/unreleased/al-214420-pass-hard-delete-on-snippets-destroy.yml b/changelogs/unreleased/al-214420-pass-hard-delete-on-snippets-destroy.yml
new file mode 100644
index 00000000000..3d4e35b7322
--- /dev/null
+++ b/changelogs/unreleased/al-214420-pass-hard-delete-on-snippets-destroy.yml
@@ -0,0 +1,5 @@
+---
+title: Pass hard delete option to snippets bulk destroy
+merge_request: 33520
+author:
+type: fixed
diff --git a/changelogs/unreleased/al-215200-snippets-by-type-usage-counter.yml b/changelogs/unreleased/al-215200-snippets-by-type-usage-counter.yml
new file mode 100644
index 00000000000..de49e91fe13
--- /dev/null
+++ b/changelogs/unreleased/al-215200-snippets-by-type-usage-counter.yml
@@ -0,0 +1,5 @@
+---
+title: Add index on id and type for Snippets
+merge_request: 32885
+author:
+type: performance
diff --git a/changelogs/unreleased/al-217784-add-blobs-field-to-snippets-in-graphql.yml b/changelogs/unreleased/al-217784-add-blobs-field-to-snippets-in-graphql.yml
new file mode 100644
index 00000000000..89a3d978487
--- /dev/null
+++ b/changelogs/unreleased/al-217784-add-blobs-field-to-snippets-in-graphql.yml
@@ -0,0 +1,5 @@
+---
+title: Add blobs field to SnippetType in GraphQL
+merge_request: 33657
+author:
+type: changed
diff --git a/changelogs/unreleased/alert-assignee-dropdown.yml b/changelogs/unreleased/alert-assignee-dropdown.yml
new file mode 100644
index 00000000000..3ad54932780
--- /dev/null
+++ b/changelogs/unreleased/alert-assignee-dropdown.yml
@@ -0,0 +1,5 @@
+---
+title: Allow the assignment of alerts to users from the alert detail view
+merge_request: 33122
+author:
+type: added
diff --git a/changelogs/unreleased/alert-assignee-list-view.yml b/changelogs/unreleased/alert-assignee-list-view.yml
new file mode 100644
index 00000000000..cb942b87c0b
--- /dev/null
+++ b/changelogs/unreleased/alert-assignee-list-view.yml
@@ -0,0 +1,5 @@
+---
+title: Add database and GraphQL support for alert assignees
+merge_request: 32609
+author:
+type: added
diff --git a/changelogs/unreleased/alert-endpoint.yml b/changelogs/unreleased/alert-endpoint.yml
new file mode 100644
index 00000000000..1ebcdb6e319
--- /dev/null
+++ b/changelogs/unreleased/alert-endpoint.yml
@@ -0,0 +1,5 @@
+---
+title: Move configuration for Alerts endpoint from "Settings > Integration" to "Settings > Operations > Alerts"
+merge_request: 35187
+author:
+type: other
diff --git a/changelogs/unreleased/alert-intergration-trigger-test-docs.yml b/changelogs/unreleased/alert-intergration-trigger-test-docs.yml
new file mode 100644
index 00000000000..7fd7930e5dc
--- /dev/null
+++ b/changelogs/unreleased/alert-intergration-trigger-test-docs.yml
@@ -0,0 +1,5 @@
+---
+title: Add docs for Alert trigger test alerts
+merge_request: 36647
+author:
+type: added
diff --git a/changelogs/unreleased/alert-list-dropdown-header.yml b/changelogs/unreleased/alert-list-dropdown-header.yml
new file mode 100644
index 00000000000..27f8938ac3b
--- /dev/null
+++ b/changelogs/unreleased/alert-list-dropdown-header.yml
@@ -0,0 +1,5 @@
+---
+title: Hide dropdown header on list view
+merge_request: 35954
+author:
+type: other
diff --git a/changelogs/unreleased/alert-management-mobile-alignment.yml b/changelogs/unreleased/alert-management-mobile-alignment.yml
new file mode 100644
index 00000000000..04f2c3224ff
--- /dev/null
+++ b/changelogs/unreleased/alert-management-mobile-alignment.yml
@@ -0,0 +1,5 @@
+---
+title: Update alert management mobile table alignment
+merge_request: 32295
+author:
+type: other
diff --git a/changelogs/unreleased/alert-settings-link-follow-through.yml b/changelogs/unreleased/alert-settings-link-follow-through.yml
new file mode 100644
index 00000000000..2864793837f
--- /dev/null
+++ b/changelogs/unreleased/alert-settings-link-follow-through.yml
@@ -0,0 +1,5 @@
+---
+title: Expand Operations > Alerts section by default via link follow through
+merge_request: 36649
+author:
+type: other
diff --git a/changelogs/unreleased/alert-system-notes-tool-tip.yml b/changelogs/unreleased/alert-system-notes-tool-tip.yml
new file mode 100644
index 00000000000..3129a4ff521
--- /dev/null
+++ b/changelogs/unreleased/alert-system-notes-tool-tip.yml
@@ -0,0 +1,5 @@
+---
+title: Enable ability to assign alerts to users with corresponding system notes and todos
+merge_request: 34360
+author:
+type: added
diff --git a/changelogs/unreleased/alert-system-notes.yml b/changelogs/unreleased/alert-system-notes.yml
new file mode 100644
index 00000000000..2cff9e8b2a4
--- /dev/null
+++ b/changelogs/unreleased/alert-system-notes.yml
@@ -0,0 +1,5 @@
+---
+title: Add system note when assigning user to alert
+merge_request: 33217
+author:
+type: added
diff --git a/changelogs/unreleased/alert-table-classes-hotfix.yml b/changelogs/unreleased/alert-table-classes-hotfix.yml
new file mode 100644
index 00000000000..46d4ae10cbb
--- /dev/null
+++ b/changelogs/unreleased/alert-table-classes-hotfix.yml
@@ -0,0 +1,5 @@
+---
+title: Fix broken CSS classes inside alert management list
+merge_request: 33038
+author:
+type: fixed
diff --git a/changelogs/unreleased/allow-extra-sentry-tags-from-environment.yml b/changelogs/unreleased/allow-extra-sentry-tags-from-environment.yml
new file mode 100644
index 00000000000..11b9dfb5afc
--- /dev/null
+++ b/changelogs/unreleased/allow-extra-sentry-tags-from-environment.yml
@@ -0,0 +1,5 @@
+---
+title: Allow setting extra tags for Sentry exceptions with GITLAB_SENTRY_EXTRA_TAGS
+merge_request: 35965
+author:
+type: changed
diff --git a/changelogs/unreleased/allow_skipped.yml b/changelogs/unreleased/allow_skipped.yml
new file mode 100644
index 00000000000..b920298f42e
--- /dev/null
+++ b/changelogs/unreleased/allow_skipped.yml
@@ -0,0 +1,5 @@
+---
+title: Add setting to allow merge on skipped pipeline
+merge_request: 27490
+author: Mathieu Parent
+type: added
diff --git a/changelogs/unreleased/an-throttle-project-update-repository-storage-worker.yml b/changelogs/unreleased/an-throttle-project-update-repository-storage-worker.yml
new file mode 100644
index 00000000000..f06c1e97b9e
--- /dev/null
+++ b/changelogs/unreleased/an-throttle-project-update-repository-storage-worker.yml
@@ -0,0 +1,5 @@
+---
+title: Throttle ProjectUpdateRepositoryStorageWorker Jobs
+merge_request: 35230
+author:
+type: other
diff --git a/changelogs/unreleased/andr3-218471-code-review-diff-overlaps-sidebar.yml b/changelogs/unreleased/andr3-218471-code-review-diff-overlaps-sidebar.yml
new file mode 100644
index 00000000000..799e32d1578
--- /dev/null
+++ b/changelogs/unreleased/andr3-218471-code-review-diff-overlaps-sidebar.yml
@@ -0,0 +1,5 @@
+---
+title: Fix whitespace changes overgrowing the diff container
+merge_request: 32774
+author:
+type: fixed
diff --git a/changelogs/unreleased/asciidoc-alignment-roles.yml b/changelogs/unreleased/asciidoc-alignment-roles.yml
new file mode 100644
index 00000000000..d53d3705243
--- /dev/null
+++ b/changelogs/unreleased/asciidoc-alignment-roles.yml
@@ -0,0 +1,5 @@
+---
+title: 'AsciiDoc: Add support for built-in alignment roles.'
+merge_request: 32928
+author: mnrvwl
+type: fixed
diff --git a/changelogs/unreleased/assign-alerts-permissions-hotfix.yml b/changelogs/unreleased/assign-alerts-permissions-hotfix.yml
new file mode 100644
index 00000000000..fcbeb807b6e
--- /dev/null
+++ b/changelogs/unreleased/assign-alerts-permissions-hotfix.yml
@@ -0,0 +1,5 @@
+---
+title: Restrict alert assignee user search to current project in alert management details
+merge_request: 34649
+author:
+type: fixed
diff --git a/changelogs/unreleased/assign-alerts-sidebar-base.yml b/changelogs/unreleased/assign-alerts-sidebar-base.yml
new file mode 100644
index 00000000000..3785df5e8e0
--- /dev/null
+++ b/changelogs/unreleased/assign-alerts-sidebar-base.yml
@@ -0,0 +1,5 @@
+---
+title: Assign alerts sidebar base
+merge_request 32642:
+author:
+type: changed
diff --git a/changelogs/unreleased/assign-alerts-sidebar-container-fix.yml b/changelogs/unreleased/assign-alerts-sidebar-container-fix.yml
new file mode 100644
index 00000000000..cdf9bb479ce
--- /dev/null
+++ b/changelogs/unreleased/assign-alerts-sidebar-container-fix.yml
@@ -0,0 +1,5 @@
+---
+title: Assign alerts sidebar container fix
+merge_request: 32743
+author:
+type: other
diff --git a/changelogs/unreleased/astoicescu-adaptDateFormatOnTimeSeries.yml b/changelogs/unreleased/astoicescu-adaptDateFormatOnTimeSeries.yml
new file mode 100644
index 00000000000..29c49885914
--- /dev/null
+++ b/changelogs/unreleased/astoicescu-adaptDateFormatOnTimeSeries.yml
@@ -0,0 +1,5 @@
+---
+title: Add date to x-axes timestamps
+merge_request: 36675
+author:
+type: changed
diff --git a/changelogs/unreleased/astoicescu-addMetricsDashboardActionsMenu.yml b/changelogs/unreleased/astoicescu-addMetricsDashboardActionsMenu.yml
new file mode 100644
index 00000000000..71d6a5f34a9
--- /dev/null
+++ b/changelogs/unreleased/astoicescu-addMetricsDashboardActionsMenu.yml
@@ -0,0 +1,5 @@
+---
+title: Add metrics settings menu to dashboard header
+merge_request: 35028
+author:
+type: added
diff --git a/changelogs/unreleased/astoicescu-allowOotbDashboardsToBeCloned.yml b/changelogs/unreleased/astoicescu-allowOotbDashboardsToBeCloned.yml
new file mode 100644
index 00000000000..0423640571b
--- /dev/null
+++ b/changelogs/unreleased/astoicescu-allowOotbDashboardsToBeCloned.yml
@@ -0,0 +1,5 @@
+---
+title: Allow self monitoring dashboard to be duplicated
+merge_request: 36433
+author:
+type: fixed
diff --git a/changelogs/unreleased/astoicescu-fullWidthCharts.yml b/changelogs/unreleased/astoicescu-fullWidthCharts.yml
new file mode 100644
index 00000000000..c2d7afe5bcb
--- /dev/null
+++ b/changelogs/unreleased/astoicescu-fullWidthCharts.yml
@@ -0,0 +1,5 @@
+---
+title: Add full width to single charts in a row
+merge_request: 34999
+author:
+type: added
diff --git a/changelogs/unreleased/astoicescu-settingsButton.yml b/changelogs/unreleased/astoicescu-settingsButton.yml
new file mode 100644
index 00000000000..51ca3a0d9a5
--- /dev/null
+++ b/changelogs/unreleased/astoicescu-settingsButton.yml
@@ -0,0 +1,5 @@
+---
+title: Add a metrics settings button to the dashboard header
+merge_request: 35848
+author:
+type: added
diff --git a/changelogs/unreleased/autodevops-secrets.yml b/changelogs/unreleased/autodevops-secrets.yml
new file mode 100644
index 00000000000..dd1df110620
--- /dev/null
+++ b/changelogs/unreleased/autodevops-secrets.yml
@@ -0,0 +1,5 @@
+---
+title: Add secret detection template to Auto DevOps
+merge_request: 34467
+author:
+type: changed
diff --git a/changelogs/unreleased/aws-guidance.yml b/changelogs/unreleased/aws-guidance.yml
new file mode 100644
index 00000000000..ab3725c1397
--- /dev/null
+++ b/changelogs/unreleased/aws-guidance.yml
@@ -0,0 +1,5 @@
+---
+title: Adds AWS guidance to CI/CD > Add Variable modal
+merge_request: 34009
+author:
+type: added
diff --git a/changelogs/unreleased/backfill-routes-for-users-migration.yml b/changelogs/unreleased/backfill-routes-for-users-migration.yml
new file mode 100644
index 00000000000..c883d95ca0d
--- /dev/null
+++ b/changelogs/unreleased/backfill-routes-for-users-migration.yml
@@ -0,0 +1,5 @@
+---
+title: Backfill missing routes for Bot users
+merge_request: 35960
+author:
+type: fixed
diff --git a/changelogs/unreleased/background-migration-tracking.yml b/changelogs/unreleased/background-migration-tracking.yml
new file mode 100644
index 00000000000..c58cfc9ba5b
--- /dev/null
+++ b/changelogs/unreleased/background-migration-tracking.yml
@@ -0,0 +1,5 @@
+---
+title: Add background_migration_jobs table to trace background migrations
+merge_request: 35913
+author:
+type: added
diff --git a/changelogs/unreleased/branch-name-default-to-true.yml b/changelogs/unreleased/branch-name-default-to-true.yml
new file mode 100644
index 00000000000..58a2d015a02
--- /dev/null
+++ b/changelogs/unreleased/branch-name-default-to-true.yml
@@ -0,0 +1,6 @@
+---
+title: Default the feature flag to true to always show the default initial branch
+ name setting
+merge_request: 36889
+author:
+type: added
diff --git a/changelogs/unreleased/browse-locked-artifact.yml b/changelogs/unreleased/browse-locked-artifact.yml
new file mode 100644
index 00000000000..d89739a3de6
--- /dev/null
+++ b/changelogs/unreleased/browse-locked-artifact.yml
@@ -0,0 +1,5 @@
+---
+title: Update artifacts section to show when an artifact is locked
+merge_request: 32992
+author:
+type: changed
diff --git a/changelogs/unreleased/bump-gei-version.yml b/changelogs/unreleased/bump-gei-version.yml
new file mode 100644
index 00000000000..aeb429f7b5d
--- /dev/null
+++ b/changelogs/unreleased/bump-gei-version.yml
@@ -0,0 +1,5 @@
+---
+title: Update GitLab Elasticsearch Indexer
+merge_request: 35966
+author:
+type: other
diff --git a/changelogs/unreleased/bump_ci_auto_deploy_0_16.yml b/changelogs/unreleased/bump_ci_auto_deploy_0_16.yml
new file mode 100644
index 00000000000..b222b69f295
--- /dev/null
+++ b/changelogs/unreleased/bump_ci_auto_deploy_0_16.yml
@@ -0,0 +1,5 @@
+---
+title: Update Auto deploy image to v0.16.1, introducing support for AUTO_DEVOPS_DEPLOY_DEBUG
+merge_request: 33799
+author:
+type: changed
diff --git a/changelogs/unreleased/bump_cluster_applications_version.yml b/changelogs/unreleased/bump_cluster_applications_version.yml
new file mode 100644
index 00000000000..7ea7407350d
--- /dev/null
+++ b/changelogs/unreleased/bump_cluster_applications_version.yml
@@ -0,0 +1,5 @@
+---
+title: Bump cluster-applications version to v0.20.0
+merge_request: 34569
+author:
+type: added
diff --git a/changelogs/unreleased/cablett-email-link.yml b/changelogs/unreleased/cablett-email-link.yml
new file mode 100644
index 00000000000..405bcb649c5
--- /dev/null
+++ b/changelogs/unreleased/cablett-email-link.yml
@@ -0,0 +1,5 @@
+---
+title: Add new issue link to email notification header
+merge_request: 32833
+author:
+type: changed
diff --git a/changelogs/unreleased/cablett-merge-request-merged-email-ref.yml b/changelogs/unreleased/cablett-merge-request-merged-email-ref.yml
new file mode 100644
index 00000000000..4d2128c5910
--- /dev/null
+++ b/changelogs/unreleased/cablett-merge-request-merged-email-ref.yml
@@ -0,0 +1,5 @@
+---
+title: Render Merge request reference as link
+merge_request: 33248
+author:
+type: changed
diff --git a/changelogs/unreleased/calebw-update-stuck-runner-message.yml b/changelogs/unreleased/calebw-update-stuck-runner-message.yml
new file mode 100644
index 00000000000..57c48377ae5
--- /dev/null
+++ b/changelogs/unreleased/calebw-update-stuck-runner-message.yml
@@ -0,0 +1,5 @@
+---
+title: Clarify verbiage for stuck job messages.
+merge_request: 32250
+author:
+type: other
diff --git a/changelogs/unreleased/change-pipeline-widget-when-no-pipeline-ran-for-commit.yml b/changelogs/unreleased/change-pipeline-widget-when-no-pipeline-ran-for-commit.yml
new file mode 100644
index 00000000000..6fb36f72b98
--- /dev/null
+++ b/changelogs/unreleased/change-pipeline-widget-when-no-pipeline-ran-for-commit.yml
@@ -0,0 +1,5 @@
+---
+title: Add generic message when no pipeline in MR
+merge_request: 35980
+author:
+type: fixed
diff --git a/changelogs/unreleased/change_from_vendor_specific_to_gitlab.yml b/changelogs/unreleased/change_from_vendor_specific_to_gitlab.yml
new file mode 100644
index 00000000000..f32424612b9
--- /dev/null
+++ b/changelogs/unreleased/change_from_vendor_specific_to_gitlab.yml
@@ -0,0 +1,5 @@
+---
+title: Change from vendor specific to Gitlab
+merge_request: 34576
+author:
+type: changed
diff --git a/changelogs/unreleased/chore-bump-omniauth_openid_connect.yml b/changelogs/unreleased/chore-bump-omniauth_openid_connect.yml
new file mode 100644
index 00000000000..a9415ef2def
--- /dev/null
+++ b/changelogs/unreleased/chore-bump-omniauth_openid_connect.yml
@@ -0,0 +1,5 @@
+---
+title: Bump omniauth_openid_connect to 0.3.5
+merge_request: 34030
+author: Roger Meier
+type: other
diff --git a/changelogs/unreleased/chore-styles-removal.yml b/changelogs/unreleased/chore-styles-removal.yml
new file mode 100644
index 00000000000..b395ad5cf20
--- /dev/null
+++ b/changelogs/unreleased/chore-styles-removal.yml
@@ -0,0 +1,5 @@
+---
+title: Remove temporary datepicker position fix as it is no longer required
+merge_request: 31836
+author: Arun Kumar Mohan
+type: removed
diff --git a/changelogs/unreleased/ci-rust-cargo-test-workspace-option.yml b/changelogs/unreleased/ci-rust-cargo-test-workspace-option.yml
new file mode 100644
index 00000000000..91e3117bffa
--- /dev/null
+++ b/changelogs/unreleased/ci-rust-cargo-test-workspace-option.yml
@@ -0,0 +1,5 @@
+---
+title: "Rust CI template: Replace --all with --workspace on cargo test."
+merge_request: 33517
+author: Markus Becker
+type: fixed
diff --git a/changelogs/unreleased/ci-secrets-persistence.yml b/changelogs/unreleased/ci-secrets-persistence.yml
new file mode 100644
index 00000000000..9105801c352
--- /dev/null
+++ b/changelogs/unreleased/ci-secrets-persistence.yml
@@ -0,0 +1,5 @@
+---
+title: Add ci_builds_metadata.secrets column
+merge_request: 34480
+author:
+type: added
diff --git a/changelogs/unreleased/cilium-cluster-application-artifact-parsing.yml b/changelogs/unreleased/cilium-cluster-application-artifact-parsing.yml
new file mode 100644
index 00000000000..9f6a4018799
--- /dev/null
+++ b/changelogs/unreleased/cilium-cluster-application-artifact-parsing.yml
@@ -0,0 +1,5 @@
+---
+title: Add Cilium to the ParseClusterApplicationsArtifactService
+merge_request: 34695
+author:
+type: added
diff --git a/changelogs/unreleased/cilium-cluster-application-migration.yml b/changelogs/unreleased/cilium-cluster-application-migration.yml
new file mode 100644
index 00000000000..baadbdbdd3a
--- /dev/null
+++ b/changelogs/unreleased/cilium-cluster-application-migration.yml
@@ -0,0 +1,5 @@
+---
+title: Add clusters_applications_cilium DB table
+merge_request: 34601
+author:
+type: added
diff --git a/changelogs/unreleased/cilium-state-metrics.yml b/changelogs/unreleased/cilium-state-metrics.yml
new file mode 100644
index 00000000000..70e8616c79d
--- /dev/null
+++ b/changelogs/unreleased/cilium-state-metrics.yml
@@ -0,0 +1,5 @@
+---
+title: Add installed state metrics for Cilium cluster application
+merge_request: 35808
+author:
+type: added
diff --git a/changelogs/unreleased/clean-up-install-from-source-gitlab-shell.yml b/changelogs/unreleased/clean-up-install-from-source-gitlab-shell.yml
new file mode 100644
index 00000000000..d14cf0f9382
--- /dev/null
+++ b/changelogs/unreleased/clean-up-install-from-source-gitlab-shell.yml
@@ -0,0 +1,5 @@
+---
+title: Clean up gitlab-shell install-from-source path
+merge_request: 33057
+author:
+type: changed
diff --git a/changelogs/unreleased/cluster-application-disable-cleanup-managed.yml b/changelogs/unreleased/cluster-application-disable-cleanup-managed.yml
new file mode 100644
index 00000000000..925a89ed60f
--- /dev/null
+++ b/changelogs/unreleased/cluster-application-disable-cleanup-managed.yml
@@ -0,0 +1,5 @@
+---
+title: Hide cleanup button for clusters with management project
+merge_request: 35576
+author:
+type: changed
diff --git a/changelogs/unreleased/cluster-applications-0-23-0.yml b/changelogs/unreleased/cluster-applications-0-23-0.yml
new file mode 100644
index 00000000000..17c5b664f77
--- /dev/null
+++ b/changelogs/unreleased/cluster-applications-0-23-0.yml
@@ -0,0 +1,5 @@
+---
+title: Update cluster-applications to 0.23.0
+merge_request: 35691
+author:
+type: added
diff --git a/changelogs/unreleased/cluster-applications-0-24-2.yml b/changelogs/unreleased/cluster-applications-0-24-2.yml
new file mode 100644
index 00000000000..d30a4e149ef
--- /dev/null
+++ b/changelogs/unreleased/cluster-applications-0-24-2.yml
@@ -0,0 +1,5 @@
+---
+title: Update cluster-applications to 0.24.2
+merge_request: 36768
+author:
+type: added
diff --git a/changelogs/unreleased/cngo-add-count-to-imported-jira-issues-message.yml b/changelogs/unreleased/cngo-add-count-to-imported-jira-issues-message.yml
new file mode 100644
index 00000000000..a0f1cc5f017
--- /dev/null
+++ b/changelogs/unreleased/cngo-add-count-to-imported-jira-issues-message.yml
@@ -0,0 +1,5 @@
+---
+title: Add count to imported Jira issues message
+merge_request: 36075
+author:
+type: added
diff --git a/changelogs/unreleased/cngo-add-link-text-to-collapsed-left-sidebar.yml b/changelogs/unreleased/cngo-add-link-text-to-collapsed-left-sidebar.yml
new file mode 100644
index 00000000000..0d7f75d24b8
--- /dev/null
+++ b/changelogs/unreleased/cngo-add-link-text-to-collapsed-left-sidebar.yml
@@ -0,0 +1,5 @@
+---
+title: Add link text to collapsed left sidebar links for screen readers
+merge_request: 33866
+author:
+type: fixed
diff --git a/changelogs/unreleased/cngo-fix-border-radius-base.yml b/changelogs/unreleased/cngo-fix-border-radius-base.yml
new file mode 100644
index 00000000000..e2698328927
--- /dev/null
+++ b/changelogs/unreleased/cngo-fix-border-radius-base.yml
@@ -0,0 +1,5 @@
+---
+title: Fix border-radius-base SCSS value
+merge_request: 35740
+author:
+type: fixed
diff --git a/changelogs/unreleased/cngo-improve-header-accessibility.yml b/changelogs/unreleased/cngo-improve-header-accessibility.yml
new file mode 100644
index 00000000000..384db4602b2
--- /dev/null
+++ b/changelogs/unreleased/cngo-improve-header-accessibility.yml
@@ -0,0 +1,5 @@
+---
+title: Improve header acccessibility
+merge_request: 33603
+author:
+type: fixed
diff --git a/changelogs/unreleased/cngo-labels-bug.yml b/changelogs/unreleased/cngo-labels-bug.yml
new file mode 100644
index 00000000000..4386b4e1aa9
--- /dev/null
+++ b/changelogs/unreleased/cngo-labels-bug.yml
@@ -0,0 +1,5 @@
+---
+title: Fix bulk editing labels bug
+merge_request: 36981
+author:
+type: fixed
diff --git a/changelogs/unreleased/cngo-make-markdown-textarea-buttons-tab-accessible.yml b/changelogs/unreleased/cngo-make-markdown-textarea-buttons-tab-accessible.yml
new file mode 100644
index 00000000000..d29cf35e94e
--- /dev/null
+++ b/changelogs/unreleased/cngo-make-markdown-textarea-buttons-tab-accessible.yml
@@ -0,0 +1,5 @@
+---
+title: Make markdown textarea buttons tab accessible
+merge_request: 34300
+author:
+type: fixed
diff --git a/changelogs/unreleased/cngo-make-markdown-textarea-links-tab-accessible.yml b/changelogs/unreleased/cngo-make-markdown-textarea-links-tab-accessible.yml
new file mode 100644
index 00000000000..5da2079f9b4
--- /dev/null
+++ b/changelogs/unreleased/cngo-make-markdown-textarea-links-tab-accessible.yml
@@ -0,0 +1,5 @@
+---
+title: Make markdown textarea links tab-accessible
+merge_request: 33518
+author:
+type: fixed
diff --git a/changelogs/unreleased/create-branch-url.yml b/changelogs/unreleased/create-branch-url.yml
new file mode 100644
index 00000000000..84f89bba805
--- /dev/null
+++ b/changelogs/unreleased/create-branch-url.yml
@@ -0,0 +1,5 @@
+---
+title: Add anchor for creating a branch
+merge_request: 32745
+author:
+type: other
diff --git a/changelogs/unreleased/create-ops-ff-issues-table.yml b/changelogs/unreleased/create-ops-ff-issues-table.yml
new file mode 100644
index 00000000000..9d6395d26cc
--- /dev/null
+++ b/changelogs/unreleased/create-ops-ff-issues-table.yml
@@ -0,0 +1,5 @@
+---
+title: Create operations_feature_flags_issues table
+merge_request: 32876
+author:
+type: added
diff --git a/changelogs/unreleased/create-state-events-pd.yml b/changelogs/unreleased/create-state-events-pd.yml
new file mode 100644
index 00000000000..9c20e3f73ce
--- /dev/null
+++ b/changelogs/unreleased/create-state-events-pd.yml
@@ -0,0 +1,5 @@
+---
+title: Add source to resource state events
+merge_request: 32924
+author:
+type: other
diff --git a/changelogs/unreleased/curd-auto-merge-in-transaction.yml b/changelogs/unreleased/curd-auto-merge-in-transaction.yml
new file mode 100644
index 00000000000..9a9cfbea1c7
--- /dev/null
+++ b/changelogs/unreleased/curd-auto-merge-in-transaction.yml
@@ -0,0 +1,5 @@
+---
+title: Wrap auto merge parameters update in database transaction
+merge_request: 33471
+author:
+type: fixed
diff --git a/changelogs/unreleased/dag-annotations-sticky.yml b/changelogs/unreleased/dag-annotations-sticky.yml
new file mode 100644
index 00000000000..763ee558e97
--- /dev/null
+++ b/changelogs/unreleased/dag-annotations-sticky.yml
@@ -0,0 +1,5 @@
+---
+title: Make DAG annotations stick
+merge_request: 37068
+author:
+type: changed
diff --git a/changelogs/unreleased/dashboardValidationWarnings.yml b/changelogs/unreleased/dashboardValidationWarnings.yml
new file mode 100644
index 00000000000..5c000c7ef51
--- /dev/null
+++ b/changelogs/unreleased/dashboardValidationWarnings.yml
@@ -0,0 +1,5 @@
+---
+title: Add dashboard validation warning to metrics dashboard
+merge_request: 33769
+author:
+type: added
diff --git a/changelogs/unreleased/data-endpoint-for-dag-visualization-2.yml b/changelogs/unreleased/data-endpoint-for-dag-visualization-2.yml
new file mode 100644
index 00000000000..d4b91718861
--- /dev/null
+++ b/changelogs/unreleased/data-endpoint-for-dag-visualization-2.yml
@@ -0,0 +1,5 @@
+---
+title: Add DAG serializer for pipelines controller
+merge_request: 31583
+author:
+type: added
diff --git a/changelogs/unreleased/dblessing-new-sign-in-email-beautification.yml b/changelogs/unreleased/dblessing-new-sign-in-email-beautification.yml
new file mode 100644
index 00000000000..96ad1bc440d
--- /dev/null
+++ b/changelogs/unreleased/dblessing-new-sign-in-email-beautification.yml
@@ -0,0 +1,5 @@
+---
+title: Improve new/unknown sign-in email styling
+merge_request: 32808
+author:
+type: changed
diff --git a/changelogs/unreleased/dblessing-project-bot-prevent-removal.yml b/changelogs/unreleased/dblessing-project-bot-prevent-removal.yml
new file mode 100644
index 00000000000..af29cee48ba
--- /dev/null
+++ b/changelogs/unreleased/dblessing-project-bot-prevent-removal.yml
@@ -0,0 +1,5 @@
+---
+title: Prevent a project bot from being removed as member
+merge_request: 35899
+author:
+type: added
diff --git a/changelogs/unreleased/dblessing_known_sign_in_.yml b/changelogs/unreleased/dblessing_known_sign_in_.yml
new file mode 100644
index 00000000000..eaf1a813150
--- /dev/null
+++ b/changelogs/unreleased/dblessing_known_sign_in_.yml
@@ -0,0 +1,5 @@
+---
+title: Use IP or cookie in known sign-in check
+merge_request: 34102
+author:
+type: changed
diff --git a/changelogs/unreleased/dblessing_known_sign_in_global_setting.yml b/changelogs/unreleased/dblessing_known_sign_in_global_setting.yml
new file mode 100644
index 00000000000..a26a7d951c6
--- /dev/null
+++ b/changelogs/unreleased/dblessing_known_sign_in_global_setting.yml
@@ -0,0 +1,5 @@
+---
+title: Add global setting to disable/enable email notification on unknown sign-ins
+merge_request: 34562
+author:
+type: added
diff --git a/changelogs/unreleased/dedup-merge-request-metrics.yml b/changelogs/unreleased/dedup-merge-request-metrics.yml
new file mode 100644
index 00000000000..276b68ed686
--- /dev/null
+++ b/changelogs/unreleased/dedup-merge-request-metrics.yml
@@ -0,0 +1,5 @@
+---
+title: Deduplicate merge_request_metrics table
+merge_request: 29566
+author:
+type: other
diff --git a/changelogs/unreleased/defect-pipeline-related-mr-spinner.yml b/changelogs/unreleased/defect-pipeline-related-mr-spinner.yml
new file mode 100644
index 00000000000..1468c266538
--- /dev/null
+++ b/changelogs/unreleased/defect-pipeline-related-mr-spinner.yml
@@ -0,0 +1,6 @@
+---
+title: Fix infinite loading spinner for related merge requests on commit pipelines
+ tab
+merge_request: 36077
+author:
+type: fixed
diff --git a/changelogs/unreleased/dennis-project-templates-add-gitbook-logo.yml b/changelogs/unreleased/dennis-project-templates-add-gitbook-logo.yml
new file mode 100644
index 00000000000..ebe823442f0
--- /dev/null
+++ b/changelogs/unreleased/dennis-project-templates-add-gitbook-logo.yml
@@ -0,0 +1,5 @@
+---
+title: Add GitBook logo to project templates
+merge_request: 33403
+author:
+type: changed
diff --git a/changelogs/unreleased/dennis-project-templates-add-gomicro-logo.yml b/changelogs/unreleased/dennis-project-templates-add-gomicro-logo.yml
new file mode 100644
index 00000000000..19bce8e3c28
--- /dev/null
+++ b/changelogs/unreleased/dennis-project-templates-add-gomicro-logo.yml
@@ -0,0 +1,5 @@
+---
+title: Add GoMicro logo to project templates
+merge_request: 33404
+author:
+type: changed
diff --git a/changelogs/unreleased/dennis-project-templates-add-hexo-logo.yml b/changelogs/unreleased/dennis-project-templates-add-hexo-logo.yml
new file mode 100644
index 00000000000..59ae2a3a99a
--- /dev/null
+++ b/changelogs/unreleased/dennis-project-templates-add-hexo-logo.yml
@@ -0,0 +1,5 @@
+---
+title: Add Hexo logo to project templates
+merge_request: 33406
+author:
+type: changed
diff --git a/changelogs/unreleased/dennis-project-templates-add-hugo-logo.yml b/changelogs/unreleased/dennis-project-templates-add-hugo-logo.yml
new file mode 100644
index 00000000000..df2305c1406
--- /dev/null
+++ b/changelogs/unreleased/dennis-project-templates-add-hugo-logo.yml
@@ -0,0 +1,5 @@
+---
+title: Add Hugo logo to project templates
+merge_request: 33402
+author:
+type: changed
diff --git a/changelogs/unreleased/dennis-project-templates-add-jekyll-logo.yml b/changelogs/unreleased/dennis-project-templates-add-jekyll-logo.yml
new file mode 100644
index 00000000000..1cb28444cbc
--- /dev/null
+++ b/changelogs/unreleased/dennis-project-templates-add-jekyll-logo.yml
@@ -0,0 +1,5 @@
+---
+title: Add Jekyll logo to project templates
+merge_request: 33405
+author:
+type: changed
diff --git a/changelogs/unreleased/dennis-update-webhooks-page-from-whitelist-to-allowlist.yml b/changelogs/unreleased/dennis-update-webhooks-page-from-whitelist-to-allowlist.yml
new file mode 100644
index 00000000000..80d2ec6d7ba
--- /dev/null
+++ b/changelogs/unreleased/dennis-update-webhooks-page-from-whitelist-to-allowlist.yml
@@ -0,0 +1,5 @@
+---
+title: Update local IP address and domain name allow list input label
+merge_request: 33812
+author:
+type: changed
diff --git a/changelogs/unreleased/design-management-transitions.yml b/changelogs/unreleased/design-management-transitions.yml
new file mode 100644
index 00000000000..755f5d7ac2d
--- /dev/null
+++ b/changelogs/unreleased/design-management-transitions.yml
@@ -0,0 +1,5 @@
+---
+title: Improve animations of design note selection in design management
+merge_request: 36927
+author:
+type: changed
diff --git a/changelogs/unreleased/design-view-scrolling-issue-bug.yml b/changelogs/unreleased/design-view-scrolling-issue-bug.yml
new file mode 100644
index 00000000000..d47ecfa848a
--- /dev/null
+++ b/changelogs/unreleased/design-view-scrolling-issue-bug.yml
@@ -0,0 +1,5 @@
+---
+title: Remove ability to scroll Issue while in Design View
+merge_request: 29881
+author:
+type: fixed
diff --git a/changelogs/unreleased/disable_ilm_on_ELK_yaml.yml b/changelogs/unreleased/disable_ilm_on_ELK_yaml.yml
new file mode 100644
index 00000000000..d48f1d935da
--- /dev/null
+++ b/changelogs/unreleased/disable_ilm_on_ELK_yaml.yml
@@ -0,0 +1,5 @@
+---
+title: Disable ILM on ELK vendor yaml
+merge_request: 35398
+author:
+type: fixed
diff --git a/changelogs/unreleased/dmishunov-editor-lite-extensions.yml b/changelogs/unreleased/dmishunov-editor-lite-extensions.yml
new file mode 100644
index 00000000000..e8eb64bcffe
--- /dev/null
+++ b/changelogs/unreleased/dmishunov-editor-lite-extensions.yml
@@ -0,0 +1,5 @@
+---
+title: Support extensibility for Editor Lite
+merge_request: 35008
+author:
+type: added
diff --git a/changelogs/unreleased/docs-auto-build-cnb-custom-builder.yml b/changelogs/unreleased/docs-auto-build-cnb-custom-builder.yml
new file mode 100644
index 00000000000..8e96cf2d53a
--- /dev/null
+++ b/changelogs/unreleased/docs-auto-build-cnb-custom-builder.yml
@@ -0,0 +1,5 @@
+---
+title: Customize the Cloud Native Buildpack builder used with Auto Build
+merge_request: 32691
+author:
+type: added
diff --git a/changelogs/unreleased/docs-firefox-u2f-api.yml b/changelogs/unreleased/docs-firefox-u2f-api.yml
new file mode 100644
index 00000000000..c15a81694f5
--- /dev/null
+++ b/changelogs/unreleased/docs-firefox-u2f-api.yml
@@ -0,0 +1,5 @@
+---
+title: Update U2F docs for Firefox 67+
+merge_request: 32289
+author: Takuya Noguchi
+type: other
diff --git a/changelogs/unreleased/docs-fj-update-web-ide-terminal-doc-core.yml b/changelogs/unreleased/docs-fj-update-web-ide-terminal-doc-core.yml
new file mode 100644
index 00000000000..a48e6e235f9
--- /dev/null
+++ b/changelogs/unreleased/docs-fj-update-web-ide-terminal-doc-core.yml
@@ -0,0 +1,5 @@
+---
+title: Update docs to reflect move web IDE Terminal and file sync to Core
+merge_request: 33419
+author:
+type: other
diff --git a/changelogs/unreleased/docs-links-from-ui-2.yml b/changelogs/unreleased/docs-links-from-ui-2.yml
new file mode 100644
index 00000000000..82e99d385e4
--- /dev/null
+++ b/changelogs/unreleased/docs-links-from-ui-2.yml
@@ -0,0 +1,5 @@
+---
+title: Update more UI links to docs in core features
+merge_request: 36174
+author:
+type: other
diff --git a/changelogs/unreleased/docs-links-from-ui.yml b/changelogs/unreleased/docs-links-from-ui.yml
new file mode 100644
index 00000000000..c845768a223
--- /dev/null
+++ b/changelogs/unreleased/docs-links-from-ui.yml
@@ -0,0 +1,5 @@
+---
+title: Update UI links to docs in core features
+merge_request: 35488
+author:
+type: other
diff --git a/changelogs/unreleased/docs-saml-sso-from-core-at-self-hosted.yml b/changelogs/unreleased/docs-saml-sso-from-core-at-self-hosted.yml
new file mode 100644
index 00000000000..9ad183db112
--- /dev/null
+++ b/changelogs/unreleased/docs-saml-sso-from-core-at-self-hosted.yml
@@ -0,0 +1,5 @@
+---
+title: Specify tiers for SAML SSO at self-hosted plans
+merge_request: 34040
+author: Takuya Noguchi
+type: other
diff --git a/changelogs/unreleased/docs-u2f-version-history.yml b/changelogs/unreleased/docs-u2f-version-history.yml
new file mode 100644
index 00000000000..e847faa317e
--- /dev/null
+++ b/changelogs/unreleased/docs-u2f-version-history.yml
@@ -0,0 +1,5 @@
+---
+title: Add version history information on U2F support
+merge_request: 33229
+author: Takuya Noguchi
+type: other
diff --git a/changelogs/unreleased/dont-include-changes-in-webhook-payload-when-old-associations-is-empty.yml b/changelogs/unreleased/dont-include-changes-in-webhook-payload-when-old-associations-is-empty.yml
new file mode 100644
index 00000000000..37d512f5d56
--- /dev/null
+++ b/changelogs/unreleased/dont-include-changes-in-webhook-payload-when-old-associations-is-empty.yml
@@ -0,0 +1,5 @@
+---
+title: Don't include changes in webhook payload when old associations are empty
+merge_request: 35158
+author:
+type: fixed
diff --git a/changelogs/unreleased/downloadable_reports.yml b/changelogs/unreleased/downloadable_reports.yml
new file mode 100644
index 00000000000..58f2c6d5ee1
--- /dev/null
+++ b/changelogs/unreleased/downloadable_reports.yml
@@ -0,0 +1,5 @@
+---
+title: Add secret_detection to DOWNLOADABLE_TYPES
+merge_request: 34313
+author:
+type: added
diff --git a/changelogs/unreleased/downstream-pipeline-ux.yml b/changelogs/unreleased/downstream-pipeline-ux.yml
new file mode 100644
index 00000000000..d8aec39bc41
--- /dev/null
+++ b/changelogs/unreleased/downstream-pipeline-ux.yml
@@ -0,0 +1,5 @@
+---
+title: Add correlation between trigger job and child pipeline
+merge_request: 36750
+author:
+type: changed
diff --git a/changelogs/unreleased/drop-old-non-unique-index-on-mr-metrics.yml b/changelogs/unreleased/drop-old-non-unique-index-on-mr-metrics.yml
new file mode 100644
index 00000000000..369ff3100bd
--- /dev/null
+++ b/changelogs/unreleased/drop-old-non-unique-index-on-mr-metrics.yml
@@ -0,0 +1,5 @@
+---
+title: Remove non-unique index on `merge_request_metrics.merge_request_id` column
+merge_request: 36170
+author:
+type: changed
diff --git a/changelogs/unreleased/dz-redirect-unscoped-pipelines-routes.yml b/changelogs/unreleased/dz-redirect-unscoped-pipelines-routes.yml
new file mode 100644
index 00000000000..dbe7c17f61d
--- /dev/null
+++ b/changelogs/unreleased/dz-redirect-unscoped-pipelines-routes.yml
@@ -0,0 +1,5 @@
+---
+title: Move pipelines routing under /-/ scope
+merge_request: 30730
+author:
+type: changed
diff --git a/changelogs/unreleased/dz-scope-project-snippet-routes.yml b/changelogs/unreleased/dz-scope-project-snippet-routes.yml
new file mode 100644
index 00000000000..66902b26bd9
--- /dev/null
+++ b/changelogs/unreleased/dz-scope-project-snippet-routes.yml
@@ -0,0 +1,5 @@
+---
+title: Copy project snippet routes under - scope
+merge_request: 35022
+author:
+type: other
diff --git a/changelogs/unreleased/dz-scope-snippet-routes.yml b/changelogs/unreleased/dz-scope-snippet-routes.yml
new file mode 100644
index 00000000000..7e6fe32a196
--- /dev/null
+++ b/changelogs/unreleased/dz-scope-snippet-routes.yml
@@ -0,0 +1,5 @@
+---
+title: Copy snippet route under - scope
+merge_request: 35020
+author:
+type: other
diff --git a/changelogs/unreleased/eb-drop-old-daily-report-results-table.yml b/changelogs/unreleased/eb-drop-old-daily-report-results-table.yml
new file mode 100644
index 00000000000..ed0a3e7e5a8
--- /dev/null
+++ b/changelogs/unreleased/eb-drop-old-daily-report-results-table.yml
@@ -0,0 +1,5 @@
+---
+title: Add migration to drop unused daily report results table
+merge_request: 36102
+author:
+type: other
diff --git a/changelogs/unreleased/eb-report-file-size-mechanism.yml b/changelogs/unreleased/eb-report-file-size-mechanism.yml
new file mode 100644
index 00000000000..e8f30d9149d
--- /dev/null
+++ b/changelogs/unreleased/eb-report-file-size-mechanism.yml
@@ -0,0 +1,5 @@
+---
+title: Add plan limits for max size per artifact type
+merge_request: 34767
+author:
+type: added
diff --git a/changelogs/unreleased/ee-app-services-1.yml b/changelogs/unreleased/ee-app-services-1.yml
new file mode 100644
index 00000000000..bbd916a9d2c
--- /dev/null
+++ b/changelogs/unreleased/ee-app-services-1.yml
@@ -0,0 +1,5 @@
+---
+title: Move prepend to last in ee-app-services
+merge_request: 31838
+author: Rajendra Kadam
+type: fixed
diff --git a/changelogs/unreleased/emilyring-cluster-list-refactor-provider-icon.yml b/changelogs/unreleased/emilyring-cluster-list-refactor-provider-icon.yml
new file mode 100644
index 00000000000..b16051b7303
--- /dev/null
+++ b/changelogs/unreleased/emilyring-cluster-list-refactor-provider-icon.yml
@@ -0,0 +1,5 @@
+---
+title: Added provider type icon to cluster list
+merge_request: 33196
+author:
+type: changed
diff --git a/changelogs/unreleased/emilyring-remove-tf-plan-name.yml b/changelogs/unreleased/emilyring-remove-tf-plan-name.yml
new file mode 100644
index 00000000000..c7bbb46c82f
--- /dev/null
+++ b/changelogs/unreleased/emilyring-remove-tf-plan-name.yml
@@ -0,0 +1,5 @@
+---
+title: Removed default artifact name for Terraform template
+merge_request: 34557
+author:
+type: fixed
diff --git a/changelogs/unreleased/emilyring-terraform-translation.yml b/changelogs/unreleased/emilyring-terraform-translation.yml
new file mode 100644
index 00000000000..f106c161997
--- /dev/null
+++ b/changelogs/unreleased/emilyring-terraform-translation.yml
@@ -0,0 +1,5 @@
+---
+title: Fixed translation errors on MR Widget
+merge_request: 35888
+author:
+type: fixed
diff --git a/changelogs/unreleased/emilyring-tf-widget-multiple.yml b/changelogs/unreleased/emilyring-tf-widget-multiple.yml
new file mode 100644
index 00000000000..1bd7a002ede
--- /dev/null
+++ b/changelogs/unreleased/emilyring-tf-widget-multiple.yml
@@ -0,0 +1,5 @@
+---
+title: Allow Tf Plan to genrate multiple reports
+merge_request: 33867
+author:
+type: changed
diff --git a/changelogs/unreleased/emoji-api-model.yml b/changelogs/unreleased/emoji-api-model.yml
new file mode 100644
index 00000000000..8e2d9a89808
--- /dev/null
+++ b/changelogs/unreleased/emoji-api-model.yml
@@ -0,0 +1,5 @@
+---
+title: Add custom emoji model and database table
+merge_request: 24229
+author: Rajendra Kadam
+type: added
diff --git a/changelogs/unreleased/enable-atomic-processing-by-default.yml b/changelogs/unreleased/enable-atomic-processing-by-default.yml
new file mode 100644
index 00000000000..691728b4ae4
--- /dev/null
+++ b/changelogs/unreleased/enable-atomic-processing-by-default.yml
@@ -0,0 +1,5 @@
+---
+title: Enable CI Atomic Processing by default
+merge_request:
+author:
+type: performance
diff --git a/changelogs/unreleased/enable-bulk-insert-for-needs.yml b/changelogs/unreleased/enable-bulk-insert-for-needs.yml
new file mode 100644
index 00000000000..d4b8f8b958e
--- /dev/null
+++ b/changelogs/unreleased/enable-bulk-insert-for-needs.yml
@@ -0,0 +1,5 @@
+---
+title: Enable BulkInsertSafe on Ci::BuildNeed
+merge_request: 36815
+author:
+type: performance
diff --git a/changelogs/unreleased/exclude-server-fields-from-exceptions-log.yml b/changelogs/unreleased/exclude-server-fields-from-exceptions-log.yml
new file mode 100644
index 00000000000..2fac0330b5c
--- /dev/null
+++ b/changelogs/unreleased/exclude-server-fields-from-exceptions-log.yml
@@ -0,0 +1,5 @@
+---
+title: Exclude extra.server fields from exceptions_json.log
+merge_request: 32770
+author:
+type: changed
diff --git a/changelogs/unreleased/experimentation-cookie-domain.yml b/changelogs/unreleased/experimentation-cookie-domain.yml
new file mode 100644
index 00000000000..5184837934d
--- /dev/null
+++ b/changelogs/unreleased/experimentation-cookie-domain.yml
@@ -0,0 +1,5 @@
+---
+title: Set experiementation cookie for GitLab domain only
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/faster-label-transfer-queries.yml b/changelogs/unreleased/faster-label-transfer-queries.yml
new file mode 100644
index 00000000000..f5415b00a6d
--- /dev/null
+++ b/changelogs/unreleased/faster-label-transfer-queries.yml
@@ -0,0 +1,5 @@
+---
+title: Remove unindexed condition on label transfer
+merge_request: 37060
+author:
+type: performance
diff --git a/changelogs/unreleased/feat-admin-pages-show-custom-attributes.yml b/changelogs/unreleased/feat-admin-pages-show-custom-attributes.yml
new file mode 100644
index 00000000000..6a9efcff172
--- /dev/null
+++ b/changelogs/unreleased/feat-admin-pages-show-custom-attributes.yml
@@ -0,0 +1,5 @@
+---
+title: Show custom attributes within Admin Pages
+merge_request: 34017
+author: Roger Meier
+type: added
diff --git a/changelogs/unreleased/feat-use-new-api-icon.yml b/changelogs/unreleased/feat-use-new-api-icon.yml
new file mode 100644
index 00000000000..f45279946b8
--- /dev/null
+++ b/changelogs/unreleased/feat-use-new-api-icon.yml
@@ -0,0 +1,5 @@
+---
+title: Use new icon for api preview
+merge_request: 34700
+author: Roger Meier
+type: added
diff --git a/changelogs/unreleased/feature-api-add-bridge-api-endpoint.yml b/changelogs/unreleased/feature-api-add-bridge-api-endpoint.yml
new file mode 100644
index 00000000000..02298d74233
--- /dev/null
+++ b/changelogs/unreleased/feature-api-add-bridge-api-endpoint.yml
@@ -0,0 +1,5 @@
+---
+title: Add API endpoint for listing bridge jobs.
+merge_request: 31370
+author: Abhijith Sivarajan
+type: added
diff --git a/changelogs/unreleased/feature-bump-cluster-applications-to-0-16-0.yml b/changelogs/unreleased/feature-bump-cluster-applications-to-0-16-0.yml
new file mode 100644
index 00000000000..f548d243a05
--- /dev/null
+++ b/changelogs/unreleased/feature-bump-cluster-applications-to-0-16-0.yml
@@ -0,0 +1,5 @@
+---
+title: Adds PostHog as a CI/CD Managed Application
+merge_request: 32856
+author:
+type: added
diff --git a/changelogs/unreleased/feature-gb-artifacts-exclude-feature-flag.yml b/changelogs/unreleased/feature-gb-artifacts-exclude-feature-flag.yml
new file mode 100644
index 00000000000..1bb65ee2127
--- /dev/null
+++ b/changelogs/unreleased/feature-gb-artifacts-exclude-feature-flag.yml
@@ -0,0 +1,5 @@
+---
+title: Add support for artifacts/exclude configuration
+merge_request: 33170
+author:
+type: added
diff --git a/changelogs/unreleased/feature-secure-eslint-to-core.yml b/changelogs/unreleased/feature-secure-eslint-to-core.yml
new file mode 100644
index 00000000000..d93ef151a6a
--- /dev/null
+++ b/changelogs/unreleased/feature-secure-eslint-to-core.yml
@@ -0,0 +1,5 @@
+---
+title: Bring SAST to Core - eslint
+merge_request: 36392
+author:
+type: changed
diff --git a/changelogs/unreleased/feature-show-memory-cpu-on-cluster-list.yml b/changelogs/unreleased/feature-show-memory-cpu-on-cluster-list.yml
new file mode 100644
index 00000000000..e3375d85821
--- /dev/null
+++ b/changelogs/unreleased/feature-show-memory-cpu-on-cluster-list.yml
@@ -0,0 +1,5 @@
+---
+title: Adds cluster CPU and Memory to cluster index
+merge_request: 32601
+author:
+type: changed
diff --git a/changelogs/unreleased/filter-from-url-query-params-pipeline.yml b/changelogs/unreleased/filter-from-url-query-params-pipeline.yml
new file mode 100644
index 00000000000..e1de53cb4ef
--- /dev/null
+++ b/changelogs/unreleased/filter-from-url-query-params-pipeline.yml
@@ -0,0 +1,5 @@
+---
+title: Filter pipelines based on url query params
+merge_request: 32230
+author:
+type: added
diff --git a/changelogs/unreleased/filter-pipelines-by-status.yml b/changelogs/unreleased/filter-pipelines-by-status.yml
new file mode 100644
index 00000000000..e768fb3e112
--- /dev/null
+++ b/changelogs/unreleased/filter-pipelines-by-status.yml
@@ -0,0 +1,5 @@
+---
+title: Filter pipelines by status
+merge_request: 32151
+author:
+type: added
diff --git a/changelogs/unreleased/filter-pipelines-by-tag-name.yml b/changelogs/unreleased/filter-pipelines-by-tag-name.yml
new file mode 100644
index 00000000000..259fd8e9808
--- /dev/null
+++ b/changelogs/unreleased/filter-pipelines-by-tag-name.yml
@@ -0,0 +1,5 @@
+---
+title: Filter Pipelines by Tag Name
+merge_request: 32470
+author:
+type: added
diff --git a/changelogs/unreleased/fix-atomic-processing-lock-version.yml b/changelogs/unreleased/fix-atomic-processing-lock-version.yml
new file mode 100644
index 00000000000..9db891d651f
--- /dev/null
+++ b/changelogs/unreleased/fix-atomic-processing-lock-version.yml
@@ -0,0 +1,5 @@
+---
+title: Fix atomic processing bumping a lock_version
+merge_request: 32914
+author:
+type: fixed
diff --git a/changelogs/unreleased/fix-attach-file-icon-margin.yml b/changelogs/unreleased/fix-attach-file-icon-margin.yml
new file mode 100644
index 00000000000..fbd44ece540
--- /dev/null
+++ b/changelogs/unreleased/fix-attach-file-icon-margin.yml
@@ -0,0 +1,5 @@
+---
+title: Update icon associated with attach a file actions
+merge_request: 34401
+author:
+type: other
diff --git a/changelogs/unreleased/fix-ci-variables-regression.yml b/changelogs/unreleased/fix-ci-variables-regression.yml
new file mode 100644
index 00000000000..9aefac9ec63
--- /dev/null
+++ b/changelogs/unreleased/fix-ci-variables-regression.yml
@@ -0,0 +1,5 @@
+---
+title: Fix not being able to add more than one CI variable through the UI
+merge_request: 37001
+author:
+type: fixed
diff --git a/changelogs/unreleased/fix-design-note-border-radius.yml b/changelogs/unreleased/fix-design-note-border-radius.yml
new file mode 100644
index 00000000000..af67e4ffce8
--- /dev/null
+++ b/changelogs/unreleased/fix-design-note-border-radius.yml
@@ -0,0 +1,5 @@
+---
+title: Fix background overflow when design note is selected
+merge_request: 36931
+author:
+type: fixed
diff --git a/changelogs/unreleased/fix-design-notes-filename-duplication.yml b/changelogs/unreleased/fix-design-notes-filename-duplication.yml
new file mode 100644
index 00000000000..5d82323f6f2
--- /dev/null
+++ b/changelogs/unreleased/fix-design-notes-filename-duplication.yml
@@ -0,0 +1,5 @@
+---
+title: Fix filename duplication in design notes in activity feeds
+merge_request: 32823
+author: Arun Kumar Mohan
+type: fixed
diff --git a/changelogs/unreleased/fix-design-todos-filename-duplication.yml b/changelogs/unreleased/fix-design-todos-filename-duplication.yml
new file mode 100644
index 00000000000..901e8bbc8e3
--- /dev/null
+++ b/changelogs/unreleased/fix-design-todos-filename-duplication.yml
@@ -0,0 +1,5 @@
+---
+title: Fix duplicate filename displayed in design todos
+merge_request: 32274
+author: Arun Kumar Mohan
+type: fixed
diff --git a/changelogs/unreleased/fix-ecs-detached-branch-pipeline.yml b/changelogs/unreleased/fix-ecs-detached-branch-pipeline.yml
new file mode 100644
index 00000000000..cca6bb4eac3
--- /dev/null
+++ b/changelogs/unreleased/fix-ecs-detached-branch-pipeline.yml
@@ -0,0 +1,5 @@
+---
+title: Fix CI rules for ECS related jobs
+merge_request: 33527
+author:
+type: fixed
diff --git a/changelogs/unreleased/fix-failing-dashboard-schema-validation-calls.yml b/changelogs/unreleased/fix-failing-dashboard-schema-validation-calls.yml
new file mode 100644
index 00000000000..6d354af5974
--- /dev/null
+++ b/changelogs/unreleased/fix-failing-dashboard-schema-validation-calls.yml
@@ -0,0 +1,5 @@
+---
+title: Fix failing dashboard schema validation calls
+merge_request: 37108
+author:
+type: fixed
diff --git a/changelogs/unreleased/fix-gb-pipeline-index-latest.yml b/changelogs/unreleased/fix-gb-pipeline-index-latest.yml
new file mode 100644
index 00000000000..6c982337dd7
--- /dev/null
+++ b/changelogs/unreleased/fix-gb-pipeline-index-latest.yml
@@ -0,0 +1,5 @@
+---
+title: Improve pipeline index controller performance by resolving Gitaly N+1 calls
+merge_request: 34160
+author:
+type: performance
diff --git a/changelogs/unreleased/fix-group-api-archived.yml b/changelogs/unreleased/fix-group-api-archived.yml
new file mode 100644
index 00000000000..549d6f48fe9
--- /dev/null
+++ b/changelogs/unreleased/fix-group-api-archived.yml
@@ -0,0 +1,5 @@
+---
+title: Remove default "archived" parameter value from Groups API's projects endpoint
+merge_request: 34018
+author: Justin Sleep
+type: fixed
diff --git a/changelogs/unreleased/fix-group-transfer-to-subgroup.yml b/changelogs/unreleased/fix-group-transfer-to-subgroup.yml
new file mode 100644
index 00000000000..41294ceca36
--- /dev/null
+++ b/changelogs/unreleased/fix-group-transfer-to-subgroup.yml
@@ -0,0 +1,5 @@
+---
+title: Fix group transfer service to deny moving group to its subgroup
+merge_request: 31495
+author: Abhisek Datta
+type: fixed
diff --git a/changelogs/unreleased/fix-invalid-error-tracking-method.yml b/changelogs/unreleased/fix-invalid-error-tracking-method.yml
new file mode 100644
index 00000000000..90a49dcd8bd
--- /dev/null
+++ b/changelogs/unreleased/fix-invalid-error-tracking-method.yml
@@ -0,0 +1,5 @@
+---
+title: Fix NoMethodError by using the correct method to report exceptions to Sentry
+merge_request: 33260
+author:
+type: fixed
diff --git a/changelogs/unreleased/fix-logrotate-su-parameter.yml b/changelogs/unreleased/fix-logrotate-su-parameter.yml
new file mode 100644
index 00000000000..a35fec0e651
--- /dev/null
+++ b/changelogs/unreleased/fix-logrotate-su-parameter.yml
@@ -0,0 +1,5 @@
+---
+title: Make logrotate run as git user for source installations
+merge_request: 35519
+author:
+type: security
diff --git a/changelogs/unreleased/fix-max_import_size.yml b/changelogs/unreleased/fix-max_import_size.yml
new file mode 100644
index 00000000000..0cd9ceaa4d9
--- /dev/null
+++ b/changelogs/unreleased/fix-max_import_size.yml
@@ -0,0 +1,5 @@
+---
+title: Add max import file size option
+merge_request: 33215
+author: Roger Meier
+type: added
diff --git a/changelogs/unreleased/fix-omniauth-buttons-js.yml b/changelogs/unreleased/fix-omniauth-buttons-js.yml
new file mode 100644
index 00000000000..9350af2cdd0
--- /dev/null
+++ b/changelogs/unreleased/fix-omniauth-buttons-js.yml
@@ -0,0 +1,5 @@
+---
+title: Avoid javascript for omniauth logins
+merge_request: 33459
+author: Diego Louzán
+type: other
diff --git a/changelogs/unreleased/fix-open-sse-on-subgroups.yml b/changelogs/unreleased/fix-open-sse-on-subgroups.yml
new file mode 100644
index 00000000000..41ef3a5054c
--- /dev/null
+++ b/changelogs/unreleased/fix-open-sse-on-subgroups.yml
@@ -0,0 +1,5 @@
+---
+title: 'Static Site Editor can’t be opened in projects belonging to a subgroup'
+merge_request: 35378
+author:
+type: fixed
diff --git a/changelogs/unreleased/fix-pagination-for-resource-milestone-events-api.yml b/changelogs/unreleased/fix-pagination-for-resource-milestone-events-api.yml
new file mode 100644
index 00000000000..18cc7e3bc29
--- /dev/null
+++ b/changelogs/unreleased/fix-pagination-for-resource-milestone-events-api.yml
@@ -0,0 +1,5 @@
+---
+title: Fix pagination for resource milestone events api
+merge_request: 33845
+author:
+type: fixed
diff --git a/changelogs/unreleased/fix-pagination-link.yml b/changelogs/unreleased/fix-pagination-link.yml
new file mode 100644
index 00000000000..d2c1fc1eb94
--- /dev/null
+++ b/changelogs/unreleased/fix-pagination-link.yml
@@ -0,0 +1,5 @@
+---
+title: Fix pagination link header
+merge_request: 33714
+author: Max Wittig
+type: fixed
diff --git a/changelogs/unreleased/fix-routes-for-internal-users.yml b/changelogs/unreleased/fix-routes-for-internal-users.yml
new file mode 100644
index 00000000000..5f96dd7f227
--- /dev/null
+++ b/changelogs/unreleased/fix-routes-for-internal-users.yml
@@ -0,0 +1,5 @@
+---
+title: Create associated routes when a new bot user is created
+merge_request: 35711
+author:
+type: fixed
diff --git a/changelogs/unreleased/fix-runner-hearbeat.yml b/changelogs/unreleased/fix-runner-hearbeat.yml
new file mode 100644
index 00000000000..8125bad48ab
--- /dev/null
+++ b/changelogs/unreleased/fix-runner-hearbeat.yml
@@ -0,0 +1,5 @@
+---
+title: Fix Runner heartbeats that results in considering them offline
+merge_request: 32851
+author:
+type: fixed
diff --git a/changelogs/unreleased/fix-same-family-pipeline-ids.yml b/changelogs/unreleased/fix-same-family-pipeline-ids.yml
new file mode 100644
index 00000000000..684fd4d6060
--- /dev/null
+++ b/changelogs/unreleased/fix-same-family-pipeline-ids.yml
@@ -0,0 +1,5 @@
+---
+title: Use an array for fetching same_family_pipeline_ids
+merge_request: 36883
+author:
+type: fixed
diff --git a/changelogs/unreleased/fix-selecting-status-emoji-twice.yml b/changelogs/unreleased/fix-selecting-status-emoji-twice.yml
new file mode 100644
index 00000000000..b4da2f096b6
--- /dev/null
+++ b/changelogs/unreleased/fix-selecting-status-emoji-twice.yml
@@ -0,0 +1,5 @@
+---
+title: Fix invisible emoji modal on Set Status form when clicked the second time
+merge_request: 33398
+author:
+type: fixed
diff --git a/changelogs/unreleased/fix-sse-escaping-sequences.yml b/changelogs/unreleased/fix-sse-escaping-sequences.yml
new file mode 100644
index 00000000000..7658fac1f3a
--- /dev/null
+++ b/changelogs/unreleased/fix-sse-escaping-sequences.yml
@@ -0,0 +1,5 @@
+---
+title: Fix incorrect text escaping in the Static Site Editor
+merge_request: 35671
+author:
+type: fixed
diff --git a/changelogs/unreleased/fix-typo-issues-limit-settings-template.yml b/changelogs/unreleased/fix-typo-issues-limit-settings-template.yml
new file mode 100644
index 00000000000..c0fd8b45692
--- /dev/null
+++ b/changelogs/unreleased/fix-typo-issues-limit-settings-template.yml
@@ -0,0 +1,5 @@
+---
+title: Update issue limits template to use minutes
+merge_request: 34254
+author:
+type: fixed
diff --git a/changelogs/unreleased/fix-uninitialized-constant.yml b/changelogs/unreleased/fix-uninitialized-constant.yml
new file mode 100644
index 00000000000..cc7a579e967
--- /dev/null
+++ b/changelogs/unreleased/fix-uninitialized-constant.yml
@@ -0,0 +1,5 @@
+---
+title: Fix error message when saving an integration and testing the settings.
+merge_request: 36700
+author:
+type: fixed
diff --git a/changelogs/unreleased/fix-update-plan-limits-functionality.yml b/changelogs/unreleased/fix-update-plan-limits-functionality.yml
new file mode 100644
index 00000000000..ae3769f6dcf
--- /dev/null
+++ b/changelogs/unreleased/fix-update-plan-limits-functionality.yml
@@ -0,0 +1,5 @@
+---
+title: Assign plan_id when building a new plan limit
+merge_request: 34845
+author:
+type: fixed
diff --git a/changelogs/unreleased/fix-vertically-center-action-icon.yml b/changelogs/unreleased/fix-vertically-center-action-icon.yml
new file mode 100644
index 00000000000..40c97bbc73b
--- /dev/null
+++ b/changelogs/unreleased/fix-vertically-center-action-icon.yml
@@ -0,0 +1,5 @@
+---
+title: vertically center action icon in the CI pipeline
+merge_request: 33427
+author: Nathanael Weber
+type: fixed
diff --git a/changelogs/unreleased/fix_back_button_when_switching_mr_tabs.yml b/changelogs/unreleased/fix_back_button_when_switching_mr_tabs.yml
new file mode 100644
index 00000000000..bb9904faa50
--- /dev/null
+++ b/changelogs/unreleased/fix_back_button_when_switching_mr_tabs.yml
@@ -0,0 +1,5 @@
+---
+title: Fix back button when switching MR tabs
+merge_request: 29862
+author: Lee Tickett
+type: fixed
diff --git a/changelogs/unreleased/fix_blocked_issue_warning.yml b/changelogs/unreleased/fix_blocked_issue_warning.yml
new file mode 100644
index 00000000000..3f587601e54
--- /dev/null
+++ b/changelogs/unreleased/fix_blocked_issue_warning.yml
@@ -0,0 +1,5 @@
+---
+title: Remove `:prevent_closing_blocked_issues` feature flag
+merge_request: 32630
+author: Lee Tickett
+type: fixed
diff --git a/changelogs/unreleased/fix_default_path_when_creating_project_from_group_template.yml b/changelogs/unreleased/fix_default_path_when_creating_project_from_group_template.yml
new file mode 100644
index 00000000000..500b688f13f
--- /dev/null
+++ b/changelogs/unreleased/fix_default_path_when_creating_project_from_group_template.yml
@@ -0,0 +1,5 @@
+---
+title: Fix default path when creating project from group template
+merge_request: 30597
+author: Lee Tickett
+type: fixed
diff --git a/changelogs/unreleased/fix_deleting_user_psql_error_on_events_table_v2.yml b/changelogs/unreleased/fix_deleting_user_psql_error_on_events_table_v2.yml
new file mode 100644
index 00000000000..33572c95188
--- /dev/null
+++ b/changelogs/unreleased/fix_deleting_user_psql_error_on_events_table_v2.yml
@@ -0,0 +1,5 @@
+---
+title: Remove not null constraint from events tables
+merge_request: 34190
+author:
+type: fixed
diff --git a/changelogs/unreleased/fix_mr_note_label_urls.yml b/changelogs/unreleased/fix_mr_note_label_urls.yml
new file mode 100644
index 00000000000..5558fe22804
--- /dev/null
+++ b/changelogs/unreleased/fix_mr_note_label_urls.yml
@@ -0,0 +1,5 @@
+---
+title: Fix merge request note label URLs
+merge_request: 30428
+author: Lee Tickett
+type: fixed
diff --git a/changelogs/unreleased/fix_nil_class_for_bytesize_error.yml b/changelogs/unreleased/fix_nil_class_for_bytesize_error.yml
new file mode 100644
index 00000000000..7db554864c9
--- /dev/null
+++ b/changelogs/unreleased/fix_nil_class_for_bytesize_error.yml
@@ -0,0 +1,5 @@
+---
+title: Fix undefined error in Gitlab::Git::Diff
+merge_request: 32967
+author:
+type: fixed
diff --git a/changelogs/unreleased/fix_preconnect_typo.yml b/changelogs/unreleased/fix_preconnect_typo.yml
new file mode 100644
index 00000000000..433bab8ec9d
--- /dev/null
+++ b/changelogs/unreleased/fix_preconnect_typo.yml
@@ -0,0 +1,5 @@
+---
+title: Fix preconnect typo in rel link
+merge_request: 33255
+author:
+type: performance
diff --git a/changelogs/unreleased/fix_shard_move_archive.yml b/changelogs/unreleased/fix_shard_move_archive.yml
new file mode 100644
index 00000000000..932312488df
--- /dev/null
+++ b/changelogs/unreleased/fix_shard_move_archive.yml
@@ -0,0 +1,5 @@
+---
+title: Ensure original repository is archived after a shard move
+merge_request: 34895
+author:
+type: fixed
diff --git a/changelogs/unreleased/fj-218516-add-validation-to-move-action.yml b/changelogs/unreleased/fj-218516-add-validation-to-move-action.yml
new file mode 100644
index 00000000000..0dfd311efb9
--- /dev/null
+++ b/changelogs/unreleased/fj-218516-add-validation-to-move-action.yml
@@ -0,0 +1,5 @@
+---
+title: Add validation for move action in SnippetInputAction
+merge_request: 34911
+author:
+type: other
diff --git a/changelogs/unreleased/fj-219399-add-usage-data-monthly-snippet-by-type.yml b/changelogs/unreleased/fj-219399-add-usage-data-monthly-snippet-by-type.yml
new file mode 100644
index 00000000000..277bdb8f97c
--- /dev/null
+++ b/changelogs/unreleased/fj-219399-add-usage-data-monthly-snippet-by-type.yml
@@ -0,0 +1,5 @@
+---
+title: Add personal and project snippet monthly counters to usage data
+merge_request: 35155
+author:
+type: changed
diff --git a/changelogs/unreleased/fj-223696-add-snippet-statistics.yml b/changelogs/unreleased/fj-223696-add-snippet-statistics.yml
new file mode 100644
index 00000000000..bb49e92eee6
--- /dev/null
+++ b/changelogs/unreleased/fj-223696-add-snippet-statistics.yml
@@ -0,0 +1,5 @@
+---
+title: Add SnippetStatistics model
+merge_request: 35026
+author:
+type: added
diff --git a/changelogs/unreleased/fj-223700-add-count-logic-to-snippet-statistics.yml b/changelogs/unreleased/fj-223700-add-count-logic-to-snippet-statistics.yml
new file mode 100644
index 00000000000..32c9129f6c0
--- /dev/null
+++ b/changelogs/unreleased/fj-223700-add-count-logic-to-snippet-statistics.yml
@@ -0,0 +1,5 @@
+---
+title: Add snippet statistics logic
+merge_request: 35118
+author:
+type: added
diff --git a/changelogs/unreleased/fj-223701-update-snippets-statistics-after-post-receive.yml b/changelogs/unreleased/fj-223701-update-snippets-statistics-after-post-receive.yml
new file mode 100644
index 00000000000..93959519d9f
--- /dev/null
+++ b/changelogs/unreleased/fj-223701-update-snippets-statistics-after-post-receive.yml
@@ -0,0 +1,5 @@
+---
+title: Update snippet and project statistics after certain events
+merge_request: 35340
+author:
+type: changed
diff --git a/changelogs/unreleased/fj-223703-add-snippets-size-to-project-statistics.yml b/changelogs/unreleased/fj-223703-add-snippets-size-to-project-statistics.yml
new file mode 100644
index 00000000000..af27e384676
--- /dev/null
+++ b/changelogs/unreleased/fj-223703-add-snippets-size-to-project-statistics.yml
@@ -0,0 +1,5 @@
+---
+title: Add snippets_size to ProjectStatistics
+merge_request: 35017
+author:
+type: added
diff --git a/changelogs/unreleased/fj-223705-add-snippets-size-to-project-statistics-entity.yml b/changelogs/unreleased/fj-223705-add-snippets-size-to-project-statistics-entity.yml
new file mode 100644
index 00000000000..f2c4953f4a3
--- /dev/null
+++ b/changelogs/unreleased/fj-223705-add-snippets-size-to-project-statistics-entity.yml
@@ -0,0 +1,5 @@
+---
+title: Expose snippets_size in ProjectStatistics Entity
+merge_request: 35316
+author:
+type: changed
diff --git a/changelogs/unreleased/fj-223706-add-snippets-size-to-project-statistics-type.yml b/changelogs/unreleased/fj-223706-add-snippets-size-to-project-statistics-type.yml
new file mode 100644
index 00000000000..dc7ac3b0abb
--- /dev/null
+++ b/changelogs/unreleased/fj-223706-add-snippets-size-to-project-statistics-type.yml
@@ -0,0 +1,5 @@
+---
+title: Add snippets_size to ProjectStatistics GraphQL type
+merge_request: 35319
+author:
+type: changed
diff --git a/changelogs/unreleased/fj-223712-include-snippets-size-in-project-statistics.yml b/changelogs/unreleased/fj-223712-include-snippets-size-in-project-statistics.yml
new file mode 100644
index 00000000000..dab9c5ee229
--- /dev/null
+++ b/changelogs/unreleased/fj-223712-include-snippets-size-in-project-statistics.yml
@@ -0,0 +1,5 @@
+---
+title: Include snippets size in project statistics
+merge_request: 35120
+author:
+type: changed
diff --git a/changelogs/unreleased/fj-223731-update-snippet-statistics-on-project-import.yml b/changelogs/unreleased/fj-223731-update-snippet-statistics-on-project-import.yml
new file mode 100644
index 00000000000..95ffa92d868
--- /dev/null
+++ b/changelogs/unreleased/fj-223731-update-snippet-statistics-on-project-import.yml
@@ -0,0 +1,5 @@
+---
+title: Update snippet statistics after project import
+merge_request: 35730
+author:
+type: changed
diff --git a/changelogs/unreleased/fj-223817-populate-project-snippets-statistics.yml b/changelogs/unreleased/fj-223817-populate-project-snippets-statistics.yml
new file mode 100644
index 00000000000..f68d65afac6
--- /dev/null
+++ b/changelogs/unreleased/fj-223817-populate-project-snippets-statistics.yml
@@ -0,0 +1,5 @@
+---
+title: Backfill project snippet statistics
+merge_request: 36444
+author:
+type: other
diff --git a/changelogs/unreleased/fj-224486-add-snippets-size-column-to-root-storage-statistics.yml b/changelogs/unreleased/fj-224486-add-snippets-size-column-to-root-storage-statistics.yml
new file mode 100644
index 00000000000..2e893cb6c2f
--- /dev/null
+++ b/changelogs/unreleased/fj-224486-add-snippets-size-column-to-root-storage-statistics.yml
@@ -0,0 +1,5 @@
+---
+title: Add snippets_size to namespace_root_storage_statistics
+merge_request: 35311
+author:
+type: changed
diff --git a/changelogs/unreleased/fj-224487-include-snippets-size-in-root-storage-statistics.yml b/changelogs/unreleased/fj-224487-include-snippets-size-in-root-storage-statistics.yml
new file mode 100644
index 00000000000..e2321d282b9
--- /dev/null
+++ b/changelogs/unreleased/fj-224487-include-snippets-size-in-root-storage-statistics.yml
@@ -0,0 +1,5 @@
+---
+title: Include snippets_size statistic inside RootStorageStatistics
+merge_request: 35601
+author:
+type: changed
diff --git a/changelogs/unreleased/fj-224657-include-snippets-size-in-group-statistics.yml b/changelogs/unreleased/fj-224657-include-snippets-size-in-group-statistics.yml
new file mode 100644
index 00000000000..c8bc75ca696
--- /dev/null
+++ b/changelogs/unreleased/fj-224657-include-snippets-size-in-group-statistics.yml
@@ -0,0 +1,5 @@
+---
+title: Add snippets_size to Group entity
+merge_request: 35585
+author:
+type: changed
diff --git a/changelogs/unreleased/fj-224658-add-snippets-size-to-root-storage-statistics-type.yml b/changelogs/unreleased/fj-224658-add-snippets-size-to-root-storage-statistics-type.yml
new file mode 100644
index 00000000000..31d21fd5225
--- /dev/null
+++ b/changelogs/unreleased/fj-224658-add-snippets-size-to-root-storage-statistics-type.yml
@@ -0,0 +1,5 @@
+---
+title: Add snippets_size to RootStorageStatisticsType
+merge_request: 35586
+author:
+type: changed
diff --git a/changelogs/unreleased/fj-225613-add-snippets-quota-information.yml b/changelogs/unreleased/fj-225613-add-snippets-quota-information.yml
new file mode 100644
index 00000000000..4012653d7d1
--- /dev/null
+++ b/changelogs/unreleased/fj-225613-add-snippets-quota-information.yml
@@ -0,0 +1,5 @@
+---
+title: Update snippets housecleaning docs
+merge_request: 36715
+author:
+type: other
diff --git a/changelogs/unreleased/fj-225964-include-personal-snippets-in-snippets-size.yml b/changelogs/unreleased/fj-225964-include-personal-snippets-in-snippets-size.yml
new file mode 100644
index 00000000000..1997abffcaf
--- /dev/null
+++ b/changelogs/unreleased/fj-225964-include-personal-snippets-in-snippets-size.yml
@@ -0,0 +1,5 @@
+---
+title: Include personal snippets size in RootStorageStatistics
+merge_request: 35984
+author:
+type: changed
diff --git a/changelogs/unreleased/fj-227311-optimize-snippets-finder-query.yml b/changelogs/unreleased/fj-227311-optimize-snippets-finder-query.yml
new file mode 100644
index 00000000000..8b230b60ae8
--- /dev/null
+++ b/changelogs/unreleased/fj-227311-optimize-snippets-finder-query.yml
@@ -0,0 +1,5 @@
+---
+title: Improve snippet finders queries
+merge_request: 36292
+author:
+type: performance
diff --git a/changelogs/unreleased/fj-228825-remove-file-path-validation-in-snippet-create-action.yml b/changelogs/unreleased/fj-228825-remove-file-path-validation-in-snippet-create-action.yml
new file mode 100644
index 00000000000..8db8de05139
--- /dev/null
+++ b/changelogs/unreleased/fj-228825-remove-file-path-validation-in-snippet-create-action.yml
@@ -0,0 +1,5 @@
+---
+title: Remove file_path validation in snippet create action
+merge_request: 36809
+author:
+type: changed
diff --git a/changelogs/unreleased/fj-add-allowed-actions-to-snippet-input-action.yml b/changelogs/unreleased/fj-add-allowed-actions-to-snippet-input-action.yml
new file mode 100644
index 00000000000..ac25131bd1f
--- /dev/null
+++ b/changelogs/unreleased/fj-add-allowed-actions-to-snippet-input-action.yml
@@ -0,0 +1,5 @@
+---
+title: Add allowed actions to snippet input action
+merge_request: 34499
+author:
+type: changed
diff --git a/changelogs/unreleased/fj-add-snippet-file-input-action.yml b/changelogs/unreleased/fj-add-snippet-file-input-action.yml
new file mode 100644
index 00000000000..583e5038633
--- /dev/null
+++ b/changelogs/unreleased/fj-add-snippet-file-input-action.yml
@@ -0,0 +1,5 @@
+---
+title: Add GraphQL snippet FileInputType
+merge_request: 34442
+author:
+type: other
diff --git a/changelogs/unreleased/fj-add-snippet-files-param-to-snippet-create-service.yml b/changelogs/unreleased/fj-add-snippet-files-param-to-snippet-create-service.yml
new file mode 100644
index 00000000000..cd3a5ead9e6
--- /dev/null
+++ b/changelogs/unreleased/fj-add-snippet-files-param-to-snippet-create-service.yml
@@ -0,0 +1,5 @@
+---
+title: Allow the snippet create service to accept an array of files
+merge_request: 32649
+author:
+type: changed
diff --git a/changelogs/unreleased/fj-add-snippet-files-param-to-snippet-update-service.yml b/changelogs/unreleased/fj-add-snippet-files-param-to-snippet-update-service.yml
new file mode 100644
index 00000000000..8892a5166ea
--- /dev/null
+++ b/changelogs/unreleased/fj-add-snippet-files-param-to-snippet-update-service.yml
@@ -0,0 +1,5 @@
+---
+title: Allow the snippet update service to accept an array of files
+merge_request: 32832
+author:
+type: changed
diff --git a/changelogs/unreleased/fj-add-snippet-input-file-action-to-create-mutation.yml b/changelogs/unreleased/fj-add-snippet-input-file-action-to-create-mutation.yml
new file mode 100644
index 00000000000..c84f3be055e
--- /dev/null
+++ b/changelogs/unreleased/fj-add-snippet-input-file-action-to-create-mutation.yml
@@ -0,0 +1,5 @@
+---
+title: Add files argument to snippet create mutation
+merge_request: 34449
+author:
+type: changed
diff --git a/changelogs/unreleased/fj-add-validations-snippet-input-action.yml b/changelogs/unreleased/fj-add-validations-snippet-input-action.yml
new file mode 100644
index 00000000000..005ec4ccf9d
--- /dev/null
+++ b/changelogs/unreleased/fj-add-validations-snippet-input-action.yml
@@ -0,0 +1,5 @@
+---
+title: Add update validations to SnippetInputAction
+merge_request: 33379
+author:
+type: changed
diff --git a/changelogs/unreleased/fj-avoid-updating-snippet-content-when-not-present.yml b/changelogs/unreleased/fj-avoid-updating-snippet-content-when-not-present.yml
new file mode 100644
index 00000000000..e6ac0146923
--- /dev/null
+++ b/changelogs/unreleased/fj-avoid-updating-snippet-content-when-not-present.yml
@@ -0,0 +1,5 @@
+---
+title: Avoid updating snippet content when snippet_files content is not present
+merge_request: 34865
+author:
+type: fixed
diff --git a/changelogs/unreleased/fj-backfill-imported-snippet-repositories.yml b/changelogs/unreleased/fj-backfill-imported-snippet-repositories.yml
new file mode 100644
index 00000000000..d2ea7fe1be1
--- /dev/null
+++ b/changelogs/unreleased/fj-backfill-imported-snippet-repositories.yml
@@ -0,0 +1,5 @@
+---
+title: Backfill failed imported snippet repositories
+merge_request: 34052
+author:
+type: other
diff --git a/changelogs/unreleased/fj-bump-lfs-token-default-expiration-time.yml b/changelogs/unreleased/fj-bump-lfs-token-default-expiration-time.yml
new file mode 100644
index 00000000000..c0dc036f79f
--- /dev/null
+++ b/changelogs/unreleased/fj-bump-lfs-token-default-expiration-time.yml
@@ -0,0 +1,5 @@
+---
+title: Increase LFS token default time to 2 hours
+merge_request: 33140
+author:
+type: changed
diff --git a/changelogs/unreleased/fj-change-snippet-author-nullable-graphql-type.yml b/changelogs/unreleased/fj-change-snippet-author-nullable-graphql-type.yml
new file mode 100644
index 00000000000..925f7ff9a25
--- /dev/null
+++ b/changelogs/unreleased/fj-change-snippet-author-nullable-graphql-type.yml
@@ -0,0 +1,5 @@
+---
+title: Set author as nullable in snippet GraphQL Type
+merge_request: 34135
+author:
+type: fixed
diff --git a/changelogs/unreleased/fj-default-order-snippet-lists.yml b/changelogs/unreleased/fj-default-order-snippet-lists.yml
new file mode 100644
index 00000000000..e902628ee0f
--- /dev/null
+++ b/changelogs/unreleased/fj-default-order-snippet-lists.yml
@@ -0,0 +1,5 @@
+---
+title: Reorder snippets in lists using `updated_at` column
+merge_request: 34393
+author: Dibyadarshi Dash @ddash2
+type: changed
diff --git a/changelogs/unreleased/fj-fix-import-from-different-types-exports.yml b/changelogs/unreleased/fj-fix-import-from-different-types-exports.yml
new file mode 100644
index 00000000000..5a6fd312ee4
--- /dev/null
+++ b/changelogs/unreleased/fj-fix-import-from-different-types-exports.yml
@@ -0,0 +1,5 @@
+---
+title: Fix snippet repository import fail with older export files
+merge_request: 33584
+author:
+type: fixed
diff --git a/changelogs/unreleased/fj-fix-single-param-update.yml b/changelogs/unreleased/fj-fix-single-param-update.yml
new file mode 100644
index 00000000000..8d5f9dd80d4
--- /dev/null
+++ b/changelogs/unreleased/fj-fix-single-param-update.yml
@@ -0,0 +1,5 @@
+---
+title: Fix bug in snippets updating only file_name or content
+merge_request: 33375
+author:
+type: fixed
diff --git a/changelogs/unreleased/fj-fix-snippet-create-mutation-non-activerecord-errors.yml b/changelogs/unreleased/fj-fix-snippet-create-mutation-non-activerecord-errors.yml
new file mode 100644
index 00000000000..4d5bbceb870
--- /dev/null
+++ b/changelogs/unreleased/fj-fix-snippet-create-mutation-non-activerecord-errors.yml
@@ -0,0 +1,5 @@
+---
+title: Fix bug in snippet create mutation with non ActiveRecord errors
+merge_request: 33085
+author:
+type: fixed
diff --git a/changelogs/unreleased/fj-fix-snippet-import-from-database.yml b/changelogs/unreleased/fj-fix-snippet-import-from-database.yml
new file mode 100644
index 00000000000..a8c9678405b
--- /dev/null
+++ b/changelogs/unreleased/fj-fix-snippet-import-from-database.yml
@@ -0,0 +1,5 @@
+---
+title: Fix snippet repository import edge cases
+merge_request: 33506
+author:
+type: fixed
diff --git a/changelogs/unreleased/fj-fix-snippet-import-when-fails.yml b/changelogs/unreleased/fj-fix-snippet-import-when-fails.yml
new file mode 100644
index 00000000000..d040ee3fccd
--- /dev/null
+++ b/changelogs/unreleased/fj-fix-snippet-import-when-fails.yml
@@ -0,0 +1,5 @@
+---
+title: Remove non migrated snippets from failed imports
+merge_request: 33621
+author:
+type: fixed
diff --git a/changelogs/unreleased/fj-fj-add-snippet-input-file-action-to-update-mutation.yml b/changelogs/unreleased/fj-fj-add-snippet-input-file-action-to-update-mutation.yml
new file mode 100644
index 00000000000..a43312cc27f
--- /dev/null
+++ b/changelogs/unreleased/fj-fj-add-snippet-input-file-action-to-update-mutation.yml
@@ -0,0 +1,5 @@
+---
+title: Add files argument to snippet update mutation
+merge_request: 34514
+author:
+type: changed
diff --git a/changelogs/unreleased/fj-rethink-snippet-storage-callbacks.yml b/changelogs/unreleased/fj-rethink-snippet-storage-callbacks.yml
new file mode 100644
index 00000000000..59510f45585
--- /dev/null
+++ b/changelogs/unreleased/fj-rethink-snippet-storage-callbacks.yml
@@ -0,0 +1,5 @@
+---
+title: Update namespace statistics after personal snippet update/removal
+merge_request: 36031
+author:
+type: changed
diff --git a/changelogs/unreleased/followup-leakyconst-master-check.yml b/changelogs/unreleased/followup-leakyconst-master-check.yml
new file mode 100644
index 00000000000..3c0e00510f1
--- /dev/null
+++ b/changelogs/unreleased/followup-leakyconst-master-check.yml
@@ -0,0 +1,5 @@
+---
+title: Move NoPrimary table def to last context in spec
+merge_request: 33015
+author: Rajendra Kadam
+type: other
diff --git a/changelogs/unreleased/fox-comment-icons-commits.yml b/changelogs/unreleased/fox-comment-icons-commits.yml
new file mode 100644
index 00000000000..9eb198629ae
--- /dev/null
+++ b/changelogs/unreleased/fox-comment-icons-commits.yml
@@ -0,0 +1,5 @@
+---
+title: Use sprites for comment icons on Commits
+merge_request: 31696
+author:
+type: changed
diff --git a/changelogs/unreleased/georgekoltsov-emit-bitbucket-server-metrics.yml b/changelogs/unreleased/georgekoltsov-emit-bitbucket-server-metrics.yml
new file mode 100644
index 00000000000..6f43b37bbae
--- /dev/null
+++ b/changelogs/unreleased/georgekoltsov-emit-bitbucket-server-metrics.yml
@@ -0,0 +1,5 @@
+---
+title: Emit Bitbucket Server Importer metrics
+merge_request: 33700
+author:
+type: changed
diff --git a/changelogs/unreleased/georgekoltsov-fix-profile-applications-page-i18n.yml b/changelogs/unreleased/georgekoltsov-fix-profile-applications-page-i18n.yml
new file mode 100644
index 00000000000..db58689e17c
--- /dev/null
+++ b/changelogs/unreleased/georgekoltsov-fix-profile-applications-page-i18n.yml
@@ -0,0 +1,5 @@
+---
+title: Fix Profile Applications page to be shown in correct locale
+merge_request: 35661
+author:
+type: fixed
diff --git a/changelogs/unreleased/georgekoltsov-fix-services-relation-on-import.yml b/changelogs/unreleased/georgekoltsov-fix-services-relation-on-import.yml
new file mode 100644
index 00000000000..94658289981
--- /dev/null
+++ b/changelogs/unreleased/georgekoltsov-fix-services-relation-on-import.yml
@@ -0,0 +1,5 @@
+---
+title: Exclude services relation from Project Import/Export
+merge_request: 36569
+author:
+type: fixed
diff --git a/changelogs/unreleased/georgekoltsov-import-export-rate-limits-as-application-settings.yml b/changelogs/unreleased/georgekoltsov-import-export-rate-limits-as-application-settings.yml
new file mode 100644
index 00000000000..8d44f84b2ef
--- /dev/null
+++ b/changelogs/unreleased/georgekoltsov-import-export-rate-limits-as-application-settings.yml
@@ -0,0 +1,5 @@
+---
+title: Convert Import/Export rate limits to configurable application settings
+merge_request: 35728
+author:
+type: added
diff --git a/changelogs/unreleased/georgekoltsov-import-export-tmp-folder-cleanup.yml b/changelogs/unreleased/georgekoltsov-import-export-tmp-folder-cleanup.yml
new file mode 100644
index 00000000000..d79f751a48f
--- /dev/null
+++ b/changelogs/unreleased/georgekoltsov-import-export-tmp-folder-cleanup.yml
@@ -0,0 +1,5 @@
+---
+title: Clean up shared/tmp folder after Import/Export
+merge_request: 32326
+author:
+type: fixed
diff --git a/changelogs/unreleased/gitaly-version-v13.1.0-rc1.yml b/changelogs/unreleased/gitaly-version-v13.1.0-rc1.yml
new file mode 100644
index 00000000000..8c2ea5800a5
--- /dev/null
+++ b/changelogs/unreleased/gitaly-version-v13.1.0-rc1.yml
@@ -0,0 +1,5 @@
+---
+title: Upgrade to Gitaly v13.1.0-rc1
+merge_request: 33302
+author:
+type: changed
diff --git a/changelogs/unreleased/gitlab-emoji-specs.yml b/changelogs/unreleased/gitlab-emoji-specs.yml
new file mode 100644
index 00000000000..8e7d300af80
--- /dev/null
+++ b/changelogs/unreleased/gitlab-emoji-specs.yml
@@ -0,0 +1,5 @@
+---
+title: Add RSpecs for Gitlab::Emoji module
+merge_request: 34980
+author: Rajendra Kadam
+type: fixed
diff --git a/changelogs/unreleased/gitlab-json-grape.yml b/changelogs/unreleased/gitlab-json-grape.yml
new file mode 100644
index 00000000000..481face95f4
--- /dev/null
+++ b/changelogs/unreleased/gitlab-json-grape.yml
@@ -0,0 +1,5 @@
+---
+title: Swap Grape over to Gitlab::Json
+merge_request: 36472
+author:
+type: performance
diff --git a/changelogs/unreleased/gitlab-ui-badge-integration-2.yml b/changelogs/unreleased/gitlab-ui-badge-integration-2.yml
new file mode 100644
index 00000000000..fef025138d5
--- /dev/null
+++ b/changelogs/unreleased/gitlab-ui-badge-integration-2.yml
@@ -0,0 +1,5 @@
+---
+title: Update the visual design of badges in some areas
+merge_request: 31646
+author:
+type: other
diff --git a/changelogs/unreleased/gl-cluster-applications-json.yml b/changelogs/unreleased/gl-cluster-applications-json.yml
new file mode 100644
index 00000000000..d27029eb769
--- /dev/null
+++ b/changelogs/unreleased/gl-cluster-applications-json.yml
@@ -0,0 +1,6 @@
+---
+title: Bump cluster-applications to 0.17.0, which updates Runner to 0.17.0 and Cilium
+ to 1.7.4
+merge_request: 32931
+author:
+type: changed
diff --git a/changelogs/unreleased/graphql-bug-fix-get-todo-by-type.yml b/changelogs/unreleased/graphql-bug-fix-get-todo-by-type.yml
new file mode 100644
index 00000000000..e7ae939e138
--- /dev/null
+++ b/changelogs/unreleased/graphql-bug-fix-get-todo-by-type.yml
@@ -0,0 +1,5 @@
+---
+title: Fix broken todo GraphQL API filtering when filtering by type
+merge_request: 34790
+author:
+type: fixed
diff --git a/changelogs/unreleased/groups_routing_priority.yml b/changelogs/unreleased/groups_routing_priority.yml
new file mode 100644
index 00000000000..042da076018
--- /dev/null
+++ b/changelogs/unreleased/groups_routing_priority.yml
@@ -0,0 +1,5 @@
+---
+title: Fix routing for paths starting with help and projects
+merge_request: 36048
+author:
+type: fixed
diff --git a/changelogs/unreleased/grp-finder-code-refactor.yml b/changelogs/unreleased/grp-finder-code-refactor.yml
new file mode 100644
index 00000000000..a80f2286559
--- /dev/null
+++ b/changelogs/unreleased/grp-finder-code-refactor.yml
@@ -0,0 +1,5 @@
+---
+title: Move filter code into finder
+merge_request: 34470
+author: Ravishankar
+type: other
diff --git a/changelogs/unreleased/hpaluch-master.yml b/changelogs/unreleased/hpaluch-master.yml
new file mode 100644
index 00000000000..b71b7b89f6b
--- /dev/null
+++ b/changelogs/unreleased/hpaluch-master.yml
@@ -0,0 +1,5 @@
+---
+title: Log name of class that failed to obtain exclusive lease
+merge_request: 35228
+author:
+type: added
diff --git a/changelogs/unreleased/id-approval-rules-usage-ping.yml b/changelogs/unreleased/id-approval-rules-usage-ping.yml
new file mode 100644
index 00000000000..070f067ad04
--- /dev/null
+++ b/changelogs/unreleased/id-approval-rules-usage-ping.yml
@@ -0,0 +1,5 @@
+---
+title: Add number of approval project rules to usage ping
+merge_request: 36316
+author:
+type: added
diff --git a/changelogs/unreleased/id-code-nav-500-error.yml b/changelogs/unreleased/id-code-nav-500-error.yml
new file mode 100644
index 00000000000..e88980602c3
--- /dev/null
+++ b/changelogs/unreleased/id-code-nav-500-error.yml
@@ -0,0 +1,5 @@
+---
+title: Return code navigation path for nil diff_refs
+merge_request: 33850
+author:
+type: fixed
diff --git a/changelogs/unreleased/id-code-navigation-enable-feature.yml b/changelogs/unreleased/id-code-navigation-enable-feature.yml
new file mode 100644
index 00000000000..e1f9a9c1978
--- /dev/null
+++ b/changelogs/unreleased/id-code-navigation-enable-feature.yml
@@ -0,0 +1,5 @@
+---
+title: Add native code intelligence
+merge_request: 34542
+author:
+type: added
diff --git a/changelogs/unreleased/id-draft-wip.yml b/changelogs/unreleased/id-draft-wip.yml
new file mode 100644
index 00000000000..9be0bb2320d
--- /dev/null
+++ b/changelogs/unreleased/id-draft-wip.yml
@@ -0,0 +1,5 @@
+---
+title: Allow prefixing with Draft to mark MR as WIP
+merge_request: 35940
+author:
+type: added
diff --git a/changelogs/unreleased/id-expose-approvals-endpoints.yml b/changelogs/unreleased/id-expose-approvals-endpoints.yml
new file mode 100644
index 00000000000..4689e5779e0
--- /dev/null
+++ b/changelogs/unreleased/id-expose-approvals-endpoints.yml
@@ -0,0 +1,5 @@
+---
+title: Expose approvals fields for FOSS FE
+merge_request: 36564
+author:
+type: changed
diff --git a/changelogs/unreleased/id-fix-timeout-query.yml b/changelogs/unreleased/id-fix-timeout-query.yml
new file mode 100644
index 00000000000..d719f83379f
--- /dev/null
+++ b/changelogs/unreleased/id-fix-timeout-query.yml
@@ -0,0 +1,5 @@
+---
+title: Reduce number of scanned commits for code intelligence
+merge_request: 36093
+author:
+type: performance
diff --git a/changelogs/unreleased/id-fix-wip-in-the-middle-of-title.yml b/changelogs/unreleased/id-fix-wip-in-the-middle-of-title.yml
new file mode 100644
index 00000000000..bacac42b380
--- /dev/null
+++ b/changelogs/unreleased/id-fix-wip-in-the-middle-of-title.yml
@@ -0,0 +1,5 @@
+---
+title: Fix incorrect marking MR as Draft
+merge_request: 36869
+author:
+type: fixed
diff --git a/changelogs/unreleased/id-move-approvals-endpoints-to-ce.yml b/changelogs/unreleased/id-move-approvals-endpoints-to-ce.yml
new file mode 100644
index 00000000000..dbd721c48ee
--- /dev/null
+++ b/changelogs/unreleased/id-move-approvals-endpoints-to-ce.yml
@@ -0,0 +1,5 @@
+---
+title: Move approvals endpoints to FOSS version
+merge_request: 36237
+author:
+type: added
diff --git a/changelogs/unreleased/id-split-query-for-code-nav-path.yml b/changelogs/unreleased/id-split-query-for-code-nav-path.yml
new file mode 100644
index 00000000000..54d4365041a
--- /dev/null
+++ b/changelogs/unreleased/id-split-query-for-code-nav-path.yml
@@ -0,0 +1,5 @@
+---
+title: Split query for code-nav path into two queries
+merge_request: 37092
+author:
+type: performance
diff --git a/changelogs/unreleased/id-update-workhorse.yml b/changelogs/unreleased/id-update-workhorse.yml
new file mode 100644
index 00000000000..5c9333a6a7a
--- /dev/null
+++ b/changelogs/unreleased/id-update-workhorse.yml
@@ -0,0 +1,5 @@
+---
+title: Update Workhorse to v8.36.0
+merge_request: 34759
+author:
+type: other
diff --git a/changelogs/unreleased/image-check-disable.yml b/changelogs/unreleased/image-check-disable.yml
new file mode 100644
index 00000000000..a6e5e611ff8
--- /dev/null
+++ b/changelogs/unreleased/image-check-disable.yml
@@ -0,0 +1,4 @@
+title: Conditionally render Docker row checkbox
+merge_request: 36000
+author: gfyoung
+type: fixed
diff --git a/changelogs/unreleased/implement_vulnerability_stats_model.yml b/changelogs/unreleased/implement_vulnerability_stats_model.yml
new file mode 100644
index 00000000000..5e38495cdaa
--- /dev/null
+++ b/changelogs/unreleased/implement_vulnerability_stats_model.yml
@@ -0,0 +1,5 @@
+---
+title: Create vulnerability_statistics table
+merge_request: 34289
+author:
+type: added
diff --git a/changelogs/unreleased/improve_issue_labels_api.yml b/changelogs/unreleased/improve_issue_labels_api.yml
new file mode 100644
index 00000000000..e57aba6ab59
--- /dev/null
+++ b/changelogs/unreleased/improve_issue_labels_api.yml
@@ -0,0 +1,5 @@
+---
+title: Improve Add/Remove Issue Labels API
+merge_request: 31864
+author: Lee Tickett
+type: added
diff --git a/changelogs/unreleased/improve_storage_move_workflow.yml b/changelogs/unreleased/improve_storage_move_workflow.yml
new file mode 100644
index 00000000000..c5e74e07267
--- /dev/null
+++ b/changelogs/unreleased/improve_storage_move_workflow.yml
@@ -0,0 +1,5 @@
+---
+title: Add project specific repository storage API
+merge_request: 32493
+author:
+type: added
diff --git a/changelogs/unreleased/increase-events-count-for-prometheus-alerts.yml b/changelogs/unreleased/increase-events-count-for-prometheus-alerts.yml
new file mode 100644
index 00000000000..29ccc347d09
--- /dev/null
+++ b/changelogs/unreleased/increase-events-count-for-prometheus-alerts.yml
@@ -0,0 +1,5 @@
+---
+title: Increase events count for Prometheus alerts
+merge_request: 33706
+author:
+type: added
diff --git a/changelogs/unreleased/insert-project-authorization-directly-when-creating-project.yml b/changelogs/unreleased/insert-project-authorization-directly-when-creating-project.yml
new file mode 100644
index 00000000000..6b4a6c2e4d7
--- /dev/null
+++ b/changelogs/unreleased/insert-project-authorization-directly-when-creating-project.yml
@@ -0,0 +1,5 @@
+---
+title: Speed up project creation for users with many projects
+merge_request: 37070
+author:
+type: performance
diff --git a/changelogs/unreleased/instance-variables-ui.yml b/changelogs/unreleased/instance-variables-ui.yml
new file mode 100644
index 00000000000..26d59c0fafd
--- /dev/null
+++ b/changelogs/unreleased/instance-variables-ui.yml
@@ -0,0 +1,5 @@
+---
+title: New instance-level variables UI
+merge_request: 33510
+author:
+type: added
diff --git a/changelogs/unreleased/instance_auto_devops_enabled_usage_ping.yml b/changelogs/unreleased/instance_auto_devops_enabled_usage_ping.yml
new file mode 100644
index 00000000000..22e72c5bb8f
--- /dev/null
+++ b/changelogs/unreleased/instance_auto_devops_enabled_usage_ping.yml
@@ -0,0 +1,5 @@
+---
+title: Add whether instance has Auto DevOps enabled to usage ping
+merge_request: 33811
+author:
+type: changed
diff --git a/changelogs/unreleased/issue-bulk-update.yml b/changelogs/unreleased/issue-bulk-update.yml
new file mode 100644
index 00000000000..968c16f4d25
--- /dev/null
+++ b/changelogs/unreleased/issue-bulk-update.yml
@@ -0,0 +1,5 @@
+---
+title: Allow different in bulk editing issues
+merge_request: 32734
+author:
+type: fixed
diff --git a/changelogs/unreleased/issue_22856_fe.yml b/changelogs/unreleased/issue_22856_fe.yml
new file mode 100644
index 00000000000..dcd1147bbba
--- /dev/null
+++ b/changelogs/unreleased/issue_22856_fe.yml
@@ -0,0 +1,5 @@
+---
+title: Move service desk feature to core
+merge_request: 36613
+author:
+type: changed
diff --git a/changelogs/unreleased/jc-add-seed-to-repository-storages-weighted.yml b/changelogs/unreleased/jc-add-seed-to-repository-storages-weighted.yml
new file mode 100644
index 00000000000..627697f510c
--- /dev/null
+++ b/changelogs/unreleased/jc-add-seed-to-repository-storages-weighted.yml
@@ -0,0 +1,5 @@
+---
+title: Fix existing repository_storages_weighted migrations
+merge_request: 35814
+author:
+type: fixed
diff --git a/changelogs/unreleased/jc-modify-verbiage-in-repository-storages-settings.yml b/changelogs/unreleased/jc-modify-verbiage-in-repository-storages-settings.yml
new file mode 100644
index 00000000000..bc5630850ad
--- /dev/null
+++ b/changelogs/unreleased/jc-modify-verbiage-in-repository-storages-settings.yml
@@ -0,0 +1,5 @@
+---
+title: Adjust verbiage on repository storages settings page
+merge_request: 34675
+author:
+type: changed
diff --git a/changelogs/unreleased/jc-pick-weighted-repository.yml b/changelogs/unreleased/jc-pick-weighted-repository.yml
new file mode 100644
index 00000000000..803741e00cc
--- /dev/null
+++ b/changelogs/unreleased/jc-pick-weighted-repository.yml
@@ -0,0 +1,5 @@
+---
+title: Pick repository storage based on weight
+merge_request: 34095
+author:
+type: changed
diff --git a/changelogs/unreleased/jc-show-all-storages.yml b/changelogs/unreleased/jc-show-all-storages.yml
new file mode 100644
index 00000000000..fac5d5bb813
--- /dev/null
+++ b/changelogs/unreleased/jc-show-all-storages.yml
@@ -0,0 +1,5 @@
+---
+title: Show all storages in settings
+merge_request: 34093
+author:
+type: fixed
diff --git a/changelogs/unreleased/jc-ui-repository-storages-weighted.yml b/changelogs/unreleased/jc-ui-repository-storages-weighted.yml
new file mode 100644
index 00000000000..eefe0012310
--- /dev/null
+++ b/changelogs/unreleased/jc-ui-repository-storages-weighted.yml
@@ -0,0 +1,5 @@
+---
+title: Add ApplicationSetting ui changes for repository_storages_weighted
+merge_request: 33096
+author:
+type: added
diff --git a/changelogs/unreleased/jc-weighted-repository-storages.yml b/changelogs/unreleased/jc-weighted-repository-storages.yml
new file mode 100644
index 00000000000..060c1a26494
--- /dev/null
+++ b/changelogs/unreleased/jc-weighted-repository-storages.yml
@@ -0,0 +1,5 @@
+---
+title: Save repository storages in application settings with weights
+merge_request: 31645
+author:
+type: added
diff --git a/changelogs/unreleased/jcunha-bump-deploy-image-to-0-17-0.yml b/changelogs/unreleased/jcunha-bump-deploy-image-to-0-17-0.yml
new file mode 100644
index 00000000000..21c60bc9871
--- /dev/null
+++ b/changelogs/unreleased/jcunha-bump-deploy-image-to-0-17-0.yml
@@ -0,0 +1,6 @@
+---
+title: Updated Auto DevOps with a fix to delete PostgreSQL PVC on environment cleanup,
+ a fix for multiline K8S_SECRET variables, updated Helm to 2.16.7 and glibc to 2.31
+merge_request: 34399
+author: verenion
+type: fixed
diff --git a/changelogs/unreleased/jcunha-bump-helm-version-to-2-16-7.yml b/changelogs/unreleased/jcunha-bump-helm-version-to-2-16-7.yml
new file mode 100644
index 00000000000..ce9e65c254e
--- /dev/null
+++ b/changelogs/unreleased/jcunha-bump-helm-version-to-2-16-7.yml
@@ -0,0 +1,5 @@
+---
+title: Updates Helm version to 2.16.7, which has some fixes
+merge_request: 34452
+author:
+type: fixed
diff --git a/changelogs/unreleased/jcunha-update-helm-version-to-2-16-9.yml b/changelogs/unreleased/jcunha-update-helm-version-to-2-16-9.yml
new file mode 100644
index 00000000000..1a512caa044
--- /dev/null
+++ b/changelogs/unreleased/jcunha-update-helm-version-to-2-16-9.yml
@@ -0,0 +1,5 @@
+---
+title: Updates Helm version to 2.16.9 which has some fixes
+merge_request: 36746
+author:
+type: fixed
diff --git a/changelogs/unreleased/jdb-fix-multiline-comment-form-reply.yml b/changelogs/unreleased/jdb-fix-multiline-comment-form-reply.yml
new file mode 100644
index 00000000000..96cefc955da
--- /dev/null
+++ b/changelogs/unreleased/jdb-fix-multiline-comment-form-reply.yml
@@ -0,0 +1,5 @@
+---
+title: Fix showing MLC form on replies
+merge_request: 37139
+author:
+type: fixed
diff --git a/changelogs/unreleased/jdb-highlight-commented-rows.yml b/changelogs/unreleased/jdb-highlight-commented-rows.yml
new file mode 100644
index 00000000000..a4aa76ca684
--- /dev/null
+++ b/changelogs/unreleased/jdb-highlight-commented-rows.yml
@@ -0,0 +1,5 @@
+---
+title: Highlight commented rows
+merge_request: 34432
+author:
+type: added
diff --git a/changelogs/unreleased/jdb-mutliline-comment-fe.yml b/changelogs/unreleased/jdb-mutliline-comment-fe.yml
new file mode 100644
index 00000000000..da4b6e28dd5
--- /dev/null
+++ b/changelogs/unreleased/jdb-mutliline-comment-fe.yml
@@ -0,0 +1,5 @@
+---
+title: Add frontend support for multiline comments
+merge_request: 29516
+author:
+type: added
diff --git a/changelogs/unreleased/jdb-save-whitespace-setting.yml b/changelogs/unreleased/jdb-save-whitespace-setting.yml
new file mode 100644
index 00000000000..1785accf764
--- /dev/null
+++ b/changelogs/unreleased/jdb-save-whitespace-setting.yml
@@ -0,0 +1,5 @@
+---
+title: Save show whitespace changes
+merge_request: 35806
+author:
+type: fixed
diff --git a/changelogs/unreleased/jej-api-for-root-groups.yml b/changelogs/unreleased/jej-api-for-root-groups.yml
new file mode 100644
index 00000000000..d859273f912
--- /dev/null
+++ b/changelogs/unreleased/jej-api-for-root-groups.yml
@@ -0,0 +1,5 @@
+---
+title: Groups API has top_level_only option to exclude subgroups
+merge_request: 32870
+author:
+type: added
diff --git a/changelogs/unreleased/jh-drop_jid_null_constraint.yml b/changelogs/unreleased/jh-drop_jid_null_constraint.yml
new file mode 100644
index 00000000000..ef1637c814e
--- /dev/null
+++ b/changelogs/unreleased/jh-drop_jid_null_constraint.yml
@@ -0,0 +1,5 @@
+---
+title: Remove null constraint for JID in GroupImportState
+merge_request: 33181
+author:
+type: changed
diff --git a/changelogs/unreleased/jh-group_import_status.yml b/changelogs/unreleased/jh-group_import_status.yml
new file mode 100644
index 00000000000..9441ad39216
--- /dev/null
+++ b/changelogs/unreleased/jh-group_import_status.yml
@@ -0,0 +1,5 @@
+---
+title: Show import in progress screen for group imports
+merge_request: 31731
+author:
+type: added
diff --git a/changelogs/unreleased/jh-group_import_ui_frontend.yml b/changelogs/unreleased/jh-group_import_ui_frontend.yml
new file mode 100644
index 00000000000..e820607cc76
--- /dev/null
+++ b/changelogs/unreleased/jh-group_import_ui_frontend.yml
@@ -0,0 +1,5 @@
+---
+title: Create Group import UI for creating new Groups
+merge_request: 29271
+author:
+type: added
diff --git a/changelogs/unreleased/jh-rate_limit_project_export.yml b/changelogs/unreleased/jh-rate_limit_project_export.yml
new file mode 100644
index 00000000000..743755b1c49
--- /dev/null
+++ b/changelogs/unreleased/jh-rate_limit_project_export.yml
@@ -0,0 +1,5 @@
+---
+title: Rate limit project export by user
+merge_request: 31719
+author:
+type: changed
diff --git a/changelogs/unreleased/jira-projects-api-wrapper.yml b/changelogs/unreleased/jira-projects-api-wrapper.yml
new file mode 100644
index 00000000000..4fe86aa3f4c
--- /dev/null
+++ b/changelogs/unreleased/jira-projects-api-wrapper.yml
@@ -0,0 +1,5 @@
+---
+title: Add a GraphQL endpoint to fetch Jira projects through its REST API
+merge_request: 28190
+author:
+type: changed
diff --git a/changelogs/unreleased/jivanvl-add-keyboard-shortcuts-metrics-dashboard.yml b/changelogs/unreleased/jivanvl-add-keyboard-shortcuts-metrics-dashboard.yml
new file mode 100644
index 00000000000..d1c9005ab41
--- /dev/null
+++ b/changelogs/unreleased/jivanvl-add-keyboard-shortcuts-metrics-dashboard.yml
@@ -0,0 +1,5 @@
+---
+title: Add keyboard shortcuts to metrics dashboard
+merge_request: 32804
+author:
+type: added
diff --git a/changelogs/unreleased/jivanvl-add-managed-apps-environments-dropdown.yml b/changelogs/unreleased/jivanvl-add-managed-apps-environments-dropdown.yml
new file mode 100644
index 00000000000..20cff6b9c74
--- /dev/null
+++ b/changelogs/unreleased/jivanvl-add-managed-apps-environments-dropdown.yml
@@ -0,0 +1,5 @@
+---
+title: Add managed-apps section in log explorer
+merge_request: 36769
+author:
+type: changed
diff --git a/changelogs/unreleased/jivanvl-add-snowplow-logs.yml b/changelogs/unreleased/jivanvl-add-snowplow-logs.yml
new file mode 100644
index 00000000000..bc0d3592e74
--- /dev/null
+++ b/changelogs/unreleased/jivanvl-add-snowplow-logs.yml
@@ -0,0 +1,5 @@
+---
+title: Add snowplow tracking for logs page
+merge_request: 32704
+author:
+type: other
diff --git a/changelogs/unreleased/jivanvl-make-chart-panels-focusable-keyboard.yml b/changelogs/unreleased/jivanvl-make-chart-panels-focusable-keyboard.yml
new file mode 100644
index 00000000000..99798f4c58a
--- /dev/null
+++ b/changelogs/unreleased/jivanvl-make-chart-panels-focusable-keyboard.yml
@@ -0,0 +1,5 @@
+---
+title: Focus and toggle metrics dashboard panels via keyboard
+merge_request: 28603
+author:
+type: added
diff --git a/changelogs/unreleased/john_long-support-multiple-mailbox-email-check.yml b/changelogs/unreleased/john_long-support-multiple-mailbox-email-check.yml
new file mode 100644
index 00000000000..a566fb73d2c
--- /dev/null
+++ b/changelogs/unreleased/john_long-support-multiple-mailbox-email-check.yml
@@ -0,0 +1,5 @@
+---
+title: Support multiple mailboxes incoming email check
+merge_request: 35639
+author:
+type: fixed
diff --git a/changelogs/unreleased/jsonnet-template.yml b/changelogs/unreleased/jsonnet-template.yml
new file mode 100644
index 00000000000..64743e05c0f
--- /dev/null
+++ b/changelogs/unreleased/jsonnet-template.yml
@@ -0,0 +1,5 @@
+---
+title: Add Jsonnet template for GitLab
+merge_request: 37058
+author:
+type: added
diff --git a/changelogs/unreleased/jsx-analyzer.yml b/changelogs/unreleased/jsx-analyzer.yml
new file mode 100644
index 00000000000..3faf249148a
--- /dev/null
+++ b/changelogs/unreleased/jsx-analyzer.yml
@@ -0,0 +1,5 @@
+---
+title: Update eslint secure analyzer to analyze jsx
+merge_request: 36505
+author:
+type: changed
diff --git a/changelogs/unreleased/justin_ho-change-redirect-path-after-integration-save.yml b/changelogs/unreleased/justin_ho-change-redirect-path-after-integration-save.yml
new file mode 100644
index 00000000000..a6816585580
--- /dev/null
+++ b/changelogs/unreleased/justin_ho-change-redirect-path-after-integration-save.yml
@@ -0,0 +1,5 @@
+---
+title: Change redirect path after integration save
+merge_request: 34697
+author:
+type: changed
diff --git a/changelogs/unreleased/justin_ho-enable-integration_form_refactor-by-default.yml b/changelogs/unreleased/justin_ho-enable-integration_form_refactor-by-default.yml
new file mode 100644
index 00000000000..d6160ef03ff
--- /dev/null
+++ b/changelogs/unreleased/justin_ho-enable-integration_form_refactor-by-default.yml
@@ -0,0 +1,5 @@
+---
+title: Update integration form to use GitLab UI components
+merge_request: 35582
+author:
+type: changed
diff --git a/changelogs/unreleased/justin_ho-refine-ui-of-integration-form.yml b/changelogs/unreleased/justin_ho-refine-ui-of-integration-form.yml
new file mode 100644
index 00000000000..aa8a5d66703
--- /dev/null
+++ b/changelogs/unreleased/justin_ho-refine-ui-of-integration-form.yml
@@ -0,0 +1,5 @@
+---
+title: Refine UI of integration form
+merge_request: 34707
+author:
+type: changed
diff --git a/changelogs/unreleased/justin_ho-replace-fa-exchange-icon.yml b/changelogs/unreleased/justin_ho-replace-fa-exchange-icon.yml
new file mode 100644
index 00000000000..f593421e0c6
--- /dev/null
+++ b/changelogs/unreleased/justin_ho-replace-fa-exchange-icon.yml
@@ -0,0 +1,5 @@
+---
+title: Replace FA exchange icon with GitLab SVG
+merge_request: 35634
+author:
+type: changed
diff --git a/changelogs/unreleased/kassio-fix-user-default-language.yml b/changelogs/unreleased/kassio-fix-user-default-language.yml
new file mode 100644
index 00000000000..406d58a82f9
--- /dev/null
+++ b/changelogs/unreleased/kassio-fix-user-default-language.yml
@@ -0,0 +1,5 @@
+---
+title: "Use the user's preferred language as default"
+merge_request: 35676
+author:
+type: fixed
diff --git a/changelogs/unreleased/kborges-github-import-rake-add-rate-limit-doc.yml b/changelogs/unreleased/kborges-github-import-rake-add-rate-limit-doc.yml
new file mode 100644
index 00000000000..5d797b0f7a1
--- /dev/null
+++ b/changelogs/unreleased/kborges-github-import-rake-add-rate-limit-doc.yml
@@ -0,0 +1,5 @@
+---
+title: Document github rate limit behavior
+merge_request: 33090
+author:
+type: other
diff --git a/changelogs/unreleased/ld-graphql-aliased-mutations.yml b/changelogs/unreleased/ld-graphql-aliased-mutations.yml
new file mode 100644
index 00000000000..f5ddb81a9e4
--- /dev/null
+++ b/changelogs/unreleased/ld-graphql-aliased-mutations.yml
@@ -0,0 +1,6 @@
+---
+title: Rename GraphQL AwardEmoji mutations to follow naming conventions, deprecating
+ the old mutations
+merge_request: 34798
+author:
+type: changed
diff --git a/changelogs/unreleased/leaky-constant-fix-11.yml b/changelogs/unreleased/leaky-constant-fix-11.yml
new file mode 100644
index 00000000000..b373491e61c
--- /dev/null
+++ b/changelogs/unreleased/leaky-constant-fix-11.yml
@@ -0,0 +1,5 @@
+---
+title: Fix leaky constant issue in commit entity spec
+merge_request: 32039
+author: Rajendra Kadam
+type: fixed
diff --git a/changelogs/unreleased/leaky-constant-fix-14.yml b/changelogs/unreleased/leaky-constant-fix-14.yml
new file mode 100644
index 00000000000..b124371e415
--- /dev/null
+++ b/changelogs/unreleased/leaky-constant-fix-14.yml
@@ -0,0 +1,5 @@
+---
+title: Fix leaky constant issue in task completion status spec
+merge_request: 32043
+author: Rajendra Kadam
+type: fixed
diff --git a/changelogs/unreleased/leaky-constant-fix-22.yml b/changelogs/unreleased/leaky-constant-fix-22.yml
new file mode 100644
index 00000000000..ebeaceee09f
--- /dev/null
+++ b/changelogs/unreleased/leaky-constant-fix-22.yml
@@ -0,0 +1,5 @@
+---
+title: Fix leaky constant issue in admin mode migration spec
+merge_request: 32074
+author: Rajendra Kadam
+type: fixed
diff --git a/changelogs/unreleased/leaky-constant-fix-28.yml b/changelogs/unreleased/leaky-constant-fix-28.yml
new file mode 100644
index 00000000000..d1a04a25caa
--- /dev/null
+++ b/changelogs/unreleased/leaky-constant-fix-28.yml
@@ -0,0 +1,5 @@
+---
+title: Fix leaky constant issue in sidekiq middleware server metric spec
+merge_request: 32104
+author: Rajendra Kadam
+type: fixed
diff --git a/changelogs/unreleased/leaky-constant-fix-29.yml b/changelogs/unreleased/leaky-constant-fix-29.yml
new file mode 100644
index 00000000000..eafa7e599c6
--- /dev/null
+++ b/changelogs/unreleased/leaky-constant-fix-29.yml
@@ -0,0 +1,5 @@
+---
+title: Fix leaky constant issue in sidekiq middleware client metric spec
+merge_request: 32108
+author: Rajendra Kadam
+type: fixed
diff --git a/changelogs/unreleased/leaky-constant-fix-3.yml b/changelogs/unreleased/leaky-constant-fix-3.yml
new file mode 100644
index 00000000000..3ad8fd7a15f
--- /dev/null
+++ b/changelogs/unreleased/leaky-constant-fix-3.yml
@@ -0,0 +1,5 @@
+---
+title: Add class stubs and fix leaky constant alert in query limit helper spec
+merge_request: 31949
+author: Rajendra Kadam
+type: fixed
diff --git a/changelogs/unreleased/leaky-constant-fix-30.yml b/changelogs/unreleased/leaky-constant-fix-30.yml
new file mode 100644
index 00000000000..f4f957d5b08
--- /dev/null
+++ b/changelogs/unreleased/leaky-constant-fix-30.yml
@@ -0,0 +1,5 @@
+---
+title: Fix leaky constant issue in path regex spec
+merge_request: 32115
+author: Rajendra Kadam
+type: fixed
diff --git a/changelogs/unreleased/leaky-constant-fix-32.yml b/changelogs/unreleased/leaky-constant-fix-32.yml
new file mode 100644
index 00000000000..a5c5674d5da
--- /dev/null
+++ b/changelogs/unreleased/leaky-constant-fix-32.yml
@@ -0,0 +1,5 @@
+---
+title: Fix leaky constant issue importer and cache headers spec
+merge_request: 32122
+author: Rajendra Kadam
+type: fixed
diff --git a/changelogs/unreleased/leaky-constant-fix-33.yml b/changelogs/unreleased/leaky-constant-fix-33.yml
new file mode 100644
index 00000000000..f53da019813
--- /dev/null
+++ b/changelogs/unreleased/leaky-constant-fix-33.yml
@@ -0,0 +1,5 @@
+---
+title: Fix leaky constant issue in relation factory spec
+merge_request: 32129
+author: Rajendra Kadam
+type: fixed
diff --git a/changelogs/unreleased/leaky-constant-fix-34.yml b/changelogs/unreleased/leaky-constant-fix-34.yml
new file mode 100644
index 00000000000..822b9ed0471
--- /dev/null
+++ b/changelogs/unreleased/leaky-constant-fix-34.yml
@@ -0,0 +1,5 @@
+---
+title: Fix leaky constant issue in test coverage spec
+merge_request: 32134
+author: Rajendra Kadam
+type: fixed
diff --git a/changelogs/unreleased/leaky-constant-fix-36.yml b/changelogs/unreleased/leaky-constant-fix-36.yml
new file mode 100644
index 00000000000..1c487a8329e
--- /dev/null
+++ b/changelogs/unreleased/leaky-constant-fix-36.yml
@@ -0,0 +1,5 @@
+---
+title: Fix leaky constant issue in diff collection spec
+merge_request: 32163
+author: Rajendra Kadam
+type: fixed
diff --git a/changelogs/unreleased/leaky-constant-fix-37.yml b/changelogs/unreleased/leaky-constant-fix-37.yml
new file mode 100644
index 00000000000..af986b7bb13
--- /dev/null
+++ b/changelogs/unreleased/leaky-constant-fix-37.yml
@@ -0,0 +1,5 @@
+---
+title: Fix leaky constant issue in migration helpers, with lock retries and ignored cols spec
+merge_request: 32170
+author: Rajendra Kadam
+type: fixed
diff --git a/changelogs/unreleased/leaky-constant-fix-38.yml b/changelogs/unreleased/leaky-constant-fix-38.yml
new file mode 100644
index 00000000000..7ed7fc71da6
--- /dev/null
+++ b/changelogs/unreleased/leaky-constant-fix-38.yml
@@ -0,0 +1,5 @@
+---
+title: Fix leaky constant issue in factory spec
+merge_request: 32174
+author: Rajendra Kadam
+type: fixed
diff --git a/changelogs/unreleased/leaky-constant-fix-39.yml b/changelogs/unreleased/leaky-constant-fix-39.yml
new file mode 100644
index 00000000000..13ea6ac7cb4
--- /dev/null
+++ b/changelogs/unreleased/leaky-constant-fix-39.yml
@@ -0,0 +1,5 @@
+---
+title: Fix leaky constant issue in creds factory spec
+merge_request: 32176
+author: Rajendra Kadam
+type: fixed
diff --git a/changelogs/unreleased/leaky-constant-fix-5.yml b/changelogs/unreleased/leaky-constant-fix-5.yml
new file mode 100644
index 00000000000..b67057fe237
--- /dev/null
+++ b/changelogs/unreleased/leaky-constant-fix-5.yml
@@ -0,0 +1,5 @@
+---
+title: Remove usage of spam constants in spec
+merge_request: 31959
+author: Rajendra Kadam
+type: fixed
diff --git a/changelogs/unreleased/leaky-constant-fix-9.yml b/changelogs/unreleased/leaky-constant-fix-9.yml
new file mode 100644
index 00000000000..e361c58c746
--- /dev/null
+++ b/changelogs/unreleased/leaky-constant-fix-9.yml
@@ -0,0 +1,5 @@
+---
+title: Fix leaky constant issue in uninstall progress service check
+merge_request: 32036
+author: Rajendra Kadam
+type: fixed
diff --git a/changelogs/unreleased/leipert-remove-ie11-polyfills.yml b/changelogs/unreleased/leipert-remove-ie11-polyfills.yml
new file mode 100644
index 00000000000..7ea38e24845
--- /dev/null
+++ b/changelogs/unreleased/leipert-remove-ie11-polyfills.yml
@@ -0,0 +1,5 @@
+---
+title: Remove Internet Explorer 11 specific polyfills
+merge_request: 36830
+author:
+type: removed
diff --git a/changelogs/unreleased/limit-alert-assignees.yml b/changelogs/unreleased/limit-alert-assignees.yml
new file mode 100644
index 00000000000..8e57e2c0562
--- /dev/null
+++ b/changelogs/unreleased/limit-alert-assignees.yml
@@ -0,0 +1,5 @@
+---
+title: Limit alert assignment to only users who can read alerts
+merge_request: 34681
+author:
+type: fixed
diff --git a/changelogs/unreleased/lm-change-sorting.yml b/changelogs/unreleased/lm-change-sorting.yml
new file mode 100644
index 00000000000..d061995cb48
--- /dev/null
+++ b/changelogs/unreleased/lm-change-sorting.yml
@@ -0,0 +1,5 @@
+---
+title: 'Alert Managament: Change sorting order to have newest alerts first'
+merge_request: 33642
+author:
+type: changed
diff --git a/changelogs/unreleased/lm-follow-up.yml b/changelogs/unreleased/lm-follow-up.yml
new file mode 100644
index 00000000000..afb305c9738
--- /dev/null
+++ b/changelogs/unreleased/lm-follow-up.yml
@@ -0,0 +1,5 @@
+---
+title: Removes ci_ensure_scheduling_type feature flag
+merge_request: 36140
+author:
+type: other
diff --git a/changelogs/unreleased/lm-hover-state-sort.yml b/changelogs/unreleased/lm-hover-state-sort.yml
new file mode 100644
index 00000000000..4a701511787
--- /dev/null
+++ b/changelogs/unreleased/lm-hover-state-sort.yml
@@ -0,0 +1,5 @@
+---
+title: Add hovering icon for sorting columns on alert management list
+merge_request: 33429
+author:
+type: other
diff --git a/changelogs/unreleased/lm-remove-counts-and-redirect.yml b/changelogs/unreleased/lm-remove-counts-and-redirect.yml
new file mode 100644
index 00000000000..c8698e6b8e5
--- /dev/null
+++ b/changelogs/unreleased/lm-remove-counts-and-redirect.yml
@@ -0,0 +1,5 @@
+---
+title: Remove count for pending/running/finished pipelines in tabs
+merge_request: 35693
+author:
+type: changed
diff --git a/changelogs/unreleased/lm-resolve-timeout.yml b/changelogs/unreleased/lm-resolve-timeout.yml
new file mode 100644
index 00000000000..d4a34db9ea9
--- /dev/null
+++ b/changelogs/unreleased/lm-resolve-timeout.yml
@@ -0,0 +1,5 @@
+---
+title: Resolve timeout in admin/jobs
+merge_request: 35385
+author:
+type: fixed
diff --git a/changelogs/unreleased/lm-sorting-list.yml b/changelogs/unreleased/lm-sorting-list.yml
new file mode 100644
index 00000000000..e1a9dceec5c
--- /dev/null
+++ b/changelogs/unreleased/lm-sorting-list.yml
@@ -0,0 +1,5 @@
+---
+title: Adds sorting by column to alert management list
+merge_request: 32478
+author:
+type: added
diff --git a/changelogs/unreleased/long-path-mr-bug.yml b/changelogs/unreleased/long-path-mr-bug.yml
new file mode 100644
index 00000000000..ab171f3da5d
--- /dev/null
+++ b/changelogs/unreleased/long-path-mr-bug.yml
@@ -0,0 +1,5 @@
+---
+title: Fix rendering of very long paths in merge request file tree
+merge_request: 34153
+author:
+type: fixed
diff --git a/changelogs/unreleased/make-fixed-notification-default-enabled.yml b/changelogs/unreleased/make-fixed-notification-default-enabled.yml
new file mode 100644
index 00000000000..7c66e234220
--- /dev/null
+++ b/changelogs/unreleased/make-fixed-notification-default-enabled.yml
@@ -0,0 +1,5 @@
+---
+title: Send fixed pipeline notification by default
+merge_request: 34589
+author:
+type: added
diff --git a/changelogs/unreleased/make_repository_moves_job_idempotent.yml b/changelogs/unreleased/make_repository_moves_job_idempotent.yml
new file mode 100644
index 00000000000..546e1511763
--- /dev/null
+++ b/changelogs/unreleased/make_repository_moves_job_idempotent.yml
@@ -0,0 +1,5 @@
+---
+title: Make ProjectUpdateRepositoryStorageWorker idempotent
+merge_request: 35483
+author:
+type: fixed
diff --git a/changelogs/unreleased/markdown-toolbar-list-style.yml b/changelogs/unreleased/markdown-toolbar-list-style.yml
new file mode 100644
index 00000000000..4121af3e744
--- /dev/null
+++ b/changelogs/unreleased/markdown-toolbar-list-style.yml
@@ -0,0 +1,5 @@
+---
+title: Set markdown toolbar to use hyphens for lists
+merge_request: 31426
+author:
+type: changed
diff --git a/changelogs/unreleased/mattkasa-207510-terraform-state-usage-ping.yml b/changelogs/unreleased/mattkasa-207510-terraform-state-usage-ping.yml
new file mode 100644
index 00000000000..9d047e66b4b
--- /dev/null
+++ b/changelogs/unreleased/mattkasa-207510-terraform-state-usage-ping.yml
@@ -0,0 +1,5 @@
+---
+title: Add usage data metrics for terraform states
+merge_request: 31280
+author:
+type: added
diff --git a/changelogs/unreleased/mattkasa-207532-add-usage-ping-for-terraform-reports.yml b/changelogs/unreleased/mattkasa-207532-add-usage-ping-for-terraform-reports.yml
new file mode 100644
index 00000000000..8656f398610
--- /dev/null
+++ b/changelogs/unreleased/mattkasa-207532-add-usage-ping-for-terraform-reports.yml
@@ -0,0 +1,5 @@
+---
+title: Add usage data metrics for terraform reports
+merge_request: 31281
+author:
+type: added
diff --git a/changelogs/unreleased/merge-cancel-deploy-followup.yml b/changelogs/unreleased/merge-cancel-deploy-followup.yml
new file mode 100644
index 00000000000..be634cede6c
--- /dev/null
+++ b/changelogs/unreleased/merge-cancel-deploy-followup.yml
@@ -0,0 +1,5 @@
+---
+title: Guard against data integrity issues when canceling review app jobs
+merge_request: 35555
+author:
+type: fixed
diff --git a/changelogs/unreleased/merge-cancel-deploy.yml b/changelogs/unreleased/merge-cancel-deploy.yml
new file mode 100644
index 00000000000..dc684b8292e
--- /dev/null
+++ b/changelogs/unreleased/merge-cancel-deploy.yml
@@ -0,0 +1,5 @@
+---
+title: Cancel review app deployment when MR is merged
+merge_request: 34960
+author:
+type: fixed
diff --git a/changelogs/unreleased/merge-ref-diffs-position-updates.yml b/changelogs/unreleased/merge-ref-diffs-position-updates.yml
new file mode 100644
index 00000000000..ffd3ad05f5c
--- /dev/null
+++ b/changelogs/unreleased/merge-ref-diffs-position-updates.yml
@@ -0,0 +1,5 @@
+---
+title: Update diff discussion positions on demand
+merge_request: 34148
+author:
+type: added
diff --git a/changelogs/unreleased/merge-tslint-with-eslint.yml b/changelogs/unreleased/merge-tslint-with-eslint.yml
new file mode 100644
index 00000000000..d9f9d83339a
--- /dev/null
+++ b/changelogs/unreleased/merge-tslint-with-eslint.yml
@@ -0,0 +1,5 @@
+---
+title: Merge tslint secure analyzer with eslint secure analyzer
+merge_request: 36400
+author:
+type: changed
diff --git a/changelogs/unreleased/mf-codequality-widget-frontend-feature-move.yml b/changelogs/unreleased/mf-codequality-widget-frontend-feature-move.yml
new file mode 100644
index 00000000000..93aadbedec4
--- /dev/null
+++ b/changelogs/unreleased/mf-codequality-widget-frontend-feature-move.yml
@@ -0,0 +1,5 @@
+---
+title: Use new vuex store for code quality MR widget
+merge_request: 36120
+author:
+type: changed
diff --git a/changelogs/unreleased/mg-fix-katex-fonts.yml b/changelogs/unreleased/mg-fix-katex-fonts.yml
new file mode 100644
index 00000000000..71acf5ca351
--- /dev/null
+++ b/changelogs/unreleased/mg-fix-katex-fonts.yml
@@ -0,0 +1,5 @@
+---
+title: Fix KaTeX font paths
+merge_request: 33338
+author:
+type: fixed
diff --git a/changelogs/unreleased/migration-confirm-project-bot-users.yml b/changelogs/unreleased/migration-confirm-project-bot-users.yml
new file mode 100644
index 00000000000..6e29aa5e162
--- /dev/null
+++ b/changelogs/unreleased/migration-confirm-project-bot-users.yml
@@ -0,0 +1,5 @@
+---
+title: Mark existing Project Bot Users as confirmed
+merge_request: 36692
+author:
+type: fixed
diff --git a/changelogs/unreleased/mo-add-build-report-result.yml b/changelogs/unreleased/mo-add-build-report-result.yml
new file mode 100644
index 00000000000..4b24a194500
--- /dev/null
+++ b/changelogs/unreleased/mo-add-build-report-result.yml
@@ -0,0 +1,5 @@
+---
+title: Add build report results data model
+merge_request: 32991
+author:
+type: performance
diff --git a/changelogs/unreleased/monospace-ci-variable-value.yml b/changelogs/unreleased/monospace-ci-variable-value.yml
new file mode 100644
index 00000000000..5b24c28e30e
--- /dev/null
+++ b/changelogs/unreleased/monospace-ci-variable-value.yml
@@ -0,0 +1,5 @@
+---
+title: Change CI variable font family to monospace
+merge_request: 34788
+author: Aaron Walker
+type: changed
diff --git a/changelogs/unreleased/move-delete-to-bottom-of-list.yml b/changelogs/unreleased/move-delete-to-bottom-of-list.yml
new file mode 100644
index 00000000000..0c6b322829d
--- /dev/null
+++ b/changelogs/unreleased/move-delete-to-bottom-of-list.yml
@@ -0,0 +1,5 @@
+---
+title: Move 'Delete comment' button to bottom of 'More actions' list
+merge_request: 35237
+author:
+type: fixed
diff --git a/changelogs/unreleased/move-has-status-concern-to-ci-namespace.yml b/changelogs/unreleased/move-has-status-concern-to-ci-namespace.yml
new file mode 100644
index 00000000000..0eb359024fe
--- /dev/null
+++ b/changelogs/unreleased/move-has-status-concern-to-ci-namespace.yml
@@ -0,0 +1,5 @@
+---
+title: Move HasStatus module to the Ci namespace
+merge_request: 34577
+author: blackst0ne
+type: other
diff --git a/changelogs/unreleased/move-profiles-keys-get-keys-to-users.yml b/changelogs/unreleased/move-profiles-keys-get-keys-to-users.yml
new file mode 100644
index 00000000000..d36674be345
--- /dev/null
+++ b/changelogs/unreleased/move-profiles-keys-get-keys-to-users.yml
@@ -0,0 +1,5 @@
+---
+title: Move profiles/keys#get_keys to users#ssh_keys
+merge_request: 35507
+author: Takuya Noguchi
+type: other
diff --git a/changelogs/unreleased/move_migration_to_post_deployment.yml b/changelogs/unreleased/move_migration_to_post_deployment.yml
new file mode 100644
index 00000000000..d70ab5249c4
--- /dev/null
+++ b/changelogs/unreleased/move_migration_to_post_deployment.yml
@@ -0,0 +1,5 @@
+---
+title: Move migration related to ci_builds to post_deployment
+merge_request: 33416
+author:
+type: performance
diff --git a/changelogs/unreleased/mr-rebase-button-move-to-bottom.yml b/changelogs/unreleased/mr-rebase-button-move-to-bottom.yml
new file mode 100644
index 00000000000..45191e55ab8
--- /dev/null
+++ b/changelogs/unreleased/mr-rebase-button-move-to-bottom.yml
@@ -0,0 +1,5 @@
+---
+title: Check WIP status after all other possible statuses
+merge_request: 36624
+author:
+type: changed
diff --git a/changelogs/unreleased/mvanremmerden-master-patch-68859.yml b/changelogs/unreleased/mvanremmerden-master-patch-68859.yml
new file mode 100644
index 00000000000..931fd7a6829
--- /dev/null
+++ b/changelogs/unreleased/mvanremmerden-master-patch-68859.yml
@@ -0,0 +1,5 @@
+---
+title: Fix styling bug for disabled merge button
+merge_request: 35365
+author:
+type: fixed
diff --git a/changelogs/unreleased/mwaw-208224-move-cluster-metrics-dashboard-endpoint-into-gitlab-core-BE.yml b/changelogs/unreleased/mwaw-208224-move-cluster-metrics-dashboard-endpoint-into-gitlab-core-BE.yml
new file mode 100644
index 00000000000..c0bcc88cc92
--- /dev/null
+++ b/changelogs/unreleased/mwaw-208224-move-cluster-metrics-dashboard-endpoint-into-gitlab-core-BE.yml
@@ -0,0 +1,5 @@
+---
+title: Open source cluster health dashboard and make it available to all users
+merge_request: 35721
+author:
+type: changed
diff --git a/changelogs/unreleased/mwaw-208790-allow-logs-controller-to-use-cluster-id-parameter.yml b/changelogs/unreleased/mwaw-208790-allow-logs-controller-to-use-cluster-id-parameter.yml
new file mode 100644
index 00000000000..59782723970
--- /dev/null
+++ b/changelogs/unreleased/mwaw-208790-allow-logs-controller-to-use-cluster-id-parameter.yml
@@ -0,0 +1,5 @@
+---
+title: Expose gitlab managed apps logs inside log explorer
+merge_request: 36336
+author:
+type: added
diff --git a/changelogs/unreleased/mwaw-210289-add-metrics-dashboard-validation-to-grapql.yml b/changelogs/unreleased/mwaw-210289-add-metrics-dashboard-validation-to-grapql.yml
new file mode 100644
index 00000000000..675f08ffe27
--- /dev/null
+++ b/changelogs/unreleased/mwaw-210289-add-metrics-dashboard-validation-to-grapql.yml
@@ -0,0 +1,5 @@
+---
+title: Add dashboard schema validation warnings as metrics dashboard GraphQL field
+merge_request: 33592
+author:
+type: added
diff --git a/changelogs/unreleased/mwaw-210289-fix-dashboard-validation-false-positives-warnings.yml b/changelogs/unreleased/mwaw-210289-fix-dashboard-validation-false-positives-warnings.yml
new file mode 100644
index 00000000000..9b28639e534
--- /dev/null
+++ b/changelogs/unreleased/mwaw-210289-fix-dashboard-validation-false-positives-warnings.yml
@@ -0,0 +1,5 @@
+---
+title: Fix 500 errors and false positive warnings during metrics dashboard validation.
+merge_request: 34166
+author:
+type: fixed
diff --git a/changelogs/unreleased/mwaw-210289-metrics-dashboard-file-validation-mvc-1.yml b/changelogs/unreleased/mwaw-210289-metrics-dashboard-file-validation-mvc-1.yml
new file mode 100644
index 00000000000..7c07e780906
--- /dev/null
+++ b/changelogs/unreleased/mwaw-210289-metrics-dashboard-file-validation-mvc-1.yml
@@ -0,0 +1,5 @@
+---
+title: Added validation for YAML files with metrics dashboard definitions.
+merge_request: 33202
+author:
+type: added
diff --git a/changelogs/unreleased/mwaw-211433-metrics-dashboard-annotations-reaper-job-database-layer.yml b/changelogs/unreleased/mwaw-211433-metrics-dashboard-annotations-reaper-job-database-layer.yml
new file mode 100644
index 00000000000..454f6aab8e9
--- /dev/null
+++ b/changelogs/unreleased/mwaw-211433-metrics-dashboard-annotations-reaper-job-database-layer.yml
@@ -0,0 +1,6 @@
+---
+title: Table index added to `metrics_dashboard_annotations` for future pruning of stale metrics
+ Annotations for metrics dashboards are now checked for valid start and end dates.
+merge_request: 32433
+author:
+type: added
diff --git a/changelogs/unreleased/mwaw-211433-metrics-dashboard-annotations-reaper-job-worker.yml b/changelogs/unreleased/mwaw-211433-metrics-dashboard-annotations-reaper-job-worker.yml
new file mode 100644
index 00000000000..4dfeb792c5a
--- /dev/null
+++ b/changelogs/unreleased/mwaw-211433-metrics-dashboard-annotations-reaper-job-worker.yml
@@ -0,0 +1,5 @@
+---
+title: Remove metrics dashboard annotations attached to time periods older than two weeks.
+merge_request: 32838
+author:
+type: added
diff --git a/changelogs/unreleased/nfriend-add-CI_ROOT_NAMESPACE-env-variable.yml b/changelogs/unreleased/nfriend-add-CI_ROOT_NAMESPACE-env-variable.yml
new file mode 100644
index 00000000000..5ebee05975f
--- /dev/null
+++ b/changelogs/unreleased/nfriend-add-CI_ROOT_NAMESPACE-env-variable.yml
@@ -0,0 +1,5 @@
+---
+title: Add CI_PROJECT_ROOT_NAMESPACE predefined environment variable
+merge_request: 34733
+author:
+type: added
diff --git a/changelogs/unreleased/nfriend-add-copy-branch-name-shortcut.yml b/changelogs/unreleased/nfriend-add-copy-branch-name-shortcut.yml
new file mode 100644
index 00000000000..b039955edc9
--- /dev/null
+++ b/changelogs/unreleased/nfriend-add-copy-branch-name-shortcut.yml
@@ -0,0 +1,5 @@
+---
+title: Add keyboard shortcut ('b') to copy MR source branch name on MR page
+merge_request: 36338
+author:
+type: added
diff --git a/changelogs/unreleased/nfriend-add-issue-stats-to-milestones-graphql.yml b/changelogs/unreleased/nfriend-add-issue-stats-to-milestones-graphql.yml
new file mode 100644
index 00000000000..73cb73cd41f
--- /dev/null
+++ b/changelogs/unreleased/nfriend-add-issue-stats-to-milestones-graphql.yml
@@ -0,0 +1,5 @@
+---
+title: Add milestone stats to GraphQL endpoint
+merge_request: 35066
+author:
+type: added
diff --git a/changelogs/unreleased/nfriend-enable-graphql_release_data-feature-flag.yml b/changelogs/unreleased/nfriend-enable-graphql_release_data-feature-flag.yml
new file mode 100644
index 00000000000..b56c2337149
--- /dev/null
+++ b/changelogs/unreleased/nfriend-enable-graphql_release_data-feature-flag.yml
@@ -0,0 +1,5 @@
+---
+title: Add release data to GraphQL endpoint
+merge_request: 34937
+author:
+type: added
diff --git a/changelogs/unreleased/nfriend-enable-release_asset_link_type-f.yml b/changelogs/unreleased/nfriend-enable-release_asset_link_type-f.yml
new file mode 100644
index 00000000000..dc24c002774
--- /dev/null
+++ b/changelogs/unreleased/nfriend-enable-release_asset_link_type-f.yml
@@ -0,0 +1,5 @@
+---
+title: Allow Release asset links to be associated with a type
+merge_request: 33998
+author:
+type: added
diff --git a/changelogs/unreleased/nfriend-fix-assets-for-guest-users.yml b/changelogs/unreleased/nfriend-fix-assets-for-guest-users.yml
new file mode 100644
index 00000000000..b32b5dc39bc
--- /dev/null
+++ b/changelogs/unreleased/nfriend-fix-assets-for-guest-users.yml
@@ -0,0 +1,5 @@
+---
+title: Fix release assets for Guest users of private projects
+merge_request: 35166
+author:
+type: fixed
diff --git a/changelogs/unreleased/nfriend-fix-npm-template.yml b/changelogs/unreleased/nfriend-fix-npm-template.yml
new file mode 100644
index 00000000000..11b17d7711b
--- /dev/null
+++ b/changelogs/unreleased/nfriend-fix-npm-template.yml
@@ -0,0 +1,5 @@
+---
+title: Remove hardcoded reference to gitlab.com in NPM .gitlab-ci.yml template
+merge_request: 36124
+author:
+type: fixed
diff --git a/changelogs/unreleased/nfriend-fix-release-button-alignment.yml b/changelogs/unreleased/nfriend-fix-release-button-alignment.yml
new file mode 100644
index 00000000000..79c6087fc54
--- /dev/null
+++ b/changelogs/unreleased/nfriend-fix-release-button-alignment.yml
@@ -0,0 +1,5 @@
+---
+title: Fix alignment of button text on the Edit Release page
+merge_request: 33104
+author:
+type: fixed
diff --git a/changelogs/unreleased/nfriend-fix-theme-alignment.yml b/changelogs/unreleased/nfriend-fix-theme-alignment.yml
new file mode 100644
index 00000000000..e3e55479a07
--- /dev/null
+++ b/changelogs/unreleased/nfriend-fix-theme-alignment.yml
@@ -0,0 +1,5 @@
+---
+title: Fix alignment of navigation theme options
+merge_request: 35041
+author:
+type: fixed
diff --git a/changelogs/unreleased/nfriend-validate-package-name.yml b/changelogs/unreleased/nfriend-validate-package-name.yml
new file mode 100644
index 00000000000..d09b6137076
--- /dev/null
+++ b/changelogs/unreleased/nfriend-validate-package-name.yml
@@ -0,0 +1,5 @@
+---
+title: Add package scope validation to Node.js template
+merge_request: 34778
+author:
+type: added
diff --git a/changelogs/unreleased/nicolasdular-additional-purchased-storage-db.yml b/changelogs/unreleased/nicolasdular-additional-purchased-storage-db.yml
new file mode 100644
index 00000000000..b43e0e7f33d
--- /dev/null
+++ b/changelogs/unreleased/nicolasdular-additional-purchased-storage-db.yml
@@ -0,0 +1,5 @@
+---
+title: Create namespace_limits table with additional purchase columns
+merge_request: 34746
+author:
+type: added
diff --git a/changelogs/unreleased/nicolasdular-broadcast-notification-close-btn.yml b/changelogs/unreleased/nicolasdular-broadcast-notification-close-btn.yml
new file mode 100644
index 00000000000..f7eb40584af
--- /dev/null
+++ b/changelogs/unreleased/nicolasdular-broadcast-notification-close-btn.yml
@@ -0,0 +1,5 @@
+---
+title: Move broadcast notification dismiss button to the top
+merge_request: 33174
+author:
+type: changed
diff --git a/changelogs/unreleased/nicolasdular-expose-storage-limit-in-api.yml b/changelogs/unreleased/nicolasdular-expose-storage-limit-in-api.yml
new file mode 100644
index 00000000000..f0fbc00325c
--- /dev/null
+++ b/changelogs/unreleased/nicolasdular-expose-storage-limit-in-api.yml
@@ -0,0 +1,5 @@
+---
+title: Expose storage size limit for namespaces in GraphQL
+merge_request: 34882
+author:
+type: changed
diff --git a/changelogs/unreleased/nicolasdular-fix-graphql-storage-type.yml b/changelogs/unreleased/nicolasdular-fix-graphql-storage-type.yml
new file mode 100644
index 00000000000..8311844ce1d
--- /dev/null
+++ b/changelogs/unreleased/nicolasdular-fix-graphql-storage-type.yml
@@ -0,0 +1,5 @@
+---
+title: Use FLOAT_TYPE for storage limit
+merge_request: 35559
+author:
+type: fixed
diff --git a/changelogs/unreleased/notes-ee-feature.yml b/changelogs/unreleased/notes-ee-feature.yml
new file mode 100644
index 00000000000..38d9471ce39
--- /dev/null
+++ b/changelogs/unreleased/notes-ee-feature.yml
@@ -0,0 +1,5 @@
+---
+title: Move Admin note feature to GitLab Core
+merge_request: 31457
+author: Rajendra
+type: added
diff --git a/changelogs/unreleased/ntepluhina-fix-note-scrolling.yml b/changelogs/unreleased/ntepluhina-fix-note-scrolling.yml
new file mode 100644
index 00000000000..8bd0a746504
--- /dev/null
+++ b/changelogs/unreleased/ntepluhina-fix-note-scrolling.yml
@@ -0,0 +1,5 @@
+---
+title: Fix design note scrolling
+merge_request: 33939
+author:
+type: fixed
diff --git a/changelogs/unreleased/optimize-milestones-page.yml b/changelogs/unreleased/optimize-milestones-page.yml
new file mode 100644
index 00000000000..dd067c08faf
--- /dev/null
+++ b/changelogs/unreleased/optimize-milestones-page.yml
@@ -0,0 +1,5 @@
+---
+title: Optimize SQL queries on Milestone index page
+merge_request: 32953
+author:
+type: performance
diff --git a/changelogs/unreleased/osw-add-redis-metrics-to-sidekiq-job-run.yml b/changelogs/unreleased/osw-add-redis-metrics-to-sidekiq-job-run.yml
new file mode 100644
index 00000000000..88efab2233c
--- /dev/null
+++ b/changelogs/unreleased/osw-add-redis-metrics-to-sidekiq-job-run.yml
@@ -0,0 +1,5 @@
+---
+title: Add metrics for Redis usage during Sidekiq job execution
+merge_request: 32265
+author:
+type: added
diff --git a/changelogs/unreleased/osw-add-redis-metrics-to-web-requests.yml b/changelogs/unreleased/osw-add-redis-metrics-to-web-requests.yml
new file mode 100644
index 00000000000..77602f9a31c
--- /dev/null
+++ b/changelogs/unreleased/osw-add-redis-metrics-to-web-requests.yml
@@ -0,0 +1,5 @@
+---
+title: Add metrics for Redis usage during web requests
+merge_request: 32605
+author:
+type: added
diff --git a/changelogs/unreleased/osw-instrument-lazily-consumed-gitaly-streams.yml b/changelogs/unreleased/osw-instrument-lazily-consumed-gitaly-streams.yml
new file mode 100644
index 00000000000..4969d0f4dc8
--- /dev/null
+++ b/changelogs/unreleased/osw-instrument-lazily-consumed-gitaly-streams.yml
@@ -0,0 +1,5 @@
+---
+title: Add instrumentation to Gitaly streamed responses
+merge_request: 35283
+author:
+type: fixed
diff --git a/changelogs/unreleased/osw-separate-redis-logs.yml b/changelogs/unreleased/osw-separate-redis-logs.yml
new file mode 100644
index 00000000000..dbef2557df7
--- /dev/null
+++ b/changelogs/unreleased/osw-separate-redis-logs.yml
@@ -0,0 +1,6 @@
+---
+title: Add detailed logs of each Redis instance usage during job execution and web
+ requests
+merge_request: 34110
+author:
+type: added
diff --git a/changelogs/unreleased/osw-update-gitlab-pages-to-1-20.yml b/changelogs/unreleased/osw-update-gitlab-pages-to-1-20.yml
new file mode 100644
index 00000000000..36f83fa9c9a
--- /dev/null
+++ b/changelogs/unreleased/osw-update-gitlab-pages-to-1-20.yml
@@ -0,0 +1,5 @@
+---
+title: Upgrade Pages to 1.20.0
+merge_request: 35177
+author:
+type: added
diff --git a/changelogs/unreleased/pages-1-21-0.yml b/changelogs/unreleased/pages-1-21-0.yml
new file mode 100644
index 00000000000..aee1f177a8e
--- /dev/null
+++ b/changelogs/unreleased/pages-1-21-0.yml
@@ -0,0 +1,5 @@
+---
+title: Upgrade GitLab Pages to 1.21.0
+merge_request: 36214
+author:
+type: added
diff --git a/changelogs/unreleased/parameterize-pg-deprecation-notice.yml b/changelogs/unreleased/parameterize-pg-deprecation-notice.yml
new file mode 100644
index 00000000000..80fa8bf0052
--- /dev/null
+++ b/changelogs/unreleased/parameterize-pg-deprecation-notice.yml
@@ -0,0 +1,5 @@
+---
+title: Parameterize PG deprecation notice
+merge_request: 35271
+author:
+type: changed
diff --git a/changelogs/unreleased/patch-109.yml b/changelogs/unreleased/patch-109.yml
new file mode 100644
index 00000000000..4b716f004d6
--- /dev/null
+++ b/changelogs/unreleased/patch-109.yml
@@ -0,0 +1,5 @@
+---
+title: Fixed issue (#198424) that prevented k8s authentication with intermediate certificates
+merge_request: 31254
+author: Abdelrahman Mohamed
+type: fixed
diff --git a/changelogs/unreleased/pb-background-migration-tracking-cleanup-on-rollback.yml b/changelogs/unreleased/pb-background-migration-tracking-cleanup-on-rollback.yml
new file mode 100644
index 00000000000..565f5519ddc
--- /dev/null
+++ b/changelogs/unreleased/pb-background-migration-tracking-cleanup-on-rollback.yml
@@ -0,0 +1,5 @@
+---
+title: Delete tracking records on partitioning migration rollback
+merge_request: 36743
+author:
+type: fixed
diff --git a/changelogs/unreleased/pb-move-merge-requests-users-metric.yml b/changelogs/unreleased/pb-move-merge-requests-users-metric.yml
new file mode 100644
index 00000000000..637525daed1
--- /dev/null
+++ b/changelogs/unreleased/pb-move-merge-requests-users-metric.yml
@@ -0,0 +1,5 @@
+---
+title: Move merge_requests_users metric to stage section
+merge_request: 35593
+author:
+type: changed
diff --git a/changelogs/unreleased/perf-use-build-stubbed.yml b/changelogs/unreleased/perf-use-build-stubbed.yml
new file mode 100644
index 00000000000..0f78d8649df
--- /dev/null
+++ b/changelogs/unreleased/perf-use-build-stubbed.yml
@@ -0,0 +1,5 @@
+---
+title: Use build_stubbed to avoid interacting with the DB in todos helper specs
+merge_request: 32906
+author: Arun Kumar Mohan
+type: performance
diff --git a/changelogs/unreleased/ph-220889-fixMermaidNotRendering.yml b/changelogs/unreleased/ph-220889-fixMermaidNotRendering.yml
new file mode 100644
index 00000000000..ff55f975586
--- /dev/null
+++ b/changelogs/unreleased/ph-220889-fixMermaidNotRendering.yml
@@ -0,0 +1,5 @@
+---
+title: Fixed mermaid not rendering when switching diff tabs
+merge_request: 35023
+author:
+type: fixed
diff --git a/changelogs/unreleased/ph-222790-diffSingleFileView.yml b/changelogs/unreleased/ph-222790-diffSingleFileView.yml
new file mode 100644
index 00000000000..96167c59294
--- /dev/null
+++ b/changelogs/unreleased/ph-222790-diffSingleFileView.yml
@@ -0,0 +1,5 @@
+---
+title: Allow diffs to be viewed file-by-file
+merge_request: 35223
+author: rinslow
+type: added
diff --git a/changelogs/unreleased/ph-28154-moveFrontendBatchCommentsFiles.yml b/changelogs/unreleased/ph-28154-moveFrontendBatchCommentsFiles.yml
new file mode 100644
index 00000000000..9deb3e23517
--- /dev/null
+++ b/changelogs/unreleased/ph-28154-moveFrontendBatchCommentsFiles.yml
@@ -0,0 +1,5 @@
+---
+title: Moves merge request reviews into Core
+merge_request: 32558
+author:
+type: other
diff --git a/changelogs/unreleased/ph-approvalsFEToFoss.yml b/changelogs/unreleased/ph-approvalsFEToFoss.yml
new file mode 100644
index 00000000000..20def1c56a8
--- /dev/null
+++ b/changelogs/unreleased/ph-approvalsFEToFoss.yml
@@ -0,0 +1,5 @@
+---
+title: Show Approve button on merge requests in Core
+merge_request: 36449
+author:
+type: added
diff --git a/changelogs/unreleased/ph-codeNavigationUXImprovements.yml b/changelogs/unreleased/ph-codeNavigationUXImprovements.yml
new file mode 100644
index 00000000000..07c599dbdc9
--- /dev/null
+++ b/changelogs/unreleased/ph-codeNavigationUXImprovements.yml
@@ -0,0 +1,5 @@
+---
+title: Improved UX of the code navigation popover
+merge_request:
+author:
+type: changed
diff --git a/changelogs/unreleased/pl-alert-management-fix-multiple-issue-creation.yml b/changelogs/unreleased/pl-alert-management-fix-multiple-issue-creation.yml
new file mode 100644
index 00000000000..a4837dd7df8
--- /dev/null
+++ b/changelogs/unreleased/pl-alert-management-fix-multiple-issue-creation.yml
@@ -0,0 +1,5 @@
+---
+title: Fix linking alerts to created issues for the Generic alerts intergration
+merge_request: 33647
+author:
+type: fixed
diff --git a/changelogs/unreleased/pl-remove-feature-flag-alert-integration-dropdown.yml b/changelogs/unreleased/pl-remove-feature-flag-alert-integration-dropdown.yml
new file mode 100644
index 00000000000..2f7bfebb0ef
--- /dev/null
+++ b/changelogs/unreleased/pl-remove-feature-flag-alert-integration-dropdown.yml
@@ -0,0 +1,5 @@
+---
+title: Enable Alerts dropdown in Operations Settings
+merge_request: 36296
+author:
+type: added
diff --git a/changelogs/unreleased/pokstad1-gitaly-13-2-0-rc1.yml b/changelogs/unreleased/pokstad1-gitaly-13-2-0-rc1.yml
new file mode 100644
index 00000000000..ae51c1d1cc5
--- /dev/null
+++ b/changelogs/unreleased/pokstad1-gitaly-13-2-0-rc1.yml
@@ -0,0 +1,5 @@
+---
+title: Bump Gitaly to v13.2.0-rc1
+merge_request: 34977
+author:
+type: added
diff --git a/changelogs/unreleased/product-analytics-db-migration.yml b/changelogs/unreleased/product-analytics-db-migration.yml
new file mode 100644
index 00000000000..17bfb860ee4
--- /dev/null
+++ b/changelogs/unreleased/product-analytics-db-migration.yml
@@ -0,0 +1,5 @@
+---
+title: Add migration for experimental product analytics table
+merge_request: 35168
+author:
+type: added
diff --git a/changelogs/unreleased/project-soft-delete-setting.yml b/changelogs/unreleased/project-soft-delete-setting.yml
new file mode 100644
index 00000000000..7d245c8af45
--- /dev/null
+++ b/changelogs/unreleased/project-soft-delete-setting.yml
@@ -0,0 +1,5 @@
+---
+title: Introduces Group Level Delayed Project Removal Setting
+merge_request: 35689
+author:
+type: added
diff --git a/changelogs/unreleased/projects-api-sort-by-statistics.yml b/changelogs/unreleased/projects-api-sort-by-statistics.yml
new file mode 100644
index 00000000000..e46c4a53cc5
--- /dev/null
+++ b/changelogs/unreleased/projects-api-sort-by-statistics.yml
@@ -0,0 +1,5 @@
+---
+title: Allow advanced API projects filtering for admins
+merge_request: 32879
+author:
+type: added
diff --git a/changelogs/unreleased/propagate-ds-java-version-in-dependency-scanning.yml b/changelogs/unreleased/propagate-ds-java-version-in-dependency-scanning.yml
new file mode 100644
index 00000000000..85677505b0b
--- /dev/null
+++ b/changelogs/unreleased/propagate-ds-java-version-in-dependency-scanning.yml
@@ -0,0 +1,5 @@
+---
+title: Propagate DS_JAVA_VERSION for dependency scanning
+merge_request: 36448
+author:
+type: fixed
diff --git a/changelogs/unreleased/ps-find-remote-is-storage-scoped.yml b/changelogs/unreleased/ps-find-remote-is-storage-scoped.yml
new file mode 100644
index 00000000000..da55f846707
--- /dev/null
+++ b/changelogs/unreleased/ps-find-remote-is-storage-scoped.yml
@@ -0,0 +1,5 @@
+---
+title: FindRemoteRepository is storage scoped
+merge_request: 35962
+author:
+type: added
diff --git a/changelogs/unreleased/ps-fix-single-file-editor-long-branch.yml b/changelogs/unreleased/ps-fix-single-file-editor-long-branch.yml
new file mode 100644
index 00000000000..0618487fd74
--- /dev/null
+++ b/changelogs/unreleased/ps-fix-single-file-editor-long-branch.yml
@@ -0,0 +1,5 @@
+---
+title: Fix single file editor with long branch name
+merge_request: 36371
+author:
+type: fixed
diff --git a/changelogs/unreleased/psi-dark-mode-issue-fixes.yml b/changelogs/unreleased/psi-dark-mode-issue-fixes.yml
new file mode 100644
index 00000000000..da513844bc4
--- /dev/null
+++ b/changelogs/unreleased/psi-dark-mode-issue-fixes.yml
@@ -0,0 +1,5 @@
+---
+title: Minor UI fixes for Issue page in dark mode
+merge_request: 35395
+author:
+type: fixed
diff --git a/changelogs/unreleased/psi-dark-suggestions.yml b/changelogs/unreleased/psi-dark-suggestions.yml
new file mode 100644
index 00000000000..fd0d2f67b52
--- /dev/null
+++ b/changelogs/unreleased/psi-dark-suggestions.yml
@@ -0,0 +1,5 @@
+---
+title: Fix issue suggestion text color on dark mode
+merge_request: 34899
+author:
+type: fixed
diff --git a/changelogs/unreleased/psi-dark-theme.yml b/changelogs/unreleased/psi-dark-theme.yml
new file mode 100644
index 00000000000..21257280afb
--- /dev/null
+++ b/changelogs/unreleased/psi-dark-theme.yml
@@ -0,0 +1,5 @@
+---
+title: Add dark theme (alpha)
+merge_request: 28252
+author:
+type: added
diff --git a/changelogs/unreleased/psi-funtional-line-component.yml b/changelogs/unreleased/psi-funtional-line-component.yml
new file mode 100644
index 00000000000..16485049bcd
--- /dev/null
+++ b/changelogs/unreleased/psi-funtional-line-component.yml
@@ -0,0 +1,5 @@
+---
+title: Performance improvement for job logs
+merge_request: 35504
+author:
+type: performance
diff --git a/changelogs/unreleased/psi-iteration-pagination-sensation.yml b/changelogs/unreleased/psi-iteration-pagination-sensation.yml
new file mode 100644
index 00000000000..2123e93b85a
--- /dev/null
+++ b/changelogs/unreleased/psi-iteration-pagination-sensation.yml
@@ -0,0 +1,5 @@
+---
+title: Add pagination to iterations list
+merge_request: 37052
+author:
+type: added
diff --git a/changelogs/unreleased/psi-job-log-text.yml b/changelogs/unreleased/psi-job-log-text.yml
new file mode 100644
index 00000000000..81043bab09f
--- /dev/null
+++ b/changelogs/unreleased/psi-job-log-text.yml
@@ -0,0 +1,5 @@
+---
+title: Fix job log text color in dark mode
+merge_request: 35387
+author:
+type: fixed
diff --git a/changelogs/unreleased/rails-logger-cop-1.yml b/changelogs/unreleased/rails-logger-cop-1.yml
new file mode 100644
index 00000000000..c9b37eb9244
--- /dev/null
+++ b/changelogs/unreleased/rails-logger-cop-1.yml
@@ -0,0 +1,5 @@
+---
+title: Use applogger in project import state file
+merge_request: 32182
+author: Rajendra Kadam
+type: fixed
diff --git a/changelogs/unreleased/rails-logger-cop-10.yml b/changelogs/unreleased/rails-logger-cop-10.yml
new file mode 100644
index 00000000000..c81a504350c
--- /dev/null
+++ b/changelogs/unreleased/rails-logger-cop-10.yml
@@ -0,0 +1,5 @@
+---
+title: Use applogger in exclusive_lease_guard
+merge_request: 32194
+author: Rajendra Kadam
+type: fixed
diff --git a/changelogs/unreleased/rails-logger-cop-11.yml b/changelogs/unreleased/rails-logger-cop-11.yml
new file mode 100644
index 00000000000..fa337bf4658
--- /dev/null
+++ b/changelogs/unreleased/rails-logger-cop-11.yml
@@ -0,0 +1,5 @@
+---
+title: Use applogger in groups destroy service and label create service
+merge_request: 32195
+author: Rajendra Kadam
+type: fixed
diff --git a/changelogs/unreleased/rails-logger-cop-12.yml b/changelogs/unreleased/rails-logger-cop-12.yml
new file mode 100644
index 00000000000..c08f8d5426a
--- /dev/null
+++ b/changelogs/unreleased/rails-logger-cop-12.yml
@@ -0,0 +1,5 @@
+---
+title: Use applogger in merge_service.rb
+merge_request: 32196
+author: Rajendra Kadam
+type: fixed
diff --git a/changelogs/unreleased/rails-logger-cop-13.yml b/changelogs/unreleased/rails-logger-cop-13.yml
new file mode 100644
index 00000000000..ab0414f0ae3
--- /dev/null
+++ b/changelogs/unreleased/rails-logger-cop-13.yml
@@ -0,0 +1,5 @@
+---
+title: Use applogger in project create service and after import service
+merge_request: 32198
+author: Rajendra Kadam
+type: fixed
diff --git a/changelogs/unreleased/rails-logger-cop-14.yml b/changelogs/unreleased/rails-logger-cop-14.yml
new file mode 100644
index 00000000000..07f332d9d35
--- /dev/null
+++ b/changelogs/unreleased/rails-logger-cop-14.yml
@@ -0,0 +1,5 @@
+---
+title: Use applogger in update stats service
+merge_request: 32200
+author: Rajendra Kadam
+type: fixed
diff --git a/changelogs/unreleased/rails-logger-cop-15.yml b/changelogs/unreleased/rails-logger-cop-15.yml
new file mode 100644
index 00000000000..1c9ccafe5cb
--- /dev/null
+++ b/changelogs/unreleased/rails-logger-cop-15.yml
@@ -0,0 +1,5 @@
+---
+title: Use applogger in base attachment service
+merge_request: 32201
+author: Rajendra Kadam
+type: fixed
diff --git a/changelogs/unreleased/rails-logger-cop-16.yml b/changelogs/unreleased/rails-logger-cop-16.yml
new file mode 100644
index 00000000000..be60fdce4d0
--- /dev/null
+++ b/changelogs/unreleased/rails-logger-cop-16.yml
@@ -0,0 +1,5 @@
+---
+title: Use applogger in export service
+merge_request: 32203
+author: Rajendra Kadam
+type: fixed
diff --git a/changelogs/unreleased/rails-logger-cop-17.yml b/changelogs/unreleased/rails-logger-cop-17.yml
new file mode 100644
index 00000000000..013457961cf
--- /dev/null
+++ b/changelogs/unreleased/rails-logger-cop-17.yml
@@ -0,0 +1,5 @@
+---
+title: Use applogger in akismet service
+merge_request: 32205
+author: Rajendra Kadam
+type: fixed
diff --git a/changelogs/unreleased/rails-logger-cop-18.yml b/changelogs/unreleased/rails-logger-cop-18.yml
new file mode 100644
index 00000000000..d5ebd1b4a11
--- /dev/null
+++ b/changelogs/unreleased/rails-logger-cop-18.yml
@@ -0,0 +1,5 @@
+---
+title: Use applogger in file mover file
+merge_request: 32206
+author: Rajendra Kadam
+type: fixed
diff --git a/changelogs/unreleased/rails-logger-cop-19.yml b/changelogs/unreleased/rails-logger-cop-19.yml
new file mode 100644
index 00000000000..b56f06caf73
--- /dev/null
+++ b/changelogs/unreleased/rails-logger-cop-19.yml
@@ -0,0 +1,5 @@
+---
+title: Use applogger in commit signature worker
+merge_request: 32207
+author: Rajendra Kadam
+type: fixed
diff --git a/changelogs/unreleased/rails-logger-cop-2.yml b/changelogs/unreleased/rails-logger-cop-2.yml
new file mode 100644
index 00000000000..0685f503e44
--- /dev/null
+++ b/changelogs/unreleased/rails-logger-cop-2.yml
@@ -0,0 +1,5 @@
+---
+title: Use applogger in project.rb
+merge_request: 32183
+author: Rajendra Kadam
+type: fixed
diff --git a/changelogs/unreleased/rails-logger-cop-20.yml b/changelogs/unreleased/rails-logger-cop-20.yml
new file mode 100644
index 00000000000..1128b8022e3
--- /dev/null
+++ b/changelogs/unreleased/rails-logger-cop-20.yml
@@ -0,0 +1,5 @@
+---
+title: Use applogger in delete user worker
+merge_request: 32209
+author: Rajendra Kadam
+type: fixed
diff --git a/changelogs/unreleased/rails-logger-cop-21.yml b/changelogs/unreleased/rails-logger-cop-21.yml
new file mode 100644
index 00000000000..099142ff482
--- /dev/null
+++ b/changelogs/unreleased/rails-logger-cop-21.yml
@@ -0,0 +1,5 @@
+---
+title: Use applogger in email receiver worker
+merge_request: 32211
+author: Rajendra Kadam
+type: fixed
diff --git a/changelogs/unreleased/rails-logger-cop-22.yml b/changelogs/unreleased/rails-logger-cop-22.yml
new file mode 100644
index 00000000000..df22074b16a
--- /dev/null
+++ b/changelogs/unreleased/rails-logger-cop-22.yml
@@ -0,0 +1,5 @@
+---
+title: Use applogger in artifact worker
+merge_request: 32212
+author: Rajendra Kadam
+type: fixed
diff --git a/changelogs/unreleased/rails-logger-cop-23.yml b/changelogs/unreleased/rails-logger-cop-23.yml
new file mode 100644
index 00000000000..4e7513570e9
--- /dev/null
+++ b/changelogs/unreleased/rails-logger-cop-23.yml
@@ -0,0 +1,5 @@
+---
+title: Use applogger in new note worker
+merge_request: 32213
+author: Rajendra Kadam
+type: fixed
diff --git a/changelogs/unreleased/rails-logger-cop-3.yml b/changelogs/unreleased/rails-logger-cop-3.yml
new file mode 100644
index 00000000000..b7de674cdb6
--- /dev/null
+++ b/changelogs/unreleased/rails-logger-cop-3.yml
@@ -0,0 +1,5 @@
+---
+title: Use applogger in chat_team.rb
+merge_request: 32184
+author: Rajendra Kadam
+type: fixed
diff --git a/changelogs/unreleased/rails-logger-cop-4.yml b/changelogs/unreleased/rails-logger-cop-4.yml
new file mode 100644
index 00000000000..a7f76711de7
--- /dev/null
+++ b/changelogs/unreleased/rails-logger-cop-4.yml
@@ -0,0 +1,5 @@
+---
+title: Use applogger in repository model
+merge_request: 32185
+author: Rajendra Kadam
+type: fixed
diff --git a/changelogs/unreleased/rails-logger-cop-5.yml b/changelogs/unreleased/rails-logger-cop-5.yml
new file mode 100644
index 00000000000..4e758bde275
--- /dev/null
+++ b/changelogs/unreleased/rails-logger-cop-5.yml
@@ -0,0 +1,5 @@
+---
+title: Use applogger in build and ssh host key
+merge_request: 32187
+author: Rajendra Kadam
+type: fixed
diff --git a/changelogs/unreleased/rails-logger-cop-6.yml b/changelogs/unreleased/rails-logger-cop-6.yml
new file mode 100644
index 00000000000..a5fea225b08
--- /dev/null
+++ b/changelogs/unreleased/rails-logger-cop-6.yml
@@ -0,0 +1,5 @@
+---
+title: Use applogger in cache attrs and highest role ruby files
+merge_request: 32189
+author: Rajendra Kadam
+type: fixed
diff --git a/changelogs/unreleased/rails-logger-cop-7.yml b/changelogs/unreleased/rails-logger-cop-7.yml
new file mode 100644
index 00000000000..b8482ccef54
--- /dev/null
+++ b/changelogs/unreleased/rails-logger-cop-7.yml
@@ -0,0 +1,5 @@
+---
+title: Use applogger in legacy project and namespace
+merge_request: 32190
+author: Rajendra Kadam
+type: fixed
diff --git a/changelogs/unreleased/rails-logger-cop-8.yml b/changelogs/unreleased/rails-logger-cop-8.yml
new file mode 100644
index 00000000000..4765c087bb7
--- /dev/null
+++ b/changelogs/unreleased/rails-logger-cop-8.yml
@@ -0,0 +1,5 @@
+---
+title: Use applogger in base.rb
+merge_request: 32191
+author: Rajendra Kadam
+type: fixed
diff --git a/changelogs/unreleased/rails-logger-cop-9.yml b/changelogs/unreleased/rails-logger-cop-9.yml
new file mode 100644
index 00000000000..8e740f6f1bd
--- /dev/null
+++ b/changelogs/unreleased/rails-logger-cop-9.yml
@@ -0,0 +1,5 @@
+---
+title: Use applogger in usage ping and webhook service
+merge_request: 32192
+author: Rajendra Kadam
+type: fixed
diff --git a/changelogs/unreleased/rails-save-bang-1.yml b/changelogs/unreleased/rails-save-bang-1.yml
new file mode 100644
index 00000000000..c7d20372b9a
--- /dev/null
+++ b/changelogs/unreleased/rails-save-bang-1.yml
@@ -0,0 +1,5 @@
+---
+title: Refactor issues controller spec to fix SaveBang Cop
+merge_request: 36582
+author: Rajendra Kadam
+type: fixed
diff --git a/changelogs/unreleased/rails-save-bang-2.yml b/changelogs/unreleased/rails-save-bang-2.yml
new file mode 100644
index 00000000000..99ccdf1ea70
--- /dev/null
+++ b/changelogs/unreleased/rails-save-bang-2.yml
@@ -0,0 +1,5 @@
+---
+title: Refactor group controllers specs to fix SaveBang Cop
+merge_request: 36853
+author: Rajendra Kadam
+type: fixed
diff --git a/changelogs/unreleased/rails-save-bang-3.yml b/changelogs/unreleased/rails-save-bang-3.yml
new file mode 100644
index 00000000000..70760d6c038
--- /dev/null
+++ b/changelogs/unreleased/rails-save-bang-3.yml
@@ -0,0 +1,5 @@
+---
+title: Refactor projects controllers specs to fix SaveBang Cop
+merge_request: 36920
+author: Rajendra Kadam
+type: fixed
diff --git a/changelogs/unreleased/rc-add_dashboard_timezone.yml b/changelogs/unreleased/rc-add_dashboard_timezone.yml
new file mode 100644
index 00000000000..190d3faadec
--- /dev/null
+++ b/changelogs/unreleased/rc-add_dashboard_timezone.yml
@@ -0,0 +1,5 @@
+---
+title: Add column dashboard_timezone to project_metrics_setting
+merge_request: 33120
+author:
+type: added
diff --git a/changelogs/unreleased/rc-add_new_dashboard_route.yml b/changelogs/unreleased/rc-add_new_dashboard_route.yml
new file mode 100644
index 00000000000..b23ac6488df
--- /dev/null
+++ b/changelogs/unreleased/rc-add_new_dashboard_route.yml
@@ -0,0 +1,5 @@
+---
+title: Add new path to access project metrics dashboard
+merge_request: 33905
+author:
+type: added
diff --git a/changelogs/unreleased/rc-enforce_unique_metrics_id_across_project.yml b/changelogs/unreleased/rc-enforce_unique_metrics_id_across_project.yml
new file mode 100644
index 00000000000..4ad2f1722f4
--- /dev/null
+++ b/changelogs/unreleased/rc-enforce_unique_metrics_id_across_project.yml
@@ -0,0 +1,5 @@
+---
+title: Enforce prometheus metric uniqueness across project scope
+merge_request: 35566
+author:
+type: fixed
diff --git a/changelogs/unreleased/rc-escape_dashboard_paths.yml b/changelogs/unreleased/rc-escape_dashboard_paths.yml
new file mode 100644
index 00000000000..1751c592f4b
--- /dev/null
+++ b/changelogs/unreleased/rc-escape_dashboard_paths.yml
@@ -0,0 +1,5 @@
+---
+title: Allow special characters in dashboard path
+merge_request: 32714
+author:
+type: fixed
diff --git a/changelogs/unreleased/rc-remove_metric_identifier_index.yml b/changelogs/unreleased/rc-remove_metric_identifier_index.yml
new file mode 100644
index 00000000000..3196313e731
--- /dev/null
+++ b/changelogs/unreleased/rc-remove_metric_identifier_index.yml
@@ -0,0 +1,5 @@
+---
+title: Change PrometheusMetrics identifier index
+merge_request: 35912
+author:
+type: fixed
diff --git a/changelogs/unreleased/rearchitect-fixed-pipelines-notification-v2.yml b/changelogs/unreleased/rearchitect-fixed-pipelines-notification-v2.yml
new file mode 100644
index 00000000000..6f65d844a7a
--- /dev/null
+++ b/changelogs/unreleased/rearchitect-fixed-pipelines-notification-v2.yml
@@ -0,0 +1,5 @@
+---
+title: Make Fixed Email Notification Generally Available
+merge_request: 28338
+author: jacopo-beschi
+type: added
diff --git a/changelogs/unreleased/reduce-repo-size.yml b/changelogs/unreleased/reduce-repo-size.yml
new file mode 100644
index 00000000000..8483cc0a705
--- /dev/null
+++ b/changelogs/unreleased/reduce-repo-size.yml
@@ -0,0 +1,5 @@
+---
+title: Add support for `git filter-repo` to repository cleanup
+merge_request: 33576
+author:
+type: added
diff --git a/changelogs/unreleased/reduce_pipeline_status_gitaly_call.yml b/changelogs/unreleased/reduce_pipeline_status_gitaly_call.yml
new file mode 100644
index 00000000000..6534546dee6
--- /dev/null
+++ b/changelogs/unreleased/reduce_pipeline_status_gitaly_call.yml
@@ -0,0 +1,5 @@
+---
+title: Remove need to call commit (gitaly call) in ProjectPipelineStatus
+merge_request: 33712
+author:
+type: performance
diff --git a/changelogs/unreleased/refactor-stuck-imports-jobs-worker.yml b/changelogs/unreleased/refactor-stuck-imports-jobs-worker.yml
new file mode 100644
index 00000000000..72508bd724c
--- /dev/null
+++ b/changelogs/unreleased/refactor-stuck-imports-jobs-worker.yml
@@ -0,0 +1,5 @@
+---
+title: Process stuck jira import jobs
+merge_request: 32643
+author:
+type: added
diff --git a/changelogs/unreleased/remove-a11y-widget-ff.yml b/changelogs/unreleased/remove-a11y-widget-ff.yml
new file mode 100644
index 00000000000..0f99d7a1e93
--- /dev/null
+++ b/changelogs/unreleased/remove-a11y-widget-ff.yml
@@ -0,0 +1,5 @@
+---
+title: Add accessibility report MR widget
+merge_request: 32902
+author:
+type: added
diff --git a/changelogs/unreleased/remove-dead-elasticsearch-indexing-code.yml b/changelogs/unreleased/remove-dead-elasticsearch-indexing-code.yml
new file mode 100644
index 00000000000..56d40dbc9ce
--- /dev/null
+++ b/changelogs/unreleased/remove-dead-elasticsearch-indexing-code.yml
@@ -0,0 +1,5 @@
+---
+title: Remove dead Elasticsearch indexing code
+merge_request: 35936
+author:
+type: other
diff --git a/changelogs/unreleased/remove-fa-heart.yml b/changelogs/unreleased/remove-fa-heart.yml
new file mode 100644
index 00000000000..6d577851115
--- /dev/null
+++ b/changelogs/unreleased/remove-fa-heart.yml
@@ -0,0 +1,5 @@
+---
+title: Update heart icon from FontAwesome to GitLab SVG
+merge_request: 34777
+author:
+type: other
diff --git a/changelogs/unreleased/remove-ff-in-count-users-by-group.yml b/changelogs/unreleased/remove-ff-in-count-users-by-group.yml
new file mode 100644
index 00000000000..0a1973fcf25
--- /dev/null
+++ b/changelogs/unreleased/remove-ff-in-count-users-by-group.yml
@@ -0,0 +1,5 @@
+---
+title: Remove optimized_count_users_by_group_id feature flag
+merge_request: 36953
+author:
+type: performance
diff --git a/changelogs/unreleased/remove-ff-job-log-json.yml b/changelogs/unreleased/remove-ff-job-log-json.yml
new file mode 100644
index 00000000000..cedf36273a5
--- /dev/null
+++ b/changelogs/unreleased/remove-ff-job-log-json.yml
@@ -0,0 +1,5 @@
+---
+title: Remove legacy job log rendering
+merge_request: 34538
+author:
+type: other
diff --git a/changelogs/unreleased/remove-group_milestone_descendants.yml b/changelogs/unreleased/remove-group_milestone_descendants.yml
new file mode 100644
index 00000000000..770b4ec12de
--- /dev/null
+++ b/changelogs/unreleased/remove-group_milestone_descendants.yml
@@ -0,0 +1,5 @@
+---
+title: Include project and subgroup milestones on Roadmap page
+merge_request: 35973
+author:
+type: added
diff --git a/changelogs/unreleased/remove-partial-clone-feature-flag.yml b/changelogs/unreleased/remove-partial-clone-feature-flag.yml
new file mode 100644
index 00000000000..2bbcaf26729
--- /dev/null
+++ b/changelogs/unreleased/remove-partial-clone-feature-flag.yml
@@ -0,0 +1,5 @@
+---
+title: Remove partial clone feature flag
+merge_request: 34703
+author:
+type: added
diff --git a/changelogs/unreleased/remove-prometheus-iap-feature-flag.yml b/changelogs/unreleased/remove-prometheus-iap-feature-flag.yml
new file mode 100644
index 00000000000..2f00a924b64
--- /dev/null
+++ b/changelogs/unreleased/remove-prometheus-iap-feature-flag.yml
@@ -0,0 +1,5 @@
+---
+title: Prometheus instances behind Google IAP can now be accessed via manual configurations
+merge_request: 36856
+author:
+type: added
diff --git a/changelogs/unreleased/remove-redundant-modsecurity-indexes.yml b/changelogs/unreleased/remove-redundant-modsecurity-indexes.yml
new file mode 100644
index 00000000000..41a1759281d
--- /dev/null
+++ b/changelogs/unreleased/remove-redundant-modsecurity-indexes.yml
@@ -0,0 +1,5 @@
+---
+title: Remove unused WAF indexes from CI variables
+merge_request: 30021
+author:
+type: other
diff --git a/changelogs/unreleased/remove-some-tabs-from-pipelines-list.yml b/changelogs/unreleased/remove-some-tabs-from-pipelines-list.yml
new file mode 100644
index 00000000000..cab2a15dd3c
--- /dev/null
+++ b/changelogs/unreleased/remove-some-tabs-from-pipelines-list.yml
@@ -0,0 +1,5 @@
+---
+title: Remove pending and running tabs from pipelines list and remove count from finished tab
+merge_request: 35062
+author:
+type: changed
diff --git a/changelogs/unreleased/remove_hyperlink_from_close_reopen_button.yml b/changelogs/unreleased/remove_hyperlink_from_close_reopen_button.yml
new file mode 100644
index 00000000000..c0d68102041
--- /dev/null
+++ b/changelogs/unreleased/remove_hyperlink_from_close_reopen_button.yml
@@ -0,0 +1,5 @@
+---
+title: Remove broken hyperlink from close and reopen button
+merge_request: 22220
+author: Lee Tickett
+type: fixed
diff --git a/changelogs/unreleased/remove_old_csrf_generation_monkey_patch.yml b/changelogs/unreleased/remove_old_csrf_generation_monkey_patch.yml
new file mode 100644
index 00000000000..3a751b5bf4c
--- /dev/null
+++ b/changelogs/unreleased/remove_old_csrf_generation_monkey_patch.yml
@@ -0,0 +1,5 @@
+---
+title: Removes monkey patch to generate 6.0.3 style token
+merge_request: 35104
+author:
+type: other
diff --git a/changelogs/unreleased/remove_scoped_approval_rules_feature_flag.yml b/changelogs/unreleased/remove_scoped_approval_rules_feature_flag.yml
new file mode 100644
index 00000000000..f577053d074
--- /dev/null
+++ b/changelogs/unreleased/remove_scoped_approval_rules_feature_flag.yml
@@ -0,0 +1,5 @@
+---
+title: Remove `scoped_approval_rules` feature flag
+merge_request: 28864
+author: Lee Tickett
+type: added
diff --git a/changelogs/unreleased/render-job-details-on-downstream-pipelines.yml b/changelogs/unreleased/render-job-details-on-downstream-pipelines.yml
new file mode 100644
index 00000000000..eb939b76873
--- /dev/null
+++ b/changelogs/unreleased/render-job-details-on-downstream-pipelines.yml
@@ -0,0 +1,5 @@
+---
+title: Render source job info in TriggeredPipelineEntity
+merge_request: 35232
+author:
+type: added
diff --git a/changelogs/unreleased/replace-slot-for-vue-3-migration.yml b/changelogs/unreleased/replace-slot-for-vue-3-migration.yml
new file mode 100644
index 00000000000..c2f1e2a4697
--- /dev/null
+++ b/changelogs/unreleased/replace-slot-for-vue-3-migration.yml
@@ -0,0 +1,5 @@
+---
+title: Replace slot syntax for Vue 3 migration
+merge_request: 31987
+author: gaslan
+type: other
diff --git a/changelogs/unreleased/replicate_repository_stop_creating_destination.yml b/changelogs/unreleased/replicate_repository_stop_creating_destination.yml
new file mode 100644
index 00000000000..5f8925ed79c
--- /dev/null
+++ b/changelogs/unreleased/replicate_repository_stop_creating_destination.yml
@@ -0,0 +1,5 @@
+---
+title: Use snapshot transfers for repository shard moves when possible
+merge_request: 34113
+author:
+type: performance
diff --git a/changelogs/unreleased/revert-bc8546a9.yml b/changelogs/unreleased/revert-bc8546a9.yml
new file mode 100644
index 00000000000..45513d899a1
--- /dev/null
+++ b/changelogs/unreleased/revert-bc8546a9.yml
@@ -0,0 +1,5 @@
+---
+title: Restore the search autocomplete for groups/project/other
+merge_request: 35983
+author:
+type: other
diff --git a/changelogs/unreleased/reword-addMultipleToDiscussionWarning.yml b/changelogs/unreleased/reword-addMultipleToDiscussionWarning.yml
new file mode 100644
index 00000000000..47e984576c5
--- /dev/null
+++ b/changelogs/unreleased/reword-addMultipleToDiscussionWarning.yml
@@ -0,0 +1,5 @@
+---
+title: Update wording of addMultipleToDiscussionWarning
+merge_request: 34088
+author:
+type: fixed
diff --git a/changelogs/unreleased/rf-brakeman-to-core.yml b/changelogs/unreleased/rf-brakeman-to-core.yml
new file mode 100644
index 00000000000..3f2d4536815
--- /dev/null
+++ b/changelogs/unreleased/rf-brakeman-to-core.yml
@@ -0,0 +1,5 @@
+---
+title: Bring SAST to Core - brakeman
+merge_request: 34217
+author:
+type: added
diff --git a/changelogs/unreleased/rm-25228-pre-receive-error.yml b/changelogs/unreleased/rm-25228-pre-receive-error.yml
new file mode 100644
index 00000000000..da34b3edef5
--- /dev/null
+++ b/changelogs/unreleased/rm-25228-pre-receive-error.yml
@@ -0,0 +1,5 @@
+---
+title: Propagate error on FF pre-receive failure
+merge_request: 35633
+author:
+type: fixed
diff --git a/changelogs/unreleased/rmv-worker-code.yml b/changelogs/unreleased/rmv-worker-code.yml
new file mode 100644
index 00000000000..316c099e570
--- /dev/null
+++ b/changelogs/unreleased/rmv-worker-code.yml
@@ -0,0 +1,5 @@
+---
+title: Remove the unused worker code and its queue
+merge_request: 32595
+author: Ravishankar
+type: deprecated
diff --git a/changelogs/unreleased/rp-use-gitlab-yaml-loader-blob-viewer.yml b/changelogs/unreleased/rp-use-gitlab-yaml-loader-blob-viewer.yml
new file mode 100644
index 00000000000..e9ecbc0cd66
--- /dev/null
+++ b/changelogs/unreleased/rp-use-gitlab-yaml-loader-blob-viewer.yml
@@ -0,0 +1,5 @@
+---
+title: Display error for YAML files that are too large
+merge_request: 34199
+author:
+type: changed
diff --git a/changelogs/unreleased/rp-use-gitlab-yaml-loader-dashboard-stages.yml b/changelogs/unreleased/rp-use-gitlab-yaml-loader-dashboard-stages.yml
new file mode 100644
index 00000000000..c6e93dc21e0
--- /dev/null
+++ b/changelogs/unreleased/rp-use-gitlab-yaml-loader-dashboard-stages.yml
@@ -0,0 +1,5 @@
+---
+title: Display error if metrics dashboard YAML is too large
+merge_request: 34834
+author:
+type: changed
diff --git a/changelogs/unreleased/rp-use-stable-sort-in-sorter.yml b/changelogs/unreleased/rp-use-stable-sort-in-sorter.yml
new file mode 100644
index 00000000000..54b0e21950a
--- /dev/null
+++ b/changelogs/unreleased/rp-use-stable-sort-in-sorter.yml
@@ -0,0 +1,5 @@
+---
+title: Sort metrics dashboard panels and groups using a stable sort
+merge_request: 36278
+author:
+type: fixed
diff --git a/changelogs/unreleased/rspec-rails-fast-failure-template.yml b/changelogs/unreleased/rspec-rails-fast-failure-template.yml
new file mode 100644
index 00000000000..c466c68075a
--- /dev/null
+++ b/changelogs/unreleased/rspec-rails-fast-failure-template.yml
@@ -0,0 +1,5 @@
+---
+title: Add Verify/FailFast CI template
+merge_request: 31812
+author:
+type: added
diff --git a/changelogs/unreleased/run-unassign-issuables-worker-when-out-of-transaction.yml b/changelogs/unreleased/run-unassign-issuables-worker-when-out-of-transaction.yml
new file mode 100644
index 00000000000..c858df2f15d
--- /dev/null
+++ b/changelogs/unreleased/run-unassign-issuables-worker-when-out-of-transaction.yml
@@ -0,0 +1,5 @@
+---
+title: Ensure to run unassign issuables worker when not in a transaction
+merge_request: 36680
+author:
+type: fixed
diff --git a/changelogs/unreleased/sast-ci-config.yml b/changelogs/unreleased/sast-ci-config.yml
new file mode 100644
index 00000000000..28b4b651ca3
--- /dev/null
+++ b/changelogs/unreleased/sast-ci-config.yml
@@ -0,0 +1,5 @@
+---
+title: Implement GraphQL query to generate JSON for SAST config UI
+merge_request: 35397
+author:
+type: added
diff --git a/changelogs/unreleased/sav-1566-pat-for-projects-db-changes.yml b/changelogs/unreleased/sav-1566-pat-for-projects-db-changes.yml
new file mode 100644
index 00000000000..50a32041395
--- /dev/null
+++ b/changelogs/unreleased/sav-1566-pat-for-projects-db-changes.yml
@@ -0,0 +1,5 @@
+---
+title: Add ProjectAccessToken table
+merge_request: 33272
+author:
+type: added
diff --git a/changelogs/unreleased/say-no-to-hacks.yml b/changelogs/unreleased/say-no-to-hacks.yml
new file mode 100644
index 00000000000..7d9c342121f
--- /dev/null
+++ b/changelogs/unreleased/say-no-to-hacks.yml
@@ -0,0 +1,5 @@
+---
+title: Removes fixes that broke the pipeline table
+merge_request: 36803
+author:
+type: fixed
diff --git a/changelogs/unreleased/schedule_storage_move_api.yml b/changelogs/unreleased/schedule_storage_move_api.yml
new file mode 100644
index 00000000000..8c205aade84
--- /dev/null
+++ b/changelogs/unreleased/schedule_storage_move_api.yml
@@ -0,0 +1,5 @@
+---
+title: Add API to schedule project repository storage moves
+merge_request: 34119
+author:
+type: added
diff --git a/changelogs/unreleased/secret-detection-remove-extra-job.yml b/changelogs/unreleased/secret-detection-remove-extra-job.yml
new file mode 100644
index 00000000000..66d05c9f4a6
--- /dev/null
+++ b/changelogs/unreleased/secret-detection-remove-extra-job.yml
@@ -0,0 +1,5 @@
+---
+title: Remove extra Secret-Detection job on merge requests
+merge_request: 36884
+author:
+type: fixed
diff --git a/changelogs/unreleased/services-usage-1.yml b/changelogs/unreleased/services-usage-1.yml
new file mode 100644
index 00000000000..d202273ca8c
--- /dev/null
+++ b/changelogs/unreleased/services-usage-1.yml
@@ -0,0 +1,5 @@
+---
+title: Record audit event when an admin creates a new SSH Key for a user via the API
+merge_request: 33859
+author: Rajendra Kadam
+type: fixed
diff --git a/changelogs/unreleased/services-usage-2.yml b/changelogs/unreleased/services-usage-2.yml
new file mode 100644
index 00000000000..8cc4e96d520
--- /dev/null
+++ b/changelogs/unreleased/services-usage-2.yml
@@ -0,0 +1,5 @@
+---
+title: Use Keys::DestroyService for deleting an SSH key when an admin deletes a key via the API
+merge_request: 34535
+author: Rajendra Kadam
+type: fixed
diff --git a/changelogs/unreleased/services-usage-3.yml b/changelogs/unreleased/services-usage-3.yml
new file mode 100644
index 00000000000..3084e61cf0f
--- /dev/null
+++ b/changelogs/unreleased/services-usage-3.yml
@@ -0,0 +1,5 @@
+---
+title: Record audit event when a user creates a new SSH Key for themselves via the API
+merge_request: 34645
+author: Rajendra Kadam
+type: fixed
diff --git a/changelogs/unreleased/services-usage-4.yml b/changelogs/unreleased/services-usage-4.yml
new file mode 100644
index 00000000000..cd3e54a52be
--- /dev/null
+++ b/changelogs/unreleased/services-usage-4.yml
@@ -0,0 +1,5 @@
+---
+title: Use Keys::DestroyService for deleting an SSH key when a user deletes a key via the API
+merge_request: 34718
+author: Rajendra Kadam
+type: fixed
diff --git a/changelogs/unreleased/services-usage-5.yml b/changelogs/unreleased/services-usage-5.yml
new file mode 100644
index 00000000000..04657cf147d
--- /dev/null
+++ b/changelogs/unreleased/services-usage-5.yml
@@ -0,0 +1,5 @@
+---
+title: Use GpgKeys::CreateService when an admin creates a new GPG key for a user
+merge_request: 34737
+author: Rajendra Kadam
+type: fixed
diff --git a/changelogs/unreleased/services-usage-6.yml b/changelogs/unreleased/services-usage-6.yml
new file mode 100644
index 00000000000..14e4384ad94
--- /dev/null
+++ b/changelogs/unreleased/services-usage-6.yml
@@ -0,0 +1,5 @@
+---
+title: Use GpgKeys::CreateService when a user creates GPG keys for themselves via the API
+merge_request: 34817
+author: Rajendra Kadam
+type: fixed
diff --git a/changelogs/unreleased/services-usage-7.yml b/changelogs/unreleased/services-usage-7.yml
new file mode 100644
index 00000000000..ca3900f36a3
--- /dev/null
+++ b/changelogs/unreleased/services-usage-7.yml
@@ -0,0 +1,5 @@
+---
+title: Add DestroyService for GPG keys and use for deleting GPG keys via API
+merge_request: 34935
+author: Rajendra Kadam
+type: fixed
diff --git a/changelogs/unreleased/services-usage-8.yml b/changelogs/unreleased/services-usage-8.yml
new file mode 100644
index 00000000000..fb3e3cfa709
--- /dev/null
+++ b/changelogs/unreleased/services-usage-8.yml
@@ -0,0 +1,5 @@
+---
+title: Use GpgKeys::DestroyService when a user deletes GPG keys for themselves via the API
+merge_request: 35033
+author: Rajendra Kadam
+type: fixed
diff --git a/changelogs/unreleased/sh-add-partial-index-locked-merge-requests.yml b/changelogs/unreleased/sh-add-partial-index-locked-merge-requests.yml
new file mode 100644
index 00000000000..bfeadcfded5
--- /dev/null
+++ b/changelogs/unreleased/sh-add-partial-index-locked-merge-requests.yml
@@ -0,0 +1,5 @@
+---
+title: Add partial index on locked merge requets
+merge_request: 34127
+author:
+type: performance
diff --git a/changelogs/unreleased/sh-avoid-extra-route-reload.yml b/changelogs/unreleased/sh-avoid-extra-route-reload.yml
new file mode 100644
index 00000000000..7c363ac92d8
--- /dev/null
+++ b/changelogs/unreleased/sh-avoid-extra-route-reload.yml
@@ -0,0 +1,5 @@
+---
+title: Speed up boot time in production
+merge_request: 33929
+author:
+type: performance
diff --git a/changelogs/unreleased/sh-bump-gitaly-rc2.yml b/changelogs/unreleased/sh-bump-gitaly-rc2.yml
new file mode 100644
index 00000000000..da3ed9a10aa
--- /dev/null
+++ b/changelogs/unreleased/sh-bump-gitaly-rc2.yml
@@ -0,0 +1,5 @@
+---
+title: Upgrade Gitaly to 13.2.0-rc2
+merge_request: 35345
+author:
+type: other
diff --git a/changelogs/unreleased/sh-consolidate-object-storage-config.yml b/changelogs/unreleased/sh-consolidate-object-storage-config.yml
new file mode 100644
index 00000000000..7bb7b624d7f
--- /dev/null
+++ b/changelogs/unreleased/sh-consolidate-object-storage-config.yml
@@ -0,0 +1,5 @@
+---
+title: Consolidate object storage config in one place
+merge_request: 34460
+author:
+type: changed
diff --git a/changelogs/unreleased/sh-disable-gitconfig-write-on-imports-and-forks.yml b/changelogs/unreleased/sh-disable-gitconfig-write-on-imports-and-forks.yml
new file mode 100644
index 00000000000..f376b42a12a
--- /dev/null
+++ b/changelogs/unreleased/sh-disable-gitconfig-write-on-imports-and-forks.yml
@@ -0,0 +1,5 @@
+---
+title: Defer updating .git/config for imported projects
+merge_request: 35308
+author:
+type: fixed
diff --git a/changelogs/unreleased/sh-enable-workhorse-s3-client-consolidated.yml b/changelogs/unreleased/sh-enable-workhorse-s3-client-consolidated.yml
new file mode 100644
index 00000000000..3289469af5f
--- /dev/null
+++ b/changelogs/unreleased/sh-enable-workhorse-s3-client-consolidated.yml
@@ -0,0 +1,5 @@
+---
+title: Enable S3 Workhorse client if consolidated object settings used
+merge_request: 35480
+author:
+type: added
diff --git a/changelogs/unreleased/sh-error-tracking-query-canceled.yml b/changelogs/unreleased/sh-error-tracking-query-canceled.yml
new file mode 100644
index 00000000000..82751283983
--- /dev/null
+++ b/changelogs/unreleased/sh-error-tracking-query-canceled.yml
@@ -0,0 +1,5 @@
+---
+title: Avoid grouping statement timeouts in Sentry
+merge_request: 35479
+author:
+type: other
diff --git a/changelogs/unreleased/sh-extend-remember-me-token.yml b/changelogs/unreleased/sh-extend-remember-me-token.yml
new file mode 100644
index 00000000000..ddee3f8be7e
--- /dev/null
+++ b/changelogs/unreleased/sh-extend-remember-me-token.yml
@@ -0,0 +1,5 @@
+---
+title: Extend "Remember me" token after each login
+merge_request: 32730
+author:
+type: other
diff --git a/changelogs/unreleased/sh-fix-any-approvals-with-project-rules.yml b/changelogs/unreleased/sh-fix-any-approvals-with-project-rules.yml
new file mode 100644
index 00000000000..8d21283bd2b
--- /dev/null
+++ b/changelogs/unreleased/sh-fix-any-approvals-with-project-rules.yml
@@ -0,0 +1,5 @@
+---
+title: Fix approval rule type when project rule has users/groups
+merge_request: 34026
+author:
+type: fixed
diff --git a/changelogs/unreleased/sh-fix-api-error-handling.yml b/changelogs/unreleased/sh-fix-api-error-handling.yml
new file mode 100644
index 00000000000..91e302a33dd
--- /dev/null
+++ b/changelogs/unreleased/sh-fix-api-error-handling.yml
@@ -0,0 +1,5 @@
+---
+title: Fix 500 errors with invalid access tokens
+merge_request: 35895
+author:
+type: fixed
diff --git a/changelogs/unreleased/sh-fix-auto-merge-after-resolve-discussions.yml b/changelogs/unreleased/sh-fix-auto-merge-after-resolve-discussions.yml
new file mode 100644
index 00000000000..c4aa8d22ca3
--- /dev/null
+++ b/changelogs/unreleased/sh-fix-auto-merge-after-resolve-discussions.yml
@@ -0,0 +1,5 @@
+---
+title: Fix auto-merge not running after discussions resolved
+merge_request: 33371
+author:
+type: fixed
diff --git a/changelogs/unreleased/sh-fix-delete-blob-failure.yml b/changelogs/unreleased/sh-fix-delete-blob-failure.yml
new file mode 100644
index 00000000000..ec3a48a09f2
--- /dev/null
+++ b/changelogs/unreleased/sh-fix-delete-blob-failure.yml
@@ -0,0 +1,5 @@
+---
+title: Fix 500 error in BlobController#delete
+merge_request: 34367
+author:
+type: fixed
diff --git a/changelogs/unreleased/sh-fix-gitaly-blob-service-durations.yml b/changelogs/unreleased/sh-fix-gitaly-blob-service-durations.yml
new file mode 100644
index 00000000000..4c844ccb6ef
--- /dev/null
+++ b/changelogs/unreleased/sh-fix-gitaly-blob-service-durations.yml
@@ -0,0 +1,5 @@
+---
+title: Fix Gitaly duration timings of BlobService RPCs
+merge_request: 34906
+author:
+type: other
diff --git a/changelogs/unreleased/sh-fix-gitaly-bug-2857-for-dirs.yml b/changelogs/unreleased/sh-fix-gitaly-bug-2857-for-dirs.yml
new file mode 100644
index 00000000000..dc09f0f3b53
--- /dev/null
+++ b/changelogs/unreleased/sh-fix-gitaly-bug-2857-for-dirs.yml
@@ -0,0 +1,5 @@
+---
+title: Fix directory and last commit not loading for some filenames
+merge_request: 34985
+author:
+type: fixed
diff --git a/changelogs/unreleased/sh-fix-gitaly-bug-2857.yml b/changelogs/unreleased/sh-fix-gitaly-bug-2857.yml
new file mode 100644
index 00000000000..111b8d42da1
--- /dev/null
+++ b/changelogs/unreleased/sh-fix-gitaly-bug-2857.yml
@@ -0,0 +1,5 @@
+---
+title: Fix 500 errors with filenames that contain glob characters
+merge_request: 34864
+author:
+type: fixed
diff --git a/changelogs/unreleased/sh-fix-gitaly-commit-service-durations.yml b/changelogs/unreleased/sh-fix-gitaly-commit-service-durations.yml
new file mode 100644
index 00000000000..3d1bb3b2b2f
--- /dev/null
+++ b/changelogs/unreleased/sh-fix-gitaly-commit-service-durations.yml
@@ -0,0 +1,5 @@
+---
+title: Fix Gitaly duration timings for other CommitService RPCs
+merge_request: 34933
+author:
+type: other
diff --git a/changelogs/unreleased/sh-fix-gitaly-conflicts-service-duration.yml b/changelogs/unreleased/sh-fix-gitaly-conflicts-service-duration.yml
new file mode 100644
index 00000000000..0e9e9945052
--- /dev/null
+++ b/changelogs/unreleased/sh-fix-gitaly-conflicts-service-duration.yml
@@ -0,0 +1,5 @@
+---
+title: Fix Gitaly duration timings for conflicts and search RPCs
+merge_request: 34909
+author:
+type: other
diff --git a/changelogs/unreleased/sh-fix-gitaly-ref-service-durations.yml b/changelogs/unreleased/sh-fix-gitaly-ref-service-durations.yml
new file mode 100644
index 00000000000..17aa1e539eb
--- /dev/null
+++ b/changelogs/unreleased/sh-fix-gitaly-ref-service-durations.yml
@@ -0,0 +1,5 @@
+---
+title: Fix Gitaly duration tracking of RefService RPCs
+merge_request: 34904
+author:
+type: other
diff --git a/changelogs/unreleased/sh-fix-issue-219991.yml b/changelogs/unreleased/sh-fix-issue-219991.yml
new file mode 100644
index 00000000000..f09cb34b761
--- /dev/null
+++ b/changelogs/unreleased/sh-fix-issue-219991.yml
@@ -0,0 +1,5 @@
+---
+title: Fix force_remove_source_branch not working in API
+merge_request: 33804
+author:
+type: fixed
diff --git a/changelogs/unreleased/sh-handle-distant-pat-expiry.yml b/changelogs/unreleased/sh-handle-distant-pat-expiry.yml
new file mode 100644
index 00000000000..fc89ae67492
--- /dev/null
+++ b/changelogs/unreleased/sh-handle-distant-pat-expiry.yml
@@ -0,0 +1,5 @@
+---
+title: Avoid 500 errors with long expiration dates in tokens
+merge_request: 36657
+author:
+type: fixed
diff --git a/changelogs/unreleased/sh-loosen-aws-tokens.yml b/changelogs/unreleased/sh-loosen-aws-tokens.yml
new file mode 100644
index 00000000000..846bb634cc8
--- /dev/null
+++ b/changelogs/unreleased/sh-loosen-aws-tokens.yml
@@ -0,0 +1,5 @@
+---
+title: Remove CI/CD variable validations on AWS keys
+merge_request: 36679
+author:
+type: fixed
diff --git a/changelogs/unreleased/sh-memoize-project-license-name.yml b/changelogs/unreleased/sh-memoize-project-license-name.yml
new file mode 100644
index 00000000000..ad40978a7a2
--- /dev/null
+++ b/changelogs/unreleased/sh-memoize-project-license-name.yml
@@ -0,0 +1,5 @@
+---
+title: Only load project license if needed
+merge_request: 35068
+author:
+type: performance
diff --git a/changelogs/unreleased/sh-requeue-failed-job-register.yml b/changelogs/unreleased/sh-requeue-failed-job-register.yml
new file mode 100644
index 00000000000..1a80095aeee
--- /dev/null
+++ b/changelogs/unreleased/sh-requeue-failed-job-register.yml
@@ -0,0 +1,5 @@
+---
+title: Fail jobs that fail to render registration response
+merge_request: 36274
+author:
+type: fixed
diff --git a/changelogs/unreleased/sh-reseed-repository-storages.yml b/changelogs/unreleased/sh-reseed-repository-storages.yml
new file mode 100644
index 00000000000..d8d96b86e34
--- /dev/null
+++ b/changelogs/unreleased/sh-reseed-repository-storages.yml
@@ -0,0 +1,5 @@
+---
+title: Fix error 500s creating new projects due to empty weights
+merge_request: 35829
+author:
+type: fixed
diff --git a/changelogs/unreleased/sh-update-git-config-for-forks.yml b/changelogs/unreleased/sh-update-git-config-for-forks.yml
new file mode 100644
index 00000000000..4827f01feec
--- /dev/null
+++ b/changelogs/unreleased/sh-update-git-config-for-forks.yml
@@ -0,0 +1,5 @@
+---
+title: Ensure .git/config is updated for forks
+merge_request: 35305
+author:
+type: fixed
diff --git a/changelogs/unreleased/sh-update-grape-1-4-0.yml b/changelogs/unreleased/sh-update-grape-1-4-0.yml
new file mode 100644
index 00000000000..d6273afea29
--- /dev/null
+++ b/changelogs/unreleased/sh-update-grape-1-4-0.yml
@@ -0,0 +1,5 @@
+---
+title: Update to Grape v1.4.0
+merge_request: 36628
+author:
+type: fixed
diff --git a/changelogs/unreleased/sh-update-grape-gem.yml b/changelogs/unreleased/sh-update-grape-gem.yml
new file mode 100644
index 00000000000..fbed6ad665b
--- /dev/null
+++ b/changelogs/unreleased/sh-update-grape-gem.yml
@@ -0,0 +1,5 @@
+---
+title: Upgrade Grape v1.1.0 to v1.3.3
+merge_request: 33450
+author:
+type: other
diff --git a/changelogs/unreleased/sh-update-importer-log-error-field.yml b/changelogs/unreleased/sh-update-importer-log-error-field.yml
new file mode 100644
index 00000000000..3e3c9dd1da5
--- /dev/null
+++ b/changelogs/unreleased/sh-update-importer-log-error-field.yml
@@ -0,0 +1,5 @@
+---
+title: Use error.message instead of error in importer.log
+merge_request: 36104
+author:
+type: fixed
diff --git a/changelogs/unreleased/sh-update-sidekiq-5-2-9.yml b/changelogs/unreleased/sh-update-sidekiq-5-2-9.yml
new file mode 100644
index 00000000000..b9ed7510028
--- /dev/null
+++ b/changelogs/unreleased/sh-update-sidekiq-5-2-9.yml
@@ -0,0 +1,5 @@
+---
+title: Update Sidekiq to v5.2.9
+merge_request: 35495
+author:
+type: other
diff --git a/changelogs/unreleased/sh-update-workhorse-8-34-0.yml b/changelogs/unreleased/sh-update-workhorse-8-34-0.yml
new file mode 100644
index 00000000000..5ecb3840463
--- /dev/null
+++ b/changelogs/unreleased/sh-update-workhorse-8-34-0.yml
@@ -0,0 +1,5 @@
+---
+title: Update GitLab Workhorse to v8.34.0
+merge_request: 33543
+author:
+type: fixed
diff --git a/changelogs/unreleased/sh-workhorse-direct-access-upload.yml b/changelogs/unreleased/sh-workhorse-direct-access-upload.yml
new file mode 100644
index 00000000000..41aee482891
--- /dev/null
+++ b/changelogs/unreleased/sh-workhorse-direct-access-upload.yml
@@ -0,0 +1,5 @@
+---
+title: Support Workhorse directly uploading files to S3
+merge_request: 29389
+author:
+type: added
diff --git a/changelogs/unreleased/short-url-for-custom-metrics-dashboards.yml b/changelogs/unreleased/short-url-for-custom-metrics-dashboards.yml
new file mode 100644
index 00000000000..7e79f0aa0fd
--- /dev/null
+++ b/changelogs/unreleased/short-url-for-custom-metrics-dashboards.yml
@@ -0,0 +1,5 @@
+---
+title: Support short urls for custom metrics dashboards
+merge_request: 36740
+author:
+type: added
diff --git a/changelogs/unreleased/show-redis-instance-in-performance-bar.yml b/changelogs/unreleased/show-redis-instance-in-performance-bar.yml
new file mode 100644
index 00000000000..80dbd33f110
--- /dev/null
+++ b/changelogs/unreleased/show-redis-instance-in-performance-bar.yml
@@ -0,0 +1,5 @@
+---
+title: Show Redis instance in performance bar
+merge_request: 34377
+author:
+type: changed
diff --git a/changelogs/unreleased/show_build_status_in_branch_list.yml b/changelogs/unreleased/show_build_status_in_branch_list.yml
new file mode 100644
index 00000000000..259b916af10
--- /dev/null
+++ b/changelogs/unreleased/show_build_status_in_branch_list.yml
@@ -0,0 +1,5 @@
+---
+title: Show build status on branch list
+merge_request: 30948
+author: Lee Tickett
+type: added
diff --git a/changelogs/unreleased/show_estimate_on_issues_list.yml b/changelogs/unreleased/show_estimate_on_issues_list.yml
new file mode 100644
index 00000000000..9845fcc2f57
--- /dev/null
+++ b/changelogs/unreleased/show_estimate_on_issues_list.yml
@@ -0,0 +1,5 @@
+---
+title: Show estimate on issues list
+merge_request: 28271
+author: Lee Tickett
+type: added
diff --git a/changelogs/unreleased/sidekiq-arguments-logging-tokens.yml b/changelogs/unreleased/sidekiq-arguments-logging-tokens.yml
new file mode 100644
index 00000000000..143a85b2986
--- /dev/null
+++ b/changelogs/unreleased/sidekiq-arguments-logging-tokens.yml
@@ -0,0 +1,5 @@
+---
+title: Filter potentially-sensitive Sidekiq arguments from logs and Sentry
+merge_request: 33967
+author:
+type: changed
diff --git a/changelogs/unreleased/skip-importing-failed-jira-issue-instead-of-entrire-batch.yml b/changelogs/unreleased/skip-importing-failed-jira-issue-instead-of-entrire-batch.yml
new file mode 100644
index 00000000000..89e4261c40d
--- /dev/null
+++ b/changelogs/unreleased/skip-importing-failed-jira-issue-instead-of-entrire-batch.yml
@@ -0,0 +1,5 @@
+---
+title: Skip the individual JIRA issues if failed to import vs failing the whole batch
+merge_request: 32673
+author:
+type: fixed
diff --git a/changelogs/unreleased/sort-code-coverage-graph-by-dates.yml b/changelogs/unreleased/sort-code-coverage-graph-by-dates.yml
new file mode 100644
index 00000000000..43fa9d519df
--- /dev/null
+++ b/changelogs/unreleased/sort-code-coverage-graph-by-dates.yml
@@ -0,0 +1,5 @@
+---
+title: Sort code coverage graph in ascending order
+merge_request: 34750
+author:
+type: fixed
diff --git a/changelogs/unreleased/stackprof.yml b/changelogs/unreleased/stackprof.yml
new file mode 100644
index 00000000000..11361381d2d
--- /dev/null
+++ b/changelogs/unreleased/stackprof.yml
@@ -0,0 +1,5 @@
+---
+title: Trigger stackprof by sending a SIGUSR2 signal
+merge_request: 35993
+author:
+type: performance
diff --git a/changelogs/unreleased/support-file-names-for-metrics-dashboards.yml b/changelogs/unreleased/support-file-names-for-metrics-dashboards.yml
new file mode 100644
index 00000000000..951c502daea
--- /dev/null
+++ b/changelogs/unreleased/support-file-names-for-metrics-dashboards.yml
@@ -0,0 +1,5 @@
+---
+title: Support metrics dashboard with file name
+merge_request: 34115
+author:
+type: added
diff --git a/changelogs/unreleased/support-grafana-links-in-metrics-dashboard.yml b/changelogs/unreleased/support-grafana-links-in-metrics-dashboard.yml
new file mode 100644
index 00000000000..cbbce1b2b34
--- /dev/null
+++ b/changelogs/unreleased/support-grafana-links-in-metrics-dashboard.yml
@@ -0,0 +1,5 @@
+---
+title: Support user-defined Grafana links in metrics dashboard
+merge_request: 34003
+author:
+type: added
diff --git a/changelogs/unreleased/support-warnings-in-pipeline-creation.yml b/changelogs/unreleased/support-warnings-in-pipeline-creation.yml
new file mode 100644
index 00000000000..88202c9a516
--- /dev/null
+++ b/changelogs/unreleased/support-warnings-in-pipeline-creation.yml
@@ -0,0 +1,5 @@
+---
+title: Store pipeline creation errors and warnings into Ci::PipelineMessage
+merge_request: 33762
+author:
+type: other
diff --git a/changelogs/unreleased/suppress-progress-on-pulling-image-in-builtin-templates.yml b/changelogs/unreleased/suppress-progress-on-pulling-image-in-builtin-templates.yml
new file mode 100644
index 00000000000..c9cbc4181a0
--- /dev/null
+++ b/changelogs/unreleased/suppress-progress-on-pulling-image-in-builtin-templates.yml
@@ -0,0 +1,5 @@
+---
+title: Suppress progress on docker pulling in builtin templates
+merge_request: 35253
+author: Takuya Noguchi
+type: other
diff --git a/changelogs/unreleased/swap-to-oj.yml b/changelogs/unreleased/swap-to-oj.yml
new file mode 100644
index 00000000000..000516df915
--- /dev/null
+++ b/changelogs/unreleased/swap-to-oj.yml
@@ -0,0 +1,5 @@
+---
+title: Add oj gem for faster JSON
+merge_request: 36555
+author:
+type: performance
diff --git a/changelogs/unreleased/switch-diff-view-fix.yml b/changelogs/unreleased/switch-diff-view-fix.yml
new file mode 100644
index 00000000000..ff3e1e11e49
--- /dev/null
+++ b/changelogs/unreleased/switch-diff-view-fix.yml
@@ -0,0 +1,6 @@
+---
+title: Deduplicate URL parameters when requesting merge request diffs which causes
+ diffs load to fail
+merge_request: 33117
+author:
+type: fixed
diff --git a/changelogs/unreleased/sy-alert-issue-system-notes.yml b/changelogs/unreleased/sy-alert-issue-system-notes.yml
new file mode 100644
index 00000000000..d9ade458159
--- /dev/null
+++ b/changelogs/unreleased/sy-alert-issue-system-notes.yml
@@ -0,0 +1,5 @@
+---
+title: Add system note for alert when creating issue
+merge_request: 36370
+author:
+type: added
diff --git a/changelogs/unreleased/sy-alert-status-system-notes.yml b/changelogs/unreleased/sy-alert-status-system-notes.yml
new file mode 100644
index 00000000000..1421e0e33d1
--- /dev/null
+++ b/changelogs/unreleased/sy-alert-status-system-notes.yml
@@ -0,0 +1,5 @@
+---
+title: Add system notes for status updates on alerts
+merge_request: 35467
+author:
+type: added
diff --git a/changelogs/unreleased/sy-metrics-embeds-in-alerts.yml b/changelogs/unreleased/sy-metrics-embeds-in-alerts.yml
new file mode 100644
index 00000000000..ec7d201359b
--- /dev/null
+++ b/changelogs/unreleased/sy-metrics-embeds-in-alerts.yml
@@ -0,0 +1,5 @@
+---
+title: Expose metrics dashboard URL for alert GraphQL query
+merge_request: 35293
+author:
+type: added
diff --git a/changelogs/unreleased/sy-publish-command.yml b/changelogs/unreleased/sy-publish-command.yml
new file mode 100644
index 00000000000..cfc46e8f1c1
--- /dev/null
+++ b/changelogs/unreleased/sy-publish-command.yml
@@ -0,0 +1,6 @@
+---
+title: Backfill StatusPage::Published incidents and enable a publish quick action
+ for EE
+merge_request: 30906
+author:
+type: added
diff --git a/changelogs/unreleased/sy-publish-status-ui-fe.yml b/changelogs/unreleased/sy-publish-status-ui-fe.yml
new file mode 100644
index 00000000000..f0de124efb1
--- /dev/null
+++ b/changelogs/unreleased/sy-publish-status-ui-fe.yml
@@ -0,0 +1,5 @@
+---
+title: Add link to status page detail view for status page published issues
+merge_request: 30249
+author:
+type: added
diff --git a/changelogs/unreleased/symlink-icon-graphql-file-mode.yml b/changelogs/unreleased/symlink-icon-graphql-file-mode.yml
new file mode 100644
index 00000000000..b84fb22c24b
--- /dev/null
+++ b/changelogs/unreleased/symlink-icon-graphql-file-mode.yml
@@ -0,0 +1,5 @@
+---
+title: Expose blob mode in GraphQL for repository files
+merge_request: 36488
+author:
+type: other
diff --git a/changelogs/unreleased/symlink-icon-ui.yml b/changelogs/unreleased/symlink-icon-ui.yml
new file mode 100644
index 00000000000..6a907bec6cb
--- /dev/null
+++ b/changelogs/unreleased/symlink-icon-ui.yml
@@ -0,0 +1,5 @@
+---
+title: Show symlink icon in repository browser
+merge_request: 36524
+author:
+type: fixed
diff --git a/changelogs/unreleased/system-notes-broken-css.yml b/changelogs/unreleased/system-notes-broken-css.yml
new file mode 100644
index 00000000000..082fc324030
--- /dev/null
+++ b/changelogs/unreleased/system-notes-broken-css.yml
@@ -0,0 +1,5 @@
+---
+title: Fix broken CSS for system notes
+merge_request: 34870
+author:
+type: other
diff --git a/changelogs/unreleased/tc-fix-plain-text-commit-mails.yml b/changelogs/unreleased/tc-fix-plain-text-commit-mails.yml
new file mode 100644
index 00000000000..a3475d4529f
--- /dev/null
+++ b/changelogs/unreleased/tc-fix-plain-text-commit-mails.yml
@@ -0,0 +1,5 @@
+---
+title: Remove HTML link from plain text mail
+merge_request: 36301
+author:
+type: fixed
diff --git a/changelogs/unreleased/templates-current-folder-fix.yml b/changelogs/unreleased/templates-current-folder-fix.yml
new file mode 100644
index 00000000000..9b4999c9d54
--- /dev/null
+++ b/changelogs/unreleased/templates-current-folder-fix.yml
@@ -0,0 +1,5 @@
+---
+title: "Web IDE: Create template files in the folder from which new file request was made"
+merge_request: 33585
+author: Ashesh Vidyut
+type: fixed
diff --git a/changelogs/unreleased/test-report-link-fix.yml b/changelogs/unreleased/test-report-link-fix.yml
new file mode 100644
index 00000000000..04a4e04bb81
--- /dev/null
+++ b/changelogs/unreleased/test-report-link-fix.yml
@@ -0,0 +1,5 @@
+---
+title: Fix for test report link in MR widget
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/tidy_put_projects_issues_spec.yml b/changelogs/unreleased/tidy_put_projects_issues_spec.yml
new file mode 100644
index 00000000000..ef0d8d1745d
--- /dev/null
+++ b/changelogs/unreleased/tidy_put_projects_issues_spec.yml
@@ -0,0 +1,5 @@
+---
+title: Tidy
+merge_request: 32759
+author: Lee Tickett
+type: other
diff --git a/changelogs/unreleased/tr-alert-column-spacing.yml b/changelogs/unreleased/tr-alert-column-spacing.yml
new file mode 100644
index 00000000000..95f107935f8
--- /dev/null
+++ b/changelogs/unreleased/tr-alert-column-spacing.yml
@@ -0,0 +1,5 @@
+---
+title: Improve alert list spacing
+merge_request: 35400
+author:
+type: fixed
diff --git a/changelogs/unreleased/tr-alert-issue-link.yml b/changelogs/unreleased/tr-alert-issue-link.yml
new file mode 100644
index 00000000000..3ec3a3a5ce1
--- /dev/null
+++ b/changelogs/unreleased/tr-alert-issue-link.yml
@@ -0,0 +1,5 @@
+---
+title: Add issue column to alert list
+merge_request: 35291
+author:
+type: added
diff --git a/changelogs/unreleased/tr-alert-text-search.yml b/changelogs/unreleased/tr-alert-text-search.yml
new file mode 100644
index 00000000000..23a9acb7473
--- /dev/null
+++ b/changelogs/unreleased/tr-alert-text-search.yml
@@ -0,0 +1,5 @@
+---
+title: Search plain text in alert list frontend
+merge_request: 34631
+author:
+type: added
diff --git a/changelogs/unreleased/tr-avoid-alert-refetch.yml b/changelogs/unreleased/tr-avoid-alert-refetch.yml
new file mode 100644
index 00000000000..3dafc79a366
--- /dev/null
+++ b/changelogs/unreleased/tr-avoid-alert-refetch.yml
@@ -0,0 +1,5 @@
+---
+title: Avoid refresh to show endedAt after mutation
+merge_request: 32636
+author:
+type: fixed
diff --git a/changelogs/unreleased/tr-fix-broken-incident-link.yml b/changelogs/unreleased/tr-fix-broken-incident-link.yml
new file mode 100644
index 00000000000..bea818796d0
--- /dev/null
+++ b/changelogs/unreleased/tr-fix-broken-incident-link.yml
@@ -0,0 +1,5 @@
+---
+title: Fix broken help link on operations settings page
+merge_request: 32722
+author:
+type: fixed
diff --git a/changelogs/unreleased/tr-fix-prometheus-alert-list.yml b/changelogs/unreleased/tr-fix-prometheus-alert-list.yml
new file mode 100644
index 00000000000..4dd13f6aeb8
--- /dev/null
+++ b/changelogs/unreleased/tr-fix-prometheus-alert-list.yml
@@ -0,0 +1,5 @@
+---
+title: Ensure that alerts are shown when prometheus service is active
+merge_request: 33928
+author:
+type: fixed
diff --git a/changelogs/unreleased/tr-prettify-graphql-files.yml b/changelogs/unreleased/tr-prettify-graphql-files.yml
new file mode 100644
index 00000000000..136595d2d32
--- /dev/null
+++ b/changelogs/unreleased/tr-prettify-graphql-files.yml
@@ -0,0 +1,5 @@
+---
+title: Format graphql files with prettier
+merge_request: 36244
+author:
+type: other
diff --git a/changelogs/unreleased/tr-reword-alert-service.yml b/changelogs/unreleased/tr-reword-alert-service.yml
new file mode 100644
index 00000000000..e7665822e72
--- /dev/null
+++ b/changelogs/unreleased/tr-reword-alert-service.yml
@@ -0,0 +1,5 @@
+---
+title: Add more detail to alert integration settings description
+merge_request: 33244
+author:
+type: added
diff --git a/changelogs/unreleased/tr-show-new-alerts.yml b/changelogs/unreleased/tr-show-new-alerts.yml
new file mode 100644
index 00000000000..add7bda4b9c
--- /dev/null
+++ b/changelogs/unreleased/tr-show-new-alerts.yml
@@ -0,0 +1,5 @@
+---
+title: Show when alert is new in the Alerts list
+merge_request: 35708
+author:
+type: added
diff --git a/changelogs/unreleased/track-pod-logs-refresh-action.yml b/changelogs/unreleased/track-pod-logs-refresh-action.yml
new file mode 100644
index 00000000000..a7d9021a90e
--- /dev/null
+++ b/changelogs/unreleased/track-pod-logs-refresh-action.yml
@@ -0,0 +1,5 @@
+---
+title: Track pod logs refresh action
+merge_request: 33802
+author:
+type: added
diff --git a/changelogs/unreleased/traversal-hierarchy.yml b/changelogs/unreleased/traversal-hierarchy.yml
new file mode 100644
index 00000000000..3c8afee2cdc
--- /dev/null
+++ b/changelogs/unreleased/traversal-hierarchy.yml
@@ -0,0 +1,5 @@
+---
+title: Define a namespace traversal cache
+merge_request: 35713
+author:
+type: performance
diff --git a/changelogs/unreleased/unconfirm-wrongfully-verified-email-records.yml b/changelogs/unreleased/unconfirm-wrongfully-verified-email-records.yml
new file mode 100644
index 00000000000..ebf216836f8
--- /dev/null
+++ b/changelogs/unreleased/unconfirm-wrongfully-verified-email-records.yml
@@ -0,0 +1,5 @@
+---
+title: Unconfirm wrongfully verified email addresses and user accounts
+merge_request: 35492
+author:
+type: security
diff --git a/changelogs/unreleased/update--variables-to-match-gitlab-ui.yml b/changelogs/unreleased/update--variables-to-match-gitlab-ui.yml
new file mode 100644
index 00000000000..32fd0a95dab
--- /dev/null
+++ b/changelogs/unreleased/update--variables-to-match-gitlab-ui.yml
@@ -0,0 +1,5 @@
+---
+title: Update blue hex values to match GitLab UI
+merge_request: 34530
+author:
+type: other
diff --git a/changelogs/unreleased/update-ado-deploy-image-to-0-17-2.yml b/changelogs/unreleased/update-ado-deploy-image-to-0-17-2.yml
new file mode 100644
index 00000000000..d1472c8e417
--- /dev/null
+++ b/changelogs/unreleased/update-ado-deploy-image-to-0-17-2.yml
@@ -0,0 +1,5 @@
+---
+title: Uses --set-string to avoid Helm confusion over short SHA vs Scientific Notation
+merge_request: 37004
+author: Bryan H. @galador
+type: fixed
diff --git a/changelogs/unreleased/update-auto-build-image-to-0-3-0.yml b/changelogs/unreleased/update-auto-build-image-to-0-3-0.yml
new file mode 100644
index 00000000000..a8f5534f043
--- /dev/null
+++ b/changelogs/unreleased/update-auto-build-image-to-0-3-0.yml
@@ -0,0 +1,5 @@
+---
+title: Add custom Dockerfile paths to Auto DevOps Build stage with DOCKERFILE_PATH
+merge_request: 35662
+author: thklein
+type: added
diff --git a/changelogs/unreleased/update-deprecated-slot-syntax-in---app-assets-javascripts-pages-admin-pro.yml b/changelogs/unreleased/update-deprecated-slot-syntax-in---app-assets-javascripts-pages-admin-pro.yml
new file mode 100644
index 00000000000..f4775f7db5c
--- /dev/null
+++ b/changelogs/unreleased/update-deprecated-slot-syntax-in---app-assets-javascripts-pages-admin-pro.yml
@@ -0,0 +1,5 @@
+---
+title: Update deprecated slot syntax in ./app/assets/javascripts/pages/admin/projects/index/components/delete_project_modal.vue
+merge_request: 31994
+author: Gilang Gumilar
+type: other
diff --git a/changelogs/unreleased/update-deprecated-slot-syntax-in---app-assets-javascripts-pages-projects-.yml b/changelogs/unreleased/update-deprecated-slot-syntax-in---app-assets-javascripts-pages-projects-.yml
new file mode 100644
index 00000000000..8ee3646bcd1
--- /dev/null
+++ b/changelogs/unreleased/update-deprecated-slot-syntax-in---app-assets-javascripts-pages-projects-.yml
@@ -0,0 +1,5 @@
+---
+title: Update deprecated slot syntax in ./app/assets/javascripts/pages/projects/labels/components/promote_label_modal.vue
+merge_request: 31995
+author: Gilang Gumilar
+type: other
diff --git a/changelogs/unreleased/update-deprecated-slot-syntax-in---clusters-applications-vue.yml b/changelogs/unreleased/update-deprecated-slot-syntax-in---clusters-applications-vue.yml
new file mode 100644
index 00000000000..a18b4990414
--- /dev/null
+++ b/changelogs/unreleased/update-deprecated-slot-syntax-in---clusters-applications-vue.yml
@@ -0,0 +1,5 @@
+---
+title: Update deprecated slot syntax in ./app/assets/javascripts/clusters/components/remove_cluster_confirmation.vue
+merge_request: 32010
+author: Gilang Gumilar
+type: other
diff --git a/changelogs/unreleased/update-deprecated-slot-syntax-in---javascripts-environments_app-vue.yml b/changelogs/unreleased/update-deprecated-slot-syntax-in---javascripts-environments_app-vue.yml
new file mode 100644
index 00000000000..a4f77b53937
--- /dev/null
+++ b/changelogs/unreleased/update-deprecated-slot-syntax-in---javascripts-environments_app-vue.yml
@@ -0,0 +1,5 @@
+---
+title: Update deprecated slot syntax in ./app/assets/javascripts/environments/components/environments_app.vue
+merge_request: 32011
+author: Gilang Gumilar
+type: other
diff --git a/changelogs/unreleased/update-deprecated-slot-syntax-in-app-assets-javascripts-reports-component.yml b/changelogs/unreleased/update-deprecated-slot-syntax-in-app-assets-javascripts-reports-component.yml
new file mode 100644
index 00000000000..b880020aa7f
--- /dev/null
+++ b/changelogs/unreleased/update-deprecated-slot-syntax-in-app-assets-javascripts-reports-component.yml
@@ -0,0 +1,5 @@
+---
+title: Update deprecated slot syntax in app/assets/javascripts/reports/components/grouped_test_reports_app.vue
+merge_request: 31975
+author: Gilang Gumilar
+type: other
diff --git a/changelogs/unreleased/update-gitlab-runner-helm-chart-to-0-17-0.yml b/changelogs/unreleased/update-gitlab-runner-helm-chart-to-0-17-0.yml
new file mode 100644
index 00000000000..98f8ec0c03b
--- /dev/null
+++ b/changelogs/unreleased/update-gitlab-runner-helm-chart-to-0-17-0.yml
@@ -0,0 +1,5 @@
+---
+title: Update GitLab Runner Helm Chart to 0.17.0
+merge_request: 32634
+author:
+type: other
diff --git a/changelogs/unreleased/update-gitlab-runner-helm-chart-to-0-17-1.yml b/changelogs/unreleased/update-gitlab-runner-helm-chart-to-0-17-1.yml
new file mode 100644
index 00000000000..74cf7405268
--- /dev/null
+++ b/changelogs/unreleased/update-gitlab-runner-helm-chart-to-0-17-1.yml
@@ -0,0 +1,5 @@
+---
+title: Update GitLab Runner Helm Chart to 0.17.1
+merge_request: 33504
+author:
+type: other
diff --git a/changelogs/unreleased/update-gitlab-runner-helm-chart-to-0-18-0.yml b/changelogs/unreleased/update-gitlab-runner-helm-chart-to-0-18-0.yml
new file mode 100644
index 00000000000..3b07fe7c573
--- /dev/null
+++ b/changelogs/unreleased/update-gitlab-runner-helm-chart-to-0-18-0.yml
@@ -0,0 +1,5 @@
+---
+title: Update GitLab Runner Helm Chart to 0.18.0
+merge_request: 34969
+author:
+type: other
diff --git a/changelogs/unreleased/update-gitlab-runner-helm-chart-to-0.18.1.yml b/changelogs/unreleased/update-gitlab-runner-helm-chart-to-0.18.1.yml
new file mode 100644
index 00000000000..ee3f216ebcc
--- /dev/null
+++ b/changelogs/unreleased/update-gitlab-runner-helm-chart-to-0.18.1.yml
@@ -0,0 +1,5 @@
+---
+title: Update GitLab Runner Helm Chart to 0.18.1
+merge_request: 35712
+author:
+type: other
diff --git a/changelogs/unreleased/update-green-variables-to-match-gitlab-ui.yml b/changelogs/unreleased/update-green-variables-to-match-gitlab-ui.yml
new file mode 100644
index 00000000000..32f07dcd1a8
--- /dev/null
+++ b/changelogs/unreleased/update-green-variables-to-match-gitlab-ui.yml
@@ -0,0 +1,5 @@
+---
+title: Update green hex values to match GitLab UI
+merge_request: 34547
+author:
+type: other
diff --git a/changelogs/unreleased/update-index-artifacts-expire.yml b/changelogs/unreleased/update-index-artifacts-expire.yml
new file mode 100644
index 00000000000..9eaed111aa8
--- /dev/null
+++ b/changelogs/unreleased/update-index-artifacts-expire.yml
@@ -0,0 +1,6 @@
+---
+title: Update index_ci_builds_on_commit_id_and_artifacts_expireatandidpartial index
+ for secret_detection
+merge_request: 32584
+author:
+type: performance
diff --git a/changelogs/unreleased/update-lfs-setting-label.yml b/changelogs/unreleased/update-lfs-setting-label.yml
new file mode 100644
index 00000000000..1b505af2fef
--- /dev/null
+++ b/changelogs/unreleased/update-lfs-setting-label.yml
@@ -0,0 +1,5 @@
+---
+title: Update LFS setting label
+merge_request: 34829
+author: George Tsiolis
+type: changed
diff --git a/changelogs/unreleased/update-rack-timeout.yml b/changelogs/unreleased/update-rack-timeout.yml
new file mode 100644
index 00000000000..a7cafe7255f
--- /dev/null
+++ b/changelogs/unreleased/update-rack-timeout.yml
@@ -0,0 +1,5 @@
+---
+title: Update `rack-timeout` to `0.5.2`
+merge_request: 36289
+author:
+type: changed
diff --git a/changelogs/unreleased/update-rouge-3-21.yml b/changelogs/unreleased/update-rouge-3-21.yml
new file mode 100644
index 00000000000..69a204b87c1
--- /dev/null
+++ b/changelogs/unreleased/update-rouge-3-21.yml
@@ -0,0 +1,5 @@
+---
+title: Update Rouge to v3.21.0
+merge_request: 36942
+author:
+type: other
diff --git a/changelogs/unreleased/update-secure-smau-metric.yml b/changelogs/unreleased/update-secure-smau-metric.yml
new file mode 100644
index 00000000000..b07ec45a9f3
--- /dev/null
+++ b/changelogs/unreleased/update-secure-smau-metric.yml
@@ -0,0 +1,5 @@
+---
+title: Report all unique users for Secure scanners
+merge_request: 33881
+author:
+type: changed
diff --git a/changelogs/unreleased/update-validates-hostname-gem.yml b/changelogs/unreleased/update-validates-hostname-gem.yml
new file mode 100644
index 00000000000..4665ebe2724
--- /dev/null
+++ b/changelogs/unreleased/update-validates-hostname-gem.yml
@@ -0,0 +1,5 @@
+---
+title: Update validates_hostname gem with support for more TLDs
+merge_request: 34010
+author:
+type: fixed
diff --git a/changelogs/unreleased/update-workhorse-version-master.yml b/changelogs/unreleased/update-workhorse-version-master.yml
new file mode 100644
index 00000000000..3a8aa981668
--- /dev/null
+++ b/changelogs/unreleased/update-workhorse-version-master.yml
@@ -0,0 +1,5 @@
+---
+title: Update Workhorse to v8.35.0
+merge_request: 33817
+author:
+type: other
diff --git a/changelogs/unreleased/update_android_ci.yml b/changelogs/unreleased/update_android_ci.yml
new file mode 100644
index 00000000000..62a99d5b328
--- /dev/null
+++ b/changelogs/unreleased/update_android_ci.yml
@@ -0,0 +1,5 @@
+---
+title: Updated the Android CI Script
+merge_request: 34007
+author: s-ayush2903
+type: fixed
diff --git a/changelogs/unreleased/upgrade-codequality-template.yml b/changelogs/unreleased/upgrade-codequality-template.yml
new file mode 100644
index 00000000000..7af201cddd0
--- /dev/null
+++ b/changelogs/unreleased/upgrade-codequality-template.yml
@@ -0,0 +1,5 @@
+---
+title: Use CodeQuality 0.85.10 in the CI template
+merge_request: 34329
+author:
+type: changed
diff --git a/changelogs/unreleased/upgrade-pages-to-1-19.yml b/changelogs/unreleased/upgrade-pages-to-1-19.yml
new file mode 100644
index 00000000000..a509b3d1cb6
--- /dev/null
+++ b/changelogs/unreleased/upgrade-pages-to-1-19.yml
@@ -0,0 +1,5 @@
+---
+title: Upgrade GitLab Pages to 1.19.0
+merge_request: 34730
+author:
+type: added
diff --git a/changelogs/unreleased/upgrade-renamed-diff-show-full-diff-for-renamed-files.yml b/changelogs/unreleased/upgrade-renamed-diff-show-full-diff-for-renamed-files.yml
new file mode 100644
index 00000000000..3a9cc4c383a
--- /dev/null
+++ b/changelogs/unreleased/upgrade-renamed-diff-show-full-diff-for-renamed-files.yml
@@ -0,0 +1,5 @@
+---
+title: Add a link to the `renamed` viewer to fully expand the renamed file (if it's text)
+merge_request: 28448
+author:
+type: added
diff --git a/changelogs/unreleased/use-local-tiller-by-default.yml b/changelogs/unreleased/use-local-tiller-by-default.yml
new file mode 100644
index 00000000000..7bc0c6555dd
--- /dev/null
+++ b/changelogs/unreleased/use-local-tiller-by-default.yml
@@ -0,0 +1,5 @@
+---
+title: Use local Tiller by default for GitLab-managed apps
+merge_request: 35562
+author:
+type: changed
diff --git a/changelogs/unreleased/vij-raw-snippet-blobs.yml b/changelogs/unreleased/vij-raw-snippet-blobs.yml
new file mode 100644
index 00000000000..0576de8bd33
--- /dev/null
+++ b/changelogs/unreleased/vij-raw-snippet-blobs.yml
@@ -0,0 +1,5 @@
+---
+title: Add new raw snippet blob endpoint
+merge_request: 33938
+author:
+type: added
diff --git a/changelogs/unreleased/vij-update-existing-raw-snippet.yml b/changelogs/unreleased/vij-update-existing-raw-snippet.yml
new file mode 100644
index 00000000000..d47638439d4
--- /dev/null
+++ b/changelogs/unreleased/vij-update-existing-raw-snippet.yml
@@ -0,0 +1,5 @@
+---
+title: Add raw snippet repository file endpoint to API
+merge_request: 36037
+author:
+type: changed
diff --git a/changelogs/unreleased/vs-fix-account-delete-plural-msg.yml b/changelogs/unreleased/vs-fix-account-delete-plural-msg.yml
new file mode 100644
index 00000000000..39dccc2fbde
--- /dev/null
+++ b/changelogs/unreleased/vs-fix-account-delete-plural-msg.yml
@@ -0,0 +1,5 @@
+---
+title: Fix plural message in account deletion section
+merge_request: 32868
+author:
+type: fixed
diff --git a/changelogs/unreleased/wiki-edit-invalid-page.yml b/changelogs/unreleased/wiki-edit-invalid-page.yml
new file mode 100644
index 00000000000..d7d0ea00776
--- /dev/null
+++ b/changelogs/unreleased/wiki-edit-invalid-page.yml
@@ -0,0 +1,5 @@
+---
+title: Redirect wiki edit actions for missing pages
+merge_request: 35350
+author:
+type: fixed
diff --git a/changelogs/unreleased/xanf-expose-bitbucket-error.yml b/changelogs/unreleased/xanf-expose-bitbucket-error.yml
new file mode 100644
index 00000000000..571bc65bc30
--- /dev/null
+++ b/changelogs/unreleased/xanf-expose-bitbucket-error.yml
@@ -0,0 +1,5 @@
+---
+title: Fix displaying import errors from server
+merge_request: 37073
+author:
+type: fixed
diff --git a/changelogs/unreleased/xanf-fix-404-on-import.yml b/changelogs/unreleased/xanf-fix-404-on-import.yml
new file mode 100644
index 00000000000..8c25c1f0d44
--- /dev/null
+++ b/changelogs/unreleased/xanf-fix-404-on-import.yml
@@ -0,0 +1,5 @@
+---
+title: Fix 404 when importing project with developer permission
+merge_request: 35667
+author:
+type: fixed
diff --git a/changelogs/unreleased/xanf-use-new-import-ui-templates.yml b/changelogs/unreleased/xanf-use-new-import-ui-templates.yml
new file mode 100644
index 00000000000..32546178546
--- /dev/null
+++ b/changelogs/unreleased/xanf-use-new-import-ui-templates.yml
@@ -0,0 +1,5 @@
+---
+title: Introduce a feature flag for Vue-based UI for all import providers
+merge_request: 33980
+author:
+type: added
diff --git a/config/application.rb b/config/application.rb
index 524827226e7..772b3b042c1 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -17,6 +17,7 @@ module Gitlab
class Application < Rails::Application
require_dependency Rails.root.join('lib/gitlab')
require_dependency Rails.root.join('lib/gitlab/utils')
+ require_dependency Rails.root.join('lib/gitlab/action_cable/config')
require_dependency Rails.root.join('lib/gitlab/redis/wrapper')
require_dependency Rails.root.join('lib/gitlab/redis/cache')
require_dependency Rails.root.join('lib/gitlab/redis/queues')
@@ -157,6 +158,8 @@ module Gitlab
# Webpack dev server configuration is handled in initializers/static_files.rb
config.webpack.dev_server.enabled = false
+ config.action_mailer.delivery_job = "ActionMailer::MailDeliveryJob"
+
# Enable the asset pipeline
config.assets.enabled = true
diff --git a/config/dependency_decisions.yml b/config/dependency_decisions.yml
index ff5ccbb3c1b..3724fbb767b 100644
--- a/config/dependency_decisions.yml
+++ b/config/dependency_decisions.yml
@@ -454,12 +454,6 @@
:why: https://github.com/jaredhanson/utils-merge/blob/v1.0.0/LICENSE
:versions: []
:when: 2017-09-16 05:18:26.193764000 Z
-- - :approve
- - svg4everybody
- - :who: Tim Zallmann
- :why: CC0 1.0 - https://github.com/jonathantneal/svg4everybody/blob/master/LICENSE.md
- :versions: []
- :when: 2017-09-13 17:31:16.425819400 Z
- - :license
- "@gitlab/svgs"
- MIT
diff --git a/config/environments/development.rb b/config/environments/development.rb
index 25d57467060..9d4fc6ba5e9 100644
--- a/config/environments/development.rb
+++ b/config/environments/development.rb
@@ -49,8 +49,6 @@ Rails.application.configure do
# Do not log asset requests
config.assets.quiet = true
- config.allow_concurrency = Gitlab::Runtime.multi_threaded?
-
# BetterErrors live shell (REPL) on every stack frame
BetterErrors::Middleware.allow_ip!("127.0.0.1/0")
diff --git a/config/environments/production.rb b/config/environments/production.rb
index c03421040a3..393a274606e 100644
--- a/config/environments/production.rb
+++ b/config/environments/production.rb
@@ -77,6 +77,4 @@ Rails.application.configure do
config.action_mailer.raise_delivery_errors = true
config.eager_load = true
-
- config.allow_concurrency = Gitlab::Runtime.multi_threaded?
end
diff --git a/config/environments/test.rb b/config/environments/test.rb
index c130eb84baa..e08e2a34ff4 100644
--- a/config/environments/test.rb
+++ b/config/environments/test.rb
@@ -54,4 +54,8 @@ Rails.application.configure do
config.logger = ActiveSupport::TaggedLogging.new(Logger.new(nil))
config.log_level = :fatal
end
+
+ # Mount the ActionCable Engine in-app so that we don't have to spawn another Puma
+ # process for feature specs
+ ENV['ACTION_CABLE_IN_APP'] = 'true'
end
diff --git a/config/feature_categories.yml b/config/feature_categories.yml
index 7cbc90497a4..fd4cc8bf2a5 100644
--- a/config/feature_categories.yml
+++ b/config/feature_categories.yml
@@ -8,6 +8,7 @@
#
---
- accessibility_testing
+- advanced_deployments
- alert_management
- analysis
- api
@@ -28,7 +29,7 @@
- code_testing
- collection
- compliance_management
-- container_behavior_analytics
+- container_host_security
- container_network_security
- container_registry
- container_scanning
@@ -39,6 +40,7 @@
- dependency_proxy
- dependency_scanning
- design_management
+- design_system
- devops_reports
- digital_experience_management
- disaster_recovery
@@ -60,7 +62,6 @@
- helm_chart_registry
- importers
- incident_management
-- incremental_rollout
- infrastructure_as_code
- integrations
- interactive_application_security_testing
@@ -79,17 +80,18 @@
- malware_scanning
- merge_trains
- metrics
+- navigation
- omnibus_package
- package_registry
- pages
- pki_management
- planning_analytics
- product_analytics
+- projects
- quality_management
- release_evidence
- release_orchestration
- requirements_management
-- responsible_disclosure
- review_apps
- roadmaps
- runbooks
diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example
index bd328d9919a..7ba256b39cd 100644
--- a/config/gitlab.yml.example
+++ b/config/gitlab.yml.example
@@ -205,6 +205,33 @@ production: &base
# Whether to expunge (permanently remove) messages from the mailbox when they are deleted after delivery
expunge_deleted: false
+ ## Consolidated object store config
+ ## This will only take effect if the object_store sections are not defined
+ ## within the types (e.g. artifacts, lfs, etc.).
+ # object_store:
+ # enabled: false
+ # proxy_download: false # Passthrough all downloads via GitLab instead of using Redirects to Object Storage
+ # connection:
+ # provider: AWS # Only AWS supported at the moment
+ # aws_access_key_id: AWS_ACCESS_KEY_ID
+ # aws_secret_access_key: AWS_SECRET_ACCESS_KEY
+ # region: us-east-1
+ # aws_signature_version: 4 # For creation of signed URLs. Set to 2 if provider does not support v4.
+ # endpoint: 'https://s3.amazonaws.com' # default: nil - Useful for S3 compliant services such as DigitalOcean Spaces
+ # objects:
+ # artifacts:
+ # bucket: artifacts
+ # external_diffs:
+ # bucket: external-diffs
+ # lfs:
+ # bucket: lfs-objects
+ # uploads:
+ # bucket: uploads
+ # packages:
+ # bucket: packages
+ # dependency_proxy:
+ # bucket: dependency_proxy
+
## Build Artifacts
artifacts:
enabled: true
@@ -333,7 +360,7 @@ production: &base
# storage_path: shared/terraform_state
object_store:
enabled: false
- remote_directory: terraform_state # The bucket name
+ remote_directory: terraform # The bucket name
connection:
provider: AWS
aws_access_key_id: AWS_ACCESS_KEY_ID
@@ -474,11 +501,6 @@ production: &base
geo_registry_sync_worker:
cron: "*/1 * * * *"
- # GitLab Geo migrated local files clean up worker
- # NOTE: This will only take effect if Geo is enabled (secondary nodes only)
- geo_migrated_local_files_clean_up_worker:
- cron: "15 */6 * * *"
-
# Export pseudonymized data in CSV format for analysis
pseudonymizer_worker:
cron: "0 * * * *"
@@ -487,12 +509,17 @@ production: &base
# NOTE: This will only take effect if elasticsearch is enabled.
elastic_index_bulk_cron_worker:
cron: "*/1 * * * *"
-
+
# Elasticsearch bulk updater for initial updates.
# NOTE: This will only take effect if elasticsearch is enabled.
elastic_index_initial_bulk_cron_worker:
cron: "*/1 * * * *"
+ # Elasticsearch reindexing worker
+ # NOTE: This will only take effect if elasticsearch is enabled.
+ elastic_index_initial_bulk_cron_worker:
+ cron: "*/10 * * * *"
+
registry:
# enabled: true
# host: registry.example.com
@@ -1078,11 +1105,6 @@ production: &base
# host: localhost
# port: 3808
- ## ActionCable settings
- action_cable:
- # Number of threads used to process ActionCable connection callbacks and channel actions
- # worker_pool_size: 4
-
## Monitoring
# Built in monitoring settings
monitoring:
@@ -1181,7 +1203,7 @@ test:
# has been pushed).
# when: always
# The location where external diffs are stored (default: shared/external-diffs).
- # storage_path: shared/external-diffs
+ storage_path: tmp/tests/external-diffs
object_store:
enabled: false
remote_directory: external-diffs # The bucket name
@@ -1231,7 +1253,7 @@ test:
storage_path: tmp/tests/terraform_state
object_store:
enabled: false
- remote_directory: terraform_state
+ remote_directory: terraform
connection:
provider: AWS # Only AWS supported at the moment
aws_access_key_id: AWS_ACCESS_KEY_ID
diff --git a/config/initializers/01_secret_token.rb b/config/initializers/01_secret_token.rb
index 8b96727a2a1..5949f463457 100644
--- a/config/initializers/01_secret_token.rb
+++ b/config/initializers/01_secret_token.rb
@@ -1,13 +1,5 @@
-# WARNING: If you add a new secret to this file, make sure you also
-# update Omnibus GitLab or updates will fail. Omnibus is responsible for
-# writing the `secrets.yml` file. If Omnibus doesn't know about a
-# secret, Rails will attempt to write to the file, but this will fail
-# because Rails doesn't have write access.
-#
-# As an example:
-# * https://gitlab.com/gitlab-org/gitlab-foss/merge_requests/27581
-# * https://gitlab.com/gitlab-org/omnibus-gitlab/merge_requests/3267
-#
+# WARNING: Before you make a change to secrets.yml, read the development guide for GitLab secrets
+# doc/development/application_secrets.md.
#
# This file needs to be loaded BEFORE any initializers that attempt to
# prepend modules that require access to secrets (e.g. EE's 0_as_concern.rb).
diff --git a/config/initializers/0_inject_feature_flags.rb b/config/initializers/0_inject_feature_flags.rb
new file mode 100644
index 00000000000..45e6546e294
--- /dev/null
+++ b/config/initializers/0_inject_feature_flags.rb
@@ -0,0 +1,5 @@
+# This needs to be loaded after
+# config/initializers/0_inject_enterprise_edition_module.rb
+
+Feature.register_feature_groups
+Feature.register_definitions
diff --git a/config/initializers/1_postgresql_only.rb b/config/initializers/1_postgresql_only.rb
index be771bebf47..415fc6f2cae 100644
--- a/config/initializers/1_postgresql_only.rb
+++ b/config/initializers/1_postgresql_only.rb
@@ -2,3 +2,5 @@
raise "PostgreSQL is the only supported database from GitLab 12.1" unless
Gitlab::Database.postgresql?
+
+Gitlab::Database.check_postgres_version_and_print_warning
diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb
index 0afd43634c3..b7432c4cbe6 100644
--- a/config/initializers/1_settings.rb
+++ b/config/initializers/1_settings.rb
@@ -254,7 +254,7 @@ Settings.artifacts['storage_path'] = Settings.absolute(Settings.artifacts.values
# Settings.artifact['path'] is deprecated, use `storage_path` instead
Settings.artifacts['path'] = Settings.artifacts['storage_path']
Settings.artifacts['max_size'] ||= 100 # in megabytes
-Settings.artifacts['object_store'] = ObjectStoreSettings.parse(Settings.artifacts['object_store'])
+Settings.artifacts['object_store'] = ObjectStoreSettings.legacy_parse(Settings.artifacts['object_store'])
#
# Registry
@@ -325,7 +325,7 @@ Settings['external_diffs'] ||= Settingslogic.new({})
Settings.external_diffs['enabled'] = false if Settings.external_diffs['enabled'].nil?
Settings.external_diffs['when'] = 'always' if Settings.external_diffs['when'].nil?
Settings.external_diffs['storage_path'] = Settings.absolute(Settings.external_diffs['storage_path'] || File.join(Settings.shared['path'], 'external-diffs'))
-Settings.external_diffs['object_store'] = ObjectStoreSettings.parse(Settings.external_diffs['object_store'])
+Settings.external_diffs['object_store'] = ObjectStoreSettings.legacy_parse(Settings.external_diffs['object_store'])
#
# Git LFS
@@ -333,7 +333,7 @@ Settings.external_diffs['object_store'] = ObjectStoreSettings.parse(Settings.ext
Settings['lfs'] ||= Settingslogic.new({})
Settings.lfs['enabled'] = true if Settings.lfs['enabled'].nil?
Settings.lfs['storage_path'] = Settings.absolute(Settings.lfs['storage_path'] || File.join(Settings.shared['path'], "lfs-objects"))
-Settings.lfs['object_store'] = ObjectStoreSettings.parse(Settings.lfs['object_store'])
+Settings.lfs['object_store'] = ObjectStoreSettings.legacy_parse(Settings.lfs['object_store'])
#
# Uploads
@@ -341,18 +341,16 @@ Settings.lfs['object_store'] = ObjectStoreSettings.parse(Settings.lfs['object_st
Settings['uploads'] ||= Settingslogic.new({})
Settings.uploads['storage_path'] = Settings.absolute(Settings.uploads['storage_path'] || 'public')
Settings.uploads['base_dir'] = Settings.uploads['base_dir'] || 'uploads/-/system'
-Settings.uploads['object_store'] = ObjectStoreSettings.parse(Settings.uploads['object_store'])
+Settings.uploads['object_store'] = ObjectStoreSettings.legacy_parse(Settings.uploads['object_store'])
Settings.uploads['object_store']['remote_directory'] ||= 'uploads'
#
# Packages
#
-Gitlab.ee do
- Settings['packages'] ||= Settingslogic.new({})
- Settings.packages['enabled'] = true if Settings.packages['enabled'].nil?
- Settings.packages['storage_path'] = Settings.absolute(Settings.packages['storage_path'] || File.join(Settings.shared['path'], "packages"))
- Settings.packages['object_store'] = ObjectStoreSettings.parse(Settings.packages['object_store'])
-end
+Settings['packages'] ||= Settingslogic.new({})
+Settings.packages['enabled'] = true if Settings.packages['enabled'].nil?
+Settings.packages['storage_path'] = Settings.absolute(Settings.packages['storage_path'] || File.join(Settings.shared['path'], "packages"))
+Settings.packages['object_store'] = ObjectStoreSettings.legacy_parse(Settings.packages['object_store'])
#
# Dependency Proxy
@@ -361,7 +359,7 @@ Gitlab.ee do
Settings['dependency_proxy'] ||= Settingslogic.new({})
Settings.dependency_proxy['enabled'] = true if Settings.dependency_proxy['enabled'].nil?
Settings.dependency_proxy['storage_path'] = Settings.absolute(Settings.dependency_proxy['storage_path'] || File.join(Settings.shared['path'], "dependency_proxy"))
- Settings.dependency_proxy['object_store'] = ObjectStoreSettings.parse(Settings.dependency_proxy['object_store'])
+ Settings.dependency_proxy['object_store'] = ObjectStoreSettings.legacy_parse(Settings.dependency_proxy['object_store'])
# For first iteration dependency proxy uses Rails server to download blobs.
# To ensure acceptable performance we only allow feature to be used with
@@ -376,7 +374,7 @@ end
Settings['terraform_state'] ||= Settingslogic.new({})
Settings.terraform_state['enabled'] = true if Settings.terraform_state['enabled'].nil?
Settings.terraform_state['storage_path'] = Settings.absolute(Settings.terraform_state['storage_path'] || File.join(Settings.shared['path'], "terraform_state"))
-Settings.terraform_state['object_store'] = ObjectStoreSettings.parse(Settings.terraform_state['object_store'])
+Settings.terraform_state['object_store'] = ObjectStoreSettings.legacy_parse(Settings.terraform_state['object_store'])
#
# Mattermost
@@ -502,6 +500,12 @@ Settings.cron_jobs['users_create_statistics_worker']['job_class'] = 'Users::Crea
Settings.cron_jobs['authorized_project_update_periodic_recalculate_worker'] ||= Settingslogic.new({})
Settings.cron_jobs['authorized_project_update_periodic_recalculate_worker']['cron'] ||= '45 1 * * 6'
Settings.cron_jobs['authorized_project_update_periodic_recalculate_worker']['job_class'] = 'AuthorizedProjectUpdate::PeriodicRecalculateWorker'
+Settings.cron_jobs['update_container_registry_info_worker'] ||= Settingslogic.new({})
+Settings.cron_jobs['update_container_registry_info_worker']['cron'] ||= '0 0 * * *'
+Settings.cron_jobs['update_container_registry_info_worker']['job_class'] = 'UpdateContainerRegistryInfoWorker'
+Settings.cron_jobs['postgres_dynamic_partitions_creator'] ||= Settingslogic.new({})
+Settings.cron_jobs['postgres_dynamic_partitions_creator']['cron'] ||= '21 */6 * * *'
+Settings.cron_jobs['postgres_dynamic_partitions_creator']['job_class'] ||= 'PartitionCreationWorker'
Gitlab.ee do
Settings.cron_jobs['adjourned_group_deletion_worker'] ||= Settingslogic.new({})
@@ -522,9 +526,6 @@ Gitlab.ee do
Settings.cron_jobs['geo_metrics_update_worker'] ||= Settingslogic.new({})
Settings.cron_jobs['geo_metrics_update_worker']['cron'] ||= '*/1 * * * *'
Settings.cron_jobs['geo_metrics_update_worker']['job_class'] ||= 'Geo::MetricsUpdateWorker'
- Settings.cron_jobs['geo_migrated_local_files_clean_up_worker'] ||= Settingslogic.new({})
- Settings.cron_jobs['geo_migrated_local_files_clean_up_worker']['cron'] ||= '15 */6 * * *'
- Settings.cron_jobs['geo_migrated_local_files_clean_up_worker']['job_class'] ||= 'Geo::MigratedLocalFilesCleanUpWorker'
Settings.cron_jobs['geo_prune_event_log_worker'] ||= Settingslogic.new({})
Settings.cron_jobs['geo_prune_event_log_worker']['cron'] ||= '*/5 * * * *'
Settings.cron_jobs['geo_prune_event_log_worker']['job_class'] ||= 'Geo::PruneEventLogWorker'
@@ -567,12 +568,27 @@ Gitlab.ee do
Settings.cron_jobs['elastic_index_initial_bulk_cron_worker'] ||= Settingslogic.new({})
Settings.cron_jobs['elastic_index_initial_bulk_cron_worker']['cron'] ||= '*/1 * * * *'
Settings.cron_jobs['elastic_index_initial_bulk_cron_worker']['job_class'] ||= 'ElasticIndexInitialBulkCronWorker'
+ Settings.cron_jobs['elastic_cluster_reindexing_cron_worker'] ||= Settingslogic.new({})
+ Settings.cron_jobs['elastic_cluster_reindexing_cron_worker']['cron'] ||= '*/10 * * * *'
+ Settings.cron_jobs['elastic_cluster_reindexing_cron_worker']['job_class'] ||= 'ElasticClusterReindexingCronWorker'
Settings.cron_jobs['sync_seat_link_worker'] ||= Settingslogic.new({})
Settings.cron_jobs['sync_seat_link_worker']['cron'] ||= "#{rand(60)} 0 * * *"
Settings.cron_jobs['sync_seat_link_worker']['job_class'] = 'SyncSeatLinkWorker'
Settings.cron_jobs['web_application_firewall_metrics_worker'] ||= Settingslogic.new({})
Settings.cron_jobs['web_application_firewall_metrics_worker']['cron'] ||= '0 1 * * 0'
Settings.cron_jobs['web_application_firewall_metrics_worker']['job_class'] = 'IngressModsecurityCounterMetricsWorker'
+ Settings.cron_jobs['users_create_statistics_worker'] ||= Settingslogic.new({})
+ Settings.cron_jobs['users_create_statistics_worker']['cron'] ||= '2 15 * * *'
+ Settings.cron_jobs['users_create_statistics_worker']['job_class'] = 'Users::CreateStatisticsWorker'
+ Settings.cron_jobs['network_policy_metrics_worker'] ||= Settingslogic.new({})
+ Settings.cron_jobs['network_policy_metrics_worker']['cron'] ||= '0 3 * * 0'
+ Settings.cron_jobs['network_policy_metrics_worker']['job_class'] = 'NetworkPolicyMetricsWorker'
+ Settings.cron_jobs['iterations_update_status_worker'] ||= Settingslogic.new({})
+ Settings.cron_jobs['iterations_update_status_worker']['cron'] ||= '5 0 * * *'
+ Settings.cron_jobs['iterations_update_status_worker']['job_class'] = 'IterationsUpdateStatusWorker'
+ Settings.cron_jobs['vulnerability_statistics_schedule_worker'] ||= Settingslogic.new({})
+ Settings.cron_jobs['vulnerability_statistics_schedule_worker']['cron'] ||= '15 1 * * *'
+ Settings.cron_jobs['vulnerability_statistics_schedule_worker']['job_class'] = 'Vulnerabilities::Statistics::ScheduleWorker'
end
#
@@ -598,6 +614,9 @@ Settings.gitlab_shell['owner_group'] ||= Settings.gitlab.user
Settings.gitlab_shell['ssh_path_prefix'] ||= Settings.__send__(:build_gitlab_shell_ssh_path_prefix)
Settings.gitlab_shell['git_timeout'] ||= 10800
+# Object storage
+ObjectStoreSettings.new(Settings).parse!
+
#
# Workhorse
#
@@ -735,12 +754,6 @@ Settings.webpack.dev_server['host'] ||= 'localhost'
Settings.webpack.dev_server['port'] ||= 3808
#
-# ActionCable settings
-#
-Settings['action_cable'] ||= Settingslogic.new({})
-Settings.action_cable['worker_pool_size'] ||= 4
-
-#
# Monitoring settings
#
Settings['monitoring'] ||= Settingslogic.new({})
diff --git a/config/initializers/action_cable.rb b/config/initializers/action_cable.rb
index c549dd45ad9..5530e7d64a2 100644
--- a/config/initializers/action_cable.rb
+++ b/config/initializers/action_cable.rb
@@ -3,11 +3,11 @@
require 'action_cable/subscription_adapter/redis'
Rails.application.configure do
- # We only mount the ActionCable engine in tests where we run it in-app
- # For other environments, we run it on a standalone Puma server
- config.action_cable.mount_path = Rails.env.test? ? '/-/cable' : nil
+ # Mount the ActionCable engine when in-app mode is enabled
+ config.action_cable.mount_path = Gitlab::ActionCable::Config.in_app? ? '/-/cable' : nil
+
config.action_cable.url = Gitlab::Utils.append_path(Gitlab.config.gitlab.relative_url_root, '/-/cable')
- config.action_cable.worker_pool_size = Gitlab.config.action_cable.worker_pool_size
+ config.action_cable.worker_pool_size = Gitlab::ActionCable::Config.worker_pool_size
end
# https://github.com/rails/rails/blob/bb5ac1623e8de08c1b7b62b1368758f0d3bb6379/actioncable/lib/action_cable/subscription_adapter/redis.rb#L18
diff --git a/config/initializers/action_dispatch_journey_formatter.rb b/config/initializers/action_dispatch_journey_formatter.rb
index 93cf407c73c..108fb2e5012 100644
--- a/config/initializers/action_dispatch_journey_formatter.rb
+++ b/config/initializers/action_dispatch_journey_formatter.rb
@@ -9,8 +9,8 @@ module ActionDispatch
module Path
class Pattern
def requirements_for_missing_keys_check
- @requirements_for_missing_keys_check ||= requirements.each_with_object({}) do |(key, regex), hash|
- hash[key] = /\A#{regex}\Z/
+ @requirements_for_missing_keys_check ||= requirements.transform_values do |regex|
+ /\A#{regex}\Z/
end
end
end
diff --git a/config/initializers/actionpack_generate_old_csrf_token.rb b/config/initializers/actionpack_generate_old_csrf_token.rb
deleted file mode 100644
index 6367a1d4d59..00000000000
--- a/config/initializers/actionpack_generate_old_csrf_token.rb
+++ /dev/null
@@ -1,33 +0,0 @@
-# frozen_string_literal: true
-
-module Gitlab
- module RequestForgeryProtectionPatch
- private
-
- # Patch to generate 6.0.3 tokens so that we do not have CSRF errors while
- # rolling out 6.0.3.1. This enables GitLab to have a mix of 6.0.3 and
- # 6.0.3.1 Rails servers
- #
- # 1. Deploy this patch with :global_csrf_token FF disabled.
- # 2. Once all Rails servers are on 6.0.3.1, enable :global_csrf_token FF.
- # 3. On GitLab 13.2, remove this patch
- def masked_authenticity_token(session, form_options: {})
- action, method = form_options.values_at(:action, :method)
-
- raw_token = if per_form_csrf_tokens && action && method
- action_path = normalize_action_path(action)
- per_form_csrf_token(session, action_path, method)
- else
- if Feature.enabled?(:global_csrf_token)
- global_csrf_token(session)
- else
- real_csrf_token(session)
- end
- end
-
- mask_token(raw_token)
- end
- end
-end
-
-ActionController::Base.include Gitlab::RequestForgeryProtectionPatch
diff --git a/config/initializers/active_record_schema_ignore_tables.rb b/config/initializers/active_record_schema_ignore_tables.rb
index 661135f8ade..8ac565f239e 100644
--- a/config/initializers/active_record_schema_ignore_tables.rb
+++ b/config/initializers/active_record_schema_ignore_tables.rb
@@ -1,2 +1,5 @@
# Ignore table used temporarily in background migration
ActiveRecord::SchemaDumper.ignore_tables = ["untracked_files_for_uploads"]
+
+# Ignore dynamically managed partitions in static application schema
+ActiveRecord::SchemaDumper.ignore_tables += ["#{Gitlab::Database::DYNAMIC_PARTITIONS_SCHEMA}.*"]
diff --git a/config/initializers/config_initializers_active_record_locking.rb b/config/initializers/config_initializers_active_record_locking.rb
deleted file mode 100644
index 9f9908283c6..00000000000
--- a/config/initializers/config_initializers_active_record_locking.rb
+++ /dev/null
@@ -1,46 +0,0 @@
-# frozen_string_literal: true
-
-# ensure ActiveRecord's version has been required already
-require 'active_record/locking/optimistic'
-
-# rubocop:disable Lint/RescueException
-module ActiveRecord
- module Locking
- module Optimistic
- private
-
- def _update_row(attribute_names, attempted_action = "update")
- return super unless locking_enabled?
-
- begin
- locking_column = self.class.locking_column
- previous_lock_value = read_attribute_before_type_cast(locking_column)
- attribute_names << locking_column
-
- self[locking_column] += 1
-
- # Patched because when `lock_version` is read as `0`, it may actually be `NULL` in the DB.
- possible_previous_lock_value = previous_lock_value.to_i == 0 ? [nil, 0] : previous_lock_value
-
- affected_rows = self.class.unscoped.where(
- locking_column => possible_previous_lock_value,
- self.class.primary_key => id_in_database
- ).update_all(
- attributes_with_values(attribute_names)
- )
-
- if affected_rows != 1
- raise ActiveRecord::StaleObjectError.new(self, attempted_action)
- end
-
- affected_rows
-
- # If something went wrong, revert the locking_column value.
- rescue Exception
- self[locking_column] = previous_lock_value.to_i
- raise
- end
- end
- end
- end
-end
diff --git a/config/initializers/doorkeeper_openid_connect.rb b/config/initializers/doorkeeper_openid_connect.rb
index fd5a62c39c6..3523776c4f7 100644
--- a/config/initializers/doorkeeper_openid_connect.rb
+++ b/config/initializers/doorkeeper_openid_connect.rb
@@ -37,10 +37,10 @@ Doorkeeper::OpenidConnect.configure do
# public email address (if present)
# This allows existing solutions built for GitLab's old behavior to keep
# working without modification.
- o.claim(:email) do |user, scopes|
+ o.claim(:email, response: [:id_token, :user_info]) do |user, scopes|
scopes.exists?(:email) ? user.email : user.public_email
end
- o.claim(:email_verified) do |user, scopes|
+ o.claim(:email_verified, response: [:id_token, :user_info]) do |user, scopes|
if scopes.exists?(:email)
user.primary_email_verified?
elsif user.public_email?
diff --git a/config/initializers/flipper.rb b/config/initializers/flipper.rb
deleted file mode 100644
index 80cab7273e5..00000000000
--- a/config/initializers/flipper.rb
+++ /dev/null
@@ -1 +0,0 @@
-Feature.register_feature_groups
diff --git a/config/initializers/grape_patch.rb b/config/initializers/grape_patch.rb
new file mode 100644
index 00000000000..a9ac0840541
--- /dev/null
+++ b/config/initializers/grape_patch.rb
@@ -0,0 +1,31 @@
+# frozen_string_literal: true
+# Monkey patch for Grape v1.4.0: https://github.com/ruby-grape/grape/pull/2088
+
+require 'grape'
+
+# rubocop:disable Gitlab/ModuleWithInstanceVariables
+module Grape
+ module DSL
+ module InsideRoute
+ def stream(value = nil)
+ return if value.nil? && @stream.nil?
+
+ header 'Content-Length', nil
+ header 'Transfer-Encoding', nil
+ header 'Cache-Control', 'no-cache' # Skips ETag generation (reading the response up front)
+
+ if value.is_a?(String)
+ file_body = Grape::ServeStream::FileBody.new(value)
+ @stream = Grape::ServeStream::StreamResponse.new(file_body)
+ elsif value.respond_to?(:each)
+ @stream = Grape::ServeStream::StreamResponse.new(value)
+ elsif !value.is_a?(NilClass)
+ raise ArgumentError, 'Stream object must respond to :each.'
+ else
+ @stream
+ end
+ end
+ end
+ end
+end
+# rubocop:enable Gitlab/ModuleWithInstanceVariables
diff --git a/config/initializers/lograge.rb b/config/initializers/lograge.rb
index 01353ad4ec1..42c97e4aebd 100644
--- a/config/initializers/lograge.rb
+++ b/config/initializers/lograge.rb
@@ -15,6 +15,7 @@ unless Gitlab::Runtime.sidekiq?
data[:db_duration_s] = Gitlab::Utils.ms_to_round_sec(data.delete(:db)) if data[:db]
data[:view_duration_s] = Gitlab::Utils.ms_to_round_sec(data.delete(:view)) if data[:view]
data[:duration_s] = Gitlab::Utils.ms_to_round_sec(data.delete(:duration)) if data[:duration]
+ data.merge!(::Gitlab::Metrics::Subscribers::ActiveRecord.db_counter_payload)
data
end
diff --git a/config/initializers/multi_json.rb b/config/initializers/multi_json.rb
new file mode 100644
index 00000000000..93a81d8320d
--- /dev/null
+++ b/config/initializers/multi_json.rb
@@ -0,0 +1,5 @@
+# frozen_string_literal: true
+
+# Explicitly set the JSON adapter used by MultiJson
+# Currently we want this to default to the existing json gem
+MultiJson.use(:json_gem)
diff --git a/config/initializers/oj.rb b/config/initializers/oj.rb
new file mode 100644
index 00000000000..3fa26259fc6
--- /dev/null
+++ b/config/initializers/oj.rb
@@ -0,0 +1,4 @@
+# frozen_string_literal: true
+
+# Ensure Oj runs in json-gem compatibility mode by default
+Oj.default_options = { mode: :rails }
diff --git a/config/initializers/postgres_partitioning.rb b/config/initializers/postgres_partitioning.rb
new file mode 100644
index 00000000000..6c8a72d9bd5
--- /dev/null
+++ b/config/initializers/postgres_partitioning.rb
@@ -0,0 +1,10 @@
+# frozen_string_literal: true
+
+# Make sure we have loaded partitioned models here
+# (even with eager loading disabled).
+
+begin
+ Gitlab::Database::Partitioning::PartitionCreator.new.create_partitions
+rescue ActiveRecord::ActiveRecordError, PG::Error
+ # ignore - happens when Rake tasks yet have to create a database, e.g. for testing
+end
diff --git a/config/initializers/rack_attack.rb b/config/initializers/rack_attack.rb
index 51b49bec864..b0778633199 100644
--- a/config/initializers/rack_attack.rb
+++ b/config/initializers/rack_attack.rb
@@ -68,6 +68,15 @@ class Rack::Attack
end
end
+ # Product analytics feature is in experimental stage.
+ # At this point we want to limit amount of events registered
+ # per application (aid stands for application id).
+ throttle('throttle_product_analytics_collector', limit: 100, period: 60) do |req|
+ if req.product_analytics_collector_request?
+ req.params['aid']
+ end
+ end
+
throttle('throttle_authenticated_web', Gitlab::Throttle.authenticated_web_options) do |req|
if req.web_request? &&
Gitlab::Throttle.settings.throttle_authenticated_web_enabled
@@ -128,6 +137,10 @@ class Rack::Attack
path =~ %r{^/-/(health|liveness|readiness)}
end
+ def product_analytics_collector_request?
+ path.start_with?('/-/collector/i')
+ end
+
def should_be_skipped?
api_internal_request? || health_check_request?
end
diff --git a/config/initializers/rack_timeout.rb b/config/initializers/rack_timeout.rb
index 5d5a5fcf980..e217398ee7d 100644
--- a/config/initializers/rack_timeout.rb
+++ b/config/initializers/rack_timeout.rb
@@ -10,8 +10,6 @@
# logged and we should fix the potential timeout issue in the code itself.
if Gitlab::Runtime.puma? && !Rails.env.test?
- require 'rack/timeout/base'
-
Rack::Timeout::Logger.level = Logger::ERROR
Gitlab::Application.configure do |config|
diff --git a/config/initializers/stackprof.rb b/config/initializers/stackprof.rb
new file mode 100644
index 00000000000..5497ff9a459
--- /dev/null
+++ b/config/initializers/stackprof.rb
@@ -0,0 +1,101 @@
+# frozen_string_literal: true
+
+# trigger stackprof by sending a SIGUSR2 signal
+#
+# default settings:
+# * collect raw samples
+# * sample at 100hz (every 10k microseconds)
+# * timeout profile after 30 seconds
+# * write to $TMPDIR/stackprof.$PID.$RAND.profile
+
+if Gitlab::Utils.to_boolean(ENV['STACKPROF_ENABLED'].to_s)
+ Gitlab::Cluster::LifecycleEvents.on_worker_start do
+ require 'stackprof'
+ require 'tmpdir'
+
+ Gitlab::AppJsonLogger.info "stackprof: listening on SIGUSR2 signal"
+
+ # create a pipe in order to propagate signal out of the signal handler
+ # see also: https://cr.yp.to/docs/selfpipe.html
+ read, write = IO.pipe
+
+ # create a separate thread that polls for signals on the pipe.
+ #
+ # this way we do not execute in signal handler context, which
+ # lifts restrictions and also serializes the calls in a thread-safe
+ # manner.
+ #
+ # it's very similar to a goroutine and channel design.
+ #
+ # another nice benefit of this method is that we can timeout the
+ # IO.select call, allowing the profile to automatically stop after
+ # a given interval (by default 30 seconds), avoiding unbounded memory
+ # growth from a profile that was started and never stopped.
+ t = Thread.new do
+ timeout_s = ENV['STACKPROF_TIMEOUT_S']&.to_i || 30
+ current_timeout_s = nil
+ loop do
+ got_value = IO.select([read], nil, nil, current_timeout_s)
+ read.getbyte if got_value
+
+ if StackProf.running?
+ stackprof_file_prefix = ENV['STACKPROF_FILE_PREFIX'] || Dir.tmpdir
+ stackprof_out_file = "#{stackprof_file_prefix}/stackprof.#{Process.pid}.#{SecureRandom.hex(6)}.profile"
+
+ Gitlab::AppJsonLogger.info(
+ event: "stackprof",
+ message: "stopping profile",
+ output_filename: stackprof_out_file,
+ pid: Process.pid,
+ timeout_s: timeout_s,
+ timed_out: got_value.nil?
+ )
+
+ StackProf.stop
+ StackProf.results(stackprof_out_file)
+ current_timeout_s = nil
+ else
+ Gitlab::AppJsonLogger.info(
+ event: "stackprof",
+ message: "starting profile",
+ pid: Process.pid
+ )
+
+ StackProf.start(
+ mode: :cpu,
+ raw: Gitlab::Utils.to_boolean(ENV['STACKPROF_RAW'] || 'true'),
+ interval: ENV['STACKPROF_INTERVAL_US']&.to_i || 10_000
+ )
+ current_timeout_s = timeout_s
+ end
+ end
+ end
+ t.abort_on_exception = true
+
+ # in the case of puma, this will override the existing SIGUSR2 signal handler
+ # that can be used to trigger a restart.
+ #
+ # puma cluster has two types of restarts:
+ # * SIGUSR1: phased restart
+ # * SIGUSR2: restart
+ #
+ # phased restart is not supported in our configuration, because we use
+ # preload_app. this means we will always perform a normal restart.
+ # additionally, phased restart is not supported when sending a SIGUSR2
+ # directly to a puma worker (as opposed to the master process).
+ #
+ # the result is that the behaviour of SIGUSR1 and SIGUSR2 is identical in
+ # our configuration, and we can always use a SIGUSR1 to perform a restart.
+ #
+ # thus, it is acceptable for us to re-appropriate the SIGUSR2 signal, and
+ # override the puma behaviour.
+ #
+ # see also:
+ # * https://github.com/puma/puma/blob/master/docs/signals.md#puma-signals
+ # * https://github.com/phusion/unicorn/blob/master/SIGNALS
+ # * https://github.com/mperham/sidekiq/wiki/Signals
+ Signal.trap('SIGUSR2') do
+ write.write('.')
+ end
+ end
+end
diff --git a/config/initializers_before_autoloader/000_inflections.rb b/config/initializers_before_autoloader/000_inflections.rb
index 1fabce9a57e..5c1a3e87fba 100644
--- a/config/initializers_before_autoloader/000_inflections.rb
+++ b/config/initializers_before_autoloader/000_inflections.rb
@@ -11,6 +11,7 @@
#
ActiveSupport::Inflector.inflections do |inflect|
inflect.uncountable %w(
+ custom_emoji
award_emoji
container_repository_registry
design_registry
diff --git a/config/karma.config.js b/config/karma.config.js
index 97794225a3f..31fdd5bffd1 100644
--- a/config/karma.config.js
+++ b/config/karma.config.js
@@ -181,7 +181,7 @@ module.exports = function(config) {
if (process.env.BABEL_ENV === 'coverage' || process.env.NODE_ENV === 'coverage') {
karmaConfig.reporters.push('coverage-istanbul');
karmaConfig.coverageIstanbulReporter = {
- reports: ['html', 'text-summary'],
+ reports: ['html', 'text-summary', 'cobertura'],
dir: 'coverage-javascript/',
subdir: '.',
fixWebpackSourcePaths: true,
diff --git a/config/locales/devise.en.yml b/config/locales/devise.en.yml
index bd4c3ebc69e..ee75ffbb8e9 100644
--- a/config/locales/devise.en.yml
+++ b/config/locales/devise.en.yml
@@ -15,7 +15,7 @@ en:
not_found_in_database: "Invalid %{authentication_keys} or password."
timeout: "Your session expired. Please sign in again to continue."
unauthenticated: "You need to sign in or sign up before continuing."
- unconfirmed: "You have to confirm your email address before continuing."
+ unconfirmed: "You have to confirm your email address before continuing. Please check your email for the link we sent you, or click 'Resend confirmation email'."
mailer:
confirmation_instructions:
subject: "Confirmation instructions"
diff --git a/config/locales/en.yml b/config/locales/en.yml
index ed0552ab452..7ff4e3bf7da 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -22,6 +22,8 @@ en:
grafana_enabled: "Grafana integration enabled"
user/user_detail:
job_title: 'Job title'
+ user/user_detail:
+ bio: 'Bio'
views:
pagination:
previous: "Prev"
diff --git a/config/object_store_settings.rb b/config/object_store_settings.rb
index d85ff394dcc..d8e1939a346 100644
--- a/config/object_store_settings.rb
+++ b/config/object_store_settings.rb
@@ -1,6 +1,12 @@
# Set default values for object_store settings
class ObjectStoreSettings
- def self.parse(object_store)
+ SUPPORTED_TYPES = %w(artifacts external_diffs lfs uploads packages dependency_proxy terraform_state).freeze
+ ALLOWED_OBJECT_STORE_OVERRIDES = %w(bucket enabled proxy_download).freeze
+
+ attr_accessor :settings
+
+ # Legacy parser
+ def self.legacy_parse(object_store)
object_store ||= Settingslogic.new({})
object_store['enabled'] = false if object_store['enabled'].nil?
object_store['remote_directory'] ||= nil
@@ -12,4 +18,128 @@ class ObjectStoreSettings
object_store['connection']&.deep_stringify_keys!
object_store
end
+
+ def initialize(settings)
+ @settings = settings
+ end
+
+ # This method converts the common object storage settings to
+ # the legacy, internal representation.
+ #
+ # For example, with the folowing YAML:
+ #
+ # object_store:
+ # enabled: true
+ # connection:
+ # provider: AWS
+ # aws_access_key_id: minio
+ # aws_secret_access_key: gdk-minio
+ # region: gdk
+ # endpoint: 'http://127.0.0.1:9000'
+ # path_style: true
+ # proxy_download: true
+ # objects:
+ # artifacts:
+ # bucket: artifacts
+ # proxy_download: false
+ # lfs:
+ # bucket: lfs-objects
+ #
+ # This method then will essentially call:
+ #
+ # Settings.artifacts['object_store'] = {
+ # "enabled" => true,
+ # "connection"=> {
+ # "provider" => "AWS",
+ # "aws_access_key_id" => "minio",
+ # "aws_secret_access_key" => "gdk-minio",
+ # "region" => "gdk",
+ # "endpoint" => "http://127.0.0.1:9000",
+ # "path_style" => true
+ # },
+ # "direct_upload" => true,
+ # "background_upload" => false,
+ # "proxy_download" => false,
+ # "remote_directory" => "artifacts"
+ # }
+ #
+ # Settings.lfs['object_store'] = {
+ # "enabled" => true,
+ # "connection" => {
+ # "provider" => "AWS",
+ # "aws_access_key_id" => "minio",
+ # "aws_secret_access_key" => "gdk-minio",
+ # "region" => "gdk",
+ # "endpoint" => "http://127.0.0.1:9000",
+ # "path_style" => true
+ # },
+ # "direct_upload" => true,
+ # "background_upload" => false,
+ # "proxy_download" => true,
+ # "remote_directory" => "lfs-objects"
+ # }
+ #
+ # Note that with the common config:
+ # 1. Only one object store credentials can now be used. This is
+ # necessary to limit configuration overhead when an object storage
+ # client (e.g. AWS S3) is used inside GitLab Workhorse.
+ # 2. However, a bucket has to be specified for each object
+ # type. Reusing buckets is not really supported, but we don't
+ # enforce that yet.
+ # 3. direct_upload and background_upload cannot be configured anymore.
+ def parse!
+ return unless use_consolidated_settings?
+
+ main_config = settings['object_store']
+ common_config = main_config.slice('enabled', 'connection', 'proxy_download')
+ # Convert connection settings to use string keys, to make Fog happy
+ common_config['connection']&.deep_stringify_keys!
+ # These are no longer configurable if common config is used
+ common_config['direct_upload'] = true
+ common_config['background_upload'] = false
+
+ SUPPORTED_TYPES.each do |store_type|
+ overrides = main_config.dig('objects', store_type) || {}
+ target_config = common_config.merge(overrides.slice(*ALLOWED_OBJECT_STORE_OVERRIDES))
+ section = settings.try(store_type)
+
+ next unless section
+
+ raise "Object storage for #{store_type} must have a bucket specified" if section['enabled'] && target_config['bucket'].blank?
+
+ # Map bucket (external name) -> remote_directory (internal representation)
+ target_config['remote_directory'] = target_config.delete('bucket')
+ target_config['consolidated_settings'] = true
+ section['object_store'] = target_config
+ end
+ end
+
+ private
+
+ # We only can use the common object storage settings if:
+ # 1. The common settings are defined
+ # 2. The legacy settings are not defined
+ def use_consolidated_settings?
+ return false unless settings.dig('object_store', 'enabled')
+ return false unless settings.dig('object_store', 'connection').present?
+
+ SUPPORTED_TYPES.each do |store|
+ # to_h is needed because something strange happens to
+ # Settingslogic#dig when stub_storage_settings is run in tests:
+ #
+ # (byebug) section.dig
+ # *** ArgumentError Exception: wrong number of arguments (given 0, expected 1+)
+ # (byebug) section.dig('object_store')
+ # *** ArgumentError Exception: wrong number of arguments (given 1, expected 0)
+ section = settings.try(store)&.to_h
+
+ next unless section
+
+ return false if section.dig('object_store', 'enabled')
+ # Omnibus defaults to an empty hash
+ return false if section.dig('object_store', 'connection').present?
+ end
+
+ true
+ end
end
diff --git a/config/plugins/monaco_webpack.js b/config/plugins/monaco_webpack.js
new file mode 100644
index 00000000000..7d283782453
--- /dev/null
+++ b/config/plugins/monaco_webpack.js
@@ -0,0 +1,17 @@
+const { languagesArr } = require('monaco-editor-webpack-plugin/out/languages');
+
+// monaco-yaml library doesn't play so well with monaco-editor-webpack-plugin
+// so the only way to include its workers is by patching the list of languages
+// in monaco-editor-webpack-plugin and adding support for yaml workers. This is
+// a known issue in the library and this workaround was suggested here:
+// https://github.com/pengx17/monaco-yaml/issues/20
+
+const yamlLang = languagesArr.find(t => t.label === 'yaml');
+
+yamlLang.entry = [yamlLang.entry, '../../monaco-yaml/esm/monaco.contribution'];
+yamlLang.worker = {
+ id: 'vs/language/yaml/yamlWorker',
+ entry: '../../monaco-yaml/esm/yaml.worker.js',
+};
+
+module.exports = require('monaco-editor-webpack-plugin');
diff --git a/config/prometheus/cluster_metrics.yml b/config/prometheus/cluster_metrics.yml
index f2a41e4c337..1e396f4bbbd 100644
--- a/config/prometheus/cluster_metrics.yml
+++ b/config/prometheus/cluster_metrics.yml
@@ -1,63 +1,40 @@
+dashboard: 'Cluster health'
+priority: 1
+panel_groups:
- group: Cluster Health
- priority: 1
- metrics:
+ priority: 10
+ panels:
- title: "CPU Usage"
+ type: "area-chart"
y_label: "CPU (cores)"
- required_metrics: ['container_cpu_usage_seconds_total']
weight: 1
- queries:
- - query_range: 'avg(sum(rate(container_cpu_usage_seconds_total{id="/"}[15m])) by (job)) without (job)'
- label: Usage (cores)
- unit: "cores"
- appearance:
- line:
- width: 2
- area:
- opacity: 0
- - query_range: 'sum(kube_pod_container_resource_requests_cpu_cores{kubernetes_namespace="gitlab-managed-apps"})'
- label: Requested (cores)
- unit: "cores"
- appearance:
- line:
- width: 2
- area:
- opacity: 0
- - query_range: 'sum(kube_node_status_capacity_cpu_cores{kubernetes_namespace="gitlab-managed-apps"})'
- label: Capacity (cores)
- unit: "cores"
- appearance:
- line:
- type: 'dashed'
- width: 2
- area:
- opacity: 0
- - title: "Memory usage"
+ metrics:
+ - id: cluster_health_cpu_usage
+ query_range: 'avg(sum(rate(container_cpu_usage_seconds_total{id="/"}[15m])) by (job)) without (job)'
+ unit: cores
+ label: Usage (cores)
+ - id: cluster_health_cpu_requested
+ query_range: 'sum(kube_pod_container_resource_requests_cpu_cores{kubernetes_namespace="gitlab-managed-apps"})'
+ unit: cores
+ label: Requested (cores)
+ - id: cluster_health_cpu_capacity
+ query_range: 'sum(kube_node_status_capacity_cpu_cores{kubernetes_namespace="gitlab-managed-apps"})'
+ unit: cores
+ label: Capacity (cores)
+ - title: "Memory Usage"
+ type: "area-chart"
y_label: "Memory (GiB)"
- required_metrics: ['container_memory_usage_bytes']
weight: 1
- queries:
- - query_range: 'avg(sum(container_memory_usage_bytes{id="/"}) by (job)) without (job) / 2^30'
- label: Usage (GiB)
- unit: "GiB"
- appearance:
- line:
- width: 2
- area:
- opacity: 0
- - query_range: 'sum(kube_pod_container_resource_requests_memory_bytes{kubernetes_namespace="gitlab-managed-apps"})/2^30'
- label: Requested (GiB)
- unit: "GiB"
- appearance:
- line:
- width: 2
- area:
- opacity: 0
- - query_range: 'sum(kube_node_status_capacity_memory_bytes{kubernetes_namespace="gitlab-managed-apps"})/2^30'
- label: Capacity (GiB)
- unit: "GiB"
- appearance:
- line:
- type: 'dashed'
- width: 2
- area:
- opacity: 0
+ metrics:
+ - id: cluster_health_memory_usage
+ query_range: 'avg(sum(container_memory_usage_bytes{id="/"}) by (job)) without (job) / 2^30'
+ unit: GiB
+ label: Usage (GiB)
+ - id: cluster_health_memory_requested
+ query_range: 'sum(kube_pod_container_resource_requests_memory_bytes{kubernetes_namespace="gitlab-managed-apps"})/2^30'
+ unit: GiB
+ label: Requested (GiB)
+ - id: cluster_health_memory_capacity
+ query_range: 'sum(kube_node_status_capacity_memory_bytes{kubernetes_namespace="gitlab-managed-apps"})/2^30'
+ unit: GiB
+ label: Capacity (GiB)
diff --git a/config/prometheus/common_metrics.yml b/config/prometheus/common_metrics.yml
index f0491df3db9..d9aaff12a4d 100644
--- a/config/prometheus/common_metrics.yml
+++ b/config/prometheus/common_metrics.yml
@@ -10,7 +10,9 @@ panel_groups:
weight: 4
metrics:
- id: system_metrics_kubernetes_container_memory_total
- query_range: 'avg(sum(container_memory_usage_bytes{container_name!="POD",pod_name=~"^{{ci_environment_slug}}-(.*)",namespace="{{kube_namespace}}"}) by (job)) without (job) /1024/1024/1024'
+ # Remove the second metric (after OR) when we drop support for K8s 1.13
+ # https://gitlab.com/gitlab-org/gitlab/-/issues/229279
+ query_range: 'avg(sum(container_memory_usage_bytes{container!="POD",pod=~"^{{ci_environment_slug}}-(.*)",namespace="{{kube_namespace}}"}) by (job)) without (job) /1024/1024/1024 OR avg(sum(container_memory_usage_bytes{container_name!="POD",pod_name=~"^{{ci_environment_slug}}-(.*)",namespace="{{kube_namespace}}"}) by (job)) without (job) /1024/1024/1024'
label: Total (GB)
unit: GB
- title: "Core Usage (Total)"
@@ -19,7 +21,9 @@ panel_groups:
weight: 3
metrics:
- id: system_metrics_kubernetes_container_cores_total
- query_range: 'avg(sum(rate(container_cpu_usage_seconds_total{container_name!="POD",pod_name=~"^{{ci_environment_slug}}-(.*)",namespace="{{kube_namespace}}"}[15m])) by (job)) without (job)'
+ # Remove the second metric (after OR) when we drop support for K8s 1.13
+ # https://gitlab.com/gitlab-org/gitlab/-/issues/229279
+ query_range: 'avg(sum(rate(container_cpu_usage_seconds_total{container!="POD",pod=~"^{{ci_environment_slug}}-(.*)",namespace="{{kube_namespace}}"}[15m])) by (job)) without (job) OR avg(sum(rate(container_cpu_usage_seconds_total{container_name!="POD",pod_name=~"^{{ci_environment_slug}}-(.*)",namespace="{{kube_namespace}}"}[15m])) by (job)) without (job)'
label: Total (cores)
unit: "cores"
- title: "Memory Usage (Pod average)"
@@ -28,7 +32,9 @@ panel_groups:
weight: 2
metrics:
- id: system_metrics_kubernetes_container_memory_average
- query_range: 'avg(sum(container_memory_usage_bytes{container_name!="POD",pod_name=~"^{{ci_environment_slug}}-([^c].*|c([^a]|a([^n]|n([^a]|a([^r]|r[^y])))).*|)-(.*)",namespace="{{kube_namespace}}"}) by (job)) without (job) / count(avg(container_memory_usage_bytes{container_name!="POD",pod_name=~"^{{ci_environment_slug}}-([^c].*|c([^a]|a([^n]|n([^a]|a([^r]|r[^y])))).*|)-(.*)",namespace="{{kube_namespace}}"}) without (job)) /1024/1024'
+ # Remove the second metric (after OR) when we drop support for K8s 1.13
+ # https://gitlab.com/gitlab-org/gitlab/-/issues/229279
+ query_range: 'avg(sum(container_memory_usage_bytes{container!="POD",pod=~"^{{ci_environment_slug}}-([^c].*|c([^a]|a([^n]|n([^a]|a([^r]|r[^y])))).*|)-(.*)",namespace="{{kube_namespace}}"}) by (job)) without (job) / count(avg(container_memory_usage_bytes{container!="POD",pod=~"^{{ci_environment_slug}}-([^c].*|c([^a]|a([^n]|n([^a]|a([^r]|r[^y])))).*|)-(.*)",namespace="{{kube_namespace}}"}) without (job)) /1024/1024 OR avg(sum(container_memory_usage_bytes{container_name!="POD",pod_name=~"^{{ci_environment_slug}}-([^c].*|c([^a]|a([^n]|n([^a]|a([^r]|r[^y])))).*|)-(.*)",namespace="{{kube_namespace}}"}) by (job)) without (job) / count(avg(container_memory_usage_bytes{container_name!="POD",pod_name=~"^{{ci_environment_slug}}-([^c].*|c([^a]|a([^n]|n([^a]|a([^r]|r[^y])))).*|)-(.*)",namespace="{{kube_namespace}}"}) without (job)) /1024/1024'
label: Pod average (MB)
unit: MB
- title: "Canary: Memory Usage (Pod Average)"
@@ -37,7 +43,9 @@ panel_groups:
weight: 2
metrics:
- id: system_metrics_kubernetes_container_memory_average_canary
- query_range: 'avg(sum(container_memory_usage_bytes{container_name!="POD",pod_name=~"^{{ci_environment_slug}}-canary-(.*)",namespace="{{kube_namespace}}"}) by (job)) without (job) / count(avg(container_memory_usage_bytes{container_name!="POD",pod_name=~"^{{ci_environment_slug}}-canary-(.*)",namespace="{{kube_namespace}}"}) without (job)) /1024/1024'
+ # Remove the second metric (after OR) when we drop support for K8s 1.13
+ # https://gitlab.com/gitlab-org/gitlab/-/issues/229279
+ query_range: 'avg(sum(container_memory_usage_bytes{container!="POD",pod=~"^{{ci_environment_slug}}-canary-(.*)",namespace="{{kube_namespace}}"}) by (job)) without (job) / count(avg(container_memory_usage_bytes{container!="POD",pod=~"^{{ci_environment_slug}}-canary-(.*)",namespace="{{kube_namespace}}"}) without (job)) /1024/1024 OR avg(sum(container_memory_usage_bytes{container_name!="POD",pod_name=~"^{{ci_environment_slug}}-canary-(.*)",namespace="{{kube_namespace}}"}) by (job)) without (job) / count(avg(container_memory_usage_bytes{container_name!="POD",pod_name=~"^{{ci_environment_slug}}-canary-(.*)",namespace="{{kube_namespace}}"}) without (job)) /1024/1024'
label: Pod average (MB)
unit: MB
track: canary
@@ -47,7 +55,9 @@ panel_groups:
weight: 1
metrics:
- id: system_metrics_kubernetes_container_core_usage
- query_range: 'avg(sum(rate(container_cpu_usage_seconds_total{container_name!="POD",pod_name=~"^{{ci_environment_slug}}-([^c].*|c([^a]|a([^n]|n([^a]|a([^r]|r[^y])))).*|)-(.*)",namespace="{{kube_namespace}}"}[15m])) by (job)) without (job) / count(sum(rate(container_cpu_usage_seconds_total{container_name!="POD",pod_name=~"^{{ci_environment_slug}}-([^c].*|c([^a]|a([^n]|n([^a]|a([^r]|r[^y])))).*|)-(.*)",namespace="{{kube_namespace}}"}[15m])) by (pod_name))'
+ # Remove the second metric (after OR) when we drop support for K8s 1.13
+ # https://gitlab.com/gitlab-org/gitlab/-/issues/229279
+ query_range: 'avg(sum(rate(container_cpu_usage_seconds_total{container!="POD",pod=~"^{{ci_environment_slug}}-([^c].*|c([^a]|a([^n]|n([^a]|a([^r]|r[^y])))).*|)-(.*)",namespace="{{kube_namespace}}"}[15m])) by (job)) without (job) / count(sum(rate(container_cpu_usage_seconds_total{container!="POD",pod=~"^{{ci_environment_slug}}-([^c].*|c([^a]|a([^n]|n([^a]|a([^r]|r[^y])))).*|)-(.*)",namespace="{{kube_namespace}}"}[15m])) by (pod)) OR avg(sum(rate(container_cpu_usage_seconds_total{container_name!="POD",pod_name=~"^{{ci_environment_slug}}-([^c].*|c([^a]|a([^n]|n([^a]|a([^r]|r[^y])))).*|)-(.*)",namespace="{{kube_namespace}}"}[15m])) by (job)) without (job) / count(sum(rate(container_cpu_usage_seconds_total{container_name!="POD",pod_name=~"^{{ci_environment_slug}}-([^c].*|c([^a]|a([^n]|n([^a]|a([^r]|r[^y])))).*|)-(.*)",namespace="{{kube_namespace}}"}[15m])) by (pod_name))'
label: Pod average (cores)
unit: "cores"
- title: "Canary: Core Usage (Pod Average)"
@@ -56,7 +66,9 @@ panel_groups:
weight: 1
metrics:
- id: system_metrics_kubernetes_container_core_usage_canary
- query_range: 'avg(sum(rate(container_cpu_usage_seconds_total{container_name!="POD",pod_name=~"^{{ci_environment_slug}}-canary-(.*)",namespace="{{kube_namespace}}"}[15m])) by (job)) without (job) / count(sum(rate(container_cpu_usage_seconds_total{container_name!="POD",pod_name=~"^{{ci_environment_slug}}-canary-(.*)",namespace="{{kube_namespace}}"}[15m])) by (pod_name))'
+ # Remove the second metric (after OR) when we drop support for K8s 1.13
+ # https://gitlab.com/gitlab-org/gitlab/-/issues/229279
+ query_range: 'avg(sum(rate(container_cpu_usage_seconds_total{container!="POD",pod=~"^{{ci_environment_slug}}-canary-(.*)",namespace="{{kube_namespace}}"}[15m])) by (job)) without (job) / count(sum(rate(container_cpu_usage_seconds_total{container!="POD",pod=~"^{{ci_environment_slug}}-canary-(.*)",namespace="{{kube_namespace}}"}[15m])) by (pod)) OR avg(sum(rate(container_cpu_usage_seconds_total{container_name!="POD",pod_name=~"^{{ci_environment_slug}}-canary-(.*)",namespace="{{kube_namespace}}"}[15m])) by (job)) without (job) / count(sum(rate(container_cpu_usage_seconds_total{container_name!="POD",pod_name=~"^{{ci_environment_slug}}-canary-(.*)",namespace="{{kube_namespace}}"}[15m])) by (pod_name))'
label: Pod average (cores)
unit: "cores"
track: canary
diff --git a/config/prometheus/queries_cluster_metrics.yml b/config/prometheus/queries_cluster_metrics.yml
new file mode 100644
index 00000000000..bec3ba22d83
--- /dev/null
+++ b/config/prometheus/queries_cluster_metrics.yml
@@ -0,0 +1,65 @@
+# most likely this file can be removed, but until we are sure and have capacity to tackle that I've
+# only moved it and added https://gitlab.com/gitlab-org/gitlab/-/issues/225869 to track work need to clean up codebase.
+- group: Cluster Health
+ priority: 1
+ metrics:
+ - title: "CPU Usage"
+ y_label: "CPU (cores)"
+ required_metrics: ['container_cpu_usage_seconds_total']
+ weight: 1
+ queries:
+ - query_range: 'avg(sum(rate(container_cpu_usage_seconds_total{id="/"}[15m])) by (job)) without (job)'
+ label: Usage (cores)
+ unit: "cores"
+ appearance:
+ line:
+ width: 2
+ area:
+ opacity: 0
+ - query_range: 'sum(kube_pod_container_resource_requests_cpu_cores{kubernetes_namespace="gitlab-managed-apps"})'
+ label: Requested (cores)
+ unit: "cores"
+ appearance:
+ line:
+ width: 2
+ area:
+ opacity: 0
+ - query_range: 'sum(kube_node_status_capacity_cpu_cores{kubernetes_namespace="gitlab-managed-apps"})'
+ label: Capacity (cores)
+ unit: "cores"
+ appearance:
+ line:
+ type: 'dashed'
+ width: 2
+ area:
+ opacity: 0
+ - title: "Memory usage"
+ y_label: "Memory (GiB)"
+ required_metrics: ['container_memory_usage_bytes']
+ weight: 1
+ queries:
+ - query_range: 'avg(sum(container_memory_usage_bytes{id="/"}) by (job)) without (job) / 2^30'
+ label: Usage (GiB)
+ unit: "GiB"
+ appearance:
+ line:
+ width: 2
+ area:
+ opacity: 0
+ - query_range: 'sum(kube_pod_container_resource_requests_memory_bytes{kubernetes_namespace="gitlab-managed-apps"})/2^30'
+ label: Requested (GiB)
+ unit: "GiB"
+ appearance:
+ line:
+ width: 2
+ area:
+ opacity: 0
+ - query_range: 'sum(kube_node_status_capacity_memory_bytes{kubernetes_namespace="gitlab-managed-apps"})/2^30'
+ label: Capacity (GiB)
+ unit: "GiB"
+ appearance:
+ line:
+ type: 'dashed'
+ width: 2
+ area:
+ opacity: 0
diff --git a/config/routes.rb b/config/routes.rb
index 598a52cddb3..dd84bc859bb 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -1,5 +1,6 @@
require 'sidekiq/web'
require 'sidekiq/cron/web'
+require 'product_analytics/collector_app'
Rails.application.routes.draw do
concern :access_requestable do
@@ -58,6 +59,7 @@ Rails.application.routes.draw do
# Search
get 'search' => 'search#show'
+ get 'search/autocomplete' => 'search#autocomplete', as: :search_autocomplete
get 'search/count' => 'search#count', as: :search_count
# JSON Web Token
@@ -75,6 +77,7 @@ Rails.application.routes.draw do
get '/autocomplete/projects' => 'autocomplete#projects'
get '/autocomplete/award_emojis' => 'autocomplete#award_emojis'
get '/autocomplete/merge_request_target_branches' => 'autocomplete#merge_request_target_branches'
+ get '/autocomplete/deploy_keys_with_owners' => 'autocomplete#deploy_keys_with_owners'
Gitlab.ee do
get '/autocomplete/project_groups' => 'autocomplete#project_groups'
@@ -174,6 +177,9 @@ Rails.application.routes.draw do
# Used by third parties to verify CI_JOB_JWT, placeholder route
# in case we decide to move away from doorkeeper-openid_connect
get 'jwks' => 'doorkeeper/openid_connect/discovery#keys'
+
+ # Product analytics collector
+ match '/collector/i', to: ProductAnalytics::CollectorApp.new, via: :all
end
# End of the /-/ scope.
@@ -189,8 +195,6 @@ Rails.application.routes.draw do
member do
Gitlab.ee do
get :metrics, format: :json
- get :metrics_dashboard
- get :'/prometheus/api/v1/*proxy_path', to: 'clusters#prometheus_proxy', as: :prometheus_api
get :environments, format: :json
end
@@ -200,6 +204,8 @@ Rails.application.routes.draw do
delete '/:application', to: 'clusters/applications#destroy', as: :uninstall_applications
end
+ get :metrics_dashboard
+ get :'/prometheus/api/v1/*proxy_path', to: 'clusters#prometheus_proxy', as: :prometheus_api
get :cluster_status, format: :json
delete :clear_cache
end
@@ -242,6 +248,8 @@ Rails.application.routes.draw do
post :preview_markdown
end
+ draw :group
+
resources :projects, only: [:index, :new, :create]
get '/projects/:id' => 'projects#resolve'
@@ -258,10 +266,17 @@ Rails.application.routes.draw do
draw :admin
draw :profile
draw :dashboard
- draw :group
draw :user
draw :project
+ # Serve snippet routes under /-/snippets.
+ # To ensure an old unscoped routing is used for the UI we need to
+ # add prefix 'as' to the scope routing and place it below original routing.
+ # Issue https://gitlab.com/gitlab-org/gitlab/-/issues/210024
+ scope '-', as: :scoped do
+ draw :snippets
+ end
+
root to: "root#index"
get '*unmatched_route', to: 'application#route_not_found'
diff --git a/config/routes/import.rb b/config/routes/import.rb
index cd8278f6fd0..1dc27d489f0 100644
--- a/config/routes/import.rb
+++ b/config/routes/import.rb
@@ -24,14 +24,12 @@ namespace :import do
resource :gitlab, only: [:create], controller: :gitlab do
get :status
get :callback
- get :jobs
get :realtime_changes
end
resource :bitbucket, only: [:create], controller: :bitbucket do
get :status
get :callback
- get :jobs
get :realtime_changes
end
@@ -39,7 +37,6 @@ namespace :import do
post :configure
get :status
get :callback
- get :jobs
get :realtime_changes
end
@@ -55,7 +52,6 @@ namespace :import do
resource :fogbugz, only: [:create, :new], controller: :fogbugz do
get :status
post :callback
- get :jobs
get :realtime_changes
get :new_user_map, path: :user_map
diff --git a/config/routes/issues.rb b/config/routes/issues.rb
index 04a935c1016..eae1cacfcf7 100644
--- a/config/routes/issues.rb
+++ b/config/routes/issues.rb
@@ -17,6 +17,7 @@ resources :issues, concerns: :awardable, constraints: { id: /\d+/ } do
end
collection do
+ get :service_desk
post :bulk_update
post :import_csv
post :export_csv
diff --git a/config/routes/pipelines.rb b/config/routes/pipelines.rb
index cc3c3400526..c7f9bf8791c 100644
--- a/config/routes/pipelines.rb
+++ b/config/routes/pipelines.rb
@@ -22,9 +22,13 @@ resources :pipelines, only: [:index, :new, :create, :show, :destroy] do
get :test_reports_count
end
- member do
- resources :stages, only: [], param: :name do
- post :play_manual
+ resources :stages, only: [], param: :name, controller: 'pipelines/stages' do
+ post :play_manual
+ end
+
+ resources :tests, only: [:show], param: :suite_name, controller: 'pipelines/tests' do
+ collection do
+ get :summary
end
end
end
diff --git a/config/routes/project.rb b/config/routes/project.rb
index 78dcc189d5b..3bd72dbf87c 100644
--- a/config/routes/project.rb
+++ b/config/routes/project.rb
@@ -25,6 +25,8 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
# Use this scope for all new project routes.
scope '-' do
get 'archive/*id', constraints: { format: Gitlab::PathRegex.archive_formats_regex, id: /.+?/ }, to: 'repositories#archive', as: 'archive'
+ get 'metrics(/:dashboard_path)', constraints: { dashboard_path: /.+\.yml/ },
+ to: 'metrics_dashboard#show', as: :metrics_dashboard, format: false
resources :artifacts, only: [:index, :destroy]
@@ -80,6 +82,7 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
resource :operations, only: [:show, :update] do
member do
post :reset_alerting_token
+ post :reset_pagerduty_token
end
end
@@ -181,7 +184,7 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
end
end
- resources :releases, only: [:index, :show, :edit], param: :tag, constraints: { tag: %r{[^/]+} } do
+ resources :releases, only: [:index, :new, :show, :edit], param: :tag, constraints: { tag: %r{[^/]+} } do
member do
get :downloads, path: 'downloads/*filepath', format: false
scope module: :releases do
@@ -257,7 +260,7 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
# This route is also defined in gitlab-workhorse. Make sure to update accordingly.
get '/terminal.ws/authorize', to: 'environments#terminal_websocket_authorize', format: false
- get '/prometheus/api/v1/*proxy_path', to: 'environments/prometheus_api#proxy', as: :prometheus_api
+ get '/prometheus/api/v1/*proxy_path', to: 'environments/prometheus_api#prometheus_proxy', as: :prometheus_api
get '/sample_metrics', to: 'environments/sample_metrics#query'
end
@@ -313,6 +316,12 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
end
end
+ get '/snippets/:snippet_id/raw/:ref/*path',
+ to: 'snippets/blobs#raw',
+ format: false,
+ as: :snippet_blob_raw,
+ constraints: { snippet_id: /\d+/ }
+
draw :issues
draw :merge_requests
draw :pipelines
@@ -333,6 +342,12 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
# Look for scope '-' at the top of the file.
#
+ # Service Desk
+ #
+ get '/service_desk' => 'service_desk#show', as: :service_desk
+ put '/service_desk' => 'service_desk#update', as: :service_desk_refresh
+
+ #
# Templates
#
get '/templates/:template_type/:key' => 'templates#show',
@@ -363,6 +378,19 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
end
end
+ # Serve snippet routes under /-/snippets.
+ # To ensure an old unscoped routing is used for the UI we need to
+ # add prefix 'as' to the scope routing and place it below original routing.
+ # Issue https://gitlab.com/gitlab-org/gitlab/-/issues/29572
+ scope '-', as: :scoped do
+ resources :snippets, concerns: :awardable, constraints: { id: /\d+/ } do # rubocop: disable Cop/PutProjectRoutesUnderScope
+ member do
+ get :raw
+ post :mark_as_spam
+ end
+ end
+ end
+
namespace :prometheus do
resources :alerts, constraints: { id: /\d+/ }, only: [:index, :create, :show, :update, :destroy] do # rubocop: disable Cop/PutProjectRoutesUnderScope
post :notify, on: :collection
@@ -379,6 +407,8 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
post 'alerts/notify', to: 'alerting/notifications#create'
+ post 'incidents/pagerduty', to: 'incident_management/pager_duty_incidents#create'
+
draw :legacy_builds
resources :hooks, only: [:index, :create, :edit, :update, :destroy], constraints: { id: /\d+/ } do # rubocop: disable Cop/PutProjectRoutesUnderScope
diff --git a/config/routes/snippets.rb b/config/routes/snippets.rb
index ba6da3ac57e..1ea9a6431d8 100644
--- a/config/routes/snippets.rb
+++ b/config/routes/snippets.rb
@@ -17,5 +17,14 @@ resources :snippets, concerns: :awardable do
end
end
+# Use this /-/ scope for all new snippet routes.
+scope path: '-' do
+ get '/snippets/:snippet_id/raw/:ref/*path',
+ to: 'snippets/blobs#raw',
+ as: :snippet_blob_raw,
+ format: false,
+ constraints: { snippet_id: /\d+/ }
+end
+
get '/s/:username', to: redirect('users/%{username}/snippets'),
constraints: { username: /[a-zA-Z.0-9_\-]+(?<!\.atom)/ }
diff --git a/config/routes/user.rb b/config/routes/user.rb
index 9db3a71a270..3bf13908d39 100644
--- a/config/routes/user.rb
+++ b/config/routes/user.rb
@@ -56,7 +56,7 @@ end
constraints(::Constraints::UserUrlConstrainer.new) do
# Get all keys of user
- get ':username.keys' => 'profiles/keys#get_keys', constraints: { username: Gitlab::PathRegex.root_namespace_route_regex }
+ get ':username.keys', controller: :users, action: :ssh_keys, constraints: { username: Gitlab::PathRegex.root_namespace_route_regex }
scope(path: ':username',
as: :user,
diff --git a/config/routes/wiki.rb b/config/routes/wiki.rb
index d439c99270e..49ad39e8369 100644
--- a/config/routes/wiki.rb
+++ b/config/routes/wiki.rb
@@ -3,13 +3,17 @@ scope(controller: :wikis) do
get :git_access
get :pages
get :new
- get '/', to: redirect('%{namespace_id}/%{project_id}/wikis/home')
+ get '/', to: redirect { |params, request| "#{request.path}/home" }
post '/', to: 'wikis#create'
+ scope '-' do
+ resource :confluence, only: :show
+ end
end
scope(path: 'wikis/*id', as: :wiki, format: false) do
get :edit
get :history
+ get :diff
post :preview_markdown
get '/', action: :show
put '/', action: :update
diff --git a/config/settings.rb b/config/settings.rb
index 99f1b85202e..c681fa32491 100644
--- a/config/settings.rb
+++ b/config/settings.rb
@@ -184,14 +184,15 @@ class Settings < Settingslogic
URI.parse(url_without_path).host
end
- # Runs at a random time of day on a consistent day of the week based on
+ # Runs at a consistent random time of day on a day of the week based on
# the instance UUID. This is to balance the load on the service receiving
# these pings. The sidekiq job handles temporary http failures.
def cron_for_usage_ping
- hour = rand(24)
- minute = rand(60)
# Set a default UUID for the case when the UUID hasn't been initialized.
uuid = Gitlab::CurrentSettings.uuid || 'uuid-not-set'
+
+ minute = Digest::MD5.hexdigest(uuid + 'minute').to_i(16) % 60
+ hour = Digest::MD5.hexdigest(uuid + 'hour').to_i(16) % 24
day_of_week = Digest::MD5.hexdigest(uuid).to_i(16) % 7
"#{minute} #{hour} * * #{day_of_week}"
diff --git a/config/sidekiq_queues.yml b/config/sidekiq_queues.yml
index 0052910b56e..8dd60bcd65c 100644
--- a/config/sidekiq_queues.yml
+++ b/config/sidekiq_queues.yml
@@ -78,8 +78,6 @@
- 1
- - detect_repository_languages
- 1
-- - elastic_batch_project_indexer
- - 1
- - elastic_commit_indexer
- 1
- - elastic_delete_project
@@ -166,8 +164,6 @@
- 2
- - new_note
- 2
-- - notifications
- - 2
- - object_pool
- 1
- - object_storage
@@ -262,6 +258,8 @@
- 1
- - todos_destroyer
- 1
+- - unassign_issuables
+ - 1
- - update_external_pull_requests
- 3
- - update_highest_role
@@ -274,6 +272,8 @@
- 1
- - upload_checksum
- 1
+- - vulnerabilities_statistics_adjustment
+ - 1
- - vulnerability_exports_export
- 1
- - vulnerability_exports_export_deletion
diff --git a/config/webpack.config.js b/config/webpack.config.js
index 557db58b1b9..8e51ce537c5 100644
--- a/config/webpack.config.js
+++ b/config/webpack.config.js
@@ -5,7 +5,7 @@ const webpack = require('webpack');
const VueLoaderPlugin = require('vue-loader/lib/plugin');
const StatsWriterPlugin = require('webpack-stats-plugin').StatsWriterPlugin;
const CompressionPlugin = require('compression-webpack-plugin');
-const MonacoWebpackPlugin = require('monaco-editor-webpack-plugin');
+const MonacoWebpackPlugin = require('./plugins/monaco_webpack');
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
const CopyWebpackPlugin = require('copy-webpack-plugin');
const vendorDllHash = require('./helpers/vendor_dll_hash');
@@ -241,7 +241,7 @@ module.exports = {
},
{
test: /\.(eot|ttf|woff|woff2)$/,
- include: /node_modules\/katex\/dist\/fonts/,
+ include: /node_modules\/(katex\/dist\/fonts|monaco-editor)/,
loader: 'file-loader',
options: {
name: '[name].[contenthash:8].[ext]',
diff --git a/crowdin.yml b/crowdin.yml
index 2861d34c941..27d9fba92b2 100644
--- a/crowdin.yml
+++ b/crowdin.yml
@@ -1,7 +1,9 @@
project_identifier: 'gitlab-ee'
api_key_env: CROWDIN_API_KEY
preserve_hierarchy: true
-commit_message: "[skip ci]"
+commit_message: |
+
+ [skip ci]
files:
- source: /locale/gitlab.pot
diff --git a/danger/changelog/Dangerfile b/danger/changelog/Dangerfile
index aa7a81555a3..f9e65bbf4c7 100644
--- a/danger/changelog/Dangerfile
+++ b/danger/changelog/Dangerfile
@@ -35,7 +35,11 @@ def check_changelog_yaml(path)
fail "`title` should be set, in #{gitlab.html_link(path)}! #{SEE_DOC}" if yaml["title"].nil?
fail "`type` should be set, in #{gitlab.html_link(path)}! #{SEE_DOC}" if yaml["type"].nil?
- if yaml["merge_request"].nil? && !helper.security_mr?
+ return if helper.security_mr?
+
+ cherry_pick_against_stable_branch = helper.cherry_pick_mr? && helper.stable_branch?
+
+ if yaml["merge_request"].nil?
mr_line = raw_file.lines.find_index("merge_request:\n")
if mr_line
@@ -43,14 +47,14 @@ def check_changelog_yaml(path)
else
message "Consider setting `merge_request` to #{gitlab.mr_json["iid"]} in #{gitlab.html_link(path)}. #{SEE_DOC}"
end
- elsif yaml["merge_request"] != gitlab.mr_json["iid"] && !helper.security_mr?
+ elsif yaml["merge_request"] != gitlab.mr_json["iid"] && !cherry_pick_against_stable_branch
fail "Merge request ID was not set to #{gitlab.mr_json["iid"]}! #{SEE_DOC}"
end
rescue Psych::SyntaxError, Psych::DisallowedClass, Psych::BadAlias
# YAML could not be parsed, fail the build.
fail "#{gitlab.html_link(path)} isn't valid YAML! #{SEE_DOC}"
rescue StandardError => e
- warn "There was a problem trying to check the Changelog. Exception: #{e.name} - #{e.message}"
+ warn "There was a problem trying to check the Changelog. Exception: #{e.class.name} - #{e.message}"
end
def check_changelog_path(path)
diff --git a/danger/documentation/Dangerfile b/danger/documentation/Dangerfile
index 1e27f9779e8..1dd6d484968 100644
--- a/danger/documentation/Dangerfile
+++ b/danger/documentation/Dangerfile
@@ -1,27 +1,33 @@
# frozen_string_literal: true
+def gitlab_danger
+ @gitlab_danger ||= GitlabDanger.new(helper.gitlab_helper)
+end
+
docs_paths_to_review = helper.changes_by_category[:docs]
-unless docs_paths_to_review.empty?
- message 'This merge request adds or changes files that require a review ' \
- 'from the Technical Writing team.'
+return if docs_paths_to_review.empty?
+
+message 'This merge request adds or changes files that require a review ' \
+ 'from the Technical Writing team.'
+
+return unless gitlab_danger.ci?
- if GitlabDanger.new(helper.gitlab_helper).ci?
- markdown(<<~MARKDOWN)
- ## Documentation review
+markdown(<<~MARKDOWN)
+ ## Documentation review
- The following files require a review from a technical writer:
+ The following files require a review from a technical writer:
- * #{docs_paths_to_review.map { |path| "`#{path}`" }.join("\n* ")}
+ * #{docs_paths_to_review.map { |path| "`#{path}`" }.join("\n* ")}
- The review does not need to block merging this merge request. See the:
+ The review does not need to block merging this merge request. See the:
- - [DevOps stages](https://about.gitlab.com/handbook/product/categories/#devops-stages) for the appropriate technical writer for this review.
- - [Documentation workflows](https://docs.gitlab.com/ee/development/documentation/workflow.html) for information on when to assign a merge request for review.
- MARKDOWN
+ - [Technical Writers assignments](https://about.gitlab.com/handbook/engineering/technical-writing/#designated-technical-writers) for the appropriate technical writer for this review.
+ - [Documentation workflows](https://docs.gitlab.com/ee/development/documentation/workflow.html) for information on when to assign a merge request for review.
+MARKDOWN
- unless gitlab.mr_labels.include?('documentation')
- warn 'This merge request is missing the ~documentation label.'
- end
- end
+unless gitlab.mr_labels.include?('documentation')
+ gitlab.api.update_merge_request(gitlab.mr_json['project_id'],
+ gitlab.mr_json['iid'],
+ labels: (gitlab.mr_labels + ['documentation']).join(','))
end
diff --git a/danger/metadata/Dangerfile b/danger/metadata/Dangerfile
index b3313674951..1d7f863c201 100644
--- a/danger/metadata/Dangerfile
+++ b/danger/metadata/Dangerfile
@@ -5,7 +5,11 @@ THROUGHPUT_LABELS = [
'security',
'bug',
'feature',
- 'backstage',
+ 'feature::addition',
+ 'feature::maintenance',
+ 'tooling',
+ 'tooling::pipelines',
+ 'tooling::workflow',
'documentation'
].freeze
diff --git a/danger/plugins/sidekiq_queues.rb b/danger/plugins/sidekiq_queues.rb
new file mode 100644
index 00000000000..1edeb6da3d5
--- /dev/null
+++ b/danger/plugins/sidekiq_queues.rb
@@ -0,0 +1,10 @@
+# frozen_string_literal: true
+
+require_relative '../../lib/gitlab/danger/sidekiq_queues'
+
+module Danger
+ class SidekiqQueues < Plugin
+ # Put the helper code somewhere it can be tested
+ include Gitlab::Danger::SidekiqQueues
+ end
+end
diff --git a/danger/roulette/Dangerfile b/danger/roulette/Dangerfile
index 54559af4066..6c192c3a311 100644
--- a/danger/roulette/Dangerfile
+++ b/danger/roulette/Dangerfile
@@ -12,17 +12,19 @@ MARKDOWN
CATEGORY_TABLE_HEADER = <<MARKDOWN
-To spread load more evenly across eligible reviewers, Danger has randomly picked
-a candidate for each review slot. Feel free to
+To spread load more evenly across eligible reviewers, Danger has picked a candidate for each
+review slot, based on their timezone. Feel free to
[override these selections](https://about.gitlab.com/handbook/engineering/projects/#gitlab)
if you think someone else would be better-suited, or the chosen person is unavailable.
To read more on how to use the reviewer roulette, please take a look at the
[Engineering workflow](https://about.gitlab.com/handbook/engineering/workflow/#basics)
and [code review guidelines](https://docs.gitlab.com/ee/development/code_review.html).
+Please consider assigning a reviewer or maintainer who is a
+[domain expert](https://about.gitlab.com/handbook/engineering/projects/#gitlab) in the area of the merge request.
Once you've decided who will review this merge request, mention them as you
-normally would! Danger does not (yet?) automatically notify them for you.
+normally would! Danger does not automatically notify them for you.
| Category | Reviewer | Maintainer |
| -------- | -------- | ---------- |
@@ -38,13 +40,18 @@ MARKDOWN
OPTIONAL_REVIEW_TEMPLATE = "%{role} review is optional for %{category}".freeze
NOT_AVAILABLE_TEMPLATE = 'No %{role} available'.freeze
+TIMEZONE_EXPERIMENT = true
+
+def mr_author
+ roulette.team.find { |person| person.username == gitlab.mr_author }
+end
def note_for_category_role(spin, role)
if spin.optional_role == role
return OPTIONAL_REVIEW_TEMPLATE % { role: role.capitalize, category: helper.label_for_category(spin.category) }
end
- spin.public_send(role)&.markdown_name || NOT_AVAILABLE_TEMPLATE % { role: role } # rubocop:disable GitlabSecurity/PublicSend
+ spin.public_send(role)&.markdown_name(timezone_experiment: TIMEZONE_EXPERIMENT, author: mr_author) || NOT_AVAILABLE_TEMPLATE % { role: role } # rubocop:disable GitlabSecurity/PublicSend
end
def markdown_row_for_spin(spin)
@@ -58,6 +65,8 @@ changes = helper.changes_by_category
# Ignore any files that are known but uncategorized. Prompt for any unknown files
changes.delete(:none)
+# To reinstate roulette for documentation, remove this line.
+changes.delete(:docs)
categories = changes.keys - [:unknown]
# Ensure to spin for database reviewer/maintainer when ~database is applied (e.g. to review SQL queries)
@@ -67,12 +76,22 @@ if changes.any?
project = helper.project_name
branch_name = gitlab.mr_json['source_branch']
- roulette_spins = roulette.spin(project, categories, branch_name)
- rows = roulette_spins.map { |spin| markdown_row_for_spin(spin) }
+ markdown(MESSAGE)
- unknown = changes.fetch(:unknown, [])
+ roulette_spins = roulette.spin(project, categories, branch_name, timezone_experiment: TIMEZONE_EXPERIMENT)
+ rows = roulette_spins.map do |spin|
+ # MR includes QA changes, but also other changes, and author isn't an SET
+ if spin.category == :qa && categories.size > 1 && mr_author && !mr_author.reviewer?(project, spin.category, [])
+ spin.optional_role = :maintainer
+ end
+
+ spin.optional_role = :maintainer if spin.category == :test
+
+ markdown_row_for_spin(spin)
+ end
- markdown(MESSAGE)
markdown(CATEGORY_TABLE_HEADER + rows.join("\n")) unless rows.empty?
+
+ unknown = changes.fetch(:unknown, [])
markdown(UNKNOWN_FILES_MESSAGE + helper.markdown_list(unknown)) unless unknown.empty?
end
diff --git a/danger/sidekiq_queues/Dangerfile b/danger/sidekiq_queues/Dangerfile
new file mode 100644
index 00000000000..b40be7a486e
--- /dev/null
+++ b/danger/sidekiq_queues/Dangerfile
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+SCALABILITY_REVIEW_MESSAGE = <<~MSG
+## Sidekiq queue changes
+
+This merge request contains changes to Sidekiq queues. Please follow the [documentation on changing a queue's urgency](https://docs.gitlab.com/ee/development/sidekiq_style_guide.html#changing-a-queues-urgency).
+MSG
+
+ADDED_QUEUES_MESSAGE = <<~MSG
+These queues were added:
+MSG
+
+CHANGED_QUEUES_MESSAGE = <<~MSG
+These queues had their attributes changed:
+MSG
+
+if sidekiq_queues.added_queue_names.any? || sidekiq_queues.changed_queue_names.any?
+ markdown(SCALABILITY_REVIEW_MESSAGE)
+
+ if sidekiq_queues.added_queue_names.any?
+ markdown(ADDED_QUEUES_MESSAGE + helper.markdown_list(sidekiq_queues.added_queue_names))
+ end
+
+ if sidekiq_queues.changed_queue_names.any?
+ markdown(CHANGED_QUEUES_MESSAGE + helper.markdown_list(sidekiq_queues.changed_queue_names))
+ end
+end
diff --git a/danger/specs/Dangerfile b/danger/specs/Dangerfile
index 784ce75fef8..72e0c8e92f4 100644
--- a/danger/specs/Dangerfile
+++ b/danger/specs/Dangerfile
@@ -1,6 +1,12 @@
# frozen_string_literal: true
-NO_SPECS_LABELS = %w[backstage documentation QA].freeze
+NO_SPECS_LABELS = [
+ 'tooling',
+ 'tooling::pipelines',
+ 'tooling::workflow',
+ 'documentation',
+ 'QA'
+].freeze
NO_NEW_SPEC_MESSAGE = <<~MSG
You've made some app changes, but didn't add any tests.
That's OK as long as you're refactoring existing code,
diff --git a/db/fixtures/development/24_forks.rb b/db/fixtures/development/24_forks.rb
index cb6dbb7504d..536d9f9e2ba 100644
--- a/db/fixtures/development/24_forks.rb
+++ b/db/fixtures/development/24_forks.rb
@@ -10,17 +10,30 @@ Sidekiq::Testing.inline! do
# we use randomized approach (e.g. `Array#sample`).
return unless source_project
- fork_project = Projects::ForkService.new(
- source_project,
- user,
- namespace: user.namespace,
- skip_disk_validation: true
- ).execute
+ Sidekiq::Worker.skipping_transaction_check do
+ fork_project = Projects::ForkService.new(
+ source_project,
+ user,
+ namespace: user.namespace,
+ skip_disk_validation: true
+ ).execute
- if fork_project.valid?
- print '.'
- else
- print 'F'
+ # Seed-Fu runs this entire fixture in a transaction, so the `after_commit`
+ # hook won't run until after the fixture is loaded. That is too late
+ # since the Sidekiq::Testing block has already exited. Force clearing
+ # the `after_commit` queue to ensure the job is run now.
+ fork_project.send(:_run_after_commit_queue)
+ fork_project.import_state.send(:_run_after_commit_queue)
+
+ # Expire repository cache after import to ensure
+ # valid_repo? call below returns a correct answer
+ fork_project.repository.expire_all_method_caches
+
+ if fork_project.valid? && fork_project.valid_repo?
+ print '.'
+ else
+ print 'F'
+ end
end
end
end
diff --git a/db/fixtures/development/26_packages.rb b/db/fixtures/development/26_packages.rb
new file mode 100644
index 00000000000..6096fd8962a
--- /dev/null
+++ b/db/fixtures/development/26_packages.rb
@@ -0,0 +1,159 @@
+# frozen_string_literal: true
+
+class Gitlab::Seeder::Packages
+ attr_reader :project
+
+ def initialize(project)
+ @project = project
+ end
+
+ def seed_packages(package_type)
+ send("seed_#{package_type}_packages")
+ end
+
+ def seed_npm_packages
+ 5.times do |i|
+ name = "@#{@project.root_namespace.path}/npm_package_#{SecureRandom.hex}"
+ version = "1.12.#{i}"
+
+ params = Gitlab::Json.parse(read_fixture_file('npm', 'payload.json')
+ .gsub('@root/npm-test', name)
+ .gsub('1.0.1', version))
+ .with_indifferent_access
+
+ ::Packages::Npm::CreatePackageService.new(project, project.owner, params).execute
+
+ print '.'
+ end
+ end
+
+ def seed_maven_packages
+ 5.times do |i|
+ name = "my/company/app/maven-app-#{i}"
+ version = "1.0.#{i}-SNAPSHOT"
+
+ params = {
+ name: name,
+ version: version,
+ path: "#{name}/#{version}"
+ }
+
+ pkg = ::Packages::Maven::CreatePackageService.new(project, project.owner, params).execute
+
+ %w(maven-metadata.xml my-app-1.0-20180724.124855-1.pom my-app-1.0-20180724.124855-1.jar).each do |filename|
+ with_cloned_fixture_file('maven', filename) do |filepath|
+ file_params = {
+ file: UploadedFile.new(filepath, filename: filename),
+ file_name: filename,
+ file_sha1: '1234567890',
+ size: 100.kilobytes
+ }
+ ::Packages::CreatePackageFileService.new(pkg, file_params).execute
+ end
+ end
+
+ print '.'
+ end
+ end
+
+ def seed_conan_packages
+ 5.times do |i|
+ name = "my-conan-pkg-#{i}"
+ version = "2.0.#{i}"
+
+ params = {
+ package_name: name,
+ package_version: version,
+ package_username: ::Packages::Conan::Metadatum.package_username_from(full_path: project.full_path),
+ package_channel: 'stable'
+ }
+
+ pkg = ::Packages::Conan::CreatePackageService.new(project, project.owner, params).execute
+
+ fixtures = {
+ 'recipe_files' => %w(conanfile.py conanmanifest.txt),
+ 'package_files' => %w(conanmanifest.txt conaninfo.txt conan_package.tgz)
+ }
+
+ fixtures.each do |folder, filenames|
+ filenames.each do |filename|
+ with_cloned_fixture_file(File.join('conan', folder), filename) do |filepath|
+ file = UploadedFile.new(filepath, filename: filename)
+ file_params = {
+ file_name: filename,
+ 'file.sha1': '1234567890',
+ 'file.size': 100.kilobytes,
+ 'file.md5': '12345',
+ recipe_revision: '0',
+ package_revision: '0',
+ conan_package_reference: '123456789',
+ conan_file_type: :package_file
+ }
+ ::Packages::Conan::CreatePackageFileService.new(pkg, file, file_params).execute
+ end
+ end
+ end
+
+ print '.'
+ end
+ end
+
+ def seed_nuget_packages
+ 5.times do |i|
+ name = "MyNugetApp.Package#{i}"
+ version = "4.2.#{i}"
+
+ pkg = ::Packages::Nuget::CreatePackageService.new(project, project.owner, {}).execute
+ # when using ::Packages::Nuget::CreatePackageService, packages have a fixed name and a fixed version.
+ pkg.update!(name: name, version: version)
+
+ filename = 'package.nupkg'
+ with_cloned_fixture_file('nuget', filename) do |filepath|
+ file_params = {
+ file: UploadedFile.new(filepath, filename: filename),
+ file_name: filename,
+ file_sha1: '1234567890',
+ size: 100.kilobytes
+ }
+ ::Packages::CreatePackageFileService.new(pkg, file_params).execute
+ end
+
+ print '.'
+ end
+ end
+
+ private
+
+ def read_fixture_file(package_type, file)
+ File.read(fixture_path(package_type, file))
+ end
+
+ def fixture_path(package_type, file)
+ Rails.root.join('spec', 'fixtures', 'packages', package_type, file)
+ end
+
+ def with_cloned_fixture_file(package_type, file)
+ Dir.mktmpdir do |dirpath|
+ cloned_path = File.join(dirpath, file)
+ FileUtils.cp(fixture_path(package_type, file), cloned_path)
+ yield cloned_path
+ end
+ end
+end
+
+Gitlab::Seeder.quiet do
+ flag = 'SEED_ALL_PACKAGE_TYPES'
+
+ puts "Use the `#{flag}` environment variable to seed packages of all types." unless ENV[flag]
+
+ package_types = ENV[flag] ? %i[npm maven conan nuget] : [:npm]
+
+ Project.not_mass_generated.sample(5).each do |project|
+ puts "\nSeeding packages for the '#{project.full_path}' project"
+ seeder = Gitlab::Seeder::Packages.new(project)
+
+ package_types.each do |package_type|
+ seeder.seed_packages(package_type)
+ end
+ end
+end
diff --git a/db/fixtures/development/27_product_analytics_events.rb b/db/fixtures/development/27_product_analytics_events.rb
new file mode 100644
index 00000000000..19237afd8ea
--- /dev/null
+++ b/db/fixtures/development/27_product_analytics_events.rb
@@ -0,0 +1,56 @@
+# frozen_string_literal: true
+
+Gitlab::Seeder.quiet do
+ # The data set takes approximately 2 minutes to load,
+ # so its put behind the flag. To seed this data use the flag and the filter:
+ # SEED_PRODUCT_ANALYTICS_EVENTS=1 FILTER=product_analytics_events rake db:seed_fu
+ flag = 'SEED_PRODUCT_ANALYTICS_EVENTS'
+
+ if ENV[flag]
+ Project.all.sample(2).each do |project|
+ # Let's generate approx a week of events from now into the past with 1 minute step.
+ # To add some differentiation we add a random offset of up to 45 seconds.
+ 10000.times do |i|
+ dvce_created_tstamp = DateTime.now - i.minute - rand(45).seconds
+
+ # Add a random delay to collector timestamp. Up to 2 seconds.
+ collector_tstamp = dvce_created_tstamp + rand(3).second
+
+ ProductAnalyticsEvent.create!(
+ project_id: project.id,
+ platform: ["web", "mob", "mob", "app"].sample,
+ collector_tstamp: collector_tstamp,
+ dvce_created_tstamp: dvce_created_tstamp,
+ event: nil,
+ event_id: SecureRandom.uuid,
+ name_tracker: "sp",
+ v_tracker: "js-2.14.0",
+ v_collector: Gitlab::VERSION,
+ v_etl: Gitlab::VERSION,
+ domain_userid: SecureRandom.uuid,
+ domain_sessionidx: 4,
+ page_url: "#{project.web_url}/-/product_analytics/test",
+ page_title: 'Test page',
+ page_referrer: "#{project.web_url}/-/product_analytics/test",
+ br_lang: ["en-US", "en-US", "en-GB", "nl", "fi"].sample, # https://www.andiamo.co.uk/resources/iso-language-codes/
+ br_features_pdf: true,
+ br_cookies: [true, true, true, false].sample,
+ br_colordepth: ["24", "24", "16", "8"].sample,
+ os_timezone: ["America/Los_Angeles", "America/Los_Angeles", "America/Lima", "Asia/Dubai", "Africa/Bangui"].sample, # https://en.wikipedia.org/wiki/List_of_tz_database_time_zones
+ doc_charset: ["UTF-8", "UTF-8", "UTF-8", "DOS", "EUC"].sample,
+ domain_sessionid: SecureRandom.uuid
+ )
+ end
+
+ unless Feature.enabled?(:product_analytics, project)
+ if Feature.enable(:product_analytics, project)
+ puts "Product analytics feature was enabled for #{project.full_path}"
+ end
+ end
+
+ puts "10K events added to #{project.full_path}"
+ end
+ else
+ puts "Skipped. Use the `#{flag}` environment variable to enable."
+ end
+end
diff --git a/db/migrate/20190225160300_steal_encrypt_runners_tokens.rb b/db/migrate/20190225160300_steal_encrypt_runners_tokens.rb
deleted file mode 100644
index 18c0d2a2e1b..00000000000
--- a/db/migrate/20190225160300_steal_encrypt_runners_tokens.rb
+++ /dev/null
@@ -1,19 +0,0 @@
-# frozen_string_literal: true
-
-class StealEncryptRunnersTokens < ActiveRecord::Migration[5.0]
- include Gitlab::Database::MigrationHelpers
-
- # This cleans after `EncryptRunnersTokens`
-
- DOWNTIME = false
-
- disable_ddl_transaction!
-
- def up
- Gitlab::BackgroundMigration.steal('EncryptRunnersTokens')
- end
-
- def down
- # no-op
- end
-end
diff --git a/db/migrate/20190315191339_create_merge_request_assignees_table.rb b/db/migrate/20190315191339_create_merge_request_assignees_table.rb
index dbd9ea3e35b..6fc4463f281 100644
--- a/db/migrate/20190315191339_create_merge_request_assignees_table.rb
+++ b/db/migrate/20190315191339_create_merge_request_assignees_table.rb
@@ -17,8 +17,6 @@ class CreateMergeRequestAssigneesTable < ActiveRecord::Migration[5.0]
end
def down
- # rubocop:disable Migration/DropTable
drop_table :merge_request_assignees
- # rubocop:enable Migration/DropTable
end
end
diff --git a/db/migrate/20190722144316_create_milestone_releases_table.rb b/db/migrate/20190722144316_create_milestone_releases_table.rb
index 911ca941a56..55878bcec41 100644
--- a/db/migrate/20190722144316_create_milestone_releases_table.rb
+++ b/db/migrate/20190722144316_create_milestone_releases_table.rb
@@ -15,8 +15,6 @@ class CreateMilestoneReleasesTable < ActiveRecord::Migration[5.2]
end
def down
- # rubocop:disable Migration/DropTable
drop_table :milestone_releases
- # rubocop:enable Migration/DropTable
end
end
diff --git a/db/migrate/20190927055500_create_description_versions.rb b/db/migrate/20190927055500_create_description_versions.rb
index 9046ebbc499..b3082533a6f 100644
--- a/db/migrate/20190927055500_create_description_versions.rb
+++ b/db/migrate/20190927055500_create_description_versions.rb
@@ -24,8 +24,6 @@ class CreateDescriptionVersions < ActiveRecord::Migration[5.2]
def down
remove_column :system_note_metadata, :description_version_id
- # rubocop:disable Migration/DropTable
drop_table :description_versions
- # rubocop:enable Migration/DropTable
end
end
diff --git a/db/migrate/20191112212815_create_web_authn_table.rb b/db/migrate/20191112212815_create_web_authn_table.rb
new file mode 100644
index 00000000000..72895f955df
--- /dev/null
+++ b/db/migrate/20191112212815_create_web_authn_table.rb
@@ -0,0 +1,26 @@
+# frozen_string_literal: true
+
+class CreateWebAuthnTable < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ # disable_ddl_transaction!
+
+ # rubocop:disable Migration/AddLimitToTextColumns
+ # limits are added in subsequent migration
+ def change
+ create_table :webauthn_registrations do |t|
+ t.bigint :user_id, null: false, index: true
+
+ t.bigint :counter, default: 0, null: false
+ t.timestamps_with_timezone
+ t.text :credential_xid, null: false, index: { unique: true }
+ t.text :name, null: false
+ # The length of the public key is determined by the device
+ # and not specified. Thus we can't set a limit
+ t.text :public_key, null: false # rubocop:disable Migration/AddLimitToTextColumns
+ end
+ end
+ # rubocop:enable Migration/AddLimitToTextColumns
+end
diff --git a/db/migrate/20191118053631_add_group_deletion_schedules.rb b/db/migrate/20191118053631_add_group_deletion_schedules.rb
index bc18480e5b9..6f3ed27e156 100644
--- a/db/migrate/20191118053631_add_group_deletion_schedules.rb
+++ b/db/migrate/20191118053631_add_group_deletion_schedules.rb
@@ -23,8 +23,6 @@ class AddGroupDeletionSchedules < ActiveRecord::Migration[5.2]
end
def down
- # rubocop:disable Migration/DropTable
drop_table :group_deletion_schedules
- # rubocop:enable Migration/DropTable
end
end
diff --git a/db/migrate/20191127151619_create_gitlab_subscription_histories.rb b/db/migrate/20191127151619_create_gitlab_subscription_histories.rb
index db2617112a3..718f2c1b313 100644
--- a/db/migrate/20191127151619_create_gitlab_subscription_histories.rb
+++ b/db/migrate/20191127151619_create_gitlab_subscription_histories.rb
@@ -23,8 +23,6 @@ class CreateGitlabSubscriptionHistories < ActiveRecord::Migration[5.2]
end
def down
- # rubocop:disable Migration/DropTable
drop_table :gitlab_subscription_histories
- # rubocop:enable Migration/DropTable
end
end
diff --git a/db/migrate/20200214025454_add_canonical_emails.rb b/db/migrate/20200214025454_add_canonical_emails.rb
index 70ab7208ade..0732d39169d 100644
--- a/db/migrate/20200214025454_add_canonical_emails.rb
+++ b/db/migrate/20200214025454_add_canonical_emails.rb
@@ -20,9 +20,7 @@ class AddCanonicalEmails < ActiveRecord::Migration[6.0]
def down
with_lock_retries do
- # rubocop:disable Migration/DropTable
drop_table(:user_canonical_emails)
- # rubocop:enable Migration/DropTable
end
end
end
diff --git a/db/migrate/20200227165129_create_user_details.rb b/db/migrate/20200227165129_create_user_details.rb
index 474dc357266..89258eadb9f 100644
--- a/db/migrate/20200227165129_create_user_details.rb
+++ b/db/migrate/20200227165129_create_user_details.rb
@@ -20,9 +20,7 @@ class CreateUserDetails < ActiveRecord::Migration[6.0]
def down
with_lock_retries do
- # rubocop:disable Migration/DropTable
drop_table :user_details
- # rubocop:enable Migration/DropTable
end
end
end
diff --git a/db/migrate/20200229171700_create_custom_emojis.rb b/db/migrate/20200229171700_create_custom_emojis.rb
new file mode 100644
index 00000000000..1a60d7c8a63
--- /dev/null
+++ b/db/migrate/20200229171700_create_custom_emojis.rb
@@ -0,0 +1,29 @@
+class CreateCustomEmojis < ActiveRecord::Migration[5.2]
+ include Gitlab::Database::MigrationHelpers
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ unless table_exists?(:custom_emoji)
+ create_table :custom_emoji do |t|
+ t.references :namespace, index: false, null: false, foreign_key: { on_delete: :cascade }
+ t.datetime_with_timezone :created_at, null: false
+ t.datetime_with_timezone :updated_at, null: false
+ t.text :name, null: false
+ t.text :file, null: false
+ end
+ end
+
+ unless index_exists?(:custom_emoji, [:namespace_id, :name], unique: true)
+ add_index :custom_emoji, [:namespace_id, :name], unique: true
+ end
+
+ add_text_limit(:custom_emoji, :name, 36)
+ add_text_limit(:custom_emoji, :file, 255)
+ end
+
+ def down
+ drop_table :custom_emoji
+ end
+end
diff --git a/db/migrate/20200305020458_add_label_restore_table.rb b/db/migrate/20200305020458_add_label_restore_table.rb
new file mode 100644
index 00000000000..a5809cfe14b
--- /dev/null
+++ b/db/migrate/20200305020458_add_label_restore_table.rb
@@ -0,0 +1,31 @@
+# frozen_string_literal: true
+
+class AddLabelRestoreTable < ActiveRecord::Migration[6.0]
+ DOWNTIME = false
+
+ def up
+ # copy table
+ execute "CREATE TABLE #{backup_labels_table_name} (LIKE #{labels_table_name} INCLUDING ALL);"
+
+ # make the primary key a real functioning one rather than incremental
+ execute "ALTER TABLE #{backup_labels_table_name} ALTER COLUMN ID DROP DEFAULT;"
+
+ # add some fields that make changes trackable
+ execute "ALTER TABLE #{backup_labels_table_name} ADD COLUMN restore_action INTEGER;"
+ execute "ALTER TABLE #{backup_labels_table_name} ADD COLUMN new_title VARCHAR;"
+ end
+
+ def down
+ drop_table backup_labels_table_name
+ end
+
+ private
+
+ def labels_table_name
+ :labels
+ end
+
+ def backup_labels_table_name
+ :backup_labels
+ end
+end
diff --git a/db/migrate/20200305020459_add_label_restore_foreign_keys.rb b/db/migrate/20200305020459_add_label_restore_foreign_keys.rb
new file mode 100644
index 00000000000..4b7c68cb20b
--- /dev/null
+++ b/db/migrate/20200305020459_add_label_restore_foreign_keys.rb
@@ -0,0 +1,35 @@
+# frozen_string_literal: true
+
+class AddLabelRestoreForeignKeys < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ # create foreign keys
+ connection.foreign_keys(labels_table_name).each do |fk|
+ fk_options = fk.options
+ add_concurrent_foreign_key(backup_labels_table_name, fk.to_table, name: fk.name, column: fk_options[:column])
+ end
+ end
+
+ def down
+ connection.foreign_keys(backup_labels_table_name).each do |fk|
+ with_lock_retries do
+ remove_foreign_key backup_labels_table_name, name: fk.name
+ end
+ end
+ end
+
+ private
+
+ def labels_table_name
+ :labels
+ end
+
+ def backup_labels_table_name
+ :backup_labels
+ end
+end
diff --git a/db/migrate/20200311093210_create_user_highest_roles.rb b/db/migrate/20200311093210_create_user_highest_roles.rb
index df2b02b7d91..36007f196d1 100644
--- a/db/migrate/20200311093210_create_user_highest_roles.rb
+++ b/db/migrate/20200311093210_create_user_highest_roles.rb
@@ -19,9 +19,7 @@ class CreateUserHighestRoles < ActiveRecord::Migration[6.0]
def down
with_lock_retries do
- # rubocop:disable Migration/DropTable
drop_table :user_highest_roles
- # rubocop:enable Migration/DropTable
end
end
end
diff --git a/db/migrate/20200326122700_create_diff_note_positions.rb b/db/migrate/20200326122700_create_diff_note_positions.rb
index 6c558516471..d37f7fef078 100644
--- a/db/migrate/20200326122700_create_diff_note_positions.rb
+++ b/db/migrate/20200326122700_create_diff_note_positions.rb
@@ -30,8 +30,6 @@ class CreateDiffNotePositions < ActiveRecord::Migration[6.0]
# rubocop:enable Migration/AddLimitToTextColumns
def down
- # rubocop:disable Migration/DropTable
drop_table :diff_note_positions
- # rubocop:enable Migration/DropTable
end
end
diff --git a/db/migrate/20200330203837_recreate_ci_ref.rb b/db/migrate/20200330203837_recreate_ci_ref.rb
index 5a7bede4dc8..734795f594f 100644
--- a/db/migrate/20200330203837_recreate_ci_ref.rb
+++ b/db/migrate/20200330203837_recreate_ci_ref.rb
@@ -25,9 +25,7 @@ class RecreateCiRef < ActiveRecord::Migration[6.0]
def down
with_lock_retries do
- # rubocop:disable Migration/DropTable
drop_table :ci_refs
- # rubocop:enable Migration/DropTable
create_table :ci_refs do |t|
t.references :project, null: false, index: false, foreign_key: { on_delete: :cascade }, type: :integer
diff --git a/db/migrate/20200331132103_add_project_compliance_framework_settings_table.rb b/db/migrate/20200331132103_add_project_compliance_framework_settings_table.rb
index 169c8602cab..6af8c6db939 100644
--- a/db/migrate/20200331132103_add_project_compliance_framework_settings_table.rb
+++ b/db/migrate/20200331132103_add_project_compliance_framework_settings_table.rb
@@ -16,9 +16,7 @@ class AddProjectComplianceFrameworkSettingsTable < ActiveRecord::Migration[6.0]
def down
with_lock_retries do
- # rubocop:disable Migration/DropTable
drop_table :project_compliance_framework_settings
- # rubocop:enable Migration/DropTable
end
end
end
diff --git a/db/migrate/20200407182205_create_partitioned_foreign_keys.rb b/db/migrate/20200407182205_create_partitioned_foreign_keys.rb
index 59e7d88b238..aca8116d2dd 100644
--- a/db/migrate/20200407182205_create_partitioned_foreign_keys.rb
+++ b/db/migrate/20200407182205_create_partitioned_foreign_keys.rb
@@ -26,8 +26,6 @@ class CreatePartitionedForeignKeys < ActiveRecord::Migration[6.0]
end
def down
- # rubocop:disable Migration/DropTable
drop_table :partitioned_foreign_keys
- # rubocop:enable Migration/DropTable
end
end
diff --git a/db/migrate/20200407222647_create_project_repository_storage_moves.rb b/db/migrate/20200407222647_create_project_repository_storage_moves.rb
index 98e44aa2fc6..402a1cdd4a6 100644
--- a/db/migrate/20200407222647_create_project_repository_storage_moves.rb
+++ b/db/migrate/20200407222647_create_project_repository_storage_moves.rb
@@ -26,8 +26,6 @@ class CreateProjectRepositoryStorageMoves < ActiveRecord::Migration[6.0]
remove_check_constraint(:project_repository_storage_moves, 'project_repository_storage_moves_source_storage_name')
remove_check_constraint(:project_repository_storage_moves, 'project_repository_storage_moves_destination_storage_name')
- # rubocop:disable Migration/DropTable
drop_table :project_repository_storage_moves
- # rubocop:enable Migration/DropTable
end
end
diff --git a/db/migrate/20200408125046_create_ci_freeze_periods.rb b/db/migrate/20200408125046_create_ci_freeze_periods.rb
index 98f0e20b11b..42a385150b8 100644
--- a/db/migrate/20200408125046_create_ci_freeze_periods.rb
+++ b/db/migrate/20200408125046_create_ci_freeze_periods.rb
@@ -25,8 +25,6 @@ class CreateCiFreezePeriods < ActiveRecord::Migration[6.0]
end
def down
- # rubocop:disable Migration/DropTable
drop_table :ci_freeze_periods
- # rubocop:enable Migration/DropTable
end
end
diff --git a/db/migrate/20200416005331_create_status_page_published_incidents.rb b/db/migrate/20200416005331_create_status_page_published_incidents.rb
index ea2ddcde925..75889cd5bb6 100644
--- a/db/migrate/20200416005331_create_status_page_published_incidents.rb
+++ b/db/migrate/20200416005331_create_status_page_published_incidents.rb
@@ -15,8 +15,6 @@ class CreateStatusPagePublishedIncidents < ActiveRecord::Migration[6.0]
end
def down
- # rubocop:disable Migration/DropTable
drop_table :status_page_published_incidents
- # rubocop:enable Migration/DropTable
end
end
diff --git a/db/migrate/20200417044453_create_alert_management_alerts.rb b/db/migrate/20200417044453_create_alert_management_alerts.rb
index 3509f4946a7..6221eeeb24b 100644
--- a/db/migrate/20200417044453_create_alert_management_alerts.rb
+++ b/db/migrate/20200417044453_create_alert_management_alerts.rb
@@ -39,8 +39,6 @@ class CreateAlertManagementAlerts < ActiveRecord::Migration[6.0]
end
def down
- # rubocop:disable Migration/DropTable
drop_table :alert_management_alerts
- # rubocop:enable Migration/DropTable
end
end
diff --git a/db/migrate/20200420104303_add_group_import_states_table.rb b/db/migrate/20200420104303_add_group_import_states_table.rb
index 10b8cd4aa49..a44a2ea75f3 100644
--- a/db/migrate/20200420104303_add_group_import_states_table.rb
+++ b/db/migrate/20200420104303_add_group_import_states_table.rb
@@ -20,8 +20,6 @@ class AddGroupImportStatesTable < ActiveRecord::Migration[6.0]
# rubocop:enable Migration/AddLimitToTextColumns
def down
- # rubocop:disable Migration/DropTable
drop_table :group_import_states
- # rubocop:enable Migration/DropTable
end
end
diff --git a/db/migrate/20200420115948_create_metrics_users_starred_dashboard.rb b/db/migrate/20200420115948_create_metrics_users_starred_dashboard.rb
index 8e9495f3a83..27130136e9d 100644
--- a/db/migrate/20200420115948_create_metrics_users_starred_dashboard.rb
+++ b/db/migrate/20200420115948_create_metrics_users_starred_dashboard.rb
@@ -20,8 +20,6 @@ class CreateMetricsUsersStarredDashboard < ActiveRecord::Migration[6.0]
# rubocop: enable Migration/AddLimitToTextColumns
def down
- # rubocop:disable Migration/DropTable
drop_table :metrics_users_starred_dashboards
- # rubocop:enable Migration/DropTable
end
end
diff --git a/db/migrate/20200422091541_create_ci_instance_variables.rb b/db/migrate/20200422091541_create_ci_instance_variables.rb
index d1c954b4020..ab2a4722f89 100644
--- a/db/migrate/20200422091541_create_ci_instance_variables.rb
+++ b/db/migrate/20200422091541_create_ci_instance_variables.rb
@@ -26,8 +26,6 @@ class CreateCiInstanceVariables < ActiveRecord::Migration[6.0]
end
def down
- # rubocop:disable Migration/DropTable
drop_table :ci_instance_variables
- # rubocop:enable Migration/DropTable
end
end
diff --git a/db/migrate/20200424102023_add_shared_runners_enabled_and_override_to_namespaces.rb b/db/migrate/20200424102023_add_shared_runners_enabled_and_override_to_namespaces.rb
new file mode 100644
index 00000000000..2555a50be44
--- /dev/null
+++ b/db/migrate/20200424102023_add_shared_runners_enabled_and_override_to_namespaces.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+class AddSharedRunnersEnabledAndOverrideToNamespaces < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ def up
+ with_lock_retries do
+ add_column :namespaces, :shared_runners_enabled, :boolean, default: true, null: false
+ add_column :namespaces, :allow_descendants_override_disabled_shared_runners, :boolean, default: false, null: false
+ end
+ end
+
+ def down
+ with_lock_retries do
+ remove_column :namespaces, :shared_runners_enabled, :boolean
+ remove_column :namespaces, :allow_descendants_override_disabled_shared_runners, :boolean
+ end
+ end
+end
diff --git a/db/migrate/20200424135319_create_nuget_dependency_link_metadata.rb b/db/migrate/20200424135319_create_nuget_dependency_link_metadata.rb
index 79adf41f973..1043616a1c7 100644
--- a/db/migrate/20200424135319_create_nuget_dependency_link_metadata.rb
+++ b/db/migrate/20200424135319_create_nuget_dependency_link_metadata.rb
@@ -21,8 +21,6 @@ class CreateNugetDependencyLinkMetadata < ActiveRecord::Migration[6.0]
end
def down
- # rubocop:disable Migration/DropTable
drop_table :packages_nuget_dependency_link_metadata
- # rubocop:enable Migration/DropTable
end
end
diff --git a/db/migrate/20200430130048_create_packages_nuget_metadata.rb b/db/migrate/20200430130048_create_packages_nuget_metadata.rb
index 3c89a143932..0f0d490c93d 100644
--- a/db/migrate/20200430130048_create_packages_nuget_metadata.rb
+++ b/db/migrate/20200430130048_create_packages_nuget_metadata.rb
@@ -29,8 +29,6 @@ class CreatePackagesNugetMetadata < ActiveRecord::Migration[6.0]
end
def down
- # rubocop:disable Migration/DropTable
drop_table :packages_nuget_metadata
- # rubocop:enable Migration/DropTable
end
end
diff --git a/db/migrate/20200430174637_create_group_deploy_keys.rb b/db/migrate/20200430174637_create_group_deploy_keys.rb
index 283c8769a80..9771ae013ea 100644
--- a/db/migrate/20200430174637_create_group_deploy_keys.rb
+++ b/db/migrate/20200430174637_create_group_deploy_keys.rb
@@ -31,8 +31,6 @@ class CreateGroupDeployKeys < ActiveRecord::Migration[6.0]
end
def down
- # rubocop:disable Migration/DropTable
drop_table :group_deploy_keys
- # rubocop:enable Migration/DropTable
end
end
diff --git a/db/migrate/20200510181937_add_web_authn_xid_to_user_details.rb b/db/migrate/20200510181937_add_web_authn_xid_to_user_details.rb
new file mode 100644
index 00000000000..d41e6611c45
--- /dev/null
+++ b/db/migrate/20200510181937_add_web_authn_xid_to_user_details.rb
@@ -0,0 +1,12 @@
+# frozen_string_literal: true
+
+class AddWebAuthnXidToUserDetails < ActiveRecord::Migration[6.0]
+ DOWNTIME = false
+
+ # rubocop:disable Migration/AddLimitToTextColumns
+ # limit is added in subsequent migration
+ def change
+ add_column :user_details, :webauthn_xid, :text
+ end
+ # rubocop:enable Migration/AddLimitToTextColumns
+end
diff --git a/db/migrate/20200510182218_add_text_limit_to_user_details_webauthn_xid.rb b/db/migrate/20200510182218_add_text_limit_to_user_details_webauthn_xid.rb
new file mode 100644
index 00000000000..715cfd771f5
--- /dev/null
+++ b/db/migrate/20200510182218_add_text_limit_to_user_details_webauthn_xid.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+class AddTextLimitToUserDetailsWebauthnXid < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::MigrationHelpers
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ add_text_limit :user_details, :webauthn_xid, 100
+ end
+
+ def down
+ remove_text_limit :user_details, :webauthn_xid
+ end
+end
diff --git a/db/migrate/20200510182556_add_text_limit_to_webauthn_registrations_name.rb b/db/migrate/20200510182556_add_text_limit_to_webauthn_registrations_name.rb
new file mode 100644
index 00000000000..28805505ba6
--- /dev/null
+++ b/db/migrate/20200510182556_add_text_limit_to_webauthn_registrations_name.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+class AddTextLimitToWebauthnRegistrationsName < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::MigrationHelpers
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ add_text_limit :webauthn_registrations, :name, 255
+ end
+
+ def down
+ remove_text_limit :webauthn_registrations, :name
+ end
+end
diff --git a/db/migrate/20200510182824_add_text_limit_to_webauthn_registrations_credential_xid.rb b/db/migrate/20200510182824_add_text_limit_to_webauthn_registrations_credential_xid.rb
new file mode 100644
index 00000000000..51155f370ef
--- /dev/null
+++ b/db/migrate/20200510182824_add_text_limit_to_webauthn_registrations_credential_xid.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+class AddTextLimitToWebauthnRegistrationsCredentialXid < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::MigrationHelpers
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ add_text_limit :webauthn_registrations, :credential_xid, 255
+ end
+
+ def down
+ remove_text_limit :webauthn_registrations, :credential_xid
+ end
+end
diff --git a/db/migrate/20200510183128_add_foreign_key_from_webauthn_registrations_to_users.rb b/db/migrate/20200510183128_add_foreign_key_from_webauthn_registrations_to_users.rb
new file mode 100644
index 00000000000..f71a5276dee
--- /dev/null
+++ b/db/migrate/20200510183128_add_foreign_key_from_webauthn_registrations_to_users.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+class AddForeignKeyFromWebauthnRegistrationsToUsers < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ # disable_ddl_transaction!
+
+ def up
+ with_lock_retries do
+ add_foreign_key :webauthn_registrations, :users, column: :user_id, on_delete: :cascade # rubocop:disable Migration/AddConcurrentForeignKey
+ end
+ end
+
+ def down
+ with_lock_retries do
+ remove_foreign_key :webauthn_registrations, column: :user_id
+ end
+ end
+end
diff --git a/db/migrate/20200521225327_create_alert_management_alert_assignees.rb b/db/migrate/20200521225327_create_alert_management_alert_assignees.rb
index 095b2fdfeee..99de2646b3d 100644
--- a/db/migrate/20200521225327_create_alert_management_alert_assignees.rb
+++ b/db/migrate/20200521225327_create_alert_management_alert_assignees.rb
@@ -17,8 +17,6 @@ class CreateAlertManagementAlertAssignees < ActiveRecord::Migration[6.0]
end
def down
- # rubocop:disable Migration/DropTable
drop_table :alert_management_alert_assignees
- # rubocop:enable Migration/DropTable
end
end
diff --git a/db/migrate/20200522205606_create_group_deploy_keys_group.rb b/db/migrate/20200522205606_create_group_deploy_keys_group.rb
new file mode 100644
index 00000000000..85dbc14b0f5
--- /dev/null
+++ b/db/migrate/20200522205606_create_group_deploy_keys_group.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+class CreateGroupDeployKeysGroup < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::MigrationHelpers
+ DOWNTIME = false
+
+ def up
+ with_lock_retries do
+ create_table :group_deploy_keys_groups do |t|
+ t.timestamps_with_timezone
+
+ t.references :group, index: false, null: false, foreign_key: { to_table: :namespaces, on_delete: :cascade }
+ t.references :group_deploy_key, null: false, foreign_key: { on_delete: :cascade }
+
+ t.index [:group_id, :group_deploy_key_id], unique: true, name: 'index_group_deploy_keys_group_on_group_deploy_key_and_group_ids'
+ end
+ end
+ end
+
+ def down
+ with_lock_retries do
+ drop_table :group_deploy_keys_groups
+ end
+ end
+end
diff --git a/db/migrate/20200524104346_add_source_to_resource_state_event.rb b/db/migrate/20200524104346_add_source_to_resource_state_event.rb
new file mode 100644
index 00000000000..a1d1575bb02
--- /dev/null
+++ b/db/migrate/20200524104346_add_source_to_resource_state_event.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+class AddSourceToResourceStateEvent < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ unless column_exists?(:resource_state_events, :source_commit)
+ add_column :resource_state_events, :source_commit, :text
+ end
+
+ add_text_limit :resource_state_events, :source_commit, 40
+ end
+
+ def down
+ remove_column :resource_state_events, :source_commit
+ end
+end
diff --git a/db/migrate/20200526193555_add_squash_option_to_project.rb b/db/migrate/20200526193555_add_squash_option_to_project.rb
new file mode 100644
index 00000000000..6dd4e1c7c8e
--- /dev/null
+++ b/db/migrate/20200526193555_add_squash_option_to_project.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+class AddSquashOptionToProject < ActiveRecord::Migration[6.0]
+ DOWNTIME = false
+
+ def change
+ add_column :project_settings, :squash_option, :integer, default: 3, limit: 2
+ end
+end
diff --git a/db/migrate/20200526231421_update_index_approval_rule_name_for_code_owners_rule_type.rb b/db/migrate/20200526231421_update_index_approval_rule_name_for_code_owners_rule_type.rb
new file mode 100644
index 00000000000..2483ff7f8fa
--- /dev/null
+++ b/db/migrate/20200526231421_update_index_approval_rule_name_for_code_owners_rule_type.rb
@@ -0,0 +1,116 @@
+# frozen_string_literal: true
+
+class UpdateIndexApprovalRuleNameForCodeOwnersRuleType < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ LEGACY_INDEX_NAME_RULE_TYPE = "index_approval_rule_name_for_code_owners_rule_type"
+ LEGACY_INDEX_NAME_CODE_OWNERS = "approval_rule_name_index_for_code_owners"
+
+ SECTIONAL_INDEX_NAME = "index_approval_rule_name_for_sectional_code_owners_rule_type"
+
+ CODE_OWNER_RULE_TYPE = 2
+
+ def up
+ unless index_exists_by_name?(:approval_merge_request_rules, SECTIONAL_INDEX_NAME)
+ # Ensure only 1 code_owner rule with the same name and section per merge_request
+ #
+ add_concurrent_index(
+ :approval_merge_request_rules,
+ [:merge_request_id, :name, :section],
+ unique: true,
+ where: "rule_type = #{CODE_OWNER_RULE_TYPE}",
+ name: SECTIONAL_INDEX_NAME
+ )
+ end
+
+ remove_concurrent_index_by_name :approval_merge_request_rules, LEGACY_INDEX_NAME_RULE_TYPE
+ remove_concurrent_index_by_name :approval_merge_request_rules, LEGACY_INDEX_NAME_CODE_OWNERS
+
+ add_concurrent_index(
+ :approval_merge_request_rules,
+ [:merge_request_id, :name],
+ unique: true,
+ where: "rule_type = #{CODE_OWNER_RULE_TYPE} AND section IS NULL",
+ name: LEGACY_INDEX_NAME_RULE_TYPE
+ )
+
+ add_concurrent_index(
+ :approval_merge_request_rules,
+ [:merge_request_id, :code_owner, :name],
+ unique: true,
+ where: "code_owner = true AND section IS NULL",
+ name: LEGACY_INDEX_NAME_CODE_OWNERS
+ )
+ end
+
+ def down
+ # In a rollback situation, we can't guarantee that there will not be
+ # records that were allowed under the more specific SECTIONAL_INDEX_NAME
+ # index but would cause uniqueness violations under both the
+ # LEGACY_INDEX_NAME_RULE_TYPE and LEGACY_INDEX_NAME_CODE_OWNERS indices.
+ # Therefore, we need to first find all the MergeRequests with
+ # ApprovalMergeRequestRules that would violate these "new" indices and
+ # delete those approval rules, then create the new index, then finally
+ # recreate the approval rules for those merge requests.
+ #
+
+ # First, find all MergeRequests with ApprovalMergeRequestRules that will
+ # violate the new index.
+ #
+ if Gitlab.ee?
+ merge_request_ids = ApprovalMergeRequestRule
+ .select(:merge_request_id)
+ .where(rule_type: CODE_OWNER_RULE_TYPE)
+ .group(:merge_request_id, :rule_type, :name)
+ .includes(:merge_request)
+ .having("count(*) > 1")
+ .collect(&:merge_request_id)
+
+ # Delete ALL their code_owner approval rules
+ #
+ merge_request_ids.each_slice(10) do |ids|
+ ApprovalMergeRequestRule.where(merge_request_id: ids).code_owner.delete_all
+ end
+ end
+
+ # Remove legacy partial indices that only apply to `section IS NULL` records
+ #
+ remove_concurrent_index_by_name :approval_merge_request_rules, LEGACY_INDEX_NAME_RULE_TYPE
+ remove_concurrent_index_by_name :approval_merge_request_rules, LEGACY_INDEX_NAME_CODE_OWNERS
+
+ # Reconstruct original "legacy" indices
+ #
+ add_concurrent_index(
+ :approval_merge_request_rules,
+ [:merge_request_id, :name],
+ unique: true,
+ where: "rule_type = #{CODE_OWNER_RULE_TYPE}",
+ name: LEGACY_INDEX_NAME_RULE_TYPE
+ )
+
+ add_concurrent_index(
+ :approval_merge_request_rules,
+ [:merge_request_id, :code_owner, :name],
+ unique: true,
+ where: "code_owner = true",
+ name: LEGACY_INDEX_NAME_CODE_OWNERS
+ )
+
+ # MergeRequest::SyncCodeOwnerApprovalRules recreates the code_owner rules
+ # from scratch, adding them to the index. Duplicates will be rejected.
+ #
+ if Gitlab.ee?
+ merge_request_ids.each_slice(10) do |ids|
+ MergeRequest.where(id: ids).each do |merge_request|
+ MergeRequests::SyncCodeOwnerApprovalRules.new(merge_request).execute
+ end
+ end
+ end
+
+ remove_concurrent_index_by_name :approval_merge_request_rules, SECTIONAL_INDEX_NAME
+ end
+end
diff --git a/db/migrate/20200527211605_add_locked_to_ci_pipelines.rb b/db/migrate/20200527211605_add_locked_to_ci_pipelines.rb
new file mode 100644
index 00000000000..3587e6c4a08
--- /dev/null
+++ b/db/migrate/20200527211605_add_locked_to_ci_pipelines.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+class AddLockedToCiPipelines < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ def up
+ with_lock_retries do
+ add_column :ci_pipelines, :locked, :integer, limit: 2, null: false, default: 0
+ end
+ end
+
+ def down
+ with_lock_retries do
+ remove_column :ci_pipelines, :locked
+ end
+ end
+end
diff --git a/db/migrate/20200604001128_add_secrets_to_ci_builds_metadata.rb b/db/migrate/20200604001128_add_secrets_to_ci_builds_metadata.rb
new file mode 100644
index 00000000000..9a67993cf5c
--- /dev/null
+++ b/db/migrate/20200604001128_add_secrets_to_ci_builds_metadata.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+class AddSecretsToCiBuildsMetadata < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ def up
+ with_lock_retries do
+ add_column :ci_builds_metadata, :secrets, :jsonb, default: {}, null: false
+ end
+ end
+
+ def down
+ with_lock_retries do
+ remove_column :ci_builds_metadata, :secrets
+ end
+ end
+end
diff --git a/db/migrate/20200604143628_create_project_security_settings.rb b/db/migrate/20200604143628_create_project_security_settings.rb
index b1a08cf8781..f972cb509a7 100644
--- a/db/migrate/20200604143628_create_project_security_settings.rb
+++ b/db/migrate/20200604143628_create_project_security_settings.rb
@@ -21,9 +21,7 @@ class CreateProjectSecuritySettings < ActiveRecord::Migration[6.0]
def down
with_lock_retries do
- # rubocop:disable Migration/DropTable
drop_table :project_security_settings
- # rubocop:enable Migration/DropTable
end
end
end
diff --git a/db/migrate/20200604145731_create_board_user_preferences.rb b/db/migrate/20200604145731_create_board_user_preferences.rb
index 36e5014fdbe..e83f467d690 100644
--- a/db/migrate/20200604145731_create_board_user_preferences.rb
+++ b/db/migrate/20200604145731_create_board_user_preferences.rb
@@ -15,8 +15,6 @@ class CreateBoardUserPreferences < ActiveRecord::Migration[6.0]
end
def down
- # rubocop:disable Migration/DropTable
drop_table :board_user_preferences
- # rubocop:enable Migration/DropTable
end
end
diff --git a/db/migrate/20200605160806_add_index_on_repository_size_and_project_id_to_project_statistics.rb b/db/migrate/20200605160806_add_index_on_repository_size_and_project_id_to_project_statistics.rb
new file mode 100644
index 00000000000..2e7b75bcd17
--- /dev/null
+++ b/db/migrate/20200605160806_add_index_on_repository_size_and_project_id_to_project_statistics.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+class AddIndexOnRepositorySizeAndProjectIdToProjectStatistics < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ add_concurrent_index :project_statistics, [:repository_size, :project_id]
+ end
+
+ def down
+ remove_concurrent_index :project_statistics, [:repository_size, :project_id]
+ end
+end
diff --git a/db/migrate/20200605160836_add_index_on_storage_size_and_project_id_to_project_statistics.rb b/db/migrate/20200605160836_add_index_on_storage_size_and_project_id_to_project_statistics.rb
new file mode 100644
index 00000000000..22f9dab634b
--- /dev/null
+++ b/db/migrate/20200605160836_add_index_on_storage_size_and_project_id_to_project_statistics.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+class AddIndexOnStorageSizeAndProjectIdToProjectStatistics < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ add_concurrent_index :project_statistics, [:storage_size, :project_id]
+ end
+
+ def down
+ remove_concurrent_index :project_statistics, [:storage_size, :project_id]
+ end
+end
diff --git a/db/migrate/20200605160851_add_index_on_wiki_size_and_project_id_to_project_statistics.rb b/db/migrate/20200605160851_add_index_on_wiki_size_and_project_id_to_project_statistics.rb
new file mode 100644
index 00000000000..d32994afbfc
--- /dev/null
+++ b/db/migrate/20200605160851_add_index_on_wiki_size_and_project_id_to_project_statistics.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+class AddIndexOnWikiSizeAndProjectIdToProjectStatistics < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ add_concurrent_index :project_statistics, [:wiki_size, :project_id]
+ end
+
+ def down
+ remove_concurrent_index :project_statistics, [:wiki_size, :project_id]
+ end
+end
diff --git a/db/migrate/20200609012539_add_traversal_ids_to_namespaces.rb b/db/migrate/20200609012539_add_traversal_ids_to_namespaces.rb
new file mode 100644
index 00000000000..d7f282b69f8
--- /dev/null
+++ b/db/migrate/20200609012539_add_traversal_ids_to_namespaces.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+class AddTraversalIdsToNamespaces < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ def up
+ with_lock_retries do
+ add_column :namespaces, :traversal_ids, :integer, array: true, default: [], null: false
+ end
+ end
+
+ def down
+ with_lock_retries do
+ remove_column :namespaces, :traversal_ids
+ end
+ end
+end
diff --git a/db/migrate/20200610130002_create_vulnerability_statistics.rb b/db/migrate/20200610130002_create_vulnerability_statistics.rb
new file mode 100644
index 00000000000..77fd116230e
--- /dev/null
+++ b/db/migrate/20200610130002_create_vulnerability_statistics.rb
@@ -0,0 +1,30 @@
+# frozen_string_literal: true
+
+class CreateVulnerabilityStatistics < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ def up
+ with_lock_retries do
+ create_table :vulnerability_statistics do |t|
+ t.timestamps_with_timezone null: false
+ t.references :project, null: false, foreign_key: { on_delete: :cascade }
+ t.integer :total, default: 0, null: false
+ t.integer :critical, default: 0, null: false
+ t.integer :high, default: 0, null: false
+ t.integer :medium, default: 0, null: false
+ t.integer :low, default: 0, null: false
+ t.integer :unknown, default: 0, null: false
+ t.integer :info, default: 0, null: false
+ t.integer :letter_grade, limit: 1, index: true, null: false
+ end
+ end
+ end
+
+ def down
+ with_lock_retries do
+ drop_table :vulnerability_statistics
+ end
+ end
+end
diff --git a/db/migrate/20200613104045_add_compliance_frameworks_to_application_settings.rb b/db/migrate/20200613104045_add_compliance_frameworks_to_application_settings.rb
new file mode 100644
index 00000000000..be6f14692e7
--- /dev/null
+++ b/db/migrate/20200613104045_add_compliance_frameworks_to_application_settings.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+class AddComplianceFrameworksToApplicationSettings < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ def up
+ with_lock_retries do
+ add_column :application_settings, :compliance_frameworks, :integer, limit: 2, array: true, default: [], null: false
+ end
+ end
+
+ def down
+ with_lock_retries do
+ remove_column :application_settings, :compliance_frameworks
+ end
+ end
+end
diff --git a/db/migrate/20200615141554_add_closed_by_fields_to_resource_state_events.rb b/db/migrate/20200615141554_add_closed_by_fields_to_resource_state_events.rb
new file mode 100644
index 00000000000..ba11e64e667
--- /dev/null
+++ b/db/migrate/20200615141554_add_closed_by_fields_to_resource_state_events.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+class AddClosedByFieldsToResourceStateEvents < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ def up
+ add_column :resource_state_events, :close_after_error_tracking_resolve, :boolean, default: false, null: false
+ add_column :resource_state_events, :close_auto_resolve_prometheus_alert, :boolean, default: false, null: false
+ end
+
+ def down
+ remove_column :resource_state_events, :close_auto_resolve_prometheus_alert, :boolean
+ remove_column :resource_state_events, :close_after_error_tracking_resolve, :boolean
+ end
+end
diff --git a/db/migrate/20200615193524_add_verify_known_sign_in_to_application_settings.rb b/db/migrate/20200615193524_add_verify_known_sign_in_to_application_settings.rb
new file mode 100644
index 00000000000..c50a17968a9
--- /dev/null
+++ b/db/migrate/20200615193524_add_verify_known_sign_in_to_application_settings.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+class AddVerifyKnownSignInToApplicationSettings < ActiveRecord::Migration[6.0]
+ DOWNTIME = false
+
+ def change
+ add_column :application_settings, :notify_on_unknown_sign_in, :boolean, default: true, null: false
+ end
+end
diff --git a/db/migrate/20200615234047_create_clusters_applications_cilium.rb b/db/migrate/20200615234047_create_clusters_applications_cilium.rb
new file mode 100644
index 00000000000..9f77ee71164
--- /dev/null
+++ b/db/migrate/20200615234047_create_clusters_applications_cilium.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+
+class CreateClustersApplicationsCilium < ActiveRecord::Migration[6.0]
+ DOWNTIME = false
+
+ def change
+ create_table :clusters_applications_cilium do |t|
+ t.references :cluster, null: false, index: { unique: true }, foreign_key: { on_delete: :cascade }
+ t.timestamps_with_timezone null: false
+ t.integer :status, null: false
+ t.text :status_reason # rubocop:disable Migration/AddLimitToTextColumns
+ end
+ end
+end
diff --git a/db/migrate/20200616124338_add_plan_limits_for_max_size_per_artifact_type.rb b/db/migrate/20200616124338_add_plan_limits_for_max_size_per_artifact_type.rb
new file mode 100644
index 00000000000..28fadb495dd
--- /dev/null
+++ b/db/migrate/20200616124338_add_plan_limits_for_max_size_per_artifact_type.rb
@@ -0,0 +1,48 @@
+# frozen_string_literal: true
+
+class AddPlanLimitsForMaxSizePerArtifactType < ActiveRecord::Migration[6.0]
+ DOWNTIME = false
+
+ def change
+ # We need to set the 20mb default for lsif for backward compatibility
+ # See https://gitlab.com/gitlab-org/gitlab/-/merge_requests/34767#note_371619075
+ add_column :plan_limits, "ci_max_artifact_size_lsif", :integer, default: 20, null: false
+
+ artifact_types.each do |type|
+ add_column :plan_limits, "ci_max_artifact_size_#{type}", :integer, default: 0, null: false
+ end
+ end
+
+ private
+
+ def artifact_types
+ # The list of artifact types (except lsif) from Ci::JobArtifact file_type enum as of this writing.
+ # Intentionally duplicated so that the migration won't change behavior
+ # if ever we remove or add more to the list later on.
+ %w[
+ archive
+ metadata
+ trace
+ junit
+ sast
+ dependency_scanning
+ container_scanning
+ dast
+ codequality
+ license_management
+ license_scanning
+ performance
+ metrics
+ metrics_referee
+ network_referee
+ dotenv
+ cobertura
+ terraform
+ accessibility
+ cluster_applications
+ secret_detection
+ requirements
+ coverage_fuzzing
+ ]
+ end
+end
diff --git a/db/migrate/20200616145031_add_author_id_index_to_audit_events.rb b/db/migrate/20200616145031_add_author_id_index_to_audit_events.rb
new file mode 100644
index 00000000000..cb06fbe2b11
--- /dev/null
+++ b/db/migrate/20200616145031_add_author_id_index_to_audit_events.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+class AddAuthorIdIndexToAuditEvents < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ INDEX_NAME = 'index_audit_events_on_entity_id_entity_type_id_desc_author_id'
+ OLD_INDEX_NAME = 'index_audit_events_on_entity_id_and_entity_type_and_id_desc'
+
+ def up
+ add_concurrent_index(:audit_events, [:entity_id, :entity_type, :id, :author_id], order: { id: :desc }, name: INDEX_NAME)
+ remove_concurrent_index_by_name(:audit_events, OLD_INDEX_NAME)
+ end
+
+ def down
+ add_concurrent_index(:audit_events, [:entity_id, :entity_type, :id], order: { id: :desc }, name: OLD_INDEX_NAME)
+ remove_concurrent_index_by_name(:audit_events, INDEX_NAME)
+ end
+end
diff --git a/db/migrate/20200617000757_clean_up_file_store_lfs_objects.rb b/db/migrate/20200617000757_clean_up_file_store_lfs_objects.rb
new file mode 100644
index 00000000000..a1a86355d02
--- /dev/null
+++ b/db/migrate/20200617000757_clean_up_file_store_lfs_objects.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+class CleanUpFileStoreLfsObjects < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ # rubocop:disable Migration/UpdateColumnInBatches
+ update_column_in_batches(:lfs_objects, :file_store, 1) do |table, query|
+ query.where(table[:file_store].eq(nil))
+ end
+ # rubocop:enable Migration/UpdateColumnInBatches
+ end
+
+ def down
+ # no-op
+ end
+end
diff --git a/db/migrate/20200617001001_clean_up_store_uploads.rb b/db/migrate/20200617001001_clean_up_store_uploads.rb
new file mode 100644
index 00000000000..970d0485b0d
--- /dev/null
+++ b/db/migrate/20200617001001_clean_up_store_uploads.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+class CleanUpStoreUploads < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ # rubocop:disable Migration/UpdateColumnInBatches
+ update_column_in_batches(:uploads, :store, 1) do |table, query|
+ query.where(table[:store].eq(nil))
+ end
+ # rubocop:enable Migration/UpdateColumnInBatches
+ end
+
+ def down
+ # no-op
+ end
+end
diff --git a/db/migrate/20200617001118_clean_up_file_store_ci_job_artifacts.rb b/db/migrate/20200617001118_clean_up_file_store_ci_job_artifacts.rb
new file mode 100644
index 00000000000..e30f7e342e5
--- /dev/null
+++ b/db/migrate/20200617001118_clean_up_file_store_ci_job_artifacts.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+class CleanUpFileStoreCiJobArtifacts < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ # rubocop:disable Migration/UpdateColumnInBatches
+ # rubocop:disable Migration/UpdateLargeTable
+ update_column_in_batches(:ci_job_artifacts, :file_store, 1) do |table, query|
+ query.where(table[:file_store].eq(nil))
+ end
+ # rubocop:enable Migration/UpdateColumnInBatches
+ # rubocop:enable Migration/UpdateLargeTable
+ end
+
+ def down
+ # no-op
+ end
+end
diff --git a/db/migrate/20200617150041_create_namespace_limits.rb b/db/migrate/20200617150041_create_namespace_limits.rb
new file mode 100644
index 00000000000..59a014ff7ca
--- /dev/null
+++ b/db/migrate/20200617150041_create_namespace_limits.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+class CreateNamespaceLimits < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ def up
+ with_lock_retries do
+ create_table :namespace_limits, id: false do |t|
+ t.bigint :additional_purchased_storage_size, default: 0, null: false
+ t.date :additional_purchased_storage_ends_on, null: true
+
+ t.references :namespace, primary_key: true, default: nil, type: :integer, index: false, foreign_key: { on_delete: :cascade }
+ end
+ end
+ end
+
+ def down
+ drop_table :namespace_limits
+ end
+end
diff --git a/db/migrate/20200617205000_add_deploy_key_id_to_push_access_levels.rb b/db/migrate/20200617205000_add_deploy_key_id_to_push_access_levels.rb
new file mode 100644
index 00000000000..11b92c2a321
--- /dev/null
+++ b/db/migrate/20200617205000_add_deploy_key_id_to_push_access_levels.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+class AddDeployKeyIdToPushAccessLevels < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ unless column_exists?(:protected_branch_push_access_levels, :deploy_key_id)
+ add_column :protected_branch_push_access_levels, :deploy_key_id, :integer
+ end
+
+ add_concurrent_foreign_key :protected_branch_push_access_levels, :keys, column: :deploy_key_id, on_delete: :cascade
+ add_concurrent_index :protected_branch_push_access_levels, :deploy_key_id, name: 'index_deploy_key_id_on_protected_branch_push_access_levels'
+ end
+
+ def down
+ remove_column :protected_branch_push_access_levels, :deploy_key_id
+ end
+end
diff --git a/db/migrate/20200618105638_add_index_on_id_and_created_at_to_snippets.rb b/db/migrate/20200618105638_add_index_on_id_and_created_at_to_snippets.rb
new file mode 100644
index 00000000000..d9ef3b18e9b
--- /dev/null
+++ b/db/migrate/20200618105638_add_index_on_id_and_created_at_to_snippets.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+class AddIndexOnIdAndCreatedAtToSnippets < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ add_concurrent_index :snippets, [:id, :created_at]
+ end
+
+ def down
+ remove_concurrent_index :snippets, [:id, :created_at]
+ end
+end
diff --git a/db/migrate/20200618134223_restore_previous_schema_without_lock_version_null_constraint.rb b/db/migrate/20200618134223_restore_previous_schema_without_lock_version_null_constraint.rb
new file mode 100644
index 00000000000..85e98d05d02
--- /dev/null
+++ b/db/migrate/20200618134223_restore_previous_schema_without_lock_version_null_constraint.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+
+class RestorePreviousSchemaWithoutLockVersionNullConstraint < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+ TABLES = %i(epics merge_requests issues ci_stages ci_builds ci_pipelines).freeze
+
+ disable_ddl_transaction!
+
+ def up
+ TABLES.each do |table|
+ remove_not_null_constraint table, :lock_version
+ end
+ end
+
+ def down
+ # no-op
+ end
+end
diff --git a/db/migrate/20200618134723_restore_previous_schema_with_lock_version_indices.rb b/db/migrate/20200618134723_restore_previous_schema_with_lock_version_indices.rb
new file mode 100644
index 00000000000..64fdb373387
--- /dev/null
+++ b/db/migrate/20200618134723_restore_previous_schema_with_lock_version_indices.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+class RestorePreviousSchemaWithLockVersionIndices < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ add_concurrent_index :issues, :lock_version, where: "lock_version IS NULL"
+ add_concurrent_index :merge_requests, :lock_version, where: "lock_version IS NULL"
+ add_concurrent_index :epics, :lock_version, where: "lock_version IS NULL"
+ add_concurrent_index :ci_stages, :id, where: "lock_version IS NULL", name: "tmp_index_ci_stages_lock_version"
+ add_concurrent_index :ci_builds, :id, where: "lock_version IS NULL", name: "tmp_index_ci_builds_lock_version"
+ add_concurrent_index :ci_pipelines, :id, where: "lock_version IS NULL", name: "tmp_index_ci_pipelines_lock_version"
+ end
+
+ def down
+ # no-op
+ end
+end
diff --git a/db/migrate/20200619000316_add_has_confluence_to_project_settings.rb b/db/migrate/20200619000316_add_has_confluence_to_project_settings.rb
new file mode 100644
index 00000000000..0f33e0bc47a
--- /dev/null
+++ b/db/migrate/20200619000316_add_has_confluence_to_project_settings.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+class AddHasConfluenceToProjectSettings < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ def up
+ with_lock_retries do
+ add_column :project_settings, :has_confluence, :boolean, default: false, null: false
+ end
+ end
+
+ def down
+ with_lock_retries do
+ remove_column :project_settings, :has_confluence
+ end
+ end
+end
diff --git a/db/migrate/20200619154527_add_project_key_to_jira_tracker_data.rb b/db/migrate/20200619154527_add_project_key_to_jira_tracker_data.rb
new file mode 100644
index 00000000000..574eb99a6cc
--- /dev/null
+++ b/db/migrate/20200619154527_add_project_key_to_jira_tracker_data.rb
@@ -0,0 +1,12 @@
+# frozen_string_literal: true
+
+class AddProjectKeyToJiraTrackerData < ActiveRecord::Migration[6.0]
+ DOWNTIME = false
+
+ # rubocop:disable Migration/AddLimitToTextColumns
+ # limit is added in 20200619154528_add_text_limit_to_jira_tracker_data_project_key
+ def change
+ add_column :jira_tracker_data, :project_key, :text
+ end
+ # rubocop:enable Migration/AddLimitToTextColumns
+end
diff --git a/db/migrate/20200619154528_add_text_limit_to_jira_tracker_data_project_key.rb b/db/migrate/20200619154528_add_text_limit_to_jira_tracker_data_project_key.rb
new file mode 100644
index 00000000000..6cf8a787381
--- /dev/null
+++ b/db/migrate/20200619154528_add_text_limit_to_jira_tracker_data_project_key.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+class AddTextLimitToJiraTrackerDataProjectKey < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ add_text_limit :jira_tracker_data, :project_key, 255
+ end
+
+ def down
+ remove_text_limit :jira_tracker_data, :project_key
+ end
+end
diff --git a/db/migrate/20200622040750_add_prometheus_alert_id_to_alert_management_alerts.rb b/db/migrate/20200622040750_add_prometheus_alert_id_to_alert_management_alerts.rb
new file mode 100644
index 00000000000..e7669db0a28
--- /dev/null
+++ b/db/migrate/20200622040750_add_prometheus_alert_id_to_alert_management_alerts.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+class AddPrometheusAlertIdToAlertManagementAlerts < ActiveRecord::Migration[6.0]
+ DOWNTIME = false
+
+ def up
+ add_column :alert_management_alerts, :prometheus_alert_id, :integer
+
+ add_column :alert_management_alerts, :environment_id, :integer
+ end
+
+ def down
+ remove_column :alert_management_alerts, :prometheus_alert_id
+ remove_column :alert_management_alerts, :environment_id
+ end
+end
diff --git a/db/migrate/20200622070606_add_vendor_to_vulnerability_scanners.rb b/db/migrate/20200622070606_add_vendor_to_vulnerability_scanners.rb
new file mode 100644
index 00000000000..2c9c0a9110d
--- /dev/null
+++ b/db/migrate/20200622070606_add_vendor_to_vulnerability_scanners.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+class AddVendorToVulnerabilityScanners < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+ DEFAULT_SCANNER_VENDOR = 'GitLab'
+
+ # rubocop:disable Migration/AddLimitToTextColumns
+ # limit is added in 20200622070620_add_limit_to_vulnerability_scanners_vendor
+ def up
+ with_lock_retries do
+ add_column :vulnerability_scanners, :vendor, :text, default: DEFAULT_SCANNER_VENDOR, null: false
+ end
+ end
+
+ def down
+ with_lock_retries do
+ remove_column :vulnerability_scanners, :vendor
+ end
+ end
+ # rubocop:enable Migration/AddLimitToTextColumns
+end
diff --git a/db/migrate/20200622070620_add_limit_to_vulnerability_scanners_vendor.rb b/db/migrate/20200622070620_add_limit_to_vulnerability_scanners_vendor.rb
new file mode 100644
index 00000000000..efcbbde3e29
--- /dev/null
+++ b/db/migrate/20200622070620_add_limit_to_vulnerability_scanners_vendor.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+class AddLimitToVulnerabilityScannersVendor < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ add_text_limit :vulnerability_scanners, :vendor, 255, validate: false
+ end
+
+ def down
+ remove_text_limit :vulnerability_scanners, :vendor
+ end
+end
diff --git a/db/migrate/20200622095419_add_snippets_size_to_project_statistics.rb b/db/migrate/20200622095419_add_snippets_size_to_project_statistics.rb
new file mode 100644
index 00000000000..9fe470478b8
--- /dev/null
+++ b/db/migrate/20200622095419_add_snippets_size_to_project_statistics.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+class AddSnippetsSizeToProjectStatistics < ActiveRecord::Migration[6.0]
+ DOWNTIME = false
+
+ def change
+ add_column :project_statistics, :snippets_size, :bigint
+ end
+end
diff --git a/db/migrate/20200622103836_create_snippet_statistics.rb b/db/migrate/20200622103836_create_snippet_statistics.rb
new file mode 100644
index 00000000000..691a9acdc04
--- /dev/null
+++ b/db/migrate/20200622103836_create_snippet_statistics.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+
+class CreateSnippetStatistics < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ def up
+ with_lock_retries do
+ create_table :snippet_statistics, id: false do |t|
+ t.references :snippet, primary_key: true, default: nil, index: false, foreign_key: { on_delete: :cascade }
+ t.bigint :repository_size, default: 0, null: false
+ t.bigint :file_count, default: 0, null: false
+ t.bigint :commit_count, default: 0, null: false
+ end
+ end
+ end
+
+ def down
+ with_lock_retries do
+ drop_table :snippet_statistics
+ end
+ end
+end
diff --git a/db/migrate/20200622104923_create_ci_pipeline_messages_table.rb b/db/migrate/20200622104923_create_ci_pipeline_messages_table.rb
new file mode 100644
index 00000000000..40d63b1189e
--- /dev/null
+++ b/db/migrate/20200622104923_create_ci_pipeline_messages_table.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+class CreateCiPipelineMessagesTable < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+ ERROR_SEVERITY = 0
+ MAX_CONTENT_LENGTH = 10_000
+
+ disable_ddl_transaction!
+
+ def up
+ with_lock_retries do
+ create_table :ci_pipeline_messages do |t|
+ t.integer :severity, null: false, default: ERROR_SEVERITY, limit: 2
+ t.references :pipeline, index: true, null: false, foreign_key: { to_table: :ci_pipelines, on_delete: :cascade }, type: :integer
+ t.text :content, null: false
+ end
+ end
+
+ add_text_limit :ci_pipeline_messages, :content, MAX_CONTENT_LENGTH
+ end
+
+ def down
+ drop_table :ci_pipeline_messages
+ end
+end
diff --git a/db/migrate/20200622235737_remove_index_ci_job_artifacts_file_store_is_null.rb b/db/migrate/20200622235737_remove_index_ci_job_artifacts_file_store_is_null.rb
new file mode 100644
index 00000000000..e293bcfa1ce
--- /dev/null
+++ b/db/migrate/20200622235737_remove_index_ci_job_artifacts_file_store_is_null.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+class RemoveIndexCiJobArtifactsFileStoreIsNull < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+ INDEX_NAME = 'index_ci_job_artifacts_file_store_is_null'
+
+ disable_ddl_transaction!
+
+ def up
+ remove_concurrent_index_by_name(:ci_job_artifacts, INDEX_NAME)
+ end
+
+ def down
+ add_concurrent_index(:ci_job_artifacts, :id, where: "file_store IS NULL", name: INDEX_NAME)
+ end
+end
diff --git a/db/migrate/20200623000148_remove_index_lfs_objects_file_store_is_null.rb b/db/migrate/20200623000148_remove_index_lfs_objects_file_store_is_null.rb
new file mode 100644
index 00000000000..76faa5c4cd2
--- /dev/null
+++ b/db/migrate/20200623000148_remove_index_lfs_objects_file_store_is_null.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+class RemoveIndexLfsObjectsFileStoreIsNull < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+ INDEX_NAME = 'index_lfs_objects_file_store_is_null'
+
+ disable_ddl_transaction!
+
+ def up
+ remove_concurrent_index_by_name(:lfs_objects, INDEX_NAME)
+ end
+
+ def down
+ add_concurrent_index(:lfs_objects, :id, where: "file_store IS NULL", name: INDEX_NAME)
+ end
+end
diff --git a/db/migrate/20200623000320_remove_index_uploads_store_is_null.rb b/db/migrate/20200623000320_remove_index_uploads_store_is_null.rb
new file mode 100644
index 00000000000..ad84cd5a649
--- /dev/null
+++ b/db/migrate/20200623000320_remove_index_uploads_store_is_null.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+class RemoveIndexUploadsStoreIsNull < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+ INDEX_NAME = 'index_uploads_store_is_null'
+
+ disable_ddl_transaction!
+
+ def up
+ remove_concurrent_index_by_name(:uploads, INDEX_NAME)
+ end
+
+ def down
+ add_concurrent_index(:uploads, :id, where: "store IS NULL", name: INDEX_NAME)
+ end
+end
diff --git a/db/migrate/20200623073431_add_source_merge_request_id_to_resource_state_events.rb b/db/migrate/20200623073431_add_source_merge_request_id_to_resource_state_events.rb
new file mode 100644
index 00000000000..8970797d3c0
--- /dev/null
+++ b/db/migrate/20200623073431_add_source_merge_request_id_to_resource_state_events.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+class AddSourceMergeRequestIdToResourceStateEvents < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::MigrationHelpers
+
+ INDEX_NAME = 'index_resource_state_events_on_source_merge_request_id'
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ unless column_exists?(:resource_state_events, :source_merge_request_id)
+ add_column :resource_state_events, :source_merge_request_id, :bigint
+ end
+
+ unless index_exists?(:resource_state_events, :source_merge_request_id, name: INDEX_NAME)
+ add_index :resource_state_events, :source_merge_request_id, name: INDEX_NAME # rubocop: disable Migration/AddIndex
+ end
+
+ unless foreign_key_exists?(:resource_state_events, :merge_requests, column: :source_merge_request_id)
+ with_lock_retries do
+ add_foreign_key :resource_state_events, :merge_requests, column: :source_merge_request_id, on_delete: :nullify # rubocop:disable Migration/AddConcurrentForeignKey
+ end
+ end
+ end
+
+ def down
+ with_lock_retries do
+ remove_column :resource_state_events, :source_merge_request_id
+ end
+ end
+end
diff --git a/db/migrate/20200623090030_add_author_name_to_audit_event.rb b/db/migrate/20200623090030_add_author_name_to_audit_event.rb
new file mode 100644
index 00000000000..8b45503189a
--- /dev/null
+++ b/db/migrate/20200623090030_add_author_name_to_audit_event.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+class AddAuthorNameToAuditEvent < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+ disable_ddl_transaction!
+
+ def up
+ unless column_exists?(:audit_events, :author_name)
+ with_lock_retries do
+ add_column :audit_events, :author_name, :text
+ end
+ end
+
+ add_text_limit :audit_events, :author_name, 255
+ end
+
+ def down
+ remove_column :audit_events, :author_name
+ end
+end
diff --git a/db/migrate/20200623121135_create_dynamic_partitions_schema.rb b/db/migrate/20200623121135_create_dynamic_partitions_schema.rb
new file mode 100644
index 00000000000..931a55ebcf4
--- /dev/null
+++ b/db/migrate/20200623121135_create_dynamic_partitions_schema.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+class CreateDynamicPartitionsSchema < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::SchemaHelpers
+
+ DOWNTIME = false
+
+ def up
+ execute 'CREATE SCHEMA gitlab_partitions_dynamic'
+
+ create_comment(:schema, :gitlab_partitions_dynamic, <<~EOS.strip)
+ Schema to hold partitions managed dynamically from the application, e.g. for time space partitioning.
+ EOS
+ end
+
+ def down
+ execute 'DROP SCHEMA gitlab_partitions_dynamic'
+ end
+end
diff --git a/db/migrate/20200623141217_add_view_diffs_file_by_file_to_user_preferences.rb b/db/migrate/20200623141217_add_view_diffs_file_by_file_to_user_preferences.rb
new file mode 100644
index 00000000000..9ea38bd4ab4
--- /dev/null
+++ b/db/migrate/20200623141217_add_view_diffs_file_by_file_to_user_preferences.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+class AddViewDiffsFileByFileToUserPreferences < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ with_lock_retries do
+ add_column :user_preferences, :view_diffs_file_by_file, :boolean, default: false, null: false
+ end
+ end
+
+ def down
+ with_lock_retries do
+ remove_column :user_preferences, :view_diffs_file_by_file, :boolean
+ end
+ end
+end
diff --git a/db/migrate/20200623141544_create_elastic_reindexing_task.rb b/db/migrate/20200623141544_create_elastic_reindexing_task.rb
new file mode 100644
index 00000000000..7089df4f1ea
--- /dev/null
+++ b/db/migrate/20200623141544_create_elastic_reindexing_task.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+class CreateElasticReindexingTask < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ create_table :elastic_reindexing_tasks do |t|
+ t.timestamps_with_timezone null: false
+ t.integer :documents_count
+ t.integer :state, null: false, default: 0, limit: 2, index: true
+ t.boolean :in_progress, null: false, default: true
+ t.text :index_name_from
+ t.text :index_name_to
+ t.text :elastic_task
+ t.text :error_message
+ end
+
+ add_text_limit :elastic_reindexing_tasks, :index_name_from, 255
+ add_text_limit :elastic_reindexing_tasks, :index_name_to, 255
+ add_text_limit :elastic_reindexing_tasks, :elastic_task, 255
+ add_text_limit :elastic_reindexing_tasks, :error_message, 255
+
+ add_index :elastic_reindexing_tasks, :in_progress, unique: true, where: 'in_progress'
+ end
+
+ def down
+ drop_table :elastic_reindexing_tasks
+ end
+end
diff --git a/db/migrate/20200623170000_create_static_partitions_schema.rb b/db/migrate/20200623170000_create_static_partitions_schema.rb
new file mode 100644
index 00000000000..d8878d2fe33
--- /dev/null
+++ b/db/migrate/20200623170000_create_static_partitions_schema.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+class CreateStaticPartitionsSchema < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::SchemaHelpers
+
+ DOWNTIME = false
+
+ def up
+ execute 'CREATE SCHEMA gitlab_partitions_static'
+
+ create_comment(:schema, :gitlab_partitions_static, <<~EOS.strip)
+ Schema to hold static partitions, e.g. for hash partitioning
+ EOS
+ end
+
+ def down
+ execute 'DROP SCHEMA gitlab_partitions_static'
+ end
+end
diff --git a/db/migrate/20200623185440_add_product_analytics_table.rb b/db/migrate/20200623185440_add_product_analytics_table.rb
new file mode 100644
index 00000000000..0a0d438bfb9
--- /dev/null
+++ b/db/migrate/20200623185440_add_product_analytics_table.rb
@@ -0,0 +1,202 @@
+# frozen_string_literal: true
+
+class AddProductAnalyticsTable < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::MigrationHelpers
+ include Gitlab::Database::PartitioningMigrationHelpers::TableManagementHelpers
+
+ # Set this constant to true if this migration requires downtime.
+ DOWNTIME = false
+
+ # Table is based on https://github.com/snowplow/snowplow/blob/master/4-storage/postgres-storage/sql/atomic-def.sql 6e07b1c, with the following differences:
+ # * app_id varchar -> project_id integer (+ FK)
+ # * Add `id bigserial`
+ # * Hash partitioning based on `project_id`
+ # * Timestamp columns: Change type to timestamp with time zone
+ #
+ # This table is part of the "product analytics experiment" and as such marked "experimental". The goal here is to
+ # explore the product analytics as a MVP feature more. We are explicitly not spending time on relational modeling
+ # here.
+ #
+ # We expect significant changes to the database part of this once the feature has been validated.
+ # Therefore, we expect to drop the table when feature validation is complete. All data will be lost.
+ def up
+ with_lock_retries do
+ execute <<~SQL
+ CREATE TABLE "product_analytics_events_experimental" (
+ id bigserial NOT NULL,
+ -- App
+ "project_id" integer NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
+ "platform" varchar(255),
+ -- Date/time
+ "etl_tstamp" timestamp with time zone,
+ "collector_tstamp" timestamp with time zone NOT NULL,
+ "dvce_created_tstamp" timestamp with time zone,
+ -- Date/time
+ "event" varchar(128),
+ "event_id" char(36) NOT NULL,
+ "txn_id" integer,
+ -- Versioning
+ "name_tracker" varchar(128),
+ "v_tracker" varchar(100),
+ "v_collector" varchar(100) NOT NULL,
+ "v_etl" varchar(100) NOT NULL,
+ -- User and visit
+ "user_id" varchar(255),
+ "user_ipaddress" varchar(45),
+ "user_fingerprint" varchar(50),
+ "domain_userid" varchar(36),
+ "domain_sessionidx" smallint,
+ "network_userid" varchar(38),
+ -- Location
+ "geo_country" char(2),
+ "geo_region" char(3),
+ "geo_city" varchar(75),
+ "geo_zipcode" varchar(15),
+ "geo_latitude" double precision,
+ "geo_longitude" double precision,
+ "geo_region_name" varchar(100),
+ -- IP lookups
+ "ip_isp" varchar(100),
+ "ip_organization" varchar(100),
+ "ip_domain" varchar(100),
+ "ip_netspeed" varchar(100),
+ -- Page
+ "page_url" text,
+ "page_title" varchar(2000),
+ "page_referrer" text,
+ -- Page URL components
+ "page_urlscheme" varchar(16),
+ "page_urlhost" varchar(255),
+ "page_urlport" integer,
+ "page_urlpath" varchar(3000),
+ "page_urlquery" varchar(6000),
+ "page_urlfragment" varchar(3000),
+ -- Referrer URL components
+ "refr_urlscheme" varchar(16),
+ "refr_urlhost" varchar(255),
+ "refr_urlport" integer,
+ "refr_urlpath" varchar(6000),
+ "refr_urlquery" varchar(6000),
+ "refr_urlfragment" varchar(3000),
+ -- Referrer details
+ "refr_medium" varchar(25),
+ "refr_source" varchar(50),
+ "refr_term" varchar(255),
+ -- Marketing
+ "mkt_medium" varchar(255),
+ "mkt_source" varchar(255),
+ "mkt_term" varchar(255),
+ "mkt_content" varchar(500),
+ "mkt_campaign" varchar(255),
+ -- Custom structured event
+ "se_category" varchar(1000),
+ "se_action" varchar(1000),
+ "se_label" varchar(1000),
+ "se_property" varchar(1000),
+ "se_value" double precision,
+ -- Ecommerce
+ "tr_orderid" varchar(255),
+ "tr_affiliation" varchar(255),
+ "tr_total" decimal(18,2),
+ "tr_tax" decimal(18,2),
+ "tr_shipping" decimal(18,2),
+ "tr_city" varchar(255),
+ "tr_state" varchar(255),
+ "tr_country" varchar(255),
+ "ti_orderid" varchar(255),
+ "ti_sku" varchar(255),
+ "ti_name" varchar(255),
+ "ti_category" varchar(255),
+ "ti_price" decimal(18,2),
+ "ti_quantity" integer,
+ -- Page ping
+ "pp_xoffset_min" integer,
+ "pp_xoffset_max" integer,
+ "pp_yoffset_min" integer,
+ "pp_yoffset_max" integer,
+ -- User Agent
+ "useragent" varchar(1000),
+ -- Browser
+ "br_name" varchar(50),
+ "br_family" varchar(50),
+ "br_version" varchar(50),
+ "br_type" varchar(50),
+ "br_renderengine" varchar(50),
+ "br_lang" varchar(255),
+ "br_features_pdf" boolean,
+ "br_features_flash" boolean,
+ "br_features_java" boolean,
+ "br_features_director" boolean,
+ "br_features_quicktime" boolean,
+ "br_features_realplayer" boolean,
+ "br_features_windowsmedia" boolean,
+ "br_features_gears" boolean,
+ "br_features_silverlight" boolean,
+ "br_cookies" boolean,
+ "br_colordepth" varchar(12),
+ "br_viewwidth" integer,
+ "br_viewheight" integer,
+ -- Operating System
+ "os_name" varchar(50),
+ "os_family" varchar(50),
+ "os_manufacturer" varchar(50),
+ "os_timezone" varchar(50),
+ -- Device/Hardware
+ "dvce_type" varchar(50),
+ "dvce_ismobile" boolean,
+ "dvce_screenwidth" integer,
+ "dvce_screenheight" integer,
+ -- Document
+ "doc_charset" varchar(128),
+ "doc_width" integer,
+ "doc_height" integer,
+ -- Currency
+ "tr_currency" char(3),
+ "tr_total_base" decimal(18, 2),
+ "tr_tax_base" decimal(18, 2),
+ "tr_shipping_base" decimal(18, 2),
+ "ti_currency" char(3),
+ "ti_price_base" decimal(18, 2),
+ "base_currency" char(3),
+ -- Geolocation
+ "geo_timezone" varchar(64),
+ -- Click ID
+ "mkt_clickid" varchar(128),
+ "mkt_network" varchar(64),
+ -- ETL tags
+ "etl_tags" varchar(500),
+ -- Time event was sent
+ "dvce_sent_tstamp" timestamp with time zone,
+ -- Referer
+ "refr_domain_userid" varchar(36),
+ "refr_dvce_tstamp" timestamp with time zone,
+ -- Session ID
+ "domain_sessionid" char(36),
+ -- Derived timestamp
+ "derived_tstamp" timestamp with time zone,
+ -- Event schema
+ "event_vendor" varchar(1000),
+ "event_name" varchar(1000),
+ "event_format" varchar(128),
+ "event_version" varchar(128),
+ -- Event fingerprint
+ "event_fingerprint" varchar(128),
+ -- True timestamp
+ "true_tstamp" timestamp with time zone,
+ PRIMARY KEY (id, project_id)
+ ) PARTITION BY HASH (project_id)
+ WITHOUT OIDS;
+
+ CREATE INDEX index_product_analytics_events_experimental_project_and_time ON product_analytics_events_experimental (project_id, collector_tstamp);
+ SQL
+
+ create_hash_partitions :product_analytics_events_experimental, 64
+ end
+ end
+
+ def down
+ with_lock_retries do
+ execute 'DROP TABLE product_analytics_events_experimental'
+ end
+ end
+end
diff --git a/db/migrate/20200624075411_add_storage_size_limit_to_plan_limit.rb b/db/migrate/20200624075411_add_storage_size_limit_to_plan_limit.rb
new file mode 100644
index 00000000000..7d1b081be02
--- /dev/null
+++ b/db/migrate/20200624075411_add_storage_size_limit_to_plan_limit.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+class AddStorageSizeLimitToPlanLimit < ActiveRecord::Migration[6.0]
+ DOWNTIME = false
+
+ def change
+ add_column :plan_limits, :storage_size_limit, :integer, default: 0, null: false
+ end
+end
diff --git a/db/migrate/20200624142107_create_analytics_cycle_analytics_group_value_streams.rb b/db/migrate/20200624142107_create_analytics_cycle_analytics_group_value_streams.rb
new file mode 100644
index 00000000000..24afe463684
--- /dev/null
+++ b/db/migrate/20200624142107_create_analytics_cycle_analytics_group_value_streams.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+class CreateAnalyticsCycleAnalyticsGroupValueStreams < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+ INDEX_NAME = 'index_analytics_ca_group_value_streams_on_group_id_and_name'
+
+ disable_ddl_transaction!
+
+ def up
+ unless table_exists?(:analytics_cycle_analytics_group_value_streams)
+ with_lock_retries do
+ create_table :analytics_cycle_analytics_group_value_streams do |t|
+ t.timestamps_with_timezone
+ t.references(:group, {
+ null: false,
+ index: false,
+ foreign_key: { to_table: :namespaces, on_delete: :cascade }
+ })
+ t.text :name, null: false
+ t.index [:group_id, :name], unique: true, name: INDEX_NAME
+ end
+ end
+ end
+
+ add_text_limit :analytics_cycle_analytics_group_value_streams, :name, 100
+ end
+
+ def down
+ drop_table :analytics_cycle_analytics_group_value_streams
+ end
+end
diff --git a/db/migrate/20200624142207_add_group_value_stream_to_cycle_analytics_group_stages.rb b/db/migrate/20200624142207_add_group_value_stream_to_cycle_analytics_group_stages.rb
new file mode 100644
index 00000000000..3ce912eb440
--- /dev/null
+++ b/db/migrate/20200624142207_add_group_value_stream_to_cycle_analytics_group_stages.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+class AddGroupValueStreamToCycleAnalyticsGroupStages < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ def up
+ with_lock_retries do
+ add_column :analytics_cycle_analytics_group_stages, :group_value_stream_id, :bigint
+ end
+ end
+
+ def down
+ with_lock_retries do
+ remove_column :analytics_cycle_analytics_group_stages, :group_value_stream_id
+ end
+ end
+end
diff --git a/db/migrate/20200624222443_add_default_branch_name_to_application_settings.rb b/db/migrate/20200624222443_add_default_branch_name_to_application_settings.rb
new file mode 100644
index 00000000000..5da9006e9a0
--- /dev/null
+++ b/db/migrate/20200624222443_add_default_branch_name_to_application_settings.rb
@@ -0,0 +1,12 @@
+# frozen_string_literal: true
+
+class AddDefaultBranchNameToApplicationSettings < ActiveRecord::Migration[6.0]
+ DOWNTIME = false
+
+ # rubocop:disable Migration/AddLimitToTextColumns
+ # limit is added in db/migrate/20200625190458_add_limit_to_default_branch_name_to_application_settings
+ def change
+ add_column :application_settings, :default_branch_name, :text
+ end
+ # rubocop:enable Migration/AddLimitToTextColumns
+end
diff --git a/db/migrate/20200625045442_add_idx_and_fk_for_prometheus_and_environment_to_alert_management_alerts.rb b/db/migrate/20200625045442_add_idx_and_fk_for_prometheus_and_environment_to_alert_management_alerts.rb
new file mode 100644
index 00000000000..f4869b15735
--- /dev/null
+++ b/db/migrate/20200625045442_add_idx_and_fk_for_prometheus_and_environment_to_alert_management_alerts.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+class AddIdxAndFkForPrometheusAndEnvironmentToAlertManagementAlerts < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ add_concurrent_index :alert_management_alerts, :prometheus_alert_id, where: 'prometheus_alert_id is not null'
+ add_concurrent_foreign_key :alert_management_alerts, :prometheus_alerts, column: :prometheus_alert_id, on_delete: :cascade
+
+ add_concurrent_index :alert_management_alerts, :environment_id, where: 'environment_id is not null'
+ add_concurrent_foreign_key :alert_management_alerts, :environments, column: :environment_id, on_delete: :nullify
+ end
+
+ def down
+ remove_concurrent_index :alert_management_alerts, :prometheus_alert_id
+ remove_foreign_key_without_error :alert_management_alerts, column: :prometheus_alert_id
+
+ remove_concurrent_index :alert_management_alerts, :environment_id
+ remove_foreign_key_without_error :alert_management_alerts, column: :environment_id
+ end
+end
diff --git a/db/migrate/20200625082258_add_snippets_size_to_root_storage_statistics.rb b/db/migrate/20200625082258_add_snippets_size_to_root_storage_statistics.rb
new file mode 100644
index 00000000000..7dd0bd94805
--- /dev/null
+++ b/db/migrate/20200625082258_add_snippets_size_to_root_storage_statistics.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+class AddSnippetsSizeToRootStorageStatistics < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ def up
+ with_lock_retries do
+ add_column :namespace_root_storage_statistics, :snippets_size, :bigint, default: 0, null: false
+ end
+ end
+
+ def down
+ with_lock_retries do
+ remove_column :namespace_root_storage_statistics, :snippets_size
+ end
+ end
+end
diff --git a/db/migrate/20200625113337_add_last_used_to_personal_access_tokens.rb b/db/migrate/20200625113337_add_last_used_to_personal_access_tokens.rb
new file mode 100644
index 00000000000..c9d155eb628
--- /dev/null
+++ b/db/migrate/20200625113337_add_last_used_to_personal_access_tokens.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+class AddLastUsedToPersonalAccessTokens < ActiveRecord::Migration[6.0]
+ DOWNTIME = false
+
+ include Gitlab::Database::MigrationHelpers
+
+ def up
+ with_lock_retries do
+ add_column :personal_access_tokens, :last_used_at, :datetime_with_timezone
+ end
+ end
+
+ def down
+ with_lock_retries do
+ remove_column :personal_access_tokens, :last_used_at, :datetime_with_timezone
+ end
+ end
+end
diff --git a/db/migrate/20200625174052_add_partial_index_to_locked_pipelines.rb b/db/migrate/20200625174052_add_partial_index_to_locked_pipelines.rb
new file mode 100644
index 00000000000..85f706f5d31
--- /dev/null
+++ b/db/migrate/20200625174052_add_partial_index_to_locked_pipelines.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+class AddPartialIndexToLockedPipelines < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ add_concurrent_index :ci_pipelines, [:ci_ref_id, :id], name: 'idx_ci_pipelines_artifacts_locked', where: 'locked = 1'
+ end
+
+ def down
+ remove_concurrent_index :ci_pipelines, 'idx_ci_pipelines_artifacts_locked'
+ end
+end
diff --git a/db/migrate/20200625190458_add_limit_to_default_branch_name_to_application_settings.rb b/db/migrate/20200625190458_add_limit_to_default_branch_name_to_application_settings.rb
new file mode 100644
index 00000000000..b11eb514577
--- /dev/null
+++ b/db/migrate/20200625190458_add_limit_to_default_branch_name_to_application_settings.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+class AddLimitToDefaultBranchNameToApplicationSettings < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ add_text_limit :application_settings, :default_branch_name, 255
+ end
+
+ def down
+ remove_text_limit :application_settings, :default_branch_name
+ end
+end
diff --git a/db/migrate/20200626130220_drop_partitions_dynamic_schema_if_exists.rb b/db/migrate/20200626130220_drop_partitions_dynamic_schema_if_exists.rb
new file mode 100644
index 00000000000..fa31bc8ff0e
--- /dev/null
+++ b/db/migrate/20200626130220_drop_partitions_dynamic_schema_if_exists.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+
+class DropPartitionsDynamicSchemaIfExists < ActiveRecord::Migration[6.0]
+ DOWNTIME = false
+
+ def up
+ # This targets GitLab.com only - we deployed a migration to create this schema, but reverted the change
+ execute 'DROP SCHEMA IF EXISTS partitions_dynamic'
+ end
+
+ def down
+ # no-op
+ end
+end
diff --git a/db/migrate/20200628210938_add_maintenance_mode_application_to_settings.rb b/db/migrate/20200628210938_add_maintenance_mode_application_to_settings.rb
new file mode 100644
index 00000000000..6f1959a128f
--- /dev/null
+++ b/db/migrate/20200628210938_add_maintenance_mode_application_to_settings.rb
@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+
+class AddMaintenanceModeApplicationToSettings < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::MigrationHelpers
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ unless column_exists?(:application_settings, :maintenance_mode)
+ change_table :application_settings do |t|
+ t.boolean :maintenance_mode, default: false, null: false
+ t.text :maintenance_mode_message
+ end
+ end
+
+ add_text_limit(:application_settings, :maintenance_mode_message, 255)
+ end
+
+ def down
+ if column_exists?(:application_settings, :maintenance_mode)
+ remove_column :application_settings, :maintenance_mode
+ end
+
+ if column_exists?(:application_settings, :maintenance_mode_message)
+ remove_column :application_settings, :maintenance_mode_message
+ end
+ end
+end
diff --git a/db/migrate/20200629192638_add_uniq_index_on_metric_identifier_and_project_id.rb b/db/migrate/20200629192638_add_uniq_index_on_metric_identifier_and_project_id.rb
new file mode 100644
index 00000000000..3e77f80b5e8
--- /dev/null
+++ b/db/migrate/20200629192638_add_uniq_index_on_metric_identifier_and_project_id.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+class AddUniqIndexOnMetricIdentifierAndProjectId < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ add_concurrent_index :prometheus_metrics, [:identifier, :project_id], unique: true
+ end
+
+ def down
+ remove_concurrent_index :prometheus_metrics, [:identifier, :project_id]
+ end
+end
diff --git a/db/migrate/20200630091656_add_bio_html_to_user_details.rb b/db/migrate/20200630091656_add_bio_html_to_user_details.rb
new file mode 100644
index 00000000000..6a9df85d6a4
--- /dev/null
+++ b/db/migrate/20200630091656_add_bio_html_to_user_details.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+
+class AddBioHtmlToUserDetails < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ with_lock_retries do
+ # Note: bio_html is calculated from bio, the bio column is already constrained
+ add_column :user_details, :bio_html, :text # rubocop:disable Migration/AddLimitToTextColumns
+ add_column :user_details, :cached_markdown_version, :integer
+ end
+ end
+
+ def down
+ with_lock_retries do
+ remove_column :user_details, :bio_html
+ remove_column :user_details, :cached_markdown_version
+ end
+ end
+end
diff --git a/db/migrate/20200630110826_add_documents_count_target_to_elastic_reindexing_tasks.rb b/db/migrate/20200630110826_add_documents_count_target_to_elastic_reindexing_tasks.rb
new file mode 100644
index 00000000000..dcb4ccc857d
--- /dev/null
+++ b/db/migrate/20200630110826_add_documents_count_target_to_elastic_reindexing_tasks.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+class AddDocumentsCountTargetToElasticReindexingTasks < ActiveRecord::Migration[6.0]
+ DOWNTIME = false
+
+ def change
+ add_column :elastic_reindexing_tasks, :documents_count_target, :integer
+ end
+end
diff --git a/db/migrate/20200701064756_add_not_valid_foreign_key_to_cycle_analytics_group_stages.rb b/db/migrate/20200701064756_add_not_valid_foreign_key_to_cycle_analytics_group_stages.rb
new file mode 100644
index 00000000000..e54cecc5af8
--- /dev/null
+++ b/db/migrate/20200701064756_add_not_valid_foreign_key_to_cycle_analytics_group_stages.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+class AddNotValidForeignKeyToCycleAnalyticsGroupStages < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+ CONSTRAINT_NAME = 'fk_analytics_cycle_analytics_group_stages_group_value_stream_id'
+ INDEX_NAME = 'index_analytics_ca_group_stages_on_value_stream_id'
+
+ disable_ddl_transaction!
+
+ def up
+ add_concurrent_index :analytics_cycle_analytics_group_stages, :group_value_stream_id, name: INDEX_NAME
+ add_foreign_key :analytics_cycle_analytics_group_stages, :analytics_cycle_analytics_group_value_streams,
+ column: :group_value_stream_id, name: CONSTRAINT_NAME, on_delete: :cascade, validate: false
+ end
+
+ def down
+ remove_foreign_key_if_exists :analytics_cycle_analytics_group_stages, column: :group_value_stream_id, name: CONSTRAINT_NAME
+ remove_concurrent_index :analytics_cycle_analytics_group_stages, :group_value_stream_id
+ end
+end
diff --git a/db/migrate/20200701093859_add_import_export_limits_to_application_settings.rb b/db/migrate/20200701093859_add_import_export_limits_to_application_settings.rb
new file mode 100644
index 00000000000..6f40f439385
--- /dev/null
+++ b/db/migrate/20200701093859_add_import_export_limits_to_application_settings.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+class AddImportExportLimitsToApplicationSettings < ActiveRecord::Migration[6.0]
+ DOWNTIME = false
+
+ def change
+ add_column :application_settings, :project_import_limit, :integer, default: 6, null: false
+ add_column :application_settings, :project_export_limit, :integer, default: 6, null: false
+ add_column :application_settings, :project_download_export_limit, :integer, default: 1, null: false
+
+ add_column :application_settings, :group_import_limit, :integer, default: 6, null: false
+ add_column :application_settings, :group_export_limit, :integer, default: 6, null: false
+ add_column :application_settings, :group_download_export_limit, :integer, default: 1, null: false
+ end
+end
diff --git a/db/migrate/20200701190523_add_delayed_project_removal_to_namespaces.rb b/db/migrate/20200701190523_add_delayed_project_removal_to_namespaces.rb
new file mode 100644
index 00000000000..2d5bbc1cba1
--- /dev/null
+++ b/db/migrate/20200701190523_add_delayed_project_removal_to_namespaces.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+class AddDelayedProjectRemovalToNamespaces < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ def up
+ with_lock_retries do
+ add_column :namespaces, :delayed_project_removal, :boolean, default: false, null: false
+ end
+ end
+
+ def down
+ with_lock_retries do
+ remove_column :namespaces, :delayed_project_removal
+ end
+ end
+end
diff --git a/db/migrate/20200701205710_create_background_migration_jobs.rb b/db/migrate/20200701205710_create_background_migration_jobs.rb
new file mode 100644
index 00000000000..706b8bd5abe
--- /dev/null
+++ b/db/migrate/20200701205710_create_background_migration_jobs.rb
@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+
+class CreateBackgroundMigrationJobs < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ unless table_exists?(:background_migration_jobs)
+ create_table :background_migration_jobs do |t|
+ t.timestamps_with_timezone
+ t.integer :status, null: false, limit: 2, default: 0
+ t.text :class_name, null: false
+ t.jsonb :arguments, null: false
+
+ t.index [:class_name, :arguments]
+ t.index [:class_name, :status, :id]
+ end
+ end
+
+ add_text_limit :background_migration_jobs, :class_name, 200
+ end
+
+ def down
+ drop_table :background_migration_jobs
+ end
+end
diff --git a/db/migrate/20200702123805_change_project_id_index_to_be_unique_on_vulnerability_statistics_table.rb b/db/migrate/20200702123805_change_project_id_index_to_be_unique_on_vulnerability_statistics_table.rb
new file mode 100644
index 00000000000..7562a32a25e
--- /dev/null
+++ b/db/migrate/20200702123805_change_project_id_index_to_be_unique_on_vulnerability_statistics_table.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+class ChangeProjectIdIndexToBeUniqueOnVulnerabilityStatisticsTable < ActiveRecord::Migration[6.0]
+ DOWNTIME = false
+
+ INDEX_NAME = 'index_vulnerability_statistics_on_unique_project_id'
+
+ def up
+ remove_index :vulnerability_statistics, :project_id # rubocop:disable Migration/RemoveIndex (table is empty)
+ add_index :vulnerability_statistics, :project_id, name: INDEX_NAME, unique: true # rubocop:disable Migration/AddIndex (table is empty)
+ end
+
+ def down
+ remove_index :vulnerability_statistics, name: INDEX_NAME # rubocop:disable Migration/RemoveIndex (table is empty)
+ add_index :vulnerability_statistics, :project_id # rubocop:disable Migration/AddIndex (table is empty)
+ end
+end
diff --git a/db/migrate/20200702201039_change_prometheus_metrics_identifier_index.rb b/db/migrate/20200702201039_change_prometheus_metrics_identifier_index.rb
new file mode 100644
index 00000000000..248195c8c75
--- /dev/null
+++ b/db/migrate/20200702201039_change_prometheus_metrics_identifier_index.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+class ChangePrometheusMetricsIdentifierIndex < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ NEW_INDEX = :index_prometheus_metrics_on_identifier_and_null_project
+ OLD_INDEX = :index_prometheus_metrics_on_identifier
+
+ disable_ddl_transaction!
+
+ def up
+ add_concurrent_index :prometheus_metrics, :identifier, name: NEW_INDEX, unique: true, where: 'project_id IS NULL'
+ remove_concurrent_index_by_name :prometheus_metrics, OLD_INDEX
+ end
+
+ def down
+ add_concurrent_index :prometheus_metrics, :identifier, name: OLD_INDEX, unique: true
+ remove_concurrent_index_by_name :prometheus_metrics, NEW_INDEX
+ end
+end
diff --git a/db/migrate/20200703121557_remove_f_keys_from_ci_daily_report_results_table.rb b/db/migrate/20200703121557_remove_f_keys_from_ci_daily_report_results_table.rb
new file mode 100644
index 00000000000..43b869b007d
--- /dev/null
+++ b/db/migrate/20200703121557_remove_f_keys_from_ci_daily_report_results_table.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+class RemoveFKeysFromCiDailyReportResultsTable < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ with_lock_retries do
+ remove_foreign_key_if_exists :ci_daily_report_results, :projects
+ remove_foreign_key_if_exists :ci_daily_report_results, :ci_pipelines
+ end
+ end
+
+ def down
+ add_concurrent_foreign_key :ci_daily_report_results, :projects, column: :project_id, on_delete: :cascade
+ add_concurrent_foreign_key :ci_daily_report_results, :ci_pipelines, column: :last_pipeline_id, on_delete: :cascade
+ end
+end
diff --git a/db/migrate/20200703124823_create_namespace_settings.rb b/db/migrate/20200703124823_create_namespace_settings.rb
new file mode 100644
index 00000000000..907b9d2ca8c
--- /dev/null
+++ b/db/migrate/20200703124823_create_namespace_settings.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+class CreateNamespaceSettings < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ with_lock_retries do
+ create_table :namespace_settings, id: false do |t|
+ t.timestamps_with_timezone null: false
+ t.references :namespace, primary_key: true, default: nil, type: :integer, index: false, foreign_key: { on_delete: :cascade }
+ end
+ end
+ end
+
+ def down
+ drop_table :namespace_settings
+ end
+end
diff --git a/db/migrate/20200703154822_add_issues_enabled_to_jira_tracker_data.rb b/db/migrate/20200703154822_add_issues_enabled_to_jira_tracker_data.rb
new file mode 100644
index 00000000000..e56f6f6ee11
--- /dev/null
+++ b/db/migrate/20200703154822_add_issues_enabled_to_jira_tracker_data.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+class AddIssuesEnabledToJiraTrackerData < ActiveRecord::Migration[6.0]
+ DOWNTIME = false
+
+ def change
+ add_column :jira_tracker_data, :issues_enabled, :boolean, default: false, null: false
+ end
+end
diff --git a/db/migrate/20200706005325_remove_elastic_batch_project_indexer_worker_queue.rb b/db/migrate/20200706005325_remove_elastic_batch_project_indexer_worker_queue.rb
new file mode 100644
index 00000000000..07854096a8b
--- /dev/null
+++ b/db/migrate/20200706005325_remove_elastic_batch_project_indexer_worker_queue.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+class RemoveElasticBatchProjectIndexerWorkerQueue < ActiveRecord::Migration[6.0]
+ DOWNTIME = false
+
+ def up
+ Sidekiq.redis do |conn|
+ conn.del "queue:elastic_batch_project_indexer"
+ end
+ end
+end
diff --git a/db/migrate/20200706035141_adjust_unique_index_alert_management_alerts.rb b/db/migrate/20200706035141_adjust_unique_index_alert_management_alerts.rb
new file mode 100644
index 00000000000..0a6e614be9a
--- /dev/null
+++ b/db/migrate/20200706035141_adjust_unique_index_alert_management_alerts.rb
@@ -0,0 +1,35 @@
+# frozen_string_literal: true
+
+class AdjustUniqueIndexAlertManagementAlerts < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+ INDEX_NAME = 'index_alert_management_alerts_on_project_id_and_fingerprint'
+ NEW_INDEX_NAME = 'index_partial_am_alerts_on_project_id_and_fingerprint'
+ RESOLVED_STATUS = 2
+
+ disable_ddl_transaction!
+
+ def up
+ add_concurrent_index(:alert_management_alerts, %w(project_id fingerprint), where: "status <> #{RESOLVED_STATUS}", name: NEW_INDEX_NAME, unique: true, using: :btree)
+ remove_concurrent_index_by_name :alert_management_alerts, INDEX_NAME
+ end
+
+ def down
+ # Nullify duplicate fingerprints, except for the newest of each match (project_id, fingerprint).
+ query = <<-SQL
+ UPDATE alert_management_alerts am
+ SET fingerprint = NULL
+ WHERE am.created_at <>
+ (SELECT MAX(created_at)
+ FROM alert_management_alerts am2
+ WHERE am.fingerprint = am2.fingerprint AND am.project_id = am2.project_id)
+ AND am.fingerprint IS NOT NULL;
+ SQL
+
+ execute(query)
+
+ remove_concurrent_index_by_name :alert_management_alerts, NEW_INDEX_NAME
+ add_concurrent_index(:alert_management_alerts, %w(project_id fingerprint), name: INDEX_NAME, unique: true, using: :btree)
+ end
+end
diff --git a/db/migrate/20200706170536_add_temporary_storage_increase_to_namespace_limits.rb b/db/migrate/20200706170536_add_temporary_storage_increase_to_namespace_limits.rb
new file mode 100644
index 00000000000..0b6d57831c7
--- /dev/null
+++ b/db/migrate/20200706170536_add_temporary_storage_increase_to_namespace_limits.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+class AddTemporaryStorageIncreaseToNamespaceLimits < ActiveRecord::Migration[6.0]
+ DOWNTIME = false
+
+ def change
+ add_column :namespace_limits, :temporary_storage_increase_ends_on, :date, null: true
+ end
+end
diff --git a/db/migrate/20200707071941_drop_old_non_unique_index_on_mr_metrics.rb b/db/migrate/20200707071941_drop_old_non_unique_index_on_mr_metrics.rb
new file mode 100644
index 00000000000..aa90a0c5915
--- /dev/null
+++ b/db/migrate/20200707071941_drop_old_non_unique_index_on_mr_metrics.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+class DropOldNonUniqueIndexOnMrMetrics < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+ INDEX_NAME = 'index_merge_request_metrics'
+
+ disable_ddl_transaction!
+
+ def up
+ remove_concurrent_index_by_name(:merge_request_metrics, INDEX_NAME)
+ end
+
+ def down
+ add_concurrent_index :merge_request_metrics, :merge_request_id, name: INDEX_NAME
+ end
+end
diff --git a/db/migrate/20200707094341_add_browser_performance_to_plan_limits.rb b/db/migrate/20200707094341_add_browser_performance_to_plan_limits.rb
new file mode 100644
index 00000000000..ef0bea88ead
--- /dev/null
+++ b/db/migrate/20200707094341_add_browser_performance_to_plan_limits.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+class AddBrowserPerformanceToPlanLimits < ActiveRecord::Migration[6.0]
+ DOWNTIME = false
+
+ def change
+ add_column :plan_limits, "ci_max_artifact_size_browser_performance", :integer, default: 0, null: false
+ end
+end
diff --git a/db/migrate/20200707095849_add_load_performance_to_plan_limits.rb b/db/migrate/20200707095849_add_load_performance_to_plan_limits.rb
new file mode 100644
index 00000000000..df95956f089
--- /dev/null
+++ b/db/migrate/20200707095849_add_load_performance_to_plan_limits.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+class AddLoadPerformanceToPlanLimits < ActiveRecord::Migration[6.0]
+ DOWNTIME = false
+
+ def change
+ add_column :plan_limits, "ci_max_artifact_size_load_performance", :integer, default: 0, null: false
+ end
+end
diff --git a/db/migrate/20200708080631_add_pager_duty_integration_columns_to_project_incident_management_settings.rb b/db/migrate/20200708080631_add_pager_duty_integration_columns_to_project_incident_management_settings.rb
new file mode 100644
index 00000000000..ab56a863f51
--- /dev/null
+++ b/db/migrate/20200708080631_add_pager_duty_integration_columns_to_project_incident_management_settings.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+class AddPagerDutyIntegrationColumnsToProjectIncidentManagementSettings < ActiveRecord::Migration[6.0]
+ DOWNTIME = false
+
+ # limit constraints added in a separate migration:
+ # 20200710130234_add_limit_constraints_to_project_incident_management_settings_token.rb
+ def change
+ add_column :project_incident_management_settings, :pagerduty_active, :boolean, null: false, default: false
+ add_column :project_incident_management_settings, :encrypted_pagerduty_token, :binary, null: true
+ add_column :project_incident_management_settings, :encrypted_pagerduty_token_iv, :binary, null: true
+ end
+end
diff --git a/db/migrate/20200710105332_change_issues_create_limit_default.rb b/db/migrate/20200710105332_change_issues_create_limit_default.rb
new file mode 100644
index 00000000000..9fbd5375395
--- /dev/null
+++ b/db/migrate/20200710105332_change_issues_create_limit_default.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+class ChangeIssuesCreateLimitDefault < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ def up
+ with_lock_retries do
+ change_column_default :application_settings, :issues_create_limit, from: 300, to: 0
+ end
+ end
+
+ def down
+ with_lock_retries do
+ change_column_default :application_settings, :issues_create_limit, from: 0, to: 300
+ end
+ end
+end
diff --git a/db/migrate/20200710130234_add_limit_constraints_to_project_incident_management_settings_token.rb b/db/migrate/20200710130234_add_limit_constraints_to_project_incident_management_settings_token.rb
new file mode 100644
index 00000000000..8af927d0959
--- /dev/null
+++ b/db/migrate/20200710130234_add_limit_constraints_to_project_incident_management_settings_token.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+class AddLimitConstraintsToProjectIncidentManagementSettingsToken < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::MigrationHelpers
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ add_check_constraint :project_incident_management_settings, 'octet_length(encrypted_pagerduty_token) <= 255', 'pagerduty_token_length_constraint'
+ add_check_constraint :project_incident_management_settings, 'octet_length(encrypted_pagerduty_token_iv) <= 12', 'pagerduty_token_iv_length_constraint'
+ end
+
+ def down
+ remove_check_constraint :project_incident_management_settings, 'pagerduty_token_length_constraint'
+ remove_check_constraint :project_incident_management_settings, 'pagerduty_token_iv_length_constraint'
+ end
+end
diff --git a/db/migrate/20200712084655_create_dast_sites.rb b/db/migrate/20200712084655_create_dast_sites.rb
new file mode 100644
index 00000000000..fc8d423ffb2
--- /dev/null
+++ b/db/migrate/20200712084655_create_dast_sites.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+class CreateDastSites < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ with_lock_retries do
+ create_table :dast_sites do |t|
+ t.references :project, foreign_key: { on_delete: :cascade }, null: false, index: false
+ t.timestamps_with_timezone null: false
+
+ t.text :url, null: false
+ end
+ end
+
+ add_concurrent_index :dast_sites, [:project_id, :url], unique: true
+ add_text_limit :dast_sites, :url, 255
+ end
+
+ def down
+ drop_table :dast_sites
+ end
+end
diff --git a/db/migrate/20200712235622_create_dast_site_profiles.rb b/db/migrate/20200712235622_create_dast_site_profiles.rb
new file mode 100644
index 00000000000..593cccd450a
--- /dev/null
+++ b/db/migrate/20200712235622_create_dast_site_profiles.rb
@@ -0,0 +1,28 @@
+# frozen_string_literal: true
+
+class CreateDastSiteProfiles < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ with_lock_retries do
+ create_table :dast_site_profiles do |t|
+ t.references :project, foreign_key: { on_delete: :cascade }, null: false, index: false
+ t.references :dast_site, foreign_key: { on_delete: :cascade }, null: false
+ t.timestamps_with_timezone null: false
+
+ t.text :name, null: false
+ end
+ end
+
+ add_concurrent_index :dast_site_profiles, [:project_id, :name], unique: true
+ add_text_limit :dast_site_profiles, :name, 255
+ end
+
+ def down
+ drop_table :dast_site_profiles
+ end
+end
diff --git a/db/migrate/20200713152443_add_background_migration_job_index_for_partitioning_migrations.rb b/db/migrate/20200713152443_add_background_migration_job_index_for_partitioning_migrations.rb
new file mode 100644
index 00000000000..5ca9b6536ee
--- /dev/null
+++ b/db/migrate/20200713152443_add_background_migration_job_index_for_partitioning_migrations.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+class AddBackgroundMigrationJobIndexForPartitioningMigrations < ActiveRecord::Migration[6.0]
+ DOWNTIME = false
+
+ INDEX_NAME = 'index_background_migration_jobs_for_partitioning_migrations'
+
+ def up
+ # rubocop:disable Migration/AddIndex
+ add_index :background_migration_jobs, '((arguments ->> 2))', name: INDEX_NAME,
+ where: "class_name = 'Gitlab::Database::PartitioningMigrationHelpers::BackfillPartitionedTable'"
+ # rubocop:enable Migration/AddIndex
+ end
+
+ def down
+ remove_index :background_migration_jobs, name: INDEX_NAME # rubocop:disable Migration/RemoveIndex
+ end
+end
diff --git a/db/migrate/20200716044023_add_entity_path_to_audit_events.rb b/db/migrate/20200716044023_add_entity_path_to_audit_events.rb
new file mode 100644
index 00000000000..d16bfaa0beb
--- /dev/null
+++ b/db/migrate/20200716044023_add_entity_path_to_audit_events.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+class AddEntityPathToAuditEvents < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ def up
+ with_lock_retries do
+ # rubocop:disable Migration/AddLimitToTextColumns
+ add_column(:audit_events, :entity_path, :text)
+ # rubocop:enable Migration/AddLimitToTextColumns
+ end
+ end
+
+ def down
+ with_lock_retries do
+ remove_column(:audit_events, :entity_path)
+ end
+ end
+end
diff --git a/db/migrate/20200716120419_add_text_limit_on_entity_path_to_audit_events.rb b/db/migrate/20200716120419_add_text_limit_on_entity_path_to_audit_events.rb
new file mode 100644
index 00000000000..0dea811f41c
--- /dev/null
+++ b/db/migrate/20200716120419_add_text_limit_on_entity_path_to_audit_events.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+class AddTextLimitOnEntityPathToAuditEvents < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ add_text_limit :audit_events, :entity_path, 5_500
+ end
+
+ def down
+ remove_text_limit :audit_events, :entity_path
+ end
+end
diff --git a/db/post_migrate/20200305082754_remove_duplicate_labels_from_project.rb b/db/post_migrate/20200305082754_remove_duplicate_labels_from_project.rb
new file mode 100644
index 00000000000..33f8118534d
--- /dev/null
+++ b/db/post_migrate/20200305082754_remove_duplicate_labels_from_project.rb
@@ -0,0 +1,130 @@
+# frozen_string_literal: true
+
+class RemoveDuplicateLabelsFromProject < ActiveRecord::Migration[6.0]
+ DOWNTIME = false
+
+ CREATE = 1
+ RENAME = 2
+
+ disable_ddl_transaction!
+
+ class BackupLabel < Label
+ self.table_name = 'backup_labels'
+ end
+
+ class Label < ApplicationRecord
+ self.table_name = 'labels'
+ end
+
+ class Project < ApplicationRecord
+ include EachBatch
+
+ self.table_name = 'projects'
+ end
+
+ BATCH_SIZE = 100_000
+
+ def up
+ # Split to smaller chunks
+ # Loop rather than background job, every 100,000
+ # there are 45,000,000 projects in total
+ Project.each_batch(of: BATCH_SIZE) do |batch|
+ range = batch.pluck('MIN(id)', 'MAX(id)').first
+
+ transaction do
+ remove_full_duplicates(*range)
+ end
+
+ transaction do
+ rename_partial_duplicates(*range)
+ end
+ end
+ end
+
+ def down
+ Project.each_batch(of: BATCH_SIZE) do |batch|
+ range = batch.pluck('MIN(id)', 'MAX(id)').first
+
+ restore_renamed_labels(*range)
+ restore_deleted_labels(*range)
+ end
+ end
+
+ def remove_full_duplicates(start_id, stop_id)
+ # Fields that are considered duplicate:
+ # project_id title template description type color
+
+ duplicate_labels = ApplicationRecord.connection.execute(<<-SQL.squish)
+WITH data AS (
+ SELECT labels.*,
+ row_number() OVER (PARTITION BY labels.project_id, labels.title, labels.template, labels.description, labels.type, labels.color ORDER BY labels.id) AS row_number,
+ #{CREATE} AS restore_action
+ FROM labels
+ WHERE labels.project_id BETWEEN #{start_id} AND #{stop_id}
+ AND NOT EXISTS (SELECT * FROM board_labels WHERE board_labels.label_id = labels.id)
+ AND NOT EXISTS (SELECT * FROM label_links WHERE label_links.label_id = labels.id)
+ AND NOT EXISTS (SELECT * FROM label_priorities WHERE label_priorities.label_id = labels.id)
+ AND NOT EXISTS (SELECT * FROM lists WHERE lists.label_id = labels.id)
+ AND NOT EXISTS (SELECT * FROM resource_label_events WHERE resource_label_events.label_id = labels.id)
+) SELECT * FROM data WHERE row_number > 1;
+ SQL
+
+ if duplicate_labels.any?
+ # create backup records
+ BackupLabel.insert_all!(duplicate_labels.map { |label| label.except("row_number") })
+
+ Label.where(id: duplicate_labels.pluck("id")).delete_all
+ end
+ end
+
+ def rename_partial_duplicates(start_id, stop_id)
+ # We need to ensure that the new title (with `_duplicate#{ID}`) doesn't exceed the limit.
+ # Truncate the original title (if needed) to 245 characters minus the length of the ID
+ # then add `_duplicate#{ID}`
+
+ soft_duplicates = ApplicationRecord.connection.execute(<<-SQL.squish)
+WITH data AS (
+ SELECT
+ *,
+ substring(title from 1 for 245 - length(id::text)) || '_duplicate' || id::text as new_title,
+ #{RENAME} AS restore_action,
+ row_number() OVER (PARTITION BY project_id, title ORDER BY id) AS row_number
+ FROM labels
+ WHERE project_id BETWEEN #{start_id} AND #{stop_id}
+) SELECT * FROM data WHERE row_number > 1;
+ SQL
+
+ if soft_duplicates.any?
+ # create backup records
+ BackupLabel.insert_all!(soft_duplicates.map { |label| label.except("row_number") })
+
+ ApplicationRecord.connection.execute(<<-SQL.squish)
+UPDATE labels SET title = substring(title from 1 for 245 - length(id::text)) || '_duplicate' || id::text
+WHERE labels.id IN (#{soft_duplicates.map { |dup| dup["id"] }.join(", ")});
+ SQL
+ end
+ end
+
+ def restore_renamed_labels(start_id, stop_id)
+ # the backup label IDs are not incremental, they are copied directly from the Labels table
+ ApplicationRecord.connection.execute(<<-SQL.squish)
+WITH backups AS (
+ SELECT id, title
+ FROM backup_labels
+ WHERE project_id BETWEEN #{start_id} AND #{stop_id} AND
+ restore_action = #{RENAME}
+) UPDATE labels SET title = backups.title
+FROM backups
+WHERE labels.id = backups.id;
+ SQL
+ end
+
+ def restore_deleted_labels(start_id, stop_id)
+ ActiveRecord::Base.connection.execute(<<-SQL.squish)
+INSERT INTO labels
+SELECT id, title, color, project_id, created_at, updated_at, template, description, description_html, type, group_id, cached_markdown_version FROM backup_labels
+ WHERE backup_labels.project_id BETWEEN #{start_id} AND #{stop_id}
+ AND backup_labels.restore_action = #{CREATE}
+ SQL
+ end
+end
diff --git a/db/post_migrate/20200305082858_add_uniqueness_index_to_label_title_and_project.rb b/db/post_migrate/20200305082858_add_uniqueness_index_to_label_title_and_project.rb
new file mode 100644
index 00000000000..ce235ba4aea
--- /dev/null
+++ b/db/post_migrate/20200305082858_add_uniqueness_index_to_label_title_and_project.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+class AddUniquenessIndexToLabelTitleAndProject < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ PROJECT_AND_TITLE = [:project_id, :title]
+
+ def up
+ add_concurrent_index :labels, PROJECT_AND_TITLE, where: "labels.group_id IS NULL", unique: true, name: "index_labels_on_project_id_and_title_unique"
+ remove_concurrent_index :labels, PROJECT_AND_TITLE, name: "index_labels_on_project_id_and_title"
+ end
+
+ def down
+ add_concurrent_index :labels, PROJECT_AND_TITLE, where: "labels.group_id IS NULL", unique: false, name: "index_labels_on_project_id_and_title"
+ remove_concurrent_index :labels, PROJECT_AND_TITLE, name: "index_labels_on_project_id_and_title_unique"
+ end
+end
diff --git a/db/post_migrate/20200519201128_migrate_vulnerability_dismissal_feedback.rb b/db/post_migrate/20200519201128_migrate_vulnerability_dismissal_feedback.rb
new file mode 100644
index 00000000000..fee2f59abb5
--- /dev/null
+++ b/db/post_migrate/20200519201128_migrate_vulnerability_dismissal_feedback.rb
@@ -0,0 +1,36 @@
+# frozen_string_literal: true
+
+class MigrateVulnerabilityDismissalFeedback < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+ disable_ddl_transaction!
+
+ MIGRATION = 'UpdateVulnerabilitiesFromDismissalFeedback'
+ BATCH_SIZE = 500
+ DELAY_INTERVAL = 2.minutes.to_i
+
+ class Vulnerability < ActiveRecord::Base
+ self.table_name = 'vulnerabilities'
+ self.inheritance_column = :_type_disabled
+
+ include ::EachBatch
+ end
+
+ def up
+ return unless Gitlab.ee?
+
+ Vulnerability.select('project_id').group(:project_id).each_batch(of: BATCH_SIZE, column: "project_id") do |project_batch, index|
+ batch_delay = (index - 1) * BATCH_SIZE * DELAY_INTERVAL
+
+ project_batch.each_with_index do |project, project_batch_index|
+ project_delay = project_batch_index * DELAY_INTERVAL
+ migrate_in(batch_delay + project_delay, MIGRATION, project[:project_id])
+ end
+ end
+ end
+
+ def down
+ # nothing to do
+ end
+end
diff --git a/db/post_migrate/20200526115436_dedup_mr_metrics.rb b/db/post_migrate/20200526115436_dedup_mr_metrics.rb
new file mode 100644
index 00000000000..d2660504939
--- /dev/null
+++ b/db/post_migrate/20200526115436_dedup_mr_metrics.rb
@@ -0,0 +1,65 @@
+# frozen_string_literal: true
+
+class DedupMrMetrics < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+ TMP_INDEX_NAME = 'tmp_unique_merge_request_metrics_by_merge_request_id'
+ INDEX_NAME = 'unique_merge_request_metrics_by_merge_request_id'
+
+ disable_ddl_transaction!
+
+ class MergeRequestMetrics < ActiveRecord::Base
+ self.table_name = 'merge_request_metrics'
+
+ include EachBatch
+ end
+
+ def up
+ last_metrics_record_id = MergeRequestMetrics.maximum(:id) || 0
+
+ # This index will disallow further duplicates while we're deduplicating the data.
+ add_concurrent_index(:merge_request_metrics, :merge_request_id, where: "id > #{Integer(last_metrics_record_id)}", unique: true, name: TMP_INDEX_NAME)
+
+ MergeRequestMetrics.each_batch do |relation|
+ duplicated_merge_request_ids = MergeRequestMetrics
+ .where(merge_request_id: relation.select(:merge_request_id))
+ .select(:merge_request_id)
+ .group(:merge_request_id)
+ .having('COUNT(merge_request_metrics.merge_request_id) > 1')
+ .pluck(:merge_request_id)
+
+ duplicated_merge_request_ids.each do |merge_request_id|
+ deduplicate_item(merge_request_id)
+ end
+ end
+
+ add_concurrent_index(:merge_request_metrics, :merge_request_id, unique: true, name: INDEX_NAME)
+ remove_concurrent_index_by_name(:merge_request_metrics, TMP_INDEX_NAME)
+ end
+
+ def down
+ remove_concurrent_index_by_name(:merge_request_metrics, TMP_INDEX_NAME)
+ remove_concurrent_index_by_name(:merge_request_metrics, INDEX_NAME)
+ end
+
+ private
+
+ def deduplicate_item(merge_request_id)
+ merge_request_metrics_records = MergeRequestMetrics.where(merge_request_id: merge_request_id).order(updated_at: :asc).to_a
+
+ attributes = {}
+ merge_request_metrics_records.each do |merge_request_metrics_record|
+ params = merge_request_metrics_record.attributes.except('id')
+ attributes.merge!(params.compact)
+ end
+
+ ActiveRecord::Base.transaction do
+ record_to_keep = merge_request_metrics_records.pop
+ records_to_delete = merge_request_metrics_records
+
+ MergeRequestMetrics.where(id: records_to_delete.map(&:id)).delete_all
+ record_to_keep.update!(attributes)
+ end
+ end
+end
diff --git a/db/post_migrate/20200608195222_set_lock_version_not_null_constraint.rb b/db/post_migrate/20200608195222_set_lock_version_not_null_constraint.rb
new file mode 100644
index 00000000000..ec72053b307
--- /dev/null
+++ b/db/post_migrate/20200608195222_set_lock_version_not_null_constraint.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+class SetLockVersionNotNullConstraint < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ TABLES = %i(epics merge_requests issues ci_stages ci_builds ci_pipelines).freeze
+
+ def up
+ TABLES.each do |table|
+ add_not_null_constraint table, :lock_version, validate: false
+ end
+ end
+
+ def down
+ TABLES.each do |table|
+ remove_not_null_constraint table, :lock_version
+ end
+ end
+end
diff --git a/db/post_migrate/20200608203426_set_proper_lock_version_indices.rb b/db/post_migrate/20200608203426_set_proper_lock_version_indices.rb
new file mode 100644
index 00000000000..924ca73e6cc
--- /dev/null
+++ b/db/post_migrate/20200608203426_set_proper_lock_version_indices.rb
@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+
+class SetProperLockVersionIndices < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ remove_concurrent_index :epics, :lock_version, where: "lock_version IS NULL"
+ remove_concurrent_index :merge_requests, :lock_version, where: "lock_version IS NULL"
+ remove_concurrent_index :issues, :lock_version, where: "lock_version IS NULL"
+
+ add_concurrent_index :epics, :id, where: "lock_version IS NULL", name: 'index_epics_on_id'
+ add_concurrent_index :merge_requests, :id, where: "lock_version IS NULL", name: 'index_merge_requests_on_id'
+ add_concurrent_index :issues, :id, where: "lock_version IS NULL", name: 'index_issues_on_id'
+ end
+
+ def down
+ add_concurrent_index :epics, :lock_version, where: "lock_version IS NULL"
+ add_concurrent_index :merge_requests, :lock_version, where: "lock_version IS NULL"
+ add_concurrent_index :issues, :lock_version, where: "lock_version IS NULL"
+
+ remove_concurrent_index_by_name :epics, name: 'index_epics_on_id'
+ remove_concurrent_index_by_name :merge_requests, name: 'index_merge_requests_on_id'
+ remove_concurrent_index_by_name :issues, name: 'index_issues_on_id'
+ end
+end
diff --git a/db/post_migrate/20200608205813_set_lock_version_to_not_null.rb b/db/post_migrate/20200608205813_set_lock_version_to_not_null.rb
new file mode 100644
index 00000000000..69f43a8decf
--- /dev/null
+++ b/db/post_migrate/20200608205813_set_lock_version_to_not_null.rb
@@ -0,0 +1,31 @@
+# frozen_string_literal: true
+
+class SetLockVersionToNotNull < ActiveRecord::Migration[6.0]
+ DOWNTIME = false
+
+ TABLES = %w(epics merge_requests issues ci_stages ci_builds ci_pipelines).freeze
+ BATCH_SIZE = 10_000
+
+ disable_ddl_transaction!
+
+ def declare_class(table)
+ Class.new(ActiveRecord::Base) do
+ include EachBatch
+
+ self.table_name = table
+ self.inheritance_column = :_type_disabled # Disable STI
+ end
+ end
+
+ def up
+ TABLES.each do |table|
+ declare_class(table).where(lock_version: nil).each_batch(of: BATCH_SIZE) do |batch|
+ batch.update_all(lock_version: 0)
+ end
+ end
+ end
+
+ def down
+ # Nothing to do...
+ end
+end
diff --git a/db/post_migrate/20200608212030_lock_version_cleanup_for_epics.rb b/db/post_migrate/20200608212030_lock_version_cleanup_for_epics.rb
new file mode 100644
index 00000000000..aafa6a83200
--- /dev/null
+++ b/db/post_migrate/20200608212030_lock_version_cleanup_for_epics.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+class LockVersionCleanupForEpics < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ validate_not_null_constraint :epics, :lock_version
+ remove_concurrent_index_by_name :epics, name: 'index_epics_on_id'
+ end
+
+ def down
+ add_concurrent_index :epics, :id, where: "lock_version IS NULL", name: 'index_epics_on_id'
+ end
+end
diff --git a/db/post_migrate/20200608212435_lock_version_cleanup_for_merge_requests.rb b/db/post_migrate/20200608212435_lock_version_cleanup_for_merge_requests.rb
new file mode 100644
index 00000000000..cb8ab86b6a3
--- /dev/null
+++ b/db/post_migrate/20200608212435_lock_version_cleanup_for_merge_requests.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+class LockVersionCleanupForMergeRequests < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ validate_not_null_constraint :merge_requests, :lock_version
+ remove_concurrent_index_by_name :merge_requests, name: 'index_merge_requests_on_id'
+ end
+
+ def down
+ add_concurrent_index :merge_requests, :id, where: "lock_version IS NULL", name: 'index_merge_requests_on_id'
+ end
+end
diff --git a/db/post_migrate/20200608212549_lock_version_cleanup_for_issues.rb b/db/post_migrate/20200608212549_lock_version_cleanup_for_issues.rb
new file mode 100644
index 00000000000..ad3fea8b131
--- /dev/null
+++ b/db/post_migrate/20200608212549_lock_version_cleanup_for_issues.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+class LockVersionCleanupForIssues < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ validate_not_null_constraint :issues, :lock_version
+ remove_concurrent_index_by_name :issues, name: 'index_issues_on_id'
+ end
+
+ def down
+ add_concurrent_index :issues, :id, where: "lock_version IS NULL", name: 'index_issues_on_id'
+ end
+end
diff --git a/db/post_migrate/20200608212652_lock_version_cleanup_for_ci_stages.rb b/db/post_migrate/20200608212652_lock_version_cleanup_for_ci_stages.rb
new file mode 100644
index 00000000000..12e2897123e
--- /dev/null
+++ b/db/post_migrate/20200608212652_lock_version_cleanup_for_ci_stages.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+class LockVersionCleanupForCiStages < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ validate_not_null_constraint :ci_stages, :lock_version
+ remove_concurrent_index :ci_stages, :id, where: "lock_version IS NULL", name: "tmp_index_ci_stages_lock_version"
+ end
+
+ def down
+ add_concurrent_index :ci_stages, :id, where: "lock_version IS NULL", name: "tmp_index_ci_stages_lock_version"
+ end
+end
diff --git a/db/post_migrate/20200608212807_lock_version_cleanup_for_ci_builds.rb b/db/post_migrate/20200608212807_lock_version_cleanup_for_ci_builds.rb
new file mode 100644
index 00000000000..0512869971b
--- /dev/null
+++ b/db/post_migrate/20200608212807_lock_version_cleanup_for_ci_builds.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+class LockVersionCleanupForCiBuilds < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ validate_not_null_constraint :ci_builds, :lock_version
+ remove_concurrent_index :ci_builds, :id, where: "lock_version IS NULL", name: "tmp_index_ci_builds_lock_version"
+ end
+
+ def down
+ add_concurrent_index :ci_builds, :id, where: "lock_version IS NULL", name: "tmp_index_ci_builds_lock_version"
+ end
+end
diff --git a/db/post_migrate/20200608212824_lock_version_cleanup_for_ci_pipelines.rb b/db/post_migrate/20200608212824_lock_version_cleanup_for_ci_pipelines.rb
new file mode 100644
index 00000000000..228dd72da8d
--- /dev/null
+++ b/db/post_migrate/20200608212824_lock_version_cleanup_for_ci_pipelines.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+class LockVersionCleanupForCiPipelines < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ validate_not_null_constraint :ci_pipelines, :lock_version
+ remove_concurrent_index :ci_pipelines, :id, where: "lock_version IS NULL", name: "tmp_index_ci_pipelines_lock_version"
+ end
+
+ def down
+ add_concurrent_index :ci_pipelines, :id, where: "lock_version IS NULL", name: "tmp_index_ci_pipelines_lock_version"
+ end
+end
diff --git a/db/post_migrate/20200609002841_add_partial_index_on_locked_state_id_to_merge_requests.rb b/db/post_migrate/20200609002841_add_partial_index_on_locked_state_id_to_merge_requests.rb
index 076c8fd8715..7602ad00796 100644
--- a/db/post_migrate/20200609002841_add_partial_index_on_locked_state_id_to_merge_requests.rb
+++ b/db/post_migrate/20200609002841_add_partial_index_on_locked_state_id_to_merge_requests.rb
@@ -14,6 +14,6 @@ class AddPartialIndexOnLockedStateIdToMergeRequests < ActiveRecord::Migration[6.
end
def down
- remove_concurrent_index_by_name :merge_requests, name: INDEX_NAME
+ remove_concurrent_index_by_name :merge_requests, INDEX_NAME
end
end
diff --git a/db/post_migrate/20200615111857_unconfirm_wrongfully_verified_emails.rb b/db/post_migrate/20200615111857_unconfirm_wrongfully_verified_emails.rb
new file mode 100644
index 00000000000..41280872a94
--- /dev/null
+++ b/db/post_migrate/20200615111857_unconfirm_wrongfully_verified_emails.rb
@@ -0,0 +1,31 @@
+# frozen_string_literal: true
+
+class UnconfirmWrongfullyVerifiedEmails < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ INTERVAL = 5.minutes.to_i
+ BATCH_SIZE = 500
+ MIGRATION = 'WrongfullyConfirmedEmailUnconfirmer'
+ EMAIL_INDEX_NAME = 'tmp_index_for_email_unconfirmation_migration'
+
+ class Email < ActiveRecord::Base
+ include EachBatch
+ end
+
+ def up
+ add_concurrent_index :emails, :id, where: 'confirmed_at IS NOT NULL', name: EMAIL_INDEX_NAME
+
+ queue_background_migration_jobs_by_range_at_intervals(Email,
+ MIGRATION,
+ INTERVAL,
+ batch_size: BATCH_SIZE)
+ end
+
+ def down
+ remove_concurrent_index_by_name(:emails, EMAIL_INDEX_NAME)
+ end
+end
diff --git a/db/post_migrate/20200617001637_validate_file_store_not_null_constraint_on_lfs_objects.rb b/db/post_migrate/20200617001637_validate_file_store_not_null_constraint_on_lfs_objects.rb
new file mode 100644
index 00000000000..27a30b1d696
--- /dev/null
+++ b/db/post_migrate/20200617001637_validate_file_store_not_null_constraint_on_lfs_objects.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+class ValidateFileStoreNotNullConstraintOnLfsObjects < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ validate_check_constraint(:lfs_objects, :check_eecfc5717d)
+ end
+
+ def down
+ # no-op
+ end
+end
diff --git a/db/post_migrate/20200617001848_validate_store_not_null_constraint_uploads.rb b/db/post_migrate/20200617001848_validate_store_not_null_constraint_uploads.rb
new file mode 100644
index 00000000000..83cb6cb3e85
--- /dev/null
+++ b/db/post_migrate/20200617001848_validate_store_not_null_constraint_uploads.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+class ValidateStoreNotNullConstraintUploads < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ validate_check_constraint(:uploads, :check_5e9547379c)
+ end
+
+ def down
+ # no-op
+ end
+end
diff --git a/db/post_migrate/20200617002030_validate_file_store_not_null_constraint_on_ci_job_artifacts.rb b/db/post_migrate/20200617002030_validate_file_store_not_null_constraint_on_ci_job_artifacts.rb
new file mode 100644
index 00000000000..8e766a508b7
--- /dev/null
+++ b/db/post_migrate/20200617002030_validate_file_store_not_null_constraint_on_ci_job_artifacts.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+class ValidateFileStoreNotNullConstraintOnCiJobArtifacts < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ validate_check_constraint(:ci_job_artifacts, :check_27f0f6dbab)
+ end
+
+ def down
+ # no-op
+ end
+end
diff --git a/db/post_migrate/20200618152212_update_secure_smau_index.rb b/db/post_migrate/20200618152212_update_secure_smau_index.rb
new file mode 100644
index 00000000000..ba989c279be
--- /dev/null
+++ b/db/post_migrate/20200618152212_update_secure_smau_index.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+class UpdateSecureSmauIndex < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+ INDEX_NAME = 'index_secure_ci_builds_on_user_id_created_at'
+
+ disable_ddl_transaction!
+
+ def up
+ add_concurrent_index(
+ :ci_builds,
+ [:user_id, :created_at],
+ where: "(((type)::text = 'Ci::Build'::text) AND ((name)::text = ANY (ARRAY[('container_scanning'::character varying)::text, ('dast'::character varying)::text, ('dependency_scanning'::character varying)::text, ('license_management'::character varying)::text, ('license_scanning'::character varying)::text, ('sast'::character varying)::text, ('secret_detection'::character varying)::text])))",
+ name: INDEX_NAME
+ )
+ end
+
+ def down
+ remove_concurrent_index_by_name :ci_builds, INDEX_NAME
+ end
+end
diff --git a/db/post_migrate/20200623142159_remove_gitlab_issue_tracker_service_records.rb b/db/post_migrate/20200623142159_remove_gitlab_issue_tracker_service_records.rb
new file mode 100644
index 00000000000..743499e7b76
--- /dev/null
+++ b/db/post_migrate/20200623142159_remove_gitlab_issue_tracker_service_records.rb
@@ -0,0 +1,28 @@
+# frozen_string_literal: true
+
+class RemoveGitlabIssueTrackerServiceRecords < ActiveRecord::Migration[6.0]
+ DOWNTIME = false
+ BATCH_SIZE = 5000
+
+ disable_ddl_transaction!
+
+ class Service < ActiveRecord::Base
+ include EachBatch
+
+ self.table_name = 'services'
+
+ def self.gitlab_issue_tracker_service
+ where(type: 'GitlabIssueTrackerService')
+ end
+ end
+
+ def up
+ Service.each_batch(of: BATCH_SIZE) do |services|
+ services.gitlab_issue_tracker_service.delete_all
+ end
+ end
+
+ def down
+ # no-op
+ end
+end
diff --git a/db/post_migrate/20200626060151_add_disable_overriding_approvers_per_merge_request_indices.rb b/db/post_migrate/20200626060151_add_disable_overriding_approvers_per_merge_request_indices.rb
new file mode 100644
index 00000000000..6f2db4035e2
--- /dev/null
+++ b/db/post_migrate/20200626060151_add_disable_overriding_approvers_per_merge_request_indices.rb
@@ -0,0 +1,26 @@
+# frozen_string_literal: true
+
+class AddDisableOverridingApproversPerMergeRequestIndices < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+ DISABLE_OVERRIDING_APPROVERS_TRUE_INDEX_NAME = "idx_projects_id_created_at_disable_overriding_approvers_true"
+ DISABLE_OVERRIDING_APPROVERS_FALSE_INDEX_NAME = "idx_projects_id_created_at_disable_overriding_approvers_false"
+
+ disable_ddl_transaction!
+
+ def up
+ add_concurrent_index :projects, [:id, :created_at],
+ where: "disable_overriding_approvers_per_merge_request = TRUE",
+ name: DISABLE_OVERRIDING_APPROVERS_TRUE_INDEX_NAME
+
+ add_concurrent_index :projects, [:id, :created_at],
+ where: "(disable_overriding_approvers_per_merge_request = FALSE) OR (disable_overriding_approvers_per_merge_request IS NULL)",
+ name: DISABLE_OVERRIDING_APPROVERS_FALSE_INDEX_NAME
+ end
+
+ def down
+ remove_concurrent_index_by_name :projects, DISABLE_OVERRIDING_APPROVERS_TRUE_INDEX_NAME
+ remove_concurrent_index_by_name :projects, DISABLE_OVERRIDING_APPROVERS_FALSE_INDEX_NAME
+ end
+end
diff --git a/db/post_migrate/20200701070435_add_default_value_stream_to_groups_with_group_stages.rb b/db/post_migrate/20200701070435_add_default_value_stream_to_groups_with_group_stages.rb
new file mode 100644
index 00000000000..971eb3c489f
--- /dev/null
+++ b/db/post_migrate/20200701070435_add_default_value_stream_to_groups_with_group_stages.rb
@@ -0,0 +1,55 @@
+# frozen_string_literal: true
+
+class AddDefaultValueStreamToGroupsWithGroupStages < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ class Group < ActiveRecord::Base
+ def self.find_sti_class(typename)
+ if typename == 'Group'
+ Group
+ else
+ super
+ end
+ end
+ self.table_name = 'namespaces'
+ has_many :group_value_streams
+ has_many :group_stages
+ end
+
+ class GroupValueStream < ActiveRecord::Base
+ self.table_name = 'analytics_cycle_analytics_group_value_streams'
+ has_many :group_stages
+ belongs_to :group
+ end
+
+ class GroupStage < ActiveRecord::Base
+ self.table_name = 'analytics_cycle_analytics_group_stages'
+ belongs_to :group_value_stream
+ end
+
+ def up
+ Group.where(type: 'Group').joins(:group_stages).distinct.find_each do |group|
+ Group.transaction do
+ group_value_stream = group.group_value_streams.first_or_create!(name: 'default')
+ group.group_stages.update_all(group_value_stream_id: group_value_stream.id)
+ end
+ end
+
+ change_column_null :analytics_cycle_analytics_group_stages, :group_value_stream_id, false
+ end
+
+ def down
+ change_column_null :analytics_cycle_analytics_group_stages, :group_value_stream_id, true
+
+ GroupValueStream.where(name: 'default').includes(:group_stages).find_each do |value_stream|
+ GroupValueStream.transaction do
+ value_stream.group_stages.update_all(group_value_stream_id: nil)
+ value_stream.destroy!
+ end
+ end
+ end
+end
diff --git a/db/post_migrate/20200701091253_validate_foreign_key_on_cycle_analytics_group_stages.rb b/db/post_migrate/20200701091253_validate_foreign_key_on_cycle_analytics_group_stages.rb
new file mode 100644
index 00000000000..0a8926ed6de
--- /dev/null
+++ b/db/post_migrate/20200701091253_validate_foreign_key_on_cycle_analytics_group_stages.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+class ValidateForeignKeyOnCycleAnalyticsGroupStages < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+ # same as in db/migrate/20200701064756_add_not_valid_foreign_key_to_cycle_analytics_group_stages.rb
+ CONSTRAINT_NAME = 'fk_analytics_cycle_analytics_group_stages_group_value_stream_id'
+
+ def up
+ validate_foreign_key :analytics_cycle_analytics_group_stages, :group_value_stream_id, name: CONSTRAINT_NAME
+ end
+
+ def down
+ remove_foreign_key_if_exists :analytics_cycle_analytics_group_stages, column: :group_value_stream_id, name: CONSTRAINT_NAME
+ add_foreign_key :analytics_cycle_analytics_group_stages, :analytics_cycle_analytics_group_value_streams,
+ column: :group_value_stream_id, name: CONSTRAINT_NAME, on_delete: :cascade, validate: false
+ end
+end
diff --git a/db/post_migrate/20200703064117_generate_missing_routes_for_bots.rb b/db/post_migrate/20200703064117_generate_missing_routes_for_bots.rb
new file mode 100644
index 00000000000..85d62cbb6dd
--- /dev/null
+++ b/db/post_migrate/20200703064117_generate_missing_routes_for_bots.rb
@@ -0,0 +1,92 @@
+# frozen_string_literal: true
+
+class GenerateMissingRoutesForBots < ActiveRecord::Migration[6.0]
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ class User < ActiveRecord::Base
+ self.table_name = 'users'
+
+ USER_TYPES = {
+ human: nil,
+ support_bot: 1,
+ alert_bot: 2,
+ visual_review_bot: 3,
+ service_user: 4,
+ ghost: 5,
+ project_bot: 6,
+ migration_bot: 7
+ }.with_indifferent_access.freeze
+
+ BOT_USER_TYPES = %w[alert_bot project_bot support_bot visual_review_bot migration_bot].freeze
+
+ scope :bots, -> { where(user_type: USER_TYPES.values_at(*BOT_USER_TYPES)) }
+ end
+
+ class Route < ActiveRecord::Base
+ self.table_name = 'routes'
+
+ validates :path,
+ uniqueness: { case_sensitive: false }
+ end
+
+ class Namespace < ActiveRecord::Base
+ self.table_name = 'namespaces'
+
+ belongs_to :owner, class_name: 'GenerateMissingRoutesForBots::User'
+
+ scope :for_user, -> { where('type IS NULL') }
+ scope :for_bots, -> { for_user.joins(:owner).merge(GenerateMissingRoutesForBots::User.bots) }
+
+ scope :without_routes, -> do
+ where(
+ 'NOT EXISTS (
+ SELECT 1
+ FROM routes
+ WHERE source_type = ?
+ AND source_id = namespaces.id
+ )',
+ self.source_type_for_route
+ )
+ end
+
+ def self.source_type_for_route
+ 'Namespace'
+ end
+
+ def attributes_for_insert
+ {
+ source_type: self.class.source_type_for_route,
+ source_id: id,
+ name: name,
+ path: path
+ }
+ end
+ end
+
+ def up
+ # Reset the column information of all the models that update the database
+ # to ensure the Active Record's knowledge of the table structure is current
+ Route.reset_column_information
+
+ logger = Gitlab::BackgroundMigration::Logger.build
+ attributes_to_be_logged = %w(id path name)
+
+ GenerateMissingRoutesForBots::Namespace.for_bots.without_routes.each do |namespace|
+ route = GenerateMissingRoutesForBots::Route.create(namespace.attributes_for_insert)
+ namespace_details = namespace.as_json.slice(*attributes_to_be_logged)
+
+ if route.persisted?
+ logger.info namespace_details.merge(message: 'a new route was created for the namespace')
+ else
+ errors = route.errors.full_messages.join(',')
+ logger.info namespace_details.merge(message: 'route creation failed for the namespace', errors: errors)
+ end
+ end
+ end
+
+ def down
+ # no op
+ end
+end
diff --git a/db/post_migrate/20200703125016_backfill_namespace_settings.rb b/db/post_migrate/20200703125016_backfill_namespace_settings.rb
new file mode 100644
index 00000000000..a7335e2d2b8
--- /dev/null
+++ b/db/post_migrate/20200703125016_backfill_namespace_settings.rb
@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+
+class BackfillNamespaceSettings < ActiveRecord::Migration[5.2]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ MIGRATION = 'BackfillNamespaceSettings'
+ DELAY_INTERVAL = 2.minutes
+ BATCH_SIZE = 10_000
+
+ disable_ddl_transaction!
+
+ class Namespace < ActiveRecord::Base
+ include EachBatch
+
+ self.table_name = 'namespaces'
+ end
+
+ def up
+ say "Scheduling `#{MIGRATION}` jobs"
+
+ queue_background_migration_jobs_by_range_at_intervals(Namespace, MIGRATION, DELAY_INTERVAL, batch_size: BATCH_SIZE)
+ end
+
+ def down
+ # NOOP
+ end
+end
diff --git a/db/post_migrate/20200704143633_add_index_on_user_id_and_created_at_where_source_to_ci_pipelines.rb b/db/post_migrate/20200704143633_add_index_on_user_id_and_created_at_where_source_to_ci_pipelines.rb
new file mode 100644
index 00000000000..d84b2b4cad3
--- /dev/null
+++ b/db/post_migrate/20200704143633_add_index_on_user_id_and_created_at_where_source_to_ci_pipelines.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+class AddIndexOnUserIdAndCreatedAtWhereSourceToCiPipelines < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ add_concurrent_index :ci_pipelines, [:user_id, :created_at, :config_source]
+ end
+
+ def down
+ remove_concurrent_index :ci_pipelines, [:user_id, :created_at, :config_source]
+ end
+end
diff --git a/db/post_migrate/20200704161600_add_index_on_id_and_status_and_created_at_to_deployments.rb b/db/post_migrate/20200704161600_add_index_on_id_and_status_and_created_at_to_deployments.rb
new file mode 100644
index 00000000000..3aab2fd2949
--- /dev/null
+++ b/db/post_migrate/20200704161600_add_index_on_id_and_status_and_created_at_to_deployments.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+class AddIndexOnIdAndStatusAndCreatedAtToDeployments < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ add_concurrent_index :deployments, [:id, :status, :created_at]
+ remove_concurrent_index :deployments, [:id, :status]
+ end
+
+ def down
+ add_concurrent_index :deployments, [:id, :status]
+ remove_concurrent_index :deployments, [:id, :status, :created_at]
+ end
+end
diff --git a/db/post_migrate/20200706154619_drop_ci_daily_report_results_table.rb b/db/post_migrate/20200706154619_drop_ci_daily_report_results_table.rb
new file mode 100644
index 00000000000..c6ce52012d6
--- /dev/null
+++ b/db/post_migrate/20200706154619_drop_ci_daily_report_results_table.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+
+class DropCiDailyReportResultsTable < ActiveRecord::Migration[6.0]
+ DOWNTIME = false
+
+ def up
+ drop_table :ci_daily_report_results
+ end
+
+ def down
+ create_table :ci_daily_report_results do |t|
+ t.date :date, null: false
+ t.bigint :project_id, null: false
+ t.bigint :last_pipeline_id, null: false
+ t.float :value, null: false
+ t.integer :param_type, limit: 8, null: false
+ t.string :ref_path, null: false
+ t.string :title, null: false
+
+ t.index :last_pipeline_id
+ t.index [:project_id, :ref_path, :param_type, :date, :title], name: 'index_daily_report_results_unique_columns', unique: true
+ end
+ end
+end
diff --git a/db/post_migrate/20200709101408_schedule_populate_project_snippet_statistics.rb b/db/post_migrate/20200709101408_schedule_populate_project_snippet_statistics.rb
new file mode 100644
index 00000000000..28527e67f4a
--- /dev/null
+++ b/db/post_migrate/20200709101408_schedule_populate_project_snippet_statistics.rb
@@ -0,0 +1,30 @@
+# frozen_string_literal: true
+
+class SchedulePopulateProjectSnippetStatistics < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+ DELAY_INTERVAL = 2.minutes.to_i
+ BATCH_SIZE = 500
+ MIGRATION = 'PopulateProjectSnippetStatistics'
+
+ disable_ddl_transaction!
+
+ def up
+ snippets = exec_query <<~SQL
+ SELECT snippets.id
+ FROM snippets
+ INNER JOIN projects ON projects.id = snippets.project_id
+ WHERE snippets.type = 'ProjectSnippet'
+ ORDER BY projects.namespace_id ASC, snippets.project_id ASC, snippets.id ASC
+ SQL
+
+ snippets.rows.flatten.in_groups_of(BATCH_SIZE, false).each_with_index do |snippet_ids, index|
+ migrate_in(index * DELAY_INTERVAL, MIGRATION, [snippet_ids])
+ end
+ end
+
+ def down
+ # no-op
+ end
+end
diff --git a/db/post_migrate/20200710102418_delete_user_callout_alerts_moved.rb b/db/post_migrate/20200710102418_delete_user_callout_alerts_moved.rb
new file mode 100644
index 00000000000..e14cd7ac3ee
--- /dev/null
+++ b/db/post_migrate/20200710102418_delete_user_callout_alerts_moved.rb
@@ -0,0 +1,28 @@
+# frozen_string_literal: true
+
+class DeleteUserCalloutAlertsMoved < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ class UserCallout < ActiveRecord::Base
+ include EachBatch
+
+ self.table_name = 'user_callouts'
+ end
+
+ BATCH_SIZE = 1_000
+
+ # Inlined from UserCalloutEnums.feature_names
+ FEATURE_NAME_ALERTS_MOVED = 20
+
+ def up
+ UserCallout.each_batch(of: BATCH_SIZE, column: :user_id) do |callout|
+ callout.where(feature_name: FEATURE_NAME_ALERTS_MOVED).delete_all
+ end
+ end
+
+ def down
+ # no-op
+ end
+end
diff --git a/db/post_migrate/20200710102846_drop_index_ruby_objects_in_details_on_audit_events.rb b/db/post_migrate/20200710102846_drop_index_ruby_objects_in_details_on_audit_events.rb
new file mode 100644
index 00000000000..6869938466a
--- /dev/null
+++ b/db/post_migrate/20200710102846_drop_index_ruby_objects_in_details_on_audit_events.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+class DropIndexRubyObjectsInDetailsOnAuditEvents < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+ INDEX_NAME = 'index_audit_events_on_ruby_object_in_details'
+
+ disable_ddl_transaction!
+
+ def up
+ remove_concurrent_index_by_name(:audit_events, INDEX_NAME)
+ end
+
+ def down
+ add_concurrent_index(:audit_events, :id, where: "details ~~ '%ruby/object%'", name: INDEX_NAME)
+ end
+end
diff --git a/db/post_migrate/20200713071042_confirm_project_bot_users.rb b/db/post_migrate/20200713071042_confirm_project_bot_users.rb
new file mode 100644
index 00000000000..0578fc42ef2
--- /dev/null
+++ b/db/post_migrate/20200713071042_confirm_project_bot_users.rb
@@ -0,0 +1,30 @@
+# frozen_string_literal: true
+
+class ConfirmProjectBotUsers < ActiveRecord::Migration[6.0]
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ class User < ApplicationRecord
+ self.table_name = 'users'
+
+ include ::EachBatch
+
+ USER_TYPE_PROJECT_BOT = 6
+
+ scope :project_bots, -> { where(user_type: USER_TYPE_PROJECT_BOT) }
+ scope :unconfirmed, -> { where(confirmed_at: nil) }
+ end
+
+ def up
+ User.reset_column_information
+
+ User.project_bots.unconfirmed.each_batch do |relation|
+ relation.update_all('confirmed_at = created_at')
+ end
+ end
+
+ def down
+ # no-op
+ end
+end
diff --git a/db/structure.sql b/db/structure.sql
index fbcbcfc0f16..4cf825da8c9 100644
--- a/db/structure.sql
+++ b/db/structure.sql
@@ -1,7 +1,8669 @@
SET search_path=public;
+CREATE SCHEMA gitlab_partitions_dynamic;
+
+COMMENT ON SCHEMA gitlab_partitions_dynamic IS 'Schema to hold partitions managed dynamically from the application, e.g. for time space partitioning.';
+
+CREATE SCHEMA gitlab_partitions_static;
+
+COMMENT ON SCHEMA gitlab_partitions_static IS 'Schema to hold static partitions, e.g. for hash partitioning';
+
CREATE EXTENSION IF NOT EXISTS pg_trgm WITH SCHEMA public;
+CREATE TABLE public.product_analytics_events_experimental (
+ id bigint NOT NULL,
+ project_id integer NOT NULL,
+ platform character varying(255),
+ etl_tstamp timestamp with time zone,
+ collector_tstamp timestamp with time zone NOT NULL,
+ dvce_created_tstamp timestamp with time zone,
+ event character varying(128),
+ event_id character(36) NOT NULL,
+ txn_id integer,
+ name_tracker character varying(128),
+ v_tracker character varying(100),
+ v_collector character varying(100) NOT NULL,
+ v_etl character varying(100) NOT NULL,
+ user_id character varying(255),
+ user_ipaddress character varying(45),
+ user_fingerprint character varying(50),
+ domain_userid character varying(36),
+ domain_sessionidx smallint,
+ network_userid character varying(38),
+ geo_country character(2),
+ geo_region character(3),
+ geo_city character varying(75),
+ geo_zipcode character varying(15),
+ geo_latitude double precision,
+ geo_longitude double precision,
+ geo_region_name character varying(100),
+ ip_isp character varying(100),
+ ip_organization character varying(100),
+ ip_domain character varying(100),
+ ip_netspeed character varying(100),
+ page_url text,
+ page_title character varying(2000),
+ page_referrer text,
+ page_urlscheme character varying(16),
+ page_urlhost character varying(255),
+ page_urlport integer,
+ page_urlpath character varying(3000),
+ page_urlquery character varying(6000),
+ page_urlfragment character varying(3000),
+ refr_urlscheme character varying(16),
+ refr_urlhost character varying(255),
+ refr_urlport integer,
+ refr_urlpath character varying(6000),
+ refr_urlquery character varying(6000),
+ refr_urlfragment character varying(3000),
+ refr_medium character varying(25),
+ refr_source character varying(50),
+ refr_term character varying(255),
+ mkt_medium character varying(255),
+ mkt_source character varying(255),
+ mkt_term character varying(255),
+ mkt_content character varying(500),
+ mkt_campaign character varying(255),
+ se_category character varying(1000),
+ se_action character varying(1000),
+ se_label character varying(1000),
+ se_property character varying(1000),
+ se_value double precision,
+ tr_orderid character varying(255),
+ tr_affiliation character varying(255),
+ tr_total numeric(18,2),
+ tr_tax numeric(18,2),
+ tr_shipping numeric(18,2),
+ tr_city character varying(255),
+ tr_state character varying(255),
+ tr_country character varying(255),
+ ti_orderid character varying(255),
+ ti_sku character varying(255),
+ ti_name character varying(255),
+ ti_category character varying(255),
+ ti_price numeric(18,2),
+ ti_quantity integer,
+ pp_xoffset_min integer,
+ pp_xoffset_max integer,
+ pp_yoffset_min integer,
+ pp_yoffset_max integer,
+ useragent character varying(1000),
+ br_name character varying(50),
+ br_family character varying(50),
+ br_version character varying(50),
+ br_type character varying(50),
+ br_renderengine character varying(50),
+ br_lang character varying(255),
+ br_features_pdf boolean,
+ br_features_flash boolean,
+ br_features_java boolean,
+ br_features_director boolean,
+ br_features_quicktime boolean,
+ br_features_realplayer boolean,
+ br_features_windowsmedia boolean,
+ br_features_gears boolean,
+ br_features_silverlight boolean,
+ br_cookies boolean,
+ br_colordepth character varying(12),
+ br_viewwidth integer,
+ br_viewheight integer,
+ os_name character varying(50),
+ os_family character varying(50),
+ os_manufacturer character varying(50),
+ os_timezone character varying(50),
+ dvce_type character varying(50),
+ dvce_ismobile boolean,
+ dvce_screenwidth integer,
+ dvce_screenheight integer,
+ doc_charset character varying(128),
+ doc_width integer,
+ doc_height integer,
+ tr_currency character(3),
+ tr_total_base numeric(18,2),
+ tr_tax_base numeric(18,2),
+ tr_shipping_base numeric(18,2),
+ ti_currency character(3),
+ ti_price_base numeric(18,2),
+ base_currency character(3),
+ geo_timezone character varying(64),
+ mkt_clickid character varying(128),
+ mkt_network character varying(64),
+ etl_tags character varying(500),
+ dvce_sent_tstamp timestamp with time zone,
+ refr_domain_userid character varying(36),
+ refr_dvce_tstamp timestamp with time zone,
+ domain_sessionid character(36),
+ derived_tstamp timestamp with time zone,
+ event_vendor character varying(1000),
+ event_name character varying(1000),
+ event_format character varying(128),
+ event_version character varying(128),
+ event_fingerprint character varying(128),
+ true_tstamp timestamp with time zone
+)
+PARTITION BY HASH (project_id);
+
+CREATE SEQUENCE public.product_analytics_events_experimental_id_seq
+ START WITH 1
+ INCREMENT BY 1
+ NO MINVALUE
+ NO MAXVALUE
+ CACHE 1;
+
+ALTER SEQUENCE public.product_analytics_events_experimental_id_seq OWNED BY public.product_analytics_events_experimental.id;
+
+CREATE TABLE gitlab_partitions_static.product_analytics_events_experimental_00 (
+ id bigint DEFAULT nextval('public.product_analytics_events_experimental_id_seq'::regclass) NOT NULL,
+ project_id integer NOT NULL,
+ platform character varying(255),
+ etl_tstamp timestamp with time zone,
+ collector_tstamp timestamp with time zone NOT NULL,
+ dvce_created_tstamp timestamp with time zone,
+ event character varying(128),
+ event_id character(36) NOT NULL,
+ txn_id integer,
+ name_tracker character varying(128),
+ v_tracker character varying(100),
+ v_collector character varying(100) NOT NULL,
+ v_etl character varying(100) NOT NULL,
+ user_id character varying(255),
+ user_ipaddress character varying(45),
+ user_fingerprint character varying(50),
+ domain_userid character varying(36),
+ domain_sessionidx smallint,
+ network_userid character varying(38),
+ geo_country character(2),
+ geo_region character(3),
+ geo_city character varying(75),
+ geo_zipcode character varying(15),
+ geo_latitude double precision,
+ geo_longitude double precision,
+ geo_region_name character varying(100),
+ ip_isp character varying(100),
+ ip_organization character varying(100),
+ ip_domain character varying(100),
+ ip_netspeed character varying(100),
+ page_url text,
+ page_title character varying(2000),
+ page_referrer text,
+ page_urlscheme character varying(16),
+ page_urlhost character varying(255),
+ page_urlport integer,
+ page_urlpath character varying(3000),
+ page_urlquery character varying(6000),
+ page_urlfragment character varying(3000),
+ refr_urlscheme character varying(16),
+ refr_urlhost character varying(255),
+ refr_urlport integer,
+ refr_urlpath character varying(6000),
+ refr_urlquery character varying(6000),
+ refr_urlfragment character varying(3000),
+ refr_medium character varying(25),
+ refr_source character varying(50),
+ refr_term character varying(255),
+ mkt_medium character varying(255),
+ mkt_source character varying(255),
+ mkt_term character varying(255),
+ mkt_content character varying(500),
+ mkt_campaign character varying(255),
+ se_category character varying(1000),
+ se_action character varying(1000),
+ se_label character varying(1000),
+ se_property character varying(1000),
+ se_value double precision,
+ tr_orderid character varying(255),
+ tr_affiliation character varying(255),
+ tr_total numeric(18,2),
+ tr_tax numeric(18,2),
+ tr_shipping numeric(18,2),
+ tr_city character varying(255),
+ tr_state character varying(255),
+ tr_country character varying(255),
+ ti_orderid character varying(255),
+ ti_sku character varying(255),
+ ti_name character varying(255),
+ ti_category character varying(255),
+ ti_price numeric(18,2),
+ ti_quantity integer,
+ pp_xoffset_min integer,
+ pp_xoffset_max integer,
+ pp_yoffset_min integer,
+ pp_yoffset_max integer,
+ useragent character varying(1000),
+ br_name character varying(50),
+ br_family character varying(50),
+ br_version character varying(50),
+ br_type character varying(50),
+ br_renderengine character varying(50),
+ br_lang character varying(255),
+ br_features_pdf boolean,
+ br_features_flash boolean,
+ br_features_java boolean,
+ br_features_director boolean,
+ br_features_quicktime boolean,
+ br_features_realplayer boolean,
+ br_features_windowsmedia boolean,
+ br_features_gears boolean,
+ br_features_silverlight boolean,
+ br_cookies boolean,
+ br_colordepth character varying(12),
+ br_viewwidth integer,
+ br_viewheight integer,
+ os_name character varying(50),
+ os_family character varying(50),
+ os_manufacturer character varying(50),
+ os_timezone character varying(50),
+ dvce_type character varying(50),
+ dvce_ismobile boolean,
+ dvce_screenwidth integer,
+ dvce_screenheight integer,
+ doc_charset character varying(128),
+ doc_width integer,
+ doc_height integer,
+ tr_currency character(3),
+ tr_total_base numeric(18,2),
+ tr_tax_base numeric(18,2),
+ tr_shipping_base numeric(18,2),
+ ti_currency character(3),
+ ti_price_base numeric(18,2),
+ base_currency character(3),
+ geo_timezone character varying(64),
+ mkt_clickid character varying(128),
+ mkt_network character varying(64),
+ etl_tags character varying(500),
+ dvce_sent_tstamp timestamp with time zone,
+ refr_domain_userid character varying(36),
+ refr_dvce_tstamp timestamp with time zone,
+ domain_sessionid character(36),
+ derived_tstamp timestamp with time zone,
+ event_vendor character varying(1000),
+ event_name character varying(1000),
+ event_format character varying(128),
+ event_version character varying(128),
+ event_fingerprint character varying(128),
+ true_tstamp timestamp with time zone
+);
+ALTER TABLE ONLY public.product_analytics_events_experimental ATTACH PARTITION gitlab_partitions_static.product_analytics_events_experimental_00 FOR VALUES WITH (modulus 64, remainder 0);
+
+CREATE TABLE gitlab_partitions_static.product_analytics_events_experimental_01 (
+ id bigint DEFAULT nextval('public.product_analytics_events_experimental_id_seq'::regclass) NOT NULL,
+ project_id integer NOT NULL,
+ platform character varying(255),
+ etl_tstamp timestamp with time zone,
+ collector_tstamp timestamp with time zone NOT NULL,
+ dvce_created_tstamp timestamp with time zone,
+ event character varying(128),
+ event_id character(36) NOT NULL,
+ txn_id integer,
+ name_tracker character varying(128),
+ v_tracker character varying(100),
+ v_collector character varying(100) NOT NULL,
+ v_etl character varying(100) NOT NULL,
+ user_id character varying(255),
+ user_ipaddress character varying(45),
+ user_fingerprint character varying(50),
+ domain_userid character varying(36),
+ domain_sessionidx smallint,
+ network_userid character varying(38),
+ geo_country character(2),
+ geo_region character(3),
+ geo_city character varying(75),
+ geo_zipcode character varying(15),
+ geo_latitude double precision,
+ geo_longitude double precision,
+ geo_region_name character varying(100),
+ ip_isp character varying(100),
+ ip_organization character varying(100),
+ ip_domain character varying(100),
+ ip_netspeed character varying(100),
+ page_url text,
+ page_title character varying(2000),
+ page_referrer text,
+ page_urlscheme character varying(16),
+ page_urlhost character varying(255),
+ page_urlport integer,
+ page_urlpath character varying(3000),
+ page_urlquery character varying(6000),
+ page_urlfragment character varying(3000),
+ refr_urlscheme character varying(16),
+ refr_urlhost character varying(255),
+ refr_urlport integer,
+ refr_urlpath character varying(6000),
+ refr_urlquery character varying(6000),
+ refr_urlfragment character varying(3000),
+ refr_medium character varying(25),
+ refr_source character varying(50),
+ refr_term character varying(255),
+ mkt_medium character varying(255),
+ mkt_source character varying(255),
+ mkt_term character varying(255),
+ mkt_content character varying(500),
+ mkt_campaign character varying(255),
+ se_category character varying(1000),
+ se_action character varying(1000),
+ se_label character varying(1000),
+ se_property character varying(1000),
+ se_value double precision,
+ tr_orderid character varying(255),
+ tr_affiliation character varying(255),
+ tr_total numeric(18,2),
+ tr_tax numeric(18,2),
+ tr_shipping numeric(18,2),
+ tr_city character varying(255),
+ tr_state character varying(255),
+ tr_country character varying(255),
+ ti_orderid character varying(255),
+ ti_sku character varying(255),
+ ti_name character varying(255),
+ ti_category character varying(255),
+ ti_price numeric(18,2),
+ ti_quantity integer,
+ pp_xoffset_min integer,
+ pp_xoffset_max integer,
+ pp_yoffset_min integer,
+ pp_yoffset_max integer,
+ useragent character varying(1000),
+ br_name character varying(50),
+ br_family character varying(50),
+ br_version character varying(50),
+ br_type character varying(50),
+ br_renderengine character varying(50),
+ br_lang character varying(255),
+ br_features_pdf boolean,
+ br_features_flash boolean,
+ br_features_java boolean,
+ br_features_director boolean,
+ br_features_quicktime boolean,
+ br_features_realplayer boolean,
+ br_features_windowsmedia boolean,
+ br_features_gears boolean,
+ br_features_silverlight boolean,
+ br_cookies boolean,
+ br_colordepth character varying(12),
+ br_viewwidth integer,
+ br_viewheight integer,
+ os_name character varying(50),
+ os_family character varying(50),
+ os_manufacturer character varying(50),
+ os_timezone character varying(50),
+ dvce_type character varying(50),
+ dvce_ismobile boolean,
+ dvce_screenwidth integer,
+ dvce_screenheight integer,
+ doc_charset character varying(128),
+ doc_width integer,
+ doc_height integer,
+ tr_currency character(3),
+ tr_total_base numeric(18,2),
+ tr_tax_base numeric(18,2),
+ tr_shipping_base numeric(18,2),
+ ti_currency character(3),
+ ti_price_base numeric(18,2),
+ base_currency character(3),
+ geo_timezone character varying(64),
+ mkt_clickid character varying(128),
+ mkt_network character varying(64),
+ etl_tags character varying(500),
+ dvce_sent_tstamp timestamp with time zone,
+ refr_domain_userid character varying(36),
+ refr_dvce_tstamp timestamp with time zone,
+ domain_sessionid character(36),
+ derived_tstamp timestamp with time zone,
+ event_vendor character varying(1000),
+ event_name character varying(1000),
+ event_format character varying(128),
+ event_version character varying(128),
+ event_fingerprint character varying(128),
+ true_tstamp timestamp with time zone
+);
+ALTER TABLE ONLY public.product_analytics_events_experimental ATTACH PARTITION gitlab_partitions_static.product_analytics_events_experimental_01 FOR VALUES WITH (modulus 64, remainder 1);
+
+CREATE TABLE gitlab_partitions_static.product_analytics_events_experimental_02 (
+ id bigint DEFAULT nextval('public.product_analytics_events_experimental_id_seq'::regclass) NOT NULL,
+ project_id integer NOT NULL,
+ platform character varying(255),
+ etl_tstamp timestamp with time zone,
+ collector_tstamp timestamp with time zone NOT NULL,
+ dvce_created_tstamp timestamp with time zone,
+ event character varying(128),
+ event_id character(36) NOT NULL,
+ txn_id integer,
+ name_tracker character varying(128),
+ v_tracker character varying(100),
+ v_collector character varying(100) NOT NULL,
+ v_etl character varying(100) NOT NULL,
+ user_id character varying(255),
+ user_ipaddress character varying(45),
+ user_fingerprint character varying(50),
+ domain_userid character varying(36),
+ domain_sessionidx smallint,
+ network_userid character varying(38),
+ geo_country character(2),
+ geo_region character(3),
+ geo_city character varying(75),
+ geo_zipcode character varying(15),
+ geo_latitude double precision,
+ geo_longitude double precision,
+ geo_region_name character varying(100),
+ ip_isp character varying(100),
+ ip_organization character varying(100),
+ ip_domain character varying(100),
+ ip_netspeed character varying(100),
+ page_url text,
+ page_title character varying(2000),
+ page_referrer text,
+ page_urlscheme character varying(16),
+ page_urlhost character varying(255),
+ page_urlport integer,
+ page_urlpath character varying(3000),
+ page_urlquery character varying(6000),
+ page_urlfragment character varying(3000),
+ refr_urlscheme character varying(16),
+ refr_urlhost character varying(255),
+ refr_urlport integer,
+ refr_urlpath character varying(6000),
+ refr_urlquery character varying(6000),
+ refr_urlfragment character varying(3000),
+ refr_medium character varying(25),
+ refr_source character varying(50),
+ refr_term character varying(255),
+ mkt_medium character varying(255),
+ mkt_source character varying(255),
+ mkt_term character varying(255),
+ mkt_content character varying(500),
+ mkt_campaign character varying(255),
+ se_category character varying(1000),
+ se_action character varying(1000),
+ se_label character varying(1000),
+ se_property character varying(1000),
+ se_value double precision,
+ tr_orderid character varying(255),
+ tr_affiliation character varying(255),
+ tr_total numeric(18,2),
+ tr_tax numeric(18,2),
+ tr_shipping numeric(18,2),
+ tr_city character varying(255),
+ tr_state character varying(255),
+ tr_country character varying(255),
+ ti_orderid character varying(255),
+ ti_sku character varying(255),
+ ti_name character varying(255),
+ ti_category character varying(255),
+ ti_price numeric(18,2),
+ ti_quantity integer,
+ pp_xoffset_min integer,
+ pp_xoffset_max integer,
+ pp_yoffset_min integer,
+ pp_yoffset_max integer,
+ useragent character varying(1000),
+ br_name character varying(50),
+ br_family character varying(50),
+ br_version character varying(50),
+ br_type character varying(50),
+ br_renderengine character varying(50),
+ br_lang character varying(255),
+ br_features_pdf boolean,
+ br_features_flash boolean,
+ br_features_java boolean,
+ br_features_director boolean,
+ br_features_quicktime boolean,
+ br_features_realplayer boolean,
+ br_features_windowsmedia boolean,
+ br_features_gears boolean,
+ br_features_silverlight boolean,
+ br_cookies boolean,
+ br_colordepth character varying(12),
+ br_viewwidth integer,
+ br_viewheight integer,
+ os_name character varying(50),
+ os_family character varying(50),
+ os_manufacturer character varying(50),
+ os_timezone character varying(50),
+ dvce_type character varying(50),
+ dvce_ismobile boolean,
+ dvce_screenwidth integer,
+ dvce_screenheight integer,
+ doc_charset character varying(128),
+ doc_width integer,
+ doc_height integer,
+ tr_currency character(3),
+ tr_total_base numeric(18,2),
+ tr_tax_base numeric(18,2),
+ tr_shipping_base numeric(18,2),
+ ti_currency character(3),
+ ti_price_base numeric(18,2),
+ base_currency character(3),
+ geo_timezone character varying(64),
+ mkt_clickid character varying(128),
+ mkt_network character varying(64),
+ etl_tags character varying(500),
+ dvce_sent_tstamp timestamp with time zone,
+ refr_domain_userid character varying(36),
+ refr_dvce_tstamp timestamp with time zone,
+ domain_sessionid character(36),
+ derived_tstamp timestamp with time zone,
+ event_vendor character varying(1000),
+ event_name character varying(1000),
+ event_format character varying(128),
+ event_version character varying(128),
+ event_fingerprint character varying(128),
+ true_tstamp timestamp with time zone
+);
+ALTER TABLE ONLY public.product_analytics_events_experimental ATTACH PARTITION gitlab_partitions_static.product_analytics_events_experimental_02 FOR VALUES WITH (modulus 64, remainder 2);
+
+CREATE TABLE gitlab_partitions_static.product_analytics_events_experimental_03 (
+ id bigint DEFAULT nextval('public.product_analytics_events_experimental_id_seq'::regclass) NOT NULL,
+ project_id integer NOT NULL,
+ platform character varying(255),
+ etl_tstamp timestamp with time zone,
+ collector_tstamp timestamp with time zone NOT NULL,
+ dvce_created_tstamp timestamp with time zone,
+ event character varying(128),
+ event_id character(36) NOT NULL,
+ txn_id integer,
+ name_tracker character varying(128),
+ v_tracker character varying(100),
+ v_collector character varying(100) NOT NULL,
+ v_etl character varying(100) NOT NULL,
+ user_id character varying(255),
+ user_ipaddress character varying(45),
+ user_fingerprint character varying(50),
+ domain_userid character varying(36),
+ domain_sessionidx smallint,
+ network_userid character varying(38),
+ geo_country character(2),
+ geo_region character(3),
+ geo_city character varying(75),
+ geo_zipcode character varying(15),
+ geo_latitude double precision,
+ geo_longitude double precision,
+ geo_region_name character varying(100),
+ ip_isp character varying(100),
+ ip_organization character varying(100),
+ ip_domain character varying(100),
+ ip_netspeed character varying(100),
+ page_url text,
+ page_title character varying(2000),
+ page_referrer text,
+ page_urlscheme character varying(16),
+ page_urlhost character varying(255),
+ page_urlport integer,
+ page_urlpath character varying(3000),
+ page_urlquery character varying(6000),
+ page_urlfragment character varying(3000),
+ refr_urlscheme character varying(16),
+ refr_urlhost character varying(255),
+ refr_urlport integer,
+ refr_urlpath character varying(6000),
+ refr_urlquery character varying(6000),
+ refr_urlfragment character varying(3000),
+ refr_medium character varying(25),
+ refr_source character varying(50),
+ refr_term character varying(255),
+ mkt_medium character varying(255),
+ mkt_source character varying(255),
+ mkt_term character varying(255),
+ mkt_content character varying(500),
+ mkt_campaign character varying(255),
+ se_category character varying(1000),
+ se_action character varying(1000),
+ se_label character varying(1000),
+ se_property character varying(1000),
+ se_value double precision,
+ tr_orderid character varying(255),
+ tr_affiliation character varying(255),
+ tr_total numeric(18,2),
+ tr_tax numeric(18,2),
+ tr_shipping numeric(18,2),
+ tr_city character varying(255),
+ tr_state character varying(255),
+ tr_country character varying(255),
+ ti_orderid character varying(255),
+ ti_sku character varying(255),
+ ti_name character varying(255),
+ ti_category character varying(255),
+ ti_price numeric(18,2),
+ ti_quantity integer,
+ pp_xoffset_min integer,
+ pp_xoffset_max integer,
+ pp_yoffset_min integer,
+ pp_yoffset_max integer,
+ useragent character varying(1000),
+ br_name character varying(50),
+ br_family character varying(50),
+ br_version character varying(50),
+ br_type character varying(50),
+ br_renderengine character varying(50),
+ br_lang character varying(255),
+ br_features_pdf boolean,
+ br_features_flash boolean,
+ br_features_java boolean,
+ br_features_director boolean,
+ br_features_quicktime boolean,
+ br_features_realplayer boolean,
+ br_features_windowsmedia boolean,
+ br_features_gears boolean,
+ br_features_silverlight boolean,
+ br_cookies boolean,
+ br_colordepth character varying(12),
+ br_viewwidth integer,
+ br_viewheight integer,
+ os_name character varying(50),
+ os_family character varying(50),
+ os_manufacturer character varying(50),
+ os_timezone character varying(50),
+ dvce_type character varying(50),
+ dvce_ismobile boolean,
+ dvce_screenwidth integer,
+ dvce_screenheight integer,
+ doc_charset character varying(128),
+ doc_width integer,
+ doc_height integer,
+ tr_currency character(3),
+ tr_total_base numeric(18,2),
+ tr_tax_base numeric(18,2),
+ tr_shipping_base numeric(18,2),
+ ti_currency character(3),
+ ti_price_base numeric(18,2),
+ base_currency character(3),
+ geo_timezone character varying(64),
+ mkt_clickid character varying(128),
+ mkt_network character varying(64),
+ etl_tags character varying(500),
+ dvce_sent_tstamp timestamp with time zone,
+ refr_domain_userid character varying(36),
+ refr_dvce_tstamp timestamp with time zone,
+ domain_sessionid character(36),
+ derived_tstamp timestamp with time zone,
+ event_vendor character varying(1000),
+ event_name character varying(1000),
+ event_format character varying(128),
+ event_version character varying(128),
+ event_fingerprint character varying(128),
+ true_tstamp timestamp with time zone
+);
+ALTER TABLE ONLY public.product_analytics_events_experimental ATTACH PARTITION gitlab_partitions_static.product_analytics_events_experimental_03 FOR VALUES WITH (modulus 64, remainder 3);
+
+CREATE TABLE gitlab_partitions_static.product_analytics_events_experimental_04 (
+ id bigint DEFAULT nextval('public.product_analytics_events_experimental_id_seq'::regclass) NOT NULL,
+ project_id integer NOT NULL,
+ platform character varying(255),
+ etl_tstamp timestamp with time zone,
+ collector_tstamp timestamp with time zone NOT NULL,
+ dvce_created_tstamp timestamp with time zone,
+ event character varying(128),
+ event_id character(36) NOT NULL,
+ txn_id integer,
+ name_tracker character varying(128),
+ v_tracker character varying(100),
+ v_collector character varying(100) NOT NULL,
+ v_etl character varying(100) NOT NULL,
+ user_id character varying(255),
+ user_ipaddress character varying(45),
+ user_fingerprint character varying(50),
+ domain_userid character varying(36),
+ domain_sessionidx smallint,
+ network_userid character varying(38),
+ geo_country character(2),
+ geo_region character(3),
+ geo_city character varying(75),
+ geo_zipcode character varying(15),
+ geo_latitude double precision,
+ geo_longitude double precision,
+ geo_region_name character varying(100),
+ ip_isp character varying(100),
+ ip_organization character varying(100),
+ ip_domain character varying(100),
+ ip_netspeed character varying(100),
+ page_url text,
+ page_title character varying(2000),
+ page_referrer text,
+ page_urlscheme character varying(16),
+ page_urlhost character varying(255),
+ page_urlport integer,
+ page_urlpath character varying(3000),
+ page_urlquery character varying(6000),
+ page_urlfragment character varying(3000),
+ refr_urlscheme character varying(16),
+ refr_urlhost character varying(255),
+ refr_urlport integer,
+ refr_urlpath character varying(6000),
+ refr_urlquery character varying(6000),
+ refr_urlfragment character varying(3000),
+ refr_medium character varying(25),
+ refr_source character varying(50),
+ refr_term character varying(255),
+ mkt_medium character varying(255),
+ mkt_source character varying(255),
+ mkt_term character varying(255),
+ mkt_content character varying(500),
+ mkt_campaign character varying(255),
+ se_category character varying(1000),
+ se_action character varying(1000),
+ se_label character varying(1000),
+ se_property character varying(1000),
+ se_value double precision,
+ tr_orderid character varying(255),
+ tr_affiliation character varying(255),
+ tr_total numeric(18,2),
+ tr_tax numeric(18,2),
+ tr_shipping numeric(18,2),
+ tr_city character varying(255),
+ tr_state character varying(255),
+ tr_country character varying(255),
+ ti_orderid character varying(255),
+ ti_sku character varying(255),
+ ti_name character varying(255),
+ ti_category character varying(255),
+ ti_price numeric(18,2),
+ ti_quantity integer,
+ pp_xoffset_min integer,
+ pp_xoffset_max integer,
+ pp_yoffset_min integer,
+ pp_yoffset_max integer,
+ useragent character varying(1000),
+ br_name character varying(50),
+ br_family character varying(50),
+ br_version character varying(50),
+ br_type character varying(50),
+ br_renderengine character varying(50),
+ br_lang character varying(255),
+ br_features_pdf boolean,
+ br_features_flash boolean,
+ br_features_java boolean,
+ br_features_director boolean,
+ br_features_quicktime boolean,
+ br_features_realplayer boolean,
+ br_features_windowsmedia boolean,
+ br_features_gears boolean,
+ br_features_silverlight boolean,
+ br_cookies boolean,
+ br_colordepth character varying(12),
+ br_viewwidth integer,
+ br_viewheight integer,
+ os_name character varying(50),
+ os_family character varying(50),
+ os_manufacturer character varying(50),
+ os_timezone character varying(50),
+ dvce_type character varying(50),
+ dvce_ismobile boolean,
+ dvce_screenwidth integer,
+ dvce_screenheight integer,
+ doc_charset character varying(128),
+ doc_width integer,
+ doc_height integer,
+ tr_currency character(3),
+ tr_total_base numeric(18,2),
+ tr_tax_base numeric(18,2),
+ tr_shipping_base numeric(18,2),
+ ti_currency character(3),
+ ti_price_base numeric(18,2),
+ base_currency character(3),
+ geo_timezone character varying(64),
+ mkt_clickid character varying(128),
+ mkt_network character varying(64),
+ etl_tags character varying(500),
+ dvce_sent_tstamp timestamp with time zone,
+ refr_domain_userid character varying(36),
+ refr_dvce_tstamp timestamp with time zone,
+ domain_sessionid character(36),
+ derived_tstamp timestamp with time zone,
+ event_vendor character varying(1000),
+ event_name character varying(1000),
+ event_format character varying(128),
+ event_version character varying(128),
+ event_fingerprint character varying(128),
+ true_tstamp timestamp with time zone
+);
+ALTER TABLE ONLY public.product_analytics_events_experimental ATTACH PARTITION gitlab_partitions_static.product_analytics_events_experimental_04 FOR VALUES WITH (modulus 64, remainder 4);
+
+CREATE TABLE gitlab_partitions_static.product_analytics_events_experimental_05 (
+ id bigint DEFAULT nextval('public.product_analytics_events_experimental_id_seq'::regclass) NOT NULL,
+ project_id integer NOT NULL,
+ platform character varying(255),
+ etl_tstamp timestamp with time zone,
+ collector_tstamp timestamp with time zone NOT NULL,
+ dvce_created_tstamp timestamp with time zone,
+ event character varying(128),
+ event_id character(36) NOT NULL,
+ txn_id integer,
+ name_tracker character varying(128),
+ v_tracker character varying(100),
+ v_collector character varying(100) NOT NULL,
+ v_etl character varying(100) NOT NULL,
+ user_id character varying(255),
+ user_ipaddress character varying(45),
+ user_fingerprint character varying(50),
+ domain_userid character varying(36),
+ domain_sessionidx smallint,
+ network_userid character varying(38),
+ geo_country character(2),
+ geo_region character(3),
+ geo_city character varying(75),
+ geo_zipcode character varying(15),
+ geo_latitude double precision,
+ geo_longitude double precision,
+ geo_region_name character varying(100),
+ ip_isp character varying(100),
+ ip_organization character varying(100),
+ ip_domain character varying(100),
+ ip_netspeed character varying(100),
+ page_url text,
+ page_title character varying(2000),
+ page_referrer text,
+ page_urlscheme character varying(16),
+ page_urlhost character varying(255),
+ page_urlport integer,
+ page_urlpath character varying(3000),
+ page_urlquery character varying(6000),
+ page_urlfragment character varying(3000),
+ refr_urlscheme character varying(16),
+ refr_urlhost character varying(255),
+ refr_urlport integer,
+ refr_urlpath character varying(6000),
+ refr_urlquery character varying(6000),
+ refr_urlfragment character varying(3000),
+ refr_medium character varying(25),
+ refr_source character varying(50),
+ refr_term character varying(255),
+ mkt_medium character varying(255),
+ mkt_source character varying(255),
+ mkt_term character varying(255),
+ mkt_content character varying(500),
+ mkt_campaign character varying(255),
+ se_category character varying(1000),
+ se_action character varying(1000),
+ se_label character varying(1000),
+ se_property character varying(1000),
+ se_value double precision,
+ tr_orderid character varying(255),
+ tr_affiliation character varying(255),
+ tr_total numeric(18,2),
+ tr_tax numeric(18,2),
+ tr_shipping numeric(18,2),
+ tr_city character varying(255),
+ tr_state character varying(255),
+ tr_country character varying(255),
+ ti_orderid character varying(255),
+ ti_sku character varying(255),
+ ti_name character varying(255),
+ ti_category character varying(255),
+ ti_price numeric(18,2),
+ ti_quantity integer,
+ pp_xoffset_min integer,
+ pp_xoffset_max integer,
+ pp_yoffset_min integer,
+ pp_yoffset_max integer,
+ useragent character varying(1000),
+ br_name character varying(50),
+ br_family character varying(50),
+ br_version character varying(50),
+ br_type character varying(50),
+ br_renderengine character varying(50),
+ br_lang character varying(255),
+ br_features_pdf boolean,
+ br_features_flash boolean,
+ br_features_java boolean,
+ br_features_director boolean,
+ br_features_quicktime boolean,
+ br_features_realplayer boolean,
+ br_features_windowsmedia boolean,
+ br_features_gears boolean,
+ br_features_silverlight boolean,
+ br_cookies boolean,
+ br_colordepth character varying(12),
+ br_viewwidth integer,
+ br_viewheight integer,
+ os_name character varying(50),
+ os_family character varying(50),
+ os_manufacturer character varying(50),
+ os_timezone character varying(50),
+ dvce_type character varying(50),
+ dvce_ismobile boolean,
+ dvce_screenwidth integer,
+ dvce_screenheight integer,
+ doc_charset character varying(128),
+ doc_width integer,
+ doc_height integer,
+ tr_currency character(3),
+ tr_total_base numeric(18,2),
+ tr_tax_base numeric(18,2),
+ tr_shipping_base numeric(18,2),
+ ti_currency character(3),
+ ti_price_base numeric(18,2),
+ base_currency character(3),
+ geo_timezone character varying(64),
+ mkt_clickid character varying(128),
+ mkt_network character varying(64),
+ etl_tags character varying(500),
+ dvce_sent_tstamp timestamp with time zone,
+ refr_domain_userid character varying(36),
+ refr_dvce_tstamp timestamp with time zone,
+ domain_sessionid character(36),
+ derived_tstamp timestamp with time zone,
+ event_vendor character varying(1000),
+ event_name character varying(1000),
+ event_format character varying(128),
+ event_version character varying(128),
+ event_fingerprint character varying(128),
+ true_tstamp timestamp with time zone
+);
+ALTER TABLE ONLY public.product_analytics_events_experimental ATTACH PARTITION gitlab_partitions_static.product_analytics_events_experimental_05 FOR VALUES WITH (modulus 64, remainder 5);
+
+CREATE TABLE gitlab_partitions_static.product_analytics_events_experimental_06 (
+ id bigint DEFAULT nextval('public.product_analytics_events_experimental_id_seq'::regclass) NOT NULL,
+ project_id integer NOT NULL,
+ platform character varying(255),
+ etl_tstamp timestamp with time zone,
+ collector_tstamp timestamp with time zone NOT NULL,
+ dvce_created_tstamp timestamp with time zone,
+ event character varying(128),
+ event_id character(36) NOT NULL,
+ txn_id integer,
+ name_tracker character varying(128),
+ v_tracker character varying(100),
+ v_collector character varying(100) NOT NULL,
+ v_etl character varying(100) NOT NULL,
+ user_id character varying(255),
+ user_ipaddress character varying(45),
+ user_fingerprint character varying(50),
+ domain_userid character varying(36),
+ domain_sessionidx smallint,
+ network_userid character varying(38),
+ geo_country character(2),
+ geo_region character(3),
+ geo_city character varying(75),
+ geo_zipcode character varying(15),
+ geo_latitude double precision,
+ geo_longitude double precision,
+ geo_region_name character varying(100),
+ ip_isp character varying(100),
+ ip_organization character varying(100),
+ ip_domain character varying(100),
+ ip_netspeed character varying(100),
+ page_url text,
+ page_title character varying(2000),
+ page_referrer text,
+ page_urlscheme character varying(16),
+ page_urlhost character varying(255),
+ page_urlport integer,
+ page_urlpath character varying(3000),
+ page_urlquery character varying(6000),
+ page_urlfragment character varying(3000),
+ refr_urlscheme character varying(16),
+ refr_urlhost character varying(255),
+ refr_urlport integer,
+ refr_urlpath character varying(6000),
+ refr_urlquery character varying(6000),
+ refr_urlfragment character varying(3000),
+ refr_medium character varying(25),
+ refr_source character varying(50),
+ refr_term character varying(255),
+ mkt_medium character varying(255),
+ mkt_source character varying(255),
+ mkt_term character varying(255),
+ mkt_content character varying(500),
+ mkt_campaign character varying(255),
+ se_category character varying(1000),
+ se_action character varying(1000),
+ se_label character varying(1000),
+ se_property character varying(1000),
+ se_value double precision,
+ tr_orderid character varying(255),
+ tr_affiliation character varying(255),
+ tr_total numeric(18,2),
+ tr_tax numeric(18,2),
+ tr_shipping numeric(18,2),
+ tr_city character varying(255),
+ tr_state character varying(255),
+ tr_country character varying(255),
+ ti_orderid character varying(255),
+ ti_sku character varying(255),
+ ti_name character varying(255),
+ ti_category character varying(255),
+ ti_price numeric(18,2),
+ ti_quantity integer,
+ pp_xoffset_min integer,
+ pp_xoffset_max integer,
+ pp_yoffset_min integer,
+ pp_yoffset_max integer,
+ useragent character varying(1000),
+ br_name character varying(50),
+ br_family character varying(50),
+ br_version character varying(50),
+ br_type character varying(50),
+ br_renderengine character varying(50),
+ br_lang character varying(255),
+ br_features_pdf boolean,
+ br_features_flash boolean,
+ br_features_java boolean,
+ br_features_director boolean,
+ br_features_quicktime boolean,
+ br_features_realplayer boolean,
+ br_features_windowsmedia boolean,
+ br_features_gears boolean,
+ br_features_silverlight boolean,
+ br_cookies boolean,
+ br_colordepth character varying(12),
+ br_viewwidth integer,
+ br_viewheight integer,
+ os_name character varying(50),
+ os_family character varying(50),
+ os_manufacturer character varying(50),
+ os_timezone character varying(50),
+ dvce_type character varying(50),
+ dvce_ismobile boolean,
+ dvce_screenwidth integer,
+ dvce_screenheight integer,
+ doc_charset character varying(128),
+ doc_width integer,
+ doc_height integer,
+ tr_currency character(3),
+ tr_total_base numeric(18,2),
+ tr_tax_base numeric(18,2),
+ tr_shipping_base numeric(18,2),
+ ti_currency character(3),
+ ti_price_base numeric(18,2),
+ base_currency character(3),
+ geo_timezone character varying(64),
+ mkt_clickid character varying(128),
+ mkt_network character varying(64),
+ etl_tags character varying(500),
+ dvce_sent_tstamp timestamp with time zone,
+ refr_domain_userid character varying(36),
+ refr_dvce_tstamp timestamp with time zone,
+ domain_sessionid character(36),
+ derived_tstamp timestamp with time zone,
+ event_vendor character varying(1000),
+ event_name character varying(1000),
+ event_format character varying(128),
+ event_version character varying(128),
+ event_fingerprint character varying(128),
+ true_tstamp timestamp with time zone
+);
+ALTER TABLE ONLY public.product_analytics_events_experimental ATTACH PARTITION gitlab_partitions_static.product_analytics_events_experimental_06 FOR VALUES WITH (modulus 64, remainder 6);
+
+CREATE TABLE gitlab_partitions_static.product_analytics_events_experimental_07 (
+ id bigint DEFAULT nextval('public.product_analytics_events_experimental_id_seq'::regclass) NOT NULL,
+ project_id integer NOT NULL,
+ platform character varying(255),
+ etl_tstamp timestamp with time zone,
+ collector_tstamp timestamp with time zone NOT NULL,
+ dvce_created_tstamp timestamp with time zone,
+ event character varying(128),
+ event_id character(36) NOT NULL,
+ txn_id integer,
+ name_tracker character varying(128),
+ v_tracker character varying(100),
+ v_collector character varying(100) NOT NULL,
+ v_etl character varying(100) NOT NULL,
+ user_id character varying(255),
+ user_ipaddress character varying(45),
+ user_fingerprint character varying(50),
+ domain_userid character varying(36),
+ domain_sessionidx smallint,
+ network_userid character varying(38),
+ geo_country character(2),
+ geo_region character(3),
+ geo_city character varying(75),
+ geo_zipcode character varying(15),
+ geo_latitude double precision,
+ geo_longitude double precision,
+ geo_region_name character varying(100),
+ ip_isp character varying(100),
+ ip_organization character varying(100),
+ ip_domain character varying(100),
+ ip_netspeed character varying(100),
+ page_url text,
+ page_title character varying(2000),
+ page_referrer text,
+ page_urlscheme character varying(16),
+ page_urlhost character varying(255),
+ page_urlport integer,
+ page_urlpath character varying(3000),
+ page_urlquery character varying(6000),
+ page_urlfragment character varying(3000),
+ refr_urlscheme character varying(16),
+ refr_urlhost character varying(255),
+ refr_urlport integer,
+ refr_urlpath character varying(6000),
+ refr_urlquery character varying(6000),
+ refr_urlfragment character varying(3000),
+ refr_medium character varying(25),
+ refr_source character varying(50),
+ refr_term character varying(255),
+ mkt_medium character varying(255),
+ mkt_source character varying(255),
+ mkt_term character varying(255),
+ mkt_content character varying(500),
+ mkt_campaign character varying(255),
+ se_category character varying(1000),
+ se_action character varying(1000),
+ se_label character varying(1000),
+ se_property character varying(1000),
+ se_value double precision,
+ tr_orderid character varying(255),
+ tr_affiliation character varying(255),
+ tr_total numeric(18,2),
+ tr_tax numeric(18,2),
+ tr_shipping numeric(18,2),
+ tr_city character varying(255),
+ tr_state character varying(255),
+ tr_country character varying(255),
+ ti_orderid character varying(255),
+ ti_sku character varying(255),
+ ti_name character varying(255),
+ ti_category character varying(255),
+ ti_price numeric(18,2),
+ ti_quantity integer,
+ pp_xoffset_min integer,
+ pp_xoffset_max integer,
+ pp_yoffset_min integer,
+ pp_yoffset_max integer,
+ useragent character varying(1000),
+ br_name character varying(50),
+ br_family character varying(50),
+ br_version character varying(50),
+ br_type character varying(50),
+ br_renderengine character varying(50),
+ br_lang character varying(255),
+ br_features_pdf boolean,
+ br_features_flash boolean,
+ br_features_java boolean,
+ br_features_director boolean,
+ br_features_quicktime boolean,
+ br_features_realplayer boolean,
+ br_features_windowsmedia boolean,
+ br_features_gears boolean,
+ br_features_silverlight boolean,
+ br_cookies boolean,
+ br_colordepth character varying(12),
+ br_viewwidth integer,
+ br_viewheight integer,
+ os_name character varying(50),
+ os_family character varying(50),
+ os_manufacturer character varying(50),
+ os_timezone character varying(50),
+ dvce_type character varying(50),
+ dvce_ismobile boolean,
+ dvce_screenwidth integer,
+ dvce_screenheight integer,
+ doc_charset character varying(128),
+ doc_width integer,
+ doc_height integer,
+ tr_currency character(3),
+ tr_total_base numeric(18,2),
+ tr_tax_base numeric(18,2),
+ tr_shipping_base numeric(18,2),
+ ti_currency character(3),
+ ti_price_base numeric(18,2),
+ base_currency character(3),
+ geo_timezone character varying(64),
+ mkt_clickid character varying(128),
+ mkt_network character varying(64),
+ etl_tags character varying(500),
+ dvce_sent_tstamp timestamp with time zone,
+ refr_domain_userid character varying(36),
+ refr_dvce_tstamp timestamp with time zone,
+ domain_sessionid character(36),
+ derived_tstamp timestamp with time zone,
+ event_vendor character varying(1000),
+ event_name character varying(1000),
+ event_format character varying(128),
+ event_version character varying(128),
+ event_fingerprint character varying(128),
+ true_tstamp timestamp with time zone
+);
+ALTER TABLE ONLY public.product_analytics_events_experimental ATTACH PARTITION gitlab_partitions_static.product_analytics_events_experimental_07 FOR VALUES WITH (modulus 64, remainder 7);
+
+CREATE TABLE gitlab_partitions_static.product_analytics_events_experimental_08 (
+ id bigint DEFAULT nextval('public.product_analytics_events_experimental_id_seq'::regclass) NOT NULL,
+ project_id integer NOT NULL,
+ platform character varying(255),
+ etl_tstamp timestamp with time zone,
+ collector_tstamp timestamp with time zone NOT NULL,
+ dvce_created_tstamp timestamp with time zone,
+ event character varying(128),
+ event_id character(36) NOT NULL,
+ txn_id integer,
+ name_tracker character varying(128),
+ v_tracker character varying(100),
+ v_collector character varying(100) NOT NULL,
+ v_etl character varying(100) NOT NULL,
+ user_id character varying(255),
+ user_ipaddress character varying(45),
+ user_fingerprint character varying(50),
+ domain_userid character varying(36),
+ domain_sessionidx smallint,
+ network_userid character varying(38),
+ geo_country character(2),
+ geo_region character(3),
+ geo_city character varying(75),
+ geo_zipcode character varying(15),
+ geo_latitude double precision,
+ geo_longitude double precision,
+ geo_region_name character varying(100),
+ ip_isp character varying(100),
+ ip_organization character varying(100),
+ ip_domain character varying(100),
+ ip_netspeed character varying(100),
+ page_url text,
+ page_title character varying(2000),
+ page_referrer text,
+ page_urlscheme character varying(16),
+ page_urlhost character varying(255),
+ page_urlport integer,
+ page_urlpath character varying(3000),
+ page_urlquery character varying(6000),
+ page_urlfragment character varying(3000),
+ refr_urlscheme character varying(16),
+ refr_urlhost character varying(255),
+ refr_urlport integer,
+ refr_urlpath character varying(6000),
+ refr_urlquery character varying(6000),
+ refr_urlfragment character varying(3000),
+ refr_medium character varying(25),
+ refr_source character varying(50),
+ refr_term character varying(255),
+ mkt_medium character varying(255),
+ mkt_source character varying(255),
+ mkt_term character varying(255),
+ mkt_content character varying(500),
+ mkt_campaign character varying(255),
+ se_category character varying(1000),
+ se_action character varying(1000),
+ se_label character varying(1000),
+ se_property character varying(1000),
+ se_value double precision,
+ tr_orderid character varying(255),
+ tr_affiliation character varying(255),
+ tr_total numeric(18,2),
+ tr_tax numeric(18,2),
+ tr_shipping numeric(18,2),
+ tr_city character varying(255),
+ tr_state character varying(255),
+ tr_country character varying(255),
+ ti_orderid character varying(255),
+ ti_sku character varying(255),
+ ti_name character varying(255),
+ ti_category character varying(255),
+ ti_price numeric(18,2),
+ ti_quantity integer,
+ pp_xoffset_min integer,
+ pp_xoffset_max integer,
+ pp_yoffset_min integer,
+ pp_yoffset_max integer,
+ useragent character varying(1000),
+ br_name character varying(50),
+ br_family character varying(50),
+ br_version character varying(50),
+ br_type character varying(50),
+ br_renderengine character varying(50),
+ br_lang character varying(255),
+ br_features_pdf boolean,
+ br_features_flash boolean,
+ br_features_java boolean,
+ br_features_director boolean,
+ br_features_quicktime boolean,
+ br_features_realplayer boolean,
+ br_features_windowsmedia boolean,
+ br_features_gears boolean,
+ br_features_silverlight boolean,
+ br_cookies boolean,
+ br_colordepth character varying(12),
+ br_viewwidth integer,
+ br_viewheight integer,
+ os_name character varying(50),
+ os_family character varying(50),
+ os_manufacturer character varying(50),
+ os_timezone character varying(50),
+ dvce_type character varying(50),
+ dvce_ismobile boolean,
+ dvce_screenwidth integer,
+ dvce_screenheight integer,
+ doc_charset character varying(128),
+ doc_width integer,
+ doc_height integer,
+ tr_currency character(3),
+ tr_total_base numeric(18,2),
+ tr_tax_base numeric(18,2),
+ tr_shipping_base numeric(18,2),
+ ti_currency character(3),
+ ti_price_base numeric(18,2),
+ base_currency character(3),
+ geo_timezone character varying(64),
+ mkt_clickid character varying(128),
+ mkt_network character varying(64),
+ etl_tags character varying(500),
+ dvce_sent_tstamp timestamp with time zone,
+ refr_domain_userid character varying(36),
+ refr_dvce_tstamp timestamp with time zone,
+ domain_sessionid character(36),
+ derived_tstamp timestamp with time zone,
+ event_vendor character varying(1000),
+ event_name character varying(1000),
+ event_format character varying(128),
+ event_version character varying(128),
+ event_fingerprint character varying(128),
+ true_tstamp timestamp with time zone
+);
+ALTER TABLE ONLY public.product_analytics_events_experimental ATTACH PARTITION gitlab_partitions_static.product_analytics_events_experimental_08 FOR VALUES WITH (modulus 64, remainder 8);
+
+CREATE TABLE gitlab_partitions_static.product_analytics_events_experimental_09 (
+ id bigint DEFAULT nextval('public.product_analytics_events_experimental_id_seq'::regclass) NOT NULL,
+ project_id integer NOT NULL,
+ platform character varying(255),
+ etl_tstamp timestamp with time zone,
+ collector_tstamp timestamp with time zone NOT NULL,
+ dvce_created_tstamp timestamp with time zone,
+ event character varying(128),
+ event_id character(36) NOT NULL,
+ txn_id integer,
+ name_tracker character varying(128),
+ v_tracker character varying(100),
+ v_collector character varying(100) NOT NULL,
+ v_etl character varying(100) NOT NULL,
+ user_id character varying(255),
+ user_ipaddress character varying(45),
+ user_fingerprint character varying(50),
+ domain_userid character varying(36),
+ domain_sessionidx smallint,
+ network_userid character varying(38),
+ geo_country character(2),
+ geo_region character(3),
+ geo_city character varying(75),
+ geo_zipcode character varying(15),
+ geo_latitude double precision,
+ geo_longitude double precision,
+ geo_region_name character varying(100),
+ ip_isp character varying(100),
+ ip_organization character varying(100),
+ ip_domain character varying(100),
+ ip_netspeed character varying(100),
+ page_url text,
+ page_title character varying(2000),
+ page_referrer text,
+ page_urlscheme character varying(16),
+ page_urlhost character varying(255),
+ page_urlport integer,
+ page_urlpath character varying(3000),
+ page_urlquery character varying(6000),
+ page_urlfragment character varying(3000),
+ refr_urlscheme character varying(16),
+ refr_urlhost character varying(255),
+ refr_urlport integer,
+ refr_urlpath character varying(6000),
+ refr_urlquery character varying(6000),
+ refr_urlfragment character varying(3000),
+ refr_medium character varying(25),
+ refr_source character varying(50),
+ refr_term character varying(255),
+ mkt_medium character varying(255),
+ mkt_source character varying(255),
+ mkt_term character varying(255),
+ mkt_content character varying(500),
+ mkt_campaign character varying(255),
+ se_category character varying(1000),
+ se_action character varying(1000),
+ se_label character varying(1000),
+ se_property character varying(1000),
+ se_value double precision,
+ tr_orderid character varying(255),
+ tr_affiliation character varying(255),
+ tr_total numeric(18,2),
+ tr_tax numeric(18,2),
+ tr_shipping numeric(18,2),
+ tr_city character varying(255),
+ tr_state character varying(255),
+ tr_country character varying(255),
+ ti_orderid character varying(255),
+ ti_sku character varying(255),
+ ti_name character varying(255),
+ ti_category character varying(255),
+ ti_price numeric(18,2),
+ ti_quantity integer,
+ pp_xoffset_min integer,
+ pp_xoffset_max integer,
+ pp_yoffset_min integer,
+ pp_yoffset_max integer,
+ useragent character varying(1000),
+ br_name character varying(50),
+ br_family character varying(50),
+ br_version character varying(50),
+ br_type character varying(50),
+ br_renderengine character varying(50),
+ br_lang character varying(255),
+ br_features_pdf boolean,
+ br_features_flash boolean,
+ br_features_java boolean,
+ br_features_director boolean,
+ br_features_quicktime boolean,
+ br_features_realplayer boolean,
+ br_features_windowsmedia boolean,
+ br_features_gears boolean,
+ br_features_silverlight boolean,
+ br_cookies boolean,
+ br_colordepth character varying(12),
+ br_viewwidth integer,
+ br_viewheight integer,
+ os_name character varying(50),
+ os_family character varying(50),
+ os_manufacturer character varying(50),
+ os_timezone character varying(50),
+ dvce_type character varying(50),
+ dvce_ismobile boolean,
+ dvce_screenwidth integer,
+ dvce_screenheight integer,
+ doc_charset character varying(128),
+ doc_width integer,
+ doc_height integer,
+ tr_currency character(3),
+ tr_total_base numeric(18,2),
+ tr_tax_base numeric(18,2),
+ tr_shipping_base numeric(18,2),
+ ti_currency character(3),
+ ti_price_base numeric(18,2),
+ base_currency character(3),
+ geo_timezone character varying(64),
+ mkt_clickid character varying(128),
+ mkt_network character varying(64),
+ etl_tags character varying(500),
+ dvce_sent_tstamp timestamp with time zone,
+ refr_domain_userid character varying(36),
+ refr_dvce_tstamp timestamp with time zone,
+ domain_sessionid character(36),
+ derived_tstamp timestamp with time zone,
+ event_vendor character varying(1000),
+ event_name character varying(1000),
+ event_format character varying(128),
+ event_version character varying(128),
+ event_fingerprint character varying(128),
+ true_tstamp timestamp with time zone
+);
+ALTER TABLE ONLY public.product_analytics_events_experimental ATTACH PARTITION gitlab_partitions_static.product_analytics_events_experimental_09 FOR VALUES WITH (modulus 64, remainder 9);
+
+CREATE TABLE gitlab_partitions_static.product_analytics_events_experimental_10 (
+ id bigint DEFAULT nextval('public.product_analytics_events_experimental_id_seq'::regclass) NOT NULL,
+ project_id integer NOT NULL,
+ platform character varying(255),
+ etl_tstamp timestamp with time zone,
+ collector_tstamp timestamp with time zone NOT NULL,
+ dvce_created_tstamp timestamp with time zone,
+ event character varying(128),
+ event_id character(36) NOT NULL,
+ txn_id integer,
+ name_tracker character varying(128),
+ v_tracker character varying(100),
+ v_collector character varying(100) NOT NULL,
+ v_etl character varying(100) NOT NULL,
+ user_id character varying(255),
+ user_ipaddress character varying(45),
+ user_fingerprint character varying(50),
+ domain_userid character varying(36),
+ domain_sessionidx smallint,
+ network_userid character varying(38),
+ geo_country character(2),
+ geo_region character(3),
+ geo_city character varying(75),
+ geo_zipcode character varying(15),
+ geo_latitude double precision,
+ geo_longitude double precision,
+ geo_region_name character varying(100),
+ ip_isp character varying(100),
+ ip_organization character varying(100),
+ ip_domain character varying(100),
+ ip_netspeed character varying(100),
+ page_url text,
+ page_title character varying(2000),
+ page_referrer text,
+ page_urlscheme character varying(16),
+ page_urlhost character varying(255),
+ page_urlport integer,
+ page_urlpath character varying(3000),
+ page_urlquery character varying(6000),
+ page_urlfragment character varying(3000),
+ refr_urlscheme character varying(16),
+ refr_urlhost character varying(255),
+ refr_urlport integer,
+ refr_urlpath character varying(6000),
+ refr_urlquery character varying(6000),
+ refr_urlfragment character varying(3000),
+ refr_medium character varying(25),
+ refr_source character varying(50),
+ refr_term character varying(255),
+ mkt_medium character varying(255),
+ mkt_source character varying(255),
+ mkt_term character varying(255),
+ mkt_content character varying(500),
+ mkt_campaign character varying(255),
+ se_category character varying(1000),
+ se_action character varying(1000),
+ se_label character varying(1000),
+ se_property character varying(1000),
+ se_value double precision,
+ tr_orderid character varying(255),
+ tr_affiliation character varying(255),
+ tr_total numeric(18,2),
+ tr_tax numeric(18,2),
+ tr_shipping numeric(18,2),
+ tr_city character varying(255),
+ tr_state character varying(255),
+ tr_country character varying(255),
+ ti_orderid character varying(255),
+ ti_sku character varying(255),
+ ti_name character varying(255),
+ ti_category character varying(255),
+ ti_price numeric(18,2),
+ ti_quantity integer,
+ pp_xoffset_min integer,
+ pp_xoffset_max integer,
+ pp_yoffset_min integer,
+ pp_yoffset_max integer,
+ useragent character varying(1000),
+ br_name character varying(50),
+ br_family character varying(50),
+ br_version character varying(50),
+ br_type character varying(50),
+ br_renderengine character varying(50),
+ br_lang character varying(255),
+ br_features_pdf boolean,
+ br_features_flash boolean,
+ br_features_java boolean,
+ br_features_director boolean,
+ br_features_quicktime boolean,
+ br_features_realplayer boolean,
+ br_features_windowsmedia boolean,
+ br_features_gears boolean,
+ br_features_silverlight boolean,
+ br_cookies boolean,
+ br_colordepth character varying(12),
+ br_viewwidth integer,
+ br_viewheight integer,
+ os_name character varying(50),
+ os_family character varying(50),
+ os_manufacturer character varying(50),
+ os_timezone character varying(50),
+ dvce_type character varying(50),
+ dvce_ismobile boolean,
+ dvce_screenwidth integer,
+ dvce_screenheight integer,
+ doc_charset character varying(128),
+ doc_width integer,
+ doc_height integer,
+ tr_currency character(3),
+ tr_total_base numeric(18,2),
+ tr_tax_base numeric(18,2),
+ tr_shipping_base numeric(18,2),
+ ti_currency character(3),
+ ti_price_base numeric(18,2),
+ base_currency character(3),
+ geo_timezone character varying(64),
+ mkt_clickid character varying(128),
+ mkt_network character varying(64),
+ etl_tags character varying(500),
+ dvce_sent_tstamp timestamp with time zone,
+ refr_domain_userid character varying(36),
+ refr_dvce_tstamp timestamp with time zone,
+ domain_sessionid character(36),
+ derived_tstamp timestamp with time zone,
+ event_vendor character varying(1000),
+ event_name character varying(1000),
+ event_format character varying(128),
+ event_version character varying(128),
+ event_fingerprint character varying(128),
+ true_tstamp timestamp with time zone
+);
+ALTER TABLE ONLY public.product_analytics_events_experimental ATTACH PARTITION gitlab_partitions_static.product_analytics_events_experimental_10 FOR VALUES WITH (modulus 64, remainder 10);
+
+CREATE TABLE gitlab_partitions_static.product_analytics_events_experimental_11 (
+ id bigint DEFAULT nextval('public.product_analytics_events_experimental_id_seq'::regclass) NOT NULL,
+ project_id integer NOT NULL,
+ platform character varying(255),
+ etl_tstamp timestamp with time zone,
+ collector_tstamp timestamp with time zone NOT NULL,
+ dvce_created_tstamp timestamp with time zone,
+ event character varying(128),
+ event_id character(36) NOT NULL,
+ txn_id integer,
+ name_tracker character varying(128),
+ v_tracker character varying(100),
+ v_collector character varying(100) NOT NULL,
+ v_etl character varying(100) NOT NULL,
+ user_id character varying(255),
+ user_ipaddress character varying(45),
+ user_fingerprint character varying(50),
+ domain_userid character varying(36),
+ domain_sessionidx smallint,
+ network_userid character varying(38),
+ geo_country character(2),
+ geo_region character(3),
+ geo_city character varying(75),
+ geo_zipcode character varying(15),
+ geo_latitude double precision,
+ geo_longitude double precision,
+ geo_region_name character varying(100),
+ ip_isp character varying(100),
+ ip_organization character varying(100),
+ ip_domain character varying(100),
+ ip_netspeed character varying(100),
+ page_url text,
+ page_title character varying(2000),
+ page_referrer text,
+ page_urlscheme character varying(16),
+ page_urlhost character varying(255),
+ page_urlport integer,
+ page_urlpath character varying(3000),
+ page_urlquery character varying(6000),
+ page_urlfragment character varying(3000),
+ refr_urlscheme character varying(16),
+ refr_urlhost character varying(255),
+ refr_urlport integer,
+ refr_urlpath character varying(6000),
+ refr_urlquery character varying(6000),
+ refr_urlfragment character varying(3000),
+ refr_medium character varying(25),
+ refr_source character varying(50),
+ refr_term character varying(255),
+ mkt_medium character varying(255),
+ mkt_source character varying(255),
+ mkt_term character varying(255),
+ mkt_content character varying(500),
+ mkt_campaign character varying(255),
+ se_category character varying(1000),
+ se_action character varying(1000),
+ se_label character varying(1000),
+ se_property character varying(1000),
+ se_value double precision,
+ tr_orderid character varying(255),
+ tr_affiliation character varying(255),
+ tr_total numeric(18,2),
+ tr_tax numeric(18,2),
+ tr_shipping numeric(18,2),
+ tr_city character varying(255),
+ tr_state character varying(255),
+ tr_country character varying(255),
+ ti_orderid character varying(255),
+ ti_sku character varying(255),
+ ti_name character varying(255),
+ ti_category character varying(255),
+ ti_price numeric(18,2),
+ ti_quantity integer,
+ pp_xoffset_min integer,
+ pp_xoffset_max integer,
+ pp_yoffset_min integer,
+ pp_yoffset_max integer,
+ useragent character varying(1000),
+ br_name character varying(50),
+ br_family character varying(50),
+ br_version character varying(50),
+ br_type character varying(50),
+ br_renderengine character varying(50),
+ br_lang character varying(255),
+ br_features_pdf boolean,
+ br_features_flash boolean,
+ br_features_java boolean,
+ br_features_director boolean,
+ br_features_quicktime boolean,
+ br_features_realplayer boolean,
+ br_features_windowsmedia boolean,
+ br_features_gears boolean,
+ br_features_silverlight boolean,
+ br_cookies boolean,
+ br_colordepth character varying(12),
+ br_viewwidth integer,
+ br_viewheight integer,
+ os_name character varying(50),
+ os_family character varying(50),
+ os_manufacturer character varying(50),
+ os_timezone character varying(50),
+ dvce_type character varying(50),
+ dvce_ismobile boolean,
+ dvce_screenwidth integer,
+ dvce_screenheight integer,
+ doc_charset character varying(128),
+ doc_width integer,
+ doc_height integer,
+ tr_currency character(3),
+ tr_total_base numeric(18,2),
+ tr_tax_base numeric(18,2),
+ tr_shipping_base numeric(18,2),
+ ti_currency character(3),
+ ti_price_base numeric(18,2),
+ base_currency character(3),
+ geo_timezone character varying(64),
+ mkt_clickid character varying(128),
+ mkt_network character varying(64),
+ etl_tags character varying(500),
+ dvce_sent_tstamp timestamp with time zone,
+ refr_domain_userid character varying(36),
+ refr_dvce_tstamp timestamp with time zone,
+ domain_sessionid character(36),
+ derived_tstamp timestamp with time zone,
+ event_vendor character varying(1000),
+ event_name character varying(1000),
+ event_format character varying(128),
+ event_version character varying(128),
+ event_fingerprint character varying(128),
+ true_tstamp timestamp with time zone
+);
+ALTER TABLE ONLY public.product_analytics_events_experimental ATTACH PARTITION gitlab_partitions_static.product_analytics_events_experimental_11 FOR VALUES WITH (modulus 64, remainder 11);
+
+CREATE TABLE gitlab_partitions_static.product_analytics_events_experimental_12 (
+ id bigint DEFAULT nextval('public.product_analytics_events_experimental_id_seq'::regclass) NOT NULL,
+ project_id integer NOT NULL,
+ platform character varying(255),
+ etl_tstamp timestamp with time zone,
+ collector_tstamp timestamp with time zone NOT NULL,
+ dvce_created_tstamp timestamp with time zone,
+ event character varying(128),
+ event_id character(36) NOT NULL,
+ txn_id integer,
+ name_tracker character varying(128),
+ v_tracker character varying(100),
+ v_collector character varying(100) NOT NULL,
+ v_etl character varying(100) NOT NULL,
+ user_id character varying(255),
+ user_ipaddress character varying(45),
+ user_fingerprint character varying(50),
+ domain_userid character varying(36),
+ domain_sessionidx smallint,
+ network_userid character varying(38),
+ geo_country character(2),
+ geo_region character(3),
+ geo_city character varying(75),
+ geo_zipcode character varying(15),
+ geo_latitude double precision,
+ geo_longitude double precision,
+ geo_region_name character varying(100),
+ ip_isp character varying(100),
+ ip_organization character varying(100),
+ ip_domain character varying(100),
+ ip_netspeed character varying(100),
+ page_url text,
+ page_title character varying(2000),
+ page_referrer text,
+ page_urlscheme character varying(16),
+ page_urlhost character varying(255),
+ page_urlport integer,
+ page_urlpath character varying(3000),
+ page_urlquery character varying(6000),
+ page_urlfragment character varying(3000),
+ refr_urlscheme character varying(16),
+ refr_urlhost character varying(255),
+ refr_urlport integer,
+ refr_urlpath character varying(6000),
+ refr_urlquery character varying(6000),
+ refr_urlfragment character varying(3000),
+ refr_medium character varying(25),
+ refr_source character varying(50),
+ refr_term character varying(255),
+ mkt_medium character varying(255),
+ mkt_source character varying(255),
+ mkt_term character varying(255),
+ mkt_content character varying(500),
+ mkt_campaign character varying(255),
+ se_category character varying(1000),
+ se_action character varying(1000),
+ se_label character varying(1000),
+ se_property character varying(1000),
+ se_value double precision,
+ tr_orderid character varying(255),
+ tr_affiliation character varying(255),
+ tr_total numeric(18,2),
+ tr_tax numeric(18,2),
+ tr_shipping numeric(18,2),
+ tr_city character varying(255),
+ tr_state character varying(255),
+ tr_country character varying(255),
+ ti_orderid character varying(255),
+ ti_sku character varying(255),
+ ti_name character varying(255),
+ ti_category character varying(255),
+ ti_price numeric(18,2),
+ ti_quantity integer,
+ pp_xoffset_min integer,
+ pp_xoffset_max integer,
+ pp_yoffset_min integer,
+ pp_yoffset_max integer,
+ useragent character varying(1000),
+ br_name character varying(50),
+ br_family character varying(50),
+ br_version character varying(50),
+ br_type character varying(50),
+ br_renderengine character varying(50),
+ br_lang character varying(255),
+ br_features_pdf boolean,
+ br_features_flash boolean,
+ br_features_java boolean,
+ br_features_director boolean,
+ br_features_quicktime boolean,
+ br_features_realplayer boolean,
+ br_features_windowsmedia boolean,
+ br_features_gears boolean,
+ br_features_silverlight boolean,
+ br_cookies boolean,
+ br_colordepth character varying(12),
+ br_viewwidth integer,
+ br_viewheight integer,
+ os_name character varying(50),
+ os_family character varying(50),
+ os_manufacturer character varying(50),
+ os_timezone character varying(50),
+ dvce_type character varying(50),
+ dvce_ismobile boolean,
+ dvce_screenwidth integer,
+ dvce_screenheight integer,
+ doc_charset character varying(128),
+ doc_width integer,
+ doc_height integer,
+ tr_currency character(3),
+ tr_total_base numeric(18,2),
+ tr_tax_base numeric(18,2),
+ tr_shipping_base numeric(18,2),
+ ti_currency character(3),
+ ti_price_base numeric(18,2),
+ base_currency character(3),
+ geo_timezone character varying(64),
+ mkt_clickid character varying(128),
+ mkt_network character varying(64),
+ etl_tags character varying(500),
+ dvce_sent_tstamp timestamp with time zone,
+ refr_domain_userid character varying(36),
+ refr_dvce_tstamp timestamp with time zone,
+ domain_sessionid character(36),
+ derived_tstamp timestamp with time zone,
+ event_vendor character varying(1000),
+ event_name character varying(1000),
+ event_format character varying(128),
+ event_version character varying(128),
+ event_fingerprint character varying(128),
+ true_tstamp timestamp with time zone
+);
+ALTER TABLE ONLY public.product_analytics_events_experimental ATTACH PARTITION gitlab_partitions_static.product_analytics_events_experimental_12 FOR VALUES WITH (modulus 64, remainder 12);
+
+CREATE TABLE gitlab_partitions_static.product_analytics_events_experimental_13 (
+ id bigint DEFAULT nextval('public.product_analytics_events_experimental_id_seq'::regclass) NOT NULL,
+ project_id integer NOT NULL,
+ platform character varying(255),
+ etl_tstamp timestamp with time zone,
+ collector_tstamp timestamp with time zone NOT NULL,
+ dvce_created_tstamp timestamp with time zone,
+ event character varying(128),
+ event_id character(36) NOT NULL,
+ txn_id integer,
+ name_tracker character varying(128),
+ v_tracker character varying(100),
+ v_collector character varying(100) NOT NULL,
+ v_etl character varying(100) NOT NULL,
+ user_id character varying(255),
+ user_ipaddress character varying(45),
+ user_fingerprint character varying(50),
+ domain_userid character varying(36),
+ domain_sessionidx smallint,
+ network_userid character varying(38),
+ geo_country character(2),
+ geo_region character(3),
+ geo_city character varying(75),
+ geo_zipcode character varying(15),
+ geo_latitude double precision,
+ geo_longitude double precision,
+ geo_region_name character varying(100),
+ ip_isp character varying(100),
+ ip_organization character varying(100),
+ ip_domain character varying(100),
+ ip_netspeed character varying(100),
+ page_url text,
+ page_title character varying(2000),
+ page_referrer text,
+ page_urlscheme character varying(16),
+ page_urlhost character varying(255),
+ page_urlport integer,
+ page_urlpath character varying(3000),
+ page_urlquery character varying(6000),
+ page_urlfragment character varying(3000),
+ refr_urlscheme character varying(16),
+ refr_urlhost character varying(255),
+ refr_urlport integer,
+ refr_urlpath character varying(6000),
+ refr_urlquery character varying(6000),
+ refr_urlfragment character varying(3000),
+ refr_medium character varying(25),
+ refr_source character varying(50),
+ refr_term character varying(255),
+ mkt_medium character varying(255),
+ mkt_source character varying(255),
+ mkt_term character varying(255),
+ mkt_content character varying(500),
+ mkt_campaign character varying(255),
+ se_category character varying(1000),
+ se_action character varying(1000),
+ se_label character varying(1000),
+ se_property character varying(1000),
+ se_value double precision,
+ tr_orderid character varying(255),
+ tr_affiliation character varying(255),
+ tr_total numeric(18,2),
+ tr_tax numeric(18,2),
+ tr_shipping numeric(18,2),
+ tr_city character varying(255),
+ tr_state character varying(255),
+ tr_country character varying(255),
+ ti_orderid character varying(255),
+ ti_sku character varying(255),
+ ti_name character varying(255),
+ ti_category character varying(255),
+ ti_price numeric(18,2),
+ ti_quantity integer,
+ pp_xoffset_min integer,
+ pp_xoffset_max integer,
+ pp_yoffset_min integer,
+ pp_yoffset_max integer,
+ useragent character varying(1000),
+ br_name character varying(50),
+ br_family character varying(50),
+ br_version character varying(50),
+ br_type character varying(50),
+ br_renderengine character varying(50),
+ br_lang character varying(255),
+ br_features_pdf boolean,
+ br_features_flash boolean,
+ br_features_java boolean,
+ br_features_director boolean,
+ br_features_quicktime boolean,
+ br_features_realplayer boolean,
+ br_features_windowsmedia boolean,
+ br_features_gears boolean,
+ br_features_silverlight boolean,
+ br_cookies boolean,
+ br_colordepth character varying(12),
+ br_viewwidth integer,
+ br_viewheight integer,
+ os_name character varying(50),
+ os_family character varying(50),
+ os_manufacturer character varying(50),
+ os_timezone character varying(50),
+ dvce_type character varying(50),
+ dvce_ismobile boolean,
+ dvce_screenwidth integer,
+ dvce_screenheight integer,
+ doc_charset character varying(128),
+ doc_width integer,
+ doc_height integer,
+ tr_currency character(3),
+ tr_total_base numeric(18,2),
+ tr_tax_base numeric(18,2),
+ tr_shipping_base numeric(18,2),
+ ti_currency character(3),
+ ti_price_base numeric(18,2),
+ base_currency character(3),
+ geo_timezone character varying(64),
+ mkt_clickid character varying(128),
+ mkt_network character varying(64),
+ etl_tags character varying(500),
+ dvce_sent_tstamp timestamp with time zone,
+ refr_domain_userid character varying(36),
+ refr_dvce_tstamp timestamp with time zone,
+ domain_sessionid character(36),
+ derived_tstamp timestamp with time zone,
+ event_vendor character varying(1000),
+ event_name character varying(1000),
+ event_format character varying(128),
+ event_version character varying(128),
+ event_fingerprint character varying(128),
+ true_tstamp timestamp with time zone
+);
+ALTER TABLE ONLY public.product_analytics_events_experimental ATTACH PARTITION gitlab_partitions_static.product_analytics_events_experimental_13 FOR VALUES WITH (modulus 64, remainder 13);
+
+CREATE TABLE gitlab_partitions_static.product_analytics_events_experimental_14 (
+ id bigint DEFAULT nextval('public.product_analytics_events_experimental_id_seq'::regclass) NOT NULL,
+ project_id integer NOT NULL,
+ platform character varying(255),
+ etl_tstamp timestamp with time zone,
+ collector_tstamp timestamp with time zone NOT NULL,
+ dvce_created_tstamp timestamp with time zone,
+ event character varying(128),
+ event_id character(36) NOT NULL,
+ txn_id integer,
+ name_tracker character varying(128),
+ v_tracker character varying(100),
+ v_collector character varying(100) NOT NULL,
+ v_etl character varying(100) NOT NULL,
+ user_id character varying(255),
+ user_ipaddress character varying(45),
+ user_fingerprint character varying(50),
+ domain_userid character varying(36),
+ domain_sessionidx smallint,
+ network_userid character varying(38),
+ geo_country character(2),
+ geo_region character(3),
+ geo_city character varying(75),
+ geo_zipcode character varying(15),
+ geo_latitude double precision,
+ geo_longitude double precision,
+ geo_region_name character varying(100),
+ ip_isp character varying(100),
+ ip_organization character varying(100),
+ ip_domain character varying(100),
+ ip_netspeed character varying(100),
+ page_url text,
+ page_title character varying(2000),
+ page_referrer text,
+ page_urlscheme character varying(16),
+ page_urlhost character varying(255),
+ page_urlport integer,
+ page_urlpath character varying(3000),
+ page_urlquery character varying(6000),
+ page_urlfragment character varying(3000),
+ refr_urlscheme character varying(16),
+ refr_urlhost character varying(255),
+ refr_urlport integer,
+ refr_urlpath character varying(6000),
+ refr_urlquery character varying(6000),
+ refr_urlfragment character varying(3000),
+ refr_medium character varying(25),
+ refr_source character varying(50),
+ refr_term character varying(255),
+ mkt_medium character varying(255),
+ mkt_source character varying(255),
+ mkt_term character varying(255),
+ mkt_content character varying(500),
+ mkt_campaign character varying(255),
+ se_category character varying(1000),
+ se_action character varying(1000),
+ se_label character varying(1000),
+ se_property character varying(1000),
+ se_value double precision,
+ tr_orderid character varying(255),
+ tr_affiliation character varying(255),
+ tr_total numeric(18,2),
+ tr_tax numeric(18,2),
+ tr_shipping numeric(18,2),
+ tr_city character varying(255),
+ tr_state character varying(255),
+ tr_country character varying(255),
+ ti_orderid character varying(255),
+ ti_sku character varying(255),
+ ti_name character varying(255),
+ ti_category character varying(255),
+ ti_price numeric(18,2),
+ ti_quantity integer,
+ pp_xoffset_min integer,
+ pp_xoffset_max integer,
+ pp_yoffset_min integer,
+ pp_yoffset_max integer,
+ useragent character varying(1000),
+ br_name character varying(50),
+ br_family character varying(50),
+ br_version character varying(50),
+ br_type character varying(50),
+ br_renderengine character varying(50),
+ br_lang character varying(255),
+ br_features_pdf boolean,
+ br_features_flash boolean,
+ br_features_java boolean,
+ br_features_director boolean,
+ br_features_quicktime boolean,
+ br_features_realplayer boolean,
+ br_features_windowsmedia boolean,
+ br_features_gears boolean,
+ br_features_silverlight boolean,
+ br_cookies boolean,
+ br_colordepth character varying(12),
+ br_viewwidth integer,
+ br_viewheight integer,
+ os_name character varying(50),
+ os_family character varying(50),
+ os_manufacturer character varying(50),
+ os_timezone character varying(50),
+ dvce_type character varying(50),
+ dvce_ismobile boolean,
+ dvce_screenwidth integer,
+ dvce_screenheight integer,
+ doc_charset character varying(128),
+ doc_width integer,
+ doc_height integer,
+ tr_currency character(3),
+ tr_total_base numeric(18,2),
+ tr_tax_base numeric(18,2),
+ tr_shipping_base numeric(18,2),
+ ti_currency character(3),
+ ti_price_base numeric(18,2),
+ base_currency character(3),
+ geo_timezone character varying(64),
+ mkt_clickid character varying(128),
+ mkt_network character varying(64),
+ etl_tags character varying(500),
+ dvce_sent_tstamp timestamp with time zone,
+ refr_domain_userid character varying(36),
+ refr_dvce_tstamp timestamp with time zone,
+ domain_sessionid character(36),
+ derived_tstamp timestamp with time zone,
+ event_vendor character varying(1000),
+ event_name character varying(1000),
+ event_format character varying(128),
+ event_version character varying(128),
+ event_fingerprint character varying(128),
+ true_tstamp timestamp with time zone
+);
+ALTER TABLE ONLY public.product_analytics_events_experimental ATTACH PARTITION gitlab_partitions_static.product_analytics_events_experimental_14 FOR VALUES WITH (modulus 64, remainder 14);
+
+CREATE TABLE gitlab_partitions_static.product_analytics_events_experimental_15 (
+ id bigint DEFAULT nextval('public.product_analytics_events_experimental_id_seq'::regclass) NOT NULL,
+ project_id integer NOT NULL,
+ platform character varying(255),
+ etl_tstamp timestamp with time zone,
+ collector_tstamp timestamp with time zone NOT NULL,
+ dvce_created_tstamp timestamp with time zone,
+ event character varying(128),
+ event_id character(36) NOT NULL,
+ txn_id integer,
+ name_tracker character varying(128),
+ v_tracker character varying(100),
+ v_collector character varying(100) NOT NULL,
+ v_etl character varying(100) NOT NULL,
+ user_id character varying(255),
+ user_ipaddress character varying(45),
+ user_fingerprint character varying(50),
+ domain_userid character varying(36),
+ domain_sessionidx smallint,
+ network_userid character varying(38),
+ geo_country character(2),
+ geo_region character(3),
+ geo_city character varying(75),
+ geo_zipcode character varying(15),
+ geo_latitude double precision,
+ geo_longitude double precision,
+ geo_region_name character varying(100),
+ ip_isp character varying(100),
+ ip_organization character varying(100),
+ ip_domain character varying(100),
+ ip_netspeed character varying(100),
+ page_url text,
+ page_title character varying(2000),
+ page_referrer text,
+ page_urlscheme character varying(16),
+ page_urlhost character varying(255),
+ page_urlport integer,
+ page_urlpath character varying(3000),
+ page_urlquery character varying(6000),
+ page_urlfragment character varying(3000),
+ refr_urlscheme character varying(16),
+ refr_urlhost character varying(255),
+ refr_urlport integer,
+ refr_urlpath character varying(6000),
+ refr_urlquery character varying(6000),
+ refr_urlfragment character varying(3000),
+ refr_medium character varying(25),
+ refr_source character varying(50),
+ refr_term character varying(255),
+ mkt_medium character varying(255),
+ mkt_source character varying(255),
+ mkt_term character varying(255),
+ mkt_content character varying(500),
+ mkt_campaign character varying(255),
+ se_category character varying(1000),
+ se_action character varying(1000),
+ se_label character varying(1000),
+ se_property character varying(1000),
+ se_value double precision,
+ tr_orderid character varying(255),
+ tr_affiliation character varying(255),
+ tr_total numeric(18,2),
+ tr_tax numeric(18,2),
+ tr_shipping numeric(18,2),
+ tr_city character varying(255),
+ tr_state character varying(255),
+ tr_country character varying(255),
+ ti_orderid character varying(255),
+ ti_sku character varying(255),
+ ti_name character varying(255),
+ ti_category character varying(255),
+ ti_price numeric(18,2),
+ ti_quantity integer,
+ pp_xoffset_min integer,
+ pp_xoffset_max integer,
+ pp_yoffset_min integer,
+ pp_yoffset_max integer,
+ useragent character varying(1000),
+ br_name character varying(50),
+ br_family character varying(50),
+ br_version character varying(50),
+ br_type character varying(50),
+ br_renderengine character varying(50),
+ br_lang character varying(255),
+ br_features_pdf boolean,
+ br_features_flash boolean,
+ br_features_java boolean,
+ br_features_director boolean,
+ br_features_quicktime boolean,
+ br_features_realplayer boolean,
+ br_features_windowsmedia boolean,
+ br_features_gears boolean,
+ br_features_silverlight boolean,
+ br_cookies boolean,
+ br_colordepth character varying(12),
+ br_viewwidth integer,
+ br_viewheight integer,
+ os_name character varying(50),
+ os_family character varying(50),
+ os_manufacturer character varying(50),
+ os_timezone character varying(50),
+ dvce_type character varying(50),
+ dvce_ismobile boolean,
+ dvce_screenwidth integer,
+ dvce_screenheight integer,
+ doc_charset character varying(128),
+ doc_width integer,
+ doc_height integer,
+ tr_currency character(3),
+ tr_total_base numeric(18,2),
+ tr_tax_base numeric(18,2),
+ tr_shipping_base numeric(18,2),
+ ti_currency character(3),
+ ti_price_base numeric(18,2),
+ base_currency character(3),
+ geo_timezone character varying(64),
+ mkt_clickid character varying(128),
+ mkt_network character varying(64),
+ etl_tags character varying(500),
+ dvce_sent_tstamp timestamp with time zone,
+ refr_domain_userid character varying(36),
+ refr_dvce_tstamp timestamp with time zone,
+ domain_sessionid character(36),
+ derived_tstamp timestamp with time zone,
+ event_vendor character varying(1000),
+ event_name character varying(1000),
+ event_format character varying(128),
+ event_version character varying(128),
+ event_fingerprint character varying(128),
+ true_tstamp timestamp with time zone
+);
+ALTER TABLE ONLY public.product_analytics_events_experimental ATTACH PARTITION gitlab_partitions_static.product_analytics_events_experimental_15 FOR VALUES WITH (modulus 64, remainder 15);
+
+CREATE TABLE gitlab_partitions_static.product_analytics_events_experimental_16 (
+ id bigint DEFAULT nextval('public.product_analytics_events_experimental_id_seq'::regclass) NOT NULL,
+ project_id integer NOT NULL,
+ platform character varying(255),
+ etl_tstamp timestamp with time zone,
+ collector_tstamp timestamp with time zone NOT NULL,
+ dvce_created_tstamp timestamp with time zone,
+ event character varying(128),
+ event_id character(36) NOT NULL,
+ txn_id integer,
+ name_tracker character varying(128),
+ v_tracker character varying(100),
+ v_collector character varying(100) NOT NULL,
+ v_etl character varying(100) NOT NULL,
+ user_id character varying(255),
+ user_ipaddress character varying(45),
+ user_fingerprint character varying(50),
+ domain_userid character varying(36),
+ domain_sessionidx smallint,
+ network_userid character varying(38),
+ geo_country character(2),
+ geo_region character(3),
+ geo_city character varying(75),
+ geo_zipcode character varying(15),
+ geo_latitude double precision,
+ geo_longitude double precision,
+ geo_region_name character varying(100),
+ ip_isp character varying(100),
+ ip_organization character varying(100),
+ ip_domain character varying(100),
+ ip_netspeed character varying(100),
+ page_url text,
+ page_title character varying(2000),
+ page_referrer text,
+ page_urlscheme character varying(16),
+ page_urlhost character varying(255),
+ page_urlport integer,
+ page_urlpath character varying(3000),
+ page_urlquery character varying(6000),
+ page_urlfragment character varying(3000),
+ refr_urlscheme character varying(16),
+ refr_urlhost character varying(255),
+ refr_urlport integer,
+ refr_urlpath character varying(6000),
+ refr_urlquery character varying(6000),
+ refr_urlfragment character varying(3000),
+ refr_medium character varying(25),
+ refr_source character varying(50),
+ refr_term character varying(255),
+ mkt_medium character varying(255),
+ mkt_source character varying(255),
+ mkt_term character varying(255),
+ mkt_content character varying(500),
+ mkt_campaign character varying(255),
+ se_category character varying(1000),
+ se_action character varying(1000),
+ se_label character varying(1000),
+ se_property character varying(1000),
+ se_value double precision,
+ tr_orderid character varying(255),
+ tr_affiliation character varying(255),
+ tr_total numeric(18,2),
+ tr_tax numeric(18,2),
+ tr_shipping numeric(18,2),
+ tr_city character varying(255),
+ tr_state character varying(255),
+ tr_country character varying(255),
+ ti_orderid character varying(255),
+ ti_sku character varying(255),
+ ti_name character varying(255),
+ ti_category character varying(255),
+ ti_price numeric(18,2),
+ ti_quantity integer,
+ pp_xoffset_min integer,
+ pp_xoffset_max integer,
+ pp_yoffset_min integer,
+ pp_yoffset_max integer,
+ useragent character varying(1000),
+ br_name character varying(50),
+ br_family character varying(50),
+ br_version character varying(50),
+ br_type character varying(50),
+ br_renderengine character varying(50),
+ br_lang character varying(255),
+ br_features_pdf boolean,
+ br_features_flash boolean,
+ br_features_java boolean,
+ br_features_director boolean,
+ br_features_quicktime boolean,
+ br_features_realplayer boolean,
+ br_features_windowsmedia boolean,
+ br_features_gears boolean,
+ br_features_silverlight boolean,
+ br_cookies boolean,
+ br_colordepth character varying(12),
+ br_viewwidth integer,
+ br_viewheight integer,
+ os_name character varying(50),
+ os_family character varying(50),
+ os_manufacturer character varying(50),
+ os_timezone character varying(50),
+ dvce_type character varying(50),
+ dvce_ismobile boolean,
+ dvce_screenwidth integer,
+ dvce_screenheight integer,
+ doc_charset character varying(128),
+ doc_width integer,
+ doc_height integer,
+ tr_currency character(3),
+ tr_total_base numeric(18,2),
+ tr_tax_base numeric(18,2),
+ tr_shipping_base numeric(18,2),
+ ti_currency character(3),
+ ti_price_base numeric(18,2),
+ base_currency character(3),
+ geo_timezone character varying(64),
+ mkt_clickid character varying(128),
+ mkt_network character varying(64),
+ etl_tags character varying(500),
+ dvce_sent_tstamp timestamp with time zone,
+ refr_domain_userid character varying(36),
+ refr_dvce_tstamp timestamp with time zone,
+ domain_sessionid character(36),
+ derived_tstamp timestamp with time zone,
+ event_vendor character varying(1000),
+ event_name character varying(1000),
+ event_format character varying(128),
+ event_version character varying(128),
+ event_fingerprint character varying(128),
+ true_tstamp timestamp with time zone
+);
+ALTER TABLE ONLY public.product_analytics_events_experimental ATTACH PARTITION gitlab_partitions_static.product_analytics_events_experimental_16 FOR VALUES WITH (modulus 64, remainder 16);
+
+CREATE TABLE gitlab_partitions_static.product_analytics_events_experimental_17 (
+ id bigint DEFAULT nextval('public.product_analytics_events_experimental_id_seq'::regclass) NOT NULL,
+ project_id integer NOT NULL,
+ platform character varying(255),
+ etl_tstamp timestamp with time zone,
+ collector_tstamp timestamp with time zone NOT NULL,
+ dvce_created_tstamp timestamp with time zone,
+ event character varying(128),
+ event_id character(36) NOT NULL,
+ txn_id integer,
+ name_tracker character varying(128),
+ v_tracker character varying(100),
+ v_collector character varying(100) NOT NULL,
+ v_etl character varying(100) NOT NULL,
+ user_id character varying(255),
+ user_ipaddress character varying(45),
+ user_fingerprint character varying(50),
+ domain_userid character varying(36),
+ domain_sessionidx smallint,
+ network_userid character varying(38),
+ geo_country character(2),
+ geo_region character(3),
+ geo_city character varying(75),
+ geo_zipcode character varying(15),
+ geo_latitude double precision,
+ geo_longitude double precision,
+ geo_region_name character varying(100),
+ ip_isp character varying(100),
+ ip_organization character varying(100),
+ ip_domain character varying(100),
+ ip_netspeed character varying(100),
+ page_url text,
+ page_title character varying(2000),
+ page_referrer text,
+ page_urlscheme character varying(16),
+ page_urlhost character varying(255),
+ page_urlport integer,
+ page_urlpath character varying(3000),
+ page_urlquery character varying(6000),
+ page_urlfragment character varying(3000),
+ refr_urlscheme character varying(16),
+ refr_urlhost character varying(255),
+ refr_urlport integer,
+ refr_urlpath character varying(6000),
+ refr_urlquery character varying(6000),
+ refr_urlfragment character varying(3000),
+ refr_medium character varying(25),
+ refr_source character varying(50),
+ refr_term character varying(255),
+ mkt_medium character varying(255),
+ mkt_source character varying(255),
+ mkt_term character varying(255),
+ mkt_content character varying(500),
+ mkt_campaign character varying(255),
+ se_category character varying(1000),
+ se_action character varying(1000),
+ se_label character varying(1000),
+ se_property character varying(1000),
+ se_value double precision,
+ tr_orderid character varying(255),
+ tr_affiliation character varying(255),
+ tr_total numeric(18,2),
+ tr_tax numeric(18,2),
+ tr_shipping numeric(18,2),
+ tr_city character varying(255),
+ tr_state character varying(255),
+ tr_country character varying(255),
+ ti_orderid character varying(255),
+ ti_sku character varying(255),
+ ti_name character varying(255),
+ ti_category character varying(255),
+ ti_price numeric(18,2),
+ ti_quantity integer,
+ pp_xoffset_min integer,
+ pp_xoffset_max integer,
+ pp_yoffset_min integer,
+ pp_yoffset_max integer,
+ useragent character varying(1000),
+ br_name character varying(50),
+ br_family character varying(50),
+ br_version character varying(50),
+ br_type character varying(50),
+ br_renderengine character varying(50),
+ br_lang character varying(255),
+ br_features_pdf boolean,
+ br_features_flash boolean,
+ br_features_java boolean,
+ br_features_director boolean,
+ br_features_quicktime boolean,
+ br_features_realplayer boolean,
+ br_features_windowsmedia boolean,
+ br_features_gears boolean,
+ br_features_silverlight boolean,
+ br_cookies boolean,
+ br_colordepth character varying(12),
+ br_viewwidth integer,
+ br_viewheight integer,
+ os_name character varying(50),
+ os_family character varying(50),
+ os_manufacturer character varying(50),
+ os_timezone character varying(50),
+ dvce_type character varying(50),
+ dvce_ismobile boolean,
+ dvce_screenwidth integer,
+ dvce_screenheight integer,
+ doc_charset character varying(128),
+ doc_width integer,
+ doc_height integer,
+ tr_currency character(3),
+ tr_total_base numeric(18,2),
+ tr_tax_base numeric(18,2),
+ tr_shipping_base numeric(18,2),
+ ti_currency character(3),
+ ti_price_base numeric(18,2),
+ base_currency character(3),
+ geo_timezone character varying(64),
+ mkt_clickid character varying(128),
+ mkt_network character varying(64),
+ etl_tags character varying(500),
+ dvce_sent_tstamp timestamp with time zone,
+ refr_domain_userid character varying(36),
+ refr_dvce_tstamp timestamp with time zone,
+ domain_sessionid character(36),
+ derived_tstamp timestamp with time zone,
+ event_vendor character varying(1000),
+ event_name character varying(1000),
+ event_format character varying(128),
+ event_version character varying(128),
+ event_fingerprint character varying(128),
+ true_tstamp timestamp with time zone
+);
+ALTER TABLE ONLY public.product_analytics_events_experimental ATTACH PARTITION gitlab_partitions_static.product_analytics_events_experimental_17 FOR VALUES WITH (modulus 64, remainder 17);
+
+CREATE TABLE gitlab_partitions_static.product_analytics_events_experimental_18 (
+ id bigint DEFAULT nextval('public.product_analytics_events_experimental_id_seq'::regclass) NOT NULL,
+ project_id integer NOT NULL,
+ platform character varying(255),
+ etl_tstamp timestamp with time zone,
+ collector_tstamp timestamp with time zone NOT NULL,
+ dvce_created_tstamp timestamp with time zone,
+ event character varying(128),
+ event_id character(36) NOT NULL,
+ txn_id integer,
+ name_tracker character varying(128),
+ v_tracker character varying(100),
+ v_collector character varying(100) NOT NULL,
+ v_etl character varying(100) NOT NULL,
+ user_id character varying(255),
+ user_ipaddress character varying(45),
+ user_fingerprint character varying(50),
+ domain_userid character varying(36),
+ domain_sessionidx smallint,
+ network_userid character varying(38),
+ geo_country character(2),
+ geo_region character(3),
+ geo_city character varying(75),
+ geo_zipcode character varying(15),
+ geo_latitude double precision,
+ geo_longitude double precision,
+ geo_region_name character varying(100),
+ ip_isp character varying(100),
+ ip_organization character varying(100),
+ ip_domain character varying(100),
+ ip_netspeed character varying(100),
+ page_url text,
+ page_title character varying(2000),
+ page_referrer text,
+ page_urlscheme character varying(16),
+ page_urlhost character varying(255),
+ page_urlport integer,
+ page_urlpath character varying(3000),
+ page_urlquery character varying(6000),
+ page_urlfragment character varying(3000),
+ refr_urlscheme character varying(16),
+ refr_urlhost character varying(255),
+ refr_urlport integer,
+ refr_urlpath character varying(6000),
+ refr_urlquery character varying(6000),
+ refr_urlfragment character varying(3000),
+ refr_medium character varying(25),
+ refr_source character varying(50),
+ refr_term character varying(255),
+ mkt_medium character varying(255),
+ mkt_source character varying(255),
+ mkt_term character varying(255),
+ mkt_content character varying(500),
+ mkt_campaign character varying(255),
+ se_category character varying(1000),
+ se_action character varying(1000),
+ se_label character varying(1000),
+ se_property character varying(1000),
+ se_value double precision,
+ tr_orderid character varying(255),
+ tr_affiliation character varying(255),
+ tr_total numeric(18,2),
+ tr_tax numeric(18,2),
+ tr_shipping numeric(18,2),
+ tr_city character varying(255),
+ tr_state character varying(255),
+ tr_country character varying(255),
+ ti_orderid character varying(255),
+ ti_sku character varying(255),
+ ti_name character varying(255),
+ ti_category character varying(255),
+ ti_price numeric(18,2),
+ ti_quantity integer,
+ pp_xoffset_min integer,
+ pp_xoffset_max integer,
+ pp_yoffset_min integer,
+ pp_yoffset_max integer,
+ useragent character varying(1000),
+ br_name character varying(50),
+ br_family character varying(50),
+ br_version character varying(50),
+ br_type character varying(50),
+ br_renderengine character varying(50),
+ br_lang character varying(255),
+ br_features_pdf boolean,
+ br_features_flash boolean,
+ br_features_java boolean,
+ br_features_director boolean,
+ br_features_quicktime boolean,
+ br_features_realplayer boolean,
+ br_features_windowsmedia boolean,
+ br_features_gears boolean,
+ br_features_silverlight boolean,
+ br_cookies boolean,
+ br_colordepth character varying(12),
+ br_viewwidth integer,
+ br_viewheight integer,
+ os_name character varying(50),
+ os_family character varying(50),
+ os_manufacturer character varying(50),
+ os_timezone character varying(50),
+ dvce_type character varying(50),
+ dvce_ismobile boolean,
+ dvce_screenwidth integer,
+ dvce_screenheight integer,
+ doc_charset character varying(128),
+ doc_width integer,
+ doc_height integer,
+ tr_currency character(3),
+ tr_total_base numeric(18,2),
+ tr_tax_base numeric(18,2),
+ tr_shipping_base numeric(18,2),
+ ti_currency character(3),
+ ti_price_base numeric(18,2),
+ base_currency character(3),
+ geo_timezone character varying(64),
+ mkt_clickid character varying(128),
+ mkt_network character varying(64),
+ etl_tags character varying(500),
+ dvce_sent_tstamp timestamp with time zone,
+ refr_domain_userid character varying(36),
+ refr_dvce_tstamp timestamp with time zone,
+ domain_sessionid character(36),
+ derived_tstamp timestamp with time zone,
+ event_vendor character varying(1000),
+ event_name character varying(1000),
+ event_format character varying(128),
+ event_version character varying(128),
+ event_fingerprint character varying(128),
+ true_tstamp timestamp with time zone
+);
+ALTER TABLE ONLY public.product_analytics_events_experimental ATTACH PARTITION gitlab_partitions_static.product_analytics_events_experimental_18 FOR VALUES WITH (modulus 64, remainder 18);
+
+CREATE TABLE gitlab_partitions_static.product_analytics_events_experimental_19 (
+ id bigint DEFAULT nextval('public.product_analytics_events_experimental_id_seq'::regclass) NOT NULL,
+ project_id integer NOT NULL,
+ platform character varying(255),
+ etl_tstamp timestamp with time zone,
+ collector_tstamp timestamp with time zone NOT NULL,
+ dvce_created_tstamp timestamp with time zone,
+ event character varying(128),
+ event_id character(36) NOT NULL,
+ txn_id integer,
+ name_tracker character varying(128),
+ v_tracker character varying(100),
+ v_collector character varying(100) NOT NULL,
+ v_etl character varying(100) NOT NULL,
+ user_id character varying(255),
+ user_ipaddress character varying(45),
+ user_fingerprint character varying(50),
+ domain_userid character varying(36),
+ domain_sessionidx smallint,
+ network_userid character varying(38),
+ geo_country character(2),
+ geo_region character(3),
+ geo_city character varying(75),
+ geo_zipcode character varying(15),
+ geo_latitude double precision,
+ geo_longitude double precision,
+ geo_region_name character varying(100),
+ ip_isp character varying(100),
+ ip_organization character varying(100),
+ ip_domain character varying(100),
+ ip_netspeed character varying(100),
+ page_url text,
+ page_title character varying(2000),
+ page_referrer text,
+ page_urlscheme character varying(16),
+ page_urlhost character varying(255),
+ page_urlport integer,
+ page_urlpath character varying(3000),
+ page_urlquery character varying(6000),
+ page_urlfragment character varying(3000),
+ refr_urlscheme character varying(16),
+ refr_urlhost character varying(255),
+ refr_urlport integer,
+ refr_urlpath character varying(6000),
+ refr_urlquery character varying(6000),
+ refr_urlfragment character varying(3000),
+ refr_medium character varying(25),
+ refr_source character varying(50),
+ refr_term character varying(255),
+ mkt_medium character varying(255),
+ mkt_source character varying(255),
+ mkt_term character varying(255),
+ mkt_content character varying(500),
+ mkt_campaign character varying(255),
+ se_category character varying(1000),
+ se_action character varying(1000),
+ se_label character varying(1000),
+ se_property character varying(1000),
+ se_value double precision,
+ tr_orderid character varying(255),
+ tr_affiliation character varying(255),
+ tr_total numeric(18,2),
+ tr_tax numeric(18,2),
+ tr_shipping numeric(18,2),
+ tr_city character varying(255),
+ tr_state character varying(255),
+ tr_country character varying(255),
+ ti_orderid character varying(255),
+ ti_sku character varying(255),
+ ti_name character varying(255),
+ ti_category character varying(255),
+ ti_price numeric(18,2),
+ ti_quantity integer,
+ pp_xoffset_min integer,
+ pp_xoffset_max integer,
+ pp_yoffset_min integer,
+ pp_yoffset_max integer,
+ useragent character varying(1000),
+ br_name character varying(50),
+ br_family character varying(50),
+ br_version character varying(50),
+ br_type character varying(50),
+ br_renderengine character varying(50),
+ br_lang character varying(255),
+ br_features_pdf boolean,
+ br_features_flash boolean,
+ br_features_java boolean,
+ br_features_director boolean,
+ br_features_quicktime boolean,
+ br_features_realplayer boolean,
+ br_features_windowsmedia boolean,
+ br_features_gears boolean,
+ br_features_silverlight boolean,
+ br_cookies boolean,
+ br_colordepth character varying(12),
+ br_viewwidth integer,
+ br_viewheight integer,
+ os_name character varying(50),
+ os_family character varying(50),
+ os_manufacturer character varying(50),
+ os_timezone character varying(50),
+ dvce_type character varying(50),
+ dvce_ismobile boolean,
+ dvce_screenwidth integer,
+ dvce_screenheight integer,
+ doc_charset character varying(128),
+ doc_width integer,
+ doc_height integer,
+ tr_currency character(3),
+ tr_total_base numeric(18,2),
+ tr_tax_base numeric(18,2),
+ tr_shipping_base numeric(18,2),
+ ti_currency character(3),
+ ti_price_base numeric(18,2),
+ base_currency character(3),
+ geo_timezone character varying(64),
+ mkt_clickid character varying(128),
+ mkt_network character varying(64),
+ etl_tags character varying(500),
+ dvce_sent_tstamp timestamp with time zone,
+ refr_domain_userid character varying(36),
+ refr_dvce_tstamp timestamp with time zone,
+ domain_sessionid character(36),
+ derived_tstamp timestamp with time zone,
+ event_vendor character varying(1000),
+ event_name character varying(1000),
+ event_format character varying(128),
+ event_version character varying(128),
+ event_fingerprint character varying(128),
+ true_tstamp timestamp with time zone
+);
+ALTER TABLE ONLY public.product_analytics_events_experimental ATTACH PARTITION gitlab_partitions_static.product_analytics_events_experimental_19 FOR VALUES WITH (modulus 64, remainder 19);
+
+CREATE TABLE gitlab_partitions_static.product_analytics_events_experimental_20 (
+ id bigint DEFAULT nextval('public.product_analytics_events_experimental_id_seq'::regclass) NOT NULL,
+ project_id integer NOT NULL,
+ platform character varying(255),
+ etl_tstamp timestamp with time zone,
+ collector_tstamp timestamp with time zone NOT NULL,
+ dvce_created_tstamp timestamp with time zone,
+ event character varying(128),
+ event_id character(36) NOT NULL,
+ txn_id integer,
+ name_tracker character varying(128),
+ v_tracker character varying(100),
+ v_collector character varying(100) NOT NULL,
+ v_etl character varying(100) NOT NULL,
+ user_id character varying(255),
+ user_ipaddress character varying(45),
+ user_fingerprint character varying(50),
+ domain_userid character varying(36),
+ domain_sessionidx smallint,
+ network_userid character varying(38),
+ geo_country character(2),
+ geo_region character(3),
+ geo_city character varying(75),
+ geo_zipcode character varying(15),
+ geo_latitude double precision,
+ geo_longitude double precision,
+ geo_region_name character varying(100),
+ ip_isp character varying(100),
+ ip_organization character varying(100),
+ ip_domain character varying(100),
+ ip_netspeed character varying(100),
+ page_url text,
+ page_title character varying(2000),
+ page_referrer text,
+ page_urlscheme character varying(16),
+ page_urlhost character varying(255),
+ page_urlport integer,
+ page_urlpath character varying(3000),
+ page_urlquery character varying(6000),
+ page_urlfragment character varying(3000),
+ refr_urlscheme character varying(16),
+ refr_urlhost character varying(255),
+ refr_urlport integer,
+ refr_urlpath character varying(6000),
+ refr_urlquery character varying(6000),
+ refr_urlfragment character varying(3000),
+ refr_medium character varying(25),
+ refr_source character varying(50),
+ refr_term character varying(255),
+ mkt_medium character varying(255),
+ mkt_source character varying(255),
+ mkt_term character varying(255),
+ mkt_content character varying(500),
+ mkt_campaign character varying(255),
+ se_category character varying(1000),
+ se_action character varying(1000),
+ se_label character varying(1000),
+ se_property character varying(1000),
+ se_value double precision,
+ tr_orderid character varying(255),
+ tr_affiliation character varying(255),
+ tr_total numeric(18,2),
+ tr_tax numeric(18,2),
+ tr_shipping numeric(18,2),
+ tr_city character varying(255),
+ tr_state character varying(255),
+ tr_country character varying(255),
+ ti_orderid character varying(255),
+ ti_sku character varying(255),
+ ti_name character varying(255),
+ ti_category character varying(255),
+ ti_price numeric(18,2),
+ ti_quantity integer,
+ pp_xoffset_min integer,
+ pp_xoffset_max integer,
+ pp_yoffset_min integer,
+ pp_yoffset_max integer,
+ useragent character varying(1000),
+ br_name character varying(50),
+ br_family character varying(50),
+ br_version character varying(50),
+ br_type character varying(50),
+ br_renderengine character varying(50),
+ br_lang character varying(255),
+ br_features_pdf boolean,
+ br_features_flash boolean,
+ br_features_java boolean,
+ br_features_director boolean,
+ br_features_quicktime boolean,
+ br_features_realplayer boolean,
+ br_features_windowsmedia boolean,
+ br_features_gears boolean,
+ br_features_silverlight boolean,
+ br_cookies boolean,
+ br_colordepth character varying(12),
+ br_viewwidth integer,
+ br_viewheight integer,
+ os_name character varying(50),
+ os_family character varying(50),
+ os_manufacturer character varying(50),
+ os_timezone character varying(50),
+ dvce_type character varying(50),
+ dvce_ismobile boolean,
+ dvce_screenwidth integer,
+ dvce_screenheight integer,
+ doc_charset character varying(128),
+ doc_width integer,
+ doc_height integer,
+ tr_currency character(3),
+ tr_total_base numeric(18,2),
+ tr_tax_base numeric(18,2),
+ tr_shipping_base numeric(18,2),
+ ti_currency character(3),
+ ti_price_base numeric(18,2),
+ base_currency character(3),
+ geo_timezone character varying(64),
+ mkt_clickid character varying(128),
+ mkt_network character varying(64),
+ etl_tags character varying(500),
+ dvce_sent_tstamp timestamp with time zone,
+ refr_domain_userid character varying(36),
+ refr_dvce_tstamp timestamp with time zone,
+ domain_sessionid character(36),
+ derived_tstamp timestamp with time zone,
+ event_vendor character varying(1000),
+ event_name character varying(1000),
+ event_format character varying(128),
+ event_version character varying(128),
+ event_fingerprint character varying(128),
+ true_tstamp timestamp with time zone
+);
+ALTER TABLE ONLY public.product_analytics_events_experimental ATTACH PARTITION gitlab_partitions_static.product_analytics_events_experimental_20 FOR VALUES WITH (modulus 64, remainder 20);
+
+CREATE TABLE gitlab_partitions_static.product_analytics_events_experimental_21 (
+ id bigint DEFAULT nextval('public.product_analytics_events_experimental_id_seq'::regclass) NOT NULL,
+ project_id integer NOT NULL,
+ platform character varying(255),
+ etl_tstamp timestamp with time zone,
+ collector_tstamp timestamp with time zone NOT NULL,
+ dvce_created_tstamp timestamp with time zone,
+ event character varying(128),
+ event_id character(36) NOT NULL,
+ txn_id integer,
+ name_tracker character varying(128),
+ v_tracker character varying(100),
+ v_collector character varying(100) NOT NULL,
+ v_etl character varying(100) NOT NULL,
+ user_id character varying(255),
+ user_ipaddress character varying(45),
+ user_fingerprint character varying(50),
+ domain_userid character varying(36),
+ domain_sessionidx smallint,
+ network_userid character varying(38),
+ geo_country character(2),
+ geo_region character(3),
+ geo_city character varying(75),
+ geo_zipcode character varying(15),
+ geo_latitude double precision,
+ geo_longitude double precision,
+ geo_region_name character varying(100),
+ ip_isp character varying(100),
+ ip_organization character varying(100),
+ ip_domain character varying(100),
+ ip_netspeed character varying(100),
+ page_url text,
+ page_title character varying(2000),
+ page_referrer text,
+ page_urlscheme character varying(16),
+ page_urlhost character varying(255),
+ page_urlport integer,
+ page_urlpath character varying(3000),
+ page_urlquery character varying(6000),
+ page_urlfragment character varying(3000),
+ refr_urlscheme character varying(16),
+ refr_urlhost character varying(255),
+ refr_urlport integer,
+ refr_urlpath character varying(6000),
+ refr_urlquery character varying(6000),
+ refr_urlfragment character varying(3000),
+ refr_medium character varying(25),
+ refr_source character varying(50),
+ refr_term character varying(255),
+ mkt_medium character varying(255),
+ mkt_source character varying(255),
+ mkt_term character varying(255),
+ mkt_content character varying(500),
+ mkt_campaign character varying(255),
+ se_category character varying(1000),
+ se_action character varying(1000),
+ se_label character varying(1000),
+ se_property character varying(1000),
+ se_value double precision,
+ tr_orderid character varying(255),
+ tr_affiliation character varying(255),
+ tr_total numeric(18,2),
+ tr_tax numeric(18,2),
+ tr_shipping numeric(18,2),
+ tr_city character varying(255),
+ tr_state character varying(255),
+ tr_country character varying(255),
+ ti_orderid character varying(255),
+ ti_sku character varying(255),
+ ti_name character varying(255),
+ ti_category character varying(255),
+ ti_price numeric(18,2),
+ ti_quantity integer,
+ pp_xoffset_min integer,
+ pp_xoffset_max integer,
+ pp_yoffset_min integer,
+ pp_yoffset_max integer,
+ useragent character varying(1000),
+ br_name character varying(50),
+ br_family character varying(50),
+ br_version character varying(50),
+ br_type character varying(50),
+ br_renderengine character varying(50),
+ br_lang character varying(255),
+ br_features_pdf boolean,
+ br_features_flash boolean,
+ br_features_java boolean,
+ br_features_director boolean,
+ br_features_quicktime boolean,
+ br_features_realplayer boolean,
+ br_features_windowsmedia boolean,
+ br_features_gears boolean,
+ br_features_silverlight boolean,
+ br_cookies boolean,
+ br_colordepth character varying(12),
+ br_viewwidth integer,
+ br_viewheight integer,
+ os_name character varying(50),
+ os_family character varying(50),
+ os_manufacturer character varying(50),
+ os_timezone character varying(50),
+ dvce_type character varying(50),
+ dvce_ismobile boolean,
+ dvce_screenwidth integer,
+ dvce_screenheight integer,
+ doc_charset character varying(128),
+ doc_width integer,
+ doc_height integer,
+ tr_currency character(3),
+ tr_total_base numeric(18,2),
+ tr_tax_base numeric(18,2),
+ tr_shipping_base numeric(18,2),
+ ti_currency character(3),
+ ti_price_base numeric(18,2),
+ base_currency character(3),
+ geo_timezone character varying(64),
+ mkt_clickid character varying(128),
+ mkt_network character varying(64),
+ etl_tags character varying(500),
+ dvce_sent_tstamp timestamp with time zone,
+ refr_domain_userid character varying(36),
+ refr_dvce_tstamp timestamp with time zone,
+ domain_sessionid character(36),
+ derived_tstamp timestamp with time zone,
+ event_vendor character varying(1000),
+ event_name character varying(1000),
+ event_format character varying(128),
+ event_version character varying(128),
+ event_fingerprint character varying(128),
+ true_tstamp timestamp with time zone
+);
+ALTER TABLE ONLY public.product_analytics_events_experimental ATTACH PARTITION gitlab_partitions_static.product_analytics_events_experimental_21 FOR VALUES WITH (modulus 64, remainder 21);
+
+CREATE TABLE gitlab_partitions_static.product_analytics_events_experimental_22 (
+ id bigint DEFAULT nextval('public.product_analytics_events_experimental_id_seq'::regclass) NOT NULL,
+ project_id integer NOT NULL,
+ platform character varying(255),
+ etl_tstamp timestamp with time zone,
+ collector_tstamp timestamp with time zone NOT NULL,
+ dvce_created_tstamp timestamp with time zone,
+ event character varying(128),
+ event_id character(36) NOT NULL,
+ txn_id integer,
+ name_tracker character varying(128),
+ v_tracker character varying(100),
+ v_collector character varying(100) NOT NULL,
+ v_etl character varying(100) NOT NULL,
+ user_id character varying(255),
+ user_ipaddress character varying(45),
+ user_fingerprint character varying(50),
+ domain_userid character varying(36),
+ domain_sessionidx smallint,
+ network_userid character varying(38),
+ geo_country character(2),
+ geo_region character(3),
+ geo_city character varying(75),
+ geo_zipcode character varying(15),
+ geo_latitude double precision,
+ geo_longitude double precision,
+ geo_region_name character varying(100),
+ ip_isp character varying(100),
+ ip_organization character varying(100),
+ ip_domain character varying(100),
+ ip_netspeed character varying(100),
+ page_url text,
+ page_title character varying(2000),
+ page_referrer text,
+ page_urlscheme character varying(16),
+ page_urlhost character varying(255),
+ page_urlport integer,
+ page_urlpath character varying(3000),
+ page_urlquery character varying(6000),
+ page_urlfragment character varying(3000),
+ refr_urlscheme character varying(16),
+ refr_urlhost character varying(255),
+ refr_urlport integer,
+ refr_urlpath character varying(6000),
+ refr_urlquery character varying(6000),
+ refr_urlfragment character varying(3000),
+ refr_medium character varying(25),
+ refr_source character varying(50),
+ refr_term character varying(255),
+ mkt_medium character varying(255),
+ mkt_source character varying(255),
+ mkt_term character varying(255),
+ mkt_content character varying(500),
+ mkt_campaign character varying(255),
+ se_category character varying(1000),
+ se_action character varying(1000),
+ se_label character varying(1000),
+ se_property character varying(1000),
+ se_value double precision,
+ tr_orderid character varying(255),
+ tr_affiliation character varying(255),
+ tr_total numeric(18,2),
+ tr_tax numeric(18,2),
+ tr_shipping numeric(18,2),
+ tr_city character varying(255),
+ tr_state character varying(255),
+ tr_country character varying(255),
+ ti_orderid character varying(255),
+ ti_sku character varying(255),
+ ti_name character varying(255),
+ ti_category character varying(255),
+ ti_price numeric(18,2),
+ ti_quantity integer,
+ pp_xoffset_min integer,
+ pp_xoffset_max integer,
+ pp_yoffset_min integer,
+ pp_yoffset_max integer,
+ useragent character varying(1000),
+ br_name character varying(50),
+ br_family character varying(50),
+ br_version character varying(50),
+ br_type character varying(50),
+ br_renderengine character varying(50),
+ br_lang character varying(255),
+ br_features_pdf boolean,
+ br_features_flash boolean,
+ br_features_java boolean,
+ br_features_director boolean,
+ br_features_quicktime boolean,
+ br_features_realplayer boolean,
+ br_features_windowsmedia boolean,
+ br_features_gears boolean,
+ br_features_silverlight boolean,
+ br_cookies boolean,
+ br_colordepth character varying(12),
+ br_viewwidth integer,
+ br_viewheight integer,
+ os_name character varying(50),
+ os_family character varying(50),
+ os_manufacturer character varying(50),
+ os_timezone character varying(50),
+ dvce_type character varying(50),
+ dvce_ismobile boolean,
+ dvce_screenwidth integer,
+ dvce_screenheight integer,
+ doc_charset character varying(128),
+ doc_width integer,
+ doc_height integer,
+ tr_currency character(3),
+ tr_total_base numeric(18,2),
+ tr_tax_base numeric(18,2),
+ tr_shipping_base numeric(18,2),
+ ti_currency character(3),
+ ti_price_base numeric(18,2),
+ base_currency character(3),
+ geo_timezone character varying(64),
+ mkt_clickid character varying(128),
+ mkt_network character varying(64),
+ etl_tags character varying(500),
+ dvce_sent_tstamp timestamp with time zone,
+ refr_domain_userid character varying(36),
+ refr_dvce_tstamp timestamp with time zone,
+ domain_sessionid character(36),
+ derived_tstamp timestamp with time zone,
+ event_vendor character varying(1000),
+ event_name character varying(1000),
+ event_format character varying(128),
+ event_version character varying(128),
+ event_fingerprint character varying(128),
+ true_tstamp timestamp with time zone
+);
+ALTER TABLE ONLY public.product_analytics_events_experimental ATTACH PARTITION gitlab_partitions_static.product_analytics_events_experimental_22 FOR VALUES WITH (modulus 64, remainder 22);
+
+CREATE TABLE gitlab_partitions_static.product_analytics_events_experimental_23 (
+ id bigint DEFAULT nextval('public.product_analytics_events_experimental_id_seq'::regclass) NOT NULL,
+ project_id integer NOT NULL,
+ platform character varying(255),
+ etl_tstamp timestamp with time zone,
+ collector_tstamp timestamp with time zone NOT NULL,
+ dvce_created_tstamp timestamp with time zone,
+ event character varying(128),
+ event_id character(36) NOT NULL,
+ txn_id integer,
+ name_tracker character varying(128),
+ v_tracker character varying(100),
+ v_collector character varying(100) NOT NULL,
+ v_etl character varying(100) NOT NULL,
+ user_id character varying(255),
+ user_ipaddress character varying(45),
+ user_fingerprint character varying(50),
+ domain_userid character varying(36),
+ domain_sessionidx smallint,
+ network_userid character varying(38),
+ geo_country character(2),
+ geo_region character(3),
+ geo_city character varying(75),
+ geo_zipcode character varying(15),
+ geo_latitude double precision,
+ geo_longitude double precision,
+ geo_region_name character varying(100),
+ ip_isp character varying(100),
+ ip_organization character varying(100),
+ ip_domain character varying(100),
+ ip_netspeed character varying(100),
+ page_url text,
+ page_title character varying(2000),
+ page_referrer text,
+ page_urlscheme character varying(16),
+ page_urlhost character varying(255),
+ page_urlport integer,
+ page_urlpath character varying(3000),
+ page_urlquery character varying(6000),
+ page_urlfragment character varying(3000),
+ refr_urlscheme character varying(16),
+ refr_urlhost character varying(255),
+ refr_urlport integer,
+ refr_urlpath character varying(6000),
+ refr_urlquery character varying(6000),
+ refr_urlfragment character varying(3000),
+ refr_medium character varying(25),
+ refr_source character varying(50),
+ refr_term character varying(255),
+ mkt_medium character varying(255),
+ mkt_source character varying(255),
+ mkt_term character varying(255),
+ mkt_content character varying(500),
+ mkt_campaign character varying(255),
+ se_category character varying(1000),
+ se_action character varying(1000),
+ se_label character varying(1000),
+ se_property character varying(1000),
+ se_value double precision,
+ tr_orderid character varying(255),
+ tr_affiliation character varying(255),
+ tr_total numeric(18,2),
+ tr_tax numeric(18,2),
+ tr_shipping numeric(18,2),
+ tr_city character varying(255),
+ tr_state character varying(255),
+ tr_country character varying(255),
+ ti_orderid character varying(255),
+ ti_sku character varying(255),
+ ti_name character varying(255),
+ ti_category character varying(255),
+ ti_price numeric(18,2),
+ ti_quantity integer,
+ pp_xoffset_min integer,
+ pp_xoffset_max integer,
+ pp_yoffset_min integer,
+ pp_yoffset_max integer,
+ useragent character varying(1000),
+ br_name character varying(50),
+ br_family character varying(50),
+ br_version character varying(50),
+ br_type character varying(50),
+ br_renderengine character varying(50),
+ br_lang character varying(255),
+ br_features_pdf boolean,
+ br_features_flash boolean,
+ br_features_java boolean,
+ br_features_director boolean,
+ br_features_quicktime boolean,
+ br_features_realplayer boolean,
+ br_features_windowsmedia boolean,
+ br_features_gears boolean,
+ br_features_silverlight boolean,
+ br_cookies boolean,
+ br_colordepth character varying(12),
+ br_viewwidth integer,
+ br_viewheight integer,
+ os_name character varying(50),
+ os_family character varying(50),
+ os_manufacturer character varying(50),
+ os_timezone character varying(50),
+ dvce_type character varying(50),
+ dvce_ismobile boolean,
+ dvce_screenwidth integer,
+ dvce_screenheight integer,
+ doc_charset character varying(128),
+ doc_width integer,
+ doc_height integer,
+ tr_currency character(3),
+ tr_total_base numeric(18,2),
+ tr_tax_base numeric(18,2),
+ tr_shipping_base numeric(18,2),
+ ti_currency character(3),
+ ti_price_base numeric(18,2),
+ base_currency character(3),
+ geo_timezone character varying(64),
+ mkt_clickid character varying(128),
+ mkt_network character varying(64),
+ etl_tags character varying(500),
+ dvce_sent_tstamp timestamp with time zone,
+ refr_domain_userid character varying(36),
+ refr_dvce_tstamp timestamp with time zone,
+ domain_sessionid character(36),
+ derived_tstamp timestamp with time zone,
+ event_vendor character varying(1000),
+ event_name character varying(1000),
+ event_format character varying(128),
+ event_version character varying(128),
+ event_fingerprint character varying(128),
+ true_tstamp timestamp with time zone
+);
+ALTER TABLE ONLY public.product_analytics_events_experimental ATTACH PARTITION gitlab_partitions_static.product_analytics_events_experimental_23 FOR VALUES WITH (modulus 64, remainder 23);
+
+CREATE TABLE gitlab_partitions_static.product_analytics_events_experimental_24 (
+ id bigint DEFAULT nextval('public.product_analytics_events_experimental_id_seq'::regclass) NOT NULL,
+ project_id integer NOT NULL,
+ platform character varying(255),
+ etl_tstamp timestamp with time zone,
+ collector_tstamp timestamp with time zone NOT NULL,
+ dvce_created_tstamp timestamp with time zone,
+ event character varying(128),
+ event_id character(36) NOT NULL,
+ txn_id integer,
+ name_tracker character varying(128),
+ v_tracker character varying(100),
+ v_collector character varying(100) NOT NULL,
+ v_etl character varying(100) NOT NULL,
+ user_id character varying(255),
+ user_ipaddress character varying(45),
+ user_fingerprint character varying(50),
+ domain_userid character varying(36),
+ domain_sessionidx smallint,
+ network_userid character varying(38),
+ geo_country character(2),
+ geo_region character(3),
+ geo_city character varying(75),
+ geo_zipcode character varying(15),
+ geo_latitude double precision,
+ geo_longitude double precision,
+ geo_region_name character varying(100),
+ ip_isp character varying(100),
+ ip_organization character varying(100),
+ ip_domain character varying(100),
+ ip_netspeed character varying(100),
+ page_url text,
+ page_title character varying(2000),
+ page_referrer text,
+ page_urlscheme character varying(16),
+ page_urlhost character varying(255),
+ page_urlport integer,
+ page_urlpath character varying(3000),
+ page_urlquery character varying(6000),
+ page_urlfragment character varying(3000),
+ refr_urlscheme character varying(16),
+ refr_urlhost character varying(255),
+ refr_urlport integer,
+ refr_urlpath character varying(6000),
+ refr_urlquery character varying(6000),
+ refr_urlfragment character varying(3000),
+ refr_medium character varying(25),
+ refr_source character varying(50),
+ refr_term character varying(255),
+ mkt_medium character varying(255),
+ mkt_source character varying(255),
+ mkt_term character varying(255),
+ mkt_content character varying(500),
+ mkt_campaign character varying(255),
+ se_category character varying(1000),
+ se_action character varying(1000),
+ se_label character varying(1000),
+ se_property character varying(1000),
+ se_value double precision,
+ tr_orderid character varying(255),
+ tr_affiliation character varying(255),
+ tr_total numeric(18,2),
+ tr_tax numeric(18,2),
+ tr_shipping numeric(18,2),
+ tr_city character varying(255),
+ tr_state character varying(255),
+ tr_country character varying(255),
+ ti_orderid character varying(255),
+ ti_sku character varying(255),
+ ti_name character varying(255),
+ ti_category character varying(255),
+ ti_price numeric(18,2),
+ ti_quantity integer,
+ pp_xoffset_min integer,
+ pp_xoffset_max integer,
+ pp_yoffset_min integer,
+ pp_yoffset_max integer,
+ useragent character varying(1000),
+ br_name character varying(50),
+ br_family character varying(50),
+ br_version character varying(50),
+ br_type character varying(50),
+ br_renderengine character varying(50),
+ br_lang character varying(255),
+ br_features_pdf boolean,
+ br_features_flash boolean,
+ br_features_java boolean,
+ br_features_director boolean,
+ br_features_quicktime boolean,
+ br_features_realplayer boolean,
+ br_features_windowsmedia boolean,
+ br_features_gears boolean,
+ br_features_silverlight boolean,
+ br_cookies boolean,
+ br_colordepth character varying(12),
+ br_viewwidth integer,
+ br_viewheight integer,
+ os_name character varying(50),
+ os_family character varying(50),
+ os_manufacturer character varying(50),
+ os_timezone character varying(50),
+ dvce_type character varying(50),
+ dvce_ismobile boolean,
+ dvce_screenwidth integer,
+ dvce_screenheight integer,
+ doc_charset character varying(128),
+ doc_width integer,
+ doc_height integer,
+ tr_currency character(3),
+ tr_total_base numeric(18,2),
+ tr_tax_base numeric(18,2),
+ tr_shipping_base numeric(18,2),
+ ti_currency character(3),
+ ti_price_base numeric(18,2),
+ base_currency character(3),
+ geo_timezone character varying(64),
+ mkt_clickid character varying(128),
+ mkt_network character varying(64),
+ etl_tags character varying(500),
+ dvce_sent_tstamp timestamp with time zone,
+ refr_domain_userid character varying(36),
+ refr_dvce_tstamp timestamp with time zone,
+ domain_sessionid character(36),
+ derived_tstamp timestamp with time zone,
+ event_vendor character varying(1000),
+ event_name character varying(1000),
+ event_format character varying(128),
+ event_version character varying(128),
+ event_fingerprint character varying(128),
+ true_tstamp timestamp with time zone
+);
+ALTER TABLE ONLY public.product_analytics_events_experimental ATTACH PARTITION gitlab_partitions_static.product_analytics_events_experimental_24 FOR VALUES WITH (modulus 64, remainder 24);
+
+CREATE TABLE gitlab_partitions_static.product_analytics_events_experimental_25 (
+ id bigint DEFAULT nextval('public.product_analytics_events_experimental_id_seq'::regclass) NOT NULL,
+ project_id integer NOT NULL,
+ platform character varying(255),
+ etl_tstamp timestamp with time zone,
+ collector_tstamp timestamp with time zone NOT NULL,
+ dvce_created_tstamp timestamp with time zone,
+ event character varying(128),
+ event_id character(36) NOT NULL,
+ txn_id integer,
+ name_tracker character varying(128),
+ v_tracker character varying(100),
+ v_collector character varying(100) NOT NULL,
+ v_etl character varying(100) NOT NULL,
+ user_id character varying(255),
+ user_ipaddress character varying(45),
+ user_fingerprint character varying(50),
+ domain_userid character varying(36),
+ domain_sessionidx smallint,
+ network_userid character varying(38),
+ geo_country character(2),
+ geo_region character(3),
+ geo_city character varying(75),
+ geo_zipcode character varying(15),
+ geo_latitude double precision,
+ geo_longitude double precision,
+ geo_region_name character varying(100),
+ ip_isp character varying(100),
+ ip_organization character varying(100),
+ ip_domain character varying(100),
+ ip_netspeed character varying(100),
+ page_url text,
+ page_title character varying(2000),
+ page_referrer text,
+ page_urlscheme character varying(16),
+ page_urlhost character varying(255),
+ page_urlport integer,
+ page_urlpath character varying(3000),
+ page_urlquery character varying(6000),
+ page_urlfragment character varying(3000),
+ refr_urlscheme character varying(16),
+ refr_urlhost character varying(255),
+ refr_urlport integer,
+ refr_urlpath character varying(6000),
+ refr_urlquery character varying(6000),
+ refr_urlfragment character varying(3000),
+ refr_medium character varying(25),
+ refr_source character varying(50),
+ refr_term character varying(255),
+ mkt_medium character varying(255),
+ mkt_source character varying(255),
+ mkt_term character varying(255),
+ mkt_content character varying(500),
+ mkt_campaign character varying(255),
+ se_category character varying(1000),
+ se_action character varying(1000),
+ se_label character varying(1000),
+ se_property character varying(1000),
+ se_value double precision,
+ tr_orderid character varying(255),
+ tr_affiliation character varying(255),
+ tr_total numeric(18,2),
+ tr_tax numeric(18,2),
+ tr_shipping numeric(18,2),
+ tr_city character varying(255),
+ tr_state character varying(255),
+ tr_country character varying(255),
+ ti_orderid character varying(255),
+ ti_sku character varying(255),
+ ti_name character varying(255),
+ ti_category character varying(255),
+ ti_price numeric(18,2),
+ ti_quantity integer,
+ pp_xoffset_min integer,
+ pp_xoffset_max integer,
+ pp_yoffset_min integer,
+ pp_yoffset_max integer,
+ useragent character varying(1000),
+ br_name character varying(50),
+ br_family character varying(50),
+ br_version character varying(50),
+ br_type character varying(50),
+ br_renderengine character varying(50),
+ br_lang character varying(255),
+ br_features_pdf boolean,
+ br_features_flash boolean,
+ br_features_java boolean,
+ br_features_director boolean,
+ br_features_quicktime boolean,
+ br_features_realplayer boolean,
+ br_features_windowsmedia boolean,
+ br_features_gears boolean,
+ br_features_silverlight boolean,
+ br_cookies boolean,
+ br_colordepth character varying(12),
+ br_viewwidth integer,
+ br_viewheight integer,
+ os_name character varying(50),
+ os_family character varying(50),
+ os_manufacturer character varying(50),
+ os_timezone character varying(50),
+ dvce_type character varying(50),
+ dvce_ismobile boolean,
+ dvce_screenwidth integer,
+ dvce_screenheight integer,
+ doc_charset character varying(128),
+ doc_width integer,
+ doc_height integer,
+ tr_currency character(3),
+ tr_total_base numeric(18,2),
+ tr_tax_base numeric(18,2),
+ tr_shipping_base numeric(18,2),
+ ti_currency character(3),
+ ti_price_base numeric(18,2),
+ base_currency character(3),
+ geo_timezone character varying(64),
+ mkt_clickid character varying(128),
+ mkt_network character varying(64),
+ etl_tags character varying(500),
+ dvce_sent_tstamp timestamp with time zone,
+ refr_domain_userid character varying(36),
+ refr_dvce_tstamp timestamp with time zone,
+ domain_sessionid character(36),
+ derived_tstamp timestamp with time zone,
+ event_vendor character varying(1000),
+ event_name character varying(1000),
+ event_format character varying(128),
+ event_version character varying(128),
+ event_fingerprint character varying(128),
+ true_tstamp timestamp with time zone
+);
+ALTER TABLE ONLY public.product_analytics_events_experimental ATTACH PARTITION gitlab_partitions_static.product_analytics_events_experimental_25 FOR VALUES WITH (modulus 64, remainder 25);
+
+CREATE TABLE gitlab_partitions_static.product_analytics_events_experimental_26 (
+ id bigint DEFAULT nextval('public.product_analytics_events_experimental_id_seq'::regclass) NOT NULL,
+ project_id integer NOT NULL,
+ platform character varying(255),
+ etl_tstamp timestamp with time zone,
+ collector_tstamp timestamp with time zone NOT NULL,
+ dvce_created_tstamp timestamp with time zone,
+ event character varying(128),
+ event_id character(36) NOT NULL,
+ txn_id integer,
+ name_tracker character varying(128),
+ v_tracker character varying(100),
+ v_collector character varying(100) NOT NULL,
+ v_etl character varying(100) NOT NULL,
+ user_id character varying(255),
+ user_ipaddress character varying(45),
+ user_fingerprint character varying(50),
+ domain_userid character varying(36),
+ domain_sessionidx smallint,
+ network_userid character varying(38),
+ geo_country character(2),
+ geo_region character(3),
+ geo_city character varying(75),
+ geo_zipcode character varying(15),
+ geo_latitude double precision,
+ geo_longitude double precision,
+ geo_region_name character varying(100),
+ ip_isp character varying(100),
+ ip_organization character varying(100),
+ ip_domain character varying(100),
+ ip_netspeed character varying(100),
+ page_url text,
+ page_title character varying(2000),
+ page_referrer text,
+ page_urlscheme character varying(16),
+ page_urlhost character varying(255),
+ page_urlport integer,
+ page_urlpath character varying(3000),
+ page_urlquery character varying(6000),
+ page_urlfragment character varying(3000),
+ refr_urlscheme character varying(16),
+ refr_urlhost character varying(255),
+ refr_urlport integer,
+ refr_urlpath character varying(6000),
+ refr_urlquery character varying(6000),
+ refr_urlfragment character varying(3000),
+ refr_medium character varying(25),
+ refr_source character varying(50),
+ refr_term character varying(255),
+ mkt_medium character varying(255),
+ mkt_source character varying(255),
+ mkt_term character varying(255),
+ mkt_content character varying(500),
+ mkt_campaign character varying(255),
+ se_category character varying(1000),
+ se_action character varying(1000),
+ se_label character varying(1000),
+ se_property character varying(1000),
+ se_value double precision,
+ tr_orderid character varying(255),
+ tr_affiliation character varying(255),
+ tr_total numeric(18,2),
+ tr_tax numeric(18,2),
+ tr_shipping numeric(18,2),
+ tr_city character varying(255),
+ tr_state character varying(255),
+ tr_country character varying(255),
+ ti_orderid character varying(255),
+ ti_sku character varying(255),
+ ti_name character varying(255),
+ ti_category character varying(255),
+ ti_price numeric(18,2),
+ ti_quantity integer,
+ pp_xoffset_min integer,
+ pp_xoffset_max integer,
+ pp_yoffset_min integer,
+ pp_yoffset_max integer,
+ useragent character varying(1000),
+ br_name character varying(50),
+ br_family character varying(50),
+ br_version character varying(50),
+ br_type character varying(50),
+ br_renderengine character varying(50),
+ br_lang character varying(255),
+ br_features_pdf boolean,
+ br_features_flash boolean,
+ br_features_java boolean,
+ br_features_director boolean,
+ br_features_quicktime boolean,
+ br_features_realplayer boolean,
+ br_features_windowsmedia boolean,
+ br_features_gears boolean,
+ br_features_silverlight boolean,
+ br_cookies boolean,
+ br_colordepth character varying(12),
+ br_viewwidth integer,
+ br_viewheight integer,
+ os_name character varying(50),
+ os_family character varying(50),
+ os_manufacturer character varying(50),
+ os_timezone character varying(50),
+ dvce_type character varying(50),
+ dvce_ismobile boolean,
+ dvce_screenwidth integer,
+ dvce_screenheight integer,
+ doc_charset character varying(128),
+ doc_width integer,
+ doc_height integer,
+ tr_currency character(3),
+ tr_total_base numeric(18,2),
+ tr_tax_base numeric(18,2),
+ tr_shipping_base numeric(18,2),
+ ti_currency character(3),
+ ti_price_base numeric(18,2),
+ base_currency character(3),
+ geo_timezone character varying(64),
+ mkt_clickid character varying(128),
+ mkt_network character varying(64),
+ etl_tags character varying(500),
+ dvce_sent_tstamp timestamp with time zone,
+ refr_domain_userid character varying(36),
+ refr_dvce_tstamp timestamp with time zone,
+ domain_sessionid character(36),
+ derived_tstamp timestamp with time zone,
+ event_vendor character varying(1000),
+ event_name character varying(1000),
+ event_format character varying(128),
+ event_version character varying(128),
+ event_fingerprint character varying(128),
+ true_tstamp timestamp with time zone
+);
+ALTER TABLE ONLY public.product_analytics_events_experimental ATTACH PARTITION gitlab_partitions_static.product_analytics_events_experimental_26 FOR VALUES WITH (modulus 64, remainder 26);
+
+CREATE TABLE gitlab_partitions_static.product_analytics_events_experimental_27 (
+ id bigint DEFAULT nextval('public.product_analytics_events_experimental_id_seq'::regclass) NOT NULL,
+ project_id integer NOT NULL,
+ platform character varying(255),
+ etl_tstamp timestamp with time zone,
+ collector_tstamp timestamp with time zone NOT NULL,
+ dvce_created_tstamp timestamp with time zone,
+ event character varying(128),
+ event_id character(36) NOT NULL,
+ txn_id integer,
+ name_tracker character varying(128),
+ v_tracker character varying(100),
+ v_collector character varying(100) NOT NULL,
+ v_etl character varying(100) NOT NULL,
+ user_id character varying(255),
+ user_ipaddress character varying(45),
+ user_fingerprint character varying(50),
+ domain_userid character varying(36),
+ domain_sessionidx smallint,
+ network_userid character varying(38),
+ geo_country character(2),
+ geo_region character(3),
+ geo_city character varying(75),
+ geo_zipcode character varying(15),
+ geo_latitude double precision,
+ geo_longitude double precision,
+ geo_region_name character varying(100),
+ ip_isp character varying(100),
+ ip_organization character varying(100),
+ ip_domain character varying(100),
+ ip_netspeed character varying(100),
+ page_url text,
+ page_title character varying(2000),
+ page_referrer text,
+ page_urlscheme character varying(16),
+ page_urlhost character varying(255),
+ page_urlport integer,
+ page_urlpath character varying(3000),
+ page_urlquery character varying(6000),
+ page_urlfragment character varying(3000),
+ refr_urlscheme character varying(16),
+ refr_urlhost character varying(255),
+ refr_urlport integer,
+ refr_urlpath character varying(6000),
+ refr_urlquery character varying(6000),
+ refr_urlfragment character varying(3000),
+ refr_medium character varying(25),
+ refr_source character varying(50),
+ refr_term character varying(255),
+ mkt_medium character varying(255),
+ mkt_source character varying(255),
+ mkt_term character varying(255),
+ mkt_content character varying(500),
+ mkt_campaign character varying(255),
+ se_category character varying(1000),
+ se_action character varying(1000),
+ se_label character varying(1000),
+ se_property character varying(1000),
+ se_value double precision,
+ tr_orderid character varying(255),
+ tr_affiliation character varying(255),
+ tr_total numeric(18,2),
+ tr_tax numeric(18,2),
+ tr_shipping numeric(18,2),
+ tr_city character varying(255),
+ tr_state character varying(255),
+ tr_country character varying(255),
+ ti_orderid character varying(255),
+ ti_sku character varying(255),
+ ti_name character varying(255),
+ ti_category character varying(255),
+ ti_price numeric(18,2),
+ ti_quantity integer,
+ pp_xoffset_min integer,
+ pp_xoffset_max integer,
+ pp_yoffset_min integer,
+ pp_yoffset_max integer,
+ useragent character varying(1000),
+ br_name character varying(50),
+ br_family character varying(50),
+ br_version character varying(50),
+ br_type character varying(50),
+ br_renderengine character varying(50),
+ br_lang character varying(255),
+ br_features_pdf boolean,
+ br_features_flash boolean,
+ br_features_java boolean,
+ br_features_director boolean,
+ br_features_quicktime boolean,
+ br_features_realplayer boolean,
+ br_features_windowsmedia boolean,
+ br_features_gears boolean,
+ br_features_silverlight boolean,
+ br_cookies boolean,
+ br_colordepth character varying(12),
+ br_viewwidth integer,
+ br_viewheight integer,
+ os_name character varying(50),
+ os_family character varying(50),
+ os_manufacturer character varying(50),
+ os_timezone character varying(50),
+ dvce_type character varying(50),
+ dvce_ismobile boolean,
+ dvce_screenwidth integer,
+ dvce_screenheight integer,
+ doc_charset character varying(128),
+ doc_width integer,
+ doc_height integer,
+ tr_currency character(3),
+ tr_total_base numeric(18,2),
+ tr_tax_base numeric(18,2),
+ tr_shipping_base numeric(18,2),
+ ti_currency character(3),
+ ti_price_base numeric(18,2),
+ base_currency character(3),
+ geo_timezone character varying(64),
+ mkt_clickid character varying(128),
+ mkt_network character varying(64),
+ etl_tags character varying(500),
+ dvce_sent_tstamp timestamp with time zone,
+ refr_domain_userid character varying(36),
+ refr_dvce_tstamp timestamp with time zone,
+ domain_sessionid character(36),
+ derived_tstamp timestamp with time zone,
+ event_vendor character varying(1000),
+ event_name character varying(1000),
+ event_format character varying(128),
+ event_version character varying(128),
+ event_fingerprint character varying(128),
+ true_tstamp timestamp with time zone
+);
+ALTER TABLE ONLY public.product_analytics_events_experimental ATTACH PARTITION gitlab_partitions_static.product_analytics_events_experimental_27 FOR VALUES WITH (modulus 64, remainder 27);
+
+CREATE TABLE gitlab_partitions_static.product_analytics_events_experimental_28 (
+ id bigint DEFAULT nextval('public.product_analytics_events_experimental_id_seq'::regclass) NOT NULL,
+ project_id integer NOT NULL,
+ platform character varying(255),
+ etl_tstamp timestamp with time zone,
+ collector_tstamp timestamp with time zone NOT NULL,
+ dvce_created_tstamp timestamp with time zone,
+ event character varying(128),
+ event_id character(36) NOT NULL,
+ txn_id integer,
+ name_tracker character varying(128),
+ v_tracker character varying(100),
+ v_collector character varying(100) NOT NULL,
+ v_etl character varying(100) NOT NULL,
+ user_id character varying(255),
+ user_ipaddress character varying(45),
+ user_fingerprint character varying(50),
+ domain_userid character varying(36),
+ domain_sessionidx smallint,
+ network_userid character varying(38),
+ geo_country character(2),
+ geo_region character(3),
+ geo_city character varying(75),
+ geo_zipcode character varying(15),
+ geo_latitude double precision,
+ geo_longitude double precision,
+ geo_region_name character varying(100),
+ ip_isp character varying(100),
+ ip_organization character varying(100),
+ ip_domain character varying(100),
+ ip_netspeed character varying(100),
+ page_url text,
+ page_title character varying(2000),
+ page_referrer text,
+ page_urlscheme character varying(16),
+ page_urlhost character varying(255),
+ page_urlport integer,
+ page_urlpath character varying(3000),
+ page_urlquery character varying(6000),
+ page_urlfragment character varying(3000),
+ refr_urlscheme character varying(16),
+ refr_urlhost character varying(255),
+ refr_urlport integer,
+ refr_urlpath character varying(6000),
+ refr_urlquery character varying(6000),
+ refr_urlfragment character varying(3000),
+ refr_medium character varying(25),
+ refr_source character varying(50),
+ refr_term character varying(255),
+ mkt_medium character varying(255),
+ mkt_source character varying(255),
+ mkt_term character varying(255),
+ mkt_content character varying(500),
+ mkt_campaign character varying(255),
+ se_category character varying(1000),
+ se_action character varying(1000),
+ se_label character varying(1000),
+ se_property character varying(1000),
+ se_value double precision,
+ tr_orderid character varying(255),
+ tr_affiliation character varying(255),
+ tr_total numeric(18,2),
+ tr_tax numeric(18,2),
+ tr_shipping numeric(18,2),
+ tr_city character varying(255),
+ tr_state character varying(255),
+ tr_country character varying(255),
+ ti_orderid character varying(255),
+ ti_sku character varying(255),
+ ti_name character varying(255),
+ ti_category character varying(255),
+ ti_price numeric(18,2),
+ ti_quantity integer,
+ pp_xoffset_min integer,
+ pp_xoffset_max integer,
+ pp_yoffset_min integer,
+ pp_yoffset_max integer,
+ useragent character varying(1000),
+ br_name character varying(50),
+ br_family character varying(50),
+ br_version character varying(50),
+ br_type character varying(50),
+ br_renderengine character varying(50),
+ br_lang character varying(255),
+ br_features_pdf boolean,
+ br_features_flash boolean,
+ br_features_java boolean,
+ br_features_director boolean,
+ br_features_quicktime boolean,
+ br_features_realplayer boolean,
+ br_features_windowsmedia boolean,
+ br_features_gears boolean,
+ br_features_silverlight boolean,
+ br_cookies boolean,
+ br_colordepth character varying(12),
+ br_viewwidth integer,
+ br_viewheight integer,
+ os_name character varying(50),
+ os_family character varying(50),
+ os_manufacturer character varying(50),
+ os_timezone character varying(50),
+ dvce_type character varying(50),
+ dvce_ismobile boolean,
+ dvce_screenwidth integer,
+ dvce_screenheight integer,
+ doc_charset character varying(128),
+ doc_width integer,
+ doc_height integer,
+ tr_currency character(3),
+ tr_total_base numeric(18,2),
+ tr_tax_base numeric(18,2),
+ tr_shipping_base numeric(18,2),
+ ti_currency character(3),
+ ti_price_base numeric(18,2),
+ base_currency character(3),
+ geo_timezone character varying(64),
+ mkt_clickid character varying(128),
+ mkt_network character varying(64),
+ etl_tags character varying(500),
+ dvce_sent_tstamp timestamp with time zone,
+ refr_domain_userid character varying(36),
+ refr_dvce_tstamp timestamp with time zone,
+ domain_sessionid character(36),
+ derived_tstamp timestamp with time zone,
+ event_vendor character varying(1000),
+ event_name character varying(1000),
+ event_format character varying(128),
+ event_version character varying(128),
+ event_fingerprint character varying(128),
+ true_tstamp timestamp with time zone
+);
+ALTER TABLE ONLY public.product_analytics_events_experimental ATTACH PARTITION gitlab_partitions_static.product_analytics_events_experimental_28 FOR VALUES WITH (modulus 64, remainder 28);
+
+CREATE TABLE gitlab_partitions_static.product_analytics_events_experimental_29 (
+ id bigint DEFAULT nextval('public.product_analytics_events_experimental_id_seq'::regclass) NOT NULL,
+ project_id integer NOT NULL,
+ platform character varying(255),
+ etl_tstamp timestamp with time zone,
+ collector_tstamp timestamp with time zone NOT NULL,
+ dvce_created_tstamp timestamp with time zone,
+ event character varying(128),
+ event_id character(36) NOT NULL,
+ txn_id integer,
+ name_tracker character varying(128),
+ v_tracker character varying(100),
+ v_collector character varying(100) NOT NULL,
+ v_etl character varying(100) NOT NULL,
+ user_id character varying(255),
+ user_ipaddress character varying(45),
+ user_fingerprint character varying(50),
+ domain_userid character varying(36),
+ domain_sessionidx smallint,
+ network_userid character varying(38),
+ geo_country character(2),
+ geo_region character(3),
+ geo_city character varying(75),
+ geo_zipcode character varying(15),
+ geo_latitude double precision,
+ geo_longitude double precision,
+ geo_region_name character varying(100),
+ ip_isp character varying(100),
+ ip_organization character varying(100),
+ ip_domain character varying(100),
+ ip_netspeed character varying(100),
+ page_url text,
+ page_title character varying(2000),
+ page_referrer text,
+ page_urlscheme character varying(16),
+ page_urlhost character varying(255),
+ page_urlport integer,
+ page_urlpath character varying(3000),
+ page_urlquery character varying(6000),
+ page_urlfragment character varying(3000),
+ refr_urlscheme character varying(16),
+ refr_urlhost character varying(255),
+ refr_urlport integer,
+ refr_urlpath character varying(6000),
+ refr_urlquery character varying(6000),
+ refr_urlfragment character varying(3000),
+ refr_medium character varying(25),
+ refr_source character varying(50),
+ refr_term character varying(255),
+ mkt_medium character varying(255),
+ mkt_source character varying(255),
+ mkt_term character varying(255),
+ mkt_content character varying(500),
+ mkt_campaign character varying(255),
+ se_category character varying(1000),
+ se_action character varying(1000),
+ se_label character varying(1000),
+ se_property character varying(1000),
+ se_value double precision,
+ tr_orderid character varying(255),
+ tr_affiliation character varying(255),
+ tr_total numeric(18,2),
+ tr_tax numeric(18,2),
+ tr_shipping numeric(18,2),
+ tr_city character varying(255),
+ tr_state character varying(255),
+ tr_country character varying(255),
+ ti_orderid character varying(255),
+ ti_sku character varying(255),
+ ti_name character varying(255),
+ ti_category character varying(255),
+ ti_price numeric(18,2),
+ ti_quantity integer,
+ pp_xoffset_min integer,
+ pp_xoffset_max integer,
+ pp_yoffset_min integer,
+ pp_yoffset_max integer,
+ useragent character varying(1000),
+ br_name character varying(50),
+ br_family character varying(50),
+ br_version character varying(50),
+ br_type character varying(50),
+ br_renderengine character varying(50),
+ br_lang character varying(255),
+ br_features_pdf boolean,
+ br_features_flash boolean,
+ br_features_java boolean,
+ br_features_director boolean,
+ br_features_quicktime boolean,
+ br_features_realplayer boolean,
+ br_features_windowsmedia boolean,
+ br_features_gears boolean,
+ br_features_silverlight boolean,
+ br_cookies boolean,
+ br_colordepth character varying(12),
+ br_viewwidth integer,
+ br_viewheight integer,
+ os_name character varying(50),
+ os_family character varying(50),
+ os_manufacturer character varying(50),
+ os_timezone character varying(50),
+ dvce_type character varying(50),
+ dvce_ismobile boolean,
+ dvce_screenwidth integer,
+ dvce_screenheight integer,
+ doc_charset character varying(128),
+ doc_width integer,
+ doc_height integer,
+ tr_currency character(3),
+ tr_total_base numeric(18,2),
+ tr_tax_base numeric(18,2),
+ tr_shipping_base numeric(18,2),
+ ti_currency character(3),
+ ti_price_base numeric(18,2),
+ base_currency character(3),
+ geo_timezone character varying(64),
+ mkt_clickid character varying(128),
+ mkt_network character varying(64),
+ etl_tags character varying(500),
+ dvce_sent_tstamp timestamp with time zone,
+ refr_domain_userid character varying(36),
+ refr_dvce_tstamp timestamp with time zone,
+ domain_sessionid character(36),
+ derived_tstamp timestamp with time zone,
+ event_vendor character varying(1000),
+ event_name character varying(1000),
+ event_format character varying(128),
+ event_version character varying(128),
+ event_fingerprint character varying(128),
+ true_tstamp timestamp with time zone
+);
+ALTER TABLE ONLY public.product_analytics_events_experimental ATTACH PARTITION gitlab_partitions_static.product_analytics_events_experimental_29 FOR VALUES WITH (modulus 64, remainder 29);
+
+CREATE TABLE gitlab_partitions_static.product_analytics_events_experimental_30 (
+ id bigint DEFAULT nextval('public.product_analytics_events_experimental_id_seq'::regclass) NOT NULL,
+ project_id integer NOT NULL,
+ platform character varying(255),
+ etl_tstamp timestamp with time zone,
+ collector_tstamp timestamp with time zone NOT NULL,
+ dvce_created_tstamp timestamp with time zone,
+ event character varying(128),
+ event_id character(36) NOT NULL,
+ txn_id integer,
+ name_tracker character varying(128),
+ v_tracker character varying(100),
+ v_collector character varying(100) NOT NULL,
+ v_etl character varying(100) NOT NULL,
+ user_id character varying(255),
+ user_ipaddress character varying(45),
+ user_fingerprint character varying(50),
+ domain_userid character varying(36),
+ domain_sessionidx smallint,
+ network_userid character varying(38),
+ geo_country character(2),
+ geo_region character(3),
+ geo_city character varying(75),
+ geo_zipcode character varying(15),
+ geo_latitude double precision,
+ geo_longitude double precision,
+ geo_region_name character varying(100),
+ ip_isp character varying(100),
+ ip_organization character varying(100),
+ ip_domain character varying(100),
+ ip_netspeed character varying(100),
+ page_url text,
+ page_title character varying(2000),
+ page_referrer text,
+ page_urlscheme character varying(16),
+ page_urlhost character varying(255),
+ page_urlport integer,
+ page_urlpath character varying(3000),
+ page_urlquery character varying(6000),
+ page_urlfragment character varying(3000),
+ refr_urlscheme character varying(16),
+ refr_urlhost character varying(255),
+ refr_urlport integer,
+ refr_urlpath character varying(6000),
+ refr_urlquery character varying(6000),
+ refr_urlfragment character varying(3000),
+ refr_medium character varying(25),
+ refr_source character varying(50),
+ refr_term character varying(255),
+ mkt_medium character varying(255),
+ mkt_source character varying(255),
+ mkt_term character varying(255),
+ mkt_content character varying(500),
+ mkt_campaign character varying(255),
+ se_category character varying(1000),
+ se_action character varying(1000),
+ se_label character varying(1000),
+ se_property character varying(1000),
+ se_value double precision,
+ tr_orderid character varying(255),
+ tr_affiliation character varying(255),
+ tr_total numeric(18,2),
+ tr_tax numeric(18,2),
+ tr_shipping numeric(18,2),
+ tr_city character varying(255),
+ tr_state character varying(255),
+ tr_country character varying(255),
+ ti_orderid character varying(255),
+ ti_sku character varying(255),
+ ti_name character varying(255),
+ ti_category character varying(255),
+ ti_price numeric(18,2),
+ ti_quantity integer,
+ pp_xoffset_min integer,
+ pp_xoffset_max integer,
+ pp_yoffset_min integer,
+ pp_yoffset_max integer,
+ useragent character varying(1000),
+ br_name character varying(50),
+ br_family character varying(50),
+ br_version character varying(50),
+ br_type character varying(50),
+ br_renderengine character varying(50),
+ br_lang character varying(255),
+ br_features_pdf boolean,
+ br_features_flash boolean,
+ br_features_java boolean,
+ br_features_director boolean,
+ br_features_quicktime boolean,
+ br_features_realplayer boolean,
+ br_features_windowsmedia boolean,
+ br_features_gears boolean,
+ br_features_silverlight boolean,
+ br_cookies boolean,
+ br_colordepth character varying(12),
+ br_viewwidth integer,
+ br_viewheight integer,
+ os_name character varying(50),
+ os_family character varying(50),
+ os_manufacturer character varying(50),
+ os_timezone character varying(50),
+ dvce_type character varying(50),
+ dvce_ismobile boolean,
+ dvce_screenwidth integer,
+ dvce_screenheight integer,
+ doc_charset character varying(128),
+ doc_width integer,
+ doc_height integer,
+ tr_currency character(3),
+ tr_total_base numeric(18,2),
+ tr_tax_base numeric(18,2),
+ tr_shipping_base numeric(18,2),
+ ti_currency character(3),
+ ti_price_base numeric(18,2),
+ base_currency character(3),
+ geo_timezone character varying(64),
+ mkt_clickid character varying(128),
+ mkt_network character varying(64),
+ etl_tags character varying(500),
+ dvce_sent_tstamp timestamp with time zone,
+ refr_domain_userid character varying(36),
+ refr_dvce_tstamp timestamp with time zone,
+ domain_sessionid character(36),
+ derived_tstamp timestamp with time zone,
+ event_vendor character varying(1000),
+ event_name character varying(1000),
+ event_format character varying(128),
+ event_version character varying(128),
+ event_fingerprint character varying(128),
+ true_tstamp timestamp with time zone
+);
+ALTER TABLE ONLY public.product_analytics_events_experimental ATTACH PARTITION gitlab_partitions_static.product_analytics_events_experimental_30 FOR VALUES WITH (modulus 64, remainder 30);
+
+CREATE TABLE gitlab_partitions_static.product_analytics_events_experimental_31 (
+ id bigint DEFAULT nextval('public.product_analytics_events_experimental_id_seq'::regclass) NOT NULL,
+ project_id integer NOT NULL,
+ platform character varying(255),
+ etl_tstamp timestamp with time zone,
+ collector_tstamp timestamp with time zone NOT NULL,
+ dvce_created_tstamp timestamp with time zone,
+ event character varying(128),
+ event_id character(36) NOT NULL,
+ txn_id integer,
+ name_tracker character varying(128),
+ v_tracker character varying(100),
+ v_collector character varying(100) NOT NULL,
+ v_etl character varying(100) NOT NULL,
+ user_id character varying(255),
+ user_ipaddress character varying(45),
+ user_fingerprint character varying(50),
+ domain_userid character varying(36),
+ domain_sessionidx smallint,
+ network_userid character varying(38),
+ geo_country character(2),
+ geo_region character(3),
+ geo_city character varying(75),
+ geo_zipcode character varying(15),
+ geo_latitude double precision,
+ geo_longitude double precision,
+ geo_region_name character varying(100),
+ ip_isp character varying(100),
+ ip_organization character varying(100),
+ ip_domain character varying(100),
+ ip_netspeed character varying(100),
+ page_url text,
+ page_title character varying(2000),
+ page_referrer text,
+ page_urlscheme character varying(16),
+ page_urlhost character varying(255),
+ page_urlport integer,
+ page_urlpath character varying(3000),
+ page_urlquery character varying(6000),
+ page_urlfragment character varying(3000),
+ refr_urlscheme character varying(16),
+ refr_urlhost character varying(255),
+ refr_urlport integer,
+ refr_urlpath character varying(6000),
+ refr_urlquery character varying(6000),
+ refr_urlfragment character varying(3000),
+ refr_medium character varying(25),
+ refr_source character varying(50),
+ refr_term character varying(255),
+ mkt_medium character varying(255),
+ mkt_source character varying(255),
+ mkt_term character varying(255),
+ mkt_content character varying(500),
+ mkt_campaign character varying(255),
+ se_category character varying(1000),
+ se_action character varying(1000),
+ se_label character varying(1000),
+ se_property character varying(1000),
+ se_value double precision,
+ tr_orderid character varying(255),
+ tr_affiliation character varying(255),
+ tr_total numeric(18,2),
+ tr_tax numeric(18,2),
+ tr_shipping numeric(18,2),
+ tr_city character varying(255),
+ tr_state character varying(255),
+ tr_country character varying(255),
+ ti_orderid character varying(255),
+ ti_sku character varying(255),
+ ti_name character varying(255),
+ ti_category character varying(255),
+ ti_price numeric(18,2),
+ ti_quantity integer,
+ pp_xoffset_min integer,
+ pp_xoffset_max integer,
+ pp_yoffset_min integer,
+ pp_yoffset_max integer,
+ useragent character varying(1000),
+ br_name character varying(50),
+ br_family character varying(50),
+ br_version character varying(50),
+ br_type character varying(50),
+ br_renderengine character varying(50),
+ br_lang character varying(255),
+ br_features_pdf boolean,
+ br_features_flash boolean,
+ br_features_java boolean,
+ br_features_director boolean,
+ br_features_quicktime boolean,
+ br_features_realplayer boolean,
+ br_features_windowsmedia boolean,
+ br_features_gears boolean,
+ br_features_silverlight boolean,
+ br_cookies boolean,
+ br_colordepth character varying(12),
+ br_viewwidth integer,
+ br_viewheight integer,
+ os_name character varying(50),
+ os_family character varying(50),
+ os_manufacturer character varying(50),
+ os_timezone character varying(50),
+ dvce_type character varying(50),
+ dvce_ismobile boolean,
+ dvce_screenwidth integer,
+ dvce_screenheight integer,
+ doc_charset character varying(128),
+ doc_width integer,
+ doc_height integer,
+ tr_currency character(3),
+ tr_total_base numeric(18,2),
+ tr_tax_base numeric(18,2),
+ tr_shipping_base numeric(18,2),
+ ti_currency character(3),
+ ti_price_base numeric(18,2),
+ base_currency character(3),
+ geo_timezone character varying(64),
+ mkt_clickid character varying(128),
+ mkt_network character varying(64),
+ etl_tags character varying(500),
+ dvce_sent_tstamp timestamp with time zone,
+ refr_domain_userid character varying(36),
+ refr_dvce_tstamp timestamp with time zone,
+ domain_sessionid character(36),
+ derived_tstamp timestamp with time zone,
+ event_vendor character varying(1000),
+ event_name character varying(1000),
+ event_format character varying(128),
+ event_version character varying(128),
+ event_fingerprint character varying(128),
+ true_tstamp timestamp with time zone
+);
+ALTER TABLE ONLY public.product_analytics_events_experimental ATTACH PARTITION gitlab_partitions_static.product_analytics_events_experimental_31 FOR VALUES WITH (modulus 64, remainder 31);
+
+CREATE TABLE gitlab_partitions_static.product_analytics_events_experimental_32 (
+ id bigint DEFAULT nextval('public.product_analytics_events_experimental_id_seq'::regclass) NOT NULL,
+ project_id integer NOT NULL,
+ platform character varying(255),
+ etl_tstamp timestamp with time zone,
+ collector_tstamp timestamp with time zone NOT NULL,
+ dvce_created_tstamp timestamp with time zone,
+ event character varying(128),
+ event_id character(36) NOT NULL,
+ txn_id integer,
+ name_tracker character varying(128),
+ v_tracker character varying(100),
+ v_collector character varying(100) NOT NULL,
+ v_etl character varying(100) NOT NULL,
+ user_id character varying(255),
+ user_ipaddress character varying(45),
+ user_fingerprint character varying(50),
+ domain_userid character varying(36),
+ domain_sessionidx smallint,
+ network_userid character varying(38),
+ geo_country character(2),
+ geo_region character(3),
+ geo_city character varying(75),
+ geo_zipcode character varying(15),
+ geo_latitude double precision,
+ geo_longitude double precision,
+ geo_region_name character varying(100),
+ ip_isp character varying(100),
+ ip_organization character varying(100),
+ ip_domain character varying(100),
+ ip_netspeed character varying(100),
+ page_url text,
+ page_title character varying(2000),
+ page_referrer text,
+ page_urlscheme character varying(16),
+ page_urlhost character varying(255),
+ page_urlport integer,
+ page_urlpath character varying(3000),
+ page_urlquery character varying(6000),
+ page_urlfragment character varying(3000),
+ refr_urlscheme character varying(16),
+ refr_urlhost character varying(255),
+ refr_urlport integer,
+ refr_urlpath character varying(6000),
+ refr_urlquery character varying(6000),
+ refr_urlfragment character varying(3000),
+ refr_medium character varying(25),
+ refr_source character varying(50),
+ refr_term character varying(255),
+ mkt_medium character varying(255),
+ mkt_source character varying(255),
+ mkt_term character varying(255),
+ mkt_content character varying(500),
+ mkt_campaign character varying(255),
+ se_category character varying(1000),
+ se_action character varying(1000),
+ se_label character varying(1000),
+ se_property character varying(1000),
+ se_value double precision,
+ tr_orderid character varying(255),
+ tr_affiliation character varying(255),
+ tr_total numeric(18,2),
+ tr_tax numeric(18,2),
+ tr_shipping numeric(18,2),
+ tr_city character varying(255),
+ tr_state character varying(255),
+ tr_country character varying(255),
+ ti_orderid character varying(255),
+ ti_sku character varying(255),
+ ti_name character varying(255),
+ ti_category character varying(255),
+ ti_price numeric(18,2),
+ ti_quantity integer,
+ pp_xoffset_min integer,
+ pp_xoffset_max integer,
+ pp_yoffset_min integer,
+ pp_yoffset_max integer,
+ useragent character varying(1000),
+ br_name character varying(50),
+ br_family character varying(50),
+ br_version character varying(50),
+ br_type character varying(50),
+ br_renderengine character varying(50),
+ br_lang character varying(255),
+ br_features_pdf boolean,
+ br_features_flash boolean,
+ br_features_java boolean,
+ br_features_director boolean,
+ br_features_quicktime boolean,
+ br_features_realplayer boolean,
+ br_features_windowsmedia boolean,
+ br_features_gears boolean,
+ br_features_silverlight boolean,
+ br_cookies boolean,
+ br_colordepth character varying(12),
+ br_viewwidth integer,
+ br_viewheight integer,
+ os_name character varying(50),
+ os_family character varying(50),
+ os_manufacturer character varying(50),
+ os_timezone character varying(50),
+ dvce_type character varying(50),
+ dvce_ismobile boolean,
+ dvce_screenwidth integer,
+ dvce_screenheight integer,
+ doc_charset character varying(128),
+ doc_width integer,
+ doc_height integer,
+ tr_currency character(3),
+ tr_total_base numeric(18,2),
+ tr_tax_base numeric(18,2),
+ tr_shipping_base numeric(18,2),
+ ti_currency character(3),
+ ti_price_base numeric(18,2),
+ base_currency character(3),
+ geo_timezone character varying(64),
+ mkt_clickid character varying(128),
+ mkt_network character varying(64),
+ etl_tags character varying(500),
+ dvce_sent_tstamp timestamp with time zone,
+ refr_domain_userid character varying(36),
+ refr_dvce_tstamp timestamp with time zone,
+ domain_sessionid character(36),
+ derived_tstamp timestamp with time zone,
+ event_vendor character varying(1000),
+ event_name character varying(1000),
+ event_format character varying(128),
+ event_version character varying(128),
+ event_fingerprint character varying(128),
+ true_tstamp timestamp with time zone
+);
+ALTER TABLE ONLY public.product_analytics_events_experimental ATTACH PARTITION gitlab_partitions_static.product_analytics_events_experimental_32 FOR VALUES WITH (modulus 64, remainder 32);
+
+CREATE TABLE gitlab_partitions_static.product_analytics_events_experimental_33 (
+ id bigint DEFAULT nextval('public.product_analytics_events_experimental_id_seq'::regclass) NOT NULL,
+ project_id integer NOT NULL,
+ platform character varying(255),
+ etl_tstamp timestamp with time zone,
+ collector_tstamp timestamp with time zone NOT NULL,
+ dvce_created_tstamp timestamp with time zone,
+ event character varying(128),
+ event_id character(36) NOT NULL,
+ txn_id integer,
+ name_tracker character varying(128),
+ v_tracker character varying(100),
+ v_collector character varying(100) NOT NULL,
+ v_etl character varying(100) NOT NULL,
+ user_id character varying(255),
+ user_ipaddress character varying(45),
+ user_fingerprint character varying(50),
+ domain_userid character varying(36),
+ domain_sessionidx smallint,
+ network_userid character varying(38),
+ geo_country character(2),
+ geo_region character(3),
+ geo_city character varying(75),
+ geo_zipcode character varying(15),
+ geo_latitude double precision,
+ geo_longitude double precision,
+ geo_region_name character varying(100),
+ ip_isp character varying(100),
+ ip_organization character varying(100),
+ ip_domain character varying(100),
+ ip_netspeed character varying(100),
+ page_url text,
+ page_title character varying(2000),
+ page_referrer text,
+ page_urlscheme character varying(16),
+ page_urlhost character varying(255),
+ page_urlport integer,
+ page_urlpath character varying(3000),
+ page_urlquery character varying(6000),
+ page_urlfragment character varying(3000),
+ refr_urlscheme character varying(16),
+ refr_urlhost character varying(255),
+ refr_urlport integer,
+ refr_urlpath character varying(6000),
+ refr_urlquery character varying(6000),
+ refr_urlfragment character varying(3000),
+ refr_medium character varying(25),
+ refr_source character varying(50),
+ refr_term character varying(255),
+ mkt_medium character varying(255),
+ mkt_source character varying(255),
+ mkt_term character varying(255),
+ mkt_content character varying(500),
+ mkt_campaign character varying(255),
+ se_category character varying(1000),
+ se_action character varying(1000),
+ se_label character varying(1000),
+ se_property character varying(1000),
+ se_value double precision,
+ tr_orderid character varying(255),
+ tr_affiliation character varying(255),
+ tr_total numeric(18,2),
+ tr_tax numeric(18,2),
+ tr_shipping numeric(18,2),
+ tr_city character varying(255),
+ tr_state character varying(255),
+ tr_country character varying(255),
+ ti_orderid character varying(255),
+ ti_sku character varying(255),
+ ti_name character varying(255),
+ ti_category character varying(255),
+ ti_price numeric(18,2),
+ ti_quantity integer,
+ pp_xoffset_min integer,
+ pp_xoffset_max integer,
+ pp_yoffset_min integer,
+ pp_yoffset_max integer,
+ useragent character varying(1000),
+ br_name character varying(50),
+ br_family character varying(50),
+ br_version character varying(50),
+ br_type character varying(50),
+ br_renderengine character varying(50),
+ br_lang character varying(255),
+ br_features_pdf boolean,
+ br_features_flash boolean,
+ br_features_java boolean,
+ br_features_director boolean,
+ br_features_quicktime boolean,
+ br_features_realplayer boolean,
+ br_features_windowsmedia boolean,
+ br_features_gears boolean,
+ br_features_silverlight boolean,
+ br_cookies boolean,
+ br_colordepth character varying(12),
+ br_viewwidth integer,
+ br_viewheight integer,
+ os_name character varying(50),
+ os_family character varying(50),
+ os_manufacturer character varying(50),
+ os_timezone character varying(50),
+ dvce_type character varying(50),
+ dvce_ismobile boolean,
+ dvce_screenwidth integer,
+ dvce_screenheight integer,
+ doc_charset character varying(128),
+ doc_width integer,
+ doc_height integer,
+ tr_currency character(3),
+ tr_total_base numeric(18,2),
+ tr_tax_base numeric(18,2),
+ tr_shipping_base numeric(18,2),
+ ti_currency character(3),
+ ti_price_base numeric(18,2),
+ base_currency character(3),
+ geo_timezone character varying(64),
+ mkt_clickid character varying(128),
+ mkt_network character varying(64),
+ etl_tags character varying(500),
+ dvce_sent_tstamp timestamp with time zone,
+ refr_domain_userid character varying(36),
+ refr_dvce_tstamp timestamp with time zone,
+ domain_sessionid character(36),
+ derived_tstamp timestamp with time zone,
+ event_vendor character varying(1000),
+ event_name character varying(1000),
+ event_format character varying(128),
+ event_version character varying(128),
+ event_fingerprint character varying(128),
+ true_tstamp timestamp with time zone
+);
+ALTER TABLE ONLY public.product_analytics_events_experimental ATTACH PARTITION gitlab_partitions_static.product_analytics_events_experimental_33 FOR VALUES WITH (modulus 64, remainder 33);
+
+CREATE TABLE gitlab_partitions_static.product_analytics_events_experimental_34 (
+ id bigint DEFAULT nextval('public.product_analytics_events_experimental_id_seq'::regclass) NOT NULL,
+ project_id integer NOT NULL,
+ platform character varying(255),
+ etl_tstamp timestamp with time zone,
+ collector_tstamp timestamp with time zone NOT NULL,
+ dvce_created_tstamp timestamp with time zone,
+ event character varying(128),
+ event_id character(36) NOT NULL,
+ txn_id integer,
+ name_tracker character varying(128),
+ v_tracker character varying(100),
+ v_collector character varying(100) NOT NULL,
+ v_etl character varying(100) NOT NULL,
+ user_id character varying(255),
+ user_ipaddress character varying(45),
+ user_fingerprint character varying(50),
+ domain_userid character varying(36),
+ domain_sessionidx smallint,
+ network_userid character varying(38),
+ geo_country character(2),
+ geo_region character(3),
+ geo_city character varying(75),
+ geo_zipcode character varying(15),
+ geo_latitude double precision,
+ geo_longitude double precision,
+ geo_region_name character varying(100),
+ ip_isp character varying(100),
+ ip_organization character varying(100),
+ ip_domain character varying(100),
+ ip_netspeed character varying(100),
+ page_url text,
+ page_title character varying(2000),
+ page_referrer text,
+ page_urlscheme character varying(16),
+ page_urlhost character varying(255),
+ page_urlport integer,
+ page_urlpath character varying(3000),
+ page_urlquery character varying(6000),
+ page_urlfragment character varying(3000),
+ refr_urlscheme character varying(16),
+ refr_urlhost character varying(255),
+ refr_urlport integer,
+ refr_urlpath character varying(6000),
+ refr_urlquery character varying(6000),
+ refr_urlfragment character varying(3000),
+ refr_medium character varying(25),
+ refr_source character varying(50),
+ refr_term character varying(255),
+ mkt_medium character varying(255),
+ mkt_source character varying(255),
+ mkt_term character varying(255),
+ mkt_content character varying(500),
+ mkt_campaign character varying(255),
+ se_category character varying(1000),
+ se_action character varying(1000),
+ se_label character varying(1000),
+ se_property character varying(1000),
+ se_value double precision,
+ tr_orderid character varying(255),
+ tr_affiliation character varying(255),
+ tr_total numeric(18,2),
+ tr_tax numeric(18,2),
+ tr_shipping numeric(18,2),
+ tr_city character varying(255),
+ tr_state character varying(255),
+ tr_country character varying(255),
+ ti_orderid character varying(255),
+ ti_sku character varying(255),
+ ti_name character varying(255),
+ ti_category character varying(255),
+ ti_price numeric(18,2),
+ ti_quantity integer,
+ pp_xoffset_min integer,
+ pp_xoffset_max integer,
+ pp_yoffset_min integer,
+ pp_yoffset_max integer,
+ useragent character varying(1000),
+ br_name character varying(50),
+ br_family character varying(50),
+ br_version character varying(50),
+ br_type character varying(50),
+ br_renderengine character varying(50),
+ br_lang character varying(255),
+ br_features_pdf boolean,
+ br_features_flash boolean,
+ br_features_java boolean,
+ br_features_director boolean,
+ br_features_quicktime boolean,
+ br_features_realplayer boolean,
+ br_features_windowsmedia boolean,
+ br_features_gears boolean,
+ br_features_silverlight boolean,
+ br_cookies boolean,
+ br_colordepth character varying(12),
+ br_viewwidth integer,
+ br_viewheight integer,
+ os_name character varying(50),
+ os_family character varying(50),
+ os_manufacturer character varying(50),
+ os_timezone character varying(50),
+ dvce_type character varying(50),
+ dvce_ismobile boolean,
+ dvce_screenwidth integer,
+ dvce_screenheight integer,
+ doc_charset character varying(128),
+ doc_width integer,
+ doc_height integer,
+ tr_currency character(3),
+ tr_total_base numeric(18,2),
+ tr_tax_base numeric(18,2),
+ tr_shipping_base numeric(18,2),
+ ti_currency character(3),
+ ti_price_base numeric(18,2),
+ base_currency character(3),
+ geo_timezone character varying(64),
+ mkt_clickid character varying(128),
+ mkt_network character varying(64),
+ etl_tags character varying(500),
+ dvce_sent_tstamp timestamp with time zone,
+ refr_domain_userid character varying(36),
+ refr_dvce_tstamp timestamp with time zone,
+ domain_sessionid character(36),
+ derived_tstamp timestamp with time zone,
+ event_vendor character varying(1000),
+ event_name character varying(1000),
+ event_format character varying(128),
+ event_version character varying(128),
+ event_fingerprint character varying(128),
+ true_tstamp timestamp with time zone
+);
+ALTER TABLE ONLY public.product_analytics_events_experimental ATTACH PARTITION gitlab_partitions_static.product_analytics_events_experimental_34 FOR VALUES WITH (modulus 64, remainder 34);
+
+CREATE TABLE gitlab_partitions_static.product_analytics_events_experimental_35 (
+ id bigint DEFAULT nextval('public.product_analytics_events_experimental_id_seq'::regclass) NOT NULL,
+ project_id integer NOT NULL,
+ platform character varying(255),
+ etl_tstamp timestamp with time zone,
+ collector_tstamp timestamp with time zone NOT NULL,
+ dvce_created_tstamp timestamp with time zone,
+ event character varying(128),
+ event_id character(36) NOT NULL,
+ txn_id integer,
+ name_tracker character varying(128),
+ v_tracker character varying(100),
+ v_collector character varying(100) NOT NULL,
+ v_etl character varying(100) NOT NULL,
+ user_id character varying(255),
+ user_ipaddress character varying(45),
+ user_fingerprint character varying(50),
+ domain_userid character varying(36),
+ domain_sessionidx smallint,
+ network_userid character varying(38),
+ geo_country character(2),
+ geo_region character(3),
+ geo_city character varying(75),
+ geo_zipcode character varying(15),
+ geo_latitude double precision,
+ geo_longitude double precision,
+ geo_region_name character varying(100),
+ ip_isp character varying(100),
+ ip_organization character varying(100),
+ ip_domain character varying(100),
+ ip_netspeed character varying(100),
+ page_url text,
+ page_title character varying(2000),
+ page_referrer text,
+ page_urlscheme character varying(16),
+ page_urlhost character varying(255),
+ page_urlport integer,
+ page_urlpath character varying(3000),
+ page_urlquery character varying(6000),
+ page_urlfragment character varying(3000),
+ refr_urlscheme character varying(16),
+ refr_urlhost character varying(255),
+ refr_urlport integer,
+ refr_urlpath character varying(6000),
+ refr_urlquery character varying(6000),
+ refr_urlfragment character varying(3000),
+ refr_medium character varying(25),
+ refr_source character varying(50),
+ refr_term character varying(255),
+ mkt_medium character varying(255),
+ mkt_source character varying(255),
+ mkt_term character varying(255),
+ mkt_content character varying(500),
+ mkt_campaign character varying(255),
+ se_category character varying(1000),
+ se_action character varying(1000),
+ se_label character varying(1000),
+ se_property character varying(1000),
+ se_value double precision,
+ tr_orderid character varying(255),
+ tr_affiliation character varying(255),
+ tr_total numeric(18,2),
+ tr_tax numeric(18,2),
+ tr_shipping numeric(18,2),
+ tr_city character varying(255),
+ tr_state character varying(255),
+ tr_country character varying(255),
+ ti_orderid character varying(255),
+ ti_sku character varying(255),
+ ti_name character varying(255),
+ ti_category character varying(255),
+ ti_price numeric(18,2),
+ ti_quantity integer,
+ pp_xoffset_min integer,
+ pp_xoffset_max integer,
+ pp_yoffset_min integer,
+ pp_yoffset_max integer,
+ useragent character varying(1000),
+ br_name character varying(50),
+ br_family character varying(50),
+ br_version character varying(50),
+ br_type character varying(50),
+ br_renderengine character varying(50),
+ br_lang character varying(255),
+ br_features_pdf boolean,
+ br_features_flash boolean,
+ br_features_java boolean,
+ br_features_director boolean,
+ br_features_quicktime boolean,
+ br_features_realplayer boolean,
+ br_features_windowsmedia boolean,
+ br_features_gears boolean,
+ br_features_silverlight boolean,
+ br_cookies boolean,
+ br_colordepth character varying(12),
+ br_viewwidth integer,
+ br_viewheight integer,
+ os_name character varying(50),
+ os_family character varying(50),
+ os_manufacturer character varying(50),
+ os_timezone character varying(50),
+ dvce_type character varying(50),
+ dvce_ismobile boolean,
+ dvce_screenwidth integer,
+ dvce_screenheight integer,
+ doc_charset character varying(128),
+ doc_width integer,
+ doc_height integer,
+ tr_currency character(3),
+ tr_total_base numeric(18,2),
+ tr_tax_base numeric(18,2),
+ tr_shipping_base numeric(18,2),
+ ti_currency character(3),
+ ti_price_base numeric(18,2),
+ base_currency character(3),
+ geo_timezone character varying(64),
+ mkt_clickid character varying(128),
+ mkt_network character varying(64),
+ etl_tags character varying(500),
+ dvce_sent_tstamp timestamp with time zone,
+ refr_domain_userid character varying(36),
+ refr_dvce_tstamp timestamp with time zone,
+ domain_sessionid character(36),
+ derived_tstamp timestamp with time zone,
+ event_vendor character varying(1000),
+ event_name character varying(1000),
+ event_format character varying(128),
+ event_version character varying(128),
+ event_fingerprint character varying(128),
+ true_tstamp timestamp with time zone
+);
+ALTER TABLE ONLY public.product_analytics_events_experimental ATTACH PARTITION gitlab_partitions_static.product_analytics_events_experimental_35 FOR VALUES WITH (modulus 64, remainder 35);
+
+CREATE TABLE gitlab_partitions_static.product_analytics_events_experimental_36 (
+ id bigint DEFAULT nextval('public.product_analytics_events_experimental_id_seq'::regclass) NOT NULL,
+ project_id integer NOT NULL,
+ platform character varying(255),
+ etl_tstamp timestamp with time zone,
+ collector_tstamp timestamp with time zone NOT NULL,
+ dvce_created_tstamp timestamp with time zone,
+ event character varying(128),
+ event_id character(36) NOT NULL,
+ txn_id integer,
+ name_tracker character varying(128),
+ v_tracker character varying(100),
+ v_collector character varying(100) NOT NULL,
+ v_etl character varying(100) NOT NULL,
+ user_id character varying(255),
+ user_ipaddress character varying(45),
+ user_fingerprint character varying(50),
+ domain_userid character varying(36),
+ domain_sessionidx smallint,
+ network_userid character varying(38),
+ geo_country character(2),
+ geo_region character(3),
+ geo_city character varying(75),
+ geo_zipcode character varying(15),
+ geo_latitude double precision,
+ geo_longitude double precision,
+ geo_region_name character varying(100),
+ ip_isp character varying(100),
+ ip_organization character varying(100),
+ ip_domain character varying(100),
+ ip_netspeed character varying(100),
+ page_url text,
+ page_title character varying(2000),
+ page_referrer text,
+ page_urlscheme character varying(16),
+ page_urlhost character varying(255),
+ page_urlport integer,
+ page_urlpath character varying(3000),
+ page_urlquery character varying(6000),
+ page_urlfragment character varying(3000),
+ refr_urlscheme character varying(16),
+ refr_urlhost character varying(255),
+ refr_urlport integer,
+ refr_urlpath character varying(6000),
+ refr_urlquery character varying(6000),
+ refr_urlfragment character varying(3000),
+ refr_medium character varying(25),
+ refr_source character varying(50),
+ refr_term character varying(255),
+ mkt_medium character varying(255),
+ mkt_source character varying(255),
+ mkt_term character varying(255),
+ mkt_content character varying(500),
+ mkt_campaign character varying(255),
+ se_category character varying(1000),
+ se_action character varying(1000),
+ se_label character varying(1000),
+ se_property character varying(1000),
+ se_value double precision,
+ tr_orderid character varying(255),
+ tr_affiliation character varying(255),
+ tr_total numeric(18,2),
+ tr_tax numeric(18,2),
+ tr_shipping numeric(18,2),
+ tr_city character varying(255),
+ tr_state character varying(255),
+ tr_country character varying(255),
+ ti_orderid character varying(255),
+ ti_sku character varying(255),
+ ti_name character varying(255),
+ ti_category character varying(255),
+ ti_price numeric(18,2),
+ ti_quantity integer,
+ pp_xoffset_min integer,
+ pp_xoffset_max integer,
+ pp_yoffset_min integer,
+ pp_yoffset_max integer,
+ useragent character varying(1000),
+ br_name character varying(50),
+ br_family character varying(50),
+ br_version character varying(50),
+ br_type character varying(50),
+ br_renderengine character varying(50),
+ br_lang character varying(255),
+ br_features_pdf boolean,
+ br_features_flash boolean,
+ br_features_java boolean,
+ br_features_director boolean,
+ br_features_quicktime boolean,
+ br_features_realplayer boolean,
+ br_features_windowsmedia boolean,
+ br_features_gears boolean,
+ br_features_silverlight boolean,
+ br_cookies boolean,
+ br_colordepth character varying(12),
+ br_viewwidth integer,
+ br_viewheight integer,
+ os_name character varying(50),
+ os_family character varying(50),
+ os_manufacturer character varying(50),
+ os_timezone character varying(50),
+ dvce_type character varying(50),
+ dvce_ismobile boolean,
+ dvce_screenwidth integer,
+ dvce_screenheight integer,
+ doc_charset character varying(128),
+ doc_width integer,
+ doc_height integer,
+ tr_currency character(3),
+ tr_total_base numeric(18,2),
+ tr_tax_base numeric(18,2),
+ tr_shipping_base numeric(18,2),
+ ti_currency character(3),
+ ti_price_base numeric(18,2),
+ base_currency character(3),
+ geo_timezone character varying(64),
+ mkt_clickid character varying(128),
+ mkt_network character varying(64),
+ etl_tags character varying(500),
+ dvce_sent_tstamp timestamp with time zone,
+ refr_domain_userid character varying(36),
+ refr_dvce_tstamp timestamp with time zone,
+ domain_sessionid character(36),
+ derived_tstamp timestamp with time zone,
+ event_vendor character varying(1000),
+ event_name character varying(1000),
+ event_format character varying(128),
+ event_version character varying(128),
+ event_fingerprint character varying(128),
+ true_tstamp timestamp with time zone
+);
+ALTER TABLE ONLY public.product_analytics_events_experimental ATTACH PARTITION gitlab_partitions_static.product_analytics_events_experimental_36 FOR VALUES WITH (modulus 64, remainder 36);
+
+CREATE TABLE gitlab_partitions_static.product_analytics_events_experimental_37 (
+ id bigint DEFAULT nextval('public.product_analytics_events_experimental_id_seq'::regclass) NOT NULL,
+ project_id integer NOT NULL,
+ platform character varying(255),
+ etl_tstamp timestamp with time zone,
+ collector_tstamp timestamp with time zone NOT NULL,
+ dvce_created_tstamp timestamp with time zone,
+ event character varying(128),
+ event_id character(36) NOT NULL,
+ txn_id integer,
+ name_tracker character varying(128),
+ v_tracker character varying(100),
+ v_collector character varying(100) NOT NULL,
+ v_etl character varying(100) NOT NULL,
+ user_id character varying(255),
+ user_ipaddress character varying(45),
+ user_fingerprint character varying(50),
+ domain_userid character varying(36),
+ domain_sessionidx smallint,
+ network_userid character varying(38),
+ geo_country character(2),
+ geo_region character(3),
+ geo_city character varying(75),
+ geo_zipcode character varying(15),
+ geo_latitude double precision,
+ geo_longitude double precision,
+ geo_region_name character varying(100),
+ ip_isp character varying(100),
+ ip_organization character varying(100),
+ ip_domain character varying(100),
+ ip_netspeed character varying(100),
+ page_url text,
+ page_title character varying(2000),
+ page_referrer text,
+ page_urlscheme character varying(16),
+ page_urlhost character varying(255),
+ page_urlport integer,
+ page_urlpath character varying(3000),
+ page_urlquery character varying(6000),
+ page_urlfragment character varying(3000),
+ refr_urlscheme character varying(16),
+ refr_urlhost character varying(255),
+ refr_urlport integer,
+ refr_urlpath character varying(6000),
+ refr_urlquery character varying(6000),
+ refr_urlfragment character varying(3000),
+ refr_medium character varying(25),
+ refr_source character varying(50),
+ refr_term character varying(255),
+ mkt_medium character varying(255),
+ mkt_source character varying(255),
+ mkt_term character varying(255),
+ mkt_content character varying(500),
+ mkt_campaign character varying(255),
+ se_category character varying(1000),
+ se_action character varying(1000),
+ se_label character varying(1000),
+ se_property character varying(1000),
+ se_value double precision,
+ tr_orderid character varying(255),
+ tr_affiliation character varying(255),
+ tr_total numeric(18,2),
+ tr_tax numeric(18,2),
+ tr_shipping numeric(18,2),
+ tr_city character varying(255),
+ tr_state character varying(255),
+ tr_country character varying(255),
+ ti_orderid character varying(255),
+ ti_sku character varying(255),
+ ti_name character varying(255),
+ ti_category character varying(255),
+ ti_price numeric(18,2),
+ ti_quantity integer,
+ pp_xoffset_min integer,
+ pp_xoffset_max integer,
+ pp_yoffset_min integer,
+ pp_yoffset_max integer,
+ useragent character varying(1000),
+ br_name character varying(50),
+ br_family character varying(50),
+ br_version character varying(50),
+ br_type character varying(50),
+ br_renderengine character varying(50),
+ br_lang character varying(255),
+ br_features_pdf boolean,
+ br_features_flash boolean,
+ br_features_java boolean,
+ br_features_director boolean,
+ br_features_quicktime boolean,
+ br_features_realplayer boolean,
+ br_features_windowsmedia boolean,
+ br_features_gears boolean,
+ br_features_silverlight boolean,
+ br_cookies boolean,
+ br_colordepth character varying(12),
+ br_viewwidth integer,
+ br_viewheight integer,
+ os_name character varying(50),
+ os_family character varying(50),
+ os_manufacturer character varying(50),
+ os_timezone character varying(50),
+ dvce_type character varying(50),
+ dvce_ismobile boolean,
+ dvce_screenwidth integer,
+ dvce_screenheight integer,
+ doc_charset character varying(128),
+ doc_width integer,
+ doc_height integer,
+ tr_currency character(3),
+ tr_total_base numeric(18,2),
+ tr_tax_base numeric(18,2),
+ tr_shipping_base numeric(18,2),
+ ti_currency character(3),
+ ti_price_base numeric(18,2),
+ base_currency character(3),
+ geo_timezone character varying(64),
+ mkt_clickid character varying(128),
+ mkt_network character varying(64),
+ etl_tags character varying(500),
+ dvce_sent_tstamp timestamp with time zone,
+ refr_domain_userid character varying(36),
+ refr_dvce_tstamp timestamp with time zone,
+ domain_sessionid character(36),
+ derived_tstamp timestamp with time zone,
+ event_vendor character varying(1000),
+ event_name character varying(1000),
+ event_format character varying(128),
+ event_version character varying(128),
+ event_fingerprint character varying(128),
+ true_tstamp timestamp with time zone
+);
+ALTER TABLE ONLY public.product_analytics_events_experimental ATTACH PARTITION gitlab_partitions_static.product_analytics_events_experimental_37 FOR VALUES WITH (modulus 64, remainder 37);
+
+CREATE TABLE gitlab_partitions_static.product_analytics_events_experimental_38 (
+ id bigint DEFAULT nextval('public.product_analytics_events_experimental_id_seq'::regclass) NOT NULL,
+ project_id integer NOT NULL,
+ platform character varying(255),
+ etl_tstamp timestamp with time zone,
+ collector_tstamp timestamp with time zone NOT NULL,
+ dvce_created_tstamp timestamp with time zone,
+ event character varying(128),
+ event_id character(36) NOT NULL,
+ txn_id integer,
+ name_tracker character varying(128),
+ v_tracker character varying(100),
+ v_collector character varying(100) NOT NULL,
+ v_etl character varying(100) NOT NULL,
+ user_id character varying(255),
+ user_ipaddress character varying(45),
+ user_fingerprint character varying(50),
+ domain_userid character varying(36),
+ domain_sessionidx smallint,
+ network_userid character varying(38),
+ geo_country character(2),
+ geo_region character(3),
+ geo_city character varying(75),
+ geo_zipcode character varying(15),
+ geo_latitude double precision,
+ geo_longitude double precision,
+ geo_region_name character varying(100),
+ ip_isp character varying(100),
+ ip_organization character varying(100),
+ ip_domain character varying(100),
+ ip_netspeed character varying(100),
+ page_url text,
+ page_title character varying(2000),
+ page_referrer text,
+ page_urlscheme character varying(16),
+ page_urlhost character varying(255),
+ page_urlport integer,
+ page_urlpath character varying(3000),
+ page_urlquery character varying(6000),
+ page_urlfragment character varying(3000),
+ refr_urlscheme character varying(16),
+ refr_urlhost character varying(255),
+ refr_urlport integer,
+ refr_urlpath character varying(6000),
+ refr_urlquery character varying(6000),
+ refr_urlfragment character varying(3000),
+ refr_medium character varying(25),
+ refr_source character varying(50),
+ refr_term character varying(255),
+ mkt_medium character varying(255),
+ mkt_source character varying(255),
+ mkt_term character varying(255),
+ mkt_content character varying(500),
+ mkt_campaign character varying(255),
+ se_category character varying(1000),
+ se_action character varying(1000),
+ se_label character varying(1000),
+ se_property character varying(1000),
+ se_value double precision,
+ tr_orderid character varying(255),
+ tr_affiliation character varying(255),
+ tr_total numeric(18,2),
+ tr_tax numeric(18,2),
+ tr_shipping numeric(18,2),
+ tr_city character varying(255),
+ tr_state character varying(255),
+ tr_country character varying(255),
+ ti_orderid character varying(255),
+ ti_sku character varying(255),
+ ti_name character varying(255),
+ ti_category character varying(255),
+ ti_price numeric(18,2),
+ ti_quantity integer,
+ pp_xoffset_min integer,
+ pp_xoffset_max integer,
+ pp_yoffset_min integer,
+ pp_yoffset_max integer,
+ useragent character varying(1000),
+ br_name character varying(50),
+ br_family character varying(50),
+ br_version character varying(50),
+ br_type character varying(50),
+ br_renderengine character varying(50),
+ br_lang character varying(255),
+ br_features_pdf boolean,
+ br_features_flash boolean,
+ br_features_java boolean,
+ br_features_director boolean,
+ br_features_quicktime boolean,
+ br_features_realplayer boolean,
+ br_features_windowsmedia boolean,
+ br_features_gears boolean,
+ br_features_silverlight boolean,
+ br_cookies boolean,
+ br_colordepth character varying(12),
+ br_viewwidth integer,
+ br_viewheight integer,
+ os_name character varying(50),
+ os_family character varying(50),
+ os_manufacturer character varying(50),
+ os_timezone character varying(50),
+ dvce_type character varying(50),
+ dvce_ismobile boolean,
+ dvce_screenwidth integer,
+ dvce_screenheight integer,
+ doc_charset character varying(128),
+ doc_width integer,
+ doc_height integer,
+ tr_currency character(3),
+ tr_total_base numeric(18,2),
+ tr_tax_base numeric(18,2),
+ tr_shipping_base numeric(18,2),
+ ti_currency character(3),
+ ti_price_base numeric(18,2),
+ base_currency character(3),
+ geo_timezone character varying(64),
+ mkt_clickid character varying(128),
+ mkt_network character varying(64),
+ etl_tags character varying(500),
+ dvce_sent_tstamp timestamp with time zone,
+ refr_domain_userid character varying(36),
+ refr_dvce_tstamp timestamp with time zone,
+ domain_sessionid character(36),
+ derived_tstamp timestamp with time zone,
+ event_vendor character varying(1000),
+ event_name character varying(1000),
+ event_format character varying(128),
+ event_version character varying(128),
+ event_fingerprint character varying(128),
+ true_tstamp timestamp with time zone
+);
+ALTER TABLE ONLY public.product_analytics_events_experimental ATTACH PARTITION gitlab_partitions_static.product_analytics_events_experimental_38 FOR VALUES WITH (modulus 64, remainder 38);
+
+CREATE TABLE gitlab_partitions_static.product_analytics_events_experimental_39 (
+ id bigint DEFAULT nextval('public.product_analytics_events_experimental_id_seq'::regclass) NOT NULL,
+ project_id integer NOT NULL,
+ platform character varying(255),
+ etl_tstamp timestamp with time zone,
+ collector_tstamp timestamp with time zone NOT NULL,
+ dvce_created_tstamp timestamp with time zone,
+ event character varying(128),
+ event_id character(36) NOT NULL,
+ txn_id integer,
+ name_tracker character varying(128),
+ v_tracker character varying(100),
+ v_collector character varying(100) NOT NULL,
+ v_etl character varying(100) NOT NULL,
+ user_id character varying(255),
+ user_ipaddress character varying(45),
+ user_fingerprint character varying(50),
+ domain_userid character varying(36),
+ domain_sessionidx smallint,
+ network_userid character varying(38),
+ geo_country character(2),
+ geo_region character(3),
+ geo_city character varying(75),
+ geo_zipcode character varying(15),
+ geo_latitude double precision,
+ geo_longitude double precision,
+ geo_region_name character varying(100),
+ ip_isp character varying(100),
+ ip_organization character varying(100),
+ ip_domain character varying(100),
+ ip_netspeed character varying(100),
+ page_url text,
+ page_title character varying(2000),
+ page_referrer text,
+ page_urlscheme character varying(16),
+ page_urlhost character varying(255),
+ page_urlport integer,
+ page_urlpath character varying(3000),
+ page_urlquery character varying(6000),
+ page_urlfragment character varying(3000),
+ refr_urlscheme character varying(16),
+ refr_urlhost character varying(255),
+ refr_urlport integer,
+ refr_urlpath character varying(6000),
+ refr_urlquery character varying(6000),
+ refr_urlfragment character varying(3000),
+ refr_medium character varying(25),
+ refr_source character varying(50),
+ refr_term character varying(255),
+ mkt_medium character varying(255),
+ mkt_source character varying(255),
+ mkt_term character varying(255),
+ mkt_content character varying(500),
+ mkt_campaign character varying(255),
+ se_category character varying(1000),
+ se_action character varying(1000),
+ se_label character varying(1000),
+ se_property character varying(1000),
+ se_value double precision,
+ tr_orderid character varying(255),
+ tr_affiliation character varying(255),
+ tr_total numeric(18,2),
+ tr_tax numeric(18,2),
+ tr_shipping numeric(18,2),
+ tr_city character varying(255),
+ tr_state character varying(255),
+ tr_country character varying(255),
+ ti_orderid character varying(255),
+ ti_sku character varying(255),
+ ti_name character varying(255),
+ ti_category character varying(255),
+ ti_price numeric(18,2),
+ ti_quantity integer,
+ pp_xoffset_min integer,
+ pp_xoffset_max integer,
+ pp_yoffset_min integer,
+ pp_yoffset_max integer,
+ useragent character varying(1000),
+ br_name character varying(50),
+ br_family character varying(50),
+ br_version character varying(50),
+ br_type character varying(50),
+ br_renderengine character varying(50),
+ br_lang character varying(255),
+ br_features_pdf boolean,
+ br_features_flash boolean,
+ br_features_java boolean,
+ br_features_director boolean,
+ br_features_quicktime boolean,
+ br_features_realplayer boolean,
+ br_features_windowsmedia boolean,
+ br_features_gears boolean,
+ br_features_silverlight boolean,
+ br_cookies boolean,
+ br_colordepth character varying(12),
+ br_viewwidth integer,
+ br_viewheight integer,
+ os_name character varying(50),
+ os_family character varying(50),
+ os_manufacturer character varying(50),
+ os_timezone character varying(50),
+ dvce_type character varying(50),
+ dvce_ismobile boolean,
+ dvce_screenwidth integer,
+ dvce_screenheight integer,
+ doc_charset character varying(128),
+ doc_width integer,
+ doc_height integer,
+ tr_currency character(3),
+ tr_total_base numeric(18,2),
+ tr_tax_base numeric(18,2),
+ tr_shipping_base numeric(18,2),
+ ti_currency character(3),
+ ti_price_base numeric(18,2),
+ base_currency character(3),
+ geo_timezone character varying(64),
+ mkt_clickid character varying(128),
+ mkt_network character varying(64),
+ etl_tags character varying(500),
+ dvce_sent_tstamp timestamp with time zone,
+ refr_domain_userid character varying(36),
+ refr_dvce_tstamp timestamp with time zone,
+ domain_sessionid character(36),
+ derived_tstamp timestamp with time zone,
+ event_vendor character varying(1000),
+ event_name character varying(1000),
+ event_format character varying(128),
+ event_version character varying(128),
+ event_fingerprint character varying(128),
+ true_tstamp timestamp with time zone
+);
+ALTER TABLE ONLY public.product_analytics_events_experimental ATTACH PARTITION gitlab_partitions_static.product_analytics_events_experimental_39 FOR VALUES WITH (modulus 64, remainder 39);
+
+CREATE TABLE gitlab_partitions_static.product_analytics_events_experimental_40 (
+ id bigint DEFAULT nextval('public.product_analytics_events_experimental_id_seq'::regclass) NOT NULL,
+ project_id integer NOT NULL,
+ platform character varying(255),
+ etl_tstamp timestamp with time zone,
+ collector_tstamp timestamp with time zone NOT NULL,
+ dvce_created_tstamp timestamp with time zone,
+ event character varying(128),
+ event_id character(36) NOT NULL,
+ txn_id integer,
+ name_tracker character varying(128),
+ v_tracker character varying(100),
+ v_collector character varying(100) NOT NULL,
+ v_etl character varying(100) NOT NULL,
+ user_id character varying(255),
+ user_ipaddress character varying(45),
+ user_fingerprint character varying(50),
+ domain_userid character varying(36),
+ domain_sessionidx smallint,
+ network_userid character varying(38),
+ geo_country character(2),
+ geo_region character(3),
+ geo_city character varying(75),
+ geo_zipcode character varying(15),
+ geo_latitude double precision,
+ geo_longitude double precision,
+ geo_region_name character varying(100),
+ ip_isp character varying(100),
+ ip_organization character varying(100),
+ ip_domain character varying(100),
+ ip_netspeed character varying(100),
+ page_url text,
+ page_title character varying(2000),
+ page_referrer text,
+ page_urlscheme character varying(16),
+ page_urlhost character varying(255),
+ page_urlport integer,
+ page_urlpath character varying(3000),
+ page_urlquery character varying(6000),
+ page_urlfragment character varying(3000),
+ refr_urlscheme character varying(16),
+ refr_urlhost character varying(255),
+ refr_urlport integer,
+ refr_urlpath character varying(6000),
+ refr_urlquery character varying(6000),
+ refr_urlfragment character varying(3000),
+ refr_medium character varying(25),
+ refr_source character varying(50),
+ refr_term character varying(255),
+ mkt_medium character varying(255),
+ mkt_source character varying(255),
+ mkt_term character varying(255),
+ mkt_content character varying(500),
+ mkt_campaign character varying(255),
+ se_category character varying(1000),
+ se_action character varying(1000),
+ se_label character varying(1000),
+ se_property character varying(1000),
+ se_value double precision,
+ tr_orderid character varying(255),
+ tr_affiliation character varying(255),
+ tr_total numeric(18,2),
+ tr_tax numeric(18,2),
+ tr_shipping numeric(18,2),
+ tr_city character varying(255),
+ tr_state character varying(255),
+ tr_country character varying(255),
+ ti_orderid character varying(255),
+ ti_sku character varying(255),
+ ti_name character varying(255),
+ ti_category character varying(255),
+ ti_price numeric(18,2),
+ ti_quantity integer,
+ pp_xoffset_min integer,
+ pp_xoffset_max integer,
+ pp_yoffset_min integer,
+ pp_yoffset_max integer,
+ useragent character varying(1000),
+ br_name character varying(50),
+ br_family character varying(50),
+ br_version character varying(50),
+ br_type character varying(50),
+ br_renderengine character varying(50),
+ br_lang character varying(255),
+ br_features_pdf boolean,
+ br_features_flash boolean,
+ br_features_java boolean,
+ br_features_director boolean,
+ br_features_quicktime boolean,
+ br_features_realplayer boolean,
+ br_features_windowsmedia boolean,
+ br_features_gears boolean,
+ br_features_silverlight boolean,
+ br_cookies boolean,
+ br_colordepth character varying(12),
+ br_viewwidth integer,
+ br_viewheight integer,
+ os_name character varying(50),
+ os_family character varying(50),
+ os_manufacturer character varying(50),
+ os_timezone character varying(50),
+ dvce_type character varying(50),
+ dvce_ismobile boolean,
+ dvce_screenwidth integer,
+ dvce_screenheight integer,
+ doc_charset character varying(128),
+ doc_width integer,
+ doc_height integer,
+ tr_currency character(3),
+ tr_total_base numeric(18,2),
+ tr_tax_base numeric(18,2),
+ tr_shipping_base numeric(18,2),
+ ti_currency character(3),
+ ti_price_base numeric(18,2),
+ base_currency character(3),
+ geo_timezone character varying(64),
+ mkt_clickid character varying(128),
+ mkt_network character varying(64),
+ etl_tags character varying(500),
+ dvce_sent_tstamp timestamp with time zone,
+ refr_domain_userid character varying(36),
+ refr_dvce_tstamp timestamp with time zone,
+ domain_sessionid character(36),
+ derived_tstamp timestamp with time zone,
+ event_vendor character varying(1000),
+ event_name character varying(1000),
+ event_format character varying(128),
+ event_version character varying(128),
+ event_fingerprint character varying(128),
+ true_tstamp timestamp with time zone
+);
+ALTER TABLE ONLY public.product_analytics_events_experimental ATTACH PARTITION gitlab_partitions_static.product_analytics_events_experimental_40 FOR VALUES WITH (modulus 64, remainder 40);
+
+CREATE TABLE gitlab_partitions_static.product_analytics_events_experimental_41 (
+ id bigint DEFAULT nextval('public.product_analytics_events_experimental_id_seq'::regclass) NOT NULL,
+ project_id integer NOT NULL,
+ platform character varying(255),
+ etl_tstamp timestamp with time zone,
+ collector_tstamp timestamp with time zone NOT NULL,
+ dvce_created_tstamp timestamp with time zone,
+ event character varying(128),
+ event_id character(36) NOT NULL,
+ txn_id integer,
+ name_tracker character varying(128),
+ v_tracker character varying(100),
+ v_collector character varying(100) NOT NULL,
+ v_etl character varying(100) NOT NULL,
+ user_id character varying(255),
+ user_ipaddress character varying(45),
+ user_fingerprint character varying(50),
+ domain_userid character varying(36),
+ domain_sessionidx smallint,
+ network_userid character varying(38),
+ geo_country character(2),
+ geo_region character(3),
+ geo_city character varying(75),
+ geo_zipcode character varying(15),
+ geo_latitude double precision,
+ geo_longitude double precision,
+ geo_region_name character varying(100),
+ ip_isp character varying(100),
+ ip_organization character varying(100),
+ ip_domain character varying(100),
+ ip_netspeed character varying(100),
+ page_url text,
+ page_title character varying(2000),
+ page_referrer text,
+ page_urlscheme character varying(16),
+ page_urlhost character varying(255),
+ page_urlport integer,
+ page_urlpath character varying(3000),
+ page_urlquery character varying(6000),
+ page_urlfragment character varying(3000),
+ refr_urlscheme character varying(16),
+ refr_urlhost character varying(255),
+ refr_urlport integer,
+ refr_urlpath character varying(6000),
+ refr_urlquery character varying(6000),
+ refr_urlfragment character varying(3000),
+ refr_medium character varying(25),
+ refr_source character varying(50),
+ refr_term character varying(255),
+ mkt_medium character varying(255),
+ mkt_source character varying(255),
+ mkt_term character varying(255),
+ mkt_content character varying(500),
+ mkt_campaign character varying(255),
+ se_category character varying(1000),
+ se_action character varying(1000),
+ se_label character varying(1000),
+ se_property character varying(1000),
+ se_value double precision,
+ tr_orderid character varying(255),
+ tr_affiliation character varying(255),
+ tr_total numeric(18,2),
+ tr_tax numeric(18,2),
+ tr_shipping numeric(18,2),
+ tr_city character varying(255),
+ tr_state character varying(255),
+ tr_country character varying(255),
+ ti_orderid character varying(255),
+ ti_sku character varying(255),
+ ti_name character varying(255),
+ ti_category character varying(255),
+ ti_price numeric(18,2),
+ ti_quantity integer,
+ pp_xoffset_min integer,
+ pp_xoffset_max integer,
+ pp_yoffset_min integer,
+ pp_yoffset_max integer,
+ useragent character varying(1000),
+ br_name character varying(50),
+ br_family character varying(50),
+ br_version character varying(50),
+ br_type character varying(50),
+ br_renderengine character varying(50),
+ br_lang character varying(255),
+ br_features_pdf boolean,
+ br_features_flash boolean,
+ br_features_java boolean,
+ br_features_director boolean,
+ br_features_quicktime boolean,
+ br_features_realplayer boolean,
+ br_features_windowsmedia boolean,
+ br_features_gears boolean,
+ br_features_silverlight boolean,
+ br_cookies boolean,
+ br_colordepth character varying(12),
+ br_viewwidth integer,
+ br_viewheight integer,
+ os_name character varying(50),
+ os_family character varying(50),
+ os_manufacturer character varying(50),
+ os_timezone character varying(50),
+ dvce_type character varying(50),
+ dvce_ismobile boolean,
+ dvce_screenwidth integer,
+ dvce_screenheight integer,
+ doc_charset character varying(128),
+ doc_width integer,
+ doc_height integer,
+ tr_currency character(3),
+ tr_total_base numeric(18,2),
+ tr_tax_base numeric(18,2),
+ tr_shipping_base numeric(18,2),
+ ti_currency character(3),
+ ti_price_base numeric(18,2),
+ base_currency character(3),
+ geo_timezone character varying(64),
+ mkt_clickid character varying(128),
+ mkt_network character varying(64),
+ etl_tags character varying(500),
+ dvce_sent_tstamp timestamp with time zone,
+ refr_domain_userid character varying(36),
+ refr_dvce_tstamp timestamp with time zone,
+ domain_sessionid character(36),
+ derived_tstamp timestamp with time zone,
+ event_vendor character varying(1000),
+ event_name character varying(1000),
+ event_format character varying(128),
+ event_version character varying(128),
+ event_fingerprint character varying(128),
+ true_tstamp timestamp with time zone
+);
+ALTER TABLE ONLY public.product_analytics_events_experimental ATTACH PARTITION gitlab_partitions_static.product_analytics_events_experimental_41 FOR VALUES WITH (modulus 64, remainder 41);
+
+CREATE TABLE gitlab_partitions_static.product_analytics_events_experimental_42 (
+ id bigint DEFAULT nextval('public.product_analytics_events_experimental_id_seq'::regclass) NOT NULL,
+ project_id integer NOT NULL,
+ platform character varying(255),
+ etl_tstamp timestamp with time zone,
+ collector_tstamp timestamp with time zone NOT NULL,
+ dvce_created_tstamp timestamp with time zone,
+ event character varying(128),
+ event_id character(36) NOT NULL,
+ txn_id integer,
+ name_tracker character varying(128),
+ v_tracker character varying(100),
+ v_collector character varying(100) NOT NULL,
+ v_etl character varying(100) NOT NULL,
+ user_id character varying(255),
+ user_ipaddress character varying(45),
+ user_fingerprint character varying(50),
+ domain_userid character varying(36),
+ domain_sessionidx smallint,
+ network_userid character varying(38),
+ geo_country character(2),
+ geo_region character(3),
+ geo_city character varying(75),
+ geo_zipcode character varying(15),
+ geo_latitude double precision,
+ geo_longitude double precision,
+ geo_region_name character varying(100),
+ ip_isp character varying(100),
+ ip_organization character varying(100),
+ ip_domain character varying(100),
+ ip_netspeed character varying(100),
+ page_url text,
+ page_title character varying(2000),
+ page_referrer text,
+ page_urlscheme character varying(16),
+ page_urlhost character varying(255),
+ page_urlport integer,
+ page_urlpath character varying(3000),
+ page_urlquery character varying(6000),
+ page_urlfragment character varying(3000),
+ refr_urlscheme character varying(16),
+ refr_urlhost character varying(255),
+ refr_urlport integer,
+ refr_urlpath character varying(6000),
+ refr_urlquery character varying(6000),
+ refr_urlfragment character varying(3000),
+ refr_medium character varying(25),
+ refr_source character varying(50),
+ refr_term character varying(255),
+ mkt_medium character varying(255),
+ mkt_source character varying(255),
+ mkt_term character varying(255),
+ mkt_content character varying(500),
+ mkt_campaign character varying(255),
+ se_category character varying(1000),
+ se_action character varying(1000),
+ se_label character varying(1000),
+ se_property character varying(1000),
+ se_value double precision,
+ tr_orderid character varying(255),
+ tr_affiliation character varying(255),
+ tr_total numeric(18,2),
+ tr_tax numeric(18,2),
+ tr_shipping numeric(18,2),
+ tr_city character varying(255),
+ tr_state character varying(255),
+ tr_country character varying(255),
+ ti_orderid character varying(255),
+ ti_sku character varying(255),
+ ti_name character varying(255),
+ ti_category character varying(255),
+ ti_price numeric(18,2),
+ ti_quantity integer,
+ pp_xoffset_min integer,
+ pp_xoffset_max integer,
+ pp_yoffset_min integer,
+ pp_yoffset_max integer,
+ useragent character varying(1000),
+ br_name character varying(50),
+ br_family character varying(50),
+ br_version character varying(50),
+ br_type character varying(50),
+ br_renderengine character varying(50),
+ br_lang character varying(255),
+ br_features_pdf boolean,
+ br_features_flash boolean,
+ br_features_java boolean,
+ br_features_director boolean,
+ br_features_quicktime boolean,
+ br_features_realplayer boolean,
+ br_features_windowsmedia boolean,
+ br_features_gears boolean,
+ br_features_silverlight boolean,
+ br_cookies boolean,
+ br_colordepth character varying(12),
+ br_viewwidth integer,
+ br_viewheight integer,
+ os_name character varying(50),
+ os_family character varying(50),
+ os_manufacturer character varying(50),
+ os_timezone character varying(50),
+ dvce_type character varying(50),
+ dvce_ismobile boolean,
+ dvce_screenwidth integer,
+ dvce_screenheight integer,
+ doc_charset character varying(128),
+ doc_width integer,
+ doc_height integer,
+ tr_currency character(3),
+ tr_total_base numeric(18,2),
+ tr_tax_base numeric(18,2),
+ tr_shipping_base numeric(18,2),
+ ti_currency character(3),
+ ti_price_base numeric(18,2),
+ base_currency character(3),
+ geo_timezone character varying(64),
+ mkt_clickid character varying(128),
+ mkt_network character varying(64),
+ etl_tags character varying(500),
+ dvce_sent_tstamp timestamp with time zone,
+ refr_domain_userid character varying(36),
+ refr_dvce_tstamp timestamp with time zone,
+ domain_sessionid character(36),
+ derived_tstamp timestamp with time zone,
+ event_vendor character varying(1000),
+ event_name character varying(1000),
+ event_format character varying(128),
+ event_version character varying(128),
+ event_fingerprint character varying(128),
+ true_tstamp timestamp with time zone
+);
+ALTER TABLE ONLY public.product_analytics_events_experimental ATTACH PARTITION gitlab_partitions_static.product_analytics_events_experimental_42 FOR VALUES WITH (modulus 64, remainder 42);
+
+CREATE TABLE gitlab_partitions_static.product_analytics_events_experimental_43 (
+ id bigint DEFAULT nextval('public.product_analytics_events_experimental_id_seq'::regclass) NOT NULL,
+ project_id integer NOT NULL,
+ platform character varying(255),
+ etl_tstamp timestamp with time zone,
+ collector_tstamp timestamp with time zone NOT NULL,
+ dvce_created_tstamp timestamp with time zone,
+ event character varying(128),
+ event_id character(36) NOT NULL,
+ txn_id integer,
+ name_tracker character varying(128),
+ v_tracker character varying(100),
+ v_collector character varying(100) NOT NULL,
+ v_etl character varying(100) NOT NULL,
+ user_id character varying(255),
+ user_ipaddress character varying(45),
+ user_fingerprint character varying(50),
+ domain_userid character varying(36),
+ domain_sessionidx smallint,
+ network_userid character varying(38),
+ geo_country character(2),
+ geo_region character(3),
+ geo_city character varying(75),
+ geo_zipcode character varying(15),
+ geo_latitude double precision,
+ geo_longitude double precision,
+ geo_region_name character varying(100),
+ ip_isp character varying(100),
+ ip_organization character varying(100),
+ ip_domain character varying(100),
+ ip_netspeed character varying(100),
+ page_url text,
+ page_title character varying(2000),
+ page_referrer text,
+ page_urlscheme character varying(16),
+ page_urlhost character varying(255),
+ page_urlport integer,
+ page_urlpath character varying(3000),
+ page_urlquery character varying(6000),
+ page_urlfragment character varying(3000),
+ refr_urlscheme character varying(16),
+ refr_urlhost character varying(255),
+ refr_urlport integer,
+ refr_urlpath character varying(6000),
+ refr_urlquery character varying(6000),
+ refr_urlfragment character varying(3000),
+ refr_medium character varying(25),
+ refr_source character varying(50),
+ refr_term character varying(255),
+ mkt_medium character varying(255),
+ mkt_source character varying(255),
+ mkt_term character varying(255),
+ mkt_content character varying(500),
+ mkt_campaign character varying(255),
+ se_category character varying(1000),
+ se_action character varying(1000),
+ se_label character varying(1000),
+ se_property character varying(1000),
+ se_value double precision,
+ tr_orderid character varying(255),
+ tr_affiliation character varying(255),
+ tr_total numeric(18,2),
+ tr_tax numeric(18,2),
+ tr_shipping numeric(18,2),
+ tr_city character varying(255),
+ tr_state character varying(255),
+ tr_country character varying(255),
+ ti_orderid character varying(255),
+ ti_sku character varying(255),
+ ti_name character varying(255),
+ ti_category character varying(255),
+ ti_price numeric(18,2),
+ ti_quantity integer,
+ pp_xoffset_min integer,
+ pp_xoffset_max integer,
+ pp_yoffset_min integer,
+ pp_yoffset_max integer,
+ useragent character varying(1000),
+ br_name character varying(50),
+ br_family character varying(50),
+ br_version character varying(50),
+ br_type character varying(50),
+ br_renderengine character varying(50),
+ br_lang character varying(255),
+ br_features_pdf boolean,
+ br_features_flash boolean,
+ br_features_java boolean,
+ br_features_director boolean,
+ br_features_quicktime boolean,
+ br_features_realplayer boolean,
+ br_features_windowsmedia boolean,
+ br_features_gears boolean,
+ br_features_silverlight boolean,
+ br_cookies boolean,
+ br_colordepth character varying(12),
+ br_viewwidth integer,
+ br_viewheight integer,
+ os_name character varying(50),
+ os_family character varying(50),
+ os_manufacturer character varying(50),
+ os_timezone character varying(50),
+ dvce_type character varying(50),
+ dvce_ismobile boolean,
+ dvce_screenwidth integer,
+ dvce_screenheight integer,
+ doc_charset character varying(128),
+ doc_width integer,
+ doc_height integer,
+ tr_currency character(3),
+ tr_total_base numeric(18,2),
+ tr_tax_base numeric(18,2),
+ tr_shipping_base numeric(18,2),
+ ti_currency character(3),
+ ti_price_base numeric(18,2),
+ base_currency character(3),
+ geo_timezone character varying(64),
+ mkt_clickid character varying(128),
+ mkt_network character varying(64),
+ etl_tags character varying(500),
+ dvce_sent_tstamp timestamp with time zone,
+ refr_domain_userid character varying(36),
+ refr_dvce_tstamp timestamp with time zone,
+ domain_sessionid character(36),
+ derived_tstamp timestamp with time zone,
+ event_vendor character varying(1000),
+ event_name character varying(1000),
+ event_format character varying(128),
+ event_version character varying(128),
+ event_fingerprint character varying(128),
+ true_tstamp timestamp with time zone
+);
+ALTER TABLE ONLY public.product_analytics_events_experimental ATTACH PARTITION gitlab_partitions_static.product_analytics_events_experimental_43 FOR VALUES WITH (modulus 64, remainder 43);
+
+CREATE TABLE gitlab_partitions_static.product_analytics_events_experimental_44 (
+ id bigint DEFAULT nextval('public.product_analytics_events_experimental_id_seq'::regclass) NOT NULL,
+ project_id integer NOT NULL,
+ platform character varying(255),
+ etl_tstamp timestamp with time zone,
+ collector_tstamp timestamp with time zone NOT NULL,
+ dvce_created_tstamp timestamp with time zone,
+ event character varying(128),
+ event_id character(36) NOT NULL,
+ txn_id integer,
+ name_tracker character varying(128),
+ v_tracker character varying(100),
+ v_collector character varying(100) NOT NULL,
+ v_etl character varying(100) NOT NULL,
+ user_id character varying(255),
+ user_ipaddress character varying(45),
+ user_fingerprint character varying(50),
+ domain_userid character varying(36),
+ domain_sessionidx smallint,
+ network_userid character varying(38),
+ geo_country character(2),
+ geo_region character(3),
+ geo_city character varying(75),
+ geo_zipcode character varying(15),
+ geo_latitude double precision,
+ geo_longitude double precision,
+ geo_region_name character varying(100),
+ ip_isp character varying(100),
+ ip_organization character varying(100),
+ ip_domain character varying(100),
+ ip_netspeed character varying(100),
+ page_url text,
+ page_title character varying(2000),
+ page_referrer text,
+ page_urlscheme character varying(16),
+ page_urlhost character varying(255),
+ page_urlport integer,
+ page_urlpath character varying(3000),
+ page_urlquery character varying(6000),
+ page_urlfragment character varying(3000),
+ refr_urlscheme character varying(16),
+ refr_urlhost character varying(255),
+ refr_urlport integer,
+ refr_urlpath character varying(6000),
+ refr_urlquery character varying(6000),
+ refr_urlfragment character varying(3000),
+ refr_medium character varying(25),
+ refr_source character varying(50),
+ refr_term character varying(255),
+ mkt_medium character varying(255),
+ mkt_source character varying(255),
+ mkt_term character varying(255),
+ mkt_content character varying(500),
+ mkt_campaign character varying(255),
+ se_category character varying(1000),
+ se_action character varying(1000),
+ se_label character varying(1000),
+ se_property character varying(1000),
+ se_value double precision,
+ tr_orderid character varying(255),
+ tr_affiliation character varying(255),
+ tr_total numeric(18,2),
+ tr_tax numeric(18,2),
+ tr_shipping numeric(18,2),
+ tr_city character varying(255),
+ tr_state character varying(255),
+ tr_country character varying(255),
+ ti_orderid character varying(255),
+ ti_sku character varying(255),
+ ti_name character varying(255),
+ ti_category character varying(255),
+ ti_price numeric(18,2),
+ ti_quantity integer,
+ pp_xoffset_min integer,
+ pp_xoffset_max integer,
+ pp_yoffset_min integer,
+ pp_yoffset_max integer,
+ useragent character varying(1000),
+ br_name character varying(50),
+ br_family character varying(50),
+ br_version character varying(50),
+ br_type character varying(50),
+ br_renderengine character varying(50),
+ br_lang character varying(255),
+ br_features_pdf boolean,
+ br_features_flash boolean,
+ br_features_java boolean,
+ br_features_director boolean,
+ br_features_quicktime boolean,
+ br_features_realplayer boolean,
+ br_features_windowsmedia boolean,
+ br_features_gears boolean,
+ br_features_silverlight boolean,
+ br_cookies boolean,
+ br_colordepth character varying(12),
+ br_viewwidth integer,
+ br_viewheight integer,
+ os_name character varying(50),
+ os_family character varying(50),
+ os_manufacturer character varying(50),
+ os_timezone character varying(50),
+ dvce_type character varying(50),
+ dvce_ismobile boolean,
+ dvce_screenwidth integer,
+ dvce_screenheight integer,
+ doc_charset character varying(128),
+ doc_width integer,
+ doc_height integer,
+ tr_currency character(3),
+ tr_total_base numeric(18,2),
+ tr_tax_base numeric(18,2),
+ tr_shipping_base numeric(18,2),
+ ti_currency character(3),
+ ti_price_base numeric(18,2),
+ base_currency character(3),
+ geo_timezone character varying(64),
+ mkt_clickid character varying(128),
+ mkt_network character varying(64),
+ etl_tags character varying(500),
+ dvce_sent_tstamp timestamp with time zone,
+ refr_domain_userid character varying(36),
+ refr_dvce_tstamp timestamp with time zone,
+ domain_sessionid character(36),
+ derived_tstamp timestamp with time zone,
+ event_vendor character varying(1000),
+ event_name character varying(1000),
+ event_format character varying(128),
+ event_version character varying(128),
+ event_fingerprint character varying(128),
+ true_tstamp timestamp with time zone
+);
+ALTER TABLE ONLY public.product_analytics_events_experimental ATTACH PARTITION gitlab_partitions_static.product_analytics_events_experimental_44 FOR VALUES WITH (modulus 64, remainder 44);
+
+CREATE TABLE gitlab_partitions_static.product_analytics_events_experimental_45 (
+ id bigint DEFAULT nextval('public.product_analytics_events_experimental_id_seq'::regclass) NOT NULL,
+ project_id integer NOT NULL,
+ platform character varying(255),
+ etl_tstamp timestamp with time zone,
+ collector_tstamp timestamp with time zone NOT NULL,
+ dvce_created_tstamp timestamp with time zone,
+ event character varying(128),
+ event_id character(36) NOT NULL,
+ txn_id integer,
+ name_tracker character varying(128),
+ v_tracker character varying(100),
+ v_collector character varying(100) NOT NULL,
+ v_etl character varying(100) NOT NULL,
+ user_id character varying(255),
+ user_ipaddress character varying(45),
+ user_fingerprint character varying(50),
+ domain_userid character varying(36),
+ domain_sessionidx smallint,
+ network_userid character varying(38),
+ geo_country character(2),
+ geo_region character(3),
+ geo_city character varying(75),
+ geo_zipcode character varying(15),
+ geo_latitude double precision,
+ geo_longitude double precision,
+ geo_region_name character varying(100),
+ ip_isp character varying(100),
+ ip_organization character varying(100),
+ ip_domain character varying(100),
+ ip_netspeed character varying(100),
+ page_url text,
+ page_title character varying(2000),
+ page_referrer text,
+ page_urlscheme character varying(16),
+ page_urlhost character varying(255),
+ page_urlport integer,
+ page_urlpath character varying(3000),
+ page_urlquery character varying(6000),
+ page_urlfragment character varying(3000),
+ refr_urlscheme character varying(16),
+ refr_urlhost character varying(255),
+ refr_urlport integer,
+ refr_urlpath character varying(6000),
+ refr_urlquery character varying(6000),
+ refr_urlfragment character varying(3000),
+ refr_medium character varying(25),
+ refr_source character varying(50),
+ refr_term character varying(255),
+ mkt_medium character varying(255),
+ mkt_source character varying(255),
+ mkt_term character varying(255),
+ mkt_content character varying(500),
+ mkt_campaign character varying(255),
+ se_category character varying(1000),
+ se_action character varying(1000),
+ se_label character varying(1000),
+ se_property character varying(1000),
+ se_value double precision,
+ tr_orderid character varying(255),
+ tr_affiliation character varying(255),
+ tr_total numeric(18,2),
+ tr_tax numeric(18,2),
+ tr_shipping numeric(18,2),
+ tr_city character varying(255),
+ tr_state character varying(255),
+ tr_country character varying(255),
+ ti_orderid character varying(255),
+ ti_sku character varying(255),
+ ti_name character varying(255),
+ ti_category character varying(255),
+ ti_price numeric(18,2),
+ ti_quantity integer,
+ pp_xoffset_min integer,
+ pp_xoffset_max integer,
+ pp_yoffset_min integer,
+ pp_yoffset_max integer,
+ useragent character varying(1000),
+ br_name character varying(50),
+ br_family character varying(50),
+ br_version character varying(50),
+ br_type character varying(50),
+ br_renderengine character varying(50),
+ br_lang character varying(255),
+ br_features_pdf boolean,
+ br_features_flash boolean,
+ br_features_java boolean,
+ br_features_director boolean,
+ br_features_quicktime boolean,
+ br_features_realplayer boolean,
+ br_features_windowsmedia boolean,
+ br_features_gears boolean,
+ br_features_silverlight boolean,
+ br_cookies boolean,
+ br_colordepth character varying(12),
+ br_viewwidth integer,
+ br_viewheight integer,
+ os_name character varying(50),
+ os_family character varying(50),
+ os_manufacturer character varying(50),
+ os_timezone character varying(50),
+ dvce_type character varying(50),
+ dvce_ismobile boolean,
+ dvce_screenwidth integer,
+ dvce_screenheight integer,
+ doc_charset character varying(128),
+ doc_width integer,
+ doc_height integer,
+ tr_currency character(3),
+ tr_total_base numeric(18,2),
+ tr_tax_base numeric(18,2),
+ tr_shipping_base numeric(18,2),
+ ti_currency character(3),
+ ti_price_base numeric(18,2),
+ base_currency character(3),
+ geo_timezone character varying(64),
+ mkt_clickid character varying(128),
+ mkt_network character varying(64),
+ etl_tags character varying(500),
+ dvce_sent_tstamp timestamp with time zone,
+ refr_domain_userid character varying(36),
+ refr_dvce_tstamp timestamp with time zone,
+ domain_sessionid character(36),
+ derived_tstamp timestamp with time zone,
+ event_vendor character varying(1000),
+ event_name character varying(1000),
+ event_format character varying(128),
+ event_version character varying(128),
+ event_fingerprint character varying(128),
+ true_tstamp timestamp with time zone
+);
+ALTER TABLE ONLY public.product_analytics_events_experimental ATTACH PARTITION gitlab_partitions_static.product_analytics_events_experimental_45 FOR VALUES WITH (modulus 64, remainder 45);
+
+CREATE TABLE gitlab_partitions_static.product_analytics_events_experimental_46 (
+ id bigint DEFAULT nextval('public.product_analytics_events_experimental_id_seq'::regclass) NOT NULL,
+ project_id integer NOT NULL,
+ platform character varying(255),
+ etl_tstamp timestamp with time zone,
+ collector_tstamp timestamp with time zone NOT NULL,
+ dvce_created_tstamp timestamp with time zone,
+ event character varying(128),
+ event_id character(36) NOT NULL,
+ txn_id integer,
+ name_tracker character varying(128),
+ v_tracker character varying(100),
+ v_collector character varying(100) NOT NULL,
+ v_etl character varying(100) NOT NULL,
+ user_id character varying(255),
+ user_ipaddress character varying(45),
+ user_fingerprint character varying(50),
+ domain_userid character varying(36),
+ domain_sessionidx smallint,
+ network_userid character varying(38),
+ geo_country character(2),
+ geo_region character(3),
+ geo_city character varying(75),
+ geo_zipcode character varying(15),
+ geo_latitude double precision,
+ geo_longitude double precision,
+ geo_region_name character varying(100),
+ ip_isp character varying(100),
+ ip_organization character varying(100),
+ ip_domain character varying(100),
+ ip_netspeed character varying(100),
+ page_url text,
+ page_title character varying(2000),
+ page_referrer text,
+ page_urlscheme character varying(16),
+ page_urlhost character varying(255),
+ page_urlport integer,
+ page_urlpath character varying(3000),
+ page_urlquery character varying(6000),
+ page_urlfragment character varying(3000),
+ refr_urlscheme character varying(16),
+ refr_urlhost character varying(255),
+ refr_urlport integer,
+ refr_urlpath character varying(6000),
+ refr_urlquery character varying(6000),
+ refr_urlfragment character varying(3000),
+ refr_medium character varying(25),
+ refr_source character varying(50),
+ refr_term character varying(255),
+ mkt_medium character varying(255),
+ mkt_source character varying(255),
+ mkt_term character varying(255),
+ mkt_content character varying(500),
+ mkt_campaign character varying(255),
+ se_category character varying(1000),
+ se_action character varying(1000),
+ se_label character varying(1000),
+ se_property character varying(1000),
+ se_value double precision,
+ tr_orderid character varying(255),
+ tr_affiliation character varying(255),
+ tr_total numeric(18,2),
+ tr_tax numeric(18,2),
+ tr_shipping numeric(18,2),
+ tr_city character varying(255),
+ tr_state character varying(255),
+ tr_country character varying(255),
+ ti_orderid character varying(255),
+ ti_sku character varying(255),
+ ti_name character varying(255),
+ ti_category character varying(255),
+ ti_price numeric(18,2),
+ ti_quantity integer,
+ pp_xoffset_min integer,
+ pp_xoffset_max integer,
+ pp_yoffset_min integer,
+ pp_yoffset_max integer,
+ useragent character varying(1000),
+ br_name character varying(50),
+ br_family character varying(50),
+ br_version character varying(50),
+ br_type character varying(50),
+ br_renderengine character varying(50),
+ br_lang character varying(255),
+ br_features_pdf boolean,
+ br_features_flash boolean,
+ br_features_java boolean,
+ br_features_director boolean,
+ br_features_quicktime boolean,
+ br_features_realplayer boolean,
+ br_features_windowsmedia boolean,
+ br_features_gears boolean,
+ br_features_silverlight boolean,
+ br_cookies boolean,
+ br_colordepth character varying(12),
+ br_viewwidth integer,
+ br_viewheight integer,
+ os_name character varying(50),
+ os_family character varying(50),
+ os_manufacturer character varying(50),
+ os_timezone character varying(50),
+ dvce_type character varying(50),
+ dvce_ismobile boolean,
+ dvce_screenwidth integer,
+ dvce_screenheight integer,
+ doc_charset character varying(128),
+ doc_width integer,
+ doc_height integer,
+ tr_currency character(3),
+ tr_total_base numeric(18,2),
+ tr_tax_base numeric(18,2),
+ tr_shipping_base numeric(18,2),
+ ti_currency character(3),
+ ti_price_base numeric(18,2),
+ base_currency character(3),
+ geo_timezone character varying(64),
+ mkt_clickid character varying(128),
+ mkt_network character varying(64),
+ etl_tags character varying(500),
+ dvce_sent_tstamp timestamp with time zone,
+ refr_domain_userid character varying(36),
+ refr_dvce_tstamp timestamp with time zone,
+ domain_sessionid character(36),
+ derived_tstamp timestamp with time zone,
+ event_vendor character varying(1000),
+ event_name character varying(1000),
+ event_format character varying(128),
+ event_version character varying(128),
+ event_fingerprint character varying(128),
+ true_tstamp timestamp with time zone
+);
+ALTER TABLE ONLY public.product_analytics_events_experimental ATTACH PARTITION gitlab_partitions_static.product_analytics_events_experimental_46 FOR VALUES WITH (modulus 64, remainder 46);
+
+CREATE TABLE gitlab_partitions_static.product_analytics_events_experimental_47 (
+ id bigint DEFAULT nextval('public.product_analytics_events_experimental_id_seq'::regclass) NOT NULL,
+ project_id integer NOT NULL,
+ platform character varying(255),
+ etl_tstamp timestamp with time zone,
+ collector_tstamp timestamp with time zone NOT NULL,
+ dvce_created_tstamp timestamp with time zone,
+ event character varying(128),
+ event_id character(36) NOT NULL,
+ txn_id integer,
+ name_tracker character varying(128),
+ v_tracker character varying(100),
+ v_collector character varying(100) NOT NULL,
+ v_etl character varying(100) NOT NULL,
+ user_id character varying(255),
+ user_ipaddress character varying(45),
+ user_fingerprint character varying(50),
+ domain_userid character varying(36),
+ domain_sessionidx smallint,
+ network_userid character varying(38),
+ geo_country character(2),
+ geo_region character(3),
+ geo_city character varying(75),
+ geo_zipcode character varying(15),
+ geo_latitude double precision,
+ geo_longitude double precision,
+ geo_region_name character varying(100),
+ ip_isp character varying(100),
+ ip_organization character varying(100),
+ ip_domain character varying(100),
+ ip_netspeed character varying(100),
+ page_url text,
+ page_title character varying(2000),
+ page_referrer text,
+ page_urlscheme character varying(16),
+ page_urlhost character varying(255),
+ page_urlport integer,
+ page_urlpath character varying(3000),
+ page_urlquery character varying(6000),
+ page_urlfragment character varying(3000),
+ refr_urlscheme character varying(16),
+ refr_urlhost character varying(255),
+ refr_urlport integer,
+ refr_urlpath character varying(6000),
+ refr_urlquery character varying(6000),
+ refr_urlfragment character varying(3000),
+ refr_medium character varying(25),
+ refr_source character varying(50),
+ refr_term character varying(255),
+ mkt_medium character varying(255),
+ mkt_source character varying(255),
+ mkt_term character varying(255),
+ mkt_content character varying(500),
+ mkt_campaign character varying(255),
+ se_category character varying(1000),
+ se_action character varying(1000),
+ se_label character varying(1000),
+ se_property character varying(1000),
+ se_value double precision,
+ tr_orderid character varying(255),
+ tr_affiliation character varying(255),
+ tr_total numeric(18,2),
+ tr_tax numeric(18,2),
+ tr_shipping numeric(18,2),
+ tr_city character varying(255),
+ tr_state character varying(255),
+ tr_country character varying(255),
+ ti_orderid character varying(255),
+ ti_sku character varying(255),
+ ti_name character varying(255),
+ ti_category character varying(255),
+ ti_price numeric(18,2),
+ ti_quantity integer,
+ pp_xoffset_min integer,
+ pp_xoffset_max integer,
+ pp_yoffset_min integer,
+ pp_yoffset_max integer,
+ useragent character varying(1000),
+ br_name character varying(50),
+ br_family character varying(50),
+ br_version character varying(50),
+ br_type character varying(50),
+ br_renderengine character varying(50),
+ br_lang character varying(255),
+ br_features_pdf boolean,
+ br_features_flash boolean,
+ br_features_java boolean,
+ br_features_director boolean,
+ br_features_quicktime boolean,
+ br_features_realplayer boolean,
+ br_features_windowsmedia boolean,
+ br_features_gears boolean,
+ br_features_silverlight boolean,
+ br_cookies boolean,
+ br_colordepth character varying(12),
+ br_viewwidth integer,
+ br_viewheight integer,
+ os_name character varying(50),
+ os_family character varying(50),
+ os_manufacturer character varying(50),
+ os_timezone character varying(50),
+ dvce_type character varying(50),
+ dvce_ismobile boolean,
+ dvce_screenwidth integer,
+ dvce_screenheight integer,
+ doc_charset character varying(128),
+ doc_width integer,
+ doc_height integer,
+ tr_currency character(3),
+ tr_total_base numeric(18,2),
+ tr_tax_base numeric(18,2),
+ tr_shipping_base numeric(18,2),
+ ti_currency character(3),
+ ti_price_base numeric(18,2),
+ base_currency character(3),
+ geo_timezone character varying(64),
+ mkt_clickid character varying(128),
+ mkt_network character varying(64),
+ etl_tags character varying(500),
+ dvce_sent_tstamp timestamp with time zone,
+ refr_domain_userid character varying(36),
+ refr_dvce_tstamp timestamp with time zone,
+ domain_sessionid character(36),
+ derived_tstamp timestamp with time zone,
+ event_vendor character varying(1000),
+ event_name character varying(1000),
+ event_format character varying(128),
+ event_version character varying(128),
+ event_fingerprint character varying(128),
+ true_tstamp timestamp with time zone
+);
+ALTER TABLE ONLY public.product_analytics_events_experimental ATTACH PARTITION gitlab_partitions_static.product_analytics_events_experimental_47 FOR VALUES WITH (modulus 64, remainder 47);
+
+CREATE TABLE gitlab_partitions_static.product_analytics_events_experimental_48 (
+ id bigint DEFAULT nextval('public.product_analytics_events_experimental_id_seq'::regclass) NOT NULL,
+ project_id integer NOT NULL,
+ platform character varying(255),
+ etl_tstamp timestamp with time zone,
+ collector_tstamp timestamp with time zone NOT NULL,
+ dvce_created_tstamp timestamp with time zone,
+ event character varying(128),
+ event_id character(36) NOT NULL,
+ txn_id integer,
+ name_tracker character varying(128),
+ v_tracker character varying(100),
+ v_collector character varying(100) NOT NULL,
+ v_etl character varying(100) NOT NULL,
+ user_id character varying(255),
+ user_ipaddress character varying(45),
+ user_fingerprint character varying(50),
+ domain_userid character varying(36),
+ domain_sessionidx smallint,
+ network_userid character varying(38),
+ geo_country character(2),
+ geo_region character(3),
+ geo_city character varying(75),
+ geo_zipcode character varying(15),
+ geo_latitude double precision,
+ geo_longitude double precision,
+ geo_region_name character varying(100),
+ ip_isp character varying(100),
+ ip_organization character varying(100),
+ ip_domain character varying(100),
+ ip_netspeed character varying(100),
+ page_url text,
+ page_title character varying(2000),
+ page_referrer text,
+ page_urlscheme character varying(16),
+ page_urlhost character varying(255),
+ page_urlport integer,
+ page_urlpath character varying(3000),
+ page_urlquery character varying(6000),
+ page_urlfragment character varying(3000),
+ refr_urlscheme character varying(16),
+ refr_urlhost character varying(255),
+ refr_urlport integer,
+ refr_urlpath character varying(6000),
+ refr_urlquery character varying(6000),
+ refr_urlfragment character varying(3000),
+ refr_medium character varying(25),
+ refr_source character varying(50),
+ refr_term character varying(255),
+ mkt_medium character varying(255),
+ mkt_source character varying(255),
+ mkt_term character varying(255),
+ mkt_content character varying(500),
+ mkt_campaign character varying(255),
+ se_category character varying(1000),
+ se_action character varying(1000),
+ se_label character varying(1000),
+ se_property character varying(1000),
+ se_value double precision,
+ tr_orderid character varying(255),
+ tr_affiliation character varying(255),
+ tr_total numeric(18,2),
+ tr_tax numeric(18,2),
+ tr_shipping numeric(18,2),
+ tr_city character varying(255),
+ tr_state character varying(255),
+ tr_country character varying(255),
+ ti_orderid character varying(255),
+ ti_sku character varying(255),
+ ti_name character varying(255),
+ ti_category character varying(255),
+ ti_price numeric(18,2),
+ ti_quantity integer,
+ pp_xoffset_min integer,
+ pp_xoffset_max integer,
+ pp_yoffset_min integer,
+ pp_yoffset_max integer,
+ useragent character varying(1000),
+ br_name character varying(50),
+ br_family character varying(50),
+ br_version character varying(50),
+ br_type character varying(50),
+ br_renderengine character varying(50),
+ br_lang character varying(255),
+ br_features_pdf boolean,
+ br_features_flash boolean,
+ br_features_java boolean,
+ br_features_director boolean,
+ br_features_quicktime boolean,
+ br_features_realplayer boolean,
+ br_features_windowsmedia boolean,
+ br_features_gears boolean,
+ br_features_silverlight boolean,
+ br_cookies boolean,
+ br_colordepth character varying(12),
+ br_viewwidth integer,
+ br_viewheight integer,
+ os_name character varying(50),
+ os_family character varying(50),
+ os_manufacturer character varying(50),
+ os_timezone character varying(50),
+ dvce_type character varying(50),
+ dvce_ismobile boolean,
+ dvce_screenwidth integer,
+ dvce_screenheight integer,
+ doc_charset character varying(128),
+ doc_width integer,
+ doc_height integer,
+ tr_currency character(3),
+ tr_total_base numeric(18,2),
+ tr_tax_base numeric(18,2),
+ tr_shipping_base numeric(18,2),
+ ti_currency character(3),
+ ti_price_base numeric(18,2),
+ base_currency character(3),
+ geo_timezone character varying(64),
+ mkt_clickid character varying(128),
+ mkt_network character varying(64),
+ etl_tags character varying(500),
+ dvce_sent_tstamp timestamp with time zone,
+ refr_domain_userid character varying(36),
+ refr_dvce_tstamp timestamp with time zone,
+ domain_sessionid character(36),
+ derived_tstamp timestamp with time zone,
+ event_vendor character varying(1000),
+ event_name character varying(1000),
+ event_format character varying(128),
+ event_version character varying(128),
+ event_fingerprint character varying(128),
+ true_tstamp timestamp with time zone
+);
+ALTER TABLE ONLY public.product_analytics_events_experimental ATTACH PARTITION gitlab_partitions_static.product_analytics_events_experimental_48 FOR VALUES WITH (modulus 64, remainder 48);
+
+CREATE TABLE gitlab_partitions_static.product_analytics_events_experimental_49 (
+ id bigint DEFAULT nextval('public.product_analytics_events_experimental_id_seq'::regclass) NOT NULL,
+ project_id integer NOT NULL,
+ platform character varying(255),
+ etl_tstamp timestamp with time zone,
+ collector_tstamp timestamp with time zone NOT NULL,
+ dvce_created_tstamp timestamp with time zone,
+ event character varying(128),
+ event_id character(36) NOT NULL,
+ txn_id integer,
+ name_tracker character varying(128),
+ v_tracker character varying(100),
+ v_collector character varying(100) NOT NULL,
+ v_etl character varying(100) NOT NULL,
+ user_id character varying(255),
+ user_ipaddress character varying(45),
+ user_fingerprint character varying(50),
+ domain_userid character varying(36),
+ domain_sessionidx smallint,
+ network_userid character varying(38),
+ geo_country character(2),
+ geo_region character(3),
+ geo_city character varying(75),
+ geo_zipcode character varying(15),
+ geo_latitude double precision,
+ geo_longitude double precision,
+ geo_region_name character varying(100),
+ ip_isp character varying(100),
+ ip_organization character varying(100),
+ ip_domain character varying(100),
+ ip_netspeed character varying(100),
+ page_url text,
+ page_title character varying(2000),
+ page_referrer text,
+ page_urlscheme character varying(16),
+ page_urlhost character varying(255),
+ page_urlport integer,
+ page_urlpath character varying(3000),
+ page_urlquery character varying(6000),
+ page_urlfragment character varying(3000),
+ refr_urlscheme character varying(16),
+ refr_urlhost character varying(255),
+ refr_urlport integer,
+ refr_urlpath character varying(6000),
+ refr_urlquery character varying(6000),
+ refr_urlfragment character varying(3000),
+ refr_medium character varying(25),
+ refr_source character varying(50),
+ refr_term character varying(255),
+ mkt_medium character varying(255),
+ mkt_source character varying(255),
+ mkt_term character varying(255),
+ mkt_content character varying(500),
+ mkt_campaign character varying(255),
+ se_category character varying(1000),
+ se_action character varying(1000),
+ se_label character varying(1000),
+ se_property character varying(1000),
+ se_value double precision,
+ tr_orderid character varying(255),
+ tr_affiliation character varying(255),
+ tr_total numeric(18,2),
+ tr_tax numeric(18,2),
+ tr_shipping numeric(18,2),
+ tr_city character varying(255),
+ tr_state character varying(255),
+ tr_country character varying(255),
+ ti_orderid character varying(255),
+ ti_sku character varying(255),
+ ti_name character varying(255),
+ ti_category character varying(255),
+ ti_price numeric(18,2),
+ ti_quantity integer,
+ pp_xoffset_min integer,
+ pp_xoffset_max integer,
+ pp_yoffset_min integer,
+ pp_yoffset_max integer,
+ useragent character varying(1000),
+ br_name character varying(50),
+ br_family character varying(50),
+ br_version character varying(50),
+ br_type character varying(50),
+ br_renderengine character varying(50),
+ br_lang character varying(255),
+ br_features_pdf boolean,
+ br_features_flash boolean,
+ br_features_java boolean,
+ br_features_director boolean,
+ br_features_quicktime boolean,
+ br_features_realplayer boolean,
+ br_features_windowsmedia boolean,
+ br_features_gears boolean,
+ br_features_silverlight boolean,
+ br_cookies boolean,
+ br_colordepth character varying(12),
+ br_viewwidth integer,
+ br_viewheight integer,
+ os_name character varying(50),
+ os_family character varying(50),
+ os_manufacturer character varying(50),
+ os_timezone character varying(50),
+ dvce_type character varying(50),
+ dvce_ismobile boolean,
+ dvce_screenwidth integer,
+ dvce_screenheight integer,
+ doc_charset character varying(128),
+ doc_width integer,
+ doc_height integer,
+ tr_currency character(3),
+ tr_total_base numeric(18,2),
+ tr_tax_base numeric(18,2),
+ tr_shipping_base numeric(18,2),
+ ti_currency character(3),
+ ti_price_base numeric(18,2),
+ base_currency character(3),
+ geo_timezone character varying(64),
+ mkt_clickid character varying(128),
+ mkt_network character varying(64),
+ etl_tags character varying(500),
+ dvce_sent_tstamp timestamp with time zone,
+ refr_domain_userid character varying(36),
+ refr_dvce_tstamp timestamp with time zone,
+ domain_sessionid character(36),
+ derived_tstamp timestamp with time zone,
+ event_vendor character varying(1000),
+ event_name character varying(1000),
+ event_format character varying(128),
+ event_version character varying(128),
+ event_fingerprint character varying(128),
+ true_tstamp timestamp with time zone
+);
+ALTER TABLE ONLY public.product_analytics_events_experimental ATTACH PARTITION gitlab_partitions_static.product_analytics_events_experimental_49 FOR VALUES WITH (modulus 64, remainder 49);
+
+CREATE TABLE gitlab_partitions_static.product_analytics_events_experimental_50 (
+ id bigint DEFAULT nextval('public.product_analytics_events_experimental_id_seq'::regclass) NOT NULL,
+ project_id integer NOT NULL,
+ platform character varying(255),
+ etl_tstamp timestamp with time zone,
+ collector_tstamp timestamp with time zone NOT NULL,
+ dvce_created_tstamp timestamp with time zone,
+ event character varying(128),
+ event_id character(36) NOT NULL,
+ txn_id integer,
+ name_tracker character varying(128),
+ v_tracker character varying(100),
+ v_collector character varying(100) NOT NULL,
+ v_etl character varying(100) NOT NULL,
+ user_id character varying(255),
+ user_ipaddress character varying(45),
+ user_fingerprint character varying(50),
+ domain_userid character varying(36),
+ domain_sessionidx smallint,
+ network_userid character varying(38),
+ geo_country character(2),
+ geo_region character(3),
+ geo_city character varying(75),
+ geo_zipcode character varying(15),
+ geo_latitude double precision,
+ geo_longitude double precision,
+ geo_region_name character varying(100),
+ ip_isp character varying(100),
+ ip_organization character varying(100),
+ ip_domain character varying(100),
+ ip_netspeed character varying(100),
+ page_url text,
+ page_title character varying(2000),
+ page_referrer text,
+ page_urlscheme character varying(16),
+ page_urlhost character varying(255),
+ page_urlport integer,
+ page_urlpath character varying(3000),
+ page_urlquery character varying(6000),
+ page_urlfragment character varying(3000),
+ refr_urlscheme character varying(16),
+ refr_urlhost character varying(255),
+ refr_urlport integer,
+ refr_urlpath character varying(6000),
+ refr_urlquery character varying(6000),
+ refr_urlfragment character varying(3000),
+ refr_medium character varying(25),
+ refr_source character varying(50),
+ refr_term character varying(255),
+ mkt_medium character varying(255),
+ mkt_source character varying(255),
+ mkt_term character varying(255),
+ mkt_content character varying(500),
+ mkt_campaign character varying(255),
+ se_category character varying(1000),
+ se_action character varying(1000),
+ se_label character varying(1000),
+ se_property character varying(1000),
+ se_value double precision,
+ tr_orderid character varying(255),
+ tr_affiliation character varying(255),
+ tr_total numeric(18,2),
+ tr_tax numeric(18,2),
+ tr_shipping numeric(18,2),
+ tr_city character varying(255),
+ tr_state character varying(255),
+ tr_country character varying(255),
+ ti_orderid character varying(255),
+ ti_sku character varying(255),
+ ti_name character varying(255),
+ ti_category character varying(255),
+ ti_price numeric(18,2),
+ ti_quantity integer,
+ pp_xoffset_min integer,
+ pp_xoffset_max integer,
+ pp_yoffset_min integer,
+ pp_yoffset_max integer,
+ useragent character varying(1000),
+ br_name character varying(50),
+ br_family character varying(50),
+ br_version character varying(50),
+ br_type character varying(50),
+ br_renderengine character varying(50),
+ br_lang character varying(255),
+ br_features_pdf boolean,
+ br_features_flash boolean,
+ br_features_java boolean,
+ br_features_director boolean,
+ br_features_quicktime boolean,
+ br_features_realplayer boolean,
+ br_features_windowsmedia boolean,
+ br_features_gears boolean,
+ br_features_silverlight boolean,
+ br_cookies boolean,
+ br_colordepth character varying(12),
+ br_viewwidth integer,
+ br_viewheight integer,
+ os_name character varying(50),
+ os_family character varying(50),
+ os_manufacturer character varying(50),
+ os_timezone character varying(50),
+ dvce_type character varying(50),
+ dvce_ismobile boolean,
+ dvce_screenwidth integer,
+ dvce_screenheight integer,
+ doc_charset character varying(128),
+ doc_width integer,
+ doc_height integer,
+ tr_currency character(3),
+ tr_total_base numeric(18,2),
+ tr_tax_base numeric(18,2),
+ tr_shipping_base numeric(18,2),
+ ti_currency character(3),
+ ti_price_base numeric(18,2),
+ base_currency character(3),
+ geo_timezone character varying(64),
+ mkt_clickid character varying(128),
+ mkt_network character varying(64),
+ etl_tags character varying(500),
+ dvce_sent_tstamp timestamp with time zone,
+ refr_domain_userid character varying(36),
+ refr_dvce_tstamp timestamp with time zone,
+ domain_sessionid character(36),
+ derived_tstamp timestamp with time zone,
+ event_vendor character varying(1000),
+ event_name character varying(1000),
+ event_format character varying(128),
+ event_version character varying(128),
+ event_fingerprint character varying(128),
+ true_tstamp timestamp with time zone
+);
+ALTER TABLE ONLY public.product_analytics_events_experimental ATTACH PARTITION gitlab_partitions_static.product_analytics_events_experimental_50 FOR VALUES WITH (modulus 64, remainder 50);
+
+CREATE TABLE gitlab_partitions_static.product_analytics_events_experimental_51 (
+ id bigint DEFAULT nextval('public.product_analytics_events_experimental_id_seq'::regclass) NOT NULL,
+ project_id integer NOT NULL,
+ platform character varying(255),
+ etl_tstamp timestamp with time zone,
+ collector_tstamp timestamp with time zone NOT NULL,
+ dvce_created_tstamp timestamp with time zone,
+ event character varying(128),
+ event_id character(36) NOT NULL,
+ txn_id integer,
+ name_tracker character varying(128),
+ v_tracker character varying(100),
+ v_collector character varying(100) NOT NULL,
+ v_etl character varying(100) NOT NULL,
+ user_id character varying(255),
+ user_ipaddress character varying(45),
+ user_fingerprint character varying(50),
+ domain_userid character varying(36),
+ domain_sessionidx smallint,
+ network_userid character varying(38),
+ geo_country character(2),
+ geo_region character(3),
+ geo_city character varying(75),
+ geo_zipcode character varying(15),
+ geo_latitude double precision,
+ geo_longitude double precision,
+ geo_region_name character varying(100),
+ ip_isp character varying(100),
+ ip_organization character varying(100),
+ ip_domain character varying(100),
+ ip_netspeed character varying(100),
+ page_url text,
+ page_title character varying(2000),
+ page_referrer text,
+ page_urlscheme character varying(16),
+ page_urlhost character varying(255),
+ page_urlport integer,
+ page_urlpath character varying(3000),
+ page_urlquery character varying(6000),
+ page_urlfragment character varying(3000),
+ refr_urlscheme character varying(16),
+ refr_urlhost character varying(255),
+ refr_urlport integer,
+ refr_urlpath character varying(6000),
+ refr_urlquery character varying(6000),
+ refr_urlfragment character varying(3000),
+ refr_medium character varying(25),
+ refr_source character varying(50),
+ refr_term character varying(255),
+ mkt_medium character varying(255),
+ mkt_source character varying(255),
+ mkt_term character varying(255),
+ mkt_content character varying(500),
+ mkt_campaign character varying(255),
+ se_category character varying(1000),
+ se_action character varying(1000),
+ se_label character varying(1000),
+ se_property character varying(1000),
+ se_value double precision,
+ tr_orderid character varying(255),
+ tr_affiliation character varying(255),
+ tr_total numeric(18,2),
+ tr_tax numeric(18,2),
+ tr_shipping numeric(18,2),
+ tr_city character varying(255),
+ tr_state character varying(255),
+ tr_country character varying(255),
+ ti_orderid character varying(255),
+ ti_sku character varying(255),
+ ti_name character varying(255),
+ ti_category character varying(255),
+ ti_price numeric(18,2),
+ ti_quantity integer,
+ pp_xoffset_min integer,
+ pp_xoffset_max integer,
+ pp_yoffset_min integer,
+ pp_yoffset_max integer,
+ useragent character varying(1000),
+ br_name character varying(50),
+ br_family character varying(50),
+ br_version character varying(50),
+ br_type character varying(50),
+ br_renderengine character varying(50),
+ br_lang character varying(255),
+ br_features_pdf boolean,
+ br_features_flash boolean,
+ br_features_java boolean,
+ br_features_director boolean,
+ br_features_quicktime boolean,
+ br_features_realplayer boolean,
+ br_features_windowsmedia boolean,
+ br_features_gears boolean,
+ br_features_silverlight boolean,
+ br_cookies boolean,
+ br_colordepth character varying(12),
+ br_viewwidth integer,
+ br_viewheight integer,
+ os_name character varying(50),
+ os_family character varying(50),
+ os_manufacturer character varying(50),
+ os_timezone character varying(50),
+ dvce_type character varying(50),
+ dvce_ismobile boolean,
+ dvce_screenwidth integer,
+ dvce_screenheight integer,
+ doc_charset character varying(128),
+ doc_width integer,
+ doc_height integer,
+ tr_currency character(3),
+ tr_total_base numeric(18,2),
+ tr_tax_base numeric(18,2),
+ tr_shipping_base numeric(18,2),
+ ti_currency character(3),
+ ti_price_base numeric(18,2),
+ base_currency character(3),
+ geo_timezone character varying(64),
+ mkt_clickid character varying(128),
+ mkt_network character varying(64),
+ etl_tags character varying(500),
+ dvce_sent_tstamp timestamp with time zone,
+ refr_domain_userid character varying(36),
+ refr_dvce_tstamp timestamp with time zone,
+ domain_sessionid character(36),
+ derived_tstamp timestamp with time zone,
+ event_vendor character varying(1000),
+ event_name character varying(1000),
+ event_format character varying(128),
+ event_version character varying(128),
+ event_fingerprint character varying(128),
+ true_tstamp timestamp with time zone
+);
+ALTER TABLE ONLY public.product_analytics_events_experimental ATTACH PARTITION gitlab_partitions_static.product_analytics_events_experimental_51 FOR VALUES WITH (modulus 64, remainder 51);
+
+CREATE TABLE gitlab_partitions_static.product_analytics_events_experimental_52 (
+ id bigint DEFAULT nextval('public.product_analytics_events_experimental_id_seq'::regclass) NOT NULL,
+ project_id integer NOT NULL,
+ platform character varying(255),
+ etl_tstamp timestamp with time zone,
+ collector_tstamp timestamp with time zone NOT NULL,
+ dvce_created_tstamp timestamp with time zone,
+ event character varying(128),
+ event_id character(36) NOT NULL,
+ txn_id integer,
+ name_tracker character varying(128),
+ v_tracker character varying(100),
+ v_collector character varying(100) NOT NULL,
+ v_etl character varying(100) NOT NULL,
+ user_id character varying(255),
+ user_ipaddress character varying(45),
+ user_fingerprint character varying(50),
+ domain_userid character varying(36),
+ domain_sessionidx smallint,
+ network_userid character varying(38),
+ geo_country character(2),
+ geo_region character(3),
+ geo_city character varying(75),
+ geo_zipcode character varying(15),
+ geo_latitude double precision,
+ geo_longitude double precision,
+ geo_region_name character varying(100),
+ ip_isp character varying(100),
+ ip_organization character varying(100),
+ ip_domain character varying(100),
+ ip_netspeed character varying(100),
+ page_url text,
+ page_title character varying(2000),
+ page_referrer text,
+ page_urlscheme character varying(16),
+ page_urlhost character varying(255),
+ page_urlport integer,
+ page_urlpath character varying(3000),
+ page_urlquery character varying(6000),
+ page_urlfragment character varying(3000),
+ refr_urlscheme character varying(16),
+ refr_urlhost character varying(255),
+ refr_urlport integer,
+ refr_urlpath character varying(6000),
+ refr_urlquery character varying(6000),
+ refr_urlfragment character varying(3000),
+ refr_medium character varying(25),
+ refr_source character varying(50),
+ refr_term character varying(255),
+ mkt_medium character varying(255),
+ mkt_source character varying(255),
+ mkt_term character varying(255),
+ mkt_content character varying(500),
+ mkt_campaign character varying(255),
+ se_category character varying(1000),
+ se_action character varying(1000),
+ se_label character varying(1000),
+ se_property character varying(1000),
+ se_value double precision,
+ tr_orderid character varying(255),
+ tr_affiliation character varying(255),
+ tr_total numeric(18,2),
+ tr_tax numeric(18,2),
+ tr_shipping numeric(18,2),
+ tr_city character varying(255),
+ tr_state character varying(255),
+ tr_country character varying(255),
+ ti_orderid character varying(255),
+ ti_sku character varying(255),
+ ti_name character varying(255),
+ ti_category character varying(255),
+ ti_price numeric(18,2),
+ ti_quantity integer,
+ pp_xoffset_min integer,
+ pp_xoffset_max integer,
+ pp_yoffset_min integer,
+ pp_yoffset_max integer,
+ useragent character varying(1000),
+ br_name character varying(50),
+ br_family character varying(50),
+ br_version character varying(50),
+ br_type character varying(50),
+ br_renderengine character varying(50),
+ br_lang character varying(255),
+ br_features_pdf boolean,
+ br_features_flash boolean,
+ br_features_java boolean,
+ br_features_director boolean,
+ br_features_quicktime boolean,
+ br_features_realplayer boolean,
+ br_features_windowsmedia boolean,
+ br_features_gears boolean,
+ br_features_silverlight boolean,
+ br_cookies boolean,
+ br_colordepth character varying(12),
+ br_viewwidth integer,
+ br_viewheight integer,
+ os_name character varying(50),
+ os_family character varying(50),
+ os_manufacturer character varying(50),
+ os_timezone character varying(50),
+ dvce_type character varying(50),
+ dvce_ismobile boolean,
+ dvce_screenwidth integer,
+ dvce_screenheight integer,
+ doc_charset character varying(128),
+ doc_width integer,
+ doc_height integer,
+ tr_currency character(3),
+ tr_total_base numeric(18,2),
+ tr_tax_base numeric(18,2),
+ tr_shipping_base numeric(18,2),
+ ti_currency character(3),
+ ti_price_base numeric(18,2),
+ base_currency character(3),
+ geo_timezone character varying(64),
+ mkt_clickid character varying(128),
+ mkt_network character varying(64),
+ etl_tags character varying(500),
+ dvce_sent_tstamp timestamp with time zone,
+ refr_domain_userid character varying(36),
+ refr_dvce_tstamp timestamp with time zone,
+ domain_sessionid character(36),
+ derived_tstamp timestamp with time zone,
+ event_vendor character varying(1000),
+ event_name character varying(1000),
+ event_format character varying(128),
+ event_version character varying(128),
+ event_fingerprint character varying(128),
+ true_tstamp timestamp with time zone
+);
+ALTER TABLE ONLY public.product_analytics_events_experimental ATTACH PARTITION gitlab_partitions_static.product_analytics_events_experimental_52 FOR VALUES WITH (modulus 64, remainder 52);
+
+CREATE TABLE gitlab_partitions_static.product_analytics_events_experimental_53 (
+ id bigint DEFAULT nextval('public.product_analytics_events_experimental_id_seq'::regclass) NOT NULL,
+ project_id integer NOT NULL,
+ platform character varying(255),
+ etl_tstamp timestamp with time zone,
+ collector_tstamp timestamp with time zone NOT NULL,
+ dvce_created_tstamp timestamp with time zone,
+ event character varying(128),
+ event_id character(36) NOT NULL,
+ txn_id integer,
+ name_tracker character varying(128),
+ v_tracker character varying(100),
+ v_collector character varying(100) NOT NULL,
+ v_etl character varying(100) NOT NULL,
+ user_id character varying(255),
+ user_ipaddress character varying(45),
+ user_fingerprint character varying(50),
+ domain_userid character varying(36),
+ domain_sessionidx smallint,
+ network_userid character varying(38),
+ geo_country character(2),
+ geo_region character(3),
+ geo_city character varying(75),
+ geo_zipcode character varying(15),
+ geo_latitude double precision,
+ geo_longitude double precision,
+ geo_region_name character varying(100),
+ ip_isp character varying(100),
+ ip_organization character varying(100),
+ ip_domain character varying(100),
+ ip_netspeed character varying(100),
+ page_url text,
+ page_title character varying(2000),
+ page_referrer text,
+ page_urlscheme character varying(16),
+ page_urlhost character varying(255),
+ page_urlport integer,
+ page_urlpath character varying(3000),
+ page_urlquery character varying(6000),
+ page_urlfragment character varying(3000),
+ refr_urlscheme character varying(16),
+ refr_urlhost character varying(255),
+ refr_urlport integer,
+ refr_urlpath character varying(6000),
+ refr_urlquery character varying(6000),
+ refr_urlfragment character varying(3000),
+ refr_medium character varying(25),
+ refr_source character varying(50),
+ refr_term character varying(255),
+ mkt_medium character varying(255),
+ mkt_source character varying(255),
+ mkt_term character varying(255),
+ mkt_content character varying(500),
+ mkt_campaign character varying(255),
+ se_category character varying(1000),
+ se_action character varying(1000),
+ se_label character varying(1000),
+ se_property character varying(1000),
+ se_value double precision,
+ tr_orderid character varying(255),
+ tr_affiliation character varying(255),
+ tr_total numeric(18,2),
+ tr_tax numeric(18,2),
+ tr_shipping numeric(18,2),
+ tr_city character varying(255),
+ tr_state character varying(255),
+ tr_country character varying(255),
+ ti_orderid character varying(255),
+ ti_sku character varying(255),
+ ti_name character varying(255),
+ ti_category character varying(255),
+ ti_price numeric(18,2),
+ ti_quantity integer,
+ pp_xoffset_min integer,
+ pp_xoffset_max integer,
+ pp_yoffset_min integer,
+ pp_yoffset_max integer,
+ useragent character varying(1000),
+ br_name character varying(50),
+ br_family character varying(50),
+ br_version character varying(50),
+ br_type character varying(50),
+ br_renderengine character varying(50),
+ br_lang character varying(255),
+ br_features_pdf boolean,
+ br_features_flash boolean,
+ br_features_java boolean,
+ br_features_director boolean,
+ br_features_quicktime boolean,
+ br_features_realplayer boolean,
+ br_features_windowsmedia boolean,
+ br_features_gears boolean,
+ br_features_silverlight boolean,
+ br_cookies boolean,
+ br_colordepth character varying(12),
+ br_viewwidth integer,
+ br_viewheight integer,
+ os_name character varying(50),
+ os_family character varying(50),
+ os_manufacturer character varying(50),
+ os_timezone character varying(50),
+ dvce_type character varying(50),
+ dvce_ismobile boolean,
+ dvce_screenwidth integer,
+ dvce_screenheight integer,
+ doc_charset character varying(128),
+ doc_width integer,
+ doc_height integer,
+ tr_currency character(3),
+ tr_total_base numeric(18,2),
+ tr_tax_base numeric(18,2),
+ tr_shipping_base numeric(18,2),
+ ti_currency character(3),
+ ti_price_base numeric(18,2),
+ base_currency character(3),
+ geo_timezone character varying(64),
+ mkt_clickid character varying(128),
+ mkt_network character varying(64),
+ etl_tags character varying(500),
+ dvce_sent_tstamp timestamp with time zone,
+ refr_domain_userid character varying(36),
+ refr_dvce_tstamp timestamp with time zone,
+ domain_sessionid character(36),
+ derived_tstamp timestamp with time zone,
+ event_vendor character varying(1000),
+ event_name character varying(1000),
+ event_format character varying(128),
+ event_version character varying(128),
+ event_fingerprint character varying(128),
+ true_tstamp timestamp with time zone
+);
+ALTER TABLE ONLY public.product_analytics_events_experimental ATTACH PARTITION gitlab_partitions_static.product_analytics_events_experimental_53 FOR VALUES WITH (modulus 64, remainder 53);
+
+CREATE TABLE gitlab_partitions_static.product_analytics_events_experimental_54 (
+ id bigint DEFAULT nextval('public.product_analytics_events_experimental_id_seq'::regclass) NOT NULL,
+ project_id integer NOT NULL,
+ platform character varying(255),
+ etl_tstamp timestamp with time zone,
+ collector_tstamp timestamp with time zone NOT NULL,
+ dvce_created_tstamp timestamp with time zone,
+ event character varying(128),
+ event_id character(36) NOT NULL,
+ txn_id integer,
+ name_tracker character varying(128),
+ v_tracker character varying(100),
+ v_collector character varying(100) NOT NULL,
+ v_etl character varying(100) NOT NULL,
+ user_id character varying(255),
+ user_ipaddress character varying(45),
+ user_fingerprint character varying(50),
+ domain_userid character varying(36),
+ domain_sessionidx smallint,
+ network_userid character varying(38),
+ geo_country character(2),
+ geo_region character(3),
+ geo_city character varying(75),
+ geo_zipcode character varying(15),
+ geo_latitude double precision,
+ geo_longitude double precision,
+ geo_region_name character varying(100),
+ ip_isp character varying(100),
+ ip_organization character varying(100),
+ ip_domain character varying(100),
+ ip_netspeed character varying(100),
+ page_url text,
+ page_title character varying(2000),
+ page_referrer text,
+ page_urlscheme character varying(16),
+ page_urlhost character varying(255),
+ page_urlport integer,
+ page_urlpath character varying(3000),
+ page_urlquery character varying(6000),
+ page_urlfragment character varying(3000),
+ refr_urlscheme character varying(16),
+ refr_urlhost character varying(255),
+ refr_urlport integer,
+ refr_urlpath character varying(6000),
+ refr_urlquery character varying(6000),
+ refr_urlfragment character varying(3000),
+ refr_medium character varying(25),
+ refr_source character varying(50),
+ refr_term character varying(255),
+ mkt_medium character varying(255),
+ mkt_source character varying(255),
+ mkt_term character varying(255),
+ mkt_content character varying(500),
+ mkt_campaign character varying(255),
+ se_category character varying(1000),
+ se_action character varying(1000),
+ se_label character varying(1000),
+ se_property character varying(1000),
+ se_value double precision,
+ tr_orderid character varying(255),
+ tr_affiliation character varying(255),
+ tr_total numeric(18,2),
+ tr_tax numeric(18,2),
+ tr_shipping numeric(18,2),
+ tr_city character varying(255),
+ tr_state character varying(255),
+ tr_country character varying(255),
+ ti_orderid character varying(255),
+ ti_sku character varying(255),
+ ti_name character varying(255),
+ ti_category character varying(255),
+ ti_price numeric(18,2),
+ ti_quantity integer,
+ pp_xoffset_min integer,
+ pp_xoffset_max integer,
+ pp_yoffset_min integer,
+ pp_yoffset_max integer,
+ useragent character varying(1000),
+ br_name character varying(50),
+ br_family character varying(50),
+ br_version character varying(50),
+ br_type character varying(50),
+ br_renderengine character varying(50),
+ br_lang character varying(255),
+ br_features_pdf boolean,
+ br_features_flash boolean,
+ br_features_java boolean,
+ br_features_director boolean,
+ br_features_quicktime boolean,
+ br_features_realplayer boolean,
+ br_features_windowsmedia boolean,
+ br_features_gears boolean,
+ br_features_silverlight boolean,
+ br_cookies boolean,
+ br_colordepth character varying(12),
+ br_viewwidth integer,
+ br_viewheight integer,
+ os_name character varying(50),
+ os_family character varying(50),
+ os_manufacturer character varying(50),
+ os_timezone character varying(50),
+ dvce_type character varying(50),
+ dvce_ismobile boolean,
+ dvce_screenwidth integer,
+ dvce_screenheight integer,
+ doc_charset character varying(128),
+ doc_width integer,
+ doc_height integer,
+ tr_currency character(3),
+ tr_total_base numeric(18,2),
+ tr_tax_base numeric(18,2),
+ tr_shipping_base numeric(18,2),
+ ti_currency character(3),
+ ti_price_base numeric(18,2),
+ base_currency character(3),
+ geo_timezone character varying(64),
+ mkt_clickid character varying(128),
+ mkt_network character varying(64),
+ etl_tags character varying(500),
+ dvce_sent_tstamp timestamp with time zone,
+ refr_domain_userid character varying(36),
+ refr_dvce_tstamp timestamp with time zone,
+ domain_sessionid character(36),
+ derived_tstamp timestamp with time zone,
+ event_vendor character varying(1000),
+ event_name character varying(1000),
+ event_format character varying(128),
+ event_version character varying(128),
+ event_fingerprint character varying(128),
+ true_tstamp timestamp with time zone
+);
+ALTER TABLE ONLY public.product_analytics_events_experimental ATTACH PARTITION gitlab_partitions_static.product_analytics_events_experimental_54 FOR VALUES WITH (modulus 64, remainder 54);
+
+CREATE TABLE gitlab_partitions_static.product_analytics_events_experimental_55 (
+ id bigint DEFAULT nextval('public.product_analytics_events_experimental_id_seq'::regclass) NOT NULL,
+ project_id integer NOT NULL,
+ platform character varying(255),
+ etl_tstamp timestamp with time zone,
+ collector_tstamp timestamp with time zone NOT NULL,
+ dvce_created_tstamp timestamp with time zone,
+ event character varying(128),
+ event_id character(36) NOT NULL,
+ txn_id integer,
+ name_tracker character varying(128),
+ v_tracker character varying(100),
+ v_collector character varying(100) NOT NULL,
+ v_etl character varying(100) NOT NULL,
+ user_id character varying(255),
+ user_ipaddress character varying(45),
+ user_fingerprint character varying(50),
+ domain_userid character varying(36),
+ domain_sessionidx smallint,
+ network_userid character varying(38),
+ geo_country character(2),
+ geo_region character(3),
+ geo_city character varying(75),
+ geo_zipcode character varying(15),
+ geo_latitude double precision,
+ geo_longitude double precision,
+ geo_region_name character varying(100),
+ ip_isp character varying(100),
+ ip_organization character varying(100),
+ ip_domain character varying(100),
+ ip_netspeed character varying(100),
+ page_url text,
+ page_title character varying(2000),
+ page_referrer text,
+ page_urlscheme character varying(16),
+ page_urlhost character varying(255),
+ page_urlport integer,
+ page_urlpath character varying(3000),
+ page_urlquery character varying(6000),
+ page_urlfragment character varying(3000),
+ refr_urlscheme character varying(16),
+ refr_urlhost character varying(255),
+ refr_urlport integer,
+ refr_urlpath character varying(6000),
+ refr_urlquery character varying(6000),
+ refr_urlfragment character varying(3000),
+ refr_medium character varying(25),
+ refr_source character varying(50),
+ refr_term character varying(255),
+ mkt_medium character varying(255),
+ mkt_source character varying(255),
+ mkt_term character varying(255),
+ mkt_content character varying(500),
+ mkt_campaign character varying(255),
+ se_category character varying(1000),
+ se_action character varying(1000),
+ se_label character varying(1000),
+ se_property character varying(1000),
+ se_value double precision,
+ tr_orderid character varying(255),
+ tr_affiliation character varying(255),
+ tr_total numeric(18,2),
+ tr_tax numeric(18,2),
+ tr_shipping numeric(18,2),
+ tr_city character varying(255),
+ tr_state character varying(255),
+ tr_country character varying(255),
+ ti_orderid character varying(255),
+ ti_sku character varying(255),
+ ti_name character varying(255),
+ ti_category character varying(255),
+ ti_price numeric(18,2),
+ ti_quantity integer,
+ pp_xoffset_min integer,
+ pp_xoffset_max integer,
+ pp_yoffset_min integer,
+ pp_yoffset_max integer,
+ useragent character varying(1000),
+ br_name character varying(50),
+ br_family character varying(50),
+ br_version character varying(50),
+ br_type character varying(50),
+ br_renderengine character varying(50),
+ br_lang character varying(255),
+ br_features_pdf boolean,
+ br_features_flash boolean,
+ br_features_java boolean,
+ br_features_director boolean,
+ br_features_quicktime boolean,
+ br_features_realplayer boolean,
+ br_features_windowsmedia boolean,
+ br_features_gears boolean,
+ br_features_silverlight boolean,
+ br_cookies boolean,
+ br_colordepth character varying(12),
+ br_viewwidth integer,
+ br_viewheight integer,
+ os_name character varying(50),
+ os_family character varying(50),
+ os_manufacturer character varying(50),
+ os_timezone character varying(50),
+ dvce_type character varying(50),
+ dvce_ismobile boolean,
+ dvce_screenwidth integer,
+ dvce_screenheight integer,
+ doc_charset character varying(128),
+ doc_width integer,
+ doc_height integer,
+ tr_currency character(3),
+ tr_total_base numeric(18,2),
+ tr_tax_base numeric(18,2),
+ tr_shipping_base numeric(18,2),
+ ti_currency character(3),
+ ti_price_base numeric(18,2),
+ base_currency character(3),
+ geo_timezone character varying(64),
+ mkt_clickid character varying(128),
+ mkt_network character varying(64),
+ etl_tags character varying(500),
+ dvce_sent_tstamp timestamp with time zone,
+ refr_domain_userid character varying(36),
+ refr_dvce_tstamp timestamp with time zone,
+ domain_sessionid character(36),
+ derived_tstamp timestamp with time zone,
+ event_vendor character varying(1000),
+ event_name character varying(1000),
+ event_format character varying(128),
+ event_version character varying(128),
+ event_fingerprint character varying(128),
+ true_tstamp timestamp with time zone
+);
+ALTER TABLE ONLY public.product_analytics_events_experimental ATTACH PARTITION gitlab_partitions_static.product_analytics_events_experimental_55 FOR VALUES WITH (modulus 64, remainder 55);
+
+CREATE TABLE gitlab_partitions_static.product_analytics_events_experimental_56 (
+ id bigint DEFAULT nextval('public.product_analytics_events_experimental_id_seq'::regclass) NOT NULL,
+ project_id integer NOT NULL,
+ platform character varying(255),
+ etl_tstamp timestamp with time zone,
+ collector_tstamp timestamp with time zone NOT NULL,
+ dvce_created_tstamp timestamp with time zone,
+ event character varying(128),
+ event_id character(36) NOT NULL,
+ txn_id integer,
+ name_tracker character varying(128),
+ v_tracker character varying(100),
+ v_collector character varying(100) NOT NULL,
+ v_etl character varying(100) NOT NULL,
+ user_id character varying(255),
+ user_ipaddress character varying(45),
+ user_fingerprint character varying(50),
+ domain_userid character varying(36),
+ domain_sessionidx smallint,
+ network_userid character varying(38),
+ geo_country character(2),
+ geo_region character(3),
+ geo_city character varying(75),
+ geo_zipcode character varying(15),
+ geo_latitude double precision,
+ geo_longitude double precision,
+ geo_region_name character varying(100),
+ ip_isp character varying(100),
+ ip_organization character varying(100),
+ ip_domain character varying(100),
+ ip_netspeed character varying(100),
+ page_url text,
+ page_title character varying(2000),
+ page_referrer text,
+ page_urlscheme character varying(16),
+ page_urlhost character varying(255),
+ page_urlport integer,
+ page_urlpath character varying(3000),
+ page_urlquery character varying(6000),
+ page_urlfragment character varying(3000),
+ refr_urlscheme character varying(16),
+ refr_urlhost character varying(255),
+ refr_urlport integer,
+ refr_urlpath character varying(6000),
+ refr_urlquery character varying(6000),
+ refr_urlfragment character varying(3000),
+ refr_medium character varying(25),
+ refr_source character varying(50),
+ refr_term character varying(255),
+ mkt_medium character varying(255),
+ mkt_source character varying(255),
+ mkt_term character varying(255),
+ mkt_content character varying(500),
+ mkt_campaign character varying(255),
+ se_category character varying(1000),
+ se_action character varying(1000),
+ se_label character varying(1000),
+ se_property character varying(1000),
+ se_value double precision,
+ tr_orderid character varying(255),
+ tr_affiliation character varying(255),
+ tr_total numeric(18,2),
+ tr_tax numeric(18,2),
+ tr_shipping numeric(18,2),
+ tr_city character varying(255),
+ tr_state character varying(255),
+ tr_country character varying(255),
+ ti_orderid character varying(255),
+ ti_sku character varying(255),
+ ti_name character varying(255),
+ ti_category character varying(255),
+ ti_price numeric(18,2),
+ ti_quantity integer,
+ pp_xoffset_min integer,
+ pp_xoffset_max integer,
+ pp_yoffset_min integer,
+ pp_yoffset_max integer,
+ useragent character varying(1000),
+ br_name character varying(50),
+ br_family character varying(50),
+ br_version character varying(50),
+ br_type character varying(50),
+ br_renderengine character varying(50),
+ br_lang character varying(255),
+ br_features_pdf boolean,
+ br_features_flash boolean,
+ br_features_java boolean,
+ br_features_director boolean,
+ br_features_quicktime boolean,
+ br_features_realplayer boolean,
+ br_features_windowsmedia boolean,
+ br_features_gears boolean,
+ br_features_silverlight boolean,
+ br_cookies boolean,
+ br_colordepth character varying(12),
+ br_viewwidth integer,
+ br_viewheight integer,
+ os_name character varying(50),
+ os_family character varying(50),
+ os_manufacturer character varying(50),
+ os_timezone character varying(50),
+ dvce_type character varying(50),
+ dvce_ismobile boolean,
+ dvce_screenwidth integer,
+ dvce_screenheight integer,
+ doc_charset character varying(128),
+ doc_width integer,
+ doc_height integer,
+ tr_currency character(3),
+ tr_total_base numeric(18,2),
+ tr_tax_base numeric(18,2),
+ tr_shipping_base numeric(18,2),
+ ti_currency character(3),
+ ti_price_base numeric(18,2),
+ base_currency character(3),
+ geo_timezone character varying(64),
+ mkt_clickid character varying(128),
+ mkt_network character varying(64),
+ etl_tags character varying(500),
+ dvce_sent_tstamp timestamp with time zone,
+ refr_domain_userid character varying(36),
+ refr_dvce_tstamp timestamp with time zone,
+ domain_sessionid character(36),
+ derived_tstamp timestamp with time zone,
+ event_vendor character varying(1000),
+ event_name character varying(1000),
+ event_format character varying(128),
+ event_version character varying(128),
+ event_fingerprint character varying(128),
+ true_tstamp timestamp with time zone
+);
+ALTER TABLE ONLY public.product_analytics_events_experimental ATTACH PARTITION gitlab_partitions_static.product_analytics_events_experimental_56 FOR VALUES WITH (modulus 64, remainder 56);
+
+CREATE TABLE gitlab_partitions_static.product_analytics_events_experimental_57 (
+ id bigint DEFAULT nextval('public.product_analytics_events_experimental_id_seq'::regclass) NOT NULL,
+ project_id integer NOT NULL,
+ platform character varying(255),
+ etl_tstamp timestamp with time zone,
+ collector_tstamp timestamp with time zone NOT NULL,
+ dvce_created_tstamp timestamp with time zone,
+ event character varying(128),
+ event_id character(36) NOT NULL,
+ txn_id integer,
+ name_tracker character varying(128),
+ v_tracker character varying(100),
+ v_collector character varying(100) NOT NULL,
+ v_etl character varying(100) NOT NULL,
+ user_id character varying(255),
+ user_ipaddress character varying(45),
+ user_fingerprint character varying(50),
+ domain_userid character varying(36),
+ domain_sessionidx smallint,
+ network_userid character varying(38),
+ geo_country character(2),
+ geo_region character(3),
+ geo_city character varying(75),
+ geo_zipcode character varying(15),
+ geo_latitude double precision,
+ geo_longitude double precision,
+ geo_region_name character varying(100),
+ ip_isp character varying(100),
+ ip_organization character varying(100),
+ ip_domain character varying(100),
+ ip_netspeed character varying(100),
+ page_url text,
+ page_title character varying(2000),
+ page_referrer text,
+ page_urlscheme character varying(16),
+ page_urlhost character varying(255),
+ page_urlport integer,
+ page_urlpath character varying(3000),
+ page_urlquery character varying(6000),
+ page_urlfragment character varying(3000),
+ refr_urlscheme character varying(16),
+ refr_urlhost character varying(255),
+ refr_urlport integer,
+ refr_urlpath character varying(6000),
+ refr_urlquery character varying(6000),
+ refr_urlfragment character varying(3000),
+ refr_medium character varying(25),
+ refr_source character varying(50),
+ refr_term character varying(255),
+ mkt_medium character varying(255),
+ mkt_source character varying(255),
+ mkt_term character varying(255),
+ mkt_content character varying(500),
+ mkt_campaign character varying(255),
+ se_category character varying(1000),
+ se_action character varying(1000),
+ se_label character varying(1000),
+ se_property character varying(1000),
+ se_value double precision,
+ tr_orderid character varying(255),
+ tr_affiliation character varying(255),
+ tr_total numeric(18,2),
+ tr_tax numeric(18,2),
+ tr_shipping numeric(18,2),
+ tr_city character varying(255),
+ tr_state character varying(255),
+ tr_country character varying(255),
+ ti_orderid character varying(255),
+ ti_sku character varying(255),
+ ti_name character varying(255),
+ ti_category character varying(255),
+ ti_price numeric(18,2),
+ ti_quantity integer,
+ pp_xoffset_min integer,
+ pp_xoffset_max integer,
+ pp_yoffset_min integer,
+ pp_yoffset_max integer,
+ useragent character varying(1000),
+ br_name character varying(50),
+ br_family character varying(50),
+ br_version character varying(50),
+ br_type character varying(50),
+ br_renderengine character varying(50),
+ br_lang character varying(255),
+ br_features_pdf boolean,
+ br_features_flash boolean,
+ br_features_java boolean,
+ br_features_director boolean,
+ br_features_quicktime boolean,
+ br_features_realplayer boolean,
+ br_features_windowsmedia boolean,
+ br_features_gears boolean,
+ br_features_silverlight boolean,
+ br_cookies boolean,
+ br_colordepth character varying(12),
+ br_viewwidth integer,
+ br_viewheight integer,
+ os_name character varying(50),
+ os_family character varying(50),
+ os_manufacturer character varying(50),
+ os_timezone character varying(50),
+ dvce_type character varying(50),
+ dvce_ismobile boolean,
+ dvce_screenwidth integer,
+ dvce_screenheight integer,
+ doc_charset character varying(128),
+ doc_width integer,
+ doc_height integer,
+ tr_currency character(3),
+ tr_total_base numeric(18,2),
+ tr_tax_base numeric(18,2),
+ tr_shipping_base numeric(18,2),
+ ti_currency character(3),
+ ti_price_base numeric(18,2),
+ base_currency character(3),
+ geo_timezone character varying(64),
+ mkt_clickid character varying(128),
+ mkt_network character varying(64),
+ etl_tags character varying(500),
+ dvce_sent_tstamp timestamp with time zone,
+ refr_domain_userid character varying(36),
+ refr_dvce_tstamp timestamp with time zone,
+ domain_sessionid character(36),
+ derived_tstamp timestamp with time zone,
+ event_vendor character varying(1000),
+ event_name character varying(1000),
+ event_format character varying(128),
+ event_version character varying(128),
+ event_fingerprint character varying(128),
+ true_tstamp timestamp with time zone
+);
+ALTER TABLE ONLY public.product_analytics_events_experimental ATTACH PARTITION gitlab_partitions_static.product_analytics_events_experimental_57 FOR VALUES WITH (modulus 64, remainder 57);
+
+CREATE TABLE gitlab_partitions_static.product_analytics_events_experimental_58 (
+ id bigint DEFAULT nextval('public.product_analytics_events_experimental_id_seq'::regclass) NOT NULL,
+ project_id integer NOT NULL,
+ platform character varying(255),
+ etl_tstamp timestamp with time zone,
+ collector_tstamp timestamp with time zone NOT NULL,
+ dvce_created_tstamp timestamp with time zone,
+ event character varying(128),
+ event_id character(36) NOT NULL,
+ txn_id integer,
+ name_tracker character varying(128),
+ v_tracker character varying(100),
+ v_collector character varying(100) NOT NULL,
+ v_etl character varying(100) NOT NULL,
+ user_id character varying(255),
+ user_ipaddress character varying(45),
+ user_fingerprint character varying(50),
+ domain_userid character varying(36),
+ domain_sessionidx smallint,
+ network_userid character varying(38),
+ geo_country character(2),
+ geo_region character(3),
+ geo_city character varying(75),
+ geo_zipcode character varying(15),
+ geo_latitude double precision,
+ geo_longitude double precision,
+ geo_region_name character varying(100),
+ ip_isp character varying(100),
+ ip_organization character varying(100),
+ ip_domain character varying(100),
+ ip_netspeed character varying(100),
+ page_url text,
+ page_title character varying(2000),
+ page_referrer text,
+ page_urlscheme character varying(16),
+ page_urlhost character varying(255),
+ page_urlport integer,
+ page_urlpath character varying(3000),
+ page_urlquery character varying(6000),
+ page_urlfragment character varying(3000),
+ refr_urlscheme character varying(16),
+ refr_urlhost character varying(255),
+ refr_urlport integer,
+ refr_urlpath character varying(6000),
+ refr_urlquery character varying(6000),
+ refr_urlfragment character varying(3000),
+ refr_medium character varying(25),
+ refr_source character varying(50),
+ refr_term character varying(255),
+ mkt_medium character varying(255),
+ mkt_source character varying(255),
+ mkt_term character varying(255),
+ mkt_content character varying(500),
+ mkt_campaign character varying(255),
+ se_category character varying(1000),
+ se_action character varying(1000),
+ se_label character varying(1000),
+ se_property character varying(1000),
+ se_value double precision,
+ tr_orderid character varying(255),
+ tr_affiliation character varying(255),
+ tr_total numeric(18,2),
+ tr_tax numeric(18,2),
+ tr_shipping numeric(18,2),
+ tr_city character varying(255),
+ tr_state character varying(255),
+ tr_country character varying(255),
+ ti_orderid character varying(255),
+ ti_sku character varying(255),
+ ti_name character varying(255),
+ ti_category character varying(255),
+ ti_price numeric(18,2),
+ ti_quantity integer,
+ pp_xoffset_min integer,
+ pp_xoffset_max integer,
+ pp_yoffset_min integer,
+ pp_yoffset_max integer,
+ useragent character varying(1000),
+ br_name character varying(50),
+ br_family character varying(50),
+ br_version character varying(50),
+ br_type character varying(50),
+ br_renderengine character varying(50),
+ br_lang character varying(255),
+ br_features_pdf boolean,
+ br_features_flash boolean,
+ br_features_java boolean,
+ br_features_director boolean,
+ br_features_quicktime boolean,
+ br_features_realplayer boolean,
+ br_features_windowsmedia boolean,
+ br_features_gears boolean,
+ br_features_silverlight boolean,
+ br_cookies boolean,
+ br_colordepth character varying(12),
+ br_viewwidth integer,
+ br_viewheight integer,
+ os_name character varying(50),
+ os_family character varying(50),
+ os_manufacturer character varying(50),
+ os_timezone character varying(50),
+ dvce_type character varying(50),
+ dvce_ismobile boolean,
+ dvce_screenwidth integer,
+ dvce_screenheight integer,
+ doc_charset character varying(128),
+ doc_width integer,
+ doc_height integer,
+ tr_currency character(3),
+ tr_total_base numeric(18,2),
+ tr_tax_base numeric(18,2),
+ tr_shipping_base numeric(18,2),
+ ti_currency character(3),
+ ti_price_base numeric(18,2),
+ base_currency character(3),
+ geo_timezone character varying(64),
+ mkt_clickid character varying(128),
+ mkt_network character varying(64),
+ etl_tags character varying(500),
+ dvce_sent_tstamp timestamp with time zone,
+ refr_domain_userid character varying(36),
+ refr_dvce_tstamp timestamp with time zone,
+ domain_sessionid character(36),
+ derived_tstamp timestamp with time zone,
+ event_vendor character varying(1000),
+ event_name character varying(1000),
+ event_format character varying(128),
+ event_version character varying(128),
+ event_fingerprint character varying(128),
+ true_tstamp timestamp with time zone
+);
+ALTER TABLE ONLY public.product_analytics_events_experimental ATTACH PARTITION gitlab_partitions_static.product_analytics_events_experimental_58 FOR VALUES WITH (modulus 64, remainder 58);
+
+CREATE TABLE gitlab_partitions_static.product_analytics_events_experimental_59 (
+ id bigint DEFAULT nextval('public.product_analytics_events_experimental_id_seq'::regclass) NOT NULL,
+ project_id integer NOT NULL,
+ platform character varying(255),
+ etl_tstamp timestamp with time zone,
+ collector_tstamp timestamp with time zone NOT NULL,
+ dvce_created_tstamp timestamp with time zone,
+ event character varying(128),
+ event_id character(36) NOT NULL,
+ txn_id integer,
+ name_tracker character varying(128),
+ v_tracker character varying(100),
+ v_collector character varying(100) NOT NULL,
+ v_etl character varying(100) NOT NULL,
+ user_id character varying(255),
+ user_ipaddress character varying(45),
+ user_fingerprint character varying(50),
+ domain_userid character varying(36),
+ domain_sessionidx smallint,
+ network_userid character varying(38),
+ geo_country character(2),
+ geo_region character(3),
+ geo_city character varying(75),
+ geo_zipcode character varying(15),
+ geo_latitude double precision,
+ geo_longitude double precision,
+ geo_region_name character varying(100),
+ ip_isp character varying(100),
+ ip_organization character varying(100),
+ ip_domain character varying(100),
+ ip_netspeed character varying(100),
+ page_url text,
+ page_title character varying(2000),
+ page_referrer text,
+ page_urlscheme character varying(16),
+ page_urlhost character varying(255),
+ page_urlport integer,
+ page_urlpath character varying(3000),
+ page_urlquery character varying(6000),
+ page_urlfragment character varying(3000),
+ refr_urlscheme character varying(16),
+ refr_urlhost character varying(255),
+ refr_urlport integer,
+ refr_urlpath character varying(6000),
+ refr_urlquery character varying(6000),
+ refr_urlfragment character varying(3000),
+ refr_medium character varying(25),
+ refr_source character varying(50),
+ refr_term character varying(255),
+ mkt_medium character varying(255),
+ mkt_source character varying(255),
+ mkt_term character varying(255),
+ mkt_content character varying(500),
+ mkt_campaign character varying(255),
+ se_category character varying(1000),
+ se_action character varying(1000),
+ se_label character varying(1000),
+ se_property character varying(1000),
+ se_value double precision,
+ tr_orderid character varying(255),
+ tr_affiliation character varying(255),
+ tr_total numeric(18,2),
+ tr_tax numeric(18,2),
+ tr_shipping numeric(18,2),
+ tr_city character varying(255),
+ tr_state character varying(255),
+ tr_country character varying(255),
+ ti_orderid character varying(255),
+ ti_sku character varying(255),
+ ti_name character varying(255),
+ ti_category character varying(255),
+ ti_price numeric(18,2),
+ ti_quantity integer,
+ pp_xoffset_min integer,
+ pp_xoffset_max integer,
+ pp_yoffset_min integer,
+ pp_yoffset_max integer,
+ useragent character varying(1000),
+ br_name character varying(50),
+ br_family character varying(50),
+ br_version character varying(50),
+ br_type character varying(50),
+ br_renderengine character varying(50),
+ br_lang character varying(255),
+ br_features_pdf boolean,
+ br_features_flash boolean,
+ br_features_java boolean,
+ br_features_director boolean,
+ br_features_quicktime boolean,
+ br_features_realplayer boolean,
+ br_features_windowsmedia boolean,
+ br_features_gears boolean,
+ br_features_silverlight boolean,
+ br_cookies boolean,
+ br_colordepth character varying(12),
+ br_viewwidth integer,
+ br_viewheight integer,
+ os_name character varying(50),
+ os_family character varying(50),
+ os_manufacturer character varying(50),
+ os_timezone character varying(50),
+ dvce_type character varying(50),
+ dvce_ismobile boolean,
+ dvce_screenwidth integer,
+ dvce_screenheight integer,
+ doc_charset character varying(128),
+ doc_width integer,
+ doc_height integer,
+ tr_currency character(3),
+ tr_total_base numeric(18,2),
+ tr_tax_base numeric(18,2),
+ tr_shipping_base numeric(18,2),
+ ti_currency character(3),
+ ti_price_base numeric(18,2),
+ base_currency character(3),
+ geo_timezone character varying(64),
+ mkt_clickid character varying(128),
+ mkt_network character varying(64),
+ etl_tags character varying(500),
+ dvce_sent_tstamp timestamp with time zone,
+ refr_domain_userid character varying(36),
+ refr_dvce_tstamp timestamp with time zone,
+ domain_sessionid character(36),
+ derived_tstamp timestamp with time zone,
+ event_vendor character varying(1000),
+ event_name character varying(1000),
+ event_format character varying(128),
+ event_version character varying(128),
+ event_fingerprint character varying(128),
+ true_tstamp timestamp with time zone
+);
+ALTER TABLE ONLY public.product_analytics_events_experimental ATTACH PARTITION gitlab_partitions_static.product_analytics_events_experimental_59 FOR VALUES WITH (modulus 64, remainder 59);
+
+CREATE TABLE gitlab_partitions_static.product_analytics_events_experimental_60 (
+ id bigint DEFAULT nextval('public.product_analytics_events_experimental_id_seq'::regclass) NOT NULL,
+ project_id integer NOT NULL,
+ platform character varying(255),
+ etl_tstamp timestamp with time zone,
+ collector_tstamp timestamp with time zone NOT NULL,
+ dvce_created_tstamp timestamp with time zone,
+ event character varying(128),
+ event_id character(36) NOT NULL,
+ txn_id integer,
+ name_tracker character varying(128),
+ v_tracker character varying(100),
+ v_collector character varying(100) NOT NULL,
+ v_etl character varying(100) NOT NULL,
+ user_id character varying(255),
+ user_ipaddress character varying(45),
+ user_fingerprint character varying(50),
+ domain_userid character varying(36),
+ domain_sessionidx smallint,
+ network_userid character varying(38),
+ geo_country character(2),
+ geo_region character(3),
+ geo_city character varying(75),
+ geo_zipcode character varying(15),
+ geo_latitude double precision,
+ geo_longitude double precision,
+ geo_region_name character varying(100),
+ ip_isp character varying(100),
+ ip_organization character varying(100),
+ ip_domain character varying(100),
+ ip_netspeed character varying(100),
+ page_url text,
+ page_title character varying(2000),
+ page_referrer text,
+ page_urlscheme character varying(16),
+ page_urlhost character varying(255),
+ page_urlport integer,
+ page_urlpath character varying(3000),
+ page_urlquery character varying(6000),
+ page_urlfragment character varying(3000),
+ refr_urlscheme character varying(16),
+ refr_urlhost character varying(255),
+ refr_urlport integer,
+ refr_urlpath character varying(6000),
+ refr_urlquery character varying(6000),
+ refr_urlfragment character varying(3000),
+ refr_medium character varying(25),
+ refr_source character varying(50),
+ refr_term character varying(255),
+ mkt_medium character varying(255),
+ mkt_source character varying(255),
+ mkt_term character varying(255),
+ mkt_content character varying(500),
+ mkt_campaign character varying(255),
+ se_category character varying(1000),
+ se_action character varying(1000),
+ se_label character varying(1000),
+ se_property character varying(1000),
+ se_value double precision,
+ tr_orderid character varying(255),
+ tr_affiliation character varying(255),
+ tr_total numeric(18,2),
+ tr_tax numeric(18,2),
+ tr_shipping numeric(18,2),
+ tr_city character varying(255),
+ tr_state character varying(255),
+ tr_country character varying(255),
+ ti_orderid character varying(255),
+ ti_sku character varying(255),
+ ti_name character varying(255),
+ ti_category character varying(255),
+ ti_price numeric(18,2),
+ ti_quantity integer,
+ pp_xoffset_min integer,
+ pp_xoffset_max integer,
+ pp_yoffset_min integer,
+ pp_yoffset_max integer,
+ useragent character varying(1000),
+ br_name character varying(50),
+ br_family character varying(50),
+ br_version character varying(50),
+ br_type character varying(50),
+ br_renderengine character varying(50),
+ br_lang character varying(255),
+ br_features_pdf boolean,
+ br_features_flash boolean,
+ br_features_java boolean,
+ br_features_director boolean,
+ br_features_quicktime boolean,
+ br_features_realplayer boolean,
+ br_features_windowsmedia boolean,
+ br_features_gears boolean,
+ br_features_silverlight boolean,
+ br_cookies boolean,
+ br_colordepth character varying(12),
+ br_viewwidth integer,
+ br_viewheight integer,
+ os_name character varying(50),
+ os_family character varying(50),
+ os_manufacturer character varying(50),
+ os_timezone character varying(50),
+ dvce_type character varying(50),
+ dvce_ismobile boolean,
+ dvce_screenwidth integer,
+ dvce_screenheight integer,
+ doc_charset character varying(128),
+ doc_width integer,
+ doc_height integer,
+ tr_currency character(3),
+ tr_total_base numeric(18,2),
+ tr_tax_base numeric(18,2),
+ tr_shipping_base numeric(18,2),
+ ti_currency character(3),
+ ti_price_base numeric(18,2),
+ base_currency character(3),
+ geo_timezone character varying(64),
+ mkt_clickid character varying(128),
+ mkt_network character varying(64),
+ etl_tags character varying(500),
+ dvce_sent_tstamp timestamp with time zone,
+ refr_domain_userid character varying(36),
+ refr_dvce_tstamp timestamp with time zone,
+ domain_sessionid character(36),
+ derived_tstamp timestamp with time zone,
+ event_vendor character varying(1000),
+ event_name character varying(1000),
+ event_format character varying(128),
+ event_version character varying(128),
+ event_fingerprint character varying(128),
+ true_tstamp timestamp with time zone
+);
+ALTER TABLE ONLY public.product_analytics_events_experimental ATTACH PARTITION gitlab_partitions_static.product_analytics_events_experimental_60 FOR VALUES WITH (modulus 64, remainder 60);
+
+CREATE TABLE gitlab_partitions_static.product_analytics_events_experimental_61 (
+ id bigint DEFAULT nextval('public.product_analytics_events_experimental_id_seq'::regclass) NOT NULL,
+ project_id integer NOT NULL,
+ platform character varying(255),
+ etl_tstamp timestamp with time zone,
+ collector_tstamp timestamp with time zone NOT NULL,
+ dvce_created_tstamp timestamp with time zone,
+ event character varying(128),
+ event_id character(36) NOT NULL,
+ txn_id integer,
+ name_tracker character varying(128),
+ v_tracker character varying(100),
+ v_collector character varying(100) NOT NULL,
+ v_etl character varying(100) NOT NULL,
+ user_id character varying(255),
+ user_ipaddress character varying(45),
+ user_fingerprint character varying(50),
+ domain_userid character varying(36),
+ domain_sessionidx smallint,
+ network_userid character varying(38),
+ geo_country character(2),
+ geo_region character(3),
+ geo_city character varying(75),
+ geo_zipcode character varying(15),
+ geo_latitude double precision,
+ geo_longitude double precision,
+ geo_region_name character varying(100),
+ ip_isp character varying(100),
+ ip_organization character varying(100),
+ ip_domain character varying(100),
+ ip_netspeed character varying(100),
+ page_url text,
+ page_title character varying(2000),
+ page_referrer text,
+ page_urlscheme character varying(16),
+ page_urlhost character varying(255),
+ page_urlport integer,
+ page_urlpath character varying(3000),
+ page_urlquery character varying(6000),
+ page_urlfragment character varying(3000),
+ refr_urlscheme character varying(16),
+ refr_urlhost character varying(255),
+ refr_urlport integer,
+ refr_urlpath character varying(6000),
+ refr_urlquery character varying(6000),
+ refr_urlfragment character varying(3000),
+ refr_medium character varying(25),
+ refr_source character varying(50),
+ refr_term character varying(255),
+ mkt_medium character varying(255),
+ mkt_source character varying(255),
+ mkt_term character varying(255),
+ mkt_content character varying(500),
+ mkt_campaign character varying(255),
+ se_category character varying(1000),
+ se_action character varying(1000),
+ se_label character varying(1000),
+ se_property character varying(1000),
+ se_value double precision,
+ tr_orderid character varying(255),
+ tr_affiliation character varying(255),
+ tr_total numeric(18,2),
+ tr_tax numeric(18,2),
+ tr_shipping numeric(18,2),
+ tr_city character varying(255),
+ tr_state character varying(255),
+ tr_country character varying(255),
+ ti_orderid character varying(255),
+ ti_sku character varying(255),
+ ti_name character varying(255),
+ ti_category character varying(255),
+ ti_price numeric(18,2),
+ ti_quantity integer,
+ pp_xoffset_min integer,
+ pp_xoffset_max integer,
+ pp_yoffset_min integer,
+ pp_yoffset_max integer,
+ useragent character varying(1000),
+ br_name character varying(50),
+ br_family character varying(50),
+ br_version character varying(50),
+ br_type character varying(50),
+ br_renderengine character varying(50),
+ br_lang character varying(255),
+ br_features_pdf boolean,
+ br_features_flash boolean,
+ br_features_java boolean,
+ br_features_director boolean,
+ br_features_quicktime boolean,
+ br_features_realplayer boolean,
+ br_features_windowsmedia boolean,
+ br_features_gears boolean,
+ br_features_silverlight boolean,
+ br_cookies boolean,
+ br_colordepth character varying(12),
+ br_viewwidth integer,
+ br_viewheight integer,
+ os_name character varying(50),
+ os_family character varying(50),
+ os_manufacturer character varying(50),
+ os_timezone character varying(50),
+ dvce_type character varying(50),
+ dvce_ismobile boolean,
+ dvce_screenwidth integer,
+ dvce_screenheight integer,
+ doc_charset character varying(128),
+ doc_width integer,
+ doc_height integer,
+ tr_currency character(3),
+ tr_total_base numeric(18,2),
+ tr_tax_base numeric(18,2),
+ tr_shipping_base numeric(18,2),
+ ti_currency character(3),
+ ti_price_base numeric(18,2),
+ base_currency character(3),
+ geo_timezone character varying(64),
+ mkt_clickid character varying(128),
+ mkt_network character varying(64),
+ etl_tags character varying(500),
+ dvce_sent_tstamp timestamp with time zone,
+ refr_domain_userid character varying(36),
+ refr_dvce_tstamp timestamp with time zone,
+ domain_sessionid character(36),
+ derived_tstamp timestamp with time zone,
+ event_vendor character varying(1000),
+ event_name character varying(1000),
+ event_format character varying(128),
+ event_version character varying(128),
+ event_fingerprint character varying(128),
+ true_tstamp timestamp with time zone
+);
+ALTER TABLE ONLY public.product_analytics_events_experimental ATTACH PARTITION gitlab_partitions_static.product_analytics_events_experimental_61 FOR VALUES WITH (modulus 64, remainder 61);
+
+CREATE TABLE gitlab_partitions_static.product_analytics_events_experimental_62 (
+ id bigint DEFAULT nextval('public.product_analytics_events_experimental_id_seq'::regclass) NOT NULL,
+ project_id integer NOT NULL,
+ platform character varying(255),
+ etl_tstamp timestamp with time zone,
+ collector_tstamp timestamp with time zone NOT NULL,
+ dvce_created_tstamp timestamp with time zone,
+ event character varying(128),
+ event_id character(36) NOT NULL,
+ txn_id integer,
+ name_tracker character varying(128),
+ v_tracker character varying(100),
+ v_collector character varying(100) NOT NULL,
+ v_etl character varying(100) NOT NULL,
+ user_id character varying(255),
+ user_ipaddress character varying(45),
+ user_fingerprint character varying(50),
+ domain_userid character varying(36),
+ domain_sessionidx smallint,
+ network_userid character varying(38),
+ geo_country character(2),
+ geo_region character(3),
+ geo_city character varying(75),
+ geo_zipcode character varying(15),
+ geo_latitude double precision,
+ geo_longitude double precision,
+ geo_region_name character varying(100),
+ ip_isp character varying(100),
+ ip_organization character varying(100),
+ ip_domain character varying(100),
+ ip_netspeed character varying(100),
+ page_url text,
+ page_title character varying(2000),
+ page_referrer text,
+ page_urlscheme character varying(16),
+ page_urlhost character varying(255),
+ page_urlport integer,
+ page_urlpath character varying(3000),
+ page_urlquery character varying(6000),
+ page_urlfragment character varying(3000),
+ refr_urlscheme character varying(16),
+ refr_urlhost character varying(255),
+ refr_urlport integer,
+ refr_urlpath character varying(6000),
+ refr_urlquery character varying(6000),
+ refr_urlfragment character varying(3000),
+ refr_medium character varying(25),
+ refr_source character varying(50),
+ refr_term character varying(255),
+ mkt_medium character varying(255),
+ mkt_source character varying(255),
+ mkt_term character varying(255),
+ mkt_content character varying(500),
+ mkt_campaign character varying(255),
+ se_category character varying(1000),
+ se_action character varying(1000),
+ se_label character varying(1000),
+ se_property character varying(1000),
+ se_value double precision,
+ tr_orderid character varying(255),
+ tr_affiliation character varying(255),
+ tr_total numeric(18,2),
+ tr_tax numeric(18,2),
+ tr_shipping numeric(18,2),
+ tr_city character varying(255),
+ tr_state character varying(255),
+ tr_country character varying(255),
+ ti_orderid character varying(255),
+ ti_sku character varying(255),
+ ti_name character varying(255),
+ ti_category character varying(255),
+ ti_price numeric(18,2),
+ ti_quantity integer,
+ pp_xoffset_min integer,
+ pp_xoffset_max integer,
+ pp_yoffset_min integer,
+ pp_yoffset_max integer,
+ useragent character varying(1000),
+ br_name character varying(50),
+ br_family character varying(50),
+ br_version character varying(50),
+ br_type character varying(50),
+ br_renderengine character varying(50),
+ br_lang character varying(255),
+ br_features_pdf boolean,
+ br_features_flash boolean,
+ br_features_java boolean,
+ br_features_director boolean,
+ br_features_quicktime boolean,
+ br_features_realplayer boolean,
+ br_features_windowsmedia boolean,
+ br_features_gears boolean,
+ br_features_silverlight boolean,
+ br_cookies boolean,
+ br_colordepth character varying(12),
+ br_viewwidth integer,
+ br_viewheight integer,
+ os_name character varying(50),
+ os_family character varying(50),
+ os_manufacturer character varying(50),
+ os_timezone character varying(50),
+ dvce_type character varying(50),
+ dvce_ismobile boolean,
+ dvce_screenwidth integer,
+ dvce_screenheight integer,
+ doc_charset character varying(128),
+ doc_width integer,
+ doc_height integer,
+ tr_currency character(3),
+ tr_total_base numeric(18,2),
+ tr_tax_base numeric(18,2),
+ tr_shipping_base numeric(18,2),
+ ti_currency character(3),
+ ti_price_base numeric(18,2),
+ base_currency character(3),
+ geo_timezone character varying(64),
+ mkt_clickid character varying(128),
+ mkt_network character varying(64),
+ etl_tags character varying(500),
+ dvce_sent_tstamp timestamp with time zone,
+ refr_domain_userid character varying(36),
+ refr_dvce_tstamp timestamp with time zone,
+ domain_sessionid character(36),
+ derived_tstamp timestamp with time zone,
+ event_vendor character varying(1000),
+ event_name character varying(1000),
+ event_format character varying(128),
+ event_version character varying(128),
+ event_fingerprint character varying(128),
+ true_tstamp timestamp with time zone
+);
+ALTER TABLE ONLY public.product_analytics_events_experimental ATTACH PARTITION gitlab_partitions_static.product_analytics_events_experimental_62 FOR VALUES WITH (modulus 64, remainder 62);
+
+CREATE TABLE gitlab_partitions_static.product_analytics_events_experimental_63 (
+ id bigint DEFAULT nextval('public.product_analytics_events_experimental_id_seq'::regclass) NOT NULL,
+ project_id integer NOT NULL,
+ platform character varying(255),
+ etl_tstamp timestamp with time zone,
+ collector_tstamp timestamp with time zone NOT NULL,
+ dvce_created_tstamp timestamp with time zone,
+ event character varying(128),
+ event_id character(36) NOT NULL,
+ txn_id integer,
+ name_tracker character varying(128),
+ v_tracker character varying(100),
+ v_collector character varying(100) NOT NULL,
+ v_etl character varying(100) NOT NULL,
+ user_id character varying(255),
+ user_ipaddress character varying(45),
+ user_fingerprint character varying(50),
+ domain_userid character varying(36),
+ domain_sessionidx smallint,
+ network_userid character varying(38),
+ geo_country character(2),
+ geo_region character(3),
+ geo_city character varying(75),
+ geo_zipcode character varying(15),
+ geo_latitude double precision,
+ geo_longitude double precision,
+ geo_region_name character varying(100),
+ ip_isp character varying(100),
+ ip_organization character varying(100),
+ ip_domain character varying(100),
+ ip_netspeed character varying(100),
+ page_url text,
+ page_title character varying(2000),
+ page_referrer text,
+ page_urlscheme character varying(16),
+ page_urlhost character varying(255),
+ page_urlport integer,
+ page_urlpath character varying(3000),
+ page_urlquery character varying(6000),
+ page_urlfragment character varying(3000),
+ refr_urlscheme character varying(16),
+ refr_urlhost character varying(255),
+ refr_urlport integer,
+ refr_urlpath character varying(6000),
+ refr_urlquery character varying(6000),
+ refr_urlfragment character varying(3000),
+ refr_medium character varying(25),
+ refr_source character varying(50),
+ refr_term character varying(255),
+ mkt_medium character varying(255),
+ mkt_source character varying(255),
+ mkt_term character varying(255),
+ mkt_content character varying(500),
+ mkt_campaign character varying(255),
+ se_category character varying(1000),
+ se_action character varying(1000),
+ se_label character varying(1000),
+ se_property character varying(1000),
+ se_value double precision,
+ tr_orderid character varying(255),
+ tr_affiliation character varying(255),
+ tr_total numeric(18,2),
+ tr_tax numeric(18,2),
+ tr_shipping numeric(18,2),
+ tr_city character varying(255),
+ tr_state character varying(255),
+ tr_country character varying(255),
+ ti_orderid character varying(255),
+ ti_sku character varying(255),
+ ti_name character varying(255),
+ ti_category character varying(255),
+ ti_price numeric(18,2),
+ ti_quantity integer,
+ pp_xoffset_min integer,
+ pp_xoffset_max integer,
+ pp_yoffset_min integer,
+ pp_yoffset_max integer,
+ useragent character varying(1000),
+ br_name character varying(50),
+ br_family character varying(50),
+ br_version character varying(50),
+ br_type character varying(50),
+ br_renderengine character varying(50),
+ br_lang character varying(255),
+ br_features_pdf boolean,
+ br_features_flash boolean,
+ br_features_java boolean,
+ br_features_director boolean,
+ br_features_quicktime boolean,
+ br_features_realplayer boolean,
+ br_features_windowsmedia boolean,
+ br_features_gears boolean,
+ br_features_silverlight boolean,
+ br_cookies boolean,
+ br_colordepth character varying(12),
+ br_viewwidth integer,
+ br_viewheight integer,
+ os_name character varying(50),
+ os_family character varying(50),
+ os_manufacturer character varying(50),
+ os_timezone character varying(50),
+ dvce_type character varying(50),
+ dvce_ismobile boolean,
+ dvce_screenwidth integer,
+ dvce_screenheight integer,
+ doc_charset character varying(128),
+ doc_width integer,
+ doc_height integer,
+ tr_currency character(3),
+ tr_total_base numeric(18,2),
+ tr_tax_base numeric(18,2),
+ tr_shipping_base numeric(18,2),
+ ti_currency character(3),
+ ti_price_base numeric(18,2),
+ base_currency character(3),
+ geo_timezone character varying(64),
+ mkt_clickid character varying(128),
+ mkt_network character varying(64),
+ etl_tags character varying(500),
+ dvce_sent_tstamp timestamp with time zone,
+ refr_domain_userid character varying(36),
+ refr_dvce_tstamp timestamp with time zone,
+ domain_sessionid character(36),
+ derived_tstamp timestamp with time zone,
+ event_vendor character varying(1000),
+ event_name character varying(1000),
+ event_format character varying(128),
+ event_version character varying(128),
+ event_fingerprint character varying(128),
+ true_tstamp timestamp with time zone
+);
+ALTER TABLE ONLY public.product_analytics_events_experimental ATTACH PARTITION gitlab_partitions_static.product_analytics_events_experimental_63 FOR VALUES WITH (modulus 64, remainder 63);
+
CREATE TABLE public.abuse_reports (
id integer NOT NULL,
reporter_id integer,
@@ -74,6 +8736,8 @@ CREATE TABLE public.alert_management_alerts (
monitoring_tool text,
hosts text[] DEFAULT '{}'::text[] NOT NULL,
payload jsonb DEFAULT '{}'::jsonb NOT NULL,
+ prometheus_alert_id integer,
+ environment_id integer,
CONSTRAINT check_2df3e2fdc1 CHECK ((char_length(monitoring_tool) <= 100)),
CONSTRAINT check_5e9e57cadb CHECK ((char_length(description) <= 1000)),
CONSTRAINT check_bac14dddde CHECK ((char_length(service) <= 100)),
@@ -136,7 +8800,8 @@ CREATE TABLE public.analytics_cycle_analytics_group_stages (
end_event_label_id bigint,
hidden boolean DEFAULT false NOT NULL,
custom boolean DEFAULT true NOT NULL,
- name character varying(255) NOT NULL
+ name character varying(255) NOT NULL,
+ group_value_stream_id bigint NOT NULL
);
CREATE SEQUENCE public.analytics_cycle_analytics_group_stages_id_seq
@@ -148,6 +8813,24 @@ CREATE SEQUENCE public.analytics_cycle_analytics_group_stages_id_seq
ALTER SEQUENCE public.analytics_cycle_analytics_group_stages_id_seq OWNED BY public.analytics_cycle_analytics_group_stages.id;
+CREATE TABLE public.analytics_cycle_analytics_group_value_streams (
+ id bigint NOT NULL,
+ created_at timestamp with time zone NOT NULL,
+ updated_at timestamp with time zone NOT NULL,
+ group_id bigint NOT NULL,
+ name text NOT NULL,
+ CONSTRAINT check_bc1ed5f1f7 CHECK ((char_length(name) <= 100))
+);
+
+CREATE SEQUENCE public.analytics_cycle_analytics_group_value_streams_id_seq
+ START WITH 1
+ INCREMENT BY 1
+ NO MINVALUE
+ NO MAXVALUE
+ CACHE 1;
+
+ALTER SEQUENCE public.analytics_cycle_analytics_group_value_streams_id_seq OWNED BY public.analytics_cycle_analytics_group_value_streams.id;
+
CREATE TABLE public.analytics_cycle_analytics_project_stages (
id bigint NOT NULL,
created_at timestamp with time zone NOT NULL,
@@ -466,7 +9149,7 @@ CREATE TABLE public.application_settings (
namespace_storage_size_limit bigint DEFAULT 0 NOT NULL,
seat_link_enabled boolean DEFAULT true NOT NULL,
container_expiration_policies_enable_historic_entries boolean DEFAULT false NOT NULL,
- issues_create_limit integer DEFAULT 300 NOT NULL,
+ issues_create_limit integer DEFAULT 0 NOT NULL,
push_rule_id bigint,
group_owners_can_manage_default_branch_protection boolean DEFAULT true NOT NULL,
container_registry_vendor text DEFAULT ''::text NOT NULL,
@@ -478,6 +9161,19 @@ CREATE TABLE public.application_settings (
repository_storages_weighted jsonb DEFAULT '{}'::jsonb NOT NULL,
max_import_size integer DEFAULT 50 NOT NULL,
enforce_pat_expiration boolean DEFAULT true NOT NULL,
+ compliance_frameworks smallint[] DEFAULT '{}'::smallint[] NOT NULL,
+ notify_on_unknown_sign_in boolean DEFAULT true NOT NULL,
+ default_branch_name text,
+ project_import_limit integer DEFAULT 6 NOT NULL,
+ project_export_limit integer DEFAULT 6 NOT NULL,
+ project_download_export_limit integer DEFAULT 1 NOT NULL,
+ group_import_limit integer DEFAULT 6 NOT NULL,
+ group_export_limit integer DEFAULT 6 NOT NULL,
+ group_download_export_limit integer DEFAULT 1 NOT NULL,
+ maintenance_mode boolean DEFAULT false NOT NULL,
+ maintenance_mode_message text,
+ CONSTRAINT check_51700b31b5 CHECK ((char_length(default_branch_name) <= 255)),
+ CONSTRAINT check_9c6c447a13 CHECK ((char_length(maintenance_mode_message) <= 255)),
CONSTRAINT check_d03919528d CHECK ((char_length(container_registry_vendor) <= 255)),
CONSTRAINT check_d820146492 CHECK ((char_length(spam_check_endpoint_url) <= 255)),
CONSTRAINT check_e5aba18f02 CHECK ((char_length(container_registry_version) <= 255))
@@ -698,7 +9394,11 @@ CREATE TABLE public.audit_events (
details text,
created_at timestamp without time zone,
updated_at timestamp without time zone,
- ip_address inet
+ ip_address inet,
+ author_name text,
+ entity_path text,
+ CONSTRAINT check_492aaa021d CHECK ((char_length(entity_path) <= 5500)),
+ CONSTRAINT check_83ff8406e2 CHECK ((char_length(author_name) <= 255))
);
CREATE SEQUENCE public.audit_events_id_seq
@@ -737,6 +9437,42 @@ CREATE TABLE public.aws_roles (
role_external_id character varying(64) NOT NULL
);
+CREATE TABLE public.background_migration_jobs (
+ id bigint NOT NULL,
+ created_at timestamp with time zone NOT NULL,
+ updated_at timestamp with time zone NOT NULL,
+ status smallint DEFAULT 0 NOT NULL,
+ class_name text NOT NULL,
+ arguments jsonb NOT NULL,
+ CONSTRAINT check_b0de0a5852 CHECK ((char_length(class_name) <= 200))
+);
+
+CREATE SEQUENCE public.background_migration_jobs_id_seq
+ START WITH 1
+ INCREMENT BY 1
+ NO MINVALUE
+ NO MAXVALUE
+ CACHE 1;
+
+ALTER SEQUENCE public.background_migration_jobs_id_seq OWNED BY public.background_migration_jobs.id;
+
+CREATE TABLE public.backup_labels (
+ id integer NOT NULL,
+ title character varying,
+ color character varying,
+ project_id integer,
+ created_at timestamp without time zone,
+ updated_at timestamp without time zone,
+ template boolean DEFAULT false,
+ description character varying,
+ description_html text,
+ type character varying,
+ group_id integer,
+ cached_markdown_version integer,
+ restore_action integer,
+ new_title character varying
+);
+
CREATE TABLE public.badges (
id integer NOT NULL,
link_url character varying NOT NULL,
@@ -1051,7 +9787,8 @@ CREATE TABLE public.ci_builds (
resource_group_id bigint,
waiting_for_resource_at timestamp with time zone,
processed boolean,
- scheduling_type smallint
+ scheduling_type smallint,
+ CONSTRAINT check_1e2fbd1b39 CHECK ((lock_version IS NOT NULL))
);
CREATE SEQUENCE public.ci_builds_id_seq
@@ -1074,7 +9811,8 @@ CREATE TABLE public.ci_builds_metadata (
config_variables jsonb,
has_exposed_artifacts boolean,
environment_auto_stop_in character varying(255),
- expanded_environment_name character varying(255)
+ expanded_environment_name character varying(255),
+ secrets jsonb DEFAULT '{}'::jsonb NOT NULL
);
CREATE SEQUENCE public.ci_builds_metadata_id_seq
@@ -1122,26 +9860,6 @@ CREATE SEQUENCE public.ci_daily_build_group_report_results_id_seq
ALTER SEQUENCE public.ci_daily_build_group_report_results_id_seq OWNED BY public.ci_daily_build_group_report_results.id;
-CREATE TABLE public.ci_daily_report_results (
- id bigint NOT NULL,
- date date NOT NULL,
- project_id bigint NOT NULL,
- last_pipeline_id bigint NOT NULL,
- value double precision NOT NULL,
- param_type bigint NOT NULL,
- ref_path character varying NOT NULL,
- title character varying NOT NULL
-);
-
-CREATE SEQUENCE public.ci_daily_report_results_id_seq
- START WITH 1
- INCREMENT BY 1
- NO MINVALUE
- NO MAXVALUE
- CACHE 1;
-
-ALTER SEQUENCE public.ci_daily_report_results_id_seq OWNED BY public.ci_daily_report_results.id;
-
CREATE TABLE public.ci_freeze_periods (
id bigint NOT NULL,
project_id bigint NOT NULL,
@@ -1221,7 +9939,8 @@ CREATE TABLE public.ci_job_artifacts (
file_sha256 bytea,
file_format smallint,
file_location smallint,
- locked boolean
+ locked boolean,
+ CONSTRAINT check_27f0f6dbab CHECK ((file_store IS NOT NULL))
);
CREATE SEQUENCE public.ci_job_artifacts_id_seq
@@ -1268,6 +9987,23 @@ CREATE SEQUENCE public.ci_pipeline_chat_data_id_seq
ALTER SEQUENCE public.ci_pipeline_chat_data_id_seq OWNED BY public.ci_pipeline_chat_data.id;
+CREATE TABLE public.ci_pipeline_messages (
+ id bigint NOT NULL,
+ severity smallint DEFAULT 0 NOT NULL,
+ pipeline_id integer NOT NULL,
+ content text NOT NULL,
+ CONSTRAINT check_58ca2981b2 CHECK ((char_length(content) <= 10000))
+);
+
+CREATE SEQUENCE public.ci_pipeline_messages_id_seq
+ START WITH 1
+ INCREMENT BY 1
+ NO MINVALUE
+ NO MAXVALUE
+ CACHE 1;
+
+ALTER SEQUENCE public.ci_pipeline_messages_id_seq OWNED BY public.ci_pipeline_messages.id;
+
CREATE TABLE public.ci_pipeline_schedule_variables (
id integer NOT NULL,
key character varying NOT NULL,
@@ -1361,7 +10097,9 @@ CREATE TABLE public.ci_pipelines (
source_sha bytea,
target_sha bytea,
external_pull_request_id bigint,
- ci_ref_id bigint
+ ci_ref_id bigint,
+ locked smallint DEFAULT 0 NOT NULL,
+ CONSTRAINT check_d7e99a025e CHECK ((lock_version IS NOT NULL))
);
CREATE TABLE public.ci_pipelines_config (
@@ -1546,7 +10284,8 @@ CREATE TABLE public.ci_stages (
name character varying,
status integer,
lock_version integer DEFAULT 0,
- "position" integer
+ "position" integer,
+ CONSTRAINT check_81b431e49b CHECK ((lock_version IS NOT NULL))
);
CREATE SEQUENCE public.ci_stages_id_seq
@@ -1789,6 +10528,24 @@ CREATE SEQUENCE public.clusters_applications_cert_managers_id_seq
ALTER SEQUENCE public.clusters_applications_cert_managers_id_seq OWNED BY public.clusters_applications_cert_managers.id;
+CREATE TABLE public.clusters_applications_cilium (
+ id bigint NOT NULL,
+ cluster_id bigint NOT NULL,
+ created_at timestamp with time zone NOT NULL,
+ updated_at timestamp with time zone NOT NULL,
+ status integer NOT NULL,
+ status_reason text
+);
+
+CREATE SEQUENCE public.clusters_applications_cilium_id_seq
+ START WITH 1
+ INCREMENT BY 1
+ NO MINVALUE
+ NO MAXVALUE
+ CACHE 1;
+
+ALTER SEQUENCE public.clusters_applications_cilium_id_seq OWNED BY public.clusters_applications_cilium.id;
+
CREATE TABLE public.clusters_applications_crossplane (
id integer NOT NULL,
created_at timestamp with time zone NOT NULL,
@@ -2113,6 +10870,63 @@ CREATE SEQUENCE public.conversational_development_index_metrics_id_seq
ALTER SEQUENCE public.conversational_development_index_metrics_id_seq OWNED BY public.conversational_development_index_metrics.id;
+CREATE TABLE public.custom_emoji (
+ id bigint NOT NULL,
+ namespace_id bigint NOT NULL,
+ created_at timestamp with time zone NOT NULL,
+ updated_at timestamp with time zone NOT NULL,
+ name text NOT NULL,
+ file text NOT NULL,
+ CONSTRAINT check_8c586dd507 CHECK ((char_length(name) <= 36)),
+ CONSTRAINT check_dd5d60f1fb CHECK ((char_length(file) <= 255))
+);
+
+CREATE SEQUENCE public.custom_emoji_id_seq
+ START WITH 1
+ INCREMENT BY 1
+ NO MINVALUE
+ NO MAXVALUE
+ CACHE 1;
+
+ALTER SEQUENCE public.custom_emoji_id_seq OWNED BY public.custom_emoji.id;
+
+CREATE TABLE public.dast_site_profiles (
+ id bigint NOT NULL,
+ project_id bigint NOT NULL,
+ dast_site_id bigint NOT NULL,
+ created_at timestamp with time zone NOT NULL,
+ updated_at timestamp with time zone NOT NULL,
+ name text NOT NULL,
+ CONSTRAINT check_6cfab17b48 CHECK ((char_length(name) <= 255))
+);
+
+CREATE SEQUENCE public.dast_site_profiles_id_seq
+ START WITH 1
+ INCREMENT BY 1
+ NO MINVALUE
+ NO MAXVALUE
+ CACHE 1;
+
+ALTER SEQUENCE public.dast_site_profiles_id_seq OWNED BY public.dast_site_profiles.id;
+
+CREATE TABLE public.dast_sites (
+ id bigint NOT NULL,
+ project_id bigint NOT NULL,
+ created_at timestamp with time zone NOT NULL,
+ updated_at timestamp with time zone NOT NULL,
+ url text NOT NULL,
+ CONSTRAINT check_46df8b449c CHECK ((char_length(url) <= 255))
+);
+
+CREATE SEQUENCE public.dast_sites_id_seq
+ START WITH 1
+ INCREMENT BY 1
+ NO MINVALUE
+ NO MAXVALUE
+ CACHE 1;
+
+ALTER SEQUENCE public.dast_sites_id_seq OWNED BY public.dast_sites.id;
+
CREATE TABLE public.dependency_proxy_blobs (
id integer NOT NULL,
group_id integer NOT NULL,
@@ -2368,6 +11182,33 @@ CREATE SEQUENCE public.draft_notes_id_seq
ALTER SEQUENCE public.draft_notes_id_seq OWNED BY public.draft_notes.id;
+CREATE TABLE public.elastic_reindexing_tasks (
+ id bigint NOT NULL,
+ created_at timestamp with time zone NOT NULL,
+ updated_at timestamp with time zone NOT NULL,
+ documents_count integer,
+ state smallint DEFAULT 0 NOT NULL,
+ in_progress boolean DEFAULT true NOT NULL,
+ index_name_from text,
+ index_name_to text,
+ elastic_task text,
+ error_message text,
+ documents_count_target integer,
+ CONSTRAINT check_04151aca42 CHECK ((char_length(index_name_from) <= 255)),
+ CONSTRAINT check_7f64acda8e CHECK ((char_length(error_message) <= 255)),
+ CONSTRAINT check_85ebff7124 CHECK ((char_length(index_name_to) <= 255)),
+ CONSTRAINT check_942e5aae53 CHECK ((char_length(elastic_task) <= 255))
+);
+
+CREATE SEQUENCE public.elastic_reindexing_tasks_id_seq
+ START WITH 1
+ INCREMENT BY 1
+ NO MINVALUE
+ NO MAXVALUE
+ CACHE 1;
+
+ALTER SEQUENCE public.elastic_reindexing_tasks_id_seq OWNED BY public.elastic_reindexing_tasks.id;
+
CREATE TABLE public.elasticsearch_indexed_namespaces (
created_at timestamp with time zone NOT NULL,
updated_at timestamp with time zone NOT NULL,
@@ -2505,7 +11346,8 @@ CREATE TABLE public.epics (
start_date_sourcing_epic_id integer,
due_date_sourcing_epic_id integer,
confidential boolean DEFAULT false NOT NULL,
- external_key character varying(255)
+ external_key character varying(255),
+ CONSTRAINT check_fcfb4a93ff CHECK ((lock_version IS NOT NULL))
);
CREATE SEQUENCE public.epics_id_seq
@@ -3199,6 +12041,23 @@ CREATE TABLE public.group_deploy_keys (
CONSTRAINT check_f58fa0a0f7 CHECK ((char_length(key) <= 4096))
);
+CREATE TABLE public.group_deploy_keys_groups (
+ id bigint NOT NULL,
+ created_at timestamp with time zone NOT NULL,
+ updated_at timestamp with time zone NOT NULL,
+ group_id bigint NOT NULL,
+ group_deploy_key_id bigint NOT NULL
+);
+
+CREATE SEQUENCE public.group_deploy_keys_groups_id_seq
+ START WITH 1
+ INCREMENT BY 1
+ NO MINVALUE
+ NO MAXVALUE
+ CACHE 1;
+
+ALTER SEQUENCE public.group_deploy_keys_groups_id_seq OWNED BY public.group_deploy_keys_groups.id;
+
CREATE SEQUENCE public.group_deploy_keys_id_seq
START WITH 1
INCREMENT BY 1
@@ -3531,7 +12390,8 @@ CREATE TABLE public.issues (
promoted_to_epic_id integer,
health_status smallint,
external_key character varying(255),
- sprint_id bigint
+ sprint_id bigint,
+ CONSTRAINT check_fba63f706d CHECK ((lock_version IS NOT NULL))
);
CREATE SEQUENCE public.issues_id_seq
@@ -3634,7 +12494,10 @@ CREATE TABLE public.jira_tracker_data (
encrypted_username_iv character varying,
encrypted_password character varying,
encrypted_password_iv character varying,
- jira_issue_transition_id character varying
+ jira_issue_transition_id character varying,
+ project_key text,
+ issues_enabled boolean DEFAULT false NOT NULL,
+ CONSTRAINT check_214cf6a48b CHECK ((char_length(project_key) <= 255))
);
CREATE SEQUENCE public.jira_tracker_data_id_seq
@@ -3774,7 +12637,8 @@ CREATE TABLE public.lfs_objects (
created_at timestamp without time zone,
updated_at timestamp without time zone,
file character varying,
- file_store integer DEFAULT 1
+ file_store integer DEFAULT 1,
+ CONSTRAINT check_eecfc5717d CHECK ((file_store IS NOT NULL))
);
CREATE SEQUENCE public.lfs_objects_id_seq
@@ -4110,7 +12974,8 @@ CREATE TABLE public.merge_requests (
state_id smallint DEFAULT 1 NOT NULL,
rebase_jid character varying,
squash_commit_sha bytea,
- sprint_id bigint
+ sprint_id bigint,
+ CONSTRAINT check_970d272570 CHECK ((lock_version IS NOT NULL))
);
CREATE TABLE public.merge_requests_closing_issues (
@@ -4236,6 +13101,13 @@ CREATE TABLE public.namespace_aggregation_schedules (
namespace_id integer NOT NULL
);
+CREATE TABLE public.namespace_limits (
+ additional_purchased_storage_size bigint DEFAULT 0 NOT NULL,
+ additional_purchased_storage_ends_on date,
+ namespace_id integer NOT NULL,
+ temporary_storage_increase_ends_on date
+);
+
CREATE TABLE public.namespace_root_storage_statistics (
namespace_id integer NOT NULL,
updated_at timestamp with time zone NOT NULL,
@@ -4244,7 +13116,14 @@ CREATE TABLE public.namespace_root_storage_statistics (
wiki_size bigint DEFAULT 0 NOT NULL,
build_artifacts_size bigint DEFAULT 0 NOT NULL,
storage_size bigint DEFAULT 0 NOT NULL,
- packages_size bigint DEFAULT 0 NOT NULL
+ packages_size bigint DEFAULT 0 NOT NULL,
+ snippets_size bigint DEFAULT 0 NOT NULL
+);
+
+CREATE TABLE public.namespace_settings (
+ created_at timestamp with time zone NOT NULL,
+ updated_at timestamp with time zone NOT NULL,
+ namespace_id integer NOT NULL
);
CREATE TABLE public.namespace_statistics (
@@ -4308,7 +13187,11 @@ CREATE TABLE public.namespaces (
default_branch_protection smallint,
unlock_membership_to_ldap boolean,
max_personal_access_token_lifetime integer,
- push_rule_id bigint
+ push_rule_id bigint,
+ shared_runners_enabled boolean DEFAULT true NOT NULL,
+ allow_descendants_override_disabled_shared_runners boolean DEFAULT false NOT NULL,
+ traversal_ids integer[] DEFAULT '{}'::integer[] NOT NULL,
+ delayed_project_removal boolean DEFAULT false NOT NULL
);
CREATE SEQUENCE public.namespaces_id_seq
@@ -4957,7 +13840,8 @@ CREATE TABLE public.personal_access_tokens (
'::character varying NOT NULL,
impersonation boolean DEFAULT false NOT NULL,
token_digest character varying,
- expire_notification_delivered boolean DEFAULT false NOT NULL
+ expire_notification_delivered boolean DEFAULT false NOT NULL,
+ last_used_at timestamp with time zone
);
CREATE SEQUENCE public.personal_access_tokens_id_seq
@@ -4980,7 +13864,34 @@ CREATE TABLE public.plan_limits (
ci_project_subscriptions integer DEFAULT 2 NOT NULL,
ci_pipeline_schedules integer DEFAULT 10 NOT NULL,
offset_pagination_limit integer DEFAULT 50000 NOT NULL,
- ci_instance_level_variables integer DEFAULT 25 NOT NULL
+ ci_instance_level_variables integer DEFAULT 25 NOT NULL,
+ storage_size_limit integer DEFAULT 0 NOT NULL,
+ ci_max_artifact_size_lsif integer DEFAULT 20 NOT NULL,
+ ci_max_artifact_size_archive integer DEFAULT 0 NOT NULL,
+ ci_max_artifact_size_metadata integer DEFAULT 0 NOT NULL,
+ ci_max_artifact_size_trace integer DEFAULT 0 NOT NULL,
+ ci_max_artifact_size_junit integer DEFAULT 0 NOT NULL,
+ ci_max_artifact_size_sast integer DEFAULT 0 NOT NULL,
+ ci_max_artifact_size_dependency_scanning integer DEFAULT 0 NOT NULL,
+ ci_max_artifact_size_container_scanning integer DEFAULT 0 NOT NULL,
+ ci_max_artifact_size_dast integer DEFAULT 0 NOT NULL,
+ ci_max_artifact_size_codequality integer DEFAULT 0 NOT NULL,
+ ci_max_artifact_size_license_management integer DEFAULT 0 NOT NULL,
+ ci_max_artifact_size_license_scanning integer DEFAULT 0 NOT NULL,
+ ci_max_artifact_size_performance integer DEFAULT 0 NOT NULL,
+ ci_max_artifact_size_metrics integer DEFAULT 0 NOT NULL,
+ ci_max_artifact_size_metrics_referee integer DEFAULT 0 NOT NULL,
+ ci_max_artifact_size_network_referee integer DEFAULT 0 NOT NULL,
+ ci_max_artifact_size_dotenv integer DEFAULT 0 NOT NULL,
+ ci_max_artifact_size_cobertura integer DEFAULT 0 NOT NULL,
+ ci_max_artifact_size_terraform integer DEFAULT 0 NOT NULL,
+ ci_max_artifact_size_accessibility integer DEFAULT 0 NOT NULL,
+ ci_max_artifact_size_cluster_applications integer DEFAULT 0 NOT NULL,
+ ci_max_artifact_size_secret_detection integer DEFAULT 0 NOT NULL,
+ ci_max_artifact_size_requirements integer DEFAULT 0 NOT NULL,
+ ci_max_artifact_size_coverage_fuzzing integer DEFAULT 0 NOT NULL,
+ ci_max_artifact_size_browser_performance integer DEFAULT 0 NOT NULL,
+ ci_max_artifact_size_load_performance integer DEFAULT 0 NOT NULL
);
CREATE SEQUENCE public.plan_limits_id_seq
@@ -5276,7 +14187,12 @@ CREATE TABLE public.project_incident_management_settings (
project_id integer NOT NULL,
create_issue boolean DEFAULT false NOT NULL,
send_email boolean DEFAULT false NOT NULL,
- issue_template_key text
+ issue_template_key text,
+ pagerduty_active boolean DEFAULT false NOT NULL,
+ encrypted_pagerduty_token bytea,
+ encrypted_pagerduty_token_iv bytea,
+ CONSTRAINT pagerduty_token_iv_length_constraint CHECK ((octet_length(encrypted_pagerduty_token_iv) <= 12)),
+ CONSTRAINT pagerduty_token_length_constraint CHECK ((octet_length(encrypted_pagerduty_token) <= 255))
);
CREATE SEQUENCE public.project_incident_management_settings_project_id_seq
@@ -5410,6 +14326,8 @@ CREATE TABLE public.project_settings (
push_rule_id bigint,
show_default_award_emojis boolean DEFAULT true,
allow_merge_on_skipped_pipeline boolean,
+ squash_option smallint DEFAULT 3,
+ has_confluence boolean DEFAULT false NOT NULL,
CONSTRAINT check_bde223416c CHECK ((show_default_award_emojis IS NOT NULL))
);
@@ -5425,7 +14343,8 @@ CREATE TABLE public.project_statistics (
shared_runners_seconds bigint DEFAULT 0 NOT NULL,
shared_runners_seconds_last_reset timestamp without time zone,
packages_size bigint DEFAULT 0 NOT NULL,
- wiki_size bigint
+ wiki_size bigint,
+ snippets_size bigint
);
CREATE SEQUENCE public.project_statistics_id_seq
@@ -5637,7 +14556,8 @@ CREATE TABLE public.protected_branch_push_access_levels (
created_at timestamp without time zone NOT NULL,
updated_at timestamp without time zone NOT NULL,
user_id integer,
- group_id integer
+ group_id integer,
+ deploy_key_id integer
);
CREATE SEQUENCE public.protected_branch_push_access_levels_id_seq
@@ -5989,6 +14909,11 @@ CREATE TABLE public.resource_state_events (
created_at timestamp with time zone NOT NULL,
state smallint NOT NULL,
epic_id integer,
+ source_commit text,
+ close_after_error_tracking_resolve boolean DEFAULT false NOT NULL,
+ close_auto_resolve_prometheus_alert boolean DEFAULT false NOT NULL,
+ source_merge_request_id bigint,
+ CONSTRAINT check_f0bcfaa3a2 CHECK ((char_length(source_commit) <= 40)),
CONSTRAINT state_events_must_belong_to_issue_or_merge_request_or_epic CHECK ((((issue_id <> NULL::bigint) AND (merge_request_id IS NULL) AND (epic_id IS NULL)) OR ((issue_id IS NULL) AND (merge_request_id <> NULL::bigint) AND (epic_id IS NULL)) OR ((issue_id IS NULL) AND (merge_request_id IS NULL) AND (epic_id <> NULL::integer))))
);
@@ -6307,6 +15232,13 @@ CREATE TABLE public.snippet_repositories (
disk_path character varying(80) NOT NULL
);
+CREATE TABLE public.snippet_statistics (
+ snippet_id bigint NOT NULL,
+ repository_size bigint DEFAULT 0 NOT NULL,
+ file_count bigint DEFAULT 0 NOT NULL,
+ commit_count bigint DEFAULT 0 NOT NULL
+);
+
CREATE TABLE public.snippet_user_mentions (
id bigint NOT NULL,
snippet_id integer NOT NULL,
@@ -6708,7 +15640,8 @@ CREATE TABLE public.uploads (
created_at timestamp without time zone NOT NULL,
store integer DEFAULT 1,
mount_point character varying,
- secret character varying
+ secret character varying,
+ CONSTRAINT check_5e9547379c CHECK ((store IS NOT NULL))
);
CREATE SEQUENCE public.uploads_id_seq
@@ -6794,7 +15727,11 @@ ALTER SEQUENCE public.user_custom_attributes_id_seq OWNED BY public.user_custom_
CREATE TABLE public.user_details (
user_id bigint NOT NULL,
job_title character varying(200) DEFAULT ''::character varying NOT NULL,
- bio character varying(255) DEFAULT ''::character varying NOT NULL
+ bio character varying(255) DEFAULT ''::character varying NOT NULL,
+ bio_html text,
+ cached_markdown_version integer,
+ webauthn_xid text,
+ CONSTRAINT check_245664af82 CHECK ((char_length(webauthn_xid) <= 100))
);
CREATE SEQUENCE public.user_details_user_id_seq
@@ -6841,7 +15778,8 @@ CREATE TABLE public.user_preferences (
render_whitespace_in_code boolean,
tab_width smallint,
feature_filter_type bigint,
- experience_level smallint
+ experience_level smallint,
+ view_diffs_file_by_file boolean DEFAULT false NOT NULL
);
CREATE SEQUENCE public.user_preferences_id_seq
@@ -7245,7 +16183,8 @@ CREATE TABLE public.vulnerability_scanners (
updated_at timestamp with time zone NOT NULL,
project_id integer NOT NULL,
external_id character varying NOT NULL,
- name character varying NOT NULL
+ name character varying NOT NULL,
+ vendor text DEFAULT 'GitLab'::text NOT NULL
);
CREATE SEQUENCE public.vulnerability_scanners_id_seq
@@ -7257,6 +16196,30 @@ CREATE SEQUENCE public.vulnerability_scanners_id_seq
ALTER SEQUENCE public.vulnerability_scanners_id_seq OWNED BY public.vulnerability_scanners.id;
+CREATE TABLE public.vulnerability_statistics (
+ id bigint NOT NULL,
+ created_at timestamp with time zone NOT NULL,
+ updated_at timestamp with time zone NOT NULL,
+ project_id bigint NOT NULL,
+ total integer DEFAULT 0 NOT NULL,
+ critical integer DEFAULT 0 NOT NULL,
+ high integer DEFAULT 0 NOT NULL,
+ medium integer DEFAULT 0 NOT NULL,
+ low integer DEFAULT 0 NOT NULL,
+ unknown integer DEFAULT 0 NOT NULL,
+ info integer DEFAULT 0 NOT NULL,
+ letter_grade smallint NOT NULL
+);
+
+CREATE SEQUENCE public.vulnerability_statistics_id_seq
+ START WITH 1
+ INCREMENT BY 1
+ NO MINVALUE
+ NO MAXVALUE
+ CACHE 1;
+
+ALTER SEQUENCE public.vulnerability_statistics_id_seq OWNED BY public.vulnerability_statistics.id;
+
CREATE TABLE public.vulnerability_user_mentions (
id bigint NOT NULL,
vulnerability_id bigint NOT NULL,
@@ -7336,6 +16299,28 @@ CREATE SEQUENCE public.web_hooks_id_seq
ALTER SEQUENCE public.web_hooks_id_seq OWNED BY public.web_hooks.id;
+CREATE TABLE public.webauthn_registrations (
+ id bigint NOT NULL,
+ user_id bigint NOT NULL,
+ counter bigint DEFAULT 0 NOT NULL,
+ created_at timestamp with time zone NOT NULL,
+ updated_at timestamp with time zone NOT NULL,
+ credential_xid text NOT NULL,
+ name text NOT NULL,
+ public_key text NOT NULL,
+ CONSTRAINT check_242f0cc65c CHECK ((char_length(credential_xid) <= 255)),
+ CONSTRAINT check_2f02e74321 CHECK ((char_length(name) <= 255))
+);
+
+CREATE SEQUENCE public.webauthn_registrations_id_seq
+ START WITH 1
+ INCREMENT BY 1
+ NO MINVALUE
+ NO MAXVALUE
+ CACHE 1;
+
+ALTER SEQUENCE public.webauthn_registrations_id_seq OWNED BY public.webauthn_registrations.id;
+
CREATE TABLE public.wiki_page_meta (
id integer NOT NULL,
project_id bigint NOT NULL,
@@ -7462,6 +16447,8 @@ ALTER TABLE ONLY public.allowed_email_domains ALTER COLUMN id SET DEFAULT nextva
ALTER TABLE ONLY public.analytics_cycle_analytics_group_stages ALTER COLUMN id SET DEFAULT nextval('public.analytics_cycle_analytics_group_stages_id_seq'::regclass);
+ALTER TABLE ONLY public.analytics_cycle_analytics_group_value_streams ALTER COLUMN id SET DEFAULT nextval('public.analytics_cycle_analytics_group_value_streams_id_seq'::regclass);
+
ALTER TABLE ONLY public.analytics_cycle_analytics_project_stages ALTER COLUMN id SET DEFAULT nextval('public.analytics_cycle_analytics_project_stages_id_seq'::regclass);
ALTER TABLE ONLY public.appearances ALTER COLUMN id SET DEFAULT nextval('public.appearances_id_seq'::regclass);
@@ -7496,6 +16483,8 @@ ALTER TABLE ONLY public.audit_events ALTER COLUMN id SET DEFAULT nextval('public
ALTER TABLE ONLY public.award_emoji ALTER COLUMN id SET DEFAULT nextval('public.award_emoji_id_seq'::regclass);
+ALTER TABLE ONLY public.background_migration_jobs ALTER COLUMN id SET DEFAULT nextval('public.background_migration_jobs_id_seq'::regclass);
+
ALTER TABLE ONLY public.badges ALTER COLUMN id SET DEFAULT nextval('public.badges_id_seq'::regclass);
ALTER TABLE ONLY public.board_assignees ALTER COLUMN id SET DEFAULT nextval('public.board_assignees_id_seq'::regclass);
@@ -7532,8 +16521,6 @@ ALTER TABLE ONLY public.ci_builds_runner_session ALTER COLUMN id SET DEFAULT nex
ALTER TABLE ONLY public.ci_daily_build_group_report_results ALTER COLUMN id SET DEFAULT nextval('public.ci_daily_build_group_report_results_id_seq'::regclass);
-ALTER TABLE ONLY public.ci_daily_report_results ALTER COLUMN id SET DEFAULT nextval('public.ci_daily_report_results_id_seq'::regclass);
-
ALTER TABLE ONLY public.ci_freeze_periods ALTER COLUMN id SET DEFAULT nextval('public.ci_freeze_periods_id_seq'::regclass);
ALTER TABLE ONLY public.ci_group_variables ALTER COLUMN id SET DEFAULT nextval('public.ci_group_variables_id_seq'::regclass);
@@ -7546,6 +16533,8 @@ ALTER TABLE ONLY public.ci_job_variables ALTER COLUMN id SET DEFAULT nextval('pu
ALTER TABLE ONLY public.ci_pipeline_chat_data ALTER COLUMN id SET DEFAULT nextval('public.ci_pipeline_chat_data_id_seq'::regclass);
+ALTER TABLE ONLY public.ci_pipeline_messages ALTER COLUMN id SET DEFAULT nextval('public.ci_pipeline_messages_id_seq'::regclass);
+
ALTER TABLE ONLY public.ci_pipeline_schedule_variables ALTER COLUMN id SET DEFAULT nextval('public.ci_pipeline_schedule_variables_id_seq'::regclass);
ALTER TABLE ONLY public.ci_pipeline_schedules ALTER COLUMN id SET DEFAULT nextval('public.ci_pipeline_schedules_id_seq'::regclass);
@@ -7596,6 +16585,8 @@ ALTER TABLE ONLY public.clusters ALTER COLUMN id SET DEFAULT nextval('public.clu
ALTER TABLE ONLY public.clusters_applications_cert_managers ALTER COLUMN id SET DEFAULT nextval('public.clusters_applications_cert_managers_id_seq'::regclass);
+ALTER TABLE ONLY public.clusters_applications_cilium ALTER COLUMN id SET DEFAULT nextval('public.clusters_applications_cilium_id_seq'::regclass);
+
ALTER TABLE ONLY public.clusters_applications_crossplane ALTER COLUMN id SET DEFAULT nextval('public.clusters_applications_crossplane_id_seq'::regclass);
ALTER TABLE ONLY public.clusters_applications_elastic_stacks ALTER COLUMN id SET DEFAULT nextval('public.clusters_applications_elastic_stacks_id_seq'::regclass);
@@ -7622,6 +16613,12 @@ ALTER TABLE ONLY public.container_repositories ALTER COLUMN id SET DEFAULT nextv
ALTER TABLE ONLY public.conversational_development_index_metrics ALTER COLUMN id SET DEFAULT nextval('public.conversational_development_index_metrics_id_seq'::regclass);
+ALTER TABLE ONLY public.custom_emoji ALTER COLUMN id SET DEFAULT nextval('public.custom_emoji_id_seq'::regclass);
+
+ALTER TABLE ONLY public.dast_site_profiles ALTER COLUMN id SET DEFAULT nextval('public.dast_site_profiles_id_seq'::regclass);
+
+ALTER TABLE ONLY public.dast_sites ALTER COLUMN id SET DEFAULT nextval('public.dast_sites_id_seq'::regclass);
+
ALTER TABLE ONLY public.dependency_proxy_blobs ALTER COLUMN id SET DEFAULT nextval('public.dependency_proxy_blobs_id_seq'::regclass);
ALTER TABLE ONLY public.dependency_proxy_group_settings ALTER COLUMN id SET DEFAULT nextval('public.dependency_proxy_group_settings_id_seq'::regclass);
@@ -7646,6 +16643,8 @@ ALTER TABLE ONLY public.diff_note_positions ALTER COLUMN id SET DEFAULT nextval(
ALTER TABLE ONLY public.draft_notes ALTER COLUMN id SET DEFAULT nextval('public.draft_notes_id_seq'::regclass);
+ALTER TABLE ONLY public.elastic_reindexing_tasks ALTER COLUMN id SET DEFAULT nextval('public.elastic_reindexing_tasks_id_seq'::regclass);
+
ALTER TABLE ONLY public.emails ALTER COLUMN id SET DEFAULT nextval('public.emails_id_seq'::regclass);
ALTER TABLE ONLY public.environments ALTER COLUMN id SET DEFAULT nextval('public.environments_id_seq'::regclass);
@@ -7724,6 +16723,8 @@ ALTER TABLE ONLY public.group_custom_attributes ALTER COLUMN id SET DEFAULT next
ALTER TABLE ONLY public.group_deploy_keys ALTER COLUMN id SET DEFAULT nextval('public.group_deploy_keys_id_seq'::regclass);
+ALTER TABLE ONLY public.group_deploy_keys_groups ALTER COLUMN id SET DEFAULT nextval('public.group_deploy_keys_groups_id_seq'::regclass);
+
ALTER TABLE ONLY public.group_deploy_tokens ALTER COLUMN id SET DEFAULT nextval('public.group_deploy_tokens_id_seq'::regclass);
ALTER TABLE ONLY public.group_group_links ALTER COLUMN id SET DEFAULT nextval('public.group_group_links_id_seq'::regclass);
@@ -7882,6 +16883,8 @@ ALTER TABLE ONLY public.plans ALTER COLUMN id SET DEFAULT nextval('public.plans_
ALTER TABLE ONLY public.pool_repositories ALTER COLUMN id SET DEFAULT nextval('public.pool_repositories_id_seq'::regclass);
+ALTER TABLE ONLY public.product_analytics_events_experimental ALTER COLUMN id SET DEFAULT nextval('public.product_analytics_events_experimental_id_seq'::regclass);
+
ALTER TABLE ONLY public.programming_languages ALTER COLUMN id SET DEFAULT nextval('public.programming_languages_id_seq'::regclass);
ALTER TABLE ONLY public.project_aliases ALTER COLUMN id SET DEFAULT nextval('public.project_aliases_id_seq'::regclass);
@@ -8076,12 +17079,16 @@ ALTER TABLE ONLY public.vulnerability_occurrences ALTER COLUMN id SET DEFAULT ne
ALTER TABLE ONLY public.vulnerability_scanners ALTER COLUMN id SET DEFAULT nextval('public.vulnerability_scanners_id_seq'::regclass);
+ALTER TABLE ONLY public.vulnerability_statistics ALTER COLUMN id SET DEFAULT nextval('public.vulnerability_statistics_id_seq'::regclass);
+
ALTER TABLE ONLY public.vulnerability_user_mentions ALTER COLUMN id SET DEFAULT nextval('public.vulnerability_user_mentions_id_seq'::regclass);
ALTER TABLE ONLY public.web_hook_logs ALTER COLUMN id SET DEFAULT nextval('public.web_hook_logs_id_seq'::regclass);
ALTER TABLE ONLY public.web_hooks ALTER COLUMN id SET DEFAULT nextval('public.web_hooks_id_seq'::regclass);
+ALTER TABLE ONLY public.webauthn_registrations ALTER COLUMN id SET DEFAULT nextval('public.webauthn_registrations_id_seq'::regclass);
+
ALTER TABLE ONLY public.wiki_page_meta ALTER COLUMN id SET DEFAULT nextval('public.wiki_page_meta_id_seq'::regclass);
ALTER TABLE ONLY public.wiki_page_slugs ALTER COLUMN id SET DEFAULT nextval('public.wiki_page_slugs_id_seq'::regclass);
@@ -8094,6 +17101,201 @@ ALTER TABLE ONLY public.x509_issuers ALTER COLUMN id SET DEFAULT nextval('public
ALTER TABLE ONLY public.zoom_meetings ALTER COLUMN id SET DEFAULT nextval('public.zoom_meetings_id_seq'::regclass);
+ALTER TABLE ONLY public.product_analytics_events_experimental
+ ADD CONSTRAINT product_analytics_events_experimental_pkey PRIMARY KEY (id, project_id);
+
+ALTER TABLE ONLY gitlab_partitions_static.product_analytics_events_experimental_00
+ ADD CONSTRAINT product_analytics_events_experimental_00_pkey PRIMARY KEY (id, project_id);
+
+ALTER TABLE ONLY gitlab_partitions_static.product_analytics_events_experimental_01
+ ADD CONSTRAINT product_analytics_events_experimental_01_pkey PRIMARY KEY (id, project_id);
+
+ALTER TABLE ONLY gitlab_partitions_static.product_analytics_events_experimental_02
+ ADD CONSTRAINT product_analytics_events_experimental_02_pkey PRIMARY KEY (id, project_id);
+
+ALTER TABLE ONLY gitlab_partitions_static.product_analytics_events_experimental_03
+ ADD CONSTRAINT product_analytics_events_experimental_03_pkey PRIMARY KEY (id, project_id);
+
+ALTER TABLE ONLY gitlab_partitions_static.product_analytics_events_experimental_04
+ ADD CONSTRAINT product_analytics_events_experimental_04_pkey PRIMARY KEY (id, project_id);
+
+ALTER TABLE ONLY gitlab_partitions_static.product_analytics_events_experimental_05
+ ADD CONSTRAINT product_analytics_events_experimental_05_pkey PRIMARY KEY (id, project_id);
+
+ALTER TABLE ONLY gitlab_partitions_static.product_analytics_events_experimental_06
+ ADD CONSTRAINT product_analytics_events_experimental_06_pkey PRIMARY KEY (id, project_id);
+
+ALTER TABLE ONLY gitlab_partitions_static.product_analytics_events_experimental_07
+ ADD CONSTRAINT product_analytics_events_experimental_07_pkey PRIMARY KEY (id, project_id);
+
+ALTER TABLE ONLY gitlab_partitions_static.product_analytics_events_experimental_08
+ ADD CONSTRAINT product_analytics_events_experimental_08_pkey PRIMARY KEY (id, project_id);
+
+ALTER TABLE ONLY gitlab_partitions_static.product_analytics_events_experimental_09
+ ADD CONSTRAINT product_analytics_events_experimental_09_pkey PRIMARY KEY (id, project_id);
+
+ALTER TABLE ONLY gitlab_partitions_static.product_analytics_events_experimental_10
+ ADD CONSTRAINT product_analytics_events_experimental_10_pkey PRIMARY KEY (id, project_id);
+
+ALTER TABLE ONLY gitlab_partitions_static.product_analytics_events_experimental_11
+ ADD CONSTRAINT product_analytics_events_experimental_11_pkey PRIMARY KEY (id, project_id);
+
+ALTER TABLE ONLY gitlab_partitions_static.product_analytics_events_experimental_12
+ ADD CONSTRAINT product_analytics_events_experimental_12_pkey PRIMARY KEY (id, project_id);
+
+ALTER TABLE ONLY gitlab_partitions_static.product_analytics_events_experimental_13
+ ADD CONSTRAINT product_analytics_events_experimental_13_pkey PRIMARY KEY (id, project_id);
+
+ALTER TABLE ONLY gitlab_partitions_static.product_analytics_events_experimental_14
+ ADD CONSTRAINT product_analytics_events_experimental_14_pkey PRIMARY KEY (id, project_id);
+
+ALTER TABLE ONLY gitlab_partitions_static.product_analytics_events_experimental_15
+ ADD CONSTRAINT product_analytics_events_experimental_15_pkey PRIMARY KEY (id, project_id);
+
+ALTER TABLE ONLY gitlab_partitions_static.product_analytics_events_experimental_16
+ ADD CONSTRAINT product_analytics_events_experimental_16_pkey PRIMARY KEY (id, project_id);
+
+ALTER TABLE ONLY gitlab_partitions_static.product_analytics_events_experimental_17
+ ADD CONSTRAINT product_analytics_events_experimental_17_pkey PRIMARY KEY (id, project_id);
+
+ALTER TABLE ONLY gitlab_partitions_static.product_analytics_events_experimental_18
+ ADD CONSTRAINT product_analytics_events_experimental_18_pkey PRIMARY KEY (id, project_id);
+
+ALTER TABLE ONLY gitlab_partitions_static.product_analytics_events_experimental_19
+ ADD CONSTRAINT product_analytics_events_experimental_19_pkey PRIMARY KEY (id, project_id);
+
+ALTER TABLE ONLY gitlab_partitions_static.product_analytics_events_experimental_20
+ ADD CONSTRAINT product_analytics_events_experimental_20_pkey PRIMARY KEY (id, project_id);
+
+ALTER TABLE ONLY gitlab_partitions_static.product_analytics_events_experimental_21
+ ADD CONSTRAINT product_analytics_events_experimental_21_pkey PRIMARY KEY (id, project_id);
+
+ALTER TABLE ONLY gitlab_partitions_static.product_analytics_events_experimental_22
+ ADD CONSTRAINT product_analytics_events_experimental_22_pkey PRIMARY KEY (id, project_id);
+
+ALTER TABLE ONLY gitlab_partitions_static.product_analytics_events_experimental_23
+ ADD CONSTRAINT product_analytics_events_experimental_23_pkey PRIMARY KEY (id, project_id);
+
+ALTER TABLE ONLY gitlab_partitions_static.product_analytics_events_experimental_24
+ ADD CONSTRAINT product_analytics_events_experimental_24_pkey PRIMARY KEY (id, project_id);
+
+ALTER TABLE ONLY gitlab_partitions_static.product_analytics_events_experimental_25
+ ADD CONSTRAINT product_analytics_events_experimental_25_pkey PRIMARY KEY (id, project_id);
+
+ALTER TABLE ONLY gitlab_partitions_static.product_analytics_events_experimental_26
+ ADD CONSTRAINT product_analytics_events_experimental_26_pkey PRIMARY KEY (id, project_id);
+
+ALTER TABLE ONLY gitlab_partitions_static.product_analytics_events_experimental_27
+ ADD CONSTRAINT product_analytics_events_experimental_27_pkey PRIMARY KEY (id, project_id);
+
+ALTER TABLE ONLY gitlab_partitions_static.product_analytics_events_experimental_28
+ ADD CONSTRAINT product_analytics_events_experimental_28_pkey PRIMARY KEY (id, project_id);
+
+ALTER TABLE ONLY gitlab_partitions_static.product_analytics_events_experimental_29
+ ADD CONSTRAINT product_analytics_events_experimental_29_pkey PRIMARY KEY (id, project_id);
+
+ALTER TABLE ONLY gitlab_partitions_static.product_analytics_events_experimental_30
+ ADD CONSTRAINT product_analytics_events_experimental_30_pkey PRIMARY KEY (id, project_id);
+
+ALTER TABLE ONLY gitlab_partitions_static.product_analytics_events_experimental_31
+ ADD CONSTRAINT product_analytics_events_experimental_31_pkey PRIMARY KEY (id, project_id);
+
+ALTER TABLE ONLY gitlab_partitions_static.product_analytics_events_experimental_32
+ ADD CONSTRAINT product_analytics_events_experimental_32_pkey PRIMARY KEY (id, project_id);
+
+ALTER TABLE ONLY gitlab_partitions_static.product_analytics_events_experimental_33
+ ADD CONSTRAINT product_analytics_events_experimental_33_pkey PRIMARY KEY (id, project_id);
+
+ALTER TABLE ONLY gitlab_partitions_static.product_analytics_events_experimental_34
+ ADD CONSTRAINT product_analytics_events_experimental_34_pkey PRIMARY KEY (id, project_id);
+
+ALTER TABLE ONLY gitlab_partitions_static.product_analytics_events_experimental_35
+ ADD CONSTRAINT product_analytics_events_experimental_35_pkey PRIMARY KEY (id, project_id);
+
+ALTER TABLE ONLY gitlab_partitions_static.product_analytics_events_experimental_36
+ ADD CONSTRAINT product_analytics_events_experimental_36_pkey PRIMARY KEY (id, project_id);
+
+ALTER TABLE ONLY gitlab_partitions_static.product_analytics_events_experimental_37
+ ADD CONSTRAINT product_analytics_events_experimental_37_pkey PRIMARY KEY (id, project_id);
+
+ALTER TABLE ONLY gitlab_partitions_static.product_analytics_events_experimental_38
+ ADD CONSTRAINT product_analytics_events_experimental_38_pkey PRIMARY KEY (id, project_id);
+
+ALTER TABLE ONLY gitlab_partitions_static.product_analytics_events_experimental_39
+ ADD CONSTRAINT product_analytics_events_experimental_39_pkey PRIMARY KEY (id, project_id);
+
+ALTER TABLE ONLY gitlab_partitions_static.product_analytics_events_experimental_40
+ ADD CONSTRAINT product_analytics_events_experimental_40_pkey PRIMARY KEY (id, project_id);
+
+ALTER TABLE ONLY gitlab_partitions_static.product_analytics_events_experimental_41
+ ADD CONSTRAINT product_analytics_events_experimental_41_pkey PRIMARY KEY (id, project_id);
+
+ALTER TABLE ONLY gitlab_partitions_static.product_analytics_events_experimental_42
+ ADD CONSTRAINT product_analytics_events_experimental_42_pkey PRIMARY KEY (id, project_id);
+
+ALTER TABLE ONLY gitlab_partitions_static.product_analytics_events_experimental_43
+ ADD CONSTRAINT product_analytics_events_experimental_43_pkey PRIMARY KEY (id, project_id);
+
+ALTER TABLE ONLY gitlab_partitions_static.product_analytics_events_experimental_44
+ ADD CONSTRAINT product_analytics_events_experimental_44_pkey PRIMARY KEY (id, project_id);
+
+ALTER TABLE ONLY gitlab_partitions_static.product_analytics_events_experimental_45
+ ADD CONSTRAINT product_analytics_events_experimental_45_pkey PRIMARY KEY (id, project_id);
+
+ALTER TABLE ONLY gitlab_partitions_static.product_analytics_events_experimental_46
+ ADD CONSTRAINT product_analytics_events_experimental_46_pkey PRIMARY KEY (id, project_id);
+
+ALTER TABLE ONLY gitlab_partitions_static.product_analytics_events_experimental_47
+ ADD CONSTRAINT product_analytics_events_experimental_47_pkey PRIMARY KEY (id, project_id);
+
+ALTER TABLE ONLY gitlab_partitions_static.product_analytics_events_experimental_48
+ ADD CONSTRAINT product_analytics_events_experimental_48_pkey PRIMARY KEY (id, project_id);
+
+ALTER TABLE ONLY gitlab_partitions_static.product_analytics_events_experimental_49
+ ADD CONSTRAINT product_analytics_events_experimental_49_pkey PRIMARY KEY (id, project_id);
+
+ALTER TABLE ONLY gitlab_partitions_static.product_analytics_events_experimental_50
+ ADD CONSTRAINT product_analytics_events_experimental_50_pkey PRIMARY KEY (id, project_id);
+
+ALTER TABLE ONLY gitlab_partitions_static.product_analytics_events_experimental_51
+ ADD CONSTRAINT product_analytics_events_experimental_51_pkey PRIMARY KEY (id, project_id);
+
+ALTER TABLE ONLY gitlab_partitions_static.product_analytics_events_experimental_52
+ ADD CONSTRAINT product_analytics_events_experimental_52_pkey PRIMARY KEY (id, project_id);
+
+ALTER TABLE ONLY gitlab_partitions_static.product_analytics_events_experimental_53
+ ADD CONSTRAINT product_analytics_events_experimental_53_pkey PRIMARY KEY (id, project_id);
+
+ALTER TABLE ONLY gitlab_partitions_static.product_analytics_events_experimental_54
+ ADD CONSTRAINT product_analytics_events_experimental_54_pkey PRIMARY KEY (id, project_id);
+
+ALTER TABLE ONLY gitlab_partitions_static.product_analytics_events_experimental_55
+ ADD CONSTRAINT product_analytics_events_experimental_55_pkey PRIMARY KEY (id, project_id);
+
+ALTER TABLE ONLY gitlab_partitions_static.product_analytics_events_experimental_56
+ ADD CONSTRAINT product_analytics_events_experimental_56_pkey PRIMARY KEY (id, project_id);
+
+ALTER TABLE ONLY gitlab_partitions_static.product_analytics_events_experimental_57
+ ADD CONSTRAINT product_analytics_events_experimental_57_pkey PRIMARY KEY (id, project_id);
+
+ALTER TABLE ONLY gitlab_partitions_static.product_analytics_events_experimental_58
+ ADD CONSTRAINT product_analytics_events_experimental_58_pkey PRIMARY KEY (id, project_id);
+
+ALTER TABLE ONLY gitlab_partitions_static.product_analytics_events_experimental_59
+ ADD CONSTRAINT product_analytics_events_experimental_59_pkey PRIMARY KEY (id, project_id);
+
+ALTER TABLE ONLY gitlab_partitions_static.product_analytics_events_experimental_60
+ ADD CONSTRAINT product_analytics_events_experimental_60_pkey PRIMARY KEY (id, project_id);
+
+ALTER TABLE ONLY gitlab_partitions_static.product_analytics_events_experimental_61
+ ADD CONSTRAINT product_analytics_events_experimental_61_pkey PRIMARY KEY (id, project_id);
+
+ALTER TABLE ONLY gitlab_partitions_static.product_analytics_events_experimental_62
+ ADD CONSTRAINT product_analytics_events_experimental_62_pkey PRIMARY KEY (id, project_id);
+
+ALTER TABLE ONLY gitlab_partitions_static.product_analytics_events_experimental_63
+ ADD CONSTRAINT product_analytics_events_experimental_63_pkey PRIMARY KEY (id, project_id);
+
ALTER TABLE ONLY public.abuse_reports
ADD CONSTRAINT abuse_reports_pkey PRIMARY KEY (id);
@@ -8115,6 +17317,9 @@ ALTER TABLE ONLY public.allowed_email_domains
ALTER TABLE ONLY public.analytics_cycle_analytics_group_stages
ADD CONSTRAINT analytics_cycle_analytics_group_stages_pkey PRIMARY KEY (id);
+ALTER TABLE ONLY public.analytics_cycle_analytics_group_value_streams
+ ADD CONSTRAINT analytics_cycle_analytics_group_value_streams_pkey PRIMARY KEY (id);
+
ALTER TABLE ONLY public.analytics_cycle_analytics_project_stages
ADD CONSTRAINT analytics_cycle_analytics_project_stages_pkey PRIMARY KEY (id);
@@ -8172,6 +17377,12 @@ ALTER TABLE ONLY public.award_emoji
ALTER TABLE ONLY public.aws_roles
ADD CONSTRAINT aws_roles_pkey PRIMARY KEY (user_id);
+ALTER TABLE ONLY public.background_migration_jobs
+ ADD CONSTRAINT background_migration_jobs_pkey PRIMARY KEY (id);
+
+ALTER TABLE ONLY public.backup_labels
+ ADD CONSTRAINT backup_labels_pkey PRIMARY KEY (id);
+
ALTER TABLE ONLY public.badges
ADD CONSTRAINT badges_pkey PRIMARY KEY (id);
@@ -8205,14 +17416,8 @@ ALTER TABLE ONLY public.chat_teams
ALTER TABLE public.design_management_designs
ADD CONSTRAINT check_07155e2715 CHECK ((char_length((filename)::text) <= 255)) NOT VALID;
-ALTER TABLE public.ci_job_artifacts
- ADD CONSTRAINT check_27f0f6dbab CHECK ((file_store IS NOT NULL)) NOT VALID;
-
-ALTER TABLE public.uploads
- ADD CONSTRAINT check_5e9547379c CHECK ((store IS NOT NULL)) NOT VALID;
-
-ALTER TABLE public.lfs_objects
- ADD CONSTRAINT check_eecfc5717d CHECK ((file_store IS NOT NULL)) NOT VALID;
+ALTER TABLE public.vulnerability_scanners
+ ADD CONSTRAINT check_37608c9db5 CHECK ((char_length(vendor) <= 255)) NOT VALID;
ALTER TABLE ONLY public.ci_build_needs
ADD CONSTRAINT ci_build_needs_pkey PRIMARY KEY (id);
@@ -8238,9 +17443,6 @@ ALTER TABLE ONLY public.ci_builds_runner_session
ALTER TABLE ONLY public.ci_daily_build_group_report_results
ADD CONSTRAINT ci_daily_build_group_report_results_pkey PRIMARY KEY (id);
-ALTER TABLE ONLY public.ci_daily_report_results
- ADD CONSTRAINT ci_daily_report_results_pkey PRIMARY KEY (id);
-
ALTER TABLE ONLY public.ci_freeze_periods
ADD CONSTRAINT ci_freeze_periods_pkey PRIMARY KEY (id);
@@ -8259,6 +17461,9 @@ ALTER TABLE ONLY public.ci_job_variables
ALTER TABLE ONLY public.ci_pipeline_chat_data
ADD CONSTRAINT ci_pipeline_chat_data_pkey PRIMARY KEY (id);
+ALTER TABLE ONLY public.ci_pipeline_messages
+ ADD CONSTRAINT ci_pipeline_messages_pkey PRIMARY KEY (id);
+
ALTER TABLE ONLY public.ci_pipeline_schedule_variables
ADD CONSTRAINT ci_pipeline_schedule_variables_pkey PRIMARY KEY (id);
@@ -8331,6 +17536,9 @@ ALTER TABLE ONLY public.cluster_providers_gcp
ALTER TABLE ONLY public.clusters_applications_cert_managers
ADD CONSTRAINT clusters_applications_cert_managers_pkey PRIMARY KEY (id);
+ALTER TABLE ONLY public.clusters_applications_cilium
+ ADD CONSTRAINT clusters_applications_cilium_pkey PRIMARY KEY (id);
+
ALTER TABLE ONLY public.clusters_applications_crossplane
ADD CONSTRAINT clusters_applications_crossplane_pkey PRIMARY KEY (id);
@@ -8376,6 +17584,15 @@ ALTER TABLE ONLY public.container_repositories
ALTER TABLE ONLY public.conversational_development_index_metrics
ADD CONSTRAINT conversational_development_index_metrics_pkey PRIMARY KEY (id);
+ALTER TABLE ONLY public.custom_emoji
+ ADD CONSTRAINT custom_emoji_pkey PRIMARY KEY (id);
+
+ALTER TABLE ONLY public.dast_site_profiles
+ ADD CONSTRAINT dast_site_profiles_pkey PRIMARY KEY (id);
+
+ALTER TABLE ONLY public.dast_sites
+ ADD CONSTRAINT dast_sites_pkey PRIMARY KEY (id);
+
ALTER TABLE ONLY public.dependency_proxy_blobs
ADD CONSTRAINT dependency_proxy_blobs_pkey PRIMARY KEY (id);
@@ -8415,6 +17632,9 @@ ALTER TABLE ONLY public.diff_note_positions
ALTER TABLE ONLY public.draft_notes
ADD CONSTRAINT draft_notes_pkey PRIMARY KEY (id);
+ALTER TABLE ONLY public.elastic_reindexing_tasks
+ ADD CONSTRAINT elastic_reindexing_tasks_pkey PRIMARY KEY (id);
+
ALTER TABLE ONLY public.emails
ADD CONSTRAINT emails_pkey PRIMARY KEY (id);
@@ -8532,6 +17752,9 @@ ALTER TABLE ONLY public.group_custom_attributes
ALTER TABLE ONLY public.group_deletion_schedules
ADD CONSTRAINT group_deletion_schedules_pkey PRIMARY KEY (group_id);
+ALTER TABLE ONLY public.group_deploy_keys_groups
+ ADD CONSTRAINT group_deploy_keys_groups_pkey PRIMARY KEY (id);
+
ALTER TABLE ONLY public.group_deploy_keys
ADD CONSTRAINT group_deploy_keys_pkey PRIMARY KEY (id);
@@ -8673,9 +17896,15 @@ ALTER TABLE ONLY public.milestones
ALTER TABLE ONLY public.namespace_aggregation_schedules
ADD CONSTRAINT namespace_aggregation_schedules_pkey PRIMARY KEY (namespace_id);
+ALTER TABLE ONLY public.namespace_limits
+ ADD CONSTRAINT namespace_limits_pkey PRIMARY KEY (namespace_id);
+
ALTER TABLE ONLY public.namespace_root_storage_statistics
ADD CONSTRAINT namespace_root_storage_statistics_pkey PRIMARY KEY (namespace_id);
+ALTER TABLE ONLY public.namespace_settings
+ ADD CONSTRAINT namespace_settings_pkey PRIMARY KEY (namespace_id);
+
ALTER TABLE ONLY public.namespace_statistics
ADD CONSTRAINT namespace_statistics_pkey PRIMARY KEY (id);
@@ -8991,6 +18220,9 @@ ALTER TABLE ONLY public.smartcard_identities
ALTER TABLE ONLY public.snippet_repositories
ADD CONSTRAINT snippet_repositories_pkey PRIMARY KEY (snippet_id);
+ALTER TABLE ONLY public.snippet_statistics
+ ADD CONSTRAINT snippet_statistics_pkey PRIMARY KEY (snippet_id);
+
ALTER TABLE ONLY public.snippet_user_mentions
ADD CONSTRAINT snippet_user_mentions_pkey PRIMARY KEY (id);
@@ -9117,6 +18349,9 @@ ALTER TABLE ONLY public.vulnerability_occurrences
ALTER TABLE ONLY public.vulnerability_scanners
ADD CONSTRAINT vulnerability_scanners_pkey PRIMARY KEY (id);
+ALTER TABLE ONLY public.vulnerability_statistics
+ ADD CONSTRAINT vulnerability_statistics_pkey PRIMARY KEY (id);
+
ALTER TABLE ONLY public.vulnerability_user_mentions
ADD CONSTRAINT vulnerability_user_mentions_pkey PRIMARY KEY (id);
@@ -9126,6 +18361,9 @@ ALTER TABLE ONLY public.web_hook_logs
ALTER TABLE ONLY public.web_hooks
ADD CONSTRAINT web_hooks_pkey PRIMARY KEY (id);
+ALTER TABLE ONLY public.webauthn_registrations
+ ADD CONSTRAINT webauthn_registrations_pkey PRIMARY KEY (id);
+
ALTER TABLE ONLY public.wiki_page_meta
ADD CONSTRAINT wiki_page_meta_pkey PRIMARY KEY (id);
@@ -9144,6 +18382,136 @@ ALTER TABLE ONLY public.x509_issuers
ALTER TABLE ONLY public.zoom_meetings
ADD CONSTRAINT zoom_meetings_pkey PRIMARY KEY (id);
+CREATE INDEX index_product_analytics_events_experimental_project_and_time ON ONLY public.product_analytics_events_experimental USING btree (project_id, collector_tstamp);
+
+CREATE INDEX product_analytics_events_expe_project_id_collector_tstamp_idx10 ON gitlab_partitions_static.product_analytics_events_experimental_10 USING btree (project_id, collector_tstamp);
+
+CREATE INDEX product_analytics_events_expe_project_id_collector_tstamp_idx11 ON gitlab_partitions_static.product_analytics_events_experimental_11 USING btree (project_id, collector_tstamp);
+
+CREATE INDEX product_analytics_events_expe_project_id_collector_tstamp_idx12 ON gitlab_partitions_static.product_analytics_events_experimental_12 USING btree (project_id, collector_tstamp);
+
+CREATE INDEX product_analytics_events_expe_project_id_collector_tstamp_idx13 ON gitlab_partitions_static.product_analytics_events_experimental_13 USING btree (project_id, collector_tstamp);
+
+CREATE INDEX product_analytics_events_expe_project_id_collector_tstamp_idx14 ON gitlab_partitions_static.product_analytics_events_experimental_14 USING btree (project_id, collector_tstamp);
+
+CREATE INDEX product_analytics_events_expe_project_id_collector_tstamp_idx15 ON gitlab_partitions_static.product_analytics_events_experimental_15 USING btree (project_id, collector_tstamp);
+
+CREATE INDEX product_analytics_events_expe_project_id_collector_tstamp_idx16 ON gitlab_partitions_static.product_analytics_events_experimental_16 USING btree (project_id, collector_tstamp);
+
+CREATE INDEX product_analytics_events_expe_project_id_collector_tstamp_idx17 ON gitlab_partitions_static.product_analytics_events_experimental_17 USING btree (project_id, collector_tstamp);
+
+CREATE INDEX product_analytics_events_expe_project_id_collector_tstamp_idx18 ON gitlab_partitions_static.product_analytics_events_experimental_18 USING btree (project_id, collector_tstamp);
+
+CREATE INDEX product_analytics_events_expe_project_id_collector_tstamp_idx19 ON gitlab_partitions_static.product_analytics_events_experimental_19 USING btree (project_id, collector_tstamp);
+
+CREATE INDEX product_analytics_events_expe_project_id_collector_tstamp_idx20 ON gitlab_partitions_static.product_analytics_events_experimental_20 USING btree (project_id, collector_tstamp);
+
+CREATE INDEX product_analytics_events_expe_project_id_collector_tstamp_idx21 ON gitlab_partitions_static.product_analytics_events_experimental_21 USING btree (project_id, collector_tstamp);
+
+CREATE INDEX product_analytics_events_expe_project_id_collector_tstamp_idx22 ON gitlab_partitions_static.product_analytics_events_experimental_22 USING btree (project_id, collector_tstamp);
+
+CREATE INDEX product_analytics_events_expe_project_id_collector_tstamp_idx23 ON gitlab_partitions_static.product_analytics_events_experimental_23 USING btree (project_id, collector_tstamp);
+
+CREATE INDEX product_analytics_events_expe_project_id_collector_tstamp_idx24 ON gitlab_partitions_static.product_analytics_events_experimental_24 USING btree (project_id, collector_tstamp);
+
+CREATE INDEX product_analytics_events_expe_project_id_collector_tstamp_idx25 ON gitlab_partitions_static.product_analytics_events_experimental_25 USING btree (project_id, collector_tstamp);
+
+CREATE INDEX product_analytics_events_expe_project_id_collector_tstamp_idx26 ON gitlab_partitions_static.product_analytics_events_experimental_26 USING btree (project_id, collector_tstamp);
+
+CREATE INDEX product_analytics_events_expe_project_id_collector_tstamp_idx27 ON gitlab_partitions_static.product_analytics_events_experimental_27 USING btree (project_id, collector_tstamp);
+
+CREATE INDEX product_analytics_events_expe_project_id_collector_tstamp_idx28 ON gitlab_partitions_static.product_analytics_events_experimental_28 USING btree (project_id, collector_tstamp);
+
+CREATE INDEX product_analytics_events_expe_project_id_collector_tstamp_idx29 ON gitlab_partitions_static.product_analytics_events_experimental_29 USING btree (project_id, collector_tstamp);
+
+CREATE INDEX product_analytics_events_expe_project_id_collector_tstamp_idx30 ON gitlab_partitions_static.product_analytics_events_experimental_30 USING btree (project_id, collector_tstamp);
+
+CREATE INDEX product_analytics_events_expe_project_id_collector_tstamp_idx31 ON gitlab_partitions_static.product_analytics_events_experimental_31 USING btree (project_id, collector_tstamp);
+
+CREATE INDEX product_analytics_events_expe_project_id_collector_tstamp_idx32 ON gitlab_partitions_static.product_analytics_events_experimental_32 USING btree (project_id, collector_tstamp);
+
+CREATE INDEX product_analytics_events_expe_project_id_collector_tstamp_idx33 ON gitlab_partitions_static.product_analytics_events_experimental_33 USING btree (project_id, collector_tstamp);
+
+CREATE INDEX product_analytics_events_expe_project_id_collector_tstamp_idx34 ON gitlab_partitions_static.product_analytics_events_experimental_34 USING btree (project_id, collector_tstamp);
+
+CREATE INDEX product_analytics_events_expe_project_id_collector_tstamp_idx35 ON gitlab_partitions_static.product_analytics_events_experimental_35 USING btree (project_id, collector_tstamp);
+
+CREATE INDEX product_analytics_events_expe_project_id_collector_tstamp_idx36 ON gitlab_partitions_static.product_analytics_events_experimental_36 USING btree (project_id, collector_tstamp);
+
+CREATE INDEX product_analytics_events_expe_project_id_collector_tstamp_idx37 ON gitlab_partitions_static.product_analytics_events_experimental_37 USING btree (project_id, collector_tstamp);
+
+CREATE INDEX product_analytics_events_expe_project_id_collector_tstamp_idx38 ON gitlab_partitions_static.product_analytics_events_experimental_38 USING btree (project_id, collector_tstamp);
+
+CREATE INDEX product_analytics_events_expe_project_id_collector_tstamp_idx39 ON gitlab_partitions_static.product_analytics_events_experimental_39 USING btree (project_id, collector_tstamp);
+
+CREATE INDEX product_analytics_events_expe_project_id_collector_tstamp_idx40 ON gitlab_partitions_static.product_analytics_events_experimental_40 USING btree (project_id, collector_tstamp);
+
+CREATE INDEX product_analytics_events_expe_project_id_collector_tstamp_idx41 ON gitlab_partitions_static.product_analytics_events_experimental_41 USING btree (project_id, collector_tstamp);
+
+CREATE INDEX product_analytics_events_expe_project_id_collector_tstamp_idx42 ON gitlab_partitions_static.product_analytics_events_experimental_42 USING btree (project_id, collector_tstamp);
+
+CREATE INDEX product_analytics_events_expe_project_id_collector_tstamp_idx43 ON gitlab_partitions_static.product_analytics_events_experimental_43 USING btree (project_id, collector_tstamp);
+
+CREATE INDEX product_analytics_events_expe_project_id_collector_tstamp_idx44 ON gitlab_partitions_static.product_analytics_events_experimental_44 USING btree (project_id, collector_tstamp);
+
+CREATE INDEX product_analytics_events_expe_project_id_collector_tstamp_idx45 ON gitlab_partitions_static.product_analytics_events_experimental_45 USING btree (project_id, collector_tstamp);
+
+CREATE INDEX product_analytics_events_expe_project_id_collector_tstamp_idx46 ON gitlab_partitions_static.product_analytics_events_experimental_46 USING btree (project_id, collector_tstamp);
+
+CREATE INDEX product_analytics_events_expe_project_id_collector_tstamp_idx47 ON gitlab_partitions_static.product_analytics_events_experimental_47 USING btree (project_id, collector_tstamp);
+
+CREATE INDEX product_analytics_events_expe_project_id_collector_tstamp_idx48 ON gitlab_partitions_static.product_analytics_events_experimental_48 USING btree (project_id, collector_tstamp);
+
+CREATE INDEX product_analytics_events_expe_project_id_collector_tstamp_idx49 ON gitlab_partitions_static.product_analytics_events_experimental_49 USING btree (project_id, collector_tstamp);
+
+CREATE INDEX product_analytics_events_expe_project_id_collector_tstamp_idx50 ON gitlab_partitions_static.product_analytics_events_experimental_50 USING btree (project_id, collector_tstamp);
+
+CREATE INDEX product_analytics_events_expe_project_id_collector_tstamp_idx51 ON gitlab_partitions_static.product_analytics_events_experimental_51 USING btree (project_id, collector_tstamp);
+
+CREATE INDEX product_analytics_events_expe_project_id_collector_tstamp_idx52 ON gitlab_partitions_static.product_analytics_events_experimental_52 USING btree (project_id, collector_tstamp);
+
+CREATE INDEX product_analytics_events_expe_project_id_collector_tstamp_idx53 ON gitlab_partitions_static.product_analytics_events_experimental_53 USING btree (project_id, collector_tstamp);
+
+CREATE INDEX product_analytics_events_expe_project_id_collector_tstamp_idx54 ON gitlab_partitions_static.product_analytics_events_experimental_54 USING btree (project_id, collector_tstamp);
+
+CREATE INDEX product_analytics_events_expe_project_id_collector_tstamp_idx55 ON gitlab_partitions_static.product_analytics_events_experimental_55 USING btree (project_id, collector_tstamp);
+
+CREATE INDEX product_analytics_events_expe_project_id_collector_tstamp_idx56 ON gitlab_partitions_static.product_analytics_events_experimental_56 USING btree (project_id, collector_tstamp);
+
+CREATE INDEX product_analytics_events_expe_project_id_collector_tstamp_idx57 ON gitlab_partitions_static.product_analytics_events_experimental_57 USING btree (project_id, collector_tstamp);
+
+CREATE INDEX product_analytics_events_expe_project_id_collector_tstamp_idx58 ON gitlab_partitions_static.product_analytics_events_experimental_58 USING btree (project_id, collector_tstamp);
+
+CREATE INDEX product_analytics_events_expe_project_id_collector_tstamp_idx59 ON gitlab_partitions_static.product_analytics_events_experimental_59 USING btree (project_id, collector_tstamp);
+
+CREATE INDEX product_analytics_events_expe_project_id_collector_tstamp_idx60 ON gitlab_partitions_static.product_analytics_events_experimental_60 USING btree (project_id, collector_tstamp);
+
+CREATE INDEX product_analytics_events_expe_project_id_collector_tstamp_idx61 ON gitlab_partitions_static.product_analytics_events_experimental_61 USING btree (project_id, collector_tstamp);
+
+CREATE INDEX product_analytics_events_expe_project_id_collector_tstamp_idx62 ON gitlab_partitions_static.product_analytics_events_experimental_62 USING btree (project_id, collector_tstamp);
+
+CREATE INDEX product_analytics_events_expe_project_id_collector_tstamp_idx63 ON gitlab_partitions_static.product_analytics_events_experimental_63 USING btree (project_id, collector_tstamp);
+
+CREATE INDEX product_analytics_events_exper_project_id_collector_tstamp_idx1 ON gitlab_partitions_static.product_analytics_events_experimental_01 USING btree (project_id, collector_tstamp);
+
+CREATE INDEX product_analytics_events_exper_project_id_collector_tstamp_idx2 ON gitlab_partitions_static.product_analytics_events_experimental_02 USING btree (project_id, collector_tstamp);
+
+CREATE INDEX product_analytics_events_exper_project_id_collector_tstamp_idx3 ON gitlab_partitions_static.product_analytics_events_experimental_03 USING btree (project_id, collector_tstamp);
+
+CREATE INDEX product_analytics_events_exper_project_id_collector_tstamp_idx4 ON gitlab_partitions_static.product_analytics_events_experimental_04 USING btree (project_id, collector_tstamp);
+
+CREATE INDEX product_analytics_events_exper_project_id_collector_tstamp_idx5 ON gitlab_partitions_static.product_analytics_events_experimental_05 USING btree (project_id, collector_tstamp);
+
+CREATE INDEX product_analytics_events_exper_project_id_collector_tstamp_idx6 ON gitlab_partitions_static.product_analytics_events_experimental_06 USING btree (project_id, collector_tstamp);
+
+CREATE INDEX product_analytics_events_exper_project_id_collector_tstamp_idx7 ON gitlab_partitions_static.product_analytics_events_experimental_07 USING btree (project_id, collector_tstamp);
+
+CREATE INDEX product_analytics_events_exper_project_id_collector_tstamp_idx8 ON gitlab_partitions_static.product_analytics_events_experimental_08 USING btree (project_id, collector_tstamp);
+
+CREATE INDEX product_analytics_events_exper_project_id_collector_tstamp_idx9 ON gitlab_partitions_static.product_analytics_events_experimental_09 USING btree (project_id, collector_tstamp);
+
+CREATE INDEX product_analytics_events_experi_project_id_collector_tstamp_idx ON gitlab_partitions_static.product_analytics_events_experimental_00 USING btree (project_id, collector_tstamp);
+
CREATE INDEX analytics_index_audit_events_on_created_at_and_author_id ON public.audit_events USING btree (created_at, author_id);
CREATE INDEX analytics_index_events_on_created_at_and_author_id ON public.events USING btree (created_at, author_id);
@@ -9156,7 +18524,21 @@ CREATE UNIQUE INDEX any_approver_merge_request_rule_type_unique_index ON public.
CREATE UNIQUE INDEX any_approver_project_rule_type_unique_index ON public.approval_project_rules USING btree (project_id) WHERE (rule_type = 3);
-CREATE UNIQUE INDEX approval_rule_name_index_for_code_owners ON public.approval_merge_request_rules USING btree (merge_request_id, code_owner, name) WHERE (code_owner = true);
+CREATE UNIQUE INDEX approval_rule_name_index_for_code_owners ON public.approval_merge_request_rules USING btree (merge_request_id, code_owner, name) WHERE ((code_owner = true) AND (section IS NULL));
+
+CREATE UNIQUE INDEX backup_labels_group_id_project_id_title_idx ON public.backup_labels USING btree (group_id, project_id, title);
+
+CREATE INDEX backup_labels_group_id_title_idx ON public.backup_labels USING btree (group_id, title) WHERE (project_id = NULL::integer);
+
+CREATE INDEX backup_labels_project_id_idx ON public.backup_labels USING btree (project_id);
+
+CREATE UNIQUE INDEX backup_labels_project_id_title_idx ON public.backup_labels USING btree (project_id, title) WHERE (group_id = NULL::integer);
+
+CREATE INDEX backup_labels_template_idx ON public.backup_labels USING btree (template) WHERE template;
+
+CREATE INDEX backup_labels_title_idx ON public.backup_labels USING btree (title);
+
+CREATE INDEX backup_labels_type_project_id_idx ON public.backup_labels USING btree (type, project_id);
CREATE INDEX ci_builds_gitlab_monitor_metrics ON public.ci_builds USING btree (status, created_at, project_id) WHERE ((type)::text = 'Ci::Build'::text);
@@ -9172,6 +18554,8 @@ CREATE UNIQUE INDEX epic_user_mentions_on_epic_id_and_note_id_index ON public.ep
CREATE UNIQUE INDEX epic_user_mentions_on_epic_id_index ON public.epic_user_mentions USING btree (epic_id) WHERE (note_id IS NULL);
+CREATE INDEX idx_ci_pipelines_artifacts_locked ON public.ci_pipelines USING btree (ci_ref_id, id) WHERE (locked = 1);
+
CREATE INDEX idx_deployment_clusters_on_cluster_id_and_kubernetes_namespace ON public.deployment_clusters USING btree (cluster_id, kubernetes_namespace);
CREATE UNIQUE INDEX idx_deployment_merge_requests_unique_index ON public.deployment_merge_requests USING btree (deployment_id, merge_request_id);
@@ -9222,6 +18606,10 @@ CREATE UNIQUE INDEX idx_project_id_payload_key_self_managed_prometheus_alert_eve
CREATE INDEX idx_project_repository_check_partial ON public.projects USING btree (repository_storage, created_at) WHERE (last_repository_check_at IS NULL);
+CREATE INDEX idx_projects_id_created_at_disable_overriding_approvers_false ON public.projects USING btree (id, created_at) WHERE ((disable_overriding_approvers_per_merge_request = false) OR (disable_overriding_approvers_per_merge_request IS NULL));
+
+CREATE INDEX idx_projects_id_created_at_disable_overriding_approvers_true ON public.projects USING btree (id, created_at) WHERE (disable_overriding_approvers_per_merge_request = true);
+
CREATE INDEX idx_projects_on_repository_storage_last_repository_updated_at ON public.projects USING btree (id, repository_storage, last_repository_updated_at);
CREATE INDEX idx_repository_states_on_last_repository_verification_ran_at ON public.project_repository_states USING btree (project_id, last_repository_verification_ran_at) WHERE ((repository_verification_checksum IS NOT NULL) AND (last_repository_verification_failure IS NULL));
@@ -9250,12 +18638,14 @@ CREATE INDEX index_alert_assignees_on_alert_id ON public.alert_management_alert_
CREATE UNIQUE INDEX index_alert_assignees_on_user_id_and_alert_id ON public.alert_management_alert_assignees USING btree (user_id, alert_id);
-CREATE INDEX index_alert_management_alerts_on_issue_id ON public.alert_management_alerts USING btree (issue_id);
+CREATE INDEX index_alert_management_alerts_on_environment_id ON public.alert_management_alerts USING btree (environment_id) WHERE (environment_id IS NOT NULL);
-CREATE UNIQUE INDEX index_alert_management_alerts_on_project_id_and_fingerprint ON public.alert_management_alerts USING btree (project_id, fingerprint);
+CREATE INDEX index_alert_management_alerts_on_issue_id ON public.alert_management_alerts USING btree (issue_id);
CREATE UNIQUE INDEX index_alert_management_alerts_on_project_id_and_iid ON public.alert_management_alerts USING btree (project_id, iid);
+CREATE INDEX index_alert_management_alerts_on_prometheus_alert_id ON public.alert_management_alerts USING btree (prometheus_alert_id) WHERE (prometheus_alert_id IS NOT NULL);
+
CREATE UNIQUE INDEX index_alert_user_mentions_on_alert_id ON public.alert_management_alert_user_mentions USING btree (alert_management_alert_id) WHERE (note_id IS NULL);
CREATE UNIQUE INDEX index_alert_user_mentions_on_alert_id_and_note_id ON public.alert_management_alert_user_mentions USING btree (alert_management_alert_id, note_id);
@@ -9276,6 +18666,10 @@ CREATE INDEX index_analytics_ca_group_stages_on_relative_position ON public.anal
CREATE INDEX index_analytics_ca_group_stages_on_start_event_label_id ON public.analytics_cycle_analytics_group_stages USING btree (start_event_label_id);
+CREATE INDEX index_analytics_ca_group_stages_on_value_stream_id ON public.analytics_cycle_analytics_group_stages USING btree (group_value_stream_id);
+
+CREATE UNIQUE INDEX index_analytics_ca_group_value_streams_on_group_id_and_name ON public.analytics_cycle_analytics_group_value_streams USING btree (group_id, name);
+
CREATE INDEX index_analytics_ca_project_stages_on_end_event_label_id ON public.analytics_cycle_analytics_project_stages USING btree (end_event_label_id);
CREATE INDEX index_analytics_ca_project_stages_on_project_id ON public.analytics_cycle_analytics_project_stages USING btree (project_id);
@@ -9334,7 +18728,9 @@ CREATE UNIQUE INDEX index_approval_project_rules_users_1 ON public.approval_proj
CREATE INDEX index_approval_project_rules_users_2 ON public.approval_project_rules_users USING btree (user_id);
-CREATE UNIQUE INDEX index_approval_rule_name_for_code_owners_rule_type ON public.approval_merge_request_rules USING btree (merge_request_id, name) WHERE (rule_type = 2);
+CREATE UNIQUE INDEX index_approval_rule_name_for_code_owners_rule_type ON public.approval_merge_request_rules USING btree (merge_request_id, name) WHERE ((rule_type = 2) AND (section IS NULL));
+
+CREATE UNIQUE INDEX index_approval_rule_name_for_sectional_code_owners_rule_type ON public.approval_merge_request_rules USING btree (merge_request_id, name, section) WHERE (rule_type = 2);
CREATE INDEX index_approval_rules_code_owners_rule_type ON public.approval_merge_request_rules USING btree (merge_request_id) WHERE (rule_type = 2);
@@ -9350,9 +18746,7 @@ CREATE INDEX index_approvers_on_target_id_and_target_type ON public.approvers US
CREATE INDEX index_approvers_on_user_id ON public.approvers USING btree (user_id);
-CREATE INDEX index_audit_events_on_entity_id_and_entity_type_and_id_desc ON public.audit_events USING btree (entity_id, entity_type, id DESC);
-
-CREATE INDEX index_audit_events_on_ruby_object_in_details ON public.audit_events USING btree (id) WHERE (details ~~ '%ruby/object%'::text);
+CREATE INDEX index_audit_events_on_entity_id_entity_type_id_desc_author_id ON public.audit_events USING btree (entity_id, entity_type, id DESC, author_id);
CREATE INDEX index_award_emoji_on_awardable_type_and_awardable_id ON public.award_emoji USING btree (awardable_type, awardable_id);
@@ -9362,6 +18756,12 @@ CREATE UNIQUE INDEX index_aws_roles_on_role_external_id ON public.aws_roles USIN
CREATE UNIQUE INDEX index_aws_roles_on_user_id ON public.aws_roles USING btree (user_id);
+CREATE INDEX index_background_migration_jobs_for_partitioning_migrations ON public.background_migration_jobs USING btree (((arguments ->> 2))) WHERE (class_name = 'Gitlab::Database::PartitioningMigrationHelpers::BackfillPartitionedTable'::text);
+
+CREATE INDEX index_background_migration_jobs_on_class_name_and_arguments ON public.background_migration_jobs USING btree (class_name, arguments);
+
+CREATE INDEX index_background_migration_jobs_on_class_name_and_status_and_id ON public.background_migration_jobs USING btree (class_name, status, id);
+
CREATE INDEX index_badges_on_group_id ON public.badges USING btree (group_id);
CREATE INDEX index_badges_on_project_id ON public.badges USING btree (project_id);
@@ -9482,16 +18882,12 @@ CREATE UNIQUE INDEX index_ci_builds_runner_session_on_build_id ON public.ci_buil
CREATE INDEX index_ci_daily_build_group_report_results_on_last_pipeline_id ON public.ci_daily_build_group_report_results USING btree (last_pipeline_id);
-CREATE INDEX index_ci_daily_report_results_on_last_pipeline_id ON public.ci_daily_report_results USING btree (last_pipeline_id);
-
CREATE INDEX index_ci_freeze_periods_on_project_id ON public.ci_freeze_periods USING btree (project_id);
CREATE UNIQUE INDEX index_ci_group_variables_on_group_id_and_key ON public.ci_group_variables USING btree (group_id, key);
CREATE UNIQUE INDEX index_ci_instance_variables_on_key ON public.ci_instance_variables USING btree (key);
-CREATE INDEX index_ci_job_artifacts_file_store_is_null ON public.ci_job_artifacts USING btree (id) WHERE (file_store IS NULL);
-
CREATE INDEX index_ci_job_artifacts_for_terraform_reports ON public.ci_job_artifacts USING btree (project_id, id) WHERE (file_type = 18);
CREATE INDEX index_ci_job_artifacts_on_expire_at_and_job_id ON public.ci_job_artifacts USING btree (expire_at, job_id);
@@ -9512,6 +18908,8 @@ CREATE INDEX index_ci_pipeline_chat_data_on_chat_name_id ON public.ci_pipeline_c
CREATE UNIQUE INDEX index_ci_pipeline_chat_data_on_pipeline_id ON public.ci_pipeline_chat_data USING btree (pipeline_id);
+CREATE INDEX index_ci_pipeline_messages_on_pipeline_id ON public.ci_pipeline_messages USING btree (pipeline_id);
+
CREATE UNIQUE INDEX index_ci_pipeline_schedule_variables_on_schedule_id_and_key ON public.ci_pipeline_schedule_variables USING btree (pipeline_schedule_id, key);
CREATE INDEX index_ci_pipeline_schedules_on_next_run_at_and_active ON public.ci_pipeline_schedules USING btree (next_run_at, active);
@@ -9554,6 +18952,8 @@ CREATE INDEX index_ci_pipelines_on_project_idandrefandiddesc ON public.ci_pipeli
CREATE INDEX index_ci_pipelines_on_status ON public.ci_pipelines USING btree (status);
+CREATE INDEX index_ci_pipelines_on_user_id_and_created_at_and_config_source ON public.ci_pipelines USING btree (user_id, created_at, config_source);
+
CREATE INDEX index_ci_pipelines_on_user_id_and_created_at_and_source ON public.ci_pipelines USING btree (user_id, created_at, source);
CREATE UNIQUE INDEX index_ci_refs_on_project_id_and_ref_path ON public.ci_refs USING btree (project_id, ref_path);
@@ -9642,6 +19042,8 @@ CREATE UNIQUE INDEX index_cluster_providers_gcp_on_cluster_id ON public.cluster_
CREATE UNIQUE INDEX index_clusters_applications_cert_managers_on_cluster_id ON public.clusters_applications_cert_managers USING btree (cluster_id);
+CREATE UNIQUE INDEX index_clusters_applications_cilium_on_cluster_id ON public.clusters_applications_cilium USING btree (cluster_id);
+
CREATE UNIQUE INDEX index_clusters_applications_crossplane_on_cluster_id ON public.clusters_applications_crossplane USING btree (cluster_id);
CREATE UNIQUE INDEX index_clusters_applications_elastic_stacks_on_cluster_id ON public.clusters_applications_elastic_stacks USING btree (cluster_id);
@@ -9692,14 +19094,22 @@ CREATE UNIQUE INDEX index_container_repositories_on_project_id_and_name ON publi
CREATE INDEX index_container_repository_on_name_trigram ON public.container_repositories USING gin (name public.gin_trgm_ops);
+CREATE UNIQUE INDEX index_custom_emoji_on_namespace_id_and_name ON public.custom_emoji USING btree (namespace_id, name);
+
CREATE UNIQUE INDEX index_daily_build_group_report_results_unique_columns ON public.ci_daily_build_group_report_results USING btree (project_id, ref_path, date, group_name);
-CREATE UNIQUE INDEX index_daily_report_results_unique_columns ON public.ci_daily_report_results USING btree (project_id, ref_path, param_type, date, title);
+CREATE INDEX index_dast_site_profiles_on_dast_site_id ON public.dast_site_profiles USING btree (dast_site_id);
+
+CREATE UNIQUE INDEX index_dast_site_profiles_on_project_id_and_name ON public.dast_site_profiles USING btree (project_id, name);
+
+CREATE UNIQUE INDEX index_dast_sites_on_project_id_and_url ON public.dast_sites USING btree (project_id, url);
CREATE INDEX index_dependency_proxy_blobs_on_group_id_and_file_name ON public.dependency_proxy_blobs USING btree (group_id, file_name);
CREATE INDEX index_dependency_proxy_group_settings_on_group_id ON public.dependency_proxy_group_settings USING btree (group_id);
+CREATE INDEX index_deploy_key_id_on_protected_branch_push_access_levels ON public.protected_branch_push_access_levels USING btree (deploy_key_id);
+
CREATE INDEX index_deploy_keys_projects_on_deploy_key_id ON public.deploy_keys_projects USING btree (deploy_key_id);
CREATE INDEX index_deploy_keys_projects_on_project_id ON public.deploy_keys_projects USING btree (project_id);
@@ -9726,7 +19136,7 @@ CREATE INDEX index_deployments_on_environment_id_and_iid_and_project_id ON publi
CREATE INDEX index_deployments_on_environment_id_and_status ON public.deployments USING btree (environment_id, status);
-CREATE INDEX index_deployments_on_id_and_status ON public.deployments USING btree (id, status);
+CREATE INDEX index_deployments_on_id_and_status_and_created_at ON public.deployments USING btree (id, status, created_at);
CREATE INDEX index_deployments_on_id_where_cluster_id_present ON public.deployments USING btree (id) WHERE (cluster_id IS NOT NULL);
@@ -9776,6 +19186,10 @@ CREATE INDEX index_draft_notes_on_discussion_id ON public.draft_notes USING btre
CREATE INDEX index_draft_notes_on_merge_request_id ON public.draft_notes USING btree (merge_request_id);
+CREATE UNIQUE INDEX index_elastic_reindexing_tasks_on_in_progress ON public.elastic_reindexing_tasks USING btree (in_progress) WHERE in_progress;
+
+CREATE INDEX index_elastic_reindexing_tasks_on_state ON public.elastic_reindexing_tasks USING btree (state);
+
CREATE INDEX index_elasticsearch_indexed_namespaces_on_created_at ON public.elasticsearch_indexed_namespaces USING btree (created_at);
CREATE UNIQUE INDEX index_elasticsearch_indexed_namespaces_on_namespace_id ON public.elasticsearch_indexed_namespaces USING btree (namespace_id);
@@ -9834,8 +19248,6 @@ CREATE INDEX index_epics_on_iid ON public.epics USING btree (iid);
CREATE INDEX index_epics_on_last_edited_by_id ON public.epics USING btree (last_edited_by_id);
-CREATE INDEX index_epics_on_lock_version ON public.epics USING btree (lock_version) WHERE (lock_version IS NULL);
-
CREATE INDEX index_epics_on_parent_id ON public.epics USING btree (parent_id);
CREATE INDEX index_epics_on_start_date ON public.epics USING btree (start_date);
@@ -9992,6 +19404,10 @@ CREATE INDEX index_group_deletion_schedules_on_marked_for_deletion_on ON public.
CREATE INDEX index_group_deletion_schedules_on_user_id ON public.group_deletion_schedules USING btree (user_id);
+CREATE UNIQUE INDEX index_group_deploy_keys_group_on_group_deploy_key_and_group_ids ON public.group_deploy_keys_groups USING btree (group_id, group_deploy_key_id);
+
+CREATE INDEX index_group_deploy_keys_groups_on_group_deploy_key_id ON public.group_deploy_keys_groups USING btree (group_deploy_key_id);
+
CREATE UNIQUE INDEX index_group_deploy_keys_on_fingerprint ON public.group_deploy_keys USING btree (fingerprint);
CREATE INDEX index_group_deploy_keys_on_fingerprint_sha256 ON public.group_deploy_keys USING btree (fingerprint_sha256);
@@ -10078,8 +19494,6 @@ CREATE INDEX index_issues_on_duplicated_to_id ON public.issues USING btree (dupl
CREATE INDEX index_issues_on_last_edited_by_id ON public.issues USING btree (last_edited_by_id);
-CREATE INDEX index_issues_on_lock_version ON public.issues USING btree (lock_version) WHERE (lock_version IS NULL);
-
CREATE INDEX index_issues_on_milestone_id ON public.issues USING btree (milestone_id);
CREATE INDEX index_issues_on_moved_to_id ON public.issues USING btree (moved_to_id) WHERE (moved_to_id IS NOT NULL);
@@ -10140,7 +19554,7 @@ CREATE INDEX index_labels_on_group_id_and_title ON public.labels USING btree (gr
CREATE INDEX index_labels_on_project_id ON public.labels USING btree (project_id);
-CREATE INDEX index_labels_on_project_id_and_title ON public.labels USING btree (project_id, title) WHERE (group_id = NULL::integer);
+CREATE UNIQUE INDEX index_labels_on_project_id_and_title_unique ON public.labels USING btree (project_id, title) WHERE (group_id IS NULL);
CREATE INDEX index_labels_on_template ON public.labels USING btree (template) WHERE template;
@@ -10152,8 +19566,6 @@ CREATE UNIQUE INDEX index_lfs_file_locks_on_project_id_and_path ON public.lfs_fi
CREATE INDEX index_lfs_file_locks_on_user_id ON public.lfs_file_locks USING btree (user_id);
-CREATE INDEX index_lfs_objects_file_store_is_null ON public.lfs_objects USING btree (id) WHERE (file_store IS NULL);
-
CREATE INDEX index_lfs_objects_on_file_store ON public.lfs_objects USING btree (file_store);
CREATE UNIQUE INDEX index_lfs_objects_on_oid ON public.lfs_objects USING btree (oid);
@@ -10212,8 +19624,6 @@ CREATE INDEX index_merge_request_diffs_on_merge_request_id_and_id ON public.merg
CREATE INDEX index_merge_request_diffs_on_merge_request_id_and_id_partial ON public.merge_request_diffs USING btree (merge_request_id, id) WHERE ((NOT stored_externally) OR (stored_externally IS NULL));
-CREATE INDEX index_merge_request_metrics ON public.merge_request_metrics USING btree (merge_request_id);
-
CREATE INDEX index_merge_request_metrics_on_first_deployed_to_production_at ON public.merge_request_metrics USING btree (first_deployed_to_production_at);
CREATE INDEX index_merge_request_metrics_on_latest_closed_at ON public.merge_request_metrics USING btree (latest_closed_at) WHERE (latest_closed_at IS NOT NULL);
@@ -10246,8 +19656,6 @@ CREATE INDEX index_merge_requests_on_head_pipeline_id ON public.merge_requests U
CREATE INDEX index_merge_requests_on_latest_merge_request_diff_id ON public.merge_requests USING btree (latest_merge_request_diff_id);
-CREATE INDEX index_merge_requests_on_lock_version ON public.merge_requests USING btree (lock_version) WHERE (lock_version IS NULL);
-
CREATE INDEX index_merge_requests_on_merge_user_id ON public.merge_requests USING btree (merge_user_id) WHERE (merge_user_id IS NOT NULL);
CREATE INDEX index_merge_requests_on_milestone_id ON public.merge_requests USING btree (milestone_id);
@@ -10488,6 +19896,8 @@ CREATE INDEX index_pages_domains_on_verified_at_and_enabled_until ON public.page
CREATE INDEX index_pages_domains_on_wildcard ON public.pages_domains USING btree (wildcard);
+CREATE UNIQUE INDEX index_partial_am_alerts_on_project_id_and_fingerprint ON public.alert_management_alerts USING btree (project_id, fingerprint) WHERE (status <> 2);
+
CREATE UNIQUE INDEX index_partitioned_foreign_keys_unique_index ON public.partitioned_foreign_keys USING btree (to_table, from_table, from_column);
CREATE INDEX index_pat_on_user_id_and_expires_at ON public.personal_access_tokens USING btree (user_id, expires_at);
@@ -10590,6 +20000,12 @@ CREATE INDEX index_project_statistics_on_namespace_id ON public.project_statisti
CREATE UNIQUE INDEX index_project_statistics_on_project_id ON public.project_statistics USING btree (project_id);
+CREATE INDEX index_project_statistics_on_repository_size_and_project_id ON public.project_statistics USING btree (repository_size, project_id);
+
+CREATE INDEX index_project_statistics_on_storage_size_and_project_id ON public.project_statistics USING btree (storage_size, project_id);
+
+CREATE INDEX index_project_statistics_on_wiki_size_and_project_id ON public.project_statistics USING btree (wiki_size, project_id);
+
CREATE UNIQUE INDEX index_project_tracing_settings_on_project_id ON public.project_tracing_settings USING btree (project_id);
CREATE INDEX index_projects_api_created_at_id_desc ON public.projects USING btree (created_at, id DESC);
@@ -10692,7 +20108,9 @@ CREATE INDEX index_prometheus_metrics_on_common ON public.prometheus_metrics USI
CREATE INDEX index_prometheus_metrics_on_group ON public.prometheus_metrics USING btree ("group");
-CREATE UNIQUE INDEX index_prometheus_metrics_on_identifier ON public.prometheus_metrics USING btree (identifier);
+CREATE UNIQUE INDEX index_prometheus_metrics_on_identifier_and_null_project ON public.prometheus_metrics USING btree (identifier) WHERE (project_id IS NULL);
+
+CREATE UNIQUE INDEX index_prometheus_metrics_on_identifier_and_project_id ON public.prometheus_metrics USING btree (identifier, project_id);
CREATE INDEX index_prometheus_metrics_on_project_id ON public.prometheus_metrics USING btree (project_id);
@@ -10810,6 +20228,8 @@ CREATE INDEX index_resource_state_events_on_issue_id_and_created_at ON public.re
CREATE INDEX index_resource_state_events_on_merge_request_id ON public.resource_state_events USING btree (merge_request_id);
+CREATE INDEX index_resource_state_events_on_source_merge_request_id ON public.resource_state_events USING btree (source_merge_request_id);
+
CREATE INDEX index_resource_state_events_on_user_id ON public.resource_state_events USING btree (user_id);
CREATE INDEX index_resource_weight_events_on_issue_id_and_created_at ON public.resource_weight_events USING btree (issue_id, created_at);
@@ -10842,6 +20262,8 @@ CREATE UNIQUE INDEX index_scim_identities_on_user_id_and_group_id ON public.scim
CREATE UNIQUE INDEX index_scim_oauth_access_tokens_on_group_id_and_token_encrypted ON public.scim_oauth_access_tokens USING btree (group_id, token_encrypted);
+CREATE INDEX index_secure_ci_builds_on_user_id_created_at ON public.ci_builds USING btree (user_id, created_at) WHERE (((type)::text = 'Ci::Build'::text) AND ((name)::text = ANY (ARRAY[('container_scanning'::character varying)::text, ('dast'::character varying)::text, ('dependency_scanning'::character varying)::text, ('license_management'::character varying)::text, ('license_scanning'::character varying)::text, ('sast'::character varying)::text, ('secret_detection'::character varying)::text])));
+
CREATE INDEX index_security_ci_builds_on_name_and_id ON public.ci_builds USING btree (name, id) WHERE (((name)::text = ANY (ARRAY[('container_scanning'::character varying)::text, ('dast'::character varying)::text, ('dependency_scanning'::character varying)::text, ('license_management'::character varying)::text, ('sast'::character varying)::text, ('secret_detection'::character varying)::text, ('license_scanning'::character varying)::text])) AND ((type)::text = 'Ci::Build'::text));
CREATE INDEX index_self_managed_prometheus_alert_events_on_environment_id ON public.self_managed_prometheus_alert_events USING btree (environment_id);
@@ -10900,6 +20322,8 @@ CREATE INDEX index_snippets_on_description_trigram ON public.snippets USING gin
CREATE INDEX index_snippets_on_file_name_trigram ON public.snippets USING gin (file_name public.gin_trgm_ops);
+CREATE INDEX index_snippets_on_id_and_created_at ON public.snippets USING btree (id, created_at);
+
CREATE INDEX index_snippets_on_id_and_type ON public.snippets USING btree (id, type);
CREATE INDEX index_snippets_on_project_id_and_visibility_level ON public.snippets USING btree (project_id, visibility_level);
@@ -11012,8 +20436,6 @@ CREATE INDEX index_uploads_on_store ON public.uploads USING btree (store);
CREATE INDEX index_uploads_on_uploader_and_path ON public.uploads USING btree (uploader, path);
-CREATE INDEX index_uploads_store_is_null ON public.uploads USING btree (id) WHERE (store IS NULL);
-
CREATE INDEX index_user_agent_details_on_subject_id_and_subject_type ON public.user_agent_details USING btree (subject_id, subject_type);
CREATE INDEX index_user_callouts_on_user_id ON public.user_callouts USING btree (user_id);
@@ -11158,6 +20580,10 @@ CREATE INDEX index_vulnerability_occurrences_on_vulnerability_id ON public.vulne
CREATE UNIQUE INDEX index_vulnerability_scanners_on_project_id_and_external_id ON public.vulnerability_scanners USING btree (project_id, external_id);
+CREATE INDEX index_vulnerability_statistics_on_letter_grade ON public.vulnerability_statistics USING btree (letter_grade);
+
+CREATE UNIQUE INDEX index_vulnerability_statistics_on_unique_project_id ON public.vulnerability_statistics USING btree (project_id);
+
CREATE UNIQUE INDEX index_vulnerability_user_mentions_on_note_id ON public.vulnerability_user_mentions USING btree (note_id) WHERE (note_id IS NOT NULL);
CREATE UNIQUE INDEX index_vulns_user_mentions_on_vulnerability_id ON public.vulnerability_user_mentions USING btree (vulnerability_id) WHERE (note_id IS NULL);
@@ -11174,6 +20600,10 @@ CREATE INDEX index_web_hooks_on_project_id ON public.web_hooks USING btree (proj
CREATE INDEX index_web_hooks_on_type ON public.web_hooks USING btree (type);
+CREATE UNIQUE INDEX index_webauthn_registrations_on_credential_xid ON public.webauthn_registrations USING btree (credential_xid);
+
+CREATE INDEX index_webauthn_registrations_on_user_id ON public.webauthn_registrations USING btree (user_id);
+
CREATE INDEX index_wiki_page_meta_on_project_id ON public.wiki_page_meta USING btree (project_id);
CREATE UNIQUE INDEX index_wiki_page_slugs_on_slug_and_wiki_page_meta_id ON public.wiki_page_slugs USING btree (slug, wiki_page_meta_id);
@@ -11248,11 +20678,9 @@ CREATE INDEX tmp_build_stage_position_index ON public.ci_builds USING btree (sta
CREATE INDEX tmp_idx_on_user_id_where_bio_is_filled ON public.users USING btree (id) WHERE ((COALESCE(bio, ''::character varying))::text IS DISTINCT FROM ''::text);
-CREATE INDEX tmp_index_ci_builds_lock_version ON public.ci_builds USING btree (id) WHERE (lock_version IS NULL);
-
-CREATE INDEX tmp_index_ci_pipelines_lock_version ON public.ci_pipelines USING btree (id) WHERE (lock_version IS NULL);
+CREATE INDEX tmp_index_for_email_unconfirmation_migration ON public.emails USING btree (id) WHERE (confirmed_at IS NOT NULL);
-CREATE INDEX tmp_index_ci_stages_lock_version ON public.ci_stages USING btree (id) WHERE (lock_version IS NULL);
+CREATE UNIQUE INDEX unique_merge_request_metrics_by_merge_request_id ON public.merge_request_metrics USING btree (merge_request_id);
CREATE UNIQUE INDEX users_security_dashboard_projects_unique_index ON public.users_security_dashboard_projects USING btree (project_id, user_id);
@@ -11260,6 +20688,262 @@ CREATE UNIQUE INDEX vulnerability_feedback_unique_idx ON public.vulnerability_fe
CREATE UNIQUE INDEX vulnerability_occurrence_pipelines_on_unique_keys ON public.vulnerability_occurrence_pipelines USING btree (occurrence_id, pipeline_id);
+ALTER INDEX public.index_product_analytics_events_experimental_project_and_time ATTACH PARTITION gitlab_partitions_static.product_analytics_events_expe_project_id_collector_tstamp_idx10;
+
+ALTER INDEX public.index_product_analytics_events_experimental_project_and_time ATTACH PARTITION gitlab_partitions_static.product_analytics_events_expe_project_id_collector_tstamp_idx11;
+
+ALTER INDEX public.index_product_analytics_events_experimental_project_and_time ATTACH PARTITION gitlab_partitions_static.product_analytics_events_expe_project_id_collector_tstamp_idx12;
+
+ALTER INDEX public.index_product_analytics_events_experimental_project_and_time ATTACH PARTITION gitlab_partitions_static.product_analytics_events_expe_project_id_collector_tstamp_idx13;
+
+ALTER INDEX public.index_product_analytics_events_experimental_project_and_time ATTACH PARTITION gitlab_partitions_static.product_analytics_events_expe_project_id_collector_tstamp_idx14;
+
+ALTER INDEX public.index_product_analytics_events_experimental_project_and_time ATTACH PARTITION gitlab_partitions_static.product_analytics_events_expe_project_id_collector_tstamp_idx15;
+
+ALTER INDEX public.index_product_analytics_events_experimental_project_and_time ATTACH PARTITION gitlab_partitions_static.product_analytics_events_expe_project_id_collector_tstamp_idx16;
+
+ALTER INDEX public.index_product_analytics_events_experimental_project_and_time ATTACH PARTITION gitlab_partitions_static.product_analytics_events_expe_project_id_collector_tstamp_idx17;
+
+ALTER INDEX public.index_product_analytics_events_experimental_project_and_time ATTACH PARTITION gitlab_partitions_static.product_analytics_events_expe_project_id_collector_tstamp_idx18;
+
+ALTER INDEX public.index_product_analytics_events_experimental_project_and_time ATTACH PARTITION gitlab_partitions_static.product_analytics_events_expe_project_id_collector_tstamp_idx19;
+
+ALTER INDEX public.index_product_analytics_events_experimental_project_and_time ATTACH PARTITION gitlab_partitions_static.product_analytics_events_expe_project_id_collector_tstamp_idx20;
+
+ALTER INDEX public.index_product_analytics_events_experimental_project_and_time ATTACH PARTITION gitlab_partitions_static.product_analytics_events_expe_project_id_collector_tstamp_idx21;
+
+ALTER INDEX public.index_product_analytics_events_experimental_project_and_time ATTACH PARTITION gitlab_partitions_static.product_analytics_events_expe_project_id_collector_tstamp_idx22;
+
+ALTER INDEX public.index_product_analytics_events_experimental_project_and_time ATTACH PARTITION gitlab_partitions_static.product_analytics_events_expe_project_id_collector_tstamp_idx23;
+
+ALTER INDEX public.index_product_analytics_events_experimental_project_and_time ATTACH PARTITION gitlab_partitions_static.product_analytics_events_expe_project_id_collector_tstamp_idx24;
+
+ALTER INDEX public.index_product_analytics_events_experimental_project_and_time ATTACH PARTITION gitlab_partitions_static.product_analytics_events_expe_project_id_collector_tstamp_idx25;
+
+ALTER INDEX public.index_product_analytics_events_experimental_project_and_time ATTACH PARTITION gitlab_partitions_static.product_analytics_events_expe_project_id_collector_tstamp_idx26;
+
+ALTER INDEX public.index_product_analytics_events_experimental_project_and_time ATTACH PARTITION gitlab_partitions_static.product_analytics_events_expe_project_id_collector_tstamp_idx27;
+
+ALTER INDEX public.index_product_analytics_events_experimental_project_and_time ATTACH PARTITION gitlab_partitions_static.product_analytics_events_expe_project_id_collector_tstamp_idx28;
+
+ALTER INDEX public.index_product_analytics_events_experimental_project_and_time ATTACH PARTITION gitlab_partitions_static.product_analytics_events_expe_project_id_collector_tstamp_idx29;
+
+ALTER INDEX public.index_product_analytics_events_experimental_project_and_time ATTACH PARTITION gitlab_partitions_static.product_analytics_events_expe_project_id_collector_tstamp_idx30;
+
+ALTER INDEX public.index_product_analytics_events_experimental_project_and_time ATTACH PARTITION gitlab_partitions_static.product_analytics_events_expe_project_id_collector_tstamp_idx31;
+
+ALTER INDEX public.index_product_analytics_events_experimental_project_and_time ATTACH PARTITION gitlab_partitions_static.product_analytics_events_expe_project_id_collector_tstamp_idx32;
+
+ALTER INDEX public.index_product_analytics_events_experimental_project_and_time ATTACH PARTITION gitlab_partitions_static.product_analytics_events_expe_project_id_collector_tstamp_idx33;
+
+ALTER INDEX public.index_product_analytics_events_experimental_project_and_time ATTACH PARTITION gitlab_partitions_static.product_analytics_events_expe_project_id_collector_tstamp_idx34;
+
+ALTER INDEX public.index_product_analytics_events_experimental_project_and_time ATTACH PARTITION gitlab_partitions_static.product_analytics_events_expe_project_id_collector_tstamp_idx35;
+
+ALTER INDEX public.index_product_analytics_events_experimental_project_and_time ATTACH PARTITION gitlab_partitions_static.product_analytics_events_expe_project_id_collector_tstamp_idx36;
+
+ALTER INDEX public.index_product_analytics_events_experimental_project_and_time ATTACH PARTITION gitlab_partitions_static.product_analytics_events_expe_project_id_collector_tstamp_idx37;
+
+ALTER INDEX public.index_product_analytics_events_experimental_project_and_time ATTACH PARTITION gitlab_partitions_static.product_analytics_events_expe_project_id_collector_tstamp_idx38;
+
+ALTER INDEX public.index_product_analytics_events_experimental_project_and_time ATTACH PARTITION gitlab_partitions_static.product_analytics_events_expe_project_id_collector_tstamp_idx39;
+
+ALTER INDEX public.index_product_analytics_events_experimental_project_and_time ATTACH PARTITION gitlab_partitions_static.product_analytics_events_expe_project_id_collector_tstamp_idx40;
+
+ALTER INDEX public.index_product_analytics_events_experimental_project_and_time ATTACH PARTITION gitlab_partitions_static.product_analytics_events_expe_project_id_collector_tstamp_idx41;
+
+ALTER INDEX public.index_product_analytics_events_experimental_project_and_time ATTACH PARTITION gitlab_partitions_static.product_analytics_events_expe_project_id_collector_tstamp_idx42;
+
+ALTER INDEX public.index_product_analytics_events_experimental_project_and_time ATTACH PARTITION gitlab_partitions_static.product_analytics_events_expe_project_id_collector_tstamp_idx43;
+
+ALTER INDEX public.index_product_analytics_events_experimental_project_and_time ATTACH PARTITION gitlab_partitions_static.product_analytics_events_expe_project_id_collector_tstamp_idx44;
+
+ALTER INDEX public.index_product_analytics_events_experimental_project_and_time ATTACH PARTITION gitlab_partitions_static.product_analytics_events_expe_project_id_collector_tstamp_idx45;
+
+ALTER INDEX public.index_product_analytics_events_experimental_project_and_time ATTACH PARTITION gitlab_partitions_static.product_analytics_events_expe_project_id_collector_tstamp_idx46;
+
+ALTER INDEX public.index_product_analytics_events_experimental_project_and_time ATTACH PARTITION gitlab_partitions_static.product_analytics_events_expe_project_id_collector_tstamp_idx47;
+
+ALTER INDEX public.index_product_analytics_events_experimental_project_and_time ATTACH PARTITION gitlab_partitions_static.product_analytics_events_expe_project_id_collector_tstamp_idx48;
+
+ALTER INDEX public.index_product_analytics_events_experimental_project_and_time ATTACH PARTITION gitlab_partitions_static.product_analytics_events_expe_project_id_collector_tstamp_idx49;
+
+ALTER INDEX public.index_product_analytics_events_experimental_project_and_time ATTACH PARTITION gitlab_partitions_static.product_analytics_events_expe_project_id_collector_tstamp_idx50;
+
+ALTER INDEX public.index_product_analytics_events_experimental_project_and_time ATTACH PARTITION gitlab_partitions_static.product_analytics_events_expe_project_id_collector_tstamp_idx51;
+
+ALTER INDEX public.index_product_analytics_events_experimental_project_and_time ATTACH PARTITION gitlab_partitions_static.product_analytics_events_expe_project_id_collector_tstamp_idx52;
+
+ALTER INDEX public.index_product_analytics_events_experimental_project_and_time ATTACH PARTITION gitlab_partitions_static.product_analytics_events_expe_project_id_collector_tstamp_idx53;
+
+ALTER INDEX public.index_product_analytics_events_experimental_project_and_time ATTACH PARTITION gitlab_partitions_static.product_analytics_events_expe_project_id_collector_tstamp_idx54;
+
+ALTER INDEX public.index_product_analytics_events_experimental_project_and_time ATTACH PARTITION gitlab_partitions_static.product_analytics_events_expe_project_id_collector_tstamp_idx55;
+
+ALTER INDEX public.index_product_analytics_events_experimental_project_and_time ATTACH PARTITION gitlab_partitions_static.product_analytics_events_expe_project_id_collector_tstamp_idx56;
+
+ALTER INDEX public.index_product_analytics_events_experimental_project_and_time ATTACH PARTITION gitlab_partitions_static.product_analytics_events_expe_project_id_collector_tstamp_idx57;
+
+ALTER INDEX public.index_product_analytics_events_experimental_project_and_time ATTACH PARTITION gitlab_partitions_static.product_analytics_events_expe_project_id_collector_tstamp_idx58;
+
+ALTER INDEX public.index_product_analytics_events_experimental_project_and_time ATTACH PARTITION gitlab_partitions_static.product_analytics_events_expe_project_id_collector_tstamp_idx59;
+
+ALTER INDEX public.index_product_analytics_events_experimental_project_and_time ATTACH PARTITION gitlab_partitions_static.product_analytics_events_expe_project_id_collector_tstamp_idx60;
+
+ALTER INDEX public.index_product_analytics_events_experimental_project_and_time ATTACH PARTITION gitlab_partitions_static.product_analytics_events_expe_project_id_collector_tstamp_idx61;
+
+ALTER INDEX public.index_product_analytics_events_experimental_project_and_time ATTACH PARTITION gitlab_partitions_static.product_analytics_events_expe_project_id_collector_tstamp_idx62;
+
+ALTER INDEX public.index_product_analytics_events_experimental_project_and_time ATTACH PARTITION gitlab_partitions_static.product_analytics_events_expe_project_id_collector_tstamp_idx63;
+
+ALTER INDEX public.index_product_analytics_events_experimental_project_and_time ATTACH PARTITION gitlab_partitions_static.product_analytics_events_exper_project_id_collector_tstamp_idx1;
+
+ALTER INDEX public.index_product_analytics_events_experimental_project_and_time ATTACH PARTITION gitlab_partitions_static.product_analytics_events_exper_project_id_collector_tstamp_idx2;
+
+ALTER INDEX public.index_product_analytics_events_experimental_project_and_time ATTACH PARTITION gitlab_partitions_static.product_analytics_events_exper_project_id_collector_tstamp_idx3;
+
+ALTER INDEX public.index_product_analytics_events_experimental_project_and_time ATTACH PARTITION gitlab_partitions_static.product_analytics_events_exper_project_id_collector_tstamp_idx4;
+
+ALTER INDEX public.index_product_analytics_events_experimental_project_and_time ATTACH PARTITION gitlab_partitions_static.product_analytics_events_exper_project_id_collector_tstamp_idx5;
+
+ALTER INDEX public.index_product_analytics_events_experimental_project_and_time ATTACH PARTITION gitlab_partitions_static.product_analytics_events_exper_project_id_collector_tstamp_idx6;
+
+ALTER INDEX public.index_product_analytics_events_experimental_project_and_time ATTACH PARTITION gitlab_partitions_static.product_analytics_events_exper_project_id_collector_tstamp_idx7;
+
+ALTER INDEX public.index_product_analytics_events_experimental_project_and_time ATTACH PARTITION gitlab_partitions_static.product_analytics_events_exper_project_id_collector_tstamp_idx8;
+
+ALTER INDEX public.index_product_analytics_events_experimental_project_and_time ATTACH PARTITION gitlab_partitions_static.product_analytics_events_exper_project_id_collector_tstamp_idx9;
+
+ALTER INDEX public.index_product_analytics_events_experimental_project_and_time ATTACH PARTITION gitlab_partitions_static.product_analytics_events_experi_project_id_collector_tstamp_idx;
+
+ALTER INDEX public.product_analytics_events_experimental_pkey ATTACH PARTITION gitlab_partitions_static.product_analytics_events_experimental_00_pkey;
+
+ALTER INDEX public.product_analytics_events_experimental_pkey ATTACH PARTITION gitlab_partitions_static.product_analytics_events_experimental_01_pkey;
+
+ALTER INDEX public.product_analytics_events_experimental_pkey ATTACH PARTITION gitlab_partitions_static.product_analytics_events_experimental_02_pkey;
+
+ALTER INDEX public.product_analytics_events_experimental_pkey ATTACH PARTITION gitlab_partitions_static.product_analytics_events_experimental_03_pkey;
+
+ALTER INDEX public.product_analytics_events_experimental_pkey ATTACH PARTITION gitlab_partitions_static.product_analytics_events_experimental_04_pkey;
+
+ALTER INDEX public.product_analytics_events_experimental_pkey ATTACH PARTITION gitlab_partitions_static.product_analytics_events_experimental_05_pkey;
+
+ALTER INDEX public.product_analytics_events_experimental_pkey ATTACH PARTITION gitlab_partitions_static.product_analytics_events_experimental_06_pkey;
+
+ALTER INDEX public.product_analytics_events_experimental_pkey ATTACH PARTITION gitlab_partitions_static.product_analytics_events_experimental_07_pkey;
+
+ALTER INDEX public.product_analytics_events_experimental_pkey ATTACH PARTITION gitlab_partitions_static.product_analytics_events_experimental_08_pkey;
+
+ALTER INDEX public.product_analytics_events_experimental_pkey ATTACH PARTITION gitlab_partitions_static.product_analytics_events_experimental_09_pkey;
+
+ALTER INDEX public.product_analytics_events_experimental_pkey ATTACH PARTITION gitlab_partitions_static.product_analytics_events_experimental_10_pkey;
+
+ALTER INDEX public.product_analytics_events_experimental_pkey ATTACH PARTITION gitlab_partitions_static.product_analytics_events_experimental_11_pkey;
+
+ALTER INDEX public.product_analytics_events_experimental_pkey ATTACH PARTITION gitlab_partitions_static.product_analytics_events_experimental_12_pkey;
+
+ALTER INDEX public.product_analytics_events_experimental_pkey ATTACH PARTITION gitlab_partitions_static.product_analytics_events_experimental_13_pkey;
+
+ALTER INDEX public.product_analytics_events_experimental_pkey ATTACH PARTITION gitlab_partitions_static.product_analytics_events_experimental_14_pkey;
+
+ALTER INDEX public.product_analytics_events_experimental_pkey ATTACH PARTITION gitlab_partitions_static.product_analytics_events_experimental_15_pkey;
+
+ALTER INDEX public.product_analytics_events_experimental_pkey ATTACH PARTITION gitlab_partitions_static.product_analytics_events_experimental_16_pkey;
+
+ALTER INDEX public.product_analytics_events_experimental_pkey ATTACH PARTITION gitlab_partitions_static.product_analytics_events_experimental_17_pkey;
+
+ALTER INDEX public.product_analytics_events_experimental_pkey ATTACH PARTITION gitlab_partitions_static.product_analytics_events_experimental_18_pkey;
+
+ALTER INDEX public.product_analytics_events_experimental_pkey ATTACH PARTITION gitlab_partitions_static.product_analytics_events_experimental_19_pkey;
+
+ALTER INDEX public.product_analytics_events_experimental_pkey ATTACH PARTITION gitlab_partitions_static.product_analytics_events_experimental_20_pkey;
+
+ALTER INDEX public.product_analytics_events_experimental_pkey ATTACH PARTITION gitlab_partitions_static.product_analytics_events_experimental_21_pkey;
+
+ALTER INDEX public.product_analytics_events_experimental_pkey ATTACH PARTITION gitlab_partitions_static.product_analytics_events_experimental_22_pkey;
+
+ALTER INDEX public.product_analytics_events_experimental_pkey ATTACH PARTITION gitlab_partitions_static.product_analytics_events_experimental_23_pkey;
+
+ALTER INDEX public.product_analytics_events_experimental_pkey ATTACH PARTITION gitlab_partitions_static.product_analytics_events_experimental_24_pkey;
+
+ALTER INDEX public.product_analytics_events_experimental_pkey ATTACH PARTITION gitlab_partitions_static.product_analytics_events_experimental_25_pkey;
+
+ALTER INDEX public.product_analytics_events_experimental_pkey ATTACH PARTITION gitlab_partitions_static.product_analytics_events_experimental_26_pkey;
+
+ALTER INDEX public.product_analytics_events_experimental_pkey ATTACH PARTITION gitlab_partitions_static.product_analytics_events_experimental_27_pkey;
+
+ALTER INDEX public.product_analytics_events_experimental_pkey ATTACH PARTITION gitlab_partitions_static.product_analytics_events_experimental_28_pkey;
+
+ALTER INDEX public.product_analytics_events_experimental_pkey ATTACH PARTITION gitlab_partitions_static.product_analytics_events_experimental_29_pkey;
+
+ALTER INDEX public.product_analytics_events_experimental_pkey ATTACH PARTITION gitlab_partitions_static.product_analytics_events_experimental_30_pkey;
+
+ALTER INDEX public.product_analytics_events_experimental_pkey ATTACH PARTITION gitlab_partitions_static.product_analytics_events_experimental_31_pkey;
+
+ALTER INDEX public.product_analytics_events_experimental_pkey ATTACH PARTITION gitlab_partitions_static.product_analytics_events_experimental_32_pkey;
+
+ALTER INDEX public.product_analytics_events_experimental_pkey ATTACH PARTITION gitlab_partitions_static.product_analytics_events_experimental_33_pkey;
+
+ALTER INDEX public.product_analytics_events_experimental_pkey ATTACH PARTITION gitlab_partitions_static.product_analytics_events_experimental_34_pkey;
+
+ALTER INDEX public.product_analytics_events_experimental_pkey ATTACH PARTITION gitlab_partitions_static.product_analytics_events_experimental_35_pkey;
+
+ALTER INDEX public.product_analytics_events_experimental_pkey ATTACH PARTITION gitlab_partitions_static.product_analytics_events_experimental_36_pkey;
+
+ALTER INDEX public.product_analytics_events_experimental_pkey ATTACH PARTITION gitlab_partitions_static.product_analytics_events_experimental_37_pkey;
+
+ALTER INDEX public.product_analytics_events_experimental_pkey ATTACH PARTITION gitlab_partitions_static.product_analytics_events_experimental_38_pkey;
+
+ALTER INDEX public.product_analytics_events_experimental_pkey ATTACH PARTITION gitlab_partitions_static.product_analytics_events_experimental_39_pkey;
+
+ALTER INDEX public.product_analytics_events_experimental_pkey ATTACH PARTITION gitlab_partitions_static.product_analytics_events_experimental_40_pkey;
+
+ALTER INDEX public.product_analytics_events_experimental_pkey ATTACH PARTITION gitlab_partitions_static.product_analytics_events_experimental_41_pkey;
+
+ALTER INDEX public.product_analytics_events_experimental_pkey ATTACH PARTITION gitlab_partitions_static.product_analytics_events_experimental_42_pkey;
+
+ALTER INDEX public.product_analytics_events_experimental_pkey ATTACH PARTITION gitlab_partitions_static.product_analytics_events_experimental_43_pkey;
+
+ALTER INDEX public.product_analytics_events_experimental_pkey ATTACH PARTITION gitlab_partitions_static.product_analytics_events_experimental_44_pkey;
+
+ALTER INDEX public.product_analytics_events_experimental_pkey ATTACH PARTITION gitlab_partitions_static.product_analytics_events_experimental_45_pkey;
+
+ALTER INDEX public.product_analytics_events_experimental_pkey ATTACH PARTITION gitlab_partitions_static.product_analytics_events_experimental_46_pkey;
+
+ALTER INDEX public.product_analytics_events_experimental_pkey ATTACH PARTITION gitlab_partitions_static.product_analytics_events_experimental_47_pkey;
+
+ALTER INDEX public.product_analytics_events_experimental_pkey ATTACH PARTITION gitlab_partitions_static.product_analytics_events_experimental_48_pkey;
+
+ALTER INDEX public.product_analytics_events_experimental_pkey ATTACH PARTITION gitlab_partitions_static.product_analytics_events_experimental_49_pkey;
+
+ALTER INDEX public.product_analytics_events_experimental_pkey ATTACH PARTITION gitlab_partitions_static.product_analytics_events_experimental_50_pkey;
+
+ALTER INDEX public.product_analytics_events_experimental_pkey ATTACH PARTITION gitlab_partitions_static.product_analytics_events_experimental_51_pkey;
+
+ALTER INDEX public.product_analytics_events_experimental_pkey ATTACH PARTITION gitlab_partitions_static.product_analytics_events_experimental_52_pkey;
+
+ALTER INDEX public.product_analytics_events_experimental_pkey ATTACH PARTITION gitlab_partitions_static.product_analytics_events_experimental_53_pkey;
+
+ALTER INDEX public.product_analytics_events_experimental_pkey ATTACH PARTITION gitlab_partitions_static.product_analytics_events_experimental_54_pkey;
+
+ALTER INDEX public.product_analytics_events_experimental_pkey ATTACH PARTITION gitlab_partitions_static.product_analytics_events_experimental_55_pkey;
+
+ALTER INDEX public.product_analytics_events_experimental_pkey ATTACH PARTITION gitlab_partitions_static.product_analytics_events_experimental_56_pkey;
+
+ALTER INDEX public.product_analytics_events_experimental_pkey ATTACH PARTITION gitlab_partitions_static.product_analytics_events_experimental_57_pkey;
+
+ALTER INDEX public.product_analytics_events_experimental_pkey ATTACH PARTITION gitlab_partitions_static.product_analytics_events_experimental_58_pkey;
+
+ALTER INDEX public.product_analytics_events_experimental_pkey ATTACH PARTITION gitlab_partitions_static.product_analytics_events_experimental_59_pkey;
+
+ALTER INDEX public.product_analytics_events_experimental_pkey ATTACH PARTITION gitlab_partitions_static.product_analytics_events_experimental_60_pkey;
+
+ALTER INDEX public.product_analytics_events_experimental_pkey ATTACH PARTITION gitlab_partitions_static.product_analytics_events_experimental_61_pkey;
+
+ALTER INDEX public.product_analytics_events_experimental_pkey ATTACH PARTITION gitlab_partitions_static.product_analytics_events_experimental_62_pkey;
+
+ALTER INDEX public.product_analytics_events_experimental_pkey ATTACH PARTITION gitlab_partitions_static.product_analytics_events_experimental_63_pkey;
+
ALTER TABLE ONLY public.chat_names
ADD CONSTRAINT fk_00797a2bf9 FOREIGN KEY (service_id) REFERENCES public.services(id) ON DELETE CASCADE;
@@ -11299,6 +20983,9 @@ ALTER TABLE ONLY public.vulnerabilities
ALTER TABLE ONLY public.vulnerabilities
ADD CONSTRAINT fk_131d289c65 FOREIGN KEY (milestone_id) REFERENCES public.milestones(id) ON DELETE SET NULL;
+ALTER TABLE ONLY public.protected_branch_push_access_levels
+ ADD CONSTRAINT fk_15d2a7a4ae FOREIGN KEY (deploy_key_id) REFERENCES public.keys(id) ON DELETE CASCADE;
+
ALTER TABLE ONLY public.internal_ids
ADD CONSTRAINT fk_162941d509 FOREIGN KEY (namespace_id) REFERENCES public.namespaces(id) ON DELETE CASCADE;
@@ -11422,6 +21109,9 @@ ALTER TABLE ONLY public.geo_event_log
ALTER TABLE ONLY public.ci_build_trace_sections
ADD CONSTRAINT fk_4ebe41f502 FOREIGN KEY (build_id) REFERENCES public.ci_builds(id) ON DELETE CASCADE;
+ALTER TABLE ONLY public.alert_management_alerts
+ ADD CONSTRAINT fk_51ab4b6089 FOREIGN KEY (prometheus_alert_id) REFERENCES public.prometheus_alerts(id) ON DELETE CASCADE;
+
ALTER TABLE ONLY public.path_locks
ADD CONSTRAINT fk_5265c98f24 FOREIGN KEY (project_id) REFERENCES public.projects(id) ON DELETE CASCADE;
@@ -11509,6 +21199,9 @@ ALTER TABLE ONLY public.vulnerabilities
ALTER TABLE ONLY public.labels
ADD CONSTRAINT fk_7de4989a69 FOREIGN KEY (project_id) REFERENCES public.projects(id) ON DELETE CASCADE;
+ALTER TABLE ONLY public.backup_labels
+ ADD CONSTRAINT fk_7de4989a69 FOREIGN KEY (project_id) REFERENCES public.projects(id) ON DELETE CASCADE;
+
ALTER TABLE ONLY public.merge_requests
ADD CONSTRAINT fk_7e85395a64 FOREIGN KEY (sprint_id) REFERENCES public.sprints(id) ON DELETE CASCADE;
@@ -11629,6 +21322,9 @@ ALTER TABLE ONLY public.merge_requests
ALTER TABLE ONLY public.epics
ADD CONSTRAINT fk_aa5798e761 FOREIGN KEY (closed_by_id) REFERENCES public.users(id) ON DELETE SET NULL;
+ALTER TABLE ONLY public.alert_management_alerts
+ ADD CONSTRAINT fk_aad61aedca FOREIGN KEY (environment_id) REFERENCES public.environments(id) ON DELETE SET NULL;
+
ALTER TABLE ONLY public.identities
ADD CONSTRAINT fk_aade90f0fc FOREIGN KEY (saml_provider_id) REFERENCES public.saml_providers(id) ON DELETE CASCADE;
@@ -11644,6 +21340,9 @@ ALTER TABLE ONLY public.ci_variables
ALTER TABLE ONLY public.merge_request_metrics
ADD CONSTRAINT fk_ae440388cc FOREIGN KEY (latest_closed_by_id) REFERENCES public.users(id) ON DELETE SET NULL;
+ALTER TABLE ONLY public.analytics_cycle_analytics_group_stages
+ ADD CONSTRAINT fk_analytics_cycle_analytics_group_stages_group_value_stream_id FOREIGN KEY (group_value_stream_id) REFERENCES public.analytics_cycle_analytics_group_value_streams(id) ON DELETE CASCADE;
+
ALTER TABLE ONLY public.fork_network_members
ADD CONSTRAINT fk_b01280dae4 FOREIGN KEY (forked_from_project_id) REFERENCES public.projects(id) ON DELETE SET NULL;
@@ -12088,6 +21787,9 @@ ALTER TABLE ONLY public.analytics_cycle_analytics_project_stages
ALTER TABLE ONLY public.issue_user_mentions
ADD CONSTRAINT fk_rails_3861d9fefa FOREIGN KEY (note_id) REFERENCES public.notes(id) ON DELETE CASCADE;
+ALTER TABLE ONLY public.namespace_settings
+ ADD CONSTRAINT fk_rails_3896d4fae5 FOREIGN KEY (namespace_id) REFERENCES public.namespaces(id) ON DELETE CASCADE;
+
ALTER TABLE ONLY public.self_managed_prometheus_alert_events
ADD CONSTRAINT fk_rails_3936dadc62 FOREIGN KEY (project_id) REFERENCES public.projects(id) ON DELETE CASCADE;
@@ -12223,6 +21925,9 @@ ALTER TABLE ONLY public.project_repository_storage_moves
ALTER TABLE ONLY public.x509_commit_signatures
ADD CONSTRAINT fk_rails_53fe41188f FOREIGN KEY (x509_certificate_id) REFERENCES public.x509_certificates(id) ON DELETE CASCADE;
+ALTER TABLE ONLY public.analytics_cycle_analytics_group_value_streams
+ ADD CONSTRAINT fk_rails_540627381a FOREIGN KEY (group_id) REFERENCES public.namespaces(id) ON DELETE CASCADE;
+
ALTER TABLE ONLY public.geo_node_namespace_links
ADD CONSTRAINT fk_rails_546bf08d3e FOREIGN KEY (geo_node_id) REFERENCES public.geo_nodes(id) ON DELETE CASCADE;
@@ -12241,6 +21946,9 @@ ALTER TABLE ONLY public.issue_user_mentions
ALTER TABLE ONLY public.merge_request_assignees
ADD CONSTRAINT fk_rails_579d375628 FOREIGN KEY (user_id) REFERENCES public.users(id) ON DELETE CASCADE;
+ALTER TABLE ONLY public.clusters_applications_cilium
+ ADD CONSTRAINT fk_rails_59dc12eea6 FOREIGN KEY (cluster_id) REFERENCES public.clusters(id) ON DELETE CASCADE;
+
ALTER TABLE ONLY public.analytics_cycle_analytics_group_stages
ADD CONSTRAINT fk_rails_5a22f40223 FOREIGN KEY (start_event_label_id) REFERENCES public.labels(id) ON DELETE CASCADE;
@@ -12253,6 +21961,9 @@ ALTER TABLE ONLY public.resource_label_events
ALTER TABLE ONLY public.approval_merge_request_rules_groups
ADD CONSTRAINT fk_rails_5b2ecf6139 FOREIGN KEY (approval_merge_request_rule_id) REFERENCES public.approval_merge_request_rules(id) ON DELETE CASCADE;
+ALTER TABLE ONLY public.namespace_limits
+ ADD CONSTRAINT fk_rails_5b3f2bc334 FOREIGN KEY (namespace_id) REFERENCES public.namespaces(id) ON DELETE CASCADE;
+
ALTER TABLE ONLY public.protected_environment_deploy_access_levels
ADD CONSTRAINT fk_rails_5b9f6970fe FOREIGN KEY (user_id) REFERENCES public.users(id) ON DELETE CASCADE;
@@ -12364,6 +22075,9 @@ ALTER TABLE ONLY public.project_compliance_framework_settings
ALTER TABLE ONLY public.users_security_dashboard_projects
ADD CONSTRAINT fk_rails_6f6cf8e66e FOREIGN KEY (user_id) REFERENCES public.users(id) ON DELETE CASCADE;
+ALTER TABLE ONLY public.dast_sites
+ ADD CONSTRAINT fk_rails_6febb6ea9c FOREIGN KEY (project_id) REFERENCES public.projects(id) ON DELETE CASCADE;
+
ALTER TABLE ONLY public.ci_builds_runner_session
ADD CONSTRAINT fk_rails_70707857d3 FOREIGN KEY (build_id) REFERENCES public.ci_builds(id) ON DELETE CASCADE;
@@ -12376,6 +22090,12 @@ ALTER TABLE ONLY public.project_custom_attributes
ALTER TABLE ONLY public.slack_integrations
ADD CONSTRAINT fk_rails_73db19721a FOREIGN KEY (service_id) REFERENCES public.services(id) ON DELETE CASCADE;
+ALTER TABLE ONLY public.custom_emoji
+ ADD CONSTRAINT fk_rails_745925b412 FOREIGN KEY (namespace_id) REFERENCES public.namespaces(id) ON DELETE CASCADE;
+
+ALTER TABLE ONLY public.dast_site_profiles
+ ADD CONSTRAINT fk_rails_747dc64abc FOREIGN KEY (dast_site_id) REFERENCES public.dast_sites(id) ON DELETE CASCADE;
+
ALTER TABLE ONLY public.merge_request_context_commit_diff_files
ADD CONSTRAINT fk_rails_74a00a1787 FOREIGN KEY (merge_request_context_commit_id) REFERENCES public.merge_request_context_commits(id) ON DELETE CASCADE;
@@ -12418,6 +22138,9 @@ ALTER TABLE ONLY public.operations_scopes
ALTER TABLE ONLY public.milestone_releases
ADD CONSTRAINT fk_rails_7ae0756a2d FOREIGN KEY (milestone_id) REFERENCES public.milestones(id) ON DELETE CASCADE;
+ALTER TABLE ONLY public.resource_state_events
+ ADD CONSTRAINT fk_rails_7ddc5f7457 FOREIGN KEY (source_merge_request_id) REFERENCES public.merge_requests(id) ON DELETE SET NULL;
+
ALTER TABLE ONLY public.application_settings
ADD CONSTRAINT fk_rails_7e112a9599 FOREIGN KEY (instance_administration_project_id) REFERENCES public.projects(id) ON DELETE SET NULL;
@@ -12427,6 +22150,9 @@ ALTER TABLE ONLY public.clusters_kubernetes_namespaces
ALTER TABLE ONLY public.approval_merge_request_rules_users
ADD CONSTRAINT fk_rails_80e6801803 FOREIGN KEY (approval_merge_request_rule_id) REFERENCES public.approval_merge_request_rules(id) ON DELETE CASCADE;
+ALTER TABLE ONLY public.dast_site_profiles
+ ADD CONSTRAINT fk_rails_83e309d69e FOREIGN KEY (project_id) REFERENCES public.projects(id) ON DELETE CASCADE;
+
ALTER TABLE ONLY public.deployment_merge_requests
ADD CONSTRAINT fk_rails_86a6d8bf12 FOREIGN KEY (merge_request_id) REFERENCES public.merge_requests(id) ON DELETE CASCADE;
@@ -12469,6 +22195,9 @@ ALTER TABLE ONLY public.packages_conan_metadata
ALTER TABLE ONLY public.vulnerability_feedback
ADD CONSTRAINT fk_rails_8c77e5891a FOREIGN KEY (issue_id) REFERENCES public.issues(id) ON DELETE SET NULL;
+ALTER TABLE ONLY public.ci_pipeline_messages
+ ADD CONSTRAINT fk_rails_8d3b04e3e1 FOREIGN KEY (pipeline_id) REFERENCES public.ci_pipelines(id) ON DELETE CASCADE;
+
ALTER TABLE ONLY public.approval_merge_request_rules_approved_approvers
ADD CONSTRAINT fk_rails_8dc94cff4d FOREIGN KEY (user_id) REFERENCES public.users(id) ON DELETE CASCADE;
@@ -12628,9 +22357,15 @@ ALTER TABLE ONLY public.metrics_dashboard_annotations
ALTER TABLE ONLY public.pool_repositories
ADD CONSTRAINT fk_rails_af3f8c5d62 FOREIGN KEY (shard_id) REFERENCES public.shards(id) ON DELETE RESTRICT;
+ALTER TABLE ONLY public.vulnerability_statistics
+ ADD CONSTRAINT fk_rails_af61a7df4c FOREIGN KEY (project_id) REFERENCES public.projects(id) ON DELETE CASCADE;
+
ALTER TABLE ONLY public.resource_label_events
ADD CONSTRAINT fk_rails_b126799f57 FOREIGN KEY (label_id) REFERENCES public.labels(id) ON DELETE SET NULL;
+ALTER TABLE ONLY public.webauthn_registrations
+ ADD CONSTRAINT fk_rails_b15c016782 FOREIGN KEY (user_id) REFERENCES public.users(id) ON DELETE CASCADE;
+
ALTER TABLE ONLY public.packages_build_infos
ADD CONSTRAINT fk_rails_b18868292d FOREIGN KEY (package_id) REFERENCES public.packages_packages(id) ON DELETE CASCADE;
@@ -12697,6 +22432,9 @@ ALTER TABLE ONLY public.serverless_domain_cluster
ALTER TABLE ONLY public.labels
ADD CONSTRAINT fk_rails_c1ac5161d8 FOREIGN KEY (group_id) REFERENCES public.namespaces(id) ON DELETE CASCADE;
+ALTER TABLE ONLY public.backup_labels
+ ADD CONSTRAINT fk_rails_c1ac5161d8 FOREIGN KEY (group_id) REFERENCES public.namespaces(id) ON DELETE CASCADE;
+
ALTER TABLE ONLY public.project_feature_usages
ADD CONSTRAINT fk_rails_c22a50024b FOREIGN KEY (project_id) REFERENCES public.projects(id) ON DELETE CASCADE;
@@ -12709,6 +22447,9 @@ ALTER TABLE ONLY public.project_repositories
ALTER TABLE ONLY public.packages_nuget_dependency_link_metadata
ADD CONSTRAINT fk_rails_c3313ee2e4 FOREIGN KEY (dependency_link_id) REFERENCES public.packages_dependency_links(id) ON DELETE CASCADE;
+ALTER TABLE ONLY public.group_deploy_keys_groups
+ ADD CONSTRAINT fk_rails_c3854f19f5 FOREIGN KEY (group_deploy_key_id) REFERENCES public.group_deploy_keys(id) ON DELETE CASCADE;
+
ALTER TABLE ONLY public.merge_request_user_mentions
ADD CONSTRAINT fk_rails_c440b9ea31 FOREIGN KEY (note_id) REFERENCES public.notes(id) ON DELETE CASCADE;
@@ -12745,9 +22486,6 @@ ALTER TABLE ONLY public.gpg_signatures
ALTER TABLE ONLY public.board_group_recent_visits
ADD CONSTRAINT fk_rails_ca04c38720 FOREIGN KEY (board_id) REFERENCES public.boards(id) ON DELETE CASCADE;
-ALTER TABLE ONLY public.ci_daily_report_results
- ADD CONSTRAINT fk_rails_cc5caec7d9 FOREIGN KEY (last_pipeline_id) REFERENCES public.ci_pipelines(id) ON DELETE CASCADE;
-
ALTER TABLE ONLY public.issues_self_managed_prometheus_alert_events
ADD CONSTRAINT fk_rails_cc5d88bbb0 FOREIGN KEY (issue_id) REFERENCES public.issues(id) ON DELETE CASCADE;
@@ -12847,6 +22585,9 @@ ALTER TABLE ONLY public.merge_request_metrics
ALTER TABLE ONLY public.draft_notes
ADD CONSTRAINT fk_rails_e753681674 FOREIGN KEY (merge_request_id) REFERENCES public.merge_requests(id) ON DELETE CASCADE;
+ALTER TABLE ONLY public.group_deploy_keys_groups
+ ADD CONSTRAINT fk_rails_e87145115d FOREIGN KEY (group_id) REFERENCES public.namespaces(id) ON DELETE CASCADE;
+
ALTER TABLE ONLY public.description_versions
ADD CONSTRAINT fk_rails_e8f4caf9c7 FOREIGN KEY (epic_id) REFERENCES public.epics(id) ON DELETE CASCADE;
@@ -12862,8 +22603,8 @@ ALTER TABLE ONLY public.protected_branch_unprotect_access_levels
ALTER TABLE ONLY public.alert_management_alert_user_mentions
ADD CONSTRAINT fk_rails_eb2de0cdef FOREIGN KEY (note_id) REFERENCES public.notes(id) ON DELETE CASCADE;
-ALTER TABLE ONLY public.ci_daily_report_results
- ADD CONSTRAINT fk_rails_ebc2931b90 FOREIGN KEY (project_id) REFERENCES public.projects(id) ON DELETE CASCADE;
+ALTER TABLE ONLY public.snippet_statistics
+ ADD CONSTRAINT fk_rails_ebc283ccf1 FOREIGN KEY (snippet_id) REFERENCES public.snippets(id) ON DELETE CASCADE;
ALTER TABLE ONLY public.cluster_providers_aws
ADD CONSTRAINT fk_rails_ed1fdfaeb2 FOREIGN KEY (created_by_user_id) REFERENCES public.users(id) ON DELETE SET NULL;
@@ -12970,6 +22711,9 @@ ALTER TABLE ONLY public.timelogs
ALTER TABLE ONLY public.u2f_registrations
ADD CONSTRAINT fk_u2f_registrations_user_id FOREIGN KEY (user_id) REFERENCES public.users(id) ON DELETE CASCADE;
+ALTER TABLE public.product_analytics_events_experimental
+ ADD CONSTRAINT product_analytics_events_experimental_project_id_fkey FOREIGN KEY (project_id) REFERENCES public.projects(id) ON DELETE CASCADE;
+
COPY "schema_migrations" (version) FROM STDIN;
20181228175414
20190102152410
@@ -12998,7 +22742,6 @@ COPY "schema_migrations" (version) FROM STDIN;
20190220150130
20190222051615
20190225152525
-20190225160300
20190225160301
20190228192410
20190301081611
@@ -13377,6 +23120,7 @@ COPY "schema_migrations" (version) FROM STDIN;
20191112105448
20191112115247
20191112115317
+20191112212815
20191112214305
20191112221821
20191112232338
@@ -13653,6 +23397,7 @@ COPY "schema_migrations" (version) FROM STDIN;
20200227164113
20200227165129
20200228160542
+20200229171700
20200302142052
20200302152516
20200303055348
@@ -13671,6 +23416,10 @@ COPY "schema_migrations" (version) FROM STDIN;
20200304160801
20200304160823
20200304211738
+20200305020458
+20200305020459
+20200305082754
+20200305082858
20200305121159
20200305151736
20200305200641
@@ -13877,6 +23626,7 @@ COPY "schema_migrations" (version) FROM STDIN;
20200424043515
20200424050250
20200424101920
+20200424102023
20200424135319
20200427064130
20200428134356
@@ -13904,6 +23654,11 @@ COPY "schema_migrations" (version) FROM STDIN;
20200508140959
20200508203901
20200509203901
+20200510181937
+20200510182218
+20200510182556
+20200510182824
+20200510183128
20200511080113
20200511083541
20200511092246
@@ -13946,22 +23701,28 @@ COPY "schema_migrations" (version) FROM STDIN;
20200519141534
20200519171058
20200519194042
+20200519201128
20200520103514
20200521022725
20200521225327
20200521225337
20200521225346
+20200522205606
20200522235146
+20200524104346
20200525114553
20200525121014
20200525144525
20200526000407
20200526013844
+20200526115436
20200526120714
20200526142550
20200526153844
20200526164946
20200526164947
+20200526193555
+20200526231421
20200527092027
20200527094322
20200527095401
@@ -13971,6 +23732,7 @@ COPY "schema_migrations" (version) FROM STDIN;
20200527152657
20200527170649
20200527211000
+20200527211605
20200528054112
20200528123703
20200528125905
@@ -13981,23 +23743,126 @@ COPY "schema_migrations" (version) FROM STDIN;
20200602143020
20200603073101
20200603180338
+20200604001128
20200604143628
20200604145731
20200604174544
20200604174558
20200605003204
20200605093113
+20200605160806
+20200605160836
+20200605160851
20200608072931
20200608075553
+20200608195222
+20200608203426
+20200608205813
+20200608212030
+20200608212435
+20200608212549
+20200608212652
+20200608212807
+20200608212824
20200608214008
20200609002841
+20200609012539
20200609142506
20200609142507
20200609142508
20200609212701
+20200610130002
+20200613104045
20200615083635
+20200615111857
20200615121217
20200615123055
+20200615141554
+20200615193524
20200615232735
+20200615234047
+20200616124338
+20200616145031
+20200617000757
+20200617001001
+20200617001118
+20200617001637
+20200617001848
+20200617002030
+20200617150041
+20200617205000
+20200618105638
+20200618134223
+20200618134723
+20200618152212
+20200619000316
+20200619154527
+20200619154528
+20200622040750
+20200622070606
+20200622070620
+20200622095419
+20200622103836
+20200622104923
+20200622235737
+20200623000148
+20200623000320
+20200623073431
+20200623090030
+20200623121135
+20200623141217
+20200623141544
+20200623142159
+20200623170000
+20200623185440
+20200624075411
+20200624142107
+20200624142207
+20200624222443
+20200625045442
+20200625082258
+20200625113337
+20200625174052
+20200625190458
+20200626060151
+20200626130220
+20200628210938
+20200629192638
+20200630091656
+20200630110826
+20200701064756
+20200701070435
+20200701091253
+20200701093859
+20200701190523
+20200701205710
+20200702123805
+20200702201039
+20200703064117
+20200703121557
+20200703124823
+20200703125016
+20200703154822
+20200704143633
+20200704161600
+20200706005325
+20200706035141
+20200706154619
+20200706170536
+20200707071941
+20200707094341
+20200707095849
+20200708080631
+20200709101408
+20200710102418
+20200710102846
+20200710105332
+20200710130234
+20200712084655
+20200712235622
+20200713071042
+20200713152443
+20200716044023
+20200716120419
\.
diff --git a/doc/.vale/gitlab/Acronyms.yml b/doc/.vale/gitlab/Acronyms.yml
index 5176a18e2b6..d166e71491c 100644
--- a/doc/.vale/gitlab/Acronyms.yml
+++ b/doc/.vale/gitlab/Acronyms.yml
@@ -3,7 +3,7 @@
#
# Checks for unexpanded acronyms.
#
-# For a list of all options, see https://errata-ai.github.io/vale/styles/
+# For a list of all options, see https://errata-ai.gitbook.io/vale/getting-started/styles
extends: conditional
message: '"%s" has no definition.'
link: https://about.gitlab.com/handbook/marketing/growth-marketing/content/editorial-team/#acronyms
@@ -18,12 +18,15 @@ exceptions:
- ARN
- ASCII
- AWS
+ - CLI
- CNAME
- CPU
+ - CORE
- CSS
- CSV
- DNS
- EKS
+ - FAQ
- GDK
- GET
- GNU
@@ -33,21 +36,28 @@ exceptions:
- HTTP
- HTTPS
- IAM
+ - IBM
- IDE
+ - IRC
+ - ISO
- JSON
- LDAP
- LDAPS
- LESS
- LFS
+ - LRU
- NFS
- NGINX
- NOTE
+ - NPM
- ONLY
+ - PDF
- PGP
- PHP
- POST
- PUT
- RPC
+ - RAM
- RSA
- RSS
- SAML
@@ -58,13 +68,17 @@ exceptions:
- SSH
- SSL
- SSO
+ - SVN
+ - TCP
- TIP
- TLS
- TODO
- TOML
- UNIX
+ - USB
- URI
- URL
+ - UUID
- VPC
- WIP
- XML
diff --git a/doc/.vale/gitlab/AlertBoxStyle.yml b/doc/.vale/gitlab/AlertBoxStyle.yml
new file mode 100644
index 00000000000..831d5395fc8
--- /dev/null
+++ b/doc/.vale/gitlab/AlertBoxStyle.yml
@@ -0,0 +1,16 @@
+---
+# Error: gitlab.AlertBoxStyle
+#
+# Makes sure alert boxes follow standard formatting.
+#
+# For a list of all options, see https://errata-ai.gitbook.io/vale/getting-started/styles
+extends: existence
+message: 'Alert box "%s" must use the formatting detailed in the documentation style guide.'
+link: https://docs.gitlab.com/ee/development/documentation/styleguide.html#alert-boxes
+level: error
+scope: raw
+raw:
+ - '((NOTE|TIP|CAUTION|DANGER): \*\*[^:]*\*\*)|'
+ - '((NOTE: \*\*NOTE:\*\*)|(TIP: \*\*TIP:\*\*)|(CAUTION: \*\*CAUTION:\*\*)|(DANGER: \*\*DANGER:\*\*))|'
+ - '((NOTE: \*\*note:\*\*)|(TIP: \*\*tip:\*\*)|(CAUTION: \*\*caution:\*\*)|(DANGER: \*\*danger:\*\*))|'
+ - '((NOTE|TIP|CAUTION|DANGER): \*\*.*\*\*.+)'
diff --git a/doc/.vale/gitlab/BadgeCapitalization.yml b/doc/.vale/gitlab/BadgeCapitalization.yml
index c9e9da3b6ce..caf7143e254 100644
--- a/doc/.vale/gitlab/BadgeCapitalization.yml
+++ b/doc/.vale/gitlab/BadgeCapitalization.yml
@@ -3,7 +3,7 @@
#
# Verifies that badges are not mixed case, which won't render properly.
#
-# For a list of all options, see https://errata-ai.github.io/vale/styles/
+# For a list of all options, see https://errata-ai.gitbook.io/vale/getting-started/styles
extends: existence
message: 'Badge "%s" must be capitalized.'
link: https://docs.gitlab.com/ee/development/documentation/styleguide.html#product-badges
diff --git a/doc/.vale/gitlab/British.yml b/doc/.vale/gitlab/British.yml
index 1e5841d3648..3a0cb321f93 100644
--- a/doc/.vale/gitlab/British.yml
+++ b/doc/.vale/gitlab/British.yml
@@ -3,7 +3,7 @@
#
# Checks that US spelling is used over British spelling.
#
-# For a list of all options, see https://errata-ai.github.io/vale/styles/
+# For a list of all options, see https://errata-ai.gitbook.io/vale/getting-started/styles
extends: substitution
message: 'Use the US spelling "%s" instead of the British "%s".'
link: https://about.gitlab.com/handbook/communication/#top-misused-terms
diff --git a/doc/.vale/gitlab/CodeblockFences.yml b/doc/.vale/gitlab/CodeblockFences.yml
index 8b61a1a3c16..7258a8ef475 100644
--- a/doc/.vale/gitlab/CodeblockFences.yml
+++ b/doc/.vale/gitlab/CodeblockFences.yml
@@ -3,7 +3,7 @@
#
# Ensures all codeblock language tags use the full name, not aliases.
#
-# For a list of all options, see https://errata-ai.github.io/vale/styles/
+# For a list of all options, see https://errata-ai.gitbook.io/vale/getting-started/styles
extends: existence
message: 'Syntax highlighting hint "%s" must be one of: yaml, ruby, plaintext, markdown, javascript, shell, golang, python, dockerfile, or typescript.'
link: https://docs.gitlab.com/ee/development/documentation/styleguide.html#code-blocks
diff --git a/doc/.vale/gitlab/Contractions.yml b/doc/.vale/gitlab/Contractions.yml
index 45212945c53..dc48b876f40 100644
--- a/doc/.vale/gitlab/Contractions.yml
+++ b/doc/.vale/gitlab/Contractions.yml
@@ -3,7 +3,7 @@
#
# Checks for use of common and uncommon contractions.
#
-# For a list of all options, see https://errata-ai.github.io/vale/styles/
+# For a list of all options, see https://errata-ai.gitbook.io/vale/getting-started/styles
extends: substitution
message: 'Use "%s" instead of "%s", for a friendly, informal tone.'
link: https://docs.gitlab.com/ee/development/documentation/styleguide.html#language
@@ -20,7 +20,6 @@ swap:
have not: haven't
that is: that's
we are: we're
- will not: won't
would not: wouldn't
you are: you're
you have: you've
@@ -31,25 +30,16 @@ swap:
didn't: did not
doesn't: does not
hasn't: has not
- how'll: how will
how's: how is
isn't: is not
- it'll: it will
shouldn't: should not
- that'll: that will
- they'll: they will
they're: they are
wasn't: was not
weren't: were not
- we'll: we will
we've: we have
what's: what is
- what'll: what will
when's: when is
- when'll: when will
where's: where is
- where'll: where will
who's: who is
- who'll: who will
why's: why is
- why'll: why will
+
diff --git a/doc/.vale/gitlab/CurlStringsQuoted.yml b/doc/.vale/gitlab/CurlStringsQuoted.yml
index 4fcb0069423..39ee9372947 100644
--- a/doc/.vale/gitlab/CurlStringsQuoted.yml
+++ b/doc/.vale/gitlab/CurlStringsQuoted.yml
@@ -3,7 +3,7 @@
#
# Ensures all codeblocks using curl quote any URL strings.
#
-# For a list of all options, see https://errata-ai.github.io/vale/styles/
+# For a list of all options, see https://errata-ai.gitbook.io/vale/getting-started/styles
extends: existence
message: 'Curl commands must wrap URLs in double quotes ("): %s'
link: https://docs.gitlab.com/ee/development/documentation/styleguide.html#curl-commands
diff --git a/doc/.vale/gitlab/CurrentStatus.yml b/doc/.vale/gitlab/CurrentStatus.yml
new file mode 100644
index 00000000000..7368310eee8
--- /dev/null
+++ b/doc/.vale/gitlab/CurrentStatus.yml
@@ -0,0 +1,13 @@
+---
+# Suggestion: gitlab.CurrentStatus
+#
+# Checks for words that indicate a product or feature may change in the future.
+#
+# For a list of all options, see https://errata-ai.gitbook.io/vale/getting-started/styles
+extends: existence
+message: 'Avoid words like "%s" that promise future changes.'
+level: suggestion
+ignorecase: true
+link: https://docs.gitlab.com/ee/development/documentation/styleguide.html#language-to-avoid
+tokens:
+ - currently
diff --git a/doc/.vale/gitlab/FirstPerson.yml b/doc/.vale/gitlab/FirstPerson.yml
index 6db89dd4758..d247f137501 100644
--- a/doc/.vale/gitlab/FirstPerson.yml
+++ b/doc/.vale/gitlab/FirstPerson.yml
@@ -3,7 +3,7 @@
#
# Checks for use of first person pronouns.
#
-# For a list of all options, see https://errata-ai.github.io/vale/styles/
+# For a list of all options, see https://errata-ai.gitbook.io/vale/getting-started/styles
extends: existence
message: '"%s" is a first-person pronoun. Use second- or third-person pronouns (like we, you, us, one) instead.'
level: warning
diff --git a/doc/.vale/gitlab/FutureTense.yml b/doc/.vale/gitlab/FutureTense.yml
new file mode 100644
index 00000000000..a53a7dd29cc
--- /dev/null
+++ b/doc/.vale/gitlab/FutureTense.yml
@@ -0,0 +1,17 @@
+---
+# Suggestion: gitlab.FutureTense
+#
+# Checks for use of future tense in sentences. Present tense is preferred as
+# much as possible.
+#
+# For a list of all options, see https://errata-ai.gitbook.io/vale/getting-started/styles
+extends: existence
+message: 'Avoid using future tense: "%s"'
+ignorecase: true
+level: warning
+link: https://docs.gitlab.com/ee/development/documentation/styleguide.html#language-to-avoid
+raw:
+ - "(going to( |\n|[[:punct:]])[a-zA-Z]*|"
+ - "will( |\n|[[:punct:]])[a-zA-Z]*|"
+ - "won't( |\n|[[:punct:]])[a-zA-Z]*|"
+ - "[a-zA-Z]*'ll( |\n|[[:punct:]])[a-zA-Z]*)"
diff --git a/doc/.vale/gitlab/InternalLinkExtension.yml b/doc/.vale/gitlab/InternalLinkExtension.yml
index 94a935196a7..61a08e4a86c 100644
--- a/doc/.vale/gitlab/InternalLinkExtension.yml
+++ b/doc/.vale/gitlab/InternalLinkExtension.yml
@@ -3,7 +3,7 @@
#
# Checks that internal links have .md extenstion and not .html extension.
#
-# For a list of all options, see https://errata-ai.github.io/vale/styles/
+# For a list of all options, see https://errata-ai.gitbook.io/vale/getting-started/styles
extends: existence
message: 'Link "%s" must use the .md file extension.'
link: https://docs.gitlab.com/ee/development/documentation/styleguide.html#links-to-internal-documentation
diff --git a/doc/.vale/gitlab/LatinTerms.yml b/doc/.vale/gitlab/LatinTerms.yml
index a2d024fb1ec..26dba42839a 100644
--- a/doc/.vale/gitlab/LatinTerms.yml
+++ b/doc/.vale/gitlab/LatinTerms.yml
@@ -3,7 +3,7 @@
#
# Checks for use of latin terms.
#
-# For a list of all options, see https://errata-ai.github.io/vale/styles/
+# For a list of all options, see https://errata-ai.gitbook.io/vale/getting-started/styles
extends: substitution
message: 'Use "%s" instead of "%s", but consider rewriting the sentence.'
link: https://docs.gitlab.com/ee/development/documentation/styleguide.html#language
diff --git a/doc/.vale/gitlab/MeaningfulLinkWords.yml b/doc/.vale/gitlab/MeaningfulLinkWords.yml
index 1931112ab3e..4a255e5aae4 100644
--- a/doc/.vale/gitlab/MeaningfulLinkWords.yml
+++ b/doc/.vale/gitlab/MeaningfulLinkWords.yml
@@ -3,7 +3,7 @@
#
# Checks for the presence of semantically unhelpful words in link text.
#
-# For a list of all options, see https://errata-ai.github.io/vale/styles/
+# For a list of all options, see https://errata-ai.gitbook.io/vale/getting-started/styles
extends: existence
message: 'Improve SEO and accessibility by rewriting "%s" in the link text.'
level: warning
diff --git a/doc/.vale/gitlab/MergeConflictMarkers.yml b/doc/.vale/gitlab/MergeConflictMarkers.yml
index 4d733c856e5..4f216ac34c5 100644
--- a/doc/.vale/gitlab/MergeConflictMarkers.yml
+++ b/doc/.vale/gitlab/MergeConflictMarkers.yml
@@ -3,7 +3,7 @@
#
# Checks for the presence of merge conflict markers.
#
-# For a list of all options, see https://errata-ai.github.io/vale/styles/
+# For a list of all options, see https://errata-ai.gitbook.io/vale/getting-started/styles
extends: existence
message: 'Merge conflict marker "%s" found.'
link: https://docs.gitlab.com/ee/development/code_review.html#merging-a-merge-request
diff --git a/doc/.vale/gitlab/OutdatedVersions.yml b/doc/.vale/gitlab/OutdatedVersions.yml
new file mode 100644
index 00000000000..3252481a523
--- /dev/null
+++ b/doc/.vale/gitlab/OutdatedVersions.yml
@@ -0,0 +1,21 @@
+---
+# Warning: gitlab.OutdatedVersions
+#
+# Checks for references to versions of GitLab that are no longer supported.
+#
+# For a list of all options, see https://errata-ai.github.io/vale/styles/
+extends: existence
+message: 'Can this reference to "%s" be refactored?'
+link: https://docs.gitlab.com/ee/development/documentation/styleguide.html#importance-of-referencing-gitlab-versions-and-tiers
+level: warning
+nonword: true
+ignorecase: true
+tokens:
+ - "GitLab (v)?2."
+ - "GitLab (v)?3."
+ - "GitLab (v)?4."
+ - "GitLab (v)?5."
+ - "GitLab (v)?6."
+ - "GitLab (v)?7."
+ - "GitLab (v)?8."
+ - "GitLab (v)?9."
diff --git a/doc/.vale/gitlab/OxfordComma.yml b/doc/.vale/gitlab/OxfordComma.yml
index e04d209d960..334db5d0388 100644
--- a/doc/.vale/gitlab/OxfordComma.yml
+++ b/doc/.vale/gitlab/OxfordComma.yml
@@ -4,7 +4,7 @@
# Checks for the lack of an Oxford comma. In some cases, will catch overly
# complex sentence structures with lots of commas.
#
-# For a list of all options, see https://errata-ai.github.io/vale/styles/
+# For a list of all options, see https://errata-ai.gitbook.io/vale/getting-started/styles
extends: existence
message: 'Use a comma before the last "and" or "or" in a list of four or more items.'
link: https://docs.gitlab.com/ee/development/documentation/styleguide.html#punctuation
diff --git a/doc/.vale/gitlab/ReferenceLinks.yml b/doc/.vale/gitlab/ReferenceLinks.yml
index 8a3b6940187..49e6ed5a531 100644
--- a/doc/.vale/gitlab/ReferenceLinks.yml
+++ b/doc/.vale/gitlab/ReferenceLinks.yml
@@ -3,7 +3,7 @@
#
# Checks for the presence of reference-style links that must be inline.
#
-# For a list of all options, see https://errata-ai.github.io/vale/styles/
+# For a list of all options, see https://errata-ai.gitbook.io/vale/getting-started/styles
extends: existence
message: 'Link "%s" must be inline.'
link: https://docs.gitlab.com/ee/development/documentation/styleguide.html#basic-link-criteria
diff --git a/doc/.vale/gitlab/RelativeLinks.yml b/doc/.vale/gitlab/RelativeLinks.yml
index de24d0608e7..f7407375b84 100644
--- a/doc/.vale/gitlab/RelativeLinks.yml
+++ b/doc/.vale/gitlab/RelativeLinks.yml
@@ -3,7 +3,7 @@
#
# Checks for the presence of absolute hyperlinks that should be relative.
#
-# For a list of all options, see https://errata-ai.github.io/vale/styles/
+# For a list of all options, see https://errata-ai.gitbook.io/vale/getting-started/styles
extends: existence
message: 'Link "%s" must be relative.'
link: https://docs.gitlab.com/ee/development/documentation/styleguide.html#links-to-internal-documentation
diff --git a/doc/.vale/gitlab/Repetition.yml b/doc/.vale/gitlab/Repetition.yml
index 76afb7bb5ab..c4b0cc14192 100644
--- a/doc/.vale/gitlab/Repetition.yml
+++ b/doc/.vale/gitlab/Repetition.yml
@@ -3,7 +3,7 @@
#
# Checks for duplicate words, like `the the` or `and and`.
#
-# For a list of all options, see https://errata-ai.github.io/vale/styles/
+# For a list of all options, see https://errata-ai.gitbook.io/vale/getting-started/styles
extends: repetition
message: '"%s" is repeated.'
level: error
diff --git a/doc/.vale/gitlab/SentenceLength.yml b/doc/.vale/gitlab/SentenceLength.yml
index b19b76723a6..da9fa052584 100644
--- a/doc/.vale/gitlab/SentenceLength.yml
+++ b/doc/.vale/gitlab/SentenceLength.yml
@@ -3,7 +3,7 @@
#
# Counts words in a sentence and alerts if a sentence exceeds 25 words.
#
-# For a list of all options, see https://errata-ai.github.io/vale/styles/
+# For a list of all options, see https://errata-ai.gitbook.io/vale/getting-started/styles
extends: occurrence
message: 'Shorter sentences improve readability (max 25 words).'
scope: sentence
diff --git a/doc/.vale/gitlab/SentenceSpacing.yml b/doc/.vale/gitlab/SentenceSpacing.yml
index c460ef3ae65..e6da920222c 100644
--- a/doc/.vale/gitlab/SentenceSpacing.yml
+++ b/doc/.vale/gitlab/SentenceSpacing.yml
@@ -1,12 +1,12 @@
---
# Error: gitlab.SentenceSpacing
#
-# Check for the following in common content scenarios:
+# Checks for the following in common content scenarios:
#
# - No spaces.
# - More than one space.
#
-# For a list of all options, see https://errata-ai.github.io/vale/styles/
+# For a list of all options, see https://errata-ai.gitbook.io/vale/getting-started/styles
extends: existence
message: '"%s" must contain one and only one space.'
link: https://docs.gitlab.com/ee/development/documentation/styleguide.html#punctuation
diff --git a/doc/.vale/gitlab/Spelling.yml b/doc/.vale/gitlab/Spelling.yml
index 7bf0f085f5c..602b7cd11e6 100644
--- a/doc/.vale/gitlab/Spelling.yml
+++ b/doc/.vale/gitlab/Spelling.yml
@@ -9,7 +9,7 @@
# Commands, like `git clone` must use backticks, and must not be added to the
# exceptions.
#
-# For a list of all options, see https://errata-ai.github.io/vale/styles/
+# For a list of all options, see https://errata-ai.gitbook.io/vale/getting-started/styles
extends: spelling
message: 'Spelling check: "%s"?'
level: warning
diff --git a/doc/.vale/gitlab/SubstitutionWarning.yml b/doc/.vale/gitlab/SubstitutionWarning.yml
index 8c5f7705417..5324a48e38c 100644
--- a/doc/.vale/gitlab/SubstitutionWarning.yml
+++ b/doc/.vale/gitlab/SubstitutionWarning.yml
@@ -4,7 +4,7 @@
# Warns against using common shorthand for terms.
# For substitutions flagged as errors, see Substitutions.yml
#
-# For a list of all options, see https://errata-ai.github.io/vale/styles/
+# For a list of all options, see https://errata-ai.gitbook.io/vale/getting-started/styles
extends: substitution
message: 'If possible, use "%s" instead of "%s".'
link: https://about.gitlab.com/handbook/communication/#top-misused-terms
@@ -13,8 +13,11 @@ ignorecase: true
swap:
admin: administrator
blacklist(ed|ing)?: denylist
+ code base: codebase
config: configuration
distro: distribution
+ file name: filename
+ filesystem: file system
info: information
repo: repository
whitelist(ed|ing)?: allowlist
diff --git a/doc/.vale/gitlab/Substitutions.yml b/doc/.vale/gitlab/Substitutions.yml
index 156ff3a53f0..77536ea0b33 100644
--- a/doc/.vale/gitlab/Substitutions.yml
+++ b/doc/.vale/gitlab/Substitutions.yml
@@ -4,7 +4,7 @@
# Checks for use of some of the top misused terms at GitLab.
# For substitutions only flagged as warnings, see SubstitutionWarning.yml
#
-# For a list of all options, see https://errata-ai.github.io/vale/styles/
+# For a list of all options, see https://errata-ai.gitbook.io/vale/getting-started/styles
extends: substitution
message: 'Use "%s" instead of "%s".'
link: https://about.gitlab.com/handbook/communication/#top-misused-terms
diff --git a/doc/.vale/gitlab/VersionText.yml b/doc/.vale/gitlab/VersionText.yml
index 9a05103cc39..3723170b169 100644
--- a/doc/.vale/gitlab/VersionText.yml
+++ b/doc/.vale/gitlab/VersionText.yml
@@ -13,7 +13,7 @@
# immediately on the next line is ok. However, this will often highlight where multi-line version
# text is attempted without `-` characters.
#
-# For a list of all options, see https://errata-ai.github.io/vale/styles/
+# For a list of all options, see https://errata-ai.gitbook.io/vale/getting-started/styles
extends: existence
message: '"%s" is not formatted correctly.'
link: https://docs.gitlab.com/ee/development/documentation/styleguide.html#text-for-documentation-requiring-version-text
diff --git a/doc/.vale/gitlab/spelling-exceptions.txt b/doc/.vale/gitlab/spelling-exceptions.txt
index b56fa2861ca..28da80557ec 100644
--- a/doc/.vale/gitlab/spelling-exceptions.txt
+++ b/doc/.vale/gitlab/spelling-exceptions.txt
@@ -83,6 +83,7 @@ compilable
composable
Conda
Consul
+Contentful
Corosync
cron
crons
@@ -224,6 +225,7 @@ LDAP
ldapsearch
Leiningen
Libravatar
+liveness
Lograge
Logstash
lookahead
@@ -247,6 +249,7 @@ memoization
memoize
memoized
memoizing
+Memorystore
mergeable
Microsoft
middleware
@@ -334,8 +337,7 @@ Puma
Python
Qualys
Rackspace
-Raketask
-Raketasks
+Raspbian
reachability
rebase
rebased
@@ -423,6 +425,7 @@ storages
strace
strikethrough
strikethroughs
+stunnel
subpath
subfolder
subfolders
diff --git a/doc/README.md b/doc/README.md
index fe38b6cb419..115026dae6e 100644
--- a/doc/README.md
+++ b/doc/README.md
@@ -11,7 +11,7 @@ description: 'Learn how to use and administer GitLab, the most scalable Git-base
# GitLab Docs
-Welcome to [GitLab](https://about.gitlab.com/) Documentation.
+Welcome to [GitLab](https://about.gitlab.com/) documentation.
Here you can access the complete documentation for GitLab, the single application for the
[entire DevOps lifecycle](#the-entire-devops-lifecycle).
@@ -20,30 +20,32 @@ Here you can access the complete documentation for GitLab, the single applicatio
No matter how you use GitLab, we have documentation for you.
-| Essential Documentation | Essential Documentation |
-|:-------------------------------------------------------------------------------------------------------------------------------------------|:---------------------------------------------------------------------------------------------------------------------------|
-| [**User Documentation**](user/index.md)<br/>Discover features and concepts for GitLab users. | [**Administrator documentation**](administration/index.md)<br/>Everything GitLab self-managed administrators need to know. |
-| [**Contributing to GitLab**](#contributing-to-gitlab)<br/>At GitLab, everyone can contribute! | [**New to Git and GitLab?**](#new-to-git-and-gitlab)<br/>We have the resources to get you started. |
-| [**Building an integration with GitLab?**](#building-an-integration-with-gitlab)<br/>Consult our automation and integration documentation. | [**Coming to GitLab from another platform?**](#coming-to-gitlab-from-another-platform)<br/>Consult our handy guides. |
-| [**Install GitLab**](https://about.gitlab.com/install/)<br/>Installation options for different platforms. | [**Customers**](subscriptions/index.md)<br/>Information for new and existing customers. |
-| [**Update GitLab**](update/README.md)<br/>Update your GitLab self-managed instance to the latest version. | [**Reference Architectures**](administration/reference_architectures/index.md)<br/>GitLab's reference architectures |
-| [**GitLab Releases**](https://about.gitlab.com/releases/)<br/>What's new in GitLab. | |
-
-## Popular Documentation
-
-Have a look at some of our most popular documentation resources:
-
-| Popular Topic | Description |
-|:----------------------------------------------------------------|:-----------------------------------------------------------------|
-| [Configuring `.gitlab-ci.yml`](ci/yaml/README.md) | Complete syntax documentation for configuring your CI pipelines. |
-| [GitLab CI/CD examples](ci/examples/README.md) | Get up to speed quickly with common CI/CD scenarios. |
-| [GitLab Container Registry](user/packages/container_registry/index.md) | Host Docker images within GitLab. |
-| [GitLab Pages](user/project/pages/index.md) | Host static websites for your projects with GitLab. |
-| [GitLab.com settings](user/gitlab_com/index.md) | Settings for GitLab.com. |
-| [Kubernetes integration](user/project/clusters/index.md) | Use GitLab with Kubernetes. |
-| [SSH authentication](ssh/README.md) | Secure your network communications. |
-| [Using Docker images](ci/docker/using_docker_images.md) | Build and test your applications with Docker. |
-| [GraphQL](api/graphql/index.md) | Explore GitLab's GraphQL API. |
+| Essential documentation | Essential documentation |
+|:-------------------------------------------------------------------------------------------------------------------------------------|:---------------------------------------------------------------------------------------------------------------------------|
+| [**User Documentation**](user/index.md)<br/>Discover features and concepts for GitLab users. | [**Administrator documentation**](administration/index.md)<br/>Everything GitLab self-managed administrators need to know. |
+| [**Contributing to GitLab**](#contributing-to-gitlab)<br/>At GitLab, everyone can contribute! | [**New to Git and GitLab?**](#new-to-git-and-gitlab)<br/>We have the resources to get you started. |
+| [**Build an integration with GitLab?**](#build-an-integration-with-gitlab)<br/>Consult our automation and integration documentation. | [**Coming to GitLab from another platform?**](#coming-to-gitlab-from-another-platform)<br/>Consult our handy guides. |
+| [**Install GitLab**](https://about.gitlab.com/install/)<br/>Installation options for different platforms. | [**Customers**](subscriptions/index.md)<br/>Information for new and existing customers. |
+| [**Update GitLab**](update/README.md)<br/>Update your GitLab self-managed instance to the latest version. | [**Reference Architectures**](administration/reference_architectures/index.md)<br/>GitLab's reference architectures |
+| [**GitLab Releases**](https://about.gitlab.com/releases/)<br/>What's new in GitLab. | |
+
+## Popular topics
+
+Have a look at some of our most popular topics:
+
+| Popular topic | Description |
+|:-----------------------------------------------------------------------------------------------------------|:---------------------------------------------------------------------------|
+| [Two-factor authentication](user/profile/account/two_factor_authentication.md) | Improve the security of your GitLab account. |
+| [GitLab groups](user/group/index.md) | Manage projects together. |
+| [GitLab CI/CD pipeline configuration reference](ci/yaml/README.md) | Available configuration options for `.gitlab-ci.yml` files. |
+| [Activate GitLab EE with a license](user/admin_area/license.md) **(STARTER ONLY)** | Activate GitLab Enterprise Edition functionality with a license. |
+| [Back up and restore GitLab](raketasks/backup_restore.md) **(CORE ONLY)** | Rake tasks for backing up and restoring GitLab self-managed instances. |
+| [GitLab release and maintenance policy](policy/maintenance.md) | Policies for version naming and cadence, and also upgrade recommendations. |
+| [Elasticsearch integration](integration/elasticsearch.md) **(STARTER ONLY)** | Integrate Elasticsearch with GitLab to enable advanced searching. |
+| [Omnibus GitLab database settings](https://docs.gitlab.com/omnibus/settings/database.html) **(CORE ONLY)** | Database settings for Omnibus GitLab self-managed instances. |
+| [Omnibus GitLab NGINX settings](https://docs.gitlab.com/omnibus/settings/nginx.html) **(CORE ONLY)** | NGINX settings for Omnibus GitLab self-managed instances. |
+| [Omnibus GitLab SSL configuration](https://docs.gitlab.com/omnibus/settings/ssl.html) **(CORE ONLY)** | SSL settings for Omnibus GitLab self-managed instances. |
+| [GitLab.com settings](user/gitlab_com/index.md) | Settings used for GitLab.com. |
## The entire DevOps Lifecycle
@@ -62,7 +64,7 @@ than ever.
The following sections provide links to documentation for each DevOps stage:
-| DevOps Stage | Documentation for |
+| DevOps stage | Documentation for |
|:------------------------|:------------------------------------------------------------|
| [Manage](#manage) | Statistics and analytics features. |
| [Plan](#plan) | Project planning and management features. |
@@ -86,7 +88,7 @@ GitLab provides statistics and insight into ways you can maximize the value of G
The following documentation relates to the DevOps **Manage** stage:
-| Manage Topics | Description |
+| Manage topics | Description |
|:--------------------------------------------------------------------------------------|:-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| [Authentication and<br/>Authorization](administration/auth/README.md) **(CORE ONLY)** | Supported authentication and authorization providers. |
| [GitLab Value Stream Analytics](user/project/cycle_analytics.md) | Measure the time it takes to go from an [idea to production](https://about.gitlab.com/blog/2016/08/05/continuous-integration-delivery-and-deployment-with-gitlab/#from-idea-to-production-with-gitlab) for each project you have. |
@@ -107,7 +109,7 @@ management tools.
The following documentation relates to the DevOps **Plan** stage:
-| Plan Topics | Description |
+| Plan topics | Description |
|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:-------------------------------------------------------------------------------------------------------------------------------------------------|
| [Burndown Charts](user/project/milestones/burndown_charts.md) **(STARTER)** | Watch your project's progress throughout a specific milestone. |
| [Discussions](user/discussions/index.md) | Threads, comments, and resolvable threads in issues, commits, and merge requests. |
@@ -121,7 +123,7 @@ The following documentation relates to the DevOps **Plan** stage:
| [Related Issues](user/project/issues/related_issues.md) **(STARTER)** | Create a relationship between issues. |
| [Requirements Management](user/project/requirements/index.md) **(ULTIMATE)** | Check your products against a set of criteria. |
| [Roadmap](user/group/roadmap/index.md) **(ULTIMATE)** | Visualize epic timelines. |
-| [Service Desk](user/project/service_desk.md) **(PREMIUM)** | A simple way to allow people to create issues in your GitLab instance without needing their own user account. |
+| [Service Desk](user/project/service_desk.md) | A simple way to allow people to create issues in your GitLab instance without needing their own user account. |
| [Time Tracking](user/project/time_tracking.md) | Track time spent on issues and merge requests. |
| [Todos](user/todos.md) | Keep track of work requiring attention with a chronological list displayed on a simple dashboard. |
@@ -136,7 +138,7 @@ The following documentation relates to the DevOps **Plan** stage:
Consolidate source code into a single [distributed version control system](https://en.wikipedia.org/wiki/Distributed_version_control)
that’s easily managed and controlled without disrupting your workflow.
-GitLab’s Git repositories come complete with branching tools and access
+GitLab repositories come complete with branching tools and access
controls, providing a scalable, single source of truth for collaborating
on projects and code.
@@ -144,7 +146,7 @@ The following documentation relates to the DevOps **Create** stage:
#### Projects and Groups
-| Create Topics - Projects and Groups | Description |
+| Create topics - Projects and Groups | Description |
|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:-------------------------------------------------------------------------------------------------|
| [Advanced global search](user/search/advanced_global_search.md) **(STARTER)** | Leverage Elasticsearch for faster, more advanced code search across your entire GitLab instance. |
| [Advanced syntax search](user/search/advanced_search_syntax.md) **(STARTER)** | Use advanced queries for more targeted search results. |
@@ -169,7 +171,7 @@ The following documentation relates to the DevOps **Create** stage:
#### Repositories
-| Create Topics - Repositories | Description |
+| Create topics - Repositories | Description |
|:------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:--------------------------------------------------------------------------------|
| [Branches](user/project/repository/branches/index.md) and the [default branch](user/project/repository/branches/index.md#default-branch) | How to use branches in GitLab. |
| [Commits](user/project/repository/index.md#commits) and [signing commits](user/project/repository/gpg_signed_commits/index.md) | Work with commits, and use GPG to sign your commits. |
@@ -192,13 +194,13 @@ The following documentation relates to the DevOps **Create** stage:
#### Merge Requests
-| Create Topics - Merge Requests | Description |
+| Create topics - Merge Requests | Description |
|:--------------------------------------------------------------------------------------------------------------------------------------------|:--------------------------------------------------------------------------------------------------------------------------------------|
| [Checking out merge requests locally](user/project/merge_requests/reviewing_and_managing_merge_requests.md#checkout-merge-requests-locally) | Tips for working with merge requests locally. |
| [Cherry-picking](user/project/merge_requests/cherry_pick_changes.md) | Use GitLab for cherry-picking changes. |
| [Merge request thread resolution](user/discussions/index.md#moving-a-single-thread-to-a-new-issue) | Resolve threads, move threads in a merge request to an issue, and only allow merge requests to be merged if all threads are resolved. |
| [Merge requests](user/project/merge_requests/index.md) | Merge request management. |
-| [Work In Progress "WIP" merge requests](user/project/merge_requests/work_in_progress_merge_requests.md) | Prevent merges of work-in-progress merge requests. |
+| [**Draft** merge requests](user/project/merge_requests/work_in_progress_merge_requests.md) | Prevent merges of draft merge requests. |
<div align="right">
<a type="button" class="btn btn-default" href="#overview">
@@ -208,7 +210,7 @@ The following documentation relates to the DevOps **Create** stage:
#### Integration and Automation
-| Create Topics - Integration and Automation | Description |
+| Create topics - Integration and Automation | Description |
|:------------------------------------------------------------------------------|:-----------------------------------------------------------------------------------------------------------------------|
| [GitLab API](api/README.md) | Integrate GitLab via a simple and powerful API. |
| [GitLab Integration](integration/README.md) | Integrate with multiple third-party services with GitLab to allow external issue trackers and external authentication. |
@@ -235,9 +237,9 @@ scales to run your tests faster.
The following documentation relates to the DevOps **Verify** stage:
-| Verify Topics | Description |
+| Verify topics | Description |
|:----------------------------------------------------------------------------------|:--------------------------------------------------------------------------------------------------------|
-| [Code Quality reports](user/project/merge_requests/code_quality.md) **(STARTER)** | Analyze source code quality. |
+| [Code Quality reports](user/project/merge_requests/code_quality.md) | Analyze source code quality. |
| [GitLab CI/CD](ci/README.md) | Explore the features and capabilities of Continuous Integration with GitLab. |
| [JUnit test reports](ci/junit_test_reports.md) | Display JUnit test reports on merge requests. |
| [Multi-project pipelines](ci/multi_project_pipelines.md) **(PREMIUM)** | Visualize entire pipelines that span multiple projects, including all cross-project inter-dependencies. |
@@ -258,7 +260,7 @@ packages, which can be easily consumed as a dependency in downstream projects.
The following documentation relates to the DevOps **Package** stage:
-| Package Topics | Description |
+| Package topics | Description |
|:----------------------------------------------------------------|:-------------------------------------------------------|
| [Container Registry](user/packages/container_registry/index.md) | The GitLab Container Registry enables every project in GitLab to have its own space to store [Docker](https://www.docker.com/) images. |
| [Dependency Proxy](user/packages/dependency_proxy/index.md) **(PREMIUM)** | The GitLab Dependency Proxy sets up a local proxy for frequently used upstream images/packages. |
@@ -280,7 +282,7 @@ confidently and securely with GitLab’s built-in Continuous Delivery and Deploy
The following documentation relates to the DevOps **Release** stage:
-| Release Topics | Description |
+| Release topics | Description |
|:------------------------------------------------------------------------------------------------------------------------------------|:--------------------------------------------------------------------------------------------------------------------------------------|
| [Auto Deploy](topics/autodevops/stages.md#auto-deploy) | Configure GitLab for the deployment of your application. |
| [Canary Deployments](user/project/canary_deployments.md) **(PREMIUM)** | Employ a popular CI strategy where a small portion of the fleet is updated to the new version first. |
@@ -306,19 +308,19 @@ configuration. Then customize everything from buildpacks to CI/CD.
The following documentation relates to the DevOps **Configure** stage:
-| Configure Topics | Description |
+| Configure topics | Description |
|:-----------------------------------------------------------------------------------------------------------------------------------|:--------------------------------------------------------------------------|
| [Auto DevOps](topics/autodevops/index.md) | Automatically employ a complete DevOps lifecycle. |
| [Create Kubernetes clusters](user/project/clusters/add_remove_clusters.md#create-new-cluster) | Use Kubernetes and GitLab. |
| [Executable Runbooks](user/project/clusters/runbooks/index.md) | Documented procedures that explain how to carry out particular processes. |
| [GitLab ChatOps](ci/chatops/README.md) | Interact with CI/CD jobs through chat services. |
-| [Installing Applications](user/project/clusters/index.md#installing-applications) | Deploy Helm, Ingress, and Prometheus on Kubernetes. |
+| [Installing Applications](user/project/clusters/index.md#installing-applications) | Install Helm charts such as Ingress and Prometheus on Kubernetes. |
| [Mattermost slash commands](user/project/integrations/mattermost_slash_commands.md) | Enable and use slash commands from within Mattermost. |
-| [Multiple Kubernetes Clusters](user/project/clusters/index.md#multiple-kubernetes-clusters-premium) **(PREMIUM)** | Associate more than one Kubernetes clusters to your project. |
+| [Multiple Kubernetes Clusters](user/project/clusters/index.md#multiple-kubernetes-clusters) | Associate more than one Kubernetes clusters to your project. |
| [Protected variables](ci/variables/README.md#protect-a-custom-variable) | Restrict variables to protected branches and tags. |
| [Serverless](user/project/clusters/serverless/index.md) | Run serverless workloads on Kubernetes. |
| [Slack slash commands](user/project/integrations/slack_slash_commands.md) | Enable and use slash commands from within Slack. |
-| [Manage your infrastructure with Terraform](user/infrastructure/index.md) | Manage your infrastructure as you run your CI/CD pipeline. |
+| [Manage your infrastructure with Terraform](user/infrastructure/index.md) | Manage your infrastructure as you run your CI/CD pipeline. |
<div align="right">
<a type="button" class="btn btn-default" href="#overview">
@@ -335,7 +337,7 @@ instant how code changes impact your production environment.
The following documentation relates to the DevOps **Monitor** stage:
-| Monitor Topics | Description |
+| Monitor topics | Description |
|:------------------------------------------------------------------------------------------------|:-----------------------------------------------------------------------------------------------------------------------------------------|
| [GitLab Performance Monitoring](administration/monitoring/performance/index.md) **(CORE ONLY)** | Use Prometheus and Grafana to monitor the performance of your GitLab instance. |
| [GitLab Prometheus](administration/monitoring/prometheus/index.md) **(CORE ONLY)** | Configure the bundled Prometheus to collect various metrics from your GitLab instance. |
@@ -360,7 +362,7 @@ high-level view on projects and groups, and start remediation processes when nee
The following documentation relates to the DevOps **Secure** stage:
-| Secure Topics | Description |
+| Secure topics | Description |
|:------------------------------------------------------------------------------------------------------|:-----------------------------------------------------------------------|
| [Compliance Dashboard](user/compliance/compliance_dashboard/index.md) **(ULTIMATE)** | View the most recent Merge Request activity in a group. |
| [Container Scanning](user/application_security/container_scanning/index.md) **(ULTIMATE)** | Use Clair to scan Docker images for known vulnerabilities. |
@@ -442,7 +444,7 @@ If you are coming to GitLab from another platform, you'll find the following inf
</a>
</div>
-## Building an integration with GitLab
+## Build an integration with GitLab
There are many ways to integrate with GitLab, including:
diff --git a/doc/administration/audit_events.md b/doc/administration/audit_events.md
index cc94d756f99..c85fb2d2e47 100644
--- a/doc/administration/audit_events.md
+++ b/doc/administration/audit_events.md
@@ -56,9 +56,7 @@ From there, you can see the following actions:
- User sign-in via [Group SAML](../user/group/saml_sso/index.md)
- Permissions changes of a user assigned to a group
- Removed user from group
-- Project imported in to group
-- Project added to group and with which visibility level
-- Project removed from group
+- Project repository imported into group
- [Project shared with group](../user/project/members/share_project_with_groups.md)
and with which [permissions](../user/permissions.md)
- Removal of a previously shared group with a project
@@ -80,7 +78,7 @@ To view a project's audit events, navigate to **Project > Settings > Audit Event
From there, you can see the following actions:
- Added or removed deploy keys
-- Project created, deleted, renamed, moved(transferred), changed path
+- Project created, deleted, renamed, moved (transferred), changed path
- Project changed visibility level
- User was added to project and with which [permissions](../user/permissions.md)
- Permission changes of a user assigned to a project
@@ -96,6 +94,7 @@ From there, you can see the following actions:
- Permission to approve merge requests by committers was updated ([introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/7531) in GitLab 12.9)
- Permission to approve merge requests by authors was updated ([introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/7531) in GitLab 12.9)
- Number of required approvals was updated ([introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/7531) in GitLab 12.9)
+- Added or removed users and groups from project approval groups ([introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/213603) in GitLab 13.2)
Project events can also be accessed via the [Project Audit Events API](../api/audit_events.md#project-audit-events-starter)
diff --git a/doc/administration/auth/jwt.md b/doc/administration/auth/jwt.md
index 29b192a4845..d3a77fdaca5 100644
--- a/doc/administration/auth/jwt.md
+++ b/doc/administration/auth/jwt.md
@@ -62,7 +62,8 @@ JWT will provide you with a secret key for you to use.
}
```
- NOTE: **Note:** For more information on each configuration option refer to
+ NOTE: **Note:**
+ For more information on each configuration option refer to
the [OmniAuth JWT usage documentation](https://github.com/mbleigh/omniauth-jwt#usage).
1. Change `YOUR_APP_SECRET` to the client secret and set `auth_url` to your redirect URL.
diff --git a/doc/administration/auth/ldap/google_secure_ldap.md b/doc/administration/auth/ldap/google_secure_ldap.md
index 2271ce93b6f..1f8fca33811 100644
--- a/doc/administration/auth/ldap/google_secure_ldap.md
+++ b/doc/administration/auth/ldap/google_secure_ldap.md
@@ -34,7 +34,8 @@ The steps below cover:
'Entire domain (GitLab)' or 'Selected organizational units' for both 'Verify user
credentials' and 'Read user information'. Select 'Add LDAP Client'
- TIP: **Tip:** If you plan to use GitLab [LDAP Group Sync](index.md#group-sync-starter-only)
+ TIP: **Tip:**
+ If you plan to use GitLab [LDAP Group Sync](index.md#group-sync-starter-only)
, turn on 'Read group information'.
![Add LDAP Client Step 2](img/google_secure_ldap_add_step_2.png)
diff --git a/doc/administration/auth/ldap/index.md b/doc/administration/auth/ldap/index.md
index 4a7a972596f..aef6c70ff92 100644
--- a/doc/administration/auth/ldap/index.md
+++ b/doc/administration/auth/ldap/index.md
@@ -53,7 +53,7 @@ are already logged in or are using Git over SSH will still be able to access
GitLab for up to one hour. Manually block the user in the GitLab Admin Area to
immediately block all access.
-NOTE: **Note**:
+NOTE: **Note:**
GitLab Enterprise Edition Starter supports a
[configurable sync time](#adjusting-ldap-user-sync-schedule-starter-only).
@@ -99,7 +99,7 @@ Normally, if you specify `simple_tls` it will be on port 636, while `start_tls`
would be on port 389. `plain` also operates on port 389. Removed values: `tls` was replaced with `start_tls` and `ssl` was replaced with `simple_tls`.
NOTE: **Note:**
-LDAP users must have an email address set, regardless of whether it is used to log in.
+LDAP users must have an email address set, regardless of whether it is used to sign-in.
### Example Configurations **(CORE ONLY)**
@@ -169,7 +169,7 @@ production:
| Setting | Description | Required | Examples |
| ------- | ----------- | -------- | -------- |
-| `label` | A human-friendly name for your LDAP server. It will be displayed on your login page. | yes | `'Paris'` or `'Acme, Ltd.'` |
+| `label` | A human-friendly name for your LDAP server. It will be displayed on your sign-in page. | yes | `'Paris'` or `'Acme, Ltd.'` |
| `host` | IP address or domain name of your LDAP server. | yes | `'ldap.mydomain.com'` |
| `port` | The port to connect with on your LDAP server. Always an integer, not a string. | yes | `389` or `636` (for SSL) |
| `uid` | LDAP attribute for username. Should be the attribute, not the value that maps to the `uid`. | yes | `'sAMAccountName'`, `'uid'`, `'userPrincipalName'` |
@@ -179,7 +179,7 @@ production:
| `verify_certificates` | Enables SSL certificate verification if encryption method is `start_tls` or `simple_tls`. Defaults to true. | no | boolean |
| `timeout` | Set a timeout, in seconds, for LDAP queries. This helps avoid blocking a request if the LDAP server becomes unresponsive. A value of 0 means there is no timeout. | no | `10` or `30` |
| `active_directory` | This setting specifies if LDAP server is Active Directory LDAP server. For non-AD servers it skips the AD specific queries. If your LDAP server is not AD, set this to false. | no | boolean |
-| `allow_username_or_email_login` | If enabled, GitLab will ignore everything after the first `@` in the LDAP username submitted by the user on login. If you are using `uid: 'userPrincipalName'` on ActiveDirectory you need to disable this setting, because the userPrincipalName contains an `@`. | no | boolean |
+| `allow_username_or_email_login` | If enabled, GitLab will ignore everything after the first `@` in the LDAP username submitted by the user on sign-in. If you are using `uid: 'userPrincipalName'` on ActiveDirectory you need to disable this setting, because the userPrincipalName contains an `@`. | no | boolean |
| `block_auto_created_users` | To maintain tight control over the number of active users on your GitLab installation, enable this setting to keep new users blocked until they have been cleared by the admin (default: false). | no | boolean |
| `base` | Base where we can search for users. | yes | `'ou=people,dc=gitlab,dc=example'` or `'DC=mydomain,DC=com'` |
| `user_filter` | Filter LDAP users. Format: [RFC 4515](https://tools.ietf.org/search/rfc4515) Note: GitLab does not support `omniauth-ldap`'s custom filter syntax. | no | `'(employeeType=developer)'` or `'(&(objectclass=user)(|(samaccountname=momo)(samaccountname=toto)))'` |
@@ -197,7 +197,7 @@ production:
### Attribute Configuration Settings **(CORE ONLY)**
-LDAP attributes that GitLab will use to create an account for the LDAP user. The specified attribute can either be the attribute name as a string (e.g. `'mail'`), or an array of attribute names to try in order (e.g. `['mail', 'email']`). Note that the user's LDAP login will always be the attribute specified as `uid` above.
+LDAP attributes that GitLab will use to create an account for the LDAP user. The specified attribute can either be the attribute name as a string (e.g. `'mail'`), or an array of attribute names to try in order (e.g. `['mail', 'email']`). Note that the user's LDAP sign-in will always be the attribute specified as `uid` above.
| Setting | Description | Required | Examples |
| ------- | ----------- | -------- | -------- |
@@ -396,7 +396,7 @@ Be sure to choose a different provider ID made of letters a-z and numbers 0-9.
This ID will be stored in the database so that GitLab can remember which LDAP
server a user belongs to.
-![Multiple LDAP Servers Login](img/multi_login.gif)
+![Multiple LDAP Servers Sign in](img/multi_login.gif)
Based on the example illustrated on the image above,
our `gitlab.rb` configuration would look like:
@@ -424,7 +424,7 @@ gitlab_rails['ldap_servers'] = {
'port' => 636,
...
}
-
+
}
```
@@ -450,7 +450,7 @@ has bit 2 set. See <https://ctovswild.com/2009/09/03/bitmask-searches-in-ldap/>
for more information.
The user will be set to `ldap_blocked` state in GitLab if the above conditions
-fail. This means the user will not be able to login or push/pull code.
+fail. This means the user will not be able to sign-in or push/pull code.
The process will also update the following user information:
@@ -605,6 +605,12 @@ When enabled, the following applies:
- Users are not allowed to share project with other groups or invite members to
a project created in a group.
+To enable it you need to:
+
+1. [Enable LDAP](#configuration-core-only)
+1. Navigate to **(admin)** **Admin Area > Settings -> Visibility and access controls**.
+1. Make sure the "Lock memberships to LDAP synchronization" checkbox is enabled.
+
### Adjusting LDAP group sync schedule **(STARTER ONLY)**
NOTE: **Note:**
diff --git a/doc/administration/auth/ldap/ldap-troubleshooting.md b/doc/administration/auth/ldap/ldap-troubleshooting.md
index 909802b5dec..75183f54990 100644
--- a/doc/administration/auth/ldap/ldap-troubleshooting.md
+++ b/doc/administration/auth/ldap/ldap-troubleshooting.md
@@ -27,7 +27,7 @@ Could not authenticate you from Ldapmain because "Connection timed out - user sp
```
If your configured LDAP provider and/or endpoint is offline or otherwise
-unreachable by GitLab, no LDAP user will be able to authenticate and log in.
+unreachable by GitLab, no LDAP user will be able to authenticate and sign-in.
GitLab does not cache or store credentials for LDAP users to provide authentication
during an LDAP outage.
@@ -81,7 +81,7 @@ adapter.ldap_search(options)
For examples of how this is run,
[review the `Adapter` module](https://gitlab.com/gitlab-org/gitlab/blob/master/ee/lib/ee/gitlab/auth/ldap/adapter.rb).
-### User logins
+### User sign-ins
#### No users are found
@@ -97,24 +97,24 @@ In this case, you con confirm which of the above is true using
[ldapsearch](#ldapsearch) with the existing LDAP configuration in your
`/etc/gitlab/gitlab.rb`.
-#### User(s) cannot login
+#### User(s) cannot sign-in
-A user can have trouble logging in for any number of reasons. To get started,
+A user can have trouble signing in for any number of reasons. To get started,
here are some questions to ask yourself:
- Does the user fall under the [configured `base`](index.md#configuration-core-only) in
- LDAP? The user must fall under this `base` to login.
+ LDAP? The user must fall under this `base` to sign-in.
- Does the user pass through the [configured `user_filter`](index.md#set-up-ldap-user-filter-core-only)?
If one is not configured, this question can be ignored. If it is, then the
- user must also pass through this filter to be allowed to login.
+ user must also pass through this filter to be allowed to sign-in.
- Refer to our docs on [debugging the `user_filter`](#debug-ldap-user-filter).
If the above are both okay, the next place to look for the problem is
the logs themselves while reproducing the issue.
-- Ask the user to login and let it fail.
+- Ask the user to sign-in and let it fail.
- [Look through the output](#gitlab-logs) for any errors or other
- messages about the login. You may see one of the other error messages on
+ messages about the sign-in. You may see one of the other error messages on
this page, in which case that section can help resolve the issue.
If the logs don't lead to the root of the problem, use the
@@ -125,9 +125,9 @@ It can also be helpful to
[debug a user sync](#sync-all-users-starter-only) to
investigate further.
-#### Invalid credentials on login
+#### Invalid credentials on sign-in
-If that the login credentials used are accurate on LDAP, ensure the following
+If that the sign-in credentials used are accurate on LDAP, ensure the following
are true for the user in question:
- Make sure the user you are binding with has enough permissions to read the user's
@@ -138,7 +138,7 @@ are true for the user in question:
#### Email has already been taken
-A user tries to login with the correct LDAP credentials, is denied access,
+A user tries to sign-in with the correct LDAP credentials, is denied access,
and the [production.log](../../logs.md#productionlog) shows an error that looks like this:
```plaintext
@@ -163,7 +163,7 @@ user.username
This will show you which user has this email address. One of two steps will
have to be taken here:
-- To create a new GitLab user/username for this user when logging in with LDAP,
+- To create a new GitLab user/username for this user when signing in with LDAP,
remove the secondary email to remove the conflict.
- To use an existing GitLab user/username for this user to use with LDAP,
remove this email as a secondary email and make it a primary one so GitLab
@@ -235,7 +235,7 @@ uid: John
There's a lot here, so let's go over what could be helpful when debugging.
First, GitLab will look for all users that have previously
-logged in with LDAP and iterate on them. Each user's sync will start with
+signed in with LDAP and iterate on them. Each user's sync will start with
the following line that contains the user's username and email, as they
exist in GitLab now:
@@ -244,7 +244,7 @@ Syncing user John, email@example.com
```
If you don't find a particular user's GitLab email in the output, then that
-user hasn't logged in with LDAP yet.
+user hasn't signed in with LDAP yet.
Next, GitLab searches its `identities` table for the existing
link between this user and the configured LDAP provider(s):
@@ -313,7 +313,7 @@ things to check to debug the situation.
1. Search for the user
1. Open the user, by clicking on their name. Do not click 'Edit'.
1. Navigate to the **Identities** tab. There should be an LDAP identity with
- an LDAP DN as the 'Identifier'. If not, this user hasn't logged in with
+ an LDAP DN as the 'Identifier'. If not, this user hasn't signed in with
LDAP yet and must do so first.
- You've waited an hour or [the configured
interval](index.md#adjusting-ldap-group-sync-schedule-starter-only) for the group to
@@ -346,7 +346,7 @@ the following are true:
- A [`group_base` is also configured](index.md#group-sync-starter-only).
- The configured `admin_group` in the `gitlab.rb` is a CN, rather than a DN or an array.
- This CN falls under the scope of the configured `group_base`.
-- The members of the `admin_group` have already logged into GitLab with their LDAP
+- The members of the `admin_group` have already signed into GitLab with their LDAP
credentials. GitLab will only grant this admin access to the users whose
accounts are already connected to LDAP.
@@ -357,7 +357,7 @@ GitLab syncs the `admin_group`.
#### Sync all groups **(STARTER ONLY)**
-NOTE: **NOTE:**
+NOTE: **Note:**
To sync all groups manually when debugging is unnecessary, [use the Rake
task](../../raketasks/ldap.md#run-a-group-sync-starter-only) instead.
@@ -571,7 +571,7 @@ If a user account is blocked or unblocked due to the LDAP configuration, a
message will be [logged to `application.log`](../../logs.md#applicationlog).
If there is an unexpected error during an LDAP lookup (configuration error,
-timeout), the login is rejected and a message will be [logged to
+timeout), the sign-in is rejected and a message will be [logged to
`production.log`](../../logs.md#productionlog).
### ldapsearch
@@ -653,7 +653,7 @@ adfind -h ad.example.org:636 -ssl -u "CN=GitLabSRV,CN=Users,DC=GitLab,DC=org" -u
### Rails console
-CAUTION: **CAUTION:**
+CAUTION: **Caution:**
Please note that it is very easy to create, read, modify, and destroy data on the
rails console, so please be sure to run commands exactly as listed.
diff --git a/doc/administration/auth/okta.md b/doc/administration/auth/okta.md
index f7ab60ab56b..78b10aedb77 100644
--- a/doc/administration/auth/okta.md
+++ b/doc/administration/auth/okta.md
@@ -159,8 +159,7 @@ You might want to try this out on an incognito browser window.
>**Note:**
Make sure the groups exist and are assigned to the Okta app.
-You can take a look of the [SAML documentation](../../integration/saml.md#marking-users-as-external-based-on-saml-groups) on external groups since
-it works the same.
+You can take a look of the [SAML documentation](../../integration/saml.md#saml-groups) on configuring groups.
<!-- ## Troubleshooting
diff --git a/doc/administration/auth/smartcard.md b/doc/administration/auth/smartcard.md
index 9ad1e0641f6..80d2efbad84 100644
--- a/doc/administration/auth/smartcard.md
+++ b/doc/administration/auth/smartcard.md
@@ -208,7 +208,7 @@ attribute. As a prerequisite, you must use an LDAP server that:
client_certificate_required_port: 3443
```
- NOTE: **Note**
+ NOTE: **Note:**
Assign a value to at least one of the following variables:
`client_certificate_required_host` or `client_certificate_required_port`.
diff --git a/doc/administration/feature_flags.md b/doc/administration/feature_flags.md
index 9e87eed4508..678ab6c5d7b 100644
--- a/doc/administration/feature_flags.md
+++ b/doc/administration/feature_flags.md
@@ -103,10 +103,28 @@ Some feature flags can be enabled or disabled on a per project basis:
Feature.enable(:<feature flag>, Project.find(<project id>))
```
-For example, to enable the [`:release_evidence_collection`](../ci/junit_test_reports.md#enabling-the-feature) feature flag for project `1234`:
+For example, to enable the [`:junit_pipeline_view`](../ci/junit_test_reports.md#enabling-the-junit-test-reports-feature-core-only) feature flag for project `1234`:
```ruby
-Feature.enable(:release_evidence_collection, Project.find(1234))
+Feature.enable(:junit_pipeline_view, Project.find(1234))
+```
+
+`Feature.enable` and `Feature.disable` always return `nil`, this is not an indication that the command failed:
+
+```ruby
+irb(main):001:0> Feature.enable(:release_evidence_collection)
+=> nil
+```
+
+To check if a flag is enabled or disabled you can use `Feature.enabled?` or `Feature.disabled?`:
+
+```ruby
+Feature.enable(:release_evidence_collection)
+=> nil
+Feature.enabled?(:release_evidence_collection)
+=> true
+Feature.disabled?(:release_evidence_collection)
+=> false
```
When the feature is ready, GitLab will remove the feature flag, the option for
diff --git a/doc/administration/geo/disaster_recovery/bring_primary_back.md b/doc/administration/geo/disaster_recovery/bring_primary_back.md
index b19e55595e7..3b7c7fd549c 100644
--- a/doc/administration/geo/disaster_recovery/bring_primary_back.md
+++ b/doc/administration/geo/disaster_recovery/bring_primary_back.md
@@ -32,13 +32,15 @@ To bring the former **primary** node up to date:
sudo gitlab-ctl start
```
- NOTE: **Note:** If you [disabled the **primary** node permanently](index.md#step-2-permanently-disable-the-primary-node),
+ NOTE: **Note:**
+ If you [disabled the **primary** node permanently](index.md#step-2-permanently-disable-the-primary-node),
you need to undo those steps now. For Debian/Ubuntu you just need to run
`sudo systemctl enable gitlab-runsvdir`. For CentOS 6, you need to install
the GitLab instance from scratch and set it up as a **secondary** node by
following [Setup instructions](../replication/index.md#setup-instructions). In this case, you don't need to follow the next step.
- NOTE: **Note:** If you [changed the DNS records](index.md#step-4-optional-updating-the-primary-domain-dns-record)
+ NOTE: **Note:**
+ If you [changed the DNS records](index.md#step-4-optional-updating-the-primary-domain-dns-record)
for this node during disaster recovery procedure you may need to [block
all the writes to this node](planned_failover.md#prevent-updates-to-the-primary-node)
during this procedure.
diff --git a/doc/administration/geo/disaster_recovery/index.md b/doc/administration/geo/disaster_recovery/index.md
index f690ec63cf9..2d837ebb369 100644
--- a/doc/administration/geo/disaster_recovery/index.md
+++ b/doc/administration/geo/disaster_recovery/index.md
@@ -122,18 +122,31 @@ Note the following when promoting a secondary:
roles ['geo_secondary_role']
```
-1. Promote the **secondary** node to the **primary** node. Execute:
+1. Promote the **secondary** node to the **primary** node.
+
+ Before promoting a secondary node to primary, preflight checks should be run. They can be run separately or along with the promotion script.
+
+ To promote the secondary node to primary along with preflight checks:
```shell
gitlab-ctl promote-to-primary-node
```
- If you have already run the [preflight checks](planned_failover.md#preflight-checks), you can skip them with:
+ CAUTION: **Warning:**
+ Skipping preflight checks will promote the secondary to a primary without any further confirmation!
+
+ If you have already run the [preflight checks](planned_failover.md#preflight-checks) or don't want to run them, you can skip preflight checks with:
```shell
gitlab-ctl promote-to-primary-node --skip-preflight-check
```
+ You can also run preflight checks separately:
+
+ ```shell
+ gitlab-ctl promotion-preflight-checks
+ ```
+
1. Verify you can connect to the newly promoted **primary** node using the URL used
previously for the **secondary** node.
1. If successful, the **secondary** node has now been promoted to the **primary** node.
@@ -261,7 +274,7 @@ secondary domain, like changing Git remotes and API URLs.
external_url 'https://<new_external_url>'
```
- NOTE: **Note**
+ NOTE: **Note:**
Changing `external_url` won't prevent access via the old secondary URL, as
long as the secondary DNS records are still intact.
diff --git a/doc/administration/geo/disaster_recovery/planned_failover.md b/doc/administration/geo/disaster_recovery/planned_failover.md
index 0ce1325a537..ef0e67434d0 100644
--- a/doc/administration/geo/disaster_recovery/planned_failover.md
+++ b/doc/administration/geo/disaster_recovery/planned_failover.md
@@ -45,8 +45,19 @@ be found in `/var/opt/gitlab/gitlab-rails/shared/pages` if using Omnibus).
## Preflight checks
-Follow these steps before scheduling a planned failover to ensure the process
-will go smoothly.
+Run this command to list out all preflight checks and automatically check if replication and verification are complete before scheduling a planned failover to ensure the process will go smoothly:
+
+```shell
+gitlab-ctl promotion-preflight-checks
+```
+
+You can run this command in `force` mode to promote to primary even if preflight checks fail:
+
+```shell
+sudo gitlab-ctl promotion-preflight-checks --force
+```
+
+Each step is described in more detail below.
### Object storage
diff --git a/doc/administration/geo/replication/database.md b/doc/administration/geo/replication/database.md
index 3f2d46ba457..02f51e79907 100644
--- a/doc/administration/geo/replication/database.md
+++ b/doc/administration/geo/replication/database.md
@@ -130,7 +130,8 @@ There is an [issue where support is being discussed](https://gitlab.com/gitlab-o
connect to the **primary** node's database. For this reason, we need the address of
each node.
- NOTE: **Note:** For external PostgreSQL instances, see [additional instructions](external_database.md).
+ NOTE: **Note:**
+ For external PostgreSQL instances, see [additional instructions](external_database.md).
If you are using a cloud provider, you can lookup the addresses for each
Geo node through your cloud provider's management console.
@@ -419,7 +420,8 @@ data before running `pg_basebackup`.
1. Execute the command below to start a backup/restore and begin the replication
- CAUTION: **Warning:** Each Geo **secondary** node must have its own unique replication slot name.
+ CAUTION: **Warning:**
+ Each Geo **secondary** node must have its own unique replication slot name.
Using the same slot name between two secondaries will break PostgreSQL replication.
```shell
diff --git a/doc/administration/geo/replication/datatypes.md b/doc/administration/geo/replication/datatypes.md
index f50da27d82f..5636ff79189 100644
--- a/doc/administration/geo/replication/datatypes.md
+++ b/doc/administration/geo/replication/datatypes.md
@@ -45,6 +45,8 @@ verification methods:
| Blobs | Archived CI build traces _(object storage)_ | Geo with API/Managed (*2*) | _Not implemented_ |
| Blobs | Container registry _(filesystem)_ | Geo with API/Docker API | _Not implemented_ |
| Blobs | Container registry _(object storage)_ | Geo with API/Managed/Docker API (*2*) | _Not implemented_ |
+| Blobs | Package registry _(filesystem)_ | Geo with API | _Not implemented_ |
+| Blobs | Package registry _(object storage)_ | Geo with API/Managed (*2*) | _Not implemented_ |
- (*1*): Redis replication can be used as part of HA with Redis sentinel. It's not used between Geo nodes.
- (*2*): Object storage replication can be performed by Geo or by your object storage provider/appliance
@@ -124,41 +126,41 @@ these epics/issues:
- [Unreplicated Data Types](https://gitlab.com/groups/gitlab-org/-/epics/893)
- [Verify all replicated data](https://gitlab.com/groups/gitlab-org/-/epics/1430)
-DANGER: **DANGER**
+DANGER: **Danger:**
Features not on this list, or with **No** in the **Replicated** column,
are not replicated on the **secondary** node. Failing over without manually
replicating data from those features will cause the data to be **lost**.
If you wish to use those features on a **secondary** node, or to execute a failover
successfully, you must replicate their data using some other means.
-| Feature | Replicated | Verified | Notes |
+| Feature | Replicated (added in GitLab version) | Verified (added in GitLab version) | Notes |
|:---------------------------------------------------------------------|:---------------------------------------------------------|:--------------------------------------------------------|:-----------------------------------------------------------------------------------------------------------|
-| Application data in PostgreSQL | **Yes** | **Yes** | |
-| Project repository | **Yes** | **Yes** | |
-| Project wiki repository | **Yes** | **Yes** | |
-| Project designs repository | **Yes** | [No](https://gitlab.com/gitlab-org/gitlab/-/issues/32467) | |
-| Uploads | **Yes** | [No](https://gitlab.com/groups/gitlab-org/-/epics/1817) | Verified only on transfer, or manually (*1*) |
-| LFS objects | **Yes** | [No](https://gitlab.com/gitlab-org/gitlab/-/issues/8922) | Verified only on transfer, or manually (*1*). Unavailable for new LFS objects in 11.11.x and 12.0.x (*2*). |
-| CI job artifacts (other than traces) | **Yes** | [No](https://gitlab.com/gitlab-org/gitlab/-/issues/8923) | Verified only manually (*1*) |
-| Archived traces | **Yes** | [No](https://gitlab.com/gitlab-org/gitlab/-/issues/8923) | Verified only on transfer, or manually (*1*) |
-| Personal snippets | **Yes** | **Yes** | |
+| Application data in PostgreSQL | **Yes** (10.2) | **Yes** (10.2) | |
+| Project repository | **Yes** (10.2) | **Yes** (10.7) | |
+| Project wiki repository | **Yes** (10.2) | **Yes** (10.7) | |
+| Project designs repository | **Yes** (12.7) | [No](https://gitlab.com/gitlab-org/gitlab/-/issues/32467) | |
+| Uploads | **Yes** (10.2) | [No](https://gitlab.com/groups/gitlab-org/-/epics/1817) | Verified only on transfer, or manually (*1*) |
+| LFS objects | **Yes** (10.2) | [No](https://gitlab.com/gitlab-org/gitlab/-/issues/8922) | Verified only on transfer, or manually (*1*). Unavailable for new LFS objects in 11.11.x and 12.0.x (*2*). |
+| CI job artifacts (other than traces) | **Yes** (10.4) | [No](https://gitlab.com/gitlab-org/gitlab/-/issues/8923) | Verified only manually (*1*) |
+| Archived traces | **Yes** (10.4) | [No](https://gitlab.com/gitlab-org/gitlab/-/issues/8923) | Verified only on transfer, or manually (*1*) |
+| Personal snippets | **Yes** (10.2) | **Yes** (10.2) | |
| [Versioned snippets](../../../user/snippets.md#versioned-snippets) | [No](https://gitlab.com/groups/gitlab-org/-/epics/2809) | [No](https://gitlab.com/groups/gitlab-org/-/epics/2810) | |
-| Project snippets | **Yes** | **Yes** | |
-| Object pools for forked project deduplication | **Yes** | No | |
-| [Server-side Git Hooks](../../custom_hooks.md) | No | No | |
+| Project snippets | **Yes** (10.2) | **Yes** (10.2) | |
+| Object pools for forked project deduplication | **Yes** | No | |
+| [Server-side Git hooks](../../server_hooks.md) | No | No | |
| [Elasticsearch integration](../../../integration/elasticsearch.md) | [No](https://gitlab.com/gitlab-org/gitlab/-/issues/1186) | No | |
| [GitLab Pages](../../pages/index.md) | [No](https://gitlab.com/groups/gitlab-org/-/epics/589) | No | |
-| [Container Registry](../../packages/container_registry.md) | **Yes** | No | |
-| [NPM Registry](../../../user/packages/npm_registry/index.md) | [No](https://gitlab.com/groups/gitlab-org/-/epics/2346) | No | |
-| [Maven Repository](../../../user/packages/maven_repository/index.md) | [No](https://gitlab.com/groups/gitlab-org/-/epics/2346) | No | |
-| [Conan Repository](../../../user/packages/conan_repository/index.md) | [No](https://gitlab.com/groups/gitlab-org/-/epics/2346) | No | |
-| [NuGet Repository](../../../user/packages/nuget_repository/index.md) | [No](https://gitlab.com/groups/gitlab-org/-/epics/2346) | No | |
-| [PyPi Repository](../../../user/packages/pypi_repository/index.md) | [No](https://gitlab.com/groups/gitlab-org/-/epics/2554) | No | |
-| [Composer Repository](../../../user/packages/composer_repository/index.md) | [No](https://gitlab.com/groups/gitlab-org/-/epics/3096) | No | |
+| [Container Registry](../../packages/container_registry.md) | **Yes** (12.3) | No | |
+| [NPM Registry](../../../user/packages/npm_registry/index.md) | **Yes** (13.2) | No | |
+| [Maven Repository](../../../user/packages/maven_repository/index.md) | **Yes** (13.2) | No | |
+| [Conan Repository](../../../user/packages/conan_repository/index.md) | **Yes** (13.2) | No | |
+| [NuGet Repository](../../../user/packages/nuget_repository/index.md) | **Yes** (13.2) | No | |
+| [PyPi Repository](../../../user/packages/pypi_repository/index.md) | **Yes** (13.2) | No | |
+| [Composer Repository](../../../user/packages/composer_repository/index.md) | **Yes** (13.2) | No | |
| [External merge request diffs](../../merge_request_diffs.md) | [No](https://gitlab.com/gitlab-org/gitlab/-/issues/33817) | No | |
| [Terraform State](../../terraform_state.md) | [No](https://gitlab.com/groups/gitlab-org/-/epics/3112)(*3*) | No | |
-| [Vulnerability Export](../../../user/application_security/security_dashboard/#export-vulnerabilities-1) | [No](https://gitlab.com/groups/gitlab-org/-/epics/3111)(*3*) | No | | |
-| Content in object storage | **Yes** | No | |
+| [Vulnerability Export](../../../user/application_security/security_dashboard/#export-vulnerabilities) | [No](https://gitlab.com/groups/gitlab-org/-/epics/3111)(*3*) | No | | |
+| Content in object storage | **Yes** (12.4) | No | |
- (*1*): The integrity can be verified manually using
[Integrity Check Rake Task](../../raketasks/check.md) on both nodes and comparing
diff --git a/doc/administration/geo/replication/disable_geo.md b/doc/administration/geo/replication/disable_geo.md
new file mode 100644
index 00000000000..f53b4c1b796
--- /dev/null
+++ b/doc/administration/geo/replication/disable_geo.md
@@ -0,0 +1,93 @@
+---
+stage: Enablement
+group: Geo
+info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers
+type: howto
+---
+
+# Disabling Geo **(PREMIUM ONLY)**
+
+If you want to revert to a regular Omnibus setup after a test, or you have encountered a Disaster Recovery
+situation and you want to disable Geo momentarily, you can use these instructions to disable your
+Geo setup.
+
+There should be no functional difference between disabling Geo and having an active Geo setup with
+no secondary Geo nodes if you remove them correctly.
+
+To disable Geo, follow these steps:
+
+1. [Remove all secondary Geo nodes](#remove-all-secondary-geo-nodes).
+1. [Remove the primary node from the UI](#remove-the-primary-node-from-the-ui).
+1. [Remove secondary replication slots](#remove-secondary-replication-slots).
+1. [Remove Geo-related configuration](#remove-geo-related-configuration).
+1. [(Optional) Revert PostgreSQL settings to use a password and listen on an IP](#optional-revert-postgresql-settings-to-use-a-password-and-listen-on-an-ip).
+
+## Remove all secondary Geo nodes
+
+To disable Geo, you need to first remove all your secondary Geo nodes, which means replication will not happen
+anymore on these nodes. You can follow our docs to [remove your secondary Geo nodes](./remove_geo_node.md).
+
+If the current node that you want to keep using is a secondary node, you need to first promote it to primary.
+You can use our steps on [how to promote a secondary node](../disaster_recovery/#step-3-promoting-a-secondary-node)
+in order to do that.
+
+## Remove the primary node from the UI
+
+1. Go to **{admin}** **Admin Area >** **{location-dot}** **Geo** (`/admin/geo/nodes`).
+1. Click the **Remove** button for the **primary** node.
+1. Confirm by clicking **Remove** when the prompt appears.
+
+## Remove secondary replication slots
+
+To remove secondary replication slots, run one of the following queries on your primary
+Geo node in a PostgreSQL console (`sudo gitlab-psql`):
+
+- If you already have a PostgreSQL cluster, drop individual replication slots by name to prevent
+ removing your secondary databases from the same cluster. You can use the following to get
+ all names and then drop each individual slot:
+
+ ```sql
+ SELECT slot_name, slot_type, active FROM pg_replication_slots; -- view present replication slots
+ SELECT pg_drop_replication_slot('slot_name'); -- where slot_name is the one expected from above
+ ```
+
+- To remove all secondary replication slots:
+
+ ```sql
+ SELECT pg_drop_replication_slot(slot_name) FROM pg_replication_slots;
+ ```
+
+## Remove Geo-related configuration
+
+1. SSH into your primary Geo node and log in as root:
+
+ ```shell
+ sudo -i
+ ```
+
+1. Edit `/etc/gitlab/gitlab.rb` and remove the Geo related configuration by
+ removing any lines that enabled `geo_primary_role`:
+
+ ```ruby
+ ## In pre-11.5 documentation, the role was enabled as follows. Remove this line.
+ geo_primary_role['enable'] = true
+
+ ## In 11.5+ documentation, the role was enabled as follows. Remove this line.
+ roles ['geo_primary_role']
+ ```
+
+1. After making these changes, [reconfigure GitLab](../../restart_gitlab.md#omnibus-gitlab-reconfigure)
+ for the changes to take effect.
+
+## (Optional) Revert PostgreSQL settings to use a password and listen on an IP
+
+If you want to remove the PostgreSQL-specific settings and revert
+to the defaults (using a socket instead), you can safely remove the following
+lines from the `/etc/gitlab/gitlab.rb` file:
+
+```ruby
+postgresql['sql_user_password'] = '...'
+gitlab_rails['db_password'] = '...'
+postgresql['listen_address'] = '...'
+postgresql['md5_auth_cidr_addresses'] = ['...', '...']
+```
diff --git a/doc/administration/geo/replication/docker_registry.md b/doc/administration/geo/replication/docker_registry.md
index bea6528dc9b..c34732cba67 100644
--- a/doc/administration/geo/replication/docker_registry.md
+++ b/doc/administration/geo/replication/docker_registry.md
@@ -18,7 +18,7 @@ Registry on the **primary** node, you can use the same storage for a **secondary
Docker Registry as well. For more information, read the
[Load balancing considerations](https://docs.docker.com/registry/deploying/#load-balancing-considerations)
when deploying the Registry, and how to set up the storage driver for GitLab's
-integrated [Container Registry](../../packages/container_registry.md#container-registry-storage-driver).
+integrated [Container Registry](../../packages/container_registry.md#use-object-storage).
## Replicating Docker Registry
diff --git a/doc/administration/geo/replication/external_database.md b/doc/administration/geo/replication/external_database.md
index 491b3278ead..e85a82fa414 100644
--- a/doc/administration/geo/replication/external_database.md
+++ b/doc/administration/geo/replication/external_database.md
@@ -270,7 +270,8 @@ the tracking database on port 5432.
query_exec "GRANT USAGE ON FOREIGN SERVER gitlab_secondary TO ${GEO_DB_USER};"
```
- NOTE: **Note:** The script template above uses `gitlab-psql` as it's intended to be executed from the Geo machine,
+ NOTE: **Note:**
+ The script template above uses `gitlab-psql` as it's intended to be executed from the Geo machine,
but you can change it to `psql` and run it from any machine that has access to the database. We also recommend using
`psql` for AWS RDS.
diff --git a/doc/administration/geo/replication/geo_validation_tests.md b/doc/administration/geo/replication/geo_validation_tests.md
index 7b186d15fae..0255e5c9883 100644
--- a/doc/administration/geo/replication/geo_validation_tests.md
+++ b/doc/administration/geo/replication/geo_validation_tests.md
@@ -16,20 +16,53 @@ This section contains a journal of recent validation tests and links to the rele
The following are GitLab upgrade validation tests we performed.
+### July 2020
+
+[Upgrade Geo multi-node installation](https://gitlab.com/gitlab-org/gitlab/-/issues/225359):
+
+- Description: Tested upgrading from GitLab 12.10.12 to 13.0.10 package in a multi-node
+ configuration. As part of the issue to [Fix zero-downtime upgrade process/instructions for multi-node Geo deployments](https://gitlab.com/gitlab-org/gitlab/-/issues/22568), we monitored for downtime using the looping pipeline, HAProxy stats dashboards, and a script to log readiness status on both nodes.
+- Outcome: Partial success because we observed downtime during the upgrade of the primary and secondary sites.
+- Follow up issues/actions:
+ - [Investigate why `reconfigure` and `hup` cause downtime on multi-node Geo deployments](https://gitlab.com/gitlab-org/gitlab/-/issues/228898)
+ - [Geo multi-node deployment upgrade: investigate order when upgrading non-deploy nodes](https://gitlab.com/gitlab-org/gitlab/-/issues/228954)
+
+### June 2020
+
+[Upgrade Geo multi-node installation](https://gitlab.com/gitlab-org/gitlab/-/issues/223284):
+
+- Description: Tested upgrading from GitLab 12.9.10 to 12.10.12 package in a multi-node
+ configuration. Monitored for downtime using the looping pipeline and HAProxy stats dashboards.
+- Outcome: Partial success because we observed downtime during the upgrade of the primary and secondary sites.
+- Follow up issues/actions:
+ - [Fix zero-downtime upgrade process/instructions for multi-node Geo deployments](https://gitlab.com/gitlab-org/gitlab/-/issues/225684)
+ - [Geo:check Rake task: Exclude AuthorizedKeysCommand check if node not running Puma/Unicorn](https://gitlab.com/gitlab-org/gitlab/-/issues/225454)
+ - [Update instructions in the next upgrade issue to include monitoring HAProxy dashboards](https://gitlab.com/gitlab-org/gitlab/-/issues/225359)
+
+[Upgrade Geo multi-node installation](https://gitlab.com/gitlab-org/gitlab/-/issues/208104):
+
+- Description: Tested upgrading from GitLab 12.8.1 to 12.9.10 package in a multi-node
+ configuration.
+- Outcome: Partial success because we did not run the looping pipeline during the demo to validate
+ zero-downtime.
+- Follow up issues:
+ - [Clarify hup Puma/Unicorn should include deploy node](https://gitlab.com/gitlab-org/omnibus-gitlab/-/issues/5460)
+ - [Investigate MR creation failure after upgrade to 12.9.10](https://gitlab.com/gitlab-org/gitlab/-/issues/223282) Closed as false positive.
+
### February 2020
-[Upgrade Geo multi-server installation](https://gitlab.com/gitlab-org/gitlab/-/issues/201837):
+[Upgrade Geo multi-node installation](https://gitlab.com/gitlab-org/gitlab/-/issues/201837):
-- Description: Tested upgrading from GitLab 12.7.5 to the latest GitLab 12.8 package in a multi-server
+- Description: Tested upgrading from GitLab 12.7.5 to the latest GitLab 12.8 package in a multi-node
configuration.
- Outcome: Partial success because we did not run the looping pipeline during the demo to monitor
downtime.
### January 2020
-[Upgrade Geo multi-server installation](https://gitlab.com/gitlab-org/gitlab/-/issues/200085):
+[Upgrade Geo multi-node installation](https://gitlab.com/gitlab-org/gitlab/-/issues/200085):
-- Description: Tested upgrading from GitLab 12.6.x to the latest GitLab 12.7 package in a multi-server
+- Description: Tested upgrading from GitLab 12.6.x to the latest GitLab 12.7 package in a multi-node
configuration.
- Outcome: Upgrade test was successful.
- Follow up issues:
@@ -37,16 +70,16 @@ The following are GitLab upgrade validation tests we performed.
- [Add more logging to Geo end-to-end tests](https://gitlab.com/gitlab-org/gitlab/-/issues/201830).
- [Excess service restarts during zero-downtime upgrade](https://gitlab.com/gitlab-org/omnibus-gitlab/-/issues/5047).
-[Upgrade Geo multi-server installation](https://gitlab.com/gitlab-org/gitlab/-/issues/199836):
+[Upgrade Geo multi-node installation](https://gitlab.com/gitlab-org/gitlab/-/issues/199836):
-- Description: Tested upgrading from GitLab 12.5.7 to GitLab 12.6.6 in a multi-server configuration.
+- Description: Tested upgrading from GitLab 12.5.7 to GitLab 12.6.6 in a multi-node configuration.
- Outcome: Upgrade test was successful.
- Follow up issue:
[Update documentation for zero-downtime upgrades to ensure deploy node it not in use](https://gitlab.com/gitlab-org/omnibus-gitlab/-/issues/5046).
-[Upgrade Geo multi-server installation](https://gitlab.com/gitlab-org/gitlab/-/issues/37044):
+[Upgrade Geo multi-node installation](https://gitlab.com/gitlab-org/gitlab/-/issues/37044):
-- Description: Tested upgrading from GitLab 12.4.x to the latest GitLab 12.5 package in a multi-server
+- Description: Tested upgrading from GitLab 12.4.x to the latest GitLab 12.5 package in a multi-node
configuration.
- Outcome: Upgrade test was successful.
- Follow up issues:
@@ -55,17 +88,17 @@ The following are GitLab upgrade validation tests we performed.
### October 2019
-[Upgrade Geo multi-server installation](https://gitlab.com/gitlab-org/gitlab/-/issues/35262):
+[Upgrade Geo multi-node installation](https://gitlab.com/gitlab-org/gitlab/-/issues/35262):
-- Description: Tested upgrading from GitLab 12.3.5 to GitLab 12.4.1 in a multi-server configuration.
+- Description: Tested upgrading from GitLab 12.3.5 to GitLab 12.4.1 in a multi-node configuration.
- Outcome: Upgrade test was successful.
-[Upgrade Geo multi-server installation](https://gitlab.com/gitlab-org/gitlab/-/issues/32437):
+[Upgrade Geo multi-node installation](https://gitlab.com/gitlab-org/gitlab/-/issues/32437):
- Description: Tested upgrading from GitLab 12.2.8 to GitLab 12.3.5.
- Outcome: Upgrade test was successful.
-[Upgrade Geo multi-server installation](https://gitlab.com/gitlab-org/gitlab/-/issues/32435):
+[Upgrade Geo multi-node installation](https://gitlab.com/gitlab-org/gitlab/-/issues/32435):
- Description: Tested upgrading from GitLab 12.1.9 to GitLab 12.2.8.
- Outcome: Partial success due to possible misconfiguration issues.
@@ -80,7 +113,7 @@ The following are PostgreSQL upgrade validation tests we performed.
- Description: Prior to making PostgreSQL 11 the default version of PostgreSQL in GitLab 12.10, we
tested upgrading to PostgreSQL 11 in Geo deployments on GitLab 12.9.
-- Outcome: Partially successful. Issues were discovered in multi-server configurations with a separate
+- Outcome: Partially successful. Issues were discovered in multi-node configurations with a separate
tracking database and concerns were raised about allowing automatic upgrades when Geo enabled.
- Follow up issues:
- [`replicate-geo-database` incorrectly tries to back up repositories](https://gitlab.com/gitlab-org/omnibus-gitlab/-/issues/5241).
@@ -102,6 +135,6 @@ The following are PostgreSQL upgrade validation tests we performed.
various upgrade scenarios from GitLab 11.11.5 through to GitLab 12.1.8.
- Outcome: Multiple issues were found when upgrading and addressed in follow-up issues.
- Follow up issues:
- - [`gitlab-ctl` reconfigure fails on Redis node in multi-server Geo setup](https://gitlab.com/gitlab-org/omnibus-gitlab/-/issues/4706).
- - [Geo multi-server upgrade from 12.0.9 to 12.1.9 does not upgrade PostgreSQL](https://gitlab.com/gitlab-org/omnibus-gitlab/-/issues/4705).
- - [Refresh foreign tables fails on app server in multi-server setup after upgrade to 12.1.9](https://gitlab.com/gitlab-org/gitlab/-/issues/32119).
+ - [`gitlab-ctl` reconfigure fails on Redis node in multi-node Geo setup](https://gitlab.com/gitlab-org/omnibus-gitlab/-/issues/4706).
+ - [Geo multi-node upgrade from 12.0.9 to 12.1.9 does not upgrade PostgreSQL](https://gitlab.com/gitlab-org/omnibus-gitlab/-/issues/4705).
+ - [Refresh foreign tables fails on app server in multi-node setup after upgrade to 12.1.9](https://gitlab.com/gitlab-org/gitlab/-/issues/32119).
diff --git a/doc/administration/geo/replication/index.md b/doc/administration/geo/replication/index.md
index 5b4b476bfa8..51c831abaf3 100644
--- a/doc/administration/geo/replication/index.md
+++ b/doc/administration/geo/replication/index.md
@@ -9,7 +9,7 @@ type: howto
> - Introduced in GitLab Enterprise Edition 8.9.
> - Using Geo in combination with
-> [multi-server architectures](../../reference_architectures/index.md)
+> [multi-node architectures](../../reference_architectures/index.md)
> is considered **Generally Available** (GA) in
> [GitLab Premium](https://about.gitlab.com/pricing/) 10.4.
@@ -213,9 +213,29 @@ For information on configuring Geo, see [Geo configuration](configuration.md).
For information on how to update your Geo nodes to the latest GitLab version, see [Updating the Geo nodes](updating_the_geo_nodes.md).
-### Configuring Geo for multiple servers
+### Pausing and resuming replication
-For information on configuring Geo for multiple servers, see [Geo for multiple servers](multiple_servers.md).
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/35913) in [GitLab Premium](https://about.gitlab.com/pricing/) 13.2.
+
+In some circumstances, like during [upgrades](updating_the_geo_nodes.md) or a [planned failover](../disaster_recovery/planned_failover.md), it is desirable to pause replication between the primary and secondary.
+
+Pausing and resuming replication is done via a command line tool from the secondary node.
+
+**To Pause: (from secondary)**
+
+```shell
+gitlab-ctl geo-replication-pause
+```
+
+**To Resume: (from secondary)**
+
+```shell
+gitlab-ctl geo-replication-resume
+```
+
+### Configuring Geo for multiple nodes
+
+For information on configuring Geo for multiple nodes, see [Geo for multiple servers](multiple_servers.md).
### Configuring Geo with Object Storage
@@ -245,6 +265,10 @@ For an example of how to set up a location-aware Git remote URL with AWS Route53
For more information on removing a Geo node, see [Removing **secondary** Geo nodes](remove_geo_node.md).
+## Disable Geo
+
+To find out how to disable Geo, see [Disabling Geo](disable_geo.md).
+
## Current limitations
CAUTION: **Caution:**
diff --git a/doc/administration/geo/replication/location_aware_git_url.md b/doc/administration/geo/replication/location_aware_git_url.md
index 49c83ee1718..8b086e3ff5f 100644
--- a/doc/administration/geo/replication/location_aware_git_url.md
+++ b/doc/administration/geo/replication/location_aware_git_url.md
@@ -18,7 +18,7 @@ Though these instructions use [AWS Route53](https://aws.amazon.com/route53/),
other services such as [Cloudflare](https://www.cloudflare.com/) could be used
as well.
-NOTE: **Note**
+NOTE: **Note:**
You can also use a load balancer to distribute web UI or API traffic to
[multiple Geo **secondary** nodes](../../../user/admin_area/geo_nodes.md#multiple-secondary-nodes-behind-a-load-balancer).
Importantly, the **primary** node cannot yet be included. See the feature request
diff --git a/doc/administration/geo/replication/multiple_servers.md b/doc/administration/geo/replication/multiple_servers.md
index 2747aaeb105..d8f04e07fb0 100644
--- a/doc/administration/geo/replication/multiple_servers.md
+++ b/doc/administration/geo/replication/multiple_servers.md
@@ -5,15 +5,15 @@ info: To determine the technical writer assigned to the Stage/Group associated w
type: howto
---
-# Geo for multiple servers **(PREMIUM ONLY)**
+# Geo for multiple nodes **(PREMIUM ONLY)**
This document describes a minimal reference architecture for running Geo
-in a multi-server configuration. If your multi-server setup differs from the one
+in a multi-node configuration. If your multi-node setup differs from the one
described, it is possible to adapt these instructions to your needs.
## Architecture overview
-![Geo multi-server diagram](../../high_availability/img/geo-ha-diagram.png)
+![Geo multi-node diagram](../../high_availability/img/geo-ha-diagram.png)
_[diagram source - GitLab employees only](https://docs.google.com/drawings/d/1z0VlizKiLNXVVVaERFwgsIOuEgjcUqDTWPdQYsE7Z4c/edit)_
@@ -30,36 +30,36 @@ The only external way to access the two Geo deployments is by HTTPS at
NOTE: **Note:**
The **primary** and **secondary** Geo deployments must be able to communicate to each other over HTTPS.
-## Redis and PostgreSQL for multiple servers
+## Redis and PostgreSQL for multiple nodes
Geo supports:
-- Redis and PostgreSQL on the **primary** node configured for multiple servers.
-- Redis on **secondary** nodes configured for multiple servers.
+- Redis and PostgreSQL on the **primary** node configured for multiple nodes.
+- Redis on **secondary** nodes configured for multiple nodes.
NOTE: **Note:**
-Support for PostgreSQL on **secondary** nodes in multi-server configuration
+Support for PostgreSQL on **secondary** nodes in multi-node configuration
[is planned](https://gitlab.com/groups/gitlab-org/-/epics/2536).
Because of the additional complexity involved in setting up this configuration
-for PostgreSQL and Redis, it is not covered by this Geo multi-server documentation.
+for PostgreSQL and Redis, it is not covered by this Geo multi-node documentation.
-For more information about setting up a multi-server PostgreSQL cluster and Redis cluster using the omnibus package see the multi-server documentation for
+For more information about setting up a multi-node PostgreSQL cluster and Redis cluster using the omnibus package see the multi-node documentation for
[PostgreSQL](../../postgresql/replication_and_failover.md) and
-[Redis](../../high_availability/redis.md), respectively.
+[Redis](../../redis/replication_and_failover.md), respectively.
NOTE: **Note:**
It is possible to use cloud hosted services for PostgreSQL and Redis, but this is beyond the scope of this document.
-## Prerequisites: Two working GitLab multi-server clusters
+## Prerequisites: Two working GitLab multi-node clusters
One cluster will serve as the **primary** node. Use the
-[GitLab multi-server documentation](../../reference_architectures/index.md) to set this up. If
+[GitLab multi-node documentation](../../reference_architectures/index.md) to set this up. If
you already have a working GitLab instance that is in-use, it can be used as a
**primary**.
The second cluster will serve as the **secondary** node. Again, use the
-[GitLab multi-server documentation](../../reference_architectures/index.md) to set this up.
+[GitLab multi-node documentation](../../reference_architectures/index.md) to set this up.
It's a good idea to log in and test it, however, note that its data will be
wiped out as part of the process of replicating from the **primary**.
@@ -90,12 +90,13 @@ The following steps enable a GitLab cluster to serve as the **primary** node.
After making these changes, [reconfigure GitLab](../../restart_gitlab.md#omnibus-gitlab-reconfigure) so the changes take effect.
-NOTE: **Note:** PostgreSQL and Redis should have already been disabled on the
+NOTE: **Note:**
+PostgreSQL and Redis should have already been disabled on the
application servers, and connections from the application servers to those
-services on the backend servers configured, during normal GitLab multi-server set up. See
-multi-server configuration documentation for
+services on the backend servers configured, during normal GitLab multi-node set up. See
+multi-node configuration documentation for
[PostgreSQL](../../postgresql/replication_and_failover.md#configuring-the-application-nodes)
-and [Redis](../../high_availability/redis.md#example-configuration-for-the-gitlab-application).
+and [Redis](../../redis/replication_and_failover.md#example-configuration-for-the-gitlab-application).
### Step 2: Configure the **primary** database
@@ -110,7 +111,7 @@ and [Redis](../../high_availability/redis.md#example-configuration-for-the-gitla
## Configure a **secondary** node
-A **secondary** cluster is similar to any other GitLab multi-server cluster, with two
+A **secondary** cluster is similar to any other GitLab multi-node cluster, with two
major differences:
- The main PostgreSQL database is a read-only replica of the **primary** node's
@@ -119,8 +120,8 @@ major differences:
called the "tracking database", which tracks the synchronization state of
various resources.
-Therefore, we will set up the multi-server components one-by-one, and include deviations
-from the normal multi-server setup. However, we highly recommend first configuring a
+Therefore, we will set up the multi-node components one-by-one, and include deviations
+from the normal multi-node setup. However, we highly recommend first configuring a
brand-new cluster as if it were not part of a Geo setup so that it can be
tested and verified as a working cluster. And only then should it be modified
for use as a Geo **secondary**. This helps to separate problems that are related
@@ -128,10 +129,10 @@ and are not related to Geo setup.
### Step 1: Configure the Redis and Gitaly services on the **secondary** node
-Configure the following services, again using the non-Geo multi-server
+Configure the following services, again using the non-Geo multi-node
documentation:
-- [Configuring Redis for GitLab](../../high_availability/redis.md) for multiple servers.
+- [Configuring Redis for GitLab](../../redis/replication_and_failover.md#example-configuration-for-the-gitlab-application) for multiple nodes.
- [Gitaly](../../high_availability/gitaly.md), which will store data that is
synchronized from the **primary** node.
@@ -141,8 +142,9 @@ recommended.
### Step 2: Configure the main read-only replica PostgreSQL database on the **secondary** node
-NOTE: **Note:** The following documentation assumes the database will be run on
-a single node only. Multi-server PostgreSQL on **secondary** nodes is
+NOTE: **Note:**
+The following documentation assumes the database will be run on
+a single node only. Multi-node PostgreSQL on **secondary** nodes is
[not currently supported](https://gitlab.com/groups/gitlab-org/-/epics/2536).
Configure the [**secondary** database](database.md) as a read-only replica of
@@ -206,7 +208,8 @@ If using an external PostgreSQL instance, refer also to
### Step 3: Configure the tracking database on the **secondary** node
-NOTE: **Note:** This documentation assumes the tracking database will be run on
+NOTE: **Note:**
+This documentation assumes the tracking database will be run on
only a single machine, rather than as a PostgreSQL cluster.
Configure the tracking database.
@@ -282,7 +285,7 @@ application services. These services are enabled selectively in the
configuration.
Configure the application servers following
-[Configuring GitLab for multiple servers](../../high_availability/gitlab.md), then make the
+[Configuring GitLab for multiple nodes](../../high_availability/gitlab.md), then make the
following modifications:
1. Edit `/etc/gitlab/gitlab.rb` on each application server in the **secondary**
@@ -370,13 +373,13 @@ application servers.
In this topology, a load balancer is required at each geographic location to
route traffic to the application servers.
-See [Load Balancer for GitLab with multiple servers](../../high_availability/load_balancer.md) for
+See [Load Balancer for GitLab with multiple nodes](../../high_availability/load_balancer.md) for
more information.
### Step 6: Configure the backend application servers on the **secondary** node
The minimal reference architecture diagram above shows all application services
-running together on the same machines. However, for multiple servers we
+running together on the same machines. However, for multiple nodes we
[strongly recommend running all services separately](../../reference_architectures/index.md).
For example, a Sidekiq server could be configured similarly to the frontend
diff --git a/doc/administration/geo/replication/troubleshooting.md b/doc/administration/geo/replication/troubleshooting.md
index b03a2dae971..c2f8aa35d2d 100644
--- a/doc/administration/geo/replication/troubleshooting.md
+++ b/doc/administration/geo/replication/troubleshooting.md
@@ -5,7 +5,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w
type: howto
---
-# Geo Troubleshooting **(PREMIUM ONLY)**
+# Troubleshooting Geo **(PREMIUM ONLY)**
Setting up Geo requires careful attention to details and sometimes it's easy to
miss a step.
@@ -452,13 +452,13 @@ to start again from scratch, there are a few steps that can help you:
chown git:git /var/opt/gitlab/git-data/repositories
```
- TIP: **Tip**
+ TIP: **Tip:**
You may want to remove the `/var/opt/gitlab/git-data/repositories.old` in the future
as soon as you confirmed that you don't need it anymore, to save disk space.
1. _(Optional)_ Rename other data folders and create new ones
- CAUTION: **Caution**:
+ CAUTION: **Caution:**
You may still have files on the **secondary** node that have been removed from **primary** node but
removal have not been reflected. If you skip this step, they will never be removed
from this Geo node.
@@ -701,7 +701,8 @@ To check the configuration:
Description |
```
- NOTE: **Note:** Pay particular attention to the host and port under
+ NOTE: **Note:**
+ Pay particular attention to the host and port under
FDW options. That configuration should point to the Geo secondary
database.
diff --git a/doc/administration/geo/replication/updating_the_geo_nodes.md b/doc/administration/geo/replication/updating_the_geo_nodes.md
index 6c2778ad0fe..c0b1bc6688c 100644
--- a/doc/administration/geo/replication/updating_the_geo_nodes.md
+++ b/doc/administration/geo/replication/updating_the_geo_nodes.md
@@ -36,15 +36,18 @@ different steps.
## General update steps
-NOTE: **Note:** These general update steps are not intended for [high-availability deployments](https://docs.gitlab.com/omnibus/update/README.html#multi-node--ha-deployment), and will cause downtime. If you want to avoid downtime, consider using [zero downtime updates](https://docs.gitlab.com/omnibus/update/README.html#zero-downtime-updates).
+NOTE: **Note:**
+These general update steps are not intended for [high-availability deployments](https://docs.gitlab.com/omnibus/update/README.html#multi-node--ha-deployment), and will cause downtime. If you want to avoid downtime, consider using [zero downtime updates](https://docs.gitlab.com/omnibus/update/README.html#zero-downtime-updates).
To update the Geo nodes when a new GitLab version is released, update **primary**
and all **secondary** nodes:
+1. **Optional:** [Pause replication on each **secondary** node.](./index.md#pausing-and-resuming-replication)
1. Log into the **primary** node.
1. [Update GitLab on the **primary** node using Omnibus](https://docs.gitlab.com/omnibus/update/README.html).
1. Log into each **secondary** node.
1. [Update GitLab on each **secondary** node using Omnibus](https://docs.gitlab.com/omnibus/update/README.html).
+1. If you paused replication in step 1, [resume replication on each **secondary**](./index.md#pausing-and-resuming-replication)
1. [Test](#check-status-after-updating) **primary** and **secondary** nodes, and check version in each.
### Check status after updating
diff --git a/doc/administration/git_annex.md b/doc/administration/git_annex.md
index c4c38f8e683..ed6218ede91 100644
--- a/doc/administration/git_annex.md
+++ b/doc/administration/git_annex.md
@@ -4,9 +4,9 @@ disqus_identifier: 'https://docs.gitlab.com/ee/workflow/git_annex.html'
# Git annex
-> **Warning:** GitLab has [completely
-removed](https://gitlab.com/gitlab-org/gitlab/-/issues/1648) in GitLab 9.0 (2017/03/22).
-Read through the [migration guide from git-annex to Git LFS](../topics/git/lfs/migrate_from_git_annex_to_git_lfs.md).
+CAUTION: **Warning:**
+[Git Annex support was removed](https://gitlab.com/gitlab-org/gitlab/-/issues/1648)
+in GitLab 9.0. Read through the [migration guide from git-annex to Git LFS](../topics/git/lfs/migrate_from_git_annex_to_git_lfs.md).
The biggest limitation of Git, compared to some older centralized version
control systems has been the maximum size of the repositories.
@@ -198,7 +198,7 @@ can cause `git-annex` to raise unpredicted warnings and errors.
Consult the [Annex upgrade page](https://git-annex.branchable.com/upgrades/) for more information about
the differences between versions. You can find out which version is installed
-on your server by navigating to <https://pkgs.org/download/git-annex> and
+on your server by navigating to `https://pkgs.org/download/git-annex` and
searching for your distribution.
Although there is no general guide for `git-annex` errors, there are a few tips
diff --git a/doc/administration/gitaly/img/praefect_storage_v12_10.png b/doc/administration/gitaly/img/praefect_storage_v12_10.png
deleted file mode 100644
index f60a56fa1fb..00000000000
--- a/doc/administration/gitaly/img/praefect_storage_v12_10.png
+++ /dev/null
Binary files differ
diff --git a/doc/administration/gitaly/index.md b/doc/administration/gitaly/index.md
index 1469ed64004..057d0559c14 100644
--- a/doc/administration/gitaly/index.md
+++ b/doc/administration/gitaly/index.md
@@ -19,12 +19,13 @@ In the Gitaly documentation:
- [GitLab Shell](https://gitlab.com/gitlab-org/gitlab-shell).
- [GitLab Workhorse](https://gitlab.com/gitlab-org/gitlab-workhorse).
-GitLab end users do not have direct access to Gitaly.
+GitLab end users do not have direct access to Gitaly. Gitaly only manages Git
+repository access for GitLab. Other types of GitLab data aren't accessed using Gitaly.
CAUTION: **Caution:**
-From GitLab 13.0, using NFS for Git repositories is deprecated. In GitLab 14.0,
-support for NFS for Git repositories is scheduled to be removed. Upgrade to
-[Gitaly Cluster](praefect.md) as soon as possible.
+From GitLab 13.0, Gitaly support for NFS is deprecated. In GitLab 14.0, Gitaly support
+for NFS is scheduled to be removed. Upgrade to [Gitaly Cluster](praefect.md) as soon as
+possible.
## Architecture
@@ -49,6 +50,12 @@ To change Gitaly settings:
1. Edit `/home/git/gitaly/config.toml` and add or change the [Gitaly settings](https://gitlab.com/gitlab-org/gitaly/blob/master/config.toml.example).
1. Save the file and [restart GitLab](../restart_gitlab.md#installations-from-source).
+The following configuration options are also available:
+
+- Enabling [TLS support](#enable-tls-support).
+- Configuring the [number of `gitaly-ruby` workers](#configure-number-of-gitaly-ruby-workers).
+- Limiting [RPC concurrency](#limit-rpc-concurrency).
+
## Run Gitaly on its own server
By default, Gitaly is run on the same server as Gitaly clients and is
@@ -72,6 +79,7 @@ The process for setting up Gitaly on its own server is:
1. [Configure authentication](#configure-authentication).
1. [Configure Gitaly servers](#configure-gitaly-servers).
1. [Configure Gitaly clients](#configure-gitaly-clients).
+1. [Disable Gitaly where not required](#disable-gitaly-where-not-required-optional) (optional).
When running Gitaly on its own server, note the following regarding GitLab versions:
@@ -116,7 +124,7 @@ The following list depicts the network architecture of Gitaly:
DANGER: **Danger:**
Gitaly servers must not be exposed to the public internet as Gitaly's network traffic is unencrypted
by default. The use of firewall is highly recommended to restrict access to the Gitaly server.
-Another option is to [use TLS](#tls-support).
+Another option is to [use TLS](#enable-tls-support).
In the following sections, we describe how to configure two Gitaly servers with secret token
`abc123secret`:
@@ -380,20 +388,10 @@ Gitaly makes the following assumptions:
clients, and that Gitaly server can read and write to `/mnt/gitlab/storage2`.
- Your `gitaly1.internal` and `gitaly2.internal` Gitaly servers can reach each other.
-Note you can't a use mixed setup, with at least one of your Gitaly servers configured as a local
-server with the `path` setting provided. This is because other Gitaly instances can't communicate
-with it. The following setup is _incorrect_, because:
-
-- You must replace `path` with `gitaly_address` containing a proper value.
-- The address must be reachable from the other two addresses provided.
-
-```ruby
-git_data_dirs({
- 'default' => { 'gitaly_address' => 'tcp://gitaly1.internal:8075' },
- 'storage1' => { 'path' => '/var/opt/gitlab/git-data' },
- 'storage2' => { 'gitaly_address' => 'tcp://gitaly2.internal:8075' },
-})
-```
+You can't define Gitaly servers with some as a local Gitaly server
+(without `gitaly_address`) and some as remote
+server (with `gitaly_address`) unless you setup with special
+[mixed configuration](#mixed-configuration).
**For Omnibus GitLab**
@@ -457,15 +455,47 @@ If you have [server hooks](../server_hooks.md) configured, either per repository
must move these to the Gitaly servers. If you have multiple Gitaly servers, copy your server hooks
to all Gitaly servers.
-### Disabling the Gitaly service in a cluster environment
+#### Mixed configuration
+
+GitLab can reside on the same server as one of many Gitaly servers, but doesn't support
+configuration that mixes local and remote configuration. The following setup is incorrect, because:
+
+- All addresses must be reachable from the other Gitaly servers.
+- `storage1` will be assigned a Unix socket for `gitaly_address` which is
+ invalid for some of the Gitaly servers.
+
+```ruby
+git_data_dirs({
+ 'default' => { 'gitaly_address' => 'tcp://gitaly1.internal:8075' },
+ 'storage1' => { 'path' => '/mnt/gitlab/git-data' },
+ 'storage2' => { 'gitaly_address' => 'tcp://gitaly2.internal:8075' },
+})
+```
+
+To combine local and remote Gitaly servers, use an external address for the local Gitaly server. For
+example:
+
+```ruby
+git_data_dirs({
+ 'default' => { 'gitaly_address' => 'tcp://gitaly1.internal:8075' },
+ # Address of the GitLab server that has Gitaly running on it
+ 'storage1' => { 'gitaly_address' => 'tcp://gitlab.internal:8075', 'path' => '/mnt/gitlab/git-data' },
+ 'storage2' => { 'gitaly_address' => 'tcp://gitaly2.internal:8075' },
+})
+```
+
+`path` can only be included for storage shards on the local Gitaly server.
+If it's excluded, default Git storage directory will be used for that storage shard.
-If you are running Gitaly [as a remote
-service](#run-gitaly-on-its-own-server) you may want to disable
-the local Gitaly service that runs on your GitLab server by default.
-Disabling Gitaly only makes sense when you run GitLab in a custom
-cluster configuration, where different services run on different
-machines. Disabling Gitaly on all machines in the cluster is not a
-valid configuration.
+### Disable Gitaly where not required (optional)
+
+If you are running Gitaly [as a remote service](#run-gitaly-on-its-own-server) you may want to
+disable the local Gitaly service that runs on your GitLab server by default, leaving it only running
+where required.
+
+Disabling Gitaly on the GitLab instance only makes sense when you run GitLab in a custom cluster configuration, where
+Gitaly runs on a separate machine from the GitLab instance. Disabling Gitaly on all machines in the cluster is not
+a valid configuration (some machines much act as Gitaly servers).
To disable Gitaly on a GitLab server:
@@ -489,43 +519,44 @@ To disable Gitaly on a GitLab server:
1. Save the file and [restart GitLab](../restart_gitlab.md#installations-from-source).
-## TLS support
+## Enable TLS support
> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/22602) in GitLab 11.8.
-Gitaly supports TLS encryption. To be able to communicate
-with a Gitaly instance that listens for secure connections you will need to use `tls://` URL
-scheme in the `gitaly_address` of the corresponding storage entry in the GitLab configuration.
+Gitaly supports TLS encryption. To communicate with a Gitaly instance that listens for secure
+connections, you must use `tls://` URL scheme in the `gitaly_address` of the corresponding
+storage entry in the GitLab configuration.
-You will need to bring your own certificates as this isn't provided automatically.
-The certificate corresponding to each Gitaly server will need to be installed
-on that Gitaly server.
+You must supply your own certificates as this isn't provided automatically. The certificate
+corresponding to each Gitaly server must be installed on that Gitaly server.
-Additionally the certificate, or its certificate authority, must be installed on all Gitaly servers
-(including the Gitaly server using the certificate) and on all Gitaly clients
-that communicate with it following the procedure described in
-[GitLab custom certificate configuration](https://docs.gitlab.com/omnibus/settings/ssl.html#install-custom-public-certificates) (and repeated below).
+Additionally, the certificate (or its certificate authority) must be installed on all:
-NOTE: **Note**
-The certificate must specify the address you use to access the
-Gitaly server. If you are addressing the Gitaly server by a hostname, you can
-either use the Common Name field for this, or add it as a Subject Alternative
-Name. If you are addressing the Gitaly server by its IP address, you must add it
-as a Subject Alternative Name to the certificate.
-[gRPC does not support using an IP address as Common Name in a certificate](https://github.com/grpc/grpc/issues/2691).
+- Gitaly servers, including the Gitaly server using the certificate.
+- Gitaly clients that communicate with it.
-NOTE: **Note:**
-It is possible to configure Gitaly servers with both an
-unencrypted listening address `listen_addr` and an encrypted listening
-address `tls_listen_addr` at the same time. This allows you to do a
-gradual transition from unencrypted to encrypted traffic, if necessary.
+The process is documented in the
+[GitLab custom certificate configuration](https://docs.gitlab.com/omnibus/settings/ssl.html#install-custom-public-certificates)
+and repeated below.
+
+Note the following:
+
+- The certificate must specify the address you use to access the Gitaly server. If you are:
+ - Addressing the Gitaly server by a hostname, you can either use the Common Name field for this,
+ or add it as a Subject Alternative Name.
+ - Addressing the Gitaly server by its IP address, you must add it as a Subject Alternative Name to
+ the certificate. [gRPC does not support using an IP address as Common Name in a certificate](https://github.com/grpc/grpc/issues/2691).
+- You can configure Gitaly servers with both an unencrypted listening address `listen_addr` and an
+ encrypted listening address `tls_listen_addr` at the same time. This allows you to gradually
+ transition from unencrypted to encrypted traffic if necessary.
To configure Gitaly with TLS:
**For Omnibus GitLab**
1. Create certificates for Gitaly servers.
-1. On the Gitaly clients, copy the certificates, or their certificate authority, into the `/etc/gitlab/trusted-certs`:
+1. On the Gitaly clients, copy the certificates (or their certificate authority) into
+ `/etc/gitlab/trusted-certs`:
```shell
sudo cp cert.pem /etc/gitlab/trusted-certs/
@@ -542,7 +573,8 @@ To configure Gitaly with TLS:
```
1. Save the file and [reconfigure GitLab](../restart_gitlab.md#omnibus-gitlab-reconfigure).
-1. On the Gitaly servers, create the `/etc/gitlab/ssl` directory and copy your key and certificate there:
+1. On the Gitaly servers, create the `/etc/gitlab/ssl` directory and copy your key and certificate
+ there:
```shell
sudo mkdir -p /etc/gitlab/ssl
@@ -551,8 +583,9 @@ To configure Gitaly with TLS:
sudo chmod 644 key.pem cert.pem
```
-1. Copy all Gitaly server certificates, or their certificate authority, to `/etc/gitlab/trusted-certs` so Gitaly server will trust the certificate when
-calling into itself or other Gitaly servers:
+1. Copy all Gitaly server certificates (or their certificate authority) to
+ `/etc/gitlab/trusted-certs` so that Gitaly servers will trust the certificate when calling into themselves
+ or other Gitaly servers:
```shell
sudo cp cert1.pem cert2.pem /etc/gitlab/trusted-certs/
@@ -572,10 +605,13 @@ calling into itself or other Gitaly servers:
```
1. Save the file and [reconfigure GitLab](../restart_gitlab.md#omnibus-gitlab-reconfigure).
-1. (Optional) After [verifying that all Gitaly traffic is being served over TLS](#observe-type-of-gitaly-connections),
- you can improve security by disabling non-TLS connections by commenting out
- or deleting `gitaly['listen_addr']` in `/etc/gitlab/gitlab.rb`, saving the file,
- and [reconfiguring GitLab](../restart_gitlab.md#omnibus-gitlab-reconfigure).
+1. Verify Gitaly traffic is being served over TLS by
+ [observing the types of Gitaly connections](#observe-type-of-gitaly-connections).
+1. (Optional) Improve security by:
+ 1. Disabling non-TLS connections by commenting out or deleting `gitaly['listen_addr']` in
+ `/etc/gitlab/gitlab.rb`.
+ 1. Saving the file.
+ 1. [Reconfiguring GitLab](../restart_gitlab.md#omnibus-gitlab-reconfigure).
**For installations from source**
@@ -605,9 +641,9 @@ calling into itself or other Gitaly servers:
```
NOTE: **Note:**
- `/some/dummy/path` should be set to a local folder that exists, however no
- data will be stored in this folder. This will no longer be necessary after
- [this issue](https://gitlab.com/gitlab-org/gitaly/-/issues/1282) is resolved.
+ `/some/dummy/path` should be set to a local folder that exists, however no data will be stored
+ in this folder. This will no longer be necessary after
+ [Gitaly issue #1282](https://gitlab.com/gitlab-org/gitaly/-/issues/1282) is resolved.
1. Save the file and [restart GitLab](../restart_gitlab.md#installations-from-source).
1. On the Gitaly servers, create or edit `/etc/default/gitlab` and add:
@@ -625,7 +661,9 @@ calling into itself or other Gitaly servers:
sudo chmod 644 key.pem cert.pem
```
-1. Copy all Gitaly server certificates, or their certificate authority, to the system trusted certificates so Gitaly server will trust the certificate when calling into itself or other Gitaly servers.
+1. Copy all Gitaly server certificates (or their certificate authority) to the system trusted
+ certificates folder so Gitaly server will trust the certificate when calling into itself or other Gitaly
+ servers.
```shell
sudo cp cert.pem /usr/local/share/ca-certificates/gitaly.crt
@@ -643,15 +681,18 @@ calling into itself or other Gitaly servers:
```
1. Save the file and [restart GitLab](../restart_gitlab.md#installations-from-source).
-1. (Optional) After [verifying that all Gitaly traffic is being served over TLS](#observe-type-of-gitaly-connections),
- you can improve security by disabling non-TLS connections by commenting out
- or deleting `listen_addr` in `/home/git/gitaly/config.toml`, saving the file,
- and [restarting GitLab](../restart_gitlab.md#installations-from-source).
+1. Verify Gitaly traffic is being served over TLS by
+ [observing the types of Gitaly connections](#observe-type-of-gitaly-connections).
+1. (Optional) Improve security by:
+ 1. Disabling non-TLS connections by commenting out or deleting `listen_addr` in
+ `/home/git/gitaly/config.toml`.
+ 1. Saving the file.
+ 1. [Restarting GitLab](../restart_gitlab.md#installations-from-source).
### Observe type of Gitaly connections
-To observe what type of connections are actually being used in a
-production environment you can use the following Prometheus query:
+[Prometheus](../monitoring/prometheus/index.md) can be used observe what type of connections Gitaly
+is serving a production environment. Use the following Prometheus query:
```prometheus
sum(rate(gitaly_connections_total[5m])) by (type)
@@ -660,24 +701,26 @@ sum(rate(gitaly_connections_total[5m])) by (type)
## `gitaly-ruby`
Gitaly was developed to replace the Ruby application code in GitLab.
-In order to save time and/or avoid the risk of rewriting existing
-application logic, in some cases we chose to copy some application code
-from GitLab into Gitaly almost as-is. To be able to run that code,
-`gitaly-ruby` was created, which is a "sidecar" process for the main Gitaly Go
-process. Some examples of things that are implemented in `gitaly-ruby` are
-RPCs that deal with wikis, and RPCs that create commits on behalf of
-a user, such as merge commits.
-### Number of `gitaly-ruby` workers
+To save time and avoid the risk of rewriting existing application logic, we chose to copy some
+application code from GitLab into Gitaly.
+
+To be able to run that code, `gitaly-ruby` was created, which is a "sidecar" process for the main
+Gitaly Go process. Some examples of things that are implemented in `gitaly-ruby` are:
+
+- RPCs that deal with wikis.
+- RPCs that create commits on behalf of a user, such as merge commits.
+
+### Configure number of `gitaly-ruby` workers
-`gitaly-ruby` has much less capacity than Gitaly itself. If your Gitaly
-server has to handle a lot of requests, the default setting of having
-just one active `gitaly-ruby` sidecar might not be enough. If you see
-`ResourceExhausted` errors from Gitaly, it's very likely that you have not
-enough `gitaly-ruby` capacity.
+`gitaly-ruby` has much less capacity than Gitaly implemented in Go. If your Gitaly server has to handle lots of
+requests, the default setting of having just one active `gitaly-ruby` sidecar might not be enough.
-You can increase the number of `gitaly-ruby` processes on your Gitaly
-server with the following settings.
+If you see `ResourceExhausted` errors from Gitaly, it's very likely that you have not enough
+`gitaly-ruby` capacity.
+
+You can increase the number of `gitaly-ruby` processes on your Gitaly server with the following
+settings:
**For Omnibus GitLab**
@@ -702,13 +745,16 @@ server with the following settings.
1. Save the file and [restart GitLab](../restart_gitlab.md#installations-from-source).
-## Limiting RPC concurrency
+## Limit RPC concurrency
+
+Clone traffic can put a large strain on your Gitaly service. The bulk of the work gets done in the
+either of the following RPCs:
+
+- `SSHUploadPack` (for Git SSH).
+- `PostUploadPack` (for Git HTTP).
-It can happen that CI clone traffic puts a large strain on your Gitaly
-service. The bulk of the work gets done in the SSHUploadPack (for Git
-SSH) and PostUploadPack (for Git HTTP) RPC's. To prevent such workloads
-from overcrowding your Gitaly server you can set concurrency limits in
-Gitaly's configuration file.
+To prevent such workloads from overwhelming your Gitaly server, you can set concurrency limits in
+Gitaly's configuration file. For example:
```ruby
# in /etc/gitlab/gitlab.rb
@@ -725,226 +771,250 @@ gitaly['concurrency'] = [
]
```
-This will limit the number of in-flight RPC calls for the given RPC's.
-The limit is applied per repository. In the example above, each on the
-Gitaly server can have at most 20 simultaneous `PostUploadPack` calls in
-flight, and the same for `SSHUploadPack`. If another request comes in for
-a repository that has used up its 20 slots, that request will get
-queued.
+This limits the number of in-flight RPC calls for the given RPCs. The limit is applied per
+repository. In the example above:
+
+- Each repository served by the Gitaly server can have at most 20 simultaneous `PostUploadPack` RPC
+ calls in flight, and the same for `SSHUploadPack`.
+- If another request comes in for a repository that has used up its 20 slots, that request gets
+ queued.
+
+You can observe the behavior of this queue using the Gitaly logs and Prometheus:
-You can observe the behavior of this queue via the Gitaly logs and via
-Prometheus. In the Gitaly logs, you can look for the string (or
-structured log field) `acquire_ms`. Messages that have this field are
-reporting about the concurrency limiter. In Prometheus, look for the
-`gitaly_rate_limiting_in_progress`, `gitaly_rate_limiting_queued` and
-`gitaly_rate_limiting_seconds` metrics.
+- In the Gitaly logs, look for the string (or structured log field) `acquire_ms`. Messages that have
+ this field are reporting about the concurrency limiter.
+- In Prometheus, look for the following metrics:
-The name of the Prometheus metric is not quite right because this is a
-concurrency limiter, not a rate limiter. If a Gitaly client makes 1000 requests
-in a row in a very short timespan, the concurrency will not exceed 1,
-and this mechanism (the concurrency limiter) will do nothing.
+ - `gitaly_rate_limiting_in_progress`.
+ - `gitaly_rate_limiting_queued`.
+ - `gitaly_rate_limiting_seconds`.
-## Rotating a Gitaly authentication token
+NOTE: **Note:**
+Though the name of the Prometheus metric contains `rate_limiting`, it is a concurrency limiter, not
+a rate limiter. If a Gitaly client makes 1000 requests in a row very quickly, concurrency will not
+exceed 1 and the concurrency limiter has no effect.
+
+## Rotate Gitaly authentication token
+
+Rotating credentials in a production environment often requires downtime, causes outages, or both.
-Rotating credentials in a production environment often either requires
-downtime, or causes outages, or both. If you are careful, though, you
-*can* rotate Gitaly credentials without a service interruption.
+However, you can rotate Gitaly credentials without a service interruption. Rotating a Gitaly
+authentication token involves:
-This procedure also works if you are running GitLab on a single server.
-In that case, "Gitaly server" and "Gitaly client" refers to the same
-machine.
+- [Verifying authentication monitoring](#verify-authentication-monitoring).
+- [Enabling "auth transitioning" mode](#enable-auth-transitioning-mode).
+- [Updating Gitaly authentication tokens](#update-gitaly-authentication-token).
+- [Ensuring there are no authentication failures](#ensure-there-are-no-authentication-failures).
+- [Disabling "auth transitioning" mode](#disable-auth-transitioning-mode).
+- [Verifying authentication is enforced](#verify-authentication-is-enforced).
-### 1. Monitor current authentication behavior
+This procedure also works if you are running GitLab on a single server. In that case, "Gitaly
+server" and "Gitaly client" refers to the same machine.
-Use Prometheus to see what the current authentication behavior of your
-GitLab installation is.
+### Verify authentication monitoring
+
+Before rotating a Gitaly authentication token, verify that you can monitor the authentication
+behavior of your GitLab installation using Prometheus. Use the following Prometheus query:
```prometheus
sum(rate(gitaly_authentications_total[5m])) by (enforced, status)
```
-In a system where authentication is configured correctly, and where you
-have live traffic, you will see something like this:
+In a system where authentication is configured correctly and where you have live traffic, you will
+see something like this:
```prometheus
{enforced="true",status="ok"} 4424.985419441742
```
-There may also be other numbers with rate 0. We only care about the
-non-zero numbers.
+There may also be other numbers with rate 0. We only care about the non-zero numbers.
-The only non-zero number should have `enforced="true",status="ok"`. If
-you have other non-zero numbers, something is wrong in your
-configuration.
+The only non-zero number should have `enforced="true",status="ok"`. If you have other non-zero
+numbers, something is wrong in your configuration.
-The `status="ok"` number reflects your current request rate. In the example
-above, Gitaly is handling about 4000 requests per second.
+The `status="ok"` number reflects your current request rate. In the example above, Gitaly is
+handling about 4000 requests per second.
-Now you have established that you can monitor the Gitaly authentication
-behavior of your GitLab installation.
+Now that you have established that you can monitor the Gitaly authentication behavior of your GitLab
+installation, you can begin the rest of the procedure.
-### 2. Reconfigure all Gitaly servers to be in "auth transitioning" mode
+### Enable "auth transitioning" mode
-The second step is to temporarily disable authentication on the Gitaly servers.
+Temporarily disable Gitaly authentication on the Gitaly servers by putting them into "auth
+transitioning" mode as follows:
```ruby
# in /etc/gitlab/gitlab.rb
gitaly['auth_transitioning'] = true
```
-After you have applied this, your Prometheus query should return
-something like this:
+After you have made this change, your [Prometheus query](#verify-authentication-monitoring)
+should return something like:
```prometheus
{enforced="false",status="would be ok"} 4424.985419441742
```
-Because `enforced="false"`, it will be safe to start rolling out the new
-token.
+Because `enforced="false"`, it is safe to start rolling out the new token.
-### 3. Update Gitaly token on all clients and servers
+### Update Gitaly authentication token
-```ruby
-# in /etc/gitlab/gitlab.rb
+To update to a new Gitaly authentication token, on each Gitaly client **and** Gitaly server:
-gitaly['auth_token'] = 'my new secret token'
-```
+1. Update the configuration:
+
+ ```ruby
+ # in /etc/gitlab/gitlab.rb
+
+ gitaly['auth_token'] = '<new secret token>'
+ ```
-Remember to apply this on both your Gitaly clients *and* servers. If you
-check your Prometheus query while this change is being rolled out, you
-will see non-zero values for the `enforced="false",status="denied"` counter.
+1. Restart Gitaly:
-### 4. Use Prometheus to ensure there are no authentication failures
+ ```shell
+ gitlab-ctl restart gitaly
+ ```
+
+If you run your [Prometheus query](#verify-authentication-monitoring) while this change is
+being rolled out, you will see non-zero values for the `enforced="false",status="denied"` counter.
-After you applied the Gitaly token change everywhere, and all services
-involved have been restarted, you should will temporarily see a mix of
-`status="would be ok"` and `status="denied"`.
+### Ensure there are no authentication failures
-After the new token has been picked up by all Gitaly clients and
-servers, the **only non-zero rate** should be
-`enforced="false",status="would be ok"`.
+After the new token is set, and all services involved have been restarted, you will
+[temporarily see](#verify-authentication-monitoring) a mix of:
-### 5. Disable "auth transitioning" Mode
+- `status="would be ok"`.
+- `status="denied"`.
-Now we turn off the 'auth transitioning' mode. These final steps are
-important: without them, you have **no authentication**.
+After the new token has been picked up by all Gitaly clients and Gitaly servers, the
+**only non-zero rate** should be `enforced="false",status="would be ok"`.
-Update the configuration on your Gitaly servers:
+### Disable "auth transitioning" mode
+
+To re-enable Gitaly authentication, disable "auth transitioning" mode. Update the configuration on
+your Gitaly servers as follows:
```ruby
# in /etc/gitlab/gitlab.rb
gitaly['auth_transitioning'] = false
```
-### 6. Verify that authentication is enforced again
+CAUTION: **Caution:**
+Without completing this step, you have **no Gitaly authentication**.
+
+### Verify authentication is enforced
-Refresh your Prometheus query. You should now see the same kind of
-result as you did in the beginning:
+Refresh your [Prometheus query](#verify-authentication-monitoring). You should now see a similar
+result as you did at the start. For example:
```prometheus
{enforced="true",status="ok"} 4424.985419441742
```
-Note that `enforced="true"`, meaning that authentication is being enforced.
+Note that `enforced="true"` means that authentication is being enforced.
-## Direct Git access in GitLab Rails
+## Direct Git access bypassing Gitaly
-Also known as "the Rugged patches".
+While it is possible to access Gitaly repositories stored on disk directly with a Git client,
+it is not advisable because Gitaly is being continuously improved and changed. Theses improvements may invalidate assumptions, resulting in performance degradation, instability, and even data loss.
+
+Gitaly has optimizations, such as the
+[`info/refs` advertisement cache](https://gitlab.com/gitlab-org/gitaly/blob/master/doc/design_diskcache.md),
+that rely on Gitaly controlling and monitoring access to repositories via the
+official gRPC interface. Likewise, Praefect has optimizations, such as fault
+tolerance and distributed reads, that depend on the gRPC interface and
+database to determine repository state.
+
+For these reasons, **accessing repositories directly is done at your own risk
+and is not supported**.
+
+## Direct access to Git in GitLab
+
+Direct access to Git uses code in GitLab known as the "Rugged patches".
### History
-Before Gitaly existed, the things that are now Gitaly clients used to
-access Git repositories directly. Either on a local disk in the case of
-e.g. a single-machine Omnibus GitLab installation, or via NFS in the
-case of a horizontally scaled GitLab installation.
+Before Gitaly existed, what are now Gitaly clients used to access Git repositories directly, either:
+
+- On a local disk in the case of a single-machine Omnibus GitLab installation
+- Using NFS in the case of a horizontally-scaled GitLab installation.
-Besides running plain `git` commands, in GitLab Rails we also used to
-use a Ruby gem (library) called
+Besides running plain `git` commands, GitLab used to use a Ruby library called
[Rugged](https://github.com/libgit2/rugged). Rugged is a wrapper around
-[libgit2](https://libgit2.org/), a stand-alone implementation of Git in
-the form of a C library.
-
-Over time it has become clear to use that Rugged, and particularly
-Rugged in combination with the [Unicorn](https://yhbt.net/unicorn/)
-web server, is extremely efficient. Because libgit2 is a *library* and
-not an external process, there was very little overhead between GitLab
-application code that tried to look up data in Git repositories, and the
-Git implementation itself.
-
-Because Rugged+Unicorn was so efficient, GitLab's application code ended
-up with lots of duplicate Git object lookups (like looking up the
-`master` commit a dozen times in one request). We could write
-inefficient code without being punished for it.
-
-When we migrated these Git lookups to Gitaly calls, we were suddenly
-getting a much higher fixed cost per Git lookup. Even when Gitaly is
-able to re-use an already-running `git` process to look up e.g. a commit
-you still have the cost of a network roundtrip to Gitaly, and within
-Gitaly a write/read roundtrip on the Unix pipes that connect Gitaly to
-the `git` process.
-
-Using GitLab.com performance as our yardstick, we pushed down the number
-of Gitaly calls per request until the loss of Rugged's efficiency was no
-longer felt. It also helped that we run Gitaly itself directly on the
-Git file severs, rather than via NFS mounts: this gave us a speed boost
-that counteracted the negative effect of not using Rugged anymore.
-
-Unfortunately, some *other* deployments of GitLab could not ditch NFS
-like we did on GitLab.com and they got the worst of both worlds: the
-slowness of NFS and the increased inherent overhead of Gitaly.
-
-As a performance band-aid for these stuck-on-NFS deployments, we
-re-introduced some of the old Rugged code that got deleted from
-GitLab Rails during the Gitaly migration project. These pieces of
-re-introduced code are informally referred to as "the Rugged patches".
-
-### Activation of direct Git access in GitLab Rails
-
-The Ruby methods that perform direct Git access are hidden behind [feature
-flags](../../development/gitaly.md#legacy-rugged-code). These feature
-flags are off by default. It is not good if you need to know about
-feature flags to get the best performance so in a second iteration, we
-added an automatic mechanism that will enable direct Git access.
-
-When GitLab Rails calls a function that has a Rugged patch it performs
-two checks. The result of both of these checks is cached.
-
-1. Is the feature flag for this patch set in the database? If so, do
- what the feature flag says.
-1. If the feature flag is not set (i.e. neither true nor false), try to
- see if we can access filesystem underneath the Gitaly server
- directly. If so, use the Rugged patch.
-
-To see if GitLab Rails can access the repository filesystem directly, we use
-the following heuristic:
-
-- Gitaly ensures that the filesystem has a metadata file in its root
- with a UUID in it.
-- Gitaly reports this UUID to GitLab Rails via the `ServerInfo` RPC.
-- GitLab Rails tries to read the metadata file directly. If it exists,
- and if the UUID's match, assume we have direct access.
-
-Because of the way the UUID check works, and because Omnibus GitLab will
-fill in the correct repository paths in the GitLab Rails config file
-`config/gitlab.yml`, **direct Git access in GitLab Rails is on by default in
-Omnibus**.
-
-### Plans to remove direct Git access in GitLab Rails
-
-For the sake of removing complexity it is desirable that we get rid of
-direct Git access in GitLab Rails. For as long as some GitLab installations are stuck
-with Git repositories on slow NFS, however, we cannot just remove them.
-
-There are two prongs to our efforts to remove direct Git access in GitLab Rails:
-
-1. Reduce the number of (inefficient) Gitaly queries made by
- GitLab Rails.
-1. Persuade everybody who runs a Highly Available / horizontally scaled
- GitLab installation to move off of NFS.
-
-The second prong is the only real solution. For this we need [Gitaly
-HA](https://gitlab.com/groups/gitlab-org/-/epics?scope=all&utf8=%E2%9C%93&state=opened&label_name[]=Gitaly%20HA),
-which is still under development as of December 2019.
+[libgit2](https://libgit2.org/), a stand-alone implementation of Git in the form of a C library.
+
+Over time it became clear that Rugged, particularly in combination with
+[Unicorn](https://yhbt.net/unicorn/), is extremely efficient. Because `libgit2` is a library and
+not an external process, there was very little overhead between:
+
+- GitLab application code that tried to look up data in Git repositories.
+- The Git implementation itself.
+
+Because the combination of Rugged and Unicorn was so efficient, GitLab's application code ended up with lots of
+duplicate Git object lookups. For example, looking up the `master` commit a dozen times in one
+request. We could write inefficient code without poor performance.
+
+When we migrated these Git lookups to Gitaly calls, we suddenly had a much higher fixed cost per Git
+lookup. Even when Gitaly is able to re-use an already-running `git` process (for example, to look up
+a commit), you still have:
+
+- The cost of a network roundtrip to Gitaly.
+- Within Gitaly, a write/read roundtrip on the Unix pipes that connect Gitaly to the `git` process.
+
+Using GitLab.com to measure, we reduced the number of Gitaly calls per request until the loss of
+Rugged's efficiency was no longer felt. It also helped that we run Gitaly itself directly on the Git
+file severs, rather than via NFS mounts. This gave us a speed boost that counteracted the negative
+effect of not using Rugged anymore.
+
+Unfortunately, other deployments of GitLab could not remove NFS like we did on GitLab.com, and they
+got the worst of both worlds:
+
+- The slowness of NFS.
+- The increased inherent overhead of Gitaly.
+
+The code removed from GitLab during the Gitaly migration project affected these deployments. As a
+performance workaround for these NFS-based deployments, we re-introduced some of the old Rugged
+code. This re-introduced code is informally referred to as the "Rugged patches".
+
+### How it works
+
+The Ruby methods that perform direct Git access are behind
+[feature flags](../../development/gitaly.md#legacy-rugged-code), disabled by default. It wasn't
+convenient to set feature flags to get the best performance, so we added an automatic mechanism that
+enables direct Git access.
+
+When GitLab calls a function that has a "Rugged patch", it performs two checks:
+
+- Is the feature flag for this patch set in the database? If so, the feature flag setting controls
+ GitLab's use of "Rugged patch" code.
+- If the feature flag is not set, GitLab tries accessing the filesystem underneath the
+ Gitaly server directly. If it can, it will use the "Rugged patch".
+
+The result of both of these checks is cached.
+
+To see if GitLab can access the repository filesystem directly, we use the following heuristic:
+
+- Gitaly ensures that the filesystem has a metadata file in its root with a UUID in it.
+- Gitaly reports this UUID to GitLab via the `ServerInfo` RPC.
+- GitLab Rails tries to read the metadata file directly. If it exists, and if the UUID's match,
+ assume we have direct access.
+
+Direct Git access is enable by default in Omnibus GitLab because it fills in the correct repository
+paths in the GitLab configuration file `config/gitlab.yml`. This satisfies the UUID check.
+
+### Transition to Gitaly Cluster
+
+For the sake of removing complexity, we must remove direct Git access in GitLab. However, we can't
+remove it as long some GitLab installations require Git repositories on NFS.
+
+There are two facets to our efforts to remove direct Git access in GitLab:
+
+- Reduce the number of inefficient Gitaly queries made by GitLab.
+- Persuade administrators of fault-tolerant or horizontally-scaled GitLab instances to migrate off
+ NFS.
+
+The second facet presents the only real solution. For this, we developed
+[Gitaly Cluster](praefect.md).
## Troubleshooting Gitaly
diff --git a/doc/administration/gitaly/praefect.md b/doc/administration/gitaly/praefect.md
index 3d4e606205e..1f97cd304f9 100644
--- a/doc/administration/gitaly/praefect.md
+++ b/doc/administration/gitaly/praefect.md
@@ -35,8 +35,8 @@ The availability objectives for Gitaly clusters are:
Writes are replicated asynchronously. Any writes that have not been replicated
to the newly promoted primary are lost.
- [Strong Consistency](https://gitlab.com/groups/gitlab-org/-/epics/1189) is
- planned to improve this to "no loss".
+ [Strong consistency](#strong-consistency) can be used to avoid loss in some
+ circumstances.
- **Recovery Time Objective (RTO):** Less than 10 seconds.
@@ -103,7 +103,7 @@ GitLab](https://about.gitlab.com/install/).
You will need the IP/host address for each node.
-1. `LOAD_BALANCER_SERVER_ADDRESS`: the IP/hots address of the load balancer
+1. `LOAD_BALANCER_SERVER_ADDRESS`: the IP/host address of the load balancer
1. `POSTGRESQL_SERVER_ADDRESS`: the IP/host address of the PostgreSQL server
1. `PRAEFECT_HOST`: the IP/host address of the Praefect server
1. `GITALY_HOST`: the IP/host address of each Gitaly server
@@ -131,14 +131,13 @@ with secure tokens as you complete the setup process.
Praefect cluster directly; that could lead to data loss.
1. `PRAEFECT_SQL_PASSWORD`: this password is used by Praefect to connect to
PostgreSQL.
-1. `GRAFANA_PASSWORD`: this password is used to access the `admin`
- account in the Grafana dashboards.
We will note in the instructions below where these secrets are required.
### PostgreSQL
-NOTE: **Note:** do not store the GitLab application database and the Praefect
+NOTE: **Note:**
+Do not store the GitLab application database and the Praefect
database on the same PostgreSQL server if using
[Geo](../geo/replication/index.md). The replication state is internal to each instance
of GitLab and should not be replicated.
@@ -283,9 +282,16 @@ application server, or a Gitaly node.
1. Configure the **Praefect** cluster to connect to each Gitaly node in the
cluster by editing `/etc/gitlab/gitlab.rb`.
- In the example below we have configured one virtual storage (or shard) named
- `storage-1`. This cluster has three Gitaly nodes `gitaly-1`, `gitaly-2`, and
- `gitaly-3`, which will be replicas of each other.
+ The virtual storage's name must match the configured storage name in GitLab
+ configuration. In a later step, we configure the storage name as `default`
+ so we use `default` here as well. This cluster has three Gitaly nodes `gitaly-1`,
+ `gitaly-2`, and `gitaly-3`, which will be replicas of each other.
+
+ CAUTION: **Caution:**
+ If you have data on an already existing storage called
+ `default`, you should configure the virtual storage with another name and
+ [migrate the data to the Praefect storage](#migrating-existing-repositories-to-praefect)
+ afterwards.
Replace `PRAEFECT_INTERNAL_TOKEN` with a strong secret, which will be used by
Praefect when communicating with Gitaly nodes in the cluster. This token is
@@ -296,7 +302,8 @@ application server, or a Gitaly node.
More Gitaly nodes can be added to the cluster to increase the number of
replicas. More clusters can also be added for very large GitLab instances.
- NOTE: **Note:** The `gitaly-1` node is currently denoted the primary. This
+ NOTE: **Note:**
+ The `gitaly-1` node is currently denoted the primary. This
can be used to manually fail from one node to another. This will be removed
in the [future](https://gitlab.com/gitlab-org/gitaly/-/issues/2634).
@@ -304,7 +311,7 @@ application server, or a Gitaly node.
# Name of storage hash must match storage name in git_data_dirs on GitLab
# server ('praefect') and in git_data_dirs on Gitaly nodes ('gitaly-1')
praefect['virtual_storages'] = {
- 'storage-1' => {
+ 'default' => {
'gitaly-1' => {
'address' => 'tcp://GITALY_HOST:8075',
'token' => 'PRAEFECT_INTERNAL_TOKEN',
@@ -322,6 +329,8 @@ application server, or a Gitaly node.
}
```
+1. [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/2013) in GitLab 13.1 and later, enable [distribution of reads](#distributed-reads).
+
1. Save the changes to `/etc/gitlab/gitlab.rb` and [reconfigure
Praefect](../restart_gitlab.md#omnibus-gitlab-reconfigure):
@@ -349,9 +358,146 @@ application server, or a Gitaly node.
**The steps above must be completed for each Praefect node!**
+## Enabling TLS support
+
+> [Introduced](https://gitlab.com/gitlab-org/gitaly/-/issues/1698) in GitLab 13.2.
+
+Praefect supports TLS encryption. To communicate with a Praefect instance that listens
+for secure connections, you must:
+
+- Use a `tls://` URL scheme in the `gitaly_address` of the corresponding storage entry
+ in the GitLab configuration.
+- Bring your own certificates because this isn't provided automatically. The certificate
+ corresponding to each Praefect server must be installed on that Praefect server.
+
+Additionally the certificate, or its certificate authority, must be installed on all Gitaly servers
+and on all Praefect clients that communicate with it following the procedure described in
+[GitLab custom certificate configuration](https://docs.gitlab.com/omnibus/settings/ssl.html#install-custom-public-certificates) (and repeated below).
+
+Note the following:
+
+- The certificate must specify the address you use to access the Praefect server. If
+ addressing the Praefect server by:
+
+ - Hostname, you can either use the Common Name field for this, or add it as a Subject
+ Alternative Name.
+ - IP address, you must add it as a Subject Alternative Name to the certificate.
+
+- You can configure Praefect servers with both an unencrypted listening address
+ `listen_addr` and an encrypted listening address `tls_listen_addr` at the same time.
+ This allows you to do a gradual transition from unencrypted to encrypted traffic, if
+ necessary.
+
+To configure Praefect with TLS:
+
+**For Omnibus GitLab**
+
+1. Create certificates for Praefect servers.
+1. On the Praefect servers, create the `/etc/gitlab/ssl` directory and copy your key
+ and certificate there:
+
+ ```shell
+ sudo mkdir -p /etc/gitlab/ssl
+ sudo chmod 755 /etc/gitlab/ssl
+ sudo cp key.pem cert.pem /etc/gitlab/ssl/
+ sudo chmod 644 key.pem cert.pem
+ ```
+
+1. Edit `/etc/gitlab/gitlab.rb` and add:
+
+ ```ruby
+ praefect['tls_listen_addr'] = "0.0.0.0:3305"
+ praefect['certificate_path'] = "/etc/gitlab/ssl/cert.pem"
+ praefect['key_path'] = "/etc/gitlab/ssl/key.pem"
+ ```
+
+1. Save the file and [reconfigure GitLab](../restart_gitlab.md#omnibus-gitlab-reconfigure).
+1. On the Praefect clients (including each Gitaly server), copy the certificates,
+ or their certificate authority, into `/etc/gitlab/trusted-certs`:
+
+ ```shell
+ sudo cp cert.pem /etc/gitlab/trusted-certs/
+ ```
+
+1. On the Praefect clients (except Gitaly servers), edit `git_data_dirs` in
+ `/etc/gitlab/gitlab.rb` as follows:
+
+ ```ruby
+ git_data_dirs({
+ 'default' => { 'gitaly_address' => 'tls://praefect1.internal:3305' },
+ 'storage1' => { 'gitaly_address' => 'tls://praefect2.internal:3305' },
+ })
+ ```
+
+1. Save the file and [reconfigure GitLab](../restart_gitlab.md#omnibus-gitlab-reconfigure).
+
+**For installations from source**
+
+1. Create certificates for Praefect servers.
+1. On the Praefect servers, create the `/etc/gitlab/ssl` directory and copy your key and certificate
+ there:
+
+ ```shell
+ sudo mkdir -p /etc/gitlab/ssl
+ sudo chmod 755 /etc/gitlab/ssl
+ sudo cp key.pem cert.pem /etc/gitlab/ssl/
+ sudo chmod 644 key.pem cert.pem
+ ```
+
+1. On the Praefect clients (including each Gitaly server), copy the certificates,
+ or their certificate authority, into the system trusted certificates:
+
+ ```shell
+ sudo cp cert.pem /usr/local/share/ca-certificates/praefect.crt
+ sudo update-ca-certificates
+ ```
+
+1. On the Praefect clients (except Gitaly servers), edit `storages` in
+ `/home/git/gitlab/config/gitlab.yml` as follows:
+
+ ```yaml
+ gitlab:
+ repositories:
+ storages:
+ default:
+ gitaly_address: tls://praefect1.internal:3305
+ path: /some/dummy/path
+ storage1:
+ gitaly_address: tls://praefect2.internal:3305
+ path: /some/dummy/path
+ ```
+
+ NOTE: **Note:**
+ `/some/dummy/path` should be set to a local folder that exists, however no
+ data will be stored in this folder. This will no longer be necessary after
+ [this issue](https://gitlab.com/gitlab-org/gitaly/-/issues/1282) is resolved.
+
+1. Save the file and [restart GitLab](../restart_gitlab.md#installations-from-source).
+1. Copy all Praefect server certificates, or their certificate authority, to the system
+ trusted certificates on each Gitaly server so the Praefect server will trust the
+ certificate when called by Gitaly servers:
+
+ ```shell
+ sudo cp cert.pem /usr/local/share/ca-certificates/praefect.crt
+ sudo update-ca-certificates
+ ```
+
+1. Edit `/home/git/praefect/config.toml` and add:
+
+ ```toml
+ tls_listen_addr = '0.0.0.0:3305'
+
+ [tls]
+ certificate_path = '/etc/gitlab/ssl/cert.pem'
+ key_path = '/etc/gitlab/ssl/key.pem'
+ ```
+
+1. Save the file and [restart GitLab](../restart_gitlab.md#installations-from-source).
+
### Gitaly
-NOTE: **Note:** Complete these steps for **each** Gitaly node.
+NOTE: **Note:**
+Complete these steps for **each** Gitaly node.
To complete this section you will need:
@@ -555,6 +701,17 @@ Particular attention should be shown to:
external_url 'GITLAB_SERVER_URL'
```
+1. Disable the default Gitaly service running on the GitLab host. It won't be needed
+ as GitLab will connect to the configured cluster.
+
+ CAUTION: **Caution:**
+ If you have existing data stored on the default Gitaly storage,
+ you should [migrate the data your Praefect storage first](#migrating-existing-repositories-to-praefect).
+
+ ```ruby
+ gitaly['enable'] = false
+ ```
+
1. Add the Praefect cluster as a storage location by editing
`/etc/gitlab/gitlab.rb`.
@@ -562,28 +719,17 @@ Particular attention should be shown to:
- `LOAD_BALANCER_SERVER_ADDRESS` with the IP address or hostname of the load
balancer.
- - `GITLAB_HOST` with the IP address or hostname of the GitLab server
- `PRAEFECT_EXTERNAL_TOKEN` with the real secret
```ruby
git_data_dirs({
"default" => {
- "gitaly_address" => "tcp://GITLAB_HOST:8075"
- },
- "storage-1" => {
"gitaly_address" => "tcp://LOAD_BALANCER_SERVER_ADDRESS:2305",
"gitaly_token" => 'PRAEFECT_EXTERNAL_TOKEN'
}
})
```
-1. Allow Gitaly to listen on a TCP port by editing
- `/etc/gitlab/gitlab.rb`
-
- ```ruby
- gitaly['listen_addr'] = '0.0.0.0:8075'
- ```
-
1. Configure the `gitlab_shell['secret_token']` so that callbacks from Gitaly
nodes during a `git push` are properly authenticated by editing
`/etc/gitlab/gitlab.rb`:
@@ -632,14 +778,6 @@ Particular attention should be shown to:
gitlab-ctl reconfigure
```
-1. To ensure that Gitaly [has updated its Prometheus listen
- address](https://gitlab.com/gitlab-org/gitaly/-/issues/2734), [restart
- Gitaly](../restart_gitlab.md#omnibus-gitlab-restart):
-
- ```shell
- gitlab-ctl restart gitaly
- ```
-
1. Verify each `gitlab-shell` on each Gitaly instance can reach GitLab. On each Gitaly instance run:
```shell
@@ -652,16 +790,11 @@ Particular attention should be shown to:
gitlab-rake gitlab:gitaly:check
```
-1. Update the **Repository storage** settings from **Admin Area > Settings >
- Repository > Repository storage** to make the newly configured Praefect
- cluster the storage location for new Git repositories.
-
- - The default option is unchecked.
- - The Praefect option is checked.
+1. Check in **Admin Area > Settings > Repository > Repository storage** that the Praefect storage
+ is configured to store new repositories. Following this guide, the `default` storage should have
+ weight 100 to store all new repositories.
- ![Update repository storage](img/praefect_storage_v12_10.png)
-
-1. Verify everything is still working by creating a new project. Check the
+1. Verify everything is working by creating a new project. Check the
"Initialize repository with a README" box so that there is content in the
repository that viewed. If the project is created, and you can see the
README file, it works!
@@ -712,6 +845,72 @@ To get started quickly:
Congratulations! You've configured an observable highly available Praefect
cluster.
+## Distributed reads
+
+> Introduced in GitLab 13.1 in [beta](https://about.gitlab.com/handbook/product/#alpha-beta-ga) with feature flag `gitaly_distributed_reads` set to disabled.
+
+Praefect supports distribution of read operations across Gitaly nodes that are
+configured for the virtual node.
+
+To allow for [performance testing](https://gitlab.com/gitlab-org/quality/performance/-/issues/231),
+distributed reads are currently in
+[beta](https://about.gitlab.com/handbook/product/#alpha-beta-ga) and disabled by
+default. To enable distributed reads, the `gitaly_distributed_reads`
+[feature flag](../feature_flags.md) must be enabled in a Ruby console:
+
+```ruby
+Feature.enable(:gitaly_distributed_reads)
+```
+
+If enabled, all RPCs marked with `ACCESSOR` option like
+[GetBlob](https://gitlab.com/gitlab-org/gitaly/-/blob/v12.10.6/proto/blob.proto#L16)
+are redirected to an up to date and healthy Gitaly node.
+
+_Up to date_ in this context means that:
+
+- There is no replication operations scheduled for this node.
+- The last replication operation is in _completed_ state.
+
+If there is no such nodes, or any other error occurs during node selection, the primary
+node will be chosen to serve the request.
+
+To track distribution of read operations, you can use the `gitaly_praefect_read_distribution`
+Prometheus counter metric. It has two labels:
+
+- `virtual_storage`.
+- `storage`.
+
+They reflect configuration defined for this instance of Praefect.
+
+## Strong consistency
+
+> Introduced in GitLab 13.1 in [alpha](https://about.gitlab.com/handbook/product/#alpha-beta-ga), disabled by default.
+
+Praefect guarantees eventual consistency by replicating all writes to secondary nodes
+after the write to the primary Gitaly node has happened.
+
+Praefect can instead provide strong consistency by creating a transaction and writing
+changes to all Gitaly nodes at once. Strong consistency is currently in
+[alpha](https://about.gitlab.com/handbook/product/#alpha-beta-ga) and not enabled by
+default. If enabled, transactions are only available for a subset of RPCs. For more
+information, see the [strong consistency epic](https://gitlab.com/groups/gitlab-org/-/epics/1189).
+
+To enable strong consistency:
+
+- In GitLab 13.2 and later, enable the `:gitaly_reference_transactions` feature flag.
+- In GitLab 13.1, enable the `:gitaly_reference_transactions` and `:gitaly_hooks_rpc`
+ feature flags.
+
+Enabling feature flags requires [access to the Rails console](../feature_flags.md#start-the-gitlab-rails-console).
+In the Rails console, enable or disable the flags as required. For example:
+
+```ruby
+Feature.enable(:gitaly_reference_transactions)
+```
+
+To monitor strong consistency, use the `gitaly_praefect_transactions_total` and
+`gitaly_praefect_transactions_delay_seconds` Prometheus counter metrics.
+
## Automatic failover and leader election
Praefect regularly checks the health of each backend Gitaly node. This
@@ -739,38 +938,68 @@ current primary node is found to be unhealthy.
It is likely that we will implement support for Consul, and a cloud native
strategy in the future.
-## Identifying Impact of a Primary Node Failure
+## Primary Node Failure
-When a primary Gitaly node fails, there is a chance of data loss. Data loss can occur if there were outstanding replication jobs the secondaries did not manage to process before the failure. The `dataloss` Praefect sub-command helps identify these cases by counting the number of dead replication jobs for each repository. This command must be executed on a Praefect node.
+Praefect recovers from a failing primary Gitaly node by promoting a healthy secondary as the new primary. To minimize data loss, Praefect elects the secondary with the least unreplicated writes from the primary. There can still be some unreplicated writes, leading to data loss.
-A time frame to search can be specified with `-from` and `-to`:
+Praefect switches a virtual storage in to read-only mode after a failover event. This eases data recovery efforts by preventing new, possibly conflicting writes to the newly elected primary. This allows the administrator to attempt recovering the lost data before allowing new writes.
+
+If you prefer write availability over consistency, this behavior can be turned off by setting `praefect['failover_read_only_after_failover'] = false` in `/etc/gitlab/gitlab.rb` and [reconfiguring Praefect](../restart_gitlab.md#omnibus-gitlab-reconfigure).
+
+### Checking for data loss
+
+The Praefect `dataloss` sub-command helps identify lost writes by checking for uncompleted replication jobs. This is useful for identifying possible data loss cases after a failover. This command must be executed on a Praefect node.
```shell
-sudo /opt/gitlab/embedded/bin/praefect -config /var/opt/gitlab/praefect/config.toml dataloss -from <rfc3339-time> -to <rfc3339-time>
+sudo /opt/gitlab/embedded/bin/praefect -config /var/opt/gitlab/praefect/config.toml dataloss [-virtual-storage <virtual-storage>]
```
-If the time frame is not specified, dead replication jobs from the last six hours are counted:
+If the virtual storage is not specified, every configured virtual storage is checked for data loss.
```shell
sudo /opt/gitlab/embedded/bin/praefect -config /var/opt/gitlab/praefect/config.toml dataloss
+```
-Failed replication jobs between [2020-01-02 00:00:00 +0000 UTC, 2020-01-02 06:00:00 +0000 UTC):
-@hashed/fa/53/fa539965395b8382145f8370b34eab249cf610d2d6f2943c95b9b9d08a63d4a3.git: 2 jobs
+```shell
+Virtual storage: default
+ Current read-only primary: gitaly-2
+ Previous write-enabled primary: gitaly-1
+ Nodes with data loss from failing over from gitaly-1:
+ @hashed/2c/62/2c624232cdd221771294dfbb310aca000a0df6ac8b66b696d90ef06fdefb64a3.git: gitaly-0
+ @hashed/4b/22/4b227777d4dd1fc61c6f884f48641d02b4d121d3fd328cb08b5531fcacdabf8a.git: gitaly-0, gitaly-2
```
-To specify a time frame in UTC, run:
+Currently `dataloss` only considers a repository up to date if it has been directly replicated to from the previous write-enabled primary. While reconciling from an up to date secondary can recover the data, this is not visible in the data loss report. This is due for improvement via [Gitaly#2866](https://gitlab.com/gitlab-org/gitaly/-/issues/2866).
+
+NOTE: **Note:**
+`dataloss` is still in beta and the output format is subject to change.
+
+### Checking repository checksums
+
+To check a project's repository checksums across on all Gitaly nodes, run the
+[replicas Rake task](../raketasks/praefect.md#replica-checksums) on the main GitLab node.
+
+### Recovering lost writes
+
+The Praefect `reconcile` sub-command can be used to recover lost writes from the
+previous primary once it is back online. This is only possible when the virtual storage
+is still in read-only mode.
```shell
-sudo /opt/gitlab/embedded/bin/praefect -config /var/opt/gitlab/praefect/config.toml dataloss -from 2020-01-02T00:00:00+00:00 -to 2020-01-02T00:02:00+00:00
+sudo /opt/gitlab/embedded/bin/praefect -config /var/opt/gitlab/praefect/config.toml reconcile -virtual <virtual-storage> -reference <previous-primary> -target <current-primary> -f
```
-### Checking repository checksums
+Refer to [Backend Node Recovery](#backend-node-recovery) section for more details on
+the `reconcile` sub-command.
+
+### Enabling Writes
-To check a project's repository checksums across on all Gitaly nodes, the
-replicas Rake task can be run on the main GitLab node:
+Any data recovery attempts should have been made before enabling writes to eliminate
+any chance of conflicting writes. Virtual storage can be re-enabled for writes by using
+the Praefect `enable-writes` sub-command.
```shell
-sudo gitlab-rake "gitlab:praefect:replicas[project_id]"
+sudo /opt/gitlab/embedded/bin/praefect -config /var/opt/gitlab/praefect/config.toml enable-writes -virtual-storage <virtual-storage>
```
## Backend Node Recovery
diff --git a/doc/administration/gitaly/reference.md b/doc/administration/gitaly/reference.md
index 52fd6fa6900..0429149ec2d 100644
--- a/doc/administration/gitaly/reference.md
+++ b/doc/administration/gitaly/reference.md
@@ -91,7 +91,7 @@ certificate_path = '/home/git/cert.cert'
key_path = '/home/git/key.pem'
```
-[Read more](index.md#tls-support) about TLS in Gitaly.
+[Read more](index.md#enable-tls-support) about TLS in Gitaly.
### Storage
diff --git a/doc/administration/high_availability/consul.md b/doc/administration/high_availability/consul.md
index a87c1f1027f..978ba08c4fa 100644
--- a/doc/administration/high_availability/consul.md
+++ b/doc/administration/high_availability/consul.md
@@ -113,14 +113,14 @@ Nodes running GitLab-bundled Consul should be:
- Members of a healthy cluster prior to upgrading the Omnibus GitLab package.
- Upgraded one node at a time.
-NOTE: **NOTE:**
+NOTE: **Note:**
Running `curl http://127.0.0.1:8500/v1/health/state/critical` from any Consul node will identify existing health issues in the cluster. The command will return an empty array if the cluster is healthy.
Consul clusters communicate using the raft protocol. If the current leader goes offline, there needs to be a leader election. A leader node must exist to facilitate synchronization across the cluster. If too many nodes go offline at the same time, the cluster will lose quorum and not elect a leader due to [broken consensus](https://www.consul.io/docs/internals/consensus.html).
Consult the [troubleshooting section](#troubleshooting) if the cluster is not able to recover after the upgrade. The [outage recovery](#outage-recovery) may be of particular interest.
-NOTE: **NOTE:**
+NOTE: **Note:**
GitLab only uses Consul to store transient data that is easily regenerated. If the bundled Consul was not used by any process other than GitLab itself, then [rebuilding the cluster from scratch](#recreate-from-scratch) is fine.
## Troubleshooting
diff --git a/doc/administration/high_availability/database.md b/doc/administration/high_availability/database.md
index 75183436046..784e496d10e 100644
--- a/doc/administration/high_availability/database.md
+++ b/doc/administration/high_availability/database.md
@@ -1,20 +1,5 @@
---
-type: reference
+redirect_to: '../postgresql/index.md'
---
-# Configuring PostgreSQL for Scaling and High Availability
-
-In this section, you'll be guided through configuring a PostgreSQL database to
-be used with GitLab in one of our [Scalable and Highly Available Setups](../reference_architectures/index.md).
-
-## Provide your own PostgreSQL instance **(CORE ONLY)**
-
-This content has been moved to a [new location](../postgresql/external.md).
-
-## Standalone PostgreSQL using Omnibus GitLab **(CORE ONLY)**
-
-This content has been moved to a [new location](../postgresql/standalone.md).
-
-## PostgreSQL replication and failover with Omnibus GitLab **(PREMIUM ONLY)**
-
-This content has been moved to a [new location](../postgresql/replication_and_failover.md).
+This document was moved to [another location](../postgresql/index.md).
diff --git a/doc/administration/high_availability/gitlab.md b/doc/administration/high_availability/gitlab.md
index 67a84f99bea..dc8c997bab5 100644
--- a/doc/administration/high_availability/gitlab.md
+++ b/doc/administration/high_availability/gitlab.md
@@ -6,11 +6,13 @@ type: reference
This section describes how to configure the GitLab application (Rails) component.
-NOTE: **Note:** There is some additional configuration near the bottom for
+NOTE: **Note:**
+There is some additional configuration near the bottom for
additional GitLab application servers. It's important to read and understand
these additional steps before proceeding with GitLab installation.
-NOTE: **Note:** [Cloud Object Storage service](object_storage.md) with [Gitaly](gitaly.md)
+NOTE: **Note:**
+[Cloud Object Storage service](object_storage.md) with [Gitaly](gitaly.md)
is recommended over [NFS](nfs.md) wherever possible for improved performance.
1. If necessary, install the NFS client utility packages using the following
@@ -79,19 +81,22 @@ is recommended over [NFS](nfs.md) wherever possible for improved performance.
1. [Enable monitoring](#enable-monitoring)
- NOTE: **Note:** To maintain uniformity of links across HA clusters, the `external_url`
+ NOTE: **Note:**
+ To maintain uniformity of links across HA clusters, the `external_url`
on the first application server as well as the additional application
servers should point to the external URL that users will use to access GitLab.
In a typical HA setup, this will be the URL of the load balancer which will
route traffic to all GitLab application servers in the HA cluster.
- NOTE: **Note:** When you specify `https` in the `external_url`, as in the example
+ NOTE: **Note:**
+ When you specify `https` in the `external_url`, as in the example
above, GitLab assumes you have SSL certificates in `/etc/gitlab/ssl/`. If
certificates are not present, NGINX will fail to start. See
[NGINX documentation](https://docs.gitlab.com/omnibus/settings/nginx.html#enable-https)
for more information.
- NOTE: **Note:** It is best to set the `uid` and `gid`s prior to the initial reconfigure
+ NOTE: **Note:**
+ It is best to set the `uid` and `gid`s prior to the initial reconfigure
of GitLab. Omnibus will not recursively `chown` directories if set after the initial reconfigure.
## First GitLab application server
@@ -126,14 +131,15 @@ need some extra configuration.
from running on upgrade. Only the primary GitLab application server should
handle migrations.
-1. **Recommended** Configure host keys. Copy the contents (primary and public keys) of `/etc/ssh/` on
+1. **Recommended** Configure host keys. Copy the contents (private and public keys) of `/etc/ssh/` on
the primary application server to `/etc/ssh` on all secondary servers. This
prevents false man-in-the-middle-attack alerts when accessing servers in your
High Availability cluster behind a load balancer.
1. Run `sudo gitlab-ctl reconfigure` to compile the configuration.
-NOTE: **Note:** You will need to restart the GitLab applications nodes after an update has occurred and database
+NOTE: **Note:**
+You will need to restart the GitLab applications nodes after an update has occurred and database
migrations performed.
## Enable Monitoring
diff --git a/doc/administration/high_availability/monitoring_node.md b/doc/administration/high_availability/monitoring_node.md
index 653a0b32ad7..6b6f0ae9ea3 100644
--- a/doc/administration/high_availability/monitoring_node.md
+++ b/doc/administration/high_availability/monitoring_node.md
@@ -71,6 +71,19 @@ Omnibus:
1. Run `sudo gitlab-ctl reconfigure` to compile the configuration.
+The next step is to tell all the other nodes where the monitoring node is:
+
+1. Edit `/etc/gitlab/gitlab.rb`, and add, or find and uncomment the following line:
+
+ ```ruby
+ gitlab_rails['prometheus_address'] = '10.0.0.1:9090'
+ ```
+
+ Where `10.0.0.1:9090` is the IP address and port of the Prometheus node.
+
+1. Save the file and [reconfigure GitLab](../restart_gitlab.md#omnibus-gitlab-reconfigure) for the changes to
+ take effect.
+
## Migrating to Service Discovery
Once monitoring using Service Discovery is enabled with `consul['monitoring_service_discovery'] = true`,
diff --git a/doc/administration/high_availability/nfs.md b/doc/administration/high_availability/nfs.md
index 6511f9bd85d..6e8dc2c6c57 100644
--- a/doc/administration/high_availability/nfs.md
+++ b/doc/administration/high_availability/nfs.md
@@ -12,11 +12,29 @@ From GitLab 13.0, using NFS for Git repositories is deprecated. In GitLab 14.0,
support for NFS for Git repositories is scheduled to be removed. Upgrade to
[Gitaly Cluster](../gitaly/praefect.md) as soon as possible.
-NOTE: **Note:** Filesystem performance has a big impact on overall GitLab
+NOTE: **Note:**
+Filesystem performance has a big impact on overall GitLab
performance, especially for actions that read or write to Git repositories. See
[Filesystem Performance Benchmarking](../operations/filesystem_benchmarking.md)
for steps to test filesystem performance.
+## Known kernel version incompatibilities
+
+RedHat Enterprise Linux (RHEL) and CentOS v7.7 and v7.8 ship with kernel
+version `3.10.0-1127`, which [contains a
+bug](https://bugzilla.redhat.com/show_bug.cgi?id=1783554) that causes
+[uploads to fail to copy over NFS](https://gitlab.com/gitlab-org/gitlab/-/issues/218999). The
+following GitLab versions include a fix to work properly with that
+kernel version:
+
+1. [12.10.12](https://about.gitlab.com/releases/2020/06/25/gitlab-12-10-12-released/)
+1. [13.0.7](https://about.gitlab.com/releases/2020/06/25/gitlab-13-0-7-released/)
+1. [13.1.1](https://about.gitlab.com/releases/2020/06/24/gitlab-13-1-1-released/)
+1. 13.2 and up
+
+If you are using that kernel version, be sure to upgrade GitLab to avoid
+errors.
+
## NFS Server features
### Required features
@@ -88,7 +106,8 @@ administrators to keep NFS server delegation disabled.
#### Improving NFS performance with Unicorn
-NOTE: **Note:** From GitLab 12.1, it will automatically be detected if Rugged can and should be used per storage.
+NOTE: **Note:**
+From GitLab 12.1, it will automatically be detected if Rugged can and should be used per storage.
If you previously enabled Rugged using the feature flag, you will need to unset the feature flag by using:
@@ -100,7 +119,8 @@ If the Rugged feature flag is explicitly set to either true or false, GitLab wil
#### Improving NFS performance with Puma
-NOTE: **Note:** From GitLab 12.7, Rugged auto-detection is disabled if Puma thread count is greater than 1.
+NOTE: **Note:**
+From GitLab 12.7, Rugged auto-detection is disabled if Puma thread count is greater than 1.
If you want to use Rugged with Puma, it is recommended to [set Puma thread count to 1](https://docs.gitlab.com/omnibus/settings/puma.html#puma-settings).
diff --git a/doc/administration/high_availability/nfs_host_client_setup.md b/doc/administration/high_availability/nfs_host_client_setup.md
index 7519ebf028d..213680e2f64 100644
--- a/doc/administration/high_availability/nfs_host_client_setup.md
+++ b/doc/administration/high_availability/nfs_host_client_setup.md
@@ -38,10 +38,10 @@ In this setup we will share the home directory on the host with the client. Edit
```plaintext
#/etc/exports for one client
-/home <client-ip-address>(rw,sync,no_root_squash,no_subtree_check)
+/home <client_ip_address>(rw,sync,no_root_squash,no_subtree_check)
#/etc/exports for three clients
-/home <client-ip-address>(rw,sync,no_root_squash,no_subtree_check) <client-2-ip-address>(rw,sync,no_root_squash,no_subtree_check) <client-3-ip-address>(rw,sync,no_root_squash,no_subtree_check)
+/home <client_ip_address>(rw,sync,no_root_squash,no_subtree_check) <client_2_ip_address>(rw,sync,no_root_squash,no_subtree_check) <client_3_ip_address>(rw,sync,no_root_squash,no_subtree_check)
```
Restart the NFS server after making changes to the `exports` file for the changes
@@ -54,7 +54,7 @@ systemctl restart nfs-kernel-server
NOTE: **Note:**
You may need to update your server's firewall. See the [firewall section](#nfs-in-a-firewalled-environment) at the end of this guide.
-## Client/ GitLab application node Setup
+## Client / GitLab application node Setup
> Follow the instructions below to connect any GitLab Rails application node running
inside your HA environment to the NFS server configured above.
@@ -90,7 +90,7 @@ df -h
### Step 3 - Set up Automatic Mounts on Boot
-Edit `/etc/fstab` on client as below to mount the remote shares automatically at boot.
+Edit `/etc/fstab` on the client as below to mount the remote shares automatically at boot.
Note that GitLab requires advisory file locking, which is only supported natively in
NFS version 4. NFSv3 also supports locking as long as Linux Kernel 2.6.5+ is used.
We recommend using version 4 and do not specifically test NFSv3.
@@ -98,14 +98,19 @@ See [NFS documentation](nfs.md#nfs-client-mount-options) for guidance on mount o
```plaintext
#/etc/fstab
-10.0.0.1:/nfs/home /nfs/home nfs4 defaults,hard,vers=4.1,rsize=1048576,wsize=1048576,noatime,nofail,lookupcache=positive 0 2
+<host_ip_address>:/home /nfs/home nfs4 defaults,hard,vers=4.1,rsize=1048576,wsize=1048576,noatime,nofail,lookupcache=positive 0 2
```
Reboot the client and confirm that the mount point is mounted automatically.
+NOTE: **Note:**
+If you followed our guide to [GitLab Pages on a separate server](../pages/index.md#running-gitlab-pages-on-a-separate-server)
+here, please continue there with the pages-specific NFS mounts.
+The step below is for broader use-cases than only sharing pages data.
+
### Step 4 - Set up GitLab to Use NFS mounts
-When using the default Omnibus configuration you will need to share 5 data locations
+When using the default Omnibus configuration you will need to share 4 data locations
between all GitLab cluster nodes. No other locations should be shared. Changing the
default file locations in `gitlab.rb` on the client allows you to have one main mount
point and have all the required locations as subdirectories to use the NFS mount for
@@ -136,7 +141,7 @@ the command: `sudo ufw status`. If it's being blocked, then you can allow traffi
client with the command below.
```shell
-sudo ufw allow from <client-ip-address> to any port nfs
+sudo ufw allow from <client_ip_address> to any port nfs
```
<!-- ## Troubleshooting
diff --git a/doc/administration/high_availability/redis.md b/doc/administration/high_availability/redis.md
index bad50f7ca74..2b5771f49f2 100644
--- a/doc/administration/high_availability/redis.md
+++ b/doc/administration/high_availability/redis.md
@@ -1,1008 +1,5 @@
---
-type: reference
+redirect_to: ../redis/index.md
---
-# Configuring Redis for Scaling and High Availability
-
-## Provide your own Redis instance **(CORE ONLY)**
-
-The following are the requirements for providing your own Redis instance:
-
-- Redis version 5.0 or higher is recommended, as this is what ships with
- Omnibus GitLab packages starting with GitLab 12.7.
-- Support for Redis 3.2 is deprecated with GitLab 12.10 and will be completely
- removed in GitLab 13.0.
-- GitLab 12.0 and later requires Redis version 3.2 or higher. Older Redis
- versions do not support an optional count argument to SPOP which is now
- required for [Merge Trains](../../ci/merge_request_pipelines/pipelines_for_merged_results/merge_trains/index.md).
-- In addition, if Redis 4 or later is available, GitLab makes use of certain
- commands like `UNLINK` and `USAGE` which were introduced only in Redis 4.
-- Standalone Redis or Redis high availability with Sentinel are supported. Redis
- Cluster is not supported.
-- Managed Redis from cloud providers such as AWS ElastiCache will work. If these
- services support high availability, be sure it is not the Redis Cluster type.
-
-Note the Redis node's IP address or hostname, port, and password (if required).
-These will be necessary when configuring the GitLab application servers later.
-
-## Redis in a Scaled and Highly Available Environment
-
-This section is relevant for [scalable and highly available setups](../reference_architectures/index.md).
-
-### Provide your own Redis instance **(CORE ONLY)**
-
-If you want to use your own deployed Redis instance(s),
-see [Provide your own Redis instance](#provide-your-own-redis-instance-core-only)
-for more details. However, you can use the Omnibus GitLab package to easily
-deploy the bundled Redis.
-
-### Standalone Redis using Omnibus GitLab **(CORE ONLY)**
-
-The Omnibus GitLab package can be used to configure a standalone Redis server.
-In this configuration Redis is not highly available, and represents a single
-point of failure. However, in a scaled environment the objective is to allow
-the environment to handle more users or to increase throughput. Redis itself
-is generally stable and can handle many requests so it is an acceptable
-trade off to have only a single instance. See the [reference architectures](../reference_architectures/index.md)
-page for an overview of GitLab scaling and high availability options.
-
-The steps below are the minimum necessary to configure a Redis server with
-Omnibus:
-
-1. SSH into the Redis server.
-1. [Download/install](https://about.gitlab.com/install/) the Omnibus GitLab
- package you want using **steps 1 and 2** from the GitLab downloads page.
- - Do not complete any other steps on the download page.
-
-1. Edit `/etc/gitlab/gitlab.rb` and add the contents:
-
- ```ruby
- ## Enable Redis
- redis['enable'] = true
-
- ## Disable all other services
- sidekiq['enable'] = false
- gitlab_workhorse['enable'] = false
- puma['enable'] = false
- postgresql['enable'] = false
- nginx['enable'] = false
- prometheus['enable'] = false
- alertmanager['enable'] = false
- pgbouncer_exporter['enable'] = false
- gitlab_exporter['enable'] = false
- gitaly['enable'] = false
-
- redis['bind'] = '0.0.0.0'
- redis['port'] = 6379
- redis['password'] = 'SECRET_PASSWORD_HERE'
-
- gitlab_rails['enable'] = false
- ```
-
-1. [Reconfigure Omnibus GitLab](../restart_gitlab.md#omnibus-gitlab-reconfigure) for the changes to take effect.
-1. Note the Redis node's IP address or hostname, port, and
- Redis password. These will be necessary when configuring the GitLab
- application servers later.
-1. [Enable Monitoring](#enable-monitoring)
-
-Advanced configuration options are supported and can be added if
-needed.
-
-Continue configuration of other components by going back to the
-[reference architectures](../reference_architectures/index.md#configure-gitlab-to-scale) page.
-
-### High Availability with Omnibus GitLab **(PREMIUM ONLY)**
-
-> Experimental Redis Sentinel support was [introduced in GitLab 8.11](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/1877).
-Starting with 8.14, Redis Sentinel is no longer experimental.
-If you've used it with versions `< 8.14` before, please check the updated
-documentation here.
-
-High Availability with [Redis](https://redis.io/) is possible using a **Master** x **Replica**
-topology with a [Redis Sentinel](https://redis.io/topics/sentinel) service to watch and automatically
-start the failover procedure.
-
-You can choose to install and manage Redis and Sentinel yourself, use
-a hosted cloud solution or you can use the one that comes bundled with
-Omnibus GitLab packages.
-
-> **Notes:**
->
-> - Redis requires authentication for High Availability. See
-> [Redis Security](https://redis.io/topics/security) documentation for more
-> information. We recommend using a combination of a Redis password and tight
-> firewall rules to secure your Redis service.
-> - You are highly encouraged to read the [Redis Sentinel](https://redis.io/topics/sentinel) documentation
-> before configuring Redis HA with GitLab to fully understand the topology and
-> architecture.
-> - This is the documentation for the Omnibus GitLab packages. For installations
-> from source, follow the [Redis HA source installation](redis_source.md) guide.
-> - Redis Sentinel daemon is bundled with Omnibus GitLab Enterprise Edition only.
-> For configuring Sentinel with the Omnibus GitLab Community Edition and
-> installations from source, read the
-> [Available configuration setups](#available-configuration-setups) section
-> below.
-
-## Overview
-
-Before diving into the details of setting up Redis and Redis Sentinel for HA,
-make sure you read this Overview section to better understand how the components
-are tied together.
-
-You need at least `3` independent machines: physical, or VMs running into
-distinct physical machines. It is essential that all master and replica Redis
-instances run in different machines. If you fail to provision the machines in
-that specific way, any issue with the shared environment can bring your entire
-setup down.
-
-It is OK to run a Sentinel alongside of a master or replica Redis instance.
-There should be no more than one Sentinel on the same machine though.
-
-You also need to take into consideration the underlying network topology,
-making sure you have redundant connectivity between Redis / Sentinel and
-GitLab instances, otherwise the networks will become a single point of
-failure.
-
-Make sure that you read this document once as a whole before configuring the
-components below.
-
-> **Notes:**
->
-> - Starting with GitLab `8.11`, you can configure a list of Redis Sentinel
-> servers that will monitor a group of Redis servers to provide failover support.
-> - Starting with GitLab `8.14`, the Omnibus GitLab Enterprise Edition package
-> comes with Redis Sentinel daemon built-in.
-
-High Availability with Redis requires a few things:
-
-- Multiple Redis instances
-- Run Redis in a **Master** x **Replica** topology
-- Multiple Sentinel instances
-- Application support and visibility to all Sentinel and Redis instances
-
-Redis Sentinel can handle the most important tasks in an HA environment and that's
-to help keep servers online with minimal to no downtime. Redis Sentinel:
-
-- Monitors **Master** and **Replicas** instances to see if they are available
-- Promotes a **Replica** to **Master** when the **Master** fails
-- Demotes a **Master** to **Replica** when the failed **Master** comes back online
- (to prevent data-partitioning)
-- Can be queried by the application to always connect to the current **Master**
- server
-
-When a **Master** fails to respond, it's the application's responsibility
-(in our case GitLab) to handle timeout and reconnect (querying a **Sentinel**
-for a new **Master**).
-
-To get a better understanding on how to correctly set up Sentinel, please read
-the [Redis Sentinel documentation](https://redis.io/topics/sentinel) first, as
-failing to configure it correctly can lead to data loss or can bring your
-whole cluster down, invalidating the failover effort.
-
-### Recommended setup
-
-For a minimal setup, you will install the Omnibus GitLab package in `3`
-**independent** machines, both with **Redis** and **Sentinel**:
-
-- Redis Master + Sentinel
-- Redis Replica + Sentinel
-- Redis Replica + Sentinel
-
-If you are not sure or don't understand why and where the amount of nodes come
-from, read [Redis setup overview](#redis-setup-overview) and
-[Sentinel setup overview](#sentinel-setup-overview).
-
-For a recommended setup that can resist more failures, you will install
-the Omnibus GitLab package in `5` **independent** machines, both with
-**Redis** and **Sentinel**:
-
-- Redis Master + Sentinel
-- Redis Replica + Sentinel
-- Redis Replica + Sentinel
-- Redis Replica + Sentinel
-- Redis Replica + Sentinel
-
-### Redis setup overview
-
-You must have at least `3` Redis servers: `1` Master, `2` Replicas, and they
-need to each be on independent machines (see explanation above).
-
-You can have additional Redis nodes, that will help survive a situation
-where more nodes goes down. Whenever there is only `2` nodes online, a failover
-will not be initiated.
-
-As an example, if you have `6` Redis nodes, a maximum of `3` can be
-simultaneously down.
-
-Please note that there are different requirements for Sentinel nodes.
-If you host them in the same Redis machines, you may need to take
-that restrictions into consideration when calculating the amount of
-nodes to be provisioned. See [Sentinel setup overview](#sentinel-setup-overview)
-documentation for more information.
-
-All Redis nodes should be configured the same way and with similar server specs, as
-in a failover situation, any **Replica** can be promoted as the new **Master** by
-the Sentinel servers.
-
-The replication requires authentication, so you need to define a password to
-protect all Redis nodes and the Sentinels. They will all share the same
-password, and all instances must be able to talk to
-each other over the network.
-
-### Sentinel setup overview
-
-Sentinels watch both other Sentinels and Redis nodes. Whenever a Sentinel
-detects that a Redis node is not responding, it will announce that to the
-other Sentinels. They have to reach the **quorum**, that is the minimum amount
-of Sentinels that agrees a node is down, in order to be able to start a failover.
-
-Whenever the **quorum** is met, the **majority** of all known Sentinel nodes
-need to be available and reachable, so that they can elect the Sentinel **leader**
-who will take all the decisions to restore the service availability by:
-
-- Promoting a new **Master**
-- Reconfiguring the other **Replicas** and make them point to the new **Master**
-- Announce the new **Master** to every other Sentinel peer
-- Reconfigure the old **Master** and demote to **Replica** when it comes back online
-
-You must have at least `3` Redis Sentinel servers, and they need to
-be each in an independent machine (that are believed to fail independently),
-ideally in different geographical areas.
-
-You can configure them in the same machines where you've configured the other
-Redis servers, but understand that if a whole node goes down, you loose both
-a Sentinel and a Redis instance.
-
-The number of sentinels should ideally always be an **odd** number, for the
-consensus algorithm to be effective in the case of a failure.
-
-In a `3` nodes topology, you can only afford `1` Sentinel node going down.
-Whenever the **majority** of the Sentinels goes down, the network partition
-protection prevents destructive actions and a failover **will not be started**.
-
-Here are some examples:
-
-- With `5` or `6` sentinels, a maximum of `2` can go down for a failover begin.
-- With `7` sentinels, a maximum of `3` nodes can go down.
-
-The **Leader** election can sometimes fail the voting round when **consensus**
-is not achieved (see the odd number of nodes requirement above). In that case,
-a new attempt will be made after the amount of time defined in
-`sentinel['failover_timeout']` (in milliseconds).
-
->**Note:**
-We will see where `sentinel['failover_timeout']` is defined later.
-
-The `failover_timeout` variable has a lot of different use cases. According to
-the official documentation:
-
-- The time needed to re-start a failover after a previous failover was
- already tried against the same master by a given Sentinel, is two
- times the failover timeout.
-
-- The time needed for a replica replicating to a wrong master according
- to a Sentinel current configuration, to be forced to replicate
- with the right master, is exactly the failover timeout (counting since
- the moment a Sentinel detected the misconfiguration).
-
-- The time needed to cancel a failover that is already in progress but
- did not produced any configuration change (REPLICAOF NO ONE yet not
- acknowledged by the promoted replica).
-
-- The maximum time a failover in progress waits for all the replicas to be
- reconfigured as replicas of the new master. However even after this time
- the replicas will be reconfigured by the Sentinels anyway, but not with
- the exact parallel-syncs progression as specified.
-
-### Available configuration setups
-
-Based on your infrastructure setup and how you have installed GitLab, there are
-multiple ways to configure Redis HA. Omnibus GitLab packages have Redis and/or
-Redis Sentinel bundled with them so you only need to focus on configuration.
-Pick the one that suits your needs.
-
-- [Installations from source](../../install/installation.md): You need to install Redis and Sentinel
- yourself. Use the [Redis HA installation from source](redis_source.md)
- documentation.
-- [Omnibus GitLab **Community Edition** (CE) package](https://about.gitlab.com/install/?version=ce): Redis is bundled, so you
- can use the package with only the Redis service enabled as described in steps
- 1 and 2 of this document (works for both master and replica setups). To install
- and configure Sentinel, jump directly to the Sentinel section in the
- [Redis HA installation from source](redis_source.md#step-3-configuring-the-redis-sentinel-instances) documentation.
-- [Omnibus GitLab **Enterprise Edition** (EE) package](https://about.gitlab.com/install/?version=ee): Both Redis and Sentinel
- are bundled in the package, so you can use the EE package to set up the whole
- Redis HA infrastructure (master, replica and Sentinel) which is described in
- this document.
-- If you have installed GitLab using the Omnibus GitLab packages (CE or EE),
- but you want to use your own external Redis server, follow steps 1-3 in the
- [Redis HA installation from source](redis_source.md) documentation, then go
- straight to step 4 in this guide to
- [set up the GitLab application](#step-4-configuring-the-gitlab-application).
-
-## Configuring Redis HA
-
-This is the section where we install and set up the new Redis instances.
-
-> **Notes:**
->
-> - We assume that you have installed GitLab and all HA components from scratch. If you
-> already have it installed and running, read how to
-> [switch from a single-machine installation to Redis HA](#switching-from-an-existing-single-machine-installation-to-redis-ha).
-> - Redis nodes (both master and replica) will need the same password defined in
-> `redis['password']`. At any time during a failover the Sentinels can
-> reconfigure a node and change its status from master to replica and vice versa.
-
-### Prerequisites
-
-The prerequisites for a HA Redis setup are the following:
-
-1. Provision the minimum required number of instances as specified in the
- [recommended setup](#recommended-setup) section.
-1. We **Do not** recommend installing Redis or Redis Sentinel in the same machines your
- GitLab application is running on as this weakens your HA configuration. You can however opt in to install Redis
- and Sentinel in the same machine.
-1. All Redis nodes must be able to talk to each other and accept incoming
- connections over Redis (`6379`) and Sentinel (`26379`) ports (unless you
- change the default ones).
-1. The server that hosts the GitLab application must be able to access the
- Redis nodes.
-1. Protect the nodes from access from external networks ([Internet](https://gitlab.com/gitlab-org/gitlab-foss/uploads/c4cc8cd353604bd80315f9384035ff9e/The_Internet_IT_Crowd.png)), using
- firewall.
-
-### Step 1. Configuring the master Redis instance
-
-1. SSH into the **master** Redis server.
-1. [Download/install](https://about.gitlab.com/install/) the Omnibus GitLab
- package you want using **steps 1 and 2** from the GitLab downloads page.
- - Make sure you select the correct Omnibus package, with the same version
- and type (Community, Enterprise editions) of your current install.
- - Do not complete any other steps on the download page.
-
-1. Edit `/etc/gitlab/gitlab.rb` and add the contents:
-
- ```ruby
- # Specify server role as 'redis_master_role'
- roles ['redis_master_role']
-
- # IP address pointing to a local IP that the other machines can reach to.
- # You can also set bind to '0.0.0.0' which listen in all interfaces.
- # If you really need to bind to an external accessible IP, make
- # sure you add extra firewall rules to prevent unauthorized access.
- redis['bind'] = '10.0.0.1'
-
- # Define a port so Redis can listen for TCP requests which will allow other
- # machines to connect to it.
- redis['port'] = 6379
-
- # Set up password authentication for Redis (use the same password in all nodes).
- redis['password'] = 'redis-password-goes-here'
- ```
-
-1. Only the primary GitLab application server should handle migrations. To
- prevent database migrations from running on upgrade, add the following
- configuration to your `/etc/gitlab/gitlab.rb` file:
-
- ```ruby
- gitlab_rails['auto_migrate'] = false
- ```
-
-1. [Reconfigure Omnibus GitLab](../restart_gitlab.md#omnibus-gitlab-reconfigure) for the changes to take effect.
-
-> Note: You can specify multiple roles like sentinel and Redis as:
-> `roles ['redis_sentinel_role', 'redis_master_role']`. Read more about high
-> availability roles at <https://docs.gitlab.com/omnibus/roles/>.
-
-### Step 2. Configuring the replica Redis instances
-
-1. SSH into the **replica** Redis server.
-1. [Download/install](https://about.gitlab.com/install/) the Omnibus GitLab
- package you want using **steps 1 and 2** from the GitLab downloads page.
- - Make sure you select the correct Omnibus package, with the same version
- and type (Community, Enterprise editions) of your current install.
- - Do not complete any other steps on the download page.
-
-1. Edit `/etc/gitlab/gitlab.rb` and add the contents:
-
- ```ruby
- # Specify server role as 'redis_replica_role'
- roles ['redis_replica_role']
-
- # IP address pointing to a local IP that the other machines can reach to.
- # You can also set bind to '0.0.0.0' which listen in all interfaces.
- # If you really need to bind to an external accessible IP, make
- # sure you add extra firewall rules to prevent unauthorized access.
- redis['bind'] = '10.0.0.2'
-
- # Define a port so Redis can listen for TCP requests which will allow other
- # machines to connect to it.
- redis['port'] = 6379
-
- # The same password for Redis authentication you set up for the master node.
- redis['password'] = 'redis-password-goes-here'
-
- # The IP of the master Redis node.
- redis['master_ip'] = '10.0.0.1'
-
- # Port of master Redis server, uncomment to change to non default. Defaults
- # to `6379`.
- #redis['master_port'] = 6379
- ```
-
-1. To prevent reconfigure from running automatically on upgrade, run:
-
- ```shell
- sudo touch /etc/gitlab/skip-auto-reconfigure
- ```
-
-1. [Reconfigure Omnibus GitLab](../restart_gitlab.md#omnibus-gitlab-reconfigure) for the changes to take effect.
-1. Go through the steps again for all the other replica nodes.
-
-> Note: You can specify multiple roles like sentinel and Redis as:
-> `roles ['redis_sentinel_role', 'redis_replica_role']`. Read more about high
-> availability roles at <https://docs.gitlab.com/omnibus/roles/>.
-
----
-
-These values don't have to be changed again in `/etc/gitlab/gitlab.rb` after
-a failover, as the nodes will be managed by the Sentinels, and even after a
-`gitlab-ctl reconfigure`, they will get their configuration restored by
-the same Sentinels.
-
-### Step 3. Configuring the Redis Sentinel instances
-
->**Note:**
-Redis Sentinel is bundled with Omnibus GitLab Enterprise Edition only. The
-following section assumes you are using Omnibus GitLab Enterprise Edition.
-For the Omnibus Community Edition and installations from source, follow the
-[Redis HA source install](redis_source.md) guide.
-
-NOTE: **Note:** If you are using an external Redis Sentinel instance, be sure
-to exclude the `requirepass` parameter from the Sentinel
-configuration. This parameter will cause clients to report `NOAUTH
-Authentication required.`. [Redis Sentinel 3.2.x does not support
-password authentication](https://github.com/antirez/redis/issues/3279).
-
-Now that the Redis servers are all set up, let's configure the Sentinel
-servers.
-
-If you are not sure if your Redis servers are working and replicating
-correctly, please read the [Troubleshooting Replication](#troubleshooting-redis-replication)
-and fix it before proceeding with Sentinel setup.
-
-You must have at least `3` Redis Sentinel servers, and they need to
-be each in an independent machine. You can configure them in the same
-machines where you've configured the other Redis servers.
-
-With GitLab Enterprise Edition, you can use the Omnibus package to set up
-multiple machines with the Sentinel daemon.
-
----
-
-1. SSH into the server that will host Redis Sentinel.
-1. **You can omit this step if the Sentinels will be hosted in the same node as
- the other Redis instances.**
-
- [Download/install](https://about.gitlab.com/install/) the
- Omnibus GitLab Enterprise Edition package using **steps 1 and 2** from the
- GitLab downloads page.
- - Make sure you select the correct Omnibus package, with the same version
- the GitLab application is running.
- - Do not complete any other steps on the download page.
-
-1. Edit `/etc/gitlab/gitlab.rb` and add the contents (if you are installing the
- Sentinels in the same node as the other Redis instances, some values might
- be duplicate below):
-
- ```ruby
- roles ['redis_sentinel_role']
-
- # Must be the same in every sentinel node
- redis['master_name'] = 'gitlab-redis'
-
- # The same password for Redis authentication you set up for the master node.
- redis['master_password'] = 'redis-password-goes-here'
-
- # The IP of the master Redis node.
- redis['master_ip'] = '10.0.0.1'
-
- # Define a port so Redis can listen for TCP requests which will allow other
- # machines to connect to it.
- redis['port'] = 6379
-
- # Port of master Redis server, uncomment to change to non default. Defaults
- # to `6379`.
- #redis['master_port'] = 6379
-
- ## Configure Sentinel
- sentinel['bind'] = '10.0.0.1'
-
- # Port that Sentinel listens on, uncomment to change to non default. Defaults
- # to `26379`.
- # sentinel['port'] = 26379
-
- ## Quorum must reflect the amount of voting sentinels it take to start a failover.
- ## Value must NOT be greater then the amount of sentinels.
- ##
- ## The quorum can be used to tune Sentinel in two ways:
- ## 1. If a the quorum is set to a value smaller than the majority of Sentinels
- ## we deploy, we are basically making Sentinel more sensible to master failures,
- ## triggering a failover as soon as even just a minority of Sentinels is no longer
- ## able to talk with the master.
- ## 1. If a quorum is set to a value greater than the majority of Sentinels, we are
- ## making Sentinel able to failover only when there are a very large number (larger
- ## than majority) of well connected Sentinels which agree about the master being down.s
- sentinel['quorum'] = 2
-
- ## Consider unresponsive server down after x amount of ms.
- # sentinel['down_after_milliseconds'] = 10000
-
- ## Specifies the failover timeout in milliseconds. It is used in many ways:
- ##
- ## - The time needed to re-start a failover after a previous failover was
- ## already tried against the same master by a given Sentinel, is two
- ## times the failover timeout.
- ##
- ## - The time needed for a replica replicating to a wrong master according
- ## to a Sentinel current configuration, to be forced to replicate
- ## with the right master, is exactly the failover timeout (counting since
- ## the moment a Sentinel detected the misconfiguration).
- ##
- ## - The time needed to cancel a failover that is already in progress but
- ## did not produced any configuration change (REPLICAOF NO ONE yet not
- ## acknowledged by the promoted replica).
- ##
- ## - The maximum time a failover in progress waits for all the replica to be
- ## reconfigured as replicas of the new master. However even after this time
- ## the replicas will be reconfigured by the Sentinels anyway, but not with
- ## the exact parallel-syncs progression as specified.
- # sentinel['failover_timeout'] = 60000
- ```
-
-1. To prevent database migrations from running on upgrade, run:
-
- ```shell
- sudo touch /etc/gitlab/skip-auto-reconfigure
- ```
-
- Only the primary GitLab application server should handle migrations.
-
-1. [Reconfigure Omnibus GitLab](../restart_gitlab.md#omnibus-gitlab-reconfigure) for the changes to take effect.
-1. Go through the steps again for all the other Sentinel nodes.
-
-### Step 4. Configuring the GitLab application
-
-The final part is to inform the main GitLab application server of the Redis
-Sentinels servers and authentication credentials.
-
-You can enable or disable Sentinel support at any time in new or existing
-installations. From the GitLab application perspective, all it requires is
-the correct credentials for the Sentinel nodes.
-
-While it doesn't require a list of all Sentinel nodes, in case of a failure,
-it needs to access at least one of the listed.
-
->**Note:**
-The following steps should be performed in the [GitLab application server](gitlab.md)
-which ideally should not have Redis or Sentinels on it for a HA setup.
-
-1. SSH into the server where the GitLab application is installed.
-1. Edit `/etc/gitlab/gitlab.rb` and add/change the following lines:
-
- ```ruby
- ## Must be the same in every sentinel node
- redis['master_name'] = 'gitlab-redis'
-
- ## The same password for Redis authentication you set up for the master node.
- redis['master_password'] = 'redis-password-goes-here'
-
- ## A list of sentinels with `host` and `port`
- gitlab_rails['redis_sentinels'] = [
- {'host' => '10.0.0.1', 'port' => 26379},
- {'host' => '10.0.0.2', 'port' => 26379},
- {'host' => '10.0.0.3', 'port' => 26379}
- ]
- ```
-
-1. [Reconfigure Omnibus GitLab](../restart_gitlab.md#omnibus-gitlab-reconfigure) for the changes to take effect.
-
-## Switching from an existing single-machine installation to Redis HA
-
-If you already have a single-machine GitLab install running, you will need to
-replicate from this machine first, before de-activating the Redis instance
-inside it.
-
-Your single-machine install will be the initial **Master**, and the `3` others
-should be configured as **Replica** pointing to this machine.
-
-After replication catches up, you will need to stop services in the
-single-machine install, to rotate the **Master** to one of the new nodes.
-
-Make the required changes in configuration and restart the new nodes again.
-
-To disable Redis in the single install, edit `/etc/gitlab/gitlab.rb`:
-
-```ruby
-redis['enable'] = false
-```
-
-If you fail to replicate first, you may loose data (unprocessed background jobs).
-
-## Example of a minimal configuration with 1 master, 2 replicas and 3 Sentinels
-
->**Note:**
-Redis Sentinel is bundled with Omnibus GitLab Enterprise Edition only. For
-different setups, read the
-[available configuration setups](#available-configuration-setups) section.
-
-In this example we consider that all servers have an internal network
-interface with IPs in the `10.0.0.x` range, and that they can connect
-to each other using these IPs.
-
-In a real world usage, you would also set up firewall rules to prevent
-unauthorized access from other machines and block traffic from the
-outside (Internet).
-
-We will use the same `3` nodes with **Redis** + **Sentinel** topology
-discussed in [Redis setup overview](#redis-setup-overview) and
-[Sentinel setup overview](#sentinel-setup-overview) documentation.
-
-Here is a list and description of each **machine** and the assigned **IP**:
-
-- `10.0.0.1`: Redis Master + Sentinel 1
-- `10.0.0.2`: Redis Replica 1 + Sentinel 2
-- `10.0.0.3`: Redis Replica 2 + Sentinel 3
-- `10.0.0.4`: GitLab application
-
-Please note that after the initial configuration, if a failover is initiated
-by the Sentinel nodes, the Redis nodes will be reconfigured and the **Master**
-will change permanently (including in `redis.conf`) from one node to the other,
-until a new failover is initiated again.
-
-The same thing will happen with `sentinel.conf` that will be overridden after the
-initial execution, after any new sentinel node starts watching the **Master**,
-or a failover promotes a different **Master** node.
-
-### Example configuration for Redis master and Sentinel 1
-
-In `/etc/gitlab/gitlab.rb`:
-
-```ruby
-roles ['redis_sentinel_role', 'redis_master_role']
-redis['bind'] = '10.0.0.1'
-redis['port'] = 6379
-redis['password'] = 'redis-password-goes-here'
-redis['master_name'] = 'gitlab-redis' # must be the same in every sentinel node
-redis['master_password'] = 'redis-password-goes-here' # the same value defined in redis['password'] in the master instance
-redis['master_ip'] = '10.0.0.1' # ip of the initial master redis instance
-#redis['master_port'] = 6379 # port of the initial master redis instance, uncomment to change to non default
-sentinel['bind'] = '10.0.0.1'
-# sentinel['port'] = 26379 # uncomment to change default port
-sentinel['quorum'] = 2
-# sentinel['down_after_milliseconds'] = 10000
-# sentinel['failover_timeout'] = 60000
-```
-
-[Reconfigure Omnibus GitLab](../restart_gitlab.md#omnibus-gitlab-reconfigure) for the changes to take effect.
-
-### Example configuration for Redis replica 1 and Sentinel 2
-
-In `/etc/gitlab/gitlab.rb`:
-
-```ruby
-roles ['redis_sentinel_role', 'redis_replica_role']
-redis['bind'] = '10.0.0.2'
-redis['port'] = 6379
-redis['password'] = 'redis-password-goes-here'
-redis['master_password'] = 'redis-password-goes-here'
-redis['master_ip'] = '10.0.0.1' # IP of master Redis server
-#redis['master_port'] = 6379 # Port of master Redis server, uncomment to change to non default
-redis['master_name'] = 'gitlab-redis' # must be the same in every sentinel node
-sentinel['bind'] = '10.0.0.2'
-# sentinel['port'] = 26379 # uncomment to change default port
-sentinel['quorum'] = 2
-# sentinel['down_after_milliseconds'] = 10000
-# sentinel['failover_timeout'] = 60000
-```
-
-[Reconfigure Omnibus GitLab](../restart_gitlab.md#omnibus-gitlab-reconfigure) for the changes to take effect.
-
-### Example configuration for Redis replica 2 and Sentinel 3
-
-In `/etc/gitlab/gitlab.rb`:
-
-```ruby
-roles ['redis_sentinel_role', 'redis_replica_role']
-redis['bind'] = '10.0.0.3'
-redis['port'] = 6379
-redis['password'] = 'redis-password-goes-here'
-redis['master_password'] = 'redis-password-goes-here'
-redis['master_ip'] = '10.0.0.1' # IP of master Redis server
-#redis['master_port'] = 6379 # Port of master Redis server, uncomment to change to non default
-redis['master_name'] = 'gitlab-redis' # must be the same in every sentinel node
-sentinel['bind'] = '10.0.0.3'
-# sentinel['port'] = 26379 # uncomment to change default port
-sentinel['quorum'] = 2
-# sentinel['down_after_milliseconds'] = 10000
-# sentinel['failover_timeout'] = 60000
-```
-
-[Reconfigure Omnibus GitLab](../restart_gitlab.md#omnibus-gitlab-reconfigure) for the changes to take effect.
-
-### Example configuration for the GitLab application
-
-In `/etc/gitlab/gitlab.rb`:
-
-```ruby
-redis['master_name'] = 'gitlab-redis'
-redis['master_password'] = 'redis-password-goes-here'
-gitlab_rails['redis_sentinels'] = [
- {'host' => '10.0.0.1', 'port' => 26379},
- {'host' => '10.0.0.2', 'port' => 26379},
- {'host' => '10.0.0.3', 'port' => 26379}
-]
-```
-
-[Reconfigure Omnibus GitLab](../restart_gitlab.md#omnibus-gitlab-reconfigure) for the changes to take effect.
-
-## Enable Monitoring
-
-> [Introduced](https://gitlab.com/gitlab-org/omnibus-gitlab/-/issues/3786) in GitLab 12.0.
-
-If you enable Monitoring, it must be enabled on **all** Redis servers.
-
-1. Make sure to collect [`CONSUL_SERVER_NODES`](../postgresql/replication_and_failover.md#consul-information), which are the IP addresses or DNS records of the Consul server nodes, for the next step. Note they are presented as `Y.Y.Y.Y consul1.gitlab.example.com Z.Z.Z.Z`
-
-1. Create/edit `/etc/gitlab/gitlab.rb` and add the following configuration:
-
- ```ruby
- # Enable service discovery for Prometheus
- consul['enable'] = true
- consul['monitoring_service_discovery'] = true
-
- # Replace placeholders
- # Y.Y.Y.Y consul1.gitlab.example.com Z.Z.Z.Z
- # with the addresses of the Consul server nodes
- consul['configuration'] = {
- retry_join: %w(Y.Y.Y.Y consul1.gitlab.example.com Z.Z.Z.Z),
- }
-
- # Set the network addresses that the exporters will listen on
- node_exporter['listen_address'] = '0.0.0.0:9100'
- redis_exporter['listen_address'] = '0.0.0.0:9121'
- ```
-
-1. Run `sudo gitlab-ctl reconfigure` to compile the configuration.
-
-## Advanced configuration
-
-Omnibus GitLab configures some things behind the curtains to make the sysadmins'
-lives easier. If you want to know what happens underneath keep reading.
-
-### Running multiple Redis clusters
-
-GitLab supports running [separate Redis clusters for different persistent
-classes](https://docs.gitlab.com/omnibus/settings/redis.html#running-with-multiple-redis-instances):
-cache, queues, and shared_state. To make this work with Sentinel:
-
-1. Set the appropriate variable in `/etc/gitlab/gitlab.rb` for each instance you are using:
-
- ```ruby
- gitlab_rails['redis_cache_instance'] = REDIS_CACHE_URL
- gitlab_rails['redis_queues_instance'] = REDIS_QUEUES_URL
- gitlab_rails['redis_shared_state_instance'] = REDIS_SHARED_STATE_URL
- ```
-
- **Note**: Redis URLs should be in the format: `redis://:PASSWORD@SENTINEL_MASTER_NAME`
-
- 1. PASSWORD is the plaintext password for the Redis instance
- 1. SENTINEL_MASTER_NAME is the Sentinel master name (e.g. `gitlab-redis-cache`)
-
-1. Include an array of hashes with host/port combinations, such as the following:
-
- ```ruby
- gitlab_rails['redis_cache_sentinels'] = [
- { host: REDIS_CACHE_SENTINEL_HOST, port: PORT1 },
- { host: REDIS_CACHE_SENTINEL_HOST2, port: PORT2 }
- ]
- gitlab_rails['redis_queues_sentinels'] = [
- { host: REDIS_QUEUES_SENTINEL_HOST, port: PORT1 },
- { host: REDIS_QUEUES_SENTINEL_HOST2, port: PORT2 }
- ]
- gitlab_rails['redis_shared_state_sentinels'] = [
- { host: SHARED_STATE_SENTINEL_HOST, port: PORT1 },
- { host: SHARED_STATE_SENTINEL_HOST2, port: PORT2 }
- ]
- ```
-
-1. Note that for each persistence class, GitLab will default to using the
- configuration specified in `gitlab_rails['redis_sentinels']` unless
- overridden by the settings above.
-1. Be sure to include BOTH configuration options for each persistent classes. For example,
- if you choose to configure a cache instance, you must specify both `gitlab_rails['redis_cache_instance']`
- and `gitlab_rails['redis_cache_sentinels']` for GitLab to generate the proper configuration files.
-1. Run `gitlab-ctl reconfigure`
-
-### Control running services
-
-In the previous example, we've used `redis_sentinel_role` and
-`redis_master_role` which simplifies the amount of configuration changes.
-
-If you want more control, here is what each one sets for you automatically
-when enabled:
-
-```ruby
-## Redis Sentinel Role
-redis_sentinel_role['enable'] = true
-
-# When Sentinel Role is enabled, the following services are also enabled
-sentinel['enable'] = true
-
-# The following services are disabled
-redis['enable'] = false
-bootstrap['enable'] = false
-nginx['enable'] = false
-postgresql['enable'] = false
-gitlab_rails['enable'] = false
-mailroom['enable'] = false
-
--------
-
-## Redis master/replica Role
-redis_master_role['enable'] = true # enable only one of them
-redis_replica_role['enable'] = true # enable only one of them
-
-# When Redis Master or Replica role are enabled, the following services are
-# enabled/disabled. Note that if Redis and Sentinel roles are combined, both
-# services will be enabled.
-
-# The following services are disabled
-sentinel['enable'] = false
-bootstrap['enable'] = false
-nginx['enable'] = false
-postgresql['enable'] = false
-gitlab_rails['enable'] = false
-mailroom['enable'] = false
-
-# For Redis Replica role, also change this setting from default 'true' to 'false':
-redis['master'] = false
-```
-
-You can find the relevant attributes defined in [`gitlab_rails.rb`](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/files/gitlab-cookbooks/gitlab/libraries/gitlab_rails.rb).
-
-## Troubleshooting
-
-There are a lot of moving parts that needs to be taken care carefully
-in order for the HA setup to work as expected.
-
-Before proceeding with the troubleshooting below, check your firewall rules:
-
-- Redis machines
- - Accept TCP connection in `6379`
- - Connect to the other Redis machines via TCP in `6379`
-- Sentinel machines
- - Accept TCP connection in `26379`
- - Connect to other Sentinel machines via TCP in `26379`
- - Connect to the Redis machines via TCP in `6379`
-
-### Troubleshooting Redis replication
-
-You can check if everything is correct by connecting to each server using
-`redis-cli` application, and sending the `info replication` command as below.
-
-```shell
-/opt/gitlab/embedded/bin/redis-cli -h <redis-host-or-ip> -a '<redis-password>' info replication
-```
-
-When connected to a `master` Redis, you will see the number of connected
-`replicas`, and a list of each with connection details:
-
-```plaintext
-# Replication
-role:master
-connected_replicas:1
-replica0:ip=10.133.5.21,port=6379,state=online,offset=208037514,lag=1
-master_repl_offset:208037658
-repl_backlog_active:1
-repl_backlog_size:1048576
-repl_backlog_first_byte_offset:206989083
-repl_backlog_histlen:1048576
-```
-
-When it's a `replica`, you will see details of the master connection and if
-its `up` or `down`:
-
-```plaintext
-# Replication
-role:replica
-master_host:10.133.1.58
-master_port:6379
-master_link_status:up
-master_last_io_seconds_ago:1
-master_sync_in_progress:0
-replica_repl_offset:208096498
-replica_priority:100
-replica_read_only:1
-connected_replicas:0
-master_repl_offset:0
-repl_backlog_active:0
-repl_backlog_size:1048576
-repl_backlog_first_byte_offset:0
-repl_backlog_histlen:0
-```
-
-### Troubleshooting Sentinel
-
-If you get an error like: `Redis::CannotConnectError: No sentinels available.`,
-there may be something wrong with your configuration files or it can be related
-to [this issue](https://github.com/redis/redis-rb/issues/531).
-
-You must make sure you are defining the same value in `redis['master_name']`
-and `redis['master_pasword']` as you defined for your sentinel node.
-
-The way the Redis connector `redis-rb` works with sentinel is a bit
-non-intuitive. We try to hide the complexity in omnibus, but it still requires
-a few extra configurations.
-
----
-
-To make sure your configuration is correct:
-
-1. SSH into your GitLab application server
-1. Enter the Rails console:
-
- ```shell
- # For Omnibus installations
- sudo gitlab-rails console
-
- # For source installations
- sudo -u git rails console -e production
- ```
-
-1. Run in the console:
-
- ```ruby
- redis = Redis.new(Gitlab::Redis::SharedState.params)
- redis.info
- ```
-
- Keep this screen open and try to simulate a failover below.
-
-1. To simulate a failover on master Redis, SSH into the Redis server and run:
-
- ```shell
- # port must match your master redis port, and the sleep time must be a few seconds bigger than defined one
- redis-cli -h localhost -p 6379 DEBUG sleep 20
- ```
-
-1. Then back in the Rails console from the first step, run:
-
- ```ruby
- redis.info
- ```
-
- You should see a different port after a few seconds delay
- (the failover/reconnect time).
-
-## Changelog
-
-Changes to Redis HA over time.
-
-**8.14**
-
-- Redis Sentinel support is production-ready and bundled in the Omnibus GitLab
- Enterprise Edition package
-- Documentation restructure for better readability
-
-**8.11**
-
-- Experimental Redis Sentinel support was added
-
-## Further reading
-
-Read more on High Availability:
-
-1. [High Availability Overview](README.md)
-1. [Configure the database](../postgresql/replication_and_failover.md)
-1. [Configure NFS](nfs.md)
-1. [Configure the GitLab application servers](gitlab.md)
-1. [Configure the load balancers](load_balancer.md)
+This document was moved to [another location](../redis/index.md).
diff --git a/doc/administration/high_availability/redis_source.md b/doc/administration/high_availability/redis_source.md
index 97be480bc3b..75496638979 100644
--- a/doc/administration/high_availability/redis_source.md
+++ b/doc/administration/high_availability/redis_source.md
@@ -1,371 +1,5 @@
---
-type: reference
+redirect_to: ../redis/replication_and_failover_external.md
---
-# Configuring non-Omnibus Redis for GitLab HA
-
-This is the documentation for configuring a Highly Available Redis setup when
-you have installed Redis all by yourself and not using the bundled one that
-comes with the Omnibus packages.
-
-Note also that you may elect to override all references to
-`/home/git/gitlab/config/resque.yml` in accordance with the advanced Redis
-settings outlined in
-[Configuration Files Documentation](https://gitlab.com/gitlab-org/gitlab/blob/master/config/README.md).
-
-We cannot stress enough the importance of reading the
-[Overview section](redis.md#overview) of the Omnibus Redis HA as it provides
-some invaluable information to the configuration of Redis. Please proceed to
-read it before going forward with this guide.
-
-We also highly recommend that you use the Omnibus GitLab packages, as we
-optimize them specifically for GitLab, and we will take care of upgrading Redis
-to the latest supported version.
-
-If you're not sure whether this guide is for you, please refer to
-[Available configuration setups](redis.md#available-configuration-setups) in
-the Omnibus Redis HA documentation.
-
-## Configuring your own Redis server
-
-This is the section where we install and set up the new Redis instances.
-
-### Prerequisites
-
-- All Redis servers in this guide must be configured to use a TCP connection
- instead of a socket. To configure Redis to use TCP connections you need to
- define both `bind` and `port` in the Redis config file. You can bind to all
- interfaces (`0.0.0.0`) or specify the IP of the desired interface
- (e.g., one from an internal network).
-- Since Redis 3.2, you must define a password to receive external connections
- (`requirepass`).
-- If you are using Redis with Sentinel, you will also need to define the same
- password for the replica password definition (`masterauth`) in the same instance.
-
-In addition, read the prerequisites as described in the
-[Omnibus Redis HA document](redis.md#prerequisites) since they provide some
-valuable information for the general setup.
-
-### Step 1. Configuring the master Redis instance
-
-Assuming that the Redis master instance IP is `10.0.0.1`:
-
-1. [Install Redis](../../install/installation.md#7-redis).
-1. Edit `/etc/redis/redis.conf`:
-
- ```conf
- ## Define a `bind` address pointing to a local IP that your other machines
- ## can reach you. If you really need to bind to an external accessible IP, make
- ## sure you add extra firewall rules to prevent unauthorized access:
- bind 10.0.0.1
-
- ## Define a `port` to force redis to listen on TCP so other machines can
- ## connect to it (default port is `6379`).
- port 6379
-
- ## Set up password authentication (use the same password in all nodes).
- ## The password should be defined equal for both `requirepass` and `masterauth`
- ## when setting up Redis to use with Sentinel.
- requirepass redis-password-goes-here
- masterauth redis-password-goes-here
- ```
-
-1. Restart the Redis service for the changes to take effect.
-
-### Step 2. Configuring the replica Redis instances
-
-Assuming that the Redis replica instance IP is `10.0.0.2`:
-
-1. [Install Redis](../../install/installation.md#7-redis).
-1. Edit `/etc/redis/redis.conf`:
-
- ```conf
- ## Define a `bind` address pointing to a local IP that your other machines
- ## can reach you. If you really need to bind to an external accessible IP, make
- ## sure you add extra firewall rules to prevent unauthorized access:
- bind 10.0.0.2
-
- ## Define a `port` to force redis to listen on TCP so other machines can
- ## connect to it (default port is `6379`).
- port 6379
-
- ## Set up password authentication (use the same password in all nodes).
- ## The password should be defined equal for both `requirepass` and `masterauth`
- ## when setting up Redis to use with Sentinel.
- requirepass redis-password-goes-here
- masterauth redis-password-goes-here
-
- ## Define `replicaof` pointing to the Redis master instance with IP and port.
- replicaof 10.0.0.1 6379
- ```
-
-1. Restart the Redis service for the changes to take effect.
-1. Go through the steps again for all the other replica nodes.
-
-### Step 3. Configuring the Redis Sentinel instances
-
-Sentinel is a special type of Redis server. It inherits most of the basic
-configuration options you can define in `redis.conf`, with specific ones
-starting with `sentinel` prefix.
-
-Assuming that the Redis Sentinel is installed on the same instance as Redis
-master with IP `10.0.0.1` (some settings might overlap with the master):
-
-1. [Install Redis Sentinel](https://redis.io/topics/sentinel)
-1. Edit `/etc/redis/sentinel.conf`:
-
- ```conf
- ## Define a `bind` address pointing to a local IP that your other machines
- ## can reach you. If you really need to bind to an external accessible IP, make
- ## sure you add extra firewall rules to prevent unauthorized access:
- bind 10.0.0.1
-
- ## Define a `port` to force Sentinel to listen on TCP so other machines can
- ## connect to it (default port is `6379`).
- port 26379
-
- ## Set up password authentication (use the same password in all nodes).
- ## The password should be defined equal for both `requirepass` and `masterauth`
- ## when setting up Redis to use with Sentinel.
- requirepass redis-password-goes-here
- masterauth redis-password-goes-here
-
- ## Define with `sentinel auth-pass` the same shared password you have
- ## defined for both Redis master and replicas instances.
- sentinel auth-pass gitlab-redis redis-password-goes-here
-
- ## Define with `sentinel monitor` the IP and port of the Redis
- ## master node, and the quorum required to start a failover.
- sentinel monitor gitlab-redis 10.0.0.1 6379 2
-
- ## Define with `sentinel down-after-milliseconds` the time in `ms`
- ## that an unresponsive server will be considered down.
- sentinel down-after-milliseconds gitlab-redis 10000
-
- ## Define a value for `sentinel failover_timeout` in `ms`. This has multiple
- ## meanings:
- ##
- ## * The time needed to re-start a failover after a previous failover was
- ## already tried against the same master by a given Sentinel, is two
- ## times the failover timeout.
- ##
- ## * The time needed for a replica replicating to a wrong master according
- ## to a Sentinel current configuration, to be forced to replicate
- ## with the right master, is exactly the failover timeout (counting since
- ## the moment a Sentinel detected the misconfiguration).
- ##
- ## * The time needed to cancel a failover that is already in progress but
- ## did not produced any configuration change (REPLICAOF NO ONE yet not
- ## acknowledged by the promoted replica).
- ##
- ## * The maximum time a failover in progress waits for all the replicas to be
- ## reconfigured as replicas of the new master. However even after this time
- ## the replicas will be reconfigured by the Sentinels anyway, but not with
- ## the exact parallel-syncs progression as specified.
- sentinel failover_timeout 30000
- ```
-
-1. Restart the Redis service for the changes to take effect.
-1. Go through the steps again for all the other Sentinel nodes.
-
-### Step 4. Configuring the GitLab application
-
-You can enable or disable Sentinel support at any time in new or existing
-installations. From the GitLab application perspective, all it requires is
-the correct credentials for the Sentinel nodes.
-
-While it doesn't require a list of all Sentinel nodes, in case of a failure,
-it needs to access at least one of listed ones.
-
-The following steps should be performed in the [GitLab application server](gitlab.md)
-which ideally should not have Redis or Sentinels in the same machine for a HA
-setup:
-
-1. Edit `/home/git/gitlab/config/resque.yml` following the example in
- [resque.yml.example](https://gitlab.com/gitlab-org/gitlab/blob/master/config/resque.yml.example), and uncomment the Sentinel lines, pointing to
- the correct server credentials:
-
- ```yaml
- # resque.yaml
- production:
- url: redis://:redi-password-goes-here@gitlab-redis/
- sentinels:
- -
- host: 10.0.0.1
- port: 26379 # point to sentinel, not to redis port
- -
- host: 10.0.0.2
- port: 26379 # point to sentinel, not to redis port
- -
- host: 10.0.0.3
- port: 26379 # point to sentinel, not to redis port
- ```
-
-1. [Restart GitLab](../restart_gitlab.md#installations-from-source) for the changes to take effect.
-
-## Example of minimal configuration with 1 master, 2 replicas and 3 Sentinels
-
-In this example we consider that all servers have an internal network
-interface with IPs in the `10.0.0.x` range, and that they can connect
-to each other using these IPs.
-
-In a real world usage, you would also set up firewall rules to prevent
-unauthorized access from other machines, and block traffic from the
-outside ([Internet](https://gitlab.com/gitlab-org/gitlab-foss/uploads/c4cc8cd353604bd80315f9384035ff9e/The_Internet_IT_Crowd.png)).
-
-For this example, **Sentinel 1** will be configured in the same machine as the
-**Redis Master**, **Sentinel 2** and **Sentinel 3** in the same machines as the
-**Replica 1** and **Replica 2** respectively.
-
-Here is a list and description of each **machine** and the assigned **IP**:
-
-- `10.0.0.1`: Redis Master + Sentinel 1
-- `10.0.0.2`: Redis Replica 1 + Sentinel 2
-- `10.0.0.3`: Redis Replica 2 + Sentinel 3
-- `10.0.0.4`: GitLab application
-
-Please note that after the initial configuration, if a failover is initiated
-by the Sentinel nodes, the Redis nodes will be reconfigured and the **Master**
-will change permanently (including in `redis.conf`) from one node to the other,
-until a new failover is initiated again.
-
-The same thing will happen with `sentinel.conf` that will be overridden after the
-initial execution, after any new sentinel node starts watching the **Master**,
-or a failover promotes a different **Master** node.
-
-### Example configuration for Redis master and Sentinel 1
-
-1. In `/etc/redis/redis.conf`:
-
- ```conf
- bind 10.0.0.1
- port 6379
- requirepass redis-password-goes-here
- masterauth redis-password-goes-here
- ```
-
-1. In `/etc/redis/sentinel.conf`:
-
- ```conf
- bind 10.0.0.1
- port 26379
- sentinel auth-pass gitlab-redis redis-password-goes-here
- sentinel monitor gitlab-redis 10.0.0.1 6379 2
- sentinel down-after-milliseconds gitlab-redis 10000
- sentinel failover_timeout 30000
- ```
-
-1. Restart the Redis service for the changes to take effect.
-
-### Example configuration for Redis replica 1 and Sentinel 2
-
-1. In `/etc/redis/redis.conf`:
-
- ```conf
- bind 10.0.0.2
- port 6379
- requirepass redis-password-goes-here
- masterauth redis-password-goes-here
- replicaof 10.0.0.1 6379
- ```
-
-1. In `/etc/redis/sentinel.conf`:
-
- ```conf
- bind 10.0.0.2
- port 26379
- sentinel auth-pass gitlab-redis redis-password-goes-here
- sentinel monitor gitlab-redis 10.0.0.1 6379 2
- sentinel down-after-milliseconds gitlab-redis 10000
- sentinel failover_timeout 30000
- ```
-
-1. Restart the Redis service for the changes to take effect.
-
-### Example configuration for Redis replica 2 and Sentinel 3
-
-1. In `/etc/redis/redis.conf`:
-
- ```conf
- bind 10.0.0.3
- port 6379
- requirepass redis-password-goes-here
- masterauth redis-password-goes-here
- replicaof 10.0.0.1 6379
- ```
-
-1. In `/etc/redis/sentinel.conf`:
-
- ```conf
- bind 10.0.0.3
- port 26379
- sentinel auth-pass gitlab-redis redis-password-goes-here
- sentinel monitor gitlab-redis 10.0.0.1 6379 2
- sentinel down-after-milliseconds gitlab-redis 10000
- sentinel failover_timeout 30000
- ```
-
-1. Restart the Redis service for the changes to take effect.
-
-### Example configuration of the GitLab application
-
-1. Edit `/home/git/gitlab/config/resque.yml`:
-
- ```yaml
- production:
- url: redis://:redi-password-goes-here@gitlab-redis/
- sentinels:
- -
- host: 10.0.0.1
- port: 26379 # point to sentinel, not to redis port
- -
- host: 10.0.0.2
- port: 26379 # point to sentinel, not to redis port
- -
- host: 10.0.0.3
- port: 26379 # point to sentinel, not to redis port
- ```
-
-1. [Restart GitLab](../restart_gitlab.md#installations-from-source) for the changes to take effect.
-
-## Troubleshooting
-
-We have a more detailed [Troubleshooting](redis.md#troubleshooting) explained
-in the documentation for Omnibus GitLab installations. Here we will list only
-the things that are specific to a source installation.
-
-If you get an error in GitLab like `Redis::CannotConnectError: No sentinels available.`,
-there may be something wrong with your configuration files or it can be related
-to [this upstream issue](https://github.com/redis/redis-rb/issues/531).
-
-You must make sure that `resque.yml` and `sentinel.conf` are configured correctly,
-otherwise `redis-rb` will not work properly.
-
-The `master-group-name` (`gitlab-redis`) defined in (`sentinel.conf`)
-**must** be used as the hostname in GitLab (`resque.yml`):
-
-```conf
-# sentinel.conf:
-sentinel monitor gitlab-redis 10.0.0.1 6379 2
-sentinel down-after-milliseconds gitlab-redis 10000
-sentinel config-epoch gitlab-redis 0
-sentinel leader-epoch gitlab-redis 0
-```
-
-```yaml
-# resque.yaml
-production:
- url: redis://:myredispassword@gitlab-redis/
- sentinels:
- -
- host: 10.0.0.1
- port: 26379 # point to sentinel, not to redis port
- -
- host: 10.0.0.2
- port: 26379 # point to sentinel, not to redis port
- -
- host: 10.0.0.3
- port: 26379 # point to sentinel, not to redis port
-```
-
-When in doubt, please read [Redis Sentinel documentation](https://redis.io/topics/sentinel).
+This document was moved to [another location](../redis/replication_and_failover_external.md).
diff --git a/doc/administration/high_availability/sidekiq.md b/doc/administration/high_availability/sidekiq.md
index 493929dcf3b..98a9af64e5e 100644
--- a/doc/administration/high_availability/sidekiq.md
+++ b/doc/administration/high_availability/sidekiq.md
@@ -94,7 +94,8 @@ you want using steps 1 and 2 from the GitLab downloads page.
1. Run `gitlab-ctl reconfigure`.
-NOTE: **Note:** You will need to restart the Sidekiq nodes after an update has occurred and database
+NOTE: **Note:**
+You will need to restart the Sidekiq nodes after an update has occurred and database
migrations performed.
## Example configuration
diff --git a/doc/administration/img/repository_storages_admin_ui_v12_10.png b/doc/administration/img/repository_storages_admin_ui_v12_10.png
deleted file mode 100644
index e5ac09524b8..00000000000
--- a/doc/administration/img/repository_storages_admin_ui_v12_10.png
+++ /dev/null
Binary files differ
diff --git a/doc/administration/img/repository_storages_admin_ui_v13_1.png b/doc/administration/img/repository_storages_admin_ui_v13_1.png
new file mode 100644
index 00000000000..f8c13a35369
--- /dev/null
+++ b/doc/administration/img/repository_storages_admin_ui_v13_1.png
Binary files differ
diff --git a/doc/administration/incoming_email.md b/doc/administration/incoming_email.md
index e078a7c1098..36156c4a580 100644
--- a/doc/administration/incoming_email.md
+++ b/doc/administration/incoming_email.md
@@ -17,10 +17,15 @@ GitLab has several features based on receiving incoming emails:
allow GitLab users to create a new merge request by sending an email to a
user-specific email address.
- [Service Desk](../user/project/service_desk.md): provide e-mail support to
- your customers through GitLab. **(PREMIUM)**
+ your customers through GitLab.
## Requirements
+NOTE: **Note:**
+It is **not** recommended to use an email address that receives or will receive any
+messages not intended for the GitLab instance. Any incoming emails not intended
+for GitLab will receive a reject notice.
+
Handling incoming emails requires an [IMAP](https://en.wikipedia.org/wiki/Internet_Message_Access_Protocol)-enabled
email account. GitLab requires one of the following three strategies:
@@ -69,6 +74,11 @@ and [allowed less secure apps to access the account](https://support.google.com/
or [turn-on 2-step validation](https://support.google.com/accounts/answer/185839)
and use [an application password](https://support.google.com/mail/answer/185833).
+If you want to use Office 365, and two-factor authentication is enabled, make sure
+you're using an
+[app password](https://docs.microsoft.com/en-us/azure/active-directory/user-help/multi-factor-authentication-end-user-app-passwords)
+instead of the regular password for the mailbox.
+
To set up a basic Postfix mail server with IMAP access on Ubuntu, follow the
[Postfix setup documentation](reply_by_email_postfix_setup.md).
@@ -101,6 +111,16 @@ Alternatively, use a dedicated domain for GitLab email communications such as
See GitLab issue [#30366](https://gitlab.com/gitlab-org/gitlab-foss/-/issues/30366)
for a real-world example of this exploit.
+CAUTION:**Caution:**
+Be sure to use a mail server that has been configured to reduce
+spam.
+A Postfix mail server that is running on a default configuration, for example,
+can result in abuse. All messages received on the configured mailbox will be processed
+and messages that are not intended for the GitLab instance will receive a reject notice.
+If the sender's address is spoofed, the reject notice will be delivered to the spoofed
+`FROM` address, which can cause the mail server's IP or domain to appear on a block
+list.
+
### Omnibus package installations
1. Find the `incoming_email` section in `/etc/gitlab/gitlab.rb`, enable the feature
diff --git a/doc/administration/instance_limits.md b/doc/administration/instance_limits.md
index 3d2f7380494..6d2fbd95d5e 100644
--- a/doc/administration/instance_limits.md
+++ b/doc/administration/instance_limits.md
@@ -8,6 +8,99 @@ GitLab, like most large applications, enforces limits within certain features to
minimum quality of performance. Allowing some features to be limitless could affect security,
performance, data, or could even exhaust the allocated resources for the application.
+## Rate limits
+
+Rate limits can be used to improve the security and durability of GitLab.
+
+For example, a simple script can make thousands of web requests per second. Whether malicious, apathetic, or just a bug, your application and infrastructure may not be able to cope with the load. Rate limits can help mitigate these types of attacks.
+
+Read more about [configuring rate limits](../security/rate_limits.md) in the Security documentation.
+
+### Issue creation
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/28129) in GitLab 12.10.
+
+This setting limits the request rate to the issue creation endpoint.
+
+Read more on [issue creation rate limits](../user/admin_area/settings/rate_limit_on_issues_creation.md).
+
+- **Default rate limit** - Disabled by default
+
+### By User or IP
+
+This setting limits the request rate per user or IP.
+
+Read more on [User and IP rate limits](../user/admin_area/settings/user_and_ip_rate_limits.md).
+
+- **Default rate limit** - Disabled by default
+
+### By raw endpoint
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/30829) in GitLab 12.2.
+
+This setting limits the request rate per endpoint.
+
+Read more on [raw endpoint rate limits](../user/admin_area/settings/rate_limits_on_raw_endpoints.md).
+
+- **Default rate limit** - 300 requests per project, per commit and per file path
+
+### By protected path
+
+This setting limits the request rate on specific paths.
+
+GitLab rate limits the following paths by default:
+
+```plaintext
+'/users/password',
+'/users/sign_in',
+'/api/#{API::API.version}/session.json',
+'/api/#{API::API.version}/session',
+'/users',
+'/users/confirmation',
+'/unsubscribes/',
+'/import/github/personal_access_token',
+'/admin/session'
+```
+
+Read more on [protected path rate limits](../user/admin_area/settings/protected_paths.md).
+
+- **Default rate limit** - After 10 requests, the client must wait 60 seconds before trying again
+
+### Import/Export
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/35728) in GitLab 13.2.
+
+This setting limits the import/export actions for groups and projects.
+
+| Limit | Default (per minute per user) |
+| ----- | ----------------------------- |
+| Project Import | 6 |
+| Project Export | 6 |
+| Project Export Download | 1 |
+| Group Import | 6 |
+| Group Export | 6 |
+| Group Export | Download | 1 |
+
+Read more on [import/export rate limits](../user/admin_area/settings/import_export_rate_limits.md).
+
+### Rack attack
+
+This method of rate limiting is cumbersome, but has some advantages. It allows
+throttling of specific paths, and is also integrated into Git and container
+registry requests.
+
+Read more on the [Rack Attack initializer](../security/rack_attack.md) method of setting rate limits.
+
+- **Default rate limit** - Disabled
+
+## Gitaly concurrency limit
+
+Clone traffic can put a large strain on your Gitaly service. To prevent such workloads from overwhelming your Gitaly server, you can set concurrency limits in Gitaly’s configuration file.
+
+Read more on [Gitaly concurrency limits](gitaly/index.md#limit-rpc-concurrency).
+
+- **Default rate limit** - Disabled
+
## Number of comments per issue, merge request or commit
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/22388) in GitLab 12.4.
@@ -77,13 +170,14 @@ To set this limit on a self-managed installation, run the following in the
# Plan.default.create_limits!
# For project webhooks
-Plan.default.limits.update!(project_hooks: 100)
+Plan.default.actual_limits.update!(project_hooks: 100)
# For group webhooks
-Plan.default.limits.update!(group_hooks: 100)
+Plan.default.actual_limits.update!(group_hooks: 100)
```
-NOTE: **Note:** Set the limit to `0` to disable it.
+NOTE: **Note:**
+Set the limit to `0` to disable it.
## Incoming emails from auto-responders
@@ -115,12 +209,13 @@ To set this limit on a self-managed installation, run the following in the
# If limits don't exist for the default plan, you can create one with:
# Plan.default.create_limits!
-Plan.default.limits.update!(offset_pagination_limit: 10000)
+Plan.default.actual_limits.update!(offset_pagination_limit: 10000)
```
- **Default offset pagination limit:** 50000
-NOTE: **Note:** Set the limit to `0` to disable it.
+NOTE: **Note:**
+Set the limit to `0` to disable it.
## CI/CD limits
@@ -149,10 +244,11 @@ To set this limit on a self-managed installation, run the following in the
# If limits don't exist for the default plan, you can create one with:
# Plan.default.create_limits!
-Plan.default.limits.update!(ci_active_jobs: 500)
+Plan.default.actual_limits.update!(ci_active_jobs: 500)
```
-NOTE: **Note:** Set the limit to `0` to disable it.
+NOTE: **Note:**
+Set the limit to `0` to disable it.
### Number of CI/CD subscriptions to a project
@@ -171,10 +267,11 @@ To set this limit on a self-managed installation, run the following in the
[GitLab Rails console](troubleshooting/debug.md#starting-a-rails-console-session):
```ruby
-Plan.default.limits.update!(ci_project_subscriptions: 500)
+Plan.default.actual_limits.update!(ci_project_subscriptions: 500)
```
-NOTE: **Note:** Set the limit to `0` to disable it.
+NOTE: **Note:**
+Set the limit to `0` to disable it.
### Number of pipeline schedules
@@ -196,7 +293,7 @@ To set this limit on a self-managed installation, run the following in the
[GitLab Rails console](troubleshooting/debug.md#starting-a-rails-console-session):
```ruby
-Plan.default.limits.update!(ci_pipeline_schedules: 100)
+Plan.default.actual_limits.update!(ci_pipeline_schedules: 100)
```
### Number of instance level variables
@@ -214,7 +311,7 @@ To update this limit to a new value on a self-managed installation, run the foll
[GitLab Rails console](troubleshooting/debug.md#starting-a-rails-console-session):
```ruby
-Plan.default.limits.update!(ci_instance_level_variables: 30)
+Plan.default.actual_limits.update!(ci_instance_level_variables: 30)
```
## Instance monitoring and metrics
@@ -243,6 +340,38 @@ Prometheus alert payloads sent to the `notify.json` endpoint are limited to 1 MB
Alert payloads sent to the `notify.json` endpoint are limited to 1 MB in size.
+### Metrics dashboard YAML files
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/34834) in GitLab 13.2.
+
+The memory occupied by a parsed metrics dashboard YAML file cannot exceed 1 MB.
+
+The maximum depth of each YAML file is limited to 100. The maximum depth of a YAML
+file is the amount of nesting of its most nested key. Each hash and array on the
+path of the most nested key counts towards its depth. For example, the depth of the
+most nested key in the following YAML is 7:
+
+```yaml
+dashboard: 'Test dashboard'
+links:
+- title: Link 1
+ url: https://gitlab.com
+panel_groups:
+- group: Group A
+ priority: 1
+ panels:
+ - title: "Super Chart A1"
+ type: "area-chart"
+ y_label: "y_label"
+ weight: 1
+ max_value: 1
+ metrics:
+ - id: metric_a1
+ query_range: 'query'
+ unit: unit
+ label: Legend Label
+```
+
## Environment data on Deploy Boards
[Deploy Boards](../user/project/deploy_boards.md) load information from Kubernetes about
@@ -274,7 +403,8 @@ characters and the rest will not be indexed and hence will not be searchable.
This limit can be configured for self-managed installations when [enabling
Elasticsearch](../integration/elasticsearch.md#enabling-elasticsearch).
-NOTE: **Note:** Set the limit to `0` to disable it.
+NOTE: **Note:**
+Set the limit to `0` to disable it.
## Wiki limits
@@ -284,6 +414,10 @@ NOTE: **Note:** Set the limit to `0` to disable it.
See the [documentation on Snippets settings](snippets/index.md).
+## Design Management limits
+
+See the [Design Management Limitations](../user/project/issues/design_management.md#limitations) section.
+
## Push Event Limits
### Webhooks and Project Services
diff --git a/doc/administration/integration/plantuml.md b/doc/administration/integration/plantuml.md
index cd25fbf351b..2a30eced7c4 100644
--- a/doc/administration/integration/plantuml.md
+++ b/doc/administration/integration/plantuml.md
@@ -130,7 +130,8 @@ that, login with an Admin account and do following:
- Check **Enable PlantUML** checkbox.
- Set the PlantUML instance as `https://gitlab.example.com/-/plantuml/`.
-NOTE: **Note:** If you are using a PlantUML server running v1.2020.9 and
+NOTE: **Note:**
+If you are using a PlantUML server running v1.2020.9 and
above (for example, [plantuml.com](https://plantuml.com)), set the `PLANTUML_ENCODING`
environment variable to enable the `deflate` compression. On Omnibus,
this can be done set in `/etc/gitlab.rb`:
diff --git a/doc/administration/integration/terminal.md b/doc/administration/integration/terminal.md
index bc5816ce120..fd9e09dc17a 100644
--- a/doc/administration/integration/terminal.md
+++ b/doc/administration/integration/terminal.md
@@ -45,7 +45,8 @@ detail below.
## Enabling and disabling terminal support
-NOTE: **Note:** AWS Elastic Load Balancers (ELBs) do not support web sockets.
+NOTE: **Note:**
+AWS Elastic Load Balancers (ELBs) do not support web sockets.
AWS Application Load Balancers (ALBs) must be used if you want web terminals
to work. See [AWS Elastic Load Balancing Product Comparison](https://aws.amazon.com/elasticloadbalancing/features/#compare)
for more information.
diff --git a/doc/administration/job_artifacts.md b/doc/administration/job_artifacts.md
index 0dbda67d39b..cb31a8934b1 100644
--- a/doc/administration/job_artifacts.md
+++ b/doc/administration/job_artifacts.md
@@ -106,6 +106,11 @@ If you configure GitLab to store CI logs and artifacts on object storage, you mu
#### Object Storage Settings
+NOTE: **Note:**
+In GitLab 13.2 and later, we recommend using the
+[consolidated object storage settings](object_storage.md#consolidated-object-storage-configuration).
+This section describes the earlier configuration format.
+
For source installations the following settings are nested under `artifacts:` and then `object_store:`. On Omnibus GitLab installs they are prefixed by `artifacts_object_store_`.
| Setting | Description | Default |
@@ -117,22 +122,9 @@ For source installations the following settings are nested under `artifacts:` an
| `proxy_download` | Set to true to enable proxying all files served. Option allows to reduce egress traffic as this allows clients to download directly from remote storage instead of proxying all data | `false` |
| `connection` | Various connection options described below | |
-##### S3 compatible connection settings
-
-The connection settings match those provided by [Fog](https://github.com/fog), and are as follows:
+#### Connection settings
-| Setting | Description | Default |
-|---------|-------------|---------|
-| `provider` | Always `AWS` for compatible hosts | AWS |
-| `aws_access_key_id` | AWS credentials, or compatible | |
-| `aws_secret_access_key` | AWS credentials, or compatible | |
-| `aws_signature_version` | AWS signature version to use. 2 or 4 are valid options. Digital Ocean Spaces and other providers may need 2. | 4 |
-| `enable_signature_v4_streaming` | Set to true to enable HTTP chunked transfers with [AWS v4 signatures](https://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-streaming.html). Oracle Cloud S3 needs this to be false | true |
-| `region` | AWS region | us-east-1 |
-| `host` | S3 compatible host for when not using AWS, for example `localhost` or `storage.example.com` | s3.amazonaws.com |
-| `endpoint` | Can be used when configuring an S3 compatible service such as [MinIO](https://min.io), by entering a URL such as `http://127.0.0.1:9000` | (optional) |
-| `path_style` | Set to true to use `host/bucket_name/object` style paths instead of `bucket_name.host/object`. Leave as false for AWS S3 | false |
-| `use_iam_profile` | Set to true to use IAM profile instead of access keys | false
+See [the available connection settings for different providers](object_storage.md#connection-settings).
**In Omnibus installations:**
@@ -172,7 +164,7 @@ _The artifacts are stored by default in
gitlab-rake gitlab:artifacts:migrate
```
-CAUTION: **CAUTION:**
+CAUTION: **Caution:**
JUnit test report artifact (`junit.xml.gz`) migration
[is not supported](https://gitlab.com/gitlab-org/gitlab/-/issues/27698)
by the `gitlab:artifacts:migrate` script.
@@ -205,24 +197,14 @@ _The artifacts are stored by default in
sudo -u git -H bundle exec rake gitlab:artifacts:migrate RAILS_ENV=production
```
-CAUTION: **CAUTION:**
+CAUTION: **Caution:**
JUnit test report artifact (`junit.xml.gz`) migration
[is not supported](https://gitlab.com/gitlab-org/gitlab/-/issues/27698)
by the `gitlab:artifacts:migrate` script.
-### OpenStack compatible connection settings
+### OpenStack example
-The connection settings match those provided by [Fog](https://github.com/fog), and are as follows:
-
-| Setting | Description | Default |
-|---------|-------------|---------|
-| `provider` | Always `OpenStack` for compatible hosts | OpenStack |
-| `openstack_username` | OpenStack username | |
-| `openstack_api_key` | OpenStack API key | |
-| `openstack_temp_url_key` | OpenStack key for generating temporary URLs | |
-| `openstack_auth_url` | OpenStack authentication endpoint | |
-| `openstack_region` | OpenStack region | |
-| `openstack_tenant_id` | OpenStack tenant ID |
+See [the available connection settings for OpenStack](object_storage.md#openstack-compatible-connection-settings).
**In Omnibus installations:**
@@ -453,7 +435,7 @@ the number you want.
#### Delete job artifacts from jobs completed before a specific date
-CAUTION: **CAUTION:**
+CAUTION: **Caution:**
These commands remove data permanently from the database and from disk. We
highly recommend running them only under the guidance of a Support Engineer, or
running them in a test environment with a backup of the instance ready to be
@@ -479,7 +461,7 @@ If you need to manually remove job artifacts associated with multiple jobs while
1. Delete job artifacts older than a specific date:
- NOTE: **NOTE:**
+ NOTE: **Note:**
This step will also erase artifacts that users have chosen to
["keep"](../ci/pipelines/job_artifacts.md#browsing-artifacts).
@@ -500,7 +482,7 @@ If you need to manually remove job artifacts associated with multiple jobs while
#### Delete job artifacts and logs from jobs completed before a specific date
-CAUTION: **CAUTION:**
+CAUTION: **Caution:**
These commands remove data permanently from the database and from disk. We
highly recommend running them only under the guidance of a Support Engineer, or
running them in a test environment with a backup of the instance ready to be
diff --git a/doc/administration/job_logs.md b/doc/administration/job_logs.md
index d3484536a76..4dba33b796a 100644
--- a/doc/administration/job_logs.md
+++ b/doc/administration/job_logs.md
@@ -73,7 +73,7 @@ job output in the UI will be empty.
For example, to delete all job logs older than 60 days, run the following from a shell in your GitLab instance:
-DANGER: **Warning:**
+DANGER: **Danger:**
This command will permanently delete the log files and is irreversible.
```shell
diff --git a/doc/administration/lfs/index.md b/doc/administration/lfs/index.md
index dd0e25b05f1..4a8151bd091 100644
--- a/doc/administration/lfs/index.md
+++ b/doc/administration/lfs/index.md
@@ -63,6 +63,11 @@ GitLab provides two different options for the uploading mechanism: "Direct uploa
[Read more about using object storage with GitLab](../object_storage.md).
+NOTE: **Note:**
+In GitLab 13.2 and later, we recommend using the
+[consolidated object storage settings](../object_storage.md#consolidated-object-storage-configuration).
+This section describes the earlier configuration format.
+
**Option 1. Direct upload**
1. User pushes an `lfs` file to the GitLab instance
@@ -86,54 +91,10 @@ The following general settings are supported.
| `proxy_download` | Set to true to enable proxying all files served. Option allows to reduce egress traffic as this allows clients to download directly from remote storage instead of proxying all data | `false` |
| `connection` | Various connection options described below | |
-The `connection` settings match those provided by [Fog](https://github.com/fog).
+See [the available connection settings for different providers](../object_storage.md#connection-settings).
Here is a configuration example with S3.
-| Setting | Description | example |
-|---------|-------------|---------|
-| `provider` | The provider name | AWS |
-| `aws_access_key_id` | AWS credentials, or compatible | `ABC123DEF456` |
-| `aws_secret_access_key` | AWS credentials, or compatible | `ABC123DEF456ABC123DEF456ABC123DEF456` |
-| `aws_signature_version` | AWS signature version to use. 2 or 4 are valid options. Digital Ocean Spaces and other providers may need 2. | 4 |
-| `enable_signature_v4_streaming` | Set to true to enable HTTP chunked transfers with [AWS v4 signatures](https://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-streaming.html). Oracle Cloud S3 needs this to be false | true |
-| `region` | AWS region | us-east-1 |
-| `host` | S3 compatible host for when not using AWS, e.g. `localhost` or `storage.example.com` | s3.amazonaws.com |
-| `endpoint` | Can be used when configuring an S3 compatible service such as [MinIO](https://min.io), by entering a URL such as `http://127.0.0.1:9000` | (optional) |
-| `path_style` | Set to true to use `host/bucket_name/object` style paths instead of `bucket_name.host/object`. Leave as false for AWS S3 | false |
-| `use_iam_profile` | Set to true to use IAM profile instead of access keys | false
-
-Here is a configuration example with GCS.
-
-| Setting | Description | example |
-|---------|-------------|---------|
-| `provider` | The provider name | `Google` |
-| `google_project` | GCP project name | `gcp-project-12345` |
-| `google_client_email` | The email address of the service account | `foo@gcp-project-12345.iam.gserviceaccount.com` |
-| `google_json_key_location` | The JSON key path | `/path/to/gcp-project-12345-abcde.json` |
-
-NOTE: **Note:**
-The service account must have permission to access the bucket.
-[See more](https://cloud.google.com/storage/docs/authentication)
-
-Here is a configuration example with Rackspace Cloud Files.
-
-| Setting | Description | example |
-|---------|-------------|---------|
-| `provider` | The provider name | `Rackspace` |
-| `rackspace_username` | The username of the Rackspace account with access to the container | `joe.smith` |
-| `rackspace_api_key` | The API key of the Rackspace account with access to the container | `ABC123DEF456ABC123DEF456ABC123DE` |
-| `rackspace_region` | The Rackspace storage region to use, a three letter code from the [list of service access endpoints](https://developer.rackspace.com/docs/cloud-files/v1/general-api-info/service-access/) | `iad` |
-| `rackspace_temp_url_key` | The private key you have set in the Rackspace API for temporary URLs. Read more [here](https://developer.rackspace.com/docs/cloud-files/v1/use-cases/public-access-to-your-cloud-files-account/#tempurl) | `ABC123DEF456ABC123DEF456ABC123DE` |
-
-NOTE: **Note:**
-Regardless of whether the container has public access enabled or disabled, Fog will
-use the TempURL method to grant access to LFS objects. If you see errors in logs referencing
-instantiating storage with a `temp-url-key`, ensure that you have set the key properly
-on the Rackspace API and in `gitlab.rb`. You can verify the value of the key Rackspace
-has set by sending a GET request with token header to the service access endpoint URL
-and comparing the output of the returned headers.
-
### Manual uploading to an object storage
There are two ways to manually do the same thing as automatic uploading (described above).
diff --git a/doc/administration/logs.md b/doc/administration/logs.md
index 7d7053a26db..3db9d32563e 100644
--- a/doc/administration/logs.md
+++ b/doc/administration/logs.md
@@ -26,7 +26,7 @@ GitLab, thanks to [Lograge](https://github.com/roidrage/lograge/). Note that
requests from the API are logged to a separate file in `api_json.log`.
Each line contains a JSON line that can be ingested by services like Elasticsearch and Splunk.
-Line breaks have been added to this example for legibility:
+Line breaks were added to examples for legibility:
```json
{
@@ -79,7 +79,7 @@ seconds:
User clone and fetch activity using HTTP transport appears in this log as `action: git_upload_pack`.
In addition, the log contains the originating IP address,
-(`remote_ip`),the user's ID (`user_id`), and username (`username`).
+(`remote_ip`), the user's ID (`user_id`), and username (`username`).
Some endpoints such as `/search` may make requests to Elasticsearch if using
[Advanced Global Search](../user/search/advanced_global_search.md). These will
@@ -112,7 +112,8 @@ The ActionCable connection or channel class is used as the `controller`.
}
```
-NOTE: **Note:** Starting with GitLab 12.5, if an error occurs, an
+NOTE: **Note:**
+Starting with GitLab 12.5, if an error occurs, an
`exception` field is included with `class`, `message`, and
`backtrace`. Previous versions included an `error` field instead of
`exception.class` and `exception.message`. For example:
@@ -227,7 +228,7 @@ It helps you see requests made directly to the API. For example:
}
```
-This entry shows an access to an internal endpoint to check whether an
+This entry shows an internal endpoint accessed to check whether an
associated SSH key can download the project in question via a `git fetch` or
`git clone`. In this example, we see:
@@ -320,7 +321,7 @@ packages or in `/home/git/gitlab/log/kubernetes.log` for
installations from source.
It logs information related to the Kubernetes Integration including errors
-during installing cluster applications on your GitLab managed Kubernetes
+during installing cluster applications on your managed Kubernetes
clusters.
Each line contains a JSON line that can be ingested by services like Elasticsearch and Splunk.
@@ -362,7 +363,7 @@ After 12.2, this file was renamed from `githost.log` to
`git_json.log` and stored in JSON format.
GitLab has to interact with Git repositories, but in some rare cases
-something can go wrong, and in this case you will know what exactly
+something can go wrong, and in this case you may need know what exactly
happened. This log file contains all failed requests from GitLab to Git
repositories. In the majority of cases this file will be useful for developers
only. For example:
@@ -473,8 +474,8 @@ This file lives in `/var/log/gitlab/gitlab-rails/sidekiq_client.log` for
Omnibus GitLab packages or in `/home/git/gitlab/log/sidekiq_client.log` for
installations from source.
-This file contains logging information about jobs before they are start
-being processed by Sidekiq, for example before being enqueued.
+This file contains logging information about jobs before Sidekiq starts
+processing them, such as before being enqueued.
This log file follows the same structure as
[`sidekiq.log`](#sidekiqlog), so it will be structured as JSON if
@@ -571,32 +572,45 @@ User clone/fetch activity using SSH transport appears in this log as `executing
## Gitaly Logs
-This file lives in `/var/log/gitlab/gitaly/current` and is produced by [runit](http://smarden.org/runit/). `runit` is packaged with Omnibus and a brief explanation of its purpose is available [in the omnibus documentation](https://docs.gitlab.com/omnibus/architecture/#runit). [Log files are rotated](http://smarden.org/runit/svlogd.8.html), renamed in Unix timestamp format and `gzip`-compressed (e.g. `@1584057562.s`).
+This file lives in `/var/log/gitlab/gitaly/current` and is produced by [runit](http://smarden.org/runit/). `runit` is packaged with Omnibus GitLab and a brief explanation of its purpose is available [in the Omnibus GitLab documentation](https://docs.gitlab.com/omnibus/architecture/#runit). [Log files are rotated](http://smarden.org/runit/svlogd.8.html), renamed in Unix timestamp format, and `gzip`-compressed (like `@1584057562.s`).
### `grpc.log`
This file lives in `/var/log/gitlab/gitlab-rails/grpc.log` for Omnibus GitLab packages. Native [gRPC](https://grpc.io/) logging used by Gitaly.
-## `puma_stderr.log` & `puma_stdout.log`
+## Puma Logs
-This file lives in `/var/log/gitlab/puma/puma_stderr.log` and `/var/log/gitlab/puma/puma_stdout.log` for
-Omnibus GitLab packages or in `/home/git/gitlab/log/puma_stderr.log` and `/home/git/gitlab/log/puma_stdout.log`
-for installations from source.
+### `puma_stdout.log`
+
+This file lives in `/var/log/gitlab/puma/puma_stdout.log` for
+Omnibus GitLab packages, and `/home/git/gitlab/log/puma_stdout.log` for
+installations from source.
+
+### `puma_stderr.log`
+
+This file lives in `/var/log/gitlab/puma/puma_stderr.log` for
+Omnibus GitLab packages, or in `/home/git/gitlab/log/puma_stderr.log` for
+installations from source.
-## `unicorn_stderr.log` & `unicorn_stdout.log`
+## Unicorn Logs
NOTE: **Note:**
Starting with GitLab 13.0, Puma is the default web server used in GitLab
all-in-one package based installations as well as GitLab Helm chart deployments.
-This file lives in `/var/log/gitlab/unicorn/unicorn_stderr.log` and `/var/log/gitlab/unicorn/unicorn_stdout.log` for
-Omnibus GitLab packages or in `/home/git/gitlab/log/unicorn_stderr.log` and `/home/git/gitlab/log/unicorn_stdout.log`
+### `unicorn_stdout.log`
+
+This file lives in `/var/log/gitlab/unicorn/unicorn_stdout.log` for
+Omnibus GitLab packages or in `/home/git/gitlab/log/unicorn_stdout.log` for
+for installations from source.
+
+### `unicorn_stderr.log`
+
+This file lives in `/var/log/gitlab/unicorn/unicorn_stderr.log` for
+Omnibus GitLab packages or in `/home/git/gitlab/log/unicorn_stderr.log` for
for installations from source.
-Unicorn is a high-performance forking Web server which is used for
-serving the GitLab application. You can look at this log if, for
-example, your application does not respond. This log contains all
-information about the state of Unicorn processes at any given time.
+These logs contain all information about the state of Unicorn processes at any given time.
```plaintext
I, [2015-02-13T06:14:46.680381 #9047] INFO -- : Refreshing Gem list
@@ -657,7 +671,7 @@ This log records:
- [Protected paths](../user/admin_area/settings/protected_paths.md) abusive requests.
NOTE: **Note:**
-From [%12.3](https://gitlab.com/gitlab-org/gitlab/-/issues/29239), user ID and username are also
+In GitLab versions [12.3](https://gitlab.com/gitlab-org/gitlab/-/issues/29239) and greater, user ID and username are also
recorded on this log, if available.
## `graphql_json.log`
@@ -686,7 +700,7 @@ installations from source.
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/19186) in GitLab 12.6.
-This file lives in `/var/log/gitlab/mailroom/mail_room_json.log` for
+This file lives in `/var/log/gitlab/mailroom/current` for
Omnibus GitLab packages or in `/home/git/gitlab/log/mail_room_json.log` for
installations from source.
@@ -793,8 +807,8 @@ This file lives in `/var/log/gitlab/gitlab-rails/service_measurement.log` for
Omnibus GitLab packages or in `/home/git/gitlab/log/service_measurement.log` for
installations from source.
-It contain only a single structured log with measurements for each service execution.
-It will contain measurement such as: number of SQL calls, `execution_time`, `gc_stats`, memory usage, etc...
+It contains only a single structured log with measurements for each service execution.
+It will contain measurements such as the number of SQL calls, `execution_time`, `gc_stats`, and `memory usage`.
For example:
@@ -870,22 +884,46 @@ For example:
}
```
+## Mattermost Logs
+
+For Omnibus GitLab installations, Mattermost logs reside in `/var/log/gitlab/mattermost/mattermost.log`.
+
## Workhorse Logs
-For Omnibus installations, Workhorse logs reside in `/var/log/gitlab/gitlab-workhorse/current`.
+For Omnibus GitLab installations, Workhorse logs reside in `/var/log/gitlab/gitlab-workhorse/`.
## PostgreSQL Logs
-For Omnibus installations, PostgreSQL logs reside in `/var/log/gitlab/postgresql/current`.
+For Omnibus GitLab installations, PostgreSQL logs reside in `/var/log/gitlab/postgresql/`.
## Prometheus Logs
-For Omnibus installations, Prometheus logs reside in `/var/log/gitlab/prometheus/current`.
+For Omnibus GitLab installations, Prometheus logs reside in `/var/log/gitlab/prometheus/`.
## Redis Logs
-For Omnibus installations, Redis logs reside in `/var/log/gitlab/redis/current`.
+For Omnibus GitLab installations, Redis logs reside in `/var/log/gitlab/redis/`.
-## Mattermost Logs
+## Alertmanager Logs
+
+For Omnibus GitLab installations, Alertmanager logs reside in `/var/log/gitlab/alertmanager/`.
+
+## Crond Logs
+
+For Omnibus GitLab installations, crond logs reside in `/var/log/gitlab/crond/`.
+
+## Grafana Logs
+
+For Omnibus GitLab installations, Grafana logs reside in `/var/log/gitlab/grafana/`.
+
+## LogRotate Logs
+
+For Omnibus GitLab installations, logrotate logs reside in `/var/log/gitlab/logrotate/`.
+
+## GitLab Monitor Logs
+
+For Omnibus GitLab installations, GitLab Monitor logs reside in `/var/log/gitlab/gitlab-monitor/`.
+
+## GitLab Exporter
-For Omnibus installations, Mattermost logs reside in `/var/log/gitlab/mattermost/mattermost.log`.
+For Omnibus GitLab installations, GitLab Exporter logs reside in `/var/log/gitlab/gitlab-exporter/`.
diff --git a/doc/administration/merge_request_diffs.md b/doc/administration/merge_request_diffs.md
index da9ca42ed9e..93cdbff6621 100644
--- a/doc/administration/merge_request_diffs.md
+++ b/doc/administration/merge_request_diffs.md
@@ -72,6 +72,11 @@ be configured already.
## Object Storage Settings
+NOTE: **Note:**
+In GitLab 13.2 and later, we recommend using the
+[consolidated object storage settings](object_storage.md#consolidated-object-storage-configuration).
+This section describes the earlier configuration format.
+
For source installations, these settings are nested under `external_diffs:` and
then `object_store:`. On Omnibus installations, they are prefixed by
`external_diffs_object_store_`.
@@ -87,20 +92,7 @@ then `object_store:`. On Omnibus installations, they are prefixed by
### S3 compatible connection settings
-The connection settings match those provided by [Fog](https://github.com/fog), and are as follows:
-
-| Setting | Description | Default |
-|---------|-------------|---------|
-| `provider` | Always `AWS` for compatible hosts | AWS |
-| `aws_access_key_id` | AWS credentials, or compatible | |
-| `aws_secret_access_key` | AWS credentials, or compatible | |
-| `aws_signature_version` | AWS signature version to use. 2 or 4 are valid options. Digital Ocean Spaces and other providers may need 2. | 4 |
-| `enable_signature_v4_streaming` | Set to true to enable HTTP chunked transfers with [AWS v4 signatures](https://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-streaming.html). Oracle Cloud S3 needs this to be false | true |
-| `region` | AWS region | us-east-1 |
-| `host` | S3 compatible host for when not using AWS, e.g. `localhost` or `storage.example.com` | s3.amazonaws.com |
-| `endpoint` | Can be used when configuring an S3 compatible service such as [MinIO](https://min.io), by entering a URL such as `http://127.0.0.1:9000` | (optional) |
-| `path_style` | Set to true to use `host/bucket_name/object` style paths instead of `bucket_name.host/object`. Leave as false for AWS S3 | false |
-| `use_iam_profile` | Set to true to use IAM profile instead of access keys | false
+See [the available connection settings for different providers](object_storage.md#connection-settings).
**In Omnibus installations:**
@@ -195,3 +187,51 @@ conditions become true:
These rules strike a balance between space and performance by only storing
frequently-accessed diffs in the database. Diffs that are less likely to be
accessed are moved to external storage instead.
+
+## Correcting incorrectly-migrated diffs
+
+Versions of GitLab earlier than `v13.0.0` would incorrectly record the location
+of some merge request diffs when [external diffs in object storage](#object-storage-settings)
+were enabled. This mainly affected imported merge requests, and was resolved
+with [this merge request](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/31005).
+
+If you are using object storage, have never used on-disk storage for external
+diffs, the "changes" tab for some merge requests fails to load with a 500 error,
+and the exception for that error is of this form:
+
+```plain
+Errno::ENOENT (No such file or directory @ rb_sysopen - /var/opt/gitlab/gitlab-rails/shared/external-diffs/merge_request_diffs/mr-6167082/diff-8199789)
+```
+
+Then you are affected by this issue. Since it's not possible to safely determine
+all these conditions automatically, we've provided a Rake task in GitLab v13.2.0
+that you can run manually to correct the data:
+
+**In Omnibus installations:**
+
+```shell
+sudo gitlab-rake gitlab:external_diffs:force_object_storage
+```
+
+**In installations from source:**
+
+```shell
+sudo -u git -H bundle exec rake gitlab:external_diffs:force_object_storage RAILS_ENV=production
+```
+
+Environment variables can be provided to modify the behavior of the task. The
+available variables are:
+
+| Name | Default value | Purpose |
+| ---- | ------------- | ------- |
+| `ANSI` | `true` | Use ANSI escape codes to make output more understandable |
+| `BATCH_SIZE` | `1000` | Iterate through the table in batches of this size |
+| `START_ID` | `nil` | If set, begin scanning at this ID |
+| `END_ID` | `nil` | If set, stop scanning at this ID |
+| `UPDATE_DELAY` | `1` | Number of seconds to sleep between updates |
+
+The `START_ID` and `END_ID` variables may be used to run the update in parallel,
+by assigning different processes to different parts of the table. The `BATCH`
+and `UPDATE_DELAY` parameters allow the speed of the migration to be traded off
+against concurrent access to the table. The `ANSI` parameter should be set to
+false if your terminal does not support ANSI escape codes.
diff --git a/doc/administration/monitoring/gitlab_self_monitoring_project/img/self_monitoring_default_dashboard.png b/doc/administration/monitoring/gitlab_self_monitoring_project/img/self_monitoring_default_dashboard.png
new file mode 100644
index 00000000000..1d61823ce41
--- /dev/null
+++ b/doc/administration/monitoring/gitlab_self_monitoring_project/img/self_monitoring_default_dashboard.png
Binary files differ
diff --git a/doc/administration/monitoring/gitlab_self_monitoring_project/index.md b/doc/administration/monitoring/gitlab_self_monitoring_project/index.md
index d05fb803c5c..44ba26296b9 100644
--- a/doc/administration/monitoring/gitlab_self_monitoring_project/index.md
+++ b/doc/administration/monitoring/gitlab_self_monitoring_project/index.md
@@ -26,7 +26,7 @@ of the project shows some basic resource usage charts, such as CPU and memory us
of each server in [Omnibus GitLab](https://docs.gitlab.com/omnibus/) installations.
You can also use the project to configure your own
-[custom metrics](../../../user/project/integrations/prometheus.md#adding-custom-metrics) using
+[custom metrics](../../../operations/metrics/index.md#adding-custom-metrics) using
metrics exposed by the [GitLab exporter](../prometheus/gitlab_metrics.md#metrics-available).
## Creating the self monitoring project
@@ -47,6 +47,19 @@ project. If you create the project again, it will be created in its default stat
It can take a few seconds for it to be deleted.
1. After the project is deleted, GitLab displays a message confirming your action.
+## Dashboards available in Omnibus GitLab
+
+Omnibus GitLab provides a dashboard that displays CPU and memory usage
+of each GitLab server. To select the servers to be displayed in the
+panels, provide a regular expression in the **Instance label regex** field.
+The dashboard uses metrics available in
+[Omnibus GitLab](https://docs.gitlab.com/omnibus/) installations.
+
+![GitLab self monitoring default dashboard](img/self_monitoring_default_dashboard.png)
+
+You can also
+[create your own dashboards](../../../operations/metrics/dashboards/index.md#defining-custom-dashboards-per-project).
+
## Connection to Prometheus
The project will be automatically configured to connect to the
@@ -60,18 +73,18 @@ you should
## Taking action on Prometheus alerts **(ULTIMATE)**
-You can [add a webhook](../../../user/project/integrations/prometheus.md#external-prometheus-instances)
+You can [add a webhook](../../../operations/metrics/alerts.md#external-prometheus-instances)
to the Prometheus configuration in order for GitLab to receive notifications of any alerts.
Once the webhook is setup, you can
-[take action on incoming alerts](../../../user/project/integrations/prometheus.md#taking-action-on-incidents-ultimate).
+[take action on incoming alerts](../../../operations/metrics/alerts.md#trigger-actions-from-alerts-ultimate).
## Adding custom metrics to the self monitoring project
You can add custom metrics in the self monitoring project by:
-1. [Duplicating](../../../user/project/integrations/prometheus.md#duplicating-a-gitlab-defined-dashboard) the default dashboard.
-1. [Editing](../../../user/project/integrations/prometheus.md#view-and-edit-the-source-file-of-a-custom-dashboard) the newly created dashboard file and configuring it with [dashboard YAML properties](../../../user/project/integrations/prometheus.md#dashboard-yaml-properties).
+1. [Duplicating](../../../operations/metrics/dashboards/index.md#duplicating-a-gitlab-defined-dashboard) the default dashboard.
+1. [Editing](../../../operations/metrics/dashboards/index.md#view-and-edit-the-source-file-of-a-custom-dashboard) the newly created dashboard file and configuring it with [dashboard YAML properties](../../../operations/metrics/dashboards/yaml.md).
## Troubleshooting
diff --git a/doc/administration/monitoring/performance/grafana_configuration.md b/doc/administration/monitoring/performance/grafana_configuration.md
index 4dbe3aed84e..96f1377fb73 100644
--- a/doc/administration/monitoring/performance/grafana_configuration.md
+++ b/doc/administration/monitoring/performance/grafana_configuration.md
@@ -6,85 +6,90 @@ info: To determine the technical writer assigned to the Stage/Group associated w
# Grafana Configuration
-[Grafana](https://grafana.com/) is a tool that allows you to visualize time
-series metrics through graphs and dashboards. GitLab writes performance data to Prometheus
-and Grafana will allow you to query to display useful graphs.
+[Grafana](https://grafana.com/) is a tool that enables you to visualize time
+series metrics through graphs and dashboards. GitLab writes performance data to Prometheus,
+and Grafana allows you to query the data to display useful graphs.
## Installation
-[Omnibus GitLab can help you install Grafana (recommended)](https://docs.gitlab.com/omnibus/settings/grafana.html)
+Omnibus GitLab can [help you install Grafana (recommended)](https://docs.gitlab.com/omnibus/settings/grafana.html)
or Grafana supplies package repositories (Yum/Apt) for easy installation.
See [Grafana installation documentation](https://grafana.com/docs/grafana/latest/installation/)
for detailed steps.
NOTE: **Note:**
Before starting Grafana for the first time, set the admin user
-and password in `/etc/grafana/grafana.ini`. Otherwise, the default password
-will be `admin`.
+and password in `/etc/grafana/grafana.ini`. If you don't, the default password
+is `admin`.
## Configuration
-Login as the admin user. Expand the menu by clicking the Grafana logo in the
-top left corner. Choose 'Data Sources' from the menu. Then, click 'Add new'
-in the top bar.
-
-![Grafana empty data source page](img/grafana_data_source_empty.png)
-
-![Grafana data source configurations](img/grafana_data_source_configuration.png)
+1. Log in to Grafana as the admin user.
+1. Expand the menu by clicking the Grafana logo in the top left corner.
+1. Choose **Data Sources** from the menu.
+1. Click **Add new** in the top bar:
+ ![Grafana empty data source page](img/grafana_data_source_empty.png)
+1. Edit the data source to fit your needs:
+ ![Grafana data source configurations](img/grafana_data_source_configuration.png)
+1. Click **Save**.
## Import Dashboards
-You can now import a set of default dashboards that will give you a good
-start on displaying useful information. GitLab has published a set of default
-[Grafana dashboards](https://gitlab.com/gitlab-org/grafana-dashboards) to get you started. Clone the
-repository or download a zip/tarball, then follow these steps to import each
-JSON file.
-
-Open the dashboard dropdown menu and click 'Import'
-
-![Grafana dashboard dropdown](img/grafana_dashboard_dropdown.png)
-
-Click 'Choose file' and browse to the location where you downloaded or cloned
-the dashboard repository. Pick one of the JSON files to import.
-
-![Grafana dashboard import](img/grafana_dashboard_import.png)
-
-Once the dashboard is imported, be sure to click save icon in the top bar. If
-you do not save the dashboard after importing it will be removed when you
-navigate away.
-
-![Grafana save icon](img/grafana_save_icon.png)
+You can now import a set of default dashboards to start displaying useful information.
+GitLab has published a set of default
+[Grafana dashboards](https://gitlab.com/gitlab-org/grafana-dashboards) to get you started.
+Clone the repository, or download a ZIP file or tarball, then follow these steps to import each
+JSON file individually:
+
+1. Log in to Grafana as the admin user.
+1. Open the dashboard dropdown menu and click **Import**:
+ ![Grafana dashboard dropdown](img/grafana_dashboard_dropdown.png)
+1. Click **Choose file**, and browse to the location where you downloaded or
+ cloned the dashboard repository. Select a JSON file to import:
+ ![Grafana dashboard import](img/grafana_dashboard_import.png)
+1. After the dashboard is imported, click the **Save dashboard** icon in the top bar:
+ ![Grafana save icon](img/grafana_save_icon.png)
+
+ NOTE: **Note:**
+ If you don't save the dashboard after importing it, the dashboard is removed
+ when you navigate away from the page.
Repeat this process for each dashboard you wish to import.
-Alternatively you can automatically import all the dashboards into your Grafana
-instance. See the README of the [Grafana dashboards](https://gitlab.com/gitlab-org/grafana-dashboards)
-repository for more information on this process.
+Alternatively, you can import all the dashboards into your Grafana
+instance. For more information about this process, see the
+[README of the Grafana dashboards](https://gitlab.com/gitlab-org/grafana-dashboards)
+repository.
## Integration with GitLab UI
> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/issues/61005) in GitLab 12.1.
-If you have set up Grafana, you can enable a link to access it easily from the sidebar:
+After setting up Grafana, you can enable a link to access it easily from the
+GitLab sidebar:
-1. Go to the **Admin Area > Settings > Metrics and profiling**.
+1. Navigate to the **{admin}** **Admin Area > Settings > Metrics and profiling**.
1. Expand **Metrics - Grafana**.
-1. Check the "Enable access to Grafana" checkbox.
-1. If Grafana is enabled through Omnibus GitLab and on the same server,
- leave **Grafana URL** unchanged. It should be `/-/grafana`.
-
- In any other case, enter the full URL of the Grafana instance.
+1. Check the **Enable access to Grafana** checkbox.
+1. Configure the **Grafana URL**:
+ - *If Grafana is enabled through Omnibus GitLab and on the same server,*
+ leave **Grafana URL** unchanged. It should be `/-/grafana`.
+ - *Otherwise,* enter the full URL of the Grafana instance.
1. Click **Save changes**.
-1. The new link will be available in the **Admin Area > Monitoring > Metrics Dashboard**.
+
+GitLab displays your link in the **{admin}** **Admin Area > Monitoring > Metrics Dashboard**.
## Security Update
-Users running GitLab version 12.0 or later should immediately upgrade to one of the following security releases due to a known vulnerability with the embedded Grafana dashboard:
+Users running GitLab version 12.0 or later should immediately upgrade to one of the
+following security releases due to a known vulnerability with the embedded Grafana dashboard:
- 12.0.6
- 12.1.6
-After upgrading, the Grafana dashboard will be disabled and the location of your existing Grafana data will be changed from `/var/opt/gitlab/grafana/data/` to `/var/opt/gitlab/grafana/data.bak.#{Date.today}/`.
+After upgrading, the Grafana dashboard is disabled, and the location of your
+existing Grafana data is changed from `/var/opt/gitlab/grafana/data/` to
+`/var/opt/gitlab/grafana/data.bak.#{Date.today}/`.
To prevent the data from being relocated, you can run the following command prior to upgrading:
@@ -100,19 +105,23 @@ sudo mv /var/opt/gitlab/grafana/data.bak.xxxx/ /var/opt/gitlab/grafana/data/
However, you should **not** reinstate your old data _except_ under one of the following conditions:
-1. If you are certain that you changed your default admin password when you enabled Grafana
-1. If you run GitLab in a private network, accessed only by trusted users, and your Grafana login page has not been exposed to the internet
+1. If you're certain that you changed your default admin password when you enabled Grafana.
+1. If you run GitLab in a private network, accessed only by trusted users, and your
+ Grafana login page has not been exposed to the internet.
-If you require access to your old Grafana data but do not meet one of these criteria, you may consider:
+If you require access to your old Grafana data but don't meet one of these criteria, you may consider:
1. Reinstating it temporarily.
1. [Exporting the dashboards](https://grafana.com/docs/grafana/latest/reference/export_import/#exporting-a-dashboard) you need.
1. Refreshing the data and [re-importing your dashboards](https://grafana.com/docs/grafana/latest/reference/export_import/#importing-a-dashboard).
DANGER: **Danger:**
-This poses a temporary vulnerability while your old Grafana data is in use and the decision to do so should be weighed carefully with your need to access existing data and dashboards.
+These actions pose a temporary vulnerability while your old Grafana data is in use.
+Deciding to take any of these actions should be weighed carefully with your need to access
+existing data and dashboards.
-For more information and further mitigation details, please refer to our [blog post on the security release](https://about.gitlab.com/releases/2019/08/12/critical-security-release-gitlab-12-dot-1-dot-6-released/).
+For more information and further mitigation details, please refer to our
+[blog post on the security release](https://about.gitlab.com/releases/2019/08/12/critical-security-release-gitlab-12-dot-1-dot-6-released/).
---
diff --git a/doc/administration/monitoring/performance/img/performance_bar_configuration_settings.png b/doc/administration/monitoring/performance/img/performance_bar_configuration_settings.png
deleted file mode 100644
index fafc50cd000..00000000000
--- a/doc/administration/monitoring/performance/img/performance_bar_configuration_settings.png
+++ /dev/null
Binary files differ
diff --git a/doc/administration/monitoring/performance/img/performance_bar_frontend.png b/doc/administration/monitoring/performance/img/performance_bar_frontend.png
index 32a241e032b..9cc874c883a 100644
--- a/doc/administration/monitoring/performance/img/performance_bar_frontend.png
+++ b/doc/administration/monitoring/performance/img/performance_bar_frontend.png
Binary files differ
diff --git a/doc/administration/monitoring/performance/img/performance_bar_gitaly_calls.png b/doc/administration/monitoring/performance/img/performance_bar_gitaly_calls.png
index 2c43201cbd0..6caa2869d9b 100644
--- a/doc/administration/monitoring/performance/img/performance_bar_gitaly_calls.png
+++ b/doc/administration/monitoring/performance/img/performance_bar_gitaly_calls.png
Binary files differ
diff --git a/doc/administration/monitoring/performance/img/performance_bar_gitaly_threshold.png b/doc/administration/monitoring/performance/img/performance_bar_gitaly_threshold.png
index 4e42d904cdf..386558da813 100644
--- a/doc/administration/monitoring/performance/img/performance_bar_gitaly_threshold.png
+++ b/doc/administration/monitoring/performance/img/performance_bar_gitaly_threshold.png
Binary files differ
diff --git a/doc/administration/monitoring/performance/img/performance_bar_redis_calls.png b/doc/administration/monitoring/performance/img/performance_bar_redis_calls.png
index ecb2dffbf8d..f2df8c794db 100644
--- a/doc/administration/monitoring/performance/img/performance_bar_redis_calls.png
+++ b/doc/administration/monitoring/performance/img/performance_bar_redis_calls.png
Binary files differ
diff --git a/doc/administration/monitoring/performance/img/performance_bar_request_selector_warning.png b/doc/administration/monitoring/performance/img/performance_bar_request_selector_warning.png
index 74711387ffe..3872c8b41b2 100644
--- a/doc/administration/monitoring/performance/img/performance_bar_request_selector_warning.png
+++ b/doc/administration/monitoring/performance/img/performance_bar_request_selector_warning.png
Binary files differ
diff --git a/doc/administration/monitoring/performance/img/performance_bar_rugged_calls.png b/doc/administration/monitoring/performance/img/performance_bar_rugged_calls.png
index 210f80a713d..0340ca1b2f7 100644
--- a/doc/administration/monitoring/performance/img/performance_bar_rugged_calls.png
+++ b/doc/administration/monitoring/performance/img/performance_bar_rugged_calls.png
Binary files differ
diff --git a/doc/administration/monitoring/performance/performance_bar.md b/doc/administration/monitoring/performance/performance_bar.md
index c681dedbca8..8002a9ab296 100644
--- a/doc/administration/monitoring/performance/performance_bar.md
+++ b/doc/administration/monitoring/performance/performance_bar.md
@@ -6,71 +6,83 @@ info: To determine the technical writer assigned to the Stage/Group associated w
# Performance Bar
-A Performance Bar can be displayed, to dig into the performance of a page. When
-activated, it looks as follows:
+You can display the GitLab Performance Bar to see statistics for the performance
+of a page. When activated, it looks as follows:
![Performance Bar](img/performance_bar.png)
-It allows you to see (from left to right):
+From left to right, it displays:
-- the current host serving the page
-- time taken and number of DB queries; click through for details of these queries
+- **Current Host**: the current host serving the page.
+- **Database queries**: the time taken (in milliseconds) and the total number
+ of database queries, displayed in the format `00ms / 00pg`. Click to display
+ a modal window with more details:
![SQL profiling using the Performance Bar](img/performance_bar_sql_queries.png)
-- time taken and number of [Gitaly](../../gitaly/index.md) calls; click through for details of these calls
+- **Gitaly calls**: the time taken (in milliseconds) and the total number of
+ [Gitaly](../../gitaly/index.md) calls. Click to display a modal window with more
+ details:
![Gitaly profiling using the Performance Bar](img/performance_bar_gitaly_calls.png)
-- time taken and number of [Rugged](../../high_availability/nfs.md#improving-nfs-performance-with-gitlab) calls; click through for details of these calls
+- **Rugged calls**: the time taken (in milliseconds) and the total number of
+ [Rugged](../../high_availability/nfs.md#improving-nfs-performance-with-gitlab) calls.
+ Click to display a modal window with more details:
![Rugged profiling using the Performance Bar](img/performance_bar_rugged_calls.png)
-- time taken and number of Redis calls; click through for details of these calls
+- **Redis calls**: the time taken (in milliseconds) and the total number of
+ Redis calls. Click to display a modal window with more details:
![Redis profiling using the Performance Bar](img/performance_bar_redis_calls.png)
-- total load timings of the page; click through for details of these calls. Values in the following order:
- - Backend - Time that the actual base page took to load
- - [First Contentful Paint](hhttps://web.dev/first-contentful-paint/) - Time until something was visible to the user
- - [DomContentLoaded](https://developers.google.com/web/fundamentals/performance/critical-rendering-path/measure-crp) Event
- - Number of Requests that the page loaded
- ![Frontend requests using the Performance Bar](img/performance_bar_frontend.png)
-- a link to add a request's details to the performance bar; the request can be
- added by its full URL (authenticated as the current user), or by the value of
- its `X-Request-Id` header
-- a link to download the raw JSON used to generate the Performance Bar reports
-
-On the far right is a request selector that allows you to view the same metrics
-(excluding the page timing and line profiler) for any requests made while the
-page was open. Only the first two requests per unique URL are captured.
+- **Elasticsearch calls**: the time taken (in milliseconds) and the total number of
+ Elasticsearch calls. Click to display a modal window with more details.
+- **Load timings** of the page: if your browser supports load timings (Chromium
+ and Chrome) several values in milliseconds, separated by slashes.
+ Click to display a modal window with more details. The values, from left to right:
+ - **Backend**: time needed for the base page to load.
+ - [**First Contentful Paint**](https://web.dev/first-contentful-paint/):
+ Time until something was visible to the user.
+ - [**DomContentLoaded**](https://developers.google.com/web/fundamentals/performance/critical-rendering-path/measure-crp) Event.
+ - **Total number of requests** the page loaded:
+ ![Frontend requests using the Performance Bar](img/performance_bar_frontend.png)
+- **Trace**: If Jaeger is integrated, **Trace** links to a Jaeger tracing page
+ with the current request's `correlation_id` included.
+- **+**: A link to add a request's details to the performance bar. The request
+ can be added by its full URL (authenticated as the current user), or by the value of
+ its `X-Request-Id` header.
+- **Download**: a link to download the raw JSON used to generate the Performance Bar reports.
+- **Request Selector**: a select box displayed on the right-hand side of the
+ Performance Bar which enables you to view these metrics for any requests made while
+ the current page was open. Only the first two requests per unique URL are captured.
## Request warnings
-For requests exceeding predefined limits, a warning icon will be shown
-next to the failing metric, along with an explanation. In this example,
-the Gitaly call duration exceeded the threshold:
+Requests exceeding predefined limits display a warning **{warning}** icon and
+explanation next to the failing metric. In this example, the Gitaly call duration
+exceeded the threshold:
![Gitaly call duration exceeded threshold](img/performance_bar_gitaly_threshold.png)
-If any requests on the current page generated warnings, the icon will
-appear next to the request selector:
+If any requests on the current page generated warnings, the warning icon displays
+next to the **Request selector**:
![Request selector showing two requests with warnings](img/performance_bar_request_selector_warning.png)
-And requests with warnings are indicated in the request selector with a
-`(!)` after their path:
+Requests with warnings display `(!)` after their path in the **Request selector**:
![Request selector showing dropdown](img/performance_bar_request_selector_warning_expanded.png)
## Enable the Performance Bar via the Admin panel
-GitLab Performance Bar is disabled by default. To enable it for a given group,
-navigate to **Admin Area > Settings > Metrics and profiling**
-(`admin/application_settings/metrics_and_profiling`), and expand the section
-**Profiling - Performance bar**.
+The GitLab Performance Bar is disabled by default. To enable it for a given group:
-The only required setting you need to set is the full path of the group that
-will be allowed to display the Performance Bar.
-Make sure _Enable the Performance Bar_ is checked and hit
-**Save** to save the changes.
+1. Sign in as a user with Administrator [permissions](../../../user/permissions.md).
+1. In the menu bar, click the **{admin}** **Admin Area** icon.
+1. Navigate to **{settings}** **Settings > Metrics and profiling**
+ (`admin/application_settings/metrics_and_profiling`), and expand the section
+ **Profiling - Performance bar**.
+1. Click **Enable access to the Performance Bar**.
+1. In the **Allowed group** field, provide the full path of the group allowed
+ to access the GitLab Performance Bar.
+1. Click **Save changes**.
-Once the Performance Bar is enabled, you will need to press the [<kbd>p</kbd> +
-<kbd>b</kbd> keyboard shortcut](../../../user/shortcuts.md) to actually
-display it.
+## Keyboard shortcut for the Performance Bar
-You can toggle the Bar using the same shortcut.
-
-![GitLab Performance Bar Admin Settings](img/performance_bar_configuration_settings.png)
+After enabling the GitLab Performance Bar, press the [<kbd>p</kbd> +
+<kbd>b</kbd> keyboard shortcut](../../../user/shortcuts.md) to display it, and
+again to hide it.
diff --git a/doc/administration/monitoring/prometheus/gitlab_exporter.md b/doc/administration/monitoring/prometheus/gitlab_exporter.md
index 3effca4a2bf..686ed14ba42 100644
--- a/doc/administration/monitoring/prometheus/gitlab_exporter.md
+++ b/doc/administration/monitoring/prometheus/gitlab_exporter.md
@@ -9,27 +9,25 @@ info: To determine the technical writer assigned to the Stage/Group associated w
>- Available since [Omnibus GitLab 8.17](https://gitlab.com/gitlab-org/omnibus-gitlab/-/merge_requests/1132).
>- Renamed from `GitLab monitor exporter` to `GitLab exporter` in [GitLab 12.3](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/16511).
-The [GitLab exporter](https://gitlab.com/gitlab-org/gitlab-exporter) allows you to
-measure various GitLab metrics, pulled from Redis and the database, in Omnibus GitLab
+The [GitLab exporter](https://gitlab.com/gitlab-org/gitlab-exporter) enables you to
+measure various GitLab metrics pulled from Redis and the database in Omnibus GitLab
instances.
NOTE: **Note:**
-For installations from source you'll have to install and configure it yourself.
+For installations from source you must install and configure it yourself.
To enable the GitLab exporter in an Omnibus GitLab instance:
-1. [Enable Prometheus](index.md#configuring-prometheus)
-1. Edit `/etc/gitlab/gitlab.rb`
-1. Add or find and uncomment the following line, making sure it's set to `true`:
+1. [Enable Prometheus](index.md#configuring-prometheus).
+1. Edit `/etc/gitlab/gitlab.rb`.
+1. Add, or find and uncomment, the following line, making sure it's set to `true`:
```ruby
gitlab_exporter['enable'] = true
```
1. Save the file and [reconfigure GitLab](../../restart_gitlab.md#omnibus-gitlab-reconfigure)
- for the changes to take effect
+ for the changes to take effect.
-Prometheus will now automatically begin collecting performance data from
-the GitLab exporter exposed under `localhost:9168`.
-
-[↠Back to the main Prometheus page](index.md)
+Prometheus automatically begins collecting performance data from
+the GitLab exporter exposed at `localhost:9168`.
diff --git a/doc/administration/monitoring/prometheus/gitlab_metrics.md b/doc/administration/monitoring/prometheus/gitlab_metrics.md
index f3084b1732e..fa5e5da80c3 100644
--- a/doc/administration/monitoring/prometheus/gitlab_metrics.md
+++ b/doc/administration/monitoring/prometheus/gitlab_metrics.md
@@ -14,18 +14,18 @@ To enable the GitLab Prometheus metrics:
1. [Restart GitLab](../../restart_gitlab.md#omnibus-gitlab-restart) for the changes to take effect.
NOTE: **Note:**
-For installations from source you'll have to configure it yourself.
+For installations from source you must configure it yourself.
## Collecting the metrics
GitLab monitors its own internal service metrics, and makes them available at the
`/-/metrics` endpoint. Unlike other [Prometheus](https://prometheus.io) exporters, to access
-it, the client IP address needs to be [explicitly allowed](../ip_whitelist.md).
+the metrics, the client IP address must be [explicitly allowed](../ip_whitelist.md).
For [Omnibus GitLab](https://docs.gitlab.com/omnibus/) and Chart installations,
these metrics are enabled and collected as of
[GitLab 9.4](https://gitlab.com/gitlab-org/omnibus-gitlab/-/merge_requests/1702).
-For source installations or earlier versions, these metrics must be enabled
+For source installations, these metrics must be enabled
manually and collected by a Prometheus server.
For enabling and viewing metrics from Sidekiq nodes, see [Sidekiq metrics](#sidekiq-metrics).
@@ -40,7 +40,7 @@ The following metrics are available:
| `gitlab_banzai_cacheless_render_real_duration_seconds` | Histogram | 9.4 | Duration of rendering Markdown into HTML when cached output does not exist | `controller`, `action` |
| `gitlab_cache_misses_total` | Counter | 10.2 | Cache read miss | `controller`, `action` |
| `gitlab_cache_operation_duration_seconds` | Histogram | 10.2 | Cache access time | |
-| `gitlab_cache_operations_total` | Counter | 12.2 | Cache operations by controller/action | `controller`, `action`, `operation` |
+| `gitlab_cache_operations_total` | Counter | 12.2 | Cache operations by controller or action | `controller`, `action`, `operation` |
| `gitlab_ci_pipeline_creation_duration_seconds` | Histogram | 13.0 | Time in seconds it takes to create a CI/CD pipeline | |
| `gitlab_ci_pipeline_size_builds` | Histogram | 13.1 | Total number of builds within a pipeline grouped by a pipeline source | `source` |
| `job_waiter_started_total` | Counter | 12.9 | Number of batches of jobs started where a web request is waiting for the jobs to complete | `worker` |
@@ -92,16 +92,16 @@ The following metrics are available:
| `gitlab_view_rendering_duration_seconds` | Histogram | 10.2 | Duration for views (histogram) | `controller`, `action`, `view` |
| `http_requests_total` | Counter | 9.4 | Rack request count | `method` |
| `http_request_duration_seconds` | Histogram | 9.4 | HTTP response time from rack middleware | `method`, `status` |
-| `gitlab_transaction_db_count_total` | Counter | 13.1 | Counter for total number of sql calls | `controller`, `action` |
-| `gitlab_transaction_db_write_count_total` | Counter | 13.1 | Counter for total number of write sql calls | `controller`, `action` |
-| `gitlab_transaction_db_cached_count_total` | Counter | 13.1 | Counter for total number of cached sql calls | `controller`, `action` |
+| `gitlab_transaction_db_count_total` | Counter | 13.1 | Counter for total number of SQL calls | `controller`, `action` |
+| `gitlab_transaction_db_write_count_total` | Counter | 13.1 | Counter for total number of write SQL calls | `controller`, `action` |
+| `gitlab_transaction_db_cached_count_total` | Counter | 13.1 | Counter for total number of cached SQL calls | `controller`, `action` |
| `http_redis_requests_duration_seconds` | Histogram | 13.1 | Redis requests duration during web transactions | `controller`, `action` |
| `http_redis_requests_total` | Counter | 13.1 | Redis requests count during web transactions | `controller`, `action` |
| `http_elasticsearch_requests_duration_seconds` **(STARTER)** | Histogram | 13.1 | Elasticsearch requests duration during web transactions | `controller`, `action` |
| `http_elasticsearch_requests_total` **(STARTER)** | Counter | 13.1 | Elasticsearch requests count during web transactions | `controller`, `action` |
| `pipelines_created_total` | Counter | 9.4 | Counter of pipelines created | |
| `rack_uncaught_errors_total` | Counter | 9.4 | Rack connections handling uncaught errors count | |
-| `user_session_logins_total` | Counter | 9.4 | Counter of how many users have logged in | |
+| `user_session_logins_total` | Counter | 9.4 | Counter of how many users have logged in since GitLab was started or restarted | |
| `upload_file_does_not_exist` | Counter | 10.7 in EE, 11.5 in CE | Number of times an upload record could not find its file | |
| `failed_login_captcha_total` | Gauge | 11.0 | Counter of failed CAPTCHA attempts during login | |
| `successful_login_captcha_total` | Gauge | 11.0 | Counter of successful CAPTCHA attempts during login | |
@@ -120,7 +120,7 @@ The following metrics can be controlled by feature flags:
## Sidekiq metrics
Sidekiq jobs may also gather metrics, and these metrics can be accessed if the
-Sidekiq exporter is enabled (for example, using the `monitoring.sidekiq_exporter`
+Sidekiq exporter is enabled: for example, using the `monitoring.sidekiq_exporter`
configuration option in `gitlab.yml`. These metrics are served from the
`/metrics` path on the configured port.
@@ -173,6 +173,7 @@ configuration option in `gitlab.yml`. These metrics are served from the
| `geo_repositories_retrying_verification_count` | Gauge | 11.2 | Number of repositories verification failures that Geo is actively trying to correct on secondary | `url` |
| `geo_wikis_retrying_verification_count` | Gauge | 11.2 | Number of wikis verification failures that Geo is actively trying to correct on secondary | `url` |
| `global_search_bulk_cron_queue_size` | Gauge | 12.10 | Number of database records waiting to be synchronized to Elasticsearch | |
+| `global_search_awaiting_indexing_queue_size` | Gauge | 13.2 | Number of database updates waiting to be synchronized to Elasticsearch while indexing is paused | |
| `package_files_count` | Gauge | 13.0 | Number of package files on primary | `url` |
| `package_files_checksummed_count` | Gauge | 13.0 | Number of package files checksummed on primary | `url` |
| `package_files_checksum_failed_count` | Gauge | 13.0 | Number of package files failed to calculate the checksum on primary
@@ -187,16 +188,16 @@ The following metrics are available:
## Connection pool metrics
-These metrics record the status of the database [connection pools](https://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/ConnectionPool.html).
+These metrics record the status of the database
+[connection pools](https://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/ConnectionPool.html),
+and the metrics all have these labels:
-They all have these labels:
-
-1. `class` - the Ruby class being recorded.
- 1. `ActiveRecord::Base` is the main database connection.
- 1. `Geo::TrackingBase` is the connection to the Geo tracking database, if
- enabled.
-1. `host` - the host name used to connect to the database.
-1. `port` - the port used to connect to the database.
+- `class` - the Ruby class being recorded.
+ - `ActiveRecord::Base` is the main database connection.
+ - `Geo::TrackingBase` is the connection to the Geo tracking database, if
+ enabled.
+- `host` - the host name used to connect to the database.
+- `port` - the port used to connect to the database.
| Metric | Type | Since | Description |
|:----------------------------------------------|:------|:------|:--------------------------------------------------|
@@ -251,15 +252,29 @@ When Puma is used instead of Unicorn, the following metrics are available:
| `puma_idle_threads` | Gauge | 12.0 | Number of spawned threads which are not processing a request |
| `puma_killer_terminations_total` | Gauge | 12.0 | Number of workers terminated by PumaWorkerKiller |
+## Redis metrics
+
+These client metrics are meant to complement Redis server metrics.
+These metrics are broken down per [Redis
+instance](https://docs.gitlab.com/omnibus/settings/redis.html#running-with-multiple-redis-instances).
+These metrics all have a `storage` label which indicates the Redis
+instance (`cache`, `shared_state` etc.).
+
+| Metric | Type | Since | Description |
+|:--------------------------------- |:------- |:----- |:----------- |
+| `gitlab_redis_client_exceptions_total` | Counter | 13.2 | Number of Redis client exceptions, broken down by exception class |
+| `gitlab_redis_client_requests_total` | Counter | 13.2 | Number of Redis client requests |
+| `gitlab_redis_client_requests_duration_seconds` | Histogram | 13.2 | Redis request latency, excluding blocking commands |
+
## Metrics shared directory
GitLab's Prometheus client requires a directory to store metrics data shared between multi-process services.
Those files are shared among all instances running under Unicorn server.
The directory must be accessible to all running Unicorn's processes, or
-metrics won't function correctly.
+metrics can't function correctly.
This directory's location is configured using environment variable `prometheus_multiproc_dir`.
For best performance, create this directory in `tmpfs`.
If GitLab is installed using [Omnibus GitLab](https://docs.gitlab.com/omnibus/)
-and `tmpfs` is available, then the metrics directory will be configured for you.
+and `tmpfs` is available, then GitLab configures the metrics directory for you.
diff --git a/doc/administration/monitoring/prometheus/index.md b/doc/administration/monitoring/prometheus/index.md
index 1e233b890a2..f0ad0a1a2e6 100644
--- a/doc/administration/monitoring/prometheus/index.md
+++ b/doc/administration/monitoring/prometheus/index.md
@@ -145,6 +145,12 @@ To use an external Prometheus server:
gitlab_rails['monitoring_whitelist'] = ['127.0.0.0/8', '192.168.0.1']
```
+1. On **all** GitLab Rails(Puma/Unicorn, Sidekiq) servers, set the Prometheus server IP address and listen port. For example:
+
+ ```ruby
+ gitlab_rails['prometheus_address'] = '192.168.0.1:9090'
+ ```
+
1. To scrape NGINX metrics, you'll also need to configure NGINX to allow the Prometheus server
IP. For example:
@@ -165,54 +171,54 @@ To use an external Prometheus server:
```yaml
scrape_configs:
- - job_name: nginx
- static_configs:
- - targets:
- - 1.1.1.1:8060
- - job_name: redis
- static_configs:
- - targets:
- - 1.1.1.1:9121
- - job_name: postgres
- static_configs:
- - targets:
- - 1.1.1.1:9187
- - job_name: node
- static_configs:
- - targets:
- - 1.1.1.1:9100
- - job_name: gitlab-workhorse
- static_configs:
- - targets:
- - 1.1.1.1:9229
- - job_name: gitlab-rails
- metrics_path: "/-/metrics"
- static_configs:
- - targets:
- - 1.1.1.1:8080
- - job_name: gitlab-sidekiq
- static_configs:
- - targets:
- - 1.1.1.1:8082
- - job_name: gitlab_exporter_database
- metrics_path: "/database"
- static_configs:
- - targets:
- - 1.1.1.1:9168
- - job_name: gitlab_exporter_sidekiq
- metrics_path: "/sidekiq"
- static_configs:
- - targets:
- - 1.1.1.1:9168
- - job_name: gitlab_exporter_process
- metrics_path: "/process"
- static_configs:
- - targets:
- - 1.1.1.1:9168
- - job_name: gitaly
- static_configs:
- - targets:
- - 1.1.1.1:9236
+ - job_name: nginx
+ static_configs:
+ - targets:
+ - 1.1.1.1:8060
+ - job_name: redis
+ static_configs:
+ - targets:
+ - 1.1.1.1:9121
+ - job_name: postgres
+ static_configs:
+ - targets:
+ - 1.1.1.1:9187
+ - job_name: node
+ static_configs:
+ - targets:
+ - 1.1.1.1:9100
+ - job_name: gitlab-workhorse
+ static_configs:
+ - targets:
+ - 1.1.1.1:9229
+ - job_name: gitlab-rails
+ metrics_path: "/-/metrics"
+ static_configs:
+ - targets:
+ - 1.1.1.1:8080
+ - job_name: gitlab-sidekiq
+ static_configs:
+ - targets:
+ - 1.1.1.1:8082
+ - job_name: gitlab_exporter_database
+ metrics_path: "/database"
+ static_configs:
+ - targets:
+ - 1.1.1.1:9168
+ - job_name: gitlab_exporter_sidekiq
+ metrics_path: "/sidekiq"
+ static_configs:
+ - targets:
+ - 1.1.1.1:9168
+ - job_name: gitlab_exporter_process
+ metrics_path: "/process"
+ static_configs:
+ - targets:
+ - 1.1.1.1:9168
+ - job_name: gitaly
+ static_configs:
+ - targets:
+ - 1.1.1.1:9236
```
1. Reload the Prometheus server.
diff --git a/doc/administration/object_storage.md b/doc/administration/object_storage.md
index 1dea2de73f6..b51b722fbd7 100644
--- a/doc/administration/object_storage.md
+++ b/doc/administration/object_storage.md
@@ -11,29 +11,430 @@ typically much more performant, reliable, and scalable.
## Options
-Object storage options that GitLab has tested, or is aware of customers using include:
+GitLab has been tested on a number of object storage providers:
-- SaaS/Cloud solutions such as [Amazon S3](https://aws.amazon.com/s3/), [Google cloud storage](https://cloud.google.com/storage).
+- [Amazon S3](https://aws.amazon.com/s3/)
+- [Google Cloud Storage](https://cloud.google.com/storage)
+- [Digital Ocean Spaces](https://www.digitalocean.com/products/spaces)
+- [Oracle Cloud Infrastructure](https://docs.cloud.oracle.com/en-us/iaas/Content/Object/Tasks/s3compatibleapi.htm)
+- [Openstack Swift](https://docs.openstack.org/swift/latest/s3_compat.html)
- On-premises hardware and appliances from various storage vendors.
- MinIO. We have [a guide to deploying this](https://docs.gitlab.com/charts/advanced/external-object-storage/minio.html) within our Helm Chart documentation.
## Configuration guides
-For configuring GitLab to use Object Storage refer to the following guides:
-
-1. Configure [object storage for backups](../raketasks/backup_restore.md#uploading-backups-to-a-remote-cloud-storage).
-1. Configure [object storage for job artifacts](job_artifacts.md#using-object-storage)
- including [incremental logging](job_logs.md#new-incremental-logging-architecture).
-1. Configure [object storage for LFS objects](lfs/index.md#storing-lfs-objects-in-remote-object-storage).
-1. Configure [object storage for uploads](uploads.md#using-object-storage-core-only).
-1. Configure [object storage for merge request diffs](merge_request_diffs.md#using-object-storage).
-1. Configure [object storage for Container Registry](packages/container_registry.md#container-registry-storage-driver) (optional feature).
-1. Configure [object storage for Mattermost](https://docs.mattermost.com/administration/config-settings.html#file-storage) (optional feature).
-1. Configure [object storage for packages](packages/index.md#using-object-storage) (optional feature). **(PREMIUM ONLY)**
-1. Configure [object storage for Dependency Proxy](packages/dependency_proxy.md#using-object-storage) (optional feature). **(PREMIUM ONLY)**
-1. Configure [object storage for Pseudonymizer](pseudonymizer.md#configuration) (optional feature). **(ULTIMATE ONLY)**
-1. Configure [object storage for autoscale Runner caching](https://docs.gitlab.com/runner/configuration/autoscale.html#distributed-runners-caching) (optional - for improved performance).
-1. Configure [object storage for Terraform state files](terraform_state.md#using-object-storage-core-only)
+There are two ways of specifying object storage configuration in GitLab:
+
+- [Consolidated form](#consolidated-object-storage-configuration): A single credential is
+ shared by all supported object types.
+- [Storage-specific form](#storage-specific-configuration): Every object defines its
+ own object storage [connection and configuration](#connection-settings).
+
+For more information on the differences and to transition from one form to another, see
+[Transition to consolidated form](#transition-to-consolidated-form).
+
+### Consolidated object storage configuration
+
+> Introduced in [GitLab 13.2](https://gitlab.com/gitlab-org/omnibus-gitlab/-/merge_requests/4368).
+
+Using the consolidated object storage configuration has a number of advantages:
+
+- It can simplify your GitLab configuration since the connection details are shared
+ across object types.
+- It enables the use of [encrypted S3 buckets](#encrypted-s3-buckets).
+- It [uploads files to S3 with proper `Content-MD5` headers](https://gitlab.com/gitlab-org/gitlab-workhorse/-/issues/222).
+
+NOTE: **Note:**
+Only AWS S3-compatible providers and Google are
+supported at the moment since [direct upload
+mode](../development/uploads.md#direct-upload) must be used. Background
+upload is not supported in this mode. We recommend direct upload mode because
+it does not require a shared folder, and [this setting may become the default](https://gitlab.com/gitlab-org/gitlab/-/issues/27331).
+
+NOTE: **Note:**
+Consolidated object storage configuration cannot be used for
+backups or Mattermost. See [the full table for a complete list](#storage-specific-configuration).
+
+Most types of objects, such as CI artifacts, LFS files, upload
+attachments, and so on can be saved in object storage by specifying a single
+credential for object storage with multiple buckets. A [different bucket
+for each type must be used](#use-separate-buckets).
+
+When the consolidated form is:
+
+- Used with an S3-compatible object storage, Workhorse uses its internal S3 client to
+ upload files.
+- Not used with an S3-compatible object storage, Workhorse falls back to using
+ pre-signed URLs.
+
+See the section on [ETag mismatch errors](#etag-mismatch) for more details.
+
+**In Omnibus installations:**
+
+1. Edit `/etc/gitlab/gitlab.rb` and add the following lines, substituting
+ the values you want:
+
+ ```ruby
+ # Consolidated object storage configuration
+ gitlab_rails['object_store']['enabled'] = true
+ gitlab_rails['object_store']['proxy_download'] = true
+ gitlab_rails['object_store']['connection'] = {
+ 'provider' => 'AWS',
+ 'region' => '<eu-central-1>',
+ 'aws_access_key_id' => '<AWS_ACCESS_KEY_ID>',
+ 'aws_secret_access_key' => '<AWS_SECRET_ACCESS_KEY>'
+ }
+ gitlab_rails['object_store']['objects']['artifacts']['bucket'] = '<artifacts>'
+ gitlab_rails['object_store']['objects']['external_diffs']['bucket'] = '<external-diffs>'
+ gitlab_rails['object_store']['objects']['lfs']['bucket'] = '<lfs-objects>'
+ gitlab_rails['object_store']['objects']['uploads']['bucket'] = '<uploads>'
+ gitlab_rails['object_store']['objects']['packages']['bucket'] = '<packages>'
+ gitlab_rails['object_store']['objects']['dependency_proxy']['bucket'] = '<dependency-proxy>'
+ gitlab_rails['object_store']['objects']['terraform_state']['bucket'] = '<terraform-state>'
+ ```
+
+ NOTE: For GitLab 9.4 or later, if you're using AWS IAM profiles, be sure to omit the
+ AWS access key and secret access key/value pairs. For example:
+
+ ```ruby
+ gitlab_rails['object_store_connection'] = {
+ 'provider' => 'AWS',
+ 'region' => '<eu-central-1>',
+ 'use_iam_profile' => true
+ }
+ ```
+
+1. Save the file and [reconfigure GitLab](restart_gitlab.md#omnibus-gitlab-reconfigure) for the changes to take effect.
+
+**In installations from source:**
+
+1. Edit `/home/git/gitlab/config/gitlab.yml` and add or amend the following lines:
+
+ ```yaml
+ object_store:
+ enabled: true
+ proxy_download: true
+ connection:
+ provider: AWS
+ aws_access_key_id: <AWS_ACCESS_KEY_ID>
+ aws_secret_access_key: <AWS_SECRET_ACCESS_KEY>
+ region: <eu-central-1>
+ objects:
+ artifacts:
+ bucket: <artifacts>
+ external_diffs:
+ bucket: <external-diffs>
+ lfs:
+ bucket: <lfs-objects>
+ uploads:
+ bucket: <uploads>
+ packages:
+ bucket: <packages>
+ dependency_proxy:
+ bucket: <dependency_proxy>
+ terraform_state:
+ bucket: <terraform>
+ ```
+
+1. Edit `/home/git/gitlab-workhorse/config.toml` and add or amend the following lines:
+
+ ```toml
+ [object_storage]
+ enabled = true
+ provider = "AWS"
+
+ [object_storage.s3]
+ aws_access_key_id = "<AWS_ACCESS_KEY_ID>"
+ aws_secret_access_key = "<AWS_SECRET_ACCESS_KEY>"
+ ```
+
+1. Save the file and [restart GitLab](restart_gitlab.md#installations-from-source) for the changes to take effect.
+
+#### Common parameters
+
+In the consolidated configuration, the `object_store` section defines a
+common set of parameters. Here we use the YAML from the source
+installation because it's easier to see the inheritance:
+
+```yaml
+ object_store:
+ enabled: true
+ proxy_download: true
+ connection:
+ provider: AWS
+ aws_access_key_id: <AWS_ACCESS_KEY_ID>
+ aws_secret_access_key: <AWS_SECRET_ACCESS_KEY>
+ objects:
+ ...
+```
+
+The Omnibus configuration maps directly to this:
+
+```ruby
+gitlab_rails['object_store']['enabled'] = true
+gitlab_rails['object_store']['proxy_download'] = true
+gitlab_rails['object_store']['connection'] = {
+ 'provider' => 'AWS',
+ 'aws_access_key_id' => '<AWS_ACCESS_KEY_ID',
+ 'aws_secret_access_key' => '<AWS_SECRET_ACCESS_KEY>'
+}
+```
+
+| Setting | Description |
+|---------|-------------|
+| `enabled` | Enable/disable object storage |
+| `proxy_download` | Set to `true` to [enable proxying all files served](#proxy-download). Option allows to reduce egress traffic as this allows clients to download directly from remote storage instead of proxying all data |
+| `connection` | Various connection options described below |
+| `objects` | [Object-specific configuration](#object-specific-configuration)
+
+### Connection settings
+
+Both consolidated configuration form and storage-specific configuration form must configure a connection. The following sections describe parameters that can be used
+in the `connection` setting.
+
+#### S3-compatible connection settings
+
+The connection settings match those provided by [fog-aws](https://github.com/fog/fog-aws):
+
+| Setting | Description | Default |
+|---------|-------------|---------|
+| `provider` | Always `AWS` for compatible hosts | `AWS` |
+| `aws_access_key_id` | AWS credentials, or compatible | |
+| `aws_secret_access_key` | AWS credentials, or compatible | |
+| `aws_signature_version` | AWS signature version to use. `2` or `4` are valid options. Digital Ocean Spaces and other providers may need `2`. | `4` |
+| `enable_signature_v4_streaming` | Set to `true` to enable HTTP chunked transfers with [AWS v4 signatures](https://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-streaming.html). Oracle Cloud S3 needs this to be `false`. | `true` |
+| `region` | AWS region | us-east-1 |
+| `host` | S3 compatible host for when not using AWS, e.g. `localhost` or `storage.example.com`. HTTPS and port 443 is assumed. | `s3.amazonaws.com` |
+| `endpoint` | Can be used when configuring an S3 compatible service such as [MinIO](https://min.io), by entering a URL such as `http://127.0.0.1:9000`. This takes precedence over `host`. | (optional) |
+| `path_style` | Set to `true` to use `host/bucket_name/object` style paths instead of `bucket_name.host/object`. Leave as `false` for AWS S3. | `false` |
+| `use_iam_profile` | Set to `true` to use IAM profile instead of access keys | `false`
+
+#### Oracle Cloud S3 connection settings
+
+Note that Oracle Cloud S3 must be sure to use the following settings:
+
+| Setting | Value |
+|---------|-------|
+| `enable_signature_v4_streaming` | `false` |
+| `path_style` | `true` |
+
+If `enable_signature_v4_streaming` is set to `true`, you may see the
+following error in `production.log`:
+
+```plaintext
+STREAMING-AWS4-HMAC-SHA256-PAYLOAD is not supported
+```
+
+#### Google Cloud Storage (GCS)
+
+Here are the valid connection parameters for GCS:
+
+| Setting | Description | example |
+|---------|-------------|---------|
+| `provider` | The provider name | `Google` |
+| `google_project` | GCP project name | `gcp-project-12345` |
+| `google_client_email` | The email address of the service account | `foo@gcp-project-12345.iam.gserviceaccount.com` |
+| `google_json_key_location` | The JSON key path | `/path/to/gcp-project-12345-abcde.json` |
+
+NOTE: **Note:**
+The service account must have permission to access the bucket.
+[See more](https://cloud.google.com/storage/docs/authentication)
+
+##### Google example (consolidated form)
+
+For Omnibus installations, this is an example of the `connection` setting:
+
+```ruby
+gitlab_rails['object_store']['connection'] = {
+ 'provider' => 'Google',
+ 'google_project' => '<GOOGLE PROJECT>',
+ 'google_client_email' => '<GOOGLE CLIENT EMAIL>',
+ 'google_json_key_location' => '<FILENAME>'
+}
+```
+
+#### OpenStack-compatible connection settings
+
+NOTE: **Note:**
+This is not compatible with the consolidated object storage form.
+OpenStack Swift is only supported with the storage-specific form. See the
+[S3 settings](#s3-compatible-connection-settings) if you want to use the consolidated form.
+
+While OpenStack Swift provides S3 compatibliity, some users may want to use the
+[Swift API](https://docs.openstack.org/swift/latest/api/object_api_v1_overview.html).
+Here are the valid connection settings below for the Swift API, provided by
+[fog-openstack](https://github.com/fog/fog-openstack):
+
+| Setting | Description | Default |
+|---------|-------------|---------|
+| `provider` | Always `OpenStack` for compatible hosts | `OpenStack` |
+| `openstack_username` | OpenStack username | |
+| `openstack_api_key` | OpenStack API key | |
+| `openstack_temp_url_key` | OpenStack key for generating temporary URLs | |
+| `openstack_auth_url` | OpenStack authentication endpoint | |
+| `openstack_region` | OpenStack region | |
+| `openstack_tenant` | OpenStack tenant ID |
+
+#### Rackspace Cloud Files
+
+NOTE: **Note:**
+This is not compatible with the consolidated object
+storage form. Rackspace Cloud is only supported with the storage-specific form.
+
+Here are the valid connection parameters for Rackspace Cloud, provided by
+[fog-rackspace](https://github.com/fog/fog-rackspace/):
+
+| Setting | Description | example |
+|---------|-------------|---------|
+| `provider` | The provider name | `Rackspace` |
+| `rackspace_username` | The username of the Rackspace account with access to the container | `joe.smith` |
+| `rackspace_api_key` | The API key of the Rackspace account with access to the container | `ABC123DEF456ABC123DEF456ABC123DE` |
+| `rackspace_region` | The Rackspace storage region to use, a three letter code from the [list of service access endpoints](https://developer.rackspace.com/docs/cloud-files/v1/general-api-info/service-access/) | `iad` |
+| `rackspace_temp_url_key` | The private key you have set in the Rackspace API for temporary URLs. Read more [here](https://developer.rackspace.com/docs/cloud-files/v1/use-cases/public-access-to-your-cloud-files-account/#tempurl) | `ABC123DEF456ABC123DEF456ABC123DE` |
+
+NOTE: **Note:**
+Regardless of whether the container has public access enabled or disabled, Fog will
+use the TempURL method to grant access to LFS objects. If you see errors in logs referencing
+instantiating storage with a `temp-url-key`, ensure that you have set the key properly
+on the Rackspace API and in `gitlab.rb`. You can verify the value of the key Rackspace
+has set by sending a GET request with token header to the service access endpoint URL
+and comparing the output of the returned headers.
+
+### Object-specific configuration
+
+The following YAML shows how the `object_store` section defines
+object-specific configuration block and how the `enabled` and
+`proxy_download` flags can be overriden. The `bucket` is the only
+required parameter within each type:
+
+```yaml
+ object_store:
+ connection:
+ ...
+ objects:
+ artifacts:
+ bucket: artifacts
+ proxy_download: false
+ external_diffs:
+ bucket: external-diffs
+ lfs:
+ bucket: lfs-objects
+ uploads:
+ bucket: uploads
+ packages:
+ bucket: packages
+ dependency_proxy:
+ enabled: false
+ bucket: dependency_proxy
+ terraform_state:
+ bucket: terraform
+```
+
+This maps to this Omnibus GitLab configuration:
+
+```ruby
+gitlab_rails['object_store']['objects']['artifacts']['bucket'] = 'artifacts'
+gitlab_rails['object_store']['objects']['artifacts']['proxy_download'] = false
+gitlab_rails['object_store']['objects']['external_diffs']['bucket'] = 'external-diffs'
+gitlab_rails['object_store']['objects']['lfs']['bucket'] = 'lfs-objects'
+gitlab_rails['object_store']['objects']['uploads']['bucket'] = 'uploads'
+gitlab_rails['object_store']['objects']['packages']['bucket'] = 'packages'
+gitlab_rails['object_store']['objects']['dependency_proxy']['enabled'] = false
+gitlab_rails['object_store']['objects']['dependency_proxy']['bucket'] = 'dependency-proxy'
+gitlab_rails['object_store']['objects']['terraform_state']['bucket'] = 'terraform-state'
+```
+
+This is the list of valid `objects` that can be used:
+
+| Type | Description |
+|--------------------|---------------|
+| `artifacts` | [CI artifacts](job_artifacts.md) |
+| `external_diffs` | [Merge request diffs](merge_request_diffs.md) |
+| `uploads` | [User uploads](uploads.md) |
+| `lfs` | [Git Large File Storage objects](lfs/index.md) |
+| `packages` | [Project packages (e.g. PyPI, Maven, NuGet, etc.)](packages/index.md) |
+| `dependency_proxy` | [GitLab Dependency Proxy](packages/dependency_proxy.md) |
+| `terraform_state` | [Terraform state files](terraform_state.md) |
+
+Within each object type, three parameters can be defined:
+
+| Setting | Required? | Description |
+|------------------|-----------|-------------|
+| `bucket` | Yes | The bucket name for the object storage. |
+| `enabled` | No | Overrides the common parameter |
+| `proxy_download` | No | Overrides the common parameter |
+
+#### Selectively disabling object storage
+
+As seen above, object storage can be disabled for specific types by
+setting the `enabled` flag to `false`. For example, to disable object
+storage for CI artifacts:
+
+```ruby
+gitlab_rails['object_store']['objects']['artifacts']['enabled'] = false
+```
+
+A bucket is not needed if the feature is disabled entirely. For example,
+no bucket is needed if CI artifacts are disabled with this setting:
+
+```ruby
+gitlab_rails['artifacts_enabled'] = false
+```
+
+### Transition to consolidated form
+
+Prior to GitLab 13.2:
+
+- Object storage configuration for all types of objects such as CI/CD artifacts, LFS
+ files, upload attachments, and so on had to be configured independently.
+- Object store connection parameters such as passwords and endpoint URLs had to be
+ duplicated for each type.
+
+For example, an Omnibus GitLab install might have the following configuration:
+
+```ruby
+# Original object storage configuration
+gitlab_rails['artifacts_object_store_enabled'] = true
+gitlab_rails['artifacts_object_store_direct_upload'] = true
+gitlab_rails['artifacts_object_store_proxy_download'] = true
+gitlab_rails['artifacts_object_store_remote_directory'] = 'artifacts'
+gitlab_rails['artifacts_object_store_connection'] = { 'provider' => 'AWS', 'aws_access_key_id' => 'access_key', 'aws_secret_access_key' => 'secret' }
+gitlab_rails['uploads_object_store_enabled'] = true
+gitlab_rails['uploads_object_store_direct_upload'] = true
+gitlab_rails['uploads_object_store_proxy_download'] = true
+gitlab_rails['uploads_object_store_remote_directory'] = 'uploads'
+gitlab_rails['uploads_object_store_connection'] = { 'provider' => 'AWS', 'aws_access_key_id' => 'access_key', 'aws_secret_access_key' => 'secret' }
+```
+
+While this provides flexibility in that it makes it possible for GitLab
+to store objects across different cloud providers, it also creates
+additional complexity and unnecessary redundancy. Since both GitLab
+Rails and Workhorse components need access to object storage, the
+consolidated form avoids excessive duplication of credentials.
+
+NOTE: **Note:**
+The consolidated object storage configuration is **only** used if all
+lines from the original form is omitted. To move to the consolidated form, remove the original configuration (for example, `artifacts_object_store_enabled`, `uploads_object_store_connection`, and so on.)
+
+## Storage-specific configuration
+
+For configuring object storage in GitLab 13.1 and earlier, or for storage types not
+supported by consolidated configuration form, refer to the following guides:
+
+|Object storage type|Supported by consolidated configuration?|
+|-------------------|----------------------------------------|
+| [Backups](../raketasks/backup_restore.md#uploading-backups-to-a-remote-cloud-storage)|No|
+| [Job artifacts](job_artifacts.md#using-object-storage) and [incremental logging](job_logs.md#new-incremental-logging-architecture) | Yes |
+| [LFS objects](lfs/index.md#storing-lfs-objects-in-remote-object-storage) | Yes |
+| [Uploads](uploads.md#using-object-storage-core-only) | Yes |
+| [Container Registry](packages/container_registry.md#use-object-storage) (optional feature) | No |
+| [Merge request diffs](merge_request_diffs.md#using-object-storage) | Yes |
+| [Mattermost](https://docs.mattermost.com/administration/config-settings.html#file-storage)| No |
+| [Packages](packages/index.md#using-object-storage) (optional feature) **(PREMIUM ONLY)** | Yes |
+| [Dependency Proxy](packages/dependency_proxy.md#using-object-storage) (optional feature) **(PREMIUM ONLY)** | Yes |
+| [Pseudonymizer](pseudonymizer.md#configuration) (optional feature) **(ULTIMATE ONLY)** | No |
+| [Autoscale Runner caching](https://docs.gitlab.com/runner/configuration/autoscale.html#distributed-runners-caching) (optional for improved performance) | No |
+| [Terraform state files](terraform_state.md#using-object-storage-core-only) | Yes |
### Other alternatives to filesystem storage
@@ -69,7 +470,7 @@ backups might not be realised until the organisation had a critical requirement
### S3 API compatibility issues
Not all S3 providers [are fully compatible](../raketasks/backup_restore.md#other-s3-providers)
-with the Fog library that GitLab uses. Symptoms include:
+with the Fog library that GitLab uses. Symptoms include an error in `production.log`:
```plaintext
411 Length Required
@@ -143,14 +544,26 @@ might generate `ETag mismatch` errors.
If you are seeing this ETag mismatch error with Amazon Web Services S3,
it's likely this is due to [encryption settings on your bucket](https://docs.aws.amazon.com/AmazonS3/latest/API/RESTCommonResponseHeaders.html).
-See the section on [using Amazon instance profiles](#using-amazon-instance-profiles) on how to fix this issue.
+To fix this issue, you have two options:
-When using GitLab direct upload, the
+- [Use the consolidated object configuration](#consolidated-object-storage-configuration).
+- [Use Amazon instance profiles](#using-amazon-instance-profiles).
+
+The first option is recommended for MinIO. Otherwise, the
[workaround for MinIO](https://gitlab.com/gitlab-org/charts/gitlab/-/issues/1564#note_244497658)
is to use the `--compat` parameter on the server.
-We are working on a fix to the [GitLab Workhorse
-component](https://gitlab.com/gitlab-org/gitlab-workhorse/-/issues/222).
+Without consolidated object store configuration or instance profiles enabled,
+GitLab Workhorse will upload files to S3 using pre-signed URLs that do
+not have a `Content-MD5` HTTP header computed for them. To ensure data
+is not corrupted, Workhorse checks that the MD5 hash of the data sent
+equals the ETag header returned from the S3 server. When encryption is
+enabled, this is not the case, which causes Workhorse to report an `ETag
+mismatch` error during an upload.
+
+With the consolidated object configuration and instance profile, Workhorse has
+S3 credentials so that it can compute the `Content-MD5` header. This
+eliminates the need to compare ETag headers returned from the S3 server.
### Using Amazon instance profiles
@@ -163,66 +576,57 @@ configuration.
#### Encrypted S3 buckets
-> Introduced in [GitLab 13.1](https://gitlab.com/gitlab-org/gitlab-workhorse/-/merge_requests/466) only for instance profiles.
+> - Introduced in [GitLab 13.1](https://gitlab.com/gitlab-org/gitlab-workhorse/-/merge_requests/466) for instance profiles only.
+> - Introduced in [GitLab 13.2](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/34460) for static credentials when [consolidated object storage configuration](#consolidated-object-storage-configuration) is used.
-When configured to use an instance profile, GitLab Workhorse
-will properly upload files to S3 buckets that have [SSE-S3 or SSE-KMS
-encryption enabled by default](https://docs.aws.amazon.com/kms/latest/developerguide/services-s3.html).
-Note that customer master keys (CMKs) and SSE-C encryption are not yet
-supported since this requires supplying keys to the GitLab
-configuration.
+When configured either with an instance profile or with the consolidated
+object configuration, GitLab Workhorse properly uploads files to S3 buckets
+that have [SSE-S3 or SSE-KMS encryption enabled by
+default](https://docs.aws.amazon.com/kms/latest/developerguide/services-s3.html).
+Note that customer master keys (CMKs) and
+SSE-C encryption are [not yet supported since this requires supplying
+keys to the GitLab configuration](https://gitlab.com/gitlab-org/gitlab/-/issues/226006).
-Without instance profiles enabled (or prior to GitLab 13.1), GitLab
-Workhorse will upload files to S3 using pre-signed URLs that do not have
-a `Content-MD5` HTTP header computed for them. To ensure data is not
-corrupted, Workhorse checks that the MD5 hash of the data sent equals
-the ETag header returned from the S3 server. When encryption is enabled,
-this is not the case, which causes Workhorse to report an `ETag
-mismatch` error during an upload.
+##### Disabling the feature
-With instance profiles enabled, GitLab Workhorse uses an AWS S3 client
-that properly computes and sends the `Content-MD5` header to the server,
-which eliminates the need for comparing ETag headers. If the data is
-corrupted in transit, the S3 server will reject the file.
+The Workhorse S3 client is enabled by default when the
+[`use_iam_profile` configuration option](#iam-permissions) is set to `true`.
-#### IAM Permissions
-
-To set up an instance profile, create an Amazon Identity Access and
-Management (IAM) role with the necessary permissions. The following
-example is a role for an S3 bucket named `test-bucket`:
-
-```json
-{
- "Version": "2012-10-17",
- "Statement": [
- {
- "Sid": "VisualEditor0",
- "Effect": "Allow",
- "Action": [
- "s3:PutObject",
- "s3:GetObject",
- "s3:AbortMultipartUpload",
- "s3:DeleteObject"
- ],
- "Resource": "arn:aws:s3:::test-bucket/*"
- }
- ]
-}
-```
-
-Associate this role with your GitLab instance, and then configure GitLab
-to use it via the `use_iam_profile` configuration option. For example,
-when configuring uploads to use object storage, see the `AWS IAM profiles`
-section in [S3 compatible connection settings](uploads.md#s3-compatible-connection-settings).
-
-#### Disabling the feature
-
-The Workhorse S3 client is only enabled when the `use_iam_profile`
-configuration flag is `true`.
-
-To disable this feature, ask a GitLab administrator with [Rails console access](feature_flags.md#how-to-enable-and-disable-features-behind-flags) to run the
+The feature can be disabled using the `:use_workhorse_s3_client` feature flag. To disable the
+feature, ask a GitLab administrator with
+[Rails console access](feature_flags.md#how-to-enable-and-disable-features-behind-flags) to run the
following command:
```ruby
Feature.disable(:use_workhorse_s3_client)
```
+
+#### IAM Permissions
+
+To set up an instance profile:
+
+1. Create an Amazon Identity Access and Management (IAM) role with the necessary permissions. The
+ following example is a role for an S3 bucket named `test-bucket`:
+
+ ```json
+ {
+ "Version": "2012-10-17",
+ "Statement": [
+ {
+ "Sid": "VisualEditor0",
+ "Effect": "Allow",
+ "Action": [
+ "s3:PutObject",
+ "s3:GetObject",
+ "s3:AbortMultipartUpload",
+ "s3:DeleteObject"
+ ],
+ "Resource": "arn:aws:s3:::test-bucket/*"
+ }
+ ]
+ }
+ ```
+
+1. [Attach this role](https://aws.amazon.com/premiumsupport/knowledge-center/attach-replace-ec2-instance-profile/)
+ to the EC2 instance hosting your GitLab instance.
+1. Configure GitLab to use it via the `use_iam_profile` configuration option.
diff --git a/doc/administration/operations/fast_ssh_key_lookup.md b/doc/administration/operations/fast_ssh_key_lookup.md
index 9f67c927128..b874a4257f0 100644
--- a/doc/administration/operations/fast_ssh_key_lookup.md
+++ b/doc/administration/operations/fast_ssh_key_lookup.md
@@ -3,7 +3,8 @@
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/1631) in [GitLab Starter](https://about.gitlab.com/pricing/) 9.3.
> - [Available in](https://gitlab.com/gitlab-org/gitlab/-/issues/3953) GitLab Community Edition 10.4.
-NOTE: **Note:** This document describes a drop-in replacement for the
+NOTE: **Note:**
+This document describes a drop-in replacement for the
`authorized_keys` file. For normal (non-deploy key) users, consider using
[SSH certificates](ssh_certificates.md). They are even faster, but are not a
drop-in replacement.
@@ -67,19 +68,25 @@ sudo service ssh reload
sudo service sshd reload
```
-Confirm that SSH is working by removing your user's SSH key in the UI, adding a
-new one, and attempting to pull a repository.
+Confirm that SSH is working by commenting out your user's key in the `authorized_keys`
+(start the line with a `#` to comment it), and attempting to pull a repository.
-NOTE: **Note:** For Omnibus Docker, `AuthorizedKeysCommand` is setup by default in
+A successful pull would mean that GitLab was able to find the key in the database,
+since it is not present in the file anymore.
+
+NOTE: **Note:**
+For Omnibus Docker, `AuthorizedKeysCommand` is setup by default in
GitLab 11.11 and later.
-NOTE: **Note:** For Installations from source, the command would be located at
+NOTE: **Note:**
+For Installations from source, the command would be located at
`/home/git/gitlab-shell/bin/gitlab-shell-authorized-keys-check` if [the install from source](../../install/installation.md#install-gitlab-shell) instructions were followed.
You might want to consider creating a wrapper script somewhere else since this command needs to be
owned by `root` and not be writable by group or others. You could also consider changing the ownership of this command
as required, but that might require temporary ownership changes during `gitlab-shell` upgrades.
-CAUTION: **Caution:** Do not disable writes until SSH is confirmed to be working
+CAUTION: **Caution:**
+Do not disable writes until SSH is confirmed to be working
perfectly, because the file will quickly become out-of-date.
In the case of lookup failures (which are common), the `authorized_keys`
@@ -96,6 +103,8 @@ Again, confirm that SSH is working by removing your user's SSH key in the UI,
adding a new one, and attempting to pull a repository.
Then you can backup and delete your `authorized_keys` file for best performance.
+The current users' keys are already present in the database, so there is no need for migration
+or for asking users to re-add their keys.
## How to go back to using the `authorized_keys` file
@@ -200,3 +209,13 @@ the database. The following instructions can be used to build OpenSSH 7.5:
# Only run this if you run into a problem logging in
yum downgrade openssh-server openssh openssh-clients
```
+
+## SELinux support and limitations
+
+> [Introduced](https://gitlab.com/gitlab-org/omnibus-gitlab/-/issues/2855) in GitLab 10.5.
+
+GitLab supports `authorized_keys` database lookups with [SELinux](https://en.wikipedia.org/wiki/Security-Enhanced_Linux).
+
+Because the SELinux policy is static, GitLab doesn't support the ability to change
+internal Unicorn ports at the moment. Admins would have to create a special `.te`
+file for the environment, since it isn't generated dynamically.
diff --git a/doc/administration/operations/filesystem_benchmarking.md b/doc/administration/operations/filesystem_benchmarking.md
index c5c5a8b4313..856061348ed 100644
--- a/doc/administration/operations/filesystem_benchmarking.md
+++ b/doc/administration/operations/filesystem_benchmarking.md
@@ -65,7 +65,8 @@ operations per second.
### Simple benchmarking
-NOTE: **Note:** This test is naive but may be useful if `fio` is not
+NOTE: **Note:**
+This test is naive but may be useful if `fio` is not
available on the system. It's possible to receive good results on this
test but still have poor performance due to read speed and various other
factors.
diff --git a/doc/administration/operations/puma.md b/doc/administration/operations/puma.md
index af28335ef91..62b93d40a6b 100644
--- a/doc/administration/operations/puma.md
+++ b/doc/administration/operations/puma.md
@@ -1,11 +1,11 @@
# Switching to Puma
-## Puma
-
As of GitLab 12.9, [Puma](https://github.com/puma/puma) has replaced [Unicorn](https://yhbt.net/unicorn/).
-as the default web server. Starting with 13.0, both all-in-one package based
-installations as well as Helm chart based installations will run Puma instead of
-Unicorn unless explicitly specified not to.
+as the default web server. From GitLab 13.0, the following run Puma instead of Unicorn unless
+explicitly configured not to:
+
+- All-in-one package-based installations.
+- Helm chart-based installations.
## Why switch to Puma?
@@ -32,10 +32,12 @@ Additionally we strongly recommend that multi-node deployments [configure their
## Performance caveat when using Puma with Rugged
For deployments where NFS is used to store Git repository, we allow GitLab to use
-[Direct Git Access](../gitaly/#direct-git-access-in-gitlab-rails) to improve performance via usage of [Rugged](https://github.com/libgit2/rugged).
+[direct Git access](../gitaly/index.md#direct-access-to-git-in-gitlab) to improve performance using
+[Rugged](https://github.com/libgit2/rugged).
-Rugged usage is automatically enabled if Direct Git Access is present, unless it
-is disabled by [feature flags](../../development/gitaly.md#legacy-rugged-code).
+Rugged usage is automatically enabled if direct Git access
+[is available](../gitaly/index.md#how-it-works), unless it is disabled by
+[feature flags](../../development/gitaly.md#legacy-rugged-code).
MRI Ruby uses a GVL. This allows MRI Ruby to be multi-threaded, but running at
most on a single core. Since Rugged can use a thread for long periods of
diff --git a/doc/administration/operations/unicorn.md b/doc/administration/operations/unicorn.md
index eabf99eb08c..a1593c3a6c3 100644
--- a/doc/administration/operations/unicorn.md
+++ b/doc/administration/operations/unicorn.md
@@ -45,7 +45,7 @@ master process has PID 56227 below.
The main tunable options for Unicorn are the number of worker processes and the
request timeout after which the Unicorn master terminates a worker process.
See the [Omnibus GitLab Unicorn settings
-documentation](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/doc/settings/unicorn.md)
+documentation](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/doc/settings/unicorn.html)
if you want to adjust these settings.
## unicorn-worker-killer
diff --git a/doc/administration/packages/container_registry.md b/doc/administration/packages/container_registry.md
index 8f55345a9a8..44f1d075a5e 100644
--- a/doc/administration/packages/container_registry.md
+++ b/doc/administration/packages/container_registry.md
@@ -76,7 +76,7 @@ where:
| `port` | The port under which the external Registry domain will listen on. |
| `api_url` | The internal API URL under which the Registry is exposed to. It defaults to `http://localhost:5000`. |
| `key` | The private key location that is a pair of Registry's `rootcertbundle`. Read the [token auth configuration documentation](https://docs.docker.com/registry/configuration/#token). |
-| `path` | This should be the same directory like specified in Registry's `rootdirectory`. Read the [storage configuration documentation](https://docs.docker.com/registry/configuration/#storage). This path needs to be readable by the GitLab user, the web-server user and the Registry user. Read more in [#container-registry-storage-path](#container-registry-storage-path). |
+| `path` | This should be the same directory like specified in Registry's `rootdirectory`. Read the [storage configuration documentation](https://docs.docker.com/registry/configuration/#storage). This path needs to be readable by the GitLab user, the web-server user and the Registry user. Read more in [#configure-storage-for-the-container-registry](#configure-storage-for-the-container-registry). |
| `issuer` | This should be the same value as configured in Registry's `issuer`. Read the [token auth configuration documentation](https://docs.docker.com/registry/configuration/#token). |
NOTE: **Note:**
@@ -313,11 +313,28 @@ the Container Registry by themselves, follow the steps below.
1. Save the file and [restart GitLab](../restart_gitlab.md#installations-from-source) for the changes to take effect.
-## Container Registry storage path
+## Configure storage for the Container Registry
-NOTE: **Note:**
-For configuring storage in the cloud instead of the filesystem, see the
-[storage driver configuration](#container-registry-storage-driver).
+You can configure the Container Registry to use various storage backends by
+configuring a storage driver. By default the GitLab Container Registry
+is configured to use the [filesystem driver](#use-filesystem)
+configuration.
+
+The different supported drivers are:
+
+| Driver | Description |
+|------------|-------------------------------------|
+| filesystem | Uses a path on the local filesystem |
+| Azure | Microsoft Azure Blob Storage |
+| gcs | Google Cloud Storage |
+| s3 | Amazon Simple Storage Service. Be sure to configure your storage bucket with the correct [S3 Permission Scopes](https://docs.docker.com/registry/storage-drivers/s3/#s3-permission-scopes). |
+| swift | OpenStack Swift Object Storage |
+| oss | Aliyun OSS |
+
+Read more about the individual driver's configuration options in the
+[Docker Registry docs](https://docs.docker.com/registry/configuration/#storage).
+
+### Use filesystem
If you want to store your images on the filesystem, you can change the storage
path for the Container Registry, follow the steps below.
@@ -327,7 +344,8 @@ This path is accessible to:
- The user running the Container Registry daemon.
- The user running GitLab.
-CAUTION: **Warning** You should confirm that all GitLab, Registry and web server users
+CAUTION: **Warning:**
+You should confirm that all GitLab, Registry and web server users
have access to this directory.
**Omnibus GitLab installations**
@@ -358,30 +376,15 @@ The default location where images are stored in source installations, is
1. Save the file and [restart GitLab](../restart_gitlab.md#installations-from-source) for the changes to take effect.
-### Container Registry storage driver
+### Use object storage
-You can configure the Container Registry to use a different storage backend by
-configuring a different storage driver. By default the GitLab Container Registry
-is configured to use the filesystem driver, which makes use of [storage path](#container-registry-storage-path)
-configuration.
-
-The different supported drivers are:
-
-| Driver | Description |
-|------------|-------------------------------------|
-| filesystem | Uses a path on the local filesystem |
-| Azure | Microsoft Azure Blob Storage |
-| gcs | Google Cloud Storage |
-| s3 | Amazon Simple Storage Service. Be sure to configure your storage bucket with the correct [S3 Permission Scopes](https://docs.docker.com/registry/storage-drivers/s3/#s3-permission-scopes). |
-| swift | OpenStack Swift Object Storage |
-| oss | Aliyun OSS |
-
-Read more about the individual driver's configuration options in the
-[Docker Registry docs](https://docs.docker.com/registry/configuration/#storage).
+If you want to store your images on object storage, you can change the storage
+driver for the Container Registry.
[Read more about using object storage with GitLab](../object_storage.md).
-CAUTION: **Warning:** GitLab will not backup Docker images that are not stored on the
+CAUTION: **Warning:**
+GitLab will not backup Docker images that are not stored on the
filesystem. Remember to enable backups with your object storage provider if
desired.
@@ -435,21 +438,43 @@ storage:
NOTE: **Note:**
`your-s3-bucket` should only be the name of a bucket that exists, and can't include subdirectories.
-**Migrate without downtime**
+#### Migrate to object storage without downtime
+
+To migrate storage without stopping the Container Registry, set the Container Registry
+to read-only mode. On large instances, this may require the Container Registry
+to be in read-only mode for a while. During this time,
+you can pull from the Container Registry, but you cannot push.
+
+1. Optional: To reduce the amount of data to be migrated, run the [garbage collection tool without downtime](#performing-garbage-collection-without-downtime).
+1. Copy initial data to your S3 bucket, for example with the AWS CLI [`cp`](https://awscli.amazonaws.com/v2/documentation/api/latest/reference/s3/cp.html)
+ or [`sync`](https://awscli.amazonaws.com/v2/documentation/api/latest/reference/s3/sync.html)
+ command. Make sure to keep the `docker` folder as the top-level folder inside the bucket.
+
+ ```shell
+ aws s3 sync registry s3://mybucket
+ ```
+
+1. To perform the final data sync,
+ [put the Container Registry in `read-only` mode](#performing-garbage-collection-without-downtime) and
+ [reconfigure GitLab](../restart_gitlab.md#omnibus-gitlab-reconfigure).
+1. Sync any changes since the initial data load to your S3 bucket and delete files that exist in the destination bucket but not in the source:
+
+ ```shell
+ aws s3 sync registry s3://mybucket --delete
+ ```
-To migrate the data to AWS S3 without downtime:
+ DANGER: **Danger:**
+ The `--delete` flag will delete files that exist in the destination but not in the source.
+ Make sure not to swap the source and destination, or you will delete all data in the Registry.
-1. To reduce the amount of data to be migrated, run the [garbage collection tool without downtime](#performing-garbage-collection-without-downtime). Part of this process sets the registry to `read-only`.
-1. Copy the data to your AWS S3 bucket, for example with [AWS CLI's `cp`](https://docs.aws.amazon.com/cli/latest/reference/s3/cp.html) command.
-1. Configure your registry to use the S3 bucket for storage.
-1. Put the registry back to `read-write`.
-1. [Reconfigure GitLab](../restart_gitlab.md#omnibus-gitlab-reconfigure) for the changes to take effect.
+1. Configure your registry to [use the S3 bucket for storage](#use-object-storage).
+1. For the changes to take effect, set the Registry back to `read-write` mode and [reconfigure GitLab](../restart_gitlab.md#omnibus-gitlab-reconfigure).
### Disable redirect for storage driver
By default, users accessing a registry configured with a remote backend are redirected to the default backend for the storage driver. For example, registries can be configured using the `s3` storage driver, which redirects requests to a remote S3 bucket to alleviate load on the GitLab server.
-However, this behavior is undesirable for registries used by internal hosts that usually can't access public servers. To disable redirects, set the `disable` flag to true as follows. This makes all traffic to always go through the Registry service. This results in improved security (less surface attack as the storage backend is not publicly accessible), but worse performance (all traffic is redirected via the service).
+However, this behavior is undesirable for registries used by internal hosts that usually can't access public servers. To disable redirects and [proxy download](../object_storage.md#proxy-download), set the `disable` flag to true as follows. This makes all traffic always go through the Registry service. This results in improved security (less surface attack as the storage backend is not publicly accessible), but worse performance (all traffic is redirected via the service).
**Omnibus GitLab installations**
@@ -779,13 +804,15 @@ that you have backed up all registry data.
> [Introduced](https://gitlab.com/gitlab-org/omnibus-gitlab/-/merge_requests/764) in GitLab 8.8.
-You can perform a garbage collection without stopping the Container Registry by setting
-it into a read-only mode and by not using the built-in command. During this time,
+You can perform garbage collection without stopping the Container Registry by putting
+it in read-only mode and by not using the built-in command. On large instances
+this could require Container Registry to be in read-only mode for a while.
+During this time,
you will be able to pull from the Container Registry, but you will not be able to
push.
NOTE: **Note:**
-By default, the [registry storage path](#container-registry-storage-path)
+By default, the [registry storage path](#configure-storage-for-the-container-registry)
is `/var/opt/gitlab/gitlab-rails/shared/registry`.
To enable the read-only mode:
@@ -927,7 +954,7 @@ larger images, or images that take longer than 5 minutes to push, users may
encounter this error.
Administrators can increase the token duration in **Admin area > Settings >
-Container Registry > Authorization token duration (minutes)**.
+CI/CD > Container Registry > Authorization token duration (minutes)**.
### AWS S3 with the GitLab registry error when pushing large images
diff --git a/doc/administration/packages/dependency_proxy.md b/doc/administration/packages/dependency_proxy.md
index 565a4521c2a..2f9cfecc9d4 100644
--- a/doc/administration/packages/dependency_proxy.md
+++ b/doc/administration/packages/dependency_proxy.md
@@ -87,6 +87,11 @@ store the blobs of the dependency proxy.
[Read more about using object storage with GitLab](../object_storage.md).
+NOTE: **Note:**
+In GitLab 13.2 and later, we recommend using the
+[consolidated object storage settings](../object_storage.md#consolidated-object-storage-configuration).
+This section describes the earlier configuration format.
+
**Omnibus GitLab installations**
1. Edit `/etc/gitlab/gitlab.rb` and add the following lines (uncomment where
diff --git a/doc/administration/packages/index.md b/doc/administration/packages/index.md
index 5088dd86a86..5d07136ef40 100644
--- a/doc/administration/packages/index.md
+++ b/doc/administration/packages/index.md
@@ -99,6 +99,9 @@ store packages.
[Read more about using object storage with GitLab](../object_storage.md).
+NOTE: **Note:**
+We recommend using the [consolidated object storage settings](../object_storage.md#consolidated-object-storage-configuration). The following instructions apply to the original config format.
+
**Omnibus GitLab installations**
1. Edit `/etc/gitlab/gitlab.rb` and add the following lines (uncomment where
diff --git a/doc/administration/pages/index.md b/doc/administration/pages/index.md
index a7a3a86de8e..4efd92eaa07 100644
--- a/doc/administration/pages/index.md
+++ b/doc/administration/pages/index.md
@@ -190,7 +190,7 @@ control over how the Pages daemon runs and serves content in your environment.
| Setting | Description |
| ------- | ----------- |
| `pages_external_url` | The URL where GitLab Pages is accessible, including protocol (HTTP / HTTPS). If `https://` is used, you must also set `gitlab_pages['ssl_certificate']` and `gitlab_pages['ssl_certificate_key']`.
-| **gitlab_pages[]** | |
+| `gitlab_pages[]` | |
| `access_control` | Whether to enable [access control](index.md#access-control).
| `api_secret_key` | Full path to file with secret key used to authenticate with the GitLab API. Auto-generated when left unset.
| `artifacts_server` | Enable viewing [artifacts](../job_artifacts.md) in GitLab Pages.
@@ -213,7 +213,7 @@ control over how the Pages daemon runs and serves content in your environment.
| `internal_gitlab_server` | Internal GitLab server address used exclusively for API requests. Useful if you want to send that traffic over an internal load balancer. Defaults to GitLab `external_url`.
| `listen_proxy` | The addresses to listen on for reverse-proxy requests. Pages will bind to these addresses' network socket and receives incoming requests from it. Sets the value of `proxy_pass` in `$nginx-dir/conf/gitlab-pages.conf`.
| `log_directory` | Absolute path to a log directory.
-| `log_format` | The log output format: 'text' or 'json'.
+| `log_format` | The log output format: `text` or `json`.
| `log_verbose` | Verbose logging, true/false.
| `max_connections` | Limit on the number of concurrent connections to the HTTP, HTTPS or proxy listeners.
| `metrics_address` | The address to listen on for metrics requests.
@@ -225,14 +225,14 @@ control over how the Pages daemon runs and serves content in your environment.
| `tls_max_version` | Specifies the maximum SSL/TLS version ("ssl3", "tls1.0", "tls1.1" or "tls1.2").
| `tls_min_version` | Specifies the minimum SSL/TLS version ("ssl3", "tls1.0", "tls1.1" or "tls1.2").
| `use_http2` | Enable HTTP2 support.
-| **gitlab_pages['env'][]** | |
+| `gitlab_pages['env'][]` | |
| `http_proxy` | Configure GitLab Pages to use an HTTP Proxy to mediate traffic between Pages and GitLab. Sets an environment variable `http_proxy` when starting Pages daemon.
-| **gitlab_rails[]** | |
+| `gitlab_rails[]` | |
| `pages_domain_verification_cron_worker` | Schedule for verifying custom GitLab Pages domains.
| `pages_domain_ssl_renewal_cron_worker` | Schedule for obtaining and renewing SSL certificates through Let's Encrypt for GitLab Pages domains.
| `pages_domain_removal_cron_worker` | Schedule for removing unverified custom GitLab Pages domains.
| `pages_path` | The directory on disk where pages are stored, defaults to `GITLAB-RAILS/shared/pages`.
-| **pages_nginx[]** | |
+| `pages_nginx[]` | |
| `enable` | Include a virtual host `server{}` block for Pages inside NGINX. Needed for NGINX to proxy traffic back to the Pages daemon. Set to `false` if the Pages daemon should directly receive all requests, for example, when using [custom domains](index.md#custom-domains).
---
@@ -408,6 +408,9 @@ pages:
### Using a custom Certificate Authority (CA)
+NOTE: **Note:**
+[Before 13.2](https://gitlab.com/gitlab-org/omnibus-gitlab/-/merge_requests/4289), when using Omnibus, a [workaround was required](https://docs.gitlab.com/13.1/ee/administration/pages/index.html#using-a-custom-certificate-authority-ca).
+
When using certificates issued by a custom CA, [Access Control](../../user/project/pages/pages_access_control.md#gitlab-pages-access-control) and
the [online view of HTML job artifacts](../../ci/pipelines/job_artifacts.md#browsing-artifacts)
will fail to work if the custom CA is not recognized.
@@ -415,28 +418,10 @@ will fail to work if the custom CA is not recognized.
This usually results in this error:
`Post /oauth/token: x509: certificate signed by unknown authority`.
-For installation from source this can be fixed by installing the custom Certificate
+For installation from source, this can be fixed by installing the custom Certificate
Authority (CA) in the system certificate store.
-For Omnibus, normally this would be fixed by [installing a custom CA in Omnibus GitLab](https://docs.gitlab.com/omnibus/settings/ssl.html#install-custom-public-certificates)
-but a [bug](https://gitlab.com/gitlab-org/gitlab/-/issues/25411) is currently preventing
-that method from working. Use the following workaround:
-
-1. Append your GitLab server TLS/SSL certificate to `/opt/gitlab/embedded/ssl/certs/cacert.pem` where `gitlab-domain-example.com` is your GitLab application URL
-
- ```shell
- printf "\ngitlab-domain-example.com\n===========================\n" | sudo tee --append /opt/gitlab/embedded/ssl/certs/cacert.pem
- echo -n | openssl s_client -connect gitlab-domain-example.com:443 | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' | sudo tee --append /opt/gitlab/embedded/ssl/certs/cacert.pem
- ```
-
-1. [Restart](../restart_gitlab.md) the GitLab Pages Daemon. For Omnibus GitLab instances:
-
- ```shell
- sudo gitlab-ctl restart gitlab-pages
- ```
-
-CAUTION: **Caution:**
-Some Omnibus GitLab upgrades will revert this workaround and you'll need to apply it again.
+For Omnibus, this is fixed by [installing a custom CA in Omnibus GitLab](https://docs.gitlab.com/omnibus/settings/ssl.html#install-custom-public-certificates).
## Activate verbose logging for daemon
@@ -526,10 +511,17 @@ The following procedure includes steps to back up and edit the
`gitlab-secrets.json` file. This file contains secrets that control
database encryption. Proceed with caution.
+1. Create a backup of the secrets file on the **GitLab server**:
+
+ ```shell
+ cp /etc/gitlab/gitlab-secrets.json /etc/gitlab/gitlab-secrets.json.bak
+ ```
+
1. On the **GitLab server**, to enable Pages, add the following to `/etc/gitlab/gitlab.rb`:
```ruby
gitlab_pages['enable'] = true
+ pages_external_url "http://<pages_server_URL>"
```
1. Optionally, to enable [access control](#access-control), add the following to `/etc/gitlab/gitlab.rb`:
@@ -542,26 +534,25 @@ database encryption. Proceed with caution.
changes to take effect. The `gitlab-secrets.json` file is now updated with the
new configuration.
-1. Create a backup of the secrets file on the **GitLab server**:
-
- ```shell
- cp /etc/gitlab/gitlab-secrets.json /etc/gitlab/gitlab-secrets.json.bak
- ```
-
1. Set up a new server. This will become the **Pages server**.
-1. Create an [NFS share](../high_availability/nfs_host_client_setup.md) on the new server and configure this share to
- allow access from your main **GitLab server**. For this example, we use the
+1. Create an [NFS share](../high_availability/nfs_host_client_setup.md)
+ on the **Pages server** and configure this share to
+ allow access from your main **GitLab server**.
+ Note that the example there is more general and
+ shares several sub-directories from `/home` to several `/nfs/home` mountpoints.
+ For our Pages-specific example here, we instead share only the
default GitLab Pages folder `/var/opt/gitlab/gitlab-rails/shared/pages`
- as the shared folder on the new server and we will mount it to `/mnt/pages`
+ from the **Pages server** and we mount it to `/mnt/pages`
on the **GitLab server**.
+ Therefore, omit "Step 4" there.
1. On the **Pages server**, install Omnibus GitLab and modify `/etc/gitlab/gitlab.rb`
to include:
```ruby
- external_url 'http://<ip-address-of-the-server>'
- pages_external_url "http://<your-pages-server-URL>"
+ external_url 'http://<gitlab_server_IP_or_URL>'
+ pages_external_url "http://<pages_server_URL>"
postgresql['enable'] = false
redis['enable'] = false
prometheus['enable'] = false
@@ -581,7 +572,15 @@ database encryption. Proceed with caution.
```
1. Copy the `/etc/gitlab/gitlab-secrets.json` file from the **GitLab server**
- to the **Pages server**.
+ to the **Pages server**, for example via the NFS share.
+
+ ```shell
+ # On the GitLab server
+ cp /etc/gitlab/gitlab-secrets.json /mnt/pages/gitlab-secrets.json
+
+ # On the Pages server
+ mv /var/opt/gitlab/gitlab-rails/shared/pages/gitlab-secrets.json /etc/gitlab/gitlab-secrets.json
+ ```
1. [Reconfigure GitLab](../restart_gitlab.md#omnibus-gitlab-reconfigure) for the changes to take effect.
@@ -589,13 +588,13 @@ database encryption. Proceed with caution.
```ruby
gitlab_pages['enable'] = false
- pages_external_url "http://<your-pages-server-URL>"
+ pages_external_url "http://<pages_server_URL>"
gitlab_rails['pages_path'] = "/mnt/pages"
```
1. [Reconfigure GitLab](../restart_gitlab.md#omnibus-gitlab-reconfigure) for the changes to take effect.
-It is possible to run GitLab Pages on multiple servers if you wish to distribute
+It's possible to run GitLab Pages on multiple servers if you wish to distribute
the load. You can do this through standard load balancing practices such as
configuring your DNS server to return multiple IPs for your Pages server,
configuring a load balancer to work at the IP level, and so on. If you wish to
@@ -655,3 +654,45 @@ The fix is to correct the source file permissions and restart Pages:
sudo chmod 644 /opt/gitlab/embedded/ssl/certs/cacert.pem
sudo gitlab-ctl restart gitlab-pages
```
+
+### `dial tcp: lookup gitlab.example.com` and `x509: certificate signed by unknown authority`
+
+When setting both `inplace_chroot` and `access_control` to `true`, you might encounter errors like:
+
+```plaintext
+dial tcp: lookup gitlab.example.com on [::1]:53: dial udp [::1]:53: connect: cannot assign requested address
+```
+
+Or:
+
+```plaintext
+open /opt/gitlab/embedded/ssl/certs/cacert.pem: no such file or directory
+x509: certificate signed by unknown authority
+```
+
+The reason for those errors is that the files `resolv.conf` and `ca-bundle.pem` are missing inside the chroot.
+The fix is to copy the host's `/etc/resolv.conf` and GitLab's certificate bundle inside the chroot:
+
+```shell
+sudo mkdir -p /var/opt/gitlab/gitlab-rails/shared/pages/etc/ssl
+sudo mkdir -p /var/opt/gitlab/gitlab-rails/shared/pages/opt/gitlab/embedded/ssl/certs/
+
+sudo cp /etc/resolv.conf /var/opt/gitlab/gitlab-rails/shared/pages/etc
+sudo cp /opt/gitlab/embedded/ssl/certs/cacert.pem /var/opt/gitlab/gitlab-rails/shared/pages/opt/gitlab/embedded/ssl/certs/
+sudo cp /opt/gitlab/embedded/ssl/certs/cacert.pem /var/opt/gitlab/gitlab-rails/shared/pages/etc/ssl/ca-bundle.pem
+```
+
+### 404 error after transferring project to a different group or user
+
+If you encounter a `404 Not Found` error a Pages site after transferring a project to
+another group or user, you must trigger adomain configuration update for Pages. To do
+so, write something in the `.update` file. The Pages daemon monitors for changes to this
+file, and reloads the configuration when changes occur.
+
+Use this example to fix a `404 Not Found` error after transferring a project with Pages:
+
+```shell
+date > /var/opt/gitlab/gitlab-rails/shared/pages/.update
+```
+
+If you've customized the Pages storage path, adjust the command above to use your custom path.
diff --git a/doc/administration/postgresql/index.md b/doc/administration/postgresql/index.md
new file mode 100644
index 00000000000..7e0a2f3cae1
--- /dev/null
+++ b/doc/administration/postgresql/index.md
@@ -0,0 +1,36 @@
+---
+type: reference
+---
+
+# Configuring PostgreSQL for scaling
+
+In this section, you'll be guided through configuring a PostgreSQL database to
+be used with GitLab in one of our [Scalable and Highly Available Setups](../reference_architectures/index.md).
+There are essentially three setups to choose from.
+
+## PostgreSQL replication and failover with Omnibus GitLab **(PREMIUM ONLY)**
+
+This setup is for when you have installed GitLab using the
+[Omnibus GitLab **Enterprise Edition** (EE) package](https://about.gitlab.com/install/?version=ee).
+
+All the tools that are needed like PostgreSQL, PgBouncer, Repmgr are bundled in
+the package, so you can it to set up the whole PostgreSQL infrastructure (primary, replica).
+
+[> Read how to set up PostgreSQL replication and failover using Omnibus GitLab](replication_and_failover.md)
+
+## Standalone PostgreSQL using Omnibus GitLab **(CORE ONLY)**
+
+This setup is for when you have installed the
+[Omnibus GitLab packages](https://about.gitlab.com/install/) (CE or EE),
+to use the bundled PostgreSQL having only its service enabled.
+
+[> Read how to set up a standalone PostgreSQL instance using Omnibus GitLab](standalone.md)
+
+## Provide your own PostgreSQL instance **(CORE ONLY)**
+
+This setup is for when you have installed GitLab using the
+[Omnibus GitLab packages](https://about.gitlab.com/install/) (CE or EE),
+or installed it [from source](../../install/installation.md), but you want to use
+your own external PostgreSQL server.
+
+[> Read how to set up an external PostgreSQL instance](external.md)
diff --git a/doc/administration/postgresql/replication_and_failover.md b/doc/administration/postgresql/replication_and_failover.md
index aa95b983d20..5f550f09e5b 100644
--- a/doc/administration/postgresql/replication_and_failover.md
+++ b/doc/administration/postgresql/replication_and_failover.md
@@ -1,16 +1,15 @@
# PostgreSQL replication and failover with Omnibus GitLab **(PREMIUM ONLY)**
-> Important notes:
->
-> - This document will focus only on configuration supported with [GitLab Premium](https://about.gitlab.com/pricing/), using the Omnibus GitLab package.
-> - If you are a Community Edition or Starter user, consider using a cloud hosted solution.
-> - This document will not cover installations from source.
->
-> - If a setup with replication and failover is not what you were looking for, see the [database configuration document](https://docs.gitlab.com/omnibus/settings/database.html)
-> for the Omnibus GitLab packages.
->
-> Please read this document fully before attempting to configure PostgreSQL with
-> replication and failover for GitLab.
+This document will focus only on configuration supported with [GitLab Premium](https://about.gitlab.com/pricing/), using the Omnibus GitLab package.
+If you are a Community Edition or Starter user, consider using a cloud hosted solution.
+This document will not cover installations from source.
+
+If a setup with replication and failover is not what you were looking for, see
+the [database configuration document](https://docs.gitlab.com/omnibus/settings/database.html)
+for the Omnibus GitLab packages.
+
+It's recommended to read this document fully before attempting to configure PostgreSQL with
+replication and failover for GitLab.
## Architecture
@@ -967,7 +966,8 @@ after it has been restored to service.
gitlab-ctl restart repmgrd
```
- CAUTION: **Warning:** When the server is brought back online, and before
+ CAUTION: **Warning:**
+ When the server is brought back online, and before
you switch it to a standby node, repmgr will report that there are two masters.
If there are any clients that are still attempting to write to the old master,
this will cause a split, and the old master will need to be resynced from
@@ -1127,3 +1127,213 @@ If you're running into an issue with a component not outlined here, be sure to c
- [Consul](../high_availability/consul.md#troubleshooting)
- [PostgreSQL](https://docs.gitlab.com/omnibus/settings/database.html#troubleshooting)
- [GitLab application](../high_availability/gitlab.md#troubleshooting)
+
+## Patroni
+
+NOTE: **Note:**
+Starting from GitLab 13.1, Patroni is available for **experimental** use to replace repmgr. Due to its
+experimental nature, Patroni support is **subject to change without notice.**
+
+Patroni is an opinionated solution for PostgreSQL high-availability. It takes the control of PostgreSQL, overrides its
+configuration and manages its lifecycle (start, stop, restart). This is a more active approach when compared to repmgr.
+Both repmgr and Patroni are both supported and available. But Patroni will be the default (and perhaps the only) option
+for PostgreSQL 12 clustering and cascading replication for Geo deployments.
+
+The [architecture](#example-recommended-setup-manual-steps) (that was mentioned above) does not change for Patroni.
+You do not need any special consideration for Patroni while provisioning your database nodes. Patroni heavily relies on
+Consul to store the state of the cluster and elect a leader. Any failure in Consul cluster and its leader election will
+propagate to Patroni cluster as well.
+
+Similar to repmgr, Patroni monitors the cluster and handles failover. When the primary node fails it works with Consul
+to notify PgBouncer. However, as opposed to repmgr, on failure, Patroni handles the transitioning of the old primary to
+a replica and rejoins it to the cluster automatically. So you do not need any manual operation for recovering the
+cluster as you do with repmgr.
+
+With Patroni the connection flow is slightly different. Patroni on each node connects to Consul agent to join the
+cluster. Only after this point it decides if the node is the primary or a replica. Based on this decision, it configures
+and starts PostgreSQL which it communicates with directly over a Unix socket. This implies that if Consul cluster is not
+functional or does not have a leader, Patroni and by extension PostgreSQL will not start. Patroni also exposes a REST
+API which can be accessed via its [default port](https://docs.gitlab.com/omnibus/package-information/defaults.html#patroni)
+on each node.
+
+### Configuring Patroni cluster
+
+You must enable Patroni explicitly to be able to use it (with `patroni['enable'] = true`). When Patroni is enabled
+repmgr will be disabled automatically.
+
+Any PostgreSQL configuration item that controls replication, for example `wal_level`, `max_wal_senders`, etc, are strictly
+controlled by Patroni and will override the original settings that you make with the `postgresql[...]` configuration key.
+Hence, they are all separated and placed under `patroni['postgresql'][...]`. This behavior is limited to replication.
+Patroni honours any other PostgreSQL configuration that was made with the `postgresql[...]` configuration key. For example,
+`max_wal_senders` by default is set to `5`. If you wish to change this you must set it with the `patroni['postgresql']['max_wal_senders']`
+configuration key.
+
+The configuration of Patroni node is very similar to a repmgr but shorter. When Patroni is enabled, first you can ignore
+any replication setting of PostgreSQL (it will be overwritten anyway). Then you can remove any `repmgr[...]` or
+repmgr-specific configuration as well. Especially, make sure that you remove `postgresql['shared_preload_libraries'] = 'repmgr_funcs'`.
+
+Here is an example similar to [the one that was done with repmgr](#configuring-the-database-nodes):
+
+```ruby
+# Disable all components except PostgreSQL and Repmgr and Consul
+roles['postgres_role']
+
+# Enable Patroni
+patroni['enable'] = true
+
+# PostgreSQL configuration
+postgresql['listen_address'] = '0.0.0.0'
+
+# Disable automatic database migrations
+gitlab_rails['auto_migrate'] = false
+
+# Configure the Consul agent
+consul['services'] = %w(postgresql)
+
+# START user configuration
+# Please set the real values as explained in Required Information section
+#
+# Replace PGBOUNCER_PASSWORD_HASH with a generated md5 value
+postgresql['pgbouncer_user_password'] = 'PGBOUNCER_PASSWORD_HASH'
+# Replace POSTGRESQL_PASSWORD_HASH with a generated md5 value
+postgresql['sql_user_password'] = 'POSTGRESQL_PASSWORD_HASH'
+
+# Replace X with value of number of db nodes + 1 (OPTIONAL the default value is 5)
+patroni['postgresql']['max_wal_senders'] = X
+patroni['postgresql']['max_replication_slots'] = X
+
+# Replace XXX.XXX.XXX.XXX/YY with Network Address
+postgresql['trust_auth_cidr_addresses'] = %w(XXX.XXX.XXX.XXX/YY)
+
+# Replace placeholders:
+#
+# Y.Y.Y.Y consul1.gitlab.example.com Z.Z.Z.Z
+# with the addresses gathered for CONSUL_SERVER_NODES
+consul['configuration'] = {
+ retry_join: %w(Y.Y.Y.Y consul1.gitlab.example.com Z.Z.Z.Z)
+}
+#
+# END user configuration
+```
+
+You do not need an additional or different configuration for replica nodes. As a matter of fact, you don't have to have
+a predetermined primary node. Therefore all database nodes use the same configuration.
+
+Once the configuration of a node is done, you must [reconfigure Omnibus GitLab](../restart_gitlab.md#omnibus-gitlab-reconfigure)
+on each node for the changes to take effect.
+
+Generally, when Consul cluster is ready, the first node that [reconfigures](../restart_gitlab.md#omnibus-gitlab-reconfigure)
+becomes the leader. You do not need to sequence the nodes reconfiguration. You can run them in parallel or in any order.
+If you choose an arbitrary order you do not have any predetermined master.
+
+As opposed to repmgr, once the nodes are reconfigured you do not need any further action or additional command to join
+the replicas.
+
+#### Database authorization for Patroni
+
+Patroni uses Unix socket to manage PostgreSQL instance. Therefore, the connection from the `local` socket must be trusted.
+
+Also, replicas use the replication user (`gitlab_replicator` by default) to communicate with the leader. For this user,
+you can choose between `trust` and `md5` authentication. If you set `postgresql['sql_replication_password']`,
+Patroni will use `md5` authentication, otherwise it falls back to `trust`. You must to specify the cluster CIDR in
+`postgresql['md5_auth_cidr_addresses']` or `postgresql['trust_auth_cidr_addresses']` respectively.
+
+### Interacting with Patroni cluster
+
+You can use `gitlab-ctl patroni members` to check the status of the cluster members. To check the status of each node
+`gitlab-ctl patroni` provides two additional sub-commands, `check-leader` and `check-replica` which indicate if a node
+is the primary or a replica.
+
+When Patroni is enabled, you don't have direct control over `postgresql` service. Patroni will signal PostgreSQL's startup,
+shutdown, and restart. For example, for shutting down PostgreSQL on a node, you must shutdown Patroni on the same node
+with:
+
+```shell
+sudo gitlab-ctl stop patroni
+```
+
+### Manual failover procedure for Patroni
+
+While Patroni supports automatic failover, you also have the ability to perform
+a manual one, where you have two slightly different options:
+
+- **Failover**: allows you to perform a manual failover when there are no healthy nodes.
+ You can perform this action in any PostgreSQL node:
+
+ ```shell
+ sudo gitlab-ctl patroni failover
+ ```
+
+- **Switchover**: only works when the cluster is healthy and allows you to schedule a switchover (it can happen immediately).
+ You can perform this action in any PostgreSQL node:
+
+ ```shell
+ sudo gitlab-ctl patroni switchover
+ ```
+
+For further details on this subject, see the
+[Patroni documentation](https://patroni.readthedocs.io/en/latest/rest_api.html#switchover-and-failover-endpoints).
+
+### Recovering the Patroni cluster
+
+To recover the old primary and rejoin it to the cluster as a replica, you can simply start Patroni with:
+
+```shell
+sudo gitlab-ctl start patroni
+```
+
+No further configuration or intervention is needed.
+
+### Maintenance procedure for Patroni
+
+With Patroni enabled, you can run a planned maintenance. If you want to do some maintenance work on one node and you
+don't want Patroni to manage it, you can use put it into maintenance mode:
+
+```shell
+sudo gitlab-ctl patroni pause
+```
+
+When Patroni runs in a paused mode, it does not change the state of PostgreSQL. Once you are done you can resume Patroni:
+
+```shell
+sudo gitlab-ctl patroni resume
+```
+
+For further details, see [Patroni documentation on this subject](https://patroni.readthedocs.io/en/latest/pause.html).
+
+### Switching from repmgr to Patroni
+
+CAUTION: **Warning:**
+Although switching from repmgr to Patroni is fairly straightforward the other way around is not. Rolling back from
+Patroni to repmgr can be complicated and may involve deletion of data directory. If you need to do that, please contact
+GitLab support.
+
+You can switch an exiting database cluster to use Patroni instead of repmgr with the following steps:
+
+1. Stop repmgr on all replica nodes and lastly with the primary node:
+
+ ```shell
+ sudo gitlab-ctl stop repmgrd
+ ```
+
+1. Stop PostgreSQL on all replica nodes:
+
+ ```shell
+ sudo gitlab-ctl stop postgresql
+ ```
+
+ NOTE: **Note:**
+ Ensure that there is no `walsender` process running on the primary node.
+ `ps aux | grep walsender` must not show any running process.
+
+1. On the primary node, [configure Patroni](#configuring-patroni-cluster). Remove `repmgr` and any other
+ repmgr-specific configuration. Also remove any configuration that is related to PostgreSQL replication.
+1. [Reconfigure Omnibus GitLab](../restart_gitlab.md#omnibus-gitlab-reconfigure) on the primary node. It will become
+ the leader. You can check this with:
+
+ ```shell
+ sudo gitlab-ctl tail patroni
+ ```
+
+1. Repeat the last two steps for all replica nodes. `gitlab.rb` should look the same on all nodes.
+1. Optional: You can remove `gitlab_repmgr` database and role on the primary.
diff --git a/doc/administration/postgresql/standalone.md b/doc/administration/postgresql/standalone.md
index 3e7826ce009..2747749066e 100644
--- a/doc/administration/postgresql/standalone.md
+++ b/doc/administration/postgresql/standalone.md
@@ -53,7 +53,8 @@ together with Omnibus GitLab. This is recommended as part of our
gitlab_rails['auto_migrate'] = false
```
- NOTE: **Note:** The role `postgres_role` was introduced with GitLab 10.3
+ NOTE: **Note:**
+ The role `postgres_role` was introduced with GitLab 10.3
1. [Reconfigure GitLab](../restart_gitlab.md#omnibus-gitlab-reconfigure) for the changes to take effect.
1. Note the PostgreSQL node's IP address or hostname, port, and
diff --git a/doc/administration/raketasks/ldap.md b/doc/administration/raketasks/ldap.md
index d168e3d568c..30bb9828aa0 100644
--- a/doc/administration/raketasks/ldap.md
+++ b/doc/administration/raketasks/ldap.md
@@ -36,7 +36,7 @@ The following task will run a [group sync](../auth/ldap/index.md#group-sync-star
when you'd like to update all configured group memberships against LDAP without
waiting for the next scheduled group sync to be run.
-NOTE: **NOTE:**
+NOTE: **Note:**
If you'd like to change the frequency at which a group sync is performed,
[adjust the cron schedule](../auth/ldap/index.md#adjusting-ldap-group-sync-schedule-starter-only)
instead.
diff --git a/doc/administration/raketasks/maintenance.md b/doc/administration/raketasks/maintenance.md
index 78094f00a43..19781b6a5db 100644
--- a/doc/administration/raketasks/maintenance.md
+++ b/doc/administration/raketasks/maintenance.md
@@ -260,7 +260,7 @@ clear it.
To clear all exclusive leases:
-DANGER: **DANGER**:
+DANGER: **Danger:**
Don't run it while GitLab or Sidekiq is running
```shell
@@ -317,7 +317,7 @@ migrations are completed (have an `up` status).
Sometimes you may need to re-import the common metrics that power the Metrics dashboards.
-This could be as a result of [updating existing metrics](../../development/prometheus_metrics.md#update-existing-metrics), or as a [troubleshooting measure](../../user/project/integrations/prometheus.md#troubleshooting).
+This could be as a result of [updating existing metrics](../../development/prometheus_metrics.md#update-existing-metrics), or as a [troubleshooting measure](../../operations/metrics/dashboards/index.md#troubleshooting).
To re-import the metrics you can run:
diff --git a/doc/administration/raketasks/praefect.md b/doc/administration/raketasks/praefect.md
index c48e23df77a..ca9c2065904 100644
--- a/doc/administration/raketasks/praefect.md
+++ b/doc/administration/raketasks/praefect.md
@@ -1,13 +1,23 @@
-# Praefect Rake Tasks **(CORE ONLY)**
+---
+stage: Create
+group: Gitaly
+info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers
+type: reference
+---
+
+# Praefect Rake tasks **(CORE ONLY)**
> [Introduced]( https://gitlab.com/gitlab-org/gitlab/-/merge_requests/28369) in GitLab 12.10.
+Rake tasks are available for projects that have been created on Praefect storage. See the
+[Praefect documentation](../gitaly/praefect.md) for information on configuring Praefect.
+
## Replica checksums
-Prints out checksums of the repository of a given project_id on the primary as well as secondary internal Gitaly nodes.
+`gitlab:praefect:replicas` prints out checksums of the repository of a given `project_id` on:
-NOTE: **Note:**
-This only is relevant and works for projects that have been created on a Praefect storage. See the [Praefect Documentation](../gitaly/praefect.md) for configuring Praefect.
+- The primary Gitaly node.
+- Secondary internal Gitaly nodes.
**Omnibus Installation**
diff --git a/doc/administration/raketasks/project_import_export.md b/doc/administration/raketasks/project_import_export.md
index 2e65e889c90..b807e03b01f 100644
--- a/doc/administration/raketasks/project_import_export.md
+++ b/doc/administration/raketasks/project_import_export.md
@@ -7,6 +7,16 @@ GitLab provides Rake tasks relating to project import and export. For more infor
- [Project import/export documentation](../../user/project/settings/import_export.md).
- [Project import/export API](../../api/project_import_export.md).
+- [Developer documentation: project import/export](../../development/import_export.md)
+
+## Project import status
+
+You can query an import through the [Project import/export API](../../api/project_import_export.md#import-status).
+As described in the API documentation, the query may return an import error or exceptions.
+
+## Import large projects
+
+If you have a larger project, consider using a Rake task, as described in our [developer documentation](../../development/import_project.md#importing-via-a-rake-task).
## Import/export tasks
diff --git a/doc/administration/redis/index.md b/doc/administration/redis/index.md
new file mode 100644
index 00000000000..0bd56666ab8
--- /dev/null
+++ b/doc/administration/redis/index.md
@@ -0,0 +1,42 @@
+---
+type: index
+stage: Enablement
+group: Distribution
+info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers
+---
+
+# Configuring Redis for scaling
+
+Based on your infrastructure setup and how you have installed GitLab, there are
+multiple ways to configure Redis.
+
+You can choose to install and manage Redis and Sentinel yourself, use a hosted
+cloud solution, or you can use the ones that come bundled with the Omnibus GitLab
+packages so you only need to focus on configuration. Pick the one that suits your needs.
+
+## Redis replication and failover using Omnibus GitLab
+
+This setup is for when you have installed GitLab using the
+[Omnibus GitLab **Enterprise Edition** (EE) package](https://about.gitlab.com/install/?version=ee).
+
+Both Redis and Sentinel are bundled in the package, so you can it to set up the whole
+Redis infrastructure (primary, replica and sentinel).
+
+[> Read how to set up Redis replication and failover using Omnibus GitLab](replication_and_failover.md)
+
+## Redis replication and failover using the non-bundled Redis
+
+This setup is for when you have installed GitLab using the
+[Omnibus GitLab packages](https://about.gitlab.com/install/) (CE or EE),
+or installed it [from source](../../install/installation.md), but you want to use
+your own external Redis and sentinel servers.
+
+[> Read how to set up Redis replication and failover using the non-bundled Redis](replication_and_failover_external.md)
+
+## Standalone Redis using Omnibus GitLab
+
+This setup is for when you have installed the
+[Omnibus GitLab **Community Edition** (CE) package](https://about.gitlab.com/install/?version=ce)
+to use the bundled Redis, so you can use the package with only the Redis service enabled.
+
+[> Read how to set up a standalone Redis instance using Omnibus GitLab](standalone.md)
diff --git a/doc/administration/redis/replication_and_failover.md b/doc/administration/redis/replication_and_failover.md
new file mode 100644
index 00000000000..ac31b909c89
--- /dev/null
+++ b/doc/administration/redis/replication_and_failover.md
@@ -0,0 +1,741 @@
+---
+type: howto
+stage: Enablement
+group: Distribution
+info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers
+---
+
+# Redis replication and failover with Omnibus GitLab **(PREMIUM ONLY)**
+
+NOTE: **Note:**
+This is the documentation for the Omnibus GitLab packages. For using your own
+non-bundled Redis, follow the [relevant documentation](replication_and_failover_external.md).
+
+NOTE: **Note:**
+In Redis lingo, primary is called master. In this document, primary is used
+instead of master, except the settings where `master` is required.
+
+Using [Redis](https://redis.io/) in scalable environment is possible using a **Primary** x **Replica**
+topology with a [Redis Sentinel](https://redis.io/topics/sentinel) service to watch and automatically
+start the failover procedure.
+
+Redis requires authentication if used with Sentinel. See
+[Redis Security](https://redis.io/topics/security) documentation for more
+information. We recommend using a combination of a Redis password and tight
+firewall rules to secure your Redis service.
+You are highly encouraged to read the [Redis Sentinel](https://redis.io/topics/sentinel) documentation
+before configuring Redis with GitLab to fully understand the topology and
+architecture.
+
+Before diving into the details of setting up Redis and Redis Sentinel for a
+replicated topology, make sure you read this document once as a whole to better
+understand how the components are tied together.
+
+You need at least `3` independent machines: physical, or VMs running into
+distinct physical machines. It is essential that all primary and replica Redis
+instances run in different machines. If you fail to provision the machines in
+that specific way, any issue with the shared environment can bring your entire
+setup down.
+
+It is OK to run a Sentinel alongside of a primary or replica Redis instance.
+There should be no more than one Sentinel on the same machine though.
+
+You also need to take into consideration the underlying network topology,
+making sure you have redundant connectivity between Redis / Sentinel and
+GitLab instances, otherwise the networks will become a single point of
+failure.
+
+Running Redis in a scaled environment requires a few things:
+
+- Multiple Redis instances
+- Run Redis in a **Primary** x **Replica** topology
+- Multiple Sentinel instances
+- Application support and visibility to all Sentinel and Redis instances
+
+Redis Sentinel can handle the most important tasks in an HA environment and that's
+to help keep servers online with minimal to no downtime. Redis Sentinel:
+
+- Monitors **Primary** and **Replicas** instances to see if they are available
+- Promotes a **Replica** to **Primary** when the **Primary** fails
+- Demotes a **Primary** to **Replica** when the failed **Primary** comes back online
+ (to prevent data-partitioning)
+- Can be queried by the application to always connect to the current **Primary**
+ server
+
+When a **Primary** fails to respond, it's the application's responsibility
+(in our case GitLab) to handle timeout and reconnect (querying a **Sentinel**
+for a new **Primary**).
+
+To get a better understanding on how to correctly set up Sentinel, please read
+the [Redis Sentinel documentation](https://redis.io/topics/sentinel) first, as
+failing to configure it correctly can lead to data loss or can bring your
+whole cluster down, invalidating the failover effort.
+
+## Recommended setup
+
+For a minimal setup, you will install the Omnibus GitLab package in `3`
+**independent** machines, both with **Redis** and **Sentinel**:
+
+- Redis Primary + Sentinel
+- Redis Replica + Sentinel
+- Redis Replica + Sentinel
+
+If you are not sure or don't understand why and where the amount of nodes come
+from, read [Redis setup overview](#redis-setup-overview) and
+[Sentinel setup overview](#sentinel-setup-overview).
+
+For a recommended setup that can resist more failures, you will install
+the Omnibus GitLab package in `5` **independent** machines, both with
+**Redis** and **Sentinel**:
+
+- Redis Primary + Sentinel
+- Redis Replica + Sentinel
+- Redis Replica + Sentinel
+- Redis Replica + Sentinel
+- Redis Replica + Sentinel
+
+### Redis setup overview
+
+You must have at least `3` Redis servers: `1` primary, `2` Replicas, and they
+need to each be on independent machines (see explanation above).
+
+You can have additional Redis nodes, that will help survive a situation
+where more nodes goes down. Whenever there is only `2` nodes online, a failover
+will not be initiated.
+
+As an example, if you have `6` Redis nodes, a maximum of `3` can be
+simultaneously down.
+
+Please note that there are different requirements for Sentinel nodes.
+If you host them in the same Redis machines, you may need to take
+that restrictions into consideration when calculating the amount of
+nodes to be provisioned. See [Sentinel setup overview](#sentinel-setup-overview)
+documentation for more information.
+
+All Redis nodes should be configured the same way and with similar server specs, as
+in a failover situation, any **Replica** can be promoted as the new **Primary** by
+the Sentinel servers.
+
+The replication requires authentication, so you need to define a password to
+protect all Redis nodes and the Sentinels. They will all share the same
+password, and all instances must be able to talk to
+each other over the network.
+
+### Sentinel setup overview
+
+Sentinels watch both other Sentinels and Redis nodes. Whenever a Sentinel
+detects that a Redis node is not responding, it will announce that to the
+other Sentinels. They have to reach the **quorum**, that is the minimum amount
+of Sentinels that agrees a node is down, in order to be able to start a failover.
+
+Whenever the **quorum** is met, the **majority** of all known Sentinel nodes
+need to be available and reachable, so that they can elect the Sentinel **leader**
+who will take all the decisions to restore the service availability by:
+
+- Promoting a new **Primary**
+- Reconfiguring the other **Replicas** and make them point to the new **Primary**
+- Announce the new **Primary** to every other Sentinel peer
+- Reconfigure the old **Primary** and demote to **Replica** when it comes back online
+
+You must have at least `3` Redis Sentinel servers, and they need to
+be each in an independent machine (that are believed to fail independently),
+ideally in different geographical areas.
+
+You can configure them in the same machines where you've configured the other
+Redis servers, but understand that if a whole node goes down, you loose both
+a Sentinel and a Redis instance.
+
+The number of sentinels should ideally always be an **odd** number, for the
+consensus algorithm to be effective in the case of a failure.
+
+In a `3` nodes topology, you can only afford `1` Sentinel node going down.
+Whenever the **majority** of the Sentinels goes down, the network partition
+protection prevents destructive actions and a failover **will not be started**.
+
+Here are some examples:
+
+- With `5` or `6` sentinels, a maximum of `2` can go down for a failover begin.
+- With `7` sentinels, a maximum of `3` nodes can go down.
+
+The **Leader** election can sometimes fail the voting round when **consensus**
+is not achieved (see the odd number of nodes requirement above). In that case,
+a new attempt will be made after the amount of time defined in
+`sentinel['failover_timeout']` (in milliseconds).
+
+NOTE: **Note:**
+We will see where `sentinel['failover_timeout']` is defined later.
+
+The `failover_timeout` variable has a lot of different use cases. According to
+the official documentation:
+
+- The time needed to re-start a failover after a previous failover was
+ already tried against the same primary by a given Sentinel, is two
+ times the failover timeout.
+
+- The time needed for a replica replicating to a wrong primary according
+ to a Sentinel current configuration, to be forced to replicate
+ with the right primary, is exactly the failover timeout (counting since
+ the moment a Sentinel detected the misconfiguration).
+
+- The time needed to cancel a failover that is already in progress but
+ did not produced any configuration change (REPLICAOF NO ONE yet not
+ acknowledged by the promoted replica).
+
+- The maximum time a failover in progress waits for all the replicas to be
+ reconfigured as replicas of the new primary. However even after this time
+ the replicas will be reconfigured by the Sentinels anyway, but not with
+ the exact parallel-syncs progression as specified.
+
+## Configuring Redis
+
+This is the section where we install and set up the new Redis instances.
+
+It is assumed that you have installed GitLab and all its components from scratch.
+If you already have Redis installed and running, read how to
+[switch from a single-machine installation](#switching-from-an-existing-single-machine-installation).
+
+NOTE: **Note:**
+Redis nodes (both primary and replica) will need the same password defined in
+`redis['password']`. At any time during a failover the Sentinels can
+reconfigure a node and change its status from primary to replica and vice versa.
+
+### Requirements
+
+The requirements for a Redis setup are the following:
+
+1. Provision the minimum required number of instances as specified in the
+ [recommended setup](#recommended-setup) section.
+1. We **Do not** recommend installing Redis or Redis Sentinel in the same machines your
+ GitLab application is running on as this weakens your HA configuration. You can however opt in to install Redis
+ and Sentinel in the same machine.
+1. All Redis nodes must be able to talk to each other and accept incoming
+ connections over Redis (`6379`) and Sentinel (`26379`) ports (unless you
+ change the default ones).
+1. The server that hosts the GitLab application must be able to access the
+ Redis nodes.
+1. Protect the nodes from access from external networks ([Internet](https://gitlab.com/gitlab-org/gitlab-foss/uploads/c4cc8cd353604bd80315f9384035ff9e/The_Internet_IT_Crowd.png)), using
+ firewall.
+
+### Switching from an existing single-machine installation
+
+If you already have a single-machine GitLab install running, you will need to
+replicate from this machine first, before de-activating the Redis instance
+inside it.
+
+Your single-machine install will be the initial **Primary**, and the `3` others
+should be configured as **Replica** pointing to this machine.
+
+After replication catches up, you will need to stop services in the
+single-machine install, to rotate the **Primary** to one of the new nodes.
+
+Make the required changes in configuration and restart the new nodes again.
+
+To disable Redis in the single install, edit `/etc/gitlab/gitlab.rb`:
+
+```ruby
+redis['enable'] = false
+```
+
+If you fail to replicate first, you may loose data (unprocessed background jobs).
+
+### Step 1. Configuring the primary Redis instance
+
+1. SSH into the **Primary** Redis server.
+1. [Download/install](https://about.gitlab.com/install/) the Omnibus GitLab
+ package you want using **steps 1 and 2** from the GitLab downloads page.
+ - Make sure you select the correct Omnibus package, with the same version
+ and type (Community, Enterprise editions) of your current install.
+ - Do not complete any other steps on the download page.
+
+1. Edit `/etc/gitlab/gitlab.rb` and add the contents:
+
+ ```ruby
+ # Specify server role as 'redis_master_role'
+ roles ['redis_master_role']
+
+ # IP address pointing to a local IP that the other machines can reach to.
+ # You can also set bind to '0.0.0.0' which listen in all interfaces.
+ # If you really need to bind to an external accessible IP, make
+ # sure you add extra firewall rules to prevent unauthorized access.
+ redis['bind'] = '10.0.0.1'
+
+ # Define a port so Redis can listen for TCP requests which will allow other
+ # machines to connect to it.
+ redis['port'] = 6379
+
+ # Set up password authentication for Redis (use the same password in all nodes).
+ redis['password'] = 'redis-password-goes-here'
+ ```
+
+1. Only the primary GitLab application server should handle migrations. To
+ prevent database migrations from running on upgrade, add the following
+ configuration to your `/etc/gitlab/gitlab.rb` file:
+
+ ```ruby
+ gitlab_rails['auto_migrate'] = false
+ ```
+
+1. [Reconfigure Omnibus GitLab](../restart_gitlab.md#omnibus-gitlab-reconfigure) for the changes to take effect.
+
+NOTE: **Note:**
+You can specify multiple roles like sentinel and Redis as:
+`roles ['redis_sentinel_role', 'redis_master_role']`.
+Read more about [roles](https://docs.gitlab.com/omnibus/roles/).
+
+### Step 2. Configuring the replica Redis instances
+
+1. SSH into the **replica** Redis server.
+1. [Download/install](https://about.gitlab.com/install/) the Omnibus GitLab
+ package you want using **steps 1 and 2** from the GitLab downloads page.
+ - Make sure you select the correct Omnibus package, with the same version
+ and type (Community, Enterprise editions) of your current install.
+ - Do not complete any other steps on the download page.
+
+1. Edit `/etc/gitlab/gitlab.rb` and add the contents:
+
+ ```ruby
+ # Specify server role as 'redis_replica_role'
+ roles ['redis_replica_role']
+
+ # IP address pointing to a local IP that the other machines can reach to.
+ # You can also set bind to '0.0.0.0' which listen in all interfaces.
+ # If you really need to bind to an external accessible IP, make
+ # sure you add extra firewall rules to prevent unauthorized access.
+ redis['bind'] = '10.0.0.2'
+
+ # Define a port so Redis can listen for TCP requests which will allow other
+ # machines to connect to it.
+ redis['port'] = 6379
+
+ # The same password for Redis authentication you set up for the primary node.
+ redis['password'] = 'redis-password-goes-here'
+
+ # The IP of the primary Redis node.
+ redis['master_ip'] = '10.0.0.1'
+
+ # Port of primary Redis server, uncomment to change to non default. Defaults
+ # to `6379`.
+ #redis['master_port'] = 6379
+ ```
+
+1. To prevent reconfigure from running automatically on upgrade, run:
+
+ ```shell
+ sudo touch /etc/gitlab/skip-auto-reconfigure
+ ```
+
+1. [Reconfigure Omnibus GitLab](../restart_gitlab.md#omnibus-gitlab-reconfigure) for the changes to take effect.
+1. Go through the steps again for all the other replica nodes.
+
+NOTE: **Note:**
+You can specify multiple roles like sentinel and Redis as:
+`roles ['redis_sentinel_role', 'redis_master_role']`.
+Read more about [roles](https://docs.gitlab.com/omnibus/roles/).
+
+These values don't have to be changed again in `/etc/gitlab/gitlab.rb` after
+a failover, as the nodes will be managed by the Sentinels, and even after a
+`gitlab-ctl reconfigure`, they will get their configuration restored by
+the same Sentinels.
+
+### Step 3. Configuring the Redis Sentinel instances
+
+NOTE: **Note:**
+If you are using an external Redis Sentinel instance, be sure
+to exclude the `requirepass` parameter from the Sentinel
+configuration. This parameter will cause clients to report `NOAUTH
+Authentication required.`. [Redis Sentinel 3.2.x does not support
+password authentication](https://github.com/antirez/redis/issues/3279).
+
+Now that the Redis servers are all set up, let's configure the Sentinel
+servers.
+
+If you are not sure if your Redis servers are working and replicating
+correctly, please read the [Troubleshooting Replication](troubleshooting.md#troubleshooting-redis-replication)
+and fix it before proceeding with Sentinel setup.
+
+You must have at least `3` Redis Sentinel servers, and they need to
+be each in an independent machine. You can configure them in the same
+machines where you've configured the other Redis servers.
+
+With GitLab Enterprise Edition, you can use the Omnibus package to set up
+multiple machines with the Sentinel daemon.
+
+---
+
+1. SSH into the server that will host Redis Sentinel.
+1. **You can omit this step if the Sentinels will be hosted in the same node as
+ the other Redis instances.**
+
+ [Download/install](https://about.gitlab.com/install/) the
+ Omnibus GitLab Enterprise Edition package using **steps 1 and 2** from the
+ GitLab downloads page.
+ - Make sure you select the correct Omnibus package, with the same version
+ the GitLab application is running.
+ - Do not complete any other steps on the download page.
+
+1. Edit `/etc/gitlab/gitlab.rb` and add the contents (if you are installing the
+ Sentinels in the same node as the other Redis instances, some values might
+ be duplicate below):
+
+ ```ruby
+ roles ['redis_sentinel_role']
+
+ # Must be the same in every sentinel node
+ redis['master_name'] = 'gitlab-redis'
+
+ # The same password for Redis authentication you set up for the primary node.
+ redis['master_password'] = 'redis-password-goes-here'
+
+ # The IP of the primary Redis node.
+ redis['master_ip'] = '10.0.0.1'
+
+ # Define a port so Redis can listen for TCP requests which will allow other
+ # machines to connect to it.
+ redis['port'] = 6379
+
+ # Port of primary Redis server, uncomment to change to non default. Defaults
+ # to `6379`.
+ #redis['master_port'] = 6379
+
+ ## Configure Sentinel
+ sentinel['bind'] = '10.0.0.1'
+
+ # Port that Sentinel listens on, uncomment to change to non default. Defaults
+ # to `26379`.
+ # sentinel['port'] = 26379
+
+ ## Quorum must reflect the amount of voting sentinels it take to start a failover.
+ ## Value must NOT be greater then the amount of sentinels.
+ ##
+ ## The quorum can be used to tune Sentinel in two ways:
+ ## 1. If a the quorum is set to a value smaller than the majority of Sentinels
+ ## we deploy, we are basically making Sentinel more sensible to primary failures,
+ ## triggering a failover as soon as even just a minority of Sentinels is no longer
+ ## able to talk with the primary.
+ ## 1. If a quorum is set to a value greater than the majority of Sentinels, we are
+ ## making Sentinel able to failover only when there are a very large number (larger
+ ## than majority) of well connected Sentinels which agree about the primary being down.s
+ sentinel['quorum'] = 2
+
+ ## Consider unresponsive server down after x amount of ms.
+ # sentinel['down_after_milliseconds'] = 10000
+
+ ## Specifies the failover timeout in milliseconds. It is used in many ways:
+ ##
+ ## - The time needed to re-start a failover after a previous failover was
+ ## already tried against the same primary by a given Sentinel, is two
+ ## times the failover timeout.
+ ##
+ ## - The time needed for a replica replicating to a wrong primary according
+ ## to a Sentinel current configuration, to be forced to replicate
+ ## with the right primary, is exactly the failover timeout (counting since
+ ## the moment a Sentinel detected the misconfiguration).
+ ##
+ ## - The time needed to cancel a failover that is already in progress but
+ ## did not produced any configuration change (REPLICAOF NO ONE yet not
+ ## acknowledged by the promoted replica).
+ ##
+ ## - The maximum time a failover in progress waits for all the replica to be
+ ## reconfigured as replicas of the new primary. However even after this time
+ ## the replicas will be reconfigured by the Sentinels anyway, but not with
+ ## the exact parallel-syncs progression as specified.
+ # sentinel['failover_timeout'] = 60000
+ ```
+
+1. To prevent database migrations from running on upgrade, run:
+
+ ```shell
+ sudo touch /etc/gitlab/skip-auto-reconfigure
+ ```
+
+ Only the primary GitLab application server should handle migrations.
+
+1. [Reconfigure Omnibus GitLab](../restart_gitlab.md#omnibus-gitlab-reconfigure) for the changes to take effect.
+1. Go through the steps again for all the other Sentinel nodes.
+
+### Step 4. Configuring the GitLab application
+
+The final part is to inform the main GitLab application server of the Redis
+Sentinels servers and authentication credentials.
+
+You can enable or disable Sentinel support at any time in new or existing
+installations. From the GitLab application perspective, all it requires is
+the correct credentials for the Sentinel nodes.
+
+While it doesn't require a list of all Sentinel nodes, in case of a failure,
+it needs to access at least one of the listed.
+
+NOTE: **Note:**
+The following steps should be performed in the [GitLab application server](../high_availability/gitlab.md)
+which ideally should not have Redis or Sentinels on it for a HA setup.
+
+1. SSH into the server where the GitLab application is installed.
+1. Edit `/etc/gitlab/gitlab.rb` and add/change the following lines:
+
+ ```ruby
+ ## Must be the same in every sentinel node
+ redis['master_name'] = 'gitlab-redis'
+
+ ## The same password for Redis authentication you set up for the primary node.
+ redis['master_password'] = 'redis-password-goes-here'
+
+ ## A list of sentinels with `host` and `port`
+ gitlab_rails['redis_sentinels'] = [
+ {'host' => '10.0.0.1', 'port' => 26379},
+ {'host' => '10.0.0.2', 'port' => 26379},
+ {'host' => '10.0.0.3', 'port' => 26379}
+ ]
+ ```
+
+1. [Reconfigure Omnibus GitLab](../restart_gitlab.md#omnibus-gitlab-reconfigure) for the changes to take effect.
+
+### Step 5. Enable Monitoring
+
+> [Introduced](https://gitlab.com/gitlab-org/omnibus-gitlab/-/issues/3786) in GitLab 12.0.
+
+If you enable Monitoring, it must be enabled on **all** Redis servers.
+
+1. Make sure to collect [`CONSUL_SERVER_NODES`](../postgresql/replication_and_failover.md#consul-information), which are the IP addresses or DNS records of the Consul server nodes, for the next step. Note they are presented as `Y.Y.Y.Y consul1.gitlab.example.com Z.Z.Z.Z`
+
+1. Create/edit `/etc/gitlab/gitlab.rb` and add the following configuration:
+
+ ```ruby
+ # Enable service discovery for Prometheus
+ consul['enable'] = true
+ consul['monitoring_service_discovery'] = true
+
+ # Replace placeholders
+ # Y.Y.Y.Y consul1.gitlab.example.com Z.Z.Z.Z
+ # with the addresses of the Consul server nodes
+ consul['configuration'] = {
+ retry_join: %w(Y.Y.Y.Y consul1.gitlab.example.com Z.Z.Z.Z),
+ }
+
+ # Set the network addresses that the exporters will listen on
+ node_exporter['listen_address'] = '0.0.0.0:9100'
+ redis_exporter['listen_address'] = '0.0.0.0:9121'
+ ```
+
+1. Run `sudo gitlab-ctl reconfigure` to compile the configuration.
+
+## Example of a minimal configuration with 1 primary, 2 replicas and 3 Sentinels
+
+In this example we consider that all servers have an internal network
+interface with IPs in the `10.0.0.x` range, and that they can connect
+to each other using these IPs.
+
+In a real world usage, you would also set up firewall rules to prevent
+unauthorized access from other machines and block traffic from the
+outside (Internet).
+
+We will use the same `3` nodes with **Redis** + **Sentinel** topology
+discussed in [Redis setup overview](#redis-setup-overview) and
+[Sentinel setup overview](#sentinel-setup-overview) documentation.
+
+Here is a list and description of each **machine** and the assigned **IP**:
+
+- `10.0.0.1`: Redis primary + Sentinel 1
+- `10.0.0.2`: Redis Replica 1 + Sentinel 2
+- `10.0.0.3`: Redis Replica 2 + Sentinel 3
+- `10.0.0.4`: GitLab application
+
+Please note that after the initial configuration, if a failover is initiated
+by the Sentinel nodes, the Redis nodes will be reconfigured and the **Primary**
+will change permanently (including in `redis.conf`) from one node to the other,
+until a new failover is initiated again.
+
+The same thing will happen with `sentinel.conf` that will be overridden after the
+initial execution, after any new sentinel node starts watching the **Primary**,
+or a failover promotes a different **Primary** node.
+
+### Example configuration for Redis primary and Sentinel 1
+
+In `/etc/gitlab/gitlab.rb`:
+
+```ruby
+roles ['redis_sentinel_role', 'redis_master_role']
+redis['bind'] = '10.0.0.1'
+redis['port'] = 6379
+redis['password'] = 'redis-password-goes-here'
+redis['master_name'] = 'gitlab-redis' # must be the same in every sentinel node
+redis['master_password'] = 'redis-password-goes-here' # the same value defined in redis['password'] in the primary instance
+redis['master_ip'] = '10.0.0.1' # ip of the initial primary redis instance
+#redis['master_port'] = 6379 # port of the initial primary redis instance, uncomment to change to non default
+sentinel['bind'] = '10.0.0.1'
+# sentinel['port'] = 26379 # uncomment to change default port
+sentinel['quorum'] = 2
+# sentinel['down_after_milliseconds'] = 10000
+# sentinel['failover_timeout'] = 60000
+```
+
+[Reconfigure Omnibus GitLab](../restart_gitlab.md#omnibus-gitlab-reconfigure) for the changes to take effect.
+
+### Example configuration for Redis replica 1 and Sentinel 2
+
+In `/etc/gitlab/gitlab.rb`:
+
+```ruby
+roles ['redis_sentinel_role', 'redis_replica_role']
+redis['bind'] = '10.0.0.2'
+redis['port'] = 6379
+redis['password'] = 'redis-password-goes-here'
+redis['master_password'] = 'redis-password-goes-here'
+redis['master_ip'] = '10.0.0.1' # IP of primary Redis server
+#redis['master_port'] = 6379 # Port of primary Redis server, uncomment to change to non default
+redis['master_name'] = 'gitlab-redis' # must be the same in every sentinel node
+sentinel['bind'] = '10.0.0.2'
+# sentinel['port'] = 26379 # uncomment to change default port
+sentinel['quorum'] = 2
+# sentinel['down_after_milliseconds'] = 10000
+# sentinel['failover_timeout'] = 60000
+```
+
+[Reconfigure Omnibus GitLab](../restart_gitlab.md#omnibus-gitlab-reconfigure) for the changes to take effect.
+
+### Example configuration for Redis replica 2 and Sentinel 3
+
+In `/etc/gitlab/gitlab.rb`:
+
+```ruby
+roles ['redis_sentinel_role', 'redis_replica_role']
+redis['bind'] = '10.0.0.3'
+redis['port'] = 6379
+redis['password'] = 'redis-password-goes-here'
+redis['master_password'] = 'redis-password-goes-here'
+redis['master_ip'] = '10.0.0.1' # IP of primary Redis server
+#redis['master_port'] = 6379 # Port of primary Redis server, uncomment to change to non default
+redis['master_name'] = 'gitlab-redis' # must be the same in every sentinel node
+sentinel['bind'] = '10.0.0.3'
+# sentinel['port'] = 26379 # uncomment to change default port
+sentinel['quorum'] = 2
+# sentinel['down_after_milliseconds'] = 10000
+# sentinel['failover_timeout'] = 60000
+```
+
+[Reconfigure Omnibus GitLab](../restart_gitlab.md#omnibus-gitlab-reconfigure) for the changes to take effect.
+
+### Example configuration for the GitLab application
+
+In `/etc/gitlab/gitlab.rb`:
+
+```ruby
+redis['master_name'] = 'gitlab-redis'
+redis['master_password'] = 'redis-password-goes-here'
+gitlab_rails['redis_sentinels'] = [
+ {'host' => '10.0.0.1', 'port' => 26379},
+ {'host' => '10.0.0.2', 'port' => 26379},
+ {'host' => '10.0.0.3', 'port' => 26379}
+]
+```
+
+[Reconfigure Omnibus GitLab](../restart_gitlab.md#omnibus-gitlab-reconfigure) for the changes to take effect.
+
+## Advanced configuration
+
+Omnibus GitLab configures some things behind the curtains to make the sysadmins'
+lives easier. If you want to know what happens underneath keep reading.
+
+### Running multiple Redis clusters
+
+GitLab supports running [separate Redis clusters for different persistent
+classes](https://docs.gitlab.com/omnibus/settings/redis.html#running-with-multiple-redis-instances):
+cache, queues, and shared_state. To make this work with Sentinel:
+
+1. Set the appropriate variable in `/etc/gitlab/gitlab.rb` for each instance you are using:
+
+ ```ruby
+ gitlab_rails['redis_cache_instance'] = REDIS_CACHE_URL
+ gitlab_rails['redis_queues_instance'] = REDIS_QUEUES_URL
+ gitlab_rails['redis_shared_state_instance'] = REDIS_SHARED_STATE_URL
+ ```
+
+ **Note**: Redis URLs should be in the format: `redis://:PASSWORD@SENTINEL_PRIMARY_NAME`
+
+ 1. PASSWORD is the plaintext password for the Redis instance
+ 1. SENTINEL_PRIMARY_NAME is the Sentinel primary name (e.g. `gitlab-redis-cache`)
+
+1. Include an array of hashes with host/port combinations, such as the following:
+
+ ```ruby
+ gitlab_rails['redis_cache_sentinels'] = [
+ { host: REDIS_CACHE_SENTINEL_HOST, port: PORT1 },
+ { host: REDIS_CACHE_SENTINEL_HOST2, port: PORT2 }
+ ]
+ gitlab_rails['redis_queues_sentinels'] = [
+ { host: REDIS_QUEUES_SENTINEL_HOST, port: PORT1 },
+ { host: REDIS_QUEUES_SENTINEL_HOST2, port: PORT2 }
+ ]
+ gitlab_rails['redis_shared_state_sentinels'] = [
+ { host: SHARED_STATE_SENTINEL_HOST, port: PORT1 },
+ { host: SHARED_STATE_SENTINEL_HOST2, port: PORT2 }
+ ]
+ ```
+
+1. Note that for each persistence class, GitLab will default to using the
+ configuration specified in `gitlab_rails['redis_sentinels']` unless
+ overridden by the settings above.
+1. Be sure to include BOTH configuration options for each persistent classes. For example,
+ if you choose to configure a cache instance, you must specify both `gitlab_rails['redis_cache_instance']`
+ and `gitlab_rails['redis_cache_sentinels']` for GitLab to generate the proper configuration files.
+1. Run `gitlab-ctl reconfigure`
+
+### Control running services
+
+In the previous example, we've used `redis_sentinel_role` and
+`redis_master_role` which simplifies the amount of configuration changes.
+
+If you want more control, here is what each one sets for you automatically
+when enabled:
+
+```ruby
+## Redis Sentinel Role
+redis_sentinel_role['enable'] = true
+
+# When Sentinel Role is enabled, the following services are also enabled
+sentinel['enable'] = true
+
+# The following services are disabled
+redis['enable'] = false
+bootstrap['enable'] = false
+nginx['enable'] = false
+postgresql['enable'] = false
+gitlab_rails['enable'] = false
+mailroom['enable'] = false
+
+-------
+
+## Redis primary/replica Role
+redis_master_role['enable'] = true # enable only one of them
+redis_replica_role['enable'] = true # enable only one of them
+
+# When Redis primary or Replica role are enabled, the following services are
+# enabled/disabled. Note that if Redis and Sentinel roles are combined, both
+# services will be enabled.
+
+# The following services are disabled
+sentinel['enable'] = false
+bootstrap['enable'] = false
+nginx['enable'] = false
+postgresql['enable'] = false
+gitlab_rails['enable'] = false
+mailroom['enable'] = false
+
+# For Redis Replica role, also change this setting from default 'true' to 'false':
+redis['master'] = false
+```
+
+You can find the relevant attributes defined in [`gitlab_rails.rb`](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/files/gitlab-cookbooks/gitlab/libraries/gitlab_rails.rb).
+
+## Troubleshooting
+
+See the [Redis troubleshooting guide](troubleshooting.md).
+
+## Further reading
+
+Read more:
+
+1. [Reference architectures](../reference_architectures/index.md)
+1. [Configure the database](../postgresql/replication_and_failover.md)
+1. [Configure NFS](../high_availability/nfs.md)
+1. [Configure the GitLab application servers](../high_availability/gitlab.md)
+1. [Configure the load balancers](../high_availability/load_balancer.md)
diff --git a/doc/administration/redis/replication_and_failover_external.md b/doc/administration/redis/replication_and_failover_external.md
new file mode 100644
index 00000000000..244b44dd76a
--- /dev/null
+++ b/doc/administration/redis/replication_and_failover_external.md
@@ -0,0 +1,376 @@
+---
+type: howto
+stage: Enablement
+group: Distribution
+info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers
+---
+
+# Redis replication and failover providing your own instance **(CORE ONLY)**
+
+If you’re hosting GitLab on a cloud provider, you can optionally use a managed
+service for Redis. For example, AWS offers ElastiCache that runs Redis.
+
+Alternatively, you may opt to manage your own Redis instance separate from the
+Omnibus GitLab package.
+
+## Requirements
+
+The following are the requirements for providing your own Redis instance:
+
+- Redis version 5.0 or higher is recommended, as this is what ships with
+ Omnibus GitLab packages starting with GitLab 12.7.
+- Support for Redis 3.2 is deprecated with GitLab 12.10 and will be completely
+ removed in GitLab 13.0.
+- GitLab 12.0 and later requires Redis version 3.2 or higher. Older Redis
+ versions do not support an optional count argument to SPOP which is now
+ required for [Merge Trains](../../ci/merge_request_pipelines/pipelines_for_merged_results/merge_trains/index.md).
+- In addition, if Redis 4 or later is available, GitLab makes use of certain
+ commands like `UNLINK` and `USAGE` which were introduced only in Redis 4.
+- Standalone Redis or Redis high availability with Sentinel are supported. Redis
+ Cluster is not supported.
+- Managed Redis from cloud providers such as AWS ElastiCache will work. If these
+ services support high availability, be sure it is **not** the Redis Cluster type.
+
+Note the Redis node's IP address or hostname, port, and password (if required).
+
+## Redis as a managed service in a cloud provider
+
+1. Set up Redis according to the [requirements](#requirements).
+1. Configure the GitLab application servers with the appropriate connection details
+ for your external Redis service in your `/etc/gitlab/gitlab.rb` file:
+
+ ```ruby
+ redis['enable'] = false
+
+ gitlab_rails['redis_host'] = 'redis.example.com'
+ gitlab_rails['redis_port'] = 6379
+
+ # Required if Redis authentication is configured on the Redis node
+ gitlab_rails['redis_password'] = 'Redis Password'
+ ```
+
+1. Reconfigure for the changes to take effect:
+
+ ```shell
+ sudo gitlab-ctl reconfigure
+ ```
+
+## Redis replication and failover with your own Redis servers
+
+This is the documentation for configuring a scalable Redis setup when
+you have installed Redis all by yourself and not using the bundled one that
+comes with the Omnibus packages, although using the Omnibus GitLab packages is
+highly recommend as we optimize them specifically for GitLab, and we will take
+care of upgrading Redis to the latest supported version.
+
+Note also that you may elect to override all references to
+`/home/git/gitlab/config/resque.yml` in accordance with the advanced Redis
+settings outlined in
+[Configuration Files Documentation](https://gitlab.com/gitlab-org/gitlab/blob/master/config/README.md).
+
+We cannot stress enough the importance of reading the
+[replication and failover](replication_and_failover.md) documentation of the
+Omnibus Redis HA as it provides some invaluable information to the configuration
+of Redis. Please proceed to read it before going forward with this guide.
+
+Before proceeding on setting up the new Redis instances, here are some
+requirements:
+
+- All Redis servers in this guide must be configured to use a TCP connection
+ instead of a socket. To configure Redis to use TCP connections you need to
+ define both `bind` and `port` in the Redis config file. You can bind to all
+ interfaces (`0.0.0.0`) or specify the IP of the desired interface
+ (e.g., one from an internal network).
+- Since Redis 3.2, you must define a password to receive external connections
+ (`requirepass`).
+- If you are using Redis with Sentinel, you will also need to define the same
+ password for the replica password definition (`masterauth`) in the same instance.
+
+In addition, read the prerequisites as described in the
+[Omnibus Redis document](replication_and_failover.md#requirements) since they provide some
+valuable information for the general setup.
+
+### Step 1. Configuring the primary Redis instance
+
+Assuming that the Redis primary instance IP is `10.0.0.1`:
+
+1. [Install Redis](../../install/installation.md#7-redis).
+1. Edit `/etc/redis/redis.conf`:
+
+ ```conf
+ ## Define a `bind` address pointing to a local IP that your other machines
+ ## can reach you. If you really need to bind to an external accessible IP, make
+ ## sure you add extra firewall rules to prevent unauthorized access:
+ bind 10.0.0.1
+
+ ## Define a `port` to force redis to listen on TCP so other machines can
+ ## connect to it (default port is `6379`).
+ port 6379
+
+ ## Set up password authentication (use the same password in all nodes).
+ ## The password should be defined equal for both `requirepass` and `masterauth`
+ ## when setting up Redis to use with Sentinel.
+ requirepass redis-password-goes-here
+ masterauth redis-password-goes-here
+ ```
+
+1. Restart the Redis service for the changes to take effect.
+
+### Step 2. Configuring the replica Redis instances
+
+Assuming that the Redis replica instance IP is `10.0.0.2`:
+
+1. [Install Redis](../../install/installation.md#7-redis).
+1. Edit `/etc/redis/redis.conf`:
+
+ ```conf
+ ## Define a `bind` address pointing to a local IP that your other machines
+ ## can reach you. If you really need to bind to an external accessible IP, make
+ ## sure you add extra firewall rules to prevent unauthorized access:
+ bind 10.0.0.2
+
+ ## Define a `port` to force redis to listen on TCP so other machines can
+ ## connect to it (default port is `6379`).
+ port 6379
+
+ ## Set up password authentication (use the same password in all nodes).
+ ## The password should be defined equal for both `requirepass` and `masterauth`
+ ## when setting up Redis to use with Sentinel.
+ requirepass redis-password-goes-here
+ masterauth redis-password-goes-here
+
+ ## Define `replicaof` pointing to the Redis primary instance with IP and port.
+ replicaof 10.0.0.1 6379
+ ```
+
+1. Restart the Redis service for the changes to take effect.
+1. Go through the steps again for all the other replica nodes.
+
+### Step 3. Configuring the Redis Sentinel instances
+
+Sentinel is a special type of Redis server. It inherits most of the basic
+configuration options you can define in `redis.conf`, with specific ones
+starting with `sentinel` prefix.
+
+Assuming that the Redis Sentinel is installed on the same instance as Redis
+primary with IP `10.0.0.1` (some settings might overlap with the primary):
+
+1. [Install Redis Sentinel](https://redis.io/topics/sentinel).
+1. Edit `/etc/redis/sentinel.conf`:
+
+ ```conf
+ ## Define a `bind` address pointing to a local IP that your other machines
+ ## can reach you. If you really need to bind to an external accessible IP, make
+ ## sure you add extra firewall rules to prevent unauthorized access:
+ bind 10.0.0.1
+
+ ## Define a `port` to force Sentinel to listen on TCP so other machines can
+ ## connect to it (default port is `6379`).
+ port 26379
+
+ ## Set up password authentication (use the same password in all nodes).
+ ## The password should be defined equal for both `requirepass` and `masterauth`
+ ## when setting up Redis to use with Sentinel.
+ requirepass redis-password-goes-here
+ masterauth redis-password-goes-here
+
+ ## Define with `sentinel auth-pass` the same shared password you have
+ ## defined for both Redis primary and replicas instances.
+ sentinel auth-pass gitlab-redis redis-password-goes-here
+
+ ## Define with `sentinel monitor` the IP and port of the Redis
+ ## primary node, and the quorum required to start a failover.
+ sentinel monitor gitlab-redis 10.0.0.1 6379 2
+
+ ## Define with `sentinel down-after-milliseconds` the time in `ms`
+ ## that an unresponsive server will be considered down.
+ sentinel down-after-milliseconds gitlab-redis 10000
+
+ ## Define a value for `sentinel failover_timeout` in `ms`. This has multiple
+ ## meanings:
+ ##
+ ## * The time needed to re-start a failover after a previous failover was
+ ## already tried against the same primary by a given Sentinel, is two
+ ## times the failover timeout.
+ ##
+ ## * The time needed for a replica replicating to a wrong primary according
+ ## to a Sentinel current configuration, to be forced to replicate
+ ## with the right primary, is exactly the failover timeout (counting since
+ ## the moment a Sentinel detected the misconfiguration).
+ ##
+ ## * The time needed to cancel a failover that is already in progress but
+ ## did not produced any configuration change (REPLICAOF NO ONE yet not
+ ## acknowledged by the promoted replica).
+ ##
+ ## * The maximum time a failover in progress waits for all the replicas to be
+ ## reconfigured as replicas of the new primary. However even after this time
+ ## the replicas will be reconfigured by the Sentinels anyway, but not with
+ ## the exact parallel-syncs progression as specified.
+ sentinel failover_timeout 30000
+ ```
+
+1. Restart the Redis service for the changes to take effect.
+1. Go through the steps again for all the other Sentinel nodes.
+
+### Step 4. Configuring the GitLab application
+
+You can enable or disable Sentinel support at any time in new or existing
+installations. From the GitLab application perspective, all it requires is
+the correct credentials for the Sentinel nodes.
+
+While it doesn't require a list of all Sentinel nodes, in case of a failure,
+it needs to access at least one of listed ones.
+
+The following steps should be performed in the [GitLab application server](../high_availability/gitlab.md)
+which ideally should not have Redis or Sentinels in the same machine:
+
+1. Edit `/home/git/gitlab/config/resque.yml` following the example in
+ [resque.yml.example](https://gitlab.com/gitlab-org/gitlab/blob/master/config/resque.yml.example), and uncomment the Sentinel lines, pointing to
+ the correct server credentials:
+
+ ```yaml
+ # resque.yaml
+ production:
+ url: redis://:redi-password-goes-here@gitlab-redis/
+ sentinels:
+ -
+ host: 10.0.0.1
+ port: 26379 # point to sentinel, not to redis port
+ -
+ host: 10.0.0.2
+ port: 26379 # point to sentinel, not to redis port
+ -
+ host: 10.0.0.3
+ port: 26379 # point to sentinel, not to redis port
+ ```
+
+1. [Restart GitLab](../restart_gitlab.md#installations-from-source) for the changes to take effect.
+
+## Example of minimal configuration with 1 primary, 2 replicas and 3 sentinels
+
+In this example we consider that all servers have an internal network
+interface with IPs in the `10.0.0.x` range, and that they can connect
+to each other using these IPs.
+
+In a real world usage, you would also set up firewall rules to prevent
+unauthorized access from other machines, and block traffic from the
+outside ([Internet](https://gitlab.com/gitlab-org/gitlab-foss/uploads/c4cc8cd353604bd80315f9384035ff9e/The_Internet_IT_Crowd.png)).
+
+For this example, **Sentinel 1** will be configured in the same machine as the
+**Redis Primary**, **Sentinel 2** and **Sentinel 3** in the same machines as the
+**Replica 1** and **Replica 2** respectively.
+
+Here is a list and description of each **machine** and the assigned **IP**:
+
+- `10.0.0.1`: Redis Primary + Sentinel 1
+- `10.0.0.2`: Redis Replica 1 + Sentinel 2
+- `10.0.0.3`: Redis Replica 2 + Sentinel 3
+- `10.0.0.4`: GitLab application
+
+Please note that after the initial configuration, if a failover is initiated
+by the Sentinel nodes, the Redis nodes will be reconfigured and the **Primary**
+will change permanently (including in `redis.conf`) from one node to the other,
+until a new failover is initiated again.
+
+The same thing will happen with `sentinel.conf` that will be overridden after the
+initial execution, after any new sentinel node starts watching the **Primary**,
+or a failover promotes a different **Primary** node.
+
+### Example configuration for Redis primary and Sentinel 1
+
+1. In `/etc/redis/redis.conf`:
+
+ ```conf
+ bind 10.0.0.1
+ port 6379
+ requirepass redis-password-goes-here
+ masterauth redis-password-goes-here
+ ```
+
+1. In `/etc/redis/sentinel.conf`:
+
+ ```conf
+ bind 10.0.0.1
+ port 26379
+ sentinel auth-pass gitlab-redis redis-password-goes-here
+ sentinel monitor gitlab-redis 10.0.0.1 6379 2
+ sentinel down-after-milliseconds gitlab-redis 10000
+ sentinel failover_timeout 30000
+ ```
+
+1. Restart the Redis service for the changes to take effect.
+
+### Example configuration for Redis replica 1 and Sentinel 2
+
+1. In `/etc/redis/redis.conf`:
+
+ ```conf
+ bind 10.0.0.2
+ port 6379
+ requirepass redis-password-goes-here
+ masterauth redis-password-goes-here
+ replicaof 10.0.0.1 6379
+ ```
+
+1. In `/etc/redis/sentinel.conf`:
+
+ ```conf
+ bind 10.0.0.2
+ port 26379
+ sentinel auth-pass gitlab-redis redis-password-goes-here
+ sentinel monitor gitlab-redis 10.0.0.1 6379 2
+ sentinel down-after-milliseconds gitlab-redis 10000
+ sentinel failover_timeout 30000
+ ```
+
+1. Restart the Redis service for the changes to take effect.
+
+### Example configuration for Redis replica 2 and Sentinel 3
+
+1. In `/etc/redis/redis.conf`:
+
+ ```conf
+ bind 10.0.0.3
+ port 6379
+ requirepass redis-password-goes-here
+ masterauth redis-password-goes-here
+ replicaof 10.0.0.1 6379
+ ```
+
+1. In `/etc/redis/sentinel.conf`:
+
+ ```conf
+ bind 10.0.0.3
+ port 26379
+ sentinel auth-pass gitlab-redis redis-password-goes-here
+ sentinel monitor gitlab-redis 10.0.0.1 6379 2
+ sentinel down-after-milliseconds gitlab-redis 10000
+ sentinel failover_timeout 30000
+ ```
+
+1. Restart the Redis service for the changes to take effect.
+
+### Example configuration of the GitLab application
+
+1. Edit `/home/git/gitlab/config/resque.yml`:
+
+ ```yaml
+ production:
+ url: redis://:redi-password-goes-here@gitlab-redis/
+ sentinels:
+ -
+ host: 10.0.0.1
+ port: 26379 # point to sentinel, not to redis port
+ -
+ host: 10.0.0.2
+ port: 26379 # point to sentinel, not to redis port
+ -
+ host: 10.0.0.3
+ port: 26379 # point to sentinel, not to redis port
+ ```
+
+1. [Restart GitLab](../restart_gitlab.md#installations-from-source) for the changes to take effect.
+
+## Troubleshooting
+
+See the [Redis troubleshooting guide](troubleshooting.md).
diff --git a/doc/administration/redis/standalone.md b/doc/administration/redis/standalone.md
new file mode 100644
index 00000000000..12e932dbc5e
--- /dev/null
+++ b/doc/administration/redis/standalone.md
@@ -0,0 +1,63 @@
+---
+type: howto
+stage: Enablement
+group: Distribution
+info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers
+---
+
+# Standalone Redis using Omnibus GitLab **(CORE ONLY)**
+
+The Omnibus GitLab package can be used to configure a standalone Redis server.
+In this configuration, Redis is not scaled, and represents a single
+point of failure. However, in a scaled environment the objective is to allow
+the environment to handle more users or to increase throughput. Redis itself
+is generally stable and can handle many requests, so it is an acceptable
+trade off to have only a single instance. See the [reference architectures](../reference_architectures/index.md)
+page for an overview of GitLab scaling options.
+
+## Set up a standalone Redis instance
+
+The steps below are the minimum necessary to configure a Redis server with
+Omnibus GitLab:
+
+1. SSH into the Redis server.
+1. [Download and install](https://about.gitlab.com/install/) the Omnibus GitLab
+ package you want by using **steps 1 and 2** from the GitLab downloads page.
+ Do not complete any other steps on the download page.
+
+1. Edit `/etc/gitlab/gitlab.rb` and add the contents:
+
+ ```ruby
+ ## Enable Redis
+ redis['enable'] = true
+
+ ## Disable all other services
+ sidekiq['enable'] = false
+ gitlab_workhorse['enable'] = false
+ puma['enable'] = false
+ postgresql['enable'] = false
+ nginx['enable'] = false
+ prometheus['enable'] = false
+ alertmanager['enable'] = false
+ pgbouncer_exporter['enable'] = false
+ gitlab_exporter['enable'] = false
+ gitaly['enable'] = false
+
+ redis['bind'] = '0.0.0.0'
+ redis['port'] = 6379
+ redis['password'] = 'SECRET_PASSWORD_HERE'
+
+ gitlab_rails['enable'] = false
+ ```
+
+1. [Reconfigure Omnibus GitLab](../restart_gitlab.md#omnibus-gitlab-reconfigure) for the changes to take effect.
+1. Note the Redis node's IP address or hostname, port, and
+ Redis password. These will be necessary when configuring the GitLab
+ application servers later.
+
+[Advanced configuration options](https://docs.gitlab.com/omnibus/settings/redis.html)
+are supported and can be added if needed.
+
+## Troubleshooting
+
+See the [Redis troubleshooting guide](troubleshooting.md).
diff --git a/doc/administration/redis/troubleshooting.md b/doc/administration/redis/troubleshooting.md
new file mode 100644
index 00000000000..402b60e5b7b
--- /dev/null
+++ b/doc/administration/redis/troubleshooting.md
@@ -0,0 +1,158 @@
+---
+type: reference
+stage: Enablement
+group: Distribution
+info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers
+---
+
+# Troubleshooting Redis
+
+There are a lot of moving parts that needs to be taken care carefully
+in order for the HA setup to work as expected.
+
+Before proceeding with the troubleshooting below, check your firewall rules:
+
+- Redis machines
+ - Accept TCP connection in `6379`
+ - Connect to the other Redis machines via TCP in `6379`
+- Sentinel machines
+ - Accept TCP connection in `26379`
+ - Connect to other Sentinel machines via TCP in `26379`
+ - Connect to the Redis machines via TCP in `6379`
+
+## Troubleshooting Redis replication
+
+You can check if everything is correct by connecting to each server using
+`redis-cli` application, and sending the `info replication` command as below.
+
+```shell
+/opt/gitlab/embedded/bin/redis-cli -h <redis-host-or-ip> -a '<redis-password>' info replication
+```
+
+When connected to a `Primary` Redis, you will see the number of connected
+`replicas`, and a list of each with connection details:
+
+```plaintext
+# Replication
+role:master
+connected_replicas:1
+replica0:ip=10.133.5.21,port=6379,state=online,offset=208037514,lag=1
+master_repl_offset:208037658
+repl_backlog_active:1
+repl_backlog_size:1048576
+repl_backlog_first_byte_offset:206989083
+repl_backlog_histlen:1048576
+```
+
+When it's a `replica`, you will see details of the primary connection and if
+its `up` or `down`:
+
+```plaintext
+# Replication
+role:replica
+master_host:10.133.1.58
+master_port:6379
+master_link_status:up
+master_last_io_seconds_ago:1
+master_sync_in_progress:0
+replica_repl_offset:208096498
+replica_priority:100
+replica_read_only:1
+connected_replicas:0
+master_repl_offset:0
+repl_backlog_active:0
+repl_backlog_size:1048576
+repl_backlog_first_byte_offset:0
+repl_backlog_histlen:0
+```
+
+## Troubleshooting Sentinel
+
+If you get an error like: `Redis::CannotConnectError: No sentinels available.`,
+there may be something wrong with your configuration files or it can be related
+to [this issue](https://github.com/redis/redis-rb/issues/531).
+
+You must make sure you are defining the same value in `redis['master_name']`
+and `redis['master_pasword']` as you defined for your sentinel node.
+
+The way the Redis connector `redis-rb` works with sentinel is a bit
+non-intuitive. We try to hide the complexity in omnibus, but it still requires
+a few extra configurations.
+
+---
+
+To make sure your configuration is correct:
+
+1. SSH into your GitLab application server
+1. Enter the Rails console:
+
+ ```shell
+ # For Omnibus installations
+ sudo gitlab-rails console
+
+ # For source installations
+ sudo -u git rails console -e production
+ ```
+
+1. Run in the console:
+
+ ```ruby
+ redis = Redis.new(Gitlab::Redis::SharedState.params)
+ redis.info
+ ```
+
+ Keep this screen open and try to simulate a failover below.
+
+1. To simulate a failover on primary Redis, SSH into the Redis server and run:
+
+ ```shell
+ # port must match your primary redis port, and the sleep time must be a few seconds bigger than defined one
+ redis-cli -h localhost -p 6379 DEBUG sleep 20
+ ```
+
+1. Then back in the Rails console from the first step, run:
+
+ ```ruby
+ redis.info
+ ```
+
+ You should see a different port after a few seconds delay
+ (the failover/reconnect time).
+
+## Troubleshooting a non-bundled Redis with an installation from source
+
+If you get an error in GitLab like `Redis::CannotConnectError: No sentinels available.`,
+there may be something wrong with your configuration files or it can be related
+to [this upstream issue](https://github.com/redis/redis-rb/issues/531).
+
+You must make sure that `resque.yml` and `sentinel.conf` are configured correctly,
+otherwise `redis-rb` will not work properly.
+
+The `master-group-name` (`gitlab-redis`) defined in (`sentinel.conf`)
+**must** be used as the hostname in GitLab (`resque.yml`):
+
+```conf
+# sentinel.conf:
+sentinel monitor gitlab-redis 10.0.0.1 6379 2
+sentinel down-after-milliseconds gitlab-redis 10000
+sentinel config-epoch gitlab-redis 0
+sentinel leader-epoch gitlab-redis 0
+```
+
+```yaml
+# resque.yaml
+production:
+ url: redis://:myredispassword@gitlab-redis/
+ sentinels:
+ -
+ host: 10.0.0.1
+ port: 26379 # point to sentinel, not to redis port
+ -
+ host: 10.0.0.2
+ port: 26379 # point to sentinel, not to redis port
+ -
+ host: 10.0.0.3
+ port: 26379 # point to sentinel, not to redis port
+```
+
+When in doubt, read the [Redis Sentinel documentation](https://redis.io/topics/sentinel).
diff --git a/doc/administration/reference_architectures/10k_users.md b/doc/administration/reference_architectures/10k_users.md
index a15fcf722a5..5367021af4e 100644
--- a/doc/administration/reference_architectures/10k_users.md
+++ b/doc/administration/reference_architectures/10k_users.md
@@ -47,7 +47,7 @@ For a full list of reference architectures, see
For medium sized installs (3,000 - 5,000) we suggest one Redis cluster for all
classes and that Redis Sentinel is hosted alongside Consul.
For larger architectures (10,000 users or more) we suggest running a separate
- [Redis Cluster](../high_availability/redis.md#running-multiple-redis-clusters) for the Cache class
+ [Redis Cluster](../redis/replication_and_failover.md#running-multiple-redis-clusters) for the Cache class
and another for the Queues and Shared State classes respectively. We also recommend
that you run the Redis Sentinel clusters separately for each Redis Cluster.
diff --git a/doc/administration/reference_architectures/1k_users.md b/doc/administration/reference_architectures/1k_users.md
index 34805a8ac68..def23619a5c 100644
--- a/doc/administration/reference_architectures/1k_users.md
+++ b/doc/administration/reference_architectures/1k_users.md
@@ -57,7 +57,7 @@ added performance and reliability at a reduced complexity cost.
For medium sized installs (3,000 - 5,000) we suggest one Redis cluster for all
classes and that Redis Sentinel is hosted alongside Consul.
For larger architectures (10,000 users or more) we suggest running a separate
- [Redis Cluster](../high_availability/redis.md#running-multiple-redis-clusters) for the Cache class
+ [Redis Cluster](../redis/replication_and_failover.md#running-multiple-redis-clusters) for the Cache class
and another for the Queues and Shared State classes respectively. We also recommend
that you run the Redis Sentinel clusters separately for each Redis Cluster.
diff --git a/doc/administration/reference_architectures/25k_users.md b/doc/administration/reference_architectures/25k_users.md
index d851fa124c6..17f4300eb03 100644
--- a/doc/administration/reference_architectures/25k_users.md
+++ b/doc/administration/reference_architectures/25k_users.md
@@ -47,7 +47,7 @@ For a full list of reference architectures, see
For medium sized installs (3,000 - 5,000) we suggest one Redis cluster for all
classes and that Redis Sentinel is hosted alongside Consul.
For larger architectures (10,000 users or more) we suggest running a separate
- [Redis Cluster](../high_availability/redis.md#running-multiple-redis-clusters) for the Cache class
+ [Redis Cluster](../redis/replication_and_failover.md#running-multiple-redis-clusters) for the Cache class
and another for the Queues and Shared State classes respectively. We also recommend
that you run the Redis Sentinel clusters separately for each Redis Cluster.
diff --git a/doc/administration/reference_architectures/2k_users.md b/doc/administration/reference_architectures/2k_users.md
index 0a3ade1acf1..d182daf45b3 100644
--- a/doc/administration/reference_architectures/2k_users.md
+++ b/doc/administration/reference_architectures/2k_users.md
@@ -12,16 +12,16 @@ For a full list of reference architectures, see
> - **High Availability:** False
> - **Test requests per second (RPS) rates:** API: 40 RPS, Web: 4 RPS, Git: 4 RPS
-| Service | Nodes | Configuration | GCP | AWS | Azure |
-|--------------------------------------------------------------|-----------|---------------------------------|---------------|-----------------------|----------------|
-| Load balancer | 1 | 2 vCPU, 1.8GB memory | n1-highcpu-2 | c5.large | F2s v2 |
-| Object storage | n/a | n/a | n/a | n/a | n/a |
-| NFS server (optional, not recommended) | 1 | 4 vCPU, 3.6GB memory | n1-highcpu-4 | c5.xlarge | F4s v2 |
-| PostgreSQL | 1 | 2 vCPU, 7.5GB memory | n1-standard-2 | m5.large | D2s v3 |
-| Redis | 1 | 1 vCPU, 3.75GB memory | n1-standard-1 | m5.large | D2s v3 |
-| Gitaly | 1 | 4 vCPU, 15GB memory | n1-standard-4 | m5.xlarge | D4s v3 |
-| GitLab Rails | 2 | 8 vCPU, 7.2GB memory | n1-highcpu-8 | c5.2xlarge | F8s v2 |
-| Monitoring node | 1 | 2 vCPU, 1.8GB memory | n1-highcpu-2 | c5.large | F2s v2 |
+| Service | Nodes | Configuration | GCP | AWS | Azure |
+|------------------------------------------|--------|-------------------------|-----------------|----------------|-----------|
+| Load balancer | 1 | 2 vCPU, 1.8GB memory | `n1-highcpu-2` | `c5.large` | `F2s v2` |
+| PostgreSQL | 1 | 2 vCPU, 7.5GB memory | `n1-standard-2` | `m5.large` | `D2s v3` |
+| Redis | 1 | 1 vCPU, 3.75GB memory | `n1-standard-1` | `m5.large` | `D2s v3` |
+| Gitaly | 1 | 4 vCPU, 15GB memory | `n1-standard-4` | `m5.xlarge` | `D4s v3` |
+| GitLab Rails | 2 | 8 vCPU, 7.2GB memory | `n1-highcpu-8` | `c5.2xlarge` | `F8s v2` |
+| Monitoring node | 1 | 2 vCPU, 1.8GB memory | `n1-highcpu-2` | `c5.large` | `F2s v2` |
+| Object storage | n/a | n/a | n/a | n/a | n/a |
+| NFS server (optional, not recommended) | 1 | 4 vCPU, 3.6GB memory | `n1-highcpu-4` | `c5.xlarge` | `F4s v2` |
The Google Cloud Platform (GCP) architectures were built and tested using the
[Intel Xeon E5 v3 (Haswell)](https://cloud.google.com/compute/docs/cpu-platforms)
@@ -30,9 +30,6 @@ or higher, are required for your CPU or node counts. For more information, see
our [Sysbench](https://github.com/akopytov/sysbench)-based
[CPU benchmark](https://gitlab.com/gitlab-org/quality/performance/-/wikis/Reference-Architectures/GCP-CPU-Benchmarks).
-AWS-equivalent and Azure-equivalent configurations are rough suggestions that
-may change in the future, and haven't been tested or validated.
-
Due to better performance and availability, for data objects (such as LFS,
uploads, or artifacts), using an [object storage service](#configure-the-object-storage)
is recommended instead of using NFS. Using an object storage service also
@@ -44,12 +41,6 @@ To set up GitLab and its components to accommodate up to 2,000 users:
1. [Configure the external load balancing node](#configure-the-load-balancer)
to handle the load balancing of the two GitLab application services nodes.
-1. [Configure the object storage](#configure-the-object-storage) used for
- shared data objects.
-1. [Configure NFS](#configure-nfs-optional) (optional, and not recommended)
- to have shared disk storage service as an alternative to Gitaly or object
- storage. You can skip this step if you're not using GitLab Pages (which
- requires NFS).
1. [Configure PostgreSQL](#configure-postgresql), the database for GitLab.
1. [Configure Redis](#configure-redis).
1. [Configure Gitaly](#configure-gitaly), which provides access to the Git
@@ -59,6 +50,12 @@ To set up GitLab and its components to accommodate up to 2,000 users:
requests (which include UI, API, and Git over HTTP/SSH).
1. [Configure Prometheus](#configure-prometheus) to monitor your GitLab
environment.
+1. [Configure the object storage](#configure-the-object-storage) used for
+ shared data objects.
+1. [Configure NFS](#configure-nfs-optional) (optional, and not recommended)
+ to have shared disk storage service as an alternative to Gitaly or object
+ storage. You can skip this step if you're not using GitLab Pages (which
+ requires NFS).
## Configure the load balancer
@@ -173,73 +170,6 @@ Configure DNS for an alternate SSH hostname, such as `altssh.gitlab.example.com`
</a>
</div>
-## Configure the object storage
-
-GitLab supports using an object storage service for holding several types of
-data, and is recommended over [NFS](#configure-nfs-optional). In general,
-object storage services are better for larger environments, as object storage
-is typically much more performant, reliable, and scalable.
-
-Object storage options that GitLab has either tested or is aware of customers
-using, includes:
-
-- SaaS/Cloud solutions (such as [Amazon S3](https://aws.amazon.com/s3/) or
- [Google Cloud Storage](https://cloud.google.com/storage)).
-- On-premises hardware and appliances, from various storage vendors.
-- MinIO ([Deployment guide](https://docs.gitlab.com/charts/advanced/external-object-storage/minio.html)).
-
-To configure GitLab to use object storage, refer to the following guides based
-on the features you intend to use:
-
-1. [Object storage for backups](../../raketasks/backup_restore.md#uploading-backups-to-a-remote-cloud-storage).
-1. [Object storage for job artifacts](../job_artifacts.md#using-object-storage)
- including [incremental logging](../job_logs.md#new-incremental-logging-architecture).
-1. [Object storage for LFS objects](../lfs/index.md#storing-lfs-objects-in-remote-object-storage).
-1. [Object storage for uploads](../uploads.md#using-object-storage-core-only).
-1. [Object storage for merge request diffs](../merge_request_diffs.md#using-object-storage).
-1. [Object storage for Container Registry](../packages/container_registry.md#container-registry-storage-driver) (optional feature).
-1. [Object storage for Mattermost](https://docs.mattermost.com/administration/config-settings.html#file-storage) (optional feature).
-1. [Object storage for packages](../packages/index.md#using-object-storage) (optional feature). **(PREMIUM ONLY)**
-1. [Object storage for Dependency Proxy](../packages/dependency_proxy.md#using-object-storage) (optional feature). **(PREMIUM ONLY)**
-1. [Object storage for Pseudonymizer](../pseudonymizer.md#configuration) (optional feature). **(ULTIMATE ONLY)**
-1. [Object storage for autoscale Runner caching](https://docs.gitlab.com/runner/configuration/autoscale.html#distributed-runners-caching) (optional, for improved performance).
-1. [Object storage for Terraform state files](../terraform_state.md#using-object-storage-core-only).
-
-Using separate buckets for each data type is the recommended approach for GitLab.
-
-A limitation of our configuration is that each use of object storage is
-separately configured. We have an [issue](https://gitlab.com/gitlab-org/gitlab/-/issues/23345)
-for improving this, which would allow for one bucket with separate folders.
-
-Using a single bucket when GitLab is deployed with the Helm chart causes
-restoring from a backup to
-[not function properly](https://docs.gitlab.com/charts/advanced/external-object-storage/#lfs-artifacts-uploads-packages-external-diffs-pseudonymizer).
-Although you may not be using a Helm deployment right now, if you migrate
-GitLab to a Helm deployment later, GitLab would still work, but you may not
-realize backups aren't working correctly until a critical requirement for
-functioning backups is encountered.
-
-<div align="right">
- <a type="button" class="btn btn-default" href="#setup-components">
- Back to setup components <i class="fa fa-angle-double-up" aria-hidden="true"></i>
- </a>
-</div>
-
-## Configure NFS (optional)
-
-For improved performance, [object storage](#configure-the-object-storage),
-along with [Gitaly](#configure-gitaly), are recommended over using NFS whenever
-possible. However, if you intend to use GitLab Pages,
-[you must use NFS](troubleshooting.md#gitlab-pages-requires-nfs).
-
-For information about configuring NFS, see the [NFS documentation page](../high_availability/nfs.md).
-
-<div align="right">
- <a type="button" class="btn btn-default" href="#setup-components">
- Back to setup components <i class="fa fa-angle-double-up" aria-hidden="true"></i>
- </a>
-</div>
-
## Configure PostgreSQL
In this section, you'll be guided through configuring an external PostgreSQL database
@@ -543,7 +473,7 @@ nodes (including the Gitaly node using the certificate) and on all client nodes
that communicate with it following the procedure described in
[GitLab custom certificate configuration](https://docs.gitlab.com/omnibus/settings/ssl.html#install-custom-public-certificates).
-NOTE: **Note**
+NOTE: **Note:**
The self-signed certificate must specify the address you use to access the
Gitaly server. If you are addressing the Gitaly server by a hostname, you can
either use the Common Name field for this, or add it as a Subject Alternative
@@ -728,7 +658,8 @@ On each node perform the following:
sudo gitlab-ctl tail gitaly
```
-NOTE: **Note:** When you specify `https` in the `external_url`, as in the example
+NOTE: **Note:**
+When you specify `https` in the `external_url`, as in the example
above, GitLab assumes you have SSL certificates in `/etc/gitlab/ssl/`. If
certificates are not present, NGINX will fail to start. See the
[NGINX documentation](https://docs.gitlab.com/omnibus/settings/nginx.html#enable-https)
@@ -862,6 +793,73 @@ running [Prometheus](../monitoring/prometheus/index.md) and
</a>
</div>
+## Configure the object storage
+
+GitLab supports using an object storage service for holding several types of
+data, and is recommended over [NFS](#configure-nfs-optional). In general,
+object storage services are better for larger environments, as object storage
+is typically much more performant, reliable, and scalable.
+
+Object storage options that GitLab has either tested or is aware of customers
+using, includes:
+
+- SaaS/Cloud solutions (such as [Amazon S3](https://aws.amazon.com/s3/) or
+ [Google Cloud Storage](https://cloud.google.com/storage)).
+- On-premises hardware and appliances, from various storage vendors.
+- MinIO ([Deployment guide](https://docs.gitlab.com/charts/advanced/external-object-storage/minio.html)).
+
+To configure GitLab to use object storage, refer to the following guides based
+on the features you intend to use:
+
+1. [Object storage for backups](../../raketasks/backup_restore.md#uploading-backups-to-a-remote-cloud-storage).
+1. [Object storage for job artifacts](../job_artifacts.md#using-object-storage)
+ including [incremental logging](../job_logs.md#new-incremental-logging-architecture).
+1. [Object storage for LFS objects](../lfs/index.md#storing-lfs-objects-in-remote-object-storage).
+1. [Object storage for uploads](../uploads.md#using-object-storage-core-only).
+1. [Object storage for merge request diffs](../merge_request_diffs.md#using-object-storage).
+1. [Object storage for Container Registry](../packages/container_registry.md#use-object-storage) (optional feature).
+1. [Object storage for Mattermost](https://docs.mattermost.com/administration/config-settings.html#file-storage) (optional feature).
+1. [Object storage for packages](../packages/index.md#using-object-storage) (optional feature). **(PREMIUM ONLY)**
+1. [Object storage for Dependency Proxy](../packages/dependency_proxy.md#using-object-storage) (optional feature). **(PREMIUM ONLY)**
+1. [Object storage for Pseudonymizer](../pseudonymizer.md#configuration) (optional feature). **(ULTIMATE ONLY)**
+1. [Object storage for autoscale Runner caching](https://docs.gitlab.com/runner/configuration/autoscale.html#distributed-runners-caching) (optional, for improved performance).
+1. [Object storage for Terraform state files](../terraform_state.md#using-object-storage-core-only).
+
+Using separate buckets for each data type is the recommended approach for GitLab.
+
+A limitation of our configuration is that each use of object storage is
+separately configured. We have an [issue](https://gitlab.com/gitlab-org/gitlab/-/issues/23345)
+for improving this, which would allow for one bucket with separate folders.
+
+Using a single bucket when GitLab is deployed with the Helm chart causes
+restoring from a backup to
+[not function properly](https://docs.gitlab.com/charts/advanced/external-object-storage/#lfs-artifacts-uploads-packages-external-diffs-pseudonymizer).
+Although you may not be using a Helm deployment right now, if you migrate
+GitLab to a Helm deployment later, GitLab would still work, but you may not
+realize backups aren't working correctly until a critical requirement for
+functioning backups is encountered.
+
+<div align="right">
+ <a type="button" class="btn btn-default" href="#setup-components">
+ Back to setup components <i class="fa fa-angle-double-up" aria-hidden="true"></i>
+ </a>
+</div>
+
+## Configure NFS (optional)
+
+For improved performance, [object storage](#configure-the-object-storage),
+along with [Gitaly](#configure-gitaly), are recommended over using NFS whenever
+possible. However, if you intend to use GitLab Pages,
+[you must use NFS](troubleshooting.md#gitlab-pages-requires-nfs).
+
+For information about configuring NFS, see the [NFS documentation page](../high_availability/nfs.md).
+
+<div align="right">
+ <a type="button" class="btn btn-default" href="#setup-components">
+ Back to setup components <i class="fa fa-angle-double-up" aria-hidden="true"></i>
+ </a>
+</div>
+
## Troubleshooting
See the [troubleshooting documentation](troubleshooting.md).
diff --git a/doc/administration/reference_architectures/3k_users.md b/doc/administration/reference_architectures/3k_users.md
index efeed3e9ffd..04cb9fa4769 100644
--- a/doc/administration/reference_architectures/3k_users.md
+++ b/doc/administration/reference_architectures/3k_users.md
@@ -1,10 +1,15 @@
+---
+reading_time: true
+---
+
# Reference architecture: up to 3,000 users
This page describes GitLab reference architecture for up to 3,000 users.
For a full list of reference architectures, see
[Available reference architectures](index.md#available-reference-architectures).
-NOTE: **Note:** The 3,000-user reference architecture documented below is
+NOTE: **Note:**
+The 3,000-user reference architecture documented below is
designed to help your organization achieve a highly-available GitLab deployment.
If you do not have the expertise or need to maintain a highly-available
environment, you can have a simpler and less costly-to-operate environment by
@@ -14,66 +19,1749 @@ following the [2,000-user reference architecture](2k_users.md).
> - **High Availability:** True
> - **Test RPS rates:** API: 60 RPS, Web: 6 RPS, Git: 6 RPS
-| Service | Nodes | Configuration ([8](#footnotes)) | GCP | AWS | Azure |
-|--------------------------------------------------------------|-------|---------------------------------|---------------|-----------------------|----------------|
-| GitLab Rails ([1](#footnotes)) | 3 | 8 vCPU, 7.2GB Memory | `n1-highcpu-8` | `c5.2xlarge` | F8s v2 |
-| PostgreSQL | 3 | 2 vCPU, 7.5GB Memory | `n1-standard-2` | `m5.large` | D2s v3 |
-| PgBouncer | 3 | 2 vCPU, 1.8GB Memory | `n1-highcpu-2` | `c5.large` | F2s v2 |
-| Gitaly ([2](#footnotes)) ([5](#footnotes)) ([7](#footnotes)) | X | 4 vCPU, 15GB Memory | `n1-standard-4` | `m5.xlarge` | D4s v3 |
-| Redis ([3](#footnotes)) | 3 | 2 vCPU, 7.5GB Memory | `n1-standard-2` | `m5.large` | D2s v3 |
-| Consul + Sentinel ([3](#footnotes)) | 3 | 2 vCPU, 1.8GB Memory | `n1-highcpu-2` | `c5.large` | F2s v2 |
-| Sidekiq | 4 | 2 vCPU, 7.5GB Memory | `n1-standard-2` | `m5.large` | D2s v3 |
-| Object Storage ([4](#footnotes)) | - | - | - | - | - |
-| NFS Server ([5](#footnotes)) ([7](#footnotes)) | 1 | 4 vCPU, 3.6GB Memory | `n1-highcpu-4` | `c5.xlarge` | F4s v2 |
-| Monitoring node | 1 | 2 vCPU, 1.8GB Memory | `n1-highcpu-2` | `c5.large` | F2s v2 |
-| External load balancing node ([6](#footnotes)) | 1 | 2 vCPU, 1.8GB Memory | `n1-highcpu-2` | `c5.large` | F2s v2 |
-| Internal load balancing node ([6](#footnotes)) | 1 | 2 vCPU, 1.8GB Memory | `n1-highcpu-2` | `c5.large` | F2s v2 |
-
-## Footnotes
-
-1. In our architectures we run each GitLab Rails node using the Puma webserver
- and have its number of workers set to 90% of available CPUs along with four threads. For
- nodes that are running Rails with other components the worker value should be reduced
- accordingly where we've found 50% achieves a good balance but this is dependent
- on workload.
-
-1. Gitaly node requirements are dependent on customer data, specifically the number of
- projects and their sizes. We recommend two nodes as an absolute minimum for HA environments
- and at least four nodes should be used when supporting 50,000 or more users.
- We also recommend that each Gitaly node should store no more than 5TB of data
- and have the number of [`gitaly-ruby` workers](../gitaly/index.md#gitaly-ruby)
- set to 20% of available CPUs. Additional nodes should be considered in conjunction
- with a review of expected data size and spread based on the recommendations above.
-
-1. Recommended Redis setup differs depending on the size of the architecture.
- For smaller architectures (less than 3,000 users) a single instance should suffice.
- For medium sized installs (3,000 - 5,000) we suggest one Redis cluster for all
- classes and that Redis Sentinel is hosted alongside Consul.
- For larger architectures (10,000 users or more) we suggest running a separate
- [Redis Cluster](../high_availability/redis.md#running-multiple-redis-clusters) for the Cache class
- and another for the Queues and Shared State classes respectively. We also recommend
- that you run the Redis Sentinel clusters separately for each Redis Cluster.
-
-1. For data objects such as LFS, Uploads, Artifacts, etc. We recommend an [Object Storage service](../object_storage.md)
- over NFS where possible, due to better performance and availability.
-
-1. NFS can be used as an alternative for both repository data (replacing Gitaly) and
- object storage but this isn't typically recommended for performance reasons. Note however it is required for
- [GitLab Pages](https://gitlab.com/gitlab-org/gitlab-pages/-/issues/196).
-
-1. Our architectures have been tested and validated with [HAProxy](https://www.haproxy.org/)
- as the load balancer. Although other load balancers with similar feature sets
- could also be used, those load balancers have not been validated.
-
-1. We strongly recommend that any Gitaly or NFS nodes be set up with SSD disks over
- HDD with a throughput of at least 8,000 IOPS for read operations and 2,000 IOPS for write
- as these components have heavy I/O. These IOPS values are recommended only as a starter
- as with time they may be adjusted higher or lower depending on the scale of your
- environment's workload. If you're running the environment on a Cloud provider
- you may need to refer to their documentation on how configure IOPS correctly.
-
-1. The architectures were built and tested with the [Intel Xeon E5 v3 (Haswell)](https://cloud.google.com/compute/docs/cpu-platforms)
- CPU platform on GCP. On different hardware you may find that adjustments, either lower
- or higher, are required for your CPU or Node counts accordingly. For more information, a
- [Sysbench](https://github.com/akopytov/sysbench) benchmark of the CPU can be found
- [here](https://gitlab.com/gitlab-org/quality/performance/-/wikis/Reference-Architectures/GCP-CPU-Benchmarks).
+| Service | Nodes | Configuration | GCP | AWS | Azure |
+|--------------------------------------------------------------|-------|---------------------------------|-----------------|-------------------------|----------------|
+| External load balancing node | 1 | 2 vCPU, 1.8GB Memory | `n1-highcpu-2` | `c5.large` | `F2s v2` |
+| Redis | 3 | 2 vCPU, 7.5GB Memory | `n1-standard-2` | `m5.large` | `D2s v3` |
+| Consul + Sentinel | 3 | 2 vCPU, 1.8GB Memory | `n1-highcpu-2` | `c5.large` | `F2s v2` |
+| PostgreSQL | 3 | 2 vCPU, 7.5GB Memory | `n1-standard-2` | `m5.large` | `D2s v3` |
+| PgBouncer | 3 | 2 vCPU, 1.8GB Memory | `n1-highcpu-2` | `c5.large` | `F2s v2` |
+| Internal load balancing node | 1 | 2 vCPU, 1.8GB Memory | `n1-highcpu-2` | `c5.large` | `F2s v2` |
+| Gitaly | 2 minimum | 4 vCPU, 15GB Memory | `n1-standard-4` | `m5.xlarge` | `D4s v3` |
+| Sidekiq | 4 | 2 vCPU, 7.5GB Memory | `n1-standard-2` | `m5.large` | `D2s v3` |
+| GitLab Rails | 3 | 8 vCPU, 7.2GB Memory | `n1-highcpu-8` | `c5.2xlarge` | `F8s v2` |
+| Monitoring node | 1 | 2 vCPU, 1.8GB Memory | `n1-highcpu-2` | `c5.large` | `F2s v2` |
+| Object Storage | n/a | n/a | n/a | n/a | n/a |
+| NFS Server (optional, not recommended) | 1 | 4 vCPU, 3.6GB Memory | `n1-highcpu-4` | `c5.xlarge` | `F4s v2` |
+
+The architectures were built and tested with the [Intel Xeon E5 v3 (Haswell)](https://cloud.google.com/compute/docs/cpu-platforms)
+CPU platform on GCP. On different hardware you may find that adjustments, either lower
+or higher, are required for your CPU or Node counts accordingly. For more information, a
+[Sysbench](https://github.com/akopytov/sysbench) benchmark of the CPU can be found
+[here](https://gitlab.com/gitlab-org/quality/performance/-/wikis/Reference-Architectures/GCP-CPU-Benchmarks).
+
+For data objects such as LFS, Uploads, Artifacts, etc, an [object storage service](#configure-the-object-storage)
+is recommended over NFS where possible, due to better performance and availability.
+Since this doesn't require a node to be set up, it's marked as not applicable (n/a)
+in the table above.
+
+## Setup components
+
+To set up GitLab and its components to accommodate up to 3,000 users:
+
+1. [Configure the external load balancing node](#configure-the-external-load-balancer)
+ that will handle the load balancing of the two GitLab application services nodes.
+1. [Configure Redis](#configure-redis).
+1. [Configure Consul and Sentinel](#configure-consul-and-sentinel).
+1. [Configure PostgreSQL](#configure-postgresql), the database for GitLab.
+1. [Configure PgBouncer](#configure-pgbouncer).
+1. [Configure the internal load balancing node](#configure-the-internal-load-balancer)
+1. [Configure Gitaly](#configure-gitaly),
+ which provides access to the Git repositories.
+1. [Configure Sidekiq](#configure-sidekiq).
+1. [Configure the main GitLab Rails application](#configure-gitlab-rails)
+ to run Puma/Unicorn, Workhorse, GitLab Shell, and to serve all frontend requests (UI, API, Git
+ over HTTP/SSH).
+1. [Configure Prometheus](#configure-prometheus) to monitor your GitLab environment.
+1. [Configure the Object Storage](#configure-the-object-storage)
+ used for shared data objects.
+1. [Configure NFS (Optional)](#configure-nfs-optional)
+ to have shared disk storage service as an alternative to Gitaly and/or Object Storage (although
+ not recommended). NFS is required for GitLab Pages, you can skip this step if you're not using
+ that feature.
+
+We start with all servers on the same 10.6.0.0/16 private network range, they
+can connect to each other freely on those addresses.
+
+Here is a list and description of each machine and the assigned IP:
+
+- `10.6.0.10`: External Load Balancer
+- `10.6.0.61`: Redis Primary
+- `10.6.0.62`: Redis Replica 1
+- `10.6.0.63`: Redis Replica 2
+- `10.6.0.11`: Consul/Sentinel 1
+- `10.6.0.12`: Consul/Sentinel 2
+- `10.6.0.13`: Consul/Sentinel 3
+- `10.6.0.31`: PostgreSQL primary
+- `10.6.0.32`: PostgreSQL secondary 1
+- `10.6.0.33`: PostgreSQL secondary 2
+- `10.6.0.21`: PgBouncer 1
+- `10.6.0.22`: PgBouncer 2
+- `10.6.0.23`: PgBouncer 3
+- `10.6.0.20`: Internal Load Balancer
+- `10.6.0.51`: Gitaly 1
+- `10.6.0.52`: Gitaly 2
+- `10.6.0.71`: Sidekiq 1
+- `10.6.0.72`: Sidekiq 2
+- `10.6.0.73`: Sidekiq 3
+- `10.6.0.74`: Sidekiq 4
+- `10.6.0.41`: GitLab application 1
+- `10.6.0.42`: GitLab application 2
+- `10.6.0.43`: GitLab application 3
+- `10.6.0.81`: Prometheus
+
+## Configure the external load balancer
+
+NOTE: **Note:**
+This architecture has been tested and validated with [HAProxy](https://www.haproxy.org/)
+as the load balancer. Although other load balancers with similar feature sets
+could also be used, those load balancers have not been validated.
+
+In an active/active GitLab configuration, you will need a load balancer to route
+traffic to the application servers. The specifics on which load balancer to use
+or the exact configuration is beyond the scope of GitLab documentation. We hope
+that if you're managing multi-node systems like GitLab you have a load balancer of
+choice already. Some examples including HAProxy (open-source), F5 Big-IP LTM,
+and Citrix Net Scaler. This documentation will outline what ports and protocols
+you need to use with GitLab.
+
+The next question is how you will handle SSL in your environment.
+There are several different options:
+
+- [The application node terminates SSL](#application-node-terminates-ssl).
+- [The load balancer terminates SSL without backend SSL](#load-balancer-terminates-ssl-without-backend-ssl)
+ and communication is not secure between the load balancer and the application node.
+- [The load balancer terminates SSL with backend SSL](#load-balancer-terminates-ssl-with-backend-ssl)
+ and communication is *secure* between the load balancer and the application node.
+
+### Application node terminates SSL
+
+Configure your load balancer to pass connections on port 443 as `TCP` rather
+than `HTTP(S)` protocol. This will pass the connection to the application node's
+NGINX service untouched. NGINX will have the SSL certificate and listen on port 443.
+
+See the [NGINX HTTPS documentation](https://docs.gitlab.com/omnibus/settings/nginx.html#enable-https)
+for details on managing SSL certificates and configuring NGINX.
+
+### Load balancer terminates SSL without backend SSL
+
+Configure your load balancer to use the `HTTP(S)` protocol rather than `TCP`.
+The load balancer will then be responsible for managing SSL certificates and
+terminating SSL.
+
+Since communication between the load balancer and GitLab will not be secure,
+there is some additional configuration needed. See the
+[NGINX proxied SSL documentation](https://docs.gitlab.com/omnibus/settings/nginx.html#supporting-proxied-ssl)
+for details.
+
+### Load balancer terminates SSL with backend SSL
+
+Configure your load balancer(s) to use the 'HTTP(S)' protocol rather than 'TCP'.
+The load balancer(s) will be responsible for managing SSL certificates that
+end users will see.
+
+Traffic will also be secure between the load balancer(s) and NGINX in this
+scenario. There is no need to add configuration for proxied SSL since the
+connection will be secure all the way. However, configuration will need to be
+added to GitLab to configure SSL certificates. See
+[NGINX HTTPS documentation](https://docs.gitlab.com/omnibus/settings/nginx.html#enable-https)
+for details on managing SSL certificates and configuring NGINX.
+
+### Ports
+
+The basic ports to be used are shown in the table below.
+
+| LB Port | Backend Port | Protocol |
+| ------- | ------------ | ------------------------ |
+| 80 | 80 | HTTP (*1*) |
+| 443 | 443 | TCP or HTTPS (*1*) (*2*) |
+| 22 | 22 | TCP |
+
+- (*1*): [Web terminal](../../ci/environments/index.md#web-terminals) support requires
+ your load balancer to correctly handle WebSocket connections. When using
+ HTTP or HTTPS proxying, this means your load balancer must be configured
+ to pass through the `Connection` and `Upgrade` hop-by-hop headers. See the
+ [web terminal](../integration/terminal.md) integration guide for
+ more details.
+- (*2*): When using HTTPS protocol for port 443, you will need to add an SSL
+ certificate to the load balancers. If you wish to terminate SSL at the
+ GitLab application server instead, use TCP protocol.
+
+If you're using GitLab Pages with custom domain support you will need some
+additional port configurations.
+GitLab Pages requires a separate virtual IP address. Configure DNS to point the
+`pages_external_url` from `/etc/gitlab/gitlab.rb` at the new virtual IP address. See the
+[GitLab Pages documentation](../pages/index.md) for more information.
+
+| LB Port | Backend Port | Protocol |
+| ------- | ------------- | --------- |
+| 80 | Varies (*1*) | HTTP |
+| 443 | Varies (*1*) | TCP (*2*) |
+
+- (*1*): The backend port for GitLab Pages depends on the
+ `gitlab_pages['external_http']` and `gitlab_pages['external_https']`
+ setting. See [GitLab Pages documentation](../pages/index.md) for more details.
+- (*2*): Port 443 for GitLab Pages should always use the TCP protocol. Users can
+ configure custom domains with custom SSL, which would not be possible
+ if SSL was terminated at the load balancer.
+
+#### Alternate SSH Port
+
+Some organizations have policies against opening SSH port 22. In this case,
+it may be helpful to configure an alternate SSH hostname that allows users
+to use SSH on port 443. An alternate SSH hostname will require a new virtual IP address
+compared to the other GitLab HTTP configuration above.
+
+Configure DNS for an alternate SSH hostname such as `altssh.gitlab.example.com`.
+
+| LB Port | Backend Port | Protocol |
+| ------- | ------------ | -------- |
+| 443 | 22 | TCP |
+
+<div align="right">
+ <a type="button" class="btn btn-default" href="#setup-components">
+ Back to setup components <i class="fa fa-angle-double-up" aria-hidden="true"></i>
+ </a>
+</div>
+
+## Configure Redis
+
+Using [Redis](https://redis.io/) in scalable environment is possible using a **Primary** x **Replica**
+topology with a [Redis Sentinel](https://redis.io/topics/sentinel) service to watch and automatically
+start the failover procedure.
+
+Redis requires authentication if used with Sentinel. See
+[Redis Security](https://redis.io/topics/security) documentation for more
+information. We recommend using a combination of a Redis password and tight
+firewall rules to secure your Redis service.
+You are highly encouraged to read the [Redis Sentinel](https://redis.io/topics/sentinel) documentation
+before configuring Redis with GitLab to fully understand the topology and
+architecture.
+
+In this section, you'll be guided through configuring an external Redis instance
+to be used with GitLab. The following IPs will be used as an example:
+
+- `10.6.0.61`: Redis Primary
+- `10.6.0.62`: Redis Replica 1
+- `10.6.0.63`: Redis Replica 2
+
+### Provide your own Redis instance
+
+Managed Redis from cloud providers such as AWS ElastiCache will work. If these
+services support high availability, be sure it is **not** the Redis Cluster type.
+
+Redis version 5.0 or higher is required, as this is what ships with
+Omnibus GitLab packages starting with GitLab 13.0. Older Redis versions
+do not support an optional count argument to SPOP which is now required for
+[Merge Trains](../../ci/merge_request_pipelines/pipelines_for_merged_results/merge_trains/index.md).
+
+Note the Redis node's IP address or hostname, port, and password (if required).
+These will be necessary when configuring the
+[GitLab application servers](#configure-gitlab-rails) later.
+
+### Standalone Redis using Omnibus GitLab
+
+This is the section where we install and set up the new Redis instances.
+
+The requirements for a Redis setup are the following:
+
+1. All Redis nodes must be able to talk to each other and accept incoming
+ connections over Redis (`6379`) and Sentinel (`26379`) ports (unless you
+ change the default ones).
+1. The server that hosts the GitLab application must be able to access the
+ Redis nodes.
+1. Protect the nodes from access from external networks
+ ([Internet](https://gitlab.com/gitlab-org/gitlab-foss/uploads/c4cc8cd353604bd80315f9384035ff9e/The_Internet_IT_Crowd.png)),
+ using a firewall.
+
+NOTE: **Note:**
+Redis nodes (both primary and replica) will need the same password defined in
+`redis['password']`. At any time during a failover the Sentinels can
+reconfigure a node and change its status from primary to replica and vice versa.
+
+#### Configuring the primary Redis instance
+
+1. SSH into the **Primary** Redis server.
+1. [Download/install](https://about.gitlab.com/install/) the Omnibus GitLab
+ package you want using **steps 1 and 2** from the GitLab downloads page.
+ - Make sure you select the correct Omnibus package, with the same version
+ and type (Community, Enterprise editions) of your current install.
+ - Do not complete any other steps on the download page.
+
+1. Edit `/etc/gitlab/gitlab.rb` and add the contents:
+
+ ```ruby
+ # Specify server role as 'redis_master_role'
+ roles ['redis_master_role']
+
+ # IP address pointing to a local IP that the other machines can reach to.
+ # You can also set bind to '0.0.0.0' which listen in all interfaces.
+ # If you really need to bind to an external accessible IP, make
+ # sure you add extra firewall rules to prevent unauthorized access.
+ redis['bind'] = '10.6.0.61'
+
+ # Define a port so Redis can listen for TCP requests which will allow other
+ # machines to connect to it.
+ redis['port'] = 6379
+
+ # Set up password authentication for Redis (use the same password in all nodes).
+ redis['password'] = 'redis-password-goes-here'
+
+ ## Enable service discovery for Prometheus
+ consul['enable'] = true
+ consul['monitoring_service_discovery'] = true
+
+ ## The IPs of the Consul server nodes
+ ## You can also use FQDNs and intermix them with IPs
+ consul['configuration'] = {
+ retry_join: %w(10.6.0.11 10.6.0.12 10.6.0.13),
+ }
+
+ # Set the network addresses that the exporters will listen on
+ node_exporter['listen_address'] = '0.0.0.0:9100'
+ redis_exporter['listen_address'] = '0.0.0.0:9121'
+ redis_exporter['flags'] = {
+ 'redis.addr' => 'redis://10.6.0.61:6379',
+ 'redis.password' => 'redis-password-goes-here',
+ }
+
+ # Disable auto migrations
+ gitlab_rails['auto_migrate'] = false
+ ```
+
+1. [Reconfigure Omnibus GitLab](../restart_gitlab.md#omnibus-gitlab-reconfigure) for the changes to take effect.
+
+NOTE: **Note:**
+You can specify multiple roles like sentinel and Redis as:
+`roles ['redis_sentinel_role', 'redis_master_role']`.
+Read more about [roles](https://docs.gitlab.com/omnibus/roles/).
+
+You can list the current Redis Primary, Replica status via:
+
+```shell
+/opt/gitlab/embedded/bin/redis-cli -h <host> -a 'redis-password-goes-here' info replication
+```
+
+Show running GitLab services via:
+
+```shell
+gitlab-ctl status
+```
+
+The output should be similar to the following:
+
+```plaintext
+run: consul: (pid 30043) 76863s; run: log: (pid 29691) 76892s
+run: logrotate: (pid 31152) 3070s; run: log: (pid 29595) 76908s
+run: node-exporter: (pid 30064) 76862s; run: log: (pid 29624) 76904s
+run: redis: (pid 30070) 76861s; run: log: (pid 29573) 76914s
+run: redis-exporter: (pid 30075) 76861s; run: log: (pid 29674) 76896s
+```
+
+#### Configuring the replica Redis instances
+
+1. SSH into the **replica** Redis server.
+1. [Download/install](https://about.gitlab.com/install/) the Omnibus GitLab
+ package you want using **steps 1 and 2** from the GitLab downloads page.
+ - Make sure you select the correct Omnibus package, with the same version
+ and type (Community, Enterprise editions) of your current install.
+ - Do not complete any other steps on the download page.
+
+1. Edit `/etc/gitlab/gitlab.rb` and add the contents:
+
+ ```ruby
+ # Specify server role as 'redis_replica_role'
+ roles ['redis_replica_role']
+
+ # IP address pointing to a local IP that the other machines can reach to.
+ # You can also set bind to '0.0.0.0' which listen in all interfaces.
+ # If you really need to bind to an external accessible IP, make
+ # sure you add extra firewall rules to prevent unauthorized access.
+ redis['bind'] = '10.6.0.62'
+
+ # Define a port so Redis can listen for TCP requests which will allow other
+ # machines to connect to it.
+ redis['port'] = 6379
+
+ # The same password for Redis authentication you set up for the primary node.
+ redis['password'] = 'redis-password-goes-here'
+
+ # The IP of the primary Redis node.
+ redis['master_ip'] = '10.6.0.61'
+
+ # Port of primary Redis server, uncomment to change to non default. Defaults
+ # to `6379`.
+ #redis['master_port'] = 6379
+
+ ## Enable service discovery for Prometheus
+ consul['enable'] = true
+ consul['monitoring_service_discovery'] = true
+
+ ## The IPs of the Consul server nodes
+ ## You can also use FQDNs and intermix them with IPs
+ consul['configuration'] = {
+ retry_join: %w(10.6.0.11 10.6.0.12 10.6.0.13),
+ }
+
+ # Set the network addresses that the exporters will listen on
+ node_exporter['listen_address'] = '0.0.0.0:9100'
+ redis_exporter['listen_address'] = '0.0.0.0:9121'
+ redis_exporter['flags'] = {
+ 'redis.addr' => 'redis://10.6.0.62:6379',
+ 'redis.password' => 'redis-password-goes-here',
+ }
+
+ # Disable auto migrations
+ gitlab_rails['auto_migrate'] = false
+ ```
+
+1. [Reconfigure Omnibus GitLab](../restart_gitlab.md#omnibus-gitlab-reconfigure) for the changes to take effect.
+1. Go through the steps again for all the other replica nodes, and
+ make sure to set up the IPs correctly.
+
+NOTE: **Note:**
+You can specify multiple roles like sentinel and Redis as:
+`roles ['redis_sentinel_role', 'redis_master_role']`.
+Read more about [roles](https://docs.gitlab.com/omnibus/roles/).
+
+These values don't have to be changed again in `/etc/gitlab/gitlab.rb` after
+a failover, as the nodes will be managed by the [Sentinels](#configure-consul-and-sentinel), and even after a
+`gitlab-ctl reconfigure`, they will get their configuration restored by
+the same Sentinels.
+
+Advanced [configuration options](https://docs.gitlab.com/omnibus/settings/redis.html)
+are supported and can be added if needed.
+
+<div align="right">
+ <a type="button" class="btn btn-default" href="#setup-components">
+ Back to setup components <i class="fa fa-angle-double-up" aria-hidden="true"></i>
+ </a>
+</div>
+
+## Configure Consul and Sentinel
+
+NOTE: **Note:**
+If you are using an external Redis Sentinel instance, be sure
+to exclude the `requirepass` parameter from the Sentinel
+configuration. This parameter will cause clients to report `NOAUTH
+Authentication required.`. [Redis Sentinel 3.2.x does not support
+password authentication](https://github.com/antirez/redis/issues/3279).
+
+Now that the Redis servers are all set up, let's configure the Sentinel
+servers. The following IPs will be used as an example:
+
+- `10.6.0.11`: Consul/Sentinel 1
+- `10.6.0.12`: Consul/Sentinel 2
+- `10.6.0.13`: Consul/Sentinel 3
+
+To configure the Sentinel:
+
+1. SSH into the server that will host Consul/Sentinel.
+1. [Download/install](https://about.gitlab.com/install/) the
+ Omnibus GitLab Enterprise Edition package using **steps 1 and 2** from the
+ GitLab downloads page.
+ - Make sure you select the correct Omnibus package, with the same version
+ the GitLab application is running.
+ - Do not complete any other steps on the download page.
+
+1. Edit `/etc/gitlab/gitlab.rb` and add the contents:
+
+ ```ruby
+ roles ['redis_sentinel_role', 'consul_role']
+
+ # Must be the same in every sentinel node
+ redis['master_name'] = 'gitlab-redis'
+
+ # The same password for Redis authentication you set up for the primary node.
+ redis['master_password'] = 'redis-password-goes-here'
+
+ # The IP of the primary Redis node.
+ redis['master_ip'] = '10.6.0.61'
+
+ # Define a port so Redis can listen for TCP requests which will allow other
+ # machines to connect to it.
+ redis['port'] = 6379
+
+ # Port of primary Redis server, uncomment to change to non default. Defaults
+ # to `6379`.
+ #redis['master_port'] = 6379
+
+ ## Configure Sentinel
+ sentinel['bind'] = '10.6.0.11'
+
+ # Port that Sentinel listens on, uncomment to change to non default. Defaults
+ # to `26379`.
+ # sentinel['port'] = 26379
+
+ ## Quorum must reflect the amount of voting sentinels it take to start a failover.
+ ## Value must NOT be greater then the amount of sentinels.
+ ##
+ ## The quorum can be used to tune Sentinel in two ways:
+ ## 1. If a the quorum is set to a value smaller than the majority of Sentinels
+ ## we deploy, we are basically making Sentinel more sensible to primary failures,
+ ## triggering a failover as soon as even just a minority of Sentinels is no longer
+ ## able to talk with the primary.
+ ## 1. If a quorum is set to a value greater than the majority of Sentinels, we are
+ ## making Sentinel able to failover only when there are a very large number (larger
+ ## than majority) of well connected Sentinels which agree about the primary being down.s
+ sentinel['quorum'] = 2
+
+ ## Consider unresponsive server down after x amount of ms.
+ # sentinel['down_after_milliseconds'] = 10000
+
+ ## Specifies the failover timeout in milliseconds. It is used in many ways:
+ ##
+ ## - The time needed to re-start a failover after a previous failover was
+ ## already tried against the same primary by a given Sentinel, is two
+ ## times the failover timeout.
+ ##
+ ## - The time needed for a replica replicating to a wrong primary according
+ ## to a Sentinel current configuration, to be forced to replicate
+ ## with the right primary, is exactly the failover timeout (counting since
+ ## the moment a Sentinel detected the misconfiguration).
+ ##
+ ## - The time needed to cancel a failover that is already in progress but
+ ## did not produced any configuration change (REPLICAOF NO ONE yet not
+ ## acknowledged by the promoted replica).
+ ##
+ ## - The maximum time a failover in progress waits for all the replica to be
+ ## reconfigured as replicas of the new primary. However even after this time
+ ## the replicas will be reconfigured by the Sentinels anyway, but not with
+ ## the exact parallel-syncs progression as specified.
+ # sentinel['failover_timeout'] = 60000
+
+ ## Enable service discovery for Prometheus
+ consul['enable'] = true
+ consul['monitoring_service_discovery'] = true
+
+ ## The IPs of the Consul server nodes
+ ## You can also use FQDNs and intermix them with IPs
+ consul['configuration'] = {
+ server: true,
+ retry_join: %w(10.6.0.11 10.6.0.12 10.6.0.13),
+ }
+
+ # Set the network addresses that the exporters will listen on
+ node_exporter['listen_address'] = '0.0.0.0:9100'
+ redis_exporter['listen_address'] = '0.0.0.0:9121'
+
+ # Disable auto migrations
+ gitlab_rails['auto_migrate'] = false
+ ```
+
+1. [Reconfigure Omnibus GitLab](../restart_gitlab.md#omnibus-gitlab-reconfigure) for the changes to take effect.
+1. Go through the steps again for all the other Consul/Sentinel nodes, and
+ make sure you set up the correct IPs.
+
+NOTE: **Note:**
+A Consul leader will be elected when the provisioning of the third Consul server is completed.
+Viewing the Consul logs `sudo gitlab-ctl tail consul` will display
+`...[INFO] consul: New leader elected: ...`
+
+You can list the current Consul members (server, client):
+
+```shell
+sudo /opt/gitlab/embedded/bin/consul members
+```
+
+You can verify the GitLab services are running:
+
+```shell
+sudo gitlab-ctl status
+```
+
+The output should be similar to the following:
+
+```plaintext
+run: consul: (pid 30074) 76834s; run: log: (pid 29740) 76844s
+run: logrotate: (pid 30925) 3041s; run: log: (pid 29649) 76861s
+run: node-exporter: (pid 30093) 76833s; run: log: (pid 29663) 76855s
+run: sentinel: (pid 30098) 76832s; run: log: (pid 29704) 76850s
+```
+
+<div align="right">
+ <a type="button" class="btn btn-default" href="#setup-components">
+ Back to setup components <i class="fa fa-angle-double-up" aria-hidden="true"></i>
+ </a>
+</div>
+
+## Configure PostgreSQL
+
+In this section, you'll be guided through configuring an external PostgreSQL database
+to be used with GitLab.
+
+### Provide your own PostgreSQL instance
+
+If you're hosting GitLab on a cloud provider, you can optionally use a
+managed service for PostgreSQL. For example, AWS offers a managed Relational
+Database Service (RDS) that runs PostgreSQL.
+
+If you use a cloud-managed service, or provide your own PostgreSQL:
+
+1. Set up PostgreSQL according to the
+ [database requirements document](../../install/requirements.md#database).
+1. Set up a `gitlab` username with a password of your choice. The `gitlab` user
+ needs privileges to create the `gitlabhq_production` database.
+1. Configure the GitLab application servers with the appropriate details.
+ This step is covered in [Configuring the GitLab Rails application](#configure-gitlab-rails).
+
+### Standalone PostgreSQL using Omnibus GitLab
+
+The following IPs will be used as an example:
+
+- `10.6.0.31`: PostgreSQL primary
+- `10.6.0.32`: PostgreSQL secondary 1
+- `10.6.0.33`: PostgreSQL secondary 2
+
+First, make sure to [install](https://about.gitlab.com/install/)
+the Linux GitLab package **on each node**. Following the steps,
+install the necessary dependencies from step 1, and add the
+GitLab package repository from step 2. When installing GitLab
+in the second step, do not supply the `EXTERNAL_URL` value.
+
+#### PostgreSQL primary node
+
+1. SSH into the PostgreSQL primary node.
+1. Generate a password hash for the PostgreSQL username/password pair. This assumes you will use the default
+ username of `gitlab` (recommended). The command will request a password
+ and confirmation. Use the value that is output by this command in the next
+ step as the value of `<postgresql_password_hash>`:
+
+ ```shell
+ sudo gitlab-ctl pg-password-md5 gitlab
+ ```
+
+1. Generate a password hash for the PgBouncer username/password pair. This assumes you will use the default
+ username of `pgbouncer` (recommended). The command will request a password
+ and confirmation. Use the value that is output by this command in the next
+ step as the value of `<pgbouncer_password_hash>`:
+
+ ```shell
+ sudo gitlab-ctl pg-password-md5 pgbouncer
+ ```
+
+1. Generate a password hash for the Consul database username/password pair. This assumes you will use the default
+ username of `gitlab-consul` (recommended). The command will request a password
+ and confirmation. Use the value that is output by this command in the next
+ step as the value of `<consul_password_hash>`:
+
+ ```shell
+ sudo gitlab-ctl pg-password-md5 gitlab-consul
+ ```
+
+1. On the primary database node, edit `/etc/gitlab/gitlab.rb` replacing values noted in the `# START user configuration` section:
+
+ ```ruby
+ # Disable all components except PostgreSQL and Repmgr and Consul
+ roles ['postgres_role']
+
+ # PostgreSQL configuration
+ postgresql['listen_address'] = '0.0.0.0'
+ postgresql['hot_standby'] = 'on'
+ postgresql['wal_level'] = 'replica'
+ postgresql['shared_preload_libraries'] = 'repmgr_funcs'
+
+ # Disable automatic database migrations
+ gitlab_rails['auto_migrate'] = false
+
+ # Configure the Consul agent
+ consul['services'] = %w(postgresql)
+
+ # START user configuration
+ # Please set the real values as explained in Required Information section
+ #
+ # Replace PGBOUNCER_PASSWORD_HASH with a generated md5 value
+ postgresql['pgbouncer_user_password'] = '<pgbouncer_password_hash>'
+ # Replace POSTGRESQL_PASSWORD_HASH with a generated md5 value
+ postgresql['sql_user_password'] = '<postgresql_password_hash>'
+ # Set `max_wal_senders` to one more than the number of database nodes in the cluster.
+ # This is used to prevent replication from using up all of the
+ # available database connections.
+ postgresql['max_wal_senders'] = 4
+ postgresql['max_replication_slots'] = 4
+
+ # Replace XXX.XXX.XXX.XXX/YY with Network Address
+ postgresql['trust_auth_cidr_addresses'] = %w(127.0.0.1/32 10.6.0.0/24)
+ repmgr['trust_auth_cidr_addresses'] = %w(127.0.0.1/32 10.6.0.0/24)
+
+ ## Enable service discovery for Prometheus
+ consul['enable'] = true
+ consul['monitoring_service_discovery'] = true
+
+ # Set the network addresses that the exporters will listen on for monitoring
+ node_exporter['listen_address'] = '0.0.0.0:9100'
+ postgres_exporter['listen_address'] = '0.0.0.0:9187'
+ postgres_exporter['dbname'] = 'gitlabhq_production'
+ postgres_exporter['password'] = '<postgresql_password_hash>'
+
+ ## The IPs of the Consul server nodes
+ ## You can also use FQDNs and intermix them with IPs
+ consul['configuration'] = {
+ retry_join: %w(10.6.0.11 10.6.0.12 10.6.0.13),
+ }
+ #
+ # END user configuration
+ ```
+
+1. [Reconfigure GitLab](../restart_gitlab.md#omnibus-gitlab-reconfigure) for the changes to take effect.
+1. You can list the current PostgreSQL primary, secondary nodes status via:
+
+ ```shell
+ sudo /opt/gitlab/bin/gitlab-ctl repmgr cluster show
+ ```
+
+1. Verify the GitLab services are running:
+
+ ```shell
+ sudo gitlab-ctl status
+ ```
+
+ The output should be similar to the following:
+
+ ```plaintext
+ run: consul: (pid 30593) 77133s; run: log: (pid 29912) 77156s
+ run: logrotate: (pid 23449) 3341s; run: log: (pid 29794) 77175s
+ run: node-exporter: (pid 30613) 77133s; run: log: (pid 29824) 77170s
+ run: postgres-exporter: (pid 30620) 77132s; run: log: (pid 29894) 77163s
+ run: postgresql: (pid 30630) 77132s; run: log: (pid 29618) 77181s
+ run: repmgrd: (pid 30639) 77132s; run: log: (pid 29985) 77150s
+ ```
+
+<div align="right">
+ <a type="button" class="btn btn-default" href="#setup-components">
+ Back to setup components <i class="fa fa-angle-double-up" aria-hidden="true"></i>
+ </a>
+</div>
+
+#### PostgreSQL secondary nodes
+
+1. On both the secondary nodes, add the same configuration specified above for the primary node
+ with an additional setting that will inform `gitlab-ctl` that they are standby nodes initially
+ and there's no need to attempt to register them as a primary node:
+
+ ```ruby
+ # Disable all components except PostgreSQL and Repmgr and Consul
+ roles ['postgres_role']
+
+ # PostgreSQL configuration
+ postgresql['listen_address'] = '0.0.0.0'
+ postgresql['hot_standby'] = 'on'
+ postgresql['wal_level'] = 'replica'
+ postgresql['shared_preload_libraries'] = 'repmgr_funcs'
+
+ # Disable automatic database migrations
+ gitlab_rails['auto_migrate'] = false
+
+ # Configure the Consul agent
+ consul['services'] = %w(postgresql)
+
+ # Specify if a node should attempt to be primary on initialization.
+ repmgr['master_on_initialization'] = false
+
+ # START user configuration
+ # Please set the real values as explained in Required Information section
+ #
+ # Replace PGBOUNCER_PASSWORD_HASH with a generated md5 value
+ postgresql['pgbouncer_user_password'] = '<pgbouncer_password_hash>'
+ # Replace POSTGRESQL_PASSWORD_HASH with a generated md5 value
+ postgresql['sql_user_password'] = '<postgresql_password_hash>'
+ # Set `max_wal_senders` to one more than the number of database nodes in the cluster.
+ # This is used to prevent replication from using up all of the
+ # available database connections.
+ postgresql['max_wal_senders'] = 4
+ postgresql['max_replication_slots'] = 4
+
+ # Replace XXX.XXX.XXX.XXX/YY with Network Address
+ postgresql['trust_auth_cidr_addresses'] = %w(127.0.0.1/32 10.6.0.0/24)
+ repmgr['trust_auth_cidr_addresses'] = %w(127.0.0.1/32 10.6.0.0/24)
+
+ ## Enable service discovery for Prometheus
+ consul['enable'] = true
+ consul['monitoring_service_discovery'] = true
+
+ # Set the network addresses that the exporters will listen on for monitoring
+ node_exporter['listen_address'] = '0.0.0.0:9100'
+ postgres_exporter['listen_address'] = '0.0.0.0:9187'
+ postgres_exporter['dbname'] = 'gitlabhq_production'
+ postgres_exporter['password'] = '<postgresql_password_hash>'
+
+ ## The IPs of the Consul server nodes
+ ## You can also use FQDNs and intermix them with IPs
+ consul['configuration'] = {
+ retry_join: %w(10.6.0.11 10.6.0.12 10.6.0.13),
+ }
+ # END user configuration
+ ```
+
+1. [Reconfigure GitLab](../restart_gitlab.md#omnibus-gitlab-reconfigure) for the changes to take effect.
+
+Advanced [configuration options](https://docs.gitlab.com/omnibus/settings/database.html)
+are supported and can be added if needed.
+
+<div align="right">
+ <a type="button" class="btn btn-default" href="#setup-components">
+ Back to setup components <i class="fa fa-angle-double-up" aria-hidden="true"></i>
+ </a>
+</div>
+
+#### PostgreSQL post-configuration
+
+SSH into the **primary node**:
+
+1. Open a database prompt:
+
+ ```shell
+ gitlab-psql -d gitlabhq_production
+ ```
+
+1. Enable the `pg_trgm` extension:
+
+ ```shell
+ CREATE EXTENSION pg_trgm;
+ ```
+
+1. Exit the database prompt by typing `\q` and Enter.
+
+1. Verify the cluster is initialized with one node:
+
+ ```shell
+ gitlab-ctl repmgr cluster show
+ ```
+
+ The output should be similar to the following:
+
+ ```plaintext
+ Role | Name | Upstream | Connection String
+ ----------+----------|----------|----------------------------------------
+ * master | HOSTNAME | | host=HOSTNAME user=gitlab_repmgr dbname=gitlab_repmgr
+ ```
+
+1. Note down the hostname or IP address in the connection string: `host=HOSTNAME`. We will
+ refer to the hostname in the next section as `<primary_node_name>`. If the value
+ is not an IP address, it will need to be a resolvable name (via DNS or
+ `/etc/hosts`)
+
+SSH into the **secondary node**:
+
+1. Set up the repmgr standby:
+
+ ```shell
+ gitlab-ctl repmgr standby setup <primary_node_name>
+ ```
+
+ Do note that this will remove the existing data on the node. The command
+ has a wait time.
+
+ The output should be similar to the following:
+
+ ```console
+ Doing this will delete the entire contents of /var/opt/gitlab/postgresql/data
+ If this is not what you want, hit Ctrl-C now to exit
+ To skip waiting, rerun with the -w option
+ Sleeping for 30 seconds
+ Stopping the database
+ Removing the data
+ Cloning the data
+ Starting the database
+ Registering the node with the cluster
+ ok: run: repmgrd: (pid 19068) 0s
+ ```
+
+Before moving on, make sure the databases are configured correctly. Run the
+following command on the **primary** node to verify that replication is working
+properly and the secondary nodes appear in the cluster:
+
+```shell
+gitlab-ctl repmgr cluster show
+```
+
+The output should be similar to the following:
+
+```plaintext
+Role | Name | Upstream | Connection String
+----------+---------|-----------|------------------------------------------------
+* master | MASTER | | host=<primary_node_name> user=gitlab_repmgr dbname=gitlab_repmgr
+ standby | STANDBY | MASTER | host=<secondary_node_name> user=gitlab_repmgr dbname=gitlab_repmgr
+ standby | STANDBY | MASTER | host=<secondary_node_name> user=gitlab_repmgr dbname=gitlab_repmgr
+```
+
+If the 'Role' column for any node says "FAILED", check the
+[Troubleshooting section](troubleshooting.md) before proceeding.
+
+Also, check that the `repmgr-check-master` command works successfully on each node:
+
+```shell
+su - gitlab-consul
+gitlab-ctl repmgr-check-master || echo 'This node is a standby repmgr node'
+```
+
+This command relies on exit codes to tell Consul whether a particular node is a master
+or secondary. The most important thing here is that this command does not produce errors.
+If there are errors it's most likely due to incorrect `gitlab-consul` database user permissions.
+Check the [Troubleshooting section](troubleshooting.md) before proceeding.
+
+<div align="right">
+ <a type="button" class="btn btn-default" href="#setup-components">
+ Back to setup components <i class="fa fa-angle-double-up" aria-hidden="true"></i>
+ </a>
+</div>
+
+## Configure PgBouncer
+
+Now that the PostgreSQL servers are all set up, let's configure PgBouncer.
+The following IPs will be used as an example:
+
+- `10.6.0.21`: PgBouncer 1
+- `10.6.0.22`: PgBouncer 2
+- `10.6.0.23`: PgBouncer 3
+
+1. On each PgBouncer node, edit `/etc/gitlab/gitlab.rb`, and replace
+ `<consul_password_hash>` and `<pgbouncer_password_hash>` with the
+ password hashes you [set up previously](#postgresql-primary-node):
+
+ ```ruby
+ # Disable all components except Pgbouncer and Consul agent
+ roles ['pgbouncer_role']
+
+ # Configure PgBouncer
+ pgbouncer['admin_users'] = %w(pgbouncer gitlab-consul)
+
+ pgbouncer['users'] = {
+ 'gitlab-consul': {
+ password: '<consul_password_hash>'
+ },
+ 'pgbouncer': {
+ password: '<pgbouncer_password_hash>'
+ }
+ }
+
+ # Configure Consul agent
+ consul['watchers'] = %w(postgresql)
+ consul['enable'] = true
+ consul['configuration'] = {
+ retry_join: %w(10.6.0.11 10.6.0.12 10.6.0.13)
+ }
+
+ # Enable service discovery for Prometheus
+ consul['monitoring_service_discovery'] = true
+
+ # Set the network addresses that the exporters will listen on
+ node_exporter['listen_address'] = '0.0.0.0:9100'
+ pgbouncer_exporter['listen_address'] = '0.0.0.0:9188'
+ ```
+
+1. [Reconfigure Omnibus GitLab](../restart_gitlab.md#omnibus-gitlab-reconfigure) for the changes to take effect.
+
+1. Create a `.pgpass` file so Consul is able to
+ reload PgBouncer. Enter the PgBouncer password twice when asked:
+
+ ```shell
+ gitlab-ctl write-pgpass --host 127.0.0.1 --database pgbouncer --user pgbouncer --hostuser gitlab-consul
+ ```
+
+1. Ensure each node is talking to the current master:
+
+ ```shell
+ gitlab-ctl pgb-console # You will be prompted for PGBOUNCER_PASSWORD
+ ```
+
+ If there is an error `psql: ERROR: Auth failed` after typing in the
+ password, ensure you previously generated the MD5 password hashes with the correct
+ format. The correct format is to concatenate the password and the username:
+ `PASSWORDUSERNAME`. For example, `Sup3rS3cr3tpgbouncer` would be the text
+ needed to generate an MD5 password hash for the `pgbouncer` user.
+
+1. Once the console prompt is available, run the following queries:
+
+ ```shell
+ show databases ; show clients ;
+ ```
+
+ The output should be similar to the following:
+
+ ```plaintext
+ name | host | port | database | force_user | pool_size | reserve_pool | pool_mode | max_connections | current_connections
+ ---------------------+-------------+------+---------------------+------------+-----------+--------------+-----------+-----------------+---------------------
+ gitlabhq_production | MASTER_HOST | 5432 | gitlabhq_production | | 20 | 0 | | 0 | 0
+ pgbouncer | | 6432 | pgbouncer | pgbouncer | 2 | 0 | statement | 0 | 0
+ (2 rows)
+
+ type | user | database | state | addr | port | local_addr | local_port | connect_time | request_time | ptr | link | remote_pid | tls
+ ------+-----------+---------------------+---------+----------------+-------+------------+------------+---------------------+---------------------+-----------+------+------------+-----
+ C | pgbouncer | pgbouncer | active | 127.0.0.1 | 56846 | 127.0.0.1 | 6432 | 2017-08-21 18:09:59 | 2017-08-21 18:10:48 | 0x22b3880 | | 0 |
+ (2 rows)
+ ```
+
+1. Verify the GitLab services are running:
+
+ ```shell
+ sudo gitlab-ctl status
+ ```
+
+ The output should be similar to the following:
+
+ ```plaintext
+ run: consul: (pid 31530) 77150s; run: log: (pid 31106) 77182s
+ run: logrotate: (pid 32613) 3357s; run: log: (pid 30107) 77500s
+ run: node-exporter: (pid 31550) 77149s; run: log: (pid 30138) 77493s
+ run: pgbouncer: (pid 32033) 75593s; run: log: (pid 31117) 77175s
+ run: pgbouncer-exporter: (pid 31558) 77148s; run: log: (pid 31498) 77156s
+ ```
+
+<div align="right">
+ <a type="button" class="btn btn-default" href="#setup-components">
+ Back to setup components <i class="fa fa-angle-double-up" aria-hidden="true"></i>
+ </a>
+</div>
+
+### Configure the internal load balancer
+
+If you're running more than one PgBouncer node as recommended, then at this time you'll need to set
+up a TCP internal load balancer to serve each correctly.
+
+The following IP will be used as an example:
+
+- `10.6.0.20`: Internal Load Balancer
+
+Here's how you could do it with [HAProxy](https://www.haproxy.org/):
+
+```plaintext
+global
+ log /dev/log local0
+ log localhost local1 notice
+ log stdout format raw local0
+
+defaults
+ log global
+ default-server inter 10s fall 3 rise 2
+ balance leastconn
+
+frontend internal-pgbouncer-tcp-in
+ bind *:6432
+ mode tcp
+ option tcplog
+
+ default_backend pgbouncer
+
+backend pgbouncer
+ mode tcp
+ option tcp-check
+
+ server pgbouncer1 10.6.0.21:6432 check
+ server pgbouncer2 10.6.0.22:6432 check
+ server pgbouncer3 10.6.0.23:6432 check
+```
+
+Refer to your preferred Load Balancer's documentation for further guidance.
+
+<div align="right">
+ <a type="button" class="btn btn-default" href="#setup-components">
+ Back to setup components <i class="fa fa-angle-double-up" aria-hidden="true"></i>
+ </a>
+</div>
+
+## Configure Gitaly
+
+Deploying Gitaly in its own server can benefit GitLab installations that are
+larger than a single machine.
+
+The Gitaly node requirements are dependent on customer data, specifically the number of
+projects and their repository sizes. Two nodes are recommended as an absolute minimum.
+Each Gitaly node should store no more than 5TB of data and have the number of
+[`gitaly-ruby` workers](../gitaly/index.md#gitaly-ruby) set to 20% of available CPUs.
+Additional nodes should be considered in conjunction with a review of expected
+data size and spread based on the recommendations above.
+
+It is also strongly recommended that all Gitaly nodes be set up with SSD disks with
+a throughput of at least 8,000 IOPS for read operations and 2,000 IOPS for write,
+as Gitaly has heavy I/O. These IOPS values are recommended only as a starter as with
+time they may be adjusted higher or lower depending on the scale of your environment's workload.
+If you're running the environment on a Cloud provider, you may need to refer to
+their documentation on how to configure IOPS correctly.
+
+Some things to note:
+
+- The GitLab Rails application shards repositories into [repository storages](../repository_storage_paths.md).
+- A Gitaly server can host one or more storages.
+- A GitLab server can use one or more Gitaly servers.
+- Gitaly addresses must be specified in such a way that they resolve
+ correctly for ALL Gitaly clients.
+- Gitaly servers must not be exposed to the public internet, as Gitaly's network
+ traffic is unencrypted by default. The use of a firewall is highly recommended
+ to restrict access to the Gitaly server. Another option is to
+ [use TLS](#gitaly-tls-support).
+
+TIP: **Tip:**
+For more information about Gitaly's history and network architecture see the
+[standalone Gitaly documentation](../gitaly/index.md).
+
+Note: **Note:** The token referred to throughout the Gitaly documentation is
+just an arbitrary password selected by the administrator. It is unrelated to
+tokens created for the GitLab API or other similar web API tokens.
+
+Below we describe how to configure two Gitaly servers, with IPs and
+domain names:
+
+- `10.6.0.51`: Gitaly 1 (`gitaly1.internal`)
+- `10.6.0.52`: Gitaly 2 (`gitaly2.internal`)
+
+The secret token is assumed to be `gitalysecret` and that
+your GitLab installation has three repository storages:
+
+- `default` on Gitaly 1
+- `storage1` on Gitaly 1
+- `storage2` on Gitaly 2
+
+On each node:
+
+1. [Download/Install](https://about.gitlab.com/install/) the Omnibus GitLab
+ package you want using **steps 1 and 2** from the GitLab downloads page but
+ **without** providing the `EXTERNAL_URL` value.
+1. Edit `/etc/gitlab/gitlab.rb` to configure storage paths, enable
+ the network listener and configure the token:
+
+ <!--
+ updates to following example must also be made at
+ https://gitlab.com/gitlab-org/charts/gitlab/blob/master/doc/advanced/external-gitaly/external-omnibus-gitaly.md#configure-omnibus-gitlab
+ -->
+
+ ```ruby
+ # /etc/gitlab/gitlab.rb
+
+ # Gitaly and GitLab use two shared secrets for authentication, one to authenticate gRPC requests
+ # to Gitaly, and a second for authentication callbacks from GitLab-Shell to the GitLab internal API.
+ # The following two values must be the same as their respective values
+ # of the GitLab Rails application setup
+ gitaly['auth_token'] = 'gitlaysecret'
+ gitlab_shell['secret_token'] = 'shellsecret'
+
+ # Avoid running unnecessary services on the Gitaly server
+ postgresql['enable'] = false
+ redis['enable'] = false
+ nginx['enable'] = false
+ puma['enable'] = false
+ unicorn['enable'] = false
+ sidekiq['enable'] = false
+ gitlab_workhorse['enable'] = false
+ grafana['enable'] = false
+ gitlab_exporter['enable'] = false
+
+ # If you run a seperate monitoring node you can disable these services
+ alertmanager['enable'] = false
+ prometheus['enable'] = false
+
+ # Prevent database connections during 'gitlab-ctl reconfigure'
+ gitlab_rails['rake_cache_clear'] = false
+ gitlab_rails['auto_migrate'] = false
+
+ # Configure the gitlab-shell API callback URL. Without this, `git push` will
+ # fail. This can be your 'front door' GitLab URL or an internal load
+ # balancer.
+ # Don't forget to copy `/etc/gitlab/gitlab-secrets.json` from web server to Gitaly server.
+ gitlab_rails['internal_api_url'] = 'https://gitlab.example.com'
+
+ # Make Gitaly accept connections on all network interfaces. You must use
+ # firewalls to restrict access to this address/port.
+ # Comment out following line if you only want to support TLS connections
+ gitaly['listen_addr'] = "0.0.0.0:8075"
+
+ ## Enable service discovery for Prometheus
+ consul['enable'] = true
+ consul['monitoring_service_discovery'] = true
+
+ # Set the network addresses that the exporters will listen on for monitoring
+ gitaly['prometheus_listen_addr'] = "0.0.0.0:9236"
+ node_exporter['listen_address'] = '0.0.0.0:9100'
+ gitlab_rails['prometheus_address'] = '10.6.0.81:9090'
+
+ ## The IPs of the Consul server nodes
+ ## You can also use FQDNs and intermix them with IPs
+ consul['configuration'] = {
+ retry_join: %w(10.6.0.11 10.6.0.12 10.6.0.13),
+ }
+ ```
+
+1. Append the following to `/etc/gitlab/gitlab.rb` for each respective server:
+ 1. On `gitaly1.internal`:
+
+ ```ruby
+ git_data_dirs({
+ 'default' => {
+ 'path' => '/var/opt/gitlab/git-data'
+ },
+ 'storage1' => {
+ 'path' => '/mnt/gitlab/git-data'
+ },
+ })
+ ```
+
+ 1. On `gitaly2.internal`:
+
+ ```ruby
+ git_data_dirs({
+ 'storage2' => {
+ 'path' => '/mnt/gitlab/git-data'
+ },
+ })
+ ```
+
+ <!--
+ updates to following example must also be made at
+ https://gitlab.com/gitlab-org/charts/gitlab/blob/master/doc/advanced/external-gitaly/external-omnibus-gitaly.md#configure-omnibus-gitlab
+ -->
+
+1. Save the file and [reconfigure GitLab](../restart_gitlab.md#omnibus-gitlab-reconfigure).
+1. Confirm that Gitaly can perform callbacks to the internal API:
+
+ ```shell
+ sudo /opt/gitlab/embedded/service/gitlab-shell/bin/check -config /opt/gitlab/embedded/service/gitlab-shell/config.yml
+ ```
+
+1. Verify the GitLab services are running:
+
+ ```shell
+ sudo gitlab-ctl status
+ ```
+
+ The output should be similar to the following:
+
+ ```plaintext
+ run: consul: (pid 30339) 77006s; run: log: (pid 29878) 77020s
+ run: gitaly: (pid 30351) 77005s; run: log: (pid 29660) 77040s
+ run: logrotate: (pid 7760) 3213s; run: log: (pid 29782) 77032s
+ run: node-exporter: (pid 30378) 77004s; run: log: (pid 29812) 77026s
+ ```
+
+### Gitaly TLS support
+
+Gitaly supports TLS encryption. To be able to communicate
+with a Gitaly instance that listens for secure connections you will need to use `tls://` URL
+scheme in the `gitaly_address` of the corresponding storage entry in the GitLab configuration.
+
+You will need to bring your own certificates as this isn't provided automatically.
+The certificate, or its certificate authority, must be installed on all Gitaly
+nodes (including the Gitaly node using the certificate) and on all client nodes
+that communicate with it following the procedure described in
+[GitLab custom certificate configuration](https://docs.gitlab.com/omnibus/settings/ssl.html#install-custom-public-certificates).
+
+NOTE: **Note:**
+The self-signed certificate must specify the address you use to access the
+Gitaly server. If you are addressing the Gitaly server by a hostname, you can
+either use the Common Name field for this, or add it as a Subject Alternative
+Name. If you are addressing the Gitaly server by its IP address, you must add it
+as a Subject Alternative Name to the certificate.
+[gRPC does not support using an IP address as Common Name in a certificate](https://github.com/grpc/grpc/issues/2691).
+
+NOTE: **Note:**
+It is possible to configure Gitaly servers with both an
+unencrypted listening address `listen_addr` and an encrypted listening
+address `tls_listen_addr` at the same time. This allows you to do a
+gradual transition from unencrypted to encrypted traffic, if necessary.
+
+To configure Gitaly with TLS:
+
+1. Create the `/etc/gitlab/ssl` directory and copy your key and certificate there:
+
+ ```shell
+ sudo mkdir -p /etc/gitlab/ssl
+ sudo chmod 755 /etc/gitlab/ssl
+ sudo cp key.pem cert.pem /etc/gitlab/ssl/
+ sudo chmod 644 key.pem cert.pem
+ ```
+
+1. Copy the cert to `/etc/gitlab/trusted-certs` so Gitaly will trust the cert when
+ calling into itself:
+
+ ```shell
+ sudo cp /etc/gitlab/ssl/cert.pem /etc/gitlab/trusted-certs/
+ ```
+
+1. Edit `/etc/gitlab/gitlab.rb` and add:
+
+ <!--
+ updates to following example must also be made at
+ https://gitlab.com/gitlab-org/charts/gitlab/blob/master/doc/advanced/external-gitaly/external-omnibus-gitaly.md#configure-omnibus-gitlab
+ -->
+
+ ```ruby
+ gitaly['tls_listen_addr'] = "0.0.0.0:9999"
+ gitaly['certificate_path'] = "/etc/gitlab/ssl/cert.pem"
+ gitaly['key_path'] = "/etc/gitlab/ssl/key.pem"
+ ```
+
+1. Delete `gitaly['listen_addr']` to allow only encrypted connections.
+1. Save the file and [reconfigure GitLab](../restart_gitlab.md#omnibus-gitlab-reconfigure).
+
+<div align="right">
+ <a type="button" class="btn btn-default" href="#setup-components">
+ Back to setup components <i class="fa fa-angle-double-up" aria-hidden="true"></i>
+ </a>
+</div>
+
+## Configure Sidekiq
+
+Sidekiq requires connection to the Redis, PostgreSQL and Gitaly instance.
+The following IPs will be used as an example:
+
+- `10.6.0.71`: Sidekiq 1
+- `10.6.0.72`: Sidekiq 2
+- `10.6.0.73`: Sidekiq 3
+- `10.6.0.74`: Sidekiq 4
+
+To configure the Sidekiq nodes, one each one:
+
+1. SSH into the Sidekiq server.
+1. [Download/install](https://about.gitlab.com/install/) the Omnibus GitLab package
+you want using steps 1 and 2 from the GitLab downloads page.
+**Do not complete any other steps on the download page.**
+1. Open `/etc/gitlab/gitlab.rb` with your editor:
+
+ ```ruby
+ ########################################
+ ##### Services Disabled ###
+ ########################################
+
+ nginx['enable'] = false
+ grafana['enable'] = false
+ prometheus['enable'] = false
+ gitlab_rails['auto_migrate'] = false
+ alertmanager['enable'] = false
+ gitaly['enable'] = false
+ gitlab_workhorse['enable'] = false
+ nginx['enable'] = false
+ puma['enable'] = false
+ postgres_exporter['enable'] = false
+ postgresql['enable'] = false
+ redis['enable'] = false
+ redis_exporter['enable'] = false
+ gitlab_exporter['enable'] = false
+
+ ########################################
+ #### Redis ###
+ ########################################
+
+ ## Must be the same in every sentinel node
+ redis['master_name'] = 'gitlab-redis'
+
+ ## The same password for Redis authentication you set up for the master node.
+ redis['master_password'] = '<redis_primary_password>'
+
+ ## A list of sentinels with `host` and `port`
+ gitlab_rails['redis_sentinels'] = [
+ {'host' => '10.6.0.11', 'port' => 26379},
+ {'host' => '10.6.0.12', 'port' => 26379},
+ {'host' => '10.6.0.13', 'port' => 26379},
+ ]
+
+ #######################################
+ ### Gitaly ###
+ #######################################
+
+ git_data_dirs({
+ 'default' => { 'gitaly_address' => 'tcp://gitaly1.internal:8075' },
+ 'storage1' => { 'gitaly_address' => 'tcp://gitaly1.internal:8075' },
+ 'storage2' => { 'gitaly_address' => 'tcp://gitaly2.internal:8075' },
+ })
+ gitlab_rails['gitaly_token'] = 'YOUR_TOKEN'
+
+ #######################################
+ ### Postgres ###
+ #######################################
+ gitlab_rails['db_host'] = '10.6.0.20' # internal load balancer IP
+ gitlab_rails['db_port'] = 6432
+ gitlab_rails['db_password'] = '<postgresql_user_password>'
+ gitlab_rails['db_adapter'] = 'postgresql'
+ gitlab_rails['db_encoding'] = 'unicode'
+ gitlab_rails['auto_migrate'] = false
+
+ #######################################
+ ### Sidekiq configuration ###
+ #######################################
+ sidekiq['listen_address'] = "0.0.0.0"
+
+ #######################################
+ ### Monitoring configuration ###
+ #######################################
+ consul['enable'] = true
+ consul['monitoring_service_discovery'] = true
+
+ consul['configuration'] = {
+ retry_join: %w(10.6.0.11 10.6.0.12 10.6.0.13)
+ }
+
+ # Set the network addresses that the exporters will listen on
+ node_exporter['listen_address'] = '0.0.0.0:9100'
+
+ # Rails Status for prometheus
+ gitlab_rails['monitoring_whitelist'] = ['10.6.0.81/32', '127.0.0.0/8']
+ gitlab_rails['prometheus_address'] = '10.6.0.81:9090'
+ ```
+
+1. Save the file and [reconfigure GitLab](../restart_gitlab.md#omnibus-gitlab-reconfigure).
+1. Verify the GitLab services are running:
+
+ ```shell
+ sudo gitlab-ctl status
+ ```
+
+ The output should be similar to the following:
+
+ ```plaintext
+ run: consul: (pid 30114) 77353s; run: log: (pid 29756) 77367s
+ run: logrotate: (pid 9898) 3561s; run: log: (pid 29653) 77380s
+ run: node-exporter: (pid 30134) 77353s; run: log: (pid 29706) 77372s
+ run: sidekiq: (pid 30142) 77351s; run: log: (pid 29638) 77386s
+ ```
+
+TIP: **Tip:**
+You can also run [multiple Sidekiq processes](../operations/extra_sidekiq_processes.md).
+
+<div align="right">
+ <a type="button" class="btn btn-default" href="#setup-components">
+ Back to setup components <i class="fa fa-angle-double-up" aria-hidden="true"></i>
+ </a>
+</div>
+
+## Configure GitLab Rails
+
+NOTE: **Note:**
+In our architectures we run each GitLab Rails node using the Puma webserver
+and have its number of workers set to 90% of available CPUs along with four threads. For
+nodes that are running Rails with other components the worker value should be reduced
+accordingly where we've found 50% achieves a good balance but this is dependent
+on workload.
+
+This section describes how to configure the GitLab application (Rails) component.
+On each node perform the following:
+
+1. If you're [using NFS](#configure-nfs-optional):
+
+ 1. If necessary, install the NFS client utility packages using the following
+ commands:
+
+ ```shell
+ # Ubuntu/Debian
+ apt-get install nfs-common
+
+ # CentOS/Red Hat
+ yum install nfs-utils nfs-utils-lib
+ ```
+
+ 1. Specify the necessary NFS mounts in `/etc/fstab`.
+ The exact contents of `/etc/fstab` will depend on how you chose
+ to configure your NFS server. See the [NFS documentation](../high_availability/nfs.md)
+ for examples and the various options.
+
+ 1. Create the shared directories. These may be different depending on your NFS
+ mount locations.
+
+ ```shell
+ mkdir -p /var/opt/gitlab/.ssh /var/opt/gitlab/gitlab-rails/uploads /var/opt/gitlab/gitlab-rails/shared /var/opt/gitlab/gitlab-ci/builds /var/opt/gitlab/git-data
+ ```
+
+1. Download/install Omnibus GitLab using **steps 1 and 2** from
+ [GitLab downloads](https://about.gitlab.com/install/). Do not complete other
+ steps on the download page.
+1. Create/edit `/etc/gitlab/gitlab.rb` and use the following configuration.
+ To maintain uniformity of links across nodes, the `external_url`
+ on the application server should point to the external URL that users will use
+ to access GitLab. This would be the URL of the [external load balancer](#configure-the-external-load-balancer)
+ which will route traffic to the GitLab application server:
+
+ ```ruby
+ external_url 'https://gitlab.example.com'
+
+ # Gitaly and GitLab use two shared secrets for authentication, one to authenticate gRPC requests
+ # to Gitaly, and a second for authentication callbacks from GitLab-Shell to the GitLab internal API.
+ # The following two values must be the same as their respective values
+ # of the Gitaly setup
+ gitlab_rails['gitaly_token'] = 'gitalyecret'
+ gitlab_shell['secret_token'] = 'shellsecret'
+
+ git_data_dirs({
+ 'default' => { 'gitaly_address' => 'tcp://gitaly1.internal:8075' },
+ 'storage1' => { 'gitaly_address' => 'tcp://gitaly1.internal:8075' },
+ 'storage2' => { 'gitaly_address' => 'tcp://gitaly2.internal:8075' },
+ })
+
+ ## Disable components that will not be on the GitLab application server
+ roles ['application_role']
+ gitaly['enable'] = false
+ nginx['enable'] = true
+ sidekiq['enable'] = false
+
+ ## PostgreSQL connection details
+ # Disable PostgreSQL on the application node
+ postgresql['enable'] = false
+ gitlab_rails['db_host'] = '10.6.0.20' # internal load balancer IP
+ gitlab_rails['db_port'] = 6432
+ gitlab_rails['db_password'] = '<postgresql_user_password>'
+ gitlab_rails['auto_migrate'] = false
+
+ ## Redis connection details
+ ## Must be the same in every sentinel node
+ redis['master_name'] = 'gitlab-redis'
+
+ ## The same password for Redis authentication you set up for the Redis primary node.
+ redis['master_password'] = '<redis_primary_password>'
+
+ ## A list of sentinels with `host` and `port`
+ gitlab_rails['redis_sentinels'] = [
+ {'host' => '10.6.0.11', 'port' => 26379},
+ {'host' => '10.6.0.12', 'port' => 26379},
+ {'host' => '10.6.0.13', 'port' => 26379}
+ ]
+
+ ## Enable service discovery for Prometheus
+ consul['enable'] = true
+ consul['monitoring_service_discovery'] = true
+
+ # Set the network addresses that the exporters used for monitoring will listen on
+ node_exporter['listen_address'] = '0.0.0.0:9100'
+ gitlab_workhorse['prometheus_listen_addr'] = '0.0.0.0:9229'
+ sidekiq['listen_address'] = "0.0.0.0"
+ puma['listen'] = '0.0.0.0'
+
+ ## The IPs of the Consul server nodes
+ ## You can also use FQDNs and intermix them with IPs
+ consul['configuration'] = {
+ retry_join: %w(10.6.0.11 10.6.0.12 10.6.0.13),
+ }
+
+ # Add the monitoring node's IP address to the monitoring whitelist and allow it to
+ # scrape the NGINX metrics
+ gitlab_rails['monitoring_whitelist'] = ['10.6.0.81/32', '127.0.0.0/8']
+ nginx['status']['options']['allow'] = ['10.6.0.81/32', '127.0.0.0/8']
+ gitlab_rails['prometheus_address'] = '10.6.0.81:9090'
+
+ ## Uncomment and edit the following options if you have set up NFS
+ ##
+ ## Prevent GitLab from starting if NFS data mounts are not available
+ ##
+ #high_availability['mountpoint'] = '/var/opt/gitlab/git-data'
+ ##
+ ## Ensure UIDs and GIDs match between servers for permissions via NFS
+ ##
+ #user['uid'] = 9000
+ #user['gid'] = 9000
+ #web_server['uid'] = 9001
+ #web_server['gid'] = 9001
+ #registry['uid'] = 9002
+ #registry['gid'] = 9002
+ ```
+
+1. If you're using [Gitaly with TLS support](#gitaly-tls-support), make sure the
+ `git_data_dirs` entry is configured with `tls` instead of `tcp`:
+
+ ```ruby
+ git_data_dirs({
+ 'default' => { 'gitaly_address' => 'tls://gitaly1.internal:9999' },
+ 'storage1' => { 'gitaly_address' => 'tls://gitaly1.internal:9999' },
+ 'storage2' => { 'gitaly_address' => 'tls://gitaly2.internal:9999' },
+ })
+ ```
+
+ 1. Copy the cert into `/etc/gitlab/trusted-certs`:
+
+ ```shell
+ sudo cp cert.pem /etc/gitlab/trusted-certs/
+ ```
+
+1. Save the file and [reconfigure GitLab](../restart_gitlab.md#omnibus-gitlab-reconfigure).
+1. Run `sudo gitlab-rake gitlab:gitaly:check` to confirm the node can connect to Gitaly.
+1. Tail the logs to see the requests:
+
+ ```shell
+ sudo gitlab-ctl tail gitaly
+ ```
+
+1. Verify the GitLab services are running:
+
+ ```shell
+ sudo gitlab-ctl status
+ ```
+
+ The output should be similar to the following:
+
+ ```plaintext
+ run: consul: (pid 4890) 8647s; run: log: (pid 29962) 79128s
+ run: gitlab-exporter: (pid 4902) 8647s; run: log: (pid 29913) 79134s
+ run: gitlab-workhorse: (pid 4904) 8646s; run: log: (pid 29713) 79155s
+ run: logrotate: (pid 12425) 1446s; run: log: (pid 29798) 79146s
+ run: nginx: (pid 4925) 8646s; run: log: (pid 29726) 79152s
+ run: node-exporter: (pid 4931) 8645s; run: log: (pid 29855) 79140s
+ run: puma: (pid 4936) 8645s; run: log: (pid 29656) 79161s
+ ```
+
+NOTE: **Note:**
+When you specify `https` in the `external_url`, as in the example
+above, GitLab assumes you have SSL certificates in `/etc/gitlab/ssl/`. If
+certificates are not present, NGINX will fail to start. See the
+[NGINX documentation](https://docs.gitlab.com/omnibus/settings/nginx.html#enable-https)
+for more information.
+
+### GitLab Rails post-configuration
+
+1. Ensure that all migrations ran:
+
+ ```shell
+ gitlab-rake gitlab:db:configure
+ ```
+
+ NOTE: **Note:**
+ If you encounter a `rake aborted!` error stating that PgBouncer is failing to connect to
+ PostgreSQL it may be that your PgBouncer node's IP address is missing from
+ PostgreSQL's `trust_auth_cidr_addresses` in `gitlab.rb` on your database nodes. See
+ [PgBouncer error `ERROR: pgbouncer cannot connect to server`](troubleshooting.md#pgbouncer-error-error-pgbouncer-cannot-connect-to-server)
+ in the Troubleshooting section before proceeding.
+
+1. [Configure fast lookup of authorized SSH keys in the database](../operations/fast_ssh_key_lookup.md).
+
+<div align="right">
+ <a type="button" class="btn btn-default" href="#setup-components">
+ Back to setup components <i class="fa fa-angle-double-up" aria-hidden="true"></i>
+ </a>
+</div>
+
+## Configure Prometheus
+
+The Omnibus GitLab package can be used to configure a standalone Monitoring node
+running [Prometheus](../monitoring/prometheus/index.md) and
+[Grafana](../monitoring/performance/grafana_configuration.md):
+
+1. SSH into the Monitoring node.
+1. [Download/install](https://about.gitlab.com/install/) the Omnibus GitLab
+ package you want using **steps 1 and 2** from the GitLab downloads page.
+ Do not complete any other steps on the download page.
+1. Edit `/etc/gitlab/gitlab.rb` and add the contents:
+
+ ```ruby
+ external_url 'http://gitlab.example.com'
+
+ # Disable all other services
+ gitlab_rails['auto_migrate'] = false
+ alertmanager['enable'] = false
+ gitaly['enable'] = false
+ gitlab_exporter['enable'] = false
+ gitlab_workhorse['enable'] = false
+ nginx['enable'] = true
+ postgres_exporter['enable'] = false
+ postgresql['enable'] = false
+ redis['enable'] = false
+ redis_exporter['enable'] = false
+ sidekiq['enable'] = false
+ puma['enable'] = false
+ unicorn['enable'] = false
+ node_exporter['enable'] = false
+ gitlab_exporter['enable'] = false
+
+ # Enable Prometheus
+ prometheus['enable'] = true
+ prometheus['listen_address'] = '0.0.0.0:9090'
+ prometheus['monitor_kubernetes'] = false
+
+ # Enable Login form
+ grafana['disable_login_form'] = false
+
+ # Enable Grafana
+ grafana['enable'] = true
+ grafana['admin_password'] = '<grafana_password>'
+
+ # Enable service discovery for Prometheus
+ consul['enable'] = true
+ consul['monitoring_service_discovery'] = true
+ consul['configuration'] = {
+ retry_join: %w(10.6.0.11 10.6.0.12 10.6.0.13)
+ }
+ ```
+
+1. Save the file and [reconfigure GitLab](../restart_gitlab.md#omnibus-gitlab-reconfigure).
+1. In the GitLab UI, set `admin/application_settings/metrics_and_profiling` > Metrics - Grafana to `/-/grafana` to
+ `http[s]://<MONITOR NODE>/-/grafana`.
+1. Verify the GitLab services are running:
+
+ ```shell
+ sudo gitlab-ctl status
+ ```
+
+ The output should be similar to the following:
+
+ ```plaintext
+ run: consul: (pid 31637) 17337s; run: log: (pid 29748) 78432s
+ run: grafana: (pid 31644) 17337s; run: log: (pid 29719) 78438s
+ run: logrotate: (pid 31809) 2936s; run: log: (pid 29581) 78462s
+ run: nginx: (pid 31665) 17335s; run: log: (pid 29556) 78468s
+ run: prometheus: (pid 31672) 17335s; run: log: (pid 29633) 78456s
+ ```
+
+<div align="right">
+ <a type="button" class="btn btn-default" href="#setup-components">
+ Back to setup components <i class="fa fa-angle-double-up" aria-hidden="true"></i>
+ </a>
+</div>
+
+## Configure the object storage
+
+GitLab supports using an object storage service for holding numerous types of data.
+It's recommended over [NFS](#configure-nfs-optional) and in general it's better
+in larger setups as object storage is typically much more performant, reliable,
+and scalable.
+
+Object storage options that GitLab has tested, or is aware of customers using include:
+
+- SaaS/Cloud solutions such as [Amazon S3](https://aws.amazon.com/s3/), [Google cloud storage](https://cloud.google.com/storage).
+- On-premises hardware and appliances from various storage vendors.
+- MinIO. There is [a guide to deploying this](https://docs.gitlab.com/charts/advanced/external-object-storage/minio.html) within our Helm Chart documentation.
+
+For configuring GitLab to use Object Storage refer to the following guides
+based on what features you intend to use:
+
+1. Configure [object storage for backups](../../raketasks/backup_restore.md#uploading-backups-to-a-remote-cloud-storage).
+1. Configure [object storage for job artifacts](../job_artifacts.md#using-object-storage)
+ including [incremental logging](../job_logs.md#new-incremental-logging-architecture).
+1. Configure [object storage for LFS objects](../lfs/index.md#storing-lfs-objects-in-remote-object-storage).
+1. Configure [object storage for uploads](../uploads.md#using-object-storage-core-only).
+1. Configure [object storage for merge request diffs](../merge_request_diffs.md#using-object-storage).
+1. Configure [object storage for Container Registry](../packages/container_registry.md#use-object-storage) (optional feature).
+1. Configure [object storage for Mattermost](https://docs.mattermost.com/administration/config-settings.html#file-storage) (optional feature).
+1. Configure [object storage for packages](../packages/index.md#using-object-storage) (optional feature). **(PREMIUM ONLY)**
+1. Configure [object storage for Dependency Proxy](../packages/dependency_proxy.md#using-object-storage) (optional feature). **(PREMIUM ONLY)**
+1. Configure [object storage for Pseudonymizer](../pseudonymizer.md#configuration) (optional feature). **(ULTIMATE ONLY)**
+1. Configure [object storage for autoscale Runner caching](https://docs.gitlab.com/runner/configuration/autoscale.html#distributed-runners-caching) (optional - for improved performance).
+1. Configure [object storage for Terraform state files](../terraform_state.md#using-object-storage-core-only).
+
+Using separate buckets for each data type is the recommended approach for GitLab.
+
+A limitation of our configuration is that each use of object storage is separately configured.
+[We have an issue for improving this](https://gitlab.com/gitlab-org/gitlab/-/issues/23345)
+and easily using one bucket with separate folders is one improvement that this might bring.
+
+There is at least one specific issue with using the same bucket:
+when GitLab is deployed with the Helm chart restore from backup
+[will not properly function](https://docs.gitlab.com/charts/advanced/external-object-storage/#lfs-artifacts-uploads-packages-external-diffs-pseudonymizer)
+unless separate buckets are used.
+
+One risk of using a single bucket would be if your organization decided to
+migrate GitLab to the Helm deployment in the future. GitLab would run, but the situation with
+backups might not be realized until the organization had a critical requirement for the backups to
+work.
+
+<div align="right">
+ <a type="button" class="btn btn-default" href="#setup-components">
+ Back to setup components <i class="fa fa-angle-double-up" aria-hidden="true"></i>
+ </a>
+</div>
+
+## Configure NFS (optional)
+
+[Object storage](#configure-the-object-storage), along with [Gitaly](#configure-gitaly)
+are recommended over NFS wherever possible for improved performance. If you intend
+to use GitLab Pages, this currently [requires NFS](troubleshooting.md#gitlab-pages-requires-nfs).
+
+See how to [configure NFS](../high_availability/nfs.md).
+
+<div align="right">
+ <a type="button" class="btn btn-default" href="#setup-components">
+ Back to setup components <i class="fa fa-angle-double-up" aria-hidden="true"></i>
+ </a>
+</div>
+
+## Troubleshooting
+
+See the [troubleshooting documentation](troubleshooting.md).
+
+<div align="right">
+ <a type="button" class="btn btn-default" href="#setup-components">
+ Back to setup components <i class="fa fa-angle-double-up" aria-hidden="true"></i>
+ </a>
+</div>
diff --git a/doc/administration/reference_architectures/50k_users.md b/doc/administration/reference_architectures/50k_users.md
index dd94f5470b4..2540fe68dac 100644
--- a/doc/administration/reference_architectures/50k_users.md
+++ b/doc/administration/reference_architectures/50k_users.md
@@ -47,7 +47,7 @@ For a full list of reference architectures, see
For medium sized installs (3,000 - 5,000) we suggest one Redis cluster for all
classes and that Redis Sentinel is hosted alongside Consul.
For larger architectures (10,000 users or more) we suggest running a separate
- [Redis Cluster](../high_availability/redis.md#running-multiple-redis-clusters) for the Cache class
+ [Redis Cluster](../redis/replication_and_failover.md#running-multiple-redis-clusters) for the Cache class
and another for the Queues and Shared State classes respectively. We also recommend
that you run the Redis Sentinel clusters separately for each Redis Cluster.
diff --git a/doc/administration/reference_architectures/5k_users.md b/doc/administration/reference_architectures/5k_users.md
index 604572b083e..0b4114bca6e 100644
--- a/doc/administration/reference_architectures/5k_users.md
+++ b/doc/administration/reference_architectures/5k_users.md
@@ -1,73 +1,1767 @@
+---
+reading_time: true
+---
+
# Reference architecture: up to 5,000 users
This page describes GitLab reference architecture for up to 5,000 users.
For a full list of reference architectures, see
[Available reference architectures](index.md#available-reference-architectures).
+NOTE: **Note:**
+The 5,000-user reference architecture documented below is
+designed to help your organization achieve a highly-available GitLab deployment.
+If you do not have the expertise or need to maintain a highly-available
+environment, you can have a simpler and less costly-to-operate environment by
+following the [2,000-user reference architecture](2k_users.md).
+
> - **Supported users (approximate):** 5,000
> - **High Availability:** True
> - **Test RPS rates:** API: 100 RPS, Web: 10 RPS, Git: 10 RPS
-| Service | Nodes | Configuration ([8](#footnotes)) | GCP | AWS | Azure |
-|--------------------------------------------------------------|-------|---------------------------------|---------------|-----------------------|----------------|
-| GitLab Rails ([1](#footnotes)) | 3 | 16 vCPU, 14.4GB Memory | `n1-highcpu-16` | `c5.4xlarge` | F16s v2 |
-| PostgreSQL | 3 | 2 vCPU, 7.5GB Memory | `n1-standard-2` | `m5.large` | D2s v3 |
-| PgBouncer | 3 | 2 vCPU, 1.8GB Memory | `n1-highcpu-2` | `c5.large` | F2s v2 |
-| Gitaly ([2](#footnotes)) ([5](#footnotes)) ([7](#footnotes)) | X | 8 vCPU, 30GB Memory | `n1-standard-8` | `m5.2xlarge` | D8s v3 |
-| Redis ([3](#footnotes)) | 3 | 2 vCPU, 7.5GB Memory | `n1-standard-2` | `m5.large` | D2s v3 |
-| Consul + Sentinel ([3](#footnotes)) | 3 | 2 vCPU, 1.8GB Memory | `n1-highcpu-2` | `c5.large` | F2s v2 |
-| Sidekiq | 4 | 2 vCPU, 7.5GB Memory | `n1-standard-2` | `m5.large` | D2s v3 |
-| Object Storage ([4](#footnotes)) | - | - | - | - | - |
-| NFS Server ([5](#footnotes)) ([7](#footnotes)) | 1 | 4 vCPU, 3.6GB Memory | `n1-highcpu-4` | `c5.xlarge` | F4s v2 |
-| Monitoring node | 1 | 2 vCPU, 1.8GB Memory | `n1-highcpu-2` | `c5.large` | F2s v2 |
-| External load balancing node ([6](#footnotes)) | 1 | 2 vCPU, 1.8GB Memory | `n1-highcpu-2` | `c5.large` | F2s v2 |
-| Internal load balancing node ([6](#footnotes)) | 1 | 2 vCPU, 1.8GB Memory | `n1-highcpu-2` | `c5.large` | F2s v2 |
-
-## Footnotes
-
-1. In our architectures we run each GitLab Rails node using the Puma webserver
- and have its number of workers set to 90% of available CPUs along with four threads. For
- nodes that are running Rails with other components the worker value should be reduced
- accordingly where we've found 50% achieves a good balance but this is dependent
- on workload.
-
-1. Gitaly node requirements are dependent on customer data, specifically the number of
- projects and their sizes. We recommend two nodes as an absolute minimum for HA environments
- and at least four nodes should be used when supporting 50,000 or more users.
- We also recommend that each Gitaly node should store no more than 5TB of data
- and have the number of [`gitaly-ruby` workers](../gitaly/index.md#gitaly-ruby)
- set to 20% of available CPUs. Additional nodes should be considered in conjunction
- with a review of expected data size and spread based on the recommendations above.
-
-1. Recommended Redis setup differs depending on the size of the architecture.
- For smaller architectures (less than 3,000 users) a single instance should suffice.
- For medium sized installs (3,000 - 5,000) we suggest one Redis cluster for all
- classes and that Redis Sentinel is hosted alongside Consul.
- For larger architectures (10,000 users or more) we suggest running a separate
- [Redis Cluster](../high_availability/redis.md#running-multiple-redis-clusters) for the Cache class
- and another for the Queues and Shared State classes respectively. We also recommend
- that you run the Redis Sentinel clusters separately for each Redis Cluster.
-
-1. For data objects such as LFS, Uploads, Artifacts, etc. We recommend an [Object Storage service](../object_storage.md)
- over NFS where possible, due to better performance and availability.
-
-1. NFS can be used as an alternative for both repository data (replacing Gitaly) and
- object storage but this isn't typically recommended for performance reasons. Note however it is required for
- [GitLab Pages](https://gitlab.com/gitlab-org/gitlab-pages/-/issues/196).
-
-1. Our architectures have been tested and validated with [HAProxy](https://www.haproxy.org/)
- as the load balancer. Although other load balancers with similar feature sets
- could also be used, those load balancers have not been validated.
-
-1. We strongly recommend that any Gitaly or NFS nodes be set up with SSD disks over
- HDD with a throughput of at least 8,000 IOPS for read operations and 2,000 IOPS for write
- as these components have heavy I/O. These IOPS values are recommended only as a starter
- as with time they may be adjusted higher or lower depending on the scale of your
- environment's workload. If you're running the environment on a Cloud provider
- you may need to refer to their documentation on how configure IOPS correctly.
-
-1. The architectures were built and tested with the [Intel Xeon E5 v3 (Haswell)](https://cloud.google.com/compute/docs/cpu-platforms)
- CPU platform on GCP. On different hardware you may find that adjustments, either lower
- or higher, are required for your CPU or Node counts accordingly. For more information, a
- [Sysbench](https://github.com/akopytov/sysbench) benchmark of the CPU can be found
- [here](https://gitlab.com/gitlab-org/quality/performance/-/wikis/Reference-Architectures/GCP-CPU-Benchmarks).
+| Service | Nodes | Configuration | GCP | AWS | Azure |
+|--------------------------------------------------------------|-------|---------------------------------|-----------------|-----------------------|----------------|
+| External load balancing node | 1 | 2 vCPU, 1.8GB Memory | `n1-highcpu-2` | `c5.large` | `F2s v2` |
+| Redis | 3 | 2 vCPU, 7.5GB Memory | `n1-standard-2` | `m5.large` | `D2s v3` |
+| Consul + Sentinel | 3 | 2 vCPU, 1.8GB Memory | `n1-highcpu-2` | `c5.large` | `F2s v2` |
+| PostgreSQL | 3 | 2 vCPU, 7.5GB Memory | `n1-standard-2` | `m5.large` | `D2s v3` |
+| PgBouncer | 3 | 2 vCPU, 1.8GB Memory | `n1-highcpu-2` | `c5.large` | `F2s v2` |
+| Internal load balancing node | 1 | 2 vCPU, 1.8GB Memory | `n1-highcpu-2` | `c5.large` | `F2s v2` |
+| Gitaly | 2 minimum | 8 vCPU, 30GB Memory | `n1-standard-8` | `m5.2xlarge` | `D8s v3` |
+| Sidekiq | 4 | 2 vCPU, 7.5GB Memory | `n1-standard-2` | `m5.large` | `D2s v3` |
+| GitLab Rails | 3 | 16 vCPU, 14.4GB Memory | `n1-highcpu-16` | `c5.4xlarge` | `F16s v2` |
+| Monitoring node | 1 | 2 vCPU, 1.8GB Memory | `n1-highcpu-2` | `c5.large` | `F2s v2` |
+| Object Storage | n/a | n/a | n/a | n/a | n/a |
+| NFS Server (optional, not recommended) | 1 | 4 vCPU, 3.6GB Memory | `n1-highcpu-4` | `c5.xlarge` | `F4s v2` |
+
+The architectures were built and tested with the [Intel Xeon E5 v3 (Haswell)](https://cloud.google.com/compute/docs/cpu-platforms)
+CPU platform on GCP. On different hardware you may find that adjustments, either lower
+or higher, are required for your CPU or Node counts accordingly. For more information, a
+[Sysbench](https://github.com/akopytov/sysbench) benchmark of the CPU can be found
+[here](https://gitlab.com/gitlab-org/quality/performance/-/wikis/Reference-Architectures/GCP-CPU-Benchmarks).
+
+For data objects such as LFS, Uploads, Artifacts, etc, an [object storage service](#configure-the-object-storage)
+is recommended over NFS where possible, due to better performance and availability.
+Since this doesn't require a node to be set up, it's marked as not applicable (n/a)
+in the table above.
+
+## Setup components
+
+To set up GitLab and its components to accommodate up to 5,000 users:
+
+1. [Configure the external load balancing node](#configure-the-external-load-balancer)
+ that will handle the load balancing of the two GitLab application services nodes.
+1. [Configure Redis](#configure-redis).
+1. [Configure Consul and Sentinel](#configure-consul-and-sentinel).
+1. [Configure PostgreSQL](#configure-postgresql), the database for GitLab.
+1. [Configure PgBouncer](#configure-pgbouncer).
+1. [Configure the internal load balancing node](#configure-the-internal-load-balancer)
+1. [Configure Gitaly](#configure-gitaly),
+ which provides access to the Git repositories.
+1. [Configure Sidekiq](#configure-sidekiq).
+1. [Configure the main GitLab Rails application](#configure-gitlab-rails)
+ to run Puma/Unicorn, Workhorse, GitLab Shell, and to serve all frontend requests (UI, API, Git
+ over HTTP/SSH).
+1. [Configure Prometheus](#configure-prometheus) to monitor your GitLab environment.
+1. [Configure the Object Storage](#configure-the-object-storage)
+ used for shared data objects.
+1. [Configure NFS (Optional)](#configure-nfs-optional)
+ to have shared disk storage service as an alternative to Gitaly and/or Object Storage (although
+ not recommended). NFS is required for GitLab Pages, you can skip this step if you're not using
+ that feature.
+
+We start with all servers on the same 10.6.0.0/16 private network range, they
+can connect to each other freely on those addresses.
+
+Here is a list and description of each machine and the assigned IP:
+
+- `10.6.0.10`: External Load Balancer
+- `10.6.0.61`: Redis Primary
+- `10.6.0.62`: Redis Replica 1
+- `10.6.0.63`: Redis Replica 2
+- `10.6.0.11`: Consul/Sentinel 1
+- `10.6.0.12`: Consul/Sentinel 2
+- `10.6.0.13`: Consul/Sentinel 3
+- `10.6.0.31`: PostgreSQL primary
+- `10.6.0.32`: PostgreSQL secondary 1
+- `10.6.0.33`: PostgreSQL secondary 2
+- `10.6.0.21`: PgBouncer 1
+- `10.6.0.22`: PgBouncer 2
+- `10.6.0.23`: PgBouncer 3
+- `10.6.0.20`: Internal Load Balancer
+- `10.6.0.51`: Gitaly 1
+- `10.6.0.52`: Gitaly 2
+- `10.6.0.71`: Sidekiq 1
+- `10.6.0.72`: Sidekiq 2
+- `10.6.0.73`: Sidekiq 3
+- `10.6.0.74`: Sidekiq 4
+- `10.6.0.41`: GitLab application 1
+- `10.6.0.42`: GitLab application 2
+- `10.6.0.43`: GitLab application 3
+- `10.6.0.81`: Prometheus
+
+## Configure the external load balancer
+
+NOTE: **Note:**
+This architecture has been tested and validated with [HAProxy](https://www.haproxy.org/)
+as the load balancer. Although other load balancers with similar feature sets
+could also be used, those load balancers have not been validated.
+
+In an active/active GitLab configuration, you will need a load balancer to route
+traffic to the application servers. The specifics on which load balancer to use
+or the exact configuration is beyond the scope of GitLab documentation. We hope
+that if you're managing multi-node systems like GitLab you have a load balancer of
+choice already. Some examples including HAProxy (open-source), F5 Big-IP LTM,
+and Citrix Net Scaler. This documentation will outline what ports and protocols
+you need to use with GitLab.
+
+The next question is how you will handle SSL in your environment.
+There are several different options:
+
+- [The application node terminates SSL](#application-node-terminates-ssl).
+- [The load balancer terminates SSL without backend SSL](#load-balancer-terminates-ssl-without-backend-ssl)
+ and communication is not secure between the load balancer and the application node.
+- [The load balancer terminates SSL with backend SSL](#load-balancer-terminates-ssl-with-backend-ssl)
+ and communication is *secure* between the load balancer and the application node.
+
+### Application node terminates SSL
+
+Configure your load balancer to pass connections on port 443 as `TCP` rather
+than `HTTP(S)` protocol. This will pass the connection to the application node's
+NGINX service untouched. NGINX will have the SSL certificate and listen on port 443.
+
+See the [NGINX HTTPS documentation](https://docs.gitlab.com/omnibus/settings/nginx.html#enable-https)
+for details on managing SSL certificates and configuring NGINX.
+
+### Load balancer terminates SSL without backend SSL
+
+Configure your load balancer to use the `HTTP(S)` protocol rather than `TCP`.
+The load balancer will then be responsible for managing SSL certificates and
+terminating SSL.
+
+Since communication between the load balancer and GitLab will not be secure,
+there is some additional configuration needed. See the
+[NGINX proxied SSL documentation](https://docs.gitlab.com/omnibus/settings/nginx.html#supporting-proxied-ssl)
+for details.
+
+### Load balancer terminates SSL with backend SSL
+
+Configure your load balancer(s) to use the 'HTTP(S)' protocol rather than 'TCP'.
+The load balancer(s) will be responsible for managing SSL certificates that
+end users will see.
+
+Traffic will also be secure between the load balancer(s) and NGINX in this
+scenario. There is no need to add configuration for proxied SSL since the
+connection will be secure all the way. However, configuration will need to be
+added to GitLab to configure SSL certificates. See
+[NGINX HTTPS documentation](https://docs.gitlab.com/omnibus/settings/nginx.html#enable-https)
+for details on managing SSL certificates and configuring NGINX.
+
+### Ports
+
+The basic ports to be used are shown in the table below.
+
+| LB Port | Backend Port | Protocol |
+| ------- | ------------ | ------------------------ |
+| 80 | 80 | HTTP (*1*) |
+| 443 | 443 | TCP or HTTPS (*1*) (*2*) |
+| 22 | 22 | TCP |
+
+- (*1*): [Web terminal](../../ci/environments/index.md#web-terminals) support requires
+ your load balancer to correctly handle WebSocket connections. When using
+ HTTP or HTTPS proxying, this means your load balancer must be configured
+ to pass through the `Connection` and `Upgrade` hop-by-hop headers. See the
+ [web terminal](../integration/terminal.md) integration guide for
+ more details.
+- (*2*): When using HTTPS protocol for port 443, you will need to add an SSL
+ certificate to the load balancers. If you wish to terminate SSL at the
+ GitLab application server instead, use TCP protocol.
+
+If you're using GitLab Pages with custom domain support you will need some
+additional port configurations.
+GitLab Pages requires a separate virtual IP address. Configure DNS to point the
+`pages_external_url` from `/etc/gitlab/gitlab.rb` at the new virtual IP address. See the
+[GitLab Pages documentation](../pages/index.md) for more information.
+
+| LB Port | Backend Port | Protocol |
+| ------- | ------------- | --------- |
+| 80 | Varies (*1*) | HTTP |
+| 443 | Varies (*1*) | TCP (*2*) |
+
+- (*1*): The backend port for GitLab Pages depends on the
+ `gitlab_pages['external_http']` and `gitlab_pages['external_https']`
+ setting. See [GitLab Pages documentation](../pages/index.md) for more details.
+- (*2*): Port 443 for GitLab Pages should always use the TCP protocol. Users can
+ configure custom domains with custom SSL, which would not be possible
+ if SSL was terminated at the load balancer.
+
+#### Alternate SSH Port
+
+Some organizations have policies against opening SSH port 22. In this case,
+it may be helpful to configure an alternate SSH hostname that allows users
+to use SSH on port 443. An alternate SSH hostname will require a new virtual IP address
+compared to the other GitLab HTTP configuration above.
+
+Configure DNS for an alternate SSH hostname such as `altssh.gitlab.example.com`.
+
+| LB Port | Backend Port | Protocol |
+| ------- | ------------ | -------- |
+| 443 | 22 | TCP |
+
+<div align="right">
+ <a type="button" class="btn btn-default" href="#setup-components">
+ Back to setup components <i class="fa fa-angle-double-up" aria-hidden="true"></i>
+ </a>
+</div>
+
+## Configure Redis
+
+Using [Redis](https://redis.io/) in scalable environment is possible using a **Primary** x **Replica**
+topology with a [Redis Sentinel](https://redis.io/topics/sentinel) service to watch and automatically
+start the failover procedure.
+
+Redis requires authentication if used with Sentinel. See
+[Redis Security](https://redis.io/topics/security) documentation for more
+information. We recommend using a combination of a Redis password and tight
+firewall rules to secure your Redis service.
+You are highly encouraged to read the [Redis Sentinel](https://redis.io/topics/sentinel) documentation
+before configuring Redis with GitLab to fully understand the topology and
+architecture.
+
+In this section, you'll be guided through configuring an external Redis instance
+to be used with GitLab. The following IPs will be used as an example:
+
+- `10.6.0.61`: Redis Primary
+- `10.6.0.62`: Redis Replica 1
+- `10.6.0.63`: Redis Replica 2
+
+### Provide your own Redis instance
+
+Managed Redis from cloud providers such as AWS ElastiCache will work. If these
+services support high availability, be sure it is **not** the Redis Cluster type.
+
+Redis version 5.0 or higher is required, as this is what ships with
+Omnibus GitLab packages starting with GitLab 13.0. Older Redis versions
+do not support an optional count argument to SPOP which is now required for
+[Merge Trains](../../ci/merge_request_pipelines/pipelines_for_merged_results/merge_trains/index.md).
+
+Note the Redis node's IP address or hostname, port, and password (if required).
+These will be necessary when configuring the
+[GitLab application servers](#configure-gitlab-rails) later.
+
+### Standalone Redis using Omnibus GitLab
+
+This is the section where we install and set up the new Redis instances.
+
+The requirements for a Redis setup are the following:
+
+1. All Redis nodes must be able to talk to each other and accept incoming
+ connections over Redis (`6379`) and Sentinel (`26379`) ports (unless you
+ change the default ones).
+1. The server that hosts the GitLab application must be able to access the
+ Redis nodes.
+1. Protect the nodes from access from external networks
+ ([Internet](https://gitlab.com/gitlab-org/gitlab-foss/uploads/c4cc8cd353604bd80315f9384035ff9e/The_Internet_IT_Crowd.png)),
+ using a firewall.
+
+NOTE: **Note:**
+Redis nodes (both primary and replica) will need the same password defined in
+`redis['password']`. At any time during a failover the Sentinels can
+reconfigure a node and change its status from primary to replica and vice versa.
+
+#### Configuring the primary Redis instance
+
+1. SSH into the **Primary** Redis server.
+1. [Download/install](https://about.gitlab.com/install/) the Omnibus GitLab
+ package you want using **steps 1 and 2** from the GitLab downloads page.
+ - Make sure you select the correct Omnibus package, with the same version
+ and type (Community, Enterprise editions) of your current install.
+ - Do not complete any other steps on the download page.
+
+1. Edit `/etc/gitlab/gitlab.rb` and add the contents:
+
+ ```ruby
+ # Specify server role as 'redis_master_role'
+ roles ['redis_master_role']
+
+ # IP address pointing to a local IP that the other machines can reach to.
+ # You can also set bind to '0.0.0.0' which listen in all interfaces.
+ # If you really need to bind to an external accessible IP, make
+ # sure you add extra firewall rules to prevent unauthorized access.
+ redis['bind'] = '10.6.0.61'
+
+ # Define a port so Redis can listen for TCP requests which will allow other
+ # machines to connect to it.
+ redis['port'] = 6379
+
+ # Set up password authentication for Redis (use the same password in all nodes).
+ redis['password'] = 'redis-password-goes-here'
+
+ ## Enable service discovery for Prometheus
+ consul['enable'] = true
+ consul['monitoring_service_discovery'] = true
+
+ ## The IPs of the Consul server nodes
+ ## You can also use FQDNs and intermix them with IPs
+ consul['configuration'] = {
+ retry_join: %w(10.6.0.11 10.6.0.12 10.6.0.13),
+ }
+
+ # Set the network addresses that the exporters will listen on
+ node_exporter['listen_address'] = '0.0.0.0:9100'
+ redis_exporter['listen_address'] = '0.0.0.0:9121'
+ redis_exporter['flags'] = {
+ 'redis.addr' => 'redis://10.6.0.61:6379',
+ 'redis.password' => 'redis-password-goes-here',
+ }
+
+ # Disable auto migrations
+ gitlab_rails['auto_migrate'] = false
+ ```
+
+1. [Reconfigure Omnibus GitLab](../restart_gitlab.md#omnibus-gitlab-reconfigure) for the changes to take effect.
+
+NOTE: **Note:**
+You can specify multiple roles like sentinel and Redis as:
+`roles ['redis_sentinel_role', 'redis_master_role']`.
+Read more about [roles](https://docs.gitlab.com/omnibus/roles/).
+
+You can list the current Redis Primary, Replica status via:
+
+```shell
+/opt/gitlab/embedded/bin/redis-cli -h <host> -a 'redis-password-goes-here' info replication
+```
+
+Show running GitLab services via:
+
+```shell
+gitlab-ctl status
+```
+
+The output should be similar to the following:
+
+```plaintext
+run: consul: (pid 30043) 76863s; run: log: (pid 29691) 76892s
+run: logrotate: (pid 31152) 3070s; run: log: (pid 29595) 76908s
+run: node-exporter: (pid 30064) 76862s; run: log: (pid 29624) 76904s
+run: redis: (pid 30070) 76861s; run: log: (pid 29573) 76914s
+run: redis-exporter: (pid 30075) 76861s; run: log: (pid 29674) 76896s
+```
+
+#### Configuring the replica Redis instances
+
+1. SSH into the **replica** Redis server.
+1. [Download/install](https://about.gitlab.com/install/) the Omnibus GitLab
+ package you want using **steps 1 and 2** from the GitLab downloads page.
+ - Make sure you select the correct Omnibus package, with the same version
+ and type (Community, Enterprise editions) of your current install.
+ - Do not complete any other steps on the download page.
+
+1. Edit `/etc/gitlab/gitlab.rb` and add the contents:
+
+ ```ruby
+ # Specify server role as 'redis_replica_role'
+ roles ['redis_replica_role']
+
+ # IP address pointing to a local IP that the other machines can reach to.
+ # You can also set bind to '0.0.0.0' which listen in all interfaces.
+ # If you really need to bind to an external accessible IP, make
+ # sure you add extra firewall rules to prevent unauthorized access.
+ redis['bind'] = '10.6.0.62'
+
+ # Define a port so Redis can listen for TCP requests which will allow other
+ # machines to connect to it.
+ redis['port'] = 6379
+
+ # The same password for Redis authentication you set up for the primary node.
+ redis['password'] = 'redis-password-goes-here'
+
+ # The IP of the primary Redis node.
+ redis['master_ip'] = '10.6.0.61'
+
+ # Port of primary Redis server, uncomment to change to non default. Defaults
+ # to `6379`.
+ #redis['master_port'] = 6379
+
+ ## Enable service discovery for Prometheus
+ consul['enable'] = true
+ consul['monitoring_service_discovery'] = true
+
+ ## The IPs of the Consul server nodes
+ ## You can also use FQDNs and intermix them with IPs
+ consul['configuration'] = {
+ retry_join: %w(10.6.0.11 10.6.0.12 10.6.0.13),
+ }
+
+ # Set the network addresses that the exporters will listen on
+ node_exporter['listen_address'] = '0.0.0.0:9100'
+ redis_exporter['listen_address'] = '0.0.0.0:9121'
+ redis_exporter['flags'] = {
+ 'redis.addr' => 'redis://10.6.0.62:6379',
+ 'redis.password' => 'redis-password-goes-here',
+ }
+
+ # Disable auto migrations
+ gitlab_rails['auto_migrate'] = false
+ ```
+
+1. [Reconfigure Omnibus GitLab](../restart_gitlab.md#omnibus-gitlab-reconfigure) for the changes to take effect.
+1. Go through the steps again for all the other replica nodes, and
+ make sure to set up the IPs correctly.
+
+NOTE: **Note:**
+You can specify multiple roles like sentinel and Redis as:
+`roles ['redis_sentinel_role', 'redis_master_role']`.
+Read more about [roles](https://docs.gitlab.com/omnibus/roles/).
+
+These values don't have to be changed again in `/etc/gitlab/gitlab.rb` after
+a failover, as the nodes will be managed by the [Sentinels](#configure-consul-and-sentinel), and even after a
+`gitlab-ctl reconfigure`, they will get their configuration restored by
+the same Sentinels.
+
+Advanced [configuration options](https://docs.gitlab.com/omnibus/settings/redis.html)
+are supported and can be added if needed.
+
+<div align="right">
+ <a type="button" class="btn btn-default" href="#setup-components">
+ Back to setup components <i class="fa fa-angle-double-up" aria-hidden="true"></i>
+ </a>
+</div>
+
+## Configure Consul and Sentinel
+
+NOTE: **Note:**
+If you are using an external Redis Sentinel instance, be sure
+to exclude the `requirepass` parameter from the Sentinel
+configuration. This parameter will cause clients to report `NOAUTH
+Authentication required.`. [Redis Sentinel 3.2.x does not support
+password authentication](https://github.com/antirez/redis/issues/3279).
+
+Now that the Redis servers are all set up, let's configure the Sentinel
+servers. The following IPs will be used as an example:
+
+- `10.6.0.11`: Consul/Sentinel 1
+- `10.6.0.12`: Consul/Sentinel 2
+- `10.6.0.13`: Consul/Sentinel 3
+
+To configure the Sentinel:
+
+1. SSH into the server that will host Consul/Sentinel.
+1. [Download/install](https://about.gitlab.com/install/) the
+ Omnibus GitLab Enterprise Edition package using **steps 1 and 2** from the
+ GitLab downloads page.
+ - Make sure you select the correct Omnibus package, with the same version
+ the GitLab application is running.
+ - Do not complete any other steps on the download page.
+
+1. Edit `/etc/gitlab/gitlab.rb` and add the contents:
+
+ ```ruby
+ roles ['redis_sentinel_role', 'consul_role']
+
+ # Must be the same in every sentinel node
+ redis['master_name'] = 'gitlab-redis'
+
+ # The same password for Redis authentication you set up for the primary node.
+ redis['master_password'] = 'redis-password-goes-here'
+
+ # The IP of the primary Redis node.
+ redis['master_ip'] = '10.6.0.61'
+
+ # Define a port so Redis can listen for TCP requests which will allow other
+ # machines to connect to it.
+ redis['port'] = 6379
+
+ # Port of primary Redis server, uncomment to change to non default. Defaults
+ # to `6379`.
+ #redis['master_port'] = 6379
+
+ ## Configure Sentinel
+ sentinel['bind'] = '10.6.0.11'
+
+ # Port that Sentinel listens on, uncomment to change to non default. Defaults
+ # to `26379`.
+ # sentinel['port'] = 26379
+
+ ## Quorum must reflect the amount of voting sentinels it take to start a failover.
+ ## Value must NOT be greater then the amount of sentinels.
+ ##
+ ## The quorum can be used to tune Sentinel in two ways:
+ ## 1. If a the quorum is set to a value smaller than the majority of Sentinels
+ ## we deploy, we are basically making Sentinel more sensible to primary failures,
+ ## triggering a failover as soon as even just a minority of Sentinels is no longer
+ ## able to talk with the primary.
+ ## 1. If a quorum is set to a value greater than the majority of Sentinels, we are
+ ## making Sentinel able to failover only when there are a very large number (larger
+ ## than majority) of well connected Sentinels which agree about the primary being down.s
+ sentinel['quorum'] = 2
+
+ ## Consider unresponsive server down after x amount of ms.
+ # sentinel['down_after_milliseconds'] = 10000
+
+ ## Specifies the failover timeout in milliseconds. It is used in many ways:
+ ##
+ ## - The time needed to re-start a failover after a previous failover was
+ ## already tried against the same primary by a given Sentinel, is two
+ ## times the failover timeout.
+ ##
+ ## - The time needed for a replica replicating to a wrong primary according
+ ## to a Sentinel current configuration, to be forced to replicate
+ ## with the right primary, is exactly the failover timeout (counting since
+ ## the moment a Sentinel detected the misconfiguration).
+ ##
+ ## - The time needed to cancel a failover that is already in progress but
+ ## did not produced any configuration change (REPLICAOF NO ONE yet not
+ ## acknowledged by the promoted replica).
+ ##
+ ## - The maximum time a failover in progress waits for all the replica to be
+ ## reconfigured as replicas of the new primary. However even after this time
+ ## the replicas will be reconfigured by the Sentinels anyway, but not with
+ ## the exact parallel-syncs progression as specified.
+ # sentinel['failover_timeout'] = 60000
+
+ ## Enable service discovery for Prometheus
+ consul['enable'] = true
+ consul['monitoring_service_discovery'] = true
+
+ ## The IPs of the Consul server nodes
+ ## You can also use FQDNs and intermix them with IPs
+ consul['configuration'] = {
+ server: true,
+ retry_join: %w(10.6.0.11 10.6.0.12 10.6.0.13),
+ }
+
+ # Set the network addresses that the exporters will listen on
+ node_exporter['listen_address'] = '0.0.0.0:9100'
+ redis_exporter['listen_address'] = '0.0.0.0:9121'
+
+ # Disable auto migrations
+ gitlab_rails['auto_migrate'] = false
+ ```
+
+1. [Reconfigure Omnibus GitLab](../restart_gitlab.md#omnibus-gitlab-reconfigure) for the changes to take effect.
+1. Go through the steps again for all the other Consul/Sentinel nodes, and
+ make sure you set up the correct IPs.
+
+NOTE: **Note:**
+A Consul leader will be elected when the provisioning of the third Consul server is completed.
+Viewing the Consul logs `sudo gitlab-ctl tail consul` will display
+`...[INFO] consul: New leader elected: ...`
+
+You can list the current Consul members (server, client):
+
+```shell
+sudo /opt/gitlab/embedded/bin/consul members
+```
+
+You can verify the GitLab services are running:
+
+```shell
+sudo gitlab-ctl status
+```
+
+The output should be similar to the following:
+
+```plaintext
+run: consul: (pid 30074) 76834s; run: log: (pid 29740) 76844s
+run: logrotate: (pid 30925) 3041s; run: log: (pid 29649) 76861s
+run: node-exporter: (pid 30093) 76833s; run: log: (pid 29663) 76855s
+run: sentinel: (pid 30098) 76832s; run: log: (pid 29704) 76850s
+```
+
+<div align="right">
+ <a type="button" class="btn btn-default" href="#setup-components">
+ Back to setup components <i class="fa fa-angle-double-up" aria-hidden="true"></i>
+ </a>
+</div>
+
+## Configure PostgreSQL
+
+In this section, you'll be guided through configuring an external PostgreSQL database
+to be used with GitLab.
+
+### Provide your own PostgreSQL instance
+
+If you're hosting GitLab on a cloud provider, you can optionally use a
+managed service for PostgreSQL. For example, AWS offers a managed Relational
+Database Service (RDS) that runs PostgreSQL.
+
+If you use a cloud-managed service, or provide your own PostgreSQL:
+
+1. Set up PostgreSQL according to the
+ [database requirements document](../../install/requirements.md#database).
+1. Set up a `gitlab` username with a password of your choice. The `gitlab` user
+ needs privileges to create the `gitlabhq_production` database.
+1. Configure the GitLab application servers with the appropriate details.
+ This step is covered in [Configuring the GitLab Rails application](#configure-gitlab-rails).
+
+### Standalone PostgreSQL using Omnibus GitLab
+
+The following IPs will be used as an example:
+
+- `10.6.0.31`: PostgreSQL primary
+- `10.6.0.32`: PostgreSQL secondary 1
+- `10.6.0.33`: PostgreSQL secondary 2
+
+First, make sure to [install](https://about.gitlab.com/install/)
+the Linux GitLab package **on each node**. Following the steps,
+install the necessary dependencies from step 1, and add the
+GitLab package repository from step 2. When installing GitLab
+in the second step, do not supply the `EXTERNAL_URL` value.
+
+#### PostgreSQL primary node
+
+1. SSH into the PostgreSQL primary node.
+1. Generate a password hash for the PostgreSQL username/password pair. This assumes you will use the default
+ username of `gitlab` (recommended). The command will request a password
+ and confirmation. Use the value that is output by this command in the next
+ step as the value of `<postgresql_password_hash>`:
+
+ ```shell
+ sudo gitlab-ctl pg-password-md5 gitlab
+ ```
+
+1. Generate a password hash for the PgBouncer username/password pair. This assumes you will use the default
+ username of `pgbouncer` (recommended). The command will request a password
+ and confirmation. Use the value that is output by this command in the next
+ step as the value of `<pgbouncer_password_hash>`:
+
+ ```shell
+ sudo gitlab-ctl pg-password-md5 pgbouncer
+ ```
+
+1. Generate a password hash for the Consul database username/password pair. This assumes you will use the default
+ username of `gitlab-consul` (recommended). The command will request a password
+ and confirmation. Use the value that is output by this command in the next
+ step as the value of `<consul_password_hash>`:
+
+ ```shell
+ sudo gitlab-ctl pg-password-md5 gitlab-consul
+ ```
+
+1. On the primary database node, edit `/etc/gitlab/gitlab.rb` replacing values noted in the `# START user configuration` section:
+
+ ```ruby
+ # Disable all components except PostgreSQL and Repmgr and Consul
+ roles ['postgres_role']
+
+ # PostgreSQL configuration
+ postgresql['listen_address'] = '0.0.0.0'
+ postgresql['hot_standby'] = 'on'
+ postgresql['wal_level'] = 'replica'
+ postgresql['shared_preload_libraries'] = 'repmgr_funcs'
+
+ # Disable automatic database migrations
+ gitlab_rails['auto_migrate'] = false
+
+ # Configure the Consul agent
+ consul['services'] = %w(postgresql)
+
+ # START user configuration
+ # Please set the real values as explained in Required Information section
+ #
+ # Replace PGBOUNCER_PASSWORD_HASH with a generated md5 value
+ postgresql['pgbouncer_user_password'] = '<pgbouncer_password_hash>'
+ # Replace POSTGRESQL_PASSWORD_HASH with a generated md5 value
+ postgresql['sql_user_password'] = '<postgresql_password_hash>'
+ # Set `max_wal_senders` to one more than the number of database nodes in the cluster.
+ # This is used to prevent replication from using up all of the
+ # available database connections.
+ postgresql['max_wal_senders'] = 4
+ postgresql['max_replication_slots'] = 4
+
+ # Replace XXX.XXX.XXX.XXX/YY with Network Address
+ postgresql['trust_auth_cidr_addresses'] = %w(127.0.0.1/32 10.6.0.0/24)
+ repmgr['trust_auth_cidr_addresses'] = %w(127.0.0.1/32 10.6.0.0/24)
+
+ ## Enable service discovery for Prometheus
+ consul['enable'] = true
+ consul['monitoring_service_discovery'] = true
+
+ # Set the network addresses that the exporters will listen on for monitoring
+ node_exporter['listen_address'] = '0.0.0.0:9100'
+ postgres_exporter['listen_address'] = '0.0.0.0:9187'
+ postgres_exporter['dbname'] = 'gitlabhq_production'
+ postgres_exporter['password'] = '<postgresql_password_hash>'
+
+ ## The IPs of the Consul server nodes
+ ## You can also use FQDNs and intermix them with IPs
+ consul['configuration'] = {
+ retry_join: %w(10.6.0.11 10.6.0.12 10.6.0.13),
+ }
+ #
+ # END user configuration
+ ```
+
+1. [Reconfigure GitLab](../restart_gitlab.md#omnibus-gitlab-reconfigure) for the changes to take effect.
+1. You can list the current PostgreSQL primary, secondary nodes status via:
+
+ ```shell
+ sudo /opt/gitlab/bin/gitlab-ctl repmgr cluster show
+ ```
+
+1. Verify the GitLab services are running:
+
+ ```shell
+ sudo gitlab-ctl status
+ ```
+
+ The output should be similar to the following:
+
+ ```plaintext
+ run: consul: (pid 30593) 77133s; run: log: (pid 29912) 77156s
+ run: logrotate: (pid 23449) 3341s; run: log: (pid 29794) 77175s
+ run: node-exporter: (pid 30613) 77133s; run: log: (pid 29824) 77170s
+ run: postgres-exporter: (pid 30620) 77132s; run: log: (pid 29894) 77163s
+ run: postgresql: (pid 30630) 77132s; run: log: (pid 29618) 77181s
+ run: repmgrd: (pid 30639) 77132s; run: log: (pid 29985) 77150s
+ ```
+
+<div align="right">
+ <a type="button" class="btn btn-default" href="#setup-components">
+ Back to setup components <i class="fa fa-angle-double-up" aria-hidden="true"></i>
+ </a>
+</div>
+
+#### PostgreSQL secondary nodes
+
+1. On both the secondary nodes, add the same configuration specified above for the primary node
+ with an additional setting that will inform `gitlab-ctl` that they are standby nodes initially
+ and there's no need to attempt to register them as a primary node:
+
+ ```ruby
+ # Disable all components except PostgreSQL and Repmgr and Consul
+ roles ['postgres_role']
+
+ # PostgreSQL configuration
+ postgresql['listen_address'] = '0.0.0.0'
+ postgresql['hot_standby'] = 'on'
+ postgresql['wal_level'] = 'replica'
+ postgresql['shared_preload_libraries'] = 'repmgr_funcs'
+
+ # Disable automatic database migrations
+ gitlab_rails['auto_migrate'] = false
+
+ # Configure the Consul agent
+ consul['services'] = %w(postgresql)
+
+ # Specify if a node should attempt to be primary on initialization.
+ repmgr['master_on_initialization'] = false
+
+ # START user configuration
+ # Please set the real values as explained in Required Information section
+ #
+ # Replace PGBOUNCER_PASSWORD_HASH with a generated md5 value
+ postgresql['pgbouncer_user_password'] = '<pgbouncer_password_hash>'
+ # Replace POSTGRESQL_PASSWORD_HASH with a generated md5 value
+ postgresql['sql_user_password'] = '<postgresql_password_hash>'
+ # Set `max_wal_senders` to one more than the number of database nodes in the cluster.
+ # This is used to prevent replication from using up all of the
+ # available database connections.
+ postgresql['max_wal_senders'] = 4
+ postgresql['max_replication_slots'] = 4
+
+ # Replace XXX.XXX.XXX.XXX/YY with Network Address
+ postgresql['trust_auth_cidr_addresses'] = %w(127.0.0.1/32 10.6.0.0/24)
+ repmgr['trust_auth_cidr_addresses'] = %w(127.0.0.1/32 10.6.0.0/24)
+
+ ## Enable service discovery for Prometheus
+ consul['enable'] = true
+ consul['monitoring_service_discovery'] = true
+
+ # Set the network addresses that the exporters will listen on for monitoring
+ node_exporter['listen_address'] = '0.0.0.0:9100'
+ postgres_exporter['listen_address'] = '0.0.0.0:9187'
+ postgres_exporter['dbname'] = 'gitlabhq_production'
+ postgres_exporter['password'] = '<postgresql_password_hash>'
+
+ ## The IPs of the Consul server nodes
+ ## You can also use FQDNs and intermix them with IPs
+ consul['configuration'] = {
+ retry_join: %w(10.6.0.11 10.6.0.12 10.6.0.13),
+ }
+ # END user configuration
+ ```
+
+1. [Reconfigure GitLab](../restart_gitlab.md#omnibus-gitlab-reconfigure) for the changes to take effect.
+
+Advanced [configuration options](https://docs.gitlab.com/omnibus/settings/database.html)
+are supported and can be added if needed.
+
+<div align="right">
+ <a type="button" class="btn btn-default" href="#setup-components">
+ Back to setup components <i class="fa fa-angle-double-up" aria-hidden="true"></i>
+ </a>
+</div>
+
+#### PostgreSQL post-configuration
+
+SSH into the **primary node**:
+
+1. Open a database prompt:
+
+ ```shell
+ gitlab-psql -d gitlabhq_production
+ ```
+
+1. Enable the `pg_trgm` extension:
+
+ ```shell
+ CREATE EXTENSION pg_trgm;
+ ```
+
+1. Exit the database prompt by typing `\q` and Enter.
+
+1. Verify the cluster is initialized with one node:
+
+ ```shell
+ gitlab-ctl repmgr cluster show
+ ```
+
+ The output should be similar to the following:
+
+ ```plaintext
+ Role | Name | Upstream | Connection String
+ ----------+----------|----------|----------------------------------------
+ * master | HOSTNAME | | host=HOSTNAME user=gitlab_repmgr dbname=gitlab_repmgr
+ ```
+
+1. Note down the hostname or IP address in the connection string: `host=HOSTNAME`. We will
+ refer to the hostname in the next section as `<primary_node_name>`. If the value
+ is not an IP address, it will need to be a resolvable name (via DNS or
+ `/etc/hosts`)
+
+SSH into the **secondary node**:
+
+1. Set up the repmgr standby:
+
+ ```shell
+ gitlab-ctl repmgr standby setup <primary_node_name>
+ ```
+
+ Do note that this will remove the existing data on the node. The command
+ has a wait time.
+
+ The output should be similar to the following:
+
+ ```console
+ Doing this will delete the entire contents of /var/opt/gitlab/postgresql/data
+ If this is not what you want, hit Ctrl-C now to exit
+ To skip waiting, rerun with the -w option
+ Sleeping for 30 seconds
+ Stopping the database
+ Removing the data
+ Cloning the data
+ Starting the database
+ Registering the node with the cluster
+ ok: run: repmgrd: (pid 19068) 0s
+ ```
+
+Before moving on, make sure the databases are configured correctly. Run the
+following command on the **primary** node to verify that replication is working
+properly and the secondary nodes appear in the cluster:
+
+```shell
+gitlab-ctl repmgr cluster show
+```
+
+The output should be similar to the following:
+
+```plaintext
+Role | Name | Upstream | Connection String
+----------+---------|-----------|------------------------------------------------
+* master | MASTER | | host=<primary_node_name> user=gitlab_repmgr dbname=gitlab_repmgr
+ standby | STANDBY | MASTER | host=<secondary_node_name> user=gitlab_repmgr dbname=gitlab_repmgr
+ standby | STANDBY | MASTER | host=<secondary_node_name> user=gitlab_repmgr dbname=gitlab_repmgr
+```
+
+If the 'Role' column for any node says "FAILED", check the
+[Troubleshooting section](troubleshooting.md) before proceeding.
+
+Also, check that the `repmgr-check-master` command works successfully on each node:
+
+```shell
+su - gitlab-consul
+gitlab-ctl repmgr-check-master || echo 'This node is a standby repmgr node'
+```
+
+This command relies on exit codes to tell Consul whether a particular node is a master
+or secondary. The most important thing here is that this command does not produce errors.
+If there are errors it's most likely due to incorrect `gitlab-consul` database user permissions.
+Check the [Troubleshooting section](troubleshooting.md) before proceeding.
+
+<div align="right">
+ <a type="button" class="btn btn-default" href="#setup-components">
+ Back to setup components <i class="fa fa-angle-double-up" aria-hidden="true"></i>
+ </a>
+</div>
+
+## Configure PgBouncer
+
+Now that the PostgreSQL servers are all set up, let's configure PgBouncer.
+The following IPs will be used as an example:
+
+- `10.6.0.21`: PgBouncer 1
+- `10.6.0.22`: PgBouncer 2
+- `10.6.0.23`: PgBouncer 3
+
+1. On each PgBouncer node, edit `/etc/gitlab/gitlab.rb`, and replace
+ `<consul_password_hash>` and `<pgbouncer_password_hash>` with the
+ password hashes you [set up previously](#postgresql-primary-node):
+
+ ```ruby
+ # Disable all components except Pgbouncer and Consul agent
+ roles ['pgbouncer_role']
+
+ # Configure PgBouncer
+ pgbouncer['admin_users'] = %w(pgbouncer gitlab-consul)
+
+ pgbouncer['users'] = {
+ 'gitlab-consul': {
+ password: '<consul_password_hash>'
+ },
+ 'pgbouncer': {
+ password: '<pgbouncer_password_hash>'
+ }
+ }
+
+ # Configure Consul agent
+ consul['watchers'] = %w(postgresql)
+ consul['enable'] = true
+ consul['configuration'] = {
+ retry_join: %w(10.6.0.11 10.6.0.12 10.6.0.13)
+ }
+
+ # Enable service discovery for Prometheus
+ consul['monitoring_service_discovery'] = true
+
+ # Set the network addresses that the exporters will listen on
+ node_exporter['listen_address'] = '0.0.0.0:9100'
+ pgbouncer_exporter['listen_address'] = '0.0.0.0:9188'
+ ```
+
+1. [Reconfigure Omnibus GitLab](../restart_gitlab.md#omnibus-gitlab-reconfigure) for the changes to take effect.
+
+1. Create a `.pgpass` file so Consul is able to
+ reload PgBouncer. Enter the PgBouncer password twice when asked:
+
+ ```shell
+ gitlab-ctl write-pgpass --host 127.0.0.1 --database pgbouncer --user pgbouncer --hostuser gitlab-consul
+ ```
+
+1. Ensure each node is talking to the current master:
+
+ ```shell
+ gitlab-ctl pgb-console # You will be prompted for PGBOUNCER_PASSWORD
+ ```
+
+ If there is an error `psql: ERROR: Auth failed` after typing in the
+ password, ensure you previously generated the MD5 password hashes with the correct
+ format. The correct format is to concatenate the password and the username:
+ `PASSWORDUSERNAME`. For example, `Sup3rS3cr3tpgbouncer` would be the text
+ needed to generate an MD5 password hash for the `pgbouncer` user.
+
+1. Once the console prompt is available, run the following queries:
+
+ ```shell
+ show databases ; show clients ;
+ ```
+
+ The output should be similar to the following:
+
+ ```plaintext
+ name | host | port | database | force_user | pool_size | reserve_pool | pool_mode | max_connections | current_connections
+ ---------------------+-------------+------+---------------------+------------+-----------+--------------+-----------+-----------------+---------------------
+ gitlabhq_production | MASTER_HOST | 5432 | gitlabhq_production | | 20 | 0 | | 0 | 0
+ pgbouncer | | 6432 | pgbouncer | pgbouncer | 2 | 0 | statement | 0 | 0
+ (2 rows)
+
+ type | user | database | state | addr | port | local_addr | local_port | connect_time | request_time | ptr | link | remote_pid | tls
+ ------+-----------+---------------------+---------+----------------+-------+------------+------------+---------------------+---------------------+-----------+------+------------+-----
+ C | pgbouncer | pgbouncer | active | 127.0.0.1 | 56846 | 127.0.0.1 | 6432 | 2017-08-21 18:09:59 | 2017-08-21 18:10:48 | 0x22b3880 | | 0 |
+ (2 rows)
+ ```
+
+1. Verify the GitLab services are running:
+
+ ```shell
+ sudo gitlab-ctl status
+ ```
+
+ The output should be similar to the following:
+
+ ```plaintext
+ run: consul: (pid 31530) 77150s; run: log: (pid 31106) 77182s
+ run: logrotate: (pid 32613) 3357s; run: log: (pid 30107) 77500s
+ run: node-exporter: (pid 31550) 77149s; run: log: (pid 30138) 77493s
+ run: pgbouncer: (pid 32033) 75593s; run: log: (pid 31117) 77175s
+ run: pgbouncer-exporter: (pid 31558) 77148s; run: log: (pid 31498) 77156s
+ ```
+
+<div align="right">
+ <a type="button" class="btn btn-default" href="#setup-components">
+ Back to setup components <i class="fa fa-angle-double-up" aria-hidden="true"></i>
+ </a>
+</div>
+
+### Configure the internal load balancer
+
+If you're running more than one PgBouncer node as recommended, then at this time you'll need to set
+up a TCP internal load balancer to serve each correctly.
+
+The following IP will be used as an example:
+
+- `10.6.0.20`: Internal Load Balancer
+
+Here's how you could do it with [HAProxy](https://www.haproxy.org/):
+
+```plaintext
+global
+ log /dev/log local0
+ log localhost local1 notice
+ log stdout format raw local0
+
+defaults
+ log global
+ default-server inter 10s fall 3 rise 2
+ balance leastconn
+
+frontend internal-pgbouncer-tcp-in
+ bind *:6432
+ mode tcp
+ option tcplog
+
+ default_backend pgbouncer
+
+backend pgbouncer
+ mode tcp
+ option tcp-check
+
+ server pgbouncer1 10.6.0.21:6432 check
+ server pgbouncer2 10.6.0.22:6432 check
+ server pgbouncer3 10.6.0.23:6432 check
+```
+
+Refer to your preferred Load Balancer's documentation for further guidance.
+
+<div align="right">
+ <a type="button" class="btn btn-default" href="#setup-components">
+ Back to setup components <i class="fa fa-angle-double-up" aria-hidden="true"></i>
+ </a>
+</div>
+
+## Configure Gitaly
+
+Deploying Gitaly in its own server can benefit GitLab installations that are
+larger than a single machine.
+
+The Gitaly node requirements are dependent on customer data, specifically the number of
+projects and their repository sizes. Two nodes are recommended as an absolute minimum.
+Each Gitaly node should store no more than 5TB of data and have the number of
+[`gitaly-ruby` workers](../gitaly/index.md#gitaly-ruby) set to 20% of available CPUs.
+Additional nodes should be considered in conjunction with a review of expected
+data size and spread based on the recommendations above.
+
+It is also strongly recommended that all Gitaly nodes be set up with SSD disks with
+a throughput of at least 8,000 IOPS for read operations and 2,000 IOPS for write,
+as Gitaly has heavy I/O. These IOPS values are recommended only as a starter as with
+time they may be adjusted higher or lower depending on the scale of your environment's workload.
+If you're running the environment on a Cloud provider, you may need to refer to
+their documentation on how to configure IOPS correctly.
+
+Some things to note:
+
+- The GitLab Rails application shards repositories into [repository storages](../repository_storage_paths.md).
+- A Gitaly server can host one or more storages.
+- A GitLab server can use one or more Gitaly servers.
+- Gitaly addresses must be specified in such a way that they resolve
+ correctly for ALL Gitaly clients.
+- Gitaly servers must not be exposed to the public internet, as Gitaly's network
+ traffic is unencrypted by default. The use of a firewall is highly recommended
+ to restrict access to the Gitaly server. Another option is to
+ [use TLS](#gitaly-tls-support).
+
+TIP: **Tip:**
+For more information about Gitaly's history and network architecture see the
+[standalone Gitaly documentation](../gitaly/index.md).
+
+Note: **Note:** The token referred to throughout the Gitaly documentation is
+just an arbitrary password selected by the administrator. It is unrelated to
+tokens created for the GitLab API or other similar web API tokens.
+
+Below we describe how to configure two Gitaly servers, with IPs and
+domain names:
+
+- `10.6.0.51`: Gitaly 1 (`gitaly1.internal`)
+- `10.6.0.52`: Gitaly 2 (`gitaly2.internal`)
+
+The secret token is assumed to be `gitalysecret` and that
+your GitLab installation has three repository storages:
+
+- `default` on Gitaly 1
+- `storage1` on Gitaly 1
+- `storage2` on Gitaly 2
+
+On each node:
+
+1. [Download/Install](https://about.gitlab.com/install/) the Omnibus GitLab
+ package you want using **steps 1 and 2** from the GitLab downloads page but
+ **without** providing the `EXTERNAL_URL` value.
+1. Edit `/etc/gitlab/gitlab.rb` to configure storage paths, enable
+ the network listener and configure the token:
+
+ <!--
+ updates to following example must also be made at
+ https://gitlab.com/gitlab-org/charts/gitlab/blob/master/doc/advanced/external-gitaly/external-omnibus-gitaly.md#configure-omnibus-gitlab
+ -->
+
+ ```ruby
+ # /etc/gitlab/gitlab.rb
+
+ # Gitaly and GitLab use two shared secrets for authentication, one to authenticate gRPC requests
+ # to Gitaly, and a second for authentication callbacks from GitLab-Shell to the GitLab internal API.
+ # The following two values must be the same as their respective values
+ # of the GitLab Rails application setup
+ gitaly['auth_token'] = 'gitlaysecret'
+ gitlab_shell['secret_token'] = 'shellsecret'
+
+ # Avoid running unnecessary services on the Gitaly server
+ postgresql['enable'] = false
+ redis['enable'] = false
+ nginx['enable'] = false
+ puma['enable'] = false
+ unicorn['enable'] = false
+ sidekiq['enable'] = false
+ gitlab_workhorse['enable'] = false
+ grafana['enable'] = false
+ gitlab_exporter['enable'] = false
+
+ # If you run a seperate monitoring node you can disable these services
+ alertmanager['enable'] = false
+ prometheus['enable'] = false
+
+ # Prevent database connections during 'gitlab-ctl reconfigure'
+ gitlab_rails['rake_cache_clear'] = false
+ gitlab_rails['auto_migrate'] = false
+
+ # Configure the gitlab-shell API callback URL. Without this, `git push` will
+ # fail. This can be your 'front door' GitLab URL or an internal load
+ # balancer.
+ # Don't forget to copy `/etc/gitlab/gitlab-secrets.json` from web server to Gitaly server.
+ gitlab_rails['internal_api_url'] = 'https://gitlab.example.com'
+
+ # Make Gitaly accept connections on all network interfaces. You must use
+ # firewalls to restrict access to this address/port.
+ # Comment out following line if you only want to support TLS connections
+ gitaly['listen_addr'] = "0.0.0.0:8075"
+
+ ## Enable service discovery for Prometheus
+ consul['enable'] = true
+ consul['monitoring_service_discovery'] = true
+
+ # Set the network addresses that the exporters will listen on for monitoring
+ gitaly['prometheus_listen_addr'] = "0.0.0.0:9236"
+ node_exporter['listen_address'] = '0.0.0.0:9100'
+ gitlab_rails['prometheus_address'] = '10.6.0.81:9090'
+
+ ## The IPs of the Consul server nodes
+ ## You can also use FQDNs and intermix them with IPs
+ consul['configuration'] = {
+ retry_join: %w(10.6.0.11 10.6.0.12 10.6.0.13),
+ }
+ ```
+
+1. Append the following to `/etc/gitlab/gitlab.rb` for each respective server:
+ 1. On `gitaly1.internal`:
+
+ ```ruby
+ git_data_dirs({
+ 'default' => {
+ 'path' => '/var/opt/gitlab/git-data'
+ },
+ 'storage1' => {
+ 'path' => '/mnt/gitlab/git-data'
+ },
+ })
+ ```
+
+ 1. On `gitaly2.internal`:
+
+ ```ruby
+ git_data_dirs({
+ 'storage2' => {
+ 'path' => '/mnt/gitlab/git-data'
+ },
+ })
+ ```
+
+ <!--
+ updates to following example must also be made at
+ https://gitlab.com/gitlab-org/charts/gitlab/blob/master/doc/advanced/external-gitaly/external-omnibus-gitaly.md#configure-omnibus-gitlab
+ -->
+
+1. Save the file and [reconfigure GitLab](../restart_gitlab.md#omnibus-gitlab-reconfigure).
+1. Confirm that Gitaly can perform callbacks to the internal API:
+
+ ```shell
+ sudo /opt/gitlab/embedded/service/gitlab-shell/bin/check -config /opt/gitlab/embedded/service/gitlab-shell/config.yml
+ ```
+
+1. Verify the GitLab services are running:
+
+ ```shell
+ sudo gitlab-ctl status
+ ```
+
+ The output should be similar to the following:
+
+ ```plaintext
+ run: consul: (pid 30339) 77006s; run: log: (pid 29878) 77020s
+ run: gitaly: (pid 30351) 77005s; run: log: (pid 29660) 77040s
+ run: logrotate: (pid 7760) 3213s; run: log: (pid 29782) 77032s
+ run: node-exporter: (pid 30378) 77004s; run: log: (pid 29812) 77026s
+ ```
+
+### Gitaly TLS support
+
+Gitaly supports TLS encryption. To be able to communicate
+with a Gitaly instance that listens for secure connections you will need to use `tls://` URL
+scheme in the `gitaly_address` of the corresponding storage entry in the GitLab configuration.
+
+You will need to bring your own certificates as this isn't provided automatically.
+The certificate, or its certificate authority, must be installed on all Gitaly
+nodes (including the Gitaly node using the certificate) and on all client nodes
+that communicate with it following the procedure described in
+[GitLab custom certificate configuration](https://docs.gitlab.com/omnibus/settings/ssl.html#install-custom-public-certificates).
+
+NOTE: **Note:**
+The self-signed certificate must specify the address you use to access the
+Gitaly server. If you are addressing the Gitaly server by a hostname, you can
+either use the Common Name field for this, or add it as a Subject Alternative
+Name. If you are addressing the Gitaly server by its IP address, you must add it
+as a Subject Alternative Name to the certificate.
+[gRPC does not support using an IP address as Common Name in a certificate](https://github.com/grpc/grpc/issues/2691).
+
+NOTE: **Note:**
+It is possible to configure Gitaly servers with both an
+unencrypted listening address `listen_addr` and an encrypted listening
+address `tls_listen_addr` at the same time. This allows you to do a
+gradual transition from unencrypted to encrypted traffic, if necessary.
+
+To configure Gitaly with TLS:
+
+1. Create the `/etc/gitlab/ssl` directory and copy your key and certificate there:
+
+ ```shell
+ sudo mkdir -p /etc/gitlab/ssl
+ sudo chmod 755 /etc/gitlab/ssl
+ sudo cp key.pem cert.pem /etc/gitlab/ssl/
+ sudo chmod 644 key.pem cert.pem
+ ```
+
+1. Copy the cert to `/etc/gitlab/trusted-certs` so Gitaly will trust the cert when
+ calling into itself:
+
+ ```shell
+ sudo cp /etc/gitlab/ssl/cert.pem /etc/gitlab/trusted-certs/
+ ```
+
+1. Edit `/etc/gitlab/gitlab.rb` and add:
+
+ <!--
+ updates to following example must also be made at
+ https://gitlab.com/gitlab-org/charts/gitlab/blob/master/doc/advanced/external-gitaly/external-omnibus-gitaly.md#configure-omnibus-gitlab
+ -->
+
+ ```ruby
+ gitaly['tls_listen_addr'] = "0.0.0.0:9999"
+ gitaly['certificate_path'] = "/etc/gitlab/ssl/cert.pem"
+ gitaly['key_path'] = "/etc/gitlab/ssl/key.pem"
+ ```
+
+1. Delete `gitaly['listen_addr']` to allow only encrypted connections.
+1. Save the file and [reconfigure GitLab](../restart_gitlab.md#omnibus-gitlab-reconfigure).
+
+<div align="right">
+ <a type="button" class="btn btn-default" href="#setup-components">
+ Back to setup components <i class="fa fa-angle-double-up" aria-hidden="true"></i>
+ </a>
+</div>
+
+## Configure Sidekiq
+
+Sidekiq requires connection to the Redis, PostgreSQL and Gitaly instance.
+The following IPs will be used as an example:
+
+- `10.6.0.71`: Sidekiq 1
+- `10.6.0.72`: Sidekiq 2
+- `10.6.0.73`: Sidekiq 3
+- `10.6.0.74`: Sidekiq 4
+
+To configure the Sidekiq nodes, one each one:
+
+1. SSH into the Sidekiq server.
+1. [Download/install](https://about.gitlab.com/install/) the Omnibus GitLab package
+you want using steps 1 and 2 from the GitLab downloads page.
+**Do not complete any other steps on the download page.**
+1. Open `/etc/gitlab/gitlab.rb` with your editor:
+
+ ```ruby
+ ########################################
+ ##### Services Disabled ###
+ ########################################
+
+ nginx['enable'] = false
+ grafana['enable'] = false
+ prometheus['enable'] = false
+ gitlab_rails['auto_migrate'] = false
+ alertmanager['enable'] = false
+ gitaly['enable'] = false
+ gitlab_workhorse['enable'] = false
+ nginx['enable'] = false
+ puma['enable'] = false
+ postgres_exporter['enable'] = false
+ postgresql['enable'] = false
+ redis['enable'] = false
+ redis_exporter['enable'] = false
+ gitlab_exporter['enable'] = false
+
+ ########################################
+ #### Redis ###
+ ########################################
+
+ ## Must be the same in every sentinel node
+ redis['master_name'] = 'gitlab-redis'
+
+ ## The same password for Redis authentication you set up for the master node.
+ redis['master_password'] = '<redis_primary_password>'
+
+ ## A list of sentinels with `host` and `port`
+ gitlab_rails['redis_sentinels'] = [
+ {'host' => '10.6.0.11', 'port' => 26379},
+ {'host' => '10.6.0.12', 'port' => 26379},
+ {'host' => '10.6.0.13', 'port' => 26379},
+ ]
+
+ #######################################
+ ### Gitaly ###
+ #######################################
+
+ git_data_dirs({
+ 'default' => { 'gitaly_address' => 'tcp://gitaly1.internal:8075' },
+ 'storage1' => { 'gitaly_address' => 'tcp://gitaly1.internal:8075' },
+ 'storage2' => { 'gitaly_address' => 'tcp://gitaly2.internal:8075' },
+ })
+ gitlab_rails['gitaly_token'] = 'YOUR_TOKEN'
+
+ #######################################
+ ### Postgres ###
+ #######################################
+ gitlab_rails['db_host'] = '10.6.0.20' # internal load balancer IP
+ gitlab_rails['db_port'] = 6432
+ gitlab_rails['db_password'] = '<postgresql_user_password>'
+ gitlab_rails['db_adapter'] = 'postgresql'
+ gitlab_rails['db_encoding'] = 'unicode'
+ gitlab_rails['auto_migrate'] = false
+
+ #######################################
+ ### Sidekiq configuration ###
+ #######################################
+ sidekiq['listen_address'] = "0.0.0.0"
+
+ #######################################
+ ### Monitoring configuration ###
+ #######################################
+ consul['enable'] = true
+ consul['monitoring_service_discovery'] = true
+
+ consul['configuration'] = {
+ retry_join: %w(10.6.0.11 10.6.0.12 10.6.0.13)
+ }
+
+ # Set the network addresses that the exporters will listen on
+ node_exporter['listen_address'] = '0.0.0.0:9100'
+
+ # Rails Status for prometheus
+ gitlab_rails['monitoring_whitelist'] = ['10.6.0.81/32', '127.0.0.0/8']
+ gitlab_rails['prometheus_address'] = '10.6.0.81:9090'
+ ```
+
+1. Save the file and [reconfigure GitLab](../restart_gitlab.md#omnibus-gitlab-reconfigure).
+1. Verify the GitLab services are running:
+
+ ```shell
+ sudo gitlab-ctl status
+ ```
+
+ The output should be similar to the following:
+
+ ```plaintext
+ run: consul: (pid 30114) 77353s; run: log: (pid 29756) 77367s
+ run: logrotate: (pid 9898) 3561s; run: log: (pid 29653) 77380s
+ run: node-exporter: (pid 30134) 77353s; run: log: (pid 29706) 77372s
+ run: sidekiq: (pid 30142) 77351s; run: log: (pid 29638) 77386s
+ ```
+
+TIP: **Tip:**
+You can also run [multiple Sidekiq processes](../operations/extra_sidekiq_processes.md).
+
+<div align="right">
+ <a type="button" class="btn btn-default" href="#setup-components">
+ Back to setup components <i class="fa fa-angle-double-up" aria-hidden="true"></i>
+ </a>
+</div>
+
+## Configure GitLab Rails
+
+NOTE: **Note:**
+In our architectures we run each GitLab Rails node using the Puma webserver
+and have its number of workers set to 90% of available CPUs along with four threads. For
+nodes that are running Rails with other components the worker value should be reduced
+accordingly where we've found 50% achieves a good balance but this is dependent
+on workload.
+
+This section describes how to configure the GitLab application (Rails) component.
+On each node perform the following:
+
+1. If you're [using NFS](#configure-nfs-optional):
+
+ 1. If necessary, install the NFS client utility packages using the following
+ commands:
+
+ ```shell
+ # Ubuntu/Debian
+ apt-get install nfs-common
+
+ # CentOS/Red Hat
+ yum install nfs-utils nfs-utils-lib
+ ```
+
+ 1. Specify the necessary NFS mounts in `/etc/fstab`.
+ The exact contents of `/etc/fstab` will depend on how you chose
+ to configure your NFS server. See the [NFS documentation](../high_availability/nfs.md)
+ for examples and the various options.
+
+ 1. Create the shared directories. These may be different depending on your NFS
+ mount locations.
+
+ ```shell
+ mkdir -p /var/opt/gitlab/.ssh /var/opt/gitlab/gitlab-rails/uploads /var/opt/gitlab/gitlab-rails/shared /var/opt/gitlab/gitlab-ci/builds /var/opt/gitlab/git-data
+ ```
+
+1. Download/install Omnibus GitLab using **steps 1 and 2** from
+ [GitLab downloads](https://about.gitlab.com/install/). Do not complete other
+ steps on the download page.
+1. Create/edit `/etc/gitlab/gitlab.rb` and use the following configuration.
+ To maintain uniformity of links across nodes, the `external_url`
+ on the application server should point to the external URL that users will use
+ to access GitLab. This would be the URL of the [external load balancer](#configure-the-external-load-balancer)
+ which will route traffic to the GitLab application server:
+
+ ```ruby
+ external_url 'https://gitlab.example.com'
+
+ # Gitaly and GitLab use two shared secrets for authentication, one to authenticate gRPC requests
+ # to Gitaly, and a second for authentication callbacks from GitLab-Shell to the GitLab internal API.
+ # The following two values must be the same as their respective values
+ # of the Gitaly setup
+ gitlab_rails['gitaly_token'] = 'gitalyecret'
+ gitlab_shell['secret_token'] = 'shellsecret'
+
+ git_data_dirs({
+ 'default' => { 'gitaly_address' => 'tcp://gitaly1.internal:8075' },
+ 'storage1' => { 'gitaly_address' => 'tcp://gitaly1.internal:8075' },
+ 'storage2' => { 'gitaly_address' => 'tcp://gitaly2.internal:8075' },
+ })
+
+ ## Disable components that will not be on the GitLab application server
+ roles ['application_role']
+ gitaly['enable'] = false
+ nginx['enable'] = true
+ sidekiq['enable'] = false
+
+ ## PostgreSQL connection details
+ # Disable PostgreSQL on the application node
+ postgresql['enable'] = false
+ gitlab_rails['db_host'] = '10.6.0.20' # internal load balancer IP
+ gitlab_rails['db_port'] = 6432
+ gitlab_rails['db_password'] = '<postgresql_user_password>'
+ gitlab_rails['auto_migrate'] = false
+
+ ## Redis connection details
+ ## Must be the same in every sentinel node
+ redis['master_name'] = 'gitlab-redis'
+
+ ## The same password for Redis authentication you set up for the Redis primary node.
+ redis['master_password'] = '<redis_primary_password>'
+
+ ## A list of sentinels with `host` and `port`
+ gitlab_rails['redis_sentinels'] = [
+ {'host' => '10.6.0.11', 'port' => 26379},
+ {'host' => '10.6.0.12', 'port' => 26379},
+ {'host' => '10.6.0.13', 'port' => 26379}
+ ]
+
+ ## Enable service discovery for Prometheus
+ consul['enable'] = true
+ consul['monitoring_service_discovery'] = true
+
+ # Set the network addresses that the exporters used for monitoring will listen on
+ node_exporter['listen_address'] = '0.0.0.0:9100'
+ gitlab_workhorse['prometheus_listen_addr'] = '0.0.0.0:9229'
+ sidekiq['listen_address'] = "0.0.0.0"
+ puma['listen'] = '0.0.0.0'
+
+ ## The IPs of the Consul server nodes
+ ## You can also use FQDNs and intermix them with IPs
+ consul['configuration'] = {
+ retry_join: %w(10.6.0.11 10.6.0.12 10.6.0.13),
+ }
+
+ # Add the monitoring node's IP address to the monitoring whitelist and allow it to
+ # scrape the NGINX metrics
+ gitlab_rails['monitoring_whitelist'] = ['10.6.0.81/32', '127.0.0.0/8']
+ nginx['status']['options']['allow'] = ['10.6.0.81/32', '127.0.0.0/8']
+ gitlab_rails['prometheus_address'] = '10.6.0.81:9090'
+
+ ## Uncomment and edit the following options if you have set up NFS
+ ##
+ ## Prevent GitLab from starting if NFS data mounts are not available
+ ##
+ #high_availability['mountpoint'] = '/var/opt/gitlab/git-data'
+ ##
+ ## Ensure UIDs and GIDs match between servers for permissions via NFS
+ ##
+ #user['uid'] = 9000
+ #user['gid'] = 9000
+ #web_server['uid'] = 9001
+ #web_server['gid'] = 9001
+ #registry['uid'] = 9002
+ #registry['gid'] = 9002
+ ```
+
+1. If you're using [Gitaly with TLS support](#gitaly-tls-support), make sure the
+ `git_data_dirs` entry is configured with `tls` instead of `tcp`:
+
+ ```ruby
+ git_data_dirs({
+ 'default' => { 'gitaly_address' => 'tls://gitaly1.internal:9999' },
+ 'storage1' => { 'gitaly_address' => 'tls://gitaly1.internal:9999' },
+ 'storage2' => { 'gitaly_address' => 'tls://gitaly2.internal:9999' },
+ })
+ ```
+
+ 1. Copy the cert into `/etc/gitlab/trusted-certs`:
+
+ ```shell
+ sudo cp cert.pem /etc/gitlab/trusted-certs/
+ ```
+
+1. Save the file and [reconfigure GitLab](../restart_gitlab.md#omnibus-gitlab-reconfigure).
+1. Run `sudo gitlab-rake gitlab:gitaly:check` to confirm the node can connect to Gitaly.
+1. Tail the logs to see the requests:
+
+ ```shell
+ sudo gitlab-ctl tail gitaly
+ ```
+
+1. Verify the GitLab services are running:
+
+ ```shell
+ sudo gitlab-ctl status
+ ```
+
+ The output should be similar to the following:
+
+ ```plaintext
+ run: consul: (pid 4890) 8647s; run: log: (pid 29962) 79128s
+ run: gitlab-exporter: (pid 4902) 8647s; run: log: (pid 29913) 79134s
+ run: gitlab-workhorse: (pid 4904) 8646s; run: log: (pid 29713) 79155s
+ run: logrotate: (pid 12425) 1446s; run: log: (pid 29798) 79146s
+ run: nginx: (pid 4925) 8646s; run: log: (pid 29726) 79152s
+ run: node-exporter: (pid 4931) 8645s; run: log: (pid 29855) 79140s
+ run: puma: (pid 4936) 8645s; run: log: (pid 29656) 79161s
+ ```
+
+NOTE: **Note:**
+When you specify `https` in the `external_url`, as in the example
+above, GitLab assumes you have SSL certificates in `/etc/gitlab/ssl/`. If
+certificates are not present, NGINX will fail to start. See the
+[NGINX documentation](https://docs.gitlab.com/omnibus/settings/nginx.html#enable-https)
+for more information.
+
+### GitLab Rails post-configuration
+
+1. Ensure that all migrations ran:
+
+ ```shell
+ gitlab-rake gitlab:db:configure
+ ```
+
+ NOTE: **Note:**
+ If you encounter a `rake aborted!` error stating that PgBouncer is failing to connect to
+ PostgreSQL it may be that your PgBouncer node's IP address is missing from
+ PostgreSQL's `trust_auth_cidr_addresses` in `gitlab.rb` on your database nodes. See
+ [PgBouncer error `ERROR: pgbouncer cannot connect to server`](troubleshooting.md#pgbouncer-error-error-pgbouncer-cannot-connect-to-server)
+ in the Troubleshooting section before proceeding.
+
+1. [Configure fast lookup of authorized SSH keys in the database](../operations/fast_ssh_key_lookup.md).
+
+<div align="right">
+ <a type="button" class="btn btn-default" href="#setup-components">
+ Back to setup components <i class="fa fa-angle-double-up" aria-hidden="true"></i>
+ </a>
+</div>
+
+## Configure Prometheus
+
+The Omnibus GitLab package can be used to configure a standalone Monitoring node
+running [Prometheus](../monitoring/prometheus/index.md) and
+[Grafana](../monitoring/performance/grafana_configuration.md):
+
+1. SSH into the Monitoring node.
+1. [Download/install](https://about.gitlab.com/install/) the Omnibus GitLab
+ package you want using **steps 1 and 2** from the GitLab downloads page.
+ Do not complete any other steps on the download page.
+1. Edit `/etc/gitlab/gitlab.rb` and add the contents:
+
+ ```ruby
+ external_url 'http://gitlab.example.com'
+
+ # Disable all other services
+ gitlab_rails['auto_migrate'] = false
+ alertmanager['enable'] = false
+ gitaly['enable'] = false
+ gitlab_exporter['enable'] = false
+ gitlab_workhorse['enable'] = false
+ nginx['enable'] = true
+ postgres_exporter['enable'] = false
+ postgresql['enable'] = false
+ redis['enable'] = false
+ redis_exporter['enable'] = false
+ sidekiq['enable'] = false
+ puma['enable'] = false
+ unicorn['enable'] = false
+ node_exporter['enable'] = false
+ gitlab_exporter['enable'] = false
+
+ # Enable Prometheus
+ prometheus['enable'] = true
+ prometheus['listen_address'] = '0.0.0.0:9090'
+ prometheus['monitor_kubernetes'] = false
+
+ # Enable Login form
+ grafana['disable_login_form'] = false
+
+ # Enable Grafana
+ grafana['enable'] = true
+ grafana['admin_password'] = '<grafana_password>'
+
+ # Enable service discovery for Prometheus
+ consul['enable'] = true
+ consul['monitoring_service_discovery'] = true
+ consul['configuration'] = {
+ retry_join: %w(10.6.0.11 10.6.0.12 10.6.0.13)
+ }
+ ```
+
+1. Save the file and [reconfigure GitLab](../restart_gitlab.md#omnibus-gitlab-reconfigure).
+1. In the GitLab UI, set `admin/application_settings/metrics_and_profiling` > Metrics - Grafana to `/-/grafana` to
+ `http[s]://<MONITOR NODE>/-/grafana`.
+1. Verify the GitLab services are running:
+
+ ```shell
+ sudo gitlab-ctl status
+ ```
+
+ The output should be similar to the following:
+
+ ```plaintext
+ run: consul: (pid 31637) 17337s; run: log: (pid 29748) 78432s
+ run: grafana: (pid 31644) 17337s; run: log: (pid 29719) 78438s
+ run: logrotate: (pid 31809) 2936s; run: log: (pid 29581) 78462s
+ run: nginx: (pid 31665) 17335s; run: log: (pid 29556) 78468s
+ run: prometheus: (pid 31672) 17335s; run: log: (pid 29633) 78456s
+ ```
+
+<div align="right">
+ <a type="button" class="btn btn-default" href="#setup-components">
+ Back to setup components <i class="fa fa-angle-double-up" aria-hidden="true"></i>
+ </a>
+</div>
+
+## Configure the object storage
+
+GitLab supports using an object storage service for holding numerous types of data.
+It's recommended over [NFS](#configure-nfs-optional) and in general it's better
+in larger setups as object storage is typically much more performant, reliable,
+and scalable.
+
+Object storage options that GitLab has tested, or is aware of customers using include:
+
+- SaaS/Cloud solutions such as [Amazon S3](https://aws.amazon.com/s3/), [Google cloud storage](https://cloud.google.com/storage).
+- On-premises hardware and appliances from various storage vendors.
+- MinIO. There is [a guide to deploying this](https://docs.gitlab.com/charts/advanced/external-object-storage/minio.html) within our Helm Chart documentation.
+
+For configuring GitLab to use Object Storage refer to the following guides
+based on what features you intend to use:
+
+1. Configure [object storage for backups](../../raketasks/backup_restore.md#uploading-backups-to-a-remote-cloud-storage).
+1. Configure [object storage for job artifacts](../job_artifacts.md#using-object-storage)
+ including [incremental logging](../job_logs.md#new-incremental-logging-architecture).
+1. Configure [object storage for LFS objects](../lfs/index.md#storing-lfs-objects-in-remote-object-storage).
+1. Configure [object storage for uploads](../uploads.md#using-object-storage-core-only).
+1. Configure [object storage for merge request diffs](../merge_request_diffs.md#using-object-storage).
+1. Configure [object storage for Container Registry](../packages/container_registry.md#use-object-storage) (optional feature).
+1. Configure [object storage for Mattermost](https://docs.mattermost.com/administration/config-settings.html#file-storage) (optional feature).
+1. Configure [object storage for packages](../packages/index.md#using-object-storage) (optional feature). **(PREMIUM ONLY)**
+1. Configure [object storage for Dependency Proxy](../packages/dependency_proxy.md#using-object-storage) (optional feature). **(PREMIUM ONLY)**
+1. Configure [object storage for Pseudonymizer](../pseudonymizer.md#configuration) (optional feature). **(ULTIMATE ONLY)**
+1. Configure [object storage for autoscale Runner caching](https://docs.gitlab.com/runner/configuration/autoscale.html#distributed-runners-caching) (optional - for improved performance).
+1. Configure [object storage for Terraform state files](../terraform_state.md#using-object-storage-core-only).
+
+Using separate buckets for each data type is the recommended approach for GitLab.
+
+A limitation of our configuration is that each use of object storage is separately configured.
+[We have an issue for improving this](https://gitlab.com/gitlab-org/gitlab/-/issues/23345)
+and easily using one bucket with separate folders is one improvement that this might bring.
+
+There is at least one specific issue with using the same bucket:
+when GitLab is deployed with the Helm chart restore from backup
+[will not properly function](https://docs.gitlab.com/charts/advanced/external-object-storage/#lfs-artifacts-uploads-packages-external-diffs-pseudonymizer)
+unless separate buckets are used.
+
+One risk of using a single bucket would be if your organization decided to
+migrate GitLab to the Helm deployment in the future. GitLab would run, but the situation with
+backups might not be realized until the organization had a critical requirement for the backups to
+work.
+
+<div align="right">
+ <a type="button" class="btn btn-default" href="#setup-components">
+ Back to setup components <i class="fa fa-angle-double-up" aria-hidden="true"></i>
+ </a>
+</div>
+
+## Configure NFS (optional)
+
+[Object storage](#configure-the-object-storage), along with [Gitaly](#configure-gitaly)
+are recommended over NFS wherever possible for improved performance. If you intend
+to use GitLab Pages, this currently [requires NFS](troubleshooting.md#gitlab-pages-requires-nfs).
+
+See how to [configure NFS](../high_availability/nfs.md).
+
+<div align="right">
+ <a type="button" class="btn btn-default" href="#setup-components">
+ Back to setup components <i class="fa fa-angle-double-up" aria-hidden="true"></i>
+ </a>
+</div>
+
+## Troubleshooting
+
+See the [troubleshooting documentation](troubleshooting.md).
+
+<div align="right">
+ <a type="button" class="btn btn-default" href="#setup-components">
+ Back to setup components <i class="fa fa-angle-double-up" aria-hidden="true"></i>
+ </a>
+</div>
diff --git a/doc/administration/reference_architectures/index.md b/doc/administration/reference_architectures/index.md
index 623d7f3f776..8fde71a66bf 100644
--- a/doc/administration/reference_architectures/index.md
+++ b/doc/administration/reference_architectures/index.md
@@ -38,7 +38,8 @@ When scaling GitLab, there are several factors to consider:
- A load balancer is added in front to distribute traffic across the application nodes.
- The application nodes connects to a shared file server and PostgreSQL and Redis services on the backend.
-NOTE: **Note:** Depending on your workflow, the following recommended
+NOTE: **Note:**
+Depending on your workflow, the following recommended
reference architectures may need to be adapted accordingly. Your workload
is influenced by factors including how active your users are,
how much automation you use, mirroring, and repository/change size. Additionally the
@@ -120,13 +121,13 @@ As long as at least one of each component is online and capable of handling the
### Automated database failover **(PREMIUM ONLY)**
> - Level of complexity: **High**
-> - Required domain knowledge: PgBouncer, Repmgr, shared storage, distributed systems
+> - Required domain knowledge: PgBouncer, Repmgr or Patroni, shared storage, distributed systems
> - Supported tiers: [GitLab Premium and Ultimate](https://about.gitlab.com/pricing/)
By adding automatic failover for database systems, you can enable higher uptime
with additional database nodes. This extends the default database with
cluster management and failover policies.
-[PgBouncer in conjunction with Repmgr](../postgresql/replication_and_failover.md)
+[PgBouncer in conjunction with Repmgr or Patroni](../postgresql/replication_and_failover.md)
is recommended.
### Instance level replication with GitLab Geo **(PREMIUM ONLY)**
@@ -164,6 +165,7 @@ column.
| [PostgreSQL](../../development/architecture.md#postgresql) | Database | [PostgreSQL configuration](https://docs.gitlab.com/omnibus/settings/database.html) | Yes |
| [PgBouncer](../../development/architecture.md#pgbouncer) | Database connection pooler | [PgBouncer configuration](../high_availability/pgbouncer.md#running-pgbouncer-as-part-of-a-non-ha-gitlab-installation) **(PREMIUM ONLY)** | Yes |
| Repmgr | PostgreSQL cluster management and failover | [PostgreSQL and Repmgr configuration](../postgresql/replication_and_failover.md) | Yes |
+| Patroni | An alternative PostgreSQL cluster management and failover | [PostgreSQL and Patroni configuration](../postgresql/replication_and_failover.md#patroni) | Yes |
| [Redis](../../development/architecture.md#redis) ([3](#footnotes)) | Key/value store for fast data lookup and caching | [Redis configuration](../high_availability/redis.md) | Yes |
| Redis Sentinel | Redis | [Redis Sentinel configuration](../high_availability/redis.md) | Yes |
| [Gitaly](../../development/architecture.md#gitaly) ([2](#footnotes)) ([7](#footnotes)) | Provides access to Git repositories | [Gitaly configuration](../gitaly/index.md#run-gitaly-on-its-own-server) | Yes |
@@ -209,7 +211,7 @@ cluster with the Rails nodes broken down into a number of smaller Pods across th
For medium sized installs (3,000 - 5,000) we suggest one Redis cluster for all
classes and that Redis Sentinel is hosted alongside Consul.
For larger architectures (10,000 users or more) we suggest running a separate
- [Redis Cluster](../high_availability/redis.md#running-multiple-redis-clusters) for the Cache class
+ [Redis Cluster](../redis/replication_and_failover.md#running-multiple-redis-clusters) for the Cache class
and another for the Queues and Shared State classes respectively. We also recommend
that you run the Redis Sentinel clusters separately for each Redis Cluster.
diff --git a/doc/administration/reference_architectures/troubleshooting.md b/doc/administration/reference_architectures/troubleshooting.md
index 15e377fe183..35bdb65c810 100644
--- a/doc/administration/reference_architectures/troubleshooting.md
+++ b/doc/administration/reference_architectures/troubleshooting.md
@@ -1,4 +1,4 @@
-# Troubleshooting a reference architecture set up
+# Troubleshooting a reference architecture setup
This page serves as the troubleshooting documentation if you followed one of
the [reference architectures](index.md#reference-architectures).
@@ -17,7 +17,7 @@ with the Fog library that GitLab uses. Symptoms include:
### GitLab Pages requires NFS
If you intend to use [GitLab Pages](../../user/project/pages/index.md), this currently requires
-[NFS](../high_availability/nfs.md). There is [work in progress](https://gitlab.com/gitlab-org/gitlab-pages/issues/196)
+[NFS](../high_availability/nfs.md). There is [work in progress](https://gitlab.com/gitlab-org/gitlab-pages/-/issues/196)
to remove this dependency. In the future, GitLab Pages may use
[object storage](https://gitlab.com/gitlab-org/gitlab/-/issues/208135).
@@ -86,8 +86,117 @@ a workaround, in the mean time, to
## Troubleshooting Redis
-If the application node cannot connect to the Redis node, check your firewall rules and
-make sure Redis can accept TCP connections under port `6379`.
+There are a lot of moving parts that needs to be taken care carefully
+in order for the HA setup to work as expected.
+
+Before proceeding with the troubleshooting below, check your firewall rules:
+
+- Redis machines
+ - Accept TCP connection in `6379`
+ - Connect to the other Redis machines via TCP in `6379`
+- Sentinel machines
+ - Accept TCP connection in `26379`
+ - Connect to other Sentinel machines via TCP in `26379`
+ - Connect to the Redis machines via TCP in `6379`
+
+### Troubleshooting Redis replication
+
+You can check if everything is correct by connecting to each server using
+`redis-cli` application, and sending the `info replication` command as below.
+
+```shell
+/opt/gitlab/embedded/bin/redis-cli -h <redis-host-or-ip> -a '<redis-password>' info replication
+```
+
+When connected to a `Primary` Redis, you will see the number of connected
+`replicas`, and a list of each with connection details:
+
+```plaintext
+# Replication
+role:master
+connected_replicas:1
+replica0:ip=10.133.5.21,port=6379,state=online,offset=208037514,lag=1
+master_repl_offset:208037658
+repl_backlog_active:1
+repl_backlog_size:1048576
+repl_backlog_first_byte_offset:206989083
+repl_backlog_histlen:1048576
+```
+
+When it's a `replica`, you will see details of the primary connection and if
+its `up` or `down`:
+
+```plaintext
+# Replication
+role:replica
+master_host:10.133.1.58
+master_port:6379
+master_link_status:up
+master_last_io_seconds_ago:1
+master_sync_in_progress:0
+replica_repl_offset:208096498
+replica_priority:100
+replica_read_only:1
+connected_replicas:0
+master_repl_offset:0
+repl_backlog_active:0
+repl_backlog_size:1048576
+repl_backlog_first_byte_offset:0
+repl_backlog_histlen:0
+```
+
+### Troubleshooting Sentinel
+
+If you get an error like: `Redis::CannotConnectError: No sentinels available.`,
+there may be something wrong with your configuration files or it can be related
+to [this issue](https://github.com/redis/redis-rb/issues/531).
+
+You must make sure you are defining the same value in `redis['master_name']`
+and `redis['master_pasword']` as you defined for your sentinel node.
+
+The way the Redis connector `redis-rb` works with sentinel is a bit
+non-intuitive. We try to hide the complexity in omnibus, but it still requires
+a few extra configurations.
+
+---
+
+To make sure your configuration is correct:
+
+1. SSH into your GitLab application server
+1. Enter the Rails console:
+
+ ```shell
+ # For Omnibus installations
+ sudo gitlab-rails console
+
+ # For source installations
+ sudo -u git rails console -e production
+ ```
+
+1. Run in the console:
+
+ ```ruby
+ redis = Redis.new(Gitlab::Redis::SharedState.params)
+ redis.info
+ ```
+
+ Keep this screen open and try to simulate a failover below.
+
+1. To simulate a failover on primary Redis, SSH into the Redis server and run:
+
+ ```shell
+ # port must match your primary redis port, and the sleep time must be a few seconds bigger than defined one
+ redis-cli -h localhost -p 6379 DEBUG sleep 20
+ ```
+
+1. Then back in the Rails console from the first step, run:
+
+ ```ruby
+ redis.info
+ ```
+
+ You should see a different port after a few seconds delay
+ (the failover/reconnect time).
## Troubleshooting Gitaly
@@ -291,7 +400,7 @@ unset https_proxy
When updating the `gitaly['listen_addr']` or `gitaly['prometheus_listen_addr']` values, Gitaly may continue to listen on the old address after a `sudo gitlab-ctl reconfigure`.
-When this occurs, performing a `sudo gitlab-ctl restart` will resolve the issue. This will no longer be necessary after [this issue](https://gitlab.com/gitlab-org/gitaly/issues/2521) is resolved.
+When this occurs, performing a `sudo gitlab-ctl restart` will resolve the issue. This will no longer be necessary after [this issue](https://gitlab.com/gitlab-org/gitaly/-/issues/2521) is resolved.
### Permission denied errors appearing in Gitaly logs when accessing repositories from a standalone Gitaly node
@@ -327,3 +436,135 @@ or
```shell
curl http[s]://localhost:<EXPORTER LISTENING PORT>/-/metric
```
+
+## Troubleshooting PgBouncer
+
+In case you are experiencing any issues connecting through PgBouncer, the first place to check is always the logs:
+
+```shell
+sudo gitlab-ctl tail pgbouncer
+```
+
+Additionally, you can check the output from `show databases` in the [administrative console](#pgbouncer-administrative-console). In the output, you would expect to see values in the `host` field for the `gitlabhq_production` database. Additionally, `current_connections` should be greater than 1.
+
+### PgBouncer administrative console
+
+As part of Omnibus GitLab, the `gitlab-ctl pgb-console` command is provided to automatically connect to the PgBouncer administrative console. See the [PgBouncer documentation](https://www.pgbouncer.org/usage.html#admin-console) for detailed instructions on how to interact with the console.
+
+To start a session:
+
+```shell
+sudo gitlab-ctl pgb-console
+```
+
+The password you will be prompted for is the `pgbouncer_user_password`
+
+To get some basic information about the instance, run
+
+```shell
+pgbouncer=# show databases; show clients; show servers;
+ name | host | port | database | force_user | pool_size | reserve_pool | pool_mode | max_connections | current_connections
+---------------------+-----------+------+---------------------+------------+-----------+--------------+-----------+-----------------+---------------------
+ gitlabhq_production | 127.0.0.1 | 5432 | gitlabhq_production | | 100 | 5 | | 0 | 1
+ pgbouncer | | 6432 | pgbouncer | pgbouncer | 2 | 0 | statement | 0 | 0
+(2 rows)
+
+ type | user | database | state | addr | port | local_addr | local_port | connect_time | request_time | ptr | link
+| remote_pid | tls
+------+-----------+---------------------+--------+-----------+-------+------------+------------+---------------------+---------------------+-----------+------
++------------+-----
+ C | gitlab | gitlabhq_production | active | 127.0.0.1 | 44590 | 127.0.0.1 | 6432 | 2018-04-24 22:13:10 | 2018-04-24 22:17:10 | 0x12444c0 |
+| 0 |
+ C | gitlab | gitlabhq_production | active | 127.0.0.1 | 44592 | 127.0.0.1 | 6432 | 2018-04-24 22:13:10 | 2018-04-24 22:17:10 | 0x12447c0 |
+| 0 |
+ C | gitlab | gitlabhq_production | active | 127.0.0.1 | 44594 | 127.0.0.1 | 6432 | 2018-04-24 22:13:10 | 2018-04-24 22:17:10 | 0x1244940 |
+| 0 |
+ C | gitlab | gitlabhq_production | active | 127.0.0.1 | 44706 | 127.0.0.1 | 6432 | 2018-04-24 22:14:22 | 2018-04-24 22:16:31 | 0x1244ac0 |
+| 0 |
+ C | gitlab | gitlabhq_production | active | 127.0.0.1 | 44708 | 127.0.0.1 | 6432 | 2018-04-24 22:14:22 | 2018-04-24 22:15:15 | 0x1244c40 |
+| 0 |
+ C | gitlab | gitlabhq_production | active | 127.0.0.1 | 44794 | 127.0.0.1 | 6432 | 2018-04-24 22:15:15 | 2018-04-24 22:15:15 | 0x1244dc0 |
+| 0 |
+ C | gitlab | gitlabhq_production | active | 127.0.0.1 | 44798 | 127.0.0.1 | 6432 | 2018-04-24 22:15:15 | 2018-04-24 22:16:31 | 0x1244f40 |
+| 0 |
+ C | pgbouncer | pgbouncer | active | 127.0.0.1 | 44660 | 127.0.0.1 | 6432 | 2018-04-24 22:13:51 | 2018-04-24 22:17:12 | 0x1244640 |
+| 0 |
+(8 rows)
+
+ type | user | database | state | addr | port | local_addr | local_port | connect_time | request_time | ptr | link | rem
+ote_pid | tls
+------+--------+---------------------+-------+-----------+------+------------+------------+---------------------+---------------------+-----------+------+----
+--------+-----
+ S | gitlab | gitlabhq_production | idle | 127.0.0.1 | 5432 | 127.0.0.1 | 35646 | 2018-04-24 22:15:15 | 2018-04-24 22:17:10 | 0x124dca0 | |
+ 19980 |
+(1 row)
+```
+
+### Message: `LOG: invalid CIDR mask in address`
+
+See the suggested fix [in Geo documentation](../geo/replication/troubleshooting.md#message-log--invalid-cidr-mask-in-address).
+
+### Message: `LOG: invalid IP mask "md5": Name or service not known`
+
+See the suggested fix [in Geo documentation](../geo/replication/troubleshooting.md#message-log--invalid-ip-mask-md5-name-or-service-not-known).
+
+## Troubleshooting PostgreSQL
+
+In case you are experiencing any issues connecting through PgBouncer, the first place to check is always the logs:
+
+```shell
+sudo gitlab-ctl tail postgresql
+```
+
+### Consul and PostgreSQL changes not taking effect
+
+Due to the potential impacts, `gitlab-ctl reconfigure` only reloads Consul and PostgreSQL, it will not restart the services. However, not all changes can be activated by reloading.
+
+To restart either service, run `gitlab-ctl restart SERVICE`
+
+For PostgreSQL, it is usually safe to restart the master node by default. Automatic failover defaults to a 1 minute timeout. Provided the database returns before then, nothing else needs to be done. To be safe, you can stop `repmgrd` on the standby nodes first with `gitlab-ctl stop repmgrd`, then start afterwards with `gitlab-ctl start repmgrd`.
+
+On the Consul server nodes, it is important to restart the Consul service in a controlled fashion. Read our [Consul documentation](../high_availability/consul.md#restarting-the-server-cluster) for instructions on how to restart the service.
+
+### `gitlab-ctl repmgr-check-master` command produces errors
+
+If this command displays errors about database permissions it is likely that something failed during
+install, resulting in the `gitlab-consul` database user getting incorrect permissions. Follow these
+steps to fix the problem:
+
+1. On the master database node, connect to the database prompt - `gitlab-psql -d template1`
+1. Delete the `gitlab-consul` user - `DROP USER "gitlab-consul";`
+1. Exit the database prompt - `\q`
+1. [Reconfigure GitLab](../restart_gitlab.md#omnibus-gitlab-reconfigure) and the user will be re-added with the proper permissions.
+1. Change to the `gitlab-consul` user - `su - gitlab-consul`
+1. Try the check command again - `gitlab-ctl repmgr-check-master`.
+
+Now there should not be errors. If errors still occur then there is another problem.
+
+### PgBouncer error `ERROR: pgbouncer cannot connect to server`
+
+You may get this error when running `gitlab-rake gitlab:db:configure` or you
+may see the error in the PgBouncer log file.
+
+```plaintext
+PG::ConnectionBad: ERROR: pgbouncer cannot connect to server
+```
+
+The problem may be that your PgBouncer node's IP address is not included in the
+`trust_auth_cidr_addresses` setting in `/etc/gitlab/gitlab.rb` on the database nodes.
+
+You can confirm that this is the issue by checking the PostgreSQL log on the master
+database node. If you see the following error then `trust_auth_cidr_addresses`
+is the problem.
+
+```plaintext
+2018-03-29_13:59:12.11776 FATAL: no pg_hba.conf entry for host "123.123.123.123", user "pgbouncer", database "gitlabhq_production", SSL off
+```
+
+To fix the problem, add the IP address to `/etc/gitlab/gitlab.rb`.
+
+```ruby
+postgresql['trust_auth_cidr_addresses'] = %w(123.123.123.123/32 <other_cidrs>)
+```
+
+[Reconfigure GitLab](../restart_gitlab.md#omnibus-gitlab-reconfigure) for the changes to take effect.
diff --git a/doc/administration/repository_storage_paths.md b/doc/administration/repository_storage_paths.md
index 87f901becf5..5520eeace0a 100644
--- a/doc/administration/repository_storage_paths.md
+++ b/doc/administration/repository_storage_paths.md
@@ -1,3 +1,10 @@
+---
+stage: Create
+group: Gitaly
+info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers
+type: reference, howto
+---
+
# Repository storage paths
> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/4578) in GitLab 8.10.
@@ -60,7 +67,8 @@ files and add the full paths of the alternative repository storage paths. In
the example below, we add two more mount points that are named `nfs_1` and `nfs_2`
respectively.
-NOTE: **Note:** This example uses NFS. We do not recommend using EFS for storage as it may impact GitLab's performance. See the [relevant documentation](high_availability/nfs.md#avoid-using-awss-elastic-file-system-efs) for more details.
+NOTE: **Note:**
+This example uses NFS. We do not recommend using EFS for storage as it may impact GitLab's performance. See the [relevant documentation](high_availability/nfs.md#avoid-using-awss-elastic-file-system-efs) for more details.
**For installations from source**
@@ -110,7 +118,10 @@ Once you set the multiple storage paths, you can choose where new repositories
will be stored under **Admin Area > Settings > Repository >
Repository storage > Storage nodes for new repositories**.
-![Choose repository storage path in Admin Area](img/repository_storages_admin_ui_v12_10.png)
+Each storage can be assigned a weight from 0-100. When a new project is created, these
+weights are used to determine the storage location the repository will be created on.
+
+![Choose repository storage path in Admin Area](img/repository_storages_admin_ui_v13_1.png)
Beginning with GitLab 8.13.4, multiple paths can be chosen. New repositories
will be randomly placed on one of the selected paths.
diff --git a/doc/administration/repository_storage_types.md b/doc/administration/repository_storage_types.md
index 825da08b66e..1c036cfe2ac 100644
--- a/doc/administration/repository_storage_types.md
+++ b/doc/administration/repository_storage_types.md
@@ -1,3 +1,10 @@
+---
+stage: Create
+group: Gitaly
+info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers
+type: reference, howto
+---
+
# Repository storage types **(CORE ONLY)**
> - [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/issues/28283) in GitLab 10.0.
diff --git a/doc/administration/server_hooks.md b/doc/administration/server_hooks.md
index 6df0f187a42..2fea000b799 100644
--- a/doc/administration/server_hooks.md
+++ b/doc/administration/server_hooks.md
@@ -8,130 +8,183 @@ disqus_identifier: 'https://docs.gitlab.com/ee/administration/custom_hooks.html'
# Server hooks **(CORE ONLY)**
-> **Notes:**
->
-> - Server hooks were [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/196051) in GitLab 12.8 replacing Custom Hooks.
-> - Server hooks must be configured on the filesystem of the GitLab server. Only GitLab server administrators will be able to complete these tasks. Please explore [webhooks](../user/project/integrations/webhooks.md) and [GitLab CI/CD](../ci/README.md) as an option if you do not have filesystem access. For a user-configurable Git hook interface, see [Push Rules](../push_rules/push_rules.md), available in GitLab Starter **(STARTER)**.
-> - Server hooks won't be replicated to secondary nodes if you use [GitLab Geo](geo/replication/index.md).
-
-Git natively supports hooks that are executed on different actions. These hooks run
-on the server and can be used to enforce specific commit policies or perform other
-tasks based on the state of the repository.
-
-Examples of server-side Git hooks include `pre-receive`, `post-receive`, and `update`.
-See [Git SCM Server-Side Hooks](https://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks#Server-Side-Hooks)
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/196051) in GitLab 12.8 replacing Custom Hooks.
+
+Git supports hooks that are executed on different actions. These hooks run on the server and can be
+used to enforce specific commit policies or perform other tasks based on the state of the
+repository.
+
+Git supports the following hooks:
+
+- `pre-receive`
+- `post-receive`
+- `update`
+
+See [the Git documentation](https://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks#_server_side_hooks)
for more information about each hook type.
-## Create a server hook for a repository
+Server-side Git hooks can be configured for:
-Server-side Git hooks are typically placed in the repository's `hooks`
-subdirectory. In GitLab, hook directories are symlinked to the GitLab Shell
-`hooks` directory for ease of maintenance between GitLab Shell upgrades.
-Server hooks are implemented differently, but the behavior is exactly the same
-once the hook is created.
+- [A single repository](#create-a-server-hook-for-a-repository).
+- [All repositories](#create-a-global-server-hook-for-all-repositories).
+
+Note the following about server hooks:
+
+- Server hooks must be configured on the file system of the GitLab server. Only GitLab server
+ administrators are able to complete these tasks. If you don't have file system access, see
+ possible alternatives such as:
+ - [Webhooks](../user/project/integrations/webhooks.md).
+ - [GitLab CI/CD](../ci/README.md).
+ - [Push Rules](../push_rules/push_rules.md), for a user-configurable Git hook
+ interface. **(STARTER)**
+- Server hooks aren't replicated to [Geo](geo/replication/index.md) secondary nodes.
+
+## Create a server hook for a repository
-NOTE: **Note:**
If you are not using [hashed storage](repository_storage_types.md#hashed-storage), the project's
-repository directory might not exactly match the instructions below. In that case,
-for an installation from source the path is usually `/home/git/repositories/<group>/<project>.git`.
-For Omnibus installs the path is usually `/var/opt/gitlab/git-data/repositories/<group>/<project>.git`.
-
-Follow the steps below to set up a server hook for a
-repository:
-
-1. Find that project's path on the GitLab server, by navigating to the
- **Admin area > Projects**. From there, select the project for which you
- would like to add a hook. You can find the path to the project's repository
- under **Gitaly relative path** on that page.
-1. Create a new directory in this location called `custom_hooks`.
-1. Inside the new `custom_hooks` directory, create a file with a name matching
- the hook type. For a pre-receive hook the file name should be `pre-receive`
- with no extension.
-1. Make the hook file executable and make sure it's owned by Git.
-1. Write the code to make the server hook function as expected. Hooks can be
- in any language. Ensure the 'shebang' at the top properly reflects the language
- type. For example, if the script is in Ruby the shebang will probably be
+repository directory might not exactly match the instructions below. In that case:
+
+- For an installation from source, the path is usually
+ `/home/git/repositories/<group>/<project>.git`.
+- For Omnibus GitLab installs, the path is usually
+ `/var/opt/gitlab/git-data/repositories/<group>/<project>.git`.
+
+Follow the steps below to set up a server-side hook for a repository:
+
+1. Navigate to **Admin area > Projects** and click on the project you want to add a server hook to.
+1. Locate the **Gitaly relative path** on the page that appears. This is where the server hook
+ must be implemented. For information on interpreting the relative path, see
+ [Translating hashed storage paths](repository_storage_types.md#translating-hashed-storage-paths).
+1. On the file system, create a new directory in this location called `custom_hooks`.
+1. Inside the new `custom_hooks` directory, create a file with a name matching the hook type. For
+ example, for a pre-receive hook the filename should be `pre-receive` with no extension.
+1. Make the hook file executable and ensure that it's owned by the Git user.
+1. Write the code to make the server hook function as expected. Hooks can be in any language. Ensure
+ the ["shebang"](https://en.wikipedia.org/wiki/Shebang_(Unix)) at the top properly reflects the
+ language type. For example, if the script is in Ruby the shebang is probably
`#!/usr/bin/env ruby`.
-Assuming the hook code is properly implemented the hook will run as appropriate.
-
-## Set a global server hook for all repositories
-
-To create a Git hook that applies to all of your repositories in
-your instance, set a global server hook. Since GitLab will look inside the GitLab Shell
-`hooks` directory for global hooks, adding any hook there will apply it to all repositories.
-Follow the steps below to properly set up a server hook for all repositories:
-
-1. On the GitLab server, navigate to the configured custom hook directory. The
- default is in the GitLab Shell directory. The GitLab Shell `hook` directory
- for an installation from source the path is usually
- `/home/git/gitlab-shell/hooks`. For Omnibus installs the path is usually
- `/opt/gitlab/embedded/service/gitlab-shell/hooks`.
- To look in a different directory for the global custom hooks,
- set `custom_hooks_dir` in the Gitaly config. For Omnibus installations, this is set
- in `gitlab.rb`. For source installations, the configuration location depends on the
- GitLab version. For:
-
- - GitLab 13.0 and earlier, this is set in `gitlab-shell/config.yml`.
- - GitLab 13.1 and later, this is set in `gitaly/config.toml` under the `[hooks]`
- section.
-
- NOTE: **Note:**
- The `custom_hooks_dir` value in `gitlab-shell/config.yml` is still honored in GitLab
- 13.1 and later if the value in `gitaly/config.toml` is blank or non-existent.
-
-1. Create a new directory in this location. Depending on your hook, it will be
- either a `pre-receive.d`, `post-receive.d`, or `update.d` directory.
-1. Inside this new directory, add your hook. Hooks can be
- in any language. Ensure the 'shebang' at the top properly reflects the language
- type. For example, if the script is in Ruby the shebang will probably be
+Assuming the hook code is properly implemented, the hook code is executed as appropriate.
+
+## Create a global server hook for all repositories
+
+To create a Git hook that applies to all of the repositories in your instance, set a global server
+hook. The default global server hook directory is in the GitLab Shell directory. Any
+hook added there applies to all repositories.
+
+The default directory:
+
+- For an installation from source is usually `/home/git/gitlab-shell/hooks`.
+- For Omnibus GitLab installs is usually `/opt/gitlab/embedded/service/gitlab-shell/hooks`.
+
+To use a different directory for global server hooks, set `custom_hooks_dir` in Gitaly
+configuration:
+
+- For Omnibus installations, this is set in `gitlab.rb`.
+- For source installations, the configuration location depends on the GitLab version. For:
+ - GitLab 13.0 and earlier, this is set in `gitlab-shell/config.yml`.
+ - GitLab 13.1 and later, this is set in `gitaly/config.toml` under the `[hooks]` section.
+
+NOTE: **Note:**
+The `custom_hooks_dir` value in `gitlab-shell/config.yml` is still honored in GitLab 13.1 and later
+if the value in `gitaly/config.toml` is blank or non-existent.
+
+Follow the steps below to set up a global server hook for all repositories:
+
+1. On the GitLab server, navigate to the configured global server hook directory.
+1. Create a new directory in this location. Depending on the type of hook, it can be either a
+ `pre-receive.d`, `post-receive.d`, or `update.d` directory.
+1. Inside this new directory, add your hook. Hooks can be in any language. Ensure the
+ ["shebang"](https://en.wikipedia.org/wiki/Shebang_(Unix)) at the top properly reflects the
+ language type. For example, if the script is in Ruby the shebang is probably
`#!/usr/bin/env ruby`.
-1. Make the hook file executable and make sure it's owned by Git.
+1. Make the hook file executable and ensure that it's owned by the Git user.
Now test the hook to check whether it is functioning properly.
-## Chained hooks support
+## Chained hooks
> [Introduced](https://gitlab.com/gitlab-org/gitlab-shell/-/merge_requests/93) in GitLab Shell 4.1.0 and GitLab 8.15.
-Hooks can be also global or be set per project directories and support a chained
-execution of the hooks.
+Server hooks set [per project](#create-a-server-hook-for-a-repository) or
+[globally](#create-a-global-server-hook-for-all-repositories) can be executed in a chain.
-NOTE: **Note:**
-`<hook_name>.d` would need to be either `pre-receive.d`,
-`post-receive.d`, or `update.d` to work properly. Any other names will be ignored.
+Server hooks are searched for and executed in the following order of priority:
+
+- Built-in GitLab server hooks. These are not user-customizable.
+- `<project>.git/custom_hooks/<hook_name>`: Per-project hooks. This was kept for backwards
+ compatibility.
+- `<project>.git/custom_hooks/<hook_name>.d/*`: Location for per-project hooks.
+- `<custom_hooks_dir>/<hook_name>.d/*`: Location for all executable global hook files
+ except editor backup files.
+
+Within a directory, server hooks:
+
+- Are executed in alphabetical order.
+- Stop executing when a hook exits with a non-zero value.
+
+Note:
+
+- `<hook_name>.d` must be either `pre-receive.d`, `post-receive.d`, or `update.d` to work properly.
+ Any other names are ignored.
+- Files in `.d` directories must be executable and not match the backup file pattern (`*~`).
+- For `<project>.git` you need to [translate](repository_storage_types.md#translating-hashed-storage-paths)
+ your project name into the hashed storage format that GitLab uses.
+
+## Environment Variables
+
+The following set of environment variables are available to server hooks.
+
+| Environment variable | Description |
+|:---------------------|:----------------------------------------------------------------------------|
+| `GL_ID` | GitLab identifier of user that initiated the push. For example, `user-2234` |
+| `GL_PROJECT_PATH` | (GitLab 13.2 and later) GitLab project path |
+| `GL_PROTOCOL` | (GitLab 13.2 and later) Protocol used with push |
+| `GL_REPOSITORY` | `project-<id>` where `id` is the ID of the project |
+| `GL_USERNAME` | GitLab username of the user that initiated the push |
+
+Pre-receive and post-receive server hooks can also access the following Git environment variables.
+
+| Environment variable | Description |
+|:-----------------------------------|:-----------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| `GIT_ALTERNATE_OBJECT_DIRECTORIES` | Alternate object directories in the quarantine environment. See [Git `receive-pack` documentation](https://git-scm.com/docs/git-receive-pack#_quarantine_environment). |
+| `GIT_OBJECT_DIRECTORY` | GitLab project path in the quarantine environment. See [Git `receive-pack` documentation](https://git-scm.com/docs/git-receive-pack#_quarantine_environment). |
+| `GIT_PUSH_OPTION_COUNT` | Number of push options. See [Git `pre-receive` documentation](https://git-scm.com/docs/githooks#pre-receive). |
+| `GIT_PUSH_OPTION_<i>` | Value of push options where `i` is from `0` to `GIT_PUSH_OPTION_COUNT - 1`. See [Git `pre-receive` documentation](https://git-scm.com/docs/githooks#pre-receive). |
NOTE: **Note:**
-Files in `.d` directories need to be executable and not match the backup file
-pattern (`*~`).
+While other environment variables can be passed to server hooks, your application should not rely on
+them as they can change.
-The hooks are searched and executed in this order:
+## Transition to Go
-1. Built-in GitLab server hooks (not user-customizable).
-1. `<project>.git/custom_hooks/<hook_name>` - per-project hook (this was kept as the already existing behavior).
-1. `<project>.git/custom_hooks/<hook_name>.d/*` - per-project hooks.
-1. `<custom_hooks_dir>/<hook_name>.d/*` - global hooks: all executable files (except editor backup files).
+> Introduced in GitLab 13.2 using feature flags.
-The hooks of the same type are executed in order and execution stops on the
-first script exiting with a non-zero value.
+The following server hooks have been re-implemented in Go:
-For `<project>.git` you'll need to
-[translate your project name into the hashed storage format](repository_storage_types.md#translating-hashed-storage-paths)
-that GitLab uses.
+- `pre-receive`, with the Go implementation used by default. To use the Ruby implementation instead,
+ [disable](../operations/feature_flags.md#enable-or-disable-feature-flag-strategies) the
+ `:gitaly_go_preceive_hook` feature flag.
+- `update`, with the Go implementation used by default. To use the Ruby implementation instead,
+ [disable](../operations/feature_flags.md#enable-or-disable-feature-flag-strategies) the
+ `:gitaly_go_update_hook` feature flag.
+- `post-receive`, however the Ruby implementation is used by default. To use the Go implementation
+ instead, [enable](../operations/feature_flags.md#enable-or-disable-feature-flag-strategies) the
+ `:gitaly_go_postreceive_hook` feature flag.
## Custom error messages
> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/5073) in GitLab 8.10.
-To have custom error messages appear in GitLab's UI when the commit is
-declined or an error occurs during the Git hook, your script should:
+To have custom error messages appear in GitLab's UI when a commit is declined or an error occurs
+during the Git hook, your script should:
- Send the custom error messages to either the script's `stdout` or `stderr`.
- Prefix each message with `GL-HOOK-ERR:` with no characters appearing before the prefix.
### Example custom error message
-This hook script written in bash will generate the following message in GitLab's UI:
+This hook script written in Bash generates the following message in GitLab's UI:
```shell
#!/bin/sh
diff --git a/doc/administration/smime_signing_email.md b/doc/administration/smime_signing_email.md
index bab7c5c260d..304b65917c1 100644
--- a/doc/administration/smime_signing_email.md
+++ b/doc/administration/smime_signing_email.md
@@ -21,7 +21,8 @@ files must be provided:
Optionally, you can also provide a bundle of CA certs (PEM-encoded) to be
included on each signature. This will typically be an intermediate CA.
-NOTE: **Note:** Be mindful of the access levels for your private keys and visibility to
+NOTE: **Note:**
+Be mindful of the access levels for your private keys and visibility to
third parties.
**For Omnibus installations:**
@@ -38,7 +39,8 @@ third parties.
1. Save the file and [reconfigure GitLab](restart_gitlab.md#omnibus-gitlab-reconfigure) for the changes to take effect.
-NOTE: **Note:** The key needs to be readable by the GitLab system user (`git` by default).
+NOTE: **Note:**
+The key needs to be readable by the GitLab system user (`git` by default).
**For installations from source:**
@@ -61,7 +63,8 @@ NOTE: **Note:** The key needs to be readable by the GitLab system user (`git` by
1. Save the file and [restart GitLab](restart_gitlab.md#installations-from-source) for the changes to take effect.
-NOTE: **Note:** The key needs to be readable by the GitLab system user (`git` by default).
+NOTE: **Note:**
+The key needs to be readable by the GitLab system user (`git` by default).
### How to convert S/MIME PKCS#12 / PFX format to PEM encoding
diff --git a/doc/administration/terraform_state.md b/doc/administration/terraform_state.md
index 77d5495418e..8d3ddc4e306 100644
--- a/doc/administration/terraform_state.md
+++ b/doc/administration/terraform_state.md
@@ -68,20 +68,7 @@ The following settings are:
### S3-compatible connection settings
-The connection settings match those provided by [Fog](https://github.com/fog), and are as follows:
-
-| Setting | Description | Default |
-|---------|-------------|---------|
-| `provider` | Always `AWS` for compatible hosts | `AWS` |
-| `aws_access_key_id` | Credentials for AWS or compatible provider | |
-| `aws_secret_access_key` | Credentials for AWS or compatible provider | |
-| `aws_signature_version` | AWS signature version to use. 2 or 4 are valid options. Digital Ocean Spaces and other providers may need 2. | 4 |
-| `enable_signature_v4_streaming` | Set to true to enable HTTP chunked transfers with [AWS v4 signatures](https://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-streaming.html). Oracle Cloud S3 needs this to be false | `true` |
-| `region` | AWS region | us-east-1 |
-| `host` | S3-compatible host when not using AWS. For example, `localhost` or `storage.example.com` | `s3.amazonaws.com` |
-| `endpoint` | Can be used when configuring an S3-compatible service such as [MinIO](https://min.io), by entering a URL such as `http://127.0.0.1:9000` | (optional) |
-| `path_style` | Set to true to use `host/bucket_name/object` style paths instead of `bucket_name.host/object`. Leave as false for AWS S3 | `false` |
-| `use_iam_profile` | For AWS S3, set to true to use an IAM profile instead of access keys | `false` |
+See [the available connection settings for different providers](object_storage.md#connection-settings).
**In Omnibus installations:**
@@ -91,7 +78,7 @@ The connection settings match those provided by [Fog](https://github.com/fog), a
```ruby
gitlab_rails['terraform_state_enabled'] = true
gitlab_rails['terraform_state_object_store_enabled'] = true
- gitlab_rails['terraform_state_object_store_remote_directory'] = "terraform_state"
+ gitlab_rails['terraform_state_object_store_remote_directory'] = "terraform"
gitlab_rails['terraform_state_object_store_connection'] = {
'provider' => 'AWS',
'region' => 'eu-central-1',
@@ -123,7 +110,7 @@ The connection settings match those provided by [Fog](https://github.com/fog), a
enabled: true
object_store:
enabled: true
- remote_directory: "terraform_state" # The bucket name
+ remote_directory: "terraform" # The bucket name
connection:
provider: AWS # Only AWS supported at the moment
aws_access_key_id: AWS_ACESS_KEY_ID
diff --git a/doc/administration/troubleshooting/elasticsearch.md b/doc/administration/troubleshooting/elasticsearch.md
index 12b82e4bc48..e13261e3074 100644
--- a/doc/administration/troubleshooting/elasticsearch.md
+++ b/doc/administration/troubleshooting/elasticsearch.md
@@ -261,6 +261,9 @@ Beyond that, you will want to review the error. If it is:
- Specifically from the indexer, this could be a bug/issue and should be escalated to
GitLab support.
- An OS issue, you will want to reach out to your systems administrator.
+- A `Faraday::TimeoutError (execution expired)` error **and** you're using a proxy,
+ [set a custom `gitlab_rails['env']` environment variable, called `no_proxy`](https://docs.gitlab.com/omnibus/settings/environment-variables.html)
+ with the IP address of your Elasticsearch host.
### Troubleshooting performance
diff --git a/doc/administration/troubleshooting/gitlab_rails_cheat_sheet.md b/doc/administration/troubleshooting/gitlab_rails_cheat_sheet.md
index c911c617210..8e9749fb239 100644
--- a/doc/administration/troubleshooting/gitlab_rails_cheat_sheet.md
+++ b/doc/administration/troubleshooting/gitlab_rails_cheat_sheet.md
@@ -11,13 +11,13 @@ having an issue with GitLab, it is highly recommended that you check your
[support options](https://about.gitlab.com/support/) first, before attempting to use
this information.
-CAUTION: **CAUTION:**
+CAUTION: **Caution:**
Please note that some of these scripts could be damaging if not run correctly,
or under the right conditions. We highly recommend running them under the
guidance of a Support Engineer, or running them in a test environment with a
backup of the instance ready to be restored, just in case.
-CAUTION: **CAUTION:**
+CAUTION: **Caution:**
Please also note that as GitLab changes, changes to the code are inevitable,
and so some scripts may not work as they once used to. These are not kept
up-to-date as these scripts/commands were added as they were found/needed. As
@@ -201,6 +201,20 @@ project.repository_read_only = true; project.save
project.update!(repository_read_only: true)
```
+### Transfer project from one namespace to another
+
+```ruby
+ p= Project.find_by_full_path('')
+
+ # To set the owner of the project
+ current_user= p.creator
+
+# Namespace where you want this to be moved.
+namespace = Namespace.find_by_full_path("")
+
+::Projects::TransferService.new(p, current_user).execute(namespace)
+```
+
### Bulk update service integration password for _all_ projects
For example, change the Jira user's password for all projects that have the Jira
@@ -447,7 +461,7 @@ group = Group.find_by_path_or_name("groupname")
# Count users from subgroup and up (inherited)
group.members_with_parents.count
-# Count users from parent group and down (specific grants)
+# Count users from the parent group and down (specific grants)
parent.members_with_descendants.count
```
diff --git a/doc/administration/troubleshooting/linux_cheat_sheet.md b/doc/administration/troubleshooting/linux_cheat_sheet.md
index 7af4219caa3..06c49d67f40 100644
--- a/doc/administration/troubleshooting/linux_cheat_sheet.md
+++ b/doc/administration/troubleshooting/linux_cheat_sheet.md
@@ -10,7 +10,7 @@ and it may be useful for users with experience with Linux. If you are currently
having an issue with GitLab, you may want to check your [support options](https://about.gitlab.com/support/)
first, before attempting to use this information.
-CAUTION: **CAUTION:**
+CAUTION: **Caution:**
If you are administering GitLab you are expected to know these commands for your distribution
of choice. If you are a GitLab Support Engineer, consider this a cross-reference to
translate `yum` -> `apt-get` and the like.
diff --git a/doc/administration/troubleshooting/navigating_gitlab_via_rails_console.md b/doc/administration/troubleshooting/navigating_gitlab_via_rails_console.md
index 69af7ea6801..571973c12d9 100644
--- a/doc/administration/troubleshooting/navigating_gitlab_via_rails_console.md
+++ b/doc/administration/troubleshooting/navigating_gitlab_via_rails_console.md
@@ -6,7 +6,7 @@ Thanks to this, we also get access to the amazing tools built right into Rails.
In this guide, we'll introduce the [Rails console](debug.md#starting-a-rails-console-session)
and the basics of interacting with your GitLab instance from the command line.
-CAUTION: **CAUTION:**
+CAUTION: **Caution:**
The Rails console interacts directly with your GitLab instance. In many cases,
there are no handrails to prevent you from permanently modifying, corrupting
or destroying production data. If you would like to explore the Rails console
@@ -186,7 +186,7 @@ user.save
Which would return:
```ruby
-Enqueued ActionMailer::DeliveryJob (Job ID: 05915c4e-c849-4e14-80bb-696d5ae22065) to Sidekiq(mailers) with arguments: "DeviseMailer", "password_change", "deliver_now", #<GlobalID:0x00007f42d8ccebe8 @uri=#<URI::GID gid://gitlab/User/1>>
+Enqueued ActionMailer::MailDeliveryJob (Job ID: 05915c4e-c849-4e14-80bb-696d5ae22065) to Sidekiq(mailers) with arguments: "DeviseMailer", "password_change", "deliver_now", #<GlobalID:0x00007f42d8ccebe8 @uri=#<URI::GID gid://gitlab/User/1>>
=> true
```
diff --git a/doc/administration/troubleshooting/postgresql.md b/doc/administration/troubleshooting/postgresql.md
index e5a4dffb3cc..36c1f20818a 100644
--- a/doc/administration/troubleshooting/postgresql.md
+++ b/doc/administration/troubleshooting/postgresql.md
@@ -8,7 +8,8 @@ This page is useful information about PostgreSQL that the GitLab Support
Team sometimes uses while troubleshooting. GitLab is making this public, so that anyone
can make use of the Support team's collected knowledge.
-CAUTION: **Caution:** Some procedures documented here may break your GitLab instance. Use at your own risk.
+CAUTION: **Caution:**
+Some procedures documented here may break your GitLab instance. Use at your own risk.
If you are on a [paid tier](https://about.gitlab.com/pricing/) and are not sure how
to use these commands, it is best to [contact Support](https://about.gitlab.com/support/)
@@ -46,7 +47,7 @@ This section is for links to information elsewhere in the GitLab documentation.
- Managing Omnibus PostgreSQL versions [from the development docs](https://docs.gitlab.com/omnibus/development/managing-postgresql-versions.html)
- [PostgreSQL scaling](../postgresql/replication_and_failover.md)
- - including [troubleshooting](../postgresql/replication_and_failover.md#troubleshooting) `gitlab-ctl repmgr-check-master` and PgBouncer errors
+ - including [troubleshooting](../postgresql/replication_and_failover.md#troubleshooting) `gitlab-ctl repmgr-check-master` (or `gitlab-ctl patroni check-leader` if you are using Patroni) and PgBouncer errors
- [Developer database documentation](../../development/README.md#database-guides) - some of which is absolutely not for production use. Including:
- understanding EXPLAIN plans
@@ -115,7 +116,8 @@ Quoting from issue [#1](https://gitlab.com/gitlab-org/gitlab/-/issues/30528):
> "If a deadlock is hit, and we resolve it through aborting the transaction after a short period, then the retry mechanisms we already have will make the deadlocked piece of work try again, and it's unlikely we'll deadlock multiple times in a row."
-TIP: **Tip:** In support, our general approach to reconfiguring timeouts (applies also to the HTTP stack as well) is that it's acceptable to do it temporarily as a workaround. If it makes GitLab usable for the customer, then it buys time to understand the problem more completely, implement a hot fix, or make some other change that addresses the root cause. Generally, the timeouts should be put back to reasonable defaults once the root cause is resolved.
+TIP: **Tip:**
+In support, our general approach to reconfiguring timeouts (applies also to the HTTP stack as well) is that it's acceptable to do it temporarily as a workaround. If it makes GitLab usable for the customer, then it buys time to understand the problem more completely, implement a hot fix, or make some other change that addresses the root cause. Generally, the timeouts should be put back to reasonable defaults once the root cause is resolved.
In this case, the guidance we had from development was to drop deadlock_timeout and/or statement_timeout but to leave the third setting at 60s. Setting idle_in_transaction protects the database from sessions potentially hanging for days. There's more discussion in [the issue relating to introducing this timeout on GitLab.com](https://gitlab.com/gitlab-com/gl-infra/production/-/issues/1053).
diff --git a/doc/administration/troubleshooting/sidekiq.md b/doc/administration/troubleshooting/sidekiq.md
index 5109a3baff2..566e2686735 100644
--- a/doc/administration/troubleshooting/sidekiq.md
+++ b/doc/administration/troubleshooting/sidekiq.md
@@ -38,7 +38,7 @@ Example log output:
```json
{"severity":"INFO","time":"2020-06-08T14:37:37.892Z","class":"AdminEmailsWorker","args":["[FILTERED]","[FILTERED]","[FILTERED]"],"retry":3,"queue":"admin_emails","backtrace":true,"jid":"9e35e2674ac7b12d123e13cc","created_at":"2020-06-08T14:37:37.373Z","meta.user":"root","meta.caller_id":"Admin::EmailsController#create","correlation_id":"37D3lArJmT1","uber-trace-id":"2d942cc98cc1b561:6dc94409cfdd4d77:9fbe19bdee865293:1","enqueued_at":"2020-06-08T14:37:37.410Z","pid":65011,"message":"AdminEmailsWorker JID-9e35e2674ac7b12d123e13cc: done: 0.48085 sec","job_status":"done","scheduling_latency_s":0.001012,"redis_calls":9,"redis_duration_s":0.004608,"redis_read_bytes":696,"redis_write_bytes":6141,"duration_s":0.48085,"cpu_s":0.308849,"completed_at":"2020-06-08T14:37:37.892Z","db_duration_s":0.010742}
-{"severity":"INFO","time":"2020-06-08T14:37:37.894Z","class":"ActiveJob::QueueAdapters::SidekiqAdapter::JobWrapper","wrapped":"ActionMailer::DeliveryJob","queue":"mailers","args":["[FILTERED]"],"retry":3,"backtrace":true,"jid":"e47a4f6793d475378432e3c8","created_at":"2020-06-08T14:37:37.884Z","meta.user":"root","meta.caller_id":"AdminEmailsWorker","correlation_id":"37D3lArJmT1","uber-trace-id":"2d942cc98cc1b561:29344de0f966446d:5c3b0e0e1bef987b:1","enqueued_at":"2020-06-08T14:37:37.885Z","pid":65011,"message":"ActiveJob::QueueAdapters::SidekiqAdapter::JobWrapper JID-e47a4f6793d475378432e3c8: start","job_status":"start","scheduling_latency_s":0.009473}
+{"severity":"INFO","time":"2020-06-08T14:37:37.894Z","class":"ActiveJob::QueueAdapters::SidekiqAdapter::JobWrapper","wrapped":"ActionMailer::MailDeliveryJob","queue":"mailers","args":["[FILTERED]"],"retry":3,"backtrace":true,"jid":"e47a4f6793d475378432e3c8","created_at":"2020-06-08T14:37:37.884Z","meta.user":"root","meta.caller_id":"AdminEmailsWorker","correlation_id":"37D3lArJmT1","uber-trace-id":"2d942cc98cc1b561:29344de0f966446d:5c3b0e0e1bef987b:1","enqueued_at":"2020-06-08T14:37:37.885Z","pid":65011,"message":"ActiveJob::QueueAdapters::SidekiqAdapter::JobWrapper JID-e47a4f6793d475378432e3c8: start","job_status":"start","scheduling_latency_s":0.009473}
{"severity":"INFO","time":"2020-06-08T14:39:50.648Z","class":"NewIssueWorker","args":["455","1"],"retry":3,"queue":"new_issue","backtrace":true,"jid":"a24af71f96fd129ec47f5d1e","created_at":"2020-06-08T14:39:50.643Z","meta.user":"root","meta.project":"h5bp/html5-boilerplate","meta.root_namespace":"h5bp","meta.caller_id":"Projects::IssuesController#create","correlation_id":"f9UCZHqhuP7","uber-trace-id":"28f65730f99f55a3:a5d2b62dec38dffc:48ddd092707fa1b7:1","enqueued_at":"2020-06-08T14:39:50.646Z","pid":65011,"message":"NewIssueWorker JID-a24af71f96fd129ec47f5d1e: start","job_status":"start","scheduling_latency_s":0.001144}
```
@@ -283,7 +283,8 @@ queue = Sidekiq::Queue.new('<queue name>')
queue.each { |job| job.delete if <condition>}
```
-NOTE: **Note:** This will remove jobs that are queued but not started, running jobs will not be killed. Have a look at the section below for cancelling running jobs.
+NOTE: **Note:**
+This will remove jobs that are queued but not started, running jobs will not be killed. Have a look at the section below for cancelling running jobs.
In the method above, `<queue-name>` is the name of the queue that contains the job(s) you want to delete and `<condition>` will decide which jobs get deleted.
diff --git a/doc/administration/uploads.md b/doc/administration/uploads.md
index 620f349912c..d9902208e93 100644
--- a/doc/administration/uploads.md
+++ b/doc/administration/uploads.md
@@ -57,6 +57,9 @@ This configuration relies on valid AWS credentials to be configured already.
[Read more about using object storage with GitLab](object_storage.md).
+NOTE: **Note:**
+We recommend using the [consolidated object storage settings](object_storage.md#consolidated-object-storage-configuration). The following instructions apply to the original config format.
+
## Object Storage Settings
For source installations the following settings are nested under `uploads:` and then `object_store:`. On Omnibus GitLab installs they are prefixed by `uploads_object_store_`.
@@ -70,22 +73,9 @@ For source installations the following settings are nested under `uploads:` and
| `proxy_download` | Set to true to enable proxying all files served. Option allows to reduce egress traffic as this allows clients to download directly from remote storage instead of proxying all data | `false` |
| `connection` | Various connection options described below | |
-### S3 compatible connection settings
+### Connection settings
-The connection settings match those provided by [Fog](https://github.com/fog), and are as follows:
-
-| Setting | Description | Default |
-|---------|-------------|---------|
-| `provider` | Always `AWS` for compatible hosts | `AWS` |
-| `aws_access_key_id` | AWS credentials, or compatible | |
-| `aws_secret_access_key` | AWS credentials, or compatible | |
-| `aws_signature_version` | AWS signature version to use. `2` or `4` are valid options. Digital Ocean Spaces and other providers may need `2`. | `4` |
-| `enable_signature_v4_streaming` | Set to true to enable HTTP chunked transfers with [AWS v4 signatures](https://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-streaming.html). Oracle Cloud S3 needs this to be `false`. | `true` |
-| `region` | AWS region | us-east-1 |
-| `host` | S3 compatible host for when not using AWS, e.g. `localhost` or `storage.example.com` | `s3.amazonaws.com` |
-| `endpoint` | Can be used when configuring an S3 compatible service such as [MinIO](https://min.io), by entering a URL such as `http://127.0.0.1:9000` | (optional) |
-| `path_style` | Set to `true` to use `host/bucket_name/object` style paths instead of `bucket_name.host/object`. Leave as `false` for AWS S3. | `false` |
-| `use_iam_profile` | Set to `true` to use IAM profile instead of access keys | false
+See [the available connection settings for different providers](object_storage.md#connection-settings).
**In Omnibus installations:**
@@ -143,35 +133,7 @@ _The uploads are stored by default in
1. Save the file and [restart GitLab](restart_gitlab.md#installations-from-source) for the changes to take effect.
1. Migrate any existing local uploads to the object storage using [`gitlab:uploads:migrate` Rake task](raketasks/uploads/migrate.md).
-### Oracle Cloud S3 connection settings
-
-Note that Oracle Cloud S3 must be sure to use the following settings:
-
-| Setting | Value |
-|---------|-------|
-| `enable_signature_v4_streaming` | `false` |
-| `path_style` | `true` |
-
-If `enable_signature_v4_streaming` is set to `true`, you may see the
-following error:
-
-```plaintext
-STREAMING-AWS4-HMAC-SHA256-PAYLOAD is not supported
-```
-
-### OpenStack compatible connection settings
-
-The connection settings match those provided by [Fog](https://github.com/fog), and are as follows:
-
-| Setting | Description | Default |
-|---------|-------------|---------|
-| `provider` | Always `OpenStack` for compatible hosts | `OpenStack` |
-| `openstack_username` | OpenStack username | |
-| `openstack_api_key` | OpenStack API key | |
-| `openstack_temp_url_key` | OpenStack key for generating temporary URLs | |
-| `openstack_auth_url` | OpenStack authentication endpoint | |
-| `openstack_region` | OpenStack region | |
-| `openstack_tenant` | OpenStack tenant ID |
+### OpenStack example
**In Omnibus installations:**
diff --git a/doc/api/README.md b/doc/api/README.md
index 6cbb99a76cb..b07f14b5a7a 100644
--- a/doc/api/README.md
+++ b/doc/api/README.md
@@ -423,7 +423,7 @@ Status: 200 OK
```
CAUTION: **Deprecation:**
-The `Links` Header will be removed in GitLab 14.0 to be aligned with the [W3C specification](https://www.w3.org/wiki/LinkHeader)
+The `Links` Header will be removed in GitLab 14.0 to be aligned with the [W3C `Link` specification](https://www.w3.org/wiki/LinkHeader)
The link to the next page contains an additional filter `id_after=42` which excludes records we have retrieved already.
Note the type of filter depends on the `order_by` option used and we may have more than one additional filter.
@@ -491,6 +491,28 @@ GET /api/v4/projects/1/branches/my%2Fbranch/commits
GET /api/v4/projects/1/repository/tags/my%2Ftag
```
+## Request Payload
+
+API Requests can use parameters sent as [query strings](https://en.wikipedia.org/wiki/Query_string)
+or as a [payload body](https://tools.ietf.org/html/draft-ietf-httpbis-p3-payload-14#section-3.2).
+GET requests usually send a query string, while PUT/POST requests usually send the payload body:
+
+- Query string:
+
+ ```shell
+ curl --request POST "https://gitlab/api/v4/projects?name=<example-name>&description=<example-description>"
+ ```
+
+- Request payload (JSON):
+
+ ```shell
+ curl --request POST --header "Content-Type: application/json" --data '{"name":"<example-name>", "description":"<example-description"}' "https://gitlab/api/v4/projects"
+ ```
+
+URL encoded query strings have a length limitation. Requests that are too large will
+result in a `414 Request-URI Too Large` error message. This can be resolved by using
+a payload body instead.
+
## Encoding API parameters of `array` and `hash` types
We can call the API with `array` and `hash` types parameters as shown below:
diff --git a/doc/api/api_resources.md b/doc/api/api_resources.md
index 2adf06a8e95..e93dfed3b1f 100644
--- a/doc/api/api_resources.md
+++ b/doc/api/api_resources.md
@@ -129,6 +129,7 @@ The following API resources are available outside of project and group contexts
| [Geo Nodes](geo_nodes.md) **(PREMIUM ONLY)** | `/geo_nodes` |
| [Group Activity Analytics](group_activity_analytics.md) **(STARTER)** | `/analytics/group_activity/{issues_count | merge_requests_count | new_members_count }` |
| [Import repository from GitHub](import.md) | `/import/github` |
+| [Instance clusters](instance_clusters.md) | `/admin/clusters` |
| [Issues](issues.md) | `/issues` (also available for groups and projects) |
| [Issues Statistics](issues_statistics.md) | `/issues_statistics` (also available for groups and projects) |
| [Keys](keys.md) | `/keys` |
@@ -140,7 +141,7 @@ The following API resources are available outside of project and group contexts
| [Notification settings](notification_settings.md) | `/notification_settings` (also available for groups and projects) |
| [Pages domains](pages_domains.md) | `/pages/domains` (also available for projects) |
| [Projects](projects.md) | `/users/:id/projects` (also available for projects) |
-| [Project Repository Storage Moves](project_repository_storage_moves.md) | `/project_repository_storage_moves` |
+| [Project repository storage moves](project_repository_storage_moves.md) **(CORE ONLY)** | `/project_repository_storage_moves` |
| [Runners](runners.md) | `/runners` (also available for projects) |
| [Search](search.md) | `/search` (also available for groups and projects) |
| [Settings](settings.md) **(CORE ONLY)** | `/application/settings` |
diff --git a/doc/api/boards.md b/doc/api/boards.md
index 155a876e76a..a370205aa01 100644
--- a/doc/api/boards.md
+++ b/doc/api/boards.md
@@ -452,7 +452,7 @@ POST /projects/:id/boards/:board_id/lists
| `assignee_id` **(PREMIUM)** | integer | no | The ID of a user |
| `milestone_id` **(PREMIUM)** | integer | no | The ID of a milestone |
-NOTE: **Note**:
+NOTE: **Note:**
Label, assignee and milestone arguments are mutually exclusive,
that is, only one of them are accepted in a request.
Check the [Issue Board docs](../user/project/issue_board.md#summary-of-features-per-tier)
diff --git a/doc/api/container_registry.md b/doc/api/container_registry.md
index d4a4fc1a733..e4687994017 100644
--- a/doc/api/container_registry.md
+++ b/doc/api/container_registry.md
@@ -1,3 +1,9 @@
+---
+stage: Package
+group: Package
+info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers
+---
+
# Container Registry API
> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/issues/55978) in GitLab 11.8.
diff --git a/doc/api/deploy_keys.md b/doc/api/deploy_keys.md
index 1634d07768a..97b7614f932 100644
--- a/doc/api/deploy_keys.md
+++ b/doc/api/deploy_keys.md
@@ -1,3 +1,9 @@
+---
+stage: Release
+group: Progressive Delivery
+info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers
+---
+
# Deploy Keys API
## List all deploy keys
diff --git a/doc/api/deploy_tokens.md b/doc/api/deploy_tokens.md
index 16fc57eaa58..f11f88ab5c9 100644
--- a/doc/api/deploy_tokens.md
+++ b/doc/api/deploy_tokens.md
@@ -1,3 +1,9 @@
+---
+stage: Release
+group: Progressive Delivery
+info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers
+---
+
# Deploy Tokens API
## List all deploy tokens
diff --git a/doc/api/deployments.md b/doc/api/deployments.md
index 8c952ba07b1..426b3e10ecf 100644
--- a/doc/api/deployments.md
+++ b/doc/api/deployments.md
@@ -1,3 +1,10 @@
+---
+stage: Release
+group: Release Management
+info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers
+type: concepts, howto
+---
+
# Deployments API
## List project deployments
diff --git a/doc/api/discussions.md b/doc/api/discussions.md
index 1f509a7aadc..aa1a691a8f8 100644
--- a/doc/api/discussions.md
+++ b/doc/api/discussions.md
@@ -782,10 +782,14 @@ Diff comments also contain position:
"old_line": 27,
"new_line": 27,
"line_range": {
- "start_line_code": "588440f66559714280628a4f9799f0c4eb880a4a_10_10",
- "start_line_type": "new",
- "end_line_code": "588440f66559714280628a4f9799f0c4eb880a4a_11_11",
- "end_line_type": "old"
+ "start": {
+ "line_code": "588440f66559714280628a4f9799f0c4eb880a4a_10_10",
+ "type": "new",
+ },
+ "end": {
+ "line_code": "588440f66559714280628a4f9799f0c4eb880a4a_11_11",
+ "type": "old"
+ },
}
},
"resolved": false,
@@ -832,30 +836,32 @@ POST /projects/:id/merge_requests/:merge_request_iid/discussions
Parameters:
-| Attribute | Type | Required | Description |
-| --------------------------------------- | -------------- | -------- | ----------- |
-| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) |
-| `merge_request_iid` | integer | yes | The IID of a merge request |
-| `body` | string | yes | The content of the thread |
-| `created_at` | string | no | Date time string, ISO 8601 formatted, e.g. 2016-03-11T03:45:40Z (requires admin or project/group owner rights) |
-| `position` | hash | no | Position when creating a diff note |
-| `position[base_sha]` | string | yes | Base commit SHA in the source branch |
-| `position[start_sha]` | string | yes | SHA referencing commit in target branch |
-| `position[head_sha]` | string | yes | SHA referencing HEAD of this merge request |
-| `position[position_type]` | string | yes | Type of the position reference', allowed values: 'text' or 'image' |
-| `position[new_path]` | string | no | File path after change |
-| `position[new_line]` | integer | no | Line number after change (for 'text' diff notes) |
-| `position[old_path]` | string | no | File path before change |
-| `position[old_line]` | integer | no | Line number before change (for 'text' diff notes) |
-| `position[line_range]` | hash | no | Line range for a multi-line diff note |
-| `position[line_range][start_line_code]` | string | yes | Line code for the start line |
-| `position[line_range][end_line_code]` | string | yes | Line code for the end line |
-| `position[line_range][start_line_type]` | string | yes | Line type for the start line |
-| `position[line_range][end_line_type]` | string | yes | Line type for the end line |
-| `position[width]` | integer | no | Width of the image (for 'image' diff notes) |
-| `position[height]` | integer | no | Height of the image (for 'image' diff notes) |
-| `position[x]` | integer | no | X coordinate (for 'image' diff notes) |
-| `position[y]` | integer | no | Y coordinate (for 'image' diff notes) |
+| Attribute | Type | Required | Description |
+| ---------------------------------------- | -------------- | -------- | ----------- |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) |
+| `merge_request_iid` | integer | yes | The IID of a merge request |
+| `body` | string | yes | The content of the thread |
+| `created_at` | string | no | Date time string, ISO 8601 formatted, e.g. 2016-03-11T03:45:40Z (requires admin or project/group owner rights) |
+| `position` | hash | no | Position when creating a diff note |
+| `position[base_sha]` | string | yes | Base commit SHA in the source branch |
+| `position[start_sha]` | string | yes | SHA referencing commit in target branch |
+| `position[head_sha]` | string | yes | SHA referencing HEAD of this merge request |
+| `position[position_type]` | string | yes | Type of the position reference', allowed values: 'text' or 'image' |
+| `position[new_path]` | string | no | File path after change |
+| `position[new_line]` | integer | no | Line number after change (for 'text' diff notes) |
+| `position[old_path]` | string | no | File path before change |
+| `position[old_line]` | integer | no | Line number before change (for 'text' diff notes) |
+| `position[line_range]` | hash | no | Line range for a multi-line diff note |
+| `position[line_range][start]` | hash | no | Multiline note starting line |
+| `position[line_range][start][line_code]` | string | yes | Line code for the start line |
+| `position[line_range][start][type]` | string | yes | Line type for the start line |
+| `position[line_range][end]` | hash | no | Multiline note ending line |
+| `position[line_range][end][line_code]` | string | yes | Line code for the end line |
+| `position[line_range][end][type]` | string | yes | Line type for the end line |
+| `position[width]` | integer | no | Width of the image (for 'image' diff notes) |
+| `position[height]` | integer | no | Height of the image (for 'image' diff notes) |
+| `position[x]` | integer | no | X coordinate (for 'image' diff notes) |
+| `position[y]` | integer | no | Y coordinate (for 'image' diff notes) |
```shell
curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/5/merge_requests/11/discussions?body=comment"
diff --git a/doc/api/environments.md b/doc/api/environments.md
index 5f6bdc251ba..2287ec9aad2 100644
--- a/doc/api/environments.md
+++ b/doc/api/environments.md
@@ -1,3 +1,10 @@
+---
+stage: Release
+group: Release Management
+info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers
+type: concepts, howto
+---
+
# Environments API
## List environments
@@ -13,6 +20,7 @@ GET /projects/:id/environments
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
| `name` | string | no | Return the environment with this name. Mutually exclusive with `search` |
| `search` | string | no | Return list of environments matching the search criteria. Mutually exclusive with `name` |
+| `states` | string | no | List all environments that match a specific state. Accepted values: `available` or `stopped`. If no state value given, returns all environments. |
```shell
curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/1/environments?name=review%2Ffix-foo"
diff --git a/doc/api/epic_issues.md b/doc/api/epic_issues.md
index 86f88b0322f..4ab505f3627 100644
--- a/doc/api/epic_issues.md
+++ b/doc/api/epic_issues.md
@@ -4,13 +4,15 @@ group: Portfolio Management
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers
---
-# Epic Issues API **(ULTIMATE)**
+# Epic Issues API **(PREMIUM)**
Every API call to epic_issues must be authenticated.
-If a user is not a member of a group and the group is private, a `GET` request on that group will result to a `404` status code.
+If a user is not a member of a group and the group is private, a `GET` request on that group will
+result in a `404` status code.
-Epics are available only in Ultimate. If epics feature is not available a `403` status code will be returned.
+Epics are available only in GitLab [Premium and higher](https://about.gitlab.com/pricing/).
+If the Epics feature is not available, a `403` status code will be returned.
## List issues for an epic
diff --git a/doc/api/epic_links.md b/doc/api/epic_links.md
index 756a41a0680..1d54bfe01e3 100644
--- a/doc/api/epic_links.md
+++ b/doc/api/epic_links.md
@@ -9,7 +9,8 @@ Every API call to `epic_links` must be authenticated.
If a user is not a member of a group and the group is private, a `GET` request on that group will result to a `404` status code.
-Epics are available only in the [Ultimate/Gold tier](https://about.gitlab.com/pricing/). If the epics feature is not available, a `403` status code will be returned.
+Multi-level Epics are available only in GitLab [Ultimate/Gold](https://about.gitlab.com/pricing/).
+If the Multi-level Epics feature is not available, a `403` status code will be returned.
## List epics related to a given epic
diff --git a/doc/api/epics.md b/doc/api/epics.md
index a420ef4cd15..fcdbb8cea71 100644
--- a/doc/api/epics.md
+++ b/doc/api/epics.md
@@ -38,11 +38,11 @@ are paginated.
Read more on [pagination](README.md#pagination).
-CAUTION: **Deprecation**
+CAUTION: **Deprecation:**
> `reference` attribute in response is deprecated in favour of `references`.
> Introduced [GitLab 12.6](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/20354)
-NOTE: **Note**
+NOTE: **Note:**
> `references.relative` is relative to the group that the epic is being requested. When epic is fetched from its origin group
> `relative` format would be the same as `short` format and when requested cross groups it is expected to be the same as `full` format.
diff --git a/doc/api/error_tracking.md b/doc/api/error_tracking.md
index e18fbaf25c3..658480ce6fa 100644
--- a/doc/api/error_tracking.md
+++ b/doc/api/error_tracking.md
@@ -1,3 +1,9 @@
+---
+stage: Monitor
+group: Health
+info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers
+---
+
# Error Tracking settings API
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/34940) in GitLab 12.7.
diff --git a/doc/api/feature_flag_specs.md b/doc/api/feature_flag_specs.md
index 52a4864fdc5..eaa6ea36c23 100644
--- a/doc/api/feature_flag_specs.md
+++ b/doc/api/feature_flag_specs.md
@@ -8,12 +8,12 @@ info: To determine the technical writer assigned to the Stage/Group associated w
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/9566) in [GitLab Premium](https://about.gitlab.com/pricing/) 12.5.
-CAUTION: **Deprecation**
+CAUTION: **Deprecation:**
This API is deprecated and [scheduled for removal in GitLab 14.0](https://gitlab.com/gitlab-org/gitlab/-/issues/213369).
The API for creating, updating, reading and deleting Feature Flag Specs.
Automation engineers benefit from this API by being able to modify Feature Flag Specs without accessing user interface.
-To manage the [Feature Flag](../user/project/operations/feature_flags.md) resources via public API, please refer to the [Feature Flags API](feature_flags.md) document.
+To manage the [Feature Flag](../operations/feature_flags.md) resources via public API, please refer to the [Feature Flags API](feature_flags.md) document.
Users with Developer or higher [permissions](../user/permissions.md) can access Feature Flag Specs API.
@@ -166,7 +166,7 @@ POST /projects/:id/feature_flags/:name/scopes
| `name` | string | yes | The name of the feature flag. |
| `environment_scope` | string | yes | The [environment spec](../ci/environments/index.md#scoping-environments-with-specs) of the feature flag. |
| `active` | boolean | yes | Whether the spec is active. |
-| `strategies` | json | yes | The [strategies](../user/project/operations/feature_flags.md#feature-flag-strategies) of the feature flag spec. |
+| `strategies` | JSON | yes | The [strategies](../operations/feature_flags.md#feature-flag-strategies) of the feature flag spec. |
```shell
curl "https://gitlab.example.com/api/v4/projects/1/feature_flags/new_live_trace/scopes" \
@@ -249,7 +249,7 @@ PUT /projects/:id/feature_flags/:name/scopes/:environment_scope
| `name` | string | yes | The name of the feature flag. |
| `environment_scope` | string | yes | The URL-encoded [environment spec](../ci/environments/index.md#scoping-environments-with-specs) of the feature flag. |
| `active` | boolean | yes | Whether the spec is active. |
-| `strategies` | json | yes | The [strategies](../user/project/operations/feature_flags.md#feature-flag-strategies) of the feature flag spec. |
+| `strategies` | JSON | yes | The [strategies](../operations/feature_flags.md#feature-flag-strategies) of the feature flag spec. |
```shell
curl "https://gitlab.example.com/api/v4/projects/1/feature_flags/new_live_trace/scopes/production" \
diff --git a/doc/api/feature_flags.md b/doc/api/feature_flags.md
index f3af662c972..99303e23c37 100644
--- a/doc/api/feature_flags.md
+++ b/doc/api/feature_flags.md
@@ -8,10 +8,11 @@ info: To determine the technical writer assigned to the Stage/Group associated w
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/9566) in [GitLab Premium](https://about.gitlab.com/pricing/) 12.5.
-NOTE: **Note**
-This API is behind a [feature flag](../user/project/operations/feature_flags.md#feature-flag-behavior-change-in-130). If this flag is not enabled in your environment, you can use the [legacy feature flags API](feature_flags_legacy.md).
+NOTE: **Note:**
+This API is behind a [feature flag](../operations/feature_flags.md#enable-or-disable-feature-flag-strategies).
+If this flag is not enabled in your environment, you can use the [legacy feature flags API](feature_flags_legacy.md).
-API for accessing resources of [GitLab Feature Flags](../user/project/operations/feature_flags.md).
+API for accessing resources of [GitLab Feature Flags](../operations/feature_flags.md).
Users with Developer or higher [permissions](../user/permissions.md) can access Feature Flag API.
@@ -145,7 +146,7 @@ POST /projects/:id/feature_flags
| `name` | string | yes | The name of the feature flag. |
| `version` | string | yes | The version of the feature flag. Must be `new_version_flag`. Omit or set to `legacy_flag` to create a [Legacy Feature Flag](feature_flags_legacy.md). |
| `description` | string | no | The description of the feature flag. |
-| `strategies` | JSON | no | The feature flag [strategies](../user/project/operations/feature_flags.md#feature-flag-strategies). |
+| `strategies` | JSON | no | The feature flag [strategies](../operations/feature_flags.md#feature-flag-strategies). |
| `strategies:name` | JSON | no | The strategy name. |
| `strategies:parameters` | JSON | no | The strategy parameters. |
| `strategies:scopes` | JSON | no | The scopes for the strategy. |
@@ -203,7 +204,7 @@ PUT /projects/:id/feature_flags/:name
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding). |
| `name` | string | yes | The name of the feature flag. |
| `description` | string | no | The description of the feature flag. |
-| `strategies` | JSON | no | The feature flag [strategies](../user/project/operations/feature_flags.md#feature-flag-strategies). |
+| `strategies` | JSON | no | The feature flag [strategies](../operations/feature_flags.md#feature-flag-strategies). |
| `strategies:id` | JSON | no | The feature flag strategy id. |
| `strategies:name` | JSON | no | The strategy name. |
| `strategies:parameters` | JSON | no | The strategy parameters. |
diff --git a/doc/api/feature_flags_legacy.md b/doc/api/feature_flags_legacy.md
index 30bae9c5eeb..7e4fc47a1de 100644
--- a/doc/api/feature_flags_legacy.md
+++ b/doc/api/feature_flags_legacy.md
@@ -8,10 +8,10 @@ info: To determine the technical writer assigned to the Stage/Group associated w
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/9566) in [GitLab Premium](https://about.gitlab.com/pricing/) 12.5.
-CAUTION: **Deprecation**
+CAUTION: **Deprecation:**
This API is deprecated and [scheduled for removal in GitLab 14.0](https://gitlab.com/gitlab-org/gitlab/-/issues/213369). Use [this API](feature_flags.md) instead.
-API for accessing resources of [GitLab Feature Flags](../user/project/operations/feature_flags.md).
+API for accessing resources of [GitLab Feature Flags](../operations/feature_flags.md).
Users with Developer or higher [permissions](../user/permissions.md) can access Feature Flag API.
@@ -166,7 +166,7 @@ POST /projects/:id/feature_flags
| `scopes` | JSON | no | The feature flag specs of the feature flag. |
| `scopes:environment_scope` | string | no | The environment spec. |
| `scopes:active` | boolean | no | Whether the spec is active. |
-| `scopes:strategies` | JSON | no | The [strategies](../user/project/operations/feature_flags.md#feature-flag-strategies) of the feature flag spec. |
+| `scopes:strategies` | JSON | no | The [strategies](../operations/feature_flags.md#feature-flag-strategies) of the feature flag spec. |
```shell
curl https://gitlab.example.com/api/v4/projects/1/feature_flags \
diff --git a/doc/api/freeze_periods.md b/doc/api/freeze_periods.md
index ee5e657c945..e6a5e69497f 100644
--- a/doc/api/freeze_periods.md
+++ b/doc/api/freeze_periods.md
@@ -1,8 +1,15 @@
+---
+stage: Release
+group: Release Management
+info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers
+type: concepts, howto
+---
+
# Freeze Periods API
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/29382) in GitLab 13.0.
-You can use the Freeze Periods API to manipulate GitLab's [Freeze Period](../user/project/releases/index.md#set-a-deploy-freeze) entries.
+You can use the Freeze Periods API to manipulate GitLab's [Freeze Period](../user/project/releases/index.md#prevent-unintentional-releases-by-setting-a-deploy-freeze) entries.
## Permissions and security
diff --git a/doc/api/geo_nodes.md b/doc/api/geo_nodes.md
index 12f785a3e3d..402170fba37 100644
--- a/doc/api/geo_nodes.md
+++ b/doc/api/geo_nodes.md
@@ -367,8 +367,9 @@ Example response:
"package_files_count": 10,
"package_files_checksummed_count": 10,
"package_files_checksum_failed_count": 0,
- "package_files_synced_count": 10,
- "package_files_failed_count": 5
+ "package_files_registry_count": 10,
+ "package_files_synced_count": 6,
+ "package_files_failed_count": 3
},
{
"geo_node_id": 2,
@@ -440,8 +441,9 @@ Example response:
"package_files_count": 10,
"package_files_checksummed_count": 10,
"package_files_checksum_failed_count": 0,
- "package_files_synced_count": 10,
- "package_files_failed_count": 5
+ "package_files_registry_count": 10,
+ "package_files_synced_count": 6,
+ "package_files_failed_count": 3
}
]
```
diff --git a/doc/api/graphql/getting_started.md b/doc/api/graphql/getting_started.md
index 901bf85ff40..bf8a2120734 100644
--- a/doc/api/graphql/getting_started.md
+++ b/doc/api/graphql/getting_started.md
@@ -147,7 +147,7 @@ Example: Let's have some tea - add a `:tea:` reaction emoji to an issue.
```graphql
mutation {
- addAwardEmoji(input: { awardableId: "gid://gitlab/Issue/27039960",
+ awardEmojiAdd(input: { awardableId: "gid://gitlab/Issue/27039960",
name: "tea"
}) {
awardEmoji {
diff --git a/doc/api/graphql/reference/gitlab_schema.graphql b/doc/api/graphql/reference/gitlab_schema.graphql
index c4bbe7d969d..2ed6bec104d 100644
--- a/doc/api/graphql/reference/gitlab_schema.graphql
+++ b/doc/api/graphql/reference/gitlab_schema.graphql
@@ -260,6 +260,11 @@ type AlertManagementAlert implements Noteable {
issueIid: ID
"""
+ URL for metrics embed for the alert
+ """
+ metricsDashboardUrl: String
+
+ """
Monitoring tool the alert came from
"""
monitoringTool: String
@@ -390,12 +395,12 @@ enum AlertManagementAlertSort {
EVENT_COUNT_DESC
"""
- Severity by ascending order
+ Severity from less critical to more critical
"""
SEVERITY_ASC
"""
- Severity by descending order
+ Severity from more critical to less critical
"""
SEVERITY_DESC
@@ -410,12 +415,12 @@ enum AlertManagementAlertSort {
STARTED_AT_DESC
"""
- Status by ascending order
+ Status by order: Ignored > Resolved > Acknowledged > Triggered
"""
STATUS_ASC
"""
- Status by descending order
+ Status by order: Triggered > Acknowledged > Resolved > Ignored
"""
STATUS_DESC
@@ -598,6 +603,61 @@ type AlertSetAssigneesPayload {
The issue created after mutation
"""
issue: Issue
+
+ """
+ The todo after mutation
+ """
+ todo: Todo
+}
+
+"""
+Autogenerated input type of AlertTodoCreate
+"""
+input AlertTodoCreateInput {
+ """
+ A unique identifier for the client performing the mutation.
+ """
+ clientMutationId: String
+
+ """
+ The iid of the alert to mutate
+ """
+ iid: String!
+
+ """
+ The project the alert to mutate is in
+ """
+ projectPath: ID!
+}
+
+"""
+Autogenerated return type of AlertTodoCreate
+"""
+type AlertTodoCreatePayload {
+ """
+ The alert after mutation
+ """
+ alert: AlertManagementAlert
+
+ """
+ A unique identifier for the client performing the mutation.
+ """
+ clientMutationId: String
+
+ """
+ Errors encountered during execution of the mutation.
+ """
+ errors: [String!]!
+
+ """
+ The issue created after mutation
+ """
+ issue: Issue
+
+ """
+ The todo after mutation
+ """
+ todo: Todo
}
"""
@@ -635,6 +695,131 @@ type AwardEmoji {
user: User!
}
+"""
+Autogenerated input type of AwardEmojiAdd
+"""
+input AwardEmojiAddInput {
+ """
+ The global id of the awardable resource
+ """
+ awardableId: ID!
+
+ """
+ A unique identifier for the client performing the mutation.
+ """
+ clientMutationId: String
+
+ """
+ The emoji name
+ """
+ name: String!
+}
+
+"""
+Autogenerated return type of AwardEmojiAdd
+"""
+type AwardEmojiAddPayload {
+ """
+ The award emoji after mutation
+ """
+ awardEmoji: AwardEmoji
+
+ """
+ A unique identifier for the client performing the mutation.
+ """
+ clientMutationId: String
+
+ """
+ Errors encountered during execution of the mutation.
+ """
+ errors: [String!]!
+}
+
+"""
+Autogenerated input type of AwardEmojiRemove
+"""
+input AwardEmojiRemoveInput {
+ """
+ The global id of the awardable resource
+ """
+ awardableId: ID!
+
+ """
+ A unique identifier for the client performing the mutation.
+ """
+ clientMutationId: String
+
+ """
+ The emoji name
+ """
+ name: String!
+}
+
+"""
+Autogenerated return type of AwardEmojiRemove
+"""
+type AwardEmojiRemovePayload {
+ """
+ The award emoji after mutation
+ """
+ awardEmoji: AwardEmoji
+
+ """
+ A unique identifier for the client performing the mutation.
+ """
+ clientMutationId: String
+
+ """
+ Errors encountered during execution of the mutation.
+ """
+ errors: [String!]!
+}
+
+"""
+Autogenerated input type of AwardEmojiToggle
+"""
+input AwardEmojiToggleInput {
+ """
+ The global id of the awardable resource
+ """
+ awardableId: ID!
+
+ """
+ A unique identifier for the client performing the mutation.
+ """
+ clientMutationId: String
+
+ """
+ The emoji name
+ """
+ name: String!
+}
+
+"""
+Autogenerated return type of AwardEmojiToggle
+"""
+type AwardEmojiTogglePayload {
+ """
+ The award emoji after mutation
+ """
+ awardEmoji: AwardEmoji
+
+ """
+ A unique identifier for the client performing the mutation.
+ """
+ clientMutationId: String
+
+ """
+ Errors encountered during execution of the mutation.
+ """
+ errors: [String!]!
+
+ """
+ Indicates the status of the emoji. True if the toggle awarded the emoji, and false if the toggle removed the emoji.
+ """
+ toggledOn: Boolean!
+}
+
type BaseService implements Service {
"""
Indicates if the service is active
@@ -664,6 +849,11 @@ type Blob implements Entry {
lfsOid: String
"""
+ Blob mode in numeric format
+ """
+ mode: String
+
+ """
Name of the entry
"""
name: String!
@@ -1222,6 +1412,51 @@ enum CommitEncoding {
}
"""
+Represents a ComplianceFramework associated with a Project
+"""
+type ComplianceFramework {
+ """
+ Name of the compliance framework
+ """
+ name: ProjectSettingEnum!
+}
+
+"""
+The connection type for ComplianceFramework.
+"""
+type ComplianceFrameworkConnection {
+ """
+ A list of edges.
+ """
+ edges: [ComplianceFrameworkEdge]
+
+ """
+ A list of nodes.
+ """
+ nodes: [ComplianceFramework]
+
+ """
+ Information to aid in pagination.
+ """
+ pageInfo: PageInfo!
+}
+
+"""
+An edge in a connection.
+"""
+type ComplianceFrameworkEdge {
+ """
+ A cursor for use in pagination.
+ """
+ cursor: String!
+
+ """
+ The item at the end of the edge.
+ """
+ node: ComplianceFramework
+}
+
+"""
A tag expiration policy designed to keep only the images that matter most
"""
type ContainerExpirationPolicy {
@@ -1248,12 +1483,12 @@ type ContainerExpirationPolicy {
"""
Tags with names matching this regex pattern will expire
"""
- nameRegex: String
+ nameRegex: UntrustedRegexp
"""
Tags with names matching this regex pattern will be preserved
"""
- nameRegexKeep: String
+ nameRegexKeep: UntrustedRegexp
"""
Next time that this container expiration policy will get executed
@@ -1395,6 +1630,11 @@ type CreateAlertIssuePayload {
The issue created after mutation
"""
issue: Issue
+
+ """
+ The todo after mutation
+ """
+ todo: Todo
}
"""
@@ -1517,6 +1757,11 @@ input CreateDiffNoteInput {
clientMutationId: String
"""
+ The confidentiality flag of a note. Default is false.
+ """
+ confidential: Boolean
+
+ """
The global id of the resource to add a note to
"""
noteableId: ID!
@@ -1642,6 +1887,11 @@ input CreateImageDiffNoteInput {
clientMutationId: String
"""
+ The confidentiality flag of a note. Default is false.
+ """
+ confidential: Boolean
+
+ """
The global id of the resource to add a note to
"""
noteableId: ID!
@@ -1742,6 +1992,11 @@ input CreateNoteInput {
clientMutationId: String
"""
+ The confidentiality flag of a note. Default is false.
+ """
+ confidential: Boolean
+
+ """
The global id of the discussion this note is in reply to
"""
discussionId: ID
@@ -1824,7 +2079,7 @@ input CreateSnippetInput {
"""
Content of the snippet
"""
- content: String!
+ content: String
"""
Description of the snippet
@@ -1837,6 +2092,11 @@ input CreateSnippetInput {
fileName: String
"""
+ The snippet files to create
+ """
+ files: [SnippetFileInputType!]
+
+ """
The project full path the snippet is associated with
"""
projectPath: ID
@@ -1885,6 +2145,51 @@ enum DastScanTypeEnum {
}
"""
+Autogenerated input type of DastSiteProfileCreate
+"""
+input DastSiteProfileCreateInput {
+ """
+ A unique identifier for the client performing the mutation.
+ """
+ clientMutationId: String
+
+ """
+ The project the site profile belongs to.
+ """
+ fullPath: ID!
+
+ """
+ The name of the site profile.
+ """
+ profileName: String!
+
+ """
+ The URL of the target to be scanned.
+ """
+ targetUrl: String
+}
+
+"""
+Autogenerated return type of DastSiteProfileCreate
+"""
+type DastSiteProfileCreatePayload {
+ """
+ A unique identifier for the client performing the mutation.
+ """
+ clientMutationId: String
+
+ """
+ Errors encountered during execution of the mutation.
+ """
+ errors: [String!]!
+
+ """
+ ID of the site profile.
+ """
+ id: ID
+}
+
+"""
Autogenerated input type of DeleteAnnotation
"""
input DeleteAnnotationInput {
@@ -2946,6 +3251,51 @@ type DiffRefs {
startSha: String!
}
+"""
+Changes to a single file
+"""
+type DiffStats {
+ """
+ Number of lines added to this file
+ """
+ additions: Int!
+
+ """
+ Number of lines deleted from this file
+ """
+ deletions: Int!
+
+ """
+ File path, relative to repository root
+ """
+ path: String!
+}
+
+"""
+Aggregated summary of changes
+"""
+type DiffStatsSummary {
+ """
+ Number of lines added
+ """
+ additions: Int!
+
+ """
+ Number of lines changed
+ """
+ changes: Int!
+
+ """
+ Number of lines deleted
+ """
+ deletions: Int!
+
+ """
+ Number of files changed
+ """
+ fileCount: Int!
+}
+
type Discussion implements ResolvableInterface {
"""
Timestamp of the discussion's creation
@@ -3294,7 +3644,7 @@ type Epic implements Noteable {
last: Int
"""
- Filter epics by title and description
+ Search query for epic title or description
"""
search: String
@@ -4081,6 +4431,11 @@ The connection type for EpicIssue.
"""
type EpicIssueConnection {
"""
+ Total count of collection
+ """
+ count: Int!
+
+ """
A list of edges.
"""
edges: [EpicIssueEdge]
@@ -4571,7 +4926,7 @@ type Group {
labelName: [String!]
"""
- Filter epics by title and description
+ Search query for epic title or description
"""
search: String
@@ -4648,7 +5003,7 @@ type Group {
last: Int
"""
- Filter epics by title and description
+ Search query for epic title or description
"""
search: String
@@ -4754,6 +5109,11 @@ type Group {
iids: [String!]
"""
+ Iterations applied to the issue
+ """
+ iterationId: [ID]
+
+ """
Labels applied to this issue
"""
labelName: [String]
@@ -4769,7 +5129,7 @@ type Group {
milestoneTitle: [String]
"""
- Search query for finding issues by title or description
+ Search query for issue title or description
"""
search: String
@@ -4820,6 +5180,21 @@ type Group {
first: Int
"""
+ The ID of the Iteration to look up
+ """
+ id: ID
+
+ """
+ The internal ID of the Iteration to look up
+ """
+ iid: ID
+
+ """
+ Whether to include ancestor iterations. Defaults to true
+ """
+ includeAncestors: Boolean
+
+ """
Returns the last _n_ elements from the list.
"""
last: Int
@@ -5014,11 +5389,21 @@ type Group {
shareWithGroupLock: Boolean
"""
+ Total storage limit of the root namespace in bytes
+ """
+ storageSizeLimit: Float
+
+ """
The permission level required to create subgroups within the group
"""
subgroupCreationLevel: String
"""
+ Date until the temporary storage increase is active
+ """
+ temporaryStorageIncreaseEndsOn: Time
+
+ """
Time logged in issues by group members
"""
timelogs(
@@ -5113,6 +5498,11 @@ type Group {
reportType: [VulnerabilityReportType!]
"""
+ Filter vulnerabilities by scanner
+ """
+ scanner: [String!]
+
+ """
Filter vulnerabilities by severity
"""
severity: [VulnerabilitySeverity!]
@@ -5159,6 +5549,31 @@ type Group {
): VulnerabilitiesCountByDayAndSeverityConnection
"""
+ Vulnerability scanners reported on the project vulnerabilties of the group and its subgroups
+ """
+ vulnerabilityScanners(
+ """
+ Returns the elements in the list that come after the specified cursor.
+ """
+ after: String
+
+ """
+ Returns the elements in the list that come before the specified cursor.
+ """
+ before: String
+
+ """
+ Returns the first _n_ elements from the list.
+ """
+ first: Int
+
+ """
+ Returns the last _n_ elements from the list.
+ """
+ last: Int
+ ): VulnerabilityScannerConnection
+
+ """
Web URL of the group
"""
webUrl: String!
@@ -5285,6 +5700,31 @@ type InstanceSecurityDashboard {
"""
last: Int
): ProjectConnection!
+
+ """
+ Vulnerability scanners reported on the vulnerabilties from projects selected in Instance Security Dashboard
+ """
+ vulnerabilityScanners(
+ """
+ Returns the elements in the list that come after the specified cursor.
+ """
+ after: String
+
+ """
+ Returns the elements in the list that come before the specified cursor.
+ """
+ before: String
+
+ """
+ Returns the first _n_ elements from the list.
+ """
+ first: Int
+
+ """
+ Returns the last _n_ elements from the list.
+ """
+ last: Int
+ ): VulnerabilityScannerConnection
}
"""
@@ -5413,6 +5853,11 @@ type Issue implements Noteable {
healthStatus: HealthStatus
"""
+ ID of the issue
+ """
+ id: ID!
+
+ """
Internal ID of the issue
"""
iid: ID!
@@ -5593,6 +6038,11 @@ The connection type for Issue.
"""
type IssueConnection {
"""
+ Total count of collection
+ """
+ count: Int!
+
+ """
A list of edges.
"""
edges: [IssueEdge]
@@ -5804,6 +6254,51 @@ type IssueSetIterationPayload {
}
"""
+Autogenerated input type of IssueSetLocked
+"""
+input IssueSetLockedInput {
+ """
+ A unique identifier for the client performing the mutation.
+ """
+ clientMutationId: String
+
+ """
+ The iid of the issue to mutate
+ """
+ iid: String!
+
+ """
+ Whether or not to lock discussion on the issue
+ """
+ locked: Boolean!
+
+ """
+ The project the issue to mutate is in
+ """
+ projectPath: ID!
+}
+
+"""
+Autogenerated return type of IssueSetLocked
+"""
+type IssueSetLockedPayload {
+ """
+ A unique identifier for the client performing the mutation.
+ """
+ clientMutationId: String
+
+ """
+ Errors encountered during execution of the mutation.
+ """
+ errors: [String!]!
+
+ """
+ The issue after mutation
+ """
+ issue: Issue
+}
+
+"""
Autogenerated input type of IssueSetWeight
"""
input IssueSetWeightInput {
@@ -5962,6 +6457,11 @@ type Iteration {
id: ID!
"""
+ Internal ID of the iteration
+ """
+ iid: ID!
+
+ """
Timestamp of the iteration start date
"""
startDate: Time
@@ -6138,6 +6638,11 @@ input JiraImportStartInput {
The project to import the Jira project into
"""
projectPath: ID!
+
+ """
+ The mapping of Jira to GitLab users
+ """
+ usersMapping: [JiraUsersMappingInputType!]
}
"""
@@ -6259,7 +6764,7 @@ type JiraService implements Service {
active: Boolean
"""
- List of Jira projects fetched through Jira REST API
+ List of all Jira projects fetched through Jira REST API
"""
projects(
"""
@@ -6296,11 +6801,21 @@ type JiraService implements Service {
type JiraUser {
"""
- Id of the matched GitLab user
+ ID of the matched GitLab user
"""
gitlabId: Int
"""
+ Name of the matched GitLab user
+ """
+ gitlabName: String
+
+ """
+ Username of the matched GitLab user
+ """
+ gitlabUsername: String
+
+ """
Account id of the Jira user
"""
jiraAccountId: String!
@@ -6316,6 +6831,18 @@ type JiraUser {
jiraEmail: String
}
+input JiraUsersMappingInputType {
+ """
+ Id of the GitLab user
+ """
+ gitlabId: Int
+
+ """
+ Jira account id of the user
+ """
+ jiraAccountId: String!
+}
+
type Label {
"""
Background color of the label
@@ -6521,6 +7048,21 @@ type MergeRequest implements Noteable {
diffRefs: DiffRefs
"""
+ Details about which files were changed in this merge request
+ """
+ diffStats(
+ """
+ A specific file-path
+ """
+ path: String
+ ): [DiffStats!]
+
+ """
+ Summary of which files were changed in this merge request
+ """
+ diffStatsSummary: DiffStatsSummary
+
+ """
Indicates if comments on the merge request are locked to members only
"""
discussionLocked: Boolean!
@@ -7311,6 +7853,61 @@ enum MergeRequestState {
opened
}
+"""
+Autogenerated input type of MergeRequestUpdate
+"""
+input MergeRequestUpdateInput {
+ """
+ A unique identifier for the client performing the mutation.
+ """
+ clientMutationId: String
+
+ """
+ Description of the merge request (Markdown rendered as HTML for caching)
+ """
+ description: String
+
+ """
+ The iid of the merge request to mutate
+ """
+ iid: String!
+
+ """
+ The project the merge request to mutate is in
+ """
+ projectPath: ID!
+
+ """
+ Target branch of the merge request
+ """
+ targetBranch: String
+
+ """
+ Title of the merge request
+ """
+ title: String
+}
+
+"""
+Autogenerated return type of MergeRequestUpdate
+"""
+type MergeRequestUpdatePayload {
+ """
+ A unique identifier for the client performing the mutation.
+ """
+ clientMutationId: String
+
+ """
+ Errors encountered during execution of the mutation.
+ """
+ errors: [String!]!
+
+ """
+ The merge request after mutation
+ """
+ mergeRequest: MergeRequest
+}
+
type Metadata {
"""
Revision
@@ -7477,6 +8074,11 @@ type Milestone {
state: MilestoneStateEnum!
"""
+ Milestone statistics
+ """
+ stats: MilestoneStats
+
+ """
Indicates if milestone is at subgroup level
"""
subgroupMilestone: Boolean!
@@ -7538,6 +8140,21 @@ enum MilestoneStateEnum {
}
"""
+Contains statistics about a milestone
+"""
+type MilestoneStats {
+ """
+ Number of closed issues associated with the milestone
+ """
+ closedIssuesCount: Int
+
+ """
+ Total number of issues associated with the milestone
+ """
+ totalIssuesCount: Int
+}
+
+"""
The position to which the adjacent object should be moved
"""
enum MoveType {
@@ -7553,10 +8170,14 @@ enum MoveType {
}
type Mutation {
- addAwardEmoji(input: AddAwardEmojiInput!): AddAwardEmojiPayload
+ addAwardEmoji(input: AddAwardEmojiInput!): AddAwardEmojiPayload @deprecated(reason: "Use awardEmojiAdd. Deprecated in 13.2")
addProjectToSecurityDashboard(input: AddProjectToSecurityDashboardInput!): AddProjectToSecurityDashboardPayload
adminSidekiqQueuesDeleteJobs(input: AdminSidekiqQueuesDeleteJobsInput!): AdminSidekiqQueuesDeleteJobsPayload
alertSetAssignees(input: AlertSetAssigneesInput!): AlertSetAssigneesPayload
+ alertTodoCreate(input: AlertTodoCreateInput!): AlertTodoCreatePayload
+ awardEmojiAdd(input: AwardEmojiAddInput!): AwardEmojiAddPayload
+ awardEmojiRemove(input: AwardEmojiRemoveInput!): AwardEmojiRemovePayload
+ awardEmojiToggle(input: AwardEmojiToggleInput!): AwardEmojiTogglePayload
boardListUpdateLimitMetrics(input: BoardListUpdateLimitMetricsInput!): BoardListUpdateLimitMetricsPayload
commitCreate(input: CommitCreateInput!): CommitCreatePayload
createAlertIssue(input: CreateAlertIssueInput!): CreateAlertIssuePayload
@@ -7569,6 +8190,7 @@ type Mutation {
createNote(input: CreateNoteInput!): CreateNotePayload
createRequirement(input: CreateRequirementInput!): CreateRequirementPayload
createSnippet(input: CreateSnippetInput!): CreateSnippetPayload
+ dastSiteProfileCreate(input: DastSiteProfileCreateInput!): DastSiteProfileCreatePayload
deleteAnnotation(input: DeleteAnnotationInput!): DeleteAnnotationPayload
designManagementDelete(input: DesignManagementDeleteInput!): DesignManagementDeletePayload
designManagementUpload(input: DesignManagementUploadInput!): DesignManagementUploadPayload
@@ -7586,6 +8208,7 @@ type Mutation {
issueSetConfidential(input: IssueSetConfidentialInput!): IssueSetConfidentialPayload
issueSetDueDate(input: IssueSetDueDateInput!): IssueSetDueDatePayload
issueSetIteration(input: IssueSetIterationInput!): IssueSetIterationPayload
+ issueSetLocked(input: IssueSetLockedInput!): IssueSetLockedPayload
issueSetWeight(input: IssueSetWeightInput!): IssueSetWeightPayload
jiraImportStart(input: JiraImportStartInput!): JiraImportStartPayload
jiraImportUsers(input: JiraImportUsersInput!): JiraImportUsersPayload
@@ -7597,14 +8220,19 @@ type Mutation {
mergeRequestSetMilestone(input: MergeRequestSetMilestoneInput!): MergeRequestSetMilestonePayload
mergeRequestSetSubscription(input: MergeRequestSetSubscriptionInput!): MergeRequestSetSubscriptionPayload
mergeRequestSetWip(input: MergeRequestSetWipInput!): MergeRequestSetWipPayload
- removeAwardEmoji(input: RemoveAwardEmojiInput!): RemoveAwardEmojiPayload
+
+ """
+ Update attributes of a merge request
+ """
+ mergeRequestUpdate(input: MergeRequestUpdateInput!): MergeRequestUpdatePayload
+ removeAwardEmoji(input: RemoveAwardEmojiInput!): RemoveAwardEmojiPayload @deprecated(reason: "Use awardEmojiRemove. Deprecated in 13.2")
removeProjectFromSecurityDashboard(input: RemoveProjectFromSecurityDashboardInput!): RemoveProjectFromSecurityDashboardPayload
runDastScan(input: RunDASTScanInput!): RunDASTScanPayload
todoMarkDone(input: TodoMarkDoneInput!): TodoMarkDonePayload
todoRestore(input: TodoRestoreInput!): TodoRestorePayload
todoRestoreMany(input: TodoRestoreManyInput!): TodoRestoreManyPayload
todosMarkAllDone(input: TodosMarkAllDoneInput!): TodosMarkAllDonePayload
- toggleAwardEmoji(input: ToggleAwardEmojiInput!): ToggleAwardEmojiPayload
+ toggleAwardEmoji(input: ToggleAwardEmojiInput!): ToggleAwardEmojiPayload @deprecated(reason: "Use awardEmojiToggle. Deprecated in 13.2")
updateAlertStatus(input: UpdateAlertStatusInput!): UpdateAlertStatusPayload
updateContainerExpirationPolicy(input: UpdateContainerExpirationPolicyInput!): UpdateContainerExpirationPolicyPayload
updateEpic(input: UpdateEpicInput!): UpdateEpicPayload
@@ -7616,6 +8244,7 @@ type Mutation {
"""
updateImageDiffNote(input: UpdateImageDiffNoteInput!): UpdateImageDiffNotePayload
updateIssue(input: UpdateIssueInput!): UpdateIssuePayload
+ updateIteration(input: UpdateIterationInput!): UpdateIterationPayload
"""
Updates a Note. If the body of the Note contains only quick actions, the Note
@@ -7733,6 +8362,16 @@ type Namespace {
rootStorageStatistics: RootStorageStatistics
"""
+ Total storage limit of the root namespace in bytes
+ """
+ storageSizeLimit: Float
+
+ """
+ Date until the temporary storage increase is active
+ """
+ temporaryStorageIncreaseEndsOn: Time
+
+ """
Visibility of the namespace
"""
visibility: String
@@ -7845,6 +8484,11 @@ type Note implements ResolvableInterface {
system: Boolean!
"""
+ Name of the icon corresponding to a system note
+ """
+ systemNoteIconName: String
+
+ """
Timestamp of the note's last activity
"""
updatedAt: Time!
@@ -8463,6 +9107,31 @@ type Project {
): BoardConnection
"""
+ Compliance frameworks associated with the project
+ """
+ complianceFrameworks(
+ """
+ Returns the elements in the list that come after the specified cursor.
+ """
+ after: String
+
+ """
+ Returns the elements in the list that come before the specified cursor.
+ """
+ before: String
+
+ """
+ Returns the first _n_ elements from the list.
+ """
+ first: Int
+
+ """
+ Returns the last _n_ elements from the list.
+ """
+ last: Int
+ ): ComplianceFrameworkConnection
+
+ """
The container expiration policy of the project
"""
containerExpirationPolicy: ContainerExpirationPolicy
@@ -8517,7 +9186,7 @@ type Project {
name: String
"""
- Search query
+ Search query for environment name
"""
search: String
@@ -8607,6 +9276,11 @@ type Project {
iids: [String!]
"""
+ Iterations applied to the issue
+ """
+ iterationId: [ID]
+
+ """
Labels applied to this issue
"""
labelName: [String]
@@ -8617,7 +9291,7 @@ type Project {
milestoneTitle: [String]
"""
- Search query for finding issues by title or description
+ Search query for issue title or description
"""
search: String
@@ -8702,6 +9376,11 @@ type Project {
iids: [String!]
"""
+ Iterations applied to the issue
+ """
+ iterationId: [ID]
+
+ """
Labels applied to this issue
"""
labelName: [String]
@@ -8717,7 +9396,7 @@ type Project {
milestoneTitle: [String]
"""
- Search query for finding issues by title or description
+ Search query for issue title or description
"""
search: String
@@ -9056,7 +9735,7 @@ type Project {
publicJobs: Boolean
"""
- A single release of the project. Available only when feature flag `graphql_release_data` is enabled
+ A single release of the project
"""
release(
"""
@@ -9066,7 +9745,7 @@ type Project {
): Release
"""
- Releases of the project. Available only when feature flag `graphql_release_data` is enabled
+ Releases of the project
"""
releases(
"""
@@ -9125,7 +9804,7 @@ type Project {
iids: [ID!]
"""
- Filter requirements by title search
+ Search query for requirement title
"""
search: String
@@ -9185,7 +9864,7 @@ type Project {
last: Int
"""
- Filter requirements by title search
+ Search query for requirement title
"""
search: String
@@ -9201,6 +9880,16 @@ type Project {
): RequirementConnection
"""
+ SAST CI configuration for the project
+ """
+ sastCiConfiguration: SastCiConfiguration
+
+ """
+ Information about security analyzers used in the project
+ """
+ securityScanners: SecurityScanners
+
+ """
Detailed version of a Sentry error on the project
"""
sentryDetailedError(
@@ -9375,6 +10064,11 @@ type Project {
reportType: [VulnerabilityReportType!]
"""
+ Filter vulnerabilities by scanner
+ """
+ scanner: [String!]
+
+ """
Filter vulnerabilities by severity
"""
severity: [VulnerabilitySeverity!]
@@ -9386,6 +10080,31 @@ type Project {
): VulnerabilityConnection
"""
+ Vulnerability scanners reported on the project vulnerabilties
+ """
+ vulnerabilityScanners(
+ """
+ Returns the elements in the list that come after the specified cursor.
+ """
+ after: String
+
+ """
+ Returns the elements in the list that come before the specified cursor.
+ """
+ before: String
+
+ """
+ Returns the first _n_ elements from the list.
+ """
+ first: Int
+
+ """
+ Returns the last _n_ elements from the list.
+ """
+ last: Int
+ ): VulnerabilityScannerConnection
+
+ """
Counts for each severity of vulnerability of the project
"""
vulnerabilitySeveritiesCount: VulnerabilitySeveritiesCount
@@ -9733,6 +10452,17 @@ type ProjectPermissions {
uploadFile: Boolean!
}
+"""
+Names of compliance frameworks that can be assigned to a Project
+"""
+enum ProjectSettingEnum {
+ gdpr
+ hipaa
+ pci_dss
+ soc_2
+ sox
+}
+
type ProjectStatistics {
"""
Build artifacts size of the project
@@ -9760,6 +10490,11 @@ type ProjectStatistics {
repositorySize: Float!
"""
+ Snippets size of the project
+ """
+ snippetsSize: Float
+
+ """
Storage size of the project
"""
storageSize: Float!
@@ -9871,7 +10606,7 @@ type Query {
membership: Boolean
"""
- Search criteria
+ Search query for project name, path, or description
"""
search: String
): ProjectConnection
@@ -9932,7 +10667,7 @@ type Query {
): SnippetConnection
"""
- Find a user on this instance
+ Find a user
"""
user(
"""
@@ -10021,6 +10756,11 @@ type Query {
reportType: [VulnerabilityReportType!]
"""
+ Filter vulnerabilities by scanner
+ """
+ scanner: [String!]
+
+ """
Filter vulnerabilities by severity
"""
severity: [VulnerabilitySeverity!]
@@ -10092,6 +10832,9 @@ enum RegistryState {
SYNCED
}
+"""
+Represents a release
+"""
type Release {
"""
Assets of the release
@@ -10149,6 +10892,11 @@ type Release {
): ReleaseEvidenceConnection
"""
+ Links of the release
+ """
+ links: ReleaseLinks
+
+ """
Milestones associated to the release
"""
milestones(
@@ -10186,7 +10934,7 @@ type Release {
"""
Name of the tag associated with the release
"""
- tagName: String!
+ tagName: String
"""
Relative web path to the tag associated with the release
@@ -10194,11 +10942,104 @@ type Release {
tagPath: String
}
+"""
+Represents an asset link associated with a release
+"""
+type ReleaseAssetLink {
+ """
+ Indicates the link points to an external resource
+ """
+ external: Boolean
+
+ """
+ ID of the link
+ """
+ id: ID!
+
+ """
+ Type of the link: `other`, `runbook`, `image`, `package`; defaults to `other`
+ """
+ linkType: ReleaseAssetLinkType
+
+ """
+ Name of the link
+ """
+ name: String
+
+ """
+ URL of the link
+ """
+ url: String
+}
+
+"""
+The connection type for ReleaseAssetLink.
+"""
+type ReleaseAssetLinkConnection {
+ """
+ A list of edges.
+ """
+ edges: [ReleaseAssetLinkEdge]
+
+ """
+ A list of nodes.
+ """
+ nodes: [ReleaseAssetLink]
+
+ """
+ Information to aid in pagination.
+ """
+ pageInfo: PageInfo!
+}
+
+"""
+An edge in a connection.
+"""
+type ReleaseAssetLinkEdge {
+ """
+ A cursor for use in pagination.
+ """
+ cursor: String!
+
+ """
+ The item at the end of the edge.
+ """
+ node: ReleaseAssetLink
+}
+
+"""
+Type of the link: `other`, `runbook`, `image`, `package`; defaults to `other`
+"""
+enum ReleaseAssetLinkType {
+ """
+ Image link type
+ """
+ IMAGE
+
+ """
+ Other link type
+ """
+ OTHER
+
+ """
+ Package link type
+ """
+ PACKAGE
+
+ """
+ Runbook link type
+ """
+ RUNBOOK
+}
+
+"""
+A container for all assets associated with a release
+"""
type ReleaseAssets {
"""
Number of assets of the release
"""
- assetsCount: Int
+ count: Int
"""
Asset links of the release
@@ -10223,7 +11064,7 @@ type ReleaseAssets {
Returns the last _n_ elements from the list.
"""
last: Int
- ): ReleaseLinkConnection
+ ): ReleaseAssetLinkConnection
"""
Sources of the release
@@ -10346,93 +11187,31 @@ type ReleaseEvidenceEdge {
node: ReleaseEvidence
}
-type ReleaseLink {
+type ReleaseLinks {
"""
- Indicates the link points to an external resource
+ HTTP URL of the release's edit page
"""
- external: Boolean
+ editUrl: String
"""
- ID of the link
+ HTTP URL of the issues page filtered by this release
"""
- id: ID!
+ issuesUrl: String
"""
- Type of the link: `other`, `runbook`, `image`, `package`; defaults to `other`
+ HTTP URL of the merge request page filtered by this release
"""
- linkType: ReleaseLinkType
+ mergeRequestsUrl: String
"""
- Name of the link
+ HTTP URL of the release
"""
- name: String
-
- """
- URL of the link
- """
- url: String
+ selfUrl: String
}
"""
-The connection type for ReleaseLink.
-"""
-type ReleaseLinkConnection {
- """
- A list of edges.
- """
- edges: [ReleaseLinkEdge]
-
- """
- A list of nodes.
- """
- nodes: [ReleaseLink]
-
- """
- Information to aid in pagination.
- """
- pageInfo: PageInfo!
-}
-
-"""
-An edge in a connection.
+Represents the source code attached to a release in a particular format
"""
-type ReleaseLinkEdge {
- """
- A cursor for use in pagination.
- """
- cursor: String!
-
- """
- The item at the end of the edge.
- """
- node: ReleaseLink
-}
-
-"""
-Type of the link: `other`, `runbook`, `image`, `package`; defaults to `other`
-"""
-enum ReleaseLinkType {
- """
- Image link type
- """
- IMAGE
-
- """
- Other link type
- """
- OTHER
-
- """
- Package link type
- """
- PACKAGE
-
- """
- Runbook link type
- """
- RUNBOOK
-}
-
type ReleaseSource {
"""
Format of the source
@@ -10799,6 +11578,11 @@ type RootStorageStatistics {
repositorySize: Float!
"""
+ The snippets size in bytes
+ """
+ snippetsSize: Float!
+
+ """
The total storage in bytes
"""
storageSize: Float!
@@ -10860,6 +11644,341 @@ type RunDASTScanPayload {
}
"""
+Represents a CI configuration of SAST
+"""
+type SastCiConfiguration {
+ """
+ List of analyzers entities attached to SAST configuration.
+ """
+ analyzers(
+ """
+ Returns the elements in the list that come after the specified cursor.
+ """
+ after: String
+
+ """
+ Returns the elements in the list that come before the specified cursor.
+ """
+ before: String
+
+ """
+ Returns the first _n_ elements from the list.
+ """
+ first: Int
+
+ """
+ Returns the last _n_ elements from the list.
+ """
+ last: Int
+ ): SastCiConfigurationAnalyzersEntityConnection
+
+ """
+ List of global entities related to SAST configuration.
+ """
+ global(
+ """
+ Returns the elements in the list that come after the specified cursor.
+ """
+ after: String
+
+ """
+ Returns the elements in the list that come before the specified cursor.
+ """
+ before: String
+
+ """
+ Returns the first _n_ elements from the list.
+ """
+ first: Int
+
+ """
+ Returns the last _n_ elements from the list.
+ """
+ last: Int
+ ): SastCiConfigurationEntityConnection
+
+ """
+ List of pipeline entities related to SAST configuration.
+ """
+ pipeline(
+ """
+ Returns the elements in the list that come after the specified cursor.
+ """
+ after: String
+
+ """
+ Returns the elements in the list that come before the specified cursor.
+ """
+ before: String
+
+ """
+ Returns the first _n_ elements from the list.
+ """
+ first: Int
+
+ """
+ Returns the last _n_ elements from the list.
+ """
+ last: Int
+ ): SastCiConfigurationEntityConnection
+}
+
+"""
+Represents an analyzer entity in SAST CI configuration
+"""
+type SastCiConfigurationAnalyzersEntity {
+ """
+ Analyzer description that is displayed on the form.
+ """
+ description: String
+
+ """
+ Indicates whether an analyzer is enabled.
+ """
+ enabled: Boolean
+
+ """
+ Analyzer label used in the config UI.
+ """
+ label: String
+
+ """
+ Name of the analyzer.
+ """
+ name: String
+}
+
+"""
+The connection type for SastCiConfigurationAnalyzersEntity.
+"""
+type SastCiConfigurationAnalyzersEntityConnection {
+ """
+ A list of edges.
+ """
+ edges: [SastCiConfigurationAnalyzersEntityEdge]
+
+ """
+ A list of nodes.
+ """
+ nodes: [SastCiConfigurationAnalyzersEntity]
+
+ """
+ Information to aid in pagination.
+ """
+ pageInfo: PageInfo!
+}
+
+"""
+An edge in a connection.
+"""
+type SastCiConfigurationAnalyzersEntityEdge {
+ """
+ A cursor for use in pagination.
+ """
+ cursor: String!
+
+ """
+ The item at the end of the edge.
+ """
+ node: SastCiConfigurationAnalyzersEntity
+}
+
+"""
+Represents an entity in SAST CI configuration
+"""
+type SastCiConfigurationEntity {
+ """
+ Default value that is used if value is empty.
+ """
+ defaultValue: String
+
+ """
+ Entity description that is displayed on the form.
+ """
+ description: String
+
+ """
+ CI keyword of entity.
+ """
+ field: String
+
+ """
+ Label for entity used in the form.
+ """
+ label: String
+
+ """
+ Different possible values of the field.
+ """
+ options(
+ """
+ Returns the elements in the list that come after the specified cursor.
+ """
+ after: String
+
+ """
+ Returns the elements in the list that come before the specified cursor.
+ """
+ before: String
+
+ """
+ Returns the first _n_ elements from the list.
+ """
+ first: Int
+
+ """
+ Returns the last _n_ elements from the list.
+ """
+ last: Int
+ ): SastCiConfigurationOptionsEntityConnection
+
+ """
+ Type of the field value.
+ """
+ type: String
+
+ """
+ Current value of the entity.
+ """
+ value: String
+}
+
+"""
+The connection type for SastCiConfigurationEntity.
+"""
+type SastCiConfigurationEntityConnection {
+ """
+ A list of edges.
+ """
+ edges: [SastCiConfigurationEntityEdge]
+
+ """
+ A list of nodes.
+ """
+ nodes: [SastCiConfigurationEntity]
+
+ """
+ Information to aid in pagination.
+ """
+ pageInfo: PageInfo!
+}
+
+"""
+An edge in a connection.
+"""
+type SastCiConfigurationEntityEdge {
+ """
+ A cursor for use in pagination.
+ """
+ cursor: String!
+
+ """
+ The item at the end of the edge.
+ """
+ node: SastCiConfigurationEntity
+}
+
+"""
+Represents an entity for options in SAST CI configuration
+"""
+type SastCiConfigurationOptionsEntity {
+ """
+ Label of option entity.
+ """
+ label: String
+
+ """
+ Value of option entity.
+ """
+ value: String
+}
+
+"""
+The connection type for SastCiConfigurationOptionsEntity.
+"""
+type SastCiConfigurationOptionsEntityConnection {
+ """
+ A list of edges.
+ """
+ edges: [SastCiConfigurationOptionsEntityEdge]
+
+ """
+ A list of nodes.
+ """
+ nodes: [SastCiConfigurationOptionsEntity]
+
+ """
+ Information to aid in pagination.
+ """
+ pageInfo: PageInfo!
+}
+
+"""
+An edge in a connection.
+"""
+type SastCiConfigurationOptionsEntityEdge {
+ """
+ A cursor for use in pagination.
+ """
+ cursor: String!
+
+ """
+ The item at the end of the edge.
+ """
+ node: SastCiConfigurationOptionsEntity
+}
+
+"""
+Represents a resource scanned by a security scan
+"""
+type ScannedResource {
+ """
+ The HTTP request method used to access the URL
+ """
+ requestMethod: String
+
+ """
+ The URL scanned by the scanner
+ """
+ url: String
+}
+
+"""
+The connection type for ScannedResource.
+"""
+type ScannedResourceConnection {
+ """
+ A list of edges.
+ """
+ edges: [ScannedResourceEdge]
+
+ """
+ A list of nodes.
+ """
+ nodes: [ScannedResource]
+
+ """
+ Information to aid in pagination.
+ """
+ pageInfo: PageInfo!
+}
+
+"""
+An edge in a connection.
+"""
+type ScannedResourceEdge {
+ """
+ A cursor for use in pagination.
+ """
+ cursor: String!
+
+ """
+ The item at the end of the edge.
+ """
+ node: ScannedResource
+}
+
+"""
Represents summary of a security report
"""
type SecurityReportSummary {
@@ -10869,6 +11988,11 @@ type SecurityReportSummary {
containerScanning: SecurityReportSummarySection
"""
+ Aggregated counts for the coverage_fuzzing scan
+ """
+ coverageFuzzing: SecurityReportSummarySection
+
+ """
Aggregated counts for the dast scan
"""
dast: SecurityReportSummarySection
@@ -10894,17 +12018,78 @@ Represents a section of a summary of a security report
"""
type SecurityReportSummarySection {
"""
+ A list of the first 20 scanned resources
+ """
+ scannedResources(
+ """
+ Returns the elements in the list that come after the specified cursor.
+ """
+ after: String
+
+ """
+ Returns the elements in the list that come before the specified cursor.
+ """
+ before: String
+
+ """
+ Returns the first _n_ elements from the list.
+ """
+ first: Int
+
+ """
+ Returns the last _n_ elements from the list.
+ """
+ last: Int
+ ): ScannedResourceConnection
+
+ """
Total number of scanned resources
"""
scannedResourcesCount: Int
"""
+ Path to download all the scanned resources in CSV format
+ """
+ scannedResourcesCsvPath: String
+
+ """
Total number of vulnerabilities
"""
vulnerabilitiesCount: Int
}
"""
+The type of the security scanner.
+"""
+enum SecurityScannerType {
+ CONTAINER_SCANNING
+ DAST
+ DEPENDENCY_SCANNING
+ SAST
+ SECRET_DETECTION
+}
+
+"""
+Represents a list of security scanners
+"""
+type SecurityScanners {
+ """
+ List of analyzers which are available for the project.
+ """
+ available: [SecurityScannerType!]
+
+ """
+ List of analyzers which are enabled for the project.
+ """
+ enabled: [SecurityScannerType!]
+
+ """
+ List of analyzers which ran successfully in the latest pipeline.
+ """
+ pipelineRun: [SecurityScannerType!]
+}
+
+"""
A Sentry error.
"""
type SentryDetailedError {
@@ -10934,11 +12119,16 @@ type SentryDetailedError {
firstReleaseLastCommit: String
"""
- Release version the error was first seen
+ Release short version the error was first seen
"""
firstReleaseShortVersion: String
"""
+ Release version the error was first seen
+ """
+ firstReleaseVersion: String
+
+ """
Timestamp when the error was first seen
"""
firstSeen: Time!
@@ -10974,11 +12164,16 @@ type SentryDetailedError {
lastReleaseLastCommit: String
"""
- Release version the error was last seen
+ Release short version the error was last seen
"""
lastReleaseShortVersion: String
"""
+ Release version the error was last seen
+ """
+ lastReleaseVersion: String
+
+ """
Timestamp when the error was last seen
"""
lastSeen: Time!
@@ -11178,7 +12373,7 @@ type SentryErrorCollection {
last: Int
"""
- Search term for the Sentry error.
+ Search query for the Sentry error details
"""
searchTerm: String
@@ -11401,6 +12596,7 @@ enum ServiceType {
BUGZILLA_SERVICE
BUILDKITE_SERVICE
CAMPFIRE_SERVICE
+ CONFLUENCE_SERVICE
CUSTOM_ISSUE_TRACKER_SERVICE
DISCORD_SERVICE
DRONE_CI_SERVICE
@@ -11710,6 +12906,41 @@ type SnippetEdge {
node: Snippet
}
+"""
+Type of a snippet file input action
+"""
+enum SnippetFileInputActionEnum {
+ create
+ delete
+ move
+ update
+}
+
+"""
+Represents an action to perform over a snippet file
+"""
+input SnippetFileInputType {
+ """
+ Type of input action
+ """
+ action: SnippetFileInputActionEnum!
+
+ """
+ Snippet file content
+ """
+ content: String
+
+ """
+ Path of the snippet file
+ """
+ filePath: String!
+
+ """
+ Previous path of the snippet file
+ """
+ previousPath: String
+}
+
type SnippetPermissions {
"""
Indicates the user can perform `admin_snippet` on this resource
@@ -11923,6 +13154,7 @@ type TestReportEdge {
State of a test report
"""
enum TestReportState {
+ FAILED
PASSED
}
@@ -12168,9 +13400,14 @@ type TodoRestoreManyPayload {
errors: [String!]!
"""
- The ids of the updated todo items
+ Updated todos
+ """
+ todos: [Todo!]!
+
+ """
+ The ids of the updated todo items. Deprecated in 13.2: Use todos
"""
- updatedIds: [ID!]!
+ updatedIds: [ID!]! @deprecated(reason: "Use todos. Deprecated in 13.2")
}
"""
@@ -12200,6 +13437,11 @@ enum TodoStateEnum {
enum TodoTargetEnum {
"""
+ An Alert
+ """
+ ALERT
+
+ """
A Commit
"""
COMMIT
@@ -12250,9 +13492,14 @@ type TodosMarkAllDonePayload {
errors: [String!]!
"""
- Ids of the updated todos
+ Updated todos
+ """
+ todos: [Todo!]!
+
+ """
+ Ids of the updated todos. Deprecated in 13.2: Use todos
"""
- updatedIds: [ID!]!
+ updatedIds: [ID!]! @deprecated(reason: "Use todos. Deprecated in 13.2")
}
"""
@@ -12463,6 +13710,11 @@ enum TypeEnum {
}
"""
+A regexp containing patterns sourced from user input
+"""
+scalar UntrustedRegexp
+
+"""
Autogenerated input type of UpdateAlertStatus
"""
input UpdateAlertStatusInput {
@@ -12510,6 +13762,11 @@ type UpdateAlertStatusPayload {
The issue created after mutation
"""
issue: Issue
+
+ """
+ The todo after mutation
+ """
+ todo: Todo
}
"""
@@ -12537,6 +13794,16 @@ input UpdateContainerExpirationPolicyInput {
keepN: ContainerExpirationPolicyKeepEnum
"""
+ Tags with names matching this regex pattern will expire
+ """
+ nameRegex: UntrustedRegexp
+
+ """
+ Tags with names matching this regex pattern will be preserved
+ """
+ nameRegexKeep: UntrustedRegexp
+
+ """
Tags older that this will expire
"""
olderThan: ContainerExpirationPolicyOlderThanEnum
@@ -12790,6 +14057,66 @@ type UpdateIssuePayload {
}
"""
+Autogenerated input type of UpdateIteration
+"""
+input UpdateIterationInput {
+ """
+ A unique identifier for the client performing the mutation.
+ """
+ clientMutationId: String
+
+ """
+ The description of the iteration
+ """
+ description: String
+
+ """
+ The end date of the iteration
+ """
+ dueDate: String
+
+ """
+ The group of the iteration
+ """
+ groupPath: ID!
+
+ """
+ The id of the iteration
+ """
+ id: ID!
+
+ """
+ The start date of the iteration
+ """
+ startDate: String
+
+ """
+ The title of the iteration
+ """
+ title: String
+}
+
+"""
+Autogenerated return type of UpdateIteration
+"""
+type UpdateIterationPayload {
+ """
+ A unique identifier for the client performing the mutation.
+ """
+ clientMutationId: String
+
+ """
+ Errors encountered during execution of the mutation.
+ """
+ errors: [String!]!
+
+ """
+ The updated iteration
+ """
+ iteration: Iteration
+}
+
+"""
Autogenerated input type of UpdateNote
"""
input UpdateNoteInput {
@@ -12904,6 +14231,11 @@ input UpdateSnippetInput {
fileName: String
"""
+ The snippet files to update
+ """
+ files: [SnippetFileInputType!]
+
+ """
The global id of the snippet to update
"""
id: ID!
@@ -13387,6 +14719,11 @@ type Vulnerability {
id: ID!
"""
+ Identifiers of the vulnerability.
+ """
+ identifiers: [VulnerabilityIdentifier!]!
+
+ """
List of issue links related to the vulnerability
"""
issueLinks(
@@ -13422,17 +14759,28 @@ type Vulnerability {
location: VulnerabilityLocation
"""
+ Primary identifier of the vulnerability.
+ """
+ primaryIdentifier: VulnerabilityIdentifier
+
+ """
The project on which the vulnerability was found
"""
project: Project
"""
Type of the security report that found the vulnerability (SAST,
- DEPENDENCY_SCANNING, CONTAINER_SCANNING, DAST, SECRET_DETECTION)
+ DEPENDENCY_SCANNING, CONTAINER_SCANNING, DAST, SECRET_DETECTION,
+ COVERAGE_FUZZING)
"""
reportType: VulnerabilityReportType
"""
+ Scanner metadata for the vulnerability.
+ """
+ scanner: VulnerabilityScanner
+
+ """
Severity of the vulnerability (INFO, UNKNOWN, LOW, MEDIUM, HIGH, CRITICAL)
"""
severity: VulnerabilitySeverity
@@ -13499,6 +14847,31 @@ type VulnerabilityEdge {
}
"""
+Represents a vulnerability identifier.
+"""
+type VulnerabilityIdentifier {
+ """
+ External ID of the vulnerability identifier
+ """
+ externalId: String
+
+ """
+ External type of the vulnerability identifier
+ """
+ externalType: String
+
+ """
+ Name of the vulnerability identifier
+ """
+ name: String
+
+ """
+ URL of the vulnerability identifier
+ """
+ url: String
+}
+
+"""
Represents an issue link of a vulnerability.
"""
type VulnerabilityIssueLink {
@@ -13736,6 +15109,7 @@ The type of the security scan that found the vulnerability.
"""
enum VulnerabilityReportType {
CONTAINER_SCANNING
+ COVERAGE_FUZZING
DAST
DEPENDENCY_SCANNING
SAST
@@ -13743,6 +15117,66 @@ enum VulnerabilityReportType {
}
"""
+Represents a vulnerability scanner.
+"""
+type VulnerabilityScanner {
+ """
+ External ID of the vulnerability scanner
+ """
+ externalId: String
+
+ """
+ Name of the vulnerability scanner
+ """
+ name: String
+
+ """
+ Type of the vulnerability report
+ """
+ reportType: VulnerabilityReportType
+
+ """
+ Vendor of the vulnerability scanner
+ """
+ vendor: String
+}
+
+"""
+The connection type for VulnerabilityScanner.
+"""
+type VulnerabilityScannerConnection {
+ """
+ A list of edges.
+ """
+ edges: [VulnerabilityScannerEdge]
+
+ """
+ A list of nodes.
+ """
+ nodes: [VulnerabilityScanner]
+
+ """
+ Information to aid in pagination.
+ """
+ pageInfo: PageInfo!
+}
+
+"""
+An edge in a connection.
+"""
+type VulnerabilityScannerEdge {
+ """
+ A cursor for use in pagination.
+ """
+ cursor: String!
+
+ """
+ The item at the end of the edge.
+ """
+ node: VulnerabilityScanner
+}
+
+"""
Represents vulnerability counts by severity
"""
type VulnerabilitySeveritiesCount {
diff --git a/doc/api/graphql/reference/gitlab_schema.json b/doc/api/graphql/reference/gitlab_schema.json
index d2bc599ff9d..80aaa4aa949 100644
--- a/doc/api/graphql/reference/gitlab_schema.json
+++ b/doc/api/graphql/reference/gitlab_schema.json
@@ -717,6 +717,20 @@
"deprecationReason": null
},
{
+ "name": "metricsDashboardUrl",
+ "description": "URL for metrics embed for the alert",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
"name": "monitoringTool",
"description": "Monitoring tool the alert came from",
"args": [
@@ -1089,25 +1103,25 @@
},
{
"name": "SEVERITY_ASC",
- "description": "Severity by ascending order",
+ "description": "Severity from less critical to more critical",
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "SEVERITY_DESC",
- "description": "Severity by descending order",
+ "description": "Severity from more critical to less critical",
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "STATUS_ASC",
- "description": "Status by ascending order",
+ "description": "Status by order: Ignored > Resolved > Acknowledged > Triggered",
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "STATUS_DESC",
- "description": "Status by descending order",
+ "description": "Status by order: Triggered > Acknowledged > Resolved > Ignored",
"isDeprecated": false,
"deprecationReason": null
}
@@ -1446,6 +1460,164 @@
},
"isDeprecated": false,
"deprecationReason": null
+ },
+ {
+ "name": "todo",
+ "description": "The todo after mutation",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "OBJECT",
+ "name": "Todo",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ }
+ ],
+ "inputFields": null,
+ "interfaces": [
+
+ ],
+ "enumValues": null,
+ "possibleTypes": null
+ },
+ {
+ "kind": "INPUT_OBJECT",
+ "name": "AlertTodoCreateInput",
+ "description": "Autogenerated input type of AlertTodoCreate",
+ "fields": null,
+ "inputFields": [
+ {
+ "name": "projectPath",
+ "description": "The project the alert to mutate is in",
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "ID",
+ "ofType": null
+ }
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "iid",
+ "description": "The iid of the alert to mutate",
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ }
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "clientMutationId",
+ "description": "A unique identifier for the client performing the mutation.",
+ "type": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ },
+ "defaultValue": null
+ }
+ ],
+ "interfaces": null,
+ "enumValues": null,
+ "possibleTypes": null
+ },
+ {
+ "kind": "OBJECT",
+ "name": "AlertTodoCreatePayload",
+ "description": "Autogenerated return type of AlertTodoCreate",
+ "fields": [
+ {
+ "name": "alert",
+ "description": "The alert after mutation",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "OBJECT",
+ "name": "AlertManagementAlert",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "clientMutationId",
+ "description": "A unique identifier for the client performing the mutation.",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "errors",
+ "description": "Errors encountered during execution of the mutation.",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "LIST",
+ "name": null,
+ "ofType": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ }
+ }
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "issue",
+ "description": "The issue created after mutation",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "OBJECT",
+ "name": "Issue",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "todo",
+ "description": "The todo after mutation",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "OBJECT",
+ "name": "Todo",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
}
],
"inputFields": null,
@@ -1577,6 +1749,372 @@
"possibleTypes": null
},
{
+ "kind": "INPUT_OBJECT",
+ "name": "AwardEmojiAddInput",
+ "description": "Autogenerated input type of AwardEmojiAdd",
+ "fields": null,
+ "inputFields": [
+ {
+ "name": "awardableId",
+ "description": "The global id of the awardable resource",
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "ID",
+ "ofType": null
+ }
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "name",
+ "description": "The emoji name",
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ }
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "clientMutationId",
+ "description": "A unique identifier for the client performing the mutation.",
+ "type": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ },
+ "defaultValue": null
+ }
+ ],
+ "interfaces": null,
+ "enumValues": null,
+ "possibleTypes": null
+ },
+ {
+ "kind": "OBJECT",
+ "name": "AwardEmojiAddPayload",
+ "description": "Autogenerated return type of AwardEmojiAdd",
+ "fields": [
+ {
+ "name": "awardEmoji",
+ "description": "The award emoji after mutation",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "OBJECT",
+ "name": "AwardEmoji",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "clientMutationId",
+ "description": "A unique identifier for the client performing the mutation.",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "errors",
+ "description": "Errors encountered during execution of the mutation.",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "LIST",
+ "name": null,
+ "ofType": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ }
+ }
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ }
+ ],
+ "inputFields": null,
+ "interfaces": [
+
+ ],
+ "enumValues": null,
+ "possibleTypes": null
+ },
+ {
+ "kind": "INPUT_OBJECT",
+ "name": "AwardEmojiRemoveInput",
+ "description": "Autogenerated input type of AwardEmojiRemove",
+ "fields": null,
+ "inputFields": [
+ {
+ "name": "awardableId",
+ "description": "The global id of the awardable resource",
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "ID",
+ "ofType": null
+ }
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "name",
+ "description": "The emoji name",
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ }
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "clientMutationId",
+ "description": "A unique identifier for the client performing the mutation.",
+ "type": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ },
+ "defaultValue": null
+ }
+ ],
+ "interfaces": null,
+ "enumValues": null,
+ "possibleTypes": null
+ },
+ {
+ "kind": "OBJECT",
+ "name": "AwardEmojiRemovePayload",
+ "description": "Autogenerated return type of AwardEmojiRemove",
+ "fields": [
+ {
+ "name": "awardEmoji",
+ "description": "The award emoji after mutation",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "OBJECT",
+ "name": "AwardEmoji",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "clientMutationId",
+ "description": "A unique identifier for the client performing the mutation.",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "errors",
+ "description": "Errors encountered during execution of the mutation.",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "LIST",
+ "name": null,
+ "ofType": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ }
+ }
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ }
+ ],
+ "inputFields": null,
+ "interfaces": [
+
+ ],
+ "enumValues": null,
+ "possibleTypes": null
+ },
+ {
+ "kind": "INPUT_OBJECT",
+ "name": "AwardEmojiToggleInput",
+ "description": "Autogenerated input type of AwardEmojiToggle",
+ "fields": null,
+ "inputFields": [
+ {
+ "name": "awardableId",
+ "description": "The global id of the awardable resource",
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "ID",
+ "ofType": null
+ }
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "name",
+ "description": "The emoji name",
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ }
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "clientMutationId",
+ "description": "A unique identifier for the client performing the mutation.",
+ "type": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ },
+ "defaultValue": null
+ }
+ ],
+ "interfaces": null,
+ "enumValues": null,
+ "possibleTypes": null
+ },
+ {
+ "kind": "OBJECT",
+ "name": "AwardEmojiTogglePayload",
+ "description": "Autogenerated return type of AwardEmojiToggle",
+ "fields": [
+ {
+ "name": "awardEmoji",
+ "description": "The award emoji after mutation",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "OBJECT",
+ "name": "AwardEmoji",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "clientMutationId",
+ "description": "A unique identifier for the client performing the mutation.",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "errors",
+ "description": "Errors encountered during execution of the mutation.",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "LIST",
+ "name": null,
+ "ofType": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ }
+ }
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "toggledOn",
+ "description": "Indicates the status of the emoji. True if the toggle awarded the emoji, and false if the toggle removed the emoji.",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "Boolean",
+ "ofType": null
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ }
+ ],
+ "inputFields": null,
+ "interfaces": [
+
+ ],
+ "enumValues": null,
+ "possibleTypes": null
+ },
+ {
"kind": "OBJECT",
"name": "BaseService",
"description": null,
@@ -1677,6 +2215,20 @@
"deprecationReason": null
},
{
+ "name": "mode",
+ "description": "Blob mode in numeric format",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
"name": "name",
"description": "Name of the entry",
"args": [
@@ -3243,6 +3795,149 @@
},
{
"kind": "OBJECT",
+ "name": "ComplianceFramework",
+ "description": "Represents a ComplianceFramework associated with a Project",
+ "fields": [
+ {
+ "name": "name",
+ "description": "Name of the compliance framework",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "ENUM",
+ "name": "ProjectSettingEnum",
+ "ofType": null
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ }
+ ],
+ "inputFields": null,
+ "interfaces": [
+
+ ],
+ "enumValues": null,
+ "possibleTypes": null
+ },
+ {
+ "kind": "OBJECT",
+ "name": "ComplianceFrameworkConnection",
+ "description": "The connection type for ComplianceFramework.",
+ "fields": [
+ {
+ "name": "edges",
+ "description": "A list of edges.",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "LIST",
+ "name": null,
+ "ofType": {
+ "kind": "OBJECT",
+ "name": "ComplianceFrameworkEdge",
+ "ofType": null
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "nodes",
+ "description": "A list of nodes.",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "LIST",
+ "name": null,
+ "ofType": {
+ "kind": "OBJECT",
+ "name": "ComplianceFramework",
+ "ofType": null
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "pageInfo",
+ "description": "Information to aid in pagination.",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "OBJECT",
+ "name": "PageInfo",
+ "ofType": null
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ }
+ ],
+ "inputFields": null,
+ "interfaces": [
+
+ ],
+ "enumValues": null,
+ "possibleTypes": null
+ },
+ {
+ "kind": "OBJECT",
+ "name": "ComplianceFrameworkEdge",
+ "description": "An edge in a connection.",
+ "fields": [
+ {
+ "name": "cursor",
+ "description": "A cursor for use in pagination.",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "node",
+ "description": "The item at the end of the edge.",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "OBJECT",
+ "name": "ComplianceFramework",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ }
+ ],
+ "inputFields": null,
+ "interfaces": [
+
+ ],
+ "enumValues": null,
+ "possibleTypes": null
+ },
+ {
+ "kind": "OBJECT",
"name": "ContainerExpirationPolicy",
"description": "A tag expiration policy designed to keep only the images that matter most",
"fields": [
@@ -3322,7 +4017,7 @@
],
"type": {
"kind": "SCALAR",
- "name": "String",
+ "name": "UntrustedRegexp",
"ofType": null
},
"isDeprecated": false,
@@ -3336,7 +4031,7 @@
],
"type": {
"kind": "SCALAR",
- "name": "String",
+ "name": "UntrustedRegexp",
"ofType": null
},
"isDeprecated": false,
@@ -3640,6 +4335,20 @@
},
"isDeprecated": false,
"deprecationReason": null
+ },
+ {
+ "name": "todo",
+ "description": "The todo after mutation",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "OBJECT",
+ "name": "Todo",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
}
],
"inputFields": null,
@@ -3974,6 +4683,16 @@
"defaultValue": null
},
{
+ "name": "confidential",
+ "description": "The confidentiality flag of a note. Default is false.",
+ "type": {
+ "kind": "SCALAR",
+ "name": "Boolean",
+ "ofType": null
+ },
+ "defaultValue": null
+ },
+ {
"name": "position",
"description": "The position of this note on a diff",
"type": {
@@ -4312,6 +5031,16 @@
"defaultValue": null
},
{
+ "name": "confidential",
+ "description": "The confidentiality flag of a note. Default is false.",
+ "type": {
+ "kind": "SCALAR",
+ "name": "Boolean",
+ "ofType": null
+ },
+ "defaultValue": null
+ },
+ {
"name": "position",
"description": "The position of this note on a diff",
"type": {
@@ -4584,6 +5313,16 @@
"defaultValue": null
},
{
+ "name": "confidential",
+ "description": "The confidentiality flag of a note. Default is false.",
+ "type": {
+ "kind": "SCALAR",
+ "name": "Boolean",
+ "ofType": null
+ },
+ "defaultValue": null
+ },
+ {
"name": "discussionId",
"description": "The global id of the discussion this note is in reply to",
"type": {
@@ -4825,13 +5564,9 @@
"name": "content",
"description": "Content of the snippet",
"type": {
- "kind": "NON_NULL",
- "name": null,
- "ofType": {
- "kind": "SCALAR",
- "name": "String",
- "ofType": null
- }
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
},
"defaultValue": null
},
@@ -4888,6 +5623,24 @@
"defaultValue": null
},
{
+ "name": "files",
+ "description": "The snippet files to create",
+ "type": {
+ "kind": "LIST",
+ "name": null,
+ "ofType": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "INPUT_OBJECT",
+ "name": "SnippetFileInputType",
+ "ofType": null
+ }
+ }
+ },
+ "defaultValue": null
+ },
+ {
"name": "clientMutationId",
"description": "A unique identifier for the client performing the mutation.",
"type": {
@@ -4988,6 +5741,132 @@
},
{
"kind": "INPUT_OBJECT",
+ "name": "DastSiteProfileCreateInput",
+ "description": "Autogenerated input type of DastSiteProfileCreate",
+ "fields": null,
+ "inputFields": [
+ {
+ "name": "fullPath",
+ "description": "The project the site profile belongs to.",
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "ID",
+ "ofType": null
+ }
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "profileName",
+ "description": "The name of the site profile.",
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ }
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "targetUrl",
+ "description": "The URL of the target to be scanned.",
+ "type": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "clientMutationId",
+ "description": "A unique identifier for the client performing the mutation.",
+ "type": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ },
+ "defaultValue": null
+ }
+ ],
+ "interfaces": null,
+ "enumValues": null,
+ "possibleTypes": null
+ },
+ {
+ "kind": "OBJECT",
+ "name": "DastSiteProfileCreatePayload",
+ "description": "Autogenerated return type of DastSiteProfileCreate",
+ "fields": [
+ {
+ "name": "clientMutationId",
+ "description": "A unique identifier for the client performing the mutation.",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "errors",
+ "description": "Errors encountered during execution of the mutation.",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "LIST",
+ "name": null,
+ "ofType": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ }
+ }
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "id",
+ "description": "ID of the site profile.",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "SCALAR",
+ "name": "ID",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ }
+ ],
+ "inputFields": null,
+ "interfaces": [
+
+ ],
+ "enumValues": null,
+ "possibleTypes": null
+ },
+ {
+ "kind": "INPUT_OBJECT",
"name": "DeleteAnnotationInput",
"description": "Autogenerated input type of DeleteAnnotation",
"fields": null,
@@ -8130,6 +9009,158 @@
},
{
"kind": "OBJECT",
+ "name": "DiffStats",
+ "description": "Changes to a single file",
+ "fields": [
+ {
+ "name": "additions",
+ "description": "Number of lines added to this file",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "Int",
+ "ofType": null
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "deletions",
+ "description": "Number of lines deleted from this file",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "Int",
+ "ofType": null
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "path",
+ "description": "File path, relative to repository root",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ }
+ ],
+ "inputFields": null,
+ "interfaces": [
+
+ ],
+ "enumValues": null,
+ "possibleTypes": null
+ },
+ {
+ "kind": "OBJECT",
+ "name": "DiffStatsSummary",
+ "description": "Aggregated summary of changes",
+ "fields": [
+ {
+ "name": "additions",
+ "description": "Number of lines added",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "Int",
+ "ofType": null
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "changes",
+ "description": "Number of lines changed",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "Int",
+ "ofType": null
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "deletions",
+ "description": "Number of lines deleted",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "Int",
+ "ofType": null
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "fileCount",
+ "description": "Number of files changed",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "Int",
+ "ofType": null
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ }
+ ],
+ "inputFields": null,
+ "interfaces": [
+
+ ],
+ "enumValues": null,
+ "possibleTypes": null
+ },
+ {
+ "kind": "OBJECT",
"name": "Discussion",
"description": null,
"fields": [
@@ -9117,7 +10148,7 @@
},
{
"name": "search",
- "description": "Filter epics by title and description",
+ "description": "Search query for epic title or description",
"type": {
"kind": "SCALAR",
"name": "String",
@@ -11358,6 +12389,24 @@
"description": "The connection type for EpicIssue.",
"fields": [
{
+ "name": "count",
+ "description": "Total count of collection",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "Int",
+ "ofType": null
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
"name": "edges",
"description": "A list of edges.",
"args": [
@@ -12696,7 +13745,7 @@
},
{
"name": "search",
- "description": "Filter epics by title and description",
+ "description": "Search query for epic title or description",
"type": {
"kind": "SCALAR",
"name": "String",
@@ -12825,7 +13874,7 @@
},
{
"name": "search",
- "description": "Filter epics by title and description",
+ "description": "Search query for epic title or description",
"type": {
"kind": "SCALAR",
"name": "String",
@@ -13164,7 +14213,7 @@
},
{
"name": "search",
- "description": "Search query for finding issues by title or description",
+ "description": "Search query for issue title or description",
"type": {
"kind": "SCALAR",
"name": "String",
@@ -13183,6 +14232,20 @@
"defaultValue": "created_desc"
},
{
+ "name": "iterationId",
+ "description": "Iterations applied to the issue",
+ "type": {
+ "kind": "LIST",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "ID",
+ "ofType": null
+ }
+ },
+ "defaultValue": null
+ },
+ {
"name": "after",
"description": "Returns the elements in the list that come after the specified cursor.",
"type": {
@@ -13276,6 +14339,36 @@
"defaultValue": null
},
{
+ "name": "id",
+ "description": "The ID of the Iteration to look up",
+ "type": {
+ "kind": "SCALAR",
+ "name": "ID",
+ "ofType": null
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "iid",
+ "description": "The internal ID of the Iteration to look up",
+ "type": {
+ "kind": "SCALAR",
+ "name": "ID",
+ "ofType": null
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "includeAncestors",
+ "description": "Whether to include ancestor iterations. Defaults to true",
+ "type": {
+ "kind": "SCALAR",
+ "name": "Boolean",
+ "ofType": null
+ },
+ "defaultValue": null
+ },
+ {
"name": "after",
"description": "Returns the elements in the list that come after the specified cursor.",
"type": {
@@ -13733,6 +14826,20 @@
"deprecationReason": null
},
{
+ "name": "storageSizeLimit",
+ "description": "Total storage limit of the root namespace in bytes",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "SCALAR",
+ "name": "Float",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
"name": "subgroupCreationLevel",
"description": "The permission level required to create subgroups within the group",
"args": [
@@ -13747,6 +14854,20 @@
"deprecationReason": null
},
{
+ "name": "temporaryStorageIncreaseEndsOn",
+ "description": "Date until the temporary storage increase is active",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "SCALAR",
+ "name": "Time",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
"name": "timelogs",
"description": "Time logged in issues by group members",
"args": [
@@ -13966,6 +15087,24 @@
"defaultValue": null
},
{
+ "name": "scanner",
+ "description": "Filter vulnerabilities by scanner",
+ "type": {
+ "kind": "LIST",
+ "name": null,
+ "ofType": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ }
+ }
+ },
+ "defaultValue": null
+ },
+ {
"name": "after",
"description": "Returns the elements in the list that come after the specified cursor.",
"type": {
@@ -14096,6 +15235,59 @@
"deprecationReason": null
},
{
+ "name": "vulnerabilityScanners",
+ "description": "Vulnerability scanners reported on the project vulnerabilties of the group and its subgroups",
+ "args": [
+ {
+ "name": "after",
+ "description": "Returns the elements in the list that come after the specified cursor.",
+ "type": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "before",
+ "description": "Returns the elements in the list that come before the specified cursor.",
+ "type": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "first",
+ "description": "Returns the first _n_ elements from the list.",
+ "type": {
+ "kind": "SCALAR",
+ "name": "Int",
+ "ofType": null
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "last",
+ "description": "Returns the last _n_ elements from the list.",
+ "type": {
+ "kind": "SCALAR",
+ "name": "Int",
+ "ofType": null
+ },
+ "defaultValue": null
+ }
+ ],
+ "type": {
+ "kind": "OBJECT",
+ "name": "VulnerabilityScannerConnection",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
"name": "webUrl",
"description": "Web URL of the group",
"args": [
@@ -14493,6 +15685,59 @@
},
"isDeprecated": false,
"deprecationReason": null
+ },
+ {
+ "name": "vulnerabilityScanners",
+ "description": "Vulnerability scanners reported on the vulnerabilties from projects selected in Instance Security Dashboard",
+ "args": [
+ {
+ "name": "after",
+ "description": "Returns the elements in the list that come after the specified cursor.",
+ "type": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "before",
+ "description": "Returns the elements in the list that come before the specified cursor.",
+ "type": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "first",
+ "description": "Returns the first _n_ elements from the list.",
+ "type": {
+ "kind": "SCALAR",
+ "name": "Int",
+ "ofType": null
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "last",
+ "description": "Returns the last _n_ elements from the list.",
+ "type": {
+ "kind": "SCALAR",
+ "name": "Int",
+ "ofType": null
+ },
+ "defaultValue": null
+ }
+ ],
+ "type": {
+ "kind": "OBJECT",
+ "name": "VulnerabilityScannerConnection",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
}
],
"inputFields": null,
@@ -14859,6 +16104,24 @@
"deprecationReason": null
},
{
+ "name": "id",
+ "description": "ID of the issue",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "ID",
+ "ofType": null
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
"name": "iid",
"description": "Internal ID of the issue",
"args": [
@@ -15370,6 +16633,24 @@
"description": "The connection type for Issue.",
"fields": [
{
+ "name": "count",
+ "description": "Total count of collection",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "Int",
+ "ofType": null
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
"name": "edges",
"description": "A list of edges.",
"args": [
@@ -16021,6 +17302,136 @@
},
{
"kind": "INPUT_OBJECT",
+ "name": "IssueSetLockedInput",
+ "description": "Autogenerated input type of IssueSetLocked",
+ "fields": null,
+ "inputFields": [
+ {
+ "name": "projectPath",
+ "description": "The project the issue to mutate is in",
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "ID",
+ "ofType": null
+ }
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "iid",
+ "description": "The iid of the issue to mutate",
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ }
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "locked",
+ "description": "Whether or not to lock discussion on the issue",
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "Boolean",
+ "ofType": null
+ }
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "clientMutationId",
+ "description": "A unique identifier for the client performing the mutation.",
+ "type": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ },
+ "defaultValue": null
+ }
+ ],
+ "interfaces": null,
+ "enumValues": null,
+ "possibleTypes": null
+ },
+ {
+ "kind": "OBJECT",
+ "name": "IssueSetLockedPayload",
+ "description": "Autogenerated return type of IssueSetLocked",
+ "fields": [
+ {
+ "name": "clientMutationId",
+ "description": "A unique identifier for the client performing the mutation.",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "errors",
+ "description": "Errors encountered during execution of the mutation.",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "LIST",
+ "name": null,
+ "ofType": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ }
+ }
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "issue",
+ "description": "The issue after mutation",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "OBJECT",
+ "name": "Issue",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ }
+ ],
+ "inputFields": null,
+ "interfaces": [
+
+ ],
+ "enumValues": null,
+ "possibleTypes": null
+ },
+ {
+ "kind": "INPUT_OBJECT",
"name": "IssueSetWeightInput",
"description": "Autogenerated input type of IssueSetWeight",
"fields": null,
@@ -16349,6 +17760,24 @@
"deprecationReason": null
},
{
+ "name": "iid",
+ "description": "Internal ID of the iteration",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "ID",
+ "ofType": null
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
"name": "startDate",
"description": "Timestamp of the iteration start date",
"args": [
@@ -16907,6 +18336,24 @@
"defaultValue": null
},
{
+ "name": "usersMapping",
+ "description": "The mapping of Jira to GitLab users",
+ "type": {
+ "kind": "LIST",
+ "name": null,
+ "ofType": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "INPUT_OBJECT",
+ "name": "JiraUsersMappingInputType",
+ "ofType": null
+ }
+ }
+ },
+ "defaultValue": null
+ },
+ {
"name": "clientMutationId",
"description": "A unique identifier for the client performing the mutation.",
"type": {
@@ -17304,7 +18751,7 @@
},
{
"name": "projects",
- "description": "List of Jira projects fetched through Jira REST API",
+ "description": "List of all Jira projects fetched through Jira REST API",
"args": [
{
"name": "name",
@@ -17398,7 +18845,7 @@
"fields": [
{
"name": "gitlabId",
- "description": "Id of the matched GitLab user",
+ "description": "ID of the matched GitLab user",
"args": [
],
@@ -17411,6 +18858,34 @@
"deprecationReason": null
},
{
+ "name": "gitlabName",
+ "description": "Name of the matched GitLab user",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "gitlabUsername",
+ "description": "Username of the matched GitLab user",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
"name": "jiraAccountId",
"description": "Account id of the Jira user",
"args": [
@@ -17469,6 +18944,41 @@
"possibleTypes": null
},
{
+ "kind": "INPUT_OBJECT",
+ "name": "JiraUsersMappingInputType",
+ "description": null,
+ "fields": null,
+ "inputFields": [
+ {
+ "name": "jiraAccountId",
+ "description": "Jira account id of the user",
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ }
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "gitlabId",
+ "description": "Id of the GitLab user",
+ "type": {
+ "kind": "SCALAR",
+ "name": "Int",
+ "ofType": null
+ },
+ "defaultValue": null
+ }
+ ],
+ "interfaces": null,
+ "enumValues": null,
+ "possibleTypes": null
+ },
+ {
"kind": "OBJECT",
"name": "Label",
"description": null,
@@ -18091,6 +19601,51 @@
"deprecationReason": null
},
{
+ "name": "diffStats",
+ "description": "Details about which files were changed in this merge request",
+ "args": [
+ {
+ "name": "path",
+ "description": "A specific file-path",
+ "type": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ },
+ "defaultValue": null
+ }
+ ],
+ "type": {
+ "kind": "LIST",
+ "name": null,
+ "ofType": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "OBJECT",
+ "name": "DiffStats",
+ "ofType": null
+ }
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "diffStatsSummary",
+ "description": "Summary of which files were changed in this merge request",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "OBJECT",
+ "name": "DiffStatsSummary",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
"name": "discussionLocked",
"description": "Indicates if comments on the merge request are locked to members only",
"args": [
@@ -20409,6 +21964,152 @@
"possibleTypes": null
},
{
+ "kind": "INPUT_OBJECT",
+ "name": "MergeRequestUpdateInput",
+ "description": "Autogenerated input type of MergeRequestUpdate",
+ "fields": null,
+ "inputFields": [
+ {
+ "name": "projectPath",
+ "description": "The project the merge request to mutate is in",
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "ID",
+ "ofType": null
+ }
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "iid",
+ "description": "The iid of the merge request to mutate",
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ }
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "title",
+ "description": "Title of the merge request",
+ "type": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "targetBranch",
+ "description": "Target branch of the merge request",
+ "type": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "description",
+ "description": "Description of the merge request (Markdown rendered as HTML for caching)",
+ "type": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "clientMutationId",
+ "description": "A unique identifier for the client performing the mutation.",
+ "type": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ },
+ "defaultValue": null
+ }
+ ],
+ "interfaces": null,
+ "enumValues": null,
+ "possibleTypes": null
+ },
+ {
+ "kind": "OBJECT",
+ "name": "MergeRequestUpdatePayload",
+ "description": "Autogenerated return type of MergeRequestUpdate",
+ "fields": [
+ {
+ "name": "clientMutationId",
+ "description": "A unique identifier for the client performing the mutation.",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "errors",
+ "description": "Errors encountered during execution of the mutation.",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "LIST",
+ "name": null,
+ "ofType": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ }
+ }
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "mergeRequest",
+ "description": "The merge request after mutation",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "OBJECT",
+ "name": "MergeRequest",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ }
+ ],
+ "inputFields": null,
+ "interfaces": [
+
+ ],
+ "enumValues": null,
+ "possibleTypes": null
+ },
+ {
"kind": "OBJECT",
"name": "Metadata",
"description": null,
@@ -20920,6 +22621,20 @@
"deprecationReason": null
},
{
+ "name": "stats",
+ "description": "Milestone statistics",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "OBJECT",
+ "name": "MilestoneStats",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
"name": "subgroupMilestone",
"description": "Indicates if milestone is at subgroup level",
"args": [
@@ -21135,6 +22850,47 @@
"possibleTypes": null
},
{
+ "kind": "OBJECT",
+ "name": "MilestoneStats",
+ "description": "Contains statistics about a milestone",
+ "fields": [
+ {
+ "name": "closedIssuesCount",
+ "description": "Number of closed issues associated with the milestone",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "SCALAR",
+ "name": "Int",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "totalIssuesCount",
+ "description": "Total number of issues associated with the milestone",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "SCALAR",
+ "name": "Int",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ }
+ ],
+ "inputFields": null,
+ "interfaces": [
+
+ ],
+ "enumValues": null,
+ "possibleTypes": null
+ },
+ {
"kind": "ENUM",
"name": "MoveType",
"description": "The position to which the adjacent object should be moved",
@@ -21186,8 +22942,8 @@
"name": "AddAwardEmojiPayload",
"ofType": null
},
- "isDeprecated": false,
- "deprecationReason": null
+ "isDeprecated": true,
+ "deprecationReason": "Use awardEmojiAdd. Deprecated in 13.2"
},
{
"name": "addProjectToSecurityDashboard",
@@ -21271,6 +23027,114 @@
"deprecationReason": null
},
{
+ "name": "alertTodoCreate",
+ "description": null,
+ "args": [
+ {
+ "name": "input",
+ "description": null,
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "INPUT_OBJECT",
+ "name": "AlertTodoCreateInput",
+ "ofType": null
+ }
+ },
+ "defaultValue": null
+ }
+ ],
+ "type": {
+ "kind": "OBJECT",
+ "name": "AlertTodoCreatePayload",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "awardEmojiAdd",
+ "description": null,
+ "args": [
+ {
+ "name": "input",
+ "description": null,
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "INPUT_OBJECT",
+ "name": "AwardEmojiAddInput",
+ "ofType": null
+ }
+ },
+ "defaultValue": null
+ }
+ ],
+ "type": {
+ "kind": "OBJECT",
+ "name": "AwardEmojiAddPayload",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "awardEmojiRemove",
+ "description": null,
+ "args": [
+ {
+ "name": "input",
+ "description": null,
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "INPUT_OBJECT",
+ "name": "AwardEmojiRemoveInput",
+ "ofType": null
+ }
+ },
+ "defaultValue": null
+ }
+ ],
+ "type": {
+ "kind": "OBJECT",
+ "name": "AwardEmojiRemovePayload",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "awardEmojiToggle",
+ "description": null,
+ "args": [
+ {
+ "name": "input",
+ "description": null,
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "INPUT_OBJECT",
+ "name": "AwardEmojiToggleInput",
+ "ofType": null
+ }
+ },
+ "defaultValue": null
+ }
+ ],
+ "type": {
+ "kind": "OBJECT",
+ "name": "AwardEmojiTogglePayload",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
"name": "boardListUpdateLimitMetrics",
"description": null,
"args": [
@@ -21595,6 +23459,33 @@
"deprecationReason": null
},
{
+ "name": "dastSiteProfileCreate",
+ "description": null,
+ "args": [
+ {
+ "name": "input",
+ "description": null,
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "INPUT_OBJECT",
+ "name": "DastSiteProfileCreateInput",
+ "ofType": null
+ }
+ },
+ "defaultValue": null
+ }
+ ],
+ "type": {
+ "kind": "OBJECT",
+ "name": "DastSiteProfileCreatePayload",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
"name": "deleteAnnotation",
"description": null,
"args": [
@@ -21946,6 +23837,33 @@
"deprecationReason": null
},
{
+ "name": "issueSetLocked",
+ "description": null,
+ "args": [
+ {
+ "name": "input",
+ "description": null,
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "INPUT_OBJECT",
+ "name": "IssueSetLockedInput",
+ "ofType": null
+ }
+ },
+ "defaultValue": null
+ }
+ ],
+ "type": {
+ "kind": "OBJECT",
+ "name": "IssueSetLockedPayload",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
"name": "issueSetWeight",
"description": null,
"args": [
@@ -22243,6 +24161,33 @@
"deprecationReason": null
},
{
+ "name": "mergeRequestUpdate",
+ "description": "Update attributes of a merge request",
+ "args": [
+ {
+ "name": "input",
+ "description": null,
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "INPUT_OBJECT",
+ "name": "MergeRequestUpdateInput",
+ "ofType": null
+ }
+ },
+ "defaultValue": null
+ }
+ ],
+ "type": {
+ "kind": "OBJECT",
+ "name": "MergeRequestUpdatePayload",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
"name": "removeAwardEmoji",
"description": null,
"args": [
@@ -22266,8 +24211,8 @@
"name": "RemoveAwardEmojiPayload",
"ofType": null
},
- "isDeprecated": false,
- "deprecationReason": null
+ "isDeprecated": true,
+ "deprecationReason": "Use awardEmojiRemove. Deprecated in 13.2"
},
{
"name": "removeProjectFromSecurityDashboard",
@@ -22455,8 +24400,8 @@
"name": "ToggleAwardEmojiPayload",
"ofType": null
},
- "isDeprecated": false,
- "deprecationReason": null
+ "isDeprecated": true,
+ "deprecationReason": "Use awardEmojiToggle. Deprecated in 13.2"
},
{
"name": "updateAlertStatus",
@@ -22594,6 +24539,33 @@
"deprecationReason": null
},
{
+ "name": "updateIteration",
+ "description": null,
+ "args": [
+ {
+ "name": "input",
+ "description": null,
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "INPUT_OBJECT",
+ "name": "UpdateIterationInput",
+ "ofType": null
+ }
+ },
+ "defaultValue": null
+ }
+ ],
+ "type": {
+ "kind": "OBJECT",
+ "name": "UpdateIterationPayload",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
"name": "updateNote",
"description": "Updates a Note. If the body of the Note contains only quick actions, the Note will be destroyed during the update, and no Note will be returned",
"args": [
@@ -22954,6 +24926,34 @@
"deprecationReason": null
},
{
+ "name": "storageSizeLimit",
+ "description": "Total storage limit of the root namespace in bytes",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "SCALAR",
+ "name": "Float",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "temporaryStorageIncreaseEndsOn",
+ "description": "Date until the temporary storage increase is active",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "SCALAR",
+ "name": "Time",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
"name": "visibility",
"description": "Visibility of the namespace",
"args": [
@@ -23317,6 +25317,20 @@
"deprecationReason": null
},
{
+ "name": "systemNoteIconName",
+ "description": "Name of the icon corresponding to a system note",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
"name": "updatedAt",
"description": "Timestamp of the note's last activity",
"args": [
@@ -25182,6 +27196,59 @@
"deprecationReason": null
},
{
+ "name": "complianceFrameworks",
+ "description": "Compliance frameworks associated with the project",
+ "args": [
+ {
+ "name": "after",
+ "description": "Returns the elements in the list that come after the specified cursor.",
+ "type": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "before",
+ "description": "Returns the elements in the list that come before the specified cursor.",
+ "type": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "first",
+ "description": "Returns the first _n_ elements from the list.",
+ "type": {
+ "kind": "SCALAR",
+ "name": "Int",
+ "ofType": null
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "last",
+ "description": "Returns the last _n_ elements from the list.",
+ "type": {
+ "kind": "SCALAR",
+ "name": "Int",
+ "ofType": null
+ },
+ "defaultValue": null
+ }
+ ],
+ "type": {
+ "kind": "OBJECT",
+ "name": "ComplianceFrameworkConnection",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
"name": "containerExpirationPolicy",
"description": "The container expiration policy of the project",
"args": [
@@ -25267,7 +27334,7 @@
},
{
"name": "search",
- "description": "Search query",
+ "description": "Search query for environment name",
"type": {
"kind": "SCALAR",
"name": "String",
@@ -25604,7 +27671,7 @@
},
{
"name": "search",
- "description": "Search query for finding issues by title or description",
+ "description": "Search query for issue title or description",
"type": {
"kind": "SCALAR",
"name": "String",
@@ -25621,6 +27688,20 @@
"ofType": null
},
"defaultValue": "created_desc"
+ },
+ {
+ "name": "iterationId",
+ "description": "Iterations applied to the issue",
+ "type": {
+ "kind": "LIST",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "ID",
+ "ofType": null
+ }
+ },
+ "defaultValue": null
}
],
"type": {
@@ -25783,7 +27864,7 @@
},
{
"name": "search",
- "description": "Search query for finding issues by title or description",
+ "description": "Search query for issue title or description",
"type": {
"kind": "SCALAR",
"name": "String",
@@ -25802,6 +27883,20 @@
"defaultValue": "created_desc"
},
{
+ "name": "iterationId",
+ "description": "Iterations applied to the issue",
+ "type": {
+ "kind": "LIST",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "ID",
+ "ofType": null
+ }
+ },
+ "defaultValue": null
+ },
+ {
"name": "after",
"description": "Returns the elements in the list that come after the specified cursor.",
"type": {
@@ -26619,7 +28714,7 @@
},
{
"name": "release",
- "description": "A single release of the project. Available only when feature flag `graphql_release_data` is enabled",
+ "description": "A single release of the project",
"args": [
{
"name": "tagName",
@@ -26646,7 +28741,7 @@
},
{
"name": "releases",
- "description": "Releases of the project. Available only when feature flag `graphql_release_data` is enabled",
+ "description": "Releases of the project",
"args": [
{
"name": "after",
@@ -26793,7 +28888,7 @@
},
{
"name": "search",
- "description": "Filter requirements by title search",
+ "description": "Search query for requirement title",
"type": {
"kind": "SCALAR",
"name": "String",
@@ -26896,7 +28991,7 @@
},
{
"name": "search",
- "description": "Filter requirements by title search",
+ "description": "Search query for requirement title",
"type": {
"kind": "SCALAR",
"name": "String",
@@ -26972,6 +29067,34 @@
"deprecationReason": null
},
{
+ "name": "sastCiConfiguration",
+ "description": "SAST CI configuration for the project",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "OBJECT",
+ "name": "SastCiConfiguration",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "securityScanners",
+ "description": "Information about security analyzers used in the project",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "OBJECT",
+ "name": "SecurityScanners",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
"name": "sentryDetailedError",
"description": "Detailed version of a Sentry error on the project",
"args": [
@@ -27405,6 +29528,24 @@
"defaultValue": null
},
{
+ "name": "scanner",
+ "description": "Filter vulnerabilities by scanner",
+ "type": {
+ "kind": "LIST",
+ "name": null,
+ "ofType": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ }
+ }
+ },
+ "defaultValue": null
+ },
+ {
"name": "after",
"description": "Returns the elements in the list that come after the specified cursor.",
"type": {
@@ -27454,6 +29595,59 @@
"deprecationReason": null
},
{
+ "name": "vulnerabilityScanners",
+ "description": "Vulnerability scanners reported on the project vulnerabilties",
+ "args": [
+ {
+ "name": "after",
+ "description": "Returns the elements in the list that come after the specified cursor.",
+ "type": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "before",
+ "description": "Returns the elements in the list that come before the specified cursor.",
+ "type": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "first",
+ "description": "Returns the first _n_ elements from the list.",
+ "type": {
+ "kind": "SCALAR",
+ "name": "Int",
+ "ofType": null
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "last",
+ "description": "Returns the last _n_ elements from the list.",
+ "type": {
+ "kind": "SCALAR",
+ "name": "Int",
+ "ofType": null
+ },
+ "defaultValue": null
+ }
+ ],
+ "type": {
+ "kind": "OBJECT",
+ "name": "VulnerabilityScannerConnection",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
"name": "vulnerabilitySeveritiesCount",
"description": "Counts for each severity of vulnerability of the project",
"args": [
@@ -28652,6 +30846,47 @@
"possibleTypes": null
},
{
+ "kind": "ENUM",
+ "name": "ProjectSettingEnum",
+ "description": "Names of compliance frameworks that can be assigned to a Project",
+ "fields": null,
+ "inputFields": null,
+ "interfaces": null,
+ "enumValues": [
+ {
+ "name": "gdpr",
+ "description": null,
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "hipaa",
+ "description": null,
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "pci_dss",
+ "description": null,
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "soc_2",
+ "description": null,
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "sox",
+ "description": null,
+ "isDeprecated": false,
+ "deprecationReason": null
+ }
+ ],
+ "possibleTypes": null
+ },
+ {
"kind": "OBJECT",
"name": "ProjectStatistics",
"description": null,
@@ -28747,6 +30982,20 @@
"deprecationReason": null
},
{
+ "name": "snippetsSize",
+ "description": "Snippets size of the project",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "SCALAR",
+ "name": "Float",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
"name": "storageSize",
"description": "Storage size of the project",
"args": [
@@ -29002,7 +31251,7 @@
},
{
"name": "search",
- "description": "Search criteria",
+ "description": "Search query for project name, path, or description",
"type": {
"kind": "SCALAR",
"name": "String",
@@ -29182,7 +31431,7 @@
},
{
"name": "user",
- "description": "Find a user on this instance",
+ "description": "Find a user",
"args": [
{
"name": "id",
@@ -29389,6 +31638,24 @@
"defaultValue": null
},
{
+ "name": "scanner",
+ "description": "Filter vulnerabilities by scanner",
+ "type": {
+ "kind": "LIST",
+ "name": null,
+ "ofType": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ }
+ }
+ },
+ "defaultValue": null
+ },
+ {
"name": "after",
"description": "Returns the elements in the list that come after the specified cursor.",
"type": {
@@ -29564,7 +31831,7 @@
{
"kind": "OBJECT",
"name": "Release",
- "description": null,
+ "description": "Represents a release",
"fields": [
{
"name": "assets",
@@ -29704,6 +31971,20 @@
"deprecationReason": null
},
{
+ "name": "links",
+ "description": "Links of the release",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "OBJECT",
+ "name": "ReleaseLinks",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
"name": "milestones",
"description": "Milestones associated to the release",
"args": [
@@ -29791,11 +32072,66 @@
],
"type": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "tagPath",
+ "description": "Relative web path to the tag associated with the release",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ }
+ ],
+ "inputFields": null,
+ "interfaces": [
+
+ ],
+ "enumValues": null,
+ "possibleTypes": null
+ },
+ {
+ "kind": "OBJECT",
+ "name": "ReleaseAssetLink",
+ "description": "Represents an asset link associated with a release",
+ "fields": [
+ {
+ "name": "external",
+ "description": "Indicates the link points to an external resource",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "SCALAR",
+ "name": "Boolean",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "id",
+ "description": "ID of the link",
+ "args": [
+
+ ],
+ "type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
- "name": "String",
+ "name": "ID",
"ofType": null
}
},
@@ -29803,8 +32139,36 @@
"deprecationReason": null
},
{
- "name": "tagPath",
- "description": "Relative web path to the tag associated with the release",
+ "name": "linkType",
+ "description": "Type of the link: `other`, `runbook`, `image`, `package`; defaults to `other`",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "ENUM",
+ "name": "ReleaseAssetLinkType",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "name",
+ "description": "Name of the link",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "url",
+ "description": "URL of the link",
"args": [
],
@@ -29826,11 +32190,158 @@
},
{
"kind": "OBJECT",
+ "name": "ReleaseAssetLinkConnection",
+ "description": "The connection type for ReleaseAssetLink.",
+ "fields": [
+ {
+ "name": "edges",
+ "description": "A list of edges.",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "LIST",
+ "name": null,
+ "ofType": {
+ "kind": "OBJECT",
+ "name": "ReleaseAssetLinkEdge",
+ "ofType": null
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "nodes",
+ "description": "A list of nodes.",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "LIST",
+ "name": null,
+ "ofType": {
+ "kind": "OBJECT",
+ "name": "ReleaseAssetLink",
+ "ofType": null
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "pageInfo",
+ "description": "Information to aid in pagination.",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "OBJECT",
+ "name": "PageInfo",
+ "ofType": null
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ }
+ ],
+ "inputFields": null,
+ "interfaces": [
+
+ ],
+ "enumValues": null,
+ "possibleTypes": null
+ },
+ {
+ "kind": "OBJECT",
+ "name": "ReleaseAssetLinkEdge",
+ "description": "An edge in a connection.",
+ "fields": [
+ {
+ "name": "cursor",
+ "description": "A cursor for use in pagination.",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "node",
+ "description": "The item at the end of the edge.",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "OBJECT",
+ "name": "ReleaseAssetLink",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ }
+ ],
+ "inputFields": null,
+ "interfaces": [
+
+ ],
+ "enumValues": null,
+ "possibleTypes": null
+ },
+ {
+ "kind": "ENUM",
+ "name": "ReleaseAssetLinkType",
+ "description": "Type of the link: `other`, `runbook`, `image`, `package`; defaults to `other`",
+ "fields": null,
+ "inputFields": null,
+ "interfaces": null,
+ "enumValues": [
+ {
+ "name": "OTHER",
+ "description": "Other link type",
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "RUNBOOK",
+ "description": "Runbook link type",
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "PACKAGE",
+ "description": "Package link type",
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "IMAGE",
+ "description": "Image link type",
+ "isDeprecated": false,
+ "deprecationReason": null
+ }
+ ],
+ "possibleTypes": null
+ },
+ {
+ "kind": "OBJECT",
"name": "ReleaseAssets",
- "description": null,
+ "description": "A container for all assets associated with a release",
"fields": [
{
- "name": "assetsCount",
+ "name": "count",
"description": "Number of assets of the release",
"args": [
@@ -29890,7 +32401,7 @@
],
"type": {
"kind": "OBJECT",
- "name": "ReleaseLinkConnection",
+ "name": "ReleaseAssetLinkConnection",
"ofType": null
},
"isDeprecated": false,
@@ -30256,58 +32767,26 @@
},
{
"kind": "OBJECT",
- "name": "ReleaseLink",
+ "name": "ReleaseLinks",
"description": null,
"fields": [
{
- "name": "external",
- "description": "Indicates the link points to an external resource",
+ "name": "editUrl",
+ "description": "HTTP URL of the release's edit page",
"args": [
],
"type": {
"kind": "SCALAR",
- "name": "Boolean",
- "ofType": null
- },
- "isDeprecated": false,
- "deprecationReason": null
- },
- {
- "name": "id",
- "description": "ID of the link",
- "args": [
-
- ],
- "type": {
- "kind": "NON_NULL",
- "name": null,
- "ofType": {
- "kind": "SCALAR",
- "name": "ID",
- "ofType": null
- }
- },
- "isDeprecated": false,
- "deprecationReason": null
- },
- {
- "name": "linkType",
- "description": "Type of the link: `other`, `runbook`, `image`, `package`; defaults to `other`",
- "args": [
-
- ],
- "type": {
- "kind": "ENUM",
- "name": "ReleaseLinkType",
+ "name": "String",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
- "name": "name",
- "description": "Name of the link",
+ "name": "issuesUrl",
+ "description": "HTTP URL of the issues page filtered by this release",
"args": [
],
@@ -30320,8 +32799,8 @@
"deprecationReason": null
},
{
- "name": "url",
- "description": "URL of the link",
+ "name": "mergeRequestsUrl",
+ "description": "HTTP URL of the merge request page filtered by this release",
"args": [
],
@@ -30332,114 +32811,16 @@
},
"isDeprecated": false,
"deprecationReason": null
- }
- ],
- "inputFields": null,
- "interfaces": [
-
- ],
- "enumValues": null,
- "possibleTypes": null
- },
- {
- "kind": "OBJECT",
- "name": "ReleaseLinkConnection",
- "description": "The connection type for ReleaseLink.",
- "fields": [
- {
- "name": "edges",
- "description": "A list of edges.",
- "args": [
-
- ],
- "type": {
- "kind": "LIST",
- "name": null,
- "ofType": {
- "kind": "OBJECT",
- "name": "ReleaseLinkEdge",
- "ofType": null
- }
- },
- "isDeprecated": false,
- "deprecationReason": null
},
{
- "name": "nodes",
- "description": "A list of nodes.",
+ "name": "selfUrl",
+ "description": "HTTP URL of the release",
"args": [
],
"type": {
- "kind": "LIST",
- "name": null,
- "ofType": {
- "kind": "OBJECT",
- "name": "ReleaseLink",
- "ofType": null
- }
- },
- "isDeprecated": false,
- "deprecationReason": null
- },
- {
- "name": "pageInfo",
- "description": "Information to aid in pagination.",
- "args": [
-
- ],
- "type": {
- "kind": "NON_NULL",
- "name": null,
- "ofType": {
- "kind": "OBJECT",
- "name": "PageInfo",
- "ofType": null
- }
- },
- "isDeprecated": false,
- "deprecationReason": null
- }
- ],
- "inputFields": null,
- "interfaces": [
-
- ],
- "enumValues": null,
- "possibleTypes": null
- },
- {
- "kind": "OBJECT",
- "name": "ReleaseLinkEdge",
- "description": "An edge in a connection.",
- "fields": [
- {
- "name": "cursor",
- "description": "A cursor for use in pagination.",
- "args": [
-
- ],
- "type": {
- "kind": "NON_NULL",
- "name": null,
- "ofType": {
- "kind": "SCALAR",
- "name": "String",
- "ofType": null
- }
- },
- "isDeprecated": false,
- "deprecationReason": null
- },
- {
- "name": "node",
- "description": "The item at the end of the edge.",
- "args": [
-
- ],
- "type": {
- "kind": "OBJECT",
- "name": "ReleaseLink",
+ "kind": "SCALAR",
+ "name": "String",
"ofType": null
},
"isDeprecated": false,
@@ -30454,44 +32835,9 @@
"possibleTypes": null
},
{
- "kind": "ENUM",
- "name": "ReleaseLinkType",
- "description": "Type of the link: `other`, `runbook`, `image`, `package`; defaults to `other`",
- "fields": null,
- "inputFields": null,
- "interfaces": null,
- "enumValues": [
- {
- "name": "OTHER",
- "description": "Other link type",
- "isDeprecated": false,
- "deprecationReason": null
- },
- {
- "name": "RUNBOOK",
- "description": "Runbook link type",
- "isDeprecated": false,
- "deprecationReason": null
- },
- {
- "name": "PACKAGE",
- "description": "Package link type",
- "isDeprecated": false,
- "deprecationReason": null
- },
- {
- "name": "IMAGE",
- "description": "Image link type",
- "isDeprecated": false,
- "deprecationReason": null
- }
- ],
- "possibleTypes": null
- },
- {
"kind": "OBJECT",
"name": "ReleaseSource",
- "description": null,
+ "description": "Represents the source code attached to a release in a particular format",
"fields": [
{
"name": "format",
@@ -31628,6 +33974,24 @@
"deprecationReason": null
},
{
+ "name": "snippetsSize",
+ "description": "The snippets size in bytes",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "Float",
+ "ofType": null
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
"name": "storageSize",
"description": "The total storage in bytes",
"args": [
@@ -31817,6 +34181,927 @@
},
{
"kind": "OBJECT",
+ "name": "SastCiConfiguration",
+ "description": "Represents a CI configuration of SAST",
+ "fields": [
+ {
+ "name": "analyzers",
+ "description": "List of analyzers entities attached to SAST configuration.",
+ "args": [
+ {
+ "name": "after",
+ "description": "Returns the elements in the list that come after the specified cursor.",
+ "type": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "before",
+ "description": "Returns the elements in the list that come before the specified cursor.",
+ "type": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "first",
+ "description": "Returns the first _n_ elements from the list.",
+ "type": {
+ "kind": "SCALAR",
+ "name": "Int",
+ "ofType": null
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "last",
+ "description": "Returns the last _n_ elements from the list.",
+ "type": {
+ "kind": "SCALAR",
+ "name": "Int",
+ "ofType": null
+ },
+ "defaultValue": null
+ }
+ ],
+ "type": {
+ "kind": "OBJECT",
+ "name": "SastCiConfigurationAnalyzersEntityConnection",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "global",
+ "description": "List of global entities related to SAST configuration.",
+ "args": [
+ {
+ "name": "after",
+ "description": "Returns the elements in the list that come after the specified cursor.",
+ "type": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "before",
+ "description": "Returns the elements in the list that come before the specified cursor.",
+ "type": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "first",
+ "description": "Returns the first _n_ elements from the list.",
+ "type": {
+ "kind": "SCALAR",
+ "name": "Int",
+ "ofType": null
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "last",
+ "description": "Returns the last _n_ elements from the list.",
+ "type": {
+ "kind": "SCALAR",
+ "name": "Int",
+ "ofType": null
+ },
+ "defaultValue": null
+ }
+ ],
+ "type": {
+ "kind": "OBJECT",
+ "name": "SastCiConfigurationEntityConnection",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "pipeline",
+ "description": "List of pipeline entities related to SAST configuration.",
+ "args": [
+ {
+ "name": "after",
+ "description": "Returns the elements in the list that come after the specified cursor.",
+ "type": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "before",
+ "description": "Returns the elements in the list that come before the specified cursor.",
+ "type": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "first",
+ "description": "Returns the first _n_ elements from the list.",
+ "type": {
+ "kind": "SCALAR",
+ "name": "Int",
+ "ofType": null
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "last",
+ "description": "Returns the last _n_ elements from the list.",
+ "type": {
+ "kind": "SCALAR",
+ "name": "Int",
+ "ofType": null
+ },
+ "defaultValue": null
+ }
+ ],
+ "type": {
+ "kind": "OBJECT",
+ "name": "SastCiConfigurationEntityConnection",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ }
+ ],
+ "inputFields": null,
+ "interfaces": [
+
+ ],
+ "enumValues": null,
+ "possibleTypes": null
+ },
+ {
+ "kind": "OBJECT",
+ "name": "SastCiConfigurationAnalyzersEntity",
+ "description": "Represents an analyzer entity in SAST CI configuration",
+ "fields": [
+ {
+ "name": "description",
+ "description": "Analyzer description that is displayed on the form.",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "enabled",
+ "description": "Indicates whether an analyzer is enabled.",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "SCALAR",
+ "name": "Boolean",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "label",
+ "description": "Analyzer label used in the config UI.",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "name",
+ "description": "Name of the analyzer.",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ }
+ ],
+ "inputFields": null,
+ "interfaces": [
+
+ ],
+ "enumValues": null,
+ "possibleTypes": null
+ },
+ {
+ "kind": "OBJECT",
+ "name": "SastCiConfigurationAnalyzersEntityConnection",
+ "description": "The connection type for SastCiConfigurationAnalyzersEntity.",
+ "fields": [
+ {
+ "name": "edges",
+ "description": "A list of edges.",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "LIST",
+ "name": null,
+ "ofType": {
+ "kind": "OBJECT",
+ "name": "SastCiConfigurationAnalyzersEntityEdge",
+ "ofType": null
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "nodes",
+ "description": "A list of nodes.",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "LIST",
+ "name": null,
+ "ofType": {
+ "kind": "OBJECT",
+ "name": "SastCiConfigurationAnalyzersEntity",
+ "ofType": null
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "pageInfo",
+ "description": "Information to aid in pagination.",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "OBJECT",
+ "name": "PageInfo",
+ "ofType": null
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ }
+ ],
+ "inputFields": null,
+ "interfaces": [
+
+ ],
+ "enumValues": null,
+ "possibleTypes": null
+ },
+ {
+ "kind": "OBJECT",
+ "name": "SastCiConfigurationAnalyzersEntityEdge",
+ "description": "An edge in a connection.",
+ "fields": [
+ {
+ "name": "cursor",
+ "description": "A cursor for use in pagination.",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "node",
+ "description": "The item at the end of the edge.",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "OBJECT",
+ "name": "SastCiConfigurationAnalyzersEntity",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ }
+ ],
+ "inputFields": null,
+ "interfaces": [
+
+ ],
+ "enumValues": null,
+ "possibleTypes": null
+ },
+ {
+ "kind": "OBJECT",
+ "name": "SastCiConfigurationEntity",
+ "description": "Represents an entity in SAST CI configuration",
+ "fields": [
+ {
+ "name": "defaultValue",
+ "description": "Default value that is used if value is empty.",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "description",
+ "description": "Entity description that is displayed on the form.",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "field",
+ "description": "CI keyword of entity.",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "label",
+ "description": "Label for entity used in the form.",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "options",
+ "description": "Different possible values of the field.",
+ "args": [
+ {
+ "name": "after",
+ "description": "Returns the elements in the list that come after the specified cursor.",
+ "type": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "before",
+ "description": "Returns the elements in the list that come before the specified cursor.",
+ "type": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "first",
+ "description": "Returns the first _n_ elements from the list.",
+ "type": {
+ "kind": "SCALAR",
+ "name": "Int",
+ "ofType": null
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "last",
+ "description": "Returns the last _n_ elements from the list.",
+ "type": {
+ "kind": "SCALAR",
+ "name": "Int",
+ "ofType": null
+ },
+ "defaultValue": null
+ }
+ ],
+ "type": {
+ "kind": "OBJECT",
+ "name": "SastCiConfigurationOptionsEntityConnection",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "type",
+ "description": "Type of the field value.",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "value",
+ "description": "Current value of the entity.",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ }
+ ],
+ "inputFields": null,
+ "interfaces": [
+
+ ],
+ "enumValues": null,
+ "possibleTypes": null
+ },
+ {
+ "kind": "OBJECT",
+ "name": "SastCiConfigurationEntityConnection",
+ "description": "The connection type for SastCiConfigurationEntity.",
+ "fields": [
+ {
+ "name": "edges",
+ "description": "A list of edges.",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "LIST",
+ "name": null,
+ "ofType": {
+ "kind": "OBJECT",
+ "name": "SastCiConfigurationEntityEdge",
+ "ofType": null
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "nodes",
+ "description": "A list of nodes.",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "LIST",
+ "name": null,
+ "ofType": {
+ "kind": "OBJECT",
+ "name": "SastCiConfigurationEntity",
+ "ofType": null
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "pageInfo",
+ "description": "Information to aid in pagination.",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "OBJECT",
+ "name": "PageInfo",
+ "ofType": null
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ }
+ ],
+ "inputFields": null,
+ "interfaces": [
+
+ ],
+ "enumValues": null,
+ "possibleTypes": null
+ },
+ {
+ "kind": "OBJECT",
+ "name": "SastCiConfigurationEntityEdge",
+ "description": "An edge in a connection.",
+ "fields": [
+ {
+ "name": "cursor",
+ "description": "A cursor for use in pagination.",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "node",
+ "description": "The item at the end of the edge.",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "OBJECT",
+ "name": "SastCiConfigurationEntity",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ }
+ ],
+ "inputFields": null,
+ "interfaces": [
+
+ ],
+ "enumValues": null,
+ "possibleTypes": null
+ },
+ {
+ "kind": "OBJECT",
+ "name": "SastCiConfigurationOptionsEntity",
+ "description": "Represents an entity for options in SAST CI configuration",
+ "fields": [
+ {
+ "name": "label",
+ "description": "Label of option entity.",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "value",
+ "description": "Value of option entity.",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ }
+ ],
+ "inputFields": null,
+ "interfaces": [
+
+ ],
+ "enumValues": null,
+ "possibleTypes": null
+ },
+ {
+ "kind": "OBJECT",
+ "name": "SastCiConfigurationOptionsEntityConnection",
+ "description": "The connection type for SastCiConfigurationOptionsEntity.",
+ "fields": [
+ {
+ "name": "edges",
+ "description": "A list of edges.",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "LIST",
+ "name": null,
+ "ofType": {
+ "kind": "OBJECT",
+ "name": "SastCiConfigurationOptionsEntityEdge",
+ "ofType": null
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "nodes",
+ "description": "A list of nodes.",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "LIST",
+ "name": null,
+ "ofType": {
+ "kind": "OBJECT",
+ "name": "SastCiConfigurationOptionsEntity",
+ "ofType": null
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "pageInfo",
+ "description": "Information to aid in pagination.",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "OBJECT",
+ "name": "PageInfo",
+ "ofType": null
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ }
+ ],
+ "inputFields": null,
+ "interfaces": [
+
+ ],
+ "enumValues": null,
+ "possibleTypes": null
+ },
+ {
+ "kind": "OBJECT",
+ "name": "SastCiConfigurationOptionsEntityEdge",
+ "description": "An edge in a connection.",
+ "fields": [
+ {
+ "name": "cursor",
+ "description": "A cursor for use in pagination.",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "node",
+ "description": "The item at the end of the edge.",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "OBJECT",
+ "name": "SastCiConfigurationOptionsEntity",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ }
+ ],
+ "inputFields": null,
+ "interfaces": [
+
+ ],
+ "enumValues": null,
+ "possibleTypes": null
+ },
+ {
+ "kind": "OBJECT",
+ "name": "ScannedResource",
+ "description": "Represents a resource scanned by a security scan",
+ "fields": [
+ {
+ "name": "requestMethod",
+ "description": "The HTTP request method used to access the URL",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "url",
+ "description": "The URL scanned by the scanner",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ }
+ ],
+ "inputFields": null,
+ "interfaces": [
+
+ ],
+ "enumValues": null,
+ "possibleTypes": null
+ },
+ {
+ "kind": "OBJECT",
+ "name": "ScannedResourceConnection",
+ "description": "The connection type for ScannedResource.",
+ "fields": [
+ {
+ "name": "edges",
+ "description": "A list of edges.",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "LIST",
+ "name": null,
+ "ofType": {
+ "kind": "OBJECT",
+ "name": "ScannedResourceEdge",
+ "ofType": null
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "nodes",
+ "description": "A list of nodes.",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "LIST",
+ "name": null,
+ "ofType": {
+ "kind": "OBJECT",
+ "name": "ScannedResource",
+ "ofType": null
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "pageInfo",
+ "description": "Information to aid in pagination.",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "OBJECT",
+ "name": "PageInfo",
+ "ofType": null
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ }
+ ],
+ "inputFields": null,
+ "interfaces": [
+
+ ],
+ "enumValues": null,
+ "possibleTypes": null
+ },
+ {
+ "kind": "OBJECT",
+ "name": "ScannedResourceEdge",
+ "description": "An edge in a connection.",
+ "fields": [
+ {
+ "name": "cursor",
+ "description": "A cursor for use in pagination.",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "node",
+ "description": "The item at the end of the edge.",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "OBJECT",
+ "name": "ScannedResource",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ }
+ ],
+ "inputFields": null,
+ "interfaces": [
+
+ ],
+ "enumValues": null,
+ "possibleTypes": null
+ },
+ {
+ "kind": "OBJECT",
"name": "SecurityReportSummary",
"description": "Represents summary of a security report",
"fields": [
@@ -31835,6 +35120,20 @@
"deprecationReason": null
},
{
+ "name": "coverageFuzzing",
+ "description": "Aggregated counts for the coverage_fuzzing scan",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "OBJECT",
+ "name": "SecurityReportSummarySection",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
"name": "dast",
"description": "Aggregated counts for the dast scan",
"args": [
@@ -31904,6 +35203,59 @@
"description": "Represents a section of a summary of a security report",
"fields": [
{
+ "name": "scannedResources",
+ "description": "A list of the first 20 scanned resources",
+ "args": [
+ {
+ "name": "after",
+ "description": "Returns the elements in the list that come after the specified cursor.",
+ "type": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "before",
+ "description": "Returns the elements in the list that come before the specified cursor.",
+ "type": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "first",
+ "description": "Returns the first _n_ elements from the list.",
+ "type": {
+ "kind": "SCALAR",
+ "name": "Int",
+ "ofType": null
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "last",
+ "description": "Returns the last _n_ elements from the list.",
+ "type": {
+ "kind": "SCALAR",
+ "name": "Int",
+ "ofType": null
+ },
+ "defaultValue": null
+ }
+ ],
+ "type": {
+ "kind": "OBJECT",
+ "name": "ScannedResourceConnection",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
"name": "scannedResourcesCount",
"description": "Total number of scanned resources",
"args": [
@@ -31918,6 +35270,20 @@
"deprecationReason": null
},
{
+ "name": "scannedResourcesCsvPath",
+ "description": "Path to download all the scanned resources in CSV format",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
"name": "vulnerabilitiesCount",
"description": "Total number of vulnerabilities",
"args": [
@@ -31940,6 +35306,126 @@
"possibleTypes": null
},
{
+ "kind": "ENUM",
+ "name": "SecurityScannerType",
+ "description": "The type of the security scanner.",
+ "fields": null,
+ "inputFields": null,
+ "interfaces": null,
+ "enumValues": [
+ {
+ "name": "SAST",
+ "description": null,
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "DAST",
+ "description": null,
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "DEPENDENCY_SCANNING",
+ "description": null,
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "CONTAINER_SCANNING",
+ "description": null,
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "SECRET_DETECTION",
+ "description": null,
+ "isDeprecated": false,
+ "deprecationReason": null
+ }
+ ],
+ "possibleTypes": null
+ },
+ {
+ "kind": "OBJECT",
+ "name": "SecurityScanners",
+ "description": "Represents a list of security scanners",
+ "fields": [
+ {
+ "name": "available",
+ "description": "List of analyzers which are available for the project.",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "LIST",
+ "name": null,
+ "ofType": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "ENUM",
+ "name": "SecurityScannerType",
+ "ofType": null
+ }
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "enabled",
+ "description": "List of analyzers which are enabled for the project.",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "LIST",
+ "name": null,
+ "ofType": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "ENUM",
+ "name": "SecurityScannerType",
+ "ofType": null
+ }
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "pipelineRun",
+ "description": "List of analyzers which ran successfully in the latest pipeline.",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "LIST",
+ "name": null,
+ "ofType": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "ENUM",
+ "name": "SecurityScannerType",
+ "ofType": null
+ }
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ }
+ ],
+ "inputFields": null,
+ "interfaces": [
+
+ ],
+ "enumValues": null,
+ "possibleTypes": null
+ },
+ {
"kind": "OBJECT",
"name": "SentryDetailedError",
"description": "A Sentry error.",
@@ -32032,6 +35518,20 @@
},
{
"name": "firstReleaseShortVersion",
+ "description": "Release short version the error was first seen",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "firstReleaseVersion",
"description": "Release version the error was first seen",
"args": [
@@ -32164,6 +35664,20 @@
},
{
"name": "lastReleaseShortVersion",
+ "description": "Release short version the error was last seen",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "lastReleaseVersion",
"description": "Release version the error was last seen",
"args": [
@@ -32824,7 +36338,7 @@
},
{
"name": "searchTerm",
- "description": "Search term for the Sentry error.",
+ "description": "Search query for the Sentry error details",
"type": {
"kind": "SCALAR",
"name": "String",
@@ -33538,6 +37052,12 @@
"deprecationReason": null
},
{
+ "name": "CONFLUENCE_SERVICE",
+ "description": null,
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
"name": "CUSTOM_ISSUE_TRACKER_SERVICE",
"description": null,
"isDeprecated": false,
@@ -34568,6 +38088,100 @@
"possibleTypes": null
},
{
+ "kind": "ENUM",
+ "name": "SnippetFileInputActionEnum",
+ "description": "Type of a snippet file input action",
+ "fields": null,
+ "inputFields": null,
+ "interfaces": null,
+ "enumValues": [
+ {
+ "name": "create",
+ "description": null,
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "update",
+ "description": null,
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "delete",
+ "description": null,
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "move",
+ "description": null,
+ "isDeprecated": false,
+ "deprecationReason": null
+ }
+ ],
+ "possibleTypes": null
+ },
+ {
+ "kind": "INPUT_OBJECT",
+ "name": "SnippetFileInputType",
+ "description": "Represents an action to perform over a snippet file",
+ "fields": null,
+ "inputFields": [
+ {
+ "name": "action",
+ "description": "Type of input action",
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "ENUM",
+ "name": "SnippetFileInputActionEnum",
+ "ofType": null
+ }
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "previousPath",
+ "description": "Previous path of the snippet file",
+ "type": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "filePath",
+ "description": "Path of the snippet file",
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ }
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "content",
+ "description": "Snippet file content",
+ "type": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ },
+ "defaultValue": null
+ }
+ ],
+ "interfaces": null,
+ "enumValues": null,
+ "possibleTypes": null
+ },
+ {
"kind": "OBJECT",
"name": "SnippetPermissions",
"description": null,
@@ -35253,6 +38867,12 @@
"description": null,
"isDeprecated": false,
"deprecationReason": null
+ },
+ {
+ "name": "FAILED",
+ "description": null,
+ "isDeprecated": false,
+ "deprecationReason": null
}
],
"possibleTypes": null
@@ -36036,8 +39656,34 @@
"deprecationReason": null
},
{
+ "name": "todos",
+ "description": "Updated todos",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "LIST",
+ "name": null,
+ "ofType": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "OBJECT",
+ "name": "Todo",
+ "ofType": null
+ }
+ }
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
"name": "updatedIds",
- "description": "The ids of the updated todo items",
+ "description": "The ids of the updated todo items. Deprecated in 13.2: Use todos",
"args": [
],
@@ -36058,8 +39704,8 @@
}
}
},
- "isDeprecated": false,
- "deprecationReason": null
+ "isDeprecated": true,
+ "deprecationReason": "Use todos. Deprecated in 13.2"
}
],
"inputFields": null,
@@ -36196,6 +39842,12 @@
"deprecationReason": null
},
{
+ "name": "ALERT",
+ "description": "An Alert",
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
"name": "EPIC",
"description": "An Epic",
"isDeprecated": false,
@@ -36271,8 +39923,34 @@
"deprecationReason": null
},
{
+ "name": "todos",
+ "description": "Updated todos",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "LIST",
+ "name": null,
+ "ofType": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "OBJECT",
+ "name": "Todo",
+ "ofType": null
+ }
+ }
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
"name": "updatedIds",
- "description": "Ids of the updated todos",
+ "description": "Ids of the updated todos. Deprecated in 13.2: Use todos",
"args": [
],
@@ -36293,8 +39971,8 @@
}
}
},
- "isDeprecated": false,
- "deprecationReason": null
+ "isDeprecated": true,
+ "deprecationReason": "Use todos. Deprecated in 13.2"
}
],
"inputFields": null,
@@ -36911,6 +40589,16 @@
"possibleTypes": null
},
{
+ "kind": "SCALAR",
+ "name": "UntrustedRegexp",
+ "description": "A regexp containing patterns sourced from user input",
+ "fields": null,
+ "inputFields": null,
+ "interfaces": null,
+ "enumValues": null,
+ "possibleTypes": null
+ },
+ {
"kind": "INPUT_OBJECT",
"name": "UpdateAlertStatusInput",
"description": "Autogenerated input type of UpdateAlertStatus",
@@ -37045,6 +40733,20 @@
},
"isDeprecated": false,
"deprecationReason": null
+ },
+ {
+ "name": "todo",
+ "description": "The todo after mutation",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "OBJECT",
+ "name": "Todo",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
}
],
"inputFields": null,
@@ -37115,6 +40817,26 @@
"defaultValue": null
},
{
+ "name": "nameRegex",
+ "description": "Tags with names matching this regex pattern will expire",
+ "type": {
+ "kind": "SCALAR",
+ "name": "UntrustedRegexp",
+ "ofType": null
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "nameRegexKeep",
+ "description": "Tags with names matching this regex pattern will be preserved",
+ "type": {
+ "kind": "SCALAR",
+ "name": "UntrustedRegexp",
+ "ofType": null
+ },
+ "defaultValue": null
+ },
+ {
"name": "clientMutationId",
"description": "A unique identifier for the client performing the mutation.",
"type": {
@@ -37769,6 +41491,162 @@
},
{
"kind": "INPUT_OBJECT",
+ "name": "UpdateIterationInput",
+ "description": "Autogenerated input type of UpdateIteration",
+ "fields": null,
+ "inputFields": [
+ {
+ "name": "groupPath",
+ "description": "The group of the iteration",
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "ID",
+ "ofType": null
+ }
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "id",
+ "description": "The id of the iteration",
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "ID",
+ "ofType": null
+ }
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "title",
+ "description": "The title of the iteration",
+ "type": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "description",
+ "description": "The description of the iteration",
+ "type": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "startDate",
+ "description": "The start date of the iteration",
+ "type": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "dueDate",
+ "description": "The end date of the iteration",
+ "type": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "clientMutationId",
+ "description": "A unique identifier for the client performing the mutation.",
+ "type": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ },
+ "defaultValue": null
+ }
+ ],
+ "interfaces": null,
+ "enumValues": null,
+ "possibleTypes": null
+ },
+ {
+ "kind": "OBJECT",
+ "name": "UpdateIterationPayload",
+ "description": "Autogenerated return type of UpdateIteration",
+ "fields": [
+ {
+ "name": "clientMutationId",
+ "description": "A unique identifier for the client performing the mutation.",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "errors",
+ "description": "Errors encountered during execution of the mutation.",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "LIST",
+ "name": null,
+ "ofType": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ }
+ }
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "iteration",
+ "description": "The updated iteration",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "OBJECT",
+ "name": "Iteration",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ }
+ ],
+ "inputFields": null,
+ "interfaces": [
+
+ ],
+ "enumValues": null,
+ "possibleTypes": null
+ },
+ {
+ "kind": "INPUT_OBJECT",
"name": "UpdateNoteInput",
"description": "Autogenerated input type of UpdateNote",
"fields": null,
@@ -38090,6 +41968,24 @@
"defaultValue": null
},
{
+ "name": "files",
+ "description": "The snippet files to update",
+ "type": {
+ "kind": "LIST",
+ "name": null,
+ "ofType": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "INPUT_OBJECT",
+ "name": "SnippetFileInputType",
+ "ofType": null
+ }
+ }
+ },
+ "defaultValue": null
+ },
+ {
"name": "clientMutationId",
"description": "A unique identifier for the client performing the mutation.",
"type": {
@@ -39423,6 +43319,32 @@
"deprecationReason": null
},
{
+ "name": "identifiers",
+ "description": "Identifiers of the vulnerability.",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "LIST",
+ "name": null,
+ "ofType": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "OBJECT",
+ "name": "VulnerabilityIdentifier",
+ "ofType": null
+ }
+ }
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
"name": "issueLinks",
"description": "List of issue links related to the vulnerability",
"args": [
@@ -39504,6 +43426,20 @@
"deprecationReason": null
},
{
+ "name": "primaryIdentifier",
+ "description": "Primary identifier of the vulnerability.",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "OBJECT",
+ "name": "VulnerabilityIdentifier",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
"name": "project",
"description": "The project on which the vulnerability was found",
"args": [
@@ -39519,7 +43455,7 @@
},
{
"name": "reportType",
- "description": "Type of the security report that found the vulnerability (SAST, DEPENDENCY_SCANNING, CONTAINER_SCANNING, DAST, SECRET_DETECTION)",
+ "description": "Type of the security report that found the vulnerability (SAST, DEPENDENCY_SCANNING, CONTAINER_SCANNING, DAST, SECRET_DETECTION, COVERAGE_FUZZING)",
"args": [
],
@@ -39532,6 +43468,20 @@
"deprecationReason": null
},
{
+ "name": "scanner",
+ "description": "Scanner metadata for the vulnerability.",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "OBJECT",
+ "name": "VulnerabilityScanner",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
"name": "severity",
"description": "Severity of the vulnerability (INFO, UNKNOWN, LOW, MEDIUM, HIGH, CRITICAL)",
"args": [
@@ -39745,6 +43695,75 @@
},
{
"kind": "OBJECT",
+ "name": "VulnerabilityIdentifier",
+ "description": "Represents a vulnerability identifier.",
+ "fields": [
+ {
+ "name": "externalId",
+ "description": "External ID of the vulnerability identifier",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "externalType",
+ "description": "External type of the vulnerability identifier",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "name",
+ "description": "Name of the vulnerability identifier",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "url",
+ "description": "URL of the vulnerability identifier",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ }
+ ],
+ "inputFields": null,
+ "interfaces": [
+
+ ],
+ "enumValues": null,
+ "possibleTypes": null
+ },
+ {
+ "kind": "OBJECT",
"name": "VulnerabilityIssueLink",
"description": "Represents an issue link of a vulnerability.",
"fields": [
@@ -40506,8 +44525,195 @@
"description": null,
"isDeprecated": false,
"deprecationReason": null
+ },
+ {
+ "name": "COVERAGE_FUZZING",
+ "description": null,
+ "isDeprecated": false,
+ "deprecationReason": null
+ }
+ ],
+ "possibleTypes": null
+ },
+ {
+ "kind": "OBJECT",
+ "name": "VulnerabilityScanner",
+ "description": "Represents a vulnerability scanner.",
+ "fields": [
+ {
+ "name": "externalId",
+ "description": "External ID of the vulnerability scanner",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "name",
+ "description": "Name of the vulnerability scanner",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "reportType",
+ "description": "Type of the vulnerability report",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "ENUM",
+ "name": "VulnerabilityReportType",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "vendor",
+ "description": "Vendor of the vulnerability scanner",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ }
+ ],
+ "inputFields": null,
+ "interfaces": [
+
+ ],
+ "enumValues": null,
+ "possibleTypes": null
+ },
+ {
+ "kind": "OBJECT",
+ "name": "VulnerabilityScannerConnection",
+ "description": "The connection type for VulnerabilityScanner.",
+ "fields": [
+ {
+ "name": "edges",
+ "description": "A list of edges.",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "LIST",
+ "name": null,
+ "ofType": {
+ "kind": "OBJECT",
+ "name": "VulnerabilityScannerEdge",
+ "ofType": null
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "nodes",
+ "description": "A list of nodes.",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "LIST",
+ "name": null,
+ "ofType": {
+ "kind": "OBJECT",
+ "name": "VulnerabilityScanner",
+ "ofType": null
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "pageInfo",
+ "description": "Information to aid in pagination.",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "OBJECT",
+ "name": "PageInfo",
+ "ofType": null
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ }
+ ],
+ "inputFields": null,
+ "interfaces": [
+
+ ],
+ "enumValues": null,
+ "possibleTypes": null
+ },
+ {
+ "kind": "OBJECT",
+ "name": "VulnerabilityScannerEdge",
+ "description": "An edge in a connection.",
+ "fields": [
+ {
+ "name": "cursor",
+ "description": "A cursor for use in pagination.",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "node",
+ "description": "The item at the end of the edge.",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "OBJECT",
+ "name": "VulnerabilityScanner",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
}
],
+ "inputFields": null,
+ "interfaces": [
+
+ ],
+ "enumValues": null,
"possibleTypes": null
},
{
diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md
index befb57c1cba..6df6632f3bd 100644
--- a/doc/api/graphql/reference/index.md
+++ b/doc/api/graphql/reference/index.md
@@ -69,6 +69,7 @@ Describes an alert from the project's Alert Management
| `hosts` | String! => Array | List of hosts the alert came from |
| `iid` | ID! | Internal ID of the alert |
| `issueIid` | ID | Internal ID of the GitLab issue attached to the alert |
+| `metricsDashboardUrl` | String | URL for metrics embed for the alert |
| `monitoringTool` | String | Monitoring tool the alert came from |
| `service` | String | Service the alert came from |
| `severity` | AlertManagementSeverity | Severity of the alert |
@@ -100,6 +101,19 @@ Autogenerated return type of AlertSetAssignees
| `clientMutationId` | String | A unique identifier for the client performing the mutation. |
| `errors` | String! => Array | Errors encountered during execution of the mutation. |
| `issue` | Issue | The issue created after mutation |
+| `todo` | Todo | The todo after mutation |
+
+## AlertTodoCreatePayload
+
+Autogenerated return type of AlertTodoCreate
+
+| Name | Type | Description |
+| --- | ---- | ---------- |
+| `alert` | AlertManagementAlert | The alert after mutation |
+| `clientMutationId` | String | A unique identifier for the client performing the mutation. |
+| `errors` | String! => Array | Errors encountered during execution of the mutation. |
+| `issue` | Issue | The issue created after mutation |
+| `todo` | Todo | The todo after mutation |
## AwardEmoji
@@ -114,6 +128,37 @@ An emoji awarded by a user.
| `unicodeVersion` | String! | The unicode version for this emoji |
| `user` | User! | The user who awarded the emoji |
+## AwardEmojiAddPayload
+
+Autogenerated return type of AwardEmojiAdd
+
+| Name | Type | Description |
+| --- | ---- | ---------- |
+| `awardEmoji` | AwardEmoji | The award emoji after mutation |
+| `clientMutationId` | String | A unique identifier for the client performing the mutation. |
+| `errors` | String! => Array | Errors encountered during execution of the mutation. |
+
+## AwardEmojiRemovePayload
+
+Autogenerated return type of AwardEmojiRemove
+
+| Name | Type | Description |
+| --- | ---- | ---------- |
+| `awardEmoji` | AwardEmoji | The award emoji after mutation |
+| `clientMutationId` | String | A unique identifier for the client performing the mutation. |
+| `errors` | String! => Array | Errors encountered during execution of the mutation. |
+
+## AwardEmojiTogglePayload
+
+Autogenerated return type of AwardEmojiToggle
+
+| Name | Type | Description |
+| --- | ---- | ---------- |
+| `awardEmoji` | AwardEmoji | The award emoji after mutation |
+| `clientMutationId` | String | A unique identifier for the client performing the mutation. |
+| `errors` | String! => Array | Errors encountered during execution of the mutation. |
+| `toggledOn` | Boolean! | Indicates the status of the emoji. True if the toggle awarded the emoji, and false if the toggle removed the emoji. |
+
## BaseService
| Name | Type | Description |
@@ -128,6 +173,7 @@ An emoji awarded by a user.
| `flatPath` | String! | Flat path of the entry |
| `id` | ID! | ID of the entry |
| `lfsOid` | String | LFS ID of the blob |
+| `mode` | String | Blob mode in numeric format |
| `name` | String! | Name of the entry |
| `path` | String! | Path of the entry |
| `sha` | String! | Last commit sha for the entry |
@@ -207,6 +253,14 @@ Autogenerated return type of CommitCreate
| `commit` | Commit | The commit after mutation |
| `errors` | String! => Array | Errors encountered during execution of the mutation. |
+## ComplianceFramework
+
+Represents a ComplianceFramework associated with a Project
+
+| Name | Type | Description |
+| --- | ---- | ---------- |
+| `name` | ProjectSettingEnum! | Name of the compliance framework |
+
## ContainerExpirationPolicy
A tag expiration policy designed to keep only the images that matter most
@@ -217,8 +271,8 @@ A tag expiration policy designed to keep only the images that matter most
| `createdAt` | Time! | Timestamp of when the container expiration policy was created |
| `enabled` | Boolean! | Indicates whether this container expiration policy is enabled |
| `keepN` | ContainerExpirationPolicyKeepEnum | Number of tags to retain |
-| `nameRegex` | String | Tags with names matching this regex pattern will expire |
-| `nameRegexKeep` | String | Tags with names matching this regex pattern will be preserved |
+| `nameRegex` | UntrustedRegexp | Tags with names matching this regex pattern will expire |
+| `nameRegexKeep` | UntrustedRegexp | Tags with names matching this regex pattern will be preserved |
| `nextRunAt` | Time | Next time that this container expiration policy will get executed |
| `olderThan` | ContainerExpirationPolicyOlderThanEnum | Tags older that this will expire |
| `updatedAt` | Time! | Timestamp of when the container expiration policy was updated |
@@ -233,6 +287,7 @@ Autogenerated return type of CreateAlertIssue
| `clientMutationId` | String | A unique identifier for the client performing the mutation. |
| `errors` | String! => Array | Errors encountered during execution of the mutation. |
| `issue` | Issue | The issue created after mutation |
+| `todo` | Todo | The todo after mutation |
## CreateAnnotationPayload
@@ -324,6 +379,16 @@ Autogenerated return type of CreateSnippet
| `errors` | String! => Array | Errors encountered during execution of the mutation. |
| `snippet` | Snippet | The snippet after mutation |
+## DastSiteProfileCreatePayload
+
+Autogenerated return type of DastSiteProfileCreate
+
+| Name | Type | Description |
+| --- | ---- | ---------- |
+| `clientMutationId` | String | A unique identifier for the client performing the mutation. |
+| `errors` | String! => Array | Errors encountered during execution of the mutation. |
+| `id` | ID | ID of the site profile. |
+
## DeleteAnnotationPayload
Autogenerated return type of DeleteAnnotation
@@ -486,6 +551,27 @@ Autogenerated return type of DestroySnippet
| `headSha` | String! | SHA of the HEAD at the time the comment was made |
| `startSha` | String! | SHA of the branch being compared against |
+## DiffStats
+
+Changes to a single file
+
+| Name | Type | Description |
+| --- | ---- | ---------- |
+| `additions` | Int! | Number of lines added to this file |
+| `deletions` | Int! | Number of lines deleted from this file |
+| `path` | String! | File path, relative to repository root |
+
+## DiffStatsSummary
+
+Aggregated summary of changes
+
+| Name | Type | Description |
+| --- | ---- | ---------- |
+| `additions` | Int! | Number of lines added |
+| `changes` | Int! | Number of lines changed |
+| `deletions` | Int! | Number of lines deleted |
+| `fileCount` | Int! | Number of files changed |
+
## Discussion
| Name | Type | Description |
@@ -745,7 +831,9 @@ Autogenerated return type of EpicTreeReorder
| `requireTwoFactorAuthentication` | Boolean | Indicates if all users in this group are required to set up two-factor authentication |
| `rootStorageStatistics` | RootStorageStatistics | Aggregated storage statistics of the namespace. Only available for root namespaces |
| `shareWithGroupLock` | Boolean | Indicates if sharing a project with another group within this group is prevented |
+| `storageSizeLimit` | Float | Total storage limit of the root namespace in bytes |
| `subgroupCreationLevel` | String | The permission level required to create subgroups within the group |
+| `temporaryStorageIncreaseEndsOn` | Time | Date until the temporary storage increase is active |
| `twoFactorGracePeriod` | Int | Time before two-factor authentication is enforced |
| `userPermissions` | GroupPermissions! | Permissions for the current user on the resource |
| `visibility` | String | Visibility of the namespace |
@@ -788,6 +876,7 @@ Represents a Group Member
| `dueDate` | Time | Due date of the issue |
| `epic` | Epic | Epic to which this issue belongs |
| `healthStatus` | HealthStatus | Current health status. Returns null if `save_issuable_health_status` feature flag is disabled. |
+| `id` | ID! | ID of the issue |
| `iid` | ID! | Internal ID of the issue |
| `iteration` | Iteration | Iteration of the issue |
| `milestone` | Milestone | Milestone of the issue |
@@ -853,6 +942,16 @@ Autogenerated return type of IssueSetIteration
| `errors` | String! => Array | Errors encountered during execution of the mutation. |
| `issue` | Issue | The issue after mutation |
+## IssueSetLockedPayload
+
+Autogenerated return type of IssueSetLocked
+
+| Name | Type | Description |
+| --- | ---- | ---------- |
+| `clientMutationId` | String | A unique identifier for the client performing the mutation. |
+| `errors` | String! => Array | Errors encountered during execution of the mutation. |
+| `issue` | Issue | The issue after mutation |
+
## IssueSetWeightPayload
Autogenerated return type of IssueSetWeight
@@ -873,6 +972,7 @@ Represents an iteration object.
| `description` | String | Description of the iteration |
| `dueDate` | Time | Timestamp of the iteration due date |
| `id` | ID! | ID of the iteration |
+| `iid` | ID! | Internal ID of the iteration |
| `startDate` | Time | Timestamp of the iteration start date |
| `state` | IterationState! | State of the iteration |
| `title` | String! | Title of the iteration |
@@ -925,14 +1025,16 @@ Autogenerated return type of JiraImportUsers
| Name | Type | Description |
| --- | ---- | ---------- |
| `active` | Boolean | Indicates if the service is active |
-| `projects` | JiraProjectConnection | List of Jira projects fetched through Jira REST API |
+| `projects` | JiraProjectConnection | List of all Jira projects fetched through Jira REST API |
| `type` | String | Class name of the service |
## JiraUser
| Name | Type | Description |
| --- | ---- | ---------- |
-| `gitlabId` | Int | Id of the matched GitLab user |
+| `gitlabId` | Int | ID of the matched GitLab user |
+| `gitlabName` | String | Name of the matched GitLab user |
+| `gitlabUsername` | String | Username of the matched GitLab user |
| `jiraAccountId` | String! | Account id of the Jira user |
| `jiraDisplayName` | String! | Display name of the Jira user |
| `jiraEmail` | String | Email of the Jira user, returned only for users with public emails |
@@ -970,6 +1072,8 @@ Autogenerated return type of MarkAsSpamSnippet
| `descriptionHtml` | String | The GitLab Flavored Markdown rendering of `description` |
| `diffHeadSha` | String | Diff head SHA of the merge request |
| `diffRefs` | DiffRefs | References of the base SHA, the head SHA, and the start SHA for this merge request |
+| `diffStats` | DiffStats! => Array | Details about which files were changed in this merge request |
+| `diffStatsSummary` | DiffStatsSummary | Summary of which files were changed in this merge request |
| `discussionLocked` | Boolean! | Indicates if comments on the merge request are locked to members only |
| `downvotes` | Int! | Number of downvotes for the merge request |
| `forceRemoveSourceBranch` | Boolean | Indicates if the project settings will lead to source branch deletion after merge |
@@ -1100,6 +1204,16 @@ Autogenerated return type of MergeRequestSetWip
| `errors` | String! => Array | Errors encountered during execution of the mutation. |
| `mergeRequest` | MergeRequest | The merge request after mutation |
+## MergeRequestUpdatePayload
+
+Autogenerated return type of MergeRequestUpdate
+
+| Name | Type | Description |
+| --- | ---- | ---------- |
+| `clientMutationId` | String | A unique identifier for the client performing the mutation. |
+| `errors` | String! => Array | Errors encountered during execution of the mutation. |
+| `mergeRequest` | MergeRequest | The merge request after mutation |
+
## Metadata
| Name | Type | Description |
@@ -1138,11 +1252,21 @@ Represents a milestone.
| `projectMilestone` | Boolean! | Indicates if milestone is at project level |
| `startDate` | Time | Timestamp of the milestone start date |
| `state` | MilestoneStateEnum! | State of the milestone |
+| `stats` | MilestoneStats | Milestone statistics |
| `subgroupMilestone` | Boolean! | Indicates if milestone is at subgroup level |
| `title` | String! | Title of the milestone |
| `updatedAt` | Time! | Timestamp of last milestone update |
| `webPath` | String! | Web path of the milestone |
+## MilestoneStats
+
+Contains statistics about a milestone
+
+| Name | Type | Description |
+| --- | ---- | ---------- |
+| `closedIssuesCount` | Int | Number of closed issues associated with the milestone |
+| `totalIssuesCount` | Int | Total number of issues associated with the milestone |
+
## Namespace
| Name | Type | Description |
@@ -1157,6 +1281,8 @@ Represents a milestone.
| `path` | String! | Path of the namespace |
| `requestAccessEnabled` | Boolean | Indicates if users can request access to namespace |
| `rootStorageStatistics` | RootStorageStatistics | Aggregated storage statistics of the namespace. Only available for root namespaces |
+| `storageSizeLimit` | Float | Total storage limit of the root namespace in bytes |
+| `temporaryStorageIncreaseEndsOn` | Time | Date until the temporary storage increase is active |
| `visibility` | String | Visibility of the namespace |
## Note
@@ -1177,6 +1303,7 @@ Represents a milestone.
| `resolvedAt` | Time | Timestamp of when the object was resolved |
| `resolvedBy` | User | User who resolved the object |
| `system` | Boolean! | Indicates whether this note was created by the system or by a user |
+| `systemNoteIconName` | String | Name of the icon corresponding to a system note |
| `updatedAt` | Time! | Timestamp of the note's last activity |
| `userPermissions` | NotePermissions! | Permissions for the current user on the resource |
@@ -1300,12 +1427,14 @@ Information about pagination in a connection.
| `pipeline` | Pipeline | Build pipeline of the project |
| `printingMergeRequestLinkEnabled` | Boolean | Indicates if a link to create or view a merge request should display after a push to Git repositories of the project from the command line |
| `publicJobs` | Boolean | Indicates if there is public access to pipelines and job details of the project, including output logs and artifacts |
-| `release` | Release | A single release of the project. Available only when feature flag `graphql_release_data` is enabled |
+| `release` | Release | A single release of the project |
| `removeSourceBranchAfterMerge` | Boolean | Indicates if `Delete source branch` option should be enabled by default for all new merge requests of the project |
| `repository` | Repository | Git repository of the project |
| `requestAccessEnabled` | Boolean | Indicates if users can request member access to the project |
| `requirement` | Requirement | Find a single requirement. Available only when feature flag `requirements_management` is enabled. |
| `requirementStatesCount` | RequirementStatesCount | Number of requirements for the project by their state |
+| `sastCiConfiguration` | SastCiConfiguration | SAST CI configuration for the project |
+| `securityScanners` | SecurityScanners | Information about security analyzers used in the project |
| `sentryDetailedError` | SentryDetailedError | Detailed version of a Sentry error on the project |
| `sentryErrors` | SentryErrorCollection | Paginated collection of Sentry errors on the project |
| `serviceDeskAddress` | String | E-mail address of the service desk. |
@@ -1395,11 +1524,14 @@ Represents a Project Member
| `lfsObjectsSize` | Float! | Large File Storage (LFS) object size of the project |
| `packagesSize` | Float! | Packages size of the project |
| `repositorySize` | Float! | Repository size of the project |
+| `snippetsSize` | Float | Snippets size of the project |
| `storageSize` | Float! | Storage size of the project |
| `wikiSize` | Float | Wiki size of the project |
## Release
+Represents a release
+
| Name | Type | Description |
| --- | ---- | ---------- |
| `assets` | ReleaseAssets | Assets of the release |
@@ -1408,16 +1540,31 @@ Represents a Project Member
| `createdAt` | Time | Timestamp of when the release was created |
| `description` | String | Description (also known as "release notes") of the release |
| `descriptionHtml` | String | The GitLab Flavored Markdown rendering of `description` |
+| `links` | ReleaseLinks | Links of the release |
| `name` | String | Name of the release |
| `releasedAt` | Time | Timestamp of when the release was released |
-| `tagName` | String! | Name of the tag associated with the release |
+| `tagName` | String | Name of the tag associated with the release |
| `tagPath` | String | Relative web path to the tag associated with the release |
+## ReleaseAssetLink
+
+Represents an asset link associated with a release
+
+| Name | Type | Description |
+| --- | ---- | ---------- |
+| `external` | Boolean | Indicates the link points to an external resource |
+| `id` | ID! | ID of the link |
+| `linkType` | ReleaseAssetLinkType | Type of the link: `other`, `runbook`, `image`, `package`; defaults to `other` |
+| `name` | String | Name of the link |
+| `url` | String | URL of the link |
+
## ReleaseAssets
+A container for all assets associated with a release
+
| Name | Type | Description |
| --- | ---- | ---------- |
-| `assetsCount` | Int | Number of assets of the release |
+| `count` | Int | Number of assets of the release |
## ReleaseEvidence
@@ -1430,18 +1577,19 @@ Evidence for a release
| `id` | ID! | ID of the evidence |
| `sha` | String | SHA1 ID of the evidence hash |
-## ReleaseLink
+## ReleaseLinks
| Name | Type | Description |
| --- | ---- | ---------- |
-| `external` | Boolean | Indicates the link points to an external resource |
-| `id` | ID! | ID of the link |
-| `linkType` | ReleaseLinkType | Type of the link: `other`, `runbook`, `image`, `package`; defaults to `other` |
-| `name` | String | Name of the link |
-| `url` | String | URL of the link |
+| `editUrl` | String | HTTP URL of the release's edit page |
+| `issuesUrl` | String | HTTP URL of the issues page filtered by this release |
+| `mergeRequestsUrl` | String | HTTP URL of the merge request page filtered by this release |
+| `selfUrl` | String | HTTP URL of the release |
## ReleaseSource
+Represents the source code attached to a release in a particular format
+
| Name | Type | Description |
| --- | ---- | ---------- |
| `format` | String | Format of the source |
@@ -1520,6 +1668,7 @@ Counts of requirements by their state.
| `lfsObjectsSize` | Float! | The LFS objects size in bytes |
| `packagesSize` | Float! | The packages size in bytes |
| `repositorySize` | Float! | The Git repository size in bytes |
+| `snippetsSize` | Float! | The snippets size in bytes |
| `storageSize` | Float! | The total storage in bytes |
| `wikiSize` | Float! | The wiki size in bytes |
@@ -1533,6 +1682,48 @@ Autogenerated return type of RunDASTScan
| `errors` | String! => Array | Errors encountered during execution of the mutation. |
| `pipelineUrl` | String | URL of the pipeline that was created. |
+## SastCiConfigurationAnalyzersEntity
+
+Represents an analyzer entity in SAST CI configuration
+
+| Name | Type | Description |
+| --- | ---- | ---------- |
+| `description` | String | Analyzer description that is displayed on the form. |
+| `enabled` | Boolean | Indicates whether an analyzer is enabled. |
+| `label` | String | Analyzer label used in the config UI. |
+| `name` | String | Name of the analyzer. |
+
+## SastCiConfigurationEntity
+
+Represents an entity in SAST CI configuration
+
+| Name | Type | Description |
+| --- | ---- | ---------- |
+| `defaultValue` | String | Default value that is used if value is empty. |
+| `description` | String | Entity description that is displayed on the form. |
+| `field` | String | CI keyword of entity. |
+| `label` | String | Label for entity used in the form. |
+| `type` | String | Type of the field value. |
+| `value` | String | Current value of the entity. |
+
+## SastCiConfigurationOptionsEntity
+
+Represents an entity for options in SAST CI configuration
+
+| Name | Type | Description |
+| --- | ---- | ---------- |
+| `label` | String | Label of option entity. |
+| `value` | String | Value of option entity. |
+
+## ScannedResource
+
+Represents a resource scanned by a security scan
+
+| Name | Type | Description |
+| --- | ---- | ---------- |
+| `requestMethod` | String | The HTTP request method used to access the URL |
+| `url` | String | The URL scanned by the scanner |
+
## SecurityReportSummary
Represents summary of a security report
@@ -1540,6 +1731,7 @@ Represents summary of a security report
| Name | Type | Description |
| --- | ---- | ---------- |
| `containerScanning` | SecurityReportSummarySection | Aggregated counts for the container_scanning scan |
+| `coverageFuzzing` | SecurityReportSummarySection | Aggregated counts for the coverage_fuzzing scan |
| `dast` | SecurityReportSummarySection | Aggregated counts for the dast scan |
| `dependencyScanning` | SecurityReportSummarySection | Aggregated counts for the dependency_scanning scan |
| `sast` | SecurityReportSummarySection | Aggregated counts for the sast scan |
@@ -1552,8 +1744,19 @@ Represents a section of a summary of a security report
| Name | Type | Description |
| --- | ---- | ---------- |
| `scannedResourcesCount` | Int | Total number of scanned resources |
+| `scannedResourcesCsvPath` | String | Path to download all the scanned resources in CSV format |
| `vulnerabilitiesCount` | Int | Total number of vulnerabilities |
+## SecurityScanners
+
+Represents a list of security scanners
+
+| Name | Type | Description |
+| --- | ---- | ---------- |
+| `available` | SecurityScannerType! => Array | List of analyzers which are available for the project. |
+| `enabled` | SecurityScannerType! => Array | List of analyzers which are enabled for the project. |
+| `pipelineRun` | SecurityScannerType! => Array | List of analyzers which ran successfully in the latest pipeline. |
+
## SentryDetailedError
A Sentry error.
@@ -1565,7 +1768,8 @@ A Sentry error.
| `externalBaseUrl` | String! | External Base URL of the Sentry Instance |
| `externalUrl` | String! | External URL of the error |
| `firstReleaseLastCommit` | String | Commit the error was first seen |
-| `firstReleaseShortVersion` | String | Release version the error was first seen |
+| `firstReleaseShortVersion` | String | Release short version the error was first seen |
+| `firstReleaseVersion` | String | Release version the error was first seen |
| `firstSeen` | Time! | Timestamp when the error was first seen |
| `frequency` | SentryErrorFrequency! => Array | Last 24hr stats of the error |
| `gitlabCommit` | String | GitLab commit SHA attributed to the Error based on the release version |
@@ -1573,7 +1777,8 @@ A Sentry error.
| `gitlabIssuePath` | String | URL of GitLab Issue |
| `id` | ID! | ID (global ID) of the error |
| `lastReleaseLastCommit` | String | Commit the error was last seen |
-| `lastReleaseShortVersion` | String | Release version the error was last seen |
+| `lastReleaseShortVersion` | String | Release short version the error was last seen |
+| `lastReleaseVersion` | String | Release version the error was last seen |
| `lastSeen` | Time! | Timestamp when the error was last seen |
| `message` | String | Sentry metadata message of the error |
| `sentryId` | String! | ID (Sentry ID) of the error |
@@ -1814,7 +2019,8 @@ Autogenerated return type of TodoRestoreMany
| --- | ---- | ---------- |
| `clientMutationId` | String | A unique identifier for the client performing the mutation. |
| `errors` | String! => Array | Errors encountered during execution of the mutation. |
-| `updatedIds` | ID! => Array | The ids of the updated todo items |
+| `todos` | Todo! => Array | Updated todos |
+| `updatedIds` **{warning-solid}** | ID! => Array | **Deprecated:** Use todos. Deprecated in 13.2 |
## TodoRestorePayload
@@ -1834,7 +2040,8 @@ Autogenerated return type of TodosMarkAllDone
| --- | ---- | ---------- |
| `clientMutationId` | String | A unique identifier for the client performing the mutation. |
| `errors` | String! => Array | Errors encountered during execution of the mutation. |
-| `updatedIds` | ID! => Array | Ids of the updated todos |
+| `todos` | Todo! => Array | Updated todos |
+| `updatedIds` **{warning-solid}** | ID! => Array | **Deprecated:** Use todos. Deprecated in 13.2 |
## ToggleAwardEmojiPayload
@@ -1877,6 +2084,7 @@ Autogenerated return type of UpdateAlertStatus
| `clientMutationId` | String | A unique identifier for the client performing the mutation. |
| `errors` | String! => Array | Errors encountered during execution of the mutation. |
| `issue` | Issue | The issue created after mutation |
+| `todo` | Todo | The todo after mutation |
## UpdateContainerExpirationPolicyPayload
@@ -1918,6 +2126,16 @@ Autogenerated return type of UpdateIssue
| `errors` | String! => Array | Errors encountered during execution of the mutation. |
| `issue` | Issue | The issue after mutation |
+## UpdateIterationPayload
+
+Autogenerated return type of UpdateIteration
+
+| Name | Type | Description |
+| --- | ---- | ---------- |
+| `clientMutationId` | String | A unique identifier for the client performing the mutation. |
+| `errors` | String! => Array | Errors encountered during execution of the mutation. |
+| `iteration` | Iteration | The updated iteration |
+
## UpdateNotePayload
Autogenerated return type of UpdateNote
@@ -1984,9 +2202,12 @@ Represents a vulnerability.
| --- | ---- | ---------- |
| `description` | String | Description of the vulnerability |
| `id` | ID! | GraphQL ID of the vulnerability |
+| `identifiers` | VulnerabilityIdentifier! => Array | Identifiers of the vulnerability. |
| `location` | VulnerabilityLocation | Location metadata for the vulnerability. Its fields depend on the type of security scan that found the vulnerability |
+| `primaryIdentifier` | VulnerabilityIdentifier | Primary identifier of the vulnerability. |
| `project` | Project | The project on which the vulnerability was found |
-| `reportType` | VulnerabilityReportType | Type of the security report that found the vulnerability (SAST, DEPENDENCY_SCANNING, CONTAINER_SCANNING, DAST, SECRET_DETECTION) |
+| `reportType` | VulnerabilityReportType | Type of the security report that found the vulnerability (SAST, DEPENDENCY_SCANNING, CONTAINER_SCANNING, DAST, SECRET_DETECTION, COVERAGE_FUZZING) |
+| `scanner` | VulnerabilityScanner | Scanner metadata for the vulnerability. |
| `severity` | VulnerabilitySeverity | Severity of the vulnerability (INFO, UNKNOWN, LOW, MEDIUM, HIGH, CRITICAL) |
| `state` | VulnerabilityState | State of the vulnerability (DETECTED, DISMISSED, RESOLVED, CONFIRMED) |
| `title` | String | Title of the vulnerability |
@@ -1994,6 +2215,17 @@ Represents a vulnerability.
| `userPermissions` | VulnerabilityPermissions! | Permissions for the current user on the resource |
| `vulnerabilityPath` | String | URL to the vulnerability's details page |
+## VulnerabilityIdentifier
+
+Represents a vulnerability identifier.
+
+| Name | Type | Description |
+| --- | ---- | ---------- |
+| `externalId` | String | External ID of the vulnerability identifier |
+| `externalType` | String | External type of the vulnerability identifier |
+| `name` | String | Name of the vulnerability identifier |
+| `url` | String | URL of the vulnerability identifier |
+
## VulnerabilityIssueLink
Represents an issue link of a vulnerability.
@@ -2073,6 +2305,17 @@ Check permissions for the current user on a vulnerability
| `readVulnerabilityFeedback` | Boolean! | Indicates the user can perform `read_vulnerability_feedback` on this resource |
| `updateVulnerabilityFeedback` | Boolean! | Indicates the user can perform `update_vulnerability_feedback` on this resource |
+## VulnerabilityScanner
+
+Represents a vulnerability scanner.
+
+| Name | Type | Description |
+| --- | ---- | ---------- |
+| `externalId` | String | External ID of the vulnerability scanner |
+| `name` | String | Name of the vulnerability scanner |
+| `reportType` | VulnerabilityReportType | Type of the vulnerability report |
+| `vendor` | String | Vendor of the vulnerability scanner |
+
## VulnerabilitySeveritiesCount
Represents vulnerability counts by severity
diff --git a/doc/api/group_clusters.md b/doc/api/group_clusters.md
index b600a92bc38..17413ea2a3b 100644
--- a/doc/api/group_clusters.md
+++ b/doc/api/group_clusters.md
@@ -1,3 +1,9 @@
+---
+stage: Configure
+group: Configure
+info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers
+---
+
# Group clusters API
> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/30213) in GitLab 12.1.
@@ -22,7 +28,7 @@ Parameters:
Example request:
```shell
-curl --header 'Private-Token: <your_access_token>' "https://gitlab.example.com/api/v4/groups/26/clusters"
+curl --header "Private-Token: <your_access_token>" "https://gitlab.example.com/api/v4/groups/26/clusters"
```
Example response:
@@ -238,7 +244,7 @@ through the ["Add existing cluster to group"](#add-existing-cluster-to-group) en
Example request:
```shell
-curl --header 'Private-Token: <your_access_token>' "https://gitlab.example.com/api/v4/groups/26/clusters/24" \
+curl --header "Private-Token: <your_access_token>" "https://gitlab.example.com/api/v4/groups/26/clusters/24" \
-H "Content-Type:application/json" \
--request PUT --data '{"name":"new-cluster-name","domain":"new-domain.com","api_url":"https://new-api-url.com"}'
```
diff --git a/doc/api/group_labels.md b/doc/api/group_labels.md
index 5ae5ea4286a..260ee669e08 100644
--- a/doc/api/group_labels.md
+++ b/doc/api/group_labels.md
@@ -170,7 +170,8 @@ Example response:
}
```
-NOTE: **Note:** An older endpoint `PUT /groups/:id/labels` with `name` in the parameters is still available, but deprecated.
+NOTE: **Note:**
+An older endpoint `PUT /groups/:id/labels` with `name` in the parameters is still available, but deprecated.
## Delete a group label
@@ -189,7 +190,8 @@ DELETE /groups/:id/labels/:label_id
curl --request DELETE --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/groups/5/labels/bug"
```
-NOTE: **Note:** An older endpoint `DELETE /groups/:id/labels` with `name` in the parameters is still available, but deprecated.
+NOTE: **Note:**
+An older endpoint `DELETE /groups/:id/labels` with `name` in the parameters is still available, but deprecated.
## Subscribe to a group label
diff --git a/doc/api/group_wikis.md b/doc/api/group_wikis.md
new file mode 100644
index 00000000000..62094ffc940
--- /dev/null
+++ b/doc/api/group_wikis.md
@@ -0,0 +1,196 @@
+# Wikis API
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/212199) in GitLab 13.2.
+
+Available only in APIv4.
+
+## List wiki pages
+
+List all wiki pages for a given group.
+
+```plaintext
+GET /groups/:id/wikis
+```
+
+| Attribute | Type | Required | Description |
+| --------- | ------- | -------- | --------------------- |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) |
+| `with_content` | boolean | no | Include pages' content |
+
+```shell
+curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/groups/1/wikis?with_content=1"
+```
+
+Example response:
+
+```json
+[
+ {
+ "content" : "Here is an instruction how to deploy this project.",
+ "format" : "markdown",
+ "slug" : "deploy",
+ "title" : "deploy"
+ },
+ {
+ "content" : "Our development process is described here.",
+ "format" : "markdown",
+ "slug" : "development",
+ "title" : "development"
+ },{
+ "content" : "* [Deploy](deploy)\n* [Development](development)",
+ "format" : "markdown",
+ "slug" : "home",
+ "title" : "home"
+ }
+]
+```
+
+## Get a wiki page
+
+Get a wiki page for a given group.
+
+```plaintext
+GET /groups/:id/wikis/:slug
+```
+
+| Attribute | Type | Required | Description |
+| --------- | ------- | -------- | --------------------- |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) |
+| `slug` | string | yes | The slug (a unique string) of the wiki page |
+
+```shell
+curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/groups/1/wikis/home"
+```
+
+Example response:
+
+```json
+{
+ "content" : "home page",
+ "format" : "markdown",
+ "slug" : "home",
+ "title" : "home"
+}
+```
+
+## Create a new wiki page
+
+Create a new wiki page for the given repository with the given title, slug, and content.
+
+```plaintext
+POST /projects/:id/wikis
+```
+
+| Attribute | Type | Required | Description |
+| ------------- | ------- | -------- | ---------------------------- |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) |
+| `content` | string | yes | The content of the wiki page |
+| `title` | string | yes | The title of the wiki page |
+| `format` | string | no | The format of the wiki page. Available formats are: `markdown` (default), `rdoc`, `asciidoc` and `org` |
+
+```shell
+curl --data "format=rdoc&title=Hello&content=Hello world" \
+ --header "PRIVATE-TOKEN: <your_access_token>" \
+ "https://gitlab.example.com/api/v4/groups/1/wikis"
+```
+
+Example response:
+
+```json
+{
+ "content" : "Hello world",
+ "format" : "markdown",
+ "slug" : "Hello",
+ "title" : "Hello"
+}
+```
+
+## Edit an existing wiki page
+
+Update an existing wiki page. At least one parameter is required to update the wiki page.
+
+```plaintext
+PUT /groups/:id/wikis/:slug
+```
+
+| Attribute | Type | Required | Description |
+| --------------- | ------- | --------------------------------- | ------------------------------- |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) |
+| `content` | string | yes if `title` is not provided | The content of the wiki page |
+| `title` | string | yes if `content` is not provided | The title of the wiki page |
+| `format` | string | no | The format of the wiki page. Available formats are: `markdown` (default), `rdoc`, `asciidoc` and `org` |
+| `slug` | string | yes | The slug (a unique identifier) of the wiki page |
+
+```shell
+curl --request PUT --data "format=rdoc&content=documentation&title=Docs" \
+ --header "PRIVATE-TOKEN: <your_access_token>" \
+ "https://gitlab.example.com/api/v4/groups/1/wikis/foo"
+```
+
+Example response:
+
+```json
+{
+ "content" : "documentation",
+ "format" : "markdown",
+ "slug" : "Docs",
+ "title" : "Docs"
+}
+```
+
+## Delete a wiki page
+
+Delete a wiki page with a given slug.
+
+```plaintext
+DELETE /groups/:id/wikis/:slug
+```
+
+| Attribute | Type | Required | Description |
+| --------- | ------- | -------- | --------------------- |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) |
+| `slug` | string | yes | The slug (a unique identifier) of the wiki page |
+
+```shell
+curl --request DELETE --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/groups/1/wikis/foo"
+```
+
+On success the HTTP status code is `204` and no JSON response is expected.
+
+## Upload an attachment to the wiki repository
+
+Upload a file to the attachment folder inside the wiki's repository. The
+attachment folder is the `uploads` folder.
+
+```plaintext
+POST /groups/:id/wikis/attachments
+```
+
+| Attribute | Type | Required | Description |
+| ------------- | ------- | -------- | ---------------------------- |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) |
+| `file` | string | yes | The attachment to be uploaded |
+| `branch` | string | no | The name of the branch. Defaults to the wiki repository default branch |
+
+To upload a file from your filesystem, use the `--form` argument. This causes
+cURL to post data using the header `Content-Type: multipart/form-data`.
+The `file=` parameter must point to a file on your filesystem and be preceded
+by `@`. For example:
+
+```shell
+curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" --form "file=@dk.png" "https://gitlab.example.com/api/v4/groups/1/wikis/attachments"
+```
+
+Example response:
+
+```json
+{
+ "file_name" : "dk.png",
+ "file_path" : "uploads/6a061c4cf9f1c28cb22c384b4b8d4e3c/dk.png",
+ "branch" : "master",
+ "link" : {
+ "url" : "uploads/6a061c4cf9f1c28cb22c384b4b8d4e3c/dk.png",
+ "markdown" : "![dk](uploads/6a061c4cf9f1c28cb22c384b4b8d4e3c/dk.png)"
+ }
+}
+```
diff --git a/doc/api/groups.md b/doc/api/groups.md
index e58506380d1..a5a0c210540 100644
--- a/doc/api/groups.md
+++ b/doc/api/groups.md
@@ -19,7 +19,7 @@ Parameters:
| `statistics` | boolean | no | Include group statistics (admins only) |
| `with_custom_attributes` | boolean | no | Include [custom attributes](custom_attributes.md) in response (admins only) |
| `owned` | boolean | no | Limit to groups explicitly owned by the current user |
-| `min_access_level` | integer | no | Limit to groups where current user has at least this [access level](members.md) |
+| `min_access_level` | integer | no | Limit to groups where current user has at least this [access level](members.md#valid-access-levels) |
| `top_level_only` | boolean | no | Limit to top level groups, excluding all subgroups |
```plaintext
@@ -94,7 +94,8 @@ GET /groups?statistics=true
"wiki_size" : 100,
"lfs_objects_size" : 123,
"job_artifacts_size" : 57,
- "packages_size": 0
+ "packages_size": 0,
+ "snippets_size" : 50,
}
}
]
@@ -121,7 +122,7 @@ Parameters:
| Attribute | Type | Required | Description |
| ------------------------ | ----------------- | -------- | ----------- |
-| `id` | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) of the parent group |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) of the immediate parent group |
| `skip_groups` | array of integers | no | Skip the group IDs passed |
| `all_available` | boolean | no | Show all the groups you have access to (defaults to `false` for authenticated users, `true` for admin); Attributes `owned` and `min_access_level` have precedence |
| `search` | string | no | Return the list of authorized groups matching the search criteria |
@@ -130,7 +131,7 @@ Parameters:
| `statistics` | boolean | no | Include group statistics (admins only) |
| `with_custom_attributes` | boolean | no | Include [custom attributes](custom_attributes.md) in response (admins only) |
| `owned` | boolean | no | Limit to groups explicitly owned by the current user |
-| `min_access_level` | integer | no | Limit to groups where current user has at least this [access level](members.md) |
+| `min_access_level` | integer | no | Limit to groups where current user has at least this [access level](members.md#valid-access-levels) |
```plaintext
GET /groups/:id/subgroups
@@ -193,7 +194,7 @@ Parameters:
| `with_merge_requests_enabled` | boolean | no | Limit by projects with merge requests feature enabled. Default is `false` |
| `with_shared` | boolean | no | Include projects shared to this group. Default is `true` |
| `include_subgroups` | boolean | no | Include projects in subgroups of this group. Default is `false` |
-| `min_access_level` | integer | no | Limit to projects where current user has at least this [access level](members.md) |
+| `min_access_level` | integer | no | Limit to projects where current user has at least this [access level](members.md#valid-access-levels) |
| `with_custom_attributes` | boolean | no | Include [custom attributes](custom_attributes.md) in response (admins only) |
| `with_security_reports` | boolean | no | **(ULTIMATE)** Return only projects that have security reports artifacts present in any of their builds. This means "projects with security reports enabled". Default is `false` |
@@ -268,7 +269,7 @@ Parameters:
| `starred` | boolean | no | Limit by projects starred by the current user |
| `with_issues_enabled` | boolean | no | Limit by projects with issues feature enabled. Default is `false` |
| `with_merge_requests_enabled` | boolean | no | Limit by projects with merge requests feature enabled. Default is `false` |
-| `min_access_level` | integer | no | Limit to projects where current user has at least this [access level](members.md) |
+| `min_access_level` | integer | no | Limit to projects where current user has at least this [access level](members.md#valid-access-levels) |
| `with_custom_attributes` | boolean | no | Include [custom attributes](custom_attributes.md) in response (admins only) |
Example response:
@@ -666,8 +667,8 @@ Parameters:
| `request_access_enabled` | boolean | no | Allow users to request member access. |
| `parent_id` | integer | no | The parent group ID for creating nested group. |
| `default_branch_protection` | integer | no | See [Options for `default_branch_protection`](#options-for-default_branch_protection). Default to the global level default branch protection setting. |
-| `shared_runners_minutes_limit` | integer | no | **(STARTER ONLY)** Pipeline minutes quota for this group. |
-| `extra_shared_runners_minutes_limit` | integer | no | **(STARTER ONLY)** Extra pipeline minutes quota for this group. |
+| `shared_runners_minutes_limit` | integer | no | **(STARTER ONLY)** Pipeline minutes quota for this group (included in plan). Can be `nil` (default; inherit system default), `0` (unlimited) or `> 0` |
+| `extra_shared_runners_minutes_limit` | integer | no | **(STARTER ONLY)** Extra pipeline minutes quota for this group (purchased in addition to the minutes included in the plan). |
### Options for `default_branch_protection`
@@ -740,8 +741,8 @@ PUT /groups/:id
| `request_access_enabled` | boolean | no | Allow users to request member access. |
| `default_branch_protection` | integer | no | See [Options for `default_branch_protection`](#options-for-default_branch_protection). |
| `file_template_project_id` | integer | no | **(PREMIUM)** The ID of a project to load custom file templates from. |
-| `shared_runners_minutes_limit` | integer | no | **(STARTER ONLY)** Pipeline minutes quota for this group. |
-| `extra_shared_runners_minutes_limit` | integer | no | **(STARTER ONLY)** Extra pipeline minutes quota for this group. |
+| `shared_runners_minutes_limit` | integer | no | **(STARTER ONLY)** Pipeline minutes quota for this group (included in plan). Can be `nil` (default; inherit system default), `0` (unlimited) or `> 0` |
+| `extra_shared_runners_minutes_limit` | integer | no | **(STARTER ONLY)** Extra pipeline minutes quota for this group (purchased in addition to the minutes included in the plan). |
NOTE: **Note:**
The `projects` and `shared_projects` attributes in the response are deprecated and will be [removed in API v5](https://gitlab.com/gitlab-org/gitlab/-/issues/213797).
@@ -1051,7 +1052,7 @@ POST /groups/:id/ldap_group_links
| `id` | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) |
| `cn` | string | no | The CN of an LDAP group |
| `filter` | string | no | The LDAP filter for the group |
-| `group_access` | integer | yes | Minimum access level for members of the LDAP group |
+| `group_access` | integer | yes | Minimum [access level](members.md#valid-access-levels) for members of the LDAP group |
| `provider` | string | yes | LDAP provider for the LDAP group link |
NOTE: **Note:**
@@ -1140,7 +1141,7 @@ POST /groups/:id/share
| --------- | -------------- | -------- | ----------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) |
| `group_id` | integer | yes | The ID of the group to share with |
-| `group_access` | integer | yes | The [permissions level](members.md) to grant the group |
+| `group_access` | integer | yes | The [access level](members.md#valid-access-levels) to grant the group |
| `expires_at` | string | no | Share expiration date in ISO 8601 format: 2016-09-26 |
### Delete link sharing group with another group
diff --git a/doc/api/import.md b/doc/api/import.md
index 307796f8acb..54e7eb12ed1 100644
--- a/doc/api/import.md
+++ b/doc/api/import.md
@@ -29,3 +29,40 @@ Example response:
"full_name": "Administrator / my-repo"
}
```
+
+## Import repository from Bitbucket Server
+
+Import your projects from Bitbucket Server to GitLab via the API.
+
+NOTE: **Note:**
+The Bitbucket Project Key is only used for finding the repository in Bitbucket.
+You must specify a `target_namespace` if you want to import the repository to a GitLab group.
+If you do not specify `target_namespace`, the project will import to your personal user namespace.
+
+```plaintext
+POST /import/bitbucket_server
+```
+
+| Attribute | Type | Required | Description |
+|------------|---------|----------|---------------------|
+| `bitbucket_server_url` | string | yes | Bitbucket Server URL |
+| `bitbucket_server_username` | string | yes | Bitbucket Server Username |
+| `personal_access_token` | string | yes | Bitbucket Server personal access token/password |
+| `bitbucket_server_project` | string | yes | Bitbucket Project Key |
+| `bitbucket_server_repo` | string | yes | Bitbucket Repository Name |
+| `new_name` | string | no | New repo name |
+| `target_namespace` | string | no | Namespace to import repo into |
+
+```shell
+curl --request POST \
+ --url https://gitlab.example.com/api/v4/import/bitbucket_server \
+ --header "content-type: application/json" \
+ --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" \
+ --data '{
+ "bitbucket_server_url": "http://bitbucket.example.com",
+ "bitbucket_server_username": "root",
+ "personal_access_token": "Nzk4MDcxODY4MDAyOiP8y410zF3tGAyLnHRv/E0+3xYs",
+ "bitbucket_server_project": "NEW",
+ "bitbucket_server_repo": "my-repo"
+}'
+```
diff --git a/doc/api/instance_clusters.md b/doc/api/instance_clusters.md
new file mode 100644
index 00000000000..1108550eee7
--- /dev/null
+++ b/doc/api/instance_clusters.md
@@ -0,0 +1,293 @@
+# Instance clusters API
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/36001) in GitLab 13.2.
+
+NOTE: **Note:**
+User will need admin access to use these endpoints.
+
+Use these API endpoints with your instance clusters, which enable you to use the same cluster across multiple projects. [More information](../user/instance/clusters/index.md)
+
+## List instance clusters
+
+Returns a list of instance clusters.
+
+```plaintext
+GET /admin/clusters
+```
+
+Example request:
+
+```shell
+curl --header "Private-Token: <your_access_token>" "https://gitlab.example.com/api/v4/admin/clusters"
+```
+
+Example response:
+
+```json
+[
+ {
+ "id": 9,
+ "name": "cluster-1",
+ "created_at": "2020-07-14T18:36:10.440Z",
+ "domain": null,
+ "provider_type": "user",
+ "platform_type": "kubernetes",
+ "environment_scope": "*",
+ "cluster_type": "instance_type",
+ "user": {
+ "id": 1,
+ "name": "Administrator",
+ "username": "root",
+ "state": "active",
+ "avatar_url": "https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon",
+ "web_url": "https://gitlab.example.com/root"
+ },
+ "platform_kubernetes": {
+ "api_url": "https://example.com",
+ "namespace": null,
+ "authorization_type": "rbac",
+ "ca_cert":"-----BEGIN CERTIFICATE-----IxMDM1MV0ZDJkZjM...-----END CERTIFICATE-----"
+ },
+ "provider_gcp": null,
+ "management_project": null
+ },
+ {
+ "id": 10,
+ "name": "cluster-2",
+ "created_at": "2020-07-14T18:39:05.383Z",
+ "domain": null,
+ "provider_type": "user",
+ "platform_type": "kubernetes",
+ "environment_scope": "staging",
+ "cluster_type": "instance_type",
+ "user": {
+ "id": 1,
+ "name": "Administrator",
+ "username": "root",
+ "state": "active",
+ "avatar_url": "https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon",
+ "web_url": "https://gitlab.example.com/root"
+ },
+ "platform_kubernetes": {
+ "api_url": "https://example.com",
+ "namespace": null,
+ "authorization_type": "rbac",
+ "ca_cert":"-----BEGIN CERTIFICATE-----LzEtMCadtaLGxcsGAZjM...-----END CERTIFICATE-----"
+ },
+ "provider_gcp": null,
+ "management_project": null
+ }
+ {
+ "id": 11,
+ "name": "cluster-3",
+ ...
+ }
+]
+
+```
+
+## Get a single instance cluster
+
+Returns a single instance cluster.
+
+Parameters:
+
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `cluster_id` | integer | yes | The ID of the cluster |
+
+```plaintext
+GET /admin/clusters/:cluster_id
+```
+
+Example request:
+
+```shell
+curl --header "Private-Token: <your_access_token>" "https://gitlab.example.com/api/v4/admin/clusters/9"
+```
+
+Example response:
+
+```json
+{
+ "id": 9,
+ "name": "cluster-1",
+ "created_at": "2020-07-14T18:36:10.440Z",
+ "domain": null,
+ "provider_type": "user",
+ "platform_type": "kubernetes",
+ "environment_scope": "*",
+ "cluster_type": "instance_type",
+ "user": {
+ "id": 1,
+ "name": "Administrator",
+ "username": "root",
+ "state": "active",
+ "avatar_url": "https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon",
+ "web_url": "https://gitlab.example.com/root"
+ },
+ "platform_kubernetes": {
+ "api_url": "https://example.com",
+ "namespace": null,
+ "authorization_type": "rbac",
+ "ca_cert":"-----BEGIN CERTIFICATE-----IxMDM1MV0ZDJkZjM...-----END CERTIFICATE-----"
+ },
+ "provider_gcp": null,
+ "management_project": null
+}
+```
+
+## Add existing instance cluster
+
+Adds an existing Kubernetes instance cluster.
+
+```plaintext
+POST /admin/clusters/add
+```
+
+Parameters:
+
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `name` | string | yes | The name of the cluster |
+| `domain` | string | no | The [base domain](../user/project/clusters/index.md#base-domain) of the cluster |
+| `environment_scope` | string | no | The associated environment to the cluster. Defaults to `*` |
+| `management_project_id` | integer | no | The ID of the [management project](../user/clusters/management_project.md) for the cluster |
+| `enabled` | boolean | no | Determines if cluster is active or not, defaults to true |
+| `managed` | boolean | no | Determines if GitLab will manage namespaces and service accounts for this cluster, defaults to true |
+| `platform_kubernetes_attributes[api_url]` | string | yes | The URL to access the Kubernetes API |
+| `platform_kubernetes_attributes[token]` | string | yes | The token to authenticate against Kubernetes |
+| `platform_kubernetes_attributes[ca_cert]` | string | no | TLS certificate. Required if API is using a self-signed TLS certificate. |
+| `platform_kubernetes_attributes[namespace]` | string | no | The unique namespace related to the project |
+| `platform_kubernetes_attributes[authorization_type]` | string | no | The cluster authorization type: `rbac`, `abac` or `unknown_authorization`. Defaults to `rbac`. |
+
+Example request:
+
+```shell
+curl --header "Private-Token:<your_access_token>" "http://gitlab.example.com/api/v4/admin/clusters/add" \
+-H "Accept:application/json" \
+-H "Content-Type:application/json" \
+-X POST --data '{"name":"cluster-3", "environment_scope":"production", "platform_kubernetes_attributes":{"api_url":"https://example.com", "token":"12345", "ca_cert":"-----BEGIN CERTIFICATE-----qpoeiXXZafCM0ZDJkZjM...-----END CERTIFICATE-----"}}'
+
+```
+
+Example response:
+
+```json
+{
+ "id": 11,
+ "name": "cluster-3",
+ "created_at": "2020-07-14T18:42:50.805Z",
+ "domain": null,
+ "provider_type": "user",
+ "platform_type": "kubernetes",
+ "environment_scope": "production",
+ "cluster_type": "instance_type",
+ "user": {
+ "id": 1,
+ "name": "Administrator",
+ "username": "root",
+ "state": "active",
+ "avatar_url": "https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon",
+ "web_url": "http://gitlab.example.com:3000/root"
+ },
+ "platform_kubernetes": {
+ "api_url": "https://example.com",
+ "namespace": null,
+ "authorization_type": "rbac",
+ "ca_cert":"-----BEGIN CERTIFICATE-----qpoeiXXZafCM0ZDJkZjM...-----END CERTIFICATE-----"
+ },
+ "provider_gcp": null,
+ "management_project": null
+}
+```
+
+## Edit instance cluster
+
+Updates an existing instance cluster.
+
+```shell
+PUT /admin/clusters/:cluster_id
+```
+
+Parameters:
+
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `cluster_id` | integer | yes | The ID of the cluster |
+| `name` | string | no | The name of the cluster |
+| `domain` | string | no | The [base domain](../user/project/clusters/index.md#base-domain) of the cluster |
+| `environment_scope` | string | no | The associated environment to the cluster |
+| `management_project_id` | integer | no | The ID of the [management project](../user/clusters/management_project.md) for the cluster |
+| `enabled` | boolean | no | Determines if cluster is active or not, defaults to true |
+| `platform_kubernetes_attributes[api_url]` | string | no | The URL to access the Kubernetes API |
+| `platform_kubernetes_attributes[token]` | string | no | The token to authenticate against Kubernetes |
+| `platform_kubernetes_attributes[ca_cert]` | string | no | TLS certificate. Required if API is using a self-signed TLS certificate. |
+| `platform_kubernetes_attributes[namespace]` | string | no | The unique namespace related to the project |
+
+NOTE: **Note:**
+`name`, `api_url`, `ca_cert` and `token` can only be updated if the cluster was added
+through the [Add existing Kubernetes cluster](../user/project/clusters/add_remove_clusters.md#add-existing-cluster) option or
+through the [Add existing instance cluster](#add-existing-instance-cluster) endpoint.
+
+Example request:
+
+```shell
+curl --header "Private-Token: <your_access_token>" "http://gitlab.example.com/api/v4/admin/clusters/9" \
+-H "Content-Type:application/json" \
+-X PUT --data '{"name":"update-cluster-name", "platform_kubernetes_attributes":{"api_url":"https://new-example.com","token":"new-token"}}'
+
+```
+
+Example response:
+
+```json
+{
+ "id": 9,
+ "name": "update-cluster-name",
+ "created_at": "2020-07-14T18:36:10.440Z",
+ "domain": null,
+ "provider_type": "user",
+ "platform_type": "kubernetes",
+ "environment_scope": "*",
+ "cluster_type": "instance_type",
+ "user": {
+ "id": 1,
+ "name": "Administrator",
+ "username": "root",
+ "state": "active",
+ "avatar_url": "https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon",
+ "web_url": "https://gitlab.example.com/root"
+ },
+ "platform_kubernetes": {
+ "api_url": "https://new-example.com",
+ "namespace": null,
+ "authorization_type": "rbac",
+ "ca_cert":"-----BEGIN CERTIFICATE-----IxMDM1MV0ZDJkZjM...-----END CERTIFICATE-----"
+ },
+ "provider_gcp": null,
+ "management_project": null,
+ "project": null
+}
+
+```
+
+## Delete instance cluster
+
+Deletes an existing instance cluster.
+
+```plaintext
+DELETE /admin/clusters/:cluster_id
+```
+
+Parameters:
+
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `cluster_id` | integer | yes | The ID of the cluster |
+
+Example request:
+
+```shell
+curl --request DELETE --header "Private-Token: <your_access_token>" "https://gitlab.example.com/api/v4/admin/clusters/11"
+```
diff --git a/doc/api/instance_level_ci_variables.md b/doc/api/instance_level_ci_variables.md
index 72d20109fbd..ceaf7e30c48 100644
--- a/doc/api/instance_level_ci_variables.md
+++ b/doc/api/instance_level_ci_variables.md
@@ -1,10 +1,7 @@
# Instance-level CI/CD variables API
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/14108) in GitLab 13.0
-> - It's deployed behind a feature flag, enabled by default.
-> - It's enabled on GitLab.com.
-> - It's recommended for production use.
-> - For GitLab self-managed instances, GitLab administrators can opt to [disable it](#enable-or-disable-instance-level-cicd-variables-core-only). **(CORE ONLY)**
+> - [Feature flag removed](https://gitlab.com/gitlab-org/gitlab/-/issues/218249) in GitLab 13.2.
## List all instance variables
@@ -140,22 +137,3 @@ DELETE /admin/ci/variables/:key
```shell
curl --request DELETE --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/admin/ci/variables/VARIABLE_1"
```
-
-### Enable or disable instance-level CI/CD variables **(CORE ONLY)**
-
-Instance-level CI/CD variables is under development but ready for production use.
-It is deployed behind a feature flag that is **enabled by default**.
-[GitLab administrators with access to the GitLab Rails console](../administration/feature_flags.md)
-can opt to disable it for your instance.
-
-To disable it:
-
-```ruby
-Feature.disable(:ci_instance_level_variables)
-```
-
-To enable it:
-
-```ruby
-Feature.enable(:ci_instance_level_variables)
-```
diff --git a/doc/api/issues.md b/doc/api/issues.md
index f640300e3ae..22f5d994f85 100644
--- a/doc/api/issues.md
+++ b/doc/api/issues.md
@@ -6,8 +6,6 @@ info: To determine the technical writer assigned to the Stage/Group associated w
# Issues API
-Every API call to issues must be authenticated.
-
If a user is not a member of a project and the project is private, a `GET`
request on that project will result in a `404` status code.
@@ -18,11 +16,11 @@ are paginated.
Read more on [pagination](README.md#pagination).
-CAUTION: **Deprecation**
+CAUTION: **Deprecation:**
> `reference` attribute in response is deprecated in favour of `references`.
> Introduced [GitLab 12.6](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/20354)
-NOTE: **Note**
+NOTE: **Note:**
> `references.relative` is relative to the group / project that the issue is being requested. When issue is fetched from its project
> `relative` format would be the same as `short` format and when requested across groups / projects it is expected to be the same as `full` format.
@@ -72,7 +70,7 @@ GET /issues?confidential=true
| `updated_after` | datetime | no | Return issues updated on or after the given time |
| `updated_before` | datetime | no | Return issues updated on or before the given time |
| `confidential` | boolean | no | Filter confidential or public issues. |
-| `not` | Hash | no | Return issues that do not match the parameters supplied. Accepts: `labels`, `milestone`, `author_id`, `author_username`, `assignee_id`, `assignee_username`, `my_reaction_emoji`, `search`, `in` |
+| `not` | Hash | no | Return issues that do not match the parameters supplied. Accepts: `labels`, `milestone`, `author_id`, `author_username`, `assignee_id`, `assignee_username`, `my_reaction_emoji` |
| `non_archived` | boolean | no | Return issues only from non-archived projects. If `false`, response will return issues from both archived and non-archived projects. Default is `true`. _(Introduced in [GitLab 13.0](https://gitlab.com/gitlab-org/gitlab/-/issues/197170))_ |
```shell
@@ -188,6 +186,9 @@ the `weight` parameter:
Get a list of a group's issues.
+If the group is private, credentials will need to be provided for authorization.
+The preferred way to do this, is by using [personal access tokens](../user/profile/personal_access_tokens.md).
+
```plaintext
GET /groups/:id/issues
GET /groups/:id/issues?state=opened
@@ -343,6 +344,9 @@ the `weight` parameter:
Get a list of a project's issues.
+If the project is private, credentials will need to be provided for authorization.
+The preferred way to do this, is by using [personal access tokens](../user/profile/personal_access_tokens.md).
+
```plaintext
GET /projects/:id/issues
GET /projects/:id/issues?state=opened
@@ -504,6 +508,9 @@ the `weight` parameter:
Get a single project issue.
+If the project is private or the issue is confidential, credentials will need to be provided for authorization.
+The preferred way to do this, is by using [personal access tokens](../user/profile/personal_access_tokens.md).
+
```plaintext
GET /projects/:id/issues/:issue_iid
```
@@ -613,14 +620,13 @@ the `weight` parameter:
}
```
-Users on GitLab [Ultimate](https://about.gitlab.com/pricing/) will additionally see
+Users on GitLab [Premium](https://about.gitlab.com/pricing/) will additionally see
the `epic` property:
```javascript
{
"project_id" : 4,
"description" : "Omnis vero earum sunt corporis dolor et placeat.",
- "epic": {
"epic_iid" : 5, //deprecated, use `iid` of the `epic` attribute
"epic": {
"id" : 42,
@@ -663,8 +669,8 @@ POST /projects/:id/issues
| `merge_request_to_resolve_discussions_of` | integer | no | The IID of a merge request in which to resolve all issues. This will fill the issue with a default description and mark all discussions as resolved. When passing a description or title, these values will take precedence over the default values.|
| `discussion_to_resolve` | string | no | The ID of a discussion to resolve. This will fill in the issue with a default description and mark the discussion as resolved. Use in combination with `merge_request_to_resolve_discussions_of`. |
| `weight` **(STARTER)** | integer | no | The weight of the issue. Valid values are greater than or equal to 0. |
-| `epic_id` **(ULTIMATE)** | integer | no | ID of the epic to add the issue to. Valid values are greater than or equal to 0. |
-| `epic_iid` **(ULTIMATE)** | integer | no | IID of the epic to add the issue to. Valid values are greater than or equal to 0. (deprecated, [will be removed in version 5](https://gitlab.com/gitlab-org/gitlab/-/issues/35157)) |
+| `epic_id` **(PREMIUM)** | integer | no | ID of the epic to add the issue to. Valid values are greater than or equal to 0. |
+| `epic_iid` **(PREMIUM)** | integer | no | IID of the epic to add the issue to. Valid values are greater than or equal to 0. (deprecated, [will be removed in version 5](https://gitlab.com/gitlab-org/gitlab/-/issues/35157)) |
```shell
curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/4/issues?title=Issues%20with%20auth&labels=bug"
@@ -781,8 +787,8 @@ PUT /projects/:id/issues/:issue_iid
| `due_date` | string | no | Date time string in the format YEAR-MONTH-DAY, for example `2016-03-11` |
| `weight` **(STARTER)** | integer | no | The weight of the issue. Valid values are greater than or equal to 0. 0 |
| `discussion_locked` | boolean | no | Flag indicating if the issue's discussion is locked. If the discussion is locked only project members can add or edit comments. |
-| `epic_id` **(ULTIMATE)** | integer | no | ID of the epic to add the issue to. Valid values are greater than or equal to 0. |
-| `epic_iid` **(ULTIMATE)** | integer | no | IID of the epic to add the issue to. Valid values are greater than or equal to 0. (deprecated, [will be removed in version 5](https://gitlab.com/gitlab-org/gitlab/-/issues/35157)) |
+| `epic_id` **(PREMIUM)** | integer | no | ID of the epic to add the issue to. Valid values are greater than or equal to 0. |
+| `epic_iid` **(PREMIUM)** | integer | no | IID of the epic to add the issue to. Valid values are greater than or equal to 0. (deprecated, [will be removed in version 5](https://gitlab.com/gitlab-org/gitlab/-/issues/35157)) |
```shell
curl --request PUT --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/4/issues/85?state_event=close"
@@ -871,10 +877,10 @@ the `weight` parameter:
NOTE: **Note:**
At least one of following parameters is required to be passed for the request to be successful: `:assignee_id`, `:assignee_ids`, `:confidential`, `:created_at`, `:description`, `:discussion_locked`, `:due_date`, `:labels`, `:milestone_id`, `:state_event`, or `:title`.
-NOTE: **Note**:
+NOTE: **Note:**
`assignee` column is deprecated. We now show it as a single-sized array `assignees` to conform to the GitLab EE API.
-NOTE: **Note**:
+NOTE: **Note:**
The `closed_by` attribute was [introduced in GitLab 10.6](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/17042). This value will only be present for issues which were closed after GitLab 10.6 and when the user account that closed the issue still exists.
## Delete an issue
@@ -894,6 +900,27 @@ DELETE /projects/:id/issues/:issue_iid
curl --request DELETE --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/4/issues/85"
```
+## Reorder an issue
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/211864) as a [community contribution](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/35349) in GitLab 13.2.
+
+Reorders an issue, you can see the results when sorting issues manually
+
+```plaintext
+PUT /projects/:id/issues/:issue_iid/reorder
+```
+
+| Attribute | Type | Required | Description |
+|-------------|---------|----------|--------------------------------------|
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
+| `issue_iid` | integer | yes | The internal ID of a project's issue |
+| `move_after_id` | integer | no | The ID of a projet's issue to move this issue after |
+| `move_before_id` | integer | no | The ID of a projet's issue to move this issue before |
+
+```shell
+curl --request PUT --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/4/issues/85/reorder?move_after_id=51&move_before_id=92"
+```
+
## Move an issue
Moves an issue to a different project. If the target project
@@ -1413,6 +1440,9 @@ Example response:
## Get time tracking stats
+If the project is private or the issue is confidential, credentials will need to be provided for authorization.
+The preferred way to do this, is by using [personal access tokens](../user/profile/personal_access_tokens.md).
+
```plaintext
GET /projects/:id/issues/:issue_iid/time_stats
```
@@ -1441,6 +1471,9 @@ Example response:
Get all the merge requests that are related to the issue.
+If the project is private or the issue is confidential, credentials will need to be provided for authorization.
+The preferred way to do this, is by using [personal access tokens](../user/profile/personal_access_tokens.md).
+
```plaintext
GET /projects/:id/issues/:issue_id/related_merge_requests
```
@@ -1597,6 +1630,9 @@ Example response:
Get all the merge requests that will close issue when merged.
+If the project is private or the issue is confidential, credentials will need to be provided for authorization.
+The preferred way to do this, is by using [personal access tokens](../user/profile/personal_access_tokens.md).
+
```plaintext
GET /projects/:id/issues/:issue_iid/closed_by
```
@@ -1670,6 +1706,9 @@ Example response:
## Participants on issues
+If the project is private or the issue is confidential, credentials will need to be provided for authorization.
+The preferred way to do this, is by using [personal access tokens](../user/profile/personal_access_tokens.md).
+
```plaintext
GET /projects/:id/issues/:issue_iid/participants
```
diff --git a/doc/api/labels.md b/doc/api/labels.md
index 3ab059fca7c..30290f18653 100644
--- a/doc/api/labels.md
+++ b/doc/api/labels.md
@@ -199,7 +199,8 @@ DELETE /projects/:id/labels/:label_id
curl --request DELETE --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/1/labels/bug"
```
-NOTE: **Note:** An older endpoint `DELETE /projects/:id/labels` with `name` in the parameters is still available, but deprecated.
+NOTE: **Note:**
+An older endpoint `DELETE /projects/:id/labels` with `name` in the parameters is still available, but deprecated.
## Edit an existing label
@@ -242,7 +243,8 @@ Example response:
}
```
-NOTE: **Note:** An older endpoint `PUT /projects/:id/labels` with `name` or `label_id` in the parameters is still available, but deprecated.
+NOTE: **Note:**
+An older endpoint `PUT /projects/:id/labels` with `name` or `label_id` in the parameters is still available, but deprecated.
## Promote a project label to a group label
@@ -279,7 +281,8 @@ Example response:
}
```
-NOTE: **Note:** An older endpoint `PUT /projects/:id/labels/promote` with `name` in the parameters is still available, but deprecated.
+NOTE: **Note:**
+An older endpoint `PUT /projects/:id/labels/promote` with `name` in the parameters is still available, but deprecated.
## Subscribe to a label
diff --git a/doc/api/members.md b/doc/api/members.md
index dadd609b7ed..8cd7bafdd77 100644
--- a/doc/api/members.md
+++ b/doc/api/members.md
@@ -1,6 +1,6 @@
# Group and project members API
-**Valid access levels**
+## Valid access levels
The access levels are defined in the `Gitlab::Access` module. Currently, these levels are recognized:
@@ -290,7 +290,7 @@ Example response:
### Set override flag for a member of a group
-> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/4875) in GitLab 12.10.
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/4875) in GitLab 13.0.
By default, the access level of LDAP group members is set to the value specified
by LDAP through Group Sync. You can allow access level overrides by calling this endpoint.
@@ -326,7 +326,7 @@ Example response:
### Remove override for a member of a group
-> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/4875) in GitLab 12.10.
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/4875) in GitLab 13.0.
Sets the override flag to false and allows LDAP Group Sync to reset the access
level to the LDAP-prescribed value.
@@ -373,6 +373,7 @@ DELETE /projects/:id/members/:user_id
| --------- | ---- | -------- | ----------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the project or group](README.md#namespaced-path-encoding) owned by the authenticated user |
| `user_id` | integer | yes | The user ID of the member |
+| `unassign_issuables` | boolean | false | Flag indicating if the removed member should be unassigned from any issues or merge requests within given group or project |
```shell
curl --request DELETE --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/groups/:id/members/:user_id"
diff --git a/doc/api/merge_requests.md b/doc/api/merge_requests.md
index 41c0428485f..959cf87ba62 100644
--- a/doc/api/merge_requests.md
+++ b/doc/api/merge_requests.md
@@ -2,11 +2,11 @@
Every API call to merge requests must be authenticated.
-CAUTION: **Deprecation**
+CAUTION: **Deprecation:**
> `reference` attribute in response is deprecated in favour of `references`.
> Introduced [GitLab 12.6](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/20354)
-NOTE: **Note**
+NOTE: **Note:**
> `references.relative` is relative to the group / project that the merge request is being requested. When merge request is fetched from its project
> `relative` format would be the same as `short` format and when requested across groups / projects it is expected to be the same as `full` format.
@@ -64,6 +64,7 @@ Parameters:
| `search` | string | no | Search merge requests against their `title` and `description` |
| `in` | string | no | Modify the scope of the `search` attribute. `title`, `description`, or a string joining them with comma. Default is `title,description` |
| `wip` | string | no | Filter merge requests against their `wip` status. `yes` to return *only* WIP merge requests, `no` to return *non* WIP merge requests |
+| `not` | Hash | no | Return merge requests that do not match the parameters supplied. Accepts: `labels`, `milestone`, `author_id`, `author_username`, `assignee_id`, `assignee_username`, `my_reaction_emoji` |
NOTE: **Note:**
[Starting in GitLab 13.0](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/31890),
@@ -1302,7 +1303,7 @@ curl --request DELETE --header "PRIVATE-TOKEN: <your_access_token>" "https://git
Merge changes submitted with MR using this API.
-If merge request is unable to be accepted (ie: Work in Progress, Closed, Pipeline Pending Completion, or Failed while requiring Success) - you'll get a `405` and the error message 'Method Not Allowed'
+If merge request is unable to be accepted (ie: Draft, Closed, Pipeline Pending Completion, or Failed while requiring Success) - you'll get a `405` and the error message 'Method Not Allowed'
If it has some conflicts and can not be merged - you'll get a `406` and the error message 'Branch cannot be merged'
diff --git a/doc/api/metrics_dashboard_annotations.md b/doc/api/metrics_dashboard_annotations.md
index 05bf7156a7e..10dfd3d1c3b 100644
--- a/doc/api/metrics_dashboard_annotations.md
+++ b/doc/api/metrics_dashboard_annotations.md
@@ -1,3 +1,10 @@
+---
+stage: Monitor
+group: APM
+info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers
+type: concepts, howto
+---
+
# Dashboard annotations API
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/29089) in GitLab 12.10 behind a disabled feature flag.
diff --git a/doc/api/metrics_user_starred_dashboards.md b/doc/api/metrics_user_starred_dashboards.md
index dd9144d1319..df9cdd3b0e4 100644
--- a/doc/api/metrics_user_starred_dashboards.md
+++ b/doc/api/metrics_user_starred_dashboards.md
@@ -1,3 +1,10 @@
+---
+stage: Monitor
+group: APM
+info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers
+type: concepts, howto
+---
+
# User-starred metrics dashboards API
The starred dashboard feature makes navigating to frequently-used dashboards easier
@@ -15,6 +22,7 @@ Parameters:
| Attribute | Type | Required | Description |
|:---------------|:---------------|:---------|:-----------------------------------------------------------------------------|
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) |
| `dashboard_path` | string | yes | URL-encoded path to file defining the dashboard which should be marked as favorite. |
```shell
@@ -45,6 +53,7 @@ Parameters:
| Attribute | Type | Required | Description |
|:---------------|:---------------|:---------|:-----------------------------------------------------------------------------|
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) |
| `dashboard_path` | string | no | URL-encoded path to file defining the dashboard which should no longer be marked as favorite. When not supplied all dashboards within given projects will be removed from favorites. |
```shell
diff --git a/doc/api/namespaces.md b/doc/api/namespaces.md
index d1a2812bfb4..e38e725fb97 100644
--- a/doc/api/namespaces.md
+++ b/doc/api/namespaces.md
@@ -68,7 +68,8 @@ the `plan` parameter associated with a namespace:
]
```
-NOTE: **Note:** Only group maintainers/owners are presented with `members_count_with_descendants`, as well as `plan` **(BRONZE ONLY)**.
+NOTE: **Note:**
+Only group maintainers/owners are presented with `members_count_with_descendants`, as well as `plan` **(BRONZE ONLY)**.
## Search for namespace
diff --git a/doc/api/notes.md b/doc/api/notes.md
index 74d941edec1..9a75b950f28 100644
--- a/doc/api/notes.md
+++ b/doc/api/notes.md
@@ -116,6 +116,7 @@ Parameters:
- `id` (required) - The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding)
- `issue_iid` (required) - The IID of an issue
- `body` (required) - The content of a note. Limited to 1,000,000 characters.
+- `confidential` (optional) - The confidential flag of a note. Default is false.
- `created_at` (optional) - Date time string, ISO 8601 formatted, e.g. 2016-03-11T03:45:40Z (requires admin or project/group owner rights)
```shell
diff --git a/doc/api/packages.md b/doc/api/packages.md
index ca7113bc743..19828208a26 100644
--- a/doc/api/packages.md
+++ b/doc/api/packages.md
@@ -80,7 +80,7 @@ GET /groups/:id/packages
curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/groups/:id/packages?exclude_subgroups=true"
```
-CAUTION: **Deprecation**
+CAUTION: **Deprecation:**
> The `build_info` attribute in the response is deprecated in favour of `pipeline`.
> Introduced [GitLab 12.10](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/28040).
@@ -165,7 +165,7 @@ GET /projects/:id/packages/:package_id
curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/:id/packages/:package_id"
```
-CAUTION: **Deprecation**
+CAUTION: **Deprecation:**
> The `build_info` attribute in the response is deprecated in favour of `pipeline`.
> Introduced [GitLab 12.10](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/28040).
diff --git a/doc/api/pipelines.md b/doc/api/pipelines.md
index e84d7663bdb..563829b8192 100644
--- a/doc/api/pipelines.md
+++ b/doc/api/pipelines.md
@@ -142,7 +142,7 @@ Example of response
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/202525) in GitLab 13.0.
CAUTION: **Caution:**
-This API route is part of the [JUnit test report](../ci/junit_test_reports.md) feature. It is protected by a [feature flag](../development/feature_flags/index.md) that is **disabled** due to performance issues with very large data sets. See [the documentation for the feature](../ci/junit_test_reports.md#enabling-the-feature) for further details.
+This API route is part of the [JUnit test report](../ci/junit_test_reports.md) feature. It is protected by a [feature flag](../development/feature_flags/index.md) that is **disabled** due to performance issues with very large data sets.
```plaintext
GET /projects/:id/pipelines/:pipeline_id/test_report
diff --git a/doc/api/project_clusters.md b/doc/api/project_clusters.md
index f7c8ffc8df6..04694157561 100644
--- a/doc/api/project_clusters.md
+++ b/doc/api/project_clusters.md
@@ -1,3 +1,9 @@
+---
+stage: Configure
+group: Configure
+info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers
+---
+
# Project clusters API
> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/23922) in GitLab 11.7.
@@ -22,7 +28,7 @@ Parameters:
Example request:
```shell
-curl --header 'Private-Token: <your_access_token>' "https://gitlab.example.com/api/v4/projects/26/clusters"
+curl --header "Private-Token: <your_access_token>" "https://gitlab.example.com/api/v4/projects/26/clusters"
```
Example response:
@@ -192,7 +198,7 @@ Parameters:
Example request:
```shell
-curl --header 'Private-Token: <your_access_token>' "https://gitlab.example.com/api/v4/projects/26/clusters/user" \
+curl --header "Private-Token: <your_access_token>" "https://gitlab.example.com/api/v4/projects/26/clusters/user" \
-H "Accept: application/json" \
-H "Content-Type:application/json" \
-X POST --data '{"name":"cluster-5", "platform_kubernetes_attributes":{"api_url":"https://35.111.51.20","token":"12345","namespace":"cluster-5-namespace","ca_cert":"-----BEGIN CERTIFICATE-----\r\nhFiK1L61owwDQYJKoZIhvcNAQELBQAw\r\nLzEtMCsGA1UEAxMkZDA1YzQ1YjctNzdiMS00NDY0LThjNmEtMTQ0ZDJkZjM4ZDBj\r\nMB4XDTE4MTIyNzIwMDM1MVoXDTIzMTIyNjIxMDM1MVowLzEtMCsGA1UEAxMkZDA1\r\nYzQ1YjctNzdiMS00NDY0LThjNmEtMTQ0ZDJkZjM.......-----END CERTIFICATE-----"}}'
@@ -289,7 +295,7 @@ through the ["Add existing cluster to project"](#add-existing-cluster-to-project
Example request:
```shell
-curl --header 'Private-Token: <your_access_token>' "https://gitlab.example.com/api/v4/projects/26/clusters/24" \
+curl --header "Private-Token: <your_access_token>" "https://gitlab.example.com/api/v4/projects/26/clusters/24" \
-H "Content-Type:application/json" \
-X PUT --data '{"name":"new-cluster-name","domain":"new-domain.com","api_url":"https://new-api-url.com"}'
```
@@ -383,5 +389,5 @@ Parameters:
Example request:
```shell
-curl --request DELETE --header 'Private-Token: <your_access_token>' "https://gitlab.example.com/api/v4/projects/26/clusters/23"
+curl --request DELETE --header "Private-Token: <your_access_token>" "https://gitlab.example.com/api/v4/projects/26/clusters/23"
```
diff --git a/doc/api/project_level_variables.md b/doc/api/project_level_variables.md
index fbeba9d6c7d..407e506e082 100644
--- a/doc/api/project_level_variables.md
+++ b/doc/api/project_level_variables.md
@@ -43,6 +43,7 @@ GET /projects/:id/variables/:key
|-----------|---------|----------|-----------------------|
| `id` | integer/string | yes | The ID of a project or [urlencoded NAMESPACE/PROJECT_NAME of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
| `key` | string | yes | The `key` of a variable |
+| `filter` | hash | no | Available filters: `[environment_scope]`. See the [`filter` parameter details](#the-filter-parameter). |
```shell
curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/1/variables/TEST_VARIABLE_1"
@@ -108,6 +109,7 @@ PUT /projects/:id/variables/:key
| `protected` | boolean | no | Whether the variable is protected |
| `masked` | boolean | no | Whether the variable is masked |
| `environment_scope` | string | no | The `environment_scope` of the variable |
+| `filter` | hash | no | Available filters: `[environment_scope]`. See the [`filter` parameter details](#the-filter-parameter). |
```shell
curl --request PUT --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/1/variables/NEW_VARIABLE" --form "value=updated value"
@@ -136,7 +138,40 @@ DELETE /projects/:id/variables/:key
|-----------|---------|----------|-------------------------|
| `id` | integer/string | yes | The ID of a project or [urlencoded NAMESPACE/PROJECT_NAME of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
| `key` | string | yes | The `key` of a variable |
+| `filter` | hash | no | Available filters: `[environment_scope]`. See the [`filter` parameter details](#the-filter-parameter). |
```shell
curl --request DELETE --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/1/variables/VARIABLE_1"
```
+
+## The `filter` parameter
+
+> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/34490) in GitLab 13.2.
+> - It's deployed behind a feature flag, disabled by default.
+> - It's disabled on GitLab.com.
+> - To use it in GitLab self-managed instances, ask a GitLab administrator to enable it.
+
+This parameter is used for filtering by attributes, such as `environment_scope`.
+
+Example usage:
+
+```shell
+curl --request DELETE --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/1/variables/VARIABLE_1?filter[environment_scope]=production"
+```
+
+### Enable or disable
+
+[GitLab administrators with access to the GitLab Rails console](../administration/feature_flags.md)
+can enable it for your instance.
+
+To enable it:
+
+```ruby
+Feature.enable(:ci_variables_api_filter_environment_scope)
+```
+
+To disable it:
+
+```ruby
+Feature.disable(:ci_variables_api_filter_environment_scope)
+```
diff --git a/doc/api/project_repository_storage_moves.md b/doc/api/project_repository_storage_moves.md
index c55d4a19feb..f7fb361bf53 100644
--- a/doc/api/project_repository_storage_moves.md
+++ b/doc/api/project_repository_storage_moves.md
@@ -5,11 +5,12 @@ info: To determine the technical writer assigned to the Stage/Group associated w
type: reference
---
-# Project repository storage move API
+# Project repository storage moves API **(CORE ONLY)**
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/31285) in GitLab 13.0.
-Project repository storage can be moved. To retrieve project repository storage moves using the API, you must [authenticate yourself](README.md#authentication) as an administrator.
+Project repository storage can be moved. To retrieve project repository storage moves using the API,
+you must [authenticate yourself](README.md#authentication) as an administrator.
## Retrieve all project repository storage moves
@@ -23,7 +24,7 @@ are [paginated](README.md#pagination).
Example request:
```shell
-curl --header "PRIVATE-TOKEN: <your_access_token>" 'https://gitlab.example.com/api/v4/project_repository_storage_moves'
+curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/project_repository_storage_moves"
```
Example response:
@@ -66,7 +67,7 @@ Parameters:
Example request:
```shell
-curl --header "PRIVATE-TOKEN: <your_access_token>" 'https://gitlab.example.com/api/v4/projects/1/repository_storage_moves'
+curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/1/repository_storage_moves"
```
Example response:
@@ -106,7 +107,7 @@ Parameters:
Example request:
```shell
-curl --header "PRIVATE-TOKEN: <your_access_token>" 'https://gitlab.example.com/api/v4/project_repository_storage_moves/1'
+curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/project_repository_storage_moves/1"
```
Example response:
@@ -145,7 +146,7 @@ Parameters:
Example request:
```shell
-curl --header "PRIVATE-TOKEN: <your_access_token>" 'https://gitlab.example.com/api/v4/projects/1/repository_storage_moves/1'
+curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/1/repository_storage_moves/1"
```
Example response:
@@ -185,7 +186,7 @@ Example request:
```shell
curl --request POST --header "PRIVATE_TOKEN: <your_access_token>" --header "Content-Type: application/json" \
---data '{"destination_storage_name":"storage2"}' 'https://gitlab.example.com/api/v4/projects/1/repository_storage_moves'
+--data '{"destination_storage_name":"storage2"}' "https://gitlab.example.com/api/v4/projects/1/repository_storage_moves"
```
Example response:
diff --git a/doc/api/project_snippets.md b/doc/api/project_snippets.md
index e5dd85fb3bb..fd8cbd6e256 100644
--- a/doc/api/project_snippets.md
+++ b/doc/api/project_snippets.md
@@ -183,6 +183,28 @@ curl "https://gitlab.com/api/v4/projects/:id/snippets/:snippet_id/raw" \
--header "PRIVATE-TOKEN: <your_access_token>"
```
+## Snippet repository file content
+
+Returns the raw file content as plain text.
+
+```plaintext
+GET /projects/:id/snippets/:snippet_id/files/:ref/:file_path/raw
+```
+
+Parameters:
+
+- `id` (required) - The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user
+- `snippet_id` (required) - The ID of a project's snippet
+- `ref` (required) - The name of a branch, tag or commit e.g. master
+- `file_path` (required) - The URL-encoded path to the file, e.g. snippet%2Erb
+
+Example request:
+
+```shell
+curl "https://gitlab.com/api/v4/projects/1/snippets/2/files/master/snippet%2Erb/raw" \
+ --header "PRIVATE-TOKEN: <your_access_token>"
+```
+
## Get user agent details
> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/issues/29508) in GitLab 9.4.
diff --git a/doc/api/projects.md b/doc/api/projects.md
index b9ba632cd9e..6257f37f0e6 100644
--- a/doc/api/projects.md
+++ b/doc/api/projects.md
@@ -45,7 +45,7 @@ GET /projects
| --------- | ---- | -------- | ----------- |
| `archived` | boolean | no | Limit by archived status |
| `visibility` | string | no | Limit by visibility `public`, `internal`, or `private` |
-| `order_by` | string | no | Return projects ordered by `id`, `name`, `path`, `created_at`, `updated_at`, or `last_activity_at` fields. Default is `created_at` |
+| `order_by` | string | no | Return projects ordered by `id`, `name`, `path`, `created_at`, `updated_at`, or `last_activity_at` fields. `repository_size`, `storage_size`, or `wiki_size` fields are only allowed for admins. Default is `created_at` |
| `sort` | string | no | Return projects sorted in `asc` or `desc` order. Default is `desc` |
| `search` | string | no | Return list of projects matching the search criteria |
| `search_namespaces` | boolean | no | Include ancestor namespaces when matching search criteria. Default is `false` |
@@ -60,11 +60,12 @@ GET /projects
| `with_programming_language` | string | no | Limit by projects which use the given programming language |
| `wiki_checksum_failed` | boolean | no | **(PREMIUM)** Limit projects where the wiki checksum calculation has failed ([Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/6137) in [GitLab Premium](https://about.gitlab.com/pricing/) 11.2) |
| `repository_checksum_failed` | boolean | no | **(PREMIUM)** Limit projects where the repository checksum calculation has failed ([Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/6137) in [GitLab Premium](https://about.gitlab.com/pricing/) 11.2) |
-| `min_access_level` | integer | no | Limit by current user minimal [access level](members.md) |
+| `min_access_level` | integer | no | Limit by current user minimal [access level](members.md#valid-access-levels) |
| `id_after` | integer | no | Limit results to projects with IDs greater than the specified ID |
| `id_before` | integer | no | Limit results to projects with IDs less than the specified ID |
| `last_activity_after` | datetime | no | Limit results to projects with last_activity after specified time. Format: ISO 8601 YYYY-MM-DDTHH:MM:SSZ |
| `last_activity_before` | datetime | no | Limit results to projects with last_activity before specified time. Format: ISO 8601 YYYY-MM-DDTHH:MM:SSZ |
+| `repository_storage` | string | no | Limit results to projects stored on repository_storage. Available for admins only. |
NOTE: **Note:**
This endpoint supports [keyset pagination](README.md#keyset-based-pagination) for selected `order_by` options.
@@ -174,7 +175,8 @@ When the user is authenticated and `simple` is not set this returns something li
"wiki_size" : 0,
"lfs_objects_size": 0,
"job_artifacts_size": 0,
- "packages_size": 0
+ "packages_size": 0,
+ "snippets_size": 0
},
"_links": {
"self": "http://example.com/api/v4/projects",
@@ -276,7 +278,8 @@ When the user is authenticated and `simple` is not set this returns something li
"wiki_size" : 0,
"lfs_objects_size": 0,
"job_artifacts_size": 0,
- "packages_size": 0
+ "packages_size": 0,
+ "snippets_size": 0
},
"_links": {
"self": "http://example.com/api/v4/projects",
@@ -348,7 +351,7 @@ GET /users/:user_id/projects
| `with_issues_enabled` | boolean | no | Limit by enabled issues feature |
| `with_merge_requests_enabled` | boolean | no | Limit by enabled merge requests feature |
| `with_programming_language` | string | no | Limit by projects which use the given programming language |
-| `min_access_level` | integer | no | Limit by current user minimal [access level](members.md) |
+| `min_access_level` | integer | no | Limit by current user minimal [access level](members.md#valid-access-levels) |
| `id_after` | integer | no | Limit results to projects with IDs greater than the specified ID |
| `id_before` | integer | no | Limit results to projects with IDs less than the specified ID |
@@ -425,7 +428,8 @@ This endpoint supports [keyset pagination](README.md#keyset-based-pagination) fo
"wiki_size" : 0,
"lfs_objects_size": 0,
"job_artifacts_size": 0,
- "packages_size": 0
+ "packages_size": 0,
+ "snippets_size": 0
},
"_links": {
"self": "http://example.com/api/v4/projects",
@@ -527,7 +531,8 @@ This endpoint supports [keyset pagination](README.md#keyset-based-pagination) fo
"wiki_size" : 0,
"lfs_objects_size": 0,
"job_artifacts_size": 0,
- "packages_size": 0
+ "packages_size": 0,
+ "snippets_size": 0
},
"_links": {
"self": "http://example.com/api/v4/projects",
@@ -566,7 +571,7 @@ GET /users/:user_id/starred_projects
| `with_custom_attributes` | boolean | no | Include [custom attributes](custom_attributes.md) in response (admins only). |
| `with_issues_enabled` | boolean | no | Limit by enabled issues feature. |
| `with_merge_requests_enabled` | boolean | no | Limit by enabled merge requests feature. |
-| `min_access_level` | integer | no | Limit by current user minimal [access level](members.md). |
+| `min_access_level` | integer | no | Limit by current user minimal [access level](members.md#valid-access-levels). |
```shell
curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/users/5/starred_projects"
@@ -890,6 +895,7 @@ GET /projects/:id
"suggestion_commit_message": null,
"marked_for_deletion_at": "2020-04-03", // Deprecated and will be removed in API v5 in favor of marked_for_deletion_on
"marked_for_deletion_on": "2020-04-03",
+ "compliance_frameworks": [ "sox" ],
"statistics": {
"commit_count": 37,
"storage_size": 1038090,
@@ -897,7 +903,8 @@ GET /projects/:id
"wiki_size" : 0,
"lfs_objects_size": 0,
"job_artifacts_size": 0,
- "packages_size": 0
+ "packages_size": 0,
+ "snippets_size": 0
},
"_links": {
"self": "http://example.com/api/v4/projects",
@@ -1045,7 +1052,7 @@ POST /projects
| `show_default_award_emojis` | boolean | no | Show default award emojis |
| `resolve_outdated_diff_discussions` | boolean | no | Automatically resolve merge request diffs discussions on lines changed with a push |
| `container_registry_enabled` | boolean | no | Enable container registry for this project |
-| `container_expiration_policy_attributes` | hash | no | Update the image expiration policy for this project. Accepts: `cadence` (string), `keep_n` (string), `older_than` (string), `name_regex` (string), `name_regex_delete` (string), `name_regex_keep` (string), `enabled` (boolean) |
+| `container_expiration_policy_attributes` | hash | no | Update the image cleanup policy for this project. Accepts: `cadence` (string), `keep_n` (string), `older_than` (string), `name_regex` (string), `name_regex_delete` (string), `name_regex_keep` (string), `enabled` (boolean) |
| `shared_runners_enabled` | boolean | no | Enable shared runners for this project |
| `visibility` | string | no | See [project visibility level](#project-visibility-level) |
| `import_url` | string | no | URL to import repository from |
@@ -1080,7 +1087,8 @@ POST /projects
| `group_with_project_templates_id` | integer | no | **(PREMIUM)** For group-level custom templates, specifies ID of group from which all the custom project templates are sourced. Leave empty for instance-level templates. Requires `use_custom_template` to be true |
| `packages_enabled` | boolean | no | **(PREMIUM ONLY)** Enable or disable packages repository feature |
-NOTE: **Note:** If your HTTP repository is not publicly accessible,
+NOTE: **Note:**
+If your HTTP repository is not publicly accessible,
add authentication information to the URL: `https://username:password@gitlab.company.com/group/project.git`
where `password` is a public access key with the `api` scope enabled.
@@ -1150,7 +1158,8 @@ POST /projects/user/:user_id
| `group_with_project_templates_id` | integer | no | **(PREMIUM)** For group-level custom templates, specifies ID of group from which all the custom project templates are sourced. Leave empty for instance-level templates. Requires `use_custom_template` to be true |
| `packages_enabled` | boolean | no | **(PREMIUM ONLY)** Enable or disable packages repository feature |
-NOTE: **Note:** If your HTTP repository is not publicly accessible,
+NOTE: **Note:**
+If your HTTP repository is not publicly accessible,
add authentication information to the URL: `https://username:password@gitlab.company.com/group/project.git`
where `password` is a public access key with the `api` scope enabled.
@@ -1186,7 +1195,7 @@ PUT /projects/:id
| `show_default_award_emojis` | boolean | no | Show default award emojis |
| `resolve_outdated_diff_discussions` | boolean | no | Automatically resolve merge request diffs discussions on lines changed with a push |
| `container_registry_enabled` | boolean | no | Enable container registry for this project |
-| `container_expiration_policy_attributes` | hash | no | Update the image expiration policy for this project. Accepts: `cadence` (string), `keep_n` (string), `older_than` (string), `name_regex` (string), `name_regex_delete` (string), `name_regex_keep` (string), `enabled` (boolean) |
+| `container_expiration_policy_attributes` | hash | no | Update the image cleanup policy for this project. Accepts: `cadence` (string), `keep_n` (string), `older_than` (string), `name_regex` (string), `name_regex_delete` (string), `name_regex_keep` (string), `enabled` (boolean) |
| `shared_runners_enabled` | boolean | no | Enable shared runners for this project |
| `visibility` | string | no | See [project visibility level](#project-visibility-level) |
| `import_url` | string | no | URL to import repository from |
@@ -1219,9 +1228,10 @@ PUT /projects/:id
| `only_mirror_protected_branches` | boolean | no | **(STARTER)** Only mirror protected branches |
| `mirror_overwrites_diverged_branches` | boolean | no | **(STARTER)** Pull mirror overwrites diverged branches |
| `packages_enabled` | boolean | no | **(PREMIUM ONLY)** Enable or disable packages repository feature |
-| `service_desk_enabled` | boolean | no | **(PREMIUM ONLY)** Enable or disable service desk feature |
+| `service_desk_enabled` | boolean | no | Enable or disable service desk feature |
-NOTE: **Note:** If your HTTP repository is not publicly accessible,
+NOTE: **Note:**
+If your HTTP repository is not publicly accessible,
add authentication information to the URL: `https://username:password@gitlab.company.com/group/project.git`
where `password` is a public access key with the `api` scope enabled.
@@ -1272,7 +1282,7 @@ GET /projects/:id/forks
| `with_custom_attributes` | boolean | no | Include [custom attributes](custom_attributes.md) in response (admins only) |
| `with_issues_enabled` | boolean | no | Limit by enabled issues feature |
| `with_merge_requests_enabled` | boolean | no | Limit by enabled merge requests feature |
-| `min_access_level` | integer | no | Limit by current user minimal [access level](members.md) |
+| `min_access_level` | integer | no | Limit by current user minimal [access level](members.md#valid-access-levels) |
```shell
curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/5/forks"
@@ -1824,12 +1834,19 @@ Example response:
## Remove project
-This endpoint either:
+This endpoint:
- Removes a project including all associated resources (issues, merge requests etc).
-- From [GitLab 12.6](https://gitlab.com/gitlab-org/gitlab/-/issues/32935) on [Premium or Silver](https://about.gitlab.com/pricing/) or higher tiers, marks a project for deletion. Actual
- deletion happens after number of days specified in
- [instance settings](../user/admin_area/settings/visibility_and_access_controls.md#default-deletion-adjourned-period-premium-only).
+- From [GitLab 13.2](https://gitlab.com/gitlab-org/gitlab/-/issues/220382) on [Premium or Silver](https://about.gitlab.com/pricing/) or higher tiers,
+group admins can [configure](../user/group/index.md#enabling-delayed-project-removal-premium) projects within a group
+to be deleted after a delayed period.
+When enabled, actual deletion happens after the number of days
+specified in the [default deletion period](../user/admin_area/settings/visibility_and_access_controls.md#default-deletion-adjourned-period-premium-only).
+
+CAUTION: **Warning:**
+The default behavior of [Delayed Project deletion](https://gitlab.com/gitlab-org/gitlab/-/issues/32935) in GitLab 12.6
+was changed to [Immediate deletion](https://gitlab.com/gitlab-org/gitlab/-/issues/220382)
+in GitLab 13.2, as discussed in [Enabling delayed project removal](../user/group/index.md#enabling-delayed-project-removal-premium).
```plaintext
DELETE /projects/:id
@@ -1902,7 +1919,7 @@ POST /projects/:id/share
| --------- | ---- | -------- | ----------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) |
| `group_id` | integer | yes | The ID of the group to share with |
-| `group_access` | integer | yes | The [permissions level](members.md) to grant the group |
+| `group_access` | integer | yes | The [access level](members.md#valid-access-levels) to grant the group |
| `expires_at` | string | no | Share expiration date in ISO 8601 format: 2016-09-26 |
## Delete a shared project link within a group
diff --git a/doc/api/protected_environments.md b/doc/api/protected_environments.md
index 765b8d2364d..56b399cec9b 100644
--- a/doc/api/protected_environments.md
+++ b/doc/api/protected_environments.md
@@ -1,3 +1,10 @@
+---
+stage: Release
+group: Release Management
+info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers
+type: concepts, howto
+---
+
# Protected environments API **(PREMIUM)**
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/30595) in [GitLab Premium](https://about.gitlab.com/pricing/) 12.8.
diff --git a/doc/api/releases/links.md b/doc/api/releases/links.md
index 35cb66e59a1..242b5eb41f5 100644
--- a/doc/api/releases/links.md
+++ b/doc/api/releases/links.md
@@ -138,7 +138,7 @@ PUT /projects/:id/releases/:tag_name/assets/links/:link_id
| `url` | string | no | The URL of the link. |
| `link_type` | string | no | The type of the link: `other`, `runbook`, `image`, `package`. Defaults to `other`. |
-NOTE: **NOTE**
+NOTE: **Note:**
You have to specify at least one of `name` or `url`
Example request:
diff --git a/doc/api/resource_milestone_events.md b/doc/api/resource_milestone_events.md
index 695687ada6d..8a81615857c 100644
--- a/doc/api/resource_milestone_events.md
+++ b/doc/api/resource_milestone_events.md
@@ -6,8 +6,8 @@ info: To determine the technical writer assigned to the Stage/Group associated w
# Resource milestone events API
-Resource milestone events keep track of what happens to GitLab [issues](../user/project/issues/),
-[merge requests](../user/project/merge_requests/), and [epics](../user/group/epics/).
+Resource milestone events keep track of what happens to GitLab [issues](../user/project/issues/) and
+[merge requests](../user/project/merge_requests/).
Use them to track which milestone was added or removed, who did it, and when it happened.
diff --git a/doc/api/resource_state_events.md b/doc/api/resource_state_events.md
new file mode 100644
index 00000000000..6b257f10c6e
--- /dev/null
+++ b/doc/api/resource_state_events.md
@@ -0,0 +1,212 @@
+---
+stage: Plan
+group: Project Management
+info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers
+---
+
+# Resource state events API
+
+Resource state events keep track of what happens to GitLab [issues](../user/project/issues/) and
+[merge requests](../user/project/merge_requests/).
+
+Use them to track which state was set, who did it, and when it happened.
+
+## Issues
+
+### List project issue state events
+
+Gets a list of all state events for a single issue.
+
+```plaintext
+GET /projects/:id/issues/:issue_iid/resource_state_events
+```
+
+| Attribute | Type | Required | Description |
+| ----------- | -------------- | -------- | ------------------------------------------------------------------------------- |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) |
+| `issue_iid` | integer | yes | The IID of an issue |
+
+Example request:
+
+```shell
+curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/5/issues/11/resource_state_events"
+```
+
+Example response:
+
+```json
+[
+ {
+ "id": 142,
+ "user": {
+ "id": 1,
+ "name": "Administrator",
+ "username": "root",
+ "state": "active",
+ "avatar_url": "https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon",
+ "web_url": "http://gitlab.example.com/root"
+ },
+ "created_at": "2018-08-20T13:38:20.077Z",
+ "resource_type": "Issue",
+ "resource_id": 11,
+ "state": "opened"
+ },
+ {
+ "id": 143,
+ "user": {
+ "id": 1,
+ "name": "Administrator",
+ "username": "root",
+ "state": "active",
+ "avatar_url": "https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon",
+ "web_url": "http://gitlab.example.com/root"
+ },
+ "created_at": "2018-08-21T14:38:20.077Z",
+ "resource_type": "Issue",
+ "resource_id": 11,
+ "state": "closed"
+ }
+]
+```
+
+### Get single issue state event
+
+Returns a single state event for a specific project issue
+
+```plaintext
+GET /projects/:id/issues/:issue_iid/resource_state_events/:resource_state_event_id
+```
+
+Parameters:
+
+| Attribute | Type | Required | Description |
+| ----------------------------- | -------------- | -------- | ------------------------------------------------------------------------------- |
+| `id` | integer/string | yes | The ID or [URL-encoded path](README.md#namespaced-path-encoding) of the project |
+| `issue_iid` | integer | yes | The IID of an issue |
+| `resource_state_event_id` | integer | yes | The ID of a state event |
+
+Example request:
+
+```shell
+curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/5/issues/11/resource_state_events/143"
+```
+
+Example response:
+
+```json
+{
+ "id": 143,
+ "user": {
+ "id": 1,
+ "name": "Administrator",
+ "username": "root",
+ "state": "active",
+ "avatar_url": "https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon",
+ "web_url": "http://gitlab.example.com/root"
+ },
+ "created_at": "2018-08-21T14:38:20.077Z",
+ "resource_type": "Issue",
+ "resource_id": 11,
+ "state": "closed"
+}
+```
+
+## Merge requests
+
+### List project merge request state events
+
+Gets a list of all state events for a single merge request.
+
+```plaintext
+GET /projects/:id/merge_requests/:merge_request_iid/resource_state_events
+```
+
+| Attribute | Type | Required | Description |
+| ------------------- | -------------- | -------- | ------------------------------------------------------------------------------- |
+| `id` | integer/string | yes | The ID or [URL-encoded path](README.md#namespaced-path-encoding) of the project |
+| `merge_request_iid` | integer | yes | The IID of a merge request |
+
+Example request:
+
+```shell
+curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/5/merge_requests/11/resource_state_events"
+```
+
+Example response:
+
+```json
+[
+ {
+ "id": 142,
+ "user": {
+ "id": 1,
+ "name": "Administrator",
+ "username": "root",
+ "state": "active",
+ "avatar_url": "https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon",
+ "web_url": "http://gitlab.example.com/root"
+ },
+ "created_at": "2018-08-20T13:38:20.077Z",
+ "resource_type": "MergeRequest",
+ "resource_id": 11,
+ "state": "opened"
+ },
+ {
+ "id": 143,
+ "user": {
+ "id": 1,
+ "name": "Administrator",
+ "username": "root",
+ "state": "active",
+ "avatar_url": "https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon",
+ "web_url": "http://gitlab.example.com/root"
+ },
+ "created_at": "2018-08-21T14:38:20.077Z",
+ "resource_type": "MergeRequest",
+ "resource_id": 11,
+ "state": "closed"
+ }
+]
+```
+
+### Get single merge request state event
+
+Returns a single state event for a specific project merge request
+
+```plaintext
+GET /projects/:id/merge_requests/:merge_request_iid/resource_state_events/:resource_state_event_id
+```
+
+Parameters:
+
+| Attribute | Type | Required | Description |
+| ----------------------------- | -------------- | -------- | ------------------------------------------------------------------------------- |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) |
+| `merge_request_iid` | integer | yes | The IID of a merge request |
+| `resource_state_event_id` | integer | yes | The ID of a state event |
+
+Example request:
+
+```shell
+curl --request GET --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/5/merge_requests/11/resource_state_events/120"
+```
+
+Example response:
+
+```json
+{
+ "id": 120,
+ "user": {
+ "id": 1,
+ "name": "Administrator",
+ "username": "root",
+ "state": "active",
+ "avatar_url": "https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon",
+ "web_url": "http://gitlab.example.com/root"
+ },
+ "created_at": "2018-08-21T14:38:20.077Z",
+ "resource_type": "MergeRequest",
+ "resource_id": 11,
+ "state": "closed"
+}
+```
diff --git a/doc/api/resource_weight_events.md b/doc/api/resource_weight_events.md
new file mode 100644
index 00000000000..700ef288440
--- /dev/null
+++ b/doc/api/resource_weight_events.md
@@ -0,0 +1,108 @@
+---
+stage: Plan
+group: Project Management
+info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers
+---
+
+# Resource weight events API
+
+Resource weight events keep track of what happens to GitLab [issues](../user/project/issues/).
+
+Use them to track which weight was set, who did it, and when it happened.
+
+## Issues
+
+### List project issue weight events
+
+Gets a list of all weight events for a single issue.
+
+```plaintext
+GET /projects/:id/issues/:issue_iid/resource_weight_events
+```
+
+| Attribute | Type | Required | Description |
+| ----------- | -------------- | -------- | ------------------------------------------------------------------------------- |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) |
+| `issue_iid` | integer | yes | The IID of an issue |
+
+Example request:
+
+```shell
+curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/5/issues/11/resource_weight_events"
+```
+
+Example response:
+
+```json
+[
+ {
+ "id": 142,
+ "user": {
+ "id": 1,
+ "name": "Administrator",
+ "username": "root",
+ "state": "active",
+ "avatar_url": "https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon",
+ "web_url": "http://gitlab.example.com/root"
+ },
+ "created_at": "2018-08-20T13:38:20.077Z",
+ "issue_id": 253,
+ "weight": 3
+ },
+ {
+ "id": 143,
+ "user": {
+ "id": 1,
+ "name": "Administrator",
+ "username": "root",
+ "state": "active",
+ "avatar_url": "https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon",
+ "web_url": "http://gitlab.example.com/root"
+ },
+ "created_at": "2018-08-21T14:38:20.077Z",
+ "issue_id": 253,
+ "weight": 2
+ }
+]
+```
+
+### Get single issue weight event
+
+Returns a single weight event for a specific project issue
+
+```plaintext
+GET /projects/:id/issues/:issue_iid/resource_weight_events/:resource_weight_event_id
+```
+
+Parameters:
+
+| Attribute | Type | Required | Description |
+| ----------------------------- | -------------- | -------- | ------------------------------------------------------------------------------- |
+| `id` | integer/string | yes | The ID or [URL-encoded path](README.md#namespaced-path-encoding) of the project |
+| `issue_iid` | integer | yes | The IID of an issue |
+| `resource_weight_event_id` | integer | yes | The ID of a weight event |
+
+Example request:
+
+```shell
+curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/5/issues/11/resource_weight_events/143"
+```
+
+Example response:
+
+```json
+{
+"id": 143,
+"user": {
+ "id": 1,
+ "name": "Administrator",
+ "username": "root",
+ "state": "active",
+ "avatar_url": "https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon",
+ "web_url": "http://gitlab.example.com/root"
+},
+"created_at": "2018-08-21T14:38:20.077Z",
+"issue_id": 253,
+"weight": 2
+}
+```
diff --git a/doc/api/services.md b/doc/api/services.md
index 02048a27c1b..4052fd22641 100644
--- a/doc/api/services.md
+++ b/doc/api/services.md
@@ -493,6 +493,42 @@ Get Emails on push service settings for a project.
GET /projects/:id/services/emails-on-push
```
+## Confluence service
+
+> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/220934) in GitLab 13.2.
+
+Replaces the link to the internal wiki with a link to a Confluence Cloud Workspace.
+
+### Create/Edit Confluence service
+
+Set Confluence service for a project.
+
+```plaintext
+PUT /projects/:id/services/confluence
+```
+
+Parameters:
+
+| Parameter | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `confluence_url` | string | true | The URL of the Confluence Cloud Workspace hosted on atlassian.net. |
+
+### Delete Confluence service
+
+Delete Confluence service for a project.
+
+```plaintext
+DELETE /projects/:id/services/confluence
+```
+
+### Get Confluence service settings
+
+Get Confluence service settings for a project.
+
+```plaintext
+GET /projects/:id/services/confluence
+```
+
## External Wiki
Replaces the link to the internal wiki with a link to an external wiki.
@@ -1008,7 +1044,7 @@ Parameters:
| --------- | ---- | -------- | ----------- |
| `api_url` | string | true | Prometheus API Base URL. For example, `http://prometheus.example.com/`. |
| `google_iap_audience_client_id` | string | false | Client ID of the IAP secured resource (looks like IAP_CLIENT_ID.apps.googleusercontent.com) |
-| `google_iap_service_account_json` | string | false | credentials.json file for your service account, like { "type": "service_account", "project_id": ... } |
+| `google_iap_service_account_json` | string | false | `credentials.json` file for your service account, like { "type": "service_account", "project_id": ... } |
### Delete Prometheus service
diff --git a/doc/api/settings.md b/doc/api/settings.md
index 78d992cff58..d87a3c72a7e 100644
--- a/doc/api/settings.md
+++ b/doc/api/settings.md
@@ -48,7 +48,7 @@ Example response:
"sign_in_text" : null,
"container_expiration_policies_enable_historic_entries": true,
"container_registry_token_expire_delay": 5,
- "repository_storages": ["default"],
+ "repository_storages_weighted": {"default": 100},
"plantuml_enabled": false,
"plantuml_url": null,
"terminal_max_session_time": 0,
@@ -291,6 +291,8 @@ are listed in the descriptions of the relevant settings.
| `mirror_max_capacity` | integer | no | **(PREMIUM)** Maximum number of mirrors that can be synchronizing at the same time. |
| `mirror_max_delay` | integer | no | **(PREMIUM)** Maximum time (in minutes) between updates that a mirror can have when scheduled to synchronize. |
| `npm_package_requests_forwarding` | boolean | no | **(PREMIUM)** Use npmjs.org as a default remote repository when the package is not found in the GitLab NPM Registry |
+| `maintenance_mode` | boolean | no | **(PREMIUM)** When instance is in maintenance mode, non-admin users can sign in with read-only access and make read-only API requests |
+| `maintenance_mode_message` | string | no | **(PREMIUM)** Message displayed when instance is in maintenance mode |
| `outbound_local_requests_whitelist` | array of strings | no | Define a list of trusted domains or ip addresses to which local requests are allowed when local requests for hooks and services are disabled.
| `pages_domain_verification_enabled` | boolean | no | Require users to prove ownership of custom domains. Domain verification is an essential security measure for public GitLab sites. Users are required to demonstrate they control a domain before it is enabled. |
| `password_authentication_enabled_for_git` | boolean | no | Enable authentication for Git over HTTP(S) via a GitLab account password. Default is `true`. |
@@ -314,7 +316,8 @@ are listed in the descriptions of the relevant settings.
| `receive_max_input_size` | integer | no | Maximum push size (MB). |
| `repository_checks_enabled` | boolean | no | GitLab will periodically run `git fsck` in all project and wiki repositories to look for silent disk corruption issues. |
| `repository_size_limit` | integer | no | **(PREMIUM)** Size limit per repository (MB) |
-| `repository_storages` | array of strings | no | A list of names of enabled storage paths, taken from `gitlab.yml`. New projects will be created in one of these stores, chosen at random. |
+| `repository_storages` | array of strings | no | (GitLab 13.0 and earlier) List of names of enabled storage paths, taken from `gitlab.yml`. New projects are created in one of these stores, chosen at random. |
+| `repository_storages_weighted` | hash of strings to integers | no | (GitLab 13.1 and later) Hash of names of taken from `gitlab.yml` to weights. New projects are created in one of these stores, chosen by a weighted random selection. |
| `require_two_factor_authentication` | boolean | no | (**If enabled, requires:** `two_factor_grace_period`) Require all users to set up Two-factor authentication. |
| `restricted_visibility_levels` | array of strings | no | Selected levels cannot be used by non-admin users for groups, projects or snippets. Can take `private`, `internal` and `public` as a parameter. Default is `null` which means there is no restriction. |
| `rsa_key_restriction` | integer | no | The minimum allowed bit length of an uploaded RSA key. Default is `0` (no restriction). `-1` disables RSA keys. |
diff --git a/doc/api/snippets.md b/doc/api/snippets.md
index 1aa3eecfd29..db94716c2d4 100644
--- a/doc/api/snippets.md
+++ b/doc/api/snippets.md
@@ -150,6 +150,34 @@ Example response:
Hello World snippet
```
+## Snippet repository file content
+
+Returns the raw file content as plain text.
+
+```plaintext
+GET /snippets/:id/files/:ref/:file_path/raw
+```
+
+Parameters:
+
+| Attribute | Type | Required | Description |
+|:------------|:--------|:---------|:-------------------------------------------------------------------|
+| `id` | integer | yes | ID of snippet to retrieve |
+| `ref` | string | yes | Reference to a tag, branch or commit |
+| `file_path` | string | yes | URL-encoded path to the file |
+
+Example request:
+
+```shell
+curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/snippets/1/files/master/snippet%2Erb/raw"
+```
+
+Example response:
+
+```plaintext
+Hello World snippet
+```
+
## Create new snippet
Create a new snippet.
diff --git a/doc/api/users.md b/doc/api/users.md
index 6ac1cd089e7..505468945cb 100644
--- a/doc/api/users.md
+++ b/doc/api/users.md
@@ -89,7 +89,8 @@ GET /users
"web_url": "http://localhost:3000/john_smith",
"created_at": "2012-05-23T08:00:58Z",
"is_admin": false,
- "bio": null,
+ "bio": "",
+ "bio_html": "",
"location": null,
"skype": "",
"linkedin": "",
@@ -128,7 +129,8 @@ GET /users
"web_url": "http://localhost:3000/jack_smith",
"created_at": "2012-05-23T08:01:01Z",
"is_admin": false,
- "bio": null,
+ "bio": "",
+ "bio_html": "",
"location": null,
"skype": "",
"linkedin": "",
@@ -245,7 +247,8 @@ Parameters:
"avatar_url": "http://localhost:3000/uploads/user/avatar/1/cd8.jpeg",
"web_url": "http://localhost:3000/john_smith",
"created_at": "2012-05-23T08:00:58Z",
- "bio": null,
+ "bio": "",
+ "bio_html": "",
"location": null,
"public_email": "john@example.com",
"skype": "",
@@ -280,7 +283,8 @@ Example Responses:
"web_url": "http://localhost:3000/john_smith",
"created_at": "2012-05-23T08:00:58Z",
"is_admin": false,
- "bio": null,
+ "bio": "",
+ "bio_html": "",
"location": null,
"public_email": "john@example.com",
"skype": "",
@@ -314,7 +318,8 @@ Example Responses:
}
```
-NOTE: **Note:** The `plan` and `trial` parameters are only available on GitLab Enterprise Edition.
+NOTE: **Note:**
+The `plan` and `trial` parameters are only available on GitLab Enterprise Edition.
Users on GitLab [Starter, Bronze, or higher](https://about.gitlab.com/pricing/) will also see
the `shared_runners_minutes_limit`, and `extra_shared_runners_minutes_limit` parameters.
@@ -368,6 +373,9 @@ over `password`. In addition, `reset_password` and
NOTE: **Note:**
From [GitLab 12.1](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/29888/), `private_profile` will default to `false`.
+NOTE: **Note:**
+From [GitLab 13.2](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/35604), `bio` will default to `""` instead of `null`.
+
```plaintext
POST /users
```
@@ -384,7 +392,7 @@ Parameters:
| `email` | Yes | Email |
| `extern_uid` | No | External UID |
| `external` | No | Flags the user as external - true or false (default) |
-| `extra_shared_runners_minutes_limit` | No | Extra pipeline minutes quota for this user **(STARTER)** |
+| `extra_shared_runners_minutes_limit` | No | Extra pipeline minutes quota for this user (purchased in addition to the minutes included in the plan) **(STARTER)** |
| `force_random_password` | No | Set user password to a random value - true or false (default) |
| `group_id_for_saml` | No | ID of group where SAML has been configured |
| `linkedin` | No | LinkedIn |
@@ -398,7 +406,7 @@ Parameters:
| `provider` | No | External provider name |
| `public_email` | No | The public email of the user |
| `reset_password` | No | Send user password reset link - true or false(default) |
-| `shared_runners_minutes_limit` | No | Pipeline minutes quota for this user **(STARTER)** |
+| `shared_runners_minutes_limit` | No | Pipeline minutes quota for this user (included in plan). Can be `nil` (default; inherit system default), `0` (unlimited) or `> 0` **(STARTER)** |
| `skip_confirmation` | No | Skip confirmation - true or false (default) |
| `skype` | No | Skype ID |
| `theme_id` | No | The GitLab theme for the user (see [the user preference docs](../user/profile/preferences.md#navigation-theme) for more information) |
@@ -426,7 +434,7 @@ Parameters:
| `email` | No | Email |
| `extern_uid` | No | External UID |
| `external` | No | Flags the user as external - true or false (default) |
-| `extra_shared_runners_minutes_limit` | No | Extra pipeline minutes quota for this user **(STARTER)** |
+| `extra_shared_runners_minutes_limit` | No | Extra pipeline minutes quota for this user (purchased in addition to the minutes included in the plan) **(STARTER)** |
| `group_id_for_saml` | No | ID of group where SAML has been configured |
| `id` | Yes | The ID of the user |
| `linkedin` | No | LinkedIn |
@@ -439,7 +447,7 @@ Parameters:
| `projects_limit` | No | Limit projects each user can create |
| `provider` | No | External provider name |
| `public_email` | No | The public email of the user |
-| `shared_runners_minutes_limit` | No | Pipeline minutes quota for this user **(STARTER)** |
+| `shared_runners_minutes_limit` | No | Pipeline minutes quota for this user (included in plan). Can be `nil` (default; inherit system default), `0` (unlimited) or `> 0` **(STARTER)** |
| `skip_reconfirmation` | No | Skip reconfirmation - true or false (default) |
| `skype` | No | Skype ID |
| `theme_id` | No | The GitLab theme for the user (see [the user preference docs](../user/profile/preferences.md#navigation-theme) for more information) |
@@ -499,7 +507,8 @@ GET /user
"avatar_url": "http://localhost:3000/uploads/user/avatar/1/index.jpg",
"web_url": "http://localhost:3000/john_smith",
"created_at": "2012-05-23T08:00:58Z",
- "bio": null,
+ "bio": "",
+ "bio_html": "",
"location": null,
"public_email": "john@example.com",
"skype": "",
@@ -548,7 +557,8 @@ GET /user
"web_url": "http://localhost:3000/john_smith",
"created_at": "2012-05-23T08:00:58Z",
"is_admin": false,
- "bio": null,
+ "bio": "",
+ "bio_html": "",
"location": null,
"public_email": "john@example.com",
"skype": "",
@@ -799,6 +809,9 @@ Parameters:
- `key` (required) - new SSH key
- `expires_at` (optional) - The expiration date of the SSH key in ISO 8601 format (`YYYY-MM-DDTHH:MM:SSZ`)
+NOTE: **Note:**
+This also adds an audit event, as described in [audit instance events](../administration/audit_events.md#instance-events-premium-only). **(PREMIUM)**
+
## Delete SSH key for current user
Deletes key owned by currently authenticated user.
@@ -1400,7 +1413,8 @@ Parameters:
### Get user activities (admin only)
-NOTE: **Note:** This API endpoint is only available on 8.15 (EE) and 9.1 (CE) and above.
+NOTE: **Note:**
+This API endpoint is only available on 8.15 (EE) and 9.1 (CE) and above.
Get the last activity date for all users, sorted from oldest to newest.
diff --git a/doc/api/vulnerability_findings.md b/doc/api/vulnerability_findings.md
index 7fbd58ea62c..e21d903e474 100644
--- a/doc/api/vulnerability_findings.md
+++ b/doc/api/vulnerability_findings.md
@@ -43,6 +43,7 @@ GET /projects/:id/vulnerability_findings?scope=all
GET /projects/:id/vulnerability_findings?scope=dismissed
GET /projects/:id/vulnerability_findings?severity=high
GET /projects/:id/vulnerability_findings?confidence=unknown,experimental
+GET /projects/:id/vulnerability_findings?scanner=bandit,find_sec_bugs
GET /projects/:id/vulnerability_findings?pipeline_id=42
```
@@ -56,6 +57,7 @@ Beginning with GitLab 12.9, the `undefined` severity and confidence level is no
| `scope` | string | no | Returns vulnerability findings for the given scope: `all` or `dismissed`. Defaults to `dismissed`. |
| `severity` | string array | no | Returns vulnerability findings belonging to specified severity level: `info`, `unknown`, `low`, `medium`, `high`, or `critical`. Defaults to all. |
| `confidence` | string array | no | Returns vulnerability findings belonging to specified confidence level: `ignore`, `unknown`, `experimental`, `low`, `medium`, `high`, or `confirmed`. Defaults to all. |
+| `scanner` | string array | no | Returns vulnerability findings detected by specified scanner.
| `pipeline_id` | integer/string | no | Returns vulnerability findings belonging to specified pipeline. |
```shell
diff --git a/doc/ci/README.md b/doc/ci/README.md
index 150f160b762..43e66adbd35 100644
--- a/doc/ci/README.md
+++ b/doc/ci/README.md
@@ -16,9 +16,9 @@ through the [continuous methodologies](introduction/index.md#introduction-to-cic
- Continuous Delivery (CD)
- Continuous Deployment (CD)
-NOTE: **Out-of-the-box management systems can decrease hours spent on maintaining toolchains by 10% or more.**
-Watch our
-["Mastering continuous software development"](https://about.gitlab.com/webcast/mastering-ci-cd/)
+NOTE: **Note:**
+Out-of-the-box management systems can decrease hours spent on maintaining toolchains by 10% or more.
+Watch our ["Mastering continuous software development"](https://about.gitlab.com/webcast/mastering-ci-cd/)
webcast to learn about continuous methods and how GitLab’s built-in CI can help you simplify and scale software development.
## Overview
@@ -58,7 +58,7 @@ the following documents:
- [How GitLab CI/CD works](introduction/index.md#how-gitlab-cicd-works).
- [Fundamental pipeline architectures](pipelines/pipeline_architectures.md).
- [GitLab CI/CD basic workflow](introduction/index.md#basic-cicd-workflow).
-- [Step-by-step guide for writing `.gitlab-ci.yml` for the first time](../user/project/pages/getting_started_part_four.md).
+- [Step-by-step guide for writing `.gitlab-ci.yml` for the first time](../user/project/pages/getting_started/pages_from_scratch.md).
If you're migrating from another CI/CD tool, check out our handy references:
@@ -126,9 +126,10 @@ Its feature set is listed on the table below according to DevOps stages.
| [ChatOps](chatops/README.md) | Trigger CI jobs from chat, with results sent back to the channel. |
|---+---|
| **Verify** ||
-| [Browser Performance Testing](../user/project/merge_requests/browser_performance_testing.md) | Quickly determine the performance impact of pending code changes. |
+| [Browser Performance Testing](../user/project/merge_requests/browser_performance_testing.md) | Quickly determine the browser performance impact of pending code changes. |
+| [Load Performance Testing](../user/project/merge_requests/load_performance_testing.md) | Quickly determine the server performance impact of pending code changes. |
| [CI services](services/README.md) | Link Docker containers with your base image.|
-| [Code Quality](../user/project/merge_requests/code_quality.md) **(STARTER)** | Analyze your source code quality. |
+| [Code Quality](../user/project/merge_requests/code_quality.md) | Analyze your source code quality. |
| [GitLab CI/CD for external repositories](ci_cd_for_external_repos/index.md) **(PREMIUM)** | Get the benefits of GitLab CI/CD combined with repositories in GitHub and Bitbucket Cloud. |
| [Interactive Web Terminals](interactive_web_terminal/index.md) **(CORE ONLY)** | Open an interactive web terminal to debug the running jobs. |
| [JUnit tests](junit_test_reports.md) | Identify script failures directly on merge requests. |
@@ -139,7 +140,7 @@ Its feature set is listed on the table below according to DevOps stages.
| [Building Docker images](docker/using_docker_build.md) | Maintain Docker-based projects using GitLab CI/CD. |
| [Canary Deployments](../user/project/canary_deployments.md) **(PREMIUM)** | Ship features to only a portion of your pods and let a percentage of your user base to visit the temporarily deployed feature. |
| [Deploy Boards](../user/project/deploy_boards.md) **(PREMIUM)** | Check the current health and status of each CI/CD environment running on Kubernetes. |
-| [Feature Flags](../user/project/operations/feature_flags.md) **(PREMIUM)** | Deploy your features behind Feature Flags. |
+| [Feature Flags](../operations/feature_flags.md) **(PREMIUM)** | Deploy your features behind Feature Flags. |
| [GitLab Pages](../user/project/pages/index.md) | Deploy static websites. |
| [GitLab Releases](../user/project/releases/index.md) | Add release notes to Git tags. |
| [Review Apps](review_apps/index.md) | Configure GitLab CI/CD to preview code changes. |
diff --git a/doc/ci/caching/index.md b/doc/ci/caching/index.md
index 392dd4fdeba..b6bd01ecf58 100644
--- a/doc/ci/caching/index.md
+++ b/doc/ci/caching/index.md
@@ -226,14 +226,14 @@ image: node:latest
cache:
key: ${CI_COMMIT_REF_SLUG}
paths:
- - .npm/
+ - .npm/
before_script:
- npm ci --cache .npm --prefer-offline
test_async:
script:
- - node ./specs/start.js ./specs/async.spec.js
+ - node ./specs/start.js ./specs/async.spec.js
```
### Caching PHP dependencies
@@ -253,16 +253,16 @@ image: php:7.2
cache:
key: ${CI_COMMIT_REF_SLUG}
paths:
- - vendor/
+ - vendor/
before_script:
-# Install and run Composer
-- curl --show-error --silent https://getcomposer.org/installer | php
-- php composer.phar install
+ # Install and run Composer
+ - curl --show-error --silent https://getcomposer.org/installer | php
+ - php composer.phar install
test:
script:
- - vendor/bin/phpunit --configuration phpunit.xml --coverage-text --colors=never
+ - vendor/bin/phpunit --configuration phpunit.xml --coverage-text --colors=never
```
### Caching Python dependencies
@@ -301,9 +301,9 @@ before_script:
test:
script:
- - python setup.py test
- - pip install flake8
- - flake8 .
+ - python setup.py test
+ - pip install flake8
+ - flake8 .
```
### Caching Ruby dependencies
@@ -330,7 +330,7 @@ before_script:
rspec:
script:
- - rspec spec
+ - rspec spec
```
### Caching Go dependencies
@@ -354,7 +354,7 @@ test:
image: golang:1.13
extends: .go-cache
script:
- - go test ./... -v -short
+ - go test ./... -v -short
```
## Availability of the cache
@@ -391,28 +391,28 @@ stages:
```yaml
stages:
-- build
-- test
+ - build
+ - test
before_script:
-- echo "Hello"
+ - echo "Hello"
job A:
stage: build
script:
- - mkdir vendor/
- - echo "build" > vendor/hello.txt
+ - mkdir vendor/
+ - echo "build" > vendor/hello.txt
cache:
key: build-cache
paths:
- - vendor/
+ - vendor/
after_script:
- - echo "World"
+ - echo "World"
job B:
stage: test
script:
- - cat vendor/hello.txt
+ - cat vendor/hello.txt
cache:
key: build-cache
```
@@ -483,8 +483,8 @@ cache when the pipeline is run for a second time.
```yaml
stages:
-- build
-- test
+ - build
+ - test
job A:
stage: build
@@ -492,7 +492,7 @@ job A:
cache:
key: same-key
paths:
- - public/
+ - public/
job B:
stage: test
@@ -500,7 +500,7 @@ job B:
cache:
key: same-key
paths:
- - vendor/
+ - vendor/
```
1. `job A` runs.
@@ -520,8 +520,8 @@ will be different):
```yaml
stages:
-- build
-- test
+ - build
+ - test
job A:
stage: build
@@ -529,7 +529,7 @@ job A:
cache:
key: keyA
paths:
- - vendor/
+ - vendor/
job B:
stage: test
@@ -537,7 +537,7 @@ job B:
cache:
key: keyB
paths:
- - vendor/
+ - vendor/
```
In that case, even if the `key` is different (no fear of overwriting), you
diff --git a/doc/ci/chatops/README.md b/doc/ci/chatops/README.md
index a8fea0836c1..ec0c41032ca 100644
--- a/doc/ci/chatops/README.md
+++ b/doc/ci/chatops/README.md
@@ -10,47 +10,75 @@ type: index, concepts, howto
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/4466) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 10.6.
> - [Moved](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/24780) to [GitLab Core](https://about.gitlab.com/pricing/) in 11.9.
-GitLab ChatOps provides a method to interact with CI/CD jobs through chat services like Slack. Many organizations' discussion, collaboration, and troubleshooting is taking place in chat services these days, and having a method to run CI/CD jobs with output posted back to the channel can significantly augment a team's workflow.
+GitLab ChatOps provides a method to interact with CI/CD jobs through chat services
+like Slack. Many organizations' discussion, collaboration, and troubleshooting takes
+place in chat services. Having a method to run CI/CD jobs with output
+posted back to the channel can significantly augment your team's workflow.
-NOTE: **Note:**
-ChatOps is currently in alpha with some important features missing, like access control.
+## How GitLab ChatOps works
-## How it works
+GitLab ChatOps is built upon [GitLab CI/CD](../README.md) and
+[Slack Slash Commands](../../user/project/integrations/slack_slash_commands.md).
+ChatOps provides a `run` action for [slash commands](../../integration/slash_commands.md)
+with the following arguments:
-GitLab ChatOps is built upon two existing features:
+- A `<job name>` to execute.
+- The `<job arguments>`.
-- [GitLab CI/CD](../README.md).
-- [Slack Slash Commands](../../user/project/integrations/slack_slash_commands.md).
+ChatOps passes the following [CI/CD variables](../variables/README.md#predefined-environment-variables)
+to the job:
-A new `run` action has been added to the [slash commands](../../integration/slash_commands.md), which takes two arguments: a `<job name>` to execute and the `<job arguments>`. When executed, ChatOps will look up the specified job name and attempt to match it to a corresponding job in [`.gitlab-ci.yml`](../yaml/README.md). If a matching job is found on `master`, a pipeline containing just that job is scheduled. Two additional [CI/CD variables](../variables/README.md#predefined-environment-variables) are passed to the job: `CHAT_INPUT` contains any additional arguments, and `CHAT_CHANNEL` is set to the name of channel the action was triggered in.
+- `CHAT_INPUT` contains any additional arguments.
+- `CHAT_CHANNEL` is set to the name of channel the action was triggered in.
-After the job has finished, its output is sent back to Slack provided it has completed within 30 minutes. If a job takes more than 30 minutes to run it must use the Slack API to manually send data back to a channel.
+When executed, ChatOps looks up the specified job name and attempts to match it
+to a corresponding job in [`.gitlab-ci.yml`](../yaml/README.md). If a matching job
+is found on `master`, a pipeline containing only that job is scheduled. After the
+job completes:
-[Developer access and above](../../user/permissions.md#project-members-permissions) is required to use the `run` command. If a job should not be able to be triggered from chat, it can be set to `except: [chat]`.
+- If the job completes in *less than 30 minutes*, the ChatOps sends the job's output to Slack.
+- If the job completes in *more than 30 minutes*, the job must use the
+ [Slack API](https://api.slack.com/) to send data to the channel.
-## Creating a ChatOps CI job
+To use the `run` command, you must have
+[Developer access or above](../../user/permissions.md#project-members-permissions).
+If a job shouldn't be able to be triggered from chat, you can set the job to `except: [chat]`.
-Since ChatOps is built upon GitLab CI/CD, the job has all the same features and functions available. There a few best practices to consider however when creating ChatOps jobs:
+## Best practices for ChatOps CI jobs
-- It is strongly recommended to set `only: [chat]` so the job does not run as part of the standard CI pipeline.
-- If the job is set to `when: manual`, the pipeline will be created however the job will wait to be started.
-- It is important to keep in mind that there is limited support for access control. If the user who triggered the slash command is a developer in the project, the job will run. The job itself can utilize existing [CI/CD variables](../variables/README.md#predefined-environment-variables) like `GITLAB_USER_ID` to perform additional rights validation, however these variables can be [overridden](../variables/README.md#priority-of-environment-variables).
+Since ChatOps is built upon GitLab CI/CD, the job has all the same features and
+functions available. Consider these best practices when creating ChatOps jobs:
+
+- GitLab strongly recommends you set `only: [chat]` so the job does not run as part
+ of the standard CI pipeline.
+- If the job is set to `when: manual`, ChatOps creates the pipeline, but the job waits to be started.
+- ChatOps provides limited support for access control. If the user triggering the
+ slash command has [Developer access or above](../../user/permissions.md#project-members-permissions)
+ in the project, the job runs. The job itself can use existing
+ [CI/CD variables](../variables/README.md#predefined-environment-variables) like
+ `GITLAB_USER_ID` to perform additional rights validation, but
+ these variables can be [overridden](../variables/README.md#priority-of-environment-variables).
### Controlling the ChatOps reply
-For jobs with a single command, its output is automatically sent back to the channel as a reply. For example the chat reply of the following job is simply `Hello World.`
+The output for jobs with a single command is sent to the channel as a reply. For
+example, the chat reply of the following job is `Hello World` in the channel:
```yaml
hello-world:
stage: chatops
only: [chat]
script:
- - echo "Hello World."
+ - echo "Hello World"
```
-Jobs that contain multiple commands, or have a `before_script`, include additional content in the chat reply. In these cases both the commands and their output are included, with the commands wrapped in ANSI colors codes.
+Jobs that contain multiple commands (or `before_script`) return additional
+content in the chat reply. In these cases, both the commands and their output are
+included, with the commands wrapped in ANSI color codes.
-To selectively reply with the output of one command, its output must be bounded by the `chat_reply` section. For example, the following job will list the files in the current directory.
+To selectively reply with the output of one command, its output must be bounded by
+the `chat_reply` section. For example, the following job lists the files in the
+current directory:
```yaml
ls:
@@ -61,20 +89,20 @@ ls:
- echo -e "section_start:$( date +%s ):chat_reply\r\033[0K\n$( ls -la )\nsection_end:$( date +%s ):chat_reply\r\033[0K"
```
-## GitLab ChatOps Examples
+## GitLab ChatOps examples
-The GitLab.com team created a repository of [common ChatOps scripts they use to interact with our Production instance of GitLab](https://gitlab.com/gitlab-com/chatops). They are likely useful
-to other administrators of GitLab instances and can serve as inspiration for ChatOps scripts you can write to interact with your own applications.
+The GitLab.com team created a repository of [common ChatOps scripts](https://gitlab.com/gitlab-com/chatops)
+they use to interact with our Production instance of GitLab. Administrators of
+other GitLab instances may find them useful. They can serve as inspiration for ChatOps
+scripts you can write to interact with your own applications.
## GitLab ChatOps icon
-Say Hi to our ChatOps bot.
+The [official GitLab ChatOps icon](img/gitlab-chatops-icon.png) is available for download.
You can find and download the official GitLab ChatOps icon here.
![GitLab ChatOps bot icon](img/gitlab-chatops-icon-small.png)
-[Download bigger image](img/gitlab-chatops-icon.png)
-
<!-- ## Troubleshooting
Include any troubleshooting steps that you can foresee. If you know beforehand what issues
diff --git a/doc/ci/cloud_deployment/index.md b/doc/ci/cloud_deployment/index.md
index 26eb99bd55a..29ce8bdf625 100644
--- a/doc/ci/cloud_deployment/index.md
+++ b/doc/ci/cloud_deployment/index.md
@@ -15,7 +15,7 @@ cloud provider more easily.
## AWS
-GitLab provides Docker images to simplify working with AWS, and a template to make
+GitLab provides Docker images that you can use to [run AWS commands from GitLab CI/CD](#run-aws-commands-from-gitlab-cicd), and a template to make
it easier to [deploy to AWS](#deploy-your-application-to-the-aws-elastic-container-service-ecs).
### Run AWS commands from GitLab CI/CD
@@ -87,6 +87,11 @@ GitLab provides a series of [CI templates that you can include in your project](
To automate deployments of your application to your [Amazon Elastic Container Service](https://aws.amazon.com/ecs/) (AWS ECS)
cluster, you can `include` the `Deploy-ECS.gitlab-ci.yml` template in your `.gitlab-ci.yml` file.
+GitLab also provides [Docker images](https://gitlab.com/gitlab-org/cloud-deploy/-/tree/master/aws) that can be used in your `gitlab-ci.yml` file to simplify working with AWS:
+
+- Use `registry.gitlab.com/gitlab-org/cloud-deploy/aws-base:latest` to use AWS CLI commands.
+- Use `registry.gitlab.com/gitlab-org/cloud-deploy/aws-ecs:latest` to deploy your application to AWS ECS.
+
Before getting started with this process, you need a cluster on AWS ECS, as well as related
components, like an ECS service, ECS task definition, a database on AWS RDS, etc.
[Read more about AWS ECS](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/Welcome.html).
diff --git a/doc/ci/directed_acyclic_graph/index.md b/doc/ci/directed_acyclic_graph/index.md
index fff0fda0ab4..b7dd74a0230 100644
--- a/doc/ci/directed_acyclic_graph/index.md
+++ b/doc/ci/directed_acyclic_graph/index.md
@@ -82,10 +82,10 @@ are certain use cases that you may need to work around. For more information:
## DAG Visualization
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/215517) in GitLab 13.1 as a [Beta feature](https://about.gitlab.com/handbook/product/#beta).
-> - It's deployed behind a feature flag, disabled by default.
+> - It was deployed behind a feature flag, disabled by default.
+> - It became [enabled by default](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/36802) in 13.2.
> - It's enabled on GitLab.com.
-> - It's not recommended for production use.
-> - For GitLab self-managed instances, GitLab administrators can opt to [enable it](#enable-or-disable-dag-visualization-core-only)
+> - For GitLab self-managed instances, GitLab administrators can opt to [disable it](#enable-or-disable-dag-visualization-core-only).
The DAG visualization makes it easier to visualize the relationships between dependent jobs in a DAG. This graph will display all the jobs in a pipeline that need or are needed by other jobs. Jobs with no relationships are not displayed in this view.
@@ -97,15 +97,15 @@ Clicking a node will highlight all the job paths it depends on.
### Enable or disable DAG Visualization **(CORE ONLY)**
-DAG Visualization is under development and requires more testing, but is being made available as a beta features so users can check its limitations and uses.
+DAG Visualization is under development, but is being made available as a beta feature so users can check its limitations and uses.
-It is deployed behind a feature flag that is **disabled by default**.
+It is deployed behind a feature flag that is **enabled by default**.
[GitLab administrators with access to the GitLab Rails console](../../administration/feature_flags.md)
-can opt to enable it for your instance:
+can opt to disable it for your instance:
```ruby
# Instance-wide
-Feature.enable(:dag_pipeline_tab)
+Feature.disable(:dag_pipeline_tab)
# or by project
-Feature.enable(:dag_pipeline_tab, Project.find(<project id>))
+Feature.disable(:dag_pipeline_tab, Project.find(<project id>))
```
diff --git a/doc/ci/docker/using_docker_build.md b/doc/ci/docker/using_docker_build.md
index 65b9c03186b..4bed6d9e323 100644
--- a/doc/ci/docker/using_docker_build.md
+++ b/doc/ci/docker/using_docker_build.md
@@ -123,7 +123,7 @@ not without its own challenges:
- By default, Docker 17.09 and higher uses `--storage-driver overlay2` which is
the recommended storage driver. See [Using the overlayfs driver](#use-the-overlayfs-driver)
for details.
-- Since the `docker:19.03.11-dind` container and the Runner container don't share their
+- Since the `docker:19.03.12-dind` container and the Runner container don't share their
root filesystem, the job's working directory can be used as a mount point for
child containers. For example, if you have files you want to share with a
child container, you may create a subdirectory under `/builds/$CI_PROJECT_PATH`
@@ -142,14 +142,14 @@ not without its own challenges:
An example project using this approach can be found here: <https://gitlab.com/gitlab-examples/docker>.
In the examples below, we are using Docker images tags to specify a
-specific version, such as `docker:19.03.11`. If tags like `docker:stable`
+specific version, such as `docker:19.03.12`. If tags like `docker:stable`
are used, you have no control over what version is going to be used and this
can lead to unpredictable behavior, especially when new versions are
released.
#### TLS enabled
-NOTE: **Note**
+NOTE: **Note:**
Requires GitLab Runner 11.11 or later, but is not supported if GitLab
Runner is installed using the [Helm
chart](https://docs.gitlab.com/runner/install/kubernetes.html). See the
@@ -158,7 +158,7 @@ issue](https://gitlab.com/gitlab-org/charts/gitlab-runner/-/issues/83) for
details.
The Docker daemon supports connection over TLS and it's done by default
-for Docker 19.03.11 or higher. This is the **suggested** way to use the
+for Docker 19.03.12 or higher. This is the **suggested** way to use the
Docker-in-Docker service and
[GitLab.com Shared Runners](../../user/gitlab_com/index.md#shared-runners)
support this.
@@ -174,13 +174,13 @@ support this.
--registration-token REGISTRATION_TOKEN \
--executor docker \
--description "My Docker Runner" \
- --docker-image "docker:19.03.11" \
+ --docker-image "docker:19.03.12" \
--docker-privileged \
--docker-volumes "/certs/client"
```
The above command will register a new Runner to use the special
- `docker:19.03.11` image, which is provided by Docker. **Notice that it's
+ `docker:19.03.12` image, which is provided by Docker. **Notice that it's
using the `privileged` mode to start the build and service
containers.** If you want to use [Docker-in-Docker](https://www.docker.com/blog/docker-can-now-run-within-docker/) mode, you always
have to use `privileged = true` in your Docker containers.
@@ -199,7 +199,7 @@ support this.
executor = "docker"
[runners.docker]
tls_verify = false
- image = "docker:19.03.11"
+ image = "docker:19.03.12"
privileged = true
disable_cache = false
volumes = ["/certs/client", "/cache"]
@@ -209,10 +209,10 @@ support this.
```
1. You can now use `docker` in the build script (note the inclusion of the
- `docker:19.03.11-dind` service):
+ `docker:19.03.12-dind` service):
```yaml
- image: docker:19.03.11
+ image: docker:19.03.12
variables:
# When using dind service, we need to instruct docker, to talk with
@@ -237,7 +237,7 @@ support this.
DOCKER_TLS_CERTDIR: "/certs"
services:
- - docker:19.03.11-dind
+ - docker:19.03.12-dind
before_script:
- docker info
@@ -264,7 +264,7 @@ Assuming that the Runner `config.toml` is similar to:
executor = "docker"
[runners.docker]
tls_verify = false
- image = "docker:19.03.11"
+ image = "docker:19.03.12"
privileged = true
disable_cache = false
volumes = ["/cache"]
@@ -274,10 +274,10 @@ Assuming that the Runner `config.toml` is similar to:
```
You can now use `docker` in the build script (note the inclusion of the
-`docker:19.03.11-dind` service):
+`docker:19.03.12-dind` service):
```yaml
-image: docker:19.03.11
+image: docker:19.03.12
variables:
# When using dind service we need to instruct docker, to talk with the
@@ -298,7 +298,7 @@ variables:
DOCKER_TLS_CERTDIR: ""
services:
- - docker:19.03.11-dind
+ - docker:19.03.12-dind
before_script:
- docker info
@@ -318,7 +318,7 @@ container so that Docker is available in the context of that image.
NOTE: **Note:**
If you bind the Docker socket [when using GitLab Runner 11.11 or
newer](https://gitlab.com/gitlab-org/gitlab-runner/-/merge_requests/1261),
-you can no longer use `docker:19.03.11-dind` as a service because volume bindings
+you can no longer use `docker:19.03.12-dind` as a service because volume bindings
are done to the services as well, making these incompatible.
In order to do that, follow the steps:
@@ -333,12 +333,12 @@ In order to do that, follow the steps:
--registration-token REGISTRATION_TOKEN \
--executor docker \
--description "My Docker Runner" \
- --docker-image "docker:19.03.11" \
+ --docker-image "docker:19.03.12" \
--docker-volumes /var/run/docker.sock:/var/run/docker.sock
```
The above command will register a new Runner to use the special
- `docker:19.03.11` image which is provided by Docker. **Notice that it's using
+ `docker:19.03.12` image which is provided by Docker. **Notice that it's using
the Docker daemon of the Runner itself, and any containers spawned by Docker
commands will be siblings of the Runner rather than children of the Runner.**
This may have complications and limitations that are unsuitable for your workflow.
@@ -352,7 +352,7 @@ In order to do that, follow the steps:
executor = "docker"
[runners.docker]
tls_verify = false
- image = "docker:19.03.11"
+ image = "docker:19.03.12"
privileged = false
disable_cache = false
volumes = ["/var/run/docker.sock:/var/run/docker.sock", "/cache"]
@@ -361,11 +361,11 @@ In order to do that, follow the steps:
```
1. You can now use `docker` in the build script (note that you don't need to
- include the `docker:19.03.11-dind` service as when using the Docker in Docker
+ include the `docker:19.03.12-dind` service as when using the Docker in Docker
executor):
```yaml
- image: docker:19.03.11
+ image: docker:19.03.12
before_script:
- docker info
@@ -419,10 +419,10 @@ any image that's used with the `--cache-from` argument must first be pulled
Here's a `.gitlab-ci.yml` file showing how Docker caching can be used:
```yaml
-image: docker:19.03.11
+image: docker:19.03.12
services:
- - docker:19.03.11-dind
+ - docker:19.03.12-dind
variables:
# Use TLS https://docs.gitlab.com/ee/ci/docker/using_docker_build.html#tls-enabled
diff --git a/doc/ci/docker/using_docker_images.md b/doc/ci/docker/using_docker_images.md
index 2448bb536ab..735cf35584f 100644
--- a/doc/ci/docker/using_docker_images.md
+++ b/doc/ci/docker/using_docker_images.md
@@ -149,14 +149,14 @@ the job will fail:
```yaml
job:
services:
- - php:7
- - node:latest
- - golang:1.10
+ - php:7
+ - node:latest
+ - golang:1.10
image: alpine:3.7
script:
- - php -v
- - node -v
- - go version
+ - php -v
+ - node -v
+ - go version
```
If you need to have `php`, `node` and `go` available for your script, you should
@@ -176,7 +176,7 @@ You can then use for example the [tutum/wordpress](https://hub.docker.com/r/tutu
```yaml
services:
-- tutum/wordpress:latest
+ - tutum/wordpress:latest
```
If you don't [specify a service alias](#available-settings-for-services),
@@ -219,7 +219,7 @@ default:
test:
script:
- - bundle exec rake spec
+ - bundle exec rake spec
```
The image name must be in one of the following formats:
@@ -238,16 +238,16 @@ default:
test:2.6:
image: ruby:2.6
services:
- - postgres:11.7
+ - postgres:11.7
script:
- - bundle exec rake spec
+ - bundle exec rake spec
test:2.7:
image: ruby:2.7
services:
- - postgres:12.2
+ - postgres:12.2
script:
- - bundle exec rake spec
+ - bundle exec rake spec
```
Or you can pass some [extended configuration options](#extended-docker-configuration-options)
@@ -260,17 +260,17 @@ default:
entrypoint: ["/bin/bash"]
services:
- - name: my-postgres:11.7
- alias: db-postgres
- entrypoint: ["/usr/local/bin/db-postgres"]
- command: ["start"]
+ - name: my-postgres:11.7
+ alias: db-postgres
+ entrypoint: ["/usr/local/bin/db-postgres"]
+ command: ["start"]
before_script:
- - bundle install
+ - bundle install
test:
script:
- - bundle exec rake spec
+ - bundle exec rake spec
```
## Passing environment variables to services
@@ -292,21 +292,21 @@ variables:
POSTGRES_INITDB_ARGS: "--encoding=UTF8 --data-checksums"
services:
-- name: postgres:11.7
- alias: db
- entrypoint: ["docker-entrypoint.sh"]
- command: ["postgres"]
+ - name: postgres:11.7
+ alias: db
+ entrypoint: ["docker-entrypoint.sh"]
+ command: ["postgres"]
image:
name: ruby:2.6
entrypoint: ["/bin/bash"]
before_script:
-- bundle install
+ - bundle install
test:
script:
- - bundle exec rake spec
+ - bundle exec rake spec
```
## Extended Docker configuration options
@@ -330,8 +330,8 @@ For example, the following two definitions are equal:
image: "registry.example.com/my/image:latest"
services:
- - postgresql:9.4
- - redis:latest
+ - postgresql:9.4
+ - redis:latest
```
1. Using a map as an option to `image` and `services`. The use of `image:name` is
@@ -342,8 +342,8 @@ For example, the following two definitions are equal:
name: "registry.example.com/my/image:latest"
services:
- - name: postgresql:9.4
- - name: redis:latest
+ - name: postgresql:9.4
+ - name: redis:latest
```
### Available settings for `image`
@@ -378,8 +378,8 @@ would not work properly:
```yaml
services:
-- mysql:latest
-- mysql:latest
+ - mysql:latest
+ - mysql:latest
```
The Runner would start two containers using the `mysql:latest` image, but both
@@ -392,10 +392,10 @@ look like:
```yaml
services:
-- name: mysql:latest
- alias: mysql-1
-- name: mysql:latest
- alias: mysql-2
+ - name: mysql:latest
+ alias: mysql-1
+ - name: mysql:latest
+ alias: mysql-2
```
The Runner will still start two containers using the `mysql:latest` image,
@@ -427,7 +427,7 @@ CMD ["/usr/bin/super-sql", "run"]
# .gitlab-ci.yml
services:
-- my-super-sql:latest
+ - my-super-sql:latest
```
After the new extended Docker configuration options, you can now simply
@@ -437,8 +437,8 @@ set a `command` in `.gitlab-ci.yml`, like:
# .gitlab-ci.yml
services:
-- name: super/sql:latest
- command: ["/usr/bin/super-sql", "run"]
+ - name: super/sql:latest
+ command: ["/usr/bin/super-sql", "run"]
```
As you can see, the syntax of `command` is similar to [Dockerfile's `CMD`](https://docs.docker.com/engine/reference/builder/#cmd).
@@ -545,9 +545,8 @@ runtime.
support for using private registries, which required manual configuration
of credentials on runner's host. We recommend to upgrade your Runner to
at least version **1.8** if you want to use private registries.
-- Not available for [Kubernetes executor](https://docs.gitlab.com/runner/executors/kubernetes.html),
- follow <https://gitlab.com/gitlab-org/gitlab-runner/-/issues/2673> for
- details.
+- Available for [Kubernetes executor](https://docs.gitlab.com/runner/executors/kubernetes.html)
+ in GitLab Runner 13.1 and later.
### Using statically-defined credentials
@@ -601,6 +600,7 @@ There are two ways to determine the value of `DOCKER_AUTH_CONFIG`:
Open a terminal and execute the following command:
```shell
+ # Note the use of "-n" - it prevents encoding a newline in the password.
echo -n "my_username:my_password" | base64
# Example output to copy
@@ -681,11 +681,13 @@ To add `DOCKER_AUTH_CONFIG` to a Runner:
1. Restart the Runner service.
-NOTE: **Note:** The double quotes included in the `DOCKER_AUTH_CONFIG`
+NOTE: **Note:**
+The double quotes included in the `DOCKER_AUTH_CONFIG`
data must be escaped with backslashes. This prevents them from being
interpreted as TOML.
-NOTE: **Note:** The `environment` option is a list. So your Runner may
+NOTE: **Note:**
+The `environment` option is a list. So your Runner may
have existing entries and you should add this to the list, not replace
it.
@@ -715,7 +717,8 @@ To configure credentials store, follow these steps:
`${GITLAB_RUNNER_HOME}/.docker/config.json`. GitLab Runner will read this configuration file
and will use the needed helper for this specific repository.
-NOTE: **Note:** `credsStore` is used to access ALL the registries.
+NOTE: **Note:**
+`credsStore` is used to access ALL the registries.
If you will want to use both images from private registry and public images from DockerHub,
pulling from DockerHub will fail, because Docker daemon will try to use the same credentials for **ALL** the registries.
diff --git a/doc/ci/docker/using_kaniko.md b/doc/ci/docker/using_kaniko.md
index d53430400ec..1580080ac6e 100644
--- a/doc/ci/docker/using_kaniko.md
+++ b/doc/ci/docker/using_kaniko.md
@@ -90,7 +90,7 @@ store:
- |
echo "-----BEGIN CERTIFICATE-----
...
- -----END CERTIFICATE-----" >> /kaniko/ssl/certs/ca-certificates.crt
+ -----END CERTIFICATE-----" >> /kaniko/ssl/certs/additional-ca-cert-bundle.crt
```
## Video walkthrough of a working example
diff --git a/doc/ci/environments/deployment_safety.md b/doc/ci/environments/deployment_safety.md
index 055baa78743..a4ff164a5ba 100644
--- a/doc/ci/environments/deployment_safety.md
+++ b/doc/ci/environments/deployment_safety.md
@@ -7,7 +7,7 @@ that help maintain deployment security and stability.
You can:
- [Restrict write-access to a critical environment](#restrict-write-access-to-a-critical-environment)
-- [Restrict deployments for a particular period](#restrict-deployments-for-a-particular-period)
+- [Prevent deployments during deploy freeze windows](#prevent-deployments-during-deploy-freeze-windows)
If you are using a continuous deployment workflow and want to ensure that concurrent deployments to the same environment do not happen, you should enable the following options:
@@ -77,10 +77,10 @@ The improved pipeline flow **after** enabling Skip outdated deployment jobs:
1. The `deploy` job in Pipeline-B finishes first, and deploys the newer code.
1. The `deploy` job in Pipeline-A is automatically cancelled, so that it doesn't overwrite the deployment from the newer pipeline.
-## Restrict deployments for a particular period
+## Prevent deployments during deploy freeze windows
If you want to prevent deployments for a particular period, for example during a planned
-vacation period when most employees are out, you can set up a [Deploy Freeze](../../user/project/releases/index.md#set-a-deploy-freeze).
+vacation period when most employees are out, you can set up a [Deploy Freeze](../../user/project/releases/index.md#prevent-unintentional-releases-by-setting-a-deploy-freeze).
During a deploy freeze period, no deployment can be executed. This is helpful to
ensure that deployments do not happen unexpectedly.
diff --git a/doc/ci/environments/index.md b/doc/ci/environments/index.md
index 84480b836f8..0273ab290a6 100644
--- a/doc/ci/environments/index.md
+++ b/doc/ci/environments/index.md
@@ -94,7 +94,7 @@ deploy_staging:
name: staging
url: https://staging.example.com
only:
- - master
+ - master
```
We have defined three [stages](../yaml/README.md#stages):
@@ -124,7 +124,7 @@ The environment `name` and `url` is exposed in various places
within GitLab. Each time a job that has an environment specified
succeeds, a deployment is recorded, along with the Git SHA, and environment name.
-CAUTION: **Caution**:
+CAUTION: **Caution:**
Some characters are not allowed in environment names. Use only letters,
numbers, spaces, and `-`, `_`, `/`, `{`, `}`, or `.`. Also, it must not start nor end with `/`.
@@ -173,8 +173,8 @@ If you want to use the environment URL in GitLab, you would have to update it ma
To address this problem, you can configure a deployment job to report back a set of
variables, including the URL that was dynamically-generated by the external service.
-GitLab supports [dotenv](https://github.com/bkeepers/dotenv) file as the format,
-and expands the `environment:url` value with variables defined in the dotenv file.
+GitLab supports the [dotenv (`.env`)](https://github.com/bkeepers/dotenv) file format,
+and expands the `environment:url` value with variables defined in the `.env` file.
To use this feature, specify the
[`artifacts:reports:dotenv`](../pipelines/job_artifacts.md#artifactsreportsdotenv) keyword in `.gitlab-ci.yml`.
@@ -213,14 +213,13 @@ stop_review:
As soon as the `review` job finishes, GitLab updates the `review/your-branch-name`
environment's URL.
-It parses the report artifact `deploy.env`, registers a list of variables as runtime-created,
+It parses the `deploy.env` report artifact, registers a list of variables as runtime-created,
uses it for expanding `environment:url: $DYNAMIC_ENVIRONMENT_URL` and sets it to the environment URL.
You can also specify a static part of the URL at `environment:url:`, such as
`https://$DYNAMIC_ENVIRONMENT_URL`. If the value of `DYNAMIC_ENVIRONMENT_URL` is
-`123.awesome.com`, the final result will be `https://123.awesome.com`.
+`example.com`, the final result will be `https://example.com`.
-The assigned URL for the `review/your-branch-name` environment is visible in the UI.
-[See where the environment URL is displayed](#using-the-environment-url).
+The assigned URL for the `review/your-branch-name` environment is [visible in the UI](#using-the-environment-url).
> **Notes:**
>
@@ -260,7 +259,7 @@ deploy_staging:
name: staging
url: https://staging.example.com
only:
- - master
+ - master
deploy_prod:
stage: deploy
@@ -271,7 +270,7 @@ deploy_prod:
url: https://example.com
when: manual
only:
- - master
+ - master
```
The `when: manual` action:
@@ -306,11 +305,6 @@ declaring their names dynamically in `.gitlab-ci.yml`.
Dynamic environments are a fundamental part of [Review apps](../review_apps/index.md).
-### Configuring incremental rollouts
-
-Learn how to release production changes to only a portion of your Kubernetes pods with
-[incremental rollouts](../environments/incremental_rollouts.md).
-
#### Allowed variables
The `name` and `url` parameters for dynamic environments can use most available CI/CD variables,
@@ -408,7 +402,7 @@ deploy:
kubernetes:
namespace: production
only:
- - master
+ - master
```
When deploying to a Kubernetes cluster using GitLab's Kubernetes integration,
@@ -423,6 +417,11 @@ that are [managed by GitLab](../../user/project/clusters/index.md#gitlab-managed
To follow progress on support for GitLab-managed clusters, see the
[relevant issue](https://gitlab.com/gitlab-org/gitlab/-/issues/38054).
+#### Configuring incremental rollouts
+
+Learn how to release production changes to only a portion of your Kubernetes pods with
+[incremental rollouts](../environments/incremental_rollouts.md).
+
### Deployment safety
Deployment jobs can be more sensitive than other jobs in a pipeline,
@@ -432,7 +431,7 @@ in GitLab that helps maintain deployment security and stability.
- [Restrict write-access to a critical environment](deployment_safety.md#restrict-write-access-to-a-critical-environment)
- [Limit the job-concurrency for deployment jobs](deployment_safety.md#ensure-only-one-deployment-job-runs-at-a-time)
- [Skip outdated deployment jobs](deployment_safety.md#skip-outdated-deployment-jobs)
-- [Restrict deployments for a particular period](deployment_safety.md#restrict-deployments-for-a-particular-period)
+- [Prevent deployments during deploy freeze windows](deployment_safety.md#prevent-deployments-during-deploy-freeze-windows)
### Complete example
@@ -484,7 +483,7 @@ deploy_staging:
name: staging
url: https://staging.example.com
only:
- - master
+ - master
deploy_prod:
stage: deploy
@@ -495,7 +494,7 @@ deploy_prod:
url: https://example.com
when: manual
only:
- - master
+ - master
```
A more realistic example would also include copying files to a location where a
@@ -740,6 +739,12 @@ To enable this feature, you need to specify the [`environment:auto_stop_in`](../
You can specify a human-friendly date as the value, such as `1 hour and 30 minutes` or `1 day`.
`auto_stop_in` uses the same format of [`artifacts:expire_in` docs](../yaml/README.md#artifactsexpire_in).
+NOTE: **Note:**
+Due to the resource limitation, a background worker for stopping environments only
+runs once every hour. This means environments will not be stopped at the exact
+timestamp as the specified period, but will be stopped when the hourly cron worker
+detects expired environments.
+
##### Auto-stop example
In the following example, there is a basic review app setup that creates a new environment
@@ -779,15 +784,9 @@ and the environment will be active until it's stopped manually.
![Environment auto stop](../img/environment_auto_stop_v12_8.png)
-NOTE: **NOTE**
-Due to the resource limitation, a background worker for stopping environments only
-runs once every hour. This means environments will not be stopped at the exact
-timestamp as the specified period, but will be stopped when the hourly cron worker
-detects expired environments.
-
#### Delete a stopped environment
-> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/22629) in GitLab 12.9.
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/20620) in GitLab 12.10.
You can delete [stopped environments](#stopping-an-environment) in one of two
ways: through the GitLab UI or through the API.
@@ -812,6 +811,31 @@ stopped environment:
Environments can also be deleted by using the [Environments API](../../api/environments.md#delete-an-environment).
+### Prepare an environment
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/208655) in GitLab 13.2.
+
+By default, GitLab creates a [deployment](#viewing-deployment-history) every time a
+build with the specified environment runs. Newer deployments can also
+[cancel older ones](deployment_safety.md#skip-outdated-deployment-jobs).
+
+You may want to specify an environment keyword to
+[protect builds from unauthorized access](protected_environments.md), or to get
+access to [scoped variables](#scoping-environments-with-specs). In these cases,
+you can use the `action: prepare` keyword to ensure deployments won't be created,
+and no builds would be canceled:
+
+```yaml
+build:
+ stage: build
+ script:
+ - echo "Building the app"
+ environment:
+ name: staging
+ action: prepare
+ url: https://staging.example.com
+```
+
### Grouping similar environments
> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/7015) in GitLab 8.14.
@@ -868,7 +892,7 @@ versions of the app, all without leaving GitLab.
#### Embedding metrics in GitLab Flavored Markdown
-Metric charts can be embedded within GitLab Flavored Markdown. See [Embedding Metrics within GitLab Flavored Markdown](../../user/project/integrations/prometheus.md#embedding-metric-charts-within-gitlab-flavored-markdown) for more details.
+Metric charts can be embedded within GitLab Flavored Markdown. See [Embedding Metrics within GitLab Flavored Markdown](../../operations/metrics/embed.md) for more details.
### Web terminals
diff --git a/doc/ci/environments/protected_environments.md b/doc/ci/environments/protected_environments.md
index 3a44fcfb182..b6141ddc7ac 100644
--- a/doc/ci/environments/protected_environments.md
+++ b/doc/ci/environments/protected_environments.md
@@ -22,7 +22,7 @@ specific environments are "protected" to prevent unauthorized people from affect
By default, a protected environment does one thing: it ensures that only people
with the right privileges can deploy to it, thus keeping it safe.
-NOTE: **Note**:
+NOTE: **Note:**
A GitLab admin is always allowed to use environments, even if they are protected.
To protect, update, or unprotect an environment, you need to have at least
diff --git a/doc/ci/examples/README.md b/doc/ci/examples/README.md
index c54e4b96332..37ccef10567 100644
--- a/doc/ci/examples/README.md
+++ b/doc/ci/examples/README.md
@@ -25,6 +25,7 @@ The following table lists examples with step-by-step tutorials that are containe
| Use case | Resource |
|:------------------------------|:---------------------------------------------------------------------------------------------------------------------------|
| Browser performance testing | [Browser Performance Testing with the Sitespeed.io container](../../user/project/merge_requests/browser_performance_testing.md). |
+| Load performance testing | [Load Performance Testing with the k6 container](../../user/project/merge_requests/load_performance_testing.md). |
| Clojure | [Test a Clojure application with GitLab CI/CD](test-clojure-application.md). |
| Deployment with Dpl | [Using `dpl` as deployment tool](deployment/README.md). |
| Elixir | [Testing a Phoenix application with GitLab CI/CD](test_phoenix_app_with_gitlab_ci_cd/index.md). |
@@ -46,8 +47,6 @@ The following table lists examples with step-by-step tutorials that are containe
Contributions are welcome! You can help your favorite programming
language users and GitLab by sending a merge request with a guide for that language.
-You may want to apply for the [GitLab Community Writers Program](https://about.gitlab.com/community/writers/)
-to get paid for writing complete articles for GitLab.
## CI/CD templates
@@ -63,6 +62,7 @@ choose one of these templates:
- [C++ (`C++.gitlab-ci.yml`)](https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/ci/templates/C++.gitlab-ci.yml)
- [Chef (`Chef.gitlab-ci.yml`)](https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/ci/templates/Chef.gitlab-ci.yml)
- [Clojure (`Clojure.gitlab-ci.yml`)](https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/ci/templates/Clojure.gitlab-ci.yml)
+- [Composer `Composer.gitlab-ci.yml`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/ci/templates/Composer.gitlab-ci.yml)
- [Crystal (`Crystal.gitlab-ci.yml`)](https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/ci/templates/Crystal.gitlab-ci.yml)
- [Django (`Django.gitlab-ci.yml`)](https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/ci/templates/Django.gitlab-ci.yml)
- [Docker (`Docker.gitlab-ci.yml`)](https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/ci/templates/Docker.gitlab-ci.yml)
@@ -78,6 +78,7 @@ choose one of these templates:
- [LaTeX (`LaTeX.gitlab-ci.yml`)](https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/ci/templates/LaTeX.gitlab-ci.yml)
- [Maven (`Maven.gitlab-ci.yml`)](https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/ci/templates/Maven.gitlab-ci.yml)
- [Mono (`Mono.gitlab-ci.yml`)](https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/ci/templates/Mono.gitlab-ci.yml)
+- [NPM (`npm.gitlab-ci.yml`)](https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/ci/templates/npm.gitlab-ci.yml)
- [Node.js (`Nodejs.gitlab-ci.yml`)](https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/ci/templates/Nodejs.gitlab-ci.yml)
- [OpenShift (`OpenShift.gitlab-ci.yml`)](https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/ci/templates/OpenShift.gitlab-ci.yml)
- [Packer (`Packer.gitlab-ci.yml`)](https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/ci/templates/Packer.gitlab-ci.yml)
diff --git a/doc/ci/examples/authenticating-with-hashicorp-vault/index.md b/doc/ci/examples/authenticating-with-hashicorp-vault/index.md
index 3b8be54ae59..9a87786ec70 100644
--- a/doc/ci/examples/authenticating-with-hashicorp-vault/index.md
+++ b/doc/ci/examples/authenticating-with-hashicorp-vault/index.md
@@ -50,7 +50,7 @@ The JWT's payload looks like this:
}
```
-The JWT is encoded by using RS256 and signed with your GitLab instance's OpenID Connect private key. The expire time for the token will be set to job's timeout, if specifed, or 5 minutes if it is not. The key used to sign this token may change without any notice. In such case retrying the job will generate new JWT using the current signing key.
+The JWT is encoded by using RS256 and signed with your GitLab instance's OpenID Connect private key. The expire time for the token will be set to job's timeout, if specified, or 5 minutes if it is not. The key used to sign this token may change without any notice. In such case retrying the job will generate new JWT using the current signing key.
You can use this JWT and your instance's JWKS endpoint (`https://gitlab.example.com/-/jwks`) to authenticate with a Vault server that is configured to allow the JWT Authentication method for authentication.
@@ -60,7 +60,7 @@ To communicate with Vault, you can use either its CLI client or perform API requ
## Example
-CAUTION: **Caution**:
+CAUTION: **Caution:**
JWTs are credentials, which can grant access to resources. Be careful where you paste them!
Let's say you have the passwords for your staging and production databases stored in a Vault server that is running on `http://vault.example.com:8200`. Your staging password is `pa$$w0rd` and your production password is `real-pa$$w0rd`.
@@ -156,7 +156,7 @@ Combined with GitLab's [protected branches](../../../user/project/protected_bran
For the full list of options, see Vault's [Create Role documentation](https://www.vaultproject.io/api/auth/jwt#create-role).
-CAUTION: **Caution**:
+CAUTION: **Caution:**
Always restrict your roles to project or namespace by using one of the provided claims (e.g. `project_id` or `namespace_id`). Otherwise any JWT generated by this instance may be allowed to authenticate using this role.
Now, configure the JWT Authentication method:
diff --git a/doc/ci/examples/deploy_spring_boot_to_cloud_foundry/index.md b/doc/ci/examples/deploy_spring_boot_to_cloud_foundry/index.md
index 87cee1820bc..e0d4f3f2402 100644
--- a/doc/ci/examples/deploy_spring_boot_to_cloud_foundry/index.md
+++ b/doc/ci/examples/deploy_spring_boot_to_cloud_foundry/index.md
@@ -61,10 +61,10 @@ content:
```yaml
---
applications:
-- name: gitlab-hello-world
- random-route: true
- memory: 1G
- path: target/demo-0.0.1-SNAPSHOT.jar
+ - name: gitlab-hello-world
+ random-route: true
+ memory: 1G
+ path: target/demo-0.0.1-SNAPSHOT.jar
```
## Configure GitLab CI/CD to deploy your application
@@ -96,11 +96,11 @@ build:
production:
stage: deploy
script:
- - curl --location "https://cli.run.pivotal.io/stable?release=linux64-binary&source=github" | tar zx
- - ./cf login -u $CF_USERNAME -p $CF_PASSWORD -a api.run.pivotal.io
- - ./cf push
+ - curl --location "https://cli.run.pivotal.io/stable?release=linux64-binary&source=github" | tar zx
+ - ./cf login -u $CF_USERNAME -p $CF_PASSWORD -a api.run.pivotal.io
+ - ./cf push
only:
- - master
+ - master
```
We've used the `java:8` [Docker
diff --git a/doc/ci/examples/deployment/README.md b/doc/ci/examples/deployment/README.md
index ec02fb6dd43..3192b365c83 100644
--- a/doc/ci/examples/deployment/README.md
+++ b/doc/ci/examples/deployment/README.md
@@ -45,8 +45,8 @@ All possible parameters can be found here: <https://github.com/travis-ci/dpl#her
staging:
stage: deploy
script:
- - gem install dpl
- - dpl --provider=heroku --app=my-app-staging --api-key=$HEROKU_STAGING_API_KEY
+ - gem install dpl
+ - dpl --provider=heroku --app=my-app-staging --api-key=$HEROKU_STAGING_API_KEY
```
In the above example we use Dpl to deploy `my-app-staging` to Heroku server with API key stored in `HEROKU_STAGING_API_KEY` secure variable.
@@ -64,12 +64,12 @@ You will have to install it:
staging:
stage: deploy
script:
- - apt-get update -yq
- - apt-get install -y ruby-dev
- - gem install dpl
- - dpl --provider=heroku --app=my-app-staging --api-key=$HEROKU_STAGING_API_KEY
+ - apt-get update -yq
+ - apt-get install -y ruby-dev
+ - gem install dpl
+ - dpl --provider=heroku --app=my-app-staging --api-key=$HEROKU_STAGING_API_KEY
only:
- - master
+ - master
```
The first line `apt-get update -yq` updates the list of available packages,
@@ -89,18 +89,18 @@ The final `.gitlab-ci.yml` for that setup would look like this:
staging:
stage: deploy
script:
- - gem install dpl
- - dpl --provider=heroku --app=my-app-staging --api-key=$HEROKU_STAGING_API_KEY
+ - gem install dpl
+ - dpl --provider=heroku --app=my-app-staging --api-key=$HEROKU_STAGING_API_KEY
only:
- - master
+ - master
production:
stage: deploy
script:
- - gem install dpl
- - dpl --provider=heroku --app=my-app-production --api-key=$HEROKU_PRODUCTION_API_KEY
+ - gem install dpl
+ - dpl --provider=heroku --app=my-app-production --api-key=$HEROKU_PRODUCTION_API_KEY
only:
- - tags
+ - tags
```
We created two deploy jobs that are executed on different events:
diff --git a/doc/ci/examples/deployment/composer-npm-deploy.md b/doc/ci/examples/deployment/composer-npm-deploy.md
index cea6f26181f..067d92c2275 100644
--- a/doc/ci/examples/deployment/composer-npm-deploy.md
+++ b/doc/ci/examples/deployment/composer-npm-deploy.md
@@ -150,7 +150,7 @@ before_script:
stage_deploy:
artifacts:
paths:
- - build/
+ - build/
only:
- dev
script:
diff --git a/doc/ci/examples/devops_and_game_dev_with_gitlab_ci_cd/index.md b/doc/ci/examples/devops_and_game_dev_with_gitlab_ci_cd/index.md
index 51d9f169939..35a35d97a4b 100644
--- a/doc/ci/examples/devops_and_game_dev_with_gitlab_ci_cd/index.md
+++ b/doc/ci/examples/devops_and_game_dev_with_gitlab_ci_cd/index.md
@@ -274,19 +274,19 @@ just pack them up in the cache. Here is the full `build` job:
```yaml
build:
- stage: build
- script:
- - npm i gulp -g
- - npm i
- - gulp
- - gulp build-test
- cache:
- policy: push
- paths:
- - node_modules
- artifacts:
- paths:
- - built
+ stage: build
+ script:
+ - npm i gulp -g
+ - npm i
+ - gulp
+ - gulp build-test
+ cache:
+ policy: push
+ paths:
+ - node_modules
+ artifacts:
+ paths:
+ - built
```
### Test your game with GitLab CI/CD
@@ -301,18 +301,18 @@ Following the YAML structure, the `test` job should look like this:
```yaml
test:
- stage: test
- script:
- - npm i gulp -g
- - npm i
- - gulp run-test
- cache:
- policy: push
- paths:
- - node_modules/
- artifacts:
- paths:
- - built/
+ stage: test
+ script:
+ - npm i gulp -g
+ - npm i
+ - gulp run-test
+ cache:
+ policy: push
+ paths:
+ - node_modules/
+ artifacts:
+ paths:
+ - built/
```
We have added unit tests for a `Weapon` class that shoots on a specified interval.
@@ -325,33 +325,33 @@ Our entire `.gitlab-ci.yml` file should now look like this:
image: node:10
build:
- stage: build
- script:
- - npm i gulp -g
- - npm i
- - gulp
- - gulp build-test
- cache:
- policy: push
- paths:
- - node_modules/
- artifacts:
- paths:
- - built/
+ stage: build
+ script:
+ - npm i gulp -g
+ - npm i
+ - gulp
+ - gulp build-test
+ cache:
+ policy: push
+ paths:
+ - node_modules/
+ artifacts:
+ paths:
+ - built/
test:
- stage: test
- script:
- - npm i gulp -g
- - npm i
- - gulp run-test
- cache:
- policy: pull
- paths:
- - node_modules/
- artifacts:
- paths:
- - built/
+ stage: test
+ script:
+ - npm i gulp -g
+ - npm i
+ - gulp run-test
+ cache:
+ policy: pull
+ paths:
+ - node_modules/
+ artifacts:
+ paths:
+ - built/
```
### Run your CI/CD pipeline
@@ -445,18 +445,18 @@ trigger the `deploy` job of our pipeline. Put these together to get the followin
```yaml
deploy:
- stage: deploy
- variables:
- AWS_ACCESS_KEY_ID: "$AWS_KEY_ID"
- AWS_SECRET_ACCESS_KEY: "$AWS_KEY_SECRET"
- script:
- - apt-get update
- - apt-get install -y python3-dev python3-pip
- - easy_install3 -U pip
- - pip3 install --upgrade awscli
- - aws s3 sync ./built s3://gitlab-game-demo --region "us-east-1" --grants read=uri=http://acs.amazonaws.com/groups/global/AllUsers --cache-control "no-cache, no-store, must-revalidate" --delete
- only:
- - master
+ stage: deploy
+ variables:
+ AWS_ACCESS_KEY_ID: "$AWS_KEY_ID"
+ AWS_SECRET_ACCESS_KEY: "$AWS_KEY_SECRET"
+ script:
+ - apt-get update
+ - apt-get install -y python3-dev python3-pip
+ - easy_install3 -U pip
+ - pip3 install --upgrade awscli
+ - aws s3 sync ./built s3://gitlab-game-demo --region "us-east-1" --grants read=uri=http://acs.amazonaws.com/groups/global/AllUsers --cache-control "no-cache, no-store, must-revalidate" --delete
+ only:
+ - master
```
Be sure to update the region and S3 URL in that last script command to fit your setup.
@@ -466,46 +466,46 @@ Our final configuration file `.gitlab-ci.yml` looks like:
image: node:10
build:
- stage: build
- script:
- - npm i gulp -g
- - npm i
- - gulp
- - gulp build-test
- cache:
- policy: push
- paths:
- - node_modules/
- artifacts:
- paths:
- - built/
+ stage: build
+ script:
+ - npm i gulp -g
+ - npm i
+ - gulp
+ - gulp build-test
+ cache:
+ policy: push
+ paths:
+ - node_modules/
+ artifacts:
+ paths:
+ - built/
test:
- stage: test
- script:
- - npm i gulp -g
- - gulp run-test
- cache:
- policy: pull
- paths:
- - node_modules/
- artifacts:
- paths:
- - built/
+ stage: test
+ script:
+ - npm i gulp -g
+ - gulp run-test
+ cache:
+ policy: pull
+ paths:
+ - node_modules/
+ artifacts:
+ paths:
+ - built/
deploy:
- stage: deploy
- variables:
- AWS_ACCESS_KEY_ID: "$AWS_KEY_ID"
- AWS_SECRET_ACCESS_KEY: "$AWS_KEY_SECRET"
- script:
- - apt-get update
- - apt-get install -y python3-dev python3-pip
- - easy_install3 -U pip
- - pip3 install --upgrade awscli
- - aws s3 sync ./built s3://gitlab-game-demo --region "us-east-1" --grants read=uri=http://acs.amazonaws.com/groups/global/AllUsers --cache-control "no-cache, no-store, must-revalidate" --delete
- only:
- - master
+ stage: deploy
+ variables:
+ AWS_ACCESS_KEY_ID: "$AWS_KEY_ID"
+ AWS_SECRET_ACCESS_KEY: "$AWS_KEY_SECRET"
+ script:
+ - apt-get update
+ - apt-get install -y python3-dev python3-pip
+ - easy_install3 -U pip
+ - pip3 install --upgrade awscli
+ - aws s3 sync ./built s3://gitlab-game-demo --region "us-east-1" --grants read=uri=http://acs.amazonaws.com/groups/global/AllUsers --cache-control "no-cache, no-store, must-revalidate" --delete
+ only:
+ - master
```
## Conclusion
diff --git a/doc/ci/examples/laravel_with_gitlab_and_envoy/index.md b/doc/ci/examples/laravel_with_gitlab_and_envoy/index.md
index 5b442436baf..ee0938b4d4c 100644
--- a/doc/ci/examples/laravel_with_gitlab_and_envoy/index.md
+++ b/doc/ci/examples/laravel_with_gitlab_and_envoy/index.md
@@ -448,7 +448,7 @@ On your GitLab project repository navigate to the **Registry** tab.
![container registry page empty image](img/container_registry_page_empty_image.png)
-You may need to [enable Container Registry](../../../user/packages/container_registry/index.md#enable-the-container-registry-for-your-project) to your project to see this tab. You'll find it under your project's **Settings > General > Permissions**.
+You may need to [enable Container Registry](../../../user/packages/container_registry/index.md#enable-the-container-registry-for-your-project) to your project to see this tab. You'll find it under your project's **Settings > General > Visibility, project features, permissions**.
To start using Container Registry on our machine, we first need to login to the GitLab registry using our GitLab username and password:
diff --git a/doc/ci/examples/php.md b/doc/ci/examples/php.md
index e7768868c15..cc62e9316f2 100644
--- a/doc/ci/examples/php.md
+++ b/doc/ci/examples/php.md
@@ -76,7 +76,7 @@ environment, let's add it in `.gitlab-ci.yml`:
...
before_script:
-- bash ci/docker_install.sh > /dev/null
+ - bash ci/docker_install.sh > /dev/null
...
```
@@ -88,7 +88,7 @@ Last step, run the actual tests using `phpunit`:
test:app:
script:
- - phpunit --configuration phpunit_myapp.xml
+ - phpunit --configuration phpunit_myapp.xml
...
```
@@ -104,11 +104,11 @@ image: php:5.6
before_script:
# Install dependencies
-- bash ci/docker_install.sh > /dev/null
+ - bash ci/docker_install.sh > /dev/null
test:app:
script:
- - phpunit --configuration phpunit_myapp.xml
+ - phpunit --configuration phpunit_myapp.xml
```
### Test against different PHP versions in Docker builds
@@ -119,19 +119,19 @@ with a different Docker image version and the runner will do the rest:
```yaml
before_script:
# Install dependencies
-- bash ci/docker_install.sh > /dev/null
+ - bash ci/docker_install.sh > /dev/null
# We test PHP5.6
test:5.6:
image: php:5.6
script:
- - phpunit --configuration phpunit_myapp.xml
+ - phpunit --configuration phpunit_myapp.xml
# We test PHP7.0 (good luck with that)
test:7.0:
image: php:7.0
script:
- - phpunit --configuration phpunit_myapp.xml
+ - phpunit --configuration phpunit_myapp.xml
```
### Custom PHP configuration in Docker builds
@@ -142,7 +142,7 @@ add a `before_script` action:
```yaml
before_script:
-- cp my_php.ini /usr/local/etc/php/conf.d/test.ini
+ - cp my_php.ini /usr/local/etc/php/conf.d/test.ini
```
Of course, `my_php.ini` must be present in the root directory of your repository.
@@ -166,7 +166,7 @@ Next, add the following snippet to your `.gitlab-ci.yml`:
```yaml
test:app:
script:
- - phpunit --configuration phpunit_myapp.xml
+ - phpunit --configuration phpunit_myapp.xml
```
Finally, push to GitLab and let the tests begin!
@@ -217,11 +217,11 @@ you can use [atoum](https://github.com/atoum/atoum):
```yaml
before_script:
-- wget http://downloads.atoum.org/nightly/mageekguy.atoum.phar
+ - wget http://downloads.atoum.org/nightly/mageekguy.atoum.phar
test:atoum:
script:
- - php mageekguy.atoum.phar
+ - php mageekguy.atoum.phar
```
### Using Composer
@@ -238,16 +238,16 @@ following in your `.gitlab-ci.yml`:
# your git repository.
cache:
paths:
- - vendor/
+ - vendor/
before_script:
# Install composer dependencies
-- wget https://composer.github.io/installer.sig -O - -q | tr -d '\n' > installer.sig
-- php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"
-- php -r "if (hash_file('SHA384', 'composer-setup.php') === file_get_contents('installer.sig')) { echo 'Installer verified'; } else { echo 'Installer corrupt'; unlink('composer-setup.php'); } echo PHP_EOL;"
-- php composer-setup.php
-- php -r "unlink('composer-setup.php'); unlink('installer.sig');"
-- php composer.phar install
+ - wget https://composer.github.io/installer.sig -O - -q | tr -d '\n' > installer.sig
+ - php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"
+ - php -r "if (hash_file('SHA384', 'composer-setup.php') === file_get_contents('installer.sig')) { echo 'Installer verified'; } else { echo 'Installer corrupt'; unlink('composer-setup.php'); } echo PHP_EOL;"
+ - php composer-setup.php
+ - php -r "unlink('composer-setup.php'); unlink('installer.sig');"
+ - php composer.phar install
...
```
diff --git a/doc/ci/examples/test-and-deploy-python-application-to-heroku.md b/doc/ci/examples/test-and-deploy-python-application-to-heroku.md
index 30a64e65607..d01e9663795 100644
--- a/doc/ci/examples/test-and-deploy-python-application-to-heroku.md
+++ b/doc/ci/examples/test-and-deploy-python-application-to-heroku.md
@@ -23,32 +23,32 @@ stages:
test:
stage: test
script:
- # this configures Django application to use attached postgres database that is run on `postgres` host
- - export DATABASE_URL=postgres://postgres:@postgres:5432/python-test-app
- - apt-get update -qy
- - apt-get install -y python-dev python-pip
- - pip install -r requirements.txt
- - python manage.py test
+ # this configures Django application to use attached postgres database that is run on `postgres` host
+ - export DATABASE_URL=postgres://postgres:@postgres:5432/python-test-app
+ - apt-get update -qy
+ - apt-get install -y python-dev python-pip
+ - pip install -r requirements.txt
+ - python manage.py test
staging:
stage: deploy
script:
- - apt-get update -qy
- - apt-get install -y ruby-dev
- - gem install dpl
- - dpl --provider=heroku --app=gitlab-ci-python-test-staging --api-key=$HEROKU_STAGING_API_KEY
+ - apt-get update -qy
+ - apt-get install -y ruby-dev
+ - gem install dpl
+ - dpl --provider=heroku --app=gitlab-ci-python-test-staging --api-key=$HEROKU_STAGING_API_KEY
only:
- - master
+ - master
production:
stage: deploy
script:
- - apt-get update -qy
- - apt-get install -y ruby-dev
- - gem install dpl
- - dpl --provider=heroku --app=gitlab-ci-python-test-prod --api-key=$HEROKU_PRODUCTION_API_KEY
+ - apt-get update -qy
+ - apt-get install -y ruby-dev
+ - gem install dpl
+ - dpl --provider=heroku --app=gitlab-ci-python-test-prod --api-key=$HEROKU_PRODUCTION_API_KEY
only:
- - tags
+ - tags
```
This project has three jobs:
diff --git a/doc/ci/img/metrics_reports_advanced_v13_0.png b/doc/ci/img/metrics_reports_advanced_v13_0.png
new file mode 100644
index 00000000000..c092c6a84e4
--- /dev/null
+++ b/doc/ci/img/metrics_reports_advanced_v13_0.png
Binary files differ
diff --git a/doc/ci/interactive_web_terminal/index.md b/doc/ci/interactive_web_terminal/index.md
index be3741332e0..c79fb5bc926 100644
--- a/doc/ci/interactive_web_terminal/index.md
+++ b/doc/ci/interactive_web_terminal/index.md
@@ -38,10 +38,12 @@ but support [is planned](https://gitlab.com/gitlab-org/charts/gitlab-runner/-/is
## Debugging a running job
-NOTE: **Note:** Not all executors are
+NOTE: **Note:**
+Not all executors are
[supported](https://docs.gitlab.com/runner/executors/#compatibility-chart).
-NOTE: **Note:** The `docker` executor does not keep running
+NOTE: **Note:**
+The `docker` executor does not keep running
after the build script is finished. At that point, the terminal will automatically
disconnect and will not wait for the user to finish. Please follow [this
issue](https://gitlab.com/gitlab-org/gitlab-runner/-/issues/3605) for updates on
@@ -67,6 +69,6 @@ close the terminal window.
![finished job with terminal open](img/finished_job_with_terminal_open.png)
-## Interactive Web Terminals for the Web IDE **(ULTIMATE ONLY)**
+## Interactive Web Terminals for the Web IDE
-Read the Web IDE docs to learn how to run [Interactive Terminals through the Web IDE](../../user/project/web_ide/index.md).
+Read the Web IDE docs to learn how to run [Interactive Terminals through the Web IDE](../../user/project/web_ide/index.md#interactive-web-terminals-for-the-web-ide).
diff --git a/doc/ci/introduction/index.md b/doc/ci/introduction/index.md
index 7ba24f9c861..db18624d4e9 100644
--- a/doc/ci/introduction/index.md
+++ b/doc/ci/introduction/index.md
@@ -12,9 +12,9 @@ In this document, we'll present an overview of the concepts of Continuous Integr
Continuous Delivery, and Continuous Deployment, as well as an introduction to
GitLab CI/CD.
-NOTE: **Out-of-the-box management systems can decrease hours spent on maintaining toolchains by 10% or more.**
-Watch our
-["Mastering continuous software development"](https://about.gitlab.com/webcast/mastering-ci-cd/)
+NOTE: **Note:**
+Out-of-the-box management systems can decrease hours spent on maintaining toolchains by 10% or more.
+Watch our ["Mastering continuous software development"](https://about.gitlab.com/webcast/mastering-ci-cd/)
webcast to learn about continuous methods and how GitLab’s built-in CI can help you simplify and scale software development.
## Introduction to CI/CD methodologies
@@ -188,8 +188,9 @@ according to each stage (Verify, Package, Release).
1. **Verify**:
- Automatically build and test your application with Continuous Integration.
- - Analyze your source code quality with [GitLab Code Quality](../../user/project/merge_requests/code_quality.md). **(STARTER)**
- - Determine the performance impact of code changes with [Browser Performance Testing](../../user/project/merge_requests/browser_performance_testing.md). **(PREMIUM)**
+ - Analyze your source code quality with [GitLab Code Quality](../../user/project/merge_requests/code_quality.md).
+ - Determine the browser performance impact of code changes with [Browser Performance Testing](../../user/project/merge_requests/browser_performance_testing.md). **(PREMIUM)**
+ - Determine the server performance impact of code changes with [Load Performance Testing](../../user/project/merge_requests/load_performance_testing.md). **(PREMIUM)**
- Perform a series of tests, such as [Container Scanning](../../user/application_security/container_scanning/index.md) **(ULTIMATE)**, [Dependency Scanning](../../user/application_security/dependency_scanning/index.md) **(ULTIMATE)**, and [JUnit tests](../junit_test_reports.md).
- Deploy your changes with [Review Apps](../review_apps/index.md) to preview the app changes on every branch.
1. **Package**:
@@ -202,7 +203,7 @@ according to each stage (Verify, Package, Release).
- Continuous Delivery, manually click to deploy your app to production.
- Deploy static websites with [GitLab Pages](../../user/project/pages/index.md).
- Ship features to only a portion of your pods and let a percentage of your user base to visit the temporarily deployed feature with [Canary Deployments](../../user/project/canary_deployments.md). **(PREMIUM)**
- - Deploy your features behind [Feature Flags](../../user/project/operations/feature_flags.md). **(PREMIUM)**
+ - Deploy your features behind [Feature Flags](../../operations/feature_flags.md). **(PREMIUM)**
- Add release notes to any Git tag with [GitLab Releases](../../user/project/releases/index.md).
- View of the current health and status of each CI environment running on Kubernetes with [Deploy Boards](../../user/project/deploy_boards.md). **(PREMIUM)**
- Deploy your application to a production environment in a Kubernetes cluster with [Auto Deploy](../../topics/autodevops/stages.md#auto-deploy).
@@ -226,7 +227,7 @@ To get started with GitLab CI/CD, you need to familiarize yourself
with the [`.gitlab-ci.yml`](../yaml/README.md) configuration file
syntax and with its attributes.
-This document [introduces the concepts of GitLab CI/CD in the scope of GitLab Pages](../../user/project/pages/getting_started_part_four.md), for deploying static websites.
+This document [introduces the concepts of GitLab CI/CD in the scope of GitLab Pages](../../user/project/pages/getting_started/pages_from_scratch.md), for deploying static websites.
Although it's meant for users who want to write their own Pages
script from scratch, it also serves as an introduction to the setup process for GitLab CI/CD.
It covers the very first general steps of writing a CI/CD configuration
diff --git a/doc/ci/jenkins/index.md b/doc/ci/jenkins/index.md
index 37e0522e74d..1d029dcdd14 100644
--- a/doc/ci/jenkins/index.md
+++ b/doc/ci/jenkins/index.md
@@ -12,16 +12,25 @@ A lot of GitLab users have successfully migrated to GitLab CI/CD from Jenkins. T
easier if you're just getting started, we've collected several resources here that you might find useful
before diving in. Think of this page as a "GitLab CI/CD for Jenkins Users" guide.
-First of all, our [Quick Start Guide](../quick_start/README.md) contains a good overview of how GitLab CI/CD works.
-You may also be interested in [Auto DevOps](../../topics/autodevops/index.md) which can potentially be used to build, test,
-and deploy your applications with little to no configuration needed at all.
+The following list of recommended steps was created after observing organizations
+that were able to quickly complete this migration:
+
+1. Start by reading the GitLab CI/CD [Quick Start Guide](../quick_start/README.md) and [important product differences](#important-product-differences).
+1. Learn the importance of [managing the organizational transition](#managing-the-organizational-transition).
+1. [Add Runners](../runners/README.md) to your GitLab instance.
+1. Educate and enable your developers to independently perform the following steps in their projects:
+ 1. Review the [Quick Start Guide](../quick_start/README.md) and [Pipeline Configuration Reference](../yaml/README.md).
+ 1. Use the [Jenkins Wrapper](#jenkinsfile-wrapper) to temporarily maintain fragile Jenkins jobs.
+ 1. Migrate the build and CI jobs and configure them to show results directly in your merge requests. They can use [Auto DevOps](../../topics/autodevops/index.md) as a starting point, and [customize](../../topics/autodevops/customize.md) or [decompose](../../topics/autodevops/customize.md#using-components-of-auto-devops) the configuration as needed.
+ 1. Add [Review Apps](../review_apps/index.md).
+ 1. Migrate the deployment jobs using [cloud deployment templates](../cloud_deployment/index.md), adding [environments](../environments/index.md), and [deploy boards](../..//user/project/deploy_boards.md).
+ 1. Work to unwrap any jobs still running with the use of the Jenkins wrapper.
+1. Take stock of any common CI/CD job definitions then create and share [templates](#templates) for them.
For an example of how to convert a Jenkins pipeline into a GitLab CI/CD pipeline,
or how to use Auto DevOps to test your code automatically, watch the
[Migrating from Jenkins to GitLab](https://www.youtube.com/watch?v=RlEVGOpYF5Y) video.
-For advanced CI/CD teams, [templates](#templates) can enable the reuse of pipeline configurations.
-
Otherwise, read on for important information that will help you get the ball rolling. Welcome
to GitLab!
@@ -53,8 +62,7 @@ We are building a [JenkinsFile Wrapper](https://gitlab.com/gitlab-org/jfr-contai
you to run a complete Jenkins instance inside of a GitLab job, including plugins. This can help ease the process
of transition, by letting you delay the migration of less urgent pipelines for a period of time.
-If you are interested, join our [public testing issue](https://gitlab.com/gitlab-org/gitlab/-/issues/215675) to
-If you are interested, you might be able to [help GitLab test the wrapper](https://gitlab.com/gitlab-org/gitlab/-/issues/215675).
+If you are interested in helping GitLab test the wrapper, join our [public testing issue](https://gitlab.com/gitlab-org/gitlab/-/issues/215675) for instructions and to provide your feedback.
## Important product differences
@@ -173,8 +181,8 @@ pdf:
script: xelatex mycv.tex
artifacts:
paths:
- - ./mycv.pdf
- - ./output/
+ - ./mycv.pdf
+ - ./output/
expire_in: 1 week
```
diff --git a/doc/ci/junit_test_reports.md b/doc/ci/junit_test_reports.md
index aa0d40a4d06..aedda3c8341 100644
--- a/doc/ci/junit_test_reports.md
+++ b/doc/ci/junit_test_reports.md
@@ -73,6 +73,7 @@ For a list of supported languages on JUnit tests, check the
To enable the JUnit reports in merge requests, you need to add
[`artifacts:reports:junit`](pipelines/job_artifacts.md#artifactsreportsjunit)
in `.gitlab-ci.yml`, and specify the path(s) of the generated test reports.
+The reports must be `.xml` files, otherwise [GitLab returns an Error 500](https://gitlab.com/gitlab-org/gitlab/-/issues/216575).
In the following examples, the job in the `test` stage runs and GitLab
collects the JUnit test report from each job. After each job is executed, the
@@ -103,7 +104,8 @@ ruby:
### Go example
-Use the following job in `.gitlab-ci.yml`:
+Use the following job in `.gitlab-ci.yml`, and ensure you use `-set-exit-code`,
+otherwise the pipeline will be marked successful, even if the tests fail:
```yaml
## Use https://github.com/jstemmer/go-junit-report to generate a JUnit report with go
@@ -111,7 +113,7 @@ golang:
stage: test
script:
- go get -u github.com/jstemmer/go-junit-report
- - go test -v 2>&1 | go-junit-report > report.xml
+ - go test -v 2>&1 | go-junit-report -set-exit-code > report.xml
artifacts:
reports:
junit: report.xml
@@ -206,7 +208,7 @@ cunit:
junit: ./my-cunit-test.xml
```
-### .Net example
+### .NET example
The [JunitXML.TestLogger](https://www.nuget.org/packages/JunitXml.TestLogger/) NuGet
package can generate test reports for .Net Framework and .Net Core applications. The following
@@ -234,7 +236,9 @@ Test:
## Viewing JUnit test reports on GitLab
-> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/24792) in GitLab 12.5.
+> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/24792) in GitLab 12.5.
+> - It's deployed behind a feature flag, disabled by default.
+> - To use it in GitLab self-managed instances, ask a GitLab administrator to [enable it](#enabling-the-junit-test-reports-feature-core-only). **(CORE ONLY)**
If JUnit XML files are generated and uploaded as part of a pipeline, these reports
can be viewed inside the pipelines details page. The **Tests** tab on this page will
@@ -248,7 +252,7 @@ with failed showing at the top, skipped next and successful cases last.
You can also retrieve the reports via the [GitLab API](../api/pipelines.md#get-a-pipelines-test-report).
-### Enabling the feature
+### Enabling the JUnit test reports feature **(CORE ONLY)**
This feature comes with the `:junit_pipeline_view` feature flag disabled by default. This
feature is disabled due to some performance issues with very large data sets.
@@ -260,13 +264,15 @@ following command:
```ruby
Feature.enable(:junit_pipeline_view)
-# Enable the feature for a specific project
+# Enable the feature for a specific project, GitLab 13.0 and above only.
Feature.enable(:junit_pipeline_view, Project.find(<your-project-id-here>))
```
## Viewing JUnit screenshots on GitLab
-> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/202114) in GitLab 13.0.
+> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/202114) in GitLab 13.0.
+> - It's deployed behind a feature flag, disabled by default.
+> - To use it in GitLab self-managed instances, ask a GitLab administrator to [enable it](#enabling-the-junit-screenshots-feature-core-only). **(CORE ONLY)**
If JUnit XML files contain an `attachment` tag, GitLab parses the attachment.
@@ -280,7 +286,7 @@ Upload your screenshots as [artifacts](pipelines/job_artifacts.md#artifactsrepor
When [this issue](https://gitlab.com/gitlab-org/gitlab/-/issues/6061) is complete, the attached file will be visible on the pipeline details page.
-### Enabling the feature
+### Enabling the JUnit screenshots feature **(CORE ONLY)**
This feature comes with the `:junit_pipeline_screenshots_view` feature flag disabled by default.
diff --git a/doc/ci/merge_request_pipelines/index.md b/doc/ci/merge_request_pipelines/index.md
index 7a4ca989fb6..2a6008e6307 100644
--- a/doc/ci/merge_request_pipelines/index.md
+++ b/doc/ci/merge_request_pipelines/index.md
@@ -24,7 +24,7 @@ can run a pipeline for merge requests.
![Merge request page](img/merge_request.png)
-NOTE: **Note**:
+NOTE: **Note:**
If you use this feature with [merge when pipeline succeeds](../../user/project/merge_requests/merge_when_pipeline_succeeds.md),
pipelines for merge requests take precedence over the other regular pipelines.
@@ -32,7 +32,6 @@ pipelines for merge requests take precedence over the other regular pipelines.
To enable pipelines for merge requests:
-- You must have maintainer [permissions](../../user/permissions.md).
- Your repository must be a GitLab repository, not an
[external repository](../ci_cd_for_external_repos/index.md).
- [In GitLab 11.10 and later](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/25504),
@@ -66,19 +65,19 @@ build:
stage: build
script: ./build
only:
- - master
+ - master
test:
stage: test
script: ./test
only:
- - merge_requests
+ - merge_requests
deploy:
stage: deploy
script: ./deploy
only:
- - master
+ - master
```
#### Excluding certain jobs
@@ -207,7 +206,7 @@ The variable names begin with the `CI_MERGE_REQUEST_` prefix.
### Two pipelines created when pushing to a merge request
If you are experiencing duplicated pipelines when using `rules`, take a look at
-the [key details when using `rules`](../yaml/README.md#key-details-when-using-rules),
+the [important differences between `rules` and `only`/`except`](../yaml/README.md#differences-between-rules-and-onlyexcept),
which will help you get your starting configuration correct.
If you are seeing two pipelines when using `only/except`, please see the caveats
diff --git a/doc/ci/merge_request_pipelines/pipelines_for_merged_results/index.md b/doc/ci/merge_request_pipelines/pipelines_for_merged_results/index.md
index a43085c8e7a..84fbefb080f 100644
--- a/doc/ci/merge_request_pipelines/pipelines_for_merged_results/index.md
+++ b/doc/ci/merge_request_pipelines/pipelines_for_merged_results/index.md
@@ -30,8 +30,7 @@ can still be successfully merged into the target.
When the merge request can't be merged, the pipeline runs against the source branch only. For example, when:
- The target branch has changes that conflict with the changes in the source branch.
-- The merge request is a
- [work in progress](../../../user/project/merge_requests/work_in_progress_merge_requests.md).
+- The merge request is a [**Draft** merge request](../../../user/project/merge_requests/work_in_progress_merge_requests.md).
In these cases, the pipeline runs as a [pipeline for merge requests](../index.md)
and is labeled as `detached`. If these cases no longer exist, new pipelines will
diff --git a/doc/ci/merge_request_pipelines/pipelines_for_merged_results/merge_trains/index.md b/doc/ci/merge_request_pipelines/pipelines_for_merged_results/merge_trains/index.md
index fc8e6368d72..e12c9d109cc 100644
--- a/doc/ci/merge_request_pipelines/pipelines_for_merged_results/merge_trains/index.md
+++ b/doc/ci/merge_request_pipelines/pipelines_for_merged_results/merge_trains/index.md
@@ -146,7 +146,7 @@ is recreated and all pipelines restart.
### Merge request dropped from the merge train immediately
-If a merge request is not mergeable (for example, it's WIP, there is a merge
+If a merge request is not mergeable (for example, it's a draft merge request, there is a merge
conflict, etc.), your merge request will be dropped from the merge train automatically.
In these cases, the reason for dropping the merge request is in the **system notes**.
diff --git a/doc/ci/metrics_reports.md b/doc/ci/metrics_reports.md
index 67f9ad00c90..14669edf7eb 100644
--- a/doc/ci/metrics_reports.md
+++ b/doc/ci/metrics_reports.md
@@ -49,3 +49,10 @@ metrics:
reports:
metrics: metrics.txt
```
+
+## Advanced Example
+
+An advanced example of an OpenMetrics text file (from the [Prometheus documentation](https://github.com/prometheus/docs/blob/master/content/docs/instrumenting/exposition_formats.md#text-format-example))
+renders in the merge request widget as:
+
+![Metrics Reports Advanced](img/metrics_reports_advanced_v13_0.png)
diff --git a/doc/ci/migration/circleci.md b/doc/ci/migration/circleci.md
index f6868abc334..625b15ca4fb 100644
--- a/doc/ci/migration/circleci.md
+++ b/doc/ci/migration/circleci.md
@@ -250,14 +250,14 @@ image: node:latest
cache:
key: $CI_COMMIT_REF_SLUG
paths:
- - .npm/
+ - .npm/
before_script:
- npm ci --cache .npm --prefer-offline
test_async:
script:
- - node ./specs/start.js ./specs/async.spec.js
+ - node ./specs/start.js ./specs/async.spec.js
```
## Contexts and variables
diff --git a/doc/ci/pipelines/index.md b/doc/ci/pipelines/index.md
index 48aaebc3456..18b3fe10bec 100644
--- a/doc/ci/pipelines/index.md
+++ b/doc/ci/pipelines/index.md
@@ -348,17 +348,17 @@ For example, these three jobs will be in a group named `build ruby`:
build ruby 1/3:
stage: build
script:
- - echo "ruby1"
+ - echo "ruby1"
build ruby 2/3:
stage: build
script:
- - echo "ruby2"
+ - echo "ruby2"
build ruby 3/3:
stage: build
script:
- - echo "ruby3"
+ - echo "ruby3"
```
In the pipeline, the result is a group named `build ruby` with three jobs:
diff --git a/doc/ci/pipelines/job_artifacts.md b/doc/ci/pipelines/job_artifacts.md
index 1d60f412e5e..c4457d17dc2 100644
--- a/doc/ci/pipelines/job_artifacts.md
+++ b/doc/ci/pipelines/job_artifacts.md
@@ -33,7 +33,7 @@ pdf:
script: xelatex mycv.tex
artifacts:
paths:
- - mycv.pdf
+ - mycv.pdf
expire_in: 1 week
```
@@ -87,8 +87,8 @@ Below is an example of collecting a JUnit XML file from Ruby's RSpec test tool:
rspec:
stage: test
script:
- - bundle install
- - rspec --format RspecJunitFormatter --out rspec.xml
+ - bundle install
+ - rspec --format RspecJunitFormatter --out rspec.xml
artifacts:
reports:
junit: rspec.xml
@@ -114,14 +114,15 @@ The `dotenv` report collects a set of environment variables as artifacts.
The collected variables are registered as runtime-created variables of the job,
which is useful to [set dynamic environment URLs after a job finishes](../environments/index.md#set-dynamic-environment-urls-after-a-job-finishes).
-There are a couple of limitations on top of the [original dotenv rules](https://github.com/motdotla/dotenv#rules).
+There are a couple of exceptions to the [original dotenv rules](https://github.com/motdotla/dotenv#rules):
-- The variable key can contain only letters, digits and underscore ('_').
-- The size of the dotenv file must be smaller than 5 kilobytes.
-- The number of variables must be less than 10.
-- It does not support variable substitution in the dotenv file itself.
-- It does not support empty lines and comments (`#`) in dotenv file.
-- It does not support quote escape, spaces in a quote, a new line expansion in a quote, in dotenv file.
+- The variable key can contain only letters, digits, and underscores (`_`).
+- The maximum size of the `.env` file is 5 KB.
+- The maximum number of variables is 10.
+- Variable substitution in the `.env` file is not supported.
+- The `.env` file can't have empty lines or comments (starting with `#`).
+- Key values in the `env` file cannot have spaces or newline characters (`\n`), including when using single or double quotes.
+- Quote escaping during parsing (`key = 'value'` -> `{key: "value"}`) is not supported.
#### `artifacts:reports:cobertura`
@@ -145,9 +146,10 @@ plan report will be uploaded to GitLab as an artifact and will be automatically
in merge requests. For more information, see
[Output `terraform plan` information into a merge request](../../user/infrastructure/index.md#output-terraform-plan-information-into-a-merge-request).
-#### `artifacts:reports:codequality` **(STARTER)**
+#### `artifacts:reports:codequality`
-> - Introduced in GitLab 11.5.
+> - Introduced in [GitLab Starter](https://about.gitlab.com/pricing/) 11.5.
+> - Made [available in all tiers](https://gitlab.com/gitlab-org/gitlab/-/issues/212499) in GitLab 13.2.
> - Requires GitLab Runner 11.5 and above.
The `codequality` report collects [CodeQuality issues](../../user/project/merge_requests/code_quality.md)
@@ -250,12 +252,23 @@ dashboards.
> - Introduced in GitLab 11.5.
> - Requires GitLab Runner 11.5 and above.
-The `performance` report collects [Performance metrics](../../user/project/merge_requests/browser_performance_testing.md)
+The `performance` report collects [Browser Performance Testing metrics](../../user/project/merge_requests/browser_performance_testing.md)
as artifacts.
-The collected Performance report will be uploaded to GitLab as an artifact and will
+The collected Browser Performance report will be uploaded to GitLab as an artifact and will
be automatically shown in merge requests.
+#### `artifacts:reports:load_performance` **(PREMIUM)**
+
+> - Introduced in [GitLab 13.2](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/35260) in [GitLab Premium](https://about.gitlab.com/pricing/) 13.2.
+> - Requires GitLab Runner 11.5 and above.
+
+The `load_performance` report collects [Load Performance Testing metrics](../../user/project/merge_requests/load_performance_testing.md)
+as artifacts.
+
+The report is uploaded to GitLab as an artifact and is
+shown in merge requests automatically.
+
#### `artifacts:reports:metrics` **(PREMIUM)**
> Introduced in GitLab 11.10.
@@ -407,7 +420,7 @@ information in the UI.
## Erasing artifacts
-DANGER: **Warning:**
+DANGER: **Danger:**
This is a destructive action that leads to data loss. Use with caution.
You can erase a single job via the UI, which will also remove the job's
diff --git a/doc/ci/pipelines/pipeline_architectures.md b/doc/ci/pipelines/pipeline_architectures.md
index 9176444660d..ace765ddb41 100644
--- a/doc/ci/pipelines/pipeline_architectures.md
+++ b/doc/ci/pipelines/pipeline_architectures.md
@@ -133,28 +133,28 @@ build_b:
test_a:
stage: test
- needs: build_a
+ needs: [build_a]
script:
- echo "This test job will start as soon as build_a finishes."
- echo "It will not wait for build_b, or other jobs in the build stage, to finish."
test_b:
stage: test
- needs: build_b
+ needs: [build_b]
script:
- echo "This test job will start as soon as build_b finishes."
- echo "It will not wait for other jobs in the build stage to finish."
deploy_a:
stage: deploy
- needs: test_a
+ needs: [test_a]
script:
- echo "Since build_a and test_a run quickly, this deploy job can run much earlier."
- echo "It does not need to wait for build_b or test_b."
deploy_b:
stage: deploy
- needs: test_b
+ needs: [test_b]
script:
- echo "Since build_b and test_b run slowly, this deploy job will run much later."
```
@@ -228,13 +228,13 @@ build_a:
test_a:
stage: test
- needs: build_a
+ needs: [build_a]
script:
- echo "This job tests something."
deploy_a:
stage: deploy
- needs: test_a
+ needs: [test_a]
script:
- echo "This job deploys something."
```
@@ -257,13 +257,13 @@ build_b:
test_b:
stage: test
- needs: build_b
+ needs: [build_b]
script:
- echo "This job tests something else."
deploy_b:
stage: deploy
- needs: test_b
+ needs: [test_b]
script:
- echo "This job deploys something else."
```
diff --git a/doc/ci/pipelines/settings.md b/doc/ci/pipelines/settings.md
index 2c9d4ccf2c4..39acef14443 100644
--- a/doc/ci/pipelines/settings.md
+++ b/doc/ci/pipelines/settings.md
@@ -36,7 +36,7 @@ in `.gitlab-ci.yml`.
> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/28919) in GitLab 12.0.
-NOTE: **Note**:
+NOTE: **Note:**
As of GitLab 12.0, newly created projects will automatically have a default
`git depth` value of `50`.
@@ -283,7 +283,7 @@ Markdown code will embed the test coverage report badge of the `coverage` job
into your `README.md`:
```markdown
-![coverage](https://gitlab.com/gitlab-org/gitlab-foss/badges/master/coverage.svg?job=coverage)
+![coverage](https://gitlab.com/gitlab-org/gitlab/badges/master/coverage.svg?job=coverage)
```
### Badge styles
@@ -296,7 +296,7 @@ Pipeline badges can be rendered in different styles by adding the `style=style_n
https://example.gitlab.com/<namespace>/<project>/badges/<branch>/coverage.svg?style=flat
```
-![Badge flat style](https://gitlab.com/gitlab-org/gitlab-foss/badges/master/coverage.svg?job=coverage&style=flat)
+![Badge flat style](https://gitlab.com/gitlab-org/gitlab/badges/master/coverage.svg?job=coverage&style=flat)
#### Flat square
@@ -306,17 +306,19 @@ https://example.gitlab.com/<namespace>/<project>/badges/<branch>/coverage.svg?st
https://example.gitlab.com/<namespace>/<project>/badges/<branch>/coverage.svg?style=flat-square
```
-![Badge flat square style](https://gitlab.com/gitlab-org/gitlab-foss/badges/master/coverage.svg?job=coverage&style=flat-square)
+![Badge flat square style](https://gitlab.com/gitlab-org/gitlab/badges/master/coverage.svg?job=coverage&style=flat-square)
### Custom badge text
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/17555) in GitLab 13.1.
+
The text for a badge can be customized. This can be useful to differentiate between multiple coverage jobs that run in the same pipeline. Customize the badge text and width by adding the `key_text=custom_text` and `key_width=custom_key_width` parameters to the URL:
```plaintext
-https://gitlab.com/gitlab-org/gitlab-foss/badges/master/coverage.svg?job=karma&key_text=Frontend+Coverage&key_width=100
+https://gitlab.com/gitlab-org/gitlab/badges/master/coverage.svg?job=karma&key_text=Frontend+Coverage&key_width=100
```
-![Badge with custom text and width](https://gitlab.com/gitlab-org/gitlab-foss/badges/master/coverage.svg?job=karma&key_text=Frontend+Coverage&key_width=100)
+![Badge with custom text and width](https://gitlab.com/gitlab-org/gitlab/badges/master/coverage.svg?job=karma&key_text=Frontend+Coverage&key_width=100)
## Environment Variables
diff --git a/doc/ci/review_apps/index.md b/doc/ci/review_apps/index.md
index 67fac70c8de..283e4c69941 100644
--- a/doc/ci/review_apps/index.md
+++ b/doc/ci/review_apps/index.md
@@ -260,10 +260,6 @@ For example, in a Ruby application, you would need to have this script:
Then, when your app is deployed via GitLab CI/CD, those variables should get
replaced with their real values.
-NOTE: **Note:**
-Future enhancements [are planned](https://gitlab.com/gitlab-org/gitlab/-/issues/11322)
-to make this process even easier.
-
### Determining merge request ID
The visual review tools retrieve the merge request ID from the `data-merge-request-id`
diff --git a/doc/ci/runners/README.md b/doc/ci/runners/README.md
index 86fabd5d54f..21c99f928d8 100644
--- a/doc/ci/runners/README.md
+++ b/doc/ci/runners/README.md
@@ -6,6 +6,8 @@ type: reference
---
# Configuring GitLab Runners
+<!-- This topic contains several commented-out sections that were accidentally added in 13.2.-->
+<!-- The commented-out sections will be added back in 13.3.-->
In GitLab CI/CD, Runners run the code defined in [`.gitlab-ci.yml`](../yaml/README.md).
A GitLab Runner is a lightweight, highly-scalable agent that picks up a CI job through
@@ -35,12 +37,21 @@ Use shared Runners when you have multiple jobs with similar requirements. Rather
having multiple Runners idling for many projects, you can have a few Runners that handle
multiple projects.
-If you are using a self-managed instance of GitLab, your administrator can create
-shared Runners and configure them to use the
-[executor](https://docs.gitlab.com/runner/executors/README.html) you want.
+If you are using a self-managed instance of GitLab:
-If you are using GitLab.com, you can select from a list of
-[shared Runners that GitLab maintains](../../user/gitlab_com/index.md#shared-runners).
+- Your administrator can install and register shared Runners by viewing the instructions
+ [here](https://docs.gitlab.com/runner/install/index.html).
+ <!-- going to your project's
+ <!-- **Settings > CI / CD**, expanding the **Runners** section, and clicking **Show Runner installation instructions**.-->
+ <!-- These instructions are also available [here](https://docs.gitlab.com/runner/install/index.html).-->
+- The administrator can also configure a maximum number of shared Runner [pipeline minutes for
+ each group](../../user/admin_area/settings/continuous_integration.md#shared-runners-pipeline-minutes-quota-starter-only).
+
+If you are using GitLab.com:
+
+- You can select from a list of [shared Runners that GitLab maintains](../../user/gitlab_com/index.md#shared-runners).
+- The shared Runners consume the [pipelines minutes](../../subscriptions/index.md#ci-pipeline-minutes)
+ included with your account.
#### How shared Runners pick jobs
@@ -95,16 +106,38 @@ The fair usage algorithm assigns jobs in this order:
1. Job 6 is next, because Project 3 is the only project left with no running jobs.
1. Lastly we choose Job 3... because, again, it's the only job left.
-#### Enable a shared Runner
+#### Enable shared Runners
+
+On GitLab.com, [shared Runners](#shared-runners) are enabled in all projects by
+default.
+
+On self-managed instances of GitLab, an administrator must [install](https://docs.gitlab.com/runner/install/index.html)
+and [register](https://docs.gitlab.com/runner/register/index.html) them.
+
+You can also enable shared Runners for individual projects.
+
+To enable shared Runners:
+
+1. Go to the project's **{settings}** **Settings > CI/CD** and expand the **Runners** section.
+1. Click **Allow shared Runners**.
-By default, all projects can use shared Runners, and they are enabled by default.
+#### Disable shared Runners
-However, you can enable or disable shared Runners for individual projects.
+You can disable shared Runners for individual projects<!-- or for groups-->.
+You must have Owner permissions for the project<!-- or group-->.
-To enable or disable a shared Runner:
+To disable shared Runners for a project:
1. Go to the project's **{settings}** **Settings > CI/CD** and expand the **Runners** section.
-1. Click **Allow shared Runners** or **Disable shared Runners**.
+1. In the **Shared Runners** area, click **Disable shared Runners**.
+
+<!--To disable shared Runners for a group:
+
+1. Go to the group's **{settings}** **Settings > CI/CD** and expand the **Runners** section.
+1. In the **Shared Runners** area, click **Disable shared Runners globally**.
+1. Optionally, to allow shared Runners to be enabled for individual projects or subgroups,
+ click **Allow projects/subgroups to override the global setting**.
+-->
### Group Runners
@@ -126,14 +159,43 @@ To create a group Runner:
1. Note the URL and token.
1. [Register the Runner](https://docs.gitlab.com/runner/register/).
+<!-- #### View and manage group Runners
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/37366/) in GitLab 13.3.
+
+You can view and manage all Runners for a group, its subgroups, and projects.
+You can do this for your self-managed GitLab instance or for GitLab.com.
+You must have [Owner permissions](../../user/permissions.md#group-members-permissions) for the group.
+
+1. Go to the group where you want to view the Runners.
+1. Go to **{settings}** **Settings > CI/CD** and expand the **Runners** section.
+1. The following fields are displayed.
+
+ | Attribute | Description |
+ | ------------ | ----------- |
+ | Type | One or more of the following states: shared, group, specific, locked, or paused |
+ | Runner token | Token used to identify the Runner, and which the Runner uses to communicate with the GitLab instance |
+ | Description | Description given to the Runner when it was created |
+ | Version | GitLab Runner version |
+ | IP address | IP address of the host on which the Runner is registered |
+ | Projects | The count of projects to which the Runner is assigned |
+ | Jobs | Total of jobs run by the Runner |
+ | Tags | Tags associated with the Runner |
+ | Last contact | Timestamp indicating when the GitLab instance last contacted the Runner |
+
+From this page, you can edit, pause, and remove Runners from the group, its subgroups, and projects. -->
+
#### Pause or remove a group Runner
-You can pause or remove a group Runner.
+You can pause or remove a group Runner for your self-managed GitLab instance or for GitLab.com.
You must have [Owner permissions](../../user/permissions.md#group-members-permissions) for the group.
1. Go to the group you want to remove or pause the Runner for.
1. Go to **{settings}** **Settings > CI/CD** and expand the **Runners** section.
1. Click **Pause** or **Remove Runner**.
+<!-- - If you pause a group Runner that is used by multiple projects, the Runner pauses for all projects. -->
+<!-- - From the group view, you cannot remove a Runner that is assigned to more than one project. -->
+<!-- You must remove it from each project first. -->
1. On the confirmation dialog, click **OK**.
### Specific Runners
diff --git a/doc/ci/triggers/README.md b/doc/ci/triggers/README.md
index c6ac87ef888..47f11a6228c 100644
--- a/doc/ci/triggers/README.md
+++ b/doc/ci/triggers/README.md
@@ -62,9 +62,9 @@ and it creates a dependent pipeline relation visible on the
build_docs:
stage: deploy
script:
- - curl --request POST --form "token=$CI_JOB_TOKEN" --form ref=master https://gitlab.example.com/api/v4/projects/9/trigger/pipeline
+ - curl --request POST --form "token=$CI_JOB_TOKEN" --form ref=master https://gitlab.example.com/api/v4/projects/9/trigger/pipeline
only:
- - tags
+ - tags
```
Pipelines triggered that way also expose a special variable:
@@ -86,11 +86,11 @@ build_submodule:
image: debian
stage: test
script:
- - apt update && apt install -y unzip
- - curl --location --output artifacts.zip "https://gitlab.example.com/api/v4/projects/1/jobs/artifacts/master/download?job=test&job_token=$CI_JOB_TOKEN"
- - unzip artifacts.zip
+ - apt update && apt install -y unzip
+ - curl --location --output artifacts.zip "https://gitlab.example.com/api/v4/projects/1/jobs/artifacts/master/download?job=test&job_token=$CI_JOB_TOKEN"
+ - unzip artifacts.zip
only:
- - tags
+ - tags
```
This allows you to use that for multi-project pipelines and download artifacts
@@ -179,9 +179,9 @@ need to add in project A's `.gitlab-ci.yml`:
build_docs:
stage: deploy
script:
- - "curl --request POST --form token=TOKEN --form ref=master https://gitlab.example.com/api/v4/projects/9/trigger/pipeline"
+ - "curl --request POST --form token=TOKEN --form ref=master https://gitlab.example.com/api/v4/projects/9/trigger/pipeline"
only:
- - tags
+ - tags
```
This means that whenever a new tag is pushed on project A, the job will run and the
@@ -235,24 +235,24 @@ variable is non-zero, `make upload` is run.
```yaml
stages:
-- test
-- build
-- package
+ - test
+ - build
+ - package
run_tests:
stage: test
script:
- - make test
+ - make test
build_package:
stage: build
script:
- - make build
+ - make build
upload_package:
stage: package
script:
- - if [ -n "${UPLOAD_TO_S3}" ]; then make upload; fi
+ - if [ -n "${UPLOAD_TO_S3}" ]; then make upload; fi
```
You can then trigger a rebuild while you pass the `UPLOAD_TO_S3` variable
diff --git a/doc/ci/troubleshooting.md b/doc/ci/troubleshooting.md
new file mode 100644
index 00000000000..a019f8232a9
--- /dev/null
+++ b/doc/ci/troubleshooting.md
@@ -0,0 +1,38 @@
+---
+stage: Verify
+group: Continuous Integration
+info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers
+type: reference
+---
+
+# Troubleshooting CI/CD
+
+## Merge request pipeline widget
+
+The merge request pipeline widget shows information about the pipeline status in a Merge Request. It's displayed above the [merge request ability to merge widget](#merge-request-ability-to-merge-widget).
+
+There are several messages that can be displayed depending on the status of the pipeline.
+
+### "Checking pipeline status"
+
+This message is shown when the merge request has no pipeline associated with the latest commit yet and [Pipelines must succeed](../user/project/merge_requests/merge_when_pipeline_succeeds.md#only-allow-merge-requests-to-be-merged-if-the-pipeline-succeeds) is turned on. This might be because:
+
+- GitLab hasn't finished creating the pipeline yet.
+- You are using an external CI service and GitLab hasn't heard back from the service yet.
+- You are not using CI/CD pipelines in your project.
+
+After the pipeline is created, the message will update with the pipeline status.
+
+Note: Currently if you delete the latest pipeline of a Merge Request, this message will be shown instead of a meaningful error message. This is a known issue and should be resolved soon.
+
+## Merge request ability to merge widget
+
+The merge request status widget shows the **Merge** button and whether or not a merge request is ready to merge. If the merge request can't be merged, the reason for this is displayed.
+
+If the pipeline is still running, the **Merge** button is replaced with the **Merge when pipeline succeeds** button.
+
+If [**Merge Trains**](merge_request_pipelines/pipelines_for_merged_results/merge_trains/index.md) are enabled, the button is either **Add to merge train** or **Add to merge train when pipeline succeeds**. **(PREMIUM)**
+
+### "A CI/CD pipeline must run and be successful before merge"
+
+This message is shown if the [Pipelines must succeed](../user/project/merge_requests/merge_when_pipeline_succeeds.md#only-allow-merge-requests-to-be-merged-if-the-pipeline-succeeds) setting is enabled in the project and a pipeline has not yet run successfully. This also applies if the pipeline has not been created yet, or if you are waiting for an external CI service. If you don't use pipelines for your project, then you should disable **Pipelines must succeed** so you can accept merge requests.
diff --git a/doc/ci/variables/README.md b/doc/ci/variables/README.md
index 541e739082f..4f9a1d8dd27 100644
--- a/doc/ci/variables/README.md
+++ b/doc/ci/variables/README.md
@@ -227,18 +227,18 @@ To protect a variable:
1. Select the **Protect variable** check box.
1. Click **Update variable**.
-The variable is available for all subsequent pipelines.
+The variable is available for all subsequent pipelines. Protected variables can only
+be updated or viewed by project members with [maintainer permissions](../../user/permissions.md#project-members-permissions).
### Custom variables validated by GitLab
Some variables are listed in the UI so you can choose them more quickly.
-GitLab validates the values of these variables to ensure they are in the correct format.
| Variable | Allowed Values | Introduced in |
|-------------------------|----------------------------------------------------|---------------|
-| `AWS_ACCESS_KEY_ID` | 20 characters: letters, digits | 12.10 |
+| `AWS_ACCESS_KEY_ID` | Any | 12.10 |
| `AWS_DEFAULT_REGION` | Any | 12.10 |
-| `AWS_SECRET_ACCESS_KEY` | 40 characters: letters, digits, special characters | 12.10 |
+| `AWS_SECRET_ACCESS_KEY` | Any | 12.10 |
NOTE: **Note:**
When you store credentials, there are security implications. If you are using AWS keys,
@@ -341,6 +341,7 @@ export CI_PROJECT_DIR="/builds/gitlab-org/gitlab-foss"
export CI_PROJECT_NAME="gitlab-foss"
export CI_PROJECT_TITLE="GitLab FOSS"
export CI_PROJECT_NAMESPACE="gitlab-org"
+export CI_PROJECT_ROOT_NAMESPACE="gitlab-org"
export CI_PROJECT_PATH="gitlab-org/gitlab-foss"
export CI_PROJECT_URL="https://example.com/gitlab-org/gitlab-foss"
export CI_REGISTRY="registry.example.com"
@@ -457,9 +458,6 @@ The UI interface for Instance-level CI/CD variables is under development but rea
It is deployed behind a feature flag that is **enabled by default**.
[GitLab administrators with access to the GitLab Rails console](../../administration/feature_flags.md) can opt to disable it for your instance.
-NOTE: **Note:**
-This feature will not work if the [instance-level CI/CD variables API feature flag is disabled](../../api/instance_level_ci_variables.md#enable-or-disable-instance-level-cicd-variables-core-only).
-
To disable it:
```ruby
@@ -895,8 +893,8 @@ if [[ -d "/builds/gitlab-examples/ci-debug-trace/.git" ]]; then
++ CI_SERVER_VERSION_PATCH=0
++ export CI_SERVER_REVISION=f4cc00ae823
++ CI_SERVER_REVISION=f4cc00ae823
-++ export GITLAB_FEATURES=audit_events,burndown_charts,code_owners,contribution_analytics,description_diffs,elastic_search,group_bulk_edit,group_burndown_charts,group_webhooks,issuable_default_templates,issue_weights,jenkins_integration,ldap_group_sync,member_lock,merge_request_approvers,multiple_issue_assignees,multiple_ldap_servers,multiple_merge_request_assignees,protected_refs_for_users,push_rules,related_issues,repository_mirrors,repository_size_limit,scoped_issue_board,usage_quotas,visual_review_app,wip_limits,adjourned_deletion_for_projects_and_groups,admin_audit_log,auditor_user,batch_comments,blocking_merge_requests,board_assignee_lists,board_milestone_lists,ci_cd_projects,cluster_deployments,code_analytics,code_owner_approval_required,commit_committer_check,cross_project_pipelines,custom_file_templates,custom_file_templates_for_namespace,custom_project_templates,custom_prometheus_metrics,cycle_analytics_for_groups,db_load_balancing,default_project_deletion_protection,dependency_proxy,deploy_board,design_management,email_additional_text,extended_audit_events,external_authorization_service_api_management,feature_flags,file_locks,geo,github_project_service_integration,group_allowed_email_domains,group_project_templates,group_saml,issues_analytics,jira_dev_panel_integration,ldap_group_sync_filter,merge_pipelines,merge_request_performance_metrics,merge_trains,metrics_reports,multiple_approval_rules,multiple_clusters,multiple_group_issue_boards,object_storage,operations_dashboard,packages,productivity_analytics,project_aliases,protected_environments,reject_unsigned_commits,required_ci_templates,scoped_labels,service_desk,smartcard_auth,group_timelogs,type_of_work_analytics,unprotection_restrictions,ci_project_subscriptions,cluster_health,container_scanning,dast,dependency_scanning,epics,group_ip_restriction,incident_management,insights,license_management,personal_access_token_expiration_policy,pod_logs,prometheus_alerts,pseudonymizer,report_approver_rules,sast,security_dashboard,tracing,web_ide_terminal
-++ GITLAB_FEATURES=audit_events,burndown_charts,code_owners,contribution_analytics,description_diffs,elastic_search,group_bulk_edit,group_burndown_charts,group_webhooks,issuable_default_templates,issue_weights,jenkins_integration,ldap_group_sync,member_lock,merge_request_approvers,multiple_issue_assignees,multiple_ldap_servers,multiple_merge_request_assignees,protected_refs_for_users,push_rules,related_issues,repository_mirrors,repository_size_limit,scoped_issue_board,usage_quotas,visual_review_app,wip_limits,adjourned_deletion_for_projects_and_groups,admin_audit_log,auditor_user,batch_comments,blocking_merge_requests,board_assignee_lists,board_milestone_lists,ci_cd_projects,cluster_deployments,code_analytics,code_owner_approval_required,commit_committer_check,cross_project_pipelines,custom_file_templates,custom_file_templates_for_namespace,custom_project_templates,custom_prometheus_metrics,cycle_analytics_for_groups,db_load_balancing,default_project_deletion_protection,dependency_proxy,deploy_board,design_management,email_additional_text,extended_audit_events,external_authorization_service_api_management,feature_flags,file_locks,geo,github_project_service_integration,group_allowed_email_domains,group_project_templates,group_saml,issues_analytics,jira_dev_panel_integration,ldap_group_sync_filter,merge_pipelines,merge_request_performance_metrics,merge_trains,metrics_reports,multiple_approval_rules,multiple_clusters,multiple_group_issue_boards,object_storage,operations_dashboard,packages,productivity_analytics,project_aliases,protected_environments,reject_unsigned_commits,required_ci_templates,scoped_labels,service_desk,smartcard_auth,group_timelogs,type_of_work_analytics,unprotection_restrictions,ci_project_subscriptions,cluster_health,container_scanning,dast,dependency_scanning,epics,group_ip_restriction,incident_management,insights,license_management,personal_access_token_expiration_policy,pod_logs,prometheus_alerts,pseudonymizer,report_approver_rules,sast,security_dashboard,tracing,web_ide_terminal
+++ export GITLAB_FEATURES=audit_events,burndown_charts,code_owners,contribution_analytics,description_diffs,elastic_search,group_bulk_edit,group_burndown_charts,group_webhooks,issuable_default_templates,issue_weights,jenkins_integration,ldap_group_sync,member_lock,merge_request_approvers,multiple_issue_assignees,multiple_ldap_servers,multiple_merge_request_assignees,protected_refs_for_users,push_rules,related_issues,repository_mirrors,repository_size_limit,scoped_issue_board,usage_quotas,visual_review_app,wip_limits,adjourned_deletion_for_projects_and_groups,admin_audit_log,auditor_user,batch_comments,blocking_merge_requests,board_assignee_lists,board_milestone_lists,ci_cd_projects,cluster_deployments,code_analytics,code_owner_approval_required,commit_committer_check,cross_project_pipelines,custom_file_templates,custom_file_templates_for_namespace,custom_project_templates,custom_prometheus_metrics,cycle_analytics_for_groups,db_load_balancing,default_project_deletion_protection,dependency_proxy,deploy_board,design_management,email_additional_text,extended_audit_events,external_authorization_service_api_management,feature_flags,file_locks,geo,github_project_service_integration,group_allowed_email_domains,group_project_templates,group_saml,issues_analytics,jira_dev_panel_integration,ldap_group_sync_filter,merge_pipelines,merge_request_performance_metrics,merge_trains,metrics_reports,multiple_approval_rules,multiple_group_issue_boards,object_storage,operations_dashboard,packages,productivity_analytics,project_aliases,protected_environments,reject_unsigned_commits,required_ci_templates,scoped_labels,service_desk,smartcard_auth,group_timelogs,type_of_work_analytics,unprotection_restrictions,ci_project_subscriptions,container_scanning,dast,dependency_scanning,epics,group_ip_restriction,incident_management,insights,license_management,personal_access_token_expiration_policy,pod_logs,prometheus_alerts,pseudonymizer,report_approver_rules,sast,security_dashboard,tracing,web_ide_terminal
+++ GITLAB_FEATURES=audit_events,burndown_charts,code_owners,contribution_analytics,description_diffs,elastic_search,group_bulk_edit,group_burndown_charts,group_webhooks,issuable_default_templates,issue_weights,jenkins_integration,ldap_group_sync,member_lock,merge_request_approvers,multiple_issue_assignees,multiple_ldap_servers,multiple_merge_request_assignees,protected_refs_for_users,push_rules,related_issues,repository_mirrors,repository_size_limit,scoped_issue_board,usage_quotas,visual_review_app,wip_limits,adjourned_deletion_for_projects_and_groups,admin_audit_log,auditor_user,batch_comments,blocking_merge_requests,board_assignee_lists,board_milestone_lists,ci_cd_projects,cluster_deployments,code_analytics,code_owner_approval_required,commit_committer_check,cross_project_pipelines,custom_file_templates,custom_file_templates_for_namespace,custom_project_templates,custom_prometheus_metrics,cycle_analytics_for_groups,db_load_balancing,default_project_deletion_protection,dependency_proxy,deploy_board,design_management,email_additional_text,extended_audit_events,external_authorization_service_api_management,feature_flags,file_locks,geo,github_project_service_integration,group_allowed_email_domains,group_project_templates,group_saml,issues_analytics,jira_dev_panel_integration,ldap_group_sync_filter,merge_pipelines,merge_request_performance_metrics,merge_trains,metrics_reports,multiple_approval_rules,multiple_group_issue_boards,object_storage,operations_dashboard,packages,productivity_analytics,project_aliases,protected_environments,reject_unsigned_commits,required_ci_templates,scoped_labels,service_desk,smartcard_auth,group_timelogs,type_of_work_analytics,unprotection_restrictions,ci_project_subscriptions,cluster_health,container_scanning,dast,dependency_scanning,epics,group_ip_restriction,incident_management,insights,license_management,personal_access_token_expiration_policy,pod_logs,prometheus_alerts,pseudonymizer,report_approver_rules,sast,security_dashboard,tracing,web_ide_terminal
++ export CI_PROJECT_ID=17893
++ CI_PROJECT_ID=17893
++ export CI_PROJECT_NAME=ci-debug-trace
@@ -909,6 +907,8 @@ if [[ -d "/builds/gitlab-examples/ci-debug-trace/.git" ]]; then
++ CI_PROJECT_PATH_SLUG=gitlab-examples-ci-debug-trace
++ export CI_PROJECT_NAMESPACE=gitlab-examples
++ CI_PROJECT_NAMESPACE=gitlab-examples
+++ export CI_PROJECT_ROOT_NAMESPACE=gitlab-examples
+++ CI_PROJECT_ROOT_NAMESPACE=gitlab-examples
++ export CI_PROJECT_URL=https://gitlab.com/gitlab-examples/ci-debug-trace
++ CI_PROJECT_URL=https://gitlab.com/gitlab-examples/ci-debug-trace
++ export CI_PROJECT_VISIBILITY=public
diff --git a/doc/ci/variables/predefined_variables.md b/doc/ci/variables/predefined_variables.md
index 8463bbeb582..98a2e7ae22f 100644
--- a/doc/ci/variables/predefined_variables.md
+++ b/doc/ci/variables/predefined_variables.md
@@ -23,15 +23,17 @@ future GitLab releases.**
You can add a command to your `.gitlab-ci.yml` file to
[output the values of all variables available for a job](README.md#list-all-environment-variables).
+Kubernetes-specific environment variables are detailed in the
+[Kubernetes deployment variables](../../user/project/clusters/index.md#deployment-variables) section.
+
| Variable | GitLab | Runner | Description |
|-----------------------------------------------|--------|--------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
-| `ARTIFACT_DOWNLOAD_ATTEMPTS` | 8.15 | 1.9 | Number of attempts to download artifacts running a job |
| `CHAT_CHANNEL` | 10.6 | all | Source chat channel which triggered the [ChatOps](../chatops/README.md) command |
| `CHAT_INPUT` | 10.6 | all | Additional arguments passed in the [ChatOps](../chatops/README.md) command |
| `CI` | all | 0.4 | Mark that job is executed in CI environment |
| `CI_API_V4_URL` | 11.7 | all | The GitLab API v4 root URL |
| `CI_BUILDS_DIR` | all | 11.10 | Top-level directory where builds are executed. |
-| `CI_COMMIT_BEFORE_SHA` | 11.2 | all | The previous latest commit present on a branch before a merge request. Only populated when there is a merge request associated with the pipeline. |
+| `CI_COMMIT_BEFORE_SHA` | 11.2 | all | The previous latest commit present on a branch. Is always `0000000000000000000000000000000000000000` in pipelines for merge requests. |
| `CI_COMMIT_DESCRIPTION` | 10.8 | all | The description of the commit: the message without first line, if the title is shorter than 100 characters; full message in other case. |
| `CI_COMMIT_MESSAGE` | 10.8 | all | The full commit message. |
| `CI_COMMIT_REF_NAME` | 9.0 | all | The branch or tag name for which project is built |
@@ -69,8 +71,8 @@ You can add a command to your `.gitlab-ci.yml` file to
| `CI_JOB_URL` | 11.1 | 0.5 | Job details URL |
| `CI_KUBERNETES_ACTIVE` | 13.0 | all | Included with the value `true` only if the pipeline has a Kubernetes cluster available for deployments. Not included if no cluster is available. Can be used as an alternative to [`only:kubernetes`/`except:kubernetes`](../yaml/README.md#onlykubernetesexceptkubernetes) with [`rules:if`](../yaml/README.md#rulesif) |
| `CI_MERGE_REQUEST_ASSIGNEES` | 11.9 | all | Comma-separated list of username(s) of assignee(s) for the merge request if [the pipelines are for merge requests](../merge_request_pipelines/index.md). Available only if `only: [merge_requests]` or [`rules`](../yaml/README.md#rules) syntax is used and the merge request is created. |
-| `CI_MERGE_REQUEST_ID` | 11.6 | all | The ID of the merge request if [the pipelines are for merge requests](../merge_request_pipelines/index.md). Available only if `only: [merge_requests]` or [`rules`](../yaml/README.md#rules) syntax is used and the merge request is created. |
-| `CI_MERGE_REQUEST_IID` | 11.6 | all | The IID of the merge request if [the pipelines are for merge requests](../merge_request_pipelines/index.md). Available only if `only: [merge_requests]` or [`rules`](../yaml/README.md#rules) syntax is used and the merge request is created. |
+| `CI_MERGE_REQUEST_ID` | 11.6 | all | The project-level ID of the merge request. Only available if [the pipelines are for merge requests](../merge_request_pipelines/index.md) and the merge request is created. |
+| `CI_MERGE_REQUEST_IID` | 11.6 | all | The instance-level IID of the merge request. Only available If [the pipelines are for merge requests](../merge_request_pipelines/index.md) and the merge request is created. |
| `CI_MERGE_REQUEST_LABELS` | 11.9 | all | Comma-separated label names of the merge request if [the pipelines are for merge requests](../merge_request_pipelines/index.md). Available only if `only: [merge_requests]` or [`rules`](../yaml/README.md#rules) syntax is used and the merge request is created. |
| `CI_MERGE_REQUEST_MILESTONE` | 11.9 | all | The milestone title of the merge request if [the pipelines are for merge requests](../merge_request_pipelines/index.md). Available only if `only: [merge_requests]` or [`rules`](../yaml/README.md#rules) syntax is used and the merge request is created. |
| `CI_MERGE_REQUEST_PROJECT_ID` | 11.6 | all | The ID of the project of the merge request if [the pipelines are for merge requests](../merge_request_pipelines/index.md). Available only if `only: [merge_requests]` or [`rules`](../yaml/README.md#rules) syntax is used and the merge request is created. |
@@ -92,13 +94,14 @@ You can add a command to your `.gitlab-ci.yml` file to
| `CI_PAGES_URL` | 11.8 | all | URL to GitLab Pages-built pages. Always belongs to a subdomain of `CI_PAGES_DOMAIN`. |
| `CI_PIPELINE_ID` | 8.10 | all | The unique ID of the current pipeline that GitLab CI/CD uses internally |
| `CI_PIPELINE_IID` | 11.0 | all | The unique ID of the current pipeline scoped to project |
-| `CI_PIPELINE_SOURCE` | 10.0 | all | Indicates how the pipeline was triggered. Possible options are: `push`, `web`, `trigger`, `schedule`, `api`, `pipeline`, `parent_pipeline`, `external`, `chat`, `merge_request_event`, and `external_pull_request_event`. For pipelines created before GitLab 9.5, this will show as `unknown` |
+| `CI_PIPELINE_SOURCE` | 10.0 | all | Indicates how the pipeline was triggered. Possible options are: `push`, `web`, `schedule`, `api`, `external`, `chat`, `webide`, `merge_request_event`, `external_pull_request_event`, `parent_pipeline`, [`trigger`, or `pipeline`](../triggers/README.md#authentication-tokens) (renamed to `cross_project_pipeline` since 13.0). For pipelines created before GitLab 9.5, this will show as `unknown`. |
| `CI_PIPELINE_TRIGGERED` | all | all | The flag to indicate that job was [triggered](../triggers/README.md) |
| `CI_PIPELINE_URL` | 11.1 | 0.5 | Pipeline details URL |
| `CI_PROJECT_DIR` | all | all | The full path where the repository is cloned and where the job is run. If the GitLab Runner `builds_dir` parameter is set, this variable is set relative to the value of `builds_dir`. For more information, see [Advanced configuration](https://docs.gitlab.com/runner/configuration/advanced-configuration.html#the-runners-section) for GitLab Runner. |
| `CI_PROJECT_ID` | all | all | The unique ID of the current project that GitLab CI/CD uses internally |
| `CI_PROJECT_NAME` | 8.10 | 0.5 | The name of the directory for the project that is currently being built. For example, if the project URL is `gitlab.example.com/group-name/project-1`, the `CI_PROJECT_NAME` would be `project-1`. |
| `CI_PROJECT_NAMESPACE` | 8.10 | 0.5 | The project namespace (username or group name) that is currently being built |
+| `CI_PROJECT_ROOT_NAMESPACE` | 13.2 | 0.5 | The **root** project namespace (username or group name) that is currently being built. For example, if `CI_PROJECT_NAME` is `root-group/child-group/grandchild-group`, `CI_PROJECT_ROOT_NAMESPACE` would be `root-group`. |
| `CI_PROJECT_PATH` | 8.10 | 0.5 | The namespace with project name |
| `CI_PROJECT_PATH_SLUG` | 9.3 | all | `$CI_PROJECT_PATH` lowercased and with everything except `0-9` and `a-z` replaced with `-`. Use in URLs and domain names. |
| `CI_PROJECT_REPOSITORY_LANGUAGES` | 12.3 | all | Comma-separated, lowercased list of the languages used in the repository (e.g. `ruby,javascript,html,css`) |
@@ -107,8 +110,8 @@ You can add a command to your `.gitlab-ci.yml` file to
| `CI_PROJECT_VISIBILITY` | 10.3 | all | The project visibility (internal, private, public) |
| `CI_REGISTRY` | 8.10 | 0.5 | If the Container Registry is enabled it returns the address of GitLab's Container Registry. This variable will include a `:port` value if one has been specified in the registry configuration. |
| `CI_REGISTRY_IMAGE` | 8.10 | 0.5 | If the Container Registry is enabled for the project it returns the address of the registry tied to the specific project |
-| `CI_REGISTRY_PASSWORD` | 9.0 | all | The password to use to push containers to the GitLab Container Registry |
-| `CI_REGISTRY_USER` | 9.0 | all | The username to use to push containers to the GitLab Container Registry |
+| `CI_REGISTRY_PASSWORD` | 9.0 | all | The password to use to push containers to the GitLab Container Registry, for the current project. |
+| `CI_REGISTRY_USER` | 9.0 | all | The username to use to push containers to the GitLab Container Registry, for the current project. |
| `CI_REPOSITORY_URL` | 9.0 | all | The URL to clone the Git repository |
| `CI_RUNNER_DESCRIPTION` | 8.10 | 0.5 | The description of the runner as saved in GitLab |
| `CI_RUNNER_EXECUTABLE_ARCH` | all | 10.6 | The OS/architecture of the GitLab Runner executable (note that this is not necessarily the same as the environment of the executor) |
@@ -129,11 +132,9 @@ You can add a command to your `.gitlab-ci.yml` file to
| `CI_SERVER_VERSION_MINOR` | 11.4 | all | GitLab version minor component |
| `CI_SERVER_VERSION_PATCH` | 11.4 | all | GitLab version patch component |
| `CI_SHARED_ENVIRONMENT` | all | 10.1 | Marks that the job is executed in a shared environment (something that is persisted across CI invocations like `shell` or `ssh` executor). If the environment is shared, it is set to true, otherwise it is not defined at all. |
-| `GET_SOURCES_ATTEMPTS` | 8.15 | 1.9 | Number of attempts to fetch sources running a job |
| `GITLAB_CI` | all | all | Mark that job is executed in GitLab CI/CD environment |
| `GITLAB_FEATURES` | 10.6 | all | The comma separated list of licensed features available for your instance and plan |
| `GITLAB_USER_EMAIL` | 8.12 | all | The email of the user who started the job |
| `GITLAB_USER_ID` | 8.12 | all | The ID of the user who started the job |
| `GITLAB_USER_LOGIN` | 10.0 | all | The login username of the user who started the job |
| `GITLAB_USER_NAME` | 10.0 | all | The real name of the user who started the job |
-| `RESTORE_CACHE_ATTEMPTS` | 8.15 | 1.9 | Number of attempts to restore the cache running a job |
diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md
index 7ed5a8fec01..e1d1d27efed 100644
--- a/doc/ci/yaml/README.md
+++ b/doc/ci/yaml/README.md
@@ -5,7 +5,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w
type: reference
---
-# GitLab CI/CD Pipeline Configuration Reference
+# GitLab CI/CD pipeline configuration reference
GitLab CI/CD [pipelines](../pipelines/index.md) are configured using a YAML file called `.gitlab-ci.yml` within each project.
@@ -117,7 +117,7 @@ The following table lists available parameters for jobs:
| [`when`](#when) | When to run job. Also available: `when:manual` and `when:delayed`. |
| [`environment`](#environment) | Name of an environment to which the job deploys. Also available: `environment:name`, `environment:url`, `environment:on_stop`, `environment:auto_stop_in` and `environment:action`. |
| [`cache`](#cache) | List of files that should be cached between subsequent runs. Also available: `cache:paths`, `cache:key`, `cache:untracked`, and `cache:policy`. |
-| [`artifacts`](#artifacts) | List of files and directories to attach to a job on success. Also available: `artifacts:paths`, `artifacts:exclude`, `artifacts:expose_as`, `artifacts:name`, `artifacts:untracked`, `artifacts:when`, `artifacts:expire_in`, `artifacts:reports`, `artifacts:reports:junit`, `artifacts:reports:cobertura`, and `artifacts:reports:terraform`.<br><br>In GitLab [Enterprise Edition](https://about.gitlab.com/pricing/), these are available: `artifacts:reports:codequality`, `artifacts:reports:sast`, `artifacts:reports:dependency_scanning`, `artifacts:reports:container_scanning`, `artifacts:reports:dast`, `artifacts:reports:license_scanning`, `artifacts:reports:license_management` (removed in GitLab 13.0),`artifacts:reports:performance` and `artifacts:reports:metrics`. |
+| [`artifacts`](#artifacts) | List of files and directories to attach to a job on success. Also available: `artifacts:paths`, `artifacts:exclude`, `artifacts:expose_as`, `artifacts:name`, `artifacts:untracked`, `artifacts:when`, `artifacts:expire_in`, `artifacts:reports`, `artifacts:reports:codequality`, `artifacts:reports:junit`, `artifacts:reports:cobertura`, and `artifacts:reports:terraform`.<br><br>In GitLab [Enterprise Edition](https://about.gitlab.com/pricing/), these are available: `artifacts:reports:sast`, `artifacts:reports:dependency_scanning`, `artifacts:reports:container_scanning`, `artifacts:reports:dast`, `artifacts:reports:license_scanning`, `artifacts:reports:license_management` (removed in GitLab 13.0), `artifacts:reports:performance`, `artifacts:reports:load_performance`, and `artifacts:reports:metrics`. |
| [`dependencies`](#dependencies) | Restrict which artifacts are passed to a specific job by providing a list of jobs to fetch artifacts from. |
| [`coverage`](#coverage) | Code coverage settings for a given job. |
| [`retry`](#retry) | When and how many times a job can be auto-retried in case of a failure. |
@@ -130,6 +130,7 @@ The following table lists available parameters for jobs:
| [`variables`](#variables) | Define job variables on a job level. |
| [`interruptible`](#interruptible) | Defines if a job can be canceled when made redundant by a newer run. |
| [`resource_group`](#resource_group) | Limit job concurrency. |
+| [`release`](#release) | Instructs the Runner to generate a [Release](../../user/project/releases/index.md) object. |
NOTE: **Note:**
Parameters `types` and `type` are [deprecated](#deprecated-parameters).
@@ -256,8 +257,8 @@ karma:
### `stages`
-`stages` is used to define stages that can be used by jobs and is defined
-globally.
+`stages` is used to define stages that contain jobs and is defined
+globally for the pipeline.
The specification of `stages` allows for having flexible multi stage pipelines.
The ordering of elements in `stages` defines the ordering of jobs' execution:
@@ -297,6 +298,26 @@ determine whether or not a pipeline is created. It currently accepts a single
`rules:` key that operates similarly to [`rules:` defined within jobs](#rules),
enabling dynamic configuration of the pipeline.
+If you are new to GitLab CI/CD and `workflow: rules`, you may find the [`workflow:rules` templates](#workflowrules-templates) useful.
+
+To define your own `workflow: rules`, the configuration options currently available are:
+
+- [`if`](#rulesif): Define a rule.
+- [`when`](#when): May be set to `always` or `never` only. If not provided, the default value is `always`​.
+
+The list of `if` rules is evaluated until a single one is matched. If none
+match, the last `when` will be used:
+
+```yaml
+workflow:
+ rules:
+ - if: $CI_COMMIT_REF_NAME =~ /-wip$/
+ when: never
+ - if: $CI_COMMIT_TAG
+ when: never
+ - when: always
+```
+
#### `workflow:rules` templates
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/217732) in GitLab 13.0.
@@ -334,24 +355,6 @@ include:
- template: 'Workflows/MergeRequest-Pipelines.gitlab-ci.yml'
```
-If you prefer to define your own rules, the configuration options currently available are:​
-
-- [`if`](#rulesif): Define a rule.
-- [`when`](#when): May be set to `always` or `never` only. If not provided, the default value is `always`​.
-
-The list of `if` rules is evaluated until a single one is matched. If none
-match, the last `when` will be used:
-
-```yaml
-workflow:
- rules:
- - if: $CI_COMMIT_REF_NAME =~ /-wip$/
- when: never
- - if: $CI_COMMIT_TAG
- when: never
- - when: always
-```
-
### `include`
> - Introduced in [GitLab Premium](https://about.gitlab.com/pricing/) 10.5.
@@ -375,12 +378,14 @@ otherwise the external file won't be included.
| [`remote`](#includeremote) | Include a file from a remote URL. Must be publicly accessible. |
| [`template`](#includetemplate) | Include templates which are provided by GitLab. |
+The `include` methods do not support [variable expansion](../variables/where_variables_can_be_used.md#variables-usage).
+
NOTE: **Note:**
`.gitlab-ci.yml` configuration included by all methods is evaluated at pipeline creation.
The configuration is a snapshot in time and persisted in the database. Any changes to
referenced `.gitlab-ci.yml` configuration won't be reflected in GitLab until the next pipeline is created.
-The files defined in `include` are:
+The files defined by `include` are:
- Deep merged with those in `.gitlab-ci.yml`.
- Always evaluated first and merged with the content of `.gitlab-ci.yml`,
@@ -388,11 +393,11 @@ The files defined in `include` are:
TIP: **Tip:**
Use merging to customize and override included CI/CD configurations with local
-definitions.
+definitions. Local definitions in `.gitlab-ci.yml` will override included definitions.
NOTE: **Note:**
-Using YAML aliases across different YAML files sourced by `include` is not
-supported. You must only refer to aliases in the same file. Instead
+Using [YAML anchors](#anchors) across different YAML files sourced by `include` is not
+supported. You must only refer to anchors in the same file. Instead
of using YAML anchors, you can use the [`extends` keyword](#extends).
#### `include:local`
@@ -699,6 +704,101 @@ job:
- Write-Host "This text is not colored"
```
+#### Multiline commands
+
+You can split long commands into multi-line commands to improve readability
+using [`|` (literal) and `>` (folded) YAML multiline block scalar indicators](https://yaml-multiline.info/).
+
+CAUTION: **Warning:**
+If multiple commands are combined into one command string, only the last command's
+failure or success will be reported,
+[incorrectly ignoring failures from earlier commands due to a bug](https://gitlab.com/gitlab-org/gitlab-runner/-/issues/25394).
+If the success of the job depends on the success or failure of these commands,
+you can run the commands as separate `script:` items, or add `exit 1` commands
+as appropriate to the command string where needed.
+
+You can use the `|` (literal) YAML multiline block scalar indicator to write
+commands over multiple lines in the `script` section of a job description.
+Each line is treated as a separate command.
+Only the first command is repeated in the job log, but additional
+commands are still executed:
+
+```yaml
+job:
+ script:
+ - |
+ echo "First command line."
+ echo "Second command line."
+ echo "Third command line."
+```
+
+The example above renders in the job log as:
+
+```shell
+$ echo First command line # collapsed multi-line command
+First command line
+Second command line.
+Third command line.
+```
+
+The `>` (folded) YAML multiline block scalar indicator treats empty lines between
+sections as the start of a new command:
+
+```yaml
+job:
+ script:
+ - >
+ echo "First command line
+ is split over two lines."
+
+ echo "Second command line."
+```
+
+This behaves similarly to writing multiline commands without the `>` or `|` block
+scalar indicators:
+
+```yaml
+job:
+ script:
+ - echo "First command line
+ is split over two lines."
+
+ echo "Second command line."
+```
+
+Both examples above render in the job log as:
+
+```shell
+$ echo First command line is split over two lines. # collapsed multi-line command
+First command line is split over two lines.
+Second command line.
+```
+
+When the `>` or `|` block scalar indicators are omitted, GitLab will form the command
+by concatenating non-empty lines, so make sure the lines can run when concatenated.
+
+Shell [here documents](https://en.wikipedia.org/wiki/Here_document) work with the
+`|` and `>` operators as well. The example below transliterates the lower case letters
+to upper case:
+
+```yaml
+job:
+ script:
+ - |
+ tr a-z A-Z << END_TEXT
+ one two three
+ four five six
+ END_TEXT
+```
+
+Results in:
+
+```shell
+$ tr a-z A-Z << END_TEXT # collapsed multi-line command
+ ONE TWO THREE
+ FOUR FIVE SIX
+```
+
### `stage`
`stage` is defined per-job and relies on [`stages`](#stages) which is defined
@@ -874,24 +974,38 @@ spinach:
```
In GitLab 12.0 and later, it's also possible to use multiple parents for
-`extends`. The algorithm used for merge is "closest scope wins", so
-keys from the last member will always shadow anything defined on other
+`extends`.
+
+#### Merge details
+
+`extends` is able to merge hashes but not arrays.
+The algorithm used for merge is "closest scope wins", so
+keys from the last member will always override anything defined on other
levels. For example:
```yaml
.only-important:
+ variables:
+ URL: "http://my-url.internal"
+ IMPORTANT_VAR: "the details"
only:
- master
- stable
tags:
- production
+ script:
+ - echo "Hello world!"
.in-docker:
+ variables:
+ URL: "http://docker-url.internal"
tags:
- docker
image: alpine
rspec:
+ variables:
+ GITLAB: "is-awesome"
extends:
- .only-important
- .in-docker
@@ -903,6 +1017,10 @@ This results in the following `rspec` job:
```yaml
rspec:
+ variables:
+ URL: "http://docker-url.internal"
+ IMPORTANT_VAR: "the details"
+ GITLAB: "is-awesome"
only:
- master
- stable
@@ -913,6 +1031,15 @@ rspec:
- rake rspec
```
+Note that in the example above:
+
+- `variables` sections have been merged but that `URL: "http://my-url.internal"`
+has been overwritten by `URL: "http://docker-url.internal"`.
+- `tags: ['production']` has been overwritten by `tags: ['docker']`.
+- `script` has not been merged but rather `script: ['echo "Hello world!"']` has
+ been overwritten by `script: ['rake rspec']`. Arrays can be
+ merged using [YAML anchors](#anchors).
+
#### Using `extends` and `include` together
`extends` works across configuration files combined with `include`.
@@ -942,113 +1069,295 @@ the `.template` job, and uses the `alpine` Docker image as defined in the local
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/27863) in GitLab 12.3.
-`rules` allows for a list of individual rule objects to be evaluated
-*in order*, until one matches and dynamically provides attributes to the job.
+The `rules` keyword can be used to include or exclude jobs in pipelines.
+
+Rules are evaluated *in order* until the first match. When matched, the job
+is either included or excluded from the pipeline, depending on the configuration.
+If included, the job also has [certain attributes](#rules-attributes)
+added to it.
CAUTION: **Caution:**
-`rules` can't be used in combination with `only/except` as it is a replacement for that functionality. If you attempt to do this, the linter will return a
+`rules` can't be used in combination with [`only/except`](#onlyexcept-basic) because it is a replacement for
+that functionality. If you attempt to do this, the linter returns a
`key may not be used with rules` error.
-#### Key details when using `rules`
+#### Rules attributes
-A very important difference between `rules` and `only/except`, is that jobs defined
-with `rules` trigger merge request pipelines by default, but `only/except` jobs do not.
-This may be surprising if migrating from `only` and `except`, so new users of `rules`
-can use one of the [`workflow: rules` templates](#workflowrules-templates) to get started.
-This will ensure that the behavior is more stable as you start adding additional `rules`
-blocks, and will avoid issues like creating a duplicate, merge request (detached) pipeline.
+The job attributes allowed by `rules` are:
-We don't recommend mixing `only/except` jobs with `rules` jobs in the same pipeline.
-It may not cause YAML errors, but debugging the exact execution behavior can be complex
-due to the different default behaviors of `only/except` and `rules`.
+- [`when`](#when): If not defined, defaults to `when: on_success`.
+ - If used as `when: delayed`, `start_in` is also required.
+- [`allow_failure`](#allow_failure): If not defined, defaults to `allow_failure: false`.
+
+If a rule evaluates to true, and `when` has any value except `never`, the job is included in the pipeline.
+
+For example:
+
+```yaml
+docker build:
+ script: docker build -t my-image:$CI_COMMIT_REF_SLUG .
+ rules:
+ - if: '$CI_COMMIT_BRANCH == "master"'
+ when: delayed
+ start_in: '3 hours'
+ allow_failure: true
+```
+
+Additional job configuration may be added to rules in the future. If something
+useful is not available, please [open an issue](https://gitlab.com/gitlab-org/gitlab/-/issues).
+
+#### Rules clauses
+
+Available rule clauses are:
+
+| Clause | Description |
+|----------------------------|------------------------------------------------------------------------------------------------------------------------------------|
+| [`if`](#rulesif) | Add or exclude jobs from a pipeline by evaluating an `if` statement. Similar to [`only:variables`](#onlyvariablesexceptvariables). |
+| [`changes`](#ruleschanges) | Add or exclude jobs from a pipeline based on what files are changed. Same as [`only:changes`](#onlychangesexceptchanges). |
+| [`exists`](#rulesexists) | Add or exclude jobs from a pipeline based on the presence of specific files. |
-### Rules clauses
+Rules are evaluated in order until a match is found. If a match is found, the attributes
+are checked to see if the job should be added to the pipeline. If no attributes are defined,
+the defaults are:
-Available rule clauses include:
+- `when: on_success`
+- `allow_failure: false`
-- [`if`](#rulesif) (similar to [`only:variables`](#onlyvariablesexceptvariables))
-- [`changes`](#ruleschanges) (same as [`only:changes`](#onlychangesexceptchanges))
-- [`exists`](#rulesexists)
+The job is added to the pipeline:
-For example, using `if`. This configuration specifies that `job` should be built
-and run for every pipeline on merge requests targeting `master`, regardless of
-the status of other builds:
+- If a rule matches and has `when: on_success`, `when: delayed` or `when: always`.
+- If no rules match, but the last clause is `when: on_success`, `when: delayed`
+ or `when: always` (with no rule).
+
+The job is not added to the pipeline:
+
+- If no rules match, and there is no standalone `when: on_success`, `when: delayed` or
+ `when: always`.
+- If a rule matches, and has `when: never` as the attribute.
+
+For example, using `if` clauses to strictly limit when jobs run:
```yaml
job:
script: "echo Hello, Rules!"
rules:
- - if: '$CI_MERGE_REQUEST_TARGET_BRANCH_NAME == "master"'
- when: always
- - if: '$VAR =~ /pattern/'
+ - if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
when: manual
+ allow_failure: true
+ - if: '$CI_PIPELINE_SOURCE == "schedule"'
+```
+
+In this example:
+
+- If the pipeline is for a merge request, the first rule matches, and the job
+ is added to the [merge request pipeline](../merge_request_pipelines/index.md)
+ with attributes of:
+ - `when: manual` (manual job)
+ - `allow_failure: true` (allows the pipeline to continue running even if the manual job is not run)
+- If the pipeline is **not** for a merge request, the first rule doesn't match, and the
+ second rule is evaluated.
+- If the pipeline is a scheduled pipeline, the second rule matches, and the job
+ is added to the scheduled pipeline. Since no attributes were defined, it is added
+ with:
+ - `when: on_success` (default)
+ - `allow_failure: false` (default)
+- In **all other cases**, no rules match, so the job is **not** added to any other pipeline.
+
+Alternatively, you can define a set of rules to exclude jobs in a few cases, but
+run them in all other cases:
+
+```yaml
+job:
+ script: "echo Hello, Rules!"
+ rules:
+ - if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
+ when: never
+ - if: '$CI_PIPELINE_SOURCE == "schedule"'
+ when: never
- when: on_success
```
-In this example, if the first rule:
+- If the pipeline is for a merge request, the job is **not** be added to the pipeline.
+- If the pipeline is a scheduled pipeline, the job is **not** be added to the pipeline.
+- In **all other cases**, the job is added to the pipeline, with `when: on_success`.
+
+CAUTION: **Caution:**
+If you use `when: on_success`, `always`, or `delayed` as the final rule, two
+simultaneous pipelines may start. Both push pipelines and merge request pipelines can
+be triggered by the same event (a push to the source branch for an open merge request).
+See the [important differences between `rules` and `only`/`except`](#differences-between-rules-and-onlyexcept)
+for more details.
+
+#### Differences between `rules` and `only`/`except`
+
+Jobs defined with `only/except` do not trigger merge request pipelines by default.
+You must explicitly add `only: merge_requests`.
+
+Jobs defined with `rules` can trigger all types of pipelines.
+You do not have to explicitly configure each type.
+
+For example:
+
+```yaml
+job:
+ script: "echo This creates double pipelines!"
+ rules:
+ - if: '$CUSTOM_VARIABLE == "false"'
+ when: never
+ - when: always
+```
+
+This job does not run when `$CUSTOM_VARIABLE` is false, but it *does* run in **all**
+other pipelines, including **both** push (branch) and merge request pipelines. With
+this configuration, every push to an open merge request's source branch
+causes duplicated pipelines. Explicitly allowing both push and merge request pipelines
+in the same job could have the same effect.
+
+We recommend using [`workflow: rules`](#workflowrules) to limit which types of pipelines
+are permitted. Allowing only merge request pipelines, or only branch pipelines,
+eliminates duplicated pipelines. Alternatively, you can rewrite the rules to be
+stricter, or avoid using a final `when` (`always`, `on_success` or `delayed`).
+
+Also, we don't recommend mixing `only/except` jobs with `rules` jobs in the same pipeline.
+It may not cause YAML errors, but debugging the exact execution behavior can be complex
+due to the different default behaviors of `only/except` and `rules`.
+
+##### `rules:if`
-- Matches, the job will be given the `when:always` attribute.
-- Does not match, the second and third rules will be evaluated sequentially
- until a match is found. That is, the job will be given either the:
- - `when: manual` attribute if the second rule matches. **The stage won't complete until this manual job is triggered and completes successfully.**
- - `when: on_success` attribute if the second rule does not match. The third
- rule will always match when reached because it has no conditional clauses.
+`rules:if` clauses determine whether or not jobs are added to a pipeline by evaluating
+a simple `if` statement. If the `if` statement is true, the job is either included
+or excluded from a pipeline. In plain English, `if` rules can be interpreted as one of:
-#### `rules:if`
+- "If this rule evaluates to true, add the job" (default).
+- "If this rule evaluates to true, do not add the job" (by adding `when: never`).
`rules:if` differs slightly from `only:variables` by accepting only a single
-expression string, rather than an array of them. Any set of expressions to be
-evaluated should be conjoined into a single expression using `&&` or `||`, and use
+expression string per rule, rather than an array of them. Any set of expressions to be
+evaluated can be conjoined into a single expression by using `&&` or `||`, and use
the [variable matching syntax](../variables/README.md#syntax-of-environment-variable-expressions).
+`if:` clauses are evaluated based on the values of [predefined environment variables](../variables/predefined_variables.md)
+or [custom environment variables](../variables/README.md#custom-environment-variables).
+
For example:
```yaml
job:
script: "echo Hello, Rules!"
rules:
- - if: '$CI_MERGE_REQUEST_SOURCE_BRANCH_NAME =~ /^feature/ && $CI_MERGE_REQUEST_TARGET_BRANCH_NAME == "master"' # This rule will be evaluated
+ - if: '$CI_MERGE_REQUEST_SOURCE_BRANCH_NAME =~ /^feature/ && $CI_MERGE_REQUEST_TARGET_BRANCH_NAME == "master"'
when: always
- - if: '$CI_MERGE_REQUEST_SOURCE_BRANCH_NAME =~ /^feature/' # This rule will only be evaluated if the target branch is not "master"
+ - if: '$CI_MERGE_REQUEST_SOURCE_BRANCH_NAME =~ /^feature/'
when: manual
- - if: '$CI_MERGE_REQUEST_SOURCE_BRANCH_NAME' # If neither of the first two match but the simple presence does, we set to "on_success" by default
+ allow_failure: true
+ - if: '$CI_MERGE_REQUEST_SOURCE_BRANCH_NAME' # Checking for the presence of a variable is possible
```
Some details regarding the logic that determines the `when` for the job:
-- If none of the provided rules match, the job is set to `when: never`, and is
+- If none of the provided rules match, the job is set to `when: never` and is
not included in the pipeline.
- A rule without any conditional clause, such as a `when` or `allow_failure`
rule without `if` or `changes`, always matches, and is always used if reached.
-- If a rule matches and has no `when` defined, the rule will use the `when`
+- If a rule matches and has no `when` defined, the rule uses the `when`
defined for the job, which defaults to `on_success` if not defined.
+- You can define `when` once per rule, or once at the job-level, which applies to
+ all rules. You can't mix `when` at the job-level with `when` in rules.
+
+For behavior similar to the [`only`/`except` keywords](#onlyexcept-basic), you can
+check the value of the `$CI_PIPELINE_SOURCE` variable.
+
+| Value | Description |
+|-------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| `push` | For pipelines triggered by a `git push` event, including for branches and tags. |
+| `web` | For pipelines created by using **Run pipeline** button in the GitLab UI, from the project's **CI/CD > Pipelines** section. |
+| `trigger` | For pipelines created by using a trigger token. |
+| `schedule` | For [scheduled pipelines](../pipelines/schedules.md). |
+| `api` | For pipelines triggered by the [pipelines API](../../api/pipelines.md#create-a-new-pipeline). |
+| `external` | When using CI services other than GitLab. |
+| `pipelines` | For multi-project pipelines created by [using the API with `CI_JOB_TOKEN`](../triggers/README.md#when-used-with-multi-project-pipelines). |
+| `chat` | For pipelines created by using a [GitLab ChatOps](../chatops/README.md) command. |
+| `webide` | For pipelines created by using the [WebIDE](../../user/project/web_ide/index.md). |
+| `merge_request_event` | For pipelines created when a merge request is created or updated. Required to enable [merge request pipelines](../merge_request_pipelines/index.md), [merged results pipelines](../merge_request_pipelines/pipelines_for_merged_results/index.md), and [merge trains](../merge_request_pipelines/pipelines_for_merged_results/merge_trains/index.md). |
+| `external_pull_request_event` | When an external pull request on GitHub is created or updated. See [Pipelines for external pull requests](../ci_cd_for_external_repos/index.md#pipelines-for-external-pull-requests). |
+| `parent_pipeline` | For pipelines triggered by a [parent/child pipeline](../parent_child_pipelines.md) with `rules`, use this in the child pipeline configuration so that it can be triggered by the parent pipeline. |
+
+For example:
+
+```yaml
+job:
+ script: "echo Hello, Rules!"
+ rules:
+ - if: '$CI_PIPELINE_SOURCE == "schedule"'
+ when: manual
+ allow_failure: true
+ - if: '$CI_PIPELINE_SOURCE == "push"'
+```
+
+This example runs the job as a manual job in scheduled pipelines or in push
+pipelines (to branches or tags), with `when: on_success` (default). It does not
+add the job to any other pipeline type.
+
+Another example:
+
+```yaml
+job:
+ script: "echo Hello, Rules!"
+ rules:
+ - if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
+ - if: '$CI_PIPELINE_SOURCE == "schedule"'
+```
+
+This example runs the job as a `when: on_success` job in [merge request pipelines](../merge_request_pipelines/index.md)
+and scheduled pipelines. It does not run in any other pipeline type.
-#### `rules:changes`
+Other commonly used variables for `if` clauses:
-`rules: changes` works exactly the same way as `only: changes` and `except: changes`,
-accepting an array of paths. Similarly, it will always return true if there is no
-Git push event. See [`only/except: changes`](#onlychangesexceptchanges) for more information.
+- `if: $CI_COMMIT_TAG`: If changes are pushed for a tag.
+- `if: $CI_COMMIT_BRANCH`: If changes are pushed to any branch.
+- `if: '$CI_COMMIT_BRANCH == "master"'`: If changes are pushed to `master`.
+- `if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'`: If changes are pushed to the default
+ branch (usually `master`). Useful if reusing the same configuration in multiple
+ projects with potentially different default branches.
+- `if: '$CI_COMMIT_BRANCH =~ /regex-expression/'`: If the commit branch matches a regular expression.
+- `if: '$CUSTOM_VARIABLE !~ /regex-expression/'`: If the [custom variable](../variables/README.md#custom-environment-variables)
+ `CUSTOM_VARIABLE` does **not** match a regular expression.
+- `if: '$CUSTOM_VARIABLE == "value1"'`: If the custom variable `CUSTOM_VARIABLE` is
+ exactly `value1`.
+
+##### `rules:changes`
+
+To determine if jobs should be added to a pipeline, `rules: changes` clauses check
+the files changed by Git push events.
+
+`rules: changes` works exactly the same way as [`only: changes` and `except: changes`](#onlychangesexceptchanges),
+accepting an array of paths. Similarly, it always returns true if there is no
+Git push event. It should only be used for branch pipelines or merge request pipelines.
For example:
```yaml
+workflow:
+ rules:
+ - if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
+
docker build:
script: docker build -t my-image:$CI_COMMIT_REF_SLUG .
rules:
- - changes: # Will include the job and set to when:manual if any of the follow paths match a modified file.
- - Dockerfile
+ - changes:
+ - Dockerfile
when: manual
- - if: '$VAR == "string value"'
- when: manual # Will include the job and set to when:manual if the expression evaluates to true, after the `changes:` rule fails to match.
- - when: on_success # If neither of the first rules match, set to on_success
+ allow_failure: true
```
-In this example, a job either set to:
+In this example:
-- Run manually if `Dockerfile` has changed OR `$VAR == "string value"`.
-- `when:on_success` by the last rule, where no earlier clauses evaluate to true.
+- [`workflow: rules`](#workflowrules) allows only pipelines for merge requests for all jobs.
+- If `Dockerfile` has changed, add the job to the pipeline as a manual job, and allow the pipeline
+ to continue running even if the job is not triggered (`allow_failure: true`).
+- If `Dockerfile` has not changed, do not add job to any pipeline (same as `when: never`).
-#### `rules:exists`
+##### `rules:exists`
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/24021) in GitLab 12.4.
@@ -1062,7 +1371,7 @@ job:
script: docker build -t my-image:$CI_COMMIT_REF_SLUG .
rules:
- exists:
- - Dockerfile
+ - Dockerfile
```
You can also use glob patterns to match multiple files in any directory within
@@ -1075,14 +1384,14 @@ job:
script: bundle exec rspec
rules:
- exists:
- - spec/**.rb
+ - spec/**.rb
```
NOTE: **Note:**
For performance reasons, using `exists` with patterns is limited to 10000
checks. After the 10000th check, rules with patterned globs will always match.
-#### `rules:allow_failure`
+##### `rules:allow_failure`
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/30235) in GitLab 12.8.
@@ -1122,53 +1431,18 @@ docker build:
rules:
- if: '$VAR == "string value"'
changes: # Will include the job and set to when:manual if any of the follow paths match a modified file.
- - Dockerfile
- - docker/scripts/*
+ - Dockerfile
+ - docker/scripts/*
when: manual
# - when: never would be redundant here, this is implied any time rules are listed.
```
-The only clauses currently available are:
-
-- `if`
-- `changes`
-- `exists`
-
Keywords such as `branches` or `refs` that are currently available for
`only`/`except` are not yet available in `rules` as they are being individually
considered for their usage and behavior in this context. Future keyword improvements
are being discussed in our [epic for improving `rules`](https://gitlab.com/groups/gitlab-org/-/epics/2783),
where anyone can add suggestions or requests.
-#### Permitted attributes
-
-The only job attributes currently set by `rules` are:
-
-- `when`.
-- `start_in`, if `when` is set to `delayed`.
-- `allow_failure`.
-
-A job will be included in a pipeline if `when` is evaluated to any value
-except `never`.
-
-Delayed jobs require a `start_in` value, so rule objects do as well. For
-example:
-
-```yaml
-docker build:
- script: docker build -t my-image:$CI_COMMIT_REF_SLUG .
- rules:
- - changes: # Will include the job and delay 3 hours when the Dockerfile has changed
- - Dockerfile
- when: delayed
- start_in: '3 hours'
- - when: on_success # Otherwise include the job and set to run normally
-```
-
-Additional job configuration may be added to rules in the future. If something
-useful is not available, please
-[open an issue](https://gitlab.com/gitlab-org/gitlab/-/issues).
-
### `only`/`except` (basic)
NOTE: **Note:**
@@ -1193,20 +1467,20 @@ There are a few rules that apply to the usage of job policy:
In addition, `only` and `except` allow the use of special keywords:
-| **Value** | **Description** |
-| --------- | ---------------- |
-| `branches` | When a Git reference of a pipeline is a branch. |
-| `tags` | When a Git reference of a pipeline is a tag. |
-| `api` | When pipeline has been triggered by a second pipelines API (not triggers API). |
-| `external` | When using CI services other than GitLab. |
-| `pipelines` | For multi-project triggers, created using the API with `CI_JOB_TOKEN`. |
-| `pushes` | Pipeline is triggered by a `git push` by the user. |
-| `schedules` | For [scheduled pipelines](../pipelines/schedules.md). |
-| `triggers` | For pipelines created using a trigger token. |
-| `web` | For pipelines created using **Run pipeline** button in GitLab UI (under your project's **Pipelines**). |
-| `merge_requests` | When a merge request is created or updated (See [pipelines for merge requests](../merge_request_pipelines/index.md)). |
-| `external_pull_requests`| When an external pull request on GitHub is created or updated (See [Pipelines for external pull requests](../ci_cd_for_external_repos/index.md#pipelines-for-external-pull-requests)). |
-| `chat` | For jobs created using a [GitLab ChatOps](../chatops/README.md) command. |
+| **Value** | **Description** |
+|--------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| `branches` | When the Git reference for a pipeline is a branch. |
+| `tags` | When the Git reference for a pipeline is a tag. |
+| `api` | For pipelines triggered by the [pipelines API](../../api/pipelines.md#create-a-new-pipeline). |
+| `external` | When using CI services other than GitLab. |
+| `pipelines` | For multi-project pipelines created by using the API with `CI_JOB_TOKEN`. |
+| `pushes` | For pipelines triggered by a `git push` event, including for branches and tags. |
+| `schedules` | For [scheduled pipelines](../pipelines/schedules.md). |
+| `triggers` | For pipelines created by using a trigger token. |
+| `web` | For pipelines created by using **Run pipeline** button in the GitLab UI, from the project's **CI/CD > Pipelines** section. |
+| `merge_requests` | For pipelines created when a merge request is created or updated. Enables [merge request pipelines](../merge_request_pipelines/index.md), [merged results pipelines](../merge_request_pipelines/pipelines_for_merged_results/index.md), and [merge trains](../merge_request_pipelines/pipelines_for_merged_results/merge_trains/index.md). |
+| `external_pull_requests` | When an external pull request on GitHub is created or updated (See [Pipelines for external pull requests](../ci_cd_for_external_repos/index.md#pipelines-for-external-pull-requests)). |
+| `chat` | For pipelines created by using a [GitLab ChatOps](../chatops/README.md) command. |
In the example below, `job` will run only for refs that start with `issue-`,
whereas all branches will be skipped:
@@ -1295,7 +1569,7 @@ and must be surrounded by `/`.
So `issue-/.*/` won't work to match all tag names or branch names
that begin with `issue-`.
-TIP: **Tip**
+TIP: **Tip:**
Use anchors `^` and `$` to avoid the regular expression
matching only a substring of the tag name or branch name.
For example, `/^issue-.*$/` is equivalent to `/^issue-/`,
@@ -1701,8 +1975,11 @@ Feature::enable(:ci_dag_limit_needs)
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/14311) in GitLab v12.6.
-When using `needs`, artifact downloads are controlled with `artifacts: true` or `artifacts: false`.
-The `dependencies` keyword should not be used with `needs`, as this is deprecated since GitLab 12.6.
+When using `needs`, artifact downloads are controlled with `artifacts: true` (default) or `artifacts: false`.
+
+Since GitLab 12.6, you can't combine the [`dependencies`](#dependencies) keyword
+with `needs` to control artifact downloads in jobs. `dependencies` is still valid
+in jobs that do not use `needs`.
In the example below, the `rspec` job will download the `build_job` artifacts, while the
`rubocop` job won't:
@@ -2160,7 +2437,8 @@ review_app:
stage: deploy
script: make deploy-app
environment:
- name: review
+ name: review/$CI_COMMIT_REF_NAME
+ url: https://$CI_ENVIRONMENT_SLUG.example.com
on_stop: stop_review_app
stop_review_app:
@@ -2170,7 +2448,7 @@ stop_review_app:
script: make delete-app
when: manual
environment:
- name: review
+ name: review/$CI_COMMIT_REF_NAME
action: stop
```
@@ -2195,8 +2473,6 @@ The `stop_review_app` job is **required** to have the following keywords defined
- `when` - [reference](#when)
- `environment:name`
- `environment:action`
-- `stage` should be the same as the `review_app` in order for the environment
- to stop automatically when the branch is deleted
Additionally, both jobs should have matching [`rules`](../yaml/README.md#onlyexcept-basic)
or [`only/except`](../yaml/README.md#onlyexcept-basic) configuration. In the example
@@ -2554,7 +2830,12 @@ be available for download in the GitLab UI.
Paths are relative to the project directory (`$CI_PROJECT_DIR`) and can't directly
link outside it. Wildcards can be used that follow the [glob](https://en.wikipedia.org/wiki/Glob_(programming))
-patterns and [`filepath.Match`](https://golang.org/pkg/path/filepath/#Match).
+patterns and:
+
+- In [GitLab Runner 13.0](https://gitlab.com/gitlab-org/gitlab-runner/-/issues/2620) and later,
+[`doublestar.Glob`](https://pkg.go.dev/github.com/bmatcuk/doublestar@v1.2.2?tab=doc#Match).
+- In GitLab Runner 12.10 and earlier,
+[`filepath.Match`](https://pkg.go.dev/path/filepath/#Match).
To restrict which jobs a specific job will fetch artifacts from, see [dependencies](#dependencies).
@@ -2645,10 +2926,10 @@ For example, to match a single file:
```yaml
test:
- script: [ 'echo 1' ]
+ script: [ "echo 'test' > file.txt" ]
artifacts:
expose_as: 'artifact 1'
- paths: ['path/to/file.txt']
+ paths: ['file.txt']
```
With this configuration, GitLab will add a link **artifact 1** to the relevant merge request
@@ -2658,14 +2939,15 @@ An example that will match an entire directory:
```yaml
test:
- script: [ 'echo 1' ]
+ script: [ "mkdir test && echo 'test' > test/file.txt" ]
artifacts:
expose_as: 'artifact 1'
- paths: ['path/to/directory/']
+ paths: ['test/']
```
Note the following:
+- Artifacts do not display in the merge request UI when using variables to define the `artifacts:paths`.
- A maximum of 10 job artifacts per merge request can be exposed.
- Glob patterns are unsupported.
- If a directory is specified, the link will be to the job [artifacts browser](../pipelines/job_artifacts.md#browsing-artifacts) if there is more than
@@ -2818,7 +3100,7 @@ job:
expire and are therefore deleted, counting from the time they are uploaded and
stored on GitLab. If the expiry time is not defined, it defaults to the
[instance wide setting](../../user/admin_area/settings/continuous_integration.md#default-artifacts-expiration-core-only)
-(30 days by default, forever on GitLab.com).
+(30 days by default).
You can use the **Keep** button on the job page to override expiration and
keep artifacts forever.
@@ -2846,8 +3128,10 @@ job:
```
NOTE: **Note:**
-For artifacts created in [GitLab 13.1](https://gitlab.com/gitlab-org/gitlab/-/issues/16267)
-and later, the latest artifact for a ref is always kept, regardless of the expiry time.
+Since [GitLab 13.0](https://gitlab.com/gitlab-org/gitlab/-/issues/16267), the latest
+artifacts for refs can be locked against deletion, and kept regardless of the expiry time. This feature is disabled
+by default and is not ready for production use. It can be enabled for testing by
+enabling the `:keep_latest_artifact_for_ref` and `:destroy_only_unlocked_expired_artifacts` [feature flags](../../administration/feature_flags.md).
#### `artifacts:reports`
@@ -2863,14 +3147,15 @@ These are the available report types:
| [`artifacts:reports:dotenv`](../pipelines/job_artifacts.md#artifactsreportsdotenv) | The `dotenv` report collects a set of environment variables. |
| [`artifacts:reports:cobertura`](../pipelines/job_artifacts.md#artifactsreportscobertura) | The `cobertura` report collects Cobertura coverage XML files. |
| [`artifacts:reports:terraform`](../pipelines/job_artifacts.md#artifactsreportsterraform) | The `terraform` report collects Terraform `tfplan.json` files. |
-| [`artifacts:reports:codequality`](../pipelines/job_artifacts.md#artifactsreportscodequality-starter) **(STARTER)** | The `codequality` report collects CodeQuality issues. |
+| [`artifacts:reports:codequality`](../pipelines/job_artifacts.md#artifactsreportscodequality) | The `codequality` report collects CodeQuality issues. |
| [`artifacts:reports:sast`](../pipelines/job_artifacts.md#artifactsreportssast-ultimate) **(ULTIMATE)** | The `sast` report collects Static Application Security Testing vulnerabilities. |
| [`artifacts:reports:dependency_scanning`](../pipelines/job_artifacts.md#artifactsreportsdependency_scanning-ultimate) **(ULTIMATE)** | The `dependency_scanning` report collects Dependency Scanning vulnerabilities. |
| [`artifacts:reports:container_scanning`](../pipelines/job_artifacts.md#artifactsreportscontainer_scanning-ultimate) **(ULTIMATE)** | The `container_scanning` report collects Container Scanning vulnerabilities. |
| [`artifacts:reports:dast`](../pipelines/job_artifacts.md#artifactsreportsdast-ultimate) **(ULTIMATE)** | The `dast` report collects Dynamic Application Security Testing vulnerabilities. |
| [`artifacts:reports:license_management`](../pipelines/job_artifacts.md#artifactsreportslicense_management-ultimate) **(ULTIMATE)** | The `license_management` report collects Licenses (*removed from GitLab 13.0*). |
| [`artifacts:reports:license_scanning`](../pipelines/job_artifacts.md#artifactsreportslicense_scanning-ultimate) **(ULTIMATE)** | The `license_scanning` report collects Licenses. |
-| [`artifacts:reports:performance`](../pipelines/job_artifacts.md#artifactsreportsperformance-premium) **(PREMIUM)** | The `performance` report collects Performance metrics. |
+| [`artifacts:reports:performance`](../pipelines/job_artifacts.md#artifactsreportsperformance-premium) **(PREMIUM)** | The `performance` report collects Browser Performance metrics. |
+| [`artifacts:reports:load_performance`](../pipelines/job_artifacts.md#artifactsreportsload_performance-premium) **(PREMIUM)** | The `load_performance` report collects load performance metrics. |
| [`artifacts:reports:metrics`](../pipelines/job_artifacts.md#artifactsreportsmetrics-premium) **(PREMIUM)** | The `metrics` report collects Metrics. |
#### `dependencies`
@@ -3127,6 +3412,10 @@ This keyword allows the creation of two different types of downstream pipelines:
- [Multi-project pipelines](../multi_project_pipelines.md#creating-multi-project-pipelines-from-gitlab-ciyml)
- [Child pipelines](../parent_child_pipelines.md)
+[Since GitLab 13.2](https://gitlab.com/gitlab-org/gitlab/-/issues/197140/), you can
+see which job triggered a downstream pipeline by hovering your mouse cursor over
+the downstream pipeline job in the [pipeline graph](../pipelines/index.md#visualize-pipelines).
+
NOTE: **Note:**
Using a `trigger` with `when:manual` together results in the error `jobs:#{job-name}
when should be on_success, on_failure or always`, because `when:manual` prevents
@@ -3271,7 +3560,7 @@ is enabled.
When enabled, a pipeline on the same branch will be canceled when:
-- it's made redundant by a newer pipeline run.
+- It's made redundant by a newer pipeline run.
- Either all jobs are set as interruptible, or any uninterruptible jobs haven't started.
Pending jobs are always considered interruptible.
@@ -3352,6 +3641,182 @@ It can't start or end with `/`.
For more information, see [Deployments Safety](../environments/deployment_safety.md).
+### `release`
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/merge_requests/19298) in GitLab 13.2.
+
+`release` indicates that the job creates a [Release](../../user/project/releases/index.md),
+and optionally includes URLs for Release assets.
+
+These methods are supported:
+
+- [`tag_name`](#releasetag_name)
+- [`name`](#releasename) (optional)
+- [`description`](#releasedescription) (optional)
+- [`ref`](#releaseref) (optional)
+- [`milestones`](#releasemilestones) (optional)
+- [`released_at`](#releasereleased_at) (optional)
+
+The Release is created only if the job processes without error. If the Rails API
+returns an error during Release creation, the `release` job fails.
+
+#### `release-cli` Docker image
+
+The Docker image to use for the `release-cli` must be specified, using the following directive:
+
+```yaml
+image: registry.gitlab.com/gitlab-org/release-cli:latest
+```
+
+#### Script
+
+All jobs require a `script` tag at a minimum. A `:release` job can use the output of a
+`:script` tag, but if this is not necessary, a placeholder script can be used, for example:
+
+```yaml
+script:
+ - echo 'release job'
+```
+
+An [issue](https://gitlab.com/gitlab-org/gitlab/-/issues/223856) exists to remove this requirement in an upcoming version of GitLab.
+
+A pipeline can have multiple `release` jobs, for example:
+
+```yaml
+ios-release:
+ script:
+ - echo 'iOS release job'
+ release:
+ tag_name: v1.0.0-ios
+ description: 'iOS release v1.0.0'
+
+android-release:
+ script:
+ - echo 'Android release job'
+ release:
+ tag_name: v1.0.0-android
+ description: 'Android release v1.0.0'
+```
+
+#### `release:tag_name`
+
+The `tag_name` must be specified. It can refer to an existing Git tag or can be specified by the user.
+
+When the specified tag doesn't exist in the repository, a new tag is created from the associated SHA of the pipeline.
+
+For example, when creating a Release from a Git tag:
+
+```yaml
+job:
+ release:
+ tag_name: $CI_COMMIT_TAG
+ description: changelog.txt
+```
+
+It is also possible to create any unique tag, in which case `only: tags` is not mandatory.
+A semantic versioning example:
+
+```yaml
+job:
+ release:
+ tag_name: ${MAJOR}_${MINOR}_${REVISION}
+ description: changelog.txt
+```
+
+- The Release is created only if the job's main script succeeds.
+- If the Release already exists, it is not updated and the job with the `release` keyword fails.
+- The `release` section executes after the `script` tag and before the `after_script`.
+
+#### `release:name`
+
+The Release name. If omitted, it is populated with the value of `release: tag_name`.
+
+#### `release:description`
+
+Specifies the longer description of the Release.
+
+#### `release:ref`
+
+If the `release: tag_name` doesn’t exist yet, the release is created from `ref`.
+`ref` can be a commit SHA, another tag name, or a branch name.
+
+#### `release:milestones`
+
+The title of each milestone the release is associated with.
+
+#### `release:released_at`
+
+The date and time when the release is ready. Defaults to the current date and time if not
+defined. Expected in ISO 8601 format (2019-03-15T08:00:00Z).
+
+#### Complete example for `release`
+
+Combining the individual examples given above for `release` results in the following
+code snippets. There are two options, depending on how you generate the
+tags. These options cannot be used together, so choose one:
+
+- To create a release when you push a Git tag, or when you add a Git tag
+ in the UI by going to **Repository > Tags**:
+
+ ```yaml
+ release_job:
+ stage: release
+ image: registry.gitlab.com/gitlab-org/release-cli:latest
+ rules:
+ - if: $CI_COMMIT_TAG # Run this job when a tag is created manually
+ script:
+ - echo 'running release_job'
+ release:
+ name: 'Release $CI_COMMIT_TAG'
+ description: 'Created using the release-cli $EXTRA_DESCRIPTION' # $EXTRA_DESCRIPTION must be defined
+ tag_name: '$CI_COMMIT_TAG' # elsewhere in the pipeline.
+ ref: '$CI_COMMIT_TAG'
+ milestones:
+ - 'm1'
+ - 'm2'
+ - 'm3'
+ released_at: '2020-07-15T08:00:00Z' # Optional, will auto generate if not defined,
+ # or can use a variable.
+ ```
+
+- To create a release automatically when changes are pushed to the default branch,
+ using a new Git tag that is defined with variables:
+
+ ```yaml
+ release_job:
+ stage: release
+ image: registry.gitlab.com/gitlab-org/release-cli:latest
+ rules:
+ - if: $CI_COMMIT_TAG
+ when: never # Do not run this job when a tag is created manually
+ - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH # Run this job when the default branch changes
+ script:
+ - echo 'running release_job'
+ release:
+ name: 'Release $CI_COMMIT_SHA'
+ description: 'Created using the release-cli $EXTRA_DESCRIPTION' # $EXTRA_DESCRIPTION and the tag_name
+ tag_name: 'v${MAJOR}.${MINOR}.${REVISION}' # variables must be defined elsewhere
+ ref: '$CI_COMMIT_SHA' # in the pipeline.
+ milestones:
+ - 'm1'
+ - 'm2'
+ - 'm3'
+ released_at: '2020-07-15T08:00:00Z' # Optional, will auto generate if not defined,
+ # or can use a variable.
+ ```
+
+#### `releaser-cli` command line
+
+The entries under the `:release` node are transformed into a `bash` command line and sent
+to the Docker container, which contains the [release-cli](https://gitlab.com/gitlab-org/release-cli).
+You can also call the `release-cli` directly from a `script` entry.
+
+The YAML described above would be translated into a CLI command like this:
+
+```shell
+release-cli create --name "Release $CI_COMMIT_SHA" --description "Created using the release-cli $EXTRA_DESCRIPTION" --tag-name "v${MAJOR}.${MINOR}.${REVISION}" --ref "$CI_COMMIT_SHA" --released-at "2020-07-15T08:00:00Z" --milestone "m1" --milestone "m2" --milestone "m3"
+```
+
### `pages`
`pages` is a special job that is used to upload static content to GitLab that
@@ -3460,7 +3925,8 @@ variables:
GIT_STRATEGY: none
```
-NOTE: **Note:** `GIT_STRATEGY` is not supported for
+NOTE: **Note:**
+`GIT_STRATEGY` is not supported for
[Kubernetes executor](https://docs.gitlab.com/runner/executors/kubernetes.html),
but may be in the future. See the [support Git strategy with Kubernetes executor feature proposal](https://gitlab.com/gitlab-org/gitlab-runner/-/issues/3847)
for updates.
@@ -3625,7 +4091,7 @@ You can set them globally or per-job in the [`variables`](#variables) section.
> Introduced in GitLab 8.9 as an experimental feature.
-NOTE: **Note**:
+NOTE: **Note:**
As of GitLab 12.0, newly created projects will automatically have a [default `git depth` value of `50`](../pipelines/settings.md#git-shallow-clone).
You can specify the depth of fetching and cloning using `GIT_DEPTH`. This allows
@@ -3748,6 +4214,10 @@ of `.gitlab-ci.yml`.
Read more about the various [YAML features](https://learnxinyminutes.com/docs/yaml/).
+In most cases, the [`extends` keyword](#extends) is more user friendly and should
+be used over these special YAML features. YAML anchors may still
+need to be used to merge arrays.
+
### Anchors
> Introduced in GitLab 8.6 and GitLab Runner v1.1.1.
@@ -3755,7 +4225,8 @@ Read more about the various [YAML features](https://learnxinyminutes.com/docs/ya
YAML has a handy feature called 'anchors', which lets you easily duplicate
content across your document. Anchors can be used to duplicate/inherit
properties, and is a perfect example to be used with [hidden jobs](#hide-jobs)
-to provide templates for your jobs.
+to provide templates for your jobs. When there is duplicate keys, GitLab will
+perform a reverse deep merge based on the keys.
The following example uses anchors and map merging. It will create two jobs,
`test1` and `test2`, that will inherit the parameters of `.job_template`, each
@@ -3816,6 +4287,8 @@ directive defined in `.postgres_services` and `.mysql_services` respectively:
.job_template: &job_definition
script:
- test project
+ tags:
+ - dev
.postgres_services:
services: &postgres_definition
@@ -3830,6 +4303,8 @@ directive defined in `.postgres_services` and `.mysql_services` respectively:
test:postgres:
<<: *job_definition
services: *postgres_definition
+ tags:
+ - postgres
test:mysql:
<<: *job_definition
@@ -3842,6 +4317,8 @@ The expanded version looks like this:
.job_template:
script:
- test project
+ tags:
+ - dev
.postgres_services:
services:
@@ -3859,6 +4336,8 @@ test:postgres:
services:
- postgres
- ruby
+ tags:
+ - postgres
test:mysql:
script:
@@ -3866,13 +4345,19 @@ test:mysql:
services:
- mysql
- ruby
+ tags:
+ - dev
```
You can see that the hidden jobs are conveniently used as templates.
NOTE: **Note:**
+Note that `tags: [dev]` has been overwritten by `tags: [postgres]`.
+
+NOTE: **Note:**
You can't use YAML anchors across multiple files when leveraging the [`include`](#include)
-feature. Anchors are only valid within the file they were defined in.
+feature. Anchors are only valid within the file they were defined in. Instead
+of using YAML anchors, you can use the [`extends` keyword](#extends).
#### YAML anchors for `before_script` and `after_script`
@@ -3886,11 +4371,11 @@ Example:
```yaml
.something_before: &something_before
-- echo 'something before'
+ - echo 'something before'
.something_after: &something_after
-- echo 'something after'
-- echo 'another thing after'
+ - echo 'something after'
+ - echo 'another thing after'
job_name:
before_script:
@@ -3912,7 +4397,7 @@ For example:
```yaml
.something: &something
-- echo 'something'
+ - echo 'something'
job_name:
script:
diff --git a/doc/ci/yaml/includes.md b/doc/ci/yaml/includes.md
index 969b54d5be8..f7ed7248dc4 100644
--- a/doc/ci/yaml/includes.md
+++ b/doc/ci/yaml/includes.md
@@ -5,7 +5,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w
type: reference
---
-# GitLab CI/CD YAML includes
+# GitLab CI/CD include examples
In addition to the [`includes` examples](README.md#include) listed in the
[GitLab CI YAML reference](README.md), this page lists more variations of `include`
diff --git a/doc/customization/issue_and_merge_request_template.md b/doc/customization/issue_and_merge_request_template.md
index ebf711e105b..bab81629221 100644
--- a/doc/customization/issue_and_merge_request_template.md
+++ b/doc/customization/issue_and_merge_request_template.md
@@ -1,5 +1,5 @@
---
-redirect_to: '../user/project/description_templates.md#setting-a-default-template-for-merge-requests-and-issues--starter'
+redirect_to: '../user/project/description_templates.md'
---
-This document was moved to [description_templates](../user/project/description_templates.md#setting-a-default-template-for-merge-requests-and-issues--starter).
+This document was moved to [description_templates](../user/project/description_templates.md).
diff --git a/doc/development/README.md b/doc/development/README.md
index d330d6d466e..ab86c252948 100644
--- a/doc/development/README.md
+++ b/doc/development/README.md
@@ -99,6 +99,9 @@ Complementary reads:
- [Code comments](code_comments.md)
- [Renaming features](renaming_features.md)
- [Windows Development on GCP](windows.md)
+- [Code Intelligence](code_intelligence/index.md)
+- [Approval Rules](approval_rules.md)
+- [Feature categorization](feature_categorization/index.md)
## Performance guides
diff --git a/doc/development/api_graphql_styleguide.md b/doc/development/api_graphql_styleguide.md
index 43e96340d10..92e6add9f17 100644
--- a/doc/development/api_graphql_styleguide.md
+++ b/doc/development/api_graphql_styleguide.md
@@ -571,7 +571,8 @@ module Types
end
```
-NOTE: **Note:** If the field's type already [has a particular
+NOTE: **Note:**
+If the field's type already [has a particular
authorization](#type-authorization) then there is no need to add that
same authorization to the field.
@@ -717,11 +718,48 @@ will be returned as the result of the mutation.
### Naming conventions
-Always provide a consistent GraphQL-name to the mutation, this name is
-used to generate the input types and the field the mutation is mounted
-on. The name should look like `<Resource being modified><Mutation
-class name>`, for example the `Mutations::MergeRequests::SetWip`
-mutation has GraphQL name `MergeRequestSetWip`.
+Each mutation must define a `graphql_name`, which is the name of the mutation in the GraphQL schema.
+
+Example:
+
+```ruby
+class UserUpdateMutation < BaseMutation
+ graphql_name 'UserUpdate'
+end
+```
+
+Our GraphQL mutation names are historically inconsistent, but new mutation names should follow the
+convention `'{Resource}{Action}'` or `'{Resource}{Action}{Attribute}'`.
+
+Mutations that **create** new resources should use the verb `Create`.
+
+Example:
+
+- `CommitCreate`
+
+Mutations that **update** data should use:
+
+- The verb `Update`.
+- A domain-specific verb like `Set`, `Add`, or `Toggle` if more appropriate.
+
+Examples:
+
+- `EpicTreeReorder`
+- `IssueSetWeight`
+- `IssueUpdate`
+- `TodoMarkDone`
+
+Mutations that **remove** data should use:
+
+- The verb `Delete` rather than `Destroy`.
+- A domain-specific verb like `Remove` if more appropriate.
+
+Examples:
+
+- `AwardEmojiRemove`
+- `NoteDelete`
+
+If you need advice for mutation naming, canvass the Slack `#graphql` channel for feedback.
### Arguments
@@ -975,6 +1013,34 @@ to make sure the error information we are passing back is useful.
See also the [frontend GraphQL guide](../development/fe_guide/graphql.md#handling-errors).
+### Aliasing and deprecating mutations
+
+The `#mount_aliased_mutation` helper allows us to alias a mutation as
+another name within `MutationType`.
+
+For example, to alias a mutation called `FooMutation` as `BarMutation`:
+
+```ruby
+mount_aliased_mutation 'BarMutation', Mutations::FooMutation
+```
+
+This allows us to rename a mutation and continue to support the old name,
+when coupled with the [`deprecated`](#deprecating-fields) argument.
+
+Example:
+
+```ruby
+mount_aliased_mutation 'UpdateFoo',
+ Mutations::Foo::Update,
+ deprecated: { reason: 'Use fooUpdate', milestone: '13.2' }
+```
+
+Deprecated mutations should be added to `Types::DeprecatedMutations` and
+tested for within the unit test of `Types::MutationType`. The merge request
+[!34798](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/34798)
+can be referred to as an example of this, including the method of testing
+deprecated aliased mutations.
+
## Validating arguments
For validations of single arguments, use the
diff --git a/doc/development/api_styleguide.md b/doc/development/api_styleguide.md
index 6a044004926..327f919d7f4 100644
--- a/doc/development/api_styleguide.md
+++ b/doc/development/api_styleguide.md
@@ -98,6 +98,46 @@ For instance:
Model.create(foo: params[:foo])
```
+## Array types
+
+With Grape v1.3+, Array types must be defined with a `coerce_with`
+block, or parameters will fail to validate when passed a string from an
+API request. See the [Grape upgrading
+documentation](https://github.com/ruby-grape/grape/blob/master/UPGRADING.md#ensure-that-array-types-have-explicit-coercions)
+for more details.
+
+### Automatic coercion of nil inputs
+
+Prior to Grape v1.3.3, Array parameters with `nil` values would
+automatically be coerced to an empty Array. However, due to [this pull
+request in v1.3.3](https://github.com/ruby-grape/grape/pull/2040), this
+is no longer the case. For example, suppose you define a PUT `/test`
+request that has an optional parameter:
+
+```ruby
+optional :user_ids, type: Array[Integer], coerce_with: ::API::Validations::Types::CommaSeparatedToIntegerArray.coerce, desc: 'The user ids for this rule'
+```
+
+Normally, a request to PUT `/test?user_ids` would cause Grape to pass
+`params` of `{ user_ids: nil }`.
+
+This may introduce errors with endpoints that expect a blank array and
+do not handle `nil` inputs properly. To preserve the previous behavior,
+there is a helper method `coerce_nil_params_to_array!` that is used
+in the `before` block of all API calls:
+
+```ruby
+before do
+ coerce_nil_params_to_array!
+end
+```
+
+With this change, a request to PUT `/test?user_ids` will cause Grape to
+pass `params` to be `{ user_ids: [] }`.
+
+There is [an open issue in the Grape tracker](https://github.com/ruby-grape/grape/issues/2068)
+to make this easier.
+
## Using HTTP status helpers
For non-200 HTTP responses, use the provided helpers in `lib/api/helpers.rb` to ensure correct behavior (`not_found!`, `no_content!` etc.). These will `throw` inside Grape and abort the execution of your endpoint.
diff --git a/doc/development/application_limits.md b/doc/development/application_limits.md
index 1f7a9ff09b9..4d296451add 100644
--- a/doc/development/application_limits.md
+++ b/doc/development/application_limits.md
@@ -11,7 +11,7 @@ coordinate with others to [document](../administration/instance_limits.md)
and communicate those limits.
There is a guide about [introducing application
-limits](https://about.gitlab.com/handbook/product/product-management/process/#introducing-application-limits).
+limits](https://about.gitlab.com/handbook/product/product-processes/#introducing-application-limits).
## Development
@@ -27,7 +27,8 @@ limit values. It's recommended to create separate migration script files.
add_column(:plan_limits, :project_hooks, :integer, default: 100, null: false)
```
- NOTE: **Note:** Plan limits entries set to `0` mean that limits are not
+ NOTE: **Note:**
+ Plan limits entries set to `0` mean that limits are not
enabled. You should use this setting only in special and documented circumstances.
1. (Optionally) Create the database migration that fine-tunes each level with
@@ -57,7 +58,8 @@ limit values. It's recommended to create separate migration script files.
end
```
-NOTE: **Note:** Some plans exist only on GitLab.com. This will be no-op
+NOTE: **Note:**
+Some plans exist only on GitLab.com. This will be no-op
for plans that do not exist.
### Plan limits validation
@@ -95,7 +97,8 @@ can be used to validate that a model does not exceed the limits. It ensures
that the count of the records for the current model does not exceed the defined
limit.
-NOTE: **Note:** You must specify the limit scope of the object being validated
+NOTE: **Note:**
+You must specify the limit scope of the object being validated
and the limit name if it's different from the pluralized model name.
```ruby
@@ -143,4 +146,5 @@ GitLab.com:
- `silver` - Namespaces and projects with a Silver subscription
- `gold` - Namespaces and projects with a Gold subscription
-NOTE: **Note:** The test environment doesn't have any plans.
+NOTE: **Note:**
+The test environment doesn't have any plans.
diff --git a/doc/development/application_secrets.md b/doc/development/application_secrets.md
new file mode 100644
index 00000000000..24755586cf8
--- /dev/null
+++ b/doc/development/application_secrets.md
@@ -0,0 +1,41 @@
+# Application secrets
+
+This page is a development guide for application secrets.
+
+## Secret entries
+
+|Entry |Description |
+|--- |--- |
+|`secret_key_base` | The base key to be used for generating a various secrets |
+| `otp_key_base` | The base key for One Time Passwords, described in [User management](../raketasks/user_management.md#rotate-two-factor-authentication-encryption-key) |
+|`db_key_base` | The base key to encrypt the data for `attr_encrypted` columns |
+|`openid_connect_signing_key` | The singing key for OpenID Connect |
+
+## Where the secrets are stored
+
+|Installation type |Location |
+|--- |--- |
+|Omnibus |[`/etc/gitlab/gitlab-secrets.json`](https://docs.gitlab.com/omnibus/settings/backups.html#backup-and-restore-omnibus-gitlab-configuration) |
+|Cloud Native GitLab Charts |[Kubernets Secrets](https://gitlab.com/gitlab-org/charts/gitlab/-/blob/f65c3d37fc8cf09a7987544680413552fb666aac/doc/installation/secrets.md#gitlab-rails-secret)|
+|Source |`<path-to-gitlab-rails>/config/secrets.yml` (Automatically generated by [01_secret_token.rb](https://gitlab.com/gitlab-org/gitlab/-/blob/master/config/initializers/01_secret_token.rb)) |
+
+## Warning: Before you add a new secret to application secrets
+
+Before you add a new secret to [`config/initializers/01_secret_token.rb`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/config/initializers/01_secret_token.rb),
+make sure you also update Omnibus GitLab or updates will fail. Omnibus is responsible for writing the `secrets.yml` file.
+If Omnibus doesn't know about a secret, Rails will attempt to write to the file, but this will fail because Rails doesn't have write access.
+The same rules apply to Cloud Native GitLab charts, you must update the charts at first.
+In case you need the secret to have same value on each node (which is usually the case) you need to make sure it's configured for all
+GitLab.com environments prior to changing this file.
+
+**Examples**
+
+- [Change for source installation](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/27581)
+- [Change for omnibus installation](https://gitlab.com/gitlab-org/omnibus-gitlab/-/merge_requests/3267)
+- [Change for omnibus installation](https://gitlab.com/gitlab-org/omnibus-gitlab/-/merge_requests/4158)
+- [Change for Cloud Native installation](https://gitlab.com/gitlab-org/charts/gitlab/-/merge_requests/1318)
+
+## Further iteration
+
+We might deprecate/remove this automatic secret generation '01_secret_token.rb' in the future.
+Please see [this issue](https://gitlab.com/gitlab-org/gitlab/-/issues/222690) for more information.
diff --git a/doc/development/approval_rules.md b/doc/development/approval_rules.md
new file mode 100644
index 00000000000..65df82721de
--- /dev/null
+++ b/doc/development/approval_rules.md
@@ -0,0 +1,280 @@
+# Approval Rules **(STARTER)**
+
+This document explains the backend design and flow of all related functionality
+about [merge request approval rules](../user/project/merge_requests/merge_request_approvals.md).
+
+This should help contributors to understand the code design easier and to also
+help see if there are parts to improve as the feature and its implementation
+evolves.
+
+It's intentional that it doesn't contain too much implementation detail as they
+can change often. The code should explain those things better. The components
+mentioned here are the major parts of the application for the approval rules
+feature to work.
+
+NOTE: **Note:**
+This is a living document and should be updated accordingly when parts
+of the codebase touched in this document changed/removed or when new components
+are added.
+
+## Data Model
+
+```mermaid
+erDiagram
+ Project ||--o{ MergeRequest: " "
+ Project ||--o{ ApprovalProjectRule: " "
+ ApprovalProjectRule }o--o{ User: " "
+ ApprovalProjectRule }o--o{ Group: " "
+ ApprovalProjectRule }o--o{ ProtectedBranch: " "
+ MergeRequest ||--|| ApprovalState: " "
+ ApprovalState ||--o{ ApprovalWrappedRule: " "
+ MergeRequest ||--o{ Approval: " "
+ MergeRequest ||--o{ ApprovalMergeRequestRule: " "
+ ApprovalMergeRequestRule }o--o{ User: " "
+ ApprovalMergeRequestRule }o--o{ Group: " "
+ ApprovalMergeRequestRule ||--o| ApprovalProjectRule: " "
+```
+
+### `Project` and `MergeRequest`
+
+`Project` and `MergeRequest` models are defined in `ee/app/models/ee/project.rb`
+and `ee/app/models/ee/merge_request.rb`. They extend the non-EE versions since
+approval rules is an EE only feature. Associations and other related stuff to
+merge request approvals are defined here.
+
+### `ApprovalState`
+
+```mermaid
+erDiagram
+ MergeRequest ||--|| ApprovalState: " "
+```
+
+`ApprovalState` class is defined in `ee/app/models/approval_state.rb`. It's not
+an actual `ActiveRecord` model. This class encapsulates all logic related to the
+state of the approvals for a certain merge request like:
+
+- Knowing the approval rules that are applicable to the merge request based on
+ its target branch.
+- Knowing the approval rules that are applicable to a certain target branch.
+- Checking if all rules were approved.
+- Checking if approval is required.
+- Knowing how many approvals were given or still required.
+
+It gets the approval rules data from the project (`ApprovalProjectRule`) or the
+merge request (`ApprovalMergeRequestRule`) and wrap it as `ApprovalWrappedRule`.
+
+### `ApprovalProjectRule`
+
+```mermaid
+erDiagram
+ Project ||--o{ ApprovalProjectRule: " "
+ ApprovalProjectRule }o--o{ User: " "
+ ApprovalProjectRule }o--o{ Group: " "
+ ApprovalProjectRule }o--o{ ProtectedBranch: " "
+```
+
+`ApprovalProjectRule` model is defined in `ee/app/models/approval_project_rule.rb`.
+
+A record is created/updated/deleted when an approval rule is added/edited/removed
+via project settings or the [project level approvals API](../api/merge_request_approvals.md#project-level-mr-approvals).
+The `ApprovalState` model get these records when approval rules are not
+overwritten.
+
+The `protected_branches` attribute is set and used when a rule is scoped to
+protected branches. See [Scoped to Protected Branch doc](../user/project/merge_requests/merge_request_approvals.md#scoped-to-protected-branch-premium)
+for more information about the feature.
+
+### `ApprovalMergeRequestRule`
+
+```mermaid
+erDiagram
+ MergeRequest ||--o{ ApprovalMergeRequestRule: " "
+ ApprovalMergeRequestRule }o--o{ User: " "
+ ApprovalMergeRequestRule }o--o{ Group: " "
+ ApprovalMergeRequestRule ||--o| ApprovalProjectRule: " "
+```
+
+`ApprovalMergeRequestRule` model is defined in `ee/app/models/approval_merge_request_rule.rb`.
+
+A record is created/updated/deleted when a rule is added/edited/removed via merge
+request create/edit form or the [merge request level approvals API](../api/merge_request_approvals.md#merge-request-level-mr-approvals).
+
+The `approval_project_rule` is set when it is based from an existing `ApprovalProjectRule`.
+
+An `ApprovalMergeRequestRule` doesn't have `protected_branches` as it inherits
+them from the `approval_project_rule` if not overridden.
+
+### `ApprovalWrappedRule`
+
+```mermaid
+erDiagram
+ ApprovalState ||--o{ ApprovalWrappedRule: " "
+```
+
+`ApprovalWrappedRule` is defined in `ee/app/modes/approval_wrapped_rule.rb` and
+is not an `ActiveRecord` model. It's used to wrap an `ApprovalProjectRule` or
+`ApprovalMergeRequestRule` for common interface. It also has the following sub
+types:
+
+- `ApprovalWrappedAnyApprovalRule` - for wrapping an `any_approver` rule.
+- `ApprovalWrappedCodeOwnerRule` - for wrapping a `code_owner` rule.
+
+This class delegates most of the responsibilities to the approval rule it wraps
+but it's also responsible for:
+
+- Checking if the approval rule is approved.
+- Knowing how many approvals were given or still required for the approval rule.
+
+It gets this information from the approval rule and the `Approval` records from
+the merge request.
+
+### `Approval`
+
+```mermaid
+erDiagram
+ MergeRequest ||--o{ Approval: " "
+```
+
+`Approval` model is defined in `ee/app/models/approval.rb`. This model is
+responsible for storing information about an approval made on a merge request.
+Whenever an approval is given/revoked, a record is created/deleted.
+
+## Controllers and Services
+
+The following controllers and services below are being utilized for the approval
+rules feature to work.
+
+### `API::ProjectApprovalSettings`
+
+This private API is defined in `ee/lib/api/project_approval_settings.rb`.
+
+This is used for the following:
+
+- Listing the approval rules in project settings.
+- Creating/updating/deleting rules in project settings.
+- Listing the approval rules on create merge request form.
+
+### `Projects::MergeRequests::CreationsController`
+
+This controller is defined in `app/controllers/projects/merge_requests/creations_controller.rb`.
+
+The `create` action of this controller is used when create merge request form is
+submitted. It accepts the `approval_rules_attributes` parameter for creating/updating/deleting
+`ApprovalMergeRequestRule` records. It passes the parameter along when it executes
+`MergeRequests::CreateService`.
+
+### `Projects::MergeRequestsController`
+
+This controller is defined in `app/controllers/projects/merge_requests_controller.rb`.
+
+The `update` action of this controller is used when edit merge request form is
+submitted. It's like `Projects::MergeRequests::CreationsController` but it executes
+`MergeRequests::UpdateService` instead.
+
+### `API::MergeRequestApprovals`
+
+This API is defined in `ee/lib/api/merge_request_approvals.rb`.
+
+The [Approvals API endpoint](../api/merge_request_approvals.md#get-configuration-1)
+is requested when merge request page loads.
+
+The `/projects/:id/merge_requests/:merge_request_iid/approval_settings` is a
+private API endpoint used for the following:
+
+- Listing the approval rules on edit merge request form.
+- Listing the approval rules on the merge request page.
+
+When approving/unapproving MR via UI and API, the [Approve Merge Request](../api/merge_request_approvals.md#approve-merge-request)
+API endpoint and the [Unapprove Merge Request](../api/merge_request_approvals.md#unapprove-merge-request)
+API endpoint are requested. They execute `MergeRequests::ApprovalService` and
+`MergeRequests::RemoveApprovalService` accordingly.
+
+### `API::ProjectApprovalRules` and `API::MergeRequestApprovalRules`
+
+These APIs are defined in `ee/lib/api/project_approval_rules.rb` and
+`ee/lib/api/merge_request_approval_rules.rb`.
+
+Used to list/create/update/delete project and merge request level rules via
+[Merge request approvals API](../api/merge_request_approvals.md).
+
+Executes `ApprovalRules::CreateService`, `ApprovalRules::UpdateService`,
+`ApprovalRules::ProjectRuleDestroyService`, and `ApprovalRules::MergeRequestRuleDestroyService`
+accordingly.
+
+### `ApprovalRules::ParamsFilteringService`
+
+This service is defined in `ee/app/services/approval_rules/params_filtering_service.rb`.
+
+It is called only when `MergeRequests::CreateService` and
+`MergeRequests::UpdateService` are executed.
+
+It is responsible for parsing `approval_rules_attributes` parameter to:
+
+- Remove it when user can't update approval rules.
+- Filter the user IDs whether they are members of the project or not.
+- Filter the group IDs whether they are visible to user.
+- Identify the `any_approver` rule.
+- Append hidden groups to it when specified.
+- Append user defined inapplicable (rules that does not apply to MR's target
+ branch) approval rules.
+
+## Flow
+
+These flowcharts should help explain the flow from the controllers down to the
+models for different functionalities.
+
+Some CRUD API endpoints are intentionally skipped because they are pretty
+straightforward.
+
+### Creating a merge request with approval rules via web UI
+
+```mermaid
+graph LR
+ Projects::MergeRequests::CreationsController --> MergeRequests::CreateService
+ MergeRequests::CreateService --> ApprovalRules::ParamsFilteringService
+ ApprovalRules::ParamsFilteringService --> MergeRequests::CreateService
+ MergeRequests::CreateService --> MergeRequest
+ MergeRequest --> db[(Database)]
+ MergeRequest --> User
+ MergeRequest --> Group
+ MergeRequest --> ApprovalProjectRule
+ User --> db[(Database)]
+ Group --> db[(Database)]
+ ApprovalProjectRule --> db[(Database)]
+```
+
+When updating, same flow is followed but it starts at `Projects::MergeRequestsController`
+and executes `MergeRequests::UpdateService` instead.
+
+### Viewing the merge request approval rules on an MR page
+
+```mermaid
+graph LR
+ API::MergeRequestApprovals --> MergeRequest
+ MergeRequest --> ApprovalState
+ ApprovalState --> id1{approval rules are overridden}
+ id1{approval rules are overridden} --> |No| ApprovalProjectRule & ApprovalMergeRequestRule
+ id1{approval rules are overridden} --> |Yes| ApprovalMergeRequestRule
+ ApprovalState --> ApprovalWrappedRule
+ ApprovalWrappedRule --> Approval
+```
+
+This flow gets initiated by the frontend component. The data returned will
+then be used to display information on the MR widget.
+
+### Approving a merge request
+
+```mermaid
+graph LR
+ API::MergeRequestApprovals --> MergeRequests::ApprovalService
+ MergeRequests::ApprovalService --> Approval
+ Approval --> db[(Database)]
+```
+
+When unapproving, same flow is followed but the `MergeRequests::RemoveApprovalService`
+is executed instead.
+
+## TODO
+
+1. Add information related to other rule types (e.g. `code_owner` and `report_approver`).
+1. Add information about side effects of approving/unapproving merge request.
diff --git a/doc/development/architecture.md b/doc/development/architecture.md
index f0ce033587d..8b28dd03017 100644
--- a/doc/development/architecture.md
+++ b/doc/development/architecture.md
@@ -140,29 +140,29 @@ Table description links:
| [Elasticsearch](#elasticsearch) | Improved search within GitLab | ⤓ | ⤓ | ⤓ | ⌠| ⤓ | ⤓ | EE Only |
| [Gitaly](#gitaly) | Git RPC service for handling all Git calls made by GitLab | ✅ | ✅ | ✅ | ✅ | ⚙ | ✅ | CE & EE |
| [GitLab Exporter](#gitlab-exporter) | Generates a variety of GitLab metrics | ✅ | ✅ | ✅ | ✅ | ⌠| ⌠| CE & EE |
-| [GitLab Geo Node](#gitlab-geo) | Geographically distributed GitLab nodes | ⚙ | ⌠| ⌠| ✅ | ⌠| ⚙ | EE Only |
+| [GitLab Geo Node](#gitlab-geo) | Geographically distributed GitLab nodes | ⚙ | ⚙ | ⌠| ✅ | ⌠| ⚙ | EE Only |
| [GitLab Managed Apps](#gitlab-managed-apps) | Deploy Helm, Ingress, Cert-Manager, Prometheus, a Runner, JupyterHub, or Knative to a cluster | ⤓ | ⤓ | ⤓ | ⤓ | ⤓ | ⤓ | CE & EE |
| [GitLab Pages](#gitlab-pages) | Hosts static websites | ⚙ | ⌠| ⌠| ✅ | ⚙ | ⚙ | CE & EE |
| [GitLab self-monitoring: Alertmanager](#alertmanager) | Deduplicates, groups, and routes alerts from Prometheus | ⚙ | ✅ | ⚙ | ✅ | ⌠| ⌠| CE & EE |
-| [GitLab self-monitoring: Grafana](#grafana) | Metrics dashboard | ✅ | ⤓ | ⤓ | ✅ | ⌠| ⌠| CE & EE |
-| [GitLab self-monitoring: Jaeger](#jaeger) | View traces generated by the GitLab instance | ⌠| ⌠| ⌠| ⌠| ⤓ | ⚙ | CE & EE |
+| [GitLab self-monitoring: Grafana](#grafana) | Metrics dashboard | ✅ | ⚙ | ⤓ | ✅ | ⌠| ⌠| CE & EE |
+| [GitLab self-monitoring: Jaeger](#jaeger) | View traces generated by the GitLab instance | ⌠| ⚙ | ⌠| ⌠| ⤓ | ⚙ | CE & EE |
| [GitLab self-monitoring: Prometheus](#prometheus) | Time-series database, metrics collection, and query service | ✅ | ✅ | ⚙ | ✅ | ⌠| ⌠| CE & EE |
-| [GitLab self-monitoring: Sentry](#sentry) | Track errors generated by the GitLab instance | ⤓ | ⌠| ⌠| ✅ | ⤓ | ⤓ | CE & EE |
+| [GitLab self-monitoring: Sentry](#sentry) | Track errors generated by the GitLab instance | ⤓ | ⤓ | ⌠| ✅ | ⤓ | ⤓ | CE & EE |
| [GitLab Shell](#gitlab-shell) | Handles `git` over SSH sessions | ✅ | ✅ | ✅ | ✅ | ⚙ | ✅ | CE & EE |
| [GitLab Workhorse](#gitlab-workhorse) | Smart reverse proxy, handles large HTTP requests | ✅ | ✅ | ✅ | ✅ | ⚙ | ✅ | CE & EE |
-| [Inbound email (SMTP)](#inbound-email) | Receive messages to update issues | ⤓ | ⤓ | ⤓ | ✅ | ⤓ | ⤓ | CE & EE |
+| [Inbound email (SMTP)](#inbound-email) | Receive messages to update issues | ⤓ | ⚙ | ⤓ | ✅ | ⤓ | ⤓ | CE & EE |
| [Jaeger integration](#jaeger) | Distributed tracing for deployed apps | ⤓ | ⤓ | ⤓ | ⤓ | ⤓ | ⤓ | EE Only |
| [LDAP Authentication](#ldap-authentication) | Authenticate users against centralized LDAP directory | ⤓ | ⤓ | ⤓ | ⌠| ⤓ | ⤓ | CE & EE |
| [Mattermost](#mattermost) | Open-source Slack alternative | ⚙ | ⤓ | ⤓ | ⤓ | ⌠| ⌠| CE & EE |
| [MinIO](#minio) | Object storage service | ⤓ | ✅ | ✅ | ✅ | ⌠| ⚙ | CE & EE |
| [NGINX](#nginx) | Routes requests to appropriate components, terminates SSL | ✅ | ✅ | ⚙ | ✅ | ⤓ | ⌠| CE & EE |
| [Node Exporter](#node-exporter) | Prometheus endpoint with system metrics | ✅ | N/A | N/A | ✅ | ⌠| ⌠| CE & EE |
-| [Outbound email (SMTP)](#outbound-email) | Send email messages to users | ⤓ | ⤓ | ⤓ | ✅ | ⤓ | ⤓ | CE & EE |
+| [Outbound email (SMTP)](#outbound-email) | Send email messages to users | ⤓ | ⚙ | ⤓ | ✅ | ⤓ | ⤓ | CE & EE |
| [PgBouncer Exporter](#pgbouncer-exporter) | Prometheus endpoint with PgBouncer metrics | ⚙ | ⌠| ⌠| ✅ | ⌠| ⌠| CE & EE |
| [PgBouncer](#pgbouncer) | Database connection pooling, failover | ⚙ | ⌠| ⌠| ✅ | ⌠| ⌠| EE Only |
| [PostgreSQL Exporter](#postgresql-exporter) | Prometheus endpoint with PostgreSQL metrics | ✅ | ✅ | ✅ | ✅ | ⌠| ⌠| CE & EE |
| [PostgreSQL](#postgresql) | Database | ✅ | ✅ | ✅ | ✅ | ⤓ | ✅ | CE & EE |
-| [Praefect](#praefect) | A transparent proxy between any Git client and Gitaly storage nodes. | ✅ | ⌠| ⌠| ✅ | ⚙ | ✅ | CE & EE |
+| [Praefect](#praefect) | A transparent proxy between any Git client and Gitaly storage nodes. | ✅ | ⚙ | ⌠| ✅ | ⚙ | ✅ | CE & EE |
| [Redis Exporter](#redis-exporter) | Prometheus endpoint with Redis metrics | ✅ | ✅ | ✅ | ✅ | ⌠| ⌠| CE & EE |
| [Redis](#redis) | Caching service | ✅ | ✅ | ✅ | ✅ | ⤓ | ✅ | CE & EE |
| [Registry](#registry) | Container registry, allows pushing and pulling of images | ⚙ | ✅ | ✅ | ✅ | ⤓ | ⚙ | CE & EE |
@@ -196,7 +196,7 @@ GitLab can be considered to have two layers from a process perspective:
- Process: `alertmanager`
- GitLab.com: [Monitoring of GitLab.com](https://about.gitlab.com/handbook/engineering/monitoring/)
-[Alert manager](https://prometheus.io/docs/alerting/alertmanager/) is a tool provided by Prometheus that _"handles alerts sent by client applications such as the Prometheus server. It takes care of deduplicating, grouping, and routing them to the correct receiver integration such as email, PagerDuty, or OpsGenie. It also takes care of silencing and inhibition of alerts."_ You can read more in [issue #45740](https://gitlab.com/gitlab-org/gitlab-foss/-/issues/45740) about what we will be alerting on.
+[Alert manager](https://prometheus.io/docs/alerting/latest/alertmanager/) is a tool provided by Prometheus that _"handles alerts sent by client applications such as the Prometheus server. It takes care of deduplicating, grouping, and routing them to the correct receiver integration such as email, PagerDuty, or Opsgenie. It also takes care of silencing and inhibition of alerts."_ You can read more in [issue #45740](https://gitlab.com/gitlab-org/gitlab-foss/-/issues/45740) about what we will be alerting on.
#### Certificate management
@@ -273,7 +273,7 @@ repository updates to secondary nodes.
- Configuration:
- [Omnibus](../administration/geo/replication/index.md#setup-instructions)
- - [Charts](https://gitlab.com/gitlab-org/charts/gitlab/-/issues/8)
+ - [Charts](https://docs.gitlab.com/charts/advanced/geo/)
- [GDK](https://gitlab.com/gitlab-org/gitlab-development-kit/blob/master/doc/howto/geo.md)
- Layer: Core Service (Processor)
@@ -349,7 +349,7 @@ GitLab CI/CD is the open-source continuous integration service included with Git
- [Project page](https://github.com/grafana/grafana/blob/master/README.md)
- Configuration:
- [Omnibus](../administration/monitoring/performance/grafana_configuration.md)
- - [Charts](https://github.com/helm/charts/tree/master/stable/grafana)
+ - [Charts](https://docs.gitlab.com/charts/charts/globals#configure-grafana-integration)
- Layer: Monitoring
- GitLab.com: [GitLab triage Grafana dashboard](https://dashboards.gitlab.com/d/RZmbBr7mk/gitlab-triage?refresh=30s)
@@ -360,7 +360,7 @@ Grafana is an open source, feature rich metrics dashboard and graph editor for G
- [Project page](https://github.com/jaegertracing/jaeger/blob/master/README.md)
- Configuration:
- [Omnibus](https://gitlab.com/gitlab-org/omnibus-gitlab/-/issues/4104)
- - [Charts](https://gitlab.com/gitlab-org/charts/gitlab/-/issues/1320)
+ - [Charts](https://docs.gitlab.com/charts/charts/globals#tracing)
- [Source](../development/distributed_tracing.md#enabling-distributed-tracing)
- [GDK](../development/distributed_tracing.md#using-jaeger-in-the-gitlab-development-kit)
- Layer: Monitoring
@@ -369,7 +369,7 @@ Grafana is an open source, feature rich metrics dashboard and graph editor for G
Jaeger, inspired by Dapper and OpenZipkin, is a distributed tracing system.
It can be used for monitoring microservices-based distributed systems.
-For monitoring deployed apps, see [Jaeger tracing documentation](../user/project/operations/tracing.md)
+For monitoring deployed apps, see [Jaeger tracing documentation](../operations/tracing.md)
#### Logrotate
@@ -458,7 +458,7 @@ Prometheus exporter for PgBouncer. Exports metrics at 9127/metrics.
- [Project page](https://github.com/postgres/postgres/blob/master/README)
- Configuration:
- [Omnibus](https://docs.gitlab.com/omnibus/settings/database.html)
- - [Charts](https://github.com/helm/charts/tree/master/stable/postgresql)
+ - [Charts](https://docs.gitlab.com/charts/installation/deployment.html#postgresql)
- [Source](../install/installation.md#6-database)
- Layer: Core Service (Data)
- Process: `postgresql`
@@ -471,7 +471,7 @@ GitLab packages the popular Database to provide storage for Application meta dat
- [Project page](https://github.com/wrouesnel/postgres_exporter/blob/master/README.md)
- Configuration:
- [Omnibus](../administration/monitoring/prometheus/postgres_exporter.md)
- - [Charts](https://github.com/helm/charts/tree/master/stable/postgresql)
+ - [Charts](https://docs.gitlab.com/charts/installation/deployment.html#postgresql)
- Layer: Monitoring
- Process: `postgres-exporter`
- GitLab.com: [Monitoring of GitLab.com](https://about.gitlab.com/handbook/engineering/monitoring/)
@@ -483,7 +483,7 @@ GitLab packages the popular Database to provide storage for Application meta dat
- [Project page](https://github.com/prometheus/prometheus/blob/master/README.md)
- Configuration:
- [Omnibus](../administration/monitoring/prometheus/index.md)
- - [Charts](https://github.com/helm/charts/tree/master/stable/prometheus)
+ - [Charts](https://docs.gitlab.com/charts/installation/deployment.html#prometheus)
- Layer: Monitoring
- Process: `prometheus`
- GitLab.com: [Prometheus](../user/gitlab_com/index.md#prometheus)
@@ -495,7 +495,7 @@ Prometheus is a time-series tool that helps GitLab administrators expose metrics
- [Project page](https://github.com/antirez/redis/blob/unstable/README.md)
- Configuration:
- [Omnibus](https://docs.gitlab.com/omnibus/settings/redis.html)
- - [Charts](https://docs.gitlab.com/charts/charts/redis/)
+ - [Charts](https://docs.gitlab.com/charts/installation/deployment.html#redis)
- [Source](../install/installation.md#7-redis)
- Layer: Core Service (Data)
- Process: `redis`
@@ -512,7 +512,7 @@ Redis is packaged to provide a place to store:
- [Project page](https://github.com/oliver006/redis_exporter/blob/master/README.md)
- Configuration:
- [Omnibus](../administration/monitoring/prometheus/redis_exporter.md)
- - [Charts](https://docs.gitlab.com/charts/charts/redis/)
+ - [Charts](https://docs.gitlab.com/charts/installation/deployment.html#redis)
- Layer: Monitoring
- Process: `redis-exporter`
- GitLab.com: [Monitoring of GitLab.com](https://about.gitlab.com/handbook/engineering/monitoring/)
@@ -545,7 +545,7 @@ An external registry can also be configured to use GitLab as an auth endpoint.
- [Project page](https://github.com/getsentry/sentry/)
- Configuration:
- [Omnibus](https://docs.gitlab.com/omnibus/settings/configuration.html#error-reporting-and-logging-with-sentry)
- - [Charts](https://gitlab.com/gitlab-org/charts/gitlab/-/issues/1319)
+ - [Charts](https://docs.gitlab.com/charts/charts/globals#sentry-settings)
- [Source](https://gitlab.com/gitlab-org/gitlab/blob/master/config/gitlab.yml.example)
- [GDK](https://gitlab.com/gitlab-org/gitlab/blob/master/config/gitlab.yml.example)
- Layer: Monitoring
@@ -567,7 +567,7 @@ For monitoring deployed apps, see the [Sentry integration docs](../user/project/
- [GDK](https://gitlab.com/gitlab-org/gitlab/blob/master/config/gitlab.yml.example)
- Layer: Core Service (Processor)
- Process: `sidekiq`
-- GitLab.com: [Sidekiq](../user/gitlab_com/index.md#Sidekiq)
+- GitLab.com: [Sidekiq](../user/gitlab_com/index.md#sidekiq)
Sidekiq is a Ruby background job processor that pulls jobs from the Redis queue and processes them. Background jobs allow GitLab to provide a faster request/response cycle by moving work into the background.
@@ -576,7 +576,7 @@ Sidekiq is a Ruby background job processor that pulls jobs from the Redis queue
- [Project page](https://gitlab.com/gitlab-org/gitlab/blob/master/README.md)
- Configuration:
- [Omnibus](https://docs.gitlab.com/omnibus/settings/unicorn.html)
- - [Charts](https://docs.gitlab.com/charts/charts/gitlab/unicorn/)
+ - [Charts](https://docs.gitlab.com/charts/charts/gitlab/webservice/)
- [Source](../install/installation.md#configure-it)
- [GDK](https://gitlab.com/gitlab-org/gitlab/blob/master/config/gitlab.yml.example)
- Layer: Core Service (Processor)
@@ -773,7 +773,7 @@ response back to the user directly.
When referring to `~git` in the pictures it means the home directory of the Git user which is typically `/home/git`.
-GitLab is primarily installed within the `/home/git` user home directory as `git` user. Within the home directory is where the gitlabhq server software resides as well as the repositories (though the repository location is configurable).
+GitLab is primarily installed within the `/home/git` user home directory as `git` user. Within the home directory is where the GitLab server software resides as well as the repositories (though the repository location is configurable).
The bare repositories are located in `/home/git/repositories`. GitLab is a Ruby on rails application so the particulars of the inner workings can be learned by studying how a Ruby on rails application works.
@@ -849,7 +849,7 @@ Usage: /etc/init.d/postgresql {start|stop|restart|reload|force-reload|status} [v
### Log locations of the services
-gitlabhq (includes Unicorn and Sidekiq logs):
+GitLab (includes Unicorn and Sidekiq logs):
- `/home/git/gitlab/log/` contains `application.log`, `production.log`, `sidekiq.log`, `unicorn.stdout.log`, `git_json.log` and `unicorn.stderr.log` normally.
diff --git a/doc/development/changelog.md b/doc/development/changelog.md
index 5a8e05f888c..00a0573a8ba 100644
--- a/doc/development/changelog.md
+++ b/doc/development/changelog.md
@@ -14,12 +14,12 @@ following format:
---
title: "Change[log]s"
merge_request: 1972
-author: Black Sabbath
+author: Black Sabbath @bsabbath
type: added
```
The `merge_request` value is a reference to a merge request that adds this
-entry, and the `author` key is used to give attribution to community
+entry, and the `author` key (format: `<full name> <GitLab username>`) is used to give attribution to community
contributors. **Both are optional**.
The `type` field maps the category of the change,
valid options are: added, fixed, changed, deprecated, removed, security, performance, other. **Type field is mandatory**.
@@ -42,10 +42,9 @@ the `author` field. GitLab team members **should not**.
Example: "Fixed a typo on the search results page."
- Any docs-only changes **should not** have a changelog entry.
- Any change behind a feature flag **should not** have a changelog entry - unless
- the feature flag has been defaulted to true. The entry should be added
- [in the merge request removing the feature flags](feature_flags/development.md).
- If the change includes a database migration (regular, post, or data migration),
- there should be a changelog entry for the migration change.
+ the feature flag has been defaulted to true.
+- A change that [removes a feature flag](feature_flags/development.md) **should** have a changelog entry -
+ only if the feature flag did not default to true already.
- A fix for a regression introduced and then fixed in the same release (i.e.,
fixing a bug introduced during a monthly release candidate) **should not**
have a changelog entry.
diff --git a/doc/development/chatops_on_gitlabcom.md b/doc/development/chatops_on_gitlabcom.md
index 60056c2f65c..a3a4f1d7adc 100644
--- a/doc/development/chatops_on_gitlabcom.md
+++ b/doc/development/chatops_on_gitlabcom.md
@@ -14,9 +14,11 @@ tasks such as:
To request access to Chatops on GitLab.com:
1. Log into <https://ops.gitlab.net/users/sign_in> **using the same username** as for GitLab.com (you may have to rename it).
+ 1. You could also use the "Sign in with" Google button to sign in, with your GitLab.com email address.
1. Ask in the [#production](https://gitlab.slack.com/messages/production) channel for an existing member to add you to the `chatops` project in Ops. They can do it by running `/chatops run member add <username> gitlab-com/chatops --ops` command in that channel.
-NOTE: **Note:** If you had to change your username for GitLab.com on the first step, make sure [to reflect this information](https://gitlab.com/gitlab-com/www-gitlab-com#adding-yourself-to-the-team-page) on [the team page](https://about.gitlab.com/company/team/).
+NOTE: **Note:**
+If you had to change your username for GitLab.com on the first step, make sure [to reflect this information](https://gitlab.com/gitlab-com/www-gitlab-com#adding-yourself-to-the-team-page) on [the team page](https://about.gitlab.com/company/team/).
## See also
diff --git a/doc/development/cicd/img/ci_template_selection_v13_1.png b/doc/development/cicd/img/ci_template_selection_v13_1.png
new file mode 100644
index 00000000000..af9f6dd1a90
--- /dev/null
+++ b/doc/development/cicd/img/ci_template_selection_v13_1.png
Binary files differ
diff --git a/doc/development/cicd/index.md b/doc/development/cicd/index.md
index 42d4b494544..e0cca00fd69 100644
--- a/doc/development/cicd/index.md
+++ b/doc/development/cicd/index.md
@@ -2,6 +2,8 @@
Development guides that are specific to CI/CD are listed here.
+If you are creating new CI/CD templates, please read [the development guide for GitLab CI/CD templates](templates.md).
+
## CI Architecture overview
The following is a simplified diagram of the CI architecture. Some details are left out in order to focus on
@@ -90,7 +92,8 @@ A job with the `created` state won't be seen by the Runner yet. To make it possi
When the Runner is connected, it requests the next `pending` job to run by polling the server continuously.
-NOTE: **Note:** API endpoints used by the Runner to interact with GitLab are defined in [`lib/api/runner.rb`](https://gitlab.com/gitlab-org/gitlab/blob/master/lib/api/runner.rb)
+NOTE: **Note:**
+API endpoints used by the Runner to interact with GitLab are defined in [`lib/api/runner.rb`](https://gitlab.com/gitlab-org/gitlab/blob/master/lib/api/runner.rb)
After the server receives the request it selects a `pending` job based on the [`Ci::RegisterJobService` algorithm](#ciregisterjobservice), then assigns and sends the job to the Runner.
@@ -124,7 +127,8 @@ There are 3 top level queries that this service uses to gather the majority of t
This list of jobs is then filtered further by matching tags between job and Runner tags.
-NOTE: **Note:** If a job contains tags, the Runner will not pick the job if it does not match **all** the tags.
+NOTE: **Note:**
+If a job contains tags, the Runner will not pick the job if it does not match **all** the tags.
The Runner may have more tags than defined for the job, but not vice-versa.
Finally if the Runner can only pick jobs that are tagged, all untagged jobs are filtered out.
diff --git a/doc/development/cicd/templates.md b/doc/development/cicd/templates.md
new file mode 100644
index 00000000000..904e7adffe2
--- /dev/null
+++ b/doc/development/cicd/templates.md
@@ -0,0 +1,66 @@
+# Development guide for GitLab CI/CD templates
+
+This document explains how to develop [GitLab CI/CD templates](../../ci/examples/README.md).
+
+## Place the template file in a relevant directory
+
+All template files reside in the `lib/gitlab/ci/templates` directory, and are categorized by the following sub-directories:
+
+| Sub-directroy | Content | [Selectable in UI](#make-sure-the-new-template-can-be-selected-in-ui) |
+|---------------|--------------------------------------------------------------|-----------------------------------------------------------------------|
+| `/Jobs/*` | Auto DevOps related jobs | Yes |
+| `/Pages/*` | Static site generators for GitLab Pages (for example Jekyll) | Yes |
+| `/Security/*` | Security related jobs | Yes |
+| `/Verify/*` | Verify/testing related jobs | Yes |
+| `/Worklows/*` | Common uses of the `workflow:` keyword | No |
+| `/*` (root) | General templates | Yes |
+
+## Criteria
+
+The file must follow the [`.gitlab-ci.yml` syntax](../../ci/yaml/README.md).
+Verify it's valid by pasting it into the CI lint tool at `https://gitlab.com/gitlab-org/gitlab/-/ci/lint`.
+
+Also, all templates must be named with the `*.gitlab-ci.yml` suffix.
+
+### Backward compatibility
+
+A template might be dynamically included with the `include:template:` keyword. If
+you make a change to an *existing* template, you must make sure that it won't break
+CI/CD in existing projects.
+
+## Testing
+
+Each CI/CD template must be tested in order to make sure that it's safe to be published.
+
+### Manual QA
+
+It's always good practice to test the template in a minimal demo project.
+To do so, please follow the following steps:
+
+1. Create a public sample project on <https://gitlab.com>.
+1. Add a `.gitlab-ci.yml` to the project with the proposed template.
+1. Run pipelines and make sure that everything runs properly, in all possible cases
+ (merge request pipelines, schedules, and so on).
+1. Link to the project in the description of the merge request that is adding a new template.
+
+This is useful information for reviewers to make sure the template is safe to be merged.
+
+### Make sure the new template can be selected in UI
+
+Templates located under some directories are also [selectable in the **New file** UI](#place-the-template-file-in-a-relevant-directory).
+When you add a template into one of those directories, make sure that it correctly appears in the dropdown:
+
+![CI/CD template selection](img/ci_template_selection_v13_1.png)
+
+### Write an RSpec test
+
+You should write an RSpec test to make sure that pipeline jobs will be generated correctly:
+
+1. Add a test file at `spec/lib/gitlab/ci/templates/<template-category>/<template-name>_spec.rb`
+1. Test that pipeline jobs are properly created via `Ci::CreatePipelineService`.
+
+## Security
+
+A template could contain malicious code. For example, a template that contains the `export` shell command in a job
+might accidentally expose project secret variables in a job log.
+If you're unsure if it's secure or not, you need to ask security experts for cross-validation.
diff --git a/doc/development/code_intelligence/index.md b/doc/development/code_intelligence/index.md
new file mode 100644
index 00000000000..bd11f0bff79
--- /dev/null
+++ b/doc/development/code_intelligence/index.md
@@ -0,0 +1,110 @@
+# Code Intelligence
+
+> [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/1576) in GitLab 13.1.
+
+This document describes the design behind [Code Intelligence](../../user/project/code_intelligence.md).
+
+GitLab's built-in Code Intelligence is powered by
+[LSIF](https://lsif.dev) and comes down to generating an LSIF document for a
+project in a CI job, processing the data, uploading it as a CI artifact and
+displaying this information for the files in the project.
+
+Here is a sequence diagram for uploading an LSIF artifact:
+
+```mermaid
+sequenceDiagram
+ participant Runner
+ participant Workhorse
+ participant Rails
+ participant Object Storage
+
+ Runner->>+Workhorse: POST /v4/jobs/:id/artifacts
+ Workhorse->>+Rails: POST /:id/artifacts/authorize
+ Rails-->>-Workhorse: Respond with ProcessLsif header
+ Note right of Workhorse: Process LSIF file
+ Workhorse->>+Object Storage: Put file
+ Object Storage-->>-Workhorse: request results
+ Workhorse->>+Rails: POST /:id/artifacts
+ Rails-->>-Workhorse: request results
+ Workhorse-->>-Runner: request results
+```
+
+1. The CI/CD job generates a document in an LSIF format (usually `dump.lsif`) using [an
+ indexer](https://lsif.dev) for the language of a project. The format
+ [describes](https://github.com/sourcegraph/sourcegraph/blob/master/doc/user/code_intelligence/writing_an_indexer.md)
+ interactions between a method or function and its definition(s) or references. The
+ document is marked to be stored as an LSIF report artifact.
+
+1. After receiving a request for storing the artifact, Workhorse asks
+ GitLab Rails to authorize the upload.
+
+1. GitLab Rails validates whether the artifact can be uploaded and sends
+ `ProcessLsif: true` header if the lsif artifact can be processed.
+
+1. Workhorse reads the LSIF document line by line and generates code intelligence
+ data for each file in the project. The output is a zipped directory of JSON
+ files which imitates the structure of the project:
+
+ Project:
+
+ ```code
+ app
+ controllers
+ application_controller.rb
+ models
+ application.rb
+ ```
+
+ Generated data:
+
+ ```code
+ app
+ controllers
+ application_controller.rb.json
+ models
+ application.rb.json
+ ```
+
+1. The zipped directory is stored as a ZIP artifact. Workhorse replaces the
+ original LSIF document with a set of JSON files in the ZIP artifact and
+ generates metadata for it. The metadata makes it possible to view a single
+ file in a ZIP file without unpacking or loading the whole file. That allows us
+ to access code intelligence data for a single file.
+
+1. When a file is viewed in the GitLab application, frontend fetches code
+ intelligence data for the file directly from the object storage. The file
+ contains information about code units in the file. For example:
+
+ ```json
+ [
+ {
+ "definition_path": "cmd/check/main.go#L4",
+ "hover": [
+ {
+ "language": "go",
+ "tokens": [
+ [
+ {
+ "class": "kn",
+ "value": "package"
+ },
+ {
+ "value": " "
+ },
+ {
+ "class": "s",
+ "value": "\"fmt\""
+ }
+ ]
+ ]
+ },
+ {
+ "value": "Package fmt implements formatted I/O with functions analogous to C's printf and scanf. The format 'verbs' are derived from C's but are simpler. \n\n### hdr-PrintingPrinting\nThe verbs: \n\nGeneral: \n\n```\n%v\tthe value in a default format\n\twhen printing st..."
+ }
+ ],
+ "start_char": 2,
+ "start_line": 33
+ }
+ ...
+ ]
+ ```
diff --git a/doc/development/code_review.md b/doc/development/code_review.md
index 9c6cb1d0237..fd53ce79534 100644
--- a/doc/development/code_review.md
+++ b/doc/development/code_review.md
@@ -95,17 +95,16 @@ with [domain expertise](#domain-experts).
**approved by a [Distribution team member](https://about.gitlab.com/company/team/)**. See how to work with the [Distribution team](https://about.gitlab.com/handbook/engineering/development/enablement/distribution/#how-to-work-with-distribution) for more details.
1. If your merge request includes documentation changes, it must be **approved
by a [Technical writer](https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers)**, based on
- the appropriate [product category](https://about.gitlab.com/handbook/product/categories/).
-1. If your merge request includes Quality and non-Quality-related changes (*3*), it must be **approved
+ the appropriate [product category](https://about.gitlab.com/handbook/product/product-categories/).
+1. If your merge request includes end-to-end **and** non-end-to-end changes (*3*), it must be **approved
by a [Software Engineer in Test](https://about.gitlab.com/handbook/engineering/quality/#individual-contributors)**.
-1. If your merge request includes _only_ Quality-related changes (*3*), it must be **approved
- by a [Quality maintainer](https://about.gitlab.com/handbook/engineering/projects/#gitlab_maintainers_qa)**.
+1. If your merge request only includes end-to-end changes (*3*) **or** if the MR author is a [Software Engineer in Test](https://about.gitlab.com/handbook/engineering/quality/#individual-contributors), it must be **approved by a [Quality maintainer](https://about.gitlab.com/handbook/engineering/projects/#gitlab_maintainers_qa)**
- (*1*): Please note that specs other than JavaScript specs are considered backend code.
- (*2*): We encourage you to seek guidance from a database maintainer if your merge
request is potentially introducing expensive queries. It is most efficient to comment
on the line of code in question with the SQL queries so they can give their advice.
-- (*3*): Quality-related changes include all files within the `qa` directory.
+- (*3*): End-to-end changes include all files within the `qa` directory.
#### Security requirements
@@ -386,9 +385,12 @@ author.
- Learning how to find the right balance takes time; that is why we have
reviewers that become maintainers after some time spent on reviewing merge
requests.
-- Finding bugs and improving code style is important, but thinking about good
- design is important as well. Building abstractions and good design is what
- makes it possible to hide complexity and makes future changes easier.
+- Finding bugs is important, but thinking about good design is important as
+ well. Building abstractions and good design is what makes it possible to hide
+ complexity and makes future changes easier.
+- Enforcing and improving [code style](contributing/style_guides.md) should be primarily done through
+ [automation](https://about.gitlab.com/handbook/values/#cleanup-over-sign-off)
+ instead of review comments.
- Asking the author to change the design sometimes means the complete rewrite
of the contributed code. It's usually a good idea to ask another maintainer or
reviewer before doing it, but have the courage to do it when you believe it is
diff --git a/doc/development/contributing/issue_workflow.md b/doc/development/contributing/issue_workflow.md
index f70299cbfc2..9feaa485bd2 100644
--- a/doc/development/contributing/issue_workflow.md
+++ b/doc/development/contributing/issue_workflow.md
@@ -81,7 +81,7 @@ already reserved for category labels).
The descriptions on the [labels page](https://gitlab.com/groups/gitlab-org/-/labels)
explain what falls under each type label.
-The GitLab handbook documents [when something is a bug and when it is a feature request](https://about.gitlab.com/handbook/product/product-management/process/feature-or-bug.html).
+The GitLab handbook documents [when something is a bug](https://about.gitlab.com/handbook/product/product-processes/#bug-issues) and [when it is a feature request](https://about.gitlab.com/handbook/product/product-processes/#feature-issues).
### Facet labels
@@ -104,7 +104,7 @@ Following is a non-exhaustive list of facet labels:
### Stage labels
-Stage labels specify which [stage](https://about.gitlab.com/handbook/product/categories/#hierarchy) the issue belongs to.
+Stage labels specify which [stage](https://about.gitlab.com/handbook/product/product-categories/#hierarchy) the issue belongs to.
#### Naming and color convention
@@ -148,7 +148,7 @@ The current group labels can be found by [searching the labels list for `group::
These labels are [scoped labels](../../user/project/labels.md#scoped-labels-premium)
and thus are mutually exclusive.
-You can find the groups listed in the [Product Stages, Groups, and Categories](https://about.gitlab.com/handbook/product/categories/) page.
+You can find the groups listed in the [Product Stages, Groups, and Categories](https://about.gitlab.com/handbook/product/product-categories/) page.
We use the term group to map down product requirements from our product stages.
As a team needs some way to collect the work their members are planning to be assigned to, we use the `~group::` labels to do so.
@@ -167,7 +167,7 @@ Please read [Stage and Group labels in Throughput](https://about.gitlab.com/hand
### Category labels
From the handbook's
-[Product stages, groups, and categories](https://about.gitlab.com/handbook/product/categories/#hierarchy)
+[Product stages, groups, and categories](https://about.gitlab.com/handbook/product/product-categories/#hierarchy)
page:
> Categories are high-level capabilities that may be a standalone product at
@@ -199,7 +199,7 @@ in <https://gitlab.com/gitlab-com/www-gitlab-com/blob/master/data/categories.yml
### Feature labels
From the handbook's
-[Product stages, groups, and categories](https://about.gitlab.com/handbook/product/categories/#hierarchy)
+[Product stages, groups, and categories](https://about.gitlab.com/handbook/product/product-categories/#hierarchy)
page:
> Features: Small, discrete functionalities. e.g. Issue weights. Some common
@@ -312,22 +312,13 @@ We want to avoid a situation when a contributor picks an
because we realize that it does not fit our vision, or we want to solve it in a
different way.
-We add the ~"Accepting merge requests" label to:
+We automatically add the ~"Accepting merge requests" label to issues
+that match the [triage policy](https://about.gitlab.com/handbook/engineering/quality/triage-operations/#accepting-merge-requests).
-- Low priority ~bug issues (i.e. we do not add it to the bugs that we want to
- solve in the ~"Next Patch Release")
-- Small ~feature
-- Small ~"technical debt" issues
-
-After adding the ~"Accepting merge requests" label, we try to estimate the
-[weight](#issue-weight) of the issue. We use issue weight to let contributors
-know how difficult the issue is. Additionally:
-
-- We advertise [`Accepting merge requests` issues with weight < 5](https://gitlab.com/groups/gitlab-org/-/issues?state=opened&label_name[]=Accepting+merge+requests&assignee_id=None&sort=weight)
- as suitable for people that have never contributed to GitLab before on the
- [Up For Grabs campaign](https://up-for-grabs.net/#/)
-- We encourage people that have never contributed to any open source project to
- look for [`Accepting merge requests` issues with a weight of 1](https://gitlab.com/groups/gitlab-org/-/issues?state=opened&label_name[]=Accepting+merge+requests&assignee_id=None&sort=weight&weight=1)
+We recommend people that have never contributed to any open source project to
+look for issues labeled `~"Accepting merge requests"` with a [weight of 1](https://gitlab.com/groups/gitlab-org/-/issues?state=opened&label_name[]=Accepting+merge+requests&assignee_id=None&sort=weight&weight=1) or the `~"Good for 1st time contributors"` [label](https://gitlab.com/gitlab-org/gitlab/-/issues?scope=all&utf8=%E2%9C%93&state=opened&label_name[]=Good%20for%201st%20time%20contributors&assignee_id=None) attached to it.
+More experienced contributors are very welcome to tackle
+[any of them](https://gitlab.com/groups/gitlab-org/-/issues?state=opened&label_name[]=Accepting+merge+requests&assignee_id=None).
If you've decided that you would like to work on an issue, please @-mention
the [appropriate product manager](https://about.gitlab.com/handbook/product/#who-to-talk-to-for-what)
diff --git a/doc/development/contributing/merge_request_workflow.md b/doc/development/contributing/merge_request_workflow.md
index 14c6b0b1073..e5a8bdad7b0 100644
--- a/doc/development/contributing/merge_request_workflow.md
+++ b/doc/development/contributing/merge_request_workflow.md
@@ -119,8 +119,7 @@ document from the Kubernetes team also has some great points regarding this.
When writing commit messages, please follow the guidelines below:
- The commit subject must contain at least 3 words.
-- The commit subject should ideally contain up to 50 characters,
- and must not be longer than 72 characters.
+- The commit subject must not be longer than 72 characters.
- The commit subject must start with a capital letter.
- The commit subject must not end with a period.
- The commit subject and body must be separated by a blank line.
diff --git a/doc/development/contributing/style_guides.md b/doc/development/contributing/style_guides.md
index 9e4870eadc4..ed254052180 100644
--- a/doc/development/contributing/style_guides.md
+++ b/doc/development/contributing/style_guides.md
@@ -56,6 +56,16 @@ Additionally, we have a dedicated
[newlines style guide](../newlines_styleguide.md), as well as dedicated
[test-specific style guides and best practices](../testing_guide/index.md).
+### Creating new RuboCop cops
+
+Typically it is better for the linting rules to be enforced programmatically as it
+reduces the aforementioned [bike-shedding](https://en.wiktionary.org/wiki/bikeshedding).
+
+To that end, we encourage creation of new RuboCop rules in the codebase.
+
+When creating a new cop that could be applied to multiple applications, we encourage you
+to add it to our [GitLab Styles](https://gitlab.com/gitlab-org/gitlab-styles) gem.
+
## Database migrations
See the dedicated [Database Migrations Style Guide](../migration_style_guide.md).
@@ -82,7 +92,7 @@ See the dedicated [Shell scripting standards and style guidelines](../shell_scri
## Markdown
-We're following [Ciro Santilli's Markdown Style Guide](https://cirosantilli.com/markdown-style-guide).
+We're following [Ciro Santilli's Markdown Style Guide](https://cirosantilli.com/markdown-style-guide/).
## Documentation
diff --git a/doc/development/dangerbot.md b/doc/development/dangerbot.md
index eda9e1e21cc..93434479846 100644
--- a/doc/development/dangerbot.md
+++ b/doc/development/dangerbot.md
@@ -128,10 +128,18 @@ Here is a (non-exhaustive) list of the kinds of things Danger has been used for
at GitLab so far:
- Coding style
-- Database review workflow
-- Documentation review workflow
+- Database review
+- Documentation review
- Merge request metrics
-- Reviewer roulette workflow
+- Reviewer roulette. Reviewers and maintainers are chosen based on:
+ - Their roles (backend, frontend, database, etc).
+ - Their availability:
+ - No "OOO"/"PTO"/"Parental Leave" in their GitLab or Slack status.
+ - No `:red_circle:`/`:palm_tree:`/`:beach:`/`:beach_umbrella:`/`:beach_with_umbrella:` emojis in GitLab or Slack status.
+ - [Experimental] Their timezone: people for which the local hour is between
+ 6 AM and 2 PM are eligible to be picked. This is to ensure they have a good
+ chance to get to perform a review during their current work day. The experimentation is tracked in
+ [this issue](https://gitlab.com/gitlab-org/quality/team-tasks/-/issues/563)
- Single codebase effort
## Limitations
diff --git a/doc/development/database/add_foreign_key_to_existing_column.md b/doc/development/database/add_foreign_key_to_existing_column.md
index b8817eddeec..1b41a52c95e 100644
--- a/doc/development/database/add_foreign_key_to_existing_column.md
+++ b/doc/development/database/add_foreign_key_to_existing_column.md
@@ -113,7 +113,8 @@ end
Validating the foreign key will scan the whole table and make sure that each relation is correct.
-NOTE: **Note:** When using [background migrations](../background_migrations.md), foreign key validation should happen in the next GitLab release.
+NOTE: **Note:**
+When using [background migrations](../background_migrations.md), foreign key validation should happen in the next GitLab release.
Migration file for validating the foreign key:
diff --git a/doc/development/database/database_reviewer_guidelines.md b/doc/development/database/database_reviewer_guidelines.md
new file mode 100644
index 00000000000..894b1ea15f0
--- /dev/null
+++ b/doc/development/database/database_reviewer_guidelines.md
@@ -0,0 +1,95 @@
+# Database Reviewer Guidelines
+
+This page includes introductory material for new database reviewers.
+
+If you are interested in getting an application update reviewed,
+check the [database review guidelines](../database_review.md).
+
+## Scope of work done by a database reviewer
+
+Database reviewers are domain experts who have substantial experience with databases,
+`SQL`, and query performance optimization.
+
+A database review is required whenever an application update [touches the database](../database_review.md#general-process).
+
+The database reviewer is tasked with reviewing the database specific updates and
+making sure that any queries or modifications will perform without issues
+at the scale of GitLab.com.
+
+For more information on the database review process, check the [database review guidelines](../database_review.md).
+
+## How to apply for becoming a database reviewer
+
+Team members are encouraged to self-identify as database domain experts and add it to their [team profile](https://gitlab.com/gitlab-com/www-gitlab-com/-/blob/master/data/team.yml)
+
+```yaml
+ projects:
+ gitlab:
+ - reviewer database
+```
+
+Assign the MR which adds your expertise to the `team.yml` file to a database maintainer
+or the [Database Team's Engineering Manager](https://about.gitlab.com/handbook/engineering/development/enablement/database/).
+
+Once the `team.yml` update is merged, the [Reviewer roulette](../code_review.md#reviewer-roulette)
+may recommend you as a database reviewer.
+
+## Resources for database reviewers
+
+As a database reviewer, join the internal `#database` Slack channel and ask questions or discuss
+database related issues with other database reviewers and maintainers.
+
+There is also an optional database office hours call held bi-weekly, alternating between
+European/US and APAC friendly hours. You can join the office hours call and bring topics
+that require a more in-depth discussion between the database reviewers and maintainers:
+
+- [Database Office Hours Agenda](https://docs.google.com/document/d/1wgfmVL30F8SdMg-9yY6Y8djPSxWNvKmhR5XmsvYX1EI/edit).
+- [Youtube playlist with past recordings](https://www.youtube.com/playlist?list=PL05JrBw4t0Kp-kqXeiF7fF7cFYaKtdqXM).
+
+You should also join the [#database-labs](../understanding_explain_plans.md#database-lab)
+Slack channel and get familiar with how to use Joe, the slackbot that provides developers
+with their own clone of the production database.
+
+Understanding and efficiently using `EXPLAIN` plans is at the core of the database review process.
+The following guides provide a quick introduction and links to follow on more advanced topics:
+
+- Guide on [understanding EXPLAIN plans](../understanding_explain_plans.md).
+- [Explaining the unexplainable series in depesz](https://www.depesz.com/tag/unexplainable/).
+
+Finally, you can find various guides in the [Database guides](index.md) page that cover more specific
+topics and use cases. The most frequently required during database reviewing are the following:
+
+- [Migrations style guide](../migration_style_guide.md) for creating safe SQL migrations.
+- [What requires downtime?](../what_requires_downtime.md).
+- [SQL guidelines](../sql.md) for working with SQL queries.
+
+## How to apply for becoming a database maintainer
+
+Once a database reviewer feels confident on switching to a database maintainer,
+they can update their [team profile](https://gitlab.com/gitlab-com/www-gitlab-com/-/blob/master/data/team.yml)
+to a `trainee_maintainer database`:
+
+```yaml
+ projects:
+ gitlab:
+ - trainee_maintainer database
+```
+
+The first step is to a create a [Trainee Database Maintainer Issue](https://gitlab.com/gitlab-com/www-gitlab-com/-/issues/new?issuable_template=trainee-database-maintainer).
+Use and follow the process described in the 'Trainee database maintainer' template.
+
+Note that [trainee maintainers](https://about.gitlab.com/handbook/engineering/workflow/code-review/#trainee-maintainer)
+are three times as likely to be picked by the [Danger bot](../dangerbot.md) as other reviewers.
+
+## What to do if you feel overwhelmed
+
+Similar to all types of reviews, [unblocking others is always a top priority](https://about.gitlab.com/handbook/values/#global-optimization).
+Database reviewers are expected to [review assigned merge requests in a timely manner](../code_review.md#review-turnaround-time)
+or let the author know as soon as possible and help them find another reviewer or maintainer.
+
+We are doing reviews to help the rest of the GitLab team and, at the same time, get exposed
+to more use cases, get a lot of insights and hone our database and data management skills.
+
+If you are feeling overwhelmed, think you are at capacity, and are unable to accept any more
+reviews until some have been completed, communicate this through your GitLab status by setting
+the `:red_circle:` emoji and mentioning that you are at capacity in the status text.
diff --git a/doc/development/database/index.md b/doc/development/database/index.md
index 665af623059..9ea5b6fcaac 100644
--- a/doc/development/database/index.md
+++ b/doc/development/database/index.md
@@ -1,5 +1,13 @@
# Database guides
+## Database Reviews
+
+- If you're creating a database MR for review, check out our [Database review guidelines](../database_review.md).
+
+ It provides an introduction on database-related changes, migrations, and complex SQL queries.
+
+- If you're a database reviewer or want to become one, check out our [introduction to reviewing database changes](database_reviewer_guidelines.md).
+
## Tooling
- [Understanding EXPLAIN plans](../understanding_explain_plans.md)
diff --git a/doc/development/database_debugging.md b/doc/development/database_debugging.md
index 629aea5b121..6da0455f392 100644
--- a/doc/development/database_debugging.md
+++ b/doc/development/database_debugging.md
@@ -1,4 +1,4 @@
-# Database Debugging and Troubleshooting
+# Troubleshooting and Debugging Database
This section is to help give some copy-pasta you can use as a reference when you
run into some head-banging database problems.
diff --git a/doc/development/database_review.md b/doc/development/database_review.md
index 5405a8808f0..967df411db5 100644
--- a/doc/development/database_review.md
+++ b/doc/development/database_review.md
@@ -19,6 +19,10 @@ A database review is required for:
generally up to the author of a merge request to decide whether or
not complex queries are being introduced and if they require a
database review.
+- Changes in usage data metrics that use `count` and `distinct_count`.
+ These metrics could have complex queries over large tables.
+ See the [Telemetry Guide](telemetry/usage_ping.md#implementing-usage-ping)
+ for implementation details.
A database reviewer is expected to look out for obviously complex
queries in the change and review those closer. If the author does not
@@ -190,7 +194,8 @@ In general, migrations for a single deploy shouldn't take longer than
1 hour for GitLab.com. The following guidelines are not hard rules, they were
estimated to keep migration timing to a minimum.
-NOTE: **Note:** Keep in mind that all runtimes should be measured against GitLab.com.
+NOTE: **Note:**
+Keep in mind that all runtimes should be measured against GitLab.com.
| Migration Type | Execution Time Recommended | Notes |
|----|----|---|
diff --git a/doc/development/distributed_tracing.md b/doc/development/distributed_tracing.md
index 7fc33380aba..15b3b8ba755 100644
--- a/doc/development/distributed_tracing.md
+++ b/doc/development/distributed_tracing.md
@@ -66,7 +66,7 @@ GITLAB_TRACING=opentracing://<driver>?<param_name>=<param_value>&<param_name_2>=
In this example, we have the following hypothetical values:
- `driver`: the driver. [GitLab supports
- `jaeger`](../user/project/operations/tracing.md). In future, other
+ `jaeger`](../operations/tracing.md). In future, other
tracing implementations may also be supported.
- `param_name`, `param_value`: these are driver specific configuration values. Configuration
parameters for Jaeger are documented [further on in this
diff --git a/doc/development/documentation/index.md b/doc/development/documentation/index.md
index f845a6ec592..2ea26985fcf 100644
--- a/doc/development/documentation/index.md
+++ b/doc/development/documentation/index.md
@@ -26,7 +26,7 @@ The source of the documentation exists within the codebase of each GitLab applic
| --- | --- |
| [GitLab](https://gitlab.com/gitlab-org/gitlab/) | [`/doc`](https://gitlab.com/gitlab-org/gitlab/tree/master/doc) |
| [GitLab Runner](https://gitlab.com/gitlab-org/gitlab-runner/) | [`/docs`](https://gitlab.com/gitlab-org/gitlab-runner/tree/master/docs) |
-| [Omnibus GitLab](https://gitlab.com/gitlab-org/omnibus-gitlab/) | [`/doc`](https://gitlab.com/gitlab-org/gitlab/tree/master/doc) |
+| [Omnibus GitLab](https://gitlab.com/gitlab-org/omnibus-gitlab/) | [`/doc`](https://gitlab.com/gitlab-org/omnibus-gitlab/tree/master/doc) |
| [Charts](https://gitlab.com/gitlab-org/charts/gitlab) | [`/doc`](https://gitlab.com/gitlab-org/charts/gitlab/tree/master/doc) |
Documentation issues and merge requests are part of their respective repositories and all have the label `Documentation`.
@@ -70,7 +70,28 @@ See the [Structure](styleguide.md#structure) section of the [Documentation Style
## Metadata
To provide additional directives or useful information, we add metadata in YAML
-format to the beginning of each product documentation page.
+format to the beginning of each product documentation page (YAML front matter).
+All values are treated as strings and are only used for the
+[docs website](site_architecture/index.md).
+
+### Stage and group metadata
+
+Each page should ideally have metadata related to the stage and group it
+belongs to, as well as an information block as described below:
+
+- `stage`: The [Stage](https://about.gitlab.com/handbook/product/product-categories/#devops-stages)
+ to which the majority of the page's content belongs.
+- `group`: The [Group](https://about.gitlab.com/company/team/structure/#product-groups)
+ to which the majority of the page's content belongs.
+- `info`: The following line, which provides direction to contributors regarding
+ how to contact the Technical Writer associated with the page's Stage and
+ Group:
+
+ ```plaintext
+ To determine the technical writer assigned to the Stage/Group
+ associated with this page, see
+ https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers
+ ```
For example, the following metadata would be at the beginning of a product
documentation page whose content is primarily associated with the Audit Events
@@ -84,24 +105,64 @@ info: To determine the technical writer assigned to the Stage/Group associated w
---
```
-The following list describes the YAML parameters in use:
+### Page type metadata
+
+Originally discussed in [this epic](https://gitlab.com/groups/gitlab-org/-/epics/1280),
+each page should have a `type` metadata. It can be one or more of the following:
+
+- `index`: Index/overview pages. They serve as a list to other pages. Doesn't
+ necessarily mean the page should be named `index.md`. [Example page](../../install/README.md).
+- `concepts`: What you need to know before using product. Informational, not
+ instructional. For example, abstract ideas, explain meaning or benefit, support
+ understanding of tasks. They are read for background information, for example
+ "Why X is important". [Example page](../../topics/autodevops/index.md).
+- `howto`: Specific use case instructions. [Example page](../../ssh/README.md).
+- `tutorial`: Learn a process/concept by doing. [Example page](../../gitlab-basics/start-using-git.md).
+- `reference`: Covers what things are/do. Things like specific settings, facts
+ without too much explanation that are read for detailed information.
+ [Example page](../../ci/yaml/README.md).
+
+### Redirection metadata
+
+The following metadata should be added when a page is moved to another location:
- `redirect_to`: The relative path and filename (with an `.md` extension) of the
location to which visitors should be redirected for a moved page.
[Learn more](#changing-document-location).
-- `stage`: The [Stage](https://about.gitlab.com/handbook/product/categories/#devops-stages)
- to which the majority of the page's content belongs.
-- `group`: The [Group](https://about.gitlab.com/company/team/structure/#product-groups)
- to which the majority of the page's content belongs.
-- `info`: The following line, which provides direction to contributors regarding
- how to contact the Technical Writer associated with the page's Stage and
- Group: `To determine the technical writer assigned to the Stage/Group
- associated with this page, see
- https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers`
- `disqus_identifier`: Identifier for Disqus commenting system. Used to keep
comments with a page that's been moved to a new URL.
[Learn more](#redirections-for-pages-with-disqus-comments).
+### Comments metadata
+
+The [docs website](site_architecture/index.md) has comments (provided by Disqus)
+enabled by default. In case you want to disable them (for example in index pages),
+set it to `false`:
+
+```yaml
+---
+comments: false
+---
+```
+
+### Additional page metadata
+
+Each page can have additional (optional) metadata (set in the
+[default.html](https://gitlab.com/gitlab-org/gitlab-docs/-/blob/fc3577921343173d589dfa43d837b4307e4e620f/layouts/default.html#L30-52)
+Nanoc layout), which will be shown to the top of the page if defined:
+
+- `author`: The name of the author of a page, usually a tutorial. It requires `author_gitlab` in order to be shown.
+- `author_gitlab`: The username of the author on GitLab.com. It requires `author` in order to be shown.
+- `date`: The date the page was created, usually for tutorials.
+- `article_type`: The type of article. Can be either `tutorial` or `user guide`.
+- `level`: The level of complexity of a how-to or tutorial. Can be either `beginner`,
+ `advanced`, or `intermediate`.
+- `last_updated`: The date in ISO format when the page was last updated. For example `2020-02-14`.
+- `reading_time`: If you want to add an indication of the approximate reading
+ time of a page, you can set `reading_time` to `true`. This uses a simple
+ [algorithm](https://gitlab.com/gitlab-org/gitlab-docs/-/blob/master/lib/helpers/reading_time.rb)
+ to calculate the reading time based on the number of words.
+
## Changing document location
Changing a document's location requires specific steps to ensure that
@@ -226,9 +287,6 @@ it increases the work of the release managers.
Every GitLab instance includes the documentation, which is available at `/help`
(`https://gitlab.example.com/help`). For example, <https://gitlab.com/help>.
-There are [plans](https://gitlab.com/groups/gitlab-org/-/epics/693) to end this
-practice and instead link out from the GitLab application to <https://docs.gitlab.com> URLs.
-
The documentation available online on <https://docs.gitlab.com> is deployed every four hours from the `master` branch of GitLab, Omnibus, and Runner. Therefore,
after a merge request gets merged, it will be available online on the same day.
However, it will be shipped (and available on `/help`) within the milestone assigned
@@ -492,122 +550,138 @@ The output should be similar to:
Note that this requires you to either have the required lint tools installed on your machine,
or a working Docker installation, in which case an image with these tools pre-installed will be used.
-### Local linting
+### Local linters
To help adhere to the [documentation style guidelines](styleguide.md), and improve the content
-added to documentation, consider locally installing and running documentation linters. This will
-help you catch common issues before raising merge requests for review of documentation.
-
-Running the following locally allows you to match the checks in the build pipeline:
+added to documentation, [install documentation linters](#install-linters) and
+[integrate them with your code editor](#configure-editors).
-- [markdownlint](#markdownlint).
-- [Vale](#vale).
+At GitLab, we mostly use:
-NOTE: **Note:**
-This list does not limit what other linters you can add to your local documentation writing toolchain.
+- [markdownlint](#markdownlint)
+- [Vale](#vale)
#### markdownlint
-[markdownlint](https://github.com/DavidAnson/markdownlint) checks that Markdown
-syntax follows [certain rules](https://github.com/DavidAnson/markdownlint/blob/master/doc/Rules.md#rules),
-and is used by the [`docs-lint` test](#testing) with a [configuration file](#markdownlint-configuration).
-Our [Documentation Style Guide](styleguide.md#markdown) and [Markdown Guide](https://about.gitlab.com/handbook/markdown-guide/)
-elaborate on which choices must be made when selecting Markdown syntax for GitLab
-documentation. This tool helps catch deviations from those guidelines.
-
-markdownlint can be used [on the command line](https://github.com/igorshubovych/markdownlint-cli#markdownlint-cli--),
-either on a single Markdown file or on all Markdown files in a project. For example, to run
-markdownlint on all documentation in the [`gitlab` project](https://gitlab.com/gitlab-org/gitlab),
-run the following commands from within your `gitlab` project root directory, which will
-automatically detect the [`.markdownlint.json`](#markdownlint-configuration) configuration
-file in the root of the project, and test all files in `/doc` and its subdirectories:
-
-```shell
-markdownlint 'doc/**/*.md'
-```
+[markdownlint](https://github.com/DavidAnson/markdownlint) checks that Markdown syntax follows
+[certain rules](https://github.com/DavidAnson/markdownlint/blob/master/doc/Rules.md#rules), and is
+used by the [`docs-lint` test](#testing).
-If you wish to use a different configuration file, use the `-c` flag:
+Our [Documentation Style Guide](styleguide.md#markdown) and
+[Markdown Guide](https://about.gitlab.com/handbook/markdown-guide/) elaborate on which choices must
+be made when selecting Markdown syntax for GitLab documentation. This tool helps catch deviations
+from those guidelines.
-```shell
-markdownlint -c <config-file-name> 'doc/**/*.md'
-```
+markdownlint configuration is found in the following projects:
-##### Run markdownlint in an editor
+- [`gitlab`](https://gitlab.com/gitlab-org/gitlab/blob/master/.markdownlint.json)
+- [`gitlab-runner`](https://gitlab.com/gitlab-org/gitlab-runner/blob/master/.markdownlint.json)
+- [`omnibus-gitlab`](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/.markdownlint.json)
+- [`charts`](https://gitlab.com/gitlab-org/charts/gitlab/-/blob/master/.markdownlint.json)
+- [`gitlab-development-kit`](https://gitlab.com/gitlab-org/gitlab-development-kit/-/blob/master/.markdownlint.json)
-markdownlint can also be run as a linter within text editors using [plugins/extensions](https://github.com/DavidAnson/markdownlint#related),
-such as:
+This configuration is also used within build pipelines.
-- [Sublime Text](https://packagecontrol.io/packages/SublimeLinter-contrib-markdownlint)
-- [Visual Studio Code](https://marketplace.visualstudio.com/items?itemName=DavidAnson.vscode-markdownlint)
-- [Atom](https://atom.io/packages/linter-node-markdownlint)
+You can use markdownlint:
-It is best to use the [same configuration file](#markdownlint-configuration) as what
-is in use in the four repositories that are the sources for <https://docs.gitlab.com>. Each
-plugin/extension has different requirements regarding the configuration file, which
-is explained in each editor's docs.
+- [On the command line](https://github.com/igorshubovych/markdownlint-cli#markdownlint-cli--).
+- [Within a code editor](#configure-editors).
-##### markdownlint configuration
+#### Vale
-Each formatting issue that markdownlint checks has an associated
-[rule](https://github.com/DavidAnson/markdownlint/blob/master/doc/Rules.md#rules).
-These rules are configured in the `.markdownlint.json` files located in the root of
-four repositories that are the sources for <https://docs.gitlab.com>:
+[Vale](https://errata-ai.gitbook.io/vale/) is a grammar, style, and word usage linter for the
+English language. Vale's configuration is stored in the
+[`.vale.ini`](https://gitlab.com/gitlab-org/gitlab/blob/master/.vale.ini) file located in the root
+directory of projects.
-- [`gitlab`](https://gitlab.com/gitlab-org/gitlab/blob/master/.markdownlint.json)
-- [`gitlab-runner`](https://gitlab.com/gitlab-org/gitlab-runner/blob/master/.markdownlint.json)
-- [`omnibus-gitlab`](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/.markdownlint.json)
-- [`charts`](https://gitlab.com/charts/gitlab/blob/master/.markdownlint.json)
+Vale supports creating [custom tests](https://errata-ai.github.io/vale/styles/) that extend any of
+several types of checks, which we store in the `.linting/vale/styles/gitlab` directory within the
+documentation directory of projects.
-By default all rules are enabled, so the configuration file is used to disable unwanted
-rules, and also to configure optional parameters for enabled rules as needed. You can
-also check [the issue](https://gitlab.com/gitlab-org/gitlab-foss/-/issues/64352) that
-tracked the changes required to implement these rules, and details which rules were
-on or off when markdownlint was enabled on the docs.
+Vale configuration is found in the following projects:
-#### Vale
+- [`gitlab`](https://gitlab.com/gitlab-org/gitlab/-/tree/master/doc/.vale/gitlab)
+- [`gitlab-runner`](https://gitlab.com/gitlab-org/gitlab-runner/-/tree/master/docs/.vale/gitlab)
+- [`omnibus-gitlab`](https://gitlab.com/gitlab-org/omnibus-gitlab/-/tree/master/doc/.vale/gitlab)
+- [`charts`](https://gitlab.com/gitlab-org/charts/gitlab/-/tree/master/doc/.vale/gitlab)
+- [`gitlab-development-kit`](https://gitlab.com/gitlab-org/gitlab-development-kit/-/tree/master/doc/.vale/gitlab)
-[Vale](https://errata-ai.gitbook.io/vale/) is a grammar, style, and word usage linter
-for the English language. Vale's configuration is stored in the
-[`.vale.ini`](https://gitlab.com/gitlab-org/gitlab/blob/master/.vale.ini) file
-located in the root directory of the [GitLab repository](https://gitlab.com/gitlab-org/gitlab).
+This configuration is also used within build pipelines.
-Vale supports creating [custom tests](https://errata-ai.github.io/vale/styles/),
-stored in the `doc/.linting/vale/styles/gitlab` directory, that extend any of
-several types of checks.
+You can use Vale:
-To view linting suggestions locally, you must install Vale on your own machine,
-and from GitLab's root directory (where `.vale.ini` is located), run:
+- [On the command line](https://errata-ai.gitbook.io/vale/getting-started/usage).
+- [Within a code editor](#configure-editors).
-```shell
-vale --glob='*.{md}' doc
-```
+#### Install linters
+
+At a minimum, install [markdownlint](#markdownlint) and [Vale](#vale) to match the checks run in
+build pipelines:
-Vale's error-level test results [are displayed](#testing) in CI pipelines.
+1. Install `markdownlint-cli`, using either:
-##### Run Vale in an editor
+ - `npm`:
+
+ ```shell
+ npm install -g markdownlint-cli
+ ```
+
+ - `yarn`:
+
+ ```shell
+ yarn global add markdownlint-cli
+ ```
+
+ We recommend installing the version of `markdownlint-cli` currently used in the documentation
+ linting [Docker image](https://gitlab.com/gitlab-org/gitlab-docs/-/blob/master/dockerfiles/Dockerfile.gitlab-docs-lint#L38).
+
+1. Install [`vale`](https://github.com/errata-ai/vale/releases). For example, to install using
+ `brew` for macOS, run:
+
+ ```shell
+ brew install vale
+ ```
+
+ We recommend installing the version of Vale currently used in the documentation linting
+ [Docker image](https://gitlab.com/gitlab-org/gitlab-docs/-/blob/master/dockerfiles/Dockerfile.gitlab-docs-lint#L16).
+
+In addition to using markdownlint and Vale at the command line, these tools can be
+[integrated with your code editor](#configure-editors).
+
+#### Configure editors
+
+To configure markdownlint within your editor, install one of the following as appropriate:
+
+- [Sublime Text](https://packagecontrol.io/packages/SublimeLinter-contrib-markdownlint)
+- [Visual Studio Code](https://marketplace.visualstudio.com/items?itemName=DavidAnson.vscode-markdownlint)
+- [Atom](https://atom.io/packages/linter-node-markdownlint)
-You can run Vale as a linter within your text editor of choice, with:
+To configure Vale within your editor, install one of the following as appropriate:
- The Sublime Text [`SublimeLinter-contrib-vale` plugin](https://packagecontrol.io/packages/SublimeLinter-contrib-vale)
- The Visual Studio Code [`testthedocs.vale` extension](https://marketplace.visualstudio.com/items?itemName=testthedocs.vale)
We don't use [Vale Server](https://errata-ai.github.io/vale/#using-vale-with-a-text-editor-or-another-third-party-application).
-##### Disable a Vale test
+#### Disable Vale tests
-You can disable a specific Vale linting rule or all Vale linting rules for any portion of a document:
+You can disable a specific Vale linting rule or all Vale linting rules for any portion of a
+document:
-- To disable a specific rule, add a `<!-- vale gitlab.rulename = NO -->` tag
- before the text, and a `<!-- vale gitlab.rulename = YES -->` tag after the text,
- replacing `rulename` with the filename of a test in the [GitLab styles](https://gitlab.com/gitlab-org/gitlab/-/tree/master/doc/.linting/vale/styles/gitlab) directory.
-- To disable all Vale linting rules, add a `<!-- vale off -->` tag before the text,
- and a `<!-- vale on -->` tag after the text.
+- To disable a specific rule, add a `<!-- vale gitlab.rulename = NO -->` tag before the text, and a
+ `<!-- vale gitlab.rulename = YES -->` tag after the text, replacing `rulename` with the filename
+ of a test in the
+ [GitLab styles](https://gitlab.com/gitlab-org/gitlab/-/tree/master/doc/.linting/vale/styles/gitlab)
+ directory.
+- To disable all Vale linting rules, add a `<!-- vale off -->` tag before the text, and a
+ `<!-- vale on -->` tag after the text.
-Whenever possible, exclude only the problematic rule and line(s).
-In some cases, such as list items, you may need to disable linting for the entire
-list until ["Ignore comments are not honored in a Markdown file"](https://github.com/errata-ai/vale/issues/175) is resolved.
+Whenever possible, exclude only the problematic rule and line(s). In some cases, such as list items,
+you may need to disable linting for the entire list until
+[Vale issue #175](https://github.com/errata-ai/vale/issues/175) is resolved.
-For more information, see [Vale's documentation](https://errata-ai.gitbook.io/vale/getting-started/markup#markup-based-configuration).
+For more information, see
+[Vale's documentation](https://errata-ai.gitbook.io/vale/getting-started/markup#markup-based-configuration).
## Danger Bot
diff --git a/doc/development/documentation/site_architecture/global_nav.md b/doc/development/documentation/site_architecture/global_nav.md
index 71020e6054e..2625fbe0415 100644
--- a/doc/development/documentation/site_architecture/global_nav.md
+++ b/doc/development/documentation/site_architecture/global_nav.md
@@ -16,6 +16,22 @@ Global navigation (the left-most pane in our three pane documentation) provides:
- The ability to refine landing pages, so they don't have to do all the work of surfacing
every page contained within the documentation.
+## Quick start
+
+To add a topic to the global nav, go to the directory that contains
+[navigation files](https://gitlab.com/gitlab-org/gitlab-docs/blob/master/content/_data/)
+and edit the `yaml` file for your product area. You can copy an existing nav entry and
+edit it to point to your topic.
+
+The files are:
+
+| File | Document | Location |
+|-----------------------|--------------------------------------------------------------------|-------------------------------------------------------|
+| `charts-nav.yaml` | GitLab cloud native Helm Chart | `https://docs.gitlab.com/charts/` |
+| `default-nav.yaml` | GitLab Docs | `https://docs.gitlab.com/ee/` |
+| `omnibus-nav.yaml` | Omnibus GitLab Docs | `https://docs.gitlab.com/omnibus/` |
+| `runner-nav.yaml` | GitLab Runner Docs | `https://docs.gitlab.com/runner/` |
+
## Adding new items
All new pages need a new navigation item. Without a navigation, the page becomes "orphaned". That
@@ -98,7 +114,7 @@ for clarity.
To see the improvements planned, check the
[global nav epic](https://gitlab.com/groups/gitlab-com/-/epics/21).
-CAUTION: **Attention!**
+NOTE: **Note:**
**Do not** [add items](#adding-new-items) to the global nav without
the consent of one of the technical writers.
@@ -275,7 +291,7 @@ and the following syntax rules.
an "information" icon on the nav to make the user aware that the feature is
EE-only.
-DANGER: **Important!**
+CAUTION: **Caution:**
All links present on the data file must end in `.html`, not `.md`. Do not
start any relative link with a forward slash `/`.
@@ -290,7 +306,7 @@ Examples:
docs:
- doc_title: Service Desk
doc_url: 'user/project/service_desk.html'
- ee_only: true
+ ee_only: false
# note that the URL above ends in html and, as the
# document is EE-only, the attribute ee_only is set to true.
```
diff --git a/doc/development/documentation/site_architecture/index.md b/doc/development/documentation/site_architecture/index.md
index 942b202a3ec..60e6d4bcb13 100644
--- a/doc/development/documentation/site_architecture/index.md
+++ b/doc/development/documentation/site_architecture/index.md
@@ -111,7 +111,7 @@ located in the [Dockerfiles directory](https://gitlab.com/gitlab-org/gitlab-docs
If you need to rebuild the Docker images immediately (must have maintainer level permissions):
-CAUTION: **Caution**
+CAUTION: **Caution:**
If you change the dockerfile configuration and rebuild the images, you can break the master
pipeline in the main `gitlab` repository as well as in `gitlab-docs`. Create an image with
a different name first and test it to ensure you do not break the pipelines.
@@ -152,9 +152,9 @@ Suppose we have the `content/_data/versions.yaml` file with the content:
```yaml
versions:
-- 10.6
-- 10.5
-- 10.4
+ - 10.6
+ - 10.5
+ - 10.4
```
We can then loop over the `versions` array with something like:
diff --git a/doc/development/documentation/structure.md b/doc/development/documentation/structure.md
index eadcedfaac0..4cfc57aa57b 100644
--- a/doc/development/documentation/structure.md
+++ b/doc/development/documentation/structure.md
@@ -20,7 +20,7 @@ Every feature or use case document should include the following content in the f
with exceptions and details noted below and in the template included on this page.
- **Title**: Top-level heading with the feature name, or a use case name, which would start with
- a verb, like Configuring, Enabling, and so on.
+ a verb, like "Configure", "Enable", and so on.
- **Introduction**: A couple sentences about the subject matter and what's to be found
on this page. Describe what the feature or topic is, what it does, and in what context it should
be used. There is no need to add a title called "Introduction" or "Overview," because people rarely
@@ -47,10 +47,13 @@ When done, remove all of this commented-out text, except a commented-out Trouble
which, if empty, can be left in place to encourage future use.-->
---
description: "Short document description." # Up to ~200 chars long. They will be displayed in Google Search snippets. It may help to write the page intro first, and then reuse it here.
+stage: "Add the stage name here, and remove the quotation marks"
+group: "Add the group name here, and remove the quotation marks"
+info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers
---
# Feature Name or Use Case Name **[TIER]** (1)
-<!--If writing about a use case, drop the tier, and start with a verb, e.g. 'Configuring', 'Implementing', + the goal/scenario-->
+<!--If writing about a use case, drop the tier, and start with a verb, e.g. "Configure", "Implement", + the goal/scenario-->
<!--For pages on newly introduced features, add the following line. If only some aspects of the feature have been introduced, specify what parts of the feature.-->
> [Introduced](link_to_issue_or_mr) in GitLab (Tier) X.Y (2).
@@ -99,12 +102,12 @@ Larger instruction sets may have subsections covering specific phases of the pro
Where appropriate, provide examples of code or configuration files to better clarify intended usage.
- Write a step-by-step guide, with no gaps between the steps.
-- Include example code or configurations as part of the relevant step. Use appropriate markdown to [wrap code blocks with syntax highlighting](../../user/markdown.md#colored-code-and-syntax-highlighting).
+- Include example code or configurations as part of the relevant step. Use appropriate Markdown to [wrap code blocks with syntax highlighting](../../user/markdown.md#colored-code-and-syntax-highlighting).
- Start with an h2 (`##`), break complex steps into small steps using
subheadings h3 > h4 > h5 > h6. _Never skip a hierarchy level, such
as h2 > h4_, as it will break the TOC and may affect the breadcrumbs.
- Use short and descriptive headings (up to ~50 chars). You can use one
-single heading like `## Configuring X` for instructions when the feature
+single heading like `## Configure X` for instructions when the feature
is simple and the document is short.
<!-- ## Troubleshooting
diff --git a/doc/development/documentation/styleguide.md b/doc/development/documentation/styleguide.md
index 9a93dbf4f94..f2c90e71bd5 100644
--- a/doc/development/documentation/styleguide.md
+++ b/doc/development/documentation/styleguide.md
@@ -88,7 +88,7 @@ New information that would be useful toward the future usage or troubleshooting
The more we reflexively add useful information to the docs, the more (and more successfully) the docs will be used to efficiently accomplish tasks and solve problems.
-If you have questions when considering, authoring, or editing docs, ask the Technical Writing team on Slack in `#docs` or in GitLab by mentioning the writer for the applicable [DevOps stage](https://about.gitlab.com/handbook/product/categories/#devops-stages). Otherwise, forge ahead with your best effort. It does not need to be perfect; the team is happy to review and improve upon your content. Please review the [Documentation guidelines](index.md) before you begin your first documentation MR.
+If you have questions when considering, authoring, or editing docs, ask the Technical Writing team on Slack in `#docs` or in GitLab by mentioning the writer for the applicable [DevOps stage](https://about.gitlab.com/handbook/product/product-categories/#devops-stages). Otherwise, forge ahead with your best effort. It does not need to be perfect; the team is happy to review and improve upon your content. Please review the [Documentation guidelines](index.md) before you begin your first documentation MR.
Having a knowledge base in any form that is separate from the documentation would be against the docs-first methodology because the content would overlap with the documentation.
@@ -325,9 +325,22 @@ tenses, words, and phrases:
- Avoid using the word *currently* when talking about the product or its
features. The documentation describes the product as it is, and not as it
will be at some indeterminate point in the future.
+- Avoid the using the word *scalability* with increasing GitLab's performance
+ for additional users. Using the words *scale* or *scaling* in other cases is
+ acceptable, but references to increasing GitLab's performance for additional
+ users should direct readers to the GitLab
+ [reference architectures](../../administration/reference_architectures/index.md)
+ page.
+- Avoid all forms of the phrases *high availability* and *HA*, and instead
+ direct readers to the GitLab [reference architectures](../../administration/reference_architectures/index.md)
+ for information about configuring GitLab to have the performance needed for
+ additional users over time.
- Don't use profanity or obscenities. Doing so may negatively affect other
users and contributors, which is contrary to GitLab's value of
- [diversity and inclusion](https://about.gitlab.com/handbook/values/#diversity-inclusion).
+ [Diversity, Inclusion, and Belonging](https://about.gitlab.com/handbook/values/#diversity-inclusion).
+- Avoid the use of [racially-insensitive terminology or phrases](https://www.marketplace.org/2020/06/17/tech-companies-update-language-to-avoid-offensive-terms/). For example:
+ - Use *primary* and *secondary* for database and server relationships.
+ - Use *allowlist* and *denylist* to describe access control lists.
### Word usage clarifications
@@ -662,7 +675,7 @@ For other punctuation rules, please refer to the
- Avoid adding things that show ephemeral statuses. For example, if a feature is
considered beta or experimental, put this information in a note, not in the heading.
- When introducing a new document, be careful for the headings to be
- grammatically and syntactically correct. Mention an [assigned technical writer (TW)](https://about.gitlab.com/handbook/product/categories/)
+ grammatically and syntactically correct. Mention an [assigned technical writer (TW)](https://about.gitlab.com/handbook/product/product-categories/)
for review.
This is to ensure that no document with wrong heading is going
live without an audit, thus preventing dead links and redirection issues when
@@ -750,6 +763,15 @@ _Internal_ refers to documentation in the same project. When linking to document
separate projects (for example, linking to Omnibus docs from GitLab docs), you must use absolute
URLs.
+Do not use absolute URLs like `https://docs.gitlab.com/ee/index.html` to crosslink
+to other docs within the same project. Use relative links to the file, like `../index.md`. (These are converted to HTML when the site is rendered.)
+
+Relative linking enables crosslinks to work:
+
+- in Review Apps, local previews, and `/help`.
+- when working on the docs locally, so you can verify that they work as early as possible in the process.
+- within the GitLab UI when browsing doc files in their respective repositories. For example, the links displayed at `https://gitlab.com/gitlab-org/gitlab/-/blob/master/doc/README.md`.
+
To link to internal documentation:
- Use relative links to Markdown files in the same repository.
@@ -778,7 +800,7 @@ To link to internal documentation:
- `../../issues/tags.md`
- `../../issues/tags.md#stages`
-NOTE: **Note**:
+NOTE: **Note:**
Using the Markdown extension is necessary for the [`/help`](index.md#gitlab-help) section of GitLab.
### Links to external documentation
@@ -839,19 +861,42 @@ To indicate the steps of navigation through the UI:
## Images
+Images, including screenshots, can help a reader better understand a concept.
+However, they can be hard to maintain, and should be used sparingly.
+
+Before including an image in the documentation, ensure it provides value to the reader.
+
+### Capture the image
+
+Use images to help the reader understand where they are in a process, or how they need to
+interact with the application.
+
+When you take screenshots:
+
+- *Capture the most relevant area of the page.* Don't include unnecessary white
+ space or areas of the page that don't help illustrate your point. Also, don't
+ include the entire page if you don't have to, but also ensure the image
+ contains enough information to allow the user to determine where things are.
+- *Be consistent.* Find a browser window size that works for you that also
+ displays all areas of the product, including the left navigation (usually >
+ 1200px wide). For consistency, use this browser window size for your
+ screenshots by installing a browser extension for setting a window to a
+ specific size (for example,
+ [Window Resizer](https://chrome.google.com/webstore/detail/window-resizer/kkelicaakdanhinjdeammmilcgefonfh/related?hl=en)
+ for Google Chrome).
+
+### Save the image
+
+- Save the image with a lowercase file name that is descriptive of the feature
+ or concept in the image. If the image is of the GitLab interface, append the
+ GitLab version to the file name, based on the following format:
+ `image_name_vX_Y.png`. For example, for a screenshot taken from the pipelines
+ page of GitLab 11.1, a valid name is `pipelines_v11_1.png`. If you're adding an
+ illustration that doesn't include parts of the user interface, add the release
+ number corresponding to the release the image was added to; for an MR added to
+ 11.1's milestone, a valid name for an illustration is `devops_diagram_v11_1.png`.
- Place images in a separate directory named `img/` in the same directory where
the `.md` document that you're working on is located.
-- Images should have a specific, non-generic name that will
- differentiate and describe them properly.
-- For screenshots of GitLab software, append the GitLab version the screenshot was taken from to the
- file name. Use the following format: `image_name_vX_Y.png`.
-- For example, for a screenshot taken from the pipelines page of
- GitLab 11.1, a valid name is `pipelines_v11_1.png`. If you're
- adding an illustration that does not include parts of the UI,
- add the release number corresponding to the release the image
- was added to. Example, for an MR added to 11.1's milestone,
- a valid name for an illustration is `devops_diagram_v11_1.png`.
-- Keep all file names in lower case.
- Consider using PNG images instead of JPEG.
- [Compress all PNG images](#compress-images).
- Compress gifs with <https://ezgif.com/optimize> or similar tool.
@@ -860,15 +905,20 @@ To indicate the steps of navigation through the UI:
- Max image size: 100KB (gifs included).
- See also how to link and embed [videos](#videos) to illustrate the docs.
-Inside the document:
+### Add the image link to content
+
+The Markdown code for including an image in a document is:
+`![Image description which will be the alt tag](img/document_image_title_vX_Y.png)`
+
+The image description is the alt text for the rendered image on the docs site.
+For accessibility and SEO, use [descriptions](https://webaim.org/techniques/alttext/)
+that:
+
+- Are accurate, succinct, and unique.
+- Don't use *image of …* or *graphic of…* to describe the image.
-- The Markdown way of using an image inside a document is:
- `![Proper description what the image is about](img/document_image_title_vX_Y.png)`
-- Always use a proper description for what the image is about. That way, when a
- browser fails to show the image, this text will be used as an alternative
- description.
-- If a heading is placed right after an image, always add three dashes (`---`)
- between the image and the heading.
+Also, if a heading immediately follows an image, be sure to add three dashes
+(`---`) between the image and the heading.
### Remove image shadow
@@ -1133,8 +1183,8 @@ The following are examples of source Markdown for menu items with their publishe
Whenever you need to call special attention to particular sentences,
use the following markup for highlighting.
-_Note that the alert boxes only work for one paragraph only. Multiple paragraphs,
-lists, headers and so on, will not render correctly. For multiple lines, use blockquotes instead._
+Note that the alert boxes only work for one paragraph only. Multiple paragraphs,
+lists, headers and so on, will not render correctly. For multiple lines, use [blockquotes](#blockquotes) instead.
Alert boxes only render on the GitLab Docs site (<https://docs.gitlab.com>).
Within GitLab itself, they will appear as plain Markdown text (like the examples
@@ -1271,20 +1321,20 @@ The following are styles to follow when describing UI elements on a screen:
### Verbs for UI elements
-The following are recommended verbs for specific uses.
+The following are recommended verbs for specific uses with UI elements:
-| Recommended | Used for | Alternatives |
-|:------------|:---------------------------|:---------------------------|
-| "click" | buttons, links, menu items | "hit", "press", "select" |
-| "check" | checkboxes | "enable", "click", "press" |
-| "select" | dropdowns | "pick" |
-| "expand" | expandable sections | "open" |
+| Recommended | Used for | Replaces |
+|:--------------------|:---------------------------|:---------------------------|
+| *click* | buttons, links, menu items | "hit", "press", "select" |
+| *select* or *clear* | checkboxes | "enable", "click", "press" |
+| *select* | dropdowns | "pick" |
+| *expand* | expandable sections | "open" |
### Other Verbs
-| Recommended | Used for | Alternatives |
-|:------------|:--------------------------------|:-------------------|
-| "go" | making a browser go to location | "navigate", "open" |
+| Recommended | Used for | Replaces |
+|:------------|:--------------------------------|:----------------------|
+| *go to* | making a browser go to location | "navigate to", "open" |
## GitLab versions and tiers
@@ -1296,6 +1346,11 @@ Tagged and released versions of GitLab documentation are available:
The version introducing a new feature is added to the top of the topic in the documentation to provide
a helpful link back to how the feature was developed.
+TIP: **Tip:**
+Whenever you have documentation related to the `gitlab.rb` file, you're working with a self-managed installation.
+The section or page is therefore likely to apply only to self-managed instances.
+If so, the relevant "`TIER` ONLY" [Product badge](#product-badges) should be included at the highest applicable heading level.
+
### Text for documentation requiring version text
- For features that need to declare the GitLab version that the feature was introduced. Text similar
@@ -1327,6 +1382,22 @@ a helpful link back to how the feature was developed.
> - [Moved](<link-to-issue>) to GitLab Core in 12.0.
```
+- If a feature is deprecated, include a link to a replacement (when available):
+
+ ```markdown
+ > - [Deprecated](<link-to-issue>) in GitLab 11.3. Replaced by [meaningful text](<link-to-appropriate-documentation>).
+ ```
+
+ It's also acceptable to describe the replacement in surrounding text, if available.
+
+ If the deprecation is not obvious in existing text, you may want to include a warning such as:
+
+ ```markdown
+ CAUTION: **Warning:**
+ This feature was [deprecated](link-to-issue) in GitLab 12.3
+ and replaced by [Feature name](link-to-feature-documentation).
+ ```
+
NOTE: **Note:**
Version text must be on its own line and surrounded by blank lines to render correctly.
@@ -1389,7 +1460,7 @@ lines with an inserted line break. Splitting product or feature names across
lines makes searching for these items more difficult, and can cause problems if
names change.
-For example, the followng Markdown content is *not* formatted correctly:
+For example, the following Markdown content is *not* formatted correctly:
```markdown
When entering a product or feature name that includes a space (such as GitLab
@@ -1430,7 +1501,7 @@ For GitLab.com only tiers (when the feature is not available for self-managed in
The tier should be ideally added to headers, so that the full badge will be displayed.
However, it can be also mentioned from paragraphs, list items, and table cells. For these cases,
-the tier mention will be represented by an orange question mark that will show the tiers on hover.
+the tier mention will be represented by an orange info icon **(information)** that will show the tiers on hover.
Use the lowest tier at the page level, even if higher-level tiers exist on the page. For example, you might have a page that is marked as Starter but a section badged as Premium.
@@ -1542,6 +1613,9 @@ can facilitate this by making sure the troubleshooting content addresses:
1. How the user can confirm they have the problem.
1. Steps the user can take towards resolution of the problem.
+If the contents of each category can be summarized in one line and a list of steps aren't required, consider setting up a
+[table](#tables) with headers of *Problem* \| *Cause* \| *Solution* (or *Workaround* if the fix is temporary), or *Error message* \| *Solution*.
+
## Feature flags
Learn how to [document features deployed behind flags](feature_flags.md).
@@ -1607,6 +1681,13 @@ and email address of a user. Don't use real user information in API calls:
- **Names**: Use strings like `Example Username`. Alternatively, use diverse or non-gendered names with
common surnames, such as `Sidney Jones`, `Zhang Wei`. or `Maria Garcia`.
+### Fake URLs
+
+When including sample URLs in the documentation, use:
+
+- `example.com` when the domain name is generic.
+- `gitlab.example.com` when referring to self-managed instances of GitLab.
+
### Fake tokens
There may be times where a token is needed to demonstrate an API call using
diff --git a/doc/development/ee_features.md b/doc/development/ee_features.md
index e22e96b6f06..ac544113cbd 100644
--- a/doc/development/ee_features.md
+++ b/doc/development/ee_features.md
@@ -512,12 +512,12 @@ do that, so we'll follow regular object-oriented practices that we define the
interface first here.
For example, suppose we have a few more optional parameters for EE. We can move the
-parameters out of the `Grape::API` class to a helper module, so we can inject it
+parameters out of the `Grape::API::Instance` class to a helper module, so we can inject it
before it would be used in the class.
```ruby
module API
- class Projects < Grape::API
+ class Projects < Grape::API::Instance
helpers Helpers::ProjectsHelpers
end
end
@@ -578,7 +578,7 @@ class definition to make it easy and clear:
```ruby
module API
- class JobArtifacts < Grape::API
+ class JobArtifacts < Grape::API::Instance
# EE::API::JobArtifacts would override the following helpers
helpers do
def authorize_download_artifacts!
@@ -622,7 +622,7 @@ route. Something like this:
```ruby
module API
- class MergeRequests < Grape::API
+ class MergeRequests < Grape::API::Instance
helpers do
# EE::API::MergeRequests would override the following helpers
def update_merge_request_ee(merge_request)
@@ -691,7 +691,7 @@ least argument. We would approach this as follows:
```ruby
# api/merge_requests/parameters.rb
module API
- class MergeRequests < Grape::API
+ class MergeRequests < Grape::API::Instance
module Parameters
def self.update_params_at_least_one_of
%i[
@@ -707,7 +707,7 @@ API::MergeRequests::Parameters.prepend_if_ee('EE::API::MergeRequests::Parameters
# api/merge_requests.rb
module API
- class MergeRequests < Grape::API
+ class MergeRequests < Grape::API::Instance
params do
at_least_one_of(*Parameters.update_params_at_least_one_of)
end
@@ -900,27 +900,79 @@ export default {
</template>
```
-#### For JS code that is EE only, like props, computed properties, methods, etc, we will keep the current approach
+#### For JS code that is EE only, like props, computed properties, methods, etc
-- Since we [can't async load a mixin](https://github.com/vuejs/vue-loader/issues/418#issuecomment-254032223) we will use the [`ee_else_ce`](../development/ee_features.md#javascript-code-in-assetsjavascripts) alias we already have for webpack.
- - This means all the EE specific props, computed properties, methods, etc that are EE only should be in a mixin in the `ee/` folder and we need to create a CE counterpart of the mixin
+- Please do not use mixins unless ABSOLUTELY NECESSARY. Please try to find an alternative pattern.
-##### Example
+##### Reccomended alternative approach (named/scoped slots)
-```javascript
-import mixin from 'ee_else_ce/path/mixin';
+- We can use slots and/or scoped slots to achieve the same thing as we did with mixins. If you only need an EE component there is no need to create the CE component.
+
+1. First, we have a CE component that can render a slot incase we need EE template and functionality to be decorated on top of the CE base.
+
+```vue
+// ./ce/my_component.vue
+
+<script>
+export default {
+ props: {
+ tooltipDefaultText: {
+ type: String,
+ },
+ },
+ computed: {
+ tooltipText() {
+ return this.tooltipDefaultText || "5 issues please";
+ }
+ },
+}
+</script>
+
+<template>
+ <span v-gl-tooltip :title="tooltipText" class="ce-text">Community Edition Only Text</span>
+ <slot name="ee-specific-component">
+</template>
+```
+
+1. Next, we render the EE component, and inside of the EE component we render the CE component and add additional content in the slot.
-{
- mixins: [mixin]
+```vue
+// ./ee/my_component.vue
+
+<script>
+export default {
+ computed: {
+ tooltipText() {
+ if (this.weight) {
+ return "5 issues with weight 10";
+ }
+ }
+ },
+ methods: {
+ submit() {
+ // do something.
+ }
+ },
}
+</script>
+
+<template>
+ <my-component :tooltipDefaultText="tooltipText">
+ <template #ee-specific-component>
+ <span class="some-ee-specific">EE Specific Value</span>
+ <button @click="submit">Click Me</button>
+ </template>
+ </my-component>
+</template>
```
-- Computed Properties/methods and getters only used in the child import still need a counterpart in CE
+1. Finally, wherever the component is needed we can require it like so
+
+`import MyComponent from 'ee_else_ce/path/my_component'.vue`
-- For store modules, we will need a CE counterpart too.
-- You can see an MR with an example [here](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/9762)
+- this way the correct component will be included for either the ce or ee implementation
-#### `template` tag
+**For EE components that need different results for the same computed values, we can pass in props to the CE wrapper as seen in the example.**
- **EE Child components**
- Since we are using the async loading to check which component to load, we'd still use the component's name, check [this example](#child-component-only-used-in-ee).
diff --git a/doc/development/elasticsearch.md b/doc/development/elasticsearch.md
index 9f54386f1af..90debab3b5c 100644
--- a/doc/development/elasticsearch.md
+++ b/doc/development/elasticsearch.md
@@ -60,7 +60,7 @@ The `whitespace` tokenizer was selected in order to have more control over how t
Please see the `code` filter for an explanation on how tokens are split.
-NOTE: **Known Issues**:
+NOTE: **Note:**
Currently the [Elasticsearch code_analyzer doesn't account for all code cases](../integration/elasticsearch.md#known-issues).
#### `code_search_analyzer`
@@ -111,11 +111,8 @@ Patterns:
- `'"((?:\\"|[^"]|\\")*)"'`: captures terms inside quotes, removing the quotes
- `"'((?:\\'|[^']|\\')*)'"`: same as above, for single-quotes
- `'\.([^.]+)(?=\.|\s|\Z)'`: separate terms with periods in-between
-- `'\/?([^\/]+)(?=\/|\b)'`: separate path terms `like/this/one`
-
-#### `edgeNGram_filter`
-
-Uses an [Edge NGram token filter](https://www.elastic.co/guide/en/elasticsearch/reference/5.5/analysis-edgengram-tokenfilter.html) to allow inputs with only parts of a token to find the token. For example it would turn `glasses` into permutations starting with `gl` and ending with `glasses`, which would allow a search for "`glass`" to find the original token `glasses`
+- `'([\p{L}_.-]+)'`: some common chars in file names to keep the whole filename intact (eg. `my_file-ñame.txt`)
+- `'([\p{L}\d_]+)'`: letters, numbers and underscores are the most common tokens in programming. Always capture them greedily regardless of context.
## Gotchas
@@ -160,7 +157,8 @@ The global configurations per version are now in the `Elastic::(Version)::Config
### Creating new version of schema
-NOTE: **Note:** this is not applicable yet as multiple indices functionality is not fully implemented.
+NOTE: **Note:**
+This is not applicable yet as multiple indices functionality is not fully implemented.
Folders like `ee/lib/elastic/v12p1` contain snapshots of search logic from different versions. To keep a continuous Git history, the latest version lives under `ee/lib/elastic/latest`, but its classes are aliased under an actual version (e.g. `ee/lib/elastic/v12p3`). When referencing these classes, never use the `Latest` namespace directly, but use the actual version (e.g. `V12p3`).
@@ -222,6 +220,16 @@ be used both locally in development and on any deployed GitLab instance to
diagnose poor search performance. This will show the exact queries being made,
which is useful to diagnose why a search might be slow.
+### Correlation ID and X-Opaque-Id
+
+Our [correlation
+ID](./distributed_tracing.md#developer-guidelines-for-working-with-correlation-ids)
+is forwarded by all requests from Rails to Elasticsearch as the
+[`X-Opaque-Id`](https://www.elastic.co/guide/en/elasticsearch/reference/current/tasks.html#_identifying_running_tasks)
+header which allows us to track any
+[tasks](https://www.elastic.co/guide/en/elasticsearch/reference/current/tasks.html)
+in the cluster back the request in GitLab.
+
## Troubleshooting
### Getting `flood stage disk watermark [95%] exceeded`
diff --git a/doc/development/emails.md b/doc/development/emails.md
index 2477a51f78f..cf7f49ee834 100644
--- a/doc/development/emails.md
+++ b/doc/development/emails.md
@@ -115,7 +115,7 @@ Examples of valid email keys:
- `1234567890abcdef1234567890abcdef-unsubscribe` (unsubscribe from a conversation)
- `1234567890abcdef1234567890abcdef` (reply to a conversation)
-Please note that the action `-issue-` is used in GitLab Premium as the handler for the Service Desk feature.
+Please note that the action `-issue-` is used in GitLab as the handler for the Service Desk feature.
### Legacy format
@@ -127,7 +127,7 @@ These are the only valid legacy formats for an email handler:
- `namespace`
- `namespace+action`
-Please note that `path/to/project` is used in GitLab Premium as handler for the Service Desk feature.
+Please note that `path/to/project` is used in GitLab as the handler for the Service Desk feature.
---
diff --git a/doc/development/experiment_guide/index.md b/doc/development/experiment_guide/index.md
index f0e05139cba..b1a15c7749f 100644
--- a/doc/development/experiment_guide/index.md
+++ b/doc/development/experiment_guide/index.md
@@ -1,12 +1,12 @@
# Experiment Guide
-Experiments will be conducted by teams from the [Growth Section](https://about.gitlab.com/handbook/engineering/development/growth/) and are not tied to releases, because they will primarily target GitLab.com.
+Experiments can be conducted by any GitLab team, most often the teams from the [Growth Sub-department](https://about.gitlab.com/handbook/engineering/development/growth/). Experiments are not tied to releases because they will primarily target GitLab.com.
Experiments will be run as an A/B test and will be behind a feature flag to turn the test on or off. Based on the data the experiment generates, the team will decide if the experiment had a positive impact and will be the new default or rolled back.
-## Follow-up issue
+## Experiment tracking issue
-Each experiment requires a follow-up issue to resolve the experiment. Immediately after an experiment is deployed, the deadline of the issue needs to be set (this depends on the experiment but can be up to a few weeks in the future).
+Each experiment should have an [Experiment tracking](https://gitlab.com/groups/gitlab-org/-/issues?scope=all&utf8=%E2%9C%93&state=opened&label_name[]=growth%20experiment&search=%22Experiment+tracking%22) issue to track the experiment from roll-out through to cleanup/removal. Immediately after an experiment is deployed, the due date of the issue should be set (this depends on the experiment but can be up to a few weeks in the future).
After the deadline, the issue needs to be resolved and either:
- It was successful and the experiment will be the new default.
@@ -17,10 +17,10 @@ In either case, an outcome of the experiment should be posted to the issue with
## Code reviews
Since the code of experiments will not be part of the codebase for a long time and we want to iterate fast to retrieve data,the code quality of experiments might sometimes not fulfill our standards but should not negatively impact the availability of GitLab whether the experiment is running or not.
-Experiments will only be deployed to a fraction of users but we still want a flawless experience for those users. Therefore, experiments still require tests.
+Initially experiments will only be deployed to a fraction of users but we still want a flawless experience for those users. Therefore, experiments still require tests.
For reviewers and maintainers: if you find code that would usually not make it through the review, but is temporarily acceptable, please mention your concerns but note that it's not necessary to change.
-The author then adds a comment to this piece of code and adds a link to the issue that resolves the experiment.
+The author then adds a comment to this piece of code and adds a link to the issue that resolves the experiment. If the experiment is successful and becomes part of the product these follow up issues should be addressed.
## How to create an A/B test
@@ -42,7 +42,7 @@ The author then adds a comment to this piece of code and adds a link to the issu
- Use the experiment in a controller:
```ruby
- class RegistrationController < Applicationcontroller
+ class RegistrationController < ApplicationController
def show
# experiment_enabled?(:feature_name) is also available in views and helpers
if experiment_enabled?(:signup_flow)
diff --git a/doc/development/fe_guide/accessibility.md b/doc/development/fe_guide/accessibility.md
index 998c71135fb..669c93eb251 100644
--- a/doc/development/fe_guide/accessibility.md
+++ b/doc/development/fe_guide/accessibility.md
@@ -1,4 +1,4 @@
-# Accessibility
+# Accessibility & Readability
## Resources
diff --git a/doc/development/fe_guide/development_process.md b/doc/development/fe_guide/development_process.md
index 892e931bf5b..ff36b8b5c6a 100644
--- a/doc/development/fe_guide/development_process.md
+++ b/doc/development/fe_guide/development_process.md
@@ -73,8 +73,8 @@ With the purpose of being [respectful of others' time](https://about.gitlab.com/
- Before assigning to a maintainer, assign to a reviewer.
- If you assigned a merge request or pinged someone directly, be patient because we work in different timezones and asynchronously. Unless the merge request is urgent (like fixing a broken master), please don't DM or reassign the merge request before waiting for a 24-hour window.
- If you have a question regarding your merge request/issue, make it on the merge request/issue. When we DM each other, we no longer have a SSOT and [no one else is able to contribute](https://about.gitlab.com/handbook/values/#public-by-default).
-- When you have a big WIP merge request with many changes, you're advised to get the review started before adding/removing significant code. Make sure it is assigned well before the release cut-off, as the reviewer(s)/maintainer(s) would always prioritize reviewing finished MRs before WIP ones.
-- Make sure to remove the WIP title before the last round of review.
+- When you have a big **Draft** merge request with many changes, you're advised to get the review started before adding/removing significant code. Make sure it is assigned well before the release cut-off, as the reviewer(s)/maintainer(s) would always prioritize reviewing finished MRs before the **Draft** ones.
+- Make sure to remove the `Draft:` title before the last round of review.
### Share your work early
diff --git a/doc/development/fe_guide/frontend_faq.md b/doc/development/fe_guide/frontend_faq.md
index 4f814f3cdde..3c0845a9aaa 100644
--- a/doc/development/fe_guide/frontend_faq.md
+++ b/doc/development/fe_guide/frontend_faq.md
@@ -146,3 +146,20 @@ export const fetchFoos = ({ state }) => {
return axios.get(state.settings.fooPath);
};
```
+
+### 7. How can I test the production build locally?
+
+Sometimes it's necessary to test locally what the frontend production build would produce, to do so the steps are:
+
+1. Stop webpack: `gdk stop webpack`.
+1. Open `gitlab.yaml` located in your `gitlab` installation folder, scroll down to the `webpack` section and change `dev_server` to `enabled: false`.
+1. Run `yarn webpack-prod && gdk restart rails-web`.
+
+The production build takes a few minutes to be completed; any code change at this point will be
+displayed only after executing the item 3 above again.
+To return to the normal development mode:
+
+1. Open `gitlab.yaml` located in your `gitlab` installation folder, scroll down to the `webpack` section and change back `dev_server` to `enabled: true`.
+1. Run `yarn clean` to remove the production assets and free some space (optional).
+1. Start webpack again: `gdk start webpack`.
+1. Restart GDK: `gdk-restart rails-web`.
diff --git a/doc/development/fe_guide/graphql.md b/doc/development/fe_guide/graphql.md
index 191ebd2ff58..3d74ec94ae4 100644
--- a/doc/development/fe_guide/graphql.md
+++ b/doc/development/fe_guide/graphql.md
@@ -646,7 +646,7 @@ defaultClient.query({ query })
.then(result => console.log(result));
```
-When [using Vuex](#Using-with-Vuex), disable the cache when:
+When [using Vuex](#using-with-vuex), disable the cache when:
- The data is being cached elsewhere
- The use case does not need caching
diff --git a/doc/development/fe_guide/icons.md b/doc/development/fe_guide/icons.md
index 131324e6479..7078b5e9b2f 100644
--- a/doc/development/fe_guide/icons.md
+++ b/doc/development/fe_guide/icons.md
@@ -61,6 +61,7 @@ export default {
<gl-icon
name="issues"
:size="24"
+ class="class-name"
/>
</template>
```
@@ -68,7 +69,7 @@ export default {
- **name** Name of the Icon in the SVG Sprite ([Overview is available here](https://gitlab-org.gitlab.io/gitlab-svgs)).
- **size (optional)** Number value for the size which is then mapped to a specific CSS class
(Available Sizes: 8, 12, 16, 18, 24, 32, 48, 72 are mapped to `sXX` CSS classes)
-- **css-classes (optional)** Additional CSS Classes to add to the SVG tag.
+- **class (optional)** Additional CSS Classes to add to the SVG tag.
### Usage in HTML/JS
diff --git a/doc/development/fe_guide/index.md b/doc/development/fe_guide/index.md
index 8fd6ba459b9..3a2b3cac9bf 100644
--- a/doc/development/fe_guide/index.md
+++ b/doc/development/fe_guide/index.md
@@ -17,7 +17,9 @@ Working with our frontend assets requires Node (v10.13.0 or greater) and Yarn
For our currently-supported browsers, see our [requirements](../../install/requirements.md#supported-web-browsers).
-Use [BrowserStack](https://www.browserstack.com/) to test with our supported browsers. Login to BrowserStack with the credentials saved in GitLab's [shared 1Password account](https://about.gitlab.com/handbook/security/#1password-for-teams).
+Use [BrowserStack](https://www.browserstack.com/) to test with our supported browsers.
+Sign in to BrowserStack with the credentials saved in the **Engineering** vault of GitLab's
+[shared 1Password account](https://about.gitlab.com/handbook/security/#1password-guide).
## Initiatives
diff --git a/doc/development/fe_guide/tooling.md b/doc/development/fe_guide/tooling.md
index 585cd969c96..28deb7d95f9 100644
--- a/doc/development/fe_guide/tooling.md
+++ b/doc/development/fe_guide/tooling.md
@@ -4,6 +4,44 @@
We use ESLint to encapsulate and enforce frontend code standards. Our configuration may be found in the [`gitlab-eslint-config`](https://gitlab.com/gitlab-org/gitlab-eslint-config) project.
+### Yarn Script
+
+This section describes yarn scripts that are available to validate and apply automatic fixes to files using ESLint.
+
+To check all currently staged files (based on `git diff`) with ESLint, run the following script:
+
+```shell
+yarn eslint-staged
+```
+
+_A list of problems found will be logged to the console._
+
+To apply automatic ESLint fixes to all currently staged files (based on `git diff`), run the following script:
+
+```shell
+yarn eslint-staged-fix
+```
+
+_If manual changes are required, a list of changes will be sent to the console._
+
+To check **all** files in the repository with ESLint, run the following script:
+
+```shell
+yarn eslint
+```
+
+_A list of problems found will be logged to the console._
+
+To apply automatic ESLint fixes to **all** files in the repository, run the following script:
+
+```shell
+yarn eslint-fix
+```
+
+_If manual changes are required, a list of changes will be sent to the console._
+
+**Caution:** Limit use to global rule updates. Otherwise, the changes can lead to huge Merge Requests.
+
### Disabling ESLint in new files
Do not disable ESLint when creating new files. Existing files may have existing rules
@@ -56,13 +94,15 @@ When declaring multiple globals, always use one `/* global [name] */` line per v
## Formatting with Prettier
-Our code is automatically formatted with [Prettier](https://prettier.io) to follow our style guides. Prettier is taking care of formatting `.js`, `.vue`, and `.scss` files based on the standard prettier rules. You can find all settings for Prettier in `.prettierrc`.
+> Support for `.graphql` [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/227280) in GitLab 13.2.
+
+Our code is automatically formatted with [Prettier](https://prettier.io) to follow our style guides. Prettier is taking care of formatting `.js`, `.vue`, `.graphql`, and `.scss` files based on the standard prettier rules. You can find all settings for Prettier in `.prettierrc`.
### Editor
The easiest way to include prettier in your workflow is by setting up your preferred editor (all major editors are supported) accordingly. We suggest setting up prettier to run automatically when each file is saved. Find [here](https://prettier.io/docs/en/editors.html) the best way to set it up in your preferred editor.
-Please take care that you only let Prettier format the same file types as the global Yarn script does (`.js`, `.vue`, and `.scss`). In VSCode by example you can easily exclude file formats in your settings file:
+Please take care that you only let Prettier format the same file types as the global Yarn script does (`.js`, `.vue`, `.graphql`, and `.scss`). In VSCode by example you can easily exclude file formats in your settings file:
```json
"prettier.disableLanguages": [
@@ -131,6 +171,9 @@ To select Prettier as a formatter, add the following properties to your User or
},
"[vue]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
+ },
+ "[graphql]": {
+ "editor.defaultFormatter": "esbenp.prettier-vscode"
}
}
```
@@ -150,5 +193,8 @@ To automatically format your files with Prettier, add the following properties t
"[vue]": {
"editor.formatOnSave": true
},
+ "[graphql]": {
+ "editor.formatOnSave": true
+ },
}
```
diff --git a/doc/development/fe_guide/vue.md b/doc/development/fe_guide/vue.md
index 0d77e4d129b..2a0556c6cda 100644
--- a/doc/development/fe_guide/vue.md
+++ b/doc/development/fe_guide/vue.md
@@ -53,14 +53,14 @@ Be sure to read about [page-specific JavaScript](./performance.md#page-specific-
#### Providing data from HAML to JavaScript
-While mounting a Vue application may be a need to provide data from Rails to JavaScript.
-To do that, provide the data through `data` attributes in the HTML element and query them while mounting the application.
+While mounting a Vue application, you might need to provide data from Rails to JavaScript.
+To do that, you can use the `data` attributes in the HTML element and query them while mounting the application.
_Note:_ You should only do this while initializing the application, because the mounted element will be replaced with Vue-generated DOM.
The advantage of providing data from the DOM to the Vue instance through `props` in the `render` function
-instead of querying the DOM inside the main Vue component is that makes tests easier by avoiding the need to
-create a fixture or an HTML element in the unit test. See the following example:
+instead of querying the DOM inside the main Vue component is avoiding the need to create a fixture or an HTML element in the unit test,
+which will make the tests easier. See the following example:
```javascript
// haml
@@ -134,7 +134,7 @@ This approach has a few benefits:
intermediate components being aware of it (c.f. passing the flag down via
props).
- Good testability, since the flag can be provided to `mount`/`shallowMount`
- from `vue-test-utils` as easily as a prop.
+ from `vue-test-utils` simply as a prop.
```javascript
import { shallowMount } from '@vue/test-utils';
@@ -155,7 +155,7 @@ This folder holds all components that are specific of this new feature.
If you need to use or create a component that will probably be used somewhere
else, please refer to `vue_shared/components`.
-A good thumb rule to know when you should create a component is to think if
+A good rule of thumb to know when you should create a component is to think if
it will be reusable elsewhere.
For example, tables are used in a quite amount of places across GitLab, a table
@@ -321,7 +321,7 @@ We should verify an event has been fired by asserting against the result of the
## Vue.js Expert Role
-One should apply to be a Vue.js expert by opening an MR when the Merge Request's they create and review show:
+You should only apply to be a Vue.js expert when your own merge requests and your reviews show:
- Deep understanding of Vue and Vuex reactivity
- Vue and Vuex code are structured according to both official and our guidelines
diff --git a/doc/development/fe_guide/vuex.md b/doc/development/fe_guide/vuex.md
index e7be67b8da5..02387c15951 100644
--- a/doc/development/fe_guide/vuex.md
+++ b/doc/development/fe_guide/vuex.md
@@ -201,6 +201,72 @@ By following this pattern we guarantee:
1. All data in the application follows the same lifecycle pattern
1. Unit tests are easier
+#### Updating complex state
+
+Sometimes, especially when the state is complex, is really hard to traverse the state to precisely update what the mutation needs to update.
+Ideally a `vuex` state should be as normalized/decoupled as possible but this is not always the case.
+
+It's important to remember that the code is much easier to read and maintain when the `portion of the mutated state` is selected and mutated in the mutation itself.
+
+Given this state:
+
+```javascript
+ export default () => ({
+ items: [
+ {
+ id: 1,
+ name: 'my_issue',
+ closed: false,
+ },
+ {
+ id: 2,
+ name: 'another_issue',
+ closed: false,
+ }
+ ]
+});
+```
+
+It may be tempting to write a mutation like so:
+
+```javascript
+// Bad
+export default {
+ [types.MARK_AS_CLOSED](state, item) {
+ Object.assign(item, {closed: true})
+ }
+}
+```
+
+While this approach works it has several dependencies:
+
+- Correct selection of `item` in the component/action.
+- The `item` property is already declared in the `closed` state.
+ - A new `confidential` property would not be reactive.
+- Noting that `item` is referenced by `items`
+
+A mutation written like this is harder to maintain and more error prone. We should rather write a mutation like this:
+
+```javascript
+// Good
+export default {
+ [types.MARK_AS_CLOSED](state, itemId) {
+ const item = state.items.find(i => i.id == itemId);
+ Vue.set(item, 'closed', true)
+
+ state.items.splice(index, 1, item)
+ }
+}
+```
+
+This approach is better because:
+
+- It selects and updates the state in the mutation, which is more maintainable.
+- It has no external dependencies, if the correct `itemId` is passed the state is correctly updated.
+- It does not have reactivity caveats, as we generate a new `item` to avoid coupling to the initial state.
+
+A mutation written like this is easier to maintain. In addition, we avoid errors due to the limitation of the reactivity system.
+
### `getters.js`
Sometimes we may need to get derived state based on store state, like filtering for a specific prop.
diff --git a/doc/development/feature_categorization/index.md b/doc/development/feature_categorization/index.md
new file mode 100644
index 00000000000..bcd5e16cc2b
--- /dev/null
+++ b/doc/development/feature_categorization/index.md
@@ -0,0 +1,130 @@
+# Feature Categorization
+
+> [Introduced](https://gitlab.com/groups/gitlab-com/gl-infra/-/epics/269) in GitLab 13.2.
+
+Each Sidekiq worker, controller action, or (eventually) API endpoint
+must declare a `feature_category` attribute. This attribute maps each
+of these to a [feature
+category](https://about.gitlab.com/handbook/product/product-categories/). This
+is done for error budgeting, alert routing, and team attribution.
+
+The list of feature categories can be found in the file `config/feature_categories.yml`.
+This file is generated from the
+[`stages.yml`](https://gitlab.com/gitlab-com/www-gitlab-com/blob/master/data/stages.yml)
+data file used in the GitLab Handbook and other GitLab resources.
+
+## Updating `config/feature_categories.yml`
+
+Occasionally new features will be added to GitLab stages, groups, and
+product categories. When this occurs, you can automatically update
+`config/feature_categories.yml` by running
+`scripts/update-feature-categories`. This script will fetch and parse
+[`stages.yml`](https://gitlab.com/gitlab-com/www-gitlab-com/blob/master/data/stages.yml)
+and generate a new version of the file, which needs to be committed to
+the repository.
+
+The [Scalabilitity
+team](https://about.gitlab.com/handbook/engineering/infrastructure/team/scalability)
+currently maintains the `stages.yml` file. They will automatically be
+notified on Slack when the file becomes outdated.
+
+## Sidekiq workers
+
+The declaration uses the `feature_category` class method, as shown below.
+
+```ruby
+class SomeScheduledTaskWorker
+ include ApplicationWorker
+
+ # Declares that this worker is part of the
+ # `continuous_integration` feature category
+ feature_category :continuous_integration
+
+ # ...
+end
+```
+
+The feature categories specified using `feature_category` should be
+defined in
+[`config/feature_categories.yml`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/config/feature_categories.yml). If
+not, the specs will fail.
+
+### Excluding Sidekiq workers from feature categorization
+
+A few Sidekiq workers, that are used across all features, cannot be mapped to a
+single category. These should be declared as such using the `feature_category_not_owned!`
+declaration, as shown below:
+
+```ruby
+class SomeCrossCuttingConcernWorker
+ include ApplicationWorker
+
+ # Declares that this worker does not map to a feature category
+ feature_category_not_owned!
+
+ # ...
+end
+```
+
+## Rails controllers
+
+Specifying feature categories on controller actions can be done using
+the `feature_category` class method.
+
+A feature category can be specified on an entire controller
+using:
+
+```ruby
+class Projects::MergeRequestsController < ApplicationController
+ feature_category :source_code_management
+end
+```
+
+The feature category can be limited to a list of actions using the
+`only` argument, actions can be excluded using the `except` argument.
+
+```ruby
+class Projects::MergeRequestsController < ApplicationController
+ feature_category :code_testing, only: [:metrics_reports]
+ feature_category :source_code_management, except: [:test_reports, :coverage_reports]
+end
+```
+
+`except` and `only` arguments can not be combined.
+
+When specifying `except` all other actions will get the specified
+category assigned.
+
+The assignment can also be scoped using `if` and `unless` procs:
+
+```ruby
+class Projects::MergeRequestsController < ApplicationController
+ feature_category :source_code_management,
+ unless: -> (action) { action.include?("reports") }
+ if: -> (action) { action.include?("widget") }
+end
+```
+
+In this case, both procs need to be satisfied for the action to get
+the category assigned.
+
+### Excluding controller actions from feature categorization
+
+In the rare case an action cannot be tied to a feature category this
+can be done using the `not_owned` feature category.
+
+```ruby
+class Admin::LogsController < ApplicationController
+ feature_category :not_owned
+end
+```
+
+### Ensuring feature categories are valid
+
+The `spec/controllers/every_controller_spec.rb` will iterate over all
+defined routes, and check the controller to see if a category is
+assigned to all actions.
+
+The spec also validates if the used feature categories are known. And
+if the actions used in `only` and `except` configuration still exist
+as routes.
diff --git a/doc/development/feature_flags/controls.md b/doc/development/feature_flags/controls.md
index d4d1ec74591..9e7ce74cc0c 100644
--- a/doc/development/feature_flags/controls.md
+++ b/doc/development/feature_flags/controls.md
@@ -210,11 +210,20 @@ actors.
Feature.enabled?(:some_feature, group)
```
-NOTE:
-
+NOTE: **Note:**
**Percentage of time** rollout is not a good idea if what you want is to make sure a feature
is always on or off to the users. In that case, **Percentage of actors** rollout is a better method.
+Lastly, to verify that the feature is deemed stable in as many cases as possible,
+you should fully roll out the feature by enabling the flag **globally** by running:
+
+```shell
+/chatops run feature set some_feature true
+```
+
+This changes the feature flag state to be **enabled** always, which overrides the
+existing gates (e.g. `--group=gitlab-org`) in the above processes.
+
### Feature flag change logging
Any feature flag change that affects GitLab.com (production) will
diff --git a/doc/development/feature_flags/development.md b/doc/development/feature_flags/development.md
index a44bc70439e..0b918478668 100644
--- a/doc/development/feature_flags/development.md
+++ b/doc/development/feature_flags/development.md
@@ -32,8 +32,8 @@ request removing the feature flag or the merge request where the default value o
the feature flag is set to true. If the feature contains any DB migration it
should include a changelog entry for DB changes.
-In the rare case that you need the feature flag to be on automatically, use
-`default_enabled: true` when checking:
+If you need the feature flag to be on automatically, use `default_enabled: true`
+when checking:
```ruby
Feature.enabled?(:feature_flag, project, default_enabled: true)
diff --git a/doc/development/feature_flags/index.md b/doc/development/feature_flags/index.md
index bd0bd8f2018..0c1e34edc6f 100644
--- a/doc/development/feature_flags/index.md
+++ b/doc/development/feature_flags/index.md
@@ -1,6 +1,6 @@
# Feature flags in development of GitLab
-[Feature Flags](../../user/project/operations/feature_flags.md)
+[Feature Flags](../../operations/feature_flags.md)
can be used to gradually roll out changes, be
it a new feature, or a performance improvement. By using feature flags, we can
comfortably measure the impact of our changes, while still being able to easily
diff --git a/doc/development/feature_flags/process.md b/doc/development/feature_flags/process.md
index 57360f5b771..b053838964b 100644
--- a/doc/development/feature_flags/process.md
+++ b/doc/development/feature_flags/process.md
@@ -4,7 +4,7 @@
This document only covers feature flags used in the development of GitLab
itself. Feature flags in deployed user applications can be found at
-[Feature Flags feature documentation](../../user/project/operations/feature_flags.md).
+[Feature Flags feature documentation](../../operations/feature_flags.md).
## Feature flags in GitLab development
@@ -21,6 +21,19 @@ should be leveraged:
- Merge requests that make changes hidden behind a feature flag, or remove an
existing feature flag because a feature is deemed stable must have the
~"feature flag" label assigned.
+- When development of a feature will be spread across multiple merge
+ requests, you can use the following workflow:
+
+ 1. Introduce a feature flag which is **off** by default, in the first merge request.
+ 1. Submit incremental changes via one or more merge requests, ensuring that any
+ new code added can only be reached if the feature flag is **on**.
+ You can keep the feature flag enabled on your local GDK during development.
+ 1. When the feature is ready to be tested, enable the feature flag for
+ a specific project and ensure that there are no issues with the implementation.
+ 1. When the feature is ready to be announced, create a merge request that adds
+ documentation about the feature, including [documentation for the feature flag itself](../documentation/feature_flags.md),
+ and a changelog entry. In the same merge request either flip the feature flag to
+ be **on by default** or remove it entirely in order to enable the new behavior.
One might be tempted to think that feature flags will delay the release of a
feature by at least one month (= one release). This is not the case. A feature
@@ -29,6 +42,8 @@ flag does not have to stick around for a specific amount of time
is deemed stable. Stable means it works on GitLab.com without causing any
problems, such as outages.
+Please also read the [development guide for feature flags](development.md).
+
### When to use feature flags
Starting with GitLab 11.4, developers are required to use feature flags for
@@ -56,7 +71,9 @@ absolutely no way to use the feature until it is enabled.
In order to build a final release and present the feature for self-managed
users, the feature flag should be at least defaulted to **on**. If the feature
is deemed stable and there is confidence that removing the feature flag is safe,
-consider removing the feature flag altogether.
+consider removing the feature flag altogether. It's _strongly_ recommended that
+the feature flag is [enabled **globally** on **production**](./controls.md#enabling-a-feature-for-gitlabcom) for **at least one day**
+before making this decision. Unexpected bugs are sometimes discovered during this period.
The process for enabling features that are disabled by default can take 5-6 days
from when the merge request is first reviewed to when the change is deployed to
@@ -71,7 +88,7 @@ Take into consideration that such action can make the feature available on
GitLab.com shortly after the change to the feature flag is merged.
Changing the default state or removing the feature flag has to be done before
-the 22nd of the month, _at least_ 2 working days before, in order for the change
+the 22nd of the month, _at least_ 3-4 working days before, in order for the change
to be included in the final self-managed release.
In addition to this, the feature behind feature flag should:
diff --git a/doc/development/foreign_keys.md b/doc/development/foreign_keys.md
index 508e5665f08..8a81dc158a7 100644
--- a/doc/development/foreign_keys.md
+++ b/doc/development/foreign_keys.md
@@ -35,6 +35,17 @@ When adding a foreign key in PostgreSQL the column is not indexed automatically,
thus you must also add a concurrent index. Not doing so will result in cascading
deletes being very slow.
+## Naming foreign keys
+
+By default Ruby on Rails uses the `_id` suffix for foreign keys. So we should
+only use this suffix for associations between two tables. If you want to
+reference an ID on a third party platform the `_xid` suffix is recommended.
+
+The spec `spec/db/schema_spec.rb` will test if all columns with the `_id` suffix
+have a foreign key constraint. So if that spec fails, don't add the column to
+`IGNORED_FK_COLUMNS`, but instead add the FK constraint, or consider naming it
+differently.
+
## Dependent Removals
Don't define options such as `dependent: :destroy` or `dependent: :delete` when
diff --git a/doc/development/geo/framework.md b/doc/development/geo/framework.md
index 85fecc41cfb..de4d6fb869d 100644
--- a/doc/development/geo/framework.md
+++ b/doc/development/geo/framework.md
@@ -1,11 +1,13 @@
# Geo self-service framework (alpha)
-NOTE: **Note:** This document might be subjected to change. It's a
+NOTE: **Note:**
+This document might be subjected to change. It's a
proposal we're working on and once the implementation is complete this
documentation will be updated. Follow progress in the
[epic](https://gitlab.com/groups/gitlab-org/-/epics/2161).
-NOTE: **Note:** The Geo self-service framework is currently in
+NOTE: **Note:**
+The Geo self-service framework is currently in
alpha. If you need to replicate a new data type, reach out to the Geo
team to discuss the options. You can contact them in `#g_geo` on Slack
or mention `@geo-team` in the issue or merge request.
@@ -178,13 +180,17 @@ For example, to add support for files referenced by a `Widget` model with a
mount_uploader :file, WidgetUploader
+ def self.replicables_for_geo_node
+ # Should be implemented. The idea of the method is to restrict
+ # the set of synced items depending on synchronization settings
+ end
...
end
```
1. Create `ee/app/replicators/geo/widget_replicator.rb`. Implement the
`#carrierwave_uploader` method which should return a `CarrierWave::Uploader`.
- And implement the private `#model` method to return the `Widget` class.
+ And implement the class method `.model` to return the `Widget` class.
```ruby
# frozen_string_literal: true
@@ -193,14 +199,12 @@ For example, to add support for files referenced by a `Widget` model with a
class WidgetReplicator < Gitlab::Geo::Replicator
include ::Geo::BlobReplicatorStrategy
- def carrierwave_uploader
- model_record.file
+ def self.model
+ ::Widget
end
- private
-
- def model
- ::Widget
+ def carrierwave_uploader
+ model_record.file
end
end
end
@@ -215,7 +219,7 @@ For example, to add support for files referenced by a `Widget` model with a
require 'spec_helper'
- describe Geo::WidgetReplicator do
+ RSpec.describe Geo::WidgetReplicator do
let(:model_record) { build(:widget) }
it_behaves_like 'a blob replicator'
@@ -231,20 +235,32 @@ For example, to add support for files referenced by a `Widget` model with a
class CreateWidgetRegistry < ActiveRecord::Migration[6.0]
DOWNTIME = false
- def change
- create_table :widget_registry, id: :serial, force: :cascade do |t|
- t.integer :widget_id, null: false
- t.integer :state, default: 0, null: false
- t.integer :retry_count, default: 0
- t.string :last_sync_failure, limit: 255
- t.datetime_with_timezone :retry_at
- t.datetime_with_timezone :last_synced_at
- t.datetime_with_timezone :created_at, null: false
-
- t.index :widget_id, name: :index_widget_registry_on_repository_id, using: :btree
- t.index :retry_at, name: :index_widget_registry_on_retry_at, using: :btree
- t.index :state, name: :index_widget_registry_on_state, using: :btree
+ disable_ddl_transaction!
+
+ def up
+ unless table_exists?(:widget_registry)
+ ActiveRecord::Base.transaction do
+ create_table :widget_registry, id: :bigserial, force: :cascade do |t|
+ t.integer :widget_id, null: false
+ t.integer :state, default: 0, null: false, limit: 2
+ t.integer :retry_count, default: 0, limit: 2
+ t.text :last_sync_failure
+ t.datetime_with_timezone :retry_at
+ t.datetime_with_timezone :last_synced_at
+ t.datetime_with_timezone :created_at, null: false
+
+ t.index :widget_id
+ t.index :retry_at
+ t.index :state
+ end
+ end
end
+
+ add_text_limit :widget_registry, :last_sync_failure, 255
+ end
+
+ def down
+ drop_table :widget_registry
end
end
```
@@ -255,21 +271,28 @@ For example, to add support for files referenced by a `Widget` model with a
# frozen_string_literal: true
class Geo::WidgetRegistry < Geo::BaseRegistry
- include Geo::StateMachineRegistry
+ include Geo::ReplicableRegistry
+ MODEL_CLASS = ::Widget
MODEL_FOREIGN_KEY = :widget_id
belongs_to :widget, class_name: 'Widget'
end
```
+ The method `has_create_events?` should return `true` in most of the cases.
+ However, if the entity you add doesn't have the create event, don't add the
+ method at all.
+
+1. Update `REGISTRY_CLASSES` in `ee/app/workers/geo/secondary/registry_consistency_worker.rb`.
+
1. Create `ee/spec/factories/geo/widget_registry.rb`:
```ruby
# frozen_string_literal: true
FactoryBot.define do
- factory :widget_registry, class: 'Geo::WidgetRegistry' do
+ factory :geo_widget_registry, class: 'Geo::WidgetRegistry' do
widget
state { Geo::WidgetRegistry.state_value(:pending) }
@@ -301,14 +324,18 @@ For example, to add support for files referenced by a `Widget` model with a
require 'spec_helper'
- describe Geo::WidgetRegistry, :geo, type: :model do
- let_it_be(:registry) { create(:widget_registry) }
+ RSpec.describe Geo::WidgetRegistry, :geo, type: :model do
+ let_it_be(:registry) { create(:geo_widget_registry) }
specify 'factory is valid' do
expect(registry).to be_valid
end
include_examples 'a Geo framework registry'
+
+ describe '.find_registry_differences' do
+ ... # To be implemented
+ end
end
```
@@ -360,36 +387,58 @@ Widgets should now be replicated by Geo!
end
```
-1. Add fields `widget_count`, `widget_checksummed_count`, `widget_checksum_failed_count`,
- `widget_synced_count` and `widget_failed_count`
- to `GeoNodeStatus#RESOURCE_STATUS_FIELDS` array in `ee/app/models/geo_node_status.rb`.
+To do: Add verification on secondaries. This should be done as part of
+[Geo: Self Service Framework - First Implementation for Package File verification](https://gitlab.com/groups/gitlab-org/-/epics/1817)
+
+Widgets should now be verified by Geo!
+
+#### Metrics
+
+Metrics are gathered by `Geo::MetricsUpdateWorker`, persisted in
+`GeoNodeStatus` for display in the UI, and sent to Prometheus.
+
+1. Add fields `widget_count`, `widget_checksummed_count`,
+ `widget_checksum_failed_count`, `widget_synced_count`,
+ `widget_failed_count`, and `widget_registry_count` to
+ `GeoNodeStatus#RESOURCE_STATUS_FIELDS` array in
+ `ee/app/models/geo_node_status.rb`.
1. Add the same fields to `GeoNodeStatus#PROMETHEUS_METRICS` hash in
`ee/app/models/geo_node_status.rb`.
1. Add the same fields to `Sidekiq metrics` table in
`doc/administration/monitoring/prometheus/gitlab_metrics.md`.
-1. Add the same fields to `GET /geo_nodes/status` example response in `doc/api/geo_nodes.md`.
-1. Modify `GeoNodeStatus#load_verification_data` to make sure the fields mantioned above
- are set:
+1. Add the same fields to `GET /geo_nodes/status` example response in
+ `doc/api/geo_nodes.md`.
+1. Add the same fields to `ee/spec/models/geo_node_status_spec.rb` and
+ `ee/spec/factories/geo_node_statuses.rb`.
+1. Set `widget_count` in `GeoNodeStatus#load_data_from_current_node`:
+
+ ```ruby
+ self.widget_count = Geo::WidgetReplicator.primary_total_count
+ ```
+
+1. Add `GeoNodeStatus#load_widgets_data` to set `widget_synced_count`,
+ `widget_failed_count`, and `widget_registry_count`:
```ruby
- self.widget_count = Geo::WidgetReplicator.model.count
- self.widget_checksummed_count = Geo::WidgetReplicator.checksummed.count
- self.widget_checksum_failed_count = Geo::WidgetReplicator.checksum_failed.count
+ def load_widget_data
self.widget_synced_count = Geo::WidgetReplicator.synced_count
self.widget_failed_count = Geo::WidgetReplicator.failed_count
+ self.widget_registry_count = Geo::WidgetReplicator.registry_count
+ end
```
-1. Make sure `Widget` model has `checksummed` and `checksum_failed` scopes.
-1. Update `ee/spec/fixtures/api/schemas/public_api/v4/geo_node_status.json` with new fields.
-1. Update `GeoNodeStatus#PROMETHEUS_METRICS` hash in `ee/app/models/geo_node_status.rb` with new fields.
-1. Update `Sidekiq metrics` table in `doc/administration/monitoring/prometheus/gitlab_metrics.md` with new fields.
-1. Update `GET /geo_nodes/status` example response in `doc/api/geo_nodes.md` with new fields.
-1. Update `ee/spec/models/geo_node_status_spec.rb` and `ee/spec/factories/geo_node_statuses.rb` with new fields.
+1. Call `GeoNodeStatus#load_widgets_data` in
+ `GeoNodeStatus#load_secondary_data`.
-To do: Add verification on secondaries. This should be done as part of
-[Geo: Self Service Framework - First Implementation for Package File verification](https://gitlab.com/groups/gitlab-org/-/epics/1817)
+1. Set `widget_checksummed_count` and `widget_checksum_failed_count` in
+ `GeoNodeStatus#load_verification_data`:
-Widgets should now be verified by Geo!
+ ```ruby
+ self.widget_checksummed_count = Geo::WidgetReplicator.checksummed_count self.widget_checksum_failed_count = Geo::WidgetReplicator.checksum_failed_count
+ ```
+
+Widget replication and verification metrics should now be available in the API,
+the Admin Area UI, and Prometheus!
#### GraphQL API
@@ -428,8 +477,8 @@ Widgets should now be verified by Geo!
require 'spec_helper'
- describe Resolvers::Geo::WidgetRegistriesResolver do
- it_behaves_like 'a Geo registries resolver', :widget_registry
+ RSpec.describe Resolvers::Geo::WidgetRegistriesResolver do
+ it_behaves_like 'a Geo registries resolver', :geo_widget_registry
end
```
@@ -452,8 +501,8 @@ Widgets should now be verified by Geo!
require 'spec_helper'
- describe Geo::WidgetRegistryFinder do
- it_behaves_like 'a framework registry finder', :widget_registry
+ RSpec.describe Geo::WidgetRegistryFinder do
+ it_behaves_like 'a framework registry finder', :geo_widget_registry
end
```
@@ -484,7 +533,7 @@ Widgets should now be verified by Geo!
require 'spec_helper'
- describe GitlabSchema.types['WidgetRegistry'] do
+ RSpec.describe GitlabSchema.types['WidgetRegistry'] do
it_behaves_like 'a Geo registry type'
it 'has the expected fields (other than those included in RegistryType)' do
@@ -503,7 +552,7 @@ Widgets should now be verified by Geo!
it_behaves_like 'gets registries for', {
field_name: 'widgetRegistries',
registry_class_name: 'WidgetRegistry',
- registry_factory: :widget_registry,
+ registry_factory: :geo_widget_registry,
registry_foreign_key_field_name: 'widgetId'
}
```
@@ -511,6 +560,13 @@ Widgets should now be verified by Geo!
Individual widget synchronization and verification data should now be available
via the GraphQL API!
+1. Take care of replicating "update" events. Geo Framework does not currently support
+ replicating "update" events because all entities added to the framework, by this time,
+ are immutable. If this is the case
+ for the entity you're going to add, please follow <https://gitlab.com/gitlab-org/gitlab/-/issues/118743>
+ and <https://gitlab.com/gitlab-org/gitlab/-/issues/118745> as examples to add the new event type.
+ Please also remove this notice when you've added it.
+
#### Admin UI
To do: This should be done as part of
diff --git a/doc/development/gitaly.md b/doc/development/gitaly.md
index 417c96a44dd..8b4e5090abb 100644
--- a/doc/development/gitaly.md
+++ b/doc/development/gitaly.md
@@ -1,7 +1,14 @@
-# GitLab Developers Guide to Working with Gitaly
+---
+stage: Create
+group: Gitaly
+info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers
+type: reference
+---
-[Gitaly](https://gitlab.com/gitlab-org/gitaly) is a high-level Git RPC service used by GitLab CE/EE,
-Workhorse and GitLab-Shell.
+# Gitaly developers guide
+
+[Gitaly](https://gitlab.com/gitlab-org/gitaly) is a high-level Git RPC service used by GitLab Rails,
+Workhorse and GitLab Shell.
## Deep Dive
@@ -114,7 +121,8 @@ bundle exec rake gitlab:features:disable_rugged
Most of this code exists in the `lib/gitlab/git/rugged_impl` directory.
-NOTE: **Note:** You should NOT need to add or modify code related to
+NOTE: **Note:**
+You should NOT need to add or modify code related to
Rugged unless explicitly discussed with the [Gitaly
Team](https://gitlab.com/groups/gl-gitaly/group_members). This code will
NOT work on GitLab.com or other GitLab instances that do not use NFS.
diff --git a/doc/development/go_guide/index.md b/doc/development/go_guide/index.md
index 5a5e163e142..779dcd9b7a2 100644
--- a/doc/development/go_guide/index.md
+++ b/doc/development/go_guide/index.md
@@ -414,7 +414,7 @@ it will display its help message (if `cli` has been used).
With the exception of [GitLab Runner](https://gitlab.com/gitlab-org/gitlab-runner),
which publishes its own binaries, our Go binaries are created by projects
-managed by the [Distribution group](https://about.gitlab.com/handbook/product/categories/#distribution-group).
+managed by the [Distribution group](https://about.gitlab.com/handbook/product/product-categories/#distribution-group).
The [Omnibus GitLab](https://gitlab.com/gitlab-org/omnibus-gitlab) project creates a
single, monolithic operating system package containing all the binaries, while
@@ -453,6 +453,28 @@ are:
To reduce unnecessary differences between two distribution methods, Omnibus and
CNG **should always use the same Go version**.
+### Supporting multiple Go versions
+
+Individual Golang-projects need to support multiple Go versions for the following reasons:
+
+1. When a new Go release is out, we should start integrating it into the CI pipelines to verify compatibility with the new compiler.
+1. We must support the [Omnibus official Go version](#updating-go-version), which may be behind the latest minor release.
+1. When Omnibus switches Go version, we still may need to support the old one for security backports.
+
+These 3 requirements may easily be satisfied by keeping support for the 3 latest minor versions of Go.
+
+It's ok to drop support for the oldest Go version and support only 2 latest releases,
+if this is enough to support backports to the last 3 GitLab minor releases.
+
+Example:
+
+In case we want to drop support for `go 1.11` in GitLab `12.10`, we need to verify which Go versions we are using in `12.9`, `12.8`, and `12.7`.
+
+We will not consider the active milestone, `12.10`, because a backport for `12.7` will be required in case of a critical security release.
+
+1. If both [Omnibus and CNG](#updating-go-version) were using Go `1.12` since GitLab `12.7`, then we safely drop support for `1.11`.
+1. If Omnibus or CNG were using `1.11` in GitLab `12.7`, then we still need to keep support for Go `1.11` for easier backporting of security fixes.
+
## Secure Team standards and style guidelines
The following are some style guidelines that are specific to the Secure Team.
diff --git a/doc/development/gotchas.md b/doc/development/gotchas.md
index 7e08da8162b..34fe3f11489 100644
--- a/doc/development/gotchas.md
+++ b/doc/development/gotchas.md
@@ -20,7 +20,7 @@ Consider the following API spec:
```ruby
require 'spec_helper'
-describe API::Labels do
+RSpec.describe API::Labels do
it 'creates a first label' do
create(:label)
@@ -71,7 +71,7 @@ Following is the fixed API spec:
```ruby
require 'spec_helper'
-describe API::Labels do
+RSpec.describe API::Labels do
it 'creates a first label' do
create(:label, title: 'foo')
diff --git a/doc/development/i18n/externalization.md b/doc/development/i18n/externalization.md
index bdd372e90ed..bce95dfc24e 100644
--- a/doc/development/i18n/externalization.md
+++ b/doc/development/i18n/externalization.md
@@ -95,7 +95,8 @@ Active Record's `:message` option accepts a `Proc`, so we can do this instead:
validates :group_id, uniqueness: { scope: [:project_id], message: -> (object, data) { _("already shared with this group") } }
```
-NOTE: **Note:** Messages in the API (`lib/api/` or `app/graphql`) do
+NOTE: **Note:**
+Messages in the API (`lib/api/` or `app/graphql`) do
not need to be externalised.
### HAML files
diff --git a/doc/development/i18n/proofreader.md b/doc/development/i18n/proofreader.md
index 8c1f62330f4..935a171f34c 100644
--- a/doc/development/i18n/proofreader.md
+++ b/doc/development/i18n/proofreader.md
@@ -105,6 +105,7 @@ are very appreciative of the work done by translators and proofreaders!
- Proofreaders needed.
- Turkish
- Ali DemirtaÅŸ - [GitLab](https://gitlab.com/alidemirtas), [CrowdIn](https://crowdin.com/profile/alidemirtas)
+ - Rıfat Ünalmış (Rifat Unalmis) - [GitLab](https://gitlab.com/runalmis), [CrowdIn](https://crowdin.com/profile/runalmis)
- Ukrainian
- Volodymyr Sobotovych - [GitLab](https://gitlab.com/wheleph), [CrowdIn](https://crowdin.com/profile/wheleph)
- Andrew Vityuk - [GitLab](https://gitlab.com/3_1_3_u), [CrowdIn](https://crowdin.com/profile/andruwa13)
diff --git a/doc/development/import_export.md b/doc/development/import_export.md
index ecdfd8a853e..556fa6c7db4 100644
--- a/doc/development/import_export.md
+++ b/doc/development/import_export.md
@@ -408,4 +408,5 @@ tree
└── 4352.json
```
-CAUTION: **Caution:** When updating these fixtures, please ensure you update both `json` files and `tree` folder, as the tests apply to both.
+CAUTION: **Caution:**
+When updating these fixtures, please ensure you update both `json` files and `tree` folder, as the tests apply to both.
diff --git a/doc/development/import_project.md b/doc/development/import_project.md
index f222a6533e8..1fa6ea5d405 100644
--- a/doc/development/import_project.md
+++ b/doc/development/import_project.md
@@ -17,7 +17,8 @@ The first option is to simply [import the Project tarball file via the GitLab UI
It should take up to 15 minutes for the project to fully import. You can head to the project's main page for the current status.
-NOTE: **Note:** This method ignores all the errors silently (including the ones related to `GITALY_DISABLE_REQUEST_LIMITS`) and is used by GitLab's users. For development and testing, check the other methods below.
+NOTE: **Note:**
+This method ignores all the errors silently (including the ones related to `GITALY_DISABLE_REQUEST_LIMITS`) and is used by GitLab's users. For development and testing, check the other methods below.
### Importing via the `import-project` script
@@ -47,7 +48,9 @@ This method will take longer to import than the other methods and will depend on
### Importing via a Rake task
-[`import.rake`](https://gitlab.com/gitlab-org/gitlab/blob/master/lib/tasks/gitlab/import_export/import.rake) was introduced for importing large GitLab project exports.
+> The [Rake task](https://gitlab.com/gitlab-org/gitlab/blob/master/lib/tasks/gitlab/import_export/import.rake) was [introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/20724) in GitLab 12.6, replacing a GitLab.com Ruby script.
+
+This script was introduced in GitLab 12.6 for importing large GitLab project exports.
As part of this script we also disable direct and background upload to avoid situations where a huge archive is being uploaded to GCS (while being inside a transaction, which can cause idle transaction timeouts).
@@ -63,9 +66,53 @@ Parameters:
| `archive_path` | string | yes | Path to the exported project tarball you want to import |
```shell
-bundle exec rake "gitlab:import_export:import[root, root, testingprojectimport, /path/to/file.tar.gz]"
+bundle exec rake "gitlab:import_export:import[root, group/subgroup, testingprojectimport, /path/to/file.tar.gz]"
+```
+
+If you're running Omnibus, run the following Rake task:
+
+```shell
+gitlab-rake "gitlab:import_export:import[root, group/subgroup, testingprojectimport, /path/to/file.tar.gz]"
+```
+
+#### Troubleshooting
+
+Check the common errors listed below, what they mean, and how to fix them.
+
+##### `Exception: undefined method 'name' for nil:NilClass`
+
+The `username` is not valid.
+
+##### `Exception: undefined method 'full_path' for nil:NilClass`
+
+The `namespace_path` does not exist.
+For example, one of the groups or subgroups is mistyped or missing
+or you've specified the project name in the path.
+
+The task will only create the project.
+If you want to import it to a new group or subgroup then create it first.
+
+##### `Exception: No such file or directory @ rb_sysopen - (filename)`
+
+The specified project export file in `archive_path` is missing.
+
+##### `Name can contain only letters, digits, emojis ...`
+
+```plaintext
+Name can contain only letters, digits, emojis, '_', '.', dash, space. It must start with letter,
+digit, emoji or '_'. and Path can contain only letters, digits, '_', '-' and '.'. Cannot start
+with '-', end in '.git' or end in '.atom'
```
+The project name specified in `project_path` is not valid for one of the specified reasons.
+
+Only put the project name in `project_path`. If, for example, you put a path of subgroups in there
+it will fail with this error as `/` is not a valid character in a project name.
+
+##### `Name has already been taken and Path has already been taken`
+
+A project with that name already exists.
+
### Importing via the Rails console
The last option is to import a project using a Rails console:
diff --git a/doc/development/insert_into_tables_in_batches.md b/doc/development/insert_into_tables_in_batches.md
index d8919789808..f65d2478d2e 100644
--- a/doc/development/insert_into_tables_in_batches.md
+++ b/doc/development/insert_into_tables_in_batches.md
@@ -121,10 +121,10 @@ These callbacks cannot be used with bulk insertions, since they are meant to be
every instance that is saved or created. Since these events do not fire when
records are inserted in bulk, we currently disallow their use.
-The specifics around which callbacks are disallowed are defined in
+The specifics around which callbacks are explicitly allowed are defined in
[`BulkInsertSafe`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/app/models/concerns/bulk_insert_safe.rb).
-Consult the module source code for details. If your class uses any of the blacklisted
-functionality, and you `include BulkInsertSafe`, the application will fail with an error.
+Consult the module source code for details. If your class uses callbacks that are not explicitly designated
+safe and you `include BulkInsertSafe` the application will fail with an error.
### `BulkInsertSafe` versus `InsertAll`
diff --git a/doc/development/integrations/elasticsearch_for_paid_tiers_on_gitlabcom.md b/doc/development/integrations/elasticsearch_for_paid_tiers_on_gitlabcom.md
new file mode 100644
index 00000000000..8289be47253
--- /dev/null
+++ b/doc/development/integrations/elasticsearch_for_paid_tiers_on_gitlabcom.md
@@ -0,0 +1,28 @@
+# Elasticsearch for paid tiers on GitLab.com
+
+> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/220246) in GitLab 13.2
+> - It's deployed behind a feature flag, disabled by default.
+> - It's disabled on GitLab.com.
+> - It's not recommended for use in GitLab self-managed instances.
+
+This document describes how to enable Elasticsearch with GitLab for all paid tiers on GitLab.com. Once enabled,
+all paid tiers will have access to the [Advanced Global Search feature](../../integration/elasticsearch.md) on GitLab.com.
+
+## Enable or disable Elasticsearch for all paid tiers on GitLab.com
+
+Since we're still in the process of rolling this out and want to control the timing this is behind a feature flag
+which defaults to off.
+
+To enable it:
+
+```ruby
+# Instance-wide
+Feature.enable(:elasticsearch_index_only_paid_groups)
+```
+
+To disable it:
+
+```ruby
+# Instance-wide
+Feature.disable(:elasticsearch_index_only_paid_groups)
+```
diff --git a/doc/development/integrations/jira_connect.md b/doc/development/integrations/jira_connect.md
index 8e619c3b0a2..374cc976caa 100644
--- a/doc/development/integrations/jira_connect.md
+++ b/doc/development/integrations/jira_connect.md
@@ -11,7 +11,7 @@ The following are required to install and test the app:
For the app to work, Jira Cloud should be able to connect to the GitLab instance through the internet.
To easily expose your local development environment, you can use tools like
- [serveo](https://medium.com/@osanda.deshan/how-to-forward-my-local-port-to-public-using-serveo-4979f352a3bf)
+ [serveo](https://medium.com/testautomator/how-to-forward-my-local-port-to-public-using-serveo-4979f352a3bf)
or [ngrok](https://ngrok.com). These also take care of SSL for you because Jira
requires all connections to the app host to be over SSL.
diff --git a/doc/development/integrations/secure.md b/doc/development/integrations/secure.md
index 1737daae0e0..22da57400e0 100644
--- a/doc/development/integrations/secure.md
+++ b/doc/development/integrations/secure.md
@@ -100,13 +100,12 @@ the project repository contains Java source code and the `dependency_scanning` f
```yaml
mysec_dependency_scanning:
- except:
- variables:
- - $DEPENDENCY_SCANNING_DISABLED
- only:
- variables:
- - $GITLAB_FEATURES =~ /\bdependency_scanning\b/ &&
- $CI_PROJECT_REPOSITORY_LANGUAGES =~ /\bjava\b/
+ rules:
+ - if: $DEPENDENCY_SCANNING_DISABLED
+ when: never
+ - if: $GITLAB_FEATURES =~ /\bdependency_scanning\b/
+ exists:
+ - '**/*.java'
```
Any additional job policy should only be configured by users based on their needs.
@@ -232,6 +231,32 @@ to colorize the messages they write to the Unix standard output and standard err
We recommend using red to report errors, yellow for warnings, and green for notices.
Also, we recommend prefixing error messages with `[ERRO]`, warnings with `[WARN]`, and notices with `[INFO]`.
+#### Logging level
+
+The scanner should filter out a log message if its log level is lower than the
+one set in the `SECURE_LOG_LEVEL` variable. For instance, `info` and `warn`
+messages should be skipped when `SECURE_LOG_LEVEL` is set to `error`. Accepted
+values are as follows, listed from highest to lowest:
+
+- `fatal`
+- `error`
+- `warn`
+- `info`
+- `debug`
+
+It is recommended to use the `debug` level for verbose logging that could be
+useful when debugging. The default value for `SECURE_LOG_LEVEL` should be set
+to `info`.
+
+#### common logutil package
+
+If you are using [go](https://golang.org/) and
+[common](https://gitlab.com/gitlab-org/security-products/analyzers/common),
+then it is suggested that you use [logrus](https://github.com/Sirupsen/logrus)
+and [common's logutil package](https://gitlab.com/gitlab-org/security-products/analyzers/common/-/tree/master/logutil)
+to configure the formatter for [logrus](https://github.com/Sirupsen/logrus).
+See the [logutil README.md](https://gitlab.com/gitlab-org/security-products/analyzers/common/-/tree/master/logutil/README.md)
+
## Report
The report is a JSON document that combines vulnerabilities with possible remediations.
@@ -547,3 +572,15 @@ remediation. `fixes[].id` contains a fixed vulnerability's [unique identifier](#
The `diff` field is a base64-encoded remediation code diff, compatible with
[`git apply`](https://git-scm.com/docs/git-format-patch#_discussion). This field is required.
+
+## Limitations
+
+### Container Scanning
+
+Container Scanning currently has these limitations:
+
+- Although the Security Dashboard can display scan results from multiple images, if multiple
+ vulnerabilities have the same fingerprint, only the first instance of that vulnerability is
+ displayed. We're working on removing this limitation. You can follow our progress on the issue
+ [Change location fingerprint for Container Scanning](https://gitlab.com/gitlab-org/gitlab/-/issues/215466).
+- Different scanners may each report the same vulnerability, resulting in duplicate findings.
diff --git a/doc/development/integrations/secure_partner_integration.md b/doc/development/integrations/secure_partner_integration.md
index 22e1f8bf769..19a497641f9 100644
--- a/doc/development/integrations/secure_partner_integration.md
+++ b/doc/development/integrations/secure_partner_integration.md
@@ -11,6 +11,15 @@ with [onboarding as a partner](https://about.gitlab.com/partners/integrate/).
The steps below are a high-level view of what needs to be done to complete an
integration as well as linking to more detailed resources for how to do so.
+## Integration Tiers
+
+GitLab's security offerings are designed for GitLab Gold and GitLab Ultimate users, and the
+[DevSecOps](https://about.gitlab.com/handbook/use-cases/#4-devsecops-shift-left-security)
+use case. All the features are in those tiers. This includes the APIs and standard reporting
+framework needed to provide a consistent experience for users to easily bring their preferred
+security tools into GitLab. We ask that our integration partners focus their work on those license
+tiers so that we can provide the most value to our mutual customers.
+
## What is the GitLab Developer Workflow?
This workflow is how GitLab users interact with our product and expect it to
diff --git a/doc/development/licensing.md b/doc/development/licensing.md
index f92151e7a37..8cda3d5f361 100644
--- a/doc/development/licensing.md
+++ b/doc/development/licensing.md
@@ -12,7 +12,7 @@ Some gems may not include their license information in their `gemspec` file, and
### License Finder commands
-> Note: License Finder currently uses GitLab misused terms of whitelist and blacklist. As a result, the commands below references those terms. We've created an [issue on their project](https://github.com/pivotal/LicenseFinder/issues/745) to propose that they rename their commands.
+> Note: License Finder currently uses GitLab misused terms of `whitelist` and `blacklist`. As a result, the commands below reference those terms. We've created an [issue on their project](https://github.com/pivotal/LicenseFinder/issues/745) to propose that they rename their commands.
There are a few basic commands License Finder provides that you'll need in order to manage license detection.
diff --git a/doc/development/migration_style_guide.md b/doc/development/migration_style_guide.md
index 7d3d9dac174..d1f956f957e 100644
--- a/doc/development/migration_style_guide.md
+++ b/doc/development/migration_style_guide.md
@@ -88,7 +88,7 @@ body:
For example:
```ruby
-class MyMigration < ActiveRecord::Migration[4.2]
+class MyMigration < ActiveRecord::Migration[6.0]
DOWNTIME = true
DOWNTIME_REASON = 'This migration requires downtime because ...'
@@ -411,7 +411,7 @@ migration. For this to work your migration needs to include the module
`Gitlab::Database::MultiThreadedMigration`:
```ruby
-class MyMigration < ActiveRecord::Migration[4.2]
+class MyMigration < ActiveRecord::Migration[6.0]
include Gitlab::Database::MigrationHelpers
include Gitlab::Database::MultiThreadedMigration
end
@@ -421,7 +421,7 @@ You can then use the method `with_multiple_threads` to perform work in separate
threads. For example:
```ruby
-class MyMigration < ActiveRecord::Migration[4.2]
+class MyMigration < ActiveRecord::Migration[6.0]
include Gitlab::Database::MigrationHelpers
include Gitlab::Database::MultiThreadedMigration
@@ -455,7 +455,7 @@ by calling the method `disable_ddl_transaction!` in the body of your migration
class like so:
```ruby
-class MyMigration < ActiveRecord::Migration[4.2]
+class MyMigration < ActiveRecord::Migration[6.0]
include Gitlab::Database::MigrationHelpers
disable_ddl_transaction!
@@ -495,9 +495,11 @@ by calling the method `disable_ddl_transaction!` in the body of your migration
class like so:
```ruby
-class MyMigration < ActiveRecord::Migration[4.2]
+class MyMigration < ActiveRecord::Migration[6.0]
include Gitlab::Database::MigrationHelpers
+ DOWNTIME = false
+
disable_ddl_transaction!
def up
@@ -534,7 +536,7 @@ Here's an example where we add a new column with a foreign key
constraint. Note it includes `index: true` to create an index for it.
```ruby
-class Migration < ActiveRecord::Migration[4.2]
+class Migration < ActiveRecord::Migration[6.0]
def change
add_reference :model, :other_model, index: true, foreign_key: { on_delete: :cascade }
@@ -602,7 +604,8 @@ In this particular case, the default value exists and we're just changing the me
`request_access_enabled` column, which does not imply a rewrite of all the existing records
in the `namespaces` table. Only when creating a new column with a default, all the records are going be rewritten.
-NOTE: **Note:** A faster [ALTER TABLE ADD COLUMN with a non-null default](https://www.depesz.com/2018/04/04/waiting-for-postgresql-11-fast-alter-table-add-column-with-a-non-null-default/)
+NOTE: **Note:**
+A faster [ALTER TABLE ADD COLUMN with a non-null default](https://www.depesz.com/2018/04/04/waiting-for-postgresql-11-fast-alter-table-add-column-with-a-non-null-default/)
was introduced on PostgresSQL 11.0, removing the need of rewriting the table when a new column with a default value is added.
For the reasons mentioned above, it's safe to use `change_column_default` in a single-transaction migration
@@ -808,6 +811,14 @@ class BuildMetadata
end
```
+When using a `JSONB` column, use the [JsonSchemaValidator](https://gitlab.com/gitlab-org/gitlab/-/blob/master/app/validators/json_schema_validator.rb) to keep control of the data being inserted over time.
+
+```ruby
+class BuildMetadata
+ validates :config_options, json_schema: { filename: 'build_metadata_config_option' }
+end
+```
+
## Testing
See the [Testing Rails migrations](testing_guide/testing_migrations_guide.md) style guide.
@@ -844,16 +855,33 @@ If you need more complex logic, you can define and use models local to a
migration. For example:
```ruby
-class MyMigration < ActiveRecord::Migration[4.2]
+class MyMigration < ActiveRecord::Migration[6.0]
class Project < ActiveRecord::Base
self.table_name = 'projects'
end
+
+ def up
+ # Reset the column information of all the models that update the database
+ # to ensure the Active Record's knowledge of the table structure is current
+ Project.reset_column_information
+
+ # ... ...
+ end
end
```
When doing so be sure to explicitly set the model's table name, so it's not
derived from the class name or namespace.
+Finally, make sure that `reset_column_information` is run in the `up` method of
+the migration for all local Models that update the database.
+
+The reason for that is that all migration classes are loaded at the beginning
+(when `db:migrate` starts), so they can get out of sync with the table schema
+they map to in case another migration updates that schema. That makes the data
+migration fail when trying to insert or make updates to the underlying table,
+as the new columns are reported as `unknown attribute` by `ActiveRecord`.
+
### Renaming reserved paths
When a new route for projects is introduced, it could conflict with any
diff --git a/doc/development/multi_version_compatibility.md b/doc/development/multi_version_compatibility.md
index 001517d44ea..aedd5c1ffb7 100644
--- a/doc/development/multi_version_compatibility.md
+++ b/doc/development/multi_version_compatibility.md
@@ -45,7 +45,7 @@ and set this column to `false`. The old servers were still updating the old colu
that updated the new column from the old one. For the new servers though, they were only updating the new column and that same trigger
was now working against us and setting it back to the wrong value.
-For more information, see this [confidential issue](../user/project/issues/confidential_issues.md) `https://gitlab.com/gitlab-com/gl-infra/infrastructure/-/issues/9176`.
+For more information, see [the relevant issue](https://gitlab.com/gitlab-com/gl-infra/infrastructure/-/issues/9176).
### Sidebar wasn't loading for some users
diff --git a/doc/development/new_fe_guide/development/accessibility.md b/doc/development/new_fe_guide/development/accessibility.md
index f76fd72d4dc..b9ee5c3a549 100644
--- a/doc/development/new_fe_guide/development/accessibility.md
+++ b/doc/development/new_fe_guide/development/accessibility.md
@@ -43,4 +43,4 @@ In forms we should use the `for` attribute in the label statement:
- [Chrome Accessibility Developer Tools](https://github.com/GoogleChrome/accessibility-developer-tools) for testing accessibility
- [Audit Rules Page](https://github.com/GoogleChrome/accessibility-developer-tools/wiki/Audit-Rules) for best practices
-- [Lighthouse Accessibility Score](https://developers.google.com/web/tools/lighthouse/scoring#a11y) for accessibility audits
+- [Lighthouse Accessibility Score](https://web.dev/performance-scoring/) for accessibility audits
diff --git a/doc/development/ordering_table_columns.md b/doc/development/ordering_table_columns.md
index b68602ea30d..18788d0b86e 100644
--- a/doc/development/ordering_table_columns.md
+++ b/doc/development/ordering_table_columns.md
@@ -1,5 +1,10 @@
# Ordering Table Columns in PostgreSQL
+For GitLab we require that columns of new tables are ordered to use the
+least amount of space. An easy way of doing this is to order them based on the
+type size in descending order with variable sizes (`text`, `varchar`, arrays,
+`json`, `jsonb`, and so on) at the end.
+
Similar to C structures the space of a table is influenced by the order of
columns. This is because the size of columns is aligned depending on the type of
the following column. Let's consider an example:
@@ -40,10 +45,9 @@ In these examples, the `id` and `user_id` columns are packed together, which
means we only need 8 bytes to store _both_ of them. This in turn means each row
will require 8 bytes less space.
-For GitLab we require that columns of new tables are ordered based to use the
-least amount of space. An easy way of doing this is to order them based on the
-type size in descending order with variable sizes (`text`, `varchar`, arrays,
-`json`, `jsonb`, and so on) at the end.
+Note: **NOTE:**
+Since Ruby on Rails 5.1, the default data type for IDs is `bigint`, which uses 8 bytes.
+We are using `integer` in the examples to showcase a more realistic reordering scenario.
## Type Sizes
diff --git a/doc/development/packages.md b/doc/development/packages.md
index edf01255e84..ae67af10d88 100644
--- a/doc/development/packages.md
+++ b/doc/development/packages.md
@@ -68,7 +68,8 @@ The current state of existing package registries availability is:
| Go | Yes | No - [open issue](https://gitlab.com/gitlab-org/gitlab/-/issues/213900) | No - [open-issue](https://gitlab.com/gitlab-org/gitlab/-/issues/213902) |
| Composer | Yes | Yes | No |
-NOTE: **Note:** NPM is currently a hybrid of the instance level and group level.
+NOTE: **Note:**
+NPM is currently a hybrid of the instance level and group level.
It is using the top-level group or namespace as the defining portion of the name
(for example, `@my-group-name/my-package-name`).
diff --git a/doc/development/performance.md b/doc/development/performance.md
index 69ad524675d..16ea1aa27ff 100644
--- a/doc/development/performance.md
+++ b/doc/development/performance.md
@@ -36,7 +36,6 @@ graphs/dashboards.
GitLab provides built-in tools to help improve performance and availability:
- [Profiling](profiling.md).
- - [Sherlock](profiling.md#sherlock).
- [Distributed Tracing](distributed_tracing.md)
- [GitLab Performance Monitoring](../administration/monitoring/performance/index.md).
- [Request Profiling](../administration/monitoring/performance/request_profiling.md).
@@ -108,16 +107,24 @@ In short:
## Profiling
By collecting snapshots of process state at regular intervals, profiling allows
-you to see where time is spent in a process. The [StackProf](https://github.com/tmm1/stackprof)
-gem is included in GitLab's development environment, allowing you to investigate
-the behavior of suspect code in detail.
+you to see where time is spent in a process. The
+[Stackprof](https://github.com/tmm1/stackprof) gem is included in GitLab,
+allowing you to profile which code is running on CPU in detail.
-It's important to note that profiling an application *alters its performance*,
-and will generally be done *in an unrepresentative environment*. In particular,
-a method is not necessarily troublesome just because it's executed many times,
-or takes a long time to execute. Profiles are tools you can use to better
-understand what is happening in an application - using that information wisely
-is up to you!
+It's important to note that profiling an application *alters its performance*.
+Different profiling strategies have different overheads. Stackprof is a sampling
+profiler. It will sample stack traces from running threads at a configurable
+frequency (e.g. 100hz, that is 100 stacks per second). This type of profiling
+has quite a low (albeit non-zero) overhead and is generally considered to be
+safe for production.
+
+### Development
+
+A profiler can be a very useful tool during development, even if it does run *in
+an unrepresentative environment*. In particular, a method is not necessarily
+troublesome just because it's executed many times, or takes a long time to
+execute. Profiles are tools you can use to better understand what is happening
+in an application - using that information wisely is up to you!
Keeping that in mind, to create a profile, identify (or create) a spec that
exercises the troublesome code path, then run it using the `bin/rspec-stackprof`
@@ -166,11 +173,30 @@ dot -Tsvg project_policy_spec.dot > project_policy_spec.svg
To load the profile in [kcachegrind](https://kcachegrind.github.io/):
```shell
-stackprof tmp/project_policy_spec.dump --callgrind > project_policy_spec.callgrind
+stackprof tmp/project_policy_spec.rb.dump --callgrind > project_policy_spec.callgrind
kcachegrind project_policy_spec.callgrind # Linux
qcachegrind project_policy_spec.callgrind # Mac
```
+For flamegraphs, enable raw collection first. Note that raw
+collection can generate a very large file, so increase the `INTERVAL`, or
+run on a smaller number of specs for smaller file size:
+
+```shell
+RAW=true bin/rspec-stackprof spec/policies/group_member_policy_spec.rb
+```
+
+You can then generate, and view the resultant flamegraph. It might take a
+while to generate based on the output file size:
+
+```shell
+# Generate
+stackprof --flamegraph tmp/group_member_policy_spec.rb.dump > group_member_policy_spec.flame
+
+# View
+stackprof --flamegraph-viewer=group_member_policy_spec.flame
+```
+
It may be useful to zoom in on a specific method, for example:
```shell
@@ -211,11 +237,57 @@ application code, these profiles can be used to investigate slow tests as well.
However, for smaller runs (like this example), this means that the cost of
setting up the test suite will tend to dominate.
-It's also possible to modify the application code in-place to output profiles
-whenever a particular code path is triggered without going through the test
-suite first. See the
-[StackProf documentation](https://github.com/tmm1/stackprof/blob/master/README.md)
-for details.
+### Production
+
+Stackprof can also be used to profile production workloads.
+
+In order to enable production profiling for Ruby processes, you can set the `STACKPROF_ENABLED` environment variable to `true`.
+
+The following configuration options can be configured:
+
+- `STACKPROF_ENABLED`: Enables stackprof signal handler on SIGUSR2 signal.
+ Defaults to `false`.
+- `STACKPROF_INTERVAL_US`: Sampling interval in microseconds. Defaults to
+ `10000` μs (100hz).
+- `STACKPROF_FILE_PREFIX`: File path prefix where profiles are stored. Defaults
+ to `$TMPDIR` (often corresponds to `/tmp`).
+- `STACKPROF_TIMEOUT_S`: Profiling timeout in seconds. Profiling will
+ automatically stop after this time has elapsed. Defaults to `30`.
+- `STACKPROF_RAW`: Whether to collect raw samples or only aggregates. Raw
+ samples are needed to generate flamegraphs, but they do have a higher memory
+ and disk overhead. Defaults to `true`.
+
+Once enabled, profiling can be triggered by sending a `SIGUSR2` signal to the
+Ruby process. The process will begin sampling stacks. Profiling can be stopped
+by sending another `SIGUSR2`. Alternatively, it will automatically stop after
+the timeout.
+
+Once profiling stops, the profile is written out to disk at
+`$STACKPROF_FILE_PREFIX/stackprof.$PID.$RAND.profile`. It can then be inspected
+further via the `stackprof` command line tool, as described in the previous
+section.
+
+Currently supported profiling targets are:
+
+- Puma worker
+- Sidekiq
+
+NOTE: **Note:**
+The Puma master process is not supported. Neither is Unicorn.
+Sending SIGUSR2 to either of those will trigger restarts. In the case of Puma,
+take care to only send the signal to Puma workers.
+
+This can be done via `pkill -USR2 puma:`. The `:` disambiguates between `puma
+4.3.3.gitlab.2 ...` (the master process) from `puma: cluster worker 0: ...` (the
+worker processes), selecting the latter.
+
+Production profiles can be especially noisy. It can be helpful to visualize them
+as a [flamegraph](https://github.com/brendangregg/FlameGraph). This can be done
+via:
+
+```shell
+bundle exec stackprof --stackcollapse /tmp/stackprof.55769.c6c3906452.profile | flamegraph.pl > flamegraph.svg
+```
## RSpec profiling
@@ -254,6 +326,13 @@ These results can also be placed into a PostgreSQL database by setting the
`RSPEC_PROFILING_POSTGRES_URL` variable. This is used to profile the test suite
when running in the CI environment.
+We store these results also when running CI jobs on the default branch on
+`gitlab.com`. Statistics of these profiling data are [available
+online](https://gitlab-org.gitlab.io/rspec_profiling_stats/). For example,
+you can find which tests take longest to run or which execute the most
+queries. This can be handy for optimizing our tests or identifying performance
+issues in our code.
+
## Memory profiling
One of the reasons of the increased memory footprint could be Ruby memory fragmentation.
diff --git a/doc/development/permissions.md b/doc/development/permissions.md
index 06a4a03de38..e930345caec 100644
--- a/doc/development/permissions.md
+++ b/doc/development/permissions.md
@@ -13,9 +13,16 @@ Groups and projects can have the following visibility levels:
- internal (`10`) - an entity is visible to logged in users
- private (`0`) - an entity is visible only to the approved members of the entity
+By default, subgroups can **not** have higher visibility levels.
+For example, if you create a new private group, it can not include a public subgroup.
+
The visibility level of a group can be changed only if all subgroups and
-sub-projects have the same or lower visibility level. (e.g., a group can be set
-to internal only if all subgroups and projects are internal or private).
+sub-projects have the same or lower visibility level. For example, a group can be set
+to internal only if all subgroups and projects are internal or private.
+
+CAUTION: **Warning:**
+If you migrate an existing group to a lower visibility level, that action does not migrate subgroups
+in the same way. This is a [known issue](https://gitlab.com/gitlab-org/gitlab/-/issues/22406).
Visibility levels can be found in the `Gitlab::VisibilityLevel` module.
@@ -48,10 +55,10 @@ levels are available (defined in the `Gitlab::Access` module):
- Maintainer (`40`)
- Owner (`50`)
-If a user is the member of both a project and the project parent group, the
+If a user is the member of both a project and the project parent group(s), the
higher permission is taken into account for the project.
-If a user is the member of a project, but not the parent group (or groups), they
+If a user is the member of a project, but not the parent group(s), they
can still view the groups and their entities (like epics).
Project membership (where the group membership is already taken into account)
diff --git a/doc/development/pipelines.md b/doc/development/pipelines.md
index 05b80cdb4a6..d3623529cd4 100644
--- a/doc/development/pipelines.md
+++ b/doc/development/pipelines.md
@@ -370,24 +370,28 @@ and are as follows:
| MRs | 11 |
| `master` (non-scheduled pipelines) | 11 |
| 2-hourly scheduled pipelines | 11 |
+| `nightly` scheduled pipelines | 11, 12 |
#### Long-term plan
We follow the [PostgreSQL versions shipped with Omnibus GitLab](https://docs.gitlab.com/omnibus/package-information/postgresql_versions.html):
-| PostgreSQL version | 12.10 (April 2020) | 13.0 (May 2020) | 13.1 (June 2020) | 13.2 (July 2020) | 13.3 (August 2020) | 13.4, 13.5 | 13.6 (November 2020) | 14.0 (May 2021?) |
-| ------ | ------------------ | --------------- | ---------------- | ---------------- | ------------------ | ------------ | -------------------- | ---------------- |
-| PG9.6 | MRs/`master`/`2-hour`/`nightly` | - | - | - | - | - | - | - |
-| PG10 | `nightly` | - | - | - | - | - | - | - |
-| PG11 | `master`/`2-hour` | MRs/`master`/`2-hour`/`nightly` | MRs/`master`/`2-hour`/`nightly` | MRs/`master`/`2-hour`/`nightly` | MRs/`master`/`2-hour`/`nightly` | MRs/`master`/`2-hour`/`nightly` | `nightly` | - |
-| PG12 | - | - | - | - | `master`/`2-hour` | `master`/`2-hour` | MRs/`master`/`2-hour`/`nightly` | `master`/`2-hour` |
-| PG13 | - | - | - | - | - | - | - | MRs/`master`/`2-hour`/`nightly` |
+| PostgreSQL version | 13.0 (May 2020) | 13.1 (June 2020) | 13.2 (July 2020) | 13.3 (August 2020) | 13.4, 13.5 | 13.6 (November 2020) | 14.0 (May 2021?) |
+| ------ | --------------- | ---------------- | ---------------- | ------------------ | ------------ | -------------------- | ---------------- |
+| PG11 | MRs/`master`/`2-hour`/`nightly` | MRs/`master`/`2-hour`/`nightly` | MRs/`master`/`2-hour`/`nightly` | MRs/`master`/`2-hour`/`nightly` | MRs/`master`/`2-hour`/`nightly` | `nightly` | - |
+| PG12 | - | - | `nightly` | `2-hour`/`nightly` | `2-hour`/`nightly` | MRs/`2-hour`/`nightly` | `2-hour`/`nightly` |
+| PG13 | - | - | - | - | - | - | MRs/`2-hour`/`nightly` |
### Test jobs
Consult [GitLab tests in the Continuous Integration (CI) context](testing_guide/ci.md)
for more information.
+We have dedicated jobs for each [testing level](testing_guide/testing_levels.md) and each job runs depending on the
+changes made in your merge request.
+If you want to force all the RSpec jobs to run regardless of your changes, you can include `RUN ALL RSPEC` in your merge
+request title.
+
### Review app jobs
Consult the [Review Apps](testing_guide/review_apps.md) dedicated page for more information.
diff --git a/doc/development/policies.md b/doc/development/policies.md
index 62442de825a..8f05948cb41 100644
--- a/doc/development/policies.md
+++ b/doc/development/policies.md
@@ -158,6 +158,89 @@ end
will include all rules from `ProjectPolicy`. The delegated conditions will be evaluated with the correct delegated subject, and will be sorted along with the regular rules in the policy. Note that only the relevant rules for a particular ability will actually be considered.
+### Overrides
+
+We allow policies to opt-out of delegated abilities.
+
+Delegated policies may define some abilities in a way that is incorrect for the
+delegating policy. Take for example a child/parent relationship, where some
+abilities can be inferred, and some cannot:
+
+```ruby
+class ParentPolicy < BasePolicy
+ condition(:speaks_spanish) { @subject.spoken_languages.include?(:es) }
+ condition(:has_license) { @subject.driving_license.present? }
+ condition(:enjoys_broccoli) { @subject.enjoyment_of(:broccoli) > 0 }
+
+ rule { speaks_spanish }.enable :read_spanish
+ rule { has_license }.enable :drive_car
+ rule { enjoys_broccoli }.enable :eat_broccoli
+ rule { ~enjoys_broccoli }.prevent :eat_broccoli
+end
+```
+
+Here, if we delegated the child policy to the parent policy, some values would be
+incorrect - we might correctly infer that the child can speak their parent's
+language, but it would be incorrect to infer that the child can drive or would
+eat broccoli just because the parent can and does.
+
+Some of these things we can deal with - we can forbid driving universally in the
+child policy, for example:
+
+```ruby
+class ChildPolicy < BasePolicy
+ delegate { @subject.parent }
+
+ rule { default }.prevent :drive_car
+end
+```
+
+But the food preferences one is harder - because of the `prevent` call in the
+parent policy, if the parent dislikes it, even calling `enable` in the child
+will not enable `:eat_broccoli`.
+
+We could remove the `prevent` call in the parent policy, but that still doesn't
+help us, since the rules are different: parents get to eat what they like, and
+children eat what they are given, provided they are well behaved. Allowing
+delegation would end up with only children whose parents enjoy green vegetables
+eating it. But a parent may well give their child broccoli, even if they dislike
+it themselves, because it is good for their child.
+
+The solution it to override the `:eat_broccoli` ability in the child policy:
+
+```ruby
+class ChildPolicy < BasePolicy
+ delegate { @subject.parent }
+
+ overrides :eat_broccoli
+
+ condition(:good_kid) { @subject.behavior_level >= Child::GOOD }
+
+ rule { good_kid }.enable :eat_broccoli
+end
+```
+
+With this definition, the `ChildPolicy` will _never_ look in the `ParentPolicy` to
+satisfy `:eat_broccoli`, but it _will_ use it for any other abilities. The child
+policy can then define `:eat_broccoli` in a way that makes sense for `Child` and not
+`Parent`.
+
+### Alternatives to using `overrides`
+
+Overriding policy delegation is complex, for the same reason delegation is
+complex - it involves reasoning about logical inference, and being clear about
+semantics. Misuse of `override` has the potential to duplicate code, and
+potentially introduce security bugs, allowing things that should be prevented.
+For this reason, it should be used only when other approaches are not feasible.
+
+Other approaches can include for example using different ability names. Choosing
+to eat a food and eating foods you are given are semantically distinct, and they
+could be named differently (perhaps `chooses_to_eat_broccoli` and
+`eats_what_is_given` in this case). It can depend on how polymorphic the call
+site is. If you know that we will always check the policy with a `Parent` or a
+`Child`, then we can choose the appropriate ability name. If the call site is
+polymorphic, then we cannot do that.
+
## Specifying Policy Class
You can also override the Policy used for a given subject:
diff --git a/doc/development/profiling.md b/doc/development/profiling.md
index 2cab6750b9b..f7ead94d725 100644
--- a/doc/development/profiling.md
+++ b/doc/development/profiling.md
@@ -10,7 +10,8 @@ There is a `Gitlab::Profiler.profile` method, and corresponding
`bin/profile-url` script, that enable profiling a GET or POST request to a
specific URL, either as an anonymous user (the default) or as a specific user.
-NOTE: **Note:** The first argument to the profiler is either a full URL
+NOTE: **Note:**
+The first argument to the profiler is either a full URL
(including the instance hostname) or an absolute path, including the
leading slash.
diff --git a/doc/development/prometheus_metrics.md b/doc/development/prometheus_metrics.md
index 004b1884bf0..024da5cc943 100644
--- a/doc/development/prometheus_metrics.md
+++ b/doc/development/prometheus_metrics.md
@@ -11,16 +11,16 @@ The requirement for adding a new metric is to make each query to have an unique
```yaml
- group: Response metrics (NGINX Ingress)
metrics:
- - title: "Throughput"
- y_axis:
- name: "Requests / Sec"
- format: "number"
- precision: 2
- queries:
- - id: response_metrics_nginx_ingress_throughput_status_code
- query_range: 'sum(rate(nginx_upstream_responses_total{upstream=~"%{kube_namespace}-%{ci_environment_slug}-.*"}[2m])) by (status_code)'
- unit: req / sec
- label: Status Code
+ - title: "Throughput"
+ y_axis:
+ name: "Requests / Sec"
+ format: "number"
+ precision: 2
+ queries:
+ - id: response_metrics_nginx_ingress_throughput_status_code
+ query_range: 'sum(rate(nginx_upstream_responses_total{upstream=~"%{kube_namespace}-%{ci_environment_slug}-.*"}[2m])) by (status_code)'
+ unit: req / sec
+ label: Status Code
```
### Update existing metrics
diff --git a/doc/development/query_recorder.md b/doc/development/query_recorder.md
index b28fdb1252b..f7d2c23e28d 100644
--- a/doc/development/query_recorder.md
+++ b/doc/development/query_recorder.md
@@ -115,6 +115,6 @@ There are multiple ways to find the source of queries.
## See also
-- [Bullet](profiling.md#Bullet) For finding `N+1` query problems
+- [Bullet](profiling.md#bullet) For finding `N+1` query problems
- [Performance guidelines](performance.md)
- [Merge request performance guidelines](merge_request_performance_guidelines.md#query-counts)
diff --git a/doc/development/rake_tasks.md b/doc/development/rake_tasks.md
index 3fa6ba40e6c..fd5dee69fc3 100644
--- a/doc/development/rake_tasks.md
+++ b/doc/development/rake_tasks.md
@@ -232,6 +232,9 @@ To see the full list of API routes, you can run:
bundle exec rake grape:path_helpers
```
+The generated list includes a full list of API endpoints and functional
+RESTful API verbs.
+
For the Rails controllers, run:
```shell
diff --git a/doc/development/redis.md b/doc/development/redis.md
index 6782ea96448..693b9e1ad0d 100644
--- a/doc/development/redis.md
+++ b/doc/development/redis.md
@@ -33,6 +33,8 @@ stop being consulted if the project is renamed. If the contents of the key are
invalidated by a name change, it is better to include a hook that will expire
the entry, instead of relying on the key changing.
+### Multi-key commands
+
We don't use [Redis Cluster](https://redis.io/topics/cluster-tutorial) at the
moment, but may wish to in the future: [#118820](https://gitlab.com/gitlab-org/gitlab/-/issues/118820).
@@ -41,3 +43,8 @@ operations that require several keys to be held on the same Redis server - for
instance, diffing two sets held in Redis - the keys should ensure that by
enclosing the changeable parts in curly braces, such as, `project:{1}:set_a` and
`project:{1}:set_b`.
+
+Currently, we validate this in the development and test environments
+with the [`RedisClusterValidator`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/instrumentation/redis_cluster_validator.rb),
+which is enabled for the `cache` and `shared_state`
+[Redis instances](https://docs.gitlab.com/omnibus/settings/redis.html#running-with-multiple-redis-instances)..
diff --git a/doc/development/scalability.md b/doc/development/scalability.md
index c0c26df88b5..0fb54d89913 100644
--- a/doc/development/scalability.md
+++ b/doc/development/scalability.md
@@ -115,8 +115,7 @@ that backup, the database can apply the WAL logs in order until the
database has reached the target time.
On GitLab.com, Consul and Patroni work together to coordinate failovers with
-the read replicas. [Omnibus ships with repmgr instead of
-Patroni](../administration/postgresql/replication_and_failover.md).
+the read replicas. [Omnibus ships with both repmgr and Patroni](../administration/postgresql/replication_and_failover.md).
#### Load-balancing
diff --git a/doc/development/secure_coding_guidelines.md b/doc/development/secure_coding_guidelines.md
index 912b8fbf043..65953620ce6 100644
--- a/doc/development/secure_coding_guidelines.md
+++ b/doc/development/secure_coding_guidelines.md
@@ -213,7 +213,7 @@ the mitigations for a new feature.
#### Feature-specific Mitigations
-For situations in which an allowlist or GitLab:HTTP cannot be used, it will be necessary to implement mitigations directly in the feature. It is best to validate the destination IP addresses themselves, not just domain names, as DNS can be controlled by the attacker. Below are a list of mitigations that should be implemented.
+For situtions in which an allowlist or GitLab:HTTP cannot be used, it will be necessary to implement mitigations directly in the feature. It is best to validate the destination IP addresses themselves, not just domain names, as DNS can be controlled by the attacker. Below are a list of mitigations that should be implemented.
**Important Note:** There are many tricks to bypass common SSRF validations. If feature-specific mitigations are necessary, they should be reviewed by the AppSec team, or a developer who has worked on SSRF mitigations previously.
@@ -278,6 +278,7 @@ For any and all input fields, ensure to define expectations on the type/format o
- Validate the [input size limits](https://youtu.be/2VFavqfDS6w?t=7582).
- Validate the input using an [allowlist approach](https://youtu.be/2VFavqfDS6w?t=7816) to only allow characters through which you are expecting to receive for the field.
- Input which fails validation should be **rejected**, and not sanitized.
+- When adding redirects or links to a user-controlled URL, ensure that the scheme is HTTP or HTTPS. Allowing other schemes like `javascript://` can lead to XSS and other security issues.
Note that denylists should be avoided, as it is near impossible to block all [variations of XSS](https://owasp.org/www-community/xss-filter-evasion-cheatsheet).
@@ -292,40 +293,60 @@ Once you've [determined when and where](#setting-expectations) the user submitte
### Additional info
-#### Mitigating XSS in Rails
+#### XSS mitigation and prevention in Rails
+
+By default, Rails automatically escapes strings when they are inserted into HTML templates. Avoid the
+methods used to keep Rails from escaping strings, especially those related to user-controlled values.
+Specifically, the following options are dangerous because they mark strings as trusted and safe:
+
+| Method | Avoid these options |
+|----------------------|-------------------------------|
+| HAML templates | `html_safe`, `raw`, `!=` |
+| Embedded Ruby (ERB) | `html_safe`, `raw`, `<%== %>` |
+In case you want to sanitize user-controlled values against XSS vulnerabilities, you can use
+[`ActionView::Helpers::SanitizeHelper`](https://api.rubyonrails.org/classes/ActionView/Helpers/SanitizeHelper.html).
+Calling `link_to` and `redirect_to` with user-controlled parameters can also lead to cross-site scripting.
+
+Do also sanitize and validate URL schemes.
+
+References:
- [XSS Defense in Rails](https://youtu.be/2VFavqfDS6w?t=2442)
- [XSS Defense with HAML](https://youtu.be/2VFavqfDS6w?t=2796)
- [Validating Untrusted URLs in Ruby](https://youtu.be/2VFavqfDS6w?t=3936)
- [RoR Model Validators](https://youtu.be/2VFavqfDS6w?t=7636)
+#### XSS mitigation and prevention in JavaScript and Vue
+
+- When updating the content of an HTML element using JavaScript, mark user-controlled values as `textContent` or `nodeValue` instead of `innerHTML`.
+- Avoid using `v-html` with user-controlled data, use [`v-safe-html`](https://gitlab-org.gitlab.io/gitlab-ui/?path=/story/directives-safe-html-directive--default) instead.
+- Consider using [`gl-sprintf`](../../ee/development/i18n/externalization.md#interpolation) to interpolate translated strings securely.
+- Avoid `__()` with translations that contain user-controlled values.
+- When working with `postMessage`, ensure the `origin` of the message is allowlisted.
+- Consider using the [Safe Link Directive](https://gitlab-org.gitlab.io/gitlab-ui/?path=/story/directives-safe-link-directive--default) to generate secure hyperlinks by default.
+
#### GitLab specific libraries for mitigating XSS
##### Vue
- [isSafeURL](https://gitlab.com/gitlab-org/gitlab/-/blob/v12.7.5-ee/app/assets/javascripts/lib/utils/url_utility.js#L190-207)
+- [GlSprintf](https://gitlab-org.gitlab.io/gitlab-ui/?path=/story/utilities-sprintf--default)
#### Content Security Policy
- [Content Security Policy](https://www.youtube.com/watch?v=2VFavqfDS6w&t=12991s)
- [Use nonce-based Content Security Policy for inline JavaScript](https://gitlab.com/gitlab-org/gitlab-foss/-/issues/65330)
-#### Free form input fields
-
-##### Sanitization
-
-- [HTML Sanitization](https://youtu.be/2VFavqfDS6w?t=5075)
-- [DOMPurify](https://youtu.be/2VFavqfDS6w?t=5381)
-
-##### `iframe` sandboxes
-
-- [iframe sandboxing](https://youtu.be/2VFavqfDS6w?t=7043)
+#### Free form input field
### Select examples of past XSS issues affecting GitLab
-- [Stored XSS in user status](https://gitlab.com/gitlab-org/gitlab-foss/-/issues/55320)
+- [Stored XSS in user status](https://gitlab.com/gitlab-org/gitlab-foss/issues/55320)
+- [XSS vulnerability on custom project templates form](https://gitlab.com/gitlab-org/gitlab/issues/197302)
+- [Stored XSS in branch names](https://gitlab.com/gitlab-org/gitlab-foss/-/issues/55320)
+- [Stored XSS in merge request pages](https://gitlab.com/gitlab-org/gitlab/-/issues/35096)
-### Developer Training
+### Internal Developer Training
- [Introduction to XSS](https://www.youtube.com/watch?v=PXR8PTojHmc&t=7785s)
- [Reflected XSS](https://youtu.be/2VFavqfDS6w?t=603s)
@@ -347,3 +368,29 @@ Once you've [determined when and where](#setting-expectations) the user submitte
- [RoR model validators](https://youtu.be/2VFavqfDS6w?t=7636)
- [Allowlist input validation](https://youtu.be/2VFavqfDS6w?t=7816)
- [Content Security Policy](https://www.youtube.com/watch?v=2VFavqfDS6w&t=12991s)
+
+## Path Traversal guidelines
+
+### Description
+
+Path Traversal vulnerabilities grant attackers access to arbitrary directories and files on the server that is executing an application, including data, code or credentials.
+
+### Impact
+
+Path Traversal attacks can lead to multiple critical and high severity issues, like arbitrary file read, remote code execution or information disclosure.
+
+### When to consider
+
+When working with user-controlled filenames/paths and filesystem APIs.
+
+### Mitigation and prevention
+
+In order to prevent Path Traversal vulnerabilities, user-controlled filenames or paths should be validated before being processed.
+
+- Comparing user input against an allowlist of allowed values or verifying that it only contains allowed characters.
+- After validating the user supplied input, it should be appended to the base directory and the path should be canonicalized using the filesystem API.
+
+#### GitLab specific validations
+
+- [`Gitlab::Utils.check_path_traversal`](https://gitlab.com/gitlab-org/security/gitlab/-/blob/master/lib/gitlab/utils.rb#L12-24) can be used to validate user input against Path Traversal vulnerabilities. Remember to add further validation when setting the `allowed_absolute` option to `true`.
+- [`file_path` API validator](https://gitlab.com/gitlab-org/security/gitlab/-/blob/master/lib/api/validations/validators/file_path.rb) to validate user input when working with the Grape gem.
diff --git a/doc/development/sidekiq_style_guide.md b/doc/development/sidekiq_style_guide.md
index 7ae3c9e9de2..2793756ff64 100644
--- a/doc/development/sidekiq_style_guide.md
+++ b/doc/development/sidekiq_style_guide.md
@@ -175,9 +175,9 @@ Jobs can have an `urgency` attribute set, which can be `:high`,
| **Urgency** | **Queue Scheduling Target** | **Execution Latency Requirement** |
|--------------|-----------------------------|------------------------------------|
-| `:high` | 100 milliseconds | p50 of 1 second, p99 of 10 seconds |
-| `:low` | 1 minute | Maximum run time of 1 hour |
-| `:throttled` | None | Maximum run time of 1 hour |
+| `:high` | 10 seconds | p50 of 1 second, p99 of 10 seconds |
+| `:low` | 1 minute | Maximum run time of 5 minutes |
+| `:throttled` | None | Maximum run time of 5 minutes |
To set a job's urgency, use the `urgency` class method:
@@ -225,6 +225,47 @@ work between two different workers, one with `urgency :high` code that
executes quickly, and the other with `urgency :low`, which has no
execution latency requirements (but also has lower scheduling targets).
+### Changing a queue's urgency
+
+On GitLab.com, we run Sidekiq in several
+[shards](https://dashboards.gitlab.net/d/sidekiq-shard-detail/sidekiq-shard-detail),
+each of which represents a particular type of workload.
+
+When changing a queue's urgency, or adding a new queue, we need to take
+into account the expected workload on the new shard. Note that, if we're
+changing an existing queue, there is also an effect on the old shard,
+but that will always be a reduction in work.
+
+To do this, we want to calculate the expected increase in total execution time
+and RPS (throughput) for the new shard. We can get these values from:
+
+- The [Queue Detail
+ dashboard](https://dashboards.gitlab.net/d/sidekiq-queue-detail/sidekiq-queue-detail)
+ has values for the queue itself. For a new queue, we can look for
+ queues that have similar patterns or are scheduled in similar
+ circumstances.
+- The [Shard Detail
+ dashboard](https://dashboards.gitlab.net/d/sidekiq-shard-detail/sidekiq-shard-detail)
+ has Total Execution Time and Throughput (RPS). The Shard Utilization
+ panel will show if there is currently any excess capacity for this
+ shard.
+
+We can then calculate the RPS * average runtime (estimated for new jobs)
+for the queue we're changing to see what the relative increase in RPS and
+execution time we expect for the new shard:
+
+```ruby
+new_queue_consumption = queue_rps * queue_duration_avg
+shard_consumption = shard_rps * shard_duration_avg
+
+(new_queue_consumption / shard_consumption) * 100
+```
+
+If we expect an increase of **less than 5%**, then no further action is needed.
+
+Otherwise, please ping `@gitlab-org/scalability` on the merge request and ask
+for a review.
+
## Jobs with External Dependencies
Most background jobs in the GitLab application communicate with other GitLab
@@ -262,7 +303,8 @@ class ExternalDependencyWorker
end
```
-NOTE: **Note:** Note that a job cannot be both high urgency and have
+NOTE: **Note:**
+Note that a job cannot be both high urgency and have
external dependencies.
## CPU-bound and Memory-bound Workers
@@ -337,55 +379,10 @@ We use the following approach to determine whether a worker is CPU-bound:
- Note that these values should not be used over small sample sizes, but
rather over fairly large aggregates.
-## Feature Categorization
-
-Each Sidekiq worker, or one of its ancestor classes, must declare a
-`feature_category` attribute. This attribute maps each worker to a feature
-category. This is done for error budgeting, alert routing, and team attribution
-for Sidekiq workers.
-
-The declaration uses the `feature_category` class method, as shown below.
-
-```ruby
-class SomeScheduledTaskWorker
- include ApplicationWorker
-
- # Declares that this worker is part of the
- # `continuous_integration` feature category
- feature_category :continuous_integration
-
- # ...
-end
-```
-
-The list of value values can be found in the file `config/feature_categories.yml`.
-This file is, in turn generated from the [`stages.yml` from the GitLab Company Handbook
-source](https://gitlab.com/gitlab-com/www-gitlab-com/blob/master/data/stages.yml).
-
-### Updating `config/feature_categories.yml`
-
-Occasionally new features will be added to GitLab stages. When this occurs, you
-can automatically update `config/feature_categories.yml` by running
-`scripts/update-feature-categories`. This script will fetch and parse
-[`stages.yml`](https://gitlab.com/gitlab-com/www-gitlab-com/blob/master/data/stages.yml)
-and generate a new version of the file, which needs to be checked into source control.
-
-### Excluding Sidekiq workers from feature categorization
+## Feature category
-A few Sidekiq workers, that are used across all features, cannot be mapped to a
-single category. These should be declared as such using the `feature_category_not_owned!`
- declaration, as shown below:
-
-```ruby
-class SomeCrossCuttingConcernWorker
- include ApplicationWorker
-
- # Declares that this worker does not map to a feature category
- feature_category_not_owned!
-
- # ...
-end
-```
+All Sidekiq workers must define a known [feature
+category](feature_categorization/index.md#sidekiq-workers).
## Job weights
@@ -401,6 +398,8 @@ default weight, which is 1.
## Worker context
+> - [Introduced](https://gitlab.com/gitlab-com/gl-infra/scalability/-/issues/9) in GitLab 12.8.
+
To have some more information about workers in the logs, we add
[metadata to the jobs in the form of an
`ApplicationContext`](logging.md#logging-context-metadata-through-rails-or-grape-requests).
@@ -417,27 +416,27 @@ need to do anything.
There are however some instances when there would be no context
present when the job is scheduled, or the context that is present is
-likely to be incorrect. For these instances we've added rubocop-rules
+likely to be incorrect. For these instances, we've added Rubocop rules
to draw attention and avoid incorrect metadata in our logs.
As with most our cops, there are perfectly valid reasons for disabling
them. In this case it could be that the context from the request is
correct. Or maybe you've specified a context already in a way that
-isn't picked up by the cops. In any case, please leave a code-comment
+isn't picked up by the cops. In any case, leave a code comment
pointing to which context will be used when disabling the cops.
-When you do provide objects to the context, please make sure that the
-route for namespaces and projects is pre-loaded. This can be done using
+When you do provide objects to the context, make sure that the
+route for namespaces and projects is pre-loaded. This can be done by using
the `.with_route` scope defined on all `Routable`s.
-### Cron-Workers
+### Cron workers
-The context is automatically cleared for workers in the cronjob-queue
-(which `include CronjobQueue`), even when scheduling them from
+The context is automatically cleared for workers in the Cronjob queue
+(`include CronjobQueue`), even when scheduling them from
requests. We do this to avoid incorrect metadata when other jobs are
-scheduled from the cron-worker.
+scheduled from the cron worker.
-Cron-Workers themselves run instance wide, so they aren't scoped to
+Cron workers themselves run instance wide, so they aren't scoped to
users, namespaces, projects, or other resources that should be added to
the context.
@@ -449,46 +448,46 @@ somewhere within the worker:
1. Wrap the code that schedules jobs in the `with_context` helper:
-```ruby
- def perform
- deletion_cutoff = Gitlab::CurrentSettings
- .deletion_adjourned_period.days.ago.to_date
- projects = Project.with_route.with_namespace
- .aimed_for_deletion(deletion_cutoff)
+ ```ruby
+ def perform
+ deletion_cutoff = Gitlab::CurrentSettings
+ .deletion_adjourned_period.days.ago.to_date
+ projects = Project.with_route.with_namespace
+ .aimed_for_deletion(deletion_cutoff)
- projects.find_each(batch_size: 100).with_index do |project, index|
- delay = index * INTERVAL
+ projects.find_each(batch_size: 100).with_index do |project, index|
+ delay = index * INTERVAL
- with_context(project: project) do
- AdjournedProjectDeletionWorker.perform_in(delay, project.id)
- end
- end
- end
-```
+ with_context(project: project) do
+ AdjournedProjectDeletionWorker.perform_in(delay, project.id)
+ end
+ end
+ end
+ ```
1. Use the a batch scheduling method that provides context:
-```ruby
- def schedule_projects_in_batch(projects)
- ProjectImportScheduleWorker.bulk_perform_async_with_contexts(
- projects,
- arguments_proc: -> (project) { project.id },
- context_proc: -> (project) { { project: project } }
- )
- end
-```
-
-or when scheduling with delays:
-
-```ruby
- diffs.each_batch(of: BATCH_SIZE) do |diffs, index|
- DeleteDiffFilesWorker
- .bulk_perform_in_with_contexts(index * 5.minutes,
- diffs,
- arguments_proc: -> (diff) { diff.id },
- context_proc: -> (diff) { { project: diff.merge_request.target_project } })
- end
-```
+ ```ruby
+ def schedule_projects_in_batch(projects)
+ ProjectImportScheduleWorker.bulk_perform_async_with_contexts(
+ projects,
+ arguments_proc: -> (project) { project.id },
+ context_proc: -> (project) { { project: project } }
+ )
+ end
+ ```
+
+ Or, when scheduling with delays:
+
+ ```ruby
+ diffs.each_batch(of: BATCH_SIZE) do |diffs, index|
+ DeleteDiffFilesWorker
+ .bulk_perform_in_with_contexts(index * 5.minutes,
+ diffs,
+ arguments_proc: -> (diff) { diff.id },
+ context_proc: -> (diff) { { project: diff.merge_request.target_project } })
+ end
+ ```
### Jobs scheduled in bulk
@@ -512,11 +511,11 @@ For example:
Each object from the enumerable in the first argument is yielded into 2
blocks:
-The `arguments_proc` which needs to return the list of arguments the
-job needs to be scheduled with.
+- The `arguments_proc` which needs to return the list of arguments the
+ job needs to be scheduled with.
-The `context_proc` which needs to return a hash with the context
-information for the job.
+- The `context_proc` which needs to return a hash with the context
+ information for the job.
## Arguments logging
@@ -597,7 +596,6 @@ There are two options for safely adding new arguments to Sidekiq workers:
1. Set up a [multi-step deployment](#multi-step-deployment) in which the new argument is first added to the worker
1. Use a [parameter hash](#parameter-hash) for additional arguments. This is perhaps the most flexible option.
-1. Use a parameter hash for additional arguments. This is perhaps the most flexible option.
##### Multi-step deployment
diff --git a/doc/development/telemetry/index.md b/doc/development/telemetry/index.md
index aee16e4049a..0000e7e9e4f 100644
--- a/doc/development/telemetry/index.md
+++ b/doc/development/telemetry/index.md
@@ -20,6 +20,7 @@ Telemetry Guide:
1. [Our tracking tools](#our-tracking-tools)
1. [What data can be tracked](#what-data-can-be-tracked)
1. [Telemetry systems overview](#telemetry-systems-overview)
+ 1. [Snowflake data warehouse](#snowflake-data-warehouse)
[Usage Ping Guide](usage_ping.md)
@@ -44,9 +45,9 @@ Telemetry Guide:
More useful links:
- [Telemetry Direction](https://about.gitlab.com/direction/telemetry/)
-- [Data Analysis Process](https://about.gitlab.com/handbook/business-ops/data-team/#-data-analysis-process)
-- [Data for Product Managers](https://about.gitlab.com/handbook/business-ops/data-team/data-for-product-managers/)
-- [Data Infrastructure](https://about.gitlab.com/handbook/business-ops/data-team/data-infrastructure/)
+- [Data Analysis Process](https://about.gitlab.com/handbook/business-ops/data-team/#data-analysis-process/)
+- [Data for Product Managers](https://about.gitlab.com/handbook/business-ops/data-team/programs/data-for-product-managers/)
+- [Data Infrastructure](https://about.gitlab.com/handbook/business-ops/data-team/platform/infrastructure/)
## Our tracking tools
@@ -68,7 +69,7 @@ For more details, read the [Usage Ping](usage_ping.md) guide.
### Database import
-Database imports are full imports of data into GitLab's data warehouse. For GitLab.com, the PostgreSQL database is loaded into Snowflake data warehouse every 6 hours. For more details, see the [data team handbook](https://about.gitlab.com/handbook/business-ops/data-team/#extract-and-load).
+Database imports are full imports of data into GitLab's data warehouse. For GitLab.com, the PostgreSQL database is loaded into Snowflake data warehouse every 6 hours. For more details, see the [data team handbook](https://about.gitlab.com/handbook/business-ops/data-team/platform/#extract-and-load).
### Log system
@@ -144,7 +145,7 @@ The systems overview is a simplified diagram showing the interactions between Gi
For Telemetry purposes, GitLab Inc has three major components:
-1. [Data Infrastructure](https://about.gitlab.com/handbook/business-ops/data-team/data-infrastructure/): This contains everything managed by our data team including Sisense Dashboards for visualization, Snowflake for Data Warehousing, incoming data sources such as PostgreSQL Pipeline and S3 Bucket, and lastly our data collectors [GitLab.com's Snowplow Collector](https://about.gitlab.com/handbook/engineering/infrastructure/library/snowplow/) and GitLab's Versions Application.
+1. [Data Infrastructure](https://about.gitlab.com/handbook/business-ops/data-team/platform/infrastructure/): This contains everything managed by our data team including Sisense Dashboards for visualization, Snowflake for Data Warehousing, incoming data sources such as PostgreSQL Pipeline and S3 Bucket, and lastly our data collectors [GitLab.com's Snowplow Collector](https://about.gitlab.com/handbook/engineering/infrastructure/library/snowplow/) and GitLab's Versions Application.
1. GitLab.com: This is the production GitLab application which is made up of a Client and Server. On the Client or browser side, a Snowplow JS Tracker (Frontend) is used to track client-side events. On the Server or application side, a Snowplow Ruby Tracker (Backend) is used to track server-side events. The server also contains Usage Ping which leverages a PostgreSQL database and a Redis in-memory data store to report on usage data. Lastly, the server also contains System Logs which are generated from running the GitLab application.
1. [Monitoring infrastructure](https://about.gitlab.com/handbook/engineering/monitoring/): This is the infrastructure used to ensure GitLab.com is operating smoothly. System Logs are sent from GitLab.com to our monitoring infrastructure and collected by a FluentD collector. From FluentD, logs are either sent to long term Google Cloud Services cold storage via Stackdriver, or, they are sent to our Elastic Cluster via Cloud Pub/Sub which can be explored in real-time using Kibana.
@@ -169,3 +170,19 @@ The differences between GitLab.com and self-managed are summarized below:
| Self-Managed | **{dotted-circle}**(1) | **{dotted-circle}**(1) | **{check-circle}** | **{dotted-circle}** | **{dotted-circle}** |
Note (1): Snowplow JS and Snowplow Ruby are available on self-managed, however, the Snowplow Collector endpoint is set to a self-managed Snowplow Collector which GitLab Inc does not have access to.
+
+## Snowflake data warehouse
+
+The Snowflake data warehouse is where we keep all of GitLab Inc's data.
+
+### Data sources
+
+There are several data sources available in Snowflake and Sisense each representing a different view of the data along the transformation pipeline.
+
+| Source | Description | Access |
+| ------ | ------ | ------ |
+| raw | These tables are the raw data source | Access via Snowflake |
+| analytics_staging | These tables have undergone little to no data transformation, meaning they're basically clones of the raw data source | Access via Snowflake or Sisense |
+| analytics | These tables have typically undergone more data transformation. They will typically end in `_xf` to represent the fact that they are transformed | Access via Snowflake or Sisense |
+
+If you are a Product Manager interested in the raw data, you will likely focus on the `analytics` and `analytics_staging` sources. The raw source is limited to the data and infrastructure teams. For more information, please see [Data For Product Managers: What's the difference between analytics_staging and analytics?](https://about.gitlab.com/handbook/business-ops/data-team/programs/data-for-product-managers/#whats-the-difference-between-analytics_staging-and-analytics)
diff --git a/doc/development/telemetry/snowplow.md b/doc/development/telemetry/snowplow.md
index b7090ee4d20..f03742afe2d 100644
--- a/doc/development/telemetry/snowplow.md
+++ b/doc/development/telemetry/snowplow.md
@@ -6,15 +6,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w
# Snowplow Guide
-This guide provides a details about how Snowplow works. It includes the following sections:
-
-1. [What is Snowplow](#what-is-snowplow)
-1. [Snowplow schema](#snowplow-schema)
-1. [Enabling Snowplow](#enabling-snowplow)
-1. [Snowplow request flow](#snowplow-request-flow)
-1. [Implementing Snowplow JS (Frontend) tracking](#implementing-snowplow-js-frontend-tracking)
-1. [Implementing Snowplow Ruby (Backend) tracking](#implementing-snowplow-ruby-backend-tracking)
-1. [Developing and testing Snowplow](#developing-and-testing-snowplow)
+This guide provides an overview of how Snowplow works, and implementation details.
For more information about Telemetry, see:
@@ -24,32 +16,31 @@ For more information about Telemetry, see:
More useful links:
- [Telemetry Direction](https://about.gitlab.com/direction/telemetry/)
-- [Data Analysis Process](https://about.gitlab.com/handbook/business-ops/data-team/#-data-analysis-process)
-- [Data for Product Managers](https://about.gitlab.com/handbook/business-ops/data-team/data-for-product-managers/)
-- [Data Infrastructure](https://about.gitlab.com/handbook/business-ops/data-team/data-infrastructure/)
+- [Data Analysis Process](https://about.gitlab.com/handbook/business-ops/data-team/#data-analysis-process/)
+- [Data for Product Managers](https://about.gitlab.com/handbook/business-ops/data-team/programs/data-for-product-managers/)
+- [Data Infrastructure](https://about.gitlab.com/handbook/business-ops/data-team/platform/infrastructure/)
## What is Snowplow
Snowplow is an enterprise-grade marketing and product analytics platform which helps track the way users engage with our website and application.
-From [Snowplow's documentation](https://github.com/snowplow/snowplow), Snowplow consists of six loosely-coupled sub-systems:
+[Snowplow](https://github.com/snowplow/snowplow) consists of the following loosely-coupled sub-systems:
-- **Trackers** fire Snowplow events. Currently Snowplow has 12 trackers, covering web, mobile, desktop, server and IoT
-- **Collectors** receive Snowplow events from trackers. Currently we have three different event collectors, sinking events either to Amazon S3, Apache Kafka or Amazon Kinesis
-- **Enrich** cleans up the raw Snowplow events, enriches them and puts them into storage. Currently we have a Hadoop-based enrichment process, and a Kinesis- or Kafka-based process
-- **Storage** is where the Snowplow events live. Currently we store the Snowplow events in a flat file structure on S3, and in the Redshift and PostgreSQL databases
-- **Data modeling** is where event-level data is joined with other data sets and aggregated into smaller data sets, and business logic is applied. This produces a clean set of tables which make it easier to perform analysis on the data. We have data models for Redshift and Looker
+- **Trackers** fire Snowplow events. Snowplow has 12 trackers, covering web, mobile, desktop, server, and IoT.
+- **Collectors** receive Snowplow events from trackers. We have three different event collectors, synchronizing events either to Amazon S3, Apache Kafka, or Amazon Kinesis.
+- **Enrich** cleans up the raw Snowplow events, enriches them and puts them into storage. We have an Hadoop-based enrichment process, and a Kinesis-based or Kafka-based process.
+- **Storage** is where the Snowplow events live. We store the Snowplow events in a flat file structure on S3, and in the Redshift and PostgreSQL databases.
+- **Data modeling** is where event-level data is joined with other data sets and aggregated into smaller data sets, and business logic is applied. This produces a clean set of tables which make it easier to perform analysis on the data. We have data models for Redshift and Looker.
- **Analytics** are performed on the Snowplow events or on the aggregate tables.
![snowplow_flow](../img/snowplow_flow.png)
-> ![snowplow_flow](../img/snowplow_flow.png)
## Snowplow schema
-We currently have many definitions of Snowplow's schema. We have an active issue to [standardize this schema](https://gitlab.com/gitlab-org/gitlab/-/issues/207930) including the following definitions:
+We have many definitions of Snowplow's schema. We have an active issue to [standardize this schema](https://gitlab.com/gitlab-org/gitlab/-/issues/207930) including the following definitions:
- Frontend and backend taxonomy as listed below
-- [Feature instrumentation taxonomy](https://about.gitlab.com/handbook/product/feature-instrumentation/#taxonomy)
+- [Feature instrumentation taxonomy](https://about.gitlab.com/handbook/product/product-processes/#taxonomy)
- [Self describing events](https://github.com/snowplow/snowplow/wiki/Custom-events#self-describing-events)
- [Iglu schema](https://gitlab.com/gitlab-org/iglu/)
- [Snowplow authored events](https://github.com/snowplow/snowplow/wiki/Snowplow-authored-events)
@@ -58,8 +49,8 @@ We currently have many definitions of Snowplow's schema. We have an active issue
Tracking can be enabled at:
-- The instance level, which will enable tracking on both the frontend and backend layers.
-- User level, though user tracking can be disabled on a per-user basis. GitLab tracking respects the [Do Not Track](https://www.eff.org/issues/do-not-track) standard, so any user who has enabled the Do Not Track option in their browser will also not be tracked from a user level.
+- The instance level, which enables tracking on both the frontend and backend layers.
+- User level, though user tracking can be disabled on a per-user basis. GitLab tracking respects the [Do Not Track](https://www.eff.org/issues/do-not-track) standard, so any user who has enabled the Do Not Track option in their browser is not tracked at a user level.
We utilize Snowplow for the majority of our tracking strategy and it is enabled on GitLab.com. On a self-managed instance, Snowplow can be enabled by navigating to:
@@ -69,14 +60,20 @@ We utilize Snowplow for the majority of our tracking strategy and it is enabled
The following configuration is required:
| Name | Value |
-| ------------- | ------------------------- |
+|---------------|---------------------------|
| Collector | `snowplow.trx.gitlab.net` |
| Site ID | `gitlab` |
| Cookie domain | `.gitlab.com` |
## Snowplow request flow
-The following example shows a basic request/response flow between a Snowplow JS / Ruby Trackers on GitLab.com, [the GitLab.com Snowplow Collector](https://about.gitlab.com/handbook/engineering/infrastructure/library/snowplow/), GitLab's S3 Bucket, GitLab's Snowflake Data Warehouse, and Sisense.:
+The following example shows a basic request/response flow between the following components:
+
+- Snowplow JS / Ruby Trackers on GitLab.com
+- [GitLab.com Snowplow Collector](https://gitlab.com/gitlab-com/gl-infra/readiness/-/blob/master/library/snowplow/index.md)
+- GitLab's S3 Bucket
+- GitLab's Snowflake Data Warehouse
+- Sisense:
```mermaid
sequenceDiagram
@@ -101,17 +98,17 @@ sequenceDiagram
## Implementing Snowplow JS (Frontend) tracking
-GitLab provides `Tracking`, an interface that wraps the [Snowplow JavaScript Tracker](https://github.com/snowplow/snowplow/wiki/javascript-tracker) for tracking custom events. There are a few ways to utilize tracking, but each generally requires at minimum, a `category` and an `action`. Additional data can be provided that adheres to our [Feature instrumentation taxonomy](https://about.gitlab.com/handbook/product/feature-instrumentation/#taxonomy).
+GitLab provides `Tracking`, an interface that wraps the [Snowplow JavaScript Tracker](https://github.com/snowplow/snowplow/wiki/javascript-tracker) for tracking custom events. There are a few ways to utilize tracking, but each generally requires at minimum, a `category` and an `action`. Additional data can be provided that adheres to our [Feature instrumentation taxonomy](https://about.gitlab.com/handbook/product/product-processes/#taxonomy).
-| field | type | default value | description |
-|:-----------|:-------|:---------------------------|:------------|
-| `category` | string | document.body.dataset.page | Page or subsection of a page that events are being captured within. |
+| field | type | default value | description |
+|:-----------|:-------|:---------------------------|:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| `category` | string | document.body.dataset.page | Page or subsection of a page that events are being captured within. |
| `action` | string | 'generic' | Action the user is taking. Clicks should be `click` and activations should be `activate`, so for example, focusing a form field would be `activate_form_input`, and clicking a button would be `click_button`. |
-| `data` | object | {} | Additional data such as `label`, `property`, `value`, and `context` as described [in our Feature Instrumentation taxonomy](https://about.gitlab.com/handbook/product/feature-instrumentation/#taxonomy). |
+| `data` | object | {} | Additional data such as `label`, `property`, `value`, and `context` as described [in our Feature Instrumentation taxonomy](https://about.gitlab.com/handbook/product/product-processes/#taxonomy). |
### Tracking in HAML (or Vue Templates)
-When working within HAML (or Vue templates) we can add `data-track-*` attributes to elements of interest. All elements that have a `data-track-event` attribute will automatically have event tracking bound on clicks.
+When working within HAML (or Vue templates) we can add `data-track-*` attributes to elements of interest. All elements that have a `data-track-event` attribute automatically have event tracking bound on clicks.
Below is an example of `data-track-*` attributes assigned to a button:
@@ -127,17 +124,17 @@ Below is an example of `data-track-*` attributes assigned to a button:
/>
```
-Event listeners are bound at the document level to handle click events on or within elements with these data attributes. This allows for them to be properly handled on re-rendering and changes to the DOM, but it's important to know that because of the way these events are bound, click events shouldn't be stopped from propagating up the DOM tree. If for any reason click events are being stopped from propagating, you'll need to implement your own listeners and follow the instructions in [Tracking in raw JavaScript](#tracking-in-raw-javascript).
+Event listeners are bound at the document level to handle click events on or within elements with these data attributes. This allows them to be properly handled on re-rendering and changes to the DOM. Note that because of the way these events are bound, click events should not be stopped from propagating up the DOM tree. If for any reason click events are being stopped from propagating, you need to implement your own listeners and follow the instructions in [Tracking in raw JavaScript](#tracking-in-raw-javascript).
Below is a list of supported `data-track-*` attributes:
| attribute | required | description |
|:----------------------|:---------|:------------|
| `data-track-event` | true | Action the user is taking. Clicks must be prepended with `click` and activations must be prepended with `activate`. For example, focusing a form field would be `activate_form_input` and clicking a button would be `click_button`. |
-| `data-track-label` | false | The `label` as described [in our Feature Instrumentation taxonomy](https://about.gitlab.com/handbook/product/feature-instrumentation/#taxonomy). |
-| `data-track-property` | false | The `property` as described [in our Feature Instrumentation taxonomy](https://about.gitlab.com/handbook/product/feature-instrumentation/#taxonomy). |
-| `data-track-value` | false | The `value` as described [in our Feature Instrumentation taxonomy](https://about.gitlab.com/handbook/product/feature-instrumentation/#taxonomy). If omitted, this will be the element's `value` property or an empty string. For checkboxes, the default value will be the element's checked attribute or `false` when unchecked. |
-| `data-track-context` | false | The `context` as described [in our Feature Instrumentation taxonomy](https://about.gitlab.com/handbook/product/feature-instrumentation/#taxonomy). |
+| `data-track-label` | false | The `label` as described [in our Feature Instrumentation taxonomy](https://about.gitlab.com/handbook/product/product-processes/#taxonomy). |
+| `data-track-property` | false | The `property` as described [in our Feature Instrumentation taxonomy](https://about.gitlab.com/handbook/product/product-processes/#taxonomy). |
+| `data-track-value` | false | The `value` as described [in our Feature Instrumentation taxonomy](https://about.gitlab.com/handbook/product/product-processes/#taxonomy). If omitted, this is the element's `value` property or an empty string. For checkboxes, the default value is the element's checked attribute or `false` when unchecked. |
+| `data-track-context` | false | The `context` as described [in our Feature Instrumentation taxonomy](https://about.gitlab.com/handbook/product/product-processes/#taxonomy). |
### Tracking within Vue components
@@ -148,9 +145,9 @@ import Tracking from '~/tracking';
const trackingMixin = Tracking.mixin({ label: 'right_sidebar' });
```
-You can provide default options that will be passed along whenever an event is tracked from within your component. For instance, if all events within a component should be tracked with a given `label`, you can provide one at this time. Available defaults are `category`, `label`, `property`, and `value`. If no category is specified, `document.body.dataset.page` is used as the default.
+You can provide default options that are passed along whenever an event is tracked from within your component. For instance, if all events within a component should be tracked with a given `label`, you can provide one at this time. Available defaults are `category`, `label`, `property`, and `value`. If no category is specified, `document.body.dataset.page` is used as the default.
-You can then use the mixin normally in your component with the `mixin`, Vue declaration. The mixin also provides the ability to specify tracking options in `data` or `computed`. These will override any defaults and allows the values to be dynamic from props, or based on state.
+You can then use the mixin normally in your component with the `mixin` Vue declaration. The mixin also provides the ability to specify tracking options in `data` or `computed`. These override any defaults and allow the values to be dynamic from props, or based on state.
```javascript
export default {
@@ -240,7 +237,6 @@ describe('MyTracking', () => {
});
});
});
-
```
In obsolete Karma tests it's used as below:
@@ -278,13 +274,13 @@ GitLab provides `Gitlab::Tracking`, an interface that wraps the [Snowplow Ruby T
Custom event tracking and instrumentation can be added by directly calling the `GitLab::Tracking.event` class method, which accepts the following arguments:
-| argument | type | default value | description |
-|:-----------|:-------|:---------------------------|:------------|
-| `category` | string | 'application' | Area or aspect of the application. This could be `HealthCheckController` or `Lfs::FileTransformer` for instance. |
-| `action` | string | 'generic' | The action being taken, which can be anything from a controller action like `create` to something like an Active Record callback. |
-| `data` | object | {} | Additional data such as `label`, `property`, `value`, and `context` as described [in our Feature Instrumentation taxonomy](https://about.gitlab.com/handbook/product/feature-instrumentation/#taxonomy). These will be set as empty strings if you don't provide them. |
+| argument | type | default value | description |
+|:-----------|:-------|:--------------|:-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| `category` | string | 'application' | Area or aspect of the application. This could be `HealthCheckController` or `Lfs::FileTransformer` for instance. |
+| `action` | string | 'generic' | The action being taken, which can be anything from a controller action like `create` to something like an Active Record callback. |
+| `data` | object | {} | Additional data such as `label`, `property`, `value`, and `context` as described [in our Feature Instrumentation taxonomy](https://about.gitlab.com/handbook/product/feature-instrumentation/#taxonomy). These are set as empty strings if you don't provide them. |
-Tracking can be viewed as either tracking user behavior, or can be utilized for instrumentation to monitor and visual performance over time in an area or aspect of code.
+Tracking can be viewed as either tracking user behavior, or can be utilized for instrumentation to monitor and visualize performance over time in an area or aspect of code.
For example:
@@ -309,27 +305,27 @@ We use the [AsyncEmitter](https://github.com/snowplow/snowplow/wiki/Ruby-Tracker
There are several tools for developing and testing Snowplow Event
-| Testing Tool | Frontend Tracking | Backend Tracking | Local Development Environment | Production Environment |
-| ------ | ------ | ------ | ------ | ------ |
-| Snowplow Analytics Debugger Chrome Extension | ✅ | ⌠| ✅ | ✅ |
-| Snowplow Inspector Chrome Extension | ✅ | ⌠| ✅ | ✅ |
-| Snowplow Micro | ✅ | ✅ | ✅ | ⌠|
-| Snowplow Mini | ✅ | ✅ | ⌠| ✅ |
+| Testing Tool | Frontend Tracking | Backend Tracking | Local Development Environment | Production Environment |
+|----------------------------------------------|--------------------|---------------------|-------------------------------|------------------------|
+| Snowplow Analytics Debugger Chrome Extension | **{check-circle}** | **{dotted-circle}** | **{check-circle}** | **{check-circle}** |
+| Snowplow Inspector Chrome Extension | **{check-circle}** | **{dotted-circle}** | **{check-circle}** | **{check-circle}** |
+| Snowplow Micro | **{check-circle}** | **{check-circle}** | **{check-circle}** | **{dotted-circle}** |
+| Snowplow Mini | **{check-circle}** | **{check-circle}** | **{dotted-circle}** | **{check-circle}** |
### Snowplow Analytics Debugger Chrome Extension
Snowplow Analytics Debugger is a browser extension for testing frontend events. This works on production, staging and local development environments.
-1. Install [Snowplow Analytics Debugger](https://chrome.google.com/webstore/detail/snowplow-analytics-debugg/jbnlcgeengmijcghameodeaenefieedm) chrome browser extension
-1. Open Chrome DevTools to the Snowplow Analytics Debugger tab
-1. Learn more at [Igloo Analytics](https://www.iglooanalytics.com/blog/snowplow-analytics-debugger-chrome-extension.html)
+1. Install the [Snowplow Analytics Debugger](https://chrome.google.com/webstore/detail/snowplow-analytics-debugg/jbnlcgeengmijcghameodeaenefieedm) Chrome browser extension.
+1. Open Chrome DevTools to the Snowplow Analytics Debugger tab.
+1. Learn more at [Igloo Analytics](https://www.iglooanalytics.com/blog/snowplow-analytics-debugger-chrome-extension.html).
### Snowplow Inspector Chrome Extension
Snowplow Inspector Chrome Extension is a browser extension for testing frontend events. This works on production, staging and local development environments.
-1. Install [Snowplow Inspector](https://chrome.google.com/webstore/detail/snowplow-inspector/maplkdomeamdlngconidoefjpogkmljm?hl=en)
-1. Open the chrome extension by pressing the Snowplow Inspector icon beside the address bar
+1. Install [Snowplow Inspector](https://chrome.google.com/webstore/detail/snowplow-inspector/maplkdomeamdlngconidoefjpogkmljm?hl=en).
+1. Open the Chrome extension by pressing the Snowplow Inspector icon beside the address bar.
1. Click around on a webpage with Snowplow and you should see JavaScript events firing in the inspector window.
### Snowplow Micro
@@ -342,53 +338,59 @@ Snowplow Micro is a Docker-based solution for testing frontend and backend event
- Look at the [Snowplow Micro repository](https://github.com/snowplow-incubator/snowplow-micro)
- Watch our [installation guide recording](https://www.youtube.com/watch?v=OX46fo_A0Ag)
-1. Install [Snowplow Micro](https://github.com/snowplow-incubator/snowplow-micro)
+1. Install [Snowplow Micro](https://github.com/snowplow-incubator/snowplow-micro):
-``` bash
-docker run --mount type=bind,source=$(pwd)/example,destination=/config -p 9090:9090 snowplow/snowplow-micro:latest --collector-config /config/micro.conf --iglu /config/iglu.json
-```
+ ```shell
+ docker run --mount type=bind,source=$(pwd)/example,destination=/config -p 9090:9090 snowplow/snowplow-micro:latest --collector-config /config/micro.conf --iglu /config/iglu.json
+ ```
-1. Install snowplow micro by cloning the settings in [this project](https://gitlab.com/a_akgun/snowplow-micro).
+1. Install snowplow micro by cloning the settings in [this project](https://gitlab.com/a_akgun/snowplow-micro):
- ``` bash
- git clone git@gitlab.com:a_akgun/snowplow-micro.git
- ./snowplow-micro.sh
- ```
+ ```shell
+ git clone git@gitlab.com:a_akgun/snowplow-micro.git
+ ./snowplow-micro.sh
+ ```
-1. Update port in SQL (needed to set 9090)
+1. Update port in SQL to set `9090`:
- ``` bash
- gdk psql -d gitlabhq_development
- update application_settings set snowplow_collector_hostname='localhost:9090', snowplow_enabled=true, snowplow_cookie_domain='.gitlab.com';
- ```
+ ```shell
+ gdk psql -d gitlabhq_development
+ update application_settings set snowplow_collector_hostname='localhost:9090', snowplow_enabled=true, snowplow_cookie_domain='.gitlab.com';
+ ```
1. Update `app/assets/javascripts/tracking.js` to [remove this line](https://gitlab.com/snippets/1918635):
- ``` javascript
- forceSecureTracker: true
- ```
+ ```javascript
+ forceSecureTracker: true
+ ```
1. Update `lib/gitlab/tracking.rb` to [add these lines](https://gitlab.com/snippets/1918635):
- ``` ruby
- protocol: 'http',
- port: 9090,
- ```
+ ```ruby
+ protocol: 'http',
+ port: 9090,
+ ```
1. Update `lib/gitlab/tracking.rb` to [change async emitter from https to http](https://gitlab.com/snippets/1918635):
- ``` ruby
- SnowplowTracker::AsyncEmitter.new(Gitlab::CurrentSettings.snowplow_collector_hostname, protocol: 'http'),
- ```
+ ```ruby
+ SnowplowTracker::AsyncEmitter.new(Gitlab::CurrentSettings.snowplow_collector_hostname, protocol: 'http'),
+ ```
1. Enable Snowplow in the admin area, Settings::Integrations::Snowplow to point to:
- `http://localhost:3000/admin/application_settings/integrations#js-snowplow-settings`
-1. `gdk restart`
-1. Send a test Snowplow event from the Rails console
+ `http://localhost:3000/admin/application_settings/integrations#js-snowplow-settings`.
+
+1. Restart GDK:
+
+ ```shell
+ `gdk restart`
+ ```
+
+1. Send a test Snowplow event from the Rails console:
- ``` ruby
- Gitlab::Tracking.self_describing_event('iglu:com.gitlab/pageview_context/jsonschema/1-0-0', { page_type: ‘MY_TYPE' }, context: nil )
- ```
+ ```ruby
+ Gitlab::Tracking.self_describing_event('iglu:com.gitlab/pageview_context/jsonschema/1-0-0', { page_type: ‘MY_TYPE' }, context: nil )
+ ```
### Snowplow Mini
@@ -396,4 +398,4 @@ docker run --mount type=bind,source=$(pwd)/example,destination=/config -p 9090:9
Snowplow Mini can be used for testing frontend and backend events on a production, staging and local development environment.
-For GitLab.com, we are currently setting up a [QA and Testing environment](https://gitlab.com/gitlab-org/telemetry/-/issues/266) using Snowplow Mini.
+For GitLab.com, we're setting up a [QA and Testing environment](https://gitlab.com/gitlab-org/telemetry/-/issues/266) using Snowplow Mini.
diff --git a/doc/development/telemetry/usage_ping.md b/doc/development/telemetry/usage_ping.md
index 0f438e02772..5e78e2c5f25 100644
--- a/doc/development/telemetry/usage_ping.md
+++ b/doc/development/telemetry/usage_ping.md
@@ -21,9 +21,9 @@ For more information about Telemetry, see:
More useful links:
- [Telemetry Direction](https://about.gitlab.com/direction/telemetry/)
-- [Data Analysis Process](https://about.gitlab.com/handbook/business-ops/data-team/#-data-analysis-process)
-- [Data for Product Managers](https://about.gitlab.com/handbook/business-ops/data-team/data-for-product-managers/)
-- [Data Infrastructure](https://about.gitlab.com/handbook/business-ops/data-team/data-infrastructure/)
+- [Data Analysis Process](https://about.gitlab.com/handbook/business-ops/data-team/#data-analysis-process/)
+- [Data for Product Managers](https://about.gitlab.com/handbook/business-ops/data-team/programs/data-for-product-managers/)
+- [Data Infrastructure](https://about.gitlab.com/handbook/business-ops/data-team/platform/infrastructure/)
## What is Usage Ping?
@@ -299,7 +299,7 @@ Paste the SQL query into `#database-lab` to see how the query performs at scale.
- `#database-lab` is a Slack channel which uses a production-sized environment to test your queries.
- GitLab.com’s production database has a 15 second timeout.
-- For each query we require an execution time of under 1 second due to cold caches which can 10x this time.
+- Any single query must stay below 1 second execution time with cold caches.
- Add a specialized index on columns involved to reduce the execution time.
In order to have an understanding of the query's execution we add in the MR description the following information:
@@ -326,7 +326,17 @@ When adding, changing, or updating metrics, please update the [Usage Statistics
Check if new metrics need to be added to the Versions Application. See `usage_data` [schema](https://gitlab.com/gitlab-services/version-gitlab-com/-/blob/master/db/schema.rb#L147) and usage data [parameters accepted](https://gitlab.com/gitlab-services/version-gitlab-com/-/blob/master/app/services/usage_ping.rb). Any metrics added under the `counts` key are saved in the `counts` column.
-### 6. Ask for a Telemetry Review
+For further details, see the [Process to add additional instrumentation to the Usage Ping](https://about.gitlab.com/handbook/product/product-processes/#process-to-add-additional-instrumentation-to-the-usage-ping).
+
+### 6. Add the feature label
+
+Add the `feature` label to the Merge Request for new Usage Ping metrics. These are user-facing changes and are part of expanding the Usage Ping feature.
+
+### 7. Add a changelog file
+
+Ensure you comply with the [Changelog entries guide](../changelog.md).
+
+### 8. Ask for a Telemetry Review
On GitLab.com, we have DangerBot setup to monitor Telemetry related files and DangerBot will recommend a Telemetry review. Mention `@gitlab-org/growth/telemetry/engineers` in your MR for a review.
@@ -378,275 +388,355 @@ appear to be associated to any of the services running, since they all appear to
## Usage Statistics definitions
-| Statistic | Section | Stage | Tier | Description |
-|:--------------------------------------------------------|:-----------------------------------|:------------|:---------------|:--------------------------------------------------|
-| `uuid` | | | | |
-| `hostname` | | | | |
-| `version` | | | | |
-| `installation_type` | | | | |
-| `active_user_count` | | | | |
-| `recorded_at` | | | | |
-| `edition` | | | | |
-| `license_md5` | | | | |
-| `license_id` | | | | |
-| `historical_max_users` | | | | |
-| `Name` | `licensee` | | | |
-| `Email` | `licensee` | | | |
-| `Company` | `licensee` | | | |
-| `license_user_count` | | | | |
-| `license_starts_at` | | | | |
-| `license_expires_at` | | | | |
-| `license_plan` | | | | |
-| `license_trial` | | | | |
-| `assignee_lists` | `counts` | | | |
-| `boards` | `counts` | | | |
-| `ci_builds` | `counts` | `verify` | | Unique builds in project |
-| `ci_internal_pipelines` | `counts` | `verify` | | Total pipelines in GitLab repositories |
-| `ci_external_pipelines` | `counts` | `verify` | | Total pipelines in external repositories |
-| `ci_pipeline_config_auto_devops` | `counts` | `verify` | | Total pipelines from an Auto DevOps template |
-| `ci_pipeline_config_repository` | `counts` | `verify` | | Total Pipelines from templates in repository |
-| `ci_runners` | `counts` | `verify` | | Total configured Runners in project |
-| `ci_triggers` | `counts` | `verify` | | Total configured Triggers in project |
-| `ci_pipeline_schedules` | `counts` | `verify` | | Pipeline schedules in GitLab |
-| `auto_devops_enabled` | `counts` |`configure` | | Projects with Auto DevOps template enabled |
-| `auto_devops_disabled` | `counts` |`configure` | | Projects with Auto DevOps template disabled |
-| `deploy_keys` | `counts` | | | |
-| `deployments` | `counts` |`release` | | Total deployments |
-| `dast_jobs` | `counts` | | | |
-| `successful_deployments` | `counts` |`release` | | Total successful deployments |
-| `failed_deployments` | `counts` |`release` | | Total failed deployments |
-| `environments` | `counts` |`release` | | Total available and stopped environments |
-| `clusters` | `counts` |`configure` | | Total GitLab Managed clusters both enabled and disabled |
-| `clusters_enabled` | `counts` |`configure` | | Total GitLab Managed clusters currently enabled |
-| `project_clusters_enabled` | `counts` |`configure` | | Total GitLab Managed clusters attached to projects|
-| `group_clusters_enabled` | `counts` |`configure` | | Total GitLab Managed clusters attached to groups |
-| `instance_clusters_enabled` | `counts` |`configure` | | Total GitLab Managed clusters attached to the instance |
-| `clusters_disabled` | `counts` |`configure` | | Total GitLab Managed disabled clusters |
-| `project_clusters_disabled` | `counts` |`configure` | | Total GitLab Managed disabled clusters previously attached to projects |
-| `group_clusters_disabled` | `counts` |`configure` | | Total GitLab Managed disabled clusters previously attached to groups |
-| `instance_clusters_disabled` | `counts` |`configure` | | Total GitLab Managed disabled clusters previously attached to the instance |
-| `clusters_platforms_eks` | `counts` |`configure` | | Total GitLab Managed clusters provisioned with GitLab on AWS EKS |
-| `clusters_platforms_gke` | `counts` |`configure` | | Total GitLab Managed clusters provisioned with GitLab on GCE GKE |
-| `clusters_platforms_user` | `counts` |`configure` | | Total GitLab Managed clusters that are user provisioned |
-| `clusters_applications_helm` | `counts` |`configure` | | Total GitLab Managed clusters with Helm enabled |
-| `clusters_applications_ingress` | `counts` |`configure` | | Total GitLab Managed clusters with Ingress enabled |
-| `clusters_applications_cert_managers` | `counts` |`configure` | | Total GitLab Managed clusters with Cert Manager enabled |
-| `clusters_applications_crossplane` | `counts` |`configure` | | Total GitLab Managed clusters with Crossplane enabled |
-| `clusters_applications_prometheus` | `counts` |`configure` | | Total GitLab Managed clusters with Prometheus enabled |
-| `clusters_applications_runner` | `counts` |`configure` | | Total GitLab Managed clusters with Runner enabled |
-| `clusters_applications_knative` | `counts` |`configure` | | Total GitLab Managed clusters with Knative enabled |
-| `clusters_applications_elastic_stack` | `counts` |`configure` | | Total GitLab Managed clusters with Elastic Stack enabled |
-| `clusters_management_project` | `counts` |`configure` | | Total GitLab Managed clusters with defined cluster management project |
-| `in_review_folder` | `counts` | | | |
-| `grafana_integrated_projects` | `counts` | | | |
-| `groups` | `counts` | | | |
-| `issues` | `counts` | | | |
-| `issues_created_from_gitlab_error_tracking_ui` | `counts` | `monitor` | | |
-| `issues_with_associated_zoom_link` | `counts` | `monitor` | | |
-| `issues_using_zoom_quick_actions` | `counts` | `monitor` | | |
-| `issues_with_embedded_grafana_charts_approx` | `counts` | `monitor` | | |
-| `issues_with_health_status` | `counts` | | | |
-| `keys` | `counts` | | | |
-| `label_lists` | `counts` | | | |
-| `lfs_objects` | `counts` | | | |
-| `milestone_lists` | `counts` | | | |
-| `milestones` | `counts` | | | |
-| `pages_domains` | `counts` |`release` | | Total GitLab Pages domains |
-| `pool_repositories` | `counts` | | | |
-| `projects` | `counts` | | | |
-| `projects_imported_from_github` | `counts` | | | |
-| `projects_with_repositories_enabled` | `counts` | | | |
-| `projects_with_error_tracking_enabled` | `counts` | `monitor` | | |
-| `protected_branches` | `counts` | | | |
-| `releases` | `counts` |`release` | | Unique release tags |
-| `remote_mirrors` | `counts` | | | |
-| `requirements_created` | `counts` | | | |
-| `snippets` | `counts` | | | |
-| `suggestions` | `counts` | | | |
-| `todos` | `counts` | | | |
-| `uploads` | `counts` | | | |
-| `web_hooks` | `counts` | | | |
-| `projects_alerts_active` | `counts` | | | |
-| `projects_asana_active` | `counts` | | | |
-| `projects_assembla_active` | `counts` | | | |
-| `projects_bamboo_active` | `counts` | | | |
-| `projects_bugzilla_active` | `counts` | | | |
-| `projects_buildkite_active` | `counts` | | | |
-| `projects_campfire_active` | `counts` | | | |
-| `projects_custom_issue_tracker_active` | `counts` | | | |
-| `projects_discord_active` | `counts` | | | |
-| `projects_drone_ci_active` | `counts` | | | |
-| `projects_emails_on_push_active` | `counts` | | | |
-| `projects_external_wiki_active` | `counts` | | |
-| `projects_flowdock_active` | `counts` | | | |
-| `projects_github_active` | `counts` | | | |
-| `projects_hangouts_chat_active` | `counts` | | | |
-| `projects_hipchat_active` | `counts` | | | |
-| `projects_irker_active` | `counts` | | | |
-| `projects_jenkins_active` | `counts` | | | |
-| `projects_jira_active` | `counts` | | | |
-| `projects_mattermost_active` | `counts` | | | |
-| `projects_mattermost_slash_commands_active` | `counts` | | | |
-| `projects_microsoft_teams_active` | `counts` | | | |
-| `projects_packagist_active` | `counts` | | | |
-| `projects_pipelines_email_active` | `counts` | | | |
-| `projects_pivotaltracker_active` | `counts` | | | |
-| `projects_prometheus_active` | `counts` | | | |
-| `projects_pushover_active` | `counts` | | | |
-| `projects_redmine_active` | `counts` | | | |
-| `projects_slack_active` | `counts` | | | |
-| `projects_slack_slash_commands_active` | `counts` | | | |
-| `projects_teamcity_active` | `counts` | | | |
-| `projects_unify_circuit_active` | `counts` | | | |
-| `projects_webex_teams_active` | `counts` | | | |
-| `projects_youtrack_active` | `counts` | | | |
-| `projects_slack_notifications_active` | `counts` | | | |
-| `projects_slack_slash_active` | `counts` | | | |
-| `projects_jira_server_active` | `counts` | | | |
-| `projects_jira_cloud_active` | `counts` | | | |
-| `projects_jira_dvcs_cloud_active` | `counts` | | | |
-| `projects_jira_dvcs_server_active` | `counts` | | | |
-| `labels` | `counts` | | | |
-| `merge_requests` | `counts` | | | |
-| `merge_requests_users` | `counts` | | | |
-| `notes` | `counts` | | | |
-| `wiki_pages_create` | `counts` | | | |
-| `wiki_pages_update` | `counts` | | | |
-| `wiki_pages_delete` | `counts` | | | |
-| `web_ide_commits` | `counts` | | | |
-| `web_ide_views` | `counts` | | | |
-| `web_ide_merge_requests` | `counts` | | | |
-| `web_ide_previews` | `counts` | | | |
-| `snippet_comment` | `counts` | | | |
-| `commit_comment` | `counts` | | | |
-| `merge_request_comment` | `counts` | | | |
-| `snippet_create` | `counts` | | | |
-| `snippet_update` | `counts` | | | |
-| `navbar_searches` | `counts` | | | |
-| `cycle_analytics_views` | `counts` | | | |
-| `productivity_analytics_views` | `counts` | | | |
-| `source_code_pushes` | `counts` | | | |
-| `merge_request_create` | `counts` | | | |
-| `design_management_designs_create` | `counts` | | | |
-| `design_management_designs_update` | `counts` | | | |
-| `design_management_designs_delete` | `counts` | | | |
-| `licenses_list_views` | `counts` | | | |
-| `user_preferences_group_overview_details` | `counts` | | | |
-| `user_preferences_group_overview_security_dashboard` | `counts` | | | |
-| `ingress_modsecurity_logging` | `counts` | | | |
-| `ingress_modsecurity_blocking` | `counts` | | | |
-| `ingress_modsecurity_disabled` | `counts` | | | |
-| `ingress_modsecurity_not_installed` | `counts` | | | |
-| `dependency_list_usages_total` | `counts` | | | |
-| `epics` | `counts` | | | |
-| `feature_flags` | `counts` | | | |
-| `geo_nodes` | `counts` | `geo` | | Number of sites in a Geo deployment |
-| `geo_event_log_max_id` | `counts` | `geo` | | Number of replication events on a Geo primary |
-| `incident_issues` | `counts` | `monitor` | | Issues created by the alert bot |
-| `alert_bot_incident_issues` | `counts` | `monitor` | | Issues created by the alert bot |
-| `incident_labeled_issues` | `counts` | `monitor` | | Issues with the incident label |
-| `issues_created_gitlab_alerts` | `counts` | `monitor` | | Issues created from alerts by non-alert bot users |
-| `issues_created_manually_from_alerts` | `counts` | `monitor` | | Issues created from alerts by non-alert bot users |
-| `issues_created_from_alerts` | `counts` | `monitor` | | Issues created from Prometheus and alert management alerts |
-| `ldap_group_links` | `counts` | | | |
-| `ldap_keys` | `counts` | | | |
-| `ldap_users` | `counts` | | | |
-| `pod_logs_usages_total` | `counts` | | | |
-| `projects_enforcing_code_owner_approval` | `counts` | | | |
-| `projects_mirrored_with_pipelines_enabled` | `counts` |`release` | | Projects with repository mirroring enabled |
-| `projects_reporting_ci_cd_back_to_github` | `counts` |`verify` | | Projects with a GitHub service pipeline enabled |
-| `projects_with_packages` | `counts` |`package` | | Projects with package registry configured |
-| `projects_with_prometheus_alerts` | `counts` |`monitor` | | Projects with Prometheus alerting enabled |
-| `projects_with_tracing_enabled` | `counts` |`monitor` | | Projects with tracing enabled |
-| `projects_with_alerts_service_enabled` | `counts` |`monitor` | | Projects with alerting service enabled |
-| `template_repositories` | `counts` | | | |
-| `container_scanning_jobs` | `counts` | | | |
-| `dependency_scanning_jobs` | `counts` | | | |
-| `license_management_jobs` | `counts` | | | |
-| `sast_jobs` | `counts` | | | |
-| `status_page_projects` | `counts` | `monitor` | | Projects with status page enabled |
-| `status_page_issues` | `counts` | `monitor` | | Issues published to a Status Page |
-| `status_page_incident_publishes` | `counts` | `monitor` | | Cumulative count of usages of publish operation |
-| `status_page_incident_unpublishes` | `counts` | `monitor` | | Cumulative count of usages of unpublish operation |
-| `epics_deepest_relationship_level` | `counts` | | | |
-| `operations_dashboard_default_dashboard` | `counts` | `monitor` | | Active users with enabled operations dashboard |
-| `operations_dashboard_users_with_projects_added` | `counts` | `monitor` | | Active users with projects on operations dashboard|
-| `container_registry_enabled` | | | | |
-| `dependency_proxy_enabled` | | | | |
-| `gitlab_shared_runners_enabled` | | | | |
-| `gravatar_enabled` | | | | |
-| `ldap_enabled` | | | | |
-| `mattermost_enabled` | | | | |
-| `omniauth_enabled` | | | | |
-| `prometheus_metrics_enabled` | | | | |
-| `reply_by_email_enabled` | | | | |
-| `average` | `avg_cycle_analytics - code` | | | |
-| `sd` | `avg_cycle_analytics - code` | | | |
-| `missing` | `avg_cycle_analytics - code` | | | |
-| `average` | `avg_cycle_analytics - test` | | | |
-| `sd` | `avg_cycle_analytics - test` | | | |
-| `missing` | `avg_cycle_analytics - test` | | | |
-| `average` | `avg_cycle_analytics - review` | | | |
-| `sd` | `avg_cycle_analytics - review` | | | |
-| `missing` | `avg_cycle_analytics - review` | | | |
-| `average` | `avg_cycle_analytics - staging` | | | |
-| `sd` | `avg_cycle_analytics - staging` | | | |
-| `missing` | `avg_cycle_analytics - staging` | | | |
-| `average` | `avg_cycle_analytics - production` | | | |
-| `sd` | `avg_cycle_analytics - production` | | | |
-| `missing` | `avg_cycle_analytics - production` | | | |
-| `total` | `avg_cycle_analytics` | | | |
-| `clusters_applications_cert_managers` | `usage_activity_by_stage` | `configure` | | Unique clusters with certificate managers enabled |
-| `clusters_applications_helm` | `usage_activity_by_stage` | `configure` | | Unique clusters with Helm enabled |
-| `clusters_applications_ingress` | `usage_activity_by_stage` | `configure` | | Unique clusters with Ingress enabled |
-| `clusters_applications_knative` | `usage_activity_by_stage` | `configure` | | Unique clusters with Knative enabled |
-| `clusters_management_project` | `usage_activity_by_stage` | `configure` | | Unique clusters with project management enabled |
-| `clusters_disabled` | `usage_activity_by_stage` | `configure` | | Total non-"GitLab Managed clusters" |
-| `clusters_enabled` | `usage_activity_by_stage` | `configure` | | Total GitLab Managed clusters |
-| `clusters_platforms_gke` | `usage_activity_by_stage` | `configure` | | Unique clusters with Google Cloud installed |
-| `clusters_platforms_eks` | `usage_activity_by_stage` | `configure` | | Unique clusters with AWS installed |
-| `clusters_platforms_user` | `usage_activity_by_stage` | `configure` | | Unique clusters that are user provided |
-| `instance_clusters_disabled` | `usage_activity_by_stage` | `configure` | | Unique clusters disabled on instance |
-| `instance_clusters_enabled` | `usage_activity_by_stage` | `configure` | | Unique clusters enabled on instance |
-| `group_clusters_disabled` | `usage_activity_by_stage` | `configure` | | Unique clusters disabled on group |
-| `group_clusters_enabled` | `usage_activity_by_stage` | `configure` | | Unique clusters enabled on group |
-| `project_clusters_disabled` | `usage_activity_by_stage` | `configure` | | Unique clusters disabled on project |
-| `project_clusters_enabled` | `usage_activity_by_stage` | `configure` | | Unique clusters enabled on project |
-| `projects_slack_notifications_active` | `usage_activity_by_stage` | `configure` | | Unique projects with Slack service enabled |
-| `projects_slack_slash_active` | `usage_activity_by_stage` | `configure` | | Unique projects with Slack '/' commands enabled |
-| `projects_with_prometheus_alerts: 0` | `usage_activity_by_stage` | `monitor` | | Projects with Prometheus enabled and no alerts |
-| `deploy_keys` | `usage_activity_by_stage` | `create` | | |
-| `keys` | `usage_activity_by_stage` | `create` | | |
-| `projects_jira_dvcs_server_active` | `usage_activity_by_stage` | `plan` | | |
-| `service_desk_enabled_projects` | `usage_activity_by_stage` | `plan` | | |
-| `service_desk_issues` | `usage_activity_by_stage` | `plan` | | |
-| `todos: 0` | `usage_activity_by_stage` | `plan` | | |
-| `deployments` | `usage_activity_by_stage` | `release` | | Total deployments |
-| `failed_deployments` | `usage_activity_by_stage` | `release` | | Total failed deployments |
-| `projects_mirrored_with_pipelines_enabled` | `usage_activity_by_stage` | `release` | | Projects with repository mirroring enabled |
-| `releases` | `usage_activity_by_stage` | `release` | | Unique release tags in project |
-| `successful_deployments: 0` | `usage_activity_by_stage` | `release` | | Total successful deployments |
-| `user_preferences_group_overview_security_dashboard: 0` | `usage_activity_by_stage` | `secure` | | |
-| `ci_builds` | `usage_activity_by_stage` | `verify` | | Unique builds in project |
-| `ci_external_pipelines` | `usage_activity_by_stage` | `verify` | | Total pipelines in external repositories |
-| `ci_internal_pipelines` | `usage_activity_by_stage` | `verify` | | Total pipelines in GitLab repositories |
-| `ci_pipeline_config_auto_devops` | `usage_activity_by_stage` | `verify` | | Total pipelines from an Auto DevOps template |
-| `ci_pipeline_config_repository` | `usage_activity_by_stage` | `verify` | | Pipelines from templates in repository |
-| `ci_pipeline_schedules` | `usage_activity_by_stage` | `verify` | | Pipeline schedules in GitLab |
-| `ci_pipelines` | `usage_activity_by_stage` | `verify` | | Total pipelines |
-| `ci_triggers` | `usage_activity_by_stage` | `verify` | | Triggers enabled |
-| `clusters_applications_runner` | `usage_activity_by_stage` | `verify` | | Unique clusters with Runner enabled |
-| `projects_reporting_ci_cd_back_to_github: 0` | `usage_activity_by_stage` | `verify` | | Unique projects with a GitHub pipeline enabled |
-| `nodes` | `topology` | `enablement`| | The list of server nodes on which GitLab components are running |
-| `duration_s` | `topology` | `enablement`| | Time it took to collect topology data |
-| `node_memory_total_bytes` | `topology > nodes` | `enablement`| | The total available memory of this node |
-| `node_cpus` | `topology > nodes` | `enablement`| | The number of CPU cores of this node |
-| `node_services` | `topology > nodes` | `enablement`| | The list of GitLab services running on this node |
-| `name` | `topology > nodes > node_services` | `enablement`| | The name of the GitLab service running on this node |
-| `process_count` | `topology > nodes > node_services` | `enablement`| | The number of processes running for this service |
-| `process_memory_rss` | `topology > nodes > node_services` | `enablement`| | The average Resident Set Size of a service process |
-| `process_memory_uss` | `topology > nodes > node_services` | `enablement`| | The average Unique Set Size of a service process |
-| `process_memory_pss` | `topology > nodes > node_services` | `enablement`| | The average Proportional Set Size of a service process |
+| Statistic | Section | Stage | Tier | Edition | Description |
+| --------------------------------------------------------- | ------------------------------------ | ------------- | ---------------- | ------- | -------------------------------------------------------------------------- |
+| `uuid` | | | | | |
+| `hostname` | | | | | |
+| `version` | | | | | |
+| `installation_type` | | | | | |
+| `active_user_count` | | | | | |
+| `recorded_at` | | | | | |
+| `recording_ce_finished_at` | | | | CE+EE | When the core features were computed |
+| `recording_ee_finished_at` | | | | EE | When the EE-specific features were computed |
+| `edition` | | | | | |
+| `license_md5` | | | | | |
+| `license_id` | | | | | |
+| `historical_max_users` | | | | | |
+| `Name` | `licensee` | | | | |
+| `Email` | `licensee` | | | | |
+| `Company` | `licensee` | | | | |
+| `license_user_count` | | | | | |
+| `license_starts_at` | | | | | |
+| `license_expires_at` | | | | | |
+| `license_plan` | | | | | |
+| `license_trial` | | | | | |
+| `assignee_lists` | `counts` | | | | |
+| `boards` | `counts` | | | | |
+| `ci_builds` | `counts` | `verify` | | | Unique builds in project |
+| `ci_internal_pipelines` | `counts` | `verify` | | | Total pipelines in GitLab repositories |
+| `ci_external_pipelines` | `counts` | `verify` | | | Total pipelines in external repositories |
+| `ci_pipeline_config_auto_devops` | `counts` | `verify` | | | Total pipelines from an Auto DevOps template |
+| `ci_pipeline_config_repository` | `counts` | `verify` | | | Total Pipelines from templates in repository |
+| `ci_runners` | `counts` | `verify` | | | Total configured Runners in project |
+| `ci_triggers` | `counts` | `verify` | | | Total configured Triggers in project |
+| `ci_pipeline_schedules` | `counts` | `verify` | | | Pipeline schedules in GitLab |
+| `auto_devops_enabled` | `counts` | `configure` | | | Projects with Auto DevOps template enabled |
+| `auto_devops_disabled` | `counts` | `configure` | | | Projects with Auto DevOps template disabled |
+| `deploy_keys` | `counts` | | | | |
+| `deployments` | `counts` | `release` | | | Total deployments |
+| `deployments` | `counts_monthly` | `release` | | | Total deployments last 28 days |
+| `dast_jobs` | `counts` | | | | |
+| `successful_deployments` | `counts` | `release` | | | Total successful deployments |
+| `successful_deployments` | `counts_monthly` | `release` | | | Total successful deployments last 28 days |
+| `failed_deployments` | `counts` | `release` | | | Total failed deployments |
+| `failed_deployments` | `counts_monthly` | `release` | | | Total failed deployments last 28 days |
+| `environments` | `counts` | `release` | | | Total available and stopped environments |
+| `clusters` | `counts` | `configure` | | | Total GitLab Managed clusters both enabled and disabled |
+| `clusters_enabled` | `counts` | `configure` | | | Total GitLab Managed clusters currently enabled |
+| `project_clusters_enabled` | `counts` | `configure` | | | Total GitLab Managed clusters attached to projects |
+| `group_clusters_enabled` | `counts` | `configure` | | | Total GitLab Managed clusters attached to groups |
+| `instance_clusters_enabled` | `counts` | `configure` | | | Total GitLab Managed clusters attached to the instance |
+| `clusters_disabled` | `counts` | `configure` | | | Total GitLab Managed disabled clusters |
+| `project_clusters_disabled` | `counts` | `configure` | | | Total GitLab Managed disabled clusters previously attached to projects |
+| `group_clusters_disabled` | `counts` | `configure` | | | Total GitLab Managed disabled clusters previously attached to groups |
+| `instance_clusters_disabled` | `counts` | `configure` | | | Total GitLab Managed disabled clusters previously attached to the instance |
+| `clusters_platforms_eks` | `counts` | `configure` | | | Total GitLab Managed clusters provisioned with GitLab on AWS EKS |
+| `clusters_platforms_gke` | `counts` | `configure` | | | Total GitLab Managed clusters provisioned with GitLab on GCE GKE |
+| `clusters_platforms_user` | `counts` | `configure` | | | Total GitLab Managed clusters that are user provisioned |
+| `clusters_applications_helm` | `counts` | `configure` | | | Total GitLab Managed clusters with Helm enabled |
+| `clusters_applications_ingress` | `counts` | `configure` | | | Total GitLab Managed clusters with Ingress enabled |
+| `clusters_applications_cert_managers` | `counts` | `configure` | | | Total GitLab Managed clusters with Cert Manager enabled |
+| `clusters_applications_crossplane` | `counts` | `configure` | | | Total GitLab Managed clusters with Crossplane enabled |
+| `clusters_applications_prometheus` | `counts` | `configure` | | | Total GitLab Managed clusters with Prometheus enabled |
+| `clusters_applications_runner` | `counts` | `configure` | | | Total GitLab Managed clusters with Runner enabled |
+| `clusters_applications_knative` | `counts` | `configure` | | | Total GitLab Managed clusters with Knative enabled |
+| `clusters_applications_elastic_stack` | `counts` | `configure` | | | Total GitLab Managed clusters with Elastic Stack enabled |
+| `clusters_applications_cilium` | `counts` | `configure` | | | Total GitLab Managed clusters with Cilium enabled |
+| `clusters_management_project` | `counts` | `configure` | | | Total GitLab Managed clusters with defined cluster management project |
+| `in_review_folder` | `counts` | | | | |
+| `grafana_integrated_projects` | `counts` | | | | |
+| `groups` | `counts` | | | | |
+| `issues` | `counts` | | | | |
+| `issues_created_from_gitlab_error_tracking_ui` | `counts` | `monitor` | | | |
+| `issues_with_associated_zoom_link` | `counts` | `monitor` | | | |
+| `issues_using_zoom_quick_actions` | `counts` | `monitor` | | | |
+| `issues_with_embedded_grafana_charts_approx` | `counts` | `monitor` | | | |
+| `issues_with_health_status` | `counts` | | | | |
+| `keys` | `counts` | | | | |
+| `label_lists` | `counts` | | | | |
+| `lfs_objects` | `counts` | | | | |
+| `milestone_lists` | `counts` | | | | |
+| `milestones` | `counts` | | | | |
+| `pages_domains` | `counts` | `release` | | | Total GitLab Pages domains |
+| `pool_repositories` | `counts` | | | | |
+| `projects` | `counts` | | | | |
+| `projects_imported_from_github` | `counts` | | | | |
+| `projects_with_repositories_enabled` | `counts` | | | | |
+| `projects_with_error_tracking_enabled` | `counts` | `monitor` | | | |
+| `protected_branches` | `counts` | | | | |
+| `releases` | `counts` | `release` | | | Unique release tags |
+| `remote_mirrors` | `counts` | | | | |
+| `requirements_created` | `counts` | | | | |
+| `snippets` | `counts` | 'create' | | CE+EE | |
+| `snippets` | `counts_monthly` | 'create' | | CE+EE | |
+| `personal_snippets` | `counts` | 'create' | | CE+EE | |
+| `personal_snippets` | `counts_monthly` | 'create' | | CE+EE | |
+| `project_snippets` | `counts` | 'create' | | CE+EE | |
+| `project_snippets` | `counts_monthly` | 'create' | | CE+EE | |
+| `suggestions` | `counts` | | | | |
+| `todos` | `counts` | | | | |
+| `uploads` | `counts` | | | | |
+| `web_hooks` | `counts` | | | | |
+| `projects_alerts_active` | `counts` | | | | |
+| `projects_asana_active` | `counts` | | | | |
+| `projects_assembla_active` | `counts` | | | | |
+| `projects_bamboo_active` | `counts` | | | | |
+| `projects_bugzilla_active` | `counts` | | | | |
+| `projects_buildkite_active` | `counts` | | | | |
+| `projects_campfire_active` | `counts` | | | | |
+| `projects_custom_issue_tracker_active` | `counts` | | | | |
+| `projects_discord_active` | `counts` | | | | |
+| `projects_drone_ci_active` | `counts` | | | | |
+| `projects_emails_on_push_active` | `counts` | | | | |
+| `projects_external_wiki_active` | `counts` | | | | |
+| `projects_flowdock_active` | `counts` | | | | |
+| `projects_github_active` | `counts` | | | | |
+| `projects_hangouts_chat_active` | `counts` | | | | |
+| `projects_hipchat_active` | `counts` | | | | |
+| `projects_irker_active` | `counts` | | | | |
+| `projects_jenkins_active` | `counts` | | | | |
+| `projects_jira_active` | `counts` | | | | |
+| `projects_mattermost_active` | `counts` | | | | |
+| `projects_mattermost_slash_commands_active` | `counts` | | | | |
+| `projects_microsoft_teams_active` | `counts` | | | | |
+| `projects_packagist_active` | `counts` | | | | |
+| `projects_pipelines_email_active` | `counts` | | | | |
+| `projects_pivotaltracker_active` | `counts` | | | | |
+| `projects_prometheus_active` | `counts` | | | | |
+| `projects_pushover_active` | `counts` | | | | |
+| `projects_redmine_active` | `counts` | | | | |
+| `projects_slack_active` | `counts` | | | | |
+| `projects_slack_slash_commands_active` | `counts` | | | | |
+| `projects_teamcity_active` | `counts` | | | | |
+| `projects_unify_circuit_active` | `counts` | | | | |
+| `projects_webex_teams_active` | `counts` | | | | |
+| `projects_youtrack_active` | `counts` | | | | |
+| `projects_jira_server_active` | `counts` | | | | |
+| `projects_jira_cloud_active` | `counts` | | | | |
+| `projects_jira_dvcs_cloud_active` | `counts` | | | | |
+| `projects_jira_dvcs_server_active` | `counts` | | | | |
+| `projects_jira_issuelist_active` | `counts` | `create` | | EE | Total Jira Issue feature enabled |
+| `labels` | `counts` | | | | |
+| `merge_requests` | `counts` | | | | |
+| `merge_requests_users` | `counts` | | | | |
+| `notes` | `counts` | | | | |
+| `wiki_pages_create` | `counts` | | | | |
+| `wiki_pages_update` | `counts` | | | | |
+| `wiki_pages_delete` | `counts` | | | | |
+| `web_ide_commits` | `counts` | | | | |
+| `web_ide_views` | `counts` | | | | |
+| `web_ide_merge_requests` | `counts` | | | | |
+| `web_ide_previews` | `counts` | | | | |
+| `snippet_comment` | `counts` | | | | |
+| `commit_comment` | `counts` | | | | |
+| `merge_request_comment` | `counts` | | | | |
+| `snippet_create` | `counts` | | | | |
+| `snippet_update` | `counts` | | | | |
+| `navbar_searches` | `counts` | | | | |
+| `cycle_analytics_views` | `counts` | | | | |
+| `productivity_analytics_views` | `counts` | | | | |
+| `source_code_pushes` | `counts` | | | | |
+| `merge_request_create` | `counts` | | | | |
+| `design_management_designs_create` | `counts` | | | | |
+| `design_management_designs_update` | `counts` | | | | |
+| `design_management_designs_delete` | `counts` | | | | |
+| `licenses_list_views` | `counts` | | | | |
+| `user_preferences_group_overview_details` | `counts` | | | | |
+| `user_preferences_group_overview_security_dashboard` | `counts` | | | | |
+| `ingress_modsecurity_logging` | `counts` | | | | |
+| `ingress_modsecurity_blocking` | `counts` | | | | |
+| `ingress_modsecurity_disabled` | `counts` | | | | |
+| `ingress_modsecurity_not_installed` | `counts` | | | | |
+| `dependency_list_usages_total` | `counts` | | | | |
+| `epics` | `counts` | | | | |
+| `feature_flags` | `counts` | | | | |
+| `geo_nodes` | `counts` | `geo` | | | Number of sites in a Geo deployment |
+| `geo_event_log_max_id` | `counts` | `geo` | | | Number of replication events on a Geo primary |
+| `incident_issues` | `counts` | `monitor` | | | Issues created by the alert bot |
+| `alert_bot_incident_issues` | `counts` | `monitor` | | | Issues created by the alert bot |
+| `incident_labeled_issues` | `counts` | `monitor` | | | Issues with the incident label |
+| `issues_created_gitlab_alerts` | `counts` | `monitor` | | | Issues created from alerts by non-alert bot users |
+| `issues_created_manually_from_alerts` | `counts` | `monitor` | | | Issues created from alerts by non-alert bot users |
+| `issues_created_from_alerts` | `counts` | `monitor` | | | Issues created from Prometheus and alert management alerts |
+| `ldap_group_links` | `counts` | | | | |
+| `ldap_keys` | `counts` | | | | |
+| `ldap_users` | `counts` | | | | |
+| `pod_logs_usages_total` | `counts` | | | | |
+| `projects_enforcing_code_owner_approval` | `counts` | | | | |
+| `projects_mirrored_with_pipelines_enabled` | `counts` | `release` | | | Projects with repository mirroring enabled |
+| `projects_reporting_ci_cd_back_to_github` | `counts` | `verify` | | | Projects with a GitHub service pipeline enabled |
+| `projects_with_packages` | `counts` | `package` | | | Projects with package registry configured |
+| `projects_with_prometheus_alerts` | `counts` | `monitor` | | | Projects with Prometheus alerting enabled |
+| `projects_with_tracing_enabled` | `counts` | `monitor` | | | Projects with tracing enabled |
+| `projects_with_alerts_service_enabled` | `counts` | `monitor` | | | Projects with alerting service enabled |
+| `template_repositories` | `counts` | | | | |
+| `container_scanning_jobs` | `counts` | | | | |
+| `dependency_scanning_jobs` | `counts` | | | | |
+| `license_management_jobs` | `counts` | | | | |
+| `sast_jobs` | `counts` | | | | |
+| `status_page_projects` | `counts` | `monitor` | | | Projects with status page enabled |
+| `status_page_issues` | `counts` | `monitor` | | | Issues published to a Status Page |
+| `status_page_incident_publishes` | `counts` | `monitor` | | | Cumulative count of usages of publish operation |
+| `status_page_incident_unpublishes` | `counts` | `monitor` | | | Cumulative count of usages of unpublish operation |
+| `epics_deepest_relationship_level` | `counts` | | | | |
+| `operations_dashboard_default_dashboard` | `counts` | `monitor` | | | Active users with enabled operations dashboard |
+| `operations_dashboard_users_with_projects_added` | `counts` | `monitor` | | | Active users with projects on operations dashboard |
+| `container_registry_enabled` | | | | | |
+| `dependency_proxy_enabled` | | | | | |
+| `gitlab_shared_runners_enabled` | | | | | |
+| `gravatar_enabled` | | | | | |
+| `ldap_enabled` | | | | | |
+| `mattermost_enabled` | | | | | |
+| `omniauth_enabled` | | | | | |
+| `prometheus_enabled` | | | | | Whether the bundled Prometheus is enabled |
+| `prometheus_metrics_enabled` | | | | | |
+| `reply_by_email_enabled` | | | | | |
+| `average` | `avg_cycle_analytics - code` | | | | |
+| `sd` | `avg_cycle_analytics - code` | | | | |
+| `missing` | `avg_cycle_analytics - code` | | | | |
+| `average` | `avg_cycle_analytics - test` | | | | |
+| `sd` | `avg_cycle_analytics - test` | | | | |
+| `missing` | `avg_cycle_analytics - test` | | | | |
+| `average` | `avg_cycle_analytics - review` | | | | |
+| `sd` | `avg_cycle_analytics - review` | | | | |
+| `missing` | `avg_cycle_analytics - review` | | | | |
+| `average` | `avg_cycle_analytics - staging` | | | | |
+| `sd` | `avg_cycle_analytics - staging` | | | | |
+| `missing` | `avg_cycle_analytics - staging` | | | | |
+| `average` | `avg_cycle_analytics - production` | | | | |
+| `sd` | `avg_cycle_analytics - production` | | | | |
+| `missing` | `avg_cycle_analytics - production` | | | | |
+| `total` | `avg_cycle_analytics` | | | | |
+| `g_analytics_contribution` | `analytics_unique_visits` | `manage` | | | Visits to /groups/:group/-/contribution_analytics |
+| `g_analytics_insights` | `analytics_unique_visits` | `manage` | | | Visits to /groups/:group/-/insights |
+| `g_analytics_issues` | `analytics_unique_visits` | `manage` | | | Visits to /groups/:group/-/issues_analytics |
+| `g_analytics_productivity` | `analytics_unique_visits` | `manage` | | | Visits to /groups/:group/-/analytics/productivity_analytics |
+| `g_analytics_valuestream` | `analytics_unique_visits` | `manage` | | | Visits to /groups/:group/-/analytics/value_stream_analytics |
+| `p_analytics_pipelines` | `analytics_unique_visits` | `manage` | | | Visits to /:group/:project/pipelines/charts |
+| `p_analytics_code_reviews` | `analytics_unique_visits` | `manage` | | | Visits to /:group/:project/-/analytics/code_reviews |
+| `p_analytics_valuestream` | `analytics_unique_visits` | `manage` | | | Visits to /:group/:project/-/value_stream_analytics |
+| `p_analytics_insights` | `analytics_unique_visits` | `manage` | | | Visits to /:group/:project/insights |
+| `p_analytics_issues` | `analytics_unique_visits` | `manage` | | | Visits to /:group/:project/-/analytics/issues_analytics |
+| `p_analytics_repo` | `analytics_unique_visits` | `manage` | | | Visits to /:group/:project/-/graphs/master/charts |
+| `u_analytics_todos` | `analytics_unique_visits` | `manage` | | | Visits to /dashboard/todos |
+| `i_analytics_cohorts` | `analytics_unique_visits` | `manage` | | | Visits to /-/instance_statistics/cohorts |
+| `i_analytics_dev_ops_score` | `analytics_unique_visits` | `manage` | | | Visits to /-/instance_statistics/dev_ops_score |
+| `analytics_unique_visits_for_any_target` | `analytics_unique_visits` | `manage` | | | Visits to any of the pages listed above |
+| `clusters_applications_cert_managers` | `usage_activity_by_stage` | `configure` | | CE+EE | Unique clusters with certificate managers enabled |
+| `clusters_applications_helm` | `usage_activity_by_stage` | `configure` | | CE+EE | Unique clusters with Helm enabled |
+| `clusters_applications_ingress` | `usage_activity_by_stage` | `configure` | | CE+EE | Unique clusters with Ingress enabled |
+| `clusters_applications_knative` | `usage_activity_by_stage` | `configure` | | CE+EE | Unique clusters with Knative enabled |
+| `clusters_management_project` | `usage_activity_by_stage` | `configure` | | CE+EE | Unique clusters with project management enabled |
+| `clusters_disabled` | `usage_activity_by_stage` | `configure` | | CE+EE | Total non-"GitLab Managed clusters" |
+| `clusters_enabled` | `usage_activity_by_stage` | `configure` | | CE+EE | Total GitLab Managed clusters |
+| `clusters_platforms_gke` | `usage_activity_by_stage` | `configure` | | CE+EE | Unique clusters with Google Cloud installed |
+| `clusters_platforms_eks` | `usage_activity_by_stage` | `configure` | | CE+EE | Unique clusters with AWS installed |
+| `clusters_platforms_user` | `usage_activity_by_stage` | `configure` | | CE+EE | Unique clusters that are user provided |
+| `instance_clusters_disabled` | `usage_activity_by_stage` | `configure` | | CE+EE | Unique clusters disabled on instance |
+| `instance_clusters_enabled` | `usage_activity_by_stage` | `configure` | | CE+EE | Unique clusters enabled on instance |
+| `group_clusters_disabled` | `usage_activity_by_stage` | `configure` | | CE+EE | Unique clusters disabled on group |
+| `group_clusters_enabled` | `usage_activity_by_stage` | `configure` | | CE+EE | Unique clusters enabled on group |
+| `project_clusters_disabled` | `usage_activity_by_stage` | `configure` | | CE+EE | Unique clusters disabled on project |
+| `project_clusters_enabled` | `usage_activity_by_stage` | `configure` | | CE+EE | Unique clusters enabled on project |
+| `projects_slack_notifications_active` | `usage_activity_by_stage` | `configure` | | EE | Unique projects with Slack service enabled |
+| `projects_slack_slash_active` | `usage_activity_by_stage` | `configure` | | EE | Unique projects with Slack '/' commands enabled |
+| `projects_with_prometheus_alerts` | `usage_activity_by_stage` | `configure` | | EE | Projects with Prometheus enabled and no alerts |
+| `deploy_keys` | `usage_activity_by_stage` | `create` | | CE+EE | |
+| `keys` | `usage_activity_by_stage` | `create` | | CE+EE | |
+| `merge_requests` | `usage_activity_by_stage` | `create` | | CE+EE | |
+| `projects_with_disable_overriding_approvers_per_merge_request` | `usage_activity_by_stage` | `create` | | CE+EE | |
+| `projects_without_disable_overriding_approvers_per_merge_request` | `usage_activity_by_stage` | `create` | | CE+EE | |
+| `remote_mirrors` | `usage_activity_by_stage` | `create` | | CE+EE | |
+| `snippets` | `usage_activity_by_stage` | `create` | | CE+EE | |
+| `merge_requests_users` | `usage_activity_by_stage_monthly` | `create` | | CE+EE | Unique count of users who used a merge request |
+| `action_monthly_active_users_project_repo` | `usage_activity_by_stage_monthly` | `create` | | CE+EE | Unique count of users who pushed to a project repo |
+| `action_monthly_active_users_design_management` | `usage_activity_by_stage_monthly` | `create` | | CE+EE | Unique count of users who interacted with the design system management |
+| `action_monthly_active_users_wiki_repo` | `usage_activity_by_stage_monthly` | `create` | | CE+EE | Unique count of users who created or updated a wiki repo |
+| `projects_enforcing_code_owner_approval` | `usage_activity_by_stage` | `create` | | EE | |
+| `merge_requests_with_optional_codeowners` | `usage_activity_by_stage` | `create` | | EE | |
+| `merge_requests_with_required_codeowners` | `usage_activity_by_stage` | `create` | | EE | |
+| `projects_imported_from_github` | `usage_activity_by_stage` | `create` | | EE | |
+| `projects_with_repositories_enabled` | `usage_activity_by_stage` | `create` | | EE | |
+| `protected_branches` | `usage_activity_by_stage` | `create` | | EE | |
+| `suggestions` | `usage_activity_by_stage` | `create` | | EE | |
+| `approval_project_rules` | `usage_activity_by_stage` | `create` | | EE | Number of project approval rules |
+| `approval_project_rules_with_target_branch` | `usage_activity_by_stage` | `create` | | EE | Number of project approval rules with not default target branch |
+| `merge_requests_with_added_rules` | `usage_activity_by_stage` | `create` | | EE | Merge Requests with added rules |
+| `clusters` | `usage_activity_by_stage` | `monitor` | | CE+EE | |
+| `clusters_applications_prometheus` | `usage_activity_by_stage` | `monitor` | | CE+EE | |
+| `operations_dashboard_default_dashboard` | `usage_activity_by_stage` | `monitor` | | CE+EE | |
+| `operations_dashboard_users_with_projects_added` | `usage_activity_by_stage` | `monitor` | | EE | |
+| `projects_prometheus_active` | `usage_activity_by_stage` | `monitor` | | EE | |
+| `projects_with_error_tracking_enabled` | `usage_activity_by_stage` | `monitor` | | EE | |
+| `projects_with_tracing_enabled` | `usage_activity_by_stage` | `monitor` | | EE | |
+| `events` | `usage_activity_by_stage` | `manage` | | CE+EE | |
+| `groups` | `usage_activity_by_stage` | `manage` | | CE+EE | |
+| `users_created_at` | `usage_activity_by_stage` | `manage` | | CE+EE | |
+| `omniauth_providers` | `usage_activity_by_stage` | `manage` | | CE+EE | |
+| `ldap_keys` | `usage_activity_by_stage` | `manage` | | EE | |
+| `ldap_users` | `usage_activity_by_stage` | `manage` | | EE | |
+| `value_stream_management_customized_group_stages` | `usage_activity_by_stage` | `manage` | | EE | |
+| `projects_with_compliance_framework` | `usage_activity_by_stage` | `manage` | | EE | |
+| `ldap_servers` | `usage_activity_by_stage` | `manage` | | EE | |
+| `ldap_group_sync_enabled` | `usage_activity_by_stage` | `manage` | | EE | |
+| `ldap_admin_sync_enabled` | `usage_activity_by_stage` | `manage` | | EE | |
+| `group_saml_enabled` | `usage_activity_by_stage` | `manage` | | EE | |
+| `issues` | `usage_activity_by_stage` | `plan` | | CE+EE | |
+| `notes` | `usage_activity_by_stage` | `plan` | | CE+EE | |
+| `projects` | `usage_activity_by_stage` | `plan` | | CE+EE | |
+| `todos` | `usage_activity_by_stage` | `plan` | | CE+EE | |
+| `assignee_lists` | `usage_activity_by_stage` | `plan` | | EE | |
+| `epics` | `usage_activity_by_stage` | `plan` | | EE | |
+| `label_lists` | `usage_activity_by_stage` | `plan` | | EE | |
+| `milestone_lists` | `usage_activity_by_stage` | `plan` | | EE | |
+| `projects_jira_active` | `usage_activity_by_stage` | `plan` | | EE | |
+| `projects_jira_dvcs_server_active` | `usage_activity_by_stage` | `plan` | | EE | |
+| `projects_jira_dvcs_server_active` | `usage_activity_by_stage` | `plan` | | EE | |
+| `service_desk_enabled_projects` | `usage_activity_by_stage` | `plan` | | CE+EE | |
+| `service_desk_issues` | `usage_activity_by_stage` | `plan` | | CE+EE | |
+| `deployments` | `usage_activity_by_stage` | `release` | | CE+EE | Total deployments |
+| `failed_deployments` | `usage_activity_by_stage` | `release` | | CE+EE | Total failed deployments |
+| `projects_mirrored_with_pipelines_enabled` | `usage_activity_by_stage` | `release` | | EE | Projects with repository mirroring enabled |
+| `releases` | `usage_activity_by_stage` | `release` | | CE+EE | Unique release tags in project |
+| `successful_deployments` | `usage_activity_by_stage` | `release` | | CE+EE | Total successful deployments |
+| `user_preferences_group_overview_security_dashboard` | `usage_activity_by_stage` | `secure` | | | |
+| `ci_builds` | `usage_activity_by_stage` | `verify` | | CE+EE | Unique builds in project |
+| `ci_external_pipelines` | `usage_activity_by_stage` | `verify` | | CE+EE | Total pipelines in external repositories |
+| `ci_internal_pipelines` | `usage_activity_by_stage` | `verify` | | CE+EE | Total pipelines in GitLab repositories |
+| `ci_pipeline_config_auto_devops` | `usage_activity_by_stage` | `verify` | | CE+EE | Total pipelines from an Auto DevOps template |
+| `ci_pipeline_config_repository` | `usage_activity_by_stage` | `verify` | | CE+EE | Pipelines from templates in repository |
+| `ci_pipeline_schedules` | `usage_activity_by_stage` | `verify` | | CE+EE | Pipeline schedules in GitLab |
+| `ci_pipelines` | `usage_activity_by_stage` | `verify` | | CE+EE | Total pipelines |
+| `ci_triggers` | `usage_activity_by_stage` | `verify` | | CE+EE | Triggers enabled |
+| `clusters_applications_runner` | `usage_activity_by_stage` | `verify` | | CE+EE | Unique clusters with Runner enabled |
+| `projects_reporting_ci_cd_back_to_github` | `usage_activity_by_stage` | `verify` | | EE | Unique projects with a GitHub pipeline enabled |
+| `merge_requests_users` | `usage_activity_by_stage_monthly` | `create` | | | Unique count of users who used a merge request |
+| `duration_s` | `topology` | `enablement` | | | Time it took to collect topology data |
+| `application_requests_per_hour` | `topology` | `enablement` | | | Number of requests to the web application per hour |
+| `failures` | `topology` | `enablement` | | | Contains information about failed queries |
+| `nodes` | `topology` | `enablement` | | | The list of server nodes on which GitLab components are running |
+| `node_memory_total_bytes` | `topology > nodes` | `enablement` | | | The total available memory of this node |
+| `node_cpus` | `topology > nodes` | `enablement` | | | The number of CPU cores of this node |
+| `node_uname_info` | `topology > nodes` | `enablement` | | | The basic hardware architecture and OS release information on this node |
+| `node_services` | `topology > nodes` | `enablement` | | | The list of GitLab services running on this node |
+| `name` | `topology > nodes > node_services` | `enablement` | | | The name of the GitLab service running on this node |
+| `process_count` | `topology > nodes > node_services` | `enablement` | | | The number of processes running for this service |
+| `process_memory_rss` | `topology > nodes > node_services` | `enablement` | | | The average Resident Set Size of a service process |
+| `process_memory_uss` | `topology > nodes > node_services` | `enablement` | | | The average Unique Set Size of a service process |
+| `process_memory_pss` | `topology > nodes > node_services` | `enablement` | | | The average Proportional Set Size of a service process |
+| `server` | `topology > nodes > node_services` | `enablement` | | | The type of web server used (Unicorn or Puma) |
+| `network_policy_forwards` | `counts` | `defend` | | EE | Cumulative count of forwarded packets by Container Network |
+| `network_policy_drops` | `counts` | `defend` | | EE | Cumulative count of dropped packets by Container Network |
## Example Usage Ping payload
@@ -690,6 +780,7 @@ The following is example content of the Usage Ping payload.
"ldap_enabled": false,
"mattermost_enabled": false,
"omniauth_enabled": true,
+ "prometheus_enabled": false,
"prometheus_metrics_enabled": false,
"reply_by_email_enabled": "incoming+%{key}@incoming.gitlab.com",
"signup_enabled": true,
@@ -765,6 +856,10 @@ The following is example content of the Usage Ping payload.
},
"total": 999
},
+ "analytics_unique_visits": {
+ "g_analytics_contribution": 999,
+ ...
+ },
"usage_activity_by_stage": {
"configure": {
"project_clusters_enabled": 999,
@@ -840,17 +935,26 @@ The following is example content of the Usage Ping payload.
}
},
"topology": {
+ "duration_s": 0.013836685999194742,
+ "application_requests_per_hour": 4224,
+ "failures": [],
"nodes": [
{
"node_memory_total_bytes": 33269903360,
"node_cpus": 16,
+ "node_uname_info": {
+ "machine": "x86_64",
+ "sysname": "Linux",
+ "release": "4.19.76-linuxkit"
+ },
"node_services": [
{
"name": "web",
"process_count": 16,
"process_memory_pss": 233349888,
"process_memory_rss": 788220927,
- "process_memory_uss": 195295487
+ "process_memory_uss": 195295487,
+ "server": "puma"
},
{
"name": "sidekiq",
@@ -864,8 +968,7 @@ The following is example content of the Usage Ping payload.
...
},
...
- ],
- "duration_s": 0.013836685999194742
+ ]
}
}
```
diff --git a/doc/development/testing_guide/best_practices.md b/doc/development/testing_guide/best_practices.md
index 7bb8473117f..4e46e691405 100644
--- a/doc/development/testing_guide/best_practices.md
+++ b/doc/development/testing_guide/best_practices.md
@@ -110,7 +110,8 @@ Use the coverage reports to ensure your tests cover 100% of your code.
### System / Feature tests
-NOTE: **Note:** Before writing a new system test, [please consider **not**
+NOTE: **Note:**
+Before writing a new system test, [please consider **not**
writing one](testing_levels.md#consider-not-writing-a-system-test)!
- Feature specs should be named `ROLE_ACTION_spec.rb`, such as
@@ -741,7 +742,7 @@ GitLab uses [factory_bot](https://github.com/thoughtbot/factory_bot) as a test f
- There should be only one top-level factory definition per file.
- FactoryBot methods are mixed in to all RSpec groups. This means you can (and
should) call `create(...)` instead of `FactoryBot.create(...)`.
-- Make use of [traits](https://www.rubydoc.info/gems/factory_bot/file/GETTING_STARTED.md#Traits) to clean up definitions and usages.
+- Make use of [traits](https://www.rubydoc.info/gems/factory_bot/file/GETTING_STARTED.md#traits) to clean up definitions and usages.
- When defining a factory, don't define attributes that are not required for the
resulting record to pass validation.
- When instantiating from a factory, don't supply attributes that aren't
@@ -813,6 +814,40 @@ file which is used by the `spec/fast_spec_helper.rb` file. See
[Fast unit tests](#fast-unit-tests) for more details about the
`spec/fast_spec_helper.rb` file.
+### Test environment logging
+
+Services for the test environment are automatically configured and started when
+tests are run, including Gitaly, Workhorse, Elasticsearch, and Capybara. When run in CI, or
+if the service needs to be installed, the test environment will log information
+about set-up time, producing log messages like the following:
+
+```plaintext
+==> Setting up Gitaly...
+ Gitaly set up in 31.459649 seconds...
+
+==> Setting up GitLab Workhorse...
+ GitLab Workhorse set up in 29.695619 seconds...
+fatal: update refs/heads/diff-files-symlink-to-image: invalid <newvalue>: 8cfca84
+From https://gitlab.com/gitlab-org/gitlab-test
+ * [new branch] diff-files-image-to-symlink -> origin/diff-files-image-to-symlink
+ * [new branch] diff-files-symlink-to-image -> origin/diff-files-symlink-to-image
+ * [new branch] diff-files-symlink-to-text -> origin/diff-files-symlink-to-text
+ * [new branch] diff-files-text-to-symlink -> origin/diff-files-text-to-symlink
+ b80faa8..40232f7 snippet/multiple-files -> origin/snippet/multiple-files
+ * [new branch] testing/branch-with-#-hash -> origin/testing/branch-with-#-hash
+
+==> Setting up GitLab Elasticsearch Indexer...
+ GitLab Elasticsearch Indexer set up in 26.514623 seconds...
+```
+
+This information is omitted when running locally and when no action needs
+to be performed. If you would always like to see these messages, set the
+following environment variable:
+
+```shell
+GITLAB_TESTING_LOG_LEVEL=debug
+```
+
---
[Return to Testing documentation](index.md)
diff --git a/doc/development/testing_guide/end_to_end/beginners_guide.md b/doc/development/testing_guide/end_to_end/beginners_guide.md
index 73960a2f74d..15a9b4406ab 100644
--- a/doc/development/testing_guide/end_to_end/beginners_guide.md
+++ b/doc/development/testing_guide/end_to_end/beginners_guide.md
@@ -62,7 +62,7 @@ end-to-end flows, and is easiest to understand.
The GitLab QA end-to-end tests are organized by the different
[stages in the DevOps lifecycle](https://gitlab.com/gitlab-org/gitlab-foss/tree/master/qa/qa/specs/features/browser_ui).
Determine where the test should be placed by
-[stage](https://about.gitlab.com/handbook/product/categories/#devops-stages),
+[stage](https://about.gitlab.com/handbook/product/product-categories/#devops-stages),
determine which feature the test will belong to, and then place it in a subdirectory
under the stage.
@@ -80,13 +80,21 @@ file `basic_login_spec.rb`.
### The outer `context` block
-Specs have an outer `context` indicating the DevOps stage.
+See the [`RSpec.describe` outer block](#the-outer-rspecdescribe-block)
+
+CAUTION: **Deprecation notice:**
+The outer `context` [was deprecated](https://gitlab.com/gitlab-org/quality/team-tasks/-/issues/550) in `13.2`
+in adherance to RSpec 4.0 specifications. Use `RSpec.describe` instead.
+
+### The outer `RSpec.describe` block
+
+Specs have an outer `RSpec.describe` indicating the DevOps stage.
```ruby
# frozen_string_literal: true
module QA
- context 'Manage' do
+ RSpec.describe 'Manage' do
end
end
@@ -94,13 +102,13 @@ end
### The `describe` block
-Inside of our outer `context`, describe the feature to test. In this case, `Login`.
+Inside of our outer `RSpec.describe`, describe the feature to test. In this case, `Login`.
```ruby
# frozen_string_literal: true
module QA
- context 'Manage' do
+ RSpec.describe 'Manage' do
describe 'Login' do
end
@@ -115,7 +123,7 @@ writing end-to-end tests is to write test case descriptions as `it` blocks:
```ruby
module QA
- context 'Manage' do
+ RSpec.describe 'Manage' do
describe 'Login' do
it 'can login' do
@@ -139,7 +147,7 @@ Begin by logging in.
# frozen_string_literal: true
module QA
- context 'Manage' do
+ RSpec.describe 'Manage' do
describe 'Login' do
it 'can login' do
Flow::Login.sign_in
@@ -162,7 +170,7 @@ should answer the question "What do we test?"
# frozen_string_literal: true
module QA
- context 'Manage' do
+ RSpec.describe 'Manage' do
describe 'Login' do
it 'can login' do
Flow::Login.sign_in
@@ -210,7 +218,7 @@ a call to `sign_in`.
# frozen_string_literal: true
module QA
- context 'Manage' do
+ RSpec.describe 'Manage' do
describe 'Login' do
before do
Flow::Login.sign_in
@@ -247,7 +255,7 @@ stage, so [create a file](#identify-the-devops-stage) in
# frozen_string_literal: true
module QA
- context 'Plan' do
+ RSpec.describe 'Plan' do
describe 'Issues' do
let(:issue) do
Resource::Issue.fabricate_via_api! do |issue|
diff --git a/doc/development/testing_guide/end_to_end/environment_selection.md b/doc/development/testing_guide/end_to_end/environment_selection.md
new file mode 100644
index 00000000000..9eb7db72a51
--- /dev/null
+++ b/doc/development/testing_guide/end_to_end/environment_selection.md
@@ -0,0 +1,54 @@
+# Environment selection
+
+Some tests are designed to be run against specific environments. We can specify
+what environments to run tests against using the `only` metadata.
+
+## Available switches
+
+| Switch | Function | Type |
+| -------| ------- | ----- |
+| `tld` | Set the top-level domain matcher | `String` |
+| `subdomain` | Set the subdomain matcher | `Array` or `String` |
+| `domain` | Set the domain matcher | `String` |
+| `production` | Match against production | `Static` |
+
+CAUTION: **Caution:**
+You cannot specify `:production` and `{ <switch>: 'value' }` simultaneously.
+These options are mutually exclusive. If you want to specify production, you
+can control the `tld` and `domain` independently.
+
+## Examples
+
+| Environment | Key | Matches (regex) |
+| ---------------- | --- | --------------- |
+| `any` | `` | `.+.com` |
+| `gitlab.com` | `only: :production` | `gitlab.com` |
+| `staging.gitlab.com` | `only: { subdomain: :staging }` | `(staging).+.com` |
+| `gitlab.com and staging.gitlab.com` | `only: { subdomain: /(staging.)?/, domain: 'gitlab' }` | `(staging.)?gitlab.com` |
+| `dev.gitlab.org` | `only: { tld: '.org', domain: 'gitlab', subdomain: 'dev' }` | `(dev).gitlab.org` |
+| `staging.gitlab.com & domain.gitlab.com` | `only: { subdomain: %i[staging domain] }` | `(staging|domain).+.com` |
+
+```ruby
+RSpec.describe 'Area' do
+ it 'runs in any environment' do; end
+
+ it 'runs only in production', only: :production do; end
+
+ it 'runs only in staging', only: { subdomain: :staging } do; end
+
+ it 'runs in dev', only: { tld: '.org', domain: 'gitlab', subdomain: 'dev' } do; end
+
+ it 'runs in prod and staging', only: { subdomain: /(staging.)?/, domain: 'gitlab' } {}
+end
+```
+
+NOTE: **Note:**
+If the test has a `before` or `after`, you must add the `only` metadata
+to the outer `RSpec.describe`.
+
+## Quarantining a test for a specific environment
+
+Similarly to specifying that a test should only run against a specific environment, it's also possible to quarantine a
+test only when it runs against a specific environment. The syntax is exactly the same, except that the `only: { ... }`
+hash is nested in the [`quarantine: { ... }`](https://about.gitlab.com/handbook/engineering/quality/guidelines/debugging-qa-test-failures/#quarantining-tests) hash.
+For instance, `quarantine: { only: { subdomain: :staging } }` will only quarantine the test when run against staging.
diff --git a/doc/development/testing_guide/end_to_end/feature_flags.md b/doc/development/testing_guide/end_to_end/feature_flags.md
index ada20cc9dad..87a9738b313 100644
--- a/doc/development/testing_guide/end_to_end/feature_flags.md
+++ b/doc/development/testing_guide/end_to_end/feature_flags.md
@@ -7,7 +7,7 @@ Note that administrator authorization is required to change feature flags. `QA::
Please be sure to include the tag `:requires_admin` so that the test can be skipped in environments where admin access is not available.
```ruby
-context "with feature flag enabled", :requires_admin do
+RSpec.describe "with feature flag enabled", :requires_admin do
before do
Runtime::Feature.enable('feature_flag_name')
end
diff --git a/doc/development/testing_guide/end_to_end/rspec_metadata_tests.md b/doc/development/testing_guide/end_to_end/rspec_metadata_tests.md
index b1a8a14163c..4059c1960e2 100644
--- a/doc/development/testing_guide/end_to_end/rspec_metadata_tests.md
+++ b/doc/development/testing_guide/end_to_end/rspec_metadata_tests.md
@@ -10,10 +10,11 @@ This is a partial list of the [RSpec metadata](https://relishapp.com/rspec/rspec
| `:elasticsearch` | The test requires an Elasticsearch service. It is used by the [instance-level scenario](https://gitlab.com/gitlab-org/gitlab-qa#definitions) [`Test::Integration::Elasticsearch`](https://gitlab.com/gitlab-org/gitlab/-/blob/72b62b51bdf513e2936301cb6c7c91ec27c35b4d/qa/qa/ee/scenario/test/integration/elasticsearch.rb) to include only tests that require Elasticsearch. |
| `:kubernetes` | The test includes a GitLab instance that is configured to be run behind an SSH tunnel, allowing a TLS-accessible GitLab. This test will also include provisioning of at least one Kubernetes cluster to test against. *This tag is often be paired with `:orchestrated`.* |
| `:orchestrated` | The GitLab instance under test may be [configured by `gitlab-qa`](https://gitlab.com/gitlab-org/gitlab-qa/-/blob/master/docs/what_tests_can_be_run.md#orchestrated-tests) to be different to the default GitLab configuration, or `gitlab-qa` may launch additional services in separate Docker containers, or both. Tests tagged with `:orchestrated` are excluded when testing environments where we can't dynamically modify GitLab's configuration (for example, Staging). |
-| `:quarantine` | The test has been [quarantined](https://about.gitlab.com/handbook/engineering/quality/guidelines/debugging-qa-test-failures/#quarantining-tests), will run in a separate job that only includes quarantined tests, and is allowed to fail. The test will be skipped in its regular job so that if it fails it will not hold up the pipeline. |
+| `:quarantine` | The test has been [quarantined](https://about.gitlab.com/handbook/engineering/quality/guidelines/debugging-qa-test-failures/#quarantining-tests), will run in a separate job that only includes quarantined tests, and is allowed to fail. The test will be skipped in its regular job so that if it fails it will not hold up the pipeline. Note that you can also [quarantine a test only when it runs against specific environment](environment_selection.md#quarantining-a-test-for-a-specific-environment). |
| `:reliable` | The test has been [promoted to a reliable test](https://about.gitlab.com/handbook/engineering/quality/guidelines/reliable-tests/#promoting-an-existing-test-to-reliable) meaning it passes consistently in all pipelines, including merge requests. |
| `:requires_admin` | The test requires an admin account. Tests with the tag are excluded when run against Canary and Production environments. |
| `:runner` | The test depends on and will set up a GitLab Runner instance, typically to run a pipeline. |
| `:gitaly_ha` | The test will run against a GitLab instance where repositories are stored on redundant Gitaly nodes behind a Praefect node. All nodes are [separate containers](../../../administration/gitaly/praefect.md#requirements-for-configuring-a-gitaly-cluster). Tests that use this tag have a longer setup time since there are three additional containers that need to be started. |
| `:skip_live_env` | The test will be excluded when run against live deployed environments such as Staging, Canary, and Production. |
| `:jira` | The test requires a Jira Server. [GitLab-QA](https://gitlab.com/gitlab-org/gitlab-qa) will provision the Jira Server in a Docker container when the `Test::Integration::Jira` test scenario is run.
+| `:only` | The test is only to be run against specific environments. See [Environment selection](environment_selection.md) for more information. |
diff --git a/doc/development/testing_guide/frontend_testing.md b/doc/development/testing_guide/frontend_testing.md
index 37e1066e7aa..ef9fd748dbb 100644
--- a/doc/development/testing_guide/frontend_testing.md
+++ b/doc/development/testing_guide/frontend_testing.md
@@ -545,6 +545,99 @@ In order to ensure that a clean wrapper object and DOM are being used in each te
See also the [Vue Test Utils documentation on `destroy`](https://vue-test-utils.vuejs.org/api/wrapper/#destroy).
+### Jest best practices
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/34209) in GitLab 13.2.
+
+#### Prefer `toBe` over `toEqual` when comparing primitive values
+
+Jest has [`toBe`](https://jestjs.io/docs/en/expect#tobevalue) and
+[`toEqual`](https://jestjs.io/docs/en/expect#toequalvalue) matchers.
+As [`toBe`](https://jestjs.io/docs/en/expect#tobevalue) uses
+[`Object.is`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is)
+to compare values, it's faster (by default) than using `toEqual`.
+While the latter will eventually fallback to leverage [`Object.is`](https://github.com/facebook/jest/blob/master/packages/expect/src/jasmineUtils.ts#L91),
+for primitive values, it should only be used when complex objects need a comparison.
+
+Examples:
+
+```javascript
+const foo = 1;
+
+// good
+expect(foo).toBe(1);
+
+// bad
+expect(foo).toEqual(1);
+```
+
+#### Prefer more befitting matchers
+
+Jest provides useful matchers like `toHaveLength` or `toBeUndefined` to make your tests more
+readable and to produce more understandable error messages. Check their docs for the
+[full list of matchers](https://jestjs.io/docs/en/expect#methods).
+
+Examples:
+
+```javascript
+const arr = [1, 2];
+
+// prints:
+// Expected length: 1
+// Received length: 2
+expect(arr).toHaveLength(1);
+
+// prints:
+// Expected: 1
+// Received: 2
+expect(arr.length).toBe(1);
+
+// prints:
+// expect(received).toBe(expected) // Object.is equality
+// Expected: undefined
+// Received: "bar"
+const foo = 'bar';
+expect(foo).toBe(undefined);
+
+// prints:
+// expect(received).toBeUndefined()
+// Received: "bar"
+const foo = 'bar';
+expect(foo).toBeUndefined();
+```
+
+#### Avoid using `toBeTruthy` or `toBeFalsy`
+
+Jest also provides following matchers: `toBeTruthy` and `toBeFalsy`. We should not use them because
+they make tests weaker and produce false-positive results.
+
+For example, `expect(someBoolean).toBeFalsy()` passes when `someBoolean === null`, and when
+`someBoolean === false`.
+
+#### Tricky `toBeDefined` matcher
+
+Jest has the tricky `toBeDefined` matcher that can produce false positive test. Because it
+[validates](https://github.com/facebook/jest/blob/master/packages/expect/src/matchers.ts#L204)
+the given value for `undefined` only.
+
+```javascript
+// good
+expect(wrapper.find('foo').exists()).toBe(true);
+
+// bad
+// if finder returns null, the test will pass
+expect(wrapper.find('foo')).toBeDefined();
+```
+
+#### Avoid using `setImmediate`
+
+Try to avoid using `setImmediate`. `setImmediate` is an ad-hoc solution to run your callback after
+the I/O completes. And it's not part of the Web API, hence, we target NodeJS environments in our
+unit tests.
+
+Instead of `setImmediate`, use `jest.runAllTimers` or `jest.runOnlyPendingTimers` to run pending timers.
+The latter is useful when you have `setInterval` in the code. **Remember:** our Jest configuration uses fake timers.
+
## Factories
TBU
@@ -856,7 +949,8 @@ Some regressions only affect a specific browser version. We can install and test
[BrowserStack](https://www.browserstack.com/) allows you to test more than 1200 mobile devices and browsers.
You can use it directly through the [live app](https://www.browserstack.com/live) or you can install the [chrome extension](https://chrome.google.com/webstore/detail/browserstack/nkihdmlheodkdfojglpcjjmioefjahjb) for easy access.
-You can find the credentials on 1Password, under `frontendteam@gitlab.com`.
+Sign in to BrowserStack with the credentials saved in the **Engineering** vault of GitLab's
+[shared 1Password account](https://about.gitlab.com/handbook/security/#1password-guide).
### Firefox
diff --git a/doc/development/testing_guide/review_apps.md b/doc/development/testing_guide/review_apps.md
index 3d7aea89e73..54f8ca0d98b 100644
--- a/doc/development/testing_guide/review_apps.md
+++ b/doc/development/testing_guide/review_apps.md
@@ -9,7 +9,7 @@ pipeline](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/6665).
```mermaid
graph TD
- A["build-qa-image, gitlab:assets:compile pull-cache<br/>(canonical default refs only)"];
+ A["build-qa-image, compile-production-assets<br/>(canonical default refs only)"];
B[review-build-cng];
C[review-deploy];
D[CNG-mirror];
@@ -30,7 +30,7 @@ subgraph "2. gitlab `review-prepare` stage"
end
subgraph "3. gitlab `review` stage"
- C["review-deploy<br><br>Helm deploys the Review App using the Cloud<br/>Native images built by the CNG-mirror pipeline.<br><br>Cloud Native images are deployed to the `review-apps-ce` or `review-apps-ee`<br>Kubernetes (GKE) cluster, in the GCP `gitlab-review-apps` project."]
+ C["review-deploy<br><br>Helm deploys the Review App using the Cloud<br/>Native images built by the CNG-mirror pipeline.<br><br>Cloud Native images are deployed to the `review-apps`<br>Kubernetes (GKE) cluster, in the GCP `gitlab-review-apps` project."]
end
subgraph "4. gitlab `qa` stage"
@@ -44,25 +44,27 @@ subgraph "CNG-mirror pipeline"
### Detailed explanation
-1. On every [pipeline](https://gitlab.com/gitlab-org/gitlab/pipelines/125315730) during the `test` stage, the
- [`gitlab:assets:compile`](https://gitlab.com/gitlab-org/gitlab/-/jobs/467724487) job is automatically started.
- - Once it's done, it starts the [`review-build-cng`](https://gitlab.com/gitlab-org/gitlab/-/jobs/467724808)
- manual job since the [`CNG-mirror`](https://gitlab.com/gitlab-org/build/CNG-mirror) pipeline triggered in the
+1. On every [pipeline](https://gitlab.com/gitlab-org/gitlab/pipelines/125315730) during the `prepare` stage, the
+ [`compile-production-assets`](https://gitlab.com/gitlab-org/gitlab/-/jobs/641770154) job is automatically started.
+ - Once it's done, the [`review-build-cng`](https://gitlab.com/gitlab-org/gitlab/-/jobs/467724808)
+ job starts since the [`CNG-mirror`](https://gitlab.com/gitlab-org/build/CNG-mirror) pipeline triggered in the
following step depends on it.
-1. The [`review-build-cng`](https://gitlab.com/gitlab-org/gitlab/-/jobs/467724808) job [triggers a pipeline](https://gitlab.com/gitlab-org/build/CNG-mirror/pipelines/44364657)
+1. Once `compile-production-assets` is done, the [`review-build-cng`](https://gitlab.com/gitlab-org/gitlab/-/jobs/467724808)
+ job [triggers a pipeline](https://gitlab.com/gitlab-org/build/CNG-mirror/pipelines/44364657)
in the [`CNG-mirror`](https://gitlab.com/gitlab-org/build/CNG-mirror) project.
+ - The `review-build-cng` job automatically starts only if your MR includes
+ [CI or frontend changes](../pipelines.md#changes-patterns). In other cases, the job is manual.
- The [`CNG-mirror`](https://gitlab.com/gitlab-org/build/CNG-mirror/pipelines/44364657) pipeline creates the Docker images of
each component (e.g. `gitlab-rails-ee`, `gitlab-shell`, `gitaly` etc.)
based on the commit from the [GitLab pipeline](https://gitlab.com/gitlab-org/gitlab/pipelines/125315730) and stores
them in its [registry](https://gitlab.com/gitlab-org/build/CNG-mirror/container_registry).
- We use the [`CNG-mirror`](https://gitlab.com/gitlab-org/build/CNG-mirror) project so that the `CNG`, (Cloud
- Native GitLab), project's registry is not overloaded with a
- lot of transient Docker images.
+ Native GitLab), project's registry is not overloaded with a lot of transient Docker images.
- Note that the official CNG images are built by the `cloud-native-image`
job, which runs only for tags, and triggers itself a [`CNG`](https://gitlab.com/gitlab-org/build/CNG) pipeline.
-1. Once the `test` stage is done, the [`review-deploy`](https://gitlab.com/gitlab-org/gitlab/-/jobs/467724810) job
+1. Once `review-build-cng` is done, the [`review-deploy`](https://gitlab.com/gitlab-org/gitlab/-/jobs/467724810) job
deploys the Review App using [the official GitLab Helm chart](https://gitlab.com/gitlab-org/charts/gitlab/) to
- the [`review-apps-ce`](https://console.cloud.google.com/kubernetes/clusters/details/us-central1-a/review-apps-ce?project=gitlab-review-apps) / [`review-apps-ee`](https://console.cloud.google.com/kubernetes/clusters/details/us-central1-b/review-apps-ee?project=gitlab-review-apps)
+ the [`review-apps`](https://console.cloud.google.com/kubernetes/clusters/details/us-central1-b/review-apps?project=gitlab-review-apps)
Kubernetes cluster on GCP.
- The actual scripts used to deploy the Review App can be found at
[`scripts/review_apps/review-apps.sh`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/scripts/review_apps/review-apps.sh).
@@ -94,10 +96,9 @@ subgraph "CNG-mirror pipeline"
- The manual `review-stop` can be used to
stop a Review App manually, and is also started by GitLab once a merge
request's branch is deleted after being merged.
-- The Kubernetes cluster is connected to the `gitlab-{ce,ee}` projects using
+- The Kubernetes cluster is connected to the `gitlab` projects using
[GitLab's Kubernetes integration](../../user/project/clusters/index.md). This basically
- allows to have a link to the Review App directly from the merge request
- widget.
+ allows to have a link to the Review App directly from the merge request widget.
### Auto-stopping of Review Apps
@@ -136,11 +137,10 @@ browser performance testing using a
### Node pools
-The `review-apps-ee` and `review-apps-ce` clusters are currently set up with
+The `review-apps` cluster is currently set up with
the following node pools:
-- `review-apps-ee` of pre-emptible `e2-highcpu-16` (16 vCPU, 16 GB memory) nodes with autoscaling
-- `review-apps-ce` of pre-emptible `n1-standard-8` (8 vCPU, 16 GB memory) nodes with autoscaling
+- `e2-highcpu-16` (16 vCPU, 16 GB memory) pre-emptible nodes with autoscaling
### Helm
@@ -189,9 +189,7 @@ secure note named `gitlab-{ce,ee} Review App's root password`.
1. Click on the `KUBECTL` dropdown, then `Exec` -> `task-runner`.
1. Replace `-c task-runner -- ls` with `-it -- gitlab-rails console` from the
default command or
- - Run `kubectl exec --namespace review-apps-ce review-qa-raise-e-12chm0-task-runner-d5455cc8-2lsvz -it -- gitlab-rails console` and
- - Replace `review-apps-ce` with `review-apps-ee` if the Review App
- is running EE, and
+ - Run `kubectl exec --namespace review-apps review-qa-raise-e-12chm0-task-runner-d5455cc8-2lsvz -it -- gitlab-rails console` and
- Replace `review-qa-raise-e-12chm0-task-runner-d5455cc8-2lsvz`
with your Pod's name.
diff --git a/doc/development/testing_guide/testing_levels.md b/doc/development/testing_guide/testing_levels.md
index 2210ec94696..88d7cf9ca08 100644
--- a/doc/development/testing_guide/testing_levels.md
+++ b/doc/development/testing_guide/testing_levels.md
@@ -482,7 +482,7 @@ Every new feature should come with a [test plan](https://gitlab.com/gitlab-org/g
| Tests path | Testing engine | Notes |
| ---------- | -------------- | ----- |
-| `qa/qa/specs/features/` | [Capybara](https://github.com/teamcapybara/capybara) + [RSpec](https://github.com/rspec/rspec-rails#feature-specs) + Custom QA framework | Tests should be placed under their corresponding [Product category](https://about.gitlab.com/handbook/product/categories/) |
+| `qa/qa/specs/features/` | [Capybara](https://github.com/teamcapybara/capybara) + [RSpec](https://github.com/rspec/rspec-rails#feature-specs) + Custom QA framework | Tests should be placed under their corresponding [Product category](https://about.gitlab.com/handbook/product/product-categories/) |
> See [end-to-end tests](end_to_end/index.md) for more information.
diff --git a/doc/development/testing_guide/testing_migrations_guide.md b/doc/development/testing_guide/testing_migrations_guide.md
index a03b940fe40..8ee758177c3 100644
--- a/doc/development/testing_guide/testing_migrations_guide.md
+++ b/doc/development/testing_guide/testing_migrations_guide.md
@@ -112,7 +112,7 @@ migration. You can find the complete spec in
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20170526185842_migrate_pipeline_stages.rb')
-describe MigratePipelineStages do
+RSpec.describe MigratePipelineStages do
# Create test data - pipeline and CI/CD jobs.
let(:jobs) { table(:ci_builds) }
let(:stages) { table(:ci_stages) }
diff --git a/doc/development/what_requires_downtime.md b/doc/development/what_requires_downtime.md
index 407899b23d5..d0b8aa18f5c 100644
--- a/doc/development/what_requires_downtime.md
+++ b/doc/development/what_requires_downtime.md
@@ -130,7 +130,8 @@ class CleanupUsersUpdatedAtRename < ActiveRecord::Migration[4.2]
end
```
-NOTE: **Note:** If you're renaming a [large table](https://gitlab.com/gitlab-org/gitlab/-/blob/master/rubocop/rubocop-migrations.yml#L3), please carefully consider the state when the first migration has run but the second cleanup migration hasn't been run yet.
+NOTE: **Note:**
+If you're renaming a [large table](https://gitlab.com/gitlab-org/gitlab/-/blob/master/rubocop/rubocop-migrations.yml#L3), please carefully consider the state when the first migration has run but the second cleanup migration hasn't been run yet.
With [Canary](https://about.gitlab.com/handbook/engineering/infrastructure/library/canary/) it is possible that the system runs in this state for a significant amount of time.
## Changing Column Constraints
@@ -202,6 +203,21 @@ end
And that's it, we're done!
+### Casting data to a new type
+
+Some type changes require casting data to a new type. For example when changing from `text` to `jsonb`.
+In this case, use the `type_cast_function` option.
+Make sure there is no bad data and the cast will always succeed. You can also provide a custom function that handles
+casting errors.
+
+Example migration:
+
+```ruby
+ def up
+ change_column_type_concurrently :users, :settings, :jsonb, type_cast_function: 'jsonb'
+ end
+```
+
## Changing The Schema For Large Tables
While `change_column_type_concurrently` and `rename_column_concurrently` can be
@@ -317,30 +333,11 @@ migrations](background_migrations.md#cleaning-up).
## Adding Indexes
-Adding indexes is an expensive process that blocks INSERT and UPDATE queries for
-the duration. You can work around this by using the `CONCURRENTLY` option:
-
-```sql
-CREATE INDEX CONCURRENTLY index_name ON projects (column_name);
-```
-
-Migrations can take advantage of this by using the method
-`add_concurrent_index`. For example:
-
-```ruby
-class MyMigration < ActiveRecord::Migration[4.2]
- def up
- add_concurrent_index :projects, :column_name
- end
-
- def down
- remove_index(:projects, :column_name) if index_exists?(:projects, :column_name)
- end
-end
-```
+Adding indexes does not require downtime when `add_concurrent_index`
+is used.
-Note that `add_concurrent_index` can not be reversed automatically, thus you
-need to manually define `up` and `down`.
+See also [Migration Style Guide](migration_style_guide.md#adding-indexes)
+for more information.
## Dropping Indexes
diff --git a/doc/gitlab-basics/start-using-git.md b/doc/gitlab-basics/start-using-git.md
index 439032f39b3..d767bee4cf1 100644
--- a/doc/gitlab-basics/start-using-git.md
+++ b/doc/gitlab-basics/start-using-git.md
@@ -1,7 +1,7 @@
---
type: howto, tutorial
description: "Introduction to using Git through the command line."
-last_updated: 2020-04-22
+last_updated: 2020-06-30
---
# Start using Git on the command line
@@ -104,68 +104,134 @@ repository.
You can read more on how Git manages configurations in the
[Git Config](https://git-scm.com/book/en/v2/Customizing-Git-Git-Configuration) documentation.
-## Basic Git commands
+## Git authentication methods
-Start using Git via the command line with the most basic commands as described below.
+To connect your computer with GitLab, you need to add your credentials to identify yourself.
+You have two options:
-### Initialize a local directory for Git version control
+- Authenticate on a project-by-project basis through HTTPS, and enter your credentials every time
+ you perform an operation between your computer and GitLab.
+- Authenticate through SSH once and GitLab won't ask your credentials every time you pull, push,
+ and clone.
-If you have an existing local directory that you want to *initialize* for version
-control, use the `init` command to instruct Git to begin tracking the directory:
+To start the authentication process, we'll [clone](#clone-a-repository) an existing repository
+to our computer:
-```shell
-git init
-```
+- If you want to use **SSH** to authenticate, follow the instructions on the [SSH documentation](../ssh/README.md)
+ to set it up before cloning.
+- If you want to use **HTTPS**, GitLab will request your user name and password:
+ - If you have 2FA enabled for your account, you'll have to use a [Personal Access Token](../user/profile/personal_access_tokens.md)
+ with **read_repository** or **write_repository** permissions instead of your account's password.
+ Create one before cloning.
+ - If you don't have 2FA enabled, use your account's password.
-This creates a `.git` directory that contains the Git configuration files.
+NOTE: **Note:**
+Authenticating via SSH is GitLab's recommended method. You can read more about credential storage
+in the [Git Credentials documentation](https://git-scm.com/book/en/v2/Git-Tools-Credential-Storage).
-Once the directory has been initialized, you can [add a remote repository](#add-a-remote-repository)
-and [send changes to GitLab.com](#send-changes-to-gitlabcom). You will also need to
-[create a new project in GitLab](../gitlab-basics/create-project.md#push-to-create-a-new-project)
-for your Git repository.
+## Git terminology
-### Clone a repository
+If you're familiar with the Git terminology, you may want to jump directly
+into the [basic commands](#basic-git-commands).
+
+### Namespace
+
+A **namespace** is either a **user name** or a **group name**.
+
+For example, suppose Jo is a GitLab.com user and they chose their user name as
+`jo`. You can see Jo's profile at `https://gitlab.com/jo`. `jo` is a namespace.
+
+Jo also created a group in GitLab, and chose the path `test-group` for their
+group. The group can be accessed under `https://gitlab.com/test-group`. `test-group` is a namespace.
+
+### Repository
+
+Your files in GitLab live in a **repository**, similar to how you have them in a folder or
+directory in your computer. **Remote** repository refers to the files in
+GitLab and the copy in your computer is called **local** copy.
+A **project** in GitLab is what holds a repository, which holds your files.
+Often, the word "repository" is shortened to "repo".
+
+### Fork
+
+When you want to copy someone else's repository, you [**fork**](../user/project/repository/forking_workflow.md#creating-a-fork)
+the project. By forking it, you'll create a copy of the project into your own
+namespace to have read and write permissions to modify the project files
+and settings.
+
+For example, if you fork this project, <https://gitlab.com/gitlab-tests/sample-project/> into your namespace, you'll create your own copy of the repository in your namespace (`https://gitlab.com/your-namespace/sample-project/`). From there, you can clone it into your computer,
+work on its files, and (optionally) submit proposed changes back to the
+original repository if you'd like.
+
+### Download vs clone
-To start working locally on an existing remote repository, clone it with the command
-`git clone <repository path>`. By cloning a repository, you'll download a copy of its
-files to your local computer, automatically preserving the Git connection with the
-remote repository.
+To create a copy of a remote repository files on your computer, you can either
+**download** or **clone** it. If you download it, you cannot sync it with the
+remote repository on GitLab.
-You can either clone it via [HTTPS](#clone-via-https) or [SSH](#clone-via-ssh). If you chose to
-clone it via HTTPS, you'll have to enter your credentials every time you pull and push.
-You can read more about credential storage in the
-[Git Credentials documentation](https://git-scm.com/book/en/v2/Git-Tools-Credential-Storage).
-With [SSH](../ssh/README.md), you enter your credentials only once.
+On the other hand, by cloning a repository, you'll download a copy of its
+files to your local computer, but preserve the Git connection with the remote
+repository, so that you can work on the its files on your computer and then
+upload the changes to GitLab.
+
+### Pull and push
+
+After you saved a local copy of a repository and modified its files on your computer, you can upload the
+changes to GitLab. This is referred to as **pushing** to GitLab, as this is achieved by the command
+[`git push`](#send-changes-to-gitlabcom).
+
+When the remote repository changes, your local copy will be behind it. You can update it with the new
+changes in the remote repo.
+This is referred to as **pulling** from GitLab, as this is achieved by the command
+[`git pull`](#download-the-latest-changes-in-the-project).
+
+## Basic Git commands
+
+For the purposes of this guide, we will use this example project on GitLab.com:
+[https://gitlab.com/gitlab-tests/sample-project/](https://gitlab.com/gitlab-tests/sample-project/).
+
+To use it, log into GitLab.com and fork the example project into your
+namespace to have your own copy to playing with. Your sample
+project will be available under `https://gitlab.com/<your-namespace>/sample-project/`.
+
+You can also choose any other project to follow this guide. Then, replace the
+example URLs with your own project's.
+
+If you want to start by copying an existing GitLab repository onto your
+computer, see how to [clone a repository](#clone-a-repository). On the other
+hand, if you want to start by uploading an existing folder from your computer
+to GitLab, see how to [convert a local folder into a Git repository](#convert-a-local-directory-into-a-repository).
+
+### Clone a repository
+
+To start working locally on an existing remote repository, clone it with the
+command `git clone <repository path>`. You can either clone it via [HTTPS](#clone-via-https) or [SSH](#clone-via-ssh), according to your preferred [authentication method](#git-authentication-methods).
You can find both paths (HTTPS and SSH) by navigating to your project's landing page
and clicking **Clone**. GitLab will prompt you with both paths, from which you can copy
and paste in your command line.
-As an example, consider this repository path:
+For example, considering our [sample project](https://gitlab.com/gitlab-tests/sample-project/):
-- HTTPS: `https://gitlab.com/gitlab-org/gitlab.git`
-- SSH: `git@gitlab.com:gitlab-org/gitlab.git`
+- To clone through HTTPS, use `https://gitlab.com/gitlab-tests/sample-project.git`.
+- To clone through SSH, use `git@gitlab.com:gitlab-tests/sample-project.git`.
-To get started, open a terminal window in the directory you wish to clone the
+To get started, open a terminal window in the directory you wish to add the
repository files into, and run one of the `git clone` commands as described below.
Both commands will download a copy of the files in a folder named after the project's
-name. You can then navigate to the new directory and start working on it locally.
+name and preserve the connection with the remote repository.
+You can then navigate to the new directory with `cd sample-project` and start working on it
+locally.
#### Clone via HTTPS
-To clone `https://gitlab.com/gitlab-org/gitlab.git` via HTTPS:
+To clone `https://gitlab.com/gitlab-tests/sample-project/` via HTTPS:
```shell
-git clone https://gitlab.com/gitlab-org/gitlab.git
+git clone https://gitlab.com/gitlab-tests/sample-project.git
```
-You'll have to add your password every time you clone through HTTPS. If you have 2FA enabled
-for your account, you'll have to use a [Personal Access Token](../user/profile/personal_access_tokens.md)
-with **read_repository** or **write_repository** permissions instead of your account's password.
-
-If you don't have 2FA enabled, use your account's password.
-
TIP: **Troubleshooting:**
On Windows, if you entered incorrect passwords multiple times and GitLab is responding `Access denied`,
you may have to add your namespace (user name or group name) to clone through HTTPS:
@@ -179,16 +245,44 @@ To clone `git@gitlab.com:gitlab-org/gitlab.git` via SSH:
git clone git@gitlab.com:gitlab-org/gitlab.git
```
-### Switch to the master branch
+### Convert a local directory into a repository
-You are always in a branch when working with Git. The main branch is the master
-branch, but you can use the same command to switch to a different branch by
-changing `master` to the branch name.
+When you have your files in a local folder and want to convert it into
+a repository, you'll need to _initialize_ the folder through the `git init`
+command. This will instruct Git to begin to track that directory as a
+repository. To do so, open the terminal on the directory you'd like to convert
+and run:
```shell
-git checkout master
+git init
```
+This command creates a `.git` folder in your directory that contains Git
+records and configuration files. We advise against editing these files
+directly.
+
+Then, on the next step, add the [path to your remote repository](#add-a-remote-repository)
+so that Git can upload your files into the correct project.
+
+#### Add a remote repository
+
+By "adding a remote repository" to your local directory you'll tell Git that
+the path to that specific project in GitLab corresponds to that specific
+folder you have in your computer. This way, your local folder will be
+identified by Git as the local content for that specific remote project.
+
+To add a remote repository to your local copy:
+
+1. In GitLab, [create a new project](../gitlab-basics/create-project.md#push-to-create-a-new-project) to hold your files.
+1. Visit this project's homepage, scroll down to **Push an existing folder**, and copy the command that starts with `git remote add`.
+1. On your computer, open the terminal in the directory you've initialized, paste the command you copied, and press <kbd>enter</kbd>:
+
+ ```shell
+ git remote add origin <git@gitlab.com:username/projectpath.git
+ ```
+
+After you've done that, you can [stage your files](#add-and-commit-local-changes) and [upload them to GitLab](#send-changes-to-gitlabcom).
+
### Download the latest changes in the project
To work on an up-to-date copy of the project (it is important to do this every time
@@ -219,16 +313,16 @@ git remote -v
The `-v` flag stands for verbose.
-### Add a remote repository
+## Branching
-To add a link to a remote repository:
+If you want to add code to a project but you're not sure if it will work properly, or you're
+collaborating on the project with others, and don't want your work to get mixed up, it's a good idea
+to work on a different **branch**.
-```shell
-git remote add <source-name> <repository-path>
-```
-
-You'll use this source name every time you [push changes to GitLab.com](#send-changes-to-gitlabcom),
-so use something easy to remember and type.
+When you create a branch in a Git repository, you make a copy of its files at the time of branching. You're free
+to do whatever you want with the code in your branch without impacting the main branch or other branches. And when
+you're ready to bring your changes to the main codebase, you can merge your branch into the main one
+used in your project (such as `master`).
### Create a branch
@@ -240,6 +334,16 @@ use a hyphen or underscore):
git checkout -b <name-of-branch>
```
+### Switch to the master branch
+
+You are always in a branch when working with Git. The main branch is the master
+branch, but you can use the same command to switch to a different branch by
+changing `master` to the branch name.
+
+```shell
+git checkout master
+```
+
### Work on an existing branch
To switch to an existing branch, so you can work on it:
@@ -279,7 +383,7 @@ git add <file-name OR folder-name>
git commit -m "COMMENT TO DESCRIBE THE INTENTION OF THE COMMIT"
```
-### Add all changes to commit
+#### Add all changes to commit
To add and commit (save) all local changes quickly:
@@ -293,10 +397,6 @@ The `.` character means _all file changes in the current directory and all subdi
### Send changes to GitLab.com
-NOTE: **Note:**
-To create a merge request from a fork to an upstream repository, see the
-[forking workflow](../user/project/repository/forking_workflow.md)
-
To push all local commits (saved changes) to the remote repository:
```shell
@@ -309,6 +409,10 @@ For example, to push your local commits to the _`master`_ branch of the _`origin
git push origin master
```
+NOTE: **Note:**
+To create a merge request from a fork to an upstream repository, see the
+[forking workflow](../user/project/repository/forking_workflow.md).
+
### Delete all changes in the branch
To delete all local changes in the branch that have not been added to the staging
@@ -353,7 +457,7 @@ git checkout <name-of-branch>
git merge master
```
-### Synchronize changes in a forked repository with the upstream
+## Synchronize changes in a forked repository with the upstream
[Forking a repository](../user/project/repository/forking_workflow.md) lets you create
a copy of a repository in your namespace. Changes made to your copy of the repository
diff --git a/doc/install/aws/index.md b/doc/install/aws/index.md
index 813e343f2cc..35c046423b0 100644
--- a/doc/install/aws/index.md
+++ b/doc/install/aws/index.md
@@ -7,8 +7,8 @@ type: howto
This page offers a walkthrough of a common configuration
for GitLab on AWS. You should customize it to accommodate your needs.
-NOTE: **Note**
-For organizations with 300 users or less, the recommended AWS installation method is to launch an EC2 single box [Omnibus Installation](https://about.gitlab.com/install/) and implement a snapshot strategy for backing up the data.
+NOTE: **Note:**
+For organizations with 1,000 users or less, the recommended AWS installation method is to launch an EC2 single box [Omnibus Installation](https://about.gitlab.com/install/) and implement a snapshot strategy for backing up the data. See the [1,000 user reference architecture](../../administration/reference_architectures/1k_users.md) for more.
## Introduction
@@ -30,7 +30,8 @@ In addition to having a basic familiarity with [AWS](https://docs.aws.amazon.com
- A domain name for the GitLab instance
- An SSL/TLS certificate to secure your domain. If you do not already own one, you can provision a free public SSL/TLS certificate through [AWS Certificate Manager](https://aws.amazon.com/certificate-manager/)(ACM) for use with the [Elastic Load Balancer](#load-balancer) we'll create.
-NOTE: **Note:** It can take a few hours to validate a certificate provisioned through ACM. To avoid delays later, request your certificate as soon as possible.
+NOTE: **Note:**
+It can take a few hours to validate a certificate provisioned through ACM. To avoid delays later, request your certificate as soon as possible.
## Architecture
@@ -230,7 +231,10 @@ On the EC2 dashboard, look for Load Balancer in the left navigation bar:
1. Click the **Create Load Balancer** button.
1. Choose the **Classic Load Balancer**.
1. Give it a name (we'll use `gitlab-loadbalancer`) and for the **Create LB Inside** option, select `gitlab-vpc` from the dropdown menu.
- 1. In the **Listeners** section, set HTTP port 80, HTTPS port 443, and TCP port 22 for both load balancer and instance protocols and ports.
+ 1. In the **Listeners** section, set the following listeners:
+ - HTTP port 80 for both load balancer and instance protocol and ports
+ - TCP port 22 for both load balancer and instance protocols and ports
+ - HTTPS port 443 for load balancer protocol and ports, forwarding to HTTP port 80 on the instance (we will configure GitLab to listen on port 80 [later in the guide](#add-support-for-proxied-ssl))
1. In the **Select Subnets** section, select both public subnets from the list so that the load balancer can route traffic to both availability zones.
1. We'll add a security group for our load balancer to act as a firewall to control what traffic is allowed through. Click **Assign Security Groups** and select **Create a new security group**, give it a name
(we'll use `gitlab-loadbalancer-sec-group`) and description, and allow both HTTP and HTTPS traffic
@@ -244,8 +248,7 @@ On the EC2 dashboard, look for Load Balancer in the left navigation bar:
1. For **Ping Path**, enter `/users/sign_in`. (We use `/users/sign_in` as it's a public endpoint that does
not require authorization.)
1. Keep the default **Advanced Details** or adjust them according to your needs.
-1. Click **Add EC2 Instances** but, as we don't have any instances to add yet, come back
-to your load balancer after creating your GitLab instances and add them.
+1. Click **Add EC2 Instances** - don't add anything as we will create an Auto Scaling Group later to manage instances for us.
1. Click **Add Tags** and add any tags you need.
1. Click **Review and Create**, review all your settings, and click **Create** if you're happy.
@@ -302,7 +305,8 @@ We need a security group for our database that will allow inbound traffic from t
### Create the database
-DANGER: **Danger:** Avoid using burstable instances (t class instances) for the database as this could lead to performance issues due to CPU credits running out during sustained periods of high load.
+DANGER: **Danger:**
+Avoid using burstable instances (t class instances) for the database as this could lead to performance issues due to CPU credits running out during sustained periods of high load.
Now, it's time to create the database:
@@ -360,7 +364,7 @@ persistence and is used to store session data, temporary cache information, and
1. Navigate back to the ElastiCache dashboard.
1. Select **Redis** on the left menu and click **Create** to create a new
- Redis cluster. Do not enable **Cluster Mode** as it is [not supported](../../administration/high_availability/redis.md#provide-your-own-redis-instance-core-only). Even without cluster mode on, you still get the
+ Redis cluster. Do not enable **Cluster Mode** as it is [not supported](../../administration/redis/replication_and_failover_external.md#requirements). Even without cluster mode on, you still get the
chance to deploy Redis in multiple availability zones.
1. In the settings section:
1. Give the cluster a name (`gitlab-redis`) and a description.
@@ -384,7 +388,8 @@ persistence and is used to store session data, temporary cache information, and
Since our GitLab instances will be in private subnets, we need a way to connect to these instances via SSH to make configuration changes, perform upgrades, etc. One way of doing this is via a [bastion host](https://en.wikipedia.org/wiki/Bastion_host), sometimes also referred to as a jump box.
-TIP: **Tip:** If you do not want to maintain bastion hosts, you can set up [AWS Systems Manager Session Manager](https://docs.aws.amazon.com/systems-manager/latest/userguide/session-manager.html) for access to instances. This is beyond the scope of this document.
+TIP: **Tip:**
+If you do not want to maintain bastion hosts, you can set up [AWS Systems Manager Session Manager](https://docs.aws.amazon.com/systems-manager/latest/userguide/session-manager.html) for access to instances. This is beyond the scope of this document.
### Create Bastion Host A
@@ -540,7 +545,9 @@ gitlab=# \q
#### Set up Gitaly
-CAUTION: **Caution:** In this architecture, having a single Gitaly server creates a single point of failure. This limitation will be removed once [Gitaly Cluster](https://gitlab.com/groups/gitlab-org/-/epics/1489) is released.
+CAUTION: **Caution:**
+In this architecture, having a single Gitaly server creates a single point of failure. Use
+[Gitaly Cluster](../../administration/gitaly/praefect.md) to remove this limitation.
Gitaly is a service that provides high-level RPC access to Git repositories.
It should be enabled and configured on a separate EC2 instance in one of the
@@ -566,7 +573,8 @@ Let's create an EC2 instance where we'll install Gitaly:
1. Click **Review and launch** followed by **Launch** if you're happy with your settings.
1. Finally, acknowledge that you have access to the selected private key file or create a new one. Click **Launch Instances**.
-NOTE: **Optional:** Instead of storing configuration _and_ repository data on the root volume, you can also choose to add an additional EBS volume for repository storage. Follow the same guidance as above. See the [Amazon EBS pricing](https://aws.amazon.com/ebs/pricing/). We do not recommend using EFS as it may negatively impact GitLab’s performance. You can review the [relevant documentation](../../administration/high_availability/nfs.md#avoid-using-awss-elastic-file-system-efs) for more details.
+NOTE: **Note:**
+Instead of storing configuration _and_ repository data on the root volume, you can also choose to add an additional EBS volume for repository storage. Follow the same guidance as above. See the [Amazon EBS pricing](https://aws.amazon.com/ebs/pricing/). We do not recommend using EFS as it may negatively impact GitLab’s performance. You can review the [relevant documentation](../../administration/high_availability/nfs.md#avoid-using-awss-elastic-file-system-efs) for more details.
Now that we have our EC2 instance ready, follow the [documentation to install GitLab and set up Gitaly on its own server](../../administration/gitaly/index.md#run-gitaly-on-its-own-server). Perform the client setup steps from that document on the [GitLab instance we created](#install-gitlab) above.
@@ -638,7 +646,7 @@ That concludes the configuration changes for our GitLab instance. Next, we'll cr
On the EC2 dashboard:
-1. Select the `GitLab` instance we [created earlier](#install-gitLab).
+1. Select the `GitLab` instance we [created earlier](#install-gitlab).
1. Click on **Actions**, scroll down to **Image** and click **Create Image**.
1. Give your image a name and description (we'll use `GitLab-Source` for both).
1. Leave everything else as default and click **Create Image**
@@ -737,7 +745,7 @@ To back up GitLab:
sudo gitlab-backup create
```
-NOTE: **Note**
+NOTE: **Note:**
For GitLab 12.1 and earlier, use `gitlab-rake gitlab:backup:create`.
### Restoring GitLab from a backup
@@ -758,7 +766,7 @@ released, you can update your GitLab instance:
sudo gitlab-backup create
```
-NOTE: **Note**
+NOTE: **Note:**
For GitLab 12.1 and earlier, use `gitlab-rake gitlab:backup:create`.
1. Update the repositories and install GitLab:
@@ -794,14 +802,14 @@ to request additional material:
Activate all GitLab Enterprise Edition functionality with a license.
- [Pricing](https://about.gitlab.com/pricing/): Pricing for the different tiers.
-<!-- ## Troubleshooting
+## Troubleshooting
+
+### Instances are failing health checks
+
+If your instances are failing the load balancer's health checks, verify that they are returning a status `200` from the health check endpoint we configured earlier. Any other status, including redirects (e.g. status `302`) will cause the health check to fail.
+
+You may have to set a password on the `root` user to prevent automatic redirects on the sign-in endpoint before health checks will pass.
-Include any troubleshooting steps that you can foresee. If you know beforehand what issues
-one might have when setting this up, or when something is changed, or on upgrading, it's
-important to describe those, too. Think of things that may go wrong and include them here.
-This is important to minimize requests for support and to avoid doc comments with
-questions that you know someone might ask.
+### "The change you requested was rejected (422)"
-Each scenario can be a third-level heading, e.g. `### Getting error message X`.
-If you have none to add when creating a doc, leave this section in place
-but commented out to help encourage others to add to it in the future. -->
+If you see this page when trying to set a password via the web interface, make sure `external_url` in `gitlab.rb` matches the domain you are making a request from, and run `sudo gitlab-ctl reconfigure` after making any changes to it.
diff --git a/doc/install/installation.md b/doc/install/installation.md
index 0997062006d..8b285e0c9f1 100644
--- a/doc/install/installation.md
+++ b/doc/install/installation.md
@@ -310,8 +310,7 @@ sudo adduser --disabled-login --gecos 'GitLab' git
## 6. Database
NOTE: **Note:**
-Starting from GitLab 12.1, only PostgreSQL is supported. Because we need to make
-use of extensions and concurrent index removal, you need at least PostgreSQL 9.2.
+Starting from GitLab 12.1, only PostgreSQL is supported. Since GitLab 13.0, we require PostgreSQL 11+.
1. Install the database packages:
@@ -426,11 +425,20 @@ cd /home/git
### Clone the Source
+Clone Community Edition:
+
```shell
# Clone GitLab repository
sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-foss.git -b X-Y-stable gitlab
```
+Clone Enterprise Edition:
+
+```shell
+# Clone GitLab repository
+sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-ee.git -b X-Y-stable gitlab
+```
+
Make sure to replace `X-Y-stable` with the stable branch that matches the
version you want to install. For example, if you want to install 11.8 you would
use the branch name `11-8-stable`.
@@ -601,7 +609,7 @@ You can specify a different Git repository by providing it as an extra parameter
sudo -u git -H bundle exec rake "gitlab:workhorse:install[/home/git/gitlab-workhorse,https://example.com/gitlab-workhorse.git]" RAILS_ENV=production
```
-### Install GitLab-Elasticsearch-indexer
+### Install GitLab-Elasticsearch-indexer on Enterprise Edition
GitLab-Elasticsearch-Indexer uses [GNU Make](https://www.gnu.org/software/make/). The
following command-line will install GitLab-Elasticsearch-Indexer in `/home/git/gitlab-elasticsearch-indexer`
@@ -620,6 +628,9 @@ sudo -u git -H bundle exec rake "gitlab:indexer:install[/home/git/gitlab-elastic
The source code will first be fetched to the path specified by the first parameter. Then a binary will be built under its `bin` directory.
You will then need to update `gitlab.yml`'s `production -> elasticsearch -> indexer_path` setting to point to that binary.
+NOTE: **Note:**
+Elasticsearch is a feature of GitLab Enterprise Edition and isn't included in GitLab Community Edition.
+
### Install GitLab Pages
GitLab Pages uses [GNU Make](https://www.gnu.org/software/make/). This step is optional and only needed if you wish to host static sites from within GitLab. The following commands will install GitLab Pages in `/home/git/gitlab-pages`. For additional setup steps, consult the [administration guide](https://gitlab.com/gitlab-org/gitlab/blob/master/doc/administration/pages/source.md) for your version of GitLab as the GitLab Pages daemon can be run several different ways.
diff --git a/doc/install/openshift_and_gitlab/index.md b/doc/install/openshift_and_gitlab/index.md
index 7ae23d6831e..519a0a5b7aa 100644
--- a/doc/install/openshift_and_gitlab/index.md
+++ b/doc/install/openshift_and_gitlab/index.md
@@ -25,7 +25,8 @@ For a video demonstration on installing GitLab on OpenShift, check the article [
## Prerequisites
-CAUTION: **Caution:** This information is no longer up to date, as the current versions
+CAUTION: **Caution:**
+This information is no longer up to date, as the current versions
have changed and products have been renamed.
OpenShift 3 is not yet deployed on RedHat's offered [Online platform](https://www.openshift.com/),
diff --git a/doc/install/requirements.md b/doc/install/requirements.md
index 0673f3e7ea3..5bc587627f5 100644
--- a/doc/install/requirements.md
+++ b/doc/install/requirements.md
@@ -54,8 +54,10 @@ The minimum required Go version is 1.13.
### Git versions
-GitLab 11.11 and higher only supports Git 2.24.x and newer, and
-[dropped support for older versions](https://gitlab.com/gitlab-org/gitlab-foss/-/issues/54255).
+From GitLab 13.1:
+
+- Git 2.25.x and later is required.
+- Git 2.27.x and later [is recommended](https://gitlab.com/gitlab-org/gitaly/-/issues/2829).
### Node.js versions
@@ -88,7 +90,8 @@ Apart from a local hard drive you can also mount a volume that supports the netw
If you have enough RAM and a recent CPU the speed of GitLab is mainly limited by hard drive seek times. Having a fast drive (7200 RPM and up) or a solid state drive (SSD) will improve the responsiveness of GitLab.
-NOTE: **Note:** Since file system performance may affect GitLab's overall performance, [we don't recommend using AWS EFS for storage](../administration/high_availability/nfs.md#avoid-using-awss-elastic-file-system-efs).
+NOTE: **Note:**
+Since file system performance may affect GitLab's overall performance, [we don't recommend using AWS EFS for storage](../administration/high_availability/nfs.md#avoid-using-awss-elastic-file-system-efs).
### CPU
@@ -143,7 +146,8 @@ GitLab database. This extension [can be enabled](https://www.postgresql.org/docs
On some systems you may need to install an additional package (for example,
`postgresql-contrib`) for this extension to become available.
-NOTE: **Note:** Support for [PostgreSQL 9.6 and 10 has been removed in GitLab 13.0](https://about.gitlab.com/releases/2020/05/22/gitlab-13-0-released/#postgresql-11-is-now-the-minimum-required-version-to-install-gitlab) so that GitLab can benefit from PostgreSQL 11 improvements, such as partitioning. For the schedule of transitioning to PostgreSQL 12, see [the related epic](https://gitlab.com/groups/gitlab-org/-/epics/2184).
+NOTE: **Note:**
+Support for [PostgreSQL 9.6 and 10 has been removed in GitLab 13.0](https://about.gitlab.com/releases/2020/05/22/gitlab-13-0-released/#postgresql-11-is-now-the-minimum-required-version-to-install-gitlab) so that GitLab can benefit from PostgreSQL 11 improvements, such as partitioning. For the schedule of transitioning to PostgreSQL 12, see [the related epic](https://gitlab.com/groups/gitlab-org/-/epics/2184).
#### Additional requirements for GitLab Geo
@@ -258,7 +262,8 @@ For reference, GitLab.com's [auto-scaling shared runner](../user/gitlab_com/inde
## Supported web browsers
-CAUTION: **Caution:** With GitLab 13.0 (May 2020) we have removed official support for Internet Explorer 11.
+CAUTION: **Caution:**
+With GitLab 13.0 (May 2020) we have removed official support for Internet Explorer 11.
With the release of GitLab 13.4 (September 2020) we will remove all code that supports Internet Explorer 11.
You can provide feedback [on this issue](https://gitlab.com/gitlab-org/gitlab/-/issues/197987) or via your usual support channels.
@@ -275,7 +280,8 @@ For the listed web browsers, GitLab supports:
- The current and previous major versions of browsers except Internet Explorer.
- The current minor version of a supported major version.
-NOTE: **Note:** We don't support running GitLab with JavaScript disabled in the browser and have no plans of supporting that
+NOTE: **Note:**
+We don't support running GitLab with JavaScript disabled in the browser and have no plans of supporting that
in the future because we have features such as Issue Boards which require JavaScript extensively.
<!-- ## Troubleshooting
diff --git a/doc/integration/README.md b/doc/integration/README.md
index f06cb5e4bb6..fdaff74ca58 100644
--- a/doc/integration/README.md
+++ b/doc/integration/README.md
@@ -56,6 +56,8 @@ GitLab can be integrated with the following enhancements:
- Configure [PlantUML](../administration/integration/plantuml.md) to use diagrams in AsciiDoc documents.
- Attach merge requests to [Trello](trello_power_up.md) cards.
- Enable integrated code intelligence powered by [Sourcegraph](sourcegraph.md).
+- Add [Elasticsearch](elasticsearch.md) for [Advanced Global Search](../user/search/advanced_global_search.md),
+ [Advanced System Search](../user/search/advanced_search_syntax.md), and faster searching.
## Integrations
diff --git a/doc/integration/elasticsearch.md b/doc/integration/elasticsearch.md
index 07ef5dbb99d..8ca45547f11 100644
--- a/doc/integration/elasticsearch.md
+++ b/doc/integration/elasticsearch.md
@@ -125,10 +125,7 @@ for each Elasticsearch node, per the [official guidelines](https://www.elastic.c
Keep in mind, this is the **minimum requirements** as per Elasticsearch. For
production instances, they recommend considerably more resources.
-Storage requirements also vary based on the installation side, but as a rule of
-thumb, you should allocate the total size of your production database, **plus**
-two-thirds of the total size of your Git repositories. Efforts to reduce this
-total are being tracked in [epic &153](https://gitlab.com/groups/gitlab-org/-/epics/153).
+The necessary storage space largely depends on the size of the repositories you want to store in GitLab but as a rule of thumb you should have at least 50% of free space as all your repositories combined take up.
## Enabling Elasticsearch
@@ -153,8 +150,8 @@ The following Elasticsearch settings are available:
| `AWS Access Key` | The AWS access key. |
| `AWS Secret Access Key` | The AWS secret access key. |
| `Maximum field length` | See [the explanation in instance limits.](../administration/instance_limits.md#maximum-field-length). |
-| `Maximum bulk request size (MiB)` | The Maximum Bulk Request size is used by GitLab's Golang-based indexer processes and indicates how much data it ought to collect (and store in memory) in a given indexing process before submitting the payload to Elasticsearch’s Bulk API. This setting works in tandem with the Bulk request concurrency setting (see below) and needs to accomodate the resource constraints of both the Elasticsearch host(s) and the host(s) running GitLab's Golang-based indexer either from the `gitlab-rake` command or the Sidekiq tasks. |
-| `Bulk request concurrency` | The Bulk request concurrency indicates how many of GitLab's Golang-based indexer processes (or threads) can run in parallel to collect data to subsequently submit to Elasticsearch’s Bulk API. This increases indexing performance, but fills the Elasticsearch bulk requests queue faster. This setting works in tandem with the Maximum bulk request size setting (see above) and needs to accomodate the resource constraints of both the Elasticsearch host(s) and the host(s) running GitLab's Golang-based indexer either from the `gitlab-rake` command or the Sidekiq tasks. |
+| `Maximum bulk request size (MiB)` | The Maximum Bulk Request size is used by GitLab's Golang-based indexer processes and indicates how much data it ought to collect (and store in memory) in a given indexing process before submitting the payload to Elasticsearch’s Bulk API. This setting should be used with the Bulk request concurrency setting (see below) and needs to accommodate the resource constraints of both the Elasticsearch host(s) and the host(s) running GitLab's Golang-based indexer either from the `gitlab-rake` command or the Sidekiq tasks. |
+| `Bulk request concurrency` | The Bulk request concurrency indicates how many of GitLab's Golang-based indexer processes (or threads) can run in parallel to collect data to subsequently submit to Elasticsearch’s Bulk API. This increases indexing performance, but fills the Elasticsearch bulk requests queue faster. This setting should be used together with the Maximum bulk request size setting (see above) and needs to accommodate the resource constraints of both the Elasticsearch host(s) and the host(s) running GitLab's Golang-based indexer either from the `gitlab-rake` command or the Sidekiq tasks. |
### Limiting namespaces and projects
@@ -170,10 +167,10 @@ You can filter the selection dropdown by writing part of the namespace or projec
![limit namespace filter](img/limit_namespace_filter.png)
-NOTE: **Note**:
+NOTE: **Note:**
If no namespaces or projects are selected, no Elasticsearch indexing will take place.
-CAUTION: **Warning**:
+CAUTION: **Warning:**
If you have already indexed your instance, you will have to regenerate the index in order to delete all existing data
for filtering to work correctly. To do this run the Rake tasks `gitlab:elastic:recreate_index` and
`gitlab:elastic:clear_index_status`. Afterwards, removing a namespace or a project from the list will delete the data
@@ -187,7 +184,7 @@ To disable the Elasticsearch integration:
1. Expand the **Elasticsearch** section and uncheck **Elasticsearch indexing**
and **Search with Elasticsearch enabled**.
1. Click **Save changes** for the changes to take effect.
-1. (Optional) Delete the existing index by running one of these commands:
+1. (Optional) Delete the existing index:
```shell
# Omnibus installations
@@ -209,7 +206,7 @@ To backfill existing data, you can use one of the methods below to index it in b
To index via the Admin Area:
1. [Configure your Elasticsearch host and port](#enabling-elasticsearch).
-1. Create empty indexes using one of the following commands:
+1. Create empty indexes:
```shell
# Omnibus installations
@@ -222,7 +219,7 @@ To index via the Admin Area:
1. [Enable **Elasticsearch indexing**](#enabling-elasticsearch).
1. Click **Index all projects** in **Admin Area > Settings > Integrations > Elasticsearch**.
1. Click **Check progress** in the confirmation message to see the status of the background jobs.
-1. Personal snippets need to be indexed manually by running one of these commands:
+1. Personal snippets need to be indexed manually:
```shell
# Omnibus installations
@@ -240,13 +237,13 @@ Indexing can be performed using Rake tasks.
#### Indexing small instances
-CAUTION: **Warning**:
+CAUTION: **Warning:**
This will delete your existing indexes.
If the database size is less than 500 MiB, and the size of all hosted repos is less than 5 GiB:
-1. [Enable **Elasticsearch indexing** and configure your host and port](#enabling-elasticsearch).
-1. Index your data using one of the following commands:
+1. [Configure your Elasticsearch host and port](#enabling-elasticsearch).
+1. Index your data:
```shell
# Omnibus installations
@@ -260,13 +257,13 @@ If the database size is less than 500 MiB, and the size of all hosted repos is l
#### Indexing large instances
-CAUTION: **Warning**:
+CAUTION: **Warning:**
Performing asynchronous indexing will generate a lot of Sidekiq jobs.
Make sure to prepare for this task by having a [Scalable and Highly Available Setup](README.md)
or creating [extra Sidekiq processes](../administration/operations/extra_sidekiq_processes.md)
1. [Configure your Elasticsearch host and port](#enabling-elasticsearch).
-1. Create empty indexes using one of the following commands:
+1. Create empty indexes:
```shell
# Omnibus installations
@@ -276,6 +273,16 @@ or creating [extra Sidekiq processes](../administration/operations/extra_sidekiq
bundle exec rake gitlab:elastic:create_empty_index RAILS_ENV=production
```
+1. If this is a re-index of your GitLab instance, clear the index status:
+
+ ```shell
+ # Omnibus installations
+ sudo gitlab-rake gitlab:elastic:clear_index_status
+
+ # Installations from source
+ bundle exec rake gitlab:elastic:clear_index_status RAILS_ENV=production
+ ```
+
1. [Enable **Elasticsearch indexing**](#enabling-elasticsearch).
1. Indexing large Git repositories can take a while. To speed up the process, you
can temporarily disable auto-refreshing and replicating. In our experience, you can expect a 20%
@@ -350,8 +357,7 @@ or creating [extra Sidekiq processes](../administration/operations/extra_sidekiq
indexer to "forget" all progress, so it will retry the indexing process from the
start.
-1. Personal snippets are not associated with a project and need to be indexed separately
- by running one of these commands:
+1. Personal snippets are not associated with a project and need to be indexed separately:
```shell
# Omnibus installations
@@ -415,7 +421,7 @@ The following are some available Rake tasks:
| Task | Description |
|:--------------------------------------------------------------------------------------------------------------------------------------------------------|:------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
-| [`sudo gitlab-rake gitlab:elastic:index`](https://gitlab.com/gitlab-org/gitlab/blob/master/ee/lib/tasks/gitlab/elastic.rake) | Wrapper task for `gitlab:elastic:create_empty_index`, `gitlab:elastic:clear_index_status`, `gitlab:elastic:index_projects`, and `gitlab:elastic:index_snippets`. |
+| [`sudo gitlab-rake gitlab:elastic:index`](https://gitlab.com/gitlab-org/gitlab/blob/master/ee/lib/tasks/gitlab/elastic.rake) | Enables Elasticsearch Indexing and run `gitlab:elastic:create_empty_index`, `gitlab:elastic:clear_index_status`, `gitlab:elastic:index_projects`, and `gitlab:elastic:index_snippets`. |
| [`sudo gitlab-rake gitlab:elastic:index_projects`](https://gitlab.com/gitlab-org/gitlab/blob/master/ee/lib/tasks/gitlab/elastic.rake) | Iterates over all projects and queues Sidekiq jobs to index them in the background. |
| [`sudo gitlab-rake gitlab:elastic:index_projects_status`](https://gitlab.com/gitlab-org/gitlab/blob/master/ee/lib/tasks/gitlab/elastic.rake) | Determines the overall status of the indexing. It is done by counting the total number of indexed projects, dividing by a count of the total number of projects, then multiplying by 100. |
| [`sudo gitlab-rake gitlab:elastic:clear_index_status`](https://gitlab.com/gitlab-org/gitlab/blob/master/ee/lib/tasks/gitlab/elastic.rake) | Deletes all instances of IndexStatus for all projects. |
@@ -424,6 +430,7 @@ The following are some available Rake tasks:
| [`sudo gitlab-rake gitlab:elastic:recreate_index[<TARGET_NAME>]`](https://gitlab.com/gitlab-org/gitlab/blob/master/ee/lib/tasks/gitlab/elastic.rake) | Wrapper task for `gitlab:elastic:delete_index[<TARGET_NAME>]` and `gitlab:elastic:create_empty_index[<TARGET_NAME>]`. |
| [`sudo gitlab-rake gitlab:elastic:index_snippets`](https://gitlab.com/gitlab-org/gitlab/blob/master/ee/lib/tasks/gitlab/elastic.rake) | Performs an Elasticsearch import that indexes the snippets data. |
| [`sudo gitlab-rake gitlab:elastic:projects_not_indexed`](https://gitlab.com/gitlab-org/gitlab/blob/master/ee/lib/tasks/gitlab/elastic.rake) | Displays which projects are not indexed. |
+| [`sudo gitlab-rake gitlab:elastic:reindex_cluster`](https://gitlab.com/gitlab-org/gitlab/blob/master/ee/lib/tasks/gitlab/elastic.rake) | Schedules a zero-downtime cluster reindexing task. This feature should be used with an index that was created after GitLab 13.0. |
NOTE: **Note:**
The `TARGET_NAME` parameter is optional and will use the default index/alias name from the current `RAILS_ENV` if not set.
@@ -466,6 +473,25 @@ When performing a search, the GitLab index will use the following scopes:
## Tuning
+### Guidance on choosing optimal cluster configuration
+
+For basic guidance on choosing a cluster configuration you may refer to [Elastic Cloud Calculator](https://cloud.elastic.co/pricing). You can find more information below.
+
+- Generally, you will want to use at least a 2-node cluster configuration with one replica, which will allow you to have resilience. If your storage usage is growing quickly, you may want to plan horizontal scaling (adding more nodes) beforehand.
+- It's not recommended to use HDD storage with the search cluster, because it will take a hit on performance. It's better to use SSD storage (NVMe or SATA SSD drives for example).
+- You can use the [GitLab Performance Tool](https://gitlab.com/gitlab-org/quality/performance) to benchmark search performance with different search cluster sizes and configurations.
+- `Heap size` should be set to no more than 50% of your physical RAM. Additionally, it shouldn't be set to more than the threshold for zero-based compressed oops. The exact threshold varies, but 26 GB is safe on most systems, but can also be as large as 30 GB on some systems. See [Setting the heap size](https://www.elastic.co/guide/en/elasticsearch/reference/current/heap-size.html#heap-size) for more details.
+- Number of CPUs (CPU cores) per node usually corresponds to the `Number of Elasticsearch shards` setting described below.
+- A good guideline is to ensure you keep the number of shards per node below 20 per GB heap it has configured. A node with a 30GB heap should therefore have a maximum of 600 shards, but the further below this limit you can keep it the better. This will generally help the cluster stay in good health.
+- Small shards result in small segments, which increases overhead. Aim to keep the average shard size between at least a few GB and a few tens of GB. Another consideration is the number of documents, you should aim for this simple formula for the number of shards: `number of expected documents / 5M +1`.
+- `refresh_interval` is a per index setting. You may want to adjust that from default `1s` to a bigger value if you don't need data in realtime. This will change how soon you will see fresh results. If that's important for you, you should leave it as close as possible to the default value.
+- You might want to raise [`indices.memory.index_buffer_size`](https://www.elastic.co/guide/en/elasticsearch/reference/current/indexing-buffer.html) to 30% or 40% if you have a lot of heavy indexing operations.
+
+### Elasticsearch integration settings guidance
+
+- The `Number of Elasticsearch shards` setting usually corresponds with the number of CPUs available in your cluster. For example, if you have a 3-node cluster with 4 cores each, this means you will benefit from having at least 3*4=12 shards in the cluster. Please note, it's only possible to change the shards number by using [Split index API](https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-split-index.html) or by reindexing to a different index with a changed number of shards.
+- The `Number of Elasticsearch replicas` setting should most of the time be equal to `1` (each shard will have 1 replica). Using `0` is not recommended, because losing one node will corrupt the index.
+
### Deleted documents
Whenever a change or deletion is made to an indexed GitLab object (a merge request description is changed, a file is deleted from the master branch in a repository, a project is deleted, etc), a document in the index is deleted. However, since these are "soft" deletes, the overall number of "deleted documents", and therefore wasted space, increases. Elasticsearch does intelligent merging of segments in order to remove these deleted documents. However, depending on the amount and type of activity in your GitLab installation, it's possible to see as much as 50% wasted space in the index.
@@ -516,7 +542,7 @@ Here are some common pitfalls and how to overcome them:
If you see `"Kaminari::PaginatableArray"` you are using Elasticsearch.
- NOTE: **Note**:
+ NOTE: **Note:**
The above instructions are used to verify that GitLab is using Elasticsearch only when indexing all namespaces. This is not to be used for scenarios that only index a [subset of namespaces](#limiting-namespaces-and-projects).
- **I updated GitLab and now I can't find anything**
@@ -539,7 +565,7 @@ Here are some common pitfalls and how to overcome them:
pp s.search_objects.to_a
```
- NOTE: **Note**:
+ NOTE: **Note:**
The above instructions are not to be used for scenarios that only index a [subset of namespaces](#limiting-namespaces-and-projects).
See [Elasticsearch Index Scopes](#elasticsearch-index-scopes) for more information on searching for specific types of data.
@@ -556,12 +582,6 @@ Here are some common pitfalls and how to overcome them:
You can run `sudo gitlab-rake gitlab:elastic:projects_not_indexed` to display projects that aren't indexed.
-- **No new data is added to the Elasticsearch index when I push code**
-
- When performing the initial indexing of blobs, we lock all projects until the project finishes indexing. It could
- happen that an error during the process causes one or multiple projects to remain locked. In order to unlock them,
- run the `gitlab:elastic:clear_locked_projects` Rake task.
-
- **"Can't specify parent if no parent field has been configured"**
If you enabled Elasticsearch before GitLab 8.12 and have not rebuilt indexes you will get
@@ -609,7 +629,8 @@ Here are some common pitfalls and how to overcome them:
**For a single node Elasticsearch cluster the functional cluster health status will be yellow** (will never be green) because the primary shard is allocated but replicas can not be as there is no other node to which Elasticsearch can assign a replica. This also applies if you are using the
[Amazon Elasticsearch](https://docs.aws.amazon.com/elasticsearch-service/latest/developerguide/aes-handling-errors.html#aes-handling-errors-yellow-cluster-status) service.
- CAUTION: **Warning**: Setting the number of replicas to `0` is not something that we recommend (this is not allowed in the GitLab Elasticsearch Integration menu). If you are planning to add more Elasticsearch nodes (for a total of more than 1 Elasticsearch) the number of replicas will need to be set to an integer value larger than `0`. Failure to do so will result in lack of redundancy (losing one node will corrupt the index).
+ CAUTION: **Warning:**
+ Setting the number of replicas to `0` is not something that we recommend (this is not allowed in the GitLab Elasticsearch Integration menu). If you are planning to add more Elasticsearch nodes (for a total of more than 1 Elasticsearch) the number of replicas will need to be set to an integer value larger than `0`. Failure to do so will result in lack of redundancy (losing one node will corrupt the index).
If you have a **hard requirement to have a green status for your single node Elasticsearch cluster**, please make sure you understand the risks outlined in the previous paragraph and then simply run the following query to set the number of replicas to `0`(the cluster will no longer try to create any shard replicas):
diff --git a/doc/integration/github.md b/doc/integration/github.md
index 68971c510d5..ccce4672cbf 100644
--- a/doc/integration/github.md
+++ b/doc/integration/github.md
@@ -12,7 +12,7 @@ When you create an OAuth 2 app in GitHub, you'll need the following information:
- The authorization callback URL; in this case, `https://gitlab.example.com/users/auth`. Include the port number if your GitLab instance uses a non-default port.
NOTE: **Note:**
-To prevent an [OAuth2 covert redirect](http://tetraph.com/covert_redirect/) vulnerability, append `/users/auth` to the end of the GitHub authorization callback URL.
+To prevent an [OAuth2 covert redirect](https://oauth.net/advisories/2014-1-covert-redirect/) vulnerability, append `/users/auth` to the end of the GitHub authorization callback URL.
See [Initial OmniAuth Configuration](omniauth.md#initial-omniauth-configuration) for initial settings.
diff --git a/doc/integration/jira_development_panel.md b/doc/integration/jira_development_panel.md
index f032345b9e0..85404ff3164 100644
--- a/doc/integration/jira_development_panel.md
+++ b/doc/integration/jira_development_panel.md
@@ -16,7 +16,7 @@ A top-level GitLab group is one that does not have any parent group itself. All
as well as projects of the top-level group's subgroups nesting down, are connected. Alternatively, you can specify
a GitLab personal namespace in the Jira configuration, which will then connect the projects in that personal namespace to Jira.
-NOTE: **Note**:
+NOTE: **Note:**
Note this is different from the [existing Jira](../user/project/integrations/jira.md) project integration, where the mapping
is one GitLab project to the entire Jira instance.
@@ -55,7 +55,7 @@ There are no special requirements if you are using GitLab.com.
replacing `<your-gitlab-instance-domain>` appropriately. So for example, if you are using GitLab.com,
this would be `https://gitlab.com/login/oauth/callback`.
- NOTE: **Note**:
+ NOTE: **Note:**
If using a GitLab version earlier than 11.3, the `Redirect URI` must be
`https://<your-gitlab-instance-domain>/-/jira/login/oauth/callback`. If you want Jira
to have access to all projects, GitLab recommends an administrator creates the
@@ -90,7 +90,7 @@ There are no special requirements if you are using GitLab.com.
replacing `<your-gitlab-instance-domain>` appropriately. So for example, if you are using GitLab.com,
this would be `https://gitlab.com/`.
- NOTE: **Note**:
+ NOTE: **Note:**
If using a GitLab version earlier than 11.3 the `Host URL` value should be `https://<your-gitlab-instance-domain>/-/jira`
For the `Client ID` field, use the `Application ID` value from the previous section.
diff --git a/doc/integration/openid_connect_provider.md b/doc/integration/openid_connect_provider.md
index 3da17347e91..b66262772da 100644
--- a/doc/integration/openid_connect_provider.md
+++ b/doc/integration/openid_connect_provider.md
@@ -37,11 +37,11 @@ Currently the following user information is shared with clients:
| `auth_time` | `integer` | The timestamp for the user's last authentication
| `name` | `string` | The user's full name
| `nickname` | `string` | The user's GitLab username
-| `email` | `string` | The user's public email address
-| `email_verified` | `boolean` | Whether the user's public email address was verified
+| `email` | `string` | The user's email address<br>This is the user's *primary* email address if the application has access to the `email` claim and the user's *public* email address otherwise
+| `email_verified` | `boolean` | Whether the user's email address was verified
| `website` | `string` | URL for the user's website
| `profile` | `string` | URL for the user's GitLab profile
| `picture` | `string` | URL for the user's GitLab avatar
| `groups` | `array` | Names of the groups the user is a member of
-Only the `sub` and `sub_legacy` claims are included in the ID token, all other claims are available from the `/oauth/userinfo` endpoint used by OIDC clients.
+The claims `sub`, `sub_legacy`, `email` and `email_verified` are included in the ID token, all other claims are available from the `/oauth/userinfo` endpoint used by OIDC clients.
diff --git a/doc/integration/recaptcha.md b/doc/integration/recaptcha.md
index bf2e6568ea6..48c83f1393b 100644
--- a/doc/integration/recaptcha.md
+++ b/doc/integration/recaptcha.md
@@ -15,6 +15,12 @@ To use reCAPTCHA, first you must create a site and private key.
1. Fill all reCAPTCHA fields with keys from previous steps.
1. Check the `Enable reCAPTCHA` checkbox.
1. Save the configuration.
+1. Change the first line of the `#execute` method in `app/services/spam/spam_verdict_service.rb`
+ to `return CONDITONAL_ALLOW` so that the spam check short-circuits and triggers the response to
+ return `recaptcha_html`.
+
+NOTE: **Note:**
+Make sure you are viewing an issuable in a project that is public, and if you're working with an issue, the issue is public.
## Enabling reCAPTCHA for user logins via passwords
diff --git a/doc/integration/saml.md b/doc/integration/saml.md
index 97384d8f6cf..9dce7269c86 100644
--- a/doc/integration/saml.md
+++ b/doc/integration/saml.md
@@ -1,11 +1,29 @@
+---
+type: reference
+stage: Manage
+group: Access
+info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers
+---
+
# SAML OmniAuth Provider **(CORE ONLY)**
-Note that:
+This page describes instance-wide SAML for self-managed GitLab instances. For SAML on GitLab.com, see [SAML SSO for GitLab.com groups](../user/group/saml_sso/index.md).
+
+You should also reference the [OmniAuth documentation](omniauth.md) for general settings that apply to all OmniAuth providers.
-- SAML OmniAuth Provider is for SAML on self-managed GitLab instances. For SAML on
- GitLab.com, see [SAML SSO for GitLab.com Groups](../user/group/saml_sso/index.md).
-- Starting from GitLab 11.4, OmniAuth is enabled by default. If you're using an
- earlier version, you'll need to explicitly enable it.
+## Common SAML Terms
+
+| Term | Description |
+|------|-------------|
+| Identity Provider (IdP) | The service which manages your user identities such as ADFS, Okta, Onelogin, or Ping Identity. |
+| Service Provider (SP) | SAML considers GitLab to be a service provider. |
+| Assertion | A piece of information about a user's identity, such as their name or role. Also know as claims or attributes. |
+| SSO | Single Sign-On. |
+| Assertion consumer service URL | The callback on GitLab where users will be redirected after successfully authenticating with the identity provider. |
+| Issuer | How GitLab identifies itself to the identity provider. Also known as a "Relying party trust identifier". |
+| Certificate fingerprint | Used to confirm that communications over SAML are secure by checking that the server is signing communications with the correct certificate. Also known as a certificate thumbprint. |
+
+## General Setup
GitLab can be configured to act as a SAML 2.0 Service Provider (SP). This allows
GitLab to consume assertions from a SAML 2.0 Identity Provider (IdP) such as
@@ -143,17 +161,10 @@ On the sign in page there should now be a SAML button below the regular sign in
Click the icon to begin the authentication process. If everything goes well the user
will be returned to GitLab and will be signed in.
-## Marking Users as External based on SAML Groups
-
->**Note:**
-This setting is only available on GitLab 8.7 and above.
+## SAML Groups
-SAML login includes support for automatically identifying whether a user should
-be considered an [external](../user/permissions.md) user based on the user's group
-membership in the SAML identity provider. This feature **does not** allow you to
-automatically add users to GitLab [Groups](../user/group/index.md), it simply
-allows you to mark users as External if they are members of certain groups in the
-Identity Provider.
+You can require users to be members of a certain group, or assign users `external`, `admin` or `auditor` roles based on group membership. This feature **does not** allow you to
+automatically add users to GitLab [Groups](../user/group/index.md).
### Requirements
@@ -164,74 +175,73 @@ with the regular SAML response. Here is an example:
```xml
<saml:AttributeStatement>
<saml:Attribute Name="Groups">
- <saml:AttributeValue xsi:type="xs:string">SecurityGroup</saml:AttributeValue>
<saml:AttributeValue xsi:type="xs:string">Developers</saml:AttributeValue>
- <saml:AttributeValue xsi:type="xs:string">Designers</saml:AttributeValue>
+ <saml:AttributeValue xsi:type="xs:string">Freelancers</saml:AttributeValue>
+ <saml:AttributeValue xsi:type="xs:string">Admins</saml:AttributeValue>
+ <saml:AttributeValue xsi:type="xs:string">Auditors</saml:AttributeValue>
</saml:Attribute>
</saml:AttributeStatement>
```
The name of the attribute can be anything you like, but it must contain the groups
to which a user belongs. In order to tell GitLab where to find these groups, you need
-to add a `groups_attribute:` element to your SAML settings. You will also need to
-tell GitLab which groups are external via the `external_groups:` element:
+to add a `groups_attribute:` element to your SAML settings.
+
+### Required groups **(STARTER ONLY)**
+
+Your IdP passes Group Information to the SP (GitLab) in the SAML Response. You need to configure GitLab to identify:
+
+- Where to look for the groups in the SAML response via the `groups_attribute` setting
+- Which group membership is requisite to sign in via the `required_groups` setting
+
+When `required_groups` is not set or it is empty, anyone with proper authentication
+will be able to use the service.
+
+Example:
```yaml
{ name: 'saml',
label: 'Our SAML Provider',
groups_attribute: 'Groups',
- external_groups: ['Freelancers', 'Interns'],
+ required_groups: ['Developers', 'Freelancers', 'Admins', 'Auditors'],
args: {
assertion_consumer_service_url: 'https://gitlab.example.com/users/auth/saml/callback',
idp_cert_fingerprint: '43:51:43:a1:b5:fc:8b:b7:0a:3a:a9:b1:0f:66:73:a8',
idp_sso_target_url: 'https://login.example.com/idp',
issuer: 'https://gitlab.example.com',
- name_identifier_format: 'urn:oasis:names:tc:SAML:2.0:nameid-format:persistent'
+ name_identifier_format: 'urn:oasis:names:tc:SAML:2.0:nameid-format:transient'
} }
```
-## Required groups **(STARTER ONLY)**
-
->**Note:**
-This setting is only available on GitLab 10.2 EE and above.
-
-This setting works like `External Groups` setting. Just like there, your IdP has to
-pass Group Information to GitLab, you have to tell GitLab where to look or the
-groups SAML response, and which group membership should be requisite for logging in.
-When `required_groups` is not set or it is empty, anyone with proper authentication
-will be able to use the service.
+### External Groups **(STARTER ONLY)**
-Example:
+SAML login supports automatic identification on whether a user should be considered an [external](../user/permissions.md) user. This is based on the user's group membership in the SAML identity provider.
```yaml
{ name: 'saml',
label: 'Our SAML Provider',
groups_attribute: 'Groups',
- required_groups: ['Developers', 'Managers', 'Admins'],
+ external_groups: ['Freelancers'],
args: {
assertion_consumer_service_url: 'https://gitlab.example.com/users/auth/saml/callback',
idp_cert_fingerprint: '43:51:43:a1:b5:fc:8b:b7:0a:3a:a9:b1:0f:66:73:a8',
idp_sso_target_url: 'https://login.example.com/idp',
issuer: 'https://gitlab.example.com',
- name_identifier_format: 'urn:oasis:names:tc:SAML:2.0:nameid-format:transient'
+ name_identifier_format: 'urn:oasis:names:tc:SAML:2.0:nameid-format:persistent'
} }
```
-## Admin Groups **(STARTER ONLY)**
-
->**Note:**
-This setting is only available on GitLab 8.8 EE and above.
+### Admin Groups **(STARTER ONLY)**
-This setting works very similarly to the `External Groups` setting. The requirements
-are the same, your IdP needs to pass Group information to GitLab, you need to tell
-GitLab where to look for the groups in the SAML response, and which group should be
-considered `admin groups`.
+The requirements are the same as the previous settings, your IdP needs to pass Group information to GitLab, you need to tell
+GitLab where to look for the groups in the SAML response, and which group(s) should be
+considered admin users.
```yaml
{ name: 'saml',
label: 'Our SAML Provider',
groups_attribute: 'Groups',
- admin_groups: ['Managers', 'Admins'],
+ admin_groups: ['Admins'],
args: {
assertion_consumer_service_url: 'https://gitlab.example.com/users/auth/saml/callback',
idp_cert_fingerprint: '43:51:43:a1:b5:fc:8b:b7:0a:3a:a9:b1:0f:66:73:a8',
@@ -241,18 +251,20 @@ considered `admin groups`.
} }
```
-## Auditor Groups **(STARTER ONLY)**
+### Auditor Groups **(STARTER ONLY)**
>**Note:**
This setting is only available on GitLab 11.4 EE and above.
-This setting also follows the requirements documented for the `External Groups` setting. GitLab uses the Group information provided by your IdP to determine if a user should be assigned the `auditor` role.
+The requirements are the same as the previous settings, your IdP needs to pass Group information to GitLab, you need to tell
+GitLab where to look for the groups in the SAML response, and which group(s) should be
+considered auditor users.
```yaml
{ name: 'saml',
label: 'Our SAML Provider',
groups_attribute: 'Groups',
- auditor_groups: ['Auditors', 'Security'],
+ auditor_groups: ['Auditors'],
args: {
assertion_consumer_service_url: 'https://gitlab.example.com/users/auth/saml/callback',
idp_cert_fingerprint: '43:51:43:a1:b5:fc:8b:b7:0a:3a:a9:b1:0f:66:73:a8',
@@ -265,7 +277,18 @@ This setting also follows the requirements documented for the `External Groups`
## Bypass two factor authentication
If you want some SAML authentication methods to count as 2FA on a per session basis, you can register them in the
-`upstream_two_factor_authn_contexts` list:
+`upstream_two_factor_authn_contexts` list.
+
+In addition to the changes in GitLab, make sure that your IdP is returning the
+`AuthnContext`. For example:
+
+```xml
+<saml:AuthnStatement>
+ <saml:AuthnContext>
+ <saml:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:MediumStrongCertificateProtectedTransport</saml:AuthnContextClassRef>
+ </saml:AuthnContext>
+</saml:AuthnStatement>
+```
**For Omnibus installations:**
@@ -324,18 +347,7 @@ If you want some SAML authentication methods to count as 2FA on a per session ba
}
```
-1. Save the file and [restart GitLab](../administration/restart_gitlab.md#installations-from-source) for the changes ot take effect
-
-In addition to the changes in GitLab, make sure that your Idp is returning the
-`AuthnContext`. For example:
-
-```xml
-<saml:AuthnStatement>
- <saml:AuthnContext>
- <saml:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:MediumStrongCertificateProtectedTransport</saml:AuthnContextClassRef>
- </saml:AuthnContext>
-</saml:AuthnStatement>
-```
+1. Save the file and [restart GitLab](../administration/restart_gitlab.md#installations-from-source) for the changes to take effect
## Customization
@@ -368,7 +380,6 @@ You may also bypass the auto signin feature by browsing to
### `attribute_statements`
>**Note:**
-This setting is only available on GitLab 8.6 and above.
This setting should only be used to map attributes that are part of the
OmniAuth `info` hash schema.
@@ -396,6 +407,21 @@ args: {
}
```
+#### Setting a username
+
+By default, the email in the SAML response will be used to automatically generate the user's GitLab username. If you'd like to set another attribute as the username, assign it to the `nickname` OmniAuth `info` hash attribute. For example, if you wanted to set the `username` attribute in your SAML Response to the username in GitLab, use the following setting:
+
+```yaml
+args: {
+ assertion_consumer_service_url: 'https://gitlab.example.com/users/auth/saml/callback',
+ idp_cert_fingerprint: '43:51:43:a1:b5:fc:8b:b7:0a:3a:a9:b1:0f:66:73:a8',
+ idp_sso_target_url: 'https://login.example.com/idp',
+ issuer: 'https://gitlab.example.com',
+ name_identifier_format: 'urn:oasis:names:tc:SAML:2.0:nameid-format:persistent',
+ attribute_statements: { nickname: ['username'] }
+}
+```
+
### `allowed_clock_drift`
The clock of the Identity Provider may drift slightly ahead of your system clocks.
@@ -557,10 +583,12 @@ Avoid user control of the following attributes:
These attributes define the SAML user. If users can change these attributes, they can impersonate others.
-Refer to the documentation for your [SAML Identity Provider](../user/group/saml_sso/index.md#providers) for information on how to fix these attributes.
+Refer to the documentation for your SAML Identity Provider for information on how to fix these attributes.
## Troubleshooting
+You can find the base64-encoded SAML Response in the [`production_json.log`](../administration/logs.md#production_jsonlog).
+
### GitLab+SAML Testing Environments
If you need to troubleshoot, [a complete GitLab+SAML testing environment using Docker compose](https://gitlab.com/gitlab-com/support/toolbox/replication/tree/master/compose_files) is available.
@@ -580,13 +608,14 @@ Make sure the IdP provides a claim containing the user's email address, using cl
If after signing in into your SAML server you are redirected back to the sign in page and
no error is displayed, check your `production.log` file. It will most likely contain the
message `Can't verify CSRF token authenticity`. This means that there is an error during
-the SAML request, but this error never reaches GitLab due to the CSRF check.
+the SAML request, but in GitLab 11.7 and earlier this error never reaches GitLab due to
+the CSRF check.
To bypass this you can add `skip_before_action :verify_authenticity_token` to the
`omniauth_callbacks_controller.rb` file immediately after the `class` line and
-comment out the `protect_from_forgery` line using a `#` then restart Unicorn. This
-will allow the error to hit GitLab, where it can then be seen in the usual logs,
-or as a flash message on the login screen.
+comment out the `protect_from_forgery` line using a `#`. Restart Unicorn for this
+change to take effect. This will allow the error to hit GitLab, where it can then
+be seen in the usual logs, or as a flash message on the login screen.
That file is located in `/opt/gitlab/embedded/service/gitlab-rails/app/controllers`
for Omnibus installations and by default in `/home/git/gitlab/app/controllers` for
@@ -613,6 +642,8 @@ is not providing this information, all SAML requests will fail.
Make sure this information is provided.
+Another issue that can result in this error is when the correct information is being sent by the IdP, but the attributes don't match the names in the OmniAuth `info` hash. In this case, you'll need to set `attribute_statements` in the SAML configuration to [map the attribute names in your SAML Response to the corresponding OmniAuth `info` hash names](#attribute_statements).
+
### Key validation error, Digest mismatch or Fingerprint mismatch
These errors all come from a similar place, the SAML certificate. SAML requests
diff --git a/doc/migrate_ci_to_ce/README.md b/doc/migrate_ci_to_ce/README.md
index 10f22af30f7..6710356d760 100644
--- a/doc/migrate_ci_to_ce/README.md
+++ b/doc/migrate_ci_to_ce/README.md
@@ -77,7 +77,7 @@ sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production SKIP=r
If this fails you need to fix it before upgrading to 8.0. Also see
<https://about.gitlab.com/get-help/>
-NOTE: **Note**
+NOTE: **Note:**
For GitLab 12.1 and earlier, use `gitlab-rake gitlab:backup:create`.
### 2. Check source and target database types
diff --git a/doc/operations/README.md b/doc/operations/README.md
deleted file mode 100644
index 319e5f48d38..00000000000
--- a/doc/operations/README.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-redirect_to: '../administration/operations/index.md'
----
-
-This document was moved to [another location](../administration/operations/index.md).
diff --git a/doc/operations/feature_flags.md b/doc/operations/feature_flags.md
new file mode 100644
index 00000000000..7614d70e132
--- /dev/null
+++ b/doc/operations/feature_flags.md
@@ -0,0 +1,328 @@
+---
+stage: Release
+group: Progressive Delivery
+info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers
+---
+
+# Feature Flags **(PREMIUM)**
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/7433) in GitLab 11.4.
+
+With Feature Flags, you can deploy your application's new features to production in smaller batches.
+You can toggle a feature on and off to subsets of users, helping you achieve Continuous Delivery.
+Feature flags help reduce risk, allowing you to do controlled testing, and separate feature
+delivery from customer launch.
+
+<i class="fa fa-youtube-play youtube" aria-hidden="true"></i>
+For an example of feature flags in action, see [GitLab for Deploys, Feature Flags, and Error Tracking](https://www.youtube.com/embed/5tw2p6lwXxo).
+
+## How it works
+
+GitLab uses [Unleash](https://github.com/Unleash/unleash), a feature
+toggle service.
+
+By enabling or disabling a flag in GitLab, your application
+can determine which features to enable or disable.
+
+You can create feature flags in GitLab and use the API from your application
+to get the list of feature flags and their statuses. The application must be configured to communicate
+with GitLab, so it's up to developers to use a compatible client library and
+[integrate the feature flags in your app](#integrate-feature-flags-with-your-application).
+
+## Create a feature flag
+
+To create and enable a feature flag:
+
+1. Navigate to your project's **Operations > Feature Flags**.
+1. Click the **New feature flag** button.
+1. Enter a name that starts with a letter and contains only lowercase letters, digits, underscores (`_`),
+ or dashes (`-`), and does not end with a dash (`-`) or underscore (`_`).
+1. Enter a description (optional, 255 characters max).
+1. Enter details about how the flag should be applied:
+ - In GitLab 13.0 and earlier, add **Environment specs**. For each environment,
+ include the **Status** (default enabled) and [**Rollout strategy**](#rollout-strategy-legacy)
+ (defaults to **All users**).
+ - In GitLab 13.1 and later, add Feature Flag [**Strategies**](#feature-flag-strategies).
+ For each strategy, include the **Type** (defaults to [**All users**](#all-users))
+ and **Environments** (defaults to all environments).
+1. Click **Create feature flag**.
+
+You can change these settings by clicking the **{pencil}** (edit) button
+next to any feature flag in the list.
+
+## Rollout strategy (legacy)
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/8240) in GitLab 12.2.
+
+In GitLab 13.0 and earlier, the **Rollout strategy** setting affects which users will experience
+the feature as enabled. Choose the percentage of users that the feature will be enabled
+for. The rollout strategy will have no effect if the environment spec is disabled.
+
+It can be set to:
+
+- All users
+- [Percent of users](#percent-of-users)
+ - Optionally, you can click the **Include additional user IDs** checkbox and add a list
+ of specific users IDs to enable the feature for.
+- [User IDs](#user-ids)
+
+## Feature flag strategies
+
+> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/35555) in GitLab 13.0.
+> - It's deployed behind a feature flag, disabled by default.
+> - It's enabled on GitLab.com.
+> - To use it in GitLab self-managed instances, ask a GitLab administrator to [enable it](#enable-or-disable-feature-flag-strategies). **(CORE ONLY)**
+
+You can apply a feature flag strategy across multiple environments, without defining
+the strategy multiple times.
+
+GitLab Feature Flags use [Unleash](https://unleash.github.io) as the feature flag
+engine. In Unleash, there are [strategies](https://unleash.github.io/docs/activation_strategy)
+for granular feature flag controls. GitLab Feature Flags can have multiple strategies,
+and the supported strategies are:
+
+- [All users](#all-users)
+- [Percent of Users](#percent-of-users)
+- [User IDs](#user-ids)
+- [User List](#user-list)
+
+Strategies can be added to feature flags when [creating a feature flag](#create-a-feature-flag),
+or by editing an existing feature flag after creation by navigating to **Operations > Feature Flags**
+and clicking **{pencil}** (edit).
+
+### All users
+
+Enables the feature for all users. It uses the [`default`](https://unleash.github.io/docs/activation_strategy#default)
+Unleash activation strategy.
+
+### Percent of Users
+
+Enables the feature for a percentage of authenticated users. It uses the
+[`gradualRolloutUserId`](https://unleash.github.io/docs/activation_strategy#gradualrolloutuserid)
+Unleash activation strategy.
+
+For example, set a value of 15% to enable the feature for 15% of authenticated users.
+
+The rollout percentage can be from 0% to 100%.
+
+NOTE: **Note:**
+Stickiness (consistent application behavior for the same user) is guaranteed for logged-in users, but not anonymous users.
+
+CAUTION: **Caution:**
+If this strategy is selected, then the Unleash client **must** be given a user
+ID for the feature to be enabled. See the [Ruby example](#ruby-application-example) below.
+
+### User IDs
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/8240) in GitLab 12.2. [Updated](https://gitlab.com/gitlab-org/gitlab/-/issues/34363) to be defined per environment in GitLab 12.6.
+
+Enables the feature for a list of target users. It is implemented
+using the Unleash [`userWithId`](https://unleash.github.io/docs/activation_strategy#userwithid)
+activation strategy.
+
+Enter user IDs as a comma-separated list of values. For example,
+`user@example.com, user2@example.com`, or `username1,username2,username3`, and so on.
+
+NOTE: **Note:**
+User IDs are identifiers for your application users. They do not need to be GitLab users.
+
+CAUTION: **Caution:**
+The Unleash client **must** be given a user ID for the feature to be enabled for
+target users. See the [Ruby example](#ruby-application-example) below.
+
+### User List
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/35930) in GitLab 13.1.
+
+Enables the feature for lists of users created with the [Feature Flag User List API](../api/feature_flag_user_lists.md).
+Similar to [User IDs](#user-ids), it uses the Unleash [`userWithId`](https://unleash.github.io/docs/activation_strategy#userwithid)
+activation strategy.
+
+### Enable or disable feature flag strategies
+
+This feature is under development, but is ready for testing. It's
+deployed behind a feature flag that is **disabled by default**.
+[GitLab administrators with access to the GitLab Rails console](../administration/feature_flags.md)
+can enable it for your instance.
+
+To enable it:
+
+```ruby
+Feature.enable(:feature_flags_new_version)
+```
+
+To disable it:
+
+```ruby
+Feature.disable(:feature_flags_new_version)
+```
+
+## Disable a feature flag for a specific environment
+
+In [GitLab 13.0 and earlier](https://gitlab.com/gitlab-org/gitlab/-/issues/8621),
+to disable a feature flag for a specific environment:
+
+1. Navigate to your project's **Operations > Feature Flags**.
+1. For the feature flag you want to disable, click the Pencil icon.
+1. To disable the flag:
+
+ - In GitLab 13.0 and earlier: Slide the Status toggle for the environment. Or, to delete the
+ environment spec, on the right, click the **Remove (X)** icon.
+ - In GitLab 13.1 and later: For each strategy it applies to, under **Environments**, delete the environment.
+
+1. Click **Save changes**.
+
+## Disable a feature flag for all environments
+
+To disable a feature flag for all environments:
+
+1. Navigate to your project's **Operations > Feature Flags**.
+1. For the feature flag you want to disable, slide the Status toggle to **Disabled**.
+
+The feature flag is displayed on the **Disabled** tab.
+
+## Integrate feature flags with your application
+
+To use feature flags with your application, get access credentials from GitLab.
+Then prepare your application with a client library.
+
+### Get access credentials
+
+To get the access credentials that your application needs to communicate with GitLab:
+
+1. Navigate to your project's **Operations > Feature Flags**.
+1. Click the **Configure** button to view the following:
+ - **API URL**: URL where the client (application) connects to get a list of feature flags.
+ - **Instance ID**: Unique token that authorizes the retrieval of the feature flags.
+ - **Application name**: The name of the running environment. For instance,
+ if the application runs for a production server, the application name would be
+ `production` or similar. This value is used for the environment spec evaluation.
+
+NOTE: **Note:**
+The meaning of these fields might change over time. For example, we are not sure
+if **Instance ID** will be single token or multiple tokens, assigned to the
+**Environment**. Also, **Application name** could describe the version of
+application instead of the running environment.
+
+### Choose a client library
+
+GitLab implements a single backend that is compatible with Unleash clients.
+
+With the Unleash client, developers can define, in the application code, the default values for flags.
+Each feature flag evaluation can express the desired outcome if the flag isn't present in the
+provided configuration file.
+
+Unleash currently [offers many SDKs for various languages and frameworks](https://github.com/Unleash/unleash#client-implementations).
+
+### Feature flags API information
+
+For API content, see:
+
+- [Feature Flags API](../api/feature_flags.md)
+- [Feature Flag Specs API](../api/feature_flag_specs.md) (Deprecated and [scheduled for removal in GitLab 14.0](https://gitlab.com/gitlab-org/gitlab/-/issues/213369).)
+- [Feature Flag User Lists API](../api/feature_flag_user_lists.md)
+- [Legacy Feature Flags API](../api/feature_flags_legacy.md)
+
+### Golang application example
+
+Here's an example of how to integrate feature flags in a Golang application:
+
+```golang
+package main
+
+import (
+ "io"
+ "log"
+ "net/http"
+
+ "github.com/Unleash/unleash-client-go"
+)
+
+type metricsInterface struct {
+}
+
+func init() {
+ unleash.Initialize(
+ unleash.WithUrl("https://gitlab.com/api/v4/feature_flags/unleash/42"),
+ unleash.WithInstanceId("29QmjsW6KngPR5JNPMWx"),
+ unleash.WithAppName("production"),
+ unleash.WithListener(&metricsInterface{}),
+ )
+}
+
+func helloServer(w http.ResponseWriter, req *http.Request) {
+ if unleash.IsEnabled("my_feature_name") {
+ io.WriteString(w, "Feature enabled\n")
+ } else {
+ io.WriteString(w, "hello, world!\n")
+ }
+}
+
+func main() {
+ http.HandleFunc("/", helloServer)
+ log.Fatal(http.ListenAndServe(":8080", nil))
+}
+```
+
+### Ruby application example
+
+Here's an example of how to integrate feature flags in a Ruby application.
+
+The Unleash client is given a user ID for use with a **Percent rollout (logged in users)** rollout strategy or a list of **Target Users**.
+
+```ruby
+#!/usr/bin/env ruby
+
+require 'unleash'
+require 'unleash/context'
+
+unleash = Unleash::Client.new({
+ url: 'http://gitlab.com/api/v4/feature_flags/unleash/42',
+ app_name: 'production',
+ instance_id: '29QmjsW6KngPR5JNPMWx'
+})
+
+unleash_context = Unleash::Context.new
+# Replace "123" with the id of an authenticated user.
+# Note that the context's user id must be a string:
+# https://unleash.github.io/docs/unleash_context
+unleash_context.user_id = "123"
+
+if unleash.is_enabled?("my_feature_name", unleash_context)
+ puts "Feature enabled"
+else
+ puts "hello, world!"
+end
+```
+
+## Feature Flag Related Issues
+
+> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/36617) in GitLab 13.2.
+> - It's deployed behind a feature flag, enabled by default.
+> - It's enabled on GitLab.com.
+> - It can't be enabled or disabled per-project
+> - It's recommended for production use.
+> - For GitLab self-managed instances, GitLab administrators can opt to disable it.
+
+You can link related issues to a feature flag. In the **Linked issues** section, click the `+` button and input the issue reference number or the full URL of the issue.
+
+This feature is similar to the [related issues](../user/project/issues/related_issues.md) feature.
+
+### Enable or disable Feature Flag Related Issues **(CORE ONLY)**
+
+Feature Flag Related Issues is under development but ready for production use.
+It is deployed behind a feature flag that is **enabled by default**.
+[GitLab administrators with access to the GitLab Rails console](../administration/feature_flags.md)
+can opt to disable it for your instance.
+
+To disable it:
+
+```ruby
+Feature.disable(:feature_flags_issue_links)
+```
+
+To enable it:
+
+```ruby
+Feature.enable(:feature_flags_issue_links)
+```
diff --git a/doc/operations/index.md b/doc/operations/index.md
new file mode 100644
index 00000000000..314a1b231ba
--- /dev/null
+++ b/doc/operations/index.md
@@ -0,0 +1,20 @@
+---
+stage: Monitor
+group: APM
+info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers
+---
+
+# Project operations
+
+GitLab provides a variety of tools to help operate and maintain
+your applications:
+
+- Collect [Prometheus metrics](../user/project/integrations/prometheus_library/index.md).
+- Deploy to different [environments](../ci/environments/index.md).
+- Manage your [Alerts](../user/project/operations/alert_management.md) and [Incidents](../user/incident_management/index.md).
+- Connect your project to a [Kubernetes cluster](../user/project/clusters/index.md).
+- Manage your infrastructure with [Infrastructure as Code](../user/infrastructure/index.md) approaches.
+- Discover and view errors generated by your applications with [Error Tracking](../user/project/operations/error_tracking.md).
+- Create, toggle, and remove [Feature Flags](feature_flags.md). **(PREMIUM)**
+- [Trace](tracing.md) the performance and health of a deployed application. **(ULTIMATE)**
+- Change the [settings of the Monitoring Dashboard](../user/project/operations/dashboard_settings.md).
diff --git a/doc/operations/metrics/alerts.md b/doc/operations/metrics/alerts.md
new file mode 100644
index 00000000000..43debbd1b78
--- /dev/null
+++ b/doc/operations/metrics/alerts.md
@@ -0,0 +1,110 @@
+---
+stage: Monitor
+group: APM
+info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers
+---
+
+# Set up alerts for Prometheus metrics
+
+After [configuring metrics for your CI/CD environment](index.md), you can set up
+alerting for Prometheus metrics depending on the location of your instances, and
+[trigger actions from alerts](#trigger-actions-from-alerts-ultimate) to notify
+your team when environment performance falls outside of the boundaries you set.
+
+## Managed Prometheus instances
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/6590) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 11.2 for [custom metrics](index.md#adding-custom-metrics), and 11.3 for [library metrics](../../user/project/integrations/prometheus_library/metrics.md).
+
+For managed Prometheus instances using auto configuration, you can
+[configure alerts for metrics](index.md#adding-custom-metrics) directly in the
+[metrics dashboard](index.md). To set an alert:
+
+1. In your project, navigate to **{cloud-gear}** **Operations > Metrics**,
+1. Identify the metric you want to create the alert for, and click the
+ **ellipsis** **{ellipsis_v}** icon in the top right corner of the metric.
+1. Choose **Alerts**.
+1. Set threshold and operator.
+1. Click **Add** to save and activate the alert.
+
+![Adding an alert](../../user/project/integrations/img/prometheus_alert.png)
+
+To remove the alert, click back on the alert icon for the desired metric, and click **Delete**.
+
+## External Prometheus instances
+
+>- [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/9258) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 11.8.
+>- [Moved](https://gitlab.com/gitlab-org/gitlab/-/issues/42640) to [GitLab Core](https://about.gitlab.com/pricing/) in 12.10.
+
+For manually configured Prometheus servers, GitLab provides a notify endpoint for
+use with Prometheus webhooks. If you have manual configuration enabled, an
+**Alerts** section is added to **{settings}** **Settings > Integrations > Prometheus**.
+This section contains the **URL** and **Authorization Key** you will need. The
+**Reset Key** button will invalidate the key and generate a new one.
+
+![Prometheus service configuration of Alerts](../../user/project/integrations/img/prometheus_service_alerts.png)
+
+To send GitLab alert notifications, copy the **URL** and **Authorization Key** into the
+[`webhook_configs`](https://prometheus.io/docs/alerting/latest/configuration/#webhook_config)
+section of your Prometheus Alertmanager configuration:
+
+```yaml
+receivers:
+ name: gitlab
+ webhook_configs:
+ - http_config:
+ bearer_token: 9e1cbfcd546896a9ea8be557caf13a76
+ send_resolved: true
+ url: http://192.168.178.31:3001/root/manual_prometheus/prometheus/alerts/notify.json
+ ...
+```
+
+For GitLab to associate your alerts with an [environment](../../ci/environments/index.md),
+you must configure a `gitlab_environment_name` label on the alerts you set up in
+Prometheus. The value of this should match the name of your environment in GitLab.
+
+NOTE: **Note:**
+In GitLab versions 13.1 and greater, you can configure your manually configured
+Prometheus server to use the
+[Generic alerts integration](../../user/project/integrations/generic_alerts.md).
+
+## Trigger actions from alerts **(ULTIMATE)**
+
+>- [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/4925) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 11.11.
+>- [From GitLab Ultimate 12.5](https://gitlab.com/gitlab-org/gitlab/-/issues/13401), when GitLab receives a recovery alert, it will automatically close the associated issue.
+
+Alerts can be used to trigger actions, like opening an issue automatically
+(disabled by default since `13.1`). To configure the actions:
+
+1. Navigate to your project's **{settings}** **Settings > Operations > Incidents**.
+1. Enable the option to create issues.
+1. Choose the [issue template](../../user/project/description_templates.md) to create the issue from.
+1. Optionally, select whether to send an email notification to the developers of the project.
+1. Click **Save changes**.
+
+After enabling, GitLab automatically opens an issue when an alert is triggered containing
+values extracted from [alert's payload](https://prometheus.io/docs/alerting/latest/configuration/#webhook_config):
+
+- Issue author: `GitLab Alert Bot`
+- Issue title: Extract from `annotations/title`, `annotations/summary` or `labels/alertname`
+- Alert `Summary`: A list of properties
+ - `starts_at`: Alert start time via `startsAt`
+ - `full_query`: Alert query extracted from `generatorURL`
+ - Optional list of attached annotations extracted from `annotations/*`
+- Alert [GFM](../../user/markdown.md): GitLab Flavored Markdown from `annotations/gitlab_incident_markdown`
+
+When GitLab receives a **Recovery Alert**, it closes the associated issue.
+This action is recorded as a system message on the issue indicating that it
+was closed automatically by the GitLab Alert bot.
+
+To further customize the issue, you can add labels, mentions, or any other supported
+[quick action](../../user/project/quick_actions.md) in the selected issue template,
+which applies to all incidents. To limit quick actions or other information to
+only specific types of alerts, use the `annotations/gitlab_incident_markdown` field.
+
+Since [version 12.2](https://gitlab.com/gitlab-org/gitlab-foss/-/issues/63373),
+GitLab tags each incident issue with the `incident` label automatically. If the label
+does not yet exist, it is also created automatically.
+
+If the metric exceeds the threshold of the alert for over 5 minutes, GitLab sends
+an email to all [Maintainers and Owners](../../user/permissions.md#project-members-permissions)
+of the project.
diff --git a/doc/operations/metrics/dashboards/index.md b/doc/operations/metrics/dashboards/index.md
new file mode 100644
index 00000000000..a46abdee2dc
--- /dev/null
+++ b/doc/operations/metrics/dashboards/index.md
@@ -0,0 +1,249 @@
+---
+stage: Monitor
+group: APM
+info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers
+---
+
+# Using the Metrics Dashboard
+
+## Manage the metrics dashboard settings
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/223204) in GitLab 13.2.
+
+To manage the settings for your metrics dashboard:
+
+1. Sign in as a user with project Maintainer or Admin
+ [permissions](../../../user/permissions.md#project-members-permissions).
+1. Navigate to your dashboard at **{cloud-gear}** **Operations > Metrics**.
+1. In the top-right corner of your dashboard, click **{settings}** **Metrics Settings**:
+
+ ![Monitoring Dashboard actions menu with create new item](../../../user/project/integrations/img/metrics_settings_button_v13_2.png)
+
+## Chart Context Menu
+
+From each of the panels in the dashboard, you can access the context menu by clicking the **{ellipsis_v}** **More actions** dropdown box above the upper right corner of the panel to take actions related to the chart's data.
+
+![Context Menu](../../../user/project/integrations/img/panel_context_menu_v13_0.png)
+
+The options are:
+
+- [Expand panel](#expand-panel)
+- [View logs](#view-logs-ultimate)
+- [Download CSV](#downloading-data-as-csv)
+- [Copy link to chart](../embed.md#embedding-gitlab-managed-kubernetes-metrics)
+- [Alerts](../alerts.md)
+
+### View and edit the source file of a custom dashboard
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/34779) in GitLab 12.5.
+
+When viewing a custom dashboard of a project, you can view the original
+`.yml` file by clicking on the **Edit dashboard** button.
+
+### Expand panel
+
+> [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/3100) in GitLab 13.0.
+
+To view a larger version of a visualization, expand the panel by clicking the
+**{ellipsis_v}** **More actions** icon and selecting **Expand panel**.
+
+To return to the metrics dashboard, click the **Back** button in your
+browser, or pressing the <kbd>Esc</kbd> key.
+
+### View Logs **(ULTIMATE)**
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/122013) in GitLab 12.8.
+
+If you have [Logs](../../../user/project/clusters/kubernetes_pod_logs.md) enabled,
+you can navigate from the charts in the dashboard to view Logs by
+clicking on the context menu in the upper-right corner.
+
+If you use the **Timeline zoom** function at the bottom of the chart, logs will narrow down to the time range you selected.
+
+### Timeline zoom and URL sharing
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/198910) in GitLab 12.8.
+
+You can use the **Timeline zoom** function at the bottom of a chart to zoom in
+on a date and time of your choice. When you click and drag the sliders to select
+a different beginning or end date of data to display, GitLab adds your selected start
+and end times to the URL, enabling you to share specific timeframes more easily.
+
+### Downloading data as CSV
+
+Data from Prometheus charts on the metrics dashboard can be downloaded as CSV.
+
+## Dashboard Annotations
+
+> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/211330) in GitLab 12.10 (enabled by feature flag `metrics_dashboard_annotations`).
+> - [Feature flag removed](https://gitlab.com/gitlab-org/gitlab/-/issues/215224) in GitLab 13.0.
+
+You can use **Metrics Dashboard Annotations** to mark any important events on
+every metrics dashboard by adding annotations to it. While viewing a dashboard,
+annotation entries assigned to the selected time range will be automatically
+fetched and displayed on every chart within that dashboard. On mouse hover, each
+annotation presents additional details, including the exact time of an event and
+its description.
+
+You can create annotations by making requests to the
+[Metrics dashboard annotations API](../../../api/metrics_dashboard_annotations.md)
+
+![Annotations UI](../../../user/project/integrations/img/metrics_dashboard_annotations_ui_v13.0.png)
+
+### Retention policy
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/211433) in GitLab 13.01.
+
+To avoid excessive storage space consumption by stale annotations, records attached
+to time periods older than two weeks are removed daily. This recurring background
+job runs at 1:00 a.m. local server time.
+
+## Add related links to custom dashboards
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/216385) in GitLab 13.1.
+
+You can embed links to other dashboards or external services in your custom
+dashboard by adding **Related links** to your dashboard's YAML file. Related links
+open in the same tab as the dashboard. Related links can be displayed in the
+following locations on your dashboard:
+
+- At the top of your dashboard as the top level [`links` dashboard property](../../../operations/metrics/dashboards/yaml.md#dashboard-top-level-properties).
+- In charts context menus as the [`links` property of a panel](../../../operations/metrics/dashboards/yaml.md#panel-panels-properties).
+
+Related links can contain the following attributes:
+
+- `url`: The full URL to the link. Required.
+- `title`: A phrase describing the link. Optional. If this attribute is not set,
+ the full URL is used for the link title.
+- `type`: A string declaring the type of link. Optional. If set to `grafana`, the
+ dashboard's time range values are converted to Grafana's time range format and
+ appended to the `url`.
+
+The dashboard's time range is appended to the `url` as URL parameters.
+
+The following example shows two related links (`GitLab.com` and `GitLab Documentation`)
+added to a dashboard:
+
+![Links UI](../../../user/project/integrations/img/related_links_v13_1.png)
+
+### Links Syntax
+
+```yaml
+links:
+ - title: GitLab.com
+ url: https://gitlab.com
+ - title: GitLab Documentation
+ url: https://docs.gitlab.com
+ - title: Public Grafana playground dashboard
+ url: https://play.grafana.org/d/000000012/grafana-play-home?orgId=1
+ type: grafana
+```
+
+## Defining custom dashboards per project
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/issues/59974) in GitLab 12.1.
+
+By default, all projects include a GitLab-defined Prometheus dashboard, which
+includes a few key metrics, but you can also define your own custom dashboards.
+
+You may create a new file from scratch or duplicate a GitLab-defined Prometheus
+dashboard.
+
+NOTE: **Note:**
+The metrics as defined below do not support alerts, unlike
+[custom metrics](../index.md#adding-custom-metrics).
+
+### Adding a new dashboard to your project
+
+> UI option [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/223204) in GitLab 13.2.
+
+You can configure a custom dashboard by adding a new YAML file into your project's
+`.gitlab/dashboards/` directory. In order for the dashboards to be displayed on
+the project's **{cloud-gear}** **Operations > Metrics** page, the files must have a `.yml`
+extension and should be present in the project's **default** branch.
+
+To create a new dashboard from the GitLab user interface:
+
+1. Sign in to GitLab as a user with Maintainer or Owner
+ [permissions](../../../user/permissions.md#project-members-permissions).
+1. Navigate to your dashboard at **{cloud-gear}** **Operations > Metrics**.
+1. In the top-right corner of your dashboard, click the **{file-addition-solid}** **Actions** menu,
+ and select **Create new**:
+ ![Monitoring Dashboard actions menu with create new item](../../../user/project/integrations/img/actions_menu_create_new_dashboard_v13_2.png)
+1. In the modal window, click **Open Repository**, then follow the instructions
+ for creating a new dashboard from the command line.
+
+To create a new dashboard from the command line:
+
+1. Create `.gitlab/dashboards/prom_alerts.yml` under your repository's root
+ directory. Each YAML file should define the layout of the dashboard and the
+ Prometheus queries used to populate data. This example dashboard displays a
+ single area chart:
+
+ ```yaml
+ dashboard: 'Dashboard Title'
+ panel_groups:
+ - group: 'Group Title'
+ panels:
+ - type: area-chart
+ title: "Chart Title"
+ y_label: "Y-Axis"
+ y_axis:
+ format: number
+ precision: 0
+ metrics:
+ - id: my_metric_id
+ query_range: 'http_requests_total'
+ label: "Instance: {{instance}}, method: {{method}}"
+ unit: "count"
+ ```
+
+1. Save the file, commit, and push to your repository. The file must be present in your **default** branch.
+1. Navigate to your project's **Operations > Metrics** and choose the custom
+ dashboard from the dropdown.
+
+NOTE: **Note:**
+Configuration files nested under subdirectories of `.gitlab/dashboards` are not
+supported and will not be available in the UI.
+
+### Navigating to a custom dashboard
+
+Custom dashboards are uniquely identified by their filenames. In order to quickly view the custom dashboard,
+just use the dashboard filename in the URL this way:
+`https://gitlab-instance.example.com/project/-/metrics/custom_dashboard_name.yml`.
+
+### Duplicating a GitLab-defined dashboard
+
+> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/37238) in GitLab 12.7.
+> - From [GitLab 12.8 onwards](https://gitlab.com/gitlab-org/gitlab/-/issues/39505), custom metrics are also duplicated when you duplicate a dashboard.
+
+You can save a complete copy of a GitLab defined dashboard along with all custom metrics added to it.
+Resulting `.yml` file can be customized and adapted to your project.
+You can decide to save the dashboard `.yml` file in the project's **default** branch or in a
+new branch.
+
+1. Click **Duplicate dashboard** in the dashboard dropdown or in the actions menu.
+
+ NOTE: **Note:**
+ You can duplicate only GitLab-defined dashboards.
+
+1. Enter the file name and other information, such as the new commit's message, and click **Duplicate**.
+
+If you select your **default** branch, the new dashboard becomes immediately available.
+If you select another branch, this branch should be merged to your **default** branch first.
+
+## Troubleshooting
+
+When troubleshooting issues with a managed Prometheus app, it is often useful to
+[view the Prometheus UI](../../../development/prometheus.md#access-the-ui-of-a-prometheus-managed-application-in-kubernetes).
+
+### "No data found" error on Metrics dashboard page
+
+If the "No data found" screen continues to appear, it could be due to:
+
+- No successful deployments have occurred to this environment.
+- Prometheus does not have performance data for this environment, or the metrics
+ are not labeled correctly. To test this, connect to the Prometheus server and
+ [run a query](../../../user/project/integrations/prometheus_library/kubernetes.md#metrics-supported), replacing `$CI_ENVIRONMENT_SLUG`
+ with the name of your environment.
+- You may need to re-add the GitLab predefined common metrics. This can be done by running the [import common metrics Rake task](../../../administration/raketasks/maintenance.md#import-common-metrics).
diff --git a/doc/operations/metrics/dashboards/panel_types.md b/doc/operations/metrics/dashboards/panel_types.md
new file mode 100644
index 00000000000..0397218fe0e
--- /dev/null
+++ b/doc/operations/metrics/dashboards/panel_types.md
@@ -0,0 +1,262 @@
+---
+stage: Monitor
+group: APM
+info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers
+---
+
+# Panel types for dashboards
+
+The below panel types are supported in monitoring dashboards.
+
+## Area or Line Chart
+
+To add an area chart panel type to a dashboard, look at the following sample dashboard file:
+
+```yaml
+dashboard: 'Dashboard Title'
+panel_groups:
+ - group: 'Group Title'
+ panels:
+ - type: area-chart # or line-chart
+ title: 'Area Chart Title'
+ y_label: "Y-Axis"
+ y_axis:
+ format: number
+ precision: 0
+ metrics:
+ - id: area_http_requests_total
+ query_range: 'http_requests_total'
+ label: "Instance: {{instance}}, Method: {{method}}"
+ unit: "count"
+```
+
+Note the following properties:
+
+| Property | Type | Required | Description |
+| ------ | ------ | ------ | ------ |
+| type | string | no | Type of panel to be rendered. Optional for area panel types |
+| query_range | string | required | For area panel types, you must use a [range query](https://prometheus.io/docs/prometheus/latest/querying/api/#range-queries) |
+
+![area panel chart](../../../user/project/integrations/img/prometheus_dashboard_area_panel_type_v12_8.png)
+
+Starting in [version 12.8](https://gitlab.com/gitlab-org/gitlab/-/issues/202696), the y-axis values will automatically scale according to the data. Previously, it always started from 0.
+
+## Anomaly chart
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/16530) in GitLab 12.5.
+
+To add an anomaly chart panel type to a dashboard, add a panel with *exactly* 3 metrics.
+
+The first metric represents the current state, and the second and third metrics represent the upper and lower limit respectively:
+
+```yaml
+dashboard: 'Dashboard Title'
+panel_groups:
+ - group: 'Group Title'
+ panels:
+ - type: anomaly-chart
+ title: "Chart Title"
+ y_label: "Y-Axis"
+ metrics:
+ - id: anomaly_requests_normal
+ query_range: 'http_requests_total'
+ label: "# of Requests"
+ unit: "count"
+ metrics:
+ - id: anomaly_requests_upper_limit
+ query_range: 10000
+ label: "Max # of requests"
+ unit: "count"
+ metrics:
+ - id: anomaly_requests_lower_limit
+ query_range: 2000
+ label: "Min # of requests"
+ unit: "count"
+```
+
+Note the following properties:
+
+| Property | Type | Required | Description |
+| ------ | ------ | ------ | ------ |
+| type | string | required | Must be `anomaly-chart` for anomaly panel types |
+| query_range | yes | required | For anomaly panel types, you must use a [range query](https://prometheus.io/docs/prometheus/latest/querying/api/#range-queries) in every metric. |
+
+![anomaly panel type](../../../user/project/integrations/img/prometheus_dashboard_anomaly_panel_type.png)
+
+## Bar chart
+
+To add a bar chart to a dashboard, look at the following sample dashboard file:
+
+```yaml
+dashboard: 'Dashboard Title'
+panel_groups:
+ - group: 'Group title'
+ panels:
+ - type: bar
+ title: "Http Handlers"
+ x_label: 'Response Size'
+ y_axis:
+ name: "Handlers"
+ metrics:
+ - id: prometheus_http_response_size_bytes_bucket
+ query_range: "sum(increase(prometheus_http_response_size_bytes_bucket[1d])) by (handler)"
+ unit: 'Bytes'
+```
+
+Note the following properties:
+
+| Property | Type | Required | Description |
+| ------ | ------ | ------ | ------ |
+| `type` | string | yes | Type of panel to be rendered. For bar chart types, set to `bar` |
+| `query_range` | yes | yes | For bar chart, you must use a [range query](https://prometheus.io/docs/prometheus/latest/querying/api/#range-queries)
+
+![bar chart panel type](../../../user/project/integrations/img/prometheus_dashboard_bar_chart_panel_type_v12.10.png)
+
+## Column chart
+
+To add a column panel type to a dashboard, look at the following sample dashboard file:
+
+```yaml
+dashboard: 'Dashboard Title'
+panel_groups:
+ - group: 'Group title'
+ panels:
+ - title: "Column"
+ type: "column"
+ metrics:
+ - id: 1024_memory
+ query: 'avg(sum(container_memory_usage_bytes{container_name!="POD",pod_name=~"^%{ci_environment_slug}-([^c].*|c([^a]|a([^n]|n([^a]|a([^r]|r[^y])))).*|)-(.*)",namespace="%{kube_namespace}"}) by (job)) without (job) / count(avg(container_memory_usage_bytes{container_name!="POD",pod_name=~"^%{ci_environment_slug}-([^c].*|c([^a]|a([^n]|n([^a]|a([^r]|r[^y])))).*|)-(.*)",namespace="%{kube_namespace}"}) without (job)) /1024/1024'
+ unit: MB
+ label: "Memory Usage"
+```
+
+Note the following properties:
+
+| Property | Type | Required | Description |
+| ------ | ------ | ------ | ------ |
+| type | string | yes | Type of panel to be rendered. For column panel types, set to `column` |
+| query_range | yes | yes | For column panel types, you must use a [range query](https://prometheus.io/docs/prometheus/latest/querying/api/#range-queries) |
+
+![anomaly panel type](../../../user/project/integrations/img/prometheus_dashboard_column_panel_type.png)
+
+## Stacked column
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/30583) in GitLab 12.8.
+
+To add a stacked column panel type to a dashboard, look at the following sample dashboard file:
+
+```yaml
+dashboard: 'Dashboard title'
+priority: 1
+panel_groups:
+ - group: 'Group Title'
+ priority: 5
+ panels:
+ - type: 'stacked-column'
+ title: "Stacked column"
+ y_label: "y label"
+ x_label: 'x label'
+ metrics:
+ - id: memory_1
+ query_range: 'memory_query'
+ label: "memory query 1"
+ unit: "count"
+ series_name: 'group 1'
+ - id: memory_2
+ query_range: 'memory_query_2'
+ label: "memory query 2"
+ unit: "count"
+ series_name: 'group 2'
+```
+
+![stacked column panel type](../../../user/project/integrations/img/prometheus_dashboard_stacked_column_panel_type_v12_8.png)
+
+| Property | Type | Required | Description |
+| ------ | ------ | ------ | ------ |
+| `type` | string | yes | Type of panel to be rendered. For stacked column panel types, set to `stacked-column` |
+| `query_range` | yes | yes | For stacked column panel types, you must use a [range query](https://prometheus.io/docs/prometheus/latest/querying/api/#range-queries) |
+
+## Single Stat
+
+To add a single stat panel type to a dashboard, look at the following sample dashboard file:
+
+```yaml
+dashboard: 'Dashboard Title'
+panel_groups:
+ - group: 'Group Title'
+ panels:
+ - title: "Single Stat"
+ type: "single-stat"
+ metrics:
+ - id: 10
+ query: 'max(go_memstats_alloc_bytes{job="prometheus"})'
+ unit: MB
+ label: "Total"
+```
+
+Note the following properties:
+
+| Property | Type | Required | Description |
+| ------ | ------ | ------ | ------ |
+| type | string | yes | Type of panel to be rendered. For single stat panel types, set to `single-stat` |
+| field | string | no | Panels display the value of a metric. For a panel to display the value of a label instead, put the name of the label in this key. |
+| query | string | yes | For single stat panel types, you must use an [instant query](https://prometheus.io/docs/prometheus/latest/querying/api/#instant-queries) |
+
+![single stat panel type](../../../user/project/integrations/img/prometheus_dashboard_single_stat_panel_type.png)
+
+## Percentile based results
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/201946) in GitLab 12.8.
+
+Query results sometimes need to be represented as a percentage value out of 100. You can use the `max_value` property at the root of the panel definition:
+
+```yaml
+dashboard: 'Dashboard Title'
+panel_groups:
+ - group: 'Group Title'
+ panels:
+ - title: "Single Stat"
+ type: "single-stat"
+ max_value: 100
+ metrics:
+ - id: 10
+ query: 'max(go_memstats_alloc_bytes{job="prometheus"})'
+ unit: '%'
+ label: "Total"
+```
+
+For example, if you have a query value of `53.6`, adding `%` as the unit results in a single stat value of `53.6%`, but if the maximum expected value of the query is `120`, the value would be `44.6%`. Adding the `max_value` causes the correct percentage value to display.
+
+## Heatmaps
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/30581) in GitLab 12.5.
+
+To add a heatmap panel type to a dashboard, look at the following sample dashboard file:
+
+```yaml
+dashboard: 'Dashboard Title'
+panel_groups:
+ - group: 'Group Title'
+ panels:
+ - title: "Heatmap"
+ type: "heatmap"
+ metrics:
+ - id: 10
+ query: 'sum(rate(nginx_upstream_responses_total{upstream=~"%{kube_namespace}-%{ci_environment_slug}-.*"}[60m])) by (status_code)'
+ unit: req/sec
+ label: "Status code"
+```
+
+Note the following properties:
+
+| Property | Type | Required | Description |
+| ------ | ------ | ------ | ------ |
+| type | string | yes | Type of panel to be rendered. For heatmap panel types, set to `heatmap` |
+| query_range | yes | yes | For area panel types, you must use a [range query](https://prometheus.io/docs/prometheus/latest/querying/api/#range-queries) |
+
+![heatmap panel type](../../../user/project/integrations/img/heatmap_panel_type.png)
+
+CAUTION: **Warning:**
+When a query returns too many data points, the heatmap data bucket dimensions tend downwards to 0, making the chart's data invisible, as shown in the image below. To fix this problem, limit the amount of data returned by changing the time range filter on the metrics dashboard UI, or adding the **step** property to your dashboard's YAML file.
+
+![heatmap chart_too_much_data](../../../user/project/integrations/img/heatmap_chart_too_much_data_v_13_2.png)
diff --git a/doc/operations/metrics/dashboards/templating_variables.md b/doc/operations/metrics/dashboards/templating_variables.md
new file mode 100644
index 00000000000..a515742ea92
--- /dev/null
+++ b/doc/operations/metrics/dashboards/templating_variables.md
@@ -0,0 +1,128 @@
+---
+stage: Monitor
+group: APM
+info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers
+---
+
+# Templating variables for metrics dashboards
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/214539) in GitLab 13.0.
+
+Templating variables can be used to make your metrics dashboard more versatile.
+
+`templating` is a top-level key in the
+[dashboard YAML](yaml.md#dashboard-top-level-properties).
+Define your variables in the `variables` key, under `templating`. The value of
+the `variables` key should be a hash, and each key under `variables`
+defines a templating variable on the dashboard, and may contain alphanumeric and underscore characters.
+
+A variable can be used in a Prometheus query in the same dashboard using the syntax
+described [in Using Variables](variables.md).
+
+## `text` variable type
+
+CAUTION: **Warning:**
+This variable type is an _alpha_ feature, and is subject to change at any time
+without prior notice!
+
+For each `text` variable defined in the dashboard YAML, there will be a free text
+box on the dashboard UI, allowing you to enter a value for each variable.
+
+The `text` variable type supports a simple and a full syntax.
+
+### Simple syntax
+
+This example creates a variable called `variable1`, with a default value
+of `default value`:
+
+```yaml
+templating:
+ variables:
+ variable1: 'default value' # `text` type variable with `default value` as its default.
+```
+
+### Full syntax
+
+This example creates a variable called `variable1`, with a default value of `default`.
+The label for the text box on the UI will be the value of the `label` key:
+
+```yaml
+templating:
+ variables:
+ variable1: # The variable name that can be used in queries.
+ label: 'Variable 1' # (Optional) label that will appear in the UI for this text box.
+ type: text
+ options:
+ default_value: 'default' # (Optional) default value.
+```
+
+## `custom` variable type
+
+CAUTION: **Warning:**
+This variable type is an _alpha_ feature, and is subject to change at any time
+without prior notice!
+
+Each `custom` variable defined in the dashboard YAML creates a dropdown
+selector on the dashboard UI, allowing you to select a value for each variable.
+
+The `custom` variable type supports a simple and a full syntax.
+
+### Simple syntax
+
+This example creates a variable called `variable1`, with a default value of `value1`.
+The dashboard UI will display a dropdown with `value1`, `value2` and `value3`
+as the choices.
+
+```yaml
+templating:
+ variables:
+ variable1: ['value1', 'value2', 'value3']
+```
+
+### Full syntax
+
+This example creates a variable called `variable1`, with a default value of `value_option_2`.
+The label for the text box on the UI will be the value of the `label` key.
+The dashboard UI will display a dropdown with `Option 1` and `Option 2`
+as the choices.
+
+If you select `Option 1` from the dropdown, the variable will be replaced with `value option 1`.
+Similarly, if you select `Option 2`, the variable will be replaced with `value_option_2`:
+
+```yaml
+templating:
+ variables:
+ variable1: # The variable name that can be used in queries.
+ label: 'Variable 1' # (Optional) label that will appear in the UI for this dropdown.
+ type: custom
+ options:
+ values:
+ - value: 'value option 1' # The value that will replace the variable in queries.
+ text: 'Option 1' # (Optional) Text that will appear in the UI dropdown.
+ - value: 'value_option_2'
+ text: 'Option 2'
+ default: true # (Optional) This option should be the default value of this variable.
+```
+
+## `metric_label_values` variable type
+
+CAUTION: **Warning:**
+This variable type is an _alpha_ feature, and is subject to change at any time
+without prior notice!
+
+### Full syntax
+
+This example creates a variable called `variable2`. The values of the dropdown will
+be all the different values of the `backend` label in the Prometheus series described by
+`up{env="production"}`.
+
+```yaml
+templating:
+ variables:
+ variable2: # The variable name that can be interpolated in queries.
+ label: 'Variable 2' # (Optional) label that will appear in the UI for this dropdown.
+ type: metric_label_values
+ options:
+ series_selector: 'up{env="production"}'
+ label: 'backend'
+```
diff --git a/doc/operations/metrics/dashboards/variables.md b/doc/operations/metrics/dashboards/variables.md
new file mode 100644
index 00000000000..19b77a1ed87
--- /dev/null
+++ b/doc/operations/metrics/dashboards/variables.md
@@ -0,0 +1,59 @@
+---
+stage: Monitor
+group: APM
+info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers
+---
+
+# Using Variables
+
+## Query Variables
+
+Variables can be specified using double curly braces, such as `"{{ci_environment_slug}}"` ([added](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/20793) in GitLab 12.7).
+
+Support for the `"%{ci_environment_slug}"` format was
+[removed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/31581) in GitLab 13.0.
+Queries that continue to use the old format will show no data.
+
+## Predefined variables
+
+GitLab supports a limited set of [CI variables](../../../ci/variables/README.md) in the Prometheus query. This is particularly useful for identifying a specific environment, for example with `ci_environment_slug`. The supported variables are:
+
+- `ci_environment_slug`
+- `kube_namespace`
+- `ci_project_name`
+- `ci_project_namespace`
+- `ci_project_path`
+- `ci_environment_name`
+- `__range`
+
+NOTE: **Note:**
+Variables for Prometheus queries must be lowercase.
+
+### __range
+
+The `__range` variable is useful in Prometheus
+[range vector selectors](https://prometheus.io/docs/prometheus/latest/querying/basics/#range-vector-selectors).
+Its value is the total number of seconds in the dashboard's time range.
+For example, if the dashboard time range is set to 8 hours, the value of
+`__range` is `28800s`.
+
+## User-defined variables
+
+[Variables can be defined](../../../operations/metrics/dashboards/yaml.md#templating-templating-properties) in a custom dashboard YAML file.
+
+## Query Variables from URL
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/214500) in GitLab 13.0.
+
+GitLab supports setting custom variables through URL parameters. Surround the variable
+name with double curly braces (`{{example}}`) to interpolate the variable in a query:
+
+```plaintext
+avg(sum(container_memory_usage_bytes{container_name!="{{pod}}"}) by (job)) without (job) /1024/1024/1024'
+```
+
+The URL for this query would be:
+
+```plaintext
+http://gitlab.com/<user>/<project>/-/environments/<environment_id>/metrics?dashboard=.gitlab%2Fdashboards%2Fcustom.yml&pod=POD
+```
diff --git a/doc/operations/metrics/dashboards/yaml.md b/doc/operations/metrics/dashboards/yaml.md
new file mode 100644
index 00000000000..6a4158798bc
--- /dev/null
+++ b/doc/operations/metrics/dashboards/yaml.md
@@ -0,0 +1,166 @@
+---
+stage: Monitor
+group: APM
+info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers
+---
+
+# Dashboard YAML properties
+
+Dashboards have several components:
+
+- Templating variables.
+- Panel groups, which consist of panels.
+- Panels, which support one or more metrics.
+
+The following tables outline the details of expected properties.
+
+## **Dashboard (top-level) properties**
+
+| Property | Type | Required | Description |
+| ------ | ------ | ------ | ------ |
+| `dashboard` | string | yes | Heading for the dashboard. Only one dashboard should be defined per file. |
+| `panel_groups` | array | yes | The panel groups which should be on the dashboard. |
+| `templating` | hash | no | Top level key under which templating related options can be added. |
+| `links` | array | no | Add links to display on the dashboard. |
+
+## **Templating (`templating`) properties**
+
+| Property | Type | Required | Description |
+| -------- | ---- | -------- | ----------- |
+| `variables` | hash | yes | Variables can be defined here. |
+
+Read the documentation on [templating](templating_variables.md).
+
+## **Links (`links`) properties**
+
+| Property | Type | Required | Description |
+| -------- | ---- | -------- | ----------- |
+| `url` | string | yes | The address of the link. |
+| `title` | string | no | Display title for the link. |
+| `type` | string | no | Type of the link. Specifies the link type, can be: `grafana` |
+
+Read the documentation on [links](index.md#add-related-links-to-custom-dashboards).
+
+## **Panel group (`panel_groups`) properties**
+
+| Property | Type | Required | Description |
+| ------ | ------ | ------ | ------ |
+| `group` | string | required | Heading for the panel group. |
+| `priority` | number | optional, defaults to order in file | Order to appear on the dashboard. Higher number means higher priority, which will be higher on the page. Numbers do not need to be consecutive. |
+| `panels` | array | required | The panels which should be in the panel group. |
+
+Panels in a panel group are laid out in rows consisting of two panels per row. An exception to this rule are single panels on a row: these panels will take the full width of their containing row.
+
+## **Panel (`panels`) properties**
+
+| Property | Type | Required | Description |
+| ------ | ------ | ------ | ------- |
+| `type` | enum | no, defaults to `area-chart` | Specifies the panel type to use, for example `area-chart`, `line-chart` or `anomaly-chart`. [View documentation on all panel types.](panel_types.md) |
+| `title` | string | yes | Heading for the panel. |
+| `y_label` | string | no, but highly encouraged | Y-Axis label for the panel. |
+| `y_axis` | string | no | Y-Axis configuration for the panel. |
+| `max_value` | number | no | Denominator value used for calculating [percentile based results](panel_types.md#percentile-based-results) |
+| `weight` | number | no, defaults to order in file | Order to appear within the grouping. Lower number means higher priority, which will be higher on the page. Numbers do not need to be consecutive. |
+| `metrics` | array | yes | The metrics which should be displayed in the panel. Any number of metrics can be displayed when `type` is `area-chart` or `line-chart`, whereas only 3 can be displayed when `type` is `anomaly-chart`. |
+| `links` | array | no | Add links to display on the chart's [context menu](index.md#chart-context-menu). |
+
+## **Axis (`panels[].y_axis`) properties**
+
+| Property | Type | Required | Description |
+| ----------- | ------ | ----------------------------- | -------------------------------------------------------------------- |
+| `name` | string | no, but highly encouraged | Y-Axis label for the panel. Replaces `y_label` if set. |
+| `format` | string | no, defaults to `engineering` | Unit format used. See the [full list of units](yaml_number_format.md). |
+| `precision` | number | no, defaults to `2` | Number of decimal places to display in the number. | |
+
+## **Metrics (`metrics`) properties**
+
+| Property | Type | Required | Description |
+| ------ | ------ | ------ | ------ |
+| `id` | string | no | Used for associating dashboard metrics with database records. Must be unique across dashboard configuration files. Required for [alerting](../alerts.md) (support not yet enabled, see [relevant issue](https://gitlab.com/gitlab-org/gitlab/-/issues/27980)). |
+| `unit` | string | yes | Defines the unit of the query's return data. |
+| `label` | string | no, but highly encouraged | Defines the legend-label for the query. Should be unique within the panel's metrics. Can contain time series labels as interpolated variables. |
+| `query` | string | yes if `query_range` is not defined | Defines the Prometheus query to be used to populate the chart/panel. If defined, the `query` endpoint of the [Prometheus API](https://prometheus.io/docs/prometheus/latest/querying/api/) will be utilized. |
+| `query_range` | string | yes if `query` is not defined | Defines the Prometheus query to be used to populate the chart/panel. If defined, the `query_range` endpoint of the [Prometheus API](https://prometheus.io/docs/prometheus/latest/querying/api/) will be utilized. |
+| `step` | number | no, value is calculated if not defined | Defines query resolution step width in float number of seconds. Metrics on the same panel should use the same `step` value. |
+
+## Dynamic labels
+
+Dynamic labels are useful when multiple time series are returned from a Prometheus query.
+
+When a static label is used and a query returns multiple time series, then all the legend items will be labeled the same, which makes identifying each time series difficult:
+
+```yaml
+metrics:
+ - id: my_metric_id
+ query_range: 'http_requests_total'
+ label: "Time Series"
+ unit: "count"
+```
+
+This may render a legend like this:
+
+![repeated legend label chart](../../../user/project/integrations/img/prometheus_dashboard_repeated_label.png)
+
+For labels to be more explicit, using variables that reflect time series labels is a good practice. The variables will be replaced by the values of the time series labels when the legend is rendered:
+
+```yaml
+metrics:
+ - id: my_metric_id
+ query_range: 'http_requests_total'
+ label: "Instance: {{instance}}, method: {{method}}"
+ unit: "count"
+```
+
+The resulting rendered legend will look like this:
+
+![legend with label variables](../../../user/project/integrations/img/prometheus_dashboard_label_variables.png)
+
+There is also a shorthand value for dynamic dashboard labels that make use of only one time series label:
+
+```yaml
+metrics:
+ - id: my_metric_id
+ query_range: 'http_requests_total'
+ label: "Method"
+ unit: "count"
+```
+
+This works by lowercasing the value of `label` and, if there are more words separated by spaces, replacing those spaces with an underscore (`_`). The transformed value is then checked against the labels of the time series returned by the Prometheus query. If a time series label is found that is equal to the transformed value, then the label value will be used and rendered in the legend like this:
+
+![legend with label shorthand variable](../../../user/project/integrations/img/prometheus_dashboard_label_variable_shorthand.png)
+
+## Dashboard YAML syntax validation
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/33202) in GitLab 13.1.
+
+To confirm your dashboard definition contains valid YAML syntax:
+
+1. Navigate to **{doc-text}** **Repository > Files**.
+1. Navigate to your dashboard file in your repository.
+1. Review the information pane about the file, displayed above the file contents.
+
+Files with valid syntax display **Metrics Dashboard YAML definition is valid**,
+and files with invalid syntax display **Metrics Dashboard YAML definition is invalid**.
+
+![Metrics Dashboard_YAML_syntax_validation](../../../user/project/integrations/img/prometheus_dashboard_yaml_validation_v13_1.png)
+
+When **Metrics Dashboard YAML definition is invalid** at least one of the following messages is displayed:
+
+1. `dashboard: can't be blank` [learn more](../../../operations/metrics/dashboards/yaml.md#dashboard-top-level-properties)
+1. `panel_groups: should be an array of panel_groups objects` [learn more](../../../operations/metrics/dashboards/yaml.md#dashboard-top-level-properties)
+1. `group: can't be blank` [learn more](../../../operations/metrics/dashboards/yaml.md#panel-group-panel_groups-properties)
+1. `panels: should be an array of panels objects` [learn more](../../../operations/metrics/dashboards/yaml.md#panel-group-panel_groups-properties)
+1. `title: can't be blank` [learn more](../../../operations/metrics/dashboards/yaml.md#panel-panels-properties)
+1. `metrics: should be an array of metrics objects` [learn more](../../../operations/metrics/dashboards/yaml.md#panel-panels-properties)
+1. `query: can't be blank` [learn more](../../../operations/metrics/dashboards/yaml.md#metrics-metrics-properties)
+1. `query_range: can't be blank` [learn more](../../../operations/metrics/dashboards/yaml.md#metrics-metrics-properties)
+1. `unit: can't be blank` [learn more](../../../operations/metrics/dashboards/yaml.md#metrics-metrics-properties)
+1. `YAML syntax: The parsed YAML is too big`
+
+ This is displayed when the YAML file is larger than 1 MB.
+
+1. `YAML syntax: Invalid configuration format`
+
+ This is displayed when the YAML file is empty or does not contain valid YAML.
+
+Metrics Dashboard YAML definition validation information is also available as a [GraphQL API field](../../../api/graphql/reference/index.md#metricsdashboard)
diff --git a/doc/operations/metrics/dashboards/yaml_number_format.md b/doc/operations/metrics/dashboards/yaml_number_format.md
new file mode 100644
index 00000000000..ae0cd9fff64
--- /dev/null
+++ b/doc/operations/metrics/dashboards/yaml_number_format.md
@@ -0,0 +1,177 @@
+---
+stage: Monitor
+group: APM
+info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers
+---
+
+# Unit formats reference
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/201999) in GitLab 12.9.
+
+Format the data in your dashboard panels.
+
+You can select units to format your charts by adding `format` to your
+[axis configuration](yaml.md).
+
+## Internationalization and localization
+
+Currently, your [internationalization and localization options](https://en.wikipedia.org/wiki/Internationalization_and_localization) for number formatting are dependent on the system you are using i.e. your OS or browser.
+
+## Engineering Notation
+
+For generic or default data, numbers are formatted according to the current locale in [engineering notation](https://en.wikipedia.org/wiki/Engineering_notation).
+
+While an [engineering notation exists for the web](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat), GitLab uses a version based off the [scientific notation](https://en.wikipedia.org/wiki/Scientific_notation). GitLab formatting acts in accordance with SI prefixes. For example, using GitLab notation, `1500.00` becomes `1.5k` instead of `1.5E3`. Keep this distinction in mind when using the engineering notation for your metrics.
+
+Formats: `engineering`
+
+SI prefixes:
+
+| Name | Symbol | Value |
+| ---------- | ------- | -------------------------- |
+| `yotta` | Y | 1000000000000000000000000 |
+| `zetta` | Z | 1000000000000000000000 |
+| `exa` | E | 1000000000000000000 |
+| `peta` | P | 1000000000000000 |
+| `tera` | T | 1000000000000 |
+| `giga` | G | 1000000000 |
+| `mega` | M | 1000000 |
+| `kilo` | k | 1000 |
+| `milli` | m | 0.001 |
+| `micro` | μ | 0.000001 |
+| `nano` | n | 0.000000001 |
+| `pico` | p | 0.000000000001 |
+| `femto` | f | 0.000000000000001 |
+| `atto` | a | 0.000000000000000001 |
+| `zepto` | z | 0.000000000000000000001 |
+| `yocto` | y | 0.000000000000000000000001 |
+
+**Examples:**
+
+| Data | Displayed |
+| --------------------------------- | --------- |
+| `0.000000000000000000000008` | 8y |
+| `0.000000000000000000008` | 8z |
+| `0.000000000000000008` | 8a |
+| `0.000000000000008` | 8f |
+| `0.000000000008` | 8p |
+| `0.000000008` | 8n |
+| `0.000008` | 8μ |
+| `0.008` | 8m |
+| `10` | 10 |
+| `1080` | 1.08k |
+| `18000` | 18k |
+| `18888` | 18.9k |
+| `188888` | 189k |
+| `18888888` | 18.9M |
+| `1888888888` | 1.89G |
+| `1888888888888` | 1.89T |
+| `1888888888888888` | 1.89P |
+| `1888888888888888888` | 1.89E |
+| `1888888888888888888888` | 1.89Z |
+| `1888888888888888888888888` | 1.89Y |
+| `1888888888888888888888888888` | 1.89e+27 |
+
+## Numbers
+
+For number data, numbers are formatted according to the current locale.
+
+Formats: `number`
+
+**Examples:**
+
+| Data | Displayed |
+| ---------- | --------- |
+| `10` | 1 |
+| `1000` | 1,000 |
+| `1000000` | 1,000,000 |
+
+## Percentage
+
+For percentage data, format numbers in the chart with a `%` symbol.
+
+Formats supported: `percent`, `percentHundred`
+
+**Examples:**
+
+| Format | Data | Displayed |
+| ---------------- | ----- | --------- |
+| `percent` | `0.5` | 50% |
+| `percent` | `1` | 100% |
+| `percent` | `2` | 200% |
+| `percentHundred` | `50` | 50% |
+| `percentHundred` | `100` | 100% |
+| `percentHundred` | `200` | 200% |
+
+## Duration
+
+For time durations, format numbers in the chart with a time unit symbol.
+
+Formats supported: `milliseconds`, `seconds`
+
+**Examples:**
+
+| Format | Data | Displayed |
+| -------------- | ------ | --------- |
+| `milliseconds` | `10` | 10ms |
+| `milliseconds` | `500` | 100ms |
+| `milliseconds` | `1000` | 1000ms |
+| `seconds` | `10` | 10s |
+| `seconds` | `500` | 500s |
+| `seconds` | `1000` | 1000s |
+
+## Digital (Metric)
+
+Converts a number of bytes using metric prefixes. It scales to
+use the unit that's the best fit.
+
+Formats supported:
+
+- `decimalBytes`
+- `kilobytes`
+- `megabytes`
+- `gigabytes`
+- `terabytes`
+- `petabytes`
+
+**Examples:**
+
+| Format | Data | Displayed |
+| -------------- | --------- | --------- |
+| `decimalBytes` | `1` | 1B |
+| `decimalBytes` | `1000` | 1kB |
+| `decimalBytes` | `1000000` | 1MB |
+| `kilobytes` | `1` | 1kB |
+| `kilobytes` | `1000` | 1MB |
+| `kilobytes` | `1000000` | 1GB |
+| `megabytes` | `1` | 1MB |
+| `megabytes` | `1000` | 1GB |
+| `megabytes` | `1000000` | 1TB |
+
+## Digital (IEC)
+
+Converts a number of bytes using binary prefixes. It scales to
+use the unit that's the best fit.
+
+Formats supported:
+
+- `bytes`
+- `kibibytes`
+- `mebibytes`
+- `gibibytes`
+- `tebibytes`
+- `pebibytes`
+
+**Examples:**
+
+| Format | Data | Displayed |
+| ----------- | ------------- | --------- |
+| `bytes` | `1` | 1B |
+| `bytes` | `1024` | 1KiB |
+| `bytes` | `1024 * 1024` | 1MiB |
+| `kibibytes` | `1` | 1KiB |
+| `kibibytes` | `1024` | 1MiB |
+| `kibibytes` | `1024 * 1024` | 1GiB |
+| `mebibytes` | `1` | 1MiB |
+| `mebibytes` | `1024` | 1GiB |
+| `mebibytes` | `1024 * 1024` | 1TiB |
diff --git a/doc/operations/metrics/embed.md b/doc/operations/metrics/embed.md
new file mode 100644
index 00000000000..5ee9b0859b9
--- /dev/null
+++ b/doc/operations/metrics/embed.md
@@ -0,0 +1,93 @@
+---
+stage: Monitor
+group: APM
+info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers
+---
+
+# Embedding metric charts within GitLab Flavored Markdown
+
+## Embedding GitLab-managed Kubernetes metrics
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/29691) in GitLab 12.2.
+
+It is possible to display metrics charts within [GitLab Flavored Markdown](../../user/markdown.md#gitlab-flavored-markdown-gfm) fields such as issue or merge request descriptions. The maximum number of embedded charts allowed in a GitLab Flavored Markdown field is 100.
+
+This can be useful if you are sharing an application incident or performance
+metrics to others and want to have relevant information directly available.
+
+NOTE: **Note:**
+Requires [Kubernetes](../../user/project/integrations/prometheus_library/kubernetes.md) metrics.
+
+To display metric charts, include a link of the form `https://<root_url>/<project>/-/environments/<environment_id>/metrics`:
+
+![Embedded Metrics Markdown](../../user/project/integrations/img/embedded_metrics_markdown_v12_8.png)
+
+GitLab unfurls the link as an embedded metrics panel:
+
+![Embedded Metrics Rendered](../../user/project/integrations/img/embedded_metrics_rendered_v12_8.png)
+
+You can also embed a single chart. To get a link to a chart, click the
+**{ellipsis_v}** **More actions** menu in the upper right corner of the chart,
+and select **Copy link to chart**, as shown in this example:
+
+![Copy Link To Chart](../../user/project/integrations/img/copy_link_to_chart_v12_10.png)
+
+The following requirements must be met for the metric to unfurl:
+
+- The `<environment_id>` must correspond to a real environment.
+- Prometheus must be monitoring the environment.
+- The GitLab instance must be configured to receive data from the environment.
+- The user must be allowed access to the monitoring dashboard for the environment ([Reporter or higher](../../user/permissions.md)).
+- The dashboard must have data within the last 8 hours.
+
+ If all of the above are true, then the metric will unfurl as seen below:
+
+![Embedded Metrics](../../user/project/integrations/img/view_embedded_metrics_v12_10.png)
+
+Metric charts may also be hidden:
+
+![Show Hide](../../user/project/integrations/img/hide_embedded_metrics_v12_10.png)
+
+You can open the link directly into your browser for a [detailed view of the data](dashboards/index.md#expand-panel).
+
+## Embedding metrics in issue templates
+
+It is also possible to embed either the default dashboard metrics or individual metrics in issue templates. For charts to render side-by-side, links to the entire metrics dashboard or individual metrics should be separated by either a comma or a space.
+
+![Embedded Metrics in issue templates](../../user/project/integrations/img/embed_metrics_issue_template.png)
+
+## Embedding metrics based on alerts in incident issues
+
+For [GitLab-managed alerting rules](alerts.md), the issue will include an embedded chart for the query corresponding to the alert. The chart displays an hour of data surrounding the starting point of the incident, 30 minutes before and after.
+
+For [manually configured Prometheus instances](../../user/project/integrations/prometheus.md#manual-configuration-of-prometheus), a chart corresponding to the query can be included if these requirements are met:
+
+- The alert corresponds to an environment managed through GitLab.
+- The alert corresponds to a [range query](https://prometheus.io/docs/prometheus/latest/querying/api/#range-queries).
+- The alert contains the required attributes listed in the chart below.
+
+| Attributes | Required | Description |
+| ---------- | -------- | ----------- |
+| `annotations/gitlab_environment_name` | Yes | Name of the GitLab-managed environment corresponding to the alert |
+| One of `annotations/title`, `annotations/summary`, `labels/alertname` | Yes | Will be used as the chart title |
+| `annotations/gitlab_y_label` | No | Will be used as the chart's y-axis label |
+
+## Embedding cluster health charts
+
+> - [Introduced](<https://gitlab.com/gitlab-org/gitlab/-/issues/40997>) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 12.9.
+> - [Moved](<https://gitlab.com/gitlab-org/gitlab/-/issues/208224>) to GitLab core in 13.2.
+
+[Cluster Health Metrics](../../user/project/clusters/index.md#visualizing-cluster-health) can also be embedded in [GitLab-flavored Markdown](../../user/markdown.md).
+
+To embed a metric chart, include a link to that chart in the form `https://<root_url>/<project>/-/cluster/<cluster_id>?<query_params>` anywhere that GitLab-flavored Markdown is supported. To generate and copy a link to the chart, follow the instructions in the [Cluster Health Metric documentation](../../user/project/clusters/index.md#visualizing-cluster-health).
+
+The following requirements must be met for the metric to unfurl:
+
+- The `<cluster_id>` must correspond to a real cluster.
+- Prometheus must be monitoring the cluster.
+- The user must be allowed access to the project cluster metrics.
+- The dashboards must be reporting data on the [Cluster Health Page](../../user/project/clusters/index.md#visualizing-cluster-health)
+
+ If the above requirements are met, then the metric will unfurl as seen below.
+
+![Embedded Cluster Metric in issue descriptions](../../user/project/integrations/img/prometheus_cluster_health_embed_v12_9.png)
diff --git a/doc/operations/metrics/embed_grafana.md b/doc/operations/metrics/embed_grafana.md
new file mode 100644
index 00000000000..427ad866442
--- /dev/null
+++ b/doc/operations/metrics/embed_grafana.md
@@ -0,0 +1,65 @@
+---
+stage: Monitor
+group: APM
+info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers
+---
+
+# Embedding Grafana charts
+
+Grafana metrics can be embedded in [GitLab Flavored Markdown](../../user/markdown.md).
+
+## Embedding charts via Grafana Rendered Images
+
+It is possible to embed live [Grafana](https://docs.gitlab.com/omnibus/settings/grafana.html) charts in issues, as a [direct linked rendered image](https://grafana.com/docs/grafana/latest/reference/share_panel/#direct-link-rendered-image).
+
+The sharing dialog within Grafana provides the link, as highlighted below.
+
+![Grafana Direct Linked Rendered Image](../../user/project/integrations/img/grafana_live_embed.png)
+
+NOTE: **Note:**
+For this embed to display correctly, the Grafana instance must be available to the target user, either as a public dashboard, or on the same network.
+
+Copy the link and add an image tag as [inline HTML](../../user/markdown.md#inline-html) in your Markdown. You may tweak the query parameters as required. For instance, removing the `&from=` and `&to=` parameters will give you a live chart. Here is example markup for a live chart from GitLab's public dashboard:
+
+```html
+<img src="https://dashboards.gitlab.com/d/RZmbBr7mk/gitlab-triage?orgId=1&refresh=30s&var-env=gprd&var-environment=gprd&var-prometheus=prometheus-01-inf-gprd&var-prometheus_app=prometheus-app-01-inf-gprd&var-backend=All&var-type=All&var-stage=main&from=1580444107655&to=1580465707655"/>
+```
+
+This will render like so:
+
+![Grafana dashboard embedded preview](../../user/project/integrations/img/grafana_embedded.png)
+
+## Embedding charts via integration with Grafana HTTP API
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/31376) in GitLab 12.5.
+
+Each project can support integration with one Grafana instance. This configuration allows a user to copy a link to a panel in Grafana, then paste it into a GitLab Markdown field. The chart will be rendered in the GitLab chart format.
+
+Prerequisites for embedding from a Grafana instance:
+
+1. The datasource must be a Prometheus instance.
+1. The datasource must be proxyable, so the HTTP Access setting should be set to `Server`.
+
+![HTTP Proxy Access](../../user/project/integrations/img/http_proxy_access_v12_5.png)
+
+## Setting up the Grafana integration
+
+1. [Generate an Admin-level API Token in Grafana.](https://grafana.com/docs/grafana/latest/http_api/auth/#create-api-token)
+1. In your GitLab project, navigate to **Settings > Operations > Grafana Authentication**.
+1. To enable the integration, check the "Active" checkbox.
+1. For "Grafana URL", enter the base URL of the Grafana instance.
+1. For "API Token", enter the Admin API Token you just generated.
+1. Click **Save Changes**.
+
+## Generating a link to a chart
+
+1. In Grafana, navigate to the dashboard you wish to embed a panel from.
+ ![Grafana Metric Panel](../../user/project/integrations/img/grafana_panel_v12_5.png)
+1. In the upper-left corner of the page, select a specific value for each variable required for the queries in the chart.
+ ![Select Query Variables](../../user/project/integrations/img/select_query_variables_v12_5.png)
+1. In Grafana, click on a panel's title, then click **Share** to open the panel's sharing dialog to the **Link** tab. If you click the _dashboard's_ share panel instead, GitLab will attempt to embed the first supported panel on the dashboard (if available).
+1. If your Prometheus queries use Grafana's custom template variables, ensure that the "Template variables" option is toggled to **On**. Of Grafana global template variables, only `$__interval`, `$__from`, and `$__to` are currently supported. Toggle **On** the "Current time range" option to specify the time range of the chart. Otherwise, the default range will be the last 8 hours.
+ ![Grafana Sharing Dialog](../../user/project/integrations/img/grafana_sharing_dialog_v12_5.png)
+1. Click **Copy** to copy the URL to the clipboard.
+1. In GitLab, paste the URL into a Markdown field and save. The chart will take a few moments to render.
+ ![GitLab Rendered Grafana Panel](../../user/project/integrations/img/rendered_grafana_embed_v12_5.png)
diff --git a/doc/operations/metrics/img/example-dashboard_v13_1.png b/doc/operations/metrics/img/example-dashboard_v13_1.png
new file mode 100644
index 00000000000..0cda4ece689
--- /dev/null
+++ b/doc/operations/metrics/img/example-dashboard_v13_1.png
Binary files differ
diff --git a/doc/operations/metrics/index.md b/doc/operations/metrics/index.md
new file mode 100644
index 00000000000..12088884f44
--- /dev/null
+++ b/doc/operations/metrics/index.md
@@ -0,0 +1,142 @@
+---
+stage: Monitor
+group: APM
+info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers
+---
+
+# Monitor metrics for your CI/CD environment
+
+After [configuring Prometheus for a cluster](../../user/project/integrations/prometheus.md),
+GitLab attempts to retrieve performance metrics for any [environment](../../ci/environments/index.md) with
+a successful deployment.
+
+GitLab scans the Prometheus server for metrics from known servers like Kubernetes
+and NGINX, and attempts to identify individual environments. To learn more about
+the supported metrics and scan processes, see the
+[Prometheus Metrics Library documentation](../../user/project/integrations/prometheus_library/index.md).
+
+To view the metrics dashboard for an environment that has
+[completed at least one deployment](#populate-your-metrics-dashboard):
+
+1. *If the metrics dashboard is only visible to project members,* sign in to
+ GitLab as a member of a project. Learn more about [metrics dashboard visibility](#metrics-dashboard-visibility).
+1. In your project, navigate to **{cloud-gear}** **Operations > Metrics**.
+
+GitLab displays the default metrics dashboard for the environment, like the
+following example:
+
+![Example of metrics dashboard](img/example-dashboard_v13_1.png)
+
+The top of the dashboard contains a navigation bar. From left to right, the
+navigation bar contains:
+
+- **Dashboard** - A dropdown list of all dashboards available for the project,
+ with starred dashboards listed first.
+- **Environment** - A dropdown list of all [environments](../index.md) that searches
+ through all environments as you type.
+- **Range** - The time period of data to display.
+- **Refresh dashboard** **{retry}** - Reload the dashboard with current data.
+- **Set refresh rate** - Set a time frame for refreshing the data displayed.
+- **Star dashboard** **{star-o}** - Click to mark a dashboard as a favorite.
+ Starred dashboards display a solid star **{star}** button, and display first
+ in the **Dashboard** dropdown list.
+ ([Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/214582) in GitLab 13.0.)
+- **Create dashboard** **{file-addition-solid}** - Create a
+ [new custom dashboard for your project](dashboards/index.md#adding-a-new-dashboard-to-your-project).
+- **Metrics settings** **{settings}** - Configure the
+ [settings for this dashboard](dashboards/index.md#manage-the-metrics-dashboard-settings).
+
+## Populate your metrics dashboard
+
+After [configuring Prometheus for a cluster](../../user/project/integrations/prometheus.md),
+you must also deploy code for the **{cloud-gear}** **Operations > Metrics** page
+to contain data. Setting up [Auto DevOps](../../topics/autodevops/index.md)
+helps quickly create a deployment:
+
+1. Navigate to your project's **{cloud-gear}** **Operations > Kubernetes** page.
+1. Ensure that, in addition to Prometheus, you also have Runner and Ingress
+ installed.
+1. After installing Ingress, copy its endpoint.
+1. Navigate to your project's **{settings}** **Settings > CI/CD** page. In the
+ **Auto DevOps** section, select a deployment strategy and save your changes.
+1. On the same page, in the **Variables** section, add a variable named
+ `KUBE_INGRESS_BASE_DOMAIN` with the value of the Ingress endpoint you
+ copied previously. Leave the type as **Variable**.
+1. Navigate to your project's **{rocket}** **CI/CD > Pipelines** page, and run a
+ pipeline on any branch.
+1. When the pipeline has run successfully, graphs are available on the
+ **{cloud-gear}** **Operations > Metrics** page.
+
+![Monitoring Dashboard](../../user/project/integrations/img/prometheus_monitoring_dashboard_v13_1.png)
+
+## Customize your metrics dashboard
+
+After creating your dashboard, you can customize it to meet your needs:
+
+- **Add custom metrics**: In addition to the GitLab default metrics, you can
+ [create custom metrics](#adding-custom-metrics) and display them on your metrics dashboard.
+- **Configure alerts for metrics**: [Configure custom alerts](alerts.md) for your team when
+ environment performance falls outside of the boundaries you set.
+- **Trigger actions from alerts**: [Open new issues for your team](alerts.md#trigger-actions-from-alerts-ultimate) **(ULTIMATE)**
+ when environment performance falls outside of the boundaries you set.
+
+## Metrics dashboard visibility
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/201924) in GitLab 13.0.
+
+You can set the visibility of the metrics dashboard to **Only Project Members**
+or **Everyone With Access**. When set to **Everyone with Access**, the metrics
+dashboard is visible to authenticated and non-authenticated users.
+
+## Adding custom metrics
+
+> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/3799) in [GitLab Premium](https://about.gitlab.com/pricing/) 10.6.
+> - [Moved](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/28527) to [GitLab Core](https://about.gitlab.com/pricing/) 12.10.
+
+Custom metrics can be monitored by adding them on the monitoring dashboard page.
+After saving them, they display on the environment metrics dashboard provided that either:
+
+- A [connected Kubernetes cluster](../../user/project/clusters/add_remove_clusters.md)
+ with the matching [environment scope](../../ci/environments/index.md#scoping-environments-with-specs) is used and
+ [Prometheus installed on the cluster](../../user/project/integrations/prometheus.md#enabling-prometheus-integration).
+- Prometheus is [manually configured](../../user/project/integrations/prometheus.md#manual-configuration-of-prometheus).
+
+![Add New Metric](../../user/project/integrations/img/prometheus_add_metric.png)
+
+A few fields are required:
+
+- **Name**: Chart title
+- **Type**: Type of metric. Metrics of the same type will be shown together.
+- **Query**: Valid [PromQL query](https://prometheus.io/docs/prometheus/latest/querying/basics/).
+- **Y-axis label**: Y axis title to display on the dashboard.
+- **Unit label**: Query units, for example `req / sec`. Shown next to the value.
+
+Multiple metrics can be displayed on the same chart if the fields **Name**, **Type**,
+and **Y-axis label** match between metrics. For example, a metric with **Name**
+`Requests Rate`, **Type** `Business`, and **Y-axis label** `rec / sec` would display
+on the same chart as a second metric with the same values. A **Legend label** is
+suggested if this feature is used.
+
+## Editing additional metrics from the dashboard
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/208976) in GitLab 12.9.
+
+You can edit existing additional custom metrics for your dashboard by clicking the
+**{ellipsis_v}** **More actions** dropdown and selecting **Edit metric**.
+
+![Edit metric](../../user/project/integrations/img/prometheus_dashboard_edit_metric_link_v_12_9.png)
+
+## Keyboard shortcuts for charts
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/202146) in GitLab 13.2.
+
+You can use keyboard shortcuts to interact more quickly with your currently-focused
+chart panel. To activate keyboard shortcuts, use keyboard tabs to highlight the
+**{ellipsis_v}** **More actions** dropdown menu, or hover over the dropdown menu
+with your mouse, then press the key corresponding to your desired action:
+
+- **Expand panel** - <kbd>e</kbd>
+- **View logs** - <kbd>l</kbd> (lowercase 'L')
+- **Download CSV** - <kbd>d</kbd>
+- **Copy link to chart** - <kbd>c</kbd>
+- **Alerts** - <kbd>a</kbd>
diff --git a/doc/operations/tracing.md b/doc/operations/tracing.md
new file mode 100644
index 00000000000..07f60c37f1b
--- /dev/null
+++ b/doc/operations/tracing.md
@@ -0,0 +1,40 @@
+---
+stage: Monitor
+group: APM
+info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers
+---
+
+# Tracing **(ULTIMATE)**
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/7903) in GitLab Ultimate 11.5.
+
+Tracing provides insight into the performance and health of a deployed application,
+tracking each function or microservice which handles a given request.
+
+This makes it easy to
+understand the end-to-end flow of a request, regardless of whether you are using a monolithic or distributed system.
+
+## Jaeger tracing
+
+[Jaeger](https://www.jaegertracing.io/) is an open source, end-to-end distributed
+tracing system used for monitoring and troubleshooting microservices-based distributed
+systems.
+
+### Deploying Jaeger
+
+To learn more about deploying Jaeger, read the official
+[Getting Started documentation](https://www.jaegertracing.io/docs/latest/getting-started/).
+There is an easy to use [all-in-one Docker image](https://www.jaegertracing.io/docs/latest/getting-started/#AllinoneDockerimage),
+as well as deployment options for [Kubernetes](https://github.com/jaegertracing/jaeger-kubernetes)
+and [OpenShift](https://github.com/jaegertracing/jaeger-openshift).
+
+### Enabling Jaeger
+
+GitLab provides an easy way to open the Jaeger UI from within your project:
+
+1. [Set up Jaeger](https://www.jaegertracing.io) and configure your application using one of the
+ [client libraries](https://www.jaegertracing.io/docs/latest/client-libraries/).
+1. Navigate to your project's **Settings > Operations** and provide the Jaeger URL.
+1. Click **Save changes** for the changes to take effect.
+1. You can now visit **Operations > Tracing** in your project's sidebar and
+ GitLab will redirect you to the configured Jaeger URL.
diff --git a/doc/policy/maintenance.md b/doc/policy/maintenance.md
index bba16e491d0..5b3fc3e0021 100644
--- a/doc/policy/maintenance.md
+++ b/doc/policy/maintenance.md
@@ -2,15 +2,19 @@
type: concepts
---
-# GitLab Release and Maintenance Policy
+# GitLab release and maintenance policy
GitLab has strict policies governing version naming, as well as release pace for major, minor,
-patch, and security releases. New releases are usually announced on the [GitLab blog](https://about.gitlab.com/releases/categories/releases/).
+patch, and security releases. New releases are announced on the [GitLab blog](https://about.gitlab.com/releases/categories/releases/).
Our current policy is:
- Backporting bug fixes for **only the current stable release** at any given time. (See [patch releases](#patch-releases).)
-- Backporting **to the previous two monthly releases in addition to the current stable release**. (See [security releases](#security-releases).)
+- Backporting security fixes **to the previous two monthly releases in addition to the current stable release**. (See [security releases](#security-releases).)
+
+In rare cases, release managers may make an exception and backport to more than
+the last two monthly releases. See [Backporting to older
+releases](#backporting-to-older-releases) for more information.
## Versioning
@@ -19,8 +23,8 @@ GitLab uses [Semantic Versioning](https://semver.org/) for its releases:
For example, for GitLab version 12.10.6:
-- `12` represents the major version. The major release was 12.0.0, but often referred to as 12.0.
-- `10` represents the minor version. The minor release was 12.10.0, but often referred to as 12.10.
+- `12` represents the major version. The major release was 12.0.0 but often referred to as 12.0.
+- `10` represents the minor version. The minor release was 12.10.0 but often referred to as 12.10.
- `6` represents the patch number.
Any part of the version number can increment into multiple digits, for example, 13.10.11.
@@ -37,7 +41,7 @@ The following table describes the version types and their release cadence:
We encourage everyone to run the [latest stable release](https://about.gitlab.com/releases/categories/releases/)
to ensure that you can easily upgrade to the most secure and feature-rich GitLab experience.
-In order to make sure you can easily run the most recent stable release, we are working
+To make sure you can easily run the most recent stable release, we are working
hard to keep the update process simple and reliable.
If you are unable to follow our monthly release cycle, there are a couple of
@@ -66,7 +70,8 @@ one major version. For example, it is safe to:
- `9.5.5` -> `9.5.9`
- `8.9.2` -> `8.9.6`
-NOTE **Note** Version specific changes in Omnibus GitLab Linux packages can be found in [the Omnibus GitLab documentation](https://docs.gitlab.com/omnibus/update/README.html#version-specific-changes).
+NOTE: **Note:**
+Version specific changes in Omnibus GitLab Linux packages can be found in [the Omnibus GitLab documentation](https://docs.gitlab.com/omnibus/update/README.html#version-specific-changes).
NOTE: **Note:**
Instructions are available for downloading an Omnibus GitLab Linux package locally and [manually installing](https://docs.gitlab.com/omnibus/manual_install.html) it.
@@ -79,7 +84,7 @@ We cannot guarantee that upgrading between major versions will be seamless.
We suggest upgrading to the latest available *minor* version within
your major version before proceeding to the next major version.
Doing this will address any backward-incompatible changes or deprecations
-to help ensure a successful upgrade to next major release.
+to help ensure a successful upgrade to the next major release.
It's also important to ensure that any background migrations have been fully completed
before upgrading to a new major version. To see the current size of the `background_migration` queue,
@@ -107,17 +112,16 @@ Please see the table below for some examples:
| Target version | Your version | Recommended upgrade path | Note |
| --------------------- | ------------ | ------------------------ | ---- |
-| `13.2.0` | `11.5.0` | `11.5.0` -> `11.11.8` -> `12.0.12` -> `12.10.6` -> `13.0.0` -> `13.2.0` | Four intermediate versions are required: the final 11.11, 12.0, and 12.10 releases, plus 13.0. |
+| `13.2.0` | `11.5.0` | `11.5.0` -> `11.11.8` -> `12.0.12` -> `12.10.6` -> `13.0.0` -> `13.2.0` | Four intermediate versions are required: the final `11.11`, `12.0`, and `12.10` releases, plus `13.0`. |
| `13.0.1` | `11.10.8` | `11.10.5` -> `11.11.8` -> `12.0.12` -> `12.10.6` -> `13.0.1` | Three intermediate versions are required: `11.11`, `12.0`, and `12.10`. |
| `12.10.6` | `11.3.4` | `11.3.4` -> `11.11.8` -> `12.0.12` -> `12.10.6` | Two intermediate versions are required: `11.11` and `12.0` |
-| `12.9.5.` | `10.4.5` | `10.4.5` -> `10.8.7` -> `11.11.8` -> `12.0.12` -> `12.9.5` | Three intermediate versions are required: `10.8`, `11.11`, and `12.0`, then `12.9.5` |
+| `12.9.5` | `10.4.5` | `10.4.5` -> `10.8.7` -> `11.11.8` -> `12.0.12` -> `12.9.5` | Three intermediate versions are required: `10.8`, `11.11`, and `12.0`, then `12.9.5` |
| `12.2.5` | `9.2.6` | `9.2.6` -> `9.5.10` -> `10.8.7` -> `11.11.8` -> `12.0.12` -> `12.2.5` | Four intermediate versions are required: `9.5`, `10.8`, `11.11`, `12.0`, then `12.2`. |
| `11.3.4` | `8.13.4` | `8.13.4` -> `8.17.7` -> `9.5.10` -> `10.8.7` -> `11.3.4` | `8.17.7` is the last version in version 8, `9.5.10` is the last version in version 9, `10.8.7` is the last version in version 10. |
### Upgrades from versions earlier than 8.12
-- `8.11.x` and earlier: you might have to upgrade to `8.12.0` specifically before you can
- upgrade to `8.17.7`. This was [reported in an issue](https://gitlab.com/gitlab-org/gitlab/-/issues/207259).
+- `8.11.x` and earlier: you might have to upgrade to `8.12.0` specifically before you can upgrade to `8.17.7`. This was [reported in an issue](https://gitlab.com/gitlab-org/gitlab/-/issues/207259).
- [CI changes prior to version 8.0](https://docs.gitlab.com/omnibus/update/README.html#updating-gitlab-ci-from-prior-540-to-version-714-via-omnibus-gitlab)
when it was merged into GitLab.
@@ -155,11 +159,11 @@ and support cost.
1. Supporting parallel version discourages incremental upgrades which over time accumulate in
complexity and create upgrade challenges for all users. GitLab has a dedicated team ensuring that
incremental upgrades (and installations) are as simple as possible.
-1. The number of changes created in the GitLab application is high, which contributes to backporting complexity to older releases. In number of cases, backporting has to go through the same
+1. The number of changes created in the GitLab application is high, which contributes to backporting complexity to older releases. In several cases, backporting has to go through the same
review process a new change goes through.
-1. Ensuring that tests pass on older release is a considerable challenge in some cases, and as such is very time consuming.
+1. Ensuring that tests pass on the older release is a considerable challenge in some cases, and as such is very time-consuming.
-Including new features in patch releases is not possible as that would break [Semantic Versioning](https://semver.org/).
+Including new features in a patch release is not possible as that would break [Semantic Versioning](https://semver.org/).
Breaking [Semantic Versioning](https://semver.org/) has the following consequences for users that
have to adhere to various internal requirements (for example, org. compliance, verifying new features, and similar):
@@ -169,7 +173,7 @@ have to adhere to various internal requirements (for example, org. compliance, v
In cases where a strategic user has a requirement to test a feature before it is
officially released, we can offer to create a Release Candidate (RC) version that will
-include the specific feature. This should be needed only in extreme cases, and can be requested for
+include the specific feature. This should be needed only in extreme cases and can be requested for
consideration by raising an issue in the [release/tasks](https://gitlab.com/gitlab-org/release/tasks/-/issues/new?issuable_template=Backporting-request) issue tracker.
It is important to note that the Release Candidate will also contain other features and changes as
it is not possible to easily isolate a specific feature (similar reasons as noted above). The
@@ -178,8 +182,8 @@ accessible.
### Backporting to older releases
-Backporting to more than one stable release is reserved for [security releases](#security-releases).
-In some cases however, we may need to backport *a bug fix* to more than one stable
+Backporting to more than one stable release is normally reserved for [security releases](#security-releases).
+In some cases, however, we may need to backport *a bug fix* to more than one stable
release, depending on the severity of the bug.
The decision on whether backporting a change will be performed is done at the discretion of the
@@ -189,16 +193,13 @@ based on *all* of the following:
1. Estimated [severity](../development/contributing/issue_workflow.md#severity-labels) of the bug:
Highest possible impact to users based on the current definition of severity.
-
1. Estimated [priority](../development/contributing/issue_workflow.md#priority-labels) of the bug:
Immediate impact on all impacted users based on the above estimated severity.
-
1. Potentially incurring data loss and/or security breach.
-
1. Potentially affecting one or more strategic accounts due to a proven inability by the user to upgrade to the current stable version.
If *all* of the above are satisfied, the backport releases can be created for
-the current stable release, and two previous monthly releases.
+the current stable release, and two previous monthly releases. In rare cases a release manager may grant an exception to backport to more than two previous monthly releases.
For instance, if we release `11.2.1` with a fix for a severe bug introduced in
`11.0.0`, we could backport the fix to a new `11.0.x`, and `11.1.x` patch release.
@@ -220,12 +221,11 @@ This decision is made on a case-by-case basis.
Check [our release posts](https://about.gitlab.com/releases/categories/releases/).
Each month, we publish either a major or minor release of GitLab. At the end
-of those release posts there are three sections to look for: Deprecations, Removals, and Important notes on upgrading. These will include:
+of those release posts, there are three sections to look for: Deprecations, Removals, and Important notes on upgrading. These will include:
- Steps you need to perform as part of an upgrade.
For example [8.12](https://about.gitlab.com/releases/2016/09/22/gitlab-8-12-released/#upgrade-barometer)
- required the Elasticsearch index to be recreated. Any older version of GitLab upgrading to 8.12 or higher
- would require this.
+ required the Elasticsearch index to be recreated. Any older version of GitLab upgrading to 8.12 or higher would require this.
- Changes to the versions of software we support such as
[ceasing support for IE11 in GitLab 13](https://about.gitlab.com/releases/2020/03/22/gitlab-12-9-released/#ending-support-for-internet-explorer-11).
diff --git a/doc/public_access/public_access.md b/doc/public_access/public_access.md
index 3f93cd7f61f..848065de001 100644
--- a/doc/public_access/public_access.md
+++ b/doc/public_access/public_access.md
@@ -59,7 +59,7 @@ it. The restriction for visibility levels on the application setting level also
applies to groups, so if that's set to internal, the explore page will be empty
for anonymous users. The group page now has a visibility level icon.
-Admin users cannot create subgroups or projects with higher visibility level than that of the parent group.
+Admin users cannot create subgroups or projects with higher visibility level than that of the immediate parent group.
## Visibility of users
diff --git a/doc/raketasks/README.md b/doc/raketasks/README.md
index eaaf1ebed99..b7cfc18534b 100644
--- a/doc/raketasks/README.md
+++ b/doc/raketasks/README.md
@@ -29,9 +29,10 @@ The following are available Rake tasks:
| [Import repositories](import.md) | Import bare repositories into your GitLab instance. |
| [Import large project exports](../development/import_project.md#importing-via-a-rake-task) | Import large GitLab [project exports](../user/project/settings/import_export.md). |
| [Integrity checks](../administration/raketasks/check.md) | Check the integrity of repositories, files, and LDAP. |
-| [LDAP maintenance](../administration/raketasks/ldap.md) | [LDAP](../administration/auth/ldap/index.md)-related tasks. |
+| [LDAP maintenance](../administration/raketasks/ldap.md) | [LDAP](../administration/auth/ldap/index.md)-related tasks. |
| [List repositories](list_repos.md) | List of all GitLab-managed Git repositories on disk. |
| [Migrate Snippets to Git](migrate_snippets.md) | Migrate GitLab Snippets to Git repositories and show migration status |
+| [Praefect Rake tasks](../administration/raketasks/praefect.md) | [Praefect](../administration/gitaly/praefect.md)-related tasks. |
| [Project import/export](../administration/raketasks/project_import_export.md) | Prepare for [project exports and imports](../user/project/settings/import_export.md). |
| [Sample Prometheus data](generate_sample_prometheus_data.md) | Generate sample Prometheus data. |
| [Repository storage](../administration/raketasks/storage.md) | List and migrate existing projects and attachments from legacy storage to hashed storage. |
diff --git a/doc/raketasks/backup_restore.md b/doc/raketasks/backup_restore.md
index 18c1cba54f7..d8522cc19a0 100644
--- a/doc/raketasks/backup_restore.md
+++ b/doc/raketasks/backup_restore.md
@@ -1,4 +1,4 @@
-# Backing up and restoring GitLab **(CORE ONLY)**
+# Back up and restore GitLab **(CORE ONLY)**
GitLab provides Rake tasks for backing up and restoring GitLab instances.
@@ -26,14 +26,6 @@ installed on your system.
sudo yum install rsync
```
-- **Tar**: Backup and restore tasks use `tar` under the hood to create and extract
- archives. Ensure you have version 1.30 or above of `tar` available in your
- system. To check the version, run:
-
- ```shell
- tar --version
- ```
-
## Backup timestamp
NOTE: **Note:**
@@ -75,7 +67,7 @@ Use this command if you've installed GitLab with the Omnibus package:
sudo gitlab-backup create
```
-NOTE: **Note**
+NOTE: **Note:**
For GitLab 12.1 and earlier, use `gitlab-rake gitlab:backup:create`.
Use this if you've installed GitLab from source:
@@ -90,7 +82,7 @@ If you are running GitLab within a Docker container, you can run the backup from
docker exec -t <container name> gitlab-backup create
```
-NOTE: **Note**
+NOTE: **Note:**
For GitLab 12.1 and earlier, use `gitlab-rake gitlab:backup:create`.
If you are using the [GitLab Helm chart](https://gitlab.com/gitlab-org/charts/gitlab) on a
@@ -206,7 +198,7 @@ To use the `copy` strategy instead of the default streaming strategy, specify
sudo gitlab-backup create STRATEGY=copy
```
-NOTE: **Note**
+NOTE: **Note:**
For GitLab 12.1 and earlier, use `gitlab-rake gitlab:backup:create`.
#### Backup filename
@@ -221,7 +213,7 @@ By default a backup file is created according to the specification in [the Backu
sudo gitlab-backup create BACKUP=dump
```
-NOTE: **Note**
+NOTE: **Note:**
For GitLab 12.1 and earlier, use `gitlab-rake gitlab:backup:create`.
The resulting file will then be `dump_gitlab_backup.tar`. This is useful for systems that make use of rsync and incremental backups, and will result in considerably faster transfer speeds.
@@ -236,7 +228,7 @@ Note that the `--rsyncable` option in `gzip` is not guaranteed to be available o
sudo gitlab-backup create BACKUP=dump GZIP_RSYNCABLE=yes
```
-NOTE: **Note**
+NOTE: **Note:**
For GitLab 12.1 and earlier, use `gitlab-rake gitlab:backup:create`.
#### Excluding specific directories from the backup
@@ -264,7 +256,7 @@ For Omnibus GitLab packages:
sudo gitlab-backup create SKIP=db,uploads
```
-NOTE: **Note**
+NOTE: **Note:**
For GitLab 12.1 and earlier, use `gitlab-rake gitlab:backup:create`.
For installations from source:
@@ -466,7 +458,13 @@ For Omnibus GitLab packages:
gitlab_rails['backup_upload_connection'] = {
'provider' => 'Google',
'google_storage_access_key_id' => 'Access Key',
- 'google_storage_secret_access_key' => 'Secret'
+ 'google_storage_secret_access_key' => 'Secret',
+
+ ## If you have CNAME buckets (foo.example.com), you might run into SSL issues
+ ## when uploading backups ("hostname foo.example.com.storage.googleapis.com
+ ## does not match the server certificate"). In that case, uncomnent the following
+ ## setting. See: https://github.com/fog/fog/issues/2834
+ #'path_style' => true
}
gitlab_rails['backup_upload_remote_directory'] = 'my.google.bucket'
```
@@ -499,7 +497,7 @@ sudo gitlab-backup create DIRECTORY=daily
sudo gitlab-backup create DIRECTORY=weekly
```
-NOTE: **Note**
+NOTE: **Note:**
For GitLab 12.1 and earlier, use `gitlab-rake gitlab:backup:create`.
#### Uploading to locally mounted shares
@@ -517,7 +515,8 @@ backups will be copied to, and will be created if it does not exist. If the
directory that you want to copy the tarballs to is the root of your mounted
directory, just use `.` instead.
-NOTE: **Note:** Since file system performance may affect GitLab's overall performance, we do not recommend using EFS for storage. See the [relevant documentation](../administration/high_availability/nfs.md#avoid-using-awss-elastic-file-system-efs) for more details.
+NOTE: **Note:**
+Since file system performance may affect GitLab's overall performance, we do not recommend using EFS for storage. See the [relevant documentation](../administration/high_availability/nfs.md#avoid-using-awss-elastic-file-system-efs) for more details.
For Omnibus GitLab packages:
@@ -605,7 +604,7 @@ For Omnibus GitLab packages:
0 2 * * * /opt/gitlab/bin/gitlab-backup create CRON=1
```
- NOTE: **Note**
+ NOTE: **Note:**
For GitLab 12.1 and earlier, use `gitlab-rake gitlab:backup:create`.
For installations from source:
@@ -679,7 +678,7 @@ You can only restore a backup to **exactly the same version and type (CE/EE)** o
GitLab that you created it on, for example CE 9.1.0.
If your backup is a different version than the current installation, you will
-need to [downgrade your GitLab installation](https://docs.gitlab.com/omnibus/update/README.html#downgrading)
+need to [downgrade your GitLab installation](https://docs.gitlab.com/omnibus/update/README.html#downgrade)
before restoring the backup.
### Restore prerequisites
@@ -806,7 +805,7 @@ restore:
sudo gitlab-backup restore BACKUP=11493107454_2018_04_25_10.6.4-ce
```
-NOTE: **Note**
+NOTE: **Note:**
For GitLab 12.1 and earlier, use `gitlab-rake gitlab:backup:restore`.
CAUTION: **Warning:**
@@ -828,6 +827,12 @@ If there is a GitLab version mismatch between your backup tar file and the insta
version of GitLab, the restore command will abort with an error. Install the
[correct GitLab version](https://packages.gitlab.com/gitlab/) and try again.
+NOTE: **Note:**
+There is currently a [known issue](https://gitlab.com/gitlab-org/omnibus-gitlab/-/issues/3470) for restore not working
+with `pgbouncer`. In order to workaround the issue, the Rails node will need to bypass `pgbouncer` and connect
+directly to the primary database node. This can be done by setting `gitlab_rails['db_host']` and `gitlab_rails['port']`
+to connect to the primary database node and [reconfiguring GitLab](../administration/restart_gitlab.md#omnibus-gitlab-reconfigure).
+
### Restore for Docker image and GitLab Helm chart installations
For GitLab installations using the Docker image or the GitLab Helm chart on
@@ -845,10 +850,25 @@ backup location (default location is `/var/opt/gitlab/backups`).
For Docker installations, the restore task can be run from host:
```shell
-docker exec -it <name of container> gitlab-backup restore
+# Stop the processes that are connected to the database
+docker exec -it <name of container> gitlab-ctl stop unicorn
+docker exec -it <name of container> gitlab-ctl stop puma
+docker exec -it <name of container> gitlab-ctl stop sidekiq
+
+# Verify that the processes are all down before continuing
+docker exec -it <name of container> gitlab-ctl status
+
+# Run the restore
+docker exec -it <name of container> gitlab-backup restore BACKUP=11493107454_2018_04_25_10.6.4-ce
+
+# Restart the GitLab container
+docker restart <name of container>
+
+# Check GitLab
+docker exec -it <name of container> gitlab-rake gitlab:check SANITIZE=true
```
-NOTE: **Note**
+NOTE: **Note:**
For GitLab 12.1 and earlier, use `gitlab-rake gitlab:backup:restore`.
CAUTION: **Warning:**
@@ -859,6 +879,28 @@ use `gitlab-backup restore` to avoid this issue.
The GitLab Helm chart uses a different process, documented in
[restoring a GitLab Helm chart installation](https://gitlab.com/gitlab-org/charts/gitlab/blob/master/doc/backup-restore/restore.md).
+### Restoring only one or a few project(s) or group(s) from a backup
+
+While the Rake task used to restore a GitLab instance doesn't support
+restoring a single project or group, you can use a workaround by
+restoring your backup to a separate, temporary GitLab instance, and
+export your project or group from there:
+
+1. [Install a new GitLab](../install/README.md) instance at the same version as
+ the backed-up instance from which you want to restore.
+1. [Restore the backup](#restore-gitlab) into this new instance and
+ export your [project](../user/project/settings/import_export.md)
+ or [group](../user/group/settings/import_export.md). Make sure to
+ read the **Important Notes** on either export feature's documentation
+ to understand what will be exported and what not.
+1. Once the export is complete, go to the old instance and import it.
+1. After importing only the project(s) or group(s) that you wanted is complete,
+ you may delete the new, temporary GitLab instance.
+
+NOTE: **Note:**
+A feature request to provide direct restore of individual projects or groups
+is being discussed in [issue #17517](https://gitlab.com/gitlab-org/gitlab/-/issues/17517).
+
## Alternative backup strategies
If your GitLab server contains a lot of Git repository data you may find the GitLab backup script to be too slow.
@@ -914,7 +956,7 @@ Be advised that, backup is successfully restored in spite of these warnings.
The Rake task runs this as the `gitlab` user which does not have the superuser access to the database. When restore is initiated it will also run as `gitlab` user but it will also try to alter the objects it does not have access to.
Those objects have no influence on the database backup/restore but they give this annoying warning.
-For more information see similar questions on PostgreSQL issue tracker[here](https://www.postgresql.org/message-id/201110220712.30886.adrian.klaver@gmail.com) and [here](https://www.postgresql.org/message-id/2039.1177339749@sss.pgh.pa.us) as well as [stack overflow](https://stackoverflow.com/questions/4368789/error-must-be-owner-of-language-plpgsql).
+For more information see similar questions on PostgreSQL issue tracker [here](https://www.postgresql.org/message-id/201110220712.30886.adrian.klaver@gmail.com) and [here](https://www.postgresql.org/message-id/2039.1177339749@sss.pgh.pa.us) as well as [stack overflow](https://stackoverflow.com/questions/4368789/error-must-be-owner-of-language-plpgsql).
### When the secrets file is lost
diff --git a/doc/raketasks/cleanup.md b/doc/raketasks/cleanup.md
index 5bdae998ec9..cf4edea383b 100644
--- a/doc/raketasks/cleanup.md
+++ b/doc/raketasks/cleanup.md
@@ -2,7 +2,7 @@
GitLab provides Rake tasks for cleaning up GitLab instances.
-## Remove unreferenced LFS files from filesystem
+## Remove unreferenced LFS files
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/36628) in GitLab 12.10.
@@ -42,7 +42,7 @@ Note that this Rake task only removes the references to LFS files. Unreferenced
later (once a day). If you need to garbage collect them immediately, run
`rake gitlab:cleanup:orphan_lfs_files` described below.
-## Remove unreferenced LFS files
+### Remove unreferenced LFS files immediately
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/36628) in GitLab 12.10.
@@ -64,7 +64,11 @@ $ sudo gitlab-rake gitlab:cleanup:orphan_lfs_files
I, [2020-01-08T20:51:17.148765 #43765] INFO -- : Removed unreferenced LFS files: 12
```
-## Remove garbage from filesystem
+## Clean up project upload files
+
+Clean up project upload files if they don't exist in GitLab database.
+
+### Clean up project upload files from filesystem
> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/20863) in GitLab 11.2.
@@ -100,11 +104,11 @@ I, [2018-07-27T12:08:33.755624 #89817] INFO -- : Did fix /opt/gitlab/embedded/s
I, [2018-07-27T12:08:33.760257 #89817] INFO -- : Did move to lost and found /opt/gitlab/embedded/service/gitlab-rails/public/uploads/foo/bar/1dd6f0f7eefd2acc4c2233f89a0f7b0b/image.png -> /opt/gitlab/embedded/service/gitlab-rails/public/uploads/-/project-lost-found/foo/bar/1dd6f0f7eefd2acc4c2233f89a0f7b0b/image.png
```
-## Remove garbage from object storage
+### Clean up project upload files from object storage
> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/20918) in GitLab 11.2.
-Remove object store upload files if they don't exist in GitLab database.
+Move object store upload files to a lost and found directory if they don't exist in GitLab database.
```shell
# omnibus-gitlab
@@ -142,7 +146,7 @@ When you notice there are more job artifacts files on disk than there
should be, you can run:
```shell
-gitlab-rake gitlab:cleanup:orphan_job_artifact_files
+sudo gitlab-rake gitlab:cleanup:orphan_job_artifact_files
```
This command:
@@ -156,13 +160,13 @@ delete. Run the command with `DRY_RUN=false` if you actually want to
delete the files:
```shell
-gitlab-rake gitlab:cleanup:orphan_job_artifact_files DRY_RUN=false
+sudo gitlab-rake gitlab:cleanup:orphan_job_artifact_files DRY_RUN=false
```
You can also limit the number of files to delete with `LIMIT`:
```shell
-gitlab-rake gitlab:cleanup:orphan_job_artifact_files LIMIT=100
+sudo gitlab-rake gitlab:cleanup:orphan_job_artifact_files LIMIT=100
```
This will only delete up to 100 files from disk. You can use this to
diff --git a/doc/security/rack_attack.md b/doc/security/rack_attack.md
index 605b669d498..d3de2222c39 100644
--- a/doc/security/rack_attack.md
+++ b/doc/security/rack_attack.md
@@ -18,11 +18,13 @@ tracking.
For more information on how to use these options see the [Rack Attack README](https://github.com/kickstarter/rack-attack/blob/master/README.md).
-NOTE: **Note:** See
+NOTE: **Note:**
+See
[User and IP rate limits](../user/admin_area/settings/user_and_ip_rate_limits.md)
for simpler limits that are configured in the UI.
-NOTE: **Note:** Starting with GitLab 11.2, Rack Attack is disabled by default. If your
+NOTE: **Note:**
+Starting with GitLab 11.2, Rack Attack is disabled by default. If your
instance is not exposed to the public internet, it is recommended that you leave
Rack Attack disabled.
diff --git a/doc/security/rate_limits.md b/doc/security/rate_limits.md
index d58eb130522..af2c14be2cd 100644
--- a/doc/security/rate_limits.md
+++ b/doc/security/rate_limits.md
@@ -26,6 +26,7 @@ similarly mitigated by a rate limit.
- [User and IP rate limits](../user/admin_area/settings/user_and_ip_rate_limits.md).
- [Raw endpoints rate limits](../user/admin_area/settings/rate_limits_on_raw_endpoints.md).
- [Protected paths](../user/admin_area/settings/protected_paths.md).
+- [Import/Export rate limits](../user/admin_area/settings/import_export_rate_limits.md).
## Rack Attack initializer
diff --git a/doc/ssh/README.md b/doc/ssh/README.md
index 5db1bde6413..980a20ade3e 100644
--- a/doc/ssh/README.md
+++ b/doc/ssh/README.md
@@ -412,7 +412,7 @@ are *explicitly not supported* and may stop working at any time.
### Options for Microsoft Windows
-If you're running Windows 10, the [Windows Subsystem for Linux (WSL)](https://docs.microsoft.com/en-us/windows/wsl/install-win10), and its latest [WSL 2](https://docs.microsoft.com/en-us/install-win10) version,
+If you're running Windows 10, the [Windows Subsystem for Linux (WSL)](https://docs.microsoft.com/en-us/windows/wsl/install-win10), and its latest [WSL 2](https://docs.microsoft.com/en-us/windows/wsl/install-win10#update-to-wsl-2) version,
support the installation of different Linux distributions, which include the Git and SSH clients.
For current versions of Windows, you can also install the Git and SSH clients with
diff --git a/doc/subscriptions/index.md b/doc/subscriptions/index.md
index 0425fe98d27..64bf2e0b50b 100644
--- a/doc/subscriptions/index.md
+++ b/doc/subscriptions/index.md
@@ -22,13 +22,13 @@ When choosing a subscription, consider the following factors:
### Choosing a GitLab tier
-Pricing is [tier-based](https://about.gitlab.com/pricing/), allowing you to choose the features which fit your budget. See the [feature comparison](https://about.gitlab.com/pricing/gitlab-com/feature-comparison/) for information on what features are available at each tier.
+Pricing is [tier-based](https://about.gitlab.com/pricing/), allowing you to choose the features which fit your budget. See the [GitLab.com feature comparison](https://about.gitlab.com/pricing/gitlab-com/feature-comparison/) and the [self-managed feature comparison](https://about.gitlab.com/pricing/self-managed/feature-comparison/) for information on what features are available at each tier for each product.
### Choosing between GitLab.com or self-managed
There are some differences in how a subscription applies, depending if you use GitLab.com or a self-managed instance.
-- [GitLab.com](#gitlabcom): GitLab's software-as-a-service offering. You don't need to install anything to use GitLab.com, you only need to [sign up](https://gitlab.com/users/sign_in) and start using GitLab straight away.
+- [GitLab.com](#gitlabcom): GitLab's software-as-a-service offering. You don't need to install anything to use GitLab.com, you only need to [sign up](https://gitlab.com/users/sign_up) and start using GitLab straight away.
- [GitLab self-managed](#self-managed): Install, administer, and maintain your own GitLab instance.
On a self-managed instance, a GitLab subscription provides the same set of features for all users. On GitLab.com you can apply a subscription to either a group or a personal namespace.
@@ -103,10 +103,11 @@ To subscribe to GitLab.com:
1. Select the **Bronze**, **Silver**, or **Gold** GitLab.com plan through the
[Customers Portal](https://customers.gitlab.com/).
1. Link your GitLab.com account with your Customers Portal account.
- Once signed into the Customers Portal, if your account is not
+ Once a plan has been selected, if your account is not
already linked, you will be prompted to link your account with a
- **Link my GitLab Account** button.
-1. Associate the group with the subscription.
+ **Sign in to GitLab.com** button.
+1. Select the namespace from the drop-down list to associate the subscription.
+1. Proceed to checkout.
TIP: **Tip:**
You can also go to the [**My Account**](https://customers.gitlab.com/customers/edit)
@@ -129,19 +130,20 @@ instance, ensure you're purchasing enough seats to
With the [Customers Portal](https://customers.gitlab.com/) you can:
-- [Change billing information](#change-billing-information)
+- [Change billing and company information](#change-billing-information)
- [Change the payment method](#change-payment-method)
- [Change the linked account](#change-the-linked-account)
- [Change the associated namespace](#change-the-associated-namespace)
+- [Change customers portal account password](#change-customer-portal-account-password)
### Change billing information
To change billing information:
1. Log in to the [Customers Portal](https://customers.gitlab.com/customers/sign_in).
-1. Go to the **My Account** page.
+1. Select the **My account** drop-down and click on **Payment methods**.
1. Make the required changes to the **Account Details** information.
-1. Click **Update Account**.
+1. Click **Save changes**.
NOTE: **Note:**
Future purchases will use the information in this section.
@@ -159,17 +161,17 @@ To change payment method or update credit card information:
### Change the linked account
-To change the GitLab.com account associated with a Customers Portal
+To change the GitLab.com account associated with your Customers Portal
account:
1. Log in to the
[Customers Portal](https://customers.gitlab.com/customers/sign_in).
-1. Go to [GitLab.com](https://gitlab.com) in a separate browser tab. Ensure you
+1. In a separate browser tab, go to [GitLab.com](https://gitlab.com) and ensure you
are not logged in.
-1. On the Customers Portal page, click
- [**My Account**](https://customers.gitlab.com/customers/edit) in the top menu.
-1. Under **Your GitLab.com account**, click **Change linked account** button.
-1. Log in to the [GitLab.com](https://gitlab.com) account you want to link to the Customers Portal.
+1. On the Customers Portal page, click **My account > Account details**.
+1. Under **Your GitLab.com account**, click **Change linked account**.
+1. Log in to the [GitLab.com](https://gitlab.com) account you want to link to the Customers Portal
+ account.
### Change the associated namespace
@@ -177,12 +179,21 @@ With a linked GitLab.com account:
1. Log in to the [Customers Portal](https://customers.gitlab.com/customers/sign_in).
1. Navigate to the **Manage Purchases** page.
-1. Click **Change linked group**.
+1. Click **Change linked namespace**.
1. Select the desired group from the **This subscription is for** dropdown.
1. Click **Proceed to checkout**.
Subscription charges are calculated based on the total number of users in a group, including its subgroups and nested projects. If the total number of users exceeds the number of seats in your subscription, you will be charged for the additional users.
+### Change customer portal account password
+
+To change the password for this customers portal account:
+
+1. Log in to the [Customers Portal](https://customers.gitlab.com/customers/sign_in).
+1. Select the **My account** drop-down and click on **Account details**.
+1. Make the required changes to the **Your password** section.
+1. Click **Save changes**.
+
## View your subscription
### View your GitLab.com subscription
@@ -224,8 +235,8 @@ To renew your subscription, [prepare for renewal by reviewing your account](#pre
The [Customers Portal](https://customers.gitlab.com/customers/sign_in) is your tool for renewing and modifying your subscription. Before going ahead with renewal, log in and verify or update:
-- The invoice contact details on the **My Account** page.
-- The credit card on file in the **Payment Methods** page.
+- The invoice contact details on the **Account details** page.
+- The credit card on file on the **Payment Methods** page.
TIP: **Tip:**
Contact our [support team](https://support.gitlab.com/hc/en-us/requests/new?ticket_form_id=360000071293) if you need assistance accessing the Customers Portal or if you need to change the contact person who manages your subscription.
@@ -241,7 +252,7 @@ A GitLab subscription is valid for a specific number of users. For details, see
##### Purchase additional seats for GitLab.com
-There is no self-service option for purchasing additional seats. You must request a quotation from GitLab Sales. To do so, contact GitLab via our [support form](https://support.gitlab.com/hc/en-us/requests/new) and select **Licensing and Renewals Problems** from the menu.
+There is no self-service option for purchasing additional seats. You must request a quotation from GitLab Sales. To do so, contact GitLab via our [support form](https://support.gitlab.com/hc/en-us/requests/new?ticket_form_id=360000071293).
The amount charged per seat is calculated by one of the following methods:
@@ -255,8 +266,8 @@ Self-managed instances can add users to a subscription any time during the subsc
To add users to a subscription:
1. Log in to the [Customers Portal](https://customers.gitlab.com/).
-1. Select **Manage Purchases**.
-1. Select **Add more seats**.
+1. Navigate to the **Manage Purchases** page.
+1. Select **Add more seats** on the relevant subscription card.
1. Enter the number of additional users.
1. Select **Proceed to checkout**.
1. Review the **Subscription Upgrade Detail**. The system lists the total price for all users on the system and a credit for what you've already paid. You will only be charged for the net change.
@@ -264,7 +275,7 @@ To add users to a subscription:
The following will be emailed to you:
-- A payment receipt. You can also access this information in the Customers Portal under **Payment History**.
+- A payment receipt. You can also access this information in the Customers Portal under [**View invoices**](https://customers.gitlab.com/receipts).
- A new license. [Upload this license](../user/admin_area/license.md#uploading-your-license) to your instance to use it.
### Seat Link
@@ -273,7 +284,7 @@ The following will be emailed to you:
Seat Link allows us to provide our self-managed customers with prorated charges for user growth throughout the year using a quarterly reconciliation process.
-Seat Link sends to GitLab daily a count of all users in connected self-managed instances. That information is used to automate prorated reconciliations. The data is sent securely through an encrypted HTTPS connection.
+Seat Link daily sends a count of all users in connected self-managed instances to GitLab. That information is used to automate prorated reconciliations. The data is sent securely through an encrypted HTTPS connection.
Seat Link provides **only** the following information to GitLab:
@@ -333,7 +344,7 @@ Sg0KU1hNMGExaE9SVGR2V2pKQlBUMWNiaUo5DQo=',
You can view the exact JSON payload in the administration panel. To view the payload:
-1. Navigate to **Admin Area > Settings > Metrics and profiling** and expand **Seat Links**.
+1. Navigate to **Admin Area > Settings > Metrics and profiling** and expand **Seat Link**.
1. Click **Preview payload**.
#### Disable Seat Link
@@ -343,7 +354,7 @@ You can view the exact JSON payload in the administration panel. To view the pay
Seat Link is enabled by default.
To disable this feature, go to
-**{admin}** **Admin Area > Settings > Metrics and profiling** and clear the **Seat Link** checkbox.
+**{admin}** **Admin Area > Settings > Metrics and profiling**, uncheck the **Enable Seat Link** checkbox > **Save changes**.
To disable Seat Link in an Omnibus GitLab installation, and prevent it from
being configured in the future through the administration panel, set the following in
@@ -379,7 +390,7 @@ To view or change automatic subscription renewal (at the same tier as the previo
- If you see **Cancel subscription**, your subscription is set to automatically renew at the end of the subscription period. Click it to cancel automatic renewal.
With automatic renewal enabled, the subscription will automatically renew on the expiration date and there will be no gap in available service.
-An invoice will be generated for the renewal and available for viewing or download in the [Payment History](https://customers.gitlab.com/receipts) page. If you have difficulty during the renewal process, contact our [support team](https://support.gitlab.com/hc/en-us/requests/new?ticket_form_id=360000071293) for assistance.
+An invoice will be generated for the renewal and available for viewing or download in the [View invoices](https://customers.gitlab.com/receipts) page. If you have difficulty during the renewal process, contact our [support team](https://support.gitlab.com/hc/en-us/requests/new?ticket_form_id=360000071293) for assistance.
### Renew a self-managed subscription
@@ -410,10 +421,10 @@ We recommend following these steps during renewal:
| Users over license | The number of users that exceed the `Users in License` for the current license term. Charges for this number of users will be incurred at the next renewal. |
1. Review your renewal details and complete the payment process.
-1. A license for the renewal term will be available on the [Manage Purchases](https://customers.gitlab.com/subscriptions) page beneath your new subscription details.
-1. [Upload](../user/admin_area/license.md) your new license to your instance.
+1. A license for the renewal term will be available for download on the [Manage Purchases](https://customers.gitlab.com/subscriptions) page on the relevant subscription card. Select **Copy license to clipboard** or **Download license** to get a copy.
+1. [Upload](../user/admin_area/license.md#uploading-your-license) your new license to your instance.
-An invoice will be generated for the renewal and available for viewing or download in the [Payment History](https://customers.gitlab.com/receipts) page. If you have difficulty during the renewal process, contact our [support team](https://support.gitlab.com/hc/en-us/requests/new?ticket_form_id=360000071293) for assistance.
+An invoice will be generated for the renewal and available for viewing or download on the [View invoices](https://customers.gitlab.com/receipts) page. If you have difficulty during the renewal process, contact our [support team](https://support.gitlab.com/hc/en-us/requests/new?ticket_form_id=360000071293) for assistance.
## Upgrade your subscription tier
@@ -424,7 +435,7 @@ The process for upgrading differs depending on whether you're a GitLab.com or se
To upgrade your [GitLab tier](https://about.gitlab.com/pricing/):
1. Log in to the [Customers Portal](https://customers.gitlab.com/customers/sign_in).
-1. Select **Upgrade** under your subscription on the [My Account](https://customers.gitlab.com/subscriptions) page.
+1. Select the **Upgrade** button on the relevant subscription card on the [Manage purchases](https://customers.gitlab.com/subscriptions) page.
1. Select the desired upgrade.
1. Confirm the active form of payment, or add a new form of payment.
1. Check the **I accept the Privacy Policy and Terms of Service** checkbox.
@@ -436,8 +447,7 @@ When the purchase has been processed, you receive confirmation of your new subsc
To upgrade your [GitLab tier](https://about.gitlab.com/pricing/), contact our sales team as this
can't be done in the Customers Portal. You can either send an email to `renewals@gitlab.com`, or
-complete the [**Contact Sales**](https://about.gitlab.com/sales/) form. Include in your message
-details of which subscription you want to upgrade, and the desired tier.
+complete the [**Contact Sales**](https://about.gitlab.com/sales/) form. Include details of which subscription you want to upgrade and the desired tier in your message.
After messaging the sales team, the workflow is as follows:
@@ -458,7 +468,7 @@ If you renew or upgrade, your data will again be accessible.
### Self-managed GitLab data
-For self-managed customers, there is a two-week grace period when your features
+For self-managed customers, there is a 14-day grace period when your features
will continue to work as-is, after which the entire instance will become read
only.
@@ -467,19 +477,24 @@ features, and the instance will be read / write again.
## CI pipeline minutes
-CI pipeline minutes are the execution time for your [pipelines](../ci/pipelines/index.md) on GitLab's shared runners. Each [GitLab.com tier](https://about.gitlab.com/pricing/) includes a monthly quota of CI pipeline minutes.
+CI pipeline minutes are the execution time for your [pipelines](../ci/pipelines/index.md) on GitLab's shared runners. Each [GitLab.com tier](https://about.gitlab.com/pricing/) includes a monthly quota of CI pipeline minutes:
+
+- Free: 2,000 minutes
+- Bronze: 2,000 minutes
+- Silver: 10,000 minutes
+- Gold: 50,000 minutes
Quotas apply to:
-- Groups, where the minutes are shared across all members of the group, its subgroups, and nested projects. To view the group's usage, navigate to the group, then **{settings}** **Settings > Usage Quotas**.
-- Your personal account, where the minutes are available for your personal projects. To view and buy personal minutes, click your avatar, then **{settings}** **Settings > Pipeline quota**.
+- Groups, where the minutes are shared across all members of the group, its subgroups, and nested projects. To view the group's usage, navigate to the group, then **{settings}** **Settings** > **Usage Quotas**.
+- Your personal account, where the minutes are available for your personal projects. To view and buy personal minutes, click your avatar, then **{settings}** **Settings** > **[Usage Quotas](https://gitlab.com/profile/usage_quotas#pipelines-quota-tab)**.
Only pipeline minutes for GitLab shared runners are restricted. If you have a specific runner set up for your projects, there is no limit to your build time on GitLab.com.
The available quota is reset on the first of each calendar month at midnight UTC.
When the CI minutes are depleted, an email is sent automatically to notify the owner(s)
-of the group/namespace. You can [purchase additional CI minutes](#purchasing-additional-ci-minutes), or upgrade your account to [Silver or Gold](https://about.gitlab.com/pricing/). Your own runners can still be used even if you reach your limits.
+of the namespace. You can [purchase additional CI minutes](#purchasing-additional-ci-minutes), or upgrade your account to [Silver or Gold](https://about.gitlab.com/pricing/). Your own runners can still be used even if you reach your limits.
### Purchasing additional CI minutes
@@ -493,16 +508,20 @@ main quota. You can find pricing for additional CI/CD minutes in the [GitLab Cus
To purchase additional minutes for your group on GitLab.com:
1. From your group, go to **{settings}** **Settings > Usage Quotas**.
+1. Select **Buy additional minutes** and you will be directed to the Customers Portal.
1. Locate the subscription card that's linked to your group on GitLab.com, click **Buy more CI minutes**, and complete the details about the transaction.
-1. Once we have processed your payment, the extra CI minutes will be synced to your group.
+1. Once we have processed your payment, the extra CI minutes will be synced to your group namespace.
1. To confirm the available CI minutes, go to your group, then **{settings}** **Settings > Usage Quotas**.
+
The **Additional minutes** displayed now includes the purchased additional CI minutes, plus any minutes rolled over from last month.
To purchase additional minutes for your personal namespace:
-1. Click your avatar, then go to **Settings > Pipeline quota**.
-1. Locate the subscription card that's linked to your personal namespace on GitLab.com, click **Buy more CI minutes**, and complete the details about the transaction. Once we have processed your payment, the extra CI minutes will be synced to your Group.
-1. To confirm the available CI minutes for your personal projects, click your avatar, then go to **Settings > Pipeline quota**.
+1. Click your avatar, then go to **Settings > Usage Quotas**.
+1. Select **Buy additional minutes** and you will be directed to the Customers Portal.
+1. Locate the subscription card that's linked to your personal namespace on GitLab.com, click **Buy more CI minutes**, and complete the details about the transaction. Once we have processed your payment, the extra CI minutes will be synced to your personal namespace.
+1. To confirm the available CI minutes for your personal projects, click your avatar, then go to **Settings > Usage Quotas**.
+
The **Additional minutes** displayed now includes the purchased additional CI minutes, plus any minutes rolled over from last month.
Be aware that:
diff --git a/doc/tools/email.md b/doc/tools/email.md
index eacf63ecdcd..69aca200311 100644
--- a/doc/tools/email.md
+++ b/doc/tools/email.md
@@ -22,7 +22,9 @@ at their primary email address.
![admin users](email1.png)
1. Compose an email and choose where it will be sent (all users or users of a
- chosen group or project):
+ chosen group or project). The email body only supports plain text messages.
+ HTML, Markdown, and other rich text formats are not supported, and will be
+ sent as plain text to users.
![compose an email](email2.png)
diff --git a/doc/topics/autodevops/customize.md b/doc/topics/autodevops/customize.md
index 253d5e56463..2d6da4d322b 100644
--- a/doc/topics/autodevops/customize.md
+++ b/doc/topics/autodevops/customize.md
@@ -41,11 +41,16 @@ If your goal is to use only a single custom buildpack, you should provide the pr
## Custom `Dockerfile`
+> Support for `DOCKERFILE_PATH` was [added in GitLab 13.2](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/35662)
+
If your project has a `Dockerfile` in the root of the project repository, Auto DevOps
builds a Docker image based on the Dockerfile, rather than using buildpacks.
This can be much faster and result in smaller images, especially if your
Dockerfile is based on [Alpine](https://hub.docker.com/_/alpine/).
+If you set the `DOCKERFILE_PATH` CI variable, Auto Build looks for a Dockerfile there
+instead.
+
## Passing arguments to `docker build`
Arguments can be passed to the `docker build` command using the
@@ -213,7 +218,7 @@ include:
See the [Auto DevOps template](https://gitlab.com/gitlab-org/gitlab/blob/master/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml) for information on available jobs.
-CAUTION: **Deprecation**
+CAUTION: **Deprecation:**
Auto DevOps templates using the [`only`](../../ci/yaml/README.md#onlyexcept-basic) or
[`except`](../../ci/yaml/README.md#onlyexcept-basic) syntax will switch
to the [`rules`](../../ci/yaml/README.md#rules) syntax, starting in
@@ -238,7 +243,7 @@ postgres://user:password@postgres-host:postgres-port/postgres-database
### Upgrading PostgresSQL
-CAUTION: **Deprecation**
+CAUTION: **Deprecation:**
The variable `AUTO_DEVOPS_POSTGRES_CHANNEL` that controls default provisioned
PostgreSQL was changed to `2` in [GitLab 13.0](https://gitlab.com/gitlab-org/gitlab/-/issues/210499).
To keep using the old PostgreSQL, set the `AUTO_DEVOPS_POSTGRES_CHANNEL` variable to
@@ -306,11 +311,13 @@ applications.
| `AUTO_DEVOPS_CHART_REPOSITORY_USERNAME` | From GitLab 11.11, used to set a username to connect to the Helm repository. Defaults to no credentials. Also set `AUTO_DEVOPS_CHART_REPOSITORY_PASSWORD`. |
| `AUTO_DEVOPS_CHART_REPOSITORY_PASSWORD` | From GitLab 11.11, used to set a password to connect to the Helm repository. Defaults to no credentials. Also set `AUTO_DEVOPS_CHART_REPOSITORY_USERNAME`. |
| `AUTO_DEVOPS_DEPLOY_DEBUG` | From GitLab 13.1, if this variable is present, Helm will output debug logs. |
+| `AUTO_DEVOPS_ALLOW_TO_FORCE_DEPLOY_V<N>` | From [auto-deploy-image](https://gitlab.com/gitlab-org/cluster-integration/auto-deploy-image) v1.0.0, if this variable is present, a new major version of chart is forcibly deployed. [More details](upgrading_chart.md#ignore-warning-and-continue-deploying) |
| `AUTO_DEVOPS_MODSECURITY_SEC_RULE_ENGINE` | From GitLab 12.5, used in combination with [ModSecurity feature flag](../../user/clusters/applications.md#web-application-firewall-modsecurity) to toggle [ModSecurity's `SecRuleEngine`](https://github.com/SpiderLabs/ModSecurity/wiki/Reference-Manual-(v2.x)#SecRuleEngine) behavior. Defaults to `DetectionOnly`. |
| `BUILDPACK_URL` | Buildpack's full URL. Can point to either [a Git repository URL or a tarball URL](#custom-buildpacks). |
| `CANARY_ENABLED` | From GitLab 11.0, used to define a [deploy policy for canary environments](#deploy-policy-for-canary-environments-premium). |
| `CANARY_PRODUCTION_REPLICAS` | Number of canary replicas to deploy for [Canary Deployments](../../user/project/canary_deployments.md) in the production environment. Takes precedence over `CANARY_REPLICAS`. Defaults to 1. |
| `CANARY_REPLICAS` | Number of canary replicas to deploy for [Canary Deployments](../../user/project/canary_deployments.md). Defaults to 1. |
+| `DOCKERFILE_PATH` | From GitLab 13.2, allows overriding the [default Dockerfile path for the build stage](#custom-dockerfile) |
| `HELM_RELEASE_NAME` | From GitLab 12.1, allows the `helm` release name to be overridden. Can be used to assign unique release names when deploying multiple projects to a single namespace. |
| `HELM_UPGRADE_VALUES_FILE` | From GitLab 12.6, allows the `helm upgrade` values file to be overridden. Defaults to `.gitlab/auto-deploy-values.yaml`. |
| `HELM_UPGRADE_EXTRA_ARGS` | From GitLab 11.11, allows extra arguments in `helm` commands when deploying the application. Note that using quotes won't prevent word splitting. |
@@ -358,7 +365,8 @@ The following table lists variables used to disable jobs.
| `DAST_DISABLED` | From GitLab 11.0, used to disable the `dast` job. If the variable is present, the job won't be created. |
| `DEPENDENCY_SCANNING_DISABLED` | From GitLab 11.0, used to disable the `dependency_scanning` job. If the variable is present, the job won't be created. |
| `LICENSE_MANAGEMENT_DISABLED` | From GitLab 11.0, used to disable the `license_management` job. If the variable is present, the job won't be created. |
-| `PERFORMANCE_DISABLED` | From GitLab 11.0, used to disable the `performance` job. If the variable is present, the job won't be created. |
+| `PERFORMANCE_DISABLED` | From GitLab 11.0, used to disable the browser `performance` job. If the variable is present, the job won't be created. |
+| `LOAD_PERFORMANCE_DISABLED` | From GitLab 13.2, used to disable the `load_performance` job. If the variable is present, the job won't be created. |
| `REVIEW_DISABLED` | From GitLab 11.0, used to disable the `review` and the manual `review:stop` job. If the variable is present, these jobs won't be created. |
| `SAST_DISABLED` | From GitLab 11.0, used to disable the `sast` job. If the variable is present, the job won't be created. |
| `TEST_DISABLED` | From GitLab 11.0, used to disable the `test` job. If the variable is present, the job won't be created. |
@@ -445,7 +453,7 @@ QA testing:
environment:
name: qa
script:
- - deploy foo
+ - deploy foo
```
The track `foo` being referenced must also be defined in the application's Helm chart, like:
diff --git a/doc/topics/autodevops/index.md b/doc/topics/autodevops/index.md
index 767ea5ee7b7..01e61095fe2 100644
--- a/doc/topics/autodevops/index.md
+++ b/doc/topics/autodevops/index.md
@@ -104,16 +104,20 @@ knowledge of the following:
- [GitLab Runner](https://docs.gitlab.com/runner/)
- [Prometheus](https://prometheus.io/docs/introduction/overview/)
-Auto DevOps provides great defaults for all the stages; you can, however,
+Auto DevOps provides great defaults for all the stages and makes use of [CI templates](https://gitlab.com/gitlab-org/gitlab/-/tree/master/lib/gitlab/ci/templates); you can, however,
[customize](customize.md) almost everything to your needs.
For an overview on the creation of Auto DevOps, read more
[in this blog post](https://about.gitlab.com/blog/2017/06/29/whats-next-for-gitlab-ci/).
-NOTE: **Note**
+NOTE: **Note:**
Kubernetes clusters can [be used without](../../user/project/clusters/index.md)
Auto DevOps.
+## Kubernetes requirements
+
+See [Auto DevOps requirements for Kubernetes](requirements.md#auto-devops-requirements-for-kubernetes).
+
## Auto DevOps base domain
The Auto DevOps base domain is required to use
@@ -163,6 +167,10 @@ set the Auto DevOps base domain to `1.2.3.4.nip.io`.
After completing setup, all requests hit the load balancer, which routes requests
to the Kubernetes pods running your application.
+### AWS ECS
+
+See [Auto DevOps requirements for Amazon ECS](requirements.md#auto-devops-requirements-for-amazon-ecs).
+
## Enabling/Disabling Auto DevOps
When first using Auto DevOps, review the [requirements](#requirements) to ensure
@@ -185,7 +193,7 @@ If enabling, check that your project does not have a `.gitlab-ci.yml`, or if one
and choose the [deployment strategy](#deployment-strategy).
1. Click **Save changes** for the changes to take effect.
-After enabling the feature, an Auto DevOps pipeline is triggered on the default branch.
+After enabling the feature, an Auto DevOps pipeline is triggered on the `master` branch.
### At the group level
@@ -240,11 +248,11 @@ TIP: **Tip:**
Use the [blue-green deployment](../../ci/environments/incremental_rollouts.md#blue-green-deployment) technique
to minimize downtime and risk.
-## Using multiple Kubernetes clusters **(PREMIUM)**
+## Using multiple Kubernetes clusters
When using Auto DevOps, you can deploy different environments to
different Kubernetes clusters, due to the 1:1 connection
-[existing between them](../../user/project/clusters/index.md#multiple-kubernetes-clusters-premium).
+[existing between them](../../user/project/clusters/index.md#multiple-kubernetes-clusters).
The [Deploy Job template](https://gitlab.com/gitlab-org/gitlab/blob/master/lib/gitlab/ci/templates/Jobs/Deploy.gitlab-ci.yml)
used by Auto DevOps currently defines 3 environment names:
@@ -271,8 +279,8 @@ To add a different cluster for each environment:
1. Navigate to your project's **{cloud-gear}** **Operations > Kubernetes**.
1. Create the Kubernetes clusters with their respective environment scope, as
described from the table above.
-1. After creating the clusters, navigate to each cluster and install Helm Tiller
- and Ingress. Wait for the Ingress IP address to be assigned.
+1. After creating the clusters, navigate to each cluster and install
+ Ingress. Wait for the Ingress IP address to be assigned.
1. Make sure you've [configured your DNS](#auto-devops-base-domain) with the
specified Auto DevOps domains.
1. Navigate to each cluster's page, through **{cloud-gear}** **Operations > Kubernetes**,
@@ -293,9 +301,9 @@ No documented way of using private container registry with Auto DevOps exists.
We strongly advise using GitLab Container Registry with Auto DevOps to
simplify configuration and prevent any unforeseen issues.
-### Installing Helm behind a proxy
+### Install applications behind a proxy
-GitLab does not support installing [Helm as a GitLab-managed App](../../user/clusters/applications.md#helm) when
+GitLab's Helm integration does not support installing applications when
behind a proxy. Users who want to do so must inject their proxy settings
into the installation pods at runtime, such as by using a
[`PodPreset`](https://kubernetes.io/docs/concepts/workloads/pods/podpreset/):
@@ -358,6 +366,55 @@ Auto Deploy will fail if GitLab can't create a Kubernetes namespace and
service account for your project. For help debugging this issue, see
[Troubleshooting failed deployment jobs](../../user/project/clusters/index.md#troubleshooting).
+### Detected an existing PostgreSQL database
+
+After upgrading to GitLab 13.0, you may encounter this message when deploying
+with Auto DevOps:
+
+```plaintext
+Detected an existing PostgreSQL database installed on the
+deprecated channel 1, but the current channel is set to 2. The default
+channel changed to 2 in of GitLab 13.0.
+[...]
+```
+
+Auto DevOps, by default, installs an in-cluster PostgreSQL database alongside
+your application. The default installation method changed in GitLab 13.0, and
+upgrading existing databases requires user involvement. The two installation
+methods are:
+
+- **channel 1 (deprecated):** Pulls in the database as a dependency of the associated
+ Helm chart. Only supports Kubernetes versions up to version 1.15.
+- **channel 2 (current):** Installs the database as an independent Helm chart. Required
+ for using the in-cluster database feature with Kubernetes versions 1.16 and greater.
+
+If you receive this error, you can do one of the following actions:
+
+- You can *safely* ignore the warning and continue using the channel 1 PostgreSQL
+ database by setting `AUTO_DEVOPS_POSTGRES_CHANNEL` to `1` and redeploying.
+
+- You can delete the channel 1 PostgreSQL database and install a fresh channel 2
+ database by setting `AUTO_DEVOPS_POSTGRES_DELETE_V1` to a non-empty value and
+ redeploying.
+
+ DANGER: **Danger:**
+ Deleting the channel 1 PostgreSQL database permanently deletes the existing
+ channel 1 database and all its data. See
+ [Upgrading PostgreSQL](upgrading_postgresql.md)
+ for more information on backing up and upgrading your database.
+
+- If you are not using the in-cluster database, you can set
+ `POSTGRES_ENABLED` to `false` and re-deploy. This option is especially relevant to
+ users of *custom charts without the in-chart PostgreSQL dependency*.
+ Database auto-detection is based on the `postgresql.enabled` Helm value for
+ your release. This value is set based on the `POSTGRES_ENABLED` CI variable
+ and persisted by Helm, regardless of whether or not your chart uses the
+ variable.
+
+DANGER: **Danger:**
+Setting `POSTGRES_ENABLED` to `false` permanently deletes any existing
+channel 1 database for your environment.
+
## Development guides
[Development guide for Auto DevOps](../../development/auto_devops.md)
diff --git a/doc/topics/autodevops/quick_start_guide.md b/doc/topics/autodevops/quick_start_guide.md
index ec5191dd4ac..4f8074f047e 100644
--- a/doc/topics/autodevops/quick_start_guide.md
+++ b/doc/topics/autodevops/quick_start_guide.md
@@ -98,24 +98,10 @@ status on your [GCP dashboard](https://console.cloud.google.com/kubernetes).
Next, you will install some applications on your cluster that are needed
to take full advantage of Auto DevOps.
-## Install the package manager
-
-After creating your Kubernetes cluster, GitLab's Kubernetes integration provides
-[pre-defined applications](../../user/project/clusters/index.md#installing-applications)
-for you to install. To install them, you must next install Helm Tiller, the
-Kubernetes package manager for Kubernetes, to enable the installation of other applications.
-
-Next to **Helm Tiller**, click **Install**.
-
-![Cluster applications](img/guide_cluster_apps_v12_3.png)
-
-After installation completes, the page reloads, and you can install other
-applications.
-
## Install Ingress and Prometheus
-After installing **Helm Tiller**, you can install other applications that rely on it,
-including Ingress and Prometheus, which we will install in this quick start guide:
+After your cluster is running, you can install your first applications.
+In this guide, we will install Ingress and Prometheus:
- Ingress - Provides load balancing, SSL termination, and name-based virtual hosting,
using NGINX behind the scenes.
@@ -305,7 +291,7 @@ all within GitLab. Despite its automatic nature, Auto DevOps can also be configu
and customized to fit your workflow. Here are some helpful resources for further reading:
1. [Auto DevOps](index.md)
-1. [Multiple Kubernetes clusters](index.md#using-multiple-kubernetes-clusters-premium) **(PREMIUM)**
+1. [Multiple Kubernetes clusters](index.md#using-multiple-kubernetes-clusters)
1. [Incremental rollout to production](customize.md#incremental-rollout-to-production-premium) **(PREMIUM)**
1. [Disable jobs you don't need with environment variables](customize.md#environment-variables)
1. [Use a static IP for your cluster](../../user/clusters/applications.md#using-a-static-ip)
diff --git a/doc/topics/autodevops/requirements.md b/doc/topics/autodevops/requirements.md
index b09a571fd16..33db94be97e 100644
--- a/doc/topics/autodevops/requirements.md
+++ b/doc/topics/autodevops/requirements.md
@@ -103,22 +103,26 @@ After all requirements are met, you can [enable Auto DevOps](index.md#enablingdi
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/208132) in GitLab 13.0.
-You can choose to target [Amazon Elastic Container Service (ECS)](../../ci/cloud_deployment/index.md) as a deployment platform instead of using Kubernetes.
+You can choose to target [AWS ECS](../../ci/cloud_deployment/index.md) as a deployment platform instead of using Kubernetes.
-To get started on Auto DevOps to Amazon ECS, you'll have to add a specific Environment
+To get started on Auto DevOps to AWS ECS, you'll have to add a specific Environment
Variable. To do so, follow these steps:
1. In your project, go to **Settings > CI / CD** and expand the **Variables**
section.
1. Specify which AWS platform to target during the Auto DevOps deployment
- by adding the `AUTO_DEVOPS_PLATFORM_TARGET` variable.
+ by adding the `AUTO_DEVOPS_PLATFORM_TARGET` variable with one of the following values:
+ - `FARGATE` if the service you're targeting must be of launch type FARGATE.
+ - `ECS` if you're not enforcing any launch type check when deploying to ECS.
-1. Give this variable the value `ECS` before saving it.
-
-When you trigger a pipeline, if Auto DevOps is enabled and if you've correctly
+When you trigger a pipeline, if you have Auto DevOps enabled and if you have correctly
[entered AWS credentials as environment variables](../../ci/cloud_deployment/index.md#deploy-your-application-to-the-aws-elastic-container-service-ecs),
-your application will be deployed to Amazon ECS.
+your application will be deployed to AWS ECS.
+
+NOTE: **Note:**
+[GitLab Managed Apps](../../user/clusters/applications.md) are not available when deploying to AWS ECS.
+You must manually configure your application (such as Ingress or Help) on AWS ECS.
NOTE: **Note:**
If you have both a valid `AUTO_DEVOPS_PLATFORM_TARGET` variable and a Kubernetes cluster tied to your project,
@@ -130,5 +134,5 @@ defined in the [`Jobs/Deploy/ECS.gitlab-ci.yml` template](https://gitlab.com/git
However, it's not recommended to [include](../../ci/yaml/README.md#includetemplate)
it on its own. This template is designed to be used with Auto DevOps only. It may change
unexpectedly causing your pipeline to fail if included on its own. Also, the job
-names within this template may also change. Don't override these jobs' names in your
+names within this template may also change. Do not override these jobs' names in your
own pipeline, as the override will stop working when the name changes.
diff --git a/doc/topics/autodevops/stages.md b/doc/topics/autodevops/stages.md
index 0c7c4919431..bf1594130f4 100644
--- a/doc/topics/autodevops/stages.md
+++ b/doc/topics/autodevops/stages.md
@@ -72,7 +72,8 @@ Heroku buildpacks, with the following caveats:
- The `/bin/herokuish` command is not present in the resulting image, and prefixing
commands with `/bin/herokuish procfile exec` is no longer required (nor possible).
-NOTE: **Note**: Auto Test still uses Herokuish, as test suite detection is not
+NOTE: **Note:**
+Auto Test still uses Herokuish, as test suite detection is not
yet part of the Cloud Native Buildpack specification. For more information, see
[this issue](https://gitlab.com/gitlab-org/gitlab/-/issues/212689).
@@ -294,7 +295,8 @@ You can disable DAST:
> Introduced in [GitLab Premium](https://about.gitlab.com/pricing/) 10.4.
-Auto Browser Performance Testing measures the performance of a web page with the
+Auto [Browser Performance Testing](../../user/project/merge_requests/browser_performance_testing.md)
+measures the browser performance of a web page with the
[Sitespeed.io container](https://hub.docker.com/r/sitespeedio/sitespeed.io/),
creates a JSON report including the overall performance score for each page, and
uploads the report as an artifact. By default, it tests the root page of your Review and
@@ -307,9 +309,26 @@ file named `.gitlab-urls.txt` in the root directory, one file per line. For exam
/direction
```
-Any performance differences between the source and target branches are also
+Any browser performance differences between the source and target branches are also
[shown in the merge request widget](../../user/project/merge_requests/browser_performance_testing.md).
+## Auto Load Performance Testing **(PREMIUM)**
+
+> Introduced in [GitLab Premium](https://about.gitlab.com/pricing/) 13.2.
+
+Auto [Load Performance Testing](../../user/project/merge_requests/load_performance_testing.md)
+measures the server performance of an application with the
+[k6 container](https://hub.docker.com/r/loadimpact/k6/),
+creates a JSON report including several key result metrics, and
+uploads the report as an artifact.
+
+Some initial setup is required. A [k6](https://k6.io/) test needs to be
+written that's tailored to your specific application. The test also needs to be
+configured so it can pick up the environment's dynamic URL via an environment variable.
+
+Any load performance test result differences between the source and target branches are also
+[shown in the merge request widget](../../user/project/merge_requests/load_performance_testing.md).
+
## Auto Deploy
This is an optional step, since many projects don't have a Kubernetes cluster
@@ -372,7 +391,7 @@ as it attempts to fetch the image using `CI_REGISTRY_PASSWORD`.
> - Support for deploying a PostgreSQL version that supports Kubernetes 1.16+ was [introduced](https://gitlab.com/gitlab-org/cluster-integration/auto-deploy-image/-/merge_requests/49) in GitLab 12.9.
> - Supported out of the box for new deployments as of GitLab 13.0.
-CAUTION: **Deprecation**
+CAUTION: **Deprecation:**
The default value for the `deploymentApiVersion` setting was changed from
`extensions/v1beta` to `apps/v1` in [GitLab 13.0](https://gitlab.com/gitlab-org/charts/auto-deploy-app/-/issues/47).
@@ -398,7 +417,8 @@ To use Auto Deploy on a Kubernetes 1.16+ cluster:
1. If you are deploying your application for the first time and are using
GitLab 12.9 or 12.10, set `AUTO_DEVOPS_POSTGRES_CHANNEL` to `2`.
-DANGER: **Danger:** On GitLab 12.9 and 12.10, opting into
+DANGER: **Danger:**
+On GitLab 12.9 and 12.10, opting into
`AUTO_DEVOPS_POSTGRES_CHANNEL` version `2` deletes the version `1` PostgreSQL
database. Follow the [guide to upgrading PostgreSQL](upgrading_postgresql.md)
to back up and restore your database before opting into version `2` (On
@@ -437,6 +457,10 @@ Herokuish, and you must prefix commands run in these images with
`/bin/herokuish procfile exec` to replicate the environment where your application
will run.
+### Upgrade auto-deploy-app Chart
+
+You can upgrade auto-deploy-app chart by following the [upgrade guide](upgrading_chart.md).
+
### Workers
Some web applications must run extra deployments for "worker processes". For
@@ -469,16 +493,16 @@ workers:
sidekiq:
replicaCount: 1
command:
- - /bin/herokuish
- - procfile
- - exec
- - sidekiq
+ - /bin/herokuish
+ - procfile
+ - exec
+ - sidekiq
preStopCommand:
- - /bin/herokuish
- - procfile
- - exec
- - sidekiqctl
- - quiet
+ - /bin/herokuish
+ - procfile
+ - exec
+ - sidekiqctl
+ - quiet
terminationGracePeriodSeconds: 60
```
@@ -524,12 +548,12 @@ networkPolicy:
matchLabels:
app.gitlab.com/env: staging
ingress:
- - from:
- - podSelector:
- matchLabels: {}
- - namespaceSelector:
- matchLabels:
- app.gitlab.com/managed_by: gitlab
+ - from:
+ - podSelector:
+ matchLabels: {}
+ - namespaceSelector:
+ matchLabels:
+ app.gitlab.com/managed_by: gitlab
```
For more information on installing Network Policies, see
diff --git a/doc/topics/autodevops/upgrading_chart.md b/doc/topics/autodevops/upgrading_chart.md
new file mode 100644
index 00000000000..e4dacdfcf5b
--- /dev/null
+++ b/doc/topics/autodevops/upgrading_chart.md
@@ -0,0 +1,72 @@
+# Upgrading auto-deploy-app chart for Auto DevOps
+
+Auto DevOps provides the auto-deploy-app chart for deploying your application to the
+Kubernetes cluster with Helm/Tiller. Major version changes of this chart could have
+a significantly different resource architecture, and may not be backwards compatible.
+
+This guide provides instructions on how to upgrade your deployments to use the latest
+chart and resource architecture.
+
+## Compatibility
+
+The following table lists the version compatibility between GitLab and [auto-deploy-image](https://gitlab.com/gitlab-org/cluster-integration/auto-deploy-image) (with the [auto-deploy-app chart](https://gitlab.com/gitlab-org/cluster-integration/auto-deploy-image/-/tree/master/assets/auto-deploy-app)).
+
+| GitLab version | auto-deploy-image version | Notes |
+|------------------|---------------------------|--------------------------------------------|
+| v10.0 and higher | v0.1.0 and higher | v0 and v1 charts are backwards compatible. |
+
+## Upgrade Guide
+
+The Auto DevOps project must use the unmodified chart managed by GitLab.
+[Customized charts](customize.md#custom-helm-chart) are unsupported.
+
+### v1 chart
+
+The v1 chart is backward compatible with the v0 chart, so no configuration changes are needed.
+
+## Troubleshooting
+
+### Major version mismatch warning
+
+If deploying a chart that has a major version that is different from the previous one,
+the new chart might not be correctly deployed. This could be due to an architectural
+change. If that happens, the deployment job fails with a message similar to:
+
+```plaintext
+*************************************************************************************
+ [WARNING]
+Detected a major version difference between the the chart that is currently deploying (auto-deploy-app-v0.7.0), and the previously deployed chart (auto-deploy-app-v1.0.0).
+A new major version might not be backward compatible with the current release (production). The deployment could fail or be stuck in an unrecoverable status.
+...
+```
+
+To clear this error message and resume deployments, you must do one of the following:
+
+- Manually [upgrade the chart version](#upgrade-guide).
+- [Use a specific chart version](#use-a-specific-chart-version).
+
+#### Use a specific chart version
+
+To use a specific chart version, you must specify a corresponding version of [auto-deploy-image](https://gitlab.com/gitlab-org/cluster-integration/auto-deploy-image).
+Do this by [customizing the image in your `.gitlab-ci.yml`](customize.md#customizing-gitlab-ciyml).
+
+For example, create the following `.gitlab-ci.yml` file in the project. It configures Auto DevOps
+to use [auto-deploy-image](https://gitlab.com/gitlab-org/cluster-integration/auto-deploy-image) version `v0.17.0`
+for deployment jobs. It will download the chart from [chart repository](https://charts.gitlab.io/):
+
+```yaml
+include:
+ - template: Auto-DevOps.gitlab-ci.yml
+
+.auto-deploy:
+ image: "registry.gitlab.com/gitlab-org/cluster-integration/auto-deploy-image:v0.17.0"
+```
+
+### Ignore warning and continue deploying
+
+If you are certain that the new chart version is safe to be deployed,
+you can add the `AUTO_DEVOPS_ALLOW_TO_FORCE_DEPLOY_V<N>` [environment variable](customize.md#build-and-deployment)
+to force the deployment to continue, where `<N>` is the major version.
+
+For example, if you want to deploy the v2.0.0 chart on a deployment that previously
+used the v0.17.0 chart, add `AUTO_DEVOPS_ALLOW_TO_FORCE_DEPLOY_V2`.
diff --git a/doc/topics/autodevops/upgrading_postgresql.md b/doc/topics/autodevops/upgrading_postgresql.md
index 893f7ba7cde..2ebe362280f 100644
--- a/doc/topics/autodevops/upgrading_postgresql.md
+++ b/doc/topics/autodevops/upgrading_postgresql.md
@@ -26,8 +26,12 @@ involves:
This varies based on Kubernetes providers.
1. Prepare for downtime. The steps below include taking the application offline
so that the in-cluster database does not get modified after the database dump is created.
+1. Ensure you have not set `POSTGRES_ENABLED` to `false`, as this setting deletes
+ any existing channel 1 database. For more information, see
+ [Detected an existing PostgreSQL database](index.md#detected-an-existing-postgresql-database).
-TIP: **Tip:** If you have configured Auto DevOps to have staging,
+TIP: **Tip:**
+If you have configured Auto DevOps to have staging,
consider trying out the backup and restore steps on staging first, or
trying this out on a review app.
@@ -158,12 +162,13 @@ pvc-9085e3d3-5239-11ea-9c8d-42010a8e0096 8Gi RWO Retain
## Install new PostgreSQL
-CAUTION: **Caution:** Using the newer version of PostgreSQL will delete
+CAUTION: **Caution:**
+Using the newer version of PostgreSQL will delete
the older 0.7.1 PostgreSQL. To prevent the underlying data from being
-deleted, you can choose to retain the [persistent
-volume](#retain-persistent-volumes).
+deleted, you can choose to retain the [persistent volume](#retain-persistent-volumes).
-TIP: **Tip:** You can also
+TIP: **Tip:**
+You can also
[scope](../../ci/environments/index.md#scoping-environments-with-specs) the
`AUTO_DEVOPS_POSTGRES_CHANNEL`, `AUTO_DEVOPS_POSTGRES_DELETE_V1` and
`POSTGRES_VERSION` variables to specific environments, e.g. `staging`.
diff --git a/doc/topics/git/index.md b/doc/topics/git/index.md
index 2e36fea14bf..89da3dfdbd0 100644
--- a/doc/topics/git/index.md
+++ b/doc/topics/git/index.md
@@ -26,8 +26,9 @@ The following resources will help you get started with Git:
- [Git Basics](https://git-scm.com/book/en/v2/Getting-Started-Git-Basics)
- [Git on the Server - GitLab](https://git-scm.com/book/en/v2/Git-on-the-Server-GitLab)
- [How to install Git](how_to_install_git/index.md)
+- [Git terminology](../../gitlab-basics/start-using-git.md#git-terminology)
- [Start using Git on the command line](../../gitlab-basics/start-using-git.md)
-- [Command line file editing basic commands](../../gitlab-basics/command-line-commands.md)
+- [Edit files through the command line](../../gitlab-basics/command-line-commands.md)
- [GitLab Git Cheat Sheet (download)](https://about.gitlab.com/images/press/git-cheat-sheet.pdf)
- Commits:
- [Revert a commit](../../user/project/merge_requests/revert_changes.md#reverting-a-commit)
diff --git a/doc/topics/git/lfs/index.md b/doc/topics/git/lfs/index.md
index 706d3c3eddf..1e2f45fd67b 100644
--- a/doc/topics/git/lfs/index.md
+++ b/doc/topics/git/lfs/index.md
@@ -89,6 +89,9 @@ that are on the remote repository, such as for a branch from origin:
git lfs fetch origin master
```
+Make sure your files aren't listed in `.gitignore`, otherwise, they will be ignored by Git thus will not
+be pushed to the remote repository.
+
### Migrate an existing repo to Git LFS
Read the documentation on how to [migrate an existing Git repository with Git LFS](migrate_to_git_lfs.md).
diff --git a/doc/topics/git/lfs/migrate_to_git_lfs.md b/doc/topics/git/lfs/migrate_to_git_lfs.md
index a64639a9238..3e287c0816d 100644
--- a/doc/topics/git/lfs/migrate_to_git_lfs.md
+++ b/doc/topics/git/lfs/migrate_to_git_lfs.md
@@ -21,7 +21,7 @@ and lastly create LFS tracking rules to prevent new binary files
from being added.
This tutorial was inspired by the guide
-[Use BFG to migrate a repository to Git LFS](https://confluence.atlassian.com/bitbucket/use-bfg-to-migrate-a-repo-to-git-lfs-834233484.html).
+[Use BFG to migrate a repository to Git LFS](https://support.atlassian.com/bitbucket-cloud/docs/use-bfg-to-migrate-a-repo-to-git-lfs/).
For more information on Git LFS, see the [references](#references)
below.
diff --git a/doc/topics/git/partial_clone.md b/doc/topics/git/partial_clone.md
index 7462406cad3..de25c8a3283 100644
--- a/doc/topics/git/partial_clone.md
+++ b/doc/topics/git/partial_clone.md
@@ -84,7 +84,7 @@ Updating files: 100% (28/28), done.
$ cd www-gitlab-com
-$ git sparse-checkout init --cone
+$ git sparse-checkout init --clone
$ git sparse-checkout add data
remote: Enumerating objects: 301, done.
@@ -113,9 +113,6 @@ file to specify which files should be included when cloning and fetching.
For more details, see the Git documentation for
[`rev-list-options`](https://gitlab.com/gitlab-org/git/-/blob/9fadedd637b312089337d73c3ed8447e9f0aa775/Documentation/rev-list-options.txt#L735-780).
-With the `uploadpack.allowFilter` and `uploadpack.allowAnySHA1InWant` options
-enabled on the Git server:
-
1. **Create a filter spec.** For example, consider a monolithic repository with
many applications, each in a different subdirectory in the root. Create a file
`shiny-app/.filterspec` using the GitLab web interface:
diff --git a/doc/topics/gitlab_flow.md b/doc/topics/gitlab_flow.md
index 6382ac0957a..04c942ab532 100644
--- a/doc/topics/gitlab_flow.md
+++ b/doc/topics/gitlab_flow.md
@@ -176,9 +176,9 @@ The name of a branch might be dictated by organizational standards.
When you are done or want to discuss the code, open a merge request.
A merge request is an online place to discuss the change and review the code.
-If you open the merge request but do not assign it to anyone, it is a "Work In Progress" merge request.
+If you open the merge request but do not assign it to anyone, it is a [draft merge request](../user/project/merge_requests/work_in_progress_merge_requests.md).
These are used to discuss the proposed implementation but are not ready for inclusion in the `master` branch yet.
-Start the title of the merge request with `[WIP]` or `WIP:` to prevent it from being merged before it's ready.
+Start the title of the merge request with `[Draft]`, `Draft:` or `(Draft)` to prevent it from being merged before it's ready.
When you think the code is ready, assign the merge request to a reviewer.
The reviewer can merge the changes when they think the code is ready for inclusion in the `master` branch.
diff --git a/doc/topics/web_application_firewall/index.md b/doc/topics/web_application_firewall/index.md
index 57043bf73b3..5ce7c0779bb 100644
--- a/doc/topics/web_application_firewall/index.md
+++ b/doc/topics/web_application_firewall/index.md
@@ -1,3 +1,10 @@
+---
+stage: Defend
+group: Container Security
+info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers
+---
+
+
# Web Application Firewall - ModSecurity
A web application firewall (or WAF) filters, monitors, and blocks HTTP traffic to
@@ -18,7 +25,7 @@ applications which have an Ingress.
The ModSecurity module runs with the [OWASP Core Rule Set (CRS)](https://coreruleset.org/) by default. The OWASP CRS will detect and log a wide range of common attacks.
-NOTE: **Note**
+NOTE: **Note:**
The WAF is deployed in "Detection-only mode" by default and will only log attack
attempts.
@@ -53,7 +60,7 @@ If you are using a self-managed instance of GitLab, you need to configure the
[Google OAuth2 OmniAuth Provider](../../integration/google.md) before
you can configure a cluster on GKE. Once this is set up, you can follow the steps on the [quick start guide](quick_start_guide.md) to get started.
-NOTE: **Note**
+NOTE: **Note:**
This guide shows how the WAF can be deployed using Auto DevOps. The WAF
is available by default to all applications no matter how they are deployed,
as long as they are using Ingress.
diff --git a/doc/topics/web_application_firewall/quick_start_guide.md b/doc/topics/web_application_firewall/quick_start_guide.md
index ec6702bb457..9e69bc7e7c7 100644
--- a/doc/topics/web_application_firewall/quick_start_guide.md
+++ b/doc/topics/web_application_firewall/quick_start_guide.md
@@ -1,3 +1,9 @@
+---
+stage: Defend
+group: Container Security
+info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers
+---
+
# Getting started with the Web Application Firewall
This is a step-by-step guide that will help you use GitLab's [Web Application Firewall](index.md) after
@@ -11,7 +17,7 @@ These instructions will also work for a self-managed GitLab instance. However, y
need to ensure your own [Runners are configured](../../ci/runners/README.md) and
[Google OAuth is enabled](../../integration/google.md).
-**Note**: GitLab's Web Application Firewall is deployed with [Ingress](../../user/clusters/applications.md#Ingress),
+**Note**: GitLab's Web Application Firewall is deployed with [Ingress](../../user/clusters/applications.md#ingress),
so it will be available to your applications no matter how you deploy them to Kubernetes.
## Configuring your Google account
@@ -86,7 +92,7 @@ status on your [GCP dashboard](https://console.cloud.google.com/kubernetes).
The next step is to install some applications on your cluster that are needed
to take full advantage of Auto DevOps.
-## Installing Helm and Ingress
+## Install Ingress
GitLab's Kubernetes integration comes with some
[pre-defined applications](../../user/project/clusters/index.md#installing-applications)
@@ -94,12 +100,6 @@ for you to install.
![Cluster applications](../autodevops/img/guide_cluster_apps_v12_3.png)
-The first one to install is Helm Tiller, a package manager for Kubernetes, which
-is needed in order to install the rest of the applications. Go ahead and click
-its **Install** button.
-Once it is installed, the other applications that rely on it will each have their
-**Install** buttons enabled.
-
For this guide, we need to install Ingress. Ingress provides load balancing,
SSL termination, and name-based virtual hosting, using NGINX behind
the scenes. Make sure to switch the toogle to the enabled position before installing.
diff --git a/doc/university/README.md b/doc/university/README.md
index df41167dbe9..2a9111276d3 100644
--- a/doc/university/README.md
+++ b/doc/university/README.md
@@ -72,7 +72,7 @@ The GitLab University curriculum is composed of GitLab videos, screencasts, pres
1. [Getting Help](https://about.gitlab.com/get-help/)
- Proposing Features and Reporting and Tracking bugs for GitLab
- - The GitLab IRC channel, Gitter Chat Room, Community Forum and Mailing List
+ - The GitLab IRC channel, Gitter Chat Room, Community Forum, and Mailing List
- Getting Technical Support
- Being part of our Great Community and Contributing to GitLab
1. [Getting Started with the GitLab Development Kit (GDK)](https://about.gitlab.com/blog/2016/06/08/getting-started-with-gitlab-development-kit/)
@@ -140,11 +140,11 @@ The GitLab University curriculum is composed of GitLab videos, screencasts, pres
## 3. GitLab Advanced
-### 3.1. Dev Ops
+### 3.1. DevOps
-1. [Xebia Labs: Dev Ops Terminology](https://xebialabs.com/glossary/)
-1. [Xebia Labs: Periodic Table of DevOps Tools](https://xebialabs.com/periodic-table-of-devops-tools/)
-1. [Puppet Labs: State of Dev Ops 2016 - Book](https://puppet.com/resources/report/2016-state-devops-report/)
+1. [XebiaLabs: DevOps Terminology](https://xebialabs.com/glossary/)
+1. [XebiaLabs: Periodic Table of DevOps Tools](https://digital.ai/periodic-table-of-devops-tools)
+1. [Puppet Labs: State of DevOps 2016 - Book](https://puppet.com/resources/report/2016-state-devops-report/)
### 3.2. Installing GitLab with Omnibus
diff --git a/doc/university/bookclub/index.md b/doc/university/bookclub/index.md
index c6dad663c0e..71dfe7fc3cb 100644
--- a/doc/university/bookclub/index.md
+++ b/doc/university/bookclub/index.md
@@ -15,7 +15,7 @@ See the [book list](booklist.md) for additional recommendations.
1. **Remote: Office not required**
David Heinemeier Hansson and Jason Fried, 2013
- ([amazon](https://www.amazon.co.uk/Remote-Required-David-Heinemeier-Hansson/dp/0091954673))
+ ([Amazon](https://www.amazon.co.uk/dp/0091954673/ref=cm_sw_r_tw_dp_x_0yy9EbZ2WXJ6Y))
1. **The Year Without Pants**
diff --git a/doc/university/training/end-user/README.md b/doc/university/training/end-user/README.md
index 0465f48bb34..8d25b865855 100644
--- a/doc/university/training/end-user/README.md
+++ b/doc/university/training/end-user/README.md
@@ -159,7 +159,7 @@ git push origin squash_some_bugs
- When you want feedback create a merge request
- Target is the ‘default’ branch (usually master)
- Assign or mention the person you would like to review
-- Add `WIP` to the title if it's a work in progress
+- Add `Draft:` to the title if it's a work in progress
- When accepting, always delete the branch
- Anyone can comment, not just the assignee
- Push corrections to the same branch
diff --git a/doc/university/training/topics/merge_requests.md b/doc/university/training/topics/merge_requests.md
index 0ea8b707ea9..ea679a7d66f 100644
--- a/doc/university/training/topics/merge_requests.md
+++ b/doc/university/training/topics/merge_requests.md
@@ -7,7 +7,7 @@ comments: false
- When you want feedback create a merge request
- Target is the default branch (usually master)
- Assign or mention the person you would like to review
-- Add 'WIP' to the title if it's a work in progress
+- Add `[Draft]` to the title if it's a work in progress
- When accepting, always delete the branch
- Anyone can comment, not just the assignee
- Push corrections to the same branch
diff --git a/doc/university/training/user_training.md b/doc/university/training/user_training.md
index d2def1f3199..43de90010b1 100644
--- a/doc/university/training/user_training.md
+++ b/doc/university/training/user_training.md
@@ -186,7 +186,7 @@ git push origin squash_some_bugs
- When you want feedback create a merge request.
- Target is the ‘default’ branch (usually master).
- Assign or mention the person you would like to review.
-- Add 'WIP' to the title if it's a work in progress.
+- Add `[Draft]` to the title if it's a work in progress.
- When accepting, always delete the branch.
- Anyone can comment, not just the assignee.
- Push corrections to the same branch.
diff --git a/doc/update/README.md b/doc/update/README.md
index f36a304495c..26e52229fd2 100644
--- a/doc/update/README.md
+++ b/doc/update/README.md
@@ -147,8 +147,37 @@ puts Sidekiq::Queue.new("background_migration").size
Sidekiq::ScheduledSet.new.select { |r| r.klass == 'BackgroundMigrationWorker' }.size
```
-There is also a [Rake task](../administration/raketasks/maintenance.md#display-status-of-database-migrations)
-for displaying the status of each database migration.
+### What do I do if my background migrations are stuck?
+
+CAUTION: **Warning:**
+The following operations can disrupt your GitLab performance.
+
+NOTE: **Note:**
+It is safe to re-execute these commands, especially if you have 1000+ pending jobs which would likely overflow your runtime memory.
+
+**For Omnibus installations**
+
+```shell
+# Start the rails console
+sudo gitlab-rails c
+
+# Execute the following in the rails console
+scheduled_queue = Sidekiq::ScheduledSet.new
+pending_job_classes = scheduled_queue.select { |job| job["class"] == "BackgroundMigrationWorker" }.map { |job| job["args"].first }.uniq
+pending_job_classes.each { |job_class| Gitlab::BackgroundMigration.steal(job_class) }
+```
+
+**For installations from source**
+
+```shell
+# Start the rails console
+sudo -u git -H bundle exec rails RAILS_ENV=production
+
+# Execute the following in the rails console
+scheduled_queue = Sidekiq::ScheduledSet.new
+pending_job_classes = scheduled_queue.select { |job| job["class"] == "BackgroundMigrationWorker" }.map { |job| job["args"].first }.uniq
+pending_job_classes.each { |job_class| Gitlab::BackgroundMigration.steal(job_class) }
+```
## Upgrading to a new major version
@@ -192,6 +221,12 @@ possible.
## Version specific upgrading instructions
+### 13.2.0
+
+GitLab installations that have multiple web nodes will need to be
+[upgraded to 13.1](#1310) before upgrading to 13.2 (and later) due to a
+breaking change in Rails that can result in authorization issues.
+
### 13.1.0
In 13.1.0, you must upgrade to either:
@@ -202,6 +237,27 @@ In 13.1.0, you must upgrade to either:
Failure to do so will result in internal errors in the Gitaly service in some RPCs due
to the use of the new `--end-of-options` Git flag.
+Additionally, in GitLab 13.1.0, the version of [Rails was upgraded from 6.0.3 to
+6.0.3.1](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/33454).
+The Rails upgrade included a change to CSRF token generation which is
+not backwards-compatible - GitLab servers with the new Rails version
+will generate CSRF tokens that are not recognizable by GitLab servers
+with the older Rails version - which could cause non-GET requests to
+fail for [multi-node GitLab installations](https://docs.gitlab.com/omnibus/update/#multi-node--ha-deployment).
+
+So, if you are using multiple Rails servers and specifically upgrading from 13.0,
+all servers must first be upgraded to 13.1.0 before upgrading to later versions:
+
+1. Ensure all GitLab web nodes are on GitLab 13.1.0.
+1. Optionally, enable the `global_csrf_token` feature flag to enable new
+ method of CSRF token generation:
+
+ ```ruby
+ Feature.enable(:global_csrf_token)
+ ```
+
+1. Only then, continue to upgrade to later versions of GitLab.
+
### 12.2.0
In 12.2.0, we enabled Rails' authenticated cookie encryption. Old sessions are
diff --git a/doc/update/restore_after_failure.md b/doc/update/restore_after_failure.md
index c850bbff1cf..2c70e38d041 100644
--- a/doc/update/restore_after_failure.md
+++ b/doc/update/restore_after_failure.md
@@ -10,7 +10,9 @@ the previous version you were using.
First, roll back the code or package. For source installations this involves
checking out the older version (branch or tag). For Omnibus installations this
-means installing the older `.deb` or `.rpm` package. Then, restore from a backup.
+means installing the older
+[`.deb` or `.rpm` package](https://packages.gitlab.com/gitlab). Then, restore from a
+backup.
Follow the instructions in the
[Backup and Restore](../raketasks/backup_restore.md#restore-gitlab)
documentation.
diff --git a/doc/update/upgrading_from_ce_to_ee.md b/doc/update/upgrading_from_ce_to_ee.md
index 78227f18457..f82f5001c89 100644
--- a/doc/update/upgrading_from_ce_to_ee.md
+++ b/doc/update/upgrading_from_ce_to_ee.md
@@ -4,7 +4,8 @@ comments: false
# Upgrading from Community Edition to Enterprise Edition from source
-NOTE: **NOTE** In the past we used separate documents for upgrading from
+NOTE: **Note:**
+In the past we used separate documents for upgrading from
Community Edition to Enterprise Edition. These documents can be found in the
[`doc/update` directory of Enterprise Edition's source
code](https://gitlab.com/gitlab-org/gitlab/tree/11-8-stable-ee/doc/update).
diff --git a/doc/user/admin_area/activating_deactivating_users.md b/doc/user/admin_area/activating_deactivating_users.md
index 06a26737495..448c65038c2 100644
--- a/doc/user/admin_area/activating_deactivating_users.md
+++ b/doc/user/admin_area/activating_deactivating_users.md
@@ -39,7 +39,7 @@ A user can be deactivated from the Admin Area. To do this:
Please note that for the deactivation option to be visible to an admin, the user:
- Must be currently active.
-- Must not have any signins or activity in the last 180 days.
+- Must not have signed in, or have any activity, in the last 180 days.
Users can also be deactivated using the [GitLab API](../../api/users.md#deactivate-user).
diff --git a/doc/user/admin_area/credentials_inventory.md b/doc/user/admin_area/credentials_inventory.md
index 5eecfbb73c6..9259c93cfa3 100644
--- a/doc/user/admin_area/credentials_inventory.md
+++ b/doc/user/admin_area/credentials_inventory.md
@@ -18,9 +18,11 @@ Using Credentials inventory, GitLab administrators can see all the personal acce
- Who they belong to.
- Their access scope.
- Their usage pattern.
+- When they expire. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/214809) in GitLab 13.2.
+- When they were revoked. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/214809) in GitLab 13.2.
To access the Credentials inventory, navigate to **Admin Area > Credentials**.
The following is an example of the Credentials inventory page:
-![Credentials inventory page](img/credentials_inventory_v12_6.png)
+![Credentials inventory page](img/credentials_inventory_v13_2.png)
diff --git a/doc/user/admin_area/img/credentials_inventory_v12_6.png b/doc/user/admin_area/img/credentials_inventory_v12_6.png
deleted file mode 100644
index 5c16781cb2d..00000000000
--- a/doc/user/admin_area/img/credentials_inventory_v12_6.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/admin_area/img/credentials_inventory_v13_2.png b/doc/user/admin_area/img/credentials_inventory_v13_2.png
new file mode 100644
index 00000000000..5b56422a0a3
--- /dev/null
+++ b/doc/user/admin_area/img/credentials_inventory_v13_2.png
Binary files differ
diff --git a/doc/user/admin_area/img/mr_approval_settings_compliance_project_v13_1.png b/doc/user/admin_area/img/mr_approval_settings_compliance_project_v13_1.png
new file mode 100644
index 00000000000..04d01f2662f
--- /dev/null
+++ b/doc/user/admin_area/img/mr_approval_settings_compliance_project_v13_1.png
Binary files differ
diff --git a/doc/user/admin_area/img/scope_mr_approval_settings_v13_1.png b/doc/user/admin_area/img/scope_mr_approval_settings_v13_1.png
new file mode 100644
index 00000000000..c6ca2bac83c
--- /dev/null
+++ b/doc/user/admin_area/img/scope_mr_approval_settings_v13_1.png
Binary files differ
diff --git a/doc/user/admin_area/license.md b/doc/user/admin_area/license.md
index 1ffaf4e0678..c5e29612596 100644
--- a/doc/user/admin_area/license.md
+++ b/doc/user/admin_area/license.md
@@ -5,7 +5,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w
type: howto
---
-# Activate all GitLab Enterprise Edition functionality with a license **(STARTER ONLY)**
+# Activate GitLab EE with a license **(STARTER ONLY)**
To activate all GitLab Enterprise Edition (EE) functionality, you need to upload
a license. Once you've received your license from GitLab Inc., you can upload it
@@ -107,14 +107,23 @@ expired license(s).
It's possible to upload and view more than one license,
but only the latest license will be used as the active license.
-<!-- ## Troubleshooting
+## Troubleshooting
-Include any troubleshooting steps that you can foresee. If you know beforehand what issues
-one might have when setting this up, or when something is changed, or on upgrading, it's
-important to describe those, too. Think of things that may go wrong and include them here.
-This is important to minimize requests for support, and to avoid doc comments with
-questions that you know someone might ask.
+### There is no License tab in the Admin Area
-Each scenario can be a third-level heading, e.g. `### Getting error message X`.
-If you have none to add when creating a doc, leave this section in place
-but commented out to help encourage others to add to it in the future. -->
+If you originally installed Community Edition rather than Enterprise Edition you will need to
+[upgrade to Enterprise Edition](../../update/README.md#community-to-enterprise-edition)
+before uploading your license.
+
+GitLab.com users cannot upload and use a self-managed license. If you
+wish to use paid features on GitLab.com, a separate subscription may be
+[purchased](../../subscriptions/index.md#subscribe-to-gitlabcom).
+
+### Users exceed license limit upon renewal
+
+If you've added new users to your GitLab instance prior to renewal you may need to
+purchase additional seats to cover those users. If this is the case and a license
+without enough users is uploaded a message is displayed prompting you to purchase
+additional users. More information on how to determine the required number of users
+and how to add additional seats can be found in the
+[licensing FAQ](https://about.gitlab.com/pricing/licensing-faq/).
diff --git a/doc/user/admin_area/merge_requests_approvals.md b/doc/user/admin_area/merge_requests_approvals.md
index 153ccfc128a..6d9d634ce14 100644
--- a/doc/user/admin_area/merge_requests_approvals.md
+++ b/doc/user/admin_area/merge_requests_approvals.md
@@ -34,3 +34,22 @@ Merge request approval rules that can be set at an instance level are:
- **Prevent users from modifying merge request approvers list**. Prevents project
maintainers from allowing users to modify the approvers list in project settings
or in individual merge requests.
+
+## Scope rules to compliance-labeled projects
+
+> Introduced in [GitLab Premium](https://gitlab.com/groups/gitlab-org/-/epics/3432) 13.2.
+
+Merge request approval rules can be further scoped to specific compliance frameworks.
+
+When the compliance framework label is selected and the project is assigned the compliance
+label, the instance-level MR approval settings will take effect and the
+[project-level settings](../project/merge_requests/merge_request_approvals.md#adding--editing-a-default-approval-rule)
+is locked for modification.
+
+When the compliance framework label is not selected or the project is not assigned the
+compliance label, the project-level MR approval settings will take effect and the users with
+Maintainer role and above can modify these.
+
+| Instance-level | Project-level |
+| -------------- | ------------- |
+| ![Scope MR approval settings to compliance frameworks](img/scope_mr_approval_settings_v13_1.png) | ![MR approval settings on compliance projects](img/mr_approval_settings_compliance_project_v13_1.png) |
diff --git a/doc/user/admin_area/monitoring/health_check.md b/doc/user/admin_area/monitoring/health_check.md
index 91e29118e3e..329b6ff5bb0 100644
--- a/doc/user/admin_area/monitoring/health_check.md
+++ b/doc/user/admin_area/monitoring/health_check.md
@@ -137,8 +137,8 @@ This check is being exempt from Rack Attack.
## Access token (Deprecated)
-> NOTE: **Note:**
-> Access token has been deprecated in GitLab 9.4 in favor of [IP whitelist](#ip-whitelist).
+NOTE: **Note:**
+Access token has been deprecated in GitLab 9.4 in favor of [IP whitelist](#ip-whitelist).
An access token needs to be provided while accessing the probe endpoints. The current
accepted token can be found under the **Admin Area > Monitoring > Health check**
@@ -152,6 +152,10 @@ The access token can be passed as a URL parameter:
https://gitlab.example.com/-/readiness?token=ACCESS_TOKEN
```
+NOTE: **Note:**
+In case the database or Redis service are unaccessible, the probe endpoints response is not guaranteed to be correct.
+You should switch to [IP whitelist](#ip-whitelist) from deprecated access token to avoid it.
+
<!-- ## Troubleshooting
Include any troubleshooting steps that you can foresee. If you know beforehand what issues
diff --git a/doc/user/admin_area/settings/account_and_limit_settings.md b/doc/user/admin_area/settings/account_and_limit_settings.md
index b4fb7a524da..167016f1cb5 100644
--- a/doc/user/admin_area/settings/account_and_limit_settings.md
+++ b/doc/user/admin_area/settings/account_and_limit_settings.md
@@ -30,11 +30,12 @@ details.
This sets a maximum size limit on each namespace. The following are included in the namespace size:
-- repository
-- wiki
+- Repository
+- Wiki
- LFS objects
-- build artifacts
-- packages
+- Build artifacts
+- Packages
+- Snippets
NOTE: **Note:**
This limit is not currently enforced but will be in a future release.
@@ -140,6 +141,39 @@ Once a lifetime for personal access tokens is set, GitLab will:
allowed lifetime. Three hours is given to allow administrators to change the allowed lifetime,
or remove it, before revocation takes place.
+## Optional enforcement of Personal Access Token expiry **(ULTIMATE ONLY)**
+
+> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/214723) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 13.1.
+> - It is deployed behind a feature flag, disabled by default.
+> - It is disabled on GitLab.com.
+> - It is not recommended for production use.
+> - To use it in GitLab self-managed instances, ask a GitLab administrator to [enable it](#enable-or-disable-optional-enforcement-of-personal-access-token-expiry-feature-core-only). **(CORE ONLY)**
+
+GitLab administrators can choose to prevent personal access tokens from expiring automatically. The tokens will be usable after the expiry date, unless they are revoked explicitly.
+
+To do this:
+
+1. Navigate to **Admin Area > Settings > General**.
+1. Expand the **Account and limit** section.
+1. Uncheck the **Enforce personal access token expiration** checkbox.
+
+### Enable or disable optional enforcement of Personal Access Token expiry Feature **(CORE ONLY)**
+
+Optional Enforcement of Personal Access Token Expiry is under development and not ready for production use. It is deployed behind a feature flag that is **disabled by default**.
+[GitLab administrators with access to the GitLab Rails console](../../../administration/feature_flags.md) can enable it for your instance from the [rails console](../../../administration/feature_flags.md#start-the-gitlab-rails-console).
+
+To enable it:
+
+```ruby
+Feature.enable(:enforce_personal_access_token_expiration)
+```
+
+To disable it:
+
+```ruby
+Feature.disable(:enforce_personal_access_token_expiration)
+```
+
## Disabling user profile name changes **(PREMIUM ONLY)**
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/24605) in GitLab 12.7.
diff --git a/doc/user/admin_area/settings/continuous_integration.md b/doc/user/admin_area/settings/continuous_integration.md
index 3a287f29a0a..0479da7fb52 100644
--- a/doc/user/admin_area/settings/continuous_integration.md
+++ b/doc/user/admin_area/settings/continuous_integration.md
@@ -17,8 +17,8 @@ You can find it in the **Admin Area > Settings > CI/CD**.
To enable (or disable) [Auto DevOps](../../../topics/autodevops/index.md)
for all projects:
-1. Go to **Admin Area > Settings > CI/CD**
-1. Check (or uncheck to disable) the box that says "Default to Auto DevOps pipeline for all projects"
+1. Go to **Admin Area > Settings > CI/CD**.
+1. Check (or uncheck to disable) the box that says **Default to Auto DevOps pipeline for all projects**.
1. Optionally, set up the [Auto DevOps base domain](../../../topics/autodevops/index.md#auto-devops-base-domain)
which is going to be used for Auto Deploy and Auto Review Apps.
1. Hit **Save changes** for the changes to take effect.
@@ -48,21 +48,21 @@ To change it at the:
1. Go to **Admin Area > Settings > CI/CD**.
1. Change the value of maximum artifacts size (in MB).
- 1. Hit **Save changes** for the changes to take effect.
+ 1. Click **Save changes** for the changes to take effect.
- [Group level](../../group/index.md#group-settings) (this will override the instance setting):
1. Go to the group's **Settings > CI / CD > General Pipelines**.
1. Change the value of **maximum artifacts size (in MB)**.
- 1. Press **Save changes** for the changes to take effect.
+ 1. Click **Save changes** for the changes to take effect.
- [Project level](../../../ci/pipelines/settings.md) (this will override the instance and group settings):
1. Go to the project's **Settings > CI / CD > General Pipelines**.
1. Change the value of **maximum artifacts size (in MB)**.
- 1. Press **Save changes** for the changes to take effect.
+ 1. Click **Save changes** for the changes to take effect.
-NOTE: **Note**
+NOTE: **Note:**
The setting at all levels is only available to GitLab administrators.
## Default artifacts expiration **(CORE ONLY)**
@@ -70,18 +70,17 @@ The setting at all levels is only available to GitLab administrators.
The default expiration time of the [job artifacts](../../../administration/job_artifacts.md)
can be set in the Admin Area of your GitLab instance. The syntax of duration is
described in [`artifacts:expire_in`](../../../ci/yaml/README.md#artifactsexpire_in)
-and the default value is `30 days`. On GitLab.com they
-[never expire](../../gitlab_com/index.md#gitlab-cicd).
+and the default value is `30 days`.
1. Go to **Admin Area > Settings > CI/CD**.
1. Change the value of default expiration time.
-1. Hit **Save changes** for the changes to take effect.
+1. Click **Save changes** for the changes to take effect.
This setting is set per job and can be overridden in
[`.gitlab-ci.yml`](../../../ci/yaml/README.md#artifactsexpire_in).
To disable the expiration, set it to `0`. The default unit is in seconds.
-NOTE: **Note**
+NOTE: **Note:**
Any changes to this setting will apply to new artifacts only. The expiration time will not
be updated for artifacts created before this setting was changed.
The administrator may need to manually search for and expire previously-created
@@ -101,9 +100,10 @@ On GitLab.com, the quota is calculated based on your
To change the pipelines minutes quota:
-1. Go to **Admin Area > Settings > CI/CD**
-1. Set the pipeline minutes quota limit.
-1. Hit **Save changes** for the changes to take effect
+1. Go to **Admin Area > Settings > CI/CD**.
+1. Expand **Continuous Integration and Deployment**.
+1. In the **Pipeline minutes quota** box, enter the maximum number of minutes.
+1. Click **Save changes** for the changes to take effect.
---
@@ -112,8 +112,8 @@ also change each group's pipeline minutes quota to override the global value.
1. Navigate to the **Admin Area > Overview > Groups** and hit the **Edit**
button for the group you wish to change the pipeline minutes quota.
-1. Set the pipeline minutes quota to the desired value
-1. Hit **Save changes** for the changes to take effect.
+1. In the **Pipeline Minutes Quota** box, enter the maximum number of minutes.
+1. Click **Save changes** for the changes to take effect.
Once saved, you can see the build quota in the group admin view.
The quota can also be viewed in the project admin view if shared Runners
@@ -143,6 +143,8 @@ Once that time passes, the jobs will be archived and no longer able to be
retried. Make it empty to never expire jobs. It has to be no less than 1 day,
for example: <code>15 days</code>, <code>1 month</code>, <code>2 years</code>.
+As of June 22, 2020 the [value is set](../../gitlab_com/index.md#gitlab-cicd) to 3 months on GitLab.com. Jobs created before that date will be archived after September 22, 2020.
+
## Default CI configuration path
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/18073) in GitLab 12.5.
diff --git a/doc/user/admin_area/settings/img/email_notification_for_unknown_sign_ins_v13_2.png b/doc/user/admin_area/settings/img/email_notification_for_unknown_sign_ins_v13_2.png
new file mode 100644
index 00000000000..fdcc542c4d7
--- /dev/null
+++ b/doc/user/admin_area/settings/img/email_notification_for_unknown_sign_ins_v13_2.png
Binary files differ
diff --git a/doc/user/admin_area/settings/img/import_export_rate_limits_v13_2.png b/doc/user/admin_area/settings/img/import_export_rate_limits_v13_2.png
new file mode 100644
index 00000000000..78c94b3803c
--- /dev/null
+++ b/doc/user/admin_area/settings/img/import_export_rate_limits_v13_2.png
Binary files differ
diff --git a/doc/user/admin_area/settings/import_export_rate_limits.md b/doc/user/admin_area/settings/import_export_rate_limits.md
new file mode 100644
index 00000000000..92cb2a1a109
--- /dev/null
+++ b/doc/user/admin_area/settings/import_export_rate_limits.md
@@ -0,0 +1,32 @@
+---
+type: reference
+stage: Manage
+group: Import
+info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers
+---
+
+# Project/Group Import/Export rate limits
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/35728) in GitLab 13.2.
+
+The following table includes configurable rate limits. The following table includes limits on a
+per minute per user basis:
+
+| Limit | Default (per minute per user) |
+|--------------------------|-------------------------------|
+| Project Import | 6 |
+| Project Export | 6 |
+| Project Export Download | 1 |
+| Group Import | 6 |
+| Group Export | 6 |
+| Group Export Download | 1 |
+
+All rate limits are:
+
+- Configurable at **(admin)** **Admin Area > Settings > Network > Import/Export Rate Limits**
+- Applied per minute per user
+- Not applied per IP address
+- Active by default. To disable, set the option to `0`
+- Logged to `auth.log` file if exceed rate limit
+
+![Import/Export rate limits](img/import_export_rate_limits_v13_2.png)
diff --git a/doc/user/admin_area/settings/index.md b/doc/user/admin_area/settings/index.md
index df087722fcf..8c1e82f838b 100644
--- a/doc/user/admin_area/settings/index.md
+++ b/doc/user/admin_area/settings/index.md
@@ -43,6 +43,7 @@ Access the default page for admin area settings by navigating to
| Option | Description |
| ------ | ----------- |
+| [Repository's custom initial branch name](../../project/repository/branches/index.md#custom-initial-branch-name-core-only) | Set a custom branch name rather than master for all the new repositories created within your instance. |
| [Repository mirror](visibility_and_access_controls.md#allow-mirrors-to-be-set-up-for-projects) | Configure repository mirroring. |
| [Repository storage](../../../administration/repository_storage_types.md) | Configure storage path settings. |
| Repository maintenance | ([Repository checks](../../../administration/repository_checks.md) and [Housekeeping](../../../administration/housekeeping.md)). Configure automatic Git checks and housekeeping on repositories. |
diff --git a/doc/user/admin_area/settings/rate_limit_on_issues_creation.md b/doc/user/admin_area/settings/rate_limit_on_issues_creation.md
index bae60aba15f..3f8ef7c9d01 100644
--- a/doc/user/admin_area/settings/rate_limit_on_issues_creation.md
+++ b/doc/user/admin_area/settings/rate_limit_on_issues_creation.md
@@ -10,10 +10,9 @@ info: To determine the technical writer assigned to the Stage/Group associated w
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/28129) in GitLab 12.10.
This setting allows you to rate limit the requests to the issue creation endpoint.
-It defaults to 300 requests per minute.
-You can change it in **Admin Area > Settings > Network > Performance Optimization**.
+You can change its value in **Admin Area > Settings > Network > Issues Rate Limits**.
-For example, requests using the
+For example, if you set a limit of 300, requests using the
[Projects::IssuesController#create](https://gitlab.com/gitlab-org/gitlab/raw/master/app/controllers/projects/issues_controller.rb)
action exceeding a rate of 300 per minute are blocked. Access to the endpoint is allowed after one minute.
@@ -23,6 +22,6 @@ This limit is:
- Applied independently per project and per user.
- Not applied per IP address.
-- Active by default. To disable it, set the option to `0`.
+- Disabled by default. To enable it, set the option to any value other than `0`.
Requests over the rate limit are logged into the `auth.log` file.
diff --git a/doc/user/admin_area/settings/sign_in_restrictions.md b/doc/user/admin_area/settings/sign_in_restrictions.md
index 1da93c7005f..311b79af7e3 100644
--- a/doc/user/admin_area/settings/sign_in_restrictions.md
+++ b/doc/user/admin_area/settings/sign_in_restrictions.md
@@ -4,9 +4,14 @@ type: reference
# Sign-in restrictions **(CORE ONLY)**
-You can use sign-in restrictions to limit the authentication with password
-for web interface and Git over HTTP(S), two-factor authentication enforcing, as well as
-as configuring the home page URL and after sign-out path.
+You can use **Sign-in restrictions** to customize authentication restrictions for web interfaces as well as Git over HTTP(S).
+
+## Settings
+
+To access sign-in restriction settings:
+
+1. Navigate to the **Admin Area > Settings > General**.
+1. Expand the **Sign-in restrictions** section.
## Password authentication enabled
@@ -25,6 +30,15 @@ period in hours.
![Two-factor grace period](img/two_factor_grace_period.png)
+## Email notification for unknown sign-ins
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/218457) in GitLab 13.2.
+
+When enabled, GitLab notifies users of sign-ins from unknown IP addresses or devices. For more information,
+see [Email notification for unknown sign-ins](../../profile/unknown_sign_in_notification.md).
+
+![Email notification for unknown sign-ins](img/email_notification_for_unknown_sign_ins_v13_2.png)
+
## Sign-in information
All users that are not logged-in will be redirected to the page represented by the configured
@@ -36,13 +50,6 @@ after sign out if value is not empty.
If a "Sign in text" in Markdown format is provided, then every user will be presented with
this message after logging-in.
-## Settings
-
-To access this feature:
-
-1. Navigate to the **Admin Area > Settings > General**.
-1. Expand the **Sign-in restrictions** section.
-
<!-- ## Troubleshooting
Include any troubleshooting steps that you can foresee. If you know beforehand what issues
diff --git a/doc/user/admin_area/settings/visibility_and_access_controls.md b/doc/user/admin_area/settings/visibility_and_access_controls.md
index d9ca4a0881a..92eeb6a04b7 100644
--- a/doc/user/admin_area/settings/visibility_and_access_controls.md
+++ b/doc/user/admin_area/settings/visibility_and_access_controls.md
@@ -68,8 +68,16 @@ To ensure only admin users can delete projects:
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/32935) in GitLab 12.6.
-By default, a project or group marked for removal will be permanently removed after 7 days.
-This period may be changed, and setting this period to 0 will enable immediate removal
+By default, a project marked for deletion will be permanently removed with immediate effect.
+By default, a group marked for deletion will be permanently removed after 7 days.
+
+CAUTION: **Warning:**
+The default behavior of [Delayed Project deletion](https://gitlab.com/gitlab-org/gitlab/-/issues/32935) in GitLab 12.6 was changed to
+[Immediate deletion](https://gitlab.com/gitlab-org/gitlab/-/issues/220382) in GitLab 13.2.
+
+Projects within a group can be deleted after a delayed period, by [configuring in Group Settings](../../group/index.md#enabling-delayed-project-removal-premium).
+
+The default period is 7 days, and can be changed. Setting this period to 0 will enable immediate removal
of projects or groups.
To change this period:
diff --git a/doc/user/analytics/value_stream_analytics.md b/doc/user/analytics/value_stream_analytics.md
index 0efe28ac5f7..b11bae98af3 100644
--- a/doc/user/analytics/value_stream_analytics.md
+++ b/doc/user/analytics/value_stream_analytics.md
@@ -332,12 +332,6 @@ The current permissions on the Project Value Stream Analytics dashboard are:
You can [read more about permissions](../../ci/yaml/README.md) in general.
-NOTE: **Note:**
-As of GitLab 12.3, the project-level page is deprecated. You should access
-project-level Value Stream Analytics from **Analytics > Value Stream Analytics** in the top
-navigation bar. We will ensure that the same project-level functionality is available
-to CE users in the new analytics space.
-
For Value Stream Analytics functionality introduced in GitLab 12.3 and later:
- Users must have Reporter access or above.
diff --git a/doc/user/application_security/configuration/index.md b/doc/user/application_security/configuration/index.md
index f0fcd0c4419..229a8572206 100644
--- a/doc/user/application_security/configuration/index.md
+++ b/doc/user/application_security/configuration/index.md
@@ -14,17 +14,23 @@ info: To determine the technical writer assigned to the Stage/Group associated w
The security configuration page displays the configuration state of each of the security
features and can be accessed through a project's sidebar nav.
-![Screenshot of security configuration page](../img/security_configuration_page_v13_1.png)
+![Screenshot of security configuration page](../img/security_configuration_page_v13_2.png)
The page uses the project's latest default branch [CI pipeline](../../../ci/pipelines/index.md) to determine the configuration
state of each feature. If a job with the expected security report artifact exists in the pipeline,
the feature is considered configured.
-NOTE: **Note:** if the latest pipeline used [Auto DevOps](../../../topics/autodevops/index.md),
+NOTE: **Note:**
+If the latest pipeline used [Auto DevOps](../../../topics/autodevops/index.md),
all security features will be configured by default.
## Limitations
-It is not possible to enable or disable a feature using the configuration page.
-However, instructions on how to enable or disable a feature can be found through
-the links next to each feature on that page.
+It is not yet possible to enable or disable most features using the
+configuration page. However, instructions on how to enable or disable a feature
+can be found through the links next to each feature on that page.
+
+If a project does not have an existing CI configuration, then the SAST feature
+can be enabled by clicking on the "Enable with Merge Request" button under the
+"Manage" column. Future work will expand this to editing _existing_ CI
+configurations, and to other security features.
diff --git a/doc/user/application_security/container_scanning/img/container_scanning_v13_0.png b/doc/user/application_security/container_scanning/img/container_scanning_v13_0.png
deleted file mode 100644
index 7a079a65072..00000000000
--- a/doc/user/application_security/container_scanning/img/container_scanning_v13_0.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/application_security/container_scanning/img/container_scanning_v13_2.png b/doc/user/application_security/container_scanning/img/container_scanning_v13_2.png
new file mode 100644
index 00000000000..254ea1dcf5d
--- /dev/null
+++ b/doc/user/application_security/container_scanning/img/container_scanning_v13_2.png
Binary files differ
diff --git a/doc/user/application_security/container_scanning/index.md b/doc/user/application_security/container_scanning/index.md
index 0ffe83cdfc9..7bc8b62825c 100644
--- a/doc/user/application_security/container_scanning/index.md
+++ b/doc/user/application_security/container_scanning/index.md
@@ -32,7 +32,7 @@ You can enable container scanning by doing one of the following:
GitLab compares the found vulnerabilities between the source and target branches, and shows the
information directly in the merge request.
-![Container Scanning Widget](img/container_scanning_v13_0.png)
+![Container Scanning Widget](img/container_scanning_v13_2.png)
<!-- NOTE: The container scanning tool references the following heading in the code, so if you
make a change to this heading, make sure to update the documentation URLs used in the
@@ -58,10 +58,10 @@ To enable Container Scanning in your pipeline, you need the following:
```yaml
build:
- image: docker:19.03.11
+ image: docker:19.03.12
stage: build
services:
- - docker:19.03.11-dind
+ - docker:19.03.12-dind
variables:
IMAGE_TAG: $CI_REGISTRY_IMAGE/$CI_COMMIT_REF_SLUG:$CI_COMMIT_SHA
script:
@@ -114,7 +114,7 @@ build:
image: docker:stable
stage: build
services:
- - docker:19.03.11-dind
+ - docker:19.03.12-dind
variables:
IMAGE: $CI_REGISTRY_IMAGE/$CI_COMMIT_REF_SLUG:$CI_COMMIT_SHA
script:
@@ -141,7 +141,7 @@ enables verbose output from Clair by setting the `CLAIR_OUTPUT` environment vari
```yaml
include:
- template: Container-Scanning.gitlab-ci.yml
+ - template: Container-Scanning.gitlab-ci.yml
variables:
CLAIR_OUTPUT: High
@@ -174,6 +174,7 @@ using environment variables.
| `CLAIR_DB_IMAGE_TAG` | (**DEPRECATED - use `CLAIR_DB_IMAGE` instead**) The Docker image tag for the [PostgreSQL server hosting the vulnerabilities definitions](https://hub.docker.com/r/arminc/clair-db). It can be useful to override this value with a specific version, for example, to provide a consistent set of vulnerabilities for integration testing purposes. | `latest` |
| `DOCKERFILE_PATH` | The path to the `Dockerfile` to be used for generating remediations. By default, the scanner will look for a file named `Dockerfile` in the root directory of the project, so this variable should only be configured if your `Dockerfile` is in a non-standard location, such as a subdirectory. See [Solutions for vulnerabilities](#solutions-for-vulnerabilities-auto-remediation) for more details. | `Dockerfile` |
| `ADDITIONAL_CA_CERT_BUNDLE` | Bundle of CA certs that you want to trust. | "" |
+| `SECURE_LOG_LEVEL` | The log levels available are: `fatal`, `error`, `warn`, `info`, `debug` | `info` |
### Overriding the Container Scanning template
@@ -183,7 +184,7 @@ specify any additional keys. For example:
```yaml
include:
- template: Container-Scanning.gitlab-ci.yml
+ - template: Container-Scanning.gitlab-ci.yml
container_scanning:
variables:
@@ -195,15 +196,15 @@ GitLab 13.0 and later doesn't support [`only` and `except`](../../../ci/yaml/REA
When overriding the template, you must use [`rules`](../../../ci/yaml/README.md#rules)
instead.
-### Vulnerability whitelisting
+### Vulnerability allowlisting
-To whitelist specific vulnerabilities, follow these steps:
+To allowlist specific vulnerabilities, follow these steps:
1. Set `GIT_STRATEGY: fetch` in your `.gitlab-ci.yml` file by following the instructions in
[overriding the Container Scanning template](#overriding-the-container-scanning-template).
-1. Define the whitelisted vulnerabilities in a YAML file named `clair-whitelist.yml`. This must use
- the format described in the [whitelist example file](https://github.com/arminc/clair-scanner/blob/v12/example-whitelist.yaml).
-1. Add the `clair-whitelist.yml` file to your project's Git repository.
+1. Define the allowlisted vulnerabilities in a YAML file named `vulnerability-allowlist.yml`. This must use
+ the format described in the [allowlist example file](https://gitlab.com/gitlab-org/security-products/analyzers/klar/-/raw/master/testdata/vulnerability-allowlist.yml).
+1. Add the `vulnerability-allowlist.yml` file to your project's Git repository.
### Running Container Scanning in an offline environment
@@ -282,7 +283,7 @@ stages:
build_latest_vulnerabilities:
stage: build
services:
- - docker:19.03.11-dind
+ - docker:19.03.12-dind
script:
- docker pull arminc/clair-db:latest
- docker tag arminc/clair-db:latest $CI_REGISTRY/namespace/clair-vulnerabilities-db
diff --git a/doc/user/application_security/coverage_fuzzing/index.md b/doc/user/application_security/coverage_fuzzing/index.md
new file mode 100644
index 00000000000..85da7d85506
--- /dev/null
+++ b/doc/user/application_security/coverage_fuzzing/index.md
@@ -0,0 +1,117 @@
+---
+stage: Secure
+group: Fuzz Testing
+info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers
+type: reference, howto
+---
+
+# Coverage Guided Fuzz Testing **(ULTIMATE)**
+
+> [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/3226) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 13.2 as an [Alpha feature](https://about.gitlab.com/handbook/product/gitlab-the-product/#alpha).
+
+GitLab allows you to add coverage-guided fuzz testing to your pipelines. This helps you discover
+bugs and potential security issues that other QA processes may miss. Coverage-guided fuzzing sends
+random inputs to an instrumented version of your application in an effort to cause unexpected
+behavior, such as a crash. Such behavior indicates a bug that you should address.
+
+We recommend that you use fuzz testing in addition to the other security scanners in [GitLab Secure](../index.md)
+and your own test processes. If you're using [GitLab CI/CD](../../../ci/README.md),
+you can run your coverage guided fuzz tests as part your CI/CD workflow. You can take advantage of
+Coverage Guided Fuzzing by including the CI job in your existing `.gitlab-ci.yml` file.
+
+## Supported fuzzing engines and languages
+
+GitLab supports these languages through the fuzzing engine listed for each. We currently provide a Docker image for apps written in Go, but you can test the other languages below by providing a Docker image with the fuzz engine to run your app.
+
+| Language | Fuzzing Engine | Example |
+|----------|---------------------------------------------------------------------------|---------|
+| C/C++ | [libFuzzer](https://llvm.org/docs/LibFuzzer.html) | |
+| GoLang | [go-fuzz (libFuzzer support)](https://github.com/dvyukov/go-fuzz) | |
+| Rust | [cargo-fuzz (libFuzzer support)](https://github.com/rust-fuzz/cargo-fuzz) | |
+
+## Configuration
+
+To enable fuzzing, you must
+[include](../../../ci/yaml/README.md#includetemplate)
+the [`Coverage-Fuzzing.gitlab-ci.yml` template](https://gitlab.com/gitlab-org/gitlab/blob/master/lib/gitlab/ci/templates/Security/Coverage-Fuzzing.gitlab-ci.yml)
+provided as part of your GitLab installation.
+
+To do so, add the following to your `.gitlab-ci.yml` file:
+
+```yaml
+include:
+ - template: Coverage-Fuzzing.gitlab-ci.yml
+```
+
+The included template makes available the [hidden job](../../../ci/yaml/README.md#hide-jobs)
+`.fuzz_base`, which you must [extend](../../../ci/yaml/README.md#extends) for each of your fuzz
+targets. Each fuzz target **must** have a separate job. For example, the
+[go-fuzzing-example project](https://gitlab.com/gitlab-org/security-products/demos/go-fuzzing-example)
+contains one job that extends `.fuzz_base` for its single fuzz target.
+
+The `my_fuzz_target` job (the separate job for your fuzz target) does the following:
+
+- Extends `.fuzz_base`.
+- Compiles the fuzz target with [go-fuzz](https://github.com/dvyukov/go-fuzz).
+- Runs the target with the `gitlab-cov-fuzz` command, which is available to each job that extends
+ `.fuzz_base`.
+- Runs on a fuzz stage that usually comes after a test stage.
+
+The `gitlab-cov-fuzz` is a command-line tool that runs the instrumented application. It parses and
+analyzes the exception information that the fuzzer outputs. It also downloads the [corpus](#glossary)
+and crash events from previous pipelines automatically. This helps your fuzz targets build on the progress of
+previous fuzzing jobs. The parsed crash events and data are written to
+`gl-coverage-fuzzing-report.json`.
+
+### Artifacts
+
+Each fuzzing step outputs these artifacts:
+
+- `gl-coverage-fuzzing-report.json`: This file's format may change in future releases.
+- `artifacts.zip`: This file contains two directories:
+ - `corpus`: Holds all test cases generated by the current and all previous jobs.
+ - `crashes`: Holds all crash events the current job encountered as well as those not fixed in
+ previous jobs.
+
+### Types of Fuzzing Jobs
+
+There are two types of jobs:
+
+- Fuzzing: Standard fuzzing session. You can configure a long session through a user defined
+ timeout.
+- Regression: Run the fuzz targets through the accumulated test cases generated by previous fuzzing
+ sessions plus fixed crashes from previous sessions. This is usually very quick.
+
+Here's our current suggestion for configuring your fuzz target's timeout:
+
+- Set `COVERAGE_FUZZING_BRANCH` to the branch where you want to run long-running (async) fuzzing
+ jobs. This is `master` by default.
+- Use regression or short-running fuzzing jobs for other branches or merge requests.
+
+This suggestion helps find new bugs on the development branch and catch old bugs in merge requests
+(like unit tests).
+
+You can configure this by passing `--regression=false/true` to `gitlab-cov-fuzz` as the [Go example](https://gitlab.com/gitlab-org/security-products/demos/go-fuzzing-example/-/blob/master/.gitlab-ci.yml)
+shows. Also note that `gitlab-cov-fuzz` is a wrapper, so you can pass those arguments to configure
+any option available in the underlying fuzzing engine.
+
+### Available variables
+
+| Environment variable | Description |
+|---------------------------|--------------------------------------------------------------------|
+| `COVERAGE_FUZZING_BRANCH` | The branch for long-running fuzzing jobs. The default is `master`. |
+
+### Additional Configuration
+
+The `gitlab-cov-fuzz` command passes all arguments it receives to the underlying fuzzing engine. You
+can therefore use all the options available in that fuzzing engine. For more information on these
+options, see the underlying fuzzing engine's documentation.
+
+### Glossary
+
+- Seed corpus: The set of test cases given as initial input to the fuzz target. This usually speeds
+ up the fuzz target substantially. This can be either manually created test cases or auto-generated
+ with the fuzz target itself from previous runs.
+- Corpus: The set of meaningful test cases that are generated while the fuzzer is running. Each
+ meaningful test case produces new coverage in the tested program. It's advised to re-use the
+ corpus and pass it to subsequent runs.
diff --git a/doc/user/application_security/dast/img/dast_all_v13_0.png b/doc/user/application_security/dast/img/dast_all_v13_0.png
deleted file mode 100644
index 7b67fc44fae..00000000000
--- a/doc/user/application_security/dast/img/dast_all_v13_0.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/application_security/dast/img/dast_on_demand_v13_2.png b/doc/user/application_security/dast/img/dast_on_demand_v13_2.png
new file mode 100644
index 00000000000..8a733c27be1
--- /dev/null
+++ b/doc/user/application_security/dast/img/dast_on_demand_v13_2.png
Binary files differ
diff --git a/doc/user/application_security/dast/img/dast_v13_2.png b/doc/user/application_security/dast/img/dast_v13_2.png
new file mode 100644
index 00000000000..bbf7944eb40
--- /dev/null
+++ b/doc/user/application_security/dast/img/dast_v13_2.png
Binary files differ
diff --git a/doc/user/application_security/dast/index.md b/doc/user/application_security/dast/index.md
index 256daae46d7..d68928d858b 100644
--- a/doc/user/application_security/dast/index.md
+++ b/doc/user/application_security/dast/index.md
@@ -9,9 +9,9 @@ type: reference, howto
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/4348) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 10.4.
-NOTE: **4 of the top 6 attacks were application based.**
-Download our whitepaper,
-["A Seismic Shift in Application Security"](https://about.gitlab.com/resources/whitepaper-seismic-shift-application-security/)
+NOTE: **Note:**
+The whitepaper ["A Seismic Shift in Application Security"](https://about.gitlab.com/resources/whitepaper-seismic-shift-application-security/)
+explains how **4 of the top 6 attacks were application based**. Download it
to learn how to protect your organization.
Running [static checks](../sast/index.md) on your code is the first step to detect
@@ -36,7 +36,7 @@ NOTE: **Note:**
This comparison logic uses only the latest pipeline executed for the target branch's base commit.
Running the pipeline on any other commit has no effect on the merge request.
-![DAST Widget](img/dast_all_v13_0.png)
+![DAST Widget](img/dast_v13_2.png)
By clicking on one of the detected linked vulnerabilities, you can
see the details and the URL(s) affected.
@@ -44,10 +44,10 @@ see the details and the URL(s) affected.
![DAST Widget Clicked](img/dast_single_v13_0.png)
[Dynamic Application Security Testing (DAST)](https://en.wikipedia.org/wiki/Dynamic_Application_Security_Testing)
-uses the popular open source tool [OWASP ZAProxy](https://github.com/zaproxy/zaproxy)
+uses the popular open source tool [OWASP Zed Attack Proxy](https://www.zaproxy.org/)
to perform an analysis on your running web application.
-By default, DAST executes [ZAP Baseline Scan](https://github.com/zaproxy/zaproxy/wiki/ZAP-Baseline-Scan)
+By default, DAST executes [ZAP Baseline Scan](https://www.zaproxy.org/docs/docker/baseline-scan/)
and performs passive scanning only. It won't actively attack your application.
However, DAST can be [configured](#full-scan)
to also perform an *active scan*: attack your application and produce a more extensive security report.
@@ -143,6 +143,22 @@ The only changes to the site should be from the DAST scanner. Be aware that any
changes that users, scheduled tasks, database changes, code changes, other pipelines, or other scanners make to
the site during a scan could lead to inaccurate results.
+### Hide sensitive information
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/36332) in GitLab 13.1.
+
+HTTP request and response headers may contain sensitive information, including cookies and
+authorization credentials. By default, the following headers are masked:
+
+- `Authorization`.
+- `Proxy-Authorization`.
+- `Set-Cookie` (values only).
+- `Cookie` (values only).
+
+Using the [`DAST_MASK_HTTP_HEADERS` variable](#available-variables), you can list the
+headers whose values you want masked. For details on how to mask headers, see
+[Customizing the DAST settings](#customizing-the-dast-settings).
+
### Authentication
It's also possible to authenticate the user before performing the DAST checks.
@@ -398,6 +414,10 @@ variables:
### Customizing the DAST settings
+CAUTION: **Deprecation:**
+Beginning in GitLab 13.0, the use of [`only` and `except`](../../../ci/yaml/README.md#onlyexcept-basic)
+is no longer supported. When overriding the template, you must use [`rules`](../../../ci/yaml/README.md#rules) instead.
+
The DAST settings can be changed through environment variables by using the
[`variables`](../../../ci/yaml/README.md#variables) parameter in `.gitlab-ci.yml`.
These variables are documented in [available variables](#available-variables).
@@ -410,68 +430,43 @@ include:
variables:
DAST_WEBSITE: https://example.com
- DAST_TARGET_AVAILABILITY_TIMEOUT: 120
+ DAST_SPIDER_MINS: 120
```
Because the template is [evaluated before](../../../ci/yaml/README.md#include) the pipeline
configuration, the last mention of the variable will take precedence.
-### Overriding the DAST template
-
-CAUTION: **Deprecation:**
-Beginning in GitLab 13.0, the use of [`only` and `except`](../../../ci/yaml/README.md#onlyexcept-basic)
-is no longer supported. When overriding the template, you must use [`rules`](../../../ci/yaml/README.md#rules) instead.
-
-If you want to override the job definition (for example, change properties like
-`variables` or `dependencies`), you need to declare a `dast` job after the
-template inclusion and specify any additional keys under it. For example:
-
-```yaml
-include:
- - template: DAST.gitlab-ci.yml
-
-dast:
- stage: dast # IMPORTANT: don't forget to add this
- variables:
- DAST_WEBSITE: https://example.com
- CI_DEBUG_TRACE: "true"
-```
-
-As the DAST job belongs to a separate `dast` stage that runs after all
-[default stages](../../../ci/yaml/README.md#stages),
-don't forget to add `stage: dast` when you override the template job definition.
-
### Available variables
DAST can be [configured](#customizing-the-dast-settings) using environment variables.
-| Environment variable | Required | Description |
+| Environment variable | Type | Description |
|-----------------------------| -----------|--------------------------------------------------------------------------------|
-| `SECURE_ANALYZERS_PREFIX` | no | Set the Docker registry base address from which to download the analyzer. |
-| `DAST_WEBSITE` | no| The URL of the website to scan. `DAST_API_SPECIFICATION` must be specified if this is omitted. |
-| `DAST_API_SPECIFICATION` | no | The API specification to import. `DAST_WEBSITE` must be specified if this is omitted. |
-| `DAST_AUTH_URL` | no | The authentication URL of the website to scan. Not supported for API scans. |
-| `DAST_USERNAME` | no | The username to authenticate to in the website. |
-| `DAST_PASSWORD` | no | The password to authenticate to in the website. |
-| `DAST_USERNAME_FIELD` | no | The name of username field at the sign-in HTML form. |
-| `DAST_PASSWORD_FIELD` | no | The name of password field at the sign-in HTML form. |
-| `DAST_AUTH_EXCLUDE_URLS` | no | The URLs to skip during the authenticated scan; comma-separated, no spaces in between. Not supported for API scans. |
-| `DAST_TARGET_AVAILABILITY_TIMEOUT` | no | Time limit in seconds to wait for target availability. Scan is attempted nevertheless if it runs out. Integer. Defaults to `60`. |
-| `DAST_FULL_SCAN_ENABLED` | no | Switches the tool to execute [ZAP Full Scan](https://github.com/zaproxy/zaproxy/wiki/ZAP-Full-Scan) instead of [ZAP Baseline Scan](https://github.com/zaproxy/zaproxy/wiki/ZAP-Baseline-Scan). Boolean. `true`, `True`, or `1` are considered as true value, otherwise false. Defaults to `false`. |
-| `DAST_FULL_SCAN_DOMAIN_VALIDATION_REQUIRED` | no | Requires [domain validation](#domain-validation) when running DAST full scans. Boolean. `true`, `True`, or `1` are considered as true value, otherwise false. Defaults to `false`. Not supported for API scans. |
-| `DAST_AUTO_UPDATE_ADDONS` | no | By default the versions of ZAP add-ons are pinned to those provided with the DAST image. Set to `true` to allow ZAP to download the latest versions. |
-| `DAST_API_HOST_OVERRIDE` | no | Used to override domains defined in API specification files. |
-| `DAST_EXCLUDE_RULES` | no | Set to a comma-separated list of Vulnerability Rule IDs to exclude them from the scan report. Currently, excluded rules will get executed but the alerts from them will be suppressed. Rule IDs are numbers and can be found from the DAST log or on the [ZAP project](https://github.com/zaproxy/zaproxy/blob/develop/docs/scanners.md). For example, `HTTP Parameter Override` has a rule ID of `10026`. |
-| `DAST_REQUEST_HEADERS` | no | Set to a comma-separated list of request header names and values. For example, `Cache-control: no-cache,User-Agent: DAST/1.0` |
-| `DAST_DEBUG` | no | Enable debug message output. Boolean. `true`, `True`, or `1` are considered as true value, otherwise false. Defaults to `false`. |
-| `DAST_SPIDER_MINS` | no | The maximum duration of the spider scan in minutes. Set to zero for unlimited. Defaults to one minute, or unlimited when the scan is a full scan. |
-| `DAST_HTML_REPORT` | no | The file name of the HTML report written at the end of a scan. |
-| `DAST_MARKDOWN_REPORT` | no | The file name of the Markdown report written at the end of a scan. |
-| `DAST_XML_REPORT` | no | The file name of the XML report written at the end of a scan. |
-| `DAST_INCLUDE_ALPHA_VULNERABILITIES` | no | Include alpha passive and active scan rules. Boolean. `true`, `True`, or `1` are considered as true value, otherwise false. Defaults to `false`. |
-| `DAST_USE_AJAX_SPIDER` | no | Use the AJAX spider in addition to the traditional spider, useful for crawling sites that require JavaScript. Boolean. `true`, `True`, or `1` are considered as true value, otherwise false. Defaults to `false`. |
-| `DAST_ZAP_CLI_OPTIONS` | no | ZAP server command-line options. For example, `-Xmx3072m` would set the Java maximum memory allocation pool size. |
-| `DAST_ZAP_LOG_CONFIGURATION` | no | Set to a semicolon-separated list of additional log4j properties for the ZAP Server. For example, `log4j.logger.org.parosproxy.paros.network.HttpSender=DEBUG` |
+| `SECURE_ANALYZERS_PREFIX` | URL | Set the Docker registry base address from which to download the analyzer. |
+| `DAST_WEBSITE` | URL | The URL of the website to scan. `DAST_API_SPECIFICATION` must be specified if this is omitted. |
+| `DAST_API_SPECIFICATION` | URL or string | The API specification to import. The specification can be hosted at a URL, or the name of a file present in the `/zap/wrk` directory. `DAST_WEBSITE` must be specified if this is omitted. |
+| `DAST_AUTH_URL` | URL | The URL of the page containing the sign-in HTML form on the target website. `DAST_USERNAME` and `DAST_PASSWORD` will be submitted with the login form to create an authenticated scan. Not supported for API scans. |
+| `DAST_USERNAME` | string | The username to authenticate to in the website. |
+| `DAST_PASSWORD` | string | The password to authenticate to in the website. |
+| `DAST_USERNAME_FIELD` | string | The name of username field at the sign-in HTML form. |
+| `DAST_PASSWORD_FIELD` | string | The name of password field at the sign-in HTML form. |
+| `DAST_MASK_HTTP_HEADERS` | string | Comma-separated list of request and response headers to be masked (introduced in GitLab 13.1). Must contain **all** headers to be masked. Refer to [list of headers that are masked by default](#hide-sensitive-information). |
+| `DAST_AUTH_EXCLUDE_URLS` | URLs | The URLs to skip during the authenticated scan; comma-separated, no spaces in between. Not supported for API scans. |
+| `DAST_FULL_SCAN_ENABLED` | boolean | Set to `true` to run a [ZAP Full Scan](https://github.com/zaproxy/zaproxy/wiki/ZAP-Full-Scan) instead of a [ZAP Baseline Scan](https://github.com/zaproxy/zaproxy/wiki/ZAP-Baseline-Scan). Default: `false` |
+| `DAST_FULL_SCAN_DOMAIN_VALIDATION_REQUIRED` | boolean | Set to `true` to require [domain validation](#domain-validation) when running DAST full scans. Not supported for API scans. Default: `false` |
+| `DAST_AUTO_UPDATE_ADDONS` | boolean | ZAP add-ons are pinned to specific versions in the DAST Docker image. Set to `true` to download the latest versions when the scan starts. Default: `false` |
+| `DAST_API_HOST_OVERRIDE` | string | Used to override domains defined in API specification files. Example: `example.com:8080` |
+| `DAST_EXCLUDE_RULES` | string | Set to a comma-separated list of Vulnerability Rule IDs to exclude them from running during the scan. Rule IDs are numbers and can be found from the DAST log or on the [ZAP project](https://github.com/zaproxy/zaproxy/blob/develop/docs/scanners.md). For example, `HTTP Parameter Override` has a rule ID of `10026`. **Note:** In earlier versions of GitLab the excluded rules were executed but alerts they generated were supressed. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/118641) in GitLab 12.10. |
+| `DAST_REQUEST_HEADERS` | string | Set to a comma-separated list of request header names and values. Headers will be added to every request made by DAST. For example, `Cache-control: no-cache,User-Agent: DAST/1.0` |
+| `DAST_DEBUG` | boolean | Enable debug message output. Default: `false` |
+| `DAST_SPIDER_MINS` | number | The maximum duration of the spider scan in minutes. Set to `0` for unlimited. Default: One minute, or unlimited when the scan is a full scan. |
+| `DAST_HTML_REPORT` | string | The file name of the HTML report written at the end of a scan. |
+| `DAST_MARKDOWN_REPORT` | string | The file name of the Markdown report written at the end of a scan. |
+| `DAST_XML_REPORT` | string | The file name of the XML report written at the end of a scan. |
+| `DAST_INCLUDE_ALPHA_VULNERABILITIES` | boolean | Set to `true` to include alpha passive and active scan rules. Default: `false` |
+| `DAST_USE_AJAX_SPIDER` | boolean | Set to `true` to use the AJAX spider in addition to the traditional spider, useful for crawling sites that require JavaScript. Default: `false` |
+| `DAST_ZAP_CLI_OPTIONS` | string | ZAP server command-line options. For example, `-Xmx3072m` would set the Java maximum memory allocation pool size. |
+| `DAST_ZAP_LOG_CONFIGURATION` | string | Set to a semicolon-separated list of additional log4j properties for the ZAP Server. For example, `log4j.logger.org.parosproxy.paros.network.HttpSender=DEBUG;log4j.logger.com.crawljax=DEBUG` |
### DAST command-line options
@@ -532,19 +527,20 @@ A DAST job has two executing processes:
Debug mode of the scripts can be enabled by using the `DAST_DEBUG` environment variable. This can help when troubleshooting the job,
and will output statements indicating what percentage of the scan is complete.
-For details on using variables, see [Overriding the DAST template](#overriding-the-dast-template).
+For details on using variables, see [Overriding the DAST template](#customizing-the-dast-settings).
Debug mode of the ZAP server can be enabled using the `DAST_ZAP_LOG_CONFIGURATION` environment variable.
The following table outlines examples of values that can be set and the effect that they have on the output that is logged.
Multiple values can be specified, separated by semicolons.
-| Log configuration value | Effect |
-|-------------------------------------------------- | ----------------------------------------------------------------- |
-| `log4j.rootLogger=DEBUG` | Enable all debug logging statements. |
-| `log4j.logger.org.apache.commons.httpclient=DEBUG` | Log every HTTP request and response made by the ZAP server. |
-| `log4j.logger.com.crawljax=DEBUG` | Enable Ajax Crawler debug logging statements. |
-| `log4j.logger.org.parosproxy.paros=DEBUG` | Enable ZAP server proxy debug logging statements. |
-| `log4j.logger.org.zaproxy.zap=DEBUG` | Enable debug logging statements of the general ZAP server code. |
+| Log configuration value | Effect |
+|-------------------------------------------------- | ----------------------------------------------------------------- |
+| `log4j.rootLogger=DEBUG` | Enable all debug logging statements. |
+| `log4j.logger.org.apache.commons.httpclient=DEBUG` | Log every HTTP request and response made by the ZAP server. |
+| `log4j.logger.org.zaproxy.zap.spider.SpiderController=DEBUG` | Log URLs found during the spider scan of the target. |
+| `log4j.logger.com.crawljax=DEBUG` | Enable Ajax Crawler debug logging statements. |
+| `log4j.logger.org.parosproxy.paros=DEBUG` | Enable ZAP server proxy debug logging statements. |
+| `log4j.logger.org.zaproxy.zap=DEBUG` | Enable debug logging statements of the general ZAP server code. |
## Running DAST in an offline environment
@@ -604,6 +600,44 @@ security reports without requiring internet access.
Alternatively, you can use the variable `SECURE_ANALYZERS_PREFIX` to override the base registry address of the `dast` image.
+## On-Demand Scans
+
+> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/218465) in GitLab 13.2.
+> - It's deployed behind a feature flag, disabled by default.
+> - It's disabled on GitLab.com.
+> - It's able to be enabled or disabled per-project.
+> - To use it in GitLab self-managed instances, ask a GitLab administrator to [enable it](#enable-or-disable-on-demand-scans).
+
+Passive DAST scans may be run on demand against a target website, outside the DevOps lifecycle. These scans will
+always be associated with the default or `master` branch of your project and the results can be seen in the project dashboard.
+
+![DAST On-Demand Scan](img/dast_on_demand_v13_2.png)
+
+### Enable or disable On-Demand Scans
+
+On-Demand Scans is under development and not ready for production use. It is
+deployed behind a feature flag that is **disabled by default**.
+[GitLab administrators with access to the GitLab Rails console](../../../administration/feature_flags.md)
+can enable it for your instance. On-Demand Scans can be enabled or disabled per-project
+
+To enable it:
+
+```ruby
+# Instance-wide
+Feature.enable(:security_on_demand_scans_feature_flag)
+# or by project
+Feature.enable(:security_on_demand_scans_feature_flag, Project.find(<project id>))
+```
+
+To disable it:
+
+```ruby
+# Instance-wide
+Feature.disable(:security_on_demand_scans_feature_flag)
+# or by project
+Feature.disable(:security_on_demand_scans_feature_flag, Project.find(<project id>))
+```
+
## Reports
The DAST tool outputs a report file in JSON format by default. However, this tool can also generate reports in
@@ -683,18 +717,6 @@ Once a vulnerability is found, you can interact with it. Read more on how to
For more information about the vulnerabilities database update, check the
[maintenance table](../index.md#maintenance-and-update-of-the-vulnerabilities-database).
-<!-- ## Troubleshooting
-
-Include any troubleshooting steps that you can foresee. If you know beforehand what issues
-one might have when setting this up, or when something is changed, or on upgrading, it's
-important to describe those, too. Think of things that may go wrong and include them here.
-This is important to minimize requests for support, and to avoid doc comments with
-questions that you know someone might ask.
-
-Each scenario can be a third-level heading, e.g. `### Getting error message X`.
-If you have none to add when creating a doc, leave this section in place
-but commented out to help encourage others to add to it in the future. -->
-
## Optimizing DAST
By default, DAST will download all artifacts defined by previous jobs in the pipeline. If
@@ -734,3 +756,15 @@ variables:
Here, DAST is being allocated 3072 MB.
Change the number after `-Xmx` to the required memory amount.
+
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
diff --git a/doc/user/application_security/dependency_scanning/analyzers.md b/doc/user/application_security/dependency_scanning/analyzers.md
index 474f9339d0b..ca2b212ffc3 100644
--- a/doc/user/application_security/dependency_scanning/analyzers.md
+++ b/doc/user/application_security/dependency_scanning/analyzers.md
@@ -1,3 +1,10 @@
+---
+type: reference, howto
+stage: Secure
+group: Composition Analysis
+info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers
+---
+
# Dependency Scanning Analyzers **(ULTIMATE)**
Dependency Scanning relies on underlying third party tools that are wrapped into
diff --git a/doc/user/application_security/dependency_scanning/img/dependency_scanning_v13_0.png b/doc/user/application_security/dependency_scanning/img/dependency_scanning_v13_0.png
deleted file mode 100644
index 9f3990df957..00000000000
--- a/doc/user/application_security/dependency_scanning/img/dependency_scanning_v13_0.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/application_security/dependency_scanning/img/dependency_scanning_v13_2.png b/doc/user/application_security/dependency_scanning/img/dependency_scanning_v13_2.png
new file mode 100644
index 00000000000..28c4eb85b7c
--- /dev/null
+++ b/doc/user/application_security/dependency_scanning/img/dependency_scanning_v13_2.png
Binary files differ
diff --git a/doc/user/application_security/dependency_scanning/index.md b/doc/user/application_security/dependency_scanning/index.md
index 84ec0ec976d..57b4fae3230 100644
--- a/doc/user/application_security/dependency_scanning/index.md
+++ b/doc/user/application_security/dependency_scanning/index.md
@@ -27,7 +27,7 @@ GitLab checks the Dependency Scanning report, compares the found vulnerabilities
between the source and target branches, and shows the information on the
merge request.
-![Dependency Scanning Widget](img/dependency_scanning_v13_0.png)
+![Dependency Scanning Widget](img/dependency_scanning_v13_2.png)
The results are sorted by the severity of the vulnerability:
@@ -61,7 +61,7 @@ The following languages and dependency managers are supported:
| Language (package managers) | Supported files | Scan tool(s) |
|----------------------------- | --------------- | ------------ |
| Java ([Gradle](https://gradle.org/), [Maven](https://maven.apache.org/)) | `build.gradle`, `build.gradle.kts`, `pom.xml` | [Gemnasium](https://gitlab.com/gitlab-org/security-products/gemnasium) |
-| JavaScript ([npm](https://www.npmjs.com/), [yarn](https://yarnpkg.com/en/)) | `package-lock.json`, `npm-shrinkwrap.json`, `yarn.lock` | [Gemnasium](https://gitlab.com/gitlab-org/security-products/gemnasium), [Retire.js](https://retirejs.github.io/retire.js) |
+| JavaScript ([npm](https://www.npmjs.com/), [yarn](https://classic.yarnpkg.com/en/)) | `package-lock.json`, `npm-shrinkwrap.json`, `yarn.lock` | [Gemnasium](https://gitlab.com/gitlab-org/security-products/gemnasium), [Retire.js](https://retirejs.github.io/retire.js/) |
| Go ([Golang](https://golang.org/)) | `go.sum` | [Gemnasium](https://gitlab.com/gitlab-org/security-products/gemnasium) |
| PHP ([Composer](https://getcomposer.org/)) | `composer.lock` | [Gemnasium](https://gitlab.com/gitlab-org/security-products/gemnasium) |
| Python ([setuptools](https://setuptools.readthedocs.io/en/latest/), [pip](https://pip.pypa.io/en/stable/), [Pipenv](https://pipenv.pypa.io/en/latest/)) | `setup.py`, `requirements.txt`, `requirements.pip`, `requires.txt`, `Pipfile` | [Gemnasium](https://gitlab.com/gitlab-org/security-products/gemnasium) |
@@ -72,7 +72,7 @@ Plans are underway for supporting the following languages, dependency managers,
| Language (package managers) | Supported files | Scan tool(s) | Issue |
|----------------------------- | --------------- | ------------ | ----- |
-| Python ([Poetry](https://poetry.eustace.io/)) | `poetry.lock` | [Gemnasium](https://gitlab.com/gitlab-org/security-products/gemnasium) | [GitLab#7006](https://gitlab.com/gitlab-org/gitlab/issues/7006) |
+| Python ([Poetry](https://python-poetry.org/)) | `poetry.lock` | [Gemnasium](https://gitlab.com/gitlab-org/security-products/gemnasium) | [GitLab#7006](https://gitlab.com/gitlab-org/gitlab/-/issues/7006) |
| Python ([Pipenv](https://pipenv.pypa.io/en/latest/)) | `Pipfile.lock` | [Gemnasium](https://gitlab.com/gitlab-org/security-products/gemnasium) | [GitLab#11756](https://gitlab.com/gitlab-org/gitlab/-/issues/11756) |
## Contribute your scanner
@@ -151,11 +151,11 @@ The following variables allow configuration of global dependency scanning settin
| Environment variable | Description |
| --------------------------------------- |------------ |
| `SECURE_ANALYZERS_PREFIX` | Override the name of the Docker registry providing the official default images (proxy). Read more about [customizing analyzers](analyzers.md). |
-| `DS_ANALYZER_IMAGE_PREFIX` | **DEPRECATED:** Use `SECURE_ANALYZERS_PREFIX` instead. |
| `DS_DEFAULT_ANALYZERS` | Override the names of the official default images. Read more about [customizing analyzers](analyzers.md). |
| `DS_DISABLE_DIND` | Disable Docker-in-Docker and run analyzers [individually](#enabling-docker-in-docker). This variable is `true` by default. |
| `ADDITIONAL_CA_CERT_BUNDLE` | Bundle of CA certs to trust. |
| `DS_EXCLUDED_PATHS` | Exclude vulnerabilities from output based on the paths. A comma-separated list of patterns. Patterns can be globs, or file or folder paths (for example, `doc,spec`). Parent directories also match patterns. Default: `"spec, test, tests, tmp"` |
+| `SECURE_LOG_LEVEL` | Default log level is `info`, you can set it to any of the following strings: `fatal`, `error`, `warn`, `info`, `debug`. |
#### Configuring Docker-in-Docker orchestrator
@@ -186,6 +186,7 @@ The following variables are used for configuring specific analyzers (used for a
| `DS_PIP_VERSION` | `gemnasium-python` | | Force the install of a specific pip version (example: `"19.3"`), otherwise the pip installed in the Docker image is used. ([Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/12811) in GitLab 12.7) |
| `DS_PIP_DEPENDENCY_PATH` | `gemnasium-python` | | Path to load Python pip dependencies from. ([Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/12412) in GitLab 12.2) |
| `DS_PYTHON_VERSION` | `retire.js` | | Version of Python. If set to 2, dependencies are installed using Python 2.7 instead of Python 3.6. ([Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/12296) in GitLab 12.1)|
+| `DS_JAVA_VERSION` | `gemnasium-maven` | `11` | Version of Java. Available versions: `8`, `11`, `13`, `14`. Maven and Gradle will use the Java version specified by this value. |
| `MAVEN_CLI_OPTS` | `gemnasium-maven` | `"-DskipTests --batch-mode"` | List of command line arguments that will be passed to `maven` by the analyzer. See an example for [using private repositories](../index.md#using-private-maven-repos). |
| `GRADLE_CLI_OPTS` | `gemnasium-maven` | | List of command line arguments that will be passed to `gradle` by the analyzer. |
| `SBT_CLI_OPTS` | `gemnasium-maven` | | List of command-line arguments that the analyzer will pass to `sbt`. |
@@ -428,14 +429,14 @@ For details on saving and transporting Docker images as a file, see Docker's doc
### Set Dependency Scanning CI job variables to use local Dependency Scanning analyzers
Add the following configuration to your `.gitlab-ci.yml` file. You must replace
-`DS_ANALYZER_IMAGE_PREFIX` to refer to your local Docker container registry:
+`SECURE_ANALYZERS_PREFIX` to refer to your local Docker container registry:
```yaml
include:
- template: Dependency-Scanning.gitlab-ci.yml
variables:
- DS_ANALYZER_IMAGE_PREFIX: "docker-registry.example.com/analyzers"
+ SECURE_ANALYZERS_PREFIX: "docker-registry.example.com/analyzers"
GEMNASIUM_DB_REMOTE_URL: "gitlab.example.com/gemnasium-db.git"
GIT_SSL_NO_VERIFY: "true"
```
diff --git a/doc/user/application_security/img/security_configuration_page_v13_1.png b/doc/user/application_security/img/security_configuration_page_v13_1.png
deleted file mode 100644
index 176c64a9e87..00000000000
--- a/doc/user/application_security/img/security_configuration_page_v13_1.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/application_security/img/security_configuration_page_v13_2.png b/doc/user/application_security/img/security_configuration_page_v13_2.png
new file mode 100644
index 00000000000..016328948cc
--- /dev/null
+++ b/doc/user/application_security/img/security_configuration_page_v13_2.png
Binary files differ
diff --git a/doc/user/application_security/index.md b/doc/user/application_security/index.md
index 49580f494a2..3aca4c59423 100644
--- a/doc/user/application_security/index.md
+++ b/doc/user/application_security/index.md
@@ -116,6 +116,44 @@ information with several options:
![Interacting with security reports](img/interacting_with_vulnerability_v13_0.png)
+### View details of a DAST vulnerability
+
+Vulnerabilities detected by DAST occur in the live web application. Rectification of these types of
+vulnerabilities requires specific information. DAST provides the information required to
+investigate and rectify the underlying cause.
+
+To view details of DAST vulnerabilities:
+
+1. To see all vulnerabilities detected:
+
+ - In a project, go to the project's **{shield}** **Security & Compliance** page.
+ - Only in a merge request, go the merge request's **Security** tab.
+
+1. Click on the vulnerability's description. The following details are provided:
+
+ | Field | Description |
+|:-----------------|:------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| Description | Description of the vulnerability. |
+| Project | Namespace and project in which the vulnerability was detected. |
+| Method | HTTP method used to detect the vulnerability. |
+| URL | URL at which the vulnerability was detected. |
+| Request Headers | Headers of the request. |
+| Response Status | Response status received from the application. |
+| Response Headers | Headers of the response received from the application. |
+| Evidence | Evidence of the data found that verified the vulnerability. Often a snippet of the request or response, this can be used to help verify that the finding is a vulnerability. |
+| Identifiers | Identifiers of the vulnerability. |
+| Severity | Severity of the vulnerability. |
+| Scanner Type | Type of vulnerability report. |
+| Links | Links to further details of the detected vulnerability. |
+| Solution | Details of a recommended solution to the vulnerability (optional). |
+
+#### Hide sensitive information in headers
+
+HTTP request and response headers may contain sensitive information, including cookies and
+authorization credentials. By default, content of specific headers are masked in DAST vulnerability
+reports. You can specify the list of all headers to be masked. For details, see
+[Hide sensitive information](dast/index.md#hide-sensitive-information).
+
### Dismissing a vulnerability
To dismiss a vulnerability, you must set its status to Dismissed. Follow these steps to do so:
@@ -258,14 +296,16 @@ An approval is optional when a security report:
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/13067) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 12.3.
-To enable License Approvals, a [project approval rule](../project/merge_requests/merge_request_approvals.md#multiple-approval-rules-premium)
-must be created with the case-sensitive name `License-Check`. This approval group must be set
-with the number of approvals required greater than zero.
+`License-Check` is an approval rule you can enable to allow an individual or group to approve a
+merge request that contains a `denied` license.
+
+You can enable `License-Check` one of two ways:
-Once this group is added to your project, the approval rule is enabled for all Merge Requests. To
-configure how this rule behaves, you can choose which licenses to `allow` or `deny` in the
-[project policies for License Compliance](../compliance/license_compliance/index.md#project-policies-for-license-compliance)
-section.
+- Create a [project approval rule](../project/merge_requests/merge_request_approvals.md#multiple-approval-rules-premium)
+ with the case-sensitive name `License-Check`.
+- Create an approval group in the [project policies section for License Compliance](../compliance/license_compliance/index.md#policies).
+ You must set this approval group's number of approvals required to greater than zero. Once you
+ enable this group in your project, the approval rule is enabled for all merge requests.
Any code changes cause the approvals required to reset.
diff --git a/doc/user/application_security/sast/analyzers.md b/doc/user/application_security/sast/analyzers.md
index 0aa20bf4373..214044ad783 100644
--- a/doc/user/application_security/sast/analyzers.md
+++ b/doc/user/application_security/sast/analyzers.md
@@ -32,7 +32,6 @@ SAST supports the following official analyzers:
- [`security-code-scan`](https://gitlab.com/gitlab-org/security-products/analyzers/security-code-scan) (Security Code Scan (.NET))
- [`sobelow`](https://gitlab.com/gitlab-org/security-products/analyzers/sobelow) (Sobelow (Elixir Phoenix))
- [`spotbugs`](https://gitlab.com/gitlab-org/security-products/analyzers/spotbugs) (SpotBugs with the Find Sec Bugs plugin (Ant, Gradle and wrapper, Grails, Maven and wrapper, SBT))
-- [`tslint`](https://gitlab.com/gitlab-org/security-products/analyzers/tslint) (TSLint (TypeScript))
The analyzers are published as Docker images that SAST will use to launch
dedicated containers for each analysis.
@@ -145,24 +144,24 @@ The [Security Scanner Integration](../../../development/integrations/secure.md)
## Analyzers Data
-| Property \ Tool | Apex | Bandit | Brakeman | ESLint security | Find Sec Bugs | Flawfinder | Gosec | Kubesec Scanner | NodeJsScan | PHP CS Security Audit | Security code Scan (.NET) | Sobelow | TSLint Security |
-| --------------------------------------- | :------------------: | :------------------: | :------------------: | :------------------: | :------------------: | :------------------: | :------------------: | :------------------: | :------------------: | :---------------------: | :-------------------------: | :----------------: | :-------------: |
-| Severity | ✓ | ✓ | ð„‚ | ð„‚ | ✓ | ð„‚ | ✓ | ✓ | ð„‚ | ✓ | ð„‚ | ð„‚ | ✓ |
-| Title | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
-| Description | ✓ | ð„‚ | ð„‚ | ✓ | ✓ | ð„‚ | ð„‚ | ✓ | ✓ | ð„‚ | ð„‚ | ✓ | ✓ |
-| File | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
-| Start line | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ð„‚ | ✓ | ✓ | ✓ | ✓ | ✓ |
-| End line | ✓ | ✓ | ð„‚ | ✓ | ✓ | ð„‚ | ð„‚ | ð„‚ | ð„‚ | ð„‚ | ð„‚ | ð„‚ | ✓ |
-| Start column | ✓ | ð„‚ | ð„‚ | ✓ | ✓ | ✓ | ✓ | ð„‚ | ð„‚ | ✓ | ✓ | ð„‚ | ✓ |
-| End column | ✓ | ð„‚ | ð„‚ | ✓ | ✓ | ð„‚ | ð„‚ | ð„‚ | ð„‚ | ð„‚ | ð„‚ | ð„‚ | ✓ |
-| External ID (e.g. CVE) | ð„‚ | ð„‚ | âš  | ð„‚ | âš  | ✓ | ð„‚ | ð„‚ | ð„‚ | ð„‚ | ð„‚ | ð„‚ | ð„‚ |
-| URLs | ✓ | ð„‚ | ✓ | ð„‚ | âš  | ð„‚ | âš  | ð„‚ | ð„‚ | ð„‚ | ð„‚ | ð„‚ | ð„‚ |
-| Internal doc/explanation | ✓ | âš  | ✓ | ð„‚ | ✓ | ð„‚ | ð„‚ | ð„‚ | ð„‚ | ð„‚ | ð„‚ | ✓ | ð„‚ |
-| Solution | ✓ | ð„‚ | ð„‚ | ð„‚ | âš  | ✓ | ð„‚ | ð„‚ | ð„‚ | ð„‚ | ð„‚ | ð„‚ | ð„‚ |
-| Affected item (e.g. class or package) | ✓ | ð„‚ | ✓ | ð„‚ | ✓ | ✓ | ð„‚ | ✓ | ð„‚ | ð„‚ | ð„‚ | ð„‚ | ð„‚ |
-| Confidence | ð„‚ | ✓ | ✓ | ð„‚ | ✓ | ✓ | ✓ | ✓ | ð„‚ | ð„‚ | ð„‚ | ✓ | ð„‚ |
-| Source code extract | ð„‚ | ✓ | ✓ | ✓ | ð„‚ | ✓ | ✓ | ð„‚ | ð„‚ | ð„‚ | ð„‚ | ð„‚ | ð„‚ |
-| Internal ID | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ð„‚ | ð„‚ | ✓ | ✓ | ✓ | ✓ |
+| Property / Tool | Apex | Bandit | Brakeman | ESLint security | SpotBugs | Flawfinder | Gosec | Kubesec Scanner | NodeJsScan | PHP CS Security Audit | Security code Scan (.NET) | Sobelow |
+| --------------------------------------- | :------------------: | :------------------: | :------------------: | :------------------: | :------------------: | :------------------: | :------------------: | :------------------: | :------------------: | :---------------------: | :-------------------------: | :----------------: |
+| Severity | ✓ | ✓ | ð„‚ | ð„‚ | ✓ | ð„‚ | ✓ | ✓ | ð„‚ | ✓ | ð„‚ | ð„‚ |
+| Title | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
+| Description | ✓ | ð„‚ | ð„‚ | ✓ | ✓ | ð„‚ | ð„‚ | ✓ | ✓ | ð„‚ | ð„‚ | ✓ |
+| File | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
+| Start line | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ð„‚ | ✓ | ✓ | ✓ | ✓ |
+| End line | ✓ | ✓ | ð„‚ | ✓ | ✓ | ð„‚ | ð„‚ | ð„‚ | ð„‚ | ð„‚ | ð„‚ | ð„‚ |
+| Start column | ✓ | ð„‚ | ð„‚ | ✓ | ✓ | ✓ | ✓ | ð„‚ | ð„‚ | ✓ | ✓ | ð„‚ |
+| End column | ✓ | ð„‚ | ð„‚ | ✓ | ✓ | ð„‚ | ð„‚ | ð„‚ | ð„‚ | ð„‚ | ð„‚ | ð„‚ |
+| External ID (e.g. CVE) | ð„‚ | ð„‚ | âš  | ð„‚ | âš  | ✓ | ð„‚ | ð„‚ | ð„‚ | ð„‚ | ð„‚ | ð„‚ |
+| URLs | ✓ | ð„‚ | ✓ | ð„‚ | âš  | ð„‚ | âš  | ð„‚ | ð„‚ | ð„‚ | ð„‚ | ð„‚ |
+| Internal doc/explanation | ✓ | âš  | ✓ | ð„‚ | ✓ | ð„‚ | ð„‚ | ð„‚ | ð„‚ | ð„‚ | ð„‚ | ✓ |
+| Solution | ✓ | ð„‚ | ð„‚ | ð„‚ | âš  | ✓ | ð„‚ | ð„‚ | ð„‚ | ð„‚ | ð„‚ | ð„‚ |
+| Affected item (e.g. class or package) | ✓ | ð„‚ | ✓ | ð„‚ | ✓ | ✓ | ð„‚ | ✓ | ð„‚ | ð„‚ | ð„‚ | ð„‚ |
+| Confidence | ð„‚ | ✓ | ✓ | ð„‚ | ✓ | ✓ | ✓ | ✓ | ð„‚ | ð„‚ | ð„‚ | ✓ |
+| Source code extract | ð„‚ | ✓ | ✓ | ✓ | ð„‚ | ✓ | ✓ | ð„‚ | ð„‚ | ð„‚ | ð„‚ | ð„‚ |
+| Internal ID | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ð„‚ | ð„‚ | ✓ | ✓ | ✓ |
- ✓ => we have that data
- âš  => we have that data but it's partially reliable, or we need to extract it from unstructured content
diff --git a/doc/user/application_security/sast/img/sast_v13_0.png b/doc/user/application_security/sast/img/sast_v13_0.png
deleted file mode 100644
index b4aea6ea466..00000000000
--- a/doc/user/application_security/sast/img/sast_v13_0.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/application_security/sast/img/sast_v13_2.png b/doc/user/application_security/sast/img/sast_v13_2.png
new file mode 100644
index 00000000000..5697ed9beb0
--- /dev/null
+++ b/doc/user/application_security/sast/img/sast_v13_2.png
Binary files differ
diff --git a/doc/user/application_security/sast/index.md b/doc/user/application_security/sast/index.md
index a5497e3d38c..70d4b513cf9 100644
--- a/doc/user/application_security/sast/index.md
+++ b/doc/user/application_security/sast/index.md
@@ -9,9 +9,9 @@ type: reference, howto
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/3775) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 10.3.
-NOTE: **4 of the top 6 attacks were application based.**
-Download our whitepaper,
-["A Seismic Shift in Application Security"](https://about.gitlab.com/resources/whitepaper-seismic-shift-application-security/)
+NOTE: **Note:**
+The whitepaper ["A Seismic Shift in Application Security"](https://about.gitlab.com/resources/whitepaper-seismic-shift-application-security/)
+explains how **4 of the top 6 attacks were application based**. Download it
to learn how to protect your organization.
## Overview
@@ -28,7 +28,7 @@ You can take advantage of SAST by doing one of the following:
GitLab checks the SAST report, compares the found vulnerabilities between the
source and target branches, and shows the information right on the merge request.
-![SAST Widget](img/sast_v13_0.png)
+![SAST Widget](img/sast_v13_2.png)
The results are sorted by the priority of the vulnerability:
@@ -58,7 +58,8 @@ If you're using the shared Runners on GitLab.com, this is enabled by default.
Beginning with GitLab 13.0, Docker privileged mode is necessary only if you've [enabled Docker-in-Docker for SAST](#enabling-docker-in-docker).
-CAUTION: **Caution:** Our SAST jobs currently expect a Linux container type. Windows containers are not yet supported.
+CAUTION: **Caution:**
+Our SAST jobs currently expect a Linux container type. Windows containers are not yet supported.
CAUTION: **Caution:**
If you use your own Runners, make sure the Docker version installed
@@ -70,31 +71,54 @@ The following table shows which languages, package managers and frameworks are s
| Language (package managers) / framework | Scan tool | Introduced in GitLab Version |
|-----------------------------------------------------------------------------|----------------------------------------------------------------------------------------|------------------------------|
-| .NET Core | [Security Code Scan](https://security-code-scan.github.io) | 11.0 |
-| .NET Framework | [Security Code Scan](https://security-code-scan.github.io) | 13.0 |
-| Any | [Gitleaks](https://github.com/zricethezav/gitleaks) and [TruffleHog](https://github.com/dxa4481/truffleHog) | 11.9 |
-| Apex (Salesforce) | [PMD](https://pmd.github.io/pmd/index.html) | 12.1 |
-| C/C++ | [Flawfinder](https://github.com/david-a-wheeler/flawfinder) | 10.7 |
-| Elixir (Phoenix) | [Sobelow](https://github.com/nccgroup/sobelow) | 11.10 |
-| Go | [Gosec](https://github.com/securego/gosec) | 10.7 |
-| Groovy ([Ant](https://ant.apache.org/), [Gradle](https://gradle.org/), [Maven](https://maven.apache.org/) and [SBT](https://www.scala-sbt.org/)) | [SpotBugs](https://spotbugs.github.io/) with the [find-sec-bugs](https://find-sec-bugs.github.io/) plugin | 11.3 (Gradle) & 11.9 (Ant, Maven, SBT) |
-| Helm Charts | [Kubesec](https://github.com/controlplaneio/kubesec) | 13.1 |
+| .NET Core | [Security Code Scan](https://security-code-scan.github.io) | 11.0 |
+| .NET Framework | [Security Code Scan](https://security-code-scan.github.io) | 13.0 |
+| Any | [Gitleaks](https://github.com/zricethezav/gitleaks) and [TruffleHog](https://github.com/dxa4481/truffleHog) | 11.9 |
+| Apex (Salesforce) | [PMD](https://pmd.github.io/pmd/index.html) | 12.1 |
+| C/C++ | [Flawfinder](https://github.com/david-a-wheeler/flawfinder) | 10.7 |
+| Elixir (Phoenix) | [Sobelow](https://github.com/nccgroup/sobelow) | 11.10 |
+| Go | [Gosec](https://github.com/securego/gosec) | 10.7 |
+| Groovy ([Ant](https://ant.apache.org/), [Gradle](https://gradle.org/), [Maven](https://maven.apache.org/) and [SBT](https://www.scala-sbt.org/)) | [SpotBugs](https://spotbugs.github.io/) with the [find-sec-bugs](https://find-sec-bugs.github.io/) plugin | 11.3 (Gradle) & 11.9 (Ant, Maven, SBT) |
+| Helm Charts | [Kubesec](https://github.com/controlplaneio/kubesec) | 13.1 |
| Java ([Ant](https://ant.apache.org/), [Gradle](https://gradle.org/), [Maven](https://maven.apache.org/) and [SBT](https://www.scala-sbt.org/)) | [SpotBugs](https://spotbugs.github.io/) with the [find-sec-bugs](https://find-sec-bugs.github.io/) plugin | 10.6 (Maven), 10.8 (Gradle) & 11.9 (Ant, SBT) |
-| JavaScript | [ESLint security plugin](https://github.com/nodesecurity/eslint-plugin-security) | 11.8 |
+| JavaScript | [ESLint security plugin](https://github.com/nodesecurity/eslint-plugin-security) | 11.8, moved to [GitLab Core](https://about.gitlab.com/pricing/) in 13.2 |
| Kubernetes manifests | [Kubesec](https://github.com/controlplaneio/kubesec) | 12.6 |
| Node.js | [NodeJsScan](https://github.com/ajinabraham/NodeJsScan) | 11.1 |
| PHP | [phpcs-security-audit](https://github.com/FloeDesignTechnologies/phpcs-security-audit) | 10.8 |
| Python ([pip](https://pip.pypa.io/en/stable/)) | [bandit](https://github.com/PyCQA/bandit) | 10.3 |
| React | [ESLint react plugin](https://github.com/yannickcr/eslint-plugin-react) | 12.5 |
-| Ruby on Rails | [brakeman](https://brakemanscanner.org) | 10.3 |
+| Ruby on Rails | [brakeman](https://brakemanscanner.org) | 10.3, moved to [GitLab Core](https://about.gitlab.com/pricing/) in 13.1 |
| Scala ([Ant](https://ant.apache.org/), [Gradle](https://gradle.org/), [Maven](https://maven.apache.org/) and [SBT](https://www.scala-sbt.org/)) | [SpotBugs](https://spotbugs.github.io/) with the [find-sec-bugs](https://find-sec-bugs.github.io/) plugin | 11.0 (SBT) & 11.9 (Ant, Gradle, Maven) |
-| TypeScript | [`tslint-config-security`](https://github.com/webschik/tslint-config-security/) | 11.9 |
+| TypeScript | [ESLint security plugin](https://github.com/nodesecurity/eslint-plugin-security) | 11.9, merged with ESLint in 13.2 |
NOTE: **Note:**
The Java analyzers can also be used for variants like the
[Gradle wrapper](https://docs.gradle.org/current/userguide/gradle_wrapper.html),
[Grails](https://grails.org/) and the [Maven wrapper](https://github.com/takari/maven-wrapper).
+### Making SAST analyzers available to all GitLab tiers
+
+All open source (OSS) analyzers are in the process of being reviewed and potentially moved to the GitLab Core tier. Progress can be
+tracked in the corresponding
+[epic](https://gitlab.com/groups/gitlab-org/-/epics/2098).
+
+Please note that support for [Docker-in-Docker](#enabling-docker-in-docker)
+will not be extended to the GitLab Core tier.
+
+#### Summary of features per tier
+
+Different features are available in different [GitLab tiers](https://about.gitlab.com/pricing/),
+as shown in the following table:
+
+| Capability | In Core | In Ultimate |
+|:--------------------------------------------------------------------------|:--------------------|:-------------------|
+| [Configure SAST Scanners](#configuration) | **{check-circle}** | **{check-circle}** |
+| [Customize SAST Settings](#customizing-the-sast-settings) | **{check-circle}** | **{check-circle}** |
+| View [JSON Report](#reports-json-format) | **{check-circle}** | **{check-circle}** |
+| [Presentation of JSON Report in Merge Request](#overview) | **{dotted-circle}** | **{check-circle}** |
+| [Interaction with Vulnerabilities](#interacting-with-the-vulnerabilities) | **{dotted-circle}** | **{check-circle}** |
+| [Access to Security Dashboard](#security-dashboard) | **{dotted-circle}** | **{check-circle}** |
+
## Contribute your scanner
The [Security Scanner Integration](../../../development/integrations/secure.md) documentation explains how to integrate other security scanners into GitLab.
@@ -222,7 +246,7 @@ a `before_script` execution to prepare your scan job.
To pass your project's dependencies as artifacts, the dependencies must be included
in the project's working directory and specified using the `artifacts:path` configuration.
-If all dependencies are present, the `-compile=false` flag can be provided to the
+If all dependencies are present, the `COMPILE=false` variable can be provided to the
analyzer and compilation will be skipped:
```yaml
@@ -247,10 +271,9 @@ build:
spotbugs-sast:
dependencies:
- build
- script:
- - /analyzer run -compile=false
variables:
MAVEN_REPO_PATH: ./.m2/repository
+ COMPILE: false
artifacts:
reports:
sast: gl-sast-report.json
@@ -266,6 +289,16 @@ See [Analyzer settings](#analyzer-settings) for the complete list of available o
SAST can be [configured](#customizing-the-sast-settings) using environment variables.
+#### Logging Level
+
+You can control the verbosity of logs by setting the `SECURE_LOG_LEVEL` env var. The default is set to `info`, you can set it to any of the following levels:
+
+- `fatal`
+- `error`
+- `warn`
+- `info`
+- `debug`
+
#### Custom Certificate Authority
To trust a custom Certificate Authority, set the `ADDITIONAL_CA_CERT_BUNDLE` variable to the bundle
@@ -278,7 +311,6 @@ The following are Docker image-related variables.
| Environment variable | Description |
|------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `SECURE_ANALYZERS_PREFIX` | Override the name of the Docker registry providing the default images (proxy). Read more about [customizing analyzers](analyzers.md). |
-| `SAST_ANALYZER_IMAGE_PREFIX` | **DEPRECATED**: Use `SECURE_ANALYZERS_PREFIX` instead. |
| `SAST_ANALYZER_IMAGE_TAG` | **DEPRECATED:** Override the Docker tag of the default images. Read more about [customizing analyzers](analyzers.md). |
| `SAST_DEFAULT_ANALYZERS` | Override the names of default images. Read more about [customizing analyzers](analyzers.md). |
| `SAST_DISABLE_DIND` | Disable Docker-in-Docker and run analyzers [individually](#enabling-docker-in-docker). This variable is `true` by default. |
@@ -287,17 +319,18 @@ The following are Docker image-related variables.
Some analyzers make it possible to filter out vulnerabilities under a given threshold.
-| Environment variable | Default value | Description |
-|-------------------------|---------------|-------------|
+| Environment variable | Default value | Description |
+|-------------------------------|--------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `SAST_EXCLUDED_PATHS` | `spec, test, tests, tmp` | Exclude vulnerabilities from output based on the paths. This is a comma-separated list of patterns. Patterns can be globs, or file or folder paths (for example, `doc,spec` ). Parent directories will also match patterns. |
-| `SAST_BANDIT_EXCLUDED_PATHS` | - | comma-separated list of paths to exclude from scan. Uses Python's [`fnmatch` syntax](https://docs.python.org/2/library/fnmatch.html); For example: `'*/tests/*'` |
-| `SAST_BRAKEMAN_LEVEL` | 1 | Ignore Brakeman vulnerabilities under given confidence level. Integer, 1=Low 3=High. |
-| `SAST_FLAWFINDER_LEVEL` | 1 | Ignore Flawfinder vulnerabilities under given risk level. Integer, 0=No risk, 5=High risk. |
-| `SAST_GITLEAKS_ENTROPY_LEVEL` | 8.0 | Minimum entropy for secret detection. Float, 0.0 = low, 8.0 = high. |
-| `SAST_GOSEC_LEVEL` | 0 | Ignore Gosec vulnerabilities under given confidence level. Integer, 0=Undefined, 1=Low, 2=Medium, 3=High. |
-| `SAST_GITLEAKS_COMMIT_FROM` | - | The commit a Gitleaks scan starts at. |
-| `SAST_GITLEAKS_COMMIT_TO` | - | The commit a Gitleaks scan ends at. |
-| `SAST_GITLEAKS_HISTORIC_SCAN` | false | Flag to enable a historic Gitleaks scan. |
+| `SAST_BANDIT_EXCLUDED_PATHS` | | Comma-separated list of paths to exclude from scan. Uses Python's [`fnmatch` syntax](https://docs.python.org/2/library/fnmatch.html); For example: `'*/tests/*, */venv/*'` |
+| `SAST_BRAKEMAN_LEVEL` | 1 | Ignore Brakeman vulnerabilities under given confidence level. Integer, 1=Low 3=High. |
+| `SAST_DISABLE_BABEL` | `false` | Disable Babel processing for the NodeJsScan scanner. Set to `true` to disable Babel processing. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/33065) in GitLab 13.2. |
+| `SAST_FLAWFINDER_LEVEL` | 1 | Ignore Flawfinder vulnerabilities under given risk level. Integer, 0=No risk, 5=High risk. |
+| `SAST_GITLEAKS_ENTROPY_LEVEL` | 8.0 | Minimum entropy for secret detection. Float, 0.0 = low, 8.0 = high. |
+| `SAST_GOSEC_LEVEL` | 0 | Ignore Gosec vulnerabilities under given confidence level. Integer, 0=Undefined, 1=Low, 2=Medium, 3=High. |
+| `SAST_GITLEAKS_COMMIT_FROM` | | The commit a Gitleaks scan starts at. |
+| `SAST_GITLEAKS_COMMIT_TO` | | The commit a Gitleaks scan ends at. |
+| `SAST_GITLEAKS_HISTORIC_SCAN` | `false` | Flag to enable a historic Gitleaks scan. |
#### Docker-in-Docker orchestrator
@@ -315,11 +348,12 @@ The following variables configure the Docker-in-Docker orchestrator, and therefo
Some analyzers can be customized with environment variables.
-| Environment variable | Analyzer | Description |
-|-----------------------------|----------|-------------|
+| Environment variable | Analyzer | Description |
+|---------------------------------------|----------------------|-------------|
| `SCAN_KUBERNETES_MANIFESTS` | Kubesec | Set to `"true"` to scan Kubernetes manifests. |
| `KUBESEC_HELM_CHARTS_PATH` | Kubesec | Optional path to Helm charts that `helm` will use to generate a Kubernetes manifest that `kubesec` will scan. If dependencies are defined, `helm dependency build` should be ran in a `before_script` to fetch the necessary dependencies. |
| `KUBESEC_HELM_OPTIONS` | Kubesec | Additional arguments for the `helm` executable. |
+| `COMPILE` | SpotBugs | Set to `false` to disable project compilation and dependency fetching. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/195252) in GitLab 13.1. |
| `ANT_HOME` | SpotBugs | The `ANT_HOME` environment variable. |
| `ANT_PATH` | SpotBugs | Path to the `ant` executable. |
| `GRADLE_PATH` | SpotBugs | Path to the `gradle` executable. |
@@ -333,6 +367,7 @@ Some analyzers can be customized with environment variables.
| `FAIL_NEVER` | SpotBugs | Set to `1` to ignore compilation failure. |
| `SAST_GOSEC_CONFIG` | Gosec | Path to configuration for Gosec (optional). |
| `PHPCS_SECURITY_AUDIT_PHP_EXTENSIONS` | phpcs-security-audit | Comma separated list of additional PHP Extensions. |
+| `SEARCH_MAX_DEPTH` | any | Maximum number of directories traversed when searching for source code files. Default: `4`. |
#### Custom environment variables
@@ -494,7 +529,6 @@ registry.gitlab.com/gitlab-org/security-products/analyzers/secrets:2
registry.gitlab.com/gitlab-org/security-products/analyzers/security-code-scan:2
registry.gitlab.com/gitlab-org/security-products/analyzers/sobelow:2
registry.gitlab.com/gitlab-org/security-products/analyzers/spotbugs:2
-registry.gitlab.com/gitlab-org/security-products/analyzers/tslint:2
```
The process for importing Docker images into a local offline Docker registry depends on
@@ -509,7 +543,7 @@ For details on saving and transporting Docker images as a file, see Docker's doc
### Set SAST CI job variables to use local SAST analyzers
Add the following configuration to your `.gitlab-ci.yml` file. You must replace
-`SAST_ANALYZER_IMAGE_PREFIX` to refer to your local Docker container registry:
+`SECURE_ANALYZERS_PREFIX` to refer to your local Docker container registry:
```yaml
include:
diff --git a/doc/user/application_security/secret_detection/img/secret-detection-merge-request-ui.png b/doc/user/application_security/secret_detection/img/secret-detection-merge-request-ui.png
deleted file mode 100644
index 17893610f10..00000000000
--- a/doc/user/application_security/secret_detection/img/secret-detection-merge-request-ui.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/application_security/secret_detection/img/secret_detection_v13_2.png b/doc/user/application_security/secret_detection/img/secret_detection_v13_2.png
new file mode 100644
index 00000000000..4aa7dd83c8d
--- /dev/null
+++ b/doc/user/application_security/secret_detection/img/secret_detection_v13_2.png
Binary files differ
diff --git a/doc/user/application_security/secret_detection/index.md b/doc/user/application_security/secret_detection/index.md
index 85933c31a34..ea635212c5d 100644
--- a/doc/user/application_security/secret_detection/index.md
+++ b/doc/user/application_security/secret_detection/index.md
@@ -25,7 +25,7 @@ GitLab displays identified secrets as part of the SAST reports visibly in a few
- Pipelines' **Security** tab
- Report in the merge request widget
-![Secret Detection in merge request widget](img/secret-detection-merge-request-ui.png)
+![Secret Detection in merge request widget](img/secret_detection_v13_2.png)
## Use cases
@@ -39,7 +39,8 @@ To run Secret Detection jobs, by default, you need GitLab Runner with the
[`kubernetes`](https://docs.gitlab.com/runner/install/kubernetes.html) executor.
If you're using the shared Runners on GitLab.com, this is enabled by default.
-CAUTION: **Caution:** Our Secret Detection jobs currently expect a Linux container type. Windows containers are not yet supported.
+CAUTION: **Caution:**
+Our Secret Detection jobs currently expect a Linux container type. Windows containers are not yet supported.
CAUTION: **Caution:**
If you use your own Runners, make sure the Docker version installed
@@ -118,15 +119,15 @@ declare a job with the same name as the SAST job to override. Place this new job
inclusion and specify any additional keys under it.
In the following example, we include the Secret Detection template and at the same time we
-override the `secret-scan` job with the `SECRET_DETECTION_HISTORIC_SCAN` variable to `true`:
+override the `secret_detection` job with the `SECRET_DETECTION_HISTORIC_SCAN` variable to `true`:
```yaml
include:
- template: Secret-Detection.gitlab-ci.yml
-secrets-scan:
+secret_detection:
variables:
- SECRET_DETECTION_HISTORIC_SCAN: true
+ SECRET_DETECTION_HISTORIC_SCAN: "true"
```
Because the template is [evaluated before](../../../ci/yaml/README.md#include)
@@ -146,6 +147,16 @@ Secret Detection can be customized by defining available variables:
| `SECRET_DETECTION_COMMIT_TO` | - | The commit a Gitleaks scan ends at. |
| `SECRET_DETECTION_HISTORIC_SCAN` | false | Flag to enable a historic Gitleaks scan. |
+### Logging Level
+
+You can control the verbosity of logs by setting the `SECURE_LOG_LEVEL` env var. The default is set to `info`, you can set it to any of the following levels:
+
+- `fatal`
+- `error`
+- `warn`
+- `info`
+- `debug`
+
## Full History Secret Scan
GitLab 12.11 introduced support for scanning the full history of a repository. This new functionality
diff --git a/doc/user/application_security/security_dashboard/img/group_security_dashboard_export_csv_v13_1.png b/doc/user/application_security/security_dashboard/img/group_security_dashboard_export_csv_v13_1.png
index 0dfe7b637cd..d98fb71ae37 100644
--- a/doc/user/application_security/security_dashboard/img/group_security_dashboard_export_csv_v13_1.png
+++ b/doc/user/application_security/security_dashboard/img/group_security_dashboard_export_csv_v13_1.png
Binary files differ
diff --git a/doc/user/application_security/security_dashboard/img/group_security_dashboard_v13_0.png b/doc/user/application_security/security_dashboard/img/group_security_dashboard_v13_0.png
deleted file mode 100644
index 4c7b5cc724f..00000000000
--- a/doc/user/application_security/security_dashboard/img/group_security_dashboard_v13_0.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/application_security/security_dashboard/img/group_security_dashboard_v13_2_noNav.png b/doc/user/application_security/security_dashboard/img/group_security_dashboard_v13_2_noNav.png
new file mode 100644
index 00000000000..d6cfc2de980
--- /dev/null
+++ b/doc/user/application_security/security_dashboard/img/group_security_dashboard_v13_2_noNav.png
Binary files differ
diff --git a/doc/user/application_security/security_dashboard/img/instance_security_dashboard_with_projects_v13_0.png b/doc/user/application_security/security_dashboard/img/instance_security_dashboard_with_projects_v13_0.png
deleted file mode 100644
index a500f186c2b..00000000000
--- a/doc/user/application_security/security_dashboard/img/instance_security_dashboard_with_projects_v13_0.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/application_security/security_dashboard/img/instance_security_dashboard_with_projects_v13_2_sm.png b/doc/user/application_security/security_dashboard/img/instance_security_dashboard_with_projects_v13_2_sm.png
new file mode 100644
index 00000000000..75b5ad1d885
--- /dev/null
+++ b/doc/user/application_security/security_dashboard/img/instance_security_dashboard_with_projects_v13_2_sm.png
Binary files differ
diff --git a/doc/user/application_security/security_dashboard/img/pipeline_security_dashboard_v12_6.png b/doc/user/application_security/security_dashboard/img/pipeline_security_dashboard_v12_6.png
deleted file mode 100644
index 670c90d10a3..00000000000
--- a/doc/user/application_security/security_dashboard/img/pipeline_security_dashboard_v12_6.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/application_security/security_dashboard/img/pipeline_security_dashboard_v13_2.png b/doc/user/application_security/security_dashboard/img/pipeline_security_dashboard_v13_2.png
new file mode 100644
index 00000000000..591a08f4d7a
--- /dev/null
+++ b/doc/user/application_security/security_dashboard/img/pipeline_security_dashboard_v13_2.png
Binary files differ
diff --git a/doc/user/application_security/security_dashboard/img/project_security_dashboard_v13_2.png b/doc/user/application_security/security_dashboard/img/project_security_dashboard_v13_2.png
new file mode 100644
index 00000000000..7cab7b0a61f
--- /dev/null
+++ b/doc/user/application_security/security_dashboard/img/project_security_dashboard_v13_2.png
Binary files differ
diff --git a/doc/user/application_security/security_dashboard/img/standalone_vulnerability_page_v13_1.png b/doc/user/application_security/security_dashboard/img/standalone_vulnerability_page_v13_1.png
new file mode 100644
index 00000000000..9cf95b197fe
--- /dev/null
+++ b/doc/user/application_security/security_dashboard/img/standalone_vulnerability_page_v13_1.png
Binary files differ
diff --git a/doc/user/application_security/security_dashboard/img/vulnerability_list_table_v13_1.png b/doc/user/application_security/security_dashboard/img/vulnerability_list_table_v13_1.png
new file mode 100644
index 00000000000..2b792727a99
--- /dev/null
+++ b/doc/user/application_security/security_dashboard/img/vulnerability_list_table_v13_1.png
Binary files differ
diff --git a/doc/user/application_security/security_dashboard/index.md b/doc/user/application_security/security_dashboard/index.md
index 60798b9c921..9a13d143d1f 100644
--- a/doc/user/application_security/security_dashboard/index.md
+++ b/doc/user/application_security/security_dashboard/index.md
@@ -1,5 +1,8 @@
---
type: reference, howto
+stage: Secure
+group: Threat Insights
+info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers
---
# GitLab Security Dashboard **(ULTIMATE)**
@@ -9,7 +12,7 @@ vulnerabilities in your groups, projects and pipelines.
You can also drill down into a vulnerability and get extra information, see which
project it comes from, the file it's in, and various metadata to help you analyze
-the risk. You can also action these vulnerabilities by creating an issue for them,
+the risk. You can also take actions on vulnerabilities by creating an issue for them,
or by dismissing them.
To benefit from the Security Dashboard you must first configure one of the
@@ -42,7 +45,7 @@ At the pipeline level, the Security section displays the vulnerabilities present
Visit the page for any pipeline which has run any of the [supported reports](#supported-reports). Click the **Security** tab to view the Security findings.
-![Pipeline Security Dashboard](img/pipeline_security_dashboard_v12_6.png)
+![Pipeline Security Dashboard](img/pipeline_security_dashboard_v13_2.png)
NOTE: **Note:**
A pipeline consists of multiple jobs, including SAST and DAST scanning. If any job fails to finish for any reason, the security dashboard will not show SAST scanner output. For example, if the SAST job finishes but the DAST job fails, the security dashboard will not show SAST results. The analyzer will output an [exit code](../../../development/integrations/secure.md#exit-code) on failure.
@@ -51,56 +54,52 @@ A pipeline consists of multiple jobs, including SAST and DAST scanning. If any j
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/6165) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 11.1.
-At the project level, the Security Dashboard displays the latest security reports for your project.
-Use it to find and fix vulnerabilities.
+At the project level, the Security Dashboard displays the vulnerabilities merged into your project's
+[default branch](../../project/repository/branches/index.md#default-branch). Access it by navigating
+to **Security & Compliance > Security Dashboard**.
-![Project Security Dashboard](img/project_security_dashboard_v13_0.png)
+The Security Dashboard first displays the total number of vulnerabilities by severity (for example,
+Critical, High, Medium, Low). Below this, a table displays each vulnerability's status, severity,
+and description. Clicking a vulnerability takes you to its [Vulnerability Details](../vulnerabilities)
+page to view more information about that vulnerability.
-### Export vulnerabilities
+You can filter the vulnerabilities by:
-> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/197494) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 12.10.
+- Status
+- Severity
+- Report type
-You can export all your project's vulnerabilities as CSV by clicking on the export button located at top right of the Project Security Dashboard. This will initiate the process, and once complete, the CSV report will be downloaded. The report will contain all vulnerabilities in the project as filters won't apply.
+You can also dismiss vulnerabilities in the table:
-NOTE: **Note:**
-It may take several minutes for the download to start if your project consists
-of thousands of vulnerabilities. Do not close the page until the download finishes.
+1. Select the checkbox for each vulnerability you want to dismiss.
+1. In the menu that appears, select the reason for dismissal and click **Dismiss Selected**.
-![CSV Export Button](img/project_security_dashboard_export_csv_v12_10.png)
+![Project Security Dashboard](img/project_security_dashboard_v13_2.png)
## Group Security Dashboard
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/6709) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 11.5.
-The group Security Dashboard gives an overview of the vulnerabilities of all the
-projects in a group and its subgroups.
+The group Security Dashboard gives an overview of the vulnerabilities in the default branches of the
+projects in a group and its subgroups. Access it by navigating to **Security > Security Dashboard**
+for your group.
-First, navigate to the Security Dashboard found under your group's
-**Security** tab.
+NOTE: **Note:**
+The Security Dashboard only shows projects with [security reports](#supported-reports) enabled in a
+group.
+
+![Dashboard with action buttons and metrics](img/group_security_dashboard_v13_2_noNav.png)
-Once you're on the dashboard, at the top you should see a series of filters for:
+You can filter which vulnerabilities the Security Dashboard displays by:
- Status
- Severity
- Report type
+- Project
-NOTE: **Note:**
-The dashboard only shows projects with [security reports](#supported-reports) enabled in a group.
-
-![Dashboard with action buttons and metrics](img/group_security_dashboard_v13_0.png)
-
-Selecting one or more filters will filter the results in this page.
-
-The main section is a list of all the vulnerabilities in the group, sorted by severity.
-In that list, you can see the severity of the vulnerability, its name, its
-confidence (likelihood of the vulnerability to be a positive one), and the project
-it's from.
-
-If you hover over a row, the following actions appear:
-
-- More info
-- Create issue
-- Dismiss vulnerability
+A table lists the vulnerabilities, sorted by severity. The table shows each vulnerability's status,
+severity, and description. Clicking a vulnerability takes you to its [Vulnerability Details](../vulnerabilities)
+page to view more information about that vulnerability.
Next to the list is a timeline chart that shows how many open
vulnerabilities your projects had at various points in time. You can filter among 30, 60, and
@@ -120,28 +119,14 @@ vulnerabilities are not included either.
Read more on how to [interact with the vulnerabilities](../index.md#interacting-with-the-vulnerabilities).
-### Export vulnerabilities
-
-> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/213013) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 13.1.
-
-You can export all your vulnerabilities as CSV by clicking the **{upload}** **Export** button
-located at the top right of the **Group Security Dashboard**. After the report builds, the CSV
-report downloads to your local machine. The report contains all vulnerabilities for the projects
-defined in the **Group Security Dashboard**, as filters don't apply to the export function.
-
-NOTE: **Note:**
-It may take several minutes for the download to start if your project contains thousands of
-vulnerabilities. Don't close the page until the download finishes.
-
-![CSV Export Button](img/group_security_dashboard_export_csv_v13_1.png)
-
## Instance Security Dashboard
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/6953) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 12.8.
-At the instance level, the Security Dashboard displays the vulnerabilities
-present in all of the projects that you have added to it. It includes all
-of the features of the [group security dashboard](#group-security-dashboard).
+At the instance level, the Security Dashboard displays the vulnerabilities present in the default
+branches of all the projects you configure to display on the dashboard. It includes all the
+[group Security Dashboard's](#group-security-dashboard)
+features.
You can access the Instance Security Dashboard from the menu
bar at the top of the page. Under **More**, select **Security**.
@@ -156,27 +141,25 @@ To add projects to the dashboard:
1. Search for and add one or more projects using the **Search your projects** field.
1. Click the **Add projects** button.
-Once added, the dashboard will display the vulnerabilities found in your chosen
-projects.
+Once added, the Security Dashboard displays the vulnerabilities found in your chosen projects'
+default branches.
-![Instance Security Dashboard with projects](img/instance_security_dashboard_with_projects_v13_0.png)
+![Instance Security Dashboard with projects](img/instance_security_dashboard_with_projects_v13_2_sm.png)
-### Export vulnerabilities
+## Export vulnerabilities
-> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/213014) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 13.0.
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/213014) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 12.10.
-You can export all your vulnerabilities as CSV by clicking the **{upload}** **Export**
-button located at top right of the **Instance Security Dashboard**. After the report
+You can export all your vulnerabilities in CSV format by clicking the **{upload}** **Export**
+button located at top right of the **Security Dashboard**. After the report
is built, the CSV report downloads to your local machine. The report contains all
-vulnerabilities for the projects defined in the **Instance Security Dashboard**,
+vulnerabilities for the projects defined in the **Security Dashboard**,
as filters don't apply to the export function.
NOTE: **Note:**
It may take several minutes for the download to start if your project contains
thousands of vulnerabilities. Do not close the page until the download finishes.
-![CSV Export Button](img/instance_security_dashboard_export_csv_v13_0.png)
-
## Keeping the dashboards up to date
The Security Dashboard displays information from the results of the most recent
@@ -194,12 +177,34 @@ Dashboard regardless of how often the default branch is updated.
That way, reports are created even if no code change happens.
+CAUTION: **Warning:**
+Running Dependency Scanning from a scheduled pipeline might result in false negatives if your
+project doesn't have a lock file and isn't configured for Continuous Delivery. A lock file is a file
+that lists all transient dependencies and keeps track of their exact versions. The false negative
+can occur because the dependency version resolved during the scan might differ from the ones
+resolved when your project was built and released, in a previous pipeline. Java projects can't have
+lock files. Python projects can have lock files, but GitLab Secure tools don't support them.
+
## Security scans using Auto DevOps
When using [Auto DevOps](../../../topics/autodevops/index.md), use
[special environment variables](../../../topics/autodevops/customize.md#environment-variables)
to configure daily security scans.
+## Vulnerability list
+
+Each dashboard's vulnerability list contains vulnerabilities from the latest scans that were merged
+into the default branch.
+Click any vulnerability in the table to see more information on that vulnerability. To create an
+issue associated with the vulnerability, click the **Create Issue** button.
+
+![Create an issue for the vulnerability](img/standalone_vulnerability_page_v13_1.png)
+
+Once you create the issue, the vulnerability list contains a link to the issue and an icon whose
+color indicates the issue's status (green for open issues, blue for closed issues).
+
+![Display attached issues](img/vulnerability_list_table_v13_1.png)
+
<!-- ## Troubleshooting
Include any troubleshooting steps that you can foresee. If you know beforehand what issues
diff --git a/doc/user/application_security/threat_monitoring/index.md b/doc/user/application_security/threat_monitoring/index.md
index 434048896fe..a6738677454 100644
--- a/doc/user/application_security/threat_monitoring/index.md
+++ b/doc/user/application_security/threat_monitoring/index.md
@@ -58,12 +58,15 @@ prerequisites:
If you're using custom Helm values for Cilium, you must enable Hubble
with flow metrics for each namespace by adding the following lines to
-your [Hubble values](../../clusters/applications.md#install-cilium-using-gitlab-cicd):
+your [Cilium values](../../clusters/applications.md#install-cilium-using-gitlab-cicd):
```yaml
-metrics:
- enabled:
- - 'flow:sourceContext=namespace;destinationContext=namespace'
+global:
+ hubble:
+ enabled: true
+ metrics:
+ enabled:
+ - 'flow:sourceContext=namespace;destinationContext=namespace'
```
The **Container Network Policy** section displays the following information
diff --git a/doc/user/application_security/vulnerabilities/img/standalone_vulnerability_page_v12_10.png b/doc/user/application_security/vulnerabilities/img/standalone_vulnerability_page_v12_10.png
deleted file mode 100644
index 0fdb8d1e201..00000000000
--- a/doc/user/application_security/vulnerabilities/img/standalone_vulnerability_page_v12_10.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/application_security/vulnerabilities/img/standalone_vulnerability_page_v13_1.png b/doc/user/application_security/vulnerabilities/img/standalone_vulnerability_page_v13_1.png
new file mode 100644
index 00000000000..e0e0fdb6f6e
--- /dev/null
+++ b/doc/user/application_security/vulnerabilities/img/standalone_vulnerability_page_v13_1.png
Binary files differ
diff --git a/doc/user/application_security/vulnerabilities/index.md b/doc/user/application_security/vulnerabilities/index.md
index b3128e49980..d5cce6434d8 100644
--- a/doc/user/application_security/vulnerabilities/index.md
+++ b/doc/user/application_security/vulnerabilities/index.md
@@ -1,7 +1,7 @@
---
type: reference, howto
stage: Secure
-group: Vulnerability Research
+group: Threat Insights
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers
---
@@ -9,10 +9,10 @@ info: To determine the technical writer assigned to the Stage/Group associated w
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/13561) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 13.0.
-Each security vulnerability in the [Vulnerability List](../dependency_list/index.md) has its own standalone
+Each security vulnerability in the [Security Dashboard](../security_dashboard/index.md#project-security-dashboard) has its own standalone
page.
-![Standalone vulnerability page](img/standalone_vulnerability_page_v12_10.png)
+![Standalone vulnerability page](img/standalone_vulnerability_page_v13_1.png)
On the standalone vulnerability page, you can interact with the vulnerability in
several different ways:
@@ -30,7 +30,7 @@ several different ways:
You can switch the status of a vulnerability using the **Status** dropdown to one of
the following values:
-| State | Description |
+| Status | Description |
|-----------|-------------------------------------------------------------------|
| Detected | The default state for a newly discovered vulnerability |
| Confirmed | A user has seen this vulnerability and confirmed it to be real |
diff --git a/doc/user/asciidoc.md b/doc/user/asciidoc.md
index 8834deb8d50..512a98d567b 100644
--- a/doc/user/asciidoc.md
+++ b/doc/user/asciidoc.md
@@ -10,14 +10,14 @@ You can find the full documentation for the AsciiDoc syntax at <https://asciidoc
### Paragraphs
-```asciidoc
+```plaintext
A normal paragraph.
Line breaks are not preserved.
```
Line comments, which are lines that start with `//`, are skipped:
-```asciidoc
+```plaintext
// this is a comment
```
@@ -25,7 +25,7 @@ A blank line separates paragraphs.
A paragraph with the `[%hardbreaks]` option will preserve line breaks:
-```asciidoc
+```plaintext
[%hardbreaks]
This paragraph carries the `hardbreaks` option.
Notice how line breaks are now preserved.
@@ -35,7 +35,7 @@ An indented (literal) paragraph disables text formatting,
preserves spaces and line breaks, and is displayed in a
monospaced font:
-```asciidoc
+```plaintext
This literal paragraph is indented with one space.
As a consequence, *text formatting*, spaces,
and lines breaks will be preserved.
@@ -43,7 +43,7 @@ monospaced font:
An admonition paragraph grabs the reader's attention:
-```asciidoc
+```plaintext
NOTE: This is a brief reference, please read the full documentation at https://asciidoctor.org/docs/.
TIP: Lists can be indented. Leading whitespace is not significant.
@@ -53,7 +53,7 @@ TIP: Lists can be indented. Leading whitespace is not significant.
**Constrained (applied at word boundaries)**
-```asciidoc
+```plaintext
*strong importance* (aka bold)
_stress emphasis_ (aka italic)
`monospaced` (aka typewriter text)
@@ -64,7 +64,7 @@ _stress emphasis_ (aka italic)
**Unconstrained (applied anywhere)**
-```asciidoc
+```plaintext
**C**reate+**R**ead+**U**pdate+**D**elete
fan__freakin__tastic
``mono``culture
@@ -72,7 +72,7 @@ fan__freakin__tastic
**Replacements**
-```asciidoc
+```plaintext
A long time ago in a galaxy far, far away...
(C) 1976 Arty Artisan
I believe I shall--no, actually I won't.
@@ -80,7 +80,7 @@ I believe I shall--no, actually I won't.
**Macros**
-```asciidoc
+```plaintext
// where c=specialchars, q=quotes, a=attributes, r=replacements, m=macros, p=post_replacements, etc.
The European icon:flag[role=blue] is blue & contains pass:[************] arranged in a icon:circle-o[role=yellow].
The pass:c[->] operator is often referred to as the stabby lambda.
@@ -93,12 +93,12 @@ stem:[sqrt(4) = 2]
**User-defined attributes**
-```asciidoc
+```plaintext
// define attributes in the document header
:name: value
```
-```asciidoc
+```plaintext
:url-gem: https://rubygems.org/gems/asciidoctor
You can download and install Asciidoctor {asciidoctor-version} from {url-gem}.
@@ -117,7 +117,7 @@ GitLab sets the following environment attributes:
### Links
-```asciidoc
+```plaintext
https://example.org/page[A webpage]
link:../path/to/file.txt[A local file]
xref:document.adoc[A sibling document]
@@ -126,7 +126,7 @@ mailto:hello@example.org[Email to say hello!]
### Anchors
-```asciidoc
+```plaintext
[[idname,reference text]]
// or written using normal block attributes as `[#idname,reftext=reference text]`
A paragraph (or any block) with an anchor (aka ID) and reftext.
@@ -142,7 +142,7 @@ This paragraph has a footnote.footnote:[This is the text of the footnote.]
#### Unordered
-```asciidoc
+```plaintext
* level 1
** level 2
*** level 3
@@ -161,7 +161,7 @@ Attach a block or paragraph to a list item using a list continuation (which you
#### Ordered
-```asciidoc
+```plaintext
. Step 1
. Step 2
.. Step 2a
@@ -177,14 +177,14 @@ Attach a block or paragraph to a list item using a list continuation (which you
#### Checklist
-```asciidoc
+```plaintext
* [x] checked
* [ ] not checked
```
#### Callout
-```asciidoc
+```plaintext
// enable callout bubbles by adding `:icons: font` to the document header
[,ruby]
----
@@ -195,7 +195,7 @@ puts 'Hello, World!' # <1>
#### Description
-```asciidoc
+```plaintext
first term:: description of first term
second term::
description of second term
@@ -205,7 +205,7 @@ description of second term
#### Header
-```asciidoc
+```plaintext
= Document Title
Author Name <author@example.org>
v1.0, 2019-01-01
@@ -213,7 +213,7 @@ v1.0, 2019-01-01
#### Sections
-```asciidoc
+```plaintext
= Document Title (Level 0)
== Level 1
=== Level 2
@@ -225,7 +225,7 @@ v1.0, 2019-01-01
#### Includes
-```asciidoc
+```plaintext
include::basics.adoc[]
// define -a allow-uri-read to allow content to be read from URI
@@ -239,13 +239,13 @@ included, a number that is inclusive of transitive dependencies.
### Blocks
-```asciidoc
+```plaintext
--
open - a general-purpose content wrapper; useful for enclosing content to attach to a list item
--
```
-```asciidoc
+```plaintext
// recognized types include CAUTION, IMPORTANT, NOTE, TIP, and WARNING
// enable admonition icons by setting `:icons: font` in the document header
[NOTE]
@@ -254,13 +254,13 @@ admonition - a notice for the reader, ranging in severity from a tip to an alert
====
```
-```asciidoc
+```plaintext
====
example - a demonstration of the concept being documented
====
```
-```asciidoc
+```plaintext
.Toggle Me
[%collapsible]
====
@@ -268,58 +268,58 @@ collapsible - these details are revealed by clicking the title
====
```
-```asciidoc
+```plaintext
****
sidebar - auxiliary content that can be read independently of the main content
****
```
-```asciidoc
+```plaintext
....
literal - an exhibit that features program output
....
```
-```asciidoc
+```plaintext
----
listing - an exhibit that features program input, source code, or the contents of a file
----
```
-```asciidoc
+```plaintext
[,language]
----
source - a listing that is embellished with (colorized) syntax highlighting
----
```
-````asciidoc
+````plaintext
\```language
fenced code - a shorthand syntax for the source block
\```
````
-```asciidoc
+```plaintext
[,attribution,citetitle]
____
quote - a quotation or excerpt; attribution with title of source are optional
____
```
-```asciidoc
+```plaintext
[verse,attribution,citetitle]
____
verse - a literary excerpt, often a poem; attribution with title of source are optional
____
```
-```asciidoc
+```plaintext
++++
pass - content passed directly to the output document; often raw HTML
++++
```
-```asciidoc
+```plaintext
// activate stem support by adding `:stem:` to the document header
[stem]
++++
@@ -327,7 +327,7 @@ x = y^2
++++
```
-```asciidoc
+```plaintext
////
comment - content which is not included in the output document
////
@@ -335,7 +335,7 @@ comment - content which is not included in the output document
### Tables
-```asciidoc
+```plaintext
.Table Attributes
[cols=>1h;2d,width=50%,frame=topbot]
|===
@@ -366,7 +366,7 @@ comment - content which is not included in the output document
### Multimedia
-```asciidoc
+```plaintext
image::screenshot.png[block image,800,450]
Press image:reload.svg[reload,16,opts=interactive] to reload the page.
@@ -380,12 +380,12 @@ video::300817511[vimeo]
### Breaks
-```asciidoc
+```plaintext
// thematic break (aka horizontal rule)
---
```
-```asciidoc
+```plaintext
// page break
<<<
```
diff --git a/doc/user/clusters/applications.md b/doc/user/clusters/applications.md
index 86624d12bcf..507ba25850d 100644
--- a/doc/user/clusters/applications.md
+++ b/doc/user/clusters/applications.md
@@ -32,8 +32,6 @@ To see a list of available applications to install. For a:
- [Group-level cluster](../group/clusters/index.md), navigate to your group's
**{cloud-gear}** **Kubernetes** page.
-Install Helm first as it's used to install other applications.
-
NOTE: **Note:**
As of GitLab 11.6, Helm will be upgraded to the latest version supported
by GitLab before installing any of the applications.
@@ -71,17 +69,47 @@ can lead to confusion during deployments.
> - Introduced in GitLab 10.2 for project-level clusters.
> - Introduced in GitLab 11.6 for group-level clusters.
+> - A local Tiller option was [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/209736) in GitLab 13.2 behind a feature flag, enabled by default.
+> - The feature flag for local Tiller is enabled on GitLab.com.
[Helm](https://helm.sh/docs/) is a package manager for Kubernetes and is
-required to install all the other applications. It is installed in its
-own pod inside the cluster which can run the `helm` CLI in a safe
-environment.
+used to install the GitLab-managed apps. GitLab runs each `helm` command
+in a pod within the `gitlab-managed-apps` namespace inside the cluster.
+
+As of GitLab 13.2, the integration uses a local
+[Tiller](https://v2.helm.sh/docs/glossary/#tiller) by default. When using a
+local Tiller, the Helm application does not need to be installed and will not
+be shown in the list of applications.
NOTE: **Note:**
-Installing Helm as a GitLab-managed App behind a proxy is not supported,
-but a [workaround](../../topics/autodevops/index.md#installing-helm-behind-a-proxy)
+GitLab's Helm integration does not support installing applications behind a proxy,
+but a [workaround](../../topics/autodevops/index.md#install-applications-behind-a-proxy)
is available.
+### Enable or disable local Tiller **(CORE ONLY)**
+
+> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/209736) in GitLab 13.2
+> - The option to disable local Tiller is [planned for removal](https://gitlab.com/gitlab-org/gitlab/-/issues/209736) in GitLab 13.3
+
+Local Tiller is under development, but is ready for production use. It is
+deployed behind a feature flag that is **enabled by default**.
+[GitLab administrators with access to the GitLab Rails console](../../administration/feature_flags.md)
+can enable it for your instance.
+
+To enable it:
+
+```ruby
+# Instance-wide
+Feature.enable(:managed_apps_local_tiller)
+```
+
+To disable it:
+
+```ruby
+# Instance-wide
+Feature.disable(:managed_apps_local_tiller)
+```
+
### cert-manager
> Introduced in GitLab 11.6 for project- and group-level clusters.
@@ -609,6 +637,7 @@ Supported applications:
- [Sentry](#install-sentry-using-gitlab-cicd)
- [GitLab Runner](#install-gitlab-runner-using-gitlab-cicd)
- [Cilium](#install-cilium-using-gitlab-cicd)
+- [Falco](#install-falco-using-gitlab-cicd)
- [Vault](#install-vault-using-gitlab-cicd)
- [JupyterHub](#install-jupyterhub-using-gitlab-cicd)
- [Elastic Stack](#install-elastic-stack-using-gitlab-cicd)
@@ -634,6 +663,11 @@ To install applications using GitLab CI/CD:
- template: Managed-Cluster-Applications.gitlab-ci.yml
```
+ NOTE: **Note:**
+ The job provided by this template connects to the cluster using tools provided
+ in a custom Docker image. It requires that you have a runner registered with the Docker,
+ Kubernetes, or Docker Machine executor.
+
1. Add a `.gitlab/managed-apps/config.yaml` file to define which
applications you would like to install. Define the `installed` key as
`true` to install the application and `false` to uninstall the
@@ -683,6 +717,10 @@ management project. Refer to the
[chart](https://github.com/helm/charts/tree/master/stable/nginx-ingress)
for the available configuration options.
+NOTE: **Note:**
+Support for installing the Ingress managed application is provided by the GitLab Configure group.
+If you run into unknown issues, please [open a new issue](https://gitlab.com/gitlab-org/gitlab/-/issues/new) and ping at least 2 people from the [Configure group](https://about.gitlab.com/handbook/product/categories/#configure-group).
+
### Install cert-manager using GitLab CI/CD
cert-manager is installed using GitLab CI/CD by defining configuration in
@@ -720,6 +758,10 @@ management project. Refer to the
[chart](https://hub.helm.sh/charts/jetstack/cert-manager) for the
available configuration options.
+NOTE: **Note:**
+Support for installing the Cert Manager managed application is provided by the GitLab Configure group.
+If you run into unknown issues, please [open a new issue](https://gitlab.com/gitlab-org/gitlab/-/issues/new) and ping at least 2 people from the [Configure group](https://about.gitlab.com/handbook/product/categories/#configure-group).
+
### Install Sentry using GitLab CI/CD
NOTE: **Note:**
@@ -781,6 +823,10 @@ postgresql:
postgresqlPassword: example-postgresql-password
```
+NOTE: **Note:**
+Support for installing the Sentry managed application is provided by the GitLab Health group.
+If you run into unknown issues, please [open a new issue](https://gitlab.com/gitlab-org/gitlab/-/issues/new) and ping at least 2 people from the [Health group](https://about.gitlab.com/handbook/product/product-categories/#health-group).
+
### Install PostHog using GitLab CI/CD
[PostHog](https://www.posthog.com) 🦔 is a developer-friendly, open-source product analytics platform.
@@ -852,6 +898,10 @@ project. Refer to the
[Configuration section of the Prometheus chart's README](https://github.com/helm/charts/tree/master/stable/prometheus#configuration)
for the available configuration options.
+NOTE: **Note:**
+Support for installing the Prometheus managed application is provided by the GitLab APM group.
+If you run into unknown issues, please [open a new issue](https://gitlab.com/gitlab-org/gitlab/-/issues/new) and ping at least 2 people from the [APM group](https://about.gitlab.com/handbook/product/product-categories/#apm-group).
+
### Install GitLab Runner using GitLab CI/CD
GitLab Runner is installed using GitLab CI/CD by defining configuration in
@@ -883,6 +933,10 @@ management project. Refer to the
[chart](https://gitlab.com/gitlab-org/charts/gitlab-runner) for the
available configuration options.
+NOTE: **Note:**
+Support for installing the Runner managed application is provided by the GitLab Runner group.
+If you run into unknown issues, please [open a new issue](https://gitlab.com/gitlab-org/gitlab/-/issues/new) and ping at least 2 people from the [Runner group](https://about.gitlab.com/handbook/product/product-categories/#runner-group).
+
### Install Cilium using GitLab CI/CD
> [Introduced](https://gitlab.com/gitlab-org/cluster-integration/cluster-applications/-/merge_requests/22) in GitLab 12.8.
@@ -909,8 +963,8 @@ a corresponding cluster type. The default value is blank. You can
check the recommended variables for each cluster type in the official
documentation:
-- [Google GKE](https://cilium.readthedocs.io/en/stable/gettingstarted/k8s-install-gke/#prepare-deploy-cilium)
-- [AWS EKS](https://cilium.readthedocs.io/en/stable/gettingstarted/k8s-install-eks/#prepare-deploy-cilium)
+- [Google GKE](https://docs.cilium.io/en/stable/gettingstarted/k8s-install-gke/#deploy-cilium)
+- [AWS EKS](https://docs.cilium.io/en/stable/gettingstarted/k8s-install-eks/#deploy-cilium)
You can customize Cilium's Helm variables by defining the
`.gitlab/managed-apps/cilium/values.yaml` file in your cluster
@@ -920,9 +974,9 @@ for the available configuration options.
CAUTION: **Caution:**
Installation and removal of the Cilium requires a **manual**
-[restart](https://cilium.readthedocs.io/en/stable/gettingstarted/k8s-install-gke/#restart-remaining-pods)
+[restart](https://docs.cilium.io/en/stable/gettingstarted/k8s-install-gke/#restart-unmanaged-pods)
of all affected pods in all namespaces to ensure that they are
-[managed](https://cilium.readthedocs.io/en/stable/troubleshooting/#ensure-pod-is-managed-by-cilium)
+[managed](https://docs.cilium.io/en/stable/troubleshooting/#ensure-pod-is-managed-by-cilium)
by the correct networking plugin.
NOTE: **Note:**
@@ -930,23 +984,20 @@ Major upgrades might require additional setup steps, please consult
the official [upgrade guide](https://docs.cilium.io/en/stable/install/upgrade/) for more
information.
-By default, Cilium will drop all disallowed packets upon policy
-deployment. The audit mode is scheduled for release in
-[Cilium 1.8](https://github.com/cilium/cilium/pull/9970). In the audit
-mode, disallowed packets will not be dropped, and audit
-notifications will be generated instead. GitLab provides alternative Docker
-images for Cilium with the audit patch included. You can switch to the
-custom build and enable the audit mode by adding the following to
+By default, Cilium's [audit
+mode](https://docs.cilium.io/en/v1.8/gettingstarted/policy-creation/?highlight=policy-audit#enable-policy-audit-mode)
+is enabled. In audit mode, Cilium doesn't drop disallowed packets. You
+can use `policy-verdict` log to observe policy-related decisions. You
+can disable audit mode by adding the following to
`.gitlab/managed-apps/cilium/values.yaml`:
```yaml
-global:
- registry: registry.gitlab.com/gitlab-org/defend/cilium
- policyAuditMode: true
+config:
+ policyAuditMode: false
agent:
monitor:
- eventTypes: ["drop", "audit"]
+ eventTypes: ["drop"]
```
The Cilium monitor log for traffic is logged out by the
@@ -968,24 +1019,121 @@ The [Hubble](https://github.com/cilium/hubble) monitoring daemon is
enabled by default and it's set to collect per namespace flow
metrics. This metrics are accessible on the [Threat Monitoring](../application_security/threat_monitoring/index.md)
dashboard. You can disable Hubble by adding the following to
-`.gitlab/managed-apps/config.yaml`:
+`.gitlab/managed-apps/cilium/values.yaml`:
```yaml
-cilium:
- installed: true
+global:
hubble:
- installed: false
+ enabled: false
```
You can also adjust Helm values for Hubble via
-`.gitlab/managed-apps/cilium/hubble-values.yaml`:
+`.gitlab/managed-apps/cilium/values.yaml`:
```yaml
-metrics:
- enabled:
- - 'flow:sourceContext=namespace;destinationContext=namespace'
+global:
+ hubble:
+ enabled: true
+ metrics:
+ enabled:
+ - 'flow:sourceContext=namespace;destinationContext=namespace'
+```
+
+NOTE: **Note:**
+Support for installing the Cilium managed application is provided by the GitLab Container Security group.
+If you run into unknown issues, please [open a new issue](https://gitlab.com/gitlab-org/gitlab/-/issues/new) and ping at least 2 people from the [Container Security group](https://about.gitlab.com/handbook/product/product-categories/#container-security-group).
+
+### Install Falco using GitLab CI/CD
+
+> [Introduced](https://gitlab.com/gitlab-org/cluster-integration/cluster-applications/-/merge_requests/91) in GitLab 13.1.
+
+GitLab Container Host Security Monitoring uses [Falco](https://falco.org/)
+as a runtime security tool that listens to the Linux kernel using eBPF. Falco parses system calls
+and asserts the stream against a configurable rules engine in real-time. For more information, see
+[Falco's Documentation](https://falco.org/docs/).
+
+You can enable Falco in the
+`.gitlab/managed-apps/config.yaml` file:
+
+```yaml
+falco:
+ installed: true
+```
+
+You can customize Falco's Helm variables by defining the
+`.gitlab/managed-apps/falco/values.yaml` file in your cluster
+management project. Refer to the
+[Falco chart](https://github.com/falcosecurity/charts/tree/master/falco)
+for the available configuration options.
+
+CAUTION: **Caution:**
+By default eBPF support is enabled and Falco will use an [eBPF probe](https://falco.org/docs/event-sources/drivers/#using-the-ebpf-probe) to pass system calls to userspace.
+If your cluster doesn't support this, you can configure it to use Falco kernel module instead by adding the following to `.gitlab/managed-apps/falco/values.yaml`:
+
+```yaml
+ebpf:
+ enabled: false
```
+In rare cases where automatic probe installation on your cluster isn't possible and the kernel/probe
+isn't precompiled, you may need to manually prepare the kernel module or eBPF probe with
+[driverkit](https://github.com/falcosecurity/driverkit#against-a-kubernetes-cluster)
+and install it on each cluster node.
+
+By default, Falco is deployed with a limited set of rules. To add more rules, add the following to
+`.gitlab/managed-apps/falco/values.yaml` (you can get examples from
+[Cloud Native Security Hub](https://securityhub.dev/)):
+
+```yaml
+customRules:
+ file-integrity.yaml: |-
+ - rule: Detect New File
+ desc: detect new file created
+ condition: >
+ evt.type = chmod or evt.type = fchmod
+ output: >
+ File below a known directory opened for writing (user=%user.name
+ command=%proc.cmdline file=%fd.name parent=%proc.pname pcmdline=%proc.pcmdline gparent=%proc.aname[2])
+ priority: ERROR
+ tags: [filesystem]
+ - rule: Detect New Directory
+ desc: detect new directory created
+ condition: >
+ mkdir
+ output: >
+ File below a known directory opened for writing (user=%user.name
+ command=%proc.cmdline file=%fd.name parent=%proc.pname pcmdline=%proc.pcmdline gparent=%proc.aname[2])
+ priority: ERROR
+ tags: [filesystem]
+```
+
+By default, Falco only outputs security events to logs as JSON objects. To set it to output to an
+[external API](https://falco.org/docs/alerts/#https-output-send-alerts-to-an-https-end-point)
+or [application](https://falco.org/docs/alerts/#program-output),
+add the following to `.gitlab/managed-apps/falco/values.yaml`:
+
+```yaml
+falco:
+ programOutput:
+ enabled: true
+ keepAlive: false
+ program: mail -s "Falco Notification" someone@example.com
+
+ httpOutput:
+ enabled: true
+ url: http://some.url
+```
+
+You can check these logs with the following command:
+
+```shell
+kubectl logs -l app=falco -n gitlab-managed-apps
+```
+
+NOTE: **Note:**
+Support for installing the Falco managed application is provided by the GitLab Container Security group.
+If you run into unknown issues, please [open a new issue](https://gitlab.com/gitlab-org/gitlab/-/issues/new) and ping at least 2 people from the [Container Security group](https://about.gitlab.com/handbook/product/product-categories/#container-security-group).
+
### Install Vault using GitLab CI/CD
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/9982) in GitLab 12.9.
@@ -1035,7 +1183,7 @@ below are examples and should be replaced with settings specific to your environ
ui:
enabled: true
server:
- # Disable the built in data storage volume as it's not safe for Hight Availablity mode
+ # Disable the built in data storage volume as it's not safe for Hight Availability mode
dataStorage:
enabled: false
# Enable High Availability Mode
@@ -1075,6 +1223,10 @@ kubectl -n gitlab-managed-apps exec -it vault-0 sh
This should give you your unseal keys and initial root token. Make sure to note these down
and keep these safe as you will need them to unseal the Vault throughout its lifecycle.
+NOTE: **Note:**
+Support for installing the Vault managed application is provided by the GitLab Release Management group.
+If you run into unknown issues, please [open a new issue](https://gitlab.com/gitlab-org/gitlab/-/issues/new) and ping at least 2 people from the [Release Management group](https://about.gitlab.com/handbook/product/product-categories/#release-management-group).
+
### Install JupyterHub using GitLab CI/CD
> [Introduced](https://gitlab.com/gitlab-org/cluster-integration/cluster-applications/-/merge_requests/40) in GitLab 12.8.
@@ -1124,6 +1276,10 @@ Refer to the
[chart reference](https://zero-to-jupyterhub.readthedocs.io/en/stable/reference/reference.html) for the
available configuration options.
+NOTE: **Note:**
+Support for installing the JupyterHub managed application is provided by the GitLab Configure group.
+If you run into unknown issues, please [open a new issue](https://gitlab.com/gitlab-org/gitlab/-/issues/new) and ping at least 2 people from the [Configure group](https://about.gitlab.com/handbook/product/categories/#configure-group).
+
### Install Elastic Stack using GitLab CI/CD
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/25138) in GitLab 12.8.
@@ -1151,6 +1307,10 @@ available configuration options.
NOTE: **Note:**
In this alpha implementation of installing Elastic Stack through CI, reading the environment logs through Elasticsearch is unsupported. This is supported if [installed via the UI](#elastic-stack).
+NOTE: **Note:**
+Support for installing the Elastic Stack managed application is provided by the GitLab APM group.
+If you run into unknown issues, please [open a new issue](https://gitlab.com/gitlab-org/gitlab/-/issues/new) and ping at least 2 people from the [APM group](https://about.gitlab.com/handbook/product/product-categories/#apm-group).
+
### Install Crossplane using GitLab CI/CD
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/35675) in GitLab 12.9.
@@ -1177,6 +1337,10 @@ management project. Refer to the
[chart](https://github.com/crossplane/crossplane/tree/master/cluster/charts/crossplane#configuration) for the
available configuration options. Note that this link points to the documentation for the current development release, which may differ from the version you have installed.
+NOTE: **Note:**
+Support for the Crossplane managed application is provided by the Crossplane team.
+If you run into issues, please [open a support ticket](https://github.com/crossplane/crossplane/issues/new/choose) directly.
+
### Install Fluentd using GitLab CI/CD
> [Introduced](https://gitlab.com/gitlab-org/cluster-integration/cluster-applications/-/merge_requests/76) in GitLab 12.10.
@@ -1201,6 +1365,10 @@ The configuration chart link points to the current development release, which
may differ from the version you have installed. To ensure compatibility, switch
to the specific branch or tag you are using.
+NOTE: **Note:**
+Support for installing the Fluentd managed application is provided by the GitLab Container Security group.
+If you run into unknown issues, please [open a new issue](https://gitlab.com/gitlab-org/gitlab/-/issues/new) and ping at least 2 people from the [Container Security group](https://about.gitlab.com/handbook/product/product-categories/#container-security-group).
+
### Install Knative using GitLab CI/CD
To install Knative, define the `.gitlab/managed-apps/config.yaml` file
@@ -1223,6 +1391,10 @@ domain: 'my.wildcard.A.record.dns'
If you plan to use GitLab Serverless capabilities, be sure to set an A record wildcard domain on your custom configuration.
+NOTE: **Note:**
+Support for installing the Knative managed application is provided by the GitLab Configure group.
+If you run into unknown issues, please [open a new issue](https://gitlab.com/gitlab-org/gitlab/-/issues/new) and ping at least 2 people from the [Configure group](https://about.gitlab.com/handbook/product/categories/#configure-group).
+
#### Knative Metrics
GitLab provides [Invocation Metrics](../project/clusters/serverless/index.md#invocation-metrics) for your functions. To collect these metrics, you must have:
@@ -1243,6 +1415,92 @@ by running the following command:
kubectl delete -f https://gitlab.com/gitlab-org/cluster-integration/cluster-applications/-/raw/02c8231e30ef5b6725e6ba368bc63863ceb3c07d/src/default-data/knative/istio-metrics.yaml
```
+### Install AppArmor using GitLab CI/CD
+
+> [Introduced](https://gitlab.com/gitlab-org/cluster-integration/cluster-applications/-/merge_requests/100) in GitLab 13.1.
+
+To install AppArmor into the `gitlab-managed-apps` namespace of your cluster using GitLab CI/CD, define the following configuration in `.gitlab/managed-apps/config.yaml`:
+
+```yaml
+apparmor:
+ installed: true
+```
+
+You can define one or more AppArmor profiles by adding them into `.gitlab/managed-apps/apparmor/values.yaml` as the following:
+
+```yaml
+profiles:
+ profile-one: |-
+ profile profile-one {
+ file,
+ }
+```
+
+Refer to the [AppArmor chart](https://gitlab.com/gitlab-org/charts/apparmor) for more information on this chart.
+
+#### Using AppArmor profiles in your deployments
+
+After installing AppAmor, you can use profiles by adding Pod Annotations. If you're using Auto
+DevOps, you can [customize `auto-deploy-values.yaml`](../../topics/autodevops/customize.md#customize-values-for-helm-chart)
+to annotate your pods. Although it's helpful to be aware of the [list of custom attributes](https://gitlab.com/gitlab-org/charts/auto-deploy-app#gitlabs-auto-deploy-helm-chart), you're only required to set
+`podAnnotations` as follows:
+
+```yaml
+podAnnotations:
+ container.apparmor.security.beta.kubernetes.io/auto-deploy-app: localhost/profile-one
+```
+
+The only information to be changed here is the profile name which is `profile-one` in this example. Refer to the [AppArmor tutorial](https://kubernetes.io/docs/tutorials/clusters/apparmor/#securing-a-pod) for more information on how AppArmor is integrated in Kubernetes.
+
+#### Using PodSecurityPolicy in your deployments
+
+NOTE: **Note:**
+To enable AppArmor annotations on a Pod Security Policy you must first
+load the correspondingAppArmor profile.
+
+[Pod Security Policies](https://kubernetes.io/docs/concepts/policy/pod-security-policy/)are
+resources at the cluster level that control security-related
+properties of deployed pods. You can use such a policy to enable
+loaded AppArmor profiles and apply necessary pod restrictions across a
+cluster. You can deploy a new policy by adding the following
+to`.gitlab/managed-apps/apparmor/values.yaml`:
+
+```yaml
+securityPolicies:
+ example:
+ defaultProfile: profile-one
+ allowedProfiles:
+ - profile-one
+ - profile-two
+ spec:
+ privileged: false
+ seLinux:
+ rule: RunAsAny
+ supplementalGroups:
+ rule: RunAsAny
+ runAsUser:
+ rule: RunAsAny
+ fsGroup:
+ rule: RunAsAny
+ volumes:
+ - '*'
+```
+
+This example creates a single policy named `example` with the provided
+specification, and enables [AppArmor
+annotations](https://kubernetes.io/docs/tutorials/clusters/apparmor/#podsecuritypolicy-annotations)on
+it.
+
+NOTE: **Note:**
+Support for installing the AppArmor managed application is provided by the GitLab Container Security group.
+If you run into unknown issues, please [open a new issue](https://gitlab.com/gitlab-org/gitlab/-/issues/new) and ping at least 2 people from the [Container Security group](https://about.gitlab.com/handbook/product/product-categories/#container-security-group).
+
+## Browse applications logs
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/36769) in GitLab 13.2.
+
+Logs produced by pods running **GitLab Managed Apps** can be browsed using [**Log Explorer**](../project/clusters/kubernetes_pod_logs.md).
+
## Upgrading applications
> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/24789) in GitLab 11.8.
@@ -1342,3 +1600,16 @@ The number and size of nodes might not have enough IP addresses to run or instal
For reference, all the AWS instance IP limits are found
[in this AWS repository on GitHub](https://github.com/aws/amazon-vpc-cni-k8s/blob/master/pkg/awsutils/vpc_ip_resource_limit.go) (search for `InstanceENIsAvailable`).
+
+### Unable to install Prometheus
+
+Installing Prometheus is failing with the following error:
+
+```shell
+# kubectl -n gitlab-managed-apps logs install-prometheus
+...
+Error: Could not get apiVersions from Kubernetes: unable to retrieve the complete list of server APIs: admission.certmanager.k8s.io/v1beta1: the server is currently unable to handle the request
+```
+
+This is a bug that was introduced in Helm `2.15` and fixed in `3.0.2`. As a workaround, you'll need
+to make sure that [`cert-manager`](#cert-manager) is installed successfully prior to installing Prometheus.
diff --git a/doc/user/clusters/crossplane.md b/doc/user/clusters/crossplane.md
index 3a430ad55bd..b30ebc57338 100644
--- a/doc/user/clusters/crossplane.md
+++ b/doc/user/clusters/crossplane.md
@@ -6,17 +6,15 @@ info: To determine the technical writer assigned to the Stage/Group associated w
# Crossplane configuration
-Once Crossplane [is installed](applications.md#crossplane), it must be configured for
-use.
-
+After [installing](applications.md#crossplane) Crossplane, you must configure it for use.
The process of configuring Crossplane includes:
-1. Configuring RBAC permissions.
-1. Configuring Crossplane with a cloud provider.
-1. Configure managed service access.
-1. Setting up Resource classes.
-1. Using Auto DevOps configuration options.
-1. Connect to the PostgreSQL instance.
+1. [Configure RBAC permissions](#configure-rbac-permissions).
+1. [Configure Crossplane with a cloud provider](#configure-crossplane-with-a-cloud-provider).
+1. [Configure managed service access](#configure-managed-service-access).
+1. [Set up Resource classes](#setting-up-resource-classes).
+1. Use [Auto DevOps configuration options](#auto-devops-configuration-options).
+1. [Connect to the PostgreSQL instance](#connect-to-the-postgresql-instance).
To allow Crossplane to provision cloud services such as PostgreSQL, the cloud provider
stack must be configured with a user account. For example:
@@ -24,14 +22,13 @@ stack must be configured with a user account. For example:
- A service account for GCP.
- An IAM user for AWS.
-Important notes:
+Some important notes:
-- This guide uses GCP as an example. However, the process for AWS and Azure will be
-similar.
-- Crossplane requires the Kubernetes cluster to be VPC native with Alias IPs enabled so
-that the IP address of the pods are routable within the GCP network.
+- This guide uses GCP as an example, but the processes for AWS and Azure are similar.
+- Crossplane requires the Kubernetes cluster to be VPC native with Alias IPs enabled,
+ so the IP addresses of the pods can be routed within the GCP network.
-First, we need to declare some environment variables with configuration that will be used throughout this guide:
+First, declare some environment variables with configuration for use in this guide:
```shell
export PROJECT_ID=crossplane-playground # the GCP project where all resources reside.
@@ -41,228 +38,223 @@ export REGION=us-central1 # the GCP region where the GKE cluster is provisioned.
## Configure RBAC permissions
-- For GitLab-managed clusters, RBAC is configured automatically.
-
-- For non-GitLab managed clusters, ensure that the service account for the token provided can manage resources in the `database.crossplane.io` API group:
-
- 1. Save the following YAML as `crossplane-database-role.yaml`:
-
- ```yaml
- apiVersion: rbac.authorization.k8s.io/v1
- kind: ClusterRole
- metadata:
- name: crossplane-database-role
- labels:
- rbac.authorization.k8s.io/aggregate-to-edit: "true"
- rules:
- - apiGroups:
- - database.crossplane.io
- resources:
- - postgresqlinstances
- verbs:
- - get
- - list
- - create
- - update
- - delete
- - patch
- - watch
- ```
-
- 1. Apply the cluster role to the cluster:
-
- ```shell
- kubectl apply -f crossplane-database-role.yaml
- ```
+For GitLab-managed clusters, role-based access control (RBAC) is configured automatically.
+
+For non-GitLab managed clusters, ensure that the service account for the token
+provided can manage resources in the `database.crossplane.io` API group:
+
+1. Save the following YAML as `crossplane-database-role.yaml`:
+
+ ```yaml
+ apiVersion: rbac.authorization.k8s.io/v1
+ kind: ClusterRole
+ metadata:
+ name: crossplane-database-role
+ labels:
+ rbac.authorization.k8s.io/aggregate-to-edit: "true"
+ rules:
+ - apiGroups:
+ - database.crossplane.io
+ resources:
+ - postgresqlinstances
+ verbs:
+ - get
+ - list
+ - create
+ - update
+ - delete
+ - patch
+ - watch
+ ```
+
+1. Apply the cluster role to the cluster:
+
+ ```shell
+ kubectl apply -f crossplane-database-role.yaml
+ ```
## Configure Crossplane with a cloud provider
See [Configure Your Cloud Provider Account](https://crossplane.github.io/docs/v0.4/cloud-providers.html)
to configure the installed cloud provider stack with a user account.
-Note that the Secret and the Provider resource referencing the Secret needs to be
+NOTE: **Note:**
+The Secret, and the Provider resource referencing the Secret, must be
applied to the `gitlab-managed-apps` namespace in the guide. Make sure you change that
while following the process.
-[Configure Providers](https://crossplane.github.io/docs/v0.4/cloud-providers.html)
-
## Configure Managed Service Access
-We need to configure connectivity between the PostgreSQL database and the GKE cluster.
-This can done by either:
+Next, configure connectivity between the PostgreSQL database and the GKE cluster
+by either:
- Using Crossplane as demonstrated below.
- Directly in the GCP console by
-[configuring private services access](https://cloud.google.com/vpc/docs/configure-private-services-access).
-Create a GlobalAddress and Connection resources:
-
-```shell
-cat > network.yaml <<EOF
----
-# gitlab-ad-globaladdress defines the IP range that will be allocated for cloud services connecting to the instances in the given Network.
-
-apiVersion: compute.gcp.crossplane.io/v1alpha3
-kind: GlobalAddress
-metadata:
- name: gitlab-ad-globaladdress
-spec:
- providerRef:
- name: gcp-provider
- reclaimPolicy: Delete
- name: gitlab-ad-globaladdress
- purpose: VPC_PEERING
- addressType: INTERNAL
- prefixLength: 16
- network: projects/$PROJECT_ID/global/networks/$NETWORK_NAME
----
-# gitlab-ad-connection is what allows cloud services to use the allocated GlobalAddress for communication. Behind
-# the scenes, it creates a VPC peering to the network that those service instances actually live.
-
-apiVersion: servicenetworking.gcp.crossplane.io/v1alpha3
-kind: Connection
-metadata:
- name: gitlab-ad-connection
-spec:
- providerRef:
- name: gcp-provider
- reclaimPolicy: Delete
- parent: services/servicenetworking.googleapis.com
- network: projects/$PROJECT_ID/global/networks/$NETWORK_NAME
- reservedPeeringRangeRefs:
- - name: gitlab-ad-globaladdress
-EOF
-```
-
-Apply the settings specified in the file with the following command:
-
-```shell
-kubectl apply -f network.yaml
-```
-
-You can verify creation of the network resources with the following commands.
-Verify that the status of both of these resources is ready and is synced.
-
-```shell
-kubectl describe connection.servicenetworking.gcp.crossplane.io gitlab-ad-connection
-kubectl describe globaladdress.compute.gcp.crossplane.io gitlab-ad-globaladdress
-```
+ [configuring private services access](https://cloud.google.com/vpc/docs/configure-private-services-access).
+
+1. Run the following command, which creates a `network.yaml` file, and configures
+ `GlobalAddress` and connection resources:
+
+ ```plaintext
+ cat > network.yaml <<EOF
+ ---
+ # gitlab-ad-globaladdress defines the IP range that will be allocated
+ # for cloud services connecting to the instances in the given Network.
+
+ apiVersion: compute.gcp.crossplane.io/v1alpha3
+ kind: GlobalAddress
+ metadata:
+ name: gitlab-ad-globaladdress
+ spec:
+ providerRef:
+ name: gcp-provider
+ reclaimPolicy: Delete
+ name: gitlab-ad-globaladdress
+ purpose: VPC_PEERING
+ addressType: INTERNAL
+ prefixLength: 16
+ network: projects/$PROJECT_ID/global/networks/$NETWORK_NAME
+ ---
+ # gitlab-ad-connection is what allows cloud services to use the allocated
+ # GlobalAddress for communication. Behind the scenes, it creates a VPC peering
+ # to the network that those service instances actually live.
+
+ apiVersion: servicenetworking.gcp.crossplane.io/v1alpha3
+ kind: Connection
+ metadata:
+ name: gitlab-ad-connection
+ spec:
+ providerRef:
+ name: gcp-provider
+ reclaimPolicy: Delete
+ parent: services/servicenetworking.googleapis.com
+ network: projects/$PROJECT_ID/global/networks/$NETWORK_NAME
+ reservedPeeringRangeRefs:
+ - name: gitlab-ad-globaladdress
+ EOF
+ ```
+
+1. Apply the settings specified in the file with the following command:
+
+ ```shell
+ kubectl apply -f network.yaml
+ ```
+
+1. Verify the creation of the network resources, and that both resources are ready and synced.
+
+ ```shell
+ kubectl describe connection.servicenetworking.gcp.crossplane.io gitlab-ad-connection
+ kubectl describe globaladdress.compute.gcp.crossplane.io gitlab-ad-globaladdress
+ ```
## Setting up Resource classes
-Resource classes are a way of defining a configuration for the required managed service. We will define the PostgreSQL Resource class
-
-- Define a `gcp-postgres-standard.yaml` resource class which contains
-
-1. A default CloudSQLInstanceClass.
-1. A CloudSQLInstanceClass with labels.
-
-```shell
-cat > gcp-postgres-standard.yaml <<EOF
-apiVersion: database.gcp.crossplane.io/v1beta1
-kind: CloudSQLInstanceClass
-metadata:
- name: cloudsqlinstancepostgresql-standard
- labels:
- gitlab-ad-demo: "true"
-specTemplate:
- writeConnectionSecretsToNamespace: gitlab-managed-apps
- forProvider:
- databaseVersion: POSTGRES_11_7
- region: $REGION
- settings:
- tier: db-custom-1-3840
- dataDiskType: PD_SSD
- dataDiskSizeGb: 10
- ipConfiguration:
- privateNetwork: projects/$PROJECT_ID/global/networks/$NETWORK_NAME
- # this should match the name of the provider created in the above step
- providerRef:
- name: gcp-provider
- reclaimPolicy: Delete
----
-apiVersion: database.gcp.crossplane.io/v1beta1
-kind: CloudSQLInstanceClass
-metadata:
- name: cloudsqlinstancepostgresql-standard-default
- annotations:
- resourceclass.crossplane.io/is-default-class: "true"
-specTemplate:
- writeConnectionSecretsToNamespace: gitlab-managed-apps
- forProvider:
- databaseVersion: POSTGRES_11_7
- region: $REGION
- settings:
- tier: db-custom-1-3840
- dataDiskType: PD_SSD
- dataDiskSizeGb: 10
- ipConfiguration:
- privateNetwork: projects/$PROJECT_ID/global/networks/$NETWORK_NAME
- # this should match the name of the provider created in the above step
- providerRef:
- name: gcp-provider
- reclaimPolicy: Delete
-EOF
-```
-
-Apply the resource class configuration with the following command:
-
-```shell
-kubectl apply -f gcp-postgres-standard.yaml
-```
-
-Verify creation of the Resource class with the following command:
-
-```shell
-kubectl get cloudsqlinstanceclasses
-```
-
-The Resource Classes allow you to define classes of service for a managed service. We could create another `CloudSQLInstanceClass` which requests for a larger or a faster disk. It could also request for a specific version of the database.
+Use resource classes to define a configuration for the required managed service.
+This example defines the PostgreSQL Resource class:
+
+1. Run the following command, which define a `gcp-postgres-standard.yaml` resource
+ class containing a default `CloudSQLInstanceClass` with labels:
+
+ ```plaintext
+ cat > gcp-postgres-standard.yaml <<EOF
+ apiVersion: database.gcp.crossplane.io/v1beta1
+ kind: CloudSQLInstanceClass
+ metadata:
+ name: cloudsqlinstancepostgresql-standard
+ labels:
+ gitlab-ad-demo: "true"
+ specTemplate:
+ writeConnectionSecretsToNamespace: gitlab-managed-apps
+ forProvider:
+ databaseVersion: POSTGRES_11_7
+ region: $REGION
+ settings:
+ tier: db-custom-1-3840
+ dataDiskType: PD_SSD
+ dataDiskSizeGb: 10
+ ipConfiguration:
+ privateNetwork: projects/$PROJECT_ID/global/networks/$NETWORK_NAME
+ # this should match the name of the provider created in the above step
+ providerRef:
+ name: gcp-provider
+ reclaimPolicy: Delete
+ ---
+ apiVersion: database.gcp.crossplane.io/v1beta1
+ kind: CloudSQLInstanceClass
+ metadata:
+ name: cloudsqlinstancepostgresql-standard-default
+ annotations:
+ resourceclass.crossplane.io/is-default-class: "true"
+ specTemplate:
+ writeConnectionSecretsToNamespace: gitlab-managed-apps
+ forProvider:
+ databaseVersion: POSTGRES_11_7
+ region: $REGION
+ settings:
+ tier: db-custom-1-3840
+ dataDiskType: PD_SSD
+ dataDiskSizeGb: 10
+ ipConfiguration:
+ privateNetwork: projects/$PROJECT_ID/global/networks/$NETWORK_NAME
+ # this should match the name of the provider created in the above step
+ providerRef:
+ name: gcp-provider
+ reclaimPolicy: Delete
+ EOF
+ ```
+
+1. Apply the resource class configuration with the following command:
+
+ ```shell
+ kubectl apply -f gcp-postgres-standard.yaml
+ ```
+
+1. Verify creation of the Resource class with the following command:
+
+ ```shell
+ kubectl get cloudsqlinstanceclasses
+ ```
+
+The Resource Classes allow you to define classes of service for a managed service.
+We could create another `CloudSQLInstanceClass` which requests for a larger or a
+faster disk. It could also request for a specific version of the database.
## Auto DevOps Configuration Options
-The Auto DevOps pipeline can be run with the following options:
-
-The Environment variables, `AUTO_DEVOPS_POSTGRES_MANAGED` and `AUTO_DEVOPS_POSTGRES_MANAGED_CLASS_SELECTOR` need to be set to provision PostgreSQL using Crossplane
-
-Alternatively, the following options can be overridden from the values for the Helm chart.
-
-- `postgres.managed` set to true which will select a default resource class.
- The resource class needs to be marked with the annotation
- `resourceclass.crossplane.io/is-default-class: "true"`. The CloudSQLInstanceClass
- `cloudsqlinstancepostgresql-standard-default` will be used to satisfy the claim.
-
-- `postgres.managed` set to `true` with `postgres.managedClassSelector`
- providing the resource class to choose based on labels. In this case, the
- value of `postgres.managedClassSelector.matchLabels.gitlab-ad-demo="true"`
- will select the CloudSQLInstance class `cloudsqlinstancepostgresql-standard`
- to satisfy the claim request.
+You can run the Auto DevOps pipeline with either of the following options:
+
+- Setting the Environment variables `AUTO_DEVOPS_POSTGRES_MANAGED` and
+ `AUTO_DEVOPS_POSTGRES_MANAGED_CLASS_SELECTOR` to provision PostgreSQL using Crossplane.
+- Overriding values for the Helm chart:
+ - Set `postgres.managed` to `true`, which selects a default resource class.
+ Mark the resource class with the annotation
+ `resourceclass.crossplane.io/is-default-class: "true"`. The CloudSQLInstanceClass
+ `cloudsqlinstancepostgresql-standard-default` is used to satisfy the claim.
+ - Set `postgres.managed` to `true` with `postgres.managedClassSelector`
+ providing the resource class to choose, based on labels. In this case, the
+ value of `postgres.managedClassSelector.matchLabels.gitlab-ad-demo="true"`
+ selects the CloudSQLInstance class `cloudsqlinstancepostgresql-standard`
+ to satisfy the claim request.
The Auto DevOps pipeline should provision a PostgresqlInstance when it runs successfully.
-Verify creation of the PostgreSQL Instance.
+To verify the PostgreSQL instance was created, run this command. When the `STATUS`
+field of the PostgresqlInstance changes to `BOUND`, it's successfully provisioned:
```shell
-kubectl get postgresqlinstance
-```
+$ kubectl get postgresqlinstance
-Sample Output: The `STATUS` field of the PostgresqlInstance transitions to `BOUND` when it is successfully provisioned.
-
-```plaintext
NAME STATUS CLASS-KIND CLASS-NAME RESOURCE-KIND RESOURCE-NAME AGE
staging-test8 Bound CloudSQLInstanceClass cloudsqlinstancepostgresql-standard CloudSQLInstance xp-ad-demo-24-staging-staging-test8-jj55c 9m
```
-The endpoint of the PostgreSQL instance, and the user credentials, are present in a secret called `app-postgres` within the same project namespace.
-
-Verify the secret with the database information is created with the following command:
+The endpoint of the PostgreSQL instance, and the user credentials, are present in
+a secret called `app-postgres` within the same project namespace. You can verify the
+secret with the following command:
```shell
-kubectl describe secret app-postgres
-```
-
-Sample Output:
+$ kubectl describe secret app-postgres
-```plaintext
Name: app-postgres
Namespace: xp-ad-demo-24-staging
Labels: <none>
diff --git a/doc/user/clusters/management_project.md b/doc/user/clusters/management_project.md
index c8755af29a3..892d2bce184 100644
--- a/doc/user/clusters/management_project.md
+++ b/doc/user/clusters/management_project.md
@@ -97,7 +97,7 @@ Development, Staging, and Production cluster respectively.
```yaml
stages:
-- deploy
+ - deploy
configure development cluster:
stage: deploy
diff --git a/doc/user/compliance/compliance_dashboard/img/compliance_dashboard_v12_10.png b/doc/user/compliance/compliance_dashboard/img/compliance_dashboard_v12_10.png
deleted file mode 100644
index 466552f746e..00000000000
--- a/doc/user/compliance/compliance_dashboard/img/compliance_dashboard_v12_10.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/compliance/compliance_dashboard/img/compliance_dashboard_v13_2.png b/doc/user/compliance/compliance_dashboard/img/compliance_dashboard_v13_2.png
new file mode 100644
index 00000000000..e1edfcdd024
--- /dev/null
+++ b/doc/user/compliance/compliance_dashboard/img/compliance_dashboard_v13_2.png
Binary files differ
diff --git a/doc/user/compliance/compliance_dashboard/index.md b/doc/user/compliance/compliance_dashboard/index.md
index 08a26a45b17..e7db73e25d9 100644
--- a/doc/user/compliance/compliance_dashboard/index.md
+++ b/doc/user/compliance/compliance_dashboard/index.md
@@ -17,7 +17,7 @@ for merging into production.
To access the Compliance Dashboard for a group, navigate to **{shield}** **Security & Compliance > Compliance** on the group's menu.
-![Compliance Dashboard](img/compliance_dashboard_v12_10.png)
+![Compliance Dashboard](img/compliance_dashboard_v13_2.png)
## Use cases
@@ -27,6 +27,7 @@ You can use the dashboard to:
- Get an overview of the latest Merge Request for each project.
- See if Merge Requests were approved and by whom.
+- See Merge Request authors.
- See the latest [CI Pipeline](../../../ci/pipelines/index.md) result for each Merge Request.
## Permissions
diff --git a/doc/user/compliance/license_compliance/img/policies_maintainer_add_v13_0.png b/doc/user/compliance/license_compliance/img/policies_maintainer_add_v13_0.png
deleted file mode 100644
index 8070e2cb1a5..00000000000
--- a/doc/user/compliance/license_compliance/img/policies_maintainer_add_v13_0.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/compliance/license_compliance/img/policies_maintainer_add_v13_2.png b/doc/user/compliance/license_compliance/img/policies_maintainer_add_v13_2.png
new file mode 100644
index 00000000000..2d5946503cf
--- /dev/null
+++ b/doc/user/compliance/license_compliance/img/policies_maintainer_add_v13_2.png
Binary files differ
diff --git a/doc/user/compliance/license_compliance/img/policies_maintainer_edit_v13_0.png b/doc/user/compliance/license_compliance/img/policies_maintainer_edit_v13_0.png
deleted file mode 100644
index 741d1237751..00000000000
--- a/doc/user/compliance/license_compliance/img/policies_maintainer_edit_v13_0.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/compliance/license_compliance/img/policies_maintainer_edit_v13_2.png b/doc/user/compliance/license_compliance/img/policies_maintainer_edit_v13_2.png
new file mode 100644
index 00000000000..ee3bb310c1a
--- /dev/null
+++ b/doc/user/compliance/license_compliance/img/policies_maintainer_edit_v13_2.png
Binary files differ
diff --git a/doc/user/compliance/license_compliance/index.md b/doc/user/compliance/license_compliance/index.md
index 4ceb393af8c..fb287fb2bf6 100644
--- a/doc/user/compliance/license_compliance/index.md
+++ b/doc/user/compliance/license_compliance/index.md
@@ -23,8 +23,8 @@ GitLab checks the License Compliance report, compares the licenses between the
source and target branches, and shows the information right on the merge request.
Denied licenses will be clearly visible with an `x` red icon next to them
as well as new licenses which need a decision from you. In addition, you can
-[manually allow or deny](#project-policies-for-license-compliance)
-licenses in your project's settings.
+[manually allow or deny](#policies)
+licenses in your project's license compliance policy section.
NOTE: **Note:**
If the license compliance report doesn't have anything to compare to, no information
@@ -46,7 +46,7 @@ When GitLab detects a **Denied** license, you can view it in the [license list](
You can view and modify existing policies from the [policies](#policies) tab.
-![Edit Policy](img/policies_maintainer_edit_v13_0.png)
+![Edit Policy](img/policies_maintainer_edit_v13_2.png)
## Use cases
@@ -64,7 +64,7 @@ The following languages and package managers are supported.
| Go | [Godep](https://github.com/tools/godep), [go mod](https://github.com/golang/go/wiki/Modules) |[License Finder](https://github.com/pivotal/LicenseFinder)|
| Java | [Gradle](https://gradle.org/), [Maven](https://maven.apache.org/) |[License Finder](https://github.com/pivotal/LicenseFinder)|
| .NET | [Nuget](https://www.nuget.org/) (.NET Framework is supported via the [mono project](https://www.mono-project.com/). Windows specific dependencies are not supported at this time.) |[License Finder](https://github.com/pivotal/LicenseFinder)|
-| Python | [pip](https://pip.pypa.io/en/stable/) (Python is supported through [requirements.txt](https://pip.pypa.io/en/1.1/requirements/) and [Pipfile.lock](https://github.com/pypa/pipfile#pipfilelock).) |[License Finder](https://github.com/pivotal/LicenseFinder)|
+| Python | [pip](https://pip.pypa.io/en/stable/) (Python is supported through [requirements.txt](https://pip.pypa.io/en/stable/user_guide/#requirements-files) and [Pipfile.lock](https://github.com/pypa/pipfile#pipfilelock).) |[License Finder](https://github.com/pivotal/LicenseFinder)|
| Ruby | [gem](https://rubygems.org/) |[License Finder](https://github.com/pivotal/LicenseFinder)|
| Objective-C, Swift | [Carthage](https://github.com/Carthage/Carthage) |[License Finder](https://github.com/pivotal/LicenseFinder)|
@@ -86,7 +86,7 @@ which means that the reported licenses might be incomplete or inaccurate.
| Elixir | [mix](https://elixir-lang.org/getting-started/mix-otp/introduction-to-mix.html) |[License Finder](https://github.com/pivotal/LicenseFinder)|
| C++/C | [conan](https://conan.io/) |[License Finder](https://github.com/pivotal/LicenseFinder)|
| Scala | [sbt](https://www.scala-sbt.org/) |[License Finder](https://github.com/pivotal/LicenseFinder)|
-| Rust | [cargo](https://crates.io/) |[License Finder](https://github.com/pivotal/LicenseFinder)|
+| Rust | [cargo](https://crates.io) |[License Finder](https://github.com/pivotal/LicenseFinder)|
| PHP | [composer](https://getcomposer.org/) |[License Finder](https://github.com/pivotal/LicenseFinder)|
## Requirements
@@ -339,7 +339,7 @@ strict-ssl = false
### Configuring Yarn projects
-You can configure Yarn projects by using a [`.yarnrc.yml`](https://yarnpkg.com/configuration/yarnrc/)
+You can configure Yarn projects by using a [`.yarnrc.yml`](https://yarnpkg.com/configuration/yarnrc)
file.
#### Using private Yarn registries
@@ -385,6 +385,26 @@ You can supply a custom root certificate to complete TLS verification by using t
specifying a `ca` setting in a [`.bowerrc`](https://bower.io/docs/config/#bowerrc-specification)
file.
+#### Using private Bundler registries
+
+If you have a private Bundler registry you can use the
+[`source`](https://bundler.io/man/gemfile.5.html#GLOBAL-SOURCES)
+setting to specify its location.
+
+For example:
+
+```plaintext
+source "https://gems.example.com"
+```
+
+#### Custom root certificates for Bundler
+
+You can supply a custom root certificate to complete TLS verification by using the
+`ADDITIONAL_CA_CERT_BUNDLE` [environment variable](#available-variables), or by
+specifying a [`BUNDLE_SSL_CA_CERT`](https://bundler.io/v2.0/man/bundle-config.1.html)
+[environment variable](../../../ci/variables/README.md#custom-environment-variables)
+in the job definition.
+
### Configuring Conan projects
You can configure [Conan](https://conan.io/) projects by adding a `.conan` directory to your
@@ -490,6 +510,29 @@ license_scanning:
GOFLAGS: '-insecure'
```
+#### Using private NuGet registries
+
+If you have a private NuGet registry you can add it as a source
+by adding it to the [`packageSources`](https://docs.microsoft.com/en-us/nuget/reference/nuget-config-file#package-source-sections)
+section of a [`nuget.config`](https://docs.microsoft.com/en-us/nuget/reference/nuget-config-file) file.
+
+For example:
+
+```xml
+<?xml version="1.0" encoding="utf-8"?>
+<configuration>
+ <packageSources>
+ <clear />
+ <add key="custom" value="https://nuget.example.com/v3/index.json" />
+ </packageSources>
+</configuration>
+```
+
+#### Custom root certificates for NuGet
+
+You can supply a custom root certificate to complete TLS verification by using the
+`ADDITIONAL_CA_CERT_BUNDLE` [environment variable](#available-variables).
+
### Migration from `license_management` to `license_scanning`
In GitLab 12.8 a new name for `license_management` job was introduced. This change was made to improve clarity around the purpose of the scan, which is to scan and collect the types of licenses present in a projects dependencies.
@@ -594,6 +637,7 @@ your code and generate security reports, without requiring internet access.
Additional configuration may be needed for connecting to
[private Bower registries](#using-private-bower-registries),
+[private Bundler registries](#using-private-bundler-registries),
[private Conan registries](#using-private-bower-registries),
[private Go registries](#using-private-go-registries),
[private Maven repositories](#using-private-maven-repos),
@@ -601,69 +645,9 @@ Additional configuration may be needed for connecting to
[private Python repositories](#using-private-python-repos),
and [private Yarn registries](#using-private-yarn-registries).
-Exact name matches are required for [project policies](#project-policies-for-license-compliance)
+Exact name matches are required for [project policies](#policies)
when running in an offline environment ([see related issue](https://gitlab.com/gitlab-org/gitlab/-/issues/212388)).
-## Project policies for License Compliance
-
-> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/5940) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 11.4.
-
-From the project's settings:
-
-- The list of licenses and their status can be managed.
-- Licenses can be manually allowed or denied.
-
-To allow or deny a license:
-
-1. Either use the **Manage licenses** button in the merge request widget, or
- navigate to the project's **Settings > CI/CD** and expand the
- **License Compliance** section.
-1. Click the **Add a license** button.
-
- ![License Compliance Add License](img/license_compliance_add_license_v13_0.png)
-
-1. In the **License name** dropdown, either:
- - Select one of the available licenses. You can search for licenses in the field
- at the top of the list.
- - Enter arbitrary text in the field at the top of the list. This will cause the text to be
- added as a license name to the list.
-1. Select the **Allow** or **Deny** radio button to allow or deny respectively
- the selected license.
-
-To modify an existing license:
-
-1. In the **License Compliance** list, click the **Allow/Deny** dropdown to change it to the desired status.
-
- ![License Compliance Settings](img/license_compliance_settings_v13_0.png)
-
-Searching for Licenses:
-
-1. Use the **Search** box to search for a specific license.
-
- ![License Compliance Search](img/license_compliance_search_v13_0.png)
-
-## License Compliance report under pipelines
-
-> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/5491) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 11.2.
-
-From your project's left sidebar, navigate to **CI/CD > Pipelines** and click on the
-pipeline ID that has a `license_scanning` job to see the Licenses tab with the listed
-licenses (if any).
-
-![License Compliance Pipeline Tab](img/license_compliance_pipeline_tab_v13_0.png)
-
-<!-- ## Troubleshooting
-
-Include any troubleshooting steps that you can foresee. If you know beforehand what issues
-one might have when setting this up, or when something is changed, or on upgrading, it's
-important to describe those, too. Think of things that may go wrong and include them here.
-This is important to minimize requests for support, and to avoid doc comments with
-questions that you know someone might ask.
-
-Each scenario can be a third-level heading, e.g. `### Getting error message X`.
-If you have none to add when creating a doc, leave this section in place
-but commented out to help encourage others to add to it in the future. -->
-
## License list
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/13582) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 12.7.
@@ -696,13 +680,40 @@ and the associated classifications for each.
Policies can be configured by maintainers of the project.
-![Edit Policy](img/policies_maintainer_edit_v13_0.png)
-![Add Policy](img/policies_maintainer_add_v13_0.png)
+![Edit Policy](img/policies_maintainer_edit_v13_2.png)
+![Add Policy](img/policies_maintainer_add_v13_2.png)
Developers of the project can view the policies configured in a project.
![View Policies](img/policies_v13_0.png)
+### Enabling License Approvals within a project
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/13067) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 12.3.
+
+`License-Check` is an approval rule you can enable to allow an approver, individual, or group to
+approve a merge request that contains a `denied` license.
+
+You can enable `License-Check` one of two ways:
+
+- Create a [project approval rule](../../project/merge_requests/merge_request_approvals.md#multiple-approval-rules-premium)
+ with the case-sensitive name `License-Check`.
+- Create an approval group in the [project policies section for License Compliance](#policies).
+ You must set this approval group's number of approvals required to greater than zero. Once you
+ enable this group in your project, the approval rule is enabled for all merge requests.
+
+Any code changes cause the approvals required to reset.
+
+An approval is required when a license report:
+
+- Contains a dependency that includes a software license that is `denied`.
+- Is not generated during pipeline execution.
+
+An approval is optional when a license report:
+
+- Contains no software license violations.
+- Contains only new licenses that are `allowed` or unknown.
+
## Troubleshooting
### `ERROR -- : asdf: No preset version installed for command`
diff --git a/doc/user/discussions/index.md b/doc/user/discussions/index.md
index 44802214d7b..599f46b2c40 100644
--- a/doc/user/discussions/index.md
+++ b/doc/user/discussions/index.md
@@ -487,14 +487,15 @@ For example, to customize the commit message to output
NOTE: **Note:**
Custom commit messages for each applied Suggestion (and for batch Suggestions) will be
-introduced by [#25381](https://gitlab.com/gitlab-org/gitlab/issues/25381).
+introduced by [#25381](https://gitlab.com/gitlab-org/gitlab/-/issues/25381).
### Batch Suggestions
-> - [Introduced](https://gitlab.com/gitlab-org/gitlab/issues/25486) in GitLab 13.1 as an [alpha feature](https://about.gitlab.com/handbook/product/#alpha).
-> - It's deployed behind a feature flag, disabled by default.
-> - It's disabled on GitLab.com.
-> - To use it in GitLab self-managed instances, ask a GitLab administrator to [enable it](#enable-or-disable-batch-suggestions).
+> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/25486) in GitLab 13.1 as an [alpha feature](https://about.gitlab.com/handbook/product/#alpha).
+> - It was deployed behind a feature flag, disabled by default.
+> - [Became enabled by default](https://gitlab.com/gitlab-org/gitlab/-/issues/227799) on GitLab 13.2.
+> - It's enabled on GitLab.com.
+> - For GitLab self-managed instances, GitLab administrators can opt to [disable it](#enable-or-disable-batch-suggestions-core-only).
You can apply multiple suggestions at once to reduce the number of commits added
to your branch to address your reviewers' requests.
@@ -515,25 +516,25 @@ to your branch to address your reviewers' requests.
![A code change suggestion displayed, with the button to apply the batch of suggestions highlighted.](img/apply_batch_of_suggestions_v13_1.jpg "Apply a batch of suggestions")
-#### Enable or disable Batch Suggestions
+#### Enable or disable Batch Suggestions **(CORE ONLY)**
Batch Suggestions is
-deployed behind a feature flag that is **disabled by default**.
+deployed behind a feature flag that is **enabled by default**.
[GitLab administrators with access to the GitLab Rails console](../../administration/feature_flags.md)
-can enable it for your instance.
+can opt to disable it for your instance.
To enable it:
```ruby
# Instance-wide
-Feature.enable(:batched_suggestions)
+Feature.enable(:batch_suggestions)
```
To disable it:
```ruby
# Instance-wide
-Feature.disable(:batched_suggestions)
+Feature.disable(:batch_suggestions)
```
## Start a thread by replying to a standard comment
diff --git a/doc/user/gitlab_com/index.md b/doc/user/gitlab_com/index.md
index 6522f5c4053..bcaba97cab7 100644
--- a/doc/user/gitlab_com/index.md
+++ b/doc/user/gitlab_com/index.md
@@ -78,31 +78,34 @@ Below are the current settings regarding [GitLab CI/CD](../../ci/README.md).
| Setting | GitLab.com | Default |
| ----------- | ----------------- | ------------- |
| Artifacts maximum size (uncompressed) | 1G | 100M |
-| Artifacts [expiry time](../../ci/yaml/README.md#artifactsexpire_in) | kept forever | deleted after 30 days unless otherwise specified |
+| Artifacts [expiry time](../../ci/yaml/README.md#artifactsexpire_in) | From June 22, 2020, deleted after 30 days unless otherwise specified (artifacts created before that date have no expiry). | deleted after 30 days unless otherwise specified |
| Scheduled Pipeline Cron | `*/5 * * * *` | `19 * * * *` |
| [Max jobs in active pipelines](../../administration/instance_limits.md#number-of-jobs-in-active-pipelines) | `500` for Free tier, unlimited otherwise | Unlimited
| [Max pipeline schedules in projects](../../administration/instance_limits.md#number-of-pipeline-schedules) | `10` for Free tier, `50` for all paid tiers | Unlimited |
| [Max number of instance level variables](../../administration/instance_limits.md#number-of-instance-level-variables) | `25` | `25` |
+| [Scheduled Job Archival](../../user/admin_area/settings/continuous_integration.md#archive-jobs-core-only) | 3 months | Never |
## Repository size limit
-The maximum size your Git repository is allowed to be, including LFS. If you are near
-or over the size limit, you can [reduce your repository size with Git](../project/repository/reducing_the_repo_size_using_git.md).
+GitLab.com has the following [account limits](../admin_area/settings/account_and_limit_settings.md) enabled. If a setting is not listed, it is set to the default value.
-| Setting | GitLab.com | Default |
-| ----------- | ----------------- | ------------- |
-| Repository size including LFS | 10G | Unlimited |
+If you are near
+or over the repository size limit, you can [reduce your repository size with Git](../project/repository/reducing_the_repo_size_using_git.md).
+
+| Setting | GitLab.com | Default |
+| ----------- | ----------- | ------------- |
+| Repository size including LFS | 10 GB | Unlimited |
NOTE: **Note:**
-`git push` and GitLab project imports are limited to 5GB per request. Git LFS and imports other than a file upload are not affected by this limit.
+`git push` and GitLab project imports are limited to 5 GB per request through Cloudflare. Git LFS and imports other than a file upload are not affected by this limit.
## IP range
GitLab.com is using the IP range `34.74.90.64/28` for traffic from its Web/API
fleet. This whole range is solely allocated to GitLab. You can expect connections from webhooks or repository mirroring to come
-from those IPs and whitelist them.
+from those IPs and allow them.
-GitLab.com is fronted by Cloudflare. For incoming connections to GitLab.com you might need to whitelist CIDR blocks of Cloudflare ([IPv4](https://www.cloudflare.com/ips-v4) and [IPv6](https://www.cloudflare.com/ips-v6))
+GitLab.com is fronted by Cloudflare. For incoming connections to GitLab.com you might need to allow CIDR blocks of Cloudflare ([IPv4](https://www.cloudflare.com/ips-v4) and [IPv6](https://www.cloudflare.com/ips-v6)).
For outgoing connections from CI/CD runners we are not providing static IP addresses.
All our runners are deployed into Google Cloud Platform (GCP) - any IP based
@@ -334,9 +337,9 @@ Windows Shared Runners:
```yaml
.shared_windows_runners:
tags:
- - shared-windows
- - windows
- - windows-1809
+ - shared-windows
+ - windows
+ - windows-1809
stages:
- build
@@ -349,17 +352,17 @@ before_script:
build:
extends:
- - .shared_windows_runners
+ - .shared_windows_runners
stage: build
script:
- - echo "running scripts in the build job"
+ - echo "running scripts in the build job"
test:
extends:
- - .shared_windows_runners
+ - .shared_windows_runners
stage: test
script:
- - echo "running scripts in the test job"
+ - echo "running scripts in the test job"
```
#### Limitations and known issues
@@ -581,9 +584,9 @@ is used to forward logs to an [Elastic cluster](https://gitlab.com/gitlab-com/ru
You can view more information in our runbooks such as:
-- A [detailed list of what we're logging](https://gitlab.com/gitlab-com/runbooks/tree/master/logging/doc#what-are-we-logging)
-- Our [current log retention policies](https://gitlab.com/gitlab-com/runbooks/tree/master/logging/doc#retention)
-- A [diagram of our logging infrastructure](https://gitlab.com/gitlab-com/runbooks/tree/master/logging/doc#logging-infrastructure-overview)
+- A [detailed list of what we're logging](https://gitlab.com/gitlab-com/runbooks/-/tree/master/docs/logging#what-are-we-logging)
+- Our [current log retention policies](https://gitlab.com/gitlab-com/runbooks/-/tree/master/docs/logging#retention)
+- A [diagram of our logging infrastructure](https://gitlab.com/gitlab-com/runbooks/-/tree/master/docs/logging#logging-infrastructure-overview)
## GitLab.com at scale
diff --git a/doc/user/group/bulk_editing/img/bulk-editing.png b/doc/user/group/bulk_editing/img/bulk-editing.png
deleted file mode 100644
index ca1662a781b..00000000000
--- a/doc/user/group/bulk_editing/img/bulk-editing.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/group/bulk_editing/img/bulk-editing_v13_2.png b/doc/user/group/bulk_editing/img/bulk-editing_v13_2.png
new file mode 100644
index 00000000000..9f28fabdf0c
--- /dev/null
+++ b/doc/user/group/bulk_editing/img/bulk-editing_v13_2.png
Binary files differ
diff --git a/doc/user/group/bulk_editing/index.md b/doc/user/group/bulk_editing/index.md
index c4ccc1f7b52..35bdc6696eb 100644
--- a/doc/user/group/bulk_editing/index.md
+++ b/doc/user/group/bulk_editing/index.md
@@ -8,12 +8,12 @@ info: To determine the technical writer assigned to the Stage/Group associated w
NOTE: **Note:**
Bulk editing issues and merge requests is also available at the **project level**.
-For more details, see [Bulk editing issues, epics, and merge requests](../../project/bulk_editing.md).
+For more details, see [Bulk editing issues and merge requests at the project level](../../project/bulk_editing.md).
If you want to update attributes across multiple issues, epics, or merge requests in a group, you
can do it by bulk editing them, that is, editing them together.
-![Bulk editing](img/bulk-editing.png)
+![Bulk editing](img/bulk-editing_v13_2.png)
## Bulk edit issues at the group level
@@ -24,8 +24,12 @@ You need a permission level of [Reporter or higher](../../permissions.md) to man
When bulk editing issues in a group, you can edit the following attributes:
+- Epic ([introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/210470) in
+ [GitLab Premium](https://about.gitlab.com/pricing/) 13.2.) **(PREMIUM)**
- Milestone
- Labels
+- Health status ([introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/218395) in
+ [GitLab Ultimate](https://about.gitlab.com/pricing/) 13.2.) **(ULTIMATE)**
To update multiple project issues at the same time:
diff --git a/doc/user/group/clusters/index.md b/doc/user/group/clusters/index.md
index 5cdac7ae892..89e0c4898fb 100644
--- a/doc/user/group/clusters/index.md
+++ b/doc/user/group/clusters/index.md
@@ -38,10 +38,11 @@ the project.
In the case of sub-groups, GitLab uses the cluster of the closest ancestor group
to the project, provided the cluster is not disabled.
-## Multiple Kubernetes clusters **(PREMIUM)**
+## Multiple Kubernetes clusters
-With [GitLab Premium](https://about.gitlab.com/pricing/premium/), you can associate
-more than one Kubernetes cluster to your group, and maintain different clusters
+> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/35094) to GitLab Core in 13.2.
+
+You can associate more than one Kubernetes cluster to your group, and maintain different clusters
for different environments, such as development, staging, and production.
When adding another cluster,
@@ -93,7 +94,7 @@ To clear the cache:
> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/24580) in GitLab 11.8.
Domains at the cluster level permit support for multiple domains
-per [multiple Kubernetes clusters](#multiple-kubernetes-clusters-premium). When specifying a domain,
+per [multiple Kubernetes clusters](#multiple-kubernetes-clusters) When specifying a domain,
this will be automatically set as an environment variable (`KUBE_INGRESS_BASE_DOMAIN`) during
the [Auto DevOps](../../../topics/autodevops/index.md) stages.
@@ -127,8 +128,8 @@ And the following environments are set in [`.gitlab-ci.yml`](../../../ci/yaml/RE
```yaml
stages:
-- test
-- deploy
+ - test
+ - deploy
test:
stage: test
diff --git a/doc/user/group/epics/img/epic_activity_sort_order_v13_2.png b/doc/user/group/epics/img/epic_activity_sort_order_v13_2.png
new file mode 100644
index 00000000000..c7e1448fea9
--- /dev/null
+++ b/doc/user/group/epics/img/epic_activity_sort_order_v13_2.png
Binary files differ
diff --git a/doc/user/group/epics/img/epics_list_view_v12.5.png b/doc/user/group/epics/img/epics_list_view_v12.5.png
deleted file mode 100644
index 6e3c39009be..00000000000
--- a/doc/user/group/epics/img/epics_list_view_v12.5.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/group/epics/img/new_epic_form_v13.2.png b/doc/user/group/epics/img/new_epic_form_v13.2.png
new file mode 100644
index 00000000000..3d24763d105
--- /dev/null
+++ b/doc/user/group/epics/img/new_epic_form_v13.2.png
Binary files differ
diff --git a/doc/user/group/epics/img/new_epic_from_groups_v13.2.png b/doc/user/group/epics/img/new_epic_from_groups_v13.2.png
new file mode 100644
index 00000000000..85bc4255595
--- /dev/null
+++ b/doc/user/group/epics/img/new_epic_from_groups_v13.2.png
Binary files differ
diff --git a/doc/user/group/epics/index.md b/doc/user/group/epics/index.md
index 85164292265..a2b04e2d7fe 100644
--- a/doc/user/group/epics/index.md
+++ b/doc/user/group/epics/index.md
@@ -14,8 +14,15 @@ Epics let you manage your portfolio of projects more efficiently and with less
effort by tracking groups of issues that share a theme, across projects and
milestones.
-<!-- Possibly swap this file with one of a single epic -->
-![epics list view](img/epics_list_view_v12.5.png)
+An epic's page contains the following tabs:
+
+- **Epics and Issues**: epics and issues added to this epic. Child epics, and their issues, are
+ shown in a tree view.
+ - Click the chevron (**>**) next to a parent epic to reveal the child epics and issues.
+ - Hover over the total counts to see a breakdown of open and closed items.
+- **Roadmap**: a roadmap view of child epics which have start and due dates.
+
+![epic view](img/epic_view_v13.0.png)
## Use cases
@@ -28,6 +35,7 @@ milestones.
To learn what you can do with an epic, see [Manage epics](manage_epics.md). Possible actions include:
- [Create an epic](manage_epics.md#create-an-epic)
+- [Edit an epic](manage_epics.md#edit-an-epic)
- [Bulk-edit epics](../bulk_editing/index.md#bulk-edit-epics)
- [Delete an epic](manage_epics.md#delete-an-epic)
- [Close an epic](manage_epics.md#close-an-epic)
@@ -165,6 +173,19 @@ Once you write your comment, you can either:
- Click **Comment**, and your comment will be published.
- Click **Start thread**, and you will start a thread within that epic's discussion.
+### Activity sort order
+
+> [Introduced](https://https://gitlab.com/gitlab-org/gitlab/-/issues/214364) in [GitLab Premium](https://about.gitlab.com/pricing/) 13.2.
+
+You can reverse the default order and interact with the activity feed sorted by most recent items
+at the top. Your preference is saved via local storage and automatically applied to every issue
+you view.
+
+To change the activity sort order, click the **Oldest first** dropdown menu and select either oldest
+or newest items to be shown first.
+
+![Issue activity sort order dropdown button](img/epic_activity_sort_order_v13_2.png)
+
## Award emoji
You can [award an emoji](../../award_emojis.md) to that epic or its comments.
diff --git a/doc/user/group/epics/manage_epics.md b/doc/user/group/epics/manage_epics.md
index 26d5cb51e01..4f9bb0e24fb 100644
--- a/doc/user/group/epics/manage_epics.md
+++ b/doc/user/group/epics/manage_epics.md
@@ -18,12 +18,42 @@ A paginated list of epics is available in each group from where you can create
a new epic. The list of epics includes also epics from all subgroups of the
selected group. From your group page:
-1. Go to **Epics**.
+### Create an epic from the epic list
+
+To create an epic from the epic list, in a group:
+
+1. Go to **{epic}** **Epics**.
1. Click **New epic**.
1. Enter a descriptive title.
1. Click **Create epic**.
-You will be taken to the new epic where can edit the following details:
+### Access the New Epic form
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/211533) in [GitLab Premium](https://about.gitlab.com/pricing/) 13.2.
+
+There are two ways to get to the New Epic form and create an epic in the group you're in:
+
+- From an epic in your group, click **New Epic**.
+- From anywhere, in the top menu, click **plus** (**{plus-square}**) **> New epic**.
+
+ ![New epic from an open epic](img/new_epic_from_groups_v13.2.png)
+
+### Elements of the New Epic form
+
+When you're creating a new epic, these are the fields you can fill in:
+
+- Title
+- Description
+- Confidentiality checkbox
+- Labels
+- Start date
+- Due date
+
+![New epic form](img/new_epic_form_v13.2.png)
+
+## Edit an epic
+
+After you create an epic, you can edit change the following details:
- Title
- Description
@@ -31,15 +61,16 @@ You will be taken to the new epic where can edit the following details:
- Due date
- Labels
-An epic's page contains the following tabs:
+To edit an epic's title or description:
-- **Epics and Issues**: epics and issues added to this epic. Child epics, and their issues, are
- shown in a tree view.
- - Click the <kbd>></kbd> beside a parent epic to reveal the child epics and issues.
- - Hover over the total counts to see a breakdown of open and closed items.
-- **Roadmap**: a roadmap view of child epics which have start and due dates.
+1. Click the **Edit title and description** **{pencil}** button.
+1. Make your changes.
+1. Click **Save changes**.
-![epic view](img/epic_view_v13.0.png)
+To edit an epics' start date, due date, or labels:
+
+1. Click **Edit** next to each section in the epic sidebar.
+1. Select the dates or labels for your epic.
## Bulk-edit epics
@@ -118,27 +149,17 @@ The sort option and order is saved and used wherever you browse epics, including
## Make an epic confidential
-> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/213068) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 13.0.
-> - It's deployed behind a feature flag, disabled by default.
-> - It's disabled on GitLab.com.
-> - It's not recommended for production use.
-> - To use it in GitLab self-managed instances, ask a GitLab administrator to [enable it](#enable-confidential-epics-premium-only). **(PREMIUM ONLY)**
+> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/213068) in [GitLab Premium](https://about.gitlab.com/pricing/) 13.0 behind a feature flag, disabled by default.
+> - [Became enabled by default](https://gitlab.com/gitlab-org/gitlab/-/issues/224513) in [GitLab Premium](https://about.gitlab.com/pricing/) 13.2.
When you're creating an epic, you can make it confidential by selecting the **Make this epic
confidential** checkbox.
-### Enable Confidential Epics **(PREMIUM ONLY)**
+### Disable confidential epics **(PREMIUM ONLY)**
-The Confidential Epics feature is under development and not ready for production use.
-It's deployed behind a feature flag that is **disabled by default**.
+The confidential epics feature is deployed behind a feature flag that is **enabled by default**.
[GitLab administrators with access to the GitLab Rails console](../../../administration/feature_flags.md)
-can enable it for your instance.
-
-To enable it:
-
-```ruby
-Feature.enable(:confidential_epics)
-```
+can disable it for your self-managed instance.
To disable it:
@@ -233,7 +254,7 @@ To move an issue to another epic:
> - [Moved](https://gitlab.com/gitlab-org/gitlab/-/issues/37081) to [GitLab Premium](https://about.gitlab.com/pricing/) in 12.8.
If you have the necessary [permissions](../../permissions.md) to close an issue and create an
-epic in the parent group, you can promote an issue to an epic with the `/promote`
+epic in the immediate parent group, you can promote an issue to an epic with the `/promote`
[quick action](../../project/quick_actions.md#quick-actions-for-issues-merge-requests-and-epics).
Only issues from projects that are in groups can be promoted. When attempting to promote a confidential
issue, a warning will display. Promoting a confidential issue to an epic will make all information
diff --git a/doc/user/group/index.md b/doc/user/group/index.md
index 324c912b2be..5ba0680127c 100644
--- a/doc/user/group/index.md
+++ b/doc/user/group/index.md
@@ -31,7 +31,7 @@ Each group on the **Groups** page is listed with:
- How many subgroups it has.
- How many projects it contains.
-- How many members the group has, not including members inherited from parent groups.
+- How many members the group has, not including members inherited from parent group(s).
- The group's visibility.
- A link to the group's settings, if you have sufficient permissions.
- A link to leave the group, if you are a member.
@@ -184,6 +184,33 @@ of a group:
1. Give a different member **Owner** permissions.
1. Have the new owner sign in and remove **Owner** permissions from you.
+## Remove a member from the group
+
+Only users with permissions of [Owner](../permissions.md#group-members-permissions) can manage
+group members.
+
+You can remove a member from the group if the given member has a direct membership in the group. If
+membership is inherited from a parent group, then the member can be removed only from the parent
+group itself.
+
+When removing a member, you can decide whether to unassign the user from all issues and merge
+requests they are currently assigned or leave the assignments as they are.
+
+- **Unassigning the removed member** from all issues and merge requests might be helpful when a user
+ is leaving a private group and you wish to revoke their access to any issues and merge requests
+ they are assigned.
+- **Keeping the issues and merge requests assigned** might be helpful for groups that accept public
+ contributions where a user doesn't have to be a member to be able to contribute to issues and
+ merge requests.
+
+To remove a member from a group:
+
+1. In a group, go to **{users}** **Members**.
+1. Click the **Delete** **{remove}** button next to a group member you want to remove.
+ A **Remove member** modal appears.
+1. (Optional) Select the **Also unassign this user from related issues and merge requests** checkbox.
+1. Click **Remove member**.
+
## Changing the default branch protection of a group
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/7583) in GitLab 12.9.
@@ -397,7 +424,7 @@ When transferring groups, note:
- Changing a group's parent can have unintended side effects. See [Redirects when changing repository paths](../project/index.md#redirects-when-changing-repository-paths).
- You can only transfer groups to groups you manage.
- You must update your local repositories to point to the new location.
-- If the parent group's visibility is lower than the group's current visibility, visibility levels for subgroups and projects will change to match the new parent group's visibility.
+- If the immediate parent group's visibility is lower than the group's current visibility, visibility levels for subgroups and projects will change to match the new parent group's visibility.
- Only explicit group membership is transferred, not inherited membership. If the group's owners have only inherited membership, this leaves the group without an owner. In this case, the user transferring the group becomes the group's owner.
## Group settings
@@ -435,7 +462,7 @@ It is currently not possible to rename a namespace if it contains a
project with [Container Registry](../packages/container_registry/index.md) tags,
because the project cannot be moved.
-TIP: **TIP:**
+TIP: **Tip:**
If you want to retain ownership over the original namespace and
protect the URL redirects, then instead of changing a group's path or renaming a
username, you can create a new group and transfer projects to it.
@@ -516,7 +543,7 @@ underlying projects, issues, etc, by IP address. This can help ensure that
particular content doesn't leave the premises, while not blocking off access to
the entire instance.
-Add one or more allowed IP subnets using CIDR notation in comma separated format to the group settings and anyone
+Add one or more allowed IP subnets using CIDR notation to the group settings and anyone
coming from a different IP address won't be able to access the restricted
content.
@@ -529,6 +556,12 @@ Restriction currently applies to:
To avoid accidental lock-out, admins and group owners are able to access
the group regardless of the IP restriction.
+To enable this feature:
+
+1. Navigate to the group’s **Settings > General** page.
+1. Expand the **Permissions, LFS, 2FA** section, and enter IP address ranges into **Allow access to the following IP addresses** field.
+1. Click **Save changes**.
+
#### Allowed domain restriction **(PREMIUM)**
>- [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/7297) in [GitLab Premium and Silver](https://about.gitlab.com/pricing/) 12.2.
@@ -554,7 +587,7 @@ Some domains cannot be restricted. These are the most popular public email domai
To enable this feature:
1. Navigate to the group's **Settings > General** page.
-1. Expand the **Permissions, LFS, 2FA** section, and enter the domain names into **Restrict membership by email** field. You can enter multiple domains by separating each domain with a comma (,).
+1. Expand the **Permissions, LFS, 2FA** section, and enter the domain names into **Restrict membership by email** field.
1. Click **Save changes**.
This will enable the domain-checking for all new users added to the group from this moment on.
@@ -571,9 +604,9 @@ You can only choose projects in the group as the template source.
This includes projects shared with the group, but it **excludes** projects in
subgroups or parent groups of the group being configured.
-You can configure this feature for both subgroups and parent groups. A project
+You can configure this feature for both subgroups and immediate parent groups. A project
in a subgroup will have access to the templates for that subgroup, as well as
-any parent groups.
+any immediate parent groups.
![Group file template dropdown](img/group_file_template_dropdown.png)
@@ -617,6 +650,23 @@ To enable this feature:
1. Expand the **Permissions, LFS, 2FA** section, and select **Disable group mentions**.
1. Click **Save changes**.
+#### Enabling delayed Project removal **(PREMIUM)**
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/220382) in GitLab 13.2.
+
+By default, projects within a group are deleted immediately.
+Optionally, on [Premium or Silver](https://about.gitlab.com/pricing/) or higher tiers,
+you can configure the projects within a group to be deleted after a delayed interval.
+
+During this interval period, the projects will be in a read-only state and can be restored, if required.
+The interval period defaults to 7 days, and can be modified by an admin in the [instance settings](../admin_area/settings/visibility_and_access_controls.md#default-deletion-adjourned-period-premium-only).
+
+To enable delayed deletion of projects:
+
+1. Navigate to the group's **Settings > General** page.
+1. Expand the **Permissions, LFS, 2FA** section, and check **Enable delayed project removal**.
+1. Click **Save changes**.
+
### Advanced settings
- **Projects**: View all projects within that group, add members to each project,
diff --git a/doc/user/group/iterations/index.md b/doc/user/group/iterations/index.md
index 2704147dcdd..bc9d228011a 100644
--- a/doc/user/group/iterations/index.md
+++ b/doc/user/group/iterations/index.md
@@ -8,11 +8,12 @@ info: To determine the technical writer assigned to the Stage/Group associated w
# Iterations **(STARTER)**
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/214713) in [GitLab Starter](https://about.gitlab.com/pricing/) 13.1.
-> - It's deployed behind a feature flag, disabled by default.
-> - It's disabled on GitLab.com.
-> - It's able to be enabled or disabled per-group
-> - It's not recommended for production use.
-> - To use it in GitLab self-managed instances, ask a GitLab administrator to [enable it](#enable-iterations-core-only). **(CORE ONLY)**
+> - It was deployed behind a feature flag, disabled by default.
+> - [Became enabled by default](https://gitlab.com/gitlab-org/gitlab/-/issues/221047) on GitLab 13.2.
+> - It's enabled on GitLab.com.
+> - It's able to be enabled or disabled per-group.
+> - It's recommended for production use.
+> - For GitLab self-managed instances, GitLab administrators can opt to [disable it](#disable-iterations-core-only). **(CORE ONLY)**
Iterations are a way to track issues over a period of time. This allows teams
to track velocity and volatility metrics. Iterations can be used with [milestones](../../project/milestones/index.md)
@@ -38,7 +39,7 @@ From there you can create a new iteration or click an iteration to get a more de
## Create an iteration
NOTE: **Note:**
-A permission level of [Developer or higher](../../permissions.md) is required to create iterations.
+You need Developer [permissions](../../permissions.md) or higher to create an iteration.
To create an iteration:
@@ -47,12 +48,20 @@ To create an iteration:
1. Enter the title, a description (optional), a start date, and a due date.
1. Click **Create iteration**. The iteration details page opens.
-### Enable Iterations **(CORE ONLY)**
+## Edit an iteration
-GitLab Iterations feature is under development and not ready for production use.
-It is deployed behind a feature flag that is **disabled by default**.
+> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/218277) in [GitLab Starter](https://about.gitlab.com/pricing/) 13.2.
+
+NOTE: **Note:**
+You need Developer [permissions](../../permissions.md) or higher to edit an iteration.
+
+To edit an iteration, click the three-dot menu (**{ellipsis_v}**) > **Edit iteration**.
+
+## Disable Iterations **(CORE ONLY)**
+
+GitLab Iterations feature is deployed with a feature flag that is **enabled by default**.
[GitLab administrators with access to the GitLab Rails console](../../../administration/feature_flags.md)
-can enable it for your instance. `:group_iterations` can be enabled or disabled per-group.
+can disable it for your instance. `:group_iterations` can be enabled or disabled per-group.
To enable it:
diff --git a/doc/user/group/roadmap/img/roadmap_view_v13_0.png b/doc/user/group/roadmap/img/roadmap_view_v13_0.png
deleted file mode 100644
index a5b76b84418..00000000000
--- a/doc/user/group/roadmap/img/roadmap_view_v13_0.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/group/roadmap/img/roadmap_view_v13_2.png b/doc/user/group/roadmap/img/roadmap_view_v13_2.png
new file mode 100644
index 00000000000..b4f889afaa4
--- /dev/null
+++ b/doc/user/group/roadmap/img/roadmap_view_v13_2.png
Binary files differ
diff --git a/doc/user/group/roadmap/index.md b/doc/user/group/roadmap/index.md
index 614ed700cfc..c185055f6b2 100644
--- a/doc/user/group/roadmap/index.md
+++ b/doc/user/group/roadmap/index.md
@@ -12,22 +12,24 @@ info: To determine the technical writer assigned to the Stage/Group associated w
> - In [GitLab 12.9](https://gitlab.com/gitlab-org/gitlab/-/issues/5164) and later, the epic bars show epics' title, progress, and completed weight percentage.
> - Milestones appear in roadmaps in [GitLab 12.10](https://gitlab.com/gitlab-org/gitlab/-/issues/6802), and later.
> - Feature flag for milestones visible in roadmaps removed in [GitLab 13.0](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/29641).
+> - In [GitLab 13.2](https://gitlab.com/gitlab-org/gitlab/-/issues/214375) and later, the Roadmap also shows milestones in projects in a group.
+> - In [GitLab 13.2](https://gitlab.com/gitlab-org/gitlab/-/issues/212494) and later, milestone bars can be collapsed and expanded.
-Epics and milestones within a group containing **Start date** and/or **Due date**
-can be visualized in a form of a timeline (that is, a Gantt chart). The Roadmap page
-shows such a visualization for all the epics and milestones which are under a group or one of its
-subgroups.
+Epics and milestones within a group containing a start date or due date can be visualized in a form
+of a timeline (that is, a Gantt chart). The Roadmap page shows the epics and milestones in a
+group, one of its subgroups, or a project in one of the groups.
On the epic bars, you can see the each epic's title, progress, and completed weight percentage.
When you hover over an epic bar, a popover appears with the epic's title, start date, due date, and
weight completed.
You can expand epics that contain child epics to show their child epics in the roadmap.
-You can click the chevron **{chevron-down}** next to the epic title to expand and collapse the child epics.
+You can click the chevron (**{chevron-down}**) next to the epic title to expand and collapse the child epics.
On top of the milestone bars, you can see their title. When you hover a milestone bar or title, a popover appears with its title, start date and due date.
+You can also click the chevron (**{chevron-down}**) next to the **Milestones** heading to toggle the list of the milestone bars.
-![roadmap view](img/roadmap_view_v13_0.png)
+![roadmap view](img/roadmap_view_v13_2.png)
A dropdown menu allows you to show only open or closed epics. By default, all epics are shown.
diff --git a/doc/user/group/saml_sso/group_managed_accounts.md b/doc/user/group/saml_sso/group_managed_accounts.md
new file mode 100644
index 00000000000..e317573d89d
--- /dev/null
+++ b/doc/user/group/saml_sso/group_managed_accounts.md
@@ -0,0 +1,116 @@
+---
+type: reference, howto
+stage: Manage
+group: Access
+info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers
+---
+
+# Group Managed Accounts **(PREMIUM)**
+
+CAUTION: **Warning:**
+This is a [Closed Beta](https://about.gitlab.com/handbook/product/#closed-beta) feature.
+
+> - [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/709) in GitLab 12.1.
+> - It's deployed behind a feature flag, disabled by default.
+
+When [SSO for Groups](index.md) is being enforced, groups can enable an additional level of protection by enforcing the creation of dedicated user accounts to access the group.
+
+With group-managed accounts enabled, users are required to create a new, dedicated user linked to the group.
+The notification email address associated with the user is locked to the email address received from the configured identity provider.
+Without group-managed accounts, users can link their SAML identity with any existing user on the instance.
+
+When this option is enabled:
+
+- All users in the group will be required to log in via the SSO URL associated with the group.
+- After the group-managed account has been created, group activity will require the use of this user account.
+- Users can't share a project in the group outside the top-level group (also applies to forked projects).
+
+Upon successful authentication, GitLab prompts the user with options, based on the email address received from the configured identity provider:
+
+- To create a unique account with the newly received email address.
+- If the received email address matches one of the user's verified GitLab email addresses, the option to convert the existing account to a group-managed account. ([Introduced in GitLab 12.9](https://gitlab.com/gitlab-org/gitlab/-/issues/13481).)
+
+Since use of the group-managed account requires the use of SSO, users of group-managed accounts will lose access to these accounts when they are no longer able to authenticate with the connected identity provider. In the case of an offboarded employee who has been removed from your identity provider:
+
+- The user will be unable to access the group (their credentials will no longer work on the identity provider when prompted to SSO).
+- Contributions in the group (e.g. issues, merge requests) will remain intact.
+
+## Assertions
+
+When using group-managed accounts, the following user details need to be passed to GitLab as SAML
+assertions to be able to create a user.
+
+| Field | Supported keys |
+|-----------------|----------------|
+| Email (required)| `email`, `mail` |
+| Full Name | `name` |
+| First Name | `first_name`, `firstname`, `firstName` |
+| Last Name | `last_name`, `lastname`, `lastName` |
+
+## Feature flag **(PREMIUM ONLY)**
+
+Currently the group-managed accounts feature is behind a feature flag: `group_managed_accounts`. The flag is disabled by default.
+To activate the feature, ask a GitLab administrator with Rails console access to run:
+
+```ruby
+Feature.enable(:group_managed_accounts)
+```
+
+## Project restrictions for Group-managed accounts
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/12420) in GitLab 12.9.
+
+Projects within groups with enabled group-managed accounts are not to be shared with:
+
+- Groups outside of the parent group.
+- Members who are not users managed by this group.
+
+This restriction also applies to projects forked from or to those groups.
+
+## Outer forks restriction for Group-managed accounts
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/34648) in GitLab 12.9.
+
+Groups with group-managed accounts can disallow forking of projects to destinations outside the group.
+To do so, enable the "Prohibit outer forks" option in **Settings > SAML SSO**.
+When enabled, projects within the group can only be forked to other destinations within the group (including its subgroups).
+
+## Credentials inventory for Group-managed accounts **(ULTIMATE)**
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/38133) in GitLab 12.8.
+
+Owners who manage user accounts in a group can view the following details of personal access tokens and SSH keys:
+
+- Owners
+- Scopes
+- Usage patterns
+
+To access the Credentials inventory of a group, navigate to **{shield}** **Security & Compliance > Credentials** in your group's sidebar.
+
+This feature is similar to the [Credentials inventory for self-managed instances](../../admin_area/credentials_inventory.md).
+
+## Limiting lifetime of personal access tokens of users in Group-managed accounts **(ULTIMATE)**
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/118893) in GitLab 12.10.
+
+Users in a group managed account can optionally specify an expiration date for
+[personal access tokens](../../profile/personal_access_tokens.md).
+This expiration date is not a requirement, and can be set to any arbitrary date.
+
+Since personal access tokens are the only token needed for programmatic access to GitLab, organizations with security requirements may want to enforce more protection to require regular rotation of these tokens.
+
+### Setting a limit
+
+Only a GitLab administrator or an owner of a Group-managed account can set a limit. Leaving it empty means that the [instance level restrictions](../../admin_area/settings/account_and_limit_settings.md#limiting-lifetime-of-personal-access-tokens-ultimate-only) on the lifetime of personal access tokens will apply.
+
+To set a limit on how long personal access tokens are valid for users in a group managed account:
+
+1. Navigate to the **{settings}** **Settings > General** page in your group's sidebar.
+1. Expand the **Permissions, LFS, 2FA** section.
+1. Fill in the **Maximum allowable lifetime for personal access tokens (days)** field.
+1. Click **Save changes**.
+
+Once a lifetime for personal access tokens is set, GitLab will:
+
+- Apply the lifetime for new personal access tokens, and require users managed by the group to set an expiration date that is no later than the allowed lifetime.
+- After three hours, revoke old tokens with no expiration date or with a lifetime longer than the allowed lifetime. Three hours is given to allow administrators/group owner to change the allowed lifetime, or remove it, before revocation takes place.
diff --git a/doc/user/group/saml_sso/index.md b/doc/user/group/saml_sso/index.md
index 81684038dc2..afd676cf897 100644
--- a/doc/user/group/saml_sso/index.md
+++ b/doc/user/group/saml_sso/index.md
@@ -5,32 +5,25 @@ group: Access
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers
---
-# SAML SSO for GitLab.com groups **(SILVER ONLY)**
+# SAML SSO for GitLab.com groups **(PREMIUM)**
-> Introduced in [GitLab.com Silver](https://about.gitlab.com/pricing/) 11.0.
+> Introduced in GitLab 11.0.
-SAML on GitLab.com allows users to be added to a group. Those users can then sign in to GitLab.com. If such users don't already have an account on the GitLab instance, they can create one when signing in for the first time.
+This page describes SAML for Groups. For instance-wide SAML on self-managed GitLab instances, see [SAML OmniAuth Provider](../../../integration/saml.md).
-If you follow our guidance to automate user provisioning using [SCIM](scim_setup.md) or [group-managed accounts](#group-managed-accounts), you do not need to create such accounts manually.
+SAML on GitLab.com allows users to sign in through their SAML identity provider. If the user is not already a member, the sign-in process automatically adds the user to the appropriate group.
-User synchronization for GitLab.com is partially supported using [SCIM](scim_setup.md).
+If you follow our guidance to automate user provisioning using [SCIM](scim_setup.md) or [group-managed accounts](group_managed_accounts.md), you do not need to create such accounts manually.
-## Important notes
-
-Note the following:
-
-- This topic is for SAML on GitLab.com Silver tier and above. For SAML on self-managed GitLab
- instances, see [SAML OmniAuth Provider](../../../integration/saml.md).
-- SAML SSO for GitLab.com groups requires SCIM to sync users between providers. If a
- group is not using SCIM, group Owners will still need to manage user accounts (for example,
- removing users when necessary).
+User synchronization of SAML SSO groups is supported through [SCIM](scim_setup.md). SCIM supports adding and removing users from the GitLab group.
+For example, if you remove a user from the SCIM app, SCIM removes that same user from the GitLab group.
## Configuring your Identity Provider
1. Navigate to the group and click **Settings > SAML SSO**.
-1. Configure your SAML server using the **Assertion consumer service URL** and **Identifier**. Alternatively GitLab provides [metadata XML configuration](#metadata-configuration). See [your identity provider's documentation](#providers) for more details.
+1. Configure your SAML server using the **Assertion consumer service URL**, **Identifier**, and **GitLab single sign-on URL**. Alternatively GitLab provides [metadata XML configuration](#metadata-configuration). See [specific identity provider documentation](#providers) for more details.
1. Configure the SAML response to include a NameID that uniquely identifies each user.
-1. Configure required assertions using the [table below](#assertions).
+1. Configure [required assertions](group_managed_accounts.md#assertions) if using [Group Managed Accounts](group_managed_accounts.md).
1. Once the identity provider is set up, move on to [configuring GitLab](#configuring-gitlab).
![Issuer and callback for configuring SAML identity provider with GitLab.com](img/group_saml_configuration_information.png)
@@ -55,123 +48,6 @@ Once users have signed into GitLab using the SSO SAML setup, changing the `NameI
We recommend setting the NameID format to `Persistent` unless using a field (such as email) that requires a different format.
-### SSO enforcement
-
-- [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/5291) in GitLab 11.8.
-- [Improved](https://gitlab.com/gitlab-org/gitlab/-/issues/9255) in GitLab 11.11 with ongoing enforcement in the GitLab UI.
-
-With this option enabled, users must use your group's GitLab single sign on URL to be added to the group or be added via SCIM. Users cannot be added manually, and may only access project/group resources via the UI by signing in through the SSO URL.
-
-However, users will not be prompted to log via SSO on each visit. GitLab will check whether a user has authenticated through the SSO link, and will only prompt the user to login via SSO if the session has expired.
-
-We intend to add a similar SSO requirement for [Git and API activity](https://gitlab.com/gitlab-org/gitlab/-/issues/9152) in the future.
-
-When SSO enforcement is enabled for a group, users cannot share a project in the group outside the top-level group, even if the project is forked.
-
-#### Group-managed accounts
-
-> [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/709) in GitLab 12.1.
-
-When SSO is being enforced, groups can enable an additional level of protection by enforcing the creation of dedicated user accounts to access the group.
-
-Without group-managed accounts, users can link their SAML identity with any existing user on the instance. With group-managed accounts enabled, users are required to create a new, dedicated user linked to the group. The notification email address associated with the user is locked to the email address received from the configured identity provider.
-
-When this option is enabled:
-
-- All existing and new users in the group will be required to log in via the SSO URL associated with the group.
-- After the group-managed account has been created, group activity will require the use of this user account.
-- Users can't share a project in the group outside the top-level group (also applies to forked projects).
-
-Upon successful authentication, GitLab prompts the user with options, based on the email address received from the configured identity provider:
-
-- To create a unique account with the newly received email address.
-- If the received email address matches one of the user's verified GitLab email addresses, the option to convert the existing account to a group-managed account. ([Introduced in GitLab 12.9](https://gitlab.com/gitlab-org/gitlab/-/issues/13481).)
-
-Since use of the group-managed account requires the use of SSO, users of group-managed accounts will lose access to these accounts when they are no longer able to authenticate with the connected identity provider. In the case of an offboarded employee who has been removed from your identity provider:
-
-- The user will be unable to access the group (their credentials will no longer work on the identity provider when prompted to SSO).
-- Contributions in the group (e.g. issues, merge requests) will remain intact.
-
-##### Feature flag
-
-Currently the group-managed accounts feature is behind a feature flag: `group_managed_accounts`. The flag is disabled by default.
-To activate the feature, ask a GitLab administrator with Rails console access to run:
-
-```ruby
-Feature.enable(:group_managed_accounts)
-```
-
-##### Credentials inventory for Group-managed accounts **(ULTIMATE)**
-
-> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/38133) in GitLab 12.8.
-
-Owners who manage user accounts in a group can view the following details of personal access tokens and SSH keys:
-
-- Owners
-- Scopes
-- Usage patterns
-
-To access the Credentials inventory of a group, navigate to **{shield}** **Security & Compliance > Credentials** in your group's sidebar.
-
-This feature is similar to the [Credentials inventory for self-managed instances](../../admin_area/credentials_inventory.md).
-
-##### Limiting lifetime of personal access tokens of users in Group-managed accounts **(ULTIMATE)**
-
-> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/118893) in GitLab 12.10.
-
-Users in a group managed account can optionally specify an expiration date for
-[personal access tokens](../../profile/personal_access_tokens.md).
-This expiration date is not a requirement, and can be set to any arbitrary date.
-
-Since personal access tokens are the only token needed for programmatic access to GitLab, organizations with security requirements may want to enforce more protection to require regular rotation of these tokens.
-
-###### Setting a limit
-
-Only a GitLab administrator or an owner of a Group-managed account can set a limit. Leaving it empty means that the [instance level restrictions](../../admin_area/settings/account_and_limit_settings.md#limiting-lifetime-of-personal-access-tokens-ultimate-only) on the lifetime of personal access tokens will apply.
-
-To set a limit on how long personal access tokens are valid for users in a group managed account:
-
-1. Navigate to the **{settings}** **Settings > General** page in your group's sidebar.
-1. Expand the **Permissions, LFS, 2FA** section.
-1. Fill in the **Maximum allowable lifetime for personal access tokens (days)** field.
-1. Click **Save changes**.
-
-Once a lifetime for personal access tokens is set, GitLab will:
-
-- Apply the lifetime for new personal access tokens, and require users managed by the group to set an expiration date that is no later than the allowed lifetime.
-- After three hours, revoke old tokens with no expiration date or with a lifetime longer than the allowed lifetime. Three hours is given to allow administrators/group owner to change the allowed lifetime, or remove it, before revocation takes place.
-
-##### Outer forks restriction for Group-managed accounts
-
-> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/34648) in GitLab 12.9.
-
-Groups with group-managed accounts can disallow forking of projects to destinations outside the group.
-To do so, enable the "Prohibit outer forks" option in **Settings > SAML SSO**.
-When enabled, projects within the group can only be forked to other destinations within the group (including its subgroups).
-
-##### Other restrictions for Group-managed accounts
-
-> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/12420) in GitLab 12.9.
-
-Projects within groups with enabled group-managed accounts are not to be shared with:
-
-- Groups outside of the parent group.
-- Members who are not users managed by this group.
-
-This restriction also applies to projects forked from or to those groups.
-
-#### Assertions
-
-When using group-managed accounts, the following user details need to be passed to GitLab as SAML
-assertions to be able to create a user.
-
-| Field | Supported keys |
-|-----------------|----------------|
-| Email (required)| `email`, `mail` |
-| Full Name | `name` |
-| First Name | `first_name`, `firstname`, `firstName` |
-| Last Name | `last_name`, `lastname`, `lastName` |
-
### Metadata configuration
GitLab provides metadata XML that can be used to configure your Identity Provider.
@@ -185,7 +61,7 @@ GitLab provides metadata XML that can be used to configure your Identity Provide
Once you've set up your identity provider to work with GitLab, you'll need to configure GitLab to use it for authentication:
1. Navigate to the group's **Settings > SAML SSO**.
-1. Find the SSO URL from your Identity Provider and enter it the **Identity provider single sign on URL** field.
+1. Find the SSO URL from your Identity Provider and enter it the **Identity provider single sign-on URL** field.
1. Find and enter the fingerprint for the SAML token signing certificate in the **Certificate** field.
1. Click the **Enable SAML authentication for this group** toggle switch.
1. Click the **Save changes** button.
@@ -193,42 +69,27 @@ Once you've set up your identity provider to work with GitLab, you'll need to co
![Group SAML Settings for GitLab.com](img/group_saml_settings.png)
NOTE: **Note:**
-Please note that the certificate [fingerprint algorithm](#additional-setup-options) must be in SHA1. When configuring the identity provider, use a secure [signature algorithm](#additional-setup-options).
-
-## User access and management
-
-Once Group SSO is configured and enabled, users can access the GitLab.com group through the identity provider's dashboard. If [SCIM](scim_setup.md) is configured, please see the [user access and linking setup section on the SCIM page](scim_setup.md#user-access-and-linking-setup).
-
-When a user tries to sign in with Group SSO, they'll need an account that's configured with one of the following:
+Please note that the certificate [fingerprint algorithm](#additional-providers-and-setup-options) must be in SHA1. When configuring the identity provider, use a secure signature algorithm.
-- [SCIM](scim_setup.md).
-- [Group-managed accounts](#group-managed-accounts).
-- A GitLab.com account.
-
-1. Click on the GitLab app in the identity provider's dashboard, or visit the Group's GitLab SSO URL.
-1. Sign in to GitLab.com. The next time you connect on the same browser, you won't have to sign in again provided the active session has not expired.
-1. Click on the **Authorize** button.
-
-On subsequent visits, users can access the group through the identify provider's dashboard or by visiting links directly. With the **enforce SSO** option turned on, users will be redirected to log in through the identity provider as required.
-
-### Role
+### SSO enforcement
-Upon first sign in, a new user is added to the parent group with the Guest role. Existing members with an appropriate role will have to elevate users to a higher role where relevant.
+- [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/5291) in GitLab 11.8.
+- [Improved](https://gitlab.com/gitlab-org/gitlab/-/issues/9255) in GitLab 11.11 with ongoing enforcement in the GitLab UI.
-If a user is already a member of the group, linking the SAML identity does not change their role.
+With this option enabled, users must go through your group's GitLab single sign-on URL. They may also be added via SCIM, if configured. Users cannot be added manually, and may only access project/group resources via the UI by signing in through the SSO URL.
-### Blocking access
+However, users will not be prompted to sign in through SSO on each visit. GitLab will check whether a user has authenticated through SSO, and will only prompt the user to sign in via SSO if the session has expired.
-To rescind access to the group:
+We intend to add a similar SSO requirement for [Git and API activity](https://gitlab.com/gitlab-org/gitlab/-/issues/9152).
-1. Remove the user from the identity provider or users list for the specific app.
-1. Remove the user from the GitLab.com group.
+When SSO enforcement is enabled for a group, users cannot share a project in the group outside the top-level group, even if the project is forked.
-Even when **enforce SSO** is active, we recommend removing the user from the group. Otherwise, the user can sign in through the identity provider if they do not have an active session.
+To disallow users to contribute outside of the top-level group, please see [Group Managed Accounts](group_managed_accounts.md).
## Providers
-NOTE: **Note:** GitLab is unable to provide support for IdPs that are not listed here.
+NOTE: **Note:**
+GitLab is unable to provide support for IdPs that are not listed here.
| Provider | Documentation |
|----------|---------------|
@@ -248,7 +109,8 @@ For a demo of the Azure SAML setup including SCIM, see [SCIM Provisioning on Azu
|--------------|----------------|
| Identifier | Identifier (Entity ID) |
| Assertion consumer service URL | Reply URL (Assertion Consumer Service URL) |
-| Identity provider single sign on URL | Sign on URL |
+| GitLab single sign-on URL | Sign on URL |
+| Identity provider single sign-on URL | Login URL |
| Certificate fingerprint | Thumbprint |
We recommend:
@@ -256,8 +118,6 @@ We recommend:
- **Unique User Identifier (Name identifier)** set to `user.objectID`.
- **nameid-format** set to persistent.
-Set other user attributes and claims according to the [assertions table](#assertions).
-
### Okta setup notes
<i class="fa fa-youtube-play youtube" aria-hidden="true"></i>
@@ -266,19 +126,17 @@ For a demo of the Okta SAML setup including SCIM, see [Demo: Okta Group SAML & S
| GitLab Setting | Okta Field |
|--------------|----------------|
| Identifier | Audience URI |
-| Assertion consumer service URL | Single sign on URL |
-
-Under Okta's **Single sign on URL** field, check the option **Use this for Recipient URL and Destination URL**.
+| Assertion consumer service URL | Single sign-on URL |
+| GitLab single sign-on URL | Login page URL (under **Application Login Page** settings) |
+| Identity provider single sign-on URL | Identity Provider Single Sign-On URL |
-Please note that Okta's generic SAML app does not have a **Login URL** field, where the **Identity provider single sign on URL** would normally go. The **Identity provider single sign on URL** may be required the first time a user is logging in if they are having any difficulties.
+Under Okta's **Single sign-on URL** field, check the option **Use this for Recipient URL and Destination URL**.
We recommend:
- **Application username** (NameID) set to **Custom** `user.getInternalProperty("id")`.
- **Name ID Format** set to **Persistent**.
-Set attribute statements according to the [assertions table](#assertions).
-
### OneLogin setup notes
The GitLab app listed in the OneLogin app catalog is for self-managed GitLab instances.
@@ -290,15 +148,24 @@ For GitLab.com, use a generic SAML Test Connector such as the SAML Test Connecto
| Assertion consumer service URL | Recipient |
| Assertion consumer service URL | ACS (Consumer) URL |
| Assertion consumer service URL (escaped version) | ACS (Consumer) URL Validator |
-| GitLab single sign on URL | Login URL |
+| GitLab single sign-on URL | Login URL |
+| Identity provider single sign-on URL | SAML 2.0 Endpoint |
Recommended `NameID` value: `OneLogin ID`.
-Set parameters according to the [assertions table](#assertions).
+### Additional providers and setup options
+
+The SAML standard means that a wide range of identity providers will work with GitLab. Unfortunately we have not verified connections with all SAML providers.
+For more information, see our [discussion on providers](#providers).
-### Additional setup options
+Your identity provider may have relevant documentation. It may be generic SAML documentation, or specifically targeted for GitLab. Examples:
+
+- [Auth0](https://auth0.com/docs/protocols/saml/saml-idp-generic)
+- [G Suite](https://support.google.com/a/answer/6087519?hl=en)
+- [JumpCloud](https://support.jumpcloud.com/support/s/article/single-sign-on-sso-with-gitlab-2019-08-21-10-36-47)
+- [PingOne by Ping Identity](https://docs.pingidentity.com/bundle/pingone/page/xsh1564020480660-1.html)
-GitLab [isn't limited to the SAML providers listed above](#my-identity-provider-isnt-listed) but your Identity Provider may require additional configuration, such as the following:
+Your Identity Provider may require additional configuration, such as the following:
| Field | Value | Notes |
|-------|-------|-------|
@@ -319,24 +186,48 @@ GitLab [isn't limited to the SAML providers listed above](#my-identity-provider-
If the information you need isn't listed above you may wish to check our [troubleshooting docs below](#i-need-additional-information-to-configure-my-identity-provider).
-## Linking SAML to your existing GitLab.com account
+## User access and management
+
+Once Group SSO is configured and enabled, users can access the GitLab.com group through the identity provider's dashboard. If [SCIM](scim_setup.md) is configured, please see the [user access and linking setup section on the SCIM page](scim_setup.md#user-access-and-linking-setup).
+
+When a user tries to sign in with Group SSO, they will need an account that's configured with one of the following:
+
+- [SCIM](scim_setup.md).
+- [Group-managed accounts](group_managed_accounts.md).
+- A GitLab.com account.
+
+### Linking SAML to your existing GitLab.com account
To link SAML to your existing GitLab.com account:
1. Sign in to your GitLab.com account.
-1. Locate the SSO URL for the group you are signing in to. A group Admin can find this on the group's **Settings > SAML SSO** page.
-1. Visit the SSO URL and click **Authorize**.
+1. Locate and visit the **GitLab single sign-on URL** for the group you are signing in to. A group Admin can find this on the group's **Settings > SAML SSO** page. If the sign-in URL is configured, users can connect to the GitLab app from the Identity Provider.
+1. Click **Authorize**.
1. Enter your credentials on the Identity Provider if prompted.
1. You will be redirected back to GitLab.com and should now have access to the group. In the future, you can use SAML to sign in to GitLab.com.
-## Signing in to GitLab.com with SAML
+On subsequent visits, you should be able to go [sign in to GitLab.com with SAML](#signing-in-to-gitlabcom-with-saml) or by visiting links directly. If the **enforce SSO** option is turned on, you will be redirected to sign in through the identity provider.
-1. Locate the SSO URL for the group you are signing in to. A group Admin can find this on a group's **Settings > SAML SSO** page. If configured, it might also be possible to sign in to GitLab starting from your Identity Provider.
-1. Visit the SSO URL and click the **Sign in with Single Sign-On** button.
-1. Enter your credentials on the Identity Provider if prompted.
+### Signing in to GitLab.com with SAML
+
+1. Sign in to your identity provider.
+1. From the list of apps, click on the "GitLab.com" app (The name is set by the administrator of the identity provider).
1. You will be signed in to GitLab.com and redirected to the group.
-## Unlinking accounts
+### Role
+
+The first time you sign in, GitLab adds you to the top-level parent group with the Guest role. Existing members with appropriate privileges can promote that new user.
+
+If a user is already a member of the group, linking the SAML identity does not change their role.
+
+### Blocking access
+
+To rescind access to the group, perform the following steps, in order:
+
+1. Remove the user from the user datastore on the identity provider or the list of users on the specific app.
+1. Remove the user from the GitLab.com group.
+
+### Unlinking accounts
Users can unlink SAML for a group from their profile page. This can be helpful if:
@@ -359,7 +250,7 @@ For example, to unlink the `MyOrg` account, the following **Disconnect** button
| Issuer | How GitLab identifies itself to the identity provider. Also known as a "Relying party trust identifier". |
| Certificate fingerprint | Used to confirm that communications over SAML are secure by checking that the server is signing communications with the correct certificate. Also known as a certificate thumbprint. |
-## Configuring on a self-managed GitLab instance
+## Configuring on a self-managed GitLab instance **(PREMIUM ONLY)**
For self-managed GitLab instances we strongly recommend using the
[instance-wide SAML OmniAuth Provider](../../../integration/saml.md) instead.
@@ -377,7 +268,7 @@ Group SAML on a self-managed instance is limited when compared to the recommende
[instance-wide SAML](../../../integration/saml.md). The recommended solution allows you to take advantage of:
- [LDAP compatibility](../../../administration/auth/ldap/index.md).
-- [LDAP Group Sync](../index.md#manage-group-memberships-via-ldap)
+- [LDAP Group Sync](../index.md#manage-group-memberships-via-ldap).
- [Required groups](../../../integration/saml.md#required-groups-starter-only).
- [Admin groups](../../../integration/saml.md#admin-groups-starter-only).
- [Auditor groups](../../../integration/saml.md#auditor-groups-starter-only).
@@ -449,7 +340,6 @@ Here are possible causes and solutions:
| Cause | Solution |
|------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| You've tried to link multiple SAML identities to the same user, for a given Identity Provider. | Change the identity that you sign in with. To do so, [unlink the previous SAML identity](#unlinking-accounts) from this GitLab account before attempting to sign in again. |
-| The Identity Provider might be returning an inconsistent [NameID](#nameid). | Ask an admin of your Identity Provider to use the [SCIM API](../../../api/scim.md) to update your `extern_uid` to match the current **NameID**. |
### Message: "SAML authentication failed: Email has already been taken"
@@ -463,6 +353,16 @@ Getting both of these errors at the same time suggests the NameID capitalization
This can be prevented by configuring the [NameID](#nameid) to return a consistent value. Fixing this for an individual user involves [unlinking SAML in the GitLab account](#unlinking-accounts), although this will cause group membership and Todos to be lost.
+### Message: "Request to link SAML account must be authorized"
+
+Ensure that the user who is trying to link their GitLab account has been added as a user within the identity provider's SAML app.
+
+### Stuck in a login "loop"
+
+Ensure that the **GitLab single sign-on URL** has been configured as "Login URL" (or similarly named field) in the identity provider's SAML app.
+
+Alternatively, when users need to [link SAML to their existing GitLab.com account](#linking-saml-to-your-existing-gitlabcom-account), provide the **GitLab single sign-on URL** and instruct users not to use the SAML app on first sign in.
+
### The NameID has changed
| Cause | Solution |
@@ -473,17 +373,6 @@ This can be prevented by configuring the [NameID](#nameid) to return a consisten
Users will need to [unlink the current SAML identity](#unlinking-accounts) and [link their identity](#user-access-and-management) to the new SAML app.
-### My identity provider isn't listed
-
-Not a problem, the SAML standard means that a wide range of identity providers will work with GitLab. Unfortunately we aren't familiar with all of them so can only offer support configuring the [listed providers](#providers).
-
-Your identity provider may also have relevant documentation. It may be generic SAML documentation, or specifically targeted for GitLab. Examples:
-
-- [Auth0](https://auth0.com/docs/protocols/saml/saml-idp-generic)
-- [G Suite](https://support.google.com/a/answer/6087519?hl=en)
-- [JumpCloud](https://support.jumpcloud.com/support/s/article/single-sign-on-sso-with-gitlab-2019-08-21-10-36-47)
-- [PingOne by Ping Identity](https://docs.pingidentity.com/bundle/pingone/page/xsh1564020480660-1.html)
-
### I need additional information to configure my identity provider
Many SAML terms can vary between providers. It is possible that the information you are looking for is listed under another name.
diff --git a/doc/user/group/saml_sso/scim_setup.md b/doc/user/group/saml_sso/scim_setup.md
index a891962b38e..13e9d623e2c 100644
--- a/doc/user/group/saml_sso/scim_setup.md
+++ b/doc/user/group/saml_sso/scim_setup.md
@@ -92,7 +92,8 @@ You can then test the connection by clicking on **Test Connection**. If the conn
1. Save your changes. For reference, you can view [an example configuration in the troubleshooting reference](../../../administration/troubleshooting/group_saml_scim.md#azure-active-directory).
- NOTE: **Note:** If you used a unique identifier **other than** `objectId`, be sure to map it to `externalId`.
+ NOTE: **Note:**
+ If you used a unique identifier **other than** `objectId`, be sure to map it to `externalId`.
1. Below the mapping list click on **Show advanced options > Edit attribute list for AppName**.
@@ -127,7 +128,8 @@ Before proceeding, be sure to complete the [GitLab configuration](#gitlab-config
1. If you see an **Admin** button in the top right, click the button. This will
ensure you are in the Admin area.
- TIP: **Tip:** If you're using the Developer Console, click **Developer Console** in the top
+ TIP: **Tip:**
+ If you're using the Developer Console, click **Developer Console** in the top
bar and select **Classic UI**. Otherwise, you may not see the buttons described
in the following steps:
@@ -163,7 +165,7 @@ As long as [Group SAML](index.md) has been configured, prior to turning on sync,
- By following these steps:
1. Sign in to GitLab.com if needed.
- 1. Click on the GitLab app in the identity provider's dashboard or visit the **GitLab single sign on URL**.
+ 1. Click on the GitLab app in the identity provider's dashboard or visit the **GitLab single sign-on URL**.
1. Click on the **Authorize** button.
New users and existing users on subsequent visits can access the group through the identify provider's dashboard or by visiting links directly.
@@ -175,7 +177,7 @@ For role information, please see the [Group SAML page](index.md#user-access-and-
To rescind access to the group, we recommend removing the user from the identity
provider or users list for the specific app.
-Upon the next sync, the user will be deprovisioned, which means that the user will be removed from the group. The user account will not be deleted unless using [group managed accounts](index.md#group-managed-accounts).
+Upon the next sync, the user will be deprovisioned, which means that the user will be removed from the group. The user account will not be deleted unless using [group managed accounts](group_managed_accounts.md).
## Troubleshooting
diff --git a/doc/user/group/subgroups/index.md b/doc/user/group/subgroups/index.md
index 842e2082be4..a370c9cf136 100644
--- a/doc/user/group/subgroups/index.md
+++ b/doc/user/group/subgroups/index.md
@@ -19,10 +19,13 @@ By using subgroups you can do the following:
- **Make it easier to manage people and control visibility.** Give people
different [permissions](../../permissions.md#group-members-permissions) depending on their group [membership](#membership).
+For more information on allowed permissions in groups and projects, see
+[visibility levels](../../../development/permissions.md#general-permissions).
+
## Overview
A group can have many subgroups inside it, and at the same time a group can have
-only 1 parent group. It resembles a directory behavior or a nested items list:
+only one immediate parent group. It resembles a directory behavior or a nested items list:
- Group 1
- Group 1.1
@@ -86,7 +89,7 @@ of words that are not allowed to be used as group names see the
[reserved names](../../reserved_names.md).
Users can always create subgroups if they are explicitly added as an Owner (or
-Maintainer, if that setting is enabled) to a parent group, even if group
+Maintainer, if that setting is enabled) to an immediate parent group, even if group
creation is disabled by an administrator in their settings.
To create a subgroup:
@@ -96,9 +99,9 @@ To create a subgroup:
![Subgroups page](img/create_subgroup_button.png)
-1. Create a new group like you would normally do. Notice that the parent group
+1. Create a new group like you would normally do. Notice that the immediate parent group
namespace is fixed under **Group path**. The visibility level can differ from
- the parent group.
+ the immediate parent group.
![Subgroups page](img/create_new_group.png)
@@ -110,12 +113,13 @@ Follow the same process to create any subsequent groups.
## Membership
When you add a member to a subgroup, they inherit the membership and permission
-level from the parent group. This model allows access to nested groups if you
+level from the parent group(s). This model allows access to nested groups if you
have membership in one of its parents.
-Jobs for pipelines in subgroups can use [Runners](../../../ci/runners/README.md) registered to the parent group. This means secrets configured for the parent group are available to subgroup jobs.
+Jobs for pipelines in subgroups can use [Runners](../../../ci/runners/README.md) registered to the parent group(s).
+This means secrets configured for the parent group are available to subgroup jobs.
-In addition, maintainers of projects that belong to subgroups can see the details of Runners registered to parent groups.
+In addition, maintainers of projects that belong to subgroups can see the details of Runners registered to parent group(s).
The group permissions for a member can be changed only by Owners, and only on
the **Members** page of the group the member was added.
diff --git a/doc/user/incident_management/img/pagerduty_incidents_integration_13_2.png b/doc/user/incident_management/img/pagerduty_incidents_integration_13_2.png
new file mode 100644
index 00000000000..9277b013676
--- /dev/null
+++ b/doc/user/incident_management/img/pagerduty_incidents_integration_13_2.png
Binary files differ
diff --git a/doc/user/incident_management/index.md b/doc/user/incident_management/index.md
index 48805bda909..996f423e770 100644
--- a/doc/user/incident_management/index.md
+++ b/doc/user/incident_management/index.md
@@ -25,7 +25,7 @@ to create issues when alerts are triggered:
checkbox to create an issue based on your own
[issue templates](../project/description_templates.md#creating-issue-templates).
For more information, see
- [Taking Action on Incidents](../project/integrations/prometheus.md#taking-action-on-incidents-ultimate) **(ULTIMATE)**.
+ [Trigger actions from alerts](../../operations/metrics/alerts.md#trigger-actions-from-alerts-ultimate) **(ULTIMATE)**.
1. To create issues from alerts, select the template in the **Issue Template**
select box.
1. To send [separate email notifications](#notify-developers-of-alerts) to users
@@ -34,9 +34,9 @@ to create issues when alerts are triggered:
1. Click **Save changes**.
Appropriately configured alerts include an
-[embedded chart](../project/integrations/prometheus.md#embedding-metrics-based-on-alerts-in-incident-issues)
+[embedded chart](../../operations/metrics/embed.md#embedding-metrics-based-on-alerts-in-incident-issues)
for the query corresponding to the alert. You can also configure GitLab to
-[close issues](../project/integrations/prometheus.md#taking-action-on-incidents-ultimate)
+[close issues](../../operations/metrics/alerts.md#trigger-actions-from-alerts-ultimate)
when you receive notification that the alert is resolved.
### Notify developers of alerts
@@ -49,12 +49,35 @@ These emails contain details of the alert, and a link for more information.
To send separate email notifications to users with
[Developer permissions](../permissions.md), see [Configure incidents](#configure-incidents-ultimate).
+## Configure PagerDuty integration
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/119018) in GitLab 13.2.
+
+You can set up a webhook with PagerDuty to automatically create a GitLab issue
+for each PagerDuty incident. This configuration requires you to make changes
+in both PagerDuty and GitLab:
+
+1. Sign in as a user with Maintainer [permissions](../permissions.md).
+1. Navigate to **{settings}** **Settings > Operations > Incidents** and expand **Incidents**.
+1. Select the **PagerDuty integration** tab:
+
+ ![PagerDuty incidents integration](img/pagerduty_incidents_integration_13_2.png)
+
+1. Activate the integration, and save the changes in GitLab.
+1. Copy the value of **Webhook URL**, as you'll need it in a later step.
+1. Follow the steps described in the
+ [PagerDuty documentation](https://support.pagerduty.com/docs/webhooks)
+ to add the webhook URL to a PagerDuty webhook integration.
+
+To confirm the integration is successful, trigger a test incident from PagerDuty to
+confirm that a GitLab issue is created from the incident.
+
## Configure Prometheus alerts
You can set up Prometheus alerts in:
-- [GitLab-managed Prometheus](../project/integrations/prometheus.md#setting-up-alerts-for-prometheus-metrics) installations.
-- [Self-managed Prometheus](../project/integrations/prometheus.md#external-prometheus-instances) installations.
+- [GitLab-managed Prometheus](../../operations/metrics/alerts.md) installations.
+- [Self-managed Prometheus](../../operations/metrics/alerts.md#external-prometheus-instances) installations.
Prometheus alerts are created by the special Alert Bot user. You can't remove this
user, but it does not count toward your license limit.
@@ -71,11 +94,11 @@ You can embed metrics anywhere [GitLab Markdown](../markdown.md) is used, such a
comments on issues, and merge requests. Embedding metrics helps you share them
when discussing incidents or performance issues. You can output the dashboard directly
into any issue, merge request, epic, or any other Markdown text field in GitLab
-by [copying and pasting the link to the metrics dashboard](../project/integrations/prometheus.md#embedding-gitlab-managed-kubernetes-metrics).
+by [copying and pasting the link to the metrics dashboard](../../operations/metrics/embed.md#embedding-gitlab-managed-kubernetes-metrics).
You can embed both
-[GitLab-hosted metrics](../project/integrations/prometheus.md#embedding-metric-charts-within-gitlab-flavored-markdown) and
-[Grafana metrics](../project/integrations/prometheus.md#embedding-grafana-charts)
+[GitLab-hosted metrics](../../operations/metrics/embed.md) and
+[Grafana metrics](../../operations/metrics/embed_grafana.md)
in incidents and issue templates.
### Context menu
@@ -86,7 +109,7 @@ above the upper right corner of the panel. The options are:
- [View logs](#view-logs-from-metrics-panel).
- **Download CSV** - Data from embedded charts can be
- [downloaded as CSV](../project/integrations/prometheus.md#downloading-data-as-csv).
+ [downloaded as CSV](../../operations/metrics/dashboards/index.md#downloading-data-as-csv).
#### View logs from metrics panel
@@ -94,7 +117,7 @@ above the upper right corner of the panel. The options are:
> - [Moved](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/25455) to [GitLab Core](https://about.gitlab.com/pricing/) 12.9.
Viewing logs from a metrics panel can be useful if you're triaging an application
-incident and need to [explore logs](../project/integrations/prometheus.md#view-logs-ultimate)
+incident and need to [explore logs](../../operations/metrics/dashboards/index.md#view-logs-ultimate)
from across your application. These logs help you understand what is affecting
your application's performance and resolve any problems.
diff --git a/doc/user/index.md b/doc/user/index.md
index cc521c2a767..f50b712e2c3 100644
--- a/doc/user/index.md
+++ b/doc/user/index.md
@@ -46,10 +46,10 @@ GitLab is a Git-based platform that integrates a great number of essential tools
- Deploying personal and professional static websites with [GitLab Pages](project/pages/index.md).
- Integrating with Docker by using [GitLab Container Registry](packages/container_registry/index.md).
- Tracking the development lifecycle by using [GitLab Value Stream Analytics](project/cycle_analytics.md).
+- Provide support with [Service Desk](project/service_desk.md).
With GitLab Enterprise Edition, you can also:
-- Provide support with [Service Desk](project/service_desk.md).
- Improve collaboration with:
- [Merge Request Approvals](project/merge_requests/merge_request_approvals.md). **(STARTER)**
- [Multiple Assignees for Issues](project/issues/multiple_assignees_for_issues.md). **(STARTER)**
@@ -148,7 +148,7 @@ requests you're assigned to.
[Snippets](snippets.md) are code blocks that you want to store in GitLab, from which
you have quick access to. You can also gather feedback on them through
-[Discussions](#Discussions).
+[Discussions](#discussions).
## Keyboard shortcuts
diff --git a/doc/user/infrastructure/img/terraform_plan_widget_v13_2.png b/doc/user/infrastructure/img/terraform_plan_widget_v13_2.png
new file mode 100644
index 00000000000..564835a5dbe
--- /dev/null
+++ b/doc/user/infrastructure/img/terraform_plan_widget_v13_2.png
Binary files differ
diff --git a/doc/user/infrastructure/index.md b/doc/user/infrastructure/index.md
index c17d1831b50..e24e669d994 100644
--- a/doc/user/infrastructure/index.md
+++ b/doc/user/infrastructure/index.md
@@ -6,8 +6,17 @@ info: To determine the technical writer assigned to the Stage/Group associated w
# Infrastructure as code with Terraform and GitLab
+## Motivation
+
+The Terraform integration features within GitLab enable your GitOps / Infrastructure-as-Code (IaC)
+workflows to tie into GitLab's authentication and authorization. These features focus on
+lowering the barrier to entry for teams to adopt Terraform, collaborate effectively within
+GitLab, and support Terraform best practices.
+
## GitLab managed Terraform State
+> [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/2673) in GitLab 13.0.
+
[Terraform remote backends](https://www.terraform.io/docs/backends/index.html)
enable you to store the state file in a remote, shared store. GitLab uses the
[Terraform HTTP backend](https://www.terraform.io/docs/backends/types/http.html)
@@ -27,6 +36,14 @@ To get started with a GitLab-managed Terraform State, there are two different op
- [Use a local machine](#get-started-using-local-development).
- [Use GitLab CI](#get-started-using-gitlab-ci).
+## Permissions for using Terraform
+
+In GitLab version 13.1, [Maintainer access](../permissions.md) was required to use a
+GitLab managed Terraform state backend. In GitLab versions 13.2 and greater,
+[Maintainer access](../permissions.md) is required to lock, unlock and write to the state
+(using `terraform apply`), while [Developer access](../permissions.md) is required to read
+the state (using `terraform plan -lock=false`).
+
## Get started using local development
If you plan to only run `terraform plan` and `terraform apply` commands from your
@@ -45,8 +62,7 @@ local machine, this is a simple way to get started:
```
1. Create a [Personal Access Token](../profile/personal_access_tokens.md) with
- the `api` scope. The Terraform backend is restricted to users with
- [Maintainer access](../permissions.md) to the repository.
+ the `api` scope.
1. On your local machine, run `terraform init`, passing in the following options,
replacing `<YOUR-PROJECT-NAME>`, `<YOUR-PROJECT-ID>`, `<YOUR-USERNAME>` and
@@ -80,10 +96,6 @@ Next, [configure the backend](#configure-the-backend).
After executing the `terraform init` command, you must configure the Terraform backend
and the CI YAML file:
-CAUTION: **Important:**
-The Terraform backend is restricted to users with [Maintainer access](../permissions.md)
-to the repository.
-
1. In your Terraform project, define the [HTTP backend](https://www.terraform.io/docs/backends/types/http.html)
by adding the following code block in a `.tf` file (such as `backend.tf`) to
define the remote backend:
@@ -95,64 +107,75 @@ to the repository.
}
```
-1. In the root directory of your project repository, configure a `.gitlab-ci.yaml` file.
- This example uses a pre-built image:
+1. In the root directory of your project repository, configure a
+ `.gitlab-ci.yaml` file. This example uses a pre-built image which includes a
+ `gitlab-terraform` helper. For supported Terraform versions, see the [GitLab
+ Terraform Images project](https://gitlab.com/gitlab-org/terraform-images).
```yaml
- image:
- name: hashicorp/terraform:light
- entrypoint:
- - '/usr/bin/env'
- - 'PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin'
+ image: registry.gitlab.com/gitlab-org/terraform-images/stable:latest
```
-1. In the `.gitlab-ci.yaml` file, define some environment variables to ease development. In this
- example, `GITLAB_TF_ADDRESS` is the URL of the GitLab instance where this pipeline
- runs, and `TF_ROOT` is the directory where the Terraform commands must be executed:
+1. In the `.gitlab-ci.yaml` file, define some environment variables to ease
+ development. In this example, `TF_ROOT` is the directory where the Terraform
+ commands must be executed, `TF_ADDRESS` is the URL to the state on the GitLab
+ instance where this pipeline runs, and the final path segment in `TF_ADDRESS`
+ is the name of the Terraform state. Projects may have multiple states, and
+ this name is arbitrary, so in this example we will set it to the name of the
+ project, and we will ensure that the `.terraform` directory is cached between
+ jobs in the pipeline using a cache key based on the state name:
```yaml
variables:
- GITLAB_TF_ADDRESS: ${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/terraform/state/${CI_PROJECT_NAME}
TF_ROOT: ${CI_PROJECT_DIR}/environments/cloudflare/production
+ TF_ADDRESS: ${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/terraform/state/${CI_PROJECT_NAME}
cache:
+ key: ${CI_PROJECT_NAME}
paths:
- - .terraform
+ - ${TF_ROOT}/.terraform
```
-1. In a `before_script`, pass a `terraform init` call containing configuration parameters
- corresponding to variables required by the
- [HTTP backend](https://www.terraform.io/docs/backends/types/http.html):
+1. In a `before_script`, change to your `TF_ROOT`:
```yaml
before_script:
- cd ${TF_ROOT}
- - terraform --version
- - terraform init -backend-config="address=${GITLAB_TF_ADDRESS}" -backend-config="lock_address=${GITLAB_TF_ADDRESS}/lock" -backend-config="unlock_address=${GITLAB_TF_ADDRESS}/lock" -backend-config="username=gitlab-ci-token" -backend-config="password=${CI_JOB_TOKEN}" -backend-config="lock_method=POST" -backend-config="unlock_method=DELETE" -backend-config="retry_wait_min=5"
stages:
+ - prepare
- validate
- build
- - test
- deploy
+ init:
+ stage: prepare
+ script:
+ - gitlab-terraform init
+
validate:
stage: validate
script:
- - terraform validate
+ - gitlab-terraform validate
plan:
stage: build
script:
- - terraform plan
- - terraform show
+ - gitlab-terraform plan
+ - gitlab-terraform plan-json
+ artifacts:
+ name: plan
+ paths:
+ - ${TF_ROOT}/plan.cache
+ reports:
+ terraform: ${TF_ROOT}/plan.json
apply:
stage: deploy
environment:
name: production
script:
- - terraform apply
+ - gitlab-terraform apply
dependencies:
- plan
when: manual
@@ -160,8 +183,9 @@ to the repository.
- master
```
-1. Push your project to GitLab, which triggers a CI job pipeline. This pipeline runs
- the `terraform init`, `terraform validate`, and `terraform plan` commands.
+1. Push your project to GitLab, which triggers a CI job pipeline. This pipeline
+ runs the `gitlab-terraform init`, `gitlab-terraform validate`, and
+ `gitlab-terraform plan` commands.
The output from the above `terraform` commands should be viewable in the job logs.
@@ -176,15 +200,18 @@ you can expose details from `terraform plan` runs directly into a merge request
enabling you to see statistics about the resources that Terraform will create,
modify, or destroy.
-Let's explore how to configure a GitLab Terraform Report artifact:
+Let's explore how to configure a GitLab Terraform Report artifact. You can
+either use a pre-built image which includes a `gitlab-terraform` helper as
+above, where `gitlab-terraform plan-json` outputs the required artifact, or you
+can configure this manually as follows:
1. For simplicity, let's define a few reusable variables to allow us to
refer to these files multiple times:
```yaml
variables:
- PLAN: plan.tfplan
- PLAN_JSON: tfplan.json
+ PLAN: plan.cache
+ PLAN_JSON: plan.json
```
1. Install `jq`, a
@@ -198,6 +225,18 @@ Let's explore how to configure a GitLab Terraform Report artifact:
- alias convert_report="jq -r '([.resource_changes[]?.change.actions?]|flatten)|{\"create\":(map(select(.==\"create\"))|length),\"update\":(map(select(.==\"update\"))|length),\"delete\":(map(select(.==\"delete\"))|length)}'"
```
+ NOTE: **Note:**
+ In distributions that use Bash (for example, Ubuntu), `alias` statements are not
+ expanded in non-interactive mode. If your pipelines fail with the error
+ `convert_report: command not found`, alias expansion can be activated explicitly
+ by adding a `shopt` command to your script:
+
+ ```yaml
+ before_script:
+ - shopt -s expand_aliases
+ - alias convert_report="jq -r '([.resource_changes[]?.change.actions?]|flatten)|{\"create\":(map(select(.==\"create\"))|length),\"update\":(map(select(.==\"update\"))|length),\"delete\":(map(select(.==\"delete\"))|length)}'"
+ ```
+
1. Define a `script` that runs `terraform plan` and `terraform show`. These commands
pipe the output and convert the relevant bits into a store variable `PLAN_JSON`.
This JSON is used to create a
@@ -212,18 +251,18 @@ Let's explore how to configure a GitLab Terraform Report artifact:
- terraform plan -out=$PLAN
- terraform show --json $PLAN | convert_report > $PLAN_JSON
artifacts:
- name: plan
- paths:
- - $PLAN
reports:
terraform: $PLAN_JSON
```
- For a full example, see [Example `.gitlab-ci.yaml` file](#example-gitlab-ciyaml-file).
+ For a full example using the pre-built image, see [Example `.gitlab-ci.yaml`
+ file](#example-gitlab-ciyaml-file).
+
+ For an example displaying multiple reports, see [`.gitlab-ci.yaml` multiple reports file](#multiple-terraform-plan-reports).
1. Running the pipeline displays the widget in the merge request, like this:
- ![MR Terraform widget](img/terraform_plan_widget_v13_0.png)
+ ![Merge Request Terraform widget](img/terraform_plan_widget_v13_2.png)
1. Clicking the **View Full Log** button in the widget takes you directly to the
plan output present in the pipeline logs:
@@ -233,64 +272,114 @@ Let's explore how to configure a GitLab Terraform Report artifact:
### Example `.gitlab-ci.yaml` file
```yaml
-image:
- name: hashicorp/terraform:light
- entrypoint:
- - '/usr/bin/env'
- - 'PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin'
+image: registry.gitlab.com/gitlab-org/terraform-images/stable:latest
-# Default output file for Terraform plan
variables:
- GITLAB_TF_ADDRESS: ${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/terraform/state/${CI_PROJECT_NAME}
- PLAN: plan.tfplan
- PLAN_JSON: tfplan.json
- TF_ROOT: ${CI_PROJECT_DIR}
+ TF_ROOT: ${CI_PROJECT_DIR}/environments/cloudflare/production
+ TF_ADDRESS: ${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/terraform/state/${CI_PROJECT_NAME}
cache:
+ key: ${CI_PROJECT_NAME}
paths:
- - .terraform
+ - ${TF_ROOT}/.terraform
before_script:
- - apk --no-cache add jq
- - alias convert_report="jq -r '([.resource_changes[]?.change.actions?]|flatten)|{\"create\":(map(select(.==\"create\"))|length),\"update\":(map(select(.==\"update\"))|length),\"delete\":(map(select(.==\"delete\"))|length)}'"
- cd ${TF_ROOT}
- - terraform --version
- - terraform init -backend-config="address=${GITLAB_TF_ADDRESS}" -backend-config="lock_address=${GITLAB_TF_ADDRESS}/lock" -backend-config="unlock_address=${GITLAB_TF_ADDRESS}/lock" -backend-config="username=${GITLAB_USER_LOGIN}" -backend-config="password=${GITLAB_TF_PASSWORD}" -backend-config="lock_method=POST" -backend-config="unlock_method=DELETE" -backend-config="retry_wait_min=5"
stages:
+ - prepare
- validate
- build
- deploy
+init:
+ stage: prepare
+ script:
+ - gitlab-terraform init
+
validate:
stage: validate
script:
- - terraform validate
+ - gitlab-terraform validate
plan:
stage: build
script:
- - terraform plan -out=$PLAN
- - terraform show --json $PLAN | convert_report > $PLAN_JSON
+ - gitlab-terraform plan
+ - gitlab-terraform plan-json
artifacts:
name: plan
paths:
- - ${TF_ROOT}/plan.tfplan
+ - ${TF_ROOT}/plan.cache
reports:
- terraform: ${TF_ROOT}/tfplan.json
+ terraform: ${TF_ROOT}/plan.json
-# Separate apply job for manual launching Terraform as it can be destructive
-# action.
apply:
stage: deploy
environment:
name: production
script:
- - terraform apply -input=false $PLAN
+ - gitlab-terraform apply
dependencies:
- plan
when: manual
only:
- master
+```
+
+### Multiple Terraform Plan reports
+
+Starting with 13.2, you can display mutiple reports on the Merge Request page. The reports will also display the `artifact: name:`. See example below for a suggested setup.
+
+```yaml
+image:
+ name: registry.gitlab.com/gitlab-org/gitlab-build-images:terraform
+ entrypoint:
+ - '/usr/bin/env'
+ - 'PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin'
+
+cache:
+ paths:
+ - .terraform
+stages:
+ - build
+
+.terraform-plan-generation:
+ stage: build
+ variables:
+ PLAN: plan.tfplan
+ JSON_PLAN_FILE: tfplan.json
+ before_script:
+ - cd ${TERRAFORM_DIRECTORY}
+ - terraform --version
+ - terraform init
+ - apk --no-cache add jq
+ script:
+ - terraform validate
+ - terraform plan -out=${PLAN}
+ - terraform show --json ${PLAN} | jq -r '([.resource_changes[]?.change.actions?]|flatten)|{"create":(map(select(.=="create"))|length),"update":(map(select(.=="update"))|length),"delete":(map(select(.=="delete"))|length)}' > ${JSON_PLAN_FILE}
+ artifacts:
+ reports:
+ terraform: ${TERRAFORM_DIRECTORY}/${JSON_PLAN_FILE}
+
+review_plan:
+ extends: .terraform-plan-generation
+ variables:
+ TERRAFORM_DIRECTORY: "review/"
+ # Review will not include an artifact name
+
+staging_plan:
+ extends: .terraform-plan-generation
+ variables:
+ TERRAFORM_DIRECTORY: "staging/"
+ artifacts:
+ name: Staging
+
+production_plan:
+ extends: .terraform-plan-generation
+ variables:
+ TERRAFORM_DIRECTORY: "production/"
+ artifacts:
+ name: Production
```
diff --git a/doc/user/markdown.md b/doc/user/markdown.md
index 0d028cfe77a..b2b3f0f925c 100644
--- a/doc/user/markdown.md
+++ b/doc/user/markdown.md
@@ -14,7 +14,8 @@ website uses an extended Kramdown gem, [GitLab Kramdown](https://gitlab.com/gitl
Consult the [GitLab Kramdown Guide](https://about.gitlab.com/handbook/markdown-guide/)
for a complete Kramdown reference.
-NOTE: **Note:** We encourage you to view this document as [rendered by GitLab itself](https://gitlab.com/gitlab-org/gitlab/blob/master/doc/user/markdown.md).
+NOTE: **Note:**
+We encourage you to view this document as [rendered by GitLab itself](https://gitlab.com/gitlab-org/gitlab/blob/master/doc/user/markdown.md).
## GitLab Flavored Markdown (GFM)
@@ -54,7 +55,7 @@ repository that were written using some of the nuances of GitLab's RedCarpet ver
of Markdown. Since CommonMark uses slightly stricter syntax, these documents
might now appear a little differently since we have transitioned to CommonMark.
-It's usually quite easy to fix. For example, numbered lists with nested lists may
+For example, numbered lists with nested lists may
render incorrectly:
```markdown
@@ -63,8 +64,8 @@ render incorrectly:
- milk
```
-Simply add a space to each nested item to align the `-` with the first character of
-the top list item (`C` in this case):
+To correct their rendering, add a space to each nested item to align the `-` with the first
+character of the top list item (`C` in this case):
```markdown
1. Chocolate
@@ -76,7 +77,8 @@ the top list item (`C` in this case):
- dark
- milk
-NOTE: **Note:** We will flag any significant differences between Redcarpet and CommonMark
+NOTE: **Note:**
+We will flag any significant differences between Redcarpet and CommonMark
Markdown in this document.
If you have a large volume of Markdown files, it can be tedious to determine
@@ -92,7 +94,7 @@ to change.
GitLab makes full use of the standard (CommonMark) formatting, but also includes additional
functionality useful for GitLab users.
-It makes use of [new Markdown features](#new-GFM-markdown-extensions),
+It makes use of [new Markdown features](#new-gfm-markdown-extensions),
not found in standard Markdown:
- [Color "chips" written in HEX, RGB or HSL](#colors)
@@ -241,7 +243,7 @@ Sometimes you want to :monkey: around a bit and add some :star2: to your :speech
You can use it to point out a :bug: or warn about :speak_no_evil: patches. And if someone improves your really :snail: code, send them some :birthday:. People will :heart: you for that.
-If you're new to this, don't be :fearful:. You can easily join the emoji :family:. All you need to do is to look up one of the supported codes.
+If you're new to this, don't be :fearful:. You can join the emoji :family:. All you need to do is to look up one of the supported codes.
Consult the [Emoji Cheat Sheet](https://www.emojicopy.com) for a list of all supported emoji codes. :thumbsup:
```
@@ -252,7 +254,7 @@ Sometimes you want to <img src="https://gitlab.com/gitlab-org/gitlab-foss/raw/ma
You can use it to point out a <img src="https://gitlab.com/gitlab-org/gitlab-foss/raw/master/app/assets/images/emoji/bug.png" width="20px" height="20px" style="display:inline;margin:0"> or warn about <img src="https://gitlab.com/gitlab-org/gitlab-foss/raw/master/app/assets/images/emoji/speak_no_evil.png" width="20px" height="20px" style="display:inline;margin:0"> patches. And if someone improves your really <img src="https://gitlab.com/gitlab-org/gitlab-foss/raw/master/app/assets/images/emoji/snail.png" width="20px" height="20px" style="display:inline;margin:0"> code, send them some <img src="https://gitlab.com/gitlab-org/gitlab-foss/raw/master/app/assets/images/emoji/birthday.png" width="20px" height="20px" style="display:inline;margin:0">. People will <img src="https://gitlab.com/gitlab-org/gitlab-foss/raw/master/app/assets/images/emoji/heart.png" width="20px" height="20px" style="display:inline;margin:0"> you for that.
-If you're new to this, don't be <img src="https://gitlab.com/gitlab-org/gitlab-foss/raw/master/app/assets/images/emoji/fearful.png" width="20px" height="20px" style="display:inline;margin:0">. You can easily join the emoji <img src="https://gitlab.com/gitlab-org/gitlab-foss/raw/master/app/assets/images/emoji/family.png" width="20px" height="20px" style="display:inline;margin:0">. All you need to do is to look up one of the supported codes.
+If you're new to this, don't be <img src="https://gitlab.com/gitlab-org/gitlab-foss/raw/master/app/assets/images/emoji/fearful.png" width="20px" height="20px" style="display:inline;margin:0">. You can join the emoji <img src="https://gitlab.com/gitlab-org/gitlab-foss/raw/master/app/assets/images/emoji/family.png" width="20px" height="20px" style="display:inline;margin:0">. All you need to do is to look up one of the supported codes.
Consult the [Emoji Cheat Sheet](https://www.webfx.com/tools/emoji-cheat-sheet/) for a list of all supported emoji codes. <img src="https://gitlab.com/gitlab-org/gitlab-foss/raw/master/app/assets/images/emoji/thumbsup.png" width="20px" height="20px" style="display:inline;margin:0">
@@ -261,7 +263,8 @@ when rendered within GitLab, may appear different depending on the OS and browse
Most emoji are natively supported on macOS, Windows, iOS, Android, and will fall back on image-based emoji where there is no support.
-NOTE: **Note:** On Linux, you can download [Noto Color Emoji](https://www.google.com/get/noto/help/emoji/)
+NOTE: **Note:**
+On Linux, you can download [Noto Color Emoji](https://www.google.com/get/noto/help/emoji/)
to get full native emoji support. Ubuntu 18.04 (like many modern Linux distributions) has
this font installed by default.
@@ -402,14 +405,15 @@ a^2+b^2=c^2
_Be advised that KaTeX only supports a [subset](https://katex.org/docs/supported.html) of LaTeX._
-NOTE: **Note:** This also works for the Asciidoctor `:stem: latexmath`. For details see
+NOTE: **Note:**
+This also works for the Asciidoctor `:stem: latexmath`. For details see
the [Asciidoctor user manual](https://asciidoctor.org/docs/user-manual/#activating-stem-support).
### Special GitLab references
-GFM recognizes special GitLab related references. For example, you can easily reference
+GFM recognizes special GitLab related references. For example, you can reference
an issue, a commit, a team member, or even the whole team within a project. GFM will turn
-that reference into a link so you can navigate between them easily.
+that reference into a link so you can navigate between them.
Additionally, GFM recognizes certain cross-project references and also has a shorthand
version to reference other projects from the same namespace.
@@ -502,8 +506,6 @@ Second section content.
![Preview of an auto-generated TOC in a Wiki](img/markdown_toc_preview_v12_9.png)
----
-
### Wiki-specific Markdown
The following examples show how links inside wikis behave.
@@ -581,7 +583,7 @@ This snippet links to `<wiki_root>/miscellaneous.md`:
### Embedding metrics in GitLab Flavored Markdown
-Metric charts can be embedded within GitLab Flavored Markdown. See [Embedding Metrics within GitLab flavored Markdown](../user/project/integrations/prometheus.md#embedding-metric-charts-within-gitlab-flavored-markdown) for more details.
+Metric charts can be embedded within GitLab Flavored Markdown. See [Embedding Metrics within GitLab flavored Markdown](../operations/metrics/embed.md#embedding-metric-charts-within-gitlab-flavored-markdown) for more details.
## Standard Markdown and extensions in GitLab
@@ -591,7 +593,7 @@ If a functionality is extended, the new option will be listed as a sub-section.
### Blockquotes
-Blockquotes are an easy way to highlight information, such as a side-note. It's generated
+Blockquotes are useful to highlight information, such as a side-note. It's generated
by starting the lines of the blockquote with `>`:
```markdown
@@ -637,9 +639,9 @@ you can quote that without having to manually prepend `>` to every line!
### Code spans and blocks
-You can easily highlight anything that should be viewed as code and not simple text.
+You can highlight anything that should be viewed as code and not simple text.
-Simple inline code is easily highlighted with single backticks `` ` ``:
+Simple inline code is highlighted with single backticks `` ` ``:
```markdown
Inline `code` has `back-ticks around` it.
@@ -781,7 +783,8 @@ Combined emphasis with **asterisks and _underscores_**.
Strikethrough uses two tildes. ~~Scratch this.~~
-NOTE: **Note:** Strikethrough is not part of the core Markdown standard, but is part of GFM.
+NOTE: **Note:**
+Strikethrough is not part of the core Markdown standard, but is part of GFM.
#### Multiple underscores in words and mid-word emphasis
@@ -1150,7 +1153,7 @@ A line break will be inserted (a new paragraph will start) if the previous text
ended with two newlines, like when you hit <kbd>Enter</kbd> twice in a row. If you only
use one newline (hit <kbd>Enter</kbd> once), the next sentence will be part of the
same paragraph. This is useful if you want to keep long lines from wrapping, and keep
-them easily editable:
+them editable:
```markdown
Here's a line for us to start with.
@@ -1248,7 +1251,8 @@ Do not change to reference style links.
Some text to show that the reference links can follow later.
-NOTE: **Note:** Relative links do not allow the referencing of project files in a wiki
+NOTE: **Note:**
+Relative links do not allow the referencing of project files in a wiki
page, or a wiki page in a project file. The reason for this is that a wiki is always
in a separate Git repository in GitLab. For example, `[I'm a reference-style link](style)`
will point the link to `wikis/style` only when the link is inside of a wiki Markdown file.
@@ -1275,7 +1279,7 @@ GFM will auto-link almost any URL you put into your text:
### Lists
-Ordered and unordered lists can be easily created.
+Ordered and unordered lists can be created.
For an ordered list, add the number you want the list
to start with, like `1.`, followed by a space, at the start of each line for ordered lists.
diff --git a/doc/user/packages/composer_repository/index.md b/doc/user/packages/composer_repository/index.md
index 8a7c70ec74d..542efba1fae 100644
--- a/doc/user/packages/composer_repository/index.md
+++ b/doc/user/packages/composer_repository/index.md
@@ -6,7 +6,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w
# GitLab Composer Repository **(PREMIUM)**
-> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/15886) in [GitLab Premium](https://about.gitlab.com/pricing/) 13.1.
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/15886) in [GitLab Premium](https://about.gitlab.com/pricing/) 13.2.
With the GitLab Composer Repository, every project can have its own space to store [Composer](https://getcomposer.org/) packages.
@@ -19,7 +19,7 @@ This option is available only if your GitLab administrator has
After the Composer Repository is enabled, it will be available for all new projects
by default. To enable it for existing projects, or if you want to disable it:
-1. Navigate to your project's **Settings > General > Permissions**.
+1. Navigate to your project's **Settings > General > Visibility, project features, permissions**.
1. Find the Packages feature and enable or disable it.
1. Click on **Save changes** for the changes to take effect.
diff --git a/doc/user/packages/conan_repository/index.md b/doc/user/packages/conan_repository/index.md
index 020028dfc10..41b420ce7f6 100644
--- a/doc/user/packages/conan_repository/index.md
+++ b/doc/user/packages/conan_repository/index.md
@@ -22,7 +22,7 @@ This option is available only if your GitLab administrator has
After the Conan Repository is enabled, it will be available for all new projects
by default. To enable it for existing projects, or if you want to disable it:
-1. Navigate to your project's **Settings > General > Permissions**.
+1. Navigate to your project's **Settings > General > Visibility, project features, permissions**.
1. Find the Packages feature and enable or disable it.
1. Click on **Save changes** for the changes to take effect.
@@ -85,7 +85,7 @@ Next, you will create a package for that recipe by running `conan create` provid
conan create . my-org+my-group+my-project/beta
```
-NOTE: **Note**
+NOTE: **Note:**
Current [naming restrictions](#package-recipe-naming-convention) require you to name the `user` value as the `+` separated path of your project on GitLab.
The example above would create a package belonging to this project: `https://gitlab.com/my-org/my-group/my-project` with a channel of `beta`.
@@ -129,12 +129,12 @@ Once you have a personal access token and have [set your Conan remote](#adding-t
conan user <gitlab_username or deploy_token_username> -r gitlab -p <personal_access_token or deploy_token>
```
-Note: **Note**
+NOTE: **Note:**
If you named your remote something other than `gitlab`, your remote name should be used in this command instead of `gitlab`.
From now on, when you run commands using `--remote=gitlab`, your username and password will automatically be included in the requests.
-Note: **Note**
+NOTE: **Note:**
The personal access token is not stored locally at any moment. Conan uses JWT, so when you run this command, Conan will request an expirable token from GitLab using your token. The JWT does expire on a regular basis, so you will need to re-enter your personal access token when that happens.
Alternatively, you could explicitly include your credentials in any given command.
@@ -152,7 +152,7 @@ If you'd like Conan to always use GitLab as the registry for your package, you c
conan remote add_ref Hello/0.1@my-group+my-project/beta gitlab
```
-NOTE: **Note**
+NOTE: **Note:**
The package recipe does include the version, so setting the default remote for `Hello/0.1@user/channel` will not work for `Hello/0.2@user/channel`.
This functionality is best suited for when you want to consume or install packages from the GitLab registry without having to specify a remote.
diff --git a/doc/user/packages/container_registry/img/expiration_policy_app_v13_0.png b/doc/user/packages/container_registry/img/expiration_policy_app_v13_0.png
deleted file mode 100644
index 93c9e00a8f5..00000000000
--- a/doc/user/packages/container_registry/img/expiration_policy_app_v13_0.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/packages/container_registry/index.md b/doc/user/packages/container_registry/index.md
index eb1933de62a..429d29b7677 100644
--- a/doc/user/packages/container_registry/index.md
+++ b/doc/user/packages/container_registry/index.md
@@ -71,7 +71,7 @@ This view will:
- Allow you to [delete](#delete-images-from-within-gitlab) one or more image repository.
- Allow you to navigate to the image repository details page.
- Show a **Quick start** dropdown with the most common commands to log in, build and push
-- Optionally, a banner will be visible if the [expiration policy](#expiration-policy) is enabled for this project.
+- Optionally, a banner will be visible if the [cleanup policy](#cleanup-policy) is enabled for this project.
### Control Container Registry for your group
@@ -248,10 +248,10 @@ should look similar to this:
```yaml
build:
- image: docker:19.03.11
+ image: docker:19.03.12
stage: build
services:
- - docker:19.03.11-dind
+ - docker:19.03.12-dind
script:
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
- docker build -t $CI_REGISTRY/group/project/image:latest .
@@ -262,10 +262,10 @@ You can also make use of [other variables](../../../ci/variables/README.md) to a
```yaml
build:
- image: docker:19.03.11
+ image: docker:19.03.12
stage: build
services:
- - docker:19.03.11-dind
+ - docker:19.03.12-dind
variables:
IMAGE_TAG: $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG
script:
@@ -288,9 +288,9 @@ when needed. Changes to `master` also get tagged as `latest` and deployed using
an application-specific deploy script:
```yaml
-image: docker:19.03.11
+image: docker:19.03.12
services:
- - docker:19.03.11-dind
+ - docker:19.03.12-dind
stages:
- build
@@ -363,9 +363,9 @@ Below is an example of what your `.gitlab-ci.yml` should look like:
```yaml
build:
- image: $CI_REGISTRY/group/project/docker:19.03.11
+ image: $CI_REGISTRY/group/project/docker:19.03.12
services:
- - name: $CI_REGISTRY/group/project/docker:19.03.11-dind
+ - name: $CI_REGISTRY/group/project/docker:19.03.12-dind
alias: docker
stage: build
script:
@@ -373,7 +373,7 @@ Below is an example of what your `.gitlab-ci.yml` should look like:
- docker run my-docker-image /script/to/run/tests
```
-If you forget to set the service alias, the `docker:19.03.11` image won't find the
+If you forget to set the service alias, the `docker:19.03.12` image won't find the
`dind` service, and an error like the following will be thrown:
```plaintext
@@ -443,10 +443,10 @@ stages:
- clean
build_image:
- image: docker:19.03.11
+ image: docker:19.03.12
stage: build
services:
- - docker:19.03.11-dind
+ - docker:19.03.12-dind
variables:
IMAGE_TAG: $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG
script:
@@ -459,10 +459,10 @@ build_image:
- master
delete_image:
- image: docker:19.03.11
+ image: docker:19.03.12
stage: clean
services:
- - docker:19.03.11-dind
+ - docker:19.03.12-dind
variables:
IMAGE_TAG: $CI_PROJECT_PATH:$CI_COMMIT_REF_SLUG
REG_SHA256: ade837fc5224acd8c34732bf54a94f579b47851cc6a7fd5899a98386b782e228
@@ -486,25 +486,33 @@ You can download the latest `reg` release from
the code example by changing the `REG_SHA256` and `REG_VERSION` variables
defined in the `delete_image` job.
-### Delete images using an expiration policy
+### Delete images by using a cleanup policy
-You can create a per-project [expiration policy](#expiration-policy) to ensure
-older tags and images are regularly removed from the Container Registry.
+You can create a per-project [cleanup policy](#cleanup-policy) to ensure older tags and images are regularly removed from the
+Container Registry.
-## Expiration policy
+## Cleanup policy
-> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/15398) in GitLab 12.8.
+> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/15398) in GitLab 12.8.
+> - [Renamed](https://gitlab.com/gitlab-org/gitlab/-/issues/218737) from "expiration policy" to "cleanup policy" in GitLab 13.2.
+
+For a specific project, if you want to remove tags you no longer need,
+you can create a cleanup policy. When the policy is applied, tags matching the regex pattern are removed.
+The underlying layers and images remain.
+
+To delete the underlying layers and images no longer associated with any tags, Instance Administrators can use
+[garbage collection](../../../administration/packages/container_registry.md#removing-unused-layers-not-referenced-by-manifests) with the `-m` switch.
NOTE: **Note:**
-For GitLab.com, expiration policies are not available for projects created before GitLab 12.8.
-For self-managed instances, expiration policies may be enabled by an admin in the
-[CI/CD Package Registry settings](./../../admin_area/settings/index.md#cicd).
+For GitLab.com, cleanup policies are not available for projects created
+before this feature was deployed to production (February 2020).
+Support for pre-existing projects on GitLab.com
+[is planned](https://gitlab.com/gitlab-org/gitlab/-/issues/196124).
+For self-managed instances, cleanup policies may be enabled by an admin in the
+[GitLab application settings](../../../api/settings.md#change-application-settings) by setting `container_expiration_policies_enable_historic_entries` to true.
Note the inherent [risks involved](./index.md#use-with-external-container-registries).
-It is possible to create a per-project expiration policy, so that you can make sure that
-older tags and images are regularly removed from the Container Registry.
-
-The expiration policy algorithm starts by collecting all the tags for a given repository in a list,
+The cleanup policy algorithm starts by collecting all the tags for a given repository in a list,
then goes through a process of excluding tags from it until only the ones to be deleted remain:
1. Collect all the tags for a given repository in a list.
@@ -513,43 +521,41 @@ then goes through a process of excluding tags from it until only the ones to be
1. Excludes any tags that do not have a manifest (not part of the options).
1. Orders the remaining tags by `created_date`.
1. Excludes from the list the N tags based on the `keep_n` value (Number of tags to retain).
-1. Excludes from the list the tags more recent than the `older_than` value (Expiration interval).
+1. Excludes from the list the tags more recent than the `older_than` value (Cleanup interval).
1. Excludes from the list any tags matching the `name_regex_keep` value (Images to preserve).
1. Finally, the remaining tags in the list are deleted from the Container Registry.
-### Managing project expiration policy through the UI
-
-To manage project expiration policy, navigate to **{settings}** **Settings > CI/CD > Container Registry tag expiration policy**.
+### Managing project cleanup policy through the UI
-![Expiration Policy App](img/expiration_policy_app_v13_0.png)
+To manage project cleanup policy, navigate to **{settings}** **Settings > CI/CD > Container Registry tag cleanup policy**.
The UI allows you to configure the following:
-- **Expiration policy:** enable or disable the expiration policy.
-- **Expiration interval:** how long tags are exempt from being deleted.
-- **Expiration schedule:** how often the cron job checking the tags should run.
+- **Cleanup policy:** enable or disable the cleanup policy.
+- **Cleanup interval:** how long tags are exempt from being deleted.
+- **Cleanup schedule:** how often the cron job checking the tags should run.
- **Number of tags to retain:** how many tags to _always_ keep for each image.
-- **Docker tags with names matching this regex pattern will expire:** the regex used to determine what tags should be expired. To qualify all tags for expiration, use the default value of `.*`.
+- **Docker tags with names matching this regex pattern will expire:** the regex used to determine what tags should be cleaned up. To qualify all tags for cleanup, use the default value of `.*`.
- **Docker tags with names matching this regex pattern will be preserved:** the regex used to determine what tags should be preserved. To preserve all tags, use the default value of `.*`.
-#### Troubleshooting expiration policies
+#### Troubleshooting cleanup policies
If you see the following message:
-"Something went wrong while updating the expiration policy."
+"Something went wrong while updating the cleanup policy."
Check the regex patterns to ensure they are valid.
You can use [Rubular](https://rubular.com/) to check your regex.
View some common [regex pattern examples](#regex-pattern-examples).
-### Managing project expiration policy through the API
+### Managing project cleanup policy through the API
-You can set, update, and disable the expiration policies using the GitLab API.
+You can set, update, and disable the cleanup policies using the GitLab API.
Examples:
-- Select all tags, keep at least 1 tag per image, expire any tag older than 14 days, run once a month, preserve any images with the name `master` and the policy is enabled:
+- Select all tags, keep at least 1 tag per image, clean up any tag older than 14 days, run once a month, preserve any images with the name `master` and the policy is enabled:
```shell
curl --request PUT --header 'Content-Type: application/json;charset=UTF-8' --header "PRIVATE-TOKEN: <your_access_token>" --data-binary '{"container_expiration_policy_attributes":{"cadence":"1month","enabled":true,"keep_n":1,"older_than":"14d","name_regex":"","name_regex_delete":".*","name_regex_keep":".*-master"}}' 'https://gitlab.example.com/api/v4/projects/2'
@@ -560,15 +566,15 @@ See the API documentation for further details: [Edit project](../../../api/proje
### Use with external container registries
When using an [external container registry](./../../../administration/packages/container_registry.md#use-an-external-container-registry-with-gitlab-as-an-auth-endpoint),
-running an expiration policy on a project may have some performance risks. If a project is going to run
+running a cleanup policy on a project may have some performance risks. If a project is going to run
a policy that will remove large quantities of tags (in the thousands), the GitLab background jobs that
-run the policy may get backed up or fail completely. It is recommended you only enable container expiration
+run the policy may get backed up or fail completely. It is recommended you only enable container cleanup
policies for projects that were created before GitLab 12.8 if you are confident the amount of tags
being cleaned up will be minimal.
### Regex pattern examples
-Expiration policies use regex patterns to determine which tags should be preserved or removed, both in the UI and the API.
+Cleanup policies use regex patterns to determine which tags should be preserved or removed, both in the UI and the API.
Here are examples of regex patterns you may want to use:
@@ -596,6 +602,15 @@ Here are examples of regex patterns you may want to use:
(?:v.+|master|release)
```
+## Use the Container Registry to store Helm Charts
+
+With the launch of [Helm v3](https://helm.sh/docs/topics/registries/),
+you can use the Container Registry to store Helm Charts. However, due to the way metadata is passed
+and stored by Docker, it is not possible for GitLab to parse this data and meet performance standards.
+[This epic](https://gitlab.com/groups/gitlab-org/-/epics/2313) updates the architecture of the Container Registry to support Helm Charts.
+
+You can read more about the above challenges [here](https://gitlab.com/gitlab-org/gitlab/-/issues/38047#note_298842890).
+
## Limitations
- Moving or renaming existing Container Registry repositories is not supported
@@ -603,7 +618,7 @@ once you have pushed images, because the images are signed, and the
signature includes the repository name. To move or rename a repository with a
Container Registry, you will have to delete all existing images.
- Prior to GitLab 12.10, any tags that use the same image ID as the `latest` tag
-will not be deleted by the expiration policy.
+will not be deleted by the cleanup policy.
## Troubleshooting the GitLab Container Registry
diff --git a/doc/user/packages/img/group_packages_list_v13_0.png b/doc/user/packages/img/group_packages_list_v13_0.png
deleted file mode 100644
index 8cf3fd1a131..00000000000
--- a/doc/user/packages/img/group_packages_list_v13_0.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/packages/img/package_detail_v13_0.png b/doc/user/packages/img/package_detail_v13_0.png
deleted file mode 100644
index dcfbc0a4787..00000000000
--- a/doc/user/packages/img/package_detail_v13_0.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/packages/img/project_packages_list_v13_0.png b/doc/user/packages/img/project_packages_list_v13_0.png
deleted file mode 100644
index 468a6fe6467..00000000000
--- a/doc/user/packages/img/project_packages_list_v13_0.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/packages/index.md b/doc/user/packages/index.md
index 6e59a87ae36..ab9cdc204f8 100644
--- a/doc/user/packages/index.md
+++ b/doc/user/packages/index.md
@@ -6,11 +6,11 @@ info: To determine the technical writer assigned to the Stage/Group associated w
# GitLab Package Registry
-GitLab Packages allows organizations to utilize GitLab as a private repository
-for a variety of common package managers. Users are able to build and publish
+With the GitLab Package Registry, you can use GitLab as a private or public repository
+for a variety of common package managers. You can build and publish
packages, which can be easily consumed as a dependency in downstream projects.
-The Packages feature allows GitLab to act as a repository for the following:
+GitLab acts as a repository for the following:
| Software repository | Description | Available in GitLab version |
| ------------------- | ----------- | --------------------------- |
@@ -22,87 +22,88 @@ The Packages feature allows GitLab to act as a repository for the following:
| [NuGet Repository](nuget_repository/index.md) **(PREMIUM)** | The GitLab NuGet Repository will enable every project in GitLab to have its own space to store [NuGet](https://www.nuget.org/) packages. | 12.8+ |
| [PyPi Repository](pypi_repository/index.md) **(PREMIUM)** | The GitLab PyPi Repository will enable every project in GitLab to have its own space to store [PyPi](https://pypi.org/) packages. | 12.10+ |
| [Go Proxy](go_proxy/index.md) **(PREMIUM)** | The Go proxy for GitLab enables every project in GitLab to be fetched with the [Go proxy protocol](https://proxy.golang.org/). | 13.1+ |
-| [Composer Repository](composer_repository/index.md) **(PREMIUM)** | The GitLab Composer Repository will enable every project in GitLab to have its own space to store [Composer](https://getcomposer.org/) packages. | 13.1+ |
+| [Composer Repository](composer_repository/index.md) **(PREMIUM)** | The GitLab Composer Repository will enable every project in GitLab to have its own space to store [Composer](https://getcomposer.org/) packages. | 13.2+ |
-## Enable the Package Registry for your project
+## View packages
-If you cannot find the **{package}** **Packages & Registries > Package
-Registry** entry under your project's sidebar, ensure that:
+You can view packages for your project or group.
-1. The GitLab Package Registry has been enabled by your administrator (following
- [this documentation](../../administration/packages/index.md)); and
-1. The Package Registry has been enabled for your project.
+1. Go to the project or group.
+1. Go to **{package}** **Packages & Registries > Package Registry**.
-Once an administrator has enabled the GitLab Package Registry for your GitLab
-instance, to enable Package Registry for your project:
+You can search, sort, and filter packages on this page.
-1. Go to your project's **Settings > General** page.
-1. Expand the **Visibility, project features, permissions** section and enable the
-**Packages** feature on your project.
-1. Press **Save changes** for the changes to take effect. You should now be able to
-see the **Packages & Registries > Package Registry** link in the sidebar.
+For information on how to create and upload a package, view the GitLab documentation for your package type.
-### View Packages for your project
+## Use GitLab CI/CD to build packages
-Navigating to your project's **{package}** **Packages & Registries > Package Registry** will show a list
-of all packages that have been added to your project.
+You can use [GitLab CI/CD](./../../ci/README.md) to build packages.
+For Maven and NPM packages, and Composer dependencies, you can
+authenticate with GitLab by using the `CI_JOB_TOKEN`.
-![Project Packages list](img/project_packages_list_v13_0.png)
+CI/CD templates, which you can use to get started, are in [this repo](https://gitlab.com/gitlab-org/gitlab/-/tree/master/lib/gitlab/ci/templates).
-On this page, you can:
+Learn more about [using CI/CD to build Maven packages](maven_repository/index.md#creating-maven-packages-with-gitlab-cicd)
+and [NPM packages](npm_registry/index.md#publishing-a-package-with-cicd).
-- View all the packages that have been uploaded to the project.
-- Sort the packages list by created date, version or name.
-- Filter the list by package name.
-- Change tabs to display packages of a certain type.
-- Remove a package (if you have suitable [permissions](../permissions.md)).
-- Navigate to specific package detail page.
+If you use CI/CD to build a package, extended activity
+information is displayed when you view the package details:
-### View Packages for your group
+![Package CI/CD activity](img/package_activity_v12_10.png)
-You can view all packages belonging to a group by navigating to **{package}**
-**Packages & Registries > Package Registry** from the group sidebar.
+You can view which pipeline published the package, as well as the commit and
+user who triggered it.
-![Group Packages list](img/group_packages_list_v13_0.png)
+## Download a package
-On this page, you can:
+To download a package:
-- View all the packages that have been uploaded to each of the groups projects.
-- Sort the packages list by created date, version, name or project.
-- Filter the list by package name.
-- Change tabs to display packages of a certain type.
-- Navigate to specific package detail page.
+1. Go to **{package}** **Packages & Registries > Package Registry**.
+1. Click the name of the package you want to download.
+1. In the **Activity** section, click the name of the package you want to download.
-### View additional package information
+## Delete a package
-Additional package information can be viewed by browsing to the package details
-page from the either the project or group list.
+You cannot edit a package after you publish it in the Package Registry. Instead, you
+must delete and recreate it.
-![Package detail](img/package_detail_v13_0.png)
+- You cannot delete packages from the group view. You must delete them from the project view instead.
+ See [this issue](https://gitlab.com/gitlab-org/gitlab/-/issues/227714) for details.
+- You must have suitable [permissions](../permissions.md).
-On this page you can:
+You can delete packages by using [the API](../../api/packages.md#delete-a-project-package) or the UI.
-- See the extended package information, including metadata. This is unique to
-each package type and will display different information for different types.
-- View quick installation and registry setup instructions. These are a shortcut
-for users who have already set up the Package Registry and just want quick
-installation instructions.
-- View the package activity, including when and how a package was published.
-- View and download the contents of the package. Outside of installing a
-package via a manager, you can also download the files individually.
-- Delete the package (if you have suitable [permissions](../permissions.md)).
+To delete a package in the UI:
-### Build packages via GitLab CI/CD
+1. Go to **{package}** **Packages & Registries > Package Registry**.
+1. Find the name of the package you want to delete.
+1. Click **Delete**.
-Some of the supported packages can be built via [GitLab CI/CD](./../../ci/README.md)
-using the `CI_JOB_TOKEN`. If a package is built this way, then extended activity
-information is displayed on the package details page:
+The package is permanently deleted.
-![Package CI/CD activity](img/package_activity_v12_10.png)
+## Disable the Package Registry
-You can view which pipeline published the package, as well as the commit and
-user who triggered it. To see if a package type supports being built via CI/CD,
-check the specific documentation for your package type.
+The Package Registry is automatically enabled.
+
+If you are using a self-managed instance of GitLab, your administrator can remove
+the menu item, **{package}** **Packages & Registries**, from the GitLab sidebar. For more information,
+see the [administration documentation](../../administration/packages/index.md).
+
+You can also remove the Package Registry for your project specifically:
+
+1. In your project, go to **{settings}** **Settings > General**.
+1. Expand the **Visibility, project features, permissions** section and disable the
+ **Packages** feature.
+1. Click **Save changes**.
+
+The **{package}** **Packages & Registries > Package Registry** entry is removed from the sidebar.
+
+## Package workflows
+
+Learn how to use the GitLab Package Registry to build your own custom package workflow.
+
+- [Use a project as a package registry](./workflows/project_registry.md) to publish all of your packages to one project.
+- Publish multiple different packages from one [monorepo project](./workflows/monorepo.md).
## Suggested contributions
@@ -125,10 +126,3 @@ are adding support for [PHP](https://gitlab.com/gitlab-org/gitlab/-/merge_reques
| [RubyGems](https://gitlab.com/gitlab-org/gitlab/-/issues/803) | Use GitLab to host your own gems. |
| [SBT](https://gitlab.com/gitlab-org/gitlab/-/issues/36898) | Resolve dependencies from and deploy build output to SBT repositories when running SBT builds. |
| [Vagrant](https://gitlab.com/gitlab-org/gitlab/-/issues/36899) | Securely host your Vagrant boxes in local repositories. |
-
-## Package workflows
-
-Learning how to use the GitLab Package Registry will help you build your own custom package workflow.
-
-- [Use a project as a package registry](./workflows/project_registry.md) to publish all of your packages to one project.
-- [Working with a monorepo](./workflows/monorepo.md): Learn how to publish multiple different packages from one monorepo project.
diff --git a/doc/user/packages/maven_repository/index.md b/doc/user/packages/maven_repository/index.md
index 2d40a623fb8..b194b7d837a 100644
--- a/doc/user/packages/maven_repository/index.md
+++ b/doc/user/packages/maven_repository/index.md
@@ -23,7 +23,7 @@ After the Packages feature is enabled, the Maven Repository will be available fo
all new projects by default. To enable it for existing projects, or if you want
to disable it:
-1. Navigate to your project's **Settings > General > Permissions**.
+1. Navigate to your project's **Settings > General > Visibility, project features, permissions**.
1. Find the Packages feature and enable or disable it.
1. Click on **Save changes** for the changes to take effect.
@@ -821,6 +821,16 @@ user's home location (in this case the user is `root` since it runs in a
Docker container), and Maven will use the configured CI
[environment variables](../../../ci/variables/README.md#predefined-environment-variables).
+### Version validation
+
+The version string is validated using the following regex.
+
+```ruby
+\A(\.?[\w\+-]+\.?)+\z
+```
+
+You can play around with the regex and try your version strings on [this regular expression editor](https://rubular.com/r/rrLQqUXjfKEoL6).
+
## Troubleshooting
### Useful Maven command line options
diff --git a/doc/user/packages/npm_registry/index.md b/doc/user/packages/npm_registry/index.md
index 4d60c227ccd..390b2c28e10 100644
--- a/doc/user/packages/npm_registry/index.md
+++ b/doc/user/packages/npm_registry/index.md
@@ -25,7 +25,7 @@ This option is available only if your GitLab administrator has
After the NPM registry is enabled, it will be available for all new projects
by default. To enable it for existing projects, or if you want to disable it:
-1. Navigate to your project's **Settings > General > Permissions**.
+1. Navigate to your project's **Settings > General > Visibility, project features, permissions**.
1. Find the Packages feature and enable or disable it.
1. Click on **Save changes** for the changes to take effect.
@@ -238,7 +238,8 @@ The regex that is used for naming is validating all package names from all packa
It allows for capital letters, while NPM does not, and allows for almost all of the
characters NPM allows with a few exceptions (`~` is not allowed).
-NOTE: **Note:** Capital letters are needed because the scope is required to be
+NOTE: **Note:**
+Capital letters are needed because the scope is required to be
identical to the top level namespace of the project. So, for example, if your
project path is `My-Group/project-foo`, your package must be named `@My-Group/any-package-name`.
`@my-group/any-package-name` will not work.
@@ -306,10 +307,12 @@ stages:
deploy:
stage: deploy
script:
- - echo '//gitlab.com/api/v4/projects/<your_project_id>/packages/npm/:_authToken=${CI_JOB_TOKEN}'>.npmrc
+ - echo '//gitlab.com/api/v4/projects/${CI_PROJECT_ID}/packages/npm/:_authToken=${CI_JOB_TOKEN}'>.npmrc
- npm publish
```
+Learn more about [using `CI_JOB_TOKEN` to authenticate to the GitLab NPM registry](#authenticating-with-a-ci-job-token).
+
## Troubleshooting
### Error running yarn with NPM registry
diff --git a/doc/user/packages/nuget_repository/index.md b/doc/user/packages/nuget_repository/index.md
index 6ada68dcceb..4ee5e5c4627 100644
--- a/doc/user/packages/nuget_repository/index.md
+++ b/doc/user/packages/nuget_repository/index.md
@@ -63,7 +63,7 @@ This option is available only if your GitLab administrator has
After the NuGet Repository is enabled, it will be available for all new projects
by default. To enable it for existing projects, or if you want to disable it:
-1. Navigate to your project's **Settings > General > Permissions**.
+1. Navigate to your project's **Settings > General > Visibility, project features, permissions**.
1. Find the Packages feature and enable or disable it.
1. Click on **Save changes** for the changes to take effect.
diff --git a/doc/user/packages/pypi_repository/index.md b/doc/user/packages/pypi_repository/index.md
index 6f6609d82b1..615741cc303 100644
--- a/doc/user/packages/pypi_repository/index.md
+++ b/doc/user/packages/pypi_repository/index.md
@@ -28,7 +28,7 @@ This option is available only if your GitLab administrator has
After the PyPi Repository is enabled, it will be available for all new projects
by default. To enable it for existing projects, or if you want to disable it:
-1. Navigate to your project's **Settings > General > Permissions**.
+1. Navigate to your project's **Settings > General > Visibility, project features, permissions**.
1. Find the Packages feature and enable or disable it.
1. Click on **Save changes** for the changes to take effect.
diff --git a/doc/user/permissions.md b/doc/user/permissions.md
index 21435c41d68..ba62cf81847 100644
--- a/doc/user/permissions.md
+++ b/doc/user/permissions.md
@@ -25,7 +25,7 @@ To add or import a user, you can follow the
## Principles behind permissions
-See our [product handbook on permissions](https://about.gitlab.com/handbook/product/#permissions-in-gitlab)
+See our [product handbook on permissions](https://about.gitlab.com/handbook/product/gitlab-the-product/#permissions-in-gitlab).
## Instance-wide user permissions
@@ -47,7 +47,7 @@ The following table depicts the various user permission levels in a project.
|---------------------------------------------------|---------|------------|-------------|----------|--------|
| Download project | ✓ (*1*) | ✓ | ✓ | ✓ | ✓ |
| Leave comments | ✓ (*1*) | ✓ | ✓ | ✓ | ✓ |
-| View allowed and denied licenses **(ULTIMATE)** | ✓ (*1*) | ✓ | ✓ | ✓ | ✓ |
+| View allowed and denied licenses **(ULTIMATE)** | ✓ (*1*) | ✓ | ✓ | ✓ | ✓ |
| View License Compliance reports **(ULTIMATE)** | ✓ (*1*) | ✓ | ✓ | ✓ | ✓ |
| View Security reports **(ULTIMATE)** | ✓ (*3*) | ✓ | ✓ | ✓ | ✓ |
| View Dependency list **(ULTIMATE)** | ✓ (*1*) | ✓ | ✓ | ✓ | ✓ |
@@ -86,7 +86,7 @@ The following table depicts the various user permission levels in a project.
| View metrics dashboard annotations | | ✓ | ✓ | ✓ | ✓ |
| Create/edit requirements **(ULTIMATE)** | | ✓ | ✓ | ✓ | ✓ |
| Pull [packages](packages/index.md) **(PREMIUM)** | | ✓ | ✓ | ✓ | ✓ |
-| Publish [packages](packages/index.md) **(PREMIUM)**| | | ✓ | ✓ | ✓ |
+| Publish [packages](packages/index.md) **(PREMIUM)**| | | ✓ | ✓ | ✓ |
| Upload [Design Management](project/issues/design_management.md) files | | | ✓ | ✓ | ✓ |
| Create/edit/delete [Releases](project/releases/index.md)| | | ✓ | ✓ | ✓ |
| Create new branches | | | ✓ | ✓ | ✓ |
@@ -120,6 +120,7 @@ The following table depicts the various user permission levels in a project.
| Rewrite/remove Git tags | | | ✓ | ✓ | ✓ |
| Manage Feature Flags **(PREMIUM)** | | | ✓ | ✓ | ✓ |
| Create/edit/delete metrics dashboard annotations | | | ✓ | ✓ | ✓ |
+| Run CI/CD pipeline against a protected branch | | | ✓ (*5*) | ✓ | ✓ |
| Use environment terminals | | | | ✓ | ✓ |
| Run Web IDE's Interactive Web Terminals **(ULTIMATE ONLY)** | | | | ✓ | ✓ |
| Add new team members | | | | ✓ | ✓ |
@@ -139,7 +140,10 @@ The following table depicts the various user permission levels in a project.
| Manage GitLab Pages domains and certificates | | | | ✓ | ✓ |
| Remove GitLab Pages | | | | ✓ | ✓ |
| Manage clusters | | | | ✓ | ✓ |
+| Manage Project Operations | | | | ✓ | ✓ |
| View Pods logs | | | | ✓ | ✓ |
+| Read Terraform state | | | ✓ | ✓ | ✓ |
+| Manage Terraform state | | | | ✓ | ✓ |
| Manage license policy **(ULTIMATE)** | | | | ✓ | ✓ |
| Edit comments (posted by any user) | | | | ✓ | ✓ |
| Manage Error Tracking | | | | ✓ | ✓ |
@@ -154,6 +158,7 @@ The following table depicts the various user permission levels in a project.
| Remove project | | | | | ✓ |
| Archive project | | | | | ✓ |
| Delete issues | | | | | ✓ |
+| Delete pipelines | | | | | ✓ |
| Delete merge request | | | | | ✓ |
| Disable notification emails | | | | | ✓ |
| Force push to protected branches (*4*) | | | | | |
@@ -246,6 +251,7 @@ group.
| Create project in group | | | ✓ (3) | ✓ (3) | ✓ (3) |
| Share (invite) groups with groups | | | | | ✓ |
| Create/edit/delete group milestones | | | ✓ | ✓ | ✓ |
+| Create/edit/delete iterations | | | ✓ | ✓ | ✓ |
| Enable/disable a dependency proxy **(PREMIUM)** | | | ✓ | ✓ | ✓ |
| Use security dashboard **(ULTIMATE)** | | | ✓ | ✓ | ✓ |
| Create/edit/delete metrics dashboard annotations | | | ✓ | ✓ | ✓ |
@@ -267,8 +273,8 @@ group.
| View Issues analytics **(PREMIUM)** | ✓ | ✓ | ✓ | ✓ | ✓ |
| View Productivity analytics **(PREMIUM)** | | ✓ | ✓ | ✓ | ✓ |
| View Value Stream analytics | ✓ | ✓ | ✓ | ✓ | ✓ |
-| View Billing **(FREE ONLY)** | ✓ | ✓ | ✓ | ✓ | ✓ (4) |
-| View Usage Quotas **(FREE ONLY)** | ✓ | ✓ | ✓ | ✓ | ✓ (4) |
+| View Billing **(FREE ONLY)** | | | | | ✓ (4) |
+| View Usage Quotas **(FREE ONLY)** | | | | | ✓ (4) |
1. Groups can be set to [allow either Owners or Owners and
Maintainers to create subgroups](group/subgroups/index.md#creating-a-subgroup)
@@ -281,7 +287,7 @@ group.
### Subgroup permissions
When you add a member to a subgroup, they inherit the membership and
-permission level from the parent group. This model allows access to
+permission level from the parent group(s). This model allows access to
nested groups if you have membership in one of its parents.
To learn more, read through the documentation on
diff --git a/doc/user/profile/account/delete_account.md b/doc/user/profile/account/delete_account.md
index 3c6f2989091..a70d11438f4 100644
--- a/doc/user/profile/account/delete_account.md
+++ b/doc/user/profile/account/delete_account.md
@@ -35,7 +35,8 @@ As an administrator, you can delete a user account by:
- **Delete user and contributions** to delete the user and
their associated records.
-DANGER: **Danger:** Using the **Delete user and contributions** option may result
+DANGER: **Danger:**
+Using the **Delete user and contributions** option may result
in removing more data than intended. Please see [associated records](#associated-records)
below for additional details.
diff --git a/doc/user/profile/account/two_factor_authentication.md b/doc/user/profile/account/two_factor_authentication.md
index bfcaeaf6a15..4f769f9a671 100644
--- a/doc/user/profile/account/two_factor_authentication.md
+++ b/doc/user/profile/account/two_factor_authentication.md
@@ -5,9 +5,9 @@ group: Access
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers
---
-# Two-Factor Authentication
+# Two-factor authentication
-Two-factor Authentication (2FA) provides an additional level of security to your
+Two-factor authentication (2FA) provides an additional level of security to your
GitLab account. Once enabled, in addition to supplying your username and
password to login, you'll be prompted for a code generated by your one time password
authenticator. For example, a password manager on one of your devices.
@@ -62,7 +62,7 @@ To enable 2FA:
1. Click **Submit**.
If the pin you entered was correct, you'll see a message indicating that
-Two-Factor Authentication has been enabled, and you'll be presented with a list
+two-factor authentication has been enabled, and you'll be presented with a list
of [recovery codes](#recovery-codes). Make sure you download them and keep them
in a safe place.
diff --git a/doc/user/profile/index.md b/doc/user/profile/index.md
index 663a2888ee7..7a871afd861 100644
--- a/doc/user/profile/index.md
+++ b/doc/user/profile/index.md
@@ -22,7 +22,7 @@ See the [authentication topic](../../topics/authentication/index.md) for more de
### Unknown sign-in
-GitLab will notify you if a sign-in occurs that is from an unknown IP address.
+GitLab will notify you if a sign-in occurs that is from an unknown IP address or device.
See [Unknown Sign-In Notification](unknown_sign_in_notification.md) for more details.
## User profile
diff --git a/doc/user/profile/notifications.md b/doc/user/profile/notifications.md
index ee228050945..dbf486e399e 100644
--- a/doc/user/profile/notifications.md
+++ b/doc/user/profile/notifications.md
@@ -187,7 +187,7 @@ To minimize the number of notifications that do not require any action, from [Gi
| Remove milestone merge request | Subscribers, participants mentioned, and Custom notification level with this event selected |
| New comment | The above, plus anyone mentioned by `@username` in the comment, with notification level "Mention" or higher |
| Failed pipeline | The author of the pipeline |
-| Fixed pipeline | The author of the pipeline. Disabled by default. To activate it you must [enable the `ci_pipeline_fixed_notifications` feature flag](../../development/feature_flags/development.md#enabling-a-feature-flag-in-development). |
+| Fixed pipeline | The author of the pipeline. Enabled by default. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/24309) in GitLab 13.1. Administrators can disable this notification option using the `ci_pipeline_fixed_notifications` [feature flag](../../administration/feature_flags.md). |
| Successful pipeline | The author of the pipeline, if they have the custom notification setting for successful pipelines set. If the pipeline failed previously, a `Fixed pipeline` message will be sent for the first successful pipeline after the failure, then a `Successful pipeline` message for any further successful pipelines. |
| New epic **(ULTIMATE)** | |
| Close epic **(ULTIMATE)** | |
diff --git a/doc/user/profile/personal_access_tokens.md b/doc/user/profile/personal_access_tokens.md
index e2c3dc74cf1..59ca124f566 100644
--- a/doc/user/profile/personal_access_tokens.md
+++ b/doc/user/profile/personal_access_tokens.md
@@ -19,6 +19,7 @@ Personal access tokens expire on the date you define, at midnight UTC.
- GitLab runs a check at 01:00 AM UTC every day to identify personal access tokens that will expire in under seven days. The owners of these tokens are notified by email.
- In GitLab Ultimate, administrators may [limit the lifetime of personal access tokens](../admin_area/settings/account_and_limit_settings.md#limiting-lifetime-of-personal-access-tokens-ultimate-only).
+- In GitLab Ultimate, administrators may [toggle enforcement of personal access token expiry](../admin_area/settings/account_and_limit_settings.md#optional-enforcement-of-personal-access-token-expiry-ultimate-only).
For examples of how you can use a personal access token to authenticate with the API, see the following section from our [API Docs](../../api/README.md#personalproject-access-tokens).
@@ -43,6 +44,10 @@ profile.
At any time, you can revoke any personal access token by clicking the
respective **Revoke** button under the **Active Personal Access Token** area.
+### Token activity
+
+You can see when a token was last used from the **Personal Access Tokens** page. Updates to the token usage is fixed at once per 24 hours. Requests to [API resources](../../api/api_resources.md) and the [GraphQL API](../../api/graphql/index.md) will update a token's usage.
+
## Limiting scopes of a personal access token
Personal access tokens can be created with one or more scopes that allow various
diff --git a/doc/user/profile/preferences.md b/doc/user/profile/preferences.md
index a5fa3cf373f..b94ae958d3b 100644
--- a/doc/user/profile/preferences.md
+++ b/doc/user/profile/preferences.md
@@ -63,7 +63,10 @@ Dark theme currently only works with the 'Dark' syntax highlighting.
NOTE: **Note:**
GitLab uses the [rouge Ruby library](http://rouge.jneen.net/ "Rouge website")
-for syntax highlighting. For a list of supported languages visit the rouge website.
+for syntax highlighting outside of any Editor context. The WebIDE (like Snippets)
+uses [Monaco Editor](https://microsoft.github.io/monaco-editor/) and it's provided [Monarch](https://microsoft.github.io/monaco-editor/monarch.html) library for
+syntax highlighting. For a list of supported languages, visit the documentation of
+the respective libraries.
Changing this setting allows you to customize the color theme when viewing any
syntax highlighted code on GitLab.
diff --git a/doc/user/profile/unknown_sign_in_notification.md b/doc/user/profile/unknown_sign_in_notification.md
index 200358bb050..6a6820bb2d4 100644
--- a/doc/user/profile/unknown_sign_in_notification.md
+++ b/doc/user/profile/unknown_sign_in_notification.md
@@ -9,16 +9,24 @@ info: To determine the technical writer assigned to the Stage/Group associated w
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/27211) in GitLab 13.0.
-When a user successfully signs in from a previously unknown IP address,
+NOTE: **Note:**
+This feature is enabled by default for self-managed instances. Administrators may disable this feature
+through the [Sign-in restrictions](../admin_area/settings/sign_in_restrictions.md#email-notification-for-unknown-sign-ins) section of the UI.
+The feature is always enabled on GitLab.com.
+
+When a user successfully signs in from a previously unknown IP address or device,
GitLab notifies the user by email. In this way, GitLab proactively alerts users of potentially
malicious or unauthorized sign-ins.
-There are two methods used to identify a known sign-in:
+There are several methods used to identify a known sign-in. All methods must fail
+for a notification email to be sent.
- Last sign-in IP: The current sign-in IP address is checked against the last sign-in
IP address.
- Current active sessions: If the user has an existing active session from the
same IP address. See [Active Sessions](active_sessions.md).
+- Cookie: After successful sign in, an encrypted cookie is stored in the browser.
+ This cookie is set to expire 14 days after the last successful sign in.
## Example email
diff --git a/doc/user/project/bulk_editing.md b/doc/user/project/bulk_editing.md
index 6d091c431a2..c4a6aea807c 100644
--- a/doc/user/project/bulk_editing.md
+++ b/doc/user/project/bulk_editing.md
@@ -14,7 +14,7 @@ For more details, see
If you want to update attributes across multiple issues or merge requests, you can do it
by bulk editing them, that is, editing them together.
-![Bulk editing](img/bulk-editing.png)
+![Bulk editing](img/bulk-editing_v13_2.png)
## Bulk edit issues at the project level
@@ -25,8 +25,12 @@ When bulk editing issues in a project, you can edit the following attributes:
- Status (open/closed)
- Assignee
+- Epic ([introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/210470) in
+ [GitLab Premium](https://about.gitlab.com/pricing/) 13.2.) **(PREMIUM)**
- Milestone
- Labels
+- Health status ([introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/218395) in
+ [GitLab Ultimate](https://about.gitlab.com/pricing/) 13.2.) **(ULTIMATE)**
- Subscriptions
To update multiple project issues at the same time:
diff --git a/doc/user/project/clusters/add_remove_clusters.md b/doc/user/project/clusters/add_remove_clusters.md
index d0cba729e35..65f1c59f4ca 100644
--- a/doc/user/project/clusters/add_remove_clusters.md
+++ b/doc/user/project/clusters/add_remove_clusters.md
@@ -13,6 +13,11 @@ GitLab offers integrated cluster creation for the following Kubernetes providers
GitLab can also integrate with any standard Kubernetes provider, either on-premise or hosted.
+NOTE: **Note:**
+Watch the webcast [Scalable app deployment with GitLab and Google Cloud Platform](https://about.gitlab.com/webcast/scalable-app-deploy/)
+and learn how to spin up a Kubernetes cluster managed by Google Cloud Platform (GCP)
+in a few clicks.
+
TIP: **Tip:**
Every new Google Cloud Platform (GCP) account receives [$300 in credit upon sign up](https://console.cloud.google.com/freetrial),
and in partnership with Google, GitLab is able to offer an additional $200 for new GCP accounts to get started with GitLab's
@@ -23,7 +28,7 @@ Google Kubernetes Engine Integration. All you have to do is [follow this link](h
Before [adding a Kubernetes cluster](#create-new-cluster) using GitLab, you need:
- GitLab itself. Either:
- - A GitLab.com [account](https://about.gitlab.com/pricing/#gitlab-com).
+ - A [GitLab.com account](https://about.gitlab.com/pricing/#gitlab-com).
- A [self-managed installation](https://about.gitlab.com/pricing/#self-managed) with GitLab version
12.5 or later. This will ensure the GitLab UI can be used for cluster creation.
- The following GitLab access:
@@ -52,14 +57,10 @@ to manage the newly created cluster.
NOTE: **Note:**
Restricted service account for deployment was [introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/issues/51716) in GitLab 11.5.
-When you install Helm into your cluster, the `tiller` service account
-is created with `cluster-admin` privileges in the `gitlab-managed-apps`
-namespace.
-
-This service account will be:
-
-- Added to the installed Helm Tiller.
-- Used by Helm to install and run [GitLab managed applications](index.md#installing-applications).
+The first time you install an application into your cluster, the `tiller` service
+account is created with `cluster-admin` privileges in the
+`gitlab-managed-apps` namespace. This service account will be used by Helm to
+install and run [GitLab managed applications](index.md#installing-applications).
Helm will also create additional service accounts and other resources for each
installed application. Consult the documentation of the Helm charts for each application
@@ -88,8 +89,8 @@ GitLab creates the following resources for RBAC clusters.
| `gitlab` | `ServiceAccount` | `default` namespace | Creating a new cluster |
| `gitlab-admin` | `ClusterRoleBinding` | [`cluster-admin`](https://kubernetes.io/docs/reference/access-authn-authz/rbac/#user-facing-roles) roleRef | Creating a new cluster |
| `gitlab-token` | `Secret` | Token for `gitlab` ServiceAccount | Creating a new cluster |
-| `tiller` | `ServiceAccount` | `gitlab-managed-apps` namespace | Installing Helm Tiller |
-| `tiller-admin` | `ClusterRoleBinding` | `cluster-admin` roleRef | Installing Helm Tiller |
+| `tiller` | `ServiceAccount` | `gitlab-managed-apps` namespace | Installing Helm charts |
+| `tiller-admin` | `ClusterRoleBinding` | `cluster-admin` roleRef | Installing Helm charts |
| Environment namespace | `Namespace` | Contains all environment-specific resources | Deploying to a cluster |
| Environment namespace | `ServiceAccount` | Uses namespace of environment | Deploying to a cluster |
| Environment namespace | `Secret` | Token for environment ServiceAccount | Deploying to a cluster |
@@ -103,8 +104,8 @@ GitLab creates the following resources for ABAC clusters.
|:----------------------|:---------------------|:-------------------------------------|:---------------------------|
| `gitlab` | `ServiceAccount` | `default` namespace | Creating a new cluster |
| `gitlab-token` | `Secret` | Token for `gitlab` ServiceAccount | Creating a new cluster |
-| `tiller` | `ServiceAccount` | `gitlab-managed-apps` namespace | Installing Helm Tiller |
-| `tiller-admin` | `ClusterRoleBinding` | `cluster-admin` roleRef | Installing Helm Tiller |
+| `tiller` | `ServiceAccount` | `gitlab-managed-apps` namespace | Installing Helm charts |
+| `tiller-admin` | `ClusterRoleBinding` | `cluster-admin` roleRef | Installing Helm charts |
| Environment namespace | `Namespace` | Contains all environment-specific resources | Deploying to a cluster |
| Environment namespace | `ServiceAccount` | Uses namespace of environment | Deploying to a cluster |
| Environment namespace | `Secret` | Token for environment ServiceAccount | Deploying to a cluster |
@@ -126,7 +127,7 @@ arbitrary images as they effectively have root access.
If you don't want to use GitLab Runner in privileged mode, either:
- Use shared Runners on GitLab.com. They don't have this security issue.
-- Set up your own Runners using configuration described at
+- Set up your own Runners using the configuration described at
[Shared Runners](../../gitlab_com/index.md#shared-runners). This involves:
1. Making sure that you don't have it installed via
[the applications](index.md#installing-applications).
@@ -135,23 +136,26 @@ If you don't want to use GitLab Runner in privileged mode, either:
## Create new cluster
-New clusters can be created using GitLab for:
+New clusters can be created using GitLab on Google Kubernetes Engine (GKE) or
+Amazon Elastic Kubernetes Service (EKS) at the project, group, or instance level:
-- [Google Kubernetes Engine (GKE)](add_gke_clusters.md).
-- [Amazon Elastic Kubernetes Service (EKS)](add_eks_clusters.md).
+1. Navigate to your:
+ - Project's **{cloud-gear}** **Operations > Kubernetes** page, for a project-level cluster.
+ - Group's **{cloud-gear}** **Kubernetes** page, for a group-level cluster.
+ - **{admin}** **Admin Area >** **{cloud-gear}** **Kubernetes** page, for an instance-level cluster.
+1. Click **Add Kubernetes cluster**.
+1. Click the **Create new cluster** tab.
+1. Click either **Amazon EKS** or **Google GKE**, and follow the instructions for your desired service:
+ - [Amazon EKS](add_eks_clusters.md#new-eks-cluster).
+ - [Google GKE](add_gke_clusters.md#creating-the-cluster-on-gke).
## Add existing cluster
If you have an existing Kubernetes cluster, you can add it to a project, group, or instance.
-For more information, see information for adding an:
-
-- [Existing Kubernetes cluster](#existing-kubernetes-cluster), including GKE clusters.
-- [Existing EKS cluster](add_eks_clusters.md#existing-eks-cluster).
-
NOTE: **Note:**
Kubernetes integration is not supported for arm64 clusters. See the issue
-[Helm Tiller fails to install on arm64 cluster](https://gitlab.com/gitlab-org/gitlab-foss/-/issues/64044) for details.
+[Helm Tiller fails to install on arm64 cluster](https://gitlab.com/gitlab-org/gitlab/-/issues/29838) for details.
### Existing Kubernetes cluster
@@ -214,9 +218,9 @@ To add a Kubernetes cluster to your project, group, or instance:
kind: ClusterRole
name: cluster-admin
subjects:
- - kind: ServiceAccount
- name: gitlab-admin
- namespace: kube-system
+ - kind: ServiceAccount
+ name: gitlab-admin
+ namespace: kube-system
```
1. Apply the service account and cluster role binding to your cluster:
@@ -297,14 +301,15 @@ to install some [pre-defined applications](index.md#installing-applications).
When connecting a cluster via GitLab integration, you may specify whether the
cluster is RBAC-enabled or not. This will affect how GitLab interacts with the
-cluster for certain operations. If you **did not** check the "RBAC-enabled cluster"
+cluster for certain operations. If you did *not* check the **RBAC-enabled cluster**
checkbox at creation time, GitLab will assume RBAC is disabled for your cluster
when interacting with it. If so, you must disable RBAC on your cluster for the
integration to work properly.
-![rbac](img/rbac.png)
+![rbac](img/rbac_v13_1.png)
-NOTE: **Note**: Disabling RBAC means that any application running in the cluster,
+NOTE: **Note:**
+Disabling RBAC means that any application running in the cluster,
or user who can authenticate to the cluster, has full API access. This is a
[security concern](index.md#security-implications), and may not be desirable.
@@ -320,17 +325,20 @@ kubectl create clusterrolebinding permissive-binding \
## Enabling or disabling integration
-After you have successfully added your cluster information, you can enable the
-Kubernetes cluster integration:
-
-1. Click the **Enabled/Disabled** switch
-1. Hit **Save** for the changes to take effect
+The Kubernetes cluster integration enables after you have successfully either created
+a new cluster or added an existing one. To disable Kubernetes cluster integration:
-To disable the Kubernetes cluster integration, follow the same procedure.
+1. Navigate to your:
+ - Project's **{cloud-gear}** **Operations > Kubernetes** page, for a project-level cluster.
+ - Group's **{cloud-gear}** **Kubernetes** page, for a group-level cluster.
+ - **{admin}** **Admin Area >** **{cloud-gear}** **Kubernetes** page, for an instance-level cluster.
+1. Click on the name of the cluster.
+1. Click the **GitLab Integration** toggle.
+1. Click **Save changes**.
## Removing integration
-To remove the Kubernetes cluster integration from your project, either:
+To remove the Kubernetes cluster integration from your project, first navigate to the **Advanced Settings** tab of the cluster details page and either:
- Select **Remove integration**, to remove only the Kubernetes integration.
- [From GitLab 12.6](https://gitlab.com/gitlab-org/gitlab/-/issues/26815), select
diff --git a/doc/user/project/clusters/img/rbac.png b/doc/user/project/clusters/img/rbac.png
deleted file mode 100644
index 517e4f7ca44..00000000000
--- a/doc/user/project/clusters/img/rbac.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/project/clusters/img/rbac_v13_1.png b/doc/user/project/clusters/img/rbac_v13_1.png
new file mode 100644
index 00000000000..2772af9ff89
--- /dev/null
+++ b/doc/user/project/clusters/img/rbac_v13_1.png
Binary files differ
diff --git a/doc/user/project/clusters/index.md b/doc/user/project/clusters/index.md
index 961a9fda5ff..ddcfd376d89 100644
--- a/doc/user/project/clusters/index.md
+++ b/doc/user/project/clusters/index.md
@@ -12,15 +12,6 @@ info: To determine the technical writer assigned to the Stage/Group associated w
> - [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/issues/39840) in
> GitLab 11.11 for [instances](../../instance/clusters/index.md).
-GitLab provides many features with a Kubernetes integration. Kubernetes can be
-integrated with projects, but also:
-
-- [Groups](../../group/clusters/index.md).
-- [Instances](../../instance/clusters/index.md).
-
-NOTE: **Scalable app deployment with GitLab and Google Cloud Platform**
-[Watch the webcast](https://about.gitlab.com/webcast/scalable-app-deploy/) and learn how to spin up a Kubernetes cluster managed by Google Cloud Platform (GCP) in a few clicks.
-
## Overview
Using the GitLab project Kubernetes integration, you can:
@@ -28,17 +19,26 @@ Using the GitLab project Kubernetes integration, you can:
- Use [Review Apps](../../../ci/review_apps/index.md).
- Run [pipelines](../../../ci/pipelines/index.md).
- [Deploy](#deploying-to-a-kubernetes-cluster) your applications.
-- Detect and [monitor Kubernetes](#kubernetes-monitoring).
+- Detect and [monitor Kubernetes](#monitoring-your-kubernetes-cluster).
- Use it with [Auto DevOps](#auto-devops).
- Use [Web terminals](#web-terminals).
- Use [Deploy Boards](#deploy-boards-premium). **(PREMIUM)**
- Use [Canary Deployments](#canary-deployments-premium). **(PREMIUM)**
-- View [Logs](#logs).
+- View [Logs](#viewing-pod-logs).
- Run serverless workloads on [Kubernetes with Knative](serverless/index.md).
+Besides integration at the project level, Kubernetes clusters can also be
+integrated at the [group level](../../group/clusters/index.md) or
+[GitLab instance level](../../instance/clusters/index.md).
+
+## Setting up
+
### Supported cluster versions
-GitLab is committed to support at least two production-ready Kubernetes minor versions at any given time. We regularly review the versions we support, and provide a four-month deprecation period before we remove support of a specific version. The range of supported versions is based on the evaluation of:
+GitLab is committed to support at least two production-ready Kubernetes minor
+versions at any given time. We regularly review the versions we support, and
+provide a four-month deprecation period before we remove support of a specific
+version. The range of supported versions is based on the evaluation of:
- Our own needs.
- The versions supported by major managed Kubernetes providers.
@@ -55,80 +55,83 @@ Currently, GitLab supports the following Kubernetes versions:
NOTE: **Note:**
Some GitLab features may support versions outside the range provided here.
-### Deploy Boards **(PREMIUM)**
-
-GitLab's Deploy Boards offer a consolidated view of the current health and
-status of each CI [environment](../../../ci/environments/index.md) running on Kubernetes,
-displaying the status of the pods in the deployment. Developers and other
-teammates can view the progress and status of a rollout, pod by pod, in the
-workflow they already use without any need to access Kubernetes.
-
-[Read more about Deploy Boards](../deploy_boards.md)
-
-### Canary Deployments **(PREMIUM)**
-
-Leverage [Kubernetes' Canary deployments](https://kubernetes.io/docs/concepts/cluster-administration/manage-deployment/#canary-deployments)
-and visualize your canary deployments right inside the Deploy Board, without
-the need to leave GitLab.
-
-[Read more about Canary Deployments](../canary_deployments.md)
+### Adding and removing clusters
-### Logs
-
-GitLab makes it easy to view the logs of running pods in connected Kubernetes clusters. By displaying the logs directly in GitLab, developers can avoid having to manage console tools or jump to a different interface.
-
-[Read more about Kubernetes logs](kubernetes_pod_logs.md)
+See [Adding and removing Kubernetes clusters](add_remove_clusters.md) for details on how
+to:
-### Kubernetes monitoring
+- Create a cluster in Google Cloud Platform (GCP) or Amazon Elastic Kubernetes Service
+ (EKS) using GitLab's UI.
+- Add an integration to an existing cluster from any Kubernetes platform.
-Automatically detect and monitor Kubernetes metrics. Automatic monitoring of
-[NGINX Ingress](../integrations/prometheus_library/nginx.md) is also supported.
+### Multiple Kubernetes clusters
-[Read more about Kubernetes monitoring](../integrations/prometheus_library/kubernetes.md)
+> - Introduced in [GitLab Premium](https://about.gitlab.com/pricing/) 10.3
+> - [Moved](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/35094) to GitLab core in 13.2.
-### Auto DevOps
+You can associate more than one Kubernetes cluster to your
+project. That way you can have different clusters for different environments,
+like dev, staging, production, and so on.
-Auto DevOps automatically detects, builds, tests, deploys, and monitors your
-applications.
+Simply add another cluster, like you did the first time, and make sure to
+[set an environment scope](#setting-the-environment-scope-premium) that will
+differentiate the new cluster with the rest.
-To make full use of Auto DevOps(Auto Deploy, Auto Review Apps, and Auto Monitoring)
-you will need the Kubernetes project integration enabled.
+#### Setting the environment scope **(PREMIUM)**
-[Read more about Auto DevOps](../../../topics/autodevops/index.md)
+When adding more than one Kubernetes cluster to your project, you need to differentiate
+them with an environment scope. The environment scope associates clusters with [environments](../../../ci/environments/index.md) similar to how the
+[environment-specific variables](../../../ci/variables/README.md#limit-the-environment-scopes-of-environment-variables) work.
-NOTE: **Note**
-Kubernetes clusters can be used without Auto DevOps.
+The default environment scope is `*`, which means all jobs, regardless of their
+environment, will use that cluster. Each scope can only be used by a single
+cluster in a project, and a validation error will occur if otherwise.
+Also, jobs that don't have an environment keyword set will not be able to access any cluster.
-### Web terminals
+For example, let's say the following Kubernetes clusters exist in a project:
-> Introduced in GitLab 8.15.
+| Cluster | Environment scope |
+| ----------- | ----------------- |
+| Development | `*` |
+| Production | `production` |
-When enabled, the Kubernetes integration adds [web terminal](../../../ci/environments/index.md#web-terminals)
-support to your [environments](../../../ci/environments/index.md). This is based on the `exec` functionality found in
-Docker and Kubernetes, so you get a new shell session within your existing
-containers. To use this integration, you should deploy to Kubernetes using
-the deployment variables above, ensuring any deployments, replica sets, and
-pods are annotated with:
+And the following environments are set in
+[`.gitlab-ci.yml`](../../../ci/yaml/README.md):
-- `app.gitlab.com/env: $CI_ENVIRONMENT_SLUG`
-- `app.gitlab.com/app: $CI_PROJECT_PATH_SLUG`
+```yaml
+stages:
+ - test
+ - deploy
-`$CI_ENVIRONMENT_SLUG` and `$CI_PROJECT_PATH_SLUG` are the values of
-the CI variables.
+test:
+ stage: test
+ script: sh test
-You must be the project owner or have `maintainer` permissions to use terminals. Support is limited
-to the first container in the first pod of your environment.
+deploy to staging:
+ stage: deploy
+ script: make deploy
+ environment:
+ name: staging
+ url: https://staging.example.com/
-## Adding and removing clusters
+deploy to production:
+ stage: deploy
+ script: make deploy
+ environment:
+ name: production
+ url: https://example.com/
+```
-See [Adding and removing Kubernetes clusters](add_remove_clusters.md) for details on how
-to:
+The result will then be:
-- Create a cluster in Google Cloud Platform (GCP) or Amazon Elastic Kubernetes Service
- (EKS) using GitLab's UI.
-- Add an integration to an existing cluster from any Kubernetes platform.
+- The Development cluster details will be available in the `deploy to staging`
+ job.
+- The production cluster details will be available in the `deploy to production`
+ job.
+- No cluster details will be available in the `test` job because it doesn't
+ define any environment.
-## Cluster configuration
+## Configuring your Kubernetes cluster
After [adding a Kubernetes cluster](add_remove_clusters.md) to GitLab, read this section that covers
important considerations for configuring Kubernetes clusters with GitLab.
@@ -203,72 +206,6 @@ you can either:
- Create an `A` record that points to the Ingress IP address with your domain provider.
- Enter a wildcard DNS address using a service such as nip.io or xip.io. For example, `192.168.1.1.xip.io`.
-### Setting the environment scope **(PREMIUM)**
-
-When adding more than one Kubernetes cluster to your project, you need to differentiate
-them with an environment scope. The environment scope associates clusters with [environments](../../../ci/environments/index.md) similar to how the
-[environment-specific variables](../../../ci/variables/README.md#limit-the-environment-scopes-of-environment-variables) work.
-
-The default environment scope is `*`, which means all jobs, regardless of their
-environment, will use that cluster. Each scope can only be used by a single
-cluster in a project, and a validation error will occur if otherwise.
-Also, jobs that don't have an environment keyword set will not be able to access any cluster.
-
-For example, let's say the following Kubernetes clusters exist in a project:
-
-| Cluster | Environment scope |
-| ----------- | ----------------- |
-| Development | `*` |
-| Production | `production` |
-
-And the following environments are set in
-[`.gitlab-ci.yml`](../../../ci/yaml/README.md):
-
-```yaml
-stages:
-- test
-- deploy
-
-test:
- stage: test
- script: sh test
-
-deploy to staging:
- stage: deploy
- script: make deploy
- environment:
- name: staging
- url: https://staging.example.com/
-
-deploy to production:
- stage: deploy
- script: make deploy
- environment:
- name: production
- url: https://example.com/
-```
-
-The result will then be:
-
-- The Development cluster details will be available in the `deploy to staging`
- job.
-- The production cluster details will be available in the `deploy to production`
- job.
-- No cluster details will be available in the `test` job because it doesn't
- define any environment.
-
-### Multiple Kubernetes clusters **(PREMIUM)**
-
-> Introduced in [GitLab Premium](https://about.gitlab.com/pricing/) 10.3.
-
-With GitLab Premium, you can associate more than one Kubernetes cluster to your
-project. That way you can have different clusters for different environments,
-like dev, staging, production, and so on.
-
-Simply add another cluster, like you did the first time, and make sure to
-[set an environment scope](#setting-the-environment-scope-premium) that will
-differentiate the new cluster with the rest.
-
## Installing applications
GitLab can install and manage some applications like Helm, GitLab Runner, Ingress,
@@ -277,6 +214,19 @@ installing, upgrading, uninstalling, and troubleshooting applications for
your project cluster, see
[GitLab Managed Apps](../../clusters/applications.md).
+## Auto DevOps
+
+Auto DevOps automatically detects, builds, tests, deploys, and monitors your
+applications.
+
+To make full use of Auto DevOps (Auto Deploy, Auto Review Apps, and
+Auto Monitoring) you will need the Kubernetes project integration enabled.
+
+[Read more about Auto DevOps](../../../topics/autodevops/index.md)
+
+NOTE: **Note:**
+Kubernetes clusters can be used without Auto DevOps.
+
## Deploying to a Kubernetes cluster
A Kubernetes cluster can be the destination for a deployment job. If
@@ -325,10 +275,59 @@ For **non**-GitLab-managed clusters, the namespace can be customized using
[`environment:kubernetes:namespace`](../../../ci/environments/index.md#configuring-kubernetes-deployments)
in `.gitlab-ci.yml`.
-NOTE: **Note:** When using a [GitLab-managed cluster](#gitlab-managed-clusters), the
+NOTE: **Note:**
+When using a [GitLab-managed cluster](#gitlab-managed-clusters), the
namespaces are created automatically prior to deployment and [can not be
customized](https://gitlab.com/gitlab-org/gitlab/-/issues/38054).
+### Integrations
+
+#### Canary Deployments **(PREMIUM)**
+
+Leverage [Kubernetes' Canary deployments](https://kubernetes.io/docs/concepts/cluster-administration/manage-deployment/#canary-deployments)
+and visualize your canary deployments right inside the Deploy Board, without
+the need to leave GitLab.
+
+[Read more about Canary Deployments](../canary_deployments.md)
+
+#### Deploy Boards **(PREMIUM)**
+
+GitLab's Deploy Boards offer a consolidated view of the current health and
+status of each CI [environment](../../../ci/environments/index.md) running on Kubernetes,
+displaying the status of the pods in the deployment. Developers and other
+teammates can view the progress and status of a rollout, pod by pod, in the
+workflow they already use without any need to access Kubernetes.
+
+[Read more about Deploy Boards](../deploy_boards.md)
+
+#### Viewing pod logs
+
+GitLab makes it easy to view the logs of running pods in connected Kubernetes
+clusters. By displaying the logs directly in GitLab, developers can avoid having
+to manage console tools or jump to a different interface.
+
+[Read more about Kubernetes logs](kubernetes_pod_logs.md)
+
+#### Web terminals
+
+> Introduced in GitLab 8.15.
+
+When enabled, the Kubernetes integration adds [web terminal](../../../ci/environments/index.md#web-terminals)
+support to your [environments](../../../ci/environments/index.md). This is based
+on the `exec` functionality found in Docker and Kubernetes, so you get a new
+shell session within your existing containers. To use this integration, you
+should deploy to Kubernetes using the deployment variables above, ensuring any
+deployments, replica sets, and pods are annotated with:
+
+- `app.gitlab.com/env: $CI_ENVIRONMENT_SLUG`
+- `app.gitlab.com/app: $CI_PROJECT_PATH_SLUG`
+
+`$CI_ENVIRONMENT_SLUG` and `$CI_PROJECT_PATH_SLUG` are the values of
+the CI variables.
+
+You must be the project owner or have `maintainer` permissions to use terminals.
+Support is limited to the first container in the first pod of your environment.
+
### Troubleshooting
Before the deployment jobs starts, GitLab creates the following specifically for
@@ -353,15 +352,23 @@ Reasons for failure include:
[`environment:name`](../../../ci/environments/index.md#defining-environments). If your job has no
`environment:name` set, it will not be passed the Kubernetes credentials.
-NOTE: **NOTE:**
+NOTE: **Note:**
Project-level clusters upgraded from GitLab 12.0 or older may be configured
in a way that causes this error. Ensure you deselect the
[GitLab-managed cluster](#gitlab-managed-clusters) option if you want to manage
namespaces and service accounts yourself.
-## Monitoring your Kubernetes cluster **(ULTIMATE)**
+## Monitoring your Kubernetes cluster
+
+Automatically detect and monitor Kubernetes metrics. Automatic monitoring of
+[NGINX Ingress](../integrations/prometheus_library/nginx.md) is also supported.
+
+[Read more about Kubernetes monitoring](../integrations/prometheus_library/kubernetes.md)
+
+### Visualizing cluster health
-> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/4701) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 10.6.
+> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/4701) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 10.6.
+> - [Moved](https://gitlab.com/gitlab-org/gitlab/-/issues/208224) to GitLab core in 13.2.
When [Prometheus is deployed](#installing-applications), GitLab will automatically monitor the cluster's health. At the top of the cluster settings page, CPU and Memory utilization is displayed, along with the total amount available. Keeping an eye on cluster resources can be important, if the cluster runs out of memory pods may be shutdown or fail to start.
diff --git a/doc/user/project/clusters/kubernetes_pod_logs.md b/doc/user/project/clusters/kubernetes_pod_logs.md
index 509be4ed0a8..ee642dc18cf 100644
--- a/doc/user/project/clusters/kubernetes_pod_logs.md
+++ b/doc/user/project/clusters/kubernetes_pod_logs.md
@@ -13,9 +13,9 @@ GitLab makes it easy to view the logs of running pods in [connected Kubernetes c
By displaying the logs directly in GitLab in the **Log Explorer**, developers can avoid
managing console tools or jumping to a different interface.
-NOTE: **Kubernetes + GitLab**
+NOTE: **Note:**
+[Learn more about Kubernetes + GitLab](https://about.gitlab.com/solutions/kubernetes/).
Everything you need to build, test, deploy, and run your application at scale.
-[Learn more](https://about.gitlab.com/solutions/kubernetes/).
## Overview
diff --git a/doc/user/project/clusters/runbooks/img/helm-install.png b/doc/user/project/clusters/runbooks/img/helm-install.png
deleted file mode 100644
index e67cf317ec1..00000000000
--- a/doc/user/project/clusters/runbooks/img/helm-install.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/project/clusters/runbooks/index.md b/doc/user/project/clusters/runbooks/index.md
index 92ef35ad93f..a592d59f964 100644
--- a/doc/user/project/clusters/runbooks/index.md
+++ b/doc/user/project/clusters/runbooks/index.md
@@ -42,9 +42,6 @@ To create an executable runbook, you will need:
- **Kubernetes** - A Kubernetes cluster is required to deploy the rest of the
applications. The simplest way to get started is to add a cluster using one
of [GitLab's integrations](../add_remove_clusters.md#create-new-cluster).
-- **Helm Tiller** - Helm is a package manager for Kubernetes and is required to
- install all the other applications. It's installed in its own pod inside the
- cluster which can run the Helm CLI in a safe environment.
- **Ingress** - Ingress can provide load balancing, SSL termination, and name-based
virtual hosting. It acts as a web proxy for your applications.
- **JupyterHub** - [JupyterHub](https://jupyterhub.readthedocs.io/) is a multi-user
@@ -68,13 +65,8 @@ the components outlined above and the pre-loaded demo runbook.
1. Add a Kubernetes cluster to your project by following the steps outlined in
[Create new cluster](../add_remove_clusters.md#create-new-cluster).
-1. After the cluster has been provisioned in GKE, click the **Install** button
- next to the **Helm Tiller** application to install Helm Tiller.
- ![install helm](img/helm-install.png)
-
-1. After Helm Tiller has been installed successfully, click the **Install** button next
- to the **Ingress** application.
+1. Click the **Install** button next to the **Ingress** application to install Ingress.
![install ingress](img/ingress-install.png)
diff --git a/doc/user/project/clusters/securing.md b/doc/user/project/clusters/securing.md
new file mode 100644
index 00000000000..b4c20cb8dbc
--- /dev/null
+++ b/doc/user/project/clusters/securing.md
@@ -0,0 +1,154 @@
+---
+stage: Defend
+group: Container Security
+info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers
+---
+
+# Securing your deployed applications
+
+GitLab makes it easy to secure applications deployed in [connected Kubernetes clusters](index.md).
+You can benefit from the protection of a [Web Application Firewall](../../../topics/web_application_firewall/quick_start_guide.md),
+[Network Policies](../../../topics/autodevops/stages.md#network-policy),
+or even [Container Host Security](../../clusters/applications.md#install-falco-using-gitlab-cicd).
+
+This page contains full end-to-end steps and instructions to connect your cluster to GitLab and
+install these features, whether or not your applications are deployed through GitLab CI/CD. If you
+use [Auto DevOps](../../../topics/autodevops/index.md)
+to build and deploy your application with GitLab, see the documentation for the respective
+[GitLab Managed Applications](../../clusters/applications.md)
+above.
+
+## Overview
+
+At a high level, the required steps include the following:
+
+- Connect the cluster to GitLab.
+- Set up one or more runners.
+- Set up a cluster management project.
+- Install a Web Application Firewall, Network Policies, and/or Container Host
+ Security.
+- Install Prometheus to get statistics and metrics in the
+ [threat monitoring](../../application_security/threat_monitoring/)
+ dashboard.
+
+### Requirements
+
+Minimum requirements (depending on the GitLab Manage Application you want to install):
+
+- Your cluster is connected to GitLab (ModSecurity, Cilium, and Falco).
+- At least one GitLab Runner is installed (Cilium and Falco only).
+
+### Understanding how GitLab Managed Apps are installed
+
+You install GitLab Managed Apps from the GitLab web interface with a one-click setup process. GitLab
+uses Sidekiq (a background processing service) to facilitate this.
+
+```mermaid
+ sequenceDiagram
+ autonumber
+ GitLab->>+Sidekiq: Install a GitLab Managed App
+ Sidekiq->>+Kubernetes: Helm install
+ Kubernetes-->>-Sidekiq: Installation complete
+ Sidekiq-->>-GitLab: Refresh UI
+```
+
+NOTE: **Note:**
+This diagram uses the term _Kubernetes_ for simplicity. In practice, Sidekiq connects to a Helm
+Tiller daemon running in a pod in the cluster.
+
+Although this installation method is easier because it's a point-and-click action in the user
+interface, it's inflexible and hard to debug. When something goes wrong, you can't see the
+deployment logs. The Web Application Firewall feature uses this installation method.
+
+However, the next generation of GitLab Managed Apps V2 ([CI/CD-based GitLab Managed Apps](https://gitlab.com/groups/gitlab-org/-/epics/2103))
+don't use Sidekiq to deploy. All the applications are deployed using a GitLab CI/CD pipeline and
+therefore GitLab Runners.
+
+```mermaid
+sequenceDiagram
+ autonumber
+ GitLab->>+GitLab: Trigger pipeline
+ GitLab->>+Runner: Run deployment job
+ Runner->>+Kubernetes: Helm install
+ Kubernetes-->>-Runner: Installation is complete
+ Runner-->>-GitLab: Report job status and update pipeline
+```
+
+Debugging is easier because you have access to the raw logs of these jobs (the Helm Tiller output is
+available as an artifact in case of failure) and the flexibility is much better. Since these
+deployments are only triggered when a pipeline is running (most likely when there's a new commit in
+the cluster management repository), every action has a paper trail and follows the classic merge
+request workflow (approvals, merge, deploy). The Network Policy (Cilium) Managed App and Container
+Host Security (Falco) are deployed with this model.
+
+## Connect the cluster to GitLab
+
+To deploy GitLab Managed Apps to your cluster, you must first
+[add your cluster](add_remove_clusters.md)
+to GitLab. Then [install](../../clusters/applications.md#installing-applications)
+the Web Application Firewall from the project or group Kubernetes page.
+
+Note that your project doesn't have to be hosted or deployed through GitLab. You can manage a
+cluster independent of the applications that use the cluster.
+
+## Set up a GitLab Runner
+
+To install CI/CD-based GitLab Managed Apps, a pipeline using a GitLab Runner must be running in
+GitLab. You can [install a GitLab Runner](../../clusters/applications.md#gitlab-runner)
+in the Kubernetes cluster added in the previous step, or use one of the shared runners provided by
+GitLab if you're using GitLab.com.
+
+With your cluster connected to GitLab and a GitLab Runner in place, you can proceed to the next
+steps and start installing the Cilium and Falco GitLab Managed Apps to secure your applications
+hosted on this cluster.
+
+## Create a Cluster Management Project
+
+A [Cluster Management Project](../../clusters/management_project.md)
+is a GitLab project that contains a `.gitlab-ci.yml` file to deploy GitLab Managed Apps to your
+cluster. This project runs the required charts with the Kubernetes
+[`cluster-admin`](https://kubernetes.io/docs/reference/access-authn-authz/rbac/#user-facing-roles)
+privileges.
+
+The creation of this project starts like any other GitLab project. Use an empty
+project and add a `gitlab-ci.yml` file at the root, containing this template:
+
+```yaml
+include:
+ - template: Managed-Cluster-Applications.gitlab-ci.yml
+```
+
+To make this project a Cluster Management Project, follow these
+[instructions](../../clusters/management_project.md#selecting-a-cluster-management-project).
+This project can be designated as such even if your application isn't hosted on GitLab. In this
+case, create a new empty project where you can select your newly created Cluster Management Project.
+
+## Install GitLab Container Network Policy
+
+GitLab Container Network Policy is based on [Cilium](https://cilium.io/). To
+install the Cilium GitLab Managed App, add a
+`.gitlab/managed-apps/config.yaml` file to your Cluster Management project:
+
+```yaml
+# possible values are gke, eks or you can leave it blank
+clusterType: gke
+
+cilium:
+ installed: true
+```
+
+Your application doesn't have to be managed or deployed by GitLab to leverage this feature.
+[Read more](../../clusters/applications.md#install-cilium-using-gitlab-cicd)
+about configuring Container Network Policy.
+
+## Install GitLab Container Host Security
+
+Similarly, you can install Container Host Security, based on
+[Falco](https://falco.org/), in your `.gitlab/managed-apps/config.yaml`:
+
+```yaml
+falco:
+ installed: true
+```
+
+[Read more] about configuring Container Host Security.
diff --git a/doc/user/project/clusters/serverless/aws.md b/doc/user/project/clusters/serverless/aws.md
index 15f7e14fda9..595d8fb3895 100644
--- a/doc/user/project/clusters/serverless/aws.md
+++ b/doc/user/project/clusters/serverless/aws.md
@@ -392,29 +392,19 @@ want to store your package:
image: python:latest
stages:
-
- deploy
production:
-
stage: deploy
-
before_script:
-
- pip3 install awscli --upgrade
-
- pip3 install aws-sam-cli --upgrade
-
script:
-
- sam build
-
- sam package --output-template-file packaged.yaml --s3-bucket <S3_bucket_name>
-
- sam deploy --template-file packaged.yaml --stack-name gitlabpoc --s3-bucket <S3_bucket_name> --capabilities CAPABILITY_IAM --region us-east-1
-
environment: production
- ```
+```
Let’s examine the configuration file more closely:
diff --git a/doc/user/project/clusters/serverless/index.md b/doc/user/project/clusters/serverless/index.md
index 45fb313d177..6af08b06294 100644
--- a/doc/user/project/clusters/serverless/index.md
+++ b/doc/user/project/clusters/serverless/index.md
@@ -51,8 +51,6 @@ To run Knative on GitLab, you will need:
1. **Kubernetes Cluster:** An RBAC-enabled Kubernetes cluster is required to deploy Knative.
The simplest way to get started is to add a cluster using GitLab's [GKE integration](../add_remove_clusters.md).
The set of minimum recommended cluster specifications to run Knative is 3 nodes, 6 vCPUs, and 22.50 GB memory.
-1. **Helm Tiller:** Helm is a package manager for Kubernetes and is required to install
- Knative.
1. **GitLab Runner:** A runner is required to run the CI jobs that will deploy serverless
applications or functions onto your cluster. You can install the GitLab Runner
onto the existing Kubernetes cluster. See [Installing Applications](../index.md#installing-applications) for more information.
@@ -80,8 +78,8 @@ To run Knative on GitLab, you will need:
NOTE: **Note:**
The minimum recommended cluster size to run Knative is 3-nodes, 6 vCPUs, and 22.50 GB memory. **RBAC must be enabled.**
-1. [Add a Kubernetes cluster](../add_remove_clusters.md) and [install Helm](../index.md#installing-applications).
-1. Once Helm has been successfully installed, scroll down to the Knative app section. Enter the domain to be used with
+1. [Add a Kubernetes cluster](../add_remove_clusters.md).
+1. Select the **Applications** tab and scroll down to the Knative app section. Enter the domain to be used with
your application/functions (e.g. `example.com`) and click **Install**.
![install-knative](img/install-knative.png)
@@ -143,24 +141,24 @@ You must do the following:
labels:
rbac.authorization.k8s.io/aggregate-to-edit: "true"
rules:
- - apiGroups:
- - serving.knative.dev
- resources:
- - configurations
- - configurationgenerations
- - routes
- - revisions
- - revisionuids
- - autoscalers
- - services
- verbs:
- - get
- - list
- - create
- - update
- - delete
- - patch
- - watch
+ - apiGroups:
+ - serving.knative.dev
+ resources:
+ - configurations
+ - configurationgenerations
+ - routes
+ - revisions
+ - revisionuids
+ - autoscalers
+ - services
+ verbs:
+ - get
+ - list
+ - create
+ - update
+ - delete
+ - patch
+ - watch
```
Then run the following command:
@@ -570,7 +568,7 @@ The simplest way to accomplish this is to
use [Certbot to manually obtain Let's Encrypt certificates](https://knative.dev/docs/serving/using-a-tls-cert/#using-certbot-to-manually-obtain-let-s-encrypt-certificates). Certbot is a free, open source software tool for automatically using Let’s Encrypt certificates on manually-administrated websites to enable HTTPS.
NOTE: **Note:**
-The instructions below relate to installing and running Certbot on a Linux server and may not work on other operating systems.
+The instructions below relate to installing and running Certbot on a Linux server that has Python 3 installed and may not work on other operating systems or with other versions of Python.
1. Install Certbot by running the
[`certbot-auto` wrapper script](https://certbot.eff.org/docs/install.html#certbot-auto).
@@ -580,7 +578,7 @@ The instructions below relate to installing and running Certbot on a Linux serve
wget https://dl.eff.org/certbot-auto
sudo mv certbot-auto /usr/local/bin/certbot-auto
sudo chown root /usr/local/bin/certbot-auto
- chmod 0755 /usr/local/bin/certbot-auto
+ sudo chmod 0755 /usr/local/bin/certbot-auto
/usr/local/bin/certbot-auto --help
```
@@ -609,7 +607,7 @@ The instructions below relate to installing and running Certbot on a Linux serve
using DNS challenge during authorization:
```shell
- ./certbot-auto certonly --manual --preferred-challenges dns -d '*.<namespace>.example.com'
+ /usr/local/bin/certbot-auto certonly --manual --preferred-challenges dns -d '*.<namespace>.example.com'
```
Where `<namespace>` is the namespace created by GitLab for your serverless project (composed of `<project_name>-<project_id>-<environment>`) and
@@ -835,7 +833,7 @@ The instructions below relate to installing and running Certbot on a Linux serve
## Using an older version of `gitlabktl`
There may be situations where you want to run an older version of `gitlabktl`. This
-requires setting an older version of the `gitlabktl` image in the `.gitlab-ci.yml file.`
+requires setting an older version of the `gitlabktl` image in the `.gitlab-ci.yml` file.
To set an older version, add `image:` to the `functions:deploy` block. For example:
diff --git a/doc/user/project/code_intelligence.md b/doc/user/project/code_intelligence.md
new file mode 100644
index 00000000000..e2c2cae3158
--- /dev/null
+++ b/doc/user/project/code_intelligence.md
@@ -0,0 +1,54 @@
+---
+type: reference
+---
+
+# Code Intelligence
+
+> [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/1576) in GitLab 13.1.
+
+Code Intelligence adds code navigation features common to interactive
+development environments (IDE), including:
+
+- Type signatures and symbol documentation.
+- Go-to definition
+
+Code Intelligence is built into GitLab and powered by [LSIF](https://lsif.dev/)
+(Language Server Index Format), a file format for precomputed code
+intelligence data.
+
+## Configuration
+
+Enable code intelligence for a project by adding a GitLab CI/CD job to the project's
+`.gitlab-ci.yml` which will generate the LSIF artifact:
+
+```yaml
+code_navigation:
+ image: golang:1.14.0
+ allow_failure: true # recommended
+ script:
+ - go get github.com/sourcegraph/lsif-go/cmd/lsif-go
+ - lsif-go
+ artifacts:
+ reports:
+ lsif: dump.lsif
+```
+
+The generated LSIF file must be less than 170MiB.
+
+After the job succeeds, code intelligence data can be viewed while browsing the code:
+
+![Code intelligence](img/code_intelligence_v13_1.png)
+
+## Language support
+
+Generating an LSIF file requires a language server indexer implementation for the
+relevant language.
+
+| Language | Implementation |
+|---|---|
+| Go | [sourcegraph/lsif-go](https://github.com/sourcegraph/lsif-go) |
+| JavaScript | [sourcegraph/lsif-node](https://github.com/sourcegraph/lsif-node) |
+| TypeScript | [sourcegraph/lsif-node](https://github.com/sourcegraph/lsif-node) |
+
+View a complete list of [available LSIF indexers](https://lsif.dev/#implementations-server) on their website and
+refer to their documentation to see how to generate an LSIF file for your specific language.
diff --git a/doc/user/project/code_owners.md b/doc/user/project/code_owners.md
index 6b81aea4b87..b35d04dfdfc 100644
--- a/doc/user/project/code_owners.md
+++ b/doc/user/project/code_owners.md
@@ -22,7 +22,7 @@ who is responsible for each file or path.
## Why is this useful?
-Code Owners allows for a version controlled single source of
+Code Owners allows for a version controlled, single source of
truth file outlining the exact GitLab users or groups that
own certain files or paths in a repository. Code Owners can be
utilized in the merge request approval process which can streamline
@@ -38,18 +38,18 @@ approval.
You can use a `CODEOWNERS` file to specify users or
[shared groups](members/share_project_with_groups.md)
-that are responsible for certain files in a repository.
+that are responsible for specific files and directories in a repository.
-You can choose and add the `CODEOWNERS` file in three places:
+You can choose to add the `CODEOWNERS` file in three places:
- To the root directory of the repository
- Inside the `.gitlab/` directory
- Inside the `docs/` directory
-The `CODEOWNERS` file is scoped to a branch, which means that with the
-introduction of new files, the person adding the new content can
+The `CODEOWNERS` file is scoped to a branch, which means that as
+new files are introduced, the user adding the new content can
specify themselves as a code owner, all before the new changes
-get merged to the default branch.
+get merged to the target branch.
When a file matches multiple entries in the `CODEOWNERS` file,
the users from last pattern matching the file are displayed on the
@@ -67,13 +67,13 @@ The user that would show for `README.md` would be `@user2`.
## Approvals by Code Owners
-Once you've set Code Owners to a project, you can configure it to
+Once you've added Code Owners to a project, you can configure it to
be used for merge request approvals:
- As [merge request eligible approvers](merge_requests/merge_request_approvals.md#code-owners-as-eligible-approvers).
- As required approvers for [protected branches](protected_branches.md#protected-branches-approval-by-code-owners-premium). **(PREMIUM)**
-NOTE: **Note**:
+NOTE: **Note:**
Developer or higher [permissions](../permissions.md) are required in order to
approve a merge request.
@@ -81,7 +81,7 @@ Once set, Code Owners are displayed in merge requests widgets:
![MR widget - Code Owners](img/code_owners_mr_widget_v12_4.png)
-While the `CODEOWNERS` file can be used in addition to Merge Request [Approval Rules](merge_requests/merge_request_approvals.md#approval-rules)
+While the `CODEOWNERS` file can be used in addition to Merge Request [Approval Rules](merge_requests/merge_request_approvals.md#approval-rules),
it can also be used as the sole driver of merge request approvals
(without using [Approval Rules](merge_requests/merge_request_approvals.md#approval-rules)).
To do so, create the file in one of the three locations specified above and
@@ -89,28 +89,38 @@ set the code owners as required approvers for [protected branches](protected_bra
Use [the syntax of Code Owners files](code_owners.md#the-syntax-of-code-owners-files)
to specify the actual owners and granular permissions.
-Using Code Owners in conjunction with [Protected Branches Approvals](protected_branches.md#protected-branches-approval-by-code-owners-premium)
-will prevent any user who is not specified in the `CODEOWNERS` file from pushing changes
-for the specified files/paths, even if their role is included in the **Allowed to push** column.
-This allows for a more inclusive push strategy, as administrators don't have to restrict developers
-from pushing directly to the protected branch, but can restrict pushing to certain
-files where a review by Code Owners is required.
+Using Code Owners in conjunction with [Protected Branches](protected_branches.md#protected-branches-approval-by-code-owners-premium)
+will prevent any user who is not specified in the `CODEOWNERS` file from pushing
+changes for the specified files/paths, even if their role is included in the
+**Allowed to push** column. This allows for a more inclusive push strategy, as
+administrators don't have to restrict developers from pushing directly to the
+protected branch, but can restrict pushing to certain files where a review by
+Code Owners is required.
## The syntax of Code Owners files
Files can be specified using the same kind of patterns you would use
-in the `.gitignore` file followed by the `@username` or email of one
-or more users or by the `@name` of one or more groups that should
-be owners of the file. Groups must be added as [members of the project](members/index.md),
+in the `.gitignore` file followed by one or more of:
+
+- A user's `@username`.
+- A user's email address.
+- The `@name` of one or more groups that should be owners of the file.
+
+Groups must be added as [members of the project](members/index.md),
or they will be ignored.
-Starting in [GitLab 13.0](https://gitlab.com/gitlab-org/gitlab/-/issues/32432), you can now specify
-groups or subgroups from the project's group hierarchy as potential code owners.
+Starting in [GitLab 13.0](https://gitlab.com/gitlab-org/gitlab/-/issues/32432),
+you can additionally specify groups or subgroups from the project's upper group
+hierarchy as potential code owners, without having to invite them specifically
+to the project. Groups outside the project's hierarchy or children beneath the
+hierarchy must still be explicitly invited to the project in order to show as
+Code Owners.
-For example, consider the following hierarchy for a given project:
+For example, consider the following hierarchy for the example project
+`example_project`:
```plaintext
-group >> sub-group >> sub-subgroup >> myproject >> file.md
+group >> sub-group >> sub-subgroup >> example_project >> file.md
```
Any of the following groups would be eligible to be specified as code owners:
@@ -119,7 +129,8 @@ Any of the following groups would be eligible to be specified as code owners:
- `@group/sub-group`
- `@group/sub-group/sub-subgroup`
-In addition, any groups that have been invited to the project using the **Members** tool will also be recognized as eligible code owners.
+In addition, any groups that have been invited to the project using the
+**Members** tool will also be recognized as eligible code owners.
The order in which the paths are defined is significant: the last
pattern that matches a given path will be used to find the code
@@ -129,7 +140,98 @@ Starting a line with a `#` indicates a comment. This needs to be
escaped using `\#` to address files for which the name starts with a
`#`.
-Example `CODEOWNERS` file:
+### Code Owners Sections **(PREMIUM)**
+
+> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/12137) in [GitLab Premium](https://about.gitlab.com/pricing/) 13.2.
+> - It's deployed behind a feature flag, enabled by default.
+> - It's enabled on GitLab.com.
+> - It can be enabled or disabled per-project.
+> - It's recommended for production use.
+> - For GitLab self-managed instances, GitLab administrators can opt to [disable it](#enable-or-disable-code-owner-sections-core-only). **(CORE ONLY)**
+
+Code Owner rules can be grouped into named sections. This allows for better
+organization of broader categories of Code Owner rules to be applied.
+Additionally, the usual guidance that only the last pattern matching the file is
+applied is expanded such that the last pattern matching _for each section_ is
+applied.
+
+For example, in a large organization, independent teams may have a common interest
+in parts of the application, for instance, a payment processing company may have
+"development", "security", and "compliance" teams looking after common parts of
+the codebase. All three teams may need to approve changes. Although approval rules
+make this possible, they apply to every merge request. Also, while Code Owners are
+applied based on which files are changed, only one CODEOWNERS pattern can match per
+file path.
+
+Using `CODEOWNERS` sections allows multiple teams that only need to approve certain
+changes, to set their own independent patterns by specifying discrete sections in the
+`CODEOWNERS` file. The section rules may be used for shared paths so that multiple
+teams can be added as reviewers.
+
+Sections can be added to `CODEOWNERS` files as a new line with the name of the
+section inside square brackets. Every entry following it is assigned to that
+section. The following example would create 2 Code Owner rules for the "README
+Owners" section:
+
+```plaintext
+[README Owners]
+README.md @user1 @user 2
+internal/README.md @user2
+```
+
+Multiple sections can be used, even with matching file or directory patterns.
+Reusing the same section name will group the results together under the same
+section, with the most specific rule or last matching pattern being used. For
+example, consider the following entries in a `CODEOWNERS` file:
+
+```plaintext
+[Documentation]
+ee/docs @gl-docs
+docs @gl-docs
+
+[Database]
+README.md @gl-database
+model/db @gl-database
+
+[DOCUMENTATION]
+README.md @gl-docs
+```
+
+This will result in 3 entries under the "Documentation" section header, and 2
+entries under "Database". Case is not considered when combining sections, so in
+this example, entries defined under the sections "Documentation" and
+"DOCUMENTATION" would be combined into one, using the case of the first instance
+of the section encountered in the file.
+
+When assigned to a section, each code owner rule displayed in merge requests
+widget is sorted under a "section" label. In the screenshot below, we can see
+the rules for "Groups" and "Documentation" sections:
+
+![MR widget - Sectional Code Owners](img/sectional_code_owners_v13.2.png)
+
+#### Enable or disable Code Owner Sections **(CORE ONLY)**
+
+Sections is under development but ready for production use.
+It is deployed behind a feature flag that is **enabled by default**.
+[GitLab administrators with access to the GitLab Rails console](../../administration/feature_flags.md)
+can opt to disable it for your instance.
+
+To disable it:
+
+```ruby
+Feature.disable(:sectional_codeowners)
+```
+
+To enable it:
+
+```ruby
+Feature.enable(:sectional_codeowners)
+```
+
+CAUTION: **Caution:**
+Disabling Sections will **not** refresh Code Owner Approval Rules on existing merge requests.
+
+## Example `CODEOWNERS` file
```plaintext
# This is an example of a code owners file
@@ -185,4 +287,17 @@ lib/ @lib-owner
# If the path contains spaces, these need to be escaped like this:
path\ with\ spaces/ @space-owner
+
+# Code Owners section:
+[Documentation]
+ee/docs @gl-docs
+docs @gl-docs
+
+[Database]
+README.md @gl-database
+model/db @gl-database
+
+# This section will be joined with the [Documentation] section previously defined:
+[DOCUMENTATION]
+README.md @gl-docs
```
diff --git a/doc/user/project/deploy_tokens/index.md b/doc/user/project/deploy_tokens/index.md
index 10f0281d0eb..b7daca567f4 100644
--- a/doc/user/project/deploy_tokens/index.md
+++ b/doc/user/project/deploy_tokens/index.md
@@ -11,7 +11,7 @@ type: howto
> - [Moved](https://gitlab.com/gitlab-org/gitlab/-/issues/199370) from **Settings > Repository** in GitLab 12.9.
> - [Added `write_registry` scope](https://gitlab.com/gitlab-org/gitlab/-/issues/22743) in GitLab 12.10.
> - [Moved](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/29280) from **Settings > CI / CD** in GitLab 12.10.1.
-> - [Added package registry scopes](https://gitlab.com/gitlab-org/gitlab/-/issues/213566) from **Settings > CI / CD** in GitLab 13.0.
+> - [Added package registry scopes](https://gitlab.com/gitlab-org/gitlab/-/issues/213566) in GitLab 13.0.
Deploy tokens allow you to download (`git clone`) or push and pull packages and container registry images of a project without having a user and a password.
@@ -46,15 +46,17 @@ respective **Revoke** button under the 'Active deploy tokens' area.
## Limiting scopes of a deploy token
-Deploy tokens can be created with two different scopes that allow various
+Deploy tokens can be created with different scopes that allow various
actions that a given token can perform. The available scopes are depicted in
-the following table.
+the following table along with GitLab version it was introduced in.
-| Scope | Description |
-| ----- | ----------- |
-| `read_repository` | Allows read-access to the repository through `git clone` |
-| `read_registry` | Allows read-access to [container registry](../../packages/container_registry/index.md) images if a project is private and authorization is required. |
-| `write_registry` | Allows write-access (push) to [container registry](../../packages/container_registry/index.md). |
+| Scope | Description | Introduced in GitLab Version |
+| ----- | ----------- | ------ |
+| `read_repository` | Allows read-access to the repository through `git clone` | 10.7 |
+| `read_registry` | Allows read-access to [container registry](../../packages/container_registry/index.md) images if a project is private and authorization is required. | 10.7 |
+| `write_registry` | Allows write-access (push) to [container registry](../../packages/container_registry/index.md). | 12.10 |
+| `read_package_registry` | Allows read access to the package registry. | 13.0 |
+| `write_package_registry` | Allows write access to the package registry. | 13.0 |
## Deploy token custom username
@@ -96,6 +98,8 @@ pull images from your Container Registry.
### Push Container Registry images
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/22743) in GitLab 12.10.
+
To push the container registry images, you'll need to:
1. Create a Deploy Token with `write_registry` as a scope.
@@ -111,6 +115,8 @@ push images to your Container Registry.
### Read or pull packages
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/213566) in GitLab 13.0.
+
To pull packages in the GitLab package registry, you'll need to:
1. Create a Deploy Token with `read_package_registry` as a scope.
@@ -119,6 +125,8 @@ To pull packages in the GitLab package registry, you'll need to:
### Push or upload packages
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/213566) in GitLab 13.0.
+
To upload packages in the GitLab package registry, you'll need to:
1. Create a Deploy Token with `write_package_registry` as a scope.
@@ -151,8 +159,7 @@ apply consistently when cloning the repository of related projects.
There's a special case when it comes to Deploy Tokens. If a user creates one
named `gitlab-deploy-token`, the username and token of the Deploy Token will be
automatically exposed to the CI/CD jobs as environment variables: `CI_DEPLOY_USER` and
-`CI_DEPLOY_PASSWORD`, respectively. With the GitLab Deploy Token, the
-`read_registry` and `write_registry` scopes are implied.
+`CI_DEPLOY_PASSWORD`, respectively.
After you create the token, you can login to the Container Registry using
those variables:
diff --git a/doc/user/project/description_templates.md b/doc/user/project/description_templates.md
index 0f90c321a14..aa5987bf5f9 100644
--- a/doc/user/project/description_templates.md
+++ b/doc/user/project/description_templates.md
@@ -81,7 +81,7 @@ changes you made after picking the template and return it to its initial status.
![Description templates](img/description_templates.png)
-## Setting a default template for merge requests and issues **(STARTER)**
+## Setting a default template for merge requests and issues **(STARTER)**
> - This feature was introduced before [description templates](#overview) and is available in [GitLab Starter](https://about.gitlab.com/pricing/). It can be enabled in the project's settings.
> - Templates for issues were [introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/28) in GitLab EE 8.1.
diff --git a/doc/user/project/highlighting.md b/doc/user/project/highlighting.md
index 2bdb0ae2706..a2740294e62 100644
--- a/doc/user/project/highlighting.md
+++ b/doc/user/project/highlighting.md
@@ -1,6 +1,11 @@
# Syntax Highlighting
-GitLab provides syntax highlighting on all files and snippets through the [Rouge](https://rubygems.org/gems/rouge) rubygem. It will try to guess what language to use based on the file extension, which most of the time is sufficient.
+GitLab provides syntax highlighting on all files through the [Rouge](https://rubygems.org/gems/rouge) Ruby gem. It will try to guess what language to use based on the file extension, which most of the time is sufficient.
+
+NOTE: **Note:**
+The [Web IDE](web_ide/index.md) and [Snippets](../snippets.md) use [Monaco Editor](https://microsoft.github.io/monaco-editor/)
+for text editing, which internally uses the [Monarch](https://microsoft.github.io/monaco-editor/monarch.html)
+library for syntax highlighting.
If GitLab is guessing wrong, you can override its choice of language using the `gitlab-language` attribute in `.gitattributes`. For example, if you are working in a Prolog project and using the `.pl` file extension (which would normally be highlighted as Perl), you can add the following to your `.gitattributes` file:
@@ -27,3 +32,6 @@ To disable highlighting entirely, use `gitlab-language=text`. Lots more fun shen
```
Please note that these configurations will only take effect when the `.gitattributes` file is in your default branch (usually `master`).
+
+NOTE: **Note:**
+The Web IDE does not support `.gitattribute` files, but it's [planned for a future release](https://gitlab.com/gitlab-org/gitlab/-/issues/22014).
diff --git a/doc/user/project/img/bulk-editing.png b/doc/user/project/img/bulk-editing.png
deleted file mode 100644
index 8ae649e5020..00000000000
--- a/doc/user/project/img/bulk-editing.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/project/img/bulk-editing_v13_2.png b/doc/user/project/img/bulk-editing_v13_2.png
new file mode 100644
index 00000000000..871cb02c01f
--- /dev/null
+++ b/doc/user/project/img/bulk-editing_v13_2.png
Binary files differ
diff --git a/doc/user/project/img/code_intelligence_v13_1.png b/doc/user/project/img/code_intelligence_v13_1.png
new file mode 100644
index 00000000000..0dff27bab43
--- /dev/null
+++ b/doc/user/project/img/code_intelligence_v13_1.png
Binary files differ
diff --git a/doc/user/project/img/sectional_code_owners_v13.2.png b/doc/user/project/img/sectional_code_owners_v13.2.png
new file mode 100644
index 00000000000..894771c26af
--- /dev/null
+++ b/doc/user/project/img/sectional_code_owners_v13.2.png
Binary files differ
diff --git a/doc/user/project/import/gemnasium.md b/doc/user/project/import/gemnasium.md
index 0d6e059f1cf..3838289aec4 100644
--- a/doc/user/project/import/gemnasium.md
+++ b/doc/user/project/import/gemnasium.md
@@ -105,7 +105,7 @@ back to both GitLab and GitHub when completed.
1. The result of the job will be visible directly from the pipeline view:
- ![Security Dashboard](../../application_security/security_dashboard/img/pipeline_security_dashboard_v12_6.png)
+ ![Security Dashboard](../../application_security/security_dashboard/img/pipeline_security_dashboard_v13_2.png)
NOTE: **Note:**
If you don't commit very often to your project, you may want to use
diff --git a/doc/user/project/import/img/jira/import_issues_from_jira_button_v12_10.png b/doc/user/project/import/img/jira/import_issues_from_jira_button_v12_10.png
index 4ab42485d0b..3c1dc44df93 100644
--- a/doc/user/project/import/img/jira/import_issues_from_jira_button_v12_10.png
+++ b/doc/user/project/import/img/jira/import_issues_from_jira_button_v12_10.png
Binary files differ
diff --git a/doc/user/project/import/img/jira/import_issues_from_jira_form_v12_10.png b/doc/user/project/import/img/jira/import_issues_from_jira_form_v12_10.png
index 6278cb5f970..d98ad2aaa6e 100644
--- a/doc/user/project/import/img/jira/import_issues_from_jira_form_v12_10.png
+++ b/doc/user/project/import/img/jira/import_issues_from_jira_form_v12_10.png
Binary files differ
diff --git a/doc/user/project/import/img/jira/import_issues_from_jira_form_v13_2.png b/doc/user/project/import/img/jira/import_issues_from_jira_form_v13_2.png
new file mode 100644
index 00000000000..9cbffe2bb36
--- /dev/null
+++ b/doc/user/project/import/img/jira/import_issues_from_jira_form_v13_2.png
Binary files differ
diff --git a/doc/user/project/import/img/jira/import_issues_from_jira_projects_v12_10.png b/doc/user/project/import/img/jira/import_issues_from_jira_projects_v12_10.png
deleted file mode 100644
index bf9728e0311..00000000000
--- a/doc/user/project/import/img/jira/import_issues_from_jira_projects_v12_10.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/project/import/jira.md b/doc/user/project/import/jira.md
index 0b8807bb9b3..395cca4726d 100644
--- a/doc/user/project/import/jira.md
+++ b/doc/user/project/import/jira.md
@@ -40,6 +40,8 @@ Make sure you have the integration set up before trying to import Jira issues.
## Import Jira issues to GitLab
+> New import form [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/216145) in GitLab 13.2.
+
To import Jira issues to a GitLab project, follow the steps below.
NOTE: **Note:**
@@ -47,27 +49,34 @@ Importing Jira issues is done as an asynchronous background job, which
may result in delays based on import queues load, system load, or other factors.
Importing large projects may take several minutes depending on the size of the import.
-1. On the **{issues}** **Issues** page, click the **Import Issues** (**{import}**) button.
-1. Select **Import from Jira**.
- This option is only visible if you have the [correct permissions](#permissions).
+1. On the **{issues}** **Issues** page, click **Import Issues** (**{import}**) **> Import from Jira**.
![Import issues from Jira button](img/jira/import_issues_from_jira_button_v12_10.png)
+ The **Import from Jira** option is only visible if you have the [correct permissions](#permissions).
+
The following form appears.
+ If you've previously set up the [Jira integration](../integrations/jira.md), you can now see
+ the Jira projects that you have access to in the dropdown.
+
+ ![Import issues from Jira form](img/jira/import_issues_from_jira_form_v13_2.png)
- ![Import issues from Jira form](img/jira/import_issues_from_jira_form_v12_10.png)
+1. Click the **Import from** dropdown and select the Jira project that you wish to import issues from.
- If you've previously set up the [Jira integration](../integrations/jira.md), you now see the Jira
- projects that you have access to in the dropdown.
+ In the **Jira-GitLab user mapping template** section, the table shows to which GitLab users your Jira
+ users will be mapped.
+ If it wasn't possible to match a Jira user with a GitLab user, the dropdown defaults to the user
+ conducting the import.
-1. Select the Jira project that you wish to import issues from.
+1. To change any of the suggested mappings, click the dropdown in the **GitLab username** column and
+ select the user you want to map to each Jira user.
- ![Import issues from Jira form](img/jira/import_issues_from_jira_projects_v12_10.png)
+ The dropdown may not show all the users, so use the search bar to find a specific
+ user in this GitLab project.
+
+1. Click **Continue**. You're presented with a confirmation that import has started.
-1. Click **Import Issues**. You're presented with a confirmation that import has started.
While the import is running in the background, you can navigate away from the import status page
to the issues page, and you'll see the new issues appearing in the issues list.
-1. To check the status of your import, go back to the Jira import page.
-
- ![Import issues from Jira button](img/jira/import_issues_from_jira_button_v12_10.png)
+1. To check the status of your import, go to the Jira import page again.
diff --git a/doc/user/project/index.md b/doc/user/project/index.md
index 3a4e240fb6c..1e71674c16f 100644
--- a/doc/user/project/index.md
+++ b/doc/user/project/index.md
@@ -78,7 +78,7 @@ When you create a project in GitLab, you'll have access to a large number of
timeout (defines the maximum amount of time in minutes that a job is able run), custom path for `.gitlab-ci.yml`, test coverage parsing, pipeline's visibility, and much more
- [Kubernetes cluster integration](clusters/index.md): Connecting your GitLab project
with a Kubernetes cluster
- - [Feature Flags](operations/feature_flags.md): Feature flags allow you to ship a project in
+ - [Feature Flags](../../operations/feature_flags.md): Feature flags allow you to ship a project in
different flavors by dynamically toggling certain functionality **(PREMIUM)**
- [GitLab Pages](pages/index.md): Build, test, and deploy your static
website with GitLab Pages
@@ -104,6 +104,7 @@ When you create a project in GitLab, you'll have access to a large number of
- [Dependency List](../application_security/dependency_list/index.md): view project dependencies. **(ULTIMATE)**
- [Requirements](requirements/index.md): Requirements allow you to create criteria to check your products against. **(ULTIMATE)**
- [Static Site Editor](static_site_editor/index.md): quickly edit content on static websites without prior knowledge of the codebase or Git commands.
+- [Code Intelligence](code_intelligence.md): code navigation features.
### Project integrations
@@ -172,6 +173,24 @@ Read through the documentation on [project settings](settings/index.md).
- [Export a project from GitLab](settings/import_export.md#exporting-a-project-and-its-data)
- [Importing and exporting projects between GitLab instances](settings/import_export.md)
+## Remove a project
+
+To remove a project, first navigate to the home page for that project.
+
+1. Navigate to **Settings > General**.
+1. Expand the **Advanced** section.
+1. Scroll down to the **Remove project** section.
+1. Click **Remove project**
+1. Confirm this action by typing in the expected text.
+
+### Delayed removal **(PREMIUM)**
+
+By default, clicking to remove a project is followed by a seven day delay. Admins can restore the project during this period of time.
+This delay [may be changed by an admin](../admin_area/settings/visibility_and_access_controls.md#default-deletion-adjourned-period-premium-only).
+
+Admins can view all projects pending deletion. If you're an administrator, go to the top navigation bar, click **Projects > Your projects**, and then select the **Removed projects** tab.
+From this tab an admin can restore any project.
+
## CI/CD for external repositories **(PREMIUM)**
Instead of importing a repository directly to GitLab, you can connect your repository
@@ -242,14 +261,52 @@ When [renaming a user](../profile/index.md#changing-your-username),
## Use your project as a Go package
-Any project can be used as a Go package including private projects in subgroups.
-GitLab responds correctly to `go get` and `godoc.org` discovery requests,
-including the [`go-import`](https://golang.org/cmd/go/#hdr-Remote_import_paths)
-and [`go-source`](https://github.com/golang/gddo/wiki/Source-Code-Links) meta
-tags, respectively. To use packages hosted in private projects with the `go get`
-command, use a [`.netrc` file](https://ec.haxx.se/usingcurl-netrc.html) and a
-[personal access token](../profile/personal_access_tokens.md) in the password
-field.
+Any project can be used as a Go package. GitLab responds correctly to `go get`
+and `godoc.org` discovery requests, including the
+[`go-import`](https://golang.org/cmd/go/#hdr-Remote_import_paths) and
+[`go-source`](https://github.com/golang/gddo/wiki/Source-Code-Links) meta tags.
+
+Private projects, including projects in subgroups, can be used as a Go package,
+but may require configuration to work correctly. GitLab will respond correctly
+to `go get` discovery requests for projects that *are not* in subgroups,
+regardless of authentication or authorization.
+[Authentication](#authenticate-go-requests) is required to use a private project
+in a subgroup as a Go package. Otherwise, GitLab will truncate the path for
+private projects in subgroups to the first two segments, causing `go get` to
+fail.
+
+GitLab implements its own Go proxy. This feature must be enabled by an
+administrator and requires additional configuration. See [GitLab Go
+Proxy](../packages/go_proxy/index.md).
+
+### Disable Go module features for private projects
+
+In Go 1.12 and later, Go queries module proxies and checksum databases in the
+process of [fetching a
+module](../../development/go_guide/dependencies.md#fetching). This can be
+selectively disabled with `GOPRIVATE` (disable both),
+[`GONOPROXY`](../../development/go_guide/dependencies.md#proxies) (disable proxy
+queries), and [`GONOSUMDB`](../../development/go_guide/dependencies.md#fetching)
+(disable checksum queries).
+
+`GOPRIVATE`, `GONOPROXY`, and `GONOSUMDB` are comma-separated lists of Go
+modules and Go module prefixes. For example,
+`GOPRIVATE=gitlab.example.com/my/private/project` will disable queries for that
+one project, but `GOPRIVATE=gitlab.example.com` will disable queries for *all*
+projects on GitLab.com. Go will not query module proxies if the module name or a
+prefix of it appears in `GOPRIVATE` or `GONOPROXY`. Go will not query checksum
+databases if the module name or a prefix of it appears in `GONOPRIVATE` or
+`GONOSUMDB`.
+
+### Authenticate Go requests
+
+To authenticate requests to private projects made by Go, use a [`.netrc`
+file](https://ec.haxx.se/usingcurl-netrc.html) and a [personal access
+token](../profile/personal_access_tokens.md) in the password field. **This only
+works if your GitLab instance can be accessed with HTTPS.** The `go` command
+will not transmit credentials over insecure connections. This will authenticate
+all HTTPS requests made directly by Go but will not authenticate requests made
+through Git.
For example:
@@ -259,6 +316,24 @@ login <gitlab_user_name>
password <personal_access_token>
```
+NOTE: **Note:**
+On Windows, Go reads `~/_netrc` instead of `~/.netrc`.
+
+### Authenticate Git fetches
+
+If a module cannot be fetched from a proxy, Go will fall back to using Git (for
+GitLab projects). Git will use `.netrc` to authenticate requests. Alternatively,
+Git can be configured to embed specific credentials in the request URL, or to
+use SSH instead of HTTPS (as Go always uses HTTPS to fetch Git repositories):
+
+```shell
+# embed credentials in any request to GitLab.com:
+git config --global url."https://${user}:${personal_access_token}@gitlab.example.com".insteadOf "https://gitlab.example.com"
+
+# use SSH instead of HTTPS:
+git config --global url."git@gitlab.example.com".insteadOf "https://gitlab.example.com"
+```
+
## Access project page with project ID
> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/issues/53671) in GitLab 11.8.
diff --git a/doc/user/project/insights/index.md b/doc/user/project/insights/index.md
index 3cc76beb323..4d646ee2f79 100644
--- a/doc/user/project/insights/index.md
+++ b/doc/user/project/insights/index.md
@@ -271,6 +271,27 @@ you defined.
| `week` | 4 |
| `month` | 12 |
+#### `query.period_field`
+
+Define the timestamp field used to group "issuables".
+
+Supported values are:
+
+- `created_at` (default): Group data using the `created_at` field.
+- `closed_at`: Group data using the `closed_at` field (for issues only).
+- `merged_at`: Group data using the `merged_at` field (for merge requests only).
+
+The `period_field` is automatically set to:
+
+- `closed_at` if `query.issuable_state` is `closed`
+- `merged_at` if `query.issuable_state` is `merged`
+- `created_at` otherwise
+
+NOTE: **Note:**
+Until [this bug](https://gitlab.com/gitlab-org/gitlab/-/issues/26911), is resolved, you may see `created_at`
+in place of `merged_at`.
+`created_at` will be used instead.
+
### `projects`
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/10904) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 12.4.
diff --git a/doc/user/project/integrations/bugzilla.md b/doc/user/project/integrations/bugzilla.md
index de43c504b05..6d44c56743e 100644
--- a/doc/user/project/integrations/bugzilla.md
+++ b/doc/user/project/integrations/bugzilla.md
@@ -6,7 +6,6 @@ in the table below.
| Field | Description |
| ----- | ----------- |
-| `description` | A name for the issue tracker (to differentiate between instances, for example) |
| `project_url` | The URL to the project in Bugzilla which is being linked to this GitLab project. Note that the `project_url` requires PRODUCT_NAME to be updated with the product/project name in Bugzilla. |
| `issues_url` | The URL to the issue in Bugzilla project that is linked to this GitLab project. Note that the `issues_url` requires `:id` in the URL. This ID is used by GitLab as a placeholder to replace the issue number. |
| `new_issue_url` | This is the URL to create a new issue in Bugzilla for the project linked to this GitLab project. Note that the `new_issue_url` requires PRODUCT_NAME to be updated with the product/project name in Bugzilla. |
diff --git a/doc/user/project/integrations/custom_issue_tracker.md b/doc/user/project/integrations/custom_issue_tracker.md
index 4eaf3a0d4b4..7d15ae82b6f 100644
--- a/doc/user/project/integrations/custom_issue_tracker.md
+++ b/doc/user/project/integrations/custom_issue_tracker.md
@@ -11,8 +11,6 @@ To enable the Custom Issue Tracker integration in a project:
| Field | Description |
| --------------- | ----------- |
- | **Title** | A title for the issue tracker (for example, to differentiate between instances). |
- | **Description** | A name for the issue tracker (for example, to differentiate between instances). |
| **Project URL** | The URL to the project in the custom issue tracker. |
| **Issues URL** | The URL to the issue in the issue tracker project that is linked to this GitLab project. Note that the `issues_url` requires `:id` in the URL. This ID is used by GitLab as a placeholder to replace the issue number. For example, `https://customissuetracker.com/project-name/:id`. |
| **New issue URL** | Currently unused. Will be changed in a future release. |
diff --git a/doc/user/project/integrations/generic_alerts.md b/doc/user/project/integrations/generic_alerts.md
index 57c1e54e48c..3490a3f2b9e 100644
--- a/doc/user/project/integrations/generic_alerts.md
+++ b/doc/user/project/integrations/generic_alerts.md
@@ -18,16 +18,21 @@ create an issue with the payload in the body of the issue. You can always
The entire payload will be posted in the issue discussion as a comment
authored by the GitLab Alert Bot.
-NOTE: **Note**
-In GitLab versions 13.1 and greater, you can configure [External Prometheus instances](prometheus.md#external-prometheus-instances) to use this endpoint.
+NOTE: **Note:**
+In GitLab versions 13.1 and greater, you can configure
+[External Prometheus instances](../../../operations/metrics/alerts.md#external-prometheus-instances)
+to use this endpoint.
## Setting up generic alerts
-To set up the generic alerts integration:
+To obtain credentials for setting up a generic alerts integration:
-1. Navigate to **Settings > Integrations** in a project.
-1. Click on **Alerts endpoint**.
-1. Toggle the **Active** alert setting. The `URL` and `Authorization Key` for the webhook configuration can be found there.
+- Sign in to GitLab as a user with maintainer [permissions](../../permissions.md) for a project.
+- Navigate to the **Operations** page for your project, depending on your installed version of GitLab:
+ - *In GitLab versions 13.1 and greater,* navigate to **{settings}** **Settings > Operations** in your project.
+ - *In GitLab versions prior to 13.1,* navigate to **{settings}** **Settings > Integrations** in your project. GitLab will display a banner encouraging you to enable the Alerts endpoint in **{settings}** **Settings > Operations** instead.
+- Click **Alerts endpoint**.
+- Toggle the **Active** alert setting to display the **URL** and **Authorization Key** for the webhook configuration.
## Customizing the payload
@@ -44,6 +49,14 @@ You can customize the payload by sending the following parameters. All fields ot
| `severity` | String | The severity of the alert. Must be one of `critical`, `high`, `medium`, `low`, `info`, `unknown`. Default is `critical`. |
| `fingerprint` | String or Array | The unique identifier of the alert. This can be used to group occurrences of the same alert. |
+You can also add custom fields to the alert's payload. The values of extra parameters
+are not limited to primitive types, such as strings or numbers, but can be a nested
+JSON object. For example:
+
+```json
+{ "foo": { "bar": { "baz": 42 } } }
+```
+
TIP: **Payload size:**
Ensure your requests are smaller than the [payload application limits](../../../administration/instance_limits.md#generic-alert-json-payloads).
@@ -70,6 +83,42 @@ Example payload:
"monitoring_tool": "value",
"hosts": "value",
"severity": "high",
- "fingerprint": "d19381d4e8ebca87b55cda6e8eee7385"
+ "fingerprint": "d19381d4e8ebca87b55cda6e8eee7385",
+ "foo": {
+ "bar": {
+ "baz": 42
+ }
+ }
}
```
+
+## Triggering test alerts
+
+> [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/3066) in GitLab Core in 13.2.
+
+After a [project maintainer or owner](#setting-up-generic-alerts)
+[configures generic alerts](#setting-up-generic-alerts), you can trigger a
+test alert to confirm your integration works properly.
+
+1. Sign in as a user with Developer or greater [permissions](../../../user/permissions.md).
+1. Navigate to **{settings}** **Settings > Operations** in your project.
+1. Click **Alerts endpoint** to expand the section.
+1. Enter a sample payload in **Alert test payload** (valid JSON is required).
+1. Click **Test alert payload**.
+
+GitLab displays an error or success message, depending on the outcome of your test.
+
+## Automatic grouping of identical alerts **(PREMIUM)**
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/214557) in [GitLab Premium](https://about.gitlab.com/pricing/) 13.2.
+
+In GitLab versions 13.2 and greater, GitLab groups alerts based on their payload.
+When an incoming alert contains the same payload as another alert (excluding the
+`start_time` and `hosts` attributes), GitLab groups these alerts together and
+displays a counter on the
+[Alert Management List](../operations/alert_management.md#alert-management-list)
+and details pages.
+
+If the existing alert is already `resolved`, then a new alert will be created instead.
+
+![Alert Management List](../operations/img/alert_list_v13_1.png)
diff --git a/doc/user/project/integrations/github.md b/doc/user/project/integrations/github.md
index f0977a4ea76..416996fb629 100644
--- a/doc/user/project/integrations/github.md
+++ b/doc/user/project/integrations/github.md
@@ -14,7 +14,7 @@ and is automatically configured on [GitHub import](../../../integration/github.m
### Complete these steps on GitHub
-This integration requires a [GitHub API token](https://help.github.com/en/github/authenticating-to-github/creating-a-personal-access-token-for-the-command-line)
+This integration requires a [GitHub API token](https://help.github.com/en/github/authenticating-to-github/creating-a-personal-access-token)
with `repo:status` access granted:
1. Go to your "Personal access tokens" page at <https://github.com/settings/tokens>
diff --git a/doc/user/project/integrations/img/actions_menu_create_new_dashboard_v13_2.png b/doc/user/project/integrations/img/actions_menu_create_new_dashboard_v13_2.png
new file mode 100644
index 00000000000..5d530a80421
--- /dev/null
+++ b/doc/user/project/integrations/img/actions_menu_create_new_dashboard_v13_2.png
Binary files differ
diff --git a/doc/user/project/integrations/img/heatmap_chart_too_much_data_v_13_2.png b/doc/user/project/integrations/img/heatmap_chart_too_much_data_v_13_2.png
new file mode 100644
index 00000000000..c3a391b06c7
--- /dev/null
+++ b/doc/user/project/integrations/img/heatmap_chart_too_much_data_v_13_2.png
Binary files differ
diff --git a/doc/user/project/integrations/img/jira/open_jira_issues_list_v13.2.png b/doc/user/project/integrations/img/jira/open_jira_issues_list_v13.2.png
new file mode 100644
index 00000000000..0cf58433b25
--- /dev/null
+++ b/doc/user/project/integrations/img/jira/open_jira_issues_list_v13.2.png
Binary files differ
diff --git a/doc/user/project/integrations/img/jira_service_page_v12_2.png b/doc/user/project/integrations/img/jira_service_page_v12_2.png
deleted file mode 100644
index ba7dad9b438..00000000000
--- a/doc/user/project/integrations/img/jira_service_page_v12_2.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/project/integrations/img/metrics_settings_button_v13_2.png b/doc/user/project/integrations/img/metrics_settings_button_v13_2.png
new file mode 100644
index 00000000000..d649f77eded
--- /dev/null
+++ b/doc/user/project/integrations/img/metrics_settings_button_v13_2.png
Binary files differ
diff --git a/doc/user/project/integrations/img/prometheus_manual_configuration_v13_2.png b/doc/user/project/integrations/img/prometheus_manual_configuration_v13_2.png
new file mode 100644
index 00000000000..b6ec08f492d
--- /dev/null
+++ b/doc/user/project/integrations/img/prometheus_manual_configuration_v13_2.png
Binary files differ
diff --git a/doc/user/project/integrations/img/prometheus_service_configuration.png b/doc/user/project/integrations/img/prometheus_service_configuration.png
deleted file mode 100644
index a38d1bce197..00000000000
--- a/doc/user/project/integrations/img/prometheus_service_configuration.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/project/integrations/jira.md b/doc/user/project/integrations/jira.md
index 442f3229de2..541c65041ad 100644
--- a/doc/user/project/integrations/jira.md
+++ b/doc/user/project/integrations/jira.md
@@ -55,29 +55,41 @@ In order to enable the Jira service in GitLab, you need to first configure the p
> **Notes:**
>
-> - The currently supported Jira versions are `v6.x, v7.x, v8.x` . GitLab 7.8 or
-> higher is required.
-> - GitLab 8.14 introduced a new way to integrate with Jira which greatly simplified
-> the configuration options you have to enter. If you are using an older version,
-> [follow this documentation](https://gitlab.com/gitlab-org/gitlab/blob/8-13-stable-ee/doc/project_services/jira.md).
+> - The supported Jira versions are `v6.x`, `v7.x`, and `v8.x`.
> - In order to support Oracle's Access Manager, GitLab will send additional cookies
> to enable Basic Auth. The cookie being added to each request is `OBBasicAuth` with
> a value of `fromDialog`.
To enable the Jira integration in a project, navigate to the
-[Integrations page](overview.md#accessing-integrations), click
-the **Jira** service, and fill in the required details on the page as described
-in the table below.
+[Integrations page](overview.md#accessing-integrations) and click
+the **Jira** service.
+
+Select **Enable integration**.
+
+Select a **Trigger** action. This determines whether a mention of a Jira issue in GitLab commits, merge requests, or both, should link the Jira issue back to that source commit/MR and transition the Jira issue, if indicated.
+
+To include a comment on the Jira issue when the above referene is made in GitLab, check **Enable comments**.
+
+Enter the further details on the page as described in the following table.
| Field | Description |
| ----- | ----------- |
| `Web URL` | The base URL to the Jira instance web interface which is being linked to this GitLab project. E.g., `https://jira.example.com`. |
| `Jira API URL` | The base URL to the Jira instance API. Web URL value will be used if not set. E.g., `https://jira-api.example.com`. Leave this field blank (or use the same value of `Web URL`) if using **Jira Cloud**. |
-| `Username/Email` | Created when [configuring Jira step](#configuring-jira). Use `username` for **Jira Server** or `email` for **Jira Cloud**. |
-| `Password/API token` |Created in [configuring Jira step](#configuring-jira). Use `password` for **Jira Server** or `API token` for **Jira Cloud**. |
-| `Transition ID` | This is the ID of a transition that moves issues to the desired state. It is possible to insert transition ids separated by `,` or `;` which means the issue will be moved to each state after another using the given order. **Closing Jira issues via commits or Merge Requests won't work if you don't set the ID correctly.** |
+| `Username or Email` | Created in [configuring Jira](#configuring-jira) step. Use `username` for **Jira Server** or `email` for **Jira Cloud**. |
+| `Password/API token` |Created in [configuring Jira](#configuring-jira) step. Use `password` for **Jira Server** or `API token` for **Jira Cloud**. |
+| `Transition ID` | Required for closing Jira issues via commits or merge requests. This is the ID of a transition in Jira that moves issues to a desired state. (See [Obtaining a transition ID](#obtaining-a-transition-id).) If you insert multiple transition IDs separated by `,` or `;`, the issue is moved to each state, one after another, using the given order. |
+
+To enable users to view Jira issues inside GitLab, select **Enable Jira issues** and enter a project key. **(PREMIUM)**
+
+CAUTION: **Caution:**
+If you enable Jira issues with the setting above, all users that have access to this GitLab project will be able to view all issues from the specified Jira project.
+
+When you have configured all settings, click **Test settings and save changes**.
-### Obtaining a transition ID
+Your GitLab project can now interact with all Jira projects in your instance and the project now displays a Jira link that opens the Jira project.
+
+#### Obtaining a transition ID
In the most recent Jira user interface, you can no longer see transition IDs in the workflow
administration UI. You can get the ID you need in either of the following ways:
@@ -90,19 +102,11 @@ administration UI. You can get the ID you need in either of the following ways:
Note that the transition ID may vary between workflows (e.g., bug vs. story),
even if the status you are changing to is the same.
-After saving the configuration, your GitLab project will be able to interact
-with all Jira projects in your Jira instance and you'll see the Jira link on the GitLab project pages that takes you to the appropriate Jira project.
-
-![Jira service page](img/jira_service_page_v12_2.png)
-
-### Disabling comments on Jira issues
-
-When you reference a Jira issue, it will always link back to the source commit/MR in GitLab, however, you can control whether GitLab will also cross-post a comment to the Jira issue. That functionality is enabled by default.
+#### Disabling comments on Jira issues
-To disable the automated commenting on Jira issues:
+You can continue to have GitLab cross-link a source commit/MR with a Jira issue while disabling the comment added to the issue.
-1. Open the [Integrations page](overview.md#accessing-integrations) and select **Jira**.
-1. In the **Event Action** section, uncheck **Comment**.
+See the [Configuring GitLab](#configuring-gitlab) section and uncheck the **Enable comments** setting.
## Jira issues
@@ -111,7 +115,7 @@ By now you should have [configured Jira](#configuring-jira) and enabled the
you should be able to reference and close Jira issues by just mentioning their
ID in GitLab commits and merge requests.
-### Referencing Jira Issues
+### Reference Jira issues
When GitLab project has Jira issue tracker configured and enabled, mentioning
Jira issue in GitLab will automatically add a comment in Jira issue with the
@@ -138,7 +142,7 @@ For example, the following commit will reference the Jira issue with `PROJECT-1`
git commit -m "PROJECT-1 Fix spelling and grammar"
```
-### Closing Jira Issues
+### Close Jira issues
Jira issues can be closed directly from GitLab by using trigger words in
commits and merge requests. When a commit which contains the trigger word
@@ -162,8 +166,6 @@ where `PROJECT-1` is the ID of the Jira issue.
> [project settings](img/jira_project_settings.png).
> - The Jira issue will not be transitioned if it has a resolution.
-### Jira issue closing example
-
Let's consider the following example:
1. For the project named `PROJECT` in Jira, we implemented a new feature
@@ -185,6 +187,45 @@ with a link to the commit that resolved the issue.
![The GitLab integration closes Jira issue](img/jira_service_close_issue.png)
+### View Jira issues **(PREMIUM)**
+
+> [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/3622) in [GitLab Premium](https://about.gitlab.com/pricing/) 13.2.
+
+You can browse and search issues from a selected Jira project directly in GitLab. This requires [configuration](#configuring-gitlab) in GitLab by an administrator.
+
+![Jira issues integration enabled](img/jira/open_jira_issues_list_v13.2.png)
+
+From the **Jira Issues** menu, click **Issues List**. The issue list defaults to sort by **Created date**, with the newest issues listed at the top. You can change this to **Last updated**.
+
+Issues are grouped into tabs based on their [Jira status](https://confluence.atlassian.com/adminjiraserver070/defining-status-field-values-749382903.html).
+
+- The **Open** tab displays all issues with a Jira status in any category other than Done.
+- The **Closed** tab displays all issues with a Jira status categorized as Done.
+- The **All** tab displays all issues of any status.
+
+Click an issue title to open its original Jira issue page for full details.
+
+#### Search and filter the issues list
+
+To refine the list of issues, use the search bar to search for any text
+contained in an issue summary (title) or description.
+
+You can also filter by labels, status, reporter, and assignee using URL parameters.
+Enhancements to be able to use these through the user interface are [planned](https://gitlab.com/groups/gitlab-org/-/epics/3622).
+
+- To filter issues by `labels`, specify one or more labels as part of the `labels[]`
+parameter in the URL. When using multiple labels, only issues that contain all specified
+labels are listed. `/-/integrations/jira/issues?labels[]=backend&labels[]=feature&labels[]=QA`
+
+- To filter issues by `status`, specify the `status` parameter in the URL.
+`/-/integrations/jira/issues?status=In Progress`
+
+- To filter issues by `reporter`, specify a reporter's Jira display name for the
+`author_username` parameter in the URL. `/-/integrations/jira/issues?author_username=John Smith`
+
+- To filter issues by `assignee`, specify their Jira display name for the
+`assignee_username` parameter in the URL. `/-/integrations/jira/issues?assignee_username=John Smith`
+
## Troubleshooting
If these features do not work as expected, it is likely due to a problem with the way the integration settings were configured.
diff --git a/doc/user/project/integrations/jira_cloud_configuration.md b/doc/user/project/integrations/jira_cloud_configuration.md
index 619c94b282b..c7157b6bd0e 100644
--- a/doc/user/project/integrations/jira_cloud_configuration.md
+++ b/doc/user/project/integrations/jira_cloud_configuration.md
@@ -5,7 +5,7 @@ below to create one:
1. Log in to [`id.atlassian.com`](https://id.atlassian.com/manage-profile/security/api-tokens) with your email address.
- NOTE: **Note**
+ NOTE: **Note:**
It is important that the user associated with this email address has *write* access
to projects in Jira.
diff --git a/doc/user/project/integrations/jira_server_configuration.md b/doc/user/project/integrations/jira_server_configuration.md
index 1efd0ff3944..c8278a0f083 100644
--- a/doc/user/project/integrations/jira_server_configuration.md
+++ b/doc/user/project/integrations/jira_server_configuration.md
@@ -6,7 +6,7 @@ need to integrate with GitLab.
As an example, we'll create a user named `gitlab` and add it to a new group
named `gitlab-developers`.
-NOTE: **Note**
+NOTE: **Note:**
It is important that the Jira user created for the integration is given 'write'
access to your Jira projects. This is covered in the process below.
diff --git a/doc/user/project/integrations/overview.md b/doc/user/project/integrations/overview.md
index 88668ab6c7d..79c55e2d140 100644
--- a/doc/user/project/integrations/overview.md
+++ b/doc/user/project/integrations/overview.md
@@ -14,8 +14,6 @@ want to configure.
![Integrations list](img/project_services.png)
-Below, you will find a list of the currently supported ones accompanied with comprehensive documentation.
-
## Integrations listing
Click on the service links to see further configuration instructions and details.
@@ -28,6 +26,7 @@ Click on the service links to see further configuration instructions and details
| Buildkite | Continuous integration and deployments | Yes |
| [Bugzilla](bugzilla.md) | Bugzilla issue tracker | No |
| Campfire | Simple web-based real-time group chat | No |
+| [Confluence](../../../api/services.md#confluence-service) | Replaces the link to the internal wiki with a link to a Confluence Cloud Workspace | No |
| Custom Issue Tracker | Custom issue tracker | No |
| [Discord Notifications](discord_notifications.md) | Receive event notifications in Discord | No |
| Drone CI | Continuous Integration platform built on Docker, written in Go | Yes |
@@ -68,16 +67,16 @@ supported by `push_hooks` and `tag_push_hooks` events won't be executed.
The number of branches or tags supported can be changed via
[`push_event_hooks_limit` application setting](../../../api/settings.md#list-of-settings-that-can-be-accessed-via-api-calls).
-## Services templates
+## Service templates
-Services templates is a way to set some predefined values in the Service of
-your liking which will then be pre-filled on each project's Service.
+Service templates are a way to set predefined values for an integration across
+all new projects on the instance.
-Read more about [Services templates in this document](services_templates.md).
+Read more about [Service templates in this document](services_templates.md).
## Troubleshooting integrations
-Some integrations use service hooks for integration with external applications. To confirm which ones use service hooks, see the [integrations listing](#integrations-listing). GitLab stores details of service hook requests made within the last 2 days. To view details of the requests, go to the service's configuration page.
+Some integrations use service hooks for integration with external applications. To confirm which ones use service hooks, see the [integrations listing](#integrations-listing) above. GitLab stores details of service hook requests made within the last 2 days. To view details of the requests, go to that integration's configuration page.
The **Recent Deliveries** section lists the details of each request made within the last 2 days:
@@ -88,17 +87,17 @@ The **Recent Deliveries** section lists the details of each request made within
- Relative time in which the request was made
To view more information about the request's execution, click the respective **View details** link.
-On the details page, you can see the data sent by GitLab (request headers and body) and the data received by GitLab (response headers and body).
+On the details page, you can see the request headers and body sent and received by GitLab.
-From this page, you can repeat delivery with the same data by clicking **Resend Request**.
+To repeat a delivery using the same data, click **Resend Request**.
![Recent deliveries](img/webhook_logs.png)
### Uninitialized repositories
Some integrations fail with an error `Test Failed. Save Anyway` when you attempt to set them up on
-uninitialized repositories. This is because the default service test uses push data to build the
-payload for the test request, and it fails, because there are no push events for the project.
+uninitialized repositories. Some integrations use push data to build the test payload,
+and this error occurs when no push events exist in the project yet.
To resolve this error, initialize the repository by pushing a test file to the project and set up
the integration again.
diff --git a/doc/user/project/integrations/prometheus.md b/doc/user/project/integrations/prometheus.md
index 0d17ff51372..f1567208a8f 100644
--- a/doc/user/project/integrations/prometheus.md
+++ b/doc/user/project/integrations/prometheus.md
@@ -19,7 +19,8 @@ There are two ways to set up Prometheus integration, depending on where your app
- For deployments on Kubernetes, GitLab can automatically [deploy and manage Prometheus](#managed-prometheus-on-kubernetes).
- For other deployment targets, simply [specify the Prometheus server](#manual-configuration-of-prometheus).
-Once enabled, GitLab will automatically detect metrics from known services in the [metric library](#monitoring-cicd-environments). You can also [add your own metrics](#adding-custom-metrics).
+Once enabled, GitLab will automatically detect metrics from known services in the [metric library](prometheus_library/index.md). You can also [add your own metrics](../../../operations/metrics/index.md#adding-custom-metrics) and create
+[custom dashboards](../../../operations/metrics/dashboards/index.md).
## Enabling Prometheus Integration
@@ -32,11 +33,10 @@ GitLab can seamlessly deploy and manage Prometheus on a [connected Kubernetes cl
#### Requirements
- A [connected Kubernetes cluster](../clusters/index.md)
-- Helm Tiller [installed by GitLab](../clusters/index.md#installing-applications)
#### Getting started
-Once you have a connected Kubernetes cluster with Helm installed, deploying a managed Prometheus is as easy as a single click.
+Once you have a connected Kubernetes cluster, deploying a managed Prometheus is as easy as a single click.
1. Go to the **Operations > Kubernetes** page to view your connected clusters
1. Select the cluster you would like to deploy Prometheus to
@@ -44,53 +44,6 @@ Once you have a connected Kubernetes cluster with Helm installed, deploying a ma
![Managed Prometheus Deploy](img/prometheus_deploy.png)
-#### Getting metrics to display on the Metrics Dashboard
-
-After completing the steps above, you will also need deployments in order to view the
-**Operations > Metrics** page. Setting up [Auto DevOps](../../../topics/autodevops/index.md)
-will help you to quickly create a deployment:
-
-1. Navigate to your project's **Operations > Kubernetes** page, and ensure that,
- in addition to "Prometheus" and "Helm Tiller", you also have "Runner" and "Ingress"
- installed. Once "Ingress" is installed, copy its endpoint.
-1. Navigate to your project's **Settings > CI/CD** page. In the Auto DevOps section,
- select a deployment strategy and save your changes.
-1. On the same page, in the Variables section, add a variable named `KUBE_INGRESS_BASE_DOMAIN`
- with the value of the Ingress endpoint you have copied in the previous step. Leave the type
- as "Variable".
-1. Navigate to your project's **CI/CD > Pipelines** page, and run a pipeline on any branch.
-1. When the pipeline has run successfully, graphs will be available on the **Operations > Metrics** page.
-
-![Monitoring Dashboard](img/prometheus_monitoring_dashboard_v13_1.png)
-
-#### Using the Metrics Dashboard
-
-##### Select an environment
-
-The **Environment** dropdown box above the dashboard displays the list of all [environments](#monitoring-cicd-environments).
-It enables you to search as you type through all environments and select the one you're looking for.
-
-![Monitoring Dashboard Environments](img/prometheus_dashboard_environments_v12_8.png)
-
-##### Select a dashboard
-
-The **dashboard** dropdown box above the dashboard displays the list of all dashboards available for the project.
-It enables you to search as you type through all dashboards and select the one you're looking for.
-
-![Monitoring Dashboard select](img/prometheus_dashboard_select_v_13_0.png)
-
-##### Mark a dashboard as favorite
-
-> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/214582) in GitLab 13.0.
-
-When viewing a dashboard, click the empty **Star dashboard** **{star-o}** button to mark a
-dashboard as a favorite. Starred dashboards display a solid star **{star}** button,
-and appear at the top of the dashboard select list.
-
-To remove dashboard from the favorites list, click the solid **Unstar Dashboard** **{star}** button.
-
-![Monitoring Dashboard favorite state toggle](img/toggle_metrics_user_starred_dashboard_v13_0.png)
-
#### About managed Prometheus deployments
Prometheus is deployed into the `gitlab-managed-apps` namespace, using the [official Helm chart](https://github.com/helm/charts/tree/master/stable/prometheus). Prometheus is only accessible within the cluster, with GitLab communicating through the [Kubernetes API](https://kubernetes.io/docs/concepts/overview/kubernetes-api/).
@@ -128,14 +81,24 @@ Installing and configuring Prometheus to monitor applications is fairly straight
The actual configuration of Prometheus integration within GitLab is very simple.
All you will need is the domain name or IP address of the Prometheus server you'd like
-to integrate with.
-
-1. Navigate to the [Integrations page](overview.md#accessing-integrations).
+to integrate with. If the Prometheus resource is secured with Google's Identity-Aware Proxy (IAP),
+additional information like Client ID and Service Account credentials can be passed which
+GitLab can use to access the resource. More information about authentication from a
+service account can be found at Google's documentation for
+[Authenticating from a service account](https://cloud.google.com/iap/docs/authentication-howto#authenticating_from_a_service_account).
+
+1. Navigate to the [Integrations page](overview.md#accessing-integrations) at
+ **{settings}** **Settings > Integrations**.
1. Click the **Prometheus** service.
-1. Provide the domain name or IP address of your server, for example `http://prometheus.example.com/` or `http://192.0.2.1/`.
+1. For **API URL**, provide the domain name or IP address of your server, such as
+ `http://prometheus.example.com/` or `http://192.0.2.1/`.
+1. (Optional) In **Google IAP Audience Client ID**, provide the Client ID of the
+ Prometheus OAuth Client secured with Google IAP.
+1. (Optional) In **Google IAP Service Account JSON**, provide the contents of the
+ Service Account credentials file that is authorized to access the Prometheus resource.
1. Click **Save changes**.
-![Configure Prometheus Service](img/prometheus_service_configuration.png)
+![Configure Prometheus Service](img/prometheus_manual_configuration_v13_2.png)
#### Thanos configuration in GitLab
@@ -158,7 +121,7 @@ one of them will be used:
[Prometheus manual configuration](#manual-configuration-of-prometheus)
and a [managed Prometheus on Kubernetes](#managed-prometheus-on-kubernetes),
the manual configuration takes precedence and is used to run queries from
- [dashboards](#defining-custom-dashboards-per-project) and [custom metrics](#adding-custom-metrics).
+ [dashboards](../../../operations/metrics/dashboards/index.md#defining-custom-dashboards-per-project) and [custom metrics](../../../operations/metrics/index.md#adding-custom-metrics).
- If you have managed Prometheus applications installed on Kubernetes clusters
at **different** levels (project, group, instance), the order of precedence is described in
[Cluster precedence](../../instance/clusters/index.md#cluster-precedence).
@@ -166,884 +129,6 @@ one of them will be used:
clusters at the **same** level, the Prometheus application of a cluster with a
matching [environment scope](../../../ci/environments/index.md#scoping-environments-with-specs) is used.
-## Monitoring CI/CD Environments
-
-Once configured, GitLab will attempt to retrieve performance metrics for any
-environment which has had a successful deployment.
-
-GitLab will automatically scan the Prometheus server for metrics from known servers like Kubernetes and NGINX, and attempt to identify individual environments. The supported metrics and scan process is detailed in our [Prometheus Metrics Library documentation](prometheus_library/index.md).
-
-You can view the performance dashboard for an environment by [clicking on the monitoring button](../../../ci/environments/index.md#monitoring-environments).
-
-### Adding custom metrics
-
-> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/3799) in [GitLab Premium](https://about.gitlab.com/pricing/) 10.6.
-> - [Moved](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/28527) to [GitLab Core](https://about.gitlab.com/pricing/) 12.10.
-
-Custom metrics can be monitored by adding them on the monitoring dashboard page. Once saved, they will be displayed on the environment performance dashboard provided that either:
-
-- A [connected Kubernetes cluster](../clusters/add_remove_clusters.md) with the environment scope of `*` is used and [Prometheus installed on the cluster](#enabling-prometheus-integration)
-- Prometheus is [manually configured](#manual-configuration-of-prometheus).
-
-![Add New Metric](img/prometheus_add_metric.png)
-
-A few fields are required:
-
-- **Name**: Chart title
-- **Type**: Type of metric. Metrics of the same type will be shown together.
-- **Query**: Valid [PromQL query](https://prometheus.io/docs/prometheus/latest/querying/basics/).
-- **Y-axis label**: Y axis title to display on the dashboard.
-- **Unit label**: Query units, for example `req / sec`. Shown next to the value.
-
-Multiple metrics can be displayed on the same chart if the fields **Name**, **Type**, and **Y-axis label** match between metrics. For example, a metric with **Name** `Requests Rate`, **Type** `Business`, and **Y-axis label** `rec / sec` would display on the same chart as a second metric with the same values. A **Legend label** is suggested if this feature is used.
-
-#### Query Variables
-
-##### Predefined variables
-
-GitLab supports a limited set of [CI variables](../../../ci/variables/README.md) in the Prometheus query. This is particularly useful for identifying a specific environment, for example with `ci_environment_slug`. The supported variables are:
-
-- `ci_environment_slug`
-- `kube_namespace`
-- `ci_project_name`
-- `ci_project_namespace`
-- `ci_project_path`
-- `ci_environment_name`
-- `__range`
-
-NOTE: **Note:**
-Variables for Prometheus queries must be lowercase.
-
-###### __range
-
-The `__range` variable is useful in Prometheus
-[range vector selectors](https://prometheus.io/docs/prometheus/latest/querying/basics/#range-vector-selectors).
-Its value is the total number of seconds in the dashboard's time range.
-For example, if the dashboard time range is set to 8 hours, the value of
-`__range` is `28800s`.
-
-##### User-defined variables
-
-[Variables can be defined](#templating-templating-properties) in a custom dashboard YAML file.
-
-##### Using variables
-
-Variables can be specified using double curly braces, such as `"{{ci_environment_slug}}"` ([added](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/20793) in GitLab 12.7).
-
-Support for the `"%{ci_environment_slug}"` format was
-[removed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/31581) in GitLab 13.0.
-Queries that continue to use the old format will show no data.
-
-#### Query Variables from URL
-
-> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/214500) in GitLab 13.0.
-
-GitLab supports setting custom variables through URL parameters. Surround the variable
-name with double curly braces (`{{example}}`) to interpolate the variable in a query:
-
-```plaintext
-avg(sum(container_memory_usage_bytes{container_name!="{{pod}}"}) by (job)) without (job) /1024/1024/1024'
-```
-
-The URL for this query would be:
-
-```plaintext
-http://gitlab.com/<user>/<project>/-/environments/<environment_id>/metrics?dashboard=.gitlab%2Fdashboards%2Fcustom.yml&pod=POD
-```
-
-#### Editing additional metrics from the dashboard
-
-> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/208976) in GitLab 12.9.
-
-You can edit existing additional custom metrics by clicking the **{ellipsis_v}** **More actions** dropdown and selecting **Edit metric**.
-
-![Edit metric](img/prometheus_dashboard_edit_metric_link_v_12_9.png)
-
-### Defining custom dashboards per project
-
-> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/issues/59974) in GitLab 12.1.
-
-By default, all projects include a GitLab-defined Prometheus dashboard, which
-includes a few key metrics, but you can also define your own custom dashboards.
-
-You may create a new file from scratch or duplicate a GitLab-defined Prometheus
-dashboard.
-
-NOTE: **Note:**
-The metrics as defined below do not support alerts, unlike
-[custom metrics](#adding-custom-metrics).
-
-#### Adding a new dashboard to your project
-
-You can configure a custom dashboard by adding a new YAML file into your project's
-`.gitlab/dashboards/` directory. In order for the dashboards to be displayed on
-the project's **Operations > Metrics** page, the files must have a `.yml`
-extension and should be present in the project's **default** branch.
-
-For example:
-
-1. Create `.gitlab/dashboards/prom_alerts.yml` under your repository's root
- directory with the following contents:
-
- ```yaml
- dashboard: 'Dashboard Title'
- panel_groups:
- - group: 'Group Title'
- panels:
- - type: area-chart
- title: "Chart Title"
- y_label: "Y-Axis"
- y_axis:
- format: number
- precision: 0
- metrics:
- - id: my_metric_id
- query_range: 'http_requests_total'
- label: "Instance: {{instance}}, method: {{method}}"
- unit: "count"
- ```
-
- The above sample dashboard would display a single area chart. Each file should
- define the layout of the dashboard and the Prometheus queries used to populate
- data.
-
-1. Save the file, commit, and push to your repository. The file must be present in your **default** branch.
-1. Navigate to your project's **Operations > Metrics** and choose the custom
- dashboard from the dropdown.
-
-NOTE: **Note:**
-Configuration files nested under subdirectories of `.gitlab/dashboards` are not
-supported and will not be available in the UI.
-
-#### Duplicating a GitLab-defined dashboard
-
-> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/37238) in GitLab 12.7.
-> - From [GitLab 12.8 onwards](https://gitlab.com/gitlab-org/gitlab/-/issues/39505), custom metrics are also duplicated when you duplicate a dashboard.
-
-You can save a complete copy of a GitLab defined dashboard along with all custom metrics added to it.
-Resulting `.yml` file can be customized and adapted to your project.
-You can decide to save the dashboard `.yml` file in the project's **default** branch or in a
-new branch.
-
-1. Click **Duplicate dashboard** in the dashboard dropdown.
-
- NOTE: **Note:**
- You can duplicate only GitLab-defined dashboards.
-
-1. Enter the file name and other information, such as the new commit's message, and click **Duplicate**.
-
-If you select your **default** branch, the new dashboard becomes immediately available.
-If you select another branch, this branch should be merged to your **default** branch first.
-
-#### Dashboard YAML syntax validation
-
-> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/33202) in GitLab 13.1.
-
-To confirm your dashboard definition contains valid YAML syntax:
-
-1. Navigate to **{doc-text}** **Repository > Files**.
-1. Navigate to your dashboard file in your repository.
-1. Review the information pane about the file, displayed above the file contents.
-
-Files with valid syntax display **Metrics Dashboard YAML definition is valid**,
-and files with invalid syntax display **Metrics Dashboard YAML definition is invalid**.
-
-![Metrics Dashboard_YAML_syntax_validation](img/prometheus_dashboard_yaml_validation_v13_1.png)
-
-When **Metrics Dashboard YAML definition is invalid** at least one of the following messages is displayed:
-
-1. `dashboard: can't be blank` [learn more](#dashboard-top-level-properties)
-1. `panel_groups: can't be blank` [learn more](#dashboard-top-level-properties)
-1. `group: can't be blank` [learn more](#panel-group-panel_groups-properties)
-1. `panels: can't be blank` [learn more](#panel-group-panel_groups-properties)
-1. `metrics: can't be blank` [learn more](#panel-panels-properties)
-1. `title: can't be blank` [learn more](#panel-panels-properties)
-1. `query: can't be blank` [learn more](#metrics-metrics-properties)
-1. `query_range: can't be blank` [learn more](#metrics-metrics-properties)
-1. `unit: can't be blank` [learn more](#metrics-metrics-properties)
-1. `YAML syntax: The parsed YAML is too big`
-
- This is displayed when the YAML file is larger than 1 MB.
-
-1. `YAML syntax: Invalid configuration format`
-
- This is displayed when the YAML file is empty or does not contain valid YAML.
-
-Metrics Dashboard YAML definition validation information is also available as a [GraphQL API field](../../../api/graphql/reference/index.md#metricsdashboard)
-
-#### Dashboard YAML properties
-
-Dashboards have several components:
-
-- Templating variables.
-- Panel groups, which consist of panels.
-- Panels, which support one or more metrics.
-
-The following tables outline the details of expected properties.
-
-##### **Dashboard (top-level) properties**
-
-| Property | Type | Required | Description |
-| ------ | ------ | ------ | ------ |
-| `dashboard` | string | yes | Heading for the dashboard. Only one dashboard should be defined per file. |
-| `panel_groups` | array | yes | The panel groups which should be on the dashboard. |
-| `templating` | hash | no | Top level key under which templating related options can be added. |
-| `links` | array | no | Add links to display on the dashboard. |
-
-##### **Templating (`templating`) properties**
-
-| Property | Type | Required | Description |
-| -------- | ---- | -------- | ----------- |
-| `variables` | hash | yes | Variables can be defined here. |
-
-Read the documentation on [templating](#templating-variables-for-metrics-dashboards).
-
-##### **Links (`links`) properties**
-
-| Property | Type | Required | Description |
-| -------- | ---- | -------- | ----------- |
-| `url` | string | yes | The address of the link. |
-| `title` | string | no | Display title for the link. |
-| `type` | string | no | Type of the link. Specifies the link type, can be: `grafana` |
-
-Read the documentation on [links](#add-related-links-to-custom-dashboards).
-
-##### **Panel group (`panel_groups`) properties**
-
-| Property | Type | Required | Description |
-| ------ | ------ | ------ | ------ |
-| `group` | string | required | Heading for the panel group. |
-| `priority` | number | optional, defaults to order in file | Order to appear on the dashboard. Higher number means higher priority, which will be higher on the page. Numbers do not need to be consecutive. |
-| `panels` | array | required | The panels which should be in the panel group. |
-
-##### **Panel (`panels`) properties**
-
-| Property | Type | Required | Description |
-| ------ | ------ | ------ | ------- |
-| `type` | enum | no, defaults to `area-chart` | Specifies the chart type to use, can be: `area-chart`, `line-chart` or `anomaly-chart`. |
-| `title` | string | yes | Heading for the panel. |
-| `y_label` | string | no, but highly encouraged | Y-Axis label for the panel. |
-| `y_axis` | string | no | Y-Axis configuration for the panel. |
-| `max_value` | number | no | Denominator value used for calculating [percentile based results](#percentile-based-results) |
-| `weight` | number | no, defaults to order in file | Order to appear within the grouping. Lower number means higher priority, which will be higher on the page. Numbers do not need to be consecutive. |
-| `metrics` | array | yes | The metrics which should be displayed in the panel. Any number of metrics can be displayed when `type` is `area-chart` or `line-chart`, whereas only 3 can be displayed when `type` is `anomaly-chart`. |
-| `links` | array | no | Add links to display on the chart's [context menu](#chart-context-menu). |
-
-##### **Axis (`panels[].y_axis`) properties**
-
-| Property | Type | Required | Description |
-| ----------- | ------ | ----------------------------- | -------------------------------------------------------------------- |
-| `name` | string | no, but highly encouraged | Y-Axis label for the panel. Replaces `y_label` if set. |
-| `format` | string | no, defaults to `engineering` | Unit format used. See the [full list of units](prometheus_units.md). |
-| `precision` | number | no, defaults to `2` | Number of decimal places to display in the number. | |
-
-##### **Metrics (`metrics`) properties**
-
-| Property | Type | Required | Description |
-| ------ | ------ | ------ | ------ |
-| `id` | string | no | Used for associating dashboard metrics with database records. Must be unique across dashboard configuration files. Required for [alerting](#setting-up-alerts-for-prometheus-metrics) (support not yet enabled, see [relevant issue](https://gitlab.com/gitlab-org/gitlab/-/issues/27980)). |
-| `unit` | string | yes | Defines the unit of the query's return data. |
-| `label` | string | no, but highly encouraged | Defines the legend-label for the query. Should be unique within the panel's metrics. Can contain time series labels as interpolated variables. |
-| `query` | string | yes if `query_range` is not defined | Defines the Prometheus query to be used to populate the chart/panel. If defined, the `query` endpoint of the [Prometheus API](https://prometheus.io/docs/prometheus/latest/querying/api/) will be utilized. |
-| `query_range` | string | yes if `query` is not defined | Defines the Prometheus query to be used to populate the chart/panel. If defined, the `query_range` endpoint of the [Prometheus API](https://prometheus.io/docs/prometheus/latest/querying/api/) will be utilized. |
-| `step` | number | no, value is calculated if not defined | Defines query resolution step width in float number of seconds. Metrics on the same panel should use the same `step` value. |
-
-##### Dynamic labels
-
-Dynamic labels are useful when multiple time series are returned from a Prometheus query.
-
-When a static label is used and a query returns multiple time series, then all the legend items will be labeled the same, which makes identifying each time series difficult:
-
-```yaml
-metrics:
- - id: my_metric_id
- query_range: 'http_requests_total'
- label: "Time Series"
- unit: "count"
-```
-
-This may render a legend like this:
-
-![repeated legend label chart](img/prometheus_dashboard_repeated_label.png)
-
-For labels to be more explicit, using variables that reflect time series labels is a good practice. The variables will be replaced by the values of the time series labels when the legend is rendered:
-
-```yaml
-metrics:
- - id: my_metric_id
- query_range: 'http_requests_total'
- label: "Instance: {{instance}}, method: {{method}}"
- unit: "count"
-```
-
-The resulting rendered legend will look like this:
-
-![legend with label variables](img/prometheus_dashboard_label_variables.png)
-
-There is also a shorthand value for dynamic dashboard labels that make use of only one time series label:
-
-```yaml
-metrics:
- - id: my_metric_id
- query_range: 'http_requests_total'
- label: "Method"
- unit: "count"
-```
-
-This works by lowercasing the value of `label` and, if there are more words separated by spaces, replacing those spaces with an underscore (`_`). The transformed value is then checked against the labels of the time series returned by the Prometheus query. If a time series label is found that is equal to the transformed value, then the label value will be used and rendered in the legend like this:
-
-![legend with label shorthand variable](img/prometheus_dashboard_label_variable_shorthand.png)
-
-#### Panel types for dashboards
-
-The below panel types are supported in monitoring dashboards.
-
-##### Area or Line Chart
-
-To add an area chart panel type to a dashboard, look at the following sample dashboard file:
-
-```yaml
-dashboard: 'Dashboard Title'
-panel_groups:
- - group: 'Group Title'
- panels:
- - type: area-chart # or line-chart
- title: 'Area Chart Title'
- y_label: "Y-Axis"
- y_axis:
- format: number
- precision: 0
- metrics:
- - id: area_http_requests_total
- query_range: 'http_requests_total'
- label: "Instance: {{instance}}, Method: {{method}}"
- unit: "count"
-```
-
-Note the following properties:
-
-| Property | Type | Required | Description |
-| ------ | ------ | ------ | ------ |
-| type | string | no | Type of panel to be rendered. Optional for area panel types |
-| query_range | string | required | For area panel types, you must use a [range query](https://prometheus.io/docs/prometheus/latest/querying/api/#range-queries) |
-
-![area panel chart](img/prometheus_dashboard_area_panel_type_v12_8.png)
-
-Starting in [version 12.8](https://gitlab.com/gitlab-org/gitlab/-/issues/202696), the y-axis values will automatically scale according to the data. Previously, it always started from 0.
-
-##### Anomaly chart
-
-> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/16530) in GitLab 12.5.
-
-To add an anomaly chart panel type to a dashboard, add a panel with *exactly* 3 metrics.
-
-The first metric represents the current state, and the second and third metrics represent the upper and lower limit respectively:
-
-```yaml
-dashboard: 'Dashboard Title'
-panel_groups:
- - group: 'Group Title'
- panels:
- - type: anomaly-chart
- title: "Chart Title"
- y_label: "Y-Axis"
- metrics:
- - id: anomaly_requests_normal
- query_range: 'http_requests_total'
- label: "# of Requests"
- unit: "count"
- metrics:
- - id: anomaly_requests_upper_limit
- query_range: 10000
- label: "Max # of requests"
- unit: "count"
- metrics:
- - id: anomaly_requests_lower_limit
- query_range: 2000
- label: "Min # of requests"
- unit: "count"
-```
-
-Note the following properties:
-
-| Property | Type | Required | Description |
-| ------ | ------ | ------ | ------ |
-| type | string | required | Must be `anomaly-chart` for anomaly panel types |
-| query_range | yes | required | For anomaly panel types, you must use a [range query](https://prometheus.io/docs/prometheus/latest/querying/api/#range-queries) in every metric. |
-
-![anomaly panel type](img/prometheus_dashboard_anomaly_panel_type.png)
-
-##### Bar chart
-
-To add a bar chart to a dashboard, look at the following sample dashboard file:
-
-```yaml
-dashboard: 'Dashboard Title'
-panel_groups:
- - group: 'Group title'
- panels:
- - type: bar
- title: "Http Handlers"
- x_label: 'Response Size'
- y_axis:
- name: "Handlers"
- metrics:
- - id: prometheus_http_response_size_bytes_bucket
- query_range: "sum(increase(prometheus_http_response_size_bytes_bucket[1d])) by (handler)"
- unit: 'Bytes'
-```
-
-Note the following properties:
-
-| Property | Type | Required | Description |
-| ------ | ------ | ------ | ------ |
-| `type` | string | yes | Type of panel to be rendered. For bar chart types, set to `bar` |
-| `query_range` | yes | yes | For bar chart, you must use a [range query](https://prometheus.io/docs/prometheus/latest/querying/api/#range-queries)
-
-![bar chart panel type](img/prometheus_dashboard_bar_chart_panel_type_v12.10.png)
-
-##### Column chart
-
-To add a column panel type to a dashboard, look at the following sample dashboard file:
-
-```yaml
-dashboard: 'Dashboard Title'
-panel_groups:
- - group: 'Group title'
- panels:
- - title: "Column"
- type: "column"
- metrics:
- - id: 1024_memory
- query: 'avg(sum(container_memory_usage_bytes{container_name!="POD",pod_name=~"^%{ci_environment_slug}-([^c].*|c([^a]|a([^n]|n([^a]|a([^r]|r[^y])))).*|)-(.*)",namespace="%{kube_namespace}"}) by (job)) without (job) / count(avg(container_memory_usage_bytes{container_name!="POD",pod_name=~"^%{ci_environment_slug}-([^c].*|c([^a]|a([^n]|n([^a]|a([^r]|r[^y])))).*|)-(.*)",namespace="%{kube_namespace}"}) without (job)) /1024/1024'
- unit: MB
- label: "Memory Usage"
-```
-
-Note the following properties:
-
-| Property | Type | Required | Description |
-| ------ | ------ | ------ | ------ |
-| type | string | yes | Type of panel to be rendered. For column panel types, set to `column` |
-| query_range | yes | yes | For column panel types, you must use a [range query](https://prometheus.io/docs/prometheus/latest/querying/api/#range-queries) |
-
-![anomaly panel type](img/prometheus_dashboard_column_panel_type.png)
-
-##### Stacked column
-
-> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/30583) in GitLab 12.8.
-
-To add a stacked column panel type to a dashboard, look at the following sample dashboard file:
-
-```yaml
-dashboard: 'Dashboard title'
-priority: 1
-panel_groups:
-- group: 'Group Title'
- priority: 5
- panels:
- - type: 'stacked-column'
- title: "Stacked column"
- y_label: "y label"
- x_label: 'x label'
- metrics:
- - id: memory_1
- query_range: 'memory_query'
- label: "memory query 1"
- unit: "count"
- series_name: 'group 1'
- - id: memory_2
- query_range: 'memory_query_2'
- label: "memory query 2"
- unit: "count"
- series_name: 'group 2'
-
-```
-
-![stacked column panel type](img/prometheus_dashboard_stacked_column_panel_type_v12_8.png)
-
-| Property | Type | Required | Description |
-| ------ | ------ | ------ | ------ |
-| `type` | string | yes | Type of panel to be rendered. For stacked column panel types, set to `stacked-column` |
-| `query_range` | yes | yes | For stacked column panel types, you must use a [range query](https://prometheus.io/docs/prometheus/latest/querying/api/#range-queries) |
-
-##### Single Stat
-
-To add a single stat panel type to a dashboard, look at the following sample dashboard file:
-
-```yaml
-dashboard: 'Dashboard Title'
-panel_groups:
- - group: 'Group Title'
- panels:
- - title: "Single Stat"
- type: "single-stat"
- metrics:
- - id: 10
- query: 'max(go_memstats_alloc_bytes{job="prometheus"})'
- unit: MB
- label: "Total"
-```
-
-Note the following properties:
-
-| Property | Type | Required | Description |
-| ------ | ------ | ------ | ------ |
-| type | string | yes | Type of panel to be rendered. For single stat panel types, set to `single-stat` |
-| query | string | yes | For single stat panel types, you must use an [instant query](https://prometheus.io/docs/prometheus/latest/querying/api/#instant-queries) |
-
-![single stat panel type](img/prometheus_dashboard_single_stat_panel_type.png)
-
-###### Percentile based results
-
-> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/201946) in GitLab 12.8.
-
-Query results sometimes need to be represented as a percentage value out of 100. You can use the `max_value` property at the root of the panel definition:
-
-```yaml
-dashboard: 'Dashboard Title'
-panel_groups:
- - group: 'Group Title'
- panels:
- - title: "Single Stat"
- type: "single-stat"
- max_value: 100
- metrics:
- - id: 10
- query: 'max(go_memstats_alloc_bytes{job="prometheus"})'
- unit: '%'
- label: "Total"
-```
-
-For example, if you have a query value of `53.6`, adding `%` as the unit results in a single stat value of `53.6%`, but if the maximum expected value of the query is `120`, the value would be `44.6%`. Adding the `max_value` causes the correct percentage value to display.
-
-##### Heatmaps
-
-> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/30581) in GitLab 12.5.
-
-To add a heatmap panel type to a dashboard, look at the following sample dashboard file:
-
-```yaml
-dashboard: 'Dashboard Title'
-panel_groups:
- - group: 'Group Title'
- panels:
- - title: "Heatmap"
- type: "heatmap"
- metrics:
- - id: 10
- query: 'sum(rate(nginx_upstream_responses_total{upstream=~"%{kube_namespace}-%{ci_environment_slug}-.*"}[60m])) by (status_code)'
- unit: req/sec
- label: "Status code"
-```
-
-Note the following properties:
-
-| Property | Type | Required | Description |
-| ------ | ------ | ------ | ------ |
-| type | string | yes | Type of panel to be rendered. For heatmap panel types, set to `heatmap` |
-| query_range | yes | yes | For area panel types, you must use a [range query](https://prometheus.io/docs/prometheus/latest/querying/api/#range-queries) |
-
-![heatmap panel type](img/heatmap_panel_type.png)
-
-### Templating variables for metrics dashboards
-
-Templating variables can be used to make your metrics dashboard more versatile.
-
-#### Templating variable types
-
-`templating` is a top-level key in the
-[dashboard YAML](#dashboard-top-level-properties).
-Define your variables in the `variables` key, under `templating`. The value of
-the `variables` key should be a hash, and each key under `variables`
-defines a templating variable on the dashboard, and may contain alphanumeric and underscore characters.
-
-A variable can be used in a Prometheus query in the same dashboard using the syntax
-described [here](#using-variables).
-
-##### `text` variable type
-
-CAUTION: **Warning:**
-This variable type is an _alpha_ feature, and is subject to change at any time
-without prior notice!
-
-For each `text` variable defined in the dashboard YAML, there will be a free text
-box on the dashboard UI, allowing you to enter a value for each variable.
-
-The `text` variable type supports a simple and a full syntax.
-
-###### Simple syntax
-
-This example creates a variable called `variable1`, with a default value
-of `default value`:
-
-```yaml
-templating:
- variables:
- variable1: 'default value' # `text` type variable with `default value` as its default.
-```
-
-###### Full syntax
-
-This example creates a variable called `variable1`, with a default value of `default`.
-The label for the text box on the UI will be the value of the `label` key:
-
-```yaml
-templating:
- variables:
- variable1: # The variable name that can be used in queries.
- label: 'Variable 1' # (Optional) label that will appear in the UI for this text box.
- type: text
- options:
- default_value: 'default' # (Optional) default value.
-```
-
-##### `custom` variable type
-
-CAUTION: **Warning:**
-This variable type is an _alpha_ feature, and is subject to change at any time
-without prior notice!
-
-Each `custom` variable defined in the dashboard YAML creates a dropdown
-selector on the dashboard UI, allowing you to select a value for each variable.
-
-The `custom` variable type supports a simple and a full syntax.
-
-###### Simple syntax
-
-This example creates a variable called `variable1`, with a default value of `value1`.
-The dashboard UI will display a dropdown with `value1`, `value2` and `value3`
-as the choices.
-
-```yaml
-templating:
- variables:
- variable1: ['value1', 'value2', 'value3']
-```
-
-###### Full syntax
-
-This example creates a variable called `variable1`, with a default value of `value_option_2`.
-The label for the text box on the UI will be the value of the `label` key.
-The dashboard UI will display a dropdown with `Option 1` and `Option 2`
-as the choices.
-
-If you select `Option 1` from the dropdown, the variable will be replaced with `value option 1`.
-Similarly, if you select `Option 2`, the variable will be replaced with `value_option_2`:
-
-```yaml
-templating:
- variables:
- variable1: # The variable name that can be used in queries.
- label: 'Variable 1' # (Optional) label that will appear in the UI for this dropdown.
- type: custom
- options:
- values:
- - value: 'value option 1' # The value that will replace the variable in queries.
- text: 'Option 1' # (Optional) Text that will appear in the UI dropdown.
- - value: 'value_option_2'
- text: 'Option 2'
- default: true # (Optional) This option should be the default value of this variable.
-```
-
-### Add related links to custom dashboards
-
-> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/216385) in GitLab 13.1.
-
-You can embed links to other dashboards or external services in your custom
-dashboard by adding **Related links** to your dashboard's YAML file. Related links
-open in the same tab as the dashboard. Related links can be displayed in the
-following locations on your dashboard:
-
-- At the top of your dashboard as the top level [`links` dashboard property](#dashboard-top-level-properties).
-- In charts context menus as the [`links` property of a panel](#panel-panels-properties).
-
-Related links can contain the following attributes:
-
-- `url`: The full URL to the link. Required.
-- `title`: A phrase describing the link. Optional. If this attribute is not set,
- the full URL is used for the link title.
-- `type`: A string declaring the type of link. Optional. If set to `grafana`, the
- dashboard's time range values are converted to Grafana's time range format and
- appended to the `url`.
-
-The dashboard's time range is appended to the `url` as URL parameters.
-
-The following example shows two related links (`GitLab.com` and `GitLab Documentation`)
-added to a dashboard:
-
-![Links UI](img/related_links_v13_1.png)
-
-#### Links Syntax
-
-```yaml
-links:
- - title: GitLab.com
- url: https://gitlab.com
- - title: GitLab Documentation
- url: https://docs.gitlab.com
- - title: Public Grafana playground dashboard
- url: https://play.grafana.org/d/000000012/grafana-play-home?orgId=1
- type: grafana
-```
-
-### View and edit the source file of a custom dashboard
-
-> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/34779) in GitLab 12.5.
-
-When viewing a custom dashboard of a project, you can view the original
-`.yml` file by clicking on the **Edit dashboard** button.
-
-### Chart Context Menu
-
-From each of the panels in the dashboard, you can access the context menu by clicking the **{ellipsis_v}** **More actions** dropdown box above the upper right corner of the panel to take actions related to the chart's data.
-
-![Context Menu](img/panel_context_menu_v13_0.png)
-
-The options are:
-
-- [Expand panel](#expand-panel)
-- [View logs](#view-logs-ultimate)
-- [Download CSV](#downloading-data-as-csv)
-- [Copy link to chart](#embedding-gitlab-managed-kubernetes-metrics)
-- [Alerts](#setting-up-alerts-for-prometheus-metrics)
-
-### Dashboard Annotations
-
-> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/211330) in GitLab 12.10 (enabled by feature flag `metrics_dashboard_annotations`).
-> - [Feature flag removed](https://gitlab.com/gitlab-org/gitlab/-/issues/215224) in GitLab 13.0.
-
-You can use **Metrics Dashboard Annotations** to mark any important events on
-every metrics dashboard by adding annotations to it. While viewing a dashboard,
-annotation entries assigned to the selected time range will be automatically
-fetched and displayed on every chart within that dashboard. On mouse hover, each
-annotation presents additional details, including the exact time of an event and
-its description.
-
-You can create annotations by making requests to the
-[Metrics dashboard annotations API](../../../api/metrics_dashboard_annotations.md)
-
-![Annotations UI](img/metrics_dashboard_annotations_ui_v13.0.png)
-
-#### Retention policy
-
-> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/211433) in GitLab 13.01.
-
-To avoid excessive storage space consumption by stale annotations, records attached
-to time periods older than two weeks are removed daily. This recurring background
-job runs at 1:00 a.m. local server time.
-
-### Expand panel
-
-> [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/3100) in GitLab 13.0.
-
-To view a larger version of a visualization, expand the panel by clicking the
-**{ellipsis_v}** **More actions** icon and selecting **Expand panel**.
-
-To return to the metrics dashboard, click the **Back** button in your
-browser, or pressing the <kbd>Esc</kbd> key.
-
-### View Logs **(ULTIMATE)**
-
-> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/122013) in GitLab 12.8.
-
-If you have [Logs](../clusters/kubernetes_pod_logs.md) enabled,
-you can navigate from the charts in the dashboard to view Logs by
-clicking on the context menu in the upper-right corner.
-
-If you use the **Timeline zoom** function at the bottom of the chart, logs will narrow down to the time range you selected.
-
-### Timeline zoom and URL sharing
-
-> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/198910) in GitLab 12.8.
-
-You can use the **Timeline zoom** function at the bottom of a chart to zoom in
-on a date and time of your choice. When you click and drag the sliders to select
-a different beginning or end date of data to display, GitLab adds your selected start
-and end times to the URL, enabling you to share specific timeframes more easily.
-
-### Downloading data as CSV
-
-Data from Prometheus charts on the metrics dashboard can be downloaded as CSV.
-
-### Setting up alerts for Prometheus metrics
-
-#### Managed Prometheus instances
-
-> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/6590) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 11.2 for [custom metrics](#adding-custom-metrics), and 11.3 for [library metrics](prometheus_library/metrics.md).
-
-For managed Prometheus instances using auto configuration, alerts for metrics [can be configured](#adding-custom-metrics) directly in the performance dashboard.
-
-To set an alert:
-
-1. Click on the ellipsis icon in the top right corner of the metric you want to create the alert for.
-1. Choose **Alerts**
-1. Set threshold and operator.
-1. Click **Add** to save and activate the alert.
-
-![Adding an alert](img/prometheus_alert.png)
-
-To remove the alert, click back on the alert icon for the desired metric, and click **Delete**.
-
-#### External Prometheus instances
-
->- [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/9258) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 11.8.
->- [Moved](https://gitlab.com/gitlab-org/gitlab/-/issues/42640) to [GitLab Core](https://about.gitlab.com/pricing/) in 12.10.
-
-For manually configured Prometheus servers, a notify endpoint is provided to use with Prometheus webhooks. If you have manual configuration enabled, an **Alerts** section is added to **Settings > Integrations > Prometheus**. This contains the *URL* and *Authorization Key*. The **Reset Key** button will invalidate the key and generate a new one.
-
-![Prometheus service configuration of Alerts](img/prometheus_service_alerts.png)
-
-To send GitLab alert notifications, copy the *URL* and *Authorization Key* into the [`webhook_configs`](https://prometheus.io/docs/alerting/configuration/#webhook_config) section of your Prometheus Alertmanager configuration:
-
-```yaml
-receivers:
- name: gitlab
- webhook_configs:
- - http_config:
- bearer_token: 9e1cbfcd546896a9ea8be557caf13a76
- send_resolved: true
- url: http://192.168.178.31:3001/root/manual_prometheus/prometheus/alerts/notify.json
- ...
-```
-
-In order for GitLab to associate your alerts with an [environment](../../../ci/environments/index.md), you need to configure a `gitlab_environment_name` label on the alerts you set up in Prometheus. The value of this should match the name of your Environment in GitLab.
-
-NOTE: **Note**
-In GitLab versions 13.1 and greater, you can configure your manually configured Prometheus server to use the [Generic alerts integration](generic_alerts.md).
-
-### Taking action on incidents **(ULTIMATE)**
-
->- [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/4925) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 11.11.
->- [From GitLab Ultimate 12.5](https://gitlab.com/gitlab-org/gitlab/-/issues/13401), when GitLab receives a recovery alert, it will automatically close the associated issue.
-
-Alerts can be used to trigger actions, like opening an issue automatically (disabled by default since `13.1`). To configure the actions:
-
-1. Navigate to your project's **Settings > Operations > Incidents**.
-1. Enable the option to create issues.
-1. Choose the [issue template](../description_templates.md) to create the issue from.
-1. Optionally, select whether to send an email notification to the developers of the project.
-1. Click **Save changes**.
-
-Once enabled, an issue will be opened automatically when an alert is triggered which contains values extracted from [alert's payload](https://prometheus.io/docs/alerting/configuration/#webhook_config
-):
-
-- Issue author: `GitLab Alert Bot`
-- Issue title: Extract from `annotations/title`, `annotations/summary` or `labels/alertname`
-- Alert `Summary`: A list of properties
- - `starts_at`: Alert start time via `startsAt`
- - `full_query`: Alert query extracted from `generatorURL`
- - Optional list of attached annotations extracted from `annotations/*`
-- Alert [GFM](../../markdown.md): GitLab Flavored Markdown from `annotations/gitlab_incident_markdown`
-
-When GitLab receives a **Recovery Alert**, it will automatically close the associated issue. This action will be recorded as a system message on the issue indicating that it was closed automatically by the GitLab Alert bot.
-
-To further customize the issue, you can add labels, mentions, or any other supported [quick action](../quick_actions.md) in the selected issue template, which will apply to all incidents. To limit quick actions or other information to only specific types of alerts, use the `annotations/gitlab_incident_markdown` field.
-
-Since [version 12.2](https://gitlab.com/gitlab-org/gitlab-foss/-/issues/63373), GitLab will tag each incident issue with the `incident` label automatically. If the label does not yet exist, it will be created automatically as well.
-
-If the metric exceeds the threshold of the alert for over 5 minutes, an email will be sent to all [Maintainers and Owners](../../permissions.md#project-members-permissions) of the project.
-
## Determining the performance impact of a merge
> - [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/10408) in GitLab 9.2.
@@ -1069,174 +154,3 @@ Performance data will be available for the duration it is persisted on the
Prometheus server.
![Merge Request with Performance Impact](img/merge_request_performance.png)
-
-## Embedding metric charts within GitLab Flavored Markdown
-
-### Embedding GitLab-managed Kubernetes metrics
-
-> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/29691) in GitLab 12.2.
-
-It is possible to display metrics charts within [GitLab Flavored Markdown](../../markdown.md#gitlab-flavored-markdown-gfm) fields such as issue or merge request descriptions. The maximum number of embedded charts allowed in a GitLab Flavored Markdown field is 100.
-
-This can be useful if you are sharing an application incident or performance
-metrics to others and want to have relevant information directly available.
-
-NOTE: **Note:**
-Requires [Kubernetes](prometheus_library/kubernetes.md) metrics.
-
-To display metric charts, include a link of the form `https://<root_url>/<project>/-/environments/<environment_id>/metrics`:
-
-![Embedded Metrics Markdown](img/embedded_metrics_markdown_v12_8.png)
-
-GitLab unfurls the link as an embedded metrics panel:
-
-![Embedded Metrics Rendered](img/embedded_metrics_rendered_v12_8.png)
-
-You can also embed a single chart. To get a link to a chart, click the
-**{ellipsis_v}** **More actions** menu in the upper right corner of the chart,
-and select **Copy link to chart**, as shown in this example:
-
-![Copy Link To Chart](img/copy_link_to_chart_v12_10.png)
-
-The following requirements must be met for the metric to unfurl:
-
-- The `<environment_id>` must correspond to a real environment.
-- Prometheus must be monitoring the environment.
-- The GitLab instance must be configured to receive data from the environment.
-- The user must be allowed access to the monitoring dashboard for the environment ([Reporter or higher](../../permissions.md)).
-- The dashboard must have data within the last 8 hours.
-
- If all of the above are true, then the metric will unfurl as seen below:
-
-![Embedded Metrics](img/view_embedded_metrics_v12_10.png)
-
-Metric charts may also be hidden:
-
-![Show Hide](img/hide_embedded_metrics_v12_10.png)
-
-You can open the link directly into your browser for a [detailed view of the data](#expand-panel).
-
-### Embedding metrics in issue templates
-
-It is also possible to embed either the default dashboard metrics or individual metrics in issue templates. For charts to render side-by-side, links to the entire metrics dashboard or individual metrics should be separated by either a comma or a space.
-
-![Embedded Metrics in issue templates](img/embed_metrics_issue_template.png)
-
-### Embedding metrics based on alerts in incident issues
-
-For [GitLab-managed alerting rules](#setting-up-alerts-for-prometheus-metrics), the issue will include an embedded chart for the query corresponding to the alert. The chart displays an hour of data surrounding the starting point of the incident, 30 minutes before and after.
-
-For [manually configured Prometheus instances](#manual-configuration-of-prometheus), a chart corresponding to the query can be included if these requirements are met:
-
-- The alert corresponds to an environment managed through GitLab.
-- The alert corresponds to a [range query](https://prometheus.io/docs/prometheus/latest/querying/api/#range-queries).
-- The alert contains the required attributes listed in the chart below.
-
-| Attributes | Required | Description |
-| ---------- | -------- | ----------- |
-| `annotations/gitlab_environment_name` | Yes | Name of the GitLab-managed environment corresponding to the alert |
-| One of `annotations/title`, `annotations/summary`, `labels/alertname` | Yes | Will be used as the chart title |
-| `annotations/gitlab_y_label` | No | Will be used as the chart's y-axis label |
-
-### Embedding Cluster Health Charts **(ULTIMATE)**
-
-> [Introduced](<https://gitlab.com/gitlab-org/gitlab/-/issues/40997>) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 12.9.
-
-[Cluster Health Metrics](../clusters/index.md#monitoring-your-kubernetes-cluster-ultimate) can also be embedded in [GitLab-flavored Markdown](../../markdown.md).
-
-To embed a metric chart, include a link to that chart in the form `https://<root_url>/<project>/-/cluster/<cluster_id>?<query_params>` anywhere that GitLab-flavored Markdown is supported. To generate and copy a link to the chart, follow the instructions in the [Cluster Health Metric documentation](../clusters/index.md#monitoring-your-kubernetes-cluster-ultimate).
-
-The following requirements must be met for the metric to unfurl:
-
-- The `<cluster_id>` must correspond to a real cluster.
-- Prometheus must be monitoring the cluster.
-- The user must be allowed access to the project cluster metrics.
-- The dashboards must be reporting data on the [Cluster Health Page](../clusters/index.md#monitoring-your-kubernetes-cluster-ultimate)
-
- If the above requirements are met, then the metric will unfurl as seen below.
-
-![Embedded Cluster Metric in issue descriptions](img/prometheus_cluster_health_embed_v12_9.png)
-
-### Embedding Grafana charts
-
-Grafana metrics can be embedded in [GitLab Flavored Markdown](../../markdown.md).
-
-#### Embedding charts via Grafana Rendered Images
-
-It is possible to embed live [Grafana](https://docs.gitlab.com/omnibus/settings/grafana.html) charts in issues, as a [direct linked rendered image](https://grafana.com/docs/grafana/latest/reference/share_panel/#direct-link-rendered-image).
-
-The sharing dialog within Grafana provides the link, as highlighted below.
-
-![Grafana Direct Linked Rendered Image](img/grafana_live_embed.png)
-
-NOTE: **Note:**
-For this embed to display correctly, the Grafana instance must be available to the target user, either as a public dashboard, or on the same network.
-
-Copy the link and add an image tag as [inline HTML](../../markdown.md#inline-html) in your Markdown. You may tweak the query parameters as required. For instance, removing the `&from=` and `&to=` parameters will give you a live chart. Here is example markup for a live chart from GitLab's public dashboard:
-
-```html
-<img src="https://dashboards.gitlab.com/d/RZmbBr7mk/gitlab-triage?orgId=1&refresh=30s&var-env=gprd&var-environment=gprd&var-prometheus=prometheus-01-inf-gprd&var-prometheus_app=prometheus-app-01-inf-gprd&var-backend=All&var-type=All&var-stage=main&from=1580444107655&to=1580465707655"/>
-```
-
-This will render like so:
-
-![Grafana dashboard embedded preview](img/grafana_embedded.png)
-
-#### Embedding charts via integration with Grafana HTTP API
-
-> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/31376) in GitLab 12.5.
-
-Each project can support integration with one Grafana instance. This configuration allows a user to copy a link to a panel in Grafana, then paste it into a GitLab Markdown field. The chart will be rendered in the GitLab chart format.
-
-Prerequisites for embedding from a Grafana instance:
-
-1. The datasource must be a Prometheus instance.
-1. The datasource must be proxyable, so the HTTP Access setting should be set to `Server`.
-
-![HTTP Proxy Access](img/http_proxy_access_v12_5.png)
-
-##### Setting up the Grafana integration
-
-1. [Generate an Admin-level API Token in Grafana.](https://grafana.com/docs/grafana/latest/http_api/auth/#create-api-token)
-1. In your GitLab project, navigate to **Settings > Operations > Grafana Authentication**.
-1. To enable the integration, check the "Active" checkbox.
-1. For "Grafana URL", enter the base URL of the Grafana instance.
-1. For "API Token", enter the Admin API Token you just generated.
-1. Click **Save Changes**.
-
-##### Generating a link to a chart
-
-1. In Grafana, navigate to the dashboard you wish to embed a panel from.
- ![Grafana Metric Panel](img/grafana_panel_v12_5.png)
-1. In the upper-left corner of the page, select a specific value for each variable required for the queries in the chart.
- ![Select Query Variables](img/select_query_variables_v12_5.png)
-1. In Grafana, click on a panel's title, then click **Share** to open the panel's sharing dialog to the **Link** tab. If you click the _dashboard's_ share panel instead, GitLab will attempt to embed the first supported panel on the dashboard (if available).
-1. If your Prometheus queries use Grafana's custom template variables, ensure that the "Template variables" option is toggled to **On**. Of Grafana global template variables, only `$__interval`, `$__from`, and `$__to` are currently supported. Toggle **On** the "Current time range" option to specify the time range of the chart. Otherwise, the default range will be the last 8 hours.
- ![Grafana Sharing Dialog](img/grafana_sharing_dialog_v12_5.png)
-1. Click **Copy** to copy the URL to the clipboard.
-1. In GitLab, paste the URL into a Markdown field and save. The chart will take a few moments to render.
- ![GitLab Rendered Grafana Panel](img/rendered_grafana_embed_v12_5.png)
-
-## Metrics dashboard visibility
-
-> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/201924) in GitLab 13.0.
-
-You can set the visibility of the metrics dashboard to **Only Project Members**
-or **Everyone With Access**. When set to **Everyone with Access**, the metrics
-dashboard is visible to authenticated and non-authenticated users.
-
-## Troubleshooting
-
-When troubleshooting issues with a managed Prometheus app, it is often useful to
-[view the Prometheus UI](../../../development/prometheus.md#access-the-ui-of-a-prometheus-managed-application-in-kubernetes).
-
-### "No data found" error on Metrics dashboard page
-
-If the "No data found" screen continues to appear, it could be due to:
-
-- No successful deployments have occurred to this environment.
-- Prometheus does not have performance data for this environment, or the metrics
- are not labeled correctly. To test this, connect to the Prometheus server and
- [run a query](prometheus_library/kubernetes.md#metrics-supported), replacing `$CI_ENVIRONMENT_SLUG`
- with the name of your environment.
-- You may need to re-add the GitLab predefined common metrics. This can be done by running the [import common metrics Rake task](../../../administration/raketasks/maintenance.md#import-common-metrics).
diff --git a/doc/user/project/integrations/prometheus_library/nginx_ingress.md b/doc/user/project/integrations/prometheus_library/nginx_ingress.md
index b2bc217e8bf..7bebe7b1e65 100644
--- a/doc/user/project/integrations/prometheus_library/nginx_ingress.md
+++ b/doc/user/project/integrations/prometheus_library/nginx_ingress.md
@@ -8,7 +8,8 @@ info: To determine the technical writer assigned to the Stage/Group associated w
> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/22133) in GitLab 11.7.
-NOTE: **Note:** NGINX Ingress versions prior to 0.16.0 offer an included [VTS Prometheus metrics exporter](nginx_ingress_vts.md), which exports metrics different than the built-in metrics.
+NOTE: **Note:**
+NGINX Ingress versions prior to 0.16.0 offer an included [VTS Prometheus metrics exporter](nginx_ingress_vts.md), which exports metrics different than the built-in metrics.
GitLab has support for automatically detecting and monitoring the Kubernetes NGINX Ingress controller. This is provided by leveraging the built-in Prometheus metrics included with Kubernetes NGINX Ingress controller [version 0.16.0](https://github.com/kubernetes/ingress-nginx/blob/master/Changelog.md#0160) onward.
diff --git a/doc/user/project/integrations/prometheus_library/nginx_ingress_vts.md b/doc/user/project/integrations/prometheus_library/nginx_ingress_vts.md
index 6ba0a7610f6..326931e9790 100644
--- a/doc/user/project/integrations/prometheus_library/nginx_ingress_vts.md
+++ b/doc/user/project/integrations/prometheus_library/nginx_ingress_vts.md
@@ -8,7 +8,8 @@ info: To determine the technical writer assigned to the Stage/Group associated w
> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/13438) in GitLab 9.5.
-NOTE: **Note:** [NGINX Ingress version 0.16](nginx_ingress.md) and above have built-in Prometheus metrics, which are different than the VTS based metrics.
+NOTE: **Note:**
+[NGINX Ingress version 0.16](nginx_ingress.md) and above have built-in Prometheus metrics, which are different than the VTS based metrics.
GitLab has support for automatically detecting and monitoring the Kubernetes NGINX Ingress controller. This is provided by leveraging the included VTS Prometheus metrics exporter in [version 0.9.0](https://github.com/kubernetes/ingress-nginx/blob/master/Changelog.md#09-beta1) through [0.15.x](https://github.com/kubernetes/ingress-nginx/blob/master/Changelog.md#0150).
diff --git a/doc/user/project/integrations/prometheus_units.md b/doc/user/project/integrations/prometheus_units.md
index 7b216ced1cc..ee4f3ed77d4 100644
--- a/doc/user/project/integrations/prometheus_units.md
+++ b/doc/user/project/integrations/prometheus_units.md
@@ -1,175 +1,5 @@
---
-stage: Monitor
-group: APM
-info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers
+redirect_to: '../../../operations/metrics/dashboards/yaml_number_format.md'
---
-# Unit formats reference
-
-> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/201999) in GitLab 12.9.
-
-You can select units to format your charts by adding `format` to your
-[axis configuration](prometheus.md#dashboard-yaml-properties).
-
-## Internationalization and localization
-
-Currently, your [internationalization and localization options](https://en.wikipedia.org/wiki/Internationalization_and_localization) for number formatting are dependent on the system you are using i.e. your OS or browser.
-
-## Engineering Notation
-
-For generic or default data, numbers are formatted according to the current locale in [engineering notation](https://en.wikipedia.org/wiki/Engineering_notation).
-
-While an [engineering notation exists for the web](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat), GitLab uses a version based off the [scientific notation](https://en.wikipedia.org/wiki/Scientific_notation). GitLab formatting acts in accordance with SI prefixes. For example, using GitLab notation, `1500.00` becomes `1.5k` instead of `1.5E3`. Keep this distinction in mind when using the engineering notation for your metrics.
-
-Formats: `engineering`
-
-SI prefixes:
-
-| Name | Symbol | Value |
-| ---------- | ------- | -------------------------- |
-| `yotta` | Y | 1000000000000000000000000 |
-| `zetta` | Z | 1000000000000000000000 |
-| `exa` | E | 1000000000000000000 |
-| `peta` | P | 1000000000000000 |
-| `tera` | T | 1000000000000 |
-| `giga` | G | 1000000000 |
-| `mega` | M | 1000000 |
-| `kilo` | k | 1000 |
-| `milli` | m | 0.001 |
-| `micro` | μ | 0.000001 |
-| `nano` | n | 0.000000001 |
-| `pico` | p | 0.000000000001 |
-| `femto` | f | 0.000000000000001 |
-| `atto` | a | 0.000000000000000001 |
-| `zepto` | z | 0.000000000000000000001 |
-| `yocto` | y | 0.000000000000000000000001 |
-
-**Examples:**
-
-| Data | Displayed |
-| --------------------------------- | --------- |
-| `0.000000000000000000000008` | 8y |
-| `0.000000000000000000008` | 8z |
-| `0.000000000000000008` | 8a |
-| `0.000000000000008` | 8f |
-| `0.000000000008` | 8p |
-| `0.000000008` | 8n |
-| `0.000008` | 8μ |
-| `0.008` | 8m |
-| `10` | 10 |
-| `1080` | 1.08k |
-| `18000` | 18k |
-| `18888` | 18.9k |
-| `188888` | 189k |
-| `18888888` | 18.9M |
-| `1888888888` | 1.89G |
-| `1888888888888` | 1.89T |
-| `1888888888888888` | 1.89P |
-| `1888888888888888888` | 1.89E |
-| `1888888888888888888888` | 1.89Z |
-| `1888888888888888888888888` | 1.89Y |
-| `1888888888888888888888888888` | 1.89e+27 |
-
-## Numbers
-
-For number data, numbers are formatted according to the current locale.
-
-Formats: `number`
-
-**Examples:**
-
-| Data | Displayed |
-| ---------- | --------- |
-| `10` | 1 |
-| `1000` | 1,000 |
-| `1000000` | 1,000,000 |
-
-## Percentage
-
-For percentage data, format numbers in the chart with a `%` symbol.
-
-Formats supported: `percent`, `percentHundred`
-
-**Examples:**
-
-| Format | Data | Displayed |
-| ---------------- | ----- | --------- |
-| `percent` | `0.5` | 50% |
-| `percent` | `1` | 100% |
-| `percent` | `2` | 200% |
-| `percentHundred` | `50` | 50% |
-| `percentHundred` | `100` | 100% |
-| `percentHundred` | `200` | 200% |
-
-## Duration
-
-For time durations, format numbers in the chart with a time unit symbol.
-
-Formats supported: `milliseconds`, `seconds`
-
-**Examples:**
-
-| Format | Data | Displayed |
-| -------------- | ------ | --------- |
-| `milliseconds` | `10` | 10ms |
-| `milliseconds` | `500` | 100ms |
-| `milliseconds` | `1000` | 1000ms |
-| `seconds` | `10` | 10s |
-| `seconds` | `500` | 500s |
-| `seconds` | `1000` | 1000s |
-
-## Digital (Metric)
-
-Converts a number of bytes using metric prefixes. It scales to
-use the unit that's the best fit.
-
-Formats supported:
-
-- `decimalBytes`
-- `kilobytes`
-- `megabytes`
-- `gigabytes`
-- `terabytes`
-- `petabytes`
-
-**Examples:**
-
-| Format | Data | Displayed |
-| -------------- | --------- | --------- |
-| `decimalBytes` | `1` | 1B |
-| `decimalBytes` | `1000` | 1kB |
-| `decimalBytes` | `1000000` | 1MB |
-| `kilobytes` | `1` | 1kB |
-| `kilobytes` | `1000` | 1MB |
-| `kilobytes` | `1000000` | 1GB |
-| `megabytes` | `1` | 1MB |
-| `megabytes` | `1000` | 1GB |
-| `megabytes` | `1000000` | 1TB |
-
-## Digital (IEC)
-
-Converts a number of bytes using binary prefixes. It scales to
-use the unit that's the best fit.
-
-Formats supported:
-
-- `bytes`
-- `kibibytes`
-- `mebibytes`
-- `gibibytes`
-- `tebibytes`
-- `pebibytes`
-
-**Examples:**
-
-| Format | Data | Displayed |
-| ----------- | ------------- | --------- |
-| `bytes` | `1` | 1B |
-| `bytes` | `1024` | 1KiB |
-| `bytes` | `1024 * 1024` | 1MiB |
-| `kibibytes` | `1` | 1KiB |
-| `kibibytes` | `1024` | 1MiB |
-| `kibibytes` | `1024 * 1024` | 1GiB |
-| `mebibytes` | `1` | 1MiB |
-| `mebibytes` | `1024` | 1GiB |
-| `mebibytes` | `1024 * 1024` | 1TiB |
+This document was moved to [another location](../../../operations/metrics/dashboards/yaml_number_format.md).
diff --git a/doc/user/project/integrations/redmine.md b/doc/user/project/integrations/redmine.md
index 9e1cdf0490c..c92ddf38ad2 100644
--- a/doc/user/project/integrations/redmine.md
+++ b/doc/user/project/integrations/redmine.md
@@ -7,7 +7,6 @@
| Field | Description |
| ----- | ----------- |
- | `description` | A name for the issue tracker (to differentiate between instances, for example) |
| `project_url` | The URL to the project in Redmine which is being linked to this GitLab project |
| `issues_url` | The URL to the issue in Redmine project that is linked to this GitLab project. Note that the `issues_url` requires `:id` in the URL. This ID is used by GitLab as a placeholder to replace the issue number. |
| `new_issue_url` | This is the URL to create a new issue in Redmine for the project linked to this GitLab project. **This is currently not being used and will be removed in a future release.** |
diff --git a/doc/user/project/integrations/services_templates.md b/doc/user/project/integrations/services_templates.md
index 8a88df88629..bc2bdde2f64 100644
--- a/doc/user/project/integrations/services_templates.md
+++ b/doc/user/project/integrations/services_templates.md
@@ -1,28 +1,25 @@
-# Services templates
+# Service templates
-A GitLab administrator can add a service template that sets a default for each
-project. After a service template is enabled, it will be applied to **all**
-projects that don't have it already enabled and its details will be pre-filled
-on the project's Service page. By disabling the template, it will be disabled
-for new projects only.
+Using a service template, GitLab administrators can provide default values for configuring integrations at the project level.
+
+When you enable a service template, the defaults are applied to **all** projects that do not
+already have the integration enabled or do not otherwise have custom values saved.
+The values are pre-filled on each project's configuration page for the applicable integration.
+
+If you disable the template, these values no longer appear as defaults, while
+any values already saved for an integration remain unchanged.
## Enable a service template
Navigate to the **Admin Area > Service Templates** and choose the service
template you wish to create.
-## Services for external issue trackers
+## Service for external issue trackers
-In the image below you can see how a service template for Redmine would look
-like.
+The following image shows an example service template for Redmine.
![Redmine service template](img/services_templates_redmine_example.png)
----
-
For each project, you will still need to configure the issue tracking
URLs by replacing `:issues_tracker_id` in the above screenshot with the ID used
-by your external issue tracker. Prior to GitLab v7.8, this ID was configured in
-the project settings, and GitLab would automatically update the URL configured
-in `gitlab.yml`. This behavior is now deprecated and all issue tracker URLs
-must be configured directly within the project's **Integrations** settings.
+by your external issue tracker.
diff --git a/doc/user/project/integrations/slack.md b/doc/user/project/integrations/slack.md
index 79fc8eceddf..6c5dc787c5e 100644
--- a/doc/user/project/integrations/slack.md
+++ b/doc/user/project/integrations/slack.md
@@ -1,25 +1,45 @@
# Slack Notifications Service
-The Slack Notifications Service allows your GitLab project to send events (e.g. issue created) to your existing Slack team as notifications. This requires configurations in both Slack and GitLab.
+The Slack Notifications Service allows your GitLab project to send events
+(such as issue creation) to your existing Slack team as notifications. Setting up
+Slack notifications requires configuration changes for both Slack and GitLab.
-> Note: You can also use Slack slash commands to control GitLab inside Slack. This is the separately configured [Slack slash commands](slack_slash_commands.md).
+NOTE: **Note:**
+You can also use Slack slash commands to control GitLab inside Slack. This is the
+separately configured [Slack slash commands](slack_slash_commands.md).
-## Slack Configuration
+## Slack configuration
1. Sign in to your Slack team and [start a new Incoming WebHooks configuration](https://my.slack.com/services/new/incoming-webhook).
-1. Select the Slack channel where notifications will be sent to by default. Click the **Add Incoming WebHooks integration** button to add the configuration.
-1. Copy the **Webhook URL**, which we'll use later in the GitLab configuration.
+1. Select the Slack channel where notifications will be sent to by default.
+ Click the **Add Incoming WebHooks integration** button to add the configuration.
+1. Copy the **Webhook URL**, which we will use later in the GitLab configuration.
-## GitLab Configuration
+## GitLab configuration
-1. Navigate to the [Integrations page](overview.md#accessing-integrations) in your project's settings, i.e. **Project > Settings > Integrations**.
+1. Open your project's page, and navigate to your project's
+ [Integrations page](overview.md#accessing-integrations) at
+ **{settings}** **Settings > Integrations**.
1. Select the **Slack notifications** integration to configure it.
-1. Ensure that the **Active** toggle is enabled.
-1. Check the checkboxes corresponding to the GitLab events you want to send to Slack as a notification.
-1. For each event, optionally enter the Slack channel names where you want to send the event, separated by a comma. If left empty, the event will be sent to the default channel that you configured in the Slack Configuration step. **Note:** Usernames and private channels are not supported. To send direct messages, use the Member ID found under user's Slack profile.
-1. Paste the **Webhook URL** that you copied from the Slack Configuration step.
-1. Optionally customize the Slack bot username that will be sending the notifications.
-1. Configure the remaining options and click `Save changes`.
+1. Click **Enable integration**.
+1. In **Trigger**, select the checkboxes for each type of GitLab event to send to Slack as a
+ notification. See [Triggers available for Slack notifications](#triggers-available-for-slack-notifications)
+ for a full list. By default, messages are sent to the channel you configured during
+ [Slack integration](#slack-configuration).
+1. (Optional) To send messages to a different channel, multiple channels, or as a direct message:
+ - To send messages to channels, enter the Slack channel names, separated by commas.
+ - To send direct messages, use the Member ID found in the user's Slack profile.
+
+ NOTE: **Note:**
+ Usernames and private channels are not supported.
+
+1. In **Webhook**, provide the webhook URL that you copied from the
+ [Slack integration](#slack-configuration) step.
+1. (Optional) In **Username**, provide the username of the Slack bot that sends the notifications.
+1. Select the **Notify only broken pipelines** check box to only notify on failures.
+1. In the **Branches to be notified** select box, choose which types of branches
+ to send notifications for.
+1. Click **Test settings and save changes**.
Your Slack team will now start receiving GitLab event notifications as configured.
@@ -43,14 +63,14 @@ The following triggers are available for Slack notifications:
## Troubleshooting
-If you're having trouble with the Slack integration not working, then start by
+If your Slack integration is not working, start troubleshooting by
searching through the [Sidekiq logs](../../../administration/logs.md#sidekiqlog)
for errors relating to your Slack service.
### Something went wrong on our end
-This is a generic error shown in the GitLab UI and doesn't mean much by itself.
-You'll need to look in [the logs](../../../administration/logs.md#productionlog) to find
+This is a generic error shown in the GitLab UI and does not mean much by itself.
+Review [the logs](../../../administration/logs.md#productionlog) to find
an error message and keep troubleshooting from there.
### `certificate verify failed`
@@ -83,10 +103,10 @@ result = Net::HTTP.get(URI('https://<SLACK URL>'));0
result = Net::HTTP.get(URI('https://<GITLAB URL>'));0
```
-If it's an issue with GitLab not trusting HTTPS connections to itself, then you may simply
+If GitLab is not trusting HTTPS connections to itself, then you may
need to [add your certificate to GitLab's trusted certificates](https://docs.gitlab.com/omnibus/settings/ssl.html#install-custom-public-certificates).
-If it's an issue with GitLab not trusting connections to Slack, then the GitLab
-OpenSSL trust store probably got messed up somehow. Typically this is from overriding
-the trust store with `gitlab_rails['env'] = {"SSL_CERT_FILE" => "/path/to/file.pem"}`
+If GitLab is not trusting connections to Slack, then the GitLab
+OpenSSL trust store is incorrect. Some typical causes: overriding
+the trust store with `gitlab_rails['env'] = {"SSL_CERT_FILE" => "/path/to/file.pem"}`,
or by accidentally modifying the default CA bundle `/opt/gitlab/embedded/ssl/certs/cacert.pem`.
diff --git a/doc/user/project/integrations/youtrack.md b/doc/user/project/integrations/youtrack.md
index 119a53f5c35..e067ab6071e 100644
--- a/doc/user/project/integrations/youtrack.md
+++ b/doc/user/project/integrations/youtrack.md
@@ -13,7 +13,6 @@ To enable YouTrack integration in a project:
| Field | Description |
|:----------------|:------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
- | **Description** | Name for the issue tracker (to differentiate between instances, for example). |
| **Project URL** | URL to the project in YouTrack which is being linked to this GitLab project. |
| **Issues URL** | URL to the issue in YouTrack project that is linked to this GitLab project. Note that the **Issues URL** requires `:id` in the URL. This ID is used by GitLab as a placeholder to replace the issue number. |
diff --git a/doc/user/project/issue_board.md b/doc/user/project/issue_board.md
index 89b17609698..1831563332f 100644
--- a/doc/user/project/issue_board.md
+++ b/doc/user/project/issue_board.md
@@ -42,8 +42,6 @@ Check all the [GitLab Enterprise features for issue boards](#gitlab-enterprise-f
![GitLab issue boards - Premium](img/issue_boards_premium.png)
----
-
## How it works
The Issue Board feature builds on GitLab's existing
@@ -98,8 +96,6 @@ If you have the labels "**backend**", "**frontend**", "**staging**", and
![issue card moving](img/issue_board_move_issue_card_list.png)
----
-
### Use cases for multiple issue boards
With [multiple issue boards](#multiple-issue-boards),
@@ -252,8 +248,6 @@ clicking **View scope**.
![Viewing board configuration](img/issue_board_view_scope.png)
----
-
### Focus mode
> - [Introduced]((https://about.gitlab.com/releases/2017/04/22/gitlab-9-1-released/#issue-boards-focus-mode-ees-eep)) in [GitLab Starter](https://about.gitlab.com/pricing/) 9.1.
@@ -275,8 +269,6 @@ especially in combination with [assignee lists](#assignee-lists-premium).
![issue board summed weights](img/issue_board_summed_weights.png)
----
-
### Group issue boards **(PREMIUM)**
> [Introduced](https://about.gitlab.com/releases/2017/09/22/gitlab-10-0-released/#group-issue-boards) in [GitLab Premium](https://about.gitlab.com/pricing/) 10.0.
@@ -292,8 +284,6 @@ Multiple group issue boards were originally [introduced](https://about.gitlab.co
![Group issue board](img/group_issue_board.png)
----
-
### Assignee lists **(PREMIUM)**
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/5784) in [GitLab Premium](https://about.gitlab.com/pricing/) 11.0.
@@ -313,8 +303,6 @@ To remove an assignee list, just as with a label list, click the trash icon.
![Assignee lists](img/issue_board_assignee_lists.png)
----
-
### Milestone lists **(PREMIUM)**
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/6469) in [GitLab Premium](https://about.gitlab.com/pricing/) 11.2.
@@ -332,8 +320,6 @@ As in other list types, click the trash icon to remove a list.
![Milestone lists](img/issue_board_milestone_lists.png)
----
-
## Work In Progress limits **(STARTER)**
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/11403) in GitLab 12.7
@@ -365,8 +351,6 @@ status.
![Blocked issues](img/issue_boards_blocked_icon_v12_8.png)
----
-
## Actions you can take on an issue board
- [Create a new list](#create-a-new-list).
@@ -437,8 +421,6 @@ the list by filtering by author, assignee, milestone, and label.
![Bulk adding issues to lists](img/issue_boards_add_issues_modal.png)
----
-
### Remove an issue from a list
Removing an issue from a list can be done by clicking the issue card and then
@@ -447,8 +429,6 @@ respective label is removed.
![Remove issue from list](img/issue_boards_remove_issue.png)
----
-
### Filter issues
You should be able to use the filters on top of your issue board to show only
@@ -492,8 +472,6 @@ to another list the label changes and a system not is recorded.
![issue board system notes](img/issue_board_system_notes.png)
----
-
### Drag issues between lists
When dragging issues between lists, different behavior occurs depending on the source list and the target list.
@@ -518,8 +496,6 @@ To select and move multiple cards:
![Multi-select Issue Cards](img/issue_boards_multi_select_v12_4.png)
----
-
### Issue ordering in a list
When visiting a board, issues appear ordered in any list. You're able to change
diff --git a/doc/user/project/issues/crosslinking_issues.md b/doc/user/project/issues/crosslinking_issues.md
index 6cc31a45309..c721bef8f4d 100644
--- a/doc/user/project/issues/crosslinking_issues.md
+++ b/doc/user/project/issues/crosslinking_issues.md
@@ -31,7 +31,8 @@ git commit -m "this is my commit message. Related to https://gitlab.com/<usernam
Of course, you can replace `gitlab.com` with the URL of your own GitLab instance.
-NOTE: **Note:** Linking your first commit to your issue is going to be relevant
+NOTE: **Note:**
+Linking your first commit to your issue is going to be relevant
for tracking your process with [GitLab Cycle Analytics](https://about.gitlab.com/stages-devops-lifecycle/value-stream-analytics/).
It will measure the time taken for planning the implementation of that issue,
which is the time between creating an issue and making the first commit.
diff --git a/doc/user/project/issues/csv_import.md b/doc/user/project/issues/csv_import.md
index d67b135186f..807f4fcffdf 100644
--- a/doc/user/project/issues/csv_import.md
+++ b/doc/user/project/issues/csv_import.md
@@ -7,7 +7,8 @@ Issues can be imported to a project by uploading a CSV file with the columns
The user uploading the CSV file will be set as the author of the imported issues.
-NOTE: **Note:** A permission level of [Developer](../../permissions.md), or higher, is required
+NOTE: **Note:**
+A permission level of [Developer](../../permissions.md), or higher, is required
to import issues.
## Prepare for the import
diff --git a/doc/user/project/issues/design_management.md b/doc/user/project/issues/design_management.md
index ac397592a3b..618506ea7ee 100644
--- a/doc/user/project/issues/design_management.md
+++ b/doc/user/project/issues/design_management.md
@@ -30,9 +30,11 @@ to be enabled:
project level, navigate to your project's **Settings > General**, expand **Visibility, project features, permissions**
and enable **Git Large File Storage**.
-Design Management requires that projects are using
-[hashed storage](../../../administration/repository_storage_types.md#hashed-storage)
-(the default storage type since v10.0).
+Design Management also requires that projects are using
+[hashed storage](../../../administration/raketasks/storage.md#migrate-to-hashed-storage). Since
+ GitLab 10.0, newly created projects use hashed storage by default. A GitLab admin can verify the storage type of a
+project by navigating to **Admin Area > Projects** and then selecting the project in question.
+A project can be identified as hashed-stored if its *Gitaly relative path* contains `@hashed`.
If the requirements are not met, the **Designs** tab displays a message to the user.
@@ -47,6 +49,7 @@ and [PDFs](https://gitlab.com/gitlab-org/gitlab/-/issues/32811) is planned for a
## Limitations
- Design uploads are limited to 10 files at a time.
+- From GitLab 13.1, Design filenames are limited to 255 characters.
- Design Management data
[isn't deleted when a project is destroyed](https://gitlab.com/gitlab-org/gitlab/-/issues/13429) yet.
- Design Management data [won't be moved](https://gitlab.com/gitlab-org/gitlab/-/issues/13426)
@@ -57,20 +60,62 @@ and [PDFs](https://gitlab.com/gitlab-org/gitlab/-/issues/32811) is planned for a
- Only the latest version of the designs can be deleted.
- Deleted designs cannot be recovered but you can see them on previous designs versions.
-## The Design Management page
+## GitLab-Figma plugin
-Navigate to the **Design Management** page from any issue by clicking the **Designs** tab:
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-figma-plugin/-/issues/2) in GitLab 13.2.
-![Designs tab](img/design_management_v12_3.png)
+Connect your design environment with your source code management in a seamless workflow. The GitLab-Figma plugin makes it quick and easy to collaborate in GitLab by bringing the work of product designers directly from Figma to GitLab Issues as uploaded Designs.
+
+To use the plugin, install it from the [Figma Directory](https://www.figma.com/community/plugin/860845891704482356)
+and connect to GitLab through a personal access token. The details are explained in the [plugin documentation](https://gitlab.com/gitlab-org/gitlab-figma-plugin/-/wikis/home).
+
+## The Design Management section
+
+> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/223193) in GitLab 13.2, Designs are displayed directly on the issue description rather than on a separate tab.
+> - The new display is deployed behind a feature flag, enabled by default.
+> - It's enabled on GitLab.com.
+> - It cannot be enabled or disabled per-project.
+> - It's recommended for production use.
+> - For GitLab self-managed instances, GitLab administrators can opt to [disable it](#enable-or-disable-displaying-designs-on-the-issue-description-core-only). If disabled, it will move Designs back to the **Designs** tab.
+
+You can find to the **Design Management** section in the issue description:
+
+![Designs section](img/design_management_v13_2.png)
+
+### Enable or disable displaying Designs on the issue description **(CORE ONLY)**
+
+Displaying Designs on the issue description is under development but ready for
+production use. It is deployed behind a feature flag that is **enabled by
+default**.
+[GitLab administrators with access to the GitLab Rails console](../../../administration/feature_flags.md)
+can opt to disable it for your instance.
+
+To disable it:
+
+```ruby
+Feature.disable(:design_management_moved)
+```
+
+To enable it:
+
+```ruby
+Feature.enable(:design_management_moved)
+```
+
+By disabling this feature, designs will be displayed on the **Designs** tab
+instead of directly on the issue description.
## Adding designs
-To upload design images, click the **Upload Designs** button and select images to upload.
+To upload Design images, drag files from your computer and drop them in the Design Management section,
+or click **upload** to select images from your file browser:
+
+![Designs empty state](img/design_management_upload_v13.2.png)
[Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/34353) in [GitLab Premium](https://about.gitlab.com/pricing/) 12.9,
you can drag and drop designs onto the dedicated drop zone to upload them.
-![Drag and drop design uploads](img/design_drag_and_drop_uploads_v12_9.png)
+![Drag and drop design uploads](img/design_drag_and_drop_uploads_v13_2.png)
[Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/202634)
in GitLab 12.10, you can also copy images from your file system and
@@ -239,3 +284,13 @@ To disable it:
```ruby
Feature.disable(:design_management_reference_filter_gfm_pipeline)
```
+
+## Design activity records
+
+> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/33051) in GitLab 13.1.
+> - [Feature flag removed](https://gitlab.com/gitlab-org/gitlab/-/issues/225205) in GitLab 13.2.
+
+User activity events on designs (creation, deletion, and updates) are tracked by GitLab and
+displayed on the [user profile](../../profile/index.md#user-profile),
+[group](../../group/index.md#view-group-activity),
+and [project](../index.md#project-activity) activity pages.
diff --git a/doc/user/project/issues/img/design_drag_and_drop_uploads_v13_2.png b/doc/user/project/issues/img/design_drag_and_drop_uploads_v13_2.png
new file mode 100644
index 00000000000..d60f1234b6d
--- /dev/null
+++ b/doc/user/project/issues/img/design_drag_and_drop_uploads_v13_2.png
Binary files differ
diff --git a/doc/user/project/issues/img/design_management_upload_v13.2.png b/doc/user/project/issues/img/design_management_upload_v13.2.png
new file mode 100644
index 00000000000..1d4b10307fc
--- /dev/null
+++ b/doc/user/project/issues/img/design_management_upload_v13.2.png
Binary files differ
diff --git a/doc/user/project/issues/img/design_management_v13_2.png b/doc/user/project/issues/img/design_management_v13_2.png
new file mode 100644
index 00000000000..0a6e2be17ab
--- /dev/null
+++ b/doc/user/project/issues/img/design_management_v13_2.png
Binary files differ
diff --git a/doc/user/project/issues/index.md b/doc/user/project/issues/index.md
index 06a80672769..9113f5344ba 100644
--- a/doc/user/project/issues/index.md
+++ b/doc/user/project/issues/index.md
@@ -175,8 +175,6 @@ requires [GraphQL](../../../api/graphql/index.md) to be enabled.
![Similar issues](img/similar_issues.png)
----
-
### Health status **(ULTIMATE)**
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/36427) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 12.10.
diff --git a/doc/user/project/issues/issue_data_and_actions.md b/doc/user/project/issues/issue_data_and_actions.md
index 6d34f91d37f..7f236d04fb6 100644
--- a/doc/user/project/issues/issue_data_and_actions.md
+++ b/doc/user/project/issues/issue_data_and_actions.md
@@ -88,7 +88,7 @@ An issue can be assigned to:
- Yourself.
- Another person.
-- [Many people](#multiple-assignees-STARTER). **(STARTER)**
+- [Many people](#multiple-assignees-starter). **(STARTER)**
The assignee(s) can be changed as often as needed. The idea is that the assignees are
responsible for that issue until it's reassigned to someone else to take it from there.
@@ -253,7 +253,7 @@ Also:
### Create Merge Request
-Create a new branch and [WIP merge request](../merge_requests/work_in_progress_merge_requests.md)
+Create a new branch and [**Draft** merge request](../merge_requests/work_in_progress_merge_requests.md)
in one action. The branch will be named `issuenumber-title` by default, but you can
choose any name, and GitLab will verify that it is not already in use. The merge request
will automatically inherit the milestone and labels of the issue, and will be set to
diff --git a/doc/user/project/issues/managing_issues.md b/doc/user/project/issues/managing_issues.md
index 08e3164b2eb..babc5030337 100644
--- a/doc/user/project/issues/managing_issues.md
+++ b/doc/user/project/issues/managing_issues.md
@@ -45,8 +45,7 @@ There are many ways to get to the New Issue form from within a project:
### Elements of the New Issue form
-> Ability to add the new issue to an epic [was introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/13847)
-> in [GitLab Premium](https://about.gitlab.com/pricing/) 13.1.
+> Ability to add the new issue to an epic [was introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/13847) in [GitLab Premium](https://about.gitlab.com/pricing/) 13.1.
![New issue from the issues list](img/new_issue_v13_1.png)
@@ -76,7 +75,7 @@ create issues for the same project.
![Create issue from group-level issue tracker](img/create_issue_from_group_level_issue_tracker.png)
-### New issue via Service Desk **(STARTER)**
+### New issue via Service Desk
Enable [Service Desk](../service_desk.md) for your project and offer email support.
By doing so, when your customer sends a new email, a new issue can be created in
diff --git a/doc/user/project/labels.md b/doc/user/project/labels.md
index 406938519b1..9886ef91f16 100644
--- a/doc/user/project/labels.md
+++ b/doc/user/project/labels.md
@@ -54,7 +54,7 @@ and edit labels.
View the project labels list by going to the project and clicking **Issues > Labels**.
The list includes all labels that are defined at the project level, as well as all
-labels inherited from the parent group. You can filter the list by entering a search
+labels inherited from the immediate parent group. You can filter the list by entering a search
query at the top and clicking search (**{search}**).
To create a new project label:
@@ -94,7 +94,7 @@ also be merged.
All issues, merge requests, issue board lists, issue board filters, and label subscriptions
with the old labels will be assigned to the new group label.
-WARNING: **Caution:**
+CAUTION: **Caution:**
Promoting a label is a permanent action, and cannot be reversed.
To promote a project label to a group label:
@@ -251,3 +251,16 @@ If you sort by `Priority`, GitLab uses this sort comparison order:
Ties are broken arbitrarily.
![Labels sort priority](img/labels_sort_priority.png)
+
+## Troubleshooting
+
+### Some label titles end with `_duplicate<number>`
+
+In specific circumstances it was possible to create labels with duplicate titles in the same
+namespace.
+
+To resolve the duplication, [in GitLab 13.2](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/21384)
+and later, some duplicate labels have `_duplicate<number>` appended to their titles.
+
+You can safely change these labels' titles if you prefer.
+For details of the original problem, see [issue 30390](https://gitlab.com/gitlab-org/gitlab/issues/30390).
diff --git a/doc/user/project/members/index.md b/doc/user/project/members/index.md
index 787a74b0065..3eb411aef18 100644
--- a/doc/user/project/members/index.md
+++ b/doc/user/project/members/index.md
@@ -128,3 +128,30 @@ If you change your mind before your request is approved, just click the
## Share project with group
Alternatively, you can [share a project with an entire group](share_project_with_groups.md) instead of adding users one by one.
+
+## Remove a member from the project
+
+Only users with permissions of [Owner](../../permissions.md#group-members-permissions) can manage
+project members.
+
+You can remove a user from the project if the given member has a direct membership in the project.
+If membership is inherited from a parent group, then the member can be removed only from the parent
+group itself.
+
+When removing a member, you can decide whether to unassign the user from all issues and merge
+requests they are currently assigned or leave the assignments as they are.
+
+- **Unassigning the removed member** from all issues and merge requests might be helpful when a user
+ is leaving a private project and you wish to revoke their access to any issues and merge requests
+ they are assigned.
+- **Keeping the issues and merge requests assigned** might be helpful for projects that accept public
+ contributions where a user doesn't have to be a member to be able to contribute to issues and
+ merge requests.
+
+To remove a member from a project:
+
+1. In a project, go to **{users}** **Members**.
+1. Click the **Delete** **{remove}** button next to a project member you want to remove.
+ A **Remove member** modal appears.
+1. (Optional) Select the **Also unassign this user from related issues and merge requests** checkbox.
+1. Click **Remove member**.
diff --git a/doc/user/project/merge_requests/accessibility_testing.md b/doc/user/project/merge_requests/accessibility_testing.md
index 417b85a6082..f3a0aac9ff4 100644
--- a/doc/user/project/merge_requests/accessibility_testing.md
+++ b/doc/user/project/merge_requests/accessibility_testing.md
@@ -1,4 +1,7 @@
---
+stage: Verify
+group: Testing
+info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers
type: reference, howto
---
@@ -20,7 +23,10 @@ analyzed to a file called `accessibility`.
## Accessibility Merge Request widget
-[Since GitLab 13.0](https://gitlab.com/gitlab-org/gitlab/-/issues/39425), in addition to the report artifact that is created, GitLab will also show the
+> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/39425) in GitLab 13.0 behind the disabled [feature flag](../../../administration/feature_flags.md) `:accessibility_report_view`.
+> - [Feature Flag removed](https://gitlab.com/gitlab-org/gitlab/-/issues/217372) in GitLab 13.1.
+
+In addition to the report artifact that is created, GitLab will also show the
Accessibility Report in the merge request widget area:
![Accessibility Merge Request Widget](img/accessibility_mr_widget_v13_0.png)
diff --git a/doc/user/project/merge_requests/browser_performance_testing.md b/doc/user/project/merge_requests/browser_performance_testing.md
index 390d480724d..10457e40e0b 100644
--- a/doc/user/project/merge_requests/browser_performance_testing.md
+++ b/doc/user/project/merge_requests/browser_performance_testing.md
@@ -1,4 +1,7 @@
---
+stage: Verify
+group: Testing
+info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers
type: reference, howto
---
@@ -7,20 +10,16 @@ type: reference, howto
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/3507) in [GitLab Premium](https://about.gitlab.com/pricing/) 10.3.
If your application offers a web interface and you're using
-[GitLab CI/CD](../../../ci/README.md), you can quickly determine the performance
-impact of pending code changes.
+[GitLab CI/CD](../../../ci/README.md), you can quickly determine the rendering performance
+impact of pending code changes in the browser.
## Overview
GitLab uses [Sitespeed.io](https://www.sitespeed.io), a free and open source
-tool, for measuring the performance of web sites. GitLab has built a simple
-[Sitespeed plugin](https://gitlab.com/gitlab-org/gl-performance) which outputs
-the performance score for each page analyzed in a file called `performance.json`.
-The [Sitespeed.io performance score](https://examples.sitespeed.io/6.0/2017-11-23-23-43-35/help.html)
-is a composite value based on best practices.
-
-GitLab can [show the Performance report](#how-browser-performance-testing-works)
-in the merge request widget area.
+tool, for measuring the rendering performance of web sites. The
+[Sitespeed plugin](https://gitlab.com/gitlab-org/gl-performance) that GitLab built outputs
+the performance score for each page analyzed in a file called `browser-performance.json`
+this data can be shown on Merge Requests.
## Use cases
@@ -38,7 +37,7 @@ Consider the following workflow:
## How browser performance testing works
First, define a job in your `.gitlab-ci.yml` file that generates the
-[Performance report artifact](../../../ci/pipelines/job_artifacts.md#artifactsreportsperformance-premium).
+[Browser Performance report artifact](../../../ci/pipelines/job_artifacts.md#artifactsreportsperformance-premium).
GitLab then checks this report, compares key performance metrics for each page
between the source and target branches, and shows the information in the merge request.
@@ -46,12 +45,13 @@ For an example Performance job, see
[Configuring Browser Performance Testing](#configuring-browser-performance-testing).
NOTE: **Note:**
-If the Performance report has no data to compare, such as when you add the
-Performance job in your `.gitlab-ci.yml` for the very first time, no information
-displays in the merge request widget area. Consecutive merge requests will have data for
-comparison, and the Performance report will be shown properly.
+If the Browser Performance report has no data to compare, such as when you add the
+Browser Performance job in your `.gitlab-ci.yml` for the very first time,
+the Browser Performance report widget won't show. It must have run at least
+once on the target branch (`master`, for example), before it will display in a
+merge request targeting that branch.
-![Performance Widget](img/browser_performance_testing.png)
+![Browser Performance Widget](img/browser_performance_testing.png)
## Configuring Browser Performance Testing
@@ -61,21 +61,7 @@ using Docker-in-Docker.
1. First, set up GitLab Runner with a
[Docker-in-Docker build](../../../ci/docker/using_docker_build.md#use-docker-in-docker-workflow-with-docker-executor).
-1. After configuring the Runner, add a new job to `.gitlab-ci.yml` that generates
- the expected report.
-1. Define the `performance` job according to your version of GitLab:
-
- - For GitLab 12.4 and later - [include](../../../ci/yaml/README.md#includetemplate) the
- [`Browser-Performance.gitlab-ci.yml` template](https://gitlab.com/gitlab-org/gitlab/blob/master/lib/gitlab/ci/templates/Verify/Browser-Performance.gitlab-ci.yml) provided as a part of your GitLab installation.
- - For GitLab versions earlier than 12.4 - Copy and use the job as defined in the
- [`Browser-Performance.gitlab-ci.yml` template](https://gitlab.com/gitlab-org/gitlab/blob/master/lib/gitlab/ci/templates/Verify/Browser-Performance.gitlab-ci.yml).
-
- CAUTION: **Caution:**
- The job definition provided by the template does not support Kubernetes yet.
- For a complete example of a more complex setup that works in Kubernetes, see
- [`Browser-Performance-Testing.gitlab-ci.yml`](https://gitlab.com/gitlab-org/gitlab/blob/master/lib/gitlab/ci/templates/Jobs/Browser-Performance-Testing.gitlab-ci.yml).
-
-1. Add the following to your `.gitlab-ci.yml` file:
+1. Configure the default Browser Performance Testing CI job as follows in your `.gitlab-ci.yml` file:
```yaml
include:
@@ -86,24 +72,32 @@ using Docker-in-Docker.
URL: https://example.com
```
- CAUTION: **Caution:**
- The job definition provided by the template is supported in GitLab 11.5 and later versions.
- It also requires GitLab Runner 11.5 or later. For earlier versions, use the
- [previous job definitions](#previous-job-definitions).
+NOTE: **Note:**
+For versions before 12.4, see the information for [older GitLab versions](#gitlab-versions-123-and-older).
+If you are using a Kubernetes cluster, use [`template: Jobs/Browser-Performance-Testing.gitlab-ci.yml`](https://gitlab.com/gitlab-org/gitlab/blob/master/lib/gitlab/ci/templates/Jobs/Browser-Performance-Testing.gitlab-ci.yml)
+instead.
The above example creates a `performance` job in your CI/CD pipeline and runs
sitespeed.io against the webpage you defined in `URL` to gather key metrics.
-The [GitLab plugin for sitespeed.io](https://gitlab.com/gitlab-org/gl-performance)
-is downloaded to save the report as a [Performance report artifact](../../../ci/pipelines/job_artifacts.md#artifactsreportsperformance-premium)
-that you can later download and analyze. Due to implementation limitations, we always
-take the latest Performance artifact available.
-The full HTML sitespeed.io report is saved as an artifact, and if
-[GitLab Pages](../pages/index.md) is enabled, it can be viewed directly in your browser.
+The example uses a CI/CD template that is included in all GitLab installations since
+12.4, but it will not work with Kubernetes clusters. If you are using GitLab 12.3
+or older, you must [add the configuration manually](#gitlab-versions-123-and-older)
+
+The template uses the [GitLab plugin for sitespeed.io](https://gitlab.com/gitlab-org/gl-performance),
+and it saves the full HTML sitespeed.io report as a [Browser Performance report artifact](../../../ci/pipelines/job_artifacts.md#artifactsreportsperformance-premium)
+that you can later download and analyze. This implementation always takes the latest
+Browser Performance artifact available. If [GitLab Pages](../pages/index.md) is enabled,
+you can view the report directly in your browser.
+
+You can also customize the jobs with environment variables:
+
+- `SITESPEED_IMAGE`: Configure the Docker image to use for the job (default `sitespeedio/sitespeed.io`), but not the image version.
+- `SITESPEED_VERSION`: Configure the version of the Docker image to use for the job (default `13.3.0`).
+- `SITESPEED_OPTIONS`: Configure any additional sitespeed.io options as required (default `nil`). Refer to the [sitespeed.io documentation](https://www.sitespeed.io/documentation/sitespeed.io/configuration/) for more details.
-You can also customize options by setting the `SITESPEED_OPTIONS` variable.
For example, you can override the number of runs sitespeed.io
-makes on the given URL:
+makes on the given URL, and change the version:
```yaml
include:
@@ -111,18 +105,11 @@ include:
performance:
variables:
- URL: https://example.com
+ URL: https://www.sitespeed.io/
+ SITESPEED_VERSION: 13.2.0
SITESPEED_OPTIONS: -n 5
```
-For further customization options for sitespeed.io, including the ability to provide a
-list of URLs to test, please see the
-[Sitespeed.io Configuration](https://www.sitespeed.io/documentation/sitespeed.io/configuration/)
-documentation.
-
-TIP: **Tip:**
-Key metrics are automatically extracted and shown in the merge request widget.
-
### Configuring degradation threshold
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/27599) in GitLab 13.0.
@@ -149,15 +136,12 @@ The above CI YAML configuration is great for testing against static environments
be extended for dynamic environments, but a few extra steps are required:
1. The `performance` job should run after the dynamic environment has started.
-1. In the `review` job, persist the hostname and upload it as an artifact so
- it's available to the `performance` job. The same can be done for static
- environments like staging and production to unify the code path. You can save it
- as an artifact with `echo $CI_ENVIRONMENT_URL > environment_url.txt`
- in your job's `script`.
-1. In the `performance` job, read the previous artifact into an environment
- variable. In this case, use `$URL` because the sitespeed.io command
- uses it for the URL parameter. Because Review App URLs are dynamic, define
- the `URL` variable through `before_script` instead of `variables`.
+1. In the `review` job:
+ 1. Generate a URL list file with the dynamic URL.
+ 1. Save the file as an artifact, for example with `echo $CI_ENVIRONMENT_URL > environment_url.txt`
+ in your job's `script`.
+ 1. Pass the list as the URL environment variable (which can be a URL or a file containing URLs)
+ to the `performance` job.
1. You can now run the sitespeed.io container against the desired hostname and
paths.
@@ -190,20 +174,21 @@ review:
performance:
dependencies:
- review
- before_script:
- - export URL=$(cat environment_url.txt)
+ variables:
+ URL: environment_url.txt
```
-### Previous job definitions
+### GitLab versions 12.3 and older
-CAUTION: **Caution:**
-Before GitLab 11.5, the Performance job and artifact had to be named specifically
-to automatically extract report data and show it in the merge request widget.
-While these old job definitions are still maintained, they have been deprecated
-and may be removed in next major release, GitLab 12.0.
-GitLab recommends you update your current `.gitlab-ci.yml` configuration to reflect that change.
+Browser Performance Testing has gone through several changes since it's introduction.
+In this section we'll detail these changes and how you can run the test based on your
+GitLab version:
-For GitLab 11.4 and earlier, the job should look like:
+- In GitLab 12.4 [a job template was made available](https://gitlab.com/gitlab-org/gitlab/blob/master/lib/gitlab/ci/templates/Verify/Browser-Performance.gitlab-ci.yml).
+- In 13.2 the feature was renamed from `Performance` to `Browser Performance` with
+additional template variables. The job name in the template is still `performance`
+for compatibility reasons, but may be renamed to match in a future iteration.
+- For 11.5 to 12.3 no template is available and the job has to be defined manually as follows:
```yaml
performance:
@@ -211,28 +196,45 @@ performance:
image: docker:git
variables:
URL: https://example.com
+ SITESPEED_VERSION: 13.3.0
+ SITESPEED_OPTIONS: ''
services:
- docker:stable-dind
script:
- mkdir gitlab-exporter
- wget -O ./gitlab-exporter/index.js https://gitlab.com/gitlab-org/gl-performance/raw/master/index.js
- mkdir sitespeed-results
- - docker run --shm-size=1g --rm -v "$(pwd)":/sitespeed.io sitespeedio/sitespeed.io:6.3.1 --plugins.add ./gitlab-exporter --outputFolder sitespeed-results $URL
+ - docker run --shm-size=1g --rm -v "$(pwd)":/sitespeed.io sitespeedio/sitespeed.io:$SITESPEED_VERSION --plugins.add ./gitlab-exporter --outputFolder sitespeed-results $URL $SITESPEED_OPTIONS
- mv sitespeed-results/data/performance.json performance.json
artifacts:
paths:
- performance.json
- sitespeed-results/
+ reports:
+ performance: performance.json
```
-<!-- ## Troubleshooting
+- For 11.4 and earlier the job should be defined as follows:
-Include any troubleshooting steps that you can foresee. If you know beforehand what issues
-one might have when setting this up, or when something is changed, or on upgrading, it's
-important to describe those, too. Think of things that may go wrong and include them here.
-This is important to minimize requests for support, and to avoid doc comments with
-questions that you know someone might ask.
+```yaml
+performance:
+ stage: performance
+ image: docker:git
+ variables:
+ URL: https://example.com
+ services:
+ - docker:stable-dind
+ script:
+ - mkdir gitlab-exporter
+ - wget -O ./gitlab-exporter/index.js https://gitlab.com/gitlab-org/gl-performance/raw/master/index.js
+ - mkdir sitespeed-results
+ - docker run --shm-size=1g --rm -v "$(pwd)":/sitespeed.io sitespeedio/sitespeed.io:6.3.1 --plugins.add ./gitlab-exporter --outputFolder sitespeed-results $URL
+ - mv sitespeed-results/data/performance.json performance.json
+ artifacts:
+ paths:
+ - performance.json
+ - sitespeed-results/
+```
-Each scenario can be a third-level heading, e.g. `### Getting error message X`.
-If you have none to add when creating a doc, leave this section in place
-but commented out to help encourage others to add to it in the future. -->
+Upgrading to the latest version and using the templates is recommended, to ensure
+you receive the latest updates, including updates to the sitespeed.io versions.
diff --git a/doc/user/project/merge_requests/code_quality.md b/doc/user/project/merge_requests/code_quality.md
index 7b4d4b668d5..36acba032ff 100644
--- a/doc/user/project/merge_requests/code_quality.md
+++ b/doc/user/project/merge_requests/code_quality.md
@@ -1,8 +1,11 @@
---
+stage: Verify
+group: Testing
+info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers
type: reference, howto
---
-# Code Quality **(STARTER)**
+# Code Quality
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/1984) in [GitLab Starter](https://about.gitlab.com/pricing/) 9.3.
@@ -22,8 +25,13 @@ Code Quality:
DevOps](../../../topics/autodevops/stages.md#auto-code-quality-starter).
- Can be extended through [Analysis Plugins](https://docs.codeclimate.com/docs/list-of-engines) or a [custom tool](#implementing-a-custom-tool).
+## Code Quality Widget
+
+> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/1984) in [GitLab Starter](https://about.gitlab.com/pricing/) 9.3.
+> - Made [available in all tiers](https://gitlab.com/gitlab-org/gitlab/-/issues/212499) in 13.2.
+
Going a step further, GitLab can show the Code Quality report right
-in the merge request widget area:
+in the merge request widget area if a report from the target branch is available to compare to:
![Code Quality Widget](img/code_quality.png)
@@ -79,7 +87,7 @@ include:
The above example will create a `code_quality` job in your CI/CD pipeline which
will scan your source code for code quality issues. The report will be saved as a
-[Code Quality report artifact](../../../ci/pipelines/job_artifacts.md#artifactsreportscodequality-starter)
+[Code Quality report artifact](../../../ci/pipelines/job_artifacts.md#artifactsreportscodequality)
that you can later download and analyze.
It's also possible to override the URL to the Code Quality image by
@@ -237,7 +245,7 @@ do this:
1. Define a job in your `.gitlab-ci.yml` file that generates the
[Code Quality report
- artifact](../../../ci/pipelines/job_artifacts.md#artifactsreportscodequality-starter).
+ artifact](../../../ci/pipelines/job_artifacts.md#artifactsreportscodequality).
1. Configure your tool to generate the Code Quality report artifact as a JSON
file that implements a subset of the [Code Climate
spec](https://github.com/codeclimate/platform/blob/master/spec/analyzers/SPEC.md#data-types).
@@ -273,11 +281,11 @@ NOTE: **Note:**
Although the Code Climate spec supports more properties, those are ignored by
GitLab.
-## Code Quality reports
+## Code Quality reports **(STARTER)**
Once the Code Quality job has completed:
-- The full list of code quality violations generated by a pipeline is available in the
+- The full list of code quality violations generated by a pipeline is shown in the
Code Quality tab of the Pipeline Details page.
- Potential changes to code quality are shown directly in the merge request.
The Code Quality widget in the merge request compares the reports from the base and head of the branch,
@@ -286,8 +294,43 @@ Once the Code Quality job has completed:
[downloadable artifact](../../../ci/pipelines/job_artifacts.md#downloading-artifacts)
for the `code_quality` job.
+## Extending functionality
+
+### Using Analysis Plugins
+
+Should there be a need to extend the default functionality provided by Code Quality, as stated in [Code Quality](#code-quality), [Analysis Plugins](https://docs.codeclimate.com/docs/list-of-engines) are available.
+
+For example, to use the [SonarJava analyzer](https://docs.codeclimate.com/docs/sonar-java),
+add a file named `.codeclimate.yml` containing the [enablement code](https://docs.codeclimate.com/docs/sonar-java#enable-the-plugin)
+for the plugin to the root of your repository:
+
+```yaml
+version: "2"
+plugins:
+ sonar-java:
+ enabled: true
+```
+
+This adds SonarJava to the `plugins:` section of the [default `.codeclimate.yml`](https://gitlab.com/gitlab-org/ci-cd/codequality/-/blob/master/codeclimate_defaults/.codeclimate.yml)
+included in your project.
+
+Changes to the `plugins:` section do not affect the `exclude_patterns` section of the
+defeault `.codeclimate.yml`. See the Code Climate documentation for
+[excluding files and folders](https://docs.codeclimate.com/docs/excluding-files-and-folders)
+for more details.
+
+Here's [an example project](https://gitlab.com/jheimbuck_gl/jh_java_example_project) that uses Code Quality with a `.codeclimate.yml` file.
+
## Troubleshooting
+### Changing the default configuration has no effect
+
+A common issue is that the terms `Code Quality` (GitLab specific) and `Code Climate`
+(Engine used by GitLab) are very similar. You must add a **`.codeclimate.yml`** file
+to change the default configuration, **not** a `.codequality.yml` file. If you use
+the wrong filename, the [default `.codeclimate.yml`](https://gitlab.com/gitlab-org/ci-cd/codequality/-/blob/master/codeclimate_defaults/.codeclimate.yml)
+is still used.
+
### No Code Quality report is displayed in a Merge Request
This can be due to multiple reasons:
@@ -295,6 +338,7 @@ This can be due to multiple reasons:
- You just added the Code Quality job in your `.gitlab-ci.yml`. The report does not
have anything to compare to yet, so no information can be displayed. Future merge
requests will have something to compare to.
+- Your pipeline is not set to run the code quality job on your default branch. If there is no report generated from the default branch, your MR branch reports will not have anything to compare to.
- If no [degradation or error is detected](https://docs.codeclimate.com/docs/maintainability#section-checks),
nothing will be displayed.
- The [`artifacts:expire_in`](../../../ci/yaml/README.md#artifactsexpire_in) CI/CD
diff --git a/doc/user/project/merge_requests/fail_fast_testing.md b/doc/user/project/merge_requests/fail_fast_testing.md
new file mode 100644
index 00000000000..619a6d04577
--- /dev/null
+++ b/doc/user/project/merge_requests/fail_fast_testing.md
@@ -0,0 +1,87 @@
+---
+stage: Verify
+group: Testing
+info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers
+type: reference, howto
+---
+
+# Fail Fast Testing **(PREMIUM)**
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/198550) in GitLab 13.1.
+
+For applications that use RSpec for running tests, we've introduced the `Verify/Failfast`
+[template to run subsets of your test suite](https://gitlab.com/gitlab-org/gitlab/-/tree/master/lib/gitlab/ci/templates/Verify/FailFast.gitlab-ci.yml),
+based on the changes in your merge request.
+
+The template uses the [test_file_finder (`tff`) gem](https://gitlab.com/gitlab-org/ci-cd/test_file_finder/)
+that accepts a list of files as input, and returns a list of spec (test) files
+that it believes to be relevant to the input files.
+
+`tff` is designed for Ruby on Rails projects, so the `Verify/FailFast` template is
+configured to run when changes to Ruby files are detected. By default, it runs in
+the [`.pre` stage](../../../ci/yaml/README.md#pre-and-post) of a GitLab CI/CD pipeline,
+before all other stages.
+
+## Example use case
+
+Fail fast testing is useful when adding new functionality to a project and adding
+new automated tests.
+
+Your project could have hundreds of thousands of tests that take a long time to complete.
+You may be confident that a new test will pass, but you have to wait for all the tests
+to complete to verify it. This could take an hour or more, even when using parallelization.
+
+Fail fast testing gives you a faster feedback loop from the pipeline. It lets you
+know quickly that the new tests are passing and the new functionality did not break
+other tests.
+
+## Requirements
+
+This template requires:
+
+- A project built in Rails that uses RSpec for testing.
+- CI/CD configured to:
+ - Use a Docker image with Ruby available.
+ - Use [Pipelines for Merge Requests](../../../ci/merge_request_pipelines/index.md#configuring-pipelines-for-merge-requests)
+- [Pipelines for Merged Results](../../../ci/merge_request_pipelines/pipelines_for_merged_results/index.md#enable-pipelines-for-merged-results)
+ enabled in the project settings.
+
+## Configure Fast RSpec Failure
+
+We'll use the following plain RSpec configuration as a starting point. It installs all the
+project gems and executes `rspec`, on merge request pipelines only.
+
+```yaml
+rspec-complete:
+ stage: test
+ rules:
+ - if: $CI_PIPELINE_SOURCE == "merge_request_event"
+ script:
+ - bundle install
+ - bundle exec rspec
+```
+
+To run the most relevant specs first instead of the whole suite, [`include`](../../../ci/yaml/README.md#include)
+the template by adding the following to your CI/CD configuration:
+
+```yaml
+include:
+ - template: Verify/FailFast.gitlab-ci.yml
+```
+
+### Example test loads
+
+For illustrative purposes, let's say our Rails app spec suite consists of 100 specs per model for ten models.
+
+If no Ruby files are changed:
+
+- `rspec-rails-modified-paths-specs` will not run any tests.
+- `rspec-complete` will run the full suite of 1000 tests.
+
+If one Ruby model is changed, for example `app/models/example.rb`, then `rspec-rails-modified-paths-specs`
+will run the 100 tests for `example.rb`:
+
+- If all of these 100 tests pass, then the full `rspec-complete` suite of 1000 tests is allowed to run.
+- If any of these 100 tests fail, they will fail quickly, and `rspec-complete` will not run any tests.
+
+The final case saves resources and time as the full 1000 test suite does not run.
diff --git a/doc/user/project/merge_requests/getting_started.md b/doc/user/project/merge_requests/getting_started.md
index 32eb0c73ed4..9ac0f3ee42e 100644
--- a/doc/user/project/merge_requests/getting_started.md
+++ b/doc/user/project/merge_requests/getting_started.md
@@ -57,7 +57,7 @@ request's page at the top-right side:
- [Close issues automatically](#merge-requests-to-close-issues) when they are merged.
- Enable the [delete source branch when merge request is accepted](#deleting-the-source-branch) option to keep your repository clean.
- Enable the [squash commits when merge request is accepted](squash_and_merge.md) option to combine all the commits into one before merging, thus keep a clean commit history in your repository.
-- Set the merge request as a [Work In Progress (WIP)](work_in_progress_merge_requests.md) to avoid accidental merges before it is ready.
+- Set the merge request as a [**Draft**](work_in_progress_merge_requests.md) to avoid accidental merges before it is ready.
Once you have created the merge request, you can also:
diff --git a/doc/user/project/merge_requests/img/browser_performance_testing.png b/doc/user/project/merge_requests/img/browser_performance_testing.png
index eea77fb8b93..c270462f7a8 100644
--- a/doc/user/project/merge_requests/img/browser_performance_testing.png
+++ b/doc/user/project/merge_requests/img/browser_performance_testing.png
Binary files differ
diff --git a/doc/user/project/merge_requests/img/draft_blocked_merge_button_v13_2.png b/doc/user/project/merge_requests/img/draft_blocked_merge_button_v13_2.png
new file mode 100644
index 00000000000..beb12c581d6
--- /dev/null
+++ b/doc/user/project/merge_requests/img/draft_blocked_merge_button_v13_2.png
Binary files differ
diff --git a/doc/user/project/merge_requests/img/file_by_file_v13_2.png b/doc/user/project/merge_requests/img/file_by_file_v13_2.png
new file mode 100644
index 00000000000..e3114ebabad
--- /dev/null
+++ b/doc/user/project/merge_requests/img/file_by_file_v13_2.png
Binary files differ
diff --git a/doc/user/project/merge_requests/img/load_performance_testing.png b/doc/user/project/merge_requests/img/load_performance_testing.png
new file mode 100644
index 00000000000..3a58e9c28d4
--- /dev/null
+++ b/doc/user/project/merge_requests/img/load_performance_testing.png
Binary files differ
diff --git a/doc/user/project/merge_requests/img/merge_when_pipeline_succeeds_only_if_succeeds_msg.png b/doc/user/project/merge_requests/img/merge_when_pipeline_succeeds_only_if_succeeds_msg.png
deleted file mode 100644
index 761690d1e0c..00000000000
--- a/doc/user/project/merge_requests/img/merge_when_pipeline_succeeds_only_if_succeeds_msg.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/project/merge_requests/img/wip_blocked_accept_button.png b/doc/user/project/merge_requests/img/wip_blocked_accept_button.png
deleted file mode 100644
index ab2c8425b83..00000000000
--- a/doc/user/project/merge_requests/img/wip_blocked_accept_button.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/project/merge_requests/index.md b/doc/user/project/merge_requests/index.md
index 5d2813f5bfc..f68fc7d7b45 100644
--- a/doc/user/project/merge_requests/index.md
+++ b/doc/user/project/merge_requests/index.md
@@ -19,7 +19,7 @@ A. Consider you're a software developer working in a team:
1. You checkout a new branch, and submit your changes through a merge request
1. You gather feedback from your team
-1. You work on the implementation optimizing code with [Code Quality reports](code_quality.md) **(STARTER)**
+1. You work on the implementation optimizing code with [Code Quality reports](code_quality.md)
1. You verify your changes with [JUnit test reports](../../../ci/junit_test_reports.md) in GitLab CI/CD
1. You avoid using dependencies whose license is not compatible with your project with [License Compliance reports](../../compliance/license_compliance/index.md) **(ULTIMATE)**
1. You request the [approval](merge_request_approvals.md) from your manager **(STARTER)**
diff --git a/doc/user/project/merge_requests/load_performance_testing.md b/doc/user/project/merge_requests/load_performance_testing.md
new file mode 100644
index 00000000000..3239269109d
--- /dev/null
+++ b/doc/user/project/merge_requests/load_performance_testing.md
@@ -0,0 +1,197 @@
+---
+stage: Verify
+group: Testing
+info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers
+type: reference, howto
+---
+
+# Load Performance Testing **(PREMIUM)**
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/10683) in [GitLab Premium](https://about.gitlab.com/pricing/) 13.2.
+
+With Load Performance Testing, you can test the impact of any pending code changes
+to your application's backend in [GitLab CI/CD](../../../ci/README.md).
+
+GitLab uses [k6](https://k6.io/), a free and open source
+tool, for measuring the system performance of applications under
+load.
+
+Unlike [Browser Performance Testing](browser_performance_testing.md), which is
+used to measure how web sites perform in client browsers, Load Performance Testing
+can be used to perform various types of [load tests](https://k6.io/docs/#use-cases)
+against application endpoints such as APIs, Web Controllers, and so on.
+This can be used to test how the backend or the server performs at scale.
+
+For example, you can use Load Performance Testing to perform many concurrent
+GET calls to a popular API endpoint in your application to see how it performs.
+
+## How Load Performance Testing works
+
+First, define a job in your `.gitlab-ci.yml` file that generates the
+[Load Performance report artifact](../../../ci/pipelines/job_artifacts.md#artifactsreportsload_performance-premium).
+GitLab checks this report, compares key load performance metrics
+between the source and target branches, and then shows the information in a merge request widget:
+
+![Load Performance Widget](img/load_performance_testing.png)
+
+Next, you need to configure the test environment and write the k6 test.
+
+The key performance metrics that the merge request widget shows after the test completes are:
+
+- Checks: The percentage pass rate of the [checks](https://k6.io/docs/using-k6/checks) configured in the k6 test.
+- TTFB P90: The 90th percentile of how long it took to start receiving responses, aka the [Time to First Byte](https://en.wikipedia.org/wiki/Time_to_first_byte) (TTFB).
+- TTFB P95: The 95th percentile for TTFB.
+- RPS: The average requests per second (RPS) rate the test was able to achieve.
+
+NOTE: **Note:**
+If the Load Performance report has no data to compare, such as when you add the
+Load Performance job in your `.gitlab-ci.yml` for the very first time,
+the Load Performance report widget won't show. It must have run at least
+once on the target branch (`master`, for example), before it will display in a
+merge request targeting that branch.
+
+## Configure the Load Performance Testing job
+
+Configuring your Load Performance Testing job can be broken down into several distinct parts:
+
+- Determine the test parameters such as throughput, and so on.
+- Set up the target test environment for load performance testing.
+- Design and write the k6 test.
+
+### Determine the test parameters
+
+The first thing you need to do is determine the [type of load test](https://k6.io/docs/test-types/introduction)
+you want to run, and how it will run (for example, the number of users, throughput, and so on).
+
+Refer to the [k6 docs](https://k6.io/docs/), especially the [k6 testing guides](https://k6.io/docs/testing-guides),
+for guidance on the above and more.
+
+### Test Environment setup
+
+A large part of the effort around load performance testing is to prepare the target test environment
+for high loads. You should ensure it's able to handle the
+[throughput](https://k6.io/blog/monthly-visits-concurrent-users) it will be tested with.
+
+It's also typically required to have representative test data in the target environment
+for the load performance test to use.
+
+We strongly recommend [not running these tests against a production environment](https://k6.io/our-beliefs#load-test-in-a-pre-production-environment).
+
+### Write the load performance test
+
+After the environment is prepared, you can write the k6 test itself. k6 is a flexible
+tool and can be used to run [many kinds of performance tests](https://k6.io/docs/test-types/introduction).
+Refer to the [k6 documentation](https://k6.io/docs/) for detailed information on how to write tests.
+
+### Configure the test in GitLab CI/CD
+
+When your k6 test is ready, the next step is to configure the load performance
+testing job in GitLab CI/CD. The easiest way to do this is to use the
+[`Verify/Load-Performance-Testing.gitlab-ci.yml`](https://gitlab.com/gitlab-org/gitlab/blob/master/lib/gitlab/ci/templates/Verify/Load-Performance-Testing.gitlab-ci.yml)
+template that is included with GitLab.
+
+NOTE: **Note:**
+For large scale k6 tests you need to ensure the GitLab Runner instance performing the actual
+test is able to handle running the test. Refer to [k6's guidance](https://k6.io/docs/testing-guides/running-large-tests#hardware-considerations)
+for spec details. The [default shared GitLab.com runners](../../gitlab_com/#linux-shared-runners)
+likely have insufficient specs to handle most large k6 tests.
+
+This template runs the
+[k6 Docker container](https://hub.docker.com/r/loadimpact/k6/) in the job and provides several ways to customize the
+job.
+
+An example configuration workflow:
+
+1. Set up a GitLab Runner that can run Docker containers, such as a Runner using the
+ [Docker-in-Docker workflow](../../../ci/docker/using_docker_build.md#use-docker-in-docker-workflow-with-docker-executor).
+1. Configure the default Load Performance Testing CI job in your `.gitlab-ci.yml` file.
+ You need to include the template and configure it with variables:
+
+ ```yaml
+ include:
+ template: Verify/Load-Performance-Testing.gitlab-ci.yml
+
+ load_performance:
+ variables:
+ K6_TEST_FILE: <PATH TO K6 TEST FILE IN PROJECT>
+ ```
+
+The above example creates a `load_performance` job in your CI/CD pipeline that runs
+the k6 test.
+
+NOTE: **Note:**
+For Kubernetes setups a different template should be used: [`Jobs/Load-Performance-Testing.gitlab-ci.yml`](https://gitlab.com/gitlab-org/gitlab/blob/master/lib/gitlab/ci/templates/Jobs/Load-Performance-Testing.gitlab-ci.yml).
+
+k6 has [various options](https://k6.io/docs/using-k6/options) to configure how it will run tests, such as what throughput (RPS) to run with,
+how long the test should run, and so on. Almost all options can be configured in the test itself, but as
+you can also pass command line options via the `K6_OPTIONS` variable.
+
+For example, you can override the duration of the test with a CLI option:
+
+```yaml
+ include:
+ template: Verify/Load-Performance-Testing.gitlab-ci.yml
+
+ load_performance:
+ variables:
+ K6_TEST_FILE: <PATH TO K6 TEST FILE IN PROJECT>
+ K6_OPTIONS: '--duration 30s'
+```
+
+GitLab only displays the key performance metrics in the MR widget if k6's results are saved
+via [summary export](https://k6.io/docs/results-visualization/json#summary-export)
+as a [Load Performance report artifact](../../../ci/pipelines/job_artifacts.md#artifactsreportsload_performance-premium).
+The latest Load Performance artifact available is always used.
+
+If [GitLab Pages](../pages/index.md) is enabled, you can view the report directly in your browser.
+
+### Load Performance testing in Review Apps
+
+The CI/CD YAML configuration example above works for testing against static environments,
+but it can be extended to work with [review apps](../../../ci/review_apps) or
+[dynamic environments](../../../ci/environments) with a few extra steps.
+
+The best approach is to capture the dynamic URL into a custom environment variable that
+is then [inherited](../../../ci/variables/README.md#inherit-environment-variables)
+by the `load_performance` job. The k6 test script to be run should then be configured to
+use that environment URL, such as: ``http.get(`${__ENV.ENVIRONMENT_URL`})``.
+
+For example:
+
+1. In the `review` job:
+ 1. Capture the dynamic URL and save it into a `.env` file, e.g. `echo "ENVIRONMENT_URL=$CI_ENVIRONMENT_URL" >> review.env`.
+ 1. Set the `.env` file to be an [`artifacts:reports:dotenv` report](../../../ci/variables/README.md#inherit-environment-variables).
+1. Set the `load_performance` job to depend on the review job, so it inherits the environment variable.
+1. Configure the k6 test script to use the environment variable in it's steps.
+
+Your `.gitlab-ci.yml` file might be similar to:
+
+```yaml
+stages:
+ - deploy
+ - performance
+
+include:
+ template: Verify/Load-Performance-Testing.gitlab-ci.yml
+
+review:
+ stage: deploy
+ environment:
+ name: review/$CI_COMMIT_REF_NAME
+ url: http://$CI_ENVIRONMENT_SLUG.example.com
+ script:
+ - run_deploy_script
+ - echo "ENVIRONMENT_URL=$CI_ENVIRONMENT_URL" >> review.env
+ artifacts:
+ reports:
+ dotenv:
+ review.env
+ rules:
+ - if: '$CI_COMMIT_BRANCH' # Modify to match your pipeline rules, or use `only/except` if needed.
+
+load_performance:
+ dependencies:
+ - review
+ rules:
+ - if: '$CI_COMMIT_BRANCH' # Modify to match your pipeline rules, or use `only/except` if needed.
+```
diff --git a/doc/user/project/merge_requests/merge_request_dependencies.md b/doc/user/project/merge_requests/merge_request_dependencies.md
index a1012e27d32..65f3cb1e0d5 100644
--- a/doc/user/project/merge_requests/merge_request_dependencies.md
+++ b/doc/user/project/merge_requests/merge_request_dependencies.md
@@ -38,15 +38,15 @@ the `awesome-project` merge request before the `awesome-lib` one would
break the `master`branch.
The `awesome-project` merge request could be [marked as
-WIP](work_in_progress_merge_requests.md),
-and the reason for the WIP stated included in the comments. However, this
+**Draft**](work_in_progress_merge_requests.md),
+and the reason for the draft stated included in the comments. However, this
requires the state of the `awesome-lib` merge request to be manually
tracked, and doesn't scale well if the `awesome-project` merge request
depends on changes to **several** other projects.
By making the `awesome-project` merge request depend on the
`awesome-lib` merge request instead, this relationship is
-automatically tracked by GitLab, and the WIP state can be used to
+automatically tracked by GitLab, and the draft state can be used to
communicate the readiness of the code in each individual merge request
instead.
diff --git a/doc/user/project/merge_requests/merge_when_pipeline_succeeds.md b/doc/user/project/merge_requests/merge_when_pipeline_succeeds.md
index d45ccdc9be9..7d90c9f3cd7 100644
--- a/doc/user/project/merge_requests/merge_when_pipeline_succeeds.md
+++ b/doc/user/project/merge_requests/merge_when_pipeline_succeeds.md
@@ -1,36 +1,38 @@
---
+stage: Create
+group: Source Code
+info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers
type: reference, concepts
---
# Merge when pipeline succeeds
-When reviewing a merge request that looks ready to merge but still has one or
-more CI jobs running, you can set it to be merged automatically when the
-jobs pipeline succeeds. This way, you don't have to wait for the jobs to
+When reviewing a merge request that looks ready to merge but still has a
+pipeline running, you can set it to merge automatically when the
+pipeline succeeds. This way, you don't have to wait for the pipeline to
finish and remember to merge the request manually.
![Enable](img/merge_when_pipeline_succeeds_enable.png)
## How it works
-When you hit the "Merge When Pipeline Succeeds" button, the status of the merge
-request will be updated to represent the impending merge. If you cannot wait
-for the pipeline to succeed and want to merge immediately, this option is
-available in the dropdown menu on the right of the main button.
+When you click "Merge When Pipeline Succeeds", the status of the merge
+request is updated to show the impending merge. If you can't wait
+for the pipeline to succeed, you can choose **Merge immediately**
+in the dropdown menu on the right of the main button.
-Both team developers and the author of the merge request have the option to
-cancel the automatic merge if they find a reason why it shouldn't be merged
-after all.
+The author of the merge request and project members with developer permissions can
+cancel the automatic merge at any time before the pipeline finishes.
![Status](img/merge_when_pipeline_succeeds_status.png)
-When the pipeline succeeds, the merge request will automatically be merged.
+When the pipeline succeeds, the merge request is automatically merged.
When the pipeline fails, the author gets a chance to retry any failed jobs,
or to push new commits to fix the failure.
When the jobs are retried and succeed on the second try, the merge request
-will automatically be merged after all. When the merge request is updated with
-new commits, the automatic merge is automatically canceled to allow the new
+is automatically merged. When the merge request is updated with
+new commits, the automatic merge is canceled to allow the new
changes to be reviewed.
## Only allow merge requests to be merged if the pipeline succeeds
@@ -42,7 +44,7 @@ or if there are threads to be resolved. This works for both:
- Pipelines run from an [external CI integration](../integrations/overview.md#integrations-listing)
As a result, [disabling GitLab CI/CD pipelines](../../../ci/enable_or_disable_ci.md)
-will not disable this feature, as it will still be possible to use pipelines from external
+does not disable this feature, as it is possible to use pipelines from external
CI providers with this feature. To enable it, you must:
1. Navigate to your project's **Settings > General** page.
@@ -50,14 +52,40 @@ CI providers with this feature. To enable it, you must:
1. In the **Merge checks** subsection, select the **Pipelines must succeed** checkbox.
1. Press **Save** for the changes to take effect.
-NOTE: **Note:** This setting also prevents merge requests from being merged if there is no pipeline.
+This setting also prevents merge requests from being merged if there is no pipeline.
-![Pipelines must succeed settings](img/merge_when_pipeline_succeeds_only_if_succeeds_settings.png)
+### Limitations
+
+When this setting is enabled, a merge request is prevented from being merged if there
+is no pipeline. This may conflict with some use cases where [`only/except`](../../../ci/yaml/README.md#onlyexcept-advanced)
+or [`rules`](../../../ci/yaml/README.md#rules) are used and they don't generate any pipelines.
+
+You should ensure that [there is always a pipeline](https://gitlab.com/gitlab-org/gitlab-foss/-/issues/54226)
+and that it's successful.
+
+If both a branch pipeline and a merge request pipeline are triggered for a single
+merge request, only the success or failure of the *merge request pipeline* is checked.
+If the merge request pipeline is configured with fewer jobs than the branch pipeline,
+it could allow code that fails tests to be merged:
+
+```yaml
+branch-pipeline-job:
+ rules:
+ - if: '$CI_PIPELINE_SOURCE == "push"'
+ script:
+ - echo "Code testing scripts here, for example."
-From now on, every time the pipeline fails you will not be able to merge the
-merge request from the UI, until you make all relevant jobs pass.
+merge-request-pipeline-job:
+ rules:
+ - if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
+ script:
+ - echo "No tests run, but this pipeline always succeeds and enables merge."
+ - echo true
+```
-![Only allow merge if pipeline succeeds message](img/merge_when_pipeline_succeeds_only_if_succeeds_msg.png)
+You should avoid configuration like this, and only use branch (`push`) pipelines
+or merge request pipelines, when possible. See [`rules` documentation](../../../ci/yaml/README.md#differences-between-rules-and-onlyexcept)
+for details on avoiding two pipelines for a single merge request.
### Skipped pipelines
@@ -72,20 +100,10 @@ merge requests from being merged. To change this behavior:
1. In the **Merge checks** subsection, select the **Skipped pipelines are considered successful** checkbox.
1. Press **Save** for the changes to take effect.
-### Limitations
-
-When this setting is enabled, a merge request is prevented from being merged if there is no pipeline. This may conflict with some use cases where [`only/except`](../../../ci/yaml/README.md#onlyexcept-advanced) rules are used and they don't generate any pipelines.
-
-Users that expect to be able to merge a merge request in this scenario should ensure that [there is always a pipeline](https://gitlab.com/gitlab-org/gitlab-foss/-/issues/54226) and that it's successful.
+## From the command line
-For example, to that on merge requests there is always a passing job even though `only/except` rules may not generate any other jobs:
-
-```yaml
-enable_merge:
- only: [merge_requests]
- script:
- - echo true
-```
+You can use [Push Options](../push_options.md) to enable merge when pipeline succeeds
+for a merge request when pushing from the command line.
<!-- ## Troubleshooting
@@ -98,8 +116,3 @@ questions that you know someone might ask.
Each scenario can be a third-level heading, e.g. `### Getting error message X`.
If you have none to add when creating a doc, leave this section in place
but commented out to help encourage others to add to it in the future. -->
-
-## Use it from the command line
-
-You can use [Push Options](../push_options.md) to trigger this feature when
-pushing.
diff --git a/doc/user/project/merge_requests/reviewing_and_managing_merge_requests.md b/doc/user/project/merge_requests/reviewing_and_managing_merge_requests.md
index a3ad41d8dd8..162ebdf157d 100644
--- a/doc/user/project/merge_requests/reviewing_and_managing_merge_requests.md
+++ b/doc/user/project/merge_requests/reviewing_and_managing_merge_requests.md
@@ -64,6 +64,43 @@ list.
![Merge request diff file navigation](img/merge_request_diff_file_navigation.png)
+### File-by-file diff navigation
+
+> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/222790) in GitLab 13.2.
+> - It's deployed behind a feature flag, disabled by default.
+> - It's enabled on GitLab.com.
+> - To use it in GitLab self-managed instances, ask a GitLab administrator to [enable it](#enable-or-disable-file-by-file-diff-navigation-core-only).
+
+For larger merge requests it might sometimes be useful to review single files at a time. To enable,
+from your avatar on the top-right navbar, click **Settings**, and go to **Preferences** on the left
+sidebar. Scroll down to the **Behavior** section and select **Show one file at a time on merge request's Changes tab**.
+Click **Save changes** to apply.
+
+From there, when reviewing merge requests' **Changes** tab, you will see only one file at a time. You can then click the buttons **Prev** and **Next** to view the other files changed.
+
+![File-by-file diff navigation](img/file_by_file_v13_2.png)
+
+#### Enable or disable file-by-file diff navigation **(CORE ONLY)**
+
+File-by-file diff navigation is under development but ready for production use. It is
+deployed behind a feature flag that is **disabled by default**.
+[GitLab administrators with access to the GitLab Rails console](../../../administration/feature_flags.md)
+can enable it for your instance.
+
+To enable it:
+
+```ruby
+# Instance-wide
+Feature.enable(:view_diffs_file_by_file)
+```
+
+To disable it:
+
+```ruby
+# Instance-wide
+Feature.disable(:view_diffs_file_by_file>)
+```
+
### Merge requests commit navigation
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/18140) in GitLab 13.0.
diff --git a/doc/user/project/merge_requests/squash_and_merge.md b/doc/user/project/merge_requests/squash_and_merge.md
index 924334055b9..79eec059293 100644
--- a/doc/user/project/merge_requests/squash_and_merge.md
+++ b/doc/user/project/merge_requests/squash_and_merge.md
@@ -85,6 +85,60 @@ it. This is because squashing is only available when accepting a merge request,
so a merge request may need to be rebased before squashing, even though
squashing can itself be considered equivalent to rebasing.
+## Squash Commits Options
+
+> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/17613) in GitLab 13.2.
+> - It's deployed behind a feature flag, disabled by default.
+> - It's disabled on GitLab.com.
+> - It's not recommended for production use.
+> - To use it in GitLab self-managed instances, ask a GitLab administrator to [enable it](#enable-or-disable-squash-commit-options-core-only). **(CORE ONLY)**
+
+With Squash Commits Options you can configure the behavior of Squash and Merge for your project.
+To set it up, navigate to your project's **Settings > General** and expand **Merge requests**.
+You will find the following options to choose, which will affect existing and new merge requests
+submitted to your project:
+
+- **Do not allow**: users cannot use Squash and Merge to squash all the commits immediately before
+ merging. The checkbox to enable or disable it will be unchecked and hidden from the users.
+- **Allow**: users will have the option to enable Squash and Merge on a merge request basis.
+ The checkbox will be unchecked (disabled) by default, but and the user is allowed to enable it.
+- **Encourage**: users will have the option to enable Squash and Merge on a merge request basis.
+ The checkbox will be checked (enabled) by default to encourage its use, but the user is allowed to
+ disable it.
+- **Require**: Squash and Merge is enabled for all merge requests, so it will always be performed.
+ The checkbox to enable or disable it will be checked and hidden from the users.
+
+The Squash and Merge checkbox is displayed when you create a merge request and when you edit the description of an existing one, except when Squash Commit Options is set to **Do not allow** or **Require**.
+
+NOTE: **Note:**
+If your project is set to **Do not allow** Squash and Merge, the users still have the option to
+squash commits locally through the command line and force-push to their remote branch before merging.
+
+### Enable or disable Squash Commit Options **(CORE ONLY)**
+
+Squash Commit Options is under development and not ready for production use. It is
+deployed behind a feature flag that is **disabled by default**.
+[GitLab administrators with access to the GitLab Rails console](../../../administration/feature_flags.md)
+can enable it for your instance. Squash Commit Options can be enabled or disabled per-project.
+
+To enable it:
+
+```ruby
+# Instance-wide
+Feature.enable(:squash_options)
+# or by project
+Feature.enable(:squash_options, Project.find(<project id>))
+```
+
+To disable it:
+
+```ruby
+# Instance-wide
+Feature.disable(:squash_options)
+# or by project
+Feature.disable(:squash_options, Project.find(<project id>))
+```
+
<!-- ## Troubleshooting
Include any troubleshooting steps that you can foresee. If you know beforehand what issues
diff --git a/doc/user/project/merge_requests/test_coverage_visualization.md b/doc/user/project/merge_requests/test_coverage_visualization.md
index 1c0e698aba5..12194423a00 100644
--- a/doc/user/project/merge_requests/test_coverage_visualization.md
+++ b/doc/user/project/merge_requests/test_coverage_visualization.md
@@ -1,4 +1,7 @@
---
+stage: Verify
+group: Testing
+info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers
type: reference, howto
---
diff --git a/doc/user/project/merge_requests/testing_and_reports_in_merge_requests.md b/doc/user/project/merge_requests/testing_and_reports_in_merge_requests.md
index f7614ed7996..e5ebc46d58f 100644
--- a/doc/user/project/merge_requests/testing_and_reports_in_merge_requests.md
+++ b/doc/user/project/merge_requests/testing_and_reports_in_merge_requests.md
@@ -1,4 +1,7 @@
---
+stage: Verify
+group: Testing
+info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers
type: index
description: "Test your code and display reports in merge requests"
---
@@ -11,8 +14,9 @@ or link to useful information directly from merge requests:
| Feature | Description |
|--------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| [Accessibility Testing](accessibility_testing.md) | Automatically report A11y violations for changed pages in merge requests. |
-| [Browser Performance Testing](browser_performance_testing.md) **(PREMIUM)** | Quickly determine the performance impact of pending code changes. |
-| [Code Quality](code_quality.md) **(STARTER)** | Analyze your source code quality using the [Code Climate](https://codeclimate.com/) analyzer and show the Code Climate report right in the merge request widget area. |
+| [Browser Performance Testing](browser_performance_testing.md) **(PREMIUM)** | Quickly determine the browser performance impact of pending code changes. |
+| [Load Performance Testing](load_performance_testing.md) **(PREMIUM)** | Quickly determine the server performance impact of pending code changes. |
+| [Code Quality](code_quality.md) | Analyze your source code quality using the [Code Climate](https://codeclimate.com/) analyzer and show the Code Climate report right in the merge request widget area. |
| [Display arbitrary job artifacts](../../../ci/yaml/README.md#artifactsexpose_as) | Configure CI pipelines with the `artifacts:expose_as` parameter to directly link to selected [artifacts](../../../ci/pipelines/job_artifacts.md) in merge requests. |
| [GitLab CI/CD](../../../ci/README.md) | Build, test, and deploy your code in a per-branch basis with built-in CI/CD. |
| [JUnit test reports](../../../ci/junit_test_reports.md) | Configure your CI jobs to use JUnit test reports, and let GitLab display a report on the merge request so that it’s easier and faster to identify the failure without having to check the entire job log. |
diff --git a/doc/user/project/merge_requests/work_in_progress_merge_requests.md b/doc/user/project/merge_requests/work_in_progress_merge_requests.md
index 8ac4131e10b..ece5e7868dc 100644
--- a/doc/user/project/merge_requests/work_in_progress_merge_requests.md
+++ b/doc/user/project/merge_requests/work_in_progress_merge_requests.md
@@ -2,42 +2,46 @@
type: reference, concepts
---
-# "Work In Progress" merge requests
+# Draft merge requests
If a merge request is not yet ready to be merged, perhaps due to continued development
or open threads, you can prevent it from being accepted before it's ready by flagging
-it as a **Work In Progress**. This will disable the "Merge" button, preventing it from
-being merged, and it will stay disabled until the "WIP" flag has been removed.
+it as a **Draft**. This will disable the "Merge" button, preventing it from
+being merged, and it will stay disabled until the "Draft" flag has been removed.
-![Blocked Accept Button](img/wip_blocked_accept_button.png)
+![Blocked Merge Button](img/draft_blocked_merge_button_v13_2.png)
-## Adding the "Work In Progress" flag to a merge request
+## Adding the "Draft" flag to a merge request
-There are several ways to flag a merge request as a Work In Progress:
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/32692) in GitLab 13.2, Work-In-Progress (WIP) merge requests were renamed to **Draft**. Support for using **WIP** will be removed in GitLab 14.0.
-- Add `[WIP]` or `WIP:` to the start of the merge request's title. Clicking on
- **Start the title with WIP:**, under the title box, when editing the merge request's
+There are several ways to flag a merge request as a Draft:
+
+- Add `[Draft]`, `Draft:` or `(Draft)` to the start of the merge request's title. Clicking on
+ **Start the title with Draft:**, under the title box, when editing the merge request's
description will have the same effect.
+- **Deprecated** Add `[WIP]` or `WIP:` to the start of the merge request's title.
+ **WIP** still works but was deprecated in favor of **Draft**. It will be removed in the next major version (GitLab 14.0).
- Add the `/wip` [quick action](../quick_actions.md#quick-actions-for-issues-merge-requests-and-epics)
in a comment in the merge request. This is a toggle, and can be repeated
to change the status back. Note that any other text in the comment will be discarded.
-- Add "wip" or "WIP" to the start of a commit message targeting the merge request's
+- Add `draft:` or `Draft:` to the start of a commit message targeting the merge request's
source branch. This is not a toggle, and doing it again in another commit will have
no effect.
-## Removing the "Work In Progress" flag from a merge request
+## Removing the "Draft" flag from a merge request
Similar to above, when a Merge Request is ready to be merged, you can remove the
-"Work in Progress" flag in several ways:
+`Draft` flag in several ways:
-- Remove `[WIP]` or `WIP:` from the start of the merge request's title. Clicking on
- **Remove the WIP: prefix from the title**, under the title box, when editing the merge
+- Remove `[Draft]`, `Draft:` or `(Draft)` from the start of the merge request's title. Clicking on
+ **Remove the Draft: prefix from the title**, under the title box, when editing the merge
request's description, will have the same effect.
- Add the `/wip` [quick action](../quick_actions.md#quick-actions-for-issues-merge-requests-and-epics)
in a comment in the merge request. This is a toggle, and can be repeated
to change the status back. Note that any other text in the comment will be discarded.
-- Click on the **Resolve WIP status** button near the bottom of the merge request description,
- next to the "Merge" button (see [image above](#work-in-progress-merge-requests)).
+- Click on the **Resolve Draft status** button near the bottom of the merge request description,
+ next to the **Merge** button (see [image above](#draft-merge-requests)).
Must have at least Developer level permissions on the project for the button to
be visible.
diff --git a/doc/user/project/milestones/index.md b/doc/user/project/milestones/index.md
index 421cb42de1e..aea5eef5efc 100644
--- a/doc/user/project/milestones/index.md
+++ b/doc/user/project/milestones/index.md
@@ -29,39 +29,67 @@ Similarly, milestones can be used as releases. To do so:
1. Set the milestone title to the version of your release, such as `Version 9.4`.
1. Add an issue to your release by associating the desired milestone from the issue's right-hand sidebar.
-Additionally, you can integrate milestones with GitLab's [Releases feature](../releases/index.md#releases-associated-with-milestones).
+Additionally, you can integrate milestones with GitLab's [Releases feature](../releases/index.md#associate-milestones-with-a-release).
## Project milestones and group milestones
-- **Project milestones** can be assigned to issues or merge requests in that project only. Navigate to **Issues > Milestones** in a project to view the project milestone list.
-- **Group milestones** can be assigned to any issue or merge request of any project in that group. Navigate to **Issues > Milestones** in a group to view the group milestone list.
-- All milestones you have access to can also be viewed in the dashboard milestones list. Click on **Milestones** on the top navigation bar to view both project milestones and group milestones you have access to.
+You can assign **project milestones** to issues or merge requests in that project only.
+To view the project milestone list, in a project, go to **{issues}** **Issues > Milestones**.
+
+You can assign **group milestones** to any issue or merge request of any project in that group.
+To view the group milestone list, in a group, go to **{issues}** **Issues > Milestones**.
+
+You can also view all milestones you have access to in the dashboard milestones list.
+To view both project milestones and group milestones you have access to, click **More > Milestones**
+on the top navigation bar.
+
+For information about project and group milestones API, see:
+
+- [Project Milestones API](../../../api/milestones.md)
+- [Group Milestones API](../../../api/group_milestones.md)
+
+NOTE: **Note:**
+If you're in a group and click **Issues > Milestones**, you'll see group milestones and the milestones
+of projects in this group.
+If you're in a project and click **Issues > Milestones**, you'll only see this project's milestones.
## Creating milestones
->**Note:**
-A permission level of `Developer` or higher is required to create milestones.
+NOTE: **Note:**
+A permission level of [Developer or higher](../../permissions.md) is required to create milestones.
### New project milestone
-To create a **project milestone**, navigate to **Issues > Milestones** in the project.
+To create a **project milestone**:
-Click the **New milestone** button. Enter the title, an optional description, an optional start date, and an optional due date. Click **Create milestone** to create the milestone.
+1. In a project, go to **{issues}** **Issues > Milestones**.
+1. Click **New milestone**.
+1. Enter the title, an optional description, an optional start date, and an optional due date.
+1. Click **New milestone**.
![New project milestone](img/milestones_new_project_milestone.png)
### New group milestone
-To create a **group milestone**, follow similar steps from above to project milestones. Navigate to **Issues > Milestones** in the group and create it from there.
+To create a **group milestone**:
+
+1. In a group, go to **{issues}** **Issues > Milestones**.
+1. Click **New milestone**.
+1. Enter the title, an optional description, an optional start date, and an optional due date.
+1. Click **New milestone**.
![New group milestone](img/milestones_new_group_milestone.png)
## Editing milestones
->**Note:**
-A permission level of `Developer` or higher is required to edit milestones.
+NOTE: **Note:**
+A permission level of [Developer or higher](../../permissions.md) is required to edit milestones.
+
+To edit a milestone:
-You can update a milestone by navigating to **Issues > Milestones** in the project or group and clicking the **Edit** button.
+1. In a project or group, go to **{issues}** **Issues > Milestones**.
+1. Click a milestone's title.
+1. Click **Edit**.
You can delete a milestone by clicking the **Delete** button.
diff --git a/doc/user/project/operations/alert_management.md b/doc/user/project/operations/alert_management.md
index 411b36411af..3e4b94473bc 100644
--- a/doc/user/project/operations/alert_management.md
+++ b/doc/user/project/operations/alert_management.md
@@ -17,16 +17,64 @@ being developed, efficiency and awareness can be increased.
NOTE: **Note:**
You will need at least Maintainer [permissions](../../permissions.md) to enable the Alert Management feature.
-1. Follow the [instructions for toggling generic alerts](../integrations/generic_alerts.md#setting-up-generic-alerts)
-1. You can now visit **{cloud-gear}** **Operations > Alerts** in your project's sidebar to [view a list](#alert-management-list) of alerts.
+There are several ways to accept alerts into your GitLab project, as outlined below.
+Enabling any of these methods will allow the Alerts list to display. After configuring
+alerts, visit **{cloud-gear}** **Operations > Alerts** in your project's sidebar
+to [view the list](#alert-management-list) of alerts.
-![Alert Management Toggle](img/alert_management_1_v13_1.png)
+### Opsgenie integration **(PREMIUM)**
-## Populate Alert data
+> [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/3066) in [GitLab Premium](https://about.gitlab.com/pricing/) 13.2.
-To populate data, see the instructions for
-[customizing the payload](../integrations/generic_alerts.md) and making a
-request to the alerts endpoint.
+A new way of monitoring Alerts via a GitLab integration is with
+[Opsgenie](https://www.atlassian.com/software/opsgenie).
+
+NOTE: **Note:**
+If you enable the Opsgenie integration, you cannot have other GitLab alert services,
+such as [Generic Alerts](../integrations/generic_alerts.md) or
+Prometheus alerts, active at the same time.
+
+To enable Opsgenie integration:
+
+1. Sign in as a user with Maintainer or Owner [permissions](../../permissions.md).
+1. Navigate to **{cloud-gear}** **Operations > Alerts**.
+1. In the **Integrations** select box, select Opsgenie.
+1. Click the **Active** toggle.
+1. In the **API URL**, enter the base URL for your Opsgenie integration, such
+ as `https://app.opsgenie.com/alert/list`.
+1. Click **Save changes**.
+
+After enabling the integration, navigate to the Alerts list page at **{cloud-gear}** **Operations > Alerts**, and click **View alerts in Opsgenie**.
+
+### Enable a Generic Alerts endpoint
+
+GitLab provides the Generic Alerts endpoint so you can accept alerts from a third-party
+alerts service. See the
+[instructions for toggling generic alerts](../integrations/generic_alerts.md#setting-up-generic-alerts)
+to add this option. After configuring the endpoint, the
+[Alerts list](#alert-management-list) is enabled.
+
+To populate the alerts with data, see [Customizing the payload](../integrations/generic_alerts.md#customizing-the-payload) for requests to the alerts endpoint.
+
+### Enable GitLab-managed Prometheus alerts
+
+You can install the GitLab-managed Prometheus application on your Kubernetes
+cluster. For more information, see
+[Managed Prometheus on Kubernetes](../integrations/prometheus.md#managed-prometheus-on-kubernetes).
+When GitLab-managed Prometheus is installed, the [Alerts list](#alert-management-list)
+is also enabled.
+
+To populate the alerts with data, see
+[GitLab-Managed Prometheus instances](../../../operations/metrics/alerts.md#managed-prometheus-instances).
+
+### Enable external Prometheus alerts
+
+You can configure an externally-managed Prometheus instance to send alerts
+to GitLab. To set up this configuration, see the [configuring Prometheus](../../../operations/metrics/alerts.md#external-prometheus-instances) documentation. Activating the external Prometheus
+configuration also enables the [Alerts list](#alert-management-list).
+
+To populate the alerts with data, see
+[External Prometheus instances](../../../operations/metrics/alerts.md#external-prometheus-instances).
## Alert Management severity
@@ -55,15 +103,42 @@ You will need at least Developer [permissions](../../permissions.md) to view the
You can find the Alert Management list at **{cloud-gear}** **Operations > Alerts** in your project's sidebar.
Each alert contains the following metrics:
-![Alert Management List](img/alert_management_1_v13_0.png)
+![Alert Management List](img/alert_list_v13_1.png)
- **Severity** - The current importance of a alert and how much attention it should receive.
- **Start time** - How long ago the alert fired. This field uses the standard GitLab pattern of `X time ago`, but is supported by a granular date/time tooltip depending on the user's locale.
-- **End time** - How long ago the alert fired was resolved. This field uses the standard GitLab pattern of `X time ago`, but is supported by a granular date/time tooltip depending on the user's locale.
- **Alert description** - The description of the alert, which attempts to capture the most meaningful data.
- **Event count** - The number of times that an alert has fired.
+- **Issue** - A link to the incident issue that has been created for the alert.
- **Status** - The [current status](#alert-management-statuses) of the alert.
+### Alert Management list sorting
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/217745) in GitLab 13.1.
+
+The Alert Management list displays alerts sorted by start time, but you can
+change the sort order by clicking the headers in the Alert Management list.
+
+To see if a column is sortable, point your mouse at the header. Sortable columns
+display an arrow next to the column name, as shown in this example:
+
+![Alert Management List Sorting](img/alert_list_sort_v13_1.png)
+
+### Searching alerts
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/213884) in GitLab 13.1.
+
+The alert list supports a simple free text search.
+
+![Alert List Search](img/alert_list_search_v13_1.png)
+
+This search filters on the following fields:
+
+- Title
+- Description
+- Monitoring tool
+- Service
+
### Alert Management statuses
Each alert contains a status dropdown to indicate which alerts need investigation.
@@ -138,14 +213,43 @@ deselect the user from the list of assignees, or click **Unassigned**.
> [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/3066) in GitLab 13.1.
-NOTE: **Note:**
-GitLab currently only supports creating system notes when assigning an Alert.
+When you take action on an alert, this is logged as a system note,
+which is visible in the Alert Details view. This gives you a linear
+timeline of the alert's investigation and assignment history.
-Assigning a user an Alert creates a system note, visible in the Alert Details view,
-giving you a linear timeline of the alert's investigation and assignment history.
+The following actions will result in a system note:
+
+- [Updating the status of an alert](#update-an-alerts-status)
+- [Creating an issue based on an alert](#create-an-issue-from-an-alert)
+- [Assignment of an alert to a user](#update-an-alerts-assignee)
![Alert Management Details View System Notes](img/alert_detail_system_notes_v13_1.png)
+### View an Alert's metrics data
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/217768) in GitLab 13.2.
+
+To view the metrics for an alert:
+
+ 1. Sign in as a user with Developer or higher [permissions](../../permissions.md).
+ 1. Navigate to **{cloud-gear}** **Operations > Alerts**.
+ 1. Click the alert you want to view.
+ 1. Below the title of the alert, click the **Metrics** tab.
+
+![Alert Management Metrics View](img/alert_detail_metrics_v13_2.png)
+
+For GitLab-managed Prometheus instances, metrics data is automatically available
+for the alert, making it easy to see surrounding behavior. See
+[Managed Prometheus instances](../../../operations/metrics/alerts.md#managed-prometheus-instances)
+for information on setting up alerts.
+
+For externally-managed Prometheus instances, you can configure your alerting rules to
+display a chart in the alert. See
+[Embedding metrics based on alerts in incident issues](../../../operations/metrics/embed.md#embedding-metrics-based-on-alerts-in-incident-issues)
+for information on how to appropriately configure your alerting rules. See
+[External Prometheus instances](../../../operations/metrics/alerts.md#external-prometheus-instances)
+for information on setting up alerts for your self-managed Prometheus instance.
+
## Use cases for assigning alerts
Consider a team formed by different sections of monitoring, collaborating on a
diff --git a/doc/user/project/operations/dashboard_settings.md b/doc/user/project/operations/dashboard_settings.md
index e14c478ab7a..f30ce5024d6 100644
--- a/doc/user/project/operations/dashboard_settings.md
+++ b/doc/user/project/operations/dashboard_settings.md
@@ -9,6 +9,9 @@ info: To determine the technical writer assigned to the Stage/Group associated w
You can configure your [Monitoring dashboard](../integrations/prometheus.md) to
display the time zone of your choice, and the links of your choice.
+To configure these settings you must have Manage Project
+Operations [permissions](../../permissions.md).
+
## Change the dashboard time zone
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/214370) in GitLab 13.1.
@@ -17,6 +20,7 @@ By default, your monitoring dashboard displays dates and times in your local
time zone, but you can display dates and times in UTC format. To change the
time zone:
+1. Sign in as a user with Manage Project Operations [permissions](../../permissions.md).
1. Navigate to **{settings}** **Settings > Operations**, and scroll to
**Metrics Dashboard**.
1. In the **Dashboard timezone** select box, select *User's local timezone*
@@ -32,6 +36,7 @@ time zone:
You can add a button on your monitoring dashboard that links directly to your
existing external dashboards:
+1. Sign in as a user with Manage Project Operations [permissions](../../permissions.md).
1. Navigate to **{settings}** **Settings > Operations**, and scroll to
**Metrics Dashboard**.
1. In **External dashboard URL**, provide the URL to your external dashboard:
diff --git a/doc/user/project/operations/feature_flags.md b/doc/user/project/operations/feature_flags.md
index a9729204cd7..b0f7cfc966a 100644
--- a/doc/user/project/operations/feature_flags.md
+++ b/doc/user/project/operations/feature_flags.md
@@ -1,261 +1,5 @@
---
-stage: Release
-group: Progressive Delivery
-info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers
+redirect_to: '../../../operations/feature_flags.md'
---
-# Feature Flags **(PREMIUM)**
-
-> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/7433) in GitLab 11.4.
-
-With Feature Flags, you can deploy your application's new features to production in smaller batches.
-You can toggle a feature on and off to subsets of users, helping you achieve Continuous Delivery.
-Feature flags help reduce risk, allowing you to do controlled testing, and separate feature
-delivery from customer launch.
-
-<i class="fa fa-youtube-play youtube" aria-hidden="true"></i>
-For an example of feature flags in action, see [GitLab for Deploys, Feature Flags, and Error Tracking](https://www.youtube.com/embed/5tw2p6lwXxo).
-
-## How it works
-
-GitLab uses [Unleash](https://github.com/Unleash/unleash), a feature
-toggle service.
-
-By enabling or disabling a flag in GitLab, your application
-can determine which features to enable or disable.
-
-You can create feature flags in GitLab and use the API from your application
-to get the list of feature flags and their statuses. The application must be configured to communicate
-with GitLab, so it's up to developers to use a compatible client library and
-[integrate the feature flags in your app](#integrate-feature-flags-with-your-application).
-
-## Create a feature flag
-
-To create and enable a feature flag:
-
-1. Navigate to your project's **Operations > Feature Flags**.
-1. Click the **New feature flag** button.
-1. Enter a name that starts with a letter and contains only lowercase letters, digits, underscores (`_`)
- and dashes (`-`), and does not end with a dash (`-`) or underscore (`_`).
-1. Enter a description (optional, 255 characters max).
-1. Enter details about how the flag should be applied:
- - In GitLab 13.0 and earlier: Enter an environment spec,
- enable or disable the flag in this environment, and select a rollout strategy.
- - In GitLab 13.1 and later (when [this feature flag](#feature-flag-behavior-change-in-130) is enabled): Select a strategy and then
- the environments to apply the strategy to.
-1. Click **Create feature flag**.
-
-The feature flag is displayed in the list. It is enabled by default.
-
-## Disable a feature flag for a specific environment
-
-In [GitLab 13.0 and earlier](https://gitlab.com/gitlab-org/gitlab/-/issues/8621),
-to disable a feature flag for a specific environment:
-
-1. Navigate to your project's **Operations > Feature Flags**.
-1. For the feature flag you want to disable, click the Pencil icon.
-1. To disable the flag:
- - In GitLab 13.0 and earlier: Slide the Status toggle for the environment. Or, to delete the
- environment spec, on the right, click the **Remove (X)** icon.
- - In GitLab 13.1 and later (when [this feature flag](#feature-flag-behavior-change-in-130) is
- enabled): For each strategy it applies to, under **Environments**, delete the environment.
-1. Click **Save changes**.
-
-## Disable a feature flag for all environments
-
-To disable a feature flag for all environments:
-
-1. Navigate to your project's **Operations > Feature Flags**.
-1. For the feature flag you want to disable, slide the Status toggle to **Disabled**.
-
-The feature flag is displayed on the **Disabled** tab.
-
-## Feature flag behavior change in 13.0
-
-> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/35555) in GitLab 13.0.
-
-Starting in GitLab 13.0, you can apply a feature flag strategy across multiple environments,
-without defining the strategy multiple times.
-
-This feature is under development and not ready for production use. It is
-deployed behind a feature flag that is **disabled by default**.
-[GitLab administrators with access to the GitLab Rails console](../../../administration/feature_flags.md)
-can enable it for your instance.
-
-To enable it:
-
-```ruby
-Feature.enable(:feature_flags_new_version)
-```
-
-To disable it:
-
-```ruby
-Feature.disable(:feature_flags_new_version)
-```
-
-## Feature flag strategies
-
-GitLab Feature Flag uses [Unleash](https://unleash.github.io)
-as the feature flag engine. In Unleash, there is a concept of rulesets for granular feature flag controls,
-called [strategies](https://unleash.github.io/docs/activation_strategy).
-Supported strategies for GitLab Feature Flags are described below.
-
-### Rollout strategy
-
-> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/8240) in GitLab 12.2.
-
-The selected rollout strategy affects which users will experience the feature as enabled.
-
-The status of an environment spec ultimately determines whether or not a feature is enabled at all.
-For instance, a feature will always be disabled for every user if the matching environment spec has a disabled status, regardless of the chosen rollout strategy.
-However, a feature will be enabled for 50% of logged-in users if the matching environment spec has an enabled status along with a **Percent rollout (logged in users)** strategy set to 50%.
-
-#### All users
-
-Enables the feature for all users. It is implemented using the Unleash
-[`default`](https://unleash.github.io/docs/activation_strategy#default)
-activation strategy.
-
-#### Percent rollout (logged in users)
-
-Enables the feature for a percentage of authenticated users. It is
-implemented using the Unleash
-[`gradualRolloutUserId`](https://unleash.github.io/docs/activation_strategy#gradualrolloutuserid)
-activation strategy.
-
-Set a value of 15%, for example, to enable the feature for 15% of authenticated users.
-
-A rollout percentage may be between 0% and 100%.
-
-CAUTION: **Caution:**
-If this strategy is selected, then the Unleash client **must** be given a user
-ID for the feature to be enabled. See the [Ruby example](#ruby-application-example) below.
-
-#### User IDs
-
-> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/8240) in GitLab 12.2. [Updated](https://gitlab.com/gitlab-org/gitlab/-/issues/34363) to be defined per environment in GitLab 12.6.
-
-A feature flag may be enabled for a list of target users. It is implemented
-using the Unleash [`userWithId`](https://unleash.github.io/docs/activation_strategy#userwithid)
-activation strategy.
-
-User IDs should be a comma-separated list of values. For example, `user@example.com, user2@example.com`, or `username1,username2,username3`, etc.
-
-CAUTION: **Caution:**
-The Unleash client **must** be given a user ID for the feature to be enabled for
-target users. See the [Ruby example](#ruby-application-example) below.
-
-## Integrate feature flags with your application
-
-To use feature flags with your application, get access credentials from GitLab.
-Then prepare your application with a client library.
-
-### Get access credentials
-
-To get the access credentials that your application needs to communicate with GitLab:
-
-1. Navigate to your project's **Operations > Feature Flags**.
-1. Click the **Configure** button to view the following:
- - **API URL**: URL where the client (application) connects to get a list of feature flags.
- - **Instance ID**: Unique token that authorizes the retrieval of the feature flags.
- - **Application name**: The name of the running environment. For instance,
- if the application runs for a production server, the application name would be
- `production` or similar. This value is used for the environment spec evaluation.
-
-NOTE: **Note:**
-The meaning of these fields might change over time. For example, we are not sure
-if **Instance ID** will be single token or multiple tokens, assigned to the
-**Environment**. Also, **Application name** could describe the version of
-application instead of the running environment.
-
-### Choose a client library
-
-GitLab implements a single backend that is compatible with Unleash clients.
-
-With the Unleash client, developers can define, in the application code, the default values for flags.
-Each feature flag evaluation can express the desired outcome if the flag isn't present in the
-provided configuration file.
-
-Unleash currently [offers many SDKs for various languages and frameworks](https://github.com/Unleash/unleash#client-implementations).
-
-### Feature flags API information
-
-For API content, see:
-
-- [Feature Flags API](../../../api/feature_flags.md)
-- [Feature Flag Specs API](../../../api/feature_flag_specs.md) (Deprecated and [scheduled for removal in GitLab 14.0](https://gitlab.com/gitlab-org/gitlab/-/issues/213369).)
-- [Feature Flag User Lists API](../../../api/feature_flag_user_lists.md)
-- [Legacy Feature Flags API](../../../api/feature_flags_legacy.md)
-
-### Golang application example
-
-Here's an example of how to integrate feature flags in a Golang application:
-
-```golang
-package main
-
-import (
- "io"
- "log"
- "net/http"
-
- "github.com/Unleash/unleash-client-go"
-)
-
-type metricsInterface struct {
-}
-
-func init() {
- unleash.Initialize(
- unleash.WithUrl("https://gitlab.com/api/v4/feature_flags/unleash/42"),
- unleash.WithInstanceId("29QmjsW6KngPR5JNPMWx"),
- unleash.WithAppName("production"),
- unleash.WithListener(&metricsInterface{}),
- )
-}
-
-func helloServer(w http.ResponseWriter, req *http.Request) {
- if unleash.IsEnabled("my_feature_name") {
- io.WriteString(w, "Feature enabled\n")
- } else {
- io.WriteString(w, "hello, world!\n")
- }
-}
-
-func main() {
- http.HandleFunc("/", helloServer)
- log.Fatal(http.ListenAndServe(":8080", nil))
-}
-```
-
-### Ruby application example
-
-Here's an example of how to integrate feature flags in a Ruby application.
-
-The Unleash client is given a user ID for use with a **Percent rollout (logged in users)** rollout strategy or a list of **Target Users**.
-
-```ruby
-#!/usr/bin/env ruby
-
-require 'unleash'
-require 'unleash/context'
-
-unleash = Unleash::Client.new({
- url: 'http://gitlab.com/api/v4/feature_flags/unleash/42',
- app_name: 'production',
- instance_id: '29QmjsW6KngPR5JNPMWx'
-})
-
-unleash_context = Unleash::Context.new
-# Replace "123" with the id of an authenticated user.
-# Note that the context's user id must be a string:
-# https://unleash.github.io/docs/unleash_context
-unleash_context.user_id = "123"
-
-if unleash.is_enabled?("my_feature_name", unleash_context)
- puts "Feature enabled"
-else
- puts "hello, world!"
-end
-```
+This document was moved to [another location](../../../operations/feature_flags.md).
diff --git a/doc/user/project/operations/img/alert_detail_metrics_v13_2.png b/doc/user/project/operations/img/alert_detail_metrics_v13_2.png
new file mode 100644
index 00000000000..84d83365ea8
--- /dev/null
+++ b/doc/user/project/operations/img/alert_detail_metrics_v13_2.png
Binary files differ
diff --git a/doc/user/project/operations/img/alert_list_search_v13_1.png b/doc/user/project/operations/img/alert_list_search_v13_1.png
new file mode 100644
index 00000000000..ba993fe530b
--- /dev/null
+++ b/doc/user/project/operations/img/alert_list_search_v13_1.png
Binary files differ
diff --git a/doc/user/project/operations/img/alert_list_sort_v13_1.png b/doc/user/project/operations/img/alert_list_sort_v13_1.png
new file mode 100644
index 00000000000..8e06c3478f7
--- /dev/null
+++ b/doc/user/project/operations/img/alert_list_sort_v13_1.png
Binary files differ
diff --git a/doc/user/project/operations/img/alert_list_v13_1.png b/doc/user/project/operations/img/alert_list_v13_1.png
new file mode 100644
index 00000000000..7a1a5f5191e
--- /dev/null
+++ b/doc/user/project/operations/img/alert_list_v13_1.png
Binary files differ
diff --git a/doc/user/project/operations/img/alert_management_1_v13_0.png b/doc/user/project/operations/img/alert_management_1_v13_0.png
deleted file mode 100644
index dbc1e795b16..00000000000
--- a/doc/user/project/operations/img/alert_management_1_v13_0.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/project/operations/img/alert_management_1_v13_1.png b/doc/user/project/operations/img/alert_management_1_v13_1.png
deleted file mode 100644
index 00aa56a6050..00000000000
--- a/doc/user/project/operations/img/alert_management_1_v13_1.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/project/operations/index.md b/doc/user/project/operations/index.md
index ca38eab9455..9cec578bc5e 100644
--- a/doc/user/project/operations/index.md
+++ b/doc/user/project/operations/index.md
@@ -1,13 +1,5 @@
-# Project operations
+---
+redirect_to: '../../../operations/index.md'
+---
-GitLab provides a variety of tools to help operate and maintain
-your applications:
-
-- Collect [Prometheus metrics](../integrations/prometheus_library/index.md).
-- Deploy to different [environments](../../../ci/environments/index.md).
-- Connect your project to a [Kubernetes cluster](../clusters/index.md).
-- Manage your infrastructure with [Infrastructure as Code](../../infrastructure/index.md) approaches.
-- Discover and view errors generated by your applications with [Error Tracking](error_tracking.md).
-- Create, toggle, and remove [Feature Flags](feature_flags.md). **(PREMIUM)**
-- [Trace](tracing.md) the performance and health of a deployed application. **(ULTIMATE)**
-- Change the [settings of the Monitoring Dashboard](dashboard_settings.md).
+This document was moved to [another location](../../../operations/index.md).
diff --git a/doc/user/project/operations/tracing.md b/doc/user/project/operations/tracing.md
index 07f60c37f1b..87567f45560 100644
--- a/doc/user/project/operations/tracing.md
+++ b/doc/user/project/operations/tracing.md
@@ -1,40 +1,5 @@
---
-stage: Monitor
-group: APM
-info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers
+redirect_to: '../../../operations/tracing.md'
---
-# Tracing **(ULTIMATE)**
-
-> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/7903) in GitLab Ultimate 11.5.
-
-Tracing provides insight into the performance and health of a deployed application,
-tracking each function or microservice which handles a given request.
-
-This makes it easy to
-understand the end-to-end flow of a request, regardless of whether you are using a monolithic or distributed system.
-
-## Jaeger tracing
-
-[Jaeger](https://www.jaegertracing.io/) is an open source, end-to-end distributed
-tracing system used for monitoring and troubleshooting microservices-based distributed
-systems.
-
-### Deploying Jaeger
-
-To learn more about deploying Jaeger, read the official
-[Getting Started documentation](https://www.jaegertracing.io/docs/latest/getting-started/).
-There is an easy to use [all-in-one Docker image](https://www.jaegertracing.io/docs/latest/getting-started/#AllinoneDockerimage),
-as well as deployment options for [Kubernetes](https://github.com/jaegertracing/jaeger-kubernetes)
-and [OpenShift](https://github.com/jaegertracing/jaeger-openshift).
-
-### Enabling Jaeger
-
-GitLab provides an easy way to open the Jaeger UI from within your project:
-
-1. [Set up Jaeger](https://www.jaegertracing.io) and configure your application using one of the
- [client libraries](https://www.jaegertracing.io/docs/latest/client-libraries/).
-1. Navigate to your project's **Settings > Operations** and provide the Jaeger URL.
-1. Click **Save changes** for the changes to take effect.
-1. You can now visit **Operations > Tracing** in your project's sidebar and
- GitLab will redirect you to the configured Jaeger URL.
+This document was moved to [another location](../../../operations/tracing.md).
diff --git a/doc/user/project/pages/custom_domains_ssl_tls_certification/index.md b/doc/user/project/pages/custom_domains_ssl_tls_certification/index.md
index bf9b58acf14..0425ca56285 100644
--- a/doc/user/project/pages/custom_domains_ssl_tls_certification/index.md
+++ b/doc/user/project/pages/custom_domains_ssl_tls_certification/index.md
@@ -287,7 +287,7 @@ To enable this setting:
1. Navigate to your project's **Settings > Pages**.
1. Tick the checkbox **Force HTTPS (requires valid certificates)**.
-NOTE: **Note**
+NOTE: **Note:**
If you use CloudFlare CDN in front of GitLab Pages, make sure to set the SSL connection setting to `full` instead of `flexible`. For more details, see the [CloudFlare CDN directions](https://support.cloudflare.com/hc/en-us/articles/200170416-End-to-end-HTTPS-with-Cloudflare-Part-3-SSL-options#h_4e0d1a7c-eb71-4204-9e22-9d3ef9ef7fef).
<!-- ## Troubleshooting
diff --git a/doc/user/project/pages/getting_started/fork_sample_project.md b/doc/user/project/pages/getting_started/fork_sample_project.md
index de9bd97b262..250e90f0302 100644
--- a/doc/user/project/pages/getting_started/fork_sample_project.md
+++ b/doc/user/project/pages/getting_started/fork_sample_project.md
@@ -1,56 +1,5 @@
---
-type: reference, howto
-stage: Release
-group: Release Management
-info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers
+redirect_to: 'pages_forked_sample_project.md'
---
-# Create a Pages website from a forked sample
-
-GitLab provides [sample projects for the most popular Static Site Generators](https://gitlab.com/pages).
-You can fork one of the sample projects and run the CI/CD pipeline to generate a Pages website.
-
-Fork a sample project when you want to test GitLab Pages or start a new project that's already
-configured to generate a Pages site.
-
-<i class="fa fa-youtube-play youtube" aria-hidden="true"></i> Watch a [video tutorial](https://www.youtube.com/watch?v=TWqh9MtT4Bg) of how this works.
-
-To fork a sample project and create a Pages website:
-
-1. View the sample projects by going to the [GitLab Pages examples](https://gitlab.com/pages) group.
-1. Click the name of the project you want to [fork](../../../../gitlab-basics/fork-project.md).
-1. In the top right, click the **Fork** button, and then choose a namespace to fork to.
-1. Go to your project's **CI/CD > Pipelines** and click **Run pipeline**.
- GitLab CI/CD builds and deploys your site.
-
-The site can take approximately 30 minutes to deploy.
-When the pipeline is finished, go to **Settings > Pages** to find the link to your website from your project.
-
-For every change pushed to your repository, GitLab CI/CD runs a new pipeline
-that immediately publishes your changes to the Pages site.
-
-You can take some **optional** further steps:
-
-- _Remove the fork relationship._ If you want to contribute to the project you forked from,
- you can keep this relationship. Otherwise, go to your project's **Settings > General**,
- expand **Advanced settings**, and scroll down to **Remove fork relationship**:
-
- ![Remove fork relationship](../img/remove_fork_relationship_v13_1.png)
-
-- _Change the URL to match your namespace._ If your Pages site is hosted on GitLab.com,
- you can rename it to `<namespace>.gitlab.io`, where `<namespace>` is your GitLab namespace
- (the one you chose when you forked the project).
-
- - Go to your project's **Settings > General** and expand **Advanced**. Scroll down to
- **Change path** and change the path to `<namespace>.gitlab.io`.
-
- For example, if your project's URL is `gitlab.com/gitlab-tests/jekyll`, your namespace is
- `gitlab-tests`.
-
- If you set the repository path to `gitlab-tests.gitlab.io`,
- the resulting URL for your Pages website is `https://gitlab-tests.gitlab.io`.
-
- ![Change repo's path](../img/change_path_v12_10.png)
-
- - Now go to your SSG's configuration file and change the [base URL](../getting_started_part_one.md#urls-and-baseurls)
- from `"project-name"` to `""`. The project name setting varies by SSG and may not be in the config file.
+This document was moved to [pages_forked_sample_project.md](pages_forked_sample_project.md).
diff --git a/doc/user/project/pages/getting_started/new_or_existing_website.md b/doc/user/project/pages/getting_started/new_or_existing_website.md
index 5d7126ab22e..86f36447b93 100644
--- a/doc/user/project/pages/getting_started/new_or_existing_website.md
+++ b/doc/user/project/pages/getting_started/new_or_existing_website.md
@@ -1,49 +1,5 @@
---
-type: reference, howto
-stage: Release
-group: Release Management
-info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers
+redirect_to: 'pages_ci_cd_template.md'
---
-# Create a Pages website by using a CI/CD template
-
-GitLab provides `.gitlab-ci.yml` templates for the most popular Static Site Generators (SSGs).
-You can create your own `.gitlab-ci.yml` file from one of these templates, and run
-the CI/CD pipeline to generate a Pages website.
-
-Use a `.gitlab-ci.yml` template when you have an existing project that you want to add a Pages site to.
-
-Your GitLab repository should contain files specific to an SSG, or plain HTML.
-After you complete these steps, you may need to do additional
-configuration for the Pages site to generate properly.
-
-1. In the left sidebar, click **Project overview**.
-1. Click **Set up CI/CD**.
-
- ![setup GitLab CI/CD](../img/setup_ci_v13_1.png)
-
- If this button is not available, CI/CD is already configured for
- your project. You may want to browse the `.gitlab-ci.yml` files
- [in these projects instead](https://gitlab.com/pages).
-
-1. From the **Apply a template** list, choose a template for the SSG you're using.
- You can also choose plain HTML.
-
- ![gitlab-ci templates](../img/choose_ci_template_v13_1.png)
-
- If you don't find a corresponding template, you can view the
- [GitLab Pages group of sample projects](https://gitlab.com/pages).
- These projects contain `.gitlab-ci.yml` files that you can modify for your needs.
- You can also [learn how to write your own `.gitlab-ci.yml`
- file for GitLab Pages](../getting_started_part_four.md).
-
-1. Save and commit the `.gitlab-ci.yml` file.
-
-If everything is configured correctly, the site can take approximately 30 minutes to deploy.
-
-You can watch the pipeline run by going to **CI / CD > Pipelines**.
-When the pipeline is finished, go to **Settings > Pages** to find the link to
-your Pages website.
-
-For every change pushed to your repository, GitLab CI/CD runs a new pipeline
-that immediately publishes your changes to the Pages site.
+This document was moved to [pages_ci_cd_template.md](pages_ci_cd_template.md).
diff --git a/doc/user/project/pages/getting_started/pages_bundled_template.md b/doc/user/project/pages/getting_started/pages_bundled_template.md
index cfa4e0910db..bc706105606 100644
--- a/doc/user/project/pages/getting_started/pages_bundled_template.md
+++ b/doc/user/project/pages/getting_started/pages_bundled_template.md
@@ -1,34 +1,5 @@
---
-type: reference, howto
-stage: Release
-group: Release Management
-info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers
+redirect_to: 'pages_new_project_template.md'
---
-# Create a Pages website from a new project template
-
-> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/issues/47857) in GitLab 11.8.
-
-GitLab provides templates for the most popular Static Site Generators (SSGs).
-You can create a new project from a template and run the CI/CD pipeline to generate a Pages website.
-
-Use a template when you want to test GitLab Pages or start a new project that's already
-configured to generate a Pages site.
-
-1. From the top navigation, click the **+** button and select **New project**.
-1. Select **Create from Template**.
-1. Next to one of the templates starting with **Pages**, click **Use template**.
-
- ![Project templates for Pages](../img/pages_project_templates_v13_1.png)
-
-1. Complete the form and click **Create project**.
-1. From the left sidebar, navigate to your project's **CI/CD > Pipelines**
- and click **Run pipeline** to trigger GitLab CI/CD to build and deploy your
- site.
-
-The site can take approximately 30 minutes to deploy.
-When the pipeline is finished, go to **Settings > Pages** to find the link to
-your Pages website.
-
-For every change pushed to your repository, GitLab CI/CD runs a new pipeline
-that immediately publishes your changes to the Pages site.
+This document was moved to [pages_new_project_template.md](pages_new_project_template.md).
diff --git a/doc/user/project/pages/getting_started/pages_ci_cd_template.md b/doc/user/project/pages/getting_started/pages_ci_cd_template.md
new file mode 100644
index 00000000000..906ffe43285
--- /dev/null
+++ b/doc/user/project/pages/getting_started/pages_ci_cd_template.md
@@ -0,0 +1,49 @@
+---
+type: reference, howto
+stage: Release
+group: Release Management
+info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers
+---
+
+# Create a Pages website by using a CI/CD template
+
+GitLab provides `.gitlab-ci.yml` templates for the most popular Static Site Generators (SSGs).
+You can create your own `.gitlab-ci.yml` file from one of these templates, and run
+the CI/CD pipeline to generate a Pages website.
+
+Use a `.gitlab-ci.yml` template when you have an existing project that you want to add a Pages site to.
+
+Your GitLab repository should contain files specific to an SSG, or plain HTML.
+After you complete these steps, you may need to do additional
+configuration for the Pages site to generate properly.
+
+1. In the left sidebar, click **Project overview**.
+1. Click **Set up CI/CD**.
+
+ ![setup GitLab CI/CD](../img/setup_ci_v13_1.png)
+
+ If this button is not available, CI/CD is already configured for
+ your project. You may want to browse the `.gitlab-ci.yml` files
+ [in these projects instead](https://gitlab.com/pages).
+
+1. From the **Apply a template** list, choose a template for the SSG you're using.
+ You can also choose plain HTML.
+
+ ![gitlab-ci templates](../img/choose_ci_template_v13_1.png)
+
+ If you don't find a corresponding template, you can view the
+ [GitLab Pages group of sample projects](https://gitlab.com/pages).
+ These projects contain `.gitlab-ci.yml` files that you can modify for your needs.
+ You can also [learn how to write your own `.gitlab-ci.yml`
+ file for GitLab Pages](pages_from_scratch.md).
+
+1. Save and commit the `.gitlab-ci.yml` file.
+
+If everything is configured correctly, the site can take approximately 30 minutes to deploy.
+
+You can watch the pipeline run by going to **CI / CD > Pipelines**.
+When the pipeline is finished, go to **Settings > Pages** to find the link to
+your Pages website.
+
+For every change pushed to your repository, GitLab CI/CD runs a new pipeline
+that immediately publishes your changes to the Pages site.
diff --git a/doc/user/project/pages/getting_started/pages_forked_sample_project.md b/doc/user/project/pages/getting_started/pages_forked_sample_project.md
new file mode 100644
index 00000000000..de9bd97b262
--- /dev/null
+++ b/doc/user/project/pages/getting_started/pages_forked_sample_project.md
@@ -0,0 +1,56 @@
+---
+type: reference, howto
+stage: Release
+group: Release Management
+info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers
+---
+
+# Create a Pages website from a forked sample
+
+GitLab provides [sample projects for the most popular Static Site Generators](https://gitlab.com/pages).
+You can fork one of the sample projects and run the CI/CD pipeline to generate a Pages website.
+
+Fork a sample project when you want to test GitLab Pages or start a new project that's already
+configured to generate a Pages site.
+
+<i class="fa fa-youtube-play youtube" aria-hidden="true"></i> Watch a [video tutorial](https://www.youtube.com/watch?v=TWqh9MtT4Bg) of how this works.
+
+To fork a sample project and create a Pages website:
+
+1. View the sample projects by going to the [GitLab Pages examples](https://gitlab.com/pages) group.
+1. Click the name of the project you want to [fork](../../../../gitlab-basics/fork-project.md).
+1. In the top right, click the **Fork** button, and then choose a namespace to fork to.
+1. Go to your project's **CI/CD > Pipelines** and click **Run pipeline**.
+ GitLab CI/CD builds and deploys your site.
+
+The site can take approximately 30 minutes to deploy.
+When the pipeline is finished, go to **Settings > Pages** to find the link to your website from your project.
+
+For every change pushed to your repository, GitLab CI/CD runs a new pipeline
+that immediately publishes your changes to the Pages site.
+
+You can take some **optional** further steps:
+
+- _Remove the fork relationship._ If you want to contribute to the project you forked from,
+ you can keep this relationship. Otherwise, go to your project's **Settings > General**,
+ expand **Advanced settings**, and scroll down to **Remove fork relationship**:
+
+ ![Remove fork relationship](../img/remove_fork_relationship_v13_1.png)
+
+- _Change the URL to match your namespace._ If your Pages site is hosted on GitLab.com,
+ you can rename it to `<namespace>.gitlab.io`, where `<namespace>` is your GitLab namespace
+ (the one you chose when you forked the project).
+
+ - Go to your project's **Settings > General** and expand **Advanced**. Scroll down to
+ **Change path** and change the path to `<namespace>.gitlab.io`.
+
+ For example, if your project's URL is `gitlab.com/gitlab-tests/jekyll`, your namespace is
+ `gitlab-tests`.
+
+ If you set the repository path to `gitlab-tests.gitlab.io`,
+ the resulting URL for your Pages website is `https://gitlab-tests.gitlab.io`.
+
+ ![Change repo's path](../img/change_path_v12_10.png)
+
+ - Now go to your SSG's configuration file and change the [base URL](../getting_started_part_one.md#urls-and-baseurls)
+ from `"project-name"` to `""`. The project name setting varies by SSG and may not be in the config file.
diff --git a/doc/user/project/pages/getting_started/pages_from_scratch.md b/doc/user/project/pages/getting_started/pages_from_scratch.md
new file mode 100644
index 00000000000..7278c734b07
--- /dev/null
+++ b/doc/user/project/pages/getting_started/pages_from_scratch.md
@@ -0,0 +1,402 @@
+---
+last_updated: 2020-01-06
+type: reference, howto
+stage: Release
+group: Release Management
+info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers
+---
+
+# Create a GitLab Pages website from scratch
+
+This tutorial shows you how to create a Pages site from scratch. You will start with
+a blank project and create your own CI file, which gives instruction to the
+[GitLab Runner](https://docs.gitlab.com/runner/). When your CI/CD
+[pipeline](../../../../ci/pipelines/index.md) runs, the Pages site is created.
+
+This example uses the [Jekyll](https://jekyllrb.com/) Static Site Generator (SSG).
+Other SSGs follow similar steps. You do not need to be familiar with Jekyll or SSGs
+to complete this tutorial.
+
+## Prerequisites
+
+To follow along with this example, start with a blank project in GitLab.
+Create three files in the root (top-level) directory.
+
+- `.gitlab-ci.yml` A YAML file that contains the commands you want to run.
+ For now, leave the file's contents blank.
+
+- `index.html` An HTML file you can populate with whatever HTML content you'd like,
+ for example:
+
+ ```html
+ <html>
+ <head>
+ <title>Home</title>
+ </head>
+ <body>
+ <h1>Hello World!</h1>
+ </body>
+ </html>
+ ```
+
+- [`Gemfile`](https://bundler.io/gemfile.html) A file that describes dependencies for Ruby programs.
+ Populate it with this content:
+
+ ```ruby
+ source "https://rubygems.org"
+
+ gem "jekyll"
+ ```
+
+## Choose a Docker image
+
+In this example, the Runner uses a [Docker image](../../../../ci/docker/using_docker_images.md)
+to run scripts and deploy the site.
+
+This specific Ruby image is maintained on [DockerHub](https://hub.docker.com/_/ruby).
+
+Edit your `.gitlab-ci.yml` and add this text as the first line.
+
+```yaml
+image: ruby:2.7
+```
+
+If your SSG needs [NodeJS](https://nodejs.org/) to build, you must specify an
+image that contains NodeJS as part of its file system. For example, for a
+[Hexo](https://gitlab.com/pages/hexo) site, you can use `image: node:12.17.0`.
+
+## Install Jekyll
+
+To run [Jekyll](https://jekyllrb.com/) locally, you would open your terminal and:
+
+- Install [Bundler](https://bundler.io/) by running `gem install bundler`.
+- Create `Gemfile.lock` by running `bundle install`.
+- Install Jekyll by running `bundle exec jekyll build`.
+
+In the `.gitlab-ci.yml` file, this is written as:
+
+```yaml
+script:
+ - gem install bundler
+ - bundle install
+ - bundle exec jekyll build
+```
+
+In addition, in the `.gitlab-ci.yml` file, each `script` is organized by a `job`.
+A `job` includes the scripts and settings you want to apply to that specific
+task.
+
+```yaml
+job:
+ script:
+ - gem install bundler
+ - bundle install
+ - bundle exec jekyll build
+```
+
+For GitLab Pages, this `job` has a specific name, called `pages`.
+This setting tells the Runner you want the job to deploy your website
+with GitLab Pages:
+
+```yaml
+pages:
+ script:
+ - gem install bundler
+ - bundle install
+ - bundle exec jekyll build
+```
+
+## Specify the `public` directory for output
+
+Jekyll needs to know where to generate its output.
+GitLab Pages only considers files in a directory called `public`.
+
+Jekyll uses destination (`-d`) to specify an output directory for the built website:
+
+```yaml
+pages:
+ script:
+ - gem install bundler
+ - bundle install
+ - bundle exec jekyll build -d public
+```
+
+## Specify the `public` directory for artifacts
+
+Now that Jekyll has output the files to the `public` directory,
+the Runner needs to know where to get them. The artifacts are stored
+in the `public` directory:
+
+```yaml
+pages:
+ script:
+ - gem install bundler
+ - bundle install
+ - bundle exec jekyll build -d public
+ artifacts:
+ paths:
+ - public
+```
+
+Paste this into `.gitlab-ci.yml` file, so it now looks like this:
+
+```yaml
+image: ruby:2.7
+
+pages:
+ script:
+ - gem install bundler
+ - bundle install
+ - bundle exec jekyll build -d public
+ artifacts:
+ paths:
+ - public
+```
+
+Now save and commit the `.gitlab-ci.yml` file. You can watch the pipeline run
+by going to **CI / CD > Pipelines**.
+
+When it succeeds, go to **Settings > Pages** to view the URL where your site
+is now available.
+
+If you want to do more advanced tasks, you can update your `.gitlab-ci.yml` file
+with [any of the available settings](../../../../ci/yaml/README.md). You can check
+your CI syntax with the [GitLab CI/CD Lint Tool](../../../../ci/yaml/README.md#validate-the-gitlab-ciyml).
+
+The following topics show other examples of other options you can add to your CI/CD file.
+
+## Deploy specific branches to a Pages site
+
+You may want to deploy to a Pages site only from specific branches.
+
+First, add a `workflow` section to force the pipeline to run only when changes are
+pushed to branches:
+
+```yaml
+image: ruby:2.7
+
+workflow:
+ rules:
+ - if: '$CI_COMMIT_BRANCH'
+
+pages:
+ script:
+ - gem install bundler
+ - bundle install
+ - bundle exec jekyll build -d public
+ artifacts:
+ paths:
+ - public
+```
+
+Then configure the pipeline to run the job for the master branch only.
+
+```yaml
+image: ruby:2.7
+
+workflow:
+ rules:
+ - if: '$CI_COMMIT_BRANCH'
+
+pages:
+ script:
+ - gem install bundler
+ - bundle install
+ - bundle exec jekyll build -d public
+ artifacts:
+ paths:
+ - public
+ rules:
+ - if: '$CI_COMMIT_BRANCH == "master"'
+```
+
+## Specify a stage to deploy
+
+There are three default stages for GitLab CI/CD: build, test,
+and deploy.
+
+If you want to test your script and check the built site before deploying
+to production, you can run the test exactly as it will run when you
+push to `master`.
+
+To specify a stage for your job to run in,
+add a `stage` line to your CI file:
+
+```yaml
+image: ruby:2.7
+
+workflow:
+ rules:
+ - if: '$CI_COMMIT_BRANCH'
+
+pages:
+ stage: deploy
+ script:
+ - gem install bundler
+ - bundle install
+ - bundle exec jekyll build -d public
+ artifacts:
+ paths:
+ - public
+ rules:
+ - if: '$CI_COMMIT_BRANCH == "master"'
+```
+
+Now add another job to the CI file, telling it to
+test every push to every branch **except** the `master` branch:
+
+```yaml
+image: ruby:2.7
+
+workflow:
+ rules:
+ - if: '$CI_COMMIT_BRANCH'
+
+pages:
+ stage: deploy
+ script:
+ - gem install bundler
+ - bundle install
+ - bundle exec jekyll build -d public
+ artifacts:
+ paths:
+ - public
+ rules:
+ - if: '$CI_COMMIT_BRANCH == "master"'
+
+test:
+ stage: test
+ script:
+ - gem install bundler
+ - bundle install
+ - bundle exec jekyll build -d test
+ artifacts:
+ paths:
+ - test
+ rules:
+ - if: '$CI_COMMIT_BRANCH != "master"'
+```
+
+When the `test` job runs in the `test` stage, Jekyll
+builds the site in a directory called `test`. The job affects
+all branches except `master`.
+
+When you apply stages to different jobs, every job in the same
+stage builds in parallel. If your web application needs more than
+one test before being deployed, you can run all your tests at the
+same time.
+
+## Remove duplicate commands
+
+To avoid duplicating the same scripts in every job, you can add them
+to a `before_script` section.
+
+In the example, `gem install bundler` and `bundle install` were running
+for both jobs, `pages` and `test`.
+
+Move these commands to a `before_script` section:
+
+```yaml
+image: ruby:2.7
+
+workflow:
+ rules:
+ - if: '$CI_COMMIT_BRANCH'
+
+before_script:
+ - gem install bundler
+ - bundle install
+
+pages:
+ stage: deploy
+ script:
+ - bundle exec jekyll build -d public
+ artifacts:
+ paths:
+ - public
+ rules:
+ - if: '$CI_COMMIT_BRANCH == "master"'
+
+test:
+ stage: test
+ script:
+ - bundle exec jekyll build -d test
+ artifacts:
+ paths:
+ - test
+ rules:
+ - if: '$CI_COMMIT_BRANCH != "master"'
+```
+
+## Build faster with cached dependencies
+
+To build faster, you can cache the installation files for your
+project's dependencies by using the `cache` parameter.
+
+This example caches Jekyll dependencies in a `vendor` directory
+when you run `bundle install`:
+
+```yaml
+image: ruby:2.7
+
+workflow:
+ rules:
+ - if: '$CI_COMMIT_BRANCH'
+
+cache:
+ paths:
+ - vendor/
+
+before_script:
+ - gem install bundler
+ - bundle install --path vendor
+
+pages:
+ stage: deploy
+ script:
+ - bundle exec jekyll build -d public
+ artifacts:
+ paths:
+ - public
+ rules:
+ - if: '$CI_COMMIT_BRANCH == "master"'
+
+test:
+ stage: test
+ script:
+ - bundle exec jekyll build -d test
+ artifacts:
+ paths:
+ - test
+ rules:
+ - if: '$CI_COMMIT_BRANCH != "master"'
+```
+
+In this case, you need to exclude the `/vendor`
+directory from the list of folders Jekyll builds. Otherwise, Jekyll
+will try to build the directory contents along with the site.
+
+In the root directory, create a file called `_config.yml`
+and add this content:
+
+```yaml
+exclude:
+ - vendor
+```
+
+Now GitLab CI/CD not only builds the website,
+but also pushes with **continuous tests** to feature-branches,
+**caches** dependencies installed with Bundler, and
+**continuously deploys** every push to the `master` branch.
+
+## Related topics
+
+For more information, see the following blog posts.
+
+- [Use GitLab CI/CD `environments` to deploy your
+ web app to staging and production](https://about.gitlab.com/blog/2016/08/26/ci-deployment-and-environments/).
+- Learn [how to run jobs sequentially,
+ in parallel, or build a custom pipeline](https://about.gitlab.com/blog/2016/07/29/the-basics-of-gitlab-ci/).
+- Learn [how to pull specific directories from different projects](https://about.gitlab.com/blog/2016/12/07/building-a-new-gitlab-docs-site-with-nanoc-gitlab-ci-and-gitlab-pages/)
+ to deploy this website, <https://docs.gitlab.com>.
+- Learn [how to use GitLab Pages to produce a code coverage report](https://about.gitlab.com/blog/2016/11/03/publish-code-coverage-report-with-gitlab-pages/).
diff --git a/doc/user/project/pages/getting_started/pages_new_project_template.md b/doc/user/project/pages/getting_started/pages_new_project_template.md
new file mode 100644
index 00000000000..cfa4e0910db
--- /dev/null
+++ b/doc/user/project/pages/getting_started/pages_new_project_template.md
@@ -0,0 +1,34 @@
+---
+type: reference, howto
+stage: Release
+group: Release Management
+info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers
+---
+
+# Create a Pages website from a new project template
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/issues/47857) in GitLab 11.8.
+
+GitLab provides templates for the most popular Static Site Generators (SSGs).
+You can create a new project from a template and run the CI/CD pipeline to generate a Pages website.
+
+Use a template when you want to test GitLab Pages or start a new project that's already
+configured to generate a Pages site.
+
+1. From the top navigation, click the **+** button and select **New project**.
+1. Select **Create from Template**.
+1. Next to one of the templates starting with **Pages**, click **Use template**.
+
+ ![Project templates for Pages](../img/pages_project_templates_v13_1.png)
+
+1. Complete the form and click **Create project**.
+1. From the left sidebar, navigate to your project's **CI/CD > Pipelines**
+ and click **Run pipeline** to trigger GitLab CI/CD to build and deploy your
+ site.
+
+The site can take approximately 30 minutes to deploy.
+When the pipeline is finished, go to **Settings > Pages** to find the link to
+your Pages website.
+
+For every change pushed to your repository, GitLab CI/CD runs a new pipeline
+that immediately publishes your changes to the Pages site.
diff --git a/doc/user/project/pages/getting_started_part_four.md b/doc/user/project/pages/getting_started_part_four.md
index 8cf58280b88..e45befe004e 100644
--- a/doc/user/project/pages/getting_started_part_four.md
+++ b/doc/user/project/pages/getting_started_part_four.md
@@ -1,402 +1,5 @@
---
-last_updated: 2020-01-06
-type: reference, howto
-stage: Release
-group: Release Management
-info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers
+redirect_to: 'getting_started/pages_from_scratch.md'
---
-# Create a GitLab Pages website from scratch
-
-This tutorial shows you how to create a Pages site from scratch. You will start with
-a blank project and create your own CI file, which gives instruction to the
-[GitLab Runner](https://docs.gitlab.com/runner/). When your CI/CD
-[pipeline](../../../ci/pipelines/index.md) runs, the Pages site is created.
-
-This example uses the [Jekyll](https://jekyllrb.com/) Static Site Generator (SSG).
-Other SSGs follow similar steps. You do not need to be familiar with Jekyll or SSGs
-to complete this tutorial.
-
-## Prerequisites
-
-To follow along with this example, start with a blank project in GitLab.
-Create three files in the root (top-level) directory.
-
-- `.gitlab-ci.yml` A YAML file that contains the commands you want to run.
- For now, leave the file's contents blank.
-
-- `index.html` An HTML file you can populate with whatever HTML content you'd like,
- for example:
-
- ```html
- <html>
- <head>
- <title>Home</title>
- </head>
- <body>
- <h1>Hello World!</h1>
- </body>
- </html>
- ```
-
-- [`Gemfile`](https://bundler.io/gemfile.html) A file that describes dependencies for Ruby programs.
- Populate it with this content:
-
- ```ruby
- source "https://rubygems.org"
-
- gem "jekyll"
- ```
-
-## Choose a Docker image
-
-In this example, the Runner uses a [Docker image](../../../ci/docker/using_docker_images.md)
-to run scripts and deploy the site.
-
-This specific Ruby image is maintained on [DockerHub](https://hub.docker.com/_/ruby).
-
-Edit your `.gitlab-ci.yml` and add this text as the first line.
-
-```yaml
-image: ruby:2.7
-```
-
-If your SSG needs [NodeJS](https://nodejs.org/) to build, you must specify an
-image that contains NodeJS as part of its file system. For example, for a
-[Hexo](https://gitlab.com/pages/hexo) site, you can use `image: node:12.17.0`.
-
-## Install Jekyll
-
-To run [Jekyll](https://jekyllrb.com/) locally, you would open your terminal and:
-
-- Install [Bundler](https://bundler.io/) by running `gem install bundler`.
-- Create `Gemfile.lock` by running `bundle install`.
-- Install Jekyll by running `bundle exec jekyll build`.
-
-In the `.gitlab-ci.yml` file, this is written as:
-
-```yaml
-script:
- - gem install bundler
- - bundle install
- - bundle exec jekyll build
-```
-
-In addition, in the `.gitlab-ci.yml` file, each `script` is organized by a `job`.
-A `job` includes the scripts and settings you want to apply to that specific
-task.
-
-```yaml
-job:
- script:
- - gem install bundler
- - bundle install
- - bundle exec jekyll build
-```
-
-For GitLab Pages, this `job` has a specific name, called `pages`.
-This setting tells the Runner you want the job to deploy your website
-with GitLab Pages:
-
-```yaml
-pages:
- script:
- - gem install bundler
- - bundle install
- - bundle exec jekyll build
-```
-
-## Specify the `public` directory for output
-
-Jekyll needs to know where to generate its output.
-GitLab Pages only considers files in a directory called `public`.
-
-Jekyll uses destination (`-d`) to specify an output directory for the built website:
-
-```yaml
-pages:
- script:
- - gem install bundler
- - bundle install
- - bundle exec jekyll build -d public
-```
-
-## Specify the `public` directory for artifacts
-
-Now that Jekyll has output the files to the `public` directory,
-the Runner needs to know where to get them. The artifacts are stored
-in the `public` directory:
-
-```yaml
-pages:
- script:
- - gem install bundler
- - bundle install
- - bundle exec jekyll build -d public
- artifacts:
- paths:
- - public
-```
-
-Paste this into `.gitlab-ci.yml` file, so it now looks like this:
-
-```yaml
-image: ruby:2.7
-
-pages:
- script:
- - gem install bundler
- - bundle install
- - bundle exec jekyll build -d public
- artifacts:
- paths:
- - public
-```
-
-Now save and commit the `.gitlab-ci.yml` file. You can watch the pipeline run
-by going to **CI / CD > Pipelines**.
-
-When it succeeds, go to **Settings > Pages** to view the URL where your site
-is now available.
-
-If you want to do more advanced tasks, you can update your `.gitlab-ci.yml` file
-with [any of the available settings](../../../ci/yaml/README.md). You can check
-your CI syntax with the [GitLab CI/CD Lint Tool](https://gitlab.com/ci/lint).
-
-The following topics show other examples of other options you can add to your CI/CD file.
-
-## Deploy specific branches to a Pages site
-
-You may want to deploy to a Pages site only from specific branches.
-
-First, add a `workflow` section to force the pipeline to run only when changes are
-pushed to branches:
-
-```yaml
-image: ruby:2.7
-
-workflow:
- rules:
- - if: '$CI_COMMIT_BRANCH'
-
-pages:
- script:
- - gem install bundler
- - bundle install
- - bundle exec jekyll build -d public
- artifacts:
- paths:
- - public
-```
-
-Then configure the pipeline to run the job for the master branch only.
-
-```yaml
-image: ruby:2.7
-
-workflow:
- rules:
- - if: '$CI_COMMIT_BRANCH'
-
-pages:
- script:
- - gem install bundler
- - bundle install
- - bundle exec jekyll build -d public
- artifacts:
- paths:
- - public
- rules:
- - if: '$CI_COMMIT_BRANCH == "master"'
-```
-
-## Specify a stage to deploy
-
-There are three default stages for GitLab CI/CD: build, test,
-and deploy.
-
-If you want to test your script and check the built site before deploying
-to production, you can run the test exactly as it will run when you
-push to `master`.
-
-To specify a stage for your job to run in,
-add a `stage` line to your CI file:
-
-```yaml
-image: ruby:2.7
-
-workflow:
- rules:
- - if: '$CI_COMMIT_BRANCH'
-
-pages:
- stage: deploy
- script:
- - gem install bundler
- - bundle install
- - bundle exec jekyll build -d public
- artifacts:
- paths:
- - public
- rules:
- - if: '$CI_COMMIT_BRANCH == "master"'
-```
-
-Now add another job to the CI file, telling it to
-test every push to every branch **except** the `master` branch:
-
-```yaml
-image: ruby:2.7
-
-workflow:
- rules:
- - if: '$CI_COMMIT_BRANCH'
-
-pages:
- stage: deploy
- script:
- - gem install bundler
- - bundle install
- - bundle exec jekyll build -d public
- artifacts:
- paths:
- - public
- rules:
- - if: '$CI_COMMIT_BRANCH == "master"'
-
-test:
- stage: test
- script:
- - gem install bundler
- - bundle install
- - bundle exec jekyll build -d test
- artifacts:
- paths:
- - test
- rules:
- - if: '$CI_COMMIT_BRANCH != "master"'
-```
-
-When the `test` job runs in the `test` stage, Jekyll
-builds the site in a directory called `test`. The job affects
-all branches except `master`.
-
-When you apply stages to different jobs, every job in the same
-stage builds in parallel. If your web application needs more than
-one test before being deployed, you can run all your tests at the
-same time.
-
-## Remove duplicate commands
-
-To avoid duplicating the same scripts in every job, you can add them
-to a `before_script` section.
-
-In the example, `gem install bundler` and `bundle install` were running
-for both jobs, `pages` and `test`.
-
-Move these commands to a `before_script` section:
-
-```yaml
-image: ruby:2.7
-
-workflow:
- rules:
- - if: '$CI_COMMIT_BRANCH'
-
-before_script:
- - gem install bundler
- - bundle install
-
-pages:
- stage: deploy
- script:
- - bundle exec jekyll build -d public
- artifacts:
- paths:
- - public
- rules:
- - if: '$CI_COMMIT_BRANCH == "master"'
-
-test:
- stage: test
- script:
- - bundle exec jekyll build -d test
- artifacts:
- paths:
- - test
- rules:
- - if: '$CI_COMMIT_BRANCH != "master"'
-```
-
-## Build faster with cached dependencies
-
-To build faster, you can cache the installation files for your
-project's dependencies by using the `cache` parameter.
-
-This example caches Jekyll dependencies in a `vendor` directory
-when you run `bundle install`:
-
-```yaml
-image: ruby:2.7
-
-workflow:
- rules:
- - if: '$CI_COMMIT_BRANCH'
-
-cache:
- paths:
- - vendor/
-
-before_script:
- - gem install bundler
- - bundle install --path vendor
-
-pages:
- stage: deploy
- script:
- - bundle exec jekyll build -d public
- artifacts:
- paths:
- - public
- rules:
- - if: '$CI_COMMIT_BRANCH == "master"'
-
-test:
- stage: test
- script:
- - bundle exec jekyll build -d test
- artifacts:
- paths:
- - test
- rules:
- - if: '$CI_COMMIT_BRANCH != "master"'
-```
-
-In this case, you need to exclude the `/vendor`
-directory from the list of folders Jekyll builds. Otherwise, Jekyll
-will try to build the directory contents along with the site.
-
-In the root directory, create a file called `_config.yml`
-and add this content:
-
-```yaml
-exclude:
- - vendor
-```
-
-Now GitLab CI/CD not only builds the website,
-but also pushes with **continuous tests** to feature-branches,
-**caches** dependencies installed with Bundler, and
-**continuously deploys** every push to the `master` branch.
-
-## Related topics
-
-For more information, see the following blog posts.
-
-- [Use GitLab CI/CD `environments` to deploy your
- web app to staging and production](https://about.gitlab.com/blog/2016/08/26/ci-deployment-and-environments/).
-- Learn [how to run jobs sequentially,
- in parallel, or build a custom pipeline](https://about.gitlab.com/blog/2016/07/29/the-basics-of-gitlab-ci/).
-- Learn [how to pull specific directories from different projects](https://about.gitlab.com/blog/2016/12/07/building-a-new-gitlab-docs-site-with-nanoc-gitlab-ci-and-gitlab-pages/)
- to deploy this website, <https://docs.gitlab.com>.
-- Learn [how to use GitLab Pages to produce a code coverage report](https://about.gitlab.com/blog/2016/11/03/publish-code-coverage-report-with-gitlab-pages/).
+This document was moved to [getting_started/pages_from_scratch.md](getting_started/pages_from_scratch.md).
diff --git a/doc/user/project/pages/index.md b/doc/user/project/pages/index.md
index 5861282ca6f..b116c28d94b 100644
--- a/doc/user/project/pages/index.md
+++ b/doc/user/project/pages/index.md
@@ -46,10 +46,10 @@ To create a GitLab Pages website:
| Document | Description |
| -------- | ----------- |
-| [Use a `.gitlab-ci.yml` template](getting_started/new_or_existing_website.md) | Add a Pages site to an existing project. Use a pre-populated CI template file. |
-| [Create a `gitlab-ci.yml` file from scratch](getting_started_part_four.md) | Add a Pages site to an existing project. Learn how to create and configure your own CI file. |
-| [Use a new project template](getting_started/pages_bundled_template.md) | Create a new project with Pages already configured by using a new project template. |
-| [Fork a sample project](getting_started/fork_sample_project.md) | Create a new project with Pages already configured by forking a sample project. |
+| [Fork a sample project](getting_started/pages_forked_sample_project.md) | Create a new project with Pages already configured by forking a sample project. |
+| [Use a new project template](getting_started/pages_new_project_template.md) | Create a new project with Pages already configured by using a new project template. |
+| [Use a `.gitlab-ci.yml` template](getting_started/pages_ci_cd_template.md) | Add a Pages site to an existing project. Use a pre-populated CI template file. |
+| [Create a `gitlab-ci.yml` file from scratch](getting_started/pages_from_scratch.md) | Add a Pages site to an existing project. Learn how to create and configure your own CI file. |
To update a GitLab Pages website:
@@ -81,7 +81,7 @@ becomes available automatically.
To deploy your site, GitLab uses its built-in tool called [GitLab CI/CD](../../../ci/README.md)
to build your site and publish it to the GitLab Pages server. The sequence of
scripts that GitLab CI/CD runs to accomplish this task is created from a file named
-`.gitlab-ci.yml`, which you can [create and modify](getting_started_part_four.md) at will. A specific `job` called `pages` in the configuration file will make GitLab aware that you are deploying a GitLab Pages website.
+`.gitlab-ci.yml`, which you can [create and modify](getting_started/pages_from_scratch.md) at will. A specific `job` called `pages` in the configuration file will make GitLab aware that you are deploying a GitLab Pages website.
You can either use GitLab's [default domain for GitLab Pages websites](getting_started_part_one.md#gitlab-pages-default-domain-names),
`*.gitlab.io`, or your own domain (`example.com`). In that case, you'll
diff --git a/doc/user/project/pages/introduction.md b/doc/user/project/pages/introduction.md
index 614a0d0dd19..a6923779f24 100644
--- a/doc/user/project/pages/introduction.md
+++ b/doc/user/project/pages/introduction.md
@@ -118,19 +118,19 @@ is so `cp` doesn't also copy `public/` to itself in an infinite loop:
```yaml
pages:
script:
- - mkdir .public
- - cp -r * .public
- - mv .public public
+ - mkdir .public
+ - cp -r * .public
+ - mv .public public
artifacts:
paths:
- - public
+ - public
only:
- - master
+ - master
```
### `.gitlab-ci.yml` for a static site generator
-See this document for a [step-by-step guide](getting_started_part_four.md).
+See this document for a [step-by-step guide](getting_started/pages_from_scratch.md).
### `.gitlab-ci.yml` for a repository where there's also actual code
@@ -161,13 +161,13 @@ image: ruby:2.6
pages:
script:
- - gem install jekyll
- - jekyll build -d public/
+ - gem install jekyll
+ - jekyll build -d public/
artifacts:
paths:
- - public
+ - public
only:
- - pages
+ - pages
```
See an example that has different files in the [`master` branch](https://gitlab.com/pages/jekyll-branched/tree/master)
diff --git a/doc/user/project/protected_tags.md b/doc/user/project/protected_tags.md
index e80b8098bec..bb5bca39398 100644
--- a/doc/user/project/protected_tags.md
+++ b/doc/user/project/protected_tags.md
@@ -12,7 +12,7 @@ This feature evolved out of [protected branches](protected_branches.md)
## Overview
-Protected tags will prevent anyone from updating or deleting the tag, as and will prevent creation of matching tags based on the permissions you have selected. By default, anyone without Maintainer permission will be prevented from creating tags.
+Protected tags will prevent anyone from updating or deleting the tag, and will prevent creation of matching tags based on the permissions you have selected. By default, anyone without Maintainer permission will be prevented from creating tags.
## Configuring protected tags
diff --git a/doc/user/project/quick_actions.md b/doc/user/project/quick_actions.md
index a3df173bd9d..def05bf94e4 100644
--- a/doc/user/project/quick_actions.md
+++ b/doc/user/project/quick_actions.md
@@ -7,14 +7,15 @@ info: To determine the technical writer assigned to the Stage/Group associated w
# GitLab Quick Actions
+> - Introduced in [GitLab 12.1](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/26672):
+> once an action is executed, an alert appears when a quick action is successfully applied.
+> - In [GitLab 13.2](https://gitlab.com/gitlab-org/gitlab/-/issues/16877) and later, you can use
+> quick actions when updating the description of issues, epics, and merge requests.
+
Quick actions are textual shortcuts for common actions on issues, epics, merge requests,
and commits that are usually done by clicking buttons or dropdowns in GitLab's UI.
-You can enter these commands while creating a new issue or merge request, or
-in comments of issues, epics, merge requests, and commits. Each command should be
-on a separate line in order to be properly detected and executed.
-
-> From [GitLab 12.1](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/26672), once an
-> action is executed, an alert appears when a quick action is successfully applied.
+You can enter these commands in the description or in comments of issues, epics, merge requests, and commits.
+Each command should be on a separate line in order to be properly detected and executed.
## Quick Actions for issues, merge requests and epics
@@ -40,7 +41,7 @@ The following quick actions are applicable to descriptions, discussions and thre
| `/create_merge_request <branch name>` | ✓ | | | Create a new merge request starting from the current issue. |
| `/done` | ✓ | ✓ | ✓ | Mark To-Do as done. |
| `/due <date>` | ✓ | | | Set due date. Examples of valid `<date>` include `in 2 days`, `this Friday` and `December 31st`. |
-| `/duplicate <#issue>` | ✓ | | | Mark this issue as a duplicate of another issue and mark them as related. **(STARTER)** |
+| `/duplicate <#issue>` | ✓ | | | Close this issue and mark as a duplicate of another issue. **(CORE)** Also, mark both as related. **(STARTER)** |
| `/epic <epic>` | ✓ | | | Add to epic `<epic>`. The `<epic>` value should be in the format of `&epic`, `group&epic`, or a URL to an epic. **(PREMIUM)** |
| `/estimate <<W>w <DD>d <hh>h <mm>m>` | ✓ | ✓ | | Set time estimate. For example, `/estimate 1w 3d 2h 14m`. |
| `/iteration *iteration:iteration` | ✓ | | | Set iteration ([Introduced in GitLab 13.1](https://gitlab.com/gitlab-org/gitlab/-/issues/196795)) **(STARTER)** |
diff --git a/doc/user/project/releases/img/custom_notifications_dropdown_v12_5.png b/doc/user/project/releases/img/custom_notifications_dropdown_v12_5.png
deleted file mode 100644
index 879599a71f5..00000000000
--- a/doc/user/project/releases/img/custom_notifications_dropdown_v12_5.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/project/releases/img/custom_notifications_new_release_v12_5.png b/doc/user/project/releases/img/custom_notifications_new_release_v12_5.png
deleted file mode 100644
index d136aa710b2..00000000000
--- a/doc/user/project/releases/img/custom_notifications_new_release_v12_5.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/project/releases/img/edit_release_page_v13_0.png b/doc/user/project/releases/img/edit_release_page_v13_0.png
deleted file mode 100644
index 1b4343019af..00000000000
--- a/doc/user/project/releases/img/edit_release_page_v13_0.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/project/releases/img/milestone_list_with_releases_v12_5.png b/doc/user/project/releases/img/milestone_list_with_releases_v12_5.png
index 2e3ec08ba87..efe48058d9a 100644
--- a/doc/user/project/releases/img/milestone_list_with_releases_v12_5.png
+++ b/doc/user/project/releases/img/milestone_list_with_releases_v12_5.png
Binary files differ
diff --git a/doc/user/project/releases/img/milestone_with_releases_v12_5.png b/doc/user/project/releases/img/milestone_with_releases_v12_5.png
deleted file mode 100644
index 8719a58ce4e..00000000000
--- a/doc/user/project/releases/img/milestone_with_releases_v12_5.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/project/releases/img/new_tag_12_5.png b/doc/user/project/releases/img/new_tag_12_5.png
deleted file mode 100644
index 9a6145d71c7..00000000000
--- a/doc/user/project/releases/img/new_tag_12_5.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/project/releases/img/release_edit_button_v12_6.png b/doc/user/project/releases/img/release_edit_button_v12_6.png
deleted file mode 100644
index 8cc080621cf..00000000000
--- a/doc/user/project/releases/img/release_edit_button_v12_6.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/project/releases/img/release_milestone_dropdown_v13_0.png b/doc/user/project/releases/img/release_milestone_dropdown_v13_0.png
deleted file mode 100644
index 453c7ca93cc..00000000000
--- a/doc/user/project/releases/img/release_milestone_dropdown_v13_0.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/project/releases/img/release_with_milestone_v12_9.png b/doc/user/project/releases/img/release_with_milestone_v12_9.png
index 53100e33955..c828e36114a 100644
--- a/doc/user/project/releases/img/release_with_milestone_v12_9.png
+++ b/doc/user/project/releases/img/release_with_milestone_v12_9.png
Binary files differ
diff --git a/doc/user/project/releases/img/releases_count_v13_2.png b/doc/user/project/releases/img/releases_count_v13_2.png
new file mode 100644
index 00000000000..1c0493326d1
--- /dev/null
+++ b/doc/user/project/releases/img/releases_count_v13_2.png
Binary files differ
diff --git a/doc/user/project/releases/img/releases_v12_9.png b/doc/user/project/releases/img/releases_v12_9.png
deleted file mode 100644
index bd23cf76651..00000000000
--- a/doc/user/project/releases/img/releases_v12_9.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/project/releases/img/tags_12_5.png b/doc/user/project/releases/img/tags_12_5.png
deleted file mode 100644
index c9673a5232d..00000000000
--- a/doc/user/project/releases/img/tags_12_5.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/project/releases/index.md b/doc/user/project/releases/index.md
index 58d143fb32b..258601574ca 100644
--- a/doc/user/project/releases/index.md
+++ b/doc/user/project/releases/index.md
@@ -9,261 +9,302 @@ info: To determine the technical writer assigned to the Stage/Group associated w
> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/issues/41766) in GitLab 11.7.
-It is typical to create a [Git tag](../../../university/training/topics/tags.md) at
-the moment of release to introduce a checkpoint in your source code
-history, but in most cases your users will need compiled objects or other
-assets output by your CI system to use them, not just the raw source
-code.
-
-GitLab's **Releases** are a way to track deliverables in your project. Consider them
-a snapshot in time of the source, build output, artifacts, and other metadata
+To introduce a checkpoint in your source code history, you can assign a
+[Git tag](https://git-scm.com/book/en/v2/Git-Basics-Tagging) at the moment of release.
+However, in most cases, your users need more than just the raw source code.
+They need compiled objects or other assets output by your CI/CD system.
+
+A GitLab *Release* is a snapshot of the source, build output, artifacts, and other metadata
associated with a released version of your code.
-## Getting started with Releases
+You can create a GitLab release on any branch. When you create a release:
-Start by giving a [description](#release-description) to the Release and
-including its [assets](#release-assets), as follows.
+- GitLab automatically archives source code and associates it with the release.
+- GitLab automatically creates a JSON file that lists everything in the release,
+ so you can compare and audit releases. This file is called [release evidence](#release-evidence).
+- You can add release notes and a message for the tag associated with the release.
-## Release versioning
+After you create a release, you can [associate milestones with it](#associate-milestones-with-a-release),
+and attach [release assets](#release-assets), like runbooks or packages.
-Release versions are manually assigned by the user in the Release title. GitLab uses [Semantic Versioning](https://semver.org/) for our releases, and we recommend you do too. Use `(Major).(Minor).(Patch)`, as detailed in the [GitLab Policy for Versioning](../../../policy/maintenance.md#versioning).
+## View releases
-For example, for GitLab version `10.5.7`:
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/36667) in GitLab 12.8.
-- `10` represents the major version. The major release was `10.0.0`, but often referred to as `10.0`.
-- `5` represents the minor version. The minor release was `10.5.0`, but often referred to as `10.5`.
-- `7` represents the patch number.
+To view a list of releases:
-Any part of the version number can be multiple digits, for example, `13.10.11`.
+- Go to **Project overview > Releases**, or
+
+- On the project's overview page, if at least one release exists, click the number of releases.
+
+ ![Number of Releases](img/releases_count_v13_2.png "Incremental counter of Releases")
-### Release description
+ - On public projects, this number is visible to all users.
+ - On private projects, this number is visible to users with Reporter
+ [permissions](../../permissions.md#project-members-permissions) or higher.
-Every Release has a description. You can add any text you like, but we recommend
-including a changelog to describe the content of your release. This will allow
-your users to quickly scan the differences between each one you publish.
+## Create a release
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/32812) in GitLab 12.9. Releases can be created directly in the GitLab UI.
NOTE: **Note:**
-[Git's tagging messages](https://git-scm.com/book/en/v2/Git-Basics-Tagging) and
-Release descriptions are unrelated. Description supports [Markdown](../../markdown.md).
+Only users with Developer permissions or higher can create releases.
+Read more about [Release permissions](../../../user/permissions.md#project-members-permissions).
-### Release assets
+You can create a release in the user interface, or by using the
+[Releases API](../../../api/releases/index.md#create-a-release).
+We recommend using the API to add release notes as one of the last steps in your CI/CD release pipeline.
-You can currently add the following types of assets to each Release:
+To create a new release through the GitLab UI:
-- [Source code](#source-code): state of the repository at the time of the Release
-- [Links](#links): to content such as built binaries or documentation
+1. Navigate to **Project overview > Releases** and click the **New release** button.
+1. In the [**Tag name**](#tag-name) box, enter a name.
+1. In the **Create from** list, select the branch or enter a tag or commit SHA.
+1. In the **Message** box, enter a message associated with the tag.
+1. Optionally, in the [**Release notes**](#release-notes-description)
+ field, enter the release's description. You can use Markdown and drag and drop files to this field.
+ - If you leave this field empty, only a tag will be created.
+ - If you populate it, both a tag and a release will be created.
+1. Click **Create tag**.
-GitLab will support more asset types in the future, including objects such
-as pre-built packages, compliance/security evidence, or container images.
+If you created a release, you can view it at **Project overview > Releases**.
+If you created a tag, you can view it at **Repository > Tags**.
-#### Source code
+You can now edit the release to [add milestones](#associate-milestones-with-a-release)
+and [release assets](#release-assets).
-GitLab automatically generate `zip`, `tar.gz`, `tar.bz2` and `tar`
-archived source code from the given Git tag. These are read-only assets.
+### Schedule a future release
-#### Links
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/issues/38105) in GitLab 12.1.
-A link is any URL which can point to whatever you like; documentation, built
-binaries, or other related materials. These can be both internal or external
-links from your GitLab instance.
+You can create a release ahead of time by using the [Releases API](../../../api/releases/index.md#upcoming-releases).
+When you set a future `released_at` date, an **Upcoming Release** badge is displayed next to the
+release tag. When the `released_at` date and time has passed, the badge is automatically removed.
-The four types of links are "Runbook," "Package," "Image," and "Other."
+![An upcoming release](img/upcoming_release_v12_7.png)
-#### Permanent links to Release assets
+## Edit a release
-> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/27300) in GitLab 12.9.
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/26016) in GitLab 12.6. Asset link editing was [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/9427) in GitLab 12.10.
-The assets associated with a Release are accessible through a permanent URL.
-GitLab will always redirect this URL to the actual asset
-location, so even if the assets move to a different location, you can continue
-to use the same URL. This is defined during [link creation](../../../api/releases/links.md#create-a-link) or [updating](../../../api/releases/links.md#update-a-link).
+NOTE: **Note:**
+Only users with Developer permissions or higher can edit releases.
+Read more about [Release permissions](../../../user/permissions.md#project-members-permissions).
-Each asset has a name, a URL of the *actual* asset location, and optionally, a
-`filepath` parameter, which, if you specify it, will create a URL pointing
-to the asset for the Release. The format of the URL is:
+To edit the details of a release:
-```html
-https://host/namespace/project/releases/:release/downloads/:filepath
-```
+1. Navigate to **Project overview > Releases**.
+1. In the top-right corner of the release you want to modify, click **Edit this release** (the pencil icon).
+1. On the **Edit Release** page, change the release's details.
+1. Click **Save changes**.
-If you have an asset for the `v11.9.0-rc2` release in the `gitlab-org`
-namespace and `gitlab-runner` project on `gitlab.com`, for example:
+You can edit the release title, notes, associated milestones, and asset links.
+To change other release information, such as the tag or release date, use the
+[Releases API](../../../api/releases/index.md#update-a-release).
-```json
-{
- "name": "linux amd64",
- "filepath": "/binaries/gitlab-runner-linux-amd64",
- "url": "https://gitlab-runner-downloads.s3.amazonaws.com/v11.9.0-rc2/binaries/gitlab-runner-linux-amd64"
-}
-```
+## Add release notes to Git tags
-This asset has a direct link of:
+If you have an existing Git tag, you can add release notes to it.
-```html
-https://gitlab.com/gitlab-org/gitlab-runner/releases/v11.9.0-rc2/downloads/binaries/gitlab-runner-linux-amd64
-```
+You can do this in the user interface, or by using the [Releases API](../../../api/releases/index.md).
+We recommend using the API to add release notes as one of the last steps in your CI/CD release pipeline.
-The physical location of the asset can change at any time and the direct link will remain unchanged.
+In the interface, to add release notes to a new Git tag:
+
+1. Navigate to your project's **Repository > Tags**.
+1. Click **New tag**.
+1. In the **Release notes** field, enter the release's description.
+ You can use Markdown and drag and drop files to this field.
+1. Click **Create tag**.
+
+In the interface, to add release notes to an existing Git tag:
+
+1. Navigate to your project's **Repository > Tags**.
+1. Click **Edit release notes** (the pencil icon).
+1. In the **Release notes** field, enter the release's description.
+ You can use Markdown in this field, and drag and drop files to it.
+1. Click **Save changes**.
-### Releases associated with milestones
+## Associate milestones with a release
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/29020) in GitLab 12.5.
> - [Updated](https://gitlab.com/gitlab-org/gitlab/-/issues/39467) to edit milestones in the UI in GitLab 13.0.
-Releases can optionally be associated with one or more
-[project milestones](../milestones/index.md#project-milestones-and-group-milestones)
-by including a `milestones` array in your requests to the
-[Releases API](../../../api/releases/index.md#create-a-release) or by using the dropdown in the [Edit Release](#editing-a-release) page.
+You can associate a release with one or more [project milestones](../milestones/index.md#project-milestones-and-group-milestones).
+
+You can do this in the user interface, or by including a `milestones` array in your request to
+the [Releases API](../../../api/releases/index.md#create-a-release).
+
+In the user interface, to associate milestones to a release:
-![Release edit page with milestones dropdown expanded](img/release_milestone_dropdown_v13_0.png)
+1. Navigate to **Project overview > Releases**.
+1. In the top-right corner of the release you want to modify, click **Edit this release** (the pencil icon).
+1. From the **Milestones** list, select each milestone you want to associate. You can select multiple milestones.
+1. Click **Save changes**.
-Releases display this association with the **Milestone** indicator in the top
-section of the Release block on the **Project overview > Releases** page, along
-with some statistics about the issues in the milestone(s).
+On the **Project overview > Releases** page, the **Milestone** is listed in the top
+section, along with statistics about the issues in the milestones.
![A Release with one associated milestone](img/release_with_milestone_v12_9.png)
-Below is an example of milestones with no Releases, one Release, and two
-Releases, respectively.
+Releases are also visible on the **Issues > Milestones** page, and when you click a milestone
+on this page.
+
+Here is an example of milestones with no releases, one release, and two releases, respectively.
![Milestones with and without Release associations](img/milestone_list_with_releases_v12_5.png)
-This relationship is also visible in the **Releases** section of the sidebar
-when viewing a specific milestone. Below is an example of a milestone
-associated with a large number of Releases.
+## Get notified when a release is created
-![Milestone with lots of associated Releases](img/milestone_with_releases_v12_5.png)
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/26001) in GitLab 12.4.
-## Releases list
+You can be notified by email when a new release is created for your project.
-Navigate to **Project > Releases** in order to see the list of releases for a given
-project.
+To subscribe to notifications for releases:
-![Releases list](img/releases_v12_9.png)
+1. Navigate to **Project overview**.
+1. Click **Notification setting** (the bell icon).
+1. In the list, click **Custom**.
+1. Select the **New release** check box.
+1. Close the dialog box to save.
-### Number of Releases
+## Prevent unintentional releases by setting a deploy freeze
-> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/36667) in GitLab 12.8.
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/29382) in GitLab 13.0.
-The incremental number of Releases is displayed on the project's details page. When clicked,
-it takes you to the list of Releases.
+Prevent unintended production releases during a period of time you specify by
+setting a [*deploy freeze* period](../../../ci/environments/deployment_safety.md).
+Deploy freezes help reduce uncertainty and risk when automating deployments.
-![Number of Releases](img/releases_count_v12_8.png "Incremental counter of Releases")
+Use the [Freeze Periods API](../../../api/freeze_periods.md) to set a `freeze_start` and a `freeze_end`, which
+are defined as [crontab](https://crontab.guru/) entries.
-For private projects, the number of Releases is displayed to users with Reporter
-[permissions](../../permissions.md#project-members-permissions) or higher. For public projects,
-it is displayed to every user regardless of their permission level.
+If the job that's executing is within a freeze period, GitLab CI/CD creates an environment
+variable named `$CI_DEPLOY_FREEZE`.
-### Upcoming Releases
+To prevent the deployment job from executing, create a `rules` entry in your
+`gitlab-ci.yaml`, for example:
-> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/issues/38105) in GitLab 12.1.
+```yaml
+deploy_to_production:
+ stage: deploy
+ script: deploy_to_prod.sh
+ rules:
+ - if: $CI_DEPLOY_FREEZE == null
+```
-A Release may be created ahead of time by specifying a future `released_at` date. Until
-the `released_at` date and time is reached, an **Upcoming Release** badge will appear next to the
-Release tag. Once the `released_at` date and time has passed, the badge is automatically removed.
+If a project contains multiple freeze periods, all periods apply. If they overlap, the freeze covers the
+complete overlapping period.
-![An upcoming release](img/upcoming_release_v12_7.png)
+For more information, see [Deployment safety](../../../ci/environments/deployment_safety.md).
-## Creating a Release
+## Release fields
-> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/32812) in GitLab 12.9, Releases can be created directly through the GitLab Releases UI.
+The following fields are available when you create or edit a release.
-NOTE: **Note:**
-Only users with Developer permissions or higher can create Releases.
-Read more about [Release permissions](../../../user/permissions.md#project-members-permissions).
+### Tag name
-To create a new Release through the GitLab UI:
+The release tag name should include the release version. GitLab uses [Semantic Versioning](https://semver.org/)
+for our releases, and we recommend you do too. Use `(Major).(Minor).(Patch)`, as detailed in the
+[GitLab Policy for Versioning](../../../policy/maintenance.md#versioning).
-1. Navigate to **Project overview > Releases** and click the **New release** button.
-1. On the **New Tag** page, fill out the tag details.
-1. Optionally, in the **Release notes** field, enter the Release's description.
- If you leave this field empty, only a tag will be created.
- If you populate it, both a tag and a Release will be created.
-1. Click **Create tag**.
+For example, for GitLab version `10.5.7`:
-If you created a release, you can view it at **Project overview > Releases**.
+- `10` represents the major version. The major release was `10.0.0`, but often referred to as `10.0`.
+- `5` represents the minor version. The minor release was `10.5.0`, but often referred to as `10.5`.
+- `7` represents the patch number.
-You can also create a Release using the [Releases API](../../../api/releases/index.md#create-a-release):
-we recommend doing this as one of the last steps in your CI/CD release pipeline.
+Any part of the version number can be multiple digits, for example, `13.10.11`.
-## Editing a release
+### Release notes description
-> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/26016) in GitLab 12.6. Asset link editing was [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/9427) in GitLab 12.10.
+Every release has a description. You can add any text you like, but we recommend
+including a changelog to describe the content of your release. This helps users
+quickly scan the differences between each release you publish.
-To edit the details of a release, navigate to **Project overview > Releases** and click
-the edit button (pencil icon) in the top-right corner of the release you want to modify.
+NOTE: **Note:**
+[Git's tagging messages](https://git-scm.com/book/en/v2/Git-Basics-Tagging) and
+Release note descriptions are unrelated. Description supports [Markdown](../../markdown.md).
-![A release with an edit button](img/release_edit_button_v12_6.png)
+### Release assets
-This will bring you to the **Edit Release** page, from which you can
-change some of the release's details.
+You can currently add the following types of assets to each release:
-![Edit release page](img/edit_release_page_v13_0.png)
+- [Source code](#source-code)
+- [Links](#links)
-Currently, it is only possible to edit the release title, notes, associated milestones, and asset
-links. To change other release information, such as its tag, or release date, use the [Releases
-API](../../../api/releases/index.md#update-a-release). Editing this information
-through the **Edit Release** page is planned for a future version of GitLab.
+GitLab will support more asset types in the future, including objects such
+as pre-built packages, compliance/security evidence, or container images.
-## Notification for Releases
+#### Permanent links to release assets
-> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/26001) in GitLab 12.4.
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/27300) in GitLab 12.9.
-You can be notified by email when a new Release is created for your project.
+The assets associated with a release are accessible through a permanent URL.
+GitLab will always redirect this URL to the actual asset
+location, so even if the assets move to a different location, you can continue
+to use the same URL. This is defined during [link creation](../../../api/releases/links.md#create-a-link) or [updating](../../../api/releases/links.md#update-a-link).
-To subscribe to Release notifications:
+Each asset has a name, a URL of the *actual* asset location, and optionally, a
+`filepath` parameter, which, if you specify it, will create a URL pointing
+to the asset for the Release. The format of the URL is:
-1. Navigate to your **Project**'s landing page.
-1. Click the bell icon (**Notification setting**).
-1. Select **Custom** from the dropdown menu.
- ![Custom notification - Dropdown menu](img/custom_notifications_dropdown_v12_5.png)
-1. Select **New release**.
- ![Custom notification - New release](img/custom_notifications_new_release_v12_5.png)
+```plaintext
+https://host/namespace/project/releases/:release/downloads/:filepath
+```
-## Add release notes to Git tags
+If you have an asset for the `v11.9.0-rc2` release in the `gitlab-org`
+namespace and `gitlab-runner` project on `gitlab.com`, for example:
-You can add release notes to any Git tag using the notes feature. Release notes
-behave like any other Markdown form in GitLab so you can write text and
-drag and drop files to it. Release notes are stored in GitLab's database.
+```json
+{
+ "name": "linux amd64",
+ "filepath": "/binaries/gitlab-runner-linux-amd64",
+ "url": "https://gitlab-runner-downloads.s3.amazonaws.com/v11.9.0-rc2/binaries/gitlab-runner-linux-amd64"
+}
+```
+
+This asset has a direct link of:
-There are several ways to add release notes:
+```plaintext
+https://gitlab.com/gitlab-org/gitlab-runner/releases/v11.9.0-rc2/downloads/binaries/gitlab-runner-linux-amd64
+```
+
+The physical location of the asset can change at any time and the direct link will remain unchanged.
-- In the interface, when you create a new Git tag.
-- In the interface, by adding a release note to an existing Git tag.
-- Using the [Releases API](../../../api/releases/index.md): (we recommend doing this as one of the last
- steps in your CI/CD release pipeline).
+### Source code
-To create a new tag, navigate to your project's **Repository > Tags** and
-click **New tag**. From there, you can fill the form with all the information
-about the release:
+GitLab automatically generates `zip`, `tar.gz`, `tar.bz2` and `tar`
+archived source code from the given Git tag. These are read-only assets.
-![new_tag](img/new_tag_12_5.png "Creation of a new tag.")
+### Links
-You can also edit an existing tag to add release notes:
+A link is any URL which can point to whatever you like: documentation, built
+binaries, or other related materials. These can be both internal or external
+links from your GitLab instance.
-![tags](img/tags_12_5.png "Addition of note to an existing tag")
+The four types of links are "Runbook," "Package," "Image," and "Other."
-## Release Evidence
+## Release evidence
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/26019) in GitLab 12.6.
Each time a release is created, GitLab takes a snapshot of data that's related to it.
-This data is called Release Evidence. It includes linked milestones and issues, and
-it is taken at moment the release is created. It provides a chain of custody and can
-facilitate processes like external audits, for example.
+This data is saved in a JSON file and called *release evidence*. It includes linked milestones
+and issues and can facilitate internal processes like external audits.
+
+To access the release evidence, on the Releases page, click the link to the JSON file that's listed
+under the **Evidence collection** heading.
You can also [use the API](../../../api/releases/index.md#collect-release-evidence-premium-only) to
-generate Release Evidence for an existing release. Because of this, [each release](#releases-list)
-can have multiple Release Evidence snapshots. You can view the Release Evidence and
-its details on the Release page.
+generate release evidence for an existing release. Because of this, each release
+can have multiple release evidence snapshots. You can view the release evidence and
+its details on the Releases page.
NOTE: **Note:**
When the issue tracker is disabled, release evidence [cannot be downloaded](https://gitlab.com/gitlab-org/gitlab/-/issues/208397).
-Release Evidence is stored as a JSON object, so you can compare evidence by using
-commonly-available tools.
-
-Here is an example of a Release Evidence object:
+Here is an example of a release evidence object:
```json
{
@@ -313,94 +354,95 @@ Here is an example of a Release Evidence object:
"created_at": "2019-04-17 15:45:12 UTC",
"issues": []
}
+ ],
+ "report_artifacts": [
+ {
+ "url":"https://gitlab.example.com/root/project-name/-/jobs/111/artifacts/download"
+ }
]
}
}
```
-### Diabling Release Evidence display **(CORE ONLY)**
+### Collect release evidence **(PREMIUM ONLY)**
-This feature comes with the `:release_evidence_collection` feature flag
-enabled by default in GitLab self-managed instances. To turn it off,
-ask a GitLab administrator with Rails console access to run the following
-command:
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/199065) in [GitLab Premium](https://about.gitlab.com/pricing/) 12.10.
-```ruby
-Feature.disable(:release_evidence_collection)
-```
+When a release is created, release evidence is automatically collected. To initiate evidence collection any other time, use an [API call](../../../api/releases/index.md#collect-release-evidence-premium-only). You can collect release evidence multiple times for one release.
-NOTE: **Note:**
-Please note that Release Evidence's data is collected regardless of this
-feature flag, which only enables or disables the display of the data on the
-Releases page.
+Evidence collection snapshots are visible on the Releases page, along with the timestamp the evidence was collected.
-### Collect release evidence **(PREMIUM ONLY)**
+### Include report artifacts as release evidence **(ULTIMATE ONLY)**
-> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/199065) in [GitLab Premium](https://about.gitlab.com/pricing/) 12.10.
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/32773) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 13.2.
-Evidence collection can be initiated by using an [API call](../../../api/releases/index.md#collect-release-evidence-premium-only) at any time. Evidence snapshots are visible on
-the Release page, along with the timestamp the Evidence was collected.
+When you create a release, if [job artifacts](../../../ci/pipelines/job_artifacts.md#artifactsreports) are included in the last pipeline that ran, they are automatically included in the release as release evidence.
-### Schedule release evidence collection
+Although job artifacts normally expire, artifacts included in release evidence do not expire.
-> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/23697) in GitLab 12.8.
+To enable job artifact collection you need to specify both:
-When the `released_at` date and time is not provided, the date and time of Release
-creation is used. The Evidence collection background job is immediately executed.
+1. [`artifacts:paths`](../../../ci/yaml/README.md#artifactspaths)
+1. [`artifacts:reports`](../../../ci/pipelines/job_artifacts.md#artifactsreports)
-If a future `released_at` is specified, the Release becomes an **Upcoming Release**. In this
-case, the Evidence is scheduled to be collected at the `released_at` date and time, via a
-background job.
+```yaml
+ruby:
+ script:
+ - gem install bundler
+ - bundle install
+ - bundle exec rspec --format progress --format RspecJunitFormatter --out rspec.xml
+ artifacts:
+ paths:
+ - rspec.xml
+ reports:
+ junit: rspec.xml
+```
-If a past `released_at` is used, no Evidence is collected for the Release.
+If the pipeline ran successfully, when you create your release, the `rspec.xml` file is saved as release evidence.
-## GitLab Releaser
+NOTE: **Note:**
+If you [schedule release evidence collection](#schedule-release-evidence-collection), some artifacts may already be expired by the time of evidence collection. To avoid this you can use the [`artifacts:expire_in`](../../../ci/yaml/README.md#artifactsexpire_in) keyword. Learn more in [this issue](https://gitlab.com/gitlab-org/gitlab/-/issues/222351).
-> [Introduced](https://gitlab.com/gitlab-org/gitlab-releaser/-/merge_requests/6) in GitLab 12.10.
+### Schedule release evidence collection
-GitLab Releaser is a CLI tool for managing GitLab Releases from the command line or from
-GitLab CI/CD's configuration file, `.gitlab-ci.yml`.
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/23697) in GitLab 12.8.
-With it, you can create, update, modify, and delete Releases right through the
-terminal.
+In the API:
-Read the [GitLab Releaser documentation](https://gitlab.com/gitlab-org/gitlab-releaser/-/tree/master/docs/index.md)
-for details.
+- If you specify a future `released_at` date, the release becomes an **Upcoming Release**
+ and the evidence is collected on the date of the release. You cannot collect
+ release evidence before then.
+- If you use a past `released_at` date, no evidence is collected.
+- If you do not specify a `released_at` date, release evidence is collected on the
+ date the release is created.
-## Set a deploy freeze
+### Disable release evidence display **(CORE ONLY)**
-> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/29382) in GitLab 13.0.
+The `:release_evidence_collection` feature flag is enabled by default in GitLab
+self-managed instances. To turn it off, ask a GitLab administrator with Rails console
+access to run the following command:
-With a deploy freeze, you can prevent an unintended production release during a
-period of time you specify, whether a company event or public holiday. You can
-now rely on the enforcement of policies that are typically outside the scope of
-GitLab to reduce uncertainty and risk when automating deployments.
+```ruby
+Feature.disable(:release_evidence_collection)
+```
-Deploy freeze periods are set at the Project level, and may be created and
-managed using the [Freeze Periods API](../../../api/freeze_periods.md).
-Each Freeze Period has a `freeze_start` and a `freeze_end`, which are defined
-as [crontab](https://crontab.guru/) entries. If a project contains multiple
-freeze periods, all will apply, and should they overlap, the freeze covers the
-complete overlapped period.
+NOTE: **Note:**
+Release evidence is collected regardless of this feature flag,
+which only enables or disables the display of the data on the
+Releases page.
-During pipeline processing, GitLab CI creates an environment variable named
-`$CI_DEPLOY_FREEZE` if the currently executing job is within a
-Freeze Period.
+## GitLab Releaser
-To take advantage of this variable, create a `rules` entry in your
-`gitlab-ci.yaml` to prevent the deployment job from executing.
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-releaser/-/merge_requests/6) in GitLab 12.10.
-For example:
+GitLab Releaser is a CLI tool for managing GitLab Releases from the command line or from
+GitLab's CI/CD configuration file, `.gitlab-ci.yml`.
-```yaml
-deploy_to_production:
- stage: deploy
- script: deploy_to_prod.sh
- rules:
- - if: $CI_DEPLOY_FREEZE == null
-```
+With it, you can create, update, modify, and delete releases right through the
+terminal.
-For more information, see [Deployment safety](../../../ci/environments/deployment_safety.md).
+Read the [GitLab Releaser documentation](https://gitlab.com/gitlab-org/gitlab-releaser/-/tree/master/docs/index.md)
+for details.
<!-- ## Troubleshooting
diff --git a/doc/user/project/repository/branches/index.md b/doc/user/project/repository/branches/index.md
index 5fc6aa184bd..f94ca7ac106 100644
--- a/doc/user/project/repository/branches/index.md
+++ b/doc/user/project/repository/branches/index.md
@@ -41,17 +41,51 @@ See also:
## Default branch
When you create a new [project](../../index.md), GitLab sets `master` as the default
-branch for your project. You can choose another branch to be your project's
+branch of the repository. You can choose another branch to be your project's
default under your project's **Settings > Repository**.
-The default branch is the branch affected by the
-[issue closing pattern](../../issues/managing_issues.md#closing-issues-automatically),
-which means that _an issue will be closed when a merge request is merged to
-the **default branch**_.
+When closing issues directly from merge requests through the [issue closing pattern](../../issues/managing_issues.md#closing-issues-automatically),
+the target is the project's **default branch**.
-The default branch is also protected against accidental deletion. Read through
-the documentation on [protected branches](../../protected_branches.md#protected-branches)
-to learn more.
+The default branch is also initially [protected](../../protected_branches.md#protected-branches)
+against accidental deletion and forced pushes.
+
+### Custom initial branch name **(CORE ONLY)**
+
+> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/221013) in GitLab 13.2.
+> - It's deployed behind a feature flag, enabled by default.
+> - It's enabled on GitLab.com.
+> - It cannot be enabled or disabled per-project.
+> - It's recommended for production use.
+> - For GitLab self-managed instances, GitLab administrators can opt to [disable it](#enable-or-disable-custom-initial-branch-name-core-only). **(CORE ONLY)**
+
+By default, when you create a new project in GitLab, the initial branch is called `master`.
+For self-managed instances, a GitLab administrator can customize the initial branch name to something
+else. This way, every new project created from then on will start from the custom branch name rather than `master`. To do so:
+
+1. Go to the **{admin}** **Admin Area > Settings > Repository** and expand **Default initial
+ branch name**.
+1. Change the default initial branch to a custom name of your choice.
+1. **Save Changes**.
+
+#### Enable or disable custom initial branch name **(CORE ONLY)**
+
+Setting the default initial branch name is under development but ready for production use.
+It is deployed behind a feature flag that is **enabled by default**.
+[GitLab administrators with access to the GitLab Rails console](../../../../administration/feature_flags.md)
+can opt to disable it for your instance.
+
+To disable it:
+
+```ruby
+Feature.disable(:global_default_branch_name)
+```
+
+To enable it:
+
+```ruby
+Feature.enable(:global_default_branch_name)
+```
## Compare
diff --git a/doc/user/project/repository/index.md b/doc/user/project/repository/index.md
index 48975b7864e..0cf375009a0 100644
--- a/doc/user/project/repository/index.md
+++ b/doc/user/project/repository/index.md
@@ -186,7 +186,8 @@ updated every 15 minutes at most, so may not reflect recent activity. The displa
The project size may differ slightly from one instance to another due to compression, housekeeping, and other factors.
-[Repository size limit](../../admin_area/settings/account_and_limit_settings.md) may be set by admins. GitLab.com's repository size limit [is set by GitLab](../../gitlab_com/index.md#repository-size-limit).
+[Repository size limit](../../admin_area/settings/account_and_limit_settings.md) may be set by admins.
+GitLab.com's repository size limit [is set by GitLab](../../gitlab_com/index.md#repository-size-limit).
## Contributors
diff --git a/doc/user/project/repository/reducing_the_repo_size_using_git.md b/doc/user/project/repository/reducing_the_repo_size_using_git.md
index 124150c441a..baad5027703 100644
--- a/doc/user/project/repository/reducing_the_repo_size_using_git.md
+++ b/doc/user/project/repository/reducing_the_repo_size_using_git.md
@@ -25,11 +25,16 @@ Rewriting repository history is a destructive operation. Make sure to backup you
you begin. The best way back up a repository is to
[export the project](../settings/import_export.md#exporting-a-project-and-its-data).
+NOTE: **Note:**
+Git LFS files can only be removed by an Administrator using a
+[Rake task](../../../raketasks/cleanup.md). Removal of this limitation
+[is planned](https://gitlab.com/gitlab-org/gitlab/-/issues/223621).
+
## Purge files from repository history
To make cloning your project faster, rewrite branches and tags to remove unwanted files.
-1. [Install `git filter-repo`](https://github.com/newren/git-filter-repo/blob/master/INSTALL.md)
+1. [Install `git filter-repo`](https://github.com/newren/git-filter-repo/blob/main/INSTALL.md)
using a supported package manager or from source.
1. Clone a fresh copy of the repository using `--bare`:
@@ -40,12 +45,25 @@ To make cloning your project faster, rewrite branches and tags to remove unwante
1. Using `git filter-repo`, purge any files from the history of your repository.
- To purge all large files, the `--strip-blobs-bigger-than` option can be used:
+ To purge large files, the `--strip-blobs-bigger-than` option can be used:
```shell
git filter-repo --strip-blobs-bigger-than 10M
```
+ To purge large files stored using Git LFS, the `--blob--callback` option can
+ be used. The example below, uses the callback to read the file size from the
+ Git LFS pointer, and removes files larger than 10MB.
+
+ ```shell
+ git filter-repo --blob-callback '
+ if blob.data.startswith(b"version https://git-lfs.github.com/spec/v1"):
+ size_in_bytes = int.from_bytes(blob.data[124:], byteorder="big")
+ if size_in_bytes > 10*1000:
+ blob.skip()
+ '
+ ```
+
To purge specific large files by path, the `--path` and `--invert-paths` options can be combined:
```shell
@@ -80,6 +98,12 @@ To make cloning your project faster, rewrite branches and tags to remove unwante
[Protected tags](../protected_tags.md) will cause this to fail. To proceed, you must remove tag
protection, push, and then re-enable protected tags.
+1. Manually run [project housekeeping](../../../administration/housekeeping.md#manual-housekeeping)
+
+NOTE: **Note:**
+Project statistics are cached for performance. You may need to wait 5-10 minutes
+to see a reduction in storage utilization.
+
## Purge files from GitLab storage
To reduce the size of your repository in GitLab, you must remove GitLab internal references to
@@ -103,7 +127,7 @@ cannot be fetched at all.
However, these refs can be accessed from the Git bundle inside a project export.
-1. [Install `git filter-repo`](https://github.com/newren/git-filter-repo/blob/master/INSTALL.md)
+1. [Install `git filter-repo`](https://github.com/newren/git-filter-repo/blob/main/INSTALL.md)
using a supported package manager or from source.
1. Generate a fresh [export from the
@@ -128,7 +152,7 @@ However, these refs can be accessed from the Git bundle inside a project export.
trying to remove internal refs, we will rely on the `commit-map` produced by each run to tell us
which internal refs to remove.
- NOTE:**Note:**
+ NOTE: **Note:**
`git filter-repo` creates a new `commit-map` file every run, and overwrite the `commit-map` from
the previous run. You will need this file from **every** run. Do the next step every time you run
`git filter-repo`.
@@ -176,6 +200,7 @@ You will receive an email once it has completed.
When using repository cleanup, note:
+- Project statistics are cached. You may need to wait 5-10 minutes to see a reduction in storage utilization.
- Housekeeping prunes loose objects older than 2 weeks. This means objects added in the last 2 weeks
will not be removed immediately. If you have access to the
[Gitaly](../../../administration/gitaly/index.md) server, you may run `git gc --prune=now` to
diff --git a/doc/user/project/repository/repository_mirroring.md b/doc/user/project/repository/repository_mirroring.md
index f75b083e6dc..bdf13100a6a 100644
--- a/doc/user/project/repository/repository_mirroring.md
+++ b/doc/user/project/repository/repository_mirroring.md
@@ -114,7 +114,7 @@ After the mirror is created, this option can currently only be modified via the
To set up a mirror from GitLab to GitHub, you need to follow these steps:
-1. Create a [GitHub personal access token](https://help.github.com/en/github/authenticating-to-github/creating-a-personal-access-token-for-the-command-line) with the `public_repo` box checked.
+1. Create a [GitHub personal access token](https://help.github.com/en/github/authenticating-to-github/creating-a-personal-access-token) with the `public_repo` box checked.
1. Fill in the **Git repository URL** field using this format: `https://<your_github_username>@github.com/<your_github_group>/<your_github_project>.git`.
1. Fill in **Password** field with your GitHub personal access token.
1. Click the **Mirror repository** button.
@@ -125,10 +125,10 @@ The repository will push soon. To force a push, click the appropriate button.
## Setting up a push mirror to another GitLab instance with 2FA activated
-1. On the destination GitLab instance, create a [personal access token](../../profile/personal_access_tokens.md) with `API` scope.
+1. On the destination GitLab instance, create a [personal access token](../../profile/personal_access_tokens.md) with `write_repository` scope.
1. On the source GitLab instance:
1. Fill in the **Git repository URL** field using this format: `https://oauth2@<destination host>/<your_gitlab_group_or_name>/<your_gitlab_project>.git`.
- 1. Fill in **Password** field with the GitLab personal access token created on the destination GitLab instance.
+ 1. Fill in the **Password** field with the GitLab personal access token created on the destination GitLab instance.
1. Click the **Mirror repository** button.
## Pulling from a remote repository **(STARTER)**
@@ -231,7 +231,7 @@ those you expect. GitLab.com and other code hosting sites publish their
fingerprints in the open for you to check:
- [AWS CodeCommit](https://docs.aws.amazon.com/codecommit/latest/userguide/regions.html#regions-fingerprints)
-- [Bitbucket](https://confluence.atlassian.com/bitbucket/ssh-keys-935365775.html)
+- [Bitbucket](https://support.atlassian.com/bitbucket-cloud/docs/configure-ssh-and-two-step-verification/)
- [GitHub](https://help.github.com/en/github/authenticating-to-github/githubs-ssh-key-fingerprints)
- [GitLab.com](../../gitlab_com/index.md#ssh-host-keys-fingerprints)
- [Launchpad](https://help.launchpad.net/SSHFingerprints)
diff --git a/doc/user/project/repository/x509_signed_commits/index.md b/doc/user/project/repository/x509_signed_commits/index.md
index d55d5c5c2d8..ffbad4e0989 100644
--- a/doc/user/project/repository/x509_signed_commits/index.md
+++ b/doc/user/project/repository/x509_signed_commits/index.md
@@ -25,9 +25,11 @@ For a commit or tag to be *verified* by GitLab:
which is usually up to three years.
- The signing time is equal or later then commit time.
-NOTE: **Note:** Certificate revocation lists are checked on a daily basis via background worker.
+NOTE: **Note:**
+Certificate revocation lists are checked on a daily basis via background worker.
-NOTE: **Note:** Self signed certificates without `authorityKeyIdentifier`,
+NOTE: **Note:**
+Self signed certificates without `authorityKeyIdentifier`,
`subjectKeyIdentifier`, and `crlDistributionPoints` are not supported. We
recommend using certificates from a PKI that are in line with
[RFC 5280](https://tools.ietf.org/html/rfc5280).
diff --git a/doc/user/project/requirements/img/requirement_archive_view_v12_10.png b/doc/user/project/requirements/img/requirement_archive_view_v12_10.png
deleted file mode 100644
index b3a52caba6c..00000000000
--- a/doc/user/project/requirements/img/requirement_archive_view_v12_10.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/project/requirements/img/requirement_create_view_v12_10.png b/doc/user/project/requirements/img/requirement_create_view_v12_10.png
deleted file mode 100644
index ecb08fe8a8b..00000000000
--- a/doc/user/project/requirements/img/requirement_create_view_v12_10.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/project/requirements/img/requirement_edit_view_v12_10.png b/doc/user/project/requirements/img/requirement_edit_view_v12_10.png
deleted file mode 100644
index 5251e7eae1e..00000000000
--- a/doc/user/project/requirements/img/requirement_edit_view_v12_10.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/project/requirements/img/requirements_archived_list_view_v12_10.png b/doc/user/project/requirements/img/requirements_archived_list_view_v12_10.png
deleted file mode 100644
index a5487b46894..00000000000
--- a/doc/user/project/requirements/img/requirements_archived_list_view_v12_10.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/project/requirements/img/requirements_archived_list_view_v13_1.png b/doc/user/project/requirements/img/requirements_archived_list_view_v13_1.png
new file mode 100644
index 00000000000..aafc8543bae
--- /dev/null
+++ b/doc/user/project/requirements/img/requirements_archived_list_view_v13_1.png
Binary files differ
diff --git a/doc/user/project/requirements/img/requirements_list_v13_1.png b/doc/user/project/requirements/img/requirements_list_v13_1.png
new file mode 100644
index 00000000000..3d2adfac074
--- /dev/null
+++ b/doc/user/project/requirements/img/requirements_list_v13_1.png
Binary files differ
diff --git a/doc/user/project/requirements/img/requirements_list_view_v12_10.png b/doc/user/project/requirements/img/requirements_list_view_v12_10.png
deleted file mode 100644
index cee1f3781f6..00000000000
--- a/doc/user/project/requirements/img/requirements_list_view_v12_10.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/project/requirements/index.md b/doc/user/project/requirements/index.md
index d9bd02518a4..ae22dbc7e72 100644
--- a/doc/user/project/requirements/index.md
+++ b/doc/user/project/requirements/index.md
@@ -22,7 +22,7 @@ When a feature is no longer necessary, you can [archive the related requirement]
<i class="fa fa-youtube-play youtube" aria-hidden="true"></i>
For an overview, see [GitLab 12.10 Introduces Requirements Management](https://www.youtube.com/watch?v=uSS7oUNSEoU).
-![requirements list view](img/requirements_list_view_v12_10.png)
+![requirements list view](img/requirements_list_v13_1.png)
## Create a requirement
@@ -38,8 +38,6 @@ To create a requirement:
You will see the newly created requirement on the top of the list, as the requirements
list is sorted by creation date in descending order.
-![requirement create view](img/requirement_create_view_v12_10.png)
-
## Edit a requirement
You can edit a requirement (if you have the necessary privileges) from the requirements
@@ -51,16 +49,12 @@ To edit a requirement:
1. Update the title in text input field.
1. Click **Save changes**.
-![requirement edit view](img/requirement_edit_view_v12_10.png)
-
## Archive a requirement
You can archive an open requirement (if you have the necessary privileges) while
you're in the **Open** tab.
-From the requirements list page, click **Archive** (**{archive}**).
-
-![requirement archive view](img/requirement_archive_view_v12_10.png)
+To archive a requirement, click **Archive** (**{archive}**).
As soon as a requirement is archived, it no longer appears in the **Open** tab.
@@ -68,35 +62,37 @@ As soon as a requirement is archived, it no longer appears in the **Open** tab.
You can view the list of archived requirements in the **Archived** tab.
-![archived requirements list](img/requirements_archived_list_view_v12_10.png)
+![archived requirements list](img/requirements_archived_list_view_v13_1.png)
To reopen an archived requirement, click **Reopen**.
As soon as a requirement is reopened, it no longer appears in the **Archived** tab.
-## Search for a requirement from the requirements list page
+## Search for a requirement
-> - Introduced in [GitLab Ultimate](https://about.gitlab.com/pricing/) 13.1.
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/212543) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 13.1.
-You can search for a requirement from the list of requirements using filtered search bar (similar to
-that of Issues and Merge Requests) based on following parameters:
+You can search for a requirement from the requirements list page based on the following criteria:
-- Title
-- Author username
+- Requirement title
+- Author's username
-To search, go to the list of requirements and click the field **Search or filter results**.
-It will display a dropdown menu, from which you can add an author. You can also enter plain
-text to search by epic title or description. When done, press <kbd>Enter</kbd> on your
-keyboard to filter the list.
+To search for a requirement:
-You can also sort requirements list by:
+1. In a project, go to **{requirements}** **Requirements > List**.
+1. Click the **Search or filter results** field. A dropdown menu appears.
+1. Select the requirement author from the dropdown or enter plain text to search by requirement title.
+1. Press <kbd>Enter</kbd> on your keyboard to filter the list.
+
+You can also sort the requirements list by:
- Created date
- Last updated
## Allow requirements to be satisfied from a CI job
-> [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/2859) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 13.1.
+> - [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/2859) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 13.1.
+> - [Added](https://gitlab.com/gitlab-org/gitlab/-/issues/215514) ability to specify individual requirements and their statuses in [GitLab Ultimate](https://about.gitlab.com/pricing/) 13.2.
GitLab supports [requirements test
reports](../../../ci/pipelines/job_artifacts.md#artifactsreportsrequirements-ultimate) now.
@@ -132,6 +128,32 @@ the requirement report is checked for the "all passed" record
(`{"*":"passed"}`), and on success, it marks all existing open requirements as
Satisfied.
+#### Specifying individual requirements
+
+It is possible to specify individual requirements and their statuses.
+
+If the following requirements exist:
+
+- `REQ-1` (with IID `1`)
+- `REQ-2` (with IID `2`)
+- `REQ-3` (with IID `3`)
+
+It is possible to specify that the first requirement passed, and the second failed.
+Valid values are "passed" and "failed".
+By omitting a requirement IID (in this case `REQ-3`'s IID `3`), no result is noted.
+
+```yaml
+requirements_confirmation:
+ when: manual
+ allow_failure: false
+ script:
+ - mkdir tmp
+ - echo "{\"1\":\"passed\", \"2\":\"failed\"}" > tmp/requirements.json
+ artifacts:
+ reports:
+ requirements: tmp/requirements.json
+```
+
### Add the manual job to CI conditionally
To configure your CI to include the manual job only when there are some open
@@ -140,9 +162,9 @@ requirements, add a rule which checks `CI_HAS_OPEN_REQUIREMENTS` CI variable.
```yaml
requirements_confirmation:
rules:
- - if: "$CI_HAS_OPEN_REQUIREMENTS" == "true"
- when: manual
- - when: never
+ - if: "$CI_HAS_OPEN_REQUIREMENTS" == "true"
+ when: manual
+ - when: never
allow_failure: false
script:
- mkdir tmp
diff --git a/doc/user/project/service_desk.md b/doc/user/project/service_desk.md
index ffb1f6a1407..cb6f8e2221d 100644
--- a/doc/user/project/service_desk.md
+++ b/doc/user/project/service_desk.md
@@ -4,10 +4,11 @@ group: Certify
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers
---
-# Service Desk **(STARTER)**
+# Service Desk
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/149) in [GitLab Premium](https://about.gitlab.com/pricing/) 9.1.
> - [Moved](https://gitlab.com/gitlab-org/gitlab/-/issues/214839) to [GitLab Starter](https://about.gitlab.com/pricing/) in 13.0.
+> - [Moved](https://gitlab.com/gitlab-org/gitlab/-/issues/215364) to [GitLab Core](https://about.gitlab.com/pricing/) in 13.2.
## Overview
@@ -61,10 +62,10 @@ users will only see the thread through email.
## Configuring Service Desk
NOTE: **Note:**
-Service Desk is enabled on GitLab.com. If you're a [Silver subscriber](https://about.gitlab.com/pricing/#gitlab-com),
-you can skip step 1 below; you only need to enable it per project.
+Service Desk is enabled on GitLab.com.
+You can skip step 1 below; you only need to enable it per project.
-If you have the correct access and a Premium license, you have the option to set up Service Desk.
+If you have project maintainer access you have the option to set up Service Desk.
Follow these steps to do so:
1. [Set up incoming email](../../administration/incoming_email.md#set-it-up) for the GitLab instance.
@@ -149,7 +150,9 @@ It can contain only lowercase letters (`a-z`), numbers (`0-9`), or underscores (
![Setting custom Service Desk email address](img/service_desk_custom_email_address_v13_0.png)
-For example, suppose you add the following to your configuration:
+You can add the following snippets to your configuration.
+
+Example for installations from source:
```yaml
service_desk_email:
@@ -167,6 +170,32 @@ service_desk_email:
expunge_deleted: true
```
+Example for Omnibus GitLab installations:
+
+```ruby
+gitlab_rails['service_desk_email_enabled'] = true
+
+gitlab_rails['service_desk_email_address'] = "project_contact+%{key}@gmail.com"
+
+gitlab_rails['service_desk_email_email'] = "project_support@gmail.com"
+
+gitlab_rails['service_desk_email_password'] = "[REDACTED]"
+
+gitlab_rails['service_desk_email_mailbox_name'] = "inbox"
+
+gitlab_rails['service_desk_email_idle_timeout'] = 60
+
+gitlab_rails['service_desk_email_log_file'] = "/var/log/gitlab/mailroom/mail_room_json.log"
+
+gitlab_rails['service_desk_email_host'] = "imap.gmail.com"
+
+gitlab_rails['service_desk_email_port'] = 993
+
+gitlab_rails['service_desk_email_ssl'] = true
+
+gitlab_rails['service_desk_email_start_tls'] = false
+```
+
In this case, suppose the `mygroup/myproject` project Service Desk settings has the project name
suffix set to `support`, and a user sends an email to `project_contact+mygroup-myproject-support@example.com`.
As a result, a new Service Desk issue is created from this email in the `mygroup/myproject` project.
diff --git a/doc/user/project/settings/import_export.md b/doc/user/project/settings/import_export.md
index 5a364eb89aa..cb9f0491b44 100644
--- a/doc/user/project/settings/import_export.md
+++ b/doc/user/project/settings/import_export.md
@@ -44,6 +44,8 @@ Note the following:
## Version history
+### 13.0+
+
Starting with GitLab 13.0, GitLab can import bundles that were exported from a different GitLab deployment.
This ability is limited to two previous GitLab [minor](../../../policy/maintenance.md#versioning)
releases, which is similar to our process for [Security Releases](../../../policy/maintenance.md#security-releases).
@@ -61,7 +63,7 @@ Prior to 13.0 this was a defined compatibility table:
| Exporting GitLab version | Importing GitLab version |
| -------------------------- | -------------------------- |
-| 11.7 to 13.0 | 11.7 to 13.0 |
+| 11.7 to 12.10 | 11.7 to 12.10 |
| 11.1 to 11.6 | 11.1 to 11.6 |
| 10.8 to 11.0 | 10.8 to 11.0 |
| 10.4 to 10.7 | 10.4 to 10.7 |
@@ -95,7 +97,7 @@ The following items will be exported:
- Project and wiki repositories
- Project uploads
-- Project configuration, including services
+- Project configuration, excluding integrations
- Issues with comments, merge requests with diffs and comments, labels, milestones, snippets, time tracking,
and other project entities
- Design Management files and data
@@ -162,6 +164,15 @@ NOTE: **Note:**
The maximum import file size can be set by the Administrator, default is 50MB.
As an administrator, you can modify the maximum import file size. To do so, use the `max_import_size` option in the [Application settings API](../../../api/settings.md#change-application-settings) or the [Admin UI](../../admin_area/settings/account_and_limit_settings.md).
+### Project import status
+
+You can query an import through the [Project import/export API](../../../api/project_import_export.md#import-status).
+As described in the API documentation, the query may return an import error or exceptions.
+
+### Import large projects **(CORE ONLY)**
+
+If you have a larger project, consider using a Rake task, as described in our [developer documentation](../../../development/import_project.md#importing-via-a-rake-task).
+
## Rate limits
To help avoid abuse, users are rate limited to:
diff --git a/doc/user/project/settings/index.md b/doc/user/project/settings/index.md
index 0798c39fff5..7fe6e702d1c 100644
--- a/doc/user/project/settings/index.md
+++ b/doc/user/project/settings/index.md
@@ -20,7 +20,7 @@ Adjust your project's name, description, avatar, [default branch](../repository/
The project description also partially supports [standard Markdown](../../markdown.md#standard-markdown-and-extensions-in-gitlab). You can use [emphasis](../../markdown.md#emphasis), [links](../../markdown.md#links), and [line-breaks](../../markdown.md#line-breaks) to add more context to the project description.
-#### Compliance framework **(ULTIMATE)**
+#### Compliance framework **(PREMIUM)**
You can select a framework label to identify that your project has certain compliance requirements or needs additional oversight. Available labels include:
@@ -68,7 +68,7 @@ Some features depend on others:
- If you disable the **Issues** option, GitLab also removes the following
features:
- **Issue Boards**
- - [**Service Desk**](#service-desk-starter) **(STARTER)**
+ - [**Service Desk**](#service-desk-starter)
NOTE: **Note:**
When the **Issues** option is disabled, you can still access **Milestones**
@@ -223,13 +223,18 @@ To remove a project:
1. In the Remove project section, click the **Remove project** button.
1. Confirm the action when asked to.
-This action either:
+This action:
- Removes a project including all associated resources (issues, merge requests etc).
-- Since [GitLab 12.6](https://gitlab.com/gitlab-org/gitlab/-/issues/32935), on
- [GitLab Premium or GitLab.com Silver](https://about.gitlab.com/pricing/) or higher tiers, marks a project for
- deletion. The deletion will happen 7 days later by default, but this can be changed in the
- [instance settings](../../admin_area/settings/visibility_and_access_controls.md#default-deletion-adjourned-period-premium-only).
+- From [GitLab 13.2](https://gitlab.com/gitlab-org/gitlab/-/issues/220382) on [Premium or Silver](https://about.gitlab.com/pricing/) or higher tiers,
+group admins can [configure](../../group/index.md#enabling-delayed-project-removal-premium) projects within a group
+to be deleted after a delayed period.
+When enabled, actual deletion happens after number of days
+specified in [instance settings](../../admin_area/settings/visibility_and_access_controls.md#default-deletion-adjourned-period-premium-only).
+
+CAUTION: **Warning:**
+The default behavior of [Delayed Project deletion](https://gitlab.com/gitlab-org/gitlab/-/issues/32935) in GitLab 12.6 was changed to
+[Immediate deletion](https://gitlab.com/gitlab-org/gitlab/-/issues/220382) in GitLab 13.2.
#### Restore a project **(PREMIUM)**
@@ -269,7 +274,7 @@ Configure Error Tracking to discover and view [Sentry errors within GitLab](../o
### Jaeger tracing **(ULTIMATE)**
-Add the URL of a Jaeger server to allow your users to [easily access the Jaeger UI from within GitLab](../operations/tracing.md).
+Add the URL of a Jaeger server to allow your users to [easily access the Jaeger UI from within GitLab](../../../operations/tracing.md).
### Status Page
diff --git a/doc/user/project/static_site_editor/index.md b/doc/user/project/static_site_editor/index.md
index 15c3bd5522e..8653bb5001a 100644
--- a/doc/user/project/static_site_editor/index.md
+++ b/doc/user/project/static_site_editor/index.md
@@ -9,6 +9,7 @@ description: "The static site editor enables users to edit content on static web
> - WYSIWYG editor [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/214559) in GitLab 13.0.
> - Support for adding images through the WYSIWYG editor [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/216640) in GitLab 13.1.
> - Markdown front matter hidden on the WYSIWYG editor [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/216834) in GitLab 13.1.
+> - Support for `*.md.erb` files [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/223171) in GitLab 13.2.
DANGER: **Danger:**
In GitLab 13.0, we [introduced breaking changes](https://gitlab.com/gitlab-org/gitlab/-/issues/213282)
@@ -100,5 +101,4 @@ company and a new feature has been added to the company product.
## Limitations
-- Currently, the Static Site Editor only works for files ending in `.md`. For example, it will not work for a file `index.html.md.erb` while it works for `index.html.md`.
- The Static Site Editor still cannot be quickly added to existing Middleman sites. Follow this [epic](https://gitlab.com/groups/gitlab-org/-/epics/2784) for updates.
diff --git a/doc/user/project/status_page/index.md b/doc/user/project/status_page/index.md
index ec0a79583d5..1c885ae043f 100644
--- a/doc/user/project/status_page/index.md
+++ b/doc/user/project/status_page/index.md
@@ -34,10 +34,12 @@ Setting up a Status Page is pretty painless but there are a few things you need
To use GitLab Status Page you first need to set up your account details for your cloud provider in the operations settings page. Today, only AWS is supported.
-1. Within your AWS account, create an AWS access key.
-1. Add the following permissions policies:
+#### AWS Setup
+
+1. Within your AWS acccout, create two new IAM policies.
- [Create bucket](https://gitlab.com/gitlab-org/status-page/-/blob/master/deploy/etc/s3_create_policy.json).
- [Update bucket contents](https://gitlab.com/gitlab-org/status-page/-/blob/master/deploy/etc/s3_update_bucket_policy.json) (Remember replace `S3_BUCKET_NAME` with your bucket name).
+1. Create a new AWS access key with the permissions policies created in the first step.
### Status Page project
diff --git a/doc/user/project/web_ide/index.md b/doc/user/project/web_ide/index.md
index ce20f2308e6..81f36317b0c 100644
--- a/doc/user/project/web_ide/index.md
+++ b/doc/user/project/web_ide/index.md
@@ -24,6 +24,8 @@ file path fragments to start seeing results.
## Syntax highlighting
+> Support for `.gitlab.ci.yml` validation [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/218472) in GitLab 13.2.
+
As expected from an IDE, syntax highlighting for many languages within
the Web IDE will make your direct editing even easier.
@@ -35,10 +37,21 @@ The Web IDE currently provides:
- IntelliSense and validation support (displaying errors and warnings, providing
smart completions, formatting, and outlining) for some languages. For example:
TypeScript, JavaScript, CSS, LESS, SCSS, JSON, and HTML.
+- Validation support for certain JSON and YAML files using schemas based on the
+ [JSON Schema Store](https://www.schemastore.org/json/). This feature
+ is only supported for the `.gitlab-ci.yml` file.
+
+ NOTE: **Note:**
+ Validation support based on schemas is hidden behind
+ the feature flag `:schema_linting` on self-managed installations. To enable the
+ feature, you can [turn on the feature flag in Rails console](../../../administration/feature_flags.md#how-to-enable-and-disable-features-behind-flags).
Because the Web IDE is based on the [Monaco Editor](https://microsoft.github.io/monaco-editor/),
you can find a more complete list of supported languages in the
-[Monaco languages](https://github.com/Microsoft/monaco-languages) repository.
+[Monaco languages](https://github.com/Microsoft/monaco-languages) repository. Under the hood,
+Monaco uses the [Monarch](https://microsoft.github.io/monaco-editor/monarch.html) library for syntax highlighting.
+
+If you are missing Syntax Highlighting support for any language, we prepared a short guide on how to [add support for a missing language Syntax Highlighting.](https://gitlab.com/gitlab-org/gitlab/-/blob/master/app/assets/javascripts/ide/lib/languages/README.md)
NOTE: **Note:**
Single file editing is based on the [Ace Editor](https://ace.c9.io).
@@ -210,16 +223,20 @@ to work:
- The Runner needs to have
[`[session_server]` configured properly](https://docs.gitlab.com/runner/configuration/advanced-configuration.html#the-session_server-section).
+ This section requires at least a `session_timeout` value (which defaults to 1800
+ seconds) and a `listen_address` value. If `advertise_address` is not defined, `listen_address` is used.
- If you are using a reverse proxy with your GitLab instance, web terminals need to be
[enabled](../../../administration/integration/terminal.md#enabling-and-disabling-terminal-support). **(ULTIMATE ONLY)**
If you have the terminal open and the job has finished with its tasks, the
terminal will block the job from finishing for the duration configured in
-[`[session_server].terminal_max_retention_time`](https://docs.gitlab.com/runner/configuration/advanced-configuration.html#the-session_server-section)
+[`[session_server].session_timeout`](https://docs.gitlab.com/runner/configuration/advanced-configuration.html#the-session_server-section)
until you close the terminal window.
-NOTE: **Note:** Not all executors are
-[supported](https://docs.gitlab.com/runner/executors/#compatibility-chart)
+NOTE: **Note:**
+Not all executors are
+[supported](https://docs.gitlab.com/runner/executors/#compatibility-chart).
+The [File Sync](#file-syncing-to-web-terminal) feature is supported on Kubernetes runners only.
### Web IDE configuration file
@@ -243,6 +260,8 @@ In the code below there is an example of this configuration file:
```yaml
terminal:
+ # This can be any image that has the necessary runtime environment for your project.
+ image: node:10-alpine
before_script:
- apt-get update
script: sleep 60
diff --git a/doc/user/project/wiki/img/wiki_page_diffs_v13_2.png b/doc/user/project/wiki/img/wiki_page_diffs_v13_2.png
new file mode 100644
index 00000000000..261e6e66c97
--- /dev/null
+++ b/doc/user/project/wiki/img/wiki_page_diffs_v13_2.png
Binary files differ
diff --git a/doc/user/project/wiki/img/wiki_page_history.png b/doc/user/project/wiki/img/wiki_page_history.png
index 5a1ae295ed2..0cef2345e27 100644
--- a/doc/user/project/wiki/img/wiki_page_history.png
+++ b/doc/user/project/wiki/img/wiki_page_history.png
Binary files differ
diff --git a/doc/user/project/wiki/index.md b/doc/user/project/wiki/index.md
index 82dbeb0ff7e..9044ee0765f 100644
--- a/doc/user/project/wiki/index.md
+++ b/doc/user/project/wiki/index.md
@@ -134,47 +134,68 @@ The changes of a wiki page over time are recorded in the wiki's Git repository,
and you can view them by clicking the **Page history** button.
From the history page you can see the revision of the page (Git commit SHA), its
-author, the commit message, when it was last updated, and the page markup format.
+author, the commit message, and when it was last updated.
To see how a previous version of the page looked like, click on a revision
-number.
+number in the **Page version** column.
![Wiki page history](img/wiki_page_history.png)
+### Viewing the changes between page versions
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/15242) in GitLab 13.2.
+
+Similar to versioned diff file views, you can see the changes made in a given Wiki page version:
+
+1. Navigate to the Wiki page you're interested in.
+1. Click on **Page history** to see all page versions.
+1. Click on the commit message in the **Changes** column for the version you're interested in:
+
+ ![Wiki page changes](img/wiki_page_diffs_v13_2.png)
+
## Wiki activity records
-> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/14902) in GitLab 12.10.
-> - It's deployed behind a feature flag, disabled by default.
+> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/14902) in **GitLab 12.10.**
+> - Git events were [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/216014) in **GitLab 13.0.**
> - It's enabled on GitLab.com.
-> - To use it in GitLab self-managed instances, ask a GitLab administrator to [enable it](#enable-or-disable-wiki-events-core-only). **(CORE ONLY)**
+> - Git access activity creation is managed by a feature flag.
+> - To use it in GitLab self-managed instances, ask a GitLab administrator to [enable it](#enable-or-disable-wiki-events-in-git-core-only). **(CORE ONLY)**
Wiki events (creation, deletion, and updates) are tracked by GitLab and
displayed on the [user profile](../../profile/index.md#user-profile),
[group](../../group/index.md#view-group-activity),
and [project](../index.md#project-activity) activity pages.
-### Limitations
-
-Only edits made in the browser or through the API have their activity recorded.
-Edits made and pushed through Git are not currently listed in the activity list.
+### Enable or disable Wiki events in Git **(CORE ONLY)**
-### Enable or disable Wiki Events **(CORE ONLY)**
-
-Wiki event activity is under development and not ready for production use. It is
+Tracking wiki events through Git is under development and not ready for production use. It is
deployed behind a feature flag that is **disabled by default**.
-[GitLab administrators with access to the GitLab Rails console](../../../administration/troubleshooting/navigating_gitlab_via_rails_console.md#starting-a-rails-console-session)
-can enable it for your instance. You're welcome to test it, but use it at your
-own risk.
+[GitLab administrators with access to the GitLab Rails console](../../../administration/feature_flags.md)
+can enable it for your instance.
To enable it:
```ruby
-Feature.enable(:wiki_events)
+Feature.enable(:wiki_events_on_git_push)
+```
+
+To enable for just a particular project:
+
+```ruby
+project = Project.find_by_full_path('your-group/your-project')
+Feature.enable(:wiki_events_on_git_push, project)
```
To disable it:
```ruby
-Feature.disable(:wiki_events)
+Feature.disable(:wiki_events_on_git_push)
+```
+
+To disable for just a particular project:
+
+```ruby
+project = Project.find_by_full_path('your-group/your-project')
+Feature.disable(:wiki_events_on_git_push, project)
```
## Adding and editing wiki pages locally
diff --git a/doc/user/search/advanced_global_search.md b/doc/user/search/advanced_global_search.md
index eaa57395b8f..0613e9b8e34 100644
--- a/doc/user/search/advanced_global_search.md
+++ b/doc/user/search/advanced_global_search.md
@@ -1,11 +1,9 @@
-# Advanced Global Search **(STARTER ONLY)**
+# Advanced Global Search **(STARTER)**
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/109) in GitLab [Starter](https://about.gitlab.com/pricing/) 8.4.
NOTE: **GitLab.com availability:**
-Advanced Global Search (powered by Elasticsearch) is not yet available on GitLab.com.
-It will be progressively enabled for all paid groups in Q3 of 2020.
-[Follow this epic](https://gitlab.com/groups/gitlab-com/-/epics/649) for the latest updates on the timeline.
+Advanced Global Search (powered by Elasticsearch) is enabled for Bronze and above on GitLab.com since 2020-07-10.
Leverage Elasticsearch for faster, more advanced code search across your entire
GitLab instance.
@@ -22,7 +20,6 @@ now search for code within other teams that can help your own project.
GitLab leverages the search capabilities of [Elasticsearch](https://www.elastic.co/elasticsearch/) and enables it when
searching in:
-- GitLab application
- Projects
- Repositories
- Commits
diff --git a/doc/user/search/advanced_search_syntax.md b/doc/user/search/advanced_search_syntax.md
index 1ca9649b581..5dc1f8fe779 100644
--- a/doc/user/search/advanced_search_syntax.md
+++ b/doc/user/search/advanced_search_syntax.md
@@ -1,13 +1,9 @@
-# Advanced Syntax Search **(STARTER ONLY)**
+# Advanced Syntax Search **(STARTER)**
-> **Notes:**
->
> - Introduced in [GitLab Enterprise Starter](https://about.gitlab.com/pricing/) 9.2
NOTE: **GitLab.com availability:**
-Advanced Global Search (powered by Elasticsearch) is not yet available on GitLab.com.
-It will be progressively enabled for all paid groups in Q3 of 2020.
-[Follow this epic](https://gitlab.com/groups/gitlab-com/-/epics/649) for the latest updates on the timeline.
+Advanced Global Search (powered by Elasticsearch) is enabled for Bronze and above on GitLab.com since 2020-07-10.
Use advanced queries for more targeted search results.
diff --git a/doc/user/shortcuts.md b/doc/user/shortcuts.md
index efa374cf1c3..314fe367ca6 100644
--- a/doc/user/shortcuts.md
+++ b/doc/user/shortcuts.md
@@ -78,10 +78,11 @@ These shortcuts are available when viewing issues and merge requests.
| <kbd>m</kbd> | Change milestone. |
| <kbd>l</kbd> | Change label. |
| <kbd>r</kbd> | Start writing a comment. If any text is selected, it will be quoted in the comment. Can't be used to reply within a thread. |
-| <kbd>n</kbd> | Move to next unresolved discussion (Merge requests only). |
-| <kbd>p</kbd> | Move to previous unresolved discussion (Merge requests only). |
-| <kbd>]</kbd> or <kbd>j</kbd> | Move to next file (Merge requests only). |
-| <kbd>[</kbd> or <kbd>k</kbd> | Move to previous file (Merge requests only). |
+| <kbd>n</kbd> | Move to next unresolved discussion (merge requests only). |
+| <kbd>p</kbd> | Move to previous unresolved discussion (merge requests only). |
+| <kbd>]</kbd> or <kbd>j</kbd> | Move to next file (merge requests only). |
+| <kbd>[</kbd> or <kbd>k</kbd> | Move to previous file (merge requests only). |
+| <kbd>b</kbd> | Copy source branch name (merge requests only). |
### Project Files
diff --git a/doc/user/snippets.md b/doc/user/snippets.md
index 68e5c5ac92c..4d8f47ee3be 100644
--- a/doc/user/snippets.md
+++ b/doc/user/snippets.md
@@ -96,6 +96,14 @@ This allows you to have a local copy of the snippet's repository and make
changes as needed. You can commit those changes and push them to the remote
master branch.
+### Reduce snippets repository size
+
+Since versioned Snippets are considered as part of the [namespace storage size](../user/admin_area/settings/account_and_limit_settings.md),
+it's recommended to keep snippets' repositories as compact as possible.
+
+For more information about tools to compact repositories,
+see the documentation on [reducing repository size](../user/project/repository/reducing_the_repo_size_using_git.md).
+
### Limitations
- Binary files are not supported.
diff --git a/doc/user/todos.md b/doc/user/todos.md
index 5d3e3e62652..ef0e75bc197 100644
--- a/doc/user/todos.md
+++ b/doc/user/todos.md
@@ -42,8 +42,7 @@ A To Do appears on your To-Do List when:
- You are `@mentioned` in a comment on a:
- Commit
- Design
-- A job in the CI pipeline running for your merge request failed, but this
- job is not allowed to fail
+- The CI/CD pipeline for your merge request failed
- An open merge request becomes unmergeable due to conflict, and you are either:
- The author
- Have set it to automatically merge once the pipeline succeeds
diff --git a/haml_lint/linter/documentation_links.rb b/haml_lint/linter/documentation_links.rb
new file mode 100644
index 00000000000..f8e0eec5cdc
--- /dev/null
+++ b/haml_lint/linter/documentation_links.rb
@@ -0,0 +1,100 @@
+# frozen_string_literal: true
+
+require_relative '../../lib/gitlab/utils/markdown'
+
+module HamlLint
+ class Linter
+ # This class is responsible for detection of help_page_path helpers
+ # with incorrect links or anchors
+ class DocumentationLinks < Linter
+ include ::HamlLint::LinterRegistry
+ include ::Gitlab::Utils::Markdown
+
+ DOCS_DIRECTORY = File.join(File.expand_path('../..', __dir__), 'doc')
+
+ HELP_PATH_LINK_PATTERN = <<~PATTERN
+ `(send nil? :help_page_path $...)
+ PATTERN
+
+ MARKDOWN_HEADER = %r{\A\#{1,6}\s+(?<header>.+)\Z}.freeze
+
+ def visit_script(node)
+ check(node)
+ end
+
+ def visit_silent_script(node)
+ check(node)
+ end
+
+ def visit_tag(node)
+ check(node)
+ end
+
+ private
+
+ def check(node)
+ match = extract_link_and_anchor(node)
+
+ return if match.empty?
+
+ path_to_file = detect_path_to_file(match[:link])
+
+ unless File.file?(path_to_file)
+ record_lint(node, "help_page_path points to the unknown location: #{path_to_file}")
+ return
+ end
+
+ unless correct_anchor?(path_to_file, match[:anchor])
+ record_lint(node, "anchor (#{match[:anchor]}) is missing in: #{path_to_file}")
+ end
+ end
+
+ def extract_link_and_anchor(node)
+ ast_tree = fetch_ast_tree(node)
+
+ return {} unless ast_tree
+
+ link_match, attributes_match = ::RuboCop::NodePattern.new(HELP_PATH_LINK_PATTERN).match(ast_tree)
+
+ { link: fetch_link(link_match), anchor: fetch_anchor(attributes_match) }.compact
+ end
+
+ def fetch_ast_tree(node)
+ # Sometimes links are provided via data attributes in html tag
+ return node.parsed_attributes.syntax_tree if node.type == :tag
+
+ node.parsed_script.syntax_tree
+ end
+
+ def detect_path_to_file(link)
+ path = File.join(DOCS_DIRECTORY, link)
+ path += '.md' unless path.end_with?('.md')
+ path
+ end
+
+ def fetch_link(link_match)
+ return unless link_match && link_match.str_type?
+
+ link_match.value
+ end
+
+ def fetch_anchor(attributes_match)
+ return unless attributes_match
+
+ attributes_match.each_pair do |pkey, pvalue|
+ break pvalue.value if pkey.value == :anchor
+ end
+ end
+
+ def correct_anchor?(path_to_file, anchor)
+ return true unless anchor
+
+ File.open(path_to_file).any? do |line|
+ result = line.match(MARKDOWN_HEADER)
+
+ string_to_anchor(result[:header]) == anchor if result
+ end
+ end
+ end
+ end
+end
diff --git a/jest.config.base.js b/jest.config.base.js
index 1a1fd4e7b62..422b6779af4 100644
--- a/jest.config.base.js
+++ b/jest.config.base.js
@@ -82,7 +82,9 @@ module.exports = path => {
'^.+\\.js$': 'babel-jest',
'^.+\\.vue$': 'vue-jest',
},
- transformIgnorePatterns: ['node_modules/(?!(@gitlab/ui|bootstrap-vue|three|monaco-editor)/)'],
+ transformIgnorePatterns: [
+ 'node_modules/(?!(@gitlab/ui|bootstrap-vue|three|monaco-editor|monaco-yaml)/)',
+ ],
timers: 'fake',
testEnvironment: '<rootDir>/spec/frontend/environment.js',
testEnvironmentOptions: {
diff --git a/lib/api/access_requests.rb b/lib/api/access_requests.rb
index ee8dc822098..5305b25538f 100644
--- a/lib/api/access_requests.rb
+++ b/lib/api/access_requests.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module API
- class AccessRequests < Grape::API
+ class AccessRequests < Grape::API::Instance
include PaginationParams
before { authenticate! }
diff --git a/lib/api/admin/ci/variables.rb b/lib/api/admin/ci/variables.rb
index df731148bac..6b0ff5e9395 100644
--- a/lib/api/admin/ci/variables.rb
+++ b/lib/api/admin/ci/variables.rb
@@ -3,7 +3,7 @@
module API
module Admin
module Ci
- class Variables < Grape::API
+ class Variables < Grape::API::Instance
include PaginationParams
before { authenticated_as_admin! }
diff --git a/lib/api/admin/instance_clusters.rb b/lib/api/admin/instance_clusters.rb
new file mode 100644
index 00000000000..8208d10c089
--- /dev/null
+++ b/lib/api/admin/instance_clusters.rb
@@ -0,0 +1,134 @@
+# frozen_string_literal: true
+
+module API
+ module Admin
+ class InstanceClusters < Grape::API::Instance
+ include PaginationParams
+
+ before do
+ authenticated_as_admin!
+ end
+
+ namespace 'admin' do
+ desc "Get list of all instance clusters" do
+ detail "This feature was introduced in GitLab 13.2."
+ end
+ get '/clusters' do
+ authorize! :read_cluster, clusterable_instance
+ present paginate(clusters_for_current_user), with: Entities::Cluster
+ end
+
+ desc "Get a single instance cluster" do
+ detail "This feature was introduced in GitLab 13.2."
+ end
+ params do
+ requires :cluster_id, type: Integer, desc: "The cluster ID"
+ end
+ get '/clusters/:cluster_id' do
+ authorize! :read_cluster, cluster
+
+ present cluster, with: Entities::Cluster
+ end
+
+ desc "Add an instance cluster" do
+ detail "This feature was introduced in GitLab 13.2."
+ end
+ params do
+ requires :name, type: String, desc: 'Cluster name'
+ optional :enabled, type: Boolean, default: true, desc: 'Determines if cluster is active or not, defaults to true'
+ optional :environment_scope, default: '*', type: String, desc: 'The associated environment to the cluster'
+ optional :domain, type: String, desc: 'Cluster base domain'
+ optional :management_project_id, type: Integer, desc: 'The ID of the management project'
+ optional :managed, type: Boolean, default: true, desc: 'Determines if GitLab will manage namespaces and service accounts for this cluster, defaults to true'
+ requires :platform_kubernetes_attributes, type: Hash, desc: %q(Platform Kubernetes data) do
+ requires :api_url, type: String, allow_blank: false, desc: 'URL to access the Kubernetes API'
+ requires :token, type: String, desc: 'Token to authenticate against Kubernetes'
+ optional :ca_cert, type: String, desc: 'TLS certificate (needed if API is using a self-signed TLS certificate)'
+ optional :namespace, type: String, desc: 'Unique namespace related to Project'
+ optional :authorization_type, type: String, values: ::Clusters::Platforms::Kubernetes.authorization_types.keys, default: 'rbac', desc: 'Cluster authorization type, defaults to RBAC'
+ end
+ end
+ post '/clusters/add' do
+ authorize! :add_cluster, clusterable_instance
+
+ user_cluster = ::Clusters::CreateService
+ .new(current_user, create_cluster_user_params)
+ .execute
+
+ if user_cluster.persisted?
+ present user_cluster, with: Entities::Cluster
+ else
+ render_validation_error!(user_cluster)
+ end
+ end
+
+ desc "Update an instance cluster" do
+ detail "This feature was introduced in GitLab 13.2."
+ end
+ params do
+ requires :cluster_id, type: Integer, desc: 'The cluster ID'
+ optional :name, type: String, desc: 'Cluster name'
+ optional :enabled, type: Boolean, desc: 'Enable or disable Gitlab\'s connection to your Kubernetes cluster'
+ optional :environment_scope, type: String, desc: 'The associated environment to the cluster'
+ optional :domain, type: String, desc: 'Cluster base domain'
+ optional :management_project_id, type: Integer, desc: 'The ID of the management project'
+ optional :platform_kubernetes_attributes, type: Hash, desc: %q(Platform Kubernetes data) do
+ optional :api_url, type: String, desc: 'URL to access the Kubernetes API'
+ optional :token, type: String, desc: 'Token to authenticate against Kubernetes'
+ optional :ca_cert, type: String, desc: 'TLS certificate (needed if API is using a self-signed TLS certificate)'
+ optional :namespace, type: String, desc: 'Unique namespace related to Project'
+ end
+ end
+ put '/clusters/:cluster_id' do
+ authorize! :update_cluster, cluster
+
+ update_service = ::Clusters::UpdateService.new(current_user, update_cluster_params)
+
+ if update_service.execute(cluster)
+ present cluster, with: Entities::ClusterProject
+ else
+ render_validation_error!(cluster)
+ end
+ end
+
+ desc "Remove a cluster" do
+ detail "This feature was introduced in GitLab 13.2."
+ end
+ params do
+ requires :cluster_id, type: Integer, desc: "The cluster ID"
+ end
+ delete '/clusters/:cluster_id' do
+ authorize! :admin_cluster, cluster
+
+ destroy_conditionally!(cluster)
+ end
+ end
+
+ helpers do
+ def clusterable_instance
+ Clusters::Instance.new
+ end
+
+ def clusters_for_current_user
+ @clusters_for_current_user ||= ClustersFinder.new(clusterable_instance, current_user, :all).execute
+ end
+
+ def cluster
+ @cluster ||= clusters_for_current_user.find(params[:cluster_id])
+ end
+
+ def create_cluster_user_params
+ declared_params.merge({
+ provider_type: :user,
+ platform_type: :kubernetes,
+ clusterable: clusterable_instance
+ })
+ end
+
+ def update_cluster_params
+ declared_params(include_missing: false).without(:cluster_id)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/api/admin/sidekiq.rb b/lib/api/admin/sidekiq.rb
index a700bea0fd7..f4c84f2eee8 100644
--- a/lib/api/admin/sidekiq.rb
+++ b/lib/api/admin/sidekiq.rb
@@ -2,7 +2,7 @@
module API
module Admin
- class Sidekiq < Grape::API
+ class Sidekiq < Grape::API::Instance
before { authenticated_as_admin! }
namespace 'admin' do
diff --git a/lib/api/api.rb b/lib/api/api.rb
index fb67258f331..a89dc0fa6fa 100644
--- a/lib/api/api.rb
+++ b/lib/api/api.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module API
- class API < Grape::API
+ class API < Grape::API::Instance
include APIGuard
LOG_FILENAME = Rails.root.join("log", "api_json.log")
@@ -46,6 +46,8 @@ module API
end
before do
+ coerce_nil_params_to_array!
+
Gitlab::ApplicationContext.push(
user: -> { @current_user },
project: -> { @project },
@@ -108,6 +110,7 @@ module API
end
format :json
+ formatter :json, Gitlab::Json::GrapeFormatter
content_type :txt, "text/plain"
# Ensure the namespace is right, otherwise we might load Grape::API::Helpers
@@ -122,6 +125,7 @@ module API
# Keep in alphabetical order
mount ::API::AccessRequests
mount ::API::Admin::Ci::Variables
+ mount ::API::Admin::InstanceClusters
mount ::API::Admin::Sidekiq
mount ::API::Appearance
mount ::API::Applications
@@ -131,6 +135,10 @@ module API
mount ::API::Boards
mount ::API::Branches
mount ::API::BroadcastMessages
+ mount ::API::Ci::Pipelines
+ mount ::API::Ci::PipelineSchedules
+ mount ::API::Ci::Runner
+ mount ::API::Ci::Runners
mount ::API::Commits
mount ::API::CommitStatuses
mount ::API::ContainerRegistryEvent
@@ -152,6 +160,7 @@ module API
mount ::API::Groups
mount ::API::GroupContainerRepositories
mount ::API::GroupVariables
+ mount ::API::ImportBitbucketServer
mount ::API::ImportGithub
mount ::API::Issues
mount ::API::JobArtifacts
@@ -163,6 +172,7 @@ module API
mount ::API::Members
mount ::API::MergeRequestDiffs
mount ::API::MergeRequests
+ mount ::API::MergeRequestApprovals
mount ::API::Metrics::Dashboard::Annotations
mount ::API::Metrics::UserStarredDashboards
mount ::API::Namespaces
@@ -170,11 +180,20 @@ module API
mount ::API::Discussions
mount ::API::ResourceLabelEvents
mount ::API::ResourceMilestoneEvents
+ mount ::API::ResourceStateEvents
mount ::API::NotificationSettings
+ mount ::API::ProjectPackages
+ mount ::API::GroupPackages
+ mount ::API::PackageFiles
+ mount ::API::NugetPackages
+ mount ::API::PypiPackages
+ mount ::API::ComposerPackages
+ mount ::API::ConanPackages
+ mount ::API::MavenPackages
+ mount ::API::NpmPackages
+ mount ::API::GoProxy
mount ::API::Pages
mount ::API::PagesDomains
- mount ::API::Pipelines
- mount ::API::PipelineSchedules
mount ::API::ProjectClusters
mount ::API::ProjectContainerRepositories
mount ::API::ProjectEvents
@@ -195,8 +214,6 @@ module API
mount ::API::Release::Links
mount ::API::RemoteMirrors
mount ::API::Repositories
- mount ::API::Runner
- mount ::API::Runners
mount ::API::Search
mount ::API::Services
mount ::API::Settings
diff --git a/lib/api/api_guard.rb b/lib/api/api_guard.rb
index c6557fce541..4b87861a3de 100644
--- a/lib/api/api_guard.rb
+++ b/lib/api/api_guard.rb
@@ -43,7 +43,6 @@ module API
# Helper Methods for Grape Endpoint
module HelperMethods
- prepend_if_ee('EE::API::APIGuard::HelperMethods') # rubocop: disable Cop/InjectEnterpriseEditionModule
include Gitlab::Auth::AuthFinders
def access_token
@@ -66,7 +65,7 @@ module API
def find_user_from_sources
deploy_token_from_request ||
- find_user_from_access_token ||
+ find_user_from_bearer_token ||
find_user_from_job_token ||
find_user_from_warden
end
@@ -153,7 +152,14 @@ module API
{ scope: e.scopes })
end
- response.finish
+ status, headers, body = response.finish
+
+ # Grape expects a Rack::Response
+ # (https://github.com/ruby-grape/grape/commit/c117bff7d22971675f4b34367d3a98bc31c8fc02),
+ # so we need to recreate the response again even though
+ # response.finish already does this.
+ # (https://github.com/nov/rack-oauth2/blob/40c9a99fd80486ccb8de0e4869ae384547c0d703/lib/rack/oauth2/server/abstract/error.rb#L26).
+ Rack::Response.new(body, status, headers)
end
end
end
diff --git a/lib/api/appearance.rb b/lib/api/appearance.rb
index 71a35bb4493..f98004af480 100644
--- a/lib/api/appearance.rb
+++ b/lib/api/appearance.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module API
- class Appearance < Grape::API
+ class Appearance < Grape::API::Instance
before { authenticated_as_admin! }
helpers do
diff --git a/lib/api/applications.rb b/lib/api/applications.rb
index 70e6b8395d7..4e8d68c8d09 100644
--- a/lib/api/applications.rb
+++ b/lib/api/applications.rb
@@ -2,7 +2,7 @@
module API
# External applications API
- class Applications < Grape::API
+ class Applications < Grape::API::Instance
before { authenticated_as_admin! }
resource :applications do
diff --git a/lib/api/avatar.rb b/lib/api/avatar.rb
index 0f14d003065..9501e777fff 100644
--- a/lib/api/avatar.rb
+++ b/lib/api/avatar.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module API
- class Avatar < Grape::API
+ class Avatar < Grape::API::Instance
resource :avatar do
desc 'Return avatar url for a user' do
success Entities::Avatar
diff --git a/lib/api/award_emoji.rb b/lib/api/award_emoji.rb
index 8e3b3ff8ce5..0a3df3ed96e 100644
--- a/lib/api/award_emoji.rb
+++ b/lib/api/award_emoji.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module API
- class AwardEmoji < Grape::API
+ class AwardEmoji < Grape::API::Instance
include PaginationParams
before { authenticate! }
diff --git a/lib/api/badges.rb b/lib/api/badges.rb
index d2152fad07b..f6cd3f83ff3 100644
--- a/lib/api/badges.rb
+++ b/lib/api/badges.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module API
- class Badges < Grape::API
+ class Badges < Grape::API::Instance
include PaginationParams
before { authenticate_non_get! }
diff --git a/lib/api/boards.rb b/lib/api/boards.rb
index 87818903705..1f5086127a8 100644
--- a/lib/api/boards.rb
+++ b/lib/api/boards.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module API
- class Boards < Grape::API
+ class Boards < Grape::API::Instance
include BoardsResponses
include PaginationParams
diff --git a/lib/api/branches.rb b/lib/api/branches.rb
index 081e8ffe4f0..5e9c2caf8f5 100644
--- a/lib/api/branches.rb
+++ b/lib/api/branches.rb
@@ -3,7 +3,7 @@
require 'mime/types'
module API
- class Branches < Grape::API
+ class Branches < Grape::API::Instance
include PaginationParams
BRANCH_ENDPOINT_REQUIREMENTS = API::NAMESPACE_OR_PROJECT_REQUIREMENTS.merge(branch: API::NO_SLASH_URL_PART_REGEX)
@@ -32,14 +32,21 @@ module API
params do
use :pagination
use :filter_params
+
+ optional :page_token, type: String, desc: 'Name of branch to start the paginaition from'
end
get ':id/repository/branches' do
user_project.preload_protected_branches
repository = user_project.repository
- branches = BranchesFinder.new(repository, declared_params(include_missing: false)).execute
- branches = paginate(::Kaminari.paginate_array(branches))
+ if Feature.enabled?(:branch_list_keyset_pagination, user_project)
+ branches = BranchesFinder.new(repository, declared_params(include_missing: false)).execute(gitaly_pagination: true)
+ else
+ branches = BranchesFinder.new(repository, declared_params(include_missing: false)).execute
+ branches = paginate(::Kaminari.paginate_array(branches))
+ end
+
merged_branch_names = repository.merged_branch_names(branches.map(&:name))
present(
diff --git a/lib/api/broadcast_messages.rb b/lib/api/broadcast_messages.rb
index 42e7dc751f0..dcf950d7a03 100644
--- a/lib/api/broadcast_messages.rb
+++ b/lib/api/broadcast_messages.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module API
- class BroadcastMessages < Grape::API
+ class BroadcastMessages < Grape::API::Instance
include PaginationParams
resource :broadcast_messages do
diff --git a/lib/api/ci/pipeline_schedules.rb b/lib/api/ci/pipeline_schedules.rb
new file mode 100644
index 00000000000..80ad8aa04dd
--- /dev/null
+++ b/lib/api/ci/pipeline_schedules.rb
@@ -0,0 +1,217 @@
+# frozen_string_literal: true
+
+module API
+ module Ci
+ class PipelineSchedules < Grape::API::Instance
+ include PaginationParams
+
+ before { authenticate! }
+
+ params do
+ requires :id, type: String, desc: 'The ID of a project'
+ end
+ resource :projects, requirements: ::API::API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
+ desc 'Get all pipeline schedules' do
+ success Entities::PipelineSchedule
+ end
+ params do
+ use :pagination
+ optional :scope, type: String, values: %w[active inactive],
+ desc: 'The scope of pipeline schedules'
+ end
+ # rubocop: disable CodeReuse/ActiveRecord
+ get ':id/pipeline_schedules' do
+ authorize! :read_pipeline_schedule, user_project
+
+ schedules = ::Ci::PipelineSchedulesFinder.new(user_project).execute(scope: params[:scope])
+ .preload([:owner, :last_pipeline])
+ present paginate(schedules), with: Entities::PipelineSchedule
+ end
+ # rubocop: enable CodeReuse/ActiveRecord
+
+ desc 'Get a single pipeline schedule' do
+ success Entities::PipelineScheduleDetails
+ end
+ params do
+ requires :pipeline_schedule_id, type: Integer, desc: 'The pipeline schedule id'
+ end
+ get ':id/pipeline_schedules/:pipeline_schedule_id' do
+ present pipeline_schedule, with: Entities::PipelineScheduleDetails
+ end
+
+ desc 'Create a new pipeline schedule' do
+ success Entities::PipelineScheduleDetails
+ end
+ params do
+ requires :description, type: String, desc: 'The description of pipeline schedule'
+ requires :ref, type: String, desc: 'The branch/tag name will be triggered', allow_blank: false
+ requires :cron, type: String, desc: 'The cron'
+ optional :cron_timezone, type: String, default: 'UTC', desc: 'The timezone'
+ optional :active, type: Boolean, default: true, desc: 'The activation of pipeline schedule'
+ end
+ post ':id/pipeline_schedules' do
+ authorize! :create_pipeline_schedule, user_project
+
+ pipeline_schedule = ::Ci::CreatePipelineScheduleService
+ .new(user_project, current_user, declared_params(include_missing: false))
+ .execute
+
+ if pipeline_schedule.persisted?
+ present pipeline_schedule, with: Entities::PipelineScheduleDetails
+ else
+ render_validation_error!(pipeline_schedule)
+ end
+ end
+
+ desc 'Edit a pipeline schedule' do
+ success Entities::PipelineScheduleDetails
+ end
+ params do
+ requires :pipeline_schedule_id, type: Integer, desc: 'The pipeline schedule id'
+ optional :description, type: String, desc: 'The description of pipeline schedule'
+ optional :ref, type: String, desc: 'The branch/tag name will be triggered'
+ optional :cron, type: String, desc: 'The cron'
+ optional :cron_timezone, type: String, desc: 'The timezone'
+ optional :active, type: Boolean, desc: 'The activation of pipeline schedule'
+ end
+ put ':id/pipeline_schedules/:pipeline_schedule_id' do
+ authorize! :update_pipeline_schedule, pipeline_schedule
+
+ if pipeline_schedule.update(declared_params(include_missing: false))
+ present pipeline_schedule, with: Entities::PipelineScheduleDetails
+ else
+ render_validation_error!(pipeline_schedule)
+ end
+ end
+
+ desc 'Take ownership of a pipeline schedule' do
+ success Entities::PipelineScheduleDetails
+ end
+ params do
+ requires :pipeline_schedule_id, type: Integer, desc: 'The pipeline schedule id'
+ end
+ post ':id/pipeline_schedules/:pipeline_schedule_id/take_ownership' do
+ authorize! :update_pipeline_schedule, pipeline_schedule
+
+ if pipeline_schedule.own!(current_user)
+ present pipeline_schedule, with: Entities::PipelineScheduleDetails
+ else
+ render_validation_error!(pipeline_schedule)
+ end
+ end
+
+ desc 'Delete a pipeline schedule' do
+ success Entities::PipelineScheduleDetails
+ end
+ params do
+ requires :pipeline_schedule_id, type: Integer, desc: 'The pipeline schedule id'
+ end
+ delete ':id/pipeline_schedules/:pipeline_schedule_id' do
+ authorize! :admin_pipeline_schedule, pipeline_schedule
+
+ destroy_conditionally!(pipeline_schedule)
+ end
+
+ desc 'Play a scheduled pipeline immediately' do
+ detail 'This feature was added in GitLab 12.8'
+ end
+ params do
+ requires :pipeline_schedule_id, type: Integer, desc: 'The pipeline schedule id'
+ end
+ post ':id/pipeline_schedules/:pipeline_schedule_id/play' do
+ authorize! :play_pipeline_schedule, pipeline_schedule
+
+ job_id = RunPipelineScheduleWorker # rubocop:disable CodeReuse/Worker
+ .perform_async(pipeline_schedule.id, current_user.id)
+
+ if job_id
+ created!
+ else
+ render_api_error!('Unable to schedule pipeline run immediately', 500)
+ end
+ end
+
+ desc 'Create a new pipeline schedule variable' do
+ success Entities::Variable
+ end
+ params do
+ requires :pipeline_schedule_id, type: Integer, desc: 'The pipeline schedule id'
+ requires :key, type: String, desc: 'The key of the variable'
+ requires :value, type: String, desc: 'The value of the variable'
+ optional :variable_type, type: String, values: ::Ci::PipelineScheduleVariable.variable_types.keys, desc: 'The type of variable, must be one of env_var or file. Defaults to env_var'
+ end
+ post ':id/pipeline_schedules/:pipeline_schedule_id/variables' do
+ authorize! :update_pipeline_schedule, pipeline_schedule
+
+ variable_params = declared_params(include_missing: false)
+ variable = pipeline_schedule.variables.create(variable_params)
+ if variable.persisted?
+ present variable, with: Entities::Variable
+ else
+ render_validation_error!(variable)
+ end
+ end
+
+ desc 'Edit a pipeline schedule variable' do
+ success Entities::Variable
+ end
+ params do
+ requires :pipeline_schedule_id, type: Integer, desc: 'The pipeline schedule id'
+ requires :key, type: String, desc: 'The key of the variable'
+ optional :value, type: String, desc: 'The value of the variable'
+ optional :variable_type, type: String, values: ::Ci::PipelineScheduleVariable.variable_types.keys, desc: 'The type of variable, must be one of env_var or file'
+ end
+ put ':id/pipeline_schedules/:pipeline_schedule_id/variables/:key' do
+ authorize! :update_pipeline_schedule, pipeline_schedule
+
+ if pipeline_schedule_variable.update(declared_params(include_missing: false))
+ present pipeline_schedule_variable, with: Entities::Variable
+ else
+ render_validation_error!(pipeline_schedule_variable)
+ end
+ end
+
+ desc 'Delete a pipeline schedule variable' do
+ success Entities::Variable
+ end
+ params do
+ requires :pipeline_schedule_id, type: Integer, desc: 'The pipeline schedule id'
+ requires :key, type: String, desc: 'The key of the variable'
+ end
+ delete ':id/pipeline_schedules/:pipeline_schedule_id/variables/:key' do
+ authorize! :admin_pipeline_schedule, pipeline_schedule
+
+ status :accepted
+ present pipeline_schedule_variable.destroy, with: Entities::Variable
+ end
+ end
+
+ helpers do
+ # rubocop: disable CodeReuse/ActiveRecord
+ def pipeline_schedule
+ @pipeline_schedule ||=
+ user_project
+ .pipeline_schedules
+ .preload(:owner, :last_pipeline)
+ .find_by(id: params.delete(:pipeline_schedule_id)).tap do |pipeline_schedule|
+ unless can?(current_user, :read_pipeline_schedule, pipeline_schedule)
+ not_found!('Pipeline Schedule')
+ end
+ end
+ end
+ # rubocop: enable CodeReuse/ActiveRecord
+
+ # rubocop: disable CodeReuse/ActiveRecord
+ def pipeline_schedule_variable
+ @pipeline_schedule_variable ||=
+ pipeline_schedule.variables.find_by(key: params[:key]).tap do |pipeline_schedule_variable|
+ unless pipeline_schedule_variable
+ not_found!('Pipeline Schedule Variable')
+ end
+ end
+ end
+ # rubocop: enable CodeReuse/ActiveRecord
+ end
+ end
+ end
+end
diff --git a/lib/api/ci/pipelines.rb b/lib/api/ci/pipelines.rb
new file mode 100644
index 00000000000..33bb8b38d92
--- /dev/null
+++ b/lib/api/ci/pipelines.rb
@@ -0,0 +1,189 @@
+# frozen_string_literal: true
+
+module API
+ module Ci
+ class Pipelines < Grape::API::Instance
+ include PaginationParams
+
+ before { authenticate_non_get! }
+
+ params do
+ requires :id, type: String, desc: 'The project ID'
+ end
+ resource :projects, requirements: ::API::API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
+ desc 'Get all Pipelines of the project' do
+ detail 'This feature was introduced in GitLab 8.11.'
+ success Entities::PipelineBasic
+ end
+ params do
+ use :pagination
+ optional :scope, type: String, values: %w[running pending finished branches tags],
+ desc: 'The scope of pipelines'
+ optional :status, type: String, values: ::Ci::HasStatus::AVAILABLE_STATUSES,
+ desc: 'The status of pipelines'
+ optional :ref, type: String, desc: 'The ref of pipelines'
+ optional :sha, type: String, desc: 'The sha of pipelines'
+ optional :yaml_errors, type: Boolean, desc: 'Returns pipelines with invalid configurations'
+ optional :name, type: String, desc: 'The name of the user who triggered pipelines'
+ optional :username, type: String, desc: 'The username of the user who triggered pipelines'
+ optional :updated_before, type: DateTime, desc: 'Return pipelines updated before the specified datetime. Format: ISO 8601 YYYY-MM-DDTHH:MM:SSZ'
+ optional :updated_after, type: DateTime, desc: 'Return pipelines updated after the specified datetime. Format: ISO 8601 YYYY-MM-DDTHH:MM:SSZ'
+ optional :order_by, type: String, values: ::Ci::PipelinesFinder::ALLOWED_INDEXED_COLUMNS, default: 'id',
+ desc: 'Order pipelines'
+ optional :sort, type: String, values: %w[asc desc], default: 'desc',
+ desc: 'Sort pipelines'
+ end
+ get ':id/pipelines' do
+ authorize! :read_pipeline, user_project
+ authorize! :read_build, user_project
+
+ pipelines = ::Ci::PipelinesFinder.new(user_project, current_user, params).execute
+ present paginate(pipelines), with: Entities::PipelineBasic
+ end
+
+ desc 'Create a new pipeline' do
+ detail 'This feature was introduced in GitLab 8.14'
+ success Entities::Pipeline
+ end
+ params do
+ requires :ref, type: String, desc: 'Reference'
+ optional :variables, Array, desc: 'Array of variables available in the pipeline'
+ end
+ post ':id/pipeline' do
+ Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-foss/issues/42124')
+
+ authorize! :create_pipeline, user_project
+
+ pipeline_params = declared_params(include_missing: false)
+ .merge(variables_attributes: params[:variables])
+ .except(:variables)
+
+ new_pipeline = ::Ci::CreatePipelineService.new(user_project,
+ current_user,
+ pipeline_params)
+ .execute(:api, ignore_skip_ci: true, save_on_errors: false)
+
+ if new_pipeline.persisted?
+ present new_pipeline, with: Entities::Pipeline
+ else
+ render_validation_error!(new_pipeline)
+ end
+ end
+
+ desc 'Gets a the latest pipeline for the project branch' do
+ detail 'This feature was introduced in GitLab 12.3'
+ success Entities::Pipeline
+ end
+ params do
+ optional :ref, type: String, desc: 'branch ref of pipeline'
+ end
+ get ':id/pipelines/latest' do
+ authorize! :read_pipeline, latest_pipeline
+
+ present latest_pipeline, with: Entities::Pipeline
+ end
+
+ desc 'Gets a specific pipeline for the project' do
+ detail 'This feature was introduced in GitLab 8.11'
+ success Entities::Pipeline
+ end
+ params do
+ requires :pipeline_id, type: Integer, desc: 'The pipeline ID'
+ end
+ get ':id/pipelines/:pipeline_id' do
+ authorize! :read_pipeline, pipeline
+
+ present pipeline, with: Entities::Pipeline
+ end
+
+ desc 'Gets the variables for a given pipeline' do
+ detail 'This feature was introduced in GitLab 11.11'
+ success Entities::Variable
+ end
+ params do
+ requires :pipeline_id, type: Integer, desc: 'The pipeline ID'
+ end
+ get ':id/pipelines/:pipeline_id/variables' do
+ authorize! :read_pipeline_variable, pipeline
+
+ present pipeline.variables, with: Entities::Variable
+ end
+
+ desc 'Gets the test report for a given pipeline' do
+ detail 'This feature was introduced in GitLab 13.0. Disabled by default behind feature flag `junit_pipeline_view`'
+ success TestReportEntity
+ end
+ params do
+ requires :pipeline_id, type: Integer, desc: 'The pipeline ID'
+ end
+ get ':id/pipelines/:pipeline_id/test_report' do
+ not_found! unless Feature.enabled?(:junit_pipeline_view, user_project)
+
+ authorize! :read_build, pipeline
+
+ present pipeline.test_reports, with: TestReportEntity, details: true
+ end
+
+ desc 'Deletes a pipeline' do
+ detail 'This feature was introduced in GitLab 11.6'
+ http_codes [[204, 'Pipeline was deleted'], [403, 'Forbidden']]
+ end
+ params do
+ requires :pipeline_id, type: Integer, desc: 'The pipeline ID'
+ end
+ delete ':id/pipelines/:pipeline_id' do
+ authorize! :destroy_pipeline, pipeline
+
+ destroy_conditionally!(pipeline) do
+ ::Ci::DestroyPipelineService.new(user_project, current_user).execute(pipeline)
+ end
+ end
+
+ desc 'Retry builds in the pipeline' do
+ detail 'This feature was introduced in GitLab 8.11.'
+ success Entities::Pipeline
+ end
+ params do
+ requires :pipeline_id, type: Integer, desc: 'The pipeline ID'
+ end
+ post ':id/pipelines/:pipeline_id/retry' do
+ authorize! :update_pipeline, pipeline
+
+ pipeline.retry_failed(current_user)
+
+ present pipeline, with: Entities::Pipeline
+ end
+
+ desc 'Cancel all builds in the pipeline' do
+ detail 'This feature was introduced in GitLab 8.11.'
+ success Entities::Pipeline
+ end
+ params do
+ requires :pipeline_id, type: Integer, desc: 'The pipeline ID'
+ end
+ post ':id/pipelines/:pipeline_id/cancel' do
+ authorize! :update_pipeline, pipeline
+
+ pipeline.cancel_running
+
+ status 200
+ present pipeline.reset, with: Entities::Pipeline
+ end
+ end
+
+ helpers do
+ def pipeline
+ strong_memoize(:pipeline) do
+ user_project.ci_pipelines.find(params[:pipeline_id])
+ end
+ end
+
+ def latest_pipeline
+ strong_memoize(:latest_pipeline) do
+ user_project.latest_pipeline_for_ref(params[:ref])
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/api/ci/runner.rb b/lib/api/ci/runner.rb
new file mode 100644
index 00000000000..31be1bb7e3e
--- /dev/null
+++ b/lib/api/ci/runner.rb
@@ -0,0 +1,318 @@
+# frozen_string_literal: true
+
+module API
+ module Ci
+ class Runner < Grape::API::Instance
+ helpers ::API::Helpers::Runner
+
+ resource :runners do
+ desc 'Registers a new Runner' do
+ success Entities::RunnerRegistrationDetails
+ http_codes [[201, 'Runner was created'], [403, 'Forbidden']]
+ end
+ params do
+ requires :token, type: String, desc: 'Registration token'
+ optional :description, type: String, desc: %q(Runner's description)
+ optional :info, type: Hash, desc: %q(Runner's metadata)
+ optional :active, type: Boolean, desc: 'Should Runner be active'
+ optional :locked, type: Boolean, desc: 'Should Runner be locked for current project'
+ optional :access_level, type: String, values: ::Ci::Runner.access_levels.keys,
+ desc: 'The access_level of the runner'
+ optional :run_untagged, type: Boolean, desc: 'Should Runner handle untagged jobs'
+ optional :tag_list, type: Array[String], coerce_with: ::API::Validations::Types::CommaSeparatedToArray.coerce, desc: %q(List of Runner's tags)
+ optional :maximum_timeout, type: Integer, desc: 'Maximum timeout set when this Runner will handle the job'
+ end
+ post '/' do
+ attributes = attributes_for_keys([:description, :active, :locked, :run_untagged, :tag_list, :access_level, :maximum_timeout])
+ .merge(get_runner_details_from_request)
+
+ attributes =
+ if runner_registration_token_valid?
+ # Create shared runner. Requires admin access
+ attributes.merge(runner_type: :instance_type)
+ elsif project = Project.find_by_runners_token(params[:token])
+ # Create a specific runner for the project
+ attributes.merge(runner_type: :project_type, projects: [project])
+ elsif group = Group.find_by_runners_token(params[:token])
+ # Create a specific runner for the group
+ attributes.merge(runner_type: :group_type, groups: [group])
+ else
+ forbidden!
+ end
+
+ runner = ::Ci::Runner.create(attributes)
+
+ if runner.persisted?
+ present runner, with: Entities::RunnerRegistrationDetails
+ else
+ render_validation_error!(runner)
+ end
+ end
+
+ desc 'Deletes a registered Runner' do
+ http_codes [[204, 'Runner was deleted'], [403, 'Forbidden']]
+ end
+ params do
+ requires :token, type: String, desc: %q(Runner's authentication token)
+ end
+ delete '/' do
+ authenticate_runner!
+
+ runner = ::Ci::Runner.find_by_token(params[:token])
+
+ destroy_conditionally!(runner)
+ end
+
+ desc 'Validates authentication credentials' do
+ http_codes [[200, 'Credentials are valid'], [403, 'Forbidden']]
+ end
+ params do
+ requires :token, type: String, desc: %q(Runner's authentication token)
+ end
+ post '/verify' do
+ authenticate_runner!
+ status 200
+ end
+ end
+
+ resource :jobs do
+ before do
+ Gitlab::ApplicationContext.push(
+ user: -> { current_job&.user },
+ project: -> { current_job&.project }
+ )
+ end
+
+ desc 'Request a job' do
+ success Entities::JobRequest::Response
+ http_codes [[201, 'Job was scheduled'],
+ [204, 'No job for Runner'],
+ [403, 'Forbidden']]
+ end
+ params do
+ requires :token, type: String, desc: %q(Runner's authentication token)
+ optional :last_update, type: String, desc: %q(Runner's queue last_update token)
+ optional :info, type: Hash, desc: %q(Runner's metadata) do
+ optional :name, type: String, desc: %q(Runner's name)
+ optional :version, type: String, desc: %q(Runner's version)
+ optional :revision, type: String, desc: %q(Runner's revision)
+ optional :platform, type: String, desc: %q(Runner's platform)
+ optional :architecture, type: String, desc: %q(Runner's architecture)
+ optional :executor, type: String, desc: %q(Runner's executor)
+ optional :features, type: Hash, desc: %q(Runner's features)
+ end
+ optional :session, type: Hash, desc: %q(Runner's session data) do
+ optional :url, type: String, desc: %q(Session's url)
+ optional :certificate, type: String, desc: %q(Session's certificate)
+ optional :authorization, type: String, desc: %q(Session's authorization)
+ end
+ optional :job_age, type: Integer, desc: %q(Job should be older than passed age in seconds to be ran on runner)
+ end
+
+ # Since we serialize the build output ourselves to ensure Gitaly
+ # gRPC calls succeed, we need a custom Grape format to handle
+ # this:
+ # 1. Grape will ordinarily call `JSON.dump` when Content-Type is set
+ # to application/json. To avoid this, we need to define a custom type in
+ # `content_type` and a custom formatter to go with it.
+ # 2. Grape will parse the request input with the parser defined for
+ # `content_type`. If no such parser exists, it will be treated as text. We
+ # reuse the existing JSON parser to preserve the previous behavior.
+ content_type :build_json, 'application/json'
+ formatter :build_json, ->(object, _) { object }
+ parser :build_json, ::Grape::Parser::Json
+
+ post '/request' do
+ authenticate_runner!
+
+ unless current_runner.active?
+ header 'X-GitLab-Last-Update', current_runner.ensure_runner_queue_value
+ break no_content!
+ end
+
+ runner_params = declared_params(include_missing: false)
+
+ if current_runner.runner_queue_value_latest?(runner_params[:last_update])
+ header 'X-GitLab-Last-Update', runner_params[:last_update]
+ Gitlab::Metrics.add_event(:build_not_found_cached)
+ break no_content!
+ end
+
+ new_update = current_runner.ensure_runner_queue_value
+ result = ::Ci::RegisterJobService.new(current_runner).execute(runner_params)
+
+ if result.valid?
+ if result.build_json
+ Gitlab::Metrics.add_event(:build_found)
+ env['api.format'] = :build_json
+ body result.build_json
+ else
+ Gitlab::Metrics.add_event(:build_not_found)
+ header 'X-GitLab-Last-Update', new_update
+ no_content!
+ end
+ else
+ # We received build that is invalid due to concurrency conflict
+ Gitlab::Metrics.add_event(:build_invalid)
+ conflict!
+ end
+ end
+
+ desc 'Updates a job' do
+ http_codes [[200, 'Job was updated'], [403, 'Forbidden']]
+ end
+ params do
+ requires :token, type: String, desc: %q(Runners's authentication token)
+ requires :id, type: Integer, desc: %q(Job's ID)
+ optional :trace, type: String, desc: %q(Job's full trace)
+ optional :state, type: String, desc: %q(Job's status: success, failed)
+ optional :failure_reason, type: String, desc: %q(Job's failure_reason)
+ end
+ put '/:id' do
+ job = authenticate_job!
+
+ job.trace.set(params[:trace]) if params[:trace]
+
+ Gitlab::Metrics.add_event(:update_build)
+
+ case params[:state].to_s
+ when 'running'
+ job.touch if job.needs_touch?
+ when 'success'
+ job.success!
+ when 'failed'
+ job.drop!(params[:failure_reason] || :unknown_failure)
+ end
+ end
+
+ desc 'Appends a patch to the job trace' do
+ http_codes [[202, 'Trace was patched'],
+ [400, 'Missing Content-Range header'],
+ [403, 'Forbidden'],
+ [416, 'Range not satisfiable']]
+ end
+ params do
+ requires :id, type: Integer, desc: %q(Job's ID)
+ optional :token, type: String, desc: %q(Job's authentication token)
+ end
+ patch '/:id/trace' do
+ job = authenticate_job!
+
+ error!('400 Missing header Content-Range', 400) unless request.headers.key?('Content-Range')
+ content_range = request.headers['Content-Range']
+ content_range = content_range.split('-')
+
+ # TODO:
+ # it seems that `Content-Range` as formatted by runner is wrong,
+ # the `byte_end` should point to final byte, but it points byte+1
+ # that means that we have to calculate end of body,
+ # as we cannot use `content_length[1]`
+ # Issue: https://gitlab.com/gitlab-org/gitlab-runner/issues/3275
+
+ body_data = request.body.read
+ body_start = content_range[0].to_i
+ body_end = body_start + body_data.bytesize
+
+ stream_size = job.trace.append(body_data, body_start)
+ unless stream_size == body_end
+ break error!('416 Range Not Satisfiable', 416, { 'Range' => "0-#{stream_size}" })
+ end
+
+ status 202
+ header 'Job-Status', job.status
+ header 'Range', "0-#{stream_size}"
+ header 'X-GitLab-Trace-Update-Interval', job.trace.update_interval.to_s
+ end
+
+ desc 'Authorize artifacts uploading for job' do
+ http_codes [[200, 'Upload allowed'],
+ [403, 'Forbidden'],
+ [405, 'Artifacts support not enabled'],
+ [413, 'File too large']]
+ end
+ params do
+ requires :id, type: Integer, desc: %q(Job's ID)
+ optional :token, type: String, desc: %q(Job's authentication token)
+
+ # NOTE:
+ # In current runner, filesize parameter would be empty here. This is because archive is streamed by runner,
+ # so the archive size is not known ahead of time. Streaming is done to not use additional I/O on
+ # Runner to first save, and then send via Network.
+ optional :filesize, type: Integer, desc: %q(Artifacts filesize)
+
+ optional :artifact_type, type: String, desc: %q(The type of artifact),
+ default: 'archive', values: ::Ci::JobArtifact.file_types.keys
+ end
+ post '/:id/artifacts/authorize' do
+ not_allowed! unless Gitlab.config.artifacts.enabled
+ require_gitlab_workhorse!
+
+ job = authenticate_job!
+
+ result = ::Ci::CreateJobArtifactsService.new(job).authorize(artifact_type: params[:artifact_type], filesize: params[:filesize])
+
+ if result[:status] == :success
+ content_type Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE
+ status :ok
+ result[:headers]
+ else
+ render_api_error!(result[:message], result[:http_status])
+ end
+ end
+
+ desc 'Upload artifacts for job' do
+ success Entities::JobRequest::Response
+ http_codes [[201, 'Artifact uploaded'],
+ [400, 'Bad request'],
+ [403, 'Forbidden'],
+ [405, 'Artifacts support not enabled'],
+ [413, 'File too large']]
+ end
+ params do
+ requires :id, type: Integer, desc: %q(Job's ID)
+ requires :file, type: ::API::Validations::Types::WorkhorseFile, desc: %(The artifact file to store (generated by Multipart middleware))
+ optional :token, type: String, desc: %q(Job's authentication token)
+ optional :expire_in, type: String, desc: %q(Specify when artifacts should expire)
+ optional :artifact_type, type: String, desc: %q(The type of artifact),
+ default: 'archive', values: ::Ci::JobArtifact.file_types.keys
+ optional :artifact_format, type: String, desc: %q(The format of artifact),
+ default: 'zip', values: ::Ci::JobArtifact.file_formats.keys
+ optional :metadata, type: ::API::Validations::Types::WorkhorseFile, desc: %(The artifact metadata to store (generated by Multipart middleware))
+ end
+ post '/:id/artifacts' do
+ not_allowed! unless Gitlab.config.artifacts.enabled
+ require_gitlab_workhorse!
+
+ job = authenticate_job!
+
+ artifacts = params[:file]
+ metadata = params[:metadata]
+
+ result = ::Ci::CreateJobArtifactsService.new(job).execute(artifacts, params, metadata_file: metadata)
+
+ if result[:status] == :success
+ status :created
+ else
+ render_api_error!(result[:message], result[:http_status])
+ end
+ end
+
+ desc 'Download the artifacts file for job' do
+ http_codes [[200, 'Upload allowed'],
+ [403, 'Forbidden'],
+ [404, 'Artifact not found']]
+ end
+ params do
+ requires :id, type: Integer, desc: %q(Job's ID)
+ optional :token, type: String, desc: %q(Job's authentication token)
+ optional :direct_download, default: false, type: Boolean, desc: %q(Perform direct download from remote storage instead of proxying artifacts)
+ end
+ get '/:id/artifacts' do
+ job = authenticate_job!(require_running: false)
+
+ present_carrierwave_file!(job.artifacts_file, supports_direct_download: params[:direct_download])
+ end
+ end
+ end
+ end
+end
diff --git a/lib/api/ci/runners.rb b/lib/api/ci/runners.rb
new file mode 100644
index 00000000000..2c156a71160
--- /dev/null
+++ b/lib/api/ci/runners.rb
@@ -0,0 +1,289 @@
+# frozen_string_literal: true
+
+module API
+ module Ci
+ class Runners < Grape::API::Instance
+ include PaginationParams
+
+ before { authenticate! }
+
+ resource :runners do
+ desc 'Get runners available for user' do
+ success Entities::Runner
+ end
+ params do
+ optional :scope, type: String, values: ::Ci::Runner::AVAILABLE_STATUSES,
+ desc: 'The scope of specific runners to show'
+ optional :type, type: String, values: ::Ci::Runner::AVAILABLE_TYPES,
+ desc: 'The type of the runners to show'
+ optional :status, type: String, values: ::Ci::Runner::AVAILABLE_STATUSES,
+ desc: 'The status of the runners to show'
+ optional :tag_list, type: Array[String], coerce_with: ::API::Validations::Types::CommaSeparatedToArray.coerce, desc: 'The tags of the runners to show'
+ use :pagination
+ end
+ get do
+ runners = current_user.ci_owned_runners
+ runners = filter_runners(runners, params[:scope], allowed_scopes: ::Ci::Runner::AVAILABLE_STATUSES)
+ runners = filter_runners(runners, params[:type], allowed_scopes: ::Ci::Runner::AVAILABLE_TYPES)
+ runners = filter_runners(runners, params[:status], allowed_scopes: ::Ci::Runner::AVAILABLE_STATUSES)
+ runners = runners.tagged_with(params[:tag_list]) if params[:tag_list]
+
+ present paginate(runners), with: Entities::Runner
+ end
+
+ desc 'Get all runners - shared and specific' do
+ success Entities::Runner
+ end
+ params do
+ optional :scope, type: String, values: ::Ci::Runner::AVAILABLE_SCOPES,
+ desc: 'The scope of specific runners to show'
+ optional :type, type: String, values: ::Ci::Runner::AVAILABLE_TYPES,
+ desc: 'The type of the runners to show'
+ optional :status, type: String, values: ::Ci::Runner::AVAILABLE_STATUSES,
+ desc: 'The status of the runners to show'
+ optional :tag_list, type: Array[String], coerce_with: ::API::Validations::Types::CommaSeparatedToArray.coerce, desc: 'The tags of the runners to show'
+ use :pagination
+ end
+ get 'all' do
+ authenticated_as_admin!
+
+ runners = ::Ci::Runner.all
+ runners = filter_runners(runners, params[:scope])
+ runners = filter_runners(runners, params[:type], allowed_scopes: ::Ci::Runner::AVAILABLE_TYPES)
+ runners = filter_runners(runners, params[:status], allowed_scopes: ::Ci::Runner::AVAILABLE_STATUSES)
+ runners = runners.tagged_with(params[:tag_list]) if params[:tag_list]
+
+ present paginate(runners), with: Entities::Runner
+ end
+
+ desc "Get runner's details" do
+ success Entities::RunnerDetails
+ end
+ params do
+ requires :id, type: Integer, desc: 'The ID of the runner'
+ end
+ get ':id' do
+ runner = get_runner(params[:id])
+ authenticate_show_runner!(runner)
+
+ present runner, with: Entities::RunnerDetails, current_user: current_user
+ end
+
+ desc "Update runner's details" do
+ success Entities::RunnerDetails
+ end
+ params do
+ requires :id, type: Integer, desc: 'The ID of the runner'
+ optional :description, type: String, desc: 'The description of the runner'
+ optional :active, type: Boolean, desc: 'The state of a runner'
+ optional :tag_list, type: Array[String], coerce_with: ::API::Validations::Types::CommaSeparatedToArray.coerce, desc: 'The list of tags for a runner'
+ optional :run_untagged, type: Boolean, desc: 'Flag indicating the runner can execute untagged jobs'
+ optional :locked, type: Boolean, desc: 'Flag indicating the runner is locked'
+ optional :access_level, type: String, values: ::Ci::Runner.access_levels.keys,
+ desc: 'The access_level of the runner'
+ optional :maximum_timeout, type: Integer, desc: 'Maximum timeout set when this Runner will handle the job'
+ at_least_one_of :description, :active, :tag_list, :run_untagged, :locked, :access_level, :maximum_timeout
+ end
+ put ':id' do
+ runner = get_runner(params.delete(:id))
+ authenticate_update_runner!(runner)
+ update_service = ::Ci::UpdateRunnerService.new(runner)
+
+ if update_service.update(declared_params(include_missing: false))
+ present runner, with: Entities::RunnerDetails, current_user: current_user
+ else
+ render_validation_error!(runner)
+ end
+ end
+
+ desc 'Remove a runner' do
+ success Entities::Runner
+ end
+ params do
+ requires :id, type: Integer, desc: 'The ID of the runner'
+ end
+ delete ':id' do
+ runner = get_runner(params[:id])
+
+ authenticate_delete_runner!(runner)
+
+ destroy_conditionally!(runner)
+ end
+
+ desc 'List jobs running on a runner' do
+ success Entities::JobBasicWithProject
+ end
+ params do
+ requires :id, type: Integer, desc: 'The ID of the runner'
+ optional :status, type: String, desc: 'Status of the job', values: ::Ci::Build::AVAILABLE_STATUSES
+ optional :order_by, type: String, desc: 'Order by `id` or not', values: ::Ci::RunnerJobsFinder::ALLOWED_INDEXED_COLUMNS
+ optional :sort, type: String, values: %w[asc desc], default: 'desc', desc: 'Sort by asc (ascending) or desc (descending)'
+ use :pagination
+ end
+ get ':id/jobs' do
+ runner = get_runner(params[:id])
+ authenticate_list_runners_jobs!(runner)
+
+ jobs = ::Ci::RunnerJobsFinder.new(runner, params).execute
+
+ present paginate(jobs), with: Entities::JobBasicWithProject
+ end
+ end
+
+ params do
+ requires :id, type: String, desc: 'The ID of a project'
+ end
+ resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
+ before { authorize_admin_project }
+
+ desc 'Get runners available for project' do
+ success Entities::Runner
+ end
+ params do
+ optional :scope, type: String, values: ::Ci::Runner::AVAILABLE_SCOPES,
+ desc: 'The scope of specific runners to show'
+ optional :type, type: String, values: ::Ci::Runner::AVAILABLE_TYPES,
+ desc: 'The type of the runners to show'
+ optional :status, type: String, values: ::Ci::Runner::AVAILABLE_STATUSES,
+ desc: 'The status of the runners to show'
+ optional :tag_list, type: Array[String], coerce_with: ::API::Validations::Types::CommaSeparatedToArray.coerce, desc: 'The tags of the runners to show'
+ use :pagination
+ end
+ get ':id/runners' do
+ runners = ::Ci::Runner.owned_or_instance_wide(user_project.id)
+ # scope is deprecated (for project runners), however api documentation still supports it.
+ # Not including them in `apply_filter` method as it's not supported for group runners
+ runners = filter_runners(runners, params[:scope])
+ runners = apply_filter(runners, params)
+
+ present paginate(runners), with: Entities::Runner
+ end
+
+ desc 'Enable a runner for a project' do
+ success Entities::Runner
+ end
+ params do
+ requires :runner_id, type: Integer, desc: 'The ID of the runner'
+ end
+ post ':id/runners' do
+ runner = get_runner(params[:runner_id])
+ authenticate_enable_runner!(runner)
+
+ if runner.assign_to(user_project)
+ present runner, with: Entities::Runner
+ else
+ render_validation_error!(runner)
+ end
+ end
+
+ desc "Disable project's runner" do
+ success Entities::Runner
+ end
+ params do
+ requires :runner_id, type: Integer, desc: 'The ID of the runner'
+ end
+ # rubocop: disable CodeReuse/ActiveRecord
+ delete ':id/runners/:runner_id' do
+ runner_project = user_project.runner_projects.find_by(runner_id: params[:runner_id])
+ not_found!('Runner') unless runner_project
+
+ runner = runner_project.runner
+ forbidden!("Only one project associated with the runner. Please remove the runner instead") if runner.projects.count == 1
+
+ destroy_conditionally!(runner_project)
+ end
+ # rubocop: enable CodeReuse/ActiveRecord
+ end
+
+ params do
+ requires :id, type: String, desc: 'The ID of a group'
+ end
+ resource :groups, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
+ before { authorize_admin_group }
+
+ desc 'Get runners available for group' do
+ success Entities::Runner
+ end
+ params do
+ optional :type, type: String, values: ::Ci::Runner::AVAILABLE_TYPES,
+ desc: 'The type of the runners to show'
+ optional :status, type: String, values: ::Ci::Runner::AVAILABLE_STATUSES,
+ desc: 'The status of the runners to show'
+ optional :tag_list, type: Array[String], coerce_with: ::API::Validations::Types::CommaSeparatedToArray.coerce, desc: 'The tags of the runners to show'
+ use :pagination
+ end
+ get ':id/runners' do
+ runners = ::Ci::Runner.belonging_to_group(user_group.id, include_ancestors: true)
+ runners = apply_filter(runners, params)
+
+ present paginate(runners), with: Entities::Runner
+ end
+ end
+
+ helpers do
+ def filter_runners(runners, scope, allowed_scopes: ::Ci::Runner::AVAILABLE_SCOPES)
+ return runners unless scope.present?
+
+ unless allowed_scopes.include?(scope)
+ render_api_error!('Scope contains invalid value', 400)
+ end
+
+ # Support deprecated scopes
+ if runners.respond_to?("deprecated_#{scope}")
+ scope = "deprecated_#{scope}"
+ end
+
+ runners.public_send(scope) # rubocop:disable GitlabSecurity/PublicSend
+ end
+
+ def apply_filter(runners, params)
+ runners = filter_runners(runners, params[:type], allowed_scopes: ::Ci::Runner::AVAILABLE_TYPES)
+ runners = filter_runners(runners, params[:status], allowed_scopes: ::Ci::Runner::AVAILABLE_STATUSES)
+ runners = runners.tagged_with(params[:tag_list]) if params[:tag_list]
+
+ runners
+ end
+
+ def get_runner(id)
+ runner = ::Ci::Runner.find(id)
+ not_found!('Runner') unless runner
+ runner
+ end
+
+ def authenticate_show_runner!(runner)
+ return if runner.instance_type? || current_user.admin?
+
+ forbidden!("No access granted") unless can?(current_user, :read_runner, runner)
+ end
+
+ def authenticate_update_runner!(runner)
+ return if current_user.admin?
+
+ forbidden!("No access granted") unless can?(current_user, :update_runner, runner)
+ end
+
+ def authenticate_delete_runner!(runner)
+ return if current_user.admin?
+
+ forbidden!("Runner associated with more than one project") if runner.projects.count > 1
+ forbidden!("No access granted") unless can?(current_user, :delete_runner, runner)
+ end
+
+ def authenticate_enable_runner!(runner)
+ forbidden!("Runner is a group runner") if runner.group_type?
+
+ return if current_user.admin?
+
+ forbidden!("Runner is locked") if runner.locked?
+ forbidden!("No access granted") unless can?(current_user, :assign_runner, runner)
+ end
+
+ def authenticate_list_runners_jobs!(runner)
+ return if current_user.admin?
+
+ forbidden!("No access granted") unless can?(current_user, :read_runner, runner)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/api/commit_statuses.rb b/lib/api/commit_statuses.rb
index b4c5d7869a2..140351c9e5c 100644
--- a/lib/api/commit_statuses.rb
+++ b/lib/api/commit_statuses.rb
@@ -3,7 +3,7 @@
require 'mime/types'
module API
- class CommitStatuses < Grape::API
+ class CommitStatuses < Grape::API::Instance
params do
requires :id, type: String, desc: 'The ID of a project'
end
@@ -60,7 +60,7 @@ module API
not_found! 'Commit' unless commit
- # Since the CommitStatus is attached to Ci::Pipeline (in the future Pipeline)
+ # Since the CommitStatus is attached to ::Ci::Pipeline (in the future Pipeline)
# We need to always have the pipeline object
# To have a valid pipeline object that can be attached to specific MR
# Other CI service needs to send `ref`
diff --git a/lib/api/commits.rb b/lib/api/commits.rb
index 086a1b7c402..1a0fe393753 100644
--- a/lib/api/commits.rb
+++ b/lib/api/commits.rb
@@ -3,7 +3,7 @@
require 'mime/types'
module API
- class Commits < Grape::API
+ class Commits < Grape::API::Instance
include PaginationParams
before do
diff --git a/lib/api/composer_packages.rb b/lib/api/composer_packages.rb
new file mode 100644
index 00000000000..726dc89271a
--- /dev/null
+++ b/lib/api/composer_packages.rb
@@ -0,0 +1,156 @@
+# frozen_string_literal: true
+
+# PHP composer support (https://getcomposer.org/)
+module API
+ class ComposerPackages < Grape::API::Instance
+ helpers ::API::Helpers::PackagesManagerClientsHelpers
+ helpers ::API::Helpers::RelatedResourcesHelpers
+ helpers ::API::Helpers::Packages::BasicAuthHelpers
+ include ::API::Helpers::Packages::BasicAuthHelpers::Constants
+ include ::Gitlab::Utils::StrongMemoize
+
+ content_type :json, 'application/json'
+ default_format :json
+
+ COMPOSER_ENDPOINT_REQUIREMENTS = {
+ package_name: API::NO_SLASH_URL_PART_REGEX
+ }.freeze
+
+ default_format :json
+
+ rescue_from ArgumentError do |e|
+ render_api_error!(e.message, 400)
+ end
+
+ rescue_from ActiveRecord::RecordInvalid do |e|
+ render_api_error!(e.message, 400)
+ end
+
+ helpers do
+ def packages
+ strong_memoize(:packages) do
+ packages = ::Packages::Composer::PackagesFinder.new(current_user, user_group).execute
+
+ if params[:package_name].present?
+ packages = packages.with_name(params[:package_name])
+ end
+
+ packages
+ end
+ end
+
+ def presenter
+ @presenter ||= ::Packages::Composer::PackagesPresenter.new(user_group, packages)
+ end
+ end
+
+ before do
+ require_packages_enabled!
+ end
+
+ params do
+ requires :id, type: String, desc: 'The ID of a group'
+ end
+
+ resource :group, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
+ before do
+ user_group
+ end
+
+ desc 'Composer packages endpoint at group level'
+
+ route_setting :authentication, job_token_allowed: true
+
+ get ':id/-/packages/composer/packages' do
+ presenter.root
+ end
+
+ desc 'Composer packages endpoint at group level for packages list'
+
+ params do
+ requires :sha, type: String, desc: 'Shasum of current json'
+ end
+
+ route_setting :authentication, job_token_allowed: true
+
+ get ':id/-/packages/composer/p/:sha' do
+ presenter.provider
+ end
+
+ desc 'Composer packages endpoint at group level for package versions metadata'
+
+ params do
+ requires :package_name, type: String, file_path: true, desc: 'The Composer package name'
+ end
+
+ route_setting :authentication, job_token_allowed: true
+
+ get ':id/-/packages/composer/*package_name', requirements: COMPOSER_ENDPOINT_REQUIREMENTS, file_path: true do
+ not_found! if packages.empty?
+
+ presenter.package_versions
+ end
+ end
+
+ params do
+ requires :id, type: Integer, desc: 'The ID of a project'
+ end
+
+ resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
+ before do
+ unauthorized_user_project!
+ end
+
+ desc 'Composer packages endpoint for registering packages'
+
+ namespace ':id/packages/composer' do
+ route_setting :authentication, job_token_allowed: true
+
+ params do
+ optional :branch, type: String, desc: 'The name of the branch'
+ optional :tag, type: String, desc: 'The name of the tag'
+ exactly_one_of :tag, :branch
+ end
+
+ post do
+ authorize_create_package!(authorized_user_project)
+
+ if params[:branch].present?
+ params[:branch] = find_branch!(params[:branch])
+ elsif params[:tag].present?
+ params[:tag] = find_tag!(params[:tag])
+ else
+ bad_request!
+ end
+
+ track_event('register_package')
+
+ ::Packages::Composer::CreatePackageService
+ .new(authorized_user_project, current_user, declared_params)
+ .execute
+
+ created!
+ end
+
+ params do
+ requires :sha, type: String, desc: 'Shasum of current json'
+ requires :package_name, type: String, file_path: true, desc: 'The Composer package name'
+ end
+
+ get 'archives/*package_name' do
+ metadata = unauthorized_user_project
+ .packages
+ .composer
+ .with_name(params[:package_name])
+ .with_composer_target(params[:sha])
+ .first
+ &.composer_metadatum
+
+ not_found! unless metadata
+
+ send_git_archive unauthorized_user_project.repository, ref: metadata.target_sha, format: 'zip', append_sha: true
+ end
+ end
+ end
+ end
+end
diff --git a/lib/api/conan_packages.rb b/lib/api/conan_packages.rb
new file mode 100644
index 00000000000..1d941e422a7
--- /dev/null
+++ b/lib/api/conan_packages.rb
@@ -0,0 +1,309 @@
+# frozen_string_literal: true
+
+# Conan Package Manager Client API
+#
+# These API endpoints are not consumed directly by users, so there is no documentation for the
+# individual endpoints. They are called by the Conan package manager client when users run commands
+# like `conan install` or `conan upload`. The usage of the GitLab Conan repository is documented here:
+# https://docs.gitlab.com/ee/user/packages/conan_repository/#installing-a-package
+#
+# Technical debt: https://gitlab.com/gitlab-org/gitlab/issues/35798
+module API
+ class ConanPackages < Grape::API::Instance
+ helpers ::API::Helpers::PackagesManagerClientsHelpers
+
+ PACKAGE_REQUIREMENTS = {
+ package_name: API::NO_SLASH_URL_PART_REGEX,
+ package_version: API::NO_SLASH_URL_PART_REGEX,
+ package_username: API::NO_SLASH_URL_PART_REGEX,
+ package_channel: API::NO_SLASH_URL_PART_REGEX
+ }.freeze
+
+ FILE_NAME_REQUIREMENTS = {
+ file_name: API::NO_SLASH_URL_PART_REGEX
+ }.freeze
+
+ PACKAGE_COMPONENT_REGEX = Gitlab::Regex.conan_recipe_component_regex
+ CONAN_REVISION_REGEX = Gitlab::Regex.conan_revision_regex
+
+ before do
+ require_packages_enabled!
+
+ # Personal access token will be extracted from Bearer or Basic authorization
+ # in the overridden find_personal_access_token or find_user_from_job_token helpers
+ authenticate!
+ end
+
+ namespace 'packages/conan/v1' do
+ desc 'Ping the Conan API' do
+ detail 'This feature was introduced in GitLab 12.2'
+ end
+ route_setting :authentication, job_token_allowed: true
+ get 'ping' do
+ header 'X-Conan-Server-Capabilities', [].join(',')
+ end
+
+ desc 'Search for packages' do
+ detail 'This feature was introduced in GitLab 12.4'
+ end
+ params do
+ requires :q, type: String, desc: 'Search query'
+ end
+ route_setting :authentication, job_token_allowed: true
+ get 'conans/search' do
+ service = ::Packages::Conan::SearchService.new(current_user, query: params[:q]).execute
+ service.payload
+ end
+
+ namespace 'users' do
+ format :txt
+
+ desc 'Authenticate user against conan CLI' do
+ detail 'This feature was introduced in GitLab 12.2'
+ end
+ route_setting :authentication, job_token_allowed: true
+ get 'authenticate' do
+ unauthorized! unless token
+
+ token.to_jwt
+ end
+
+ desc 'Check for valid user credentials per conan CLI' do
+ detail 'This feature was introduced in GitLab 12.4'
+ end
+ route_setting :authentication, job_token_allowed: true
+ get 'check_credentials' do
+ authenticate!
+ :ok
+ end
+ end
+
+ params do
+ requires :package_name, type: String, regexp: PACKAGE_COMPONENT_REGEX, desc: 'Package name'
+ requires :package_version, type: String, regexp: PACKAGE_COMPONENT_REGEX, desc: 'Package version'
+ requires :package_username, type: String, regexp: PACKAGE_COMPONENT_REGEX, desc: 'Package username'
+ requires :package_channel, type: String, regexp: PACKAGE_COMPONENT_REGEX, desc: 'Package channel'
+ end
+ namespace 'conans/:package_name/:package_version/:package_username/:package_channel', requirements: PACKAGE_REQUIREMENTS do
+ # Get the snapshot
+ #
+ # the snapshot is a hash of { filename: md5 hash }
+ # md5 hash is the has of that file. This hash is used to diff the files existing on the client
+ # to determine which client files need to be uploaded if no recipe exists the snapshot is empty
+ desc 'Package Snapshot' do
+ detail 'This feature was introduced in GitLab 12.5'
+ end
+ params do
+ requires :conan_package_reference, type: String, desc: 'Conan package ID'
+ end
+ route_setting :authentication, job_token_allowed: true
+ get 'packages/:conan_package_reference' do
+ authorize!(:read_package, project)
+
+ presenter = ::Packages::Conan::PackagePresenter.new(
+ recipe,
+ current_user,
+ project,
+ conan_package_reference: params[:conan_package_reference]
+ )
+
+ present presenter, with: ::API::Entities::ConanPackage::ConanPackageSnapshot
+ end
+
+ desc 'Recipe Snapshot' do
+ detail 'This feature was introduced in GitLab 12.5'
+ end
+ route_setting :authentication, job_token_allowed: true
+ get do
+ authorize!(:read_package, project)
+
+ presenter = ::Packages::Conan::PackagePresenter.new(recipe, current_user, project)
+
+ present presenter, with: ::API::Entities::ConanPackage::ConanRecipeSnapshot
+ end
+
+ # Get the manifest
+ # returns the download urls for the existing recipe in the registry
+ #
+ # the manifest is a hash of { filename: url }
+ # where the url is the download url for the file
+ desc 'Package Digest' do
+ detail 'This feature was introduced in GitLab 12.5'
+ end
+ params do
+ requires :conan_package_reference, type: String, desc: 'Conan package ID'
+ end
+ route_setting :authentication, job_token_allowed: true
+ get 'packages/:conan_package_reference/digest' do
+ present_package_download_urls
+ end
+
+ desc 'Recipe Digest' do
+ detail 'This feature was introduced in GitLab 12.5'
+ end
+ route_setting :authentication, job_token_allowed: true
+ get 'digest' do
+ present_recipe_download_urls
+ end
+
+ # Get the download urls
+ #
+ # returns the download urls for the existing recipe or package in the registry
+ #
+ # the manifest is a hash of { filename: url }
+ # where the url is the download url for the file
+ desc 'Package Download Urls' do
+ detail 'This feature was introduced in GitLab 12.5'
+ end
+ params do
+ requires :conan_package_reference, type: String, desc: 'Conan package ID'
+ end
+ route_setting :authentication, job_token_allowed: true
+ get 'packages/:conan_package_reference/download_urls' do
+ present_package_download_urls
+ end
+
+ desc 'Recipe Download Urls' do
+ detail 'This feature was introduced in GitLab 12.5'
+ end
+ route_setting :authentication, job_token_allowed: true
+ get 'download_urls' do
+ present_recipe_download_urls
+ end
+
+ # Get the upload urls
+ #
+ # request body contains { filename: filesize } where the filename is the
+ # name of the file the conan client is requesting to upload
+ #
+ # returns { filename: url }
+ # where the url is the upload url for the file that the conan client will use
+ desc 'Package Upload Urls' do
+ detail 'This feature was introduced in GitLab 12.4'
+ end
+ params do
+ requires :conan_package_reference, type: String, desc: 'Conan package ID'
+ end
+ route_setting :authentication, job_token_allowed: true
+ post 'packages/:conan_package_reference/upload_urls' do
+ authorize!(:read_package, project)
+
+ status 200
+ upload_urls = package_upload_urls(::Packages::Conan::FileMetadatum::PACKAGE_FILES)
+
+ present upload_urls, with: ::API::Entities::ConanPackage::ConanUploadUrls
+ end
+
+ desc 'Recipe Upload Urls' do
+ detail 'This feature was introduced in GitLab 12.4'
+ end
+ route_setting :authentication, job_token_allowed: true
+ post 'upload_urls' do
+ authorize!(:read_package, project)
+
+ status 200
+ upload_urls = recipe_upload_urls(::Packages::Conan::FileMetadatum::RECIPE_FILES)
+
+ present upload_urls, with: ::API::Entities::ConanPackage::ConanUploadUrls
+ end
+
+ desc 'Delete Package' do
+ detail 'This feature was introduced in GitLab 12.5'
+ end
+ route_setting :authentication, job_token_allowed: true
+ delete do
+ authorize!(:destroy_package, project)
+
+ track_event('delete_package')
+
+ package.destroy
+ end
+ end
+
+ params do
+ requires :package_name, type: String, regexp: PACKAGE_COMPONENT_REGEX, desc: 'Package name'
+ requires :package_version, type: String, regexp: PACKAGE_COMPONENT_REGEX, desc: 'Package version'
+ requires :package_username, type: String, regexp: PACKAGE_COMPONENT_REGEX, desc: 'Package username'
+ requires :package_channel, type: String, regexp: PACKAGE_COMPONENT_REGEX, desc: 'Package channel'
+ requires :recipe_revision, type: String, regexp: CONAN_REVISION_REGEX, desc: 'Conan Recipe Revision'
+ end
+ namespace 'files/:package_name/:package_version/:package_username/:package_channel/:recipe_revision', requirements: PACKAGE_REQUIREMENTS do
+ before do
+ authenticate_non_get!
+ end
+
+ params do
+ requires :file_name, type: String, desc: 'Package file name', regexp: Gitlab::Regex.conan_file_name_regex
+ end
+ namespace 'export/:file_name', requirements: FILE_NAME_REQUIREMENTS do
+ desc 'Download recipe files' do
+ detail 'This feature was introduced in GitLab 12.6'
+ end
+ route_setting :authentication, job_token_allowed: true
+ get do
+ download_package_file(:recipe_file)
+ end
+
+ desc 'Upload recipe package files' do
+ detail 'This feature was introduced in GitLab 12.6'
+ end
+ params do
+ use :workhorse_upload_params
+ end
+ route_setting :authentication, job_token_allowed: true
+ put do
+ upload_package_file(:recipe_file)
+ end
+
+ desc 'Workhorse authorize the conan recipe file' do
+ detail 'This feature was introduced in GitLab 12.6'
+ end
+ route_setting :authentication, job_token_allowed: true
+ put 'authorize' do
+ authorize_workhorse!(subject: project)
+ end
+ end
+
+ params do
+ requires :conan_package_reference, type: String, desc: 'Conan Package ID'
+ requires :package_revision, type: String, desc: 'Conan Package Revision'
+ requires :file_name, type: String, desc: 'Package file name', regexp: Gitlab::Regex.conan_file_name_regex
+ end
+ namespace 'package/:conan_package_reference/:package_revision/:file_name', requirements: FILE_NAME_REQUIREMENTS do
+ desc 'Download package files' do
+ detail 'This feature was introduced in GitLab 12.5'
+ end
+ route_setting :authentication, job_token_allowed: true
+ get do
+ download_package_file(:package_file)
+ end
+
+ desc 'Workhorse authorize the conan package file' do
+ detail 'This feature was introduced in GitLab 12.6'
+ end
+ route_setting :authentication, job_token_allowed: true
+ put 'authorize' do
+ authorize_workhorse!(subject: project)
+ end
+
+ desc 'Upload package files' do
+ detail 'This feature was introduced in GitLab 12.6'
+ end
+ params do
+ use :workhorse_upload_params
+ end
+ route_setting :authentication, job_token_allowed: true
+ put do
+ upload_package_file(:package_file)
+ end
+ end
+ end
+ end
+
+ helpers do
+ include Gitlab::Utils::StrongMemoize
+ include ::API::Helpers::RelatedResourcesHelpers
+ include ::API::Helpers::Packages::Conan::ApiHelpers
+ end
+ end
+end
diff --git a/lib/api/container_registry_event.rb b/lib/api/container_registry_event.rb
index 6d93cc65336..0b7c35cadbd 100644
--- a/lib/api/container_registry_event.rb
+++ b/lib/api/container_registry_event.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module API
- class ContainerRegistryEvent < Grape::API
+ class ContainerRegistryEvent < Grape::API::Instance
DOCKER_DISTRIBUTION_EVENTS_V1_JSON = 'application/vnd.docker.distribution.events.v1+json'
before { authenticate_registry_notification! }
diff --git a/lib/api/deploy_keys.rb b/lib/api/deploy_keys.rb
index 3259b615369..ad37b7578ad 100644
--- a/lib/api/deploy_keys.rb
+++ b/lib/api/deploy_keys.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module API
- class DeployKeys < Grape::API
+ class DeployKeys < Grape::API::Instance
include PaginationParams
before { authenticate! }
diff --git a/lib/api/deploy_tokens.rb b/lib/api/deploy_tokens.rb
index 0fbbd96cf02..96aa2445f56 100644
--- a/lib/api/deploy_tokens.rb
+++ b/lib/api/deploy_tokens.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module API
- class DeployTokens < Grape::API
+ class DeployTokens < Grape::API::Instance
include PaginationParams
helpers do
@@ -56,7 +56,7 @@ module API
params do
requires :name, type: String, desc: "New deploy token's name"
- requires :scopes, type: Array[String], values: ::DeployToken::AVAILABLE_SCOPES.map(&:to_s),
+ requires :scopes, type: Array[String], coerce_with: ::API::Validations::Types::CommaSeparatedToArray.coerce, values: ::DeployToken::AVAILABLE_SCOPES.map(&:to_s),
desc: 'Indicates the deploy token scopes. Must be at least one of "read_repository", "read_registry", "write_registry", "read_package_registry", or "write_package_registry".'
optional :expires_at, type: DateTime, desc: 'Expiration date for the deploy token. Does not expire if no value is provided.'
optional :username, type: String, desc: 'Username for deploy token. Default is `gitlab+deploy-token-{n}`'
@@ -119,7 +119,7 @@ module API
params do
requires :name, type: String, desc: 'The name of the deploy token'
- requires :scopes, type: Array[String], values: ::DeployToken::AVAILABLE_SCOPES.map(&:to_s),
+ requires :scopes, type: Array[String], coerce_with: ::API::Validations::Types::CommaSeparatedToArray.coerce, values: ::DeployToken::AVAILABLE_SCOPES.map(&:to_s),
desc: 'Indicates the deploy token scopes. Must be at least one of "read_repository", "read_registry", "write_registry", "read_package_registry", or "write_package_registry".'
optional :expires_at, type: DateTime, desc: 'Expiration date for the deploy token. Does not expire if no value is provided.'
optional :username, type: String, desc: 'Username for deploy token. Default is `gitlab+deploy-token-{n}`'
diff --git a/lib/api/deployments.rb b/lib/api/deployments.rb
index cb1dca11e87..87144fd31cc 100644
--- a/lib/api/deployments.rb
+++ b/lib/api/deployments.rb
@@ -2,7 +2,7 @@
module API
# Deployments RESTful API endpoints
- class Deployments < Grape::API
+ class Deployments < Grape::API::Instance
include PaginationParams
before { authenticate! }
diff --git a/lib/api/discussions.rb b/lib/api/discussions.rb
index 7b453ada41c..c431ec8e1e4 100644
--- a/lib/api/discussions.rb
+++ b/lib/api/discussions.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module API
- class Discussions < Grape::API
+ class Discussions < Grape::API::Instance
include PaginationParams
helpers ::API::Helpers::NotesHelpers
helpers ::RendersNotes
@@ -76,10 +76,18 @@ module API
optional :y, type: Integer, desc: 'Y coordinate in the image'
optional :line_range, type: Hash, desc: 'Multi-line start and end' do
- requires :start_line_code, type: String, desc: 'Start line code for multi-line note'
- requires :end_line_code, type: String, desc: 'End line code for multi-line note'
- requires :start_line_type, type: String, desc: 'Start line type for multi-line note'
- requires :end_line_type, type: String, desc: 'End line type for multi-line note'
+ optional :start, type: Hash do
+ optional :line_code, type: String, desc: 'Start line code for multi-line note'
+ optional :type, type: String, desc: 'Start line type for multi-line note'
+ optional :old_line, type: String, desc: 'Start old_line line number'
+ optional :new_line, type: String, desc: 'Start new_line line number'
+ end
+ optional :end, type: Hash do
+ optional :line_code, type: String, desc: 'End line code for multi-line note'
+ optional :type, type: String, desc: 'End line type for multi-line note'
+ optional :old_line, type: String, desc: 'End old_line line number'
+ optional :new_line, type: String, desc: 'End new_line line number'
+ end
end
end
end
diff --git a/lib/api/entities/approvals.rb b/lib/api/entities/approvals.rb
new file mode 100644
index 00000000000..74973772831
--- /dev/null
+++ b/lib/api/entities/approvals.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+module API
+ module Entities
+ class Approvals < Grape::Entity
+ expose :user, using: ::API::Entities::UserBasic
+ end
+ end
+end
diff --git a/lib/api/entities/basic_project_details.rb b/lib/api/entities/basic_project_details.rb
index 13bc19456b3..cf0b32bed26 100644
--- a/lib/api/entities/basic_project_details.rb
+++ b/lib/api/entities/basic_project_details.rb
@@ -33,7 +33,8 @@ module API
project.avatar_url(only_path: false)
end
- expose :star_count, :forks_count
+ expose :forks_count
+ expose :star_count
expose :last_activity_at
expose :namespace, using: 'API::Entities::NamespaceBasic'
expose :custom_attributes, using: 'API::Entities::CustomAttribute', if: :with_custom_attributes
diff --git a/lib/api/entities/conan_package/conan_package_manifest.rb b/lib/api/entities/conan_package/conan_package_manifest.rb
new file mode 100644
index 00000000000..e6acfe1912f
--- /dev/null
+++ b/lib/api/entities/conan_package/conan_package_manifest.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+module API
+ module Entities
+ module ConanPackage
+ class ConanPackageManifest < Grape::Entity
+ expose :package_urls, merge: true
+ end
+ end
+ end
+end
diff --git a/lib/api/entities/conan_package/conan_package_snapshot.rb b/lib/api/entities/conan_package/conan_package_snapshot.rb
new file mode 100644
index 00000000000..d7fdda09b5a
--- /dev/null
+++ b/lib/api/entities/conan_package/conan_package_snapshot.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+module API
+ module Entities
+ module ConanPackage
+ class ConanPackageSnapshot < Grape::Entity
+ expose :package_snapshot, merge: true
+ end
+ end
+ end
+end
diff --git a/lib/api/entities/conan_package/conan_recipe_manifest.rb b/lib/api/entities/conan_package/conan_recipe_manifest.rb
new file mode 100644
index 00000000000..ecaa142cef9
--- /dev/null
+++ b/lib/api/entities/conan_package/conan_recipe_manifest.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+module API
+ module Entities
+ module ConanPackage
+ class ConanRecipeManifest < Grape::Entity
+ expose :recipe_urls, merge: true
+ end
+ end
+ end
+end
diff --git a/lib/api/entities/conan_package/conan_recipe_snapshot.rb b/lib/api/entities/conan_package/conan_recipe_snapshot.rb
new file mode 100644
index 00000000000..09a60d23727
--- /dev/null
+++ b/lib/api/entities/conan_package/conan_recipe_snapshot.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+module API
+ module Entities
+ module ConanPackage
+ class ConanRecipeSnapshot < Grape::Entity
+ expose :recipe_snapshot, merge: true
+ end
+ end
+ end
+end
diff --git a/lib/api/entities/conan_package/conan_upload_urls.rb b/lib/api/entities/conan_package/conan_upload_urls.rb
new file mode 100644
index 00000000000..c14963c87f5
--- /dev/null
+++ b/lib/api/entities/conan_package/conan_upload_urls.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+module API
+ module Entities
+ module ConanPackage
+ class ConanUploadUrls < Grape::Entity
+ expose :upload_urls, merge: true
+ end
+ end
+ end
+end
diff --git a/lib/api/entities/entity_helpers.rb b/lib/api/entities/entity_helpers.rb
new file mode 100644
index 00000000000..3a68044ad35
--- /dev/null
+++ b/lib/api/entities/entity_helpers.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+module API
+ module Entities
+ module EntityHelpers
+ def can_read(attr, &block)
+ ->(obj, opts) { Ability.allowed?(opts[:user], "read_#{attr}".to_sym, yield(obj)) }
+ end
+
+ def can_destroy(attr, &block)
+ ->(obj, opts) { Ability.allowed?(opts[:user], "destroy_#{attr}".to_sym, yield(obj)) }
+ end
+
+ def expose_restricted(attr, &block)
+ expose attr, if: can_read(attr, &block)
+ end
+ end
+ end
+end
diff --git a/lib/api/entities/go_module_version.rb b/lib/api/entities/go_module_version.rb
new file mode 100644
index 00000000000..643e25df9e0
--- /dev/null
+++ b/lib/api/entities/go_module_version.rb
@@ -0,0 +1,10 @@
+# frozen_string_literal: true
+
+module API
+ module Entities
+ class GoModuleVersion < Grape::Entity
+ expose :name, as: 'Version'
+ expose :time, as: 'Time'
+ end
+ end
+end
diff --git a/lib/api/entities/group.rb b/lib/api/entities/group.rb
index 8a6a5b7057c..e430eba4880 100644
--- a/lib/api/entities/group.rb
+++ b/lib/api/entities/group.rb
@@ -31,6 +31,7 @@ module API
expose :wiki_size
expose :lfs_objects_size
expose :build_artifacts_size, as: :job_artifacts_size
+ expose :snippets_size
end
end
end
diff --git a/lib/api/entities/group_detail.rb b/lib/api/entities/group_detail.rb
index 93dc41da81d..2d9d4ca7992 100644
--- a/lib/api/entities/group_detail.rb
+++ b/lib/api/entities/group_detail.rb
@@ -7,6 +7,7 @@ module API
SharedGroupWithGroup.represent(group.shared_with_group_links.public_or_visible_to_user(group, options[:current_user]))
end
expose :runners_token, if: lambda { |group, options| options[:user_can_admin_group] }
+
expose :projects, using: Entities::Project do |group, options|
projects = GroupProjectsFinder.new(
group: group,
diff --git a/lib/api/entities/issuable_entity.rb b/lib/api/entities/issuable_entity.rb
index 5bee59de539..e2c674c0b8b 100644
--- a/lib/api/entities/issuable_entity.rb
+++ b/lib/api/entities/issuable_entity.rb
@@ -8,10 +8,38 @@ module API
expose :title, :description
expose :state, :created_at, :updated_at
- # Avoids an N+1 query when metadata is included
- def issuable_metadata(subject, options, method, args = nil)
- cached_subject = options.dig(:issuable_metadata, subject.id)
- (cached_subject || subject).public_send(method, *args) # rubocop: disable GitlabSecurity/PublicSend
+ def presented
+ lazy_issuable_metadata
+
+ super
+ end
+
+ def issuable_metadata
+ options.dig(:issuable_metadata, object.id) || lazy_issuable_metadata
+ end
+
+ protected
+
+ # This method will preload the `issuable_metadata` for the current
+ # entity according to the current top-level entity options, such
+ # as the current_user.
+ def lazy_issuable_metadata
+ BatchLoader.for(object).batch(key: [current_user, :issuable_metadata]) do |models, loader, args|
+ current_user = args[:key].first
+
+ issuable_metadata = Gitlab::IssuableMetadata.new(current_user, models)
+ metadata_by_id = issuable_metadata.data
+
+ models.each do |issuable|
+ loader.call(issuable, metadata_by_id[issuable.id])
+ end
+ end
+ end
+
+ private
+
+ def current_user
+ options[:current_user]
end
end
end
diff --git a/lib/api/entities/issue_basic.rb b/lib/api/entities/issue_basic.rb
index af92f4124f1..cf96c6556ec 100644
--- a/lib/api/entities/issue_basic.rb
+++ b/lib/api/entities/issue_basic.rb
@@ -21,10 +21,10 @@ module API
issue.assignees.first
end
- expose(:user_notes_count) { |issue, options| issuable_metadata(issue, options, :user_notes_count) }
- expose(:merge_requests_count) { |issue, options| issuable_metadata(issue, options, :merge_requests_count, options[:current_user]) }
- expose(:upvotes) { |issue, options| issuable_metadata(issue, options, :upvotes) }
- expose(:downvotes) { |issue, options| issuable_metadata(issue, options, :downvotes) }
+ expose(:user_notes_count) { |issue, options| issuable_metadata.user_notes_count }
+ expose(:merge_requests_count) { |issue, options| issuable_metadata.merge_requests_count }
+ expose(:upvotes) { |issue, options| issuable_metadata.upvotes }
+ expose(:downvotes) { |issue, options| issuable_metadata.downvotes }
expose :due_date
expose :confidential
expose :discussion_locked
diff --git a/lib/api/entities/merge_request_approvals.rb b/lib/api/entities/merge_request_approvals.rb
new file mode 100644
index 00000000000..e3d58d687c4
--- /dev/null
+++ b/lib/api/entities/merge_request_approvals.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+
+module API
+ module Entities
+ class MergeRequestApprovals < Grape::Entity
+ expose :user_has_approved do |merge_request, options|
+ merge_request.approved_by?(options[:current_user])
+ end
+
+ expose :user_can_approve do |merge_request, options|
+ !merge_request.approved_by?(options[:current_user]) &&
+ options[:current_user].can?(:approve_merge_request, merge_request)
+ end
+
+ expose :approved do |merge_request|
+ merge_request.approvals.present?
+ end
+
+ expose :approved_by, using: ::API::Entities::Approvals do |merge_request|
+ merge_request.approvals
+ end
+ end
+ end
+end
diff --git a/lib/api/entities/merge_request_basic.rb b/lib/api/entities/merge_request_basic.rb
index 1643f267938..69523e3637b 100644
--- a/lib/api/entities/merge_request_basic.rb
+++ b/lib/api/entities/merge_request_basic.rb
@@ -22,13 +22,11 @@ module API
MarkupHelper.markdown_field(entity, :description)
end
expose :target_branch, :source_branch
- expose(:user_notes_count) { |merge_request, options| issuable_metadata(merge_request, options, :user_notes_count) }
- expose(:upvotes) { |merge_request, options| issuable_metadata(merge_request, options, :upvotes) }
- expose(:downvotes) { |merge_request, options| issuable_metadata(merge_request, options, :downvotes) }
- expose :assignee, using: ::API::Entities::UserBasic do |merge_request|
- merge_request.assignee
- end
- expose :author, :assignees, using: Entities::UserBasic
+ expose(:user_notes_count) { |merge_request, options| issuable_metadata.user_notes_count }
+ expose(:upvotes) { |merge_request, options| issuable_metadata.upvotes }
+ expose(:downvotes) { |merge_request, options| issuable_metadata.downvotes }
+
+ expose :author, :assignees, :assignee, using: Entities::UserBasic
expose :source_project_id, :target_project_id
expose :labels do |merge_request, options|
if options[:with_labels_details]
@@ -57,9 +55,12 @@ module API
expose :discussion_locked
expose :should_remove_source_branch?, as: :should_remove_source_branch
expose :force_remove_source_branch?, as: :force_remove_source_branch
- expose :allow_collaboration, if: -> (merge_request, _) { merge_request.for_fork? }
- # Deprecated
- expose :allow_collaboration, as: :allow_maintainer_to_push, if: -> (merge_request, _) { merge_request.for_fork? }
+
+ with_options if: -> (merge_request, _) { merge_request.for_fork? } do
+ expose :allow_collaboration
+ # Deprecated
+ expose :allow_collaboration, as: :allow_maintainer_to_push
+ end
# reference is deprecated in favour of references
# Introduced [Gitlab 12.6](https://gitlab.com/gitlab-org/gitlab/merge_requests/20354)
diff --git a/lib/api/entities/npm_package.rb b/lib/api/entities/npm_package.rb
new file mode 100644
index 00000000000..b094f3acdb6
--- /dev/null
+++ b/lib/api/entities/npm_package.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+module API
+ module Entities
+ class NpmPackage < Grape::Entity
+ expose :name
+ expose :versions
+ expose :dist_tags, as: 'dist-tags'
+ end
+ end
+end
diff --git a/lib/api/entities/npm_package_tag.rb b/lib/api/entities/npm_package_tag.rb
new file mode 100644
index 00000000000..7f458fa037f
--- /dev/null
+++ b/lib/api/entities/npm_package_tag.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+module API
+ module Entities
+ class NpmPackageTag < Grape::Entity
+ expose :dist_tags, merge: true
+ end
+ end
+end
diff --git a/lib/api/entities/nuget/dependency.rb b/lib/api/entities/nuget/dependency.rb
new file mode 100644
index 00000000000..b61c37f5882
--- /dev/null
+++ b/lib/api/entities/nuget/dependency.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+
+module API
+ module Entities
+ module Nuget
+ class Dependency < Grape::Entity
+ expose :id, as: :@id
+ expose :type, as: :@type
+ expose :name, as: :id
+ expose :range
+ end
+ end
+ end
+end
diff --git a/lib/api/entities/nuget/dependency_group.rb b/lib/api/entities/nuget/dependency_group.rb
new file mode 100644
index 00000000000..dcab9359fcf
--- /dev/null
+++ b/lib/api/entities/nuget/dependency_group.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+
+module API
+ module Entities
+ module Nuget
+ class DependencyGroup < Grape::Entity
+ expose :id, as: :@id
+ expose :type, as: :@type
+ expose :target_framework, as: :targetFramework, expose_nil: false
+ expose :dependencies, using: ::API::Entities::Nuget::Dependency
+ end
+ end
+ end
+end
diff --git a/lib/api/entities/nuget/metadatum.rb b/lib/api/entities/nuget/metadatum.rb
new file mode 100644
index 00000000000..87caef41a85
--- /dev/null
+++ b/lib/api/entities/nuget/metadatum.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+module API
+ module Entities
+ module Nuget
+ class Metadatum < Grape::Entity
+ expose :project_url, as: :projectUrl, expose_nil: false
+ expose :license_url, as: :licenseUrl, expose_nil: false
+ expose :icon_url, as: :iconUrl, expose_nil: false
+ end
+ end
+ end
+end
diff --git a/lib/api/entities/nuget/package_metadata.rb b/lib/api/entities/nuget/package_metadata.rb
new file mode 100644
index 00000000000..e1c2a1ae161
--- /dev/null
+++ b/lib/api/entities/nuget/package_metadata.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+module API
+ module Entities
+ module Nuget
+ class PackageMetadata < Grape::Entity
+ expose :json_url, as: :@id
+ expose :archive_url, as: :packageContent
+ expose :catalog_entry, as: :catalogEntry, using: ::API::Entities::Nuget::PackageMetadataCatalogEntry
+ end
+ end
+ end
+end
diff --git a/lib/api/entities/nuget/package_metadata_catalog_entry.rb b/lib/api/entities/nuget/package_metadata_catalog_entry.rb
new file mode 100644
index 00000000000..5533f857596
--- /dev/null
+++ b/lib/api/entities/nuget/package_metadata_catalog_entry.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+module API
+ module Entities
+ module Nuget
+ class PackageMetadataCatalogEntry < Grape::Entity
+ expose :json_url, as: :@id
+ expose :authors
+ expose :dependency_groups, as: :dependencyGroups, using: ::API::Entities::Nuget::DependencyGroup
+ expose :package_name, as: :id
+ expose :package_version, as: :version
+ expose :tags
+ expose :archive_url, as: :packageContent
+ expose :summary
+ expose :metadatum, using: ::API::Entities::Nuget::Metadatum, merge: true
+ end
+ end
+ end
+end
diff --git a/lib/api/entities/nuget/packages_metadata.rb b/lib/api/entities/nuget/packages_metadata.rb
new file mode 100644
index 00000000000..1cdf2491725
--- /dev/null
+++ b/lib/api/entities/nuget/packages_metadata.rb
@@ -0,0 +1,12 @@
+# frozen_string_literal: true
+
+module API
+ module Entities
+ module Nuget
+ class PackagesMetadata < Grape::Entity
+ expose :count
+ expose :items, using: ::API::Entities::Nuget::PackagesMetadataItem
+ end
+ end
+ end
+end
diff --git a/lib/api/entities/nuget/packages_metadata_item.rb b/lib/api/entities/nuget/packages_metadata_item.rb
new file mode 100644
index 00000000000..84cc79166f3
--- /dev/null
+++ b/lib/api/entities/nuget/packages_metadata_item.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+module API
+ module Entities
+ module Nuget
+ class PackagesMetadataItem < Grape::Entity
+ expose :json_url, as: :@id
+ expose :lower_version, as: :lower
+ expose :upper_version, as: :upper
+ expose :packages_count, as: :count
+ expose :packages, as: :items, using: ::API::Entities::Nuget::PackageMetadata
+ end
+ end
+ end
+end
diff --git a/lib/api/entities/nuget/packages_versions.rb b/lib/api/entities/nuget/packages_versions.rb
new file mode 100644
index 00000000000..498c6970d5c
--- /dev/null
+++ b/lib/api/entities/nuget/packages_versions.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+module API
+ module Entities
+ module Nuget
+ class PackagesVersions < Grape::Entity
+ expose :versions
+ end
+ end
+ end
+end
diff --git a/lib/api/entities/nuget/search_result.rb b/lib/api/entities/nuget/search_result.rb
new file mode 100644
index 00000000000..8e028cbad95
--- /dev/null
+++ b/lib/api/entities/nuget/search_result.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+module API
+ module Entities
+ module Nuget
+ class SearchResult < Grape::Entity
+ expose :type, as: :@type
+ expose :authors
+ expose :name, as: :id
+ expose :name, as: :title
+ expose :summary
+ expose :total_downloads, as: :totalDownloads
+ expose :verified
+ expose :version
+ expose :versions, using: ::API::Entities::Nuget::SearchResultVersion
+ expose :tags
+ expose :metadatum, using: ::API::Entities::Nuget::Metadatum, merge: true
+ end
+ end
+ end
+end
diff --git a/lib/api/entities/nuget/search_result_version.rb b/lib/api/entities/nuget/search_result_version.rb
new file mode 100644
index 00000000000..9032c964c44
--- /dev/null
+++ b/lib/api/entities/nuget/search_result_version.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+module API
+ module Entities
+ module Nuget
+ class SearchResultVersion < Grape::Entity
+ expose :json_url, as: :@id
+ expose :version
+ expose :downloads
+ end
+ end
+ end
+end
diff --git a/lib/api/entities/nuget/search_results.rb b/lib/api/entities/nuget/search_results.rb
new file mode 100644
index 00000000000..22a77dc7b6c
--- /dev/null
+++ b/lib/api/entities/nuget/search_results.rb
@@ -0,0 +1,12 @@
+# frozen_string_literal: true
+
+module API
+ module Entities
+ module Nuget
+ class SearchResults < Grape::Entity
+ expose :total_count, as: :totalHits
+ expose :data, using: ::API::Entities::Nuget::SearchResult
+ end
+ end
+ end
+end
diff --git a/lib/api/entities/nuget/service_index.rb b/lib/api/entities/nuget/service_index.rb
new file mode 100644
index 00000000000..e57bd04adb9
--- /dev/null
+++ b/lib/api/entities/nuget/service_index.rb
@@ -0,0 +1,12 @@
+# frozen_string_literal: true
+
+module API
+ module Entities
+ module Nuget
+ class ServiceIndex < Grape::Entity
+ expose :version
+ expose :resources
+ end
+ end
+ end
+end
diff --git a/lib/api/entities/package.rb b/lib/api/entities/package.rb
new file mode 100644
index 00000000000..73473f16da9
--- /dev/null
+++ b/lib/api/entities/package.rb
@@ -0,0 +1,42 @@
+# frozen_string_literal: true
+
+module API
+ module Entities
+ class Package < Grape::Entity
+ include ::API::Helpers::RelatedResourcesHelpers
+ extend ::API::Entities::EntityHelpers
+
+ expose :id
+ expose :name
+ expose :version
+ expose :package_type
+
+ expose :_links do
+ expose :web_path do |package|
+ if ::Gitlab.ee?
+ ::Gitlab::Routing.url_helpers.project_package_path(package.project, package)
+ end
+ end
+
+ expose :delete_api_path, if: can_destroy(:package, &:project) do |package|
+ expose_url api_v4_projects_packages_path(package_id: package.id, id: package.project_id)
+ end
+ end
+
+ expose :created_at
+ expose :project_id, if: ->(_, opts) { opts[:group] }
+ expose :project_path, if: ->(obj, opts) { opts[:group] && Ability.allowed?(opts[:user], :read_project, obj.project) }
+ expose :tags
+
+ expose :pipeline, if: ->(package) { package.build_info }, using: Package::Pipeline
+
+ expose :versions, using: ::API::Entities::PackageVersion
+
+ private
+
+ def project_path
+ object.project.full_path
+ end
+ end
+ end
+end
diff --git a/lib/api/entities/package/pipeline.rb b/lib/api/entities/package/pipeline.rb
new file mode 100644
index 00000000000..e91a12e47fa
--- /dev/null
+++ b/lib/api/entities/package/pipeline.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+module API
+ module Entities
+ class Package < Grape::Entity
+ class Pipeline < ::API::Entities::PipelineBasic
+ expose :user, using: ::API::Entities::UserBasic
+ end
+ end
+ end
+end
diff --git a/lib/api/entities/package_file.rb b/lib/api/entities/package_file.rb
new file mode 100644
index 00000000000..8be4e5a4316
--- /dev/null
+++ b/lib/api/entities/package_file.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+module API
+ module Entities
+ class PackageFile < Grape::Entity
+ expose :id, :package_id, :created_at
+ expose :file_name, :size
+ expose :file_md5, :file_sha1
+ end
+ end
+end
diff --git a/lib/api/entities/package_version.rb b/lib/api/entities/package_version.rb
new file mode 100644
index 00000000000..5f3e86c3229
--- /dev/null
+++ b/lib/api/entities/package_version.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+
+module API
+ module Entities
+ class PackageVersion < Grape::Entity
+ expose :id
+ expose :version
+ expose :created_at
+ expose :tags
+
+ expose :pipeline, if: ->(package) { package.build_info }, using: Package::Pipeline
+ end
+ end
+end
diff --git a/lib/api/entities/project.rb b/lib/api/entities/project.rb
index 55a57501858..e3c5177cd0b 100644
--- a/lib/api/entities/project.rb
+++ b/lib/api/entities/project.rb
@@ -51,6 +51,8 @@ module API
expose(:wiki_enabled) { |project, options| project.feature_available?(:wiki, options[:current_user]) }
expose(:jobs_enabled) { |project, options| project.feature_available?(:builds, options[:current_user]) }
expose(:snippets_enabled) { |project, options| project.feature_available?(:snippets, options[:current_user]) }
+ expose :service_desk_enabled
+ expose :service_desk_address
expose(:can_create_merge_request_in) do |project, options|
Ability.allowed?(options[:current_user], :create_merge_request_in, project)
diff --git a/lib/api/entities/project_statistics.rb b/lib/api/entities/project_statistics.rb
index e5f6165da31..32201e88eaf 100644
--- a/lib/api/entities/project_statistics.rb
+++ b/lib/api/entities/project_statistics.rb
@@ -9,6 +9,7 @@ module API
expose :wiki_size
expose :lfs_objects_size
expose :build_artifacts_size, as: :job_artifacts_size
+ expose :snippets_size
end
end
end
diff --git a/lib/api/entities/release.rb b/lib/api/entities/release.rb
index 99fa496d368..afe14cf33cf 100644
--- a/lib/api/entities/release.rb
+++ b/lib/api/entities/release.rb
@@ -5,9 +5,7 @@ module API
class Release < Grape::Entity
include ::API::Helpers::Presentable
- expose :name do |release, _|
- can_download_code? ? release.name : "Release-#{release.id}"
- end
+ expose :name
expose :tag, as: :tag_name, if: ->(_, _) { can_download_code? }
expose :description
expose :description_html do |entity|
@@ -23,10 +21,7 @@ module API
expose :tag_path, expose_nil: false
expose :assets do
- expose :assets_count, as: :count do |release, _|
- assets_to_exclude = can_download_code? ? [] : [:sources]
- release.assets_count(except: assets_to_exclude)
- end
+ expose :assets_count, as: :count
expose :sources, using: Entities::Releases::Source, if: ->(_, _) { can_download_code? }
expose :links, using: Entities::Releases::Link do |release, options|
release.links.sorted
diff --git a/lib/api/entities/resource_state_event.rb b/lib/api/entities/resource_state_event.rb
new file mode 100644
index 00000000000..f71a38e4115
--- /dev/null
+++ b/lib/api/entities/resource_state_event.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+module API
+ module Entities
+ class ResourceStateEvent < Grape::Entity
+ expose :id
+ expose :user, using: Entities::UserBasic
+ expose :created_at
+ expose :resource_type do |event, _options|
+ event.issuable.class.name
+ end
+ expose :resource_id do |event, _options|
+ event.issuable.id
+ end
+ expose :state
+ end
+ end
+end
diff --git a/lib/api/entities/snippet.rb b/lib/api/entities/snippet.rb
index 19c89603cbc..40488eb882d 100644
--- a/lib/api/entities/snippet.rb
+++ b/lib/api/entities/snippet.rb
@@ -17,6 +17,18 @@ module API
expose :file_name do |snippet|
snippet.file_name_on_repo || snippet.file_name
end
+ expose :files, if: ->(snippet, options) { snippet_multiple_files?(snippet, options[:current_user]) } do |snippet, options|
+ snippet.list_files.map do |file|
+ {
+ path: file,
+ raw_url: Gitlab::UrlBuilder.build(snippet, file: file, ref: snippet.repository.root_ref)
+ }
+ end
+ end
+
+ def snippet_multiple_files?(snippet, current_user)
+ ::Feature.enabled?(:snippet_multiple_files, current_user) && snippet.repository_exists?
+ end
end
end
end
diff --git a/lib/api/entities/user.rb b/lib/api/entities/user.rb
index adf954ab02d..4aa5c9b7236 100644
--- a/lib/api/entities/user.rb
+++ b/lib/api/entities/user.rb
@@ -5,7 +5,7 @@ module API
class User < UserBasic
include UsersHelper
expose :created_at, if: ->(user, opts) { Ability.allowed?(opts[:current_user], :read_user_profile, user) }
- expose :bio, :location, :public_email, :skype, :linkedin, :twitter, :website_url, :organization, :job_title
+ expose :bio, :bio_html, :location, :public_email, :skype, :linkedin, :twitter, :website_url, :organization, :job_title
expose :work_information do |user|
work_information(user)
end
diff --git a/lib/api/environments.rb b/lib/api/environments.rb
index 28019ce7796..b825904e2c5 100644
--- a/lib/api/environments.rb
+++ b/lib/api/environments.rb
@@ -2,7 +2,7 @@
module API
# Environments RESTfull API endpoints
- class Environments < Grape::API
+ class Environments < Grape::API::Instance
include PaginationParams
before { authenticate! }
diff --git a/lib/api/error_tracking.rb b/lib/api/error_tracking.rb
index 14888037f53..64ec6f0a57a 100644
--- a/lib/api/error_tracking.rb
+++ b/lib/api/error_tracking.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module API
- class ErrorTracking < Grape::API
+ class ErrorTracking < Grape::API::Instance
before { authenticate! }
params do
diff --git a/lib/api/events.rb b/lib/api/events.rb
index e4c017fab42..0b79431a76d 100644
--- a/lib/api/events.rb
+++ b/lib/api/events.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module API
- class Events < Grape::API
+ class Events < Grape::API::Instance
include PaginationParams
include APIGuard
helpers ::API::Helpers::EventsHelpers
diff --git a/lib/api/features.rb b/lib/api/features.rb
index 3fb3fc92e42..9d011d658f6 100644
--- a/lib/api/features.rb
+++ b/lib/api/features.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module API
- class Features < Grape::API
+ class Features < Grape::API::Instance
before { authenticated_as_admin! }
helpers do
diff --git a/lib/api/files.rb b/lib/api/files.rb
index 76ab9a2190b..748bdfa894d 100644
--- a/lib/api/files.rb
+++ b/lib/api/files.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module API
- class Files < Grape::API
+ class Files < Grape::API::Instance
include APIGuard
FILE_ENDPOINT_REQUIREMENTS = API::NAMESPACE_OR_PROJECT_REQUIREMENTS.merge(file_path: API::NO_SLASH_URL_PART_REGEX)
@@ -56,7 +56,7 @@ module API
ref: params[:ref],
blob_id: @blob.id,
commit_id: @commit.id,
- last_commit_id: @repo.last_commit_id_for_path(@commit.sha, params[:file_path])
+ last_commit_id: @repo.last_commit_id_for_path(@commit.sha, params[:file_path], literal_pathspec: true)
}
end
diff --git a/lib/api/freeze_periods.rb b/lib/api/freeze_periods.rb
index 9c7e5a5832d..b8254ee9ab4 100644
--- a/lib/api/freeze_periods.rb
+++ b/lib/api/freeze_periods.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module API
- class FreezePeriods < Grape::API
+ class FreezePeriods < Grape::API::Instance
include PaginationParams
before { authenticate! }
diff --git a/lib/api/go_proxy.rb b/lib/api/go_proxy.rb
new file mode 100755
index 00000000000..c0207f9169c
--- /dev/null
+++ b/lib/api/go_proxy.rb
@@ -0,0 +1,135 @@
+# frozen_string_literal: true
+module API
+ class GoProxy < Grape::API::Instance
+ helpers Gitlab::Golang
+ helpers ::API::Helpers::PackagesHelpers
+
+ # basic semver, except case encoded (A => !a)
+ MODULE_VERSION_REGEX = /v(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-([-.!a-z0-9]+))?(?:\+([-.!a-z0-9]+))?/.freeze
+
+ MODULE_VERSION_REQUIREMENTS = { module_version: MODULE_VERSION_REGEX }.freeze
+
+ before { require_packages_enabled! }
+
+ helpers do
+ def case_decode(str)
+ # Converts "github.com/!azure" to "github.com/Azure"
+ #
+ # From `go help goproxy`:
+ #
+ # > To avoid problems when serving from case-sensitive file systems,
+ # > the <module> and <version> elements are case-encoded, replacing
+ # > every uppercase letter with an exclamation mark followed by the
+ # > corresponding lower-case letter: github.com/Azure encodes as
+ # > github.com/!azure.
+
+ str.gsub(/![[:alpha:]]/) { |s| s[1..].upcase }
+ end
+
+ def find_project!(id)
+ # based on API::Helpers::Packages::BasicAuthHelpers#authorized_project_find!
+
+ project = find_project(id)
+
+ return project if project && can?(current_user, :read_project, project)
+
+ if current_user
+ not_found!('Project')
+ else
+ unauthorized!
+ end
+ end
+
+ def find_module
+ not_found! unless Feature.enabled?(:go_proxy, user_project)
+
+ module_name = case_decode params[:module_name]
+ bad_request!('Module Name') if module_name.blank?
+
+ mod = ::Packages::Go::ModuleFinder.new(user_project, module_name).execute
+
+ not_found! unless mod
+
+ mod
+ end
+
+ def find_version
+ module_version = case_decode params[:module_version]
+ ver = ::Packages::Go::VersionFinder.new(find_module).find(module_version)
+
+ not_found! unless ver&.valid?
+
+ ver
+
+ rescue ArgumentError
+ not_found!
+ end
+ end
+
+ params do
+ requires :id, type: String, desc: 'The ID of a project'
+ requires :module_name, type: String, desc: 'Module name', coerce_with: ->(val) { CGI.unescape(val) }
+ end
+ route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true
+ resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
+ before do
+ authorize_read_package!
+ end
+
+ namespace ':id/packages/go/*module_name/@v' do
+ desc 'Get all tagged versions for a given Go module' do
+ detail 'See `go help goproxy`, GET $GOPROXY/<module>/@v/list. This feature was introduced in GitLab 13.1.'
+ end
+ get 'list' do
+ mod = find_module
+
+ content_type 'text/plain'
+ mod.versions.map { |t| t.name }.join("\n")
+ end
+
+ desc 'Get information about the given module version' do
+ detail 'See `go help goproxy`, GET $GOPROXY/<module>/@v/<version>.info. This feature was introduced in GitLab 13.1.'
+ success ::API::Entities::GoModuleVersion
+ end
+ params do
+ requires :module_version, type: String, desc: 'Module version'
+ end
+ get ':module_version.info', requirements: MODULE_VERSION_REQUIREMENTS do
+ ver = find_version
+
+ present ::Packages::Go::ModuleVersionPresenter.new(ver), with: ::API::Entities::GoModuleVersion
+ end
+
+ desc 'Get the module file of the given module version' do
+ detail 'See `go help goproxy`, GET $GOPROXY/<module>/@v/<version>.mod. This feature was introduced in GitLab 13.1.'
+ end
+ params do
+ requires :module_version, type: String, desc: 'Module version'
+ end
+ get ':module_version.mod', requirements: MODULE_VERSION_REQUIREMENTS do
+ ver = find_version
+
+ content_type 'text/plain'
+ ver.gomod
+ end
+
+ desc 'Get a zip of the source of the given module version' do
+ detail 'See `go help goproxy`, GET $GOPROXY/<module>/@v/<version>.zip. This feature was introduced in GitLab 13.1.'
+ end
+ params do
+ requires :module_version, type: String, desc: 'Module version'
+ end
+ get ':module_version.zip', requirements: MODULE_VERSION_REQUIREMENTS do
+ ver = find_version
+
+ content_type 'application/zip'
+ env['api.format'] = :binary
+ header['Content-Disposition'] = ActionDispatch::Http::ContentDisposition.format(disposition: 'attachment', filename: ver.name + '.zip')
+ header['Content-Transfer-Encoding'] = 'binary'
+ status :ok
+ body ver.archive.string
+ end
+ end
+ end
+ end
+end
diff --git a/lib/api/group_boards.rb b/lib/api/group_boards.rb
index 88d04e70e11..7efc12121d2 100644
--- a/lib/api/group_boards.rb
+++ b/lib/api/group_boards.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module API
- class GroupBoards < Grape::API
+ class GroupBoards < Grape::API::Instance
include BoardsResponses
include PaginationParams
diff --git a/lib/api/group_clusters.rb b/lib/api/group_clusters.rb
index 2c12c6387fb..ae41d9f13b8 100644
--- a/lib/api/group_clusters.rb
+++ b/lib/api/group_clusters.rb
@@ -1,23 +1,11 @@
# frozen_string_literal: true
module API
- class GroupClusters < Grape::API
+ class GroupClusters < Grape::API::Instance
include PaginationParams
before { authenticate! }
- # EE::API::GroupClusters will
- # override these methods
- helpers do
- params :create_params_ee do
- end
-
- params :update_params_ee do
- end
- end
-
- prepend_if_ee('EE::API::GroupClusters') # rubocop: disable Cop/InjectEnterpriseEditionModule
-
params do
requires :id, type: String, desc: 'The ID of the group'
end
@@ -52,6 +40,7 @@ module API
params do
requires :name, type: String, desc: 'Cluster name'
optional :enabled, type: Boolean, default: true, desc: 'Determines if cluster is active or not, defaults to true'
+ optional :environment_scope, default: '*', type: String, desc: 'The associated environment to the cluster'
optional :domain, type: String, desc: 'Cluster base domain'
optional :management_project_id, type: Integer, desc: 'The ID of the management project'
optional :managed, type: Boolean, default: true, desc: 'Determines if GitLab will manage namespaces and service accounts for this cluster, defaults to true'
@@ -62,7 +51,6 @@ module API
optional :namespace, type: String, desc: 'Unique namespace related to Group'
optional :authorization_type, type: String, values: ::Clusters::Platforms::Kubernetes.authorization_types.keys, default: 'rbac', desc: 'Cluster authorization type, defaults to RBAC'
end
- use :create_params_ee
end
post ':id/clusters/user' do
authorize! :add_cluster, user_group
@@ -85,6 +73,7 @@ module API
requires :cluster_id, type: Integer, desc: 'The cluster ID'
optional :name, type: String, desc: 'Cluster name'
optional :domain, type: String, desc: 'Cluster base domain'
+ optional :environment_scope, type: String, desc: 'The associated environment to the cluster'
optional :management_project_id, type: Integer, desc: 'The ID of the management project'
optional :platform_kubernetes_attributes, type: Hash, desc: %q(Platform Kubernetes data) do
optional :api_url, type: String, desc: 'URL to access the Kubernetes API'
@@ -92,7 +81,6 @@ module API
optional :ca_cert, type: String, desc: 'TLS certificate (needed if API is using a self-signed TLS certificate)'
optional :namespace, type: String, desc: 'Unique namespace related to Group'
end
- use :update_params_ee
end
put ':id/clusters/:cluster_id' do
authorize! :update_cluster, cluster
diff --git a/lib/api/group_container_repositories.rb b/lib/api/group_container_repositories.rb
index d34317b5271..25b3059f63b 100644
--- a/lib/api/group_container_repositories.rb
+++ b/lib/api/group_container_repositories.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module API
- class GroupContainerRepositories < Grape::API
+ class GroupContainerRepositories < Grape::API::Instance
include PaginationParams
before { authorize_read_group_container_images! }
diff --git a/lib/api/group_export.rb b/lib/api/group_export.rb
index d3010b6d147..dc14813eefc 100644
--- a/lib/api/group_export.rb
+++ b/lib/api/group_export.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module API
- class GroupExport < Grape::API
+ class GroupExport < Grape::API::Instance
helpers Helpers::RateLimiter
before do
diff --git a/lib/api/group_import.rb b/lib/api/group_import.rb
index afcbc24d3ce..b82d9fc519a 100644
--- a/lib/api/group_import.rb
+++ b/lib/api/group_import.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module API
- class GroupImport < Grape::API
+ class GroupImport < Grape::API::Instance
helpers Helpers::FileUploadHelpers
helpers do
diff --git a/lib/api/group_labels.rb b/lib/api/group_labels.rb
index 7585293031f..56f2b769464 100644
--- a/lib/api/group_labels.rb
+++ b/lib/api/group_labels.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module API
- class GroupLabels < Grape::API
+ class GroupLabels < Grape::API::Instance
include PaginationParams
helpers ::API::Helpers::LabelHelpers
diff --git a/lib/api/group_milestones.rb b/lib/api/group_milestones.rb
index 9e9f5101285..82f5df79356 100644
--- a/lib/api/group_milestones.rb
+++ b/lib/api/group_milestones.rb
@@ -1,13 +1,11 @@
# frozen_string_literal: true
module API
- class GroupMilestones < Grape::API
+ class GroupMilestones < Grape::API::Instance
include MilestoneResponses
include PaginationParams
- before do
- authenticate!
- end
+ before { authenticate! }
params do
requires :id, type: String, desc: 'The ID of a group'
diff --git a/lib/api/group_packages.rb b/lib/api/group_packages.rb
new file mode 100644
index 00000000000..aa047e260f5
--- /dev/null
+++ b/lib/api/group_packages.rb
@@ -0,0 +1,44 @@
+# frozen_string_literal: true
+
+module API
+ class GroupPackages < Grape::API::Instance
+ include PaginationParams
+
+ before do
+ authorize_packages_access!(user_group)
+ end
+
+ helpers ::API::Helpers::PackagesHelpers
+
+ params do
+ requires :id, type: String, desc: "Group's ID or path"
+ optional :exclude_subgroups, type: Boolean, default: false, desc: 'Determines if subgroups should be excluded'
+ end
+ resource :groups, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
+ desc 'Get all project packages within a group' do
+ detail 'This feature was introduced in GitLab 12.5'
+ success ::API::Entities::Package
+ end
+ params do
+ use :pagination
+ optional :order_by, type: String, values: %w[created_at name version type project_path], default: 'created_at',
+ desc: 'Return packages ordered by `created_at`, `name`, `version` or `type` fields.'
+ optional :sort, type: String, values: %w[asc desc], default: 'asc',
+ desc: 'Return packages sorted in `asc` or `desc` order.'
+ optional :package_type, type: String, values: Packages::Package.package_types.keys,
+ desc: 'Return packages of a certain type'
+ optional :package_name, type: String,
+ desc: 'Return packages with this name'
+ end
+ get ':id/packages' do
+ packages = Packages::GroupPackagesFinder.new(
+ current_user,
+ user_group,
+ declared(params).slice(:exclude_subgroups, :order_by, :sort, :package_type, :package_name)
+ ).execute
+
+ present paginate(packages), with: ::API::Entities::Package, user: current_user, group: true
+ end
+ end
+ end
+end
diff --git a/lib/api/group_variables.rb b/lib/api/group_variables.rb
index 916f89649a5..d3ca1c79e73 100644
--- a/lib/api/group_variables.rb
+++ b/lib/api/group_variables.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module API
- class GroupVariables < Grape::API
+ class GroupVariables < Grape::API::Instance
include PaginationParams
before { authenticate! }
@@ -48,7 +48,7 @@ module API
requires :value, type: String, desc: 'The value of the variable'
optional :protected, type: String, desc: 'Whether the variable is protected'
optional :masked, type: String, desc: 'Whether the variable is masked'
- optional :variable_type, type: String, values: Ci::GroupVariable.variable_types.keys, desc: 'The type of variable, must be one of env_var or file. Defaults to env_var'
+ optional :variable_type, type: String, values: ::Ci::GroupVariable.variable_types.keys, desc: 'The type of variable, must be one of env_var or file. Defaults to env_var'
end
post ':id/variables' do
variable_params = declared_params(include_missing: false)
@@ -70,7 +70,7 @@ module API
optional :value, type: String, desc: 'The value of the variable'
optional :protected, type: String, desc: 'Whether the variable is protected'
optional :masked, type: String, desc: 'Whether the variable is masked'
- optional :variable_type, type: String, values: Ci::GroupVariable.variable_types.keys, desc: 'The type of variable, must be one of env_var or file'
+ optional :variable_type, type: String, values: ::Ci::GroupVariable.variable_types.keys, desc: 'The type of variable, must be one of env_var or file'
end
# rubocop: disable CodeReuse/ActiveRecord
put ':id/variables/:key' do
diff --git a/lib/api/groups.rb b/lib/api/groups.rb
index 6e07bb46721..9ac3ac818fc 100644
--- a/lib/api/groups.rb
+++ b/lib/api/groups.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module API
- class Groups < Grape::API
+ class Groups < Grape::API::Instance
include PaginationParams
include Helpers::CustomAttributes
@@ -16,7 +16,7 @@ module API
params :group_list_params do
use :statistics_params
- optional :skip_groups, type: Array[Integer], desc: 'Array of group ids to exclude from list'
+ optional :skip_groups, type: Array[Integer], coerce_with: ::API::Validations::Types::CommaSeparatedToIntegerArray.coerce, desc: 'Array of group ids to exclude from list'
optional :all_available, type: Boolean, desc: 'Show all group that you have access to'
optional :search, type: String, desc: 'Search for a specific group'
optional :owned, type: Boolean, default: false, desc: 'Limit by owned by authenticated user'
@@ -76,9 +76,6 @@ module API
params: project_finder_params,
options: finder_options
).execute
- projects = projects.with_issues_available_for_user(current_user) if params[:with_issues_enabled]
- projects = projects.with_merge_requests_enabled if params[:with_merge_requests_enabled]
- projects = projects.visible_to_user_and_access_level(current_user, params[:min_access_level]) if params[:min_access_level]
projects = reorder_projects(projects)
paginate(projects)
end
@@ -221,7 +218,7 @@ module API
success Entities::Project
end
params do
- optional :archived, type: Boolean, default: false, desc: 'Limit by archived status'
+ optional :archived, type: Boolean, desc: 'Limit by archived status'
optional :visibility, type: String, values: Gitlab::VisibilityLevel.string_values,
desc: 'Limit by visibility'
optional :search, type: String, desc: 'Return list of authorized projects matching the search criteria'
@@ -258,7 +255,7 @@ module API
success Entities::Project
end
params do
- optional :archived, type: Boolean, default: false, desc: 'Limit by archived status'
+ optional :archived, type: Boolean, desc: 'Limit by archived status'
optional :visibility, type: String, values: Gitlab::VisibilityLevel.string_values,
desc: 'Limit by visibility'
optional :search, type: String, desc: 'Return list of authorized projects matching the search criteria'
diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb
index bbdb45da3b1..01b89959c14 100644
--- a/lib/api/helpers.rb
+++ b/lib/api/helpers.rb
@@ -41,6 +41,16 @@ module API
end
end
+ def job_token_authentication?
+ initial_current_user && @current_authenticated_job.present? # rubocop:disable Gitlab/ModuleWithInstanceVariables
+ end
+
+ # Returns the job associated with the token provided for
+ # authentication, if any
+ def current_authenticated_job
+ @current_authenticated_job
+ end
+
# rubocop:disable Gitlab/ModuleWithInstanceVariables
# We can't rewrite this with StrongMemoize because `sudo!` would
# actually write to `@current_user`, and `sudo?` would immediately
@@ -79,12 +89,6 @@ module API
@project ||= find_project!(params[:id])
end
- def wiki_page
- page = ProjectWiki.new(user_project, current_user).find_page(params[:slug])
-
- page || not_found!('Wiki Page')
- end
-
def available_labels_for(label_parent, include_ancestor_groups: true)
search_params = { include_ancestor_groups: include_ancestor_groups }
@@ -374,6 +378,12 @@ module API
render_api_error!(message.join(' '), 404)
end
+ def check_sha_param!(params, merge_request)
+ if params[:sha] && merge_request.diff_head_sha != params[:sha]
+ render_api_error!("SHA does not match HEAD of source branch: #{merge_request.diff_head_sha}", 409)
+ end
+ end
+
def unauthorized!
render_api_error!('401 Unauthorized', 401)
end
@@ -416,10 +426,14 @@ module API
def render_validation_error!(model)
if model.errors.any?
- render_api_error!(model.errors.messages || '400 Bad Request', 400)
+ render_api_error!(model_error_messages(model) || '400 Bad Request', 400)
end
end
+ def model_error_messages(model)
+ model.errors.messages
+ end
+
def render_spam_error!
render_api_error!({ error: 'Spam detected' }, 400)
end
@@ -490,7 +504,7 @@ module API
header['X-Sendfile'] = path
body
else
- file path
+ sendfile path
end
end
@@ -534,6 +548,8 @@ module API
def project_finder_params_ce
finder_params = project_finder_params_visibility_ce
+ finder_params[:with_issues_enabled] = true if params[:with_issues_enabled].present?
+ finder_params[:with_merge_requests_enabled] = true if params[:with_merge_requests_enabled].present?
finder_params[:without_deleted] = true
finder_params[:search] = params[:search] if params[:search]
finder_params[:search_namespaces] = true if params[:search_namespaces].present?
@@ -543,6 +559,7 @@ module API
finder_params[:id_before] = params[:id_before] if params[:id_before]
finder_params[:last_activity_after] = params[:last_activity_after] if params[:last_activity_after]
finder_params[:last_activity_before] = params[:last_activity_before] if params[:last_activity_before]
+ finder_params[:repository_storage] = params[:repository_storage] if params[:repository_storage]
finder_params
end
diff --git a/lib/api/helpers/common_helpers.rb b/lib/api/helpers/common_helpers.rb
index 32a15381f27..a44fd4b0a5b 100644
--- a/lib/api/helpers/common_helpers.rb
+++ b/lib/api/helpers/common_helpers.rb
@@ -12,6 +12,26 @@ module API
end
end
end
+
+ # Grape v1.3.3 no longer automatically coerces an Array
+ # type to an empty array if the value is nil.
+ def coerce_nil_params_to_array!
+ keys_to_coerce = params_with_array_types
+
+ params.each do |key, val|
+ params[key] = Array(val) if val.nil? && keys_to_coerce.include?(key)
+ end
+ end
+
+ def params_with_array_types
+ options[:route_options][:params].map do |key, val|
+ param_type = val[:type]
+ # Search for parameters with Array types (e.g. "[String]", "[Integer]", etc.)
+ if param_type =~ %r(\[\w*\])
+ key
+ end
+ end.compact.to_set
+ end
end
end
end
diff --git a/lib/api/helpers/internal_helpers.rb b/lib/api/helpers/internal_helpers.rb
index b05e82a541d..b69930b447c 100644
--- a/lib/api/helpers/internal_helpers.rb
+++ b/lib/api/helpers/internal_helpers.rb
@@ -118,8 +118,8 @@ module API
{
repository: repository.gitaly_repository,
- address: Gitlab::GitalyClient.address(container.repository_storage),
- token: Gitlab::GitalyClient.token(container.repository_storage),
+ address: Gitlab::GitalyClient.address(repository.shard),
+ token: Gitlab::GitalyClient.token(repository.shard),
features: Feature::Gitaly.server_feature_flags
}
end
diff --git a/lib/api/helpers/merge_requests_helpers.rb b/lib/api/helpers/merge_requests_helpers.rb
index 9dab2a88f0b..4d5350498a7 100644
--- a/lib/api/helpers/merge_requests_helpers.rb
+++ b/lib/api/helpers/merge_requests_helpers.rb
@@ -5,7 +5,30 @@ module API
module MergeRequestsHelpers
extend Grape::API::Helpers
+ params :merge_requests_negatable_params do
+ optional :author_id, type: Integer, desc: 'Return merge requests which are authored by the user with the given ID'
+ optional :author_username, type: String, desc: 'Return merge requests which are authored by the user with the given username'
+ mutually_exclusive :author_id, :author_username
+
+ optional :assignee_id,
+ types: [Integer, String],
+ integer_none_any: true,
+ desc: 'Return merge requests which are assigned to the user with the given ID'
+ optional :assignee_username, type: Array[String], check_assignees_count: true,
+ coerce_with: Validations::Validators::CheckAssigneesCount.coerce,
+ desc: 'Return merge requests which are assigned to the user with the given username'
+ mutually_exclusive :assignee_id, :assignee_username
+
+ optional :labels,
+ type: Array[String],
+ coerce_with: Validations::Types::CommaSeparatedToArray.coerce,
+ desc: 'Comma-separated list of label names'
+ optional :milestone, type: String, desc: 'Return merge requests for a specific milestone'
+ optional :my_reaction_emoji, type: String, desc: 'Return issues reacted by the authenticated user by the given emoji'
+ end
+
params :merge_requests_base_params do
+ use :merge_requests_negatable_params
optional :state,
type: String,
values: %w[opened closed locked merged all],
@@ -21,11 +44,6 @@ module API
values: %w[asc desc],
default: 'desc',
desc: 'Return merge requests sorted in `asc` or `desc` order.'
- optional :milestone, type: String, desc: 'Return merge requests for a specific milestone'
- optional :labels,
- type: Array[String],
- coerce_with: Validations::Types::LabelsList.coerce,
- desc: 'Comma-separated list of label names'
optional :with_labels_details, type: Boolean, desc: 'Return titles of labels and other details', default: false
optional :with_merge_status_recheck, type: Boolean, desc: 'Request that stale merge statuses be rechecked asynchronously', default: false
optional :created_after, type: DateTime, desc: 'Return merge requests created after the specified time'
@@ -37,19 +55,10 @@ module API
values: %w[simple],
desc: 'If simple, returns the `iid`, URL, title, description, and basic state of merge request'
- optional :author_id, type: Integer, desc: 'Return merge requests which are authored by the user with the given ID'
- optional :author_username, type: String, desc: 'Return merge requests which are authored by the user with the given username'
- mutually_exclusive :author_id, :author_username
-
- optional :assignee_id,
- types: [Integer, String],
- integer_none_any: true,
- desc: 'Return merge requests which are assigned to the user with the given ID'
optional :scope,
type: String,
values: %w[created-by-me assigned-to-me created_by_me assigned_to_me all],
desc: 'Return merge requests for the given scope: `created_by_me`, `assigned_to_me` or `all`'
- optional :my_reaction_emoji, type: String, desc: 'Return issues reacted by the authenticated user by the given emoji'
optional :source_branch, type: String, desc: 'Return merge requests with the given source branch'
optional :source_project_id, type: Integer, desc: 'Return merge requests with the given source project id'
optional :target_branch, type: String, desc: 'Return merge requests with the given target branch'
@@ -58,6 +67,9 @@ module API
desc: 'Search merge requests for text present in the title, description, or any combination of these'
optional :in, type: String, desc: '`title`, `description`, or a string joining them with comma'
optional :wip, type: String, values: %w[yes no], desc: 'Search merge requests for WIP in the title'
+ optional :not, type: Hash, desc: 'Parameters to negate' do
+ use :merge_requests_negatable_params
+ end
end
params :optional_scope_param do
diff --git a/lib/api/helpers/packages/basic_auth_helpers.rb b/lib/api/helpers/packages/basic_auth_helpers.rb
new file mode 100644
index 00000000000..835b5f4614c
--- /dev/null
+++ b/lib/api/helpers/packages/basic_auth_helpers.rb
@@ -0,0 +1,57 @@
+# frozen_string_literal: true
+
+module API
+ module Helpers
+ module Packages
+ module BasicAuthHelpers
+ module Constants
+ AUTHENTICATE_REALM_HEADER = 'Www-Authenticate: Basic realm'
+ AUTHENTICATE_REALM_NAME = 'GitLab Packages Registry'
+ end
+
+ include Constants
+
+ def find_personal_access_token
+ find_personal_access_token_from_http_basic_auth
+ end
+
+ def unauthorized_user_project
+ @unauthorized_user_project ||= find_project(params[:id])
+ end
+
+ def unauthorized_user_project!
+ unauthorized_user_project || not_found!
+ end
+
+ def authorized_user_project
+ @authorized_user_project ||= authorized_project_find!
+ end
+
+ def authorized_project_find!
+ project = unauthorized_user_project
+
+ unless project && can?(current_user, :read_project, project)
+ return unauthorized_or! { not_found! }
+ end
+
+ project
+ end
+
+ def authorize!(action, subject = :global, reason = nil)
+ return if can?(current_user, action, subject)
+
+ unauthorized_or! { forbidden!(reason) }
+ end
+
+ def unauthorized_or!
+ current_user ? yield : unauthorized_with_header!
+ end
+
+ def unauthorized_with_header!
+ header(AUTHENTICATE_REALM_HEADER, AUTHENTICATE_REALM_NAME)
+ unauthorized!
+ end
+ end
+ end
+ end
+end
diff --git a/lib/api/helpers/packages/conan/api_helpers.rb b/lib/api/helpers/packages/conan/api_helpers.rb
new file mode 100644
index 00000000000..30e690a5a1d
--- /dev/null
+++ b/lib/api/helpers/packages/conan/api_helpers.rb
@@ -0,0 +1,225 @@
+# frozen_string_literal: true
+
+module API
+ module Helpers
+ module Packages
+ module Conan
+ module ApiHelpers
+ def present_download_urls(entity)
+ authorize!(:read_package, project)
+
+ presenter = ::Packages::Conan::PackagePresenter.new(
+ recipe,
+ current_user,
+ project,
+ conan_package_reference: params[:conan_package_reference]
+ )
+
+ render_api_error!("No recipe manifest found", 404) if yield(presenter).empty?
+
+ present presenter, with: entity
+ end
+
+ def present_package_download_urls
+ present_download_urls(::API::Entities::ConanPackage::ConanPackageManifest, &:package_urls)
+ end
+
+ def present_recipe_download_urls
+ present_download_urls(::API::Entities::ConanPackage::ConanRecipeManifest, &:recipe_urls)
+ end
+
+ def recipe_upload_urls(file_names)
+ { upload_urls: Hash[
+ file_names.collect do |file_name|
+ [file_name, recipe_file_upload_url(file_name)]
+ end
+ ] }
+ end
+
+ def package_upload_urls(file_names)
+ { upload_urls: Hash[
+ file_names.collect do |file_name|
+ [file_name, package_file_upload_url(file_name)]
+ end
+ ] }
+ end
+
+ def package_file_upload_url(file_name)
+ expose_url(
+ api_v4_packages_conan_v1_files_package_path(
+ package_name: params[:package_name],
+ package_version: params[:package_version],
+ package_username: params[:package_username],
+ package_channel: params[:package_channel],
+ recipe_revision: '0',
+ conan_package_reference: params[:conan_package_reference],
+ package_revision: '0',
+ file_name: file_name
+ )
+ )
+ end
+
+ def recipe_file_upload_url(file_name)
+ expose_url(
+ api_v4_packages_conan_v1_files_export_path(
+ package_name: params[:package_name],
+ package_version: params[:package_version],
+ package_username: params[:package_username],
+ package_channel: params[:package_channel],
+ recipe_revision: '0',
+ file_name: file_name
+ )
+ )
+ end
+
+ def recipe
+ "%{package_name}/%{package_version}@%{package_username}/%{package_channel}" % params.symbolize_keys
+ end
+
+ def project
+ strong_memoize(:project) do
+ full_path = ::Packages::Conan::Metadatum.full_path_from(package_username: params[:package_username])
+ Project.find_by_full_path(full_path)
+ end
+ end
+
+ def package
+ strong_memoize(:package) do
+ project.packages
+ .with_name(params[:package_name])
+ .with_version(params[:package_version])
+ .with_conan_channel(params[:package_channel])
+ .order_created
+ .last
+ end
+ end
+
+ def token
+ strong_memoize(:token) do
+ token = nil
+ token = ::Gitlab::ConanToken.from_personal_access_token(access_token) if access_token
+ token = ::Gitlab::ConanToken.from_deploy_token(deploy_token_from_request) if deploy_token_from_request
+ token = ::Gitlab::ConanToken.from_job(find_job_from_token) if find_job_from_token
+ token
+ end
+ end
+
+ def download_package_file(file_type)
+ authorize!(:read_package, project)
+
+ package_file = ::Packages::Conan::PackageFileFinder
+ .new(
+ package,
+ params[:file_name].to_s,
+ conan_file_type: file_type,
+ conan_package_reference: params[:conan_package_reference]
+ ).execute!
+
+ track_event('pull_package') if params[:file_name] == ::Packages::Conan::FileMetadatum::PACKAGE_BINARY
+
+ present_carrierwave_file!(package_file.file)
+ end
+
+ def find_or_create_package
+ package || ::Packages::Conan::CreatePackageService.new(project, current_user, params).execute
+ end
+
+ def track_push_package_event
+ if params[:file_name] == ::Packages::Conan::FileMetadatum::PACKAGE_BINARY && params['file.size'] > 0
+ track_event('push_package')
+ end
+ end
+
+ def create_package_file_with_type(file_type, current_package)
+ unless params['file.size'] == 0
+ # conan sends two upload requests, the first has no file, so we skip record creation if file.size == 0
+ ::Packages::Conan::CreatePackageFileService.new(current_package, uploaded_package_file, params.merge(conan_file_type: file_type)).execute
+ end
+ end
+
+ def upload_package_file(file_type)
+ authorize_upload!(project)
+
+ current_package = find_or_create_package
+
+ track_push_package_event
+
+ create_package_file_with_type(file_type, current_package)
+ rescue ObjectStorage::RemoteStoreError => e
+ Gitlab::ErrorTracking.track_exception(e, file_name: params[:file_name], project_id: project.id)
+
+ forbidden!
+ end
+
+ def find_personal_access_token
+ personal_access_token = find_personal_access_token_from_conan_jwt ||
+ find_personal_access_token_from_http_basic_auth
+
+ personal_access_token
+ end
+
+ def find_user_from_job_token
+ return unless route_authentication_setting[:job_token_allowed]
+
+ job = find_job_from_token || raise(::Gitlab::Auth::UnauthorizedError)
+
+ job.user
+ end
+
+ def deploy_token_from_request
+ find_deploy_token_from_conan_jwt || find_deploy_token_from_http_basic_auth
+ end
+
+ def find_job_from_token
+ find_job_from_conan_jwt || find_job_from_http_basic_auth
+ end
+
+ # We need to override this one because it
+ # looks into Bearer authorization header
+ def find_oauth_access_token
+ end
+
+ def find_personal_access_token_from_conan_jwt
+ token = decode_oauth_token_from_jwt
+
+ return unless token
+
+ PersonalAccessToken.find_by_id_and_user_id(token.access_token_id, token.user_id)
+ end
+
+ def find_deploy_token_from_conan_jwt
+ token = decode_oauth_token_from_jwt
+
+ return unless token
+
+ deploy_token = DeployToken.active.find_by_token(token.access_token_id.to_s)
+ # note: uesr_id is not a user record id, but is the attribute set on ConanToken
+ return if token.user_id != deploy_token&.username
+
+ deploy_token
+ end
+
+ def find_job_from_conan_jwt
+ token = decode_oauth_token_from_jwt
+
+ return unless token
+
+ ::Ci::Build.find_by_token(token.access_token_id.to_s)
+ end
+
+ def decode_oauth_token_from_jwt
+ jwt = Doorkeeper::OAuth::Token.from_bearer_authorization(current_request)
+
+ return unless jwt
+
+ token = ::Gitlab::ConanToken.decode(jwt)
+
+ return unless token && token.access_token_id && token.user_id
+
+ token
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/api/helpers/packages/dependency_proxy_helpers.rb b/lib/api/helpers/packages/dependency_proxy_helpers.rb
new file mode 100644
index 00000000000..254af7690a2
--- /dev/null
+++ b/lib/api/helpers/packages/dependency_proxy_helpers.rb
@@ -0,0 +1,36 @@
+# frozen_string_literal: true
+
+module API
+ module Helpers
+ module Packages
+ module DependencyProxyHelpers
+ REGISTRY_BASE_URLS = {
+ npm: 'https://registry.npmjs.org/'
+ }.freeze
+
+ def redirect_registry_request(forward_to_registry, package_type, options)
+ if forward_to_registry && redirect_registry_request_available?
+ redirect(registry_url(package_type, options))
+ else
+ yield
+ end
+ end
+
+ def registry_url(package_type, options)
+ base_url = REGISTRY_BASE_URLS[package_type]
+
+ raise ArgumentError, "Can't build registry_url for package_type #{package_type}" unless base_url
+
+ case package_type
+ when :npm
+ "#{base_url}#{options[:package_name]}"
+ end
+ end
+
+ def redirect_registry_request_available?
+ ::Gitlab::CurrentSettings.current_application_settings.npm_package_requests_forwarding
+ end
+ end
+ end
+ end
+end
diff --git a/lib/api/helpers/packages_helpers.rb b/lib/api/helpers/packages_helpers.rb
new file mode 100644
index 00000000000..c6037d52de9
--- /dev/null
+++ b/lib/api/helpers/packages_helpers.rb
@@ -0,0 +1,52 @@
+# frozen_string_literal: true
+
+module API
+ module Helpers
+ module PackagesHelpers
+ MAX_PACKAGE_FILE_SIZE = 50.megabytes.freeze
+
+ def require_packages_enabled!
+ not_found! unless ::Gitlab.config.packages.enabled
+ end
+
+ def require_dependency_proxy_enabled!
+ not_found! unless ::Gitlab.config.dependency_proxy.enabled
+ end
+
+ def authorize_read_package!(subject = user_project)
+ authorize!(:read_package, subject)
+ end
+
+ def authorize_create_package!(subject = user_project)
+ authorize!(:create_package, subject)
+ end
+
+ def authorize_destroy_package!(subject = user_project)
+ authorize!(:destroy_package, subject)
+ end
+
+ def authorize_packages_access!(subject = user_project)
+ require_packages_enabled!
+ authorize_read_package!(subject)
+ end
+
+ def authorize_workhorse!(subject: user_project, has_length: true, maximum_size: MAX_PACKAGE_FILE_SIZE)
+ authorize_upload!(subject)
+
+ Gitlab::Workhorse.verify_api_request!(headers)
+
+ status 200
+ content_type Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE
+
+ params = { has_length: has_length }
+ params[:maximum_size] = maximum_size unless has_length
+ ::Packages::PackageFileUploader.workhorse_authorize(params)
+ end
+
+ def authorize_upload!(subject = user_project)
+ authorize_create_package!(subject)
+ require_gitlab_workhorse!
+ end
+ end
+ end
+end
diff --git a/lib/api/helpers/packages_manager_clients_helpers.rb b/lib/api/helpers/packages_manager_clients_helpers.rb
new file mode 100644
index 00000000000..7b5d0dd708d
--- /dev/null
+++ b/lib/api/helpers/packages_manager_clients_helpers.rb
@@ -0,0 +1,63 @@
+# frozen_string_literal: true
+
+module API
+ module Helpers
+ module PackagesManagerClientsHelpers
+ extend Grape::API::Helpers
+ include ::API::Helpers::PackagesHelpers
+
+ params :workhorse_upload_params do
+ optional 'file.path', type: String, desc: 'Path to locally stored body (generated by Workhorse)'
+ optional 'file.name', type: String, desc: 'Real filename as send in Content-Disposition (generated by Workhorse)'
+ optional 'file.type', type: String, desc: 'Real content type as send in Content-Type (generated by Workhorse)'
+ optional 'file.size', type: Integer, desc: 'Real size of file (generated by Workhorse)'
+ optional 'file.md5', type: String, desc: 'MD5 checksum of the file (generated by Workhorse)'
+ optional 'file.sha1', type: String, desc: 'SHA1 checksum of the file (generated by Workhorse)'
+ optional 'file.sha256', type: String, desc: 'SHA256 checksum of the file (generated by Workhorse)'
+ end
+
+ def find_personal_access_token_from_http_basic_auth
+ return unless headers
+
+ token = decode_token
+
+ return unless token
+
+ PersonalAccessToken.find_by_token(token)
+ end
+
+ def find_job_from_http_basic_auth
+ return unless headers
+
+ token = decode_token
+
+ return unless token
+
+ ::Ci::Build.find_by_token(token)
+ end
+
+ def find_deploy_token_from_http_basic_auth
+ return unless headers
+
+ token = decode_token
+
+ return unless token
+
+ DeployToken.active.find_by_token(token)
+ end
+
+ def uploaded_package_file(param_name = :file)
+ uploaded_file = UploadedFile.from_params(params, param_name, ::Packages::PackageFileUploader.workhorse_local_upload_path)
+ bad_request!('Missing package file!') unless uploaded_file
+ uploaded_file
+ end
+
+ private
+
+ def decode_token
+ encoded_credentials = headers['Authorization'].to_s.split('Basic ', 2).second
+ Base64.decode64(encoded_credentials || '').split(':', 2).second
+ end
+ end
+ end
+end
diff --git a/lib/api/helpers/projects_helpers.rb b/lib/api/helpers/projects_helpers.rb
index 8a115d42929..76e5bb95c4d 100644
--- a/lib/api/helpers/projects_helpers.rb
+++ b/lib/api/helpers/projects_helpers.rb
@@ -6,6 +6,8 @@ module API
extend ActiveSupport::Concern
extend Grape::API::Helpers
+ STATISTICS_SORT_PARAMS = %w[storage_size repository_size wiki_size].freeze
+
params :optional_project_params_ce do
optional :description, type: String, desc: 'The description of the project'
optional :build_git_strategy, type: String, values: %w(fetch clone), desc: 'The Git strategy. Defaults to `fetch`'
@@ -13,6 +15,7 @@ module API
optional :auto_cancel_pending_pipelines, type: String, values: %w(disabled enabled), desc: 'Auto-cancel pending pipelines'
optional :build_coverage_regex, type: String, desc: 'Test coverage parsing'
optional :ci_config_path, type: String, desc: 'The path to CI config file. Defaults to `.gitlab-ci.yml`'
+ optional :service_desk_enabled, type: Boolean, desc: 'Disable or enable the service desk'
# TODO: remove in API v5, replaced by *_access_level
optional :issues_enabled, type: Boolean, desc: 'Flag indication if the issue tracker is enabled'
@@ -46,7 +49,7 @@ module API
optional :only_allow_merge_if_pipeline_succeeds, type: Boolean, desc: 'Only allow to merge if builds succeed'
optional :allow_merge_on_skipped_pipeline, type: Boolean, desc: 'Allow to merge if pipeline is skipped'
optional :only_allow_merge_if_all_discussions_are_resolved, type: Boolean, desc: 'Only allow to merge if all discussions are resolved'
- optional :tag_list, type: Array[String], desc: 'The list of tags for a project'
+ optional :tag_list, type: Array[String], coerce_with: ::API::Validations::Types::CommaSeparatedToArray.coerce, desc: 'The list of tags for a project'
# TODO: remove rubocop disable - https://gitlab.com/gitlab-org/gitlab/issues/14960
optional :avatar, type: File, desc: 'Avatar image for project' # rubocop:disable Scalability/FileUploads
optional :printing_merge_request_link_enabled, type: Boolean, desc: 'Show link to create/view merge request when pushing from the command line'
@@ -134,6 +137,7 @@ module API
:suggestion_commit_message,
:repository_storage,
:compliance_framework_setting,
+ :service_desk_enabled,
# TODO: remove in API v5, replaced by *_access_level
:issues_enabled,
diff --git a/lib/api/helpers/runner.rb b/lib/api/helpers/runner.rb
index 293d7ed9a6a..34a2fb09875 100644
--- a/lib/api/helpers/runner.rb
+++ b/lib/api/helpers/runner.rb
@@ -60,7 +60,7 @@ module API
def current_job
strong_memoize(:current_job) do
- Ci::Build.find_by_id(params[:id])
+ ::Ci::Build.find_by_id(params[:id])
end
end
@@ -69,11 +69,6 @@ module API
token && job.valid_token?(token)
end
- def max_artifacts_size(job)
- max_size = job.project.closest_setting(:max_artifacts_size)
- max_size.megabytes.to_i
- end
-
def job_forbidden!(job, reason)
header 'Job-Status', job.status
forbidden!(reason)
diff --git a/lib/api/helpers/services_helpers.rb b/lib/api/helpers/services_helpers.rb
index 3d6039cacaa..d4870b96575 100644
--- a/lib/api/helpers/services_helpers.rb
+++ b/lib/api/helpers/services_helpers.rb
@@ -234,18 +234,6 @@ module API
name: :project_url,
type: String,
desc: 'Project URL'
- },
- {
- required: false,
- name: :description,
- type: String,
- desc: 'Description'
- },
- {
- required: false,
- name: :title,
- type: String,
- desc: 'Title'
}
],
'buildkite' => [
@@ -288,6 +276,14 @@ module API
desc: 'Campfire room'
}
],
+ 'confluence' => [
+ {
+ required: true,
+ name: :confluence_url,
+ type: String,
+ desc: 'The URL of the Confluence Cloud Workspace hosted on atlassian.net'
+ }
+ ],
'custom-issue-tracker' => [
{
required: true,
@@ -306,18 +302,6 @@ module API
name: :project_url,
type: String,
desc: 'Project URL'
- },
- {
- required: false,
- name: :description,
- type: String,
- desc: 'Description'
- },
- {
- required: false,
- name: :title,
- type: String,
- desc: 'Title'
}
],
'discord' => [
@@ -757,6 +741,7 @@ module API
::BambooService,
::BugzillaService,
::BuildkiteService,
+ ::ConfluenceService,
::CampfireService,
::CustomIssueTrackerService,
::DiscordService,
diff --git a/lib/api/helpers/snippets_helpers.rb b/lib/api/helpers/snippets_helpers.rb
index 20aeca6a9d3..f95d066bd7c 100644
--- a/lib/api/helpers/snippets_helpers.rb
+++ b/lib/api/helpers/snippets_helpers.rb
@@ -3,15 +3,37 @@
module API
module Helpers
module SnippetsHelpers
+ extend Grape::API::Helpers
+
+ params :raw_file_params do
+ requires :file_path, type: String, file_path: true, desc: 'The url encoded path to the file, e.g. lib%2Fclass%2Erb'
+ requires :ref, type: String, desc: 'The name of branch, tag or commit'
+ end
+
def content_for(snippet)
if snippet.empty_repo?
+ env['api.format'] = :txt
+ content_type 'text/plain'
+ header['Content-Disposition'] = 'attachment'
+
snippet.content
else
blob = snippet.blobs.first
- blob.load_all_data!
- blob.data
+
+ send_git_blob(blob.repository, blob)
end
end
+
+ def file_content_for(snippet)
+ repo = snippet.repository
+ commit = repo.commit(params[:ref])
+ not_found!('Reference') unless commit
+
+ blob = repo.blob_at(commit.sha, params[:file_path])
+ not_found!('File') unless blob
+
+ send_git_blob(repo, blob)
+ end
end
end
end
diff --git a/lib/api/helpers/users_helpers.rb b/lib/api/helpers/users_helpers.rb
index 99eefc1cbb9..2d7b22e66b3 100644
--- a/lib/api/helpers/users_helpers.rb
+++ b/lib/api/helpers/users_helpers.rb
@@ -11,6 +11,13 @@ module API
params :optional_index_params_ee do
end
+
+ def model_error_messages(model)
+ super.tap do |error_messages|
+ # Remapping errors from nested associations.
+ error_messages[:bio] = error_messages.delete(:"user_detail.bio") if error_messages.has_key?(:"user_detail.bio")
+ end
+ end
end
end
end
diff --git a/lib/api/helpers/wikis_helpers.rb b/lib/api/helpers/wikis_helpers.rb
new file mode 100644
index 00000000000..49da1e317ab
--- /dev/null
+++ b/lib/api/helpers/wikis_helpers.rb
@@ -0,0 +1,35 @@
+# frozen_string_literal: true
+
+module API
+ module Helpers
+ module WikisHelpers
+ def self.wiki_resource_kinds
+ [:projects]
+ end
+
+ def find_container(kind)
+ return user_project if kind == :projects
+
+ raise "Unknown wiki container #{kind}"
+ end
+
+ def wiki_page
+ Wiki.for_container(container, current_user).find_page(params[:slug]) || not_found!('Wiki Page')
+ end
+
+ def commit_params(attrs)
+ base_params = { branch_name: attrs[:branch] }
+ file_details = case attrs[:file]
+ when Hash # legacy format: TODO remove when we drop support for non accelerated uploads
+ { file_name: attrs[:file][:filename], file_content: attrs[:file][:tempfile].read }
+ else
+ { file_name: attrs[:file].original_filename, file_content: attrs[:file].read }
+ end
+
+ base_params.merge(file_details)
+ end
+ end
+ end
+end
+
+API::Helpers::WikisHelpers.prepend_if_ee('EE::API::Helpers::WikisHelpers')
diff --git a/lib/api/import_bitbucket_server.rb b/lib/api/import_bitbucket_server.rb
new file mode 100644
index 00000000000..df3235420e9
--- /dev/null
+++ b/lib/api/import_bitbucket_server.rb
@@ -0,0 +1,44 @@
+# frozen_string_literal: true
+
+module API
+ class ImportBitbucketServer < Grape::API::Instance
+ helpers do
+ def client
+ @client ||= BitbucketServer::Client.new(credentials)
+ end
+
+ def credentials
+ @credentials ||= {
+ base_uri: params[:bitbucket_server_url],
+ user: params[:bitbucket_server_username],
+ password: params[:personal_access_token]
+ }
+ end
+ end
+
+ desc 'Import a BitBucket Server repository' do
+ detail 'This feature was introduced in GitLab 13.2.'
+ success ::ProjectEntity
+ end
+
+ params do
+ requires :bitbucket_server_url, type: String, desc: 'Bitbucket Server URL'
+ requires :bitbucket_server_username, type: String, desc: 'BitBucket Server Username'
+ requires :personal_access_token, type: String, desc: 'BitBucket Server personal access token/password'
+ requires :bitbucket_server_project, type: String, desc: 'BitBucket Server Project Key'
+ requires :bitbucket_server_repo, type: String, desc: 'BitBucket Server Repository Name'
+ optional :new_name, type: String, desc: 'New repo name'
+ optional :new_namespace, type: String, desc: 'Namespace to import repo into'
+ end
+
+ post 'import/bitbucket_server' do
+ result = Import::BitbucketServerService.new(client, current_user, params).execute(credentials)
+
+ if result[:status] == :success
+ present ProjectSerializer.new.represent(result[:project], serializer: :import)
+ else
+ render_api_error!({ error: result[:message] }, result[:http_status])
+ end
+ end
+ end
+end
diff --git a/lib/api/import_github.rb b/lib/api/import_github.rb
index f31cc15dc62..1e839816006 100644
--- a/lib/api/import_github.rb
+++ b/lib/api/import_github.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module API
- class ImportGithub < Grape::API
+ class ImportGithub < Grape::API::Instance
rescue_from Octokit::Unauthorized, with: :provider_unauthorized
before do
diff --git a/lib/api/internal/base.rb b/lib/api/internal/base.rb
index 79c407b9581..6d4a4fc9c8b 100644
--- a/lib/api/internal/base.rb
+++ b/lib/api/internal/base.rb
@@ -3,7 +3,7 @@
module API
# Internal access API
module Internal
- class Base < Grape::API
+ class Base < Grape::API::Instance
before { authenticate_by_gitlab_shell_token! }
before do
@@ -63,15 +63,13 @@ module API
gl_project_path: gl_repository_path,
gl_id: Gitlab::GlId.gl_id(actor.user),
gl_username: actor.username,
- git_config_options: [],
+ git_config_options: ["uploadpack.allowFilter=true",
+ "uploadpack.allowAnySHA1InWant=true"],
gitaly: gitaly_payload(params[:action]),
gl_console_messages: check_result.console_messages
}
# Custom option for git-receive-pack command
- if Feature.enabled?(:gitaly_upload_pack_filter, project, default_enabled: true)
- payload[:git_config_options] << "uploadpack.allowFilter=true" << "uploadpack.allowAnySHA1InWant=true"
- end
receive_max_input_size = Gitlab::CurrentSettings.receive_max_input_size.to_i
diff --git a/lib/api/internal/pages.rb b/lib/api/internal/pages.rb
index 6c8da414e4d..5f8d23f15fa 100644
--- a/lib/api/internal/pages.rb
+++ b/lib/api/internal/pages.rb
@@ -3,7 +3,7 @@
module API
# Pages Internal API
module Internal
- class Pages < Grape::API
+ class Pages < Grape::API::Instance
before do
authenticate_gitlab_pages_request!
end
diff --git a/lib/api/issues.rb b/lib/api/issues.rb
index 2374ac11f4a..455511caabb 100644
--- a/lib/api/issues.rb
+++ b/lib/api/issues.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module API
- class Issues < Grape::API
+ class Issues < Grape::API::Instance
include PaginationParams
helpers Helpers::IssuesHelpers
helpers Helpers::RateLimiter
@@ -10,9 +10,9 @@ module API
helpers do
params :negatable_issue_filter_params do
- optional :labels, type: Array[String], coerce_with: Validations::Types::LabelsList.coerce, desc: 'Comma-separated list of label names'
+ optional :labels, type: Array[String], coerce_with: ::API::Validations::Types::CommaSeparatedToArray.coerce, desc: 'Comma-separated list of label names'
optional :milestone, type: String, desc: 'Milestone title'
- optional :iids, type: Array[Integer], desc: 'The IID array of issues'
+ optional :iids, type: Array[Integer], coerce_with: ::API::Validations::Types::CommaSeparatedToIntegerArray.coerce, desc: 'The IID array of issues'
optional :search, type: String, desc: 'Search issues for text present in the title, description, or any combination of these'
optional :in, type: String, desc: '`title`, `description`, or a string joining them with comma'
@@ -62,12 +62,12 @@ module API
params :issue_params do
optional :description, type: String, desc: 'The description of an issue'
- optional :assignee_ids, type: Array[Integer], desc: 'The array of user IDs to assign issue'
+ optional :assignee_ids, type: Array[Integer], coerce_with: ::API::Validations::Types::CommaSeparatedToIntegerArray.coerce, desc: 'The array of user IDs to assign issue'
optional :assignee_id, type: Integer, desc: '[Deprecated] The ID of a user to assign issue'
optional :milestone_id, type: Integer, desc: 'The ID of a milestone to assign issue'
- optional :labels, type: Array[String], coerce_with: Validations::Types::LabelsList.coerce, desc: 'Comma-separated list of label names'
- optional :add_labels, type: Array[String], coerce_with: Validations::Types::LabelsList.coerce, desc: 'Comma-separated list of label names'
- optional :remove_labels, type: Array[String], coerce_with: Validations::Types::LabelsList.coerce, desc: 'Comma-separated list of label names'
+ optional :labels, type: Array[String], coerce_with: ::API::Validations::Types::CommaSeparatedToArray.coerce, desc: 'Comma-separated list of label names'
+ optional :add_labels, type: Array[String], coerce_with: ::API::Validations::Types::CommaSeparatedToArray.coerce, desc: 'Comma-separated list of label names'
+ optional :remove_labels, type: Array[String], coerce_with: ::API::Validations::Types::CommaSeparatedToArray.coerce, desc: 'Comma-separated list of label names'
optional :due_date, type: String, desc: 'Date string in the format YEAR-MONTH-DAY'
optional :confidential, type: Boolean, desc: 'Boolean parameter if the issue should be confidential'
optional :discussion_locked, type: Boolean, desc: " Boolean parameter indicating if the issue's discussion is locked"
@@ -107,7 +107,6 @@ module API
with: Entities::Issue,
with_labels_details: declared_params[:with_labels_details],
current_user: current_user,
- issuable_metadata: Gitlab::IssuableMetadata.new(current_user, issues).data,
include_subscribed: false
}
@@ -133,7 +132,6 @@ module API
with: Entities::Issue,
with_labels_details: declared_params[:with_labels_details],
current_user: current_user,
- issuable_metadata: Gitlab::IssuableMetadata.new(current_user, issues).data,
include_subscribed: false,
group: user_group
}
@@ -170,7 +168,6 @@ module API
with_labels_details: declared_params[:with_labels_details],
current_user: current_user,
project: user_project,
- issuable_metadata: Gitlab::IssuableMetadata.new(current_user, issues).data,
include_subscribed: false
}
@@ -289,6 +286,30 @@ module API
end
# rubocop: enable CodeReuse/ActiveRecord
+ desc 'Reorder an existing issue' do
+ success Entities::Issue
+ end
+ params do
+ requires :issue_iid, type: Integer, desc: 'The internal ID of a project issue'
+ optional :move_after_id, type: Integer, desc: 'The ID of the issue we want to be after'
+ optional :move_before_id, type: Integer, desc: 'The ID of the issue we want to be before'
+ at_least_one_of :move_after_id, :move_before_id
+ end
+ # rubocop: disable CodeReuse/ActiveRecord
+ put ':id/issues/:issue_iid/reorder' do
+ issue = user_project.issues.find_by(iid: params[:issue_iid])
+ not_found!('Issue') unless issue
+
+ authorize! :update_issue, issue
+
+ if ::Issues::ReorderService.new(user_project, current_user, params).execute(issue)
+ present issue, with: Entities::Issue, current_user: current_user, project: user_project
+ else
+ render_api_error!({ error: 'Unprocessable Entity' }, 422)
+ end
+ end
+ # rubocop: enable CodeReuse/ActiveRecord
+
desc 'Move an existing issue' do
success Entities::Issue
end
diff --git a/lib/api/job_artifacts.rb b/lib/api/job_artifacts.rb
index 6a82256cc96..61c279a76e9 100644
--- a/lib/api/job_artifacts.rb
+++ b/lib/api/job_artifacts.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module API
- class JobArtifacts < Grape::API
+ class JobArtifacts < Grape::API::Instance
before { authenticate_non_get! }
# EE::API::JobArtifacts would override the following helpers
diff --git a/lib/api/jobs.rb b/lib/api/jobs.rb
index 61a7fc107ef..bcc00429dd6 100644
--- a/lib/api/jobs.rb
+++ b/lib/api/jobs.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module API
- class Jobs < Grape::API
+ class Jobs < Grape::API::Instance
include PaginationParams
before { authenticate! }
@@ -160,7 +160,7 @@ module API
authorize!(:update_build, build)
break forbidden!('Job is not retryable') unless build.retryable?
- build = Ci::Build.retry(build, current_user)
+ build = ::Ci::Build.retry(build, current_user)
present build, with: Entities::Job
end
diff --git a/lib/api/keys.rb b/lib/api/keys.rb
index b730e027063..c014641ca04 100644
--- a/lib/api/keys.rb
+++ b/lib/api/keys.rb
@@ -2,7 +2,7 @@
module API
# Keys API
- class Keys < Grape::API
+ class Keys < Grape::API::Instance
before { authenticate! }
resource :keys do
diff --git a/lib/api/labels.rb b/lib/api/labels.rb
index 2b283d82e4a..edf4a8ca14e 100644
--- a/lib/api/labels.rb
+++ b/lib/api/labels.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module API
- class Labels < Grape::API
+ class Labels < Grape::API::Instance
include PaginationParams
helpers ::API::Helpers::LabelHelpers
diff --git a/lib/api/lint.rb b/lib/api/lint.rb
index a7672021db0..f7796b1e969 100644
--- a/lib/api/lint.rb
+++ b/lib/api/lint.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module API
- class Lint < Grape::API
+ class Lint < Grape::API::Instance
namespace :ci do
desc 'Validation of .gitlab-ci.yml content'
params do
diff --git a/lib/api/markdown.rb b/lib/api/markdown.rb
index de77bef43ce..a0822271cca 100644
--- a/lib/api/markdown.rb
+++ b/lib/api/markdown.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module API
- class Markdown < Grape::API
+ class Markdown < Grape::API::Instance
params do
requires :text, type: String, desc: "The markdown text to render"
optional :gfm, type: Boolean, desc: "Render text using GitLab Flavored Markdown"
diff --git a/lib/api/maven_packages.rb b/lib/api/maven_packages.rb
new file mode 100644
index 00000000000..32a45c59cfa
--- /dev/null
+++ b/lib/api/maven_packages.rb
@@ -0,0 +1,251 @@
+# frozen_string_literal: true
+module API
+ class MavenPackages < Grape::API::Instance
+ MAVEN_ENDPOINT_REQUIREMENTS = {
+ file_name: API::NO_SLASH_URL_PART_REGEX
+ }.freeze
+
+ content_type :md5, 'text/plain'
+ content_type :sha1, 'text/plain'
+ content_type :binary, 'application/octet-stream'
+
+ rescue_from ActiveRecord::RecordInvalid do |e|
+ render_api_error!(e.message, 400)
+ end
+
+ before do
+ require_packages_enabled!
+ authenticate_non_get!
+ end
+
+ helpers ::API::Helpers::PackagesHelpers
+
+ helpers do
+ def extract_format(file_name)
+ name, _, format = file_name.rpartition('.')
+
+ if %w(md5 sha1).include?(format)
+ [name, format]
+ else
+ [file_name, format]
+ end
+ end
+
+ def verify_package_file(package_file, uploaded_file)
+ stored_sha1 = Digest::SHA256.hexdigest(package_file.file_sha1)
+ expected_sha1 = uploaded_file.sha256
+
+ if stored_sha1 == expected_sha1
+ no_content!
+ else
+ conflict!
+ end
+ end
+
+ def find_project_by_path(path)
+ project_path = path.rpartition('/').first
+ Project.find_by_full_path(project_path)
+ end
+
+ def jar_file?(format)
+ format == 'jar'
+ end
+
+ def present_carrierwave_file_with_head_support!(file, supports_direct_download: true)
+ if head_request_on_aws_file?(file, supports_direct_download) && !file.file_storage?
+ return redirect(signed_head_url(file))
+ end
+
+ present_carrierwave_file!(file, supports_direct_download: supports_direct_download)
+ end
+
+ def signed_head_url(file)
+ fog_storage = ::Fog::Storage.new(file.fog_credentials)
+ fog_dir = fog_storage.directories.new(key: file.fog_directory)
+ fog_file = fog_dir.files.new(key: file.path)
+ expire_at = ::Fog::Time.now + file.fog_authenticated_url_expiration
+
+ fog_file.collection.head_url(fog_file.key, expire_at)
+ end
+
+ def head_request_on_aws_file?(file, supports_direct_download)
+ Gitlab.config.packages.object_store.enabled &&
+ supports_direct_download &&
+ file.class.direct_download_enabled? &&
+ request.head? &&
+ file.fog_credentials[:provider] == 'AWS'
+ end
+ end
+
+ desc 'Download the maven package file at instance level' do
+ detail 'This feature was introduced in GitLab 11.6'
+ end
+ params do
+ requires :path, type: String, desc: 'Package path'
+ requires :file_name, type: String, desc: 'Package file name'
+ end
+ route_setting :authentication, job_token_allowed: true, deploy_token_allowed: true
+ get 'packages/maven/*path/:file_name', requirements: MAVEN_ENDPOINT_REQUIREMENTS do
+ file_name, format = extract_format(params[:file_name])
+
+ # To avoid name collision we require project path and project package be the same.
+ # For packages that have different name from the project we should use
+ # the endpoint that includes project id
+ project = find_project_by_path(params[:path])
+
+ authorize_read_package!(project)
+
+ package = ::Packages::Maven::PackageFinder
+ .new(params[:path], current_user, project: project).execute!
+
+ package_file = ::Packages::PackageFileFinder
+ .new(package, file_name).execute!
+
+ case format
+ when 'md5'
+ package_file.file_md5
+ when 'sha1'
+ package_file.file_sha1
+ else
+ track_event('pull_package') if jar_file?(format)
+ present_carrierwave_file_with_head_support!(package_file.file)
+ end
+ end
+
+ desc 'Download the maven package file at a group level' do
+ detail 'This feature was introduced in GitLab 11.7'
+ end
+ params do
+ requires :id, type: String, desc: 'The ID of a group'
+ end
+ resource :groups, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
+ params do
+ requires :path, type: String, desc: 'Package path'
+ requires :file_name, type: String, desc: 'Package file name'
+ end
+ route_setting :authentication, job_token_allowed: true, deploy_token_allowed: true
+ get ':id/-/packages/maven/*path/:file_name', requirements: MAVEN_ENDPOINT_REQUIREMENTS do
+ file_name, format = extract_format(params[:file_name])
+
+ group = find_group(params[:id])
+
+ not_found!('Group') unless can?(current_user, :read_group, group)
+
+ package = ::Packages::Maven::PackageFinder
+ .new(params[:path], current_user, group: group).execute!
+
+ authorize_read_package!(package.project)
+
+ package_file = ::Packages::PackageFileFinder
+ .new(package, file_name).execute!
+
+ case format
+ when 'md5'
+ package_file.file_md5
+ when 'sha1'
+ package_file.file_sha1
+ else
+ track_event('pull_package') if jar_file?(format)
+
+ present_carrierwave_file_with_head_support!(package_file.file)
+ end
+ end
+ end
+
+ params do
+ requires :id, type: String, desc: 'The ID of a project'
+ end
+ resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
+ desc 'Download the maven package file' do
+ detail 'This feature was introduced in GitLab 11.3'
+ end
+ params do
+ requires :path, type: String, desc: 'Package path'
+ requires :file_name, type: String, desc: 'Package file name'
+ end
+ route_setting :authentication, job_token_allowed: true, deploy_token_allowed: true
+ get ':id/packages/maven/*path/:file_name', requirements: MAVEN_ENDPOINT_REQUIREMENTS do
+ authorize_read_package!(user_project)
+
+ file_name, format = extract_format(params[:file_name])
+
+ package = ::Packages::Maven::PackageFinder
+ .new(params[:path], current_user, project: user_project).execute!
+
+ package_file = ::Packages::PackageFileFinder
+ .new(package, file_name).execute!
+
+ case format
+ when 'md5'
+ package_file.file_md5
+ when 'sha1'
+ package_file.file_sha1
+ else
+ track_event('pull_package') if jar_file?(format)
+
+ present_carrierwave_file_with_head_support!(package_file.file)
+ end
+ end
+
+ desc 'Workhorse authorize the maven package file upload' do
+ detail 'This feature was introduced in GitLab 11.3'
+ end
+ params do
+ requires :path, type: String, desc: 'Package path'
+ requires :file_name, type: String, desc: 'Package file name', regexp: Gitlab::Regex.maven_file_name_regex
+ end
+ route_setting :authentication, job_token_allowed: true, deploy_token_allowed: true
+ put ':id/packages/maven/*path/:file_name/authorize', requirements: MAVEN_ENDPOINT_REQUIREMENTS do
+ authorize_upload!
+
+ status 200
+ content_type Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE
+ ::Packages::PackageFileUploader.workhorse_authorize(has_length: true)
+ end
+
+ desc 'Upload the maven package file' do
+ detail 'This feature was introduced in GitLab 11.3'
+ end
+ params do
+ requires :path, type: String, desc: 'Package path'
+ requires :file_name, type: String, desc: 'Package file name', regexp: Gitlab::Regex.maven_file_name_regex
+ requires :file, type: ::API::Validations::Types::WorkhorseFile, desc: 'The package file to be published (generated by Multipart middleware)'
+ end
+ route_setting :authentication, job_token_allowed: true, deploy_token_allowed: true
+ put ':id/packages/maven/*path/:file_name', requirements: MAVEN_ENDPOINT_REQUIREMENTS do
+ authorize_upload!
+
+ file_name, format = extract_format(params[:file_name])
+
+ package = ::Packages::Maven::FindOrCreatePackageService
+ .new(user_project, current_user, params.merge(build: current_authenticated_job)).execute
+
+ case format
+ when 'sha1'
+ # After uploading a file, Maven tries to upload a sha1 and md5 version of it.
+ # Since we store md5/sha1 in database we simply need to validate our hash
+ # against one uploaded by Maven. We do this for `sha1` format.
+ package_file = ::Packages::PackageFileFinder
+ .new(package, file_name).execute!
+
+ verify_package_file(package_file, params[:file])
+ when 'md5'
+ nil
+ else
+ track_event('push_package') if jar_file?(format)
+
+ file_params = {
+ file: params[:file],
+ size: params['file.size'],
+ file_name: file_name,
+ file_type: params['file.type'],
+ file_sha1: params['file.sha1'],
+ file_md5: params['file.md5']
+ }
+
+ ::Packages::CreatePackageFileService.new(package, file_params).execute
+ end
+ end
+ end
+ end
+end
diff --git a/lib/api/members.rb b/lib/api/members.rb
index 37d4ca29b68..4edf94c6350 100644
--- a/lib/api/members.rb
+++ b/lib/api/members.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module API
- class Members < Grape::API
+ class Members < Grape::API::Instance
include PaginationParams
before { authenticate! }
@@ -18,7 +18,7 @@ module API
end
params do
optional :query, type: String, desc: 'A query string to search for members'
- optional :user_ids, type: Array[Integer], desc: 'Array of user ids to look up for membership'
+ optional :user_ids, type: Array[Integer], coerce_with: ::API::Validations::Types::CommaSeparatedToIntegerArray.coerce, desc: 'Array of user ids to look up for membership'
optional :show_seat_info, type: Boolean, desc: 'Show seat information for members'
use :optional_filter_params_ee
use :pagination
@@ -37,7 +37,7 @@ module API
end
params do
optional :query, type: String, desc: 'A query string to search for members'
- optional :user_ids, type: Array[Integer], desc: 'Array of user ids to look up for membership'
+ optional :user_ids, type: Array[Integer], coerce_with: ::API::Validations::Types::CommaSeparatedToIntegerArray.coerce, desc: 'Array of user ids to look up for membership'
optional :show_seat_info, type: Boolean, desc: 'Show seat information for members'
use :pagination
end
@@ -107,7 +107,7 @@ module API
if !member
not_allowed! # This currently can only be reached in EE
- elsif member.persisted? && member.valid?
+ elsif member.valid? && member.persisted?
present_members(member)
else
render_validation_error!(member)
@@ -145,6 +145,8 @@ module API
desc 'Removes a user from a group or project.'
params do
requires :user_id, type: Integer, desc: 'The user ID of the member'
+ optional :unassign_issuables, type: Boolean, default: false,
+ desc: 'Flag indicating if the removed member should be unassigned from any issues or merge requests within given group or project'
end
# rubocop: disable CodeReuse/ActiveRecord
delete ":id/members/:user_id" do
@@ -152,7 +154,7 @@ module API
member = source.members.find_by!(user_id: params[:user_id])
destroy_conditionally!(member) do
- ::Members::DestroyService.new(current_user).execute(member)
+ ::Members::DestroyService.new(current_user).execute(member, unassign_issuables: params[:unassign_issuables])
end
end
# rubocop: enable CodeReuse/ActiveRecord
diff --git a/lib/api/merge_request_approvals.rb b/lib/api/merge_request_approvals.rb
new file mode 100644
index 00000000000..035ed9f0e04
--- /dev/null
+++ b/lib/api/merge_request_approvals.rb
@@ -0,0 +1,78 @@
+# frozen_string_literal: true
+
+module API
+ class MergeRequestApprovals < ::Grape::API::Instance
+ before { authenticate_non_get! }
+
+ helpers do
+ params :ee_approval_params do
+ end
+
+ def present_approval(merge_request)
+ present merge_request, with: ::API::Entities::MergeRequestApprovals, current_user: current_user
+ end
+ end
+
+ resource :projects, requirements: ::API::API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
+ segment ':id/merge_requests/:merge_request_iid' do
+ # Get the status of the merge request's approvals
+ #
+ # Parameters:
+ # id (required) - The ID of a project
+ # merge_request_iid (required) - IID of MR
+ # Examples:
+ # GET /projects/:id/merge_requests/:merge_request_iid/approvals
+ desc 'List approvals for merge request'
+ get 'approvals' do
+ merge_request = find_merge_request_with_access(params[:merge_request_iid])
+
+ present_approval(merge_request)
+ end
+
+ # Approve a merge request
+ #
+ # Parameters:
+ # id (required) - The ID of a project
+ # merge_request_iid (required) - IID of MR
+ # Examples:
+ # POST /projects/:id/merge_requests/:merge_request_iid/approve
+ #
+ desc 'Approve a merge request'
+ params do
+ optional :sha, type: String, desc: 'When present, must have the HEAD SHA of the source branch'
+
+ use :ee_approval_params
+ end
+ post 'approve' do
+ merge_request = find_merge_request_with_access(params[:merge_request_iid], :approve_merge_request)
+
+ check_sha_param!(params, merge_request)
+
+ success =
+ ::MergeRequests::ApprovalService
+ .new(user_project, current_user, params)
+ .execute(merge_request)
+
+ unauthorized! unless success
+
+ present_approval(merge_request)
+ end
+
+ desc 'Remove an approval from a merge request'
+ post 'unapprove' do
+ merge_request = find_merge_request_with_access(params[:merge_request_iid], :approve_merge_request)
+
+ success = ::MergeRequests::RemoveApprovalService
+ .new(user_project, current_user)
+ .execute(merge_request)
+
+ not_found! unless success
+
+ present_approval(merge_request)
+ end
+ end
+ end
+ end
+end
+
+API::MergeRequestApprovals.prepend_if_ee('EE::API::MergeRequestApprovals')
diff --git a/lib/api/merge_request_diffs.rb b/lib/api/merge_request_diffs.rb
index 6ad30aa56e0..3e43fe8b257 100644
--- a/lib/api/merge_request_diffs.rb
+++ b/lib/api/merge_request_diffs.rb
@@ -2,7 +2,7 @@
module API
# MergeRequestDiff API
- class MergeRequestDiffs < Grape::API
+ class MergeRequestDiffs < Grape::API::Instance
include PaginationParams
before { authenticate! }
diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb
index 773a451d3a8..2e6ac40a593 100644
--- a/lib/api/merge_requests.rb
+++ b/lib/api/merge_requests.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module API
- class MergeRequests < Grape::API
+ class MergeRequests < Grape::API::Instance
include PaginationParams
CONTEXT_COMMITS_POST_LIMIT = 20
@@ -44,7 +44,9 @@ module API
def find_merge_requests(args = {})
args = declared_params.merge(args)
args[:milestone_title] = args.delete(:milestone)
+ args[:not][:milestone_title] = args[:not]&.delete(:milestone)
args[:label_name] = args.delete(:labels)
+ args[:not][:label_name] = args[:not]&.delete(:labels)
args[:scope] = args[:scope].underscore if args[:scope]
merge_requests = MergeRequestsFinder.new(current_user, args).execute
@@ -60,16 +62,8 @@ module API
# rubocop: enable CodeReuse/ActiveRecord
def merge_request_pipelines_with_access
- authorize! :read_pipeline, user_project
-
mr = find_merge_request_with_access(params[:merge_request_iid])
- mr.all_pipelines
- end
-
- def check_sha_param!(params, merge_request)
- if params[:sha] && merge_request.diff_head_sha != params[:sha]
- render_api_error!("SHA does not match HEAD of source branch: #{merge_request.diff_head_sha}", 409)
- end
+ ::Ci::PipelinesForMergeRequestFinder.new(mr, current_user).execute
end
def automatically_mergeable?(merge_when_pipeline_succeeds, merge_request)
@@ -91,7 +85,6 @@ module API
if params[:view] == 'simple'
options[:with] = Entities::MergeRequestSimple
else
- options[:issuable_metadata] = Gitlab::IssuableMetadata.new(current_user, merge_requests).data
options[:skip_merge_status_recheck] = !declared_params[:with_merge_status_recheck]
end
@@ -179,11 +172,11 @@ module API
params :optional_params do
optional :description, type: String, desc: 'The description of the merge request'
optional :assignee_id, type: Integer, desc: 'The ID of a user to assign the merge request'
- optional :assignee_ids, type: Array[Integer], desc: 'The array of user IDs to assign issue'
+ optional :assignee_ids, type: Array[Integer], coerce_with: ::API::Validations::Types::CommaSeparatedToIntegerArray.coerce, desc: 'The array of user IDs to assign issue'
optional :milestone_id, type: Integer, desc: 'The ID of a milestone to assign the merge request'
- optional :labels, type: Array[String], coerce_with: Validations::Types::LabelsList.coerce, desc: 'Comma-separated list of label names'
- optional :add_labels, type: Array[String], coerce_with: Validations::Types::LabelsList.coerce, desc: 'Comma-separated list of label names'
- optional :remove_labels, type: Array[String], coerce_with: Validations::Types::LabelsList.coerce, desc: 'Comma-separated list of label names'
+ optional :labels, type: Array[String], coerce_with: Validations::Types::CommaSeparatedToArray.coerce, desc: 'Comma-separated list of label names'
+ optional :add_labels, type: Array[String], coerce_with: Validations::Types::CommaSeparatedToArray.coerce, desc: 'Comma-separated list of label names'
+ optional :remove_labels, type: Array[String], coerce_with: Validations::Types::CommaSeparatedToArray.coerce, desc: 'Comma-separated list of label names'
optional :remove_source_branch, type: Boolean, desc: 'Remove source branch when merging'
optional :allow_collaboration, type: Boolean, desc: 'Allow commits from members who can merge to the target branch'
optional :allow_maintainer_to_push, type: Boolean, as: :allow_collaboration, desc: '[deprecated] See allow_collaboration'
@@ -198,7 +191,7 @@ module API
end
params do
use :merge_requests_params
- optional :iids, type: Array[Integer], desc: 'The IID array of merge requests'
+ optional :iids, type: Array[Integer], coerce_with: ::API::Validations::Types::CommaSeparatedToIntegerArray.coerce, desc: 'The IID array of merge requests'
end
get ":id/merge_requests" do
authorize! :read_merge_request, user_project
@@ -315,7 +308,7 @@ module API
end
params do
- requires :commits, type: Array, allow_blank: false, desc: 'List of context commits sha'
+ requires :commits, type: Array[String], coerce_with: ::API::Validations::Types::CommaSeparatedToArray.coerce, allow_blank: false, desc: 'List of context commits sha'
end
desc 'create context commits of merge request' do
success Entities::Commit
@@ -345,7 +338,7 @@ module API
end
params do
- requires :commits, type: Array, allow_blank: false, desc: 'List of context commits sha'
+ requires :commits, type: Array[String], coerce_with: ::API::Validations::Types::CommaSeparatedToArray.coerce, allow_blank: false, desc: 'List of context commits sha'
end
desc 'remove context commits of merge request'
delete ':id/merge_requests/:merge_request_iid/context_commits' do
@@ -389,8 +382,6 @@ module API
success Entities::Pipeline
end
post ':id/merge_requests/:merge_request_iid/pipelines' do
- authorize! :create_pipeline, user_project
-
pipeline = ::MergeRequests::CreatePipelineService
.new(user_project, current_user, allow_duplicate: true)
.execute(find_merge_request_with_access(params[:merge_request_iid]))
diff --git a/lib/api/metrics/dashboard/annotations.rb b/lib/api/metrics/dashboard/annotations.rb
index c8ec4d29657..e07762ac6d3 100644
--- a/lib/api/metrics/dashboard/annotations.rb
+++ b/lib/api/metrics/dashboard/annotations.rb
@@ -3,7 +3,7 @@
module API
module Metrics
module Dashboard
- class Annotations < Grape::API
+ class Annotations < Grape::API::Instance
desc 'Create a new monitoring dashboard annotation' do
success Entities::Metrics::Dashboard::Annotation
end
diff --git a/lib/api/metrics/user_starred_dashboards.rb b/lib/api/metrics/user_starred_dashboards.rb
index 85fc0f33ed8..263d2394276 100644
--- a/lib/api/metrics/user_starred_dashboards.rb
+++ b/lib/api/metrics/user_starred_dashboards.rb
@@ -2,7 +2,7 @@
module API
module Metrics
- class UserStarredDashboards < Grape::API
+ class UserStarredDashboards < Grape::API::Instance
resource :projects do
desc 'Marks selected metrics dashboard as starred' do
success Entities::Metrics::UserStarredDashboard
diff --git a/lib/api/milestone_responses.rb b/lib/api/milestone_responses.rb
index 62e159ab003..8ff885983bc 100644
--- a/lib/api/milestone_responses.rb
+++ b/lib/api/milestone_responses.rb
@@ -15,7 +15,7 @@ module API
params :list_params do
optional :state, type: String, values: %w[active closed all], default: 'all',
desc: 'Return "active", "closed", or "all" milestones'
- optional :iids, type: Array[Integer], desc: 'The IIDs of the milestones'
+ optional :iids, type: Array[Integer], coerce_with: ::API::Validations::Types::CommaSeparatedToIntegerArray.coerce, desc: 'The IIDs of the milestones'
optional :title, type: String, desc: 'The title of the milestones'
optional :search, type: String, desc: 'The search criteria for the title or description of the milestone'
use :pagination
diff --git a/lib/api/namespaces.rb b/lib/api/namespaces.rb
index e40a5dde7ce..e1f279df045 100644
--- a/lib/api/namespaces.rb
+++ b/lib/api/namespaces.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module API
- class Namespaces < Grape::API
+ class Namespaces < Grape::API::Instance
include PaginationParams
before { authenticate! }
diff --git a/lib/api/notes.rb b/lib/api/notes.rb
index 3eafc1ead77..bfd09dcd496 100644
--- a/lib/api/notes.rb
+++ b/lib/api/notes.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module API
- class Notes < Grape::API
+ class Notes < Grape::API::Instance
include PaginationParams
helpers ::API::Helpers::NotesHelpers
@@ -68,6 +68,7 @@ module API
params do
requires :noteable_id, type: Integer, desc: 'The ID of the noteable'
requires :body, type: String, desc: 'The content of a note'
+ optional :confidential, type: Boolean, desc: 'Confidentiality note flag, default is false'
optional :created_at, type: String, desc: 'The creation date of the note'
end
post ":id/#{noteables_str}/:noteable_id/notes" do
@@ -77,6 +78,7 @@ module API
note: params[:body],
noteable_type: noteables_str.classify,
noteable_id: noteable.id,
+ confidential: params[:confidential],
created_at: params[:created_at]
}
diff --git a/lib/api/notification_settings.rb b/lib/api/notification_settings.rb
index 8cb46bd3ad6..f8b621c1c38 100644
--- a/lib/api/notification_settings.rb
+++ b/lib/api/notification_settings.rb
@@ -2,7 +2,7 @@
module API
# notification_settings API
- class NotificationSettings < Grape::API
+ class NotificationSettings < Grape::API::Instance
before { authenticate! }
helpers ::API::Helpers::MembersHelpers
diff --git a/lib/api/npm_packages.rb b/lib/api/npm_packages.rb
new file mode 100644
index 00000000000..21ca57b7985
--- /dev/null
+++ b/lib/api/npm_packages.rb
@@ -0,0 +1,173 @@
+# frozen_string_literal: true
+module API
+ class NpmPackages < Grape::API::Instance
+ helpers ::API::Helpers::PackagesHelpers
+ helpers ::API::Helpers::Packages::DependencyProxyHelpers
+
+ NPM_ENDPOINT_REQUIREMENTS = {
+ package_name: API::NO_SLASH_URL_PART_REGEX
+ }.freeze
+
+ rescue_from ActiveRecord::RecordInvalid do |e|
+ render_api_error!(e.message, 400)
+ end
+
+ before do
+ require_packages_enabled!
+ authenticate_non_get!
+ end
+
+ helpers do
+ def project_by_package_name
+ strong_memoize(:project_by_package_name) do
+ ::Packages::Package.npm.with_name(params[:package_name]).first&.project
+ end
+ end
+ end
+
+ desc 'Get all tags for a given an NPM package' do
+ detail 'This feature was introduced in GitLab 12.7'
+ success ::API::Entities::NpmPackageTag
+ end
+ params do
+ requires :package_name, type: String, desc: 'Package name'
+ end
+ get 'packages/npm/-/package/*package_name/dist-tags', format: false, requirements: NPM_ENDPOINT_REQUIREMENTS do
+ package_name = params[:package_name]
+
+ bad_request!('Package Name') if package_name.blank?
+
+ authorize_read_package!(project_by_package_name)
+
+ packages = ::Packages::Npm::PackageFinder.new(project_by_package_name, package_name)
+ .execute
+
+ present ::Packages::Npm::PackagePresenter.new(package_name, packages),
+ with: ::API::Entities::NpmPackageTag
+ end
+
+ params do
+ requires :package_name, type: String, desc: 'Package name'
+ requires :tag, type: String, desc: "Package dist-tag"
+ end
+ namespace 'packages/npm/-/package/*package_name/dist-tags/:tag', requirements: NPM_ENDPOINT_REQUIREMENTS do
+ desc 'Create or Update the given tag for the given NPM package and version' do
+ detail 'This feature was introduced in GitLab 12.7'
+ end
+ put format: false do
+ package_name = params[:package_name]
+ version = env['api.request.body']
+ tag = params[:tag]
+
+ bad_request!('Package Name') if package_name.blank?
+ bad_request!('Version') if version.blank?
+ bad_request!('Tag') if tag.blank?
+
+ authorize_create_package!(project_by_package_name)
+
+ package = ::Packages::Npm::PackageFinder
+ .new(project_by_package_name, package_name)
+ .find_by_version(version)
+ not_found!('Package') unless package
+
+ ::Packages::Npm::CreateTagService.new(package, tag).execute
+
+ no_content!
+ end
+
+ desc 'Deletes the given tag' do
+ detail 'This feature was introduced in GitLab 12.7'
+ end
+ delete format: false do
+ package_name = params[:package_name]
+ tag = params[:tag]
+
+ bad_request!('Package Name') if package_name.blank?
+ bad_request!('Tag') if tag.blank?
+
+ authorize_destroy_package!(project_by_package_name)
+
+ package_tag = ::Packages::TagsFinder
+ .new(project_by_package_name, package_name, package_type: :npm)
+ .find_by_name(tag)
+
+ not_found!('Package tag') unless package_tag
+
+ ::Packages::RemoveTagService.new(package_tag).execute
+
+ no_content!
+ end
+ end
+
+ desc 'NPM registry endpoint at instance level' do
+ detail 'This feature was introduced in GitLab 11.8'
+ end
+ params do
+ requires :package_name, type: String, desc: 'Package name'
+ end
+ route_setting :authentication, job_token_allowed: true, deploy_token_allowed: true
+ get 'packages/npm/*package_name', format: false, requirements: NPM_ENDPOINT_REQUIREMENTS do
+ package_name = params[:package_name]
+
+ redirect_registry_request(project_by_package_name.blank?, :npm, package_name: package_name) do
+ authorize_read_package!(project_by_package_name)
+
+ packages = ::Packages::Npm::PackageFinder
+ .new(project_by_package_name, package_name).execute
+
+ present ::Packages::Npm::PackagePresenter.new(package_name, packages),
+ with: ::API::Entities::NpmPackage
+ end
+ end
+
+ params do
+ requires :id, type: String, desc: 'The ID of a project'
+ end
+ resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
+ desc 'Download the NPM tarball' do
+ detail 'This feature was introduced in GitLab 11.8'
+ end
+ params do
+ requires :package_name, type: String, desc: 'Package name'
+ requires :file_name, type: String, desc: 'Package file name'
+ end
+ route_setting :authentication, job_token_allowed: true, deploy_token_allowed: true
+ get ':id/packages/npm/*package_name/-/*file_name', format: false do
+ authorize_read_package!(user_project)
+
+ package = user_project.packages.npm
+ .by_name_and_file_name(params[:package_name], params[:file_name])
+
+ package_file = ::Packages::PackageFileFinder
+ .new(package, params[:file_name]).execute!
+
+ track_event('pull_package')
+
+ present_carrierwave_file!(package_file.file)
+ end
+
+ desc 'Create NPM package' do
+ detail 'This feature was introduced in GitLab 11.8'
+ end
+ params do
+ requires :package_name, type: String, desc: 'Package name'
+ requires :versions, type: Hash, desc: 'Package version info'
+ end
+ route_setting :authentication, job_token_allowed: true, deploy_token_allowed: true
+ put ':id/packages/npm/:package_name', requirements: NPM_ENDPOINT_REQUIREMENTS do
+ authorize_create_package!(user_project)
+
+ track_event('push_package')
+
+ created_package = ::Packages::Npm::CreatePackageService
+ .new(user_project, current_user, params.merge(build: current_authenticated_job)).execute
+
+ if created_package[:status] == :error
+ render_api_error!(created_package[:message], created_package[:http_status])
+ else
+ created_package
+ end
+ end
+ end
+ end
+end
diff --git a/lib/api/nuget_packages.rb b/lib/api/nuget_packages.rb
new file mode 100644
index 00000000000..eb7d320a0f5
--- /dev/null
+++ b/lib/api/nuget_packages.rb
@@ -0,0 +1,221 @@
+# frozen_string_literal: true
+
+# NuGet Package Manager Client API
+#
+# These API endpoints are not meant to be consumed directly by users. They are
+# called by the NuGet package manager client when users run commands
+# like `nuget install` or `nuget push`.
+module API
+ class NugetPackages < Grape::API::Instance
+ helpers ::API::Helpers::PackagesManagerClientsHelpers
+ helpers ::API::Helpers::Packages::BasicAuthHelpers
+
+ POSITIVE_INTEGER_REGEX = %r{\A[1-9]\d*\z}.freeze
+ NON_NEGATIVE_INTEGER_REGEX = %r{\A0|[1-9]\d*\z}.freeze
+
+ PACKAGE_FILENAME = 'package.nupkg'
+
+ default_format :json
+
+ rescue_from ArgumentError do |e|
+ render_api_error!(e.message, 400)
+ end
+
+ helpers do
+ def find_packages
+ packages = package_finder.execute
+
+ not_found!('Packages') unless packages.exists?
+
+ packages
+ end
+
+ def find_package
+ package = package_finder(package_version: params[:package_version]).execute
+ .first
+
+ not_found!('Package') unless package
+
+ package
+ end
+
+ def package_finder(finder_params = {})
+ ::Packages::Nuget::PackageFinder.new(
+ authorized_user_project,
+ finder_params.merge(package_name: params[:package_name])
+ )
+ end
+ end
+
+ before do
+ require_packages_enabled!
+ end
+
+ params do
+ requires :id, type: String, desc: 'The ID of a project', regexp: POSITIVE_INTEGER_REGEX
+ end
+ route_setting :authentication, deploy_token_allowed: true
+ resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
+ before do
+ authorized_user_project
+ end
+
+ namespace ':id/packages/nuget' do
+ # https://docs.microsoft.com/en-us/nuget/api/service-index
+ desc 'The NuGet Service Index' do
+ detail 'This feature was introduced in GitLab 12.6'
+ end
+ route_setting :authentication, deploy_token_allowed: true
+ get 'index', format: :json do
+ authorize_read_package!(authorized_user_project)
+
+ track_event('nuget_service_index')
+
+ present ::Packages::Nuget::ServiceIndexPresenter.new(authorized_user_project),
+ with: ::API::Entities::Nuget::ServiceIndex
+ end
+
+ # https://docs.microsoft.com/en-us/nuget/api/package-publish-resource
+ desc 'The NuGet Package Publish endpoint' do
+ detail 'This feature was introduced in GitLab 12.6'
+ end
+ params do
+ requires :package, type: ::API::Validations::Types::WorkhorseFile, desc: 'The package file to be published (generated by Multipart middleware)'
+ end
+ route_setting :authentication, deploy_token_allowed: true
+ put do
+ authorize_upload!(authorized_user_project)
+
+ file_params = params.merge(
+ file: params[:package],
+ file_name: PACKAGE_FILENAME
+ )
+
+ package = ::Packages::Nuget::CreatePackageService.new(authorized_user_project, current_user)
+ .execute
+
+ package_file = ::Packages::CreatePackageFileService.new(package, file_params)
+ .execute
+
+ track_event('push_package')
+
+ ::Packages::Nuget::ExtractionWorker.perform_async(package_file.id) # rubocop:disable CodeReuse/Worker
+
+ created!
+ rescue ObjectStorage::RemoteStoreError => e
+ Gitlab::ErrorTracking.track_exception(e, extra: { file_name: params[:file_name], project_id: authorized_user_project.id })
+
+ forbidden!
+ end
+ route_setting :authentication, deploy_token_allowed: true
+ put 'authorize' do
+ authorize_workhorse!(subject: authorized_user_project, has_length: false)
+ end
+
+ params do
+ requires :package_name, type: String, desc: 'The NuGet package name', regexp: API::NO_SLASH_URL_PART_REGEX
+ end
+ namespace '/metadata/*package_name' do
+ before do
+ authorize_read_package!(authorized_user_project)
+ end
+
+ # https://docs.microsoft.com/en-us/nuget/api/registration-base-url-resource
+ desc 'The NuGet Metadata Service - Package name level' do
+ detail 'This feature was introduced in GitLab 12.8'
+ end
+ route_setting :authentication, deploy_token_allowed: true
+ get 'index', format: :json do
+ present ::Packages::Nuget::PackagesMetadataPresenter.new(find_packages),
+ with: ::API::Entities::Nuget::PackagesMetadata
+ end
+
+ desc 'The NuGet Metadata Service - Package name and version level' do
+ detail 'This feature was introduced in GitLab 12.8'
+ end
+ params do
+ requires :package_version, type: String, desc: 'The NuGet package version', regexp: API::NO_SLASH_URL_PART_REGEX
+ end
+ route_setting :authentication, deploy_token_allowed: true
+ get '*package_version', format: :json do
+ present ::Packages::Nuget::PackageMetadataPresenter.new(find_package),
+ with: ::API::Entities::Nuget::PackageMetadata
+ end
+ end
+
+ # https://docs.microsoft.com/en-us/nuget/api/package-base-address-resource
+ params do
+ requires :package_name, type: String, desc: 'The NuGet package name', regexp: API::NO_SLASH_URL_PART_REGEX
+ end
+ namespace '/download/*package_name' do
+ before do
+ authorize_read_package!(authorized_user_project)
+ end
+
+ desc 'The NuGet Content Service - index request' do
+ detail 'This feature was introduced in GitLab 12.8'
+ end
+ route_setting :authentication, deploy_token_allowed: true
+ get 'index', format: :json do
+ present ::Packages::Nuget::PackagesVersionsPresenter.new(find_packages),
+ with: ::API::Entities::Nuget::PackagesVersions
+ end
+
+ desc 'The NuGet Content Service - content request' do
+ detail 'This feature was introduced in GitLab 12.8'
+ end
+ params do
+ requires :package_version, type: String, desc: 'The NuGet package version', regexp: API::NO_SLASH_URL_PART_REGEX
+ requires :package_filename, type: String, desc: 'The NuGet package filename', regexp: API::NO_SLASH_URL_PART_REGEX
+ end
+ route_setting :authentication, deploy_token_allowed: true
+ get '*package_version/*package_filename', format: :nupkg do
+ filename = "#{params[:package_filename]}.#{params[:format]}"
+ package_file = ::Packages::PackageFileFinder.new(find_package, filename, with_file_name_like: true)
+ .execute
+
+ not_found!('Package') unless package_file
+
+ track_event('pull_package')
+
+ # nuget and dotnet don't support 302 Moved status codes, supports_direct_download has to be set to false
+ present_carrierwave_file!(package_file.file, supports_direct_download: false)
+ end
+ end
+
+ params do
+ requires :q, type: String, desc: 'The search term'
+ optional :skip, type: Integer, desc: 'The number of results to skip', default: 0, regexp: NON_NEGATIVE_INTEGER_REGEX
+ optional :take, type: Integer, desc: 'The number of results to return', default: Kaminari.config.default_per_page, regexp: POSITIVE_INTEGER_REGEX
+ optional :prerelease, type: Boolean, desc: 'Include prerelease versions', default: true
+ end
+ namespace '/query' do
+ before do
+ authorize_read_package!(authorized_user_project)
+ end
+
+ # https://docs.microsoft.com/en-us/nuget/api/search-query-service-resource
+ desc 'The NuGet Search Service' do
+ detail 'This feature was introduced in GitLab 12.8'
+ end
+ route_setting :authentication, deploy_token_allowed: true
+ get format: :json do
+ search_options = {
+ include_prerelease_versions: params[:prerelease],
+ per_page: params[:take],
+ padding: params[:skip]
+ }
+ search = Packages::Nuget::SearchService
+ .new(authorized_user_project, params[:q], search_options)
+ .execute
+
+ track_event('search_package')
+
+ present ::Packages::Nuget::SearchResultsPresenter.new(search),
+ with: ::API::Entities::Nuget::SearchResults
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/api/package_files.rb b/lib/api/package_files.rb
new file mode 100644
index 00000000000..17b92df629c
--- /dev/null
+++ b/lib/api/package_files.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+module API
+ class PackageFiles < Grape::API::Instance
+ include PaginationParams
+
+ before do
+ authorize_packages_access!(user_project)
+ end
+
+ helpers ::API::Helpers::PackagesHelpers
+
+ params do
+ requires :id, type: String, desc: 'The ID of a project'
+ requires :package_id, type: Integer, desc: 'The ID of a package'
+ end
+ resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
+ desc 'Get all package files' do
+ detail 'This feature was introduced in GitLab 11.8'
+ success ::API::Entities::PackageFile
+ end
+ params do
+ use :pagination
+ end
+ get ':id/packages/:package_id/package_files' do
+ package = ::Packages::PackageFinder
+ .new(user_project, params[:package_id]).execute
+
+ present paginate(package.package_files), with: ::API::Entities::PackageFile
+ end
+ end
+ end
+end
diff --git a/lib/api/pages.rb b/lib/api/pages.rb
index ee7fe669519..79a6b527581 100644
--- a/lib/api/pages.rb
+++ b/lib/api/pages.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module API
- class Pages < Grape::API
+ class Pages < Grape::API::Instance
before do
require_pages_config_enabled!
authenticated_with_can_read_all_resources!
diff --git a/lib/api/pages_domains.rb b/lib/api/pages_domains.rb
index 4c3d2d131ac..7d27b575efa 100644
--- a/lib/api/pages_domains.rb
+++ b/lib/api/pages_domains.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module API
- class PagesDomains < Grape::API
+ class PagesDomains < Grape::API::Instance
include PaginationParams
PAGES_DOMAINS_ENDPOINT_REQUIREMENTS = API::NAMESPACE_OR_PROJECT_REQUIREMENTS.merge(domain: API::NO_SLASH_URL_PART_REGEX)
diff --git a/lib/api/pagination_params.rb b/lib/api/pagination_params.rb
index ae03595eb25..a232b58d3f7 100644
--- a/lib/api/pagination_params.rb
+++ b/lib/api/pagination_params.rb
@@ -4,7 +4,7 @@ module API
# Concern for declare pagination params.
#
# @example
- # class CustomApiResource < Grape::API
+ # class CustomApiResource < Grape::API::Instance
# include PaginationParams
#
# params do
diff --git a/lib/api/pipeline_schedules.rb b/lib/api/pipeline_schedules.rb
deleted file mode 100644
index edc99590cdb..00000000000
--- a/lib/api/pipeline_schedules.rb
+++ /dev/null
@@ -1,215 +0,0 @@
-# frozen_string_literal: true
-
-module API
- class PipelineSchedules < Grape::API
- include PaginationParams
-
- before { authenticate! }
-
- params do
- requires :id, type: String, desc: 'The ID of a project'
- end
- resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
- desc 'Get all pipeline schedules' do
- success Entities::PipelineSchedule
- end
- params do
- use :pagination
- optional :scope, type: String, values: %w[active inactive],
- desc: 'The scope of pipeline schedules'
- end
- # rubocop: disable CodeReuse/ActiveRecord
- get ':id/pipeline_schedules' do
- authorize! :read_pipeline_schedule, user_project
-
- schedules = Ci::PipelineSchedulesFinder.new(user_project).execute(scope: params[:scope])
- .preload([:owner, :last_pipeline])
- present paginate(schedules), with: Entities::PipelineSchedule
- end
- # rubocop: enable CodeReuse/ActiveRecord
-
- desc 'Get a single pipeline schedule' do
- success Entities::PipelineScheduleDetails
- end
- params do
- requires :pipeline_schedule_id, type: Integer, desc: 'The pipeline schedule id'
- end
- get ':id/pipeline_schedules/:pipeline_schedule_id' do
- present pipeline_schedule, with: Entities::PipelineScheduleDetails
- end
-
- desc 'Create a new pipeline schedule' do
- success Entities::PipelineScheduleDetails
- end
- params do
- requires :description, type: String, desc: 'The description of pipeline schedule'
- requires :ref, type: String, desc: 'The branch/tag name will be triggered', allow_blank: false
- requires :cron, type: String, desc: 'The cron'
- optional :cron_timezone, type: String, default: 'UTC', desc: 'The timezone'
- optional :active, type: Boolean, default: true, desc: 'The activation of pipeline schedule'
- end
- post ':id/pipeline_schedules' do
- authorize! :create_pipeline_schedule, user_project
-
- pipeline_schedule = Ci::CreatePipelineScheduleService
- .new(user_project, current_user, declared_params(include_missing: false))
- .execute
-
- if pipeline_schedule.persisted?
- present pipeline_schedule, with: Entities::PipelineScheduleDetails
- else
- render_validation_error!(pipeline_schedule)
- end
- end
-
- desc 'Edit a pipeline schedule' do
- success Entities::PipelineScheduleDetails
- end
- params do
- requires :pipeline_schedule_id, type: Integer, desc: 'The pipeline schedule id'
- optional :description, type: String, desc: 'The description of pipeline schedule'
- optional :ref, type: String, desc: 'The branch/tag name will be triggered'
- optional :cron, type: String, desc: 'The cron'
- optional :cron_timezone, type: String, desc: 'The timezone'
- optional :active, type: Boolean, desc: 'The activation of pipeline schedule'
- end
- put ':id/pipeline_schedules/:pipeline_schedule_id' do
- authorize! :update_pipeline_schedule, pipeline_schedule
-
- if pipeline_schedule.update(declared_params(include_missing: false))
- present pipeline_schedule, with: Entities::PipelineScheduleDetails
- else
- render_validation_error!(pipeline_schedule)
- end
- end
-
- desc 'Take ownership of a pipeline schedule' do
- success Entities::PipelineScheduleDetails
- end
- params do
- requires :pipeline_schedule_id, type: Integer, desc: 'The pipeline schedule id'
- end
- post ':id/pipeline_schedules/:pipeline_schedule_id/take_ownership' do
- authorize! :update_pipeline_schedule, pipeline_schedule
-
- if pipeline_schedule.own!(current_user)
- present pipeline_schedule, with: Entities::PipelineScheduleDetails
- else
- render_validation_error!(pipeline_schedule)
- end
- end
-
- desc 'Delete a pipeline schedule' do
- success Entities::PipelineScheduleDetails
- end
- params do
- requires :pipeline_schedule_id, type: Integer, desc: 'The pipeline schedule id'
- end
- delete ':id/pipeline_schedules/:pipeline_schedule_id' do
- authorize! :admin_pipeline_schedule, pipeline_schedule
-
- destroy_conditionally!(pipeline_schedule)
- end
-
- desc 'Play a scheduled pipeline immediately' do
- detail 'This feature was added in GitLab 12.8'
- end
- params do
- requires :pipeline_schedule_id, type: Integer, desc: 'The pipeline schedule id'
- end
- post ':id/pipeline_schedules/:pipeline_schedule_id/play' do
- authorize! :play_pipeline_schedule, pipeline_schedule
-
- job_id = RunPipelineScheduleWorker # rubocop:disable CodeReuse/Worker
- .perform_async(pipeline_schedule.id, current_user.id)
-
- if job_id
- created!
- else
- render_api_error!('Unable to schedule pipeline run immediately', 500)
- end
- end
-
- desc 'Create a new pipeline schedule variable' do
- success Entities::Variable
- end
- params do
- requires :pipeline_schedule_id, type: Integer, desc: 'The pipeline schedule id'
- requires :key, type: String, desc: 'The key of the variable'
- requires :value, type: String, desc: 'The value of the variable'
- optional :variable_type, type: String, values: Ci::PipelineScheduleVariable.variable_types.keys, desc: 'The type of variable, must be one of env_var or file. Defaults to env_var'
- end
- post ':id/pipeline_schedules/:pipeline_schedule_id/variables' do
- authorize! :update_pipeline_schedule, pipeline_schedule
-
- variable_params = declared_params(include_missing: false)
- variable = pipeline_schedule.variables.create(variable_params)
- if variable.persisted?
- present variable, with: Entities::Variable
- else
- render_validation_error!(variable)
- end
- end
-
- desc 'Edit a pipeline schedule variable' do
- success Entities::Variable
- end
- params do
- requires :pipeline_schedule_id, type: Integer, desc: 'The pipeline schedule id'
- requires :key, type: String, desc: 'The key of the variable'
- optional :value, type: String, desc: 'The value of the variable'
- optional :variable_type, type: String, values: Ci::PipelineScheduleVariable.variable_types.keys, desc: 'The type of variable, must be one of env_var or file'
- end
- put ':id/pipeline_schedules/:pipeline_schedule_id/variables/:key' do
- authorize! :update_pipeline_schedule, pipeline_schedule
-
- if pipeline_schedule_variable.update(declared_params(include_missing: false))
- present pipeline_schedule_variable, with: Entities::Variable
- else
- render_validation_error!(pipeline_schedule_variable)
- end
- end
-
- desc 'Delete a pipeline schedule variable' do
- success Entities::Variable
- end
- params do
- requires :pipeline_schedule_id, type: Integer, desc: 'The pipeline schedule id'
- requires :key, type: String, desc: 'The key of the variable'
- end
- delete ':id/pipeline_schedules/:pipeline_schedule_id/variables/:key' do
- authorize! :admin_pipeline_schedule, pipeline_schedule
-
- status :accepted
- present pipeline_schedule_variable.destroy, with: Entities::Variable
- end
- end
-
- helpers do
- # rubocop: disable CodeReuse/ActiveRecord
- def pipeline_schedule
- @pipeline_schedule ||=
- user_project
- .pipeline_schedules
- .preload(:owner, :last_pipeline)
- .find_by(id: params.delete(:pipeline_schedule_id)).tap do |pipeline_schedule|
- unless can?(current_user, :read_pipeline_schedule, pipeline_schedule)
- not_found!('Pipeline Schedule')
- end
- end
- end
- # rubocop: enable CodeReuse/ActiveRecord
-
- # rubocop: disable CodeReuse/ActiveRecord
- def pipeline_schedule_variable
- @pipeline_schedule_variable ||=
- pipeline_schedule.variables.find_by(key: params[:key]).tap do |pipeline_schedule_variable|
- unless pipeline_schedule_variable
- not_found!('Pipeline Schedule Variable')
- end
- end
- end
- # rubocop: enable CodeReuse/ActiveRecord
- end
- end
-end
diff --git a/lib/api/pipelines.rb b/lib/api/pipelines.rb
deleted file mode 100644
index c09bca26a41..00000000000
--- a/lib/api/pipelines.rb
+++ /dev/null
@@ -1,187 +0,0 @@
-# frozen_string_literal: true
-
-module API
- class Pipelines < Grape::API
- include PaginationParams
-
- before { authenticate_non_get! }
-
- params do
- requires :id, type: String, desc: 'The project ID'
- end
- resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
- desc 'Get all Pipelines of the project' do
- detail 'This feature was introduced in GitLab 8.11.'
- success Entities::PipelineBasic
- end
- params do
- use :pagination
- optional :scope, type: String, values: %w[running pending finished branches tags],
- desc: 'The scope of pipelines'
- optional :status, type: String, values: HasStatus::AVAILABLE_STATUSES,
- desc: 'The status of pipelines'
- optional :ref, type: String, desc: 'The ref of pipelines'
- optional :sha, type: String, desc: 'The sha of pipelines'
- optional :yaml_errors, type: Boolean, desc: 'Returns pipelines with invalid configurations'
- optional :name, type: String, desc: 'The name of the user who triggered pipelines'
- optional :username, type: String, desc: 'The username of the user who triggered pipelines'
- optional :updated_before, type: DateTime, desc: 'Return pipelines updated before the specified datetime. Format: ISO 8601 YYYY-MM-DDTHH:MM:SSZ'
- optional :updated_after, type: DateTime, desc: 'Return pipelines updated after the specified datetime. Format: ISO 8601 YYYY-MM-DDTHH:MM:SSZ'
- optional :order_by, type: String, values: Ci::PipelinesFinder::ALLOWED_INDEXED_COLUMNS, default: 'id',
- desc: 'Order pipelines'
- optional :sort, type: String, values: %w[asc desc], default: 'desc',
- desc: 'Sort pipelines'
- end
- get ':id/pipelines' do
- authorize! :read_pipeline, user_project
- authorize! :read_build, user_project
-
- pipelines = Ci::PipelinesFinder.new(user_project, current_user, params).execute
- present paginate(pipelines), with: Entities::PipelineBasic
- end
-
- desc 'Create a new pipeline' do
- detail 'This feature was introduced in GitLab 8.14'
- success Entities::Pipeline
- end
- params do
- requires :ref, type: String, desc: 'Reference'
- optional :variables, Array, desc: 'Array of variables available in the pipeline'
- end
- post ':id/pipeline' do
- Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-foss/issues/42124')
-
- authorize! :create_pipeline, user_project
-
- pipeline_params = declared_params(include_missing: false)
- .merge(variables_attributes: params[:variables])
- .except(:variables)
-
- new_pipeline = Ci::CreatePipelineService.new(user_project,
- current_user,
- pipeline_params)
- .execute(:api, ignore_skip_ci: true, save_on_errors: false)
-
- if new_pipeline.persisted?
- present new_pipeline, with: Entities::Pipeline
- else
- render_validation_error!(new_pipeline)
- end
- end
-
- desc 'Gets a the latest pipeline for the project branch' do
- detail 'This feature was introduced in GitLab 12.3'
- success Entities::Pipeline
- end
- params do
- optional :ref, type: String, desc: 'branch ref of pipeline'
- end
- get ':id/pipelines/latest' do
- authorize! :read_pipeline, latest_pipeline
-
- present latest_pipeline, with: Entities::Pipeline
- end
-
- desc 'Gets a specific pipeline for the project' do
- detail 'This feature was introduced in GitLab 8.11'
- success Entities::Pipeline
- end
- params do
- requires :pipeline_id, type: Integer, desc: 'The pipeline ID'
- end
- get ':id/pipelines/:pipeline_id' do
- authorize! :read_pipeline, pipeline
-
- present pipeline, with: Entities::Pipeline
- end
-
- desc 'Gets the variables for a given pipeline' do
- detail 'This feature was introduced in GitLab 11.11'
- success Entities::Variable
- end
- params do
- requires :pipeline_id, type: Integer, desc: 'The pipeline ID'
- end
- get ':id/pipelines/:pipeline_id/variables' do
- authorize! :read_pipeline_variable, pipeline
-
- present pipeline.variables, with: Entities::Variable
- end
-
- desc 'Gets the test report for a given pipeline' do
- detail 'This feature was introduced in GitLab 13.0. Disabled by default behind feature flag `junit_pipeline_view`'
- success TestReportEntity
- end
- params do
- requires :pipeline_id, type: Integer, desc: 'The pipeline ID'
- end
- get ':id/pipelines/:pipeline_id/test_report' do
- not_found! unless Feature.enabled?(:junit_pipeline_view, user_project)
-
- authorize! :read_build, pipeline
-
- present pipeline.test_reports, with: TestReportEntity
- end
-
- desc 'Deletes a pipeline' do
- detail 'This feature was introduced in GitLab 11.6'
- http_codes [[204, 'Pipeline was deleted'], [403, 'Forbidden']]
- end
- params do
- requires :pipeline_id, type: Integer, desc: 'The pipeline ID'
- end
- delete ':id/pipelines/:pipeline_id' do
- authorize! :destroy_pipeline, pipeline
-
- destroy_conditionally!(pipeline) do
- ::Ci::DestroyPipelineService.new(user_project, current_user).execute(pipeline)
- end
- end
-
- desc 'Retry builds in the pipeline' do
- detail 'This feature was introduced in GitLab 8.11.'
- success Entities::Pipeline
- end
- params do
- requires :pipeline_id, type: Integer, desc: 'The pipeline ID'
- end
- post ':id/pipelines/:pipeline_id/retry' do
- authorize! :update_pipeline, pipeline
-
- pipeline.retry_failed(current_user)
-
- present pipeline, with: Entities::Pipeline
- end
-
- desc 'Cancel all builds in the pipeline' do
- detail 'This feature was introduced in GitLab 8.11.'
- success Entities::Pipeline
- end
- params do
- requires :pipeline_id, type: Integer, desc: 'The pipeline ID'
- end
- post ':id/pipelines/:pipeline_id/cancel' do
- authorize! :update_pipeline, pipeline
-
- pipeline.cancel_running
-
- status 200
- present pipeline.reset, with: Entities::Pipeline
- end
- end
-
- helpers do
- def pipeline
- strong_memoize(:pipeline) do
- user_project.ci_pipelines.find(params[:pipeline_id])
- end
- end
-
- def latest_pipeline
- strong_memoize(:latest_pipeline) do
- user_project.latest_pipeline_for_ref(params[:ref])
- end
- end
- end
- end
-end
diff --git a/lib/api/project_clusters.rb b/lib/api/project_clusters.rb
index 299301aabc4..0e5605984e6 100644
--- a/lib/api/project_clusters.rb
+++ b/lib/api/project_clusters.rb
@@ -1,23 +1,11 @@
# frozen_string_literal: true
module API
- class ProjectClusters < Grape::API
+ class ProjectClusters < Grape::API::Instance
include PaginationParams
before { authenticate! }
- # EE::API::ProjectClusters will
- # override these methods
- helpers do
- params :create_params_ee do
- end
-
- params :update_params_ee do
- end
- end
-
- prepend_if_ee('EE::API::ProjectClusters') # rubocop: disable Cop/InjectEnterpriseEditionModule
-
params do
requires :id, type: String, desc: 'The ID of the project'
end
@@ -56,6 +44,7 @@ module API
requires :name, type: String, desc: 'Cluster name'
optional :enabled, type: Boolean, default: true, desc: 'Determines if cluster is active or not, defaults to true'
optional :domain, type: String, desc: 'Cluster base domain'
+ optional :environment_scope, default: '*', type: String, desc: 'The associated environment to the cluster'
optional :management_project_id, type: Integer, desc: 'The ID of the management project'
optional :managed, type: Boolean, default: true, desc: 'Determines if GitLab will manage namespaces and service accounts for this cluster, defaults to true'
requires :platform_kubernetes_attributes, type: Hash, desc: %q(Platform Kubernetes data) do
@@ -65,7 +54,6 @@ module API
optional :namespace, type: String, desc: 'Unique namespace related to Project'
optional :authorization_type, type: String, values: ::Clusters::Platforms::Kubernetes.authorization_types.keys, default: 'rbac', desc: 'Cluster authorization type, defaults to RBAC'
end
- use :create_params_ee
end
post ':id/clusters/user' do
authorize! :add_cluster, user_project
@@ -89,6 +77,7 @@ module API
requires :cluster_id, type: Integer, desc: 'The cluster ID'
optional :name, type: String, desc: 'Cluster name'
optional :domain, type: String, desc: 'Cluster base domain'
+ optional :environment_scope, type: String, desc: 'The associated environment to the cluster'
optional :management_project_id, type: Integer, desc: 'The ID of the management project'
optional :platform_kubernetes_attributes, type: Hash, desc: %q(Platform Kubernetes data) do
optional :api_url, type: String, desc: 'URL to access the Kubernetes API'
@@ -96,7 +85,6 @@ module API
optional :ca_cert, type: String, desc: 'TLS certificate (needed if API is using a self-signed TLS certificate)'
optional :namespace, type: String, desc: 'Unique namespace related to Project'
end
- use :update_params_ee
end
put ':id/clusters/:cluster_id' do
authorize! :update_cluster, cluster
diff --git a/lib/api/project_container_repositories.rb b/lib/api/project_container_repositories.rb
index 2a0099018d9..8f2a62bc5a4 100644
--- a/lib/api/project_container_repositories.rb
+++ b/lib/api/project_container_repositories.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module API
- class ProjectContainerRepositories < Grape::API
+ class ProjectContainerRepositories < Grape::API::Instance
include PaginationParams
REPOSITORY_ENDPOINT_REQUIREMENTS = API::NAMESPACE_OR_PROJECT_REQUIREMENTS.merge(
diff --git a/lib/api/project_events.rb b/lib/api/project_events.rb
index 734311e1142..726e693826e 100644
--- a/lib/api/project_events.rb
+++ b/lib/api/project_events.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module API
- class ProjectEvents < Grape::API
+ class ProjectEvents < Grape::API::Instance
include PaginationParams
include APIGuard
helpers ::API::Helpers::EventsHelpers
diff --git a/lib/api/project_export.rb b/lib/api/project_export.rb
index 4b35f245b8c..d11c47f8d78 100644
--- a/lib/api/project_export.rb
+++ b/lib/api/project_export.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module API
- class ProjectExport < Grape::API
+ class ProjectExport < Grape::API::Instance
helpers Helpers::RateLimiter
before do
diff --git a/lib/api/project_hooks.rb b/lib/api/project_hooks.rb
index 0e7576c9243..7cea44e6304 100644
--- a/lib/api/project_hooks.rb
+++ b/lib/api/project_hooks.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module API
- class ProjectHooks < Grape::API
+ class ProjectHooks < Grape::API::Instance
include PaginationParams
before { authenticate! }
diff --git a/lib/api/project_import.rb b/lib/api/project_import.rb
index 17d08d14a20..9f43c3c7993 100644
--- a/lib/api/project_import.rb
+++ b/lib/api/project_import.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module API
- class ProjectImport < Grape::API
+ class ProjectImport < Grape::API::Instance
include PaginationParams
MAXIMUM_FILE_SIZE = 50.megabytes
diff --git a/lib/api/project_milestones.rb b/lib/api/project_milestones.rb
index 8643854a655..2f8dd1085dc 100644
--- a/lib/api/project_milestones.rb
+++ b/lib/api/project_milestones.rb
@@ -1,13 +1,11 @@
# frozen_string_literal: true
module API
- class ProjectMilestones < Grape::API
+ class ProjectMilestones < Grape::API::Instance
include PaginationParams
include MilestoneResponses
- before do
- authenticate!
- end
+ before { authenticate! }
params do
requires :id, type: String, desc: 'The ID of a project'
diff --git a/lib/api/project_packages.rb b/lib/api/project_packages.rb
new file mode 100644
index 00000000000..359514f1f78
--- /dev/null
+++ b/lib/api/project_packages.rb
@@ -0,0 +1,71 @@
+# frozen_string_literal: true
+
+module API
+ class ProjectPackages < Grape::API::Instance
+ include PaginationParams
+
+ before do
+ authorize_packages_access!(user_project)
+ end
+
+ helpers ::API::Helpers::PackagesHelpers
+
+ params do
+ requires :id, type: String, desc: 'The ID of a project'
+ end
+ resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
+ desc 'Get all project packages' do
+ detail 'This feature was introduced in GitLab 11.8'
+ success ::API::Entities::Package
+ end
+ params do
+ use :pagination
+ optional :order_by, type: String, values: %w[created_at name version type], default: 'created_at',
+ desc: 'Return packages ordered by `created_at`, `name`, `version` or `type` fields.'
+ optional :sort, type: String, values: %w[asc desc], default: 'asc',
+ desc: 'Return packages sorted in `asc` or `desc` order.'
+ optional :package_type, type: String, values: Packages::Package.package_types.keys,
+ desc: 'Return packages of a certain type'
+ optional :package_name, type: String,
+ desc: 'Return packages with this name'
+ end
+ get ':id/packages' do
+ packages = ::Packages::PackagesFinder.new(
+ user_project,
+ declared_params.slice(:order_by, :sort, :package_type, :package_name)
+ ).execute
+
+ present paginate(packages), with: ::API::Entities::Package, user: current_user
+ end
+
+ desc 'Get a single project package' do
+ detail 'This feature was introduced in GitLab 11.9'
+ success ::API::Entities::Package
+ end
+ params do
+ requires :package_id, type: Integer, desc: 'The ID of a package'
+ end
+ get ':id/packages/:package_id' do
+ package = ::Packages::PackageFinder
+ .new(user_project, params[:package_id]).execute
+
+ present package, with: ::API::Entities::Package, user: current_user
+ end
+
+ desc 'Remove a package' do
+ detail 'This feature was introduced in GitLab 11.9'
+ end
+ params do
+ requires :package_id, type: Integer, desc: 'The ID of a package'
+ end
+ delete ':id/packages/:package_id' do
+ authorize_destroy_package!(user_project)
+
+ package = ::Packages::PackageFinder
+ .new(user_project, params[:package_id]).execute
+
+ destroy_conditionally!(package)
+ end
+ end
+ end
+end
diff --git a/lib/api/project_repository_storage_moves.rb b/lib/api/project_repository_storage_moves.rb
index 5de623102fb..c318907542b 100644
--- a/lib/api/project_repository_storage_moves.rb
+++ b/lib/api/project_repository_storage_moves.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module API
- class ProjectRepositoryStorageMoves < Grape::API
+ class ProjectRepositoryStorageMoves < Grape::API::Instance
include PaginationParams
before { authenticated_as_admin! }
diff --git a/lib/api/project_snapshots.rb b/lib/api/project_snapshots.rb
index 175fbb2ce92..360000861fc 100644
--- a/lib/api/project_snapshots.rb
+++ b/lib/api/project_snapshots.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module API
- class ProjectSnapshots < Grape::API
+ class ProjectSnapshots < Grape::API::Instance
helpers ::API::Helpers::ProjectSnapshotsHelpers
before { authorize_read_git_snapshot! }
diff --git a/lib/api/project_snippets.rb b/lib/api/project_snippets.rb
index 68f4a0dcb65..09934502e85 100644
--- a/lib/api/project_snippets.rb
+++ b/lib/api/project_snippets.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module API
- class ProjectSnippets < Grape::API
+ class ProjectSnippets < Grape::API::Instance
include PaginationParams
before { authenticate! }
@@ -37,7 +37,7 @@ module API
use :pagination
end
get ":id/snippets" do
- present paginate(snippets_for_current_user), with: Entities::ProjectSnippet
+ present paginate(snippets_for_current_user), with: Entities::ProjectSnippet, current_user: current_user
end
desc 'Get a single project snippet' do
@@ -48,7 +48,7 @@ module API
end
get ":id/snippets/:snippet_id" do
snippet = snippets_for_current_user.find(params[:snippet_id])
- present snippet, with: Entities::ProjectSnippet
+ present snippet, with: Entities::ProjectSnippet, current_user: current_user
end
desc 'Create a new project snippet' do
@@ -71,7 +71,7 @@ module API
snippet = service_response.payload[:snippet]
if service_response.success?
- present snippet, with: Entities::ProjectSnippet
+ present snippet, with: Entities::ProjectSnippet, current_user: current_user
else
render_spam_error! if snippet.spam?
@@ -107,7 +107,7 @@ module API
snippet = service_response.payload[:snippet]
if service_response.success?
- present snippet, with: Entities::ProjectSnippet
+ present snippet, with: Entities::ProjectSnippet, current_user: current_user
else
render_spam_error! if snippet.spam?
@@ -147,10 +147,19 @@ module API
snippet = snippets_for_current_user.find_by(id: params[:snippet_id])
not_found!('Snippet') unless snippet
- env['api.format'] = :txt
- content_type 'text/plain'
present content_for(snippet)
end
+
+ desc 'Get raw project snippet file contents from the repository'
+ params do
+ use :raw_file_params
+ end
+ get ":id/snippets/:snippet_id/files/:ref/:file_path/raw", requirements: { file_path: API::NO_SLASH_URL_PART_REGEX } do
+ snippet = snippets_for_current_user.find_by(id: params[:snippet_id])
+ not_found!('Snippet') unless snippet&.repo_exists?
+
+ present file_content_for(snippet)
+ end
# rubocop: enable CodeReuse/ActiveRecord
desc 'Get the user agent details for a project snippet' do
diff --git a/lib/api/project_statistics.rb b/lib/api/project_statistics.rb
index 14ee0f75513..2196801096f 100644
--- a/lib/api/project_statistics.rb
+++ b/lib/api/project_statistics.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module API
- class ProjectStatistics < Grape::API
+ class ProjectStatistics < Grape::API::Instance
before do
authenticate!
authorize! :daily_statistics, user_project
diff --git a/lib/api/project_templates.rb b/lib/api/project_templates.rb
index cfcc7f5212d..f0fe4d85c8f 100644
--- a/lib/api/project_templates.rb
+++ b/lib/api/project_templates.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module API
- class ProjectTemplates < Grape::API
+ class ProjectTemplates < Grape::API::Instance
include PaginationParams
TEMPLATE_TYPES = %w[dockerfiles gitignores gitlab_ci_ymls licenses].freeze
diff --git a/lib/api/projects.rb b/lib/api/projects.rb
index e00fb61f478..d24dab63bd9 100644
--- a/lib/api/projects.rb
+++ b/lib/api/projects.rb
@@ -3,7 +3,7 @@
require_dependency 'declarative_policy'
module API
- class Projects < Grape::API
+ class Projects < Grape::API::Instance
include PaginationParams
include Helpers::CustomAttributes
@@ -17,6 +17,7 @@ module API
projects = projects.with_issues_available_for_user(current_user) if params[:with_issues_enabled]
projects = projects.with_merge_requests_enabled if params[:with_merge_requests_enabled]
projects = projects.with_statistics if params[:statistics]
+ projects = projects.joins(:statistics) if params[:order_by].include?('project_statistics') # rubocop: disable CodeReuse/ActiveRecord
lang = params[:with_programming_language]
projects = projects.with_programming_language(lang) if lang
@@ -28,6 +29,20 @@ module API
attrs.delete(:repository_storage) unless can?(current_user, :change_repository_storage, project)
end
+ def verify_project_filters!(attrs)
+ attrs.delete(:repository_storage) unless can?(current_user, :use_project_statistics_filters)
+ end
+
+ def verify_statistics_order_by_projects!
+ return unless Helpers::ProjectsHelpers::STATISTICS_SORT_PARAMS.include?(params[:order_by])
+
+ params[:order_by] = if can?(current_user, :use_project_statistics_filters)
+ "project_statistics.#{params[:order_by]}"
+ else
+ route.params['order_by'][:default]
+ end
+ end
+
def delete_project(user_project)
destroy_conditionally!(user_project) do
::Projects::DestroyService.new(user_project, current_user, {}).async_execute
@@ -52,8 +67,9 @@ module API
end
params :sort_params do
- optional :order_by, type: String, values: %w[id name path created_at updated_at last_activity_at],
- default: 'created_at', desc: 'Return projects ordered by field'
+ optional :order_by, type: String,
+ values: %w[id name path created_at updated_at last_activity_at] + Helpers::ProjectsHelpers::STATISTICS_SORT_PARAMS,
+ default: 'created_at', desc: "Return projects ordered by field. #{Helpers::ProjectsHelpers::STATISTICS_SORT_PARAMS.join(', ')} are only available to admins."
optional :sort, type: String, values: %w[asc desc], default: 'desc',
desc: 'Return projects sorted in ascending and descending order'
end
@@ -75,6 +91,7 @@ module API
optional :id_before, type: Integer, desc: 'Limit results to projects with IDs less than the specified ID'
optional :last_activity_after, type: DateTime, desc: 'Limit results to projects with last_activity after specified time. Format: ISO 8601 YYYY-MM-DDTHH:MM:SSZ'
optional :last_activity_before, type: DateTime, desc: 'Limit results to projects with last_activity before specified time. Format: ISO 8601 YYYY-MM-DDTHH:MM:SSZ'
+ optional :repository_storage, type: String, desc: 'Which storage shard the repository is on. Available only to admins'
use :optional_filter_params_ee
end
@@ -88,10 +105,15 @@ module API
end
def load_projects
- ProjectsFinder.new(current_user: current_user, params: project_finder_params).execute
+ params = project_finder_params
+ verify_project_filters!(params)
+
+ ProjectsFinder.new(current_user: current_user, params: params).execute
end
def present_projects(projects, options = {})
+ verify_statistics_order_by_projects!
+
projects = reorder_projects(projects)
projects = apply_filters(projects)
@@ -524,7 +546,7 @@ module API
end
params do
optional :search, type: String, desc: 'Return list of users matching the search criteria'
- optional :skip_users, type: Array[Integer], desc: 'Filter out users with the specified IDs'
+ optional :skip_users, type: Array[Integer], coerce_with: ::API::Validations::Types::CommaSeparatedToIntegerArray.coerce, desc: 'Filter out users with the specified IDs'
use :pagination
end
get ':id/users' do
diff --git a/lib/api/projects_relation_builder.rb b/lib/api/projects_relation_builder.rb
index 263468c9aa6..6dfd82d109f 100644
--- a/lib/api/projects_relation_builder.rb
+++ b/lib/api/projects_relation_builder.rb
@@ -8,6 +8,10 @@ module API
def prepare_relation(projects_relation, options = {})
projects_relation = preload_relation(projects_relation, options)
execute_batch_counting(projects_relation)
+ # Call the forks count method on every project, so the BatchLoader would load them all at
+ # once when the entities are rendered
+ projects_relation.each(&:forks_count)
+
projects_relation
end
@@ -19,16 +23,11 @@ module API
projects_relation
end
- def batch_forks_counting(projects_relation)
- ::Projects::BatchForksCountService.new(forks_counting_projects(projects_relation)).refresh_cache
- end
-
def batch_open_issues_counting(projects_relation)
::Projects::BatchOpenIssuesCountService.new(projects_relation).refresh_cache
end
def execute_batch_counting(projects_relation)
- batch_forks_counting(projects_relation)
batch_open_issues_counting(projects_relation)
end
end
diff --git a/lib/api/protected_branches.rb b/lib/api/protected_branches.rb
index 1fd86d1e720..b0a7f898eec 100644
--- a/lib/api/protected_branches.rb
+++ b/lib/api/protected_branches.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module API
- class ProtectedBranches < Grape::API
+ class ProtectedBranches < Grape::API::Instance
include PaginationParams
BRANCH_ENDPOINT_REQUIREMENTS = API::NAMESPACE_OR_PROJECT_REQUIREMENTS.merge(name: API::NO_SLASH_URL_PART_REGEX)
diff --git a/lib/api/protected_tags.rb b/lib/api/protected_tags.rb
index ee13473c848..aaa31cb7cc6 100644
--- a/lib/api/protected_tags.rb
+++ b/lib/api/protected_tags.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module API
- class ProtectedTags < Grape::API
+ class ProtectedTags < Grape::API::Instance
include PaginationParams
TAG_ENDPOINT_REQUIREMENTS = API::NAMESPACE_OR_PROJECT_REQUIREMENTS.merge(name: API::NO_SLASH_URL_PART_REGEX)
diff --git a/lib/api/pypi_packages.rb b/lib/api/pypi_packages.rb
new file mode 100644
index 00000000000..a6caacd7df8
--- /dev/null
+++ b/lib/api/pypi_packages.rb
@@ -0,0 +1,148 @@
+# frozen_string_literal: true
+
+# PyPI Package Manager Client API
+#
+# These API endpoints are not meant to be consumed directly by users. They are
+# called by the PyPI package manager client when users run commands
+# like `pip install` or `twine upload`.
+module API
+ class PypiPackages < Grape::API::Instance
+ helpers ::API::Helpers::PackagesManagerClientsHelpers
+ helpers ::API::Helpers::RelatedResourcesHelpers
+ helpers ::API::Helpers::Packages::BasicAuthHelpers
+ include ::API::Helpers::Packages::BasicAuthHelpers::Constants
+
+ default_format :json
+
+ rescue_from ArgumentError do |e|
+ render_api_error!(e.message, 400)
+ end
+
+ rescue_from ActiveRecord::RecordInvalid do |e|
+ render_api_error!(e.message, 400)
+ end
+
+ rescue_from ActiveRecord::RecordInvalid do |e|
+ render_api_error!(e.message, 400)
+ end
+
+ helpers do
+ def packages_finder(project = authorized_user_project)
+ project
+ .packages
+ .pypi
+ .has_version
+ .processed
+ end
+
+ def find_package_versions
+ packages = packages_finder
+ .with_name(params[:package_name])
+
+ not_found!('Package') if packages.empty?
+
+ packages
+ end
+ end
+
+ before do
+ require_packages_enabled!
+ end
+
+ params do
+ requires :id, type: Integer, desc: 'The ID of a project'
+ end
+
+ resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
+ before do
+ unauthorized_user_project!
+ end
+
+ namespace ':id/packages/pypi' do
+ desc 'The PyPi package download endpoint' do
+ detail 'This feature was introduced in GitLab 12.10'
+ end
+
+ params do
+ requires :file_identifier, type: String, desc: 'The PyPi package file identifier', file_path: true
+ requires :sha256, type: String, desc: 'The PyPi package sha256 check sum'
+ end
+
+ route_setting :authentication, deploy_token_allowed: true
+ get 'files/:sha256/*file_identifier' do
+ project = unauthorized_user_project!
+
+ filename = "#{params[:file_identifier]}.#{params[:format]}"
+ package = packages_finder(project).by_file_name_and_sha256(filename, params[:sha256])
+ package_file = ::Packages::PackageFileFinder.new(package, filename, with_file_name_like: false).execute
+
+ track_event('pull_package')
+
+ present_carrierwave_file!(package_file.file, supports_direct_download: true)
+ end
+
+ desc 'The PyPi Simple Endpoint' do
+ detail 'This feature was introduced in GitLab 12.10'
+ end
+
+ params do
+ requires :package_name, type: String, file_path: true, desc: 'The PyPi package name'
+ end
+
+ # An Api entry point but returns an HTML file instead of JSON.
+ # PyPi simple API returns the package descriptor as a simple HTML file.
+ route_setting :authentication, deploy_token_allowed: true
+ get 'simple/*package_name', format: :txt do
+ authorize_read_package!(authorized_user_project)
+
+ track_event('list_package')
+
+ packages = find_package_versions
+ presenter = ::Packages::Pypi::PackagePresenter.new(packages, authorized_user_project)
+
+ # Adjusts grape output format
+ # to be HTML
+ content_type "text/html; charset=utf-8"
+ env['api.format'] = :binary
+
+ body presenter.body
+ end
+
+ desc 'The PyPi Package upload endpoint' do
+ detail 'This feature was introduced in GitLab 12.10'
+ end
+
+ params do
+ requires :content, type: ::API::Validations::Types::WorkhorseFile, desc: 'The package file to be published (generated by Multipart middleware)'
+ requires :requires_python, type: String
+ requires :name, type: String
+ requires :version, type: String
+ optional :md5_digest, type: String
+ optional :sha256_digest, type: String
+ end
+
+ route_setting :authentication, deploy_token_allowed: true
+ post do
+ authorize_upload!(authorized_user_project)
+
+ track_event('push_package')
+
+ ::Packages::Pypi::CreatePackageService
+ .new(authorized_user_project, current_user, declared_params)
+ .execute
+
+ created!
+ rescue ObjectStorage::RemoteStoreError => e
+ Gitlab::ErrorTracking.track_exception(e, extra: { file_name: params[:name], project_id: authorized_user_project.id })
+
+ forbidden!
+ end
+
+ route_setting :authentication, deploy_token_allowed: true
+ post 'authorize' do
+ authorize_workhorse!(subject: authorized_user_project, has_length: false)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/api/release/links.rb b/lib/api/release/links.rb
index 07c27f39539..7e1815480a5 100644
--- a/lib/api/release/links.rb
+++ b/lib/api/release/links.rb
@@ -2,7 +2,7 @@
module API
module Release
- class Links < Grape::API
+ class Links < Grape::API::Instance
include PaginationParams
RELEASE_ENDPOINT_REQUIREMENTS = API::NAMESPACE_OR_PROJECT_REQUIREMENTS
diff --git a/lib/api/releases.rb b/lib/api/releases.rb
index a5bb1a44f1f..30c5e06053e 100644
--- a/lib/api/releases.rb
+++ b/lib/api/releases.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module API
- class Releases < Grape::API
+ class Releases < Grape::API::Instance
include PaginationParams
RELEASE_ENDPOINT_REQUIREMENTS = API::NAMESPACE_OR_PROJECT_REQUIREMENTS
@@ -54,7 +54,7 @@ module API
requires :url, type: String
end
end
- optional :milestones, type: Array, desc: 'The titles of the related milestones', default: []
+ optional :milestones, type: Array[String], coerce_with: ::API::Validations::Types::CommaSeparatedToArray.coerce, desc: 'The titles of the related milestones', default: []
optional :released_at, type: DateTime, desc: 'The date when the release will be/was ready. Defaults to the current time.'
end
route_setting :authentication, job_token_allowed: true
diff --git a/lib/api/remote_mirrors.rb b/lib/api/remote_mirrors.rb
index 0808541d3c7..d1def05808b 100644
--- a/lib/api/remote_mirrors.rb
+++ b/lib/api/remote_mirrors.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module API
- class RemoteMirrors < Grape::API
+ class RemoteMirrors < Grape::API::Instance
include PaginationParams
before do
diff --git a/lib/api/repositories.rb b/lib/api/repositories.rb
index bf4f08ce390..81702f8f02a 100644
--- a/lib/api/repositories.rb
+++ b/lib/api/repositories.rb
@@ -3,7 +3,7 @@
require 'mime/types'
module API
- class Repositories < Grape::API
+ class Repositories < Grape::API::Instance
include PaginationParams
helpers ::API::Helpers::HeadersHelpers
@@ -143,7 +143,7 @@ module API
success Entities::Commit
end
params do
- requires :refs, type: Array[String]
+ requires :refs, type: Array[String], coerce_with: ::API::Validations::Types::CommaSeparatedToArray.coerce
end
get ':id/repository/merge_base' do
refs = params[:refs]
diff --git a/lib/api/resource_label_events.rb b/lib/api/resource_label_events.rb
index 1fa6898b92c..a8d3419528c 100644
--- a/lib/api/resource_label_events.rb
+++ b/lib/api/resource_label_events.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module API
- class ResourceLabelEvents < Grape::API
+ class ResourceLabelEvents < Grape::API::Instance
include PaginationParams
helpers ::API::Helpers::NotesHelpers
diff --git a/lib/api/resource_milestone_events.rb b/lib/api/resource_milestone_events.rb
index 30ff5a9b4be..a8f221f8740 100644
--- a/lib/api/resource_milestone_events.rb
+++ b/lib/api/resource_milestone_events.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module API
- class ResourceMilestoneEvents < Grape::API
+ class ResourceMilestoneEvents < Grape::API::Instance
include PaginationParams
helpers ::API::Helpers::NotesHelpers
@@ -26,8 +26,7 @@ module API
get ":id/#{eventables_str}/:eventable_id/resource_milestone_events" do
eventable = find_noteable(eventable_type, params[:eventable_id])
- opts = { page: params[:page], per_page: params[:per_page] }
- events = ResourceMilestoneEventFinder.new(current_user, eventable, opts).execute
+ events = ResourceMilestoneEventFinder.new(current_user, eventable).execute
present paginate(events), with: Entities::ResourceMilestoneEvent
end
diff --git a/lib/api/resource_state_events.rb b/lib/api/resource_state_events.rb
new file mode 100644
index 00000000000..1c1a90c09a3
--- /dev/null
+++ b/lib/api/resource_state_events.rb
@@ -0,0 +1,50 @@
+# frozen_string_literal: true
+
+module API
+ class ResourceStateEvents < Grape::API::Instance
+ include PaginationParams
+ helpers ::API::Helpers::NotesHelpers
+
+ before { authenticate! }
+
+ [Issue, MergeRequest].each do |eventable_class|
+ eventable_name = eventable_class.to_s.underscore
+
+ params do
+ requires :id, type: String, desc: "The ID of a project"
+ end
+ resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
+ desc "Get a list of #{eventable_class.to_s.downcase} resource state events" do
+ success Entities::ResourceStateEvent
+ end
+ params do
+ requires :eventable_iid, types: Integer, desc: "The IID of the #{eventable_name}"
+ use :pagination
+ end
+
+ get ":id/#{eventable_name.pluralize}/:eventable_iid/resource_state_events" do
+ eventable = find_noteable(eventable_class, params[:eventable_iid])
+
+ events = ResourceStateEventFinder.new(current_user, eventable).execute
+
+ present paginate(events), with: Entities::ResourceStateEvent
+ end
+
+ desc "Get a single #{eventable_class.to_s.downcase} resource state event" do
+ success Entities::ResourceStateEvent
+ end
+ params do
+ requires :eventable_iid, types: Integer, desc: "The IID of the #{eventable_name}"
+ requires :event_id, type: Integer, desc: 'The ID of a resource state event'
+ end
+ get ":id/#{eventable_name.pluralize}/:eventable_iid/resource_state_events/:event_id" do
+ eventable = find_noteable(eventable_class, params[:eventable_iid])
+
+ event = ResourceStateEventFinder.new(current_user, eventable).find(params[:event_id])
+
+ present event, with: Entities::ResourceStateEvent
+ end
+ end
+ end
+ end
+end
diff --git a/lib/api/runner.rb b/lib/api/runner.rb
deleted file mode 100644
index 5f08ebe4a06..00000000000
--- a/lib/api/runner.rb
+++ /dev/null
@@ -1,297 +0,0 @@
-# frozen_string_literal: true
-
-module API
- class Runner < Grape::API
- helpers ::API::Helpers::Runner
-
- resource :runners do
- desc 'Registers a new Runner' do
- success Entities::RunnerRegistrationDetails
- http_codes [[201, 'Runner was created'], [403, 'Forbidden']]
- end
- params do
- requires :token, type: String, desc: 'Registration token'
- optional :description, type: String, desc: %q(Runner's description)
- optional :info, type: Hash, desc: %q(Runner's metadata)
- optional :active, type: Boolean, desc: 'Should Runner be active'
- optional :locked, type: Boolean, desc: 'Should Runner be locked for current project'
- optional :access_level, type: String, values: Ci::Runner.access_levels.keys,
- desc: 'The access_level of the runner'
- optional :run_untagged, type: Boolean, desc: 'Should Runner handle untagged jobs'
- optional :tag_list, type: Array[String], desc: %q(List of Runner's tags)
- optional :maximum_timeout, type: Integer, desc: 'Maximum timeout set when this Runner will handle the job'
- end
- post '/' do
- attributes = attributes_for_keys([:description, :active, :locked, :run_untagged, :tag_list, :access_level, :maximum_timeout])
- .merge(get_runner_details_from_request)
-
- attributes =
- if runner_registration_token_valid?
- # Create shared runner. Requires admin access
- attributes.merge(runner_type: :instance_type)
- elsif project = Project.find_by_runners_token(params[:token])
- # Create a specific runner for the project
- attributes.merge(runner_type: :project_type, projects: [project])
- elsif group = Group.find_by_runners_token(params[:token])
- # Create a specific runner for the group
- attributes.merge(runner_type: :group_type, groups: [group])
- else
- forbidden!
- end
-
- runner = Ci::Runner.create(attributes)
-
- if runner.persisted?
- present runner, with: Entities::RunnerRegistrationDetails
- else
- render_validation_error!(runner)
- end
- end
-
- desc 'Deletes a registered Runner' do
- http_codes [[204, 'Runner was deleted'], [403, 'Forbidden']]
- end
- params do
- requires :token, type: String, desc: %q(Runner's authentication token)
- end
- delete '/' do
- authenticate_runner!
-
- runner = Ci::Runner.find_by_token(params[:token])
-
- destroy_conditionally!(runner)
- end
-
- desc 'Validates authentication credentials' do
- http_codes [[200, 'Credentials are valid'], [403, 'Forbidden']]
- end
- params do
- requires :token, type: String, desc: %q(Runner's authentication token)
- end
- post '/verify' do
- authenticate_runner!
- status 200
- end
- end
-
- resource :jobs do
- before do
- Gitlab::ApplicationContext.push(
- user: -> { current_job&.user },
- project: -> { current_job&.project }
- )
- end
-
- desc 'Request a job' do
- success Entities::JobRequest::Response
- http_codes [[201, 'Job was scheduled'],
- [204, 'No job for Runner'],
- [403, 'Forbidden']]
- end
- params do
- requires :token, type: String, desc: %q(Runner's authentication token)
- optional :last_update, type: String, desc: %q(Runner's queue last_update token)
- optional :info, type: Hash, desc: %q(Runner's metadata) do
- optional :name, type: String, desc: %q(Runner's name)
- optional :version, type: String, desc: %q(Runner's version)
- optional :revision, type: String, desc: %q(Runner's revision)
- optional :platform, type: String, desc: %q(Runner's platform)
- optional :architecture, type: String, desc: %q(Runner's architecture)
- optional :executor, type: String, desc: %q(Runner's executor)
- optional :features, type: Hash, desc: %q(Runner's features)
- end
- optional :session, type: Hash, desc: %q(Runner's session data) do
- optional :url, type: String, desc: %q(Session's url)
- optional :certificate, type: String, desc: %q(Session's certificate)
- optional :authorization, type: String, desc: %q(Session's authorization)
- end
- optional :job_age, type: Integer, desc: %q(Job should be older than passed age in seconds to be ran on runner)
- end
- post '/request' do
- authenticate_runner!
-
- unless current_runner.active?
- header 'X-GitLab-Last-Update', current_runner.ensure_runner_queue_value
- break no_content!
- end
-
- runner_params = declared_params(include_missing: false)
-
- if current_runner.runner_queue_value_latest?(runner_params[:last_update])
- header 'X-GitLab-Last-Update', runner_params[:last_update]
- Gitlab::Metrics.add_event(:build_not_found_cached)
- break no_content!
- end
-
- new_update = current_runner.ensure_runner_queue_value
- result = ::Ci::RegisterJobService.new(current_runner).execute(runner_params)
-
- if result.valid?
- if result.build
- Gitlab::Metrics.add_event(:build_found)
- present Ci::BuildRunnerPresenter.new(result.build), with: Entities::JobRequest::Response
- else
- Gitlab::Metrics.add_event(:build_not_found)
- header 'X-GitLab-Last-Update', new_update
- no_content!
- end
- else
- # We received build that is invalid due to concurrency conflict
- Gitlab::Metrics.add_event(:build_invalid)
- conflict!
- end
- end
-
- desc 'Updates a job' do
- http_codes [[200, 'Job was updated'], [403, 'Forbidden']]
- end
- params do
- requires :token, type: String, desc: %q(Runners's authentication token)
- requires :id, type: Integer, desc: %q(Job's ID)
- optional :trace, type: String, desc: %q(Job's full trace)
- optional :state, type: String, desc: %q(Job's status: success, failed)
- optional :failure_reason, type: String, desc: %q(Job's failure_reason)
- end
- put '/:id' do
- job = authenticate_job!
-
- job.trace.set(params[:trace]) if params[:trace]
-
- Gitlab::Metrics.add_event(:update_build)
-
- case params[:state].to_s
- when 'running'
- job.touch if job.needs_touch?
- when 'success'
- job.success!
- when 'failed'
- job.drop!(params[:failure_reason] || :unknown_failure)
- end
- end
-
- desc 'Appends a patch to the job trace' do
- http_codes [[202, 'Trace was patched'],
- [400, 'Missing Content-Range header'],
- [403, 'Forbidden'],
- [416, 'Range not satisfiable']]
- end
- params do
- requires :id, type: Integer, desc: %q(Job's ID)
- optional :token, type: String, desc: %q(Job's authentication token)
- end
- patch '/:id/trace' do
- job = authenticate_job!
-
- error!('400 Missing header Content-Range', 400) unless request.headers.key?('Content-Range')
- content_range = request.headers['Content-Range']
- content_range = content_range.split('-')
-
- # TODO:
- # it seems that `Content-Range` as formatted by runner is wrong,
- # the `byte_end` should point to final byte, but it points byte+1
- # that means that we have to calculate end of body,
- # as we cannot use `content_length[1]`
- # Issue: https://gitlab.com/gitlab-org/gitlab-runner/issues/3275
-
- body_data = request.body.read
- body_start = content_range[0].to_i
- body_end = body_start + body_data.bytesize
-
- stream_size = job.trace.append(body_data, body_start)
- unless stream_size == body_end
- break error!('416 Range Not Satisfiable', 416, { 'Range' => "0-#{stream_size}" })
- end
-
- status 202
- header 'Job-Status', job.status
- header 'Range', "0-#{stream_size}"
- header 'X-GitLab-Trace-Update-Interval', job.trace.update_interval.to_s
- end
-
- desc 'Authorize artifacts uploading for job' do
- http_codes [[200, 'Upload allowed'],
- [403, 'Forbidden'],
- [405, 'Artifacts support not enabled'],
- [413, 'File too large']]
- end
- params do
- requires :id, type: Integer, desc: %q(Job's ID)
- optional :token, type: String, desc: %q(Job's authentication token)
- optional :filesize, type: Integer, desc: %q(Artifacts filesize)
- optional :artifact_type, type: String, desc: %q(The type of artifact),
- default: 'archive', values: Ci::JobArtifact.file_types.keys
- end
- post '/:id/artifacts/authorize' do
- not_allowed! unless Gitlab.config.artifacts.enabled
- require_gitlab_workhorse!
- Gitlab::Workhorse.verify_api_request!(headers)
-
- job = authenticate_job!
-
- service = Ci::AuthorizeJobArtifactService.new(job, params, max_size: max_artifacts_size(job))
-
- forbidden! if service.forbidden?
- file_too_large! if service.too_large?
-
- status 200
- content_type Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE
- service.headers
- end
-
- desc 'Upload artifacts for job' do
- success Entities::JobRequest::Response
- http_codes [[201, 'Artifact uploaded'],
- [400, 'Bad request'],
- [403, 'Forbidden'],
- [405, 'Artifacts support not enabled'],
- [413, 'File too large']]
- end
- params do
- requires :id, type: Integer, desc: %q(Job's ID)
- requires :file, type: ::API::Validations::Types::WorkhorseFile, desc: %(The artifact file to store (generated by Multipart middleware))
- optional :token, type: String, desc: %q(Job's authentication token)
- optional :expire_in, type: String, desc: %q(Specify when artifacts should expire)
- optional :artifact_type, type: String, desc: %q(The type of artifact),
- default: 'archive', values: Ci::JobArtifact.file_types.keys
- optional :artifact_format, type: String, desc: %q(The format of artifact),
- default: 'zip', values: Ci::JobArtifact.file_formats.keys
- optional :metadata, type: ::API::Validations::Types::WorkhorseFile, desc: %(The artifact metadata to store (generated by Multipart middleware))
- end
- post '/:id/artifacts' do
- not_allowed! unless Gitlab.config.artifacts.enabled
- require_gitlab_workhorse!
-
- job = authenticate_job!
-
- artifacts = params[:file]
- metadata = params[:metadata]
-
- file_too_large! unless artifacts.size < max_artifacts_size(job)
-
- result = Ci::CreateJobArtifactsService.new(job.project).execute(job, artifacts, params, metadata_file: metadata)
-
- if result[:status] == :success
- status :created
- else
- render_api_error!(result[:message], result[:http_status])
- end
- end
-
- desc 'Download the artifacts file for job' do
- http_codes [[200, 'Upload allowed'],
- [403, 'Forbidden'],
- [404, 'Artifact not found']]
- end
- params do
- requires :id, type: Integer, desc: %q(Job's ID)
- optional :token, type: String, desc: %q(Job's authentication token)
- optional :direct_download, default: false, type: Boolean, desc: %q(Perform direct download from remote storage instead of proxying artifacts)
- end
- get '/:id/artifacts' do
- job = authenticate_job!(require_running: false)
-
- present_carrierwave_file!(job.artifacts_file, supports_direct_download: params[:direct_download])
- end
- end
- end
-end
diff --git a/lib/api/runners.rb b/lib/api/runners.rb
deleted file mode 100644
index 43ee1dd1f71..00000000000
--- a/lib/api/runners.rb
+++ /dev/null
@@ -1,287 +0,0 @@
-# frozen_string_literal: true
-
-module API
- class Runners < Grape::API
- include PaginationParams
-
- before { authenticate! }
-
- resource :runners do
- desc 'Get runners available for user' do
- success Entities::Runner
- end
- params do
- optional :scope, type: String, values: Ci::Runner::AVAILABLE_STATUSES,
- desc: 'The scope of specific runners to show'
- optional :type, type: String, values: Ci::Runner::AVAILABLE_TYPES,
- desc: 'The type of the runners to show'
- optional :status, type: String, values: Ci::Runner::AVAILABLE_STATUSES,
- desc: 'The status of the runners to show'
- optional :tag_list, type: Array[String], desc: 'The tags of the runners to show'
- use :pagination
- end
- get do
- runners = current_user.ci_owned_runners
- runners = filter_runners(runners, params[:scope], allowed_scopes: Ci::Runner::AVAILABLE_STATUSES)
- runners = filter_runners(runners, params[:type], allowed_scopes: Ci::Runner::AVAILABLE_TYPES)
- runners = filter_runners(runners, params[:status], allowed_scopes: Ci::Runner::AVAILABLE_STATUSES)
- runners = runners.tagged_with(params[:tag_list]) if params[:tag_list]
-
- present paginate(runners), with: Entities::Runner
- end
-
- desc 'Get all runners - shared and specific' do
- success Entities::Runner
- end
- params do
- optional :scope, type: String, values: Ci::Runner::AVAILABLE_SCOPES,
- desc: 'The scope of specific runners to show'
- optional :type, type: String, values: Ci::Runner::AVAILABLE_TYPES,
- desc: 'The type of the runners to show'
- optional :status, type: String, values: Ci::Runner::AVAILABLE_STATUSES,
- desc: 'The status of the runners to show'
- optional :tag_list, type: Array[String], desc: 'The tags of the runners to show'
- use :pagination
- end
- get 'all' do
- authenticated_as_admin!
-
- runners = Ci::Runner.all
- runners = filter_runners(runners, params[:scope])
- runners = filter_runners(runners, params[:type], allowed_scopes: Ci::Runner::AVAILABLE_TYPES)
- runners = filter_runners(runners, params[:status], allowed_scopes: Ci::Runner::AVAILABLE_STATUSES)
- runners = runners.tagged_with(params[:tag_list]) if params[:tag_list]
-
- present paginate(runners), with: Entities::Runner
- end
-
- desc "Get runner's details" do
- success Entities::RunnerDetails
- end
- params do
- requires :id, type: Integer, desc: 'The ID of the runner'
- end
- get ':id' do
- runner = get_runner(params[:id])
- authenticate_show_runner!(runner)
-
- present runner, with: Entities::RunnerDetails, current_user: current_user
- end
-
- desc "Update runner's details" do
- success Entities::RunnerDetails
- end
- params do
- requires :id, type: Integer, desc: 'The ID of the runner'
- optional :description, type: String, desc: 'The description of the runner'
- optional :active, type: Boolean, desc: 'The state of a runner'
- optional :tag_list, type: Array[String], desc: 'The list of tags for a runner'
- optional :run_untagged, type: Boolean, desc: 'Flag indicating the runner can execute untagged jobs'
- optional :locked, type: Boolean, desc: 'Flag indicating the runner is locked'
- optional :access_level, type: String, values: Ci::Runner.access_levels.keys,
- desc: 'The access_level of the runner'
- optional :maximum_timeout, type: Integer, desc: 'Maximum timeout set when this Runner will handle the job'
- at_least_one_of :description, :active, :tag_list, :run_untagged, :locked, :access_level, :maximum_timeout
- end
- put ':id' do
- runner = get_runner(params.delete(:id))
- authenticate_update_runner!(runner)
- update_service = Ci::UpdateRunnerService.new(runner)
-
- if update_service.update(declared_params(include_missing: false))
- present runner, with: Entities::RunnerDetails, current_user: current_user
- else
- render_validation_error!(runner)
- end
- end
-
- desc 'Remove a runner' do
- success Entities::Runner
- end
- params do
- requires :id, type: Integer, desc: 'The ID of the runner'
- end
- delete ':id' do
- runner = get_runner(params[:id])
-
- authenticate_delete_runner!(runner)
-
- destroy_conditionally!(runner)
- end
-
- desc 'List jobs running on a runner' do
- success Entities::JobBasicWithProject
- end
- params do
- requires :id, type: Integer, desc: 'The ID of the runner'
- optional :status, type: String, desc: 'Status of the job', values: Ci::Build::AVAILABLE_STATUSES
- optional :order_by, type: String, desc: 'Order by `id` or not', values: Ci::RunnerJobsFinder::ALLOWED_INDEXED_COLUMNS
- optional :sort, type: String, values: %w[asc desc], default: 'desc', desc: 'Sort by asc (ascending) or desc (descending)'
- use :pagination
- end
- get ':id/jobs' do
- runner = get_runner(params[:id])
- authenticate_list_runners_jobs!(runner)
-
- jobs = Ci::RunnerJobsFinder.new(runner, params).execute
-
- present paginate(jobs), with: Entities::JobBasicWithProject
- end
- end
-
- params do
- requires :id, type: String, desc: 'The ID of a project'
- end
- resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
- before { authorize_admin_project }
-
- desc 'Get runners available for project' do
- success Entities::Runner
- end
- params do
- optional :scope, type: String, values: Ci::Runner::AVAILABLE_SCOPES,
- desc: 'The scope of specific runners to show'
- optional :type, type: String, values: Ci::Runner::AVAILABLE_TYPES,
- desc: 'The type of the runners to show'
- optional :status, type: String, values: Ci::Runner::AVAILABLE_STATUSES,
- desc: 'The status of the runners to show'
- optional :tag_list, type: Array[String], desc: 'The tags of the runners to show'
- use :pagination
- end
- get ':id/runners' do
- runners = Ci::Runner.owned_or_instance_wide(user_project.id)
- # scope is deprecated (for project runners), however api documentation still supports it.
- # Not including them in `apply_filter` method as it's not supported for group runners
- runners = filter_runners(runners, params[:scope])
- runners = apply_filter(runners, params)
-
- present paginate(runners), with: Entities::Runner
- end
-
- desc 'Enable a runner for a project' do
- success Entities::Runner
- end
- params do
- requires :runner_id, type: Integer, desc: 'The ID of the runner'
- end
- post ':id/runners' do
- runner = get_runner(params[:runner_id])
- authenticate_enable_runner!(runner)
-
- if runner.assign_to(user_project)
- present runner, with: Entities::Runner
- else
- render_validation_error!(runner)
- end
- end
-
- desc "Disable project's runner" do
- success Entities::Runner
- end
- params do
- requires :runner_id, type: Integer, desc: 'The ID of the runner'
- end
- # rubocop: disable CodeReuse/ActiveRecord
- delete ':id/runners/:runner_id' do
- runner_project = user_project.runner_projects.find_by(runner_id: params[:runner_id])
- not_found!('Runner') unless runner_project
-
- runner = runner_project.runner
- forbidden!("Only one project associated with the runner. Please remove the runner instead") if runner.projects.count == 1
-
- destroy_conditionally!(runner_project)
- end
- # rubocop: enable CodeReuse/ActiveRecord
- end
-
- params do
- requires :id, type: String, desc: 'The ID of a group'
- end
- resource :groups, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
- before { authorize_admin_group }
-
- desc 'Get runners available for group' do
- success Entities::Runner
- end
- params do
- optional :type, type: String, values: Ci::Runner::AVAILABLE_TYPES,
- desc: 'The type of the runners to show'
- optional :status, type: String, values: Ci::Runner::AVAILABLE_STATUSES,
- desc: 'The status of the runners to show'
- optional :tag_list, type: Array[String], desc: 'The tags of the runners to show'
- use :pagination
- end
- get ':id/runners' do
- runners = Ci::Runner.belonging_to_group(user_group.id, include_ancestors: true)
- runners = apply_filter(runners, params)
-
- present paginate(runners), with: Entities::Runner
- end
- end
-
- helpers do
- def filter_runners(runners, scope, allowed_scopes: ::Ci::Runner::AVAILABLE_SCOPES)
- return runners unless scope.present?
-
- unless allowed_scopes.include?(scope)
- render_api_error!('Scope contains invalid value', 400)
- end
-
- # Support deprecated scopes
- if runners.respond_to?("deprecated_#{scope}")
- scope = "deprecated_#{scope}"
- end
-
- runners.public_send(scope) # rubocop:disable GitlabSecurity/PublicSend
- end
-
- def apply_filter(runners, params)
- runners = filter_runners(runners, params[:type], allowed_scopes: Ci::Runner::AVAILABLE_TYPES)
- runners = filter_runners(runners, params[:status], allowed_scopes: Ci::Runner::AVAILABLE_STATUSES)
- runners = runners.tagged_with(params[:tag_list]) if params[:tag_list]
-
- runners
- end
-
- def get_runner(id)
- runner = Ci::Runner.find(id)
- not_found!('Runner') unless runner
- runner
- end
-
- def authenticate_show_runner!(runner)
- return if runner.instance_type? || current_user.admin?
-
- forbidden!("No access granted") unless can?(current_user, :read_runner, runner)
- end
-
- def authenticate_update_runner!(runner)
- return if current_user.admin?
-
- forbidden!("No access granted") unless can?(current_user, :update_runner, runner)
- end
-
- def authenticate_delete_runner!(runner)
- return if current_user.admin?
-
- forbidden!("Runner associated with more than one project") if runner.projects.count > 1
- forbidden!("No access granted") unless can?(current_user, :delete_runner, runner)
- end
-
- def authenticate_enable_runner!(runner)
- forbidden!("Runner is a group runner") if runner.group_type?
-
- return if current_user.admin?
-
- forbidden!("Runner is locked") if runner.locked?
- forbidden!("No access granted") unless can?(current_user, :assign_runner, runner)
- end
-
- def authenticate_list_runners_jobs!(runner)
- return if current_user.admin?
-
- forbidden!("No access granted") unless can?(current_user, :read_runner, runner)
- end
- end
- end
-end
diff --git a/lib/api/search.rb b/lib/api/search.rb
index ac00d3682a0..53095e0b81a 100644
--- a/lib/api/search.rb
+++ b/lib/api/search.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module API
- class Search < Grape::API
+ class Search < Grape::API::Instance
include PaginationParams
before { authenticate! }
@@ -24,7 +24,8 @@ module API
merge_requests: :with_api_entity_associations,
projects: :with_api_entity_associations,
issues: :with_api_entity_associations,
- milestones: :with_api_entity_associations
+ milestones: :with_api_entity_associations,
+ commits: :with_api_commit_entity_associations
}.freeze
def search(additional_params = {})
diff --git a/lib/api/services.rb b/lib/api/services.rb
index 5fd5c6bd9b0..9ee1822339c 100644
--- a/lib/api/services.rb
+++ b/lib/api/services.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
module API
- class Services < Grape::API
+ class Services < Grape::API::Instance
services = Helpers::ServicesHelpers.services
service_classes = Helpers::ServicesHelpers.service_classes
diff --git a/lib/api/settings.rb b/lib/api/settings.rb
index 0bf5eed26b4..3463e29041b 100644
--- a/lib/api/settings.rb
+++ b/lib/api/settings.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module API
- class Settings < Grape::API
+ class Settings < Grape::API::Instance
before { authenticated_as_admin! }
helpers Helpers::SettingsHelpers
@@ -49,7 +49,7 @@ module API
optional :default_project_visibility, type: String, values: Gitlab::VisibilityLevel.string_values, desc: 'The default project visibility'
optional :default_projects_limit, type: Integer, desc: 'The maximum number of personal projects'
optional :default_snippet_visibility, type: String, values: Gitlab::VisibilityLevel.string_values, desc: 'The default snippet visibility'
- optional :disabled_oauth_sign_in_sources, type: Array[String], desc: 'Disable certain OAuth sign-in sources'
+ optional :disabled_oauth_sign_in_sources, type: Array[String], coerce_with: Validations::Types::CommaSeparatedToArray.coerce, desc: 'Disable certain OAuth sign-in sources'
optional :domain_blacklist_enabled, type: Boolean, desc: 'Enable domain blacklist for sign ups'
optional :domain_blacklist, type: Array[String], coerce_with: Validations::Types::CommaSeparatedToArray.coerce, desc: 'Users with e-mail addresses that match these domain(s) will NOT be able to sign-up. Wildcards allowed. Use separate lines for multiple entries. Ex: domain.com, *.domain.com'
optional :domain_whitelist, type: Array[String], coerce_with: Validations::Types::CommaSeparatedToArray.coerce, desc: 'ONLY users with e-mail addresses that match these domain(s) will be able to sign-up. Wildcards allowed. Use separate lines for multiple entries. Ex: domain.com, *.domain.com'
@@ -79,7 +79,8 @@ module API
requires :housekeeping_incremental_repack_period, type: Integer, desc: "Number of Git pushes after which an incremental 'git repack' is run."
end
optional :html_emails_enabled, type: Boolean, desc: 'By default GitLab sends emails in HTML and plain text formats so mail clients can choose what format to use. Disable this option if you only want to send emails in plain text format.'
- optional :import_sources, type: Array[String], values: %w[github bitbucket bitbucket_server gitlab google_code fogbugz git gitlab_project gitea manifest phabricator],
+ optional :import_sources, type: Array[String], coerce_with: Validations::Types::CommaSeparatedToArray.coerce,
+ values: %w[github bitbucket bitbucket_server gitlab google_code fogbugz git gitlab_project gitea manifest phabricator],
desc: 'Enabled sources for code import during project creation. OmniAuth must be configured for GitHub, Bitbucket, and GitLab.com'
optional :max_artifacts_size, type: Integer, desc: "Set the maximum file size for each job's artifacts"
optional :max_attachment_size, type: Integer, desc: 'Maximum attachment size in MB'
@@ -113,13 +114,13 @@ module API
requires :recaptcha_private_key, type: String, desc: 'Generate private key at http://www.google.com/recaptcha'
end
optional :repository_checks_enabled, type: Boolean, desc: "GitLab will periodically run 'git fsck' in all project and wiki repositories to look for silent disk corruption issues."
- optional :repository_storages, type: Array[String], desc: 'Storage paths for new projects'
+ optional :repository_storages, type: Array[String], coerce_with: Validations::Types::CommaSeparatedToArray.coerce, desc: 'Storage paths for new projects'
optional :repository_storages_weighted, type: Hash, desc: 'Storage paths for new projects with a weighted value between 0 and 100'
optional :require_two_factor_authentication, type: Boolean, desc: 'Require all users to set up Two-factor authentication'
given require_two_factor_authentication: ->(val) { val } do
requires :two_factor_grace_period, type: Integer, desc: 'Amount of time (in hours) that users are allowed to skip forced configuration of two-factor authentication'
end
- optional :restricted_visibility_levels, type: Array[String], desc: 'Selected levels cannot be used by non-admin users for groups, projects or snippets. If the public level is restricted, user profiles are only visible to logged in users.'
+ optional :restricted_visibility_levels, type: Array[String], coerce_with: Validations::Types::CommaSeparatedToArray.coerce, desc: 'Selected levels cannot be used by non-admin users for groups, projects or snippets. If the public level is restricted, user profiles are only visible to logged in users.'
optional :send_user_confirmation_email, type: Boolean, desc: 'Send confirmation email on sign-up'
optional :session_expire_delay, type: Integer, desc: 'Session duration in minutes. GitLab restart is required to apply changes.'
optional :shared_runners_enabled, type: Boolean, desc: 'Enable shared runners for new projects'
diff --git a/lib/api/sidekiq_metrics.rb b/lib/api/sidekiq_metrics.rb
index 693c20cb73a..de1373144e3 100644
--- a/lib/api/sidekiq_metrics.rb
+++ b/lib/api/sidekiq_metrics.rb
@@ -3,7 +3,7 @@
require 'sidekiq/api'
module API
- class SidekiqMetrics < Grape::API
+ class SidekiqMetrics < Grape::API::Instance
before { authenticated_as_admin! }
helpers do
diff --git a/lib/api/snippets.rb b/lib/api/snippets.rb
index be58b832f97..118045e3af2 100644
--- a/lib/api/snippets.rb
+++ b/lib/api/snippets.rb
@@ -2,7 +2,7 @@
module API
# Snippets API
- class Snippets < Grape::API
+ class Snippets < Grape::API::Instance
include PaginationParams
before { authenticate! }
@@ -31,7 +31,7 @@ module API
use :pagination
end
get do
- present paginate(snippets_for_current_user), with: Entities::Snippet
+ present paginate(snippets_for_current_user), with: Entities::Snippet, current_user: current_user
end
desc 'List all public personal snippets current_user has access to' do
@@ -42,7 +42,7 @@ module API
use :pagination
end
get 'public' do
- present paginate(public_snippets), with: Entities::PersonalSnippet
+ present paginate(public_snippets), with: Entities::PersonalSnippet, current_user: current_user
end
desc 'Get a single snippet' do
@@ -57,7 +57,7 @@ module API
break not_found!('Snippet') unless snippet
- present snippet, with: Entities::PersonalSnippet
+ present snippet, with: Entities::PersonalSnippet, current_user: current_user
end
desc 'Create new snippet' do
@@ -82,7 +82,7 @@ module API
snippet = service_response.payload[:snippet]
if service_response.success?
- present snippet, with: Entities::PersonalSnippet
+ present snippet, with: Entities::PersonalSnippet, current_user: current_user
else
render_spam_error! if snippet.spam?
@@ -116,7 +116,7 @@ module API
snippet = service_response.payload[:snippet]
if service_response.success?
- present snippet, with: Entities::PersonalSnippet
+ present snippet, with: Entities::PersonalSnippet, current_user: current_user
else
render_spam_error! if snippet.spam?
@@ -155,14 +155,22 @@ module API
end
get ":id/raw" do
snippet = snippets.find_by_id(params.delete(:id))
- break not_found!('Snippet') unless snippet
+ not_found!('Snippet') unless snippet
- env['api.format'] = :txt
- content_type 'text/plain'
- header['Content-Disposition'] = 'attachment'
present content_for(snippet)
end
+ desc 'Get raw snippet file contents from the repository'
+ params do
+ use :raw_file_params
+ end
+ get ":id/files/:ref/:file_path/raw", requirements: { file_path: API::NO_SLASH_URL_PART_REGEX } do
+ snippet = snippets.find_by_id(params.delete(:id))
+ not_found!('Snippet') unless snippet&.repo_exists?
+
+ present file_content_for(snippet)
+ end
+
desc 'Get the user agent details for a snippet' do
success Entities::UserAgentDetail
end
diff --git a/lib/api/statistics.rb b/lib/api/statistics.rb
index d2dce34dfa5..3869fd3ac76 100644
--- a/lib/api/statistics.rb
+++ b/lib/api/statistics.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module API
- class Statistics < Grape::API
+ class Statistics < Grape::API::Instance
before { authenticated_as_admin! }
COUNTED_ITEMS = [Project, User, Group, ForkNetworkMember, ForkNetwork, Issue,
diff --git a/lib/api/submodules.rb b/lib/api/submodules.rb
index 72d7d994102..34d21d3d7d8 100644
--- a/lib/api/submodules.rb
+++ b/lib/api/submodules.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module API
- class Submodules < Grape::API
+ class Submodules < Grape::API::Instance
before { authenticate! }
helpers do
diff --git a/lib/api/subscriptions.rb b/lib/api/subscriptions.rb
index dfb54446ddf..533663fb087 100644
--- a/lib/api/subscriptions.rb
+++ b/lib/api/subscriptions.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module API
- class Subscriptions < Grape::API
+ class Subscriptions < Grape::API::Instance
helpers ::API::Helpers::LabelHelpers
before { authenticate! }
diff --git a/lib/api/suggestions.rb b/lib/api/suggestions.rb
index 05aaa8a6f41..38e96c080f2 100644
--- a/lib/api/suggestions.rb
+++ b/lib/api/suggestions.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module API
- class Suggestions < Grape::API
+ class Suggestions < Grape::API::Instance
before { authenticate! }
resource :suggestions do
@@ -25,7 +25,7 @@ module API
success Entities::Suggestion
end
params do
- requires :ids, type: Array[String], desc: "An array of suggestion ID's"
+ requires :ids, type: Array[Integer], coerce_with: ::API::Validations::Types::CommaSeparatedToIntegerArray.coerce, desc: "An array of suggestion ID's"
end
put 'batch_apply' do
ids = params[:ids]
diff --git a/lib/api/system_hooks.rb b/lib/api/system_hooks.rb
index 51fae0e54aa..d8e0a425625 100644
--- a/lib/api/system_hooks.rb
+++ b/lib/api/system_hooks.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module API
- class SystemHooks < Grape::API
+ class SystemHooks < Grape::API::Instance
include PaginationParams
before do
diff --git a/lib/api/tags.rb b/lib/api/tags.rb
index 796b1450602..c1fbd3ca7c6 100644
--- a/lib/api/tags.rb
+++ b/lib/api/tags.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module API
- class Tags < Grape::API
+ class Tags < Grape::API::Instance
include PaginationParams
TAG_ENDPOINT_REQUIREMENTS = API::NAMESPACE_OR_PROJECT_REQUIREMENTS.merge(tag_name: API::NO_SLASH_URL_PART_REGEX)
diff --git a/lib/api/templates.rb b/lib/api/templates.rb
index 51f357d9477..80a97aae429 100644
--- a/lib/api/templates.rb
+++ b/lib/api/templates.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module API
- class Templates < Grape::API
+ class Templates < Grape::API::Instance
include PaginationParams
GLOBAL_TEMPLATE_TYPES = {
diff --git a/lib/api/terraform/state.rb b/lib/api/terraform/state.rb
index e7c9627c753..f6e966defce 100644
--- a/lib/api/terraform/state.rb
+++ b/lib/api/terraform/state.rb
@@ -4,14 +4,14 @@ require_dependency 'api/validations/validators/limit'
module API
module Terraform
- class State < Grape::API
+ class State < Grape::API::Instance
include ::Gitlab::Utils::StrongMemoize
default_format :json
before do
authenticate!
- authorize! :admin_terraform_state, user_project
+ authorize! :read_terraform_state, user_project
end
params do
@@ -46,6 +46,8 @@ module API
desc 'Add a new terraform state or update an existing one'
route_setting :authentication, basic_auth_personal_access_token: true, job_token_allowed: :basic_auth
post do
+ authorize! :admin_terraform_state, user_project
+
data = request.body.read
no_content! if data.empty?
@@ -59,6 +61,8 @@ module API
desc 'Delete a terraform state of a certain name'
route_setting :authentication, basic_auth_personal_access_token: true, job_token_allowed: :basic_auth
delete do
+ authorize! :admin_terraform_state, user_project
+
remote_state_handler.handle_with_lock do |state|
state.destroy!
status :ok
@@ -77,6 +81,8 @@ module API
requires :Path, type: String, desc: 'Terraform path'
end
post '/lock' do
+ authorize! :admin_terraform_state, user_project
+
status_code = :ok
lock_info = {
'Operation' => params[:Operation],
@@ -108,6 +114,8 @@ module API
optional :ID, type: String, limit: 255, desc: 'Terraform state lock ID'
end
delete '/lock' do
+ authorize! :admin_terraform_state, user_project
+
remote_state_handler.unlock!
status :ok
rescue ::Terraform::RemoteStateHandler::StateLockedError
diff --git a/lib/api/todos.rb b/lib/api/todos.rb
index e36ddf21277..4a73e3e0e94 100644
--- a/lib/api/todos.rb
+++ b/lib/api/todos.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module API
- class Todos < Grape::API
+ class Todos < Grape::API::Instance
include PaginationParams
before { authenticate! }
diff --git a/lib/api/triggers.rb b/lib/api/triggers.rb
index e1829403941..de67a149274 100644
--- a/lib/api/triggers.rb
+++ b/lib/api/triggers.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module API
- class Triggers < Grape::API
+ class Triggers < Grape::API::Instance
include PaginationParams
HTTP_GITLAB_EVENT_HEADER = "HTTP_#{WebHookService::GITLAB_EVENT_HEADER}".underscore.upcase
@@ -32,7 +32,7 @@ module API
project = find_project(params[:id])
not_found! unless project
- result = Ci::PipelineTriggerService.new(project, nil, params).execute
+ result = ::Ci::PipelineTriggerService.new(project, nil, params).execute
not_found! unless result
if result[:http_status]
diff --git a/lib/api/user_counts.rb b/lib/api/user_counts.rb
index 8df4b381bbf..90127ecbc73 100644
--- a/lib/api/user_counts.rb
+++ b/lib/api/user_counts.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module API
- class UserCounts < Grape::API
+ class UserCounts < Grape::API::Instance
resource :user_counts do
desc 'Return the user specific counts' do
detail 'Open MR Count'
diff --git a/lib/api/users.rb b/lib/api/users.rb
index 3d8ae09edf1..7942777287b 100644
--- a/lib/api/users.rb
+++ b/lib/api/users.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module API
- class Users < Grape::API
+ class Users < Grape::API::Instance
include PaginationParams
include APIGuard
include Helpers::CustomAttributes
@@ -117,6 +117,8 @@ module API
users = users.preload(:identities, :u2f_registrations) if entity == Entities::UserWithAdmin
users, options = with_custom_attributes(users, { with: entity, current_user: current_user })
+ users = users.preload(:user_detail)
+
present paginate(users), options
end
# rubocop: enable CodeReuse/ActiveRecord
@@ -328,9 +330,9 @@ module API
user = User.find_by(id: params.delete(:id))
not_found!('User') unless user
- key = user.gpg_keys.new(declared_params(include_missing: false))
+ key = ::GpgKeys::CreateService.new(user, declared_params(include_missing: false)).execute
- if key.save
+ if key.persisted?
present key, with: Entities::GpgKey
else
render_validation_error!(key)
@@ -374,9 +376,10 @@ module API
key = user.gpg_keys.find_by(id: params[:key_id])
not_found!('GPG Key') unless key
- key.destroy
-
- no_content!
+ destroy_conditionally!(key) do |key|
+ destroy_service = ::GpgKeys::DestroyService.new(current_user)
+ destroy_service.execute(key)
+ end
end
# rubocop: enable CodeReuse/ActiveRecord
@@ -730,9 +733,9 @@ module API
optional :expires_at, type: DateTime, desc: 'The expiration date of the SSH key in ISO 8601 format (YYYY-MM-DDTHH:MM:SSZ)'
end
post "keys" do
- key = current_user.keys.new(declared_params)
+ key = ::Keys::CreateService.new(current_user, declared_params(include_missing: false)).execute
- if key.save
+ if key.persisted?
present key, with: Entities::SSHKey
else
render_validation_error!(key)
@@ -750,7 +753,10 @@ module API
key = current_user.keys.find_by(id: params[:key_id])
not_found!('Key') unless key
- destroy_conditionally!(key)
+ destroy_conditionally!(key) do |key|
+ destroy_service = ::Keys::DestroyService.new(current_user)
+ destroy_service.execute(key)
+ end
end
# rubocop: enable CodeReuse/ActiveRecord
@@ -789,9 +795,9 @@ module API
requires :key, type: String, desc: 'The new GPG key'
end
post 'gpg_keys' do
- key = current_user.gpg_keys.new(declared_params)
+ key = ::GpgKeys::CreateService.new(current_user, declared_params(include_missing: false)).execute
- if key.save
+ if key.persisted?
present key, with: Entities::GpgKey
else
render_validation_error!(key)
@@ -825,9 +831,10 @@ module API
key = current_user.gpg_keys.find_by(id: params[:key_id])
not_found!('GPG Key') unless key
- key.destroy
-
- no_content!
+ destroy_conditionally!(key) do |key|
+ destroy_service = ::GpgKeys::DestroyService.new(current_user)
+ destroy_service.execute(key)
+ end
end
# rubocop: enable CodeReuse/ActiveRecord
diff --git a/lib/api/validations/types/comma_separated_to_array.rb b/lib/api/validations/types/comma_separated_to_array.rb
index b551878abd1..409eb67a3d3 100644
--- a/lib/api/validations/types/comma_separated_to_array.rb
+++ b/lib/api/validations/types/comma_separated_to_array.rb
@@ -10,7 +10,7 @@ module API
when String
value.split(',').map(&:strip)
when Array
- value.map { |v| v.to_s.split(',').map(&:strip) }.flatten
+ value.flat_map { |v| v.to_s.split(',').map(&:strip) }
else
[]
end
diff --git a/lib/api/validations/types/comma_separated_to_integer_array.rb b/lib/api/validations/types/comma_separated_to_integer_array.rb
new file mode 100644
index 00000000000..b8ab08b3fd4
--- /dev/null
+++ b/lib/api/validations/types/comma_separated_to_integer_array.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+module API
+ module Validations
+ module Types
+ class CommaSeparatedToIntegerArray < CommaSeparatedToArray
+ def self.coerce
+ lambda do |value|
+ super.call(value).map(&:to_i)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/api/validations/types/labels_list.rb b/lib/api/validations/types/labels_list.rb
deleted file mode 100644
index 60277b99106..00000000000
--- a/lib/api/validations/types/labels_list.rb
+++ /dev/null
@@ -1,24 +0,0 @@
-# frozen_string_literal: true
-
-module API
- module Validations
- module Types
- class LabelsList
- def self.coerce
- lambda do |value|
- case value
- when String
- value.split(',').map(&:strip)
- when Array
- value.flat_map { |v| v.to_s.split(',').map(&:strip) }
- when LabelsList
- value
- else
- []
- end
- end
- end
- end
- end
- end
-end
diff --git a/lib/api/validations/types/safe_file.rb b/lib/api/validations/types/safe_file.rb
deleted file mode 100644
index 53b5790bfa2..00000000000
--- a/lib/api/validations/types/safe_file.rb
+++ /dev/null
@@ -1,15 +0,0 @@
-# frozen_string_literal: true
-
-# This module overrides the Grape type validator defined in
-# https://github.com/ruby-grape/grape/blob/master/lib/grape/validations/types/file.rb
-module API
- module Validations
- module Types
- class SafeFile < ::Grape::Validations::Types::File
- def value_coerced?(value)
- super && value[:tempfile].is_a?(Tempfile)
- end
- end
- end
- end
-end
diff --git a/lib/api/validations/types/workhorse_file.rb b/lib/api/validations/types/workhorse_file.rb
index 18d111f6556..e65e94fc8db 100644
--- a/lib/api/validations/types/workhorse_file.rb
+++ b/lib/api/validations/types/workhorse_file.rb
@@ -3,15 +3,14 @@
module API
module Validations
module Types
- class WorkhorseFile < Virtus::Attribute
- def coerce(input)
- # Processing of multipart file objects
- # is already taken care of by Gitlab::Middleware::Multipart.
- # Nothing to do here.
- input
+ class WorkhorseFile
+ def self.parse(value)
+ raise "#{value.class} is not an UploadedFile type" unless parsed?(value)
+
+ value
end
- def value_coerced?(value)
+ def self.parsed?(value)
value.is_a?(::UploadedFile)
end
end
diff --git a/lib/api/variables.rb b/lib/api/variables.rb
index 192b06b8a1b..50d137ec7c1 100644
--- a/lib/api/variables.rb
+++ b/lib/api/variables.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module API
- class Variables < Grape::API
+ class Variables < Grape::API::Instance
include PaginationParams
before { authenticate! }
@@ -13,6 +13,15 @@ module API
# parameters, without having to modify the source code directly.
params
end
+
+ def find_variable(params)
+ variables = ::Ci::VariablesFinder.new(user_project, params).execute.to_a
+
+ return variables.first unless ::Gitlab::Ci::Features.variables_api_filter_environment_scope?
+ return variables.first unless variables.many? # rubocop: disable CodeReuse/ActiveRecord
+
+ conflict!("There are multiple variables with provided parameters. Please use 'filter[environment_scope]'")
+ end
end
params do
@@ -39,10 +48,8 @@ module API
end
# rubocop: disable CodeReuse/ActiveRecord
get ':id/variables/:key' do
- key = params[:key]
- variable = user_project.variables.find_by(key: key)
-
- break not_found!('Variable') unless variable
+ variable = find_variable(params)
+ not_found!('Variable') unless variable
present variable, with: Entities::Variable
end
@@ -56,7 +63,7 @@ module API
requires :value, type: String, desc: 'The value of the variable'
optional :protected, type: Boolean, desc: 'Whether the variable is protected'
optional :masked, type: Boolean, desc: 'Whether the variable is masked'
- optional :variable_type, type: String, values: Ci::Variable.variable_types.keys, desc: 'The type of variable, must be one of env_var or file. Defaults to env_var'
+ optional :variable_type, type: String, values: ::Ci::Variable.variable_types.keys, desc: 'The type of variable, must be one of env_var or file. Defaults to env_var'
optional :environment_scope, type: String, desc: 'The environment_scope of the variable'
end
post ':id/variables' do
@@ -80,16 +87,16 @@ module API
optional :value, type: String, desc: 'The value of the variable'
optional :protected, type: Boolean, desc: 'Whether the variable is protected'
optional :masked, type: Boolean, desc: 'Whether the variable is masked'
- optional :variable_type, type: String, values: Ci::Variable.variable_types.keys, desc: 'The type of variable, must be one of env_var or file'
+ optional :variable_type, type: String, values: ::Ci::Variable.variable_types.keys, desc: 'The type of variable, must be one of env_var or file'
optional :environment_scope, type: String, desc: 'The environment_scope of the variable'
+ optional :filter, type: Hash, desc: 'Available filters: [environment_scope]. Example: filter[environment_scope]=production'
end
# rubocop: disable CodeReuse/ActiveRecord
put ':id/variables/:key' do
- variable = user_project.variables.find_by(key: params[:key])
-
- break not_found!('Variable') unless variable
+ variable = find_variable(params)
+ not_found!('Variable') unless variable
- variable_params = declared_params(include_missing: false).except(:key)
+ variable_params = declared_params(include_missing: false).except(:key, :filter)
variable_params = filter_variable_parameters(variable_params)
if variable.update(variable_params)
@@ -105,10 +112,11 @@ module API
end
params do
requires :key, type: String, desc: 'The key of the variable'
+ optional :filter, type: Hash, desc: 'Available filters: [environment_scope]. Example: filter[environment_scope]=production'
end
# rubocop: disable CodeReuse/ActiveRecord
delete ':id/variables/:key' do
- variable = user_project.variables.find_by(key: params[:key])
+ variable = find_variable(params)
not_found!('Variable') unless variable
# Variables don't have a timestamp. Therefore, destroy unconditionally.
diff --git a/lib/api/version.rb b/lib/api/version.rb
index 2d8c90260fa..6a480fc2bd9 100644
--- a/lib/api/version.rb
+++ b/lib/api/version.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module API
- class Version < Grape::API
+ class Version < Grape::API::Instance
helpers ::API::Helpers::GraphqlHelpers
include APIGuard
diff --git a/lib/api/wikis.rb b/lib/api/wikis.rb
index c1bf3a64923..713136e0887 100644
--- a/lib/api/wikis.rb
+++ b/lib/api/wikis.rb
@@ -1,25 +1,11 @@
# frozen_string_literal: true
module API
- class Wikis < Grape::API
+ class Wikis < Grape::API::Instance
+ helpers ::API::Helpers::WikisHelpers
+
helpers do
- def commit_params(attrs)
- # In order to avoid service disruption this can work with an old workhorse without the acceleration
- # the first branch of this if must be removed when we drop support for non accelerated uploads
- if attrs[:file].is_a?(Hash)
- {
- file_name: attrs[:file][:filename],
- file_content: attrs[:file][:tempfile].read,
- branch_name: attrs[:branch]
- }
- else
- {
- file_name: attrs[:file].original_filename,
- file_content: attrs[:file].read,
- branch_name: attrs[:branch]
- }
- end
- end
+ attr_reader :container
params :common_wiki_page_params do
optional :format,
@@ -32,108 +18,118 @@ module API
WIKI_ENDPOINT_REQUIREMENTS = API::NAMESPACE_OR_PROJECT_REQUIREMENTS.merge(slug: API::NO_SLASH_URL_PART_REGEX)
- resource :projects, requirements: WIKI_ENDPOINT_REQUIREMENTS do
- desc 'Get a list of wiki pages' do
- success Entities::WikiPageBasic
- end
- params do
- optional :with_content, type: Boolean, default: false, desc: "Include pages' content"
- end
- get ':id/wikis' do
- authorize! :read_wiki, user_project
-
- entity = params[:with_content] ? Entities::WikiPage : Entities::WikiPageBasic
+ ::API::Helpers::WikisHelpers.wiki_resource_kinds.each do |container_resource|
+ resource container_resource, requirements: WIKI_ENDPOINT_REQUIREMENTS do
+ after_validation do
+ @container = Gitlab::Lazy.new { find_container(container_resource) }
+ end
- present user_project.wiki.list_pages(load_content: params[:with_content]), with: entity
- end
+ desc 'Get a list of wiki pages' do
+ success Entities::WikiPageBasic
+ end
+ params do
+ optional :with_content, type: Boolean, default: false, desc: "Include pages' content"
+ end
+ get ':id/wikis' do
+ authorize! :read_wiki, container
- desc 'Get a wiki page' do
- success Entities::WikiPage
- end
- params do
- requires :slug, type: String, desc: 'The slug of a wiki page'
- end
- get ':id/wikis/:slug' do
- authorize! :read_wiki, user_project
+ entity = params[:with_content] ? Entities::WikiPage : Entities::WikiPageBasic
- present wiki_page, with: Entities::WikiPage
- end
+ present container.wiki.list_pages(load_content: params[:with_content]), with: entity
+ end
- desc 'Create a wiki page' do
- success Entities::WikiPage
- end
- params do
- requires :title, type: String, desc: 'Title of a wiki page'
- requires :content, type: String, desc: 'Content of a wiki page'
- use :common_wiki_page_params
- end
- post ':id/wikis' do
- authorize! :create_wiki, user_project
+ desc 'Get a wiki page' do
+ success Entities::WikiPage
+ end
+ params do
+ requires :slug, type: String, desc: 'The slug of a wiki page'
+ end
+ get ':id/wikis/:slug' do
+ authorize! :read_wiki, container
- page = WikiPages::CreateService.new(container: user_project, current_user: current_user, params: params).execute
+ present wiki_page, with: Entities::WikiPage
+ end
- if page.valid?
- present page, with: Entities::WikiPage
- else
- render_validation_error!(page)
+ desc 'Create a wiki page' do
+ success Entities::WikiPage
end
- end
+ params do
+ requires :title, type: String, desc: 'Title of a wiki page'
+ requires :content, type: String, desc: 'Content of a wiki page'
+ use :common_wiki_page_params
+ end
+ post ':id/wikis' do
+ authorize! :create_wiki, container
- desc 'Update a wiki page' do
- success Entities::WikiPage
- end
- params do
- optional :title, type: String, desc: 'Title of a wiki page'
- optional :content, type: String, desc: 'Content of a wiki page'
- use :common_wiki_page_params
- at_least_one_of :content, :title, :format
- end
- put ':id/wikis/:slug' do
- authorize! :create_wiki, user_project
+ page = WikiPages::CreateService.new(container: container, current_user: current_user, params: params).execute
- page = WikiPages::UpdateService.new(container: user_project, current_user: current_user, params: params).execute(wiki_page)
+ if page.valid?
+ present page, with: Entities::WikiPage
+ else
+ render_validation_error!(page)
+ end
+ end
- if page.valid?
- present page, with: Entities::WikiPage
- else
- render_validation_error!(page)
+ desc 'Update a wiki page' do
+ success Entities::WikiPage
+ end
+ params do
+ optional :title, type: String, desc: 'Title of a wiki page'
+ optional :content, type: String, desc: 'Content of a wiki page'
+ use :common_wiki_page_params
+ at_least_one_of :content, :title, :format
+ end
+ put ':id/wikis/:slug' do
+ authorize! :create_wiki, container
+
+ page = WikiPages::UpdateService
+ .new(container: container, current_user: current_user, params: params)
+ .execute(wiki_page)
+
+ if page.valid?
+ present page, with: Entities::WikiPage
+ else
+ render_validation_error!(page)
+ end
end
- end
- desc 'Delete a wiki page'
- params do
- requires :slug, type: String, desc: 'The slug of a wiki page'
- end
- delete ':id/wikis/:slug' do
- authorize! :admin_wiki, user_project
+ desc 'Delete a wiki page'
+ params do
+ requires :slug, type: String, desc: 'The slug of a wiki page'
+ end
+ delete ':id/wikis/:slug' do
+ authorize! :admin_wiki, container
- WikiPages::DestroyService.new(container: user_project, current_user: current_user).execute(wiki_page)
+ WikiPages::DestroyService
+ .new(container: container, current_user: current_user)
+ .execute(wiki_page)
- no_content!
- end
+ no_content!
+ end
- desc 'Upload an attachment to the wiki repository' do
- detail 'This feature was introduced in GitLab 11.3.'
- success Entities::WikiAttachment
- end
- params do
- requires :file, types: [::API::Validations::Types::SafeFile, ::API::Validations::Types::WorkhorseFile], desc: 'The attachment file to be uploaded'
- optional :branch, type: String, desc: 'The name of the branch'
- end
- post ":id/wikis/attachments" do
- authorize! :create_wiki, user_project
-
- result = ::Wikis::CreateAttachmentService.new(
- container: user_project,
- current_user: current_user,
- params: commit_params(declared_params(include_missing: false))
- ).execute
-
- if result[:status] == :success
- status(201)
- present OpenStruct.new(result[:result]), with: Entities::WikiAttachment
- else
- render_api_error!(result[:message], 400)
+ desc 'Upload an attachment to the wiki repository' do
+ detail 'This feature was introduced in GitLab 11.3.'
+ success Entities::WikiAttachment
+ end
+ params do
+ requires :file, types: [Rack::Multipart::UploadedFile, ::API::Validations::Types::WorkhorseFile], desc: 'The attachment file to be uploaded'
+ optional :branch, type: String, desc: 'The name of the branch'
+ end
+ post ":id/wikis/attachments" do
+ authorize! :create_wiki, container
+
+ result = ::Wikis::CreateAttachmentService.new(
+ container: container,
+ current_user: current_user,
+ params: commit_params(declared_params(include_missing: false))
+ ).execute
+
+ if result[:status] == :success
+ status(201)
+ present OpenStruct.new(result[:result]), with: Entities::WikiAttachment
+ else
+ render_api_error!(result[:message], 400)
+ end
end
end
end
diff --git a/lib/backup/database.rb b/lib/backup/database.rb
index 7e457c4982d..d4c1ce260e4 100644
--- a/lib/backup/database.rb
+++ b/lib/backup/database.rb
@@ -27,12 +27,18 @@ module Backup
progress.print "Dumping PostgreSQL database #{config['database']} ... "
pg_env
pgsql_args = ["--clean"] # Pass '--clean' to include 'DROP TABLE' statements in the DB dump.
+
if Gitlab.config.backup.pg_schema
- pgsql_args << "-n"
+ pgsql_args << '-n'
pgsql_args << Gitlab.config.backup.pg_schema
+
+ Gitlab::Database::EXTRA_SCHEMAS.each do |schema|
+ pgsql_args << '-n'
+ pgsql_args << schema.to_s
+ end
end
- spawn('pg_dump', *pgsql_args, config['database'], out: compress_wr)
+ Process.spawn('pg_dump', *pgsql_args, config['database'], out: compress_wr)
end
compress_wr.close
diff --git a/lib/banzai/filter/abstract_reference_filter.rb b/lib/banzai/filter/abstract_reference_filter.rb
index f142333d797..38105e2237c 100644
--- a/lib/banzai/filter/abstract_reference_filter.rb
+++ b/lib/banzai/filter/abstract_reference_filter.rb
@@ -146,16 +146,16 @@ module Banzai
link_pattern_start = /\A#{link_pattern}/
link_pattern_anchor = /\A#{link_pattern}\z/
- nodes.each do |node|
+ nodes.each_with_index do |node, index|
if text_node?(node) && ref_pattern
- replace_text_when_pattern_matches(node, ref_pattern) do |content|
+ replace_text_when_pattern_matches(node, index, ref_pattern) do |content|
object_link_filter(content, ref_pattern)
end
elsif element_node?(node)
yield_valid_link(node) do |link, inner_html|
if ref_pattern && link =~ ref_pattern_anchor
- replace_link_node_with_href(node, link) do
+ replace_link_node_with_href(node, index, link) do
object_link_filter(link, ref_pattern, link_content: inner_html)
end
@@ -165,7 +165,7 @@ module Banzai
next unless link_pattern
if link == inner_html && inner_html =~ link_pattern_start
- replace_link_node_with_text(node, link) do
+ replace_link_node_with_text(node, index) do
object_link_filter(inner_html, link_pattern, link_reference: true)
end
@@ -173,7 +173,7 @@ module Banzai
end
if link =~ link_pattern_anchor
- replace_link_node_with_href(node, link) do
+ replace_link_node_with_href(node, index, link) do
object_link_filter(link, link_pattern, link_content: inner_html, link_reference: true)
end
diff --git a/lib/banzai/filter/commit_trailers_filter.rb b/lib/banzai/filter/commit_trailers_filter.rb
index 02a47556151..5288db3b0cb 100644
--- a/lib/banzai/filter/commit_trailers_filter.rb
+++ b/lib/banzai/filter/commit_trailers_filter.rb
@@ -144,10 +144,7 @@ module Banzai
end
def data_attributes_from_hash(data = {})
- data.reject! {|_, value| value.nil?}
- data.map do |key, value|
- [%(data-#{key.to_s.dasherize}), value]
- end.to_h
+ data.compact.transform_keys { |key| %(data-#{key.to_s.dasherize}) }
end
end
end
diff --git a/lib/banzai/filter/external_issue_reference_filter.rb b/lib/banzai/filter/external_issue_reference_filter.rb
index 74bc102320c..fcf4863ab4f 100644
--- a/lib/banzai/filter/external_issue_reference_filter.rb
+++ b/lib/banzai/filter/external_issue_reference_filter.rb
@@ -34,16 +34,16 @@ module Banzai
ref_pattern = issue_reference_pattern
ref_start_pattern = /\A#{ref_pattern}\z/
- each_node do |node|
+ nodes.each_with_index do |node, index|
if text_node?(node)
- replace_text_when_pattern_matches(node, ref_pattern) do |content|
+ replace_text_when_pattern_matches(node, index, ref_pattern) do |content|
issue_link_filter(content)
end
elsif element_node?(node)
yield_valid_link(node) do |link, inner_html|
if link =~ ref_start_pattern
- replace_link_node_with_href(node, link) do
+ replace_link_node_with_href(node, index, link) do
issue_link_filter(link, link_content: inner_html)
end
end
diff --git a/lib/banzai/filter/inline_cluster_metrics_filter.rb b/lib/banzai/filter/inline_cluster_metrics_filter.rb
new file mode 100644
index 00000000000..5ef68388ea9
--- /dev/null
+++ b/lib/banzai/filter/inline_cluster_metrics_filter.rb
@@ -0,0 +1,40 @@
+# frozen_string_literal: true
+
+module Banzai
+ module Filter
+ class InlineClusterMetricsFilter < ::Banzai::Filter::InlineEmbedsFilter
+ def embed_params(node)
+ url = node['href']
+ @query_params = query_params(url)
+ return unless [:group, :title, :y_label].all? do |param|
+ @query_params.include?(param)
+ end
+
+ link_pattern.match(url) { |m| m.named_captures }.symbolize_keys
+ end
+
+ def xpath_search
+ "descendant-or-self::a[contains(@href,'clusters') and \
+ starts-with(@href, '#{::Gitlab.config.gitlab.url}')]"
+ end
+
+ def link_pattern
+ ::Gitlab::Metrics::Dashboard::Url.clusters_regex
+ end
+
+ def metrics_dashboard_url(params)
+ ::Gitlab::Routing.url_helpers.metrics_dashboard_namespace_project_cluster_url(
+ params[:namespace],
+ params[:project],
+ params[:cluster_id],
+ # Only Project clusters are supported for now
+ # admin and group cluster types may be supported in the future
+ cluster_type: :project,
+ embedded: true,
+ format: :json,
+ **@query_params
+ )
+ end
+ end
+ end
+end
diff --git a/lib/banzai/filter/inline_metrics_redactor_filter.rb b/lib/banzai/filter/inline_metrics_redactor_filter.rb
index 75bd3325bd4..7f98a52d421 100644
--- a/lib/banzai/filter/inline_metrics_redactor_filter.rb
+++ b/lib/banzai/filter/inline_metrics_redactor_filter.rb
@@ -77,6 +77,10 @@ module Banzai
Route.new(
::Gitlab::Metrics::Dashboard::Url.grafana_regex,
:read_project
+ ),
+ Route.new(
+ ::Gitlab::Metrics::Dashboard::Url.clusters_regex,
+ :read_cluster
)
]
end
diff --git a/lib/banzai/filter/jira_import/adf_to_commonmark_filter.rb b/lib/banzai/filter/jira_import/adf_to_commonmark_filter.rb
new file mode 100644
index 00000000000..3db2244d641
--- /dev/null
+++ b/lib/banzai/filter/jira_import/adf_to_commonmark_filter.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+
+module Banzai
+ module Filter
+ module JiraImport
+ # Uses Kramdown to convert from the Atlassian Document Format (json)
+ # into CommonMark
+ # @see https://developer.atlassian.com/cloud/jira/platform/apis/document/structure/
+ class AdfToCommonmarkFilter < HTML::Pipeline::TextFilter
+ def initialize(text, context = nil, result = nil)
+ super(text, context, result)
+ end
+
+ def call
+ Kramdown::Document.new(@text, input: 'AtlassianDocumentFormat', html_tables: true).to_commonmark
+ rescue ::Kramdown::Error => e
+ # If we get an error, then just return the original text so at
+ # least the user knows something went wrong
+ "#{e.message}\n\n#{@text}"
+ end
+ end
+ end
+ end
+end
diff --git a/lib/banzai/filter/project_reference_filter.rb b/lib/banzai/filter/project_reference_filter.rb
index 292d4b1d56c..50e23460cb8 100644
--- a/lib/banzai/filter/project_reference_filter.rb
+++ b/lib/banzai/filter/project_reference_filter.rb
@@ -27,15 +27,15 @@ module Banzai
ref_pattern = Project.markdown_reference_pattern
ref_pattern_start = /\A#{ref_pattern}\z/
- nodes.each do |node|
+ nodes.each_with_index do |node, index|
if text_node?(node)
- replace_text_when_pattern_matches(node, ref_pattern) do |content|
+ replace_text_when_pattern_matches(node, index, ref_pattern) do |content|
project_link_filter(content)
end
elsif element_node?(node)
yield_valid_link(node) do |link, inner_html|
if link =~ ref_pattern_start
- replace_link_node_with_href(node, link) do
+ replace_link_node_with_href(node, index, link) do
project_link_filter(link, link_content: inner_html)
end
end
diff --git a/lib/banzai/filter/reference_filter.rb b/lib/banzai/filter/reference_filter.rb
index 9e932ccf9f8..9032ca6ddc6 100644
--- a/lib/banzai/filter/reference_filter.rb
+++ b/lib/banzai/filter/reference_filter.rb
@@ -16,6 +16,23 @@ module Banzai
class << self
attr_accessor :reference_type
+
+ def call(doc, context = nil, result = nil)
+ new(doc, context, result).call_and_update_nodes
+ end
+ end
+
+ def initialize(doc, context = nil, result = nil)
+ super
+
+ if update_nodes_enabled?
+ @new_nodes = {}
+ @nodes = self.result[:reference_filter_nodes]
+ end
+ end
+
+ def call_and_update_nodes
+ update_nodes_enabled? ? with_update_nodes { call } : call
end
# Returns a data attribute String to attach to a reference link
@@ -89,11 +106,6 @@ module Banzai
def each_node
return to_enum(__method__) unless block_given?
- query = %Q{descendant-or-self::text()[not(#{ignore_ancestor_query})]
- | descendant-or-self::a[
- not(contains(concat(" ", @class, " "), " gfm ")) and not(@href = "")
- ]}
-
doc.xpath(query).each do |node|
yield node
end
@@ -114,25 +126,25 @@ module Banzai
yield link, inner_html
end
- def replace_text_when_pattern_matches(node, pattern)
+ def replace_text_when_pattern_matches(node, index, pattern)
return unless node.text =~ pattern
content = node.to_html
html = yield content
- node.replace(html) unless content == html
+ replace_text_with_html(node, index, html) unless html == content
end
- def replace_link_node_with_text(node, link)
+ def replace_link_node_with_text(node, index)
html = yield
- node.replace(html) unless html == node.text
+ replace_text_with_html(node, index, html) unless html == node.text
end
- def replace_link_node_with_href(node, link)
+ def replace_link_node_with_href(node, index, link)
html = yield
- node.replace(html) unless html == link
+ replace_text_with_html(node, index, html) unless html == link
end
def text_node?(node)
@@ -145,9 +157,62 @@ module Banzai
private
+ def query
+ @query ||= %Q{descendant-or-self::text()[not(#{ignore_ancestor_query})]
+ | descendant-or-self::a[
+ not(contains(concat(" ", @class, " "), " gfm ")) and not(@href = "")
+ ]}
+ end
+
+ def replace_text_with_html(node, index, html)
+ if update_nodes_enabled?
+ replace_and_update_new_nodes(node, index, html)
+ else
+ node.replace(html)
+ end
+ end
+
+ def replace_and_update_new_nodes(node, index, html)
+ previous_node = node.previous
+ next_node = node.next
+ parent_node = node.parent
+ # Unfortunately node.replace(html) returns re-parented nodes, not the actual replaced nodes in the doc
+ # We need to find the actual nodes in the doc that were replaced
+ node.replace(html)
+ @new_nodes[index] = []
+
+ # We replaced node with new nodes, so we find first new node. If previous_node is nil, we take first parent child
+ new_node = previous_node ? previous_node.next : parent_node&.children&.first
+
+ # We iterate from first to last replaced node and store replaced nodes in @new_nodes
+ while new_node && new_node != next_node
+ @new_nodes[index] << new_node.xpath(query)
+ new_node = new_node.next
+ end
+
+ @new_nodes[index].flatten!
+ end
+
def only_path?
context[:only_path]
end
+
+ def with_update_nodes
+ @new_nodes = {}
+ yield.tap { update_nodes! }
+ end
+
+ # Once Filter completes replacing nodes, we update nodes with @new_nodes
+ def update_nodes!
+ @new_nodes.sort_by { |index, _new_nodes| -index }.each do |index, new_nodes|
+ nodes[index, 1] = new_nodes
+ end
+ result[:reference_filter_nodes] = nodes
+ end
+
+ def update_nodes_enabled?
+ Feature.enabled?(:update_nodes_for_banzai_reference_filter, project)
+ end
end
end
end
diff --git a/lib/banzai/filter/table_of_contents_filter.rb b/lib/banzai/filter/table_of_contents_filter.rb
index a2c8e92e560..b362607aed2 100644
--- a/lib/banzai/filter/table_of_contents_filter.rb
+++ b/lib/banzai/filter/table_of_contents_filter.rb
@@ -17,7 +17,7 @@ module Banzai
# :toc - String containing Table of Contents data as a `ul` element with
# `li` child elements.
class TableOfContentsFilter < HTML::Pipeline::Filter
- PUNCTUATION_REGEXP = /[^\p{Word}\- ]/u.freeze
+ include Gitlab::Utils::Markdown
def call
return doc if context[:no_header_anchors]
@@ -29,14 +29,7 @@ module Banzai
doc.css('h1, h2, h3, h4, h5, h6').each do |node|
if header_content = node.children.first
- id = node
- .text
- .strip
- .downcase
- .gsub(PUNCTUATION_REGEXP, '') # remove punctuation
- .tr(' ', '-') # replace spaces with dash
- .squeeze('-') # replace multiple dashes with one
- .gsub(/\A(\d+)\z/, 'anchor-\1') # digits-only hrefs conflict with issue refs
+ id = string_to_anchor(node.text)
uniq = headers[id] > 0 ? "-#{headers[id]}" : ''
headers[id] += 1
diff --git a/lib/banzai/filter/user_reference_filter.rb b/lib/banzai/filter/user_reference_filter.rb
index 9268ff1a827..262385524f4 100644
--- a/lib/banzai/filter/user_reference_filter.rb
+++ b/lib/banzai/filter/user_reference_filter.rb
@@ -31,15 +31,15 @@ module Banzai
ref_pattern = User.reference_pattern
ref_pattern_start = /\A#{ref_pattern}\z/
- nodes.each do |node|
+ nodes.each_with_index do |node, index|
if text_node?(node)
- replace_text_when_pattern_matches(node, ref_pattern) do |content|
+ replace_text_when_pattern_matches(node, index, ref_pattern) do |content|
user_link_filter(content)
end
elsif element_node?(node)
yield_valid_link(node) do |link, inner_html|
if link =~ ref_pattern_start
- replace_link_node_with_href(node, link) do
+ replace_link_node_with_href(node, index, link) do
user_link_filter(link, link_content: inner_html)
end
end
diff --git a/lib/banzai/pipeline/gfm_pipeline.rb b/lib/banzai/pipeline/gfm_pipeline.rb
index 2ea5fd3388a..10ac813ea15 100644
--- a/lib/banzai/pipeline/gfm_pipeline.rb
+++ b/lib/banzai/pipeline/gfm_pipeline.rb
@@ -48,7 +48,8 @@ module Banzai
def self.metrics_filters
[
Filter::InlineMetricsFilter,
- Filter::InlineGrafanaMetricsFilter
+ Filter::InlineGrafanaMetricsFilter,
+ Filter::InlineClusterMetricsFilter
]
end
diff --git a/lib/banzai/pipeline/jira_import/adf_commonmark_pipeline.rb b/lib/banzai/pipeline/jira_import/adf_commonmark_pipeline.rb
new file mode 100644
index 00000000000..8af0279673c
--- /dev/null
+++ b/lib/banzai/pipeline/jira_import/adf_commonmark_pipeline.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+module Banzai
+ module Pipeline
+ module JiraImport
+ class AdfCommonmarkPipeline < BasePipeline
+ def self.filters
+ FilterArray[
+ Filter::JiraImport::AdfToCommonmarkFilter
+ ]
+ end
+ end
+ end
+ end
+end
diff --git a/lib/container_registry/tag.rb b/lib/container_registry/tag.rb
index e1a2891e43a..09c0aa66a0d 100644
--- a/lib/container_registry/tag.rb
+++ b/lib/container_registry/tag.rb
@@ -6,9 +6,6 @@ module ContainerRegistry
attr_reader :repository, :name
- # https://github.com/docker/distribution/commit/3150937b9f2b1b5b096b2634d0e7c44d4a0f89fb
- TAG_NAME_REGEX = /^[\w][\w.-]{0,127}$/.freeze
-
delegate :registry, :client, to: :repository
delegate :revision, :short_revision, to: :config_blob, allow_nil: true
@@ -16,10 +13,6 @@ module ContainerRegistry
@repository, @name = repository, name
end
- def valid_name?
- !name.match(TAG_NAME_REGEX).nil?
- end
-
def valid?
manifest.present?
end
diff --git a/lib/declarative_policy/base.rb b/lib/declarative_policy/base.rb
index cd6e1606f22..4af0251b990 100644
--- a/lib/declarative_policy/base.rb
+++ b/lib/declarative_policy/base.rb
@@ -117,6 +117,23 @@ module DeclarativePolicy
own_delegations[name] = delegation_block
end
+ # Declare that the given abilities should not be read from delegates.
+ #
+ # This is useful if you have an ability that you want to define
+ # differently in a policy than in a delegated policy, but still want to
+ # delegate all other abilities.
+ #
+ # example:
+ #
+ # delegate { @subect.parent }
+ #
+ # overrides :drive_car, :watch_tv
+ #
+ def overrides(*names)
+ @overrides ||= [].to_set
+ @overrides.merge(names)
+ end
+
# Declares a rule, constructed using RuleDsl, and returns
# a PolicyDsl which is used for registering the rule with
# this class. PolicyDsl will call back into Base.enable_when,
@@ -265,9 +282,13 @@ module DeclarativePolicy
@runners ||= {}
@runners[ability] ||=
begin
- delegated_runners = delegated_policies.values.compact.map { |p| p.runner(ability) }
own_runner = Runner.new(own_steps(ability))
- delegated_runners.inject(own_runner, &:merge_runner)
+ if self.class.overrides.include?(ability)
+ own_runner
+ else
+ delegated_runners = delegated_policies.values.compact.map { |p| p.runner(ability) }
+ delegated_runners.inject(own_runner, &:merge_runner)
+ end
end
end
diff --git a/lib/event_filter.rb b/lib/event_filter.rb
index 538727dc422..0b5833b91ed 100644
--- a/lib/event_filter.rb
+++ b/lib/event_filter.rb
@@ -1,6 +1,8 @@
# frozen_string_literal: true
class EventFilter
+ include Gitlab::Utils::StrongMemoize
+
attr_accessor :filter
ALL = 'all'
@@ -10,6 +12,7 @@ class EventFilter
COMMENTS = 'comments'
TEAM = 'team'
WIKI = 'wiki'
+ DESIGNS = 'designs'
def initialize(filter)
# Split using comma to maintain backward compatibility Ex/ "filter1,filter2"
@@ -23,8 +26,6 @@ class EventFilter
# rubocop: disable CodeReuse/ActiveRecord
def apply_filter(events)
- events = apply_feature_flags(events)
-
case filter
when PUSH
events.pushed_action
@@ -38,6 +39,8 @@ class EventFilter
events.where(action: [:created, :updated, :closed, :reopened], target_type: 'Issue')
when WIKI
wiki_events(events)
+ when DESIGNS
+ design_events(events)
else
events
end
@@ -46,20 +49,16 @@ class EventFilter
private
- def apply_feature_flags(events)
- return events.not_wiki_page unless Feature.enabled?(:wiki_events)
-
- events
- end
-
def wiki_events(events)
- return events unless Feature.enabled?(:wiki_events)
-
events.for_wiki_page
end
+ def design_events(events)
+ events.for_design
+ end
+
def filters
- [ALL, PUSH, MERGED, ISSUE, COMMENTS, TEAM, WIKI]
+ [ALL, PUSH, MERGED, ISSUE, COMMENTS, TEAM, WIKI, DESIGNS]
end
end
diff --git a/lib/feature.rb b/lib/feature.rb
index d995e0a988f..7cf40b63fdf 100644
--- a/lib/feature.rb
+++ b/lib/feature.rb
@@ -34,26 +34,13 @@ class Feature
def persisted_names
return [] unless Gitlab::Database.exists?
- if Gitlab::Utils.to_boolean(ENV['FF_LEGACY_PERSISTED_NAMES'])
- # To be removed:
- # This uses a legacy persisted names that are know to work (always)
- Gitlab::SafeRequestStore[:flipper_persisted_names] ||=
- begin
- # We saw on GitLab.com, this database request was called 2300
- # times/s. Let's cache it for a minute to avoid that load.
- Gitlab::ProcessMemoryCache.cache_backend.fetch('flipper:persisted_names', expires_in: 1.minute) do
- FlipperFeature.feature_names
- end.to_set
- end
- else
- # This loads names of all stored feature flags
- # and returns a stable Set in the following order:
- # - Memoized: using Gitlab::SafeRequestStore or @flipper
- # - L1: using Process cache
- # - L2: using Redis cache
- # - DB: using a single SQL query
- flipper.adapter.features
- end
+ # This loads names of all stored feature flags
+ # and returns a stable Set in the following order:
+ # - Memoized: using Gitlab::SafeRequestStore or @flipper
+ # - L1: using Process cache
+ # - L2: using Redis cache
+ # - DB: using a single SQL query
+ flipper.adapter.features
end
def persisted_name?(feature_name)
@@ -67,12 +54,14 @@ class Feature
# unless set explicitly. The default is `disabled`
# TODO: remove the `default_enabled:` and read it from the `defintion_yaml`
# check: https://gitlab.com/gitlab-org/gitlab/-/issues/30228
- def enabled?(key, thing = nil, default_enabled: false)
+ def enabled?(key, thing = nil, type: :development, default_enabled: false)
if check_feature_flags_definition?
if thing && !thing.respond_to?(:flipper_id)
raise InvalidFeatureFlagError,
"The thing '#{thing.class.name}' for feature flag '#{key}' needs to include `FeatureGate` or implement `flipper_id`"
end
+
+ Feature::Definition.valid_usage!(key, type: type, default_enabled: default_enabled)
end
# During setup the database does not exist yet. So we haven't stored a value
@@ -88,9 +77,9 @@ class Feature
!default_enabled || Feature.persisted_name?(feature.name) ? feature.enabled?(thing) : true
end
- def disabled?(key, thing = nil, default_enabled: false)
+ def disabled?(key, thing = nil, type: :development, default_enabled: false)
# we need to make different method calls to make it easy to mock / define expectations in test mode
- thing.nil? ? !enabled?(key, default_enabled: default_enabled) : !enabled?(key, thing, default_enabled: default_enabled)
+ thing.nil? ? !enabled?(key, type: type, default_enabled: default_enabled) : !enabled?(key, thing, type: type, default_enabled: default_enabled)
end
def enable(key, thing = true)
@@ -142,6 +131,12 @@ class Feature
def register_feature_groups
end
+ def register_definitions
+ return unless check_feature_flags_definition?
+
+ Feature::Definition.load_all!
+ end
+
private
def flipper
diff --git a/lib/feature/definition.rb b/lib/feature/definition.rb
new file mode 100644
index 00000000000..b0ea55c5805
--- /dev/null
+++ b/lib/feature/definition.rb
@@ -0,0 +1,137 @@
+# frozen_string_literal: true
+
+class Feature
+ class Definition
+ include ::Feature::Shared
+
+ attr_reader :path
+ attr_reader :attributes
+
+ PARAMS.each do |param|
+ define_method(param) do
+ attributes[param]
+ end
+ end
+
+ def initialize(path, opts = {})
+ @path = path
+ @attributes = {}
+
+ # assign nil, for all unknown opts
+ PARAMS.each do |param|
+ @attributes[param] = opts[param]
+ end
+ end
+
+ def key
+ name.to_sym
+ end
+
+ def validate!
+ unless name.present?
+ raise Feature::InvalidFeatureFlagError, "Feature flag is missing name"
+ end
+
+ unless path.present?
+ raise Feature::InvalidFeatureFlagError, "Feature flag '#{name}' is missing path"
+ end
+
+ unless type.present?
+ raise Feature::InvalidFeatureFlagError, "Feature flag '#{name}' is missing type. Ensure to update #{path}"
+ end
+
+ unless Definition::TYPES.include?(type.to_sym)
+ raise Feature::InvalidFeatureFlagError, "Feature flag '#{name}' type '#{type}' is invalid. Ensure to update #{path}"
+ end
+
+ unless File.basename(path, ".yml") == name
+ raise Feature::InvalidFeatureFlagError, "Feature flag '#{name}' has an invalid path: '#{path}'. Ensure to update #{path}"
+ end
+
+ unless File.basename(File.dirname(path)) == type
+ raise Feature::InvalidFeatureFlagError, "Feature flag '#{name}' has an invalid type: '#{path}'. Ensure to update #{path}"
+ end
+
+ if default_enabled.nil?
+ raise Feature::InvalidFeatureFlagError, "Feature flag '#{name}' is missing default_enabled. Ensure to update #{path}"
+ end
+ end
+
+ def valid_usage!(type_in_code:, default_enabled_in_code:)
+ unless Array(type).include?(type_in_code.to_s)
+ # Raise exception in test and dev
+ raise Feature::InvalidFeatureFlagError, "The `type:` of `#{key}` is not equal to config: " \
+ "#{type_in_code} vs #{type}. Ensure to use valid type in #{path} or ensure that you use " \
+ "a valid syntax: #{TYPES.dig(type, :example)}"
+ end
+
+ # We accept an array of defaults as some features are undefined
+ # and have `default_enabled: true/false`
+ unless Array(default_enabled).include?(default_enabled_in_code)
+ # Raise exception in test and dev
+ raise Feature::InvalidFeatureFlagError, "The `default_enabled:` of `#{key}` is not equal to config: " \
+ "#{default_enabled_in_code} vs #{default_enabled}. Ensure to update #{path}"
+ end
+ end
+
+ def to_h
+ attributes
+ end
+
+ class << self
+ def paths
+ @paths ||= [Rails.root.join('config', 'feature_flags', '**', '*.yml')]
+ end
+
+ def definitions
+ @definitions ||= {}
+ end
+
+ def load_all!
+ definitions.clear
+
+ paths.each do |glob_path|
+ load_all_from_path!(glob_path)
+ end
+
+ definitions
+ end
+
+ def valid_usage!(key, type:, default_enabled:)
+ if definition = definitions[key.to_sym]
+ definition.valid_usage!(type_in_code: type, default_enabled_in_code: default_enabled)
+ elsif type_definition = self::TYPES[type]
+ raise InvalidFeatureFlagError, "Missing feature definition for `#{key}`" unless type_definition[:optional]
+ else
+ raise InvalidFeatureFlagError, "Unknown feature flag type used: `#{type}`"
+ end
+ end
+
+ private
+
+ def load_from_file(path)
+ definition = File.read(path)
+ definition = YAML.safe_load(definition)
+ definition.deep_symbolize_keys!
+
+ self.new(path, definition).tap(&:validate!)
+ rescue => e
+ raise Feature::InvalidFeatureFlagError, "Invalid definition for `#{path}`: #{e.message}"
+ end
+
+ def load_all_from_path!(glob_path)
+ Dir.glob(glob_path).each do |path|
+ definition = load_from_file(path)
+
+ if previous = definitions[definition.key]
+ raise InvalidFeatureFlagError, "Feature flag '#{definition.key}' is already defined in '#{previous.path}'"
+ end
+
+ definitions[definition.key] = definition
+ end
+ end
+ end
+ end
+end
+
+Feature::Definition.prepend_if_ee('EE::Feature::Definition')
diff --git a/lib/feature/shared.rb b/lib/feature/shared.rb
new file mode 100644
index 00000000000..14efbb07100
--- /dev/null
+++ b/lib/feature/shared.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+# This file can contain only simple constructs as it is shared between:
+# 1. `Pure Ruby`: `bin/feature-flag`
+# 2. `GitLab Rails`: `lib/feature/definition.rb`
+
+class Feature
+ module Shared
+ # optional: defines if a on-disk definition is required for this feature flag type
+ # rollout_issue: defines if `bin/feature-flag` asks for rollout issue
+ # example: usage being shown when exception is raised
+ TYPES = {
+ development: {
+ description: 'Short lived, used to enable unfinished code to be deployed',
+ optional: true,
+ rollout_issue: true,
+ example: <<-EOS
+ Feature.enabled?(:my_feature_flag)
+ Feature.enabled?(:my_feature_flag, type: :development)
+ EOS
+ }
+ }.freeze
+
+ PARAMS = %i[
+ name
+ default_enabled
+ type
+ introduced_by_url
+ rollout_issue_url
+ group
+ ].freeze
+ end
+end
diff --git a/lib/gitlab/action_cable/config.rb b/lib/gitlab/action_cable/config.rb
new file mode 100644
index 00000000000..38e870353eb
--- /dev/null
+++ b/lib/gitlab/action_cable/config.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module ActionCable
+ class Config
+ class << self
+ def in_app?
+ Gitlab::Utils.to_boolean(ENV.fetch('ACTION_CABLE_IN_APP', false))
+ end
+
+ def worker_pool_size
+ ENV.fetch('ACTION_CABLE_WORKER_POOL_SIZE', 4).to_i
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/alert_management/alert_params.rb b/lib/gitlab/alert_management/alert_params.rb
index 789a4fe246a..84a75e62ecf 100644
--- a/lib/gitlab/alert_management/alert_params.rb
+++ b/lib/gitlab/alert_management/alert_params.rb
@@ -8,7 +8,7 @@ module Gitlab
}.freeze
def self.from_generic_alert(project:, payload:)
- parsed_payload = Gitlab::Alerting::NotificationPayloadParser.call(payload).with_indifferent_access
+ parsed_payload = Gitlab::Alerting::NotificationPayloadParser.call(payload, project).with_indifferent_access
annotations = parsed_payload[:annotations]
{
@@ -34,7 +34,9 @@ module Gitlab
payload: parsed_alert.payload,
started_at: parsed_alert.starts_at,
ended_at: parsed_alert.ends_at,
- fingerprint: parsed_alert.gitlab_fingerprint
+ fingerprint: parsed_alert.gitlab_fingerprint,
+ environment: parsed_alert.environment,
+ prometheus_alert: parsed_alert.gitlab_alert
}
end
end
diff --git a/lib/gitlab/alert_management/fingerprint.rb b/lib/gitlab/alert_management/fingerprint.rb
index 6ab47c88ca1..d7842d3b37d 100644
--- a/lib/gitlab/alert_management/fingerprint.rb
+++ b/lib/gitlab/alert_management/fingerprint.rb
@@ -10,11 +10,14 @@ module Gitlab
def generate(data)
return unless data.present?
- if data.is_a?(Array)
- data = flatten_array(data)
- end
+ string = case data
+ when Array then flatten_array(data)
+ when Hash then flatten_hash(data)
+ else
+ data.to_s
+ end
- Digest::SHA1.hexdigest(data.to_s)
+ Digest::SHA1.hexdigest(string)
end
private
@@ -22,6 +25,11 @@ module Gitlab
def flatten_array(array)
array.flatten.map!(&:to_s).join
end
+
+ def flatten_hash(hash)
+ # Sort hash so SHA generated is the same
+ Gitlab::Utils::SafeInlineHash.merge_keys!(hash).sort.to_s
+ end
end
end
end
diff --git a/lib/gitlab/alerting/notification_payload_parser.rb b/lib/gitlab/alerting/notification_payload_parser.rb
index d98b9296347..f285dcf507f 100644
--- a/lib/gitlab/alerting/notification_payload_parser.rb
+++ b/lib/gitlab/alerting/notification_payload_parser.rb
@@ -8,12 +8,13 @@ module Gitlab
DEFAULT_TITLE = 'New: Incident'
DEFAULT_SEVERITY = 'critical'
- def initialize(payload)
+ def initialize(payload, project)
@payload = payload.to_h.with_indifferent_access
+ @project = project
end
- def self.call(payload)
- new(payload).call
+ def self.call(payload, project)
+ new(payload, project).call
end
def call
@@ -25,7 +26,7 @@ module Gitlab
private
- attr_reader :payload
+ attr_reader :payload, :project
def title
payload[:title].presence || DEFAULT_TITLE
@@ -84,3 +85,5 @@ module Gitlab
end
end
end
+
+Gitlab::Alerting::NotificationPayloadParser.prepend_if_ee('EE::Gitlab::Alerting::NotificationPayloadParser')
diff --git a/lib/gitlab/analytics/cycle_analytics/records_fetcher.rb b/lib/gitlab/analytics/cycle_analytics/records_fetcher.rb
index e7352a23b99..4d47a17545a 100644
--- a/lib/gitlab/analytics/cycle_analytics/records_fetcher.rb
+++ b/lib/gitlab/analytics/cycle_analytics/records_fetcher.rb
@@ -90,9 +90,7 @@ module Gitlab
end
def ordered_and_limited_query
- query
- .reorder(stage.end_event.timestamp_projection.desc)
- .limit(MAX_RECORDS)
+ order_by_end_event(query).limit(MAX_RECORDS)
end
def records
diff --git a/lib/gitlab/analytics/cycle_analytics/stage_events.rb b/lib/gitlab/analytics/cycle_analytics/stage_events.rb
index 5146f92f521..39dc706dff5 100644
--- a/lib/gitlab/analytics/cycle_analytics/stage_events.rb
+++ b/lib/gitlab/analytics/cycle_analytics/stage_events.rb
@@ -60,7 +60,7 @@ module Gitlab
# hash for defining ActiveRecord enum: identifier => number
def self.to_enum
- enum_mapping.each_with_object({}) { |(k, v), hash| hash[k.identifier] = v }
+ enum_mapping.transform_keys { |k| k.identifier }
end
def self.pairing_rules
diff --git a/lib/gitlab/analytics/cycle_analytics/stage_events/code_stage_start.rb b/lib/gitlab/analytics/cycle_analytics/stage_events/code_stage_start.rb
index 9f0ca80ba50..c5f843d5f1a 100644
--- a/lib/gitlab/analytics/cycle_analytics/stage_events/code_stage_start.rb
+++ b/lib/gitlab/analytics/cycle_analytics/stage_events/code_stage_start.rb
@@ -21,6 +21,11 @@ module Gitlab
issue_metrics_table[:first_mentioned_in_commit_at]
end
+ override :column_list
+ def column_list
+ [timestamp_projection]
+ end
+
# rubocop: disable CodeReuse/ActiveRecord
def apply_query_customization(query)
issue_metrics_join = mr_closing_issues_table
diff --git a/lib/gitlab/analytics/cycle_analytics/stage_events/issue_stage_end.rb b/lib/gitlab/analytics/cycle_analytics/stage_events/issue_stage_end.rb
index 0ea98e82ecc..7c1f4436c93 100644
--- a/lib/gitlab/analytics/cycle_analytics/stage_events/issue_stage_end.rb
+++ b/lib/gitlab/analytics/cycle_analytics/stage_events/issue_stage_end.rb
@@ -18,10 +18,15 @@ module Gitlab
end
def timestamp_projection
- Arel::Nodes::NamedFunction.new('COALESCE', [
+ Arel::Nodes::NamedFunction.new('COALESCE', column_list)
+ end
+
+ override :column_list
+ def column_list
+ [
issue_metrics_table[:first_associated_with_milestone_at],
issue_metrics_table[:first_added_to_board_at]
- ])
+ ]
end
# rubocop: disable CodeReuse/ActiveRecord
diff --git a/lib/gitlab/analytics/cycle_analytics/stage_events/metrics_based_stage_event.rb b/lib/gitlab/analytics/cycle_analytics/stage_events/metrics_based_stage_event.rb
index 4ca8745abe4..fe477490648 100644
--- a/lib/gitlab/analytics/cycle_analytics/stage_events/metrics_based_stage_event.rb
+++ b/lib/gitlab/analytics/cycle_analytics/stage_events/metrics_based_stage_event.rb
@@ -10,6 +10,11 @@ module Gitlab
query.joins(:metrics)
end
# rubocop: enable CodeReuse/ActiveRecord
+
+ override :column_list
+ def column_list
+ [timestamp_projection]
+ end
end
end
end
diff --git a/lib/gitlab/analytics/cycle_analytics/stage_events/plan_stage_start.rb b/lib/gitlab/analytics/cycle_analytics/stage_events/plan_stage_start.rb
index 37168a1fb0f..bddc326de71 100644
--- a/lib/gitlab/analytics/cycle_analytics/stage_events/plan_stage_start.rb
+++ b/lib/gitlab/analytics/cycle_analytics/stage_events/plan_stage_start.rb
@@ -18,10 +18,15 @@ module Gitlab
end
def timestamp_projection
- Arel::Nodes::NamedFunction.new('COALESCE', [
+ Arel::Nodes::NamedFunction.new('COALESCE', column_list)
+ end
+
+ override :column_list
+ def column_list
+ [
issue_metrics_table[:first_associated_with_milestone_at],
issue_metrics_table[:first_added_to_board_at]
- ])
+ ]
end
# rubocop: disable CodeReuse/ActiveRecord
diff --git a/lib/gitlab/analytics/cycle_analytics/stage_events/production_stage_end.rb b/lib/gitlab/analytics/cycle_analytics/stage_events/production_stage_end.rb
index 619b45664fa..cf05ebeb706 100644
--- a/lib/gitlab/analytics/cycle_analytics/stage_events/production_stage_end.rb
+++ b/lib/gitlab/analytics/cycle_analytics/stage_events/production_stage_end.rb
@@ -21,6 +21,11 @@ module Gitlab
mr_metrics_table[:first_deployed_to_production_at]
end
+ override :column_list
+ def column_list
+ [timestamp_projection]
+ end
+
# rubocop: disable CodeReuse/ActiveRecord
def apply_query_customization(query)
query.joins(merge_requests_closing_issues: { merge_request: [:metrics] }).where(mr_metrics_table[:first_deployed_to_production_at].gteq(mr_table[:created_at]))
diff --git a/lib/gitlab/analytics/cycle_analytics/stage_events/stage_event.rb b/lib/gitlab/analytics/cycle_analytics/stage_events/stage_event.rb
index 0c75a141c3c..79738747e71 100644
--- a/lib/gitlab/analytics/cycle_analytics/stage_events/stage_event.rb
+++ b/lib/gitlab/analytics/cycle_analytics/stage_events/stage_event.rb
@@ -7,6 +7,7 @@ module Gitlab
# Base class for expressing an event that can be used for a stage.
class StageEvent
include Gitlab::CycleAnalytics::MetricsTables
+ extend Gitlab::Utils::Override
delegate :label_based?, to: :class
@@ -32,6 +33,13 @@ module Gitlab
raise NotImplementedError
end
+ # List of columns that are referenced in the `timestamp_projection` expression
+ # Example timestamp projection: COALESCE(issue_metrics.created_at, issue_metrics.updated_at)
+ # Expected column list: issue_metrics.created_at, issue_metrics.updated_at
+ def column_list
+ []
+ end
+
# Optionally a StageEvent may apply additional filtering or join other tables on the base query.
def apply_query_customization(query)
query
diff --git a/lib/gitlab/analytics/cycle_analytics/stage_query_helpers.rb b/lib/gitlab/analytics/cycle_analytics/stage_query_helpers.rb
index 29a2d55df1a..c9a75b39959 100644
--- a/lib/gitlab/analytics/cycle_analytics/stage_query_helpers.rb
+++ b/lib/gitlab/analytics/cycle_analytics/stage_query_helpers.rb
@@ -22,6 +22,29 @@ module Gitlab
stage.start_event.timestamp_projection
)
end
+
+ # rubocop: disable CodeReuse/ActiveRecord
+ def order_by_end_event(query)
+ ordered_query = query.reorder(stage.end_event.timestamp_projection.desc)
+
+ # When filtering for more than one label, postgres requires the columns in ORDER BY to be present in the GROUP BY clause
+ if requires_grouping?
+ column_list = [
+ ordered_query.arel_table[:id],
+ *stage.end_event.column_list,
+ *stage.start_event.column_list
+ ]
+
+ ordered_query = ordered_query.group(column_list)
+ end
+
+ ordered_query
+ end
+ # rubocop: enable CodeReuse/ActiveRecord
+
+ def requires_grouping?
+ Array(params[:label_name]).size > 1
+ end
end
end
end
diff --git a/lib/gitlab/analytics/unique_visits.rb b/lib/gitlab/analytics/unique_visits.rb
new file mode 100644
index 00000000000..9dd7d048eec
--- /dev/null
+++ b/lib/gitlab/analytics/unique_visits.rb
@@ -0,0 +1,60 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Analytics
+ class UniqueVisits
+ TARGET_IDS = Set[
+ 'g_analytics_contribution',
+ 'g_analytics_insights',
+ 'g_analytics_issues',
+ 'g_analytics_productivity',
+ 'g_analytics_valuestream',
+ 'p_analytics_pipelines',
+ 'p_analytics_code_reviews',
+ 'p_analytics_valuestream',
+ 'p_analytics_insights',
+ 'p_analytics_issues',
+ 'p_analytics_repo',
+ 'u_analytics_todos',
+ 'i_analytics_cohorts',
+ 'i_analytics_dev_ops_score'
+ ].freeze
+
+ KEY_EXPIRY_LENGTH = 28.days
+
+ def track_visit(visitor_id, target_id, time = Time.zone.now)
+ target_key = key(target_id, time)
+
+ Gitlab::Redis::SharedState.with do |redis|
+ redis.multi do |multi|
+ multi.pfadd(target_key, visitor_id)
+ multi.expire(target_key, KEY_EXPIRY_LENGTH)
+ end
+ end
+ end
+
+ def weekly_unique_visits_for_target(target_id, week_of: 7.days.ago)
+ Gitlab::Redis::SharedState.with do |redis|
+ redis.pfcount(key(target_id, week_of))
+ end
+ end
+
+ def weekly_unique_visits_for_any_target(week_of: 7.days.ago)
+ keys = TARGET_IDS.map { |target_id| key(target_id, week_of) }
+
+ Gitlab::Redis::SharedState.with do |redis|
+ redis.pfcount(*keys)
+ end
+ end
+
+ private
+
+ def key(target_id, time)
+ raise "Invalid target id #{target_id}" unless TARGET_IDS.include?(target_id.to_s)
+
+ year_week = time.strftime('%G-%V')
+ "#{target_id}-{#{year_week}}"
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/application_rate_limiter.rb b/lib/gitlab/application_rate_limiter.rb
index 3277ddd9f49..ed963476524 100644
--- a/lib/gitlab/application_rate_limiter.rb
+++ b/lib/gitlab/application_rate_limiter.rb
@@ -19,17 +19,17 @@ module Gitlab
# and only do that when it's needed.
def rate_limits
{
- issues_create: { threshold: -> { Gitlab::CurrentSettings.current_application_settings.issues_create_limit }, interval: 1.minute },
- project_export: { threshold: 30, interval: 5.minutes },
- project_download_export: { threshold: 10, interval: 10.minutes },
+ issues_create: { threshold: -> { application_settings.issues_create_limit }, interval: 1.minute },
+ project_export: { threshold: -> { application_settings.project_export_limit }, interval: 1.minute },
+ project_download_export: { threshold: -> { application_settings.project_download_export_limit }, interval: 1.minute },
project_repositories_archive: { threshold: 5, interval: 1.minute },
- project_generate_new_export: { threshold: 30, interval: 5.minutes },
- project_import: { threshold: 30, interval: 5.minutes },
- play_pipeline_schedule: { threshold: 1, interval: 1.minute },
- show_raw_controller: { threshold: -> { Gitlab::CurrentSettings.current_application_settings.raw_blob_request_limit }, interval: 1.minute },
- group_export: { threshold: 30, interval: 5.minutes },
- group_download_export: { threshold: 10, interval: 10.minutes },
- group_import: { threshold: 30, interval: 5.minutes }
+ project_generate_new_export: { threshold: -> { application_settings.project_export_limit }, interval: 1.minute },
+ project_import: { threshold: -> { application_settings.project_import_limit }, interval: 1.minute },
+ play_pipeline_schedule: { threshold: 1, interval: 1.minute },
+ show_raw_controller: { threshold: -> { application_settings.raw_blob_request_limit }, interval: 1.minute },
+ group_export: { threshold: -> { application_settings.group_export_limit }, interval: 1.minute },
+ group_download_export: { threshold: -> { application_settings.group_download_export_limit }, interval: 1.minute },
+ group_import: { threshold: -> { application_settings.group_import_limit }, interval: 1.minute }
}.freeze
end
@@ -130,6 +130,10 @@ module Gitlab
"application_rate_limiter:#{serialized}"
end
+
+ def application_settings
+ Gitlab::CurrentSettings.current_application_settings
+ end
end
end
end
diff --git a/lib/gitlab/auth/auth_finders.rb b/lib/gitlab/auth/auth_finders.rb
index 93342fbad51..bd5aed0d964 100644
--- a/lib/gitlab/auth/auth_finders.rb
+++ b/lib/gitlab/auth/auth_finders.rb
@@ -54,6 +54,11 @@ module Gitlab
User.find_by_feed_token(token) || raise(UnauthorizedError)
end
+ def find_user_from_bearer_token
+ find_user_from_job_bearer_token ||
+ find_user_from_access_token
+ end
+
def find_user_from_job_token
return unless route_authentication_setting[:job_token_allowed]
return find_user_from_basic_auth_job if route_authentication_setting[:job_token_allowed] == :basic_auth
@@ -92,6 +97,8 @@ module Gitlab
validate_access_token!(scopes: [:api])
+ ::PersonalAccessTokens::LastUsedService.new(access_token).execute
+
access_token.user || raise(UnauthorizedError)
end
@@ -100,6 +107,8 @@ module Gitlab
validate_access_token!
+ ::PersonalAccessTokens::LastUsedService.new(access_token).execute
+
access_token.user || raise(UnauthorizedError)
end
@@ -132,6 +141,9 @@ module Gitlab
end
def validate_access_token!(scopes: [])
+ # return early if we've already authenticated via a job token
+ return if @current_authenticated_job.present? # rubocop:disable Gitlab/ModuleWithInstanceVariables
+
# return early if we've already authenticated via a deploy token
return if @current_authenticated_deploy_token.present? # rubocop:disable Gitlab/ModuleWithInstanceVariables
@@ -151,6 +163,20 @@ module Gitlab
private
+ def find_user_from_job_bearer_token
+ return unless route_authentication_setting[:job_token_allowed]
+
+ token = parsed_oauth_token
+ return unless token
+
+ job = ::Ci::Build.find_by_token(token)
+ return unless job
+
+ @current_authenticated_job = job # rubocop:disable Gitlab/ModuleWithInstanceVariables
+
+ job.user
+ end
+
def route_authentication_setting
return {} unless respond_to?(:route_setting)
diff --git a/lib/gitlab/background_migration.rb b/lib/gitlab/background_migration.rb
index 6a16c37e880..ea0307e8bd6 100644
--- a/lib/gitlab/background_migration.rb
+++ b/lib/gitlab/background_migration.rb
@@ -33,6 +33,7 @@ module Gitlab
next unless job.queue == self.queue
next unless migration_class == steal_class
+ next if block_given? && !(yield migration_args)
begin
perform(migration_class, migration_args) if job.delete
diff --git a/lib/gitlab/background_migration/backfill_namespace_settings.rb b/lib/gitlab/background_migration/backfill_namespace_settings.rb
new file mode 100644
index 00000000000..a391d5f4ebe
--- /dev/null
+++ b/lib/gitlab/background_migration/backfill_namespace_settings.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module BackgroundMigration
+ # Backfillnamespace_settings for a range of namespaces
+ class BackfillNamespaceSettings
+ def perform(start_id, end_id)
+ ActiveRecord::Base.connection.execute <<~SQL
+ INSERT INTO namespace_settings (namespace_id, created_at, updated_at)
+ SELECT namespaces.id, now(), now()
+ FROM namespaces
+ WHERE namespaces.id BETWEEN #{start_id} AND #{end_id}
+ ON CONFLICT (namespace_id) DO NOTHING;
+ SQL
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/background_migration/cleanup_concurrent_schema_change.rb b/lib/gitlab/background_migration/cleanup_concurrent_schema_change.rb
index 54f77f184d5..91b50c1a493 100644
--- a/lib/gitlab/background_migration/cleanup_concurrent_schema_change.rb
+++ b/lib/gitlab/background_migration/cleanup_concurrent_schema_change.rb
@@ -2,7 +2,7 @@
module Gitlab
module BackgroundMigration
- # Base class for cleaning up concurrent schema changes.
+ # Base class for background migration for rename/type changes.
class CleanupConcurrentSchemaChange
include Database::MigrationHelpers
@@ -10,7 +10,7 @@ module Gitlab
# old_column - The name of the old (to drop) column.
# new_column - The name of the new column.
def perform(table, old_column, new_column)
- return unless column_exists?(table, new_column)
+ return unless column_exists?(table, new_column) && column_exists?(table, old_column)
rows_to_migrate = define_model_for(table)
.where(new_column => nil)
@@ -28,6 +28,10 @@ module Gitlab
end
end
+ def cleanup_concurrent_schema_change(_table, _old_column, _new_column)
+ raise NotImplementedError
+ end
+
# These methods are necessary so we can re-use the migration helpers in
# this class.
def connection
diff --git a/lib/gitlab/background_migration/digest_column.rb b/lib/gitlab/background_migration/digest_column.rb
deleted file mode 100644
index 22a3bb8f8f3..00000000000
--- a/lib/gitlab/background_migration/digest_column.rb
+++ /dev/null
@@ -1,25 +0,0 @@
-# frozen_string_literal: true
-
-# rubocop:disable Style/Documentation
-module Gitlab
- module BackgroundMigration
- class DigestColumn
- class PersonalAccessToken < ActiveRecord::Base
- self.table_name = 'personal_access_tokens'
- end
-
- def perform(model, attribute_from, attribute_to, start_id, stop_id)
- model = model.constantize if model.is_a?(String)
-
- model.transaction do
- relation = model.where(id: start_id..stop_id).where.not(attribute_from => nil).lock
-
- relation.each do |instance|
- instance.update_columns(attribute_to => Gitlab::CryptoHelper.sha256(instance.read_attribute(attribute_from)),
- attribute_from => nil)
- end
- end
- end
- end
- end
-end
diff --git a/lib/gitlab/background_migration/encrypt_columns.rb b/lib/gitlab/background_migration/encrypt_columns.rb
deleted file mode 100644
index 173543b7c25..00000000000
--- a/lib/gitlab/background_migration/encrypt_columns.rb
+++ /dev/null
@@ -1,104 +0,0 @@
-# frozen_string_literal: true
-
-module Gitlab
- module BackgroundMigration
- # EncryptColumn migrates data from an unencrypted column - `foo`, say - to
- # an encrypted column - `encrypted_foo`, say.
- #
- # To avoid depending on a particular version of the model in app/, add a
- # model to `lib/gitlab/background_migration/models/encrypt_columns` and use
- # it in the migration that enqueues the jobs, so code can be shared.
- #
- # For this background migration to work, the table that is migrated _has_ to
- # have an `id` column as the primary key. Additionally, the encrypted column
- # should be managed by attr_encrypted, and map to an attribute with the same
- # name as the unencrypted column (i.e., the unencrypted column should be
- # shadowed), unless you want to define specific methods / accessors in the
- # temporary model in `/models/encrypt_columns/your_model.rb`.
- #
- class EncryptColumns
- def perform(model, attributes, from, to)
- model = model.constantize if model.is_a?(String)
-
- # If sidekiq hasn't undergone a restart, its idea of what columns are
- # present may be inaccurate, so ensure this is as fresh as possible
- model.reset_column_information
- model.define_attribute_methods
-
- attributes = expand_attributes(model, Array(attributes).map(&:to_sym))
-
- model.transaction do
- # Use SELECT ... FOR UPDATE to prevent the value being changed while
- # we are encrypting it
- relation = model.where(id: from..to).lock
-
- relation.each do |instance|
- encrypt!(instance, attributes)
- end
- end
- end
-
- def clear_migrated_values?
- true
- end
-
- private
-
- # Build a hash of { attribute => encrypted column name }
- def expand_attributes(klass, attributes)
- expanded = attributes.flat_map do |attribute|
- attr_config = klass.encrypted_attributes[attribute]
- crypt_column_name = attr_config&.fetch(:attribute)
-
- raise "Couldn't determine encrypted column for #{klass}##{attribute}" if
- crypt_column_name.nil?
-
- raise "#{klass} source column: #{attribute} is missing" unless
- klass.column_names.include?(attribute.to_s)
-
- # Running the migration without the destination column being present
- # leads to data loss
- raise "#{klass} destination column: #{crypt_column_name} is missing" unless
- klass.column_names.include?(crypt_column_name.to_s)
-
- [attribute, crypt_column_name]
- end
-
- Hash[*expanded]
- end
-
- # Generate ciphertext for each column and update the database
- def encrypt!(instance, attributes)
- to_clear = attributes
- .map { |plain, crypt| apply_attribute!(instance, plain, crypt) }
- .compact
- .flat_map { |plain| [plain, nil] }
-
- to_clear = Hash[*to_clear]
-
- if instance.changed?
- instance.save!
-
- if clear_migrated_values?
- instance.update_columns(to_clear)
- end
- end
- end
-
- def apply_attribute!(instance, plain_column, crypt_column)
- plaintext = instance[plain_column]
- ciphertext = instance[crypt_column]
-
- # No need to do anything if the plaintext is nil, or an encrypted
- # value already exists
- return unless plaintext.present?
- return if ciphertext.present?
-
- # attr_encrypted will calculate and set the expected value for us
- instance.public_send("#{plain_column}=", plaintext) # rubocop:disable GitlabSecurity/PublicSend
-
- plain_column
- end
- end
- end
-end
diff --git a/lib/gitlab/background_migration/encrypt_runners_tokens.rb b/lib/gitlab/background_migration/encrypt_runners_tokens.rb
deleted file mode 100644
index ec64a73542e..00000000000
--- a/lib/gitlab/background_migration/encrypt_runners_tokens.rb
+++ /dev/null
@@ -1,32 +0,0 @@
-# frozen_string_literal: true
-
-module Gitlab
- module BackgroundMigration
- # EncryptColumn migrates data from an unencrypted column - `foo`, say - to
- # an encrypted column - `encrypted_foo`, say.
- #
- # We only create a subclass here because we want to isolate this migration
- # (migrating unencrypted runner registration tokens to encrypted columns)
- # from other `EncryptColumns` migration. This class name is going to be
- # serialized and stored in Redis and later picked by Sidekiq, so we need to
- # create a separate class name in order to isolate these migration tasks.
- #
- # We can solve this differently, see tech debt issue:
- #
- # https://gitlab.com/gitlab-org/gitlab-foss/issues/54328
- #
- class EncryptRunnersTokens < EncryptColumns
- def perform(model, from, to)
- resource = "::Gitlab::BackgroundMigration::Models::EncryptColumns::#{model.to_s.capitalize}"
- model = resource.constantize
- attributes = model.encrypted_attributes.keys
-
- super(model, attributes, from, to)
- end
-
- def clear_migrated_values?
- false
- end
- end
- end
-end
diff --git a/lib/gitlab/background_migration/fix_pages_access_level.rb b/lib/gitlab/background_migration/fix_pages_access_level.rb
index 0d49f3dd8c5..31d2e78b2d2 100644
--- a/lib/gitlab/background_migration/fix_pages_access_level.rb
+++ b/lib/gitlab/background_migration/fix_pages_access_level.rb
@@ -16,7 +16,7 @@ module Gitlab
end
# Namespace
- class Namespace < ApplicationRecord
+ class Namespace < ActiveRecord::Base
self.table_name = 'namespaces'
self.inheritance_column = :_type_disabled
diff --git a/lib/gitlab/background_migration/mailers/unconfirm_mailer.rb b/lib/gitlab/background_migration/mailers/unconfirm_mailer.rb
new file mode 100644
index 00000000000..c096dae0631
--- /dev/null
+++ b/lib/gitlab/background_migration/mailers/unconfirm_mailer.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+# rubocop:disable Style/Documentation
+
+module Gitlab
+ module BackgroundMigration
+ module Mailers
+ class UnconfirmMailer < ::Notify
+ prepend_view_path(File.join(__dir__, 'views'))
+
+ def unconfirm_notification_email(user)
+ @user = user
+ @verification_from_mail = Gitlab.config.gitlab.email_from
+
+ mail(
+ template_path: 'unconfirm_mailer',
+ template_name: 'unconfirm_notification_email',
+ to: @user.notification_email,
+ subject: subject('GitLab email verification request')
+ )
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/background_migration/mailers/views/unconfirm_mailer/unconfirm_notification_email.html.haml b/lib/gitlab/background_migration/mailers/views/unconfirm_mailer/unconfirm_notification_email.html.haml
new file mode 100644
index 00000000000..d8f7466a1ca
--- /dev/null
+++ b/lib/gitlab/background_migration/mailers/views/unconfirm_mailer/unconfirm_notification_email.html.haml
@@ -0,0 +1,19 @@
+-# haml-lint:disable NoPlainNodes
+%p
+ Dear GitLab user,
+
+%p
+ As part of our commitment to keeping GitLab secure, we have identified and addressed a vulnerability in GitLab that allowed some users to bypass the email verification process in a #{link_to("recent security release", "https://about.gitlab.com/releases/2020/05/27/security-release-13-0-1-released", target: '_blank')}.
+
+%p
+ As a precautionary measure, you will need to re-verify some of your account's email addresses before continuing to use GitLab. Sorry for the inconvenience!
+
+%p
+ We have already sent the re-verification email with a subject line of "Confirmation instructions" from #{@verification_from_mail}. Please feel free to contribute any questions or comments to #{link_to("this issue", "https://gitlab.com/gitlab-com/www-gitlab-com/-/issues/7942", target: '_blank')}.
+
+%p
+ If you are not "#{@user.username}", please #{link_to 'report this to our administrator', new_abuse_report_url(user_id: @user.id)}
+
+%p
+ Thank you for being a GitLab user!
+-# haml-lint:enable NoPlainNodes
diff --git a/lib/gitlab/background_migration/mailers/views/unconfirm_mailer/unconfirm_notification_email.text.erb b/lib/gitlab/background_migration/mailers/views/unconfirm_mailer/unconfirm_notification_email.text.erb
new file mode 100644
index 00000000000..d20af9b9803
--- /dev/null
+++ b/lib/gitlab/background_migration/mailers/views/unconfirm_mailer/unconfirm_notification_email.text.erb
@@ -0,0 +1,14 @@
+Dear GitLab user,
+
+As part of our commitment to keeping GitLab secure, we have identified and addressed a vulnerability in GitLab that allowed some users to bypass the email verification process in a recent security release.
+
+Security release: https://about.gitlab.com/releases/2020/05/27/security-release-13-0-1-released
+
+As a precautionary measure, you will need to re-verify some of your account's email addresses before continuing to use GitLab. Sorry for the inconvenience!
+
+We have already sent the re-verification email with a subject line of "Confirmation instructions" from <%= @verification_from_mail %>.
+Please feel free to contribute any questions or comments to this issue: https://gitlab.com/gitlab-com/www-gitlab-com/-/issues/7942
+
+If you are not "<%= @user.username %>", please report this to our administrator. Report link: <%= new_abuse_report_url(user_id: @user.id) %>
+
+Thank you for being a GitLab user!
diff --git a/lib/gitlab/background_migration/models/encrypt_columns/namespace.rb b/lib/gitlab/background_migration/models/encrypt_columns/namespace.rb
deleted file mode 100644
index 41f18979d76..00000000000
--- a/lib/gitlab/background_migration/models/encrypt_columns/namespace.rb
+++ /dev/null
@@ -1,28 +0,0 @@
-# frozen_string_literal: true
-
-module Gitlab
- module BackgroundMigration
- module Models
- module EncryptColumns
- # This model is shared between synchronous and background migrations to
- # encrypt the `runners_token` column in `namespaces` table.
- #
- class Namespace < ActiveRecord::Base
- include ::EachBatch
-
- self.table_name = 'namespaces'
- self.inheritance_column = :_type_disabled
-
- def runners_token=(value)
- self.runners_token_encrypted =
- ::Gitlab::CryptoHelper.aes256_gcm_encrypt(value)
- end
-
- def self.encrypted_attributes
- { runners_token: { attribute: :runners_token_encrypted } }
- end
- end
- end
- end
- end
-end
diff --git a/lib/gitlab/background_migration/models/encrypt_columns/project.rb b/lib/gitlab/background_migration/models/encrypt_columns/project.rb
deleted file mode 100644
index bfeae14584d..00000000000
--- a/lib/gitlab/background_migration/models/encrypt_columns/project.rb
+++ /dev/null
@@ -1,28 +0,0 @@
-# frozen_string_literal: true
-
-module Gitlab
- module BackgroundMigration
- module Models
- module EncryptColumns
- # This model is shared between synchronous and background migrations to
- # encrypt the `runners_token` column in `projects` table.
- #
- class Project < ActiveRecord::Base
- include ::EachBatch
-
- self.table_name = 'projects'
- self.inheritance_column = :_type_disabled
-
- def runners_token=(value)
- self.runners_token_encrypted =
- ::Gitlab::CryptoHelper.aes256_gcm_encrypt(value)
- end
-
- def self.encrypted_attributes
- { runners_token: { attribute: :runners_token_encrypted } }
- end
- end
- end
- end
- end
-end
diff --git a/lib/gitlab/background_migration/models/encrypt_columns/runner.rb b/lib/gitlab/background_migration/models/encrypt_columns/runner.rb
deleted file mode 100644
index 14ddce4b147..00000000000
--- a/lib/gitlab/background_migration/models/encrypt_columns/runner.rb
+++ /dev/null
@@ -1,28 +0,0 @@
-# frozen_string_literal: true
-
-module Gitlab
- module BackgroundMigration
- module Models
- module EncryptColumns
- # This model is shared between synchronous and background migrations to
- # encrypt the `token` column in `ci_runners` table.
- #
- class Runner < ActiveRecord::Base
- include ::EachBatch
-
- self.table_name = 'ci_runners'
- self.inheritance_column = :_type_disabled
-
- def token=(value)
- self.token_encrypted =
- ::Gitlab::CryptoHelper.aes256_gcm_encrypt(value)
- end
-
- def self.encrypted_attributes
- { token: { attribute: :token_encrypted } }
- end
- end
- end
- end
- end
-end
diff --git a/lib/gitlab/background_migration/models/encrypt_columns/settings.rb b/lib/gitlab/background_migration/models/encrypt_columns/settings.rb
deleted file mode 100644
index 08ae35c0671..00000000000
--- a/lib/gitlab/background_migration/models/encrypt_columns/settings.rb
+++ /dev/null
@@ -1,37 +0,0 @@
-# frozen_string_literal: true
-
-module Gitlab
- module BackgroundMigration
- module Models
- module EncryptColumns
- # This model is shared between synchronous and background migrations to
- # encrypt the `runners_token` column in `application_settings` table.
- #
- class Settings < ActiveRecord::Base
- include ::EachBatch
- include ::CacheableAttributes
-
- self.table_name = 'application_settings'
- self.inheritance_column = :_type_disabled
-
- after_commit do
- ::ApplicationSetting.expire
- end
-
- def runners_registration_token=(value)
- self.runners_registration_token_encrypted =
- ::Gitlab::CryptoHelper.aes256_gcm_encrypt(value)
- end
-
- def self.encrypted_attributes
- {
- runners_registration_token: {
- attribute: :runners_registration_token_encrypted
- }
- }
- end
- end
- end
- end
- end
-end
diff --git a/lib/gitlab/background_migration/models/encrypt_columns/web_hook.rb b/lib/gitlab/background_migration/models/encrypt_columns/web_hook.rb
deleted file mode 100644
index 34e72fd9f34..00000000000
--- a/lib/gitlab/background_migration/models/encrypt_columns/web_hook.rb
+++ /dev/null
@@ -1,28 +0,0 @@
-# frozen_string_literal: true
-
-module Gitlab
- module BackgroundMigration
- module Models
- module EncryptColumns
- # This model is shared between synchronous and background migrations to
- # encrypt the `token` and `url` columns
- class WebHook < ActiveRecord::Base
- include ::EachBatch
-
- self.table_name = 'web_hooks'
- self.inheritance_column = :_type_disabled
-
- attr_encrypted :token,
- mode: :per_attribute_iv,
- algorithm: 'aes-256-gcm',
- key: ::Settings.attr_encrypted_db_key_base_32
-
- attr_encrypted :url,
- mode: :per_attribute_iv,
- algorithm: 'aes-256-gcm',
- key: ::Settings.attr_encrypted_db_key_base_32
- end
- end
- end
- end
-end
diff --git a/lib/gitlab/background_migration/populate_project_snippet_statistics.rb b/lib/gitlab/background_migration/populate_project_snippet_statistics.rb
new file mode 100644
index 00000000000..7659b63271f
--- /dev/null
+++ b/lib/gitlab/background_migration/populate_project_snippet_statistics.rb
@@ -0,0 +1,61 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module BackgroundMigration
+ # This class creates/updates those project snippets statistics
+ # that haven't been created nor initialized.
+ # It also updates the related project statistics and its root storage namespace stats
+ class PopulateProjectSnippetStatistics
+ def perform(snippet_ids)
+ project_snippets(snippet_ids).group_by(&:namespace_id).each do |namespace_id, namespace_snippets|
+ namespace_snippets.group_by(&:project).each do |project, snippets|
+ upsert_snippet_statistics(snippets)
+ update_project_statistics(project)
+ rescue
+ error_message("Error updating statistics for project #{project.id}")
+ end
+
+ update_namespace_statistics(namespace_snippets.first.project.root_namespace)
+ rescue => e
+ error_message("Error updating statistics for namespace #{namespace_id}: #{e.message}")
+ end
+ end
+
+ private
+
+ def project_snippets(snippet_ids)
+ ProjectSnippet
+ .select('snippets.*, projects.namespace_id')
+ .where(id: snippet_ids)
+ .joins(:project)
+ .includes(:statistics)
+ .includes(snippet_repository: :shard)
+ .includes(project: [:route, :statistics, :namespace])
+ end
+
+ def upsert_snippet_statistics(snippets)
+ snippets.each do |snippet|
+ response = Snippets::UpdateStatisticsService.new(snippet).execute
+
+ error_message("#{response.message} snippet: #{snippet.id}") if response.error?
+ end
+ end
+
+ def logger
+ @logger ||= Gitlab::BackgroundMigration::Logger.build
+ end
+
+ def error_message(message)
+ logger.error(message: "Snippet Statistics Migration: #{message}")
+ end
+
+ def update_project_statistics(project)
+ project.statistics&.refresh!(only: [:snippets_size])
+ end
+
+ def update_namespace_statistics(namespace)
+ Namespaces::StatisticsRefresherService.new.execute(namespace)
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/background_migration/update_vulnerabilities_from_dismissal_feedback.rb b/lib/gitlab/background_migration/update_vulnerabilities_from_dismissal_feedback.rb
new file mode 100644
index 00000000000..bfe9f673b53
--- /dev/null
+++ b/lib/gitlab/background_migration/update_vulnerabilities_from_dismissal_feedback.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module BackgroundMigration
+ # rubocop: disable Style/Documentation
+ class UpdateVulnerabilitiesFromDismissalFeedback
+ def perform(project_id)
+ end
+ end
+ end
+end
+
+Gitlab::BackgroundMigration::UpdateVulnerabilitiesFromDismissalFeedback.prepend_if_ee('EE::Gitlab::BackgroundMigration::UpdateVulnerabilitiesFromDismissalFeedback')
diff --git a/lib/gitlab/background_migration/wrongfully_confirmed_email_unconfirmer.rb b/lib/gitlab/background_migration/wrongfully_confirmed_email_unconfirmer.rb
new file mode 100644
index 00000000000..5f63cf5836e
--- /dev/null
+++ b/lib/gitlab/background_migration/wrongfully_confirmed_email_unconfirmer.rb
@@ -0,0 +1,97 @@
+# frozen_string_literal: true
+# rubocop:disable Style/Documentation
+
+module Gitlab
+ module BackgroundMigration
+ class WrongfullyConfirmedEmailUnconfirmer
+ class UserModel < ActiveRecord::Base
+ alias_method :reset, :reload
+
+ self.table_name = 'users'
+
+ scope :active, -> { where(state: 'active', user_type: nil) } # only humans, skip bots
+
+ devise :confirmable
+ end
+
+ class EmailModel < ActiveRecord::Base
+ alias_method :reset, :reload
+
+ self.table_name = 'emails'
+
+ belongs_to :user
+
+ devise :confirmable
+
+ def self.wrongfully_confirmed_emails(start_id, stop_id)
+ joins(:user)
+ .merge(UserModel.active)
+ .where(id: (start_id..stop_id))
+ .where('emails.confirmed_at IS NOT NULL')
+ .where('emails.confirmed_at = users.confirmed_at')
+ .where('emails.email <> users.email')
+ end
+ end
+
+ def perform(start_id, stop_id)
+ email_records = EmailModel
+ .wrongfully_confirmed_emails(start_id, stop_id)
+ .to_a
+
+ user_ids = email_records.map(&:user_id).uniq
+
+ ActiveRecord::Base.transaction do
+ update_email_records(start_id, stop_id)
+ update_user_records(user_ids)
+ end
+
+ # Refind the records with the "real" Email model so devise will notice that the user / email is unconfirmed
+ unconfirmed_email_records = ::Email.where(id: email_records.map(&:id))
+ ActiveRecord::Associations::Preloader.new.preload(unconfirmed_email_records, [:user])
+
+ send_emails(unconfirmed_email_records)
+ end
+
+ private
+
+ def update_email_records(start_id, stop_id)
+ EmailModel.connection.execute <<-SQL
+ WITH md5_strings as (
+ #{email_query_for_update(start_id, stop_id).to_sql}
+ )
+ UPDATE #{EmailModel.connection.quote_table_name(EmailModel.table_name)}
+ SET confirmed_at = NULL,
+ confirmation_token = md5_strings.md5_string,
+ confirmation_sent_at = NOW()
+ FROM md5_strings
+ WHERE id = md5_strings.email_id
+ SQL
+ end
+
+ def update_user_records(user_ids)
+ UserModel
+ .where(id: user_ids)
+ .update_all("confirmed_at = NULL, confirmation_sent_at = NOW(), unconfirmed_email = NULL, confirmation_token=md5(users.id::varchar || users.created_at || users.encrypted_password || '#{Integer(Time.now.to_i)}')")
+ end
+
+ def email_query_for_update(start_id, stop_id)
+ EmailModel
+ .wrongfully_confirmed_emails(start_id, stop_id)
+ .select('emails.id as email_id', "md5(emails.id::varchar || emails.created_at || users.encrypted_password || '#{Integer(Time.now.to_i)}') as md5_string")
+ end
+
+ def send_emails(email_records)
+ user_records = email_records.map(&:user).uniq
+
+ user_records.each do |user|
+ Gitlab::BackgroundMigration::Mailers::UnconfirmMailer.unconfirm_notification_email(user).deliver_later
+ DeviseMailer.confirmation_instructions(user, user.confirmation_token).deliver_later(wait: 1.minute)
+ end
+
+ email_records.each do |email|
+ DeviseMailer.confirmation_instructions(email, email.confirmation_token).deliver_later(wait: 1.minute)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/bitbucket_import/importer.rb b/lib/gitlab/bitbucket_import/importer.rb
index 5a9fad3be56..e59494c9d9c 100644
--- a/lib/gitlab/bitbucket_import/importer.rb
+++ b/lib/gitlab/bitbucket_import/importer.rb
@@ -3,8 +3,6 @@
module Gitlab
module BitbucketImport
class Importer
- include Gitlab::BitbucketImport::Metrics
-
LABELS = [{ title: 'bug', color: '#FF0000' },
{ title: 'enhancement', color: '#428BCA' },
{ title: 'proposal', color: '#69D100' },
@@ -26,6 +24,7 @@ module Gitlab
import_issues
import_pull_requests
handle_errors
+ metrics.track_finished_import
true
end
@@ -115,6 +114,8 @@ module Gitlab
updated_at: issue.updated_at
)
+ metrics.issues_counter.increment
+
gitlab_issue.labels << @labels[label_name]
import_issue_comments(issue, gitlab_issue) if gitlab_issue.persisted?
@@ -195,6 +196,8 @@ module Gitlab
updated_at: pull_request.updated_at
)
+ metrics.merge_requests_counter.increment
+
import_pull_request_comments(pull_request, merge_request) if merge_request.persisted?
rescue StandardError => e
store_pull_request_error(pull_request, e)
@@ -288,6 +291,10 @@ module Gitlab
project_path: project.full_path
}
end
+
+ def metrics
+ @metrics ||= Gitlab::Import::Metrics.new(:bitbucket_importer, @project)
+ end
end
end
end
diff --git a/lib/gitlab/bitbucket_import/metrics.rb b/lib/gitlab/bitbucket_import/metrics.rb
deleted file mode 100644
index 25e2d9b211e..00000000000
--- a/lib/gitlab/bitbucket_import/metrics.rb
+++ /dev/null
@@ -1,41 +0,0 @@
-# frozen_string_literal: true
-
-module Gitlab
- module BitbucketImport
- module Metrics
- extend ActiveSupport::Concern
-
- IMPORTER = :bitbucket_importer
-
- included do
- prepend Gitlab::Import::Metrics
-
- Gitlab::Import::Metrics.measure(:execute, metrics: {
- "#{IMPORTER}_imported_projects": {
- type: :counter,
- description: 'The number of imported Bitbucket projects'
- },
- "#{IMPORTER}_total_duration_seconds": {
- type: :histogram,
- labels: { importer: IMPORTER },
- description: 'Total time spent importing Bitbucket projects, in seconds'
- }
- })
-
- Gitlab::Import::Metrics.measure(:import_issue, metrics: {
- "#{IMPORTER}_imported_issues": {
- type: :counter,
- description: 'The number of imported Bitbucket issues'
- }
- })
-
- Gitlab::Import::Metrics.measure(:import_pull_request, metrics: {
- "#{IMPORTER}_imported_pull_requests": {
- type: :counter,
- description: 'The number of imported Bitbucket pull requests'
- }
- })
- end
- end
- end
-end
diff --git a/lib/gitlab/bitbucket_server_import/importer.rb b/lib/gitlab/bitbucket_server_import/importer.rb
index 16fe5b46b1f..18a1b64729e 100644
--- a/lib/gitlab/bitbucket_server_import/importer.rb
+++ b/lib/gitlab/bitbucket_server_import/importer.rb
@@ -43,6 +43,7 @@ module Gitlab
import_pull_requests
delete_temp_branches
handle_errors
+ metrics.track_finished_import
log_info(stage: "complete")
@@ -219,7 +220,11 @@ module Gitlab
creator = Gitlab::Import::MergeRequestCreator.new(project)
merge_request = creator.execute(attributes)
- import_pull_request_comments(pull_request, merge_request) if merge_request.persisted?
+ if merge_request.persisted?
+ import_pull_request_comments(pull_request, merge_request)
+
+ metrics.merge_requests_counter.increment
+ end
log_info(stage: 'import_bitbucket_pull_requests', message: 'finished', iid: pull_request.iid)
end
@@ -388,6 +393,10 @@ module Gitlab
project_path: project.full_path
}
end
+
+ def metrics
+ @metrics ||= Gitlab::Import::Metrics.new(:bitbucket_server_importer, @project)
+ end
end
end
end
diff --git a/lib/gitlab/cache/ci/project_pipeline_status.rb b/lib/gitlab/cache/ci/project_pipeline_status.rb
index e7a7d23ef7e..d981f263c5e 100644
--- a/lib/gitlab/cache/ci/project_pipeline_status.rb
+++ b/lib/gitlab/cache/ci/project_pipeline_status.rb
@@ -49,7 +49,8 @@ module Gitlab
def load_status
return if loaded?
- return unless commit
+
+ return unless Gitlab::Ci::Features.pipeline_status_omit_commit_sha_in_cache_key?(project) || commit
if has_cache?
load_from_cache
@@ -66,6 +67,8 @@ module Gitlab
end
def load_from_project
+ return unless commit
+
self.sha, self.status, self.ref = commit.sha, commit.status, project.default_branch
end
@@ -114,7 +117,11 @@ module Gitlab
end
def cache_key
- "#{Gitlab::Redis::Cache::CACHE_NAMESPACE}:project:#{project.id}:pipeline_status:#{commit&.sha}"
+ if Gitlab::Ci::Features.pipeline_status_omit_commit_sha_in_cache_key?(project)
+ "#{Gitlab::Redis::Cache::CACHE_NAMESPACE}:project:#{project.id}:pipeline_status"
+ else
+ "#{Gitlab::Redis::Cache::CACHE_NAMESPACE}:project:#{project.id}:pipeline_status:#{commit&.sha}"
+ end
end
def commit
diff --git a/lib/gitlab/ci/build/releaser.rb b/lib/gitlab/ci/build/releaser.rb
index ba6c7857e96..facb5f619bd 100644
--- a/lib/gitlab/ci/build/releaser.rb
+++ b/lib/gitlab/ci/build/releaser.rb
@@ -5,6 +5,8 @@ module Gitlab
module Build
class Releaser
BASE_COMMAND = 'release-cli create'
+ SINGLE_FLAGS = %i[name description tag_name ref released_at].freeze
+ ARRAY_FLAGS = %i[milestones].freeze
attr_reader :config
@@ -14,9 +16,20 @@ module Gitlab
def script
command = BASE_COMMAND.dup
- config.each { |k, v| command.concat(" --#{k.to_s.dasherize} \"#{v}\"") }
+ single_flags.each { |k, v| command.concat(" --#{k.to_s.dasherize} \"#{v}\"") }
+ array_commands.each { |k, v| v.each { |elem| command.concat(" --#{k.to_s.singularize.dasherize} \"#{elem}\"") } }
- command
+ [command]
+ end
+
+ private
+
+ def single_flags
+ config.slice(*SINGLE_FLAGS)
+ end
+
+ def array_commands
+ config.slice(*ARRAY_FLAGS)
end
end
end
diff --git a/lib/gitlab/ci/config.rb b/lib/gitlab/ci/config.rb
index 10e0f4b8e4d..d81a3fef1f5 100644
--- a/lib/gitlab/ci/config.rb
+++ b/lib/gitlab/ci/config.rb
@@ -39,6 +39,10 @@ module Gitlab
@root.errors
end
+ def warnings
+ @root.warnings
+ end
+
def to_hash
@config
end
@@ -93,7 +97,7 @@ module Gitlab
Gitlab::ErrorTracking.track_and_raise_for_dev_exception(error, @context.sentry_payload)
end
- # Overriden in EE
+ # Overridden in EE
def rescue_errors
RESCUE_ERRORS
end
diff --git a/lib/gitlab/ci/config/entry/environment.rb b/lib/gitlab/ci/config/entry/environment.rb
index fc62cca58ff..64e6d48133f 100644
--- a/lib/gitlab/ci/config/entry/environment.rb
+++ b/lib/gitlab/ci/config/entry/environment.rb
@@ -44,7 +44,7 @@ module Gitlab
validates :action,
type: String,
- inclusion: { in: %w[start stop], message: 'should be start or stop' },
+ inclusion: { in: %w[start stop prepare], message: 'should be start, stop or prepare' },
allow_nil: true
validates :on_stop, type: String, allow_nil: true
diff --git a/lib/gitlab/ci/config/entry/job.rb b/lib/gitlab/ci/config/entry/job.rb
index 66050a7bbe0..a615cab1a80 100644
--- a/lib/gitlab/ci/config/entry/job.rb
+++ b/lib/gitlab/ci/config/entry/job.rb
@@ -15,7 +15,7 @@ module Gitlab
allow_failure type when start_in artifacts cache
dependencies before_script needs after_script
environment coverage retry parallel interruptible timeout
- resource_group release].freeze
+ resource_group release secrets].freeze
REQUIRED_BY_NEEDS = %i[stage].freeze
@@ -191,3 +191,5 @@ module Gitlab
end
end
end
+
+::Gitlab::Ci::Config::Entry::Job.prepend_if_ee('::EE::Gitlab::Ci::Config::Entry::Job')
diff --git a/lib/gitlab/ci/config/entry/processable.rb b/lib/gitlab/ci/config/entry/processable.rb
index 81211acbec7..b4539475d88 100644
--- a/lib/gitlab/ci/config/entry/processable.rb
+++ b/lib/gitlab/ci/config/entry/processable.rb
@@ -82,6 +82,10 @@ module Gitlab
@entries.delete(:except) unless except_defined? # rubocop:disable Gitlab/ModuleWithInstanceVariables
end
+ if has_rules? && !has_workflow_rules && Gitlab::Ci::Features.raise_job_rules_without_workflow_rules_warning?
+ add_warning('uses `rules` without defining `workflow:rules`')
+ end
+
# inherit root variables
@root_variables_value = deps&.variables_value # rubocop:disable Gitlab/ModuleWithInstanceVariables
diff --git a/lib/gitlab/ci/config/entry/release.rb b/lib/gitlab/ci/config/entry/release.rb
index b4e4c149730..7e504c24ade 100644
--- a/lib/gitlab/ci/config/entry/release.rb
+++ b/lib/gitlab/ci/config/entry/release.rb
@@ -12,8 +12,9 @@ module Gitlab
include ::Gitlab::Config::Entry::Validatable
include ::Gitlab::Config::Entry::Attributable
- ALLOWED_KEYS = %i[tag_name name description assets].freeze
- attributes %i[tag_name name assets].freeze
+ ALLOWED_KEYS = %i[tag_name name description ref released_at milestones assets].freeze
+ attributes %i[tag_name name ref milestones assets].freeze
+ attr_reader :released_at
# Attributable description conflicts with
# ::Gitlab::Config::Entry::Node.description
@@ -29,8 +30,25 @@ module Gitlab
validations do
validates :config, allowed_keys: ALLOWED_KEYS
- validates :tag_name, presence: true
+ validates :tag_name, type: String, presence: true
validates :description, type: String, presence: true
+ validates :milestones, array_of_strings_or_string: true, allow_blank: true
+ validate do
+ next unless config[:released_at]
+
+ begin
+ @released_at = DateTime.iso8601(config[:released_at])
+ rescue ArgumentError
+ errors.add(:released_at, "must be a valid datetime")
+ end
+ end
+ validate do
+ next unless config[:ref]
+ next if Commit.reference_valid?(config[:ref])
+ next if Gitlab::GitRefValidator.validate(config[:ref])
+
+ errors.add(:ref, "must be a valid ref")
+ end
end
def value
diff --git a/lib/gitlab/ci/config/entry/reports.rb b/lib/gitlab/ci/config/entry/reports.rb
index 74736b24d73..0ae65f43723 100644
--- a/lib/gitlab/ci/config/entry/reports.rb
+++ b/lib/gitlab/ci/config/entry/reports.rb
@@ -13,9 +13,9 @@ module Gitlab
ALLOWED_KEYS =
%i[junit codequality sast secret_detection dependency_scanning container_scanning
- dast performance license_management license_scanning metrics lsif
+ dast performance browser_performance load_performance license_management license_scanning metrics lsif
dotenv cobertura terraform accessibility cluster_applications
- requirements].freeze
+ requirements coverage_fuzzing].freeze
attributes ALLOWED_KEYS
@@ -25,13 +25,16 @@ module Gitlab
with_options allow_nil: true do
validates :junit, array_of_strings_or_string: true
- validates :codequality, array_of_strings_or_string: true
+ validates :coverage_fuzzing, array_of_strings_or_string: true
+ validates :sast, array_of_strings_or_string: true
validates :sast, array_of_strings_or_string: true
validates :secret_detection, array_of_strings_or_string: true
validates :dependency_scanning, array_of_strings_or_string: true
validates :container_scanning, array_of_strings_or_string: true
validates :dast, array_of_strings_or_string: true
validates :performance, array_of_strings_or_string: true
+ validates :browser_performance, array_of_strings_or_string: true
+ validates :load_performance, array_of_strings_or_string: true
validates :license_management, array_of_strings_or_string: true
validates :license_scanning, array_of_strings_or_string: true
validates :metrics, array_of_strings_or_string: true
diff --git a/lib/gitlab/ci/features.rb b/lib/gitlab/ci/features.rb
index a2eb31369c7..6130baeb9d5 100644
--- a/lib/gitlab/ci/features.rb
+++ b/lib/gitlab/ci/features.rb
@@ -10,20 +10,12 @@ module Gitlab
::Feature.enabled?(:ci_artifacts_exclude, default_enabled: true)
end
- def self.ensure_scheduling_type_enabled?
- ::Feature.enabled?(:ci_ensure_scheduling_type, default_enabled: true)
- end
-
def self.job_heartbeats_runner?(project)
::Feature.enabled?(:ci_job_heartbeats_runner, project, default_enabled: true)
end
- def self.instance_level_variables_limit_enabled?
- ::Feature.enabled?(:ci_instance_level_variables_limit, default_enabled: true)
- end
-
def self.pipeline_fixed_notifications?
- ::Feature.enabled?(:ci_pipeline_fixed_notifications)
+ ::Feature.enabled?(:ci_pipeline_fixed_notifications, default_enabled: true)
end
def self.instance_variables_ui_enabled?
@@ -38,9 +30,51 @@ module Gitlab
::Feature.enabled?(:ci_atomic_processing, project, default_enabled: true)
end
+ def self.pipeline_latest?
+ ::Feature.enabled?(:ci_pipeline_latest, default_enabled: true)
+ end
+
+ def self.pipeline_status_omit_commit_sha_in_cache_key?(project)
+ Feature.enabled?(:ci_pipeline_status_omit_commit_sha_in_cache_key, project)
+ end
+
def self.release_generation_enabled?
- ::Feature.enabled?(:ci_release_generation)
+ ::Feature.enabled?(:ci_release_generation, default_enabled: true)
+ end
+
+ # Remove in https://gitlab.com/gitlab-org/gitlab/-/issues/224199
+ def self.store_pipeline_messages?(project)
+ ::Feature.enabled?(:ci_store_pipeline_messages, project, default_enabled: true)
+ end
+
+ # Remove in https://gitlab.com/gitlab-org/gitlab/-/issues/227052
+ def self.variables_api_filter_environment_scope?
+ ::Feature.enabled?(:ci_variables_api_filter_environment_scope, default_enabled: false)
+ end
+
+ # This FF is only used for development purpose to test that warnings can be
+ # raised and propagated to the UI.
+ def self.raise_job_rules_without_workflow_rules_warning?
+ ::Feature.enabled?(:ci_raise_job_rules_without_workflow_rules_warning)
+ end
+
+ def self.keep_latest_artifacts_for_ref_enabled?(project)
+ ::Feature.enabled?(:keep_latest_artifacts_for_ref, project, default_enabled: false)
+ end
+
+ def self.destroy_only_unlocked_expired_artifacts_enabled?
+ ::Feature.enabled?(:destroy_only_unlocked_expired_artifacts, default_enabled: false)
+ end
+
+ def self.bulk_insert_on_create?(project)
+ ::Feature.enabled?(:ci_bulk_insert_on_create, project, default_enabled: true)
+ end
+
+ def self.allow_to_create_merge_request_pipelines_in_target_project?(target_project)
+ ::Feature.enabled?(:ci_allow_to_create_merge_request_pipelines_in_target_project, target_project)
end
end
end
end
+
+::Gitlab::Ci::Features.prepend_if_ee('::EE::Gitlab::Ci::Features')
diff --git a/lib/gitlab/ci/parsers/terraform/tfplan.rb b/lib/gitlab/ci/parsers/terraform/tfplan.rb
index 19f724b79af..abfbe18e23f 100644
--- a/lib/gitlab/ci/parsers/terraform/tfplan.rb
+++ b/lib/gitlab/ci/parsers/terraform/tfplan.rb
@@ -8,15 +8,21 @@ module Gitlab
TfplanParserError = Class.new(Gitlab::Ci::Parsers::ParserError)
def parse!(json_data, terraform_reports, artifact:)
+ job_details = job_details(artifact.job)
+ job_id = job_details['job_id']
plan_data = Gitlab::Json.parse(json_data)
- raise TfplanParserError, 'Tfplan missing required key' unless has_required_keys?(plan_data)
-
- terraform_reports.add_plan(artifact.job.id.to_s, tfplan(plan_data, artifact.job))
+ if has_required_keys?(plan_data)
+ terraform_reports.add_plan(job_id, valid_tfplan(plan_data, job_details))
+ else
+ terraform_reports.add_plan(job_id, invalid_tfplan(:missing_json_keys, job_details))
+ end
rescue JSON::ParserError
- raise TfplanParserError, 'JSON parsing failed'
+ terraform_reports.add_plan(job_id, invalid_tfplan(:invalid_json_format, job_details))
rescue
- raise TfplanParserError, 'Tfplan parsing failed'
+ details = job_details || {}
+ plan_name = job_id || 'failed_tf_plan'
+ terraform_reports.add_plan(plan_name, invalid_tfplan(:unknown_error, details))
end
private
@@ -25,14 +31,24 @@ module Gitlab
(%w[create update delete] - plan_data.keys).empty?
end
- def tfplan(plan_data, artifact_job)
+ def job_details(job)
{
+ 'job_id' => job.id.to_s,
+ 'job_name' => job.options.dig(:artifacts, :name).to_s,
+ 'job_path' => Gitlab::Routing.url_helpers.project_job_path(job.project, job)
+ }
+ end
+
+ def invalid_tfplan(error_type, job_details)
+ job_details.merge('tf_report_error' => error_type)
+ end
+
+ def valid_tfplan(plan_data, job_details)
+ job_details.merge(
'create' => plan_data['create'].to_i,
'delete' => plan_data['delete'].to_i,
- 'job_name' => artifact_job.options.dig(:artifacts, :name).to_s,
- 'job_path' => Gitlab::Routing.url_helpers.project_job_path(artifact_job.project, artifact_job),
'update' => plan_data['update'].to_i
- }
+ )
end
end
end
diff --git a/lib/gitlab/ci/pipeline/chain/build.rb b/lib/gitlab/ci/pipeline/chain/build.rb
index 9662209f88e..4190c40eb66 100644
--- a/lib/gitlab/ci/pipeline/chain/build.rb
+++ b/lib/gitlab/ci/pipeline/chain/build.rb
@@ -20,7 +20,11 @@ module Gitlab
pipeline_schedule: @command.schedule,
merge_request: @command.merge_request,
external_pull_request: @command.external_pull_request,
- variables_attributes: Array(@command.variables_attributes)
+ variables_attributes: Array(@command.variables_attributes),
+ # This should be removed and set on the database column default
+ # level when the keep_latest_artifacts_for_ref feature flag is
+ # removed.
+ locked: ::Gitlab::Ci::Features.keep_latest_artifacts_for_ref_enabled?(@command.project) ? :artifacts_locked : :unlocked
)
end
diff --git a/lib/gitlab/ci/pipeline/chain/command.rb b/lib/gitlab/ci/pipeline/chain/command.rb
index 8118e7b2487..74b28b181bc 100644
--- a/lib/gitlab/ci/pipeline/chain/command.rb
+++ b/lib/gitlab/ci/pipeline/chain/command.rb
@@ -10,7 +10,7 @@ module Gitlab
:trigger_request, :schedule, :merge_request, :external_pull_request,
:ignore_skip_ci, :save_incompleted,
:seeds_block, :variables_attributes, :push_options,
- :chat_data, :allow_mirror_update, :bridge,
+ :chat_data, :allow_mirror_update, :bridge, :content,
# These attributes are set by Chains during processing:
:config_content, :config_processor, :stage_seeds
) do
@@ -78,7 +78,7 @@ module Gitlab
end
def metrics
- @metrics ||= Chain::Metrics.new
+ @metrics ||= ::Gitlab::Ci::Pipeline::Metrics.new
end
def observe_creation_duration(duration)
@@ -90,6 +90,10 @@ module Gitlab
metrics.pipeline_size_histogram
.observe({ source: pipeline.source.to_s }, pipeline.total_size)
end
+
+ def dangling_build?
+ %i[ondemand_dast_scan webide].include?(source)
+ end
end
end
end
diff --git a/lib/gitlab/ci/pipeline/chain/config/content.rb b/lib/gitlab/ci/pipeline/chain/config/content.rb
index 2008010b523..5314fd471c3 100644
--- a/lib/gitlab/ci/pipeline/chain/config/content.rb
+++ b/lib/gitlab/ci/pipeline/chain/config/content.rb
@@ -9,6 +9,7 @@ module Gitlab
include Chain::Helpers
SOURCES = [
+ Gitlab::Ci::Pipeline::Chain::Config::Content::Parameter,
Gitlab::Ci::Pipeline::Chain::Config::Content::Bridge,
Gitlab::Ci::Pipeline::Chain::Config::Content::Repository,
Gitlab::Ci::Pipeline::Chain::Config::Content::ExternalProject,
diff --git a/lib/gitlab/ci/pipeline/chain/config/content/parameter.rb b/lib/gitlab/ci/pipeline/chain/config/content/parameter.rb
new file mode 100644
index 00000000000..3dd216b33d1
--- /dev/null
+++ b/lib/gitlab/ci/pipeline/chain/config/content/parameter.rb
@@ -0,0 +1,30 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Ci
+ module Pipeline
+ module Chain
+ module Config
+ class Content
+ class Parameter < Source
+ UnsupportedSourceError = Class.new(StandardError)
+
+ def content
+ strong_memoize(:content) do
+ next unless command.content.present?
+ raise UnsupportedSourceError, "#{command.source} not a dangling build" unless command.dangling_build?
+
+ command.content
+ end
+ end
+
+ def source
+ :parameter_source
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/pipeline/chain/config/content/source.rb b/lib/gitlab/ci/pipeline/chain/config/content/source.rb
index 3389187473b..8bc172f93d3 100644
--- a/lib/gitlab/ci/pipeline/chain/config/content/source.rb
+++ b/lib/gitlab/ci/pipeline/chain/config/content/source.rb
@@ -11,6 +11,8 @@ module Gitlab
DEFAULT_YAML_FILE = '.gitlab-ci.yml'
+ attr_reader :command
+
def initialize(pipeline, command)
@pipeline = pipeline
@command = command
diff --git a/lib/gitlab/ci/pipeline/chain/config/process.rb b/lib/gitlab/ci/pipeline/chain/config/process.rb
index 1e47be21b93..2cfcb295407 100644
--- a/lib/gitlab/ci/pipeline/chain/config/process.rb
+++ b/lib/gitlab/ci/pipeline/chain/config/process.rb
@@ -19,7 +19,11 @@ module Gitlab
parent_pipeline: parent_pipeline
}
)
+
+ add_warnings_to_pipeline(@command.config_processor.warnings)
rescue Gitlab::Ci::YamlProcessor::ValidationError => ex
+ add_warnings_to_pipeline(ex.warnings)
+
error(ex.message, config_error: true)
rescue => ex
Gitlab::ErrorTracking.track_exception(ex,
@@ -34,6 +38,14 @@ module Gitlab
def break?
@pipeline.errors.any? || @pipeline.persisted?
end
+
+ private
+
+ def add_warnings_to_pipeline(warnings)
+ return unless warnings.present?
+
+ warnings.each { |message| warning(message) }
+ end
end
end
end
diff --git a/lib/gitlab/ci/pipeline/chain/create.rb b/lib/gitlab/ci/pipeline/chain/create.rb
index aa627bdb009..34649fe16f3 100644
--- a/lib/gitlab/ci/pipeline/chain/create.rb
+++ b/lib/gitlab/ci/pipeline/chain/create.rb
@@ -8,7 +8,9 @@ module Gitlab
include Chain::Helpers
def perform!
- pipeline.save!
+ BulkInsertableAssociations.with_bulk_insert(enabled: ::Gitlab::Ci::Features.bulk_insert_on_create?(project)) do
+ pipeline.save!
+ end
rescue ActiveRecord::RecordInvalid => e
error("Failed to persist the pipeline: #{e}")
end
diff --git a/lib/gitlab/ci/pipeline/chain/helpers.rb b/lib/gitlab/ci/pipeline/chain/helpers.rb
index 982ecc0ff51..aba7dab508d 100644
--- a/lib/gitlab/ci/pipeline/chain/helpers.rb
+++ b/lib/gitlab/ci/pipeline/chain/helpers.rb
@@ -11,9 +11,18 @@ module Gitlab
pipeline.yaml_errors = message
end
+ pipeline.add_error_message(message)
pipeline.drop!(drop_reason) if drop_reason
+
+ # TODO: consider not to rely on AR errors directly as they can be
+ # polluted with other unrelated errors (e.g. state machine)
+ # https://gitlab.com/gitlab-org/gitlab/-/issues/220823
pipeline.errors.add(:base, message)
end
+
+ def warning(message)
+ pipeline.add_warning_message(message)
+ end
end
end
end
diff --git a/lib/gitlab/ci/pipeline/chain/metrics.rb b/lib/gitlab/ci/pipeline/chain/metrics.rb
deleted file mode 100644
index 980ab2de9b0..00000000000
--- a/lib/gitlab/ci/pipeline/chain/metrics.rb
+++ /dev/null
@@ -1,35 +0,0 @@
-# frozen_string_literal: true
-
-module Gitlab
- module Ci
- module Pipeline
- module Chain
- class Metrics
- include Gitlab::Utils::StrongMemoize
-
- def pipeline_creation_duration_histogram
- strong_memoize(:pipeline_creation_duration_histogram) do
- name = :gitlab_ci_pipeline_creation_duration_seconds
- comment = 'Pipeline creation duration'
- labels = {}
- buckets = [0.01, 0.05, 0.1, 0.5, 1.0, 2.0, 5.0, 20.0, 50.0, 240.0]
-
- ::Gitlab::Metrics.histogram(name, comment, labels, buckets)
- end
- end
-
- def pipeline_size_histogram
- strong_memoize(:pipeline_size_histogram) do
- name = :gitlab_ci_pipeline_size_builds
- comment = 'Pipeline size'
- labels = { source: nil }
- buckets = [0, 1, 5, 10, 20, 50, 100, 200, 500, 1000]
-
- ::Gitlab::Metrics.histogram(name, comment, labels, buckets)
- end
- end
- end
- end
- end
- end
-end
diff --git a/lib/gitlab/ci/pipeline/chain/validate/abilities.rb b/lib/gitlab/ci/pipeline/chain/validate/abilities.rb
index a30b6c6ef0e..769d0dffd0b 100644
--- a/lib/gitlab/ci/pipeline/chain/validate/abilities.rb
+++ b/lib/gitlab/ci/pipeline/chain/validate/abilities.rb
@@ -19,7 +19,7 @@ module Gitlab
end
unless allowed_to_write_ref?
- return error("Insufficient permissions for protected ref '#{command.ref}'")
+ error("Insufficient permissions for protected ref '#{command.ref}'")
end
end
diff --git a/lib/gitlab/ci/pipeline/chain/validate/repository.rb b/lib/gitlab/ci/pipeline/chain/validate/repository.rb
index 8f5445850d7..7977ce90443 100644
--- a/lib/gitlab/ci/pipeline/chain/validate/repository.rb
+++ b/lib/gitlab/ci/pipeline/chain/validate/repository.rb
@@ -18,7 +18,7 @@ module Gitlab
end
if @command.ambiguous_ref?
- return error('Ref is ambiguous')
+ error('Ref is ambiguous')
end
end
diff --git a/lib/gitlab/ci/pipeline/metrics.rb b/lib/gitlab/ci/pipeline/metrics.rb
new file mode 100644
index 00000000000..649da745eea
--- /dev/null
+++ b/lib/gitlab/ci/pipeline/metrics.rb
@@ -0,0 +1,42 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Ci
+ module Pipeline
+ class Metrics
+ include Gitlab::Utils::StrongMemoize
+
+ def pipeline_creation_duration_histogram
+ strong_memoize(:pipeline_creation_duration_histogram) do
+ name = :gitlab_ci_pipeline_creation_duration_seconds
+ comment = 'Pipeline creation duration'
+ labels = {}
+ buckets = [0.01, 0.05, 0.1, 0.5, 1.0, 2.0, 5.0, 20.0, 50.0, 240.0]
+
+ ::Gitlab::Metrics.histogram(name, comment, labels, buckets)
+ end
+ end
+
+ def pipeline_size_histogram
+ strong_memoize(:pipeline_size_histogram) do
+ name = :gitlab_ci_pipeline_size_builds
+ comment = 'Pipeline size'
+ labels = { source: nil }
+ buckets = [0, 1, 5, 10, 20, 50, 100, 200, 500, 1000]
+
+ ::Gitlab::Metrics.histogram(name, comment, labels, buckets)
+ end
+ end
+
+ def pipeline_processing_events_counter
+ strong_memoize(:pipeline_processing_events_counter) do
+ name = :gitlab_ci_pipeline_processing_events_total
+ comment = 'Total amount of pipeline processing events'
+
+ Gitlab::Metrics.counter(name, comment)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/pipeline/preloader.rb b/lib/gitlab/ci/pipeline/preloader.rb
index db0a1ea4dab..7befc126ca9 100644
--- a/lib/gitlab/ci/pipeline/preloader.rb
+++ b/lib/gitlab/ci/pipeline/preloader.rb
@@ -17,6 +17,7 @@ module Gitlab
pipelines.each do |pipeline|
self.new(pipeline).tap do |preloader|
preloader.preload_commit_authors
+ preloader.preload_ref_commits
preloader.preload_pipeline_warnings
preloader.preload_stages_warnings
end
@@ -27,12 +28,19 @@ module Gitlab
@pipeline = pipeline
end
+ # This also preloads the author of every commit. We're using "lazy_author"
+ # here since "author" immediately loads the data on the first call.
def preload_commit_authors
- # This also preloads the author of every commit. We're using "lazy_author"
- # here since "author" immediately loads the data on the first call.
@pipeline.commit.try(:lazy_author)
end
+ # This preloads latest commits for given refs and therefore makes it
+ # much less expensive to check if a pipeline is a latest one for
+ # given branch.
+ def preload_ref_commits
+ @pipeline.lazy_ref_commit
+ end
+
def preload_pipeline_warnings
# This preloads the number of warnings for every pipeline, ensuring
# that Ci::Pipeline#has_warnings? doesn't execute any additional
@@ -40,10 +48,10 @@ module Gitlab
@pipeline.number_of_warnings
end
+ # This preloads the number of warnings for every stage, ensuring
+ # that Ci::Stage#has_warnings? doesn't execute any additional
+ # queries.
def preload_stages_warnings
- # This preloads the number of warnings for every stage, ensuring
- # that Ci::Stage#has_warnings? doesn't execute any additional
- # queries.
@pipeline.stages.each { |stage| stage.number_of_warnings }
end
end
diff --git a/lib/gitlab/ci/reports/test_report_summary.rb b/lib/gitlab/ci/reports/test_report_summary.rb
new file mode 100644
index 00000000000..85b83b790e7
--- /dev/null
+++ b/lib/gitlab/ci/reports/test_report_summary.rb
@@ -0,0 +1,49 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Ci
+ module Reports
+ class TestReportSummary
+ attr_reader :all_results
+
+ def initialize(all_results)
+ @all_results = all_results
+ end
+
+ def total
+ TestSuiteSummary.new(all_results)
+ end
+
+ def total_time
+ total.total_time
+ end
+
+ def total_count
+ total.total_count
+ end
+
+ def success_count
+ total.success_count
+ end
+
+ def failed_count
+ total.failed_count
+ end
+
+ def skipped_count
+ total.skipped_count
+ end
+
+ def error_count
+ total.error_count
+ end
+
+ def test_suites
+ all_results
+ .group_by(&:tests_name)
+ .transform_values { |results| TestSuiteSummary.new(results) }
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/reports/test_suite.rb b/lib/gitlab/ci/reports/test_suite.rb
index 8bbf2e0f6cf..28b81e7a471 100644
--- a/lib/gitlab/ci/reports/test_suite.rb
+++ b/lib/gitlab/ci/reports/test_suite.rb
@@ -4,9 +4,9 @@ module Gitlab
module Ci
module Reports
class TestSuite
- attr_reader :name
- attr_reader :test_cases
- attr_reader :total_time
+ attr_accessor :name
+ attr_accessor :test_cases
+ attr_accessor :total_time
attr_reader :suite_error
def initialize(name = nil)
@@ -70,6 +70,14 @@ module Gitlab
@suite_error = msg
end
+ def +(other)
+ self.class.new.tap do |test_suite|
+ test_suite.name = self.name
+ test_suite.test_cases = self.test_cases.deep_merge(other.test_cases)
+ test_suite.total_time = self.total_time + other.total_time
+ end
+ end
+
private
def existing_key?(test_case)
diff --git a/lib/gitlab/ci/reports/test_suite_summary.rb b/lib/gitlab/ci/reports/test_suite_summary.rb
new file mode 100644
index 00000000000..f9b0bedb712
--- /dev/null
+++ b/lib/gitlab/ci/reports/test_suite_summary.rb
@@ -0,0 +1,49 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Ci
+ module Reports
+ class TestSuiteSummary
+ attr_reader :results
+
+ def initialize(results)
+ @results = results
+ end
+
+ def name
+ @name ||= results.first.tests_name
+ end
+
+ # rubocop: disable CodeReuse/ActiveRecord
+ def build_ids
+ results.pluck(:build_id)
+ end
+
+ def total_time
+ @total_time ||= results.sum(&:tests_duration)
+ end
+
+ def success_count
+ @success_count ||= results.sum(&:tests_success)
+ end
+
+ def failed_count
+ @failed_count ||= results.sum(&:tests_failed)
+ end
+
+ def skipped_count
+ @skipped_count ||= results.sum(&:tests_skipped)
+ end
+
+ def error_count
+ @error_count ||= results.sum(&:tests_errored)
+ end
+
+ def total_count
+ @total_count ||= [success_count, failed_count, skipped_count, error_count].sum
+ end
+ # rubocop: disable CodeReuse/ActiveRecord
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/status/composite.rb b/lib/gitlab/ci/status/composite.rb
index 074651f1040..04a9fc29802 100644
--- a/lib/gitlab/ci/status/composite.rb
+++ b/lib/gitlab/ci/status/composite.rb
@@ -112,13 +112,13 @@ module Gitlab
def success_with_warnings?(status)
@allow_failure_key &&
status[@allow_failure_key] &&
- HasStatus::PASSED_WITH_WARNINGS_STATUSES.include?(status[@status_key])
+ ::Ci::HasStatus::PASSED_WITH_WARNINGS_STATUSES.include?(status[@status_key])
end
def ignored_status?(status)
@allow_failure_key &&
status[@allow_failure_key] &&
- HasStatus::EXCLUDE_IGNORED_STATUSES.include?(status[@status_key])
+ ::Ci::HasStatus::EXCLUDE_IGNORED_STATUSES.include?(status[@status_key])
end
end
end
diff --git a/lib/gitlab/ci/status/factory.rb b/lib/gitlab/ci/status/factory.rb
index 73c73a3b3fc..4a384531c57 100644
--- a/lib/gitlab/ci/status/factory.rb
+++ b/lib/gitlab/ci/status/factory.rb
@@ -7,7 +7,7 @@ module Gitlab
def initialize(subject, user)
@subject = subject
@user = user
- @status = subject.status || HasStatus::DEFAULT_STATUS
+ @status = subject.status || ::Ci::HasStatus::DEFAULT_STATUS
end
def fabricate!
diff --git a/lib/gitlab/ci/status/stage/play_manual.rb b/lib/gitlab/ci/status/stage/play_manual.rb
index ac3fc0912fa..58859a8f191 100644
--- a/lib/gitlab/ci/status/stage/play_manual.rb
+++ b/lib/gitlab/ci/status/stage/play_manual.rb
@@ -18,7 +18,7 @@ module Gitlab
def action_path
pipeline = subject.pipeline
- project_stage_play_manual_path(pipeline.project, pipeline, subject.name)
+ project_pipeline_stage_play_manual_path(pipeline.project, pipeline, subject.name)
end
def action_method
diff --git a/lib/gitlab/ci/templates/Android-Fastlane.gitlab-ci.yml b/lib/gitlab/ci/templates/Android-Fastlane.gitlab-ci.yml
index be584814271..5ebbbf15682 100644
--- a/lib/gitlab/ci/templates/Android-Fastlane.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Android-Fastlane.gitlab-ci.yml
@@ -20,7 +20,7 @@ stages:
- docker:dind
script:
- docker login -u gitlab-ci-token -p $CI_JOB_TOKEN $CI_REGISTRY
- - docker pull $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG || true
+ - docker pull --quiet $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG || true
- docker build --cache-from $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG -t $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG .
- docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG
diff --git a/lib/gitlab/ci/templates/Android.gitlab-ci.yml b/lib/gitlab/ci/templates/Android.gitlab-ci.yml
index b7194110002..d20dabc0b00 100644
--- a/lib/gitlab/ci/templates/Android.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Android.gitlab-ci.yml
@@ -4,32 +4,65 @@
image: openjdk:8-jdk
variables:
- ANDROID_COMPILE_SDK: "28"
- ANDROID_BUILD_TOOLS: "28.0.2"
- ANDROID_SDK_TOOLS: "4333796"
+ # ANDROID_COMPILE_SDK is the version of Android you're compiling with.
+ # It should match compileSdkVersion.
+ ANDROID_COMPILE_SDK: "29"
+
+ # ANDROID_BUILD_TOOLS is the version of the Android build tools you are using.
+ # It should match buildToolsVersion.
+ ANDROID_BUILD_TOOLS: "29.0.3"
+
+ # It's what version of the command line tools we're going to download from the official site.
+ # Official Site-> https://developer.android.com/studio/index.html
+ # There, look down below at the cli tools only, sdk tools package is of format:
+ # commandlinetools-os_type-ANDROID_SDK_TOOLS_latest.zip
+ # when the script was last modified for latest compileSdkVersion, it was which is written down below
+ ANDROID_SDK_TOOLS: "6514223"
+
+# Packages installation before running script
before_script:
- apt-get --quiet update --yes
- apt-get --quiet install --yes wget tar unzip lib32stdc++6 lib32z1
- - wget --quiet --output-document=android-sdk.zip https://dl.google.com/android/repository/sdk-tools-linux-${ANDROID_SDK_TOOLS}.zip
- - unzip -d android-sdk-linux android-sdk.zip
- - echo y | android-sdk-linux/tools/bin/sdkmanager "platforms;android-${ANDROID_COMPILE_SDK}" >/dev/null
- - echo y | android-sdk-linux/tools/bin/sdkmanager "platform-tools" >/dev/null
- - echo y | android-sdk-linux/tools/bin/sdkmanager "build-tools;${ANDROID_BUILD_TOOLS}" >/dev/null
- - export ANDROID_HOME=$PWD/android-sdk-linux
- - export PATH=$PATH:$PWD/android-sdk-linux/platform-tools/
+
+ # Setup path as android_home for moving/exporting the downloaded sdk into it
+ - export ANDROID_HOME="${PWD}/android-home"
+ # Create a new directory at specified location
+ - install -d $ANDROID_HOME
+ # Here we are installing androidSDK tools from official source,
+ # (the key thing here is the url from where you are downloading these sdk tool for command line, so please do note this url pattern there and here as well)
+ # after that unzipping those tools and
+ # then running a series of SDK manager commands to install necessary android SDK packages that'll allow the app to build
+ - wget --output-document=$ANDROID_HOME/cmdline-tools.zip https://dl.google.com/android/repository/commandlinetools-linux-${ANDROID_SDK_TOOLS}_latest.zip
+ # move to the archive at ANDROID_HOME
+ - pushd $ANDROID_HOME
+ - unzip -d cmdline-tools cmdline-tools.zip
+ - popd
+ - export PATH=$PATH:${ANDROID_HOME}/cmdline-tools/tools/bin/
+
+ # Nothing fancy here, just checking sdkManager version
+ - sdkmanager --version
+
+ # use yes to accept all licenses
+ - yes | sdkmanager --sdk_root=${ANDROID_HOME} --licenses || true
+ - sdkmanager --sdk_root=${ANDROID_HOME} "platforms;android-${ANDROID_COMPILE_SDK}"
+ - sdkmanager --sdk_root=${ANDROID_HOME} "platform-tools"
+ - sdkmanager --sdk_root=${ANDROID_HOME} "build-tools;${ANDROID_BUILD_TOOLS}"
+
+ # Not necessary, but just for surity
- chmod +x ./gradlew
- # temporarily disable checking for EPIPE error and use yes to accept all licenses
- - set +o pipefail
- - yes | android-sdk-linux/tools/bin/sdkmanager --licenses
- - set -o pipefail
+# Basic android and gradle stuff
+# Check linting
lintDebug:
+ interruptible: true
stage: build
script:
- ./gradlew -Pci --console=plain :app:lintDebug -PbuildDir=lint
+# Make Project
assembleDebug:
+ interruptible: true
stage: build
script:
- ./gradlew assembleDebug
@@ -37,7 +70,9 @@ assembleDebug:
paths:
- app/build/outputs/
+# Run all tests, if any fails, interrupt the pipeline(fail it)
debugTests:
+ interruptible: true
stage: test
script:
- ./gradlew -Pci --console=plain :app:testDebug
diff --git a/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml b/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml
index e37cd14d1d1..c10d87a537b 100644
--- a/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml
@@ -12,6 +12,7 @@
# * code_quality: CODE_QUALITY_DISABLED
# * license_management: LICENSE_MANAGEMENT_DISABLED
# * performance: PERFORMANCE_DISABLED
+# * load_performance: LOAD_PERFORMANCE_DISABLED
# * sast: SAST_DISABLED
# * secret_detection: SECRET_DETECTION_DISABLED
# * dependency_scanning: DEPENDENCY_SCANNING_DISABLED
@@ -74,7 +75,7 @@ stages:
workflow:
rules:
- - if: '$BUILDPACK_URL || $AUTO_DEVOPS_EXPLICITLY_ENABLED == "1"'
+ - if: '$BUILDPACK_URL || $AUTO_DEVOPS_EXPLICITLY_ENABLED == "1" || $DOCKERFILE_PATH'
- exists:
- Dockerfile
diff --git a/lib/gitlab/ci/templates/Composer.gitlab-ci.yml b/lib/gitlab/ci/templates/Composer.gitlab-ci.yml
new file mode 100644
index 00000000000..5d9c68d3031
--- /dev/null
+++ b/lib/gitlab/ci/templates/Composer.gitlab-ci.yml
@@ -0,0 +1,19 @@
+# Publishes a tag/branch to Composer Packages of the current project
+publish:
+ image: curlimages/curl:latest
+ stage: build
+ variables:
+ URL: "$CI_SERVER_PROTOCOL://$CI_SERVER_HOST:$CI_SERVER_PORT/api/v4/projects/$CI_PROJECT_ID/packages/composer?job_token=$CI_JOB_TOKEN"
+ script:
+ - version=$([[ -z "$CI_COMMIT_TAG" ]] && echo "branch=$CI_COMMIT_REF_NAME" || echo "tag=$CI_COMMIT_TAG")
+ - insecure=$([ "$CI_SERVER_PROTOCOL" = "http" ] && echo "--insecure" || echo "")
+ - response=$(curl -s -w "\n%{http_code}" $insecure --data $version $URL)
+ - code=$(echo "$response" | tail -n 1)
+ - body=$(echo "$response" | head -n 1)
+ # Output state information
+ - if [ $code -eq 201 ]; then
+ echo "Package created - Code $code - $body";
+ else
+ echo "Could not create package - Code $code - $body";
+ exit 1;
+ fi
diff --git a/lib/gitlab/ci/templates/Dart.gitlab-ci.yml b/lib/gitlab/ci/templates/Dart.gitlab-ci.yml
new file mode 100644
index 00000000000..cc383f89b0c
--- /dev/null
+++ b/lib/gitlab/ci/templates/Dart.gitlab-ci.yml
@@ -0,0 +1,22 @@
+# https://hub.docker.com/r/google/dart
+image: google/dart:2.8.4
+
+variables:
+ # Use to learn more:
+ # pub run test --help
+ PUB_VARS: "--platform vm --timeout 30s --concurrency=6 --test-randomize-ordering-seed=random --reporter=expanded"
+
+# Cache downloaded dependencies and plugins between builds.
+# To keep cache across branches add 'key: "$CI_JOB_NAME"'
+cache:
+ paths:
+ - .pub-cache/global_packages
+
+before_script:
+ - export PATH="$PATH":"~/.pub-cache/bin"
+ - pub get --no-precompile
+
+test:
+ stage: test
+ script:
+ - pub run test $PUB_VARS
diff --git a/lib/gitlab/ci/templates/Jobs/Browser-Performance-Testing.gitlab-ci.yml b/lib/gitlab/ci/templates/Jobs/Browser-Performance-Testing.gitlab-ci.yml
index 9a34f8cb113..8553a940bd7 100644
--- a/lib/gitlab/ci/templates/Jobs/Browser-Performance-Testing.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Jobs/Browser-Performance-Testing.gitlab-ci.yml
@@ -1,11 +1,16 @@
+# Read more about the feature here: https://docs.gitlab.com/ee/user/project/merge_requests/browser_performance_testing.html
+
performance:
stage: performance
- image: docker:19.03.11
+ image: docker:19.03.12
allow_failure: true
variables:
DOCKER_TLS_CERTDIR: ""
+ SITESPEED_IMAGE: sitespeedio/sitespeed.io
+ SITESPEED_VERSION: 13.3.0
+ SITESPEED_OPTIONS: ''
services:
- - docker:19.03.11-dind
+ - docker:19.03.12-dind
script:
- |
if ! docker info &>/dev/null; then
@@ -15,21 +20,22 @@ performance:
fi
- export CI_ENVIRONMENT_URL=$(cat environment_url.txt)
- mkdir gitlab-exporter
- - wget -O gitlab-exporter/index.js https://gitlab.com/gitlab-org/gl-performance/raw/1.0.0/index.js
+ - wget -O gitlab-exporter/index.js https://gitlab.com/gitlab-org/gl-performance/raw/1.0.1/index.js
- mkdir sitespeed-results
- |
if [ -f .gitlab-urls.txt ]
then
sed -i -e 's@^@'"$CI_ENVIRONMENT_URL"'@' .gitlab-urls.txt
- docker run --shm-size=1g --rm -v "$(pwd)":/sitespeed.io sitespeedio/sitespeed.io:11.2.0 --plugins.add ./gitlab-exporter --outputFolder sitespeed-results .gitlab-urls.txt
+ docker run --shm-size=1g --rm -v "$(pwd)":/sitespeed.io $SITESPEED_IMAGE:$SITESPEED_VERSION --plugins.add ./gitlab-exporter --outputFolder sitespeed-results .gitlab-urls.txt $SITESPEED_OPTIONS
else
- docker run --shm-size=1g --rm -v "$(pwd)":/sitespeed.io sitespeedio/sitespeed.io:11.2.0 --plugins.add ./gitlab-exporter --outputFolder sitespeed-results "$CI_ENVIRONMENT_URL"
+ docker run --shm-size=1g --rm -v "$(pwd)":/sitespeed.io $SITESPEED_IMAGE:$SITESPEED_VERSION --plugins.add ./gitlab-exporter --outputFolder sitespeed-results "$CI_ENVIRONMENT_URL" $SITESPEED_OPTIONS
fi
- - mv sitespeed-results/data/performance.json performance.json
+ - mv sitespeed-results/data/performance.json browser-performance.json
artifacts:
paths:
- - performance.json
- sitespeed-results/
+ reports:
+ browser_performance: browser-performance.json
rules:
- if: '$CI_KUBERNETES_ACTIVE == null || $CI_KUBERNETES_ACTIVE == ""'
when: never
diff --git a/lib/gitlab/ci/templates/Jobs/Build.gitlab-ci.yml b/lib/gitlab/ci/templates/Jobs/Build.gitlab-ci.yml
index b5550461482..dbe870953ae 100644
--- a/lib/gitlab/ci/templates/Jobs/Build.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Jobs/Build.gitlab-ci.yml
@@ -1,10 +1,10 @@
build:
stage: build
- image: "registry.gitlab.com/gitlab-org/cluster-integration/auto-build-image:v0.2.3"
+ image: "registry.gitlab.com/gitlab-org/cluster-integration/auto-build-image:v0.3.1"
variables:
DOCKER_TLS_CERTDIR: ""
services:
- - docker:19.03.11-dind
+ - docker:19.03.12-dind
script:
- |
if [[ -z "$CI_COMMIT_TAG" ]]; then
diff --git a/lib/gitlab/ci/templates/Jobs/Code-Quality.gitlab-ci.yml b/lib/gitlab/ci/templates/Jobs/Code-Quality.gitlab-ci.yml
index bde6f185d3a..6b76d7e0c9b 100644
--- a/lib/gitlab/ci/templates/Jobs/Code-Quality.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Jobs/Code-Quality.gitlab-ci.yml
@@ -1,13 +1,14 @@
code_quality:
stage: test
- image: docker:19.03.11
+ image: docker:19.03.12
allow_failure: true
services:
- - docker:19.03.11-dind
+ - docker:19.03.12-dind
variables:
DOCKER_DRIVER: overlay2
DOCKER_TLS_CERTDIR: ""
- CODE_QUALITY_IMAGE: "registry.gitlab.com/gitlab-org/ci-cd/codequality:0.85.9"
+ CODE_QUALITY_IMAGE: "registry.gitlab.com/gitlab-org/ci-cd/codequality:0.85.10"
+ needs: []
script:
- |
if ! docker info &>/dev/null; then
diff --git a/lib/gitlab/ci/templates/Jobs/DAST-Default-Branch-Deploy.gitlab-ci.yml b/lib/gitlab/ci/templates/Jobs/DAST-Default-Branch-Deploy.gitlab-ci.yml
index bab4fae67f0..d7d927ac8ee 100644
--- a/lib/gitlab/ci/templates/Jobs/DAST-Default-Branch-Deploy.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Jobs/DAST-Default-Branch-Deploy.gitlab-ci.yml
@@ -1,5 +1,5 @@
.dast-auto-deploy:
- image: "registry.gitlab.com/gitlab-org/cluster-integration/auto-deploy-image:v0.17.0"
+ image: "registry.gitlab.com/gitlab-org/cluster-integration/auto-deploy-image:v0.17.2"
dast_environment_deploy:
extends: .dast-auto-deploy
@@ -51,3 +51,4 @@ stop_dast_environment:
- if: $CI_COMMIT_BRANCH &&
$CI_KUBERNETES_ACTIVE &&
$GITLAB_FEATURES =~ /\bdast\b/
+ when: always
diff --git a/lib/gitlab/ci/templates/Jobs/Deploy.gitlab-ci.yml b/lib/gitlab/ci/templates/Jobs/Deploy.gitlab-ci.yml
index 97b5f3fd7f5..66c60e85892 100644
--- a/lib/gitlab/ci/templates/Jobs/Deploy.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Jobs/Deploy.gitlab-ci.yml
@@ -1,5 +1,6 @@
.auto-deploy:
- image: "registry.gitlab.com/gitlab-org/cluster-integration/auto-deploy-image:v0.17.0"
+ image: "registry.gitlab.com/gitlab-org/cluster-integration/auto-deploy-image:v0.17.2"
+ dependencies: []
include:
- template: Jobs/Deploy/ECS.gitlab-ci.yml
@@ -20,7 +21,8 @@ review:
url: http://$CI_PROJECT_ID-$CI_ENVIRONMENT_SLUG.$KUBE_INGRESS_BASE_DOMAIN
on_stop: stop_review
artifacts:
- paths: [environment_url.txt]
+ paths: [environment_url.txt, tiller.log]
+ when: always
rules:
- if: '$CI_KUBERNETES_ACTIVE == null || $CI_KUBERNETES_ACTIVE == ""'
when: never
@@ -41,7 +43,6 @@ stop_review:
environment:
name: review/$CI_COMMIT_REF_NAME
action: stop
- dependencies: []
allow_failure: true
rules:
- if: '$CI_KUBERNETES_ACTIVE == null || $CI_KUBERNETES_ACTIVE == ""'
@@ -122,7 +123,8 @@ canary:
name: production
url: http://$CI_PROJECT_PATH_SLUG.$KUBE_INGRESS_BASE_DOMAIN
artifacts:
- paths: [environment_url.txt]
+ paths: [environment_url.txt, tiller.log]
+ when: always
production:
<<: *production_template
@@ -172,7 +174,8 @@ production_manual:
name: production
url: http://$CI_PROJECT_PATH_SLUG.$KUBE_INGRESS_BASE_DOMAIN
artifacts:
- paths: [environment_url.txt]
+ paths: [environment_url.txt, tiller.log]
+ when: always
.manual_rollout_template: &manual_rollout_template
<<: *rollout_template
diff --git a/lib/gitlab/ci/templates/Jobs/Deploy/ECS.gitlab-ci.yml b/lib/gitlab/ci/templates/Jobs/Deploy/ECS.gitlab-ci.yml
index bb3d5526f3a..da474f8ac88 100644
--- a/lib/gitlab/ci/templates/Jobs/Deploy/ECS.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Jobs/Deploy/ECS.gitlab-ci.yml
@@ -13,11 +13,20 @@
script:
- ecs update-task-definition
-review_ecs:
- extends: .deploy_to_ecs
+.review_ecs_base:
stage: review
+ extends: .deploy_to_ecs
environment:
name: review/$CI_COMMIT_REF_NAME
+
+.production_ecs_base:
+ stage: production
+ extends: .deploy_to_ecs
+ environment:
+ name: production
+
+review_ecs:
+ extends: .review_ecs_base
rules:
- if: '$AUTO_DEVOPS_PLATFORM_TARGET != "ECS"'
when: never
@@ -29,11 +38,21 @@ review_ecs:
when: never
- if: '$CI_COMMIT_TAG || $CI_COMMIT_BRANCH'
+review_fargate:
+ extends: .review_ecs_base
+ rules:
+ - if: '$AUTO_DEVOPS_PLATFORM_TARGET != "FARGATE"'
+ when: never
+ - if: '$CI_KUBERNETES_ACTIVE'
+ when: never
+ - if: '$REVIEW_DISABLED'
+ when: never
+ - if: '$CI_COMMIT_BRANCH == "master"'
+ when: never
+ - if: '$CI_COMMIT_TAG || $CI_COMMIT_BRANCH'
+
production_ecs:
- extends: .deploy_to_ecs
- stage: production
- environment:
- name: production
+ extends: .production_ecs_base
rules:
- if: '$AUTO_DEVOPS_PLATFORM_TARGET != "ECS"'
when: never
@@ -42,3 +61,14 @@ production_ecs:
- if: '$CI_COMMIT_BRANCH != "master"'
when: never
- if: '$CI_COMMIT_TAG || $CI_COMMIT_BRANCH'
+
+production_fargate:
+ extends: .production_ecs_base
+ rules:
+ - if: '$AUTO_DEVOPS_PLATFORM_TARGET != "FARGATE"'
+ when: never
+ - if: '$CI_KUBERNETES_ACTIVE'
+ when: never
+ - if: '$CI_COMMIT_BRANCH != "master"'
+ when: never
+ - if: '$CI_COMMIT_TAG || $CI_COMMIT_BRANCH'
diff --git a/lib/gitlab/ci/templates/Jobs/Load-Performance-Testing.gitlab-ci.yml b/lib/gitlab/ci/templates/Jobs/Load-Performance-Testing.gitlab-ci.yml
new file mode 100644
index 00000000000..b437ddbd734
--- /dev/null
+++ b/lib/gitlab/ci/templates/Jobs/Load-Performance-Testing.gitlab-ci.yml
@@ -0,0 +1,29 @@
+load_performance:
+ stage: performance
+ image: docker:19.03.11
+ allow_failure: true
+ variables:
+ DOCKER_TLS_CERTDIR: ""
+ K6_IMAGE: loadimpact/k6
+ K6_VERSION: 0.26.2
+ K6_TEST_FILE: github.com/loadimpact/k6/samples/http_get.js
+ K6_OPTIONS: ''
+ services:
+ - docker:19.03.11-dind
+ script:
+ - |
+ if ! docker info &>/dev/null; then
+ if [ -z "$DOCKER_HOST" -a "$KUBERNETES_PORT" ]; then
+ export DOCKER_HOST='tcp://localhost:2375'
+ fi
+ fi
+ - docker run --rm -v "$(pwd)":/k6 -w /k6 $K6_IMAGE:$K6_VERSION run $K6_TEST_FILE --summary-export=load-performance.json $K6_OPTIONS
+ artifacts:
+ reports:
+ load_performance: load-performance.json
+ rules:
+ - if: '$CI_KUBERNETES_ACTIVE == null || $CI_KUBERNETES_ACTIVE == ""'
+ when: never
+ - if: '$LOAD_PERFORMANCE_DISABLED'
+ when: never
+ - if: '$CI_COMMIT_TAG || $CI_COMMIT_BRANCH'
diff --git a/lib/gitlab/ci/templates/Managed-Cluster-Applications.gitlab-ci.yml b/lib/gitlab/ci/templates/Managed-Cluster-Applications.gitlab-ci.yml
index 316647b5921..3d0bacda853 100644
--- a/lib/gitlab/ci/templates/Managed-Cluster-Applications.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Managed-Cluster-Applications.gitlab-ci.yml
@@ -1,6 +1,6 @@
apply:
stage: deploy
- image: "registry.gitlab.com/gitlab-org/cluster-integration/cluster-applications:v0.20.0"
+ image: "registry.gitlab.com/gitlab-org/cluster-integration/cluster-applications:v0.24.2"
environment:
name: production
variables:
diff --git a/lib/gitlab/ci/templates/Security/Coverage-Fuzzing.gitlab-ci.yml b/lib/gitlab/ci/templates/Security/Coverage-Fuzzing.gitlab-ci.yml
new file mode 100644
index 00000000000..2fab8b95a3d
--- /dev/null
+++ b/lib/gitlab/ci/templates/Security/Coverage-Fuzzing.gitlab-ci.yml
@@ -0,0 +1,34 @@
+# Read more about this feature https://docs.gitlab.com/ee/user/application_security/coverage_fuzzing
+
+variables:
+ # Which branch we want to run full fledged long running fuzzing jobs.
+ # All others will run fuzzing regression
+ COVERAGE_FUZZING_BRANCH: "$CI_DEFAULT_BRANCH"
+ # This is using semantic version and will always download latest v1 gitlab-cov-fuzz release
+ COVERAGE_FUZZING_VERSION: v1
+ # This is for users who have an offline environment and will have to replicate gitlab-cov-fuzz release binaries
+ # to their own servers
+ COVERAGE_FUZZING_URL_PREFIX: "https://gitlab.com/gitlab-org/security-products/analyzers/gitlab-cov-fuzz/-/raw"
+
+.fuzz_base:
+ stage: fuzz
+ allow_failure: true
+ before_script:
+ - if [ -x "$(command -v apt-get)" ] ; then apt-get update && apt-get install -y wget; fi
+ - wget -O gitlab-cov-fuzz "${COVERAGE_FUZZING_URL_PREFIX}"/"${COVERAGE_FUZZING_VERSION}"/binaries/gitlab-cov-fuzz_Linux_x86_64
+ - chmod a+x gitlab-cov-fuzz
+ - export REGRESSION=true
+ - if [[ $CI_COMMIT_BRANCH = $COVERAGE_FUZZING_BRANCH ]]; then REGRESSION=false; fi;
+ artifacts:
+ paths:
+ - corpus
+ - crashes
+ - gl-coverage-fuzzing-report.json
+ reports:
+ coverage_fuzzing: gl-coverage-fuzzing-report.json
+ when: always
+ rules:
+ - if: $COVERAGE_FUZZING_DISABLED
+ when: never
+ - if: $GITLAB_FEATURES =~ /\bcoverage_fuzzing\b/
+ - if: $CI_RUNNER_EXECUTABLE_ARCH == "linux"
diff --git a/lib/gitlab/ci/templates/Security/DAST.gitlab-ci.yml b/lib/gitlab/ci/templates/Security/DAST.gitlab-ci.yml
index 07399216597..7abecfb7e49 100644
--- a/lib/gitlab/ci/templates/Security/DAST.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Security/DAST.gitlab-ci.yml
@@ -41,4 +41,11 @@ dast:
$DAST_API_SPECIFICATION == null
when: never
- if: $CI_COMMIT_BRANCH &&
+ $CI_KUBERNETES_ACTIVE &&
$GITLAB_FEATURES =~ /\bdast\b/
+ - if: $CI_COMMIT_BRANCH &&
+ $GITLAB_FEATURES =~ /\bdast\b/ &&
+ $DAST_WEBSITE
+ - if: $CI_COMMIT_BRANCH &&
+ $GITLAB_FEATURES =~ /\bdast\b/ &&
+ $DAST_API_SPECIFICATION
diff --git a/lib/gitlab/ci/templates/Security/Dependency-Scanning.gitlab-ci.yml b/lib/gitlab/ci/templates/Security/Dependency-Scanning.gitlab-ci.yml
index fa8ccb7cf93..37f6cd216ca 100644
--- a/lib/gitlab/ci/templates/Security/Dependency-Scanning.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Security/Dependency-Scanning.gitlab-ci.yml
@@ -9,9 +9,6 @@ variables:
# (SAST, Dependency Scanning, ...)
SECURE_ANALYZERS_PREFIX: "registry.gitlab.com/gitlab-org/security-products/analyzers"
- # Deprecated, use SECURE_ANALYZERS_PREFIX instead
- DS_ANALYZER_IMAGE_PREFIX: "$SECURE_ANALYZERS_PREFIX"
-
DS_DEFAULT_ANALYZERS: "bundler-audit, retire.js, gemnasium, gemnasium-maven, gemnasium-python"
DS_EXCLUDED_PATHS: "spec, test, tests, tmp"
DS_MAJOR_VERSION: 2
@@ -45,7 +42,7 @@ dependency_scanning:
docker run \
$(propagate_env_vars \
DS_ANALYZER_IMAGES \
- DS_ANALYZER_IMAGE_PREFIX \
+ SECURE_ANALYZERS_PREFIX \
DS_ANALYZER_IMAGE_TAG \
DS_DEFAULT_ANALYZERS \
DS_EXCLUDED_PATHS \
@@ -55,6 +52,7 @@ dependency_scanning:
DS_PYTHON_VERSION \
DS_PIP_VERSION \
DS_PIP_DEPENDENCY_PATH \
+ DS_JAVA_VERSION \
GEMNASIUM_DB_LOCAL_PATH \
GEMNASIUM_DB_REMOTE_URL \
GEMNASIUM_DB_REF_NAME \
@@ -98,7 +96,7 @@ dependency_scanning:
gemnasium-dependency_scanning:
extends: .ds-analyzer
image:
- name: "$DS_ANALYZER_IMAGE_PREFIX/gemnasium:$DS_MAJOR_VERSION"
+ name: "$SECURE_ANALYZERS_PREFIX/gemnasium:$DS_MAJOR_VERSION"
rules:
- if: $DEPENDENCY_SCANNING_DISABLED || $DS_DISABLE_DIND == 'false'
when: never
@@ -117,7 +115,7 @@ gemnasium-dependency_scanning:
gemnasium-maven-dependency_scanning:
extends: .ds-analyzer
image:
- name: "$DS_ANALYZER_IMAGE_PREFIX/gemnasium-maven:$DS_MAJOR_VERSION"
+ name: "$SECURE_ANALYZERS_PREFIX/gemnasium-maven:$DS_MAJOR_VERSION"
rules:
- if: $DEPENDENCY_SCANNING_DISABLED || $DS_DISABLE_DIND == 'false'
when: never
@@ -133,7 +131,7 @@ gemnasium-maven-dependency_scanning:
gemnasium-python-dependency_scanning:
extends: .ds-analyzer
image:
- name: "$DS_ANALYZER_IMAGE_PREFIX/gemnasium-python:$DS_MAJOR_VERSION"
+ name: "$SECURE_ANALYZERS_PREFIX/gemnasium-python:$DS_MAJOR_VERSION"
rules:
- if: $DEPENDENCY_SCANNING_DISABLED || $DS_DISABLE_DIND == 'false'
when: never
@@ -156,7 +154,7 @@ gemnasium-python-dependency_scanning:
bundler-audit-dependency_scanning:
extends: .ds-analyzer
image:
- name: "$DS_ANALYZER_IMAGE_PREFIX/bundler-audit:$DS_MAJOR_VERSION"
+ name: "$SECURE_ANALYZERS_PREFIX/bundler-audit:$DS_MAJOR_VERSION"
rules:
- if: $DEPENDENCY_SCANNING_DISABLED || $DS_DISABLE_DIND == 'false'
when: never
@@ -169,7 +167,7 @@ bundler-audit-dependency_scanning:
retire-js-dependency_scanning:
extends: .ds-analyzer
image:
- name: "$DS_ANALYZER_IMAGE_PREFIX/retire.js:$DS_MAJOR_VERSION"
+ name: "$SECURE_ANALYZERS_PREFIX/retire.js:$DS_MAJOR_VERSION"
rules:
- if: $DEPENDENCY_SCANNING_DISABLED || $DS_DISABLE_DIND == 'false'
when: never
diff --git a/lib/gitlab/ci/templates/Security/License-Scanning.gitlab-ci.yml b/lib/gitlab/ci/templates/Security/License-Scanning.gitlab-ci.yml
index b0c75b0aab0..cc34d23decc 100644
--- a/lib/gitlab/ci/templates/Security/License-Scanning.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Security/License-Scanning.gitlab-ci.yml
@@ -18,15 +18,15 @@ license_scanning:
name: "$SECURE_ANALYZERS_PREFIX/license-finder:$LICENSE_MANAGEMENT_VERSION"
entrypoint: [""]
variables:
- LM_REPORT_FILE: gl-license-scanning-report.json
LM_REPORT_VERSION: '2.1'
SETUP_CMD: $LICENSE_MANAGEMENT_SETUP_CMD
allow_failure: true
+ needs: []
script:
- /run.sh analyze .
artifacts:
reports:
- license_scanning: $LM_REPORT_FILE
+ license_scanning: gl-license-scanning-report.json
dependencies: []
rules:
- if: $LICENSE_MANAGEMENT_DISABLED
diff --git a/lib/gitlab/ci/templates/Security/SAST.gitlab-ci.yml b/lib/gitlab/ci/templates/Security/SAST.gitlab-ci.yml
index ec7b34d17b5..f0e2f48dd5c 100644
--- a/lib/gitlab/ci/templates/Security/SAST.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Security/SAST.gitlab-ci.yml
@@ -9,10 +9,7 @@ variables:
# (SAST, Dependency Scanning, ...)
SECURE_ANALYZERS_PREFIX: "registry.gitlab.com/gitlab-org/security-products/analyzers"
- # Deprecated, use SECURE_ANALYZERS_PREFIX instead
- SAST_ANALYZER_IMAGE_PREFIX: "$SECURE_ANALYZERS_PREFIX"
-
- SAST_DEFAULT_ANALYZERS: "bandit, brakeman, gosec, spotbugs, flawfinder, phpcs-security-audit, security-code-scan, nodejs-scan, eslint, tslint, secrets, sobelow, pmd-apex, kubesec"
+ SAST_DEFAULT_ANALYZERS: "bandit, brakeman, gosec, spotbugs, flawfinder, phpcs-security-audit, security-code-scan, nodejs-scan, eslint, secrets, sobelow, pmd-apex, kubesec"
SAST_EXCLUDED_PATHS: "spec, test, tests, tmp"
SAST_ANALYZER_IMAGE_TAG: 2
SAST_DISABLE_DIND: "true"
@@ -63,7 +60,7 @@ sast:
bandit-sast:
extends: .sast-analyzer
image:
- name: "$SAST_ANALYZER_IMAGE_PREFIX/bandit:$SAST_ANALYZER_IMAGE_TAG"
+ name: "$SECURE_ANALYZERS_PREFIX/bandit:$SAST_ANALYZER_IMAGE_TAG"
rules:
- if: $SAST_DISABLED || $SAST_DISABLE_DIND == 'false'
when: never
@@ -76,7 +73,7 @@ bandit-sast:
brakeman-sast:
extends: .sast-analyzer
image:
- name: "$SAST_ANALYZER_IMAGE_PREFIX/brakeman:$SAST_ANALYZER_IMAGE_TAG"
+ name: "$SECURE_ANALYZERS_PREFIX/brakeman:$SAST_ANALYZER_IMAGE_TAG"
rules:
- if: $SAST_DISABLED || $SAST_DISABLE_DIND == 'false'
when: never
@@ -88,21 +85,23 @@ brakeman-sast:
eslint-sast:
extends: .sast-analyzer
image:
- name: "$SAST_ANALYZER_IMAGE_PREFIX/eslint:$SAST_ANALYZER_IMAGE_TAG"
+ name: "$SECURE_ANALYZERS_PREFIX/eslint:$SAST_ANALYZER_IMAGE_TAG"
rules:
- if: $SAST_DISABLED || $SAST_DISABLE_DIND == 'false'
when: never
- if: $CI_COMMIT_BRANCH &&
- $GITLAB_FEATURES =~ /\bsast\b/ &&
$SAST_DEFAULT_ANALYZERS =~ /eslint/
exists:
- '**/*.html'
- '**/*.js'
+ - '**/*.jsx'
+ - '**/*.ts'
+ - '**/*.tsx'
flawfinder-sast:
extends: .sast-analyzer
image:
- name: "$SAST_ANALYZER_IMAGE_PREFIX/flawfinder:$SAST_ANALYZER_IMAGE_TAG"
+ name: "$SECURE_ANALYZERS_PREFIX/flawfinder:$SAST_ANALYZER_IMAGE_TAG"
rules:
- if: $SAST_DISABLED || $SAST_DISABLE_DIND == 'false'
when: never
@@ -116,7 +115,7 @@ flawfinder-sast:
kubesec-sast:
extends: .sast-analyzer
image:
- name: "$SAST_ANALYZER_IMAGE_PREFIX/kubesec:$SAST_ANALYZER_IMAGE_TAG"
+ name: "$SECURE_ANALYZERS_PREFIX/kubesec:$SAST_ANALYZER_IMAGE_TAG"
rules:
- if: $SAST_DISABLED || $SAST_DISABLE_DIND == 'false'
when: never
@@ -128,7 +127,7 @@ kubesec-sast:
gosec-sast:
extends: .sast-analyzer
image:
- name: "$SAST_ANALYZER_IMAGE_PREFIX/gosec:$SAST_ANALYZER_IMAGE_TAG"
+ name: "$SECURE_ANALYZERS_PREFIX/gosec:$SAST_ANALYZER_IMAGE_TAG"
rules:
- if: $SAST_DISABLED || $SAST_DISABLE_DIND == 'false'
when: never
@@ -141,7 +140,7 @@ gosec-sast:
nodejs-scan-sast:
extends: .sast-analyzer
image:
- name: "$SAST_ANALYZER_IMAGE_PREFIX/nodejs-scan:$SAST_ANALYZER_IMAGE_TAG"
+ name: "$SECURE_ANALYZERS_PREFIX/nodejs-scan:$SAST_ANALYZER_IMAGE_TAG"
rules:
- if: $SAST_DISABLED || $SAST_DISABLE_DIND == 'false'
when: never
@@ -154,7 +153,7 @@ nodejs-scan-sast:
phpcs-security-audit-sast:
extends: .sast-analyzer
image:
- name: "$SAST_ANALYZER_IMAGE_PREFIX/phpcs-security-audit:$SAST_ANALYZER_IMAGE_TAG"
+ name: "$SECURE_ANALYZERS_PREFIX/phpcs-security-audit:$SAST_ANALYZER_IMAGE_TAG"
rules:
- if: $SAST_DISABLED || $SAST_DISABLE_DIND == 'false'
when: never
@@ -167,7 +166,7 @@ phpcs-security-audit-sast:
pmd-apex-sast:
extends: .sast-analyzer
image:
- name: "$SAST_ANALYZER_IMAGE_PREFIX/pmd-apex:$SAST_ANALYZER_IMAGE_TAG"
+ name: "$SECURE_ANALYZERS_PREFIX/pmd-apex:$SAST_ANALYZER_IMAGE_TAG"
rules:
- if: $SAST_DISABLED || $SAST_DISABLE_DIND == 'false'
when: never
@@ -180,7 +179,7 @@ pmd-apex-sast:
secrets-sast:
extends: .sast-analyzer
image:
- name: "$SAST_ANALYZER_IMAGE_PREFIX/secrets:$SAST_ANALYZER_IMAGE_TAG"
+ name: "$SECURE_ANALYZERS_PREFIX/secrets:$SAST_ANALYZER_IMAGE_TAG"
rules:
- if: $SAST_DISABLED || $SAST_DISABLE_DIND == 'false'
when: never
@@ -191,7 +190,7 @@ secrets-sast:
security-code-scan-sast:
extends: .sast-analyzer
image:
- name: "$SAST_ANALYZER_IMAGE_PREFIX/security-code-scan:$SAST_ANALYZER_IMAGE_TAG"
+ name: "$SECURE_ANALYZERS_PREFIX/security-code-scan:$SAST_ANALYZER_IMAGE_TAG"
rules:
- if: $SAST_DISABLED || $SAST_DISABLE_DIND == 'false'
when: never
@@ -205,7 +204,7 @@ security-code-scan-sast:
sobelow-sast:
extends: .sast-analyzer
image:
- name: "$SAST_ANALYZER_IMAGE_PREFIX/sobelow:$SAST_ANALYZER_IMAGE_TAG"
+ name: "$SECURE_ANALYZERS_PREFIX/sobelow:$SAST_ANALYZER_IMAGE_TAG"
rules:
- if: $SAST_DISABLED || $SAST_DISABLE_DIND == 'false'
when: never
@@ -218,7 +217,7 @@ sobelow-sast:
spotbugs-sast:
extends: .sast-analyzer
image:
- name: "$SAST_ANALYZER_IMAGE_PREFIX/spotbugs:$SAST_ANALYZER_IMAGE_TAG"
+ name: "$SECURE_ANALYZERS_PREFIX/spotbugs:$SAST_ANALYZER_IMAGE_TAG"
rules:
- if: $SAST_DISABLED || $SAST_DISABLE_DIND == 'false'
when: never
@@ -229,16 +228,3 @@ spotbugs-sast:
- '**/*.groovy'
- '**/*.java'
- '**/*.scala'
-
-tslint-sast:
- extends: .sast-analyzer
- image:
- name: "$SAST_ANALYZER_IMAGE_PREFIX/tslint:$SAST_ANALYZER_IMAGE_TAG"
- rules:
- - if: $SAST_DISABLED || $SAST_DISABLE_DIND == 'false'
- when: never
- - if: $CI_COMMIT_BRANCH &&
- $GITLAB_FEATURES =~ /\bsast\b/ &&
- $SAST_DEFAULT_ANALYZERS =~ /tslint/
- exists:
- - '**/*.ts'
diff --git a/lib/gitlab/ci/templates/Security/Secret-Detection.gitlab-ci.yml b/lib/gitlab/ci/templates/Security/Secret-Detection.gitlab-ci.yml
index e18f89cadd7..441a57048e1 100644
--- a/lib/gitlab/ci/templates/Security/Secret-Detection.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Security/Secret-Detection.gitlab-ci.yml
@@ -8,17 +8,33 @@ variables:
SECURE_ANALYZERS_PREFIX: "registry.gitlab.com/gitlab-org/security-products/analyzers"
SECRETS_ANALYZER_VERSION: "3"
-secret_detection:
+.secret-analyzer:
stage: test
image: "$SECURE_ANALYZERS_PREFIX/secrets:$SECRETS_ANALYZER_VERSION"
services: []
- rules:
- - if: $SECRET_DETECTION_DISABLED
- when: never
- - if: $CI_COMMIT_BRANCH && $GITLAB_FEATURES =~ /\bsecret_detection\b/
- when: on_success
artifacts:
reports:
secret_detection: gl-secret-detection-report.json
+
+secret_detection_default_branch:
+ extends: .secret-analyzer
+ rules:
+ - if: $SECRET_DETECTION_DISABLED
+ when: never
+ - if: $CI_DEFAULT_BRANCH == $CI_COMMIT_BRANCH &&
+ $GITLAB_FEATURES =~ /\bsecret_detection\b/
+ script:
+ - /analyzer run
+
+secret_detection:
+ extends: .secret-analyzer
+ rules:
+ - if: $SECRET_DETECTION_DISABLED
+ when: never
+ - if: $CI_COMMIT_BRANCH && $CI_COMMIT_BRANCH != $CI_DEFAULT_BRANCH &&
+ $GITLAB_FEATURES =~ /\bsecret_detection\b/
script:
+ - git fetch origin $CI_DEFAULT_BRANCH $CI_BUILD_REF_NAME
+ - export SECRET_DETECTION_COMMIT_TO=$(git log --left-right --cherry-pick --pretty=format:"%H" refs/remotes/origin/$CI_DEFAULT_BRANCH...refs/remotes/origin/$CI_BUILD_REF_NAME | tail -n 1)
+ - export SECRET_DETECTION_COMMIT_FROM=$CI_COMMIT_SHA
- /analyzer run
diff --git a/lib/gitlab/ci/templates/Security/Secure-Binaries.gitlab-ci.yml b/lib/gitlab/ci/templates/Security/Secure-Binaries.gitlab-ci.yml
index b6c05c61db1..2d2e0859373 100644
--- a/lib/gitlab/ci/templates/Security/Secure-Binaries.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Security/Secure-Binaries.gitlab-ci.yml
@@ -13,7 +13,7 @@
variables:
SECURE_BINARIES_ANALYZERS: >-
- bandit, brakeman, gosec, spotbugs, flawfinder, phpcs-security-audit, security-code-scan, nodejs-scan, eslint, tslint, secrets, sobelow, pmd-apex, kubesec,
+ bandit, brakeman, gosec, spotbugs, flawfinder, phpcs-security-audit, security-code-scan, nodejs-scan, eslint, secrets, sobelow, pmd-apex, kubesec,
bundler-audit, retire.js, gemnasium, gemnasium-maven, gemnasium-python,
klar, clair-vulnerabilities-db,
license-finder,
@@ -40,7 +40,7 @@ variables:
- docker info
- env
- if [ -z "$SECURE_BINARIES_IMAGE" ]; then export SECURE_BINARIES_IMAGE=${SECURE_BINARIES_IMAGE:-"registry.gitlab.com/gitlab-org/security-products/analyzers/${CI_JOB_NAME}:${SECURE_BINARIES_ANALYZER_VERSION}"}; fi
- - docker pull ${SECURE_BINARIES_IMAGE}
+ - docker pull --quiet ${SECURE_BINARIES_IMAGE}
- mkdir -p output/$(dirname ${CI_JOB_NAME})
- |
if [ "$SECURE_BINARIES_SAVE_ARTIFACTS" = "true" ]; then
@@ -125,13 +125,6 @@ eslint:
- $SECURE_BINARIES_DOWNLOAD_IMAGES == "true" &&
$SECURE_BINARIES_ANALYZERS =~ /\beslint\b/
-tslint:
- extends: .download_images
- only:
- variables:
- - $SECURE_BINARIES_DOWNLOAD_IMAGES == "true" &&
- $SECURE_BINARIES_ANALYZERS =~ /\btslint\b/
-
secrets:
extends: .download_images
only:
diff --git a/lib/gitlab/ci/templates/Verify/Browser-Performance.gitlab-ci.yml b/lib/gitlab/ci/templates/Verify/Browser-Performance.gitlab-ci.yml
index e6097ae322e..9dbd9b679a8 100644
--- a/lib/gitlab/ci/templates/Verify/Browser-Performance.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Verify/Browser-Performance.gitlab-ci.yml
@@ -10,8 +10,9 @@ performance:
stage: performance
image: docker:git
variables:
- URL: https://example.com
- SITESPEED_VERSION: 11.2.0
+ URL: ''
+ SITESPEED_IMAGE: sitespeedio/sitespeed.io
+ SITESPEED_VERSION: 13.3.0
SITESPEED_OPTIONS: ''
services:
- docker:stable-dind
@@ -19,11 +20,10 @@ performance:
- mkdir gitlab-exporter
- wget -O ./gitlab-exporter/index.js https://gitlab.com/gitlab-org/gl-performance/raw/master/index.js
- mkdir sitespeed-results
- - docker run --shm-size=1g --rm -v "$(pwd)":/sitespeed.io sitespeedio/sitespeed.io:$SITESPEED_VERSION --plugins.add ./gitlab-exporter --outputFolder sitespeed-results $URL $SITESPEED_OPTIONS
- - mv sitespeed-results/data/performance.json performance.json
+ - docker run --shm-size=1g --rm -v "$(pwd)":/sitespeed.io $SITESPEED_IMAGE:$SITESPEED_VERSION --plugins.add ./gitlab-exporter --outputFolder sitespeed-results $URL $SITESPEED_OPTIONS
+ - mv sitespeed-results/data/performance.json browser-performance.json
artifacts:
paths:
- - performance.json
- sitespeed-results/
reports:
- performance: performance.json
+ browser_performance: browser-performance.json
diff --git a/lib/gitlab/ci/templates/Verify/Load-Performance-Testing.gitlab-ci.yml b/lib/gitlab/ci/templates/Verify/Load-Performance-Testing.gitlab-ci.yml
new file mode 100644
index 00000000000..d39bd234020
--- /dev/null
+++ b/lib/gitlab/ci/templates/Verify/Load-Performance-Testing.gitlab-ci.yml
@@ -0,0 +1,23 @@
+# Read more about the feature here: https://docs.gitlab.com/ee/user/project/merge_requests/load_performance_testing.html
+
+stages:
+ - build
+ - test
+ - deploy
+ - performance
+
+load_performance:
+ stage: performance
+ image: docker:git
+ variables:
+ K6_IMAGE: loadimpact/k6
+ K6_VERSION: 0.26.2
+ K6_TEST_FILE: github.com/loadimpact/k6/samples/http_get.js
+ K6_OPTIONS: ''
+ services:
+ - docker:stable-dind
+ script:
+ - docker run --rm -v "$(pwd)":/k6 -w /k6 $K6_IMAGE:$K6_VERSION run $K6_TEST_FILE --summary-export=load-performance.json $K6_OPTIONS
+ artifacts:
+ reports:
+ load_performance: load-performance.json
diff --git a/lib/gitlab/ci/templates/index.md b/lib/gitlab/ci/templates/index.md
new file mode 100644
index 00000000000..ff151dd4d1a
--- /dev/null
+++ b/lib/gitlab/ci/templates/index.md
@@ -0,0 +1,3 @@
+# Development guide for GitLab CI templates
+
+Please follow [the development guideline](../../../../doc/development/cicd/templates.md)
diff --git a/lib/gitlab/ci/templates/npm.gitlab-ci.yml b/lib/gitlab/ci/templates/npm.gitlab-ci.yml
new file mode 100644
index 00000000000..035ba52da84
--- /dev/null
+++ b/lib/gitlab/ci/templates/npm.gitlab-ci.yml
@@ -0,0 +1,59 @@
+default:
+ image: node:latest
+
+ # Validate that the repository contains a package.json and extract a few values from it.
+ before_script:
+ - |
+ if [[ ! -f package.json ]]; then
+ echo "No package.json found! A package.json file is required to publish a package to GitLab's NPM registry."
+ echo 'For more information, see https://docs.gitlab.com/ee/user/packages/npm_registry/#creating-a-project'
+ exit 1
+ fi
+ - NPM_PACKAGE_NAME=$(node -p "require('./package.json').name")
+ - NPM_PACKAGE_VERSION=$(node -p "require('./package.json').version")
+
+# Validate that the package name is properly scoped to the project's root namespace.
+# For more information, see https://docs.gitlab.com/ee/user/packages/npm_registry/#package-naming-convention
+validate_package_scope:
+ stage: build
+ script:
+ - |
+ if [[ ! $NPM_PACKAGE_NAME =~ ^@$CI_PROJECT_ROOT_NAMESPACE/ ]]; then
+ echo "Invalid package scope! Packages must be scoped in the root namespace of the project, e.g. \"@${CI_PROJECT_ROOT_NAMESPACE}/${CI_PROJECT_NAME}\""
+ echo 'For more information, see https://docs.gitlab.com/ee/user/packages/npm_registry/#package-naming-convention'
+ exit 1
+ fi
+
+# If no .npmrc if included in the repo, generate a temporary one to use during the publish step
+# that is configured to publish to GitLab's NPM registry
+create_npmrc:
+ stage: build
+ script:
+ - |
+ if [[ ! -f .npmrc ]]; then
+ echo 'No .npmrc found! Creating one now. Please review the following link for more information: https://docs.gitlab.com/ee/user/packages/npm_registry/index.html#authenticating-with-a-ci-job-token'
+
+ {
+ echo '@${CI_PROJECT_ROOT_NAMESPACE}:registry=${CI_SERVER_PROTOCOL}://${CI_SERVER_HOST}:${CI_SERVER_PORT}/api/v4/projects/${CI_PROJECT_ID}/packages/npm/'
+ echo '//${CI_SERVER_HOST}:${CI_SERVER_PORT}/api/v4/packages/npm/:_authToken=${CI_JOB_TOKEN}'
+ echo '//${CI_SERVER_HOST}:${CI_SERVER_PORT}/api/v4/projects/${CI_PROJECT_ID}/packages/npm/:_authToken=${CI_JOB_TOKEN}'
+ } >> .npmrc
+
+ fi
+ artifacts:
+ paths:
+ - .npmrc
+
+# Publish the package. If the version in package.json has not yet been published, it will be
+# published to GitLab's NPM registry. If the version already exists, the publish command
+# will fail and the existing package will not be updated.
+publish_package:
+ stage: deploy
+ script:
+ - |
+ {
+ npm publish &&
+ echo "Successfully published version ${NPM_PACKAGE_VERSION} of ${NPM_PACKAGE_NAME} to GitLab's NPM registry: ${CI_PROJECT_URL}/-/packages"
+ } || {
+ echo "No new version of ${NPM_PACKAGE_NAME} published. This is most likely because version ${NPM_PACKAGE_VERSION} already exists in GitLab's NPM registry."
+ }
diff --git a/lib/gitlab/ci/yaml_processor.rb b/lib/gitlab/ci/yaml_processor.rb
index 6a9b7b2fc85..8cf355bbfc1 100644
--- a/lib/gitlab/ci/yaml_processor.rb
+++ b/lib/gitlab/ci/yaml_processor.rb
@@ -3,15 +3,33 @@
module Gitlab
module Ci
class YamlProcessor
- ValidationError = Class.new(StandardError)
+ # ValidationError is treated like a result object in the form of an exception.
+ # We can return any warnings, raised during the config validation, along with
+ # the error object until we support multiple messages to be returned.
+ class ValidationError < StandardError
+ attr_reader :warnings
+
+ def initialize(message, warnings: [])
+ @warnings = warnings
+ super(message)
+ end
+ end
include Gitlab::Config::Entry::LegacyValidationHelpers
attr_reader :stages, :jobs
- ResultWithErrors = Struct.new(:content, :errors) do
+ class Result
+ attr_reader :config, :errors, :warnings
+
+ def initialize(config: nil, errors: [], warnings: [])
+ @config = config
+ @errors = errors
+ @warnings = warnings
+ end
+
def valid?
- errors.empty?
+ config.present? && errors.empty?
end
end
@@ -20,24 +38,32 @@ module Gitlab
@config = @ci_config.to_hash
unless @ci_config.valid?
- raise ValidationError, @ci_config.errors.first
+ error!(@ci_config.errors.first)
end
initial_parsing
rescue Gitlab::Ci::Config::ConfigError => e
- raise ValidationError, e.message
+ error!(e.message)
end
def self.new_with_validation_errors(content, opts = {})
- return ResultWithErrors.new('', ['Please provide content of .gitlab-ci.yml']) if content.blank?
+ return Result.new(errors: ['Please provide content of .gitlab-ci.yml']) if content.blank?
config = Gitlab::Ci::Config.new(content, **opts)
- return ResultWithErrors.new("", config.errors) unless config.valid?
+ return Result.new(errors: config.errors, warnings: config.warnings) unless config.valid?
config = Gitlab::Ci::YamlProcessor.new(content, opts)
- ResultWithErrors.new(config, [])
- rescue ValidationError, Gitlab::Ci::Config::ConfigError => e
- ResultWithErrors.new('', [e.message])
+ Result.new(config: config, warnings: config.warnings)
+
+ rescue ValidationError => e
+ Result.new(errors: [e.message], warnings: e.warnings)
+
+ rescue Gitlab::Ci::Config::ConfigError => e
+ Result.new(errors: [e.message])
+ end
+
+ def warnings
+ @ci_config&.warnings || []
end
def builds
@@ -66,6 +92,7 @@ module Gitlab
cache: job[:cache],
resource_group_key: job[:resource_group],
scheduling_type: job[:scheduling_type],
+ secrets: job[:secrets],
options: {
image: job[:image],
services: job[:services],
@@ -157,10 +184,14 @@ module Gitlab
return unless job[:stage]
unless job[:stage].is_a?(String) && job[:stage].in?(@stages)
- raise ValidationError, "#{name} job: chosen stage does not exist; available stages are #{@stages.join(", ")}"
+ error!("#{name} job: chosen stage does not exist; available stages are #{@stages.join(", ")}")
end
end
+ def error!(message)
+ raise ValidationError.new(message, warnings: warnings)
+ end
+
def validate_job_dependencies!(name, job)
return unless job[:dependencies]
@@ -190,7 +221,7 @@ module Gitlab
def validate_job_dependency!(name, dependency, dependency_type = 'dependency')
unless @jobs[dependency.to_sym]
- raise ValidationError, "#{name} job: undefined #{dependency_type}: #{dependency}"
+ error!("#{name} job: undefined #{dependency_type}: #{dependency}")
end
job_stage_index = stage_index(name)
@@ -199,7 +230,7 @@ module Gitlab
# A dependency might be defined later in the configuration
# with a stage that does not exist
unless dependency_stage_index.present? && dependency_stage_index < job_stage_index
- raise ValidationError, "#{name} job: #{dependency_type} #{dependency} is not defined in prior stages"
+ error!("#{name} job: #{dependency_type} #{dependency} is not defined in prior stages")
end
end
@@ -221,19 +252,19 @@ module Gitlab
on_stop_job = @jobs[on_stop.to_sym]
unless on_stop_job
- raise ValidationError, "#{name} job: on_stop job #{on_stop} is not defined"
+ error!("#{name} job: on_stop job #{on_stop} is not defined")
end
unless on_stop_job[:environment]
- raise ValidationError, "#{name} job: on_stop job #{on_stop} does not have environment defined"
+ error!("#{name} job: on_stop job #{on_stop} does not have environment defined")
end
unless on_stop_job[:environment][:name] == environment[:name]
- raise ValidationError, "#{name} job: on_stop job #{on_stop} have different environment name"
+ error!("#{name} job: on_stop job #{on_stop} have different environment name")
end
unless on_stop_job[:environment][:action] == 'stop'
- raise ValidationError, "#{name} job: on_stop job #{on_stop} needs to have action stop defined"
+ error!("#{name} job: on_stop job #{on_stop} needs to have action stop defined")
end
end
end
diff --git a/lib/gitlab/class_attributes.rb b/lib/gitlab/class_attributes.rb
new file mode 100644
index 00000000000..6560c97b2e6
--- /dev/null
+++ b/lib/gitlab/class_attributes.rb
@@ -0,0 +1,30 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module ClassAttributes
+ extend ActiveSupport::Concern
+
+ class_methods do
+ protected
+
+ # Returns an attribute declared on this class or its parent class.
+ # This approach allows declared attributes to be inherited by
+ # child classes.
+ def get_class_attribute(name)
+ class_attributes[name] || superclass_attributes(name)
+ end
+
+ private
+
+ def class_attributes
+ @class_attributes ||= {}
+ end
+
+ def superclass_attributes(name)
+ return unless superclass.include? Gitlab::ClassAttributes
+
+ superclass.get_class_attribute(name)
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/code_navigation_path.rb b/lib/gitlab/code_navigation_path.rb
index faf623faccf..909d0536b5f 100644
--- a/lib/gitlab/code_navigation_path.rb
+++ b/lib/gitlab/code_navigation_path.rb
@@ -5,7 +5,7 @@ module Gitlab
include Gitlab::Utils::StrongMemoize
include Gitlab::Routing
- LATEST_COMMITS_LIMIT = 10
+ LATEST_COMMITS_LIMIT = 2
def initialize(project, commit_sha)
@project = project
@@ -28,11 +28,11 @@ module Gitlab
latest_commits_shas =
project.repository.commits(commit_sha, limit: LATEST_COMMITS_LIMIT).map(&:sha)
- artifact =
- ::Ci::JobArtifact
- .with_file_types(['lsif'])
- .for_sha(latest_commits_shas, project.id)
- .last
+ pipeline = @project.ci_pipelines.for_sha(latest_commits_shas).last
+
+ next unless pipeline
+
+ artifact = pipeline.job_artifacts.with_file_types(['lsif']).last
artifact&.job
end
diff --git a/lib/gitlab/conan_token.rb b/lib/gitlab/conan_token.rb
new file mode 100644
index 00000000000..7526c10b608
--- /dev/null
+++ b/lib/gitlab/conan_token.rb
@@ -0,0 +1,64 @@
+# frozen_string_literal: true
+
+# The Conan client uses a JWT for authenticating with remotes.
+# This class encodes and decodes a user's personal access token or
+# CI_JOB_TOKEN into a JWT that is used by the Conan client to
+# authenticate with GitLab
+
+module Gitlab
+ class ConanToken
+ HMAC_KEY = 'gitlab-conan-packages'.freeze
+
+ attr_reader :access_token_id, :user_id
+
+ class << self
+ def from_personal_access_token(access_token)
+ new(access_token_id: access_token.id, user_id: access_token.user_id)
+ end
+
+ def from_job(job)
+ new(access_token_id: job.token, user_id: job.user.id)
+ end
+
+ def from_deploy_token(deploy_token)
+ new(access_token_id: deploy_token.token, user_id: deploy_token.username)
+ end
+
+ def decode(jwt)
+ payload = JSONWebToken::HMACToken.decode(jwt, secret).first
+
+ new(access_token_id: payload['access_token'], user_id: payload['user_id'])
+ rescue JWT::DecodeError, JWT::ExpiredSignature, JWT::ImmatureSignature
+ # we return on expired and errored tokens because the Conan client
+ # will request a new token automatically.
+ end
+
+ def secret
+ OpenSSL::HMAC.hexdigest(
+ OpenSSL::Digest::SHA256.new,
+ ::Settings.attr_encrypted_db_key_base,
+ HMAC_KEY
+ )
+ end
+ end
+
+ def initialize(access_token_id:, user_id:)
+ @access_token_id = access_token_id
+ @user_id = user_id
+ end
+
+ def to_jwt
+ hmac_token.encoded
+ end
+
+ private
+
+ def hmac_token
+ JSONWebToken::HMACToken.new(self.class.secret).tap do |token|
+ token['access_token'] = access_token_id
+ token['user_id'] = user_id
+ token.expire_time = token.issued_at + 1.hour
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/config/entry/configurable.rb b/lib/gitlab/config/entry/configurable.rb
index 571e7a5127e..6bf77ebaa5b 100644
--- a/lib/gitlab/config/entry/configurable.rb
+++ b/lib/gitlab/config/entry/configurable.rb
@@ -62,7 +62,9 @@ module Gitlab
class_methods do
def nodes
- Hash[(@nodes || {}).map { |key, factory| [key, factory.dup] }]
+ return {} unless @nodes
+
+ @nodes.transform_values(&:dup)
end
def reserved_node_names
diff --git a/lib/gitlab/config/entry/node.rb b/lib/gitlab/config/entry/node.rb
index 84d3409ed91..32912cb1046 100644
--- a/lib/gitlab/config/entry/node.rb
+++ b/lib/gitlab/config/entry/node.rb
@@ -16,6 +16,7 @@ module Gitlab
@config = config
@metadata = metadata
@entries = {}
+ @warnings = []
yield(self) if block_given?
@@ -60,6 +61,14 @@ module Gitlab
[]
end
+ def warnings
+ @warnings + descendants.flat_map(&:warnings)
+ end
+
+ def add_warning(message)
+ @warnings << "#{location} #{message}"
+ end
+
def value
if leaf?
@config
@@ -68,7 +77,7 @@ module Gitlab
value.specified? && value.relevant?
end
- Hash[meaningful.map { |key, entry| [key, entry.value] }]
+ meaningful.transform_values { |entry| entry.value }
end
end
diff --git a/lib/gitlab/config/loader/yaml.rb b/lib/gitlab/config/loader/yaml.rb
index e001742a7f8..cb3fc49944c 100644
--- a/lib/gitlab/config/loader/yaml.rb
+++ b/lib/gitlab/config/loader/yaml.rb
@@ -5,6 +5,7 @@ module Gitlab
module Loader
class Yaml
DataTooLargeError = Class.new(Loader::FormatError)
+ NotHashError = Class.new(Loader::FormatError)
include Gitlab::Utils::StrongMemoize
@@ -23,7 +24,7 @@ module Gitlab
def load_raw!
raise DataTooLargeError, 'The parsed YAML is too big' if too_big?
- raise Loader::FormatError, 'Invalid configuration format' unless hash?
+ raise NotHashError, 'Invalid configuration format' unless hash?
@config
end
diff --git a/lib/gitlab/config_checker/external_database_checker.rb b/lib/gitlab/config_checker/external_database_checker.rb
index 795082a10a0..c08dd0351f3 100644
--- a/lib/gitlab/config_checker/external_database_checker.rb
+++ b/lib/gitlab/config_checker/external_database_checker.rb
@@ -5,22 +5,43 @@ module Gitlab
module ExternalDatabaseChecker
extend self
- # DB is considered deprecated if it is below version 11
- def db_version_deprecated?
- Gitlab::Database.version.to_f < 11
- end
-
def check
- return [] unless db_version_deprecated?
+ notices = []
+
+ unless Gitlab::Database.postgresql_minimum_supported_version?
+ notices <<
+ {
+ type: 'warning',
+ message: _('You are using PostgreSQL %{pg_version_current}, but PostgreSQL ' \
+ '%{pg_version_minimum} is required for this version of GitLab. ' \
+ 'Please upgrade your environment to a supported PostgreSQL version, ' \
+ 'see %{pg_requirements_url} for details.') % {
+ pg_version_current: Gitlab::Database.version,
+ pg_version_minimum: Gitlab::Database::MINIMUM_POSTGRES_VERSION,
+ pg_requirements_url: '<a href="https://docs.gitlab.com/ee/install/requirements.html#database">database requirements</a>'
+ }
+ }
+ end
+
+ if Gitlab::Database.postgresql_upcoming_deprecation?
+ upcoming_deprecation = Gitlab::Database::UPCOMING_POSTGRES_VERSION_DETAILS
+
+ notices <<
+ {
+ type: 'warning',
+ message: _('Note that PostgreSQL %{pg_version_upcoming} will become the minimum required ' \
+ 'version in GitLab %{gl_version_upcoming} (%{gl_version_upcoming_date}). Please ' \
+ 'consider upgrading your environment to a supported PostgreSQL version soon, ' \
+ 'see <a href="%{pg_version_upcoming_url}">the related epic</a> for details.') % {
+ pg_version_upcoming: upcoming_deprecation[:pg_version_minimum],
+ gl_version_upcoming: upcoming_deprecation[:gl_version],
+ gl_version_upcoming_date: upcoming_deprecation[:gl_version_date],
+ pg_version_upcoming_url: upcoming_deprecation[:url]
+ }
+ }
+ end
- [
- {
- type: 'warning',
- message: _('Note that PostgreSQL 11 will become the minimum required PostgreSQL version in GitLab 13.0 (May 2020). '\
- 'PostgreSQL 9.6 and PostgreSQL 10 will no longer be supported in GitLab 13.0. '\
- 'Please consider upgrading your PostgreSQL version (%{db_version}) soon.') % { db_version: Gitlab::Database.version.to_s }
- }
- ]
+ notices
end
end
end
diff --git a/lib/gitlab/danger/changelog.rb b/lib/gitlab/danger/changelog.rb
index 85f386594be..4427c331b8e 100644
--- a/lib/gitlab/danger/changelog.rb
+++ b/lib/gitlab/danger/changelog.rb
@@ -3,11 +3,17 @@
module Gitlab
module Danger
module Changelog
- NO_CHANGELOG_LABELS = %w[backstage ci-build meta].freeze
+ NO_CHANGELOG_LABELS = [
+ 'tooling',
+ 'tooling::pipelines',
+ 'tooling::workflow',
+ 'ci-build',
+ 'meta'
+ ].freeze
NO_CHANGELOG_CATEGORIES = %i[docs none].freeze
def needed?
- categories_need_changelog? && (gitlab.mr_labels & NO_CHANGELOG_LABELS).empty?
+ categories_need_changelog? && without_no_changelog_label?
end
def found
@@ -27,6 +33,10 @@ module Gitlab
def categories_need_changelog?
(helper.changes_by_category.keys - NO_CHANGELOG_CATEGORIES).any?
end
+
+ def without_no_changelog_label?
+ (gitlab.mr_labels & NO_CHANGELOG_LABELS).empty?
+ end
end
end
end
diff --git a/lib/gitlab/danger/commit_linter.rb b/lib/gitlab/danger/commit_linter.rb
index 58db2b58560..954934518d7 100644
--- a/lib/gitlab/danger/commit_linter.rb
+++ b/lib/gitlab/danger/commit_linter.rb
@@ -8,8 +8,6 @@ module Gitlab
class CommitLinter
MIN_SUBJECT_WORDS_COUNT = 3
MAX_LINE_LENGTH = 72
- WARN_SUBJECT_LENGTH = 50
- URL_LIMIT_SUBJECT = "https://chris.beams.io/posts/git-commit/#limit-50"
MAX_CHANGED_FILES_IN_COMMIT = 3
MAX_CHANGED_LINES_IN_COMMIT = 30
SHORT_REFERENCE_REGEX = %r{([\w\-\/]+)?(#|!|&|%)\d+\b}.freeze
@@ -18,7 +16,6 @@ module Gitlab
PROBLEMS = {
subject_too_short: "The %s must contain at least #{MIN_SUBJECT_WORDS_COUNT} words",
subject_too_long: "The %s may not be longer than #{MAX_LINE_LENGTH} characters",
- subject_above_warning: "The %s length is acceptable, but please try to [reduce it to #{WARN_SUBJECT_LENGTH} characters](#{URL_LIMIT_SUBJECT})",
subject_starts_with_lowercase: "The %s must start with a capital letter",
subject_ends_with_a_period: "The %s must not end with a period",
separator_missing: "The commit subject and body must be separated by a blank line",
@@ -88,8 +85,6 @@ module Gitlab
if subject_too_long?
add_problem(:subject_too_long, subject_description)
- elsif subject_above_warning?
- add_problem(:subject_above_warning, subject_description)
end
if subject_starts_with_lowercase?
@@ -195,10 +190,6 @@ module Gitlab
line_too_long?(subject)
end
- def subject_above_warning?
- subject.length > WARN_SUBJECT_LENGTH
- end
-
def subject_starts_with_lowercase?
first_char = subject.sub(/\A\[.+\]\s/, '')[0]
first_char_downcased = first_char.downcase
diff --git a/lib/gitlab/danger/helper.rb b/lib/gitlab/danger/helper.rb
index 327418ad100..db799c094b2 100644
--- a/lib/gitlab/danger/helper.rb
+++ b/lib/gitlab/danger/helper.rb
@@ -6,6 +6,7 @@ module Gitlab
module Danger
module Helper
RELEASE_TOOLS_BOT = 'gitlab-release-tools-bot'
+ DRAFT_REGEX = /\A*#{Regexp.union(/(?i)(\[WIP\]\s*|WIP:\s*|WIP$)/, /(?i)(\[draft\]|\(draft\)|draft:|draft\s\-\s|draft$)/)}+\s*/i.freeze
# Returns a list of all files that have been added, modified or renamed.
# `git.modified_files` might contain paths that already have been renamed,
@@ -34,6 +35,18 @@ module Gitlab
.sort
end
+ # Returns a string containing changed lines as git diff
+ #
+ # Considering changing a line in lib/gitlab/usage_data.rb it will return:
+ #
+ # [ "--- a/lib/gitlab/usage_data.rb",
+ # "+++ b/lib/gitlab/usage_data.rb",
+ # "+ # Test change",
+ # "- # Old change" ]
+ def changed_lines(changed_file)
+ git.diff_for_file(changed_file).patch.split("\n").select { |line| %r{^[+-]}.match?(line) }
+ end
+
def all_ee_changes
all_changed_files.grep(%r{\Aee/})
end
@@ -73,16 +86,25 @@ module Gitlab
# @return [Hash<String,Array<String>>]
def changes_by_category
all_changed_files.each_with_object(Hash.new { |h, k| h[k] = [] }) do |file, hash|
- hash[category_for_file(file)] << file
+ categories_for_file(file).each { |category| hash[category] << file }
end
end
- # Determines the category a file is in, e.g., `:frontend` or `:backend`
- # @return[Symbol]
- def category_for_file(file)
- _, category = CATEGORIES.find { |regexp, _| regexp.match?(file) }
+ # Determines the categories a file is in, e.g., `[:frontend]`, `[:backend]`, or `%i[frontend engineering_productivity]`
+ # using filename regex and specific change regex if given.
+ #
+ # @return Array<Symbol>
+ def categories_for_file(file)
+ _, categories = CATEGORIES.find do |key, _|
+ filename_regex, changes_regex = Array(key)
+
+ found = filename_regex.match?(file)
+ found &&= changed_lines(file).any? { |changed_line| changes_regex.match?(changed_line) } if changes_regex
- category || :unknown
+ found
+ end
+
+ Array(categories || :unknown)
end
# Returns the GFM for a category label, making its best guess if it's not
@@ -102,8 +124,10 @@ module Gitlab
}.freeze
# First-match win, so be sure to put more specific regex at the top...
CATEGORIES = {
- %r{\Adoc/} => :none, # To reinstate roulette for documentation, set to `:docs`.
- %r{\A(CONTRIBUTING|LICENSE|MAINTENANCE|PHILOSOPHY|PROCESS|README)(\.md)?\z} => :none, # To reinstate roulette for documentation, set to `:docs`.
+ [%r{usage_data}, %r{^(\+|-).*(count|distinct_count)\(.*\)(.*)$}] => [:database, :backend],
+
+ %r{\Adoc/.*(\.(md|png|gif|jpg))\z} => :docs,
+ %r{\A(CONTRIBUTING|LICENSE|MAINTENANCE|PHILOSOPHY|PROCESS|README)(\.md)?\z} => :docs,
%r{\A(ee/)?app/(assets|views)/} => :frontend,
%r{\A(ee/)?public/} => :frontend,
@@ -125,10 +149,13 @@ module Gitlab
jest\.config\.js |
package\.json |
yarn\.lock |
- config/.+\.js |
- \.gitlab/ci/frontend\.gitlab-ci\.yml
+ config/.+\.js
)\z}x => :frontend,
+ %r{(\A|/)(
+ \.gitlab/ci/frontend\.gitlab-ci\.yml
+ )\z}x => %i[frontend engineering_productivity],
+
%r{\A(ee/)?db/(?!fixtures)[^/]+} => :database,
%r{\A(ee/)?lib/gitlab/(database|background_migration|sql|github_import)(/|\.rb)} => :database,
%r{\A(app/models/project_authorization|app/services/users/refresh_authorized_projects_service)(/|\.rb)} => :database,
@@ -136,13 +163,13 @@ module Gitlab
%r{\Arubocop/cop/migration(/|\.rb)} => :database,
%r{\A(\.gitlab-ci\.yml\z|\.gitlab\/ci)} => :engineering_productivity,
+ %r{\A\.codeclimate\.yml\z} => :engineering_productivity,
%r{\A\.overcommit\.yml\.example\z} => :engineering_productivity,
- %r{\Atooling/overcommit/} => :engineering_productivity,
- %r{\A.editorconfig\z} => :engineering_productivity,
+ %r{\A\.editorconfig\z} => :engineering_productivity,
%r{Dangerfile\z} => :engineering_productivity,
%r{\A(ee/)?(danger/|lib/gitlab/danger/)} => :engineering_productivity,
%r{\A(ee/)?scripts/} => :engineering_productivity,
- %r{\A\.codeclimate\.yml\z} => :engineering_productivity,
+ %r{\Atooling/} => :engineering_productivity,
%r{\A(ee/)?app/(?!assets|views)[^/]+} => :backend,
%r{\A(ee/)?(bin|config|generator_templates|lib|rubocop)/} => :backend,
@@ -184,7 +211,7 @@ module Gitlab
end
def sanitize_mr_title(title)
- title.gsub(/^WIP: */, '').gsub(/`/, '\\\`')
+ title.gsub(DRAFT_REGEX, '').gsub(/`/, '\\\`')
end
def security_mr?
@@ -193,6 +220,18 @@ module Gitlab
gitlab_helper.mr_json['web_url'].include?('/gitlab-org/security/')
end
+ def cherry_pick_mr?
+ return false unless gitlab_helper
+
+ /cherry[\s-]*pick/i.match?(gitlab_helper.mr_json['title'])
+ end
+
+ def stable_branch?
+ return false unless gitlab_helper
+
+ /\A\d+-\d+-stable-ee/i.match?(gitlab_helper.mr_json['target_branch'])
+ end
+
def mr_has_labels?(*labels)
return false unless gitlab_helper
diff --git a/lib/gitlab/danger/roulette.rb b/lib/gitlab/danger/roulette.rb
index 9f7980dc20a..ed4af3f4a43 100644
--- a/lib/gitlab/danger/roulette.rb
+++ b/lib/gitlab/danger/roulette.rb
@@ -5,8 +5,12 @@ require_relative 'teammate'
module Gitlab
module Danger
module Roulette
- ROULETTE_DATA_URL = 'https://about.gitlab.com/roulette.json'
- OPTIONAL_CATEGORIES = [:qa, :test].freeze
+ ROULETTE_DATA_URL = 'https://gitlab-org.gitlab.io/gitlab-roulette/roulette.json'
+ HOURS_WHEN_PERSON_CAN_BE_PICKED = (6..14).freeze
+
+ INCLUDE_TIMEZONE_FOR_CATEGORY = {
+ database: false
+ }.freeze
Spin = Struct.new(:category, :reviewer, :maintainer, :optional_role)
@@ -14,7 +18,7 @@ module Gitlab
# for each change category that a Merge Request contains.
#
# @return [Array<Spin>]
- def spin(project, categories, branch_name)
+ def spin(project, categories, branch_name, timezone_experiment: false)
team =
begin
project_team(project)
@@ -26,7 +30,9 @@ module Gitlab
canonical_branch_name = canonical_branch_name(branch_name)
spin_per_category = categories.each_with_object({}) do |category, memo|
- memo[category] = spin_for_category(team, project, category, canonical_branch_name)
+ including_timezone = INCLUDE_TIMEZONE_FOR_CATEGORY.fetch(category, timezone_experiment)
+
+ memo[category] = spin_for_category(team, project, category, canonical_branch_name, timezone_experiment: including_timezone)
end
spin_per_category.map do |category, spin|
@@ -80,9 +86,14 @@ module Gitlab
# Known issue: If someone is rejected due to OOO, and then becomes not OOO, the
# selection will change on next spin
# @param [Array<Teammate>] people
- def spin_for_person(people, random:)
- people.shuffle(random: random)
- .find(&method(:valid_person?))
+ def spin_for_person(people, random:, timezone_experiment: false)
+ shuffled_people = people.shuffle(random: random)
+
+ if timezone_experiment
+ shuffled_people.find(&method(:valid_person_with_timezone?))
+ else
+ shuffled_people.find(&method(:valid_person?))
+ end
end
private
@@ -90,7 +101,13 @@ module Gitlab
# @param [Teammate] person
# @return [Boolean]
def valid_person?(person)
- !mr_author?(person) && person.available?
+ !mr_author?(person) && person.available
+ end
+
+ # @param [Teammate] person
+ # @return [Boolean]
+ def valid_person_with_timezone?(person)
+ valid_person?(person) && HOURS_WHEN_PERSON_CAN_BE_PICKED.cover?(person.local_hour)
end
# @param [Teammate] person
@@ -105,7 +122,7 @@ module Gitlab
end
end
- def spin_for_category(team, project, category, branch_name)
+ def spin_for_category(team, project, category, branch_name, timezone_experiment: false)
reviewers, traintainers, maintainers =
%i[reviewer traintainer maintainer].map do |role|
spin_role_for_category(team, role, project, category)
@@ -116,14 +133,10 @@ module Gitlab
# Make traintainers have triple the chance to be picked as a reviewer
random = new_random(branch_name)
- reviewer = spin_for_person(reviewers + traintainers + traintainers, random: random)
- maintainer = spin_for_person(maintainers, random: random)
+ reviewer = spin_for_person(reviewers + traintainers + traintainers, random: random, timezone_experiment: timezone_experiment)
+ maintainer = spin_for_person(maintainers, random: random, timezone_experiment: timezone_experiment)
- Spin.new(category, reviewer, maintainer).tap do |spin|
- if OPTIONAL_CATEGORIES.include?(category)
- spin.optional_role = :maintainer
- end
- end
+ Spin.new(category, reviewer, maintainer)
end
end
end
diff --git a/lib/gitlab/danger/sidekiq_queues.rb b/lib/gitlab/danger/sidekiq_queues.rb
new file mode 100644
index 00000000000..726b6134abf
--- /dev/null
+++ b/lib/gitlab/danger/sidekiq_queues.rb
@@ -0,0 +1,37 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Danger
+ module SidekiqQueues
+ def changed_queue_files
+ @changed_queue_files ||= git.modified_files.grep(%r{\A(ee/)?app/workers/all_queues\.yml})
+ end
+
+ def added_queue_names
+ @added_queue_names ||= new_queues.keys - old_queues.keys
+ end
+
+ def changed_queue_names
+ @changed_queue_names ||=
+ (new_queues.values_at(*old_queues.keys) - old_queues.values)
+ .compact.map { |queue| queue[:name] }
+ end
+
+ private
+
+ def old_queues
+ @old_queues ||= queues_for(gitlab.base_commit)
+ end
+
+ def new_queues
+ @new_queues ||= queues_for(gitlab.head_commit)
+ end
+
+ def queues_for(branch)
+ changed_queue_files
+ .flat_map { |file| YAML.safe_load(`git show #{branch}:#{file}`, permitted_classes: [Symbol]) }
+ .to_h { |queue| [queue[:name], queue] }
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/danger/teammate.rb b/lib/gitlab/danger/teammate.rb
index 651b002d2bf..f7da66e77cd 100644
--- a/lib/gitlab/danger/teammate.rb
+++ b/lib/gitlab/danger/teammate.rb
@@ -1,28 +1,19 @@
# frozen_string_literal: true
-require 'cgi'
-require 'set'
-
module Gitlab
module Danger
class Teammate
- attr_reader :name, :username, :role, :projects
-
- AT_CAPACITY_EMOJI = Set.new(%w[red_circle]).freeze
- OOO_EMOJI = Set.new(%w[
- palm_tree
- beach beach_umbrella beach_with_umbrella
- ]).freeze
+ attr_reader :username, :name, :role, :projects, :available, :tz_offset_hours
+ # The options data are produced by https://gitlab.com/gitlab-org/gitlab-roulette/-/blob/master/lib/team_member.rb
def initialize(options = {})
@username = options['username']
- @name = options['name'] || @username
+ @name = options['name']
+ @markdown_name = options['markdown_name']
@role = options['role']
@projects = options['projects']
- end
-
- def markdown_name
- "[#{name}](https://gitlab.com/#{username}) (`@#{username}`)"
+ @available = options['available']
+ @tz_offset_hours = options['tz_offset_hours']
end
def in_project?(name)
@@ -43,40 +34,47 @@ module Gitlab
has_capability?(project, category, :maintainer, labels)
end
- def status
- return @status if defined?(@status)
+ def markdown_name(timezone_experiment: false, author: nil)
+ return @markdown_name unless timezone_experiment
- @status ||=
- begin
- Gitlab::Danger::RequestHelper.http_get_json(status_api_endpoint)
- rescue Gitlab::Danger::RequestHelper::HTTPError, JSON::ParserError
- nil # better no status than a crashing Danger
- end
+ "#{@markdown_name} (#{utc_offset_text(author)})"
end
- # @return [Boolean]
- def available?
- !out_of_office? && has_capacity?
+ def local_hour
+ (Time.now.utc + tz_offset_hours * 3600).hour
end
- private
+ protected
- def status_api_endpoint
- "https://gitlab.com/api/v4/users/#{CGI.escape(username)}/status"
- end
+ def floored_offset_hours
+ floored_offset = tz_offset_hours.floor(0)
- def status_emoji
- status&.dig("emoji")
+ floored_offset == tz_offset_hours ? floored_offset : tz_offset_hours
end
- # @return [Boolean]
- def out_of_office?
- status&.dig("message")&.match?(/OOO/i) || OOO_EMOJI.include?(status_emoji)
+ private
+
+ def utc_offset_text(author = nil)
+ offset_text =
+ if floored_offset_hours >= 0
+ "UTC+#{floored_offset_hours}"
+ else
+ "UTC#{floored_offset_hours}"
+ end
+
+ return offset_text unless author
+
+ "#{offset_text}, #{offset_diff_compared_to_author(author)}"
end
- # @return [Boolean]
- def has_capacity?
- !AT_CAPACITY_EMOJI.include?(status_emoji)
+ def offset_diff_compared_to_author(author)
+ diff = floored_offset_hours - author.floored_offset_hours
+ return "same timezone as `@#{author.username}`" if diff.zero?
+
+ ahead_or_behind = diff < 0 ? 'behind' : 'ahead'
+ pluralized_hours = pluralize(diff.abs, 'hour', 'hours')
+
+ "#{pluralized_hours} #{ahead_or_behind} `@#{author.username}`"
end
def has_capability?(project, category, kind, labels)
@@ -98,6 +96,12 @@ module Gitlab
def capabilities(project)
Array(projects.fetch(project, []))
end
+
+ def pluralize(count, singular, plural)
+ word = count == 1 || count.to_s =~ /^1(\.0+)?$/ ? singular : plural
+
+ "#{count || 0} #{word}"
+ end
end
end
end
diff --git a/lib/gitlab/database.rb b/lib/gitlab/database.rb
index 02005be1f6a..2bfb6c32886 100644
--- a/lib/gitlab/database.rb
+++ b/lib/gitlab/database.rb
@@ -4,6 +4,20 @@ module Gitlab
module Database
include Gitlab::Metrics::Methods
+ # Minimum PostgreSQL version requirement per documentation:
+ # https://docs.gitlab.com/ee/install/requirements.html#postgresql-requirements
+ MINIMUM_POSTGRES_VERSION = 11
+
+ # Upcoming PostgreSQL version requirements
+ # Allows a soft warning about an upcoming minimum version requirement
+ # so administrators can prepare to upgrade
+ UPCOMING_POSTGRES_VERSION_DETAILS = {
+ gl_version: '13.6.0',
+ gl_version_date: 'November 2020',
+ pg_version_minimum: 12,
+ url: 'https://gitlab.com/groups/gitlab-org/-/epics/2374'
+ }.freeze
+
# https://www.postgresql.org/docs/9.2/static/datatype-numeric.html
MAX_INT_VALUE = 2147483647
@@ -22,6 +36,16 @@ module Gitlab
MIN_SCHEMA_VERSION = 20190506135400
MIN_SCHEMA_GITLAB_VERSION = '11.11.0'
+ # Schema we store dynamically managed partitions in (e.g. for time partitioning)
+ DYNAMIC_PARTITIONS_SCHEMA = :gitlab_partitions_dynamic
+
+ # Schema we store static partitions in (e.g. for hash partitioning)
+ STATIC_PARTITIONS_SCHEMA = :gitlab_partitions_static
+
+ # This is an extensive list of postgres schemas owned by GitLab
+ # It does not include the default public schema
+ EXTRA_SCHEMAS = [DYNAMIC_PARTITIONS_SCHEMA, STATIC_PARTITIONS_SCHEMA].freeze
+
define_histogram :gitlab_database_transaction_seconds do
docstring "Time spent in database transactions, in seconds"
end
@@ -87,16 +111,39 @@ module Gitlab
version.to_f < 10
end
- def self.replication_slots_supported?
- version.to_f >= 9.4
+ def self.postgresql_minimum_supported_version?
+ version.to_f >= MINIMUM_POSTGRES_VERSION
end
- def self.postgresql_minimum_supported_version?
- version.to_f >= 9.6
+ def self.postgresql_upcoming_deprecation?
+ version.to_f < UPCOMING_POSTGRES_VERSION_DETAILS[:pg_version_minimum]
end
- def self.upsert_supported?
- version.to_f >= 9.5
+ def self.check_postgres_version_and_print_warning
+ return if Gitlab::Database.postgresql_minimum_supported_version?
+ return if Gitlab::Runtime.rails_runner?
+
+ Kernel.warn ERB.new(Rainbow.new.wrap(<<~EOS).red).result
+
+ ██†██†█████†██████†███†██â€â–ˆâ–ˆâ€â–ˆâ–ˆâ–ˆâ€ ██†██████â€
+ ██†██â€â–ˆâ–ˆâ€â€â€â–ˆâ–ˆâ€â–ˆâ–ˆâ€â€â€â–ˆâ–ˆâ€â–ˆâ–ˆâ–ˆâ–ˆâ€ ██â€â–ˆâ–ˆâ€â–ˆâ–ˆâ–ˆâ–ˆâ€ ██â€â–ˆâ–ˆâ€â€â€â€â€â€
+ ██†█†██â€â–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ€â–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ€â€â–ˆâ–ˆâ€â–ˆâ–ˆâ€ ██â€â–ˆâ–ˆâ€â–ˆâ–ˆâ€â–ˆâ–ˆâ€ ██â€â–ˆâ–ˆâ€ ███â€
+ ██â€â–ˆâ–ˆâ–ˆâ€â–ˆâ–ˆâ€â–ˆâ–ˆâ€â€â€â–ˆâ–ˆâ€â–ˆâ–ˆâ€â€â€â–ˆâ–ˆâ€â–ˆâ–ˆâ€â€â–ˆâ–ˆâ€â–ˆâ–ˆâ€â–ˆâ–ˆâ€â–ˆâ–ˆâ€â€â–ˆâ–ˆâ€â–ˆâ–ˆâ€â–ˆâ–ˆâ€ ██â€
+ â€â–ˆâ–ˆâ–ˆâ€â–ˆâ–ˆâ–ˆâ€â€â–ˆâ–ˆâ€ ██â€â–ˆâ–ˆâ€ ██â€â–ˆâ–ˆâ€ â€â–ˆâ–ˆâ–ˆâ–ˆâ€â–ˆâ–ˆâ€â–ˆâ–ˆâ€ â€â–ˆâ–ˆâ–ˆâ–ˆâ€â€â–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ€â€
+
+ ******************************************************************************
+ You are using PostgreSQL <%= Gitlab::Database.version %>, but PostgreSQL >= <%= Gitlab::Database::MINIMUM_POSTGRES_VERSION %>
+ is required for this version of GitLab.
+ <% if Rails.env.development? || Rails.env.test? %>
+ If using gitlab-development-kit, please find the relevant steps here:
+ https://gitlab.com/gitlab-org/gitlab-development-kit/-/blob/master/doc/howto/postgresql.md#upgrade-postgresql
+ <% end %>
+ Please upgrade your environment to a supported PostgreSQL version, see
+ https://docs.gitlab.com/ee/install/requirements.html#database for details.
+ ******************************************************************************
+ EOS
+ rescue ActiveRecord::ActiveRecordError, PG::Error
+ # ignore - happens when Rake tasks yet have to create a database, e.g. for testing
end
# map some of the function names that changed between PostgreSQL 9 and 10
@@ -182,9 +229,7 @@ module Gitlab
VALUES #{tuples.map { |tuple| "(#{tuple.join(', ')})" }.join(', ')}
EOF
- if upsert_supported? && on_conflict == :do_nothing
- sql = "#{sql} ON CONFLICT DO NOTHING"
- end
+ sql = "#{sql} ON CONFLICT DO NOTHING" if on_conflict == :do_nothing
sql = "#{sql} RETURNING id" if return_ids
diff --git a/lib/gitlab/database/background_migration_job.rb b/lib/gitlab/database/background_migration_job.rb
new file mode 100644
index 00000000000..445735b232a
--- /dev/null
+++ b/lib/gitlab/database/background_migration_job.rb
@@ -0,0 +1,38 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Database
+ class BackgroundMigrationJob < ActiveRecord::Base # rubocop:disable Rails/ApplicationRecord
+ self.table_name = :background_migration_jobs
+
+ scope :for_migration_class, -> (class_name) { where(class_name: normalize_class_name(class_name)) }
+ scope :for_migration_execution, -> (class_name, arguments) do
+ for_migration_class(class_name).where('arguments = ?', arguments.to_json)
+ end
+
+ scope :for_partitioning_migration, -> (class_name, table_name) do
+ for_migration_class(class_name).where('arguments ->> 2 = ?', table_name)
+ end
+
+ enum status: {
+ pending: 0,
+ succeeded: 1
+ }
+
+ def self.mark_all_as_succeeded(class_name, arguments)
+ self.pending.for_migration_execution(class_name, arguments)
+ .update_all("status = #{statuses[:succeeded]}, updated_at = NOW()")
+ end
+
+ def self.normalize_class_name(class_name)
+ return class_name unless class_name.present? && class_name.start_with?('::')
+
+ class_name[2..]
+ end
+
+ def class_name=(value)
+ write_attribute(:class_name, self.class.normalize_class_name(value))
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/database/dynamic_model_helpers.rb b/lib/gitlab/database/dynamic_model_helpers.rb
new file mode 100644
index 00000000000..892f8291780
--- /dev/null
+++ b/lib/gitlab/database/dynamic_model_helpers.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Database
+ module DynamicModelHelpers
+ def define_batchable_model(table_name)
+ Class.new(ActiveRecord::Base) do
+ include EachBatch
+
+ self.table_name = table_name
+ self.inheritance_column = :_type_disabled
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/database/migration_helpers.rb b/lib/gitlab/database/migration_helpers.rb
index fd09c31e994..006a24da8fe 100644
--- a/lib/gitlab/database/migration_helpers.rb
+++ b/lib/gitlab/database/migration_helpers.rb
@@ -3,10 +3,10 @@
module Gitlab
module Database
module MigrationHelpers
+ include Migrations::BackgroundMigrationHelpers
+
# https://www.postgresql.org/docs/current/sql-syntax-lexical.html#SQL-SYNTAX-IDENTIFIERS
MAX_IDENTIFIER_NAME_LENGTH = 63
- BACKGROUND_MIGRATION_BATCH_SIZE = 1000 # Number of rows to process per job
- BACKGROUND_MIGRATION_JOB_BUFFER_SIZE = 1000 # Number of jobs to bulk queue at a time
PERMITTED_TIMESTAMP_COLUMNS = %i[created_at updated_at deleted_at].to_set.freeze
DEFAULT_TIMESTAMP_COLUMNS = %i[created_at updated_at].freeze
@@ -136,6 +136,10 @@ module Gitlab
'in the body of your migration class'
end
+ index_name = index_name[:name] if index_name.is_a?(Hash)
+
+ raise 'remove_concurrent_index_by_name must get an index name as the second argument' if index_name.blank?
+
options = options.merge({ algorithm: :concurrently })
unless index_exists_by_name?(table_name, index_name)
@@ -477,7 +481,7 @@ module Gitlab
# type is used.
# batch_column_name - option is for tables without primary key, in this
# case another unique integer column can be used. Example: :user_id
- def rename_column_concurrently(table, old, new, type: nil, batch_column_name: :id)
+ def rename_column_concurrently(table, old, new, type: nil, type_cast_function: nil, batch_column_name: :id)
unless column_exists?(table, batch_column_name)
raise "Column #{batch_column_name} does not exist on #{table}"
end
@@ -488,7 +492,7 @@ module Gitlab
check_trigger_permissions!(table)
- create_column_from(table, old, new, type: type, batch_column_name: batch_column_name)
+ create_column_from(table, old, new, type: type, batch_column_name: batch_column_name, type_cast_function: type_cast_function)
install_rename_triggers(table, old, new)
end
@@ -536,10 +540,10 @@ module Gitlab
# table - The table containing the column.
# column - The name of the column to change.
# new_type - The new column type.
- def change_column_type_concurrently(table, column, new_type)
+ def change_column_type_concurrently(table, column, new_type, type_cast_function: nil)
temp_column = "#{column}_for_type_change"
- rename_column_concurrently(table, column, temp_column, type: new_type)
+ rename_column_concurrently(table, column, temp_column, type: new_type, type_cast_function: type_cast_function)
end
# Performs cleanup of a concurrent type change.
@@ -786,10 +790,6 @@ module Gitlab
end
end
- def perform_background_migration_inline?
- Rails.env.test? || Rails.env.development?
- end
-
# Performs a concurrent column rename when using PostgreSQL.
def install_rename_triggers_for_postgresql(trigger, table, old, new)
execute <<-EOF.strip_heredoc
@@ -973,106 +973,6 @@ into similar problems in the future (e.g. when new tables are created).
end
end
- # Bulk queues background migration jobs for an entire table, batched by ID range.
- # "Bulk" meaning many jobs will be pushed at a time for efficiency.
- # If you need a delay interval per job, then use `queue_background_migration_jobs_by_range_at_intervals`.
- #
- # model_class - The table being iterated over
- # job_class_name - The background migration job class as a string
- # batch_size - The maximum number of rows per job
- #
- # Example:
- #
- # class Route < ActiveRecord::Base
- # include EachBatch
- # self.table_name = 'routes'
- # end
- #
- # bulk_queue_background_migration_jobs_by_range(Route, 'ProcessRoutes')
- #
- # Where the model_class includes EachBatch, and the background migration exists:
- #
- # class Gitlab::BackgroundMigration::ProcessRoutes
- # def perform(start_id, end_id)
- # # do something
- # end
- # end
- def bulk_queue_background_migration_jobs_by_range(model_class, job_class_name, batch_size: BACKGROUND_MIGRATION_BATCH_SIZE)
- raise "#{model_class} does not have an ID to use for batch ranges" unless model_class.column_names.include?('id')
-
- jobs = []
- table_name = model_class.quoted_table_name
-
- model_class.each_batch(of: batch_size) do |relation|
- start_id, end_id = relation.pluck("MIN(#{table_name}.id)", "MAX(#{table_name}.id)").first
-
- if jobs.length >= BACKGROUND_MIGRATION_JOB_BUFFER_SIZE
- # Note: This code path generally only helps with many millions of rows
- # We push multiple jobs at a time to reduce the time spent in
- # Sidekiq/Redis operations. We're using this buffer based approach so we
- # don't need to run additional queries for every range.
- bulk_migrate_async(jobs)
- jobs.clear
- end
-
- jobs << [job_class_name, [start_id, end_id]]
- end
-
- bulk_migrate_async(jobs) unless jobs.empty?
- end
-
- # Queues background migration jobs for an entire table, batched by ID range.
- # Each job is scheduled with a `delay_interval` in between.
- # If you use a small interval, then some jobs may run at the same time.
- #
- # model_class - The table or relation being iterated over
- # job_class_name - The background migration job class as a string
- # delay_interval - The duration between each job's scheduled time (must respond to `to_f`)
- # batch_size - The maximum number of rows per job
- # other_arguments - Other arguments to send to the job
- #
- # *Returns the final migration delay*
- #
- # Example:
- #
- # class Route < ActiveRecord::Base
- # include EachBatch
- # self.table_name = 'routes'
- # end
- #
- # queue_background_migration_jobs_by_range_at_intervals(Route, 'ProcessRoutes', 1.minute)
- #
- # Where the model_class includes EachBatch, and the background migration exists:
- #
- # class Gitlab::BackgroundMigration::ProcessRoutes
- # def perform(start_id, end_id)
- # # do something
- # end
- # end
- def queue_background_migration_jobs_by_range_at_intervals(model_class, job_class_name, delay_interval, batch_size: BACKGROUND_MIGRATION_BATCH_SIZE, other_job_arguments: [], initial_delay: 0)
- raise "#{model_class} does not have an ID to use for batch ranges" unless model_class.column_names.include?('id')
-
- # To not overload the worker too much we enforce a minimum interval both
- # when scheduling and performing jobs.
- if delay_interval < BackgroundMigrationWorker.minimum_interval
- delay_interval = BackgroundMigrationWorker.minimum_interval
- end
-
- final_delay = 0
-
- model_class.each_batch(of: batch_size) do |relation, index|
- start_id, end_id = relation.pluck(Arel.sql('MIN(id), MAX(id)')).first
-
- # `BackgroundMigrationWorker.bulk_perform_in` schedules all jobs for
- # the same time, which is not helpful in most cases where we wish to
- # spread the work over time.
- final_delay = initial_delay + delay_interval * index
- migrate_in(final_delay, job_class_name, [start_id, end_id] + other_job_arguments)
- end
-
- final_delay
- end
-
# Fetches indexes on a column by name for postgres.
#
# This will include indexes using an expression on the column, for example:
@@ -1131,30 +1031,6 @@ into similar problems in the future (e.g. when new tables are created).
execute(sql)
end
- def migrate_async(*args)
- with_migration_context do
- BackgroundMigrationWorker.perform_async(*args)
- end
- end
-
- def migrate_in(*args)
- with_migration_context do
- BackgroundMigrationWorker.perform_in(*args)
- end
- end
-
- def bulk_migrate_in(*args)
- with_migration_context do
- BackgroundMigrationWorker.bulk_perform_in(*args)
- end
- end
-
- def bulk_migrate_async(*args)
- with_migration_context do
- BackgroundMigrationWorker.bulk_perform_async(*args)
- end
- end
-
# Returns the name for a check constraint
#
# type:
@@ -1396,7 +1272,7 @@ into similar problems in the future (e.g. when new tables are created).
"ON DELETE #{on_delete.upcase}"
end
- def create_column_from(table, old, new, type: nil, batch_column_name: :id)
+ def create_column_from(table, old, new, type: nil, batch_column_name: :id, type_cast_function: nil)
old_col = column_for(table, old)
new_type = type || old_col.type
@@ -1410,7 +1286,13 @@ into similar problems in the future (e.g. when new tables are created).
# necessary since we copy over old values further down.
change_column_default(table, new, old_col.default) unless old_col.default.nil?
- update_column_in_batches(table, new, Arel::Table.new(table)[old], batch_column_name: batch_column_name)
+ old_value = Arel::Table.new(table)[old]
+
+ if type_cast_function.present?
+ old_value = Arel::Nodes::NamedFunction.new(type_cast_function, [old_value])
+ end
+
+ update_column_in_batches(table, new, old_value, batch_column_name: batch_column_name)
add_not_null_constraint(table, new) unless old_col.null
@@ -1437,10 +1319,6 @@ into similar problems in the future (e.g. when new tables are created).
your migration class
ERROR
end
-
- def with_migration_context(&block)
- Gitlab::ApplicationContext.with_context(caller_id: self.class.to_s, &block)
- end
end
end
end
diff --git a/lib/gitlab/database/migrations/background_migration_helpers.rb b/lib/gitlab/database/migrations/background_migration_helpers.rb
new file mode 100644
index 00000000000..a6cc03aa9eb
--- /dev/null
+++ b/lib/gitlab/database/migrations/background_migration_helpers.rb
@@ -0,0 +1,157 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Database
+ module Migrations
+ module BackgroundMigrationHelpers
+ BACKGROUND_MIGRATION_BATCH_SIZE = 1_000 # Number of rows to process per job
+ BACKGROUND_MIGRATION_JOB_BUFFER_SIZE = 1_000 # Number of jobs to bulk queue at a time
+
+ # Bulk queues background migration jobs for an entire table, batched by ID range.
+ # "Bulk" meaning many jobs will be pushed at a time for efficiency.
+ # If you need a delay interval per job, then use `queue_background_migration_jobs_by_range_at_intervals`.
+ #
+ # model_class - The table being iterated over
+ # job_class_name - The background migration job class as a string
+ # batch_size - The maximum number of rows per job
+ #
+ # Example:
+ #
+ # class Route < ActiveRecord::Base
+ # include EachBatch
+ # self.table_name = 'routes'
+ # end
+ #
+ # bulk_queue_background_migration_jobs_by_range(Route, 'ProcessRoutes')
+ #
+ # Where the model_class includes EachBatch, and the background migration exists:
+ #
+ # class Gitlab::BackgroundMigration::ProcessRoutes
+ # def perform(start_id, end_id)
+ # # do something
+ # end
+ # end
+ def bulk_queue_background_migration_jobs_by_range(model_class, job_class_name, batch_size: BACKGROUND_MIGRATION_BATCH_SIZE)
+ raise "#{model_class} does not have an ID to use for batch ranges" unless model_class.column_names.include?('id')
+
+ jobs = []
+ table_name = model_class.quoted_table_name
+
+ model_class.each_batch(of: batch_size) do |relation|
+ start_id, end_id = relation.pluck("MIN(#{table_name}.id)", "MAX(#{table_name}.id)").first
+
+ if jobs.length >= BACKGROUND_MIGRATION_JOB_BUFFER_SIZE
+ # Note: This code path generally only helps with many millions of rows
+ # We push multiple jobs at a time to reduce the time spent in
+ # Sidekiq/Redis operations. We're using this buffer based approach so we
+ # don't need to run additional queries for every range.
+ bulk_migrate_async(jobs)
+ jobs.clear
+ end
+
+ jobs << [job_class_name, [start_id, end_id]]
+ end
+
+ bulk_migrate_async(jobs) unless jobs.empty?
+ end
+
+ # Queues background migration jobs for an entire table, batched by ID range.
+ # Each job is scheduled with a `delay_interval` in between.
+ # If you use a small interval, then some jobs may run at the same time.
+ #
+ # model_class - The table or relation being iterated over
+ # job_class_name - The background migration job class as a string
+ # delay_interval - The duration between each job's scheduled time (must respond to `to_f`)
+ # batch_size - The maximum number of rows per job
+ # other_arguments - Other arguments to send to the job
+ # track_jobs - When this flag is set, creates a record in the background_migration_jobs table for each job that
+ # is scheduled to be run. These records can be used to trace execution of the background job, but there is no
+ # builtin support to manage that automatically at this time. You should only set this flag if you are aware of
+ # how it works, and intend to manually cleanup the database records in your background job.
+ #
+ # *Returns the final migration delay*
+ #
+ # Example:
+ #
+ # class Route < ActiveRecord::Base
+ # include EachBatch
+ # self.table_name = 'routes'
+ # end
+ #
+ # queue_background_migration_jobs_by_range_at_intervals(Route, 'ProcessRoutes', 1.minute)
+ #
+ # Where the model_class includes EachBatch, and the background migration exists:
+ #
+ # class Gitlab::BackgroundMigration::ProcessRoutes
+ # def perform(start_id, end_id)
+ # # do something
+ # end
+ # end
+ def queue_background_migration_jobs_by_range_at_intervals(model_class, job_class_name, delay_interval, batch_size: BACKGROUND_MIGRATION_BATCH_SIZE, other_job_arguments: [], initial_delay: 0, track_jobs: false)
+ raise "#{model_class} does not have an ID to use for batch ranges" unless model_class.column_names.include?('id')
+
+ # To not overload the worker too much we enforce a minimum interval both
+ # when scheduling and performing jobs.
+ if delay_interval < BackgroundMigrationWorker.minimum_interval
+ delay_interval = BackgroundMigrationWorker.minimum_interval
+ end
+
+ final_delay = 0
+
+ model_class.each_batch(of: batch_size) do |relation, index|
+ start_id, end_id = relation.pluck(Arel.sql('MIN(id), MAX(id)')).first
+
+ # `BackgroundMigrationWorker.bulk_perform_in` schedules all jobs for
+ # the same time, which is not helpful in most cases where we wish to
+ # spread the work over time.
+ final_delay = initial_delay + delay_interval * index
+ full_job_arguments = [start_id, end_id] + other_job_arguments
+
+ track_in_database(job_class_name, full_job_arguments) if track_jobs
+ migrate_in(final_delay, job_class_name, full_job_arguments)
+ end
+
+ final_delay
+ end
+
+ def perform_background_migration_inline?
+ Rails.env.test? || Rails.env.development?
+ end
+
+ def migrate_async(*args)
+ with_migration_context do
+ BackgroundMigrationWorker.perform_async(*args)
+ end
+ end
+
+ def migrate_in(*args)
+ with_migration_context do
+ BackgroundMigrationWorker.perform_in(*args)
+ end
+ end
+
+ def bulk_migrate_in(*args)
+ with_migration_context do
+ BackgroundMigrationWorker.bulk_perform_in(*args)
+ end
+ end
+
+ def bulk_migrate_async(*args)
+ with_migration_context do
+ BackgroundMigrationWorker.bulk_perform_async(*args)
+ end
+ end
+
+ def with_migration_context(&block)
+ Gitlab::ApplicationContext.with_context(caller_id: self.class.to_s, &block)
+ end
+
+ private
+
+ def track_in_database(class_name, arguments)
+ Gitlab::Database::BackgroundMigrationJob.create!(class_name: class_name, arguments: arguments)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/database/partitioning/monthly_strategy.rb b/lib/gitlab/database/partitioning/monthly_strategy.rb
new file mode 100644
index 00000000000..ecc05d9654a
--- /dev/null
+++ b/lib/gitlab/database/partitioning/monthly_strategy.rb
@@ -0,0 +1,96 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Database
+ module Partitioning
+ class MonthlyStrategy
+ attr_reader :model, :partitioning_key
+
+ # We create this many partitions in the future
+ HEADROOM = 6.months
+
+ delegate :table_name, to: :model
+
+ def initialize(model, partitioning_key)
+ @model = model
+ @partitioning_key = partitioning_key
+ end
+
+ def current_partitions
+ result = connection.select_all(<<~SQL)
+ select
+ pg_class.relname,
+ parent_class.relname as base_table,
+ pg_get_expr(pg_class.relpartbound, inhrelid) as condition
+ from pg_class
+ inner join pg_inherits i on pg_class.oid = inhrelid
+ inner join pg_class parent_class on parent_class.oid = inhparent
+ inner join pg_namespace ON pg_namespace.oid = pg_class.relnamespace
+ where pg_namespace.nspname = #{connection.quote(Gitlab::Database::DYNAMIC_PARTITIONS_SCHEMA)}
+ and parent_class.relname = #{connection.quote(table_name)}
+ and pg_class.relispartition
+ order by pg_class.relname
+ SQL
+
+ result.map do |record|
+ TimePartition.from_sql(table_name, record['relname'], record['condition'])
+ end
+ end
+
+ # Check the currently existing partitions and determine which ones are missing
+ def missing_partitions
+ desired_partitions - current_partitions
+ end
+
+ private
+
+ def desired_partitions
+ [].tap do |parts|
+ min_date, max_date = relevant_range
+
+ parts << partition_for(upper_bound: min_date)
+
+ while min_date < max_date
+ next_date = min_date.next_month
+
+ parts << partition_for(lower_bound: min_date, upper_bound: next_date)
+
+ min_date = next_date
+ end
+ end
+ end
+
+ # This determines the relevant time range for which we expect to have data
+ # (and therefore need to create partitions for).
+ #
+ # Note: We typically expect the first partition to be half-unbounded, i.e.
+ # to start from MINVALUE to a specific date `x`. The range returned
+ # does not include the range of the first, half-unbounded partition.
+ def relevant_range
+ if first_partition = current_partitions.min
+ # Case 1: First partition starts with MINVALUE, i.e. from is nil -> start with first real partition
+ # Case 2: Rather unexpectedly, first partition does not start with MINVALUE, i.e. from is not nil
+ # In this case, use first partition beginning as a start
+ min_date = first_partition.from || first_partition.to
+ end
+
+ # In case we don't have a partition yet
+ min_date ||= Date.today
+ min_date = min_date.beginning_of_month
+
+ max_date = Date.today.end_of_month + HEADROOM
+
+ [min_date, max_date]
+ end
+
+ def partition_for(lower_bound: nil, upper_bound:)
+ TimePartition.new(table_name, lower_bound, upper_bound)
+ end
+
+ def connection
+ ActiveRecord::Base.connection
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/database/partitioning/partition_creator.rb b/lib/gitlab/database/partitioning/partition_creator.rb
new file mode 100644
index 00000000000..348dd1ba660
--- /dev/null
+++ b/lib/gitlab/database/partitioning/partition_creator.rb
@@ -0,0 +1,87 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Database
+ module Partitioning
+ class PartitionCreator
+ def self.register(model)
+ raise ArgumentError, "Only models with a #partitioning_strategy can be registered." unless model.respond_to?(:partitioning_strategy)
+
+ models << model
+ end
+
+ def self.models
+ @models ||= Set.new
+ end
+
+ LEASE_TIMEOUT = 1.minute
+ LEASE_KEY = 'database_partition_creation_%s'
+
+ attr_reader :models
+
+ def initialize(models = self.class.models)
+ @models = models
+ end
+
+ def create_partitions
+ return unless Feature.enabled?(:postgres_dynamic_partition_creation, default_enabled: true)
+
+ models.each do |model|
+ # Double-checking before getting the lease:
+ # The prevailing situation is no missing partitions
+ next if missing_partitions(model).empty?
+
+ only_with_exclusive_lease(model) do
+ partitions_to_create = missing_partitions(model)
+
+ next if partitions_to_create.empty?
+
+ create(model, partitions_to_create)
+ end
+ rescue => e
+ Gitlab::AppLogger.error("Failed to create partition(s) for #{model.table_name}: #{e.class}: #{e.message}")
+ end
+ end
+
+ private
+
+ def missing_partitions(model)
+ return [] unless connection.table_exists?(model.table_name)
+
+ model.partitioning_strategy.missing_partitions
+ end
+
+ def only_with_exclusive_lease(model)
+ lease = Gitlab::ExclusiveLease.new(LEASE_KEY % model.table_name, timeout: LEASE_TIMEOUT)
+
+ yield if lease.try_obtain
+ ensure
+ lease&.cancel
+ end
+
+ def create(model, partitions)
+ connection.transaction do
+ with_lock_retries do
+ partitions.each do |partition|
+ connection.execute partition.to_sql
+
+ Gitlab::AppLogger.info("Created partition #{partition.partition_name} for table #{partition.table}")
+ end
+ end
+ end
+ end
+
+ def with_lock_retries(&block)
+ Gitlab::Database::WithLockRetries.new({
+ klass: self.class,
+ logger: Gitlab::AppLogger
+ }).run(&block)
+ end
+
+ def connection
+ ActiveRecord::Base.connection
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/database/partitioning/time_partition.rb b/lib/gitlab/database/partitioning/time_partition.rb
new file mode 100644
index 00000000000..7dca60c0854
--- /dev/null
+++ b/lib/gitlab/database/partitioning/time_partition.rb
@@ -0,0 +1,84 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Database
+ module Partitioning
+ class TimePartition
+ include Comparable
+
+ def self.from_sql(table, partition_name, definition)
+ matches = definition.match(/FOR VALUES FROM \('?(?<from>.+)'?\) TO \('?(?<to>.+)'?\)/)
+
+ raise ArgumentError, "Unknown partition definition: #{definition}" unless matches
+
+ raise NotImplementedError, "Open-end time partitions with MAXVALUE are not supported yet" if matches[:to] == 'MAXVALUE'
+
+ from = matches[:from] == 'MINVALUE' ? nil : matches[:from]
+ to = matches[:to]
+
+ new(table, from, to, partition_name: partition_name)
+ end
+
+ attr_reader :table, :from, :to
+
+ def initialize(table, from, to, partition_name: nil)
+ @table = table.to_s
+ @from = date_or_nil(from)
+ @to = date_or_nil(to)
+ @partition_name = partition_name
+ end
+
+ def partition_name
+ return @partition_name if @partition_name
+
+ suffix = from&.strftime('%Y%m') || '000000'
+
+ "#{table}_#{suffix}"
+ end
+
+ def to_sql
+ from_sql = from ? conn.quote(from.strftime('%Y-%m-%d')) : 'MINVALUE'
+ to_sql = conn.quote(to.strftime('%Y-%m-%d'))
+
+ <<~SQL
+ CREATE TABLE IF NOT EXISTS #{fully_qualified_partition}
+ PARTITION OF #{conn.quote_table_name(table)}
+ FOR VALUES FROM (#{from_sql}) TO (#{to_sql})
+ SQL
+ end
+
+ def ==(other)
+ table == other.table && partition_name == other.partition_name && from == other.from && to == other.to
+ end
+ alias_method :eql?, :==
+
+ def hash
+ [table, partition_name, from, to].hash
+ end
+
+ def <=>(other)
+ return if table != other.table
+
+ partition_name <=> other.partition_name
+ end
+
+ private
+
+ def date_or_nil(obj)
+ return unless obj
+ return obj if obj.is_a?(Date)
+
+ Date.parse(obj)
+ end
+
+ def fully_qualified_partition
+ "%s.%s" % [conn.quote_table_name(Gitlab::Database::DYNAMIC_PARTITIONS_SCHEMA), conn.quote_table_name(partition_name)]
+ end
+
+ def conn
+ @conn ||= ActiveRecord::Base.connection
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/database/partitioning_migration_helpers/backfill_partitioned_table.rb b/lib/gitlab/database/partitioning_migration_helpers/backfill_partitioned_table.rb
new file mode 100644
index 00000000000..f9ad1e60776
--- /dev/null
+++ b/lib/gitlab/database/partitioning_migration_helpers/backfill_partitioned_table.rb
@@ -0,0 +1,105 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Database
+ module PartitioningMigrationHelpers
+ # Class that will generically copy data from a given table into its corresponding partitioned table
+ class BackfillPartitionedTable
+ include ::Gitlab::Database::DynamicModelHelpers
+
+ SUB_BATCH_SIZE = 2_500
+ PAUSE_SECONDS = 0.25
+
+ def perform(start_id, stop_id, source_table, partitioned_table, source_column)
+ return unless Feature.enabled?(:backfill_partitioned_audit_events, default_enabled: true)
+
+ if transaction_open?
+ raise "Aborting job to backfill partitioned #{source_table} table! Do not run this job in a transaction block!"
+ end
+
+ unless table_exists?(partitioned_table)
+ logger.warn "exiting backfill migration because partitioned table #{partitioned_table} does not exist. " \
+ "This could be due to the migration being rolled back after migration jobs were enqueued in sidekiq"
+ return
+ end
+
+ bulk_copy = BulkCopy.new(source_table, partitioned_table, source_column)
+ parent_batch_relation = relation_scoped_to_range(source_table, source_column, start_id, stop_id)
+
+ parent_batch_relation.each_batch(of: SUB_BATCH_SIZE) do |sub_batch|
+ sub_start_id, sub_stop_id = sub_batch.pluck(Arel.sql("MIN(#{source_column}), MAX(#{source_column})")).first
+
+ bulk_copy.copy_between(sub_start_id, sub_stop_id)
+ sleep(PAUSE_SECONDS)
+ end
+
+ mark_jobs_as_succeeded(start_id, stop_id, source_table, partitioned_table, source_column)
+ end
+
+ private
+
+ def connection
+ ActiveRecord::Base.connection
+ end
+
+ def transaction_open?
+ connection.transaction_open?
+ end
+
+ def table_exists?(table)
+ connection.table_exists?(table)
+ end
+
+ def logger
+ @logger ||= ::Gitlab::BackgroundMigration::Logger.build
+ end
+
+ def relation_scoped_to_range(source_table, source_key_column, start_id, stop_id)
+ define_batchable_model(source_table).where(source_key_column => start_id..stop_id)
+ end
+
+ def mark_jobs_as_succeeded(*arguments)
+ BackgroundMigrationJob.mark_all_as_succeeded(self.class.name, arguments)
+ end
+
+ # Helper class to copy data between two tables via upserts
+ class BulkCopy
+ DELIMITER = ', '
+
+ attr_reader :source_table, :destination_table, :source_column
+
+ def initialize(source_table, destination_table, source_column)
+ @source_table = source_table
+ @destination_table = destination_table
+ @source_column = source_column
+ end
+
+ def copy_between(start_id, stop_id)
+ connection.execute(<<~SQL)
+ INSERT INTO #{destination_table} (#{column_listing})
+ SELECT #{column_listing}
+ FROM #{source_table}
+ WHERE #{source_column} BETWEEN #{start_id} AND #{stop_id}
+ FOR UPDATE
+ ON CONFLICT (#{conflict_targets}) DO NOTHING
+ SQL
+ end
+
+ private
+
+ def connection
+ @connection ||= ActiveRecord::Base.connection
+ end
+
+ def column_listing
+ @column_listing ||= connection.columns(source_table).map(&:name).join(DELIMITER)
+ end
+
+ def conflict_targets
+ connection.primary_key(destination_table).join(DELIMITER)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/database/partitioning_migration_helpers/foreign_key_helpers.rb b/lib/gitlab/database/partitioning_migration_helpers/foreign_key_helpers.rb
index 9e687009cd7..1fb9476b7d9 100644
--- a/lib/gitlab/database/partitioning_migration_helpers/foreign_key_helpers.rb
+++ b/lib/gitlab/database/partitioning_migration_helpers/foreign_key_helpers.rb
@@ -99,7 +99,7 @@ module Gitlab
drop_function(fn_name, if_exists: true)
else
create_or_replace_fk_function(fn_name, final_keys)
- create_trigger(trigger_name, fn_name, fires: "AFTER DELETE ON #{to_table}")
+ create_trigger(to_table, trigger_name, fn_name, fires: 'AFTER DELETE')
end
end
end
diff --git a/lib/gitlab/database/partitioning_migration_helpers/table_management_helpers.rb b/lib/gitlab/database/partitioning_migration_helpers/table_management_helpers.rb
index f77fbe98df1..b676767f41d 100644
--- a/lib/gitlab/database/partitioning_migration_helpers/table_management_helpers.rb
+++ b/lib/gitlab/database/partitioning_migration_helpers/table_management_helpers.rb
@@ -5,10 +5,16 @@ module Gitlab
module PartitioningMigrationHelpers
module TableManagementHelpers
include ::Gitlab::Database::SchemaHelpers
+ include ::Gitlab::Database::DynamicModelHelpers
+ include ::Gitlab::Database::Migrations::BackgroundMigrationHelpers
- WHITELISTED_TABLES = %w[audit_events].freeze
+ ALLOWED_TABLES = %w[audit_events].freeze
ERROR_SCOPE = 'table partitioning'
+ MIGRATION_CLASS_NAME = "::#{module_parent_name}::BackfillPartitionedTable"
+ BATCH_INTERVAL = 2.minutes.freeze
+ BATCH_SIZE = 50_000
+
# Creates a partitioned copy of an existing table, using a RANGE partitioning strategy on a timestamp column.
# One partition is created per month between the given `min_date` and `max_date`.
#
@@ -18,14 +24,25 @@ module Gitlab
#
# partition_table_by_date :audit_events, :created_at, min_date: Date.new(2020, 1), max_date: Date.new(2020, 6)
#
- # Required options are:
+ # Options are:
# :min_date - a date specifying the lower bounds of the partition range
- # :max_date - a date specifying the upper bounds of the partitioning range
+ # :max_date - a date specifying the upper bounds of the partitioning range, defaults to today + 1 month
#
- def partition_table_by_date(table_name, column_name, min_date:, max_date:)
- assert_table_is_whitelisted(table_name)
+ # Unless min_date is specified explicitly, we default to
+ # 1. The minimum value for the partitioning column in the table
+ # 2. If no data is present yet, the current month
+ def partition_table_by_date(table_name, column_name, min_date: nil, max_date: nil)
+ assert_table_is_allowed(table_name)
+
assert_not_in_transaction_block(scope: ERROR_SCOPE)
+ max_date ||= Date.today + 1.month
+
+ min_date ||= connection.select_one(<<~SQL)['minimum'] || max_date - 1.month
+ SELECT date_trunc('MONTH', MIN(#{column_name})) AS minimum
+ FROM #{table_name}
+ SQL
+
raise "max_date #{max_date} must be greater than min_date #{min_date}" if min_date >= max_date
primary_key = connection.primary_key(table_name)
@@ -34,10 +51,12 @@ module Gitlab
partition_column = find_column_definition(table_name, column_name)
raise "partition column #{column_name} does not exist on #{table_name}" if partition_column.nil?
- new_table_name = partitioned_table_name(table_name)
- create_range_partitioned_copy(new_table_name, table_name, partition_column, primary_key)
- create_daterange_partitions(new_table_name, partition_column.name, min_date, max_date)
- create_sync_trigger(table_name, new_table_name, primary_key)
+ partitioned_table_name = make_partitioned_table_name(table_name)
+
+ create_range_partitioned_copy(table_name, partitioned_table_name, partition_column, primary_key)
+ create_daterange_partitions(partitioned_table_name, partition_column.name, min_date, max_date)
+ create_trigger_to_sync_tables(table_name, partitioned_table_name, primary_key)
+ enqueue_background_migration(table_name, partitioned_table_name, primary_key)
end
# Clean up a partitioned copy of an existing table. This deletes the partitioned table and all partitions.
@@ -47,39 +66,58 @@ module Gitlab
# drop_partitioned_table_for :audit_events
#
def drop_partitioned_table_for(table_name)
- assert_table_is_whitelisted(table_name)
+ assert_table_is_allowed(table_name)
assert_not_in_transaction_block(scope: ERROR_SCOPE)
+ cleanup_migration_jobs(table_name)
+
with_lock_retries do
- trigger_name = sync_trigger_name(table_name)
+ trigger_name = make_sync_trigger_name(table_name)
drop_trigger(table_name, trigger_name)
end
- function_name = sync_function_name(table_name)
+ function_name = make_sync_function_name(table_name)
drop_function(function_name)
- part_table_name = partitioned_table_name(table_name)
- drop_table(part_table_name)
+ partitioned_table_name = make_partitioned_table_name(table_name)
+ drop_table(partitioned_table_name)
+ end
+
+ def create_hash_partitions(table_name, number_of_partitions)
+ transaction do
+ (0..number_of_partitions - 1).each do |partition|
+ decimals = Math.log10(number_of_partitions).ceil
+ suffix = "%0#{decimals}d" % partition
+ partition_name = "#{table_name}_#{suffix}"
+ schema = Gitlab::Database::STATIC_PARTITIONS_SCHEMA
+
+ execute(<<~SQL)
+ CREATE TABLE #{schema}.#{partition_name}
+ PARTITION OF #{table_name}
+ FOR VALUES WITH (MODULUS #{number_of_partitions}, REMAINDER #{partition});
+ SQL
+ end
+ end
end
private
- def assert_table_is_whitelisted(table_name)
- return if WHITELISTED_TABLES.include?(table_name.to_s)
+ def assert_table_is_allowed(table_name)
+ return if ALLOWED_TABLES.include?(table_name.to_s)
- raise "partitioning helpers are in active development, and #{table_name} is not whitelisted for use, " \
+ raise "partitioning helpers are in active development, and #{table_name} is not allowed for use, " \
"for more information please contact the database team"
end
- def partitioned_table_name(table)
+ def make_partitioned_table_name(table)
tmp_table_name("#{table}_part")
end
- def sync_function_name(table)
+ def make_sync_function_name(table)
object_name(table, 'table_sync_function')
end
- def sync_trigger_name(table)
+ def make_sync_trigger_name(table)
object_name(table, 'table_sync_trigger')
end
@@ -87,11 +125,11 @@ module Gitlab
connection.columns(table).find { |c| c.name == column.to_s }
end
- def create_range_partitioned_copy(table_name, template_table_name, partition_column, primary_key)
- if table_exists?(table_name)
+ def create_range_partitioned_copy(source_table_name, partitioned_table_name, partition_column, primary_key)
+ if table_exists?(partitioned_table_name)
# rubocop:disable Gitlab/RailsLogger
Rails.logger.warn "Partitioned table not created because it already exists" \
- " (this may be due to an aborted migration or similar): table_name: #{table_name} "
+ " (this may be due to an aborted migration or similar): table_name: #{partitioned_table_name} "
# rubocop:enable Gitlab/RailsLogger
return
end
@@ -99,20 +137,20 @@ module Gitlab
tmp_column_name = object_name(partition_column.name, 'partition_key')
transaction do
execute(<<~SQL)
- CREATE TABLE #{table_name} (
- LIKE #{template_table_name} INCLUDING ALL EXCLUDING INDEXES,
+ CREATE TABLE #{partitioned_table_name} (
+ LIKE #{source_table_name} INCLUDING ALL EXCLUDING INDEXES,
#{tmp_column_name} #{partition_column.sql_type} NOT NULL,
PRIMARY KEY (#{[primary_key, tmp_column_name].join(", ")})
) PARTITION BY RANGE (#{tmp_column_name})
SQL
- remove_column(table_name, partition_column.name)
- rename_column(table_name, tmp_column_name, partition_column.name)
- change_column_default(table_name, primary_key, nil)
+ remove_column(partitioned_table_name, partition_column.name)
+ rename_column(partitioned_table_name, tmp_column_name, partition_column.name)
+ change_column_default(partitioned_table_name, primary_key, nil)
- if column_of_type?(table_name, primary_key, :integer)
+ if column_of_type?(partitioned_table_name, primary_key, :integer)
# Default to int8 primary keys to prevent overflow
- change_column(table_name, primary_key, :bigint)
+ change_column(partitioned_table_name, primary_key, :bigint)
end
end
end
@@ -125,7 +163,8 @@ module Gitlab
min_date = min_date.beginning_of_month.to_date
max_date = max_date.next_month.beginning_of_month.to_date
- create_range_partition_safely("#{table_name}_000000", table_name, 'MINVALUE', to_sql_date_literal(min_date))
+ upper_bound = to_sql_date_literal(min_date)
+ create_range_partition_safely("#{table_name}_000000", table_name, 'MINVALUE', upper_bound)
while min_date < max_date
partition_name = "#{table_name}_#{min_date.strftime('%Y%m')}"
@@ -143,7 +182,7 @@ module Gitlab
end
def create_range_partition_safely(partition_name, table_name, lower_bound, upper_bound)
- if table_exists?(partition_name)
+ if table_exists?(table_for_range_partition(partition_name))
# rubocop:disable Gitlab/RailsLogger
Rails.logger.warn "Partition not created because it already exists" \
" (this may be due to an aborted migration or similar): partition_name: #{partition_name}"
@@ -154,34 +193,42 @@ module Gitlab
create_range_partition(partition_name, table_name, lower_bound, upper_bound)
end
- def create_sync_trigger(source_table, target_table, unique_key)
- function_name = sync_function_name(source_table)
- trigger_name = sync_trigger_name(source_table)
+ def create_trigger_to_sync_tables(source_table_name, partitioned_table_name, unique_key)
+ function_name = make_sync_function_name(source_table_name)
+ trigger_name = make_sync_trigger_name(source_table_name)
with_lock_retries do
- create_sync_function(function_name, target_table, unique_key)
- create_comment('FUNCTION', function_name, "Partitioning migration: table sync for #{source_table} table")
+ create_sync_function(function_name, partitioned_table_name, unique_key)
+ create_comment('FUNCTION', function_name, "Partitioning migration: table sync for #{source_table_name} table")
- create_trigger(trigger_name, function_name, fires: "AFTER INSERT OR UPDATE OR DELETE ON #{source_table}")
+ create_sync_trigger(source_table_name, trigger_name, function_name)
end
end
- def create_sync_function(name, target_table, unique_key)
+ def create_sync_function(name, partitioned_table_name, unique_key)
+ if function_exists?(name)
+ # rubocop:disable Gitlab/RailsLogger
+ Rails.logger.warn "Partitioning sync function not created because it already exists" \
+ " (this may be due to an aborted migration or similar): function name: #{name}"
+ # rubocop:enable Gitlab/RailsLogger
+ return
+ end
+
delimiter = ",\n "
- column_names = connection.columns(target_table).map(&:name)
+ column_names = connection.columns(partitioned_table_name).map(&:name)
set_statements = build_set_statements(column_names, unique_key)
insert_values = column_names.map { |name| "NEW.#{name}" }
create_trigger_function(name, replace: false) do
<<~SQL
IF (TG_OP = 'DELETE') THEN
- DELETE FROM #{target_table} where #{unique_key} = OLD.#{unique_key};
+ DELETE FROM #{partitioned_table_name} where #{unique_key} = OLD.#{unique_key};
ELSIF (TG_OP = 'UPDATE') THEN
- UPDATE #{target_table}
+ UPDATE #{partitioned_table_name}
SET #{set_statements.join(delimiter)}
- WHERE #{target_table}.#{unique_key} = NEW.#{unique_key};
+ WHERE #{partitioned_table_name}.#{unique_key} = NEW.#{unique_key};
ELSIF (TG_OP = 'INSERT') THEN
- INSERT INTO #{target_table} (#{column_names.join(delimiter)})
+ INSERT INTO #{partitioned_table_name} (#{column_names.join(delimiter)})
VALUES (#{insert_values.join(delimiter)});
END IF;
RETURN NULL;
@@ -190,7 +237,35 @@ module Gitlab
end
def build_set_statements(column_names, unique_key)
- column_names.reject { |name| name == unique_key }.map { |column_name| "#{column_name} = NEW.#{column_name}" }
+ column_names.reject { |name| name == unique_key }.map { |name| "#{name} = NEW.#{name}" }
+ end
+
+ def create_sync_trigger(table_name, trigger_name, function_name)
+ if trigger_exists?(table_name, trigger_name)
+ # rubocop:disable Gitlab/RailsLogger
+ Rails.logger.warn "Partitioning sync trigger not created because it already exists" \
+ " (this may be due to an aborted migration or similar): trigger name: #{trigger_name}"
+ # rubocop:enable Gitlab/RailsLogger
+ return
+ end
+
+ create_trigger(table_name, trigger_name, function_name, fires: 'AFTER INSERT OR UPDATE OR DELETE')
+ end
+
+ def enqueue_background_migration(source_table_name, partitioned_table_name, source_key)
+ source_model = define_batchable_model(source_table_name)
+
+ queue_background_migration_jobs_by_range_at_intervals(
+ source_model,
+ MIGRATION_CLASS_NAME,
+ BATCH_INTERVAL,
+ batch_size: BATCH_SIZE,
+ other_job_arguments: [source_table_name.to_s, partitioned_table_name, source_key],
+ track_jobs: true)
+ end
+
+ def cleanup_migration_jobs(table_name)
+ ::Gitlab::Database::BackgroundMigrationJob.for_partitioning_migration(MIGRATION_CLASS_NAME, table_name).delete_all
end
end
end
diff --git a/lib/gitlab/database/schema_helpers.rb b/lib/gitlab/database/schema_helpers.rb
index 8e544307d81..34daafd06de 100644
--- a/lib/gitlab/database/schema_helpers.rb
+++ b/lib/gitlab/database/schema_helpers.rb
@@ -16,15 +16,30 @@ module Gitlab
SQL
end
- def create_trigger(name, function_name, fires: nil)
+ def function_exists?(name)
+ connection.select_value("SELECT 1 FROM pg_proc WHERE proname = '#{name}'")
+ end
+
+ def create_trigger(table_name, name, function_name, fires:)
execute(<<~SQL)
CREATE TRIGGER #{name}
- #{fires}
+ #{fires} ON #{table_name}
FOR EACH ROW
EXECUTE PROCEDURE #{function_name}()
SQL
end
+ def trigger_exists?(table_name, name)
+ connection.select_value(<<~SQL)
+ SELECT 1
+ FROM pg_trigger
+ INNER JOIN pg_class
+ ON pg_trigger.tgrelid = pg_class.oid
+ WHERE pg_class.relname = '#{table_name}'
+ AND pg_trigger.tgname = '#{name}'
+ SQL
+ end
+
def drop_function(name, if_exists: true)
exists_clause = optional_clause(if_exists, "IF EXISTS")
execute("DROP FUNCTION #{exists_clause} #{name}()")
@@ -69,9 +84,13 @@ module Gitlab
private
+ def table_for_range_partition(partition_name)
+ "#{Gitlab::Database::DYNAMIC_PARTITIONS_SCHEMA}.#{partition_name}"
+ end
+
def create_range_partition(partition_name, table_name, lower_bound, upper_bound)
execute(<<~SQL)
- CREATE TABLE #{partition_name} PARTITION OF #{table_name}
+ CREATE TABLE #{table_for_range_partition(partition_name)} PARTITION OF #{table_name}
FOR VALUES FROM (#{lower_bound}) TO (#{upper_bound})
SQL
end
diff --git a/lib/gitlab/diff/file.rb b/lib/gitlab/diff/file.rb
index 72dcc4fde71..dcd4bbdabf5 100644
--- a/lib/gitlab/diff/file.rb
+++ b/lib/gitlab/diff/file.rb
@@ -230,11 +230,15 @@ module Gitlab
end
def added_lines
- @stats&.additions || diff_lines.count(&:added?)
+ strong_memoize(:added_lines) do
+ @stats&.additions || diff_lines.count(&:added?)
+ end
end
def removed_lines
- @stats&.deletions || diff_lines.count(&:removed?)
+ strong_memoize(:removed_lines) do
+ @stats&.deletions || diff_lines.count(&:removed?)
+ end
end
def file_identifier
diff --git a/lib/gitlab/diff/file_collection/base.rb b/lib/gitlab/diff/file_collection/base.rb
index 38b636e4e5a..cf0611e44da 100644
--- a/lib/gitlab/diff/file_collection/base.rb
+++ b/lib/gitlab/diff/file_collection/base.rb
@@ -60,12 +60,20 @@ module Gitlab
end
end
- def diff_file_with_old_path(old_path)
- diff_files.find { |diff_file| diff_file.old_path == old_path }
+ def diff_file_with_old_path(old_path, a_mode = nil)
+ if Feature.enabled?(:file_identifier_hash) && a_mode.present?
+ diff_files.find { |diff_file| diff_file.old_path == old_path && diff_file.a_mode == a_mode }
+ else
+ diff_files.find { |diff_file| diff_file.old_path == old_path }
+ end
end
- def diff_file_with_new_path(new_path)
- diff_files.find { |diff_file| diff_file.new_path == new_path }
+ def diff_file_with_new_path(new_path, b_mode = nil)
+ if Feature.enabled?(:file_identifier_hash) && b_mode.present?
+ diff_files.find { |diff_file| diff_file.new_path == new_path && diff_file.b_mode == b_mode }
+ else
+ diff_files.find { |diff_file| diff_file.new_path == new_path }
+ end
end
def clear_cache
@@ -80,15 +88,18 @@ module Gitlab
def diff_stats_collection
strong_memoize(:diff_stats) do
- # There are scenarios where we don't need to request Diff Stats,
- # when caching for instance.
- next unless @include_stats
- next unless diff_refs
+ next unless fetch_diff_stats?
@repository.diff_stats(diff_refs.base_sha, diff_refs.head_sha)
end
end
+ def fetch_diff_stats?
+ # There are scenarios where we don't need to request Diff Stats,
+ # when caching for instance.
+ @include_stats && diff_refs
+ end
+
def decorate_diff!(diff)
return diff if diff.is_a?(File)
diff --git a/lib/gitlab/diff/file_collection/merge_request_diff_base.rb b/lib/gitlab/diff/file_collection/merge_request_diff_base.rb
index d126fdb2be2..d54e1aad19a 100644
--- a/lib/gitlab/diff/file_collection/merge_request_diff_base.rb
+++ b/lib/gitlab/diff/file_collection/merge_request_diff_base.rb
@@ -20,7 +20,7 @@ module Gitlab
strong_memoize(:diff_files) do
diff_files = super
- diff_files.each { |diff_file| cache.decorate(diff_file) }
+ diff_files.each { |diff_file| highlight_cache.decorate(diff_file) }
diff_files
end
@@ -28,16 +28,14 @@ module Gitlab
override :write_cache
def write_cache
- cache.write_if_empty
+ highlight_cache.write_if_empty
+ diff_stats_cache.write_if_empty(diff_stats_collection)
end
override :clear_cache
def clear_cache
- cache.clear
- end
-
- def cache_key
- cache.key
+ highlight_cache.clear
+ diff_stats_cache.clear
end
def real_size
@@ -46,8 +44,25 @@ module Gitlab
private
- def cache
- @cache ||= Gitlab::Diff::HighlightCache.new(self)
+ def highlight_cache
+ strong_memoize(:highlight_cache) do
+ Gitlab::Diff::HighlightCache.new(self)
+ end
+ end
+
+ def diff_stats_cache
+ strong_memoize(:diff_stats_cache) do
+ Gitlab::Diff::StatsCache.new(cachable_key: @merge_request_diff.cache_key)
+ end
+ end
+
+ override :diff_stats_collection
+ def diff_stats_collection
+ strong_memoize(:diff_stats) do
+ next unless fetch_diff_stats?
+
+ diff_stats_cache.read || super
+ end
end
end
end
diff --git a/lib/gitlab/diff/file_collection/wiki_page.rb b/lib/gitlab/diff/file_collection/wiki_page.rb
new file mode 100644
index 00000000000..7873e85a0eb
--- /dev/null
+++ b/lib/gitlab/diff/file_collection/wiki_page.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Diff
+ module FileCollection
+ class WikiPage < Base
+ def initialize(page, diff_options:)
+ commit = page.wiki.commit(page.version.commit)
+ diff_options = diff_options.merge(
+ expanded: true,
+ paths: [page.path]
+ )
+
+ super(commit,
+ # TODO: Uncouple diffing from projects
+ # https://gitlab.com/gitlab-org/gitlab/-/issues/217752
+ project: page.wiki,
+ diff_options: diff_options,
+ diff_refs: commit.diff_refs)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/diff/position_tracer.rb b/lib/gitlab/diff/position_tracer.rb
index a1c82ce9afc..1c21c35fa60 100644
--- a/lib/gitlab/diff/position_tracer.rb
+++ b/lib/gitlab/diff/position_tracer.rb
@@ -42,6 +42,10 @@ module Gitlab
@cd_diffs ||= compare(new_diff_refs.start_sha, new_diff_refs.head_sha)
end
+ def diff_file(position)
+ position.diff_file(project.repository)
+ end
+
private
def compare(start_sha, head_sha, straight: false)
diff --git a/lib/gitlab/diff/position_tracer/base_strategy.rb b/lib/gitlab/diff/position_tracer/base_strategy.rb
index 65049daabf4..61250bd2473 100644
--- a/lib/gitlab/diff/position_tracer/base_strategy.rb
+++ b/lib/gitlab/diff/position_tracer/base_strategy.rb
@@ -8,6 +8,7 @@ module Gitlab
delegate \
:project,
+ :diff_file,
:ac_diffs,
:bd_diffs,
:cd_diffs,
diff --git a/lib/gitlab/diff/position_tracer/image_strategy.rb b/lib/gitlab/diff/position_tracer/image_strategy.rb
index 79244a17951..046a6782dda 100644
--- a/lib/gitlab/diff/position_tracer/image_strategy.rb
+++ b/lib/gitlab/diff/position_tracer/image_strategy.rb
@@ -5,22 +5,26 @@ module Gitlab
class PositionTracer
class ImageStrategy < BaseStrategy
def trace(position)
+ a_path = position.old_path
b_path = position.new_path
+ diff_file = diff_file(position)
+ a_mode = diff_file&.a_mode
+ b_mode = diff_file&.b_mode
# If file exists in B->D (e.g. updated, renamed, removed), let the
# note become outdated.
- bd_diff = bd_diffs.diff_file_with_old_path(b_path)
+ bd_diff = bd_diffs.diff_file_with_old_path(b_path, b_mode)
return { position: new_position(position, bd_diff), outdated: true } if bd_diff
# If file still exists in the new diff, update the position.
- cd_diff = cd_diffs.diff_file_with_new_path(bd_diff&.new_path || b_path)
+ cd_diff = cd_diffs.diff_file_with_new_path(b_path, b_mode)
return { position: new_position(position, cd_diff), outdated: false } if cd_diff
# If file exists in A->C (e.g. rebased and same changes were present
# in target branch), let the note become outdated.
- ac_diff = ac_diffs.diff_file_with_old_path(position.old_path)
+ ac_diff = ac_diffs.diff_file_with_old_path(a_path, a_mode)
return { position: new_position(position, ac_diff), outdated: true } if ac_diff
diff --git a/lib/gitlab/diff/position_tracer/line_strategy.rb b/lib/gitlab/diff/position_tracer/line_strategy.rb
index 8db0fc6f963..e3c1e549b96 100644
--- a/lib/gitlab/diff/position_tracer/line_strategy.rb
+++ b/lib/gitlab/diff/position_tracer/line_strategy.rb
@@ -76,16 +76,20 @@ module Gitlab
def trace_added_line(position)
b_path = position.new_path
b_line = position.new_line
+ diff_file = diff_file(position)
+ b_mode = diff_file&.b_mode
- bd_diff = bd_diffs.diff_file_with_old_path(b_path)
+ bd_diff = bd_diffs.diff_file_with_old_path(b_path, b_mode)
d_path = bd_diff&.new_path || b_path
+ d_mode = bd_diff&.b_mode || b_mode
d_line = LineMapper.new(bd_diff).old_to_new(b_line)
if d_line
- cd_diff = cd_diffs.diff_file_with_new_path(d_path)
+ cd_diff = cd_diffs.diff_file_with_new_path(d_path, d_mode)
c_path = cd_diff&.old_path || d_path
+ c_mode = cd_diff&.a_mode || d_mode
c_line = LineMapper.new(cd_diff).new_to_old(d_line)
if c_line
@@ -98,7 +102,7 @@ module Gitlab
else
# If the line is no longer in the MR, we unfortunately cannot show
# the current state on the CD diff, so we treat it as outdated.
- ac_diff = ac_diffs.diff_file_with_new_path(c_path)
+ ac_diff = ac_diffs.diff_file_with_new_path(c_path, c_mode)
{ position: new_position(ac_diff, nil, c_line), outdated: true }
end
@@ -115,22 +119,26 @@ module Gitlab
def trace_removed_line(position)
a_path = position.old_path
a_line = position.old_line
+ diff_file = diff_file(position)
+ a_mode = diff_file&.a_mode
- ac_diff = ac_diffs.diff_file_with_old_path(a_path)
+ ac_diff = ac_diffs.diff_file_with_old_path(a_path, a_mode)
c_path = ac_diff&.new_path || a_path
+ c_mode = ac_diff&.b_mode || a_mode
c_line = LineMapper.new(ac_diff).old_to_new(a_line)
if c_line
- cd_diff = cd_diffs.diff_file_with_old_path(c_path)
+ cd_diff = cd_diffs.diff_file_with_old_path(c_path, c_mode)
d_path = cd_diff&.new_path || c_path
+ d_mode = cd_diff&.b_mode || c_mode
d_line = LineMapper.new(cd_diff).old_to_new(c_line)
if d_line
# If the line is still in C but also in D, it has turned from a
# removed line into an unchanged one.
- bd_diff = bd_diffs.diff_file_with_new_path(d_path)
+ bd_diff = bd_diffs.diff_file_with_new_path(d_path, d_mode)
{ position: new_position(bd_diff, nil, d_line), outdated: true }
else
@@ -148,17 +156,21 @@ module Gitlab
a_line = position.old_line
b_path = position.new_path
b_line = position.new_line
+ diff_file = diff_file(position)
+ a_mode = diff_file&.a_mode
+ b_mode = diff_file&.b_mode
- ac_diff = ac_diffs.diff_file_with_old_path(a_path)
+ ac_diff = ac_diffs.diff_file_with_old_path(a_path, a_mode)
c_path = ac_diff&.new_path || a_path
+ c_mode = ac_diff&.b_mode || a_mode
c_line = LineMapper.new(ac_diff).old_to_new(a_line)
- bd_diff = bd_diffs.diff_file_with_old_path(b_path)
+ bd_diff = bd_diffs.diff_file_with_old_path(b_path, b_mode)
d_line = LineMapper.new(bd_diff).old_to_new(b_line)
- cd_diff = cd_diffs.diff_file_with_old_path(c_path)
+ cd_diff = cd_diffs.diff_file_with_old_path(c_path, c_mode)
if c_line && d_line
# If the line is still in C and D, it is still unchanged.
diff --git a/lib/gitlab/diff/stats_cache.rb b/lib/gitlab/diff/stats_cache.rb
new file mode 100644
index 00000000000..f38fb21d497
--- /dev/null
+++ b/lib/gitlab/diff/stats_cache.rb
@@ -0,0 +1,54 @@
+# frozen_string_literal: true
+#
+module Gitlab
+ module Diff
+ class StatsCache
+ include Gitlab::Metrics::Methods
+ include Gitlab::Utils::StrongMemoize
+
+ EXPIRATION = 1.week
+ VERSION = 1
+
+ def initialize(cachable_key:)
+ @cachable_key = cachable_key
+ end
+
+ def read
+ strong_memoize(:cached_values) do
+ content = cache.fetch(key)
+
+ next unless content
+
+ stats = content.map { |stat| Gitaly::DiffStats.new(stat) }
+
+ Gitlab::Git::DiffStatsCollection.new(stats)
+ end
+ end
+
+ def write_if_empty(stats)
+ return if cache.exist?(key)
+ return unless stats
+
+ cache.write(key, stats.as_json, expires_in: EXPIRATION)
+ end
+
+ def clear
+ cache.delete(key)
+ end
+
+ private
+
+ attr_reader :cachable_key
+
+ def cache
+ Rails.cache
+ end
+
+ def key
+ strong_memoize(:redis_key) do
+ ['diff_stats', cachable_key, VERSION].join(":")
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/discussions_diff/file_collection.rb b/lib/gitlab/discussions_diff/file_collection.rb
index 7a9d4c5c0c2..60b3a1738f1 100644
--- a/lib/gitlab/discussions_diff/file_collection.rb
+++ b/lib/gitlab/discussions_diff/file_collection.rb
@@ -70,8 +70,8 @@ module Gitlab
#
# Returns a Hash with { id => [Array of Gitlab::Diff::line], ...]
def highlighted_lines_by_ids(ids)
- diff_files_indexed_by_id.slice(*ids).each_with_object({}) do |(id, file), hash|
- hash[id] = file.highlighted_diff_lines.map(&:to_hash)
+ diff_files_indexed_by_id.slice(*ids).transform_values do |file|
+ file.highlighted_diff_lines.map(&:to_hash)
end
end
end
diff --git a/lib/gitlab/discussions_diff/highlight_cache.rb b/lib/gitlab/discussions_diff/highlight_cache.rb
index 75d5a5df74b..4bec6467c1a 100644
--- a/lib/gitlab/discussions_diff/highlight_cache.rb
+++ b/lib/gitlab/discussions_diff/highlight_cache.rb
@@ -36,7 +36,9 @@ module Gitlab
content =
Redis::Cache.with do |redis|
- redis.mget(keys)
+ Gitlab::Instrumentation::RedisClusterValidator.allow_cross_slot_commands do
+ redis.mget(keys)
+ end
end
content.map! do |lines|
@@ -58,7 +60,11 @@ module Gitlab
keys = raw_keys.map { |id| cache_key_for(id) }
- Redis::Cache.with { |redis| redis.del(keys) }
+ Redis::Cache.with do |redis|
+ Gitlab::Instrumentation::RedisClusterValidator.allow_cross_slot_commands do
+ redis.del(keys)
+ end
+ end
end
def cache_key_for(raw_key)
diff --git a/lib/gitlab/email/handler.rb b/lib/gitlab/email/handler.rb
index 7f8dd815103..1b8421d34f3 100644
--- a/lib/gitlab/email/handler.rb
+++ b/lib/gitlab/email/handler.rb
@@ -12,7 +12,8 @@ module Gitlab
CreateNoteHandler,
CreateIssueHandler,
UnsubscribeHandler,
- CreateMergeRequestHandler
+ CreateMergeRequestHandler,
+ ServiceDeskHandler
]
end
@@ -25,5 +26,3 @@ module Gitlab
end
end
end
-
-Gitlab::Email::Handler.prepend_if_ee('::EE::Gitlab::Email::Handler')
diff --git a/lib/gitlab/email/handler/reply_processing.rb b/lib/gitlab/email/handler/reply_processing.rb
index 312a9fdfbae..1beea4f9054 100644
--- a/lib/gitlab/email/handler/reply_processing.rb
+++ b/lib/gitlab/email/handler/reply_processing.rb
@@ -37,7 +37,11 @@ module Gitlab
def process_message(**kwargs)
message = ReplyParser.new(mail, **kwargs).execute.strip
- add_attachments(message)
+ message_with_attachments = add_attachments(message)
+
+ # Support bot is specifically forbidden
+ # from using slash commands.
+ strip_quick_actions(message_with_attachments)
end
def add_attachments(reply)
@@ -82,6 +86,15 @@ module Gitlab
def valid_project_slug?(found_project)
project_slug == found_project.full_path_slug
end
+
+ def strip_quick_actions(content)
+ return content unless author.support_bot?
+
+ command_definitions = ::QuickActions::InterpretService.command_definitions
+ extractor = ::Gitlab::QuickActions::Extractor.new(command_definitions)
+
+ extractor.redact_commands(content)
+ end
end
end
end
diff --git a/lib/gitlab/email/handler/service_desk_handler.rb b/lib/gitlab/email/handler/service_desk_handler.rb
new file mode 100644
index 00000000000..bcd8b98a06f
--- /dev/null
+++ b/lib/gitlab/email/handler/service_desk_handler.rb
@@ -0,0 +1,152 @@
+# frozen_string_literal: true
+
+# handles service desk issue creation emails with these formats:
+# incoming+gitlab-org-gitlab-ce-20-issue-@incoming.gitlab.com
+# incoming+gitlab-org/gitlab-ce@incoming.gitlab.com (legacy)
+module Gitlab
+ module Email
+ module Handler
+ class ServiceDeskHandler < BaseHandler
+ include ReplyProcessing
+ include Gitlab::Utils::StrongMemoize
+
+ HANDLER_REGEX = /\A#{HANDLER_ACTION_BASE_REGEX}-issue-\z/.freeze
+ HANDLER_REGEX_LEGACY = /\A(?<project_path>[^\+]*)\z/.freeze
+ PROJECT_KEY_PATTERN = /\A(?<slug>.+)-(?<key>[a-z0-9_]+)\z/.freeze
+
+ def initialize(mail, mail_key, service_desk_key: nil)
+ super(mail, mail_key)
+
+ if service_desk_key.present?
+ @service_desk_key = service_desk_key
+ elsif !mail_key&.include?('/') && (matched = HANDLER_REGEX.match(mail_key.to_s))
+ @project_slug = matched[:project_slug]
+ @project_id = matched[:project_id]&.to_i
+ elsif matched = HANDLER_REGEX_LEGACY.match(mail_key.to_s)
+ @project_path = matched[:project_path]
+ end
+ end
+
+ def can_handle?
+ Gitlab::ServiceDesk.supported? && (project_id || can_handle_legacy_format? || service_desk_key)
+ end
+
+ def execute
+ raise ProjectNotFound if project.nil?
+
+ create_issue!
+ send_thank_you_email! if from_address
+ end
+
+ def metrics_params
+ super.merge(project: project&.full_path)
+ end
+
+ def metrics_event
+ :receive_email_service_desk
+ end
+
+ private
+
+ attr_reader :project_id, :project_path, :service_desk_key
+
+ def project
+ strong_memoize(:project) do
+ @project = service_desk_key ? project_from_key : super
+ @project = nil unless @project&.service_desk_enabled?
+ @project
+ end
+ end
+
+ def project_from_key
+ return unless match = service_desk_key.match(PROJECT_KEY_PATTERN)
+
+ project = Project.find_by_service_desk_project_key(match[:key])
+ return unless valid_project_key?(project, match[:slug])
+
+ project
+ end
+
+ def valid_project_key?(project, slug)
+ project.present? && slug == project.full_path_slug && Feature.enabled?(:service_desk_custom_address, project)
+ end
+
+ def create_issue!
+ @issue = Issues::CreateService.new(
+ project,
+ User.support_bot,
+ title: issue_title,
+ description: message_including_template,
+ confidential: true,
+ service_desk_reply_to: from_address
+ ).execute
+
+ raise InvalidIssueError unless @issue.persisted?
+
+ if service_desk_setting&.issue_template_missing?
+ create_template_not_found_note(@issue)
+ end
+ end
+
+ def send_thank_you_email!
+ Notify.service_desk_thank_you_email(@issue.id).deliver_later!
+ end
+
+ def message_including_template
+ description = message_including_reply
+ template_content = service_desk_setting&.issue_template_content
+
+ if template_content.present?
+ description += " \n" + template_content
+ end
+
+ description
+ end
+
+ def service_desk_setting
+ strong_memoize(:service_desk_setting) do
+ project.service_desk_setting
+ end
+ end
+
+ def create_template_not_found_note(issue)
+ issue_template_key = service_desk_setting&.issue_template_key
+
+ warning_note = <<-MD.strip_heredoc
+ WARNING: The template file #{issue_template_key}.md used for service desk issues is empty or could not be found.
+ Please check service desk settings and update the file to be used.
+ MD
+
+ note_params = {
+ noteable: issue,
+ note: warning_note
+ }
+
+ ::Notes::CreateService.new(
+ project,
+ User.support_bot,
+ note_params
+ ).execute
+ end
+
+ def from_address
+ (mail.reply_to || []).first || mail.from.first || mail.sender
+ end
+
+ def issue_title
+ from = "(from #{from_address})" if from_address
+
+ "Service Desk #{from}: #{mail.subject}"
+ end
+
+ def can_handle_legacy_format?
+ project_path && project_path.include?('/') && !mail_key.include?('+')
+ end
+
+ def author
+ User.support_bot
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/email/service_desk_receiver.rb b/lib/gitlab/email/service_desk_receiver.rb
new file mode 100644
index 00000000000..1ee5c10097b
--- /dev/null
+++ b/lib/gitlab/email/service_desk_receiver.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Email
+ class ServiceDeskReceiver < Receiver
+ private
+
+ def find_handler(mail)
+ key = service_desk_key(mail)
+ return unless key
+
+ Gitlab::Email::Handler::ServiceDeskHandler.new(mail, nil, service_desk_key: key)
+ end
+
+ def service_desk_key(mail)
+ mail.to.find do |address|
+ key = ::Gitlab::ServiceDeskEmail.key_from_address(address)
+ break key if key
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/emoji.rb b/lib/gitlab/emoji.rb
index bcf92b35720..cab21d875ab 100644
--- a/lib/gitlab/emoji.rb
+++ b/lib/gitlab/emoji.rb
@@ -44,6 +44,10 @@ module Gitlab
"<img class='emoji' title=':#{name}:' alt=':#{name}:' src='#{src}' height='20' width='20' align='absmiddle' />"
end
+ def emoji_exists?(name)
+ emojis.has_key?(name)
+ end
+
# CSS sprite fallback takes precedence over image fallback
def gl_emoji_tag(name, options = {})
emoji_name = emojis_aliases[name] || name
diff --git a/lib/gitlab/error_tracking.rb b/lib/gitlab/error_tracking.rb
index a19ce22e53f..8d5611411c9 100644
--- a/lib/gitlab/error_tracking.rb
+++ b/lib/gitlab/error_tracking.rb
@@ -10,7 +10,6 @@ module Gitlab
Acme::Client::Error::Timeout
Acme::Client::Error::UnsupportedOperation
ActiveRecord::ConnectionTimeoutError
- ActiveRecord::QueryCanceled
Gitlab::RequestContext::RequestDeadlineExceeded
GRPC::DeadlineExceeded
JIRA::HTTPError
@@ -29,7 +28,7 @@ module Gitlab
config.processors << ::Gitlab::ErrorTracking::Processor::SidekiqProcessor
# Sanitize authentication headers
config.sanitize_http_headers = %w[Authorization Private-Token]
- config.tags = { program: Gitlab.process_name }
+ config.tags = extra_tags_from_env.merge(program: Gitlab.process_name)
config.before_send = method(:before_send)
yield config if block_given?
@@ -166,6 +165,15 @@ module Gitlab
}
end
+ # Static tags that are set on application start
+ def extra_tags_from_env
+ Gitlab::Json.parse(ENV.fetch('GITLAB_SENTRY_EXTRA_TAGS', '{}')).to_hash
+ rescue => e
+ Gitlab::AppLogger.debug("GITLAB_SENTRY_EXTRA_TAGS could not be parsed as JSON: #{e.class.name}: #{e.message}")
+
+ {}
+ end
+
# Debugging for https://gitlab.com/gitlab-org/gitlab-foss/issues/57727
def add_context_from_exception_type(event, hint)
if ActiveModel::MissingAttributeError === hint[:exception]
@@ -173,8 +181,7 @@ module Gitlab
.connection
.schema_cache
.instance_variable_get(:@columns_hash)
- .map { |k, v| [k, v.map(&:first)] }
- .to_h
+ .transform_values { |v| v.map(&:first) }
event.extra.merge!(columns_hash)
end
diff --git a/lib/gitlab/error_tracking/detailed_error.rb b/lib/gitlab/error_tracking/detailed_error.rb
index b49f2472e01..5d272efa64a 100644
--- a/lib/gitlab/error_tracking/detailed_error.rb
+++ b/lib/gitlab/error_tracking/detailed_error.rb
@@ -22,6 +22,7 @@ module Gitlab
:id,
:last_release_last_commit,
:last_release_short_version,
+ :last_release_version,
:last_seen,
:message,
:project_id,
diff --git a/lib/gitlab/file_finder.rb b/lib/gitlab/file_finder.rb
index d438b0415fa..6225955a930 100644
--- a/lib/gitlab/file_finder.rb
+++ b/lib/gitlab/file_finder.rb
@@ -42,7 +42,7 @@ module Gitlab
end
end
- # Overriden in Gitlab::WikiFileFinder
+ # Overridden in Gitlab::WikiFileFinder
def search_paths(query)
repository.search_files_by_name(query, ref)
end
diff --git a/lib/gitlab/git/commit.rb b/lib/gitlab/git/commit.rb
index 17d0a62ba8c..8db73ecc480 100644
--- a/lib/gitlab/git/commit.rb
+++ b/lib/gitlab/git/commit.rb
@@ -90,14 +90,15 @@ module Gitlab
#
# Commit.last_for_path(repo, 'master', 'Gemfile')
#
- def last_for_path(repo, ref, path = nil)
+ def last_for_path(repo, ref, path = nil, literal_pathspec: false)
# rubocop: disable Rails/FindBy
# This is not where..first from ActiveRecord
where(
repo: repo,
ref: ref,
path: path,
- limit: 1
+ limit: 1,
+ literal_pathspec: literal_pathspec
).first
# rubocop: enable Rails/FindBy
end
diff --git a/lib/gitlab/git/diff.rb b/lib/gitlab/git/diff.rb
index bb845f11181..09a49b6c1ca 100644
--- a/lib/gitlab/git/diff.rb
+++ b/lib/gitlab/git/diff.rb
@@ -224,18 +224,18 @@ module Gitlab
end
end
- def init_from_gitaly(diff)
- @diff = diff.respond_to?(:patch) ? encode!(diff.patch) : ''
- @new_path = encode!(diff.to_path.dup)
- @old_path = encode!(diff.from_path.dup)
- @a_mode = diff.old_mode.to_s(8)
- @b_mode = diff.new_mode.to_s(8)
- @new_file = diff.from_id == BLANK_SHA
- @renamed_file = diff.from_path != diff.to_path
- @deleted_file = diff.to_id == BLANK_SHA
- @too_large = diff.too_large if diff.respond_to?(:too_large)
-
- collapse! if diff.respond_to?(:collapsed) && diff.collapsed
+ def init_from_gitaly(gitaly_diff)
+ @diff = gitaly_diff.respond_to?(:patch) ? encode!(gitaly_diff.patch) : ''
+ @new_path = encode!(gitaly_diff.to_path.dup)
+ @old_path = encode!(gitaly_diff.from_path.dup)
+ @a_mode = gitaly_diff.old_mode.to_s(8)
+ @b_mode = gitaly_diff.new_mode.to_s(8)
+ @new_file = gitaly_diff.from_id == BLANK_SHA
+ @renamed_file = gitaly_diff.from_path != gitaly_diff.to_path
+ @deleted_file = gitaly_diff.to_id == BLANK_SHA
+ @too_large = gitaly_diff.too_large if gitaly_diff.respond_to?(:too_large)
+
+ collapse! if gitaly_diff.respond_to?(:collapsed) && gitaly_diff.collapsed
end
def prune_diff_if_eligible
diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb
index ed746163748..ea7a6e84195 100644
--- a/lib/gitlab/git/repository.rb
+++ b/lib/gitlab/git/repository.rb
@@ -127,9 +127,9 @@ module Gitlab
end
end
- def local_branches(sort_by: nil)
+ def local_branches(sort_by: nil, pagination_params: nil)
wrapped_gitaly_errors do
- gitaly_ref_client.local_branches(sort_by: sort_by)
+ gitaly_ref_client.local_branches(sort_by: sort_by, pagination_params: pagination_params)
end
end
@@ -1002,15 +1002,21 @@ module Gitlab
end
end
- def list_last_commits_for_tree(sha, path, offset: 0, limit: 25)
+ def list_last_commits_for_tree(sha, path, offset: 0, limit: 25, literal_pathspec: false)
wrapped_gitaly_errors do
- gitaly_commit_client.list_last_commits_for_tree(sha, path, offset: offset, limit: limit)
+ gitaly_commit_client.list_last_commits_for_tree(sha, path, offset: offset, limit: limit, literal_pathspec: literal_pathspec)
end
end
- def last_commit_for_path(sha, path)
+ def list_commits_by_ref_name(refs)
wrapped_gitaly_errors do
- gitaly_commit_client.last_commit_for_path(sha, path)
+ gitaly_commit_client.list_commits_by_ref_name(refs)
+ end
+ end
+
+ def last_commit_for_path(sha, path, literal_pathspec: false)
+ wrapped_gitaly_errors do
+ gitaly_commit_client.last_commit_for_path(sha, path, literal_pathspec: literal_pathspec)
end
end
diff --git a/lib/gitlab/git/wiki.rb b/lib/gitlab/git/wiki.rb
index 3025fc6bfdb..76771f0417b 100644
--- a/lib/gitlab/git/wiki.rb
+++ b/lib/gitlab/git/wiki.rb
@@ -101,6 +101,10 @@ module Gitlab
wrapped_gitaly_errors do
gitaly_find_page(title: title, version: version, dir: dir)
end
+ rescue Gitlab::Git::CommandError
+ # Return nil for invalid versions.
+ # This can be removed with https://gitlab.com/gitlab-org/gitaly/-/merge_requests/2323 in place.
+ nil
end
def file(name, version)
diff --git a/lib/gitlab/git_ref_validator.rb b/lib/gitlab/git_ref_validator.rb
index dfff6823689..1330b06bf9c 100644
--- a/lib/gitlab/git_ref_validator.rb
+++ b/lib/gitlab/git_ref_validator.rb
@@ -19,7 +19,7 @@ module Gitlab
begin
Rugged::Reference.valid_name?("refs/heads/#{ref_name}")
rescue ArgumentError
- return false
+ false
end
end
@@ -35,7 +35,7 @@ module Gitlab
begin
Rugged::Reference.valid_name?(expanded_name)
rescue ArgumentError
- return false
+ false
end
end
end
diff --git a/lib/gitlab/gitaly_client.rb b/lib/gitlab/gitaly_client.rb
index bed99ef0ed4..b284aadc107 100644
--- a/lib/gitlab/gitaly_client.rb
+++ b/lib/gitlab/gitaly_client.rb
@@ -166,20 +166,7 @@ module Gitlab
# "gitaly-2 is at network address tcp://10.0.1.2:8075".
#
def self.call(storage, service, rpc, request, remote_storage: nil, timeout: default_timeout, &block)
- self.measure_timings(service, rpc, request) do
- self.execute(storage, service, rpc, request, remote_storage: remote_storage, timeout: timeout, &block)
- end
- end
-
- # This method is like GitalyClient.call but should be used with
- # Gitaly streaming RPCs. It measures how long the the RPC took to
- # produce the full response, not just the initial response.
- def self.streaming_call(storage, service, rpc, request, remote_storage: nil, timeout: default_timeout)
- self.measure_timings(service, rpc, request) do
- response = self.execute(storage, service, rpc, request, remote_storage: remote_storage, timeout: timeout)
-
- yield(response)
- end
+ Gitlab::GitalyClient::Call.new(storage, service, rpc, request, remote_storage, timeout).call(&block)
end
def self.execute(storage, service, rpc, request, remote_storage:, timeout:)
@@ -192,23 +179,6 @@ module Gitlab
stub(service, storage).__send__(rpc, request, kwargs) # rubocop:disable GitlabSecurity/PublicSend
end
- def self.measure_timings(service, rpc, request)
- start = Gitlab::Metrics::System.monotonic_time
-
- yield
- ensure
- duration = Gitlab::Metrics::System.monotonic_time - start
- request_hash = request.is_a?(Google::Protobuf::MessageExts) ? request.to_h : {}
-
- # Keep track, separately, for the performance bar
- self.add_query_time(duration)
-
- if Gitlab::PerformanceBar.enabled_for_request?
- add_call_details(feature: "#{service}##{rpc}", duration: duration, request: request_hash, rpc: rpc,
- backtrace: Gitlab::BacktraceCleaner.clean_backtrace(caller))
- end
- end
-
def self.query_time
query_time = Gitlab::SafeRequestStore[:gitaly_query_time] || 0
query_time.round(Gitlab::InstrumentationHelper::DURATION_PRECISION)
diff --git a/lib/gitlab/gitaly_client/blob_service.rb b/lib/gitlab/gitaly_client/blob_service.rb
index 8c704c2ceea..c66b3335d89 100644
--- a/lib/gitlab/gitaly_client/blob_service.rb
+++ b/lib/gitlab/gitaly_client/blob_service.rb
@@ -16,27 +16,7 @@ module Gitlab
limit: limit
)
response = GitalyClient.call(@gitaly_repo.storage_name, :blob_service, :get_blob, request, timeout: GitalyClient.fast_timeout)
-
- data = []
- blob = nil
- response.each do |msg|
- if blob.nil?
- blob = msg
- end
-
- data << msg.data
- end
-
- return if blob.oid.blank?
-
- data = data.join
-
- Gitlab::Git::Blob.new(
- id: blob.oid,
- size: blob.size,
- data: data,
- binary: Gitlab::Git::Blob.binary?(data)
- )
+ consume_blob_response(response)
end
def batch_lfs_pointers(blob_ids)
@@ -48,7 +28,6 @@ module Gitlab
)
response = GitalyClient.call(@gitaly_repo.storage_name, :blob_service, :get_lfs_pointers, request, timeout: GitalyClient.medium_timeout)
-
map_lfs_pointers(response)
end
@@ -70,8 +49,7 @@ module Gitlab
:blob_service,
:get_blobs,
request,
- timeout: GitalyClient.fast_timeout
- )
+ timeout: GitalyClient.fast_timeout)
GitalyClient::BlobsStitcher.new(response)
end
@@ -96,7 +74,6 @@ module Gitlab
request,
timeout: GitalyClient.fast_timeout
)
-
map_blob_types(response)
end
@@ -127,7 +104,6 @@ module Gitlab
request,
timeout: timeout
)
-
map_lfs_pointers(response)
end
@@ -137,12 +113,34 @@ module Gitlab
)
response = GitalyClient.call(@gitaly_repo.storage_name, :blob_service, :get_all_lfs_pointers, request, timeout: GitalyClient.medium_timeout)
-
map_lfs_pointers(response)
end
private
+ def consume_blob_response(response)
+ data = []
+ blob = nil
+ response.each do |msg|
+ if blob.nil?
+ blob = msg
+ end
+
+ data << msg.data
+ end
+
+ return if blob.oid.blank?
+
+ data = data.join
+
+ Gitlab::Git::Blob.new(
+ id: blob.oid,
+ size: blob.size,
+ data: data,
+ binary: Gitlab::Git::Blob.binary?(data)
+ )
+ end
+
def map_lfs_pointers(response)
response.flat_map do |message|
message.lfs_pointers.map do |lfs_pointer|
diff --git a/lib/gitlab/gitaly_client/call.rb b/lib/gitlab/gitaly_client/call.rb
new file mode 100644
index 00000000000..9d4d86997ad
--- /dev/null
+++ b/lib/gitlab/gitaly_client/call.rb
@@ -0,0 +1,72 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module GitalyClient
+ class Call
+ def initialize(storage, service, rpc, request, remote_storage, timeout)
+ @storage = storage
+ @service = service
+ @rpc = rpc
+ @request = request
+ @remote_storage = remote_storage
+ @timeout = timeout
+ @duration = 0
+ end
+
+ def call(&block)
+ response = recording_request do
+ GitalyClient.execute(@storage, @service, @rpc, @request, remote_storage: @remote_storage, timeout: @timeout, &block)
+ end
+
+ if response.is_a?(Enumerator)
+ # When the given response is an enumerator (coming from streamed
+ # responses), we wrap it in order to properly measure the stream
+ # consumption as it happens.
+ #
+ # store_timings is not called in that scenario as needs to be
+ # handled lazily in the custom Enumerator context.
+ instrument_stream(response)
+ else
+ store_timings
+ response
+ end
+ rescue => err
+ store_timings
+ raise err
+ end
+
+ private
+
+ def instrument_stream(response)
+ Enumerator.new do |yielder|
+ loop do
+ value = recording_request { response.next }
+
+ yielder.yield(value)
+ end
+ ensure
+ store_timings
+ end
+ end
+
+ def recording_request
+ start = Gitlab::Metrics::System.monotonic_time
+
+ yield
+ ensure
+ @duration += Gitlab::Metrics::System.monotonic_time - start
+ end
+
+ def store_timings
+ GitalyClient.add_query_time(@duration)
+
+ return unless Gitlab::PerformanceBar.enabled_for_request?
+
+ request_hash = @request.is_a?(Google::Protobuf::MessageExts) ? @request.to_h : {}
+
+ GitalyClient.add_call_details(feature: "#{@service}##{@rpc}", duration: @duration, request: request_hash, rpc: @rpc,
+ backtrace: Gitlab::BacktraceCleaner.clean_backtrace(caller))
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/gitaly_client/cleanup_service.rb b/lib/gitlab/gitaly_client/cleanup_service.rb
index e2293d3121a..649aaa46362 100644
--- a/lib/gitlab/gitaly_client/cleanup_service.rb
+++ b/lib/gitlab/gitaly_client/cleanup_service.rb
@@ -13,15 +13,14 @@ module Gitlab
end
def apply_bfg_object_map_stream(io, &blk)
- responses = GitalyClient.call(
+ response = GitalyClient.call(
storage,
:cleanup_service,
:apply_bfg_object_map_stream,
build_object_map_enum(io),
timeout: GitalyClient.long_timeout
)
-
- responses.each(&blk)
+ response.each(&blk)
end
private
diff --git a/lib/gitlab/gitaly_client/commit_service.rb b/lib/gitlab/gitaly_client/commit_service.rb
index aed132aaca0..464d2519b27 100644
--- a/lib/gitlab/gitaly_client/commit_service.rb
+++ b/lib/gitlab/gitaly_client/commit_service.rb
@@ -73,7 +73,6 @@ module Gitlab
def commit_deltas(commit)
request = Gitaly::CommitDeltaRequest.new(diff_from_parent_request_params(commit))
response = GitalyClient.call(@repository.storage, :diff_service, :commit_delta, request, timeout: GitalyClient.fast_timeout)
-
response.flat_map { |msg| msg.deltas }
end
@@ -162,13 +161,14 @@ module Gitlab
[response.left_count, response.right_count]
end
- def list_last_commits_for_tree(revision, path, offset: 0, limit: 25)
+ def list_last_commits_for_tree(revision, path, offset: 0, limit: 25, literal_pathspec: false)
request = Gitaly::ListLastCommitsForTreeRequest.new(
repository: @gitaly_repo,
revision: encode_binary(revision),
path: encode_binary(path.to_s),
offset: offset,
- limit: limit
+ limit: limit,
+ global_options: parse_global_options!(literal_pathspec: literal_pathspec)
)
response = GitalyClient.call(@repository.storage, :commit_service, :list_last_commits_for_tree, request, timeout: GitalyClient.medium_timeout)
@@ -180,11 +180,12 @@ module Gitlab
end
end
- def last_commit_for_path(revision, path)
+ def last_commit_for_path(revision, path, literal_pathspec: false)
request = Gitaly::LastCommitForPathRequest.new(
repository: @gitaly_repo,
revision: encode_binary(revision),
- path: encode_binary(path.to_s)
+ path: encode_binary(path.to_s),
+ global_options: parse_global_options!(literal_pathspec: literal_pathspec)
)
gitaly_commit = GitalyClient.call(@repository.storage, :commit_service, :last_commit_for_path, request, timeout: GitalyClient.fast_timeout).commit
@@ -200,9 +201,8 @@ module Gitlab
to: to
)
- GitalyClient.streaming_call(@repository.storage, :commit_service, :commits_between, request, timeout: GitalyClient.medium_timeout) do |response|
- consume_commits_response(response)
- end
+ response = GitalyClient.call(@repository.storage, :commit_service, :commits_between, request, timeout: GitalyClient.medium_timeout)
+ consume_commits_response(response)
end
def diff_stats(left_commit_sha, right_commit_sha)
@@ -212,9 +212,8 @@ module Gitlab
right_commit_id: right_commit_sha
)
- GitalyClient.streaming_call(@repository.storage, :diff_service, :diff_stats, request, timeout: GitalyClient.medium_timeout) do |response|
- response.flat_map(&:stats)
- end
+ response = GitalyClient.call(@repository.storage, :diff_service, :diff_stats, request, timeout: GitalyClient.medium_timeout)
+ response.flat_map(&:stats)
end
def find_all_commits(opts = {})
@@ -226,9 +225,8 @@ module Gitlab
)
request.order = opts[:order].upcase if opts[:order].present?
- GitalyClient.streaming_call(@repository.storage, :commit_service, :find_all_commits, request, timeout: GitalyClient.medium_timeout) do |response|
- consume_commits_response(response)
- end
+ response = GitalyClient.call(@repository.storage, :commit_service, :find_all_commits, request, timeout: GitalyClient.medium_timeout)
+ consume_commits_response(response)
end
def list_commits_by_oid(oids)
@@ -236,26 +234,25 @@ module Gitlab
request = Gitaly::ListCommitsByOidRequest.new(repository: @gitaly_repo, oid: oids)
- GitalyClient.streaming_call(@repository.storage, :commit_service, :list_commits_by_oid, request, timeout: GitalyClient.medium_timeout) do |response|
- consume_commits_response(response)
- end
+ response = GitalyClient.call(@repository.storage, :commit_service, :list_commits_by_oid, request, timeout: GitalyClient.medium_timeout)
+ consume_commits_response(response)
rescue GRPC::NotFound # If no repository is found, happens mainly during testing
[]
end
- def commits_by_message(query, revision: '', path: '', limit: 1000, offset: 0)
+ def commits_by_message(query, revision: '', path: '', limit: 1000, offset: 0, literal_pathspec: true)
request = Gitaly::CommitsByMessageRequest.new(
repository: @gitaly_repo,
query: query,
revision: encode_binary(revision),
path: encode_binary(path),
limit: limit.to_i,
- offset: offset.to_i
+ offset: offset.to_i,
+ global_options: parse_global_options!(literal_pathspec: literal_pathspec)
)
- GitalyClient.streaming_call(@repository.storage, :commit_service, :commits_by_message, request, timeout: GitalyClient.medium_timeout) do |response|
- consume_commits_response(response)
- end
+ response = GitalyClient.call(@repository.storage, :commit_service, :commits_by_message, request, timeout: GitalyClient.medium_timeout)
+ consume_commits_response(response)
end
def languages(ref = nil)
@@ -320,6 +317,7 @@ module Gitlab
skip_merges: options[:skip_merges],
all: !!options[:all],
first_parent: !!options[:first_parent],
+ global_options: parse_global_options!(options),
disable_walk: true # This option is deprecated. The 'walk' implementation is being removed.
)
request.after = GitalyClient.timestamp(options[:after]) if options[:after]
@@ -330,9 +328,8 @@ module Gitlab
request.paths = encode_repeated(Array(options[:path])) if options[:path].present?
- GitalyClient.streaming_call(@repository.storage, :commit_service, :find_commits, request, timeout: GitalyClient.medium_timeout) do |response|
- consume_commits_response(response)
- end
+ response = GitalyClient.call(@repository.storage, :commit_service, :find_commits, request, timeout: GitalyClient.medium_timeout)
+ consume_commits_response(response)
end
def filter_shas_with_signatures(shas)
@@ -349,7 +346,6 @@ module Gitlab
end
response = GitalyClient.call(@repository.storage, :commit_service, :filter_shas_with_signatures, enum, timeout: GitalyClient.fast_timeout)
-
response.flat_map do |msg|
msg.shas.map { |sha| EncodingHelper.encode!(sha) }
end
@@ -390,8 +386,28 @@ module Gitlab
messages
end
+ def list_commits_by_ref_name(refs)
+ request = Gitaly::ListCommitsByRefNameRequest
+ .new(repository: @gitaly_repo, ref_names: refs.map { |ref| encode_binary(ref) })
+
+ response = GitalyClient.call(@repository.storage, :commit_service, :list_commits_by_ref_name, request, timeout: GitalyClient.medium_timeout)
+
+ commit_refs = response.flat_map do |message|
+ message.commit_refs.map do |commit_ref|
+ [encode_utf8(commit_ref.ref_name), Gitlab::Git::Commit.new(@repository, commit_ref.commit)]
+ end
+ end
+
+ Hash[commit_refs]
+ end
+
private
+ def parse_global_options!(options)
+ literal_pathspec = options.delete(:literal_pathspec)
+ Gitaly::GlobalOptions.new(literal_pathspecs: literal_pathspec)
+ end
+
def call_commit_diff(request_params, options = {})
request_params[:ignore_whitespace_change] = options.fetch(:ignore_whitespace_change, false)
request_params[:enforce_limits] = options.fetch(:limits, true)
diff --git a/lib/gitlab/gitaly_client/conflicts_service.rb b/lib/gitlab/gitaly_client/conflicts_service.rb
index f7eb4b45197..6f08dcc69b6 100644
--- a/lib/gitlab/gitaly_client/conflicts_service.rb
+++ b/lib/gitlab/gitaly_client/conflicts_service.rb
@@ -21,7 +21,6 @@ module Gitlab
their_commit_oid: @their_commit_oid
)
response = GitalyClient.call(@repository.storage, :conflicts_service, :list_conflict_files, request, timeout: GitalyClient.long_timeout)
-
GitalyClient::ConflictFilesStitcher.new(response, @gitaly_repo)
end
diff --git a/lib/gitlab/gitaly_client/operation_service.rb b/lib/gitlab/gitaly_client/operation_service.rb
index 9ed4b2da09a..87505418ae9 100644
--- a/lib/gitlab/gitaly_client/operation_service.rb
+++ b/lib/gitlab/gitaly_client/operation_service.rb
@@ -178,6 +178,10 @@ module Gitlab
timeout: GitalyClient.long_timeout
)
+ if response.pre_receive_error.present?
+ raise Gitlab::Git::PreReceiveError.new("GL-HOOK-ERR: pre-receive hook failed.")
+ end
+
Gitlab::Git::OperationService::BranchUpdate.from_gitaly(response.branch_update)
rescue GRPC::FailedPrecondition => e
raise Gitlab::Git::CommitError, e
diff --git a/lib/gitlab/gitaly_client/ref_service.rb b/lib/gitlab/gitaly_client/ref_service.rb
index 63def4e29c9..97b6813c080 100644
--- a/lib/gitlab/gitaly_client/ref_service.rb
+++ b/lib/gitlab/gitaly_client/ref_service.rb
@@ -15,14 +15,12 @@ module Gitlab
def branches
request = Gitaly::FindAllBranchesRequest.new(repository: @gitaly_repo)
response = GitalyClient.call(@storage, :ref_service, :find_all_branches, request, timeout: GitalyClient.fast_timeout)
-
consume_find_all_branches_response(response)
end
def remote_branches(remote_name)
request = Gitaly::FindAllRemoteBranchesRequest.new(repository: @gitaly_repo, remote_name: remote_name)
- response = GitalyClient.call(@repository.storage, :ref_service, :find_all_remote_branches, request, timeout: GitalyClient.medium_timeout)
-
+ response = GitalyClient.call(@storage, :ref_service, :find_all_remote_branches, request, timeout: GitalyClient.medium_timeout)
consume_find_all_remote_branches_response(remote_name, response)
end
@@ -33,7 +31,6 @@ module Gitlab
merged_branches: branch_names.map { |s| encode_binary(s) }
)
response = GitalyClient.call(@storage, :ref_service, :find_all_branches, request, timeout: GitalyClient.fast_timeout)
-
consume_find_all_branches_response(response)
end
@@ -71,10 +68,9 @@ module Gitlab
commit_id: newrev
)
- response = GitalyClient
- .call(@storage, :ref_service, :list_new_commits, request, timeout: GitalyClient.medium_timeout)
-
commits = []
+
+ response = GitalyClient.call(@storage, :ref_service, :list_new_commits, request, timeout: GitalyClient.medium_timeout)
response.each do |msg|
msg.commits.each do |c|
commits << Gitlab::Git::Commit.new(@repository, c)
@@ -98,9 +94,7 @@ module Gitlab
GitalyClient.medium_timeout
end
- response = GitalyClient
- .call(@storage, :ref_service, :list_new_blobs, request, timeout: timeout)
-
+ response = GitalyClient.call(@storage, :ref_service, :list_new_blobs, request, timeout: timeout)
response.flat_map do |msg|
# Returns an Array of Gitaly::NewBlobObject objects
# Available methods are: #size, #oid and #path
@@ -116,8 +110,8 @@ module Gitlab
branch_names.count
end
- def local_branches(sort_by: nil)
- request = Gitaly::FindLocalBranchesRequest.new(repository: @gitaly_repo)
+ def local_branches(sort_by: nil, pagination_params: nil)
+ request = Gitaly::FindLocalBranchesRequest.new(repository: @gitaly_repo, pagination_params: pagination_params)
request.sort_by = sort_by_param(sort_by) if sort_by
response = GitalyClient.call(@storage, :ref_service, :find_local_branches, request, timeout: GitalyClient.fast_timeout)
consume_find_local_branches_response(response)
@@ -171,9 +165,8 @@ module Gitlab
limit: limit
)
- stream = GitalyClient.call(@repository.storage, :ref_service, :list_tag_names_containing_commit, request, timeout: GitalyClient.medium_timeout)
-
- consume_ref_contains_sha_response(stream, :tag_names)
+ response = GitalyClient.call(@storage, :ref_service, :list_tag_names_containing_commit, request, timeout: GitalyClient.medium_timeout)
+ consume_ref_contains_sha_response(response, :tag_names)
end
# Limit: 0 implies no limit, thus all tag names will be returned
@@ -184,18 +177,16 @@ module Gitlab
limit: limit
)
- stream = GitalyClient.call(@repository.storage, :ref_service, :list_branch_names_containing_commit, request, timeout: GitalyClient.medium_timeout)
-
- consume_ref_contains_sha_response(stream, :branch_names)
+ response = GitalyClient.call(@storage, :ref_service, :list_branch_names_containing_commit, request, timeout: GitalyClient.medium_timeout)
+ consume_ref_contains_sha_response(response, :branch_names)
end
def get_tag_messages(tag_ids)
request = Gitaly::GetTagMessagesRequest.new(repository: @gitaly_repo, tag_ids: tag_ids)
- response = GitalyClient.call(@repository.storage, :ref_service, :get_tag_messages, request, timeout: GitalyClient.fast_timeout)
-
messages = Hash.new { |h, k| h[k] = +''.b }
current_tag_id = nil
+ response = GitalyClient.call(@storage, :ref_service, :get_tag_messages, request, timeout: GitalyClient.fast_timeout)
response.each do |rpc_message|
current_tag_id = rpc_message.tag_id if rpc_message.tag_id.present?
diff --git a/lib/gitlab/gitaly_client/remote_service.rb b/lib/gitlab/gitaly_client/remote_service.rb
index 4566c59bbe0..06aaf460751 100644
--- a/lib/gitlab/gitaly_client/remote_service.rb
+++ b/lib/gitlab/gitaly_client/remote_service.rb
@@ -8,9 +8,11 @@ module Gitlab
MAX_MSG_SIZE = 128.kilobytes.freeze
def self.exists?(remote_url)
- request = Gitaly::FindRemoteRepositoryRequest.new(remote: remote_url)
+ storage = GitalyClient.random_storage
- response = GitalyClient.call(GitalyClient.random_storage,
+ request = Gitaly::FindRemoteRepositoryRequest.new(remote: remote_url, storage_name: storage)
+
+ response = GitalyClient.call(storage,
:remote_service,
:find_remote_repository, request,
timeout: GitalyClient.medium_timeout)
diff --git a/lib/gitlab/gitaly_client/repository_service.rb b/lib/gitlab/gitaly_client/repository_service.rb
index f74c9ea4192..20ad6d0184b 100644
--- a/lib/gitlab/gitaly_client/repository_service.rb
+++ b/lib/gitlab/gitaly_client/repository_service.rb
@@ -201,9 +201,9 @@ module Gitlab
response = GitalyClient.call(@storage, :repository_service, :fsck, request, timeout: GitalyClient.long_timeout)
if response.error.empty?
- return "", 0
+ ["", 0]
else
- return response.error.b, 1
+ [response.error.b, 1]
end
end
@@ -335,7 +335,6 @@ module Gitlab
def search_files_by_content(ref, query, options = {})
request = Gitaly::SearchFilesByContentRequest.new(repository: @gitaly_repo, ref: ref, query: query)
response = GitalyClient.call(@storage, :repository_service, :search_files_by_content, request, timeout: GitalyClient.default_timeout)
-
search_results_from_response(response, options)
end
@@ -410,7 +409,10 @@ module Gitlab
request,
timeout: timeout
)
+ write_stream_to_file(response, save_path)
+ end
+ def write_stream_to_file(response, save_path)
File.open(save_path, 'wb') do |f|
response.each do |message|
f.write(message.data)
diff --git a/lib/gitlab/gl_repository.rb b/lib/gitlab/gl_repository.rb
index abd4e847a50..7346de13626 100644
--- a/lib/gitlab/gl_repository.rb
+++ b/lib/gitlab/gl_repository.rb
@@ -43,10 +43,10 @@ module Gitlab
end
def self.parse(gl_repository)
- result = ::Gitlab::GlRepository::Identifier.new(gl_repository)
+ identifier = ::Gitlab::GlRepository::Identifier.parse(gl_repository)
- repo_type = result.repo_type
- container = result.fetch_container!
+ repo_type = identifier.repo_type
+ container = identifier.container
[container, repo_type.project_for(container), repo_type]
end
diff --git a/lib/gitlab/gl_repository/identifier.rb b/lib/gitlab/gl_repository/identifier.rb
index dc3e7931696..57350b1edb0 100644
--- a/lib/gitlab/gl_repository/identifier.rb
+++ b/lib/gitlab/gl_repository/identifier.rb
@@ -3,71 +3,83 @@
module Gitlab
class GlRepository
class Identifier
- attr_reader :gl_repository, :repo_type
+ include Gitlab::Utils::StrongMemoize
- def initialize(gl_repository)
- @gl_repository = gl_repository
- @segments = gl_repository.split('-')
+ InvalidIdentifier = Class.new(ArgumentError)
- raise_error if segments.size > 3
+ def self.parse(gl_repository)
+ segments = gl_repository&.split('-')
- @repo_type = find_repo_type
- @container_id = find_container_id
- @container_class = find_container_class
- end
+ # gl_repository can either have 2 or 3 segments:
+ #
+ # TODO: convert all 2-segment format to 3-segment:
+ # https://gitlab.com/gitlab-org/gitlab/-/issues/219192
+ identifier = case segments&.size
+ when 2
+ TwoPartIdentifier.new(*segments)
+ when 3
+ ThreePartIdentifier.new(*segments)
+ end
+
+ return identifier if identifier&.valid?
- def fetch_container!
- container_class.find_by_id(container_id)
+ raise InvalidIdentifier, %Q(Invalid GL Repository "#{gl_repository}")
end
- private
+ # The older 2-segment format, where the container is implied.
+ # eg. project-1, wiki-1
+ class TwoPartIdentifier < Identifier
+ def initialize(repo_type_name, container_id_str)
+ @container_id_str = container_id_str
+ @repo_type_name = repo_type_name
+ end
- attr_reader :segments, :container_class, :container_id
+ private
- def find_repo_type
- type_name = three_segments_format? ? segments.last : segments.first
- type = Gitlab::GlRepository.types[type_name]
+ def container_class
+ repo_type.container_class
+ end
+ end
- raise_error unless type
+ # The newer 3-segment format, where the container is explicit
+ # eg. group-1-wiki, project-1-wiki
+ class ThreePartIdentifier < Identifier
+ def initialize(container_type, container_id_str, repo_type_name)
+ @container_id_str = container_id_str
+ @container_type = container_type
+ @repo_type_name = repo_type_name
+ end
- type
- end
+ private
- def find_container_class
- if three_segments_format?
- case segments[0]
+ def container_class
+ case @container_type
when 'project'
Project
when 'group'
Group
- else
- raise_error
end
- else
- repo_type.container_class
end
end
- def find_container_id
- id = Integer(segments[1], 10, exception: false)
-
- raise_error unless id
+ def repo_type
+ strong_memoize(:repo_type) { Gitlab::GlRepository.types[repo_type_name] }
+ end
- id
+ def container
+ strong_memoize(:container) { container_class.find_by_id(container_id) }
end
- # gl_repository can either have 2 or 3 segments:
- # "wiki-1" is the older 2-segment format, where container is implied.
- # "group-1-wiki" is the newer 3-segment format, including container information.
- #
- # TODO: convert all 2-segment format to 3-segment:
- # https://gitlab.com/gitlab-org/gitlab/-/issues/219192
- def three_segments_format?
- segments.size == 3
+ def valid?
+ repo_type.present? && container_class.present? && container_id&.positive?
end
- def raise_error
- raise ArgumentError, "Invalid GL Repository \"#{gl_repository}\""
+ private
+
+ attr_reader :container_id_str, :repo_type_name
+
+ def container_id
+ strong_memoize(:container_id) { Integer(container_id_str, 10, exception: false) }
end
end
end
diff --git a/lib/gitlab/global_id.rb b/lib/gitlab/global_id.rb
index cc82b6c5897..e8a6006dce1 100644
--- a/lib/gitlab/global_id.rb
+++ b/lib/gitlab/global_id.rb
@@ -2,6 +2,8 @@
module Gitlab
module GlobalId
+ CoerceError = Class.new(ArgumentError)
+
def self.build(object = nil, model_name: nil, id: nil, params: nil)
if object
model_name ||= object.class.name
@@ -10,5 +12,20 @@ module Gitlab
::URI::GID.build(app: GlobalID.app, model_name: model_name, model_id: id, params: params)
end
+
+ def self.as_global_id(value, model_name: nil)
+ case value
+ when GlobalID
+ value
+ when URI::GID
+ GlobalID.new(value)
+ when Integer
+ raise CoerceError, 'Cannot coerce Integer' unless model_name.present?
+
+ GlobalID.new(::Gitlab::GlobalId.build(model_name: model_name, id: value))
+ else
+ raise CoerceError, "Invalid ID. Cannot coerce instances of #{value.class}"
+ end
+ end
end
end
diff --git a/lib/gitlab/graphql/authorize/authorize_resource.rb b/lib/gitlab/graphql/authorize/authorize_resource.rb
index 94871498cf8..27673e5c27a 100644
--- a/lib/gitlab/graphql/authorize/authorize_resource.rb
+++ b/lib/gitlab/graphql/authorize/authorize_resource.rb
@@ -30,8 +30,7 @@ module Gitlab
end
def authorized_find!(*args)
- object = find_object(*args)
- object = object.sync if object.respond_to?(:sync)
+ object = Graphql::Lazy.force(find_object(*args))
authorize!(object)
diff --git a/lib/gitlab/graphql/lazy.rb b/lib/gitlab/graphql/lazy.rb
new file mode 100644
index 00000000000..a7f7610a041
--- /dev/null
+++ b/lib/gitlab/graphql/lazy.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Graphql
+ class Lazy
+ # Force evaluation of a (possibly) lazy value
+ def self.force(value)
+ case value
+ when ::BatchLoader::GraphQL
+ value.sync
+ when ::Concurrent::Promise
+ value.execute.value
+ else
+ value
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/graphql/loaders/issuable_loader.rb b/lib/gitlab/graphql/loaders/issuable_loader.rb
new file mode 100644
index 00000000000..1cc0fbe215f
--- /dev/null
+++ b/lib/gitlab/graphql/loaders/issuable_loader.rb
@@ -0,0 +1,82 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Graphql
+ module Loaders
+ class IssuableLoader
+ attr_reader :parent, :issuable_finder
+
+ BatchKey = Struct.new(:parent, :finder_class, :current_user)
+
+ def initialize(parent, issuable_finder)
+ @parent = parent
+ @issuable_finder = issuable_finder
+ end
+
+ def batching_find_all(&with_query)
+ if issuable_finder.params.keys == ['iids']
+ batch_load_issuables(issuable_finder.params[:iids], with_query)
+ else
+ post_process(find_all, with_query)
+ end
+ end
+
+ def find_all
+ issuable_finder.params[parent_param] = parent if parent
+
+ issuable_finder.execute
+ end
+
+ private
+
+ def parent_param
+ case parent
+ when Project
+ :project_id
+ when Group
+ :group_id
+ else
+ raise "Unexpected parent: #{parent.class}"
+ end
+ end
+
+ def post_process(query, with_query)
+ if with_query
+ with_query.call(query)
+ else
+ query
+ end
+ end
+
+ def batch_load_issuables(iids, with_query)
+ Array.wrap(iids).map { |iid| batch_load(iid, with_query) }
+ end
+
+ def batch_load(iid, with_query)
+ return if parent.nil?
+
+ BatchLoader::GraphQL
+ .for([parent_param, iid.to_s])
+ .batch(key: batch_key) do |params, loader, args|
+ batch_key = args[:key]
+ user = batch_key.current_user
+
+ params.group_by(&:first).each do |key, group|
+ iids = group.map(&:second).uniq
+ args = { key => batch_key.parent, iids: iids }
+ query = batch_key.finder_class.new(user, args).execute
+
+ post_process(query, with_query).each do |item|
+ loader.call([key, item.iid.to_s], item)
+ end
+ end
+ end
+ end
+
+ def batch_key
+ BatchKey.new(parent, issuable_finder.class, issuable_finder.current_user)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/graphql/mount_mutation.rb b/lib/gitlab/graphql/mount_mutation.rb
index b10e963170a..8e507ba4531 100644
--- a/lib/gitlab/graphql/mount_mutation.rb
+++ b/lib/gitlab/graphql/mount_mutation.rb
@@ -13,6 +13,14 @@ module Gitlab
mutation: mutation_class,
**custom_kwargs
end
+
+ def mount_aliased_mutation(alias_name, mutation_class, **custom_kwargs)
+ aliased_mutation_class = Class.new(mutation_class) do
+ graphql_name alias_name
+ end
+
+ mount_mutation(aliased_mutation_class, **custom_kwargs)
+ end
end
end
end
diff --git a/lib/gitlab/graphql/query_analyzers/logger_analyzer.rb b/lib/gitlab/graphql/query_analyzers/logger_analyzer.rb
index 6f705239fa3..6b6bb72eb31 100644
--- a/lib/gitlab/graphql/query_analyzers/logger_analyzer.rb
+++ b/lib/gitlab/graphql/query_analyzers/logger_analyzer.rb
@@ -52,8 +52,7 @@ module Gitlab
end
def duration(time_started)
- nanoseconds = Gitlab::Metrics::System.monotonic_time - time_started
- nanoseconds * 1000000
+ Gitlab::Metrics::System.monotonic_time - time_started
end
def default_initial_values(query)
diff --git a/lib/gitlab/health_checks/probes/collection.rb b/lib/gitlab/health_checks/probes/collection.rb
index db3ef4834c2..08b6d82291e 100644
--- a/lib/gitlab/health_checks/probes/collection.rb
+++ b/lib/gitlab/health_checks/probes/collection.rb
@@ -20,6 +20,12 @@ module Gitlab
success ? 200 : 503,
status(success).merge(payload(readiness))
)
+ rescue => e
+ exception_payload = { message: "#{e.class} : #{e.message}" }
+
+ Probes::Status.new(
+ 500,
+ status(false).merge(exception_payload))
end
private
diff --git a/lib/gitlab/import/metrics.rb b/lib/gitlab/import/metrics.rb
index 76638a8cf86..2692ab2fa12 100644
--- a/lib/gitlab/import/metrics.rb
+++ b/lib/gitlab/import/metrics.rb
@@ -1,59 +1,54 @@
# frozen_string_literal: true
-# Prepend `Gitlab::Import::Metrics` to a class in order
-# to measure and emit `Gitlab::Metrics` metrics of specified methods.
-#
-# @example
-# class Importer
-# prepend Gitlab::Import::Metrics
-#
-# Gitlab::ImportExport::Metrics.measure :execute, metrics: {
-# importer_counter: {
-# type: :counter,
-# description: 'counter'
-# },
-# importer_histogram: {
-# type: :histogram,
-# labels: { importer: 'importer' },
-# description: 'histogram'
-# }
-# }
-#
-# def execute
-# ...
-# end
-# end
-#
-# Each call to `#execute` increments `importer_counter` as well as
-# measures `#execute` duration and reports histogram `importer_histogram`
module Gitlab
module Import
- module Metrics
- def self.measure(method_name, metrics:)
- define_method "#{method_name}" do |*args|
- start_time = Time.zone.now
+ class Metrics
+ IMPORT_DURATION_BUCKETS = [0.5, 1, 3, 5, 10, 60, 120, 240, 360, 720, 1440].freeze
- result = super(*args)
+ attr_reader :importer
- end_time = Time.zone.now
+ def initialize(importer, project)
+ @importer = importer
+ @project = project
+ end
+
+ def track_finished_import
+ duration = Time.zone.now - @project.created_at
+
+ duration_histogram.observe({ importer: importer }, duration)
+ projects_counter.increment
+ end
- report_measurement_metrics(metrics, end_time - start_time)
+ def projects_counter
+ @projects_counter ||= Gitlab::Metrics.counter(
+ :"#{importer}_imported_projects_total",
+ 'The number of imported projects'
+ )
+ end
+
+ def issues_counter
+ @issues_counter ||= Gitlab::Metrics.counter(
+ :"#{importer}_imported_issues_total",
+ 'The number of imported issues'
+ )
+ end
- result
- end
+ def merge_requests_counter
+ @merge_requests_counter ||= Gitlab::Metrics.counter(
+ :"#{importer}_imported_merge_requests_total",
+ 'The number of imported merge (pull) requests'
+ )
end
- def report_measurement_metrics(metrics, duration)
- metrics.each do |metric_name, metric_value|
- case metric_value[:type]
- when :counter
- Gitlab::Metrics.counter(metric_name, metric_value[:description]).increment
- when :histogram
- Gitlab::Metrics.histogram(metric_name, metric_value[:description]).observe(metric_value[:labels], duration)
- else
- nil
- end
- end
+ private
+
+ def duration_histogram
+ @duration_histogram ||= Gitlab::Metrics.histogram(
+ :"#{importer}_total_duration_seconds",
+ 'Total time spent importing projects, in seconds',
+ {},
+ IMPORT_DURATION_BUCKETS
+ )
end
end
end
diff --git a/lib/gitlab/import_export/json/streaming_serializer.rb b/lib/gitlab/import_export/json/streaming_serializer.rb
index 20f9c668b9c..05b7679e0ff 100644
--- a/lib/gitlab/import_export/json/streaming_serializer.rb
+++ b/lib/gitlab/import_export/json/streaming_serializer.rb
@@ -7,7 +7,7 @@ module Gitlab
include Gitlab::ImportExport::CommandLineUtil
BATCH_SIZE = 100
- SMALLER_BATCH_SIZE = 20
+ SMALLER_BATCH_SIZE = 2
def self.batch_size(exportable)
if Feature.enabled?(:export_reduce_relation_batch_size, exportable)
@@ -69,8 +69,16 @@ module Gitlab
key_preloads = preloads&.dig(key)
records = records.preload(key_preloads) if key_preloads
- records.find_each(batch_size: batch_size) do |record|
- items << Raw.new(record.to_json(options))
+ records.in_batches(of: batch_size) do |batch| # rubocop:disable Cop/InBatches
+ # order each batch by its primary key to ensure
+ # consistent and predictable ordering of each exported relation
+ # as additional `WHERE` clauses can impact the order in which data is being
+ # returned by database when no `ORDER` is specified
+ batch = batch.reorder(batch.klass.primary_key)
+
+ batch.each do |record|
+ items << Raw.new(record.to_json(options))
+ end
end
end
diff --git a/lib/gitlab/import_export/project/import_export.yml b/lib/gitlab/import_export/project/import_export.yml
index f0b733d7e95..aa961bd8d19 100644
--- a/lib/gitlab/import_export/project/import_export.yml
+++ b/lib/gitlab/import_export/project/import_export.yml
@@ -89,7 +89,6 @@ tree:
- :triggers
- :pipeline_schedules
- :container_expiration_policy
- - :services
- protected_branches:
- :merge_access_levels
- :push_access_levels
@@ -169,6 +168,7 @@ excluded_attributes:
- :marked_for_deletion_by_user_id
- :compliance_framework_setting
- :show_default_award_emojis
+ - :services
namespaces:
- :runners_token
- :runners_token_encrypted
@@ -261,10 +261,6 @@ excluded_attributes:
runners:
- :token
- :token_encrypted
- services:
- - :inherit_from_id
- - :instance
- - :template
error_tracking_setting:
- :encrypted_token
- :encrypted_token_iv
@@ -313,12 +309,14 @@ excluded_attributes:
- :merge_request_id
- :external_pull_request_id
- :ci_ref_id
+ - :locked
stages:
- :pipeline_id
merge_access_levels:
- :protected_branch_id
push_access_levels:
- :protected_branch_id
+ - :deploy_key_id
unprotect_access_levels:
- :protected_branch_id
create_access_levels:
@@ -353,8 +351,6 @@ methods:
- :type
statuses:
- :type
- services:
- - :type
merge_request_diff_files:
- :utf8_diff
merge_requests:
diff --git a/lib/gitlab/import_export/project/relation_factory.rb b/lib/gitlab/import_export/project/relation_factory.rb
index 3ab9f2c4bfa..ae92228276e 100644
--- a/lib/gitlab/import_export/project/relation_factory.rb
+++ b/lib/gitlab/import_export/project/relation_factory.rb
@@ -70,10 +70,8 @@ module Gitlab
private
def invalid_relation?
- # Do not create relation if it is:
- # - An unknown service
- # - A legacy trigger
- unknown_service? || legacy_trigger?
+ # Do not create relation if it is a legacy trigger
+ legacy_trigger?
end
def setup_models
@@ -137,11 +135,6 @@ module Gitlab
end
end
- def unknown_service?
- @relation_name == :services && parsed_relation_hash['type'] &&
- !Object.const_defined?(parsed_relation_hash['type'])
- end
-
def legacy_trigger?
@relation_name == :'Ci::Trigger' && @relation_hash['owner_id'].nil?
end
diff --git a/lib/gitlab/import_export/snippet_repo_restorer.rb b/lib/gitlab/import_export/snippet_repo_restorer.rb
index 31b1a37bbe1..2d0aa05fc3c 100644
--- a/lib/gitlab/import_export/snippet_repo_restorer.rb
+++ b/lib/gitlab/import_export/snippet_repo_restorer.rb
@@ -42,6 +42,8 @@ module Gitlab
snippet.repository.expire_exists_cache
raise SnippetRepositoryError, _("Invalid repository bundle for snippet with id %{snippet_id}") % { snippet_id: snippet.id }
+ else
+ Snippets::UpdateStatisticsService.new(snippet).execute
end
end
diff --git a/lib/gitlab/incident_management/pager_duty/incident_issue_description.rb b/lib/gitlab/incident_management/pager_duty/incident_issue_description.rb
new file mode 100644
index 00000000000..cd947b15154
--- /dev/null
+++ b/lib/gitlab/incident_management/pager_duty/incident_issue_description.rb
@@ -0,0 +1,64 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module IncidentManagement
+ module PagerDuty
+ class IncidentIssueDescription
+ def initialize(incident_payload)
+ @incident_payload = incident_payload
+ end
+
+ def to_s
+ markdown_line_break = " \n"
+
+ [
+ "**Incident:** #{markdown_incident}",
+ "**Incident number:** #{incident_payload['incident_number']}",
+ "**Urgency:** #{incident_payload['urgency']}",
+ "**Status:** #{incident_payload['status']}",
+ "**Incident key:** #{incident_payload['incident_key']}",
+ "**Created at:** #{markdown_incident_created_at}",
+ "**Assignees:** #{markdown_assignees.join(', ')}",
+ "**Impacted services:** #{markdown_impacted_services.join(', ')}"
+ ].join(markdown_line_break)
+ end
+
+ private
+
+ attr_reader :incident_payload
+
+ def markdown_incident
+ markdown_link(incident_payload['title'], incident_payload['url'])
+ end
+
+ def incident_created_at
+ Time.parse(incident_payload['created_at'])
+ rescue
+ Time.current.utc # PagerDuty provides time in UTC
+ end
+
+ def markdown_incident_created_at
+ incident_created_at.strftime('%d %B %Y, %-l:%M%p (%Z)')
+ end
+
+ def markdown_assignees
+ Array(incident_payload['assignees']).map do |assignee|
+ markdown_link(assignee['summary'], assignee['url'])
+ end
+ end
+
+ def markdown_impacted_services
+ Array(incident_payload['impacted_services']).map do |is|
+ markdown_link(is['summary'], is['url'])
+ end
+ end
+
+ def markdown_link(label, url)
+ return label if url.blank?
+
+ "[#{label}](#{url})"
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/instrumentation/elasticsearch_transport.rb b/lib/gitlab/instrumentation/elasticsearch_transport.rb
index deee0127c0c..56179eda22d 100644
--- a/lib/gitlab/instrumentation/elasticsearch_transport.rb
+++ b/lib/gitlab/instrumentation/elasticsearch_transport.rb
@@ -5,8 +5,10 @@ require 'elasticsearch-transport'
module Gitlab
module Instrumentation
module ElasticsearchTransportInterceptor
- def perform_request(*args)
+ def perform_request(method, path, params = {}, body = nil, headers = nil)
start = Time.now
+ headers = (headers || {})
+ .reverse_merge({ 'X-Opaque-Id': Labkit::Correlation::CorrelationId.current_or_new_id })
super
ensure
if ::Gitlab::SafeRequestStore.active?
@@ -14,7 +16,7 @@ module Gitlab
::Gitlab::Instrumentation::ElasticsearchTransport.increment_request_count
::Gitlab::Instrumentation::ElasticsearchTransport.add_duration(duration)
- ::Gitlab::Instrumentation::ElasticsearchTransport.add_call_details(duration, args)
+ ::Gitlab::Instrumentation::ElasticsearchTransport.add_call_details(duration, method, path, params, body)
end
end
end
@@ -47,14 +49,14 @@ module Gitlab
::Gitlab::SafeRequestStore[ELASTICSEARCH_CALL_DURATION] += duration
end
- def self.add_call_details(duration, args)
+ def self.add_call_details(duration, method, path, params, body)
return unless Gitlab::PerformanceBar.enabled_for_request?
detail_store << {
- method: args[0],
- path: args[1],
- params: args[2],
- body: args[3],
+ method: method,
+ path: path,
+ params: params,
+ body: body,
duration: duration,
backtrace: ::Gitlab::BacktraceCleaner.clean_backtrace(caller)
}
diff --git a/lib/gitlab/instrumentation/redis.rb b/lib/gitlab/instrumentation/redis.rb
index 82b4701872f..4a85a313fd7 100644
--- a/lib/gitlab/instrumentation/redis.rb
+++ b/lib/gitlab/instrumentation/redis.rb
@@ -5,9 +5,9 @@ module Gitlab
# Aggregates Redis measurements from different request storage sources.
class Redis
ActionCable = Class.new(RedisBase)
- Cache = Class.new(RedisBase)
+ Cache = Class.new(RedisBase).enable_redis_cluster_validation
Queues = Class.new(RedisBase)
- SharedState = Class.new(RedisBase)
+ SharedState = Class.new(RedisBase).enable_redis_cluster_validation
STORAGES = [ActionCable, Cache, Queues, SharedState].freeze
diff --git a/lib/gitlab/instrumentation/redis_base.rb b/lib/gitlab/instrumentation/redis_base.rb
index 012543e1645..1df899747e0 100644
--- a/lib/gitlab/instrumentation/redis_base.rb
+++ b/lib/gitlab/instrumentation/redis_base.rb
@@ -25,9 +25,6 @@ module Gitlab
# redis-rb passes an array (e.g. [[:get, key]])
return unless args.length == 1
- # TODO: Add information about current Redis client
- # being instrumented.
- # https://gitlab.com/gitlab-com/gl-infra/scalability/-/issues/316.
detail_store << {
cmd: args.first,
duration: duration,
@@ -71,6 +68,40 @@ module Gitlab
query_time.round(::Gitlab::InstrumentationHelper::DURATION_PRECISION)
end
+ def redis_cluster_validate!(command)
+ ::Gitlab::Instrumentation::RedisClusterValidator.validate!(command) if @redis_cluster_validation
+ end
+
+ def enable_redis_cluster_validation
+ @redis_cluster_validation = true
+
+ self
+ end
+
+ def instance_count_request
+ @request_counter ||= Gitlab::Metrics.counter(:gitlab_redis_client_requests_total, 'Client side Redis request count, per Redis server')
+ @request_counter.increment({ storage: storage_key })
+ end
+
+ def instance_count_exception(ex)
+ # This metric is meant to give a client side view of how the Redis
+ # server is doing. Redis itself does not expose error counts. This
+ # metric can be used for Redis alerting and service health monitoring.
+ @exception_counter ||= Gitlab::Metrics.counter(:gitlab_redis_client_exceptions_total, 'Client side Redis exception count, per Redis server, per exception class')
+ @exception_counter.increment({ storage: storage_key, exception: ex.class.to_s })
+ end
+
+ def instance_observe_duration(duration)
+ @request_latency_histogram ||= Gitlab::Metrics.histogram(
+ :gitlab_redis_client_requests_duration_seconds,
+ 'Client side Redis request latency, per Redis server, excluding blocking commands',
+ {},
+ [0.005, 0.01, 0.1, 0.5]
+ )
+
+ @request_latency_histogram.observe({ storage: storage_key }, duration)
+ end
+
private
def request_count_key
diff --git a/lib/gitlab/instrumentation/redis_cluster_validator.rb b/lib/gitlab/instrumentation/redis_cluster_validator.rb
new file mode 100644
index 00000000000..6800e5667f6
--- /dev/null
+++ b/lib/gitlab/instrumentation/redis_cluster_validator.rb
@@ -0,0 +1,106 @@
+# frozen_string_literal: true
+
+require 'rails'
+require 'redis'
+
+module Gitlab
+ module Instrumentation
+ module RedisClusterValidator
+ # Generate with:
+ #
+ # Gitlab::Redis::Cache
+ # .with { |redis| redis.call('COMMAND') }
+ # .select { |command| command[3] != command[4] }
+ # .map { |command| [command[0].upcase, { first: command[3], last: command[4], step: command[5] }] }
+ # .sort_by(&:first)
+ # .to_h
+ #
+ MULTI_KEY_COMMANDS = {
+ "BITOP" => { first: 2, last: -1, step: 1 },
+ "BLPOP" => { first: 1, last: -2, step: 1 },
+ "BRPOP" => { first: 1, last: -2, step: 1 },
+ "BRPOPLPUSH" => { first: 1, last: 2, step: 1 },
+ "BZPOPMAX" => { first: 1, last: -2, step: 1 },
+ "BZPOPMIN" => { first: 1, last: -2, step: 1 },
+ "DEL" => { first: 1, last: -1, step: 1 },
+ "EXISTS" => { first: 1, last: -1, step: 1 },
+ "MGET" => { first: 1, last: -1, step: 1 },
+ "MSET" => { first: 1, last: -1, step: 2 },
+ "MSETNX" => { first: 1, last: -1, step: 2 },
+ "PFCOUNT" => { first: 1, last: -1, step: 1 },
+ "PFMERGE" => { first: 1, last: -1, step: 1 },
+ "RENAME" => { first: 1, last: 2, step: 1 },
+ "RENAMENX" => { first: 1, last: 2, step: 1 },
+ "RPOPLPUSH" => { first: 1, last: 2, step: 1 },
+ "SDIFF" => { first: 1, last: -1, step: 1 },
+ "SDIFFSTORE" => { first: 1, last: -1, step: 1 },
+ "SINTER" => { first: 1, last: -1, step: 1 },
+ "SINTERSTORE" => { first: 1, last: -1, step: 1 },
+ "SMOVE" => { first: 1, last: 2, step: 1 },
+ "SUNION" => { first: 1, last: -1, step: 1 },
+ "SUNIONSTORE" => { first: 1, last: -1, step: 1 },
+ "UNLINK" => { first: 1, last: -1, step: 1 },
+ "WATCH" => { first: 1, last: -1, step: 1 }
+ }.freeze
+
+ CrossSlotError = Class.new(StandardError)
+
+ class << self
+ def validate!(command)
+ return unless Rails.env.development? || Rails.env.test?
+ return if allow_cross_slot_commands?
+
+ command_name = command.first.to_s.upcase
+ argument_positions = MULTI_KEY_COMMANDS[command_name]
+
+ return unless argument_positions
+
+ arguments = command.flatten[argument_positions[:first]..argument_positions[:last]]
+
+ key_slots = arguments.each_slice(argument_positions[:step]).map do |args|
+ key_slot(args.first)
+ end
+
+ unless key_slots.uniq.length == 1
+ raise CrossSlotError.new("Redis command #{command_name} arguments hash to different slots. See https://docs.gitlab.com/ee/development/redis.html#multi-key-commands")
+ end
+ end
+
+ # Keep track of the call stack to allow nested calls to work.
+ def allow_cross_slot_commands
+ Thread.current[:allow_cross_slot_commands] ||= 0
+ Thread.current[:allow_cross_slot_commands] += 1
+
+ yield
+ ensure
+ Thread.current[:allow_cross_slot_commands] -= 1
+ end
+
+ private
+
+ def allow_cross_slot_commands?
+ Thread.current[:allow_cross_slot_commands].to_i > 0
+ end
+
+ def key_slot(key)
+ ::Redis::Cluster::KeySlotConverter.convert(extract_hash_tag(key))
+ end
+
+ # This is almost identical to Redis::Cluster::Command#extract_hash_tag,
+ # except that it returns the original string if no hash tag is found.
+ #
+ def extract_hash_tag(key)
+ s = key.index('{')
+
+ return key unless s
+
+ e = key.index('}', s + 1)
+
+ return key unless e
+
+ key[s + 1..e - 1]
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/instrumentation/redis_interceptor.rb b/lib/gitlab/instrumentation/redis_interceptor.rb
index a36aade59c3..b5a5f8fd984 100644
--- a/lib/gitlab/instrumentation/redis_interceptor.rb
+++ b/lib/gitlab/instrumentation/redis_interceptor.rb
@@ -5,13 +5,26 @@ require 'redis'
module Gitlab
module Instrumentation
module RedisInterceptor
+ APDEX_EXCLUDE = %w[brpop blpop brpoplpush bzpopmin bzpopmax xread xreadgroup].freeze
+
def call(*args, &block)
- start = Time.now
+ start = Time.now # must come first so that 'start' is always defined
+ instrumentation_class.instance_count_request
+ instrumentation_class.redis_cluster_validate!(args.first)
+
super(*args, &block)
+ rescue ::Redis::BaseError => ex
+ instrumentation_class.instance_count_exception(ex)
+ raise ex
ensure
- duration = (Time.now - start)
+ duration = Time.now - start
+
+ unless APDEX_EXCLUDE.include?(command_from_args(args))
+ instrumentation_class.instance_observe_duration(duration)
+ end
if ::RequestStore.active?
+ # These metrics measure total Redis usage per Rails request / job.
instrumentation_class.increment_request_count
instrumentation_class.add_duration(duration)
instrumentation_class.add_call_details(duration, args)
@@ -77,6 +90,12 @@ module Gitlab
def instrumentation_class
@options[:instrumentation_class] # rubocop:disable Gitlab/ModuleWithInstanceVariables
end
+
+ def command_from_args(args)
+ command = args[0]
+ command = command[0] if command.is_a?(Array)
+ command.to_s.downcase
+ end
end
end
end
diff --git a/lib/gitlab/issuable_metadata.rb b/lib/gitlab/issuable_metadata.rb
index e946fc00c4d..f96c937aec3 100644
--- a/lib/gitlab/issuable_metadata.rb
+++ b/lib/gitlab/issuable_metadata.rb
@@ -7,11 +7,13 @@ module Gitlab
# data structure to store issuable meta data like
# upvotes, downvotes, notes and closing merge requests counts for issues and merge requests
# this avoiding n+1 queries when loading issuable collections on frontend
- IssuableMeta = Struct.new(:upvotes, :downvotes, :user_notes_count, :mrs_count) do
- def merge_requests_count(user = nil)
- mrs_count
- end
- end
+ IssuableMeta = Struct.new(
+ :upvotes,
+ :downvotes,
+ :user_notes_count,
+ :merge_requests_count,
+ :blocking_issues_count # EE-ONLY
+ )
attr_reader :current_user, :issuable_collection
@@ -95,3 +97,5 @@ module Gitlab
end
end
end
+
+Gitlab::IssuableMetadata.prepend_if_ee('EE::Gitlab::IssuableMetadata')
diff --git a/lib/gitlab/jira_import/issue_serializer.rb b/lib/gitlab/jira_import/issue_serializer.rb
index df57680073e..43280606bb6 100644
--- a/lib/gitlab/jira_import/issue_serializer.rb
+++ b/lib/gitlab/jira_import/issue_serializer.rb
@@ -52,7 +52,9 @@ module Gitlab
end
def map_user_id(jira_user)
- Gitlab::JiraImport::UserMapper.new(project, jira_user).execute&.id
+ return unless jira_user&.dig('accountId')
+
+ Gitlab::JiraImport.get_user_mapping(project.id, jira_user['accountId'])
end
def reporter
diff --git a/lib/gitlab/jira_import/user_mapper.rb b/lib/gitlab/jira_import/user_mapper.rb
deleted file mode 100644
index 208ee49b724..00000000000
--- a/lib/gitlab/jira_import/user_mapper.rb
+++ /dev/null
@@ -1,53 +0,0 @@
-# frozen_string_literal: true
-
-module Gitlab
- module JiraImport
- class UserMapper
- include ::Gitlab::Utils::StrongMemoize
-
- def initialize(project, jira_user)
- @project = project
- @jira_user = jira_user
- end
-
- def execute
- return unless jira_user
-
- email = jira_user['emailAddress']
-
- # We also include emails that are not yet confirmed
- users = User.by_any_email(email).to_a
-
- user = users.first
-
- # this event should never happen but we should log it in case we have invalid data
- log_user_mapping_message('Multiple users found for an email address', email) if users.count > 1
-
- unless project.project_member(user) || project.group&.group_member(user)
- log_user_mapping_message('Jira user not found', email)
-
- return
- end
-
- user
- end
-
- private
-
- attr_reader :project, :jira_user, :params
-
- def log_user_mapping_message(message, email)
- logger.info(
- project_id: project.id,
- project_path: project.full_path,
- user_email: email,
- message: message
- )
- end
-
- def logger
- @logger ||= Gitlab::Import::Logger.build
- end
- end
- end
-end
diff --git a/lib/gitlab/json.rb b/lib/gitlab/json.rb
index 5b6689dbefe..21f837c58bb 100644
--- a/lib/gitlab/json.rb
+++ b/lib/gitlab/json.rb
@@ -1,59 +1,224 @@
# frozen_string_literal: true
+# This is a GitLab-specific JSON interface. You should use this instead
+# of using `JSON` directly. This allows us to swap the adapter and handle
+# legacy issues.
+
module Gitlab
module Json
INVALID_LEGACY_TYPES = [String, TrueClass, FalseClass].freeze
class << self
- def parse(string, *args, **named_args)
- legacy_mode = legacy_mode_enabled?(named_args.delete(:legacy_mode))
- data = adapter.parse(string, *args, **named_args)
+ # Parse a string and convert it to a Ruby object
+ #
+ # @param string [String] the JSON string to convert to Ruby objects
+ # @param opts [Hash] an options hash in the standard JSON gem format
+ # @return [Boolean, String, Array, Hash]
+ # @raise [JSON::ParserError] raised if parsing fails
+ def parse(string, opts = {})
+ # First we should ensure this really is a string, not some other
+ # type which purports to be a string. This handles some legacy
+ # usage of the JSON class.
+ string = string.to_s unless string.is_a?(String)
+
+ legacy_mode = legacy_mode_enabled?(opts.delete(:legacy_mode))
+ data = adapter_load(string, opts)
handle_legacy_mode!(data) if legacy_mode
data
end
- def parse!(string, *args, **named_args)
- legacy_mode = legacy_mode_enabled?(named_args.delete(:legacy_mode))
- data = adapter.parse!(string, *args, **named_args)
+ alias_method :parse!, :parse
+
+ # Restricted method for converting a Ruby object to JSON. If you
+ # need to pass options to this, you should use `.generate` instead,
+ # as the underlying implementation of this varies wildly based on
+ # the adapter in use.
+ #
+ # @param object [Object] the object to convert to JSON
+ # @return [String]
+ def dump(object)
+ adapter_dump(object)
+ end
- handle_legacy_mode!(data) if legacy_mode
+ # Generates JSON for an object. In Oj this takes fewer options than .dump,
+ # in the JSON gem this is the only method which takes an options argument.
+ #
+ # @param object [Hash, Array, Object] must be hash, array, or an object that responds to .to_h or .to_json
+ # @param opts [Hash] an options hash with fewer supported settings than .dump
+ # @return [String]
+ def generate(object, opts = {})
+ adapter_generate(object, opts)
+ end
- data
+ # Generates JSON for an object and makes it look purdy
+ #
+ # The Oj variant in this looks seriously weird but these are the settings
+ # needed to emulate the style generated by the JSON gem.
+ #
+ # NOTE: This currently ignores Oj, because Oj doesn't generate identical
+ # formatting, issue: https://github.com/ohler55/oj/issues/608
+ #
+ # @param object [Hash, Array, Object] must be hash, array, or an object that responds to .to_h or .to_json
+ # @param opts [Hash] an options hash with fewer supported settings than .dump
+ # @return [String]
+ def pretty_generate(object, opts = {})
+ ::JSON.pretty_generate(object, opts)
end
- def dump(*args)
- adapter.dump(*args)
+ # Feature detection for using Oj instead of the `json` gem.
+ #
+ # @return [Boolean]
+ def enable_oj?
+ return false unless feature_table_exists?
+
+ Feature.enabled?(:oj_json, default_enabled: true)
end
- def generate(*args)
- adapter.generate(*args)
+ private
+
+ # Convert JSON string into Ruby through toggleable adapters.
+ #
+ # Must rescue adapter-specific errors and return `parser_error`, and
+ # must also standardize the options hash to support each adapter as
+ # they all take different options.
+ #
+ # @param string [String] the JSON string to convert to Ruby objects
+ # @param opts [Hash] an options hash in the standard JSON gem format
+ # @return [Boolean, String, Array, Hash]
+ # @raise [JSON::ParserError]
+ def adapter_load(string, *args, **opts)
+ opts = standardize_opts(opts)
+
+ if enable_oj?
+ Oj.load(string, opts)
+ else
+ ::JSON.parse(string, opts)
+ end
+ rescue Oj::ParseError, Encoding::UndefinedConversionError => ex
+ raise parser_error.new(ex)
end
- def pretty_generate(*args)
- adapter.pretty_generate(*args)
+ # Take a Ruby object and convert it to a string. This method varies
+ # based on the underlying JSON interpreter. Oj treats this like JSON
+ # treats `.generate`. JSON.dump takes no options.
+ #
+ # This supports these options to ensure this difference is recorded here,
+ # as it's very surprising. The public interface is more restrictive to
+ # prevent adapter-specific options being passed.
+ #
+ # @overload adapter_dump(object, opts)
+ # @param object [Object] the object to convert to JSON
+ # @param opts [Hash] options as named arguments, only supported by Oj
+ #
+ # @overload adapter_dump(object, anIO, limit)
+ # @param object [Object] the object, will have JSON.generate called on it
+ # @param anIO [Object] an IO-like object that responds to .write, default nil
+ # @param limit [Fixnum] the nested array/object limit, default nil
+ # @raise [ArgumentError] when depth limit exceeded
+ #
+ # @return [String]
+ def adapter_dump(object, *args, **opts)
+ if enable_oj?
+ Oj.dump(object, opts)
+ else
+ ::JSON.dump(object, *args)
+ end
end
- private
+ # Generates JSON for an object but with fewer options, using toggleable adapters.
+ #
+ # @param object [Hash, Array, Object] must be hash, array, or an object that responds to .to_h or .to_json
+ # @param opts [Hash] an options hash with fewer supported settings than .dump
+ # @return [String]
+ def adapter_generate(object, opts = {})
+ opts = standardize_opts(opts)
+
+ if enable_oj?
+ Oj.generate(object, opts)
+ else
+ ::JSON.generate(object, opts)
+ end
+ end
+
+ # Take a JSON standard options hash and standardize it to work across adapters
+ # An example of this is Oj taking :symbol_keys instead of :symbolize_names
+ #
+ # @param opts [Hash, Nil]
+ # @return [Hash]
+ def standardize_opts(opts)
+ opts ||= {}
- def adapter
- ::JSON
+ if enable_oj?
+ opts[:mode] = :rails
+ opts[:symbol_keys] = opts[:symbolize_keys] || opts[:symbolize_names]
+ end
+
+ opts
end
+ # The standard parser error we should be returning. Defined in a method
+ # so we can potentially override it later.
+ #
+ # @return [JSON::ParserError]
def parser_error
::JSON::ParserError
end
+ # @param [Nil, Boolean] an extracted :legacy_mode key from the opts hash
+ # @return [Boolean]
def legacy_mode_enabled?(arg_value)
arg_value.nil? ? false : arg_value
end
+ # If legacy mode is enabled, we need to raise an error depending on the values
+ # provided in the string. This will be deprecated.
+ #
+ # @param data [Boolean, String, Array, Hash, Object]
+ # @return [Boolean, String, Array, Hash, Object]
+ # @raise [JSON::ParserError]
def handle_legacy_mode!(data)
+ return data unless feature_table_exists?
return data unless Feature.enabled?(:json_wrapper_legacy_mode, default_enabled: true)
raise parser_error if INVALID_LEGACY_TYPES.any? { |type| data.is_a?(type) }
end
+
+ # There are a variety of database errors possible when checking the feature
+ # flags at the wrong time during boot, e.g. during migrations. We don't care
+ # about these errors, we just need to ensure that we skip feature detection
+ # if they will fail.
+ #
+ # @return [Boolean]
+ def feature_table_exists?
+ Feature::FlipperFeature.table_exists?
+ rescue
+ false
+ end
+ end
+
+ # GrapeFormatter is a JSON formatter for the Grape API.
+ # This is set in lib/api/api.rb
+
+ class GrapeFormatter
+ # Convert an object to JSON.
+ #
+ # This will default to the built-in Grape formatter if either :oj_json or :grape_gitlab_json
+ # flags are disabled.
+ #
+ # The `env` param is ignored because it's not needed in either our formatter or Grape's,
+ # but it is passed through for consistency.
+ #
+ # @param object [Object]
+ # @return [String]
+ def self.call(object, env = nil)
+ if Gitlab::Json.enable_oj? && Feature.enabled?(:grape_gitlab_json, default_enabled: true)
+ Gitlab::Json.dump(object)
+ else
+ Grape::Formatter::Json.call(object, env)
+ end
+ end
end
end
end
diff --git a/lib/gitlab/json_logger.rb b/lib/gitlab/json_logger.rb
index ab34fb03158..3a74df8dc8f 100644
--- a/lib/gitlab/json_logger.rb
+++ b/lib/gitlab/json_logger.rb
@@ -19,7 +19,7 @@ module Gitlab
data.merge!(message)
end
- data.to_json + "\n"
+ Gitlab::Json.dump(data) + "\n"
end
end
end
diff --git a/lib/gitlab/kubernetes/helm.rb b/lib/gitlab/kubernetes/helm.rb
index 9507f7bc117..39bd8d5a01f 100644
--- a/lib/gitlab/kubernetes/helm.rb
+++ b/lib/gitlab/kubernetes/helm.rb
@@ -3,7 +3,7 @@
module Gitlab
module Kubernetes
module Helm
- HELM_VERSION = '2.16.6'
+ HELM_VERSION = '2.16.9'
KUBECTL_VERSION = '1.13.12'
NAMESPACE = 'gitlab-managed-apps'
NAMESPACE_LABELS = { 'app.gitlab.com/managed_by' => :gitlab }.freeze
diff --git a/lib/gitlab/kubernetes/node.rb b/lib/gitlab/kubernetes/node.rb
new file mode 100644
index 00000000000..bd765ef3852
--- /dev/null
+++ b/lib/gitlab/kubernetes/node.rb
@@ -0,0 +1,78 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Kubernetes
+ class Node
+ def initialize(cluster)
+ @cluster = cluster
+ end
+
+ def all
+ nodes.map do |node|
+ attributes = node(node)
+ attributes.merge(node_metrics(node))
+ end
+ end
+
+ private
+
+ attr_reader :cluster
+
+ def nodes_from_cluster
+ graceful_request { cluster.kubeclient.get_nodes }
+ end
+
+ def nodes_metrics_from_cluster
+ graceful_request { cluster.kubeclient.metrics_client.get_nodes }
+ end
+
+ def nodes
+ @nodes ||= nodes_from_cluster[:response].to_a
+ end
+
+ def nodes_metrics
+ @nodes_metrics ||= nodes_metrics_from_cluster[:response].to_a
+ end
+
+ def node_metrics_from_node(node)
+ nodes_metrics.find do |node_metric|
+ node_metric.metadata.name == node.metadata.name
+ end
+ end
+
+ def graceful_request(&block)
+ ::Gitlab::Kubernetes::KubeClient.graceful_request(cluster.id, &block)
+ end
+
+ def node(node)
+ {
+ 'metadata' => {
+ 'name' => node.metadata.name
+ },
+ 'status' => {
+ 'capacity' => {
+ 'cpu' => node.status.capacity.cpu,
+ 'memory' => node.status.capacity.memory
+ },
+ 'allocatable' => {
+ 'cpu' => node.status.allocatable.cpu,
+ 'memory' => node.status.allocatable.memory
+ }
+ }
+ }
+ end
+
+ def node_metrics(node)
+ node_metrics = node_metrics_from_node(node)
+ return {} unless node_metrics
+
+ {
+ 'usage' => {
+ 'cpu' => node_metrics.usage.cpu,
+ 'memory' => node_metrics.usage.memory
+ }
+ }
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/lograge/custom_options.rb b/lib/gitlab/lograge/custom_options.rb
index 17a36c292c0..e6dd87a8bec 100644
--- a/lib/gitlab/lograge/custom_options.rb
+++ b/lib/gitlab/lograge/custom_options.rb
@@ -20,8 +20,6 @@ module Gitlab
username: event.payload[:username],
ua: event.payload[:ua]
}
- add_db_counters!(payload)
-
payload.merge!(event.payload[:metadata]) if event.payload[:metadata]
::Gitlab::InstrumentationHelper.add_instrumentation_data(payload)
@@ -46,16 +44,6 @@ module Gitlab
payload
end
-
- def self.add_db_counters!(payload)
- current_transaction = Gitlab::Metrics::Transaction.current
- if current_transaction
- payload[:db_count] = current_transaction.get(:db_count, :counter).to_i
- payload[:db_write_count] = current_transaction.get(:db_write_count, :counter).to_i
- payload[:db_cached_count] = current_transaction.get(:db_cached_count, :counter).to_i
- end
- end
- private_class_method :add_db_counters!
end
end
end
diff --git a/lib/gitlab/marginalia/comment.rb b/lib/gitlab/marginalia/comment.rb
index a0eee823763..d5dae5ef4b3 100644
--- a/lib/gitlab/marginalia/comment.rb
+++ b/lib/gitlab/marginalia/comment.rb
@@ -26,9 +26,9 @@ module Gitlab
job = ::Marginalia::Comment.marginalia_job
# We are using 'Marginalia::SidekiqInstrumentation' which does not support 'ActiveJob::Base'.
- # Gitlab also uses 'ActionMailer::DeliveryJob' which inherits from ActiveJob::Base.
+ # Gitlab also uses 'ActionMailer::MailDeliveryJob' which inherits from ActiveJob::Base.
# So below condition is used to return metadata for such jobs.
- if job && job.is_a?(ActionMailer::DeliveryJob)
+ if job.is_a?(ActionMailer::MailDeliveryJob) || job.is_a?(ActionMailer::DeliveryJob)
{
"class" => job.arguments.first,
"jid" => job.job_id
diff --git a/lib/gitlab/markdown_cache/redis/extension.rb b/lib/gitlab/markdown_cache/redis/extension.rb
index af3237f4ba6..add71fa120e 100644
--- a/lib/gitlab/markdown_cache/redis/extension.rb
+++ b/lib/gitlab/markdown_cache/redis/extension.rb
@@ -22,16 +22,32 @@ module Gitlab
end
end
- private
+ prepended do
+ def self.preload_markdown_cache!(objects)
+ fields = Gitlab::MarkdownCache::Redis::Store.bulk_read(objects)
- def save_markdown(updates)
- markdown_store.save(updates)
+ objects.each do |object|
+ fields[object.cache_key].value.each do |field_name, value|
+ object.write_markdown_field(field_name, value)
+ end
+ end
+ end
end
def write_markdown_field(field_name, value)
+ # The value read from redis is a string, so we're converting it back
+ # to an int.
+ value = value.to_i if field_name == :cached_markdown_version
+
instance_variable_set("@#{field_name}", value)
end
+ private
+
+ def save_markdown(updates)
+ markdown_store.save(updates)
+ end
+
def markdown_field_changed?(field_name)
false
end
diff --git a/lib/gitlab/markdown_cache/redis/store.rb b/lib/gitlab/markdown_cache/redis/store.rb
index 0f954404808..5a8efa34097 100644
--- a/lib/gitlab/markdown_cache/redis/store.rb
+++ b/lib/gitlab/markdown_cache/redis/store.rb
@@ -6,6 +6,20 @@ module Gitlab
class Store
EXPIRES_IN = 1.day
+ def self.bulk_read(subjects)
+ results = {}
+
+ Gitlab::Redis::Cache.with do |r|
+ r.pipelined do
+ subjects.each do |subject|
+ results[subject.cache_key] = new(subject).read
+ end
+ end
+ end
+
+ results
+ end
+
def initialize(subject)
@subject = subject
@loaded = false
@@ -23,13 +37,9 @@ module Gitlab
def read
@loaded = true
- results = Gitlab::Redis::Cache.with do |r|
+ Gitlab::Redis::Cache.with do |r|
r.mapped_hmget(markdown_cache_key, *fields)
end
- # The value read from redis is a string, so we're converting it back
- # to an int.
- results[:cached_markdown_version] = results[:cached_markdown_version].to_i
- results
end
def loaded?
diff --git a/lib/gitlab/metrics/background_transaction.rb b/lib/gitlab/metrics/background_transaction.rb
index fe1722b1095..7b05ae29b02 100644
--- a/lib/gitlab/metrics/background_transaction.rb
+++ b/lib/gitlab/metrics/background_transaction.rb
@@ -9,7 +9,7 @@ module Gitlab
end
def labels
- { controller: @worker_class.name, action: 'perform' }
+ { controller: @worker_class.name, action: 'perform', feature_category: @worker_class.try(:get_feature_category).to_s }
end
end
end
diff --git a/lib/gitlab/metrics/dashboard/errors.rb b/lib/gitlab/metrics/dashboard/errors.rb
index 264ea0488e7..07ddd315bcc 100644
--- a/lib/gitlab/metrics/dashboard/errors.rb
+++ b/lib/gitlab/metrics/dashboard/errors.rb
@@ -20,20 +20,20 @@ module Gitlab
when DashboardProcessingError
error(error.message, :unprocessable_entity)
when NOT_FOUND_ERROR
- error("#{dashboard_path} could not be found.", :not_found)
+ error(_("%{dashboard_path} could not be found.") % { dashboard_path: dashboard_path }, :not_found)
when PanelNotFoundError
error(error.message, :not_found)
when ::Grafana::Client::Error
error(error.message, :service_unavailable)
when MissingIntegrationError
- error('Proxy support for this API is not available currently', :bad_request)
+ error(_('Proxy support for this API is not available currently'), :bad_request)
else
raise error
end
end
def panels_not_found!(opts)
- raise PanelNotFoundError.new("No panels matching properties #{opts}")
+ raise PanelNotFoundError.new(_("No panels matching properties %{opts}") % { opts: opts })
end
end
end
diff --git a/lib/gitlab/metrics/dashboard/finder.rb b/lib/gitlab/metrics/dashboard/finder.rb
index d80985e0a0e..5e2d78e10a4 100644
--- a/lib/gitlab/metrics/dashboard/finder.rb
+++ b/lib/gitlab/metrics/dashboard/finder.rb
@@ -7,6 +7,19 @@ module Gitlab
module Metrics
module Dashboard
class Finder
+ # Dashboards that should not be part of the list of all dashboards
+ # displayed on the metrics dashboard page.
+ PREDEFINED_DASHBOARD_EXCLUSION_LIST = [
+ # This dashboard is only useful in the self monitoring project.
+ ::Metrics::Dashboard::SelfMonitoringDashboardService,
+
+ # This dashboard is displayed on the K8s cluster settings health page.
+ ::Metrics::Dashboard::ClusterDashboardService,
+
+ # This dashboard is not yet ready for the world.
+ ::Metrics::Dashboard::PodDashboardService
+ ].freeze
+
class << self
# Returns a formatted dashboard packed with DB info.
# @param project [Project]
@@ -67,12 +80,32 @@ module Gitlab
def find_all_paths_from_source(project)
Gitlab::Metrics::Dashboard::Cache.delete_all!
- default_dashboard_path(project)
- .+ project_service.all_dashboard_paths(project)
+ user_facing_dashboard_services(project).flat_map do |service|
+ service.all_dashboard_paths(project)
+ end
end
private
+ def user_facing_dashboard_services(project)
+ predefined_dashboard_services_for(project) + [project_service]
+ end
+
+ def predefined_dashboard_services_for(project)
+ # Only list the self monitoring dashboard on the self monitoring project,
+ # since it is the only dashboard (at time of writing) that shows data
+ # about GitLab itself.
+ if project.self_monitoring?
+ return [self_monitoring_service]
+ end
+
+ predefined_dashboard_services
+ end
+
+ def predefined_dashboard_services
+ ::Metrics::Dashboard::PredefinedDashboardService.descendants - PREDEFINED_DASHBOARD_EXCLUSION_LIST
+ end
+
def system_service
::Metrics::Dashboard::SystemDashboardService
end
@@ -85,14 +118,6 @@ module Gitlab
::Metrics::Dashboard::SelfMonitoringDashboardService
end
- def default_dashboard_path(project)
- if project.self_monitoring?
- self_monitoring_service.all_dashboard_paths(project)
- else
- system_service.all_dashboard_paths(project)
- end
- end
-
def service_for(options)
Gitlab::Metrics::Dashboard::ServiceSelector.call(options)
end
diff --git a/lib/gitlab/metrics/dashboard/service_selector.rb b/lib/gitlab/metrics/dashboard/service_selector.rb
index 49682da320c..641c0c76f8f 100644
--- a/lib/gitlab/metrics/dashboard/service_selector.rb
+++ b/lib/gitlab/metrics/dashboard/service_selector.rb
@@ -13,6 +13,8 @@ module Gitlab
include Gitlab::Utils::StrongMemoize
SERVICES = [
+ ::Metrics::Dashboard::ClusterMetricsEmbedService,
+ ::Metrics::Dashboard::ClusterDashboardService,
::Metrics::Dashboard::GitlabAlertEmbedService,
::Metrics::Dashboard::CustomMetricEmbedService,
::Metrics::Dashboard::GrafanaMetricEmbedService,
@@ -51,5 +53,3 @@ module Gitlab
end
end
end
-
-Gitlab::Metrics::Dashboard::ServiceSelector.prepend_if_ee('EE::Gitlab::Metrics::Dashboard::ServiceSelector')
diff --git a/lib/gitlab/metrics/dashboard/stages/base_stage.rb b/lib/gitlab/metrics/dashboard/stages/base_stage.rb
index 622d5aa8cdb..ee2d36621b4 100644
--- a/lib/gitlab/metrics/dashboard/stages/base_stage.rb
+++ b/lib/gitlab/metrics/dashboard/stages/base_stage.rb
@@ -48,6 +48,14 @@ module Gitlab
end
end
+ def for_variables
+ return unless dashboard.dig(:templating, :variables).is_a?(Hash)
+
+ dashboard.dig(:templating, :variables).each do |variable_name, variable|
+ yield variable_name, variable
+ end
+ end
+
def for_panel_groups
dashboard[:panel_groups].each do |panel_group|
yield panel_group
diff --git a/lib/gitlab/metrics/dashboard/stages/cluster_endpoint_inserter.rb b/lib/gitlab/metrics/dashboard/stages/cluster_endpoint_inserter.rb
new file mode 100644
index 00000000000..a12082b704c
--- /dev/null
+++ b/lib/gitlab/metrics/dashboard/stages/cluster_endpoint_inserter.rb
@@ -0,0 +1,83 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Metrics
+ module Dashboard
+ module Stages
+ class ClusterEndpointInserter < BaseStage
+ def transform!
+ verify_params
+
+ for_metrics do |metric|
+ metric[:prometheus_endpoint_path] = endpoint_for_metric(metric)
+ end
+ end
+
+ private
+
+ def admin_url(metric)
+ Gitlab::Routing.url_helpers.prometheus_api_admin_cluster_path(
+ params[:cluster],
+ proxy_path: query_type(metric),
+ query: query_for_metric(metric)
+ )
+ end
+
+ def endpoint_for_metric(metric)
+ case params[:cluster_type]
+ when :admin
+ admin_url(metric)
+ when :group
+ error!(_('Group is required when cluster_type is :group')) unless params[:group]
+ group_url(metric)
+ when :project
+ error!(_('Project is required when cluster_type is :project')) unless project
+ project_url(metric)
+ else
+ error!(_('Unrecognized cluster type'))
+ end
+ end
+
+ def error!(message)
+ raise Errors::DashboardProcessingError.new(message)
+ end
+
+ def group_url(metric)
+ Gitlab::Routing.url_helpers.prometheus_api_group_cluster_path(
+ params[:group],
+ params[:cluster],
+ proxy_path: query_type(metric),
+ query: query_for_metric(metric)
+ )
+ end
+
+ def project_url(metric)
+ Gitlab::Routing.url_helpers.prometheus_api_project_cluster_path(
+ project,
+ params[:cluster],
+ proxy_path: query_type(metric),
+ query: query_for_metric(metric)
+ )
+ end
+
+ def query_type(metric)
+ metric[:query] ? :query : :query_range
+ end
+
+ def query_for_metric(metric)
+ query = metric[query_type(metric)]
+
+ raise Errors::MissingQueryError.new('Each "metric" must define one of :query or :query_range') unless query
+
+ query
+ end
+
+ def verify_params
+ raise Errors::DashboardProcessingError.new(_('Cluster is required for Stages::ClusterEndpointInserter')) unless params[:cluster]
+ raise Errors::DashboardProcessingError.new(_('Cluster type must be specificed for Stages::ClusterEndpointInserter')) unless params[:cluster_type]
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/metrics/dashboard/stages/endpoint_inserter.rb b/lib/gitlab/metrics/dashboard/stages/endpoint_inserter.rb
deleted file mode 100644
index e085f551952..00000000000
--- a/lib/gitlab/metrics/dashboard/stages/endpoint_inserter.rb
+++ /dev/null
@@ -1,50 +0,0 @@
-# frozen_string_literal: true
-
-module Gitlab
- module Metrics
- module Dashboard
- module Stages
- class EndpointInserter < BaseStage
- def transform!
- raise Errors::DashboardProcessingError.new('Environment is required for Stages::EndpointInserter') unless params[:environment]
-
- for_metrics do |metric|
- metric[:prometheus_endpoint_path] = endpoint_for_metric(metric)
- end
- end
-
- private
-
- def endpoint_for_metric(metric)
- if params[:sample_metrics]
- Gitlab::Routing.url_helpers.sample_metrics_project_environment_path(
- project,
- params[:environment],
- identifier: metric[:id]
- )
- else
- Gitlab::Routing.url_helpers.prometheus_api_project_environment_path(
- project,
- params[:environment],
- proxy_path: query_type(metric),
- query: query_for_metric(metric)
- )
- end
- end
-
- def query_type(metric)
- metric[:query] ? :query : :query_range
- end
-
- def query_for_metric(metric)
- query = metric[query_type(metric)]
-
- raise Errors::MissingQueryError.new('Each "metric" must define one of :query or :query_range') unless query
-
- query
- end
- end
- end
- end
- end
-end
diff --git a/lib/gitlab/metrics/dashboard/stages/metric_endpoint_inserter.rb b/lib/gitlab/metrics/dashboard/stages/metric_endpoint_inserter.rb
new file mode 100644
index 00000000000..c48a7ff25a5
--- /dev/null
+++ b/lib/gitlab/metrics/dashboard/stages/metric_endpoint_inserter.rb
@@ -0,0 +1,54 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Metrics
+ module Dashboard
+ module Stages
+ class MetricEndpointInserter < BaseStage
+ def transform!
+ raise Errors::DashboardProcessingError.new(_('Environment is required for Stages::MetricEndpointInserter')) unless params[:environment]
+
+ for_metrics do |metric|
+ metric[:prometheus_endpoint_path] = endpoint_for_metric(metric)
+ end
+ end
+
+ private
+
+ def endpoint_for_metric(metric)
+ if params[:sample_metrics]
+ Gitlab::Routing.url_helpers.sample_metrics_project_environment_path(
+ project,
+ params[:environment],
+ identifier: metric[:id]
+ )
+ else
+ Gitlab::Routing.url_helpers.prometheus_api_project_environment_path(
+ project,
+ params[:environment],
+ proxy_path: query_type(metric),
+ query: query_for_metric(metric)
+ )
+ end
+ end
+
+ def query_type(metric)
+ if metric[:query]
+ ::Prometheus::ProxyService::PROMETHEUS_QUERY_API.to_sym
+ else
+ ::Prometheus::ProxyService::PROMETHEUS_QUERY_RANGE_API.to_sym
+ end
+ end
+
+ def query_for_metric(metric)
+ query = metric[query_type(metric)]
+
+ raise Errors::MissingQueryError.new('Each "metric" must define one of :query or :query_range') unless query
+
+ query
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/metrics/dashboard/stages/sorter.rb b/lib/gitlab/metrics/dashboard/stages/sorter.rb
index ba5aa78059c..882211e1441 100644
--- a/lib/gitlab/metrics/dashboard/stages/sorter.rb
+++ b/lib/gitlab/metrics/dashboard/stages/sorter.rb
@@ -16,7 +16,7 @@ module Gitlab
# Sorts the groups in the dashboard by the :priority key
def sort_groups!
- dashboard[:panel_groups] = dashboard[:panel_groups].sort_by { |group| -group[:priority].to_i }
+ dashboard[:panel_groups] = Gitlab::Utils.stable_sort_by(dashboard[:panel_groups]) { |group| -group[:priority].to_i }
end
# Sorts the panels in the dashboard by the :weight key
@@ -24,7 +24,7 @@ module Gitlab
dashboard[:panel_groups].each do |group|
missing_panels! unless group[:panels].is_a? Array
- group[:panels] = group[:panels].sort_by { |panel| -panel[:weight].to_i }
+ group[:panels] = Gitlab::Utils.stable_sort_by(group[:panels]) { |panel| -panel[:weight].to_i }
end
end
end
diff --git a/lib/gitlab/metrics/dashboard/stages/url_validator.rb b/lib/gitlab/metrics/dashboard/stages/url_validator.rb
index ff36f7b605e..9e2bb0d1a70 100644
--- a/lib/gitlab/metrics/dashboard/stages/url_validator.rb
+++ b/lib/gitlab/metrics/dashboard/stages/url_validator.rb
@@ -6,8 +6,47 @@ module Gitlab
module Stages
class UrlValidator < BaseStage
def transform!
- dashboard[:links]&.each do |link|
- Gitlab::UrlBlocker.validate!(link[:url])
+ validate_dashboard_links(dashboard)
+
+ validate_chart_links(dashboard)
+ end
+
+ private
+
+ def blocker_args
+ {
+ schemes: %w(http https),
+ ports: [],
+ allow_localhost: allow_setting_local_requests?,
+ allow_local_network: allow_setting_local_requests?,
+ ascii_only: false,
+ enforce_user: false,
+ enforce_sanitization: false,
+ dns_rebind_protection: true
+ }
+ end
+
+ def allow_setting_local_requests?
+ Gitlab::CurrentSettings.allow_local_requests_from_web_hooks_and_services?
+ end
+
+ def validate_dashboard_links(dashboard)
+ validate_links(dashboard[:links])
+ end
+
+ def validate_chart_links(dashboard)
+ dashboard[:panel_groups].each do |panel_group|
+ panel_group[:panels].each do |panel|
+ validate_links(panel[:links])
+ end
+ end
+ end
+
+ def validate_links(links)
+ links&.each do |link|
+ next unless link.is_a? Hash
+
+ Gitlab::UrlBlocker.validate!(link[:url], blocker_args)
rescue Gitlab::UrlBlocker::BlockedUrlError
link[:url] = ''
end
diff --git a/lib/gitlab/metrics/dashboard/stages/variable_endpoint_inserter.rb b/lib/gitlab/metrics/dashboard/stages/variable_endpoint_inserter.rb
new file mode 100644
index 00000000000..20e7fe477e5
--- /dev/null
+++ b/lib/gitlab/metrics/dashboard/stages/variable_endpoint_inserter.rb
@@ -0,0 +1,34 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Metrics
+ module Dashboard
+ module Stages
+ class VariableEndpointInserter < BaseStage
+ VARIABLE_TYPE_METRIC_LABEL_VALUES = 'metric_label_values'
+
+ def transform!
+ raise Errors::DashboardProcessingError.new(_('Environment is required for Stages::VariableEndpointInserter')) unless params[:environment]
+
+ for_variables do |variable_name, variable|
+ if variable.is_a?(Hash) && variable[:type] == VARIABLE_TYPE_METRIC_LABEL_VALUES
+ variable[:options][:prometheus_endpoint_path] = endpoint_for_variable(variable.dig(:options, :series_selector))
+ end
+ end
+ end
+
+ private
+
+ def endpoint_for_variable(series_selector)
+ Gitlab::Routing.url_helpers.prometheus_api_project_environment_path(
+ project,
+ params[:environment],
+ proxy_path: ::Prometheus::ProxyService::PROMETHEUS_SERIES_API,
+ match: Array(series_selector)
+ )
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/metrics/dashboard/url.rb b/lib/gitlab/metrics/dashboard/url.rb
index 31670a3f533..10a2f3c2397 100644
--- a/lib/gitlab/metrics/dashboard/url.rb
+++ b/lib/gitlab/metrics/dashboard/url.rb
@@ -60,6 +60,22 @@ module Gitlab
Gitlab::Routing.url_helpers.metrics_dashboard_namespace_project_environment_url(*args)
end
+ # Matches dashboard urls for a metric chart embed
+ # for cluster metrics
+ #
+ # EX - https://<host>/<namespace>/<project>/-/clusters/<cluster_id>/?group=Cluster%20Health&title=Memory%20Usage&y_label=Memory%20(GiB)
+ def clusters_regex
+ strong_memoize(:clusters_regex) do
+ regex_for_project_metrics(
+ %r{
+ /clusters
+ /(?<cluster_id>\d+)
+ /?
+ }x
+ )
+ end
+ end
+
private
def regex_for_project_metrics(path_suffix_pattern)
diff --git a/lib/gitlab/metrics/methods.rb b/lib/gitlab/metrics/methods.rb
index 5955987541c..83a7b925392 100644
--- a/lib/gitlab/metrics/methods.rb
+++ b/lib/gitlab/metrics/methods.rb
@@ -35,7 +35,7 @@ module Gitlab
end
def init_metric(type, name, opts = {}, &block)
- options = MetricOptions.new(opts)
+ options = ::Gitlab::Metrics::Methods::MetricOptions.new(opts)
options.evaluate(&block)
if disabled_by_feature(options)
diff --git a/lib/gitlab/metrics/sidekiq_middleware.rb b/lib/gitlab/metrics/sidekiq_middleware.rb
index de8e1ca3256..1c99e1e730c 100644
--- a/lib/gitlab/metrics/sidekiq_middleware.rb
+++ b/lib/gitlab/metrics/sidekiq_middleware.rb
@@ -26,9 +26,7 @@ module Gitlab
private
def add_info_to_payload(payload, trans)
- payload[:db_count] = trans.get(:db_count, :counter).to_i
- payload[:db_write_count] = trans.get(:db_write_count, :counter).to_i
- payload[:db_cached_count] = trans.get(:db_cached_count, :counter).to_i
+ payload.merge!(::Gitlab::Metrics::Subscribers::ActiveRecord.db_counter_payload)
end
end
end
diff --git a/lib/gitlab/metrics/subscribers/active_record.rb b/lib/gitlab/metrics/subscribers/active_record.rb
index 1628eeb5a95..d2736882432 100644
--- a/lib/gitlab/metrics/subscribers/active_record.rb
+++ b/lib/gitlab/metrics/subscribers/active_record.rb
@@ -23,6 +23,14 @@ module Gitlab
increment_db_counters(payload)
end
+ def self.db_counter_payload
+ return {} unless Gitlab::SafeRequestStore.active?
+
+ DB_COUNTERS.map do |counter|
+ [counter, Gitlab::SafeRequestStore[counter].to_i]
+ end.to_h
+ end
+
private
define_histogram :gitlab_sql_duration_seconds do
@@ -36,13 +44,21 @@ module Gitlab
end
def increment_db_counters(payload)
- current_transaction.increment(:db_count, 1)
+ increment(:db_count)
if payload.fetch(:cached, payload[:name] == 'CACHE')
- current_transaction.increment(:db_cached_count, 1)
+ increment(:db_cached_count)
end
- current_transaction.increment(:db_write_count, 1) unless select_sql_command?(payload)
+ increment(:db_write_count) unless select_sql_command?(payload)
+ end
+
+ def increment(counter)
+ current_transaction.increment(counter, 1)
+
+ if Gitlab::SafeRequestStore.active?
+ Gitlab::SafeRequestStore[counter] = Gitlab::SafeRequestStore[counter].to_i + 1
+ end
end
def current_transaction
diff --git a/lib/gitlab/metrics/transaction.rb b/lib/gitlab/metrics/transaction.rb
index 822f5243e9d..da06be9c79c 100644
--- a/lib/gitlab/metrics/transaction.rb
+++ b/lib/gitlab/metrics/transaction.rb
@@ -7,7 +7,7 @@ module Gitlab
include Gitlab::Metrics::Methods
# base labels shared among all transactions
- BASE_LABELS = { controller: nil, action: nil }.freeze
+ BASE_LABELS = { controller: nil, action: nil, feature_category: nil }.freeze
# labels that potentially contain sensitive information and will be filtered
FILTERED_LABELS = [:branch, :path].freeze
@@ -92,12 +92,6 @@ module Gitlab
self.class.transaction_metric(name, :gauge).set(labels, value) if use_prometheus
end
- def get(name, type, tags = {})
- metric = self.class.transaction_metric(name, type)
-
- metric.get(filter_tags(tags).merge(labels))
- end
-
def labels
BASE_LABELS
end
diff --git a/lib/gitlab/metrics/web_transaction.rb b/lib/gitlab/metrics/web_transaction.rb
index fa17548723e..2064f9290d3 100644
--- a/lib/gitlab/metrics/web_transaction.rb
+++ b/lib/gitlab/metrics/web_transaction.rb
@@ -32,6 +32,10 @@ module Gitlab
action = "#{controller.action_name}"
+ # Try to get the feature category, but don't fail when the controller is
+ # not an ApplicationController.
+ feature_category = controller.class.try(:feature_category_for_action, action).to_s
+
# Devise exposes a method called "request_format" that does the below.
# However, this method is not available to all controllers (e.g. certain
# Doorkeeper controllers). As such we use the underlying code directly.
@@ -45,7 +49,7 @@ module Gitlab
action = "#{action}.#{suffix}"
end
- { controller: controller.class.name, action: action }
+ { controller: controller.class.name, action: action, feature_category: feature_category }
end
def labels_from_endpoint
@@ -61,7 +65,10 @@ module Gitlab
if route
path = endpoint_paths_cache[route.request_method][route.path]
- { controller: 'Grape', action: "#{route.request_method} #{path}" }
+
+ # Feature categories will be added for grape endpoints in
+ # https://gitlab.com/gitlab-com/gl-infra/scalability/-/issues/462
+ { controller: 'Grape', action: "#{route.request_method} #{path}", feature_category: '' }
end
end
diff --git a/lib/gitlab/middleware/go.rb b/lib/gitlab/middleware/go.rb
index abdbccd3aa8..47d0b9ba8cb 100644
--- a/lib/gitlab/middleware/go.rb
+++ b/lib/gitlab/middleware/go.rb
@@ -101,7 +101,7 @@ module Gitlab
if project
# If a project is found and the user has access, we return the full project path
- return project.full_path, project.default_branch
+ [project.full_path, project.default_branch]
else
# If not, we return the first two components as if it were a simple `namespace/project` path,
# so that we don't reveal the existence of a nested project the user doesn't have access to.
@@ -112,7 +112,7 @@ module Gitlab
# `go get gitlab.com/group/subgroup/project/subpackage` will not work for private projects.
# `go get gitlab.com/group/subgroup/project.git/subpackage` will work, since Go is smart enough
# to figure that out. `import 'gitlab.com/...'` behaves the same as `go get`.
- return simple_project_path, 'master'
+ [simple_project_path, 'master']
end
end
diff --git a/lib/gitlab/middleware/multipart.rb b/lib/gitlab/middleware/multipart.rb
index 3c45f841653..c0b671abd44 100644
--- a/lib/gitlab/middleware/multipart.rb
+++ b/lib/gitlab/middleware/multipart.rb
@@ -105,6 +105,21 @@ module Gitlab
private
+ def package_allowed_paths
+ packages_config = ::Gitlab.config.packages
+ return [] unless allow_packages_storage_path?(packages_config)
+
+ [::Packages::PackageFileUploader.workhorse_upload_path]
+ end
+
+ def allow_packages_storage_path?(packages_config)
+ return false unless packages_config.enabled
+ return false unless packages_config['storage_path']
+ return false if packages_config.object_store.enabled && packages_config.object_store.direct_upload
+
+ true
+ end
+
def allowed_paths
[
::FileUploader.root,
@@ -112,7 +127,7 @@ module Gitlab
JobArtifactUploader.workhorse_upload_path,
LfsObjectUploader.workhorse_upload_path,
File.join(Rails.root, 'public/uploads/tmp')
- ]
+ ] + package_allowed_paths
end
end
@@ -135,5 +150,3 @@ module Gitlab
end
end
end
-
-::Gitlab::Middleware::Multipart::Handler.prepend_if_ee('EE::Gitlab::Middleware::Multipart::Handler')
diff --git a/lib/gitlab/project_template.rb b/lib/gitlab/project_template.rb
index fdb3fbc03bc..e6e599e079d 100644
--- a/lib/gitlab/project_template.rb
+++ b/lib/gitlab/project_template.rb
@@ -59,6 +59,7 @@ module Gitlab
ProjectTemplate.new('nfhexo', 'Netlify/Hexo', _('A Hexo site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features.'), 'https://gitlab.com/pages/nfhexo', 'illustrations/logos/netlify.svg'),
ProjectTemplate.new('salesforcedx', 'SalesforceDX', _('A project boilerplate for Salesforce App development with Salesforce Developer tools.'), 'https://gitlab.com/gitlab-org/project-templates/salesforcedx'),
ProjectTemplate.new('serverless_framework', 'Serverless Framework/JS', _('A basic page and serverless function that uses AWS Lambda, AWS API Gateway, and GitLab Pages'), 'https://gitlab.com/gitlab-org/project-templates/serverless-framework', 'illustrations/logos/serverless_framework.svg'),
+ ProjectTemplate.new('jsonnet', 'Jsonnet for Dynamic Child Pipelines', _('An example showing how to use Jsonnet with GitLab dynamic child pipelines'), 'https://gitlab.com/gitlab-org/project-templates/jsonnet'),
ProjectTemplate.new('cluster_management', 'GitLab Cluster Management', _('An example project for managing Kubernetes clusters integrated with GitLab.'), 'https://gitlab.com/gitlab-org/project-templates/cluster-management')
].freeze
end
diff --git a/lib/gitlab/prometheus_client.rb b/lib/gitlab/prometheus_client.rb
index 213e3ba835d..69499b5494e 100644
--- a/lib/gitlab/prometheus_client.rb
+++ b/lib/gitlab/prometheus_client.rb
@@ -5,6 +5,8 @@ module Gitlab
class PrometheusClient
include Gitlab::Utils::StrongMemoize
Error = Class.new(StandardError)
+ ConnectionError = Class.new(Gitlab::PrometheusClient::Error)
+ UnexpectedResponseError = Class.new(Gitlab::PrometheusClient::Error)
QueryError = Class.new(Gitlab::PrometheusClient::Error)
HEALTHY_RESPONSE = "Prometheus is Healthy.\n"
@@ -44,7 +46,7 @@ module Gitlab
path = api_path(type)
get(path, args)
rescue Gitlab::HTTP::ResponseError => ex
- raise PrometheusClient::Error, "Network connection error" unless ex.response && ex.response.try(:code)
+ raise PrometheusClient::ConnectionError, "Network connection error" unless ex.response && ex.response.try(:code)
handle_querying_api_response(ex.response)
end
@@ -115,7 +117,7 @@ module Gitlab
response = get(path, args)
handle_querying_api_response(response)
rescue Gitlab::HTTP::ResponseError => ex
- raise PrometheusClient::Error, "Network connection error" unless ex.response && ex.response.try(:code)
+ raise PrometheusClient::ConnectionError, "Network connection error" unless ex.response && ex.response.try(:code)
handle_querying_api_response(ex.response)
end
@@ -137,18 +139,18 @@ module Gitlab
def get(path, args)
Gitlab::HTTP.get(path, { query: args }.merge(http_options) )
rescue SocketError
- raise PrometheusClient::Error, "Can't connect to #{api_url}"
+ raise PrometheusClient::ConnectionError, "Can't connect to #{api_url}"
rescue OpenSSL::SSL::SSLError
- raise PrometheusClient::Error, "#{api_url} contains invalid SSL data"
+ raise PrometheusClient::ConnectionError, "#{api_url} contains invalid SSL data"
rescue Errno::ECONNREFUSED
- raise PrometheusClient::Error, 'Connection refused'
+ raise PrometheusClient::ConnectionError, 'Connection refused'
end
def handle_management_api_response(response)
if response.code == 200
response.body
else
- raise PrometheusClient::Error, "#{response.code} - #{response.body}"
+ raise PrometheusClient::UnexpectedResponseError, "#{response.code} - #{response.body}"
end
end
@@ -156,7 +158,7 @@ module Gitlab
response_code = response.try(:code)
response_body = response.try(:body)
- raise PrometheusClient::Error, "#{response_code} - #{response_body}" unless response_code
+ raise PrometheusClient::UnexpectedResponseError, "#{response_code} - #{response_body}" unless response_code
json_data = parse_json(response_body) if [200, 400].include?(response_code)
@@ -166,7 +168,7 @@ module Gitlab
when 400
raise PrometheusClient::QueryError, json_data['error'] || 'Bad data received'
else
- raise PrometheusClient::Error, "#{response_code} - #{response_body}"
+ raise PrometheusClient::UnexpectedResponseError, "#{response_code} - #{response_body}"
end
end
@@ -178,7 +180,7 @@ module Gitlab
def parse_json(response_body)
Gitlab::Json.parse(response_body, legacy_mode: true)
rescue JSON::ParserError
- raise PrometheusClient::Error, 'Parsing response failed'
+ raise PrometheusClient::UnexpectedResponseError, 'Parsing response failed'
end
end
end
diff --git a/lib/gitlab/regex.rb b/lib/gitlab/regex.rb
index 4caff8ae679..784f8b48f3c 100644
--- a/lib/gitlab/regex.rb
+++ b/lib/gitlab/regex.rb
@@ -43,6 +43,10 @@ module Gitlab
@maven_app_name_regex ||= /\A[\w\-\.]+\z/.freeze
end
+ def maven_version_regex
+ @maven_version_regex ||= /\A(\.?[\w\+-]+\.?)+\z/.freeze
+ end
+
def maven_app_group_regex
maven_app_name_regex
end
@@ -246,6 +250,14 @@ module Gitlab
@utc_date_regex ||= /\A[0-9]{4}-[0-9]{2}-[0-9]{2}\z/.freeze
end
+ def merge_request_wip
+ /(?i)(\[WIP\]\s*|WIP:\s*|WIP$)/
+ end
+
+ def merge_request_draft
+ /(?i)(\[draft\]|\(draft\)|draft:|draft\s\-\s|draft$)/
+ end
+
def issue
@issue ||= /(?<issue>\d+\b)/
end
diff --git a/lib/gitlab/runtime.rb b/lib/gitlab/runtime.rb
index abf6ee07d53..8b40aaa101a 100644
--- a/lib/gitlab/runtime.rb
+++ b/lib/gitlab/runtime.rb
@@ -37,7 +37,7 @@ module Gitlab
end
def puma?
- !!defined?(::Puma) && !defined?(ACTION_CABLE_SERVER)
+ !!defined?(::Puma)
end
# For unicorn, we need to check for actual server instances to avoid false positives.
@@ -70,11 +70,11 @@ module Gitlab
end
def web_server?
- puma? || unicorn? || action_cable?
+ puma? || unicorn?
end
def action_cable?
- !!defined?(ACTION_CABLE_SERVER)
+ web_server? && (!!defined?(ACTION_CABLE_SERVER) || Gitlab::ActionCable::Config.in_app?)
end
def multi_threaded?
@@ -82,19 +82,21 @@ module Gitlab
end
def max_threads
- main_thread = 1
+ threads = 1 # main thread
- if action_cable?
- Gitlab::Application.config.action_cable.worker_pool_size
- elsif puma?
- Puma.cli_config.options[:max_threads]
+ if puma?
+ threads += Puma.cli_config.options[:max_threads]
elsif sidekiq?
# An extra thread for the poller in Sidekiq Cron:
# https://github.com/ondrejbartas/sidekiq-cron#under-the-hood
- Sidekiq.options[:concurrency] + 1
- else
- 0
- end + main_thread
+ threads += Sidekiq.options[:concurrency] + 1
+ end
+
+ if action_cable?
+ threads += Gitlab::ActionCable::Config.worker_pool_size
+ end
+
+ threads
end
end
end
diff --git a/lib/gitlab/search_results.rb b/lib/gitlab/search_results.rb
index 6239158ef06..3d5f64ce05b 100644
--- a/lib/gitlab/search_results.rb
+++ b/lib/gitlab/search_results.rb
@@ -27,6 +27,7 @@ module Gitlab
end
def objects(scope, page: nil, per_page: DEFAULT_PER_PAGE, without_count: true, preload_method: nil)
+ should_preload = preload_method.present?
collection = case scope
when 'projects'
projects
@@ -39,9 +40,11 @@ module Gitlab
when 'users'
users
else
+ should_preload = false
Kaminari.paginate_array([])
end
+ collection = collection.public_send(preload_method) if should_preload # rubocop:disable GitlabSecurity/PublicSend
collection = collection.page(page).per(per_page)
without_count ? collection.without_count : collection
diff --git a/lib/gitlab/seeder.rb b/lib/gitlab/seeder.rb
index 53cbd5b21ea..d652719721e 100644
--- a/lib/gitlab/seeder.rb
+++ b/lib/gitlab/seeder.rb
@@ -18,6 +18,7 @@ module Gitlab
MASS_INSERT_PROJECT_START = 'mass_insert_project_'
MASS_INSERT_USER_START = 'mass_insert_user_'
+ REPORTED_USER_START = 'reported_user_'
ESTIMATED_INSERT_PER_MINUTE = 2_000_000
MASS_INSERT_ENV = 'MASS_INSERT'
@@ -36,7 +37,7 @@ module Gitlab
included do
scope :not_mass_generated, -> do
- where.not("username LIKE '#{MASS_INSERT_USER_START}%'")
+ where.not("username LIKE '#{MASS_INSERT_USER_START}%' OR username LIKE '#{REPORTED_USER_START}%'")
end
end
end
diff --git a/lib/gitlab/service_desk.rb b/lib/gitlab/service_desk.rb
new file mode 100644
index 00000000000..b3d6e890e03
--- /dev/null
+++ b/lib/gitlab/service_desk.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module ServiceDesk
+ # Check whether a project or GitLab instance can support the Service Desk
+ # feature. Use `project.service_desk_enabled?` to check whether it is
+ # enabled for a particular project.
+ def self.enabled?(project:)
+ supported? && project[:service_desk_enabled]
+ end
+
+ def self.supported?
+ Gitlab::IncomingEmail.enabled? && Gitlab::IncomingEmail.supports_wildcard?
+ end
+ end
+end
diff --git a/lib/gitlab/service_desk_email.rb b/lib/gitlab/service_desk_email.rb
new file mode 100644
index 00000000000..f8dba82cb40
--- /dev/null
+++ b/lib/gitlab/service_desk_email.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module ServiceDeskEmail
+ class << self
+ def enabled?
+ !!config&.enabled && config&.address.present?
+ end
+
+ def key_from_address(address)
+ wildcard_address = config&.address
+ return unless wildcard_address
+
+ Gitlab::IncomingEmail.key_from_address(address, wildcard_address: wildcard_address)
+ end
+
+ def config
+ Gitlab.config.service_desk_email
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/set_cache.rb b/lib/gitlab/set_cache.rb
index e891b805879..6ba9ee26634 100644
--- a/lib/gitlab/set_cache.rb
+++ b/lib/gitlab/set_cache.rb
@@ -20,7 +20,10 @@ module Gitlab
with do |redis|
keys = keys.map { |key| cache_key(key) }
- unlink_or_delete(redis, keys)
+
+ Gitlab::Instrumentation::RedisClusterValidator.allow_cross_slot_commands do
+ unlink_or_delete(redis, keys)
+ end
end
end
diff --git a/lib/gitlab/sidekiq_logging/deduplication_logger.rb b/lib/gitlab/sidekiq_logging/deduplication_logger.rb
index 01810e474dc..c5654819ffb 100644
--- a/lib/gitlab/sidekiq_logging/deduplication_logger.rb
+++ b/lib/gitlab/sidekiq_logging/deduplication_logger.rb
@@ -6,11 +6,14 @@ module Gitlab
include Singleton
include LogsJobs
- def log(job, deduplication_type)
+ def log(job, deduplication_type, deduplication_options = {})
payload = parse_job(job)
payload['job_status'] = 'deduplicated'
payload['message'] = "#{base_message(payload)}: deduplicated: #{deduplication_type}"
- payload['deduplication_type'] = deduplication_type
+ payload['deduplication.type'] = deduplication_type
+ # removing nil values from deduplication options
+ payload.merge!(
+ deduplication_options.compact.transform_keys { |k| "deduplication.options.#{k}" })
Sidekiq.logger.info payload
end
diff --git a/lib/gitlab/sidekiq_middleware/duplicate_jobs/strategies/until_executing.rb b/lib/gitlab/sidekiq_middleware/duplicate_jobs/strategies/until_executing.rb
index 0ed4912c4cc..46ce0eb4a91 100644
--- a/lib/gitlab/sidekiq_middleware/duplicate_jobs/strategies/until_executing.rb
+++ b/lib/gitlab/sidekiq_middleware/duplicate_jobs/strategies/until_executing.rb
@@ -17,7 +17,8 @@ module Gitlab
job['duplicate-of'] = duplicate_job.existing_jid
if duplicate_job.droppable?
- Gitlab::SidekiqLogging::DeduplicationLogger.instance.log(job, "dropped until executing")
+ Gitlab::SidekiqLogging::DeduplicationLogger.instance.log(
+ job, "dropped until executing", duplicate_job.options)
return false
end
end
diff --git a/lib/gitlab/static_site_editor/config.rb b/lib/gitlab/static_site_editor/config.rb
index 65c567ec2a6..08ed6599a6e 100644
--- a/lib/gitlab/static_site_editor/config.rb
+++ b/lib/gitlab/static_site_editor/config.rb
@@ -3,7 +3,7 @@
module Gitlab
module StaticSiteEditor
class Config
- SUPPORTED_EXTENSIONS = %w[.md].freeze
+ SUPPORTED_EXTENSIONS = %w[.md .md.erb].freeze
def initialize(repository, ref, file_path, return_url)
@repository = repository
@@ -20,7 +20,7 @@ module Gitlab
commit_id: commit_id,
project_id: project.id,
project: project.path,
- namespace: project.namespace.path,
+ namespace: project.namespace.full_path,
return_url: sanitize_url(return_url),
is_supported_content: supported_content?.to_s,
base_url: Gitlab::Routing.url_helpers.project_show_sse_path(project, full_path)
@@ -42,11 +42,11 @@ module Gitlab
end
def extension_supported?
- File.extname(file_path).in?(SUPPORTED_EXTENSIONS)
+ SUPPORTED_EXTENSIONS.any? { |ext| file_path.end_with?(ext) }
end
def file_exists?
- commit_id.present? && repository.blob_at(commit_id, file_path).present?
+ commit_id.present? && !repository.blob_at(commit_id, file_path).nil?
end
def full_path
diff --git a/lib/gitlab/suggestions/file_suggestion.rb b/lib/gitlab/suggestions/file_suggestion.rb
index 73b9800f0b8..7805b27902d 100644
--- a/lib/gitlab/suggestions/file_suggestion.rb
+++ b/lib/gitlab/suggestions/file_suggestion.rb
@@ -7,17 +7,14 @@ module Gitlab
SuggestionForDifferentFileError = Class.new(StandardError)
- def initialize
- @suggestions = []
- end
-
- def add_suggestion(new_suggestion)
- if for_different_file?(new_suggestion)
- raise SuggestionForDifferentFileError,
- 'Only add suggestions for the same file.'
- end
+ attr_reader :file_path
+ attr_reader :blob
+ attr_reader :suggestions
- suggestions << new_suggestion
+ def initialize(file_path, suggestions)
+ @file_path = file_path
+ @suggestions = suggestions.sort_by(&:from_line_index)
+ @blob = suggestions.first&.diff_file&.new_blob
end
def line_conflict?
@@ -30,18 +27,8 @@ module Gitlab
@new_content ||= _new_content
end
- def file_path
- @file_path ||= _file_path
- end
-
private
- attr_accessor :suggestions
-
- def blob
- first_suggestion&.diff_file&.new_blob
- end
-
def blob_data_lines
blob.load_all_data!
blob.data.lines
@@ -53,31 +40,19 @@ module Gitlab
def _new_content
current_content.tap do |content|
+ # NOTE: We need to cater for line number changes when the range is more than one line.
+ offset = 0
+
suggestions.each do |suggestion|
- range = line_range(suggestion)
+ range = line_range(suggestion, offset)
content[range] = suggestion.to_content
+ offset += range.count - 1
end
end.join
end
- def line_range(suggestion)
- suggestion.from_line_index..suggestion.to_line_index
- end
-
- def for_different_file?(suggestion)
- file_path && file_path != suggestion_file_path(suggestion)
- end
-
- def suggestion_file_path(suggestion)
- suggestion&.diff_file&.file_path
- end
-
- def first_suggestion
- suggestions.first
- end
-
- def _file_path
- suggestion_file_path(first_suggestion)
+ def line_range(suggestion, offset = 0)
+ (suggestion.from_line_index - offset)..(suggestion.to_line_index - offset)
end
def _line_conflict?
diff --git a/lib/gitlab/suggestions/suggestion_set.rb b/lib/gitlab/suggestions/suggestion_set.rb
index 22abef98bf0..abb05ba56a7 100644
--- a/lib/gitlab/suggestions/suggestion_set.rb
+++ b/lib/gitlab/suggestions/suggestion_set.rb
@@ -26,10 +26,10 @@ module Gitlab
end
def actions
- @actions ||= suggestions_per_file.map do |file_path, file_suggestion|
+ @actions ||= suggestions_per_file.map do |file_suggestion|
{
action: 'update',
- file_path: file_path,
+ file_path: file_suggestion.file_path,
content: file_suggestion.new_content
}
end
@@ -50,19 +50,9 @@ module Gitlab
end
def _suggestions_per_file
- suggestions.each_with_object({}) do |suggestion, result|
- file_path = suggestion.diff_file.file_path
- file_suggestion = result[file_path] ||= FileSuggestion.new
- file_suggestion.add_suggestion(suggestion)
- end
- end
-
- def file_suggestions
- suggestions_per_file.values
- end
-
- def first_file_suggestion
- file_suggestions.first
+ suggestions
+ .group_by { |suggestion| suggestion.diff_file.file_path }
+ .map { |file_path, group| FileSuggestion.new(file_path, group) }
end
def _error_message
@@ -72,7 +62,7 @@ module Gitlab
return message if message
end
- has_line_conflict = file_suggestions.any? do |file_suggestion|
+ has_line_conflict = suggestions_per_file.any? do |file_suggestion|
file_suggestion.line_conflict?
end
diff --git a/lib/gitlab/template/service_desk_template.rb b/lib/gitlab/template/service_desk_template.rb
new file mode 100644
index 00000000000..edc62a92004
--- /dev/null
+++ b/lib/gitlab/template/service_desk_template.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Template
+ class ServiceDeskTemplate < BaseTemplate
+ class << self
+ def extension
+ '.md'
+ end
+
+ def base_dir
+ '.gitlab/service_desk_templates/'
+ end
+
+ def finder(project)
+ Gitlab::Template::Finders::RepoTemplateFinder.new(project, self.base_dir, self.extension, self.categories)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/tracking/incident_management.rb b/lib/gitlab/tracking/incident_management.rb
index bd8d1669dd3..5fa819b3696 100644
--- a/lib/gitlab/tracking/incident_management.rb
+++ b/lib/gitlab/tracking/incident_management.rb
@@ -32,6 +32,9 @@ module Gitlab
},
send_email: {
name: 'sending_emails'
+ },
+ pagerduty_active: {
+ name: 'pagerduty_webhook'
}
}.with_indifferent_access.freeze
end
diff --git a/lib/gitlab/tree_summary.rb b/lib/gitlab/tree_summary.rb
index 4ec43e62c19..9b67599668a 100644
--- a/lib/gitlab/tree_summary.rb
+++ b/lib/gitlab/tree_summary.rb
@@ -97,7 +97,7 @@ module Gitlab
File.join(*[path, ""])
end
- commits_hsh = repository.list_last_commits_for_tree(commit.id, ensured_path, offset: offset, limit: limit)
+ commits_hsh = repository.list_last_commits_for_tree(commit.id, ensured_path, offset: offset, limit: limit, literal_pathspec: true)
prerender_commit_full_titles!(commits_hsh.values)
entries.each do |entry|
diff --git a/lib/gitlab/updated_notes_paginator.rb b/lib/gitlab/updated_notes_paginator.rb
new file mode 100644
index 00000000000..3d3d0e5bf9e
--- /dev/null
+++ b/lib/gitlab/updated_notes_paginator.rb
@@ -0,0 +1,74 @@
+# frozen_string_literal: true
+
+module Gitlab
+ # UpdatedNotesPaginator implements a rudimentary form of keyset pagination on
+ # top of a notes relation that has been initialized with a `last_fetched_at`
+ # value. This class will attempt to limit the number of notes returned, and
+ # specify a new value for `last_fetched_at` that will pick up where the last
+ # page of notes left off.
+ class UpdatedNotesPaginator
+ LIMIT = 50
+ MICROSECOND = 1_000_000
+
+ attr_reader :next_fetched_at, :notes
+
+ def initialize(relation, last_fetched_at:)
+ @last_fetched_at = last_fetched_at
+ @now = Time.current
+
+ notes, more = fetch_page(relation)
+ if more
+ init_middle_page(notes)
+ else
+ init_final_page(notes)
+ end
+ end
+
+ def metadata
+ { last_fetched_at: next_fetched_at_microseconds, more: more }
+ end
+
+ private
+
+ attr_reader :last_fetched_at, :more, :now
+
+ def next_fetched_at_microseconds
+ (next_fetched_at.to_i * MICROSECOND) + next_fetched_at.usec
+ end
+
+ def fetch_page(relation)
+ relation = relation.by_updated_at
+ notes = relation.at_most(LIMIT + 1).to_a
+
+ return [notes, false] unless notes.size > LIMIT
+
+ marker = notes.pop # Remove the marker note
+
+ # Although very unlikely, it is possible that more notes with the same
+ # updated_at may exist, e.g., if created in bulk. Add them all to the page
+ # if this is detected, so pagination won't get stuck indefinitely
+ if notes.last.updated_at == marker.updated_at
+ notes += relation
+ .with_updated_at(marker.updated_at)
+ .id_not_in(notes.map(&:id))
+ .to_a
+ end
+
+ [notes, true]
+ end
+
+ def init_middle_page(notes)
+ @more = true
+
+ # The fetch overlap can be ignored if we're in an intermediate page.
+ @next_fetched_at = notes.last.updated_at + NotesFinder::FETCH_OVERLAP
+ @notes = notes
+ end
+
+ def init_final_page(notes)
+ @more = false
+ @next_fetched_at = now
+ @notes = notes
+ end
+ end
+end
diff --git a/lib/gitlab/url_builder.rb b/lib/gitlab/url_builder.rb
index cd15130cee6..1e522ae63b6 100644
--- a/lib/gitlab/url_builder.rb
+++ b/lib/gitlab/url_builder.rb
@@ -71,7 +71,11 @@ module Gitlab
end
def snippet_url(snippet, **options)
- if options.delete(:raw).present?
+ if options[:file].present?
+ file, ref = options.values_at(:file, :ref)
+
+ instance.gitlab_raw_snippet_blob_url(snippet, file, ref)
+ elsif options.delete(:raw).present?
instance.gitlab_raw_snippet_url(snippet, **options)
else
instance.gitlab_snippet_url(snippet, **options)
@@ -81,9 +85,11 @@ module Gitlab
def wiki_url(wiki, **options)
return wiki_page_url(wiki, Wiki::HOMEPAGE, **options) unless options[:action]
- options[:controller] = 'projects/wikis'
- options[:namespace_id] = wiki.container.namespace
- options[:project_id] = wiki.container
+ if wiki.container.is_a?(Project)
+ options[:controller] = 'projects/wikis'
+ options[:namespace_id] = wiki.container.namespace
+ options[:project_id] = wiki.container
+ end
instance.url_for(**options)
end
diff --git a/lib/gitlab/usage_data.rb b/lib/gitlab/usage_data.rb
index 7b6f5e69ee1..9d7e6536608 100644
--- a/lib/gitlab/usage_data.rb
+++ b/lib/gitlab/usage_data.rb
@@ -18,7 +18,6 @@ module Gitlab
class << self
include Gitlab::Utils::UsageData
include Gitlab::Utils::StrongMemoize
- include Gitlab::UsageDataConcerns::Topology
def data(force_refresh: false)
Rails.cache.fetch('usage_data', force: force_refresh, expires_in: 2.weeks) do
@@ -27,16 +26,21 @@ module Gitlab
end
def uncached_data
- clear_memoized_limits
-
- license_usage_data
- .merge(system_usage_data)
- .merge(features_usage_data)
- .merge(components_usage_data)
- .merge(cycle_analytics_usage_data)
- .merge(object_store_usage_data)
- .merge(topology_usage_data)
- .merge(recording_ce_finish_data)
+ clear_memoized
+
+ with_finished_at(:recording_ce_finished_at) do
+ license_usage_data
+ .merge(system_usage_data)
+ .merge(system_usage_data_monthly)
+ .merge(features_usage_data)
+ .merge(components_usage_data)
+ .merge(cycle_analytics_usage_data)
+ .merge(object_store_usage_data)
+ .merge(topology_usage_data)
+ .merge(usage_activity_by_stage)
+ .merge(usage_activity_by_stage(:usage_activity_by_stage_monthly, last_28_days_time_period))
+ .merge(analytics_unique_visits_data)
+ end
end
def to_json(force_refresh: false)
@@ -59,17 +63,11 @@ module Gitlab
Time.now
end
- def recording_ce_finish_data
- {
- recording_ce_finished_at: Time.now
- }
- end
-
# rubocop: disable Metrics/AbcSize
# rubocop: disable CodeReuse/ActiveRecord
def system_usage_data
- alert_bot_incident_count = count(::Issue.authored(::User.alert_bot))
- issues_created_manually_from_alerts = count(Issue.with_alert_management_alerts.not_authored_by(::User.alert_bot))
+ alert_bot_incident_count = count(::Issue.authored(::User.alert_bot), start: issue_minimum_id, finish: issue_maximum_id)
+ issues_created_manually_from_alerts = count(Issue.with_alert_management_alerts.not_authored_by(::User.alert_bot), start: issue_minimum_id, finish: issue_maximum_id)
{
counts: {
@@ -86,9 +84,9 @@ module Gitlab
auto_devops_enabled: count(::ProjectAutoDevops.enabled),
auto_devops_disabled: count(::ProjectAutoDevops.disabled),
deploy_keys: count(DeployKey),
- deployments: count(Deployment),
- successful_deployments: count(Deployment.success),
- failed_deployments: count(Deployment.failed),
+ deployments: deployment_count(Deployment),
+ successful_deployments: deployment_count(Deployment.success),
+ failed_deployments: deployment_count(Deployment.failed),
environments: count(::Environment),
clusters: count(::Clusters::Cluster),
clusters_enabled: count(::Clusters::Cluster.enabled),
@@ -111,11 +109,12 @@ module Gitlab
clusters_applications_knative: count(::Clusters::Applications::Knative.available),
clusters_applications_elastic_stack: count(::Clusters::Applications::ElasticStack.available),
clusters_applications_jupyter: count(::Clusters::Applications::Jupyter.available),
+ clusters_applications_cilium: count(::Clusters::Applications::Cilium.available),
clusters_management_project: count(::Clusters::Cluster.with_management_project),
in_review_folder: count(::Environment.in_review_folder),
grafana_integrated_projects: count(GrafanaIntegration.enabled),
groups: count(Group),
- issues: count(Issue),
+ issues: count(Issue, start: issue_minimum_id, finish: issue_maximum_id),
issues_created_from_gitlab_error_tracking_ui: count(SentryIssue),
issues_with_associated_zoom_link: count(ZoomMeeting.added_to_issue),
issues_using_zoom_quick_actions: distinct_count(ZoomMeeting, :issue_id),
@@ -125,7 +124,7 @@ module Gitlab
issues_created_manually_from_alerts: issues_created_manually_from_alerts,
incident_issues: alert_bot_incident_count,
alert_bot_incident_issues: alert_bot_incident_count,
- incident_labeled_issues: count(::Issue.with_label_attributes(IncidentManagement::CreateIssueService::INCIDENT_LABEL)),
+ incident_labeled_issues: count(::Issue.with_label_attributes(::IncidentManagement::CreateIncidentLabelService::LABEL_PROPERTIES), start: issue_minimum_id, finish: issue_maximum_id),
keys: count(Key),
label_lists: count(List.label),
lfs_objects: count(LfsObject),
@@ -144,7 +143,6 @@ module Gitlab
protected_branches: count(ProtectedBranch),
releases: count(Release),
remote_mirrors: count(RemoteMirror),
- snippets: count(Snippet),
personal_snippets: count(PersonalSnippet),
project_snippets: count(ProjectSnippet),
suggestions: count(Suggestion),
@@ -161,14 +159,29 @@ module Gitlab
usage_counters,
user_preferences_usage,
ingress_modsecurity_usage,
- container_expiration_policies_usage,
- merge_requests_usage(default_time_period)
- )
+ container_expiration_policies_usage
+ ).tap do |data|
+ data[:snippets] = data[:personal_snippets] + data[:project_snippets]
+ end
}
end
- # rubocop: enable CodeReuse/ActiveRecord
# rubocop: enable Metrics/AbcSize
+ def system_usage_data_monthly
+ {
+ counts_monthly: {
+ deployments: deployment_count(Deployment.where(last_28_days_time_period)),
+ successful_deployments: deployment_count(Deployment.success.where(last_28_days_time_period)),
+ failed_deployments: deployment_count(Deployment.failed.where(last_28_days_time_period)),
+ personal_snippets: count(PersonalSnippet.where(last_28_days_time_period)),
+ project_snippets: count(ProjectSnippet.where(last_28_days_time_period))
+ }.tap do |data|
+ data[:snippets] = data[:personal_snippets] + data[:project_snippets]
+ end
+ }
+ end
+ # rubocop: enable CodeReuse/ActiveRecord
+
def cycle_analytics_usage_data
Gitlab::CycleAnalytics::UsageData.new.to_json
rescue ActiveRecord::StatementInvalid
@@ -197,6 +210,7 @@ module Gitlab
ldap_enabled: alt_usage_data(fallback: nil) { Gitlab.config.ldap.enabled },
mattermost_enabled: alt_usage_data(fallback: nil) { Gitlab.config.mattermost.enabled },
omniauth_enabled: alt_usage_data(fallback: nil) { Gitlab::Auth.omniauth_enabled? },
+ prometheus_enabled: alt_usage_data(fallback: nil) { Gitlab::Prometheus::Internal.prometheus_enabled? },
prometheus_metrics_enabled: alt_usage_data(fallback: nil) { Gitlab::Metrics.prometheus_metrics_enabled? },
reply_by_email_enabled: alt_usage_data(fallback: nil) { Gitlab::IncomingEmail.enabled? },
signup_enabled: alt_usage_data(fallback: nil) { Gitlab::CurrentSettings.allow_signup? },
@@ -290,6 +304,10 @@ module Gitlab
}
end
+ def topology_usage_data
+ Gitlab::UsageData::Topology.new.topology_usage_data
+ end
+
def ingress_modsecurity_usage
##
# This method measures usage of the Modsecurity Web Application Firewall across the entire
@@ -336,15 +354,9 @@ module Gitlab
# rubocop: disable CodeReuse/ActiveRecord
def services_usage
- results = Service.available_services_names.without('jira').each_with_object({}) do |service_name, response|
+ Service.available_services_names.without('jira').each_with_object({}) do |service_name, response|
response["projects_#{service_name}_active".to_sym] = count(Service.active.where(template: false, type: "#{service_name}_service".camelize))
- end
-
- # Keep old Slack keys for backward compatibility, https://gitlab.com/gitlab-data/analytics/issues/3241
- results[:projects_slack_notifications_active] = results[:projects_slack_active]
- results[:projects_slack_slash_active] = results[:projects_slack_slash_commands_active]
-
- results.merge(jira_usage).merge(jira_import_usage)
+ end.merge(jira_usage).merge(jira_import_usage)
end
def jira_usage
@@ -357,18 +369,15 @@ module Gitlab
projects_jira_active: 0
}
- Service.active
- .by_type(:JiraService)
- .includes(:jira_tracker_data)
- .find_in_batches(batch_size: BATCH_SIZE) do |services|
+ JiraService.active.includes(:jira_tracker_data).find_in_batches(batch_size: BATCH_SIZE) do |services|
counts = services.group_by do |service|
# TODO: Simplify as part of https://gitlab.com/gitlab-org/gitlab/issues/29404
service_url = service.data_fields&.url || (service.properties && service.properties['url'])
service_url&.include?('.atlassian.net') ? :cloud : :server
end
- results[:projects_jira_server_active] += counts[:server].count if counts[:server]
- results[:projects_jira_cloud_active] += counts[:cloud].count if counts[:cloud]
+ results[:projects_jira_server_active] += counts[:server].size if counts[:server]
+ results[:projects_jira_cloud_active] += counts[:cloud].size if counts[:cloud]
results[:projects_jira_active] += services.size
end
@@ -400,23 +409,18 @@ module Gitlab
end
# rubocop: disable CodeReuse/ActiveRecord
- def merge_requests_usage(time_period)
+ def merge_requests_users(time_period)
query =
Event
.where(target_type: Event::TARGET_TYPES[:merge_request].to_s)
.where(time_period)
- merge_request_users = distinct_count(
+ distinct_count(
query,
:author_id,
- batch_size: 5_000, # Based on query performance, this is the optimal batch size.
- start: User.minimum(:id),
- finish: User.maximum(:id)
+ start: user_minimum_id,
+ finish: user_maximum_id
)
-
- {
- merge_requests_users: merge_request_users
- }
end
# rubocop: enable CodeReuse/ActiveRecord
@@ -428,19 +432,207 @@ module Gitlab
end
end
- def default_time_period
+ def last_28_days_time_period
{ created_at: 28.days.ago..Time.current }
end
+ # Source: https://gitlab.com/gitlab-data/analytics/blob/master/transform/snowflake-dbt/data/ping_metrics_to_stage_mapping_data.csv
+ def usage_activity_by_stage(key = :usage_activity_by_stage, time_period = {})
+ {
+ key => {
+ configure: usage_activity_by_stage_configure(time_period),
+ create: usage_activity_by_stage_create(time_period),
+ manage: usage_activity_by_stage_manage(time_period),
+ monitor: usage_activity_by_stage_monitor(time_period),
+ package: usage_activity_by_stage_package(time_period),
+ plan: usage_activity_by_stage_plan(time_period),
+ release: usage_activity_by_stage_release(time_period),
+ secure: usage_activity_by_stage_secure(time_period),
+ verify: usage_activity_by_stage_verify(time_period)
+ }
+ }
+ end
+
+ # rubocop: disable CodeReuse/ActiveRecord
+ def usage_activity_by_stage_configure(time_period)
+ {
+ clusters_applications_cert_managers: cluster_applications_user_distinct_count(::Clusters::Applications::CertManager, time_period),
+ clusters_applications_helm: cluster_applications_user_distinct_count(::Clusters::Applications::Helm, time_period),
+ clusters_applications_ingress: cluster_applications_user_distinct_count(::Clusters::Applications::Ingress, time_period),
+ clusters_applications_knative: cluster_applications_user_distinct_count(::Clusters::Applications::Knative, time_period),
+ clusters_management_project: clusters_user_distinct_count(::Clusters::Cluster.with_management_project, time_period),
+ clusters_disabled: clusters_user_distinct_count(::Clusters::Cluster.disabled, time_period),
+ clusters_enabled: clusters_user_distinct_count(::Clusters::Cluster.enabled, time_period),
+ clusters_platforms_gke: clusters_user_distinct_count(::Clusters::Cluster.gcp_installed.enabled, time_period),
+ clusters_platforms_eks: clusters_user_distinct_count(::Clusters::Cluster.aws_installed.enabled, time_period),
+ clusters_platforms_user: clusters_user_distinct_count(::Clusters::Cluster.user_provided.enabled, time_period),
+ instance_clusters_disabled: clusters_user_distinct_count(::Clusters::Cluster.disabled.instance_type, time_period),
+ instance_clusters_enabled: clusters_user_distinct_count(::Clusters::Cluster.enabled.instance_type, time_period),
+ group_clusters_disabled: clusters_user_distinct_count(::Clusters::Cluster.disabled.group_type, time_period),
+ group_clusters_enabled: clusters_user_distinct_count(::Clusters::Cluster.enabled.group_type, time_period),
+ project_clusters_disabled: clusters_user_distinct_count(::Clusters::Cluster.disabled.project_type, time_period),
+ project_clusters_enabled: clusters_user_distinct_count(::Clusters::Cluster.enabled.project_type, time_period)
+ }
+ end
+ # rubocop: enable CodeReuse/ActiveRecord
+
+ # rubocop: disable CodeReuse/ActiveRecord
+ def usage_activity_by_stage_create(time_period)
+ {
+ deploy_keys: distinct_count(::DeployKey.where(time_period), :user_id),
+ keys: distinct_count(::Key.regular_keys.where(time_period), :user_id),
+ merge_requests: distinct_count(::MergeRequest.where(time_period), :author_id),
+ projects_with_disable_overriding_approvers_per_merge_request: count(::Project.where(time_period.merge(disable_overriding_approvers_per_merge_request: true))),
+ projects_without_disable_overriding_approvers_per_merge_request: count(::Project.where(time_period.merge(disable_overriding_approvers_per_merge_request: [false, nil]))),
+ remote_mirrors: distinct_count(::Project.with_remote_mirrors.where(time_period), :creator_id),
+ snippets: distinct_count(::Snippet.where(time_period), :author_id)
+ }.tap do |h|
+ if time_period.present?
+ h[:merge_requests_users] = merge_requests_users(time_period)
+ h.merge!(action_monthly_active_users(time_period))
+ end
+ end
+ end
+ # rubocop: enable CodeReuse/ActiveRecord
+
+ # Omitted because no user, creator or author associated: `campaigns_imported_from_github`, `ldap_group_links`
+ # rubocop: disable CodeReuse/ActiveRecord
+ def usage_activity_by_stage_manage(time_period)
+ {
+ events: distinct_count(::Event.where(time_period), :author_id),
+ groups: distinct_count(::GroupMember.where(time_period), :user_id),
+ users_created: count(::User.where(time_period), start: user_minimum_id, finish: user_maximum_id),
+ omniauth_providers: filtered_omniauth_provider_names.reject { |name| name == 'group_saml' }
+ }
+ end
+ # rubocop: enable CodeReuse/ActiveRecord
+
+ # rubocop: disable CodeReuse/ActiveRecord
+ def usage_activity_by_stage_monitor(time_period)
+ {
+ clusters: distinct_count(::Clusters::Cluster.where(time_period), :user_id),
+ clusters_applications_prometheus: cluster_applications_user_distinct_count(::Clusters::Applications::Prometheus, time_period),
+ operations_dashboard_default_dashboard: count(::User.active.with_dashboard('operations').where(time_period),
+ start: user_minimum_id,
+ finish: user_maximum_id)
+ }
+ end
+ # rubocop: enable CodeReuse/ActiveRecord
+
+ def usage_activity_by_stage_package(time_period)
+ {}
+ end
+
+ # Omitted because no user, creator or author associated: `boards`, `labels`, `milestones`, `uploads`
+ # Omitted because too expensive: `epics_deepest_relationship_level`
+ # Omitted because of encrypted properties: `projects_jira_cloud_active`, `projects_jira_server_active`
+ # rubocop: disable CodeReuse/ActiveRecord
+ def usage_activity_by_stage_plan(time_period)
+ {
+ issues: distinct_count(::Issue.where(time_period), :author_id),
+ notes: distinct_count(::Note.where(time_period), :author_id),
+ projects: distinct_count(::Project.where(time_period), :creator_id),
+ todos: distinct_count(::Todo.where(time_period), :author_id)
+ }
+ end
+ # rubocop: enable CodeReuse/ActiveRecord
+
+ # Omitted because no user, creator or author associated: `environments`, `feature_flags`, `in_review_folder`, `pages_domains`
+ # rubocop: disable CodeReuse/ActiveRecord
+ def usage_activity_by_stage_release(time_period)
+ {
+ deployments: distinct_count(::Deployment.where(time_period), :user_id),
+ failed_deployments: distinct_count(::Deployment.failed.where(time_period), :user_id),
+ releases: distinct_count(::Release.where(time_period), :author_id),
+ successful_deployments: distinct_count(::Deployment.success.where(time_period), :user_id)
+ }
+ end
+ # rubocop: enable CodeReuse/ActiveRecord
+
+ # Omitted because no user, creator or author associated: `ci_runners`
+ # rubocop: disable CodeReuse/ActiveRecord
+ def usage_activity_by_stage_verify(time_period)
+ {
+ ci_builds: distinct_count(::Ci::Build.where(time_period), :user_id),
+ ci_external_pipelines: distinct_count(::Ci::Pipeline.external.where(time_period), :user_id, start: user_minimum_id, finish: user_maximum_id),
+ ci_internal_pipelines: distinct_count(::Ci::Pipeline.internal.where(time_period), :user_id, start: user_minimum_id, finish: user_maximum_id),
+ ci_pipeline_config_auto_devops: distinct_count(::Ci::Pipeline.auto_devops_source.where(time_period), :user_id, start: user_minimum_id, finish: user_maximum_id),
+ ci_pipeline_config_repository: distinct_count(::Ci::Pipeline.repository_source.where(time_period), :user_id, start: user_minimum_id, finish: user_maximum_id),
+ ci_pipeline_schedules: distinct_count(::Ci::PipelineSchedule.where(time_period), :owner_id),
+ ci_pipelines: distinct_count(::Ci::Pipeline.where(time_period), :user_id, start: user_minimum_id, finish: user_maximum_id),
+ ci_triggers: distinct_count(::Ci::Trigger.where(time_period), :owner_id),
+ clusters_applications_runner: cluster_applications_user_distinct_count(::Clusters::Applications::Runner, time_period)
+ }
+ end
+ # rubocop: enable CodeReuse/ActiveRecord
+
+ # Currently too complicated and to get reliable counts for these stats:
+ # container_scanning_jobs, dast_jobs, dependency_scanning_jobs, license_management_jobs, sast_jobs, secret_detection_jobs
+ # Once https://gitlab.com/gitlab-org/gitlab/merge_requests/17568 is merged, this might be doable
+ def usage_activity_by_stage_secure(time_period)
+ {}
+ end
+
+ def analytics_unique_visits_data
+ results = ::Gitlab::Analytics::UniqueVisits::TARGET_IDS.each_with_object({}) do |target_id, hash|
+ hash[target_id] = redis_usage_data { unique_visit_service.weekly_unique_visits_for_target(target_id) }
+ end
+ results['analytics_unique_visits_for_any_target'] = redis_usage_data { unique_visit_service.weekly_unique_visits_for_any_target }
+
+ { analytics_unique_visits: results }
+ end
+
+ def action_monthly_active_users(time_period)
+ return {} unless Feature.enabled?(Gitlab::UsageDataCounters::TrackUniqueActions::FEATURE_FLAG)
+
+ counter = Gitlab::UsageDataCounters::TrackUniqueActions
+
+ project_count = redis_usage_data do
+ counter.count_unique_events(
+ event_action: Gitlab::UsageDataCounters::TrackUniqueActions::PUSH_ACTION,
+ date_from: time_period[:created_at].first,
+ date_to: time_period[:created_at].last
+ )
+ end
+
+ design_count = redis_usage_data do
+ counter.count_unique_events(
+ event_action: Gitlab::UsageDataCounters::TrackUniqueActions::DESIGN_ACTION,
+ date_from: time_period[:created_at].first,
+ date_to: time_period[:created_at].last
+ )
+ end
+
+ wiki_count = redis_usage_data do
+ counter.count_unique_events(
+ event_action: Gitlab::UsageDataCounters::TrackUniqueActions::WIKI_ACTION,
+ date_from: time_period[:created_at].first,
+ date_to: time_period[:created_at].last
+ )
+ end
+
+ {
+ action_monthly_active_users_project_repo: project_count,
+ action_monthly_active_users_design_management: design_count,
+ action_monthly_active_users_wiki_repo: wiki_count
+ }
+ end
+
private
+ def unique_visit_service
+ strong_memoize(:unique_visit_service) do
+ ::Gitlab::Analytics::UniqueVisits.new
+ end
+ end
+
def total_alert_issues
# Remove prometheus table queries once they are deprecated
# To be removed with https://gitlab.com/gitlab-org/gitlab/-/issues/217407.
[
- count(Issue.with_alert_management_alerts),
- count(::Issue.with_self_managed_prometheus_alert_events),
- count(::Issue.with_prometheus_alert_events)
+ count(Issue.with_alert_management_alerts, start: issue_minimum_id, finish: issue_maximum_id),
+ count(::Issue.with_self_managed_prometheus_alert_events, start: issue_minimum_id, finish: issue_maximum_id),
+ count(::Issue.with_prometheus_alert_events, start: issue_minimum_id, finish: issue_maximum_id)
].reduce(:+)
end
@@ -456,9 +648,66 @@ module Gitlab
end
end
- def clear_memoized_limits
+ def issue_minimum_id
+ strong_memoize(:issue_minimum_id) do
+ ::Issue.minimum(:id)
+ end
+ end
+
+ def issue_maximum_id
+ strong_memoize(:issue_maximum_id) do
+ ::Issue.maximum(:id)
+ end
+ end
+
+ def deployment_minimum_id
+ strong_memoize(:deployment_minimum_id) do
+ ::Deployment.minimum(:id)
+ end
+ end
+
+ def deployment_maximum_id
+ strong_memoize(:deployment_maximum_id) do
+ ::Deployment.maximum(:id)
+ end
+ end
+
+ def clear_memoized
+ clear_memoization(:issue_minimum_id)
+ clear_memoization(:issue_maximum_id)
clear_memoization(:user_minimum_id)
clear_memoization(:user_maximum_id)
+ clear_memoization(:unique_visit_service)
+ clear_memoization(:deployment_minimum_id)
+ clear_memoization(:deployment_maximum_id)
+ clear_memoization(:approval_merge_request_rule_minimum_id)
+ clear_memoization(:approval_merge_request_rule_maximum_id)
+ end
+
+ # rubocop: disable CodeReuse/ActiveRecord
+ def cluster_applications_user_distinct_count(applications, time_period)
+ distinct_count(applications.where(time_period).available.joins(:cluster), 'clusters.user_id')
+ end
+
+ def clusters_user_distinct_count(clusters, time_period)
+ distinct_count(clusters.where(time_period), :user_id)
+ end
+ # rubocop: enable CodeReuse/ActiveRecord
+
+ def omniauth_provider_names
+ ::Gitlab.config.omniauth.providers.map(&:name)
+ end
+
+ # LDAP provider names are set by customers and could include
+ # sensitive info (server names, etc). LDAP providers normally
+ # don't appear in omniauth providers but filter to ensure
+ # no internal details leak via usage ping.
+ def filtered_omniauth_provider_names
+ omniauth_provider_names.reject { |name| name.starts_with?('ldap') }
+ end
+
+ def deployment_count(relation)
+ count relation, start: deployment_minimum_id, finish: deployment_maximum_id
end
end
end
diff --git a/lib/gitlab/usage_data/topology.rb b/lib/gitlab/usage_data/topology.rb
new file mode 100644
index 00000000000..4bca2cb07e4
--- /dev/null
+++ b/lib/gitlab/usage_data/topology.rb
@@ -0,0 +1,258 @@
+# frozen_string_literal: true
+
+module Gitlab
+ class UsageData
+ class Topology
+ include Gitlab::Utils::UsageData
+
+ JOB_TO_SERVICE_NAME = {
+ 'gitlab-rails' => 'web',
+ 'gitlab-sidekiq' => 'sidekiq',
+ 'gitlab-workhorse' => 'workhorse',
+ 'redis' => 'redis',
+ 'postgres' => 'postgres',
+ 'gitaly' => 'gitaly',
+ 'prometheus' => 'prometheus',
+ 'node' => 'node-exporter',
+ 'registry' => 'registry'
+ }.freeze
+
+ CollectionFailure = Struct.new(:query, :error) do
+ def to_h
+ { query => error }
+ end
+ end
+
+ def topology_usage_data
+ @failures = []
+ @instances = Set[]
+ topology_data, duration = measure_duration { topology_fetch_all_data }
+ {
+ topology: topology_data
+ .merge(duration_s: duration)
+ .merge(failures: @failures.map(&:to_h))
+ }
+ end
+
+ private
+
+ def topology_fetch_all_data
+ with_prometheus_client(fallback: {}) do |client|
+ {
+ application_requests_per_hour: topology_app_requests_per_hour(client),
+ nodes: topology_node_data(client)
+ }.compact
+ end
+ rescue => e
+ @failures << CollectionFailure.new('other', e.class.to_s)
+
+ {}
+ end
+
+ def topology_app_requests_per_hour(client)
+ result = query_safely('gitlab_usage_ping:ops:rate5m', 'app_requests', fallback: nil) do |query|
+ client.query(one_week_average(query)).first
+ end
+
+ return unless result
+
+ # the metric is recorded as a per-second rate
+ (result['value'].last.to_f * 1.hour).to_i
+ end
+
+ def topology_node_data(client)
+ # node-level data
+ by_instance_mem = topology_node_memory(client)
+ by_instance_cpus = topology_node_cpus(client)
+ by_instance_uname_info = topology_node_uname_info(client)
+ # service-level data
+ by_instance_by_job_by_type_memory = topology_all_service_memory(client)
+ by_instance_by_job_process_count = topology_all_service_process_count(client)
+ by_instance_by_job_server_types = topology_all_service_server_types(client)
+
+ @instances.map do |instance|
+ {
+ node_memory_total_bytes: by_instance_mem[instance],
+ node_cpus: by_instance_cpus[instance],
+ node_uname_info: by_instance_uname_info[instance],
+ node_services:
+ topology_node_services(
+ instance, by_instance_by_job_process_count, by_instance_by_job_by_type_memory, by_instance_by_job_server_types
+ )
+ }.compact
+ end
+ end
+
+ def topology_node_memory(client)
+ query_safely('gitlab_usage_ping:node_memory_total_bytes:avg', 'node_memory', fallback: {}) do |query|
+ aggregate_by_instance(client, one_week_average(query))
+ end
+ end
+
+ def topology_node_cpus(client)
+ query_safely('gitlab_usage_ping:node_cpus:count', 'node_cpus', fallback: {}) do |query|
+ aggregate_by_instance(client, one_week_average(query))
+ end
+ end
+
+ def topology_node_uname_info(client)
+ node_uname_info = query_safely('node_uname_info', 'node_uname_info', fallback: []) do |query|
+ client.query(query)
+ end
+
+ map_instance_labels(node_uname_info, %w(machine sysname release))
+ end
+
+ def topology_all_service_memory(client)
+ {
+ rss: topology_service_memory_rss(client),
+ uss: topology_service_memory_uss(client),
+ pss: topology_service_memory_pss(client)
+ }
+ end
+
+ def topology_service_memory_rss(client)
+ query_safely(
+ 'gitlab_usage_ping:node_service_process_resident_memory_bytes:avg', 'service_rss', fallback: {}
+ ) { |query| aggregate_by_labels(client, one_week_average(query)) }
+ end
+
+ def topology_service_memory_uss(client)
+ query_safely(
+ 'gitlab_usage_ping:node_service_process_unique_memory_bytes:avg', 'service_uss', fallback: {}
+ ) { |query| aggregate_by_labels(client, one_week_average(query)) }
+ end
+
+ def topology_service_memory_pss(client)
+ query_safely(
+ 'gitlab_usage_ping:node_service_process_proportional_memory_bytes:avg', 'service_pss', fallback: {}
+ ) { |query| aggregate_by_labels(client, one_week_average(query)) }
+ end
+
+ def topology_all_service_process_count(client)
+ query_safely(
+ 'gitlab_usage_ping:node_service_process:count', 'service_process_count', fallback: {}
+ ) { |query| aggregate_by_labels(client, one_week_average(query)) }
+ end
+
+ def topology_all_service_server_types(client)
+ query_safely(
+ 'gitlab_usage_ping:node_service_app_server_workers:sum', 'service_workers', fallback: {}
+ ) { |query| aggregate_by_labels(client, query) }
+ end
+
+ def query_safely(query, query_name, fallback:)
+ result = yield query
+
+ return result if result.present?
+
+ @failures << CollectionFailure.new(query_name, 'empty_result')
+ fallback
+ rescue => e
+ @failures << CollectionFailure.new(query_name, e.class.to_s)
+ fallback
+ end
+
+ def topology_node_services(instance, all_process_counts, all_process_memory, all_server_types)
+ # returns all node service data grouped by service name as the key
+ instance_service_data =
+ topology_instance_service_process_count(instance, all_process_counts)
+ .deep_merge(topology_instance_service_memory(instance, all_process_memory))
+ .deep_merge(topology_instance_service_server_types(instance, all_server_types))
+
+ # map to list of hashes where service names become values instead, and remove
+ # unknown services, since they might not be ours
+ instance_service_data.each_with_object([]) do |entry, list|
+ service, service_metrics = entry
+ gitlab_service = JOB_TO_SERVICE_NAME[service.to_s]
+ next unless gitlab_service
+
+ list << { name: gitlab_service }.merge(service_metrics)
+ end
+ end
+
+ def topology_instance_service_process_count(instance, all_instance_data)
+ topology_data_for_instance(instance, all_instance_data).to_h do |metric, count|
+ [metric['job'], { process_count: count }]
+ end
+ end
+
+ # Given a hash mapping memory set types to Prometheus response data, returns a hash
+ # mapping instance/node names to services and their respective memory use in bytes
+ def topology_instance_service_memory(instance, instance_data_by_type)
+ result = {}
+ instance_data_by_type.each do |memory_type, instance_data|
+ topology_data_for_instance(instance, instance_data).each do |metric, memory_bytes|
+ job = metric['job']
+ key = "process_memory_#{memory_type}".to_sym
+
+ result[job] ||= {}
+ result[job][key] ||= memory_bytes
+ end
+ end
+
+ result
+ end
+
+ def topology_instance_service_server_types(instance, all_instance_data)
+ topology_data_for_instance(instance, all_instance_data).to_h do |metric, _value|
+ [metric['job'], { server: metric['server'] }]
+ end
+ end
+
+ def topology_data_for_instance(instance, all_instance_data)
+ all_instance_data.filter { |metric, _value| metric['instance'] == instance }
+ end
+
+ def normalize_instance_label(instance)
+ normalize_localhost_address(drop_port_number(instance))
+ end
+
+ def normalize_localhost_address(instance)
+ ip_addr = IPAddr.new(instance)
+ is_local_ip = ip_addr.loopback? || ip_addr.to_i.zero?
+
+ is_local_ip ? 'localhost' : instance
+ rescue IPAddr::InvalidAddressError
+ # This most likely means it was a host name, not an IP address
+ instance
+ end
+
+ def drop_port_number(instance)
+ instance.gsub(/:\d+$/, '')
+ end
+
+ def normalize_and_track_instance(instance)
+ normalize_instance_label(instance).tap do |normalized_instance|
+ @instances << normalized_instance
+ end
+ end
+
+ def one_week_average(query)
+ "avg_over_time (#{query}[1w])"
+ end
+
+ def aggregate_by_instance(client, query)
+ client.aggregate(query) { |metric| normalize_and_track_instance(metric['instance']) }
+ end
+
+ # Will retain a composite key that values are mapped to
+ def aggregate_by_labels(client, query)
+ client.aggregate(query) do |metric|
+ metric['instance'] = normalize_and_track_instance(metric['instance'])
+ metric
+ end
+ end
+
+ # Given query result vector, map instance to a hash of target labels key/value.
+ # @return [Hash] mapping instance to a hash of target labels key/value, or the empty hash if input empty vector
+ def map_instance_labels(query_result_vector, target_labels)
+ query_result_vector.to_h do |result|
+ key = normalize_and_track_instance(result['metric']['instance'])
+ value = result['metric'].slice(*target_labels).symbolize_keys
+ [key, value]
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/usage_data_concerns/topology.rb b/lib/gitlab/usage_data_concerns/topology.rb
deleted file mode 100644
index 6e1d29f2a17..00000000000
--- a/lib/gitlab/usage_data_concerns/topology.rb
+++ /dev/null
@@ -1,137 +0,0 @@
-# frozen_string_literal: true
-
-module Gitlab
- module UsageDataConcerns
- module Topology
- include Gitlab::Utils::UsageData
-
- JOB_TO_SERVICE_NAME = {
- 'gitlab-rails' => 'web',
- 'gitlab-sidekiq' => 'sidekiq',
- 'gitlab-workhorse' => 'workhorse',
- 'redis' => 'redis',
- 'postgres' => 'postgres',
- 'gitaly' => 'gitaly',
- 'prometheus' => 'prometheus',
- 'node' => 'node-exporter'
- }.freeze
-
- def topology_usage_data
- topology_data, duration = measure_duration do
- alt_usage_data(fallback: {}) do
- {
- nodes: topology_node_data
- }.compact
- end
- end
- { topology: topology_data.merge(duration_s: duration) }
- end
-
- private
-
- def topology_node_data
- with_prometheus_client do |client|
- # node-level data
- by_instance_mem = topology_node_memory(client)
- by_instance_cpus = topology_node_cpus(client)
- # service-level data
- by_instance_by_job_by_metric_memory = topology_all_service_memory(client)
- by_instance_by_job_process_count = topology_all_service_process_count(client)
-
- instances = Set.new(by_instance_mem.keys + by_instance_cpus.keys)
- instances.map do |instance|
- {
- node_memory_total_bytes: by_instance_mem[instance],
- node_cpus: by_instance_cpus[instance],
- node_services:
- topology_node_services(instance, by_instance_by_job_process_count, by_instance_by_job_by_metric_memory)
- }.compact
- end
- end
- end
-
- def topology_node_memory(client)
- aggregate_single(client, 'avg (node_memory_MemTotal_bytes) by (instance)')
- end
-
- def topology_node_cpus(client)
- aggregate_single(client, 'count (node_cpu_seconds_total{mode="idle"}) by (instance)')
- end
-
- def topology_all_service_memory(client)
- aggregate_many(
- client,
- 'avg ({__name__ =~ "(ruby_){0,1}process_(resident|unique|proportional)_memory_bytes", job != "gitlab_exporter_process"}) by (instance, job, __name__)'
- )
- end
-
- def topology_all_service_process_count(client)
- aggregate_many(client, 'count ({__name__ =~ "(ruby_){0,1}process_start_time_seconds", job != "gitlab_exporter_process"}) by (instance, job)')
- end
-
- def topology_node_services(instance, all_process_counts, all_process_memory)
- # returns all node service data grouped by service name as the key
- instance_service_data =
- topology_instance_service_process_count(instance, all_process_counts)
- .deep_merge(topology_instance_service_memory(instance, all_process_memory))
-
- # map to list of hashes where service names become values instead, and remove
- # unknown services, since they might not be ours
- instance_service_data.each_with_object([]) do |entry, list|
- service, service_metrics = entry
- gitlab_service = JOB_TO_SERVICE_NAME[service.to_s]
- next unless gitlab_service
-
- list << { name: gitlab_service }.merge(service_metrics)
- end
- end
-
- def topology_instance_service_process_count(instance, all_instance_data)
- topology_data_for_instance(instance, all_instance_data).to_h do |metric, count|
- [metric['job'], { process_count: count }]
- end
- end
-
- def topology_instance_service_memory(instance, all_instance_data)
- topology_data_for_instance(instance, all_instance_data).each_with_object({}) do |entry, hash|
- metric, memory = entry
- job = metric['job']
- key =
- case metric['__name__']
- when match_process_memory_metric_for_type('resident') then :process_memory_rss
- when match_process_memory_metric_for_type('unique') then :process_memory_uss
- when match_process_memory_metric_for_type('proportional') then :process_memory_pss
- end
-
- hash[job] ||= {}
- hash[job][key] ||= memory
- end
- end
-
- def match_process_memory_metric_for_type(type)
- /(ruby_){0,1}process_#{type}_memory_bytes/
- end
-
- def topology_data_for_instance(instance, all_instance_data)
- all_instance_data.filter { |metric, _value| metric['instance'] == instance }
- end
-
- def drop_port(instance)
- instance.gsub(/:.+$/, '')
- end
-
- # Will retain a single `instance` key that values are mapped to
- def aggregate_single(client, query)
- client.aggregate(query) { |metric| drop_port(metric['instance']) }
- end
-
- # Will retain a composite key that values are mapped to
- def aggregate_many(client, query)
- client.aggregate(query) do |metric|
- metric['instance'] = drop_port(metric['instance'])
- metric
- end
- end
- end
- end
-end
diff --git a/lib/gitlab/usage_data_counters/track_unique_actions.rb b/lib/gitlab/usage_data_counters/track_unique_actions.rb
new file mode 100644
index 00000000000..9fb5a29748e
--- /dev/null
+++ b/lib/gitlab/usage_data_counters/track_unique_actions.rb
@@ -0,0 +1,86 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module UsageDataCounters
+ module TrackUniqueActions
+ KEY_EXPIRY_LENGTH = 29.days
+ FEATURE_FLAG = :track_unique_actions
+
+ WIKI_ACTION = :wiki_action
+ DESIGN_ACTION = :design_action
+ PUSH_ACTION = :project_action
+
+ ACTION_TRANSFORMATIONS = HashWithIndifferentAccess.new({
+ wiki: {
+ created: WIKI_ACTION,
+ updated: WIKI_ACTION,
+ destroyed: WIKI_ACTION
+ },
+ design: {
+ created: DESIGN_ACTION,
+ updated: DESIGN_ACTION,
+ destroyed: DESIGN_ACTION
+ },
+ project: {
+ pushed: PUSH_ACTION
+ }
+ }).freeze
+
+ class << self
+ def track_action(event_action:, event_target:, author_id:, time: Time.zone.now)
+ return unless Gitlab::CurrentSettings.usage_ping_enabled
+ return unless Feature.enabled?(FEATURE_FLAG)
+ return unless valid_target?(event_target)
+ return unless valid_action?(event_action)
+
+ transformed_target = transform_target(event_target)
+ transformed_action = transform_action(event_action, transformed_target)
+
+ add_event(transformed_action, author_id, time)
+ end
+
+ def count_unique_events(event_action:, date_from:, date_to:)
+ keys = (date_from.to_date..date_to.to_date).map { |date| key(event_action, date) }
+
+ Gitlab::Redis::SharedState.with do |redis|
+ redis.pfcount(*keys)
+ end
+ end
+
+ private
+
+ def transform_action(event_action, event_target)
+ ACTION_TRANSFORMATIONS.dig(event_target, event_action) || event_action
+ end
+
+ def transform_target(event_target)
+ Event::TARGET_TYPES.key(event_target)
+ end
+
+ def valid_target?(target)
+ Event::TARGET_TYPES.value?(target)
+ end
+
+ def valid_action?(action)
+ Event.actions.key?(action)
+ end
+
+ def key(event_action, date)
+ year_day = date.strftime('%G-%j')
+ "#{year_day}-{#{event_action}}"
+ end
+
+ def add_event(event_action, author_id, date)
+ target_key = key(event_action, date)
+
+ Gitlab::Redis::SharedState.with do |redis|
+ redis.multi do |multi|
+ multi.pfadd(target_key, author_id)
+ multi.expire(target_key, KEY_EXPIRY_LENGTH)
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/user_access.rb b/lib/gitlab/user_access.rb
index 5e0a4faeba8..1551548d9b4 100644
--- a/lib/gitlab/user_access.rb
+++ b/lib/gitlab/user_access.rb
@@ -92,12 +92,6 @@ module Gitlab
end
end
- def can_read_project?
- return false unless can_access_git?
-
- user.can?(:read_project, project)
- end
-
private
def permission_cache
diff --git a/lib/gitlab/utils.rb b/lib/gitlab/utils.rb
index e80cc51dc3b..8f5c1eda456 100644
--- a/lib/gitlab/utils.rb
+++ b/lib/gitlab/utils.rb
@@ -56,7 +56,7 @@ module Gitlab
# * Maximum length is 63 bytes
# * First/Last Character is not a hyphen
def slugify(str)
- return str.downcase
+ str.downcase
.gsub(/[^a-z0-9]/, '-')[0..62]
.gsub(/(\A-+|-+\z)/, '')
end
@@ -178,5 +178,15 @@ module Gitlab
.group_by(&:first)
.transform_values { |kvs| kvs.map(&:last) }
end
+
+ # This sort is stable (see https://en.wikipedia.org/wiki/Sorting_algorithm#Stability)
+ # contrary to the bare Ruby sort_by method. Using just sort_by leads to
+ # instability across different platforms (e.g., x86_64-linux and x86_64-darwin18)
+ # which in turn leads to different sorting results for the equal elements across
+ # these platforms.
+ # This method uses a list item's original index position to break ties.
+ def stable_sort_by(list)
+ list.sort_by.with_index { |x, idx| [yield(x), idx] }
+ end
end
end
diff --git a/lib/gitlab/utils/markdown.rb b/lib/gitlab/utils/markdown.rb
new file mode 100644
index 00000000000..82c4a0e3b23
--- /dev/null
+++ b/lib/gitlab/utils/markdown.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Utils
+ module Markdown
+ PUNCTUATION_REGEXP = /[^\p{Word}\- ]/u.freeze
+
+ def string_to_anchor(string)
+ string
+ .strip
+ .downcase
+ .gsub(PUNCTUATION_REGEXP, '') # remove punctuation
+ .tr(' ', '-') # replace spaces with dash
+ .squeeze('-') # replace multiple dashes with one
+ .gsub(/\A(\d+)\z/, 'anchor-\1') # digits-only hrefs conflict with issue refs
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/utils/usage_data.rb b/lib/gitlab/utils/usage_data.rb
index afc4e000977..625e1076a54 100644
--- a/lib/gitlab/utils/usage_data.rb
+++ b/lib/gitlab/utils/usage_data.rb
@@ -77,11 +77,11 @@ module Gitlab
end
end
- def with_prometheus_client
- if Gitlab::Prometheus::Internal.prometheus_enabled?
- prometheus_address = Gitlab::Prometheus::Internal.uri
- yield Gitlab::PrometheusClient.new(prometheus_address, allow_local_requests: true)
- end
+ def with_prometheus_client(fallback: nil)
+ return fallback unless Gitlab::Prometheus::Internal.prometheus_enabled?
+
+ prometheus_address = Gitlab::Prometheus::Internal.uri
+ yield Gitlab::PrometheusClient.new(prometheus_address, allow_local_requests: true)
end
def measure_duration
@@ -92,6 +92,10 @@ module Gitlab
[result, duration]
end
+ def with_finished_at(key, &block)
+ yield.merge(key => Time.now)
+ end
+
private
def redis_usage_counter
diff --git a/lib/gitlab/workhorse.rb b/lib/gitlab/workhorse.rb
index c91d1b05440..6d935bb8828 100644
--- a/lib/gitlab/workhorse.rb
+++ b/lib/gitlab/workhorse.rb
@@ -216,8 +216,8 @@ module Gitlab
def gitaly_server_hash(repository)
{
- address: Gitlab::GitalyClient.address(repository.container.repository_storage),
- token: Gitlab::GitalyClient.token(repository.container.repository_storage),
+ address: Gitlab::GitalyClient.address(repository.shard),
+ token: Gitlab::GitalyClient.token(repository.shard),
features: Feature::Gitaly.server_feature_flags
}
end
diff --git a/lib/gitlab_danger.rb b/lib/gitlab_danger.rb
index 1c1763454a5..a98ac9200da 100644
--- a/lib/gitlab_danger.rb
+++ b/lib/gitlab_danger.rb
@@ -21,6 +21,7 @@ class GitlabDanger
specs
roulette
ce_ee_vue_templates
+ sidekiq_queues
].freeze
MESSAGE_PREFIX = '==>'.freeze
diff --git a/lib/google_api/auth.rb b/lib/google_api/auth.rb
index 319e5d2063c..7d9ff579c92 100644
--- a/lib/google_api/auth.rb
+++ b/lib/google_api/auth.rb
@@ -22,7 +22,7 @@ module GoogleApi
def get_token(code)
ret = client.auth_code.get_token(code, redirect_uri: redirect_uri)
- return ret.token, ret.expires_at
+ [ret.token, ret.expires_at]
end
protected
diff --git a/lib/kramdown/converter/commonmark.rb b/lib/kramdown/converter/commonmark.rb
new file mode 100644
index 00000000000..4abb34cc008
--- /dev/null
+++ b/lib/kramdown/converter/commonmark.rb
@@ -0,0 +1,109 @@
+# frozen_string_literal: true
+
+module Kramdown
+ module Converter
+ # Overrides the base Kramdown converter to add any special
+ # behaviour for CommonMark.
+ #
+ # Currently we support an option `html_tables` that outputs
+ # an HTML table instead of a Markdown table. This is to
+ # support possibly being given complex tables, such as from ADF.
+ #
+ # Note: this is only an initial implementation. Currently don't
+ # strip out IALs or other specific kramdown syntax.
+ class Commonmark < ::Kramdown::Converter::Kramdown
+ # replaces the ^ used in kramdown. This forces the current
+ # block to end, so that a different list or codeblock can be
+ # started. https://kramdown.gettalong.org/syntax.html#eob-marker
+ END_OF_BLOCK = '<!-- -->'
+
+ def convert(el, opts = { indent: 0 })
+ res = super
+
+ if [:ul, :dl, :ol, :codeblock].include?(el.type) && opts[:next] &&
+ ([el.type, :codeblock].include?(opts[:next].type) ||
+ (opts[:next].type == :blank && opts[:nnext] &&
+ [el.type, :codeblock].include?(opts[:nnext].type)))
+ # replace the end of block character
+ res.sub!(/\^\n\n\z/m, "#{END_OF_BLOCK}\n\n")
+ end
+
+ res
+ end
+
+ def convert_codeblock(el, _opts)
+ # Although tildes are supported in CommonMark, backticks are more common
+ "```#{el.options[:lang]}\n" +
+ el.value.split(/\n/).map {|l| l.empty? ? "" : "#{l}" }.join("\n") +
+ "\n```\n\n"
+ end
+
+ def convert_li(el, opts)
+ res = super
+
+ if el.children.first && el.children.first.type == :p && !el.children.first.options[:transparent]
+ if el.children.size == 1 && @stack.last.children.last == el &&
+ (@stack.last.children.any? {|c| c.children.first.type != :p } || @stack.last.children.size == 1)
+ # replace the end of block character
+ res.sub!(/\^\n\z/m, "#{END_OF_BLOCK}\n")
+ end
+ end
+
+ res
+ end
+
+ def convert_table(el, opts)
+ return super unless @options[:html_tables]
+
+ opts[:alignment] = el.options[:alignment]
+ result = inner(el, opts)
+
+ "<table>\n#{result}</table>\n\n"
+ end
+
+ def convert_thead(el, opts)
+ return super unless @options[:html_tables]
+
+ "<thead>\n#{inner(el, opts)}</thead>\n"
+ end
+
+ def convert_tbody(el, opts)
+ return super unless @options[:html_tables]
+
+ "<tbody>\n#{inner(el, opts)}</tbody>\n"
+ end
+
+ def convert_tfoot(el, opts)
+ return super unless @options[:html_tables]
+
+ "<tfoot>\n#{inner(el, opts)}</tfoot>\n"
+ end
+
+ def convert_tr(el, opts)
+ return super unless @options[:html_tables]
+
+ "<tr>\n#{el.children.map {|c| convert(c, opts) }.join}</tr>\n"
+ end
+
+ def convert_td(el, opts)
+ return super unless @options[:html_tables]
+
+ # We need to add two linefeeds in order for any inner text to
+ # be processed as markdown. The HTML block must be "closed",
+ # as referenced in the CommonMark spec
+ # @see https://spec.commonmark.org/0.29/#html-blocks
+ "<td>\n\n#{inner(el, opts)}</td>\n"
+ end
+
+ def convert_th(el, opts)
+ return super unless @options[:html_tables]
+
+ # We need to add two linefeeds in order for any inner text to
+ # be processed as markdown. The HTML block must be "closed",
+ # as referenced in the CommonMark spec
+ # @see https://spec.commonmark.org/0.29/#html-blocks
+ "<th>\n\n#{inner(el, opts)}</th>\n"
+ end
+ end
+ end
+end
diff --git a/lib/kramdown/parser/atlassian_document_format.rb b/lib/kramdown/parser/atlassian_document_format.rb
new file mode 100644
index 00000000000..4ceb879a04c
--- /dev/null
+++ b/lib/kramdown/parser/atlassian_document_format.rb
@@ -0,0 +1,381 @@
+# frozen_string_literal: true
+
+module Kramdown
+ module Parser
+ # Parses an Atlassian Document Format (ADF) json into a
+ # Kramdown AST tree, for conversion to another format.
+ # The primary goal is to convert in GitLab Markdown.
+ #
+ # This parser does NOT resolve external resources, such as media/attachments.
+ # A special url is generated for media based on the id, for example
+ # ![jira-10050-field-description](adf-media://79411c6b-50e0-477f-b4ed-ac3a5887750c)
+ # so that a later filter/process can resolve those.
+ #
+ # @see https://developer.atlassian.com/cloud/jira/platform/apis/document/structure/ ADF Document Structure
+ # @see https://developer.atlassian.com/cloud/jira/platform/apis/document/playground/ ADF Playground
+ # @see https://developer.atlassian.com/cloud/jira/platform/apis/document/viewer/ ADF Viewer
+ class AtlassianDocumentFormat < Kramdown::Parser::Base
+ unless defined?(TOP_LEVEL_BLOCK_NODES)
+ TOP_LEVEL_BLOCK_NODES = %w[blockquote
+ bulletList
+ codeBlock
+ heading
+ mediaGroup
+ mediaSingle
+ orderedList
+ panel
+ paragraph
+ rule
+ table].freeze
+
+ CHILD_BLOCK_NODES = %w[listItem
+ media
+ table_cell
+ table_header
+ table_row].freeze
+
+ INLINE_NODES = %w[emoji
+ hardBreak
+ inlineCard
+ mention
+ text].freeze
+
+ MARKS = %w[code
+ em
+ link
+ strike
+ strong
+ subsup
+ textColor
+ underline].freeze
+
+ TABLE_CELL_NODES = %w[blockquote
+ bulletList
+ codeBlock
+ heading
+ mediaGroup
+ orderedList
+ panel
+ paragraph
+ rule].freeze
+
+ LIST_ITEM_NODES = %w[bulletList
+ codeBlock
+ mediaSingle
+ orderedList
+ paragraph].freeze
+
+ PANEL_NODES = %w[bulletList
+ heading
+ orderedList
+ paragraph].freeze
+
+ PANEL_EMOJIS = { info: ':information_source:',
+ note: ':notepad_spiral:',
+ warning: ':warning:',
+ success: ':white_check_mark:',
+ error: ':octagonal_sign:' }.freeze
+
+ # The default language for code blocks is `java`, as indicated in
+ # You can't change the default in Jira. There was a comment that indicated
+ # Confluence can set the default language.
+ # @see https://jira.atlassian.com/secure/WikiRendererHelpAction.jspa?section=advanced&_ga=2.5135221.773220073.1591894917-438867908.1591894917
+ # @see https://jira.atlassian.com/browse/JRASERVER-29184?focusedCommentId=832255&page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel#comment-832255
+ CODE_BLOCK_DEFAULT_LANGUAGE = 'java'
+ end
+
+ def parse
+ ast = Gitlab::Json.parse(@source)
+
+ validate_document(ast)
+
+ process_content(@root, ast, TOP_LEVEL_BLOCK_NODES)
+ rescue ::JSON::ParserError => e
+ msg = 'Invalid Atlassian Document Format JSON'
+ Gitlab::AppLogger.error msg
+ Gitlab::AppLogger.error e
+
+ raise ::Kramdown::Error, msg
+ end
+
+ def process_content(element, ast_node, allowed_types)
+ ast_node['content'].each do |node|
+ next unless allowed_types.include?(node['type'])
+
+ public_send("process_#{node['type'].underscore}", element, node) # rubocop:disable GitlabSecurity/PublicSend
+ end
+ end
+
+ def process_blockquote(element, ast_node)
+ new_element = Element.new(:blockquote)
+ element.children << new_element
+
+ process_content(new_element, ast_node, TOP_LEVEL_BLOCK_NODES)
+ end
+
+ def process_bullet_list(element, ast_node)
+ new_element = Element.new(:ul)
+ element.children << new_element
+
+ process_content(new_element, ast_node, %w[listItem])
+ end
+
+ def process_code_block(element, ast_node)
+ code_text = gather_text(ast_node)
+ lang = ast_node.dig('attrs', 'language') || CODE_BLOCK_DEFAULT_LANGUAGE
+
+ element.children << Element.new(:codeblock, code_text, {}, { lang: lang })
+ end
+
+ def process_emoji(element, ast_node)
+ emoji = ast_node.dig('attrs', 'text') || ast_node.dig('attrs', 'shortName')
+ return unless emoji
+
+ add_text(emoji, element, :text)
+ end
+
+ def process_hard_break(element, ast_node)
+ element.children << Element.new(:br)
+ end
+
+ def process_heading(element, ast_node)
+ level = ast_node.dig('attrs', 'level').to_i.clamp(1, 6)
+ options = { level: level }
+ new_element = Element.new(:header, nil, nil, options)
+ element.children << new_element
+
+ process_content(new_element, ast_node, INLINE_NODES)
+ extract_element_text(new_element, new_element.options[:raw_text] = +'')
+ end
+
+ def process_inline_card(element, ast_node)
+ url = ast_node.dig('attrs', 'url')
+ data = ast_node.dig('attrs', 'data')
+
+ if url
+ # we don't pull a description from the link and create a panel,
+ # just convert to a normal link
+ new_element = Element.new(:text, url)
+ element.children << wrap_element(new_element, :a, nil, { 'href' => url })
+ elsif data
+ # data is JSONLD (https://json-ld.org/), so for now output
+ # as a codespan of text, with `adf-inlineCard: ` at the start
+ text = "adf-inlineCard: #{data}"
+ element.children << Element.new(:codespan, text, nil, { lang: 'adf-inlinecard' })
+ end
+ end
+
+ def process_list_item(element, ast_node)
+ new_element = Element.new(:li)
+ element.children << new_element
+
+ process_content(new_element, ast_node, LIST_ITEM_NODES)
+ end
+
+ def process_media(element, ast_node)
+ media_url = "adf-media://#{ast_node['attrs']['id']}"
+
+ case ast_node['attrs']['type']
+ when 'file'
+ attrs = { 'src' => media_url, 'alt' => ast_node['attrs']['collection'] }
+ media_element = Element.new(:img, nil, attrs)
+ when 'link'
+ attrs = { 'href' => media_url }
+ media_element = wrap_element(Element.new(:text, media_url), :a, nil, attrs)
+ end
+
+ media_element = wrap_element(media_element, :p)
+ element.children << media_element
+ end
+
+ # wraps a single media element.
+ # Currently ignore attrs.layout and attrs.width
+ def process_media_single(element, ast_node)
+ new_element = Element.new(:p)
+ element.children << new_element
+
+ process_content(new_element, ast_node, %w[media])
+ end
+
+ # wraps a group media element.
+ # Currently ignore attrs.layout and attrs.width
+ def process_media_group(element, ast_node)
+ ul_element = Element.new(:ul)
+ element.children << ul_element
+
+ ast_node['content'].each do |node|
+ next unless node['type'] == 'media'
+
+ li_element = Element.new(:li)
+ ul_element.children << li_element
+
+ process_media(li_element, node)
+ end
+ end
+
+ def process_mention(element, ast_node)
+ # Make it `@adf-mention:` since there is no guarantee that it is
+ # a valid username in our system. This gives us an
+ # opportunity to replace it later. Mention name can have
+ # spaces, so double quote it
+ mention_text = ast_node.dig('attrs', 'text')&.gsub('@', '')
+ mention_text = %Q("#{mention_text}") if mention_text.match?(/ /)
+ mention_text = %Q(@adf-mention:#{mention_text})
+
+ add_text(mention_text, element, :text)
+ end
+
+ def process_ordered_list(element, ast_node)
+ # `attrs.order` is not supported in the Kramdown AST
+ new_element = Element.new(:ol)
+ element.children << new_element
+
+ process_content(new_element, ast_node, %w[listItem])
+ end
+
+ # since we don't have something similar, then put <hr> around it and
+ # add a bolded status text (eg: "Error:") to the front of it.
+ def process_panel(element, ast_node)
+ panel_type = ast_node.dig('attrs', 'panelType')
+ return unless %w[info note warning success error].include?(panel_type)
+
+ panel_header_text = "#{PANEL_EMOJIS[panel_type.to_sym]} "
+ panel_header_element = Element.new(:text, panel_header_text)
+
+ new_element = Element.new(:blockquote)
+ new_element.children << panel_header_element
+ element.children << new_element
+
+ process_content(new_element, ast_node, PANEL_NODES)
+ end
+
+ def process_paragraph(element, ast_node)
+ new_element = Element.new(:p)
+ element.children << new_element
+
+ process_content(new_element, ast_node, INLINE_NODES)
+ end
+
+ def process_rule(element, ast_node)
+ element.children << Element.new(:hr)
+ end
+
+ def process_table(element, ast_node)
+ table = Element.new(:table, nil, nil, { alignment: [:default, :default] })
+ element.children << table
+
+ tbody = Element.new(:tbody)
+ table.children << tbody
+
+ process_content(tbody, ast_node, %w[tableRow])
+ end
+
+ # we ignore the attributes, attrs.background, attrs.colspan,
+ # attrs.colwidth, and attrs.rowspan
+ def process_table_cell(element, ast_node)
+ new_element = Element.new(:td)
+ element.children << new_element
+
+ process_content(new_element, ast_node, TABLE_CELL_NODES)
+ end
+
+ # we ignore the attributes, attrs.background, attrs.colspan,
+ # attrs.colwidth, and attrs.rowspan
+ def process_table_header(element, ast_node)
+ new_element = Element.new(:th)
+ element.children << new_element
+
+ process_content(new_element, ast_node, TABLE_CELL_NODES)
+ end
+
+ def process_table_row(element, ast_node)
+ new_element = Element.new(:tr)
+ element.children << new_element
+
+ process_content(new_element, ast_node, %w[tableHeader tableCell])
+ end
+
+ def process_text(element, ast_node)
+ new_element = Element.new(:text, ast_node['text'])
+ new_element = apply_marks(new_element, ast_node, MARKS)
+ element.children << new_element
+ end
+
+ private
+
+ def validate_document(ast)
+ return if ast['type'] == 'doc'
+
+ raise ::JSON::ParserError, 'missing doc node'
+ end
+
+ # ADF marks are an attribute on the node. For kramdown,
+ # we have to wrap the node with an element for the mark.
+ def apply_marks(element, ast_node, allowed_types)
+ return element unless ast_node['marks']
+
+ new_element = element
+
+ ast_node['marks'].each do |mark|
+ next unless allowed_types.include?(mark['type'])
+
+ case mark['type']
+ when 'code'
+ new_element = Element.new(:codespan, ast_node['text'])
+ when 'em'
+ new_element = wrap_element(new_element, :em)
+ when 'link'
+ attrs = { 'href' => mark.dig('attrs', 'href') }
+ attrs['title'] = mark.dig('attrs', 'title')
+ new_element = wrap_element(new_element, :a, nil, attrs)
+ when 'strike'
+ new_element = wrap_element(new_element, :html_element, 'del', {}, category: :span)
+ when 'strong'
+ new_element = wrap_element(new_element, :strong)
+ when 'subsup'
+ type = mark.dig('attrs', 'type')
+
+ case type
+ when 'sub'
+ new_element = wrap_element(new_element, :html_element, 'sub', {}, category: :span)
+ when 'sup'
+ new_element = wrap_element(new_element, :html_element, 'sup', {}, category: :span)
+ else
+ next
+ end
+ when 'textColor'
+ color = mark.dig('attrs', 'color')
+ new_element = wrap_element(new_element, :html_element, 'span', { color: color }, category: :span)
+ when 'underline'
+ new_element = wrap_element(new_element, :html_element, 'u', {}, category: :span)
+ else
+ next
+ end
+ end
+
+ new_element
+ end
+
+ def wrap_element(element, type, *args)
+ wrapper = Element.new(type, *args)
+ wrapper.children << element
+ wrapper
+ end
+
+ def extract_element_text(element, raw)
+ raw << element.value.to_s if element.type == :text
+ element.children.each { |c| extract_element_text(c, raw) }
+ end
+
+ def gather_text(ast_node)
+ ast_node['content'].inject('') do |memo, node|
+ node['type'] == 'text' ? (memo + node['text']) : memo
+ end
+ end
+
+ def method_missing(method, *args)
+ raise NotImplementedError, "method `#{method}` not implemented yet"
+ end
+ end
+ end
+end
diff --git a/lib/learn_gitlab.rb b/lib/learn_gitlab.rb
new file mode 100644
index 00000000000..771083193d1
--- /dev/null
+++ b/lib/learn_gitlab.rb
@@ -0,0 +1,35 @@
+# frozen_string_literal: true
+
+class LearnGitlab
+ PROJECT_NAME = 'Learn GitLab'.freeze
+ BOARD_NAME = 'GitLab onboarding'.freeze
+ LABEL_NAME = 'Novice'.freeze
+
+ def initialize(current_user)
+ @current_user = current_user
+ end
+
+ def available?
+ project && board && label
+ end
+
+ def project
+ @project ||= current_user.projects.find_by_name(PROJECT_NAME)
+ end
+
+ def board
+ return unless project
+
+ @board ||= project.boards.find_by_name(BOARD_NAME)
+ end
+
+ def label
+ return unless project
+
+ @label ||= project.labels.find_by_name(LABEL_NAME)
+ end
+
+ private
+
+ attr_reader :current_user
+end
diff --git a/lib/object_storage/direct_upload.rb b/lib/object_storage/direct_upload.rb
index 5eab882039d..76f92f62e9c 100644
--- a/lib/object_storage/direct_upload.rb
+++ b/lib/object_storage/direct_upload.rb
@@ -23,9 +23,9 @@ module ObjectStorage
MINIMUM_MULTIPART_SIZE = 5.megabytes
attr_reader :credentials, :bucket_name, :object_name
- attr_reader :has_length, :maximum_size
+ attr_reader :has_length, :maximum_size, :consolidated_settings
- def initialize(credentials, bucket_name, object_name, has_length:, maximum_size: nil)
+ def initialize(credentials, bucket_name, object_name, has_length:, maximum_size: nil, consolidated_settings: false)
unless has_length
raise ArgumentError, 'maximum_size has to be specified if length is unknown' unless maximum_size
end
@@ -35,6 +35,7 @@ module ObjectStorage
@object_name = object_name
@has_length = has_length
@maximum_size = maximum_size
+ @consolidated_settings = consolidated_settings
end
def to_hash
@@ -80,10 +81,12 @@ module ObjectStorage
end
def use_workhorse_s3_client?
- Feature.enabled?(:use_workhorse_s3_client, default_enabled: true) &&
- credentials.fetch(:use_iam_profile, false) &&
- # The Golang AWS SDK does not support V2 signatures
- credentials.fetch(:aws_signature_version, 4).to_i >= 4
+ return false unless Feature.enabled?(:use_workhorse_s3_client, default_enabled: true)
+ return false unless credentials.fetch(:use_iam_profile, false) || consolidated_settings
+ # The Golang AWS SDK does not support V2 signatures
+ return false unless credentials.fetch(:aws_signature_version, 4).to_i >= 4
+
+ true
end
def provider
@@ -92,7 +95,11 @@ module ObjectStorage
# Implements https://docs.aws.amazon.com/AmazonS3/latest/API/RESTObjectGET.html
def get_url
- connection.get_object_url(bucket_name, object_name, expire_at)
+ if google?
+ connection.get_object_https_url(bucket_name, object_name, expire_at)
+ else
+ connection.get_object_url(bucket_name, object_name, expire_at)
+ end
end
# Implements https://docs.aws.amazon.com/AmazonS3/latest/API/RESTObjectDELETE.html
@@ -166,6 +173,10 @@ module ObjectStorage
provider == 'AWS'
end
+ def google?
+ provider == 'Google'
+ end
+
def requires_multipart_upload?
aws? && !has_length
end
diff --git a/lib/pager_duty/webhook_payload_parser.rb b/lib/pager_duty/webhook_payload_parser.rb
new file mode 100644
index 00000000000..573fb36f0ca
--- /dev/null
+++ b/lib/pager_duty/webhook_payload_parser.rb
@@ -0,0 +1,66 @@
+# frozen_string_literal: true
+
+module PagerDuty
+ class WebhookPayloadParser
+ def initialize(payload)
+ @payload = payload
+ end
+
+ def self.call(payload)
+ new(payload).call
+ end
+
+ def call
+ Array(payload['messages']).map { |msg| parse_message(msg) }
+ end
+
+ private
+
+ attr_reader :payload
+
+ def parse_message(message)
+ {
+ 'event' => message['event'],
+ 'incident' => parse_incident(message['incident'])
+ }
+ end
+
+ def parse_incident(incident)
+ return {} if incident.blank?
+
+ {
+ 'url' => incident['html_url'],
+ 'incident_number' => incident['incident_number'],
+ 'title' => incident['title'],
+ 'status' => incident['status'],
+ 'created_at' => incident['created_at'],
+ 'urgency' => incident['urgency'],
+ 'incident_key' => incident['incident_key'],
+ 'assignees' => reject_empty(parse_assignees(incident)),
+ 'impacted_services' => reject_empty(parse_impacted_services(incident))
+ }
+ end
+
+ def parse_assignees(incident)
+ Array(incident['assignments']).map do |a|
+ {
+ 'summary' => a.dig('assignee', 'summary'),
+ 'url' => a.dig('assignee', 'html_url')
+ }
+ end
+ end
+
+ def parse_impacted_services(incident)
+ Array(incident['impacted_services']).map do |is|
+ {
+ 'summary' => is['summary'],
+ 'url' => is['html_url']
+ }
+ end
+ end
+
+ def reject_empty(entities)
+ Array(entities).reject { |e| e['summary'].blank? && e['url'].blank? }
+ end
+ end
+end
diff --git a/lib/peek/views/elasticsearch.rb b/lib/peek/views/elasticsearch.rb
index 626a6fb1316..4d82a6eac4f 100644
--- a/lib/peek/views/elasticsearch.rb
+++ b/lib/peek/views/elasticsearch.rb
@@ -40,7 +40,7 @@ module Peek
end
def format_call_details(call)
- super.merge(request: "#{call[:method]} #{call[:path]}")
+ super.merge(request: "#{call[:method]} #{call[:path]}?#{call[:params].to_query}")
end
end
end
diff --git a/lib/product_analytics/collector_app.rb b/lib/product_analytics/collector_app.rb
new file mode 100644
index 00000000000..cf971eef4b6
--- /dev/null
+++ b/lib/product_analytics/collector_app.rb
@@ -0,0 +1,40 @@
+# frozen_string_literal: true
+
+module ProductAnalytics
+ class CollectorApp
+ def call(env)
+ request = Rack::Request.new(env)
+ params = request.params
+
+ return not_found unless EventParams.has_required_params?(params)
+
+ # Product analytics feature is behind a flag and is disabled by default.
+ # We expect limited amount of projects with this feature enabled in first release.
+ # Since collector has no authentication we temporary prevent recording of events
+ # for project without the feature enabled. During increase of feature adoption, this
+ # check will be removed for better performance.
+ project = Project.find(params['aid'].to_i)
+ return not_found unless Feature.enabled?(:product_analytics, project, default_enabled: false)
+
+ # Snowplow tracker has own format of events.
+ # We need to convert them to match the schema of our database.
+ event_params = EventParams.parse_event_params(params)
+
+ if ProductAnalyticsEvent.create(event_params)
+ ok
+ else
+ not_found
+ end
+ rescue ActiveRecord::InvalidForeignKey, ActiveRecord::RecordNotFound
+ not_found
+ end
+
+ def ok
+ [200, {}, []]
+ end
+
+ def not_found
+ [404, {}, []]
+ end
+ end
+end
diff --git a/lib/product_analytics/event_params.rb b/lib/product_analytics/event_params.rb
new file mode 100644
index 00000000000..d938fe1f594
--- /dev/null
+++ b/lib/product_analytics/event_params.rb
@@ -0,0 +1,51 @@
+# frozen_string_literal: true
+
+module ProductAnalytics
+ # Converts params from Snowplow tracker to one compatible with
+ # GitLab ProductAnalyticsEvent model. The field naming corresponds
+ # with snowplow event model. Only project_id is GitLab specific.
+ #
+ # For information on what each field is you can check next resources:
+ # * Snowplow tracker protocol: https://github.com/snowplow/snowplow/wiki/snowplow-tracker-protocol
+ # * Canonical event model: https://github.com/snowplow/snowplow/wiki/canonical-event-model
+ class EventParams
+ def self.parse_event_params(params)
+ {
+ project_id: params['aid'],
+ platform: params['p'],
+ collector_tstamp: Time.zone.now,
+ event_id: params['eid'],
+ v_tracker: params['tv'],
+ v_collector: Gitlab::VERSION,
+ v_etl: Gitlab::VERSION,
+ os_timezone: params['tz'],
+ name_tracker: params['tna'],
+ br_lang: params['lang'],
+ doc_charset: params['cs'],
+ br_features_pdf: Gitlab::Utils.to_boolean(params['f_pdf']),
+ br_features_flash: Gitlab::Utils.to_boolean(params['f_fla']),
+ br_features_java: Gitlab::Utils.to_boolean(params['f_java']),
+ br_features_director: Gitlab::Utils.to_boolean(params['f_dir']),
+ br_features_quicktime: Gitlab::Utils.to_boolean(params['f_qt']),
+ br_features_realplayer: Gitlab::Utils.to_boolean(params['f_realp']),
+ br_features_windowsmedia: Gitlab::Utils.to_boolean(params['f_wma']),
+ br_features_gears: Gitlab::Utils.to_boolean(params['f_gears']),
+ br_features_silverlight: Gitlab::Utils.to_boolean(params['f_ag']),
+ br_colordepth: params['cd'],
+ br_cookies: Gitlab::Utils.to_boolean(params['cookie']),
+ dvce_created_tstamp: params['dtm'],
+ br_viewheight: params['vp'],
+ domain_sessionidx: params['vid'],
+ domain_sessionid: params['sid'],
+ domain_userid: params['duid'],
+ user_fingerprint: params['fp'],
+ page_referrer: params['refr'],
+ page_url: params['url']
+ }
+ end
+
+ def self.has_required_params?(params)
+ params['aid'].present? && params['eid'].present?
+ end
+ end
+end
diff --git a/lib/quality/helm3_client.rb b/lib/quality/helm3_client.rb
deleted file mode 100644
index afea73cbc50..00000000000
--- a/lib/quality/helm3_client.rb
+++ /dev/null
@@ -1,109 +0,0 @@
-# frozen_string_literal: true
-
-require 'time'
-require_relative '../gitlab/popen' unless defined?(Gitlab::Popen)
-
-module Quality
- class Helm3Client
- CommandFailedError = Class.new(StandardError)
-
- attr_reader :namespace
-
- RELEASE_JSON_ATTRIBUTES = %w[name revision updated status chart app_version namespace].freeze
- PAGINATION_SIZE = 256 # Default helm list pagination size
-
- Release = Struct.new(:name, :revision, :last_update, :status, :chart, :app_version, :namespace) do
- def revision
- @revision ||= self[:revision].to_i
- end
-
- def last_update
- @last_update ||= Time.parse(self[:last_update])
- end
- end
-
- # A single page of data and the corresponding page number.
- Page = Struct.new(:releases, :number)
-
- def initialize(namespace:)
- @namespace = namespace
- end
-
- def releases(args: [])
- each_release(args)
- end
-
- def delete(release_name:)
- run_command([
- 'uninstall',
- %(--namespace "#{namespace}"),
- release_name
- ])
- end
-
- private
-
- def run_command(command)
- final_command = ['helm', *command].join(' ')
- puts "Running command: `#{final_command}`" # rubocop:disable Rails/Output
-
- result = Gitlab::Popen.popen_with_detail([final_command])
-
- if result.status.success?
- result.stdout.chomp.freeze
- else
- raise CommandFailedError, "The `#{final_command}` command failed (status: #{result.status}) with the following error:\n#{result.stderr}"
- end
- end
-
- def raw_releases(page, args = [])
- command = [
- 'list',
- %(--namespace "#{namespace}"),
- %(--max #{PAGINATION_SIZE}),
- %(--offset #{PAGINATION_SIZE * page}),
- %(--output json),
- *args
- ]
- releases = JSON.parse(run_command(command))
-
- releases.map do |release|
- Release.new(*release.values_at(*RELEASE_JSON_ATTRIBUTES))
- end
- rescue JSON::ParserError => ex
- puts "Ignoring this JSON parsing error: #{ex}" # rubocop:disable Rails/Output
- []
- end
-
- # Fetches data from Helm and yields a Page object for every page
- # of data, without loading all of them into memory.
- #
- # method - The Octokit method to use for getting the data.
- # args - Arguments to pass to the `helm list` command.
- def each_releases_page(args, &block)
- return to_enum(__method__, args) unless block_given?
-
- page = 0
- final_args = args.dup
-
- begin
- collection = raw_releases(page, final_args)
-
- yield Page.new(collection, page += 1)
- end while collection.any?
- end
-
- # Iterates over all of the releases.
- #
- # args - Any arguments to pass to the `helm list` command.
- def each_release(args, &block)
- return to_enum(__method__, args) unless block_given?
-
- each_releases_page(args) do |page|
- page.releases.each do |release|
- yield release
- end
- end
- end
- end
-end
diff --git a/lib/quality/kubernetes_client.rb b/lib/quality/kubernetes_client.rb
deleted file mode 100644
index f83652e117f..00000000000
--- a/lib/quality/kubernetes_client.rb
+++ /dev/null
@@ -1,85 +0,0 @@
-# frozen_string_literal: true
-
-require_relative '../gitlab/popen' unless defined?(Gitlab::Popen)
-
-module Quality
- class KubernetesClient
- RESOURCE_LIST = 'ingress,svc,pdb,hpa,deploy,statefulset,job,pod,secret,configmap,pvc,secret,clusterrole,clusterrolebinding,role,rolebinding,sa,crd'
- CommandFailedError = Class.new(StandardError)
-
- attr_reader :namespace
-
- def initialize(namespace:)
- @namespace = namespace
- end
-
- def cleanup(release_name:, wait: true)
- delete_by_selector(release_name: release_name, wait: wait)
- delete_by_matching_name(release_name: release_name)
- end
-
- private
-
- def delete_by_selector(release_name:, wait:)
- selector = case release_name
- when String
- %(-l release="#{release_name}")
- when Array
- %(-l 'release in (#{release_name.join(', ')})')
- else
- raise ArgumentError, 'release_name must be a string or an array'
- end
-
- command = [
- 'delete',
- RESOURCE_LIST,
- %(--namespace "#{namespace}"),
- '--now',
- '--ignore-not-found',
- '--include-uninitialized',
- %(--wait=#{wait}),
- selector
- ]
-
- run_command(command)
- end
-
- def delete_by_matching_name(release_name:)
- resource_names = raw_resource_names
- command = [
- 'delete',
- %(--namespace "#{namespace}"),
- '--ignore-not-found'
- ]
-
- Array(release_name).each do |release|
- resource_names
- .select { |resource_name| resource_name.include?(release) }
- .each { |matching_resource| run_command(command + [matching_resource]) }
- end
- end
-
- def raw_resource_names
- command = [
- 'get',
- RESOURCE_LIST,
- %(--namespace "#{namespace}"),
- '-o name'
- ]
- run_command(command).lines.map(&:strip)
- end
-
- def run_command(command)
- final_command = ['kubectl', *command].join(' ')
- puts "Running command: `#{final_command}`" # rubocop:disable Rails/Output
-
- result = Gitlab::Popen.popen_with_detail([final_command])
-
- if result.status.success?
- result.stdout.chomp.freeze
- else
- raise CommandFailedError, "The `#{final_command}` command failed (status: #{result.status}) with the following error:\n#{result.stderr}"
- end
- end
- end
-end
diff --git a/lib/quality/seeders/issues.rb b/lib/quality/seeders/issues.rb
index 4c8cb6e97cc..ae19e86546a 100644
--- a/lib/quality/seeders/issues.rb
+++ b/lib/quality/seeders/issues.rb
@@ -29,6 +29,7 @@ module Quality
assignee_ids: Array(team.pluck(:id).sample(3)),
labels: labels.join(',')
}
+ params[:closed_at] = params[:created_at] + rand(35).days if params[:state] == 'closed'
issue = ::Issues::CreateService.new(project, team.sample, params).execute
if issue.persisted?
diff --git a/lib/quality/test_level.rb b/lib/quality/test_level.rb
index 334643fd0d3..cd94efddc1e 100644
--- a/lib/quality/test_level.rb
+++ b/lib/quality/test_level.rb
@@ -93,8 +93,14 @@ module Quality
private
+ def migration_and_background_migration_folders
+ TEST_LEVEL_FOLDERS.fetch(:migration) + TEST_LEVEL_FOLDERS.fetch(:background_migration)
+ end
+
def folders_pattern(level)
case level
+ when :migration
+ "{#{migration_and_background_migration_folders.join(',')}}"
# Geo specs aren't in a specific folder, but they all have the :geo tag, so we must search for them globally
when :all, :geo
'**'
@@ -105,6 +111,8 @@ module Quality
def folders_regex(level)
case level
+ when :migration
+ "(#{migration_and_background_migration_folders.join('|')})"
# Geo specs aren't in a specific folder, but they all have the :geo tag, so we must search for them globally
when :all, :geo
''
diff --git a/lib/rspec_flaky/flaky_examples_collection.rb b/lib/rspec_flaky/flaky_examples_collection.rb
index 290a51766e9..b86ec82bde6 100644
--- a/lib/rspec_flaky/flaky_examples_collection.rb
+++ b/lib/rspec_flaky/flaky_examples_collection.rb
@@ -23,7 +23,7 @@ module RspecFlaky
end
def to_h
- Hash[map { |uid, example| [uid, example.to_h] }].deep_symbolize_keys
+ transform_values { |example| example.to_h }.deep_symbolize_keys
end
def -(other)
diff --git a/lib/sentry/client/issue.rb b/lib/sentry/client/issue.rb
index 4a62b73a349..c5e9df9cd21 100644
--- a/lib/sentry/client/issue.rb
+++ b/lib/sentry/client/issue.rb
@@ -168,7 +168,8 @@ module Sentry
first_release_short_version: issue.dig('firstRelease', 'shortVersion'),
first_release_version: issue.dig('firstRelease', 'version'),
last_release_last_commit: issue.dig('lastRelease', 'lastCommit'),
- last_release_short_version: issue.dig('lastRelease', 'shortVersion')
+ last_release_short_version: issue.dig('lastRelease', 'shortVersion'),
+ last_release_version: issue.dig('lastRelease', 'version')
})
end
diff --git a/lib/support/logrotate/gitlab b/lib/support/logrotate/gitlab
index d9b07b61ec3..c34db47e214 100644
--- a/lib/support/logrotate/gitlab
+++ b/lib/support/logrotate/gitlab
@@ -2,6 +2,7 @@
# based on: http://stackoverflow.com/a/4883967
/home/git/gitlab/log/*.log {
+ su git git
daily
missingok
rotate 90
@@ -11,6 +12,7 @@
}
/home/git/gitlab-shell/gitlab-shell.log {
+ su git git
daily
missingok
rotate 90
diff --git a/lib/system_check/incoming_email/imap_authentication_check.rb b/lib/system_check/incoming_email/imap_authentication_check.rb
index 613c2296375..056021d460c 100644
--- a/lib/system_check/incoming_email/imap_authentication_check.rb
+++ b/lib/system_check/incoming_email/imap_authentication_check.rb
@@ -28,9 +28,12 @@ module SystemCheck
private
def try_connect_imap
- imap = Net::IMAP.new(config[:host], port: config[:port], ssl: config[:ssl])
- imap.starttls if config[:start_tls]
- imap.login(config[:email], config[:password])
+ config.each do |mailbox|
+ $stdout.puts "Checking #{mailbox[:email]}"
+ imap = Net::IMAP.new(mailbox[:host], port: mailbox[:port], ssl: mailbox[:ssl])
+ imap.starttls if mailbox[:start_tls]
+ imap.login(mailbox[:email], mailbox[:password])
+ end
true
rescue => error
@error = error
@@ -51,7 +54,7 @@ module SystemCheck
erb.filename = mail_room_config_path
config_file = YAML.load(erb.result)
- config_file.dig(:mailboxes, 0)
+ config_file[:mailboxes]
end
end
end
diff --git a/lib/tasks/cache.rake b/lib/tasks/cache.rake
index c380eb293b5..6af91d473a6 100644
--- a/lib/tasks/cache.rake
+++ b/lib/tasks/cache.rake
@@ -18,7 +18,9 @@ namespace :cache do
count: REDIS_CLEAR_BATCH_SIZE
)
- redis.del(*keys) if keys.any?
+ Gitlab::Instrumentation::RedisClusterValidator.allow_cross_slot_commands do
+ redis.del(*keys) if keys.any?
+ end
break if cursor == REDIS_SCAN_START_STOP
end
diff --git a/lib/tasks/gitlab/container_registry.rake b/lib/tasks/gitlab/container_registry.rake
index 7687cb237cc..cd18c873a5a 100644
--- a/lib/tasks/gitlab/container_registry.rake
+++ b/lib/tasks/gitlab/container_registry.rake
@@ -15,21 +15,7 @@ namespace :gitlab do
warn_user_is_not_gitlab
- url = registry_config.api_url
- # registry_info will query the /v2 route of the registry API. This route
- # requires authentication, but not authorization (the response has no body,
- # only headers that show the version of the registry). There is no
- # associated user when running this rake, so we need to generate a valid
- # JWT token with no access permissions to authenticate as a trusted client.
- token = Auth::ContainerRegistryAuthenticationService.access_token([], [])
- client = ContainerRegistry::Client.new(url, token: token)
- info = client.registry_info
-
- Gitlab::CurrentSettings.update!(
- container_registry_vendor: info[:vendor] || '',
- container_registry_version: info[:version] || '',
- container_registry_features: info[:features] || []
- )
+ UpdateContainerRegistryInfoService.new.execute
end
end
end
diff --git a/lib/tasks/gitlab/db.rake b/lib/tasks/gitlab/db.rake
index 4917d496d07..61318570fd5 100644
--- a/lib/tasks/gitlab/db.rake
+++ b/lib/tasks/gitlab/db.rake
@@ -39,6 +39,11 @@ namespace :gitlab do
# PG: http://www.postgresql.org/docs/current/static/ddl-depend.html
# Add `IF EXISTS` because cascade could have already deleted a table.
tables.each { |t| connection.execute("DROP TABLE IF EXISTS #{connection.quote_table_name(t)} CASCADE") }
+
+ # Drop all extra schema objects GitLab owns
+ Gitlab::Database::EXTRA_SCHEMAS.each do |schema|
+ connection.execute("DROP SCHEMA IF EXISTS #{connection.quote_table_name(schema)}")
+ end
end
desc 'GitLab | DB | Configures the database by running migrate, or by loading the schema and seeding if needed'
@@ -129,5 +134,37 @@ namespace :gitlab do
Rake::Task['db:structure:load'].enhance do
Rake::Task['gitlab:db:load_custom_structure'].invoke
end
+
+ desc 'Create missing dynamic database partitions'
+ task :create_dynamic_partitions do
+ Gitlab::Database::Partitioning::PartitionCreator.new.create_partitions
+ end
+
+ # This is targeted towards deploys and upgrades of GitLab.
+ # Since we're running migrations already at this time,
+ # we also check and create partitions as needed here.
+ Rake::Task['db:migrate'].enhance do
+ Rake::Task['gitlab:db:create_dynamic_partitions'].invoke
+ end
+
+ # When we load the database schema from db/structure.sql
+ # we don't have any dynamic partitions created. We don't really need to
+ # because application initializers/sidekiq take care of that, too.
+ # However, the presence of partitions for a table has influence on their
+ # position in db/structure.sql (which is topologically sorted).
+ #
+ # Other than that it's helpful to create partitions early when bootstrapping
+ # a new installation.
+ Rake::Task['db:structure:load'].enhance do
+ Rake::Task['gitlab:db:create_dynamic_partitions'].invoke
+ end
+
+ # During testing, db:test:load restores the database schema from scratch
+ # which does not include dynamic partitions. We cannot rely on application
+ # initializers here as the application can continue to run while
+ # a rake task reloads the database schema.
+ Rake::Task['db:test:load'].enhance do
+ Rake::Task['gitlab:db:create_dynamic_partitions'].invoke
+ end
end
end
diff --git a/lib/tasks/gitlab/external_diffs.rake b/lib/tasks/gitlab/external_diffs.rake
new file mode 100644
index 00000000000..08f25914007
--- /dev/null
+++ b/lib/tasks/gitlab/external_diffs.rake
@@ -0,0 +1,35 @@
+namespace :gitlab do
+ namespace :external_diffs do
+ desc "Override external diffs in file storage to be in object storage instead. This does not change the actual location of the data"
+ task force_object_storage: :environment do |t, args|
+ ansi = Gitlab::Utils.to_boolean(ENV.fetch('ANSI', true))
+ batch = ENV.fetch('BATCH_SIZE', 1000)
+ start_id = ENV.fetch('START_ID', nil)
+ end_id = ENV.fetch('END_ID', nil)
+ update_delay = args.fetch('UPDATE_DELAY', 1)
+
+ # Use ANSI codes to overwrite the same line repeatedly if supported
+ newline = ansi ? "\x1B8\x1B[2K" : "\n"
+
+ total = 0
+
+ # The only useful index on the table is by id, so scan through the whole
+ # table by that and filter out those we don't want on each relation
+ MergeRequestDiff.in_batches(of: batch, start: start_id, finish: end_id) do |relation| # rubocop:disable Cop/InBatches
+ count = relation
+ .except(:order)
+ .where(stored_externally: true, external_diff_store: ExternalDiffUploader::Store::LOCAL)
+ .update_all(external_diff_store: ExternalDiffUploader::Store::REMOTE)
+
+ total += count
+
+ if count > 0
+ print "#{newline}#{total} updated..."
+ sleep(update_delay) if update_delay > 0
+ end
+ end
+
+ puts "done!"
+ end
+ end
+end
diff --git a/lib/tasks/gitlab/packages/migrate.rake b/lib/tasks/gitlab/packages/migrate.rake
new file mode 100644
index 00000000000..cd6dcf78da3
--- /dev/null
+++ b/lib/tasks/gitlab/packages/migrate.rake
@@ -0,0 +1,23 @@
+require 'logger'
+
+desc "GitLab | Packages | Migrate packages files to remote storage"
+namespace :gitlab do
+ namespace :packages do
+ task migrate: :environment do
+ logger = Logger.new(STDOUT)
+ logger.info('Starting transfer of package files to object storage')
+
+ unless ::Packages::PackageFileUploader.object_store_enabled?
+ raise 'Object store is disabled for packages feature'
+ end
+
+ ::Packages::PackageFile.with_files_stored_locally.find_each(batch_size: 10) do |package_file|
+ package_file.file.migrate!(::Packages::PackageFileUploader::Store::REMOTE)
+
+ logger.info("Transferred package file #{package_file.id} of size #{package_file.size.to_i.bytes} to object storage")
+ rescue => e
+ logger.error("Failed to transfer package file #{package_file.id} with error: #{e.message}")
+ end
+ end
+ end
+end
diff --git a/locale/am_ET/gitlab.po b/locale/am_ET/gitlab.po
index bb68ba8c616..b5e5fac089a 100644
--- a/locale/am_ET/gitlab.po
+++ b/locale/am_ET/gitlab.po
@@ -2,7 +2,7 @@ msgid ""
msgstr ""
"Project-Id-Version: gitlab-ee\n"
"Report-Msgid-Bugs-To: \n"
-"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Last-Translator: \n"
"Language-Team: Amharic\n"
"Language: am_ET\n"
"MIME-Version: 1.0\n"
@@ -10,9 +10,11 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
"X-Crowdin-Project: gitlab-ee\n"
+"X-Crowdin-Project-ID: 288872\n"
"X-Crowdin-Language: am\n"
"X-Crowdin-File: /master/locale/gitlab.pot\n"
-"PO-Revision-Date: 2020-06-08 15:20\n"
+"X-Crowdin-File-ID: 6\n"
+"PO-Revision-Date: 2020-07-02 01:23\n"
msgid " %{start} to %{end}"
msgstr ""
@@ -197,6 +199,11 @@ msgid_plural "%d metrics"
msgstr[0] "%d መለኪያ"
msgstr[1] "%d መለኪያዎች"
+msgid "%d milestone"
+msgid_plural "%d milestones"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "%d minute"
msgid_plural "%d minutes"
msgstr[0] "%d ደቂቃ"
@@ -242,11 +249,6 @@ msgid_plural "%d unresolved threads"
msgstr[0] ""
msgstr[1] ""
-msgid "%d url scanned"
-msgid_plural "%d urls scanned"
-msgstr[0] ""
-msgstr[1] ""
-
msgid "%d vulnerability"
msgid_plural "%d vulnerabilities"
msgstr[0] ""
@@ -324,9 +326,15 @@ msgstr[1] "%{count} በመጠባበቅ ላይ ያሉ አስተያየቶች"
msgid "%{count} related %{pluralized_subject}: %{links}"
msgstr "%{count} ተዛማጅ %{pluralized_subject}: %{links}"
+msgid "%{dashboard_path} could not be found."
+msgstr ""
+
msgid "%{days} days until tags are automatically removed"
msgstr "መለያዠበራሱ ከመወገዱ በáŠá‰µ %{days} ቀናት ይቀራሉ"
+msgid "%{deployLinkStart}Use a template to deploy to ECS%{deployLinkEnd}, or use a docker image to %{commandsLinkStart}run AWS commands in GitLab CI/CD%{commandsLinkEnd}."
+msgstr ""
+
msgid "%{description}- Sentry event: %{errorUrl}- First seen: %{firstSeen}- Last seen: %{lastSeen} %{countLabel}: %{count}%{userCountLabel}: %{userCount}"
msgstr "%{description}- የSentry ክስተት: %{errorUrl}- በመጀመሪያ የታየá‹: %{firstSeen}- ለመጨረሻ ጊዜ የታየá‹: %{lastSeen} %{countLabel}: %{count}%{userCountLabel}: %{userCount}"
@@ -369,12 +377,18 @@ msgstr "%{group_name} በቡድን የሚተዳደሩ መለያዎችን ይጠá
msgid "%{host} sign-in from new location"
msgstr ""
-msgid "%{icon}You are about to add %{usersTag} people to the discussion. Proceed with caution."
-msgstr "%{icon} %{usersTag} ሰዎችን ወደ á‹á‹­á‹­á‰± ሊያክሉ áŠá‹á¢ ጥንቃቄ ያድርጉá¢"
+msgid "%{icon}You are about to add %{usersTag} people to the discussion. They will all receive a notification."
+msgstr ""
+
+msgid "%{integrations_link_start}Integrations%{link_end} enable you to make third-party applications part of your GitLab workflow. If the available integrations don't meet your needs, consider using a %{webhooks_link_start}webhook%{link_end}."
+msgstr ""
msgid "%{issuableType} will be removed! Are you sure?"
msgstr "%{issuableType} ይወገዳáˆ! እርáŒáŒ áŠ› ኖት? "
+msgid "%{issuesCount} issues in this group"
+msgstr ""
+
msgid "%{issuesSize} issues"
msgstr "%{issuesSize} ጉዳዮች"
@@ -384,7 +398,7 @@ msgstr "%{issuesSize} ጉዳዮች ባለ %{maxIssueCount} ወሰን"
msgid "%{labelStart}Class:%{labelEnd} %{class}"
msgstr ""
-msgid "%{labelStart}Confidence:%{labelEnd} %{confidence}"
+msgid "%{labelStart}Evidence:%{labelEnd} %{evidence}"
msgstr ""
msgid "%{labelStart}File:%{labelEnd} %{file}"
@@ -402,6 +416,9 @@ msgstr ""
msgid "%{labelStart}Report Type:%{labelEnd} %{reportType}"
msgstr ""
+msgid "%{labelStart}Scanner:%{labelEnd} %{scanner}"
+msgstr ""
+
msgid "%{labelStart}Severity:%{labelEnd} %{severity}"
msgstr ""
@@ -426,9 +443,6 @@ msgstr ""
msgid "%{link_start}Read more%{link_end} about role permissions"
msgstr "ስለሚና áˆá‰ƒá‹¶á‰½ %{link_start}ተጨማሪ ያንብቡ%{link_end}"
-msgid "%{link} can be used for binding events when something is happening within the project."
-msgstr ""
-
msgid "%{listToShow}, and %{awardsListLength} more."
msgstr "%{listToShow}ᣠእና %{awardsListLength} ተጨማሪá¢"
@@ -462,6 +476,9 @@ msgstr "%{name} %{resultsString} á‹­á‹­á‹›áˆ"
msgid "%{name} found %{resultsString}"
msgstr "በ%{name} á‹áˆµáŒ¥ %{resultsString} ተገáŠá‰·áˆ"
+msgid "%{name} is already being used for another emoji"
+msgstr ""
+
msgid "%{name} is scheduled for %{action}"
msgstr ""
@@ -509,6 +526,9 @@ msgstr ""
msgid "%{primary} (%{secondary})"
msgstr ""
+msgid "%{ref} cannot be added: %{error}"
+msgstr ""
+
msgid "%{releases} release"
msgid_plural "%{releases} releases"
msgstr[0] ""
@@ -517,13 +537,37 @@ msgstr[1] ""
msgid "%{remaining_approvals} left"
msgstr ""
-msgid "%{retryButtonStart}Try again%{retryButtonEnd} or %{newFileButtonStart}attach a new file%{newFileButtonEnd}"
+msgid "%{reportType} %{status} detected %{critical} critical and %{high} high severity vulnerabilities out of %{total}."
+msgstr ""
+
+msgid "%{reportType} %{status} detected %{critical} critical and %{high} high severity vulnerabilities."
+msgstr ""
+
+msgid "%{reportType} %{status} detected %{critical} critical severity vulnerabilities out of %{total}."
+msgstr ""
+
+msgid "%{reportType} %{status} detected %{critical} critical severity vulnerability."
+msgid_plural "%{reportType} %{status} detected %{critical} critical severity vulnerabilities."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{reportType} %{status} detected %{high} high severity vulnerabilities out of %{total}."
msgstr ""
-msgid "%{screenreaderOnlyStart}Keyboard shorcuts%{screenreaderOnlyEnd} Disabled"
+msgid "%{reportType} %{status} detected %{high} high severity vulnerability."
+msgid_plural "%{reportType} %{status} detected %{high} high severity vulnerabilities."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{reportType} %{status} detected %{other} vulnerability."
+msgid_plural "%{reportType} %{status} detected %{other} vulnerabilities."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{reportType} %{status} detected no new vulnerabilities."
msgstr ""
-msgid "%{screenreaderOnlyStart}Keyboard shorcuts%{screenreaderOnlyEnd} Enabled"
+msgid "%{retryButtonStart}Try again%{retryButtonEnd} or %{newFileButtonStart}attach a new file%{newFileButtonEnd}"
msgstr ""
msgid "%{service_title} %{message}."
@@ -620,6 +664,12 @@ msgstr ""
msgid "%{token}..."
msgstr ""
+msgid "%{totalCpu} (%{freeSpacePercentage}%{percentSymbol} free)"
+msgstr ""
+
+msgid "%{totalMemory} (%{freeSpacePercentage}%{percentSymbol} free)"
+msgstr ""
+
msgid "%{totalWeight} total weight"
msgstr ""
@@ -650,6 +700,12 @@ msgstr ""
msgid "%{verb} %{time_spent_value} spent time."
msgstr ""
+msgid "%{webhooks_link_start}%{webhook_type}%{link_end} enable you to send notifications to web applications in response to events in a group or project."
+msgstr ""
+
+msgid "%{webhooks_link_start}%{webhook_type}%{link_end} enable you to send notifications to web applications in response to events in a group or project. We recommend using an %{integrations_link_start}integration%{link_end} in preference to a webhook."
+msgstr ""
+
msgid "'%{level}' is not a valid visibility level"
msgstr ""
@@ -676,9 +732,6 @@ msgstr ""
msgid "(No changes)"
msgstr ""
-msgid "(Show all)"
-msgstr ""
-
msgid "(check progress)"
msgstr ""
@@ -745,6 +798,9 @@ msgstr ""
msgid "0 for unlimited"
msgstr ""
+msgid "0 for unlimited, only effective with remote storage enabled."
+msgstr ""
+
msgid "1 %{type} addition"
msgid_plural "%{count} %{type} additions"
msgstr[0] ""
@@ -892,6 +948,12 @@ msgstr ""
msgid "<code>\"johnsmith@example.com\": \"johnsmith@example.com\"</code> will add \"By <a href=\"#\">johnsmith@example.com</a>\" to all issues and comments originally created by johnsmith@example.com. By default, the email address or username is masked to ensure the user's privacy. Use this option if you want to show the full email address."
msgstr ""
+msgid "<code>Masked</code> to prevent the values from being displayed in job logs (must match certain regexp requirements)."
+msgstr ""
+
+msgid "<code>Protected</code> to expose them to protected branches or tags only."
+msgstr ""
+
msgid "<namespace / project>"
msgstr ""
@@ -919,9 +981,6 @@ msgstr ""
msgid "A .NET Core console application template, customizable for any .NET Core project"
msgstr ""
-msgid "A DAG must have two dependent jobs to be visualized on this tab."
-msgstr ""
-
msgid "A GitBook site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
msgstr ""
@@ -952,6 +1011,12 @@ msgstr ""
msgid "A deleted user"
msgstr ""
+msgid "A file has been changed."
+msgstr ""
+
+msgid "A file was not found."
+msgstr ""
+
msgid "A file with '%{file_name}' already exists in %{branch} branch"
msgstr ""
@@ -1018,7 +1083,7 @@ msgstr ""
msgid "A subscription will trigger a new pipeline on the default branch of this project when a pipeline successfully completes for a new tag on the %{default_branch_docs} of the subscribed project."
msgstr ""
-msgid "A terraform report was generated in your pipelines."
+msgid "A suggestion is not applicable."
msgstr ""
msgid "A user with write access to the source branch selected this option"
@@ -1189,6 +1254,9 @@ msgstr ""
msgid "Action to take when receiving an alert."
msgstr ""
+msgid "Actions"
+msgstr ""
+
msgid "Activate"
msgstr ""
@@ -1245,6 +1313,9 @@ msgstr ""
msgid "Add LICENSE"
msgstr ""
+msgid "Add New Node"
+msgstr ""
+
msgid "Add README"
msgstr ""
@@ -1320,6 +1391,9 @@ msgstr ""
msgid "Add approval rule"
msgstr ""
+msgid "Add approvers"
+msgstr ""
+
msgid "Add bold text"
msgstr ""
@@ -1377,6 +1451,9 @@ msgstr ""
msgid "Add strikethrough text"
msgstr ""
+msgid "Add suggestion to batch"
+msgstr ""
+
msgid "Add system hook"
msgstr ""
@@ -1476,6 +1553,9 @@ msgstr ""
msgid "Admin Note"
msgstr ""
+msgid "Admin Notifications"
+msgstr ""
+
msgid "Admin Overview"
msgstr ""
@@ -1587,6 +1667,9 @@ msgstr ""
msgid "AdminSettings|Integrations configured here will automatically apply to all projects on this instance."
msgstr ""
+msgid "AdminSettings|Moved to integrations"
+msgstr ""
+
msgid "AdminSettings|No required pipeline"
msgstr ""
@@ -1817,12 +1900,18 @@ msgid_plural "Alerts"
msgstr[0] ""
msgstr[1] ""
+msgid "AlertManagement| assign yourself"
+msgstr ""
+
msgid "AlertManagement|Acknowledged"
msgstr ""
msgid "AlertManagement|Alert"
msgstr ""
+msgid "AlertManagement|Alert assignee(s): %{assignees}"
+msgstr ""
+
msgid "AlertManagement|Alert detail"
msgstr ""
@@ -1838,9 +1927,15 @@ msgstr ""
msgid "AlertManagement|All alerts"
msgstr ""
+msgid "AlertManagement|Assign To"
+msgstr ""
+
msgid "AlertManagement|Assign status"
msgstr ""
+msgid "AlertManagement|Assignee"
+msgstr ""
+
msgid "AlertManagement|Assignees"
msgstr ""
@@ -1859,9 +1954,6 @@ msgstr ""
msgid "AlertManagement|Edit"
msgstr ""
-msgid "AlertManagement|End time"
-msgstr ""
-
msgid "AlertManagement|Events"
msgstr ""
@@ -1880,10 +1972,13 @@ msgstr ""
msgid "AlertManagement|More information"
msgstr ""
+msgid "AlertManagement|No Matching Results"
+msgstr ""
+
msgid "AlertManagement|No alert data to display."
msgstr ""
-msgid "AlertManagement|No alerts available to display. If you think you're seeing this message in error, refresh the page."
+msgid "AlertManagement|No alerts available to display. See %{linkStart}enabling alert management%{linkEnd} for more information on adding alerts to the list."
msgstr ""
msgid "AlertManagement|No alerts to display."
@@ -1892,6 +1987,9 @@ msgstr ""
msgid "AlertManagement|None"
msgstr ""
+msgid "AlertManagement|None -"
+msgstr ""
+
msgid "AlertManagement|Open"
msgstr ""
@@ -1928,9 +2026,18 @@ msgstr ""
msgid "AlertManagement|There was an error displaying the alerts. Confirm your endpoint's configuration details to ensure alerts appear."
msgstr ""
+msgid "AlertManagement|There was an error while updating the assignee(s) list. Please try again."
+msgstr ""
+
+msgid "AlertManagement|There was an error while updating the assignee(s) of the alert. Please try again."
+msgstr ""
+
msgid "AlertManagement|There was an error while updating the status of the alert. Please try again."
msgstr ""
+msgid "AlertManagement|This assignee cannot be assigned to this alert."
+msgstr ""
+
msgid "AlertManagement|Tool"
msgstr ""
@@ -2078,6 +2185,9 @@ msgstr ""
msgid "Allow users to request access (if visibility is public or internal)"
msgstr ""
+msgid "Allowed Geo IP"
+msgstr ""
+
msgid "Allowed email domain restriction only permitted for top-level groups"
msgstr ""
@@ -2111,6 +2221,9 @@ msgstr ""
msgid "Amazon Web Services"
msgstr ""
+msgid "Amazon Web Services Logo"
+msgstr ""
+
msgid "Amazon authentication is not %{link_start}correctly configured%{link_end}. Ask your GitLab administrator if you want to use this service."
msgstr ""
@@ -2168,10 +2281,16 @@ msgstr ""
msgid "An error occurred when updating the issue weight"
msgstr ""
+msgid "An error occurred while acknowledging the notification. Refresh the page and try again."
+msgstr ""
+
+msgid "An error occurred while adding approvers"
+msgstr ""
+
msgid "An error occurred while adding formatted title for epic"
msgstr ""
-msgid "An error occurred while checking group path"
+msgid "An error occurred while checking group path. Please refresh and try again."
msgstr ""
msgid "An error occurred while committing your changes."
@@ -2315,7 +2434,7 @@ msgstr ""
msgid "An error occurred while loading milestones"
msgstr ""
-msgid "An error occurred while loading terraform report"
+msgid "An error occurred while loading project creation UI"
msgstr ""
msgid "An error occurred while loading the data. Please try again."
@@ -2369,6 +2488,9 @@ msgstr ""
msgid "An error occurred while rendering preview broadcast message"
msgstr ""
+msgid "An error occurred while rendering the editor"
+msgstr ""
+
msgid "An error occurred while reordering issues."
msgstr ""
@@ -2465,7 +2587,7 @@ msgstr ""
msgid "Analyze your dependencies for known vulnerabilities."
msgstr ""
-msgid "Analyze your source code and git history for secrets"
+msgid "Analyze your source code and git history for secrets."
msgstr ""
msgid "Analyze your source code for known vulnerabilities."
@@ -2579,12 +2701,18 @@ msgstr ""
msgid "Apply suggestion"
msgstr ""
+msgid "Apply suggestions"
+msgstr ""
+
msgid "Apply template"
msgstr ""
msgid "Apply this approval rule to any branch or a specific protected branch."
msgstr ""
+msgid "Applying"
+msgstr ""
+
msgid "Applying a template will replace the existing issue description. Any changes you have made will be lost."
msgstr ""
@@ -2597,12 +2725,18 @@ msgstr ""
msgid "Applying multiple commands"
msgstr ""
-msgid "Applying suggestion"
+msgid "Applying suggestion..."
+msgstr ""
+
+msgid "Applying suggestions..."
msgstr ""
msgid "Approval rules"
msgstr ""
+msgid "Approval rules reset to project defaults"
+msgstr ""
+
msgid "ApprovalRuleRemove|%d member"
msgid_plural "ApprovalRuleRemove|%d members"
msgstr[0] ""
@@ -2656,6 +2790,9 @@ msgstr ""
msgid "Approved"
msgstr ""
+msgid "Approved MRs"
+msgstr ""
+
msgid "Approved by: "
msgstr ""
@@ -2869,6 +3006,9 @@ msgstr ""
msgid "Assign"
msgstr ""
+msgid "Assign Iteration"
+msgstr ""
+
msgid "Assign custom color like #FF0000"
msgstr ""
@@ -2887,6 +3027,9 @@ msgstr ""
msgid "Assign to"
msgstr ""
+msgid "Assign to commenting user"
+msgstr ""
+
msgid "Assign yourself to these issues"
msgstr ""
@@ -2913,6 +3056,9 @@ msgid_plural "%d Assignees"
msgstr[0] ""
msgstr[1] ""
+msgid "Assignee has no permissions"
+msgstr ""
+
msgid "Assignee lists not available with your current license"
msgstr ""
@@ -2922,6 +3068,9 @@ msgstr ""
msgid "Assignee(s)"
msgstr ""
+msgid "Assignees"
+msgstr ""
+
msgid "Assigns %{assignee_users_sentence}."
msgstr ""
@@ -2957,6 +3106,9 @@ msgstr ""
msgid "Audit Events is a way to keep track of important events that happened in GitLab."
msgstr ""
+msgid "Audit Log"
+msgstr ""
+
msgid "AuditEvents|(removed)"
msgstr ""
@@ -2993,6 +3145,9 @@ msgstr ""
msgid "AuditLogs|IP Address"
msgstr ""
+msgid "AuditLogs|Member Events"
+msgstr ""
+
msgid "AuditLogs|No matching %{type} found."
msgstr ""
@@ -3275,9 +3430,6 @@ msgstr ""
msgid "Badges|e.g. %{exampleUrl}"
msgstr ""
-msgid "Badge|New"
-msgstr ""
-
msgid "Balsamiq file could not be loaded."
msgstr ""
@@ -3326,6 +3478,12 @@ msgstr ""
msgid "Below you will find all the groups that are public."
msgstr ""
+msgid "Beta"
+msgstr ""
+
+msgid "Bi-weekly code coverage"
+msgstr ""
+
msgid "Billing"
msgstr ""
@@ -3386,9 +3544,15 @@ msgstr ""
msgid "Bitbucket Server Import"
msgstr ""
+msgid "Bitbucket Server import"
+msgstr ""
+
msgid "Bitbucket import"
msgstr ""
+msgid "Blame"
+msgstr ""
+
msgid "Blocked"
msgstr ""
@@ -3401,9 +3565,6 @@ msgstr ""
msgid "Blog"
msgstr ""
-msgid "Blue helpers indicate an action to be taken."
-msgstr ""
-
msgid "Board name"
msgstr ""
@@ -3602,6 +3763,9 @@ msgstr ""
msgid "Broadcast Message was successfully updated."
msgstr ""
+msgid "Broadcast Messages"
+msgstr ""
+
msgid "Browse Directory"
msgstr ""
@@ -3656,6 +3820,9 @@ msgstr ""
msgid "By %{user_name}"
msgstr ""
+msgid "By URL"
+msgstr ""
+
msgid "By default GitLab sends emails in HTML and plain text formats so mail clients can choose what format to use. Disable this option if you only want to send emails in plain text format."
msgstr ""
@@ -3761,6 +3928,9 @@ msgstr ""
msgid "Can override approvers and approvals required per merge request"
msgstr ""
+msgid "Can't apply as this line has changed or the suggestion already matches its content."
+msgstr ""
+
msgid "Can't create snippet: %{err}"
msgstr ""
@@ -3947,9 +4117,6 @@ msgstr ""
msgid "Changes are still tracked. Useful for cluster/index migrations."
msgstr ""
-msgid "Changes are unknown"
-msgstr ""
-
msgid "Changes suppressed. Click to show."
msgstr ""
@@ -4256,15 +4423,9 @@ msgstr ""
msgid "Choose what content you want to see on a group’s overview page"
msgstr ""
-msgid "Choose which groups you wish to synchronize to this secondary node"
-msgstr ""
-
msgid "Choose which repositories you want to connect and run CI/CD pipelines."
msgstr ""
-msgid "Choose which shards you wish to synchronize to this secondary node"
-msgstr ""
-
msgid "Choose your framework"
msgstr ""
@@ -4562,9 +4723,15 @@ msgstr ""
msgid "Cluster does not exist"
msgstr ""
+msgid "Cluster is required for Stages::ClusterEndpointInserter"
+msgstr ""
+
msgid "Cluster level"
msgstr ""
+msgid "Cluster type must be specificed for Stages::ClusterEndpointInserter"
+msgstr ""
+
msgid "ClusterIntegration| %{custom_domain_start}More information%{custom_domain_end}."
msgstr ""
@@ -4676,7 +4843,7 @@ msgstr ""
msgid "ClusterIntegration|Cert-Manager"
msgstr ""
-msgid "ClusterIntegration|Cert-Manager is a native Kubernetes certificate management controller that helps with issuing certificates. Installing Cert-Manager on your cluster will issue a certificate by %{letsEncrypt} and ensure that certificates are valid and up-to-date."
+msgid "ClusterIntegration|Cert-Manager is a native Kubernetes certificate management controller that helps with issuing certificates. Installing Cert-Manager on your cluster will issue a certificate by %{linkStart}Let's Encrypt%{linkEnd} and ensure that certificates are valid and up-to-date."
msgstr ""
msgid "ClusterIntegration|Certificate Authority bundle (PEM format)"
@@ -4703,9 +4870,6 @@ msgstr ""
msgid "ClusterIntegration|Clear the local cache of namespace and service accounts. This is necessary if your integration has become out of sync. The cache is repopulated during the next CI job that requires namespace and service accounts."
msgstr ""
-msgid "ClusterIntegration|Cloud Run"
-msgstr ""
-
msgid "ClusterIntegration|Cluster being created"
msgstr ""
@@ -4790,7 +4954,7 @@ msgstr ""
msgid "ClusterIntegration|Crossplane"
msgstr ""
-msgid "ClusterIntegration|Crossplane enables declarative provisioning of managed services from your cloud of choice using %{codeStart}kubectl%{codeEnd} or %{gitlabIntegrationLink}. Crossplane runs inside your Kubernetes cluster and supports secure connectivity and secrets management between app containers and the cloud services they depend on."
+msgid "ClusterIntegration|Crossplane enables declarative provisioning of managed services from your cloud of choice using %{codeStart}kubectl%{codeEnd} or %{linkStart}GitLab Integration%{linkEnd}. Crossplane runs inside your Kubernetes cluster and supports secure connectivity and secrets management between app containers and the cloud services they depend on."
msgstr ""
msgid "ClusterIntegration|Deletes all GitLab resources attached to this cluster during removal"
@@ -4874,9 +5038,6 @@ msgstr ""
msgid "ClusterIntegration|GitLab-managed cluster"
msgstr ""
-msgid "ClusterIntegration|Gitlab Integration"
-msgstr ""
-
msgid "ClusterIntegration|Global default"
msgstr ""
@@ -4922,7 +5083,7 @@ msgstr ""
msgid "ClusterIntegration|Ingress gives you a way to route requests to services based on the request host or path, centralizing a number of services into a single entrypoint."
msgstr ""
-msgid "ClusterIntegration|Installing Ingress may incur additional costs. Learn more about %{pricingLink}."
+msgid "ClusterIntegration|Installing Ingress may incur additional costs. Learn more about %{linkStart}pricing%{linkEnd}."
msgstr ""
msgid "ClusterIntegration|Instance cluster"
@@ -4937,7 +5098,7 @@ msgstr ""
msgid "ClusterIntegration|Issuer Email"
msgstr ""
-msgid "ClusterIntegration|Issuers represent a certificate authority. You must provide an email address for your Issuer. "
+msgid "ClusterIntegration|Issuers represent a certificate authority. You must provide an email address for your Issuer."
msgstr ""
msgid "ClusterIntegration|Jupyter Hostname"
@@ -5009,9 +5170,6 @@ msgstr ""
msgid "ClusterIntegration|Learn more about instance Kubernetes clusters"
msgstr ""
-msgid "ClusterIntegration|Let's Encrypt"
-msgstr ""
-
msgid "ClusterIntegration|Loading IAM Roles"
msgstr ""
@@ -5123,7 +5281,7 @@ msgstr ""
msgid "ClusterIntegration|Prometheus"
msgstr ""
-msgid "ClusterIntegration|Prometheus is an open-source monitoring system with %{gitlabIntegrationLink} to monitor deployed applications."
+msgid "ClusterIntegration|Prometheus is an open-source monitoring system with %{linkStart}GitLab Integration%{linkEnd} to monitor deployed applications."
msgstr ""
msgid "ClusterIntegration|Provider details"
@@ -5279,10 +5437,10 @@ msgstr ""
msgid "ClusterIntegration|Select zone to choose machine type"
msgstr ""
-msgid "ClusterIntegration|Send Cilium Logs"
+msgid "ClusterIntegration|Send Container Network Policies Logs"
msgstr ""
-msgid "ClusterIntegration|Send ModSecurity Logs"
+msgid "ClusterIntegration|Send Web Application Firewall Logs"
msgstr ""
msgid "ClusterIntegration|Service Token"
@@ -5450,15 +5608,12 @@ msgstr ""
msgid "ClusterIntegration|documentation"
msgstr ""
-msgid "ClusterIntegration|installed via %{installed_via}"
+msgid "ClusterIntegration|installed via %{linkStart}Cloud Run%{linkEnd}"
msgstr ""
msgid "ClusterIntegration|meets the requirements"
msgstr ""
-msgid "ClusterIntegration|pricing"
-msgstr ""
-
msgid "ClusterIntegration|sign up"
msgstr ""
@@ -5495,6 +5650,15 @@ msgstr ""
msgid "Code"
msgstr ""
+msgid "Code Coverage: %{coveragePercentage}%{percentSymbol}"
+msgstr ""
+
+msgid "Code Coverage| Empty code coverage data"
+msgstr ""
+
+msgid "Code Coverage|Couldn't fetch the code coverage data"
+msgstr ""
+
msgid "Code Owners"
msgstr ""
@@ -5549,7 +5713,7 @@ msgstr ""
msgid "Collapse approvers"
msgstr ""
-msgid "Collapse child epics"
+msgid "Collapse milestones"
msgstr ""
msgid "Collapse replies"
@@ -5567,6 +5731,9 @@ msgstr ""
msgid "Coming soon"
msgstr ""
+msgid "Comma-separated, e.g. '1.1.1.1, 2.2.2.0/24'"
+msgstr ""
+
msgid "Command"
msgstr ""
@@ -5737,6 +5904,12 @@ msgstr ""
msgid "Compliance framework (optional)"
msgstr ""
+msgid "Compliance frameworks"
+msgstr ""
+
+msgid "ComplianceDashboard|created by:"
+msgstr ""
+
msgid "ComplianceFramework|GDPR"
msgstr ""
@@ -5875,6 +6048,9 @@ msgstr ""
msgid "Connection timed out"
msgstr ""
+msgid "Connection timeout"
+msgstr ""
+
msgid "Contact sales to upgrade"
msgstr ""
@@ -5930,9 +6106,6 @@ msgstr ""
msgid "ContainerRegistry|CLI Commands"
msgstr ""
-msgid "ContainerRegistry|Compressed Size"
-msgstr ""
-
msgid "ContainerRegistry|Container Registry"
msgstr ""
@@ -5948,6 +6121,9 @@ msgstr ""
msgid "ContainerRegistry|Copy push command"
msgstr ""
+msgid "ContainerRegistry|Delete selected"
+msgstr ""
+
msgid "ContainerRegistry|Docker connection error"
msgstr ""
@@ -5981,16 +6157,16 @@ msgstr ""
msgid "ContainerRegistry|If you are not already logged in, you need to authenticate to the Container Registry by using your GitLab username and password. If you have %{twofaDocLinkStart}Two-Factor Authentication%{twofaDocLinkEnd} enabled, use a %{personalAccessTokensDocLinkStart}Personal Access Token%{personalAccessTokensDocLinkEnd} instead of a password."
msgstr ""
-msgid "ContainerRegistry|Image ID"
+msgid "ContainerRegistry|Image ID: %{imageId}"
msgstr ""
msgid "ContainerRegistry|Image Repositories"
msgstr ""
-msgid "ContainerRegistry|Keep and protect the images that matter most."
+msgid "ContainerRegistry|Image tags"
msgstr ""
-msgid "ContainerRegistry|Last Updated"
+msgid "ContainerRegistry|Keep and protect the images that matter most."
msgstr ""
msgid "ContainerRegistry|Login"
@@ -6005,6 +6181,9 @@ msgstr ""
msgid "ContainerRegistry|Please contact your administrator."
msgstr ""
+msgid "ContainerRegistry|Published %{timeInfo}"
+msgstr ""
+
msgid "ContainerRegistry|Push an image"
msgstr ""
@@ -6020,9 +6199,6 @@ msgstr ""
msgid "ContainerRegistry|Remove repository"
msgstr ""
-msgid "ContainerRegistry|Remove selected tags"
-msgstr ""
-
msgid "ContainerRegistry|Remove tag"
msgid_plural "ContainerRegistry|Remove tags"
msgstr[0] ""
@@ -6052,9 +6228,6 @@ msgstr ""
msgid "ContainerRegistry|Sorry, your filter produced no results."
msgstr ""
-msgid "ContainerRegistry|Tag"
-msgstr ""
-
msgid "ContainerRegistry|Tag expiration policy"
msgstr ""
@@ -6199,21 +6372,6 @@ msgstr ""
msgid "Control the display of third party offers."
msgstr ""
-msgid "Control the maximum concurrency of LFS/attachment backfill for this secondary node"
-msgstr ""
-
-msgid "Control the maximum concurrency of container repository operations for this Geo node"
-msgstr ""
-
-msgid "Control the maximum concurrency of repository backfill for this secondary node"
-msgstr ""
-
-msgid "Control the maximum concurrency of verification operations for this Geo node"
-msgstr ""
-
-msgid "Control the minimum interval in days that a repository should be reverified for this primary node"
-msgstr ""
-
msgid "Cookie domain"
msgstr ""
@@ -6355,6 +6513,9 @@ msgstr ""
msgid "Could not find design."
msgstr ""
+msgid "Could not find iteration"
+msgstr ""
+
msgid "Could not remove the trigger."
msgstr ""
@@ -6391,6 +6552,12 @@ msgstr ""
msgid "Coverage"
msgstr ""
+msgid "Coverage Fuzzing"
+msgstr ""
+
+msgid "Crash State"
+msgstr ""
+
msgid "Create"
msgstr ""
@@ -6439,6 +6606,9 @@ msgstr ""
msgid "Create a personal access token on your account to pull or push via %{protocol}."
msgstr ""
+msgid "Create a project pre-populated with the necessary files to get you started quickly."
+msgstr ""
+
msgid "Create an account using:"
msgstr ""
@@ -6607,6 +6777,9 @@ msgstr ""
msgid "Creates branch '%{branch_name}' and a merge request to resolve this issue."
msgstr ""
+msgid "Creating"
+msgstr ""
+
msgid "Creating epic"
msgstr ""
@@ -6658,7 +6831,10 @@ msgstr ""
msgid "Current vulnerabilities count"
msgstr ""
-msgid "CurrentUser|Buy CI minutes"
+msgid "CurrentUser|Buy Pipeline minutes"
+msgstr ""
+
+msgid "CurrentUser|One of your groups is running out"
msgstr ""
msgid "CurrentUser|Profile"
@@ -6673,6 +6849,9 @@ msgstr ""
msgid "CurrentUser|Upgrade"
msgstr ""
+msgid "Custom Attributes"
+msgstr ""
+
msgid "Custom CI configuration path"
msgstr ""
@@ -6936,12 +7115,18 @@ msgstr ""
msgid "DAG"
msgstr ""
+msgid "DAG visualization requires at least 3 dependent jobs."
+msgstr ""
+
msgid "DNS"
msgstr ""
msgid "Dashboard"
msgstr ""
+msgid "Dashboard uid not found"
+msgstr ""
+
msgid "DashboardProjects|All"
msgstr ""
@@ -6966,6 +7151,9 @@ msgstr ""
msgid "Data is still calculating..."
msgstr ""
+msgid "Datasource name not found"
+msgstr ""
+
msgid "Date"
msgstr ""
@@ -7146,6 +7334,9 @@ msgstr ""
msgid "Delete this attachment"
msgstr ""
+msgid "Delete user list"
+msgstr ""
+
msgid "Delete variable"
msgstr ""
@@ -7462,6 +7653,9 @@ msgstr ""
msgid "Deploying to"
msgstr ""
+msgid "Deploying to AWS is easy with GitLab"
+msgstr ""
+
msgid "Deployment Frequency"
msgstr ""
@@ -7824,6 +8018,9 @@ msgstr ""
msgid "Dismissed on pipeline %{pipelineLink} at %{projectLink}"
msgstr ""
+msgid "Display alerts from all your monitoring tools directly within GitLab."
+msgstr ""
+
msgid "Display name"
msgstr ""
@@ -7881,9 +8078,6 @@ msgstr ""
msgid "Don't show again"
msgstr ""
-msgid "Don't worry, you can access this tour by clicking on the help icon in the top right corner and choose <strong>Learn GitLab</strong>."
-msgstr ""
-
msgid "Done"
msgstr ""
@@ -8010,6 +8204,9 @@ msgstr ""
msgid "Edit Release"
msgstr ""
+msgid "Edit Slack integration"
+msgstr ""
+
msgid "Edit Snippet"
msgstr ""
@@ -8049,6 +8246,9 @@ msgstr ""
msgid "Edit issues"
msgstr ""
+msgid "Edit iteration"
+msgstr ""
+
msgid "Edit public deploy key"
msgstr ""
@@ -8097,6 +8297,9 @@ msgstr ""
msgid "Email"
msgstr ""
+msgid "Email Notification"
+msgstr ""
+
msgid "Email address"
msgstr ""
@@ -8109,6 +8312,9 @@ msgstr ""
msgid "Email not verified. Please verify your email in Salesforce."
msgstr ""
+msgid "Email notification for unknown sign-ins"
+msgstr ""
+
msgid "Email patch"
msgstr ""
@@ -8340,6 +8546,9 @@ msgstr ""
msgid "Enforce DNS rebinding attack protection"
msgstr ""
+msgid "Enforce personal access token expiration"
+msgstr ""
+
msgid "Ensure connectivity is available from the GitLab server to the Prometheus server"
msgstr ""
@@ -8409,6 +8618,9 @@ msgstr ""
msgid "Enter the name of your application, and we'll return a unique %{type}."
msgstr ""
+msgid "Enter weights for storages for new repositories."
+msgstr ""
+
msgid "Enter your password to approve"
msgstr ""
@@ -8418,15 +8630,27 @@ msgstr ""
msgid "Environment does not have deployments"
msgstr ""
+msgid "Environment is required for Stages::MetricEndpointInserter"
+msgstr ""
+
+msgid "Environment is required for Stages::VariableEndpointInserter"
+msgstr ""
+
msgid "Environment scope"
msgstr ""
+msgid "Environment variables are applied to all project environments in this instance via the Runner. You can use environment variables for passwords, secret keys, etc. Make variables available to the running application by prepending the variable key with <code>K8S_SECRET_</code>. You can set variables to be:"
+msgstr ""
+
msgid "Environment variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. Additionally, they can be masked so they are hidden in job logs, though they must match certain regexp requirements to do so. You can use environment variables for passwords, secret keys, or whatever you want."
msgstr ""
msgid "Environment variables are configured by your administrator to be %{link_start}protected%{link_end} by default"
msgstr ""
+msgid "Environment variables on this GitLab instance are configured to be %{link_start}protected%{link_end} by default"
+msgstr ""
+
msgid "Environment:"
msgstr ""
@@ -8958,6 +9182,9 @@ msgstr ""
msgid "EventFilterBy|Filter by comments"
msgstr ""
+msgid "EventFilterBy|Filter by designs"
+msgstr ""
+
msgid "EventFilterBy|Filter by epic events"
msgstr ""
@@ -9090,15 +9317,15 @@ msgstr ""
msgid "Expand approvers"
msgstr ""
-msgid "Expand child epics"
-msgstr ""
-
msgid "Expand down"
msgstr ""
msgid "Expand dropdown"
msgstr ""
+msgid "Expand milestones"
+msgstr ""
+
msgid "Expand sidebar"
msgstr ""
@@ -9114,6 +9341,9 @@ msgstr ""
msgid "Expiration date"
msgstr ""
+msgid "Expiration not enforced"
+msgstr ""
+
msgid "Expiration policy for the Container Registry is a perfect solution for keeping the Registry space down while still enjoying the full power of GitLab CI/CD."
msgstr ""
@@ -9234,6 +9464,9 @@ msgstr ""
msgid "Failed Jobs"
msgstr ""
+msgid "Failed on"
+msgstr ""
+
msgid "Failed to add a Zoom meeting"
msgstr ""
@@ -9294,13 +9527,19 @@ msgstr ""
msgid "Failed to install."
msgstr ""
+msgid "Failed to load assignees. Please try again."
+msgstr ""
+
+msgid "Failed to load authors. Please try again."
+msgstr ""
+
msgid "Failed to load emoji list."
msgstr ""
msgid "Failed to load error details from Sentry."
msgstr ""
-msgid "Failed to load errors from Sentry. Error message: %{errorMessage}"
+msgid "Failed to load errors from Sentry."
msgstr ""
msgid "Failed to load group activity metrics. Please try again."
@@ -9381,6 +9620,9 @@ msgstr ""
msgid "Failed to set due date because the date format is invalid."
msgstr ""
+msgid "Failed to set iteration on this issue. Please try again."
+msgstr ""
+
msgid "Failed to signing using smartcard authentication"
msgstr ""
@@ -9603,13 +9845,10 @@ msgstr ""
msgid "FeatureFlags|Target environments"
msgstr ""
-msgid "FeatureFlags|There are no active feature flags"
-msgstr ""
-
-msgid "FeatureFlags|There are no inactive feature flags"
+msgid "FeatureFlags|There was an error fetching the feature flags."
msgstr ""
-msgid "FeatureFlags|There was an error fetching the feature flags."
+msgid "FeatureFlags|There was an error retrieving user lists"
msgstr ""
msgid "FeatureFlags|Try again in a few moments or contact your support team."
@@ -9621,9 +9860,18 @@ msgstr ""
msgid "FeatureFlag|Delete strategy"
msgstr ""
+msgid "FeatureFlag|List"
+msgstr ""
+
msgid "FeatureFlag|Percentage"
msgstr ""
+msgid "FeatureFlag|Select a user list"
+msgstr ""
+
+msgid "FeatureFlag|There are no configured user lists"
+msgstr ""
+
msgid "FeatureFlag|Type"
msgstr ""
@@ -9780,6 +10028,12 @@ msgstr ""
msgid "Filter..."
msgstr ""
+msgid "Find File"
+msgstr ""
+
+msgid "Find bugs in your code with coverage-guided fuzzing"
+msgstr ""
+
msgid "Find by path"
msgstr ""
@@ -10044,6 +10298,9 @@ msgstr ""
msgid "Geo allows you to replicate your GitLab instance to other geographical locations."
msgstr ""
+msgid "Geo nodes are paused using a command run on the node"
+msgstr ""
+
msgid "GeoNodeStatusEvent|%{timeAgoStr} (%{pendingEvents} events)"
msgstr ""
@@ -10128,6 +10385,9 @@ msgstr ""
msgid "GeoNodes|Not checksummed"
msgstr ""
+msgid "GeoNodes|Package files"
+msgstr ""
+
msgid "GeoNodes|Pausing replication stops the sync process. Are you sure?"
msgstr ""
@@ -10146,6 +10406,9 @@ msgstr ""
msgid "GeoNodes|Replication slots"
msgstr ""
+msgid "GeoNodes|Replication status"
+msgstr ""
+
msgid "GeoNodes|Repositories"
msgstr ""
@@ -10356,6 +10619,9 @@ msgstr ""
msgid "Geo|Synchronization failed - %{error}"
msgstr ""
+msgid "Geo|Synchronization of %{itemTitle} is disabled."
+msgstr ""
+
msgid "Geo|The database is currently %{db_lag} behind the primary node."
msgstr ""
@@ -10668,6 +10934,9 @@ msgstr ""
msgid "Go to %{link_to_google_takeout}."
msgstr ""
+msgid "Go to <strong>Issues</strong> > <strong>Boards</strong> to access your personalized learning issue board."
+msgstr ""
+
msgid "Go to Pipelines"
msgstr ""
@@ -10767,9 +11036,6 @@ msgstr ""
msgid "Go to your snippets"
msgstr ""
-msgid "Golden Tanuki"
-msgstr ""
-
msgid "Google Cloud Platform"
msgstr ""
@@ -10791,6 +11057,9 @@ msgstr ""
msgid "Grafana URL"
msgstr ""
+msgid "Grafana response contains invalid json"
+msgstr ""
+
msgid "GrafanaIntegration|API Token"
msgstr ""
@@ -10839,9 +11108,6 @@ msgstr ""
msgid "Group %{group_name} was successfully created."
msgstr ""
-msgid "Group '%{group_name}' is being imported."
-msgstr ""
-
msgid "Group Audit Events"
msgstr ""
@@ -10878,9 +11144,6 @@ msgstr ""
msgid "Group by:"
msgstr ""
-msgid "Group could not be imported: %{errors}"
-msgstr ""
-
msgid "Group description"
msgstr ""
@@ -10908,9 +11171,15 @@ msgstr ""
msgid "Group has not been marked for deletion"
msgstr ""
+msgid "Group import could not be scheduled"
+msgstr ""
+
msgid "Group info:"
msgstr ""
+msgid "Group is required when cluster_type is :group"
+msgstr ""
+
msgid "Group maintainers can register group runners in the %{link}"
msgstr ""
@@ -10974,6 +11243,24 @@ msgstr ""
msgid "GroupActivyMetrics|Recent activity (last 90 days)"
msgstr ""
+msgid "GroupImport|Failed to import group."
+msgstr ""
+
+msgid "GroupImport|Group '%{group_name}' is being imported."
+msgstr ""
+
+msgid "GroupImport|Group could not be imported: %{errors}"
+msgstr ""
+
+msgid "GroupImport|Please wait while we import the group for you. Refresh at will."
+msgstr ""
+
+msgid "GroupImport|The group was successfully imported."
+msgstr ""
+
+msgid "GroupImport|Unable to process group import file"
+msgstr ""
+
msgid "GroupRoadmap|%{dateWord} – No end date"
msgstr ""
@@ -11241,6 +11528,9 @@ msgstr ""
msgid "Groups (%{groups})"
msgstr ""
+msgid "Groups and projects"
+msgstr ""
+
msgid "Groups can also be nested by creating %{subgroup_docs_link_start}subgroups%{subgroup_docs_link_end}."
msgstr ""
@@ -11286,6 +11576,33 @@ msgstr ""
msgid "GroupsEmptyState|You can manage your group member’s permissions and access to each project in the group."
msgstr ""
+msgid "GroupsNew|Contact an administrator to enable options for importing your group."
+msgstr ""
+
+msgid "GroupsNew|Create"
+msgstr ""
+
+msgid "GroupsNew|Create group"
+msgstr ""
+
+msgid "GroupsNew|GitLab group export"
+msgstr ""
+
+msgid "GroupsNew|Import"
+msgstr ""
+
+msgid "GroupsNew|Import group"
+msgstr ""
+
+msgid "GroupsNew|My Awesome Group"
+msgstr ""
+
+msgid "GroupsNew|No import options available"
+msgstr ""
+
+msgid "GroupsNew|To copy a GitLab group between installations, navigate to the group settings page for the original installation, generate an export file, and upload it here."
+msgstr ""
+
msgid "GroupsTree|Are you sure you want to leave the \"%{fullName}\" group?"
msgstr ""
@@ -11388,6 +11705,9 @@ msgstr ""
msgid "Helps reduce request volume for protected paths"
msgstr ""
+msgid "Here are all your projects in your group, including the one you just created. To start, let’s take a look at your personalized learning project which will help you learn about GitLab at your own pace."
+msgstr ""
+
msgid "Here you will find recent merge request activity"
msgstr ""
@@ -11402,6 +11722,9 @@ msgid_plural "Hide charts"
msgstr[0] ""
msgstr[1] ""
+msgid "Hide details"
+msgstr ""
+
msgid "Hide file browser"
msgstr ""
@@ -11411,6 +11734,9 @@ msgstr ""
msgid "Hide host keys manual input"
msgstr ""
+msgid "Hide list"
+msgstr ""
+
msgid "Hide marketing-related entries from help"
msgstr ""
@@ -11431,9 +11757,6 @@ msgstr[1] ""
msgid "Hide values"
msgstr ""
-msgid "Hiding all labels"
-msgstr ""
-
msgid "High or unknown vulnerabilities present"
msgstr ""
@@ -11518,6 +11841,9 @@ msgstr ""
msgid "ID:"
msgstr ""
+msgid "IDE"
+msgstr ""
+
msgid "IDE|Allow live previews of JavaScript projects in the Web IDE using CodeSandbox Live Preview."
msgstr ""
@@ -11608,10 +11934,10 @@ msgstr ""
msgid "If enabled"
msgstr ""
-msgid "If enabled, access to projects will be validated on an external service using their classification label."
+msgid "If enabled, GitLab will handle Object Storage replication using Geo. %{linkStart}More information%{linkEnd}"
msgstr ""
-msgid "If enabled, and if object storage is enabled, GitLab will handle Object Storage replication using Geo"
+msgid "If enabled, access to projects will be validated on an external service using their classification label."
msgstr ""
msgid "If there is no previous license or if the previous license has expired, some GitLab functionality will be blocked until a new, valid license is uploaded."
@@ -11973,6 +12299,9 @@ msgid_plural "Instances"
msgstr[0] ""
msgstr[1] ""
+msgid "Instance Configuration"
+msgstr ""
+
msgid "Instance Statistics visibility"
msgstr ""
@@ -11994,9 +12323,6 @@ msgstr ""
msgid "Integrations"
msgstr ""
-msgid "Integrations allow you to integrate GitLab with other applications"
-msgstr ""
-
msgid "Integrations|All details"
msgstr ""
@@ -12108,6 +12434,9 @@ msgstr ""
msgid "Invalid query"
msgstr ""
+msgid "Invalid repository bundle for snippet with id %{snippet_id}"
+msgstr ""
+
msgid "Invalid repository path"
msgstr ""
@@ -12126,6 +12455,9 @@ msgstr ""
msgid "Invalid two-factor code."
msgstr ""
+msgid "Invalid yaml"
+msgstr ""
+
msgid "Invitation"
msgstr ""
@@ -12345,15 +12677,24 @@ msgstr ""
msgid "It seems like the Dependency Scanning job ran successfully, but no dependencies have been detected in your project."
msgstr ""
+msgid "It seems that there is currently no available data for code coverage"
+msgstr ""
+
msgid "It's you"
msgstr ""
+msgid "Iteration"
+msgstr ""
+
msgid "Iteration changed to"
msgstr ""
msgid "Iteration removed"
msgstr ""
+msgid "Iteration updated"
+msgstr ""
+
msgid "Iterations"
msgstr ""
@@ -12384,12 +12725,18 @@ msgstr ""
msgid "January"
msgstr ""
+msgid "Jira Issues"
+msgstr ""
+
msgid "Jira import is already running."
msgstr ""
msgid "Jira integration not configured."
msgstr ""
+msgid "Jira project key is not configured"
+msgstr ""
+
msgid "Jira project: %{importProject}"
msgstr ""
@@ -12570,6 +12917,9 @@ msgstr ""
msgid "Keep divergent refs"
msgstr ""
+msgid "Kerberos access denied"
+msgstr ""
+
msgid "Key"
msgstr ""
@@ -12579,10 +12929,13 @@ msgstr ""
msgid "Key: %{key}"
msgstr ""
-msgid "Keyboard Shortcuts"
+msgid "Keyboard shortcuts"
msgstr ""
-msgid "Keyboard shortcuts"
+msgid "Keys"
+msgstr ""
+
+msgid "Ki"
msgstr ""
msgid "Kubernetes"
@@ -12624,6 +12977,9 @@ msgstr ""
msgid "LDAP"
msgstr ""
+msgid "LDAP Synchronization"
+msgstr ""
+
msgid "LDAP settings"
msgstr ""
@@ -12848,12 +13204,18 @@ msgstr ""
msgid "Learn more about custom project templates"
msgstr ""
+msgid "Learn more about deploying to AWS"
+msgstr ""
+
msgid "Learn more about deploying to a cluster"
msgstr ""
msgid "Learn more about group-level project templates"
msgstr ""
+msgid "Learn more about job dependencies"
+msgstr ""
+
msgid "Learn more about signing commits"
msgstr ""
@@ -12905,9 +13267,21 @@ msgstr ""
msgid "License History"
msgstr ""
+msgid "License ID:"
+msgstr ""
+
+msgid "License URL"
+msgstr ""
+
msgid "License-Check"
msgstr ""
+msgid "LicenseCompliance|%{docLinkStart}License Approvals%{docLinkEnd} are active"
+msgstr ""
+
+msgid "LicenseCompliance|%{docLinkStart}License Approvals%{docLinkEnd} are inactive"
+msgstr ""
+
msgid "LicenseCompliance|Add a license"
msgstr ""
@@ -12929,9 +13303,15 @@ msgstr ""
msgid "LicenseCompliance|Deny"
msgstr ""
+msgid "LicenseCompliance|Learn more about %{linkStart}License Approvals%{linkEnd}"
+msgstr ""
+
msgid "LicenseCompliance|License"
msgstr ""
+msgid "LicenseCompliance|License Approvals"
+msgstr ""
+
msgid "LicenseCompliance|License Compliance detected %d license and policy violation for the source branch only; approval required"
msgid_plural "LicenseCompliance|License Compliance detected %d licenses and policy violations for the source branch only; approval required"
msgstr[0] ""
@@ -13152,6 +13532,9 @@ msgstr ""
msgid "List your Bitbucket Server repositories"
msgstr ""
+msgid "Lists"
+msgstr ""
+
msgid "Live preview"
msgstr ""
@@ -13335,6 +13718,9 @@ msgstr ""
msgid "Manage project labels"
msgstr ""
+msgid "Manage storage usage"
+msgstr ""
+
msgid "Manage two-factor authentication"
msgstr ""
@@ -13350,6 +13736,9 @@ msgstr ""
msgid "Manifest file import"
msgstr ""
+msgid "Manifest import"
+msgstr ""
+
msgid "Manual job"
msgstr ""
@@ -13497,6 +13886,12 @@ msgstr ""
msgid "Maximum field length"
msgstr ""
+msgid "Maximum file size is 2MB. Please select a smaller file."
+msgstr ""
+
+msgid "Maximum import size (MB)"
+msgstr ""
+
msgid "Maximum job timeout"
msgstr ""
@@ -13536,6 +13931,9 @@ msgstr ""
msgid "Maximum size of Elasticsearch bulk indexing requests."
msgstr ""
+msgid "Maximum size of import files."
+msgstr ""
+
msgid "Maximum size of individual attachments in comments."
msgstr ""
@@ -13587,6 +13985,9 @@ msgstr ""
msgid "Merge (when the pipeline succeeds)"
msgstr ""
+msgid "Merge Conflicts"
+msgstr ""
+
msgid "Merge Request"
msgstr ""
@@ -13713,6 +14114,9 @@ msgstr ""
msgid "MergeRequests|Squash task canceled: another squash is already in progress."
msgstr ""
+msgid "MergeRequests|This project does not allow squashing commits when merge requests are accepted."
+msgstr ""
+
msgid "MergeRequests|Thread stays resolved"
msgstr ""
@@ -13866,6 +14270,12 @@ msgstr ""
msgid "MetricsSettings|Add a button to the metrics dashboard linking directly to your existing external dashboard."
msgstr ""
+msgid "MetricsSettings|Choose whether to display dashboard metrics in UTC or the user's local timezone."
+msgstr ""
+
+msgid "MetricsSettings|Dashboard timezone"
+msgstr ""
+
msgid "MetricsSettings|External dashboard URL"
msgstr ""
@@ -13875,6 +14285,12 @@ msgstr ""
msgid "MetricsSettings|Metrics Dashboard"
msgstr ""
+msgid "MetricsSettings|UTC (Coordinated Universal Time)"
+msgstr ""
+
+msgid "MetricsSettings|User's local timezone"
+msgstr ""
+
msgid "Metrics|Add metric"
msgstr ""
@@ -13890,6 +14306,9 @@ msgstr ""
msgid "Metrics|Create metric"
msgstr ""
+msgid "Metrics|Current"
+msgstr ""
+
msgid "Metrics|Delete metric"
msgstr ""
@@ -13940,6 +14359,9 @@ msgstr ""
msgid "Metrics|Max"
msgstr ""
+msgid "Metrics|Min"
+msgstr ""
+
msgid "Metrics|Must be a valid PromQL query."
msgstr ""
@@ -13955,6 +14377,9 @@ msgstr ""
msgid "Metrics|Refresh dashboard"
msgstr ""
+msgid "Metrics|Select a value"
+msgstr ""
+
msgid "Metrics|Star dashboard"
msgstr ""
@@ -13973,12 +14398,18 @@ msgstr ""
msgid "Metrics|There was an error getting annotations information."
msgstr ""
+msgid "Metrics|There was an error getting dashboard validation warnings information."
+msgstr ""
+
msgid "Metrics|There was an error getting deployment information."
msgstr ""
msgid "Metrics|There was an error getting environments information."
msgstr ""
+msgid "Metrics|There was an error getting options for variable \"%{name}\"."
+msgstr ""
+
msgid "Metrics|There was an error trying to validate your query"
msgstr ""
@@ -14021,6 +14452,9 @@ msgstr ""
msgid "Metrics|You're about to permanently delete this metric. This cannot be undone."
msgstr ""
+msgid "Metrics|Your dashboard schema is invalid. Edit the dashboard to correct the YAML schema."
+msgstr ""
+
msgid "Metrics|e.g. HTTP requests"
msgstr ""
@@ -14036,12 +14470,18 @@ msgstr ""
msgid "Metrics|e.g. req/sec"
msgstr ""
+msgid "Mi"
+msgstr ""
+
msgid "Microsoft Azure"
msgstr ""
msgid "Middleman project with Static Site Editor support"
msgstr ""
+msgid "Migrate your data from an external source like GitHub, Bitbucket, or another instance of GitLab."
+msgstr ""
+
msgid "Migrated %{success_count}/%{total_count} files."
msgstr ""
@@ -14176,6 +14616,9 @@ msgstr ""
msgid "Minimum capacity to be available before we schedule more mirrors preemptively."
msgstr ""
+msgid "Minimum interval in days"
+msgstr ""
+
msgid "Minimum length is %{minimum_password_length} characters"
msgstr ""
@@ -14362,6 +14805,12 @@ msgstr ""
msgid "Multiple uploaders found: %{uploader_types}"
msgstr ""
+msgid "Must match with the %{codeStart}external_url%{codeEnd} in %{codeStart}/etc/gitlab/gitlab.rb%{codeEnd}."
+msgstr ""
+
+msgid "Must match with the %{codeStart}geo_node_name%{codeEnd} in %{codeStart}/etc/gitlab/gitlab.rb%{codeEnd}. %{linkStart}More information%{linkEnd}"
+msgstr ""
+
msgid "My Awesome Group"
msgstr ""
@@ -14476,10 +14925,16 @@ msgstr ""
msgid "New Application"
msgstr ""
+msgid "New Branch"
+msgstr ""
+
+msgid "New Deploy Key"
+msgstr ""
+
msgid "New Environment"
msgstr ""
-msgid "New Geo Node"
+msgid "New File"
msgstr ""
msgid "New Group"
@@ -14496,15 +14951,15 @@ msgid_plural "New Issues"
msgstr[0] ""
msgstr[1] ""
-msgid "New Iteration"
-msgstr ""
-
msgid "New Jira import"
msgstr ""
msgid "New Label"
msgstr ""
+msgid "New Merge Request"
+msgstr ""
+
msgid "New Milestone"
msgstr ""
@@ -14523,6 +14978,9 @@ msgstr ""
msgid "New Snippet"
msgstr ""
+msgid "New User"
+msgstr ""
+
msgid "New branch"
msgstr ""
@@ -14565,6 +15023,9 @@ msgstr ""
msgid "New issue title"
msgstr ""
+msgid "New iteration"
+msgstr ""
+
msgid "New iteration created"
msgstr ""
@@ -14742,6 +15203,12 @@ msgstr ""
msgid "No grouping"
msgstr ""
+msgid "No iteration"
+msgstr ""
+
+msgid "No iterations to show"
+msgstr ""
+
msgid "No job log"
msgstr ""
@@ -14784,6 +15251,9 @@ msgstr ""
msgid "No other labels with such name or description"
msgstr ""
+msgid "No panels matching properties %{opts}"
+msgstr ""
+
msgid "No parent group"
msgstr ""
@@ -14817,6 +15287,9 @@ msgstr ""
msgid "No schedules"
msgstr ""
+msgid "No stack trace for this error"
+msgstr ""
+
msgid "No starrers matched your search"
msgstr ""
@@ -14829,7 +15302,7 @@ msgstr ""
msgid "No test coverage"
msgstr ""
-msgid "No thanks, don't show this again"
+msgid "No thanks"
msgstr ""
msgid "No vulnerabilities present"
@@ -14844,9 +15317,6 @@ msgstr ""
msgid "No, directly import the existing email addresses and usernames."
msgstr ""
-msgid "No, not interested right now"
-msgstr ""
-
msgid "No. of commits"
msgstr ""
@@ -14892,9 +15362,6 @@ msgstr ""
msgid "Not found."
msgstr ""
-msgid "Not helpful"
-msgstr ""
-
msgid "Not now"
msgstr ""
@@ -15051,6 +15518,9 @@ msgstr ""
msgid "Notifications on"
msgstr ""
+msgid "Notify users by email when sign-in location is not recognized"
+msgstr ""
+
msgid "Nov"
msgstr ""
@@ -15117,9 +15587,6 @@ msgstr ""
msgid "Oh no!"
msgstr ""
-msgid "Ok let's go"
-msgstr ""
-
msgid "Oldest first"
msgstr ""
@@ -15132,16 +15599,49 @@ msgstr ""
msgid "On track"
msgstr ""
+msgid "OnDemandScans|Attached branch"
+msgstr ""
+
+msgid "OnDemandScans|Attached branch is where the scan job runs."
+msgstr ""
+
+msgid "OnDemandScans|Could not run the scan. Please try again."
+msgstr ""
+
msgid "OnDemandScans|Create new DAST scan"
msgstr ""
+msgid "OnDemandScans|DAST will scan the target URL and any discovered sub URLs."
+msgstr ""
+
+msgid "OnDemandScans|New on-demand DAST scan"
+msgstr ""
+
msgid "OnDemandScans|On-demand Scans"
msgstr ""
+msgid "OnDemandScans|On-demand scans run outside the DevOps cycle and find vulnerabilities in your projects. %{learnMoreLinkStart}Learn more%{learnMoreLinkEnd}"
+msgstr ""
+
+msgid "OnDemandScans|Only a passive scan can be performed on demand."
+msgstr ""
+
+msgid "OnDemandScans|Passive DAST Scan"
+msgstr ""
+
+msgid "OnDemandScans|Please enter a valid URL format, ex: http://www.example.com/home"
+msgstr ""
+
+msgid "OnDemandScans|Run this scan"
+msgstr ""
+
+msgid "OnDemandScans|Scan mode"
+msgstr ""
+
msgid "OnDemandScans|Schedule or run scans immediately against target sites. Currently available on-demand scan type: DAST. %{helpLinkStart}More information%{helpLinkEnd}"
msgstr ""
-msgid "Onboarding"
+msgid "OnDemandScans|Target URL"
msgstr ""
msgid "Once imported, repositories can be mirrored over SSH. Read more %{link_start}here%{link_end}."
@@ -15167,6 +15667,9 @@ msgstr ""
msgid "One or more of you personal access tokens were revoked"
msgstr ""
+msgid "One or more of your %{provider} projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
+msgstr ""
+
msgid "One or more of your Bitbucket projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
msgstr ""
@@ -15668,6 +16171,9 @@ msgstr ""
msgid "Parent epic is not present."
msgstr ""
+msgid "Parsing error for param :embed_json. %{message}"
+msgstr ""
+
msgid "Part of merge request changes"
msgstr ""
@@ -15680,6 +16186,9 @@ msgstr ""
msgid "Passed"
msgstr ""
+msgid "Passed on"
+msgstr ""
+
msgid "Password"
msgstr ""
@@ -15764,6 +16273,9 @@ msgstr ""
msgid "Perform common operations on GitLab project"
msgstr ""
+msgid "Performance and resource management"
+msgstr ""
+
msgid "Performance optimization"
msgstr ""
@@ -15971,7 +16483,7 @@ msgstr ""
msgid "Pipelines|Something went wrong while cleaning runners cache."
msgstr ""
-msgid "Pipelines|There are currently no %{scope} pipelines."
+msgid "Pipelines|There are currently no finished pipelines."
msgstr ""
msgid "Pipelines|There are currently no pipelines."
@@ -16082,6 +16594,9 @@ msgstr ""
msgid "Pipeline|Stop pipeline #%{pipelineId}?"
msgstr ""
+msgid "Pipeline|Tag name"
+msgstr ""
+
msgid "Pipeline|Trigger author"
msgstr ""
@@ -16163,12 +16678,18 @@ msgstr ""
msgid "Please check your email (%{email}) to verify that you own this address and unlock the power of CI/CD. Didn't receive it? %{resend_link}. Wrong email address? %{update_link}."
msgstr ""
+msgid "Please choose a file"
+msgstr ""
+
msgid "Please choose a group URL with no special characters."
msgstr ""
msgid "Please complete your profile with email address"
msgstr ""
+msgid "Please convert %{linkStart}them to Git%{linkEnd}, and go through the %{linkToImportFlow} again."
+msgstr ""
+
msgid "Please convert them to %{link_to_git}, and go through the %{link_to_import_flow} again."
msgstr ""
@@ -16226,6 +16747,9 @@ msgstr ""
msgid "Please provide a valid email address."
msgstr ""
+msgid "Please provide attributes to update"
+msgstr ""
+
msgid "Please refer to <a href=\"%{docs_url}\">%{docs_url}</a>"
msgstr ""
@@ -16961,6 +17485,9 @@ msgstr ""
msgid "Project has too many %{label_for_message} to search"
msgstr ""
+msgid "Project is required when cluster_type is :project"
+msgstr ""
+
msgid "Project members"
msgstr ""
@@ -17078,7 +17605,40 @@ msgstr ""
msgid "ProjectService|Comment"
msgstr ""
-msgid "ProjectService|Comment will be posted on each event"
+msgid "ProjectService|Event will be triggered by a push to the repository"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a commit is created/updated"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a confidential issue is created/updated/closed"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a deployment finishes"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a merge request is created/updated/merged"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a new tag is pushed to the repository"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a new, unique alert is recorded"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a pipeline status changes"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a wiki page is created/updated"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when an issue is created/updated/closed"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when someone adds a comment"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when someone adds a comment on a confidential issue"
msgstr ""
msgid "ProjectService|Perform common operations on GitLab project: %{project_name}"
@@ -17129,6 +17689,9 @@ msgstr ""
msgid "ProjectSettings|Enable 'Delete source branch' option by default"
msgstr ""
+msgid "ProjectSettings|Enable merge trains and pipelines for merged results"
+msgstr ""
+
msgid "ProjectSettings|Every merge creates a merge commit"
msgstr ""
@@ -17159,7 +17722,7 @@ msgstr ""
msgid "ProjectSettings|Forks"
msgstr ""
-msgid "ProjectSettings|Git Large File Storage"
+msgid "ProjectSettings|Git Large File Storage (LFS)"
msgstr ""
msgid "ProjectSettings|Internal"
@@ -17195,9 +17758,6 @@ msgstr ""
msgid "ProjectSettings|Merge options"
msgstr ""
-msgid "ProjectSettings|Merge pipelines will try to validate the post-merge result prior to merging"
-msgstr ""
-
msgid "ProjectSettings|Merge requests"
msgstr ""
@@ -17225,6 +17785,9 @@ msgstr ""
msgid "ProjectSettings|Pipelines"
msgstr ""
+msgid "ProjectSettings|Pipelines for merge requests must be enabled in the CI/CD configuration file, or pipelines could be unresolvable or dropped"
+msgstr ""
+
msgid "ProjectSettings|Pipelines must succeed"
msgstr ""
@@ -17252,6 +17815,9 @@ msgstr ""
msgid "ProjectSettings|Show link to create/view merge request when pushing from the command line"
msgstr ""
+msgid "ProjectSettings|Skipped pipelines are considered successful"
+msgstr ""
+
msgid "ProjectSettings|Snippets"
msgstr ""
@@ -17267,6 +17833,9 @@ msgstr ""
msgid "ProjectSettings|These checks must pass before merge requests can be merged"
msgstr ""
+msgid "ProjectSettings|This introduces the risk of merging changes that will not pass the pipeline."
+msgstr ""
+
msgid "ProjectSettings|This setting is applied on the server level and can be overridden by an admin."
msgstr ""
@@ -17444,15 +18013,27 @@ msgstr ""
msgid "ProjectsNew|Blank project"
msgstr ""
+msgid "ProjectsNew|Connect your external repository to GitLab CI/CD."
+msgstr ""
+
msgid "ProjectsNew|Contact an administrator to enable options for importing your project."
msgstr ""
msgid "ProjectsNew|Create"
msgstr ""
+msgid "ProjectsNew|Create a blank project to house your files, plan your work, and collaborate on code, among other things."
+msgstr ""
+
+msgid "ProjectsNew|Create blank project"
+msgstr ""
+
msgid "ProjectsNew|Create from template"
msgstr ""
+msgid "ProjectsNew|Create new project"
+msgstr ""
+
msgid "ProjectsNew|Creating project & repository."
msgstr ""
@@ -17477,6 +18058,9 @@ msgstr ""
msgid "ProjectsNew|Project description %{tag_start}(optional)%{tag_end}"
msgstr ""
+msgid "ProjectsNew|Run CI/CD for external repository"
+msgstr ""
+
msgid "ProjectsNew|Template"
msgstr ""
@@ -17738,6 +18322,9 @@ msgstr ""
msgid "Protected Branch"
msgstr ""
+msgid "Protected Branches"
+msgstr ""
+
msgid "Protected Environment"
msgstr ""
@@ -17750,6 +18337,9 @@ msgstr ""
msgid "Protected Tag"
msgstr ""
+msgid "Protected Tags"
+msgstr ""
+
msgid "Protected branches"
msgstr ""
@@ -17849,6 +18439,9 @@ msgstr ""
msgid "Provider"
msgstr ""
+msgid "Proxy support for this API is not available currently"
+msgstr ""
+
msgid "Pseudonymizer data collection"
msgstr ""
@@ -17873,6 +18466,9 @@ msgstr ""
msgid "Publish to status page"
msgstr ""
+msgid "Published on status page"
+msgstr ""
+
msgid "Publishes this issue to the associated status page."
msgstr ""
@@ -18047,6 +18643,9 @@ msgstr ""
msgid "Recipe"
msgstr ""
+msgid "Reconfigure"
+msgstr ""
+
msgid "Recover hidden stage"
msgstr ""
@@ -18133,6 +18732,9 @@ msgstr ""
msgid "Registration|Your profile"
msgstr ""
+msgid "Regulate approvals by authors/committers, based on compliance frameworks. Can be changed only at the instance level."
+msgstr ""
+
msgid "Rejected (closed)"
msgstr ""
@@ -18183,15 +18785,24 @@ msgstr ""
msgid "ReleaseAssetLinkType|Image"
msgstr ""
+msgid "ReleaseAssetLinkType|Images"
+msgstr ""
+
msgid "ReleaseAssetLinkType|Other"
msgstr ""
msgid "ReleaseAssetLinkType|Package"
msgstr ""
+msgid "ReleaseAssetLinkType|Packages"
+msgstr ""
+
msgid "ReleaseAssetLinkType|Runbook"
msgstr ""
+msgid "ReleaseAssetLinkType|Runbooks"
+msgstr ""
+
msgid "Releases"
msgstr ""
@@ -18204,6 +18815,9 @@ msgstr ""
msgid "Releases documentation"
msgstr ""
+msgid "Releases|New Release"
+msgstr ""
+
msgid "Release|Something went wrong while getting the release details"
msgstr ""
@@ -18276,6 +18890,9 @@ msgstr ""
msgid "Remove fork relationship"
msgstr ""
+msgid "Remove from batch"
+msgstr ""
+
msgid "Remove from board"
msgstr ""
@@ -18465,6 +19082,12 @@ msgstr ""
msgid "Replication"
msgstr ""
+msgid "Replication enabled"
+msgstr ""
+
+msgid "Replication paused"
+msgstr ""
+
msgid "Reply by email"
msgstr ""
@@ -18492,15 +19115,15 @@ msgstr ""
msgid "Reported %{timeAgo} by %{reportedBy}"
msgstr ""
-msgid "Reported Resource Changes: %{addNum} to add, %{changeNum} to change, %{deleteNum} to delete"
-msgstr ""
-
msgid "Reporter"
msgstr ""
msgid "Reporting"
msgstr ""
+msgid "Reports"
+msgstr ""
+
msgid "Reports|%{combinedString} and %{resolvedString}"
msgstr ""
@@ -18539,6 +19162,9 @@ msgstr ""
msgid "Reports|Failure"
msgstr ""
+msgid "Reports|Identifier"
+msgstr ""
+
msgid "Reports|Metrics reports are loading"
msgstr ""
@@ -18551,6 +19177,9 @@ msgstr ""
msgid "Reports|Metrics reports failed loading results"
msgstr ""
+msgid "Reports|Scanner"
+msgstr ""
+
msgid "Reports|Severity"
msgstr ""
@@ -18599,15 +19228,33 @@ msgstr ""
msgid "Repository cleanup has started. You will receive an email once the cleanup operation is complete."
msgstr ""
+msgid "Repository files count over the limit"
+msgstr ""
+
+msgid "Repository has an invalid default branch name."
+msgstr ""
+
+msgid "Repository has more than one branch."
+msgstr ""
+
msgid "Repository has no locks."
msgstr ""
+msgid "Repository has tags."
+msgstr ""
+
msgid "Repository maintenance"
msgstr ""
msgid "Repository mirroring"
msgstr ""
+msgid "Repository must contain at least 1 file."
+msgstr ""
+
+msgid "Repository size is above the limit."
+msgstr ""
+
msgid "Repository static objects"
msgstr ""
@@ -18617,7 +19264,7 @@ msgstr ""
msgid "Repository sync capacity"
msgstr ""
-msgid "Repository: %{counter_repositories} / Wikis: %{counter_wikis} / Build Artifacts: %{counter_build_artifacts} / LFS: %{counter_lfs_objects}"
+msgid "Repository: %{counter_repositories} / Wikis: %{counter_wikis} / Build Artifacts: %{counter_build_artifacts} / LFS: %{counter_lfs_objects} / Snippets: %{counter_snippets}"
msgstr ""
msgid "RepositorySettingsAccessLevel|Select"
@@ -18629,6 +19276,9 @@ msgstr ""
msgid "Request Headers"
msgstr ""
+msgid "Request details"
+msgstr ""
+
msgid "Request parameter %{param} is missing."
msgstr ""
@@ -18735,6 +19385,9 @@ msgstr ""
msgid "Reset template"
msgstr ""
+msgid "Reset to project defaults"
+msgstr ""
+
msgid "Resetting the authorization key for this project will require updating the authorization key in every alert source it is enabled in."
msgstr ""
@@ -18831,9 +19484,6 @@ msgstr ""
msgid "Resume"
msgstr ""
-msgid "Resume replication"
-msgstr ""
-
msgid "Resync"
msgstr ""
@@ -19456,6 +20106,9 @@ msgstr ""
msgid "SecurityReports|False positive"
msgstr ""
+msgid "SecurityReports|Group Security Dashboard"
+msgstr ""
+
msgid "SecurityReports|Hide dismissed"
msgstr ""
@@ -19510,10 +20163,13 @@ msgstr ""
msgid "SecurityReports|Remove project from dashboard"
msgstr ""
-msgid "SecurityReports|Report type"
+msgid "SecurityReports|Return to dashboard"
msgstr ""
-msgid "SecurityReports|Return to dashboard"
+msgid "SecurityReports|Scan details"
+msgstr ""
+
+msgid "SecurityReports|Scanner type"
msgstr ""
msgid "SecurityReports|Security Dashboard"
@@ -19570,6 +20226,9 @@ msgstr ""
msgid "SecurityReports|There was an error while generating the report."
msgstr ""
+msgid "SecurityReports|Unable to add %{invalidProjectsMessage}"
+msgstr ""
+
msgid "SecurityReports|Unable to add %{invalidProjects}"
msgstr ""
@@ -19684,6 +20343,9 @@ msgstr ""
msgid "Select due date"
msgstr ""
+msgid "Select file"
+msgstr ""
+
msgid "Select group or project"
msgstr ""
@@ -19747,9 +20409,6 @@ msgstr ""
msgid "Select the branch you want to set as the default for this project. All merge requests and commits will automatically be made against this branch unless you specify a different one."
msgstr ""
-msgid "Select the configured storage available for new repositories to be placed on."
-msgstr ""
-
msgid "Select the custom project template source group."
msgstr ""
@@ -19942,12 +20601,6 @@ msgstr ""
msgid "Service Desk is enabled but not yet active"
msgstr ""
-msgid "Service Desk is off"
-msgstr ""
-
-msgid "Service Desk is on"
-msgstr ""
-
msgid "Service Templates"
msgstr ""
@@ -20035,6 +20688,15 @@ msgstr ""
msgid "Set the milestone to %{milestone_reference}."
msgstr ""
+msgid "Set the number of concurrent requests this secondary node will make to the primary node while backfilling."
+msgstr ""
+
+msgid "Set the synchronization and verification capacity for the secondary node."
+msgstr ""
+
+msgid "Set the timeout in seconds to send a secondary node status to the primary and IPs allowed for the secondary nodes."
+msgstr ""
+
msgid "Set time estimate"
msgstr ""
@@ -20074,6 +20736,9 @@ msgstr ""
msgid "Set weight to %{weight}."
msgstr ""
+msgid "Set what should be replicated by choosing specific projects or groups by the secondary node."
+msgstr ""
+
msgid "SetPasswordToCloneLink|set a password"
msgstr ""
@@ -20200,6 +20865,9 @@ msgstr ""
msgid "Show complete raw log"
msgstr ""
+msgid "Show details"
+msgstr ""
+
msgid "Show file browser"
msgstr ""
@@ -20209,10 +20877,13 @@ msgstr ""
msgid "Show latest version"
msgstr ""
+msgid "Show list"
+msgstr ""
+
msgid "Show me everything"
msgstr ""
-msgid "Show me how"
+msgid "Show me how to add a pipeline"
msgstr ""
msgid "Show me more advanced stuff"
@@ -20261,9 +20932,6 @@ msgstr ""
msgid "Showing all issues"
msgstr ""
-msgid "Showing all labels"
-msgstr ""
-
msgid "Showing last %{size} of log -"
msgstr ""
@@ -20375,9 +21043,6 @@ msgstr ""
msgid "Skip outdated deployment jobs"
msgstr ""
-msgid "Skip this for now"
-msgstr ""
-
msgid "Skipped"
msgstr ""
@@ -20522,6 +21187,9 @@ msgstr ""
msgid "Something went wrong while adding your award. Please try again."
msgstr ""
+msgid "Something went wrong while applying the batch of suggestions. Please try again."
+msgstr ""
+
msgid "Something went wrong while applying the suggestion. Please try again."
msgstr ""
@@ -20624,10 +21292,13 @@ msgstr ""
msgid "Something went wrong while updating a requirement."
msgstr ""
+msgid "Something went wrong while updating assignees"
+msgstr ""
+
msgid "Something went wrong while updating your list settings"
msgstr ""
-msgid "Something went wrong with your automatic subscription renewal"
+msgid "Something went wrong with your automatic subscription renewal."
msgstr ""
msgid "Something went wrong, unable to add %{project} to dashboard"
@@ -20645,6 +21316,9 @@ msgstr ""
msgid "Something went wrong, unable to search projects"
msgstr ""
+msgid "Something went wrong."
+msgstr ""
+
msgid "Something went wrong. Please try again."
msgstr ""
@@ -20828,6 +21502,9 @@ msgstr ""
msgid "Source code"
msgstr ""
+msgid "Source code (%{fileExtension})"
+msgstr ""
+
msgid "Source is not available"
msgstr ""
@@ -20912,6 +21589,9 @@ msgstr ""
msgid "Stack trace"
msgstr ""
+msgid "Stacktrace snippet"
+msgstr ""
+
msgid "Stage"
msgstr ""
@@ -21017,6 +21697,9 @@ msgstr ""
msgid "Start thread & reopen %{noteable_name}"
msgstr ""
+msgid "Start using Directed Acyclic Graphs (DAG)"
+msgstr ""
+
msgid "Start your Free Gold Trial"
msgstr ""
@@ -21479,6 +22162,21 @@ msgstr ""
msgid "SuggestedColors|Very pale orange"
msgstr ""
+msgid "Suggestion is not applicable as the suggestion was not found."
+msgstr ""
+
+msgid "Suggestions are not applicable as one or more suggestions were not found."
+msgstr ""
+
+msgid "Suggestions are not applicable as their lines cannot overlap."
+msgstr ""
+
+msgid "Suggestions must all be on the same branch."
+msgstr ""
+
+msgid "Suggestions that change line count can't be added to batches, yet."
+msgstr ""
+
msgid "Suggestions:"
msgstr ""
@@ -21521,9 +22219,6 @@ msgstr ""
msgid "Synchronization disabled"
msgstr ""
-msgid "Synchronization of container repositories is disabled."
-msgstr ""
-
msgid "System"
msgstr ""
@@ -21704,6 +22399,34 @@ msgstr ""
msgid "Terms of Service and Privacy Policy"
msgstr ""
+msgid "Terraform|%{number} Terraform report failed to generate"
+msgid_plural "Terraform|%{number} Terraform reports failed to generate"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Terraform|%{number} Terraform report was generated in your pipelines"
+msgid_plural "Terraform|%{number} Terraform reports were generated in your pipelines"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Terraform|A Terraform report failed to generate."
+msgstr ""
+
+msgid "Terraform|A Terraform report was generated in your pipelines."
+msgstr ""
+
+msgid "Terraform|Generating the report caused an error."
+msgstr ""
+
+msgid "Terraform|Reported Resource Changes: %{addNum} to add, %{changeNum} to change, %{deleteNum} to delete"
+msgstr ""
+
+msgid "Terraform|The Terraform report %{name} failed to generate."
+msgstr ""
+
+msgid "Terraform|The Terraform report %{name} was generated in your pipelines."
+msgstr ""
+
msgid "Test"
msgstr ""
@@ -21730,7 +22453,7 @@ msgstr ""
msgid "TestHooks|Ensure the project has CI pipelines."
msgstr ""
-msgid "TestHooks|Ensure the project has at least one commit."
+msgid "TestHooks|Ensure the project has deployments."
msgstr ""
msgid "TestHooks|Ensure the project has issues."
@@ -21831,7 +22554,7 @@ msgstr ""
msgid "The Prometheus server responded with \"bad request\". Please check your queries are correct and are supported in your Prometheus version. %{documentationLink}"
msgstr ""
-msgid "The URL defined on the primary node that secondary nodes should use to contact it. Defaults to URL"
+msgid "The URL defined on the primary node that secondary nodes should use to contact it."
msgstr ""
msgid "The URL to use for connecting to Elasticsearch. Use a comma-separated list to support clustering (e.g., \"http://localhost:9200, http://localhost:9201\")."
@@ -21840,6 +22563,9 @@ msgstr ""
msgid "The X509 Certificate to use when mutual TLS is required to communicate with the external authorization service. If left blank, the server certificate is still validated when accessing over HTTPS."
msgstr ""
+msgid "The above settings apply to all projects with the selected compliance framework(s)."
+msgstr ""
+
msgid "The amount of seconds after which a request to get a secondary node status will time out."
msgstr ""
@@ -22139,9 +22865,6 @@ msgstr ""
msgid "The total stage shows the time it takes between creating an issue and deploying the code to production. The data will be automatically added once you have completed the full idea to production cycle."
msgstr ""
-msgid "The unique identifier for the Geo node. Must match %{geoNodeName} if it is set in gitlab.rb, otherwise it must match %{externalUrl} with a trailing slash"
-msgstr ""
-
msgid "The update action will time out after %{number_of_minutes} minutes. For big repositories, use a clone/push combination."
msgstr ""
@@ -22166,9 +22889,6 @@ msgstr ""
msgid "The user you are trying to deactivate has been active in the past %{minimum_inactive_days} days and cannot be deactivated"
msgstr ""
-msgid "The user-facing URL of the Geo node"
-msgstr ""
-
msgid "The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6."
msgstr ""
@@ -22259,6 +22979,9 @@ msgstr ""
msgid "There was a problem fetching project branches."
msgstr ""
+msgid "There was a problem fetching project tags."
+msgstr ""
+
msgid "There was a problem fetching project users."
msgstr ""
@@ -22310,6 +23033,9 @@ msgstr ""
msgid "There was an error fetching the %{replicableType}"
msgstr ""
+msgid "There was an error fetching the Geo Settings"
+msgstr ""
+
msgid "There was an error fetching the Node's Groups"
msgstr ""
@@ -22430,6 +23156,12 @@ msgstr ""
msgid "This %{issuable} is locked. Only <strong>project members</strong> can comment."
msgstr ""
+msgid "This %{noteableTypeText} is %{confidentialLinkStart}confidential%{linkEnd} and %{lockedLinkStart}locked%{linkEnd}."
+msgstr ""
+
+msgid "This %{noteableTypeText} is locked."
+msgstr ""
+
msgid "This %{viewer} could not be displayed because %{reason}. You can %{options} instead."
msgstr ""
@@ -22448,6 +23180,9 @@ msgstr ""
msgid "This action can lead to data loss. To prevent accidental actions we ask you to confirm your intention."
msgstr ""
+msgid "This also resolves all related threads"
+msgstr ""
+
msgid "This also resolves the discussion"
msgstr ""
@@ -22538,6 +23273,9 @@ msgstr ""
msgid "This epic does not exist or you don't have sufficient permission."
msgstr ""
+msgid "This feature is currently in beta. We invite you to %{linkStart}give feedback%{linkEnd}."
+msgstr ""
+
msgid "This feature requires local storage to be enabled"
msgstr ""
@@ -22571,7 +23309,7 @@ msgstr ""
msgid "This is a Work in Progress"
msgstr ""
-msgid "This is a confidential issue."
+msgid "This is a confidential %{noteableTypeText}."
msgstr ""
msgid "This is a delayed job to run in %{remainingTime}"
@@ -22598,9 +23336,6 @@ msgstr ""
msgid "This is your current session"
msgstr ""
-msgid "This issue is %{confidentialLinkStart}confidential%{linkEnd} and %{lockedLinkStart}locked%{linkEnd}."
-msgstr ""
-
msgid "This issue is confidential"
msgstr ""
@@ -22610,9 +23345,6 @@ msgstr ""
msgid "This issue is in a child epic of the filtered epic"
msgstr ""
-msgid "This issue is locked."
-msgstr ""
-
msgid "This job depends on other jobs with expired/erased artifacts: %{invalid_dependencies}"
msgstr ""
@@ -22712,6 +23444,9 @@ msgstr ""
msgid "This license has already expired."
msgstr ""
+msgid "This link points to external content"
+msgstr ""
+
msgid "This may expose confidential information as the selected fork is in another namespace that can have other members."
msgstr ""
@@ -22739,6 +23474,9 @@ msgstr ""
msgid "This page will be removed in a future release."
msgstr ""
+msgid "This pipeline does not use the %{codeStart}needs%{codeEnd} keyword and can't be represented as a directed acyclic graph."
+msgstr ""
+
msgid "This pipeline makes use of a predefined CI/CD configuration enabled by %{strongStart}Auto DevOps.%{strongEnd}"
msgstr ""
@@ -22967,6 +23705,9 @@ msgstr ""
msgid "Time from last commit to merge"
msgstr ""
+msgid "Time in seconds"
+msgstr ""
+
msgid "Time in seconds GitLab will wait for a response from the external service. When the service does not respond in time, access will be denied."
msgstr ""
@@ -23388,9 +24129,15 @@ msgstr ""
msgid "Total artifacts size: %{total_size}"
msgstr ""
+msgid "Total cores (CPUs)"
+msgstr ""
+
msgid "Total issues"
msgstr ""
+msgid "Total memory (GB)"
+msgstr ""
+
msgid "Total test time for all commits/merges"
msgstr ""
@@ -23592,6 +24339,9 @@ msgstr ""
msgid "U2F only works with HTTPS-enabled websites. Contact your administrator for more details."
msgstr ""
+msgid "UI Development Kit"
+msgstr ""
+
msgid "URL"
msgstr ""
@@ -23664,9 +24414,6 @@ msgstr ""
msgid "Unable to load the merge request widget. Try reloading the page."
msgstr ""
-msgid "Unable to process group import file"
-msgstr ""
-
msgid "Unable to resolve"
msgstr ""
@@ -23682,6 +24429,9 @@ msgstr ""
msgid "Unable to sign you in to the group with SAML due to \"%{reason}\""
msgstr ""
+msgid "Unable to suggest a path. Please refresh and try again."
+msgstr ""
+
msgid "Unable to update label prioritization at this time"
msgstr ""
@@ -23697,6 +24447,9 @@ msgstr ""
msgid "Unarchiving the project will restore people's ability to make changes to it. The repository can be committed to, and issues, comments, and other entities can be created. %{strong_start}Once active, this project shows up in the search and on the dashboard.%{strong_end}"
msgstr ""
+msgid "Unassign from commenting user"
+msgstr ""
+
msgid "Unblock"
msgstr ""
@@ -23772,6 +24525,9 @@ msgstr ""
msgid "Unreachable"
msgstr ""
+msgid "Unrecognized cluster type"
+msgstr ""
+
msgid "Unresolve"
msgstr ""
@@ -23832,6 +24588,9 @@ msgstr ""
msgid "Unsubscribes from this %{quick_action_target}."
msgstr ""
+msgid "Unsupported todo type passed. Supported todo types are: %{todo_types}"
+msgstr ""
+
msgid "Until"
msgstr ""
@@ -23859,6 +24618,9 @@ msgstr ""
msgid "Update approval rule"
msgstr ""
+msgid "Update approvers"
+msgstr ""
+
msgid "Update failed"
msgstr ""
@@ -23868,6 +24630,9 @@ msgstr ""
msgid "Update it"
msgstr ""
+msgid "Update iteration"
+msgstr ""
+
msgid "Update now"
msgstr ""
@@ -23913,7 +24678,7 @@ msgstr ""
msgid "Updated at"
msgstr ""
-msgid "Updated to"
+msgid "Updated to %{linkStart}chart v%{linkEnd}"
msgstr ""
msgid "Updating"
@@ -23943,6 +24708,9 @@ msgstr ""
msgid "Upload CSV file"
msgstr ""
+msgid "Upload License"
+msgstr ""
+
msgid "Upload New File"
msgstr ""
@@ -24024,16 +24792,19 @@ msgstr ""
msgid "UsageQuota|Pipelines"
msgstr ""
+msgid "UsageQuota|Purchase more storage"
+msgstr ""
+
msgid "UsageQuota|Repositories"
msgstr ""
msgid "UsageQuota|Repository"
msgstr ""
-msgid "UsageQuota|Storage"
+msgid "UsageQuota|Snippets"
msgstr ""
-msgid "UsageQuota|Storage usage:"
+msgid "UsageQuota|Storage"
msgstr ""
msgid "UsageQuota|This namespace has no projects which use shared runners"
@@ -24066,6 +24837,12 @@ msgstr ""
msgid "UsageQuota|Wikis"
msgstr ""
+msgid "UsageQuota|You used: %{usage} %{limit}"
+msgstr ""
+
+msgid "UsageQuota|out of %{formattedLimit} of your namespace storage"
+msgstr ""
+
msgid "Use %{code_start}::%{code_end} to create a %{link_start}scoped label set%{link_end} (eg. %{code_start}priority::1%{code_end})"
msgstr ""
@@ -24132,6 +24909,9 @@ msgstr ""
msgid "User IDs"
msgstr ""
+msgid "User Lists can only be created and modified with %{linkStart}the API%{linkEnd}"
+msgstr ""
+
msgid "User OAuth applications"
msgstr ""
@@ -24156,6 +24936,9 @@ msgstr ""
msgid "User key was successfully removed."
msgstr ""
+msgid "User list %{name} will be removed. Are you sure?"
+msgstr ""
+
msgid "User map"
msgstr ""
@@ -24177,148 +24960,10 @@ msgstr ""
msgid "User was successfully updated."
msgstr ""
-msgid "UserOnboardingTour|%{activeTour}/%{totalTours}"
-msgstr ""
-
-msgid "UserOnboardingTour|%{completed}/%{total} steps completed"
-msgstr ""
-
-msgid "UserOnboardingTour|%{emphasisStart}Well done!%{emphasisEnd}%{lineBreak}%{lineBreak}That's it for our guided tour, congratulations for making it all the way to the end!%{lineBreak}%{lineBreak}We hope this gave you a good overview of GitLab and how it can help you. We'll now show you how to create your own project and invite your colleagues."
-msgstr ""
-
-msgid "UserOnboardingTour|Adding other members to a project is done through Project Settings. Click on %{emphasisStart}Settings%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Alright, that's it for Commits. Let's take a look at the %{emphasisStart}Branches%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Awesome! Now click on %{emphasisStart}Members%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Click on one of the %{emphasisStart}Compare%{emphasisEnd} buttons to compare a branch to master."
-msgstr ""
-
-msgid "UserOnboardingTour|Click on one of the %{emphasisStart}pipeline IDs%{emphasisEnd} to see the details of a pipeline."
-msgstr ""
-
-msgid "UserOnboardingTour|Click to open the latest commit to see its details."
-msgstr ""
-
-msgid "UserOnboardingTour|Close 'Learn GitLab'"
-msgstr ""
-
-msgid "UserOnboardingTour|Commits are shown in chronological order and can be filtered by the commit message or by the branch."
+msgid "UserList|Delete %{name}?"
msgstr ""
-msgid "UserOnboardingTour|Create a project"
-msgstr ""
-
-msgid "UserOnboardingTour|Exit 'Learn GitLab'"
-msgstr ""
-
-msgid "UserOnboardingTour|Got it"
-msgstr ""
-
-msgid "UserOnboardingTour|Great job! %{clapHands} We hope the tour was helpful and that you learned how to use GitLab.%{lineBreak}%{lineBreak}We'd love to get your feedback on this tour.%{lineBreak}%{lineBreak}%{emphasisStart}How helpful would you say this guided tour was?%{emphasisEnd}%{lineBreak}%{lineBreak}"
-msgstr ""
-
-msgid "UserOnboardingTour|Guided GitLab Tour"
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can compare the changes of this branch to another one. Changes are divided by files so that it's easier to see what was changed where."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can create a project from scratch, start with a template or import a repository from other platforms. Whatever you choose, we'll guide you through the process.%{lineBreak}%{lineBreak}Fill in your new project information and click on %{emphasisStart}Create Project%{emphasisEnd} to progress to the next step."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can see the breakdown of the pipelines: its stages and jobs in each of the stages and their status.%{lineBreak}%{lineBreak}Our CI/CD pipelines are quite complex, most of our users have fewer and simpler pipelines."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can see the current members of the project (just you at the moment) and invite new members.%{lineBreak}%{lineBreak}You can invite multiple members at once (existing GitLab users or invite by email) and you can also set their roles and permissions.%{lineBreak}%{lineBreak}Add a few members and click on %{emphasisStart}Add to project%{emphasisEnd} to complete this step."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can see what changes were made with this commit, on what branch and if there's a related merge request. The status of the pipeline will also show up if CI/CD is set up.%{lineBreak}%{lineBreak}You can also comment on the lines of code that were changed and start a discussion with your colleagues!"
-msgstr ""
-
-msgid "UserOnboardingTour|Here's an overview of branches in the %{emphasisStart}%{projectName}%{emphasisEnd} project. They're split into Active and Stale.%{lineBreak}%{lineBreak}From here, you can create a new merge request from a branch, or compare the branch to any other branch in the project. By default, it will compare it to the master branch."
-msgstr ""
-
-msgid "UserOnboardingTour|Invite colleagues"
-msgstr ""
-
-msgid "UserOnboardingTour|Issues are great for communicating and keeping track of progress in GitLab. These are all issues that are open in the %{emphasisStart}%{projectName}%{emphasisEnd}.%{lineBreak}%{lineBreak}You can help us improve GitLab by contributing work to issues that are labeled <span class=\"badge color-label accept-mr-label\">Accepting merge requests</span>.%{lineBreak}%{lineBreak}This list can be filtered by labels, milestones, assignees, authors... We'll show you how it looks when the list is filtered by a label."
-msgstr ""
-
-msgid "UserOnboardingTour|Learn GitLab"
-msgstr ""
-
-msgid "UserOnboardingTour|Let's take a closer look at a merge request. Click on the title of one."
-msgstr ""
-
-msgid "UserOnboardingTour|Let's take a closer look at all the commits. Click on %{emphasisStart}Commits%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Let's take a closer look at the repository of this project. Click on %{emphasisStart}Repository%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|No thanks"
-msgstr ""
-
-msgid "UserOnboardingTour|Ok, let's go"
-msgstr ""
-
-msgid "UserOnboardingTour|Ok, show me"
-msgstr ""
-
-msgid "UserOnboardingTour|Open one of the issues by clicking on its title."
-msgstr ""
-
-msgid "UserOnboardingTour|Restart this step"
-msgstr ""
-
-msgid "UserOnboardingTour|Skip this step"
-msgstr ""
-
-msgid "UserOnboardingTour|Sweet! Your project was created and is ready to be used.%{lineBreak}%{lineBreak}You can start adding files to the repository or clone it. One last thing we want to show you is how to invite your colleagues to your new project."
-msgstr ""
-
-msgid "UserOnboardingTour|Take a look. Here's a nifty menu for quickly creating issues, merge requests, snippets, projects and groups. Click on it and select \"New project\" from the \"GitLab\" section to get started."
-msgstr ""
-
-msgid "UserOnboardingTour|Thanks for taking the guided tour. Remember, if you want to go through it again, you can start %{emphasisStart}Learn GitLab%{emphasisEnd} in the help menu on the top right."
-msgstr ""
-
-msgid "UserOnboardingTour|Thanks for the feedback! %{thumbsUp}"
-msgstr ""
-
-msgid "UserOnboardingTour|That's it for issues. Let'st take a look at %{emphasisStart}Merge Requests%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|That's it for merge requests. Now for the final part of this guided tour - the %{emphasisStart}CI/CD%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|That's it for the Repository. Let's take a look at the %{emphasisStart}Issues%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|The structure of this page is very similar to issues. Status, description, discussion and the sidebar are all here.%{lineBreak}%{lineBreak}But take a look below the description and you'll notice that there's more information about the merge request, the CI/CD pipeline and the options for approving it.%{lineBreak}%{lineBreak}Alongside the discussion you can also see more information about commits in this merge request, the status of pipelines and review all changes that were made."
-msgstr ""
-
-msgid "UserOnboardingTour|There's a lot of information here but don't worry, we'll go through it.%{lineBreak}%{lineBreak}On the top you can see the status of the issue and when it was opened and by whom. Directly below it is the issue description and below that are other %{emphasisStart}related issues%{emphasisEnd} and %{emphasisStart}merge requests%{emphasisEnd} (if any). Then below that is the %{emphasisStart}discussion%{emphasisEnd}, that's where most of the communication happens.%{lineBreak}%{lineBreak}On the right, there's a sidebar where you can view/change the %{emphasisStart}assignee, milestone, due date, labels, weight%{emphasisEnd}, etc."
-msgstr ""
-
-msgid "UserOnboardingTour|These are all the CI/CD pipelines we have for our %{emphasisStart}%{projectName}%{emphasisEnd} project.%{lineBreak}%{lineBreak}Here you can see the status of each pipeline, for what commit it's running for, its stages and the status for them."
-msgstr ""
-
-msgid "UserOnboardingTour|These are all the issues that are available for community contributions. Let's take a closer look at one of them."
-msgstr ""
-
-msgid "UserOnboardingTour|This is an overview of all merge requests in this project. Similarly to the issues overview it can be filtered down by things like labels, milestones, authors, assignees, etc."
-msgstr ""
-
-msgid "UserOnboardingTour|This is the repository for the %{emphasisStart}%{projectName}%{emphasisEnd} project. All our code is stored here. Feel free to explore and take a closer look at folders and files.%{lineBreak}%{lineBreak}Above the file structure you can see the latest commit, who the author is and the status of the CI/CD pipeline.%{lineBreak}%{lineBreak}If you scroll down below the file structure, you'll find the Readme of this project. This is defined in the README.md file at the root of the repository."
-msgstr ""
-
-msgid "UserOnboardingTour|Welcome to the project overview of the %{emphasisStart}%{projectName}%{emphasisEnd} project. This is the project that we use to work on GitLab. At first, a project seems like a simple repository, but at GitLab, a project is so much more.%{lineBreak}%{lineBreak}You can create projects for hosting your codebase, use it as an issue tracker, collaborate on code, and continuously build, test, and deploy your app with built-in GitLab CI/CD."
+msgid "UserList|created %{timeago}"
msgstr ""
msgid "UserProfile|Activity"
@@ -24471,6 +25116,9 @@ msgstr ""
msgid "UsersSelect|Unassigned"
msgstr ""
+msgid "Using %{codeStart}needs%{codeEnd} allows jobs to run before their stage is reached, as soon as their individual dependencies are met, which speeds up your pipelines."
+msgstr ""
+
msgid "Using %{code_start}::%{code_end} denotes a %{link_start}scoped label set%{link_end}"
msgstr ""
@@ -24555,9 +25203,6 @@ msgstr ""
msgid "Versions"
msgstr ""
-msgid "Very helpful"
-msgstr ""
-
msgid "View Documentation"
msgstr ""
@@ -24706,6 +25351,9 @@ msgstr ""
msgid "VisibilityLevel|Unknown"
msgstr ""
+msgid "Visit settings page"
+msgstr ""
+
msgid "VisualReviewApp|%{stepStart}Step 1%{stepEnd}. Copy the following script:"
msgstr ""
@@ -24799,6 +25447,9 @@ msgstr ""
msgid "VulnerabilityManagement|Something went wrong while trying to delete the comment. Please try again later."
msgstr ""
+msgid "VulnerabilityManagement|Something went wrong while trying to refresh the vulnerability. Please try again later."
+msgstr ""
+
msgid "VulnerabilityManagement|Something went wrong while trying to retrieve the vulnerability history. Please try again later."
msgstr ""
@@ -24841,6 +25492,9 @@ msgstr ""
msgid "Vulnerability|Class"
msgstr ""
+msgid "Vulnerability|Crash Address"
+msgstr ""
+
msgid "Vulnerability|Description"
msgstr ""
@@ -24916,9 +25570,6 @@ msgstr ""
msgid "We couldn't reach the Prometheus server. Either the server no longer exists or the configuration details need updating."
msgstr ""
-msgid "We created a short guided tour that will help you learn the basics of GitLab and how it will help you be better at your job. It should only take a couple of minutes. You will be guided by two types of helpers, best recognized by their color."
-msgstr ""
-
msgid "We detected potential spam in the %{humanized_resource_name}. Please solve the reCAPTCHA to proceed."
msgstr ""
@@ -24952,6 +25603,9 @@ msgstr ""
msgid "We've found no vulnerabilities"
msgstr ""
+msgid "Web Application Firewall"
+msgstr ""
+
msgid "Web IDE"
msgstr ""
@@ -25012,9 +25666,6 @@ msgstr ""
msgid "Welcome to GitLab.com<br>@%{name}!"
msgstr ""
-msgid "Welcome to the Guided GitLab Tour"
-msgstr ""
-
msgid "Welcome to the guided GitLab tour"
msgstr ""
@@ -25062,9 +25713,6 @@ msgstr ""
msgid "While it's rare to have no vulnerabilities, it can happen. In any event, we ask that you please double check your settings to make sure you've set up your dashboard correctly."
msgstr ""
-msgid "White helpers give contextual information."
-msgstr ""
-
msgid "Who can be an approver?"
msgstr ""
@@ -25110,12 +25758,18 @@ msgstr ""
msgid "WikiEmptyIssueMessage|Suggest wiki improvement"
msgstr ""
+msgid "WikiEmptyIssueMessage|You must be a group member in order to add wiki pages. If you have suggestions for how to improve the wiki for this group, consider opening an issue in the %{issues_link}."
+msgstr ""
+
msgid "WikiEmptyIssueMessage|You must be a project member in order to add wiki pages. If you have suggestions for how to improve the wiki for this project, consider opening an issue in the %{issues_link}."
msgstr ""
msgid "WikiEmptyIssueMessage|issue tracker"
msgstr ""
+msgid "WikiEmpty|A wiki is where you can store all the details about your group. This can include why you've created it, its principles, how to use it, and so on."
+msgstr ""
+
msgid "WikiEmpty|A wiki is where you can store all the details about your project. This can include why you've created it, its principles, how to use it, and so on."
msgstr ""
@@ -25125,12 +25779,21 @@ msgstr ""
msgid "WikiEmpty|Suggest wiki improvement"
msgstr ""
+msgid "WikiEmpty|The wiki lets you write documentation for your group"
+msgstr ""
+
msgid "WikiEmpty|The wiki lets you write documentation for your project"
msgstr ""
+msgid "WikiEmpty|This group has no wiki pages"
+msgstr ""
+
msgid "WikiEmpty|This project has no wiki pages"
msgstr ""
+msgid "WikiEmpty|You must be a group member in order to add wiki pages."
+msgstr ""
+
msgid "WikiEmpty|You must be a project member in order to add wiki pages."
msgstr ""
@@ -25407,13 +26070,13 @@ msgstr ""
msgid "You can now export your security dashboard to a CSV report."
msgstr ""
-msgid "You can now submit a merge request to get this change into the original branch."
+msgid "You can now manage alert endpoint configuration in the Alerts section on the Operations settings page. Fields on this page have been deprecated."
msgstr ""
-msgid "You can now submit a merge request to get this change into the original project."
+msgid "You can now submit a merge request to get this change into the original branch."
msgstr ""
-msgid "You can only add files when you are on a branch"
+msgid "You can now submit a merge request to get this change into the original project."
msgstr ""
msgid "You can only edit files when you are on a branch"
@@ -25635,7 +26298,7 @@ msgstr ""
msgid "You need to upload a Google Takeout archive."
msgstr ""
-msgid "You reached %{usage_in_percent} of %{namespace_name}'s capacity (%{used_storage} of %{storage_limit})"
+msgid "You reached %{usage_in_percent} of %{namespace_name}'s storage capacity (%{used_storage} of %{storage_limit})"
msgstr ""
msgid "You tried to fork %{link_to_the_project} but it failed for the following reason:"
@@ -25938,13 +26601,13 @@ msgstr ""
msgid "Your subscription expired!"
msgstr ""
-msgid "Your subscription has been downgraded"
+msgid "Your subscription has been downgraded."
msgstr ""
-msgid "Your subscription will automatically renew in %{remaining_days}"
+msgid "Your subscription will automatically renew in %{remaining_days}."
msgstr ""
-msgid "Your subscription will expire in %{remaining_days}"
+msgid "Your subscription will expire in %{remaining_days}."
msgstr ""
msgid "Zoom meeting added"
@@ -26024,9 +26687,6 @@ msgstr ""
msgid "branch name"
msgstr ""
-msgid "build pipeline reference mismatch"
-msgstr ""
-
msgid "by"
msgstr ""
@@ -26045,6 +26705,9 @@ msgstr ""
msgid "cannot block others"
msgstr ""
+msgid "cannot contain HTML/XML tags, including any word between angle brackets (<,>)."
+msgstr ""
+
msgid "cannot include leading slash or directory traversal."
msgstr ""
@@ -26075,55 +26738,6 @@ msgstr ""
msgid "ciReport|%{remainingPackagesCount} more"
msgstr ""
-msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability"
-msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability for the source branch only"
-msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities for the source branch only"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerability"
-msgid_plural "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerabilities"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new vulnerability"
-msgid_plural "ciReport|%{reportType} %{status} detected %{newCount} new vulnerabilities"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities for the source branch only"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{fixedCount} fixed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} vulnerability for the source branch only"
-msgid_plural "ciReport|%{reportType} %{status} detected %{newCount} vulnerabilities for the source branch only"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "ciReport|%{reportType} %{status} detected no new vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected no vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected no vulnerabilities for the source branch only"
-msgstr ""
-
msgid "ciReport|%{reportType} is loading"
msgstr ""
@@ -26142,7 +26756,7 @@ msgstr ""
msgid "ciReport|All projects"
msgstr ""
-msgid "ciReport|All report types"
+msgid "ciReport|All scanner types"
msgstr ""
msgid "ciReport|All severities"
@@ -26166,6 +26780,9 @@ msgstr ""
msgid "ciReport|Container scanning detects known vulnerabilities in your docker images."
msgstr ""
+msgid "ciReport|Coverage Fuzzing"
+msgstr ""
+
msgid "ciReport|Create a merge request to implement this solution, or download and apply the patch manually."
msgstr ""
@@ -26324,6 +26941,9 @@ msgstr ""
msgid "customize"
msgstr ""
+msgid "data"
+msgstr ""
+
msgid "date must not be after 9999-12-31"
msgstr ""
@@ -26394,6 +27014,9 @@ msgstr ""
msgid "entries cannot contain HTML tags"
msgstr ""
+msgid "epic"
+msgstr ""
+
msgid "error"
msgstr ""
@@ -26415,9 +27038,6 @@ msgstr ""
msgid "expires on %{timebox_due_date}"
msgstr ""
-msgid "external_url"
-msgstr ""
-
msgid "failed"
msgstr ""
@@ -26453,9 +27073,6 @@ msgstr ""
msgid "from"
msgstr ""
-msgid "geo_node_name"
-msgstr ""
-
msgid "group"
msgstr ""
@@ -26477,9 +27094,6 @@ msgstr ""
msgid "https://your-bitbucket-server"
msgstr ""
-msgid "image"
-msgstr ""
-
msgid "image diff"
msgstr ""
@@ -26623,6 +27237,9 @@ msgstr ""
msgid "load it anyway"
msgstr ""
+msgid "loading"
+msgstr ""
+
msgid "locked by %{path_lock_user_name} %{created_at}"
msgstr ""
@@ -26751,9 +27368,6 @@ msgstr ""
msgid "mrWidget|Deployment statistics are not available currently"
msgstr ""
-msgid "mrWidget|Detect issues before deployment with a CI pipeline that continuously tests your code. We created a quick guide that will show you how to create one. Make your code more secure and more robust in just a minute."
-msgstr ""
-
msgid "mrWidget|Did not close"
msgstr ""
@@ -26916,6 +27530,9 @@ msgstr ""
msgid "mrWidget|To approve this merge request, please enter your password. This project requires all approvals to be authenticated."
msgstr ""
+msgid "mrWidget|Use %{linkStart}CI pipelines to test your code%{linkEnd}, simply add a GitLab CI configuration file to your project. It only takes a minute to make your code more secure and robust."
+msgstr ""
+
msgid "mrWidget|When this merge request is ready, remove the WIP: prefix from the title to allow it to be merged"
msgstr ""
@@ -26952,6 +27569,9 @@ msgstr ""
msgid "must be greater than start date"
msgstr ""
+msgid "must contain only valid frameworks"
+msgstr ""
+
msgid "my-awesome-group"
msgstr ""
@@ -26964,6 +27584,9 @@ msgstr ""
msgid "needs to be between 10 minutes and 1 month"
msgstr ""
+msgid "never"
+msgstr ""
+
msgid "never expires"
msgstr ""
@@ -26991,6 +27614,9 @@ msgstr ""
msgid "nounSeries|%{firstItem} and %{lastItem}"
msgstr ""
+msgid "nounSeries|%{item}"
+msgstr ""
+
msgid "nounSeries|%{item}, %{nextItem}"
msgstr ""
@@ -27127,6 +27753,9 @@ msgstr ""
msgid "revised"
msgstr ""
+msgid "satisfied"
+msgstr ""
+
msgid "score"
msgstr ""
@@ -27157,6 +27786,9 @@ msgstr ""
msgid "severity|Unknown"
msgstr ""
+msgid "should be an array of %{object_name} objects"
+msgstr ""
+
msgid "should be greater than or equal to %{access} inherited membership from group %{group_name}"
msgstr ""
@@ -27211,10 +27843,13 @@ msgstr ""
msgid "suggestPipeline|2/2: Commit your changes"
msgstr ""
-msgid "suggestPipeline|Commit the changes and your pipeline will automatically run for the first time."
+msgid "suggestPipeline|Choose %{boldStart}Code Quality%{boldEnd} to add a pipeline that tests the quality of your code."
msgstr ""
-msgid "suggestPipeline|We recommend the %{boldStart}Code Quality%{boldEnd} template, which will add a report widget to your Merge Requests. This way you’ll learn about code quality degradations much sooner. %{footerStart} Goodbye technical debt! %{footerEnd}"
+msgid "suggestPipeline|The template is ready! You can now commit it to create your first pipeline."
+msgstr ""
+
+msgid "suggestPipeline|We’re adding a GitLab CI configuration file to add a pipeline to the project. You could create it manually, but we recommend that you start with a GitLab template that works out of the box."
msgstr ""
msgid "syntax is correct"
@@ -27274,6 +27909,9 @@ msgstr ""
msgid "uses Kubernetes clusters to deploy your code!"
msgstr ""
+msgid "v%{version} published %{timeAgo}"
+msgstr ""
+
msgid "verify ownership"
msgstr ""
diff --git a/locale/ar_SA/gitlab.po b/locale/ar_SA/gitlab.po
index 7fba63a73c0..1b2572313c1 100644
--- a/locale/ar_SA/gitlab.po
+++ b/locale/ar_SA/gitlab.po
@@ -2,7 +2,7 @@ msgid ""
msgstr ""
"Project-Id-Version: gitlab-ee\n"
"Report-Msgid-Bugs-To: \n"
-"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Last-Translator: \n"
"Language-Team: Arabic\n"
"Language: ar_SA\n"
"MIME-Version: 1.0\n"
@@ -10,9 +10,11 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=6; plural=(n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5);\n"
"X-Crowdin-Project: gitlab-ee\n"
+"X-Crowdin-Project-ID: 288872\n"
"X-Crowdin-Language: ar\n"
"X-Crowdin-File: /master/locale/gitlab.pot\n"
-"PO-Revision-Date: 2020-06-08 15:15\n"
+"X-Crowdin-File-ID: 6\n"
+"PO-Revision-Date: 2020-07-02 01:24\n"
msgid " %{start} to %{end}"
msgstr ""
@@ -305,6 +307,15 @@ msgstr[3] ""
msgstr[4] ""
msgstr[5] ""
+msgid "%d milestone"
+msgid_plural "%d milestones"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+msgstr[4] ""
+msgstr[5] ""
+
msgid "%d minute"
msgid_plural "%d minutes"
msgstr[0] ""
@@ -386,15 +397,6 @@ msgstr[3] ""
msgstr[4] ""
msgstr[5] ""
-msgid "%d url scanned"
-msgid_plural "%d urls scanned"
-msgstr[0] ""
-msgstr[1] ""
-msgstr[2] ""
-msgstr[3] ""
-msgstr[4] ""
-msgstr[5] ""
-
msgid "%d vulnerability"
msgid_plural "%d vulnerabilities"
msgstr[0] ""
@@ -500,9 +502,15 @@ msgstr[5] ""
msgid "%{count} related %{pluralized_subject}: %{links}"
msgstr ""
+msgid "%{dashboard_path} could not be found."
+msgstr ""
+
msgid "%{days} days until tags are automatically removed"
msgstr ""
+msgid "%{deployLinkStart}Use a template to deploy to ECS%{deployLinkEnd}, or use a docker image to %{commandsLinkStart}run AWS commands in GitLab CI/CD%{commandsLinkEnd}."
+msgstr ""
+
msgid "%{description}- Sentry event: %{errorUrl}- First seen: %{firstSeen}- Last seen: %{lastSeen} %{countLabel}: %{count}%{userCountLabel}: %{userCount}"
msgstr ""
@@ -545,12 +553,18 @@ msgstr ""
msgid "%{host} sign-in from new location"
msgstr ""
-msgid "%{icon}You are about to add %{usersTag} people to the discussion. Proceed with caution."
+msgid "%{icon}You are about to add %{usersTag} people to the discussion. They will all receive a notification."
+msgstr ""
+
+msgid "%{integrations_link_start}Integrations%{link_end} enable you to make third-party applications part of your GitLab workflow. If the available integrations don't meet your needs, consider using a %{webhooks_link_start}webhook%{link_end}."
msgstr ""
msgid "%{issuableType} will be removed! Are you sure?"
msgstr ""
+msgid "%{issuesCount} issues in this group"
+msgstr ""
+
msgid "%{issuesSize} issues"
msgstr ""
@@ -560,7 +574,7 @@ msgstr ""
msgid "%{labelStart}Class:%{labelEnd} %{class}"
msgstr ""
-msgid "%{labelStart}Confidence:%{labelEnd} %{confidence}"
+msgid "%{labelStart}Evidence:%{labelEnd} %{evidence}"
msgstr ""
msgid "%{labelStart}File:%{labelEnd} %{file}"
@@ -578,6 +592,9 @@ msgstr ""
msgid "%{labelStart}Report Type:%{labelEnd} %{reportType}"
msgstr ""
+msgid "%{labelStart}Scanner:%{labelEnd} %{scanner}"
+msgstr ""
+
msgid "%{labelStart}Severity:%{labelEnd} %{severity}"
msgstr ""
@@ -602,9 +619,6 @@ msgstr ""
msgid "%{link_start}Read more%{link_end} about role permissions"
msgstr ""
-msgid "%{link} can be used for binding events when something is happening within the project."
-msgstr ""
-
msgid "%{listToShow}, and %{awardsListLength} more."
msgstr ""
@@ -638,6 +652,9 @@ msgstr ""
msgid "%{name} found %{resultsString}"
msgstr ""
+msgid "%{name} is already being used for another emoji"
+msgstr ""
+
msgid "%{name} is scheduled for %{action}"
msgstr ""
@@ -689,6 +706,9 @@ msgstr ""
msgid "%{primary} (%{secondary})"
msgstr ""
+msgid "%{ref} cannot be added: %{error}"
+msgstr ""
+
msgid "%{releases} release"
msgid_plural "%{releases} releases"
msgstr[0] ""
@@ -701,13 +721,49 @@ msgstr[5] ""
msgid "%{remaining_approvals} left"
msgstr ""
-msgid "%{retryButtonStart}Try again%{retryButtonEnd} or %{newFileButtonStart}attach a new file%{newFileButtonEnd}"
+msgid "%{reportType} %{status} detected %{critical} critical and %{high} high severity vulnerabilities out of %{total}."
msgstr ""
-msgid "%{screenreaderOnlyStart}Keyboard shorcuts%{screenreaderOnlyEnd} Disabled"
+msgid "%{reportType} %{status} detected %{critical} critical and %{high} high severity vulnerabilities."
msgstr ""
-msgid "%{screenreaderOnlyStart}Keyboard shorcuts%{screenreaderOnlyEnd} Enabled"
+msgid "%{reportType} %{status} detected %{critical} critical severity vulnerabilities out of %{total}."
+msgstr ""
+
+msgid "%{reportType} %{status} detected %{critical} critical severity vulnerability."
+msgid_plural "%{reportType} %{status} detected %{critical} critical severity vulnerabilities."
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+msgstr[4] ""
+msgstr[5] ""
+
+msgid "%{reportType} %{status} detected %{high} high severity vulnerabilities out of %{total}."
+msgstr ""
+
+msgid "%{reportType} %{status} detected %{high} high severity vulnerability."
+msgid_plural "%{reportType} %{status} detected %{high} high severity vulnerabilities."
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+msgstr[4] ""
+msgstr[5] ""
+
+msgid "%{reportType} %{status} detected %{other} vulnerability."
+msgid_plural "%{reportType} %{status} detected %{other} vulnerabilities."
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+msgstr[4] ""
+msgstr[5] ""
+
+msgid "%{reportType} %{status} detected no new vulnerabilities."
+msgstr ""
+
+msgid "%{retryButtonStart}Try again%{retryButtonEnd} or %{newFileButtonStart}attach a new file%{newFileButtonEnd}"
msgstr ""
msgid "%{service_title} %{message}."
@@ -824,6 +880,12 @@ msgstr ""
msgid "%{token}..."
msgstr ""
+msgid "%{totalCpu} (%{freeSpacePercentage}%{percentSymbol} free)"
+msgstr ""
+
+msgid "%{totalMemory} (%{freeSpacePercentage}%{percentSymbol} free)"
+msgstr ""
+
msgid "%{totalWeight} total weight"
msgstr ""
@@ -854,6 +916,12 @@ msgstr ""
msgid "%{verb} %{time_spent_value} spent time."
msgstr ""
+msgid "%{webhooks_link_start}%{webhook_type}%{link_end} enable you to send notifications to web applications in response to events in a group or project."
+msgstr ""
+
+msgid "%{webhooks_link_start}%{webhook_type}%{link_end} enable you to send notifications to web applications in response to events in a group or project. We recommend using an %{integrations_link_start}integration%{link_end} in preference to a webhook."
+msgstr ""
+
msgid "'%{level}' is not a valid visibility level"
msgstr ""
@@ -884,9 +952,6 @@ msgstr ""
msgid "(No changes)"
msgstr ""
-msgid "(Show all)"
-msgstr ""
-
msgid "(check progress)"
msgstr ""
@@ -965,6 +1030,9 @@ msgstr ""
msgid "0 for unlimited"
msgstr ""
+msgid "0 for unlimited, only effective with remote storage enabled."
+msgstr ""
+
msgid "1 %{type} addition"
msgid_plural "%{count} %{type} additions"
msgstr[0] ""
@@ -1172,6 +1240,12 @@ msgstr ""
msgid "<code>\"johnsmith@example.com\": \"johnsmith@example.com\"</code> will add \"By <a href=\"#\">johnsmith@example.com</a>\" to all issues and comments originally created by johnsmith@example.com. By default, the email address or username is masked to ensure the user's privacy. Use this option if you want to show the full email address."
msgstr ""
+msgid "<code>Masked</code> to prevent the values from being displayed in job logs (must match certain regexp requirements)."
+msgstr ""
+
+msgid "<code>Protected</code> to expose them to protected branches or tags only."
+msgstr ""
+
msgid "<namespace / project>"
msgstr ""
@@ -1199,9 +1273,6 @@ msgstr ""
msgid "A .NET Core console application template, customizable for any .NET Core project"
msgstr ""
-msgid "A DAG must have two dependent jobs to be visualized on this tab."
-msgstr ""
-
msgid "A GitBook site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
msgstr ""
@@ -1232,6 +1303,12 @@ msgstr ""
msgid "A deleted user"
msgstr ""
+msgid "A file has been changed."
+msgstr ""
+
+msgid "A file was not found."
+msgstr ""
+
msgid "A file with '%{file_name}' already exists in %{branch} branch"
msgstr ""
@@ -1298,7 +1375,7 @@ msgstr ""
msgid "A subscription will trigger a new pipeline on the default branch of this project when a pipeline successfully completes for a new tag on the %{default_branch_docs} of the subscribed project."
msgstr ""
-msgid "A terraform report was generated in your pipelines."
+msgid "A suggestion is not applicable."
msgstr ""
msgid "A user with write access to the source branch selected this option"
@@ -1469,6 +1546,9 @@ msgstr ""
msgid "Action to take when receiving an alert."
msgstr ""
+msgid "Actions"
+msgstr ""
+
msgid "Activate"
msgstr ""
@@ -1529,6 +1609,9 @@ msgstr ""
msgid "Add LICENSE"
msgstr ""
+msgid "Add New Node"
+msgstr ""
+
msgid "Add README"
msgstr ""
@@ -1604,6 +1687,9 @@ msgstr ""
msgid "Add approval rule"
msgstr ""
+msgid "Add approvers"
+msgstr ""
+
msgid "Add bold text"
msgstr ""
@@ -1661,6 +1747,9 @@ msgstr ""
msgid "Add strikethrough text"
msgstr ""
+msgid "Add suggestion to batch"
+msgstr ""
+
msgid "Add system hook"
msgstr ""
@@ -1760,6 +1849,9 @@ msgstr ""
msgid "Admin Note"
msgstr ""
+msgid "Admin Notifications"
+msgstr ""
+
msgid "Admin Overview"
msgstr ""
@@ -1871,6 +1963,9 @@ msgstr ""
msgid "AdminSettings|Integrations configured here will automatically apply to all projects on this instance."
msgstr ""
+msgid "AdminSettings|Moved to integrations"
+msgstr ""
+
msgid "AdminSettings|No required pipeline"
msgstr ""
@@ -2105,12 +2200,18 @@ msgstr[3] ""
msgstr[4] ""
msgstr[5] ""
+msgid "AlertManagement| assign yourself"
+msgstr ""
+
msgid "AlertManagement|Acknowledged"
msgstr ""
msgid "AlertManagement|Alert"
msgstr ""
+msgid "AlertManagement|Alert assignee(s): %{assignees}"
+msgstr ""
+
msgid "AlertManagement|Alert detail"
msgstr ""
@@ -2126,9 +2227,15 @@ msgstr ""
msgid "AlertManagement|All alerts"
msgstr ""
+msgid "AlertManagement|Assign To"
+msgstr ""
+
msgid "AlertManagement|Assign status"
msgstr ""
+msgid "AlertManagement|Assignee"
+msgstr ""
+
msgid "AlertManagement|Assignees"
msgstr ""
@@ -2147,9 +2254,6 @@ msgstr ""
msgid "AlertManagement|Edit"
msgstr ""
-msgid "AlertManagement|End time"
-msgstr ""
-
msgid "AlertManagement|Events"
msgstr ""
@@ -2168,10 +2272,13 @@ msgstr ""
msgid "AlertManagement|More information"
msgstr ""
+msgid "AlertManagement|No Matching Results"
+msgstr ""
+
msgid "AlertManagement|No alert data to display."
msgstr ""
-msgid "AlertManagement|No alerts available to display. If you think you're seeing this message in error, refresh the page."
+msgid "AlertManagement|No alerts available to display. See %{linkStart}enabling alert management%{linkEnd} for more information on adding alerts to the list."
msgstr ""
msgid "AlertManagement|No alerts to display."
@@ -2180,6 +2287,9 @@ msgstr ""
msgid "AlertManagement|None"
msgstr ""
+msgid "AlertManagement|None -"
+msgstr ""
+
msgid "AlertManagement|Open"
msgstr ""
@@ -2216,9 +2326,18 @@ msgstr ""
msgid "AlertManagement|There was an error displaying the alerts. Confirm your endpoint's configuration details to ensure alerts appear."
msgstr ""
+msgid "AlertManagement|There was an error while updating the assignee(s) list. Please try again."
+msgstr ""
+
+msgid "AlertManagement|There was an error while updating the assignee(s) of the alert. Please try again."
+msgstr ""
+
msgid "AlertManagement|There was an error while updating the status of the alert. Please try again."
msgstr ""
+msgid "AlertManagement|This assignee cannot be assigned to this alert."
+msgstr ""
+
msgid "AlertManagement|Tool"
msgstr ""
@@ -2366,6 +2485,9 @@ msgstr ""
msgid "Allow users to request access (if visibility is public or internal)"
msgstr ""
+msgid "Allowed Geo IP"
+msgstr ""
+
msgid "Allowed email domain restriction only permitted for top-level groups"
msgstr ""
@@ -2399,6 +2521,9 @@ msgstr ""
msgid "Amazon Web Services"
msgstr ""
+msgid "Amazon Web Services Logo"
+msgstr ""
+
msgid "Amazon authentication is not %{link_start}correctly configured%{link_end}. Ask your GitLab administrator if you want to use this service."
msgstr ""
@@ -2456,10 +2581,16 @@ msgstr ""
msgid "An error occurred when updating the issue weight"
msgstr ""
+msgid "An error occurred while acknowledging the notification. Refresh the page and try again."
+msgstr ""
+
+msgid "An error occurred while adding approvers"
+msgstr ""
+
msgid "An error occurred while adding formatted title for epic"
msgstr ""
-msgid "An error occurred while checking group path"
+msgid "An error occurred while checking group path. Please refresh and try again."
msgstr ""
msgid "An error occurred while committing your changes."
@@ -2603,7 +2734,7 @@ msgstr ""
msgid "An error occurred while loading milestones"
msgstr ""
-msgid "An error occurred while loading terraform report"
+msgid "An error occurred while loading project creation UI"
msgstr ""
msgid "An error occurred while loading the data. Please try again."
@@ -2657,6 +2788,9 @@ msgstr ""
msgid "An error occurred while rendering preview broadcast message"
msgstr ""
+msgid "An error occurred while rendering the editor"
+msgstr ""
+
msgid "An error occurred while reordering issues."
msgstr ""
@@ -2753,7 +2887,7 @@ msgstr ""
msgid "Analyze your dependencies for known vulnerabilities."
msgstr ""
-msgid "Analyze your source code and git history for secrets"
+msgid "Analyze your source code and git history for secrets."
msgstr ""
msgid "Analyze your source code for known vulnerabilities."
@@ -2867,12 +3001,18 @@ msgstr ""
msgid "Apply suggestion"
msgstr ""
+msgid "Apply suggestions"
+msgstr ""
+
msgid "Apply template"
msgstr ""
msgid "Apply this approval rule to any branch or a specific protected branch."
msgstr ""
+msgid "Applying"
+msgstr ""
+
msgid "Applying a template will replace the existing issue description. Any changes you have made will be lost."
msgstr ""
@@ -2885,12 +3025,18 @@ msgstr ""
msgid "Applying multiple commands"
msgstr ""
-msgid "Applying suggestion"
+msgid "Applying suggestion..."
+msgstr ""
+
+msgid "Applying suggestions..."
msgstr ""
msgid "Approval rules"
msgstr ""
+msgid "Approval rules reset to project defaults"
+msgstr ""
+
msgid "ApprovalRuleRemove|%d member"
msgid_plural "ApprovalRuleRemove|%d members"
msgstr[0] ""
@@ -2960,6 +3106,9 @@ msgstr ""
msgid "Approved"
msgstr ""
+msgid "Approved MRs"
+msgstr ""
+
msgid "Approved by: "
msgstr ""
@@ -3173,6 +3322,9 @@ msgstr ""
msgid "Assign"
msgstr ""
+msgid "Assign Iteration"
+msgstr ""
+
msgid "Assign custom color like #FF0000"
msgstr ""
@@ -3191,6 +3343,9 @@ msgstr ""
msgid "Assign to"
msgstr ""
+msgid "Assign to commenting user"
+msgstr ""
+
msgid "Assign yourself to these issues"
msgstr ""
@@ -3221,6 +3376,9 @@ msgstr[3] ""
msgstr[4] ""
msgstr[5] ""
+msgid "Assignee has no permissions"
+msgstr ""
+
msgid "Assignee lists not available with your current license"
msgstr ""
@@ -3230,6 +3388,9 @@ msgstr ""
msgid "Assignee(s)"
msgstr ""
+msgid "Assignees"
+msgstr ""
+
msgid "Assigns %{assignee_users_sentence}."
msgstr ""
@@ -3269,6 +3430,9 @@ msgstr ""
msgid "Audit Events is a way to keep track of important events that happened in GitLab."
msgstr ""
+msgid "Audit Log"
+msgstr ""
+
msgid "AuditEvents|(removed)"
msgstr ""
@@ -3305,6 +3469,9 @@ msgstr ""
msgid "AuditLogs|IP Address"
msgstr ""
+msgid "AuditLogs|Member Events"
+msgstr ""
+
msgid "AuditLogs|No matching %{type} found."
msgstr ""
@@ -3587,9 +3754,6 @@ msgstr ""
msgid "Badges|e.g. %{exampleUrl}"
msgstr ""
-msgid "Badge|New"
-msgstr ""
-
msgid "Balsamiq file could not be loaded."
msgstr ""
@@ -3638,6 +3802,12 @@ msgstr ""
msgid "Below you will find all the groups that are public."
msgstr ""
+msgid "Beta"
+msgstr ""
+
+msgid "Bi-weekly code coverage"
+msgstr ""
+
msgid "Billing"
msgstr ""
@@ -3698,9 +3868,15 @@ msgstr ""
msgid "Bitbucket Server Import"
msgstr ""
+msgid "Bitbucket Server import"
+msgstr ""
+
msgid "Bitbucket import"
msgstr ""
+msgid "Blame"
+msgstr ""
+
msgid "Blocked"
msgstr ""
@@ -3713,9 +3889,6 @@ msgstr ""
msgid "Blog"
msgstr ""
-msgid "Blue helpers indicate an action to be taken."
-msgstr ""
-
msgid "Board name"
msgstr ""
@@ -3914,6 +4087,9 @@ msgstr ""
msgid "Broadcast Message was successfully updated."
msgstr ""
+msgid "Broadcast Messages"
+msgstr ""
+
msgid "Browse Directory"
msgstr ""
@@ -3968,6 +4144,9 @@ msgstr ""
msgid "By %{user_name}"
msgstr ""
+msgid "By URL"
+msgstr ""
+
msgid "By default GitLab sends emails in HTML and plain text formats so mail clients can choose what format to use. Disable this option if you only want to send emails in plain text format."
msgstr ""
@@ -4073,6 +4252,9 @@ msgstr ""
msgid "Can override approvers and approvals required per merge request"
msgstr ""
+msgid "Can't apply as this line has changed or the suggestion already matches its content."
+msgstr ""
+
msgid "Can't create snippet: %{err}"
msgstr ""
@@ -4259,9 +4441,6 @@ msgstr ""
msgid "Changes are still tracked. Useful for cluster/index migrations."
msgstr ""
-msgid "Changes are unknown"
-msgstr ""
-
msgid "Changes suppressed. Click to show."
msgstr ""
@@ -4568,15 +4747,9 @@ msgstr ""
msgid "Choose what content you want to see on a group’s overview page"
msgstr ""
-msgid "Choose which groups you wish to synchronize to this secondary node"
-msgstr ""
-
msgid "Choose which repositories you want to connect and run CI/CD pipelines."
msgstr ""
-msgid "Choose which shards you wish to synchronize to this secondary node"
-msgstr ""
-
msgid "Choose your framework"
msgstr ""
@@ -4874,9 +5047,15 @@ msgstr ""
msgid "Cluster does not exist"
msgstr ""
+msgid "Cluster is required for Stages::ClusterEndpointInserter"
+msgstr ""
+
msgid "Cluster level"
msgstr ""
+msgid "Cluster type must be specificed for Stages::ClusterEndpointInserter"
+msgstr ""
+
msgid "ClusterIntegration| %{custom_domain_start}More information%{custom_domain_end}."
msgstr ""
@@ -4988,7 +5167,7 @@ msgstr ""
msgid "ClusterIntegration|Cert-Manager"
msgstr ""
-msgid "ClusterIntegration|Cert-Manager is a native Kubernetes certificate management controller that helps with issuing certificates. Installing Cert-Manager on your cluster will issue a certificate by %{letsEncrypt} and ensure that certificates are valid and up-to-date."
+msgid "ClusterIntegration|Cert-Manager is a native Kubernetes certificate management controller that helps with issuing certificates. Installing Cert-Manager on your cluster will issue a certificate by %{linkStart}Let's Encrypt%{linkEnd} and ensure that certificates are valid and up-to-date."
msgstr ""
msgid "ClusterIntegration|Certificate Authority bundle (PEM format)"
@@ -5015,9 +5194,6 @@ msgstr ""
msgid "ClusterIntegration|Clear the local cache of namespace and service accounts. This is necessary if your integration has become out of sync. The cache is repopulated during the next CI job that requires namespace and service accounts."
msgstr ""
-msgid "ClusterIntegration|Cloud Run"
-msgstr ""
-
msgid "ClusterIntegration|Cluster being created"
msgstr ""
@@ -5102,7 +5278,7 @@ msgstr ""
msgid "ClusterIntegration|Crossplane"
msgstr ""
-msgid "ClusterIntegration|Crossplane enables declarative provisioning of managed services from your cloud of choice using %{codeStart}kubectl%{codeEnd} or %{gitlabIntegrationLink}. Crossplane runs inside your Kubernetes cluster and supports secure connectivity and secrets management between app containers and the cloud services they depend on."
+msgid "ClusterIntegration|Crossplane enables declarative provisioning of managed services from your cloud of choice using %{codeStart}kubectl%{codeEnd} or %{linkStart}GitLab Integration%{linkEnd}. Crossplane runs inside your Kubernetes cluster and supports secure connectivity and secrets management between app containers and the cloud services they depend on."
msgstr ""
msgid "ClusterIntegration|Deletes all GitLab resources attached to this cluster during removal"
@@ -5186,9 +5362,6 @@ msgstr ""
msgid "ClusterIntegration|GitLab-managed cluster"
msgstr ""
-msgid "ClusterIntegration|Gitlab Integration"
-msgstr ""
-
msgid "ClusterIntegration|Global default"
msgstr ""
@@ -5234,7 +5407,7 @@ msgstr ""
msgid "ClusterIntegration|Ingress gives you a way to route requests to services based on the request host or path, centralizing a number of services into a single entrypoint."
msgstr ""
-msgid "ClusterIntegration|Installing Ingress may incur additional costs. Learn more about %{pricingLink}."
+msgid "ClusterIntegration|Installing Ingress may incur additional costs. Learn more about %{linkStart}pricing%{linkEnd}."
msgstr ""
msgid "ClusterIntegration|Instance cluster"
@@ -5249,7 +5422,7 @@ msgstr ""
msgid "ClusterIntegration|Issuer Email"
msgstr ""
-msgid "ClusterIntegration|Issuers represent a certificate authority. You must provide an email address for your Issuer. "
+msgid "ClusterIntegration|Issuers represent a certificate authority. You must provide an email address for your Issuer."
msgstr ""
msgid "ClusterIntegration|Jupyter Hostname"
@@ -5321,9 +5494,6 @@ msgstr ""
msgid "ClusterIntegration|Learn more about instance Kubernetes clusters"
msgstr ""
-msgid "ClusterIntegration|Let's Encrypt"
-msgstr ""
-
msgid "ClusterIntegration|Loading IAM Roles"
msgstr ""
@@ -5435,7 +5605,7 @@ msgstr ""
msgid "ClusterIntegration|Prometheus"
msgstr ""
-msgid "ClusterIntegration|Prometheus is an open-source monitoring system with %{gitlabIntegrationLink} to monitor deployed applications."
+msgid "ClusterIntegration|Prometheus is an open-source monitoring system with %{linkStart}GitLab Integration%{linkEnd} to monitor deployed applications."
msgstr ""
msgid "ClusterIntegration|Provider details"
@@ -5591,10 +5761,10 @@ msgstr ""
msgid "ClusterIntegration|Select zone to choose machine type"
msgstr ""
-msgid "ClusterIntegration|Send Cilium Logs"
+msgid "ClusterIntegration|Send Container Network Policies Logs"
msgstr ""
-msgid "ClusterIntegration|Send ModSecurity Logs"
+msgid "ClusterIntegration|Send Web Application Firewall Logs"
msgstr ""
msgid "ClusterIntegration|Service Token"
@@ -5762,15 +5932,12 @@ msgstr ""
msgid "ClusterIntegration|documentation"
msgstr ""
-msgid "ClusterIntegration|installed via %{installed_via}"
+msgid "ClusterIntegration|installed via %{linkStart}Cloud Run%{linkEnd}"
msgstr ""
msgid "ClusterIntegration|meets the requirements"
msgstr ""
-msgid "ClusterIntegration|pricing"
-msgstr ""
-
msgid "ClusterIntegration|sign up"
msgstr ""
@@ -5807,6 +5974,15 @@ msgstr ""
msgid "Code"
msgstr ""
+msgid "Code Coverage: %{coveragePercentage}%{percentSymbol}"
+msgstr ""
+
+msgid "Code Coverage| Empty code coverage data"
+msgstr ""
+
+msgid "Code Coverage|Couldn't fetch the code coverage data"
+msgstr ""
+
msgid "Code Owners"
msgstr ""
@@ -5861,7 +6037,7 @@ msgstr ""
msgid "Collapse approvers"
msgstr ""
-msgid "Collapse child epics"
+msgid "Collapse milestones"
msgstr ""
msgid "Collapse replies"
@@ -5879,6 +6055,9 @@ msgstr ""
msgid "Coming soon"
msgstr ""
+msgid "Comma-separated, e.g. '1.1.1.1, 2.2.2.0/24'"
+msgstr ""
+
msgid "Command"
msgstr ""
@@ -6053,6 +6232,12 @@ msgstr ""
msgid "Compliance framework (optional)"
msgstr ""
+msgid "Compliance frameworks"
+msgstr ""
+
+msgid "ComplianceDashboard|created by:"
+msgstr ""
+
msgid "ComplianceFramework|GDPR"
msgstr ""
@@ -6191,6 +6376,9 @@ msgstr ""
msgid "Connection timed out"
msgstr ""
+msgid "Connection timeout"
+msgstr ""
+
msgid "Contact sales to upgrade"
msgstr ""
@@ -6254,9 +6442,6 @@ msgstr ""
msgid "ContainerRegistry|CLI Commands"
msgstr ""
-msgid "ContainerRegistry|Compressed Size"
-msgstr ""
-
msgid "ContainerRegistry|Container Registry"
msgstr ""
@@ -6272,6 +6457,9 @@ msgstr ""
msgid "ContainerRegistry|Copy push command"
msgstr ""
+msgid "ContainerRegistry|Delete selected"
+msgstr ""
+
msgid "ContainerRegistry|Docker connection error"
msgstr ""
@@ -6305,16 +6493,16 @@ msgstr ""
msgid "ContainerRegistry|If you are not already logged in, you need to authenticate to the Container Registry by using your GitLab username and password. If you have %{twofaDocLinkStart}Two-Factor Authentication%{twofaDocLinkEnd} enabled, use a %{personalAccessTokensDocLinkStart}Personal Access Token%{personalAccessTokensDocLinkEnd} instead of a password."
msgstr ""
-msgid "ContainerRegistry|Image ID"
+msgid "ContainerRegistry|Image ID: %{imageId}"
msgstr ""
msgid "ContainerRegistry|Image Repositories"
msgstr ""
-msgid "ContainerRegistry|Keep and protect the images that matter most."
+msgid "ContainerRegistry|Image tags"
msgstr ""
-msgid "ContainerRegistry|Last Updated"
+msgid "ContainerRegistry|Keep and protect the images that matter most."
msgstr ""
msgid "ContainerRegistry|Login"
@@ -6329,6 +6517,9 @@ msgstr ""
msgid "ContainerRegistry|Please contact your administrator."
msgstr ""
+msgid "ContainerRegistry|Published %{timeInfo}"
+msgstr ""
+
msgid "ContainerRegistry|Push an image"
msgstr ""
@@ -6344,9 +6535,6 @@ msgstr ""
msgid "ContainerRegistry|Remove repository"
msgstr ""
-msgid "ContainerRegistry|Remove selected tags"
-msgstr ""
-
msgid "ContainerRegistry|Remove tag"
msgid_plural "ContainerRegistry|Remove tags"
msgstr[0] ""
@@ -6380,9 +6568,6 @@ msgstr ""
msgid "ContainerRegistry|Sorry, your filter produced no results."
msgstr ""
-msgid "ContainerRegistry|Tag"
-msgstr ""
-
msgid "ContainerRegistry|Tag expiration policy"
msgstr ""
@@ -6527,21 +6712,6 @@ msgstr ""
msgid "Control the display of third party offers."
msgstr ""
-msgid "Control the maximum concurrency of LFS/attachment backfill for this secondary node"
-msgstr ""
-
-msgid "Control the maximum concurrency of container repository operations for this Geo node"
-msgstr ""
-
-msgid "Control the maximum concurrency of repository backfill for this secondary node"
-msgstr ""
-
-msgid "Control the maximum concurrency of verification operations for this Geo node"
-msgstr ""
-
-msgid "Control the minimum interval in days that a repository should be reverified for this primary node"
-msgstr ""
-
msgid "Cookie domain"
msgstr ""
@@ -6683,6 +6853,9 @@ msgstr ""
msgid "Could not find design."
msgstr ""
+msgid "Could not find iteration"
+msgstr ""
+
msgid "Could not remove the trigger."
msgstr ""
@@ -6719,6 +6892,12 @@ msgstr ""
msgid "Coverage"
msgstr ""
+msgid "Coverage Fuzzing"
+msgstr ""
+
+msgid "Crash State"
+msgstr ""
+
msgid "Create"
msgstr ""
@@ -6767,6 +6946,9 @@ msgstr ""
msgid "Create a personal access token on your account to pull or push via %{protocol}."
msgstr ""
+msgid "Create a project pre-populated with the necessary files to get you started quickly."
+msgstr ""
+
msgid "Create an account using:"
msgstr ""
@@ -6935,6 +7117,9 @@ msgstr ""
msgid "Creates branch '%{branch_name}' and a merge request to resolve this issue."
msgstr ""
+msgid "Creating"
+msgstr ""
+
msgid "Creating epic"
msgstr ""
@@ -6986,7 +7171,10 @@ msgstr ""
msgid "Current vulnerabilities count"
msgstr ""
-msgid "CurrentUser|Buy CI minutes"
+msgid "CurrentUser|Buy Pipeline minutes"
+msgstr ""
+
+msgid "CurrentUser|One of your groups is running out"
msgstr ""
msgid "CurrentUser|Profile"
@@ -7001,6 +7189,9 @@ msgstr ""
msgid "CurrentUser|Upgrade"
msgstr ""
+msgid "Custom Attributes"
+msgstr ""
+
msgid "Custom CI configuration path"
msgstr ""
@@ -7268,12 +7459,18 @@ msgstr ""
msgid "DAG"
msgstr ""
+msgid "DAG visualization requires at least 3 dependent jobs."
+msgstr ""
+
msgid "DNS"
msgstr ""
msgid "Dashboard"
msgstr ""
+msgid "Dashboard uid not found"
+msgstr ""
+
msgid "DashboardProjects|All"
msgstr ""
@@ -7298,6 +7495,9 @@ msgstr ""
msgid "Data is still calculating..."
msgstr ""
+msgid "Datasource name not found"
+msgstr ""
+
msgid "Date"
msgstr ""
@@ -7478,6 +7678,9 @@ msgstr ""
msgid "Delete this attachment"
msgstr ""
+msgid "Delete user list"
+msgstr ""
+
msgid "Delete variable"
msgstr ""
@@ -7814,6 +8017,9 @@ msgstr ""
msgid "Deploying to"
msgstr ""
+msgid "Deploying to AWS is easy with GitLab"
+msgstr ""
+
msgid "Deployment Frequency"
msgstr ""
@@ -8180,6 +8386,9 @@ msgstr ""
msgid "Dismissed on pipeline %{pipelineLink} at %{projectLink}"
msgstr ""
+msgid "Display alerts from all your monitoring tools directly within GitLab."
+msgstr ""
+
msgid "Display name"
msgstr ""
@@ -8237,9 +8446,6 @@ msgstr ""
msgid "Don't show again"
msgstr ""
-msgid "Don't worry, you can access this tour by clicking on the help icon in the top right corner and choose <strong>Learn GitLab</strong>."
-msgstr ""
-
msgid "Done"
msgstr ""
@@ -8366,6 +8572,9 @@ msgstr ""
msgid "Edit Release"
msgstr ""
+msgid "Edit Slack integration"
+msgstr ""
+
msgid "Edit Snippet"
msgstr ""
@@ -8405,6 +8614,9 @@ msgstr ""
msgid "Edit issues"
msgstr ""
+msgid "Edit iteration"
+msgstr ""
+
msgid "Edit public deploy key"
msgstr ""
@@ -8453,6 +8665,9 @@ msgstr ""
msgid "Email"
msgstr ""
+msgid "Email Notification"
+msgstr ""
+
msgid "Email address"
msgstr ""
@@ -8465,6 +8680,9 @@ msgstr ""
msgid "Email not verified. Please verify your email in Salesforce."
msgstr ""
+msgid "Email notification for unknown sign-ins"
+msgstr ""
+
msgid "Email patch"
msgstr ""
@@ -8696,6 +8914,9 @@ msgstr ""
msgid "Enforce DNS rebinding attack protection"
msgstr ""
+msgid "Enforce personal access token expiration"
+msgstr ""
+
msgid "Ensure connectivity is available from the GitLab server to the Prometheus server"
msgstr ""
@@ -8765,6 +8986,9 @@ msgstr ""
msgid "Enter the name of your application, and we'll return a unique %{type}."
msgstr ""
+msgid "Enter weights for storages for new repositories."
+msgstr ""
+
msgid "Enter your password to approve"
msgstr ""
@@ -8774,15 +8998,27 @@ msgstr ""
msgid "Environment does not have deployments"
msgstr ""
+msgid "Environment is required for Stages::MetricEndpointInserter"
+msgstr ""
+
+msgid "Environment is required for Stages::VariableEndpointInserter"
+msgstr ""
+
msgid "Environment scope"
msgstr ""
+msgid "Environment variables are applied to all project environments in this instance via the Runner. You can use environment variables for passwords, secret keys, etc. Make variables available to the running application by prepending the variable key with <code>K8S_SECRET_</code>. You can set variables to be:"
+msgstr ""
+
msgid "Environment variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. Additionally, they can be masked so they are hidden in job logs, though they must match certain regexp requirements to do so. You can use environment variables for passwords, secret keys, or whatever you want."
msgstr ""
msgid "Environment variables are configured by your administrator to be %{link_start}protected%{link_end} by default"
msgstr ""
+msgid "Environment variables on this GitLab instance are configured to be %{link_start}protected%{link_end} by default"
+msgstr ""
+
msgid "Environment:"
msgstr ""
@@ -9314,6 +9550,9 @@ msgstr ""
msgid "EventFilterBy|Filter by comments"
msgstr ""
+msgid "EventFilterBy|Filter by designs"
+msgstr ""
+
msgid "EventFilterBy|Filter by epic events"
msgstr ""
@@ -9446,15 +9685,15 @@ msgstr ""
msgid "Expand approvers"
msgstr ""
-msgid "Expand child epics"
-msgstr ""
-
msgid "Expand down"
msgstr ""
msgid "Expand dropdown"
msgstr ""
+msgid "Expand milestones"
+msgstr ""
+
msgid "Expand sidebar"
msgstr ""
@@ -9470,6 +9709,9 @@ msgstr ""
msgid "Expiration date"
msgstr ""
+msgid "Expiration not enforced"
+msgstr ""
+
msgid "Expiration policy for the Container Registry is a perfect solution for keeping the Registry space down while still enjoying the full power of GitLab CI/CD."
msgstr ""
@@ -9590,6 +9832,9 @@ msgstr ""
msgid "Failed Jobs"
msgstr ""
+msgid "Failed on"
+msgstr ""
+
msgid "Failed to add a Zoom meeting"
msgstr ""
@@ -9650,13 +9895,19 @@ msgstr ""
msgid "Failed to install."
msgstr ""
+msgid "Failed to load assignees. Please try again."
+msgstr ""
+
+msgid "Failed to load authors. Please try again."
+msgstr ""
+
msgid "Failed to load emoji list."
msgstr ""
msgid "Failed to load error details from Sentry."
msgstr ""
-msgid "Failed to load errors from Sentry. Error message: %{errorMessage}"
+msgid "Failed to load errors from Sentry."
msgstr ""
msgid "Failed to load group activity metrics. Please try again."
@@ -9737,6 +9988,9 @@ msgstr ""
msgid "Failed to set due date because the date format is invalid."
msgstr ""
+msgid "Failed to set iteration on this issue. Please try again."
+msgstr ""
+
msgid "Failed to signing using smartcard authentication"
msgstr ""
@@ -9959,13 +10213,10 @@ msgstr ""
msgid "FeatureFlags|Target environments"
msgstr ""
-msgid "FeatureFlags|There are no active feature flags"
-msgstr ""
-
-msgid "FeatureFlags|There are no inactive feature flags"
+msgid "FeatureFlags|There was an error fetching the feature flags."
msgstr ""
-msgid "FeatureFlags|There was an error fetching the feature flags."
+msgid "FeatureFlags|There was an error retrieving user lists"
msgstr ""
msgid "FeatureFlags|Try again in a few moments or contact your support team."
@@ -9977,9 +10228,18 @@ msgstr ""
msgid "FeatureFlag|Delete strategy"
msgstr ""
+msgid "FeatureFlag|List"
+msgstr ""
+
msgid "FeatureFlag|Percentage"
msgstr ""
+msgid "FeatureFlag|Select a user list"
+msgstr ""
+
+msgid "FeatureFlag|There are no configured user lists"
+msgstr ""
+
msgid "FeatureFlag|Type"
msgstr ""
@@ -10136,6 +10396,12 @@ msgstr ""
msgid "Filter..."
msgstr ""
+msgid "Find File"
+msgstr ""
+
+msgid "Find bugs in your code with coverage-guided fuzzing"
+msgstr ""
+
msgid "Find by path"
msgstr ""
@@ -10400,6 +10666,9 @@ msgstr ""
msgid "Geo allows you to replicate your GitLab instance to other geographical locations."
msgstr ""
+msgid "Geo nodes are paused using a command run on the node"
+msgstr ""
+
msgid "GeoNodeStatusEvent|%{timeAgoStr} (%{pendingEvents} events)"
msgstr ""
@@ -10484,6 +10753,9 @@ msgstr ""
msgid "GeoNodes|Not checksummed"
msgstr ""
+msgid "GeoNodes|Package files"
+msgstr ""
+
msgid "GeoNodes|Pausing replication stops the sync process. Are you sure?"
msgstr ""
@@ -10502,6 +10774,9 @@ msgstr ""
msgid "GeoNodes|Replication slots"
msgstr ""
+msgid "GeoNodes|Replication status"
+msgstr ""
+
msgid "GeoNodes|Repositories"
msgstr ""
@@ -10712,6 +10987,9 @@ msgstr ""
msgid "Geo|Synchronization failed - %{error}"
msgstr ""
+msgid "Geo|Synchronization of %{itemTitle} is disabled."
+msgstr ""
+
msgid "Geo|The database is currently %{db_lag} behind the primary node."
msgstr ""
@@ -11024,6 +11302,9 @@ msgstr ""
msgid "Go to %{link_to_google_takeout}."
msgstr ""
+msgid "Go to <strong>Issues</strong> > <strong>Boards</strong> to access your personalized learning issue board."
+msgstr ""
+
msgid "Go to Pipelines"
msgstr ""
@@ -11123,9 +11404,6 @@ msgstr ""
msgid "Go to your snippets"
msgstr ""
-msgid "Golden Tanuki"
-msgstr ""
-
msgid "Google Cloud Platform"
msgstr ""
@@ -11147,6 +11425,9 @@ msgstr ""
msgid "Grafana URL"
msgstr ""
+msgid "Grafana response contains invalid json"
+msgstr ""
+
msgid "GrafanaIntegration|API Token"
msgstr ""
@@ -11195,9 +11476,6 @@ msgstr ""
msgid "Group %{group_name} was successfully created."
msgstr ""
-msgid "Group '%{group_name}' is being imported."
-msgstr ""
-
msgid "Group Audit Events"
msgstr ""
@@ -11234,9 +11512,6 @@ msgstr ""
msgid "Group by:"
msgstr ""
-msgid "Group could not be imported: %{errors}"
-msgstr ""
-
msgid "Group description"
msgstr ""
@@ -11264,9 +11539,15 @@ msgstr ""
msgid "Group has not been marked for deletion"
msgstr ""
+msgid "Group import could not be scheduled"
+msgstr ""
+
msgid "Group info:"
msgstr ""
+msgid "Group is required when cluster_type is :group"
+msgstr ""
+
msgid "Group maintainers can register group runners in the %{link}"
msgstr ""
@@ -11330,6 +11611,24 @@ msgstr ""
msgid "GroupActivyMetrics|Recent activity (last 90 days)"
msgstr ""
+msgid "GroupImport|Failed to import group."
+msgstr ""
+
+msgid "GroupImport|Group '%{group_name}' is being imported."
+msgstr ""
+
+msgid "GroupImport|Group could not be imported: %{errors}"
+msgstr ""
+
+msgid "GroupImport|Please wait while we import the group for you. Refresh at will."
+msgstr ""
+
+msgid "GroupImport|The group was successfully imported."
+msgstr ""
+
+msgid "GroupImport|Unable to process group import file"
+msgstr ""
+
msgid "GroupRoadmap|%{dateWord} – No end date"
msgstr ""
@@ -11597,6 +11896,9 @@ msgstr ""
msgid "Groups (%{groups})"
msgstr ""
+msgid "Groups and projects"
+msgstr ""
+
msgid "Groups can also be nested by creating %{subgroup_docs_link_start}subgroups%{subgroup_docs_link_end}."
msgstr ""
@@ -11642,6 +11944,33 @@ msgstr ""
msgid "GroupsEmptyState|You can manage your group member’s permissions and access to each project in the group."
msgstr ""
+msgid "GroupsNew|Contact an administrator to enable options for importing your group."
+msgstr ""
+
+msgid "GroupsNew|Create"
+msgstr ""
+
+msgid "GroupsNew|Create group"
+msgstr ""
+
+msgid "GroupsNew|GitLab group export"
+msgstr ""
+
+msgid "GroupsNew|Import"
+msgstr ""
+
+msgid "GroupsNew|Import group"
+msgstr ""
+
+msgid "GroupsNew|My Awesome Group"
+msgstr ""
+
+msgid "GroupsNew|No import options available"
+msgstr ""
+
+msgid "GroupsNew|To copy a GitLab group between installations, navigate to the group settings page for the original installation, generate an export file, and upload it here."
+msgstr ""
+
msgid "GroupsTree|Are you sure you want to leave the \"%{fullName}\" group?"
msgstr ""
@@ -11744,6 +12073,9 @@ msgstr ""
msgid "Helps reduce request volume for protected paths"
msgstr ""
+msgid "Here are all your projects in your group, including the one you just created. To start, let’s take a look at your personalized learning project which will help you learn about GitLab at your own pace."
+msgstr ""
+
msgid "Here you will find recent merge request activity"
msgstr ""
@@ -11762,6 +12094,9 @@ msgstr[3] ""
msgstr[4] ""
msgstr[5] ""
+msgid "Hide details"
+msgstr ""
+
msgid "Hide file browser"
msgstr ""
@@ -11771,6 +12106,9 @@ msgstr ""
msgid "Hide host keys manual input"
msgstr ""
+msgid "Hide list"
+msgstr ""
+
msgid "Hide marketing-related entries from help"
msgstr ""
@@ -11795,9 +12133,6 @@ msgstr[5] ""
msgid "Hide values"
msgstr ""
-msgid "Hiding all labels"
-msgstr ""
-
msgid "High or unknown vulnerabilities present"
msgstr ""
@@ -11882,6 +12217,9 @@ msgstr ""
msgid "ID:"
msgstr ""
+msgid "IDE"
+msgstr ""
+
msgid "IDE|Allow live previews of JavaScript projects in the Web IDE using CodeSandbox Live Preview."
msgstr ""
@@ -11972,10 +12310,10 @@ msgstr ""
msgid "If enabled"
msgstr ""
-msgid "If enabled, access to projects will be validated on an external service using their classification label."
+msgid "If enabled, GitLab will handle Object Storage replication using Geo. %{linkStart}More information%{linkEnd}"
msgstr ""
-msgid "If enabled, and if object storage is enabled, GitLab will handle Object Storage replication using Geo"
+msgid "If enabled, access to projects will be validated on an external service using their classification label."
msgstr ""
msgid "If there is no previous license or if the previous license has expired, some GitLab functionality will be blocked until a new, valid license is uploaded."
@@ -12341,6 +12679,9 @@ msgstr[3] ""
msgstr[4] ""
msgstr[5] ""
+msgid "Instance Configuration"
+msgstr ""
+
msgid "Instance Statistics visibility"
msgstr ""
@@ -12362,9 +12703,6 @@ msgstr ""
msgid "Integrations"
msgstr ""
-msgid "Integrations allow you to integrate GitLab with other applications"
-msgstr ""
-
msgid "Integrations|All details"
msgstr ""
@@ -12476,6 +12814,9 @@ msgstr ""
msgid "Invalid query"
msgstr ""
+msgid "Invalid repository bundle for snippet with id %{snippet_id}"
+msgstr ""
+
msgid "Invalid repository path"
msgstr ""
@@ -12494,6 +12835,9 @@ msgstr ""
msgid "Invalid two-factor code."
msgstr ""
+msgid "Invalid yaml"
+msgstr ""
+
msgid "Invitation"
msgstr ""
@@ -12713,15 +13057,24 @@ msgstr ""
msgid "It seems like the Dependency Scanning job ran successfully, but no dependencies have been detected in your project."
msgstr ""
+msgid "It seems that there is currently no available data for code coverage"
+msgstr ""
+
msgid "It's you"
msgstr ""
+msgid "Iteration"
+msgstr ""
+
msgid "Iteration changed to"
msgstr ""
msgid "Iteration removed"
msgstr ""
+msgid "Iteration updated"
+msgstr ""
+
msgid "Iterations"
msgstr ""
@@ -12752,12 +13105,18 @@ msgstr ""
msgid "January"
msgstr ""
+msgid "Jira Issues"
+msgstr ""
+
msgid "Jira import is already running."
msgstr ""
msgid "Jira integration not configured."
msgstr ""
+msgid "Jira project key is not configured"
+msgstr ""
+
msgid "Jira project: %{importProject}"
msgstr ""
@@ -12938,6 +13297,9 @@ msgstr ""
msgid "Keep divergent refs"
msgstr ""
+msgid "Kerberos access denied"
+msgstr ""
+
msgid "Key"
msgstr ""
@@ -12947,10 +13309,13 @@ msgstr ""
msgid "Key: %{key}"
msgstr ""
-msgid "Keyboard Shortcuts"
+msgid "Keyboard shortcuts"
+msgstr ""
+
+msgid "Keys"
msgstr ""
-msgid "Keyboard shortcuts"
+msgid "Ki"
msgstr ""
msgid "Kubernetes"
@@ -12992,6 +13357,9 @@ msgstr ""
msgid "LDAP"
msgstr ""
+msgid "LDAP Synchronization"
+msgstr ""
+
msgid "LDAP settings"
msgstr ""
@@ -13220,12 +13588,18 @@ msgstr ""
msgid "Learn more about custom project templates"
msgstr ""
+msgid "Learn more about deploying to AWS"
+msgstr ""
+
msgid "Learn more about deploying to a cluster"
msgstr ""
msgid "Learn more about group-level project templates"
msgstr ""
+msgid "Learn more about job dependencies"
+msgstr ""
+
msgid "Learn more about signing commits"
msgstr ""
@@ -13277,9 +13651,21 @@ msgstr ""
msgid "License History"
msgstr ""
+msgid "License ID:"
+msgstr ""
+
+msgid "License URL"
+msgstr ""
+
msgid "License-Check"
msgstr ""
+msgid "LicenseCompliance|%{docLinkStart}License Approvals%{docLinkEnd} are active"
+msgstr ""
+
+msgid "LicenseCompliance|%{docLinkStart}License Approvals%{docLinkEnd} are inactive"
+msgstr ""
+
msgid "LicenseCompliance|Add a license"
msgstr ""
@@ -13301,9 +13687,15 @@ msgstr ""
msgid "LicenseCompliance|Deny"
msgstr ""
+msgid "LicenseCompliance|Learn more about %{linkStart}License Approvals%{linkEnd}"
+msgstr ""
+
msgid "LicenseCompliance|License"
msgstr ""
+msgid "LicenseCompliance|License Approvals"
+msgstr ""
+
msgid "LicenseCompliance|License Compliance detected %d license and policy violation for the source branch only; approval required"
msgid_plural "LicenseCompliance|License Compliance detected %d licenses and policy violations for the source branch only; approval required"
msgstr[0] ""
@@ -13544,6 +13936,9 @@ msgstr ""
msgid "List your Bitbucket Server repositories"
msgstr ""
+msgid "Lists"
+msgstr ""
+
msgid "Live preview"
msgstr ""
@@ -13727,6 +14122,9 @@ msgstr ""
msgid "Manage project labels"
msgstr ""
+msgid "Manage storage usage"
+msgstr ""
+
msgid "Manage two-factor authentication"
msgstr ""
@@ -13742,6 +14140,9 @@ msgstr ""
msgid "Manifest file import"
msgstr ""
+msgid "Manifest import"
+msgstr ""
+
msgid "Manual job"
msgstr ""
@@ -13889,6 +14290,12 @@ msgstr ""
msgid "Maximum field length"
msgstr ""
+msgid "Maximum file size is 2MB. Please select a smaller file."
+msgstr ""
+
+msgid "Maximum import size (MB)"
+msgstr ""
+
msgid "Maximum job timeout"
msgstr ""
@@ -13928,6 +14335,9 @@ msgstr ""
msgid "Maximum size of Elasticsearch bulk indexing requests."
msgstr ""
+msgid "Maximum size of import files."
+msgstr ""
+
msgid "Maximum size of individual attachments in comments."
msgstr ""
@@ -13979,6 +14389,9 @@ msgstr ""
msgid "Merge (when the pipeline succeeds)"
msgstr ""
+msgid "Merge Conflicts"
+msgstr ""
+
msgid "Merge Request"
msgstr ""
@@ -14105,6 +14518,9 @@ msgstr ""
msgid "MergeRequests|Squash task canceled: another squash is already in progress."
msgstr ""
+msgid "MergeRequests|This project does not allow squashing commits when merge requests are accepted."
+msgstr ""
+
msgid "MergeRequests|Thread stays resolved"
msgstr ""
@@ -14258,6 +14674,12 @@ msgstr ""
msgid "MetricsSettings|Add a button to the metrics dashboard linking directly to your existing external dashboard."
msgstr ""
+msgid "MetricsSettings|Choose whether to display dashboard metrics in UTC or the user's local timezone."
+msgstr ""
+
+msgid "MetricsSettings|Dashboard timezone"
+msgstr ""
+
msgid "MetricsSettings|External dashboard URL"
msgstr ""
@@ -14267,6 +14689,12 @@ msgstr ""
msgid "MetricsSettings|Metrics Dashboard"
msgstr ""
+msgid "MetricsSettings|UTC (Coordinated Universal Time)"
+msgstr ""
+
+msgid "MetricsSettings|User's local timezone"
+msgstr ""
+
msgid "Metrics|Add metric"
msgstr ""
@@ -14282,6 +14710,9 @@ msgstr ""
msgid "Metrics|Create metric"
msgstr ""
+msgid "Metrics|Current"
+msgstr ""
+
msgid "Metrics|Delete metric"
msgstr ""
@@ -14336,6 +14767,9 @@ msgstr ""
msgid "Metrics|Max"
msgstr ""
+msgid "Metrics|Min"
+msgstr ""
+
msgid "Metrics|Must be a valid PromQL query."
msgstr ""
@@ -14351,6 +14785,9 @@ msgstr ""
msgid "Metrics|Refresh dashboard"
msgstr ""
+msgid "Metrics|Select a value"
+msgstr ""
+
msgid "Metrics|Star dashboard"
msgstr ""
@@ -14369,12 +14806,18 @@ msgstr ""
msgid "Metrics|There was an error getting annotations information."
msgstr ""
+msgid "Metrics|There was an error getting dashboard validation warnings information."
+msgstr ""
+
msgid "Metrics|There was an error getting deployment information."
msgstr ""
msgid "Metrics|There was an error getting environments information."
msgstr ""
+msgid "Metrics|There was an error getting options for variable \"%{name}\"."
+msgstr ""
+
msgid "Metrics|There was an error trying to validate your query"
msgstr ""
@@ -14417,6 +14860,9 @@ msgstr ""
msgid "Metrics|You're about to permanently delete this metric. This cannot be undone."
msgstr ""
+msgid "Metrics|Your dashboard schema is invalid. Edit the dashboard to correct the YAML schema."
+msgstr ""
+
msgid "Metrics|e.g. HTTP requests"
msgstr ""
@@ -14432,12 +14878,18 @@ msgstr ""
msgid "Metrics|e.g. req/sec"
msgstr ""
+msgid "Mi"
+msgstr ""
+
msgid "Microsoft Azure"
msgstr ""
msgid "Middleman project with Static Site Editor support"
msgstr ""
+msgid "Migrate your data from an external source like GitHub, Bitbucket, or another instance of GitLab."
+msgstr ""
+
msgid "Migrated %{success_count}/%{total_count} files."
msgstr ""
@@ -14576,6 +15028,9 @@ msgstr ""
msgid "Minimum capacity to be available before we schedule more mirrors preemptively."
msgstr ""
+msgid "Minimum interval in days"
+msgstr ""
+
msgid "Minimum length is %{minimum_password_length} characters"
msgstr ""
@@ -14762,6 +15217,12 @@ msgstr ""
msgid "Multiple uploaders found: %{uploader_types}"
msgstr ""
+msgid "Must match with the %{codeStart}external_url%{codeEnd} in %{codeStart}/etc/gitlab/gitlab.rb%{codeEnd}."
+msgstr ""
+
+msgid "Must match with the %{codeStart}geo_node_name%{codeEnd} in %{codeStart}/etc/gitlab/gitlab.rb%{codeEnd}. %{linkStart}More information%{linkEnd}"
+msgstr ""
+
msgid "My Awesome Group"
msgstr ""
@@ -14876,10 +15337,16 @@ msgstr ""
msgid "New Application"
msgstr ""
+msgid "New Branch"
+msgstr ""
+
+msgid "New Deploy Key"
+msgstr ""
+
msgid "New Environment"
msgstr ""
-msgid "New Geo Node"
+msgid "New File"
msgstr ""
msgid "New Group"
@@ -14900,15 +15367,15 @@ msgstr[3] ""
msgstr[4] ""
msgstr[5] ""
-msgid "New Iteration"
-msgstr ""
-
msgid "New Jira import"
msgstr ""
msgid "New Label"
msgstr ""
+msgid "New Merge Request"
+msgstr ""
+
msgid "New Milestone"
msgstr ""
@@ -14927,6 +15394,9 @@ msgstr ""
msgid "New Snippet"
msgstr ""
+msgid "New User"
+msgstr ""
+
msgid "New branch"
msgstr ""
@@ -14969,6 +15439,9 @@ msgstr ""
msgid "New issue title"
msgstr ""
+msgid "New iteration"
+msgstr ""
+
msgid "New iteration created"
msgstr ""
@@ -15146,6 +15619,12 @@ msgstr ""
msgid "No grouping"
msgstr ""
+msgid "No iteration"
+msgstr ""
+
+msgid "No iterations to show"
+msgstr ""
+
msgid "No job log"
msgstr ""
@@ -15188,6 +15667,9 @@ msgstr ""
msgid "No other labels with such name or description"
msgstr ""
+msgid "No panels matching properties %{opts}"
+msgstr ""
+
msgid "No parent group"
msgstr ""
@@ -15221,6 +15703,9 @@ msgstr ""
msgid "No schedules"
msgstr ""
+msgid "No stack trace for this error"
+msgstr ""
+
msgid "No starrers matched your search"
msgstr ""
@@ -15233,7 +15718,7 @@ msgstr ""
msgid "No test coverage"
msgstr ""
-msgid "No thanks, don't show this again"
+msgid "No thanks"
msgstr ""
msgid "No vulnerabilities present"
@@ -15248,9 +15733,6 @@ msgstr ""
msgid "No, directly import the existing email addresses and usernames."
msgstr ""
-msgid "No, not interested right now"
-msgstr ""
-
msgid "No. of commits"
msgstr ""
@@ -15296,9 +15778,6 @@ msgstr ""
msgid "Not found."
msgstr ""
-msgid "Not helpful"
-msgstr ""
-
msgid "Not now"
msgstr ""
@@ -15455,6 +15934,9 @@ msgstr ""
msgid "Notifications on"
msgstr ""
+msgid "Notify users by email when sign-in location is not recognized"
+msgstr ""
+
msgid "Nov"
msgstr ""
@@ -15521,9 +16003,6 @@ msgstr ""
msgid "Oh no!"
msgstr ""
-msgid "Ok let's go"
-msgstr ""
-
msgid "Oldest first"
msgstr ""
@@ -15536,16 +16015,49 @@ msgstr ""
msgid "On track"
msgstr ""
+msgid "OnDemandScans|Attached branch"
+msgstr ""
+
+msgid "OnDemandScans|Attached branch is where the scan job runs."
+msgstr ""
+
+msgid "OnDemandScans|Could not run the scan. Please try again."
+msgstr ""
+
msgid "OnDemandScans|Create new DAST scan"
msgstr ""
+msgid "OnDemandScans|DAST will scan the target URL and any discovered sub URLs."
+msgstr ""
+
+msgid "OnDemandScans|New on-demand DAST scan"
+msgstr ""
+
msgid "OnDemandScans|On-demand Scans"
msgstr ""
+msgid "OnDemandScans|On-demand scans run outside the DevOps cycle and find vulnerabilities in your projects. %{learnMoreLinkStart}Learn more%{learnMoreLinkEnd}"
+msgstr ""
+
+msgid "OnDemandScans|Only a passive scan can be performed on demand."
+msgstr ""
+
+msgid "OnDemandScans|Passive DAST Scan"
+msgstr ""
+
+msgid "OnDemandScans|Please enter a valid URL format, ex: http://www.example.com/home"
+msgstr ""
+
+msgid "OnDemandScans|Run this scan"
+msgstr ""
+
+msgid "OnDemandScans|Scan mode"
+msgstr ""
+
msgid "OnDemandScans|Schedule or run scans immediately against target sites. Currently available on-demand scan type: DAST. %{helpLinkStart}More information%{helpLinkEnd}"
msgstr ""
-msgid "Onboarding"
+msgid "OnDemandScans|Target URL"
msgstr ""
msgid "Once imported, repositories can be mirrored over SSH. Read more %{link_start}here%{link_end}."
@@ -15575,6 +16087,9 @@ msgstr ""
msgid "One or more of you personal access tokens were revoked"
msgstr ""
+msgid "One or more of your %{provider} projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
+msgstr ""
+
msgid "One or more of your Bitbucket projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
msgstr ""
@@ -16076,6 +16591,9 @@ msgstr ""
msgid "Parent epic is not present."
msgstr ""
+msgid "Parsing error for param :embed_json. %{message}"
+msgstr ""
+
msgid "Part of merge request changes"
msgstr ""
@@ -16088,6 +16606,9 @@ msgstr ""
msgid "Passed"
msgstr ""
+msgid "Passed on"
+msgstr ""
+
msgid "Password"
msgstr ""
@@ -16172,6 +16693,9 @@ msgstr ""
msgid "Perform common operations on GitLab project"
msgstr ""
+msgid "Performance and resource management"
+msgstr ""
+
msgid "Performance optimization"
msgstr ""
@@ -16379,7 +16903,7 @@ msgstr ""
msgid "Pipelines|Something went wrong while cleaning runners cache."
msgstr ""
-msgid "Pipelines|There are currently no %{scope} pipelines."
+msgid "Pipelines|There are currently no finished pipelines."
msgstr ""
msgid "Pipelines|There are currently no pipelines."
@@ -16490,6 +17014,9 @@ msgstr ""
msgid "Pipeline|Stop pipeline #%{pipelineId}?"
msgstr ""
+msgid "Pipeline|Tag name"
+msgstr ""
+
msgid "Pipeline|Trigger author"
msgstr ""
@@ -16571,12 +17098,18 @@ msgstr ""
msgid "Please check your email (%{email}) to verify that you own this address and unlock the power of CI/CD. Didn't receive it? %{resend_link}. Wrong email address? %{update_link}."
msgstr ""
+msgid "Please choose a file"
+msgstr ""
+
msgid "Please choose a group URL with no special characters."
msgstr ""
msgid "Please complete your profile with email address"
msgstr ""
+msgid "Please convert %{linkStart}them to Git%{linkEnd}, and go through the %{linkToImportFlow} again."
+msgstr ""
+
msgid "Please convert them to %{link_to_git}, and go through the %{link_to_import_flow} again."
msgstr ""
@@ -16634,6 +17167,9 @@ msgstr ""
msgid "Please provide a valid email address."
msgstr ""
+msgid "Please provide attributes to update"
+msgstr ""
+
msgid "Please refer to <a href=\"%{docs_url}\">%{docs_url}</a>"
msgstr ""
@@ -17369,6 +17905,9 @@ msgstr ""
msgid "Project has too many %{label_for_message} to search"
msgstr ""
+msgid "Project is required when cluster_type is :project"
+msgstr ""
+
msgid "Project members"
msgstr ""
@@ -17486,7 +18025,40 @@ msgstr ""
msgid "ProjectService|Comment"
msgstr ""
-msgid "ProjectService|Comment will be posted on each event"
+msgid "ProjectService|Event will be triggered by a push to the repository"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a commit is created/updated"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a confidential issue is created/updated/closed"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a deployment finishes"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a merge request is created/updated/merged"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a new tag is pushed to the repository"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a new, unique alert is recorded"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a pipeline status changes"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a wiki page is created/updated"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when an issue is created/updated/closed"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when someone adds a comment"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when someone adds a comment on a confidential issue"
msgstr ""
msgid "ProjectService|Perform common operations on GitLab project: %{project_name}"
@@ -17537,6 +18109,9 @@ msgstr ""
msgid "ProjectSettings|Enable 'Delete source branch' option by default"
msgstr ""
+msgid "ProjectSettings|Enable merge trains and pipelines for merged results"
+msgstr ""
+
msgid "ProjectSettings|Every merge creates a merge commit"
msgstr ""
@@ -17567,7 +18142,7 @@ msgstr ""
msgid "ProjectSettings|Forks"
msgstr ""
-msgid "ProjectSettings|Git Large File Storage"
+msgid "ProjectSettings|Git Large File Storage (LFS)"
msgstr ""
msgid "ProjectSettings|Internal"
@@ -17603,9 +18178,6 @@ msgstr ""
msgid "ProjectSettings|Merge options"
msgstr ""
-msgid "ProjectSettings|Merge pipelines will try to validate the post-merge result prior to merging"
-msgstr ""
-
msgid "ProjectSettings|Merge requests"
msgstr ""
@@ -17633,6 +18205,9 @@ msgstr ""
msgid "ProjectSettings|Pipelines"
msgstr ""
+msgid "ProjectSettings|Pipelines for merge requests must be enabled in the CI/CD configuration file, or pipelines could be unresolvable or dropped"
+msgstr ""
+
msgid "ProjectSettings|Pipelines must succeed"
msgstr ""
@@ -17660,6 +18235,9 @@ msgstr ""
msgid "ProjectSettings|Show link to create/view merge request when pushing from the command line"
msgstr ""
+msgid "ProjectSettings|Skipped pipelines are considered successful"
+msgstr ""
+
msgid "ProjectSettings|Snippets"
msgstr ""
@@ -17675,6 +18253,9 @@ msgstr ""
msgid "ProjectSettings|These checks must pass before merge requests can be merged"
msgstr ""
+msgid "ProjectSettings|This introduces the risk of merging changes that will not pass the pipeline."
+msgstr ""
+
msgid "ProjectSettings|This setting is applied on the server level and can be overridden by an admin."
msgstr ""
@@ -17852,15 +18433,27 @@ msgstr ""
msgid "ProjectsNew|Blank project"
msgstr ""
+msgid "ProjectsNew|Connect your external repository to GitLab CI/CD."
+msgstr ""
+
msgid "ProjectsNew|Contact an administrator to enable options for importing your project."
msgstr ""
msgid "ProjectsNew|Create"
msgstr ""
+msgid "ProjectsNew|Create a blank project to house your files, plan your work, and collaborate on code, among other things."
+msgstr ""
+
+msgid "ProjectsNew|Create blank project"
+msgstr ""
+
msgid "ProjectsNew|Create from template"
msgstr ""
+msgid "ProjectsNew|Create new project"
+msgstr ""
+
msgid "ProjectsNew|Creating project & repository."
msgstr ""
@@ -17885,6 +18478,9 @@ msgstr ""
msgid "ProjectsNew|Project description %{tag_start}(optional)%{tag_end}"
msgstr ""
+msgid "ProjectsNew|Run CI/CD for external repository"
+msgstr ""
+
msgid "ProjectsNew|Template"
msgstr ""
@@ -18146,6 +18742,9 @@ msgstr ""
msgid "Protected Branch"
msgstr ""
+msgid "Protected Branches"
+msgstr ""
+
msgid "Protected Environment"
msgstr ""
@@ -18158,6 +18757,9 @@ msgstr ""
msgid "Protected Tag"
msgstr ""
+msgid "Protected Tags"
+msgstr ""
+
msgid "Protected branches"
msgstr ""
@@ -18257,6 +18859,9 @@ msgstr ""
msgid "Provider"
msgstr ""
+msgid "Proxy support for this API is not available currently"
+msgstr ""
+
msgid "Pseudonymizer data collection"
msgstr ""
@@ -18281,6 +18886,9 @@ msgstr ""
msgid "Publish to status page"
msgstr ""
+msgid "Published on status page"
+msgstr ""
+
msgid "Publishes this issue to the associated status page."
msgstr ""
@@ -18455,6 +19063,9 @@ msgstr ""
msgid "Recipe"
msgstr ""
+msgid "Reconfigure"
+msgstr ""
+
msgid "Recover hidden stage"
msgstr ""
@@ -18545,6 +19156,9 @@ msgstr ""
msgid "Registration|Your profile"
msgstr ""
+msgid "Regulate approvals by authors/committers, based on compliance frameworks. Can be changed only at the instance level."
+msgstr ""
+
msgid "Rejected (closed)"
msgstr ""
@@ -18599,15 +19213,24 @@ msgstr ""
msgid "ReleaseAssetLinkType|Image"
msgstr ""
+msgid "ReleaseAssetLinkType|Images"
+msgstr ""
+
msgid "ReleaseAssetLinkType|Other"
msgstr ""
msgid "ReleaseAssetLinkType|Package"
msgstr ""
+msgid "ReleaseAssetLinkType|Packages"
+msgstr ""
+
msgid "ReleaseAssetLinkType|Runbook"
msgstr ""
+msgid "ReleaseAssetLinkType|Runbooks"
+msgstr ""
+
msgid "Releases"
msgstr ""
@@ -18620,6 +19243,9 @@ msgstr ""
msgid "Releases documentation"
msgstr ""
+msgid "Releases|New Release"
+msgstr ""
+
msgid "Release|Something went wrong while getting the release details"
msgstr ""
@@ -18692,6 +19318,9 @@ msgstr ""
msgid "Remove fork relationship"
msgstr ""
+msgid "Remove from batch"
+msgstr ""
+
msgid "Remove from board"
msgstr ""
@@ -18881,6 +19510,12 @@ msgstr ""
msgid "Replication"
msgstr ""
+msgid "Replication enabled"
+msgstr ""
+
+msgid "Replication paused"
+msgstr ""
+
msgid "Reply by email"
msgstr ""
@@ -18908,15 +19543,15 @@ msgstr ""
msgid "Reported %{timeAgo} by %{reportedBy}"
msgstr ""
-msgid "Reported Resource Changes: %{addNum} to add, %{changeNum} to change, %{deleteNum} to delete"
-msgstr ""
-
msgid "Reporter"
msgstr ""
msgid "Reporting"
msgstr ""
+msgid "Reports"
+msgstr ""
+
msgid "Reports|%{combinedString} and %{resolvedString}"
msgstr ""
@@ -18959,6 +19594,9 @@ msgstr ""
msgid "Reports|Failure"
msgstr ""
+msgid "Reports|Identifier"
+msgstr ""
+
msgid "Reports|Metrics reports are loading"
msgstr ""
@@ -18971,6 +19609,9 @@ msgstr ""
msgid "Reports|Metrics reports failed loading results"
msgstr ""
+msgid "Reports|Scanner"
+msgstr ""
+
msgid "Reports|Severity"
msgstr ""
@@ -19019,15 +19660,33 @@ msgstr ""
msgid "Repository cleanup has started. You will receive an email once the cleanup operation is complete."
msgstr ""
+msgid "Repository files count over the limit"
+msgstr ""
+
+msgid "Repository has an invalid default branch name."
+msgstr ""
+
+msgid "Repository has more than one branch."
+msgstr ""
+
msgid "Repository has no locks."
msgstr ""
+msgid "Repository has tags."
+msgstr ""
+
msgid "Repository maintenance"
msgstr ""
msgid "Repository mirroring"
msgstr ""
+msgid "Repository must contain at least 1 file."
+msgstr ""
+
+msgid "Repository size is above the limit."
+msgstr ""
+
msgid "Repository static objects"
msgstr ""
@@ -19037,7 +19696,7 @@ msgstr ""
msgid "Repository sync capacity"
msgstr ""
-msgid "Repository: %{counter_repositories} / Wikis: %{counter_wikis} / Build Artifacts: %{counter_build_artifacts} / LFS: %{counter_lfs_objects}"
+msgid "Repository: %{counter_repositories} / Wikis: %{counter_wikis} / Build Artifacts: %{counter_build_artifacts} / LFS: %{counter_lfs_objects} / Snippets: %{counter_snippets}"
msgstr ""
msgid "RepositorySettingsAccessLevel|Select"
@@ -19049,6 +19708,9 @@ msgstr ""
msgid "Request Headers"
msgstr ""
+msgid "Request details"
+msgstr ""
+
msgid "Request parameter %{param} is missing."
msgstr ""
@@ -19163,6 +19825,9 @@ msgstr ""
msgid "Reset template"
msgstr ""
+msgid "Reset to project defaults"
+msgstr ""
+
msgid "Resetting the authorization key for this project will require updating the authorization key in every alert source it is enabled in."
msgstr ""
@@ -19259,9 +19924,6 @@ msgstr ""
msgid "Resume"
msgstr ""
-msgid "Resume replication"
-msgstr ""
-
msgid "Resync"
msgstr ""
@@ -19928,6 +20590,9 @@ msgstr ""
msgid "SecurityReports|False positive"
msgstr ""
+msgid "SecurityReports|Group Security Dashboard"
+msgstr ""
+
msgid "SecurityReports|Hide dismissed"
msgstr ""
@@ -19982,10 +20647,13 @@ msgstr ""
msgid "SecurityReports|Remove project from dashboard"
msgstr ""
-msgid "SecurityReports|Report type"
+msgid "SecurityReports|Return to dashboard"
msgstr ""
-msgid "SecurityReports|Return to dashboard"
+msgid "SecurityReports|Scan details"
+msgstr ""
+
+msgid "SecurityReports|Scanner type"
msgstr ""
msgid "SecurityReports|Security Dashboard"
@@ -20042,6 +20710,9 @@ msgstr ""
msgid "SecurityReports|There was an error while generating the report."
msgstr ""
+msgid "SecurityReports|Unable to add %{invalidProjectsMessage}"
+msgstr ""
+
msgid "SecurityReports|Unable to add %{invalidProjects}"
msgstr ""
@@ -20156,6 +20827,9 @@ msgstr ""
msgid "Select due date"
msgstr ""
+msgid "Select file"
+msgstr ""
+
msgid "Select group or project"
msgstr ""
@@ -20219,9 +20893,6 @@ msgstr ""
msgid "Select the branch you want to set as the default for this project. All merge requests and commits will automatically be made against this branch unless you specify a different one."
msgstr ""
-msgid "Select the configured storage available for new repositories to be placed on."
-msgstr ""
-
msgid "Select the custom project template source group."
msgstr ""
@@ -20414,12 +21085,6 @@ msgstr ""
msgid "Service Desk is enabled but not yet active"
msgstr ""
-msgid "Service Desk is off"
-msgstr ""
-
-msgid "Service Desk is on"
-msgstr ""
-
msgid "Service Templates"
msgstr ""
@@ -20507,6 +21172,15 @@ msgstr ""
msgid "Set the milestone to %{milestone_reference}."
msgstr ""
+msgid "Set the number of concurrent requests this secondary node will make to the primary node while backfilling."
+msgstr ""
+
+msgid "Set the synchronization and verification capacity for the secondary node."
+msgstr ""
+
+msgid "Set the timeout in seconds to send a secondary node status to the primary and IPs allowed for the secondary nodes."
+msgstr ""
+
msgid "Set time estimate"
msgstr ""
@@ -20546,6 +21220,9 @@ msgstr ""
msgid "Set weight to %{weight}."
msgstr ""
+msgid "Set what should be replicated by choosing specific projects or groups by the secondary node."
+msgstr ""
+
msgid "SetPasswordToCloneLink|set a password"
msgstr ""
@@ -20672,6 +21349,9 @@ msgstr ""
msgid "Show complete raw log"
msgstr ""
+msgid "Show details"
+msgstr ""
+
msgid "Show file browser"
msgstr ""
@@ -20681,10 +21361,13 @@ msgstr ""
msgid "Show latest version"
msgstr ""
+msgid "Show list"
+msgstr ""
+
msgid "Show me everything"
msgstr ""
-msgid "Show me how"
+msgid "Show me how to add a pipeline"
msgstr ""
msgid "Show me more advanced stuff"
@@ -20741,9 +21424,6 @@ msgstr ""
msgid "Showing all issues"
msgstr ""
-msgid "Showing all labels"
-msgstr ""
-
msgid "Showing last %{size} of log -"
msgstr ""
@@ -20855,9 +21535,6 @@ msgstr ""
msgid "Skip outdated deployment jobs"
msgstr ""
-msgid "Skip this for now"
-msgstr ""
-
msgid "Skipped"
msgstr ""
@@ -21002,6 +21679,9 @@ msgstr ""
msgid "Something went wrong while adding your award. Please try again."
msgstr ""
+msgid "Something went wrong while applying the batch of suggestions. Please try again."
+msgstr ""
+
msgid "Something went wrong while applying the suggestion. Please try again."
msgstr ""
@@ -21104,10 +21784,13 @@ msgstr ""
msgid "Something went wrong while updating a requirement."
msgstr ""
+msgid "Something went wrong while updating assignees"
+msgstr ""
+
msgid "Something went wrong while updating your list settings"
msgstr ""
-msgid "Something went wrong with your automatic subscription renewal"
+msgid "Something went wrong with your automatic subscription renewal."
msgstr ""
msgid "Something went wrong, unable to add %{project} to dashboard"
@@ -21125,6 +21808,9 @@ msgstr ""
msgid "Something went wrong, unable to search projects"
msgstr ""
+msgid "Something went wrong."
+msgstr ""
+
msgid "Something went wrong. Please try again."
msgstr ""
@@ -21308,6 +21994,9 @@ msgstr ""
msgid "Source code"
msgstr ""
+msgid "Source code (%{fileExtension})"
+msgstr ""
+
msgid "Source is not available"
msgstr ""
@@ -21392,6 +22081,9 @@ msgstr ""
msgid "Stack trace"
msgstr ""
+msgid "Stacktrace snippet"
+msgstr ""
+
msgid "Stage"
msgstr ""
@@ -21497,6 +22189,9 @@ msgstr ""
msgid "Start thread & reopen %{noteable_name}"
msgstr ""
+msgid "Start using Directed Acyclic Graphs (DAG)"
+msgstr ""
+
msgid "Start your Free Gold Trial"
msgstr ""
@@ -21959,6 +22654,21 @@ msgstr ""
msgid "SuggestedColors|Very pale orange"
msgstr ""
+msgid "Suggestion is not applicable as the suggestion was not found."
+msgstr ""
+
+msgid "Suggestions are not applicable as one or more suggestions were not found."
+msgstr ""
+
+msgid "Suggestions are not applicable as their lines cannot overlap."
+msgstr ""
+
+msgid "Suggestions must all be on the same branch."
+msgstr ""
+
+msgid "Suggestions that change line count can't be added to batches, yet."
+msgstr ""
+
msgid "Suggestions:"
msgstr ""
@@ -22001,9 +22711,6 @@ msgstr ""
msgid "Synchronization disabled"
msgstr ""
-msgid "Synchronization of container repositories is disabled."
-msgstr ""
-
msgid "System"
msgstr ""
@@ -22184,6 +22891,42 @@ msgstr ""
msgid "Terms of Service and Privacy Policy"
msgstr ""
+msgid "Terraform|%{number} Terraform report failed to generate"
+msgid_plural "Terraform|%{number} Terraform reports failed to generate"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+msgstr[4] ""
+msgstr[5] ""
+
+msgid "Terraform|%{number} Terraform report was generated in your pipelines"
+msgid_plural "Terraform|%{number} Terraform reports were generated in your pipelines"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+msgstr[4] ""
+msgstr[5] ""
+
+msgid "Terraform|A Terraform report failed to generate."
+msgstr ""
+
+msgid "Terraform|A Terraform report was generated in your pipelines."
+msgstr ""
+
+msgid "Terraform|Generating the report caused an error."
+msgstr ""
+
+msgid "Terraform|Reported Resource Changes: %{addNum} to add, %{changeNum} to change, %{deleteNum} to delete"
+msgstr ""
+
+msgid "Terraform|The Terraform report %{name} failed to generate."
+msgstr ""
+
+msgid "Terraform|The Terraform report %{name} was generated in your pipelines."
+msgstr ""
+
msgid "Test"
msgstr ""
@@ -22214,7 +22957,7 @@ msgstr ""
msgid "TestHooks|Ensure the project has CI pipelines."
msgstr ""
-msgid "TestHooks|Ensure the project has at least one commit."
+msgid "TestHooks|Ensure the project has deployments."
msgstr ""
msgid "TestHooks|Ensure the project has issues."
@@ -22319,7 +23062,7 @@ msgstr ""
msgid "The Prometheus server responded with \"bad request\". Please check your queries are correct and are supported in your Prometheus version. %{documentationLink}"
msgstr ""
-msgid "The URL defined on the primary node that secondary nodes should use to contact it. Defaults to URL"
+msgid "The URL defined on the primary node that secondary nodes should use to contact it."
msgstr ""
msgid "The URL to use for connecting to Elasticsearch. Use a comma-separated list to support clustering (e.g., \"http://localhost:9200, http://localhost:9201\")."
@@ -22328,6 +23071,9 @@ msgstr ""
msgid "The X509 Certificate to use when mutual TLS is required to communicate with the external authorization service. If left blank, the server certificate is still validated when accessing over HTTPS."
msgstr ""
+msgid "The above settings apply to all projects with the selected compliance framework(s)."
+msgstr ""
+
msgid "The amount of seconds after which a request to get a secondary node status will time out."
msgstr ""
@@ -22631,9 +23377,6 @@ msgstr ""
msgid "The total stage shows the time it takes between creating an issue and deploying the code to production. The data will be automatically added once you have completed the full idea to production cycle."
msgstr ""
-msgid "The unique identifier for the Geo node. Must match %{geoNodeName} if it is set in gitlab.rb, otherwise it must match %{externalUrl} with a trailing slash"
-msgstr ""
-
msgid "The update action will time out after %{number_of_minutes} minutes. For big repositories, use a clone/push combination."
msgstr ""
@@ -22658,9 +23401,6 @@ msgstr ""
msgid "The user you are trying to deactivate has been active in the past %{minimum_inactive_days} days and cannot be deactivated"
msgstr ""
-msgid "The user-facing URL of the Geo node"
-msgstr ""
-
msgid "The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6."
msgstr ""
@@ -22751,6 +23491,9 @@ msgstr ""
msgid "There was a problem fetching project branches."
msgstr ""
+msgid "There was a problem fetching project tags."
+msgstr ""
+
msgid "There was a problem fetching project users."
msgstr ""
@@ -22802,6 +23545,9 @@ msgstr ""
msgid "There was an error fetching the %{replicableType}"
msgstr ""
+msgid "There was an error fetching the Geo Settings"
+msgstr ""
+
msgid "There was an error fetching the Node's Groups"
msgstr ""
@@ -22922,6 +23668,12 @@ msgstr ""
msgid "This %{issuable} is locked. Only <strong>project members</strong> can comment."
msgstr ""
+msgid "This %{noteableTypeText} is %{confidentialLinkStart}confidential%{linkEnd} and %{lockedLinkStart}locked%{linkEnd}."
+msgstr ""
+
+msgid "This %{noteableTypeText} is locked."
+msgstr ""
+
msgid "This %{viewer} could not be displayed because %{reason}. You can %{options} instead."
msgstr ""
@@ -22940,6 +23692,9 @@ msgstr ""
msgid "This action can lead to data loss. To prevent accidental actions we ask you to confirm your intention."
msgstr ""
+msgid "This also resolves all related threads"
+msgstr ""
+
msgid "This also resolves the discussion"
msgstr ""
@@ -23030,6 +23785,9 @@ msgstr ""
msgid "This epic does not exist or you don't have sufficient permission."
msgstr ""
+msgid "This feature is currently in beta. We invite you to %{linkStart}give feedback%{linkEnd}."
+msgstr ""
+
msgid "This feature requires local storage to be enabled"
msgstr ""
@@ -23063,7 +23821,7 @@ msgstr ""
msgid "This is a Work in Progress"
msgstr ""
-msgid "This is a confidential issue."
+msgid "This is a confidential %{noteableTypeText}."
msgstr ""
msgid "This is a delayed job to run in %{remainingTime}"
@@ -23090,9 +23848,6 @@ msgstr ""
msgid "This is your current session"
msgstr ""
-msgid "This issue is %{confidentialLinkStart}confidential%{linkEnd} and %{lockedLinkStart}locked%{linkEnd}."
-msgstr ""
-
msgid "This issue is confidential"
msgstr ""
@@ -23102,9 +23857,6 @@ msgstr ""
msgid "This issue is in a child epic of the filtered epic"
msgstr ""
-msgid "This issue is locked."
-msgstr ""
-
msgid "This job depends on other jobs with expired/erased artifacts: %{invalid_dependencies}"
msgstr ""
@@ -23204,6 +23956,9 @@ msgstr ""
msgid "This license has already expired."
msgstr ""
+msgid "This link points to external content"
+msgstr ""
+
msgid "This may expose confidential information as the selected fork is in another namespace that can have other members."
msgstr ""
@@ -23231,6 +23986,9 @@ msgstr ""
msgid "This page will be removed in a future release."
msgstr ""
+msgid "This pipeline does not use the %{codeStart}needs%{codeEnd} keyword and can't be represented as a directed acyclic graph."
+msgstr ""
+
msgid "This pipeline makes use of a predefined CI/CD configuration enabled by %{strongStart}Auto DevOps.%{strongEnd}"
msgstr ""
@@ -23459,6 +24217,9 @@ msgstr ""
msgid "Time from last commit to merge"
msgstr ""
+msgid "Time in seconds"
+msgstr ""
+
msgid "Time in seconds GitLab will wait for a response from the external service. When the service does not respond in time, access will be denied."
msgstr ""
@@ -23888,9 +24649,15 @@ msgstr ""
msgid "Total artifacts size: %{total_size}"
msgstr ""
+msgid "Total cores (CPUs)"
+msgstr ""
+
msgid "Total issues"
msgstr ""
+msgid "Total memory (GB)"
+msgstr ""
+
msgid "Total test time for all commits/merges"
msgstr ""
@@ -24092,6 +24859,9 @@ msgstr ""
msgid "U2F only works with HTTPS-enabled websites. Contact your administrator for more details."
msgstr ""
+msgid "UI Development Kit"
+msgstr ""
+
msgid "URL"
msgstr ""
@@ -24164,9 +24934,6 @@ msgstr ""
msgid "Unable to load the merge request widget. Try reloading the page."
msgstr ""
-msgid "Unable to process group import file"
-msgstr ""
-
msgid "Unable to resolve"
msgstr ""
@@ -24182,6 +24949,9 @@ msgstr ""
msgid "Unable to sign you in to the group with SAML due to \"%{reason}\""
msgstr ""
+msgid "Unable to suggest a path. Please refresh and try again."
+msgstr ""
+
msgid "Unable to update label prioritization at this time"
msgstr ""
@@ -24197,6 +24967,9 @@ msgstr ""
msgid "Unarchiving the project will restore people's ability to make changes to it. The repository can be committed to, and issues, comments, and other entities can be created. %{strong_start}Once active, this project shows up in the search and on the dashboard.%{strong_end}"
msgstr ""
+msgid "Unassign from commenting user"
+msgstr ""
+
msgid "Unblock"
msgstr ""
@@ -24272,6 +25045,9 @@ msgstr ""
msgid "Unreachable"
msgstr ""
+msgid "Unrecognized cluster type"
+msgstr ""
+
msgid "Unresolve"
msgstr ""
@@ -24332,6 +25108,9 @@ msgstr ""
msgid "Unsubscribes from this %{quick_action_target}."
msgstr ""
+msgid "Unsupported todo type passed. Supported todo types are: %{todo_types}"
+msgstr ""
+
msgid "Until"
msgstr ""
@@ -24359,6 +25138,9 @@ msgstr ""
msgid "Update approval rule"
msgstr ""
+msgid "Update approvers"
+msgstr ""
+
msgid "Update failed"
msgstr ""
@@ -24368,6 +25150,9 @@ msgstr ""
msgid "Update it"
msgstr ""
+msgid "Update iteration"
+msgstr ""
+
msgid "Update now"
msgstr ""
@@ -24413,7 +25198,7 @@ msgstr ""
msgid "Updated at"
msgstr ""
-msgid "Updated to"
+msgid "Updated to %{linkStart}chart v%{linkEnd}"
msgstr ""
msgid "Updating"
@@ -24443,6 +25228,9 @@ msgstr ""
msgid "Upload CSV file"
msgstr ""
+msgid "Upload License"
+msgstr ""
+
msgid "Upload New File"
msgstr ""
@@ -24524,16 +25312,19 @@ msgstr ""
msgid "UsageQuota|Pipelines"
msgstr ""
+msgid "UsageQuota|Purchase more storage"
+msgstr ""
+
msgid "UsageQuota|Repositories"
msgstr ""
msgid "UsageQuota|Repository"
msgstr ""
-msgid "UsageQuota|Storage"
+msgid "UsageQuota|Snippets"
msgstr ""
-msgid "UsageQuota|Storage usage:"
+msgid "UsageQuota|Storage"
msgstr ""
msgid "UsageQuota|This namespace has no projects which use shared runners"
@@ -24566,6 +25357,12 @@ msgstr ""
msgid "UsageQuota|Wikis"
msgstr ""
+msgid "UsageQuota|You used: %{usage} %{limit}"
+msgstr ""
+
+msgid "UsageQuota|out of %{formattedLimit} of your namespace storage"
+msgstr ""
+
msgid "Use %{code_start}::%{code_end} to create a %{link_start}scoped label set%{link_end} (eg. %{code_start}priority::1%{code_end})"
msgstr ""
@@ -24632,6 +25429,9 @@ msgstr ""
msgid "User IDs"
msgstr ""
+msgid "User Lists can only be created and modified with %{linkStart}the API%{linkEnd}"
+msgstr ""
+
msgid "User OAuth applications"
msgstr ""
@@ -24656,6 +25456,9 @@ msgstr ""
msgid "User key was successfully removed."
msgstr ""
+msgid "User list %{name} will be removed. Are you sure?"
+msgstr ""
+
msgid "User map"
msgstr ""
@@ -24677,148 +25480,10 @@ msgstr ""
msgid "User was successfully updated."
msgstr ""
-msgid "UserOnboardingTour|%{activeTour}/%{totalTours}"
+msgid "UserList|Delete %{name}?"
msgstr ""
-msgid "UserOnboardingTour|%{completed}/%{total} steps completed"
-msgstr ""
-
-msgid "UserOnboardingTour|%{emphasisStart}Well done!%{emphasisEnd}%{lineBreak}%{lineBreak}That's it for our guided tour, congratulations for making it all the way to the end!%{lineBreak}%{lineBreak}We hope this gave you a good overview of GitLab and how it can help you. We'll now show you how to create your own project and invite your colleagues."
-msgstr ""
-
-msgid "UserOnboardingTour|Adding other members to a project is done through Project Settings. Click on %{emphasisStart}Settings%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Alright, that's it for Commits. Let's take a look at the %{emphasisStart}Branches%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Awesome! Now click on %{emphasisStart}Members%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Click on one of the %{emphasisStart}Compare%{emphasisEnd} buttons to compare a branch to master."
-msgstr ""
-
-msgid "UserOnboardingTour|Click on one of the %{emphasisStart}pipeline IDs%{emphasisEnd} to see the details of a pipeline."
-msgstr ""
-
-msgid "UserOnboardingTour|Click to open the latest commit to see its details."
-msgstr ""
-
-msgid "UserOnboardingTour|Close 'Learn GitLab'"
-msgstr ""
-
-msgid "UserOnboardingTour|Commits are shown in chronological order and can be filtered by the commit message or by the branch."
-msgstr ""
-
-msgid "UserOnboardingTour|Create a project"
-msgstr ""
-
-msgid "UserOnboardingTour|Exit 'Learn GitLab'"
-msgstr ""
-
-msgid "UserOnboardingTour|Got it"
-msgstr ""
-
-msgid "UserOnboardingTour|Great job! %{clapHands} We hope the tour was helpful and that you learned how to use GitLab.%{lineBreak}%{lineBreak}We'd love to get your feedback on this tour.%{lineBreak}%{lineBreak}%{emphasisStart}How helpful would you say this guided tour was?%{emphasisEnd}%{lineBreak}%{lineBreak}"
-msgstr ""
-
-msgid "UserOnboardingTour|Guided GitLab Tour"
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can compare the changes of this branch to another one. Changes are divided by files so that it's easier to see what was changed where."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can create a project from scratch, start with a template or import a repository from other platforms. Whatever you choose, we'll guide you through the process.%{lineBreak}%{lineBreak}Fill in your new project information and click on %{emphasisStart}Create Project%{emphasisEnd} to progress to the next step."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can see the breakdown of the pipelines: its stages and jobs in each of the stages and their status.%{lineBreak}%{lineBreak}Our CI/CD pipelines are quite complex, most of our users have fewer and simpler pipelines."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can see the current members of the project (just you at the moment) and invite new members.%{lineBreak}%{lineBreak}You can invite multiple members at once (existing GitLab users or invite by email) and you can also set their roles and permissions.%{lineBreak}%{lineBreak}Add a few members and click on %{emphasisStart}Add to project%{emphasisEnd} to complete this step."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can see what changes were made with this commit, on what branch and if there's a related merge request. The status of the pipeline will also show up if CI/CD is set up.%{lineBreak}%{lineBreak}You can also comment on the lines of code that were changed and start a discussion with your colleagues!"
-msgstr ""
-
-msgid "UserOnboardingTour|Here's an overview of branches in the %{emphasisStart}%{projectName}%{emphasisEnd} project. They're split into Active and Stale.%{lineBreak}%{lineBreak}From here, you can create a new merge request from a branch, or compare the branch to any other branch in the project. By default, it will compare it to the master branch."
-msgstr ""
-
-msgid "UserOnboardingTour|Invite colleagues"
-msgstr ""
-
-msgid "UserOnboardingTour|Issues are great for communicating and keeping track of progress in GitLab. These are all issues that are open in the %{emphasisStart}%{projectName}%{emphasisEnd}.%{lineBreak}%{lineBreak}You can help us improve GitLab by contributing work to issues that are labeled <span class=\"badge color-label accept-mr-label\">Accepting merge requests</span>.%{lineBreak}%{lineBreak}This list can be filtered by labels, milestones, assignees, authors... We'll show you how it looks when the list is filtered by a label."
-msgstr ""
-
-msgid "UserOnboardingTour|Learn GitLab"
-msgstr ""
-
-msgid "UserOnboardingTour|Let's take a closer look at a merge request. Click on the title of one."
-msgstr ""
-
-msgid "UserOnboardingTour|Let's take a closer look at all the commits. Click on %{emphasisStart}Commits%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Let's take a closer look at the repository of this project. Click on %{emphasisStart}Repository%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|No thanks"
-msgstr ""
-
-msgid "UserOnboardingTour|Ok, let's go"
-msgstr ""
-
-msgid "UserOnboardingTour|Ok, show me"
-msgstr ""
-
-msgid "UserOnboardingTour|Open one of the issues by clicking on its title."
-msgstr ""
-
-msgid "UserOnboardingTour|Restart this step"
-msgstr ""
-
-msgid "UserOnboardingTour|Skip this step"
-msgstr ""
-
-msgid "UserOnboardingTour|Sweet! Your project was created and is ready to be used.%{lineBreak}%{lineBreak}You can start adding files to the repository or clone it. One last thing we want to show you is how to invite your colleagues to your new project."
-msgstr ""
-
-msgid "UserOnboardingTour|Take a look. Here's a nifty menu for quickly creating issues, merge requests, snippets, projects and groups. Click on it and select \"New project\" from the \"GitLab\" section to get started."
-msgstr ""
-
-msgid "UserOnboardingTour|Thanks for taking the guided tour. Remember, if you want to go through it again, you can start %{emphasisStart}Learn GitLab%{emphasisEnd} in the help menu on the top right."
-msgstr ""
-
-msgid "UserOnboardingTour|Thanks for the feedback! %{thumbsUp}"
-msgstr ""
-
-msgid "UserOnboardingTour|That's it for issues. Let'st take a look at %{emphasisStart}Merge Requests%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|That's it for merge requests. Now for the final part of this guided tour - the %{emphasisStart}CI/CD%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|That's it for the Repository. Let's take a look at the %{emphasisStart}Issues%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|The structure of this page is very similar to issues. Status, description, discussion and the sidebar are all here.%{lineBreak}%{lineBreak}But take a look below the description and you'll notice that there's more information about the merge request, the CI/CD pipeline and the options for approving it.%{lineBreak}%{lineBreak}Alongside the discussion you can also see more information about commits in this merge request, the status of pipelines and review all changes that were made."
-msgstr ""
-
-msgid "UserOnboardingTour|There's a lot of information here but don't worry, we'll go through it.%{lineBreak}%{lineBreak}On the top you can see the status of the issue and when it was opened and by whom. Directly below it is the issue description and below that are other %{emphasisStart}related issues%{emphasisEnd} and %{emphasisStart}merge requests%{emphasisEnd} (if any). Then below that is the %{emphasisStart}discussion%{emphasisEnd}, that's where most of the communication happens.%{lineBreak}%{lineBreak}On the right, there's a sidebar where you can view/change the %{emphasisStart}assignee, milestone, due date, labels, weight%{emphasisEnd}, etc."
-msgstr ""
-
-msgid "UserOnboardingTour|These are all the CI/CD pipelines we have for our %{emphasisStart}%{projectName}%{emphasisEnd} project.%{lineBreak}%{lineBreak}Here you can see the status of each pipeline, for what commit it's running for, its stages and the status for them."
-msgstr ""
-
-msgid "UserOnboardingTour|These are all the issues that are available for community contributions. Let's take a closer look at one of them."
-msgstr ""
-
-msgid "UserOnboardingTour|This is an overview of all merge requests in this project. Similarly to the issues overview it can be filtered down by things like labels, milestones, authors, assignees, etc."
-msgstr ""
-
-msgid "UserOnboardingTour|This is the repository for the %{emphasisStart}%{projectName}%{emphasisEnd} project. All our code is stored here. Feel free to explore and take a closer look at folders and files.%{lineBreak}%{lineBreak}Above the file structure you can see the latest commit, who the author is and the status of the CI/CD pipeline.%{lineBreak}%{lineBreak}If you scroll down below the file structure, you'll find the Readme of this project. This is defined in the README.md file at the root of the repository."
-msgstr ""
-
-msgid "UserOnboardingTour|Welcome to the project overview of the %{emphasisStart}%{projectName}%{emphasisEnd} project. This is the project that we use to work on GitLab. At first, a project seems like a simple repository, but at GitLab, a project is so much more.%{lineBreak}%{lineBreak}You can create projects for hosting your codebase, use it as an issue tracker, collaborate on code, and continuously build, test, and deploy your app with built-in GitLab CI/CD."
+msgid "UserList|created %{timeago}"
msgstr ""
msgid "UserProfile|Activity"
@@ -24971,6 +25636,9 @@ msgstr ""
msgid "UsersSelect|Unassigned"
msgstr ""
+msgid "Using %{codeStart}needs%{codeEnd} allows jobs to run before their stage is reached, as soon as their individual dependencies are met, which speeds up your pipelines."
+msgstr ""
+
msgid "Using %{code_start}::%{code_end} denotes a %{link_start}scoped label set%{link_end}"
msgstr ""
@@ -25055,9 +25723,6 @@ msgstr ""
msgid "Versions"
msgstr ""
-msgid "Very helpful"
-msgstr ""
-
msgid "View Documentation"
msgstr ""
@@ -25214,6 +25879,9 @@ msgstr ""
msgid "VisibilityLevel|Unknown"
msgstr ""
+msgid "Visit settings page"
+msgstr ""
+
msgid "VisualReviewApp|%{stepStart}Step 1%{stepEnd}. Copy the following script:"
msgstr ""
@@ -25307,6 +25975,9 @@ msgstr ""
msgid "VulnerabilityManagement|Something went wrong while trying to delete the comment. Please try again later."
msgstr ""
+msgid "VulnerabilityManagement|Something went wrong while trying to refresh the vulnerability. Please try again later."
+msgstr ""
+
msgid "VulnerabilityManagement|Something went wrong while trying to retrieve the vulnerability history. Please try again later."
msgstr ""
@@ -25349,6 +26020,9 @@ msgstr ""
msgid "Vulnerability|Class"
msgstr ""
+msgid "Vulnerability|Crash Address"
+msgstr ""
+
msgid "Vulnerability|Description"
msgstr ""
@@ -25424,9 +26098,6 @@ msgstr ""
msgid "We couldn't reach the Prometheus server. Either the server no longer exists or the configuration details need updating."
msgstr ""
-msgid "We created a short guided tour that will help you learn the basics of GitLab and how it will help you be better at your job. It should only take a couple of minutes. You will be guided by two types of helpers, best recognized by their color."
-msgstr ""
-
msgid "We detected potential spam in the %{humanized_resource_name}. Please solve the reCAPTCHA to proceed."
msgstr ""
@@ -25460,6 +26131,9 @@ msgstr ""
msgid "We've found no vulnerabilities"
msgstr ""
+msgid "Web Application Firewall"
+msgstr ""
+
msgid "Web IDE"
msgstr ""
@@ -25520,9 +26194,6 @@ msgstr ""
msgid "Welcome to GitLab.com<br>@%{name}!"
msgstr ""
-msgid "Welcome to the Guided GitLab Tour"
-msgstr ""
-
msgid "Welcome to the guided GitLab tour"
msgstr ""
@@ -25574,9 +26245,6 @@ msgstr ""
msgid "While it's rare to have no vulnerabilities, it can happen. In any event, we ask that you please double check your settings to make sure you've set up your dashboard correctly."
msgstr ""
-msgid "White helpers give contextual information."
-msgstr ""
-
msgid "Who can be an approver?"
msgstr ""
@@ -25622,12 +26290,18 @@ msgstr ""
msgid "WikiEmptyIssueMessage|Suggest wiki improvement"
msgstr ""
+msgid "WikiEmptyIssueMessage|You must be a group member in order to add wiki pages. If you have suggestions for how to improve the wiki for this group, consider opening an issue in the %{issues_link}."
+msgstr ""
+
msgid "WikiEmptyIssueMessage|You must be a project member in order to add wiki pages. If you have suggestions for how to improve the wiki for this project, consider opening an issue in the %{issues_link}."
msgstr ""
msgid "WikiEmptyIssueMessage|issue tracker"
msgstr ""
+msgid "WikiEmpty|A wiki is where you can store all the details about your group. This can include why you've created it, its principles, how to use it, and so on."
+msgstr ""
+
msgid "WikiEmpty|A wiki is where you can store all the details about your project. This can include why you've created it, its principles, how to use it, and so on."
msgstr ""
@@ -25637,12 +26311,21 @@ msgstr ""
msgid "WikiEmpty|Suggest wiki improvement"
msgstr ""
+msgid "WikiEmpty|The wiki lets you write documentation for your group"
+msgstr ""
+
msgid "WikiEmpty|The wiki lets you write documentation for your project"
msgstr ""
+msgid "WikiEmpty|This group has no wiki pages"
+msgstr ""
+
msgid "WikiEmpty|This project has no wiki pages"
msgstr ""
+msgid "WikiEmpty|You must be a group member in order to add wiki pages."
+msgstr ""
+
msgid "WikiEmpty|You must be a project member in order to add wiki pages."
msgstr ""
@@ -25919,13 +26602,13 @@ msgstr ""
msgid "You can now export your security dashboard to a CSV report."
msgstr ""
-msgid "You can now submit a merge request to get this change into the original branch."
+msgid "You can now manage alert endpoint configuration in the Alerts section on the Operations settings page. Fields on this page have been deprecated."
msgstr ""
-msgid "You can now submit a merge request to get this change into the original project."
+msgid "You can now submit a merge request to get this change into the original branch."
msgstr ""
-msgid "You can only add files when you are on a branch"
+msgid "You can now submit a merge request to get this change into the original project."
msgstr ""
msgid "You can only edit files when you are on a branch"
@@ -26147,7 +26830,7 @@ msgstr ""
msgid "You need to upload a Google Takeout archive."
msgstr ""
-msgid "You reached %{usage_in_percent} of %{namespace_name}'s capacity (%{used_storage} of %{storage_limit})"
+msgid "You reached %{usage_in_percent} of %{namespace_name}'s storage capacity (%{used_storage} of %{storage_limit})"
msgstr ""
msgid "You tried to fork %{link_to_the_project} but it failed for the following reason:"
@@ -26450,13 +27133,13 @@ msgstr ""
msgid "Your subscription expired!"
msgstr ""
-msgid "Your subscription has been downgraded"
+msgid "Your subscription has been downgraded."
msgstr ""
-msgid "Your subscription will automatically renew in %{remaining_days}"
+msgid "Your subscription will automatically renew in %{remaining_days}."
msgstr ""
-msgid "Your subscription will expire in %{remaining_days}"
+msgid "Your subscription will expire in %{remaining_days}."
msgstr ""
msgid "Zoom meeting added"
@@ -26540,9 +27223,6 @@ msgstr ""
msgid "branch name"
msgstr ""
-msgid "build pipeline reference mismatch"
-msgstr ""
-
msgid "by"
msgstr ""
@@ -26561,6 +27241,9 @@ msgstr ""
msgid "cannot block others"
msgstr ""
+msgid "cannot contain HTML/XML tags, including any word between angle brackets (<,>)."
+msgstr ""
+
msgid "cannot include leading slash or directory traversal."
msgstr ""
@@ -26591,75 +27274,6 @@ msgstr ""
msgid "ciReport|%{remainingPackagesCount} more"
msgstr ""
-msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability"
-msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities"
-msgstr[0] ""
-msgstr[1] ""
-msgstr[2] ""
-msgstr[3] ""
-msgstr[4] ""
-msgstr[5] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability for the source branch only"
-msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities for the source branch only"
-msgstr[0] ""
-msgstr[1] ""
-msgstr[2] ""
-msgstr[3] ""
-msgstr[4] ""
-msgstr[5] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerability"
-msgid_plural "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerabilities"
-msgstr[0] ""
-msgstr[1] ""
-msgstr[2] ""
-msgstr[3] ""
-msgstr[4] ""
-msgstr[5] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new vulnerability"
-msgid_plural "ciReport|%{reportType} %{status} detected %{newCount} new vulnerabilities"
-msgstr[0] ""
-msgstr[1] ""
-msgstr[2] ""
-msgstr[3] ""
-msgstr[4] ""
-msgstr[5] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities for the source branch only"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{fixedCount} fixed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} vulnerability for the source branch only"
-msgid_plural "ciReport|%{reportType} %{status} detected %{newCount} vulnerabilities for the source branch only"
-msgstr[0] ""
-msgstr[1] ""
-msgstr[2] ""
-msgstr[3] ""
-msgstr[4] ""
-msgstr[5] ""
-
-msgid "ciReport|%{reportType} %{status} detected no new vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected no vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected no vulnerabilities for the source branch only"
-msgstr ""
-
msgid "ciReport|%{reportType} is loading"
msgstr ""
@@ -26678,7 +27292,7 @@ msgstr ""
msgid "ciReport|All projects"
msgstr ""
-msgid "ciReport|All report types"
+msgid "ciReport|All scanner types"
msgstr ""
msgid "ciReport|All severities"
@@ -26702,6 +27316,9 @@ msgstr ""
msgid "ciReport|Container scanning detects known vulnerabilities in your docker images."
msgstr ""
+msgid "ciReport|Coverage Fuzzing"
+msgstr ""
+
msgid "ciReport|Create a merge request to implement this solution, or download and apply the patch manually."
msgstr ""
@@ -26864,6 +27481,9 @@ msgstr ""
msgid "customize"
msgstr ""
+msgid "data"
+msgstr ""
+
msgid "date must not be after 9999-12-31"
msgstr ""
@@ -26942,6 +27562,9 @@ msgstr ""
msgid "entries cannot contain HTML tags"
msgstr ""
+msgid "epic"
+msgstr ""
+
msgid "error"
msgstr ""
@@ -26963,9 +27586,6 @@ msgstr ""
msgid "expires on %{timebox_due_date}"
msgstr ""
-msgid "external_url"
-msgstr ""
-
msgid "failed"
msgstr ""
@@ -27005,9 +27625,6 @@ msgstr ""
msgid "from"
msgstr ""
-msgid "geo_node_name"
-msgstr ""
-
msgid "group"
msgstr ""
@@ -27029,9 +27646,6 @@ msgstr ""
msgid "https://your-bitbucket-server"
msgstr ""
-msgid "image"
-msgstr ""
-
msgid "image diff"
msgstr ""
@@ -27179,6 +27793,9 @@ msgstr ""
msgid "load it anyway"
msgstr ""
+msgid "loading"
+msgstr ""
+
msgid "locked by %{path_lock_user_name} %{created_at}"
msgstr ""
@@ -27311,9 +27928,6 @@ msgstr ""
msgid "mrWidget|Deployment statistics are not available currently"
msgstr ""
-msgid "mrWidget|Detect issues before deployment with a CI pipeline that continuously tests your code. We created a quick guide that will show you how to create one. Make your code more secure and more robust in just a minute."
-msgstr ""
-
msgid "mrWidget|Did not close"
msgstr ""
@@ -27476,6 +28090,9 @@ msgstr ""
msgid "mrWidget|To approve this merge request, please enter your password. This project requires all approvals to be authenticated."
msgstr ""
+msgid "mrWidget|Use %{linkStart}CI pipelines to test your code%{linkEnd}, simply add a GitLab CI configuration file to your project. It only takes a minute to make your code more secure and robust."
+msgstr ""
+
msgid "mrWidget|When this merge request is ready, remove the WIP: prefix from the title to allow it to be merged"
msgstr ""
@@ -27512,6 +28129,9 @@ msgstr ""
msgid "must be greater than start date"
msgstr ""
+msgid "must contain only valid frameworks"
+msgstr ""
+
msgid "my-awesome-group"
msgstr ""
@@ -27524,6 +28144,9 @@ msgstr ""
msgid "needs to be between 10 minutes and 1 month"
msgstr ""
+msgid "never"
+msgstr ""
+
msgid "never expires"
msgstr ""
@@ -27551,6 +28174,9 @@ msgstr ""
msgid "nounSeries|%{firstItem} and %{lastItem}"
msgstr ""
+msgid "nounSeries|%{item}"
+msgstr ""
+
msgid "nounSeries|%{item}, %{nextItem}"
msgstr ""
@@ -27707,6 +28333,9 @@ msgstr ""
msgid "revised"
msgstr ""
+msgid "satisfied"
+msgstr ""
+
msgid "score"
msgstr ""
@@ -27737,6 +28366,9 @@ msgstr ""
msgid "severity|Unknown"
msgstr ""
+msgid "should be an array of %{object_name} objects"
+msgstr ""
+
msgid "should be greater than or equal to %{access} inherited membership from group %{group_name}"
msgstr ""
@@ -27791,10 +28423,13 @@ msgstr ""
msgid "suggestPipeline|2/2: Commit your changes"
msgstr ""
-msgid "suggestPipeline|Commit the changes and your pipeline will automatically run for the first time."
+msgid "suggestPipeline|Choose %{boldStart}Code Quality%{boldEnd} to add a pipeline that tests the quality of your code."
msgstr ""
-msgid "suggestPipeline|We recommend the %{boldStart}Code Quality%{boldEnd} template, which will add a report widget to your Merge Requests. This way you’ll learn about code quality degradations much sooner. %{footerStart} Goodbye technical debt! %{footerEnd}"
+msgid "suggestPipeline|The template is ready! You can now commit it to create your first pipeline."
+msgstr ""
+
+msgid "suggestPipeline|We’re adding a GitLab CI configuration file to add a pipeline to the project. You could create it manually, but we recommend that you start with a GitLab template that works out of the box."
msgstr ""
msgid "syntax is correct"
@@ -27854,6 +28489,9 @@ msgstr ""
msgid "uses Kubernetes clusters to deploy your code!"
msgstr ""
+msgid "v%{version} published %{timeAgo}"
+msgstr ""
+
msgid "verify ownership"
msgstr ""
diff --git a/locale/as_IN/gitlab.po b/locale/as_IN/gitlab.po
new file mode 100644
index 00000000000..1dbaab5ea20
--- /dev/null
+++ b/locale/as_IN/gitlab.po
@@ -0,0 +1,27977 @@
+msgid ""
+msgstr ""
+"Project-Id-Version: gitlab-ee\n"
+"Report-Msgid-Bugs-To: \n"
+"Last-Translator: \n"
+"Language-Team: Assamese\n"
+"Language: as_IN\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Crowdin-Project: gitlab-ee\n"
+"X-Crowdin-Project-ID: 288872\n"
+"X-Crowdin-Language: as\n"
+"X-Crowdin-File: /master/locale/gitlab.pot\n"
+"X-Crowdin-File-ID: 6\n"
+"PO-Revision-Date: 2020-07-02 01:22\n"
+
+msgid " %{start} to %{end}"
+msgstr ""
+
+msgid " (from %{timeoutSource})"
+msgstr ""
+
+msgid " Collected %{time}"
+msgstr ""
+
+msgid " Please sign in."
+msgstr ""
+
+msgid " Try to %{action} this file again."
+msgstr ""
+
+msgid " You need to do this before %{grace_period_deadline}."
+msgstr ""
+
+msgid " and"
+msgstr ""
+
+msgid " and "
+msgstr ""
+
+msgid " and %{sliced}"
+msgstr ""
+
+msgid " degraded on %d point"
+msgid_plural " degraded on %d points"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid " improved on %d point"
+msgid_plural " improved on %d points"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid " or "
+msgstr ""
+
+msgid " or <!merge request id>"
+msgstr ""
+
+msgid " or <#issue id>"
+msgstr ""
+
+msgid " or <&epic id>"
+msgstr ""
+
+msgid " or references (e.g. path/to/project!merge_request_id)"
+msgstr ""
+
+msgid "\"%{path}\" did not exist on \"%{ref}\""
+msgstr ""
+
+msgid "%d URL scanned"
+msgid_plural "%d URLs scanned"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%d approver"
+msgid_plural "%d approvers"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%d approver (you've approved)"
+msgid_plural "%d approvers (you've approved)"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%d changed file"
+msgid_plural "%d changed files"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%d child epic"
+msgid_plural "%d child epics"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%d code quality issue"
+msgid_plural "%d code quality issues"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%d comment"
+msgid_plural "%d comments"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%d comment on this commit"
+msgid_plural "%d comments on this commit"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%d commit"
+msgid_plural "%d commits"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%d commit behind"
+msgid_plural "%d commits behind"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%d commit,"
+msgid_plural "%d commits,"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%d commits"
+msgstr ""
+
+msgid "%d contribution"
+msgid_plural "%d contributions"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%d day"
+msgid_plural "%d days"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%d error"
+msgid_plural "%d errors"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%d exporter"
+msgid_plural "%d exporters"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%d failed"
+msgid_plural "%d failed"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%d fixed test result"
+msgid_plural "%d fixed test results"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%d group selected"
+msgid_plural "%d groups selected"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%d inaccessible merge request"
+msgid_plural "%d inaccessible merge requests"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%d issue"
+msgid_plural "%d issues"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%d issue selected"
+msgid_plural "%d issues selected"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%d layer"
+msgid_plural "%d layers"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%d merge request"
+msgid_plural "%d merge requests"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%d merge request that you don't have access to."
+msgid_plural "%d merge requests that you don't have access to."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%d metric"
+msgid_plural "%d metrics"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%d milestone"
+msgid_plural "%d milestones"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%d minute"
+msgid_plural "%d minutes"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%d more comment"
+msgid_plural "%d more comments"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%d personal project will be removed and cannot be restored."
+msgid_plural "%d personal projects will be removed and cannot be restored."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%d project"
+msgid_plural "%d projects"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%d request with warnings"
+msgid_plural "%d requests with warnings"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%d second"
+msgid_plural "%d seconds"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%d shard selected"
+msgid_plural "%d shards selected"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%d tag"
+msgid_plural "%d tags"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%d unresolved thread"
+msgid_plural "%d unresolved threads"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%d vulnerability"
+msgid_plural "%d vulnerabilities"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%d vulnerability dismissed"
+msgid_plural "%d vulnerabilities dismissed"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%s additional commit has been omitted to prevent performance issues."
+msgid_plural "%s additional commits have been omitted to prevent performance issues."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{actionText} & %{openOrClose} %{noteable}"
+msgstr ""
+
+msgid "%{authorsName}'s thread"
+msgstr ""
+
+msgid "%{commit_author_link} authored %{commit_timeago}"
+msgstr ""
+
+msgid "%{completedWeight} of %{totalWeight} weight completed"
+msgstr ""
+
+msgid "%{containerScanningLinkStart}Container Scanning%{containerScanningLinkEnd} and/or %{dependencyScanningLinkStart}Dependency Scanning%{dependencyScanningLinkEnd} must be enabled. %{securityBotLinkStart}GitLab-Security-Bot%{securityBotLinkEnd} will be the author of the auto-created merge request. %{moreInfoLinkStart}More information%{moreInfoLinkEnd}."
+msgstr ""
+
+msgid "%{cores} cores"
+msgstr ""
+
+msgid "%{count} LOC/commit"
+msgstr ""
+
+msgid "%{count} approval required from %{name}"
+msgid_plural "%{count} approvals required from %{name}"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{count} approvals from %{name}"
+msgstr ""
+
+msgid "%{count} files touched"
+msgstr ""
+
+msgid "%{count} more"
+msgstr ""
+
+msgid "%{count} more assignees"
+msgstr ""
+
+msgid "%{count} more release"
+msgid_plural "%{count} more releases"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{count} of %{required} approvals from %{name}"
+msgstr ""
+
+msgid "%{count} of %{total}"
+msgstr ""
+
+msgid "%{count} participant"
+msgid_plural "%{count} participants"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{count} pending comment"
+msgid_plural "%{count} pending comments"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{count} related %{pluralized_subject}: %{links}"
+msgstr ""
+
+msgid "%{dashboard_path} could not be found."
+msgstr ""
+
+msgid "%{days} days until tags are automatically removed"
+msgstr ""
+
+msgid "%{deployLinkStart}Use a template to deploy to ECS%{deployLinkEnd}, or use a docker image to %{commandsLinkStart}run AWS commands in GitLab CI/CD%{commandsLinkEnd}."
+msgstr ""
+
+msgid "%{description}- Sentry event: %{errorUrl}- First seen: %{firstSeen}- Last seen: %{lastSeen} %{countLabel}: %{count}%{userCountLabel}: %{userCount}"
+msgstr ""
+
+msgid "%{duration}ms"
+msgstr ""
+
+msgid "%{edit_in_new_fork_notice} Try to cherry-pick this commit again."
+msgstr ""
+
+msgid "%{edit_in_new_fork_notice} Try to create a new directory again."
+msgstr ""
+
+msgid "%{edit_in_new_fork_notice} Try to revert this commit again."
+msgstr ""
+
+msgid "%{edit_in_new_fork_notice} Try to upload a file again."
+msgstr ""
+
+msgid "%{extra} more downstream pipelines"
+msgstr ""
+
+msgid "%{filePath} deleted"
+msgstr ""
+
+msgid "%{firstLabel} +%{labelCount} more"
+msgstr ""
+
+msgid "%{firstMilestoneName} + %{numberOfOtherMilestones} more"
+msgstr ""
+
+msgid "%{global_id} is not a valid id for %{expected_type}."
+msgstr ""
+
+msgid "%{group_docs_link_start}Groups%{group_docs_link_end} allow you to manage and collaborate across multiple projects. Members of a group have access to all of its projects."
+msgstr ""
+
+msgid "%{group_name} uses group managed accounts. You need to create a new GitLab account which will be managed by %{group_name}."
+msgstr ""
+
+msgid "%{host} sign-in from new location"
+msgstr ""
+
+msgid "%{icon}You are about to add %{usersTag} people to the discussion. They will all receive a notification."
+msgstr ""
+
+msgid "%{integrations_link_start}Integrations%{link_end} enable you to make third-party applications part of your GitLab workflow. If the available integrations don't meet your needs, consider using a %{webhooks_link_start}webhook%{link_end}."
+msgstr ""
+
+msgid "%{issuableType} will be removed! Are you sure?"
+msgstr ""
+
+msgid "%{issuesCount} issues in this group"
+msgstr ""
+
+msgid "%{issuesSize} issues"
+msgstr ""
+
+msgid "%{issuesSize} issues with a limit of %{maxIssueCount}"
+msgstr ""
+
+msgid "%{labelStart}Class:%{labelEnd} %{class}"
+msgstr ""
+
+msgid "%{labelStart}Evidence:%{labelEnd} %{evidence}"
+msgstr ""
+
+msgid "%{labelStart}File:%{labelEnd} %{file}"
+msgstr ""
+
+msgid "%{labelStart}Image:%{labelEnd} %{image}"
+msgstr ""
+
+msgid "%{labelStart}Method:%{labelEnd} %{method}"
+msgstr ""
+
+msgid "%{labelStart}Namespace:%{labelEnd} %{namespace}"
+msgstr ""
+
+msgid "%{labelStart}Report Type:%{labelEnd} %{reportType}"
+msgstr ""
+
+msgid "%{labelStart}Scanner:%{labelEnd} %{scanner}"
+msgstr ""
+
+msgid "%{labelStart}Severity:%{labelEnd} %{severity}"
+msgstr ""
+
+msgid "%{label_for_message} unavailable"
+msgstr ""
+
+msgid "%{lets_encrypt_link_start}Let's Encrypt%{lets_encrypt_link_end} is a free, automated, and open certificate authority (CA), that give digital certificates in order to enable HTTPS (SSL/TLS) for websites."
+msgstr ""
+
+msgid "%{level_name} is not allowed in a %{group_level_name} group."
+msgstr ""
+
+msgid "%{level_name} is not allowed since the fork source project has lower visibility."
+msgstr ""
+
+msgid "%{lineOneStart}Drag and drop to upload your designs%{lineOneEnd} or %{linkStart}click to upload%{linkEnd}."
+msgstr ""
+
+msgid "%{link_start}Learn more%{link_end} about what information is shared with GitLab Inc."
+msgstr ""
+
+msgid "%{link_start}Read more%{link_end} about role permissions"
+msgstr ""
+
+msgid "%{listToShow}, and %{awardsListLength} more."
+msgstr ""
+
+msgid "%{loadingIcon} Started"
+msgstr ""
+
+msgid "%{lock_path} is locked by GitLab User %{lock_user_id}"
+msgstr ""
+
+msgid "%{markdownDocsLinkStart}Markdown%{markdownDocsLinkEnd} and %{quickActionsDocsLinkStart}quick actions%{quickActionsDocsLinkEnd} are supported"
+msgstr ""
+
+msgid "%{mergeLength}/%{usersLength} can merge"
+msgstr ""
+
+msgid "%{mrText}, this issue will be closed automatically."
+msgstr ""
+
+msgid "%{name_with_link} has %{percent} or less Shared Runner Pipeline minutes remaining. Once it runs out, no new jobs or pipelines in its projects will run."
+msgstr ""
+
+msgid "%{name_with_link} has run out of Shared Runner Pipeline minutes so no new jobs or pipelines in its projects will run."
+msgstr ""
+
+msgid "%{namespace_name} is now read-only. You cannot: %{base_message}"
+msgstr ""
+
+msgid "%{name} contained %{resultsString}"
+msgstr ""
+
+msgid "%{name} found %{resultsString}"
+msgstr ""
+
+msgid "%{name} is already being used for another emoji"
+msgstr ""
+
+msgid "%{name} is scheduled for %{action}"
+msgstr ""
+
+msgid "%{name}'s avatar"
+msgstr ""
+
+msgid "%{name}(%{url}) has %{percent} or less Shared Runner Pipeline minutes remaining. Once it runs out, no new jobs or pipelines in its projects will run."
+msgstr ""
+
+msgid "%{name}(%{url}) has run out of Shared Runner Pipeline minutes so no new jobs or pipelines in its projects will run."
+msgstr ""
+
+msgid "%{no_of_days} day"
+msgid_plural "%{no_of_days} days"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{number_commits_behind} commits behind %{default_branch}, %{number_commits_ahead} commits ahead"
+msgstr ""
+
+msgid "%{openOrClose} %{noteable}"
+msgstr ""
+
+msgid "%{openedEpics} open, %{closedEpics} closed"
+msgstr ""
+
+msgid "%{openedIssues} open, %{closedIssues} closed"
+msgstr ""
+
+msgid "%{percentage}%% weight completed"
+msgstr ""
+
+msgid "%{percent}%% complete"
+msgstr ""
+
+msgid "%{percent}%{percentSymbol} complete"
+msgstr ""
+
+msgid "%{placeholder} is not a valid color scheme"
+msgstr ""
+
+msgid "%{placeholder} is not a valid theme"
+msgstr ""
+
+msgid "%{primary} (%{secondary})"
+msgstr ""
+
+msgid "%{ref} cannot be added: %{error}"
+msgstr ""
+
+msgid "%{releases} release"
+msgid_plural "%{releases} releases"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{remaining_approvals} left"
+msgstr ""
+
+msgid "%{reportType} %{status} detected %{critical} critical and %{high} high severity vulnerabilities out of %{total}."
+msgstr ""
+
+msgid "%{reportType} %{status} detected %{critical} critical and %{high} high severity vulnerabilities."
+msgstr ""
+
+msgid "%{reportType} %{status} detected %{critical} critical severity vulnerabilities out of %{total}."
+msgstr ""
+
+msgid "%{reportType} %{status} detected %{critical} critical severity vulnerability."
+msgid_plural "%{reportType} %{status} detected %{critical} critical severity vulnerabilities."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{reportType} %{status} detected %{high} high severity vulnerabilities out of %{total}."
+msgstr ""
+
+msgid "%{reportType} %{status} detected %{high} high severity vulnerability."
+msgid_plural "%{reportType} %{status} detected %{high} high severity vulnerabilities."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{reportType} %{status} detected %{other} vulnerability."
+msgid_plural "%{reportType} %{status} detected %{other} vulnerabilities."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{reportType} %{status} detected no new vulnerabilities."
+msgstr ""
+
+msgid "%{retryButtonStart}Try again%{retryButtonEnd} or %{newFileButtonStart}attach a new file%{newFileButtonEnd}"
+msgstr ""
+
+msgid "%{service_title} %{message}."
+msgstr ""
+
+msgid "%{size} GiB"
+msgstr ""
+
+msgid "%{size} KiB"
+msgstr ""
+
+msgid "%{size} MiB"
+msgstr ""
+
+msgid "%{size} bytes"
+msgstr ""
+
+msgid "%{spammable_titlecase} was submitted to Akismet successfully."
+msgstr ""
+
+msgid "%{spanStart}at line%{spanEnd} %{errorLine}%{errorColumn}"
+msgstr ""
+
+msgid "%{spanStart}in%{spanEnd} %{errorFn}"
+msgstr ""
+
+msgid "%{start} to %{end}"
+msgstr ""
+
+msgid "%{state} epics"
+msgstr ""
+
+msgid "%{strongStart}Note:%{strongEnd} Once a custom stage has been added you can re-order stages by dragging them into the desired position."
+msgstr ""
+
+msgid "%{strong_start}%{branch_count}%{strong_end} Branch"
+msgid_plural "%{strong_start}%{branch_count}%{strong_end} Branches"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{strong_start}%{commit_count}%{strong_end} Commit"
+msgid_plural "%{strong_start}%{commit_count}%{strong_end} Commits"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{strong_start}%{human_size}%{strong_end} Files"
+msgstr ""
+
+msgid "%{strong_start}%{human_size}%{strong_end} Storage"
+msgstr ""
+
+msgid "%{strong_start}%{release_count}%{strong_end} Release"
+msgid_plural "%{strong_start}%{release_count}%{strong_end} Releases"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{strong_start}%{tag_count}%{strong_end} Tag"
+msgid_plural "%{strong_start}%{tag_count}%{strong_end} Tags"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{tabname} changed"
+msgstr ""
+
+msgid "%{tags} tag per image name"
+msgstr ""
+
+msgid "%{tags} tags per image name"
+msgstr ""
+
+msgid "%{tag}-%{evidence}-%{filename}"
+msgstr ""
+
+msgid "%{template_project_id} is unknown or invalid"
+msgstr ""
+
+msgid "%{text} %{files}"
+msgid_plural "%{text} %{files} files"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{text} is available"
+msgstr ""
+
+msgid "%{timebox_name} should belong either to a project or a group."
+msgstr ""
+
+msgid "%{title} %{operator} %{threshold}"
+msgstr ""
+
+msgid "%{title} changes"
+msgstr ""
+
+msgid "%{token}..."
+msgstr ""
+
+msgid "%{totalCpu} (%{freeSpacePercentage}%{percentSymbol} free)"
+msgstr ""
+
+msgid "%{totalMemory} (%{freeSpacePercentage}%{percentSymbol} free)"
+msgstr ""
+
+msgid "%{totalWeight} total weight"
+msgstr ""
+
+msgid "%{total} open issue weight"
+msgstr ""
+
+msgid "%{total} open issues"
+msgstr ""
+
+msgid "%{usage_ping_link_start}Learn more%{usage_ping_link_end} about what information is shared with GitLab Inc."
+msgstr ""
+
+msgid "%{userName} (cannot merge)"
+msgstr ""
+
+msgid "%{userName}'s avatar"
+msgstr ""
+
+msgid "%{user_name} profile page"
+msgstr ""
+
+msgid "%{username}'s avatar"
+msgstr ""
+
+msgid "%{value} s"
+msgstr ""
+
+msgid "%{verb} %{time_spent_value} spent time."
+msgstr ""
+
+msgid "%{webhooks_link_start}%{webhook_type}%{link_end} enable you to send notifications to web applications in response to events in a group or project."
+msgstr ""
+
+msgid "%{webhooks_link_start}%{webhook_type}%{link_end} enable you to send notifications to web applications in response to events in a group or project. We recommend using an %{integrations_link_start}integration%{link_end} in preference to a webhook."
+msgstr ""
+
+msgid "'%{level}' is not a valid visibility level"
+msgstr ""
+
+msgid "'%{name}' stage already exists"
+msgstr ""
+
+msgid "'%{source}' is not a import source"
+msgstr ""
+
+msgid "'%{template_name}' is unknown or invalid"
+msgstr ""
+
+msgid "(%d closed)"
+msgid_plural "(%d closed)"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "(%{linkStart}Cron syntax%{linkEnd})"
+msgstr ""
+
+msgid "(%{mrCount} merged)"
+msgstr ""
+
+msgid "(No changes)"
+msgstr ""
+
+msgid "(check progress)"
+msgstr ""
+
+msgid "(external source)"
+msgstr ""
+
+msgid "(removed)"
+msgstr ""
+
+msgid "(revoked)"
+msgstr ""
+
+msgid "*"
+msgstr ""
+
+msgid "+ %{amount} more"
+msgstr ""
+
+msgid "+ %{count} more"
+msgstr ""
+
+msgid "+ %{moreCount} more"
+msgstr ""
+
+msgid "+ %{numberOfHiddenAssignees} more"
+msgstr ""
+
+msgid "+%d more"
+msgid_plural "+%d more"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "+%{approvers} more approvers"
+msgstr ""
+
+msgid "+%{tags} more"
+msgstr ""
+
+msgid ", or "
+msgstr ""
+
+msgid "- Event"
+msgid_plural "- Events"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "- Runner is active and can process any new jobs"
+msgstr ""
+
+msgid "- Runner is paused and will not receive any new jobs"
+msgstr ""
+
+msgid "- User"
+msgid_plural "- Users"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "- of - weight completed"
+msgstr ""
+
+msgid "- show less"
+msgstr ""
+
+msgid "0 for unlimited"
+msgstr ""
+
+msgid "0 for unlimited, only effective with remote storage enabled."
+msgstr ""
+
+msgid "1 %{type} addition"
+msgid_plural "%{count} %{type} additions"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "1 %{type} modification"
+msgid_plural "%{count} %{type} modifications"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "1 Day"
+msgid_plural "%d Days"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "1 closed issue"
+msgid_plural "%{issues} closed issues"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "1 closed merge request"
+msgid_plural "%{merge_requests} closed merge requests"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "1 day"
+msgid_plural "%d days"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "1 group"
+msgid_plural "%d groups"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "1 hour"
+msgid_plural "%d hours"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "1 merged merge request"
+msgid_plural "%{merge_requests} merged merge requests"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "1 minute"
+msgid_plural "%d minutes"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "1 month"
+msgstr ""
+
+msgid "1 open issue"
+msgid_plural "%{issues} open issues"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "1 open merge request"
+msgid_plural "%{merge_requests} open merge requests"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "1 pipeline"
+msgid_plural "%d pipelines"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "1 role"
+msgid_plural "%d roles"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "1 user"
+msgid_plural "%{num} users"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "1 week"
+msgstr ""
+
+msgid "1-9 contributions"
+msgstr ""
+
+msgid "10-19 contributions"
+msgstr ""
+
+msgid "1st contribution!"
+msgstr ""
+
+msgid "20-29 contributions"
+msgstr ""
+
+msgid "2FA"
+msgstr ""
+
+msgid "2FADevice|Registered On"
+msgstr ""
+
+msgid "3 days"
+msgstr ""
+
+msgid "3 hours"
+msgstr ""
+
+msgid "30 minutes"
+msgstr ""
+
+msgid "30+ contributions"
+msgstr ""
+
+msgid "403|Please contact your GitLab administrator to get permission."
+msgstr ""
+
+msgid "403|You don't have the permission to access this page."
+msgstr ""
+
+msgid "404|Make sure the address is correct and the page hasn't moved."
+msgstr ""
+
+msgid "404|Page Not Found"
+msgstr ""
+
+msgid "404|Please contact your GitLab administrator if you think this is a mistake."
+msgstr ""
+
+msgid "8 hours"
+msgstr ""
+
+msgid ":%{startLine} to %{endLine}"
+msgstr ""
+
+msgid "< 1 hour"
+msgstr ""
+
+msgid "<code>\"johnsmith@example.com\": \"@johnsmith\"</code> will add \"By <a href=\"#\">@johnsmith</a>\" to all issues and comments originally created by johnsmith@example.com, and will set <a href=\"#\">@johnsmith</a> as the assignee on all issues originally assigned to johnsmith@example.com."
+msgstr ""
+
+msgid "<code>\"johnsmith@example.com\": \"John Smith\"</code> will add \"By John Smith\" to all issues and comments originally created by johnsmith@example.com."
+msgstr ""
+
+msgid "<code>\"johnsmith@example.com\": \"johnsm...@example.com\"</code> will add \"By johnsm...@example.com\" to all issues and comments originally created by johnsmith@example.com. The email address or username is masked to ensure the user's privacy."
+msgstr ""
+
+msgid "<code>\"johnsmith@example.com\": \"johnsmith@example.com\"</code> will add \"By <a href=\"#\">johnsmith@example.com</a>\" to all issues and comments originally created by johnsmith@example.com. By default, the email address or username is masked to ensure the user's privacy. Use this option if you want to show the full email address."
+msgstr ""
+
+msgid "<code>Masked</code> to prevent the values from being displayed in job logs (must match certain regexp requirements)."
+msgstr ""
+
+msgid "<code>Protected</code> to expose them to protected branches or tags only."
+msgstr ""
+
+msgid "<namespace / project>"
+msgstr ""
+
+msgid "<no name set>"
+msgstr ""
+
+msgid "<no scopes selected>"
+msgstr ""
+
+msgid "<project name>"
+msgstr ""
+
+msgid "<strong>%{group_name}</strong> group members"
+msgstr ""
+
+msgid "<strong>%{label_name}</strong> <span>will be permanently deleted from %{subject_name}. This cannot be undone.</span>"
+msgstr ""
+
+msgid "<strong>Deletes</strong> source branch"
+msgstr ""
+
+msgid "A 'Runner' is a process which runs a job. You can set up as many Runners as you need."
+msgstr ""
+
+msgid "A .NET Core console application template, customizable for any .NET Core project"
+msgstr ""
+
+msgid "A GitBook site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
+msgid "A Hexo site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
+msgid "A Hugo site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
+msgid "A Jekyll site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
+msgid "A Let's Encrypt SSL certificate can not be obtained until your domain is verified."
+msgstr ""
+
+msgid "A Let's Encrypt account will be configured for this GitLab installation using your email address. You will receive emails to warn of expiring certificates."
+msgstr ""
+
+msgid "A basic page and serverless function that uses AWS Lambda, AWS API Gateway, and GitLab Pages"
+msgstr ""
+
+msgid "A complete DevOps platform"
+msgstr ""
+
+msgid "A default branch cannot be chosen for an empty project."
+msgstr ""
+
+msgid "A deleted user"
+msgstr ""
+
+msgid "A file has been changed."
+msgstr ""
+
+msgid "A file was not found."
+msgstr ""
+
+msgid "A file with '%{file_name}' already exists in %{branch} branch"
+msgstr ""
+
+msgid "A fork is a copy of a project.<br />Forking a repository allows you to make changes without affecting the original project."
+msgstr ""
+
+msgid "A group is a collection of several projects"
+msgstr ""
+
+msgid "A group represents your organization in GitLab. Groups allow you to manage users and collaborate across multiple projects."
+msgstr ""
+
+msgid "A member of the abuse team will review your report as soon as possible."
+msgstr ""
+
+msgid "A merge request approval is required when a security report contains a new vulnerability of high, critical, or unknown severity."
+msgstr ""
+
+msgid "A merge request approval is required when the license compliance report contains a blacklisted license."
+msgstr ""
+
+msgid "A new Auto DevOps pipeline has been created, go to %{pipelines_link_start}Pipelines page%{pipelines_link_end} for details"
+msgstr ""
+
+msgid "A new Release %{tag} for %{name} was published. Visit the %{release_link_start}Releases page%{release_link_end} to read more about it."
+msgstr ""
+
+msgid "A new Release %{tag} for %{name} was published. Visit the Releases page to read more about it:"
+msgstr ""
+
+msgid "A new branch will be created in your fork and a new merge request will be started."
+msgstr ""
+
+msgid "A new impersonation token has been created."
+msgstr ""
+
+msgid "A plain HTML site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
+msgid "A project boilerplate for Salesforce App development with Salesforce Developer tools."
+msgstr ""
+
+msgid "A project containing issues for each audit inquiry in the HIPAA Audit Protocol published by the U.S. Department of Health & Human Services"
+msgstr ""
+
+msgid "A project is where you house your files (repository), plan your work (issues), and publish your documentation (wiki), %{among_other_things_link}."
+msgstr ""
+
+msgid "A ready-to-go template for use with Android apps."
+msgstr ""
+
+msgid "A ready-to-go template for use with iOS Swift apps."
+msgstr ""
+
+msgid "A regular expression that will be used to find the test coverage output in the job log. Leave blank to disable"
+msgstr ""
+
+msgid "A secure token that identifies an external storage request."
+msgstr ""
+
+msgid "A sign-in to your account has been made from the following IP address: %{ip}"
+msgstr ""
+
+msgid "A subscription will trigger a new pipeline on the default branch of this project when a pipeline successfully completes for a new tag on the %{default_branch_docs} of the subscribed project."
+msgstr ""
+
+msgid "A suggestion is not applicable."
+msgstr ""
+
+msgid "A user with write access to the source branch selected this option"
+msgstr ""
+
+msgid "ACTION REQUIRED: Something went wrong while obtaining the Let's Encrypt certificate for GitLab Pages domain '%{domain}'"
+msgstr ""
+
+msgid "API Token"
+msgstr ""
+
+msgid "AWS Access Key"
+msgstr ""
+
+msgid "AWS Access Key. Only required if not using role instance credentials"
+msgstr ""
+
+msgid "AWS Secret Access Key"
+msgstr ""
+
+msgid "AWS Secret Access Key. Only required if not using role instance credentials"
+msgstr ""
+
+msgid "Abort"
+msgstr ""
+
+msgid "About GitLab"
+msgstr ""
+
+msgid "About GitLab CE"
+msgstr ""
+
+msgid "About auto deploy"
+msgstr ""
+
+msgid "About this feature"
+msgstr ""
+
+msgid "Abuse Reports"
+msgstr ""
+
+msgid "Abuse reports"
+msgstr ""
+
+msgid "Accept invitation"
+msgstr ""
+
+msgid "Accept terms"
+msgstr ""
+
+msgid "Acceptable for use in this project"
+msgstr ""
+
+msgid "Access Tokens"
+msgstr ""
+
+msgid "Access denied for your LDAP account."
+msgstr ""
+
+msgid "Access denied! Please verify you can add deploy keys to this repository."
+msgstr ""
+
+msgid "Access expiration date"
+msgstr ""
+
+msgid "Access forbidden. Check your access level."
+msgstr ""
+
+msgid "Access to '%{classification_label}' not allowed"
+msgstr ""
+
+msgid "Access to Pages websites are controlled based on the user's membership to a given project. By checking this box, users will be required to be logged in to have access to all Pages websites in your instance."
+msgstr ""
+
+msgid "AccessDropdown|Groups"
+msgstr ""
+
+msgid "AccessDropdown|Roles"
+msgstr ""
+
+msgid "AccessDropdown|Users"
+msgstr ""
+
+msgid "AccessTokens|Access Tokens"
+msgstr ""
+
+msgid "AccessTokens|Are you sure?"
+msgstr ""
+
+msgid "AccessTokens|Are you sure? Any RSS or calendar URLs currently in use will stop working."
+msgstr ""
+
+msgid "AccessTokens|Are you sure? Any issue email addresses currently in use will stop working."
+msgstr ""
+
+msgid "AccessTokens|Created"
+msgstr ""
+
+msgid "AccessTokens|Feed token"
+msgstr ""
+
+msgid "AccessTokens|Incoming email token"
+msgstr ""
+
+msgid "AccessTokens|It cannot be used to access any other data."
+msgstr ""
+
+msgid "AccessTokens|Keep this token secret. Anyone who gets ahold of it can access repository static objects as if they were you. You should %{reset_link_start}reset it%{reset_link_end} if that ever happens."
+msgstr ""
+
+msgid "AccessTokens|Keep this token secret. Anyone who gets ahold of it can create issues as if they were you. You should %{link_reset_it} if that ever happens."
+msgstr ""
+
+msgid "AccessTokens|Keep this token secret. Anyone who gets ahold of it can read activity and issue RSS feeds or your calendar feed as if they were you. You should %{link_reset_it} if that ever happens."
+msgstr ""
+
+msgid "AccessTokens|Personal Access Tokens"
+msgstr ""
+
+msgid "AccessTokens|Static object token"
+msgstr ""
+
+msgid "AccessTokens|They are the only accepted password when you have Two-Factor Authentication (2FA) enabled."
+msgstr ""
+
+msgid "AccessTokens|You can also use personal access tokens to authenticate against Git over HTTP."
+msgstr ""
+
+msgid "AccessTokens|You can generate a personal access token for each application you use that needs access to the GitLab API."
+msgstr ""
+
+msgid "AccessTokens|Your feed token is used to authenticate you when your RSS reader loads a personalized RSS feed or when your calendar application loads a personalized calendar, and is included in those feed URLs."
+msgstr ""
+
+msgid "AccessTokens|Your incoming email token is used to authenticate you when you create a new issue by email, and is included in your personal project-specific email addresses."
+msgstr ""
+
+msgid "AccessTokens|Your static object token is used to authenticate you when repository static objects (e.g. archives, blobs, ...) are being served from an external storage."
+msgstr ""
+
+msgid "AccessTokens|reset it"
+msgstr ""
+
+msgid "AccessibilityReport|Learn More"
+msgstr ""
+
+msgid "AccessibilityReport|Message: %{message}"
+msgstr ""
+
+msgid "AccessibilityReport|New"
+msgstr ""
+
+msgid "AccessibilityReport|The accessibility scanning found an error of the following type: %{code}"
+msgstr ""
+
+msgid "Account"
+msgstr ""
+
+msgid "Account ID"
+msgstr ""
+
+msgid "Account and limit"
+msgstr ""
+
+msgid "Account: %{account}"
+msgstr ""
+
+msgid "Action to take when receiving an alert."
+msgstr ""
+
+msgid "Actions"
+msgstr ""
+
+msgid "Activate"
+msgstr ""
+
+msgid "Activate Service Desk"
+msgstr ""
+
+msgid "Active"
+msgstr ""
+
+msgid "Active %{type} (%{token_length})"
+msgstr ""
+
+msgid "Active Sessions"
+msgstr ""
+
+msgid "Active Users:"
+msgstr ""
+
+msgid "Active users"
+msgstr ""
+
+msgid "Activity"
+msgstr ""
+
+msgid "Add"
+msgstr ""
+
+msgid "Add %d issue"
+msgid_plural "Add %d issues"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Add %{linkStart}assets%{linkEnd} to your Release. GitLab automatically includes read-only assets, like source code and release evidence."
+msgstr ""
+
+msgid "Add CHANGELOG"
+msgstr ""
+
+msgid "Add CONTRIBUTING"
+msgstr ""
+
+msgid "Add GitLab to Slack"
+msgstr ""
+
+msgid "Add Group Webhooks and GitLab Enterprise Edition."
+msgstr ""
+
+msgid "Add Jaeger URL"
+msgstr ""
+
+msgid "Add Kubernetes cluster"
+msgstr ""
+
+msgid "Add LICENSE"
+msgstr ""
+
+msgid "Add New Node"
+msgstr ""
+
+msgid "Add README"
+msgstr ""
+
+msgid "Add Variable"
+msgstr ""
+
+msgid "Add Zoom meeting"
+msgstr ""
+
+msgid "Add a %{type}"
+msgstr ""
+
+msgid "Add a GPG key"
+msgstr ""
+
+msgid "Add a Grafana button in the admin sidebar, monitoring section, to access a variety of statistics on the health and performance of GitLab."
+msgstr ""
+
+msgid "Add a To Do"
+msgstr ""
+
+msgid "Add a bullet list"
+msgstr ""
+
+msgid "Add a comment to this line"
+msgstr ""
+
+msgid "Add a general comment to this %{noteableDisplayName}."
+msgstr ""
+
+msgid "Add a general comment to this %{noteable_name}."
+msgstr ""
+
+msgid "Add a homepage to your wiki that contains information about your project and GitLab will display it here instead of this message."
+msgstr ""
+
+msgid "Add a line"
+msgstr ""
+
+msgid "Add a link"
+msgstr ""
+
+msgid "Add a new issue"
+msgstr ""
+
+msgid "Add a numbered list"
+msgstr ""
+
+msgid "Add a table"
+msgstr ""
+
+msgid "Add a task list"
+msgstr ""
+
+msgid "Add additional text to appear in all email communications. %{character_limit} character limit"
+msgstr ""
+
+msgid "Add an SSH key"
+msgstr ""
+
+msgid "Add an existing issue"
+msgstr ""
+
+msgid "Add an impersonation token"
+msgstr ""
+
+msgid "Add an issue"
+msgstr ""
+
+msgid "Add another link"
+msgstr ""
+
+msgid "Add approval rule"
+msgstr ""
+
+msgid "Add approvers"
+msgstr ""
+
+msgid "Add bold text"
+msgstr ""
+
+msgid "Add child epic to an epic"
+msgstr ""
+
+msgid "Add comment now"
+msgstr ""
+
+msgid "Add domain"
+msgstr ""
+
+msgid "Add email address"
+msgstr ""
+
+msgid "Add environment"
+msgstr ""
+
+msgid "Add header and footer to emails. Please note that color settings will only be applied within the application interface"
+msgstr ""
+
+msgid "Add image comment"
+msgstr ""
+
+msgid "Add issues"
+msgstr ""
+
+msgid "Add italic text"
+msgstr ""
+
+msgid "Add label(s)"
+msgstr ""
+
+msgid "Add license"
+msgstr ""
+
+msgid "Add list"
+msgstr ""
+
+msgid "Add new application"
+msgstr ""
+
+msgid "Add new directory"
+msgstr ""
+
+msgid "Add or subtract spent time"
+msgstr ""
+
+msgid "Add reaction"
+msgstr ""
+
+msgid "Add request manually"
+msgstr ""
+
+msgid "Add strikethrough text"
+msgstr ""
+
+msgid "Add suggestion to batch"
+msgstr ""
+
+msgid "Add system hook"
+msgstr ""
+
+msgid "Add to Slack"
+msgstr ""
+
+msgid "Add to epic"
+msgstr ""
+
+msgid "Add to merge train"
+msgstr ""
+
+msgid "Add to merge train when pipeline succeeds"
+msgstr ""
+
+msgid "Add to review"
+msgstr ""
+
+msgid "Add to tree"
+msgstr ""
+
+msgid "Add user(s) to the group:"
+msgstr ""
+
+msgid "Add users to group"
+msgstr ""
+
+msgid "Add variable"
+msgstr ""
+
+msgid "Add webhook"
+msgstr ""
+
+msgid "AddMember|No users specified."
+msgstr ""
+
+msgid "AddMember|Too many users specified (limit is %{user_limit})"
+msgstr ""
+
+msgid "Added"
+msgstr ""
+
+msgid "Added %{epic_ref} as a child epic."
+msgstr ""
+
+msgid "Added %{label_references} %{label_text}."
+msgstr ""
+
+msgid "Added a To Do."
+msgstr ""
+
+msgid "Added an issue to an epic."
+msgstr ""
+
+msgid "Added at"
+msgstr ""
+
+msgid "Added for this merge request"
+msgstr ""
+
+msgid "Added in this version"
+msgstr ""
+
+msgid "Adding new applications is disabled in your GitLab instance. Please contact your GitLab administrator to get the permission"
+msgstr ""
+
+msgid "Additional minutes"
+msgstr ""
+
+msgid "Additional text"
+msgstr ""
+
+msgid "Adds"
+msgstr ""
+
+msgid "Adds %{epic_ref} as child epic."
+msgstr ""
+
+msgid "Adds %{labels} %{label_text}."
+msgstr ""
+
+msgid "Adds a To Do."
+msgstr ""
+
+msgid "Adds a Zoom meeting"
+msgstr ""
+
+msgid "Adds an issue to an epic."
+msgstr ""
+
+msgid "Adjust your filters/search criteria above. If you believe this may be an error, please refer to the %{linkStart}Geo Troubleshooting%{linkEnd} documentation for more information."
+msgstr ""
+
+msgid "Admin Area"
+msgstr ""
+
+msgid "Admin Note"
+msgstr ""
+
+msgid "Admin Notifications"
+msgstr ""
+
+msgid "Admin Overview"
+msgstr ""
+
+msgid "Admin mode already enabled"
+msgstr ""
+
+msgid "Admin mode disabled"
+msgstr ""
+
+msgid "Admin mode enabled"
+msgstr ""
+
+msgid "Admin notes"
+msgstr ""
+
+msgid "AdminArea|Active users"
+msgstr ""
+
+msgid "AdminArea|Billable users"
+msgstr ""
+
+msgid "AdminArea|Blocked users"
+msgstr ""
+
+msgid "AdminArea|Bots"
+msgstr ""
+
+msgid "AdminArea|Developer"
+msgstr ""
+
+msgid "AdminArea|Guest"
+msgstr ""
+
+msgid "AdminArea|Included Free in license"
+msgstr ""
+
+msgid "AdminArea|Maintainer"
+msgstr ""
+
+msgid "AdminArea|Owner"
+msgstr ""
+
+msgid "AdminArea|Reporter"
+msgstr ""
+
+msgid "AdminArea|Stop all jobs"
+msgstr ""
+
+msgid "AdminArea|Stop all jobs?"
+msgstr ""
+
+msgid "AdminArea|Stop jobs"
+msgstr ""
+
+msgid "AdminArea|Stopping jobs failed"
+msgstr ""
+
+msgid "AdminArea|Total users"
+msgstr ""
+
+msgid "AdminArea|Users statistics"
+msgstr ""
+
+msgid "AdminArea|Users with highest role"
+msgstr ""
+
+msgid "AdminArea|Users without a Group and Project"
+msgstr ""
+
+msgid "AdminArea|You’re about to stop all jobs.This will halt all current jobs that are running."
+msgstr ""
+
+msgid "AdminDashboard|Error loading the statistics. Please try again"
+msgstr ""
+
+msgid "AdminNote|Note"
+msgstr ""
+
+msgid "AdminProjects| You’re about to permanently delete the project %{projectName}, its repository, and all related resources including issues, merge requests, etc.. Once you confirm and press %{strong_start}Delete project%{strong_end}, it cannot be undone or recovered."
+msgstr ""
+
+msgid "AdminProjects|Delete"
+msgstr ""
+
+msgid "AdminProjects|Delete Project %{projectName}?"
+msgstr ""
+
+msgid "AdminProjects|Delete project"
+msgstr ""
+
+msgid "AdminSettings|Apply integration settings to all Projects"
+msgstr ""
+
+msgid "AdminSettings|Auto DevOps domain"
+msgstr ""
+
+msgid "AdminSettings|Elasticsearch, PlantUML, Slack application, Third party offers, Snowplow, Amazon EKS have moved to Settings > General."
+msgstr ""
+
+msgid "AdminSettings|Enable shared runners for new projects"
+msgstr ""
+
+msgid "AdminSettings|Environment variables are protected by default"
+msgstr ""
+
+msgid "AdminSettings|Go to General Settings"
+msgstr ""
+
+msgid "AdminSettings|Integrations configured here will automatically apply to all projects on this instance."
+msgstr ""
+
+msgid "AdminSettings|Moved to integrations"
+msgstr ""
+
+msgid "AdminSettings|No required pipeline"
+msgstr ""
+
+msgid "AdminSettings|Required pipeline configuration"
+msgstr ""
+
+msgid "AdminSettings|Select a pipeline configuration file"
+msgstr ""
+
+msgid "AdminSettings|Select a template"
+msgstr ""
+
+msgid "AdminSettings|Service template allows you to set default values for integrations"
+msgstr ""
+
+msgid "AdminSettings|Set an instance-wide auto included %{link_start}pipeline configuration%{link_end}. This pipeline configuration will be run after the project's own configuration."
+msgstr ""
+
+msgid "AdminSettings|Some settings have moved"
+msgstr ""
+
+msgid "AdminSettings|Specify a domain to use by default for every project's Auto Review Apps and Auto Deploy stages."
+msgstr ""
+
+msgid "AdminSettings|The required pipeline configuration can be selected from the %{code_start}gitlab-ci%{code_end} directory inside of the configured %{link_start}instance template repository%{link_end} or from GitLab provided configurations."
+msgstr ""
+
+msgid "AdminSettings|When creating a new environment variable it will be protected by default."
+msgstr ""
+
+msgid "AdminStatistics|Active Users"
+msgstr ""
+
+msgid "AdminStatistics|Forks"
+msgstr ""
+
+msgid "AdminStatistics|Issues"
+msgstr ""
+
+msgid "AdminStatistics|Merge Requests"
+msgstr ""
+
+msgid "AdminStatistics|Milestones"
+msgstr ""
+
+msgid "AdminStatistics|Notes"
+msgstr ""
+
+msgid "AdminStatistics|SSH Keys"
+msgstr ""
+
+msgid "AdminStatistics|Snippets"
+msgstr ""
+
+msgid "AdminUsers|2FA Disabled"
+msgstr ""
+
+msgid "AdminUsers|2FA Enabled"
+msgstr ""
+
+msgid "AdminUsers|Active"
+msgstr ""
+
+msgid "AdminUsers|Admin"
+msgstr ""
+
+msgid "AdminUsers|Admins"
+msgstr ""
+
+msgid "AdminUsers|Block"
+msgstr ""
+
+msgid "AdminUsers|Block user"
+msgstr ""
+
+msgid "AdminUsers|Block user %{username}?"
+msgstr ""
+
+msgid "AdminUsers|Blocked"
+msgstr ""
+
+msgid "AdminUsers|Blocking user has the following effects:"
+msgstr ""
+
+msgid "AdminUsers|Cannot unblock LDAP blocked users"
+msgstr ""
+
+msgid "AdminUsers|Deactivate"
+msgstr ""
+
+msgid "AdminUsers|Deactivate User %{username}?"
+msgstr ""
+
+msgid "AdminUsers|Deactivate user"
+msgstr ""
+
+msgid "AdminUsers|Deactivated"
+msgstr ""
+
+msgid "AdminUsers|Deactivating a user has the following effects:"
+msgstr ""
+
+msgid "AdminUsers|Delete User %{username} and contributions?"
+msgstr ""
+
+msgid "AdminUsers|Delete User %{username}?"
+msgstr ""
+
+msgid "AdminUsers|Delete user"
+msgstr ""
+
+msgid "AdminUsers|Delete user and contributions"
+msgstr ""
+
+msgid "AdminUsers|External"
+msgstr ""
+
+msgid "AdminUsers|Is using seat"
+msgstr ""
+
+msgid "AdminUsers|It's you!"
+msgstr ""
+
+msgid "AdminUsers|New user"
+msgstr ""
+
+msgid "AdminUsers|No users found"
+msgstr ""
+
+msgid "AdminUsers|Owned groups will be left"
+msgstr ""
+
+msgid "AdminUsers|Personal projects will be left"
+msgstr ""
+
+msgid "AdminUsers|Personal projects, group and user history will be left intact"
+msgstr ""
+
+msgid "AdminUsers|Reactivating a user will:"
+msgstr ""
+
+msgid "AdminUsers|Restore user access to the account, including web, Git and API."
+msgstr ""
+
+msgid "AdminUsers|Search by name, email or username"
+msgstr ""
+
+msgid "AdminUsers|Search users"
+msgstr ""
+
+msgid "AdminUsers|Send email to users"
+msgstr ""
+
+msgid "AdminUsers|Sort by"
+msgstr ""
+
+msgid "AdminUsers|The user will be logged out"
+msgstr ""
+
+msgid "AdminUsers|The user will not be able to access git repositories"
+msgstr ""
+
+msgid "AdminUsers|The user will not be able to access the API"
+msgstr ""
+
+msgid "AdminUsers|The user will not be able to use slash commands"
+msgstr ""
+
+msgid "AdminUsers|The user will not receive any notifications"
+msgstr ""
+
+msgid "AdminUsers|To confirm, type %{projectName}"
+msgstr ""
+
+msgid "AdminUsers|To confirm, type %{username}"
+msgstr ""
+
+msgid "AdminUsers|User will not be able to access git repositories"
+msgstr ""
+
+msgid "AdminUsers|User will not be able to login"
+msgstr ""
+
+msgid "AdminUsers|When the user logs back in, their account will reactivate as a fully active account"
+msgstr ""
+
+msgid "AdminUsers|Without projects"
+msgstr ""
+
+msgid "AdminUsers|You are about to permanently delete the user %{username}. Issues, merge requests, and groups linked to them will be transferred to a system-wide \"Ghost-user\". To avoid data loss, consider using the %{strong_start}block user%{strong_end} feature instead. Once you %{strong_start}Delete user%{strong_end}, it cannot be undone or recovered."
+msgstr ""
+
+msgid "AdminUsers|You are about to permanently delete the user %{username}. This will delete all of the issues, merge requests, and groups linked to them. To avoid data loss, consider using the %{strong_start}block user%{strong_end} feature instead. Once you %{strong_start}Delete user%{strong_end}, it cannot be undone or recovered."
+msgstr ""
+
+msgid "Administration"
+msgstr ""
+
+msgid "Advanced"
+msgstr ""
+
+msgid "Advanced Settings"
+msgstr ""
+
+msgid "Advanced permissions, Large File Storage and Two-Factor authentication settings."
+msgstr ""
+
+msgid "Advanced search functionality"
+msgstr ""
+
+msgid "After a successful password update you will be redirected to login screen."
+msgstr ""
+
+msgid "After a successful password update, you will be redirected to the login page where you can log in with your new password."
+msgstr ""
+
+msgid "After that, you will not to be able to use merge approvals or code quality as well as many other features."
+msgstr ""
+
+msgid "After that, you will not to be able to use merge approvals or epics as well as many other features."
+msgstr ""
+
+msgid "After that, you will not to be able to use merge approvals or epics as well as many security features."
+msgstr ""
+
+msgid "Alert"
+msgid_plural "Alerts"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "AlertManagement| assign yourself"
+msgstr ""
+
+msgid "AlertManagement|Acknowledged"
+msgstr ""
+
+msgid "AlertManagement|Alert"
+msgstr ""
+
+msgid "AlertManagement|Alert assignee(s): %{assignees}"
+msgstr ""
+
+msgid "AlertManagement|Alert detail"
+msgstr ""
+
+msgid "AlertManagement|Alert details"
+msgstr ""
+
+msgid "AlertManagement|Alert status: %{status}"
+msgstr ""
+
+msgid "AlertManagement|Alerts"
+msgstr ""
+
+msgid "AlertManagement|All alerts"
+msgstr ""
+
+msgid "AlertManagement|Assign To"
+msgstr ""
+
+msgid "AlertManagement|Assign status"
+msgstr ""
+
+msgid "AlertManagement|Assignee"
+msgstr ""
+
+msgid "AlertManagement|Assignees"
+msgstr ""
+
+msgid "AlertManagement|Authorize external service"
+msgstr ""
+
+msgid "AlertManagement|Create issue"
+msgstr ""
+
+msgid "AlertManagement|Critical"
+msgstr ""
+
+msgid "AlertManagement|Display alerts from all your monitoring tools directly within GitLab. Streamline the investigation of your alerts and the escalation of alerts to incidents."
+msgstr ""
+
+msgid "AlertManagement|Edit"
+msgstr ""
+
+msgid "AlertManagement|Events"
+msgstr ""
+
+msgid "AlertManagement|High"
+msgstr ""
+
+msgid "AlertManagement|Info"
+msgstr ""
+
+msgid "AlertManagement|Low"
+msgstr ""
+
+msgid "AlertManagement|Medium"
+msgstr ""
+
+msgid "AlertManagement|More information"
+msgstr ""
+
+msgid "AlertManagement|No Matching Results"
+msgstr ""
+
+msgid "AlertManagement|No alert data to display."
+msgstr ""
+
+msgid "AlertManagement|No alerts available to display. See %{linkStart}enabling alert management%{linkEnd} for more information on adding alerts to the list."
+msgstr ""
+
+msgid "AlertManagement|No alerts to display."
+msgstr ""
+
+msgid "AlertManagement|None"
+msgstr ""
+
+msgid "AlertManagement|None -"
+msgstr ""
+
+msgid "AlertManagement|Open"
+msgstr ""
+
+msgid "AlertManagement|Overview"
+msgstr ""
+
+msgid "AlertManagement|Reported %{when}"
+msgstr ""
+
+msgid "AlertManagement|Reported %{when} by %{tool}"
+msgstr ""
+
+msgid "AlertManagement|Resolved"
+msgstr ""
+
+msgid "AlertManagement|Service"
+msgstr ""
+
+msgid "AlertManagement|Severity"
+msgstr ""
+
+msgid "AlertManagement|Start time"
+msgstr ""
+
+msgid "AlertManagement|Status"
+msgstr ""
+
+msgid "AlertManagement|Surface alerts in GitLab"
+msgstr ""
+
+msgid "AlertManagement|There was an error displaying the alert. Please refresh the page to try again."
+msgstr ""
+
+msgid "AlertManagement|There was an error displaying the alerts. Confirm your endpoint's configuration details to ensure alerts appear."
+msgstr ""
+
+msgid "AlertManagement|There was an error while updating the assignee(s) list. Please try again."
+msgstr ""
+
+msgid "AlertManagement|There was an error while updating the assignee(s) of the alert. Please try again."
+msgstr ""
+
+msgid "AlertManagement|There was an error while updating the status of the alert. Please try again."
+msgstr ""
+
+msgid "AlertManagement|This assignee cannot be assigned to this alert."
+msgstr ""
+
+msgid "AlertManagement|Tool"
+msgstr ""
+
+msgid "AlertManagement|Triggered"
+msgstr ""
+
+msgid "AlertManagement|Unassigned"
+msgstr ""
+
+msgid "AlertManagement|Unknown"
+msgstr ""
+
+msgid "AlertManagement|View issue"
+msgstr ""
+
+msgid "AlertService|Review your external service's documentation to learn where to provide this information to your external service, and the %{linkStart}GitLab documentation%{linkEnd} to learn more about configuring your endpoint."
+msgstr ""
+
+msgid "AlertService|You must provide this URL and authorization key to authorize an external service to send alerts to GitLab. You can provide this URL and key to multiple services. After configuring an external service, alerts from your service will display on the GitLab %{linkStart}Alerts%{linkEnd} page."
+msgstr ""
+
+msgid "Alerts"
+msgstr ""
+
+msgid "Alerts endpoint"
+msgstr ""
+
+msgid "Algorithm"
+msgstr ""
+
+msgid "All"
+msgstr ""
+
+msgid "All %{replicableType} are being scheduled for %{action}"
+msgstr ""
+
+msgid "All (default)"
+msgstr ""
+
+msgid "All Members"
+msgstr ""
+
+msgid "All branches"
+msgstr ""
+
+msgid "All changes are committed"
+msgstr ""
+
+msgid "All default stages are currently visible"
+msgstr ""
+
+msgid "All email addresses will be used to identify your commits."
+msgstr ""
+
+msgid "All environments"
+msgstr ""
+
+msgid "All features are enabled for blank projects, from templates, or when importing, but you can disable them afterward in the project settings."
+msgstr ""
+
+msgid "All groups and projects"
+msgstr ""
+
+msgid "All issues for this milestone are closed."
+msgstr ""
+
+msgid "All issues for this milestone are closed. You may close this milestone now."
+msgstr ""
+
+msgid "All merge conflicts were resolved. The merge request can now be merged."
+msgstr ""
+
+msgid "All merge request dependencies have been merged"
+msgstr ""
+
+msgid "All paths are relative to the GitLab URL. Do not include %{relative_url_link_start}relative URL%{relative_url_link_end}."
+msgstr ""
+
+msgid "All projects"
+msgstr ""
+
+msgid "All security scans are enabled because %{linkStart}Auto DevOps%{linkEnd} is enabled on this project"
+msgstr ""
+
+msgid "All threads resolved"
+msgstr ""
+
+msgid "All users"
+msgstr ""
+
+msgid "All users must have a name."
+msgstr ""
+
+msgid "Allow \"%{group_name}\" to sign you in"
+msgstr ""
+
+msgid "Allow commits from members who can merge to the target branch."
+msgstr ""
+
+msgid "Allow group owners to manage LDAP-related settings"
+msgstr ""
+
+msgid "Allow only the selected protocols to be used for Git access."
+msgstr ""
+
+msgid "Allow owners to manage default branch protection per group"
+msgstr ""
+
+msgid "Allow owners to manually add users outside of LDAP"
+msgstr ""
+
+msgid "Allow projects within this group to use Git LFS"
+msgstr ""
+
+msgid "Allow public access to pipelines and job details, including output logs and artifacts"
+msgstr ""
+
+msgid "Allow rendering of PlantUML diagrams in Asciidoc documents."
+msgstr ""
+
+msgid "Allow repository mirroring to be configured by project maintainers"
+msgstr ""
+
+msgid "Allow requests to the local network from hooks and services."
+msgstr ""
+
+msgid "Allow requests to the local network from system hooks"
+msgstr ""
+
+msgid "Allow requests to the local network from web hooks and services"
+msgstr ""
+
+msgid "Allow this key to push to repository as well? (Default only allows pull access.)"
+msgstr ""
+
+msgid "Allow this secondary node to replicate content on Object Storage"
+msgstr ""
+
+msgid "Allow users to dismiss the broadcast message"
+msgstr ""
+
+msgid "Allow users to register any application to use GitLab as an OAuth provider"
+msgstr ""
+
+msgid "Allow users to request access (if visibility is public or internal)"
+msgstr ""
+
+msgid "Allowed Geo IP"
+msgstr ""
+
+msgid "Allowed email domain restriction only permitted for top-level groups"
+msgstr ""
+
+msgid "Allowed to fail"
+msgstr ""
+
+msgid "Allows you to add and manage Kubernetes clusters."
+msgstr ""
+
+msgid "Almost there"
+msgstr ""
+
+msgid "Also called \"Issuer\" or \"Relying party trust identifier\""
+msgstr ""
+
+msgid "Also called \"Relying party service URL\" or \"Reply URL\""
+msgstr ""
+
+msgid "Alternate support URL for help page and help dropdown"
+msgstr ""
+
+msgid "Alternatively, you can convert your account to a managed account by the %{group_name} group."
+msgstr ""
+
+msgid "Amazon EKS"
+msgstr ""
+
+msgid "Amazon EKS integration allows you to provision EKS clusters from GitLab."
+msgstr ""
+
+msgid "Amazon Web Services"
+msgstr ""
+
+msgid "Amazon Web Services Logo"
+msgstr ""
+
+msgid "Amazon authentication is not %{link_start}correctly configured%{link_end}. Ask your GitLab administrator if you want to use this service."
+msgstr ""
+
+msgid "Amount of time (in hours) that users are allowed to skip forced configuration of two-factor authentication"
+msgstr ""
+
+msgid "An alert has been triggered in %{project_path}."
+msgstr ""
+
+msgid "An application called %{link_to_client} is requesting access to your GitLab account."
+msgstr ""
+
+msgid "An email notification was recently sent from the admin panel. Please wait %{wait_time_in_words} before attempting to send another message."
+msgstr ""
+
+msgid "An empty GitLab User field will add the FogBugz user's full name (e.g. \"By John Smith\") in the description of all issues and comments. It will also associate and/or assign these issues and comments with the project creator."
+msgstr ""
+
+msgid "An error has occurred"
+msgstr ""
+
+msgid "An error occurred adding a draft to the thread."
+msgstr ""
+
+msgid "An error occurred adding a new draft."
+msgstr ""
+
+msgid "An error occurred creating the new branch."
+msgstr ""
+
+msgid "An error occurred fetching the approval rules."
+msgstr ""
+
+msgid "An error occurred fetching the approvers for the new rule."
+msgstr ""
+
+msgid "An error occurred fetching the dropdown data."
+msgstr ""
+
+msgid "An error occurred fetching the project authors."
+msgstr ""
+
+msgid "An error occurred previewing the blob"
+msgstr ""
+
+msgid "An error occurred when toggling the notification subscription"
+msgstr ""
+
+msgid "An error occurred when trying to resolve a comment. Please try again."
+msgstr ""
+
+msgid "An error occurred when trying to resolve a discussion. Please try again."
+msgstr ""
+
+msgid "An error occurred when updating the issue weight"
+msgstr ""
+
+msgid "An error occurred while acknowledging the notification. Refresh the page and try again."
+msgstr ""
+
+msgid "An error occurred while adding approvers"
+msgstr ""
+
+msgid "An error occurred while adding formatted title for epic"
+msgstr ""
+
+msgid "An error occurred while checking group path. Please refresh and try again."
+msgstr ""
+
+msgid "An error occurred while committing your changes."
+msgstr ""
+
+msgid "An error occurred while decoding the file."
+msgstr ""
+
+msgid "An error occurred while deleting the approvers group"
+msgstr ""
+
+msgid "An error occurred while deleting the comment"
+msgstr ""
+
+msgid "An error occurred while deleting the pipeline."
+msgstr ""
+
+msgid "An error occurred while detecting host keys"
+msgstr ""
+
+msgid "An error occurred while disabling Service Desk."
+msgstr ""
+
+msgid "An error occurred while dismissing the alert. Refresh the page and try again."
+msgstr ""
+
+msgid "An error occurred while dismissing the feature highlight. Refresh the page and try dismissing again."
+msgstr ""
+
+msgid "An error occurred while enabling Service Desk."
+msgstr ""
+
+msgid "An error occurred while fetching coverage reports."
+msgstr ""
+
+msgid "An error occurred while fetching environments."
+msgstr ""
+
+msgid "An error occurred while fetching exposed artifacts."
+msgstr ""
+
+msgid "An error occurred while fetching folder content."
+msgstr ""
+
+msgid "An error occurred while fetching issues."
+msgstr ""
+
+msgid "An error occurred while fetching label colors."
+msgstr ""
+
+msgid "An error occurred while fetching markdown preview"
+msgstr ""
+
+msgid "An error occurred while fetching pending comments"
+msgstr ""
+
+msgid "An error occurred while fetching projects autocomplete."
+msgstr ""
+
+msgid "An error occurred while fetching sidebar data"
+msgstr ""
+
+msgid "An error occurred while fetching terraform reports."
+msgstr ""
+
+msgid "An error occurred while fetching the Service Desk address."
+msgstr ""
+
+msgid "An error occurred while fetching the board lists. Please try again."
+msgstr ""
+
+msgid "An error occurred while fetching the builds."
+msgstr ""
+
+msgid "An error occurred while fetching the job log."
+msgstr ""
+
+msgid "An error occurred while fetching the job trace."
+msgstr ""
+
+msgid "An error occurred while fetching the job."
+msgstr ""
+
+msgid "An error occurred while fetching the jobs."
+msgstr ""
+
+msgid "An error occurred while fetching the latest pipeline."
+msgstr ""
+
+msgid "An error occurred while fetching the pipeline."
+msgstr ""
+
+msgid "An error occurred while fetching the releases. Please try again."
+msgstr ""
+
+msgid "An error occurred while fetching this tab."
+msgstr ""
+
+msgid "An error occurred while generating a username. Please try again."
+msgstr ""
+
+msgid "An error occurred while getting files for - %{branchId}"
+msgstr ""
+
+msgid "An error occurred while getting projects"
+msgstr ""
+
+msgid "An error occurred while importing project: %{details}"
+msgstr ""
+
+msgid "An error occurred while initializing path locks"
+msgstr ""
+
+msgid "An error occurred while loading all the files."
+msgstr ""
+
+msgid "An error occurred while loading chart data"
+msgstr ""
+
+msgid "An error occurred while loading commit signatures"
+msgstr ""
+
+msgid "An error occurred while loading designs. Please try again."
+msgstr ""
+
+msgid "An error occurred while loading diff"
+msgstr ""
+
+msgid "An error occurred while loading filenames"
+msgstr ""
+
+msgid "An error occurred while loading group members."
+msgstr ""
+
+msgid "An error occurred while loading issues"
+msgstr ""
+
+msgid "An error occurred while loading merge requests."
+msgstr ""
+
+msgid "An error occurred while loading milestones"
+msgstr ""
+
+msgid "An error occurred while loading project creation UI"
+msgstr ""
+
+msgid "An error occurred while loading the data. Please try again."
+msgstr ""
+
+msgid "An error occurred while loading the file"
+msgstr ""
+
+msgid "An error occurred while loading the file content."
+msgstr ""
+
+msgid "An error occurred while loading the file."
+msgstr ""
+
+msgid "An error occurred while loading the file. Please try again later."
+msgstr ""
+
+msgid "An error occurred while loading the merge request changes."
+msgstr ""
+
+msgid "An error occurred while loading the merge request version data."
+msgstr ""
+
+msgid "An error occurred while loading the merge request."
+msgstr ""
+
+msgid "An error occurred while loading the pipelines jobs."
+msgstr ""
+
+msgid "An error occurred while loading the subscription details."
+msgstr ""
+
+msgid "An error occurred while making the request."
+msgstr ""
+
+msgid "An error occurred while moving the issue."
+msgstr ""
+
+msgid "An error occurred while parsing recent searches"
+msgstr ""
+
+msgid "An error occurred while parsing the file."
+msgstr ""
+
+msgid "An error occurred while removing epics."
+msgstr ""
+
+msgid "An error occurred while removing issues."
+msgstr ""
+
+msgid "An error occurred while rendering preview broadcast message"
+msgstr ""
+
+msgid "An error occurred while rendering the editor"
+msgstr ""
+
+msgid "An error occurred while reordering issues."
+msgstr ""
+
+msgid "An error occurred while retrieving calendar activity"
+msgstr ""
+
+msgid "An error occurred while retrieving diff"
+msgstr ""
+
+msgid "An error occurred while saving LDAP override status. Please try again."
+msgstr ""
+
+msgid "An error occurred while saving assignees"
+msgstr ""
+
+msgid "An error occurred while saving the approval settings"
+msgstr ""
+
+msgid "An error occurred while saving the template. Please check if the template exists."
+msgstr ""
+
+msgid "An error occurred while searching for milestones"
+msgstr ""
+
+msgid "An error occurred while subscribing to notifications."
+msgstr ""
+
+msgid "An error occurred while triggering the job."
+msgstr ""
+
+msgid "An error occurred while trying to run a new pipeline for this Merge Request."
+msgstr ""
+
+msgid "An error occurred while unsubscribing to notifications."
+msgstr ""
+
+msgid "An error occurred while updating approvers"
+msgstr ""
+
+msgid "An error occurred while updating the comment"
+msgstr ""
+
+msgid "An error occurred while validating group path"
+msgstr ""
+
+msgid "An error occurred while validating username"
+msgstr ""
+
+msgid "An error occurred. Please try again."
+msgstr ""
+
+msgid "An error ocurred while loading your content. Please try again."
+msgstr ""
+
+msgid "An example project for managing Kubernetes clusters integrated with GitLab."
+msgstr ""
+
+msgid "An instance-level serverless domain already exists."
+msgstr ""
+
+msgid "An issue already exists"
+msgstr ""
+
+msgid "An issue can be a bug, a todo or a feature request that needs to be discussed in a project. Besides, issues are searchable and filterable."
+msgstr ""
+
+msgid "An unauthenticated user"
+msgstr ""
+
+msgid "An unexpected error occurred while checking the project environment."
+msgstr ""
+
+msgid "An unexpected error occurred while checking the project runners."
+msgstr ""
+
+msgid "An unexpected error occurred while communicating with the Web Terminal."
+msgstr ""
+
+msgid "An unexpected error occurred while starting the Web Terminal."
+msgstr ""
+
+msgid "An unexpected error occurred while stopping the Web Terminal."
+msgstr ""
+
+msgid "An unknown error occurred while loading this graph."
+msgstr ""
+
+msgid "Analytics"
+msgstr ""
+
+msgid "Analyze a review version of your web application."
+msgstr ""
+
+msgid "Analyze your dependencies for known vulnerabilities."
+msgstr ""
+
+msgid "Analyze your source code and git history for secrets."
+msgstr ""
+
+msgid "Analyze your source code for known vulnerabilities."
+msgstr ""
+
+msgid "Ancestors"
+msgstr ""
+
+msgid "Anonymous"
+msgstr ""
+
+msgid "Another action is currently in progress"
+msgstr ""
+
+msgid "Another issue tracker is already in use. Only one issue tracker service can be active at a time"
+msgstr ""
+
+msgid "Anti-spam verification"
+msgstr ""
+
+msgid "Any"
+msgstr ""
+
+msgid "Any Author"
+msgstr ""
+
+msgid "Any branch"
+msgstr ""
+
+msgid "Any eligible user"
+msgstr ""
+
+msgid "Any encrypted tokens"
+msgstr ""
+
+msgid "Any label"
+msgstr ""
+
+msgid "Any member with Developer or higher permissions to the project."
+msgstr ""
+
+msgid "Any milestone"
+msgstr ""
+
+msgid "Any namespace"
+msgstr ""
+
+msgid "Any user"
+msgstr ""
+
+msgid "App ID"
+msgstr ""
+
+msgid "Appearance"
+msgstr ""
+
+msgid "Appearance was successfully created."
+msgstr ""
+
+msgid "Appearance was successfully updated."
+msgstr ""
+
+msgid "Append the comment with %{shrug}"
+msgstr ""
+
+msgid "Append the comment with %{tableflip}"
+msgstr ""
+
+msgid "Application"
+msgstr ""
+
+msgid "Application ID"
+msgstr ""
+
+msgid "Application settings saved successfully"
+msgstr ""
+
+msgid "Application settings update failed"
+msgstr ""
+
+msgid "Application uninstalled but failed to destroy: %{error_message}"
+msgstr ""
+
+msgid "Application was successfully destroyed."
+msgstr ""
+
+msgid "Application was successfully updated."
+msgstr ""
+
+msgid "Application: %{name}"
+msgstr ""
+
+msgid "Applications"
+msgstr ""
+
+msgid "Applied"
+msgstr ""
+
+msgid "Apply"
+msgstr ""
+
+msgid "Apply a label"
+msgstr ""
+
+msgid "Apply a template"
+msgstr ""
+
+msgid "Apply changes"
+msgstr ""
+
+msgid "Apply suggestion"
+msgstr ""
+
+msgid "Apply suggestions"
+msgstr ""
+
+msgid "Apply template"
+msgstr ""
+
+msgid "Apply this approval rule to any branch or a specific protected branch."
+msgstr ""
+
+msgid "Applying"
+msgstr ""
+
+msgid "Applying a template will replace the existing issue description. Any changes you have made will be lost."
+msgstr ""
+
+msgid "Applying command"
+msgstr ""
+
+msgid "Applying command to %{commandDescription}"
+msgstr ""
+
+msgid "Applying multiple commands"
+msgstr ""
+
+msgid "Applying suggestion..."
+msgstr ""
+
+msgid "Applying suggestions..."
+msgstr ""
+
+msgid "Approval rules"
+msgstr ""
+
+msgid "Approval rules reset to project defaults"
+msgstr ""
+
+msgid "ApprovalRuleRemove|%d member"
+msgid_plural "ApprovalRuleRemove|%d members"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "ApprovalRuleRemove|Approvals from this member are not revoked."
+msgid_plural "ApprovalRuleRemove|Approvals from these members are not revoked."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "ApprovalRuleRemove|You are about to remove the %{name} approver group which has %{nMembers}."
+msgstr ""
+
+msgid "ApprovalRuleSummary|%d member"
+msgid_plural "ApprovalRuleSummary|%d members"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "ApprovalRuleSummary|%{count} approval required from %{membersCount}"
+msgid_plural "ApprovalRuleSummary|%{count} approvals required from %{membersCount}"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "ApprovalRule|Approvers"
+msgstr ""
+
+msgid "ApprovalRule|Name"
+msgstr ""
+
+msgid "ApprovalRule|No. approvals required"
+msgstr ""
+
+msgid "ApprovalRule|Rule name"
+msgstr ""
+
+msgid "ApprovalRule|Target branch"
+msgstr ""
+
+msgid "ApprovalRule|e.g. QA, Security, etc."
+msgstr ""
+
+msgid "Approve"
+msgstr ""
+
+msgid "Approve a merge request"
+msgstr ""
+
+msgid "Approve the current merge request."
+msgstr ""
+
+msgid "Approved"
+msgstr ""
+
+msgid "Approved MRs"
+msgstr ""
+
+msgid "Approved by: "
+msgstr ""
+
+msgid "Approved the current merge request."
+msgstr ""
+
+msgid "Approved-By"
+msgstr ""
+
+msgid "Approver"
+msgstr ""
+
+msgid "Approvers"
+msgstr ""
+
+msgid "Apr"
+msgstr ""
+
+msgid "April"
+msgstr ""
+
+msgid "Archive"
+msgstr ""
+
+msgid "Archive jobs"
+msgstr ""
+
+msgid "Archive project"
+msgstr ""
+
+msgid "Archived"
+msgstr ""
+
+msgid "Archived project! Repository and other project resources are read only"
+msgstr ""
+
+msgid "Archived project! Repository and other project resources are read-only"
+msgstr ""
+
+msgid "Archived projects"
+msgstr ""
+
+msgid "Archiving the project will make it entirely read only. It is hidden from the dashboard and doesn't show up in searches. %{strong_start}The repository cannot be committed to, and no issues, comments, or other entities can be created.%{strong_end}"
+msgstr ""
+
+msgid "Are you setting up GitLab for a company?"
+msgstr ""
+
+msgid "Are you sure that you want to archive this project?"
+msgstr ""
+
+msgid "Are you sure that you want to unarchive this project?"
+msgstr ""
+
+msgid "Are you sure you want to cancel editing this comment?"
+msgstr ""
+
+msgid "Are you sure you want to close this blocked issue?"
+msgstr ""
+
+msgid "Are you sure you want to delete %{name}?"
+msgstr ""
+
+msgid "Are you sure you want to delete these artifacts?"
+msgstr ""
+
+msgid "Are you sure you want to delete this %{typeOfComment}?"
+msgstr ""
+
+msgid "Are you sure you want to delete this board?"
+msgstr ""
+
+msgid "Are you sure you want to delete this device? This action cannot be undone."
+msgstr ""
+
+msgid "Are you sure you want to delete this list?"
+msgstr ""
+
+msgid "Are you sure you want to delete this pipeline schedule?"
+msgstr ""
+
+msgid "Are you sure you want to delete this pipeline? Doing so will expire all pipeline caches and delete all related objects, such as builds, logs, artifacts, and triggers. This action cannot be undone."
+msgstr ""
+
+msgid "Are you sure you want to deploy this environment?"
+msgstr ""
+
+msgid "Are you sure you want to discard this comment?"
+msgstr ""
+
+msgid "Are you sure you want to erase this build?"
+msgstr ""
+
+msgid "Are you sure you want to lose unsaved changes?"
+msgstr ""
+
+msgid "Are you sure you want to lose your issue information?"
+msgstr ""
+
+msgid "Are you sure you want to merge immediately?"
+msgstr ""
+
+msgid "Are you sure you want to permanently delete this license?"
+msgstr ""
+
+msgid "Are you sure you want to re-deploy this environment?"
+msgstr ""
+
+msgid "Are you sure you want to regenerate the public key? You will have to update the public key on the remote server before mirroring will work again."
+msgstr ""
+
+msgid "Are you sure you want to remove %{group_name}?"
+msgstr ""
+
+msgid "Are you sure you want to remove the attachment?"
+msgstr ""
+
+msgid "Are you sure you want to remove the license?"
+msgstr ""
+
+msgid "Are you sure you want to remove this identity?"
+msgstr ""
+
+msgid "Are you sure you want to reset registration token?"
+msgstr ""
+
+msgid "Are you sure you want to reset the SCIM token? SCIM provisioning will stop working until the new token is updated."
+msgstr ""
+
+msgid "Are you sure you want to reset the health check token?"
+msgstr ""
+
+msgid "Are you sure you want to revoke this %{type}? This action cannot be undone."
+msgstr ""
+
+msgid "Are you sure you want to revoke this nickname?"
+msgstr ""
+
+msgid "Are you sure you want to stop this environment?"
+msgstr ""
+
+msgid "Are you sure you want to unlock %{path_lock_path}?"
+msgstr ""
+
+msgid "Are you sure you want to unsubscribe from the %{type}: %{link_to_noteable_text}?"
+msgstr ""
+
+msgid "Are you sure?"
+msgstr ""
+
+msgid "Are you sure? All commits that were signed with this GPG key will be unverified."
+msgstr ""
+
+msgid "Are you sure? Removing this GPG key does not affect already signed commits."
+msgstr ""
+
+msgid "Are you sure? The device will be signed out of GitLab."
+msgstr ""
+
+msgid "Are you sure? This will invalidate your registered applications and U2F devices."
+msgstr ""
+
+msgid "Arrange charts"
+msgstr ""
+
+msgid "Artifact"
+msgstr ""
+
+msgid "Artifact ID"
+msgstr ""
+
+msgid "Artifact could not be deleted."
+msgstr ""
+
+msgid "Artifact was successfully deleted."
+msgstr ""
+
+msgid "Artifacts"
+msgstr ""
+
+msgid "As U2F devices are only supported by a few browsers, we require that you set up a two-factor authentication app before a U2F device. That way you'll always be able to log in - even when you're using an unsupported browser."
+msgstr ""
+
+msgid "AsanaService|%{user} pushed to branch %{branch} of %{project_name} ( %{commit_url} ):"
+msgstr ""
+
+msgid "AsanaService|Asana - Teamwork without email"
+msgstr ""
+
+msgid "AsanaService|Comma-separated list of branches which will be automatically inspected. Leave blank to include all branches."
+msgstr ""
+
+msgid "AsanaService|User Personal Access Token. User must have access to task, all comments will be attributed to this user."
+msgstr ""
+
+msgid "Ascending"
+msgstr ""
+
+msgid "Ask your group maintainer to set up a group Runner."
+msgstr ""
+
+msgid "Assertion consumer service URL"
+msgstr ""
+
+msgid "Assets"
+msgstr ""
+
+msgid "Assets:"
+msgstr ""
+
+msgid "Assign"
+msgstr ""
+
+msgid "Assign Iteration"
+msgstr ""
+
+msgid "Assign custom color like #FF0000"
+msgstr ""
+
+msgid "Assign epic"
+msgstr ""
+
+msgid "Assign labels"
+msgstr ""
+
+msgid "Assign milestone"
+msgstr ""
+
+msgid "Assign some issues to this milestone."
+msgstr ""
+
+msgid "Assign to"
+msgstr ""
+
+msgid "Assign to commenting user"
+msgstr ""
+
+msgid "Assign yourself to these issues"
+msgstr ""
+
+msgid "Assign yourself to this issue"
+msgstr ""
+
+msgid "Assigned %{assignee_users_sentence}."
+msgstr ""
+
+msgid "Assigned Issues"
+msgstr ""
+
+msgid "Assigned Merge Requests"
+msgstr ""
+
+msgid "Assigned to %{assignee_name}"
+msgstr ""
+
+msgid "Assigned to me"
+msgstr ""
+
+msgid "Assignee"
+msgid_plural "%d Assignees"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Assignee has no permissions"
+msgstr ""
+
+msgid "Assignee lists not available with your current license"
+msgstr ""
+
+msgid "Assignee lists show all issues assigned to the selected user."
+msgstr ""
+
+msgid "Assignee(s)"
+msgstr ""
+
+msgid "Assignees"
+msgstr ""
+
+msgid "Assigns %{assignee_users_sentence}."
+msgstr ""
+
+msgid "At least one approval from a code owner is required to change files matching the respective CODEOWNER rules."
+msgstr ""
+
+msgid "At least one logging option is required to be enabled"
+msgstr ""
+
+msgid "At least one of group_id or project_id must be specified"
+msgstr ""
+
+msgid "At risk"
+msgstr ""
+
+msgid "Attach a file"
+msgstr ""
+
+msgid "Attach a file by drag &amp; drop or %{upload_link}"
+msgstr ""
+
+msgid "Attaching a file"
+msgid_plural "Attaching %d files"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Attaching the file failed."
+msgstr ""
+
+msgid "Audit Events"
+msgstr ""
+
+msgid "Audit Events is a way to keep track of important events that happened in GitLab."
+msgstr ""
+
+msgid "Audit Log"
+msgstr ""
+
+msgid "AuditEvents|(removed)"
+msgstr ""
+
+msgid "AuditEvents|Action"
+msgstr ""
+
+msgid "AuditEvents|At"
+msgstr ""
+
+msgid "AuditEvents|Target"
+msgstr ""
+
+msgid "AuditLogs|(removed)"
+msgstr ""
+
+msgid "AuditLogs|Action"
+msgstr ""
+
+msgid "AuditLogs|Author"
+msgstr ""
+
+msgid "AuditLogs|Date"
+msgstr ""
+
+msgid "AuditLogs|Failed to find %{type}. Please search for another %{type}."
+msgstr ""
+
+msgid "AuditLogs|Failed to find %{type}. Please try again."
+msgstr ""
+
+msgid "AuditLogs|Group Events"
+msgstr ""
+
+msgid "AuditLogs|IP Address"
+msgstr ""
+
+msgid "AuditLogs|Member Events"
+msgstr ""
+
+msgid "AuditLogs|No matching %{type} found."
+msgstr ""
+
+msgid "AuditLogs|Object"
+msgstr ""
+
+msgid "AuditLogs|Project Events"
+msgstr ""
+
+msgid "AuditLogs|Target"
+msgstr ""
+
+msgid "AuditLogs|User Events"
+msgstr ""
+
+msgid "Aug"
+msgstr ""
+
+msgid "August"
+msgstr ""
+
+msgid "Authenticate"
+msgstr ""
+
+msgid "Authenticate with GitHub"
+msgstr ""
+
+msgid "Authenticating"
+msgstr ""
+
+msgid "Authentication Failure"
+msgstr ""
+
+msgid "Authentication Log"
+msgstr ""
+
+msgid "Authentication failed: %{error_message}"
+msgstr ""
+
+msgid "Authentication log"
+msgstr ""
+
+msgid "Authentication method"
+msgstr ""
+
+msgid "Authentication method updated"
+msgstr ""
+
+msgid "Authentication via U2F device failed."
+msgstr ""
+
+msgid "Author"
+msgstr ""
+
+msgid "Authored %{timeago} by %{author}"
+msgstr ""
+
+msgid "Authorization code:"
+msgstr ""
+
+msgid "Authorization key"
+msgstr ""
+
+msgid "Authorization was granted by entering your username and password in the application."
+msgstr ""
+
+msgid "Authorize"
+msgstr ""
+
+msgid "Authorize %{link_to_client} to use your account?"
+msgstr ""
+
+msgid "Authorize external services to send alerts to GitLab"
+msgstr ""
+
+msgid "Authorized %{new_chat_name}"
+msgstr ""
+
+msgid "Authorized At"
+msgstr ""
+
+msgid "Authorized applications (%{size})"
+msgstr ""
+
+msgid "Authors: %{authors}"
+msgstr ""
+
+msgid "Auto DevOps"
+msgstr ""
+
+msgid "Auto DevOps enabled"
+msgstr ""
+
+msgid "Auto DevOps, runners and job artifacts"
+msgstr ""
+
+msgid "Auto stop successfully canceled."
+msgstr ""
+
+msgid "Auto-cancel redundant, pending pipelines"
+msgstr ""
+
+msgid "Auto-close referenced issues on default branch"
+msgstr ""
+
+msgid "AutoDevOps|Auto DevOps"
+msgstr ""
+
+msgid "AutoDevOps|Auto DevOps can automatically build, test, and deploy applications based on predefined continuous integration and delivery configuration. %{auto_devops_start}Learn more about Auto DevOps%{auto_devops_end} or use our %{quickstart_start}quick start guide%{quickstart_end} to get started right away."
+msgstr ""
+
+msgid "AutoDevOps|Auto DevOps documentation"
+msgstr ""
+
+msgid "AutoDevOps|Dismiss Auto DevOps box"
+msgstr ""
+
+msgid "AutoDevOps|Enable in settings"
+msgstr ""
+
+msgid "AutoDevOps|It will automatically build, test, and deploy your application based on a predefined CI/CD configuration."
+msgstr ""
+
+msgid "AutoDevOps|Learn more in the %{link_to_documentation}"
+msgstr ""
+
+msgid "AutoDevOps|The Auto DevOps pipeline has been enabled and will be used if no alternative CI configuration file is found. %{more_information_link}"
+msgstr ""
+
+msgid "Autocomplete"
+msgstr ""
+
+msgid "Autocomplete description"
+msgstr ""
+
+msgid "Autocomplete hint"
+msgstr ""
+
+msgid "Autocomplete usage hint"
+msgstr ""
+
+msgid "Automatic certificate management using %{lets_encrypt_link_start}Let's Encrypt%{lets_encrypt_link_end}"
+msgstr ""
+
+msgid "Automatic certificate management using Let's Encrypt"
+msgstr ""
+
+msgid "Automatically create merge requests for vulnerabilities that have fixes available."
+msgstr ""
+
+msgid "Automatically marked as default internal user"
+msgstr ""
+
+msgid "Automatically resolved"
+msgstr ""
+
+msgid "Automatically update this project's branches and tags from the upstream repository every hour."
+msgstr ""
+
+msgid "Autosave|Note"
+msgstr ""
+
+msgid "Available"
+msgstr ""
+
+msgid "Available for dependency and container scanning"
+msgstr ""
+
+msgid "Available group Runners: %{runners}"
+msgstr ""
+
+msgid "Available shared Runners:"
+msgstr ""
+
+msgid "Available specific runners"
+msgstr ""
+
+msgid "Avatar for %{assigneeName}"
+msgstr ""
+
+msgid "Avatar for %{name}"
+msgstr ""
+
+msgid "Avatar will be removed. Are you sure?"
+msgstr ""
+
+msgid "Average per day: %{average}"
+msgstr ""
+
+msgid "Back to page %{number}"
+msgstr ""
+
+msgid "Background Color"
+msgstr ""
+
+msgid "Background Jobs"
+msgstr ""
+
+msgid "Background color"
+msgstr ""
+
+msgid "Badges"
+msgstr ""
+
+msgid "Badges|A new badge was added."
+msgstr ""
+
+msgid "Badges|Add badge"
+msgstr ""
+
+msgid "Badges|Adding the badge failed, please check the entered URLs and try again."
+msgstr ""
+
+msgid "Badges|Badge image URL"
+msgstr ""
+
+msgid "Badges|Badge image preview"
+msgstr ""
+
+msgid "Badges|Delete badge"
+msgstr ""
+
+msgid "Badges|Delete badge?"
+msgstr ""
+
+msgid "Badges|Deleting the badge failed, please try again."
+msgstr ""
+
+msgid "Badges|Group Badge"
+msgstr ""
+
+msgid "Badges|Link"
+msgstr ""
+
+msgid "Badges|Name"
+msgstr ""
+
+msgid "Badges|No badge image"
+msgstr ""
+
+msgid "Badges|No image to preview"
+msgstr ""
+
+msgid "Badges|Please fill in a valid URL"
+msgstr ""
+
+msgid "Badges|Project Badge"
+msgstr ""
+
+msgid "Badges|Reload badge image"
+msgstr ""
+
+msgid "Badges|Save changes"
+msgstr ""
+
+msgid "Badges|Saving the badge failed, please check the entered URLs and try again."
+msgstr ""
+
+msgid "Badges|The %{docsLinkStart}variables%{docsLinkEnd} GitLab supports: %{placeholders}"
+msgstr ""
+
+msgid "Badges|The badge was deleted."
+msgstr ""
+
+msgid "Badges|The badge was saved."
+msgstr ""
+
+msgid "Badges|This group has no badges"
+msgstr ""
+
+msgid "Badges|This project has no badges"
+msgstr ""
+
+msgid "Badges|You are going to delete this badge. Deleted badges <strong>cannot</strong> be restored."
+msgstr ""
+
+msgid "Badges|Your badges"
+msgstr ""
+
+msgid "Badges|e.g. %{exampleUrl}"
+msgstr ""
+
+msgid "Balsamiq file could not be loaded."
+msgstr ""
+
+msgid "BambooService|A continuous integration and build server"
+msgstr ""
+
+msgid "BambooService|A user with API access, if applicable"
+msgstr ""
+
+msgid "BambooService|Atlassian Bamboo CI"
+msgstr ""
+
+msgid "BambooService|Bamboo build plan key like KEY"
+msgstr ""
+
+msgid "BambooService|Bamboo root URL like https://bamboo.example.com"
+msgstr ""
+
+msgid "BambooService|You must set up automatic revision labeling and a repository trigger in Bamboo."
+msgstr ""
+
+msgid "BatchComments|Delete all pending comments"
+msgstr ""
+
+msgid "BatchComments|Discard review?"
+msgstr ""
+
+msgid "BatchComments|You're about to discard your review which will delete all of your pending comments. The deleted comments %{strong_start}cannot%{strong_end} be restored."
+msgstr ""
+
+msgid "Be careful. Changing the project's namespace can have unintended side effects."
+msgstr ""
+
+msgid "Be careful. Renaming a project's repository can have unintended side effects."
+msgstr ""
+
+msgid "Begin with the selected commit"
+msgstr ""
+
+msgid "Below are examples of regex for existing tools:"
+msgstr ""
+
+msgid "Below are the fingerprints for the current instance SSH host keys."
+msgstr ""
+
+msgid "Below you will find all the groups that are public."
+msgstr ""
+
+msgid "Beta"
+msgstr ""
+
+msgid "Bi-weekly code coverage"
+msgstr ""
+
+msgid "Billing"
+msgstr ""
+
+msgid "BillingPlans|%{group_name} is currently using the %{plan_name} plan."
+msgstr ""
+
+msgid "BillingPlans|@%{user_name} you are currently using the %{plan_name} plan."
+msgstr ""
+
+msgid "BillingPlans|Congratulations, your new trial is activated"
+msgstr ""
+
+msgid "BillingPlans|If you would like to downgrade your plan please contact %{support_link_start}Customer Support%{support_link_end}."
+msgstr ""
+
+msgid "BillingPlans|Learn more about each plan by reading our %{faq_link}, or start a free 30-day trial of GitLab.com Gold."
+msgstr ""
+
+msgid "BillingPlans|Learn more about each plan by visiting our %{pricing_page_link}."
+msgstr ""
+
+msgid "BillingPlans|Manage plan"
+msgstr ""
+
+msgid "BillingPlans|Pricing page"
+msgstr ""
+
+msgid "BillingPlans|See all %{plan_name} features"
+msgstr ""
+
+msgid "BillingPlans|This group uses the plan associated with its parent group."
+msgstr ""
+
+msgid "BillingPlans|To manage the plan for this group, visit the billing section of %{parent_billing_page_link}."
+msgstr ""
+
+msgid "BillingPlans|Your GitLab.com %{plan} trial will <strong>expire after %{expiration_date}</strong>. You can retain access to the %{plan} features by upgrading below."
+msgstr ""
+
+msgid "BillingPlans|Your GitLab.com trial expired on %{expiration_date}. You can restore access to the features at any time by upgrading below."
+msgstr ""
+
+msgid "BillingPlans|billed annually at %{price_per_year}"
+msgstr ""
+
+msgid "BillingPlans|frequently asked questions"
+msgstr ""
+
+msgid "BillingPlans|monthly"
+msgstr ""
+
+msgid "BillingPlans|per user"
+msgstr ""
+
+msgid "BillingPlan|Upgrade"
+msgstr ""
+
+msgid "Bitbucket Server Import"
+msgstr ""
+
+msgid "Bitbucket Server import"
+msgstr ""
+
+msgid "Bitbucket import"
+msgstr ""
+
+msgid "Blame"
+msgstr ""
+
+msgid "Blocked"
+msgstr ""
+
+msgid "Blocked issue"
+msgstr ""
+
+msgid "Blocks"
+msgstr ""
+
+msgid "Blog"
+msgstr ""
+
+msgid "Board name"
+msgstr ""
+
+msgid "Board scope"
+msgstr ""
+
+msgid "Board scope affects which issues are displayed for anyone who visits this board"
+msgstr ""
+
+msgid "BoardBlankState|Add default lists"
+msgstr ""
+
+msgid "BoardBlankState|Add the following default lists to your Issue Board with one click:"
+msgstr ""
+
+msgid "BoardBlankState|Nevermind, I'll use my own"
+msgstr ""
+
+msgid "BoardBlankState|Starting out with the default set of lists will get you right on the way to making the most of your board."
+msgstr ""
+
+msgid "Boards"
+msgstr ""
+
+msgid "Boards and Board Lists"
+msgstr ""
+
+msgid "Boards|Collapse"
+msgstr ""
+
+msgid "Boards|Edit board"
+msgstr ""
+
+msgid "Boards|Expand"
+msgstr ""
+
+msgid "Boards|View scope"
+msgstr ""
+
+msgid "Branch"
+msgstr ""
+
+msgid "Branch %{branchName} was not found in this project's repository."
+msgstr ""
+
+msgid "Branch <strong>%{branch_name}</strong> was created. To set up auto deploy, choose a GitLab CI Yaml template and commit your changes. %{link_to_autodeploy_doc}"
+msgstr ""
+
+msgid "Branch has changed"
+msgstr ""
+
+msgid "Branch is already taken"
+msgstr ""
+
+msgid "Branch name"
+msgstr ""
+
+msgid "Branch not loaded - %{branchId}"
+msgstr ""
+
+msgid "Branch prefix"
+msgstr ""
+
+msgid "BranchSwitcherPlaceholder|Search branches"
+msgstr ""
+
+msgid "BranchSwitcherTitle|Switch branch"
+msgstr ""
+
+msgid "Branches"
+msgstr ""
+
+msgid "Branches|Active"
+msgstr ""
+
+msgid "Branches|Active branches"
+msgstr ""
+
+msgid "Branches|All"
+msgstr ""
+
+msgid "Branches|Cant find HEAD commit for this branch"
+msgstr ""
+
+msgid "Branches|Compare"
+msgstr ""
+
+msgid "Branches|Delete all branches that are merged into '%{default_branch}'"
+msgstr ""
+
+msgid "Branches|Delete branch"
+msgstr ""
+
+msgid "Branches|Delete merged branches"
+msgstr ""
+
+msgid "Branches|Delete protected branch"
+msgstr ""
+
+msgid "Branches|Delete protected branch '%{branch_name}'?"
+msgstr ""
+
+msgid "Branches|Deleting the '%{branch_name}' branch cannot be undone. Are you sure?"
+msgstr ""
+
+msgid "Branches|Deleting the merged branches cannot be undone. Are you sure?"
+msgstr ""
+
+msgid "Branches|Filter by branch name"
+msgstr ""
+
+msgid "Branches|Merged into %{default_branch}"
+msgstr ""
+
+msgid "Branches|New branch"
+msgstr ""
+
+msgid "Branches|No branches to show"
+msgstr ""
+
+msgid "Branches|Once you confirm and press %{delete_protected_branch}, it cannot be undone or recovered."
+msgstr ""
+
+msgid "Branches|Only a project maintainer or owner can delete a protected branch"
+msgstr ""
+
+msgid "Branches|Overview"
+msgstr ""
+
+msgid "Branches|Protected branches can be managed in %{project_settings_link}."
+msgstr ""
+
+msgid "Branches|Show active branches"
+msgstr ""
+
+msgid "Branches|Show all branches"
+msgstr ""
+
+msgid "Branches|Show more active branches"
+msgstr ""
+
+msgid "Branches|Show more stale branches"
+msgstr ""
+
+msgid "Branches|Show overview of the branches"
+msgstr ""
+
+msgid "Branches|Show stale branches"
+msgstr ""
+
+msgid "Branches|Sort by"
+msgstr ""
+
+msgid "Branches|Stale"
+msgstr ""
+
+msgid "Branches|Stale branches"
+msgstr ""
+
+msgid "Branches|The branch could not be updated automatically because it has diverged from its upstream counterpart."
+msgstr ""
+
+msgid "Branches|The default branch cannot be deleted"
+msgstr ""
+
+msgid "Branches|This branch hasn’t been merged into %{default_branch}."
+msgstr ""
+
+msgid "Branches|To avoid data loss, consider merging this branch before deleting it."
+msgstr ""
+
+msgid "Branches|To confirm, type %{branch_name_confirmation}:"
+msgstr ""
+
+msgid "Branches|To discard the local changes and overwrite the branch with the upstream version, delete it here and choose 'Update Now' above."
+msgstr ""
+
+msgid "Branches|You’re about to permanently delete the protected branch %{branch_name}."
+msgstr ""
+
+msgid "Branches|diverged from upstream"
+msgstr ""
+
+msgid "Branches|merged"
+msgstr ""
+
+msgid "Branches|project settings"
+msgstr ""
+
+msgid "Branches|protected"
+msgstr ""
+
+msgid "Broadcast Message was successfully created."
+msgstr ""
+
+msgid "Broadcast Message was successfully updated."
+msgstr ""
+
+msgid "Broadcast Messages"
+msgstr ""
+
+msgid "Browse Directory"
+msgstr ""
+
+msgid "Browse File"
+msgstr ""
+
+msgid "Browse Files"
+msgstr ""
+
+msgid "Browse artifacts"
+msgstr ""
+
+msgid "Browse files"
+msgstr ""
+
+msgid "BuildArtifacts|An error occurred while fetching the artifacts"
+msgstr ""
+
+msgid "BuildArtifacts|Loading artifacts"
+msgstr ""
+
+msgid "Built-in"
+msgstr ""
+
+msgid "Bulk request concurrency"
+msgstr ""
+
+msgid "Burndown chart"
+msgstr ""
+
+msgid "BurndownChartLabel|Open issue weight"
+msgstr ""
+
+msgid "BurndownChartLabel|Open issues"
+msgstr ""
+
+msgid "Burnup chart"
+msgstr ""
+
+msgid "Business"
+msgstr ""
+
+msgid "Business metrics (Custom)"
+msgstr ""
+
+msgid "Buy License"
+msgstr ""
+
+msgid "Buy more Pipeline minutes"
+msgstr ""
+
+msgid "By %{user_name}"
+msgstr ""
+
+msgid "By URL"
+msgstr ""
+
+msgid "By default GitLab sends emails in HTML and plain text formats so mail clients can choose what format to use. Disable this option if you only want to send emails in plain text format."
+msgstr ""
+
+msgid "By default, all projects and groups will use the global notifications setting."
+msgstr ""
+
+msgid "ByAuthor|by"
+msgstr ""
+
+msgid "CHANGELOG"
+msgstr ""
+
+msgid "CI / CD"
+msgstr ""
+
+msgid "CI / CD Analytics"
+msgstr ""
+
+msgid "CI / CD Settings"
+msgstr ""
+
+msgid "CI Lint"
+msgstr ""
+
+msgid "CI settings"
+msgstr ""
+
+msgid "CI variables"
+msgstr ""
+
+msgid "CI will run using the credentials assigned above."
+msgstr ""
+
+msgid "CI/CD"
+msgstr ""
+
+msgid "CI/CD configuration"
+msgstr ""
+
+msgid "CI/CD for external repo"
+msgstr ""
+
+msgid "CI/CD settings"
+msgstr ""
+
+msgid "CICD|Add a %{kubernetes_cluster_link_start}Kubernetes cluster integration%{link_end} with a domain or create an AUTO_DEVOPS_PLATFORM_TARGET CI variable."
+msgstr ""
+
+msgid "CICD|Auto DevOps"
+msgstr ""
+
+msgid "CICD|Automatic deployment to staging, manual deployment to production"
+msgstr ""
+
+msgid "CICD|Continuous deployment to production"
+msgstr ""
+
+msgid "CICD|Continuous deployment to production using timed incremental rollout"
+msgstr ""
+
+msgid "CICD|Default to Auto DevOps pipeline"
+msgstr ""
+
+msgid "CICD|Default to Auto DevOps pipeline for all projects"
+msgstr ""
+
+msgid "CICD|Deployment strategy"
+msgstr ""
+
+msgid "CICD|Jobs"
+msgstr ""
+
+msgid "CICD|The Auto DevOps pipeline will run if no alternative CI configuration file is found."
+msgstr ""
+
+msgid "CICD|You must add a %{base_domain_link_start}base domain%{link_end} to your %{kubernetes_cluster_link_start}Kubernetes cluster%{link_end} in order for your deployment strategy to work."
+msgstr ""
+
+msgid "CICD|group enabled"
+msgstr ""
+
+msgid "CICD|instance enabled"
+msgstr ""
+
+msgid "CLOSED"
+msgstr ""
+
+msgid "CLOSED (MOVED)"
+msgstr ""
+
+msgid "CONTRIBUTING"
+msgstr ""
+
+msgid "CPU"
+msgstr ""
+
+msgid "Callback URL"
+msgstr ""
+
+msgid "Can be manually deployed to"
+msgstr ""
+
+msgid "Can override approvers and approvals required per merge request"
+msgstr ""
+
+msgid "Can't apply as this line has changed or the suggestion already matches its content."
+msgstr ""
+
+msgid "Can't create snippet: %{err}"
+msgstr ""
+
+msgid "Can't edit as source branch was deleted"
+msgstr ""
+
+msgid "Can't find HEAD commit for this branch"
+msgstr ""
+
+msgid "Can't find variable: ZiteReader"
+msgstr ""
+
+msgid "Can't load mermaid module: %{err}"
+msgstr ""
+
+msgid "Can't remove group members without group managed account"
+msgstr ""
+
+msgid "Can't scan the code?"
+msgstr ""
+
+msgid "Can't update snippet: %{err}"
+msgstr ""
+
+msgid "Canary"
+msgstr ""
+
+msgid "Canary Deployments is a popular CI strategy, where a small portion of the fleet is updated to the new version of your application."
+msgstr ""
+
+msgid "Cancel"
+msgstr ""
+
+msgid "Cancel running"
+msgstr ""
+
+msgid "Cancel this job"
+msgstr ""
+
+msgid "Canceled deployment to"
+msgstr ""
+
+msgid "Cancelling Preview"
+msgstr ""
+
+msgid "Cannot be merged automatically"
+msgstr ""
+
+msgid "Cannot create the abuse report. The user has been deleted."
+msgstr ""
+
+msgid "Cannot create the abuse report. This user has been blocked."
+msgstr ""
+
+msgid "Cannot have multiple Jira imports running at the same time"
+msgstr ""
+
+msgid "Cannot import because issues are not available in this project."
+msgstr ""
+
+msgid "Cannot make epic confidential if it contains not-confidential issues"
+msgstr ""
+
+msgid "Cannot make epic confidential if it contains not-confidential sub-epics"
+msgstr ""
+
+msgid "Cannot merge"
+msgstr ""
+
+msgid "Cannot modify managed Kubernetes cluster"
+msgstr ""
+
+msgid "Cannot modify provider during creation"
+msgstr ""
+
+msgid "Cannot promote issue because it does not belong to a group."
+msgstr ""
+
+msgid "Cannot promote issue due to insufficient permissions."
+msgstr ""
+
+msgid "Cannot refer to a group %{timebox_type} by an internal id!"
+msgstr ""
+
+msgid "Cannot set confidential epic for not-confidential issue"
+msgstr ""
+
+msgid "Cannot show preview. For previews on sketch files, they must have the file format introduced by Sketch version 43 and above."
+msgstr ""
+
+msgid "Cannot skip two factor authentication setup"
+msgstr ""
+
+msgid "Capacity threshold"
+msgstr ""
+
+msgid "Certain user content will be moved to a system-wide \"Ghost User\" in order to maintain content for posterity. For further information, please refer to the %{link_start}user account deletion documentation.%{link_end}"
+msgstr ""
+
+msgid "Certificate"
+msgstr ""
+
+msgid "Certificate (PEM)"
+msgstr ""
+
+msgid "Certificate Issuer"
+msgstr ""
+
+msgid "Certificate Subject"
+msgstr ""
+
+msgid "Change assignee"
+msgstr ""
+
+msgid "Change assignee(s)"
+msgstr ""
+
+msgid "Change assignee(s)."
+msgstr ""
+
+msgid "Change branches"
+msgstr ""
+
+msgid "Change label"
+msgstr ""
+
+msgid "Change milestone"
+msgstr ""
+
+msgid "Change path"
+msgstr ""
+
+msgid "Change permissions"
+msgstr ""
+
+msgid "Change status"
+msgstr ""
+
+msgid "Change subscription"
+msgstr ""
+
+msgid "Change template"
+msgstr ""
+
+msgid "Change this value to influence how frequently the GitLab UI polls for updates."
+msgstr ""
+
+msgid "Change title"
+msgstr ""
+
+msgid "Change your password"
+msgstr ""
+
+msgid "Change your password or recover your current one"
+msgstr ""
+
+msgid "ChangeTypeActionLabel|Pick into branch"
+msgstr ""
+
+msgid "ChangeTypeActionLabel|Revert in branch"
+msgstr ""
+
+msgid "ChangeTypeAction|Cherry-pick"
+msgstr ""
+
+msgid "ChangeTypeAction|Revert"
+msgstr ""
+
+msgid "ChangeTypeAction|This will create a new commit in order to revert the existing changes."
+msgstr ""
+
+msgid "Changed assignee(s)."
+msgstr ""
+
+msgid "Changed the title to \"%{title_param}\"."
+msgstr ""
+
+msgid "Changes"
+msgstr ""
+
+msgid "Changes are shown as if the <b>source</b> revision was being merged into the <b>target</b> revision."
+msgstr ""
+
+msgid "Changes are still tracked. Useful for cluster/index migrations."
+msgstr ""
+
+msgid "Changes suppressed. Click to show."
+msgstr ""
+
+msgid "Changes the title to \"%{title_param}\"."
+msgstr ""
+
+msgid "Changes won't take place until the index is %{link_start}recreated%{link_end}."
+msgstr ""
+
+msgid "Changing a Release tag is only supported via Releases API. %{linkStart}More information%{linkEnd}"
+msgstr ""
+
+msgid "Changing group path can have unintended side effects."
+msgstr ""
+
+msgid "Channel handle (e.g. town-square)"
+msgstr ""
+
+msgid "Charts"
+msgstr ""
+
+msgid "Charts can't be displayed as the request for data has timed out. %{documentationLink}"
+msgstr ""
+
+msgid "Chat"
+msgstr ""
+
+msgid "ChatMessage|%{project_link}: Pipeline %{pipeline_link} of %{ref_type} %{ref_link} by %{user_combined_name} %{humanized_status} in %{duration}"
+msgstr ""
+
+msgid "ChatMessage|Branch"
+msgstr ""
+
+msgid "ChatMessage|Commit"
+msgstr ""
+
+msgid "ChatMessage|Failed job"
+msgstr ""
+
+msgid "ChatMessage|Failed stage"
+msgstr ""
+
+msgid "ChatMessage|Invalid CI config YAML file"
+msgstr ""
+
+msgid "ChatMessage|Pipeline #%{pipeline_id} %{humanized_status} in %{duration}"
+msgstr ""
+
+msgid "ChatMessage|Pipeline %{pipeline_link} of %{ref_type} %{ref_link} by %{user_combined_name} %{humanized_status}"
+msgstr ""
+
+msgid "ChatMessage|Tag"
+msgstr ""
+
+msgid "ChatMessage|and [%{count} more](%{pipeline_failed_jobs_url})"
+msgstr ""
+
+msgid "ChatMessage|has failed"
+msgstr ""
+
+msgid "ChatMessage|has passed"
+msgstr ""
+
+msgid "ChatMessage|has passed with warnings"
+msgstr ""
+
+msgid "ChatMessage|in %{duration}"
+msgstr ""
+
+msgid "ChatMessage|in %{project_link}"
+msgstr ""
+
+msgid "Check again"
+msgstr ""
+
+msgid "Check feature availability on namespace plan"
+msgstr ""
+
+msgid "Check the %{docs_link_start}documentation%{docs_link_end}."
+msgstr ""
+
+msgid "Check your .gitlab-ci.yml"
+msgstr ""
+
+msgid "Check your Docker images for known vulnerabilities."
+msgstr ""
+
+msgid "Checking %{text} availability…"
+msgstr ""
+
+msgid "Checking approval status"
+msgstr ""
+
+msgid "Checking branch availability..."
+msgstr ""
+
+msgid "Checking group path availability..."
+msgstr ""
+
+msgid "Checking username availability..."
+msgstr ""
+
+msgid "Checkout"
+msgstr ""
+
+msgid "Checkout|$%{selectedPlanPrice} per user per year"
+msgstr ""
+
+msgid "Checkout|%{cardType} ending in %{lastFourDigits}"
+msgstr ""
+
+msgid "Checkout|%{name}'s GitLab subscription"
+msgstr ""
+
+msgid "Checkout|%{selectedPlanText} plan"
+msgstr ""
+
+msgid "Checkout|%{startDate} - %{endDate}"
+msgstr ""
+
+msgid "Checkout|(x%{numberOfUsers})"
+msgstr ""
+
+msgid "Checkout|Billing address"
+msgstr ""
+
+msgid "Checkout|Checkout"
+msgstr ""
+
+msgid "Checkout|City"
+msgstr ""
+
+msgid "Checkout|Confirm purchase"
+msgstr ""
+
+msgid "Checkout|Confirming..."
+msgstr ""
+
+msgid "Checkout|Continue to billing"
+msgstr ""
+
+msgid "Checkout|Continue to payment"
+msgstr ""
+
+msgid "Checkout|Country"
+msgstr ""
+
+msgid "Checkout|Create a new group"
+msgstr ""
+
+msgid "Checkout|Credit card form failed to load. Please try again."
+msgstr ""
+
+msgid "Checkout|Credit card form failed to load: %{message}"
+msgstr ""
+
+msgid "Checkout|Edit"
+msgstr ""
+
+msgid "Checkout|Exp %{expirationMonth}/%{expirationYear}"
+msgstr ""
+
+msgid "Checkout|Failed to confirm your order! Please try again."
+msgstr ""
+
+msgid "Checkout|Failed to confirm your order: %{message}. Please try again."
+msgstr ""
+
+msgid "Checkout|Failed to load countries. Please try again."
+msgstr ""
+
+msgid "Checkout|Failed to load states. Please try again."
+msgstr ""
+
+msgid "Checkout|Failed to register credit card. Please try again."
+msgstr ""
+
+msgid "Checkout|GitLab group"
+msgstr ""
+
+msgid "Checkout|GitLab plan"
+msgstr ""
+
+msgid "Checkout|Group"
+msgstr ""
+
+msgid "Checkout|Name of company or organization using GitLab"
+msgstr ""
+
+msgid "Checkout|Need more users? Purchase GitLab for your %{company}."
+msgstr ""
+
+msgid "Checkout|Number of users"
+msgstr ""
+
+msgid "Checkout|Payment method"
+msgstr ""
+
+msgid "Checkout|Please select a country"
+msgstr ""
+
+msgid "Checkout|Please select a state"
+msgstr ""
+
+msgid "Checkout|Select"
+msgstr ""
+
+msgid "Checkout|State"
+msgstr ""
+
+msgid "Checkout|Street address"
+msgstr ""
+
+msgid "Checkout|Submitting the credit card form failed with code %{errorCode}: %{errorMessage}"
+msgstr ""
+
+msgid "Checkout|Subscription details"
+msgstr ""
+
+msgid "Checkout|Subtotal"
+msgstr ""
+
+msgid "Checkout|Tax"
+msgstr ""
+
+msgid "Checkout|Total"
+msgstr ""
+
+msgid "Checkout|Users"
+msgstr ""
+
+msgid "Checkout|You'll create your new group after checkout"
+msgstr ""
+
+msgid "Checkout|Your organization"
+msgstr ""
+
+msgid "Checkout|Your subscription will be applied to this group"
+msgstr ""
+
+msgid "Checkout|Zip code"
+msgstr ""
+
+msgid "Checkout|company or team"
+msgstr ""
+
+msgid "Cherry-pick this commit"
+msgstr ""
+
+msgid "Cherry-pick this merge request"
+msgstr ""
+
+msgid "Child"
+msgstr ""
+
+msgid "Child epic does not exist."
+msgstr ""
+
+msgid "Child epic doesn't exist."
+msgstr ""
+
+msgid "Choose <strong>Create archive</strong> and wait for archiving to complete."
+msgstr ""
+
+msgid "Choose <strong>Next</strong> at the bottom of the page."
+msgstr ""
+
+msgid "Choose a branch/tag (e.g. %{master}) or enter a commit (e.g. %{sha}) to see what's changed or to create a merge request."
+msgstr ""
+
+msgid "Choose a file"
+msgstr ""
+
+msgid "Choose a group"
+msgstr ""
+
+msgid "Choose a role permission"
+msgstr ""
+
+msgid "Choose a template"
+msgstr ""
+
+msgid "Choose a template..."
+msgstr ""
+
+msgid "Choose a type..."
+msgstr ""
+
+msgid "Choose any color."
+msgstr ""
+
+msgid "Choose between <code>clone</code> or <code>fetch</code> to get the recent application code"
+msgstr ""
+
+msgid "Choose file…"
+msgstr ""
+
+msgid "Choose the top-level group for your repository imports."
+msgstr ""
+
+msgid "Choose visibility level, enable/disable project features (issues, repository, wiki, snippets) and set permissions."
+msgstr ""
+
+msgid "Choose what content you want to see on a group’s overview page"
+msgstr ""
+
+msgid "Choose which repositories you want to connect and run CI/CD pipelines."
+msgstr ""
+
+msgid "Choose your framework"
+msgstr ""
+
+msgid "CiStatusLabel|canceled"
+msgstr ""
+
+msgid "CiStatusLabel|created"
+msgstr ""
+
+msgid "CiStatusLabel|delayed"
+msgstr ""
+
+msgid "CiStatusLabel|failed"
+msgstr ""
+
+msgid "CiStatusLabel|manual action"
+msgstr ""
+
+msgid "CiStatusLabel|passed"
+msgstr ""
+
+msgid "CiStatusLabel|passed with warnings"
+msgstr ""
+
+msgid "CiStatusLabel|pending"
+msgstr ""
+
+msgid "CiStatusLabel|preparing"
+msgstr ""
+
+msgid "CiStatusLabel|skipped"
+msgstr ""
+
+msgid "CiStatusLabel|waiting for delayed job"
+msgstr ""
+
+msgid "CiStatusLabel|waiting for manual action"
+msgstr ""
+
+msgid "CiStatusLabel|waiting for resource"
+msgstr ""
+
+msgid "CiStatusText|blocked"
+msgstr ""
+
+msgid "CiStatusText|canceled"
+msgstr ""
+
+msgid "CiStatusText|created"
+msgstr ""
+
+msgid "CiStatusText|delayed"
+msgstr ""
+
+msgid "CiStatusText|failed"
+msgstr ""
+
+msgid "CiStatusText|manual"
+msgstr ""
+
+msgid "CiStatusText|passed"
+msgstr ""
+
+msgid "CiStatusText|pending"
+msgstr ""
+
+msgid "CiStatusText|preparing"
+msgstr ""
+
+msgid "CiStatusText|skipped"
+msgstr ""
+
+msgid "CiStatusText|waiting"
+msgstr ""
+
+msgid "CiStatus|running"
+msgstr ""
+
+msgid "CiVariables|Cannot use Masked Variable with current value"
+msgstr ""
+
+msgid "CiVariables|Environments"
+msgstr ""
+
+msgid "CiVariables|Input variable key"
+msgstr ""
+
+msgid "CiVariables|Input variable value"
+msgstr ""
+
+msgid "CiVariables|Key"
+msgstr ""
+
+msgid "CiVariables|Masked"
+msgstr ""
+
+msgid "CiVariables|Protected"
+msgstr ""
+
+msgid "CiVariables|Remove variable row"
+msgstr ""
+
+msgid "CiVariables|Scope"
+msgstr ""
+
+msgid "CiVariables|Specify variable values to be used in this run. The values specified in %{linkStart}CI/CD settings%{linkEnd} will be used as default"
+msgstr ""
+
+msgid "CiVariables|State"
+msgstr ""
+
+msgid "CiVariables|Type"
+msgstr ""
+
+msgid "CiVariables|Value"
+msgstr ""
+
+msgid "CiVariables|Variables"
+msgstr ""
+
+msgid "CiVariable|* (All environments)"
+msgstr ""
+
+msgid "CiVariable|All environments"
+msgstr ""
+
+msgid "CiVariable|Create wildcard"
+msgstr ""
+
+msgid "CiVariable|Error occurred while saving variables"
+msgstr ""
+
+msgid "CiVariable|Masked"
+msgstr ""
+
+msgid "CiVariable|New environment"
+msgstr ""
+
+msgid "CiVariable|Protected"
+msgstr ""
+
+msgid "CiVariable|Search environments"
+msgstr ""
+
+msgid "CiVariable|Toggle masked"
+msgstr ""
+
+msgid "CiVariable|Toggle protected"
+msgstr ""
+
+msgid "CiVariable|Validation failed"
+msgstr ""
+
+msgid "Class"
+msgstr ""
+
+msgid "Classification Label (optional)"
+msgstr ""
+
+msgid "ClassificationLabelUnavailable|is unavailable: %{reason}"
+msgstr ""
+
+msgid "Clear"
+msgstr ""
+
+msgid "Clear chart filters"
+msgstr ""
+
+msgid "Clear due date"
+msgstr ""
+
+msgid "Clear input"
+msgstr ""
+
+msgid "Clear recent searches"
+msgstr ""
+
+msgid "Clear search"
+msgstr ""
+
+msgid "Clear search input"
+msgstr ""
+
+msgid "Clear start date"
+msgstr ""
+
+msgid "Clear templates search input"
+msgstr ""
+
+msgid "Clear weight"
+msgstr ""
+
+msgid "Cleared weight."
+msgstr ""
+
+msgid "Clears weight."
+msgstr ""
+
+msgid "Click any <strong>project name</strong> in the project list below to navigate to the project milestone."
+msgstr ""
+
+msgid "Click the <strong>Download</strong> button and wait for downloading to complete."
+msgstr ""
+
+msgid "Click the <strong>Promote</strong> button in the top right corner to promote it to a group milestone."
+msgstr ""
+
+msgid "Click the <strong>Select none</strong> button on the right, since we only need \"Google Code Project Hosting\"."
+msgstr ""
+
+msgid "Click the button below to begin the install process by navigating to the Kubernetes page"
+msgstr ""
+
+msgid "Click to expand it."
+msgstr ""
+
+msgid "Click to expand text"
+msgstr ""
+
+msgid "Client authentication certificate"
+msgstr ""
+
+msgid "Client authentication key"
+msgstr ""
+
+msgid "Client authentication key password"
+msgstr ""
+
+msgid "Clients"
+msgstr ""
+
+msgid "Clone"
+msgstr ""
+
+msgid "Clone repository"
+msgstr ""
+
+msgid "Clone with %{http_label}"
+msgstr ""
+
+msgid "Clone with %{protocol}"
+msgstr ""
+
+msgid "Clone with KRB5"
+msgstr ""
+
+msgid "Clone with SSH"
+msgstr ""
+
+msgid "Close"
+msgstr ""
+
+msgid "Close %{display_issuable_type}"
+msgstr ""
+
+msgid "Close %{tabname}"
+msgstr ""
+
+msgid "Close epic"
+msgstr ""
+
+msgid "Close milestone"
+msgstr ""
+
+msgid "Close sidebar"
+msgstr ""
+
+msgid "Close this %{quick_action_target}"
+msgstr ""
+
+msgid "Closed"
+msgstr ""
+
+msgid "Closed issues"
+msgstr ""
+
+msgid "Closed this %{quick_action_target}."
+msgstr ""
+
+msgid "Closed: %{closedIssuesCount}"
+msgstr ""
+
+msgid "Closes this %{quick_action_target}."
+msgstr ""
+
+msgid "Cluster"
+msgstr ""
+
+msgid "Cluster Health"
+msgstr ""
+
+msgid "Cluster cache cleared."
+msgstr ""
+
+msgid "Cluster does not exist"
+msgstr ""
+
+msgid "Cluster is required for Stages::ClusterEndpointInserter"
+msgstr ""
+
+msgid "Cluster level"
+msgstr ""
+
+msgid "Cluster type must be specificed for Stages::ClusterEndpointInserter"
+msgstr ""
+
+msgid "ClusterIntegration| %{custom_domain_start}More information%{custom_domain_end}."
+msgstr ""
+
+msgid "ClusterIntegration| This will permanently delete the following resources: <ul> <li>All installed applications and related resources</li> <li>The <code>gitlab-managed-apps</code> namespace</li> <li>Any project namespaces</li> <li><code>clusterroles</code></li> <li><code>clusterrolebindings</code></li> </ul>"
+msgstr ""
+
+msgid "ClusterIntegration| can be used instead of a custom domain."
+msgstr ""
+
+msgid "ClusterIntegration| is the default environment scope for this cluster. This means that all jobs, regardless of their environment, will use this cluster. %{environment_scope_start}More information%{environment_scope_end}"
+msgstr ""
+
+msgid "ClusterIntegration|%{appList} was successfully installed on your Kubernetes cluster"
+msgstr ""
+
+msgid "ClusterIntegration|%{external_ip}.nip.io"
+msgstr ""
+
+msgid "ClusterIntegration|%{title} uninstalled successfully."
+msgstr ""
+
+msgid "ClusterIntegration|%{title} updated successfully."
+msgstr ""
+
+msgid "ClusterIntegration|A cluster management project can be used to run deployment jobs with Kubernetes <code>cluster-admin</code> privileges."
+msgstr ""
+
+msgid "ClusterIntegration|A service token scoped to %{code}kube-system%{end_code} with %{code}cluster-admin%{end_code} privileges."
+msgstr ""
+
+msgid "ClusterIntegration|API URL"
+msgstr ""
+
+msgid "ClusterIntegration|API URL should be a valid http/https url."
+msgstr ""
+
+msgid "ClusterIntegration|Add Kubernetes cluster"
+msgstr ""
+
+msgid "ClusterIntegration|Add a Kubernetes cluster integration"
+msgstr ""
+
+msgid "ClusterIntegration|Adding a Kubernetes cluster to your group will automatically share the cluster across all your projects. Use review apps, deploy your applications, and easily run your pipelines for all projects using the same cluster."
+msgstr ""
+
+msgid "ClusterIntegration|Adding a Kubernetes cluster will automatically share the cluster across all projects. Use review apps, deploy your applications, and easily run your pipelines for all projects using the same cluster."
+msgstr ""
+
+msgid "ClusterIntegration|Adding an integration to your group will share the cluster across all your projects."
+msgstr ""
+
+msgid "ClusterIntegration|Adding an integration will share the cluster across all projects."
+msgstr ""
+
+msgid "ClusterIntegration|Advanced options on this Kubernetes cluster’s integration"
+msgstr ""
+
+msgid "ClusterIntegration|All data not committed to GitLab will be deleted and cannot be restored."
+msgstr ""
+
+msgid "ClusterIntegration|All data will be deleted and cannot be restored."
+msgstr ""
+
+msgid "ClusterIntegration|Allow GitLab to manage namespace and service accounts for this cluster."
+msgstr ""
+
+msgid "ClusterIntegration|Allow GitLab to manage namespace and service accounts for this cluster. %{startLink}More information%{endLink}"
+msgstr ""
+
+msgid "ClusterIntegration|Alternatively"
+msgstr ""
+
+msgid "ClusterIntegration|Amazon EKS"
+msgstr ""
+
+msgid "ClusterIntegration|An error occurred when trying to contact the Google Cloud API. Please try again later."
+msgstr ""
+
+msgid "ClusterIntegration|An error occurred while trying to fetch project zones: %{error}"
+msgstr ""
+
+msgid "ClusterIntegration|An error occurred while trying to fetch your projects: %{error}"
+msgstr ""
+
+msgid "ClusterIntegration|An error occurred while trying to fetch zone machine types: %{error}"
+msgstr ""
+
+msgid "ClusterIntegration|Any running pipelines will be canceled."
+msgstr ""
+
+msgid "ClusterIntegration|Apply for credit"
+msgstr ""
+
+msgid "ClusterIntegration|Authenticate with AWS"
+msgstr ""
+
+msgid "ClusterIntegration|Authenticate with Amazon Web Services"
+msgstr ""
+
+msgid "ClusterIntegration|Base domain"
+msgstr ""
+
+msgid "ClusterIntegration|Blocking mode"
+msgstr ""
+
+msgid "ClusterIntegration|CA Certificate"
+msgstr ""
+
+msgid "ClusterIntegration|Cert-Manager"
+msgstr ""
+
+msgid "ClusterIntegration|Cert-Manager is a native Kubernetes certificate management controller that helps with issuing certificates. Installing Cert-Manager on your cluster will issue a certificate by %{linkStart}Let's Encrypt%{linkEnd} and ensure that certificates are valid and up-to-date."
+msgstr ""
+
+msgid "ClusterIntegration|Certificate Authority bundle (PEM format)"
+msgstr ""
+
+msgid "ClusterIntegration|Choose the %{startLink}security group %{externalLinkIcon} %{endLink} to apply to the EKS-managed Elastic Network Interfaces that are created in your worker node subnets."
+msgstr ""
+
+msgid "ClusterIntegration|Choose the %{startLink}subnets %{externalLinkIcon} %{endLink} in your VPC where your worker nodes will run."
+msgstr ""
+
+msgid "ClusterIntegration|Choose the worker node %{startLink}instance type %{externalLinkIcon} %{endLink}."
+msgstr ""
+
+msgid "ClusterIntegration|Choose which applications to install on your Kubernetes cluster. Helm Tiller is required to install any of the following applications."
+msgstr ""
+
+msgid "ClusterIntegration|Choose which of your environments will use this cluster."
+msgstr ""
+
+msgid "ClusterIntegration|Clear cluster cache"
+msgstr ""
+
+msgid "ClusterIntegration|Clear the local cache of namespace and service accounts. This is necessary if your integration has become out of sync. The cache is repopulated during the next CI job that requires namespace and service accounts."
+msgstr ""
+
+msgid "ClusterIntegration|Cluster being created"
+msgstr ""
+
+msgid "ClusterIntegration|Cluster management project (alpha)"
+msgstr ""
+
+msgid "ClusterIntegration|Cluster name is required."
+msgstr ""
+
+msgid "ClusterIntegration|Cluster_applications artifact too big. Maximum allowable size: %{human_size}"
+msgstr ""
+
+msgid "ClusterIntegration|Clusters are utilized by selecting the nearest ancestor with a matching environment scope. For example, project clusters will override group clusters."
+msgstr ""
+
+msgid "ClusterIntegration|Copy API URL"
+msgstr ""
+
+msgid "ClusterIntegration|Copy CA Certificate"
+msgstr ""
+
+msgid "ClusterIntegration|Copy Ingress Endpoint"
+msgstr ""
+
+msgid "ClusterIntegration|Copy Jupyter Hostname"
+msgstr ""
+
+msgid "ClusterIntegration|Copy Knative Endpoint"
+msgstr ""
+
+msgid "ClusterIntegration|Copy Kubernetes cluster name"
+msgstr ""
+
+msgid "ClusterIntegration|Could not load IAM roles"
+msgstr ""
+
+msgid "ClusterIntegration|Could not load Key Pairs"
+msgstr ""
+
+msgid "ClusterIntegration|Could not load VPCs for the selected region"
+msgstr ""
+
+msgid "ClusterIntegration|Could not load instance types"
+msgstr ""
+
+msgid "ClusterIntegration|Could not load networks"
+msgstr ""
+
+msgid "ClusterIntegration|Could not load regions from your AWS account"
+msgstr ""
+
+msgid "ClusterIntegration|Could not load security groups for the selected VPC"
+msgstr ""
+
+msgid "ClusterIntegration|Could not load subnets for the selected VPC"
+msgstr ""
+
+msgid "ClusterIntegration|Could not load subnetworks"
+msgstr ""
+
+msgid "ClusterIntegration|Create Kubernetes cluster"
+msgstr ""
+
+msgid "ClusterIntegration|Create a provision role on %{startAwsLink}Amazon Web Services %{externalLinkIcon}%{endLink} using the account and external ID above. %{startMoreInfoLink}More information%{endLink}"
+msgstr ""
+
+msgid "ClusterIntegration|Create cluster on"
+msgstr ""
+
+msgid "ClusterIntegration|Create new cluster"
+msgstr ""
+
+msgid "ClusterIntegration|Create new cluster on EKS"
+msgstr ""
+
+msgid "ClusterIntegration|Create new cluster on GKE"
+msgstr ""
+
+msgid "ClusterIntegration|Creating Kubernetes cluster"
+msgstr ""
+
+msgid "ClusterIntegration|Crossplane"
+msgstr ""
+
+msgid "ClusterIntegration|Crossplane enables declarative provisioning of managed services from your cloud of choice using %{codeStart}kubectl%{codeEnd} or %{linkStart}GitLab Integration%{linkEnd}. Crossplane runs inside your Kubernetes cluster and supports secure connectivity and secrets management between app containers and the cloud services they depend on."
+msgstr ""
+
+msgid "ClusterIntegration|Deletes all GitLab resources attached to this cluster during removal"
+msgstr ""
+
+msgid "ClusterIntegration|Did you know?"
+msgstr ""
+
+msgid "ClusterIntegration|Elastic Kubernetes Service"
+msgstr ""
+
+msgid "ClusterIntegration|Elastic Stack"
+msgstr ""
+
+msgid "ClusterIntegration|Enable Cloud Run for Anthos"
+msgstr ""
+
+msgid "ClusterIntegration|Enable or disable GitLab's connection to your Kubernetes cluster."
+msgstr ""
+
+msgid "ClusterIntegration|Enable this setting if using role-based access control (RBAC)."
+msgstr ""
+
+msgid "ClusterIntegration|Enabled stack"
+msgstr ""
+
+msgid "ClusterIntegration|Enter new Service Token"
+msgstr ""
+
+msgid "ClusterIntegration|Enter the details for your Amazon EKS Kubernetes cluster"
+msgstr ""
+
+msgid "ClusterIntegration|Enter the details for your Kubernetes cluster"
+msgstr ""
+
+msgid "ClusterIntegration|Environment scope"
+msgstr ""
+
+msgid "ClusterIntegration|Every new Google Cloud Platform (GCP) account receives $300 in credit upon %{sign_up_link}. In partnership with Google, GitLab is able to offer an additional $200 for both new and existing GCP accounts to get started with GitLab's Google Kubernetes Engine Integration."
+msgstr ""
+
+msgid "ClusterIntegration|Failed to configure EKS provider: %{message}"
+msgstr ""
+
+msgid "ClusterIntegration|Failed to configure Google Kubernetes Engine Cluster: %{message}"
+msgstr ""
+
+msgid "ClusterIntegration|Failed to fetch CloudFormation stack: %{message}"
+msgstr ""
+
+msgid "ClusterIntegration|Failed to request to Google Cloud Platform: %{message}"
+msgstr ""
+
+msgid "ClusterIntegration|Failed to run Kubeclient: %{message}"
+msgstr ""
+
+msgid "ClusterIntegration|Fetching machine types"
+msgstr ""
+
+msgid "ClusterIntegration|Fetching projects"
+msgstr ""
+
+msgid "ClusterIntegration|Fetching zones"
+msgstr ""
+
+msgid "ClusterIntegration|Fluentd"
+msgstr ""
+
+msgid "ClusterIntegration|Fluentd is an open source data collector, which lets you unify the data collection and consumption for a better use and understanding of data. It requires at least one of the following logs to be successfully installed."
+msgstr ""
+
+msgid "ClusterIntegration|GitLab Integration"
+msgstr ""
+
+msgid "ClusterIntegration|GitLab Runner"
+msgstr ""
+
+msgid "ClusterIntegration|GitLab Runner connects to the repository and executes CI/CD jobs, pushing results back and deploying applications to production."
+msgstr ""
+
+msgid "ClusterIntegration|GitLab-managed cluster"
+msgstr ""
+
+msgid "ClusterIntegration|Global default"
+msgstr ""
+
+msgid "ClusterIntegration|Google Cloud Platform project"
+msgstr ""
+
+msgid "ClusterIntegration|Google GKE"
+msgstr ""
+
+msgid "ClusterIntegration|Google Kubernetes Engine"
+msgstr ""
+
+msgid "ClusterIntegration|Google Kubernetes Engine project"
+msgstr ""
+
+msgid "ClusterIntegration|Group cluster"
+msgstr ""
+
+msgid "ClusterIntegration|Helm Tiller"
+msgstr ""
+
+msgid "ClusterIntegration|Helm release failed to install"
+msgstr ""
+
+msgid "ClusterIntegration|Helm streamlines installing and managing Kubernetes applications. Tiller runs inside of your Kubernetes Cluster, and manages releases of your charts."
+msgstr ""
+
+msgid "ClusterIntegration|If you are setting up multiple clusters and are using Auto DevOps, %{help_link_start}read this first%{help_link_end}."
+msgstr ""
+
+msgid "ClusterIntegration|If you do not wish to delete all associated GitLab resources, you can simply remove the integration."
+msgstr ""
+
+msgid "ClusterIntegration|In order to view the health of your cluster, you must first install Prometheus in the Applications tab."
+msgstr ""
+
+msgid "ClusterIntegration|Ingress"
+msgstr ""
+
+msgid "ClusterIntegration|Ingress Endpoint"
+msgstr ""
+
+msgid "ClusterIntegration|Ingress gives you a way to route requests to services based on the request host or path, centralizing a number of services into a single entrypoint."
+msgstr ""
+
+msgid "ClusterIntegration|Installing Ingress may incur additional costs. Learn more about %{linkStart}pricing%{linkEnd}."
+msgstr ""
+
+msgid "ClusterIntegration|Instance cluster"
+msgstr ""
+
+msgid "ClusterIntegration|Instance type"
+msgstr ""
+
+msgid "ClusterIntegration|Integrate Kubernetes cluster automation"
+msgstr ""
+
+msgid "ClusterIntegration|Issuer Email"
+msgstr ""
+
+msgid "ClusterIntegration|Issuers represent a certificate authority. You must provide an email address for your Issuer."
+msgstr ""
+
+msgid "ClusterIntegration|Jupyter Hostname"
+msgstr ""
+
+msgid "ClusterIntegration|JupyterHub"
+msgstr ""
+
+msgid "ClusterIntegration|JupyterHub, a multi-user Hub, spawns, manages, and proxies multiple instances of the single-user Jupyter notebook server. JupyterHub can be used to serve notebooks to a class of students, a corporate data science group, or a scientific research group."
+msgstr ""
+
+msgid "ClusterIntegration|Key pair name"
+msgstr ""
+
+msgid "ClusterIntegration|Knative"
+msgstr ""
+
+msgid "ClusterIntegration|Knative Domain Name:"
+msgstr ""
+
+msgid "ClusterIntegration|Knative Endpoint:"
+msgstr ""
+
+msgid "ClusterIntegration|Knative domain name was updated successfully."
+msgstr ""
+
+msgid "ClusterIntegration|Knative extends Kubernetes to provide a set of middleware components that are essential to build modern, source-centric, and container-based applications that can run anywhere: on premises, in the cloud, or even in a third-party data center."
+msgstr ""
+
+msgid "ClusterIntegration|Kubernetes cluster"
+msgstr ""
+
+msgid "ClusterIntegration|Kubernetes cluster is being created..."
+msgstr ""
+
+msgid "ClusterIntegration|Kubernetes cluster name"
+msgstr ""
+
+msgid "ClusterIntegration|Kubernetes cluster was successfully created."
+msgstr ""
+
+msgid "ClusterIntegration|Kubernetes clusters allow you to use review apps, deploy your applications, run your pipelines, and much more in an easy way."
+msgstr ""
+
+msgid "ClusterIntegration|Kubernetes clusters can be used to deploy applications and to provide Review Apps for this project"
+msgstr ""
+
+msgid "ClusterIntegration|Kubernetes version"
+msgstr ""
+
+msgid "ClusterIntegration|Kubernetes version not found"
+msgstr ""
+
+msgid "ClusterIntegration|Learn more about %{help_link_start_machine_type}machine types%{help_link_end} and %{help_link_start_pricing}pricing%{help_link_end}."
+msgstr ""
+
+msgid "ClusterIntegration|Learn more about %{help_link_start}zones%{help_link_end}."
+msgstr ""
+
+msgid "ClusterIntegration|Learn more about %{startLink}Regions %{externalLinkIcon}%{endLink}."
+msgstr ""
+
+msgid "ClusterIntegration|Learn more about Kubernetes"
+msgstr ""
+
+msgid "ClusterIntegration|Learn more about group Kubernetes clusters"
+msgstr ""
+
+msgid "ClusterIntegration|Learn more about instance Kubernetes clusters"
+msgstr ""
+
+msgid "ClusterIntegration|Loading IAM Roles"
+msgstr ""
+
+msgid "ClusterIntegration|Loading Key Pairs"
+msgstr ""
+
+msgid "ClusterIntegration|Loading Regions"
+msgstr ""
+
+msgid "ClusterIntegration|Loading VPCs"
+msgstr ""
+
+msgid "ClusterIntegration|Loading instance types"
+msgstr ""
+
+msgid "ClusterIntegration|Loading networks"
+msgstr ""
+
+msgid "ClusterIntegration|Loading security groups"
+msgstr ""
+
+msgid "ClusterIntegration|Loading subnets"
+msgstr ""
+
+msgid "ClusterIntegration|Loading subnetworks"
+msgstr ""
+
+msgid "ClusterIntegration|Logging mode"
+msgstr ""
+
+msgid "ClusterIntegration|Machine type"
+msgstr ""
+
+msgid "ClusterIntegration|Make sure your account %{link_to_requirements} to create Kubernetes clusters"
+msgstr ""
+
+msgid "ClusterIntegration|Manage your Kubernetes cluster by visiting %{provider_link}"
+msgstr ""
+
+msgid "ClusterIntegration|No IAM Roles found"
+msgstr ""
+
+msgid "ClusterIntegration|No Key Pairs found"
+msgstr ""
+
+msgid "ClusterIntegration|No VPCs found"
+msgstr ""
+
+msgid "ClusterIntegration|No deployment cluster found for this job"
+msgstr ""
+
+msgid "ClusterIntegration|No deployment found for this job"
+msgstr ""
+
+msgid "ClusterIntegration|No instance type found"
+msgstr ""
+
+msgid "ClusterIntegration|No machine types matched your search"
+msgstr ""
+
+msgid "ClusterIntegration|No networks found"
+msgstr ""
+
+msgid "ClusterIntegration|No projects found"
+msgstr ""
+
+msgid "ClusterIntegration|No projects matched your search"
+msgstr ""
+
+msgid "ClusterIntegration|No region found"
+msgstr ""
+
+msgid "ClusterIntegration|No security group found"
+msgstr ""
+
+msgid "ClusterIntegration|No subnet found"
+msgstr ""
+
+msgid "ClusterIntegration|No subnetworks found"
+msgstr ""
+
+msgid "ClusterIntegration|No zones matched your search"
+msgstr ""
+
+msgid "ClusterIntegration|Number of nodes"
+msgstr ""
+
+msgid "ClusterIntegration|Number of nodes must be a numerical value."
+msgstr ""
+
+msgid "ClusterIntegration|Please enter access information for your Kubernetes cluster. If you need help, you can read our %{link_to_help_page} on Kubernetes"
+msgstr ""
+
+msgid "ClusterIntegration|Please make sure that your Google account meets the following requirements:"
+msgstr ""
+
+msgid "ClusterIntegration|Point a wildcard DNS to this generated endpoint in order to access your application after it has been deployed."
+msgstr ""
+
+msgid "ClusterIntegration|Project cluster"
+msgstr ""
+
+msgid "ClusterIntegration|Project namespace (optional, unique)"
+msgstr ""
+
+msgid "ClusterIntegration|Project namespace prefix (optional, unique)"
+msgstr ""
+
+msgid "ClusterIntegration|Prometheus"
+msgstr ""
+
+msgid "ClusterIntegration|Prometheus is an open-source monitoring system with %{linkStart}GitLab Integration%{linkEnd} to monitor deployed applications."
+msgstr ""
+
+msgid "ClusterIntegration|Provider details"
+msgstr ""
+
+msgid "ClusterIntegration|Provision Role ARN"
+msgstr ""
+
+msgid "ClusterIntegration|RBAC-enabled cluster"
+msgstr ""
+
+msgid "ClusterIntegration|Read our %{link_start}help page%{link_end} on Kubernetes cluster integration."
+msgstr ""
+
+msgid "ClusterIntegration|Real-time web application monitoring, logging and access control. %{linkStart}More information%{linkEnd}"
+msgstr ""
+
+msgid "ClusterIntegration|Region"
+msgstr ""
+
+msgid "ClusterIntegration|Remove Kubernetes cluster integration"
+msgstr ""
+
+msgid "ClusterIntegration|Remove integration"
+msgstr ""
+
+msgid "ClusterIntegration|Remove integration and resources"
+msgstr ""
+
+msgid "ClusterIntegration|Remove integration and resources?"
+msgstr ""
+
+msgid "ClusterIntegration|Remove integration?"
+msgstr ""
+
+msgid "ClusterIntegration|Remove this Kubernetes cluster's configuration from this project. This will not delete your actual Kubernetes cluster."
+msgstr ""
+
+msgid "ClusterIntegration|Removes cluster from project but keeps associated resources"
+msgstr ""
+
+msgid "ClusterIntegration|Replace this with your own hostname if you want. If you do so, point hostname to Ingress IP Address from above."
+msgstr ""
+
+msgid "ClusterIntegration|Request to begin installing failed"
+msgstr ""
+
+msgid "ClusterIntegration|Request to begin uninstalling failed"
+msgstr ""
+
+msgid "ClusterIntegration|SIEM Hostname"
+msgstr ""
+
+msgid "ClusterIntegration|SIEM Port"
+msgstr ""
+
+msgid "ClusterIntegration|SIEM Protocol"
+msgstr ""
+
+msgid "ClusterIntegration|Save changes"
+msgstr ""
+
+msgid "ClusterIntegration|Search IAM Roles"
+msgstr ""
+
+msgid "ClusterIntegration|Search Key Pairs"
+msgstr ""
+
+msgid "ClusterIntegration|Search VPCs"
+msgstr ""
+
+msgid "ClusterIntegration|Search domains"
+msgstr ""
+
+msgid "ClusterIntegration|Search instance types"
+msgstr ""
+
+msgid "ClusterIntegration|Search machine types"
+msgstr ""
+
+msgid "ClusterIntegration|Search networks"
+msgstr ""
+
+msgid "ClusterIntegration|Search projects"
+msgstr ""
+
+msgid "ClusterIntegration|Search regions"
+msgstr ""
+
+msgid "ClusterIntegration|Search security groups"
+msgstr ""
+
+msgid "ClusterIntegration|Search subnets"
+msgstr ""
+
+msgid "ClusterIntegration|Search subnetworks"
+msgstr ""
+
+msgid "ClusterIntegration|Search zones"
+msgstr ""
+
+msgid "ClusterIntegration|Security group"
+msgstr ""
+
+msgid "ClusterIntegration|See and edit the details for your Kubernetes cluster"
+msgstr ""
+
+msgid "ClusterIntegration|Select a VPC to choose a security group"
+msgstr ""
+
+msgid "ClusterIntegration|Select a VPC to choose a subnet"
+msgstr ""
+
+msgid "ClusterIntegration|Select a VPC to use for your EKS Cluster resources. To use a new VPC, first create one on %{startLink}Amazon Web Services %{externalLinkIcon} %{endLink}."
+msgstr ""
+
+msgid "ClusterIntegration|Select a network to choose a subnetwork"
+msgstr ""
+
+msgid "ClusterIntegration|Select a region to choose a Key Pair"
+msgstr ""
+
+msgid "ClusterIntegration|Select a region to choose a VPC"
+msgstr ""
+
+msgid "ClusterIntegration|Select a stack to install Crossplane."
+msgstr ""
+
+msgid "ClusterIntegration|Select a zone to choose a network"
+msgstr ""
+
+msgid "ClusterIntegration|Select existing domain or use new"
+msgstr ""
+
+msgid "ClusterIntegration|Select machine type"
+msgstr ""
+
+msgid "ClusterIntegration|Select project"
+msgstr ""
+
+msgid "ClusterIntegration|Select project and zone to choose machine type"
+msgstr ""
+
+msgid "ClusterIntegration|Select project to choose zone"
+msgstr ""
+
+msgid "ClusterIntegration|Select the key pair name that will be used to create EC2 nodes. To use a new key pair name, first create one on %{startLink}Amazon Web Services %{externalLinkIcon} %{endLink}."
+msgstr ""
+
+msgid "ClusterIntegration|Select zone"
+msgstr ""
+
+msgid "ClusterIntegration|Select zone to choose machine type"
+msgstr ""
+
+msgid "ClusterIntegration|Send Container Network Policies Logs"
+msgstr ""
+
+msgid "ClusterIntegration|Send Web Application Firewall Logs"
+msgstr ""
+
+msgid "ClusterIntegration|Service Token"
+msgstr ""
+
+msgid "ClusterIntegration|Service role"
+msgstr ""
+
+msgid "ClusterIntegration|Service token is required."
+msgstr ""
+
+msgid "ClusterIntegration|Set a prefix for your namespaces. If not set, defaults to your project path. If modified, existing environments will use their current namespaces until the cluster cache is cleared."
+msgstr ""
+
+msgid "ClusterIntegration|Set the global mode for the WAF in this cluster. This can be overridden at the environmental level."
+msgstr ""
+
+msgid "ClusterIntegration|Something went wrong on our end."
+msgstr ""
+
+msgid "ClusterIntegration|Something went wrong while creating your Kubernetes cluster"
+msgstr ""
+
+msgid "ClusterIntegration|Something went wrong while installing %{title}"
+msgstr ""
+
+msgid "ClusterIntegration|Something went wrong while trying to save your settings. Please try again."
+msgstr ""
+
+msgid "ClusterIntegration|Something went wrong while uninstalling %{title}"
+msgstr ""
+
+msgid "ClusterIntegration|Something went wrong while updating Knative domain name."
+msgstr ""
+
+msgid "ClusterIntegration|Specifying a domain will allow you to use Auto Review Apps and Auto Deploy stages for %{auto_devops_start}Auto DevOps%{auto_devops_end}. The domain should have a wildcard DNS configured matching the domain."
+msgstr ""
+
+msgid "ClusterIntegration|Subnets"
+msgstr ""
+
+msgid "ClusterIntegration|The Amazon Resource Name (ARN) associated with your role. If you do not have a provision role, first create one on %{startAwsLink}Amazon Web Services %{externalLinkIcon}%{endLink} using the above account and external IDs. %{startMoreInfoLink}More information%{endLink}"
+msgstr ""
+
+msgid "ClusterIntegration|The Kubernetes certificate used to authenticate to the cluster."
+msgstr ""
+
+msgid "ClusterIntegration|The URL used to access the Kubernetes API."
+msgstr ""
+
+msgid "ClusterIntegration|The associated IP and all deployed services will be deleted and cannot be restored. Uninstalling Knative will also remove Istio from your cluster. This will not effect any other applications."
+msgstr ""
+
+msgid "ClusterIntegration|The associated Tiller pod, the %{gitlabManagedAppsNamespace} namespace, and all of its resources will be deleted and cannot be restored."
+msgstr ""
+
+msgid "ClusterIntegration|The associated load balancer and IP will be deleted and cannot be restored."
+msgstr ""
+
+msgid "ClusterIntegration|The associated private key will be deleted and cannot be restored."
+msgstr ""
+
+msgid "ClusterIntegration|The elastic stack collects logs from all pods in your cluster"
+msgstr ""
+
+msgid "ClusterIntegration|The endpoint is in the process of being assigned. Please check your Kubernetes cluster or Quotas on Google Kubernetes Engine if it takes a long time."
+msgstr ""
+
+msgid "ClusterIntegration|The namespace associated with your project. This will be used for deploy boards, logs, and Web terminals."
+msgstr ""
+
+msgid "ClusterIntegration|There was a problem authenticating with your cluster. Please ensure your CA Certificate and Token are valid."
+msgstr ""
+
+msgid "ClusterIntegration|This account must have permissions to create a Kubernetes cluster in the %{link_to_container_project} specified below"
+msgstr ""
+
+msgid "ClusterIntegration|This option will allow you to install applications on RBAC clusters."
+msgstr ""
+
+msgid "ClusterIntegration|To access your application after deployment, point a wildcard DNS to the Knative Endpoint."
+msgstr ""
+
+msgid "ClusterIntegration|To remove your integration and resources, type %{clusterName} to confirm:"
+msgstr ""
+
+msgid "ClusterIntegration|To remove your integration, type %{clusterName} to confirm:"
+msgstr ""
+
+msgid "ClusterIntegration|Toggle Kubernetes cluster"
+msgstr ""
+
+msgid "ClusterIntegration|Uninstall %{appTitle}"
+msgstr ""
+
+msgid "ClusterIntegration|Update %{appTitle}"
+msgstr ""
+
+msgid "ClusterIntegration|Update failed. Please check the logs and try again."
+msgstr ""
+
+msgid "ClusterIntegration|Use %{query}"
+msgstr ""
+
+msgid "ClusterIntegration|Uses the Cloud Run, Istio, and HTTP Load Balancing addons for this cluster."
+msgstr ""
+
+msgid "ClusterIntegration|VPC"
+msgstr ""
+
+msgid "ClusterIntegration|Validating project billing status"
+msgstr ""
+
+msgid "ClusterIntegration|We could not verify that one of your projects on GCP has billing enabled. Please try again."
+msgstr ""
+
+msgid "ClusterIntegration|With a Kubernetes cluster associated to this project, you can use review apps, deploy your applications, run your pipelines, and much more in an easy way."
+msgstr ""
+
+msgid "ClusterIntegration|You are about to remove your cluster integration and all GitLab-created resources associated with this cluster."
+msgstr ""
+
+msgid "ClusterIntegration|You are about to remove your cluster integration."
+msgstr ""
+
+msgid "ClusterIntegration|You are about to uninstall %{appTitle} from your cluster."
+msgstr ""
+
+msgid "ClusterIntegration|You are about to update %{appTitle} on your cluster."
+msgstr ""
+
+msgid "ClusterIntegration|You must first install Helm Tiller before installing the applications below"
+msgstr ""
+
+msgid "ClusterIntegration|You must grant access to your organization’s AWS resources in order to create a new EKS cluster. To grant access, create a provision role using the account and external ID below and provide us the ARN."
+msgstr ""
+
+msgid "ClusterIntegration|You must have an RBAC-enabled cluster to install Knative."
+msgstr ""
+
+msgid "ClusterIntegration|You must specify a domain before you can install Knative."
+msgstr ""
+
+msgid "ClusterIntegration|You should select at least two subnets"
+msgstr ""
+
+msgid "ClusterIntegration|Your Elasticsearch cluster will be re-created during this upgrade. Your logs will be re-indexed, and you will lose historical logs from hosts terminated in the last 30 days."
+msgstr ""
+
+msgid "ClusterIntegration|Your account must have %{link_to_kubernetes_engine}"
+msgstr ""
+
+msgid "ClusterIntegration|Your cluster API is unreachable. Please ensure your API URL is correct."
+msgstr ""
+
+msgid "ClusterIntegration|Your service role is distinct from the provision role used when authenticating. It will allow Amazon EKS and the Kubernetes control plane to manage AWS resources on your behalf. To use a new role, first create one on %{startLink}Amazon Web Services %{externalLinkIcon} %{endLink}."
+msgstr ""
+
+msgid "ClusterIntegration|Zone"
+msgstr ""
+
+msgid "ClusterIntegration|access to Google Kubernetes Engine"
+msgstr ""
+
+msgid "ClusterIntegration|documentation"
+msgstr ""
+
+msgid "ClusterIntegration|installed via %{linkStart}Cloud Run%{linkEnd}"
+msgstr ""
+
+msgid "ClusterIntegration|meets the requirements"
+msgstr ""
+
+msgid "ClusterIntegration|sign up"
+msgstr ""
+
+msgid "ClusterIntergation|Select a VPC"
+msgstr ""
+
+msgid "ClusterIntergation|Select a network"
+msgstr ""
+
+msgid "ClusterIntergation|Select a region"
+msgstr ""
+
+msgid "ClusterIntergation|Select a security group"
+msgstr ""
+
+msgid "ClusterIntergation|Select a subnet"
+msgstr ""
+
+msgid "ClusterIntergation|Select a subnetwork"
+msgstr ""
+
+msgid "ClusterIntergation|Select an instance type"
+msgstr ""
+
+msgid "ClusterIntergation|Select key pair"
+msgstr ""
+
+msgid "ClusterIntergation|Select service role"
+msgstr ""
+
+msgid "Clusters|An error occurred while loading clusters"
+msgstr ""
+
+msgid "Code"
+msgstr ""
+
+msgid "Code Coverage: %{coveragePercentage}%{percentSymbol}"
+msgstr ""
+
+msgid "Code Coverage| Empty code coverage data"
+msgstr ""
+
+msgid "Code Coverage|Couldn't fetch the code coverage data"
+msgstr ""
+
+msgid "Code Owners"
+msgstr ""
+
+msgid "Code Owners to the merge request changes."
+msgstr ""
+
+msgid "Code Quality"
+msgstr ""
+
+msgid "Code Review"
+msgstr ""
+
+msgid "Code Review Analytics displays a table of open merge requests considered to be in code review. There are currently no merge requests in review for this project and/or filters."
+msgstr ""
+
+msgid "Code coverage statistics for master %{start_date} - %{end_date}"
+msgstr ""
+
+msgid "Code owner approval is required"
+msgstr ""
+
+msgid "Code owners"
+msgstr ""
+
+msgid "CodeOwner|Pattern"
+msgstr ""
+
+msgid "Cohorts"
+msgstr ""
+
+msgid "Cohorts|Inactive users"
+msgstr ""
+
+msgid "Cohorts|Month %{month_index}"
+msgstr ""
+
+msgid "Cohorts|New users"
+msgstr ""
+
+msgid "Cohorts|Registration month"
+msgstr ""
+
+msgid "Cohorts|Returning users"
+msgstr ""
+
+msgid "Cohorts|User cohorts are shown for the last %{months_included} months. Only users with activity are counted in the 'New users' column; inactive users are counted separately."
+msgstr ""
+
+msgid "Collapse"
+msgstr ""
+
+msgid "Collapse approvers"
+msgstr ""
+
+msgid "Collapse milestones"
+msgstr ""
+
+msgid "Collapse replies"
+msgstr ""
+
+msgid "Collapse sidebar"
+msgstr ""
+
+msgid "Collector hostname"
+msgstr ""
+
+msgid "ComboSearch is not defined"
+msgstr ""
+
+msgid "Coming soon"
+msgstr ""
+
+msgid "Comma-separated, e.g. '1.1.1.1, 2.2.2.0/24'"
+msgstr ""
+
+msgid "Command"
+msgstr ""
+
+msgid "Command line instructions"
+msgstr ""
+
+msgid "Commands applied"
+msgstr ""
+
+msgid "Commands did not apply"
+msgstr ""
+
+msgid "Comment"
+msgstr ""
+
+msgid "Comment & close %{noteable_name}"
+msgstr ""
+
+msgid "Comment & reopen %{noteable_name}"
+msgstr ""
+
+msgid "Comment & resolve thread"
+msgstr ""
+
+msgid "Comment & unresolve thread"
+msgstr ""
+
+msgid "Comment '%{label}' position"
+msgstr ""
+
+msgid "Comment form position"
+msgstr ""
+
+msgid "Comment is being updated"
+msgstr ""
+
+msgid "Comment on lines %{startLine} to %{endLine}"
+msgstr ""
+
+msgid "Comment/Reply (quoting selected text)"
+msgstr ""
+
+msgid "Comments"
+msgstr ""
+
+msgid "Commit"
+msgid_plural "Commits"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Commit %{commit_id}"
+msgstr ""
+
+msgid "Commit (when editing commit message)"
+msgstr ""
+
+msgid "Commit Message"
+msgstr ""
+
+msgid "Commit deleted"
+msgstr ""
+
+msgid "Commit message"
+msgstr ""
+
+msgid "Commit message (optional)"
+msgstr ""
+
+msgid "Commit statistics for %{ref} %{start_time} - %{end_time}"
+msgstr ""
+
+msgid "Commit to %{branchName} branch"
+msgstr ""
+
+msgid "CommitBoxTitle|Commit"
+msgstr ""
+
+msgid "CommitMessage|Add %{file_name}"
+msgstr ""
+
+msgid "CommitWidget|authored"
+msgstr ""
+
+msgid "Commits"
+msgstr ""
+
+msgid "Commits feed"
+msgstr ""
+
+msgid "Commits per day hour (UTC)"
+msgstr ""
+
+msgid "Commits per day of month"
+msgstr ""
+
+msgid "Commits per weekday"
+msgstr ""
+
+msgid "Commits to"
+msgstr ""
+
+msgid "Commits|An error occurred while fetching merge requests data."
+msgstr ""
+
+msgid "Commits|History"
+msgstr ""
+
+msgid "Commits|No related merge requests found"
+msgstr ""
+
+msgid "Committed by"
+msgstr ""
+
+msgid "Commit…"
+msgstr ""
+
+msgid "Company"
+msgstr ""
+
+msgid "Company name"
+msgstr ""
+
+msgid "Compare"
+msgstr ""
+
+msgid "Compare Git revisions"
+msgstr ""
+
+msgid "Compare Revisions"
+msgstr ""
+
+msgid "Compare changes"
+msgstr ""
+
+msgid "Compare changes with the last commit"
+msgstr ""
+
+msgid "Compare changes with the merge request target branch"
+msgstr ""
+
+msgid "Compare with previous version"
+msgstr ""
+
+msgid "CompareBranches|%{source_branch} and %{target_branch} are the same."
+msgstr ""
+
+msgid "CompareBranches|Compare"
+msgstr ""
+
+msgid "CompareBranches|Source"
+msgstr ""
+
+msgid "CompareBranches|Target"
+msgstr ""
+
+msgid "CompareBranches|There isn't anything to compare."
+msgstr ""
+
+msgid "Complete"
+msgstr ""
+
+msgid "Compliance"
+msgstr ""
+
+msgid "Compliance Dashboard"
+msgstr ""
+
+msgid "Compliance framework (optional)"
+msgstr ""
+
+msgid "Compliance frameworks"
+msgstr ""
+
+msgid "ComplianceDashboard|created by:"
+msgstr ""
+
+msgid "ComplianceFramework|GDPR"
+msgstr ""
+
+msgid "ComplianceFramework|GDPR - General Data Protection Regulation"
+msgstr ""
+
+msgid "ComplianceFramework|HIPAA"
+msgstr ""
+
+msgid "ComplianceFramework|HIPAA - Health Insurance Portability and Accountability Act"
+msgstr ""
+
+msgid "ComplianceFramework|PCI-DSS"
+msgstr ""
+
+msgid "ComplianceFramework|PCI-DSS - Payment Card Industry-Data Security Standard"
+msgstr ""
+
+msgid "ComplianceFramework|SOC 2"
+msgstr ""
+
+msgid "ComplianceFramework|SOC 2 - Service Organization Control 2"
+msgstr ""
+
+msgid "ComplianceFramework|SOX"
+msgstr ""
+
+msgid "ComplianceFramework|SOX - Sarbanes-Oxley"
+msgstr ""
+
+msgid "ComplianceFramework|This project is regulated by %{framework}."
+msgstr ""
+
+msgid "Confidence"
+msgstr ""
+
+msgid "Confidential"
+msgstr ""
+
+msgid "Confidentiality"
+msgstr ""
+
+msgid "Configuration"
+msgstr ""
+
+msgid "Configure GitLab runners to start using the Web Terminal. %{helpStart}Learn more.%{helpEnd}"
+msgstr ""
+
+msgid "Configure Gitaly timeouts."
+msgstr ""
+
+msgid "Configure Let's Encrypt"
+msgstr ""
+
+msgid "Configure Prometheus"
+msgstr ""
+
+msgid "Configure Tracing"
+msgstr ""
+
+msgid "Configure a <code>.gitlab-webide.yml</code> file in the <code>.gitlab</code> directory to start using the Web Terminal. %{helpStart}Learn more.%{helpEnd}"
+msgstr ""
+
+msgid "Configure automatic git checks and housekeeping on repositories."
+msgstr ""
+
+msgid "Configure existing installation"
+msgstr ""
+
+msgid "Configure limit for issues created per minute by web and API requests."
+msgstr ""
+
+msgid "Configure limits for web and API requests."
+msgstr ""
+
+msgid "Configure limits on the number of inbound alerts able to be sent to a project."
+msgstr ""
+
+msgid "Configure paths to be protected by Rack Attack."
+msgstr ""
+
+msgid "Configure repository mirroring."
+msgstr ""
+
+msgid "Configure storage path settings."
+msgstr ""
+
+msgid "Configure the %{link} integration."
+msgstr ""
+
+msgid "Configure the way a user creates a new account."
+msgstr ""
+
+msgid "Confirm"
+msgstr ""
+
+msgid "Confirmation email sent to %{email}"
+msgstr ""
+
+msgid "Confirmation required"
+msgstr ""
+
+msgid "Congratulations! You have enabled Two-factor Authentication!"
+msgstr ""
+
+msgid "Connect"
+msgstr ""
+
+msgid "Connect all repositories"
+msgstr ""
+
+msgid "Connect repositories from GitHub"
+msgstr ""
+
+msgid "Connect your external repositories, and CI/CD pipelines will run for new commits. A GitLab project will be created with only CI/CD features enabled."
+msgstr ""
+
+msgid "Connected"
+msgstr ""
+
+msgid "Connecting"
+msgstr ""
+
+msgid "Connecting to terminal sync service"
+msgstr ""
+
+msgid "Connecting..."
+msgstr ""
+
+msgid "Connection failed"
+msgstr ""
+
+msgid "Connection failure"
+msgstr ""
+
+msgid "Connection timed out"
+msgstr ""
+
+msgid "Connection timeout"
+msgstr ""
+
+msgid "Contact sales to upgrade"
+msgstr ""
+
+msgid "Contact support"
+msgstr ""
+
+msgid "Container Registry"
+msgstr ""
+
+msgid "Container Registry tag expiration policy"
+msgstr ""
+
+msgid "Container Scanning"
+msgstr ""
+
+msgid "Container does not exist"
+msgstr ""
+
+msgid "Container registry images"
+msgstr ""
+
+msgid "Container registry is not enabled on this GitLab instance. Ask an administrator to enable it in order for Auto DevOps to work."
+msgstr ""
+
+msgid "Container repositories sync capacity"
+msgstr ""
+
+msgid "ContainerRegistry| Please visit the %{linkStart}administration settings%{linkEnd} to enable this feature."
+msgstr ""
+
+msgid "ContainerRegistry|%{count} Image repository"
+msgid_plural "ContainerRegistry|%{count} Image repositories"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "ContainerRegistry|%{count} Tag"
+msgid_plural "ContainerRegistry|%{count} Tags"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "ContainerRegistry|%{imageName} tags"
+msgstr ""
+
+msgid "ContainerRegistry|%{title} was successfully scheduled for deletion"
+msgstr ""
+
+msgid "ContainerRegistry|Automatically remove extra images that aren't designed to be kept."
+msgstr ""
+
+msgid "ContainerRegistry|Build an image"
+msgstr ""
+
+msgid "ContainerRegistry|CLI Commands"
+msgstr ""
+
+msgid "ContainerRegistry|Container Registry"
+msgstr ""
+
+msgid "ContainerRegistry|Container Registry tag expiration and retention policy is disabled"
+msgstr ""
+
+msgid "ContainerRegistry|Copy build command"
+msgstr ""
+
+msgid "ContainerRegistry|Copy login command"
+msgstr ""
+
+msgid "ContainerRegistry|Copy push command"
+msgstr ""
+
+msgid "ContainerRegistry|Delete selected"
+msgstr ""
+
+msgid "ContainerRegistry|Docker connection error"
+msgstr ""
+
+msgid "ContainerRegistry|Docker tag expiration policy is %{toggleStatus}"
+msgstr ""
+
+msgid "ContainerRegistry|Expiration interval:"
+msgstr ""
+
+msgid "ContainerRegistry|Expiration policies help manage the storage space used by the Container Registry, but the expiration policies for this registry are disabled. Contact your administrator to enable. %{docLinkStart}More information%{docLinkEnd}"
+msgstr ""
+
+msgid "ContainerRegistry|Expiration policy is disabled"
+msgstr ""
+
+msgid "ContainerRegistry|Expiration policy successfully saved."
+msgstr ""
+
+msgid "ContainerRegistry|Expiration policy will run in %{time}"
+msgstr ""
+
+msgid "ContainerRegistry|Expiration policy:"
+msgstr ""
+
+msgid "ContainerRegistry|Expiration schedule:"
+msgstr ""
+
+msgid "ContainerRegistry|Filter by name"
+msgstr ""
+
+msgid "ContainerRegistry|If you are not already logged in, you need to authenticate to the Container Registry by using your GitLab username and password. If you have %{twofaDocLinkStart}Two-Factor Authentication%{twofaDocLinkEnd} enabled, use a %{personalAccessTokensDocLinkStart}Personal Access Token%{personalAccessTokensDocLinkEnd} instead of a password."
+msgstr ""
+
+msgid "ContainerRegistry|Image ID: %{imageId}"
+msgstr ""
+
+msgid "ContainerRegistry|Image Repositories"
+msgstr ""
+
+msgid "ContainerRegistry|Image tags"
+msgstr ""
+
+msgid "ContainerRegistry|Keep and protect the images that matter most."
+msgstr ""
+
+msgid "ContainerRegistry|Login"
+msgstr ""
+
+msgid "ContainerRegistry|Missing or insufficient permission, delete button disabled"
+msgstr ""
+
+msgid "ContainerRegistry|Number of tags to retain:"
+msgstr ""
+
+msgid "ContainerRegistry|Please contact your administrator."
+msgstr ""
+
+msgid "ContainerRegistry|Published %{timeInfo}"
+msgstr ""
+
+msgid "ContainerRegistry|Push an image"
+msgstr ""
+
+msgid "ContainerRegistry|Regular expressions such as %{codeStart}.*-master%{codeEnd} or %{codeStart}release-.*%{codeEnd} are supported"
+msgstr ""
+
+msgid "ContainerRegistry|Regular expressions such as %{codeStart}.*-test%{codeEnd} or %{codeStart}dev-.*%{codeEnd} are supported. To select all tags, use %{codeStart}.*%{codeEnd}"
+msgstr ""
+
+msgid "ContainerRegistry|Remember to run %{docLinkStart}garbage collection%{docLinkEnd} to remove the stale data from storage."
+msgstr ""
+
+msgid "ContainerRegistry|Remove repository"
+msgstr ""
+
+msgid "ContainerRegistry|Remove tag"
+msgid_plural "ContainerRegistry|Remove tags"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "ContainerRegistry|Something went wrong while fetching the expiration policy."
+msgstr ""
+
+msgid "ContainerRegistry|Something went wrong while fetching the repository list."
+msgstr ""
+
+msgid "ContainerRegistry|Something went wrong while fetching the tags list."
+msgstr ""
+
+msgid "ContainerRegistry|Something went wrong while marking the tag for deletion."
+msgstr ""
+
+msgid "ContainerRegistry|Something went wrong while marking the tags for deletion."
+msgstr ""
+
+msgid "ContainerRegistry|Something went wrong while scheduling %{title} for deletion. Please try again."
+msgstr ""
+
+msgid "ContainerRegistry|Something went wrong while updating the expiration policy."
+msgstr ""
+
+msgid "ContainerRegistry|Sorry, your filter produced no results."
+msgstr ""
+
+msgid "ContainerRegistry|Tag expiration policy"
+msgstr ""
+
+msgid "ContainerRegistry|Tag expiration policy is designed to:"
+msgstr ""
+
+msgid "ContainerRegistry|Tag successfully marked for deletion."
+msgstr ""
+
+msgid "ContainerRegistry|Tags successfully marked for deletion."
+msgstr ""
+
+msgid "ContainerRegistry|Tags with names matching this regex pattern will %{italicStart}be preserved:%{italicEnd}"
+msgstr ""
+
+msgid "ContainerRegistry|Tags with names matching this regex pattern will %{italicStart}expire:%{italicEnd}"
+msgstr ""
+
+msgid "ContainerRegistry|The Container Registry tag expiration and retention policies for this project have not been enabled."
+msgstr ""
+
+msgid "ContainerRegistry|The last tag related to this image was recently removed. This empty image and any associated data will be automatically removed as part of the regular Garbage Collection process. If you have any questions, contact your administrator."
+msgstr ""
+
+msgid "ContainerRegistry|The value of this input should be less than 255 characters"
+msgstr ""
+
+msgid "ContainerRegistry|There are no container images available in this group"
+msgstr ""
+
+msgid "ContainerRegistry|There are no container images stored for this project"
+msgstr ""
+
+msgid "ContainerRegistry|There was an error during the deletion of this image repository, please try again."
+msgstr ""
+
+msgid "ContainerRegistry|This image has no active tags"
+msgstr ""
+
+msgid "ContainerRegistry|This image repository is scheduled for deletion"
+msgstr ""
+
+msgid "ContainerRegistry|To widen your search, change or remove the filters above."
+msgstr ""
+
+msgid "ContainerRegistry|We are having trouble connecting to the Registry, which could be due to an issue with your project name or path. %{docLinkStart}More information%{docLinkEnd}"
+msgstr ""
+
+msgid "ContainerRegistry|With the Container Registry, every project can have its own space to store its Docker images. %{docLinkStart}More Information%{docLinkEnd}"
+msgstr ""
+
+msgid "ContainerRegistry|With the Container Registry, every project can have its own space to store its Docker images. Push at least one Docker image in one of this group's projects in order to show up here. %{docLinkStart}More Information%{docLinkEnd}"
+msgstr ""
+
+msgid "ContainerRegistry|With the GitLab Container Registry, every project can have its own space to store images. %{docLinkStart}More information%{docLinkEnd}"
+msgstr ""
+
+msgid "ContainerRegistry|You are about to remove %{item} tags. Are you sure?"
+msgstr ""
+
+msgid "ContainerRegistry|You are about to remove %{item}. Are you sure?"
+msgstr ""
+
+msgid "ContainerRegistry|You are about to remove repository %{title}. Once you confirm, this repository will be permanently deleted."
+msgstr ""
+
+msgid "ContainerRegistry|You can add an image to this registry with the following commands:"
+msgstr ""
+
+msgid "Contains %{count} blobs of images (%{size})"
+msgstr ""
+
+msgid "Contents of .gitlab-ci.yml"
+msgstr ""
+
+msgid "Continue"
+msgstr ""
+
+msgid "Continue to the next step"
+msgstr ""
+
+msgid "Continuous Integration and Deployment"
+msgstr ""
+
+msgid "Contribute to GitLab"
+msgstr ""
+
+msgid "Contribution"
+msgstr ""
+
+msgid "Contribution Analytics"
+msgstr ""
+
+msgid "ContributionAnalytics|<strong>%{created_count}</strong> created, <strong>%{closed_count}</strong> closed."
+msgstr ""
+
+msgid "ContributionAnalytics|<strong>%{created_count}</strong> created, <strong>%{merged_count}</strong> merged."
+msgstr ""
+
+msgid "ContributionAnalytics|<strong>%{pushes}</strong> pushes, more than <strong>%{commits}</strong> commits by <strong>%{people}</strong> contributors."
+msgstr ""
+
+msgid "ContributionAnalytics|Contribution analytics for issues, merge requests and push events since %{start_date}"
+msgstr ""
+
+msgid "ContributionAnalytics|Issues"
+msgstr ""
+
+msgid "ContributionAnalytics|Last 3 months"
+msgstr ""
+
+msgid "ContributionAnalytics|Last month"
+msgstr ""
+
+msgid "ContributionAnalytics|Last week"
+msgstr ""
+
+msgid "ContributionAnalytics|Merge Requests"
+msgstr ""
+
+msgid "ContributionAnalytics|No issues for the selected time period."
+msgstr ""
+
+msgid "ContributionAnalytics|No merge requests for the selected time period."
+msgstr ""
+
+msgid "ContributionAnalytics|No pushes for the selected time period."
+msgstr ""
+
+msgid "Contributions for <strong>%{calendar_date}</strong>"
+msgstr ""
+
+msgid "Contributions per group member"
+msgstr ""
+
+msgid "Contributors"
+msgstr ""
+
+msgid "Control emails linked to your account"
+msgstr ""
+
+msgid "Control the display of third party offers."
+msgstr ""
+
+msgid "Cookie domain"
+msgstr ""
+
+msgid "Copied"
+msgstr ""
+
+msgid "Copied labels and milestone from %{source_issuable_reference}."
+msgstr ""
+
+msgid "Copy"
+msgstr ""
+
+msgid "Copy %{field}"
+msgstr ""
+
+msgid "Copy %{http_label} clone URL"
+msgstr ""
+
+msgid "Copy %{protocol} clone URL"
+msgstr ""
+
+msgid "Copy %{proxy_url}"
+msgstr ""
+
+msgid "Copy %{type}"
+msgstr ""
+
+msgid "Copy Account ID to clipboard"
+msgstr ""
+
+msgid "Copy External ID to clipboard"
+msgstr ""
+
+msgid "Copy ID"
+msgstr ""
+
+msgid "Copy KRB5 clone URL"
+msgstr ""
+
+msgid "Copy SSH clone URL"
+msgstr ""
+
+msgid "Copy SSH public key"
+msgstr ""
+
+msgid "Copy URL"
+msgstr ""
+
+msgid "Copy branch name"
+msgstr ""
+
+msgid "Copy command"
+msgstr ""
+
+msgid "Copy commands"
+msgstr ""
+
+msgid "Copy commit SHA"
+msgstr ""
+
+msgid "Copy environment"
+msgstr ""
+
+msgid "Copy evidence SHA"
+msgstr ""
+
+msgid "Copy file contents"
+msgstr ""
+
+msgid "Copy file path"
+msgstr ""
+
+msgid "Copy key"
+msgstr ""
+
+msgid "Copy labels and milestone from %{source_issuable_reference}."
+msgstr ""
+
+msgid "Copy labels and milestone from other issue or merge request in this project"
+msgstr ""
+
+msgid "Copy link"
+msgstr ""
+
+msgid "Copy link to chart"
+msgstr ""
+
+msgid "Copy reference"
+msgstr ""
+
+msgid "Copy secret"
+msgstr ""
+
+msgid "Copy token"
+msgstr ""
+
+msgid "Copy trigger token"
+msgstr ""
+
+msgid "Copy value"
+msgstr ""
+
+msgid "Could not add admins as members"
+msgstr ""
+
+msgid "Could not authorize chat nickname. Try again!"
+msgstr ""
+
+msgid "Could not change HEAD: branch '%{branch}' does not exist"
+msgstr ""
+
+msgid "Could not connect to FogBugz, check your URL"
+msgstr ""
+
+msgid "Could not connect to Sentry. Refresh the page to try again."
+msgstr ""
+
+msgid "Could not connect to Web IDE file mirror service."
+msgstr ""
+
+msgid "Could not create Wiki Repository at this time. Please try again later."
+msgstr ""
+
+msgid "Could not create environment"
+msgstr ""
+
+msgid "Could not create group"
+msgstr ""
+
+msgid "Could not create project"
+msgstr ""
+
+msgid "Could not delete %{design}. Please try again."
+msgstr ""
+
+msgid "Could not delete chat nickname %{chat_name}."
+msgstr ""
+
+msgid "Could not find design."
+msgstr ""
+
+msgid "Could not find iteration"
+msgstr ""
+
+msgid "Could not remove the trigger."
+msgstr ""
+
+msgid "Could not restore the group"
+msgstr ""
+
+msgid "Could not revoke impersonation token %{token_name}."
+msgstr ""
+
+msgid "Could not revoke personal access token %{personal_access_token_name}."
+msgstr ""
+
+msgid "Could not revoke project access token %{project_access_token_name}."
+msgstr ""
+
+msgid "Could not save group ID"
+msgstr ""
+
+msgid "Could not save project ID"
+msgstr ""
+
+msgid "Could not save prometheus manual configuration"
+msgstr ""
+
+msgid "Could not update the LDAP settings"
+msgstr ""
+
+msgid "Could not upload your designs as one or more files uploaded are not supported."
+msgstr ""
+
+msgid "Country"
+msgstr ""
+
+msgid "Coverage"
+msgstr ""
+
+msgid "Coverage Fuzzing"
+msgstr ""
+
+msgid "Crash State"
+msgstr ""
+
+msgid "Create"
+msgstr ""
+
+msgid "Create %{environment}"
+msgstr ""
+
+msgid "Create %{type}"
+msgstr ""
+
+msgid "Create New Directory"
+msgstr ""
+
+msgid "Create New Domain"
+msgstr ""
+
+msgid "Create Project"
+msgstr ""
+
+msgid "Create a GitLab account first, and then connect it to your %{label} account."
+msgstr ""
+
+msgid "Create a Mattermost team for this group"
+msgstr ""
+
+msgid "Create a local proxy for storing frequently used upstream images. %{link_start}Learn more%{link_end} about dependency proxies."
+msgstr ""
+
+msgid "Create a merge request"
+msgstr ""
+
+msgid "Create a new branch"
+msgstr ""
+
+msgid "Create a new deploy key for this project"
+msgstr ""
+
+msgid "Create a new file as there are no files yet. Afterwards, you'll be able to commit your changes."
+msgstr ""
+
+msgid "Create a new issue"
+msgstr ""
+
+msgid "Create a new repository"
+msgstr ""
+
+msgid "Create a personal access token on your account to pull or push via %{protocol}."
+msgstr ""
+
+msgid "Create a project pre-populated with the necessary files to get you started quickly."
+msgstr ""
+
+msgid "Create an account using:"
+msgstr ""
+
+msgid "Create an issue. Issues are created for each alert triggered."
+msgstr ""
+
+msgid "Create and provide your GitHub %{link_start}Personal Access Token%{link_end}. You will need to select the <code>repo</code> scope, so we can display a list of your public and private repositories which are available to import."
+msgstr ""
+
+msgid "Create board"
+msgstr ""
+
+msgid "Create branch"
+msgstr ""
+
+msgid "Create commit"
+msgstr ""
+
+msgid "Create confidential merge request"
+msgstr ""
+
+msgid "Create confidential merge request and branch"
+msgstr ""
+
+msgid "Create directory"
+msgstr ""
+
+msgid "Create empty repository"
+msgstr ""
+
+msgid "Create epic"
+msgstr ""
+
+msgid "Create file"
+msgstr ""
+
+msgid "Create group"
+msgstr ""
+
+msgid "Create group label"
+msgstr ""
+
+msgid "Create issue"
+msgstr ""
+
+msgid "Create iteration"
+msgstr ""
+
+msgid "Create lists from labels. Issues with that label appear in that list."
+msgstr ""
+
+msgid "Create merge request"
+msgstr ""
+
+msgid "Create merge request and branch"
+msgstr ""
+
+msgid "Create milestone"
+msgstr ""
+
+msgid "Create new"
+msgstr ""
+
+msgid "Create new board"
+msgstr ""
+
+msgid "Create new branch"
+msgstr ""
+
+msgid "Create new directory"
+msgstr ""
+
+msgid "Create new file"
+msgstr ""
+
+msgid "Create new file or directory"
+msgstr ""
+
+msgid "Create new label"
+msgstr ""
+
+msgid "Create new..."
+msgstr ""
+
+msgid "Create project"
+msgstr ""
+
+msgid "Create project label"
+msgstr ""
+
+msgid "Create requirement"
+msgstr ""
+
+msgid "Create snippet"
+msgstr ""
+
+msgid "Create wildcard: %{searchTerm}"
+msgstr ""
+
+msgid "Create your first page"
+msgstr ""
+
+msgid "Create your group"
+msgstr ""
+
+msgid "Create/import your first project"
+msgstr ""
+
+msgid "CreateGroup|You don’t have permission to create a subgroup in this group."
+msgstr ""
+
+msgid "CreateGroup|You don’t have permission to create groups."
+msgstr ""
+
+msgid "CreateTag|Tag"
+msgstr ""
+
+msgid "CreateTokenToCloneLink|create a personal access token"
+msgstr ""
+
+msgid "Created"
+msgstr ""
+
+msgid "Created %{timestamp}"
+msgstr ""
+
+msgid "Created At"
+msgstr ""
+
+msgid "Created On"
+msgstr ""
+
+msgid "Created a branch and a merge request to resolve this issue."
+msgstr ""
+
+msgid "Created branch '%{branch_name}' and a merge request to resolve this issue."
+msgstr ""
+
+msgid "Created by me"
+msgstr ""
+
+msgid "Created date"
+msgstr ""
+
+msgid "Created issue %{issueLink}"
+msgstr ""
+
+msgid "Created issue %{issueLink} at %{projectLink}"
+msgstr ""
+
+msgid "Created merge request %{mergeRequestLink}"
+msgstr ""
+
+msgid "Created merge request %{mergeRequestLink} at %{projectLink}"
+msgstr ""
+
+msgid "Created on"
+msgstr ""
+
+msgid "Created on:"
+msgstr ""
+
+msgid "Creates a branch and a merge request to resolve this issue."
+msgstr ""
+
+msgid "Creates branch '%{branch_name}' and a merge request to resolve this issue."
+msgstr ""
+
+msgid "Creating"
+msgstr ""
+
+msgid "Creating epic"
+msgstr ""
+
+msgid "Creating graphs uses the data from the Prometheus server. If this takes a long time, ensure that data is available."
+msgstr ""
+
+msgid "Creation date"
+msgstr ""
+
+msgid "Credentials"
+msgstr ""
+
+msgid "CredentialsInventory|No credentials found"
+msgstr ""
+
+msgid "CredentialsInventory|Personal Access Tokens"
+msgstr ""
+
+msgid "CredentialsInventory|SSH Keys"
+msgstr ""
+
+msgid "Critical vulnerabilities present"
+msgstr ""
+
+msgid "Cron Timezone"
+msgstr ""
+
+msgid "Crossplane"
+msgstr ""
+
+msgid "Current Branch"
+msgstr ""
+
+msgid "Current Plan"
+msgstr ""
+
+msgid "Current Project"
+msgstr ""
+
+msgid "Current node"
+msgstr ""
+
+msgid "Current node must be the primary node or you will be locking yourself out"
+msgstr ""
+
+msgid "Current password"
+msgstr ""
+
+msgid "Current vulnerabilities count"
+msgstr ""
+
+msgid "CurrentUser|Buy Pipeline minutes"
+msgstr ""
+
+msgid "CurrentUser|One of your groups is running out"
+msgstr ""
+
+msgid "CurrentUser|Profile"
+msgstr ""
+
+msgid "CurrentUser|Settings"
+msgstr ""
+
+msgid "CurrentUser|Start a Gold trial"
+msgstr ""
+
+msgid "CurrentUser|Upgrade"
+msgstr ""
+
+msgid "Custom Attributes"
+msgstr ""
+
+msgid "Custom CI configuration path"
+msgstr ""
+
+msgid "Custom Git clone URL for HTTP(S)"
+msgstr ""
+
+msgid "Custom hostname (for private commit emails)"
+msgstr ""
+
+msgid "Custom notification events"
+msgstr ""
+
+msgid "Custom notification levels are the same as participating levels. With custom notification levels you will also receive notifications for select events. To find out more, check out %{notification_link}."
+msgstr ""
+
+msgid "Custom project templates"
+msgstr ""
+
+msgid "Custom project templates have not been set up for groups that you are a member of. They are enabled from a group’s settings page. Contact your group’s Owner or Maintainer to setup custom project templates."
+msgstr ""
+
+msgid "Custom range"
+msgstr ""
+
+msgid "Custom range (UTC)"
+msgstr ""
+
+msgid "CustomCycleAnalytics|Add a stage"
+msgstr ""
+
+msgid "CustomCycleAnalytics|Add stage"
+msgstr ""
+
+msgid "CustomCycleAnalytics|Editing stage"
+msgstr ""
+
+msgid "CustomCycleAnalytics|Enter a name for the stage"
+msgstr ""
+
+msgid "CustomCycleAnalytics|Name"
+msgstr ""
+
+msgid "CustomCycleAnalytics|New stage"
+msgstr ""
+
+msgid "CustomCycleAnalytics|Please select a start event first"
+msgstr ""
+
+msgid "CustomCycleAnalytics|Select start event"
+msgstr ""
+
+msgid "CustomCycleAnalytics|Select stop event"
+msgstr ""
+
+msgid "CustomCycleAnalytics|Stage name already exists"
+msgstr ""
+
+msgid "CustomCycleAnalytics|Start event"
+msgstr ""
+
+msgid "CustomCycleAnalytics|Start event changed, please select a valid stop event"
+msgstr ""
+
+msgid "CustomCycleAnalytics|Start event label"
+msgstr ""
+
+msgid "CustomCycleAnalytics|Stop event"
+msgstr ""
+
+msgid "CustomCycleAnalytics|Stop event label"
+msgstr ""
+
+msgid "CustomCycleAnalytics|Update stage"
+msgstr ""
+
+msgid "Customer Portal"
+msgstr ""
+
+msgid "Customize colors"
+msgstr ""
+
+msgid "Customize how FogBugz email addresses and usernames are imported into GitLab. In the next step, you'll be able to select the projects you want to import."
+msgstr ""
+
+msgid "Customize how Google Code email addresses and usernames are imported into GitLab. In the next step, you'll be able to select the projects you want to import."
+msgstr ""
+
+msgid "Customize icon"
+msgstr ""
+
+msgid "Customize language and region related settings."
+msgstr ""
+
+msgid "Customize name"
+msgstr ""
+
+msgid "Customize your pipeline configuration, view your pipeline status and coverage report."
+msgstr ""
+
+msgid "Customize your pipeline configuration."
+msgstr ""
+
+msgid "Cycle Time"
+msgstr ""
+
+msgid "CycleAnalyticsEvent|Issue closed"
+msgstr ""
+
+msgid "CycleAnalyticsEvent|Issue created"
+msgstr ""
+
+msgid "CycleAnalyticsEvent|Issue first added to a board"
+msgstr ""
+
+msgid "CycleAnalyticsEvent|Issue first associated with a milestone"
+msgstr ""
+
+msgid "CycleAnalyticsEvent|Issue first associated with a milestone or issue first added to a board"
+msgstr ""
+
+msgid "CycleAnalyticsEvent|Issue first mentioned in a commit"
+msgstr ""
+
+msgid "CycleAnalyticsEvent|Issue label was added"
+msgstr ""
+
+msgid "CycleAnalyticsEvent|Issue label was removed"
+msgstr ""
+
+msgid "CycleAnalyticsEvent|Issue last edited"
+msgstr ""
+
+msgid "CycleAnalyticsEvent|Merge request closed"
+msgstr ""
+
+msgid "CycleAnalyticsEvent|Merge request created"
+msgstr ""
+
+msgid "CycleAnalyticsEvent|Merge request first deployed to production"
+msgstr ""
+
+msgid "CycleAnalyticsEvent|Merge request label was added"
+msgstr ""
+
+msgid "CycleAnalyticsEvent|Merge request label was removed"
+msgstr ""
+
+msgid "CycleAnalyticsEvent|Merge request last build finish time"
+msgstr ""
+
+msgid "CycleAnalyticsEvent|Merge request last build start time"
+msgstr ""
+
+msgid "CycleAnalyticsEvent|Merge request last edited"
+msgstr ""
+
+msgid "CycleAnalyticsEvent|Merge request merged"
+msgstr ""
+
+msgid "CycleAnalyticsStage|Code"
+msgstr ""
+
+msgid "CycleAnalyticsStage|Issue"
+msgstr ""
+
+msgid "CycleAnalyticsStage|Plan"
+msgstr ""
+
+msgid "CycleAnalyticsStage|Review"
+msgstr ""
+
+msgid "CycleAnalyticsStage|Staging"
+msgstr ""
+
+msgid "CycleAnalyticsStage|Test"
+msgstr ""
+
+msgid "CycleAnalyticsStage|Total"
+msgstr ""
+
+msgid "CycleAnalyticsStage|is not available for the selected group"
+msgstr ""
+
+msgid "CycleAnalyticsStage|should be under a group"
+msgstr ""
+
+msgid "CycleAnalytics|%{selectedLabelsCount} selected (%{maxLabels} max)"
+msgstr ""
+
+msgid "CycleAnalytics|%{stageCount} stages selected"
+msgstr ""
+
+msgid "CycleAnalytics|All stages"
+msgstr ""
+
+msgid "CycleAnalytics|Date"
+msgstr ""
+
+msgid "CycleAnalytics|Days to completion"
+msgstr ""
+
+msgid "CycleAnalytics|Display chart filters"
+msgstr ""
+
+msgid "CycleAnalytics|No stages selected"
+msgstr ""
+
+msgid "CycleAnalytics|Number of tasks"
+msgstr ""
+
+msgid "CycleAnalytics|Only %{maxLabels} labels can be selected at this time"
+msgstr ""
+
+msgid "CycleAnalytics|Project selected"
+msgid_plural "CycleAnalytics|%d projects selected"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "CycleAnalytics|Select labels"
+msgstr ""
+
+msgid "CycleAnalytics|Show"
+msgstr ""
+
+msgid "CycleAnalytics|Showing %{subjectFilterText} and %{selectedLabelsCount} labels"
+msgstr ""
+
+msgid "CycleAnalytics|Showing data for group '%{groupName}' and %{selectedProjectCount} projects from %{startDate} to %{endDate}"
+msgstr ""
+
+msgid "CycleAnalytics|Showing data for group '%{groupName}' from %{startDate} to %{endDate}"
+msgstr ""
+
+msgid "CycleAnalytics|Stages"
+msgstr ""
+
+msgid "CycleAnalytics|Tasks by type"
+msgstr ""
+
+msgid "CycleAnalytics|The given date range is larger than 180 days"
+msgstr ""
+
+msgid "CycleAnalytics|Total days to completion"
+msgstr ""
+
+msgid "CycleAnalytics|Type of work"
+msgstr ""
+
+msgid "CycleAnalytics|group dropdown filter"
+msgstr ""
+
+msgid "CycleAnalytics|not allowed for the given start event"
+msgstr ""
+
+msgid "CycleAnalytics|project dropdown filter"
+msgstr ""
+
+msgid "CycleAnalytics|stage dropdown"
+msgstr ""
+
+msgid "DAG"
+msgstr ""
+
+msgid "DAG visualization requires at least 3 dependent jobs."
+msgstr ""
+
+msgid "DNS"
+msgstr ""
+
+msgid "Dashboard"
+msgstr ""
+
+msgid "Dashboard uid not found"
+msgstr ""
+
+msgid "DashboardProjects|All"
+msgstr ""
+
+msgid "DashboardProjects|Personal"
+msgstr ""
+
+msgid "DashboardProjects|Trending"
+msgstr ""
+
+msgid "Dashboards"
+msgstr ""
+
+msgid "Dashboard|%{firstProject} and %{secondProject}"
+msgstr ""
+
+msgid "Dashboard|%{firstProject}, %{rest}, and %{secondProject}"
+msgstr ""
+
+msgid "Dashboard|Unable to add %{invalidProjects}. This dashboard is available for public projects, and private projects in groups with a Silver plan."
+msgstr ""
+
+msgid "Data is still calculating..."
+msgstr ""
+
+msgid "Datasource name not found"
+msgstr ""
+
+msgid "Date"
+msgstr ""
+
+msgid "Date picker"
+msgstr ""
+
+msgid "Date range cannot exceed %{maxDateRange} days."
+msgstr ""
+
+msgid "Day of month"
+msgstr ""
+
+msgid "DayTitle|F"
+msgstr ""
+
+msgid "DayTitle|M"
+msgstr ""
+
+msgid "DayTitle|S"
+msgstr ""
+
+msgid "DayTitle|W"
+msgstr ""
+
+msgid "Days"
+msgstr ""
+
+msgid "Days to merge"
+msgstr ""
+
+msgid "Debug"
+msgstr ""
+
+msgid "Dec"
+msgstr ""
+
+msgid "December"
+msgstr ""
+
+msgid "Decline"
+msgstr ""
+
+msgid "Decline and sign out"
+msgstr ""
+
+msgid "Default Branch"
+msgstr ""
+
+msgid "Default CI configuration path"
+msgstr ""
+
+msgid "Default artifacts expiration"
+msgstr ""
+
+msgid "Default branch"
+msgstr ""
+
+msgid "Default branch and protected branches"
+msgstr ""
+
+msgid "Default classification label"
+msgstr ""
+
+msgid "Default dashboard"
+msgstr ""
+
+msgid "Default deletion adjourned period"
+msgstr ""
+
+msgid "Default description template for issues"
+msgstr ""
+
+msgid "Default description template for merge requests"
+msgstr ""
+
+msgid "Default first day of the week"
+msgstr ""
+
+msgid "Default first day of the week in calendars and date pickers."
+msgstr ""
+
+msgid "Default issue template"
+msgstr ""
+
+msgid "Default project deletion protection"
+msgstr ""
+
+msgid "Default projects limit"
+msgstr ""
+
+msgid "Default stages"
+msgstr ""
+
+msgid "Default: Directly import the Google Code email address or username"
+msgstr ""
+
+msgid "Default: Map a FogBugz account ID to a full name"
+msgstr ""
+
+msgid "Define a custom pattern with cron syntax"
+msgstr ""
+
+msgid "Define custom rules for what constitutes spam, independent of Akismet"
+msgstr ""
+
+msgid "Define environments in the deploy stage(s) in <code>.gitlab-ci.yml</code> to track deployments here."
+msgstr ""
+
+msgid "DelayedJobs|Are you sure you want to run %{jobName} immediately? Otherwise this job will run automatically after it's timer finishes."
+msgstr ""
+
+msgid "DelayedJobs|Are you sure you want to run %{job_name} immediately? This job will run automatically after it's timer finishes."
+msgstr ""
+
+msgid "DelayedJobs|Start now"
+msgstr ""
+
+msgid "DelayedJobs|Unschedule"
+msgstr ""
+
+msgid "DelayedJobs|delayed"
+msgstr ""
+
+msgid "Delete"
+msgstr ""
+
+msgid "Delete Comment"
+msgstr ""
+
+msgid "Delete Package"
+msgstr ""
+
+msgid "Delete Snippet"
+msgstr ""
+
+msgid "Delete artifacts"
+msgstr ""
+
+msgid "Delete board"
+msgstr ""
+
+msgid "Delete comment"
+msgstr ""
+
+msgid "Delete domain"
+msgstr ""
+
+msgid "Delete label"
+msgstr ""
+
+msgid "Delete label: %{label_name} ?"
+msgstr ""
+
+msgid "Delete license"
+msgstr ""
+
+msgid "Delete list"
+msgstr ""
+
+msgid "Delete pipeline"
+msgstr ""
+
+msgid "Delete project"
+msgstr ""
+
+msgid "Delete serverless domain?"
+msgstr ""
+
+msgid "Delete snippet"
+msgstr ""
+
+msgid "Delete snippet?"
+msgstr ""
+
+msgid "Delete source branch"
+msgstr ""
+
+msgid "Delete this attachment"
+msgstr ""
+
+msgid "Delete user list"
+msgstr ""
+
+msgid "Delete variable"
+msgstr ""
+
+msgid "DeleteProject|Failed to remove project repository. Please try again or contact administrator."
+msgstr ""
+
+msgid "DeleteProject|Failed to remove project snippets. Please try again or contact administrator."
+msgstr ""
+
+msgid "DeleteProject|Failed to remove some tags in project container registry. Please try again or contact administrator."
+msgstr ""
+
+msgid "DeleteProject|Failed to remove wiki repository. Please try again or contact administrator."
+msgstr ""
+
+msgid "DeleteProject|Failed to restore project repository. Please contact the administrator."
+msgstr ""
+
+msgid "DeleteProject|Failed to restore wiki repository. Please contact the administrator."
+msgstr ""
+
+msgid "Deleted"
+msgstr ""
+
+msgid "Deleted chat nickname: %{chat_name}!"
+msgstr ""
+
+msgid "Deleted in this version"
+msgstr ""
+
+msgid "Deleting"
+msgstr ""
+
+msgid "Deleting the license failed."
+msgstr ""
+
+msgid "Deleting the license failed. The license was not found."
+msgstr ""
+
+msgid "Deleting the license failed. You are not permitted to perform this action."
+msgstr ""
+
+msgid "Deletion pending. This project will be removed on %{date}. Repository and other project resources are read-only."
+msgstr ""
+
+msgid "Denied authorization of chat nickname %{user_name}."
+msgstr ""
+
+msgid "Deny"
+msgstr ""
+
+msgid "Dependencies"
+msgstr ""
+
+msgid "Dependencies help page link"
+msgstr ""
+
+msgid "Dependencies|%d additional vulnerability not shown"
+msgid_plural "Dependencies|%d additional vulnerabilities not shown"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Dependencies|%d vulnerability detected"
+msgid_plural "Dependencies|%d vulnerabilities detected"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Dependencies|%{remainingLicensesCount} more"
+msgstr ""
+
+msgid "Dependencies|All"
+msgstr ""
+
+msgid "Dependencies|Based on the %{linkStart}latest successful%{linkEnd} scan"
+msgstr ""
+
+msgid "Dependencies|Component"
+msgstr ""
+
+msgid "Dependencies|Component name"
+msgstr ""
+
+msgid "Dependencies|Export as JSON"
+msgstr ""
+
+msgid "Dependencies|Job failed to generate the dependency list"
+msgstr ""
+
+msgid "Dependencies|License"
+msgstr ""
+
+msgid "Dependencies|Location"
+msgstr ""
+
+msgid "Dependencies|Packager"
+msgstr ""
+
+msgid "Dependencies|The %{codeStartTag}dependency_scanning%{codeEndTag} job has failed and cannot generate the list. Please ensure the job is running properly and run the pipeline again."
+msgstr ""
+
+msgid "Dependencies|Toggle vulnerability list"
+msgstr ""
+
+msgid "Dependencies|Unsupported file(s) detected"
+msgstr ""
+
+msgid "Dependencies|Vulnerable components"
+msgstr ""
+
+msgid "Dependency List"
+msgstr ""
+
+msgid "Dependency List has no entries"
+msgstr ""
+
+msgid "Dependency Proxy"
+msgstr ""
+
+msgid "Dependency Scanning"
+msgstr ""
+
+msgid "Dependency proxy"
+msgstr ""
+
+msgid "Dependency proxy URL"
+msgstr ""
+
+msgid "Dependency proxy feature is limited to public groups for now."
+msgstr ""
+
+msgid "DependencyProxy|Toggle Dependency Proxy"
+msgstr ""
+
+msgid "Depends on %d merge request being merged"
+msgid_plural "Depends on %d merge requests being merged"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Depends on <strong>%d closed</strong> merge request."
+msgid_plural "Depends on <strong>%d closed</strong> merge requests."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Deploy"
+msgid_plural "Deploys"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Deploy Keys"
+msgstr ""
+
+msgid "Deploy key was successfully updated."
+msgstr ""
+
+msgid "Deploy keys allow read-only or read-write (if enabled) access to your repository. Deploy keys can be used for CI, staging or production servers. You can create a deploy key or add an existing one."
+msgstr ""
+
+msgid "Deploy progress not found. To see pods, ensure your environment matches %{linkStart}deploy board criteria%{linkEnd}."
+msgstr ""
+
+msgid "Deploy to..."
+msgstr ""
+
+msgid "DeployBoard|Matching on the %{appLabel} label has been removed for deploy boards. To see all instances on your board, you must update your chart and redeploy."
+msgstr ""
+
+msgid "DeployKeys|+%{count} others"
+msgstr ""
+
+msgid "DeployKeys|Current project"
+msgstr ""
+
+msgid "DeployKeys|Deploy key"
+msgstr ""
+
+msgid "DeployKeys|Enabled deploy keys"
+msgstr ""
+
+msgid "DeployKeys|Error enabling deploy key"
+msgstr ""
+
+msgid "DeployKeys|Error getting deploy keys"
+msgstr ""
+
+msgid "DeployKeys|Error removing deploy key"
+msgstr ""
+
+msgid "DeployKeys|Expand %{count} other projects"
+msgstr ""
+
+msgid "DeployKeys|Loading deploy keys"
+msgstr ""
+
+msgid "DeployKeys|No deploy keys found. Create one with the form above."
+msgstr ""
+
+msgid "DeployKeys|Privately accessible deploy keys"
+msgstr ""
+
+msgid "DeployKeys|Project usage"
+msgstr ""
+
+msgid "DeployKeys|Publicly accessible deploy keys"
+msgstr ""
+
+msgid "DeployKeys|Read access only"
+msgstr ""
+
+msgid "DeployKeys|Write access allowed"
+msgstr ""
+
+msgid "DeployKeys|You are going to remove this deploy key. Are you sure?"
+msgstr ""
+
+msgid "DeployTokens|Active Deploy Tokens (%{active_tokens})"
+msgstr ""
+
+msgid "DeployTokens|Add a deploy token"
+msgstr ""
+
+msgid "DeployTokens|Allows read access to the package registry"
+msgstr ""
+
+msgid "DeployTokens|Allows read-only access to the registry images"
+msgstr ""
+
+msgid "DeployTokens|Allows read-only access to the repository"
+msgstr ""
+
+msgid "DeployTokens|Allows write access to the package registry"
+msgstr ""
+
+msgid "DeployTokens|Allows write access to the registry images"
+msgstr ""
+
+msgid "DeployTokens|Copy deploy token"
+msgstr ""
+
+msgid "DeployTokens|Copy username"
+msgstr ""
+
+msgid "DeployTokens|Create deploy token"
+msgstr ""
+
+msgid "DeployTokens|Created"
+msgstr ""
+
+msgid "DeployTokens|Default format is \"gitlab+deploy-token-{n}\". Enter custom username if you want to change it."
+msgstr ""
+
+msgid "DeployTokens|Deploy Tokens"
+msgstr ""
+
+msgid "DeployTokens|Deploy tokens allow access to packages, your repository, and registry images."
+msgstr ""
+
+msgid "DeployTokens|Expires"
+msgstr ""
+
+msgid "DeployTokens|Group deploy tokens allow access to the packages, repositories, and registry images within the group."
+msgstr ""
+
+msgid "DeployTokens|Name"
+msgstr ""
+
+msgid "DeployTokens|Pick a name for the application, and we'll give you a unique deploy token."
+msgstr ""
+
+msgid "DeployTokens|Revoke"
+msgstr ""
+
+msgid "DeployTokens|Revoke %{b_start}%{name}%{b_end}?"
+msgstr ""
+
+msgid "DeployTokens|Revoke %{name}"
+msgstr ""
+
+msgid "DeployTokens|Scopes"
+msgstr ""
+
+msgid "DeployTokens|This %{entity_type} has no active Deploy Tokens."
+msgstr ""
+
+msgid "DeployTokens|This action cannot be undone."
+msgstr ""
+
+msgid "DeployTokens|Use this token as a password. Make sure you save it - you won't be able to access it again."
+msgstr ""
+
+msgid "DeployTokens|Use this username as a login."
+msgstr ""
+
+msgid "DeployTokens|Username"
+msgstr ""
+
+msgid "DeployTokens|You are about to revoke %{b_start}%{name}%{b_end}."
+msgstr ""
+
+msgid "DeployTokens|Your New Deploy Token"
+msgstr ""
+
+msgid "DeployTokens|Your new group deploy token has been created."
+msgstr ""
+
+msgid "DeployTokens|Your new project deploy token has been created."
+msgstr ""
+
+msgid "Deployed"
+msgstr ""
+
+msgid "Deployed to"
+msgstr ""
+
+msgid "Deploying to"
+msgstr ""
+
+msgid "Deploying to AWS is easy with GitLab"
+msgstr ""
+
+msgid "Deployment Frequency"
+msgstr ""
+
+msgid "Deployment|API"
+msgstr ""
+
+msgid "Deployment|This deployment was created using the API"
+msgstr ""
+
+msgid "Deployment|canceled"
+msgstr ""
+
+msgid "Deployment|created"
+msgstr ""
+
+msgid "Deployment|failed"
+msgstr ""
+
+msgid "Deployment|running"
+msgstr ""
+
+msgid "Deployment|success"
+msgstr ""
+
+msgid "Deprioritize label"
+msgstr ""
+
+msgid "Descending"
+msgstr ""
+
+msgid "Describe the goal of the changes and what reviewers should be aware of."
+msgstr ""
+
+msgid "Describe the requirement here"
+msgstr ""
+
+msgid "Description"
+msgstr ""
+
+msgid "Description parsed with %{link_start}GitLab Flavored Markdown%{link_end}"
+msgstr ""
+
+msgid "Description templates allow you to define context-specific templates for issue and merge request description fields for your project."
+msgstr ""
+
+msgid "Description:"
+msgstr ""
+
+msgid "Descriptive label"
+msgstr ""
+
+msgid "Deselect all"
+msgstr ""
+
+msgid "Design Management files and data"
+msgstr ""
+
+msgid "DesignManagement|%{current_design} of %{designs_count}"
+msgstr ""
+
+msgid "DesignManagement|%{filename} did not change."
+msgstr ""
+
+msgid "DesignManagement|Adding a design with the same filename replaces the file in a new version."
+msgstr ""
+
+msgid "DesignManagement|Are you sure you want to cancel changes to this comment?"
+msgstr ""
+
+msgid "DesignManagement|Are you sure you want to cancel creating this comment?"
+msgstr ""
+
+msgid "DesignManagement|Are you sure you want to delete the selected designs?"
+msgstr ""
+
+msgid "DesignManagement|Cancel changes"
+msgstr ""
+
+msgid "DesignManagement|Cancel comment confirmation"
+msgstr ""
+
+msgid "DesignManagement|Cancel comment update confirmation"
+msgstr ""
+
+msgid "DesignManagement|Click the image where you'd like to start a new discussion"
+msgstr ""
+
+msgid "DesignManagement|Comment"
+msgstr ""
+
+msgid "DesignManagement|Comments you resolve can be viewed and unresolved by going to the \"Resolved Comments\" section below"
+msgstr ""
+
+msgid "DesignManagement|Could not add a new comment. Please try again."
+msgstr ""
+
+msgid "DesignManagement|Could not create new discussion. Please try again."
+msgstr ""
+
+msgid "DesignManagement|Could not update discussion. Please try again."
+msgstr ""
+
+msgid "DesignManagement|Could not update note. Please try again."
+msgstr ""
+
+msgid "DesignManagement|Delete"
+msgstr ""
+
+msgid "DesignManagement|Delete designs confirmation"
+msgstr ""
+
+msgid "DesignManagement|Delete selected"
+msgstr ""
+
+msgid "DesignManagement|Deselect all"
+msgstr ""
+
+msgid "DesignManagement|Discard comment"
+msgstr ""
+
+msgid "DesignManagement|Error uploading a new design. Please try again."
+msgstr ""
+
+msgid "DesignManagement|Go back to designs"
+msgstr ""
+
+msgid "DesignManagement|Go to next design"
+msgstr ""
+
+msgid "DesignManagement|Go to previous design"
+msgstr ""
+
+msgid "DesignManagement|Keep changes"
+msgstr ""
+
+msgid "DesignManagement|Keep comment"
+msgstr ""
+
+msgid "DesignManagement|Learn more about resolving comments"
+msgstr ""
+
+msgid "DesignManagement|Requested design version does not exist. Showing latest version instead"
+msgstr ""
+
+msgid "DesignManagement|Resolve thread"
+msgstr ""
+
+msgid "DesignManagement|Resolved Comments"
+msgstr ""
+
+msgid "DesignManagement|Save comment"
+msgstr ""
+
+msgid "DesignManagement|Select all"
+msgstr ""
+
+msgid "DesignManagement|The maximum number of designs allowed to be uploaded is %{upload_limit}. Please try again."
+msgstr ""
+
+msgid "DesignManagement|To enable design management, you'll need to %{requirements_link_start}meet the requirements%{requirements_link_end}. If you need help, reach out to our %{support_link_start}support team%{support_link_end} for assistance."
+msgstr ""
+
+msgid "DesignManagement|Unresolve thread"
+msgstr ""
+
+msgid "DesignManagement|Upload designs"
+msgstr ""
+
+msgid "DesignManagement|Upload skipped."
+msgstr ""
+
+msgid "DesignManagement|and %{moreCount} more."
+msgstr ""
+
+msgid "Designs"
+msgstr ""
+
+msgid "Destroy"
+msgstr ""
+
+msgid "Detail"
+msgstr ""
+
+msgid "Details"
+msgstr ""
+
+msgid "Details (default)"
+msgstr ""
+
+msgid "Detect host keys"
+msgstr ""
+
+msgid "DevOps Score"
+msgstr ""
+
+msgid "Diff content limits"
+msgstr ""
+
+msgid "Diff limits"
+msgstr ""
+
+msgid "Difference between start date and now"
+msgstr ""
+
+msgid "DiffsCompareBaseBranch|(HEAD)"
+msgstr ""
+
+msgid "DiffsCompareBaseBranch|(base)"
+msgstr ""
+
+msgid "Diffs|No file name available"
+msgstr ""
+
+msgid "Diffs|Show unchanged lines"
+msgstr ""
+
+msgid "Diffs|Something went wrong while fetching diff lines."
+msgstr ""
+
+msgid "Direction"
+msgstr ""
+
+msgid "Directory name"
+msgstr ""
+
+msgid "Disable"
+msgstr ""
+
+msgid "Disable for this project"
+msgstr ""
+
+msgid "Disable group Runners"
+msgstr ""
+
+msgid "Disable public access to Pages sites"
+msgstr ""
+
+msgid "Disable shared Runners"
+msgstr ""
+
+msgid "Disable two-factor authentication"
+msgstr ""
+
+msgid "Disabled"
+msgstr ""
+
+msgid "Disabled mirrors can only be enabled by instance owners. It is recommended that you delete them."
+msgstr ""
+
+msgid "Discard all changes"
+msgstr ""
+
+msgid "Discard all changes?"
+msgstr ""
+
+msgid "Discard changes"
+msgstr ""
+
+msgid "Discard changes to %{path}?"
+msgstr ""
+
+msgid "Discard draft"
+msgstr ""
+
+msgid "Discard review"
+msgstr ""
+
+msgid "DiscordService|Discord Notifications"
+msgstr ""
+
+msgid "DiscordService|Receive event notifications in Discord"
+msgstr ""
+
+msgid "Discover GitLab Geo"
+msgstr ""
+
+msgid "Discover projects, groups and snippets. Share your projects with others"
+msgstr ""
+
+msgid "Discover|Check your application for security vulnerabilities that may lead to unauthorized access, data leaks, and denial of services."
+msgstr ""
+
+msgid "Discover|For code that's already live in production, our dashboards give you an easy way to prioritize any issues that are found, empowering your team to ship quickly and securely."
+msgstr ""
+
+msgid "Discover|GitLab will perform static and dynamic tests on the code of your application, looking for known flaws and report them in the merge request so you can fix them before merging."
+msgstr ""
+
+msgid "Discover|Give feedback for this page"
+msgstr ""
+
+msgid "Discover|Security capabilities, integrated into your development lifecycle"
+msgstr ""
+
+msgid "Discover|See the other features of the %{linkStart}gold plan%{linkEnd}"
+msgstr ""
+
+msgid "Discover|Start a free trial"
+msgstr ""
+
+msgid "Discover|Upgrade now"
+msgstr ""
+
+msgid "Discuss a specific suggestion or question"
+msgstr ""
+
+msgid "Discuss a specific suggestion or question that needs to be resolved"
+msgstr ""
+
+msgid "Discuss a specific suggestion or question that needs to be resolved."
+msgstr ""
+
+msgid "Discuss a specific suggestion or question."
+msgstr ""
+
+msgid "Discussion"
+msgstr ""
+
+msgid "Discussion to reply to cannot be found"
+msgstr ""
+
+msgid "Disk Usage"
+msgstr ""
+
+msgid "Dismiss"
+msgstr ""
+
+msgid "Dismiss %d selected vulnerability as"
+msgid_plural "Dismiss %d selected vulnerabilities as"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Dismiss DevOps Score introduction"
+msgstr ""
+
+msgid "Dismiss Merge Request promotion"
+msgstr ""
+
+msgid "Dismiss Selected"
+msgstr ""
+
+msgid "Dismiss Value Stream Analytics introduction box"
+msgstr ""
+
+msgid "Dismiss trial promotion"
+msgstr ""
+
+msgid "Dismissable"
+msgstr ""
+
+msgid "Dismissed"
+msgstr ""
+
+msgid "Dismissed at %{projectLink}"
+msgstr ""
+
+msgid "Dismissed on pipeline %{pipelineLink}"
+msgstr ""
+
+msgid "Dismissed on pipeline %{pipelineLink} at %{projectLink}"
+msgstr ""
+
+msgid "Display alerts from all your monitoring tools directly within GitLab."
+msgstr ""
+
+msgid "Display name"
+msgstr ""
+
+msgid "Display rendered file"
+msgstr ""
+
+msgid "Display source"
+msgstr ""
+
+msgid "Do not display offers from third parties within GitLab"
+msgstr ""
+
+msgid "Do you want to customize how Google Code email addresses and usernames are imported into GitLab?"
+msgstr ""
+
+msgid "Dockerfile"
+msgstr ""
+
+msgid "Documentation"
+msgstr ""
+
+msgid "Documentation for popular identity providers"
+msgstr ""
+
+msgid "Doing"
+msgstr ""
+
+msgid "Domain"
+msgstr ""
+
+msgid "Domain cannot be deleted while associated to one or more clusters."
+msgstr ""
+
+msgid "Domain verification is an essential security measure for public GitLab sites. Users are required to demonstrate they control a domain before it is enabled"
+msgstr ""
+
+msgid "Domain was successfully created."
+msgstr ""
+
+msgid "Domain was successfully deleted."
+msgstr ""
+
+msgid "Domain was successfully updated."
+msgstr ""
+
+msgid "Don't have an account yet?"
+msgstr ""
+
+msgid "Don't include description in commit message"
+msgstr ""
+
+msgid "Don't paste the private part of the GPG key. Paste the public part which begins with '-----BEGIN PGP PUBLIC KEY BLOCK-----'."
+msgstr ""
+
+msgid "Don't show again"
+msgstr ""
+
+msgid "Done"
+msgstr ""
+
+msgid "Download"
+msgstr ""
+
+msgid "Download %{format}"
+msgstr ""
+
+msgid "Download %{format}:"
+msgstr ""
+
+msgid "Download CSV"
+msgstr ""
+
+msgid "Download artifacts"
+msgstr ""
+
+msgid "Download as"
+msgstr ""
+
+msgid "Download asset"
+msgstr ""
+
+msgid "Download codes"
+msgstr ""
+
+msgid "Download evidence JSON"
+msgstr ""
+
+msgid "Download export"
+msgstr ""
+
+msgid "Download image"
+msgstr ""
+
+msgid "Download license"
+msgstr ""
+
+msgid "Download raw data (.csv)"
+msgstr ""
+
+msgid "Download source code"
+msgstr ""
+
+msgid "Download this directory"
+msgstr ""
+
+msgid "DownloadCommit|Email Patches"
+msgstr ""
+
+msgid "DownloadCommit|Plain Diff"
+msgstr ""
+
+msgid "DownloadSource|Download"
+msgstr ""
+
+msgid "Downstream"
+msgstr ""
+
+msgid "Downvotes"
+msgstr ""
+
+msgid "Drop your designs to start your upload."
+msgstr ""
+
+msgid "Due Date"
+msgstr ""
+
+msgid "Due date"
+msgstr ""
+
+msgid "Duration"
+msgstr ""
+
+msgid "Duration for the last 30 commits"
+msgstr ""
+
+msgid "During this process, you’ll be asked for URLs from GitLab’s side. Use the URLs shown below."
+msgstr ""
+
+msgid "Dynamic Application Security Testing (DAST)"
+msgstr ""
+
+msgid "Each Runner can be in one of the following states and/or belong to one of the following types:"
+msgstr ""
+
+msgid "Each Runner can be in one of the following states:"
+msgstr ""
+
+msgid "Edit"
+msgstr ""
+
+msgid "Edit %{issuable}"
+msgstr ""
+
+msgid "Edit %{name}"
+msgstr ""
+
+msgid "Edit Comment"
+msgstr ""
+
+msgid "Edit Deploy Key"
+msgstr ""
+
+msgid "Edit Geo Node"
+msgstr ""
+
+msgid "Edit Group Hook"
+msgstr ""
+
+msgid "Edit Label"
+msgstr ""
+
+msgid "Edit Milestone"
+msgstr ""
+
+msgid "Edit Password"
+msgstr ""
+
+msgid "Edit Pipeline Schedule %{id}"
+msgstr ""
+
+msgid "Edit Release"
+msgstr ""
+
+msgid "Edit Slack integration"
+msgstr ""
+
+msgid "Edit Snippet"
+msgstr ""
+
+msgid "Edit System Hook"
+msgstr ""
+
+msgid "Edit application"
+msgstr ""
+
+msgid "Edit board"
+msgstr ""
+
+msgid "Edit comment"
+msgstr ""
+
+msgid "Edit dashboard"
+msgstr ""
+
+msgid "Edit description"
+msgstr ""
+
+msgid "Edit environment"
+msgstr ""
+
+msgid "Edit file"
+msgstr ""
+
+msgid "Edit files in the editor and commit changes here"
+msgstr ""
+
+msgid "Edit group: %{group_name}"
+msgstr ""
+
+msgid "Edit identity for %{user_name}"
+msgstr ""
+
+msgid "Edit issues"
+msgstr ""
+
+msgid "Edit iteration"
+msgstr ""
+
+msgid "Edit public deploy key"
+msgstr ""
+
+msgid "Edit stage"
+msgstr ""
+
+msgid "Edit this release"
+msgstr ""
+
+msgid "Edit wiki page"
+msgstr ""
+
+msgid "Edit your most recent comment in a thread (from an empty textarea)"
+msgstr ""
+
+msgid "Edited %{timeago}"
+msgstr ""
+
+msgid "Editing"
+msgstr ""
+
+msgid "Elasticsearch"
+msgstr ""
+
+msgid "Elasticsearch AWS IAM credentials"
+msgstr ""
+
+msgid "Elasticsearch indexing restrictions"
+msgstr ""
+
+msgid "Elasticsearch indexing started"
+msgstr ""
+
+msgid "Elasticsearch integration. Elasticsearch AWS IAM."
+msgstr ""
+
+msgid "Elasticsearch returned status code: %{status_code}"
+msgstr ""
+
+msgid "Elastic|None. Select namespaces to index."
+msgstr ""
+
+msgid "Elastic|None. Select projects to index."
+msgstr ""
+
+msgid "Email"
+msgstr ""
+
+msgid "Email Notification"
+msgstr ""
+
+msgid "Email address"
+msgstr ""
+
+msgid "Email could not be sent"
+msgstr ""
+
+msgid "Email display name"
+msgstr ""
+
+msgid "Email not verified. Please verify your email in Salesforce."
+msgstr ""
+
+msgid "Email notification for unknown sign-ins"
+msgstr ""
+
+msgid "Email patch"
+msgstr ""
+
+msgid "Email restrictions"
+msgstr ""
+
+msgid "Email restrictions for sign-ups"
+msgstr ""
+
+msgid "Email sent"
+msgstr ""
+
+msgid "Email the pipelines status to a list of recipients."
+msgstr ""
+
+msgid "EmailError|It appears that the email is blank. Make sure your reply is at the top of the email, we can't process inline replies."
+msgstr ""
+
+msgid "EmailError|The thread you are replying to no longer exists, perhaps it was deleted? If you believe this is in error, contact a staff member."
+msgstr ""
+
+msgid "EmailError|We couldn't figure out what the email is for. Please create your issue or comment through the web interface."
+msgstr ""
+
+msgid "EmailError|We couldn't figure out what the email is in reply to. Please create your comment through the web interface."
+msgstr ""
+
+msgid "EmailError|We couldn't figure out what user corresponds to the email. Please create your comment through the web interface."
+msgstr ""
+
+msgid "EmailError|We couldn't find the project. Please check if there's any typo."
+msgstr ""
+
+msgid "EmailError|You are not allowed to perform this action. If you believe this is in error, contact a staff member."
+msgstr ""
+
+msgid "EmailError|Your account has been blocked. If you believe this is in error, contact a staff member."
+msgstr ""
+
+msgid "EmailToken|reset it"
+msgstr ""
+
+msgid "EmailToken|resetting..."
+msgstr ""
+
+msgid "Emails"
+msgstr ""
+
+msgid "Emails sent from Service Desk will have this name"
+msgstr ""
+
+msgid "Emails separated by comma"
+msgstr ""
+
+msgid "EmailsOnPushService|Disable code diffs"
+msgstr ""
+
+msgid "EmailsOnPushService|Don't include possibly sensitive code diffs in notification body."
+msgstr ""
+
+msgid "EmailsOnPushService|Email the commits and diff of each push to a list of recipients."
+msgstr ""
+
+msgid "EmailsOnPushService|Emails on push"
+msgstr ""
+
+msgid "EmailsOnPushService|Emails separated by whitespace"
+msgstr ""
+
+msgid "EmailsOnPushService|Send from committer"
+msgstr ""
+
+msgid "EmailsOnPushService|Send notifications from the committer's email address if the domain is part of the domain GitLab is running on (e.g. %{domains})."
+msgstr ""
+
+msgid "Embed"
+msgstr ""
+
+msgid "Empty file"
+msgstr ""
+
+msgid "Enable"
+msgstr ""
+
+msgid "Enable Auto DevOps"
+msgstr ""
+
+msgid "Enable HTML emails"
+msgstr ""
+
+msgid "Enable Incident Management inbound alert limit"
+msgstr ""
+
+msgid "Enable PlantUML"
+msgstr ""
+
+msgid "Enable Pseudonymizer data collection"
+msgstr ""
+
+msgid "Enable SAML authentication for this group"
+msgstr ""
+
+msgid "Enable Seat Link"
+msgstr ""
+
+msgid "Enable Spam Check via external API endpoint"
+msgstr ""
+
+msgid "Enable access to Grafana"
+msgstr ""
+
+msgid "Enable access to the Performance Bar for a given group."
+msgstr ""
+
+msgid "Enable and configure Grafana."
+msgstr ""
+
+msgid "Enable and configure Prometheus metrics."
+msgstr ""
+
+msgid "Enable classification control using an external service"
+msgstr ""
+
+msgid "Enable container expiration and retention policies for projects created earlier than GitLab 12.7."
+msgstr ""
+
+msgid "Enable email restrictions for sign ups"
+msgstr ""
+
+msgid "Enable error tracking"
+msgstr ""
+
+msgid "Enable feature to choose access level"
+msgstr ""
+
+msgid "Enable for this project"
+msgstr ""
+
+msgid "Enable group Runners"
+msgstr ""
+
+msgid "Enable header and footer in emails"
+msgstr ""
+
+msgid "Enable integration"
+msgstr ""
+
+msgid "Enable maintenance mode"
+msgstr ""
+
+msgid "Enable mirror configuration"
+msgstr ""
+
+msgid "Enable or disable Seat Link."
+msgstr ""
+
+msgid "Enable or disable keyboard shortcuts"
+msgstr ""
+
+msgid "Enable or disable the Pseudonymizer data collection."
+msgstr ""
+
+msgid "Enable or disable version check and usage ping."
+msgstr ""
+
+msgid "Enable protected paths rate limit"
+msgstr ""
+
+msgid "Enable proxy"
+msgstr ""
+
+msgid "Enable reCAPTCHA or Akismet and set IP limits. For reCAPTCHA, we currently only support %{recaptcha_v2_link_start}v2%{recaptcha_v2_link_end}"
+msgstr ""
+
+msgid "Enable shared Runners"
+msgstr ""
+
+msgid "Enable snowplow tracking"
+msgstr ""
+
+msgid "Enable two-factor authentication"
+msgstr ""
+
+msgid "Enable usage ping"
+msgstr ""
+
+msgid "Enable usage ping to get an overview of how you are using GitLab from a feature perspective."
+msgstr ""
+
+msgid "Enable/disable your service desk. %{link_start}Learn more about service desk%{link_end}."
+msgstr ""
+
+msgid "EnableReviewApp|%{stepStart}Step 1%{stepEnd}. Ensure you have Kubernetes set up and have a base domain for your %{linkStart}cluster%{linkEnd}."
+msgstr ""
+
+msgid "EnableReviewApp|%{stepStart}Step 2%{stepEnd}. Copy the following snippet:"
+msgstr ""
+
+msgid "EnableReviewApp|%{stepStart}Step 3%{stepEnd}. Add it to the project %{linkStart}gitlab-ci.yml%{linkEnd} file."
+msgstr ""
+
+msgid "EnableReviewApp|Close"
+msgstr ""
+
+msgid "EnableReviewApp|Copy snippet text"
+msgstr ""
+
+msgid "Enabled"
+msgstr ""
+
+msgid "Enabled Git access protocols"
+msgstr ""
+
+msgid "Enabled sources for code import during project creation. OmniAuth must be configured for GitHub"
+msgstr ""
+
+msgid "Enabling this will only make licensed EE features available to projects if the project namespace's plan includes the feature or if the project is public."
+msgstr ""
+
+msgid "Encountered an error while rendering: %{err}"
+msgstr ""
+
+msgid "End date"
+msgstr ""
+
+msgid "Ends at (UTC)"
+msgstr ""
+
+msgid "Enforce DNS rebinding attack protection"
+msgstr ""
+
+msgid "Enforce personal access token expiration"
+msgstr ""
+
+msgid "Ensure connectivity is available from the GitLab server to the Prometheus server"
+msgstr ""
+
+msgid "Ensure your %{linkStart}environment is part of the deploy stage%{linkEnd} of your CI pipeline to track deployments to your cluster."
+msgstr ""
+
+msgid "Enter 2FA for Admin Mode"
+msgstr ""
+
+msgid "Enter Admin Mode"
+msgstr ""
+
+msgid "Enter IP address range"
+msgstr ""
+
+msgid "Enter a number"
+msgstr ""
+
+msgid "Enter a whole number between 0 and 100"
+msgstr ""
+
+msgid "Enter at least three characters to search"
+msgstr ""
+
+msgid "Enter board name"
+msgstr ""
+
+msgid "Enter domain"
+msgstr ""
+
+msgid "Enter in your Bitbucket Server URL and personal access token below"
+msgstr ""
+
+msgid "Enter in your Phabricator Server URL and personal access token below"
+msgstr ""
+
+msgid "Enter merge request URLs"
+msgstr ""
+
+msgid "Enter new %{field_title}"
+msgstr ""
+
+msgid "Enter new AWS Secret Access Key"
+msgstr ""
+
+msgid "Enter number of issues"
+msgstr ""
+
+msgid "Enter one or more user ID separated by commas"
+msgstr ""
+
+msgid "Enter the code from the two-factor app on your mobile device. If you've lost your device, you may enter one of your recovery codes."
+msgstr ""
+
+msgid "Enter the issue description"
+msgstr ""
+
+msgid "Enter the issue title"
+msgstr ""
+
+msgid "Enter the merge request description"
+msgstr ""
+
+msgid "Enter the merge request title"
+msgstr ""
+
+msgid "Enter the name of your application, and we'll return a unique %{type}."
+msgstr ""
+
+msgid "Enter weights for storages for new repositories."
+msgstr ""
+
+msgid "Enter your password to approve"
+msgstr ""
+
+msgid "Environment"
+msgstr ""
+
+msgid "Environment does not have deployments"
+msgstr ""
+
+msgid "Environment is required for Stages::MetricEndpointInserter"
+msgstr ""
+
+msgid "Environment is required for Stages::VariableEndpointInserter"
+msgstr ""
+
+msgid "Environment scope"
+msgstr ""
+
+msgid "Environment variables are applied to all project environments in this instance via the Runner. You can use environment variables for passwords, secret keys, etc. Make variables available to the running application by prepending the variable key with <code>K8S_SECRET_</code>. You can set variables to be:"
+msgstr ""
+
+msgid "Environment variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. Additionally, they can be masked so they are hidden in job logs, though they must match certain regexp requirements to do so. You can use environment variables for passwords, secret keys, or whatever you want."
+msgstr ""
+
+msgid "Environment variables are configured by your administrator to be %{link_start}protected%{link_end} by default"
+msgstr ""
+
+msgid "Environment variables on this GitLab instance are configured to be %{link_start}protected%{link_end} by default"
+msgstr ""
+
+msgid "Environment:"
+msgstr ""
+
+msgid "EnvironmentDashboard|API"
+msgstr ""
+
+msgid "EnvironmentDashboard|Created through the Deployment API"
+msgstr ""
+
+msgid "EnvironmentDashboard|You are looking at the last updated environment"
+msgstr ""
+
+msgid "Environments"
+msgstr ""
+
+msgid "Environments Dashboard"
+msgstr ""
+
+msgid "Environments allow you to track deployments of your application %{link_to_read_more}."
+msgstr ""
+
+msgid "Environments in %{name}"
+msgstr ""
+
+msgid "EnvironmentsDashboard|Add a project to the dashboard"
+msgstr ""
+
+msgid "EnvironmentsDashboard|Add projects"
+msgstr ""
+
+msgid "EnvironmentsDashboard|Environments Dashboard"
+msgstr ""
+
+msgid "EnvironmentsDashboard|Job: %{job}"
+msgstr ""
+
+msgid "EnvironmentsDashboard|More actions"
+msgstr ""
+
+msgid "EnvironmentsDashboard|More information"
+msgstr ""
+
+msgid "EnvironmentsDashboard|Remove"
+msgstr ""
+
+msgid "EnvironmentsDashboard|The environments dashboard provides a summary of each project's environments' status, including pipeline and alert statuses."
+msgstr ""
+
+msgid "EnvironmentsDashboard|This dashboard displays a maximum of 7 projects and 3 environments per project. %{readMoreLink}"
+msgstr ""
+
+msgid "Environments|An error occurred while canceling the auto stop, please try again"
+msgstr ""
+
+msgid "Environments|An error occurred while deleting the environment. Check if the environment stopped; if not, stop it and try again."
+msgstr ""
+
+msgid "Environments|An error occurred while fetching the environments."
+msgstr ""
+
+msgid "Environments|An error occurred while making the request."
+msgstr ""
+
+msgid "Environments|An error occurred while re-deploying the environment, please try again"
+msgstr ""
+
+msgid "Environments|An error occurred while rolling back the environment, please try again"
+msgstr ""
+
+msgid "Environments|An error occurred while stopping the environment, please try again"
+msgstr ""
+
+msgid "Environments|Are you sure you want to stop this environment?"
+msgstr ""
+
+msgid "Environments|Auto stop in"
+msgstr ""
+
+msgid "Environments|Auto stops %{auto_stop_time}"
+msgstr ""
+
+msgid "Environments|Commit"
+msgstr ""
+
+msgid "Environments|Currently showing %{fetched} results."
+msgstr ""
+
+msgid "Environments|Currently showing all results."
+msgstr ""
+
+msgid "Environments|Delete"
+msgstr ""
+
+msgid "Environments|Delete environment"
+msgstr ""
+
+msgid "Environments|Deleting the '%{environmentName}' environment cannot be undone. Do you want to delete it anyway?"
+msgstr ""
+
+msgid "Environments|Deploy to..."
+msgstr ""
+
+msgid "Environments|Deployment"
+msgstr ""
+
+msgid "Environments|Enable review app"
+msgstr ""
+
+msgid "Environments|Environment"
+msgstr ""
+
+msgid "Environments|Environments"
+msgstr ""
+
+msgid "Environments|Environments are places where code gets deployed, such as staging or production."
+msgstr ""
+
+msgid "Environments|Install Elastic Stack on your cluster to enable advanced querying capabilities such as full text search."
+msgstr ""
+
+msgid "Environments|Job"
+msgstr ""
+
+msgid "Environments|Learn about environments"
+msgstr ""
+
+msgid "Environments|Learn more about stopping environments"
+msgstr ""
+
+msgid "Environments|Logs from %{start} to %{end}."
+msgstr ""
+
+msgid "Environments|New environment"
+msgstr ""
+
+msgid "Environments|No deployed environments"
+msgstr ""
+
+msgid "Environments|No deployments yet"
+msgstr ""
+
+msgid "Environments|No pod selected"
+msgstr ""
+
+msgid "Environments|No pods to display"
+msgstr ""
+
+msgid "Environments|Note that this action will stop the environment, but it will %{emphasisStart}not%{emphasisEnd} have an effect on any existing deployment due to no “stop environment action†being defined in the %{ciConfigLinkStart}.gitlab-ci.yml%{ciConfigLinkEnd} file."
+msgstr ""
+
+msgid "Environments|Note that this action will stop the environment, but it will %{emphasis_start}not%{emphasis_end} have an effect on any existing deployment due to no “stop environment action†being defined in the %{ci_config_link_start}.gitlab-ci.yml%{ci_config_link_end} file."
+msgstr ""
+
+msgid "Environments|Open live environment"
+msgstr ""
+
+msgid "Environments|Pod name"
+msgstr ""
+
+msgid "Environments|Re-deploy"
+msgstr ""
+
+msgid "Environments|Re-deploy environment %{environment_name}?"
+msgstr ""
+
+msgid "Environments|Re-deploy environment %{name}?"
+msgstr ""
+
+msgid "Environments|Re-deploy to environment"
+msgstr ""
+
+msgid "Environments|Read more about environments"
+msgstr ""
+
+msgid "Environments|Rollback"
+msgstr ""
+
+msgid "Environments|Rollback environment"
+msgstr ""
+
+msgid "Environments|Rollback environment %{environment_name}?"
+msgstr ""
+
+msgid "Environments|Rollback environment %{name}?"
+msgstr ""
+
+msgid "Environments|Select environment"
+msgstr ""
+
+msgid "Environments|Select pod"
+msgstr ""
+
+msgid "Environments|Show all"
+msgstr ""
+
+msgid "Environments|Stop"
+msgstr ""
+
+msgid "Environments|Stop environment"
+msgstr ""
+
+msgid "Environments|Stopping"
+msgstr ""
+
+msgid "Environments|There was an error fetching the logs. Please try again."
+msgstr ""
+
+msgid "Environments|This action will relaunch the job for commit %{commit_id}, putting the environment in a previous version. Are you sure you want to continue?"
+msgstr ""
+
+msgid "Environments|This action will relaunch the job for commit %{linkStart}%{commitId}%{linkEnd}, putting the environment in a previous version. Are you sure you want to continue?"
+msgstr ""
+
+msgid "Environments|This action will run the job defined by %{environment_name} for commit %{commit_id}, putting the environment in a previous version. You can revert it by re-deploying the latest version of your application. Are you sure you want to continue?"
+msgstr ""
+
+msgid "Environments|This action will run the job defined by %{name} for commit %{linkStart}%{commitId}%{linkEnd} putting the environment in a previous version. You can revert it by re-deploying the latest version of your application. Are you sure you want to continue?"
+msgstr ""
+
+msgid "Environments|Updated"
+msgstr ""
+
+msgid "Environments|You don't have any environments right now"
+msgstr ""
+
+msgid "Environments|protected"
+msgstr ""
+
+msgid "Epic"
+msgstr ""
+
+msgid "Epic cannot be found."
+msgstr ""
+
+msgid "Epic events"
+msgstr ""
+
+msgid "Epics"
+msgstr ""
+
+msgid "Epics Roadmap"
+msgstr ""
+
+msgid "Epics and Issues"
+msgstr ""
+
+msgid "Epics let you manage your portfolio of projects more efficiently and with less effort"
+msgstr ""
+
+msgid "Epics, Issues, and Merge Requests"
+msgstr ""
+
+msgid "Epics|Add a new epic"
+msgstr ""
+
+msgid "Epics|Add an existing epic"
+msgstr ""
+
+msgid "Epics|An error occurred while saving the %{epicDateType} date"
+msgstr ""
+
+msgid "Epics|An error occurred while updating labels."
+msgstr ""
+
+msgid "Epics|Are you sure you want to remove %{bStart}%{targetIssueTitle}%{bEnd} from %{bStart}%{parentEpicTitle}%{bEnd}?"
+msgstr ""
+
+msgid "Epics|How can I solve this?"
+msgstr ""
+
+msgid "Epics|More information"
+msgstr ""
+
+msgid "Epics|Remove epic"
+msgstr ""
+
+msgid "Epics|Remove issue"
+msgstr ""
+
+msgid "Epics|Show more"
+msgstr ""
+
+msgid "Epics|Something went wrong while assigning issue to epic."
+msgstr ""
+
+msgid "Epics|Something went wrong while creating child epics."
+msgstr ""
+
+msgid "Epics|Something went wrong while creating issue."
+msgstr ""
+
+msgid "Epics|Something went wrong while fetching child epics."
+msgstr ""
+
+msgid "Epics|Something went wrong while fetching group epics."
+msgstr ""
+
+msgid "Epics|Something went wrong while moving item."
+msgstr ""
+
+msgid "Epics|Something went wrong while ordering item."
+msgstr ""
+
+msgid "Epics|Something went wrong while removing issue from epic."
+msgstr ""
+
+msgid "Epics|These dates affect how your epics appear in the roadmap. Dates from milestones come from the milestones assigned to issues in the epic. You can also set fixed dates or remove them entirely."
+msgstr ""
+
+msgid "Epics|This will also remove any descendents of %{bStart}%{targetEpicTitle}%{bEnd} from %{bStart}%{parentEpicTitle}%{bEnd}. Are you sure?"
+msgstr ""
+
+msgid "Epics|To schedule your epic's %{epicDateType} date based on milestones, assign a milestone with a %{epicDateType} date to any issue in the epic."
+msgstr ""
+
+msgid "Epics|due"
+msgstr ""
+
+msgid "Epics|start"
+msgstr ""
+
+msgid "Error"
+msgstr ""
+
+msgid "Error Details"
+msgstr ""
+
+msgid "Error Tracking"
+msgstr ""
+
+msgid "Error creating epic"
+msgstr ""
+
+msgid "Error creating label."
+msgstr ""
+
+msgid "Error creating new iteration"
+msgstr ""
+
+msgid "Error creating repository for snippet with id %{snippet_id}"
+msgstr ""
+
+msgid "Error creating the snippet"
+msgstr ""
+
+msgid "Error deleting %{issuableType}"
+msgstr ""
+
+msgid "Error deleting project. Check logs for error details."
+msgstr ""
+
+msgid "Error fetching diverging counts for branches. Please try again."
+msgstr ""
+
+msgid "Error fetching forked projects. Please try again."
+msgstr ""
+
+msgid "Error fetching labels."
+msgstr ""
+
+msgid "Error fetching network graph."
+msgstr ""
+
+msgid "Error fetching payload data."
+msgstr ""
+
+msgid "Error fetching projects"
+msgstr ""
+
+msgid "Error fetching refs"
+msgstr ""
+
+msgid "Error fetching the dependency list. Please check your network connection and try again."
+msgstr ""
+
+msgid "Error loading branch data. Please try again."
+msgstr ""
+
+msgid "Error loading branches."
+msgstr ""
+
+msgid "Error loading burndown chart data"
+msgstr ""
+
+msgid "Error loading countries data."
+msgstr ""
+
+msgid "Error loading file viewer."
+msgstr ""
+
+msgid "Error loading last commit."
+msgstr ""
+
+msgid "Error loading markdown preview"
+msgstr ""
+
+msgid "Error loading merge requests."
+msgstr ""
+
+msgid "Error loading milestone tab"
+msgstr ""
+
+msgid "Error loading project data. Please try again."
+msgstr ""
+
+msgid "Error loading template types."
+msgstr ""
+
+msgid "Error loading template."
+msgstr ""
+
+msgid "Error loading viewer"
+msgstr ""
+
+msgid "Error occurred when fetching sidebar data"
+msgstr ""
+
+msgid "Error occurred when saving assignees"
+msgstr ""
+
+msgid "Error occurred when toggling the notification subscription"
+msgstr ""
+
+msgid "Error occurred while updating the issue status"
+msgstr ""
+
+msgid "Error occurred while updating the issue weight"
+msgstr ""
+
+msgid "Error occurred. A blocked user cannot be deactivated"
+msgstr ""
+
+msgid "Error occurred. A blocked user must be unblocked to be activated"
+msgstr ""
+
+msgid "Error occurred. User was not blocked"
+msgstr ""
+
+msgid "Error occurred. User was not confirmed"
+msgstr ""
+
+msgid "Error occurred. User was not unblocked"
+msgstr ""
+
+msgid "Error occurred. User was not unlocked"
+msgstr ""
+
+msgid "Error rendering markdown preview"
+msgstr ""
+
+msgid "Error saving label update."
+msgstr ""
+
+msgid "Error setting up editor. Please try again."
+msgstr ""
+
+msgid "Error updating %{issuableType}"
+msgstr ""
+
+msgid "Error updating status for all to-do items."
+msgstr ""
+
+msgid "Error updating status of to-do item."
+msgstr ""
+
+msgid "Error updating the snippet"
+msgstr ""
+
+msgid "Error uploading file"
+msgstr ""
+
+msgid "Error uploading file: %{stripped}"
+msgstr ""
+
+msgid "Error while loading the merge request. Please try again."
+msgstr ""
+
+msgid "Error while loading the project data. Please try again."
+msgstr ""
+
+msgid "Error while migrating %{upload_id}: %{error_message}"
+msgstr ""
+
+msgid "Error with Akismet. Please check the logs for more info."
+msgstr ""
+
+msgid "ErrorTracking|Active"
+msgstr ""
+
+msgid "ErrorTracking|After adding your Auth Token, use the 'Connect' button to load projects"
+msgstr ""
+
+msgid "ErrorTracking|Auth Token"
+msgstr ""
+
+msgid "ErrorTracking|Click 'Connect' to re-establish the connection to Sentry and activate the dropdown."
+msgstr ""
+
+msgid "ErrorTracking|Connection has failed. Re-check Auth Token and try again."
+msgstr ""
+
+msgid "ErrorTracking|If you self-host Sentry, enter the full URL of your Sentry instance. If you're using Sentry's hosted solution, enter https://sentry.io"
+msgstr ""
+
+msgid "ErrorTracking|No projects available"
+msgstr ""
+
+msgid "ErrorTracking|Select project"
+msgstr ""
+
+msgid "ErrorTracking|To enable project selection, enter a valid Auth Token"
+msgstr ""
+
+msgid "Errors"
+msgstr ""
+
+msgid "Errors:"
+msgstr ""
+
+msgid "Estimate"
+msgstr ""
+
+msgid "Estimated"
+msgstr ""
+
+msgid "EventFilterBy|Filter by all"
+msgstr ""
+
+msgid "EventFilterBy|Filter by comments"
+msgstr ""
+
+msgid "EventFilterBy|Filter by designs"
+msgstr ""
+
+msgid "EventFilterBy|Filter by epic events"
+msgstr ""
+
+msgid "EventFilterBy|Filter by issue events"
+msgstr ""
+
+msgid "EventFilterBy|Filter by merge events"
+msgstr ""
+
+msgid "EventFilterBy|Filter by push events"
+msgstr ""
+
+msgid "EventFilterBy|Filter by team"
+msgstr ""
+
+msgid "EventFilterBy|Filter by wiki"
+msgstr ""
+
+msgid "Events"
+msgstr ""
+
+msgid "Events in %{group_name}"
+msgstr ""
+
+msgid "Events in %{project_path}"
+msgstr ""
+
+msgid "Every %{action} attempt has failed: %{job_error_message}. Please try again."
+msgstr ""
+
+msgid "Every day"
+msgstr ""
+
+msgid "Every day (at %{time})"
+msgstr ""
+
+msgid "Every month"
+msgstr ""
+
+msgid "Every month (Day %{day} at %{time})"
+msgstr ""
+
+msgid "Every three months"
+msgstr ""
+
+msgid "Every two weeks"
+msgstr ""
+
+msgid "Every week"
+msgstr ""
+
+msgid "Every week (%{weekday} at %{time})"
+msgstr ""
+
+msgid "Everyone"
+msgstr ""
+
+msgid "Everyone With Access"
+msgstr ""
+
+msgid "Everyone can contribute"
+msgstr ""
+
+msgid "Everything on your to-do list is marked as done."
+msgstr ""
+
+msgid "Everything you need to create a GitLab Pages site using Gatsby."
+msgstr ""
+
+msgid "Everything you need to create a GitLab Pages site using GitBook."
+msgstr ""
+
+msgid "Everything you need to create a GitLab Pages site using Hexo."
+msgstr ""
+
+msgid "Everything you need to create a GitLab Pages site using Hugo."
+msgstr ""
+
+msgid "Everything you need to create a GitLab Pages site using Jekyll."
+msgstr ""
+
+msgid "Everything you need to create a GitLab Pages site using plain HTML."
+msgstr ""
+
+msgid "Evidence collection"
+msgstr ""
+
+msgid "Exactly one of %{attributes} is required"
+msgstr ""
+
+msgid "Example: <code>acme.com,acme.co.in,acme.uk</code>."
+msgstr ""
+
+msgid "Example: @sub\\.company\\.com$"
+msgstr ""
+
+msgid "Example: Usage = single query. (Requested) / (Capacity) = multiple queries combined into a formula."
+msgstr ""
+
+msgid "Except policy:"
+msgstr ""
+
+msgid "Excluding merge commits. Limited to %{limit} commits."
+msgstr ""
+
+msgid "Excluding merge commits. Limited to 6,000 commits."
+msgstr ""
+
+msgid "Existing members and groups"
+msgstr ""
+
+msgid "Existing projects may be moved into a group"
+msgstr ""
+
+msgid "Existing projects will be able to use expiration policies. Avoid enabling this if an external Container Registry is being used, as there is a performance risk if many images exist on one project."
+msgstr ""
+
+msgid "Existing shares"
+msgstr ""
+
+msgid "Existing sign in methods may be removed"
+msgstr ""
+
+msgid "Expand"
+msgstr ""
+
+msgid "Expand all"
+msgstr ""
+
+msgid "Expand approvers"
+msgstr ""
+
+msgid "Expand down"
+msgstr ""
+
+msgid "Expand dropdown"
+msgstr ""
+
+msgid "Expand milestones"
+msgstr ""
+
+msgid "Expand sidebar"
+msgstr ""
+
+msgid "Expand up"
+msgstr ""
+
+msgid "Experienced"
+msgstr ""
+
+msgid "Expiration"
+msgstr ""
+
+msgid "Expiration date"
+msgstr ""
+
+msgid "Expiration not enforced"
+msgstr ""
+
+msgid "Expiration policy for the Container Registry is a perfect solution for keeping the Registry space down while still enjoying the full power of GitLab CI/CD."
+msgstr ""
+
+msgid "Expired"
+msgstr ""
+
+msgid "Expired %{expiredOn}"
+msgstr ""
+
+msgid "Expired:"
+msgstr ""
+
+msgid "Expires"
+msgstr ""
+
+msgid "Expires at"
+msgstr ""
+
+msgid "Expires in %{expires_at}"
+msgstr ""
+
+msgid "Expires on"
+msgstr ""
+
+msgid "Expires:"
+msgstr ""
+
+msgid "Explain the problem. If appropriate, provide a link to the relevant issue or comment."
+msgstr ""
+
+msgid "Explore"
+msgstr ""
+
+msgid "Explore GitLab"
+msgstr ""
+
+msgid "Explore Groups"
+msgstr ""
+
+msgid "Explore groups"
+msgstr ""
+
+msgid "Explore projects"
+msgstr ""
+
+msgid "Explore public groups"
+msgstr ""
+
+msgid "Export as CSV"
+msgstr ""
+
+msgid "Export group"
+msgstr ""
+
+msgid "Export issues"
+msgstr ""
+
+msgid "Export project"
+msgstr ""
+
+msgid "Export this group with all related data to a new GitLab instance. Once complete, you can import the data file from the \"New Group\" page."
+msgstr ""
+
+msgid "Export this project with all its related data in order to move your project to a new GitLab instance. Once the export is finished, you can import the file from the \"New Project\" page."
+msgstr ""
+
+msgid "Export variable to pipelines running on protected branches and tags only."
+msgstr ""
+
+msgid "External Classification Policy Authorization"
+msgstr ""
+
+msgid "External ID"
+msgstr ""
+
+msgid "External URL"
+msgstr ""
+
+msgid "External Wiki"
+msgstr ""
+
+msgid "External authentication"
+msgstr ""
+
+msgid "External authorization denied access to this project"
+msgstr ""
+
+msgid "External authorization request timeout"
+msgstr ""
+
+msgid "External storage URL"
+msgstr ""
+
+msgid "External storage authentication token"
+msgstr ""
+
+msgid "ExternalAuthorizationService|Classification label"
+msgstr ""
+
+msgid "ExternalAuthorizationService|When no classification label is set the default label `%{default_label}` will be used."
+msgstr ""
+
+msgid "ExternalWikiService|External Wiki"
+msgstr ""
+
+msgid "ExternalWikiService|Replaces the link to the internal wiki with a link to an external wiki."
+msgstr ""
+
+msgid "ExternalWikiService|The URL of the external Wiki"
+msgstr ""
+
+msgid "Facebook"
+msgstr ""
+
+msgid "Failed"
+msgstr ""
+
+msgid "Failed Jobs"
+msgstr ""
+
+msgid "Failed on"
+msgstr ""
+
+msgid "Failed to add a Zoom meeting"
+msgstr ""
+
+msgid "Failed to apply commands."
+msgstr ""
+
+msgid "Failed to assign a user because no user was found."
+msgstr ""
+
+msgid "Failed to cancel auto stop because failed to update the environment."
+msgstr ""
+
+msgid "Failed to cancel auto stop because the environment is not set as auto stop."
+msgstr ""
+
+msgid "Failed to cancel auto stop because you do not have permission to update the environment."
+msgstr ""
+
+msgid "Failed to change the owner"
+msgstr ""
+
+msgid "Failed to check related branches."
+msgstr ""
+
+msgid "Failed to create Merge Request. Please try again."
+msgstr ""
+
+msgid "Failed to create a branch for this issue. Please try again."
+msgstr ""
+
+msgid "Failed to create import label for jira import."
+msgstr ""
+
+msgid "Failed to create repository"
+msgstr ""
+
+msgid "Failed to create resources"
+msgstr ""
+
+msgid "Failed to create wiki"
+msgstr ""
+
+msgid "Failed to delete board. Please try again."
+msgstr ""
+
+msgid "Failed to deploy to"
+msgstr ""
+
+msgid "Failed to enqueue the rebase operation, possibly due to a long-lived transaction. Try again later."
+msgstr ""
+
+msgid "Failed to find import label for Jira import."
+msgstr ""
+
+msgid "Failed to get ref."
+msgstr ""
+
+msgid "Failed to install."
+msgstr ""
+
+msgid "Failed to load assignees. Please try again."
+msgstr ""
+
+msgid "Failed to load authors. Please try again."
+msgstr ""
+
+msgid "Failed to load emoji list."
+msgstr ""
+
+msgid "Failed to load error details from Sentry."
+msgstr ""
+
+msgid "Failed to load errors from Sentry."
+msgstr ""
+
+msgid "Failed to load group activity metrics. Please try again."
+msgstr ""
+
+msgid "Failed to load groups & users."
+msgstr ""
+
+msgid "Failed to load labels. Please try again."
+msgstr ""
+
+msgid "Failed to load milestones. Please try again."
+msgstr ""
+
+msgid "Failed to load related branches"
+msgstr ""
+
+msgid "Failed to load stacktrace."
+msgstr ""
+
+msgid "Failed to mark this issue as a duplicate because referenced issue was not found."
+msgstr ""
+
+msgid "Failed to move this issue because label was not found."
+msgstr ""
+
+msgid "Failed to move this issue because only a single label can be provided."
+msgstr ""
+
+msgid "Failed to move this issue because target project doesn't exist."
+msgstr ""
+
+msgid "Failed to promote label due to internal error. Please contact administrators."
+msgstr ""
+
+msgid "Failed to protect the branch"
+msgstr ""
+
+msgid "Failed to protect the environment"
+msgstr ""
+
+msgid "Failed to publish issue on status page."
+msgstr ""
+
+msgid "Failed to remove a Zoom meeting"
+msgstr ""
+
+msgid "Failed to remove issue from board, please try again."
+msgstr ""
+
+msgid "Failed to remove mirror."
+msgstr ""
+
+msgid "Failed to remove the pipeline schedule"
+msgstr ""
+
+msgid "Failed to remove user identity."
+msgstr ""
+
+msgid "Failed to remove user key."
+msgstr ""
+
+msgid "Failed to reset key. Please try again."
+msgstr ""
+
+msgid "Failed to save merge conflicts resolutions. Please try again!"
+msgstr ""
+
+msgid "Failed to save new settings"
+msgstr ""
+
+msgid "Failed to save preferences (%{error_message})."
+msgstr ""
+
+msgid "Failed to save preferences."
+msgstr ""
+
+msgid "Failed to set due date because the date format is invalid."
+msgstr ""
+
+msgid "Failed to set iteration on this issue. Please try again."
+msgstr ""
+
+msgid "Failed to signing using smartcard authentication"
+msgstr ""
+
+msgid "Failed to update branch!"
+msgstr ""
+
+msgid "Failed to update environment!"
+msgstr ""
+
+msgid "Failed to update issue status"
+msgstr ""
+
+msgid "Failed to update issues, please try again."
+msgstr ""
+
+msgid "Failed to update tag!"
+msgstr ""
+
+msgid "Failed to update."
+msgstr ""
+
+msgid "Failed to upgrade."
+msgstr ""
+
+msgid "Failed to upload object map file"
+msgstr ""
+
+msgid "Failed to verify domain ownership"
+msgstr ""
+
+msgid "Failure"
+msgstr ""
+
+msgid "False positive"
+msgstr ""
+
+msgid "Fast-forward merge is not possible. Rebase the source branch onto %{targetBranch} to allow this merge request to be merged."
+msgstr ""
+
+msgid "Fast-forward merge is not possible. Rebase the source branch onto the target branch."
+msgstr ""
+
+msgid "Fast-forward merge without a merge commit"
+msgstr ""
+
+msgid "Faster as it re-uses the project workspace (falling back to clone if it doesn't exist)"
+msgstr ""
+
+msgid "Faster releases. Better code. Less pain."
+msgstr ""
+
+msgid "Favicon was successfully removed."
+msgstr ""
+
+msgid "Feature Flags"
+msgstr ""
+
+msgid "Feature flag was not removed."
+msgstr ""
+
+msgid "Feature flag was successfully removed."
+msgstr ""
+
+msgid "FeatureFlags|* (All Environments)"
+msgstr ""
+
+msgid "FeatureFlags|* (All environments)"
+msgstr ""
+
+msgid "FeatureFlags|API URL"
+msgstr ""
+
+msgid "FeatureFlags|Active"
+msgstr ""
+
+msgid "FeatureFlags|Add strategy"
+msgstr ""
+
+msgid "FeatureFlags|All users"
+msgstr ""
+
+msgid "FeatureFlags|Configure"
+msgstr ""
+
+msgid "FeatureFlags|Configure feature flags"
+msgstr ""
+
+msgid "FeatureFlags|Create feature flag"
+msgstr ""
+
+msgid "FeatureFlags|Delete %{name}?"
+msgstr ""
+
+msgid "FeatureFlags|Delete feature flag"
+msgstr ""
+
+msgid "FeatureFlags|Description"
+msgstr ""
+
+msgid "FeatureFlags|Edit Feature Flag"
+msgstr ""
+
+msgid "FeatureFlags|Edit Feature Flag User List"
+msgstr ""
+
+msgid "FeatureFlags|Edit list"
+msgstr ""
+
+msgid "FeatureFlags|Enable features for specific users and specific environments by defining feature flag strategies. By default, features are available to all users in all environments."
+msgstr ""
+
+msgid "FeatureFlags|Environment Spec"
+msgstr ""
+
+msgid "FeatureFlags|Environment Specs"
+msgstr ""
+
+msgid "FeatureFlags|Feature Flag"
+msgstr ""
+
+msgid "FeatureFlags|Feature Flag User List Details"
+msgstr ""
+
+msgid "FeatureFlags|Feature Flag behavior is built up by creating a set of rules to define the status of target environments. A default wildcard rule %{codeStart}*%{codeEnd} for %{boldStart}All Environments%{boldEnd} is set, and you are able to add as many rules as you need by choosing environment specs below. You can toggle the behavior for each of your rules to set them %{boldStart}Active%{boldEnd} or %{boldStart}Inactive%{boldEnd}."
+msgstr ""
+
+msgid "FeatureFlags|Feature Flag has no strategies"
+msgstr ""
+
+msgid "FeatureFlags|Feature Flags"
+msgstr ""
+
+msgid "FeatureFlags|Feature Flags will look different in the next milestone. No action is needed, but you may notice the functionality was changed to improve the workflow."
+msgstr ""
+
+msgid "FeatureFlags|Feature flag %{name} will be removed. Are you sure?"
+msgstr ""
+
+msgid "FeatureFlags|Feature flags allow you to configure your code into different flavors by dynamically toggling certain functionality."
+msgstr ""
+
+msgid "FeatureFlags|Flag becomes read only soon"
+msgstr ""
+
+msgid "FeatureFlags|Get started with feature flags"
+msgstr ""
+
+msgid "FeatureFlags|GitLab is moving to a new way of managing feature flags, and in 13.4, this feature flag will become read-only. Please create a new feature flag."
+msgstr ""
+
+msgid "FeatureFlags|ID"
+msgstr ""
+
+msgid "FeatureFlags|Inactive"
+msgstr ""
+
+msgid "FeatureFlags|Inactive flag for %{scope}"
+msgstr ""
+
+msgid "FeatureFlags|Include additional user IDs"
+msgstr ""
+
+msgid "FeatureFlags|Install a %{docs_link_anchored_start}compatible client library%{docs_link_anchored_end} and specify the API URL, application name, and instance ID during the configuration setup. %{docs_link_start}More Information%{docs_link_end}"
+msgstr ""
+
+msgid "FeatureFlags|Instance ID"
+msgstr ""
+
+msgid "FeatureFlags|List details"
+msgstr ""
+
+msgid "FeatureFlags|Loading feature flags"
+msgstr ""
+
+msgid "FeatureFlags|More information"
+msgstr ""
+
+msgid "FeatureFlags|Name"
+msgstr ""
+
+msgid "FeatureFlags|New"
+msgstr ""
+
+msgid "FeatureFlags|New Feature Flag"
+msgstr ""
+
+msgid "FeatureFlags|New Feature Flag User List"
+msgstr ""
+
+msgid "FeatureFlags|New feature flag"
+msgstr ""
+
+msgid "FeatureFlags|New list"
+msgstr ""
+
+msgid "FeatureFlags|Percent rollout (logged in users)"
+msgstr ""
+
+msgid "FeatureFlags|Percent rollout must be a whole number between 0 and 100"
+msgstr ""
+
+msgid "FeatureFlags|Protected"
+msgstr ""
+
+msgid "FeatureFlags|Remove"
+msgstr ""
+
+msgid "FeatureFlags|Rollout Percentage"
+msgstr ""
+
+msgid "FeatureFlags|Rollout Strategy"
+msgstr ""
+
+msgid "FeatureFlags|Status"
+msgstr ""
+
+msgid "FeatureFlags|Strategies"
+msgstr ""
+
+msgid "FeatureFlags|Target environments"
+msgstr ""
+
+msgid "FeatureFlags|There was an error fetching the feature flags."
+msgstr ""
+
+msgid "FeatureFlags|There was an error retrieving user lists"
+msgstr ""
+
+msgid "FeatureFlags|Try again in a few moments or contact your support team."
+msgstr ""
+
+msgid "FeatureFlags|User IDs"
+msgstr ""
+
+msgid "FeatureFlag|Delete strategy"
+msgstr ""
+
+msgid "FeatureFlag|List"
+msgstr ""
+
+msgid "FeatureFlag|Percentage"
+msgstr ""
+
+msgid "FeatureFlag|Select a user list"
+msgstr ""
+
+msgid "FeatureFlag|There are no configured user lists"
+msgstr ""
+
+msgid "FeatureFlag|Type"
+msgstr ""
+
+msgid "FeatureFlag|User IDs"
+msgstr ""
+
+msgid "Feb"
+msgstr ""
+
+msgid "February"
+msgstr ""
+
+msgid "Fetching incoming email"
+msgstr ""
+
+msgid "Fetching licenses failed."
+msgstr ""
+
+msgid "Fetching licenses failed. The request endpoint was not found."
+msgstr ""
+
+msgid "Fetching licenses failed. You are not permitted to perform this action."
+msgstr ""
+
+msgid "File"
+msgstr ""
+
+msgid "File Hooks"
+msgstr ""
+
+msgid "File Hooks (%{count})"
+msgstr ""
+
+msgid "File added"
+msgstr ""
+
+msgid "File browser"
+msgstr ""
+
+msgid "File deleted"
+msgstr ""
+
+msgid "File format is no longer supported"
+msgstr ""
+
+msgid "File hooks are similar to system hooks but are executed as files instead of sending data to a URL."
+msgstr ""
+
+msgid "File mode changed from %{a_mode} to %{b_mode}"
+msgstr ""
+
+msgid "File moved"
+msgstr ""
+
+msgid "File name"
+msgstr ""
+
+msgid "File renamed with no changes."
+msgstr ""
+
+msgid "File sync capacity"
+msgstr ""
+
+msgid "File templates"
+msgstr ""
+
+msgid "File upload error."
+msgstr ""
+
+msgid "Files"
+msgstr ""
+
+msgid "Files breadcrumb"
+msgstr ""
+
+msgid "Files, directories, and submodules in the path %{path} for commit reference %{ref}"
+msgstr ""
+
+msgid "Fill in the fields below, turn on <strong>%{enable_label}</strong>, and press <strong>%{save_changes}</strong>"
+msgstr ""
+
+msgid "Filter"
+msgstr ""
+
+msgid "Filter by %{issuable_type} that are currently closed."
+msgstr ""
+
+msgid "Filter by %{issuable_type} that are currently opened."
+msgstr ""
+
+msgid "Filter by %{page_context_word} that are currently opened."
+msgstr ""
+
+msgid "Filter by Git revision"
+msgstr ""
+
+msgid "Filter by commit message"
+msgstr ""
+
+msgid "Filter by issues that are currently closed."
+msgstr ""
+
+msgid "Filter by label"
+msgstr ""
+
+msgid "Filter by merge requests that are currently closed and unmerged."
+msgstr ""
+
+msgid "Filter by merge requests that are currently merged."
+msgstr ""
+
+msgid "Filter by milestone name"
+msgstr ""
+
+msgid "Filter by name"
+msgstr ""
+
+msgid "Filter by requirements that are currently archived."
+msgstr ""
+
+msgid "Filter by requirements that are currently opened."
+msgstr ""
+
+msgid "Filter by status"
+msgstr ""
+
+msgid "Filter by two-factor authentication"
+msgstr ""
+
+msgid "Filter by user"
+msgstr ""
+
+msgid "Filter pipelines"
+msgstr ""
+
+msgid "Filter projects"
+msgstr ""
+
+msgid "Filter results"
+msgstr ""
+
+msgid "Filter results by group"
+msgstr ""
+
+msgid "Filter results by project"
+msgstr ""
+
+msgid "Filter results..."
+msgstr ""
+
+msgid "Filter your projects by name"
+msgstr ""
+
+msgid "Filter..."
+msgstr ""
+
+msgid "Find File"
+msgstr ""
+
+msgid "Find bugs in your code with coverage-guided fuzzing"
+msgstr ""
+
+msgid "Find by path"
+msgstr ""
+
+msgid "Find existing members by name"
+msgstr ""
+
+msgid "Find file"
+msgstr ""
+
+msgid "Find the downloaded ZIP file and decompress it."
+msgstr ""
+
+msgid "Find the newly extracted <code>Takeout/Google Code Project Hosting/GoogleCodeProjectHosting.json</code> file."
+msgstr ""
+
+msgid "Fingerprint"
+msgstr ""
+
+msgid "Fingerprints"
+msgstr ""
+
+msgid "Finish editing this message first!"
+msgstr ""
+
+msgid "Finish review"
+msgstr ""
+
+msgid "Finish setting up your dedicated account for <strong>%{group_name}</strong>."
+msgstr ""
+
+msgid "Finished"
+msgstr ""
+
+msgid "First Name is too long (maximum is %{max_length} characters)."
+msgstr ""
+
+msgid "First Seen"
+msgstr ""
+
+msgid "First day of the week"
+msgstr ""
+
+msgid "First name"
+msgstr ""
+
+msgid "First seen"
+msgstr ""
+
+msgid "Fixed date"
+msgstr ""
+
+msgid "Fixed due date"
+msgstr ""
+
+msgid "Fixed start date"
+msgstr ""
+
+msgid "Fixed:"
+msgstr ""
+
+msgid "Flags"
+msgstr ""
+
+msgid "FlowdockService|Flowdock Git source token"
+msgstr ""
+
+msgid "FlowdockService|Flowdock is a collaboration web app for technical teams."
+msgstr ""
+
+msgid "FogBugz Email"
+msgstr ""
+
+msgid "FogBugz Import"
+msgstr ""
+
+msgid "FogBugz Password"
+msgstr ""
+
+msgid "FogBugz URL"
+msgstr ""
+
+msgid "FogBugz import"
+msgstr ""
+
+msgid "Folder/%{name}"
+msgstr ""
+
+msgid "Follow the steps below to export your Google Code project data."
+msgstr ""
+
+msgid "Font Color"
+msgstr ""
+
+msgid "Footer message"
+msgstr ""
+
+msgid "For each Jira issue successfully imported, we'll create a new GitLab issue with the following data:"
+msgstr ""
+
+msgid "For internal projects, any logged in user can view pipelines and access job details (output logs and artifacts)"
+msgstr ""
+
+msgid "For more info, read the documentation."
+msgstr ""
+
+msgid "For more information, go to the "
+msgstr ""
+
+msgid "For more information, please review %{link_start_tag}Jaeger's configuration doc%{link_end_tag}"
+msgstr ""
+
+msgid "For more information, see the File Hooks documentation."
+msgstr ""
+
+msgid "For more information, see the documentation on %{deactivating_usage_ping_link_start}deactivating the usage ping%{deactivating_usage_ping_link_end}."
+msgstr ""
+
+msgid "For more information, see the documentation on %{link_start}disabling Seat Link%{link_end}."
+msgstr ""
+
+msgid "For private projects, any member (guest or higher) can view pipelines and access job details (output logs and artifacts)"
+msgstr ""
+
+msgid "For public projects, anyone can view pipelines and access job details (output logs and artifacts)"
+msgstr ""
+
+msgid "Forgot your password?"
+msgstr ""
+
+msgid "Fork"
+msgstr ""
+
+msgid "Fork Error!"
+msgstr ""
+
+msgid "Fork project"
+msgstr ""
+
+msgid "Fork project?"
+msgstr ""
+
+msgid "ForkedFromProjectPath|Forked from"
+msgstr ""
+
+msgid "ForkedFromProjectPath|Forked from an inaccessible project"
+msgstr ""
+
+msgid "Forking in progress"
+msgstr ""
+
+msgid "Forks"
+msgstr ""
+
+msgid "Format"
+msgstr ""
+
+msgid "Format: %{dateFormat}"
+msgstr ""
+
+msgid "Forward external support email address to"
+msgstr ""
+
+msgid "Found errors in your %{gitlab_ci_yml}:"
+msgstr ""
+
+msgid "Found errors in your .gitlab-ci.yml:"
+msgstr ""
+
+msgid "Free Trial"
+msgstr ""
+
+msgid "Free Trial of GitLab.com Gold"
+msgstr ""
+
+msgid "Frequency"
+msgstr ""
+
+msgid "Friday"
+msgstr ""
+
+msgid "From"
+msgstr ""
+
+msgid "From %{providerTitle}"
+msgstr ""
+
+msgid "From <code>%{source_title}</code> into"
+msgstr ""
+
+msgid "From Bitbucket"
+msgstr ""
+
+msgid "From Bitbucket Server"
+msgstr ""
+
+msgid "From FogBugz"
+msgstr ""
+
+msgid "From GitLab.com"
+msgstr ""
+
+msgid "From Google Code"
+msgstr ""
+
+msgid "From issue creation until deploy to production"
+msgstr ""
+
+msgid "From merge request merge until deploy to production"
+msgstr ""
+
+msgid "From the Kubernetes cluster details view, install Runner from the applications list"
+msgstr ""
+
+msgid "Full name"
+msgstr ""
+
+msgid "GPG Key ID:"
+msgstr ""
+
+msgid "GPG Keys"
+msgstr ""
+
+msgid "GPG keys allow you to verify signed commits."
+msgstr ""
+
+msgid "GPG signature (loading...)"
+msgstr ""
+
+msgid "General"
+msgstr ""
+
+msgid "General Settings"
+msgstr ""
+
+msgid "General pipelines"
+msgstr ""
+
+msgid "Generate a default set of labels"
+msgstr ""
+
+msgid "Generate key"
+msgstr ""
+
+msgid "Generate new export"
+msgstr ""
+
+msgid "Geo"
+msgstr ""
+
+msgid "Geo Nodes"
+msgstr ""
+
+msgid "Geo Nodes|Cannot remove a primary node if there is a secondary node"
+msgstr ""
+
+msgid "Geo Replication"
+msgstr ""
+
+msgid "Geo Settings"
+msgstr ""
+
+msgid "Geo allows you to replicate your GitLab instance to other geographical locations."
+msgstr ""
+
+msgid "Geo nodes are paused using a command run on the node"
+msgstr ""
+
+msgid "GeoNodeStatusEvent|%{timeAgoStr} (%{pendingEvents} events)"
+msgstr ""
+
+msgid "GeoNodeSyncStatus|Node is failing or broken."
+msgstr ""
+
+msgid "GeoNodeSyncStatus|Node is slow, overloaded, or it just recovered after an outage."
+msgstr ""
+
+msgid "GeoNodes|Attachments"
+msgstr ""
+
+msgid "GeoNodes|Checksummed"
+msgstr ""
+
+msgid "GeoNodes|Consult Geo troubleshooting information"
+msgstr ""
+
+msgid "GeoNodes|Container repositories"
+msgstr ""
+
+msgid "GeoNodes|Data replication lag"
+msgstr ""
+
+msgid "GeoNodes|Design repositories"
+msgstr ""
+
+msgid "GeoNodes|Does not match the primary storage configuration"
+msgstr ""
+
+msgid "GeoNodes|Failed"
+msgstr ""
+
+msgid "GeoNodes|Full"
+msgstr ""
+
+msgid "GeoNodes|GitLab version"
+msgstr ""
+
+msgid "GeoNodes|GitLab version does not match the primary node version"
+msgstr ""
+
+msgid "GeoNodes|Health status"
+msgstr ""
+
+msgid "GeoNodes|Internal URL"
+msgstr ""
+
+msgid "GeoNodes|Job artifacts"
+msgstr ""
+
+msgid "GeoNodes|LFS objects"
+msgstr ""
+
+msgid "GeoNodes|Last event ID processed by cursor"
+msgstr ""
+
+msgid "GeoNodes|Last event ID seen from primary"
+msgstr ""
+
+msgid "GeoNodes|Learn more about Geo node statuses"
+msgstr ""
+
+msgid "GeoNodes|Loading nodes"
+msgstr ""
+
+msgid "GeoNodes|New node"
+msgstr ""
+
+msgid "GeoNodes|Node Authentication was successfully repaired."
+msgstr ""
+
+msgid "GeoNodes|Node URL"
+msgstr ""
+
+msgid "GeoNodes|Node was successfully removed."
+msgstr ""
+
+msgid "GeoNodes|Node's status was updated %{timeAgo}."
+msgstr ""
+
+msgid "GeoNodes|Not checksummed"
+msgstr ""
+
+msgid "GeoNodes|Package files"
+msgstr ""
+
+msgid "GeoNodes|Pausing replication stops the sync process. Are you sure?"
+msgstr ""
+
+msgid "GeoNodes|Removing a Geo primary node stops the synchronization to all nodes. Are you sure?"
+msgstr ""
+
+msgid "GeoNodes|Removing a Geo secondary node stops the synchronization to that node. Are you sure?"
+msgstr ""
+
+msgid "GeoNodes|Replicated data is verified with the %{nodeText} using checksums"
+msgstr ""
+
+msgid "GeoNodes|Replication slot WAL"
+msgstr ""
+
+msgid "GeoNodes|Replication slots"
+msgstr ""
+
+msgid "GeoNodes|Replication status"
+msgstr ""
+
+msgid "GeoNodes|Repositories"
+msgstr ""
+
+msgid "GeoNodes|Repository checksum progress"
+msgstr ""
+
+msgid "GeoNodes|Repository verification progress"
+msgstr ""
+
+msgid "GeoNodes|Selective (%{syncLabel})"
+msgstr ""
+
+msgid "GeoNodes|Selective synchronization"
+msgstr ""
+
+msgid "GeoNodes|Something went wrong while changing node status"
+msgstr ""
+
+msgid "GeoNodes|Something went wrong while fetching nodes"
+msgstr ""
+
+msgid "GeoNodes|Something went wrong while removing node"
+msgstr ""
+
+msgid "GeoNodes|Something went wrong while repairing node"
+msgstr ""
+
+msgid "GeoNodes|Storage config"
+msgstr ""
+
+msgid "GeoNodes|Sync settings"
+msgstr ""
+
+msgid "GeoNodes|Unused slots"
+msgstr ""
+
+msgid "GeoNodes|Unverified"
+msgstr ""
+
+msgid "GeoNodes|Updated %{timeAgo}"
+msgstr ""
+
+msgid "GeoNodes|Used slots"
+msgstr ""
+
+msgid "GeoNodes|Verified"
+msgstr ""
+
+msgid "GeoNodes|Wiki checksum progress"
+msgstr ""
+
+msgid "GeoNodes|Wiki verification progress"
+msgstr ""
+
+msgid "GeoNodes|Wikis"
+msgstr ""
+
+msgid "GeoNodes|With %{geo} you can install a special read-only and replicated instance anywhere. Before you add nodes, follow the %{instructions} in the exact order they appear."
+msgstr ""
+
+msgid "GeoNodes|You have configured Geo nodes using an insecure HTTP connection. We recommend the use of HTTPS."
+msgstr ""
+
+msgid "GeoNodes|primary node"
+msgstr ""
+
+msgid "GeoNodes|secondary nodes"
+msgstr ""
+
+msgid "Geo|%{label} can't be blank"
+msgstr ""
+
+msgid "Geo|%{label} should be between 1-999"
+msgstr ""
+
+msgid "Geo|%{name} is scheduled for forced re-download"
+msgstr ""
+
+msgid "Geo|%{name} is scheduled for re-sync"
+msgstr ""
+
+msgid "Geo|%{name} is scheduled for re-verify"
+msgstr ""
+
+msgid "Geo|Adjust your filters/search criteria above. If you believe this may be an error, please refer to the %{linkStart}Geo Troubleshooting%{linkEnd} documentation for more information."
+msgstr ""
+
+msgid "Geo|All %{replicable_name}"
+msgstr ""
+
+msgid "Geo|All projects"
+msgstr ""
+
+msgid "Geo|All projects are being scheduled for resync"
+msgstr ""
+
+msgid "Geo|All projects are being scheduled for reverify"
+msgstr ""
+
+msgid "Geo|Could not remove tracking entry for an existing project."
+msgstr ""
+
+msgid "Geo|Could not remove tracking entry for an existing upload."
+msgstr ""
+
+msgid "Geo|Failed"
+msgstr ""
+
+msgid "Geo|Filter by status"
+msgstr ""
+
+msgid "Geo|Geo Status"
+msgstr ""
+
+msgid "Geo|In progress"
+msgstr ""
+
+msgid "Geo|In sync"
+msgstr ""
+
+msgid "Geo|Last repository check run"
+msgstr ""
+
+msgid "Geo|Last successful sync"
+msgstr ""
+
+msgid "Geo|Last sync attempt"
+msgstr ""
+
+msgid "Geo|Last time verified"
+msgstr ""
+
+msgid "Geo|Never"
+msgstr ""
+
+msgid "Geo|Next sync scheduled at"
+msgstr ""
+
+msgid "Geo|Node name can't be blank"
+msgstr ""
+
+msgid "Geo|Node name should be between 1 and 255 characters"
+msgstr ""
+
+msgid "Geo|Not synced yet"
+msgstr ""
+
+msgid "Geo|Pending synchronization"
+msgstr ""
+
+msgid "Geo|Pending verification"
+msgstr ""
+
+msgid "Geo|Please refer to Geo Troubleshooting."
+msgstr ""
+
+msgid "Geo|Project"
+msgstr ""
+
+msgid "Geo|Project (ID: %{project_id}) no longer exists on the primary. It is safe to remove this entry, as this will not remove any data on disk."
+msgstr ""
+
+msgid "Geo|Projects in certain groups"
+msgstr ""
+
+msgid "Geo|Projects in certain storage shards"
+msgstr ""
+
+msgid "Geo|Redownload"
+msgstr ""
+
+msgid "Geo|Remove"
+msgstr ""
+
+msgid "Geo|Remove entry"
+msgstr ""
+
+msgid "Geo|Remove tracking database entry"
+msgstr ""
+
+msgid "Geo|Resync"
+msgstr ""
+
+msgid "Geo|Resync all"
+msgstr ""
+
+msgid "Geo|Retry count"
+msgstr ""
+
+msgid "Geo|Reverify"
+msgstr ""
+
+msgid "Geo|Reverify all"
+msgstr ""
+
+msgid "Geo|Review replication status, and resynchronize and reverify items with the primary node."
+msgstr ""
+
+msgid "Geo|Status"
+msgstr ""
+
+msgid "Geo|Synced"
+msgstr ""
+
+msgid "Geo|Synced at"
+msgstr ""
+
+msgid "Geo|Synchronization failed - %{error}"
+msgstr ""
+
+msgid "Geo|Synchronization of %{itemTitle} is disabled."
+msgstr ""
+
+msgid "Geo|The database is currently %{db_lag} behind the primary node."
+msgstr ""
+
+msgid "Geo|The node is currently %{minutes_behind} behind the primary node."
+msgstr ""
+
+msgid "Geo|There are no %{replicable_type} to show"
+msgstr ""
+
+msgid "Geo|Tracking database entry will be removed. Are you sure?"
+msgstr ""
+
+msgid "Geo|Tracking entry for project (%{project_id}) was successfully removed."
+msgstr ""
+
+msgid "Geo|Tracking entry for upload (%{type}/%{id}) was successfully removed."
+msgstr ""
+
+msgid "Geo|URL can't be blank"
+msgstr ""
+
+msgid "Geo|URL must be a valid url (ex: https://gitlab.com)"
+msgstr ""
+
+msgid "Geo|Unknown state"
+msgstr ""
+
+msgid "Geo|Verification failed - %{error}"
+msgstr ""
+
+msgid "Geo|Waiting for scheduler"
+msgstr ""
+
+msgid "Geo|You are on a secondary, <b>read-only</b> Geo node. If you want to make changes, you must visit this page on the %{primary_node}."
+msgstr ""
+
+msgid "Geo|You are on a secondary, <b>read-only</b> Geo node. You may be able to make a limited amount of changes or perform a limited amount of actions on this page."
+msgstr ""
+
+msgid "Geo|misconfigured"
+msgstr ""
+
+msgid "Geo|primary"
+msgstr ""
+
+msgid "Geo|secondary"
+msgstr ""
+
+msgid "Get a free instance review"
+msgstr ""
+
+msgid "Get started"
+msgstr ""
+
+msgid "Get started with error tracking"
+msgstr ""
+
+msgid "Get started with performance monitoring"
+msgstr ""
+
+msgid "Get started!"
+msgstr ""
+
+msgid "Getting started with releases"
+msgstr ""
+
+msgid "Git"
+msgstr ""
+
+msgid "Git LFS is not enabled on this GitLab server, contact your admin."
+msgstr ""
+
+msgid "Git LFS objects will be synced in pull mirrors if LFS is %{docs_link_start}enabled for the project%{docs_link_end}. They will <strong>not</strong> be synced in push mirrors."
+msgstr ""
+
+msgid "Git global setup"
+msgstr ""
+
+msgid "Git repository URL"
+msgstr ""
+
+msgid "Git revision"
+msgstr ""
+
+msgid "Git shallow clone"
+msgstr ""
+
+msgid "Git strategy for pipelines"
+msgstr ""
+
+msgid "Git version"
+msgstr ""
+
+msgid "GitHub API rate limit exceeded. Try again after %{reset_time}"
+msgstr ""
+
+msgid "GitHub import"
+msgstr ""
+
+msgid "GitLab / Unsubscribe"
+msgstr ""
+
+msgid "GitLab Enterprise Edition %{plan}"
+msgstr ""
+
+msgid "GitLab Group Runners can execute code for all the projects in this group."
+msgstr ""
+
+msgid "GitLab Import"
+msgstr ""
+
+msgid "GitLab Issue"
+msgstr ""
+
+msgid "GitLab Shared Runners execute code of different projects on the same Runner unless you configure GitLab Runner Autoscale with MaxBuilds 1 (which it is on GitLab.com)."
+msgstr ""
+
+msgid "GitLab Support Bot"
+msgstr ""
+
+msgid "GitLab Team Member"
+msgstr ""
+
+msgid "GitLab User"
+msgstr ""
+
+msgid "GitLab allows you to continue using your license even if you exceed the number of seats you purchased. You will be required to pay for these seats when you renew your license."
+msgstr ""
+
+msgid "GitLab commit"
+msgstr ""
+
+msgid "GitLab for Slack"
+msgstr ""
+
+msgid "GitLab is a single application for the entire software development lifecycle. From project planning and source code management to CI/CD, monitoring, and security."
+msgstr ""
+
+msgid "GitLab is obtaining a Let's Encrypt SSL certificate for this domain. This process can take some time. Please try again later."
+msgstr ""
+
+msgid "GitLab is undergoing maintenance and is operating in a read-only mode."
+msgstr ""
+
+msgid "GitLab member or Email address"
+msgstr ""
+
+msgid "GitLab metadata URL"
+msgstr ""
+
+msgid "GitLab project export"
+msgstr ""
+
+msgid "GitLab restart is required to apply changes."
+msgstr ""
+
+msgid "GitLab single sign on URL"
+msgstr ""
+
+msgid "GitLab uses %{jaeger_link} to monitor distributed systems."
+msgstr ""
+
+msgid "GitLab will run a background job that will produce pseudonymized CSVs of the GitLab database that will be uploaded to your configured object storage directory."
+msgstr ""
+
+msgid "GitLab.com import"
+msgstr ""
+
+msgid "GitLabPagesDomains|Retry"
+msgstr ""
+
+msgid "GitLabPages|%{domain} is not verified. To learn how to verify ownership, visit your %{link_start}domain details%{link_end}."
+msgstr ""
+
+msgid "GitLabPages|Access Control is enabled for this Pages website; only authorized users will be able to access it. To make your website publicly available, navigate to your project's %{strong_start}Settings > General > Visibility%{strong_end} and select %{strong_start}Everyone%{strong_end} in pages section. Read the %{link_start}documentation%{link_end} for more information."
+msgstr ""
+
+msgid "GitLabPages|Access pages"
+msgstr ""
+
+msgid "GitLabPages|Are you sure?"
+msgstr ""
+
+msgid "GitLabPages|Certificate: %{subject}"
+msgstr ""
+
+msgid "GitLabPages|Configure pages"
+msgstr ""
+
+msgid "GitLabPages|Domains"
+msgstr ""
+
+msgid "GitLabPages|Edit"
+msgstr ""
+
+msgid "GitLabPages|Expired"
+msgstr ""
+
+msgid "GitLabPages|Force HTTPS (requires valid certificates)"
+msgstr ""
+
+msgid "GitLabPages|GitLab Pages are disabled for this project. You can enable them on your project's %{strong_start}Settings > General > Visibility%{strong_end} page."
+msgstr ""
+
+msgid "GitLabPages|It may take up to 30 minutes before the site is available after the first deployment."
+msgstr ""
+
+msgid "GitLabPages|Learn how to upload your static site and have it served by GitLab by following the %{link_start}documentation on GitLab Pages%{link_end}."
+msgstr ""
+
+msgid "GitLabPages|Learn more."
+msgstr ""
+
+msgid "GitLabPages|Maximum size of pages (MB)"
+msgstr ""
+
+msgid "GitLabPages|New Domain"
+msgstr ""
+
+msgid "GitLabPages|Only project maintainers can remove pages"
+msgstr ""
+
+msgid "GitLabPages|Pages"
+msgstr ""
+
+msgid "GitLabPages|Remove"
+msgstr ""
+
+msgid "GitLabPages|Remove pages"
+msgstr ""
+
+msgid "GitLabPages|Removing pages will prevent them from being exposed to the outside world."
+msgstr ""
+
+msgid "GitLabPages|Save"
+msgstr ""
+
+msgid "GitLabPages|Something went wrong while obtaining the Let's Encrypt certificate for %{domain}. To retry visit your %{link_start}domain details%{link_end}."
+msgstr ""
+
+msgid "GitLabPages|Support for domains and certificates is disabled. Ask your system's administrator to enable it."
+msgstr ""
+
+msgid "GitLabPages|The total size of deployed static content will be limited to this size. 0 for unlimited. Leave empty to inherit the global value."
+msgstr ""
+
+msgid "GitLabPages|Unverified"
+msgstr ""
+
+msgid "GitLabPages|Verified"
+msgstr ""
+
+msgid "GitLabPages|When using Pages under the general domain of a GitLab instance (%{pages_host}), you cannot use HTTPS with sub-subdomains. This means that if your username/groupname contains a dot it will not work. This is a limitation of the HTTP Over TLS protocol. HTTP pages will continue to work provided you don't redirect HTTP to HTTPS."
+msgstr ""
+
+msgid "GitLabPages|With GitLab Pages you can host your static websites on GitLab. Combined with the power of GitLab CI and the help of GitLab Runner you can deploy static pages for your individual projects, your user or your group."
+msgstr ""
+
+msgid "GitLabPages|Your pages are served under:"
+msgstr ""
+
+msgid "Gitaly"
+msgstr ""
+
+msgid "Gitaly Servers"
+msgstr ""
+
+msgid "Gitaly|Address"
+msgstr ""
+
+msgid "Gitea Host URL"
+msgstr ""
+
+msgid "Gitea Import"
+msgstr ""
+
+msgid "Gitlab Pages"
+msgstr ""
+
+msgid "Given access %{time_ago}"
+msgstr ""
+
+msgid "Given epic is already related to this epic."
+msgstr ""
+
+msgid "Global Shortcuts"
+msgstr ""
+
+msgid "Global notification settings"
+msgstr ""
+
+msgid "Go Back"
+msgstr ""
+
+msgid "Go Micro is a framework for micro service development."
+msgstr ""
+
+msgid "Go back"
+msgstr ""
+
+msgid "Go back (while searching for files)"
+msgstr ""
+
+msgid "Go back to %{startTag}Open issues%{endTag} and select some issues to add to your board."
+msgstr ""
+
+msgid "Go full screen"
+msgstr ""
+
+msgid "Go to %{link_to_google_takeout}."
+msgstr ""
+
+msgid "Go to <strong>Issues</strong> > <strong>Boards</strong> to access your personalized learning issue board."
+msgstr ""
+
+msgid "Go to Pipelines"
+msgstr ""
+
+msgid "Go to Webhooks"
+msgstr ""
+
+msgid "Go to commits"
+msgstr ""
+
+msgid "Go to definition"
+msgstr ""
+
+msgid "Go to environments"
+msgstr ""
+
+msgid "Go to file"
+msgstr ""
+
+msgid "Go to file permalink (while viewing a file)"
+msgstr ""
+
+msgid "Go to files"
+msgstr ""
+
+msgid "Go to find file"
+msgstr ""
+
+msgid "Go to issue boards"
+msgstr ""
+
+msgid "Go to issues"
+msgstr ""
+
+msgid "Go to jobs"
+msgstr ""
+
+msgid "Go to kubernetes"
+msgstr ""
+
+msgid "Go to merge requests"
+msgstr ""
+
+msgid "Go to metrics"
+msgstr ""
+
+msgid "Go to parent"
+msgstr ""
+
+msgid "Go to project"
+msgstr ""
+
+msgid "Go to releases"
+msgstr ""
+
+msgid "Go to repository charts"
+msgstr ""
+
+msgid "Go to repository graph"
+msgstr ""
+
+msgid "Go to snippets"
+msgstr ""
+
+msgid "Go to the activity feed"
+msgstr ""
+
+msgid "Go to the milestone list"
+msgstr ""
+
+msgid "Go to the project's activity feed"
+msgstr ""
+
+msgid "Go to the project's overview page"
+msgstr ""
+
+msgid "Go to wiki"
+msgstr ""
+
+msgid "Go to your To-Do list"
+msgstr ""
+
+msgid "Go to your fork"
+msgstr ""
+
+msgid "Go to your groups"
+msgstr ""
+
+msgid "Go to your issues"
+msgstr ""
+
+msgid "Go to your merge requests"
+msgstr ""
+
+msgid "Go to your projects"
+msgstr ""
+
+msgid "Go to your snippets"
+msgstr ""
+
+msgid "Google Cloud Platform"
+msgstr ""
+
+msgid "Google Code import"
+msgstr ""
+
+msgid "Google Takeout"
+msgstr ""
+
+msgid "Google authentication is not %{link_start}properly configured%{link_end}. Ask your GitLab administrator if you want to use this service."
+msgstr ""
+
+msgid "Got it"
+msgstr ""
+
+msgid "Got it!"
+msgstr ""
+
+msgid "Grafana URL"
+msgstr ""
+
+msgid "Grafana response contains invalid json"
+msgstr ""
+
+msgid "GrafanaIntegration|API Token"
+msgstr ""
+
+msgid "GrafanaIntegration|Active"
+msgstr ""
+
+msgid "GrafanaIntegration|Embed Grafana charts in GitLab issues."
+msgstr ""
+
+msgid "GrafanaIntegration|Enter the Grafana API Token."
+msgstr ""
+
+msgid "GrafanaIntegration|Enter the base URL of the Grafana instance."
+msgstr ""
+
+msgid "GrafanaIntegration|Grafana Authentication"
+msgstr ""
+
+msgid "GrafanaIntegration|Grafana URL"
+msgstr ""
+
+msgid "Grant access"
+msgstr ""
+
+msgid "Graph"
+msgstr ""
+
+msgid "Gravatar"
+msgstr ""
+
+msgid "Gravatar enabled"
+msgstr ""
+
+msgid "Group"
+msgstr ""
+
+msgid "Group %{group_name} couldn't be exported."
+msgstr ""
+
+msgid "Group %{group_name} was exported successfully."
+msgstr ""
+
+msgid "Group %{group_name} was scheduled for deletion."
+msgstr ""
+
+msgid "Group %{group_name} was successfully created."
+msgstr ""
+
+msgid "Group Audit Events"
+msgstr ""
+
+msgid "Group CI/CD settings"
+msgstr ""
+
+msgid "Group Git LFS status:"
+msgstr ""
+
+msgid "Group Hooks"
+msgstr ""
+
+msgid "Group ID"
+msgstr ""
+
+msgid "Group ID: %{group_id}"
+msgstr ""
+
+msgid "Group Owner must have signed in with SAML before enabling Group Managed Accounts"
+msgstr ""
+
+msgid "Group Runners"
+msgstr ""
+
+msgid "Group SAML must be enabled to test"
+msgstr ""
+
+msgid "Group URL"
+msgstr ""
+
+msgid "Group avatar"
+msgstr ""
+
+msgid "Group by:"
+msgstr ""
+
+msgid "Group description"
+msgstr ""
+
+msgid "Group description (optional)"
+msgstr ""
+
+msgid "Group details"
+msgstr ""
+
+msgid "Group export could not be started."
+msgstr ""
+
+msgid "Group export error"
+msgstr ""
+
+msgid "Group export link has expired. Please generate a new export from your group settings."
+msgstr ""
+
+msgid "Group export started. A download link will be sent by email and made available on this page."
+msgstr ""
+
+msgid "Group has been already marked for deletion"
+msgstr ""
+
+msgid "Group has not been marked for deletion"
+msgstr ""
+
+msgid "Group import could not be scheduled"
+msgstr ""
+
+msgid "Group info:"
+msgstr ""
+
+msgid "Group is required when cluster_type is :group"
+msgstr ""
+
+msgid "Group maintainers can register group runners in the %{link}"
+msgstr ""
+
+msgid "Group members"
+msgstr ""
+
+msgid "Group milestone"
+msgstr ""
+
+msgid "Group name"
+msgstr ""
+
+msgid "Group name (your organization)"
+msgstr ""
+
+msgid "Group overview"
+msgstr ""
+
+msgid "Group overview content"
+msgstr ""
+
+msgid "Group path is already taken. Suggestions: "
+msgstr ""
+
+msgid "Group path is available."
+msgstr ""
+
+msgid "Group pipeline minutes were successfully reset."
+msgstr ""
+
+msgid "Group project URLs are prefixed with the group namespace"
+msgstr ""
+
+msgid "Group requires separate account"
+msgstr ""
+
+msgid "Group variables (inherited)"
+msgstr ""
+
+msgid "Group was exported"
+msgstr ""
+
+msgid "Group was successfully updated."
+msgstr ""
+
+msgid "Group: %{group_name}"
+msgstr ""
+
+msgid "Group: %{name}"
+msgstr ""
+
+msgid "GroupActivityMetrics|New Members created"
+msgstr ""
+
+msgid "GroupActivyMetrics|Issues created"
+msgstr ""
+
+msgid "GroupActivyMetrics|Merge Requests created"
+msgstr ""
+
+msgid "GroupActivyMetrics|Recent activity (last 90 days)"
+msgstr ""
+
+msgid "GroupImport|Failed to import group."
+msgstr ""
+
+msgid "GroupImport|Group '%{group_name}' is being imported."
+msgstr ""
+
+msgid "GroupImport|Group could not be imported: %{errors}"
+msgstr ""
+
+msgid "GroupImport|Please wait while we import the group for you. Refresh at will."
+msgstr ""
+
+msgid "GroupImport|The group was successfully imported."
+msgstr ""
+
+msgid "GroupImport|Unable to process group import file"
+msgstr ""
+
+msgid "GroupRoadmap|%{dateWord} – No end date"
+msgstr ""
+
+msgid "GroupRoadmap|%{startDateInWords} – %{endDateInWords}"
+msgstr ""
+
+msgid "GroupRoadmap|No start date – %{dateWord}"
+msgstr ""
+
+msgid "GroupRoadmap|Something went wrong while fetching epics"
+msgstr ""
+
+msgid "GroupRoadmap|Something went wrong while fetching milestones"
+msgstr ""
+
+msgid "GroupRoadmap|Sorry, no epics matched your search"
+msgstr ""
+
+msgid "GroupRoadmap|The roadmap shows the progress of your epics along a timeline"
+msgstr ""
+
+msgid "GroupRoadmap|To view the roadmap, add a start or due date to one of the %{linkStart}child epics%{linkEnd}."
+msgstr ""
+
+msgid "GroupRoadmap|To view the roadmap, add a start or due date to one of your epics in this group or its subgroups; from %{startDate} to %{endDate}."
+msgstr ""
+
+msgid "GroupRoadmap|To widen your search, change or remove filters; from %{startDate} to %{endDate}."
+msgstr ""
+
+msgid "GroupSAML|Certificate fingerprint"
+msgstr ""
+
+msgid "GroupSAML|Configuration"
+msgstr ""
+
+msgid "GroupSAML|Copy SAML Response XML"
+msgstr ""
+
+msgid "GroupSAML|Enable SAML authentication for this group."
+msgstr ""
+
+msgid "GroupSAML|Enforce SSO-only authentication for this group."
+msgstr ""
+
+msgid "GroupSAML|Enforce users to have dedicated group managed accounts for this group."
+msgstr ""
+
+msgid "GroupSAML|Enforced SSO"
+msgstr ""
+
+msgid "GroupSAML|Generate a SCIM token"
+msgstr ""
+
+msgid "GroupSAML|Generate a SCIM token to set up your System for Cross-Domain Identity Management."
+msgstr ""
+
+msgid "GroupSAML|Identity"
+msgstr ""
+
+msgid "GroupSAML|Identity provider single sign on URL"
+msgstr ""
+
+msgid "GroupSAML|Make sure you save this token — you won't be able to access it again."
+msgstr ""
+
+msgid "GroupSAML|Manage your group’s membership while adding another level of security with SAML."
+msgstr ""
+
+msgid "GroupSAML|Members"
+msgstr ""
+
+msgid "GroupSAML|Members will be forwarded here when signing in to your group. Get this from your identity provider, where it can also be called \"SSO Service Location\", \"SAML Token Issuance Endpoint\", or \"SAML 2.0/W-Federation URL\"."
+msgstr ""
+
+msgid "GroupSAML|NameID"
+msgstr ""
+
+msgid "GroupSAML|NameID Format"
+msgstr ""
+
+msgid "GroupSAML|Prohibit outer forks"
+msgstr ""
+
+msgid "GroupSAML|Prohibit outer forks for this group."
+msgstr ""
+
+msgid "GroupSAML|SAML Response Output"
+msgstr ""
+
+msgid "GroupSAML|SAML Response XML"
+msgstr ""
+
+msgid "GroupSAML|SAML Single Sign On"
+msgstr ""
+
+msgid "GroupSAML|SAML Single Sign On Settings"
+msgstr ""
+
+msgid "GroupSAML|SCIM API endpoint URL"
+msgstr ""
+
+msgid "GroupSAML|SCIM Token"
+msgstr ""
+
+msgid "GroupSAML|SHA1 fingerprint of the SAML token signing certificate. Get this from your identity provider, where it can also be called \"Thumbprint\"."
+msgstr ""
+
+msgid "GroupSAML|The SCIM token is now hidden. To see the value of the token again, you need to "
+msgstr ""
+
+msgid "GroupSAML|To be able to enable enforced SSO, you first need to enable SAML authentication."
+msgstr ""
+
+msgid "GroupSAML|To be able to enable group managed accounts, you first need to enable enforced SSO."
+msgstr ""
+
+msgid "GroupSAML|To be able to prohibit outer forks, you first need to enforce dedicate group managed accounts."
+msgstr ""
+
+msgid "GroupSAML|Toggle SAML authentication"
+msgstr ""
+
+msgid "GroupSAML|Valid SAML Response"
+msgstr ""
+
+msgid "GroupSAML|With group managed accounts enabled, all the users without a group managed account will be excluded from the group."
+msgstr ""
+
+msgid "GroupSAML|With prohibit outer forks flag enabled group members will be able to fork project only inside your group."
+msgstr ""
+
+msgid "GroupSAML|Your SCIM token"
+msgstr ""
+
+msgid "GroupSAML|must match stored NameID of \"%{extern_uid}\" as we use this to identify users. If the NameID changes users will be unable to sign in."
+msgstr ""
+
+msgid "GroupSAML|should be \"persistent\""
+msgstr ""
+
+msgid "GroupSAML|should be a random persistent ID, emails are discouraged"
+msgstr ""
+
+msgid "GroupSettings|Apply integration settings to all Projects"
+msgstr ""
+
+msgid "GroupSettings|Auto DevOps pipeline was updated for the group"
+msgstr ""
+
+msgid "GroupSettings|Badges"
+msgstr ""
+
+msgid "GroupSettings|Be careful. Changing a group's parent can have unintended %{side_effects_link_start}side effects%{side_effects_link_end}."
+msgstr ""
+
+msgid "GroupSettings|Cannot update the path because there are projects under this group that contain Docker images in their Container Registry. Please remove the images from your projects first and try again."
+msgstr ""
+
+msgid "GroupSettings|Change group path"
+msgstr ""
+
+msgid "GroupSettings|Changing group path can have unintended side effects."
+msgstr ""
+
+msgid "GroupSettings|Custom project templates"
+msgstr ""
+
+msgid "GroupSettings|Customize your group badges."
+msgstr ""
+
+msgid "GroupSettings|Default to Auto DevOps pipeline for all projects within this group"
+msgstr ""
+
+msgid "GroupSettings|Disable email notifications"
+msgstr ""
+
+msgid "GroupSettings|Disable group mentions"
+msgstr ""
+
+msgid "GroupSettings|Export group"
+msgstr ""
+
+msgid "GroupSettings|If the parent group's visibility is lower than the group current visibility, visibility levels for subgroups and projects will be changed to match the new parent group's visibility."
+msgstr ""
+
+msgid "GroupSettings|Integrations configured here will automatically apply to all projects in this group."
+msgstr ""
+
+msgid "GroupSettings|Learn more about badges."
+msgstr ""
+
+msgid "GroupSettings|Learn more about group-level project templates."
+msgstr ""
+
+msgid "GroupSettings|New runners registration token has been generated!"
+msgstr ""
+
+msgid "GroupSettings|Pipeline settings was updated for the group"
+msgstr ""
+
+msgid "GroupSettings|Please choose a group path with no special characters."
+msgstr ""
+
+msgid "GroupSettings|Prevent sharing a project within %{group} with other groups"
+msgstr ""
+
+msgid "GroupSettings|Select a sub-group as the custom project template source for this group."
+msgstr ""
+
+msgid "GroupSettings|The Auto DevOps pipeline will run if no alternative CI configuration file is found."
+msgstr ""
+
+msgid "GroupSettings|There was a problem updating Auto DevOps pipeline: %{error_messages}."
+msgstr ""
+
+msgid "GroupSettings|There was a problem updating the pipeline settings: %{error_messages}."
+msgstr ""
+
+msgid "GroupSettings|This setting is applied on %{ancestor_group} and has been overridden on this subgroup."
+msgstr ""
+
+msgid "GroupSettings|This setting is applied on %{ancestor_group}. To share projects in this group with another group, ask the owner to override the setting or %{remove_ancestor_share_with_group_lock}."
+msgstr ""
+
+msgid "GroupSettings|This setting is applied on %{ancestor_group}. You can override the setting or %{remove_ancestor_share_with_group_lock}."
+msgstr ""
+
+msgid "GroupSettings|This setting will be applied to all subgroups unless overridden by a group owner. Groups that already have access to the project will continue to have access unless removed manually."
+msgstr ""
+
+msgid "GroupSettings|This setting will override user notification preferences for all members of the group, subgroups, and projects."
+msgstr ""
+
+msgid "GroupSettings|This setting will prevent group members from being notified if the group is mentioned."
+msgstr ""
+
+msgid "GroupSettings|Transfer group"
+msgstr ""
+
+msgid "GroupSettings|You can only transfer the group to a group you manage."
+msgstr ""
+
+msgid "GroupSettings|You will need to update your local repositories to point to the new location."
+msgstr ""
+
+msgid "GroupSettings|cannot be changed by you"
+msgstr ""
+
+msgid "GroupSettings|cannot be disabled when the parent group \"Share with group lock\" is enabled, except by the owner of the parent group"
+msgstr ""
+
+msgid "GroupSettings|cannot change when group contains projects with NPM packages"
+msgstr ""
+
+msgid "GroupSettings|remove the share with group lock from %{ancestor_group_name}"
+msgstr ""
+
+msgid "Groups"
+msgstr ""
+
+msgid "Groups (%{count})"
+msgstr ""
+
+msgid "Groups (%{groups})"
+msgstr ""
+
+msgid "Groups and projects"
+msgstr ""
+
+msgid "Groups can also be nested by creating %{subgroup_docs_link_start}subgroups%{subgroup_docs_link_end}."
+msgstr ""
+
+msgid "Groups to synchronize"
+msgstr ""
+
+msgid "Groups with access to %{strong_start}%{group_name}%{strong_end}"
+msgstr ""
+
+msgid "Groups with access to <strong>%{project_name}</strong>"
+msgstr ""
+
+msgid "GroupsDropdown|Frequently visited"
+msgstr ""
+
+msgid "GroupsDropdown|Groups you visit often will appear here"
+msgstr ""
+
+msgid "GroupsDropdown|Loading groups"
+msgstr ""
+
+msgid "GroupsDropdown|Search your groups"
+msgstr ""
+
+msgid "GroupsDropdown|Something went wrong on our end."
+msgstr ""
+
+msgid "GroupsDropdown|Sorry, no groups matched your search"
+msgstr ""
+
+msgid "GroupsDropdown|This feature requires browser localStorage support"
+msgstr ""
+
+msgid "GroupsEmptyState|A group is a collection of several projects."
+msgstr ""
+
+msgid "GroupsEmptyState|If you organize your projects under a group, it works like a folder."
+msgstr ""
+
+msgid "GroupsEmptyState|No groups found"
+msgstr ""
+
+msgid "GroupsEmptyState|You can manage your group member’s permissions and access to each project in the group."
+msgstr ""
+
+msgid "GroupsNew|Contact an administrator to enable options for importing your group."
+msgstr ""
+
+msgid "GroupsNew|Create"
+msgstr ""
+
+msgid "GroupsNew|Create group"
+msgstr ""
+
+msgid "GroupsNew|GitLab group export"
+msgstr ""
+
+msgid "GroupsNew|Import"
+msgstr ""
+
+msgid "GroupsNew|Import group"
+msgstr ""
+
+msgid "GroupsNew|My Awesome Group"
+msgstr ""
+
+msgid "GroupsNew|No import options available"
+msgstr ""
+
+msgid "GroupsNew|To copy a GitLab group between installations, navigate to the group settings page for the original installation, generate an export file, and upload it here."
+msgstr ""
+
+msgid "GroupsTree|Are you sure you want to leave the \"%{fullName}\" group?"
+msgstr ""
+
+msgid "GroupsTree|Create a project in this group."
+msgstr ""
+
+msgid "GroupsTree|Create a subgroup in this group."
+msgstr ""
+
+msgid "GroupsTree|Edit group"
+msgstr ""
+
+msgid "GroupsTree|Failed to leave the group. Please make sure you are not the only owner."
+msgstr ""
+
+msgid "GroupsTree|Leave this group"
+msgstr ""
+
+msgid "GroupsTree|Loading groups"
+msgstr ""
+
+msgid "GroupsTree|No groups matched your search"
+msgstr ""
+
+msgid "GroupsTree|No groups or projects matched your search"
+msgstr ""
+
+msgid "GroupsTree|Search by name"
+msgstr ""
+
+msgid "Guideline"
+msgstr ""
+
+msgid "HTTP Basic: Access denied\\nYou must use a personal access token with 'api' scope for Git over HTTP.\\nYou can generate one at %{profile_personal_access_tokens_url}"
+msgstr ""
+
+msgid "Hashed Storage must be enabled to use Geo"
+msgstr ""
+
+msgid "Hashed repository storage paths"
+msgstr ""
+
+msgid "Hashed storage can't be disabled anymore for new projects"
+msgstr ""
+
+msgid "Have your users email"
+msgstr ""
+
+msgid "Header logo was successfully removed."
+msgstr ""
+
+msgid "Header message"
+msgstr ""
+
+msgid "Headings"
+msgstr ""
+
+msgid "Health"
+msgstr ""
+
+msgid "Health Check"
+msgstr ""
+
+msgid "Health information can be retrieved from the following endpoints. More information is available"
+msgstr ""
+
+msgid "HealthCheck|Access token is"
+msgstr ""
+
+msgid "HealthCheck|Healthy"
+msgstr ""
+
+msgid "HealthCheck|No Health Problems Detected"
+msgstr ""
+
+msgid "HealthCheck|Unhealthy"
+msgstr ""
+
+msgid "Hello there"
+msgstr ""
+
+msgid "Help"
+msgstr ""
+
+msgid "Help page"
+msgstr ""
+
+msgid "Help page text and support page url."
+msgstr ""
+
+msgid "Helps prevent bots from brute-force attacks."
+msgstr ""
+
+msgid "Helps prevent bots from creating accounts."
+msgstr ""
+
+msgid "Helps reduce alert volume (e.g. if creating too many issues)"
+msgstr ""
+
+msgid "Helps reduce request volume for protected paths"
+msgstr ""
+
+msgid "Here are all your projects in your group, including the one you just created. To start, let’s take a look at your personalized learning project which will help you learn about GitLab at your own pace."
+msgstr ""
+
+msgid "Here you will find recent merge request activity"
+msgstr ""
+
+msgid "Hi %{username}!"
+msgstr ""
+
+msgid "Hide archived projects"
+msgstr ""
+
+msgid "Hide chart"
+msgid_plural "Hide charts"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Hide details"
+msgstr ""
+
+msgid "Hide file browser"
+msgstr ""
+
+msgid "Hide group projects"
+msgstr ""
+
+msgid "Hide host keys manual input"
+msgstr ""
+
+msgid "Hide list"
+msgstr ""
+
+msgid "Hide marketing-related entries from help"
+msgstr ""
+
+msgid "Hide payload"
+msgstr ""
+
+msgid "Hide shared projects"
+msgstr ""
+
+msgid "Hide stage"
+msgstr ""
+
+msgid "Hide value"
+msgid_plural "Hide values"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Hide values"
+msgstr ""
+
+msgid "High or unknown vulnerabilities present"
+msgstr ""
+
+msgid "Highest number of requests per minute for each raw path, default to 300. To disable throttling set to 0."
+msgstr ""
+
+msgid "Highest role:"
+msgstr ""
+
+msgid "History"
+msgstr ""
+
+msgid "History of authentications"
+msgstr ""
+
+msgid "Homepage"
+msgstr ""
+
+msgid "Hook execution failed. Ensure the group has a project with commits."
+msgstr ""
+
+msgid "Hook was successfully created."
+msgstr ""
+
+msgid "Hook was successfully updated."
+msgstr ""
+
+msgid "Hostname"
+msgstr ""
+
+msgid "Hour (UTC)"
+msgstr ""
+
+msgid "Housekeeping"
+msgstr ""
+
+msgid "Housekeeping successfully started"
+msgstr ""
+
+msgid "Housekeeping, export, path, transfer, remove, archive."
+msgstr ""
+
+msgid "How it works"
+msgstr ""
+
+msgid "How many days need to pass between marking entity for deletion and actual removing it."
+msgstr ""
+
+msgid "How many replicas each Elasticsearch shard has."
+msgstr ""
+
+msgid "How many shards to split the Elasticsearch index over."
+msgstr ""
+
+msgid "How many users will be evaluating the trial?"
+msgstr ""
+
+msgid "However, you are already a member of this %{member_source}. Sign in using a different account to accept the invitation."
+msgstr ""
+
+msgid "I accept the %{terms_link_start}Terms of Service and Privacy Policy%{terms_link_end}"
+msgstr ""
+
+msgid "I accept the %{terms_link}"
+msgstr ""
+
+msgid "I accept the|Terms of Service and Privacy Policy"
+msgstr ""
+
+msgid "I forgot my password"
+msgstr ""
+
+msgid "I have read and agree to the Let's Encrypt %{link_start}Terms of Service%{link_end} (PDF)"
+msgstr ""
+
+msgid "I'd like to receive updates via email about GitLab"
+msgstr ""
+
+msgid "ID"
+msgstr ""
+
+msgid "ID:"
+msgstr ""
+
+msgid "IDE"
+msgstr ""
+
+msgid "IDE|Allow live previews of JavaScript projects in the Web IDE using CodeSandbox Live Preview."
+msgstr ""
+
+msgid "IDE|Back"
+msgstr ""
+
+msgid "IDE|Commit"
+msgstr ""
+
+msgid "IDE|Commit to %{branchName} branch"
+msgstr ""
+
+msgid "IDE|Edit"
+msgstr ""
+
+msgid "IDE|Get started with Live Preview"
+msgstr ""
+
+msgid "IDE|Go to project"
+msgstr ""
+
+msgid "IDE|Live Preview"
+msgstr ""
+
+msgid "IDE|Preview your web application using Web IDE client-side evaluation."
+msgstr ""
+
+msgid "IDE|Refresh preview"
+msgstr ""
+
+msgid "IDE|Review"
+msgstr ""
+
+msgid "IDE|Successful commit"
+msgstr ""
+
+msgid "IDE|This option is disabled because you are not allowed to create merge requests in this project."
+msgstr ""
+
+msgid "IDE|This option is disabled because you don't have write permissions for the current branch."
+msgstr ""
+
+msgid "INFO: Your SSH key has expired. Please generate a new key."
+msgstr ""
+
+msgid "INFO: Your SSH key is expiring soon. Please generate a new key."
+msgstr ""
+
+msgid "IP Address"
+msgstr ""
+
+msgid "IP subnet restriction only allowed for top-level groups"
+msgstr ""
+
+msgid "Identifier"
+msgstr ""
+
+msgid "Identifiers"
+msgstr ""
+
+msgid "Identities"
+msgstr ""
+
+msgid "If any indexed field exceeds this limit it will be truncated to this number of characters and the rest will not be indexed or searchable. This does not apply to repository and wiki indexing. Setting this to 0 means it is unlimited."
+msgstr ""
+
+msgid "If any job surpasses this timeout threshold, it will be marked as failed. Human readable time input language is accepted like \"1 hour\". Values without specification represent seconds."
+msgstr ""
+
+msgid "If blank, set allowable lifetime to %{instance_level_policy_in_words}, as defined by the instance admin. Once set, existing tokens for users in this group may be revoked."
+msgstr ""
+
+msgid "If checked, group owners can manage LDAP group links and LDAP member overrides"
+msgstr ""
+
+msgid "If checked, new group memberships and permissions can only be added via LDAP synchronization"
+msgstr ""
+
+msgid "If disabled, a diverged local branch will not be automatically updated with commits from its remote counterpart, to prevent local data loss. If the default branch (%{default_branch}) has diverged and cannot be updated, mirroring will fail. Other diverged branches are silently ignored."
+msgstr ""
+
+msgid "If disabled, only admins will be able to configure repository mirroring."
+msgstr ""
+
+msgid "If disabled, the access level will depend on the user's permissions in the project."
+msgstr ""
+
+msgid "If enabled"
+msgstr ""
+
+msgid "If enabled, GitLab will handle Object Storage replication using Geo. %{linkStart}More information%{linkEnd}"
+msgstr ""
+
+msgid "If enabled, access to projects will be validated on an external service using their classification label."
+msgstr ""
+
+msgid "If there is no previous license or if the previous license has expired, some GitLab functionality will be blocked until a new, valid license is uploaded."
+msgstr ""
+
+msgid "If this was a mistake you can %{leave_link_start}leave the %{source_type}%{link_end}."
+msgstr ""
+
+msgid "If this was a mistake you can leave the %{source_type}."
+msgstr ""
+
+msgid "If using GitHub, you’ll see pipeline statuses on GitHub for your commits and pull requests. %{more_info_link}"
+msgstr ""
+
+msgid "If you did not recently sign in, you should immediately %{password_link_start}change your password%{password_link_end}."
+msgstr ""
+
+msgid "If you did not recently sign in, you should immediately change your password: %{password_link}."
+msgstr ""
+
+msgid "If you lose your recovery codes you can generate new ones, invalidating all previous codes."
+msgstr ""
+
+msgid "If you reach 100%% storage capacity, you will not be able to: %{base_message}"
+msgstr ""
+
+msgid "If you recently signed in and recognize the IP address, you may disregard this email."
+msgstr ""
+
+msgid "If you remove this license, GitLab will fall back on the previous license, if any."
+msgstr ""
+
+msgid "If your HTTP repository is not publicly accessible, add your credentials."
+msgstr ""
+
+msgid "Iglu registry URL (optional)"
+msgstr ""
+
+msgid "Ignore"
+msgstr ""
+
+msgid "Ignored"
+msgstr ""
+
+msgid "Image Details"
+msgstr ""
+
+msgid "Image URL"
+msgstr ""
+
+msgid "ImageDiffViewer|2-up"
+msgstr ""
+
+msgid "ImageDiffViewer|Onion skin"
+msgstr ""
+
+msgid "ImageDiffViewer|Swipe"
+msgstr ""
+
+msgid "ImageViewerDimensions|H"
+msgstr ""
+
+msgid "ImageViewerDimensions|W"
+msgstr ""
+
+msgid "Impersonation Tokens"
+msgstr ""
+
+msgid "Impersonation has been disabled"
+msgstr ""
+
+msgid "Import"
+msgstr ""
+
+msgid "Import CSV"
+msgstr ""
+
+msgid "Import Projects from Gitea"
+msgstr ""
+
+msgid "Import all compatible projects"
+msgstr ""
+
+msgid "Import all compatible repositories"
+msgstr ""
+
+msgid "Import all projects"
+msgstr ""
+
+msgid "Import all repositories"
+msgstr ""
+
+msgid "Import an exported GitLab project"
+msgstr ""
+
+msgid "Import failed due to a GitHub error: %{original}"
+msgstr ""
+
+msgid "Import from"
+msgstr ""
+
+msgid "Import from Jira"
+msgstr ""
+
+msgid "Import in progress"
+msgstr ""
+
+msgid "Import in progress. Refresh page to see newly added issues."
+msgstr ""
+
+msgid "Import issues"
+msgstr ""
+
+msgid "Import members"
+msgstr ""
+
+msgid "Import members from another project"
+msgstr ""
+
+msgid "Import multiple repositories by uploading a manifest file."
+msgstr ""
+
+msgid "Import project"
+msgstr ""
+
+msgid "Import project members"
+msgstr ""
+
+msgid "Import projects from Bitbucket"
+msgstr ""
+
+msgid "Import projects from Bitbucket Server"
+msgstr ""
+
+msgid "Import projects from FogBugz"
+msgstr ""
+
+msgid "Import projects from GitLab.com"
+msgstr ""
+
+msgid "Import projects from Google Code"
+msgstr ""
+
+msgid "Import repositories from Bitbucket Server"
+msgstr ""
+
+msgid "Import repositories from GitHub"
+msgstr ""
+
+msgid "Import repository"
+msgstr ""
+
+msgid "Import started by: %{importInitiator}"
+msgstr ""
+
+msgid "Import tasks"
+msgstr ""
+
+msgid "Import tasks from Phabricator into issues"
+msgstr ""
+
+msgid "Import timed out. Import took longer than %{import_jobs_expiration} seconds"
+msgstr ""
+
+msgid "Import/Export illustration"
+msgstr ""
+
+msgid "ImportButtons|Connect repositories from"
+msgstr ""
+
+msgid "ImportProjects|Blocked import URL: %{message}"
+msgstr ""
+
+msgid "ImportProjects|Error importing repository %{project_safe_import_url} into %{project_full_path} - %{message}"
+msgstr ""
+
+msgid "ImportProjects|Importing the project failed"
+msgstr ""
+
+msgid "ImportProjects|Requesting your %{provider} repositories failed"
+msgstr ""
+
+msgid "ImportProjects|Select the projects you want to import"
+msgstr ""
+
+msgid "ImportProjects|The remote data could not be imported."
+msgstr ""
+
+msgid "ImportProjects|The repository could not be created."
+msgstr ""
+
+msgid "ImportProjects|Update of imported projects with realtime changes failed"
+msgstr ""
+
+msgid "Improve Issue boards"
+msgstr ""
+
+msgid "Improve Issue boards with GitLab Enterprise Edition."
+msgstr ""
+
+msgid "Improve Merge Requests and customer support with GitLab Enterprise Edition."
+msgstr ""
+
+msgid "Improve search with Advanced Global Search and GitLab Enterprise Edition."
+msgstr ""
+
+msgid "In %{time_to_now}"
+msgstr ""
+
+msgid "In order to enable instance-level analytics, please ask an admin to enable %{usage_ping_link_start}usage ping%{usage_ping_link_end}."
+msgstr ""
+
+msgid "In order to gather accurate feature usage data, it can take 1 to 2 weeks to see your index."
+msgstr ""
+
+msgid "In order to personalize your experience with GitLab<br>we would like to know a bit more about you."
+msgstr ""
+
+msgid "In order to tailor your experience with GitLab we<br>would like to know a bit more about you."
+msgstr ""
+
+msgid "In progress"
+msgstr ""
+
+msgid "In the next step, you'll be able to select the projects you want to import."
+msgstr ""
+
+msgid "Incident Management Limits"
+msgstr ""
+
+msgid "Incidents"
+msgstr ""
+
+msgid "Include a Terms of Service agreement and Privacy Policy that all users must accept."
+msgstr ""
+
+msgid "Include author name in notification email body"
+msgstr ""
+
+msgid "Include description in commit message"
+msgstr ""
+
+msgid "Include merge request description"
+msgstr ""
+
+msgid "Include the username in the URL if required: <code>https://username@gitlab.company.com/group/project.git</code>."
+msgstr ""
+
+msgid "Includes LFS objects. It can be overridden per group, or per project. 0 for unlimited."
+msgstr ""
+
+msgid "Includes an MVC structure to help you get started."
+msgstr ""
+
+msgid "Includes an MVC structure, Gemfile, Rakefile, along with many others, to help you get started."
+msgstr ""
+
+msgid "Includes an MVC structure, mvnw and pom.xml to help you get started."
+msgstr ""
+
+msgid "Includes repository storage, wiki storage, LFS objects, build artifacts and packages. 0 for unlimited."
+msgstr ""
+
+msgid "Incoming email"
+msgstr ""
+
+msgid "Incoming!"
+msgstr ""
+
+msgid "Incompatible Project"
+msgstr ""
+
+msgid "Incompatible options set!"
+msgstr ""
+
+msgid "Incompatible project"
+msgstr ""
+
+msgid "Indent"
+msgstr ""
+
+msgid "Index all projects"
+msgstr ""
+
+msgid "Indicates whether this runner can pick jobs without tags"
+msgstr ""
+
+msgid "Inform users without uploaded SSH keys that they can't push over SSH until one is added"
+msgstr ""
+
+msgid "Information about additional Pages templates and how to install them can be found in our %{pages_getting_started_guide}."
+msgstr ""
+
+msgid "Inherited:"
+msgstr ""
+
+msgid "Inline"
+msgstr ""
+
+msgid "Input host keys manually"
+msgstr ""
+
+msgid "Input your repository URL"
+msgstr ""
+
+msgid "Insert"
+msgstr ""
+
+msgid "Insert a code block"
+msgstr ""
+
+msgid "Insert a quote"
+msgstr ""
+
+msgid "Insert an image"
+msgstr ""
+
+msgid "Insert code"
+msgstr ""
+
+msgid "Insert inline code"
+msgstr ""
+
+msgid "Insert suggestion"
+msgstr ""
+
+msgid "Insights"
+msgstr ""
+
+msgid "Insights|Some items are not visible beacuse the project was filtered out in the insights.yml file (see the projects.only config for more information)."
+msgstr ""
+
+msgid "Insights|This project is filtered out in the insights.yml file (see the projects.only config for more information)."
+msgstr ""
+
+msgid "Install"
+msgstr ""
+
+msgid "Install GitLab Runner"
+msgstr ""
+
+msgid "Install Runner on Kubernetes"
+msgstr ""
+
+msgid "Install a soft token authenticator like %{free_otp_link} or Google Authenticator from your application repository and use that app to scan this QR code. More information is available in the %{help_link_start}documentation%{help_link_end}."
+msgstr ""
+
+msgid "Install on clusters"
+msgstr ""
+
+msgid "Installed"
+msgstr ""
+
+msgid "Installing"
+msgstr ""
+
+msgid "Instance"
+msgid_plural "Instances"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Instance Configuration"
+msgstr ""
+
+msgid "Instance Statistics visibility"
+msgstr ""
+
+msgid "Instance administrators group already exists"
+msgstr ""
+
+msgid "Instance does not support multiple Kubernetes clusters"
+msgstr ""
+
+msgid "Instance license"
+msgstr ""
+
+msgid "Integration"
+msgstr ""
+
+msgid "Integration Settings"
+msgstr ""
+
+msgid "Integrations"
+msgstr ""
+
+msgid "Integrations|All details"
+msgstr ""
+
+msgid "Integrations|Comment detail:"
+msgstr ""
+
+msgid "Integrations|Comment settings:"
+msgstr ""
+
+msgid "Integrations|Enable comments"
+msgstr ""
+
+msgid "Integrations|Includes Standard plus entire commit message, commit hash, and issue IDs"
+msgstr ""
+
+msgid "Integrations|Includes commit title and branch"
+msgstr ""
+
+msgid "Integrations|Standard"
+msgstr ""
+
+msgid "Integrations|When a Jira issue is mentioned in a commit or merge request a remote link and comment (if enabled) will be created."
+msgstr ""
+
+msgid "Interested parties can even contribute by pushing commits if they want to."
+msgstr ""
+
+msgid "Internal"
+msgstr ""
+
+msgid "Internal - The group and any internal projects can be viewed by any logged in user."
+msgstr ""
+
+msgid "Internal - The project can be accessed by any logged in user."
+msgstr ""
+
+msgid "Internal URL (optional)"
+msgstr ""
+
+msgid "Internal users"
+msgstr ""
+
+msgid "Interval Pattern"
+msgstr ""
+
+msgid "Introducing Value Stream Analytics"
+msgstr ""
+
+msgid "Introducing Your DevOps Score"
+msgstr ""
+
+msgid "Invalid Git ref"
+msgstr ""
+
+msgid "Invalid Insights config file detected"
+msgstr ""
+
+msgid "Invalid Login or password"
+msgstr ""
+
+msgid "Invalid URL"
+msgstr ""
+
+msgid "Invalid container_name"
+msgstr ""
+
+msgid "Invalid cursor parameter"
+msgstr ""
+
+msgid "Invalid cursor value provided"
+msgstr ""
+
+msgid "Invalid date"
+msgstr ""
+
+msgid "Invalid date format. Please use UTC format as YYYY-MM-DD"
+msgstr ""
+
+msgid "Invalid date range"
+msgstr ""
+
+msgid "Invalid feature"
+msgstr ""
+
+msgid "Invalid field"
+msgstr ""
+
+msgid "Invalid file format with specified file type"
+msgstr ""
+
+msgid "Invalid file."
+msgstr ""
+
+msgid "Invalid import params"
+msgstr ""
+
+msgid "Invalid input, please avoid emojis"
+msgstr ""
+
+msgid "Invalid login or password"
+msgstr ""
+
+msgid "Invalid pin code"
+msgstr ""
+
+msgid "Invalid pod_name"
+msgstr ""
+
+msgid "Invalid query"
+msgstr ""
+
+msgid "Invalid repository bundle for snippet with id %{snippet_id}"
+msgstr ""
+
+msgid "Invalid repository path"
+msgstr ""
+
+msgid "Invalid search parameter"
+msgstr ""
+
+msgid "Invalid server response"
+msgstr ""
+
+msgid "Invalid start or end time format"
+msgstr ""
+
+msgid "Invalid status"
+msgstr ""
+
+msgid "Invalid two-factor code."
+msgstr ""
+
+msgid "Invalid yaml"
+msgstr ""
+
+msgid "Invitation"
+msgstr ""
+
+msgid "Invite"
+msgstr ""
+
+msgid "Invite \"%{trimmed}\" by email"
+msgstr ""
+
+msgid "Invite Members"
+msgstr ""
+
+msgid "Invite group"
+msgstr ""
+
+msgid "Invite member"
+msgstr ""
+
+msgid "Invocations"
+msgstr ""
+
+msgid "Is blocked by"
+msgstr ""
+
+msgid "Is this GitLab trial for your company?"
+msgstr ""
+
+msgid "Is using license seat:"
+msgstr ""
+
+msgid "Is using seat"
+msgstr ""
+
+msgid "IssuableStatus|Closed"
+msgstr ""
+
+msgid "IssuableStatus|Closed (%{link})"
+msgstr ""
+
+msgid "IssuableStatus|duplicated"
+msgstr ""
+
+msgid "IssuableStatus|moved"
+msgstr ""
+
+msgid "IssuableStatus|promoted"
+msgstr ""
+
+msgid "Issue"
+msgstr ""
+
+msgid "Issue %{issue_reference} has already been added to epic %{epic_reference}."
+msgstr ""
+
+msgid "Issue Boards"
+msgstr ""
+
+msgid "Issue already promoted to epic."
+msgstr ""
+
+msgid "Issue cannot be found."
+msgstr ""
+
+msgid "Issue events"
+msgstr ""
+
+msgid "Issue first depoloyed to production"
+msgstr ""
+
+msgid "Issue label"
+msgstr ""
+
+msgid "Issue or Merge Request ID is required"
+msgstr ""
+
+msgid "Issue published on status page."
+msgstr ""
+
+msgid "Issue template (optional)"
+msgstr ""
+
+msgid "Issue update failed"
+msgstr ""
+
+msgid "Issue was closed by %{name} %{reason}"
+msgstr ""
+
+msgid "Issue weight"
+msgstr ""
+
+msgid "IssueAnalytics|Age"
+msgstr ""
+
+msgid "IssueAnalytics|Assignees"
+msgstr ""
+
+msgid "IssueAnalytics|Due date"
+msgstr ""
+
+msgid "IssueAnalytics|Failed to load issues. Please try again."
+msgstr ""
+
+msgid "IssueAnalytics|Issue"
+msgstr ""
+
+msgid "IssueAnalytics|Milestone"
+msgstr ""
+
+msgid "IssueAnalytics|Opened by"
+msgstr ""
+
+msgid "IssueAnalytics|Status"
+msgstr ""
+
+msgid "IssueAnalytics|Weight"
+msgstr ""
+
+msgid "IssueBoards|Board"
+msgstr ""
+
+msgid "IssueBoards|Boards"
+msgstr ""
+
+msgid "IssueBoards|Create new board"
+msgstr ""
+
+msgid "IssueBoards|Delete board"
+msgstr ""
+
+msgid "IssueBoards|No matching boards found"
+msgstr ""
+
+msgid "IssueBoards|Some of your boards are hidden, activate a license to see them again."
+msgstr ""
+
+msgid "IssueBoards|Switch board"
+msgstr ""
+
+msgid "IssueTracker|Bugzilla issue tracker"
+msgstr ""
+
+msgid "IssueTracker|Custom issue tracker"
+msgstr ""
+
+msgid "IssueTracker|GitLab issue tracker"
+msgstr ""
+
+msgid "IssueTracker|Redmine issue tracker"
+msgstr ""
+
+msgid "IssueTracker|YouTrack issue tracker"
+msgstr ""
+
+msgid "Issues"
+msgstr ""
+
+msgid "Issues Analytics"
+msgstr ""
+
+msgid "Issues Rate Limits"
+msgstr ""
+
+msgid "Issues and Merge Requests"
+msgstr ""
+
+msgid "Issues can be bugs, tasks or ideas to be discussed. Also, issues are searchable and filterable."
+msgstr ""
+
+msgid "Issues closed"
+msgstr ""
+
+msgid "Issues referenced by merge requests and commits within the default branch will be closed automatically"
+msgstr ""
+
+msgid "Issues successfully imported with the label"
+msgstr ""
+
+msgid "Issues with comments, merge requests with diffs and comments, labels, milestones, snippets, and other project entities"
+msgstr ""
+
+msgid "Issues, merge requests, pushes, and comments."
+msgstr ""
+
+msgid "IssuesAnalytics|After you begin creating issues for your projects, we can start tracking and displaying metrics for them"
+msgstr ""
+
+msgid "IssuesAnalytics|Avg/Month:"
+msgstr ""
+
+msgid "IssuesAnalytics|Issues opened"
+msgstr ""
+
+msgid "IssuesAnalytics|Issues opened per month"
+msgstr ""
+
+msgid "IssuesAnalytics|Last 12 months"
+msgstr ""
+
+msgid "IssuesAnalytics|Sorry, your filter produced no results"
+msgstr ""
+
+msgid "IssuesAnalytics|There are no issues for the projects in your group"
+msgstr ""
+
+msgid "IssuesAnalytics|To widen your search, change or remove filters in the filter bar above"
+msgstr ""
+
+msgid "IssuesAnalytics|Total:"
+msgstr ""
+
+msgid "Issue|Title"
+msgstr ""
+
+msgid "It must have a header row and at least two columns: the first column is the issue title and the second column is the issue description. The separator is automatically detected."
+msgstr ""
+
+msgid "It seems like the Dependency Scanning job ran successfully, but no dependencies have been detected in your project."
+msgstr ""
+
+msgid "It seems that there is currently no available data for code coverage"
+msgstr ""
+
+msgid "It's you"
+msgstr ""
+
+msgid "Iteration"
+msgstr ""
+
+msgid "Iteration changed to"
+msgstr ""
+
+msgid "Iteration removed"
+msgstr ""
+
+msgid "Iteration updated"
+msgstr ""
+
+msgid "Iterations"
+msgstr ""
+
+msgid "Iteration|Dates cannot overlap with other existing Iterations"
+msgstr ""
+
+msgid "Iteration|cannot be in the past"
+msgstr ""
+
+msgid "Iteration|cannot be more than 500 years in the future"
+msgstr ""
+
+msgid "I’m familiar with the basics of project management and DevOps."
+msgstr ""
+
+msgid "I’m not very familiar with the basics of project management and DevOps."
+msgstr ""
+
+msgid "Jaeger URL"
+msgstr ""
+
+msgid "Jaeger tracing"
+msgstr ""
+
+msgid "Jan"
+msgstr ""
+
+msgid "January"
+msgstr ""
+
+msgid "Jira Issues"
+msgstr ""
+
+msgid "Jira import is already running."
+msgstr ""
+
+msgid "Jira integration not configured."
+msgstr ""
+
+msgid "Jira project key is not configured"
+msgstr ""
+
+msgid "Jira project: %{importProject}"
+msgstr ""
+
+msgid "Jira service not configured."
+msgstr ""
+
+msgid "JiraService| on branch %{branch_link}"
+msgstr ""
+
+msgid "JiraService|%{user_link} mentioned this issue in %{entity_link} of %{project_link}%{branch}:{quote}%{entity_message}{quote}"
+msgstr ""
+
+msgid "JiraService|Events for %{noteable_model_name} are disabled."
+msgstr ""
+
+msgid "JiraService|If different from Web URL"
+msgstr ""
+
+msgid "JiraService|Jira API URL"
+msgstr ""
+
+msgid "JiraService|Jira comments will be created when an issue gets referenced in a commit."
+msgstr ""
+
+msgid "JiraService|Jira comments will be created when an issue gets referenced in a merge request."
+msgstr ""
+
+msgid "JiraService|Jira issue tracker"
+msgstr ""
+
+msgid "JiraService|Password or API token"
+msgstr ""
+
+msgid "JiraService|Transition ID(s)"
+msgstr ""
+
+msgid "JiraService|Use , or ; to separate multiple transition IDs"
+msgstr ""
+
+msgid "JiraService|Use a password for server version and an API token for cloud version"
+msgstr ""
+
+msgid "JiraService|Use a username for server version and an email for cloud version"
+msgstr ""
+
+msgid "JiraService|Username or Email"
+msgstr ""
+
+msgid "JiraService|Web URL"
+msgstr ""
+
+msgid "JiraService|transition ids can have only numbers which can be split with , or ;"
+msgstr ""
+
+msgid "Job"
+msgstr ""
+
+msgid "Job Failed #%{build_id}"
+msgstr ""
+
+msgid "Job ID"
+msgstr ""
+
+msgid "Job has been erased"
+msgstr ""
+
+msgid "Job has been successfully erased!"
+msgstr ""
+
+msgid "Job has wrong arguments format."
+msgstr ""
+
+msgid "Job is missing the `model_type` argument."
+msgstr ""
+
+msgid "Job is stuck. Check runners."
+msgstr ""
+
+msgid "Job logs and artifacts"
+msgstr ""
+
+msgid "Job to create self-monitoring project is in progress"
+msgstr ""
+
+msgid "Job to delete self-monitoring project is in progress"
+msgstr ""
+
+msgid "Job was retried"
+msgstr ""
+
+msgid "Jobs"
+msgstr ""
+
+msgid "Job|Browse"
+msgstr ""
+
+msgid "Job|Complete Raw"
+msgstr ""
+
+msgid "Job|Download"
+msgstr ""
+
+msgid "Job|Erase job log"
+msgstr ""
+
+msgid "Job|Job artifacts"
+msgstr ""
+
+msgid "Job|Job has been erased"
+msgstr ""
+
+msgid "Job|Job has been erased by"
+msgstr ""
+
+msgid "Job|Keep"
+msgstr ""
+
+msgid "Job|Pipeline"
+msgstr ""
+
+msgid "Job|Scroll to bottom"
+msgstr ""
+
+msgid "Job|Scroll to top"
+msgstr ""
+
+msgid "Job|Show complete raw"
+msgstr ""
+
+msgid "Job|The artifacts were removed"
+msgstr ""
+
+msgid "Job|The artifacts will be removed"
+msgstr ""
+
+msgid "Job|These artifacts are the latest. They will not be deleted (even if expired) until newer artifacts are available."
+msgstr ""
+
+msgid "Job|This job failed because the necessary resources were not successfully created."
+msgstr ""
+
+msgid "Job|This job is stuck because the project doesn't have any runners online assigned to it."
+msgstr ""
+
+msgid "Job|for"
+msgstr ""
+
+msgid "Job|into"
+msgstr ""
+
+msgid "Job|with"
+msgstr ""
+
+msgid "Join Zoom meeting"
+msgstr ""
+
+msgid "Jul"
+msgstr ""
+
+msgid "July"
+msgstr ""
+
+msgid "Jump to first unresolved thread"
+msgstr ""
+
+msgid "Jump to next unresolved thread"
+msgstr ""
+
+msgid "Jun"
+msgstr ""
+
+msgid "June"
+msgstr ""
+
+msgid "Just me"
+msgstr ""
+
+msgid "Keep divergent refs"
+msgstr ""
+
+msgid "Kerberos access denied"
+msgstr ""
+
+msgid "Key"
+msgstr ""
+
+msgid "Key (PEM)"
+msgstr ""
+
+msgid "Key: %{key}"
+msgstr ""
+
+msgid "Keyboard shortcuts"
+msgstr ""
+
+msgid "Keys"
+msgstr ""
+
+msgid "Ki"
+msgstr ""
+
+msgid "Kubernetes"
+msgstr ""
+
+msgid "Kubernetes API returned status code: %{error_code}"
+msgstr ""
+
+msgid "Kubernetes Cluster"
+msgstr ""
+
+msgid "Kubernetes Clusters"
+msgstr ""
+
+msgid "Kubernetes cluster"
+msgstr ""
+
+msgid "Kubernetes cluster creation time exceeds timeout; %{timeout}"
+msgstr ""
+
+msgid "Kubernetes cluster integration and resources are being removed."
+msgstr ""
+
+msgid "Kubernetes cluster integration was successfully removed."
+msgstr ""
+
+msgid "Kubernetes cluster was successfully updated."
+msgstr ""
+
+msgid "Kubernetes deployment not found"
+msgstr ""
+
+msgid "Kubernetes error: %{error_code}"
+msgstr ""
+
+msgid "Kubernetes popover"
+msgstr ""
+
+msgid "LDAP"
+msgstr ""
+
+msgid "LDAP Synchronization"
+msgstr ""
+
+msgid "LDAP settings"
+msgstr ""
+
+msgid "LDAP settings updated"
+msgstr ""
+
+msgid "LDAP sync in progress. This could take a few minutes. Refresh the page to see the changes."
+msgstr ""
+
+msgid "LFS"
+msgstr ""
+
+msgid "LFS objects"
+msgstr ""
+
+msgid "LFSStatus|Disabled"
+msgstr ""
+
+msgid "LFSStatus|Enabled"
+msgstr ""
+
+msgid "LICENSE"
+msgstr ""
+
+msgid "Label"
+msgstr ""
+
+msgid "Label actions dropdown"
+msgstr ""
+
+msgid "Label lists show all issues with the selected label."
+msgstr ""
+
+msgid "Label was created"
+msgstr ""
+
+msgid "Label was removed"
+msgstr ""
+
+msgid "Label was successfully updated."
+msgstr ""
+
+msgid "LabelSelect|%{firstLabelName} +%{remainingLabelCount} more"
+msgstr ""
+
+msgid "LabelSelect|%{labelsString}, and %{remainingLabelCount} more"
+msgstr ""
+
+msgid "LabelSelect|Labels"
+msgstr ""
+
+msgid "Labels"
+msgstr ""
+
+msgid "Labels can be applied to %{features}. Group labels are available for any project within the group."
+msgstr ""
+
+msgid "Labels can be applied to issues and merge requests to categorize them."
+msgstr ""
+
+msgid "Labels can be applied to issues and merge requests."
+msgstr ""
+
+msgid "Labels|<span>Promote label</span> %{labelTitle} <span>to Group Label?</span>"
+msgstr ""
+
+msgid "Labels|Promote Label"
+msgstr ""
+
+msgid "Labels|Promoting %{labelTitle} will make it available for all projects inside %{groupName}. Existing project labels with the same title will be merged. If a group label with the same title exists, it will also be merged. This action cannot be reversed."
+msgstr ""
+
+msgid "Labels|and %{count} more"
+msgstr ""
+
+msgid "Language"
+msgstr ""
+
+msgid "Large File Storage"
+msgstr ""
+
+msgid "Last %d day"
+msgid_plural "Last %d days"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Last %{days} days"
+msgstr ""
+
+msgid "Last Accessed On"
+msgstr ""
+
+msgid "Last Name is too long (maximum is %{max_length} characters)."
+msgstr ""
+
+msgid "Last Pipeline"
+msgstr ""
+
+msgid "Last Seen"
+msgstr ""
+
+msgid "Last accessed on"
+msgstr ""
+
+msgid "Last activity"
+msgstr ""
+
+msgid "Last commit"
+msgstr ""
+
+msgid "Last contact"
+msgstr ""
+
+msgid "Last edited %{date}"
+msgstr ""
+
+msgid "Last edited by %{name}"
+msgstr ""
+
+msgid "Last name"
+msgstr ""
+
+msgid "Last reply by"
+msgstr ""
+
+msgid "Last repository check (%{last_check_timestamp}) failed. See the 'repocheck.log' file for error messages."
+msgstr ""
+
+msgid "Last repository check run"
+msgstr ""
+
+msgid "Last seen"
+msgstr ""
+
+msgid "Last successful sync"
+msgstr ""
+
+msgid "Last successful update"
+msgstr ""
+
+msgid "Last time verified"
+msgstr ""
+
+msgid "Last update"
+msgstr ""
+
+msgid "Last update attempt"
+msgstr ""
+
+msgid "Last updated"
+msgstr ""
+
+msgid "Last used"
+msgstr ""
+
+msgid "Last used on:"
+msgstr ""
+
+msgid "LastCommit|authored"
+msgstr ""
+
+msgid "LastPushEvent|You pushed to"
+msgstr ""
+
+msgid "LastPushEvent|at"
+msgstr ""
+
+msgid "Latest changes"
+msgstr ""
+
+msgid "Latest pipeline for the most recent commit on this branch"
+msgstr ""
+
+msgid "Lead"
+msgstr ""
+
+msgid "Lead Time"
+msgstr ""
+
+msgid "Learn GitLab"
+msgstr ""
+
+msgid "Learn More"
+msgstr ""
+
+msgid "Learn how to %{link_start}contribute to the built-in templates%{link_end}"
+msgstr ""
+
+msgid "Learn how to %{no_packages_link_start}publish and share your packages%{no_packages_link_end} with GitLab."
+msgstr ""
+
+msgid "Learn how to enable synchronization"
+msgstr ""
+
+msgid "Learn more"
+msgstr ""
+
+msgid "Learn more about Auto DevOps"
+msgstr ""
+
+msgid "Learn more about Kubernetes"
+msgstr ""
+
+msgid "Learn more about License-Check"
+msgstr ""
+
+msgid "Learn more about Vulnerability-Check"
+msgstr ""
+
+msgid "Learn more about Web Terminal"
+msgstr ""
+
+msgid "Learn more about X.509 signed commits"
+msgstr ""
+
+msgid "Learn more about adding certificates to your project by following the %{docs_link_start}documentation on GitLab Pages%{docs_link_end}."
+msgstr ""
+
+msgid "Learn more about approvals."
+msgstr ""
+
+msgid "Learn more about custom project templates"
+msgstr ""
+
+msgid "Learn more about deploying to AWS"
+msgstr ""
+
+msgid "Learn more about deploying to a cluster"
+msgstr ""
+
+msgid "Learn more about group-level project templates"
+msgstr ""
+
+msgid "Learn more about job dependencies"
+msgstr ""
+
+msgid "Learn more about signing commits"
+msgstr ""
+
+msgid "Learn more about the dependency list"
+msgstr ""
+
+msgid "Learn more in the"
+msgstr ""
+
+msgid "Learn more in the|pipeline schedules documentation"
+msgstr ""
+
+msgid "Leave"
+msgstr ""
+
+msgid "Leave Admin Mode"
+msgstr ""
+
+msgid "Leave blank for no limit. Once set, existing personal access tokens may be revoked."
+msgstr ""
+
+msgid "Leave edit mode? All unsaved changes will be lost."
+msgstr ""
+
+msgid "Leave group"
+msgstr ""
+
+msgid "Leave project"
+msgstr ""
+
+msgid "Leave the \"File type\" and \"Delivery method\" options on their default values."
+msgstr ""
+
+msgid "Leave zen mode"
+msgstr ""
+
+msgid "Let's Encrypt does not accept emails on example.com"
+msgstr ""
+
+msgid "Let's Encrypt is a free, automated, and open certificate authority (CA) that gives digital certificates in order to enable HTTPS (SSL/TLS) for websites. Learn more about Let's Encrypt configuration by following the %{docs_link_start}documentation on GitLab Pages%{docs_link_end}."
+msgstr ""
+
+msgid "License"
+msgstr ""
+
+msgid "License Compliance"
+msgstr ""
+
+msgid "License History"
+msgstr ""
+
+msgid "License ID:"
+msgstr ""
+
+msgid "License URL"
+msgstr ""
+
+msgid "License-Check"
+msgstr ""
+
+msgid "LicenseCompliance|%{docLinkStart}License Approvals%{docLinkEnd} are active"
+msgstr ""
+
+msgid "LicenseCompliance|%{docLinkStart}License Approvals%{docLinkEnd} are inactive"
+msgstr ""
+
+msgid "LicenseCompliance|Add a license"
+msgstr ""
+
+msgid "LicenseCompliance|Add license and related policy"
+msgstr ""
+
+msgid "LicenseCompliance|Allow"
+msgstr ""
+
+msgid "LicenseCompliance|Allowed"
+msgstr ""
+
+msgid "LicenseCompliance|Cancel"
+msgstr ""
+
+msgid "LicenseCompliance|Denied"
+msgstr ""
+
+msgid "LicenseCompliance|Deny"
+msgstr ""
+
+msgid "LicenseCompliance|Learn more about %{linkStart}License Approvals%{linkEnd}"
+msgstr ""
+
+msgid "LicenseCompliance|License"
+msgstr ""
+
+msgid "LicenseCompliance|License Approvals"
+msgstr ""
+
+msgid "LicenseCompliance|License Compliance detected %d license and policy violation for the source branch only; approval required"
+msgid_plural "LicenseCompliance|License Compliance detected %d licenses and policy violations for the source branch only; approval required"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "LicenseCompliance|License Compliance detected %d license for the source branch only"
+msgid_plural "LicenseCompliance|License Compliance detected %d licenses for the source branch only"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "LicenseCompliance|License Compliance detected %d new license"
+msgid_plural "LicenseCompliance|License Compliance detected %d new licenses"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "LicenseCompliance|License Compliance detected %d new license and policy violation; approval required"
+msgid_plural "LicenseCompliance|License Compliance detected %d new licenses and policy violations; approval required"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "LicenseCompliance|License Compliance detected no licenses for the source branch only"
+msgstr ""
+
+msgid "LicenseCompliance|License Compliance detected no new licenses"
+msgstr ""
+
+msgid "LicenseCompliance|License details"
+msgstr ""
+
+msgid "LicenseCompliance|License name"
+msgstr ""
+
+msgid "LicenseCompliance|License review"
+msgstr ""
+
+msgid "LicenseCompliance|Packages"
+msgstr ""
+
+msgid "LicenseCompliance|Remove license"
+msgstr ""
+
+msgid "LicenseCompliance|Remove license?"
+msgstr ""
+
+msgid "LicenseCompliance|Submit"
+msgstr ""
+
+msgid "LicenseCompliance|There are currently no approved or blacklisted licenses in this project."
+msgstr ""
+
+msgid "LicenseCompliance|There are currently no approved or blacklisted licenses that match in this project."
+msgstr ""
+
+msgid "LicenseCompliance|This license already exists in this project."
+msgstr ""
+
+msgid "LicenseCompliance|URL"
+msgstr ""
+
+msgid "LicenseCompliance|You are about to remove the license, %{name}, from this project."
+msgstr ""
+
+msgid "LicenseManagement|Allowed"
+msgstr ""
+
+msgid "LicenseManagement|Denied"
+msgstr ""
+
+msgid "LicenseManagement|Uncategorized"
+msgstr ""
+
+msgid "Licensed Features"
+msgstr ""
+
+msgid "Licensed to"
+msgstr ""
+
+msgid "Licenses"
+msgstr ""
+
+msgid "Licenses|%{remainingComponentsCount} more"
+msgstr ""
+
+msgid "Licenses|Component"
+msgstr ""
+
+msgid "Licenses|Components"
+msgstr ""
+
+msgid "Licenses|Detected in Project"
+msgstr ""
+
+msgid "Licenses|Detected licenses that are out-of-compliance with the project's assigned policies"
+msgstr ""
+
+msgid "Licenses|Displays licenses detected in the project, based on the %{linkStart}latest successful%{linkEnd} scan"
+msgstr ""
+
+msgid "Licenses|Error fetching the license list. Please check your network connection and try again."
+msgstr ""
+
+msgid "Licenses|Learn more about license compliance"
+msgstr ""
+
+msgid "Licenses|License Compliance"
+msgstr ""
+
+msgid "Licenses|Name"
+msgstr ""
+
+msgid "Licenses|Policies"
+msgstr ""
+
+msgid "Licenses|Policy"
+msgstr ""
+
+msgid "Licenses|Policy violation: denied"
+msgstr ""
+
+msgid "Licenses|Specified policies in this project"
+msgstr ""
+
+msgid "Licenses|The license list details information about the licenses used within your project."
+msgstr ""
+
+msgid "Licenses|View license details for your project"
+msgstr ""
+
+msgid "License|Buy license"
+msgstr ""
+
+msgid "License|License"
+msgstr ""
+
+msgid "License|Licensed user count exceeded"
+msgstr ""
+
+msgid "License|You can restore access to the Gold features at any time by upgrading."
+msgstr ""
+
+msgid "License|You can start a free trial of GitLab Ultimate without any obligation or payment details."
+msgstr ""
+
+msgid "License|You do not have a license."
+msgstr ""
+
+msgid "License|Your License"
+msgstr ""
+
+msgid "License|Your free trial of GitLab Ultimate expired on %{trial_ends_on}."
+msgstr ""
+
+msgid "License|Your instance has exceeded your subscription's number of licensed users by %{extra_users_count}. You can continue to add more users and we'll include the overage in your next bill."
+msgstr ""
+
+msgid "Limit display of time tracking units to hours."
+msgstr ""
+
+msgid "Limit namespaces and projects that can be indexed"
+msgstr ""
+
+msgid "Limited to showing %d event at most"
+msgid_plural "Limited to showing %d events at most"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Line changes"
+msgstr ""
+
+msgid "Link Prometheus monitoring to GitLab."
+msgstr ""
+
+msgid "Link copied"
+msgstr ""
+
+msgid "Link title"
+msgstr ""
+
+msgid "Link title is required"
+msgstr ""
+
+msgid "Linked emails (%{email_count})"
+msgstr ""
+
+msgid "Linked issues"
+msgstr ""
+
+msgid "LinkedIn"
+msgstr ""
+
+msgid "LinkedPipelines|%{counterLabel} more downstream pipelines"
+msgstr ""
+
+msgid "Links"
+msgstr ""
+
+msgid "List"
+msgstr ""
+
+msgid "List Your Gitea Repositories"
+msgstr ""
+
+msgid "List available repositories"
+msgstr ""
+
+msgid "List of IPs and CIDRs of allowed secondary nodes. Comma-separated, e.g. \"1.1.1.1, 2.2.2.0/24\""
+msgstr ""
+
+msgid "List settings"
+msgstr ""
+
+msgid "List the merge requests that must be merged before this one."
+msgstr ""
+
+msgid "List view"
+msgstr ""
+
+msgid "List your Bitbucket Server repositories"
+msgstr ""
+
+msgid "Lists"
+msgstr ""
+
+msgid "Live preview"
+msgstr ""
+
+msgid "Loading"
+msgstr ""
+
+msgid "Loading contribution stats for group members"
+msgstr ""
+
+msgid "Loading files, directories, and submodules in the path %{path} for commit reference %{ref}"
+msgstr ""
+
+msgid "Loading functions timed out. Please reload the page to try again."
+msgstr ""
+
+msgid "Loading issues"
+msgstr ""
+
+msgid "Loading snippet"
+msgstr ""
+
+msgid "Loading the GitLab IDE..."
+msgstr ""
+
+msgid "Loading..."
+msgstr ""
+
+msgid "Loading…"
+msgstr ""
+
+msgid "Local IP addresses and domain names that hooks and services may access."
+msgstr ""
+
+msgid "Localization"
+msgstr ""
+
+msgid "Location"
+msgstr ""
+
+msgid "Lock"
+msgstr ""
+
+msgid "Lock %{issuableDisplayName}"
+msgstr ""
+
+msgid "Lock memberships to LDAP synchronization"
+msgstr ""
+
+msgid "Lock not found"
+msgstr ""
+
+msgid "Lock the discussion"
+msgstr ""
+
+msgid "Lock this %{issuableDisplayName}? Only <strong>project members</strong> will be able to comment."
+msgstr ""
+
+msgid "Lock to current projects"
+msgstr ""
+
+msgid "Locked"
+msgstr ""
+
+msgid "Locked Files"
+msgstr ""
+
+msgid "Locked by %{fileLockUserName}"
+msgstr ""
+
+msgid "Locked the discussion."
+msgstr ""
+
+msgid "Locked to current projects"
+msgstr ""
+
+msgid "Locks give the ability to lock specific file or folder."
+msgstr ""
+
+msgid "Locks the discussion."
+msgstr ""
+
+msgid "Login with smartcard"
+msgstr ""
+
+msgid "Logo was successfully removed."
+msgstr ""
+
+msgid "Logs"
+msgstr ""
+
+msgid "Logs|To see the logs, deploy your code to an environment."
+msgstr ""
+
+msgid "Low vulnerabilities present"
+msgstr ""
+
+msgid "MB"
+msgstr ""
+
+msgid "MD5"
+msgstr ""
+
+msgid "MERGED"
+msgstr ""
+
+msgid "MR widget|Take a look at our %{beginnerLinkStart}Beginner's Guide to Continuous Integration%{beginnerLinkEnd} and our %{exampleLinkStart}examples of GitLab CI/CD%{exampleLinkEnd} to see all the cool stuff you can do with it."
+msgstr ""
+
+msgid "MR widget|The pipeline will now run automatically every time you commit code. Pipelines are useful for deploying static web pages, detecting vulnerabilities in dependencies, static or dynamic application security testing (SAST and DAST), and so much more!"
+msgstr ""
+
+msgid "MRApprovals|Approvals"
+msgstr ""
+
+msgid "MRApprovals|Approved by"
+msgstr ""
+
+msgid "MRApprovals|Approvers"
+msgstr ""
+
+msgid "MRDiff|Show changes only"
+msgstr ""
+
+msgid "MRDiff|Show full file"
+msgstr ""
+
+msgid "Made this issue confidential."
+msgstr ""
+
+msgid "Maintenance mode"
+msgstr ""
+
+msgid "Make and review changes in the browser with the Web IDE"
+msgstr ""
+
+msgid "Make everyone on your team more productive regardless of their location. GitLab Geo creates read-only mirrors of your GitLab instance so you can reduce the time it takes to clone and fetch large repos."
+msgstr ""
+
+msgid "Make issue confidential"
+msgstr ""
+
+msgid "Make sure you save it - you won't be able to access it again."
+msgstr ""
+
+msgid "Make sure you're logged into the account that owns the projects you'd like to import."
+msgstr ""
+
+msgid "Make this epic confidential"
+msgstr ""
+
+msgid "Makes this issue confidential."
+msgstr ""
+
+msgid "Manage"
+msgstr ""
+
+msgid "Manage Web IDE features"
+msgstr ""
+
+msgid "Manage access"
+msgstr ""
+
+msgid "Manage all notifications"
+msgstr ""
+
+msgid "Manage applications that can use GitLab as an OAuth provider, and applications that you've authorized to use your account."
+msgstr ""
+
+msgid "Manage applications that you've authorized to use your account."
+msgstr ""
+
+msgid "Manage group labels"
+msgstr ""
+
+msgid "Manage labels"
+msgstr ""
+
+msgid "Manage milestones"
+msgstr ""
+
+msgid "Manage project labels"
+msgstr ""
+
+msgid "Manage storage usage"
+msgstr ""
+
+msgid "Manage two-factor authentication"
+msgstr ""
+
+msgid "Manage your license"
+msgstr ""
+
+msgid "Managed Account"
+msgstr ""
+
+msgid "Manifest"
+msgstr ""
+
+msgid "Manifest file import"
+msgstr ""
+
+msgid "Manifest import"
+msgstr ""
+
+msgid "Manual job"
+msgstr ""
+
+msgid "ManualOrdering|Couldn't save the order of the issues"
+msgstr ""
+
+msgid "Map a FogBugz account ID to a GitLab user"
+msgstr ""
+
+msgid "Map a Google Code user to a GitLab user"
+msgstr ""
+
+msgid "Map a Google Code user to a full email address"
+msgstr ""
+
+msgid "Map a Google Code user to a full name"
+msgstr ""
+
+msgid "Mar"
+msgstr ""
+
+msgid "March"
+msgstr ""
+
+msgid "Mark To Do as done"
+msgstr ""
+
+msgid "Mark as done"
+msgstr ""
+
+msgid "Mark as resolved"
+msgstr ""
+
+msgid "Mark this issue as a duplicate of another issue"
+msgstr ""
+
+msgid "Mark this issue as related to another issue"
+msgstr ""
+
+msgid "Markdown"
+msgstr ""
+
+msgid "Markdown enabled"
+msgstr ""
+
+msgid "Markdown is supported"
+msgstr ""
+
+msgid "Marked To Do as done."
+msgstr ""
+
+msgid "Marked this %{noun} as Work In Progress."
+msgstr ""
+
+msgid "Marked this issue as a duplicate of %{duplicate_param}."
+msgstr ""
+
+msgid "Marked this issue as related to %{issue_ref}."
+msgstr ""
+
+msgid "Marks To Do as done."
+msgstr ""
+
+msgid "Marks this %{noun} as Work In Progress."
+msgstr ""
+
+msgid "Marks this issue as a duplicate of %{duplicate_reference}."
+msgstr ""
+
+msgid "Marks this issue as related to %{issue_ref}."
+msgstr ""
+
+msgid "Mask variable"
+msgstr ""
+
+msgid "Match not found; try refining your search query."
+msgstr ""
+
+msgid "MattermostService|Add to Mattermost"
+msgstr ""
+
+msgid "MattermostService|Command trigger word"
+msgstr ""
+
+msgid "MattermostService|Fill in the word that works best for your team."
+msgstr ""
+
+msgid "MattermostService|Request URL"
+msgstr ""
+
+msgid "MattermostService|Request method"
+msgstr ""
+
+msgid "MattermostService|Response icon"
+msgstr ""
+
+msgid "MattermostService|Response username"
+msgstr ""
+
+msgid "MattermostService|See list of available commands in Mattermost after setting up this service, by entering"
+msgstr ""
+
+msgid "MattermostService|Suggestions:"
+msgstr ""
+
+msgid "MattermostService|This service allows users to perform common operations on this project by entering slash commands in Mattermost."
+msgstr ""
+
+msgid "Maven Metadata"
+msgstr ""
+
+msgid "Max access level"
+msgstr ""
+
+msgid "Max seats used"
+msgstr ""
+
+msgid "Maximum Users:"
+msgstr ""
+
+msgid "Maximum allowable lifetime for personal access token (days)"
+msgstr ""
+
+msgid "Maximum artifacts size (MB)"
+msgstr ""
+
+msgid "Maximum attachment size (MB)"
+msgstr ""
+
+msgid "Maximum bulk request size (MiB)"
+msgstr ""
+
+msgid "Maximum capacity"
+msgstr ""
+
+msgid "Maximum concurrency of Elasticsearch bulk requests per indexing operation."
+msgstr ""
+
+msgid "Maximum delay (Minutes)"
+msgstr ""
+
+msgid "Maximum duration of a session."
+msgstr ""
+
+msgid "Maximum field length"
+msgstr ""
+
+msgid "Maximum file size is 2MB. Please select a smaller file."
+msgstr ""
+
+msgid "Maximum import size (MB)"
+msgstr ""
+
+msgid "Maximum job timeout"
+msgstr ""
+
+msgid "Maximum job timeout has a value which could not be accepted"
+msgstr ""
+
+msgid "Maximum lifetime allowable for Personal Access Tokens is active, your expire date must be set before %{maximum_allowable_date}."
+msgstr ""
+
+msgid "Maximum namespace storage (MB)"
+msgstr ""
+
+msgid "Maximum number of %{name} (%{count}) exceeded"
+msgstr ""
+
+msgid "Maximum number of comments exceeded"
+msgstr ""
+
+msgid "Maximum number of mirrors that can be synchronizing at the same time."
+msgstr ""
+
+msgid "Maximum number of projects."
+msgstr ""
+
+msgid "Maximum page reached"
+msgstr ""
+
+msgid "Maximum push size (MB)"
+msgstr ""
+
+msgid "Maximum size limit for a single commit."
+msgstr ""
+
+msgid "Maximum size limit for each repository."
+msgstr ""
+
+msgid "Maximum size of Elasticsearch bulk indexing requests."
+msgstr ""
+
+msgid "Maximum size of import files."
+msgstr ""
+
+msgid "Maximum size of individual attachments in comments."
+msgstr ""
+
+msgid "Maximum time between updates that a mirror can have when scheduled to synchronize."
+msgstr ""
+
+msgid "May"
+msgstr ""
+
+msgid "Measured in bytes of code. Excludes generated and vendored code."
+msgstr ""
+
+msgid "Median"
+msgstr ""
+
+msgid "Medium vulnerabilities present"
+msgstr ""
+
+msgid "Member lock"
+msgstr ""
+
+msgid "Member since %{date}"
+msgstr ""
+
+msgid "Members"
+msgstr ""
+
+msgid "Members can be added by project <i>Maintainers</i> or <i>Owners</i>"
+msgstr ""
+
+msgid "Members of <strong>%{project_name}</strong>"
+msgstr ""
+
+msgid "Members of a group may only view projects they have permission to access"
+msgstr ""
+
+msgid "Members with access to %{strong_start}%{group_name}%{strong_end}"
+msgstr ""
+
+msgid "Members with pending access to %{strong_start}%{group_name}%{strong_end}"
+msgstr ""
+
+msgid "Memory Usage"
+msgstr ""
+
+msgid "Merge"
+msgstr ""
+
+msgid "Merge (when the pipeline succeeds)"
+msgstr ""
+
+msgid "Merge Conflicts"
+msgstr ""
+
+msgid "Merge Request"
+msgstr ""
+
+msgid "Merge Request Approvals"
+msgstr ""
+
+msgid "Merge Request Commits"
+msgstr ""
+
+msgid "Merge Requests"
+msgstr ""
+
+msgid "Merge Requests created"
+msgstr ""
+
+msgid "Merge Requests in Review"
+msgstr ""
+
+msgid "Merge automatically (%{strategy})"
+msgstr ""
+
+msgid "Merge commit message"
+msgstr ""
+
+msgid "Merge events"
+msgstr ""
+
+msgid "Merge immediately"
+msgstr ""
+
+msgid "Merge in progress"
+msgstr ""
+
+msgid "Merge options"
+msgstr ""
+
+msgid "Merge request"
+msgstr ""
+
+msgid "Merge request %{iid} authored by %{authorName}"
+msgstr ""
+
+msgid "Merge request %{mr_link} was reviewed by %{mr_author}"
+msgstr ""
+
+msgid "Merge request approvals"
+msgstr ""
+
+msgid "Merge request approvals allow you to set the number of necessary approvals and predefine a list of approvers that will need to approve every merge request in a project."
+msgstr ""
+
+msgid "Merge request dependencies"
+msgstr ""
+
+msgid "Merge requests"
+msgstr ""
+
+msgid "Merge requests approvals"
+msgstr ""
+
+msgid "Merge requests are a place to propose changes you've made to a project and discuss those changes with others"
+msgstr ""
+
+msgid "Merge requests are read-only in a secondary Geo node"
+msgstr ""
+
+msgid "Merge when pipeline succeeds"
+msgstr ""
+
+msgid "MergeConflict|Commit to source branch"
+msgstr ""
+
+msgid "MergeConflict|Committing..."
+msgstr ""
+
+msgid "MergeConflict|HEAD//our changes"
+msgstr ""
+
+msgid "MergeConflict|Use ours"
+msgstr ""
+
+msgid "MergeConflict|Use theirs"
+msgstr ""
+
+msgid "MergeConflict|conflict"
+msgstr ""
+
+msgid "MergeConflict|conflicts"
+msgstr ""
+
+msgid "MergeConflict|origin//their changes"
+msgstr ""
+
+msgid "MergeRequestDiffs|Commenting on lines %{selectStart}start%{selectEnd} to %{end}"
+msgstr ""
+
+msgid "MergeRequestDiffs|Select comment starting line"
+msgstr ""
+
+msgid "MergeRequests|Add a reply"
+msgstr ""
+
+msgid "MergeRequests|An error occurred while checking whether another squash is in progress."
+msgstr ""
+
+msgid "MergeRequests|An error occurred while saving the draft comment."
+msgstr ""
+
+msgid "MergeRequests|Failed to squash. Should be done manually."
+msgstr ""
+
+msgid "MergeRequests|Jump to next unresolved thread"
+msgstr ""
+
+msgid "MergeRequests|Reply..."
+msgstr ""
+
+msgid "MergeRequests|Resolve this thread in a new issue"
+msgstr ""
+
+msgid "MergeRequests|Saving the comment failed"
+msgstr ""
+
+msgid "MergeRequests|Squash task canceled: another squash is already in progress."
+msgstr ""
+
+msgid "MergeRequests|This project does not allow squashing commits when merge requests are accepted."
+msgstr ""
+
+msgid "MergeRequests|Thread stays resolved"
+msgstr ""
+
+msgid "MergeRequests|Thread stays unresolved"
+msgstr ""
+
+msgid "MergeRequests|Thread will be resolved"
+msgstr ""
+
+msgid "MergeRequests|Thread will be unresolved"
+msgstr ""
+
+msgid "MergeRequests|Toggle comments for this file"
+msgstr ""
+
+msgid "MergeRequests|View file @ %{commitId}"
+msgstr ""
+
+msgid "MergeRequests|View replaced file @ %{commitId}"
+msgstr ""
+
+msgid "MergeRequests|commented on commit %{commitLink}"
+msgstr ""
+
+msgid "MergeRequests|started a thread"
+msgstr ""
+
+msgid "MergeRequests|started a thread on %{linkStart}an old version of the diff%{linkEnd}"
+msgstr ""
+
+msgid "MergeRequests|started a thread on %{linkStart}the diff%{linkEnd}"
+msgstr ""
+
+msgid "MergeRequests|started a thread on an outdated change in commit %{linkStart}%{commitDisplay}%{linkEnd}"
+msgstr ""
+
+msgid "MergeRequests|started a thread on commit %{linkStart}%{commitDisplay}%{linkEnd}"
+msgstr ""
+
+msgid "MergeRequest|Compare %{target} and %{source}"
+msgstr ""
+
+msgid "MergeRequest|Error dismissing suggestion popover. Please try again."
+msgstr ""
+
+msgid "MergeRequest|Error loading full diff. Please try again."
+msgstr ""
+
+msgid "MergeRequest|No files found"
+msgstr ""
+
+msgid "MergeRequest|Search files (%{modifier_key}P)"
+msgstr ""
+
+msgid "Merged"
+msgstr ""
+
+msgid "Merged MRs"
+msgstr ""
+
+msgid "Merged branches are being deleted. This can take some time depending on the number of branches. Please refresh the page to see changes."
+msgstr ""
+
+msgid "Merged this merge request."
+msgstr ""
+
+msgid "Merges this merge request immediately."
+msgstr ""
+
+msgid "Merges this merge request when the pipeline succeeds."
+msgstr ""
+
+msgid "Merging immediately isn't recommended as it may negatively impact the existing merge train. Read the %{docsLinkStart}documentation%{docsLinkEnd} for more information."
+msgstr ""
+
+msgid "Messages"
+msgstr ""
+
+msgid "Method"
+msgstr ""
+
+msgid "Metric was successfully added."
+msgstr ""
+
+msgid "Metric was successfully updated."
+msgstr ""
+
+msgid "Metric:"
+msgstr ""
+
+msgid "MetricChart|Please select a metric"
+msgstr ""
+
+msgid "MetricChart|Selected"
+msgstr ""
+
+msgid "Metrics"
+msgstr ""
+
+msgid "Metrics - Grafana"
+msgstr ""
+
+msgid "Metrics - Prometheus"
+msgstr ""
+
+msgid "Metrics Dashboard"
+msgstr ""
+
+msgid "Metrics Dashboard YAML definition"
+msgstr ""
+
+msgid "Metrics Dashboard YAML definition is invalid:"
+msgstr ""
+
+msgid "Metrics Dashboard YAML definition is valid."
+msgstr ""
+
+msgid "Metrics and profiling"
+msgstr ""
+
+msgid "Metrics::Dashboard::Annotation|Annotation can't belong to both a cluster and an environment at the same time"
+msgstr ""
+
+msgid "Metrics::Dashboard::Annotation|Annotation has not been deleted"
+msgstr ""
+
+msgid "Metrics::Dashboard::Annotation|Annotation must belong to a cluster or an environment"
+msgstr ""
+
+msgid "Metrics::Dashboard::Annotation|Dashboard with requested path can not be found"
+msgstr ""
+
+msgid "Metrics::Dashboard::Annotation|You are not authorized to create annotation for selected cluster"
+msgstr ""
+
+msgid "Metrics::Dashboard::Annotation|You are not authorized to create annotation for selected environment"
+msgstr ""
+
+msgid "Metrics::Dashboard::Annotation|You are not authorized to delete this annotation"
+msgstr ""
+
+msgid "Metrics::Dashboard::Annotation|can't be before starting_at time"
+msgstr ""
+
+msgid "Metrics::UsersStarredDashboards|Dashboard with requested path can not be found"
+msgstr ""
+
+msgid "Metrics::UsersStarredDashboards|You are not authorized to add star to this dashboard"
+msgstr ""
+
+msgid "MetricsSettings|Add a button to the metrics dashboard linking directly to your existing external dashboard."
+msgstr ""
+
+msgid "MetricsSettings|Choose whether to display dashboard metrics in UTC or the user's local timezone."
+msgstr ""
+
+msgid "MetricsSettings|Dashboard timezone"
+msgstr ""
+
+msgid "MetricsSettings|External dashboard URL"
+msgstr ""
+
+msgid "MetricsSettings|Manage Metrics Dashboard settings."
+msgstr ""
+
+msgid "MetricsSettings|Metrics Dashboard"
+msgstr ""
+
+msgid "MetricsSettings|UTC (Coordinated Universal Time)"
+msgstr ""
+
+msgid "MetricsSettings|User's local timezone"
+msgstr ""
+
+msgid "Metrics|Add metric"
+msgstr ""
+
+msgid "Metrics|Avg"
+msgstr ""
+
+msgid "Metrics|Check out the CI/CD documentation on deploying to an environment"
+msgstr ""
+
+msgid "Metrics|Create custom dashboard %{fileName}"
+msgstr ""
+
+msgid "Metrics|Create metric"
+msgstr ""
+
+msgid "Metrics|Current"
+msgstr ""
+
+msgid "Metrics|Delete metric"
+msgstr ""
+
+msgid "Metrics|Delete metric?"
+msgstr ""
+
+msgid "Metrics|Duplicate"
+msgstr ""
+
+msgid "Metrics|Duplicate dashboard"
+msgstr ""
+
+msgid "Metrics|Duplicating..."
+msgstr ""
+
+msgid "Metrics|Edit metric"
+msgid_plural "Metrics|Edit metrics"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Metrics|Expand panel"
+msgstr ""
+
+msgid "Metrics|For grouping similar metrics"
+msgstr ""
+
+msgid "Metrics|Go back (Esc)"
+msgstr ""
+
+msgid "Metrics|Invalid time range, please verify."
+msgstr ""
+
+msgid "Metrics|Label of the y-axis (usually the unit). The x-axis always represents time."
+msgstr ""
+
+msgid "Metrics|Legend label (optional)"
+msgstr ""
+
+msgid "Metrics|Link contains an invalid time window, please verify the link to see the requested time range."
+msgstr ""
+
+msgid "Metrics|Link contains invalid chart information, please verify the link to see the expanded panel."
+msgstr ""
+
+msgid "Metrics|Manage chart links"
+msgstr ""
+
+msgid "Metrics|Max"
+msgstr ""
+
+msgid "Metrics|Min"
+msgstr ""
+
+msgid "Metrics|Must be a valid PromQL query."
+msgstr ""
+
+msgid "Metrics|New metric"
+msgstr ""
+
+msgid "Metrics|PromQL query is valid"
+msgstr ""
+
+msgid "Metrics|Prometheus Query Documentation"
+msgstr ""
+
+msgid "Metrics|Refresh dashboard"
+msgstr ""
+
+msgid "Metrics|Select a value"
+msgstr ""
+
+msgid "Metrics|Star dashboard"
+msgstr ""
+
+msgid "Metrics|There was an error creating the dashboard."
+msgstr ""
+
+msgid "Metrics|There was an error creating the dashboard. %{error}"
+msgstr ""
+
+msgid "Metrics|There was an error fetching annotations. Please try again."
+msgstr ""
+
+msgid "Metrics|There was an error fetching the environments data, please try again"
+msgstr ""
+
+msgid "Metrics|There was an error getting annotations information."
+msgstr ""
+
+msgid "Metrics|There was an error getting dashboard validation warnings information."
+msgstr ""
+
+msgid "Metrics|There was an error getting deployment information."
+msgstr ""
+
+msgid "Metrics|There was an error getting environments information."
+msgstr ""
+
+msgid "Metrics|There was an error getting options for variable \"%{name}\"."
+msgstr ""
+
+msgid "Metrics|There was an error trying to validate your query"
+msgstr ""
+
+msgid "Metrics|There was an error while retrieving metrics"
+msgstr ""
+
+msgid "Metrics|There was an error while retrieving metrics. %{message}"
+msgstr ""
+
+msgid "Metrics|Unexpected deployment data response from prometheus endpoint"
+msgstr ""
+
+msgid "Metrics|Unit label"
+msgstr ""
+
+msgid "Metrics|Unstar dashboard"
+msgstr ""
+
+msgid "Metrics|Used as a title for the chart"
+msgstr ""
+
+msgid "Metrics|Used if the query returns a single series. If it returns multiple series, their legend labels will be picked up from the response."
+msgstr ""
+
+msgid "Metrics|Validating query"
+msgstr ""
+
+msgid "Metrics|Values"
+msgstr ""
+
+msgid "Metrics|View logs"
+msgstr ""
+
+msgid "Metrics|Y-axis label"
+msgstr ""
+
+msgid "Metrics|You can save a copy of this dashboard to your repository so it can be customized. Select a file name and branch to save it."
+msgstr ""
+
+msgid "Metrics|You're about to permanently delete this metric. This cannot be undone."
+msgstr ""
+
+msgid "Metrics|Your dashboard schema is invalid. Edit the dashboard to correct the YAML schema."
+msgstr ""
+
+msgid "Metrics|e.g. HTTP requests"
+msgstr ""
+
+msgid "Metrics|e.g. Requests/second"
+msgstr ""
+
+msgid "Metrics|e.g. Throughput"
+msgstr ""
+
+msgid "Metrics|e.g. rate(http_requests_total[5m])"
+msgstr ""
+
+msgid "Metrics|e.g. req/sec"
+msgstr ""
+
+msgid "Mi"
+msgstr ""
+
+msgid "Microsoft Azure"
+msgstr ""
+
+msgid "Middleman project with Static Site Editor support"
+msgstr ""
+
+msgid "Migrate your data from an external source like GitHub, Bitbucket, or another instance of GitLab."
+msgstr ""
+
+msgid "Migrated %{success_count}/%{total_count} files."
+msgstr ""
+
+msgid "Migration successful."
+msgstr ""
+
+msgid "Milestone"
+msgid_plural "Milestones"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Milestone lists not available with your current license"
+msgstr ""
+
+msgid "Milestone lists show all issues from the selected milestone."
+msgstr ""
+
+msgid "MilestoneSidebar|Closed:"
+msgstr ""
+
+msgid "MilestoneSidebar|Copy reference"
+msgstr ""
+
+msgid "MilestoneSidebar|Due date"
+msgstr ""
+
+msgid "MilestoneSidebar|Edit"
+msgstr ""
+
+msgid "MilestoneSidebar|From"
+msgstr ""
+
+msgid "MilestoneSidebar|Issues"
+msgstr ""
+
+msgid "MilestoneSidebar|Merge requests"
+msgstr ""
+
+msgid "MilestoneSidebar|Merged:"
+msgstr ""
+
+msgid "MilestoneSidebar|New Issue"
+msgstr ""
+
+msgid "MilestoneSidebar|New issue"
+msgstr ""
+
+msgid "MilestoneSidebar|No due date"
+msgstr ""
+
+msgid "MilestoneSidebar|No start date"
+msgstr ""
+
+msgid "MilestoneSidebar|None"
+msgstr ""
+
+msgid "MilestoneSidebar|Open:"
+msgstr ""
+
+msgid "MilestoneSidebar|Reference:"
+msgstr ""
+
+msgid "MilestoneSidebar|Start date"
+msgstr ""
+
+msgid "MilestoneSidebar|Toggle sidebar"
+msgstr ""
+
+msgid "MilestoneSidebar|Until"
+msgstr ""
+
+msgid "MilestoneSidebar|complete"
+msgstr ""
+
+msgid "Milestones"
+msgstr ""
+
+msgid "Milestones| You’re about to permanently delete the milestone %{milestoneTitle} and remove it from %{issuesWithCount} and %{mergeRequestsWithCount}. Once deleted, it cannot be undone or recovered."
+msgstr ""
+
+msgid "Milestones| You’re about to permanently delete the milestone %{milestoneTitle}. This milestone is not currently used in any issues or merge requests."
+msgstr ""
+
+msgid "Milestones|Close Milestone"
+msgstr ""
+
+msgid "Milestones|Completed Issues (closed)"
+msgstr ""
+
+msgid "Milestones|Delete milestone"
+msgstr ""
+
+msgid "Milestones|Delete milestone %{milestoneTitle}?"
+msgstr ""
+
+msgid "Milestones|Failed to delete milestone %{milestoneTitle}"
+msgstr ""
+
+msgid "Milestones|Group Milestone"
+msgstr ""
+
+msgid "Milestones|Milestone %{milestoneTitle} was not found"
+msgstr ""
+
+msgid "Milestones|Ongoing Issues (open and assigned)"
+msgstr ""
+
+msgid "Milestones|Project Milestone"
+msgstr ""
+
+msgid "Milestones|Promote %{milestoneTitle} to group milestone?"
+msgstr ""
+
+msgid "Milestones|Promote Milestone"
+msgstr ""
+
+msgid "Milestones|Promote to Group Milestone"
+msgstr ""
+
+msgid "Milestones|Promoting %{milestoneTitle} will make it available for all projects inside %{groupName}. Existing project milestones with the same title will be merged."
+msgstr ""
+
+msgid "Milestones|Reopen Milestone"
+msgstr ""
+
+msgid "Milestones|This action cannot be reversed."
+msgstr ""
+
+msgid "Milestones|Unstarted Issues (open and unassigned)"
+msgstr ""
+
+msgid "Minimum capacity to be available before we schedule more mirrors preemptively."
+msgstr ""
+
+msgid "Minimum interval in days"
+msgstr ""
+
+msgid "Minimum length is %{minimum_password_length} characters"
+msgstr ""
+
+msgid "Minimum length is %{minimum_password_length} characters."
+msgstr ""
+
+msgid "Minimum password length (number of characters)"
+msgstr ""
+
+msgid "Minutes"
+msgstr ""
+
+msgid "Mirror direction"
+msgstr ""
+
+msgid "Mirror repository"
+msgstr ""
+
+msgid "Mirror settings are only available to GitLab administrators."
+msgstr ""
+
+msgid "Mirror user"
+msgstr ""
+
+msgid "Mirrored branches will have this prefix. If you enabled 'Only mirror protected branches' you need to include this prefix on protected branches in this project or nothing will be mirrored."
+msgstr ""
+
+msgid "Mirrored repositories"
+msgstr ""
+
+msgid "Mirroring repositories"
+msgstr ""
+
+msgid "Mirroring settings were successfully updated."
+msgstr ""
+
+msgid "Mirroring settings were successfully updated. The project is being updated."
+msgstr ""
+
+msgid "Mirroring was successfully disabled."
+msgstr ""
+
+msgid "Mirroring will only be available if the feature is included in the plan of the selected group or user."
+msgstr ""
+
+msgid "Missing commit signatures endpoint!"
+msgstr ""
+
+msgid "MissingSSHKeyWarningLink|Add SSH key"
+msgstr ""
+
+msgid "MissingSSHKeyWarningLink|Don't show again"
+msgstr ""
+
+msgid "MissingSSHKeyWarningLink|You won't be able to pull or push project code via SSH until you add an SSH key to your profile"
+msgstr ""
+
+msgid "ModalButton|Add projects"
+msgstr ""
+
+msgid "Modal|Cancel"
+msgstr ""
+
+msgid "Modal|Close"
+msgstr ""
+
+msgid "Modified"
+msgstr ""
+
+msgid "Modified in this version"
+msgstr ""
+
+msgid "Modify commit message"
+msgstr ""
+
+msgid "Modify commit messages"
+msgstr ""
+
+msgid "Modify merge commit"
+msgstr ""
+
+msgid "Monday"
+msgstr ""
+
+msgid "Monitor your errors by integrating with Sentry."
+msgstr ""
+
+msgid "Monitoring"
+msgstr ""
+
+msgid "Months"
+msgstr ""
+
+msgid "More"
+msgstr ""
+
+msgid "More Information"
+msgstr ""
+
+msgid "More Slack commands"
+msgstr ""
+
+msgid "More actions"
+msgstr ""
+
+msgid "More details"
+msgstr ""
+
+msgid "More info"
+msgstr ""
+
+msgid "More information"
+msgstr ""
+
+msgid "More information and share feedback"
+msgstr ""
+
+msgid "More information is available|here"
+msgstr ""
+
+msgid "More than %{number_commits_distance} commits different with %{default_branch}"
+msgstr ""
+
+msgid "Most stars"
+msgstr ""
+
+msgid "Mount point %{mounted_as} not found in %{model_class}."
+msgstr ""
+
+msgid "Move"
+msgstr ""
+
+msgid "Move issue"
+msgstr ""
+
+msgid "Move issue from one column of the board to another"
+msgstr ""
+
+msgid "Move selection down"
+msgstr ""
+
+msgid "Move selection up"
+msgstr ""
+
+msgid "Move this issue to another project."
+msgstr ""
+
+msgid "MoveIssue|Cannot move issue due to insufficient permissions!"
+msgstr ""
+
+msgid "MoveIssue|Cannot move issue to project it originates from!"
+msgstr ""
+
+msgid "Moved issue to %{label} column in the board."
+msgstr ""
+
+msgid "Moved this issue to %{path_to_project}."
+msgstr ""
+
+msgid "Moves issue to %{label} column in the board."
+msgstr ""
+
+msgid "Moves this issue to %{path_to_project}."
+msgstr ""
+
+msgid "MrDeploymentActions|Deploy"
+msgstr ""
+
+msgid "MrDeploymentActions|Re-deploy"
+msgstr ""
+
+msgid "MrDeploymentActions|Stop environment"
+msgstr ""
+
+msgid "Multiple domains are supported with comma delimiters."
+msgstr ""
+
+msgid "Multiple issue boards"
+msgstr ""
+
+msgid "Multiple model types found: %{model_types}"
+msgstr ""
+
+msgid "Multiple uploaders found: %{uploader_types}"
+msgstr ""
+
+msgid "Must match with the %{codeStart}external_url%{codeEnd} in %{codeStart}/etc/gitlab/gitlab.rb%{codeEnd}."
+msgstr ""
+
+msgid "Must match with the %{codeStart}geo_node_name%{codeEnd} in %{codeStart}/etc/gitlab/gitlab.rb%{codeEnd}. %{linkStart}More information%{linkEnd}"
+msgstr ""
+
+msgid "My Awesome Group"
+msgstr ""
+
+msgid "My company or team"
+msgstr ""
+
+msgid "My-Reaction"
+msgstr ""
+
+msgid "Name"
+msgstr ""
+
+msgid "Name has already been taken"
+msgstr ""
+
+msgid "Name new label"
+msgstr ""
+
+msgid "Name:"
+msgstr ""
+
+msgid "Namespace is empty"
+msgstr ""
+
+msgid "Namespaces to index"
+msgstr ""
+
+msgid "Naming, topics, avatar"
+msgstr ""
+
+msgid "Naming, visibility"
+msgstr ""
+
+msgid "Navigate to the project to close the milestone."
+msgstr ""
+
+msgid "Nav|Help"
+msgstr ""
+
+msgid "Nav|Home"
+msgstr ""
+
+msgid "Nav|Sign In / Register"
+msgstr ""
+
+msgid "Nav|Sign out and sign in with a different account"
+msgstr ""
+
+msgid "Need help?"
+msgstr ""
+
+msgid "Needs attention"
+msgstr ""
+
+msgid "Network"
+msgstr ""
+
+msgid "NetworkPolicies|Choose whether to enforce this policy."
+msgstr ""
+
+msgid "NetworkPolicies|Define this policy's location, conditions and actions."
+msgstr ""
+
+msgid "NetworkPolicies|Enforcement status"
+msgstr ""
+
+msgid "NetworkPolicies|Environment does not have deployment platform"
+msgstr ""
+
+msgid "NetworkPolicies|If you are using Auto DevOps, your %{monospacedStart}auto-deploy-values.yaml%{monospacedEnd} file will not be updated if you change a policy in this section. Auto DevOps users should make changes by following the %{linkStart}Container Network Policy documentation%{linkEnd}."
+msgstr ""
+
+msgid "NetworkPolicies|Invalid or empty policy"
+msgstr ""
+
+msgid "NetworkPolicies|Kubernetes error: %{error}"
+msgstr ""
+
+msgid "NetworkPolicies|Last modified"
+msgstr ""
+
+msgid "NetworkPolicies|Name"
+msgstr ""
+
+msgid "NetworkPolicies|No policies detected"
+msgstr ""
+
+msgid "NetworkPolicies|Policies are a specification of how groups of pods are allowed to communicate with each other's network endpoints."
+msgstr ""
+
+msgid "NetworkPolicies|Policy %{policyName} was successfully changed"
+msgstr ""
+
+msgid "NetworkPolicies|Policy definition"
+msgstr ""
+
+msgid "NetworkPolicies|Something went wrong, failed to update policy"
+msgstr ""
+
+msgid "NetworkPolicies|Something went wrong, unable to fetch policies"
+msgstr ""
+
+msgid "NetworkPolicies|Status"
+msgstr ""
+
+msgid "Never"
+msgstr ""
+
+msgid "New"
+msgstr ""
+
+msgid "New Application"
+msgstr ""
+
+msgid "New Branch"
+msgstr ""
+
+msgid "New Deploy Key"
+msgstr ""
+
+msgid "New Environment"
+msgstr ""
+
+msgid "New File"
+msgstr ""
+
+msgid "New Group"
+msgstr ""
+
+msgid "New Group Name"
+msgstr ""
+
+msgid "New Identity"
+msgstr ""
+
+msgid "New Issue"
+msgid_plural "New Issues"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "New Jira import"
+msgstr ""
+
+msgid "New Label"
+msgstr ""
+
+msgid "New Merge Request"
+msgstr ""
+
+msgid "New Milestone"
+msgstr ""
+
+msgid "New Pages Domain"
+msgstr ""
+
+msgid "New Password"
+msgstr ""
+
+msgid "New Pipeline Schedule"
+msgstr ""
+
+msgid "New Project"
+msgstr ""
+
+msgid "New Snippet"
+msgstr ""
+
+msgid "New User"
+msgstr ""
+
+msgid "New branch"
+msgstr ""
+
+msgid "New branch unavailable"
+msgstr ""
+
+msgid "New changes were added. %{linkStart}Reload the page to review them%{linkEnd}"
+msgstr ""
+
+msgid "New deploy key"
+msgstr ""
+
+msgid "New directory"
+msgstr ""
+
+msgid "New environment"
+msgstr ""
+
+msgid "New epic"
+msgstr ""
+
+msgid "New epic title"
+msgstr ""
+
+msgid "New file"
+msgstr ""
+
+msgid "New group"
+msgstr ""
+
+msgid "New health check access token has been generated!"
+msgstr ""
+
+msgid "New identity"
+msgstr ""
+
+msgid "New issue"
+msgstr ""
+
+msgid "New issue title"
+msgstr ""
+
+msgid "New iteration"
+msgstr ""
+
+msgid "New iteration created"
+msgstr ""
+
+msgid "New label"
+msgstr ""
+
+msgid "New merge request"
+msgstr ""
+
+msgid "New milestone"
+msgstr ""
+
+msgid "New password"
+msgstr ""
+
+msgid "New pipelines will cancel older, pending pipelines on the same branch"
+msgstr ""
+
+msgid "New project"
+msgstr ""
+
+msgid "New release"
+msgstr ""
+
+msgid "New requirement"
+msgstr ""
+
+msgid "New runners registration token has been generated!"
+msgstr ""
+
+msgid "New schedule"
+msgstr ""
+
+msgid "New snippet"
+msgstr ""
+
+msgid "New subgroup"
+msgstr ""
+
+msgid "New tag"
+msgstr ""
+
+msgid "New users set to external"
+msgstr ""
+
+msgid "New! Suggest changes directly"
+msgstr ""
+
+msgid "New..."
+msgstr ""
+
+msgid "Newest first"
+msgstr ""
+
+msgid "Newly registered users will by default be external"
+msgstr ""
+
+msgid "Next"
+msgstr ""
+
+msgid "Next commit"
+msgstr ""
+
+msgid "Next file in diff"
+msgstr ""
+
+msgid "Next unresolved discussion"
+msgstr ""
+
+msgid "Nickname"
+msgstr ""
+
+msgid "No"
+msgstr ""
+
+msgid "No %{header} for this request."
+msgstr ""
+
+msgid "No %{providerTitle} repositories found"
+msgstr ""
+
+msgid "No Epic"
+msgstr ""
+
+msgid "No Scopes"
+msgstr ""
+
+msgid "No Tag"
+msgstr ""
+
+msgid "No active admin user found"
+msgstr ""
+
+msgid "No activities found"
+msgstr ""
+
+msgid "No application_settings found"
+msgstr ""
+
+msgid "No approvers"
+msgstr ""
+
+msgid "No authentication methods configured."
+msgstr ""
+
+msgid "No available namespaces to fork the project."
+msgstr ""
+
+msgid "No branches found"
+msgstr ""
+
+msgid "No changes"
+msgstr ""
+
+msgid "No changes between %{ref_start}%{source_branch}%{ref_end} and %{ref_start}%{target_branch}%{ref_end}"
+msgstr ""
+
+msgid "No child epics match applied filters"
+msgstr ""
+
+msgid "No connection could be made to a Gitaly Server, please check your logs!"
+msgstr ""
+
+msgid "No containers available"
+msgstr ""
+
+msgid "No contributions"
+msgstr ""
+
+msgid "No contributions were found"
+msgstr ""
+
+msgid "No credit card required."
+msgstr ""
+
+msgid "No data found"
+msgstr ""
+
+msgid "No data to display"
+msgstr ""
+
+msgid "No deployments found"
+msgstr ""
+
+msgid "No due date"
+msgstr ""
+
+msgid "No endpoint provided"
+msgstr ""
+
+msgid "No errors to display."
+msgstr ""
+
+msgid "No estimate or time spent"
+msgstr ""
+
+msgid "No file chosen"
+msgstr ""
+
+msgid "No file hooks found."
+msgstr ""
+
+msgid "No file selected"
+msgstr ""
+
+msgid "No files"
+msgstr ""
+
+msgid "No files found."
+msgstr ""
+
+msgid "No forks are available to you."
+msgstr ""
+
+msgid "No grouping"
+msgstr ""
+
+msgid "No iteration"
+msgstr ""
+
+msgid "No iterations to show"
+msgstr ""
+
+msgid "No job log"
+msgstr ""
+
+msgid "No jobs to show"
+msgstr ""
+
+msgid "No label"
+msgstr ""
+
+msgid "No labels with such name or description"
+msgstr ""
+
+msgid "No license. All rights reserved"
+msgstr ""
+
+msgid "No licenses found."
+msgstr ""
+
+msgid "No matches found"
+msgstr ""
+
+msgid "No matching labels"
+msgstr ""
+
+msgid "No matching results"
+msgstr ""
+
+msgid "No merge requests found"
+msgstr ""
+
+msgid "No messages were logged"
+msgstr ""
+
+msgid "No milestone"
+msgstr ""
+
+msgid "No milestones to show"
+msgstr ""
+
+msgid "No other labels with such name or description"
+msgstr ""
+
+msgid "No panels matching properties %{opts}"
+msgstr ""
+
+msgid "No parent group"
+msgstr ""
+
+msgid "No pods available"
+msgstr ""
+
+msgid "No policy matches this license"
+msgstr ""
+
+msgid "No preview for this file type"
+msgstr ""
+
+msgid "No prioritized labels with such name or description"
+msgstr ""
+
+msgid "No public groups"
+msgstr ""
+
+msgid "No related merge requests found."
+msgstr ""
+
+msgid "No repository"
+msgstr ""
+
+msgid "No required pipeline"
+msgstr ""
+
+msgid "No runners found"
+msgstr ""
+
+msgid "No schedules"
+msgstr ""
+
+msgid "No stack trace for this error"
+msgstr ""
+
+msgid "No starrers matched your search"
+msgstr ""
+
+msgid "No start date"
+msgstr ""
+
+msgid "No template"
+msgstr ""
+
+msgid "No test coverage"
+msgstr ""
+
+msgid "No thanks"
+msgstr ""
+
+msgid "No vulnerabilities present"
+msgstr ""
+
+msgid "No webhooks found, add one in the form above."
+msgstr ""
+
+msgid "No worries, you can still use all the %{strong}%{plan_name}%{strong_close} features for now. You have %{remaining_days} to renew your subscription."
+msgstr ""
+
+msgid "No, directly import the existing email addresses and usernames."
+msgstr ""
+
+msgid "No. of commits"
+msgstr ""
+
+msgid "Nobody has starred this repository yet"
+msgstr ""
+
+msgid "Node was successfully created."
+msgstr ""
+
+msgid "Node was successfully updated."
+msgstr ""
+
+msgid "Nodes"
+msgstr ""
+
+msgid "Non-admin users can sign in with read-only access and make read-only API requests."
+msgstr ""
+
+msgid "None"
+msgstr ""
+
+msgid "Not Implemented"
+msgstr ""
+
+msgid "Not all data has been processed yet, the accuracy of the chart for the selected timeframe is limited."
+msgstr ""
+
+msgid "Not available"
+msgstr ""
+
+msgid "Not available for private projects"
+msgstr ""
+
+msgid "Not available for protected branches"
+msgstr ""
+
+msgid "Not confidential"
+msgstr ""
+
+msgid "Not enough data"
+msgstr ""
+
+msgid "Not found."
+msgstr ""
+
+msgid "Not now"
+msgstr ""
+
+msgid "Not ready yet. Try again later."
+msgstr ""
+
+msgid "Not started"
+msgstr ""
+
+msgid "Not-confidential epic cannot be assigned to a confidential parent epic"
+msgstr ""
+
+msgid "Note"
+msgstr ""
+
+msgid "Note parameters are invalid: %{errors}"
+msgstr ""
+
+msgid "Note that PostgreSQL 11 will become the minimum required PostgreSQL version in GitLab 13.0 (May 2020). PostgreSQL 9.6 and PostgreSQL 10 will no longer be supported in GitLab 13.0. Please consider upgrading your PostgreSQL version (%{db_version}) soon."
+msgstr ""
+
+msgid "Note that this invitation was sent to %{mail_to_invite_email}, but you are signed in as %{link_to_current_user} with email %{mail_to_current_user}."
+msgstr ""
+
+msgid "Note: As an administrator you may like to configure %{github_integration_link}, which will allow login via GitHub and allow connecting repositories without generating a Personal Access Token."
+msgstr ""
+
+msgid "Note: As an administrator you may like to configure %{github_integration_link}, which will allow login via GitHub and allow importing repositories without generating a Personal Access Token."
+msgstr ""
+
+msgid "Note: Consider asking your GitLab administrator to configure %{github_integration_link}, which will allow login via GitHub and allow connecting repositories without generating a Personal Access Token."
+msgstr ""
+
+msgid "Note: Consider asking your GitLab administrator to configure %{github_integration_link}, which will allow login via GitHub and allow importing repositories without generating a Personal Access Token."
+msgstr ""
+
+msgid "NoteForm|Note"
+msgstr ""
+
+msgid "Notes|Are you sure you want to cancel creating this comment?"
+msgstr ""
+
+msgid "Notes|Collapse replies"
+msgstr ""
+
+msgid "Notes|Private comments are accessible by internal staff only"
+msgstr ""
+
+msgid "Notes|Show all activity"
+msgstr ""
+
+msgid "Notes|Show comments only"
+msgstr ""
+
+msgid "Notes|Show history only"
+msgstr ""
+
+msgid "Notes|This comment has changed since you started editing, please review the %{open_link}updated comment%{close_link} to ensure information is not lost"
+msgstr ""
+
+msgid "Nothing found…"
+msgstr ""
+
+msgid "Nothing to preview."
+msgstr ""
+
+msgid "Nothing to synchronize"
+msgstr ""
+
+msgid "Notification events"
+msgstr ""
+
+msgid "Notification setting"
+msgstr ""
+
+msgid "Notification setting - %{notification_title}"
+msgstr ""
+
+msgid "Notification settings saved"
+msgstr ""
+
+msgid "NotificationEvent|Close issue"
+msgstr ""
+
+msgid "NotificationEvent|Close merge request"
+msgstr ""
+
+msgid "NotificationEvent|Failed pipeline"
+msgstr ""
+
+msgid "NotificationEvent|Fixed pipeline"
+msgstr ""
+
+msgid "NotificationEvent|Merge merge request"
+msgstr ""
+
+msgid "NotificationEvent|New epic"
+msgstr ""
+
+msgid "NotificationEvent|New issue"
+msgstr ""
+
+msgid "NotificationEvent|New merge request"
+msgstr ""
+
+msgid "NotificationEvent|New note"
+msgstr ""
+
+msgid "NotificationEvent|New release"
+msgstr ""
+
+msgid "NotificationEvent|Reassign issue"
+msgstr ""
+
+msgid "NotificationEvent|Reassign merge request"
+msgstr ""
+
+msgid "NotificationEvent|Reopen issue"
+msgstr ""
+
+msgid "NotificationEvent|Successful pipeline"
+msgstr ""
+
+msgid "NotificationLevel|Custom"
+msgstr ""
+
+msgid "NotificationLevel|Disabled"
+msgstr ""
+
+msgid "NotificationLevel|Global"
+msgstr ""
+
+msgid "NotificationLevel|On mention"
+msgstr ""
+
+msgid "NotificationLevel|Participate"
+msgstr ""
+
+msgid "NotificationLevel|Watch"
+msgstr ""
+
+msgid "NotificationSetting|Custom"
+msgstr ""
+
+msgid "Notifications"
+msgstr ""
+
+msgid "Notifications have been disabled by the project or group owner"
+msgstr ""
+
+msgid "Notifications off"
+msgstr ""
+
+msgid "Notifications on"
+msgstr ""
+
+msgid "Notify users by email when sign-in location is not recognized"
+msgstr ""
+
+msgid "Nov"
+msgstr ""
+
+msgid "November"
+msgstr ""
+
+msgid "Novice"
+msgstr ""
+
+msgid "Now you can access the merge request navigation tabs at the top, where they’re easier to find."
+msgstr ""
+
+msgid "Nuget metadatum must have at least license_url, project_url or icon_url set"
+msgstr ""
+
+msgid "Number of %{itemTitle}"
+msgstr ""
+
+msgid "Number of Elasticsearch replicas"
+msgstr ""
+
+msgid "Number of Elasticsearch shards"
+msgstr ""
+
+msgid "Number of LOCs per commit"
+msgstr ""
+
+msgid "Number of changes (branches or tags) in a single push to determine whether individual push events or bulk push event will be created. Bulk push event will be created if it surpasses that value."
+msgstr ""
+
+msgid "Number of changes (branches or tags) in a single push to determine whether webhooks and services will be fired or not. Webhooks and services won't be submitted if it surpasses that value."
+msgstr ""
+
+msgid "Number of commits"
+msgstr ""
+
+msgid "Number of commits per MR"
+msgstr ""
+
+msgid "Number of employees"
+msgstr ""
+
+msgid "Number of files touched"
+msgstr ""
+
+msgid "OK"
+msgstr ""
+
+msgid "Object Storage replication"
+msgstr ""
+
+msgid "Object does not exist on the server or you don't have permissions to access it"
+msgstr ""
+
+msgid "Oct"
+msgstr ""
+
+msgid "October"
+msgstr ""
+
+msgid "OfSearchInADropdown|Filter"
+msgstr ""
+
+msgid "Oh no!"
+msgstr ""
+
+msgid "Oldest first"
+msgstr ""
+
+msgid "OmniAuth"
+msgstr ""
+
+msgid "Omnibus Protected Paths throttle is active, and takes priority over these settings. From 12.4, Omnibus throttle is deprecated and will be removed in a future release. Please read the %{relative_url_link_start}Migrating Protected Paths documentation%{relative_url_link_end}."
+msgstr ""
+
+msgid "On track"
+msgstr ""
+
+msgid "OnDemandScans|Attached branch"
+msgstr ""
+
+msgid "OnDemandScans|Attached branch is where the scan job runs."
+msgstr ""
+
+msgid "OnDemandScans|Could not run the scan. Please try again."
+msgstr ""
+
+msgid "OnDemandScans|Create new DAST scan"
+msgstr ""
+
+msgid "OnDemandScans|DAST will scan the target URL and any discovered sub URLs."
+msgstr ""
+
+msgid "OnDemandScans|New on-demand DAST scan"
+msgstr ""
+
+msgid "OnDemandScans|On-demand Scans"
+msgstr ""
+
+msgid "OnDemandScans|On-demand scans run outside the DevOps cycle and find vulnerabilities in your projects. %{learnMoreLinkStart}Learn more%{learnMoreLinkEnd}"
+msgstr ""
+
+msgid "OnDemandScans|Only a passive scan can be performed on demand."
+msgstr ""
+
+msgid "OnDemandScans|Passive DAST Scan"
+msgstr ""
+
+msgid "OnDemandScans|Please enter a valid URL format, ex: http://www.example.com/home"
+msgstr ""
+
+msgid "OnDemandScans|Run this scan"
+msgstr ""
+
+msgid "OnDemandScans|Scan mode"
+msgstr ""
+
+msgid "OnDemandScans|Schedule or run scans immediately against target sites. Currently available on-demand scan type: DAST. %{helpLinkStart}More information%{helpLinkEnd}"
+msgstr ""
+
+msgid "OnDemandScans|Target URL"
+msgstr ""
+
+msgid "Once imported, repositories can be mirrored over SSH. Read more %{link_start}here%{link_end}."
+msgstr ""
+
+msgid "Once removed, the fork relationship cannot be restored and you will no longer be able to send merge requests to the source."
+msgstr ""
+
+msgid "Once the exported file is ready, you will receive a notification email with a download link, or you can download it from this page."
+msgstr ""
+
+msgid "Once you confirm and press \"Reduce project visibility\":"
+msgstr ""
+
+msgid "One more item"
+msgid_plural "%d more items"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "One or more groups that you don't have access to."
+msgstr ""
+
+msgid "One or more of you personal access tokens were revoked"
+msgstr ""
+
+msgid "One or more of your %{provider} projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
+msgstr ""
+
+msgid "One or more of your Bitbucket projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
+msgstr ""
+
+msgid "One or more of your Google Code projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
+msgstr ""
+
+msgid "One or more of your dependency files are not supported, and the dependency list may be incomplete. Below is a list of supported file types."
+msgstr ""
+
+msgid "One or more of your personal access tokens will expire in %{days_to_expire} days or less."
+msgstr ""
+
+msgid "Only 'Reporter' roles and above on tiers Premium / Silver and above can see Value Stream Analytics."
+msgstr ""
+
+msgid "Only 1 appearances row can exist"
+msgstr ""
+
+msgid "Only Issue ID or Merge Request ID is required"
+msgstr ""
+
+msgid "Only Project Members"
+msgstr ""
+
+msgid "Only active this projects shows up in the search and on the dashboard."
+msgstr ""
+
+msgid "Only admins"
+msgstr ""
+
+msgid "Only admins can delete project"
+msgstr ""
+
+msgid "Only mirror protected branches"
+msgstr ""
+
+msgid "Only policy:"
+msgstr ""
+
+msgid "Only proceed if you trust %{idp_url} to control your GitLab account sign in."
+msgstr ""
+
+msgid "Only project members can comment."
+msgstr ""
+
+msgid "Only project members will be imported. Group members will be skipped."
+msgstr ""
+
+msgid "Only verified users with an email address in any of these domains can be added to the group."
+msgstr ""
+
+msgid "Only ‘Reporter’ roles and above on tiers Premium / Silver and above can see Productivity Analytics."
+msgstr ""
+
+msgid "Oops, are you sure?"
+msgstr ""
+
+msgid "Open"
+msgstr ""
+
+msgid "Open Selection"
+msgstr ""
+
+msgid "Open comment type dropdown"
+msgstr ""
+
+msgid "Open errors"
+msgstr ""
+
+msgid "Open in Xcode"
+msgstr ""
+
+msgid "Open issues"
+msgstr ""
+
+msgid "Open projects"
+msgstr ""
+
+msgid "Open raw"
+msgstr ""
+
+msgid "Open sidebar"
+msgstr ""
+
+msgid "Open: %{openIssuesCount}"
+msgstr ""
+
+msgid "Open: %{open} • Closed: %{closed}"
+msgstr ""
+
+msgid "Opened"
+msgstr ""
+
+msgid "Opened MRs"
+msgstr ""
+
+msgid "Opened issues"
+msgstr ""
+
+msgid "OpenedNDaysAgo|Opened"
+msgstr ""
+
+msgid "Opens in a new window"
+msgstr ""
+
+msgid "Operation failed. Check pod logs for %{pod_name} for more details."
+msgstr ""
+
+msgid "Operation not allowed"
+msgstr ""
+
+msgid "Operation timed out. Check pod logs for %{pod_name} for more details."
+msgstr ""
+
+msgid "Operations"
+msgstr ""
+
+msgid "Operations Dashboard"
+msgstr ""
+
+msgid "Operations Settings"
+msgstr ""
+
+msgid "OperationsDashboard|Add a project to the dashboard"
+msgstr ""
+
+msgid "OperationsDashboard|Add projects"
+msgstr ""
+
+msgid "OperationsDashboard|More information"
+msgstr ""
+
+msgid "OperationsDashboard|Operations Dashboard"
+msgstr ""
+
+msgid "OperationsDashboard|The operations dashboard provides a summary of each project's operational health, including pipeline and alert statuses."
+msgstr ""
+
+msgid "Optional"
+msgstr ""
+
+msgid "Optional parameter \"variables\" must be a Hash. Ex: variables[key1]=value1"
+msgstr ""
+
+msgid "Optionally, you can %{link_to_customize} how FogBugz email addresses and usernames are imported into GitLab."
+msgstr ""
+
+msgid "Optionally, you can %{link_to_customize} how Google Code email addresses and usernames are imported into GitLab."
+msgstr ""
+
+msgid "Options"
+msgstr ""
+
+msgid "Or you can choose one of the suggested colors below"
+msgstr ""
+
+msgid "Origin"
+msgstr ""
+
+msgid "Other Labels"
+msgstr ""
+
+msgid "Other information"
+msgstr ""
+
+msgid "Other merge requests block this MR"
+msgstr ""
+
+msgid "Other visibility settings have been disabled by the administrator."
+msgstr ""
+
+msgid "Out-of-compliance with this project's policies and should be removed"
+msgstr ""
+
+msgid "Outbound requests"
+msgstr ""
+
+msgid "OutdatedBrowser|From May 2020 GitLab no longer supports Internet Explorer 11."
+msgstr ""
+
+msgid "OutdatedBrowser|GitLab may not work properly, because you are using an outdated web browser."
+msgstr ""
+
+msgid "OutdatedBrowser|Please install a %{browser_link_start}supported web browser%{browser_link_end} for a better experience."
+msgstr ""
+
+msgid "OutdatedBrowser|You can provide feedback %{feedback_link_start}on this issue%{feedback_link_end} or via your usual support channels."
+msgstr ""
+
+msgid "Outdent"
+msgstr ""
+
+msgid "Overridden"
+msgstr ""
+
+msgid "Overview"
+msgstr ""
+
+msgid "Overwrite diverged branches"
+msgstr ""
+
+msgid "Owned by anyone"
+msgstr ""
+
+msgid "Owned by me"
+msgstr ""
+
+msgid "Owner"
+msgstr ""
+
+msgid "Package Files"
+msgstr ""
+
+msgid "Package Registry"
+msgstr ""
+
+msgid "Package already exists"
+msgstr ""
+
+msgid "Package deleted successfully"
+msgstr ""
+
+msgid "Package information"
+msgstr ""
+
+msgid "Package recipe already exists"
+msgstr ""
+
+msgid "Package type must be Conan"
+msgstr ""
+
+msgid "Package type must be Maven"
+msgstr ""
+
+msgid "Package type must be NuGet"
+msgstr ""
+
+msgid "Package type must be PyPi"
+msgstr ""
+
+msgid "Package was removed"
+msgstr ""
+
+msgid "PackageRegistry|Add Conan Remote"
+msgstr ""
+
+msgid "PackageRegistry|Add NuGet Source"
+msgstr ""
+
+msgid "PackageRegistry|Conan"
+msgstr ""
+
+msgid "PackageRegistry|Conan Command"
+msgstr ""
+
+msgid "PackageRegistry|Copy .pypirc content"
+msgstr ""
+
+msgid "PackageRegistry|Copy Conan Command"
+msgstr ""
+
+msgid "PackageRegistry|Copy Conan Setup Command"
+msgstr ""
+
+msgid "PackageRegistry|Copy Maven XML"
+msgstr ""
+
+msgid "PackageRegistry|Copy Maven command"
+msgstr ""
+
+msgid "PackageRegistry|Copy Maven registry XML"
+msgstr ""
+
+msgid "PackageRegistry|Copy NuGet Command"
+msgstr ""
+
+msgid "PackageRegistry|Copy NuGet Setup Command"
+msgstr ""
+
+msgid "PackageRegistry|Copy Pip command"
+msgstr ""
+
+msgid "PackageRegistry|Copy and paste this inside your %{codeStart}pom.xml%{codeEnd} %{codeStart}dependencies%{codeEnd} block."
+msgstr ""
+
+msgid "PackageRegistry|Copy npm command"
+msgstr ""
+
+msgid "PackageRegistry|Copy npm setup command"
+msgstr ""
+
+msgid "PackageRegistry|Copy yarn command"
+msgstr ""
+
+msgid "PackageRegistry|Copy yarn setup command"
+msgstr ""
+
+msgid "PackageRegistry|Delete Package Version"
+msgstr ""
+
+msgid "PackageRegistry|Delete package"
+msgstr ""
+
+msgid "PackageRegistry|Filter by name"
+msgstr ""
+
+msgid "PackageRegistry|For more information on the Conan registry, %{linkStart}see the documentation%{linkEnd}."
+msgstr ""
+
+msgid "PackageRegistry|For more information on the Maven registry, %{linkStart}see the documentation%{linkEnd}."
+msgstr ""
+
+msgid "PackageRegistry|For more information on the NuGet registry, %{linkStart}see the documentation%{linkEnd}."
+msgstr ""
+
+msgid "PackageRegistry|For more information on the PyPi registry, %{linkStart}see the documentation%{linkEnd}."
+msgstr ""
+
+msgid "PackageRegistry|If you haven't already done so, you will need to add the below to your %{codeStart}.pypirc%{codeEnd} file."
+msgstr ""
+
+msgid "PackageRegistry|If you haven't already done so, you will need to add the below to your %{codeStart}pom.xml%{codeEnd} file."
+msgstr ""
+
+msgid "PackageRegistry|Installation"
+msgstr ""
+
+msgid "PackageRegistry|Is your favorite package manager missing? We'd love your help in building first-class support for it into GitLab! %{contributionLinkStart}Visit the contribution documentation%{contributionLinkEnd} to learn more about how to build support for new package managers into GitLab. Below is a list of package managers that are on our radar."
+msgstr ""
+
+msgid "PackageRegistry|Learn how to %{noPackagesLinkStart}publish and share your packages%{noPackagesLinkEnd} with GitLab."
+msgstr ""
+
+msgid "PackageRegistry|Manually Published"
+msgstr ""
+
+msgid "PackageRegistry|Maven"
+msgstr ""
+
+msgid "PackageRegistry|Maven Command"
+msgstr ""
+
+msgid "PackageRegistry|Maven XML"
+msgstr ""
+
+msgid "PackageRegistry|NPM"
+msgstr ""
+
+msgid "PackageRegistry|No upcoming issues"
+msgstr ""
+
+msgid "PackageRegistry|NuGet"
+msgstr ""
+
+msgid "PackageRegistry|NuGet Command"
+msgstr ""
+
+msgid "PackageRegistry|Pip Command"
+msgstr ""
+
+msgid "PackageRegistry|Pipeline %{linkStart}%{linkEnd} triggered %{timestamp} by %{author}"
+msgstr ""
+
+msgid "PackageRegistry|Published to the repository at %{timestamp}"
+msgstr ""
+
+msgid "PackageRegistry|PyPi"
+msgstr ""
+
+msgid "PackageRegistry|Registry Setup"
+msgstr ""
+
+msgid "PackageRegistry|Remove package"
+msgstr ""
+
+msgid "PackageRegistry|Sorry, your filter produced no results"
+msgstr ""
+
+msgid "PackageRegistry|There are no %{packageType} packages yet"
+msgstr ""
+
+msgid "PackageRegistry|There are no other versions of this package."
+msgstr ""
+
+msgid "PackageRegistry|There are no packages yet"
+msgstr ""
+
+msgid "PackageRegistry|There are no upcoming issues to display."
+msgstr ""
+
+msgid "PackageRegistry|There was a problem fetching the details for this package."
+msgstr ""
+
+msgid "PackageRegistry|This NuGet package has no dependencies."
+msgstr ""
+
+msgid "PackageRegistry|To widen your search, change or remove the filters above."
+msgstr ""
+
+msgid "PackageRegistry|Unable to fetch package version information."
+msgstr ""
+
+msgid "PackageRegistry|Unable to load package"
+msgstr ""
+
+msgid "PackageRegistry|Upcoming package managers"
+msgstr ""
+
+msgid "PackageRegistry|You are about to delete %{name}, this operation is irreversible, are you sure?"
+msgstr ""
+
+msgid "PackageRegistry|You are about to delete version %{version} of %{name}. Are you sure?"
+msgstr ""
+
+msgid "PackageRegistry|You may also need to setup authentication using an auth token. %{linkStart}See the documentation%{linkEnd} to find out more."
+msgstr ""
+
+msgid "PackageRegistry|npm"
+msgstr ""
+
+msgid "PackageRegistry|published by %{author}"
+msgstr ""
+
+msgid "PackageRegistry|yarn"
+msgstr ""
+
+msgid "PackageType|Conan"
+msgstr ""
+
+msgid "PackageType|Maven"
+msgstr ""
+
+msgid "PackageType|NPM"
+msgstr ""
+
+msgid "PackageType|NuGet"
+msgstr ""
+
+msgid "PackageType|PyPi"
+msgstr ""
+
+msgid "Packages"
+msgstr ""
+
+msgid "Packages & Registries"
+msgstr ""
+
+msgid "Page not found"
+msgstr ""
+
+msgid "Page was successfully deleted"
+msgstr ""
+
+msgid "Pages"
+msgstr ""
+
+msgid "Pages Domain"
+msgstr ""
+
+msgid "Pages getting started guide"
+msgstr ""
+
+msgid "Pagination|Go to first page"
+msgstr ""
+
+msgid "Pagination|Go to last page"
+msgstr ""
+
+msgid "Pagination|Go to next page"
+msgstr ""
+
+msgid "Pagination|Go to previous page"
+msgstr ""
+
+msgid "Pagination|Last »"
+msgstr ""
+
+msgid "Pagination|Next"
+msgstr ""
+
+msgid "Pagination|Prev"
+msgstr ""
+
+msgid "Pagination|« First"
+msgstr ""
+
+msgid "Parameter"
+msgstr ""
+
+msgid "Parameter \"job_id\" cannot exceed length of %{job_id_max_size}"
+msgstr ""
+
+msgid "Parent"
+msgstr ""
+
+msgid "Parent epic doesn't exist."
+msgstr ""
+
+msgid "Parent epic is not present."
+msgstr ""
+
+msgid "Parsing error for param :embed_json. %{message}"
+msgstr ""
+
+msgid "Part of merge request changes"
+msgstr ""
+
+msgid "Partial token for reference only"
+msgstr ""
+
+msgid "Participants"
+msgstr ""
+
+msgid "Passed"
+msgstr ""
+
+msgid "Passed on"
+msgstr ""
+
+msgid "Password"
+msgstr ""
+
+msgid "Password (optional)"
+msgstr ""
+
+msgid "Password Policy Guidelines"
+msgstr ""
+
+msgid "Password authentication is unavailable."
+msgstr ""
+
+msgid "Password confirmation"
+msgstr ""
+
+msgid "Password successfully changed"
+msgstr ""
+
+msgid "Password was successfully updated. Please login with it"
+msgstr ""
+
+msgid "Passwords should be unique and not used for any other sites or services."
+msgstr ""
+
+msgid "Past due"
+msgstr ""
+
+msgid "Paste a machine public key here. Read more about how to generate it"
+msgstr ""
+
+msgid "Paste a machine public key here. Read more about how to generate it %{link_start}here%{link_end}"
+msgstr ""
+
+msgid "Paste epic link"
+msgstr ""
+
+msgid "Paste issue link"
+msgstr ""
+
+msgid "Paste your public SSH key, which is usually contained in the file '~/.ssh/id_ed25519.pub' or '~/.ssh/id_rsa.pub' and begins with 'ssh-ed25519' or 'ssh-rsa'. Don't use your private SSH key."
+msgstr ""
+
+msgid "Patch to apply"
+msgstr ""
+
+msgid "Path"
+msgstr ""
+
+msgid "Path:"
+msgstr ""
+
+msgid "Paths can contain wildcards, like */welcome"
+msgstr ""
+
+msgid "Pause"
+msgstr ""
+
+msgid "Pause replication"
+msgstr ""
+
+msgid "Paused Runners don't accept new jobs"
+msgstr ""
+
+msgid "Pending"
+msgstr ""
+
+msgid "People without permission will never get a notification and won't be able to comment."
+msgstr ""
+
+msgid "People without permission will never get a notification."
+msgstr ""
+
+msgid "Percent rollout (logged in users)"
+msgstr ""
+
+msgid "Percentage"
+msgstr ""
+
+msgid "Perform advanced options such as changing path, transferring, exporting, or removing the group."
+msgstr ""
+
+msgid "Perform common operations on GitLab project"
+msgstr ""
+
+msgid "Performance and resource management"
+msgstr ""
+
+msgid "Performance optimization"
+msgstr ""
+
+msgid "PerformanceBar|Bullet notifications"
+msgstr ""
+
+msgid "PerformanceBar|Download"
+msgstr ""
+
+msgid "PerformanceBar|Elasticsearch calls"
+msgstr ""
+
+msgid "PerformanceBar|Frontend resources"
+msgstr ""
+
+msgid "PerformanceBar|Gitaly calls"
+msgstr ""
+
+msgid "PerformanceBar|Redis calls"
+msgstr ""
+
+msgid "PerformanceBar|Rugged calls"
+msgstr ""
+
+msgid "PerformanceBar|SQL queries"
+msgstr ""
+
+msgid "PerformanceBar|trace"
+msgstr ""
+
+msgid "Permissions"
+msgstr ""
+
+msgid "Permissions, LFS, 2FA"
+msgstr ""
+
+msgid "Personal Access Token"
+msgstr ""
+
+msgid "Personal project creation is not allowed. Please contact your administrator with questions"
+msgstr ""
+
+msgid "Phabricator Server Import"
+msgstr ""
+
+msgid "Phabricator Server URL"
+msgstr ""
+
+msgid "Phabricator Tasks"
+msgstr ""
+
+msgid "Pick a name"
+msgstr ""
+
+msgid "Pin code"
+msgstr ""
+
+msgid "Pipeline"
+msgstr ""
+
+msgid "Pipeline %{label}"
+msgstr ""
+
+msgid "Pipeline %{label} for \"%{dataTitle}\""
+msgstr ""
+
+msgid "Pipeline Schedule"
+msgstr ""
+
+msgid "Pipeline Schedules"
+msgstr ""
+
+msgid "Pipeline minutes quota"
+msgstr ""
+
+msgid "Pipeline subscriptions"
+msgstr ""
+
+msgid "Pipeline triggers"
+msgstr ""
+
+msgid "Pipeline: %{status}"
+msgstr ""
+
+msgid "PipelineCharts|CI / CD Analytics"
+msgstr ""
+
+msgid "PipelineCharts|Failed:"
+msgstr ""
+
+msgid "PipelineCharts|Overall statistics"
+msgstr ""
+
+msgid "PipelineCharts|Success ratio:"
+msgstr ""
+
+msgid "PipelineCharts|Successful:"
+msgstr ""
+
+msgid "PipelineCharts|Total:"
+msgstr ""
+
+msgid "PipelineSchedules|Activated"
+msgstr ""
+
+msgid "PipelineSchedules|Active"
+msgstr ""
+
+msgid "PipelineSchedules|All"
+msgstr ""
+
+msgid "PipelineSchedules|Inactive"
+msgstr ""
+
+msgid "PipelineSchedules|Next Run"
+msgstr ""
+
+msgid "PipelineSchedules|None"
+msgstr ""
+
+msgid "PipelineSchedules|Provide a short description for this pipeline"
+msgstr ""
+
+msgid "PipelineSchedules|Take ownership"
+msgstr ""
+
+msgid "PipelineSchedules|Target"
+msgstr ""
+
+msgid "PipelineSchedules|Variables"
+msgstr ""
+
+msgid "PipelineSheduleIntervalPattern|Custom"
+msgstr ""
+
+msgid "PipelineStatusTooltip|Pipeline: %{ciStatus}"
+msgstr ""
+
+msgid "PipelineStatusTooltip|Pipeline: %{ci_status}"
+msgstr ""
+
+msgid "Pipelines"
+msgstr ""
+
+msgid "Pipelines charts"
+msgstr ""
+
+msgid "Pipelines emails"
+msgstr ""
+
+msgid "Pipelines for last month (%{oneMonthAgo} - %{today})"
+msgstr ""
+
+msgid "Pipelines for last week (%{oneWeekAgo} - %{today})"
+msgstr ""
+
+msgid "Pipelines for last year"
+msgstr ""
+
+msgid "Pipelines for merge requests are configured. A detached pipeline runs in the context of the merge request, and not against the merged result. Learn more in the documentation for Pipelines for Merged Results."
+msgstr ""
+
+msgid "Pipelines must succeed for merge requests to be eligible to merge. Please enable pipelines for this project to continue. For more information, see the %{linkStart}documentation.%{linkEnd}"
+msgstr ""
+
+msgid "Pipelines settings for '%{project_name}' were successfully updated."
+msgstr ""
+
+msgid "Pipelines|API"
+msgstr ""
+
+msgid "Pipelines|Build with confidence"
+msgstr ""
+
+msgid "Pipelines|CI Lint"
+msgstr ""
+
+msgid "Pipelines|Child pipeline"
+msgstr ""
+
+msgid "Pipelines|Clear Runner Caches"
+msgstr ""
+
+msgid "Pipelines|Continuous Integration can help catch bugs by running your tests automatically, while Continuous Deployment can help you deliver code to your product environment."
+msgstr ""
+
+msgid "Pipelines|Get started with Pipelines"
+msgstr ""
+
+msgid "Pipelines|Group %{namespace_name} has %{percentage}%% or less Shared Runner Pipeline minutes remaining. Once it runs out, no new jobs or pipelines in its projects will run."
+msgstr ""
+
+msgid "Pipelines|Group %{namespace_name} has exceeded its pipeline minutes quota. Unless you buy additional pipeline minutes, no new jobs or pipelines in its projects will run."
+msgstr ""
+
+msgid "Pipelines|Loading Pipelines"
+msgstr ""
+
+msgid "Pipelines|Project cache successfully reset."
+msgstr ""
+
+msgid "Pipelines|Run Pipeline"
+msgstr ""
+
+msgid "Pipelines|Something went wrong while cleaning runners cache."
+msgstr ""
+
+msgid "Pipelines|There are currently no finished pipelines."
+msgstr ""
+
+msgid "Pipelines|There are currently no pipelines."
+msgstr ""
+
+msgid "Pipelines|There was an error fetching the pipelines. Try again in a few moments or contact your support team."
+msgstr ""
+
+msgid "Pipelines|This is a child pipeline within the parent pipeline"
+msgstr ""
+
+msgid "Pipelines|This project is not currently set up to run pipelines."
+msgstr ""
+
+msgid "Pipelines|parent"
+msgstr ""
+
+msgid "Pipeline|Branch name"
+msgstr ""
+
+msgid "Pipeline|Canceled"
+msgstr ""
+
+msgid "Pipeline|Commit"
+msgstr ""
+
+msgid "Pipeline|Could not retrieve the pipeline status. For troubleshooting steps, read the %{linkStart}documentation%{linkEnd}."
+msgstr ""
+
+msgid "Pipeline|Coverage"
+msgstr ""
+
+msgid "Pipeline|Created"
+msgstr ""
+
+msgid "Pipeline|Date"
+msgstr ""
+
+msgid "Pipeline|Detached merge request pipeline"
+msgstr ""
+
+msgid "Pipeline|Duration"
+msgstr ""
+
+msgid "Pipeline|Existing branch name or tag"
+msgstr ""
+
+msgid "Pipeline|Failed"
+msgstr ""
+
+msgid "Pipeline|Key"
+msgstr ""
+
+msgid "Pipeline|Manual"
+msgstr ""
+
+msgid "Pipeline|Merge train pipeline"
+msgstr ""
+
+msgid "Pipeline|Merged result pipeline"
+msgstr ""
+
+msgid "Pipeline|No pipeline has been run for this commit."
+msgstr ""
+
+msgid "Pipeline|Passed"
+msgstr ""
+
+msgid "Pipeline|Pending"
+msgstr ""
+
+msgid "Pipeline|Pipeline"
+msgstr ""
+
+msgid "Pipeline|Pipelines"
+msgstr ""
+
+msgid "Pipeline|Raw text search is not currently supported. Please use the available search tokens."
+msgstr ""
+
+msgid "Pipeline|Run Pipeline"
+msgstr ""
+
+msgid "Pipeline|Run for"
+msgstr ""
+
+msgid "Pipeline|Running"
+msgstr ""
+
+msgid "Pipeline|Search branches"
+msgstr ""
+
+msgid "Pipeline|Skipped"
+msgstr ""
+
+msgid "Pipeline|Specify variable values to be used in this run. The values specified in %{settings_link} will be used by default."
+msgstr ""
+
+msgid "Pipeline|Stages"
+msgstr ""
+
+msgid "Pipeline|Status"
+msgstr ""
+
+msgid "Pipeline|Stop pipeline"
+msgstr ""
+
+msgid "Pipeline|Stop pipeline #%{pipelineId}?"
+msgstr ""
+
+msgid "Pipeline|Tag name"
+msgstr ""
+
+msgid "Pipeline|Trigger author"
+msgstr ""
+
+msgid "Pipeline|Triggerer"
+msgstr ""
+
+msgid "Pipeline|Value"
+msgstr ""
+
+msgid "Pipeline|Variables"
+msgstr ""
+
+msgid "Pipeline|You’re about to stop pipeline %{pipelineId}."
+msgstr ""
+
+msgid "Pipeline|for"
+msgstr ""
+
+msgid "Pipeline|on"
+msgstr ""
+
+msgid "Pipeline|with stage"
+msgstr ""
+
+msgid "Pipeline|with stages"
+msgstr ""
+
+msgid "PivotalTrackerService|Comma-separated list of branches which will be automatically inspected. Leave blank to include all branches."
+msgstr ""
+
+msgid "PivotalTrackerService|Pivotal Tracker API token."
+msgstr ""
+
+msgid "PivotalTrackerService|Project Management Software (Source Commits Endpoint)"
+msgstr ""
+
+msgid "Plain diff"
+msgstr ""
+
+msgid "Plan"
+msgstr ""
+
+msgid "Plan:"
+msgstr ""
+
+msgid "PlantUML"
+msgstr ""
+
+msgid "Play"
+msgstr ""
+
+msgid "Play all manual"
+msgstr ""
+
+msgid "Please %{link_to_register} or %{link_to_sign_in} to comment"
+msgstr ""
+
+msgid "Please %{startTagRegister}register%{endRegisterTag} or %{startTagSignIn}sign in%{endSignInTag} to reply"
+msgstr ""
+
+msgid "Please accept the Terms of Service before continuing."
+msgstr ""
+
+msgid "Please add a comment in the text area above"
+msgstr ""
+
+msgid "Please add a list to your board first"
+msgstr ""
+
+msgid "Please check the configuration file for this chart"
+msgstr ""
+
+msgid "Please check the configuration file to ensure that a collection of charts has been declared."
+msgstr ""
+
+msgid "Please check the configuration file to ensure that it is available and the YAML is valid"
+msgstr ""
+
+msgid "Please check your email (%{email}) to verify that you own this address and unlock the power of CI/CD. Didn't receive it? %{resend_link}. Wrong email address? %{update_link}."
+msgstr ""
+
+msgid "Please choose a file"
+msgstr ""
+
+msgid "Please choose a group URL with no special characters."
+msgstr ""
+
+msgid "Please complete your profile with email address"
+msgstr ""
+
+msgid "Please convert %{linkStart}them to Git%{linkEnd}, and go through the %{linkToImportFlow} again."
+msgstr ""
+
+msgid "Please convert them to %{link_to_git}, and go through the %{link_to_import_flow} again."
+msgstr ""
+
+msgid "Please convert them to Git on Google Code, and go through the %{link_to_import_flow} again."
+msgstr ""
+
+msgid "Please create a password for your new account."
+msgstr ""
+
+msgid "Please create a username with only alphanumeric characters."
+msgstr ""
+
+msgid "Please create an index before enabling indexing"
+msgstr ""
+
+msgid "Please enable and migrate to hashed storage to avoid security issues and ensure data integrity. %{migrate_link}"
+msgstr ""
+
+msgid "Please ensure your account's %{account_link_start}recovery settings%{account_link_end} are up to date."
+msgstr ""
+
+msgid "Please enter a non-negative number"
+msgstr ""
+
+msgid "Please enter a number greater than %{number} (from the project settings)"
+msgstr ""
+
+msgid "Please enter a valid number"
+msgstr ""
+
+msgid "Please enter or upload a license."
+msgstr ""
+
+msgid "Please fill in a descriptive name for your group."
+msgstr ""
+
+msgid "Please follow the %{link_start}Let's Encrypt troubleshooting instructions%{link_end} to re-obtain your Let's Encrypt certificate."
+msgstr ""
+
+msgid "Please follow the Let's Encrypt troubleshooting instructions to re-obtain your Let's Encrypt certificate: %{docs_url}."
+msgstr ""
+
+msgid "Please migrate all existing projects to hashed storage to avoid security issues and ensure data integrity. %{migrate_link}"
+msgstr ""
+
+msgid "Please note that this application is not provided by GitLab and you should verify its authenticity before allowing access."
+msgstr ""
+
+msgid "Please provide a name"
+msgstr ""
+
+msgid "Please provide a valid URL"
+msgstr ""
+
+msgid "Please provide a valid email address."
+msgstr ""
+
+msgid "Please provide attributes to update"
+msgstr ""
+
+msgid "Please refer to <a href=\"%{docs_url}\">%{docs_url}</a>"
+msgstr ""
+
+msgid "Please retype the email address."
+msgstr ""
+
+msgid "Please select"
+msgstr ""
+
+msgid "Please select a Jira project"
+msgstr ""
+
+msgid "Please select a country"
+msgstr ""
+
+msgid "Please select a file"
+msgstr ""
+
+msgid "Please select a group."
+msgstr ""
+
+msgid "Please select a valid target branch"
+msgstr ""
+
+msgid "Please select and add a member"
+msgstr ""
+
+msgid "Please select at least one filter to see results"
+msgstr ""
+
+msgid "Please set a new password before proceeding."
+msgstr ""
+
+msgid "Please solve the reCAPTCHA"
+msgstr ""
+
+msgid "Please try again"
+msgstr ""
+
+msgid "Please type %{phrase_code} to proceed or close this modal to cancel."
+msgstr ""
+
+msgid "Please upgrade PostgreSQL to version 9.6 or greater. The status of the replication cannot be determined reliably with the current version."
+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 moment, this page will automatically refresh when ready."
+msgstr ""
+
+msgid "Please wait while we connect to your repository. Refresh at will."
+msgstr ""
+
+msgid "Please wait while we import the repository for you. Refresh at will."
+msgstr ""
+
+msgid "Plugins directory is deprecated and will be removed in 14.0. Please move this file into /file_hooks directory."
+msgstr ""
+
+msgid "Pod does not exist"
+msgstr ""
+
+msgid "Pod not found"
+msgstr ""
+
+msgid "Pods in use"
+msgstr ""
+
+msgid "Point to any links you like: documentation, built binaries, or other related materials. These can be internal or external links from your GitLab instance. Duplicate URLs are not allowed."
+msgstr ""
+
+msgid "Pre-defined push rules."
+msgstr ""
+
+msgid "Preferences"
+msgstr ""
+
+msgid "Preferences saved."
+msgstr ""
+
+msgid "Preferences|Behavior"
+msgstr ""
+
+msgid "Preferences|Choose between fixed (max. 1280px) and fluid (%{percentage}) application layout."
+msgstr ""
+
+msgid "Preferences|Choose what content you want to see on a project’s overview page."
+msgstr ""
+
+msgid "Preferences|Customize integrations with third party services."
+msgstr ""
+
+msgid "Preferences|Customize the appearance of the application header and navigation sidebar."
+msgstr ""
+
+msgid "Preferences|Default dashboard"
+msgstr ""
+
+msgid "Preferences|Display time in 24-hour format"
+msgstr ""
+
+msgid "Preferences|Enable integrated code intelligence on code views"
+msgstr ""
+
+msgid "Preferences|For example: 30 mins ago."
+msgstr ""
+
+msgid "Preferences|Integrations"
+msgstr ""
+
+msgid "Preferences|Layout width"
+msgstr ""
+
+msgid "Preferences|Must be a number between %{min} and %{max}"
+msgstr ""
+
+msgid "Preferences|Navigation theme"
+msgstr ""
+
+msgid "Preferences|Project overview content"
+msgstr ""
+
+msgid "Preferences|Render whitespace characters in the Web IDE"
+msgstr ""
+
+msgid "Preferences|Show whitespace changes in diffs"
+msgstr ""
+
+msgid "Preferences|Sourcegraph"
+msgstr ""
+
+msgid "Preferences|Syntax highlighting theme"
+msgstr ""
+
+msgid "Preferences|Tab width"
+msgstr ""
+
+msgid "Preferences|These settings will update how dates and times are displayed for you."
+msgstr ""
+
+msgid "Preferences|This feature is experimental and translations are not complete yet"
+msgstr ""
+
+msgid "Preferences|This setting allows you to customize the appearance of the syntax."
+msgstr ""
+
+msgid "Preferences|This setting allows you to customize the behavior of the system layout and default views."
+msgstr ""
+
+msgid "Preferences|Time display"
+msgstr ""
+
+msgid "Preferences|Time format"
+msgstr ""
+
+msgid "Preferences|Time preferences"
+msgstr ""
+
+msgid "Preferences|Use relative times"
+msgstr ""
+
+msgid "Press %{key}-C to copy"
+msgstr ""
+
+msgid "Prev"
+msgstr ""
+
+msgid "Prevent adding new members to project membership within this group"
+msgstr ""
+
+msgid "Prevent approval of merge requests by merge request author"
+msgstr ""
+
+msgid "Prevent approval of merge requests by merge request committers"
+msgstr ""
+
+msgid "Prevent environment from auto-stopping"
+msgstr ""
+
+msgid "Prevent users from changing their profile name"
+msgstr ""
+
+msgid "Prevent users from modifying merge request approvers list"
+msgstr ""
+
+msgid "Prevent users from performing write operations on GitLab while performing maintenance."
+msgstr ""
+
+msgid "Preview"
+msgstr ""
+
+msgid "Preview Markdown"
+msgstr ""
+
+msgid "Preview changes"
+msgstr ""
+
+msgid "Preview payload"
+msgstr ""
+
+msgid "Previous Artifacts"
+msgstr ""
+
+msgid "Previous commit"
+msgstr ""
+
+msgid "Previous file in diff"
+msgstr ""
+
+msgid "Previous unresolved discussion"
+msgstr ""
+
+msgid "Primary"
+msgstr ""
+
+msgid "Prioritize"
+msgstr ""
+
+msgid "Prioritize label"
+msgstr ""
+
+msgid "Prioritized Labels"
+msgstr ""
+
+msgid "Prioritized label"
+msgstr ""
+
+msgid "Private"
+msgstr ""
+
+msgid "Private - Project access must be granted explicitly to each user. If this project is part of a group, access will be granted to members of the group."
+msgstr ""
+
+msgid "Private - The group and its projects can only be viewed by members."
+msgstr ""
+
+msgid "Private group(s)"
+msgstr ""
+
+msgid "Private profile"
+msgstr ""
+
+msgid "Private projects Minutes cost factor"
+msgstr ""
+
+msgid "Private projects can be created in your personal namespace with:"
+msgstr ""
+
+msgid "Proceed"
+msgstr ""
+
+msgid "Productivity"
+msgstr ""
+
+msgid "Productivity Analytics"
+msgstr ""
+
+msgid "Productivity analytics can help identify the problems that are delaying your team"
+msgstr ""
+
+msgid "ProductivityAanalytics|Merge requests"
+msgstr ""
+
+msgid "ProductivityAanalytics|is earlier than the allowed minimum date"
+msgstr ""
+
+msgid "ProductivityAnalytics|Ascending"
+msgstr ""
+
+msgid "ProductivityAnalytics|Days"
+msgstr ""
+
+msgid "ProductivityAnalytics|Days to merge"
+msgstr ""
+
+msgid "ProductivityAnalytics|Descending"
+msgstr ""
+
+msgid "ProductivityAnalytics|Hours"
+msgstr ""
+
+msgid "ProductivityAnalytics|List"
+msgstr ""
+
+msgid "ProductivityAnalytics|Merge Requests"
+msgstr ""
+
+msgid "ProductivityAnalytics|Merge date"
+msgstr ""
+
+msgid "ProductivityAnalytics|Merge requests"
+msgstr ""
+
+msgid "ProductivityAnalytics|Time to merge"
+msgstr ""
+
+msgid "ProductivityAnalytics|Trendline"
+msgstr ""
+
+msgid "ProductivityAnalytics|is earlier than the given merged at after date"
+msgstr ""
+
+msgid "Profile"
+msgstr ""
+
+msgid "Profile Settings"
+msgstr ""
+
+msgid "ProfileSession|on"
+msgstr ""
+
+msgid "Profiles| You are about to permanently delete %{yourAccount}, and all of the issues, merge requests, and groups linked to your account. Once you confirm %{deleteAccount}, it cannot be undone or recovered."
+msgstr ""
+
+msgid "Profiles| You are going to change the username %{currentUsernameBold} to %{newUsernameBold}. Profile and projects will be redirected to the %{newUsername} namespace but this redirect will expire once the %{currentUsername} namespace is registered by another user or group. Please update your Git repository remotes as soon as possible."
+msgstr ""
+
+msgid "Profiles|@username"
+msgstr ""
+
+msgid "Profiles|Account scheduled for removal."
+msgstr ""
+
+msgid "Profiles|Activate signin with one of the following services"
+msgstr ""
+
+msgid "Profiles|Active"
+msgstr ""
+
+msgid "Profiles|Add key"
+msgstr ""
+
+msgid "Profiles|Add status emoji"
+msgstr ""
+
+msgid "Profiles|Avatar cropper"
+msgstr ""
+
+msgid "Profiles|Avatar will be removed. Are you sure?"
+msgstr ""
+
+msgid "Profiles|Bio"
+msgstr ""
+
+msgid "Profiles|Change username"
+msgstr ""
+
+msgid "Profiles|Changing your username can have unintended side effects."
+msgstr ""
+
+msgid "Profiles|Choose file..."
+msgstr ""
+
+msgid "Profiles|Choose to show contributions of private projects on your public profile without any project, repository or organization information"
+msgstr ""
+
+msgid "Profiles|City, country"
+msgstr ""
+
+msgid "Profiles|Clear status"
+msgstr ""
+
+msgid "Profiles|Click on icon to activate signin with one of the following services"
+msgstr ""
+
+msgid "Profiles|Commit email"
+msgstr ""
+
+msgid "Profiles|Connect"
+msgstr ""
+
+msgid "Profiles|Connected Accounts"
+msgstr ""
+
+msgid "Profiles|Current path: %{path}"
+msgstr ""
+
+msgid "Profiles|Current status"
+msgstr ""
+
+msgid "Profiles|Default notification email"
+msgstr ""
+
+msgid "Profiles|Delete Account"
+msgstr ""
+
+msgid "Profiles|Delete account"
+msgstr ""
+
+msgid "Profiles|Delete your account?"
+msgstr ""
+
+msgid "Profiles|Deleting an account has the following effects:"
+msgstr ""
+
+msgid "Profiles|Disconnect"
+msgstr ""
+
+msgid "Profiles|Do not show on profile"
+msgstr ""
+
+msgid "Profiles|Don't display activity-related personal information on your profiles"
+msgstr ""
+
+msgid "Profiles|Edit Profile"
+msgstr ""
+
+msgid "Profiles|Enter your name, so people you know can recognize you"
+msgstr ""
+
+msgid "Profiles|Expires at"
+msgstr ""
+
+msgid "Profiles|Expires:"
+msgstr ""
+
+msgid "Profiles|Feed token was successfully reset"
+msgstr ""
+
+msgid "Profiles|Full name"
+msgstr ""
+
+msgid "Profiles|Give your individual key a title"
+msgstr ""
+
+msgid "Profiles|Include private contributions on my profile"
+msgstr ""
+
+msgid "Profiles|Incoming email token was successfully reset"
+msgstr ""
+
+msgid "Profiles|Increase your account's security by enabling Two-Factor Authentication (2FA)"
+msgstr ""
+
+msgid "Profiles|Invalid password"
+msgstr ""
+
+msgid "Profiles|Invalid username"
+msgstr ""
+
+msgid "Profiles|Key"
+msgstr ""
+
+msgid "Profiles|Last used:"
+msgstr ""
+
+msgid "Profiles|Learn more"
+msgstr ""
+
+msgid "Profiles|Location"
+msgstr ""
+
+msgid "Profiles|Made a private contribution"
+msgstr ""
+
+msgid "Profiles|Main settings"
+msgstr ""
+
+msgid "Profiles|No file chosen"
+msgstr ""
+
+msgid "Profiles|Notification email"
+msgstr ""
+
+msgid "Profiles|Organization"
+msgstr ""
+
+msgid "Profiles|Path"
+msgstr ""
+
+msgid "Profiles|Position and size your new avatar"
+msgstr ""
+
+msgid "Profiles|Primary email"
+msgstr ""
+
+msgid "Profiles|Private contributions"
+msgstr ""
+
+msgid "Profiles|Profile was successfully updated"
+msgstr ""
+
+msgid "Profiles|Public Avatar"
+msgstr ""
+
+msgid "Profiles|Public email"
+msgstr ""
+
+msgid "Profiles|Remove avatar"
+msgstr ""
+
+msgid "Profiles|Set new profile picture"
+msgstr ""
+
+msgid "Profiles|Social sign-in"
+msgstr ""
+
+msgid "Profiles|Some options are unavailable for LDAP accounts"
+msgstr ""
+
+msgid "Profiles|Static object token was successfully reset"
+msgstr ""
+
+msgid "Profiles|Tell us about yourself in fewer than 250 characters"
+msgstr ""
+
+msgid "Profiles|The ability to update your name has been disabled by your administrator."
+msgstr ""
+
+msgid "Profiles|The maximum file size allowed is 200KB."
+msgstr ""
+
+msgid "Profiles|This doesn't look like a public SSH key, are you sure you want to add it?"
+msgstr ""
+
+msgid "Profiles|This email will be displayed on your public profile"
+msgstr ""
+
+msgid "Profiles|This email will be used for web based operations, such as edits and merges. %{commit_email_link_start}Learn more%{commit_email_link_end}"
+msgstr ""
+
+msgid "Profiles|This emoji and message will appear on your profile and throughout the interface."
+msgstr ""
+
+msgid "Profiles|This information will appear on your profile"
+msgstr ""
+
+msgid "Profiles|Time settings"
+msgstr ""
+
+msgid "Profiles|Two-Factor Authentication"
+msgstr ""
+
+msgid "Profiles|Type your %{confirmationValue} to confirm:"
+msgstr ""
+
+msgid "Profiles|Typically starts with \"ssh-ed25519 …\" or \"ssh-rsa …\""
+msgstr ""
+
+msgid "Profiles|Update profile settings"
+msgstr ""
+
+msgid "Profiles|Update username"
+msgstr ""
+
+msgid "Profiles|Upload new avatar"
+msgstr ""
+
+msgid "Profiles|Use a private email - %{email}"
+msgstr ""
+
+msgid "Profiles|User ID"
+msgstr ""
+
+msgid "Profiles|Username change failed - %{message}"
+msgstr ""
+
+msgid "Profiles|Username successfully changed"
+msgstr ""
+
+msgid "Profiles|Using emojis in names seems fun, but please try to set a status message instead"
+msgstr ""
+
+msgid "Profiles|What's your status?"
+msgstr ""
+
+msgid "Profiles|Who you represent or work for"
+msgstr ""
+
+msgid "Profiles|You can change your avatar here"
+msgstr ""
+
+msgid "Profiles|You can change your avatar here or remove the current avatar to revert to %{gravatar_link}"
+msgstr ""
+
+msgid "Profiles|You can set your current timezone here"
+msgstr ""
+
+msgid "Profiles|You can upload your avatar here"
+msgstr ""
+
+msgid "Profiles|You can upload your avatar here or change it at %{gravatar_link}"
+msgstr ""
+
+msgid "Profiles|You don't have access to delete this user."
+msgstr ""
+
+msgid "Profiles|You must transfer ownership or delete these groups before you can delete your account."
+msgstr ""
+
+msgid "Profiles|Your LinkedIn profile name from linkedin.com/in/profilename"
+msgstr ""
+
+msgid "Profiles|Your account is currently an owner in these groups:"
+msgstr ""
+
+msgid "Profiles|Your email address was automatically set based on your %{provider_label} account"
+msgstr ""
+
+msgid "Profiles|Your key has expired"
+msgstr ""
+
+msgid "Profiles|Your location was automatically set based on your %{provider_label} account"
+msgstr ""
+
+msgid "Profiles|Your name was automatically set based on your %{provider_label} account, so people you know can recognize you"
+msgstr ""
+
+msgid "Profiles|Your status"
+msgstr ""
+
+msgid "Profiles|e.g. My MacBook key"
+msgstr ""
+
+msgid "Profiles|username"
+msgstr ""
+
+msgid "Profiles|website.com"
+msgstr ""
+
+msgid "Profiles|your account"
+msgstr ""
+
+msgid "Profile|%{job_title} at %{organization}"
+msgstr ""
+
+msgid "Profiling - Performance bar"
+msgstr ""
+
+msgid "Programming languages used in this repository"
+msgstr ""
+
+msgid "Progress"
+msgstr ""
+
+msgid "Project"
+msgstr ""
+
+msgid "Project \"%{name}\" is no longer available. Select another project to continue."
+msgstr ""
+
+msgid "Project %{project_repo} could not be found"
+msgstr ""
+
+msgid "Project & Group can not be assigned at the same time"
+msgstr ""
+
+msgid "Project '%{project_name}' is being imported."
+msgstr ""
+
+msgid "Project '%{project_name}' is in the process of being deleted."
+msgstr ""
+
+msgid "Project '%{project_name}' is restored."
+msgstr ""
+
+msgid "Project '%{project_name}' queued for deletion."
+msgstr ""
+
+msgid "Project '%{project_name}' was successfully created."
+msgstr ""
+
+msgid "Project '%{project_name}' was successfully updated."
+msgstr ""
+
+msgid "Project '%{project_name}' will be deleted on %{date}"
+msgstr ""
+
+msgid "Project Access Tokens"
+msgstr ""
+
+msgid "Project Audit Events"
+msgstr ""
+
+msgid "Project Badges"
+msgstr ""
+
+msgid "Project Files"
+msgstr ""
+
+msgid "Project ID"
+msgstr ""
+
+msgid "Project URL"
+msgstr ""
+
+msgid "Project access must be granted explicitly to each user. If this project is part of a group, access will be granted to members of the group."
+msgstr ""
+
+msgid "Project already deleted"
+msgstr ""
+
+msgid "Project and wiki repositories"
+msgstr ""
+
+msgid "Project avatar"
+msgstr ""
+
+msgid "Project cannot be shared with the group it is in or one of its ancestors."
+msgstr ""
+
+msgid "Project clone URL"
+msgstr ""
+
+msgid "Project configuration, including services"
+msgstr ""
+
+msgid "Project description (optional)"
+msgstr ""
+
+msgid "Project details"
+msgstr ""
+
+msgid "Project does not exist or you don't have permission to perform this action"
+msgstr ""
+
+msgid "Project export could not be deleted."
+msgstr ""
+
+msgid "Project export enabled"
+msgstr ""
+
+msgid "Project export has been deleted."
+msgstr ""
+
+msgid "Project export link has expired. Please generate a new export from your project settings."
+msgstr ""
+
+msgid "Project export started. A download link will be sent by email and made available on this page."
+msgstr ""
+
+msgid "Project has too many %{label_for_message} to search"
+msgstr ""
+
+msgid "Project is required when cluster_type is :project"
+msgstr ""
+
+msgid "Project members"
+msgstr ""
+
+msgid "Project milestone"
+msgstr ""
+
+msgid "Project name"
+msgstr ""
+
+msgid "Project name suffix"
+msgstr ""
+
+msgid "Project name suffix is a user-defined string which will be appended to the project path, and will form the Service Desk email address."
+msgstr ""
+
+msgid "Project order will not be saved as local storage is not available."
+msgstr ""
+
+msgid "Project overview"
+msgstr ""
+
+msgid "Project path"
+msgstr ""
+
+msgid "Project scanning help page"
+msgstr ""
+
+msgid "Project security status"
+msgstr ""
+
+msgid "Project security status help page"
+msgstr ""
+
+msgid "Project slug"
+msgstr ""
+
+msgid "Project uploads"
+msgstr ""
+
+msgid "Project visibility level will be changed to match namespace rules when transferring to a group."
+msgstr ""
+
+msgid "Project: %{name}"
+msgstr ""
+
+msgid "ProjectActivityRSS|Subscribe"
+msgstr ""
+
+msgid "ProjectCreationLevel|Allowed to create projects"
+msgstr ""
+
+msgid "ProjectCreationLevel|Default project creation protection"
+msgstr ""
+
+msgid "ProjectCreationLevel|Developers + Maintainers"
+msgstr ""
+
+msgid "ProjectCreationLevel|Maintainers"
+msgstr ""
+
+msgid "ProjectCreationLevel|No one"
+msgstr ""
+
+msgid "ProjectFileTree|Name"
+msgstr ""
+
+msgid "ProjectLastActivity|Never"
+msgstr ""
+
+msgid "ProjectLifecycle|Stage"
+msgstr ""
+
+msgid "ProjectOverview|Fork"
+msgstr ""
+
+msgid "ProjectOverview|Forks"
+msgstr ""
+
+msgid "ProjectOverview|Go to your fork"
+msgstr ""
+
+msgid "ProjectOverview|Star"
+msgstr ""
+
+msgid "ProjectOverview|Starrer"
+msgstr ""
+
+msgid "ProjectOverview|Starrers"
+msgstr ""
+
+msgid "ProjectOverview|Unstar"
+msgstr ""
+
+msgid "ProjectOverview|You have reached your project limit"
+msgstr ""
+
+msgid "ProjectOverview|You must sign in to star a project"
+msgstr ""
+
+msgid "ProjectPage|Project ID: %{project_id}"
+msgstr ""
+
+msgid "ProjectSelect| or group"
+msgstr ""
+
+msgid "ProjectSelect|Search for project"
+msgstr ""
+
+msgid "ProjectService|%{service_title}: status off"
+msgstr ""
+
+msgid "ProjectService|%{service_title}: status on"
+msgstr ""
+
+msgid "ProjectService|Comment"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered by a push to the repository"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a commit is created/updated"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a confidential issue is created/updated/closed"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a deployment finishes"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a merge request is created/updated/merged"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a new tag is pushed to the repository"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a new, unique alert is recorded"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a pipeline status changes"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a wiki page is created/updated"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when an issue is created/updated/closed"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when someone adds a comment"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when someone adds a comment on a confidential issue"
+msgstr ""
+
+msgid "ProjectService|Perform common operations on GitLab project: %{project_name}"
+msgstr ""
+
+msgid "ProjectService|To set up this service:"
+msgstr ""
+
+msgid "ProjectSettings|Additional merge request capabilities that influence how and when merges will be performed"
+msgstr ""
+
+msgid "ProjectSettings|All discussions must be resolved"
+msgstr ""
+
+msgid "ProjectSettings|Allow users to make copies of your repository to a new project"
+msgstr ""
+
+msgid "ProjectSettings|Allow users to request access"
+msgstr ""
+
+msgid "ProjectSettings|Automatically resolve merge request diff discussions when they become outdated"
+msgstr ""
+
+msgid "ProjectSettings|Badges"
+msgstr ""
+
+msgid "ProjectSettings|Build, test, and deploy your changes"
+msgstr ""
+
+msgid "ProjectSettings|Choose your merge method, merge options, merge checks, and merge suggestions."
+msgstr ""
+
+msgid "ProjectSettings|Choose your merge method, merge options, merge checks, merge suggestions, and set up a default description template for merge requests."
+msgstr ""
+
+msgid "ProjectSettings|Contact an admin to change this setting."
+msgstr ""
+
+msgid "ProjectSettings|Container registry"
+msgstr ""
+
+msgid "ProjectSettings|Customize your project badges."
+msgstr ""
+
+msgid "ProjectSettings|Disable email notifications"
+msgstr ""
+
+msgid "ProjectSettings|Enable 'Delete source branch' option by default"
+msgstr ""
+
+msgid "ProjectSettings|Enable merge trains and pipelines for merged results"
+msgstr ""
+
+msgid "ProjectSettings|Every merge creates a merge commit"
+msgstr ""
+
+msgid "ProjectSettings|Every project can have its own space to store its Docker images"
+msgstr ""
+
+msgid "ProjectSettings|Every project can have its own space to store its packages"
+msgstr ""
+
+msgid "ProjectSettings|Everyone"
+msgstr ""
+
+msgid "ProjectSettings|Existing merge requests and protected branches are not affected"
+msgstr ""
+
+msgid "ProjectSettings|Failed to protect the tag"
+msgstr ""
+
+msgid "ProjectSettings|Failed to update tag!"
+msgstr ""
+
+msgid "ProjectSettings|Fast-forward merge"
+msgstr ""
+
+msgid "ProjectSettings|Fast-forward merges only"
+msgstr ""
+
+msgid "ProjectSettings|Forks"
+msgstr ""
+
+msgid "ProjectSettings|Git Large File Storage (LFS)"
+msgstr ""
+
+msgid "ProjectSettings|Internal"
+msgstr ""
+
+msgid "ProjectSettings|Issues"
+msgstr ""
+
+msgid "ProjectSettings|LFS objects from this repository are still available to forks. %{linkStart}How do I remove them?%{linkEnd}"
+msgstr ""
+
+msgid "ProjectSettings|Learn more about badges."
+msgstr ""
+
+msgid "ProjectSettings|Lightweight issue tracking system for this project"
+msgstr ""
+
+msgid "ProjectSettings|Manages large files such as audio, video, and graphics files"
+msgstr ""
+
+msgid "ProjectSettings|Merge checks"
+msgstr ""
+
+msgid "ProjectSettings|Merge commit"
+msgstr ""
+
+msgid "ProjectSettings|Merge commit with semi-linear history"
+msgstr ""
+
+msgid "ProjectSettings|Merge method"
+msgstr ""
+
+msgid "ProjectSettings|Merge options"
+msgstr ""
+
+msgid "ProjectSettings|Merge requests"
+msgstr ""
+
+msgid "ProjectSettings|Merge suggestions"
+msgstr ""
+
+msgid "ProjectSettings|No merge commits are created"
+msgstr ""
+
+msgid "ProjectSettings|Note: the container registry is always visible when a project is public"
+msgstr ""
+
+msgid "ProjectSettings|Only signed commits can be pushed to this repository."
+msgstr ""
+
+msgid "ProjectSettings|Packages"
+msgstr ""
+
+msgid "ProjectSettings|Pages"
+msgstr ""
+
+msgid "ProjectSettings|Pages for project documentation"
+msgstr ""
+
+msgid "ProjectSettings|Pipelines"
+msgstr ""
+
+msgid "ProjectSettings|Pipelines for merge requests must be enabled in the CI/CD configuration file, or pipelines could be unresolvable or dropped"
+msgstr ""
+
+msgid "ProjectSettings|Pipelines must succeed"
+msgstr ""
+
+msgid "ProjectSettings|Pipelines need to be configured to enable this feature."
+msgstr ""
+
+msgid "ProjectSettings|Private"
+msgstr ""
+
+msgid "ProjectSettings|Project visibility"
+msgstr ""
+
+msgid "ProjectSettings|Public"
+msgstr ""
+
+msgid "ProjectSettings|Repository"
+msgstr ""
+
+msgid "ProjectSettings|Share code pastes with others out of Git repository"
+msgstr ""
+
+msgid "ProjectSettings|Show default award emojis"
+msgstr ""
+
+msgid "ProjectSettings|Show link to create/view merge request when pushing from the command line"
+msgstr ""
+
+msgid "ProjectSettings|Skipped pipelines are considered successful"
+msgstr ""
+
+msgid "ProjectSettings|Snippets"
+msgstr ""
+
+msgid "ProjectSettings|Submit changes to be merged upstream"
+msgstr ""
+
+msgid "ProjectSettings|The commit message used to apply merge request suggestions"
+msgstr ""
+
+msgid "ProjectSettings|The variables GitLab supports:"
+msgstr ""
+
+msgid "ProjectSettings|These checks must pass before merge requests can be merged"
+msgstr ""
+
+msgid "ProjectSettings|This introduces the risk of merging changes that will not pass the pipeline."
+msgstr ""
+
+msgid "ProjectSettings|This setting is applied on the server level and can be overridden by an admin."
+msgstr ""
+
+msgid "ProjectSettings|This setting is applied on the server level but has been overridden for this project."
+msgstr ""
+
+msgid "ProjectSettings|This setting will be applied to all projects unless overridden by an admin."
+msgstr ""
+
+msgid "ProjectSettings|This setting will override user notification preferences for all project members."
+msgstr ""
+
+msgid "ProjectSettings|This will dictate the commit history when you merge a merge request"
+msgstr ""
+
+msgid "ProjectSettings|Users can only push commits to this repository that were committed with one of their own verified emails."
+msgstr ""
+
+msgid "ProjectSettings|View and edit files in this project"
+msgstr ""
+
+msgid "ProjectSettings|View and edit files in this project. Non-project members will only have read access"
+msgstr ""
+
+msgid "ProjectSettings|When conflicts arise the user is given the option to rebase"
+msgstr ""
+
+msgid "ProjectSettings|When enabled, issues, merge requests, and snippets will always show thumbs-up and thumbs-down award emoji buttons."
+msgstr ""
+
+msgid "ProjectSettings|Wiki"
+msgstr ""
+
+msgid "ProjectSettings|With GitLab Pages you can host your static websites on GitLab"
+msgstr ""
+
+msgid "ProjectSettings|With Metrics Dashboard you can visualize this project performance metrics"
+msgstr ""
+
+msgid "ProjectTemplates|.NET Core"
+msgstr ""
+
+msgid "ProjectTemplates|Android"
+msgstr ""
+
+msgid "ProjectTemplates|GitLab Cluster Management"
+msgstr ""
+
+msgid "ProjectTemplates|Go Micro"
+msgstr ""
+
+msgid "ProjectTemplates|HIPAA Audit Protocol"
+msgstr ""
+
+msgid "ProjectTemplates|Netlify/GitBook"
+msgstr ""
+
+msgid "ProjectTemplates|Netlify/Hexo"
+msgstr ""
+
+msgid "ProjectTemplates|Netlify/Hugo"
+msgstr ""
+
+msgid "ProjectTemplates|Netlify/Jekyll"
+msgstr ""
+
+msgid "ProjectTemplates|Netlify/Plain HTML"
+msgstr ""
+
+msgid "ProjectTemplates|NodeJS Express"
+msgstr ""
+
+msgid "ProjectTemplates|Pages/Gatsby"
+msgstr ""
+
+msgid "ProjectTemplates|Pages/GitBook"
+msgstr ""
+
+msgid "ProjectTemplates|Pages/Hexo"
+msgstr ""
+
+msgid "ProjectTemplates|Pages/Hugo"
+msgstr ""
+
+msgid "ProjectTemplates|Pages/Jekyll"
+msgstr ""
+
+msgid "ProjectTemplates|Pages/Plain HTML"
+msgstr ""
+
+msgid "ProjectTemplates|Ruby on Rails"
+msgstr ""
+
+msgid "ProjectTemplates|SalesforceDX"
+msgstr ""
+
+msgid "ProjectTemplates|Serverless Framework/JS"
+msgstr ""
+
+msgid "ProjectTemplates|Spring"
+msgstr ""
+
+msgid "ProjectTemplates|Static Site Editor/Middleman"
+msgstr ""
+
+msgid "ProjectTemplates|iOS (Swift)"
+msgstr ""
+
+msgid "Projects"
+msgstr ""
+
+msgid "Projects (%{count})"
+msgstr ""
+
+msgid "Projects Successfully Retrieved"
+msgstr ""
+
+msgid "Projects are graded based on the highest severity vulnerability present"
+msgstr ""
+
+msgid "Projects shared with %{group_name}"
+msgstr ""
+
+msgid "Projects that belong to a group are prefixed with the group namespace. Existing projects may be moved into a group."
+msgstr ""
+
+msgid "Projects to index"
+msgstr ""
+
+msgid "Projects with critical vulnerabilities"
+msgstr ""
+
+msgid "Projects with high or unknown vulnerabilities"
+msgstr ""
+
+msgid "Projects with low vulnerabilities"
+msgstr ""
+
+msgid "Projects with medium vulnerabilities"
+msgstr ""
+
+msgid "Projects with no vulnerabilities and security scanning enabled"
+msgstr ""
+
+msgid "Projects with write access"
+msgstr ""
+
+msgid "ProjectsDropdown|Frequently visited"
+msgstr ""
+
+msgid "ProjectsDropdown|Loading projects"
+msgstr ""
+
+msgid "ProjectsDropdown|Projects you visit often will appear here"
+msgstr ""
+
+msgid "ProjectsDropdown|Search your projects"
+msgstr ""
+
+msgid "ProjectsDropdown|Something went wrong on our end."
+msgstr ""
+
+msgid "ProjectsDropdown|Sorry, no projects matched your search"
+msgstr ""
+
+msgid "ProjectsDropdown|This feature requires browser localStorage support"
+msgstr ""
+
+msgid "ProjectsNew|Allows you to immediately clone this project’s repository. Skip this if you plan to push up an existing repository."
+msgstr ""
+
+msgid "ProjectsNew|Blank"
+msgstr ""
+
+msgid "ProjectsNew|Blank project"
+msgstr ""
+
+msgid "ProjectsNew|Connect your external repository to GitLab CI/CD."
+msgstr ""
+
+msgid "ProjectsNew|Contact an administrator to enable options for importing your project."
+msgstr ""
+
+msgid "ProjectsNew|Create"
+msgstr ""
+
+msgid "ProjectsNew|Create a blank project to house your files, plan your work, and collaborate on code, among other things."
+msgstr ""
+
+msgid "ProjectsNew|Create blank project"
+msgstr ""
+
+msgid "ProjectsNew|Create from template"
+msgstr ""
+
+msgid "ProjectsNew|Create new project"
+msgstr ""
+
+msgid "ProjectsNew|Creating project & repository."
+msgstr ""
+
+msgid "ProjectsNew|Description format"
+msgstr ""
+
+msgid "ProjectsNew|Import"
+msgstr ""
+
+msgid "ProjectsNew|Import project"
+msgstr ""
+
+msgid "ProjectsNew|Initialize repository with a README"
+msgstr ""
+
+msgid "ProjectsNew|No import options available"
+msgstr ""
+
+msgid "ProjectsNew|Please wait a moment, this page will automatically refresh when ready."
+msgstr ""
+
+msgid "ProjectsNew|Project description %{tag_start}(optional)%{tag_end}"
+msgstr ""
+
+msgid "ProjectsNew|Run CI/CD for external repository"
+msgstr ""
+
+msgid "ProjectsNew|Template"
+msgstr ""
+
+msgid "ProjectsNew|Visibility Level"
+msgstr ""
+
+msgid "ProjectsNew|Want to house several dependent projects under the same namespace? %{link_start}Create a group.%{link_end}"
+msgstr ""
+
+msgid "Prometheus"
+msgstr ""
+
+msgid "PrometheusAlerts|%{count} alerts applied"
+msgstr ""
+
+msgid "PrometheusAlerts|%{firingCount} firing"
+msgstr ""
+
+msgid "PrometheusAlerts|Add alert"
+msgstr ""
+
+msgid "PrometheusAlerts|Edit alert"
+msgstr ""
+
+msgid "PrometheusAlerts|Error creating alert"
+msgstr ""
+
+msgid "PrometheusAlerts|Error deleting alert"
+msgstr ""
+
+msgid "PrometheusAlerts|Error fetching alert"
+msgstr ""
+
+msgid "PrometheusAlerts|Error saving alert"
+msgstr ""
+
+msgid "PrometheusAlerts|Firing: %{alerts}"
+msgstr ""
+
+msgid "PrometheusAlerts|Firing: %{alert}"
+msgstr ""
+
+msgid "PrometheusAlerts|Operator"
+msgstr ""
+
+msgid "PrometheusAlerts|Select query"
+msgstr ""
+
+msgid "PrometheusAlerts|Threshold"
+msgstr ""
+
+msgid "PrometheusService|%{exporters} with %{metrics} were found"
+msgstr ""
+
+msgid "PrometheusService|<p class=\"text-tertiary\">No <a href=\"%{docsUrl}\">common metrics</a> were found</p>"
+msgstr ""
+
+msgid "PrometheusService|Active"
+msgstr ""
+
+msgid "PrometheusService|Auto configuration"
+msgstr ""
+
+msgid "PrometheusService|Automatically deploy and configure Prometheus on your clusters to monitor your project’s environments"
+msgstr ""
+
+msgid "PrometheusService|Client ID of the IAP secured resource (looks like IAP_CLIENT_ID.apps.googleusercontent.com)"
+msgstr ""
+
+msgid "PrometheusService|Common metrics"
+msgstr ""
+
+msgid "PrometheusService|Common metrics are automatically monitored based on a library of metrics from popular exporters."
+msgstr ""
+
+msgid "PrometheusService|Contents of the credentials.json file of your service account, like: { \"type\": \"service_account\", \"project_id\": ... }"
+msgstr ""
+
+msgid "PrometheusService|Custom metrics"
+msgstr ""
+
+msgid "PrometheusService|Custom metrics require Prometheus installed on a cluster with environment scope \"*\" OR a manually configured Prometheus to be available."
+msgstr ""
+
+msgid "PrometheusService|Enable Prometheus to define custom metrics, using either option above"
+msgstr ""
+
+msgid "PrometheusService|Finding and configuring metrics..."
+msgstr ""
+
+msgid "PrometheusService|Finding custom metrics..."
+msgstr ""
+
+msgid "PrometheusService|Install Prometheus on clusters"
+msgstr ""
+
+msgid "PrometheusService|Manage clusters"
+msgstr ""
+
+msgid "PrometheusService|Manual configuration"
+msgstr ""
+
+msgid "PrometheusService|Metrics"
+msgstr ""
+
+msgid "PrometheusService|Missing environment variable"
+msgstr ""
+
+msgid "PrometheusService|More information"
+msgstr ""
+
+msgid "PrometheusService|New metric"
+msgstr ""
+
+msgid "PrometheusService|No custom metrics have been created. Create one using the button above"
+msgstr ""
+
+msgid "PrometheusService|Prometheus API Base URL, like http://prometheus.example.com/"
+msgstr ""
+
+msgid "PrometheusService|Prometheus is being automatically managed on your clusters"
+msgstr ""
+
+msgid "PrometheusService|Select the Active checkbox to override the Auto Configuration with custom settings. If unchecked, Auto Configuration settings are used."
+msgstr ""
+
+msgid "PrometheusService|These metrics will only be monitored after your first deployment to an environment"
+msgstr ""
+
+msgid "PrometheusService|Time-series monitoring service"
+msgstr ""
+
+msgid "PrometheusService|To enable the installation of Prometheus on your clusters, deactivate the manual configuration below"
+msgstr ""
+
+msgid "PrometheusService|Waiting for your first deployment to an environment to find common metrics"
+msgstr ""
+
+msgid "PrometheusService|You can now manage your Prometheus settings on the %{operations_link_start}Operations%{operations_link_end} page. Fields on this page has been deprecated."
+msgstr ""
+
+msgid "Promote"
+msgstr ""
+
+msgid "Promote confidential issue to a non-confidential epic"
+msgstr ""
+
+msgid "Promote issue to an epic"
+msgstr ""
+
+msgid "Promote these project milestones into a group milestone."
+msgstr ""
+
+msgid "Promote to group label"
+msgstr ""
+
+msgid "PromoteMilestone|Only project milestones can be promoted."
+msgstr ""
+
+msgid "PromoteMilestone|Project does not belong to a group."
+msgstr ""
+
+msgid "PromoteMilestone|Promotion failed - %{message}"
+msgstr ""
+
+msgid "Promoted confidential issue to a non-confidential epic. Information in this issue is no longer confidential as epics are public to group members."
+msgstr ""
+
+msgid "Promoted issue to an epic."
+msgstr ""
+
+msgid "Promotions|Burndown Charts are visual representations of the progress of completing a milestone. At a glance, you see the current state for the completion a given milestone. Without them, you would have to organize the data from the milestone and plot it yourself to have the same sense of progress."
+msgstr ""
+
+msgid "Promotions|Buy EE"
+msgstr ""
+
+msgid "Promotions|Buy GitLab Enterprise Edition"
+msgstr ""
+
+msgid "Promotions|Contact an owner of group %{namespace_name} to upgrade the plan."
+msgstr ""
+
+msgid "Promotions|Contact owner %{link_start}%{owner_name}%{link_end} to upgrade the plan."
+msgstr ""
+
+msgid "Promotions|Contact your Administrator to upgrade your license."
+msgstr ""
+
+msgid "Promotions|Dismiss burndown charts promotion"
+msgstr ""
+
+msgid "Promotions|Don't show me this again"
+msgstr ""
+
+msgid "Promotions|Epics let you manage your portfolio of projects more efficiently and with less effort by tracking groups of issues that share a theme, across projects and milestones."
+msgstr ""
+
+msgid "Promotions|Improve issues management with Issue weight and GitLab Enterprise Edition."
+msgstr ""
+
+msgid "Promotions|Improve milestones with Burndown Charts."
+msgstr ""
+
+msgid "Promotions|Learn more"
+msgstr ""
+
+msgid "Promotions|Not now, thanks!"
+msgstr ""
+
+msgid "Promotions|See the other features in the %{subscription_link_start}bronze plan%{subscription_link_end}"
+msgstr ""
+
+msgid "Promotions|Start GitLab Ultimate trial"
+msgstr ""
+
+msgid "Promotions|This feature is locked."
+msgstr ""
+
+msgid "Promotions|Track activity with Contribution Analytics."
+msgstr ""
+
+msgid "Promotions|Try it for free"
+msgstr ""
+
+msgid "Promotions|Upgrade plan"
+msgstr ""
+
+msgid "Promotions|Upgrade your plan"
+msgstr ""
+
+msgid "Promotions|Upgrade your plan to activate Contribution Analytics."
+msgstr ""
+
+msgid "Promotions|Upgrade your plan to improve milestones with Burndown Charts."
+msgstr ""
+
+msgid "Promotions|Weight"
+msgstr ""
+
+msgid "Promotions|Weighting your issue"
+msgstr ""
+
+msgid "Promotions|When you have a lot of issues, it can be hard to get an overview. By adding a weight to your issues, you can get a better idea of the effort, cost, required time, or value of each, and so better manage them."
+msgstr ""
+
+msgid "Promotions|With Contribution Analytics you can have an overview for the activity of issues, merge requests, and push events of your organization and its members."
+msgstr ""
+
+msgid "Prompt users to upload SSH keys"
+msgstr ""
+
+msgid "Protect variable"
+msgstr ""
+
+msgid "Protected"
+msgstr ""
+
+msgid "Protected Branch"
+msgstr ""
+
+msgid "Protected Branches"
+msgstr ""
+
+msgid "Protected Environment"
+msgstr ""
+
+msgid "Protected Environments"
+msgstr ""
+
+msgid "Protected Paths"
+msgstr ""
+
+msgid "Protected Tag"
+msgstr ""
+
+msgid "Protected Tags"
+msgstr ""
+
+msgid "Protected branches"
+msgstr ""
+
+msgid "ProtectedBranch|%{wildcards_link_start}Wildcards%{wildcards_link_end} such as %{code_tag_start}*-stable%{code_tag_end} or %{code_tag_start}production/*%{code_tag_end} are supported"
+msgstr ""
+
+msgid "ProtectedBranch|Allowed to merge"
+msgstr ""
+
+msgid "ProtectedBranch|Allowed to merge:"
+msgstr ""
+
+msgid "ProtectedBranch|Allowed to push"
+msgstr ""
+
+msgid "ProtectedBranch|Allowed to push:"
+msgstr ""
+
+msgid "ProtectedBranch|Branch"
+msgstr ""
+
+msgid "ProtectedBranch|Code owner approval"
+msgstr ""
+
+msgid "ProtectedBranch|Protect"
+msgstr ""
+
+msgid "ProtectedBranch|Protect a branch"
+msgstr ""
+
+msgid "ProtectedBranch|Protected branch (%{protected_branches_count})"
+msgstr ""
+
+msgid "ProtectedBranch|Pushes that change filenames matched by the CODEOWNERS file will be rejected"
+msgstr ""
+
+msgid "ProtectedBranch|Require approval from code owners:"
+msgstr ""
+
+msgid "ProtectedBranch|There are currently no protected branches, protect a branch with the form above."
+msgstr ""
+
+msgid "ProtectedBranch|Toggle code owner approval"
+msgstr ""
+
+msgid "ProtectedEnvironment|%{environment_name} will be writable for developers. Are you sure?"
+msgstr ""
+
+msgid "ProtectedEnvironment|Allowed to deploy"
+msgstr ""
+
+msgid "ProtectedEnvironment|Environment"
+msgstr ""
+
+msgid "ProtectedEnvironment|Protect"
+msgstr ""
+
+msgid "ProtectedEnvironment|Protect an environment"
+msgstr ""
+
+msgid "ProtectedEnvironment|Protected Environment (%{protected_environments_count})"
+msgstr ""
+
+msgid "ProtectedEnvironment|Protecting an environment restricts the users who can execute deployments."
+msgstr ""
+
+msgid "ProtectedEnvironment|Select an environment"
+msgstr ""
+
+msgid "ProtectedEnvironment|Select users"
+msgstr ""
+
+msgid "ProtectedEnvironment|Select users to deploy and manage Feature Flag settings"
+msgstr ""
+
+msgid "ProtectedEnvironment|There are currently no protected environments, protect an environment with the form above."
+msgstr ""
+
+msgid "ProtectedEnvironment|Unprotect"
+msgstr ""
+
+msgid "ProtectedEnvironment|Your environment can't be unprotected"
+msgstr ""
+
+msgid "ProtectedEnvironment|Your environment has been protected."
+msgstr ""
+
+msgid "ProtectedEnvironment|Your environment has been unprotected"
+msgstr ""
+
+msgid "Protip:"
+msgstr ""
+
+msgid "Protocol"
+msgstr ""
+
+msgid "Provider"
+msgstr ""
+
+msgid "Proxy support for this API is not available currently"
+msgstr ""
+
+msgid "Pseudonymizer data collection"
+msgstr ""
+
+msgid "Public"
+msgstr ""
+
+msgid "Public - The group and any public projects can be viewed without any authentication."
+msgstr ""
+
+msgid "Public - The project can be accessed without any authentication."
+msgstr ""
+
+msgid "Public deploy keys (%{deploy_keys_count})"
+msgstr ""
+
+msgid "Public pipelines"
+msgstr ""
+
+msgid "Public projects Minutes cost factor"
+msgstr ""
+
+msgid "Publish to status page"
+msgstr ""
+
+msgid "Published on status page"
+msgstr ""
+
+msgid "Publishes this issue to the associated status page."
+msgstr ""
+
+msgid "Pull"
+msgstr ""
+
+msgid "Pull requests from fork are not supported"
+msgstr ""
+
+msgid "Puma is running with a thread count above 1 and the Rugged service is enabled. This may decrease performance in some environments. See our %{link_start}documentation%{link_end} for details of this issue."
+msgstr ""
+
+msgid "Purchase more minutes"
+msgstr ""
+
+msgid "Push"
+msgstr ""
+
+msgid "Push Rule updated successfully."
+msgstr ""
+
+msgid "Push Rules"
+msgstr ""
+
+msgid "Push Rules updated successfully."
+msgstr ""
+
+msgid "Push an existing Git repository"
+msgstr ""
+
+msgid "Push an existing folder"
+msgstr ""
+
+msgid "Push events"
+msgstr ""
+
+msgid "Push project from command line"
+msgstr ""
+
+msgid "Push to create a project"
+msgstr ""
+
+msgid "PushRule|Committer restriction"
+msgstr ""
+
+msgid "Pushed"
+msgstr ""
+
+msgid "Pushes"
+msgstr ""
+
+msgid "PushoverService|%{user_name} deleted branch \"%{ref}\"."
+msgstr ""
+
+msgid "PushoverService|%{user_name} push to branch \"%{ref}\"."
+msgstr ""
+
+msgid "PushoverService|%{user_name} pushed new branch \"%{ref}\"."
+msgstr ""
+
+msgid "PushoverService|High Priority"
+msgstr ""
+
+msgid "PushoverService|Leave blank for all active devices"
+msgstr ""
+
+msgid "PushoverService|Low Priority"
+msgstr ""
+
+msgid "PushoverService|Lowest Priority"
+msgstr ""
+
+msgid "PushoverService|Normal Priority"
+msgstr ""
+
+msgid "PushoverService|Pushover makes it easy to get real-time notifications on your Android device, iPhone, iPad, and Desktop."
+msgstr ""
+
+msgid "PushoverService|See project %{project_full_name}"
+msgstr ""
+
+msgid "PushoverService|Total commits count: %{total_commits_count}"
+msgstr ""
+
+msgid "PushoverService|Your application key"
+msgstr ""
+
+msgid "PushoverService|Your user key"
+msgstr ""
+
+msgid "Quarters"
+msgstr ""
+
+msgid "Query"
+msgstr ""
+
+msgid "Query cannot be processed"
+msgstr ""
+
+msgid "Query is valid"
+msgstr ""
+
+msgid "Queued"
+msgstr ""
+
+msgid "Quick actions"
+msgstr ""
+
+msgid "Quick actions can be used in the issues description and comment boxes."
+msgstr ""
+
+msgid "Quick range"
+msgstr ""
+
+msgid "README"
+msgstr ""
+
+msgid "Raw blob request rate limit per minute"
+msgstr ""
+
+msgid "Re-authentication period expired or never requested. Please try again"
+msgstr ""
+
+msgid "Re-authentication required"
+msgstr ""
+
+msgid "Re-verification interval"
+msgstr ""
+
+msgid "Read more"
+msgstr ""
+
+msgid "Read more about environments"
+msgstr ""
+
+msgid "Read more about project permissions <strong>%{link_to_help}</strong>"
+msgstr ""
+
+msgid "Read more about related issues"
+msgstr ""
+
+msgid "Real-time features"
+msgstr ""
+
+msgid "Rebase"
+msgstr ""
+
+msgid "Rebase in progress"
+msgstr ""
+
+msgid "Receive alerts from manually configured Prometheus servers."
+msgstr ""
+
+msgid "Receive notifications about your own activity"
+msgstr ""
+
+msgid "Recent"
+msgstr ""
+
+msgid "Recent Activity"
+msgstr ""
+
+msgid "Recent Project Activity"
+msgstr ""
+
+msgid "Recent Searches Service is unavailable"
+msgstr ""
+
+msgid "Recent searches"
+msgstr ""
+
+msgid "Recipe"
+msgstr ""
+
+msgid "Reconfigure"
+msgstr ""
+
+msgid "Recover hidden stage"
+msgstr ""
+
+msgid "Recovery Codes"
+msgstr ""
+
+msgid "Redirect to SAML provider to test configuration"
+msgstr ""
+
+msgid "Reduce project visibility"
+msgstr ""
+
+msgid "Reduce this project’s visibility?"
+msgstr ""
+
+msgid "Reference:"
+msgstr ""
+
+msgid "Refresh"
+msgstr ""
+
+msgid "Refreshing in a second to show the updated status..."
+msgid_plural "Refreshing in %d seconds to show the updated status..."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Regenerate export"
+msgstr ""
+
+msgid "Regenerate instance ID"
+msgstr ""
+
+msgid "Regenerate key"
+msgstr ""
+
+msgid "Regenerate recovery codes"
+msgstr ""
+
+msgid "Regenerating the instance ID can break integration depending on the client you are using."
+msgstr ""
+
+msgid "Regex pattern"
+msgstr ""
+
+msgid "Region that Elasticsearch is configured"
+msgstr ""
+
+msgid "Register"
+msgstr ""
+
+msgid "Register / Sign In"
+msgstr ""
+
+msgid "Register Two-Factor Authenticator"
+msgstr ""
+
+msgid "Register U2F device"
+msgstr ""
+
+msgid "Register Universal Two-Factor (U2F) Device"
+msgstr ""
+
+msgid "Register for GitLab"
+msgstr ""
+
+msgid "Register now"
+msgstr ""
+
+msgid "Register with two-factor app"
+msgstr ""
+
+msgid "Registration"
+msgstr ""
+
+msgid "Registration|Checkout"
+msgstr ""
+
+msgid "Registration|Your GitLab group"
+msgstr ""
+
+msgid "Registration|Your first project"
+msgstr ""
+
+msgid "Registration|Your profile"
+msgstr ""
+
+msgid "Regulate approvals by authors/committers, based on compliance frameworks. Can be changed only at the instance level."
+msgstr ""
+
+msgid "Rejected (closed)"
+msgstr ""
+
+msgid "Related Deployed Jobs"
+msgstr ""
+
+msgid "Related Issues"
+msgstr ""
+
+msgid "Related Jobs"
+msgstr ""
+
+msgid "Related Merge Requests"
+msgstr ""
+
+msgid "Related Merged Requests"
+msgstr ""
+
+msgid "Related merge requests"
+msgstr ""
+
+msgid "Relates to"
+msgstr ""
+
+msgid "Release"
+msgid_plural "Releases"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Release assets"
+msgstr ""
+
+msgid "Release assets documentation"
+msgstr ""
+
+msgid "Release does not have the same project as the milestone"
+msgstr ""
+
+msgid "Release notes"
+msgstr ""
+
+msgid "Release notes:"
+msgstr ""
+
+msgid "Release title"
+msgstr ""
+
+msgid "ReleaseAssetLinkType|Image"
+msgstr ""
+
+msgid "ReleaseAssetLinkType|Images"
+msgstr ""
+
+msgid "ReleaseAssetLinkType|Other"
+msgstr ""
+
+msgid "ReleaseAssetLinkType|Package"
+msgstr ""
+
+msgid "ReleaseAssetLinkType|Packages"
+msgstr ""
+
+msgid "ReleaseAssetLinkType|Runbook"
+msgstr ""
+
+msgid "ReleaseAssetLinkType|Runbooks"
+msgstr ""
+
+msgid "Releases"
+msgstr ""
+
+msgid "Releases are based on Git tags and mark specific points in a project's development history. They can contain information about the type of changes and can also deliver binaries, like compiled versions of your software."
+msgstr ""
+
+msgid "Releases are based on Git tags. We recommend tags that use semantic versioning, for example %{codeStart}v1.0%{codeEnd}, %{codeStart}v2.0-pre%{codeEnd}."
+msgstr ""
+
+msgid "Releases documentation"
+msgstr ""
+
+msgid "Releases|New Release"
+msgstr ""
+
+msgid "Release|Something went wrong while getting the release details"
+msgstr ""
+
+msgid "Release|Something went wrong while saving the release details"
+msgstr ""
+
+msgid "Remediated: needs review"
+msgstr ""
+
+msgid "Remediations"
+msgstr ""
+
+msgid "Remember me"
+msgstr ""
+
+msgid "Remind later"
+msgstr ""
+
+msgid "Remote object has no absolute path."
+msgstr ""
+
+msgid "Remove"
+msgstr ""
+
+msgid "Remove %{displayReference}"
+msgstr ""
+
+msgid "Remove Runner"
+msgstr ""
+
+msgid "Remove Zoom meeting"
+msgstr ""
+
+msgid "Remove all approvals in a merge request when new commits are pushed to its source branch"
+msgstr ""
+
+msgid "Remove all or specific assignee(s)"
+msgstr ""
+
+msgid "Remove all or specific label(s)"
+msgstr ""
+
+msgid "Remove approvers"
+msgstr ""
+
+msgid "Remove approvers?"
+msgstr ""
+
+msgid "Remove asset link"
+msgstr ""
+
+msgid "Remove assignee"
+msgstr ""
+
+msgid "Remove avatar"
+msgstr ""
+
+msgid "Remove card"
+msgstr ""
+
+msgid "Remove child epic from an epic"
+msgstr ""
+
+msgid "Remove description history"
+msgstr ""
+
+msgid "Remove due date"
+msgstr ""
+
+msgid "Remove fork relationship"
+msgstr ""
+
+msgid "Remove from batch"
+msgstr ""
+
+msgid "Remove from board"
+msgstr ""
+
+msgid "Remove from epic"
+msgstr ""
+
+msgid "Remove group"
+msgstr ""
+
+msgid "Remove iteration"
+msgstr ""
+
+msgid "Remove license"
+msgstr ""
+
+msgid "Remove limit"
+msgstr ""
+
+msgid "Remove milestone"
+msgstr ""
+
+msgid "Remove node"
+msgstr ""
+
+msgid "Remove parent epic from an epic"
+msgstr ""
+
+msgid "Remove primary node"
+msgstr ""
+
+msgid "Remove priority"
+msgstr ""
+
+msgid "Remove project"
+msgstr ""
+
+msgid "Remove secondary node"
+msgstr ""
+
+msgid "Remove spent time"
+msgstr ""
+
+msgid "Remove stage"
+msgstr ""
+
+msgid "Remove time estimate"
+msgstr ""
+
+msgid "Removed"
+msgstr ""
+
+msgid "Removed %{assignee_text} %{assignee_references}."
+msgstr ""
+
+msgid "Removed %{epic_ref} from child epics."
+msgstr ""
+
+msgid "Removed %{iteration_reference} iteration."
+msgstr ""
+
+msgid "Removed %{label_references} %{label_text}."
+msgstr ""
+
+msgid "Removed %{milestone_reference} milestone."
+msgstr ""
+
+msgid "Removed %{type} with id %{id}"
+msgstr ""
+
+msgid "Removed all labels."
+msgstr ""
+
+msgid "Removed an issue from an epic."
+msgstr ""
+
+msgid "Removed group can not be restored!"
+msgstr ""
+
+msgid "Removed parent epic %{epic_ref}."
+msgstr ""
+
+msgid "Removed projects cannot be restored!"
+msgstr ""
+
+msgid "Removed spent time."
+msgstr ""
+
+msgid "Removed the due date."
+msgstr ""
+
+msgid "Removed time estimate."
+msgstr ""
+
+msgid "Removes %{assignee_text} %{assignee_references}."
+msgstr ""
+
+msgid "Removes %{epic_ref} from child epics."
+msgstr ""
+
+msgid "Removes %{iteration_reference} iteration."
+msgstr ""
+
+msgid "Removes %{label_references} %{label_text}."
+msgstr ""
+
+msgid "Removes %{milestone_reference} milestone."
+msgstr ""
+
+msgid "Removes all labels."
+msgstr ""
+
+msgid "Removes an issue from an epic."
+msgstr ""
+
+msgid "Removes parent epic %{epic_ref}."
+msgstr ""
+
+msgid "Removes spent time."
+msgstr ""
+
+msgid "Removes the due date."
+msgstr ""
+
+msgid "Removes time estimate."
+msgstr ""
+
+msgid "Removing a project places it into a read-only state until %{date}, at which point the project will be permanantly removed. Are you ABSOLUTELY sure?"
+msgstr ""
+
+msgid "Removing a project places it into a read-only state until %{date}, at which point the project will be permanently removed."
+msgstr ""
+
+msgid "Removing license…"
+msgstr ""
+
+msgid "Removing the project will delete its repository and all related resources including issues, merge requests etc."
+msgstr ""
+
+msgid "Removing this group also removes all child projects, including archived projects, and their resources."
+msgstr ""
+
+msgid "Rename file"
+msgstr ""
+
+msgid "Rename folder"
+msgstr ""
+
+msgid "Rename/Move"
+msgstr ""
+
+msgid "Reopen"
+msgstr ""
+
+msgid "Reopen %{display_issuable_type}"
+msgstr ""
+
+msgid "Reopen epic"
+msgstr ""
+
+msgid "Reopen milestone"
+msgstr ""
+
+msgid "Reopen this %{quick_action_target}"
+msgstr ""
+
+msgid "Reopened this %{quick_action_target}."
+msgstr ""
+
+msgid "Reopens this %{quick_action_target}."
+msgstr ""
+
+msgid "Repair authentication"
+msgstr ""
+
+msgid "Replace"
+msgstr ""
+
+msgid "Replace all label(s)"
+msgstr ""
+
+msgid "Replaced all labels with %{label_references} %{label_text}."
+msgstr ""
+
+msgid "Replaces the clone URL root."
+msgstr ""
+
+msgid "Replication"
+msgstr ""
+
+msgid "Replication enabled"
+msgstr ""
+
+msgid "Replication paused"
+msgstr ""
+
+msgid "Reply by email"
+msgstr ""
+
+msgid "Reply to comment"
+msgstr ""
+
+msgid "Reply to this email directly or %{view_it_on_gitlab}."
+msgstr ""
+
+msgid "Reply..."
+msgstr ""
+
+msgid "Repo by URL"
+msgstr ""
+
+msgid "Report %{display_issuable_type} that are abusive, inappropriate or spam."
+msgstr ""
+
+msgid "Report abuse"
+msgstr ""
+
+msgid "Report abuse to admin"
+msgstr ""
+
+msgid "Reported %{timeAgo} by %{reportedBy}"
+msgstr ""
+
+msgid "Reporter"
+msgstr ""
+
+msgid "Reporting"
+msgstr ""
+
+msgid "Reports"
+msgstr ""
+
+msgid "Reports|%{combinedString} and %{resolvedString}"
+msgstr ""
+
+msgid "Reports|Accessibility scanning detected %d issue for the source branch only"
+msgid_plural "Reports|Accessibility scanning detected %d issues for the source branch only"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Reports|Accessibility scanning detected no issues for the source branch only"
+msgstr ""
+
+msgid "Reports|Accessibility scanning failed loading results"
+msgstr ""
+
+msgid "Reports|Accessibility scanning results are being parsed"
+msgstr ""
+
+msgid "Reports|Actions"
+msgstr ""
+
+msgid "Reports|An error occured while loading report"
+msgstr ""
+
+msgid "Reports|An error occurred while loading %{name} results"
+msgstr ""
+
+msgid "Reports|Class"
+msgstr ""
+
+msgid "Reports|Classname"
+msgstr ""
+
+msgid "Reports|Execution time"
+msgstr ""
+
+msgid "Reports|Failure"
+msgstr ""
+
+msgid "Reports|Identifier"
+msgstr ""
+
+msgid "Reports|Metrics reports are loading"
+msgstr ""
+
+msgid "Reports|Metrics reports changed on %{numberOfChanges} %{pointsString}"
+msgstr ""
+
+msgid "Reports|Metrics reports did not change"
+msgstr ""
+
+msgid "Reports|Metrics reports failed loading results"
+msgstr ""
+
+msgid "Reports|Scanner"
+msgstr ""
+
+msgid "Reports|Severity"
+msgstr ""
+
+msgid "Reports|System output"
+msgstr ""
+
+msgid "Reports|Test summary"
+msgstr ""
+
+msgid "Reports|Test summary failed loading results"
+msgstr ""
+
+msgid "Reports|Test summary results are being parsed"
+msgstr ""
+
+msgid "Reports|Vulnerability"
+msgstr ""
+
+msgid "Reports|no changed test results"
+msgstr ""
+
+msgid "Repository"
+msgstr ""
+
+msgid "Repository Analytics"
+msgstr ""
+
+msgid "Repository Graph"
+msgstr ""
+
+msgid "Repository Settings"
+msgstr ""
+
+msgid "Repository URL"
+msgstr ""
+
+msgid "Repository check"
+msgstr ""
+
+msgid "Repository check was triggered."
+msgstr ""
+
+msgid "Repository cleanup"
+msgstr ""
+
+msgid "Repository cleanup has started. You will receive an email once the cleanup operation is complete."
+msgstr ""
+
+msgid "Repository files count over the limit"
+msgstr ""
+
+msgid "Repository has an invalid default branch name."
+msgstr ""
+
+msgid "Repository has more than one branch."
+msgstr ""
+
+msgid "Repository has no locks."
+msgstr ""
+
+msgid "Repository has tags."
+msgstr ""
+
+msgid "Repository maintenance"
+msgstr ""
+
+msgid "Repository mirroring"
+msgstr ""
+
+msgid "Repository must contain at least 1 file."
+msgstr ""
+
+msgid "Repository size is above the limit."
+msgstr ""
+
+msgid "Repository static objects"
+msgstr ""
+
+msgid "Repository storage"
+msgstr ""
+
+msgid "Repository sync capacity"
+msgstr ""
+
+msgid "Repository: %{counter_repositories} / Wikis: %{counter_wikis} / Build Artifacts: %{counter_build_artifacts} / LFS: %{counter_lfs_objects} / Snippets: %{counter_snippets}"
+msgstr ""
+
+msgid "RepositorySettingsAccessLevel|Select"
+msgstr ""
+
+msgid "Request Access"
+msgstr ""
+
+msgid "Request Headers"
+msgstr ""
+
+msgid "Request details"
+msgstr ""
+
+msgid "Request parameter %{param} is missing."
+msgstr ""
+
+msgid "Request to link SAML account must be authorized"
+msgstr ""
+
+msgid "Requested %{time_ago}"
+msgstr ""
+
+msgid "Requested design version does not exist."
+msgstr ""
+
+msgid "Requested states are invalid"
+msgstr ""
+
+msgid "Requests"
+msgstr ""
+
+msgid "Requests Profiles"
+msgstr ""
+
+msgid "Requests to these domain(s)/address(es) on the local network will be allowed when local requests from hooks and services are not allowed. IP ranges such as 1:0:0:0:0:0:0:0/124 or 127.0.0.0/28 are supported. Domain wildcards are not supported currently. Use comma, semicolon, or newline to separate multiple entries. The allowlist can hold a maximum of 1000 entries. Domains should use IDNA encoding. Ex: example.com, 192.168.1.1, 127.0.0.0/28, xn--itlab-j1a.com."
+msgstr ""
+
+msgid "Require all users in this group to setup Two-factor authentication"
+msgstr ""
+
+msgid "Require all users to accept Terms of Service and Privacy Policy when they access GitLab."
+msgstr ""
+
+msgid "Require user password to approve"
+msgstr ""
+
+msgid "Require users to prove ownership of custom domains"
+msgstr ""
+
+msgid "Required approvals (%{approvals_given} given)"
+msgstr ""
+
+msgid "Required approvals (%{approvals_given} given, you've approved)"
+msgstr ""
+
+msgid "Requirement"
+msgstr ""
+
+msgid "Requirement %{reference} has been added"
+msgstr ""
+
+msgid "Requirement %{reference} has been archived"
+msgstr ""
+
+msgid "Requirement %{reference} has been reopened"
+msgstr ""
+
+msgid "Requirement %{reference} has been updated"
+msgstr ""
+
+msgid "Requirement title cannot have more than %{limit} characters."
+msgstr ""
+
+msgid "Requirements"
+msgstr ""
+
+msgid "Requirements can be based on users, stakeholders, system, software, or anything else you find important to capture."
+msgstr ""
+
+msgid "Requires approval from %{names}."
+msgid_plural "Requires %{count} more approvals from %{names}."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Requires approval."
+msgid_plural "Requires %d more approvals."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Requires values to meet regular expression requirements."
+msgstr ""
+
+msgid "Resend confirmation email"
+msgstr ""
+
+msgid "Resend invite"
+msgstr ""
+
+msgid "Resend it"
+msgstr ""
+
+msgid "Reset authorization key"
+msgstr ""
+
+msgid "Reset authorization key?"
+msgstr ""
+
+msgid "Reset health check access token"
+msgstr ""
+
+msgid "Reset key"
+msgstr ""
+
+msgid "Reset runners registration token"
+msgstr ""
+
+msgid "Reset template"
+msgstr ""
+
+msgid "Reset to project defaults"
+msgstr ""
+
+msgid "Resetting the authorization key for this project will require updating the authorization key in every alert source it is enabled in."
+msgstr ""
+
+msgid "Resetting the authorization key will invalidate the previous key. Existing alert configurations will need to be updated with the new key."
+msgstr ""
+
+msgid "Resolve"
+msgstr ""
+
+msgid "Resolve all threads in new issue"
+msgstr ""
+
+msgid "Resolve conflicts on source branch"
+msgstr ""
+
+msgid "Resolve thread"
+msgstr ""
+
+msgid "Resolved"
+msgstr ""
+
+msgid "Resolved 1 discussion."
+msgstr ""
+
+msgid "Resolved all discussions."
+msgstr ""
+
+msgid "Resolved by"
+msgstr ""
+
+msgid "Resolved by %{name}"
+msgstr ""
+
+msgid "Resolved by %{resolvedByName}"
+msgstr ""
+
+msgid "Resolves IP addresses once and uses them to submit requests"
+msgstr ""
+
+msgid "Response"
+msgstr ""
+
+msgid "Response Headers"
+msgstr ""
+
+msgid "Response Status"
+msgstr ""
+
+msgid "Response didn't include `service_desk_address`"
+msgstr ""
+
+msgid "Response metrics (AWS ELB)"
+msgstr ""
+
+msgid "Response metrics (Custom)"
+msgstr ""
+
+msgid "Response metrics (HA Proxy)"
+msgstr ""
+
+msgid "Response metrics (NGINX Ingress VTS)"
+msgstr ""
+
+msgid "Response metrics (NGINX Ingress)"
+msgstr ""
+
+msgid "Response metrics (NGINX)"
+msgstr ""
+
+msgid "Restart Terminal"
+msgstr ""
+
+msgid "Restore group"
+msgstr ""
+
+msgid "Restore project"
+msgstr ""
+
+msgid "Restoring the group will prevent the group, its subgroups and projects from being removed on this date."
+msgstr ""
+
+msgid "Restoring the project will prevent the project from being removed on this date and restore people's ability to make changes to it."
+msgstr ""
+
+msgid "Restrict access by IP address"
+msgstr ""
+
+msgid "Restrict membership by email"
+msgstr ""
+
+msgid "Restricts sign-ups for email addresses that match the given regex. See the %{supported_syntax_link_start}supported syntax%{supported_syntax_link_end} for more information."
+msgstr ""
+
+msgid "Resume"
+msgstr ""
+
+msgid "Resync"
+msgstr ""
+
+msgid "Resync all"
+msgstr ""
+
+msgid "Resync all %{replicableType}"
+msgstr ""
+
+msgid "Retry"
+msgstr ""
+
+msgid "Retry this job"
+msgstr ""
+
+msgid "Retry this job in order to create the necessary resources."
+msgstr ""
+
+msgid "Retry update"
+msgstr ""
+
+msgid "Retry verification"
+msgstr ""
+
+msgid "Reveal value"
+msgid_plural "Reveal values"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Reveal values"
+msgstr ""
+
+msgid "Revert this commit"
+msgstr ""
+
+msgid "Revert this merge request"
+msgstr ""
+
+msgid "Review"
+msgstr ""
+
+msgid "Review App|View app"
+msgstr ""
+
+msgid "Review App|View latest app"
+msgstr ""
+
+msgid "Review the process for configuring service providers in your identity provider — in this case, GitLab is the \"service provider\" or \"relying party\"."
+msgstr ""
+
+msgid "Review time"
+msgstr ""
+
+msgid "Review time is defined as the time it takes from first comment until merged."
+msgstr ""
+
+msgid "ReviewApp|Enable Review App"
+msgstr ""
+
+msgid "Reviewing"
+msgstr ""
+
+msgid "Reviewing (merge request !%{mergeRequestId})"
+msgstr ""
+
+msgid "Revoke"
+msgstr ""
+
+msgid "Revoked impersonation token %{token_name}!"
+msgstr ""
+
+msgid "Revoked personal access token %{personal_access_token_name}!"
+msgstr ""
+
+msgid "Revoked project access token %{project_access_token_name}!"
+msgstr ""
+
+msgid "RightSidebar|adding a"
+msgstr ""
+
+msgid "RightSidebar|deleting the"
+msgstr ""
+
+msgid "Roadmap"
+msgstr ""
+
+msgid "Role"
+msgstr ""
+
+msgid "Rollback"
+msgstr ""
+
+msgid "Rook"
+msgstr ""
+
+msgid "Rules that define what git pushes are accepted for a project in this group. All newly created projects in this group will use these settings."
+msgstr ""
+
+msgid "Rules that define what git pushes are accepted for a project. All newly created projects will use these settings."
+msgstr ""
+
+msgid "Run CI/CD pipelines for external repositories"
+msgstr ""
+
+msgid "Run housekeeping"
+msgstr ""
+
+msgid "Run tests against your code live using the Web Terminal"
+msgstr ""
+
+msgid "Run untagged jobs"
+msgstr ""
+
+msgid "Runner cannot be assigned to other projects"
+msgstr ""
+
+msgid "Runner runs jobs from all unassigned projects"
+msgstr ""
+
+msgid "Runner runs jobs from all unassigned projects in its group"
+msgstr ""
+
+msgid "Runner runs jobs from assigned projects"
+msgstr ""
+
+msgid "Runner token"
+msgstr ""
+
+msgid "Runner tokens"
+msgstr ""
+
+msgid "Runner was not updated."
+msgstr ""
+
+msgid "Runner was successfully updated."
+msgstr ""
+
+msgid "Runner will not receive any new jobs"
+msgstr ""
+
+msgid "Runners"
+msgstr ""
+
+msgid "Runners API"
+msgstr ""
+
+msgid "Runners activated for this project"
+msgstr ""
+
+msgid "Runners are processes that pick up and execute jobs for GitLab. Here you can register and see your Runners for this project."
+msgstr ""
+
+msgid "Runners can be placed on separate users, servers, and even on your local machine."
+msgstr ""
+
+msgid "Runners can be placed on separate users, servers, even on your local machine."
+msgstr ""
+
+msgid "Runners currently online: %{active_runners_count}"
+msgstr ""
+
+msgid "Runners page."
+msgstr ""
+
+msgid "Runners|You have used %{quotaUsed} out of %{quotaLimit} of your shared Runners pipeline minutes."
+msgstr ""
+
+msgid "Running"
+msgstr ""
+
+msgid "Running…"
+msgstr ""
+
+msgid "Runs a number of housekeeping tasks within the current repository, such as compressing file revisions and removing unreachable objects."
+msgstr ""
+
+msgid "SAML SSO"
+msgstr ""
+
+msgid "SAML SSO for %{group_name}"
+msgstr ""
+
+msgid "SAML discovery tokens"
+msgstr ""
+
+msgid "SAML for %{group_name}"
+msgstr ""
+
+msgid "SHA256"
+msgstr ""
+
+msgid "SSH Key"
+msgstr ""
+
+msgid "SSH Keys"
+msgstr ""
+
+msgid "SSH host key fingerprints"
+msgstr ""
+
+msgid "SSH host keys"
+msgstr ""
+
+msgid "SSH host keys are not available on this system. Please use <code>ssh-keyscan</code> command or contact your GitLab administrator for more information."
+msgstr ""
+
+msgid "SSH keys allow you to establish a secure connection between your computer and GitLab."
+msgstr ""
+
+msgid "SSH public key"
+msgstr ""
+
+msgid "SSL Verification:"
+msgstr ""
+
+msgid "Saturday"
+msgstr ""
+
+msgid "Save"
+msgstr ""
+
+msgid "Save Changes"
+msgstr ""
+
+msgid "Save anyway"
+msgstr ""
+
+msgid "Save application"
+msgstr ""
+
+msgid "Save changes"
+msgstr ""
+
+msgid "Save changes before testing"
+msgstr ""
+
+msgid "Save comment"
+msgstr ""
+
+msgid "Save expiration policy"
+msgstr ""
+
+msgid "Save password"
+msgstr ""
+
+msgid "Save pipeline schedule"
+msgstr ""
+
+msgid "Save template"
+msgstr ""
+
+msgid "Save variables"
+msgstr ""
+
+msgid "Saving"
+msgstr ""
+
+msgid "Saving project."
+msgstr ""
+
+msgid "Schedule a new pipeline"
+msgstr ""
+
+msgid "Scheduled"
+msgstr ""
+
+msgid "Scheduled to merge this merge request (%{strategy})."
+msgstr ""
+
+msgid "Scheduled to merge this merge request when the pipeline succeeds."
+msgstr ""
+
+msgid "Schedules"
+msgstr ""
+
+msgid "Schedules to merge this merge request (%{strategy})."
+msgstr ""
+
+msgid "Scheduling"
+msgstr ""
+
+msgid "Scheduling Pipelines"
+msgstr ""
+
+msgid "Scope"
+msgstr ""
+
+msgid "Scope not supported with disabled 'users_search' feature!"
+msgstr ""
+
+msgid "Scoped issue boards"
+msgstr ""
+
+msgid "Scopes"
+msgstr ""
+
+msgid "Scopes can't be blank"
+msgstr ""
+
+msgid "Scroll down"
+msgstr ""
+
+msgid "Scroll down to <strong>Google Code Project Hosting</strong> and enable the switch on the right."
+msgstr ""
+
+msgid "Scroll left"
+msgstr ""
+
+msgid "Scroll right"
+msgstr ""
+
+msgid "Scroll to bottom"
+msgstr ""
+
+msgid "Scroll to top"
+msgstr ""
+
+msgid "Scroll up"
+msgstr ""
+
+msgid "Search"
+msgstr ""
+
+msgid "Search Button"
+msgstr ""
+
+msgid "Search Milestones"
+msgstr ""
+
+msgid "Search an environment spec"
+msgstr ""
+
+msgid "Search authors"
+msgstr ""
+
+msgid "Search branches"
+msgstr ""
+
+msgid "Search branches and tags"
+msgstr ""
+
+msgid "Search by author"
+msgstr ""
+
+msgid "Search files"
+msgstr ""
+
+msgid "Search for a LDAP group"
+msgstr ""
+
+msgid "Search for a group"
+msgstr ""
+
+msgid "Search for a user"
+msgstr ""
+
+msgid "Search for projects, issues, etc."
+msgstr ""
+
+msgid "Search for this text"
+msgstr ""
+
+msgid "Search forks"
+msgstr ""
+
+msgid "Search groups"
+msgstr ""
+
+msgid "Search merge requests"
+msgstr ""
+
+msgid "Search milestones"
+msgstr ""
+
+msgid "Search or filter results..."
+msgstr ""
+
+msgid "Search or filter results…"
+msgstr ""
+
+msgid "Search or jump to…"
+msgstr ""
+
+msgid "Search project"
+msgstr ""
+
+msgid "Search projects"
+msgstr ""
+
+msgid "Search projects..."
+msgstr ""
+
+msgid "Search requirements"
+msgstr ""
+
+msgid "Search users"
+msgstr ""
+
+msgid "Search users or groups"
+msgstr ""
+
+msgid "Search your project dependencies for their licenses and apply policies."
+msgstr ""
+
+msgid "Search your projects"
+msgstr ""
+
+msgid "SearchAutocomplete|All GitLab"
+msgstr ""
+
+msgid "SearchAutocomplete|Issues I've created"
+msgstr ""
+
+msgid "SearchAutocomplete|Issues assigned to me"
+msgstr ""
+
+msgid "SearchAutocomplete|Merge requests I've created"
+msgstr ""
+
+msgid "SearchAutocomplete|Merge requests assigned to me"
+msgstr ""
+
+msgid "SearchAutocomplete|in all GitLab"
+msgstr ""
+
+msgid "SearchAutocomplete|in group %{groupName}"
+msgstr ""
+
+msgid "SearchAutocomplete|in project %{projectName}"
+msgstr ""
+
+msgid "SearchCodeResults|in"
+msgstr ""
+
+msgid "SearchCodeResults|of %{link_to_project}"
+msgstr ""
+
+msgid "SearchResults|Showing %{count} %{scope} for%{term_element}"
+msgstr ""
+
+msgid "SearchResults|Showing %{count} %{scope} for%{term_element} in your personal and project snippets"
+msgstr ""
+
+msgid "SearchResults|Showing %{from} - %{to} of %{count} %{scope} for%{term_element}"
+msgstr ""
+
+msgid "SearchResults|Showing %{from} - %{to} of %{count} %{scope} for%{term_element} in your personal and project snippets"
+msgstr ""
+
+msgid "SearchResults|We couldn't find any %{scope} matching %{term}"
+msgstr ""
+
+msgid "SearchResults|code result"
+msgid_plural "SearchResults|code results"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "SearchResults|comment"
+msgid_plural "SearchResults|comments"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "SearchResults|commit"
+msgid_plural "SearchResults|commits"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "SearchResults|issue"
+msgid_plural "SearchResults|issues"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "SearchResults|merge request"
+msgid_plural "SearchResults|merge requests"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "SearchResults|milestone"
+msgid_plural "SearchResults|milestones"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "SearchResults|project"
+msgid_plural "SearchResults|projects"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "SearchResults|snippet"
+msgid_plural "SearchResults|snippets"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "SearchResults|user"
+msgid_plural "SearchResults|users"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "SearchResults|wiki result"
+msgid_plural "SearchResults|wiki results"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Searching by both author and message is currently not supported."
+msgstr ""
+
+msgid "Seat Link"
+msgstr ""
+
+msgid "Seat Link is disabled, and cannot be configured through this form."
+msgstr ""
+
+msgid "Seats currently in use"
+msgstr ""
+
+msgid "Seats in license"
+msgstr ""
+
+msgid "Secondary"
+msgstr ""
+
+msgid "Secret"
+msgstr ""
+
+msgid "Secret Detection"
+msgstr ""
+
+msgid "Security"
+msgstr ""
+
+msgid "Security & Compliance"
+msgstr ""
+
+msgid "Security Configuration"
+msgstr ""
+
+msgid "Security Dashboard"
+msgstr ""
+
+msgid "Security dashboard"
+msgstr ""
+
+msgid "Security report is out of date. Please update your branch with the latest changes from the target branch (%{targetBranchName})"
+msgstr ""
+
+msgid "Security report is out of date. Run %{newPipelineLinkStart}a new pipeline%{newPipelineLinkEnd} for the target branch (%{targetBranchName})"
+msgstr ""
+
+msgid "SecurityConfiguration|Enabled"
+msgstr ""
+
+msgid "SecurityConfiguration|Feature documentation for %{featureName}"
+msgstr ""
+
+msgid "SecurityConfiguration|Not yet enabled"
+msgstr ""
+
+msgid "SecurityConfiguration|Security Control"
+msgstr ""
+
+msgid "SecurityConfiguration|Status"
+msgstr ""
+
+msgid "SecurityConfiguration|Testing & Compliance"
+msgstr ""
+
+msgid "SecurityReports|%{firstProject} and %{secondProject}"
+msgstr ""
+
+msgid "SecurityReports|%{firstProject}, %{secondProject}, and %{rest}"
+msgstr ""
+
+msgid "SecurityReports|Add a project to your dashboard"
+msgstr ""
+
+msgid "SecurityReports|Add or remove projects from your dashboard"
+msgstr ""
+
+msgid "SecurityReports|Add projects"
+msgstr ""
+
+msgid "SecurityReports|Comment added to '%{vulnerabilityName}'"
+msgstr ""
+
+msgid "SecurityReports|Comment deleted on '%{vulnerabilityName}'"
+msgstr ""
+
+msgid "SecurityReports|Comment edited on '%{vulnerabilityName}'"
+msgstr ""
+
+msgid "SecurityReports|Create issue"
+msgstr ""
+
+msgid "SecurityReports|Dismiss Selected"
+msgstr ""
+
+msgid "SecurityReports|Dismiss vulnerability"
+msgstr ""
+
+msgid "SecurityReports|Dismissed '%{vulnerabilityName}'"
+msgstr ""
+
+msgid "SecurityReports|Dismissed '%{vulnerabilityName}'. Turn off the hide dismissed toggle to view."
+msgstr ""
+
+msgid "SecurityReports|Each vulnerability now has a unique page that can be directly linked to, shared, referenced, and tracked as the single source of truth. Vulnerability occurrences also persist across scanner runs, which improves tracking and visibility and reduces duplicates between scans."
+msgstr ""
+
+msgid "SecurityReports|Edit dashboard"
+msgstr ""
+
+msgid "SecurityReports|Either you don't have permission to view this dashboard or the dashboard has not been setup. Please check your permission settings with your administrator or check your dashboard configurations to proceed."
+msgstr ""
+
+msgid "SecurityReports|Error fetching the vulnerability counts. Please check your network connection and try again."
+msgstr ""
+
+msgid "SecurityReports|Error fetching the vulnerability list. Please check your network connection and try again."
+msgstr ""
+
+msgid "SecurityReports|False positive"
+msgstr ""
+
+msgid "SecurityReports|Group Security Dashboard"
+msgstr ""
+
+msgid "SecurityReports|Hide dismissed"
+msgstr ""
+
+msgid "SecurityReports|Introducing standalone vulnerabilities"
+msgstr ""
+
+msgid "SecurityReports|Issue Created"
+msgstr ""
+
+msgid "SecurityReports|Learn More"
+msgstr ""
+
+msgid "SecurityReports|Learn more about setting up your dashboard"
+msgstr ""
+
+msgid "SecurityReports|Load more vulnerabilities"
+msgstr ""
+
+msgid "SecurityReports|Monitor vulnerabilities in your code"
+msgstr ""
+
+msgid "SecurityReports|More info"
+msgstr ""
+
+msgid "SecurityReports|More information"
+msgstr ""
+
+msgid "SecurityReports|No vulnerabilities found for dashboard"
+msgstr ""
+
+msgid "SecurityReports|No vulnerabilities found for this group"
+msgstr ""
+
+msgid "SecurityReports|No vulnerabilities found for this pipeline"
+msgstr ""
+
+msgid "SecurityReports|No vulnerabilities found for this project"
+msgstr ""
+
+msgid "SecurityReports|Oops, something doesn't seem right."
+msgstr ""
+
+msgid "SecurityReports|Pipeline %{pipelineLink} triggered %{timeago} by %{user}"
+msgstr ""
+
+msgid "SecurityReports|Project"
+msgstr ""
+
+msgid "SecurityReports|Projects added"
+msgstr ""
+
+msgid "SecurityReports|Remove project from dashboard"
+msgstr ""
+
+msgid "SecurityReports|Return to dashboard"
+msgstr ""
+
+msgid "SecurityReports|Scan details"
+msgstr ""
+
+msgid "SecurityReports|Scanner type"
+msgstr ""
+
+msgid "SecurityReports|Security Dashboard"
+msgstr ""
+
+msgid "SecurityReports|Security reports can only be accessed by authorized users."
+msgstr ""
+
+msgid "SecurityReports|Select a project to add by using the project search field above."
+msgstr ""
+
+msgid "SecurityReports|Select a reason"
+msgstr ""
+
+msgid "SecurityReports|Severity"
+msgstr ""
+
+msgid "SecurityReports|Status"
+msgstr ""
+
+msgid "SecurityReports|The rating \"unknown\" indicates that the underlying scanner doesn’t contain or provide a severity rating."
+msgstr ""
+
+msgid "SecurityReports|The security dashboard displays the latest security findings for projects you wish to monitor. Select \"Edit dashboard\" to add and remove projects."
+msgstr ""
+
+msgid "SecurityReports|The security dashboard displays the latest security report. Use it to find and fix vulnerabilities."
+msgstr ""
+
+msgid "SecurityReports|There was an error adding the comment."
+msgstr ""
+
+msgid "SecurityReports|There was an error creating the issue."
+msgstr ""
+
+msgid "SecurityReports|There was an error creating the merge request."
+msgstr ""
+
+msgid "SecurityReports|There was an error deleting the comment."
+msgstr ""
+
+msgid "SecurityReports|There was an error dismissing the vulnerabilities."
+msgstr ""
+
+msgid "SecurityReports|There was an error dismissing the vulnerability."
+msgstr ""
+
+msgid "SecurityReports|There was an error reverting the dismissal."
+msgstr ""
+
+msgid "SecurityReports|There was an error reverting this dismissal."
+msgstr ""
+
+msgid "SecurityReports|There was an error while generating the report."
+msgstr ""
+
+msgid "SecurityReports|Unable to add %{invalidProjectsMessage}"
+msgstr ""
+
+msgid "SecurityReports|Unable to add %{invalidProjects}"
+msgstr ""
+
+msgid "SecurityReports|Undo dismiss"
+msgstr ""
+
+msgid "SecurityReports|While it's rare to have no vulnerabilities for your group, it can happen. In any event, we ask that you double check your settings to make sure you've set up your dashboard correctly."
+msgstr ""
+
+msgid "SecurityReports|While it's rare to have no vulnerabilities for your pipeline, it can happen. In any event, we ask that you double check your settings to make sure all security scanning jobs have passed successfully."
+msgstr ""
+
+msgid "SecurityReports|While it's rare to have no vulnerabilities for your project, it can happen. In any event, we ask that you double check your settings to make sure you've set up your dashboard correctly."
+msgstr ""
+
+msgid "SecurityReports|While it's rare to have no vulnerabilities, it can happen. In any event, we ask that you please double check your settings to make sure you've set up your dashboard correctly."
+msgstr ""
+
+msgid "SecurityReports|Won't fix / Accept risk"
+msgstr ""
+
+msgid "SecurityReports|You do not have sufficient permissions to access this report"
+msgstr ""
+
+msgid "SecurityReports|You must sign in as an authorized user to see this report"
+msgstr ""
+
+msgid "SecurityReports|[No reason]"
+msgstr ""
+
+msgid "See GitLab's %{password_policy_guidelines}"
+msgstr ""
+
+msgid "See metrics"
+msgstr ""
+
+msgid "See the affected projects in the GitLab admin panel"
+msgstr ""
+
+msgid "See what's new at GitLab"
+msgstr ""
+
+msgid "Select"
+msgstr ""
+
+msgid "Select Archive Format"
+msgstr ""
+
+msgid "Select Git revision"
+msgstr ""
+
+msgid "Select GitLab project to link with your Slack team"
+msgstr ""
+
+msgid "Select Page"
+msgstr ""
+
+msgid "Select Stack"
+msgstr ""
+
+msgid "Select a file from the left sidebar to begin editing. Afterwards, you'll be able to commit your changes."
+msgstr ""
+
+msgid "Select a group to invite"
+msgstr ""
+
+msgid "Select a label"
+msgstr ""
+
+msgid "Select a namespace to fork the project"
+msgstr ""
+
+msgid "Select a new namespace"
+msgstr ""
+
+msgid "Select a project"
+msgstr ""
+
+msgid "Select a project to read Insights configuration file"
+msgstr ""
+
+msgid "Select a reason"
+msgstr ""
+
+msgid "Select a repository"
+msgstr ""
+
+msgid "Select a template repository"
+msgstr ""
+
+msgid "Select a template type"
+msgstr ""
+
+msgid "Select a timezone"
+msgstr ""
+
+msgid "Select all"
+msgstr ""
+
+msgid "Select an existing Kubernetes cluster or create a new one"
+msgstr ""
+
+msgid "Select assignee"
+msgstr ""
+
+msgid "Select branch"
+msgstr ""
+
+msgid "Select branch/tag"
+msgstr ""
+
+msgid "Select due date"
+msgstr ""
+
+msgid "Select file"
+msgstr ""
+
+msgid "Select group or project"
+msgstr ""
+
+msgid "Select groups to replicate"
+msgstr ""
+
+msgid "Select health status"
+msgstr ""
+
+msgid "Select labels"
+msgstr ""
+
+msgid "Select merge moment"
+msgstr ""
+
+msgid "Select milestone"
+msgstr ""
+
+msgid "Select private project"
+msgstr ""
+
+msgid "Select project"
+msgstr ""
+
+msgid "Select project and zone to choose machine type"
+msgstr ""
+
+msgid "Select project to choose zone"
+msgstr ""
+
+msgid "Select projects"
+msgstr ""
+
+msgid "Select projects you want to import."
+msgstr ""
+
+msgid "Select required regulatory standard"
+msgstr ""
+
+msgid "Select shards to replicate"
+msgstr ""
+
+msgid "Select source branch"
+msgstr ""
+
+msgid "Select start date"
+msgstr ""
+
+msgid "Select status"
+msgstr ""
+
+msgid "Select strategy activation method"
+msgstr ""
+
+msgid "Select subscription"
+msgstr ""
+
+msgid "Select target branch"
+msgstr ""
+
+msgid "Select the branch you want to set as the default for this project. All merge requests and commits will automatically be made against this branch unless you specify a different one."
+msgstr ""
+
+msgid "Select the custom project template source group."
+msgstr ""
+
+msgid "Select timeframe"
+msgstr ""
+
+msgid "Select user"
+msgstr ""
+
+msgid "Selected levels cannot be used by non-admin users for groups, projects or snippets. If the public level is restricted, user profiles are only visible to logged in users."
+msgstr ""
+
+msgid "Selecting a GitLab user will add a link to the GitLab user in the descriptions of issues and comments (e.g. \"By <a href=\"#\">@johnsmith</a>\"). It will also associate and/or assign these issues and comments with the selected user."
+msgstr ""
+
+msgid "Selective synchronization"
+msgstr ""
+
+msgid "Self monitoring project does not exist"
+msgstr ""
+
+msgid "Self-monitoring project does not exist. Please check logs for any error messages"
+msgstr ""
+
+msgid "Self-monitoring project has been successfully deleted"
+msgstr ""
+
+msgid "Self-monitoring project was not deleted. Please check logs for any error messages"
+msgstr ""
+
+msgid "SelfMonitoring|Disable self monitoring?"
+msgstr ""
+
+msgid "SelfMonitoring|Disabling this feature will delete the self monitoring project. Are you sure you want to delete the project?"
+msgstr ""
+
+msgid "SelfMonitoring|Enable or disable instance self monitoring"
+msgstr ""
+
+msgid "SelfMonitoring|Enabling this feature creates a %{projectLinkStart}project%{projectLinkEnd} that can be used to monitor the health of your instance."
+msgstr ""
+
+msgid "SelfMonitoring|Enabling this feature creates a project that can be used to monitor the health of your instance."
+msgstr ""
+
+msgid "SelfMonitoring|Self monitoring"
+msgstr ""
+
+msgid "SelfMonitoring|Self monitoring project has been successfully created."
+msgstr ""
+
+msgid "SelfMonitoring|Self monitoring project has been successfully deleted."
+msgstr ""
+
+msgid "Send a separate email notification to Developers."
+msgstr ""
+
+msgid "Send confirmation email"
+msgstr ""
+
+msgid "Send email"
+msgstr ""
+
+msgid "Send email notification"
+msgstr ""
+
+msgid "Send message"
+msgstr ""
+
+msgid "Send report"
+msgstr ""
+
+msgid "Send usage data"
+msgstr ""
+
+msgid "Sentry API URL"
+msgstr ""
+
+msgid "Sentry event"
+msgstr ""
+
+msgid "Sep"
+msgstr ""
+
+msgid "Separate topics with commas."
+msgstr ""
+
+msgid "September"
+msgstr ""
+
+msgid "SeriesFinalConjunction|and"
+msgstr ""
+
+msgid "Serve repository static objects (e.g. archives, blobs, ...) from an external storage (e.g. a CDN)."
+msgstr ""
+
+msgid "Server supports batch API only, please update your Git LFS client to version 1.0.1 and up."
+msgstr ""
+
+msgid "Server version"
+msgstr ""
+
+msgid "Serverless"
+msgstr ""
+
+msgid "Serverless domain"
+msgstr ""
+
+msgid "ServerlessDetails|Function invocation metrics require Prometheus to be installed first."
+msgstr ""
+
+msgid "ServerlessDetails|Install Prometheus"
+msgstr ""
+
+msgid "ServerlessDetails|Invocation metrics loading or not available at this time."
+msgstr ""
+
+msgid "ServerlessDetails|Invocations"
+msgstr ""
+
+msgid "ServerlessDetails|Kubernetes Pods"
+msgstr ""
+
+msgid "ServerlessDetails|More information"
+msgstr ""
+
+msgid "ServerlessDetails|No pods loaded at this time."
+msgstr ""
+
+msgid "ServerlessDetails|Number of Kubernetes pods in use over time based on necessity."
+msgstr ""
+
+msgid "ServerlessDetails|pod in use"
+msgstr ""
+
+msgid "ServerlessDetails|pods in use"
+msgstr ""
+
+msgid "ServerlessURL|Copy URL"
+msgstr ""
+
+msgid "Serverless| In order to start using functions as a service, you must first install Knative on your Kubernetes cluster."
+msgstr ""
+
+msgid "Serverless|Getting started with serverless"
+msgstr ""
+
+msgid "Serverless|Help shape the future of Serverless at GitLab"
+msgstr ""
+
+msgid "Serverless|If you believe none of these apply, please check back later as the function data may be in the process of becoming available."
+msgstr ""
+
+msgid "Serverless|Install Knative"
+msgstr ""
+
+msgid "Serverless|Learn more about Serverless"
+msgstr ""
+
+msgid "Serverless|No functions available"
+msgstr ""
+
+msgid "Serverless|Sign up for First Look"
+msgstr ""
+
+msgid "Serverless|The deploy job has not finished."
+msgstr ""
+
+msgid "Serverless|The functions listed in the %{startTag}serverless.yml%{endTag} file don't match the namespace of your cluster."
+msgstr ""
+
+msgid "Serverless|There is currently no function data available from Knative. This could be for a variety of reasons including:"
+msgstr ""
+
+msgid "Serverless|We are continually striving to improve our Serverless functionality. As a Knative user, we would love to hear how we can make this experience better for you. Sign up for GitLab First Look today and we will be in touch shortly."
+msgstr ""
+
+msgid "Serverless|Your %{startTag}.gitlab-ci.yml%{endTag} file is not properly configured."
+msgstr ""
+
+msgid "Serverless|Your repository does not have a corresponding %{startTag}serverless.yml%{endTag} file."
+msgstr ""
+
+msgid "Service"
+msgstr ""
+
+msgid "Service Desk"
+msgstr ""
+
+msgid "Service Desk is enabled but not yet active"
+msgstr ""
+
+msgid "Service Templates"
+msgstr ""
+
+msgid "Service URL"
+msgstr ""
+
+msgid "Session duration (minutes)"
+msgstr ""
+
+msgid "Set %{epic_ref} as the parent epic."
+msgstr ""
+
+msgid "Set a default template for issue descriptions."
+msgstr ""
+
+msgid "Set a number of approvals required, the approvers and other approval settings."
+msgstr ""
+
+msgid "Set a password on your account to pull or push via %{protocol}."
+msgstr ""
+
+msgid "Set a template repository for projects in this group"
+msgstr ""
+
+msgid "Set an instance-wide domain that will be available to all clusters when installing Knative."
+msgstr ""
+
+msgid "Set default and restrict visibility levels. Configure import sources and git access protocol."
+msgstr ""
+
+msgid "Set due date"
+msgstr ""
+
+msgid "Set instance-wide template repository"
+msgstr ""
+
+msgid "Set iteration"
+msgstr ""
+
+msgid "Set max session time for web terminal."
+msgstr ""
+
+msgid "Set milestone"
+msgstr ""
+
+msgid "Set new password"
+msgstr ""
+
+msgid "Set notification email for abuse reports."
+msgstr ""
+
+msgid "Set parent epic to an epic"
+msgstr ""
+
+msgid "Set projects and maximum size limits, session duration, user options, and check feature availability for namespace plan."
+msgstr ""
+
+msgid "Set requirements for a user to sign-in. Enable mandatory two-factor authentication."
+msgstr ""
+
+msgid "Set target branch"
+msgstr ""
+
+msgid "Set target branch to %{branch_name}."
+msgstr ""
+
+msgid "Set the default expiration time for each job's artifacts. 0 for unlimited. The default unit is in seconds, but you can define an alternative. For example: <code>4 mins 2 sec</code>, <code>2h42min</code>."
+msgstr ""
+
+msgid "Set the due date to %{due_date}."
+msgstr ""
+
+msgid "Set the duration for which the jobs will be considered as old and expired. Once that time passes, the jobs will be archived and no longer able to be retried. Make it empty to never expire jobs. It has to be no less than 1 day, for example: <code>15 days</code>, <code>1 month</code>, <code>2 years</code>."
+msgstr ""
+
+msgid "Set the iteration to %{iteration_reference}."
+msgstr ""
+
+msgid "Set the maximum file size for each job's artifacts"
+msgstr ""
+
+msgid "Set the maximum number of pipeline minutes that a group can use on shared Runners per month. 0 for unlimited."
+msgstr ""
+
+msgid "Set the milestone to %{milestone_reference}."
+msgstr ""
+
+msgid "Set the number of concurrent requests this secondary node will make to the primary node while backfilling."
+msgstr ""
+
+msgid "Set the synchronization and verification capacity for the secondary node."
+msgstr ""
+
+msgid "Set the timeout in seconds to send a secondary node status to the primary and IPs allowed for the secondary nodes."
+msgstr ""
+
+msgid "Set time estimate"
+msgstr ""
+
+msgid "Set time estimate to %{time_estimate}."
+msgstr ""
+
+msgid "Set up CI/CD"
+msgstr ""
+
+msgid "Set up Jira Integration"
+msgstr ""
+
+msgid "Set up a %{type} Runner automatically"
+msgstr ""
+
+msgid "Set up a %{type} Runner manually"
+msgstr ""
+
+msgid "Set up assertions/attributes/claims (email, first_name, last_name) and NameID according to %{docsLinkStart}the documentation %{icon}%{docsLinkEnd}"
+msgstr ""
+
+msgid "Set up new U2F device"
+msgstr ""
+
+msgid "Set up new password"
+msgstr ""
+
+msgid "Set up pipeline subscriptions for this project."
+msgstr ""
+
+msgid "Set up your project to automatically push and/or pull changes to/from another repository. Branches, tags, and commits will be synced automatically."
+msgstr ""
+
+msgid "Set weight"
+msgstr ""
+
+msgid "Set weight to %{weight}."
+msgstr ""
+
+msgid "Set what should be replicated by choosing specific projects or groups by the secondary node."
+msgstr ""
+
+msgid "SetPasswordToCloneLink|set a password"
+msgstr ""
+
+msgid "SetStatusModal|Add status emoji"
+msgstr ""
+
+msgid "SetStatusModal|Clear status"
+msgstr ""
+
+msgid "SetStatusModal|Edit status"
+msgstr ""
+
+msgid "SetStatusModal|Remove status"
+msgstr ""
+
+msgid "SetStatusModal|Set a status"
+msgstr ""
+
+msgid "SetStatusModal|Set status"
+msgstr ""
+
+msgid "SetStatusModal|Sorry, we weren't able to set your status. Please try again later."
+msgstr ""
+
+msgid "SetStatusModal|What's your status?"
+msgstr ""
+
+msgid "Sets %{epic_ref} as parent epic."
+msgstr ""
+
+msgid "Sets target branch to %{branch_name}."
+msgstr ""
+
+msgid "Sets the due date to %{due_date}."
+msgstr ""
+
+msgid "Sets the iteration to %{iteration_reference}."
+msgstr ""
+
+msgid "Sets the milestone to %{milestone_reference}."
+msgstr ""
+
+msgid "Sets time estimate to %{time_estimate}."
+msgstr ""
+
+msgid "Sets weight to %{weight}."
+msgstr ""
+
+msgid "Settings"
+msgstr ""
+
+msgid "Settings related to the use and experience of using GitLab's Package Registry."
+msgstr ""
+
+msgid "Settings to prevent self-approval across all projects in the instance. Only an administrator can modify these settings."
+msgstr ""
+
+msgid "Severity"
+msgstr ""
+
+msgid "Shards (%{shards})"
+msgstr ""
+
+msgid "Shards to synchronize"
+msgstr ""
+
+msgid "Share"
+msgstr ""
+
+msgid "Share the <strong>%{sso_label}</strong> with members so they can sign in to your group through your identity provider"
+msgstr ""
+
+msgid "Shared Runners"
+msgstr ""
+
+msgid "Shared projects"
+msgstr ""
+
+msgid "Shared runners help link"
+msgstr ""
+
+msgid "SharedRunnersMinutesSettings|By resetting the pipeline minutes for this namespace, the currently used minutes will be set to zero."
+msgstr ""
+
+msgid "SharedRunnersMinutesSettings|Reset pipeline minutes"
+msgstr ""
+
+msgid "SharedRunnersMinutesSettings|Reset used pipeline minutes"
+msgstr ""
+
+msgid "Sherlock Transactions"
+msgstr ""
+
+msgid "Should you ever lose your phone or access to your one time password secret, each of these recovery codes can be used one time each to regain access to your account. Please save them in a safe place, or you %{b_start}will%{b_end} lose access to your account."
+msgstr ""
+
+msgid "Show all activity"
+msgstr ""
+
+msgid "Show all members"
+msgstr ""
+
+msgid "Show all requirements."
+msgstr ""
+
+msgid "Show archived projects"
+msgstr ""
+
+msgid "Show archived projects only"
+msgstr ""
+
+msgid "Show command"
+msgstr ""
+
+msgid "Show comments"
+msgstr ""
+
+msgid "Show comments only"
+msgstr ""
+
+msgid "Show commit description"
+msgstr ""
+
+msgid "Show complete raw log"
+msgstr ""
+
+msgid "Show details"
+msgstr ""
+
+msgid "Show file browser"
+msgstr ""
+
+msgid "Show file contents"
+msgstr ""
+
+msgid "Show latest version"
+msgstr ""
+
+msgid "Show list"
+msgstr ""
+
+msgid "Show me everything"
+msgstr ""
+
+msgid "Show me how to add a pipeline"
+msgstr ""
+
+msgid "Show me more advanced stuff"
+msgstr ""
+
+msgid "Show only direct members"
+msgstr ""
+
+msgid "Show only inherited members"
+msgstr ""
+
+msgid "Show parent pages"
+msgstr ""
+
+msgid "Show parent subgroups"
+msgstr ""
+
+msgid "Show whitespace changes"
+msgstr ""
+
+msgid "Showing %d event"
+msgid_plural "Showing %d events"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Showing %{count} of %{total} projects"
+msgstr ""
+
+msgid "Showing %{count} project"
+msgid_plural "Showing %{count} projects"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Showing %{limit} of %{total_count} issues. "
+msgstr ""
+
+msgid "Showing %{pageSize} of %{total} issues"
+msgstr ""
+
+msgid "Showing Latest Version"
+msgstr ""
+
+msgid "Showing Version #%{versionNumber}"
+msgstr ""
+
+msgid "Showing all issues"
+msgstr ""
+
+msgid "Showing last %{size} of log -"
+msgstr ""
+
+msgid "Side-by-side"
+msgstr ""
+
+msgid "Sidebar|Assign health status"
+msgstr ""
+
+msgid "Sidebar|Change weight"
+msgstr ""
+
+msgid "Sidebar|Health status"
+msgstr ""
+
+msgid "Sidebar|No status"
+msgstr ""
+
+msgid "Sidebar|None"
+msgstr ""
+
+msgid "Sidebar|Only numeral characters allowed"
+msgstr ""
+
+msgid "Sidebar|Weight"
+msgstr ""
+
+msgid "Sign in"
+msgstr ""
+
+msgid "Sign in / Register"
+msgstr ""
+
+msgid "Sign in to \"%{group_name}\""
+msgstr ""
+
+msgid "Sign in using smart card"
+msgstr ""
+
+msgid "Sign in via 2FA code"
+msgstr ""
+
+msgid "Sign in with Single Sign-On"
+msgstr ""
+
+msgid "Sign in with smart card"
+msgstr ""
+
+msgid "Sign out"
+msgstr ""
+
+msgid "Sign out & Register"
+msgstr ""
+
+msgid "Sign up"
+msgstr ""
+
+msgid "Sign up was successful! Please confirm your email to sign in."
+msgstr ""
+
+msgid "Sign-in restrictions"
+msgstr ""
+
+msgid "Sign-up restrictions"
+msgstr ""
+
+msgid "SignUp|First Name is too long (maximum is %{max_length} characters)."
+msgstr ""
+
+msgid "SignUp|Last Name is too long (maximum is %{max_length} characters)."
+msgstr ""
+
+msgid "SignUp|Name is too long (maximum is %{max_length} characters)."
+msgstr ""
+
+msgid "SignUp|Username is too long (maximum is %{max_length} characters)."
+msgstr ""
+
+msgid "Signed in"
+msgstr ""
+
+msgid "Signed in with %{authentication} authentication"
+msgstr ""
+
+msgid "Signing in using %{label} has been disabled"
+msgstr ""
+
+msgid "Signing in using your %{label} account without a pre-existing GitLab account is not allowed."
+msgstr ""
+
+msgid "Similar issues"
+msgstr ""
+
+msgid "Single or combined queries"
+msgstr ""
+
+msgid "Size"
+msgstr ""
+
+msgid "Size and domain settings for static websites"
+msgstr ""
+
+msgid "Size limit per repository (MB)"
+msgstr ""
+
+msgid "Size settings for static websites"
+msgstr ""
+
+msgid "Skip outdated deployment jobs"
+msgstr ""
+
+msgid "Skipped"
+msgstr ""
+
+msgid "Slack application"
+msgstr ""
+
+msgid "Slack channels (e.g. general, development)"
+msgstr ""
+
+msgid "Slack integration allows you to interact with GitLab via slash commands in a chat window."
+msgstr ""
+
+msgid "SlackIntegration|%{webhooks_link_start}Add an incoming webhook%{webhooks_link_end} in your Slack team. The default channel can be overridden for each event."
+msgstr ""
+
+msgid "SlackIntegration|<strong>Note:</strong> Usernames and private channels are not supported."
+msgstr ""
+
+msgid "SlackIntegration|Paste the <strong>Webhook URL</strong> into the field below."
+msgstr ""
+
+msgid "SlackIntegration|Select events below to enable notifications. The <strong>Slack channel names</strong> and <strong>Slack username</strong> fields are optional."
+msgstr ""
+
+msgid "SlackIntegration|This service send notifications about projects' events to Slack channels. To set up this service:"
+msgstr ""
+
+msgid "SlackService|2. Paste the <strong>Token</strong> into the field below"
+msgstr ""
+
+msgid "SlackService|3. Select the <strong>Active</strong> checkbox, press <strong>Save changes</strong> and start using GitLab inside Slack!"
+msgstr ""
+
+msgid "SlackService|Fill in the word that works best for your team."
+msgstr ""
+
+msgid "SlackService|See list of available commands in Slack after setting up this service, by entering"
+msgstr ""
+
+msgid "SlackService|This service allows users to perform common operations on this project by entering slash commands in Slack."
+msgstr ""
+
+msgid "Slower but makes sure the project workspace is pristine as it clones the repository from scratch for every job"
+msgstr ""
+
+msgid "Smartcard"
+msgstr ""
+
+msgid "Smartcard authentication failed: client certificate header is missing."
+msgstr ""
+
+msgid "Snippets"
+msgstr ""
+
+msgid "Snippets with non-text files can only be edited via Git."
+msgstr ""
+
+msgid "SnippetsEmptyState|Code snippets"
+msgstr ""
+
+msgid "SnippetsEmptyState|Documentation"
+msgstr ""
+
+msgid "SnippetsEmptyState|New snippet"
+msgstr ""
+
+msgid "SnippetsEmptyState|No snippets found"
+msgstr ""
+
+msgid "SnippetsEmptyState|Store, share, and embed small pieces of code and text."
+msgstr ""
+
+msgid "SnippetsEmptyState|There are no snippets to show."
+msgstr ""
+
+msgid "Snippets|Description (optional)"
+msgstr ""
+
+msgid "Snippets|File"
+msgstr ""
+
+msgid "Snippets|Give your file a name to add code highlighting, e.g. example.rb for Ruby"
+msgstr ""
+
+msgid "Snippets|Optionally add a description about what your snippet does or how to use it..."
+msgstr ""
+
+msgid "Snippets|Optionally add a description about what your snippet does or how to use it…"
+msgstr ""
+
+msgid "Snowplow"
+msgstr ""
+
+msgid "Solution"
+msgstr ""
+
+msgid "Some child epics may be hidden due to applied filters"
+msgstr ""
+
+msgid "Some common domains are not allowed. %{read_more_link}."
+msgstr ""
+
+msgid "Some email servers do not support overriding the email sender name. Enable this option to include the name of the author of the issue, merge request or comment in the email body instead."
+msgstr ""
+
+msgid "Some of the designs you tried uploading did not change:"
+msgstr ""
+
+msgid "Some of your epics may not be visible. A roadmap is limited to the first 1,000 epics, in your selected sort order."
+msgstr ""
+
+msgid "Someone edited the issue at the same time you did. Please check out %{linkStart}the issue%{linkEnd} and make sure your changes will not unintentionally remove theirs."
+msgstr ""
+
+msgid "Someone edited this %{issueType} at the same time you did. The description has been updated and you will need to make your changes again."
+msgstr ""
+
+msgid "Someone edited this merge request at the same time you did. Please refresh the page to see changes."
+msgstr ""
+
+msgid "Something went wrong on our end"
+msgstr ""
+
+msgid "Something went wrong on our end."
+msgstr ""
+
+msgid "Something went wrong on our end. Please try again!"
+msgstr ""
+
+msgid "Something went wrong on our end. Please try again."
+msgstr ""
+
+msgid "Something went wrong trying to change the confidentiality of this issue"
+msgstr ""
+
+msgid "Something went wrong trying to change the locked state of this %{issuableDisplayName}"
+msgstr ""
+
+msgid "Something went wrong when toggling the button"
+msgstr ""
+
+msgid "Something went wrong while adding your award. Please try again."
+msgstr ""
+
+msgid "Something went wrong while applying the batch of suggestions. Please try again."
+msgstr ""
+
+msgid "Something went wrong while applying the suggestion. Please try again."
+msgstr ""
+
+msgid "Something went wrong while archiving a requirement."
+msgstr ""
+
+msgid "Something went wrong while closing the %{issuable}. Please try again later"
+msgstr ""
+
+msgid "Something went wrong while creating a requirement."
+msgstr ""
+
+msgid "Something went wrong while deleting description changes. Please try again."
+msgstr ""
+
+msgid "Something went wrong while deleting the package."
+msgstr ""
+
+msgid "Something went wrong while deleting the source branch. Please try again."
+msgstr ""
+
+msgid "Something went wrong while deleting your note. Please try again."
+msgstr ""
+
+msgid "Something went wrong while deploying this environment. Please try again."
+msgstr ""
+
+msgid "Something went wrong while editing your comment. Please try again."
+msgstr ""
+
+msgid "Something went wrong while fetching %{listType} list"
+msgstr ""
+
+msgid "Something went wrong while fetching comments. Please try again."
+msgstr ""
+
+msgid "Something went wrong while fetching description changes. Please try again."
+msgstr ""
+
+msgid "Something went wrong while fetching group member contributions"
+msgstr ""
+
+msgid "Something went wrong while fetching latest comments."
+msgstr ""
+
+msgid "Something went wrong while fetching projects"
+msgstr ""
+
+msgid "Something went wrong while fetching projects."
+msgstr ""
+
+msgid "Something went wrong while fetching related merge requests."
+msgstr ""
+
+msgid "Something went wrong while fetching requirements count."
+msgstr ""
+
+msgid "Something went wrong while fetching requirements list."
+msgstr ""
+
+msgid "Something went wrong while fetching the environments for this merge request. Please try again."
+msgstr ""
+
+msgid "Something went wrong while fetching the package."
+msgstr ""
+
+msgid "Something went wrong while fetching the packages list."
+msgstr ""
+
+msgid "Something went wrong while initializing the OpenAPI viewer"
+msgstr ""
+
+msgid "Something went wrong while merging this merge request. Please try again."
+msgstr ""
+
+msgid "Something went wrong while moving issues."
+msgstr ""
+
+msgid "Something went wrong while obtaining the Let's Encrypt certificate."
+msgstr ""
+
+msgid "Something went wrong while performing the action."
+msgstr ""
+
+msgid "Something went wrong while reopening a requirement."
+msgstr ""
+
+msgid "Something went wrong while reopening the %{issuable}. Please try again later"
+msgstr ""
+
+msgid "Something went wrong while resolving this discussion. Please try again."
+msgstr ""
+
+msgid "Something went wrong while stopping this environment. Please try again."
+msgstr ""
+
+msgid "Something went wrong while toggling auto-fix settings, please try again later."
+msgstr ""
+
+msgid "Something went wrong while updating a requirement."
+msgstr ""
+
+msgid "Something went wrong while updating assignees"
+msgstr ""
+
+msgid "Something went wrong while updating your list settings"
+msgstr ""
+
+msgid "Something went wrong with your automatic subscription renewal."
+msgstr ""
+
+msgid "Something went wrong, unable to add %{project} to dashboard"
+msgstr ""
+
+msgid "Something went wrong, unable to add projects to dashboard"
+msgstr ""
+
+msgid "Something went wrong, unable to get projects"
+msgstr ""
+
+msgid "Something went wrong, unable to remove project"
+msgstr ""
+
+msgid "Something went wrong, unable to search projects"
+msgstr ""
+
+msgid "Something went wrong."
+msgstr ""
+
+msgid "Something went wrong. Please try again."
+msgstr ""
+
+msgid "Something went wrong. Try again later."
+msgstr ""
+
+msgid "Sorry, no epics matched your search"
+msgstr ""
+
+msgid "Sorry, no projects matched your search"
+msgstr ""
+
+msgid "Sorry, you have exceeded the maximum browsable page number. Please use the API to explore further."
+msgstr ""
+
+msgid "Sorry, your filter produced no results"
+msgstr ""
+
+msgid "Sort by"
+msgstr ""
+
+msgid "Sort direction"
+msgstr ""
+
+msgid "Sort direction: Ascending"
+msgstr ""
+
+msgid "Sort direction: Descending"
+msgstr ""
+
+msgid "SortOptions|Access level, ascending"
+msgstr ""
+
+msgid "SortOptions|Access level, descending"
+msgstr ""
+
+msgid "SortOptions|Created date"
+msgstr ""
+
+msgid "SortOptions|Due date"
+msgstr ""
+
+msgid "SortOptions|Due later"
+msgstr ""
+
+msgid "SortOptions|Due soon"
+msgstr ""
+
+msgid "SortOptions|Expired date"
+msgstr ""
+
+msgid "SortOptions|Label priority"
+msgstr ""
+
+msgid "SortOptions|Largest group"
+msgstr ""
+
+msgid "SortOptions|Largest repository"
+msgstr ""
+
+msgid "SortOptions|Last Contact"
+msgstr ""
+
+msgid "SortOptions|Last created"
+msgstr ""
+
+msgid "SortOptions|Last joined"
+msgstr ""
+
+msgid "SortOptions|Last updated"
+msgstr ""
+
+msgid "SortOptions|Least popular"
+msgstr ""
+
+msgid "SortOptions|Less weight"
+msgstr ""
+
+msgid "SortOptions|Manual"
+msgstr ""
+
+msgid "SortOptions|Milestone due date"
+msgstr ""
+
+msgid "SortOptions|Milestone due later"
+msgstr ""
+
+msgid "SortOptions|Milestone due soon"
+msgstr ""
+
+msgid "SortOptions|More weight"
+msgstr ""
+
+msgid "SortOptions|Most popular"
+msgstr ""
+
+msgid "SortOptions|Most stars"
+msgstr ""
+
+msgid "SortOptions|Name"
+msgstr ""
+
+msgid "SortOptions|Name, ascending"
+msgstr ""
+
+msgid "SortOptions|Name, descending"
+msgstr ""
+
+msgid "SortOptions|Oldest created"
+msgstr ""
+
+msgid "SortOptions|Oldest joined"
+msgstr ""
+
+msgid "SortOptions|Oldest last activity"
+msgstr ""
+
+msgid "SortOptions|Oldest sign in"
+msgstr ""
+
+msgid "SortOptions|Oldest starred"
+msgstr ""
+
+msgid "SortOptions|Oldest updated"
+msgstr ""
+
+msgid "SortOptions|Popularity"
+msgstr ""
+
+msgid "SortOptions|Priority"
+msgstr ""
+
+msgid "SortOptions|Project"
+msgstr ""
+
+msgid "SortOptions|Recent last activity"
+msgstr ""
+
+msgid "SortOptions|Recent sign in"
+msgstr ""
+
+msgid "SortOptions|Recently starred"
+msgstr ""
+
+msgid "SortOptions|Size"
+msgstr ""
+
+msgid "SortOptions|Sort by:"
+msgstr ""
+
+msgid "SortOptions|Sort direction"
+msgstr ""
+
+msgid "SortOptions|Stars"
+msgstr ""
+
+msgid "SortOptions|Start date"
+msgstr ""
+
+msgid "SortOptions|Start later"
+msgstr ""
+
+msgid "SortOptions|Start soon"
+msgstr ""
+
+msgid "SortOptions|Type"
+msgstr ""
+
+msgid "SortOptions|Version"
+msgstr ""
+
+msgid "SortOptions|Weight"
+msgstr ""
+
+msgid "Source"
+msgstr ""
+
+msgid "Source (branch or tag)"
+msgstr ""
+
+msgid "Source code"
+msgstr ""
+
+msgid "Source code (%{fileExtension})"
+msgstr ""
+
+msgid "Source is not available"
+msgstr ""
+
+msgid "Source project cannot be found."
+msgstr ""
+
+msgid "Sourcegraph"
+msgstr ""
+
+msgid "SourcegraphAdmin|Block on private and internal projects"
+msgstr ""
+
+msgid "SourcegraphAdmin|Configure the URL to a Sourcegraph instance which can read your GitLab projects."
+msgstr ""
+
+msgid "SourcegraphAdmin|Enable Sourcegraph"
+msgstr ""
+
+msgid "SourcegraphAdmin|Enable code intelligence powered by %{link_start}Sourcegraph%{link_end} on your GitLab instance's code views and merge requests."
+msgstr ""
+
+msgid "SourcegraphAdmin|If checked, only public projects will have code intelligence and communicate with Sourcegraph."
+msgstr ""
+
+msgid "SourcegraphAdmin|More information"
+msgstr ""
+
+msgid "SourcegraphAdmin|Save changes"
+msgstr ""
+
+msgid "SourcegraphAdmin|Sourcegraph URL"
+msgstr ""
+
+msgid "SourcegraphAdmin|e.g. https://sourcegraph.example.com"
+msgstr ""
+
+msgid "SourcegraphPreferences|This feature is experimental and currently limited to certain projects."
+msgstr ""
+
+msgid "SourcegraphPreferences|This feature is experimental and limited to public projects."
+msgstr ""
+
+msgid "SourcegraphPreferences|This feature is experimental."
+msgstr ""
+
+msgid "SourcegraphPreferences|Uses %{link_start}Sourcegraph.com%{link_end}."
+msgstr ""
+
+msgid "SourcegraphPreferences|Uses a custom %{link_start}Sourcegraph instance%{link_end}."
+msgstr ""
+
+msgid "Spam Logs"
+msgstr ""
+
+msgid "Spam and Anti-bot Protection"
+msgstr ""
+
+msgid "Spam log successfully submitted as ham."
+msgstr ""
+
+msgid "Specific Runners"
+msgstr ""
+
+msgid "Specified URL cannot be used: \"%{reason}\""
+msgstr ""
+
+msgid "Specify an e-mail address regex pattern to identify default internal users."
+msgstr ""
+
+msgid "Specify the following URL during the Runner setup:"
+msgstr ""
+
+msgid "Speed up your DevOps<br>with GitLab"
+msgstr ""
+
+msgid "Squash commit message"
+msgstr ""
+
+msgid "Squash commits"
+msgstr ""
+
+msgid "Stack trace"
+msgstr ""
+
+msgid "Stacktrace snippet"
+msgstr ""
+
+msgid "Stage"
+msgstr ""
+
+msgid "Stage & Commit"
+msgstr ""
+
+msgid "Stage data updated"
+msgstr ""
+
+msgid "Stage removed"
+msgstr ""
+
+msgid "Standard"
+msgstr ""
+
+msgid "Star a label to make it a priority label. Order the prioritized labels to change their relative priority, by dragging."
+msgstr ""
+
+msgid "Star labels to start sorting by priority"
+msgstr ""
+
+msgid "Star toggle failed. Try again later."
+msgstr ""
+
+msgid "StarProject|Star"
+msgstr ""
+
+msgid "Starred Projects"
+msgstr ""
+
+msgid "Starred Projects' Activity"
+msgstr ""
+
+msgid "Starred projects"
+msgstr ""
+
+msgid "StarredProjectsEmptyState|Visit a project page and press on a star icon. Then, you can find the project on this page."
+msgstr ""
+
+msgid "StarredProjectsEmptyState|You don't have starred projects yet."
+msgstr ""
+
+msgid "Starrers"
+msgstr ""
+
+msgid "Stars"
+msgstr ""
+
+msgid "Start Date"
+msgstr ""
+
+msgid "Start Web Terminal"
+msgstr ""
+
+msgid "Start a %{new_merge_request} with these changes"
+msgstr ""
+
+msgid "Start a Free Gold Trial"
+msgstr ""
+
+msgid "Start a new discussion..."
+msgstr ""
+
+msgid "Start a new merge request"
+msgstr ""
+
+msgid "Start a review"
+msgstr ""
+
+msgid "Start and due date"
+msgstr ""
+
+msgid "Start by choosing a group to see how your team is spending time. You can then drill down to the project level."
+msgstr ""
+
+msgid "Start by choosing a group to start exploring the merge requests in that group. You can then proceed to filter by projects, labels, milestones and authors."
+msgstr ""
+
+msgid "Start cleanup"
+msgstr ""
+
+msgid "Start date"
+msgstr ""
+
+msgid "Start merge train"
+msgstr ""
+
+msgid "Start merge train when pipeline succeeds"
+msgstr ""
+
+msgid "Start search"
+msgstr ""
+
+msgid "Start the Runner!"
+msgstr ""
+
+msgid "Start thread"
+msgstr ""
+
+msgid "Start thread & close %{noteable_name}"
+msgstr ""
+
+msgid "Start thread & reopen %{noteable_name}"
+msgstr ""
+
+msgid "Start using Directed Acyclic Graphs (DAG)"
+msgstr ""
+
+msgid "Start your Free Gold Trial"
+msgstr ""
+
+msgid "Start your free trial"
+msgstr ""
+
+msgid "Start your trial"
+msgstr ""
+
+msgid "Started"
+msgstr ""
+
+msgid "Started %{startsIn}"
+msgstr ""
+
+msgid "Started asynchronous removal of all repository check states."
+msgstr ""
+
+msgid "Started:"
+msgstr ""
+
+msgid "Starting..."
+msgstr ""
+
+msgid "Starts %{startsIn}"
+msgstr ""
+
+msgid "Starts at (UTC)"
+msgstr ""
+
+msgid "State your message to activate"
+msgstr ""
+
+msgid "Static Application Security Testing (SAST)"
+msgstr ""
+
+msgid "StaticSiteEditor|An error occurred while submitting your changes."
+msgstr ""
+
+msgid "StaticSiteEditor|Branch could not be created."
+msgstr ""
+
+msgid "StaticSiteEditor|Could not commit the content changes."
+msgstr ""
+
+msgid "StaticSiteEditor|Could not create merge request."
+msgstr ""
+
+msgid "StaticSiteEditor|Incompatible file content"
+msgstr ""
+
+msgid "StaticSiteEditor|Return to site"
+msgstr ""
+
+msgid "StaticSiteEditor|Static site editor"
+msgstr ""
+
+msgid "StaticSiteEditor|Success!"
+msgstr ""
+
+msgid "StaticSiteEditor|Summary of changes"
+msgstr ""
+
+msgid "StaticSiteEditor|The Static Site Editor is currently configured to only edit Markdown content on pages generated from Middleman. Visit the documentation to learn more about configuring your site to use the Static Site Editor."
+msgstr ""
+
+msgid "StaticSiteEditor|Update %{sourcePath} file"
+msgstr ""
+
+msgid "StaticSiteEditor|View documentation"
+msgstr ""
+
+msgid "StaticSiteEditor|View merge request"
+msgstr ""
+
+msgid "StaticSiteEditor|You added a commit:"
+msgstr ""
+
+msgid "StaticSiteEditor|You created a merge request:"
+msgstr ""
+
+msgid "StaticSiteEditor|You created a new branch:"
+msgstr ""
+
+msgid "StaticSiteEditor|Your changes have been submitted and a merge request has been created. The changes won’t be visible on the site until the merge request has been accepted."
+msgstr ""
+
+msgid "Statistics"
+msgstr ""
+
+msgid "Status"
+msgstr ""
+
+msgid "Status:"
+msgstr ""
+
+msgid "Status: %{title}"
+msgstr ""
+
+msgid "StatusPage|AWS Secret access key"
+msgstr ""
+
+msgid "StatusPage|AWS access key ID"
+msgstr ""
+
+msgid "StatusPage|AWS documentation"
+msgstr ""
+
+msgid "StatusPage|AWS region"
+msgstr ""
+
+msgid "StatusPage|Active"
+msgstr ""
+
+msgid "StatusPage|Bucket %{docsLink}"
+msgstr ""
+
+msgid "StatusPage|Configure file storage settings to link issues in this project to an external status page."
+msgstr ""
+
+msgid "StatusPage|For help with configuration, visit %{docsLink}"
+msgstr ""
+
+msgid "StatusPage|S3 Bucket name"
+msgstr ""
+
+msgid "StatusPage|Status page"
+msgstr ""
+
+msgid "StatusPage|Status page URL"
+msgstr ""
+
+msgid "StatusPage|Status page frontend documentation"
+msgstr ""
+
+msgid "StatusPage|To publish incidents to an external status page, GitLab will store a JSON file in your Amazon S3 account in a location accessible to your external status page service. Make sure to also set up %{docsLink}"
+msgstr ""
+
+msgid "StatusPage|configuration documentation"
+msgstr ""
+
+msgid "StatusPage|your status page frontend."
+msgstr ""
+
+msgid "Stay updated about the performance and health of your environment by configuring Prometheus to monitor your deployments."
+msgstr ""
+
+msgid "Still, we recommend keeping a backup saved somewhere. Otherwise, if you ever need it and have lost it, you will need to request GitLab Inc. to send it to you again."
+msgstr ""
+
+msgid "Stop Terminal"
+msgstr ""
+
+msgid "Stop impersonation"
+msgstr ""
+
+msgid "Stop this environment"
+msgstr ""
+
+msgid "Stopped"
+msgstr ""
+
+msgid "Stopping..."
+msgstr ""
+
+msgid "Storage"
+msgstr ""
+
+msgid "Storage nodes for new repositories"
+msgstr ""
+
+msgid "Storage:"
+msgstr ""
+
+msgid "StorageSize|Unknown"
+msgstr ""
+
+msgid "Subgroup milestone"
+msgstr ""
+
+msgid "Subgroup overview"
+msgstr ""
+
+msgid "SubgroupCreationLevel|Allowed to create subgroups"
+msgstr ""
+
+msgid "SubgroupCreationlevel|Allowed to create subgroups"
+msgstr ""
+
+msgid "SubgroupCreationlevel|Maintainers"
+msgstr ""
+
+msgid "SubgroupCreationlevel|Owners"
+msgstr ""
+
+msgid "Subgroups"
+msgstr ""
+
+msgid "Subgroups and projects"
+msgstr ""
+
+msgid "Subject Key Identifier:"
+msgstr ""
+
+msgid "Subkeys"
+msgstr ""
+
+msgid "Submit %{humanized_resource_name}"
+msgstr ""
+
+msgid "Submit Changes"
+msgstr ""
+
+msgid "Submit a review"
+msgstr ""
+
+msgid "Submit as spam"
+msgstr ""
+
+msgid "Submit feedback"
+msgstr ""
+
+msgid "Submit issue"
+msgstr ""
+
+msgid "Submit review"
+msgstr ""
+
+msgid "Submit search"
+msgstr ""
+
+msgid "Submit the current review."
+msgstr ""
+
+msgid "Submitted the current review."
+msgstr ""
+
+msgid "Subscribe"
+msgstr ""
+
+msgid "Subscribe at group level"
+msgstr ""
+
+msgid "Subscribe at project level"
+msgstr ""
+
+msgid "Subscribe to RSS feed"
+msgstr ""
+
+msgid "Subscribe to calendar"
+msgstr ""
+
+msgid "Subscribed"
+msgstr ""
+
+msgid "Subscribed to this %{quick_action_target}."
+msgstr ""
+
+msgid "Subscribes to this %{quick_action_target}."
+msgstr ""
+
+msgid "Subscription"
+msgstr ""
+
+msgid "Subscription deletion failed."
+msgstr ""
+
+msgid "Subscription successfully applied to \"%{group_name}\""
+msgstr ""
+
+msgid "Subscription successfully created."
+msgstr ""
+
+msgid "Subscription successfully deleted."
+msgstr ""
+
+msgid "SubscriptionTable|Billing"
+msgstr ""
+
+msgid "SubscriptionTable|Free"
+msgstr ""
+
+msgid "SubscriptionTable|GitLab allows you to continue using your subscription even if you exceed the number of seats you purchased. You will be required to pay for these seats upon renewal."
+msgstr ""
+
+msgid "SubscriptionTable|Last invoice"
+msgstr ""
+
+msgid "SubscriptionTable|Loading subscriptions"
+msgstr ""
+
+msgid "SubscriptionTable|Manage"
+msgstr ""
+
+msgid "SubscriptionTable|Max seats used"
+msgstr ""
+
+msgid "SubscriptionTable|Next invoice"
+msgstr ""
+
+msgid "SubscriptionTable|Seats currently in use"
+msgstr ""
+
+msgid "SubscriptionTable|Seats in subscription"
+msgstr ""
+
+msgid "SubscriptionTable|Seats owed"
+msgstr ""
+
+msgid "SubscriptionTable|Subscription end date"
+msgstr ""
+
+msgid "SubscriptionTable|Subscription start date"
+msgstr ""
+
+msgid "SubscriptionTable|This is the last time the GitLab.com team was in contact with you to settle any outstanding balances."
+msgstr ""
+
+msgid "SubscriptionTable|This is the maximum number of users that have existed at the same time since this subscription started."
+msgstr ""
+
+msgid "SubscriptionTable|This is the next date when the GitLab.com team is scheduled to get in contact with you to settle any outstanding balances."
+msgstr ""
+
+msgid "SubscriptionTable|This is the number of seats you will be required to purchase if you update to a paid plan."
+msgstr ""
+
+msgid "SubscriptionTable|Trial"
+msgstr ""
+
+msgid "SubscriptionTable|Trial end date"
+msgstr ""
+
+msgid "SubscriptionTable|Trial start date"
+msgstr ""
+
+msgid "SubscriptionTable|Upgrade"
+msgstr ""
+
+msgid "SubscriptionTable|Usage"
+msgstr ""
+
+msgid "SubscriptionTable|Usage count is performed once a day at 12:00 PM."
+msgstr ""
+
+msgid "Subscriptions"
+msgstr ""
+
+msgid "Subtracted"
+msgstr ""
+
+msgid "Subtracts"
+msgstr ""
+
+msgid "Succeeded"
+msgstr ""
+
+msgid "Successfully activated"
+msgstr ""
+
+msgid "Successfully blocked"
+msgstr ""
+
+msgid "Successfully confirmed"
+msgstr ""
+
+msgid "Successfully deactivated"
+msgstr ""
+
+msgid "Successfully deleted U2F device."
+msgstr ""
+
+msgid "Successfully removed email."
+msgstr ""
+
+msgid "Successfully scheduled a pipeline to run. Go to the %{pipelines_link_start}Pipelines page%{pipelines_link_end} for details."
+msgstr ""
+
+msgid "Successfully unblocked"
+msgstr ""
+
+msgid "Successfully unlocked"
+msgstr ""
+
+msgid "Successfully verified domain ownership"
+msgstr ""
+
+msgid "Suggest code changes which can be immediately applied in one click. Try it out!"
+msgstr ""
+
+msgid "Suggested Solutions"
+msgstr ""
+
+msgid "Suggested change"
+msgstr ""
+
+msgid "Suggested solutions help link"
+msgstr ""
+
+msgid "SuggestedColors|Bright green"
+msgstr ""
+
+msgid "SuggestedColors|Dark grayish cyan"
+msgstr ""
+
+msgid "SuggestedColors|Dark moderate blue"
+msgstr ""
+
+msgid "SuggestedColors|Dark moderate orange"
+msgstr ""
+
+msgid "SuggestedColors|Dark moderate pink"
+msgstr ""
+
+msgid "SuggestedColors|Dark moderate violet"
+msgstr ""
+
+msgid "SuggestedColors|Feijoa"
+msgstr ""
+
+msgid "SuggestedColors|Lime green"
+msgstr ""
+
+msgid "SuggestedColors|Moderate blue"
+msgstr ""
+
+msgid "SuggestedColors|Pure red"
+msgstr ""
+
+msgid "SuggestedColors|Slightly desaturated blue"
+msgstr ""
+
+msgid "SuggestedColors|Slightly desaturated green"
+msgstr ""
+
+msgid "SuggestedColors|Soft orange"
+msgstr ""
+
+msgid "SuggestedColors|Soft red"
+msgstr ""
+
+msgid "SuggestedColors|Strong pink"
+msgstr ""
+
+msgid "SuggestedColors|Strong red"
+msgstr ""
+
+msgid "SuggestedColors|Strong yellow"
+msgstr ""
+
+msgid "SuggestedColors|UA blue"
+msgstr ""
+
+msgid "SuggestedColors|Very dark desaturated blue"
+msgstr ""
+
+msgid "SuggestedColors|Very dark lime green"
+msgstr ""
+
+msgid "SuggestedColors|Very pale orange"
+msgstr ""
+
+msgid "Suggestion is not applicable as the suggestion was not found."
+msgstr ""
+
+msgid "Suggestions are not applicable as one or more suggestions were not found."
+msgstr ""
+
+msgid "Suggestions are not applicable as their lines cannot overlap."
+msgstr ""
+
+msgid "Suggestions must all be on the same branch."
+msgstr ""
+
+msgid "Suggestions that change line count can't be added to batches, yet."
+msgstr ""
+
+msgid "Suggestions:"
+msgstr ""
+
+msgid "Suite"
+msgstr ""
+
+msgid "Summary"
+msgstr ""
+
+msgid "Sunday"
+msgstr ""
+
+msgid "Support"
+msgstr ""
+
+msgid "Support for custom certificates is disabled. Ask your system's administrator to enable it."
+msgstr ""
+
+msgid "Support page URL"
+msgstr ""
+
+msgid "Survey Response"
+msgstr ""
+
+msgid "Switch branch/tag"
+msgstr ""
+
+msgid "Switch to GitLab Next"
+msgstr ""
+
+msgid "Switch to the source to copy the file contents"
+msgstr ""
+
+msgid "Sync information"
+msgstr ""
+
+msgid "Synced"
+msgstr ""
+
+msgid "Synchronization disabled"
+msgstr ""
+
+msgid "System"
+msgstr ""
+
+msgid "System Hooks"
+msgstr ""
+
+msgid "System Info"
+msgstr ""
+
+msgid "System default (%{default})"
+msgstr ""
+
+msgid "System header and footer"
+msgstr ""
+
+msgid "System hook was successfully updated."
+msgstr ""
+
+msgid "System metrics (Custom)"
+msgstr ""
+
+msgid "System metrics (Kubernetes)"
+msgstr ""
+
+msgid "Table of Contents"
+msgstr ""
+
+msgid "Tag"
+msgstr ""
+
+msgid "Tag list:"
+msgstr ""
+
+msgid "Tag name"
+msgstr ""
+
+msgid "Tag this commit."
+msgstr ""
+
+msgid "Tagged this commit to %{tag_name} with \"%{message}\"."
+msgstr ""
+
+msgid "Tagged this commit to %{tag_name}."
+msgstr ""
+
+msgid "Tags"
+msgstr ""
+
+msgid "Tags feed"
+msgstr ""
+
+msgid "Tags this commit to %{tag_name} with \"%{message}\"."
+msgstr ""
+
+msgid "Tags this commit to %{tag_name}."
+msgstr ""
+
+msgid "Tags:"
+msgstr ""
+
+msgid "TagsPage|Browse commits"
+msgstr ""
+
+msgid "TagsPage|Browse files"
+msgstr ""
+
+msgid "TagsPage|Can't find HEAD commit for this tag"
+msgstr ""
+
+msgid "TagsPage|Cancel"
+msgstr ""
+
+msgid "TagsPage|Create tag"
+msgstr ""
+
+msgid "TagsPage|Delete tag"
+msgstr ""
+
+msgid "TagsPage|Deleting the %{tag_name} tag cannot be undone. Are you sure?"
+msgstr ""
+
+msgid "TagsPage|Edit release notes"
+msgstr ""
+
+msgid "TagsPage|Existing branch name, tag, or commit SHA"
+msgstr ""
+
+msgid "TagsPage|Filter by tag name"
+msgstr ""
+
+msgid "TagsPage|New Tag"
+msgstr ""
+
+msgid "TagsPage|New tag"
+msgstr ""
+
+msgid "TagsPage|Optionally, add a message to the tag. Leaving this blank creates a %{link_start}lightweight tag.%{link_end}"
+msgstr ""
+
+msgid "TagsPage|Optionally, create a public Release of your project, based on this tag. Release notes are displayed on the %{releases_page_link_start}Releases%{link_end} page. %{docs_link_start}More information%{link_end}"
+msgstr ""
+
+msgid "TagsPage|Release notes"
+msgstr ""
+
+msgid "TagsPage|Repository has no tags yet."
+msgstr ""
+
+msgid "TagsPage|Sort by"
+msgstr ""
+
+msgid "TagsPage|Tags"
+msgstr ""
+
+msgid "TagsPage|Tags give the ability to mark specific points in history as being important"
+msgstr ""
+
+msgid "TagsPage|This tag has no release notes."
+msgstr ""
+
+msgid "TagsPage|Use git tag command to add a new one:"
+msgstr ""
+
+msgid "TagsPage|Write your release notes or drag files here…"
+msgstr ""
+
+msgid "TagsPage|protected"
+msgstr ""
+
+msgid "Target Branch"
+msgstr ""
+
+msgid "Target Path"
+msgstr ""
+
+msgid "Target branch"
+msgstr ""
+
+msgid "Target-Branch"
+msgstr ""
+
+msgid "Team"
+msgstr ""
+
+msgid "Team domain"
+msgstr ""
+
+msgid "Telephone number"
+msgstr ""
+
+msgid "Telephone number (Optional)"
+msgstr ""
+
+msgid "Template"
+msgstr ""
+
+msgid "Template to append to all Service Desk issues"
+msgstr ""
+
+msgid "Template was successfully saved."
+msgstr ""
+
+msgid "Templates"
+msgstr ""
+
+msgid "Terminal"
+msgstr ""
+
+msgid "Terminal for environment"
+msgstr ""
+
+msgid "Terminal sync service is running"
+msgstr ""
+
+msgid "Terms of Service Agreement and Privacy Policy"
+msgstr ""
+
+msgid "Terms of Service and Privacy Policy"
+msgstr ""
+
+msgid "Terraform|%{number} Terraform report failed to generate"
+msgid_plural "Terraform|%{number} Terraform reports failed to generate"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Terraform|%{number} Terraform report was generated in your pipelines"
+msgid_plural "Terraform|%{number} Terraform reports were generated in your pipelines"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Terraform|A Terraform report failed to generate."
+msgstr ""
+
+msgid "Terraform|A Terraform report was generated in your pipelines."
+msgstr ""
+
+msgid "Terraform|Generating the report caused an error."
+msgstr ""
+
+msgid "Terraform|Reported Resource Changes: %{addNum} to add, %{changeNum} to change, %{deleteNum} to delete"
+msgstr ""
+
+msgid "Terraform|The Terraform report %{name} failed to generate."
+msgstr ""
+
+msgid "Terraform|The Terraform report %{name} was generated in your pipelines."
+msgstr ""
+
+msgid "Test"
+msgstr ""
+
+msgid "Test coverage parsing"
+msgstr ""
+
+msgid "Test coverage: %d hit"
+msgid_plural "Test coverage: %d hits"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Test failed."
+msgstr ""
+
+msgid "Test settings and save changes"
+msgstr ""
+
+msgid "TestHooks|Ensure one of your projects has merge requests."
+msgstr ""
+
+msgid "TestHooks|Ensure the project has CI jobs."
+msgstr ""
+
+msgid "TestHooks|Ensure the project has CI pipelines."
+msgstr ""
+
+msgid "TestHooks|Ensure the project has deployments."
+msgstr ""
+
+msgid "TestHooks|Ensure the project has issues."
+msgstr ""
+
+msgid "TestHooks|Ensure the project has merge requests."
+msgstr ""
+
+msgid "TestHooks|Ensure the project has notes."
+msgstr ""
+
+msgid "TestHooks|Ensure the wiki is enabled and has pages."
+msgstr ""
+
+msgid "TestReports|%{count} errors"
+msgstr ""
+
+msgid "TestReports|%{count} failures"
+msgstr ""
+
+msgid "TestReports|%{count} jobs"
+msgstr ""
+
+msgid "TestReports|%{rate}%{sign} success rate"
+msgstr ""
+
+msgid "TestReports|Test suites"
+msgstr ""
+
+msgid "TestReports|Tests"
+msgstr ""
+
+msgid "TestReports|There are no test cases to display."
+msgstr ""
+
+msgid "TestReports|There are no test suites to show."
+msgstr ""
+
+msgid "TestReports|There are no tests to show."
+msgstr ""
+
+msgid "TestReports|There was an error fetching the test reports."
+msgstr ""
+
+msgid "Tests"
+msgstr ""
+
+msgid "Thank you for signing up for your free trial! You will get additional instructions in your inbox shortly."
+msgstr ""
+
+msgid "Thank you for your feedback!"
+msgstr ""
+
+msgid "Thank you for your report. A GitLab administrator will look into it shortly."
+msgstr ""
+
+msgid "Thanks for your purchase!"
+msgstr ""
+
+msgid "Thanks! Don't show me this again"
+msgstr ""
+
+msgid "That's it, well done!%{celebrate}"
+msgstr ""
+
+msgid "The \"%{group_path}\" group allows you to sign in with your Single Sign-On Account"
+msgstr ""
+
+msgid "The \"Require approval from CODEOWNERS\" setting was moved to %{banner_link_start}Protected Branches%{banner_link_end}"
+msgstr ""
+
+msgid "The %{link_start}true-up model%{link_end} allows having more users, and additional users will incur a retroactive charge on renewal."
+msgstr ""
+
+msgid "The %{true_up_link_start}true-up model%{link_end} has a retroactive charge for these users at the next renewal. If you want to update your license sooner to prevent this, %{support_link_start}please contact our Support team%{link_end}."
+msgstr ""
+
+msgid "The %{type} contains the following error:"
+msgid_plural "The %{type} contains the following errors:"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "The Advanced Global Search in GitLab is a powerful search service that saves you time. Instead of creating duplicate code and wasting time, you can now search for code within other teams that can help your own project."
+msgstr ""
+
+msgid "The CSV export will be created in the background. Once finished, it will be sent to <strong>%{email}</strong> in an attachment."
+msgstr ""
+
+msgid "The Git LFS objects will <strong>not</strong> be synced."
+msgstr ""
+
+msgid "The Issue Tracker is the place to add things that need to be improved or solved in a project"
+msgstr ""
+
+msgid "The Issue Tracker is the place to add things that need to be improved or solved in a project. You can register or sign in to create issues for this project."
+msgstr ""
+
+msgid "The Prometheus server responded with \"bad request\". Please check your queries are correct and are supported in your Prometheus version. %{documentationLink}"
+msgstr ""
+
+msgid "The URL defined on the primary node that secondary nodes should use to contact it."
+msgstr ""
+
+msgid "The URL to use for connecting to Elasticsearch. Use a comma-separated list to support clustering (e.g., \"http://localhost:9200, http://localhost:9201\")."
+msgstr ""
+
+msgid "The X509 Certificate to use when mutual TLS is required to communicate with the external authorization service. If left blank, the server certificate is still validated when accessing over HTTPS."
+msgstr ""
+
+msgid "The above settings apply to all projects with the selected compliance framework(s)."
+msgstr ""
+
+msgid "The amount of seconds after which a request to get a secondary node status will time out."
+msgstr ""
+
+msgid "The application will be used where the client secret can be kept confidential. Native mobile apps and Single Page Apps are considered non-confidential."
+msgstr ""
+
+msgid "The associated issue #%{issueId} has been closed as the error is now resolved."
+msgstr ""
+
+msgid "The branch for this project has no active pipeline configuration."
+msgstr ""
+
+msgid "The branch or tag does not exist"
+msgstr ""
+
+msgid "The character highlighter helps you keep the subject line to %{titleLength} characters and wrap the body at %{bodyLength} so they are readable in git."
+msgstr ""
+
+msgid "The coding stage shows the time from the first commit to creating the merge request. The data will automatically be added here once you create your first merge request."
+msgstr ""
+
+msgid "The collection of events added to the data gathered for that stage."
+msgstr ""
+
+msgid "The commit does not exist"
+msgstr ""
+
+msgid "The connection will time out after %{timeout}. For repositories that take longer, use a clone/push combination."
+msgstr ""
+
+msgid "The content of this page is not encoded in UTF-8. Edits can only be made via the Git repository."
+msgstr ""
+
+msgid "The contents of this group, its subgroups and projects will be permanently removed after %{deletion_adjourned_period} days on %{date}. After this point, your data cannot be recovered."
+msgstr ""
+
+msgid "The current issue"
+msgstr ""
+
+msgid "The data source is connected, but there is no data to display. %{documentationLink}"
+msgstr ""
+
+msgid "The default CI configuration path for new projects."
+msgstr ""
+
+msgid "The dependency list details information about the components used within your project."
+msgstr ""
+
+msgid "The deployment of this job to %{environmentLink} did not succeed."
+msgstr ""
+
+msgid "The designs you tried uploading did not change."
+msgstr ""
+
+msgid "The directory has been successfully created."
+msgstr ""
+
+msgid "The domain you entered is misformatted."
+msgstr ""
+
+msgid "The domain you entered is not allowed."
+msgstr ""
+
+msgid "The download link will expire in 24 hours."
+msgstr ""
+
+msgid "The entered user map is not a valid JSON user map."
+msgstr ""
+
+msgid "The errors we encountered were:"
+msgstr ""
+
+msgid "The file has been successfully created."
+msgstr ""
+
+msgid "The file has been successfully deleted."
+msgstr ""
+
+msgid "The file name should have a .yml extension"
+msgstr ""
+
+msgid "The following items will NOT be exported:"
+msgstr ""
+
+msgid "The following items will be exported:"
+msgstr ""
+
+msgid "The following personal access token: %{token_names} was revoked, because a new policy to expire personal access tokens were set."
+msgid_plural "The following personal access tokens: %{token_names} were revoked, because a new policy to expire personal access tokens were set."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "The fork relationship has been removed."
+msgstr ""
+
+msgid "The global settings require you to enable Two-Factor Authentication for your account."
+msgstr ""
+
+msgid "The group and any internal projects can be viewed by any logged in user."
+msgstr ""
+
+msgid "The group and any public projects can be viewed without any authentication."
+msgstr ""
+
+msgid "The group and its projects can only be viewed by members."
+msgstr ""
+
+msgid "The group can be fully restored"
+msgstr ""
+
+msgid "The group export can be downloaded from:"
+msgstr ""
+
+msgid "The group has already been shared with this group"
+msgstr ""
+
+msgid "The group settings for %{group_links} require you to enable Two-Factor Authentication for your account. You can %{leave_group_links}."
+msgstr ""
+
+msgid "The group will be placed in 'pending removal' state"
+msgstr ""
+
+msgid "The import will time out after %{timeout}. For repositories that take longer, use a clone/push combination."
+msgstr ""
+
+msgid "The invitation could not be accepted."
+msgstr ""
+
+msgid "The invitation could not be declined."
+msgstr ""
+
+msgid "The invitation has already been accepted."
+msgstr ""
+
+msgid "The invitation was successfully resent."
+msgstr ""
+
+msgid "The issue stage shows the time it takes from creating an issue to assigning the issue to a milestone, or add the issue to a list on your Issue Board. Begin creating issues to see data for this stage."
+msgstr ""
+
+msgid "The license key is invalid. Make sure it is exactly as you received it from GitLab Inc."
+msgstr ""
+
+msgid "The license was removed. GitLab has fallen back on the previous license."
+msgstr ""
+
+msgid "The license was removed. GitLab now no longer has a valid license."
+msgstr ""
+
+msgid "The license was successfully uploaded and is now active. You can see the details below."
+msgstr ""
+
+msgid "The license was successfully uploaded and will be active from %{starts_at}. You can see the details below."
+msgstr ""
+
+msgid "The maximum file size allowed is %{size}."
+msgstr ""
+
+msgid "The maximum file size allowed is 200KB."
+msgstr ""
+
+msgid "The merge conflicts for this merge request cannot be resolved through GitLab. Please try to resolve them locally."
+msgstr ""
+
+msgid "The merge conflicts for this merge request have already been resolved."
+msgstr ""
+
+msgid "The merge conflicts for this merge request have already been resolved. Please return to the merge request."
+msgstr ""
+
+msgid "The merge request can now be merged."
+msgstr ""
+
+msgid "The name \"%{name}\" is already taken in this directory."
+msgstr ""
+
+msgid "The number of changes to be fetched from GitLab when cloning a repository. This can speed up Pipelines execution. Keep empty or set to 0 to disable shallow clone by default and make GitLab CI fetch all branches and tags each time."
+msgstr ""
+
+msgid "The number of times an upload record could not find its file"
+msgstr ""
+
+msgid "The one place for your designs"
+msgstr ""
+
+msgid "The passphrase required to decrypt the private key. This is optional and the value is encrypted at rest."
+msgstr ""
+
+msgid "The path to the CI configuration file. Defaults to <code>.gitlab-ci.yml</code>"
+msgstr ""
+
+msgid "The phase of the development lifecycle."
+msgstr ""
+
+msgid "The pipeline has been deleted"
+msgstr ""
+
+msgid "The pipelines schedule runs pipelines in the future, repeatedly, for specific branches or tags. Those scheduled pipelines will inherit limited project access based on their associated user."
+msgstr ""
+
+msgid "The planning stage shows the time from the previous step to pushing your first commit. This time will be added automatically once you push your first commit."
+msgstr ""
+
+msgid "The private key to use when a client certificate is provided. This value is encrypted at rest."
+msgstr ""
+
+msgid "The production stage shows the total time it takes between creating an issue and deploying the code to production. The data will be automatically added once you have completed the full idea to production cycle."
+msgstr ""
+
+msgid "The project can be accessed by any logged in user."
+msgstr ""
+
+msgid "The project can be accessed by any user who is logged in."
+msgstr ""
+
+msgid "The project can be accessed by anyone, regardless of authentication."
+msgstr ""
+
+msgid "The project can be accessed without any authentication."
+msgstr ""
+
+msgid "The project is accessible only by members of the project. Access must be granted explicitly to each user."
+msgstr ""
+
+msgid "The project is still being deleted. Please try again later."
+msgstr ""
+
+msgid "The project was successfully forked."
+msgstr ""
+
+msgid "The project was successfully imported."
+msgstr ""
+
+msgid "The pseudonymizer data collection is disabled. When enabled, GitLab will run a background job that will produce pseudonymized CSVs of the GitLab database that will be uploaded to your configured object storage directory."
+msgstr ""
+
+msgid "The remote mirror took to long to complete."
+msgstr ""
+
+msgid "The remote repository is being updated..."
+msgstr ""
+
+msgid "The repository can be commited to, and issues, comments and other entities can be created."
+msgstr ""
+
+msgid "The repository for this project does not exist."
+msgstr ""
+
+msgid "The repository for this project is empty"
+msgstr ""
+
+msgid "The repository is being updated..."
+msgstr ""
+
+msgid "The repository must be accessible over <code>http://</code>, <code>https://</code> or <code>git://</code>."
+msgstr ""
+
+msgid "The repository must be accessible over <code>http://</code>, <code>https://</code>, <code>ssh://</code> or <code>git://</code>."
+msgstr ""
+
+msgid "The review stage shows the time from creating the merge request to merging it. The data will automatically be added after you merge your first merge request."
+msgstr ""
+
+msgid "The roadmap shows the progress of your epics along a timeline"
+msgstr ""
+
+msgid "The schedule time must be in the future!"
+msgstr ""
+
+msgid "The snippet can be accessed without any authentication."
+msgstr ""
+
+msgid "The snippet is visible only to me."
+msgstr ""
+
+msgid "The snippet is visible only to project members."
+msgstr ""
+
+msgid "The snippet is visible to any logged in user."
+msgstr ""
+
+msgid "The specified tab is invalid, please select another"
+msgstr ""
+
+msgid "The staging stage shows the time between merging the MR and deploying code to the production environment. The data will be automatically added once you deploy to production for the first time."
+msgstr ""
+
+msgid "The status of the table below only applies to the default branch and is based on the %{linkStart}latest pipeline%{linkEnd}. Once you've enabled a scan for the default branch, any subsequent feature branch you create will include the scan."
+msgstr ""
+
+msgid "The testing stage shows the time GitLab CI takes to run every pipeline for the related merge request. The data will automatically be added after your first pipeline finishes running."
+msgstr ""
+
+msgid "The time taken by each data entry gathered by that stage."
+msgstr ""
+
+msgid "The total stage shows the time it takes between creating an issue and deploying the code to production. The data will be automatically added once you have completed the full idea to production cycle."
+msgstr ""
+
+msgid "The update action will time out after %{number_of_minutes} minutes. For big repositories, use a clone/push combination."
+msgstr ""
+
+msgid "The uploaded file is not a valid Google Takeout archive."
+msgstr ""
+
+msgid "The usage ping is disabled, and cannot be configured through this form."
+msgstr ""
+
+msgid "The user is being deleted."
+msgstr ""
+
+msgid "The user map has been saved. Continue by selecting the projects you want to import."
+msgstr ""
+
+msgid "The user map is a JSON document mapping the Google Code users that participated on your projects to the way their email addresses and usernames will be imported into GitLab. You can change this by changing the value on the right hand side of <code>:</code>. Be sure to preserve the surrounding double quotes, other punctuation and the email address or username on the left hand side."
+msgstr ""
+
+msgid "The user map is a mapping of the FogBugz users that participated on your projects to the way their email address and usernames will be imported into GitLab. You can change this by populating the table below."
+msgstr ""
+
+msgid "The user you are trying to deactivate has been active in the past %{minimum_inactive_days} days and cannot be deactivated"
+msgstr ""
+
+msgid "The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6."
+msgstr ""
+
+msgid "The vulnerability is no longer detected. Verify the vulnerability has been fixed or removed before changing its status."
+msgstr ""
+
+msgid "The vulnerability is no longer detected. Verify the vulnerability has been remediated before changing its status."
+msgstr ""
+
+msgid "There are no %{replicableTypeName} to show"
+msgstr ""
+
+msgid "There are no GPG keys associated with this account."
+msgstr ""
+
+msgid "There are no GPG keys with access to your account."
+msgstr ""
+
+msgid "There are no SSH keys associated with this account."
+msgstr ""
+
+msgid "There are no SSH keys with access to your account."
+msgstr ""
+
+msgid "There are no archived projects yet"
+msgstr ""
+
+msgid "There are no archived requirements"
+msgstr ""
+
+msgid "There are no changes"
+msgstr ""
+
+msgid "There are no charts configured for this page"
+msgstr ""
+
+msgid "There are no closed issues"
+msgstr ""
+
+msgid "There are no closed merge requests"
+msgstr ""
+
+msgid "There are no custom project templates set up for this GitLab instance. They are enabled from GitLab's Admin Area. Contact your GitLab instance administrator to setup custom project templates."
+msgstr ""
+
+msgid "There are no issues to show"
+msgstr ""
+
+msgid "There are no issues to show."
+msgstr ""
+
+msgid "There are no labels yet"
+msgstr ""
+
+msgid "There are no matching files"
+msgstr ""
+
+msgid "There are no open issues"
+msgstr ""
+
+msgid "There are no open merge requests"
+msgstr ""
+
+msgid "There are no open requirements"
+msgstr ""
+
+msgid "There are no packages yet"
+msgstr ""
+
+msgid "There are no projects shared with this group yet"
+msgstr ""
+
+msgid "There are no variables yet."
+msgstr ""
+
+msgid "There is a limit of %{ci_project_subscriptions_limit} subscriptions from or to a project."
+msgstr ""
+
+msgid "There is already a repository with that name on disk"
+msgstr ""
+
+msgid "There is no data available. Please change your selection."
+msgstr ""
+
+msgid "There was a problem communicating with your device."
+msgstr ""
+
+msgid "There was a problem fetching project branches."
+msgstr ""
+
+msgid "There was a problem fetching project tags."
+msgstr ""
+
+msgid "There was a problem fetching project users."
+msgstr ""
+
+msgid "There was a problem fetching users."
+msgstr ""
+
+msgid "There was a problem refreshing the data, please try again"
+msgstr ""
+
+msgid "There was a problem saving your custom stage, please try again"
+msgstr ""
+
+msgid "There was a problem sending the confirmation email"
+msgstr ""
+
+msgid "There was an error %{message} todo."
+msgstr ""
+
+msgid "There was an error adding a To Do."
+msgstr ""
+
+msgid "There was an error creating the dashboard, branch name is invalid."
+msgstr ""
+
+msgid "There was an error creating the dashboard, branch named: %{branch} already exists."
+msgstr ""
+
+msgid "There was an error creating the issue"
+msgstr ""
+
+msgid "There was an error deleting the To Do."
+msgstr ""
+
+msgid "There was an error fetching configuration for charts"
+msgstr ""
+
+msgid "There was an error fetching data for the selected stage"
+msgstr ""
+
+msgid "There was an error fetching data for the tasks by type chart"
+msgstr ""
+
+msgid "There was an error fetching label data for the selected group"
+msgstr ""
+
+msgid "There was an error fetching median data for stages"
+msgstr ""
+
+msgid "There was an error fetching the %{replicableType}"
+msgstr ""
+
+msgid "There was an error fetching the Geo Settings"
+msgstr ""
+
+msgid "There was an error fetching the Node's Groups"
+msgstr ""
+
+msgid "There was an error fetching the environments information."
+msgstr ""
+
+msgid "There was an error fetching the top labels for the selected group"
+msgstr ""
+
+msgid "There was an error fetching the variables."
+msgstr ""
+
+msgid "There was an error fetching value stream analytics stages."
+msgstr ""
+
+msgid "There was an error gathering the chart data"
+msgstr ""
+
+msgid "There was an error getting the epic participants."
+msgstr ""
+
+msgid "There was an error importing the Jira project."
+msgstr ""
+
+msgid "There was an error loading users activity calendar."
+msgstr ""
+
+msgid "There was an error parsing the data for this graph."
+msgstr ""
+
+msgid "There was an error removing the e-mail."
+msgstr ""
+
+msgid "There was an error removing your custom stage, please try again"
+msgstr ""
+
+msgid "There was an error resetting group pipeline minutes."
+msgstr ""
+
+msgid "There was an error resetting user pipeline minutes."
+msgstr ""
+
+msgid "There was an error saving this Geo Node."
+msgstr ""
+
+msgid "There was an error saving your changes."
+msgstr ""
+
+msgid "There was an error saving your notification settings."
+msgstr ""
+
+msgid "There was an error subscribing to this label."
+msgstr ""
+
+msgid "There was an error syncing project %{name}"
+msgstr ""
+
+msgid "There was an error syncing the %{replicableType}"
+msgstr ""
+
+msgid "There was an error trying to validate your query"
+msgstr ""
+
+msgid "There was an error updating the dashboard, branch name is invalid."
+msgstr ""
+
+msgid "There was an error updating the dashboard, branch named: %{branch} already exists."
+msgstr ""
+
+msgid "There was an error updating the stage order. Please try reloading the page."
+msgstr ""
+
+msgid "There was an error when reseting email token."
+msgstr ""
+
+msgid "There was an error when subscribing to this label."
+msgstr ""
+
+msgid "There was an error when unsubscribing from this label."
+msgstr ""
+
+msgid "There was an error while fetching value stream analytics data."
+msgstr ""
+
+msgid "There was an error while fetching value stream analytics duration data."
+msgstr ""
+
+msgid "There was an error while fetching value stream analytics duration median data."
+msgstr ""
+
+msgid "There was an error while fetching value stream analytics recent activity data."
+msgstr ""
+
+msgid "There was an error while fetching value stream analytics time summary data."
+msgstr ""
+
+msgid "There was an error with the reCAPTCHA. Please solve the reCAPTCHA again."
+msgstr ""
+
+msgid "These existing issues have a similar title. It might be better to comment there instead of creating another similar issue."
+msgstr ""
+
+msgid "These variables are configured in the parent group settings, and will be active in the current project in addition to the project variables."
+msgstr ""
+
+msgid "They can be managed using the %{link}."
+msgstr ""
+
+msgid "Third Party Advisory Link"
+msgstr ""
+
+msgid "Third party offers"
+msgstr ""
+
+msgid "This %{issuableDisplayName} is locked. Only project members can comment."
+msgstr ""
+
+msgid "This %{issuable} is locked. Only <strong>project members</strong> can comment."
+msgstr ""
+
+msgid "This %{noteableTypeText} is %{confidentialLinkStart}confidential%{linkEnd} and %{lockedLinkStart}locked%{linkEnd}."
+msgstr ""
+
+msgid "This %{noteableTypeText} is locked."
+msgstr ""
+
+msgid "This %{viewer} could not be displayed because %{reason}. You can %{options} instead."
+msgstr ""
+
+msgid "This GitLab instance does not provide any shared Runners yet. Instance administrators can register shared Runners in the admin area."
+msgstr ""
+
+msgid "This GitLab instance is licensed at the %{insufficient_license} tier. Geo is only available for users who have at least a Premium license."
+msgstr ""
+
+msgid "This Project is currently archived and read-only. Please unarchive the project first if you want to resume Pull mirroring"
+msgstr ""
+
+msgid "This URL is already used for another link; duplicate URLs are not allowed"
+msgstr ""
+
+msgid "This action can lead to data loss. To prevent accidental actions we ask you to confirm your intention."
+msgstr ""
+
+msgid "This also resolves all related threads"
+msgstr ""
+
+msgid "This also resolves the discussion"
+msgstr ""
+
+msgid "This application was created by %{link_to_owner}."
+msgstr ""
+
+msgid "This application will be able to:"
+msgstr ""
+
+msgid "This attachment has been truncated to avoid exceeding the maximum allowed attachment size of 15MB. %{written_count} of %{issues_count} issues have been included. Consider re-exporting with a narrower selection of issues."
+msgstr ""
+
+msgid "This block is self-referential"
+msgstr ""
+
+msgid "This board's scope is reduced"
+msgstr ""
+
+msgid "This branch has changed since you started editing. Would you like to create a new branch?"
+msgstr ""
+
+msgid "This chart could not be displayed"
+msgstr ""
+
+msgid "This comment has changed since you started editing, please review the %{startTag}updated comment%{endTag} to ensure information is not lost."
+msgstr ""
+
+msgid "This commit is part of merge request %{link_to_merge_request}. Comments created here will be created in the context of that merge request."
+msgstr ""
+
+msgid "This commit was signed with a <strong>verified</strong> signature and the committer email is verified to belong to the same user."
+msgstr ""
+
+msgid "This commit was signed with a different user's verified signature."
+msgstr ""
+
+msgid "This commit was signed with a verified signature, but the committer email is <strong>not verified</strong> to belong to the same user."
+msgstr ""
+
+msgid "This commit was signed with an <strong>unverified</strong> signature."
+msgstr ""
+
+msgid "This content could not be displayed because %{reason}. You can %{options} instead."
+msgstr ""
+
+msgid "This date is after the due date, so this epic won't appear in the roadmap."
+msgstr ""
+
+msgid "This date is before the start date, so this epic won't appear in the roadmap."
+msgstr ""
+
+msgid "This device has already been registered with us."
+msgstr ""
+
+msgid "This device has not been registered with us."
+msgstr ""
+
+msgid "This diff is collapsed."
+msgstr ""
+
+msgid "This diff was suppressed by a .gitattributes entry."
+msgstr ""
+
+msgid "This directory"
+msgstr ""
+
+msgid "This domain is not verified. You will need to verify ownership before access is enabled."
+msgstr ""
+
+msgid "This endpoint has been requested too many times. Try again later."
+msgstr ""
+
+msgid "This environment has no deployments yet."
+msgstr ""
+
+msgid "This environment is being deployed"
+msgstr ""
+
+msgid "This environment is being re-deployed"
+msgstr ""
+
+msgid "This epic already has the maximum number of child epics."
+msgstr ""
+
+msgid "This epic and its child elements will only be visible to team members with at minimum Reporter access."
+msgstr ""
+
+msgid "This epic does not exist or you don't have sufficient permission."
+msgstr ""
+
+msgid "This feature is currently in beta. We invite you to %{linkStart}give feedback%{linkEnd}."
+msgstr ""
+
+msgid "This feature requires local storage to be enabled"
+msgstr ""
+
+msgid "This field is required."
+msgstr ""
+
+msgid "This group"
+msgstr ""
+
+msgid "This group cannot be invited to a project inside a group with enforced SSO"
+msgstr ""
+
+msgid "This group does not provide any group Runners yet."
+msgstr ""
+
+msgid "This group has been scheduled for permanent removal on %{date}"
+msgstr ""
+
+msgid "This group, including all subgroups, projects and git repositories, will only be reachable from the specified IP address range. Multiple addresses are supported with comma delimiters.<br>Example: <code>192.168.0.0/24,192.168.1.0/24</code>. %{read_more_link}."
+msgstr ""
+
+msgid "This group, its subgroups and projects has been scheduled for removal on %{date}."
+msgstr ""
+
+msgid "This group, its subgroups and projects will be removed on %{date} since its parent group '%{parent_group_name}'' has been scheduled for removal."
+msgstr ""
+
+msgid "This is a \"Ghost User\", created to hold all issues authored by users that have since been deleted. This user cannot be removed."
+msgstr ""
+
+msgid "This is a Work in Progress"
+msgstr ""
+
+msgid "This is a confidential %{noteableTypeText}."
+msgstr ""
+
+msgid "This is a delayed job to run in %{remainingTime}"
+msgstr ""
+
+msgid "This is a list of devices that have logged into your account. Revoke any sessions that you do not recognize."
+msgstr ""
+
+msgid "This is a security log of important events involving your account."
+msgstr ""
+
+msgid "This is the author's first Merge Request to this project."
+msgstr ""
+
+msgid "This is the highest peak of users on your installation since the license started."
+msgstr ""
+
+msgid "This is the maximum number of users that have existed at the same time since the license started. This is the minimum number of seats you will need to buy when you renew your license."
+msgstr ""
+
+msgid "This is the number of currently active users on your installation, and this is the minimum number you need to purchase when you renew your license."
+msgstr ""
+
+msgid "This is your current session"
+msgstr ""
+
+msgid "This issue is confidential"
+msgstr ""
+
+msgid "This issue is currently blocked by the following issues: %{issues}."
+msgstr ""
+
+msgid "This issue is in a child epic of the filtered epic"
+msgstr ""
+
+msgid "This job depends on other jobs with expired/erased artifacts: %{invalid_dependencies}"
+msgstr ""
+
+msgid "This job depends on upstream jobs that need to succeed in order for this job to be triggered"
+msgstr ""
+
+msgid "This job does not have a trace."
+msgstr ""
+
+msgid "This job has been canceled"
+msgstr ""
+
+msgid "This job has been skipped"
+msgstr ""
+
+msgid "This job has not been triggered yet"
+msgstr ""
+
+msgid "This job has not started yet"
+msgstr ""
+
+msgid "This job is an out-of-date deployment to %{environmentLink} using cluster %{clusterNameOrLink} and namespace %{kubernetesNamespace}."
+msgstr ""
+
+msgid "This job is an out-of-date deployment to %{environmentLink} using cluster %{clusterNameOrLink} and namespace %{kubernetesNamespace}. View the %{deploymentLink}."
+msgstr ""
+
+msgid "This job is an out-of-date deployment to %{environmentLink} using cluster %{clusterNameOrLink}."
+msgstr ""
+
+msgid "This job is an out-of-date deployment to %{environmentLink} using cluster %{clusterNameOrLink}. View the %{deploymentLink}."
+msgstr ""
+
+msgid "This job is an out-of-date deployment to %{environmentLink}."
+msgstr ""
+
+msgid "This job is an out-of-date deployment to %{environmentLink}. View the %{deploymentLink}."
+msgstr ""
+
+msgid "This job is archived. Only the complete pipeline can be retried."
+msgstr ""
+
+msgid "This job is creating a deployment to %{environmentLink} using cluster %{clusterNameOrLink} and namespace %{kubernetesNamespace}."
+msgstr ""
+
+msgid "This job is creating a deployment to %{environmentLink} using cluster %{clusterNameOrLink} and namespace %{kubernetesNamespace}. This will overwrite the %{deploymentLink}."
+msgstr ""
+
+msgid "This job is creating a deployment to %{environmentLink} using cluster %{clusterNameOrLink}."
+msgstr ""
+
+msgid "This job is creating a deployment to %{environmentLink} using cluster %{clusterNameOrLink}. This will overwrite the %{deploymentLink}."
+msgstr ""
+
+msgid "This job is creating a deployment to %{environmentLink}."
+msgstr ""
+
+msgid "This job is creating a deployment to %{environmentLink}. This will overwrite the %{deploymentLink}."
+msgstr ""
+
+msgid "This job is deployed to %{environmentLink} using cluster %{clusterNameOrLink} and namespace %{kubernetesNamespace}."
+msgstr ""
+
+msgid "This job is deployed to %{environmentLink} using cluster %{clusterNameOrLink}."
+msgstr ""
+
+msgid "This job is deployed to %{environmentLink}."
+msgstr ""
+
+msgid "This job is in pending state and is waiting to be picked by a runner"
+msgstr ""
+
+msgid "This job is performing tasks that must complete before it can start"
+msgstr ""
+
+msgid "This job is preparing to start"
+msgstr ""
+
+msgid "This job is stuck because you don't have any active runners online or available with any of these tags assigned to them:"
+msgstr ""
+
+msgid "This job is stuck because you don't have any active runners that can run this job."
+msgstr ""
+
+msgid "This job is waiting for resource: "
+msgstr ""
+
+msgid "This job requires a manual action"
+msgstr ""
+
+msgid "This job requires manual intervention to start. Before starting this job, you can add variables below for last-minute configuration changes."
+msgstr ""
+
+msgid "This job will automatically run after its timer finishes. Often they are used for incremental roll-out deploys to production environments. When unscheduled it converts into a manual action."
+msgstr ""
+
+msgid "This license has already expired."
+msgstr ""
+
+msgid "This link points to external content"
+msgstr ""
+
+msgid "This may expose confidential information as the selected fork is in another namespace that can have other members."
+msgstr ""
+
+msgid "This means you can not push code until you create an empty repository or import existing one."
+msgstr ""
+
+msgid "This merge request does not have accessibility reports"
+msgstr ""
+
+msgid "This merge request is locked."
+msgstr ""
+
+msgid "This namespace has already been taken! Please choose another one."
+msgstr ""
+
+msgid "This only applies to repository indexing operations."
+msgstr ""
+
+msgid "This option is only available on GitLab.com"
+msgstr ""
+
+msgid "This page is unavailable because you are not allowed to read information across multiple projects."
+msgstr ""
+
+msgid "This page will be removed in a future release."
+msgstr ""
+
+msgid "This pipeline does not use the %{codeStart}needs%{codeEnd} keyword and can't be represented as a directed acyclic graph."
+msgstr ""
+
+msgid "This pipeline makes use of a predefined CI/CD configuration enabled by %{strongStart}Auto DevOps.%{strongEnd}"
+msgstr ""
+
+msgid "This pipeline makes use of a predefined CI/CD configuration enabled by <b>Auto DevOps.</b>"
+msgstr ""
+
+msgid "This pipeline triggered a child pipeline"
+msgstr ""
+
+msgid "This pipeline was triggered by a parent pipeline"
+msgstr ""
+
+msgid "This project"
+msgstr ""
+
+msgid "This project does not belong to a group and can therefore not make use of group Runners."
+msgstr ""
+
+msgid "This project does not have %{service_desk_link_start}Service Desk%{service_desk_link_end} enabled, so the user who created the issue will no longer receive email notifications about new activity."
+msgstr ""
+
+msgid "This project does not have a wiki homepage yet"
+msgstr ""
+
+msgid "This project does not have billing enabled. To create a cluster, <a href=%{linkToBilling} target=\"_blank\" rel=\"noopener noreferrer\">enable billing <i class=\"fa fa-external-link\" aria-hidden=\"true\"></i></a> and try again."
+msgstr ""
+
+msgid "This project has no active access tokens."
+msgstr ""
+
+msgid "This project is archived and cannot be commented on."
+msgstr ""
+
+msgid "This project path either does not exist or you do not have access."
+msgstr ""
+
+msgid "This project will be removed on %{date}"
+msgstr ""
+
+msgid "This project will be removed on %{date} since its parent group '%{parent_group_name}' has been scheduled for removal."
+msgstr ""
+
+msgid "This project will live in your group <strong>%{namespace}</strong>. A project is where you house your files (repository), plan your work (issues), publish your documentation (wiki), and so much more."
+msgstr ""
+
+msgid "This repository"
+msgstr ""
+
+msgid "This repository has never been checked."
+msgstr ""
+
+msgid "This repository is currently empty. A new Auto DevOps pipeline will be created after a new file has been pushed to a branch."
+msgstr ""
+
+msgid "This repository was last checked %{last_check_timestamp}. The check %{strong_start}failed.%{strong_end} See the 'repocheck.log' file for error messages."
+msgstr ""
+
+msgid "This repository was last checked %{last_check_timestamp}. The check passed."
+msgstr ""
+
+msgid "This runner will only run on pipelines triggered on protected branches"
+msgstr ""
+
+msgid "This setting can be overridden in each project."
+msgstr ""
+
+msgid "This setting will update the hostname that is used to generate private commit emails. %{learn_more}"
+msgstr ""
+
+msgid "This subscription is for"
+msgstr ""
+
+msgid "This timeout will take precedence when lower than project-defined timeout and accepts a human readable time input language like \"1 hour\". Values without specification represent seconds."
+msgstr ""
+
+msgid "This user cannot be unlocked manually from GitLab"
+msgstr ""
+
+msgid "This user has no active %{type}."
+msgstr ""
+
+msgid "This user has no identities"
+msgstr ""
+
+msgid "This user will be the author of all events in the activity feed that are the result of an update, like new branches being created or new commits being pushed to existing branches."
+msgstr ""
+
+msgid "This variable can not be masked."
+msgstr ""
+
+msgid "This variable does not match the expected pattern."
+msgstr ""
+
+msgid "This will help us personalize your onboarding experience."
+msgstr ""
+
+msgid "This will redirect you to an external sign in page."
+msgstr ""
+
+msgid "This will remove the fork relationship between this project and %{fork_source}."
+msgstr ""
+
+msgid "This will remove the fork relationship between this project and other projects in the fork network."
+msgstr ""
+
+msgid "Those emails automatically become issues (with the comments becoming the email conversation) listed here."
+msgstr ""
+
+msgid "Thread to reply to cannot be found"
+msgstr ""
+
+msgid "Threat Monitoring"
+msgstr ""
+
+msgid "ThreatMonitoring|Anomalous Requests"
+msgstr ""
+
+msgid "ThreatMonitoring|Application firewall not detected"
+msgstr ""
+
+msgid "ThreatMonitoring|Container Network Policies are not installed or have been disabled. To view this data, ensure your Network Policies are installed and enabled for your cluster."
+msgstr ""
+
+msgid "ThreatMonitoring|Container Network Policy"
+msgstr ""
+
+msgid "ThreatMonitoring|Container NetworkPolicies not detected"
+msgstr ""
+
+msgid "ThreatMonitoring|Dropped Packets"
+msgstr ""
+
+msgid "ThreatMonitoring|Environment"
+msgstr ""
+
+msgid "ThreatMonitoring|No environments detected"
+msgstr ""
+
+msgid "ThreatMonitoring|Operations Per Second"
+msgstr ""
+
+msgid "ThreatMonitoring|Overview"
+msgstr ""
+
+msgid "ThreatMonitoring|Packet Activity"
+msgstr ""
+
+msgid "ThreatMonitoring|Policies"
+msgstr ""
+
+msgid "ThreatMonitoring|Requests"
+msgstr ""
+
+msgid "ThreatMonitoring|Show last"
+msgstr ""
+
+msgid "ThreatMonitoring|Something went wrong, unable to fetch environments"
+msgstr ""
+
+msgid "ThreatMonitoring|Something went wrong, unable to fetch statistics"
+msgstr ""
+
+msgid "ThreatMonitoring|The firewall is not installed or has been disabled. To view this data, ensure the web application firewall is installed and enabled for your cluster."
+msgstr ""
+
+msgid "ThreatMonitoring|The graph below is an overview of traffic coming to your application as tracked by the Web Application Firewall (WAF). View the docs for instructions on how to access the WAF logs to see what type of malicious traffic is trying to access your app. The docs link is also accessible by clicking the \"?\" icon next to the title below."
+msgstr ""
+
+msgid "ThreatMonitoring|Threat Monitoring"
+msgstr ""
+
+msgid "ThreatMonitoring|Threat Monitoring help page link"
+msgstr ""
+
+msgid "ThreatMonitoring|Time"
+msgstr ""
+
+msgid "ThreatMonitoring|To view this data, ensure you have configured an environment for this project and that at least one threat monitoring feature is enabled."
+msgstr ""
+
+msgid "ThreatMonitoring|Total Packets"
+msgstr ""
+
+msgid "ThreatMonitoring|Total Requests"
+msgstr ""
+
+msgid "ThreatMonitoring|View documentation"
+msgstr ""
+
+msgid "ThreatMonitoring|Web Application Firewall"
+msgstr ""
+
+msgid "ThreatMonitoring|While it's rare to have no traffic coming to your application, it can happen. In any event, we ask that you double check your settings to make sure you've set up the WAF correctly."
+msgstr ""
+
+msgid "Thursday"
+msgstr ""
+
+msgid "Time"
+msgstr ""
+
+msgid "Time based: Yes"
+msgstr ""
+
+msgid "Time before an issue gets scheduled"
+msgstr ""
+
+msgid "Time before an issue starts implementation"
+msgstr ""
+
+msgid "Time before enforced"
+msgstr ""
+
+msgid "Time between merge request creation and merge/close"
+msgstr ""
+
+msgid "Time estimate"
+msgstr ""
+
+msgid "Time from first comment to last commit"
+msgstr ""
+
+msgid "Time from first commit until first comment"
+msgstr ""
+
+msgid "Time from last commit to merge"
+msgstr ""
+
+msgid "Time in seconds"
+msgstr ""
+
+msgid "Time in seconds GitLab will wait for a response from the external service. When the service does not respond in time, access will be denied."
+msgstr ""
+
+msgid "Time of import: %{importTime}"
+msgstr ""
+
+msgid "Time remaining"
+msgstr ""
+
+msgid "Time spent"
+msgstr ""
+
+msgid "Time to merge"
+msgstr ""
+
+msgid "Time to subtract exceeds the total time spent"
+msgstr ""
+
+msgid "Time tracking"
+msgstr ""
+
+msgid "Time until first merge request"
+msgstr ""
+
+msgid "TimeTrackingEstimated|Est"
+msgstr ""
+
+msgid "TimeTracking|%{startTag}Spent: %{endTag}%{timeSpentHumanReadable}"
+msgstr ""
+
+msgid "TimeTracking|Estimated:"
+msgstr ""
+
+msgid "TimeTracking|Over by %{timeRemainingHumanReadable}"
+msgstr ""
+
+msgid "TimeTracking|Spent"
+msgstr ""
+
+msgid "TimeTracking|Time remaining: %{timeRemainingHumanReadable}"
+msgstr ""
+
+msgid "Timeago|%s days ago"
+msgstr ""
+
+msgid "Timeago|%s days remaining"
+msgstr ""
+
+msgid "Timeago|%s hours ago"
+msgstr ""
+
+msgid "Timeago|%s hours remaining"
+msgstr ""
+
+msgid "Timeago|%s minutes ago"
+msgstr ""
+
+msgid "Timeago|%s minutes remaining"
+msgstr ""
+
+msgid "Timeago|%s months ago"
+msgstr ""
+
+msgid "Timeago|%s months remaining"
+msgstr ""
+
+msgid "Timeago|%s seconds remaining"
+msgstr ""
+
+msgid "Timeago|%s weeks ago"
+msgstr ""
+
+msgid "Timeago|%s weeks remaining"
+msgstr ""
+
+msgid "Timeago|%s years ago"
+msgstr ""
+
+msgid "Timeago|%s years remaining"
+msgstr ""
+
+msgid "Timeago|1 day ago"
+msgstr ""
+
+msgid "Timeago|1 day remaining"
+msgstr ""
+
+msgid "Timeago|1 hour ago"
+msgstr ""
+
+msgid "Timeago|1 hour remaining"
+msgstr ""
+
+msgid "Timeago|1 minute ago"
+msgstr ""
+
+msgid "Timeago|1 minute remaining"
+msgstr ""
+
+msgid "Timeago|1 month ago"
+msgstr ""
+
+msgid "Timeago|1 month remaining"
+msgstr ""
+
+msgid "Timeago|1 week ago"
+msgstr ""
+
+msgid "Timeago|1 week remaining"
+msgstr ""
+
+msgid "Timeago|1 year ago"
+msgstr ""
+
+msgid "Timeago|1 year remaining"
+msgstr ""
+
+msgid "Timeago|Past due"
+msgstr ""
+
+msgid "Timeago|in %s days"
+msgstr ""
+
+msgid "Timeago|in %s hours"
+msgstr ""
+
+msgid "Timeago|in %s minutes"
+msgstr ""
+
+msgid "Timeago|in %s months"
+msgstr ""
+
+msgid "Timeago|in %s seconds"
+msgstr ""
+
+msgid "Timeago|in %s weeks"
+msgstr ""
+
+msgid "Timeago|in %s years"
+msgstr ""
+
+msgid "Timeago|in 1 day"
+msgstr ""
+
+msgid "Timeago|in 1 hour"
+msgstr ""
+
+msgid "Timeago|in 1 minute"
+msgstr ""
+
+msgid "Timeago|in 1 month"
+msgstr ""
+
+msgid "Timeago|in 1 week"
+msgstr ""
+
+msgid "Timeago|in 1 year"
+msgstr ""
+
+msgid "Timeago|just now"
+msgstr ""
+
+msgid "Timeago|right now"
+msgstr ""
+
+msgid "Timeout"
+msgstr ""
+
+msgid "Timeout connecting to the Google API. Please try again."
+msgstr ""
+
+msgid "Time|hr"
+msgid_plural "Time|hrs"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Time|min"
+msgid_plural "Time|mins"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Time|s"
+msgstr ""
+
+msgid "Tip:"
+msgstr ""
+
+msgid "Title"
+msgstr ""
+
+msgid "Title:"
+msgstr ""
+
+msgid "Titles and Descriptions"
+msgstr ""
+
+msgid "To"
+msgstr ""
+
+msgid "To %{link_to_help} of your domain, add the above key to a TXT record within to your DNS configuration."
+msgstr ""
+
+msgid "To Do"
+msgstr ""
+
+msgid "To GitLab"
+msgstr ""
+
+msgid "To access this domain create a new DNS record"
+msgstr ""
+
+msgid "To add an SSH key you need to %{generate_link_start}generate one%{link_end} or use an %{existing_link_start}existing key%{link_end}."
+msgstr ""
+
+msgid "To add the entry manually, provide the following details to the application on your phone."
+msgstr ""
+
+msgid "To connect GitHub repositories, you can use a %{personal_access_token_link}. When you create your Personal Access Token, you will need to select the <code>repo</code> scope, so we can display a list of your public and private repositories which are available to connect."
+msgstr ""
+
+msgid "To connect GitHub repositories, you first need to authorize GitLab to access the list of your GitHub repositories."
+msgstr ""
+
+msgid "To connect GitHub repositories, you first need to authorize GitLab to access the list of your GitHub repositories:"
+msgstr ""
+
+msgid "To connect an SVN repository, check out %{svn_link}."
+msgstr ""
+
+msgid "To define internal users, first enable new users set to external"
+msgstr ""
+
+msgid "To enable it and see User Cohorts, visit %{application_settings_link_start}application settings%{application_settings_link_end}."
+msgstr ""
+
+msgid "To further protect your account, consider configuring a %{mfa_link_start}two-factor authentication%{mfa_link_end} method."
+msgstr ""
+
+msgid "To further protect your account, consider configuring a two-factor authentication method: %{mfa_link}."
+msgstr ""
+
+msgid "To get started you enter your FogBugz URL and login information below. In the next steps, you'll be able to map users and select the projects you want to import."
+msgstr ""
+
+msgid "To get started, link this page to your Jaeger server, or find out how to %{link_start_tag}install Jaeger%{link_end_tag}"
+msgstr ""
+
+msgid "To get started, please enter your Gitea Host URL and a %{link_to_personal_token}."
+msgstr ""
+
+msgid "To help improve GitLab and its user experience, GitLab will periodically collect usage information."
+msgstr ""
+
+msgid "To help improve GitLab, we would like to periodically collect usage information. This can be changed at any time in %{settings_link_start}Settings%{link_end}. %{info_link_start}More Information%{link_end}"
+msgstr ""
+
+msgid "To import an SVN repository, check out %{svn_link}."
+msgstr ""
+
+msgid "To keep this project going, create a new issue"
+msgstr ""
+
+msgid "To keep this project going, create a new merge request"
+msgstr ""
+
+msgid "To link Sentry to GitLab, enter your Sentry URL and Auth Token."
+msgstr ""
+
+msgid "To move or copy an entire GitLab project from another GitLab installation to this one, navigate to the original project's settings page, generate an export file, and upload it here."
+msgstr ""
+
+msgid "To only use CI/CD features for an external repository, choose <strong>CI/CD for external repo</strong>."
+msgstr ""
+
+msgid "To open Jaeger and easily view tracing from GitLab, link the %{link} page to your server"
+msgstr ""
+
+msgid "To preserve performance only <strong>%{display_size} of %{real_size}</strong> files are displayed."
+msgstr ""
+
+msgid "To protect this issue's confidentiality, %{forkLink} and set the fork's visibility to private."
+msgstr ""
+
+msgid "To protect this issue's confidentiality, a private fork of this project was selected."
+msgstr ""
+
+msgid "To receive alerts from manually configured Prometheus services, add the following URL and Authorization key to your Prometheus webhook config file. Learn more about %{linkStart}configuring Prometheus%{linkEnd} to send alerts to GitLab."
+msgstr ""
+
+msgid "To see all the user's personal access tokens you must impersonate them first."
+msgstr ""
+
+msgid "To see this project's operational details, %{linkStart}upgrade its group plan to Silver%{linkEnd}. You can also remove the project from the dashboard."
+msgstr ""
+
+msgid "To see this project's operational details, contact an owner of group %{groupName} to upgrade the plan. You can also remove the project from the dashboard."
+msgstr ""
+
+msgid "To set up SAML authentication for your group through an identity provider like Azure, Okta, Onelogin, Ping Identity, or your custom SAML 2.0 provider:"
+msgstr ""
+
+msgid "To set up this service:"
+msgstr ""
+
+msgid "To simplify the billing process, GitLab will collect user counts in order to prorate charges for user growth throughout the year using a quarterly reconciliation process."
+msgstr ""
+
+msgid "To specify the notification level per project of a group you belong to, you need to visit project page and change notification level there."
+msgstr ""
+
+msgid "To start serving your jobs you can add Runners to your group"
+msgstr ""
+
+msgid "To start serving your jobs you can either add specific Runners to your project or use shared Runners"
+msgstr ""
+
+msgid "To this GitLab instance"
+msgstr ""
+
+msgid "To view the roadmap, add a start or due date to one of your epics in this group or its subgroups. In the months view, only epics in the past month, current month, and next 5 months are shown."
+msgstr ""
+
+msgid "To widen your search, change or remove filters above"
+msgstr ""
+
+msgid "To widen your search, change or remove filters."
+msgstr ""
+
+msgid "To-Do List"
+msgstr ""
+
+msgid "To-do item successfully marked as done."
+msgstr ""
+
+msgid "Today"
+msgstr ""
+
+msgid "Toggle Markdown preview"
+msgstr ""
+
+msgid "Toggle Sidebar"
+msgstr ""
+
+msgid "Toggle all threads"
+msgstr ""
+
+msgid "Toggle backtrace"
+msgstr ""
+
+msgid "Toggle collapse"
+msgstr ""
+
+msgid "Toggle comments for this file"
+msgstr ""
+
+msgid "Toggle commit description"
+msgstr ""
+
+msgid "Toggle commit list"
+msgstr ""
+
+msgid "Toggle dropdown"
+msgstr ""
+
+msgid "Toggle emoji award"
+msgstr ""
+
+msgid "Toggle navigation"
+msgstr ""
+
+msgid "Toggle project"
+msgstr ""
+
+msgid "Toggle sidebar"
+msgstr ""
+
+msgid "Toggle the Performance Bar"
+msgstr ""
+
+msgid "Toggle this dialog"
+msgstr ""
+
+msgid "Toggle thread"
+msgstr ""
+
+msgid "ToggleButton|Toggle Status: OFF"
+msgstr ""
+
+msgid "ToggleButton|Toggle Status: ON"
+msgstr ""
+
+msgid "Toggled :%{name}: emoji award."
+msgstr ""
+
+msgid "Toggles :%{name}: emoji award."
+msgstr ""
+
+msgid "Tomorrow"
+msgstr ""
+
+msgid "Too many changes to show."
+msgstr ""
+
+msgid "Too many namespaces enabled. You will need to manage them via the console or the API."
+msgstr ""
+
+msgid "Too many projects enabled. You will need to manage them via the console or the API."
+msgstr ""
+
+msgid "Topics (optional)"
+msgstr ""
+
+msgid "Total"
+msgstr ""
+
+msgid "Total Contributions"
+msgstr ""
+
+msgid "Total artifacts size: %{total_size}"
+msgstr ""
+
+msgid "Total cores (CPUs)"
+msgstr ""
+
+msgid "Total issues"
+msgstr ""
+
+msgid "Total memory (GB)"
+msgstr ""
+
+msgid "Total test time for all commits/merges"
+msgstr ""
+
+msgid "Total weight"
+msgstr ""
+
+msgid "Total: %{total}"
+msgstr ""
+
+msgid "Trace"
+msgstr ""
+
+msgid "Tracing"
+msgstr ""
+
+msgid "Track groups of issues that share a theme, across projects and milestones"
+msgstr ""
+
+msgid "Track time with quick actions"
+msgstr ""
+
+msgid "Track your GitLab projects with GitLab for Slack."
+msgstr ""
+
+msgid "Track your project with Audit Events."
+msgstr ""
+
+msgid "Transfer ownership"
+msgstr ""
+
+msgid "Transfer project"
+msgstr ""
+
+msgid "TransferGroup|Cannot transfer group to one of its subgroup."
+msgstr ""
+
+msgid "TransferGroup|Cannot update the path because there are projects under this group that contain Docker images in their Container Registry. Please remove the images from your projects first and try again."
+msgstr ""
+
+msgid "TransferGroup|Database is not supported."
+msgstr ""
+
+msgid "TransferGroup|Group contains projects with NPM packages."
+msgstr ""
+
+msgid "TransferGroup|Group is already a root group."
+msgstr ""
+
+msgid "TransferGroup|Group is already associated to the parent group."
+msgstr ""
+
+msgid "TransferGroup|The parent group already has a subgroup with the same path."
+msgstr ""
+
+msgid "TransferGroup|Transfer failed: %{error_message}"
+msgstr ""
+
+msgid "TransferGroup|You don't have enough permissions."
+msgstr ""
+
+msgid "TransferProject|Cannot move project"
+msgstr ""
+
+msgid "TransferProject|Please select a new namespace for your project."
+msgstr ""
+
+msgid "TransferProject|Project cannot be transferred, because tags are present in its container registry"
+msgstr ""
+
+msgid "TransferProject|Project with same name or path in target namespace already exists"
+msgstr ""
+
+msgid "TransferProject|Root namespace can't be updated if project has NPM packages"
+msgstr ""
+
+msgid "TransferProject|Transfer failed, please contact an admin."
+msgstr ""
+
+msgid "Tree view"
+msgstr ""
+
+msgid "Trending"
+msgstr ""
+
+msgid "Trials|Go back to GitLab"
+msgstr ""
+
+msgid "Trials|Skip Trial (Continue with Free Account)"
+msgstr ""
+
+msgid "Trials|You can always resume this process by selecting your avatar and choosing 'Start a Gold trial'"
+msgstr ""
+
+msgid "Trials|You won't get a free trial right now but you can always resume this process by clicking on your avatar and choosing 'Start a free trial'"
+msgstr ""
+
+msgid "Trigger"
+msgstr ""
+
+msgid "Trigger pipelines for mirror updates"
+msgstr ""
+
+msgid "Trigger pipelines when branches or tags are updated from the upstream repository. Depending on the activity of the upstream repository, this may greatly increase the load on your CI runners. Only enable this if you know they can handle the load."
+msgstr ""
+
+msgid "Trigger removed."
+msgstr ""
+
+msgid "Trigger this manual action"
+msgstr ""
+
+msgid "Trigger token:"
+msgstr ""
+
+msgid "Trigger variables:"
+msgstr ""
+
+msgid "Trigger was created successfully."
+msgstr ""
+
+msgid "Trigger was successfully updated."
+msgstr ""
+
+msgid "Triggerer"
+msgstr ""
+
+msgid "Triggers can force a specific branch or tag to get rebuilt with an API call. These tokens will impersonate their associated user including their access to projects and their project permissions."
+msgstr ""
+
+msgid "Troubleshoot and monitor your application with tracing"
+msgstr ""
+
+msgid "Try again"
+msgstr ""
+
+msgid "Try again?"
+msgstr ""
+
+msgid "Try all GitLab has to offer for 30 days."
+msgstr ""
+
+msgid "Try changing or removing filters."
+msgstr ""
+
+msgid "Try to fork again"
+msgstr ""
+
+msgid "Try to keep the first line under 52 characters and the others under 72."
+msgstr ""
+
+msgid "Try using a different search term to find the file you are looking for."
+msgstr ""
+
+msgid "Trying to communicate with your device. Plug it in (if you haven't already) and press the button on the device now."
+msgstr ""
+
+msgid "Tuesday"
+msgstr ""
+
+msgid "Turn Off"
+msgstr ""
+
+msgid "Turn On"
+msgstr ""
+
+msgid "Turn on Service Desk"
+msgstr ""
+
+msgid "Twitter"
+msgstr ""
+
+msgid "Two-Factor Authentication"
+msgstr ""
+
+msgid "Two-Factor Authentication code"
+msgstr ""
+
+msgid "Two-factor Authentication"
+msgstr ""
+
+msgid "Two-factor Authentication Recovery codes"
+msgstr ""
+
+msgid "Two-factor Authentication has been disabled for this user"
+msgstr ""
+
+msgid "Two-factor authentication"
+msgstr ""
+
+msgid "Type"
+msgstr ""
+
+msgid "Type/State"
+msgstr ""
+
+msgid "U2F Devices (%{length})"
+msgstr ""
+
+msgid "U2F only works with HTTPS-enabled websites. Contact your administrator for more details."
+msgstr ""
+
+msgid "UI Development Kit"
+msgstr ""
+
+msgid "URL"
+msgstr ""
+
+msgid "URL is required"
+msgstr ""
+
+msgid "URL must start with %{codeStart}http://%{codeEnd}, %{codeStart}https://%{codeEnd}, or %{codeStart}ftp://%{codeEnd}"
+msgstr ""
+
+msgid "URL of the external Spam Check endpoint"
+msgstr ""
+
+msgid "URL of the external storage that will serve the repository static objects (e.g. archives, blobs, ...)."
+msgstr ""
+
+msgid "URL or request ID"
+msgstr ""
+
+msgid "UTC"
+msgstr ""
+
+msgid "Unable to apply suggestions to a deleted line."
+msgstr ""
+
+msgid "Unable to build Slack link."
+msgstr ""
+
+msgid "Unable to collect CPU info"
+msgstr ""
+
+msgid "Unable to collect memory info"
+msgstr ""
+
+msgid "Unable to connect to Elasticsearch"
+msgstr ""
+
+msgid "Unable to connect to Prometheus server"
+msgstr ""
+
+msgid "Unable to connect to server: %{error}"
+msgstr ""
+
+msgid "Unable to connect to the Jira instance. Please check your Jira integration configuration."
+msgstr ""
+
+msgid "Unable to convert Kubernetes logs encoding to UTF-8"
+msgstr ""
+
+msgid "Unable to fetch unscanned projects"
+msgstr ""
+
+msgid "Unable to fetch vulnerable projects"
+msgstr ""
+
+msgid "Unable to find Jira project to import data from."
+msgstr ""
+
+msgid "Unable to generate new instance ID"
+msgstr ""
+
+msgid "Unable to load file contents. Try again later."
+msgstr ""
+
+msgid "Unable to load the diff"
+msgstr ""
+
+msgid "Unable to load the diff. %{button_try_again}"
+msgstr ""
+
+msgid "Unable to load the merge request widget. Try reloading the page."
+msgstr ""
+
+msgid "Unable to resolve"
+msgstr ""
+
+msgid "Unable to save iteration. Please try again"
+msgstr ""
+
+msgid "Unable to save your changes. Please try again."
+msgstr ""
+
+msgid "Unable to schedule a pipeline to run immediately"
+msgstr ""
+
+msgid "Unable to sign you in to the group with SAML due to \"%{reason}\""
+msgstr ""
+
+msgid "Unable to suggest a path. Please refresh and try again."
+msgstr ""
+
+msgid "Unable to update label prioritization at this time"
+msgstr ""
+
+msgid "Unable to update this epic at this time."
+msgstr ""
+
+msgid "Unable to update this issue at this time."
+msgstr ""
+
+msgid "Unarchive project"
+msgstr ""
+
+msgid "Unarchiving the project will restore people's ability to make changes to it. The repository can be committed to, and issues, comments, and other entities can be created. %{strong_start}Once active, this project shows up in the search and on the dashboard.%{strong_end}"
+msgstr ""
+
+msgid "Unassign from commenting user"
+msgstr ""
+
+msgid "Unblock"
+msgstr ""
+
+msgid "Undo"
+msgstr ""
+
+msgid "Undo Ignore"
+msgstr ""
+
+msgid "Undo ignore"
+msgstr ""
+
+msgid "Unfortunately, your email message to GitLab could not be processed."
+msgstr ""
+
+msgid "Uninstall"
+msgstr ""
+
+msgid "Uninstalling"
+msgstr ""
+
+msgid "Units|ms"
+msgstr ""
+
+msgid "Units|s"
+msgstr ""
+
+msgid "Unknown"
+msgstr ""
+
+msgid "Unknown Error"
+msgstr ""
+
+msgid "Unknown cache key"
+msgstr ""
+
+msgid "Unknown encryption strategy: %{encrypted_strategy}!"
+msgstr ""
+
+msgid "Unknown format"
+msgstr ""
+
+msgid "Unknown response text"
+msgstr ""
+
+msgid "Unlimited"
+msgstr ""
+
+msgid "Unlock"
+msgstr ""
+
+msgid "Unlock the discussion"
+msgstr ""
+
+msgid "Unlock this %{issuableDisplayName}? <strong>Everyone</strong> will be able to comment."
+msgstr ""
+
+msgid "Unlocked"
+msgstr ""
+
+msgid "Unlocked the discussion."
+msgstr ""
+
+msgid "Unlocks the discussion."
+msgstr ""
+
+msgid "Unmarked this %{noun} as Work In Progress."
+msgstr ""
+
+msgid "Unmarks this %{noun} as Work In Progress."
+msgstr ""
+
+msgid "Unreachable"
+msgstr ""
+
+msgid "Unrecognized cluster type"
+msgstr ""
+
+msgid "Unresolve"
+msgstr ""
+
+msgid "Unresolve thread"
+msgstr ""
+
+msgid "Unresolved"
+msgstr ""
+
+msgid "UnscannedProjects|15 or more days"
+msgstr ""
+
+msgid "UnscannedProjects|30 or more days"
+msgstr ""
+
+msgid "UnscannedProjects|5 or more days"
+msgstr ""
+
+msgid "UnscannedProjects|60 or more days"
+msgstr ""
+
+msgid "UnscannedProjects|Default branch scanning by project"
+msgstr ""
+
+msgid "UnscannedProjects|Out of date"
+msgstr ""
+
+msgid "UnscannedProjects|Project scanning"
+msgstr ""
+
+msgid "UnscannedProjects|Untested"
+msgstr ""
+
+msgid "UnscannedProjects|Your projects are up do date! Nice job!"
+msgstr ""
+
+msgid "Unschedule job"
+msgstr ""
+
+msgid "Unstar"
+msgstr ""
+
+msgid "Unsubscribe"
+msgstr ""
+
+msgid "Unsubscribe at group level"
+msgstr ""
+
+msgid "Unsubscribe at project level"
+msgstr ""
+
+msgid "Unsubscribe from %{type}"
+msgstr ""
+
+msgid "Unsubscribed from this %{quick_action_target}."
+msgstr ""
+
+msgid "Unsubscribes from this %{quick_action_target}."
+msgstr ""
+
+msgid "Unsupported todo type passed. Supported todo types are: %{todo_types}"
+msgstr ""
+
+msgid "Until"
+msgstr ""
+
+msgid "Until that time, the project can be restored."
+msgstr ""
+
+msgid "Unverified"
+msgstr ""
+
+msgid "Up to date"
+msgstr ""
+
+msgid "Upcoming"
+msgstr ""
+
+msgid "Upcoming Release"
+msgstr ""
+
+msgid "Update"
+msgstr ""
+
+msgid "Update all"
+msgstr ""
+
+msgid "Update approval rule"
+msgstr ""
+
+msgid "Update approvers"
+msgstr ""
+
+msgid "Update failed"
+msgstr ""
+
+msgid "Update failed. Please try again."
+msgstr ""
+
+msgid "Update it"
+msgstr ""
+
+msgid "Update iteration"
+msgstr ""
+
+msgid "Update now"
+msgstr ""
+
+msgid "Update variable"
+msgstr ""
+
+msgid "Update your bookmarked URLs as filtered/sorted branches URL has been changed."
+msgstr ""
+
+msgid "Update your group name, description, avatar, and visibility."
+msgstr ""
+
+msgid "Update your project name, topics, description and avatar."
+msgstr ""
+
+msgid "UpdateProject|Cannot rename project because it contains container registry tags!"
+msgstr ""
+
+msgid "UpdateProject|Could not set the default branch"
+msgstr ""
+
+msgid "UpdateProject|New visibility level not allowed!"
+msgstr ""
+
+msgid "UpdateProject|Project could not be updated!"
+msgstr ""
+
+msgid "UpdateRepositoryStorage|Error moving repository storage for %{project_full_path} - %{message}"
+msgstr ""
+
+msgid "UpdateRepositoryStorage|Failed to verify %{type} repository checksum from %{old} to %{new}"
+msgstr ""
+
+msgid "UpdateRepositoryStorage|Timeout waiting for %{type} repository pushes"
+msgstr ""
+
+msgid "Updated"
+msgstr ""
+
+msgid "Updated %{updated_at} by %{updated_by}"
+msgstr ""
+
+msgid "Updated at"
+msgstr ""
+
+msgid "Updated to %{linkStart}chart v%{linkEnd}"
+msgstr ""
+
+msgid "Updating"
+msgstr ""
+
+msgid "Upgrade plan to unlock Canary Deployments feature"
+msgstr ""
+
+msgid "Upgrade your plan to activate Advanced Global Search."
+msgstr ""
+
+msgid "Upgrade your plan to activate Audit Events."
+msgstr ""
+
+msgid "Upgrade your plan to activate Group Webhooks."
+msgstr ""
+
+msgid "Upgrade your plan to improve Issue boards."
+msgstr ""
+
+msgid "Upgrade your plan to improve Merge Requests."
+msgstr ""
+
+msgid "Upload <code>GoogleCodeProjectHosting.json</code> here:"
+msgstr ""
+
+msgid "Upload CSV file"
+msgstr ""
+
+msgid "Upload License"
+msgstr ""
+
+msgid "Upload New File"
+msgstr ""
+
+msgid "Upload New License"
+msgstr ""
+
+msgid "Upload a certificate for your domain with all intermediates"
+msgstr ""
+
+msgid "Upload a private key for your certificate"
+msgstr ""
+
+msgid "Upload file"
+msgstr ""
+
+msgid "Upload object map"
+msgstr ""
+
+msgid "UploadLink|click to upload"
+msgstr ""
+
+msgid "Uploaded on"
+msgstr ""
+
+msgid "Uploaded:"
+msgstr ""
+
+msgid "Uploading changes to terminal"
+msgstr ""
+
+msgid "Uploads"
+msgstr ""
+
+msgid "Upon performing this action, the contents of this group, its subgroup and projects will be permanently removed after %{deletion_adjourned_period} days on <strong>%{date}</strong>. Until that time:"
+msgstr ""
+
+msgid "Upstream"
+msgstr ""
+
+msgid "Uptime"
+msgstr ""
+
+msgid "Upvotes"
+msgstr ""
+
+msgid "Usage"
+msgstr ""
+
+msgid "Usage ping is not enabled"
+msgstr ""
+
+msgid "Usage statistics"
+msgstr ""
+
+msgid "UsageQuota|%{help_link_start}Shared runners%{help_link_end} are disabled, so there are no limits set on pipeline usage"
+msgstr ""
+
+msgid "UsageQuota|Artifacts"
+msgstr ""
+
+msgid "UsageQuota|Build Artifacts"
+msgstr ""
+
+msgid "UsageQuota|Buy additional minutes"
+msgstr ""
+
+msgid "UsageQuota|Current period usage"
+msgstr ""
+
+msgid "UsageQuota|LFS Objects"
+msgstr ""
+
+msgid "UsageQuota|LFS Storage"
+msgstr ""
+
+msgid "UsageQuota|Packages"
+msgstr ""
+
+msgid "UsageQuota|Pipelines"
+msgstr ""
+
+msgid "UsageQuota|Purchase more storage"
+msgstr ""
+
+msgid "UsageQuota|Repositories"
+msgstr ""
+
+msgid "UsageQuota|Repository"
+msgstr ""
+
+msgid "UsageQuota|Snippets"
+msgstr ""
+
+msgid "UsageQuota|Storage"
+msgstr ""
+
+msgid "UsageQuota|This namespace has no projects which use shared runners"
+msgstr ""
+
+msgid "UsageQuota|Unlimited"
+msgstr ""
+
+msgid "UsageQuota|Usage"
+msgstr ""
+
+msgid "UsageQuota|Usage Quotas"
+msgstr ""
+
+msgid "UsageQuota|Usage of group resources across the projects in the %{strong_start}%{group_name}%{strong_end} group"
+msgstr ""
+
+msgid "UsageQuota|Usage of resources across your projects"
+msgstr ""
+
+msgid "UsageQuota|Usage quotas help link"
+msgstr ""
+
+msgid "UsageQuota|Usage since"
+msgstr ""
+
+msgid "UsageQuota|Wiki"
+msgstr ""
+
+msgid "UsageQuota|Wikis"
+msgstr ""
+
+msgid "UsageQuota|You used: %{usage} %{limit}"
+msgstr ""
+
+msgid "UsageQuota|out of %{formattedLimit} of your namespace storage"
+msgstr ""
+
+msgid "Use %{code_start}::%{code_end} to create a %{link_start}scoped label set%{link_end} (eg. %{code_start}priority::1%{code_end})"
+msgstr ""
+
+msgid "Use <code>%{native_redirect_uri}</code> for local tests"
+msgstr ""
+
+msgid "Use Service Desk to connect with your users (e.g. to offer customer support) through email right inside GitLab"
+msgstr ""
+
+msgid "Use a hardware device to add the second factor of authentication."
+msgstr ""
+
+msgid "Use an one time password authenticator on your mobile device or computer to enable two-factor authentication (2FA)."
+msgstr ""
+
+msgid "Use custom color #FF0000"
+msgstr ""
+
+msgid "Use group milestones to manage issues from multiple projects in the same milestone."
+msgstr ""
+
+msgid "Use hashed storage"
+msgstr ""
+
+msgid "Use hashed storage paths for newly created and renamed repositories. Enable immutable, hash-based paths and repository names to store repositories on disk. This prevents repositories from having to be moved or renamed when the Repository URL changes and may improve disk I/O performance. (Always enabled since 13.0)"
+msgstr ""
+
+msgid "Use one line per URI"
+msgstr ""
+
+msgid "Use template"
+msgstr ""
+
+msgid "Use the following registration token during setup:"
+msgstr ""
+
+msgid "Use your global notification setting"
+msgstr ""
+
+msgid "Use your smart card to authenticate with the LDAP server."
+msgstr ""
+
+msgid "Used by members to sign in to your group in GitLab"
+msgstr ""
+
+msgid "Used programming language"
+msgstr ""
+
+msgid "Used to help configure your identity provider"
+msgstr ""
+
+msgid "User"
+msgstr ""
+
+msgid "User %{current_user_username} has started impersonating %{username}"
+msgstr ""
+
+msgid "User %{username} was successfully removed."
+msgstr ""
+
+msgid "User Cohorts are only shown when the %{usage_ping_link_start}usage ping%{usage_ping_link_end} is enabled."
+msgstr ""
+
+msgid "User IDs"
+msgstr ""
+
+msgid "User Lists can only be created and modified with %{linkStart}the API%{linkEnd}"
+msgstr ""
+
+msgid "User OAuth applications"
+msgstr ""
+
+msgid "User Settings"
+msgstr ""
+
+msgid "User and IP Rate Limits"
+msgstr ""
+
+msgid "User identity was successfully created."
+msgstr ""
+
+msgid "User identity was successfully removed."
+msgstr ""
+
+msgid "User identity was successfully updated."
+msgstr ""
+
+msgid "User is not allowed to resolve thread"
+msgstr ""
+
+msgid "User key was successfully removed."
+msgstr ""
+
+msgid "User list %{name} will be removed. Are you sure?"
+msgstr ""
+
+msgid "User map"
+msgstr ""
+
+msgid "User pipeline minutes were successfully reset."
+msgstr ""
+
+msgid "User restrictions"
+msgstr ""
+
+msgid "User was successfully created."
+msgstr ""
+
+msgid "User was successfully removed from group and any subresources."
+msgstr ""
+
+msgid "User was successfully removed from project."
+msgstr ""
+
+msgid "User was successfully updated."
+msgstr ""
+
+msgid "UserList|Delete %{name}?"
+msgstr ""
+
+msgid "UserList|created %{timeago}"
+msgstr ""
+
+msgid "UserProfile|Activity"
+msgstr ""
+
+msgid "UserProfile|Already reported for abuse"
+msgstr ""
+
+msgid "UserProfile|Blocked user"
+msgstr ""
+
+msgid "UserProfile|Contributed projects"
+msgstr ""
+
+msgid "UserProfile|Edit profile"
+msgstr ""
+
+msgid "UserProfile|Explore public groups to find projects to contribute to."
+msgstr ""
+
+msgid "UserProfile|Groups"
+msgstr ""
+
+msgid "UserProfile|Groups are the best way to manage projects and members."
+msgstr ""
+
+msgid "UserProfile|Join or create a group to start contributing by commenting on issues or submitting merge requests!"
+msgstr ""
+
+msgid "UserProfile|Most Recent Activity"
+msgstr ""
+
+msgid "UserProfile|No snippets found."
+msgstr ""
+
+msgid "UserProfile|Overview"
+msgstr ""
+
+msgid "UserProfile|Personal projects"
+msgstr ""
+
+msgid "UserProfile|Report abuse"
+msgstr ""
+
+msgid "UserProfile|Snippets"
+msgstr ""
+
+msgid "UserProfile|Snippets in GitLab can either be private, internal, or public."
+msgstr ""
+
+msgid "UserProfile|Star projects to track their progress and show your appreciation."
+msgstr ""
+
+msgid "UserProfile|Starred projects"
+msgstr ""
+
+msgid "UserProfile|Subscribe"
+msgstr ""
+
+msgid "UserProfile|This user doesn't have any personal projects"
+msgstr ""
+
+msgid "UserProfile|This user has a private profile"
+msgstr ""
+
+msgid "UserProfile|This user hasn't contributed to any projects"
+msgstr ""
+
+msgid "UserProfile|This user hasn't starred any projects"
+msgstr ""
+
+msgid "UserProfile|This user is blocked"
+msgstr ""
+
+msgid "UserProfile|View all"
+msgstr ""
+
+msgid "UserProfile|View user in admin area"
+msgstr ""
+
+msgid "UserProfile|You can create a group for several dependent projects."
+msgstr ""
+
+msgid "UserProfile|You haven't created any personal projects."
+msgstr ""
+
+msgid "UserProfile|You haven't created any snippets."
+msgstr ""
+
+msgid "UserProfile|Your projects can be available publicly, internally, or privately, at your choice."
+msgstr ""
+
+msgid "UserProfile|at"
+msgstr ""
+
+msgid "UserProfile|made a private contribution"
+msgstr ""
+
+msgid "Username (optional)"
+msgstr ""
+
+msgid "Username is already taken."
+msgstr ""
+
+msgid "Username is available."
+msgstr ""
+
+msgid "Username is too long (maximum is %{max_length} characters)."
+msgstr ""
+
+msgid "Username or email"
+msgstr ""
+
+msgid "Users"
+msgstr ""
+
+msgid "Users in License:"
+msgstr ""
+
+msgid "Users or groups set as approvers in the project's or merge request's settings."
+msgstr ""
+
+msgid "Users outside of license"
+msgstr ""
+
+msgid "Users over License:"
+msgstr ""
+
+msgid "Users requesting access to"
+msgstr ""
+
+msgid "Users were successfully added."
+msgstr ""
+
+msgid "Users with a Guest role or those who don't belong to any projects or groups don't count towards seats in use."
+msgstr ""
+
+msgid "UsersSelect|%{name} + %{length} more"
+msgstr ""
+
+msgid "UsersSelect|Any User"
+msgstr ""
+
+msgid "UsersSelect|Assignee"
+msgstr ""
+
+msgid "UsersSelect|No assignee - %{openingTag} assign yourself %{closingTag}"
+msgstr ""
+
+msgid "UsersSelect|Unassigned"
+msgstr ""
+
+msgid "Using %{codeStart}needs%{codeEnd} allows jobs to run before their stage is reached, as soon as their individual dependencies are met, which speeds up your pipelines."
+msgstr ""
+
+msgid "Using %{code_start}::%{code_end} denotes a %{link_start}scoped label set%{link_end}"
+msgstr ""
+
+msgid "Using required encryption strategy when encrypted field is missing!"
+msgstr ""
+
+msgid "Valid from"
+msgstr ""
+
+msgid "Validate"
+msgstr ""
+
+msgid "Validate your GitLab CI configuration file"
+msgstr ""
+
+msgid "Validations failed."
+msgstr ""
+
+msgid "Validity"
+msgstr ""
+
+msgid "Value"
+msgstr ""
+
+msgid "Value Stream"
+msgstr ""
+
+msgid "Value Stream Analytics"
+msgstr ""
+
+msgid "Value Stream Analytics can help you determine your team’s velocity"
+msgstr ""
+
+msgid "Value Stream Analytics gives an overview of how much time it takes to go from idea to production in your project."
+msgstr ""
+
+msgid "ValueStreamAnalytics|%{days}d"
+msgstr ""
+
+msgid "Variable"
+msgstr ""
+
+msgid "Variable will be masked in job logs."
+msgstr ""
+
+msgid "Variables"
+msgstr ""
+
+msgid "Various container registry settings."
+msgstr ""
+
+msgid "Various email settings."
+msgstr ""
+
+msgid "Various localization settings."
+msgstr ""
+
+msgid "Various settings that affect GitLab performance."
+msgstr ""
+
+msgid "Verification capacity"
+msgstr ""
+
+msgid "Verification information"
+msgstr ""
+
+msgid "Verification status"
+msgstr ""
+
+msgid "Verified"
+msgstr ""
+
+msgid "Verify SAML Configuration"
+msgstr ""
+
+msgid "Verify configuration"
+msgstr ""
+
+msgid "Version"
+msgstr ""
+
+msgid "Versions"
+msgstr ""
+
+msgid "View Documentation"
+msgstr ""
+
+msgid "View all issues"
+msgstr ""
+
+msgid "View blame prior to this change"
+msgstr ""
+
+msgid "View chart"
+msgid_plural "View charts"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "View dependency details for your project"
+msgstr ""
+
+msgid "View deployment"
+msgstr ""
+
+msgid "View details"
+msgstr ""
+
+msgid "View details: %{details_url}"
+msgstr ""
+
+msgid "View documentation"
+msgstr ""
+
+msgid "View eligible approvers"
+msgstr ""
+
+msgid "View epics list"
+msgstr ""
+
+msgid "View exposed artifact"
+msgid_plural "View %d exposed artifacts"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "View file @ "
+msgstr ""
+
+msgid "View file @ %{commitSha}"
+msgstr ""
+
+msgid "View full dashboard"
+msgstr ""
+
+msgid "View full log"
+msgstr ""
+
+msgid "View group labels"
+msgstr ""
+
+msgid "View incident issues."
+msgstr ""
+
+msgid "View issue"
+msgstr ""
+
+msgid "View issues"
+msgstr ""
+
+msgid "View it on GitLab"
+msgstr ""
+
+msgid "View job"
+msgstr ""
+
+msgid "View job log"
+msgstr ""
+
+msgid "View jobs"
+msgstr ""
+
+msgid "View labels"
+msgstr ""
+
+msgid "View log"
+msgstr ""
+
+msgid "View open merge request"
+msgstr ""
+
+msgid "View performance dashboard."
+msgstr ""
+
+msgid "View project"
+msgstr ""
+
+msgid "View project labels"
+msgstr ""
+
+msgid "View replaced file @ "
+msgstr ""
+
+msgid "View supported languages and frameworks"
+msgstr ""
+
+msgid "View the documentation"
+msgstr ""
+
+msgid "View the latest successful deployment to this environment"
+msgstr ""
+
+msgid "View the performance dashboard at"
+msgstr ""
+
+msgid "View users statistics"
+msgstr ""
+
+msgid "Viewing commit"
+msgstr ""
+
+msgid "Visibility"
+msgstr ""
+
+msgid "Visibility and access controls"
+msgstr ""
+
+msgid "Visibility level"
+msgstr ""
+
+msgid "Visibility level:"
+msgstr ""
+
+msgid "Visibility settings have been disabled by the administrator."
+msgstr ""
+
+msgid "Visibility, project features, permissions"
+msgstr ""
+
+msgid "Visibility:"
+msgstr ""
+
+msgid "VisibilityLevel|Internal"
+msgstr ""
+
+msgid "VisibilityLevel|Private"
+msgstr ""
+
+msgid "VisibilityLevel|Public"
+msgstr ""
+
+msgid "VisibilityLevel|Unknown"
+msgstr ""
+
+msgid "Visit settings page"
+msgstr ""
+
+msgid "VisualReviewApp|%{stepStart}Step 1%{stepEnd}. Copy the following script:"
+msgstr ""
+
+msgid "VisualReviewApp|%{stepStart}Step 2%{stepEnd}. Add it to the %{headTags} tags of every page of your application, ensuring the merge request ID is set or not set as required. "
+msgstr ""
+
+msgid "VisualReviewApp|%{stepStart}Step 3%{stepEnd}. If not previously %{linkStart}configured%{linkEnd} by a developer, enter the merge request ID for the review when prompted. The ID of this merge request is %{stepStart}%{mrId}%{stepStart}."
+msgstr ""
+
+msgid "VisualReviewApp|%{stepStart}Step 4%{stepEnd}. Leave feedback in the Review App."
+msgstr ""
+
+msgid "VisualReviewApp|Cancel"
+msgstr ""
+
+msgid "VisualReviewApp|Copy merge request ID"
+msgstr ""
+
+msgid "VisualReviewApp|Copy script"
+msgstr ""
+
+msgid "VisualReviewApp|Enable Visual Reviews"
+msgstr ""
+
+msgid "VisualReviewApp|Follow the steps below to enable Visual Reviews inside your application."
+msgstr ""
+
+msgid "VisualReviewApp|No review app found or available."
+msgstr ""
+
+msgid "VisualReviewApp|Open review app"
+msgstr ""
+
+msgid "VisualReviewApp|Review"
+msgstr ""
+
+msgid "VisualReviewApp|Steps 1 and 2 (and sometimes 3) are performed once by the developer before requesting feedback. Steps 3 (if necessary), 4 is performed by the reviewer each time they perform a review."
+msgstr ""
+
+msgid "Vulnerabilities"
+msgstr ""
+
+msgid "Vulnerabilities over time"
+msgstr ""
+
+msgid "Vulnerability remediated. Review before resolving."
+msgstr ""
+
+msgid "Vulnerability resolved in %{branch}"
+msgstr ""
+
+msgid "Vulnerability resolved in the default branch"
+msgstr ""
+
+msgid "Vulnerability-Check"
+msgstr ""
+
+msgid "VulnerabilityChart|%{formattedStartDate} to today"
+msgstr ""
+
+msgid "VulnerabilityChart|Severity"
+msgstr ""
+
+msgid "VulnerabilityManagement|A true-positive and will fix"
+msgstr ""
+
+msgid "VulnerabilityManagement|Change status"
+msgstr ""
+
+msgid "VulnerabilityManagement|Confirm"
+msgstr ""
+
+msgid "VulnerabilityManagement|Confirmed %{timeago} by %{user}"
+msgstr ""
+
+msgid "VulnerabilityManagement|Detected %{timeago} in pipeline %{pipelineLink}"
+msgstr ""
+
+msgid "VulnerabilityManagement|Dismiss"
+msgstr ""
+
+msgid "VulnerabilityManagement|Dismissed %{timeago} by %{user}"
+msgstr ""
+
+msgid "VulnerabilityManagement|Resolved"
+msgstr ""
+
+msgid "VulnerabilityManagement|Resolved %{timeago} by %{user}"
+msgstr ""
+
+msgid "VulnerabilityManagement|Something went wrong while trying to delete the comment. Please try again later."
+msgstr ""
+
+msgid "VulnerabilityManagement|Something went wrong while trying to refresh the vulnerability. Please try again later."
+msgstr ""
+
+msgid "VulnerabilityManagement|Something went wrong while trying to retrieve the vulnerability history. Please try again later."
+msgstr ""
+
+msgid "VulnerabilityManagement|Something went wrong while trying to save the comment. Please try again later."
+msgstr ""
+
+msgid "VulnerabilityManagement|Something went wrong, could not create an issue."
+msgstr ""
+
+msgid "VulnerabilityManagement|Something went wrong, could not get user."
+msgstr ""
+
+msgid "VulnerabilityManagement|Something went wrong, could not update vulnerability state."
+msgstr ""
+
+msgid "VulnerabilityManagement|Verified as fixed or mitigated"
+msgstr ""
+
+msgid "VulnerabilityManagement|Will not fix or a false-positive"
+msgstr ""
+
+msgid "VulnerabilityStatusTypes|All"
+msgstr ""
+
+msgid "VulnerabilityStatusTypes|Confirmed"
+msgstr ""
+
+msgid "VulnerabilityStatusTypes|Detected"
+msgstr ""
+
+msgid "VulnerabilityStatusTypes|Dismissed"
+msgstr ""
+
+msgid "VulnerabilityStatusTypes|Resolved"
+msgstr ""
+
+msgid "Vulnerability|%{scannerName} (version %{scannerVersion})"
+msgstr ""
+
+msgid "Vulnerability|Class"
+msgstr ""
+
+msgid "Vulnerability|Crash Address"
+msgstr ""
+
+msgid "Vulnerability|Description"
+msgstr ""
+
+msgid "Vulnerability|Evidence"
+msgstr ""
+
+msgid "Vulnerability|File"
+msgstr ""
+
+msgid "Vulnerability|Identifiers"
+msgstr ""
+
+msgid "Vulnerability|Image"
+msgstr ""
+
+msgid "Vulnerability|Instances"
+msgstr ""
+
+msgid "Vulnerability|Links"
+msgstr ""
+
+msgid "Vulnerability|Method"
+msgstr ""
+
+msgid "Vulnerability|Namespace"
+msgstr ""
+
+msgid "Vulnerability|Project"
+msgstr ""
+
+msgid "Vulnerability|Scanner Provider"
+msgstr ""
+
+msgid "Vulnerability|Scanner Type"
+msgstr ""
+
+msgid "Vulnerability|Severity"
+msgstr ""
+
+msgid "Vulnerability|Status"
+msgstr ""
+
+msgid "WIP"
+msgstr ""
+
+msgid "Wait for the file to load to copy its contents"
+msgstr ""
+
+msgid "Waiting for merge (open and assigned)"
+msgstr ""
+
+msgid "Waiting for performance data"
+msgstr ""
+
+msgid "Want to see the data? Please ask an administrator for access."
+msgstr ""
+
+msgid "Warning:"
+msgstr ""
+
+msgid "Warning: Displaying this diagram might cause performance issues on this page."
+msgstr ""
+
+msgid "We are currently unable to fetch data for this graph."
+msgstr ""
+
+msgid "We could not determine the path to remove the epic"
+msgstr ""
+
+msgid "We could not determine the path to remove the issue"
+msgstr ""
+
+msgid "We couldn't reach the Prometheus server. Either the server no longer exists or the configuration details need updating."
+msgstr ""
+
+msgid "We detected potential spam in the %{humanized_resource_name}. Please solve the reCAPTCHA to proceed."
+msgstr ""
+
+msgid "We don't have enough data to show this stage."
+msgstr ""
+
+msgid "We have found the following errors:"
+msgstr ""
+
+msgid "We heard back from your device. You have been authenticated."
+msgstr ""
+
+msgid "We recommend that you buy more Pipeline minutes to avoid any interruption of service."
+msgstr ""
+
+msgid "We recommend that you buy more Pipeline minutes to resume normal service."
+msgstr ""
+
+msgid "We sent you an email with reset password instructions"
+msgstr ""
+
+msgid "We tried to automatically renew your %{strong}%{plan_name}%{strong_close} subscription for %{strong}%{namespace_name}%{strong_close} on %{expires_on} but something went wrong so your subscription was downgraded to the free plan. Don't worry, your data is safe. We suggest you check your payment method and get in touch with our support team (%{support_link}). They'll gladly help with your subscription renewal."
+msgstr ""
+
+msgid "We want to be sure it is you, please confirm you are not a robot."
+msgstr ""
+
+msgid "We will automatically renew your %{strong}%{plan_name}%{strong_close} subscription for %{strong}%{namespace_name}%{strong_close} on %{strong}%{expires_on}%{strong_close}. There's nothing that you need to do, we'll let you know when the renewal is complete. Need more seats, a higher plan or just want to review your payment method?"
+msgstr ""
+
+msgid "We've found no vulnerabilities"
+msgstr ""
+
+msgid "Web Application Firewall"
+msgstr ""
+
+msgid "Web IDE"
+msgstr ""
+
+msgid "Web Terminal"
+msgstr ""
+
+msgid "Web terminal"
+msgstr ""
+
+msgid "WebIDE|Merge request"
+msgstr ""
+
+msgid "Webhook"
+msgstr ""
+
+msgid "Webhook Logs"
+msgstr ""
+
+msgid "Webhook Settings"
+msgstr ""
+
+msgid "Webhooks"
+msgstr ""
+
+msgid "Webhooks allow you to trigger a URL if, for example, new code is pushed or a new issue is created. You can configure webhooks to listen for specific events like pushes, issues or merge requests. Group webhooks will apply to all projects in a group, allowing you to standardize webhook functionality across your entire group."
+msgstr ""
+
+msgid "Webhooks have moved. They can now be found under the Settings menu."
+msgstr ""
+
+msgid "Wednesday"
+msgstr ""
+
+msgid "Weekday"
+msgstr ""
+
+msgid "Weeks"
+msgstr ""
+
+msgid "Weight"
+msgstr ""
+
+msgid "Weight %{weight}"
+msgstr ""
+
+msgid "Welcome back! Your account had been deactivated due to inactivity but is now reactivated."
+msgstr ""
+
+msgid "Welcome to GitLab"
+msgstr ""
+
+msgid "Welcome to GitLab %{name}!"
+msgstr ""
+
+msgid "Welcome to GitLab, %{first_name}!"
+msgstr ""
+
+msgid "Welcome to GitLab.com<br>@%{name}!"
+msgstr ""
+
+msgid "Welcome to the guided GitLab tour"
+msgstr ""
+
+msgid "Welcome to your Issue Board!"
+msgstr ""
+
+msgid "What are you searching for?"
+msgstr ""
+
+msgid "What describes you best?"
+msgstr ""
+
+msgid "What’s your experience level?"
+msgstr ""
+
+msgid "When a deployment job is successful, skip older deployment jobs that are still pending"
+msgstr ""
+
+msgid "When a runner is locked, it cannot be assigned to other projects"
+msgstr ""
+
+msgid "When enabled, any user visiting %{host} will be able to create an account."
+msgstr ""
+
+msgid "When enabled, if an NPM package isn't found in the GitLab Registry, we will attempt to pull from the global NPM registry."
+msgstr ""
+
+msgid "When enabled, users cannot use GitLab until the terms have been accepted."
+msgstr ""
+
+msgid "When leaving the URL blank, classification labels can still be specified without disabling cross project features or performing external authorization checks."
+msgstr ""
+
+msgid "When this merge request is accepted"
+msgid_plural "When these merge requests are accepted"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "When using the <code>http://</code> or <code>https://</code> protocols, please provide the exact URL to the repository. HTTP redirects will not be followed."
+msgstr ""
+
+msgid "When:"
+msgstr ""
+
+msgid "While it's rare to have no vulnerabilities, it can happen. In any event, we ask that you please double check your settings to make sure you've set up your dashboard correctly."
+msgstr ""
+
+msgid "Who can be an approver?"
+msgstr ""
+
+msgid "Who can see this group?"
+msgstr ""
+
+msgid "Who will be able to see this group?"
+msgstr ""
+
+msgid "Who will be using GitLab?"
+msgstr ""
+
+msgid "Who will be using this GitLab subscription?"
+msgstr ""
+
+msgid "Who will be using this GitLab trial?"
+msgstr ""
+
+msgid "Wiki"
+msgstr ""
+
+msgid "Wiki was successfully updated."
+msgstr ""
+
+msgid "WikiClone|Clone your wiki"
+msgstr ""
+
+msgid "WikiClone|Git Access"
+msgstr ""
+
+msgid "WikiClone|Install Gollum"
+msgstr ""
+
+msgid "WikiClone|Start Gollum and edit locally"
+msgstr ""
+
+msgid "WikiEditPageTip|Tip: You can move this page by adding the path to the beginning of the title."
+msgstr ""
+
+msgid "WikiEdit|There is already a page with the same title in that path."
+msgstr ""
+
+msgid "WikiEmptyIssueMessage|Suggest wiki improvement"
+msgstr ""
+
+msgid "WikiEmptyIssueMessage|You must be a group member in order to add wiki pages. If you have suggestions for how to improve the wiki for this group, consider opening an issue in the %{issues_link}."
+msgstr ""
+
+msgid "WikiEmptyIssueMessage|You must be a project member in order to add wiki pages. If you have suggestions for how to improve the wiki for this project, consider opening an issue in the %{issues_link}."
+msgstr ""
+
+msgid "WikiEmptyIssueMessage|issue tracker"
+msgstr ""
+
+msgid "WikiEmpty|A wiki is where you can store all the details about your group. This can include why you've created it, its principles, how to use it, and so on."
+msgstr ""
+
+msgid "WikiEmpty|A wiki is where you can store all the details about your project. This can include why you've created it, its principles, how to use it, and so on."
+msgstr ""
+
+msgid "WikiEmpty|Create your first page"
+msgstr ""
+
+msgid "WikiEmpty|Suggest wiki improvement"
+msgstr ""
+
+msgid "WikiEmpty|The wiki lets you write documentation for your group"
+msgstr ""
+
+msgid "WikiEmpty|The wiki lets you write documentation for your project"
+msgstr ""
+
+msgid "WikiEmpty|This group has no wiki pages"
+msgstr ""
+
+msgid "WikiEmpty|This project has no wiki pages"
+msgstr ""
+
+msgid "WikiEmpty|You must be a group member in order to add wiki pages."
+msgstr ""
+
+msgid "WikiEmpty|You must be a project member in order to add wiki pages."
+msgstr ""
+
+msgid "WikiHistoricalPage|This is an old version of this page."
+msgstr ""
+
+msgid "WikiHistoricalPage|You can view the %{most_recent_link} or browse the %{history_link}."
+msgstr ""
+
+msgid "WikiHistoricalPage|history"
+msgstr ""
+
+msgid "WikiHistoricalPage|most recent version"
+msgstr ""
+
+msgid "WikiMarkdownDocs|More examples are in the %{docs_link}"
+msgstr ""
+
+msgid "WikiMarkdownDocs|documentation"
+msgstr ""
+
+msgid "WikiMarkdownTip|To link to a (new) page, simply type <code class=\"js-markup-link-example\">%{link_example}</code>"
+msgstr ""
+
+msgid "WikiNewPageTip|Tip: You can specify the full path for the new file. We will automatically create any missing directories."
+msgstr ""
+
+msgid "WikiPageConfirmDelete|Are you sure you want to delete this page?"
+msgstr ""
+
+msgid "WikiPageConfirmDelete|Delete page"
+msgstr ""
+
+msgid "WikiPageConfirmDelete|Delete page %{pageTitle}?"
+msgstr ""
+
+msgid "WikiPageConflictMessage|Someone edited the page the same time you did. Please check out %{page_link} and make sure your changes will not unintentionally remove theirs."
+msgstr ""
+
+msgid "WikiPageConflictMessage|the page"
+msgstr ""
+
+msgid "WikiPageCreate|Create %{pageTitle}"
+msgstr ""
+
+msgid "WikiPageEdit|Update %{pageTitle}"
+msgstr ""
+
+msgid "WikiPage|Write your content or drag files here…"
+msgstr ""
+
+msgid "Wiki|Create New Page"
+msgstr ""
+
+msgid "Wiki|Create page"
+msgstr ""
+
+msgid "Wiki|Created date"
+msgstr ""
+
+msgid "Wiki|Edit Page"
+msgstr ""
+
+msgid "Wiki|New page"
+msgstr ""
+
+msgid "Wiki|Page history"
+msgstr ""
+
+msgid "Wiki|Page title"
+msgstr ""
+
+msgid "Wiki|Page version"
+msgstr ""
+
+msgid "Wiki|Pages"
+msgstr ""
+
+msgid "Wiki|Title"
+msgstr ""
+
+msgid "Wiki|View All Pages"
+msgstr ""
+
+msgid "Wiki|Wiki Pages"
+msgstr ""
+
+msgid "Will be created"
+msgstr ""
+
+msgid "Will deploy to"
+msgstr ""
+
+msgid "With requirements, you can set criteria to check your products against."
+msgstr ""
+
+msgid "Withdraw Access Request"
+msgstr ""
+
+msgid "Won't fix / Accept risk"
+msgstr ""
+
+msgid "Work in progress (open and unassigned)"
+msgstr ""
+
+msgid "Work in progress Limit"
+msgstr ""
+
+msgid "Write"
+msgstr ""
+
+msgid "Write a comment or drag your files here…"
+msgstr ""
+
+msgid "Write a comment…"
+msgstr ""
+
+msgid "Write access allowed"
+msgstr ""
+
+msgid "Write milestone description..."
+msgstr ""
+
+msgid "Write your release notes or drag your files here…"
+msgstr ""
+
+msgid "Wrong extern UID provided. Make sure Auth0 is configured correctly."
+msgstr ""
+
+msgid "Yes"
+msgstr ""
+
+msgid "Yes or No"
+msgstr ""
+
+msgid "Yes, add it"
+msgstr ""
+
+msgid "Yes, close issue"
+msgstr ""
+
+msgid "Yes, let me map Google Code users to full names or GitLab users."
+msgstr ""
+
+msgid "Yesterday"
+msgstr ""
+
+msgid "You"
+msgstr ""
+
+msgid "You are about to delete %{domain} from your instance. This domain will no longer be available to any Knative application."
+msgstr ""
+
+msgid "You are about to transfer the control of your account to %{group_name} group. This action is NOT reversible, you won't be able to access any of your groups and projects outside of %{group_name} once this transfer is complete."
+msgstr ""
+
+msgid "You are an admin, which means granting access to <strong>%{client_name}</strong> will allow them to interact with GitLab as an admin as well. Proceed with caution."
+msgstr ""
+
+msgid "You are attempting to delete a file that has been previously updated."
+msgstr ""
+
+msgid "You are attempting to update a file that has changed since you started editing it."
+msgstr ""
+
+msgid "You are connected to the Prometheus server, but there is currently no data to display."
+msgstr ""
+
+msgid "You are going to remove %{group_name}, this will also remove all of its subgroups and projects. Removed groups CANNOT be restored! Are you ABSOLUTELY sure?"
+msgstr ""
+
+msgid "You are going to remove %{project_full_name}. Removed project CANNOT be restored! Are you ABSOLUTELY sure?"
+msgstr ""
+
+msgid "You are going to remove the fork relationship from %{project_full_name}. Are you ABSOLUTELY sure?"
+msgstr ""
+
+msgid "You are going to transfer %{project_full_name} to another owner. Are you ABSOLUTELY sure?"
+msgstr ""
+
+msgid "You are not allowed to push into this branch. Create another branch or open a merge request."
+msgstr ""
+
+msgid "You are not allowed to unlink your primary login account"
+msgstr ""
+
+msgid "You are not authorized to perform this action"
+msgstr ""
+
+msgid "You are now impersonating %{username}"
+msgstr ""
+
+msgid "You are on a read-only GitLab instance."
+msgstr ""
+
+msgid "You are receiving this message because you are a GitLab administrator for %{url}."
+msgstr ""
+
+msgid "You are trying to upload something other than an image. Please upload a .png, .jpg, .jpeg, .gif, .bmp, .tiff or .ico."
+msgstr ""
+
+msgid "You can %{linkStart}view the blob%{linkEnd} instead."
+msgstr ""
+
+msgid "You can also create a project from the command line."
+msgstr ""
+
+msgid "You can also press &#8984;-Enter"
+msgstr ""
+
+msgid "You can also press Ctrl-Enter"
+msgstr ""
+
+msgid "You can also star a label to make it a priority label."
+msgstr ""
+
+msgid "You can also test your %{gitlab_ci_yml} in %{lint_link_start}CI Lint%{lint_link_end}"
+msgstr ""
+
+msgid "You can also upload existing files from your computer using the instructions below."
+msgstr ""
+
+msgid "You can also use project access tokens to authenticate against Git over HTTP."
+msgstr ""
+
+msgid "You can always edit this later"
+msgstr ""
+
+msgid "You can apply your Trial to your Personal account or create a New Group."
+msgstr ""
+
+msgid "You can create a new one or check them in your %{pat_link_start}Personal Access Tokens%{pat_link_end} settings"
+msgstr ""
+
+msgid "You can create a new one or check them in your Personal Access Tokens settings %{pat_link}"
+msgstr ""
+
+msgid "You can create new ones at your %{pat_link_start}Personal Access Tokens%{pat_link_end} settings"
+msgstr ""
+
+msgid "You can create new ones at your Personal Access Tokens settings %{pat_link}"
+msgstr ""
+
+msgid "You can easily contribute to them by requesting to join these groups."
+msgstr ""
+
+msgid "You can easily install a Runner on a Kubernetes cluster. %{link_to_help_page}"
+msgstr ""
+
+msgid "You can filter by 'days to merge' by clicking on the columns in the chart."
+msgstr ""
+
+msgid "You can generate an access token scoped to this project for each application to use the GitLab API."
+msgstr ""
+
+msgid "You can get started by cloning the repository or start adding files to it with one of the following options."
+msgstr ""
+
+msgid "You can invite a new member to <strong>%{project_name}</strong> or invite another group."
+msgstr ""
+
+msgid "You can invite a new member to <strong>%{project_name}</strong>."
+msgstr ""
+
+msgid "You can invite another group to <strong>%{project_name}</strong>."
+msgstr ""
+
+msgid "You can move around the graph by using the arrow keys."
+msgstr ""
+
+msgid "You can notify the app / group or a project by sending them an email notification"
+msgstr ""
+
+msgid "You can now export your security dashboard to a CSV report."
+msgstr ""
+
+msgid "You can now manage alert endpoint configuration in the Alerts section on the Operations settings page. Fields on this page have been deprecated."
+msgstr ""
+
+msgid "You can now submit a merge request to get this change into the original branch."
+msgstr ""
+
+msgid "You can now submit a merge request to get this change into the original project."
+msgstr ""
+
+msgid "You can only edit files when you are on a branch"
+msgstr ""
+
+msgid "You can only merge once the items above are resolved."
+msgstr ""
+
+msgid "You can only merge once this merge request is approved."
+msgstr ""
+
+msgid "You can only transfer the project to namespaces you manage."
+msgstr ""
+
+msgid "You can only upload one design when dropping onto an existing design."
+msgstr ""
+
+msgid "You can resolve the merge conflict using either the Interactive mode, by choosing %{use_ours} or %{use_theirs} buttons, or by editing the files directly. Commit these changes into %{branch_name}"
+msgstr ""
+
+msgid "You can see your chat accounts."
+msgstr ""
+
+msgid "You can set up as many Runners as you need to run your jobs."
+msgstr ""
+
+msgid "You can set up jobs to only use Runners with specific tags. Separate tags with commas."
+msgstr ""
+
+msgid "You can specify notification level per group or per project."
+msgstr ""
+
+msgid "You can test your .gitlab-ci.yml in %{linkStart}CI Lint%{linkEnd}."
+msgstr ""
+
+msgid "You can try again using %{begin_link}basic search%{end_link}"
+msgstr ""
+
+msgid "You cannot access the raw file. Please wait a minute."
+msgstr ""
+
+msgid "You cannot impersonate a blocked user"
+msgstr ""
+
+msgid "You cannot impersonate a user who cannot log in"
+msgstr ""
+
+msgid "You cannot impersonate an internal user"
+msgstr ""
+
+msgid "You cannot play this scheduled pipeline at the moment. Please wait a minute."
+msgstr ""
+
+msgid "You cannot write to a read-only secondary GitLab Geo instance. Please use %{link_to_primary_node} instead."
+msgstr ""
+
+msgid "You cannot write to this read-only GitLab instance."
+msgstr ""
+
+msgid "You could not create a new trigger."
+msgstr ""
+
+msgid "You didn't renew your %{strong}%{plan_name}%{strong_close} subscription for %{strong}%{namespace_name}%{strong_close} so it was downgraded to the free plan."
+msgstr ""
+
+msgid "You didn't renew your %{strong}%{plan_name}%{strong_close} subscription so it was downgraded to the GitLab Core Plan."
+msgstr ""
+
+msgid "You do not have an active license"
+msgstr ""
+
+msgid "You do not have any subscriptions yet"
+msgstr ""
+
+msgid "You do not have permission to leave this %{namespaceType}."
+msgstr ""
+
+msgid "You do not have permission to run the Web Terminal. Please contact a project administrator."
+msgstr ""
+
+msgid "You do not have permissions to run the import."
+msgstr ""
+
+msgid "You do not have the correct permissions to override the settings from the LDAP group sync."
+msgstr ""
+
+msgid "You don't have any U2F devices registered yet."
+msgstr ""
+
+msgid "You don't have any active chat names."
+msgstr ""
+
+msgid "You don't have any applications"
+msgstr ""
+
+msgid "You don't have any authorized applications"
+msgstr ""
+
+msgid "You don't have any deployments right now."
+msgstr ""
+
+msgid "You don't have any open merge requests"
+msgstr ""
+
+msgid "You don't have any projects available."
+msgstr ""
+
+msgid "You don't have any recent searches"
+msgstr ""
+
+msgid "You don't have sufficient permission to perform this action."
+msgstr ""
+
+msgid "You don’t have access to Productivity Analytics in this group"
+msgstr ""
+
+msgid "You don’t have access to Value Stream Analytics for this group"
+msgstr ""
+
+msgid "You have a license that activates at a future date. Please see the License History table below."
+msgstr ""
+
+msgid "You have been granted %{access_level} access to the %{source_link} %{source_type}."
+msgstr ""
+
+msgid "You have been granted %{access_level} access to the %{source_name} %{source_type}."
+msgstr ""
+
+msgid "You have been granted %{member_human_access} access to %{label}."
+msgstr ""
+
+msgid "You have been unsubscribed from this thread."
+msgstr ""
+
+msgid "You have declined the invitation to join %{label}."
+msgstr ""
+
+msgid "You have imported from this project %{numberOfPreviousImports} times before. Each new import will create duplicate issues."
+msgstr ""
+
+msgid "You have no permissions"
+msgstr ""
+
+msgid "You have not added any approvers. Start by adding users or groups."
+msgstr ""
+
+msgid "You have reached your project limit"
+msgstr ""
+
+msgid "You have successfully purchased a %{plan} plan subscription for %{seats}. You’ll receive a receipt via email."
+msgstr ""
+
+msgid "You haven't added any issues to your project yet"
+msgstr ""
+
+msgid "You haven't selected any issues yet"
+msgstr ""
+
+msgid "You left the \"%{membershipable_human_name}\" %{source_type}."
+msgstr ""
+
+msgid "You may also add variables that are made available to the running application by prepending the variable key with <code>K8S_SECRET_</code>."
+msgstr ""
+
+msgid "You may close the milestone now."
+msgstr ""
+
+msgid "You must accept our Terms of Service and privacy policy in order to register an account"
+msgstr ""
+
+msgid "You must disassociate %{domain} from all clusters it is attached to before deletion."
+msgstr ""
+
+msgid "You must have maintainer access to force delete a lock"
+msgstr ""
+
+msgid "You must have permission to create a project in a namespace before forking."
+msgstr ""
+
+msgid "You must provide a valid current password"
+msgstr ""
+
+msgid "You must provide your current password in order to change it."
+msgstr ""
+
+msgid "You must select a stack for configuring your cloud provider. Learn more about"
+msgstr ""
+
+msgid "You must set up incoming email before it becomes active."
+msgstr ""
+
+msgid "You must upload a file with the same file name when dropping onto an existing design."
+msgstr ""
+
+msgid "You need a different license to enable FileLocks feature"
+msgstr ""
+
+msgid "You need git-lfs version %{min_git_lfs_version} (or greater) to continue. Please visit https://git-lfs.github.com"
+msgstr ""
+
+msgid "You need permission."
+msgstr ""
+
+msgid "You need to be logged in."
+msgstr ""
+
+msgid "You need to register a two-factor authentication app before you can set up a U2F device."
+msgstr ""
+
+msgid "You need to set terms to be enforced"
+msgstr ""
+
+msgid "You need to specify both an Access Token and a Host URL."
+msgstr ""
+
+msgid "You need to upload a GitLab project export archive (ending in .gz)."
+msgstr ""
+
+msgid "You need to upload a Google Takeout archive."
+msgstr ""
+
+msgid "You reached %{usage_in_percent} of %{namespace_name}'s storage capacity (%{used_storage} of %{storage_limit})"
+msgstr ""
+
+msgid "You tried to fork %{link_to_the_project} but it failed for the following reason:"
+msgstr ""
+
+msgid "You will be removed from existing projects/groups"
+msgstr ""
+
+msgid "You will be the author of all events in the activity feed that are the result of an update, like new branches being created or new commits being pushed to existing branches."
+msgstr ""
+
+msgid "You will first need to set up Jira Integration to use this feature."
+msgstr ""
+
+msgid "You will lose all changes you've made to this file. This action cannot be undone."
+msgstr ""
+
+msgid "You will lose all uncommitted changes you've made in this project. This action cannot be undone."
+msgstr ""
+
+msgid "You will need to update your local repositories to point to the new location."
+msgstr ""
+
+msgid "You will not get any notifications via email"
+msgstr ""
+
+msgid "You will only receive notifications for the events you choose"
+msgstr ""
+
+msgid "You will only receive notifications for threads you have participated in"
+msgstr ""
+
+msgid "You will receive notifications for any activity"
+msgstr ""
+
+msgid "You will receive notifications only for comments in which you were @mentioned"
+msgstr ""
+
+msgid "You won't be able to create new projects because you have reached your project limit."
+msgstr ""
+
+msgid "You won't be able to pull or push project code via %{protocol} until you %{set_password_link} on your account"
+msgstr ""
+
+msgid "You won't be able to pull or push project code via SSH until you add an SSH key to your profile"
+msgstr ""
+
+msgid "You'll be signed out from your current account automatically."
+msgstr ""
+
+msgid "You'll need to use different branch names to get a valid comparison."
+msgstr ""
+
+msgid "You're about to reduce the visibility of the project %{strong_start}%{project_name}%{strong_end} in %{strong_start}%{group_name}%{strong_end}."
+msgstr ""
+
+msgid "You're about to reduce the visibility of the project %{strong_start}%{project_name}%{strong_end}."
+msgstr ""
+
+msgid "You're at the first commit"
+msgstr ""
+
+msgid "You're at the last commit"
+msgstr ""
+
+msgid "You're not allowed to %{tag_start}edit%{tag_end} files in this project directly. Please fork this project, make your changes there, and submit a merge request."
+msgstr ""
+
+msgid "You're not allowed to make changes to this project directly. A fork of this project has been created that you can make changes in, so you can submit a merge request."
+msgstr ""
+
+msgid "You're not allowed to make changes to this project directly. A fork of this project is being created that you can make changes in, so you can submit a merge request."
+msgstr ""
+
+msgid "You're only seeing %{startTag}other activity%{endTag} in the feed. To add a comment, switch to one of the following options."
+msgstr ""
+
+msgid "You're receiving this email because of your account on %{host}."
+msgstr ""
+
+msgid "You're receiving this email because of your account on %{host}. %{manage_notifications_link} &middot; %{help_link}"
+msgstr ""
+
+msgid "You're receiving this email because of your activity on %{host}."
+msgstr ""
+
+msgid "You're receiving this email because you have been assigned an item on %{host}."
+msgstr ""
+
+msgid "You're receiving this email because you have been mentioned on %{host}."
+msgstr ""
+
+msgid "You've already enabled two-factor authentication using one time password authenticators. In order to register a different device, you must first disable two-factor authentication."
+msgstr ""
+
+msgid "YouTube"
+msgstr ""
+
+msgid "Your %{host} account was signed in to from a new location"
+msgstr ""
+
+msgid "Your %{strong}%{plan_name}%{strong_close} subscription for %{strong}%{namespace_name}%{strong_close} will expire on %{strong}%{expires_on}%{strong_close}."
+msgstr ""
+
+msgid "Your %{strong}%{plan_name}%{strong_close} subscription will expire on %{strong}%{expires_on}%{strong_close}. After that, you will not to be able to create issues or merge requests as well as many other features."
+msgstr ""
+
+msgid "Your CSV export has started. It will be emailed to %{email} when complete."
+msgstr ""
+
+msgid "Your CSV export of %{issues_count} from project %{project_link} has been added to this email as an attachment."
+msgstr ""
+
+msgid "Your CSV export of %{written_count} from project %{project_name} (%{project_url}) has been added to this email as an attachment."
+msgstr ""
+
+msgid "Your Commit Email will be used for web based operations, such as edits and merges."
+msgstr ""
+
+msgid "Your Default Notification Email will be used for account notifications if a %{openingTag}group-specific email address%{closingTag} is not set."
+msgstr ""
+
+msgid "Your DevOps Score gives an overview of how you are using GitLab from a feature perspective. View how you compare with other organizations, discover features you are not using, and learn best practices through blog posts and white papers."
+msgstr ""
+
+msgid "Your GPG keys (%{count})"
+msgstr ""
+
+msgid "Your GitLab group"
+msgstr ""
+
+msgid "Your Gitlab Gold trial will last 30 days after which point you can keep your free Gitlab account forever. We just need some additional information to activate your trial."
+msgstr ""
+
+msgid "Your Groups"
+msgstr ""
+
+msgid "Your License"
+msgstr ""
+
+msgid "Your Personal Access Tokens will expire in %{days_to_expire} days or less"
+msgstr ""
+
+msgid "Your Primary Email will be used for avatar detection."
+msgstr ""
+
+msgid "Your Projects (default)"
+msgstr ""
+
+msgid "Your Projects' Activity"
+msgstr ""
+
+msgid "Your Public Email will be displayed on your public profile."
+msgstr ""
+
+msgid "Your SSH keys (%{count})"
+msgstr ""
+
+msgid "Your To-Do List"
+msgstr ""
+
+msgid "Your U2F device did not send a valid JSON response."
+msgstr ""
+
+msgid "Your U2F device needs to be set up. Plug it in (if not already) and click the button on the left."
+msgstr ""
+
+msgid "Your U2F device was registered!"
+msgstr ""
+
+msgid "Your access request to the %{source_type} has been withdrawn."
+msgstr ""
+
+msgid "Your account has been deactivated by your administrator. Please log back in to reactivate your account."
+msgstr ""
+
+msgid "Your account is locked."
+msgstr ""
+
+msgid "Your account uses dedicated credentials for the \"%{group_name}\" group and can only be updated through SSO."
+msgstr ""
+
+msgid "Your applications (%{size})"
+msgstr ""
+
+msgid "Your authorized applications"
+msgstr ""
+
+msgid "Your browser doesn't support U2F. Please use Google Chrome desktop (version 41 or newer)."
+msgstr ""
+
+msgid "Your changes can be committed to %{branch_name} because a merge request is open."
+msgstr ""
+
+msgid "Your changes have been committed. Commit %{commitId} %{commitStats}"
+msgstr ""
+
+msgid "Your changes have been saved"
+msgstr ""
+
+msgid "Your changes have been successfully committed."
+msgstr ""
+
+msgid "Your comment could not be submitted because %{error}"
+msgstr ""
+
+msgid "Your comment could not be submitted! Please check your network connection and try again."
+msgstr ""
+
+msgid "Your comment could not be updated! Please check your network connection and try again."
+msgstr ""
+
+msgid "Your comment will be discarded."
+msgstr ""
+
+msgid "Your custom stage '%{title}' was created"
+msgstr ""
+
+msgid "Your dashboard has been copied. You can %{web_ide_link_start}edit it here%{web_ide_link_end}."
+msgstr ""
+
+msgid "Your dashboard has been updated. You can %{web_ide_link_start}edit it here%{web_ide_link_end}."
+msgstr ""
+
+msgid "Your deployment services will be broken, you will need to manually fix the services after renaming."
+msgstr ""
+
+msgid "Your device was successfully set up! Give it a name and register it with the GitLab server."
+msgstr ""
+
+msgid "Your first project"
+msgstr ""
+
+msgid "Your groups"
+msgstr ""
+
+msgid "Your instance has %{remaining_user_count} users remaining of the %{total_user_count} included in your subscription. You can add more users than the number included in your license, and we will include the overage in your next bill."
+msgstr ""
+
+msgid "Your instance is approaching its licensed user count"
+msgstr ""
+
+msgid "Your issues are being imported. Once finished, you'll get a confirmation email."
+msgstr ""
+
+msgid "Your issues will be imported in the background. Once finished, you'll get a confirmation email."
+msgstr ""
+
+msgid "Your license is valid from"
+msgstr ""
+
+msgid "Your license will be included in your GitLab backup and will survive upgrades, so in normal usage you should never need to re-upload your <code>.gitlab-license</code> file."
+msgstr ""
+
+msgid "Your message here"
+msgstr ""
+
+msgid "Your name"
+msgstr ""
+
+msgid "Your new %{type}"
+msgstr ""
+
+msgid "Your new SCIM token"
+msgstr ""
+
+msgid "Your new personal access token has been created."
+msgstr ""
+
+msgid "Your new project access token has been created."
+msgstr ""
+
+msgid "Your password isn't required to view this page. If a password or any other personal details are requested, please contact your administrator to report abuse."
+msgstr ""
+
+msgid "Your password reset token has expired."
+msgstr ""
+
+msgid "Your profile"
+msgstr ""
+
+msgid "Your project limit is %{limit} projects! Please contact your administrator to increase it"
+msgstr ""
+
+msgid "Your projects"
+msgstr ""
+
+msgid "Your request for access could not be processed: %{error_meesage}"
+msgstr ""
+
+msgid "Your request for access has been queued for review."
+msgstr ""
+
+msgid "Your response has been recorded."
+msgstr ""
+
+msgid "Your search didn't match any commits."
+msgstr ""
+
+msgid "Your subscription expired!"
+msgstr ""
+
+msgid "Your subscription has been downgraded."
+msgstr ""
+
+msgid "Your subscription will automatically renew in %{remaining_days}."
+msgstr ""
+
+msgid "Your subscription will expire in %{remaining_days}."
+msgstr ""
+
+msgid "Zoom meeting added"
+msgstr ""
+
+msgid "Zoom meeting removed"
+msgstr ""
+
+msgid "[No reason]"
+msgstr ""
+
+msgid "a deleted user"
+msgstr ""
+
+msgid "a design"
+msgstr ""
+
+msgid "about 1 hour"
+msgid_plural "about %d hours"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "activated"
+msgstr ""
+
+msgid "added %{created_at_timeago}"
+msgstr ""
+
+msgid "added a Zoom call to this issue"
+msgstr ""
+
+msgid "ago"
+msgstr ""
+
+msgid "alert"
+msgstr ""
+
+msgid "allowed to fail"
+msgstr ""
+
+msgid "already being used for another group or project %{timebox_name}."
+msgstr ""
+
+msgid "already has a \"created\" issue link"
+msgstr ""
+
+msgid "already shared with this group"
+msgstr ""
+
+msgid "among other things"
+msgstr ""
+
+msgid "any-approver for the merge request already exists"
+msgstr ""
+
+msgid "any-approver for the project already exists"
+msgstr ""
+
+msgid "archived"
+msgstr ""
+
+msgid "assign yourself"
+msgstr ""
+
+msgid "at risk"
+msgstr ""
+
+msgid "attach a new file"
+msgstr ""
+
+msgid "authored"
+msgstr ""
+
+msgid "blocks"
+msgstr ""
+
+msgid "branch name"
+msgstr ""
+
+msgid "by"
+msgstr ""
+
+msgid "by %{user}"
+msgstr ""
+
+msgid "cannot be changed if a personal project has container registry tags."
+msgstr ""
+
+msgid "cannot be enabled unless all domains have TLS certificates"
+msgstr ""
+
+msgid "cannot be modified"
+msgstr ""
+
+msgid "cannot block others"
+msgstr ""
+
+msgid "cannot contain HTML/XML tags, including any word between angle brackets (<,>)."
+msgstr ""
+
+msgid "cannot include leading slash or directory traversal."
+msgstr ""
+
+msgid "cannot itself be blocked"
+msgstr ""
+
+msgid "cannot merge"
+msgstr ""
+
+msgid "ciReport|%{linkStartTag}Learn more about Container Scanning %{linkEndTag}"
+msgstr ""
+
+msgid "ciReport|%{linkStartTag}Learn more about DAST %{linkEndTag}"
+msgstr ""
+
+msgid "ciReport|%{linkStartTag}Learn more about Dependency Scanning %{linkEndTag}"
+msgstr ""
+
+msgid "ciReport|%{linkStartTag}Learn more about SAST %{linkEndTag}"
+msgstr ""
+
+msgid "ciReport|%{linkStartTag}Learn more about Secret Scanning %{linkEndTag}"
+msgstr ""
+
+msgid "ciReport|%{linkStartTag}Learn more about codequality reports %{linkEndTag}"
+msgstr ""
+
+msgid "ciReport|%{remainingPackagesCount} more"
+msgstr ""
+
+msgid "ciReport|%{reportType} is loading"
+msgstr ""
+
+msgid "ciReport|%{reportType}: Loading resulted in an error"
+msgstr ""
+
+msgid "ciReport|(errors when loading results)"
+msgstr ""
+
+msgid "ciReport|(is loading)"
+msgstr ""
+
+msgid "ciReport|(is loading, errors when loading results)"
+msgstr ""
+
+msgid "ciReport|All projects"
+msgstr ""
+
+msgid "ciReport|All scanner types"
+msgstr ""
+
+msgid "ciReport|All severities"
+msgstr ""
+
+msgid "ciReport|Automatically apply the patch in a new branch"
+msgstr ""
+
+msgid "ciReport|Base pipeline codequality artifact not found"
+msgstr ""
+
+msgid "ciReport|Code quality"
+msgstr ""
+
+msgid "ciReport|Container Scanning"
+msgstr ""
+
+msgid "ciReport|Container scanning"
+msgstr ""
+
+msgid "ciReport|Container scanning detects known vulnerabilities in your docker images."
+msgstr ""
+
+msgid "ciReport|Coverage Fuzzing"
+msgstr ""
+
+msgid "ciReport|Create a merge request to implement this solution, or download and apply the patch manually."
+msgstr ""
+
+msgid "ciReport|Create issue"
+msgstr ""
+
+msgid "ciReport|DAST"
+msgstr ""
+
+msgid "ciReport|Dependency Scanning"
+msgstr ""
+
+msgid "ciReport|Dependency Scanning detects known vulnerabilities in your source code's dependencies."
+msgstr ""
+
+msgid "ciReport|Dependency scanning"
+msgstr ""
+
+msgid "ciReport|Download patch to resolve"
+msgstr ""
+
+msgid "ciReport|Download the patch to apply it manually"
+msgstr ""
+
+msgid "ciReport|Dynamic Application Security Testing (DAST) detects known vulnerabilities in your web application."
+msgstr ""
+
+msgid "ciReport|Failed to load %{reportName} report"
+msgstr ""
+
+msgid "ciReport|Fixed"
+msgstr ""
+
+msgid "ciReport|Fixed:"
+msgstr ""
+
+msgid "ciReport|Found %{issuesWithCount}"
+msgstr ""
+
+msgid "ciReport|Investigate this vulnerability by creating an issue"
+msgstr ""
+
+msgid "ciReport|Learn more about interacting with security reports"
+msgstr ""
+
+msgid "ciReport|Loading %{reportName} report"
+msgstr ""
+
+msgid "ciReport|Manage licenses"
+msgstr ""
+
+msgid "ciReport|New"
+msgstr ""
+
+msgid "ciReport|No changes to code quality"
+msgstr ""
+
+msgid "ciReport|No changes to performance metrics"
+msgstr ""
+
+msgid "ciReport|No code quality issues found"
+msgstr ""
+
+msgid "ciReport|Performance metrics"
+msgstr ""
+
+msgid "ciReport|Resolve with merge request"
+msgstr ""
+
+msgid "ciReport|SAST"
+msgstr ""
+
+msgid "ciReport|Secret Detection"
+msgstr ""
+
+msgid "ciReport|Secret scanning"
+msgstr ""
+
+msgid "ciReport|Secret scanning detects secrets and credentials vulnerabilities in your source code."
+msgstr ""
+
+msgid "ciReport|Security scanning"
+msgstr ""
+
+msgid "ciReport|Security scanning failed loading any results"
+msgstr ""
+
+msgid "ciReport|Solution"
+msgstr ""
+
+msgid "ciReport|Static Application Security Testing (SAST) detects known vulnerabilities in your source code."
+msgstr ""
+
+msgid "ciReport|There was an error creating the issue. Please try again."
+msgstr ""
+
+msgid "ciReport|There was an error creating the merge request. Please try again."
+msgstr ""
+
+msgid "ciReport|There was an error dismissing the vulnerability. Please try again."
+msgstr ""
+
+msgid "ciReport|There was an error fetching the codequality report."
+msgstr ""
+
+msgid "ciReport|There was an error reverting the dismissal. Please try again."
+msgstr ""
+
+msgid "ciReport|Used by %{packagesString}"
+msgid_plural "ciReport|Used by %{packagesString}, and %{lastPackage}"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "ciReport|View full report"
+msgstr ""
+
+msgid "closed issue"
+msgstr ""
+
+msgid "comment"
+msgstr ""
+
+msgid "commented on %{link_to_project}"
+msgstr ""
+
+msgid "commit %{commit_id}"
+msgstr ""
+
+msgid "committed"
+msgstr ""
+
+msgid "confidentiality|You are going to turn off the confidentiality. This means <strong>everyone</strong> will be able to see and leave a comment on this issue."
+msgstr ""
+
+msgid "confidentiality|You are going to turn on the confidentiality. This means that only team members with <strong>at least Reporter access</strong> are able to see and leave comments on the issue."
+msgstr ""
+
+msgid "connecting"
+msgstr ""
+
+msgid "container_name can contain only lowercase letters, digits, '-', and '.' and must start and end with an alphanumeric character"
+msgstr ""
+
+msgid "container_name cannot be larger than %{max_length} chars"
+msgstr ""
+
+msgid "could not read private key, is the passphrase correct?"
+msgstr ""
+
+msgid "created"
+msgstr ""
+
+msgid "created %{timeAgo}"
+msgstr ""
+
+msgid "customize"
+msgstr ""
+
+msgid "data"
+msgstr ""
+
+msgid "date must not be after 9999-12-31"
+msgstr ""
+
+msgid "day"
+msgid_plural "days"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "default branch"
+msgstr ""
+
+msgid "deleted"
+msgstr ""
+
+msgid "deploy"
+msgstr ""
+
+msgid "design"
+msgstr ""
+
+msgid "designs"
+msgstr ""
+
+msgid "detached"
+msgstr ""
+
+msgid "disabled"
+msgstr ""
+
+msgid "does not have a supported extension. Only %{extension_list} are supported"
+msgstr ""
+
+msgid "done"
+msgstr ""
+
+msgid "download it"
+msgstr ""
+
+msgid "draft"
+msgid_plural "drafts"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "e.g. %{token}"
+msgstr ""
+
+msgid "element is not a hierarchy"
+msgstr ""
+
+msgid "email '%{email}' does not match the allowed domains of %{email_domains}"
+msgstr ""
+
+msgid "email '%{email}' is not a verified email."
+msgstr ""
+
+msgid "enabled"
+msgstr ""
+
+msgid "encrypted: needs to be a :required, :optional or :migrating!"
+msgstr ""
+
+msgid "entries cannot be larger than 255 characters"
+msgstr ""
+
+msgid "entries cannot be nil"
+msgstr ""
+
+msgid "entries cannot contain HTML tags"
+msgstr ""
+
+msgid "epic"
+msgstr ""
+
+msgid "error"
+msgstr ""
+
+msgid "error code:"
+msgstr ""
+
+msgid "estimateCommand|%{slash_command} will update the estimated time with the latest command."
+msgstr ""
+
+msgid "exceeds the limit of %{bytes} bytes"
+msgstr ""
+
+msgid "exceeds the limit of %{bytes} bytes for directory name \"%{dirname}\""
+msgstr ""
+
+msgid "expired on %{timebox_due_date}"
+msgstr ""
+
+msgid "expires on %{timebox_due_date}"
+msgstr ""
+
+msgid "failed"
+msgstr ""
+
+msgid "failed to dismiss associated finding(id=%{finding_id}): %{message}"
+msgstr ""
+
+msgid "file"
+msgid_plural "files"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "finding is not found or is already attached to a vulnerability"
+msgstr ""
+
+msgid "for %{link_to_merge_request} with %{link_to_merge_request_source_branch}"
+msgstr ""
+
+msgid "for %{link_to_merge_request} with %{link_to_merge_request_source_branch} into %{link_to_merge_request_target_branch}"
+msgstr ""
+
+msgid "for %{link_to_pipeline_ref}"
+msgstr ""
+
+msgid "for %{ref}"
+msgstr ""
+
+msgid "for this project"
+msgstr ""
+
+msgid "fork this project"
+msgstr ""
+
+msgid "from"
+msgstr ""
+
+msgid "group"
+msgstr ""
+
+msgid "groups"
+msgstr ""
+
+msgid "has already been linked to another vulnerability"
+msgstr ""
+
+msgid "has already been taken"
+msgstr ""
+
+msgid "help"
+msgstr ""
+
+msgid "here"
+msgstr ""
+
+msgid "https://your-bitbucket-server"
+msgstr ""
+
+msgid "image diff"
+msgstr ""
+
+msgid "impersonation token"
+msgstr ""
+
+msgid "impersonation tokens"
+msgstr ""
+
+msgid "import flow"
+msgstr ""
+
+msgid "importing"
+msgstr ""
+
+msgid "in group %{link_to_group}"
+msgstr ""
+
+msgid "in project %{link_to_project}"
+msgstr ""
+
+msgid "index"
+msgstr ""
+
+msgid "instance completed"
+msgid_plural "instances completed"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "invalid milestone state `%{state}`"
+msgstr ""
+
+msgid "is"
+msgstr ""
+
+msgid "is already associated to a GitLab Issue. New issue will not be associated."
+msgstr ""
+
+msgid "is an invalid IP address range"
+msgstr ""
+
+msgid "is blocked by"
+msgstr ""
+
+msgid "is enabled."
+msgstr ""
+
+msgid "is invalid because there is downstream lock"
+msgstr ""
+
+msgid "is invalid because there is upstream lock"
+msgstr ""
+
+msgid "is not"
+msgstr ""
+
+msgid "is not a descendant of the Group owning the template"
+msgstr ""
+
+msgid "is not a valid X509 certificate."
+msgstr ""
+
+msgid "is not allowed. Try again with a different email address, or contact your GitLab admin."
+msgstr ""
+
+msgid "is not an email you own"
+msgstr ""
+
+msgid "is not in the group enforcing Group Managed Account"
+msgstr ""
+
+msgid "is read only"
+msgstr ""
+
+msgid "is too long (%{current_value}). The maximum size is %{max_size}."
+msgstr ""
+
+msgid "is too long (maximum is 100 entries)"
+msgstr ""
+
+msgid "is too long (maximum is 1000 entries)"
+msgstr ""
+
+msgid "issue"
+msgstr ""
+
+msgid "issues at risk"
+msgstr ""
+
+msgid "issues need attention"
+msgstr ""
+
+msgid "issues on track"
+msgstr ""
+
+msgid "it is larger than %{limit}"
+msgstr ""
+
+msgid "it is stored as a job artifact"
+msgstr ""
+
+msgid "it is stored externally"
+msgstr ""
+
+msgid "it is stored in LFS"
+msgstr ""
+
+msgid "it is too large"
+msgstr ""
+
+msgid "jigsaw is not defined"
+msgstr ""
+
+msgid "jira.issue.description.content"
+msgstr ""
+
+msgid "jira.issue.summary"
+msgstr ""
+
+msgid "latest"
+msgstr ""
+
+msgid "latest deployment"
+msgstr ""
+
+msgid "latest version"
+msgstr ""
+
+msgid "leave %{group_name}"
+msgstr ""
+
+msgid "less than a minute"
+msgstr ""
+
+msgid "level: %{level}"
+msgstr ""
+
+msgid "limit of %{project_limit} reached"
+msgstr ""
+
+msgid "load it anyway"
+msgstr ""
+
+msgid "loading"
+msgstr ""
+
+msgid "locked by %{path_lock_user_name} %{created_at}"
+msgstr ""
+
+msgid "log in"
+msgstr ""
+
+msgid "manual"
+msgstr ""
+
+msgid "math|The math in this entry is taking too long to render and may not be displayed as expected. For performance reasons, math blocks are also limited to %{maxChars} characters. Consider splitting up large formulae, splitting math blocks among multiple entries, or using an image instead."
+msgstr ""
+
+msgid "math|There was an error rendering this math block"
+msgstr ""
+
+msgid "merge request"
+msgid_plural "merge requests"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "merged %{time_ago}"
+msgstr ""
+
+msgid "missing"
+msgstr ""
+
+msgid "most recent deployment"
+msgstr ""
+
+msgid "mrWidgetCommitsAdded|%{commitCount} and %{mergeCommitCount} will be added to %{targetBranch}."
+msgstr ""
+
+msgid "mrWidgetCommitsAdded|%{commitCount} will be added to %{targetBranch}."
+msgstr ""
+
+msgid "mrWidgetCommitsAdded|1 merge commit"
+msgstr ""
+
+msgid "mrWidgetNothingToMerge|Currently there are no changes in this merge request's source branch. Please push new commits or use a different branch."
+msgstr ""
+
+msgid "mrWidgetNothingToMerge|Interested parties can even contribute by pushing commits if they want to."
+msgstr ""
+
+msgid "mrWidgetNothingToMerge|Merge requests are a place to propose changes you have made to a project and discuss those changes with others."
+msgstr ""
+
+msgid "mrWidget| Please restore it or use a different %{missingBranchName} branch"
+msgstr ""
+
+msgid "mrWidget|%{link_start}Learn more about resolving conflicts%{link_end}"
+msgstr ""
+
+msgid "mrWidget|%{metricsLinkStart} Memory %{metricsLinkEnd} usage %{emphasisStart} decreased %{emphasisEnd} from %{memoryFrom}MB to %{memoryTo}MB"
+msgstr ""
+
+msgid "mrWidget|%{metricsLinkStart} Memory %{metricsLinkEnd} usage %{emphasisStart} increased %{emphasisEnd} from %{memoryFrom}MB to %{memoryTo}MB"
+msgstr ""
+
+msgid "mrWidget|%{metricsLinkStart} Memory %{metricsLinkEnd} usage is %{emphasisStart} unchanged %{emphasisEnd} at %{memoryFrom}MB"
+msgstr ""
+
+msgid "mrWidget|%{prefixToLinkStart}No pipeline%{prefixToLinkEnd} %{addPipelineLinkStart}Add the .gitlab-ci.yml file%{addPipelineLinkEnd} to create one."
+msgstr ""
+
+msgid "mrWidget|Added to the merge train by"
+msgstr ""
+
+msgid "mrWidget|Allows commits from members who can merge to the target branch"
+msgstr ""
+
+msgid "mrWidget|An error occurred while removing your approval."
+msgstr ""
+
+msgid "mrWidget|An error occurred while retrieving approval data for this merge request."
+msgstr ""
+
+msgid "mrWidget|An error occurred while submitting your approval."
+msgstr ""
+
+msgid "mrWidget|Approval password is invalid."
+msgstr ""
+
+msgid "mrWidget|Approve"
+msgstr ""
+
+msgid "mrWidget|Approve additionally"
+msgstr ""
+
+msgid "mrWidget|Approved by"
+msgstr ""
+
+msgid "mrWidget|Are you adding technical debt or code vulnerabilities?"
+msgstr ""
+
+msgid "mrWidget|Cancel automatic merge"
+msgstr ""
+
+msgid "mrWidget|Check out branch"
+msgstr ""
+
+msgid "mrWidget|Checking ability to merge automatically…"
+msgstr ""
+
+msgid "mrWidget|Cherry-pick"
+msgstr ""
+
+msgid "mrWidget|Cherry-pick this merge request in a new merge request"
+msgstr ""
+
+msgid "mrWidget|Closed"
+msgstr ""
+
+msgid "mrWidget|Closed by"
+msgstr ""
+
+msgid "mrWidget|Closes"
+msgstr ""
+
+msgid "mrWidget|Create an issue to resolve them later"
+msgstr ""
+
+msgid "mrWidget|Delete source branch"
+msgstr ""
+
+msgid "mrWidget|Deployment statistics are not available currently"
+msgstr ""
+
+msgid "mrWidget|Did not close"
+msgstr ""
+
+msgid "mrWidget|Email patches"
+msgstr ""
+
+msgid "mrWidget|Failed to load deployment statistics"
+msgstr ""
+
+msgid "mrWidget|Fast-forward merge is not possible. To merge this request, first rebase locally."
+msgstr ""
+
+msgid "mrWidget|Fork merge requests do not create merge request pipelines which validate a post merge result"
+msgstr ""
+
+msgid "mrWidget|If the %{branch} branch exists in your local repository, you can merge this merge request manually using the"
+msgstr ""
+
+msgid "mrWidget|If the %{missingBranchName} branch exists in your local repository, you can merge this merge request manually using the command line"
+msgstr ""
+
+msgid "mrWidget|In the merge train at position %{mergeTrainPosition}"
+msgstr ""
+
+msgid "mrWidget|Loading deployment statistics"
+msgstr ""
+
+msgid "mrWidget|Mentions"
+msgstr ""
+
+msgid "mrWidget|Merge"
+msgstr ""
+
+msgid "mrWidget|Merge failed."
+msgstr ""
+
+msgid "mrWidget|Merge failed: %{mergeError}. Please try again."
+msgstr ""
+
+msgid "mrWidget|Merge locally"
+msgstr ""
+
+msgid "mrWidget|Merge request approved."
+msgstr ""
+
+msgid "mrWidget|Merged by"
+msgstr ""
+
+msgid "mrWidget|More information"
+msgstr ""
+
+msgid "mrWidget|No approval required"
+msgstr ""
+
+msgid "mrWidget|No approval required; you can still approve"
+msgstr ""
+
+msgid "mrWidget|Open in Web IDE"
+msgstr ""
+
+msgid "mrWidget|Pipeline blocked. The pipeline for this merge request requires a manual action to proceed"
+msgstr ""
+
+msgid "mrWidget|Plain diff"
+msgstr ""
+
+msgid "mrWidget|Ready to be merged automatically. Ask someone with write access to this repository to merge this request"
+msgstr ""
+
+msgid "mrWidget|Refresh"
+msgstr ""
+
+msgid "mrWidget|Refresh now"
+msgstr ""
+
+msgid "mrWidget|Refreshing now"
+msgstr ""
+
+msgid "mrWidget|Remove from merge train"
+msgstr ""
+
+msgid "mrWidget|Request to merge"
+msgstr ""
+
+msgid "mrWidget|Resolve WIP status"
+msgstr ""
+
+msgid "mrWidget|Resolve conflicts"
+msgstr ""
+
+msgid "mrWidget|Resolve these conflicts or ask someone with write access to this repository to merge it locally"
+msgstr ""
+
+msgid "mrWidget|Revert"
+msgstr ""
+
+msgid "mrWidget|Revert this merge request in a new merge request"
+msgstr ""
+
+msgid "mrWidget|Revoke approval"
+msgstr ""
+
+msgid "mrWidget|Set by"
+msgstr ""
+
+msgid "mrWidget|The changes were merged into"
+msgstr ""
+
+msgid "mrWidget|The changes were not merged into"
+msgstr ""
+
+msgid "mrWidget|The changes will be merged into"
+msgstr ""
+
+msgid "mrWidget|The pipeline for this merge request failed. Please retry the job or push a new commit to fix the failure"
+msgstr ""
+
+msgid "mrWidget|The source branch HEAD has recently changed. Please reload the page and review the changes before merging"
+msgstr ""
+
+msgid "mrWidget|The source branch has been deleted"
+msgstr ""
+
+msgid "mrWidget|The source branch is %{commitsBehindLinkStart}%{commitsBehind}%{commitsBehindLinkEnd} the target branch"
+msgstr ""
+
+msgid "mrWidget|The source branch is being deleted"
+msgstr ""
+
+msgid "mrWidget|The source branch will be deleted"
+msgstr ""
+
+msgid "mrWidget|The source branch will not be deleted"
+msgstr ""
+
+msgid "mrWidget|There are merge conflicts"
+msgstr ""
+
+msgid "mrWidget|There are unresolved threads. Please resolve these threads"
+msgstr ""
+
+msgid "mrWidget|This feature merges changes from the target branch to the source branch. You cannot use this feature since the source branch is protected."
+msgstr ""
+
+msgid "mrWidget|This merge request failed to be merged automatically"
+msgstr ""
+
+msgid "mrWidget|This merge request is in the process of being merged"
+msgstr ""
+
+msgid "mrWidget|This merge request will be added to the merge train when pipeline %{linkStart}#%{pipelineId}%{linkEnd} succeeds."
+msgstr ""
+
+msgid "mrWidget|This merge request will start a merge train when pipeline %{linkStart}#%{pipelineId}%{linkEnd} succeeds."
+msgstr ""
+
+msgid "mrWidget|This project is archived, write access has been disabled"
+msgstr ""
+
+msgid "mrWidget|To approve this merge request, please enter your password. This project requires all approvals to be authenticated."
+msgstr ""
+
+msgid "mrWidget|Use %{linkStart}CI pipelines to test your code%{linkEnd}, simply add a GitLab CI configuration file to your project. It only takes a minute to make your code more secure and robust."
+msgstr ""
+
+msgid "mrWidget|When this merge request is ready, remove the WIP: prefix from the title to allow it to be merged"
+msgstr ""
+
+msgid "mrWidget|You are not allowed to edit this project directly. Please fork to make changes."
+msgstr ""
+
+msgid "mrWidget|You can delete the source branch now"
+msgstr ""
+
+msgid "mrWidget|You can merge this merge request manually using the"
+msgstr ""
+
+msgid "mrWidget|Your password"
+msgstr ""
+
+msgid "mrWidget|branch does not exist."
+msgstr ""
+
+msgid "mrWidget|command line"
+msgstr ""
+
+msgid "mrWidget|into"
+msgstr ""
+
+msgid "mrWidget|to be added to the merge train when the pipeline succeeds"
+msgstr ""
+
+msgid "mrWidget|to be merged automatically when the pipeline succeeds"
+msgstr ""
+
+msgid "mrWidget|to start a merge train when the pipeline succeeds"
+msgstr ""
+
+msgid "must be greater than start date"
+msgstr ""
+
+msgid "must contain only valid frameworks"
+msgstr ""
+
+msgid "my-awesome-group"
+msgstr ""
+
+msgid "n/a"
+msgstr ""
+
+msgid "need attention"
+msgstr ""
+
+msgid "needs to be between 10 minutes and 1 month"
+msgstr ""
+
+msgid "never"
+msgstr ""
+
+msgid "never expires"
+msgstr ""
+
+msgid "new merge request"
+msgstr ""
+
+msgid "no contributions"
+msgstr ""
+
+msgid "no expiration"
+msgstr ""
+
+msgid "no one can merge"
+msgstr ""
+
+msgid "none"
+msgstr ""
+
+msgid "not found"
+msgstr ""
+
+msgid "notification emails"
+msgstr ""
+
+msgid "nounSeries|%{firstItem} and %{lastItem}"
+msgstr ""
+
+msgid "nounSeries|%{item}"
+msgstr ""
+
+msgid "nounSeries|%{item}, %{nextItem}"
+msgstr ""
+
+msgid "nounSeries|%{item}, and %{lastItem}"
+msgstr ""
+
+msgid "on track"
+msgstr ""
+
+msgid "open issue"
+msgstr ""
+
+msgid "opened %{timeAgoString} by %{user}"
+msgstr ""
+
+msgid "opened %{timeAgo}"
+msgstr ""
+
+msgid "or"
+msgstr ""
+
+msgid "out of %d total test"
+msgid_plural "out of %d total tests"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "parent"
+msgid_plural "parents"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "password"
+msgstr ""
+
+msgid "pending comment"
+msgstr ""
+
+msgid "pending removal"
+msgstr ""
+
+msgid "per day"
+msgstr ""
+
+msgid "personal access token"
+msgstr ""
+
+msgid "personal access tokens"
+msgstr ""
+
+msgid "pipeline"
+msgstr ""
+
+msgid "pod_name can contain only lowercase letters, digits, '-', and '.' and must start and end with an alphanumeric character"
+msgstr ""
+
+msgid "pod_name cannot be larger than %{max_length} chars"
+msgstr ""
+
+msgid "point"
+msgid_plural "points"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "private"
+msgstr ""
+
+msgid "private key does not match certificate."
+msgstr ""
+
+msgid "processing"
+msgstr ""
+
+msgid "project"
+msgid_plural "projects"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "project access token"
+msgstr ""
+
+msgid "project access tokens"
+msgstr ""
+
+msgid "project avatar"
+msgstr ""
+
+msgid "projects"
+msgstr ""
+
+msgid "push to your repository, create pipelines, create issues or add comments. To reduce storage capacity, delete unused repositories, artifacts, wikis, issues, and pipelines."
+msgstr ""
+
+msgid "quick actions"
+msgstr ""
+
+msgid "register"
+msgstr ""
+
+msgid "relates to"
+msgstr ""
+
+msgid "released %{time}"
+msgstr ""
+
+msgid "remaining"
+msgstr ""
+
+msgid "remove"
+msgstr ""
+
+msgid "remove due date"
+msgstr ""
+
+msgid "remove weight"
+msgstr ""
+
+msgid "removed a Zoom call from this issue"
+msgstr ""
+
+msgid "rendered diff"
+msgstr ""
+
+msgid "reply"
+msgid_plural "replies"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "reset it."
+msgstr ""
+
+msgid "resolved the corresponding error and closed the issue."
+msgstr ""
+
+msgid "revised"
+msgstr ""
+
+msgid "satisfied"
+msgstr ""
+
+msgid "score"
+msgstr ""
+
+msgid "security Reports|There was an error creating the merge request"
+msgstr ""
+
+msgid "settings saved, but not activated"
+msgstr ""
+
+msgid "severity|Critical"
+msgstr ""
+
+msgid "severity|High"
+msgstr ""
+
+msgid "severity|Info"
+msgstr ""
+
+msgid "severity|Low"
+msgstr ""
+
+msgid "severity|Medium"
+msgstr ""
+
+msgid "severity|None"
+msgstr ""
+
+msgid "severity|Unknown"
+msgstr ""
+
+msgid "should be an array of %{object_name} objects"
+msgstr ""
+
+msgid "should be greater than or equal to %{access} inherited membership from group %{group_name}"
+msgstr ""
+
+msgid "show %{count} more"
+msgstr ""
+
+msgid "show fewer"
+msgstr ""
+
+msgid "show less"
+msgstr ""
+
+msgid "sign in"
+msgstr ""
+
+msgid "sort:"
+msgstr ""
+
+msgid "source"
+msgstr ""
+
+msgid "source diff"
+msgstr ""
+
+msgid "specified top is not part of the tree"
+msgstr ""
+
+msgid "spendCommand|%{slash_command} will update the sum of the time spent."
+msgstr ""
+
+msgid "started"
+msgstr ""
+
+msgid "started a discussion on %{design_link}"
+msgstr ""
+
+msgid "started on %{timebox_start_date}"
+msgstr ""
+
+msgid "starts on %{timebox_start_date}"
+msgstr ""
+
+msgid "stuck"
+msgstr ""
+
+msgid "success"
+msgstr ""
+
+msgid "suggestPipeline|1/2: Choose a template"
+msgstr ""
+
+msgid "suggestPipeline|2/2: Commit your changes"
+msgstr ""
+
+msgid "suggestPipeline|Choose %{boldStart}Code Quality%{boldEnd} to add a pipeline that tests the quality of your code."
+msgstr ""
+
+msgid "suggestPipeline|The template is ready! You can now commit it to create your first pipeline."
+msgstr ""
+
+msgid "suggestPipeline|We’re adding a GitLab CI configuration file to add a pipeline to the project. You could create it manually, but we recommend that you start with a GitLab template that works out of the box."
+msgstr ""
+
+msgid "syntax is correct"
+msgstr ""
+
+msgid "syntax is incorrect"
+msgstr ""
+
+msgid "tag name"
+msgstr ""
+
+msgid "the following issue(s)"
+msgstr ""
+
+msgid "this document"
+msgstr ""
+
+msgid "to help your contributors communicate effectively!"
+msgstr ""
+
+msgid "to list"
+msgstr ""
+
+msgid "toggle collapse"
+msgstr ""
+
+msgid "toggle dropdown"
+msgstr ""
+
+msgid "triggered"
+msgstr ""
+
+msgid "unicode domains should use IDNA encoding"
+msgstr ""
+
+msgid "updated"
+msgstr ""
+
+msgid "updated %{timeAgo}"
+msgstr ""
+
+msgid "updated %{time_ago}"
+msgstr ""
+
+msgid "uploaded"
+msgstr ""
+
+msgid "uploads"
+msgstr ""
+
+msgid "user avatar"
+msgstr ""
+
+msgid "username"
+msgstr ""
+
+msgid "uses Kubernetes clusters to deploy your code!"
+msgstr ""
+
+msgid "v%{version} published %{timeAgo}"
+msgstr ""
+
+msgid "verify ownership"
+msgstr ""
+
+msgid "version %{versionIndex}"
+msgstr ""
+
+msgid "via %{closed_via}"
+msgstr ""
+
+msgid "via merge request %{link}"
+msgstr ""
+
+msgid "view it on GitLab"
+msgstr ""
+
+msgid "view the blob"
+msgstr ""
+
+msgid "view the source"
+msgstr ""
+
+msgid "vulnerability|Add a comment"
+msgstr ""
+
+msgid "vulnerability|Add a comment or reason for dismissal"
+msgstr ""
+
+msgid "vulnerability|Add comment"
+msgstr ""
+
+msgid "vulnerability|Add comment & dismiss"
+msgstr ""
+
+msgid "vulnerability|Dismiss vulnerability"
+msgstr ""
+
+msgid "vulnerability|Save comment"
+msgstr ""
+
+msgid "vulnerability|Undo dismiss"
+msgstr ""
+
+msgid "vulnerability|dismissed"
+msgstr ""
+
+msgid "wiki page"
+msgstr ""
+
+msgid "will be released %{time}"
+msgstr ""
+
+msgid "with %{additions} additions, %{deletions} deletions."
+msgstr ""
+
+msgid "with expiry changing from %{old_expiry} to %{new_expiry}"
+msgstr ""
+
+msgid "with expiry remaining unchanged at %{old_expiry}"
+msgstr ""
+
+msgid "yaml invalid"
+msgstr ""
+
diff --git a/locale/az_AZ/gitlab.po b/locale/az_AZ/gitlab.po
index 4a0b7d37c98..d062094530c 100644
--- a/locale/az_AZ/gitlab.po
+++ b/locale/az_AZ/gitlab.po
@@ -2,7 +2,7 @@ msgid ""
msgstr ""
"Project-Id-Version: gitlab-ee\n"
"Report-Msgid-Bugs-To: \n"
-"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Last-Translator: \n"
"Language-Team: Azerbaijani\n"
"Language: az_AZ\n"
"MIME-Version: 1.0\n"
@@ -10,9 +10,11 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Crowdin-Project: gitlab-ee\n"
+"X-Crowdin-Project-ID: 288872\n"
"X-Crowdin-Language: az\n"
"X-Crowdin-File: /master/locale/gitlab.pot\n"
-"PO-Revision-Date: 2020-06-08 15:11\n"
+"X-Crowdin-File-ID: 6\n"
+"PO-Revision-Date: 2020-07-02 01:21\n"
msgid " %{start} to %{end}"
msgstr ""
@@ -197,6 +199,11 @@ msgid_plural "%d metrics"
msgstr[0] ""
msgstr[1] ""
+msgid "%d milestone"
+msgid_plural "%d milestones"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "%d minute"
msgid_plural "%d minutes"
msgstr[0] ""
@@ -242,11 +249,6 @@ msgid_plural "%d unresolved threads"
msgstr[0] ""
msgstr[1] ""
-msgid "%d url scanned"
-msgid_plural "%d urls scanned"
-msgstr[0] ""
-msgstr[1] ""
-
msgid "%d vulnerability"
msgid_plural "%d vulnerabilities"
msgstr[0] ""
@@ -324,9 +326,15 @@ msgstr[1] ""
msgid "%{count} related %{pluralized_subject}: %{links}"
msgstr ""
+msgid "%{dashboard_path} could not be found."
+msgstr ""
+
msgid "%{days} days until tags are automatically removed"
msgstr ""
+msgid "%{deployLinkStart}Use a template to deploy to ECS%{deployLinkEnd}, or use a docker image to %{commandsLinkStart}run AWS commands in GitLab CI/CD%{commandsLinkEnd}."
+msgstr ""
+
msgid "%{description}- Sentry event: %{errorUrl}- First seen: %{firstSeen}- Last seen: %{lastSeen} %{countLabel}: %{count}%{userCountLabel}: %{userCount}"
msgstr ""
@@ -369,12 +377,18 @@ msgstr ""
msgid "%{host} sign-in from new location"
msgstr ""
-msgid "%{icon}You are about to add %{usersTag} people to the discussion. Proceed with caution."
+msgid "%{icon}You are about to add %{usersTag} people to the discussion. They will all receive a notification."
+msgstr ""
+
+msgid "%{integrations_link_start}Integrations%{link_end} enable you to make third-party applications part of your GitLab workflow. If the available integrations don't meet your needs, consider using a %{webhooks_link_start}webhook%{link_end}."
msgstr ""
msgid "%{issuableType} will be removed! Are you sure?"
msgstr ""
+msgid "%{issuesCount} issues in this group"
+msgstr ""
+
msgid "%{issuesSize} issues"
msgstr ""
@@ -384,7 +398,7 @@ msgstr ""
msgid "%{labelStart}Class:%{labelEnd} %{class}"
msgstr ""
-msgid "%{labelStart}Confidence:%{labelEnd} %{confidence}"
+msgid "%{labelStart}Evidence:%{labelEnd} %{evidence}"
msgstr ""
msgid "%{labelStart}File:%{labelEnd} %{file}"
@@ -402,6 +416,9 @@ msgstr ""
msgid "%{labelStart}Report Type:%{labelEnd} %{reportType}"
msgstr ""
+msgid "%{labelStart}Scanner:%{labelEnd} %{scanner}"
+msgstr ""
+
msgid "%{labelStart}Severity:%{labelEnd} %{severity}"
msgstr ""
@@ -426,9 +443,6 @@ msgstr ""
msgid "%{link_start}Read more%{link_end} about role permissions"
msgstr ""
-msgid "%{link} can be used for binding events when something is happening within the project."
-msgstr ""
-
msgid "%{listToShow}, and %{awardsListLength} more."
msgstr ""
@@ -462,6 +476,9 @@ msgstr ""
msgid "%{name} found %{resultsString}"
msgstr ""
+msgid "%{name} is already being used for another emoji"
+msgstr ""
+
msgid "%{name} is scheduled for %{action}"
msgstr ""
@@ -509,6 +526,9 @@ msgstr ""
msgid "%{primary} (%{secondary})"
msgstr ""
+msgid "%{ref} cannot be added: %{error}"
+msgstr ""
+
msgid "%{releases} release"
msgid_plural "%{releases} releases"
msgstr[0] ""
@@ -517,13 +537,37 @@ msgstr[1] ""
msgid "%{remaining_approvals} left"
msgstr ""
-msgid "%{retryButtonStart}Try again%{retryButtonEnd} or %{newFileButtonStart}attach a new file%{newFileButtonEnd}"
+msgid "%{reportType} %{status} detected %{critical} critical and %{high} high severity vulnerabilities out of %{total}."
+msgstr ""
+
+msgid "%{reportType} %{status} detected %{critical} critical and %{high} high severity vulnerabilities."
+msgstr ""
+
+msgid "%{reportType} %{status} detected %{critical} critical severity vulnerabilities out of %{total}."
+msgstr ""
+
+msgid "%{reportType} %{status} detected %{critical} critical severity vulnerability."
+msgid_plural "%{reportType} %{status} detected %{critical} critical severity vulnerabilities."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{reportType} %{status} detected %{high} high severity vulnerabilities out of %{total}."
msgstr ""
-msgid "%{screenreaderOnlyStart}Keyboard shorcuts%{screenreaderOnlyEnd} Disabled"
+msgid "%{reportType} %{status} detected %{high} high severity vulnerability."
+msgid_plural "%{reportType} %{status} detected %{high} high severity vulnerabilities."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{reportType} %{status} detected %{other} vulnerability."
+msgid_plural "%{reportType} %{status} detected %{other} vulnerabilities."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{reportType} %{status} detected no new vulnerabilities."
msgstr ""
-msgid "%{screenreaderOnlyStart}Keyboard shorcuts%{screenreaderOnlyEnd} Enabled"
+msgid "%{retryButtonStart}Try again%{retryButtonEnd} or %{newFileButtonStart}attach a new file%{newFileButtonEnd}"
msgstr ""
msgid "%{service_title} %{message}."
@@ -620,6 +664,12 @@ msgstr ""
msgid "%{token}..."
msgstr ""
+msgid "%{totalCpu} (%{freeSpacePercentage}%{percentSymbol} free)"
+msgstr ""
+
+msgid "%{totalMemory} (%{freeSpacePercentage}%{percentSymbol} free)"
+msgstr ""
+
msgid "%{totalWeight} total weight"
msgstr ""
@@ -650,6 +700,12 @@ msgstr ""
msgid "%{verb} %{time_spent_value} spent time."
msgstr ""
+msgid "%{webhooks_link_start}%{webhook_type}%{link_end} enable you to send notifications to web applications in response to events in a group or project."
+msgstr ""
+
+msgid "%{webhooks_link_start}%{webhook_type}%{link_end} enable you to send notifications to web applications in response to events in a group or project. We recommend using an %{integrations_link_start}integration%{link_end} in preference to a webhook."
+msgstr ""
+
msgid "'%{level}' is not a valid visibility level"
msgstr ""
@@ -676,9 +732,6 @@ msgstr ""
msgid "(No changes)"
msgstr ""
-msgid "(Show all)"
-msgstr ""
-
msgid "(check progress)"
msgstr ""
@@ -745,6 +798,9 @@ msgstr ""
msgid "0 for unlimited"
msgstr ""
+msgid "0 for unlimited, only effective with remote storage enabled."
+msgstr ""
+
msgid "1 %{type} addition"
msgid_plural "%{count} %{type} additions"
msgstr[0] ""
@@ -892,6 +948,12 @@ msgstr ""
msgid "<code>\"johnsmith@example.com\": \"johnsmith@example.com\"</code> will add \"By <a href=\"#\">johnsmith@example.com</a>\" to all issues and comments originally created by johnsmith@example.com. By default, the email address or username is masked to ensure the user's privacy. Use this option if you want to show the full email address."
msgstr ""
+msgid "<code>Masked</code> to prevent the values from being displayed in job logs (must match certain regexp requirements)."
+msgstr ""
+
+msgid "<code>Protected</code> to expose them to protected branches or tags only."
+msgstr ""
+
msgid "<namespace / project>"
msgstr ""
@@ -919,9 +981,6 @@ msgstr ""
msgid "A .NET Core console application template, customizable for any .NET Core project"
msgstr ""
-msgid "A DAG must have two dependent jobs to be visualized on this tab."
-msgstr ""
-
msgid "A GitBook site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
msgstr ""
@@ -952,6 +1011,12 @@ msgstr ""
msgid "A deleted user"
msgstr ""
+msgid "A file has been changed."
+msgstr ""
+
+msgid "A file was not found."
+msgstr ""
+
msgid "A file with '%{file_name}' already exists in %{branch} branch"
msgstr ""
@@ -1018,7 +1083,7 @@ msgstr ""
msgid "A subscription will trigger a new pipeline on the default branch of this project when a pipeline successfully completes for a new tag on the %{default_branch_docs} of the subscribed project."
msgstr ""
-msgid "A terraform report was generated in your pipelines."
+msgid "A suggestion is not applicable."
msgstr ""
msgid "A user with write access to the source branch selected this option"
@@ -1189,6 +1254,9 @@ msgstr ""
msgid "Action to take when receiving an alert."
msgstr ""
+msgid "Actions"
+msgstr ""
+
msgid "Activate"
msgstr ""
@@ -1245,6 +1313,9 @@ msgstr ""
msgid "Add LICENSE"
msgstr ""
+msgid "Add New Node"
+msgstr ""
+
msgid "Add README"
msgstr ""
@@ -1320,6 +1391,9 @@ msgstr ""
msgid "Add approval rule"
msgstr ""
+msgid "Add approvers"
+msgstr ""
+
msgid "Add bold text"
msgstr ""
@@ -1377,6 +1451,9 @@ msgstr ""
msgid "Add strikethrough text"
msgstr ""
+msgid "Add suggestion to batch"
+msgstr ""
+
msgid "Add system hook"
msgstr ""
@@ -1476,6 +1553,9 @@ msgstr ""
msgid "Admin Note"
msgstr ""
+msgid "Admin Notifications"
+msgstr ""
+
msgid "Admin Overview"
msgstr ""
@@ -1587,6 +1667,9 @@ msgstr ""
msgid "AdminSettings|Integrations configured here will automatically apply to all projects on this instance."
msgstr ""
+msgid "AdminSettings|Moved to integrations"
+msgstr ""
+
msgid "AdminSettings|No required pipeline"
msgstr ""
@@ -1817,12 +1900,18 @@ msgid_plural "Alerts"
msgstr[0] ""
msgstr[1] ""
+msgid "AlertManagement| assign yourself"
+msgstr ""
+
msgid "AlertManagement|Acknowledged"
msgstr ""
msgid "AlertManagement|Alert"
msgstr ""
+msgid "AlertManagement|Alert assignee(s): %{assignees}"
+msgstr ""
+
msgid "AlertManagement|Alert detail"
msgstr ""
@@ -1838,9 +1927,15 @@ msgstr ""
msgid "AlertManagement|All alerts"
msgstr ""
+msgid "AlertManagement|Assign To"
+msgstr ""
+
msgid "AlertManagement|Assign status"
msgstr ""
+msgid "AlertManagement|Assignee"
+msgstr ""
+
msgid "AlertManagement|Assignees"
msgstr ""
@@ -1859,9 +1954,6 @@ msgstr ""
msgid "AlertManagement|Edit"
msgstr ""
-msgid "AlertManagement|End time"
-msgstr ""
-
msgid "AlertManagement|Events"
msgstr ""
@@ -1880,10 +1972,13 @@ msgstr ""
msgid "AlertManagement|More information"
msgstr ""
+msgid "AlertManagement|No Matching Results"
+msgstr ""
+
msgid "AlertManagement|No alert data to display."
msgstr ""
-msgid "AlertManagement|No alerts available to display. If you think you're seeing this message in error, refresh the page."
+msgid "AlertManagement|No alerts available to display. See %{linkStart}enabling alert management%{linkEnd} for more information on adding alerts to the list."
msgstr ""
msgid "AlertManagement|No alerts to display."
@@ -1892,6 +1987,9 @@ msgstr ""
msgid "AlertManagement|None"
msgstr ""
+msgid "AlertManagement|None -"
+msgstr ""
+
msgid "AlertManagement|Open"
msgstr ""
@@ -1928,9 +2026,18 @@ msgstr ""
msgid "AlertManagement|There was an error displaying the alerts. Confirm your endpoint's configuration details to ensure alerts appear."
msgstr ""
+msgid "AlertManagement|There was an error while updating the assignee(s) list. Please try again."
+msgstr ""
+
+msgid "AlertManagement|There was an error while updating the assignee(s) of the alert. Please try again."
+msgstr ""
+
msgid "AlertManagement|There was an error while updating the status of the alert. Please try again."
msgstr ""
+msgid "AlertManagement|This assignee cannot be assigned to this alert."
+msgstr ""
+
msgid "AlertManagement|Tool"
msgstr ""
@@ -2078,6 +2185,9 @@ msgstr ""
msgid "Allow users to request access (if visibility is public or internal)"
msgstr ""
+msgid "Allowed Geo IP"
+msgstr ""
+
msgid "Allowed email domain restriction only permitted for top-level groups"
msgstr ""
@@ -2111,6 +2221,9 @@ msgstr ""
msgid "Amazon Web Services"
msgstr ""
+msgid "Amazon Web Services Logo"
+msgstr ""
+
msgid "Amazon authentication is not %{link_start}correctly configured%{link_end}. Ask your GitLab administrator if you want to use this service."
msgstr ""
@@ -2168,10 +2281,16 @@ msgstr ""
msgid "An error occurred when updating the issue weight"
msgstr ""
+msgid "An error occurred while acknowledging the notification. Refresh the page and try again."
+msgstr ""
+
+msgid "An error occurred while adding approvers"
+msgstr ""
+
msgid "An error occurred while adding formatted title for epic"
msgstr ""
-msgid "An error occurred while checking group path"
+msgid "An error occurred while checking group path. Please refresh and try again."
msgstr ""
msgid "An error occurred while committing your changes."
@@ -2315,7 +2434,7 @@ msgstr ""
msgid "An error occurred while loading milestones"
msgstr ""
-msgid "An error occurred while loading terraform report"
+msgid "An error occurred while loading project creation UI"
msgstr ""
msgid "An error occurred while loading the data. Please try again."
@@ -2369,6 +2488,9 @@ msgstr ""
msgid "An error occurred while rendering preview broadcast message"
msgstr ""
+msgid "An error occurred while rendering the editor"
+msgstr ""
+
msgid "An error occurred while reordering issues."
msgstr ""
@@ -2465,7 +2587,7 @@ msgstr ""
msgid "Analyze your dependencies for known vulnerabilities."
msgstr ""
-msgid "Analyze your source code and git history for secrets"
+msgid "Analyze your source code and git history for secrets."
msgstr ""
msgid "Analyze your source code for known vulnerabilities."
@@ -2579,12 +2701,18 @@ msgstr ""
msgid "Apply suggestion"
msgstr ""
+msgid "Apply suggestions"
+msgstr ""
+
msgid "Apply template"
msgstr ""
msgid "Apply this approval rule to any branch or a specific protected branch."
msgstr ""
+msgid "Applying"
+msgstr ""
+
msgid "Applying a template will replace the existing issue description. Any changes you have made will be lost."
msgstr ""
@@ -2597,12 +2725,18 @@ msgstr ""
msgid "Applying multiple commands"
msgstr ""
-msgid "Applying suggestion"
+msgid "Applying suggestion..."
+msgstr ""
+
+msgid "Applying suggestions..."
msgstr ""
msgid "Approval rules"
msgstr ""
+msgid "Approval rules reset to project defaults"
+msgstr ""
+
msgid "ApprovalRuleRemove|%d member"
msgid_plural "ApprovalRuleRemove|%d members"
msgstr[0] ""
@@ -2656,6 +2790,9 @@ msgstr ""
msgid "Approved"
msgstr ""
+msgid "Approved MRs"
+msgstr ""
+
msgid "Approved by: "
msgstr ""
@@ -2869,6 +3006,9 @@ msgstr ""
msgid "Assign"
msgstr ""
+msgid "Assign Iteration"
+msgstr ""
+
msgid "Assign custom color like #FF0000"
msgstr ""
@@ -2887,6 +3027,9 @@ msgstr ""
msgid "Assign to"
msgstr ""
+msgid "Assign to commenting user"
+msgstr ""
+
msgid "Assign yourself to these issues"
msgstr ""
@@ -2913,6 +3056,9 @@ msgid_plural "%d Assignees"
msgstr[0] ""
msgstr[1] ""
+msgid "Assignee has no permissions"
+msgstr ""
+
msgid "Assignee lists not available with your current license"
msgstr ""
@@ -2922,6 +3068,9 @@ msgstr ""
msgid "Assignee(s)"
msgstr ""
+msgid "Assignees"
+msgstr ""
+
msgid "Assigns %{assignee_users_sentence}."
msgstr ""
@@ -2957,6 +3106,9 @@ msgstr ""
msgid "Audit Events is a way to keep track of important events that happened in GitLab."
msgstr ""
+msgid "Audit Log"
+msgstr ""
+
msgid "AuditEvents|(removed)"
msgstr ""
@@ -2993,6 +3145,9 @@ msgstr ""
msgid "AuditLogs|IP Address"
msgstr ""
+msgid "AuditLogs|Member Events"
+msgstr ""
+
msgid "AuditLogs|No matching %{type} found."
msgstr ""
@@ -3275,9 +3430,6 @@ msgstr ""
msgid "Badges|e.g. %{exampleUrl}"
msgstr ""
-msgid "Badge|New"
-msgstr ""
-
msgid "Balsamiq file could not be loaded."
msgstr ""
@@ -3326,6 +3478,12 @@ msgstr ""
msgid "Below you will find all the groups that are public."
msgstr ""
+msgid "Beta"
+msgstr ""
+
+msgid "Bi-weekly code coverage"
+msgstr ""
+
msgid "Billing"
msgstr ""
@@ -3386,9 +3544,15 @@ msgstr ""
msgid "Bitbucket Server Import"
msgstr ""
+msgid "Bitbucket Server import"
+msgstr ""
+
msgid "Bitbucket import"
msgstr ""
+msgid "Blame"
+msgstr ""
+
msgid "Blocked"
msgstr ""
@@ -3401,9 +3565,6 @@ msgstr ""
msgid "Blog"
msgstr ""
-msgid "Blue helpers indicate an action to be taken."
-msgstr ""
-
msgid "Board name"
msgstr ""
@@ -3602,6 +3763,9 @@ msgstr ""
msgid "Broadcast Message was successfully updated."
msgstr ""
+msgid "Broadcast Messages"
+msgstr ""
+
msgid "Browse Directory"
msgstr ""
@@ -3656,6 +3820,9 @@ msgstr ""
msgid "By %{user_name}"
msgstr ""
+msgid "By URL"
+msgstr ""
+
msgid "By default GitLab sends emails in HTML and plain text formats so mail clients can choose what format to use. Disable this option if you only want to send emails in plain text format."
msgstr ""
@@ -3761,6 +3928,9 @@ msgstr ""
msgid "Can override approvers and approvals required per merge request"
msgstr ""
+msgid "Can't apply as this line has changed or the suggestion already matches its content."
+msgstr ""
+
msgid "Can't create snippet: %{err}"
msgstr ""
@@ -3947,9 +4117,6 @@ msgstr ""
msgid "Changes are still tracked. Useful for cluster/index migrations."
msgstr ""
-msgid "Changes are unknown"
-msgstr ""
-
msgid "Changes suppressed. Click to show."
msgstr ""
@@ -4256,15 +4423,9 @@ msgstr ""
msgid "Choose what content you want to see on a group’s overview page"
msgstr ""
-msgid "Choose which groups you wish to synchronize to this secondary node"
-msgstr ""
-
msgid "Choose which repositories you want to connect and run CI/CD pipelines."
msgstr ""
-msgid "Choose which shards you wish to synchronize to this secondary node"
-msgstr ""
-
msgid "Choose your framework"
msgstr ""
@@ -4562,9 +4723,15 @@ msgstr ""
msgid "Cluster does not exist"
msgstr ""
+msgid "Cluster is required for Stages::ClusterEndpointInserter"
+msgstr ""
+
msgid "Cluster level"
msgstr ""
+msgid "Cluster type must be specificed for Stages::ClusterEndpointInserter"
+msgstr ""
+
msgid "ClusterIntegration| %{custom_domain_start}More information%{custom_domain_end}."
msgstr ""
@@ -4676,7 +4843,7 @@ msgstr ""
msgid "ClusterIntegration|Cert-Manager"
msgstr ""
-msgid "ClusterIntegration|Cert-Manager is a native Kubernetes certificate management controller that helps with issuing certificates. Installing Cert-Manager on your cluster will issue a certificate by %{letsEncrypt} and ensure that certificates are valid and up-to-date."
+msgid "ClusterIntegration|Cert-Manager is a native Kubernetes certificate management controller that helps with issuing certificates. Installing Cert-Manager on your cluster will issue a certificate by %{linkStart}Let's Encrypt%{linkEnd} and ensure that certificates are valid and up-to-date."
msgstr ""
msgid "ClusterIntegration|Certificate Authority bundle (PEM format)"
@@ -4703,9 +4870,6 @@ msgstr ""
msgid "ClusterIntegration|Clear the local cache of namespace and service accounts. This is necessary if your integration has become out of sync. The cache is repopulated during the next CI job that requires namespace and service accounts."
msgstr ""
-msgid "ClusterIntegration|Cloud Run"
-msgstr ""
-
msgid "ClusterIntegration|Cluster being created"
msgstr ""
@@ -4790,7 +4954,7 @@ msgstr ""
msgid "ClusterIntegration|Crossplane"
msgstr ""
-msgid "ClusterIntegration|Crossplane enables declarative provisioning of managed services from your cloud of choice using %{codeStart}kubectl%{codeEnd} or %{gitlabIntegrationLink}. Crossplane runs inside your Kubernetes cluster and supports secure connectivity and secrets management between app containers and the cloud services they depend on."
+msgid "ClusterIntegration|Crossplane enables declarative provisioning of managed services from your cloud of choice using %{codeStart}kubectl%{codeEnd} or %{linkStart}GitLab Integration%{linkEnd}. Crossplane runs inside your Kubernetes cluster and supports secure connectivity and secrets management between app containers and the cloud services they depend on."
msgstr ""
msgid "ClusterIntegration|Deletes all GitLab resources attached to this cluster during removal"
@@ -4874,9 +5038,6 @@ msgstr ""
msgid "ClusterIntegration|GitLab-managed cluster"
msgstr ""
-msgid "ClusterIntegration|Gitlab Integration"
-msgstr ""
-
msgid "ClusterIntegration|Global default"
msgstr ""
@@ -4922,7 +5083,7 @@ msgstr ""
msgid "ClusterIntegration|Ingress gives you a way to route requests to services based on the request host or path, centralizing a number of services into a single entrypoint."
msgstr ""
-msgid "ClusterIntegration|Installing Ingress may incur additional costs. Learn more about %{pricingLink}."
+msgid "ClusterIntegration|Installing Ingress may incur additional costs. Learn more about %{linkStart}pricing%{linkEnd}."
msgstr ""
msgid "ClusterIntegration|Instance cluster"
@@ -4937,7 +5098,7 @@ msgstr ""
msgid "ClusterIntegration|Issuer Email"
msgstr ""
-msgid "ClusterIntegration|Issuers represent a certificate authority. You must provide an email address for your Issuer. "
+msgid "ClusterIntegration|Issuers represent a certificate authority. You must provide an email address for your Issuer."
msgstr ""
msgid "ClusterIntegration|Jupyter Hostname"
@@ -5009,9 +5170,6 @@ msgstr ""
msgid "ClusterIntegration|Learn more about instance Kubernetes clusters"
msgstr ""
-msgid "ClusterIntegration|Let's Encrypt"
-msgstr ""
-
msgid "ClusterIntegration|Loading IAM Roles"
msgstr ""
@@ -5123,7 +5281,7 @@ msgstr ""
msgid "ClusterIntegration|Prometheus"
msgstr ""
-msgid "ClusterIntegration|Prometheus is an open-source monitoring system with %{gitlabIntegrationLink} to monitor deployed applications."
+msgid "ClusterIntegration|Prometheus is an open-source monitoring system with %{linkStart}GitLab Integration%{linkEnd} to monitor deployed applications."
msgstr ""
msgid "ClusterIntegration|Provider details"
@@ -5279,10 +5437,10 @@ msgstr ""
msgid "ClusterIntegration|Select zone to choose machine type"
msgstr ""
-msgid "ClusterIntegration|Send Cilium Logs"
+msgid "ClusterIntegration|Send Container Network Policies Logs"
msgstr ""
-msgid "ClusterIntegration|Send ModSecurity Logs"
+msgid "ClusterIntegration|Send Web Application Firewall Logs"
msgstr ""
msgid "ClusterIntegration|Service Token"
@@ -5450,15 +5608,12 @@ msgstr ""
msgid "ClusterIntegration|documentation"
msgstr ""
-msgid "ClusterIntegration|installed via %{installed_via}"
+msgid "ClusterIntegration|installed via %{linkStart}Cloud Run%{linkEnd}"
msgstr ""
msgid "ClusterIntegration|meets the requirements"
msgstr ""
-msgid "ClusterIntegration|pricing"
-msgstr ""
-
msgid "ClusterIntegration|sign up"
msgstr ""
@@ -5495,6 +5650,15 @@ msgstr ""
msgid "Code"
msgstr ""
+msgid "Code Coverage: %{coveragePercentage}%{percentSymbol}"
+msgstr ""
+
+msgid "Code Coverage| Empty code coverage data"
+msgstr ""
+
+msgid "Code Coverage|Couldn't fetch the code coverage data"
+msgstr ""
+
msgid "Code Owners"
msgstr ""
@@ -5549,7 +5713,7 @@ msgstr ""
msgid "Collapse approvers"
msgstr ""
-msgid "Collapse child epics"
+msgid "Collapse milestones"
msgstr ""
msgid "Collapse replies"
@@ -5567,6 +5731,9 @@ msgstr ""
msgid "Coming soon"
msgstr ""
+msgid "Comma-separated, e.g. '1.1.1.1, 2.2.2.0/24'"
+msgstr ""
+
msgid "Command"
msgstr ""
@@ -5737,6 +5904,12 @@ msgstr ""
msgid "Compliance framework (optional)"
msgstr ""
+msgid "Compliance frameworks"
+msgstr ""
+
+msgid "ComplianceDashboard|created by:"
+msgstr ""
+
msgid "ComplianceFramework|GDPR"
msgstr ""
@@ -5875,6 +6048,9 @@ msgstr ""
msgid "Connection timed out"
msgstr ""
+msgid "Connection timeout"
+msgstr ""
+
msgid "Contact sales to upgrade"
msgstr ""
@@ -5930,9 +6106,6 @@ msgstr ""
msgid "ContainerRegistry|CLI Commands"
msgstr ""
-msgid "ContainerRegistry|Compressed Size"
-msgstr ""
-
msgid "ContainerRegistry|Container Registry"
msgstr ""
@@ -5948,6 +6121,9 @@ msgstr ""
msgid "ContainerRegistry|Copy push command"
msgstr ""
+msgid "ContainerRegistry|Delete selected"
+msgstr ""
+
msgid "ContainerRegistry|Docker connection error"
msgstr ""
@@ -5981,16 +6157,16 @@ msgstr ""
msgid "ContainerRegistry|If you are not already logged in, you need to authenticate to the Container Registry by using your GitLab username and password. If you have %{twofaDocLinkStart}Two-Factor Authentication%{twofaDocLinkEnd} enabled, use a %{personalAccessTokensDocLinkStart}Personal Access Token%{personalAccessTokensDocLinkEnd} instead of a password."
msgstr ""
-msgid "ContainerRegistry|Image ID"
+msgid "ContainerRegistry|Image ID: %{imageId}"
msgstr ""
msgid "ContainerRegistry|Image Repositories"
msgstr ""
-msgid "ContainerRegistry|Keep and protect the images that matter most."
+msgid "ContainerRegistry|Image tags"
msgstr ""
-msgid "ContainerRegistry|Last Updated"
+msgid "ContainerRegistry|Keep and protect the images that matter most."
msgstr ""
msgid "ContainerRegistry|Login"
@@ -6005,6 +6181,9 @@ msgstr ""
msgid "ContainerRegistry|Please contact your administrator."
msgstr ""
+msgid "ContainerRegistry|Published %{timeInfo}"
+msgstr ""
+
msgid "ContainerRegistry|Push an image"
msgstr ""
@@ -6020,9 +6199,6 @@ msgstr ""
msgid "ContainerRegistry|Remove repository"
msgstr ""
-msgid "ContainerRegistry|Remove selected tags"
-msgstr ""
-
msgid "ContainerRegistry|Remove tag"
msgid_plural "ContainerRegistry|Remove tags"
msgstr[0] ""
@@ -6052,9 +6228,6 @@ msgstr ""
msgid "ContainerRegistry|Sorry, your filter produced no results."
msgstr ""
-msgid "ContainerRegistry|Tag"
-msgstr ""
-
msgid "ContainerRegistry|Tag expiration policy"
msgstr ""
@@ -6199,21 +6372,6 @@ msgstr ""
msgid "Control the display of third party offers."
msgstr ""
-msgid "Control the maximum concurrency of LFS/attachment backfill for this secondary node"
-msgstr ""
-
-msgid "Control the maximum concurrency of container repository operations for this Geo node"
-msgstr ""
-
-msgid "Control the maximum concurrency of repository backfill for this secondary node"
-msgstr ""
-
-msgid "Control the maximum concurrency of verification operations for this Geo node"
-msgstr ""
-
-msgid "Control the minimum interval in days that a repository should be reverified for this primary node"
-msgstr ""
-
msgid "Cookie domain"
msgstr ""
@@ -6355,6 +6513,9 @@ msgstr ""
msgid "Could not find design."
msgstr ""
+msgid "Could not find iteration"
+msgstr ""
+
msgid "Could not remove the trigger."
msgstr ""
@@ -6391,6 +6552,12 @@ msgstr ""
msgid "Coverage"
msgstr ""
+msgid "Coverage Fuzzing"
+msgstr ""
+
+msgid "Crash State"
+msgstr ""
+
msgid "Create"
msgstr ""
@@ -6439,6 +6606,9 @@ msgstr ""
msgid "Create a personal access token on your account to pull or push via %{protocol}."
msgstr ""
+msgid "Create a project pre-populated with the necessary files to get you started quickly."
+msgstr ""
+
msgid "Create an account using:"
msgstr ""
@@ -6607,6 +6777,9 @@ msgstr ""
msgid "Creates branch '%{branch_name}' and a merge request to resolve this issue."
msgstr ""
+msgid "Creating"
+msgstr ""
+
msgid "Creating epic"
msgstr ""
@@ -6658,7 +6831,10 @@ msgstr ""
msgid "Current vulnerabilities count"
msgstr ""
-msgid "CurrentUser|Buy CI minutes"
+msgid "CurrentUser|Buy Pipeline minutes"
+msgstr ""
+
+msgid "CurrentUser|One of your groups is running out"
msgstr ""
msgid "CurrentUser|Profile"
@@ -6673,6 +6849,9 @@ msgstr ""
msgid "CurrentUser|Upgrade"
msgstr ""
+msgid "Custom Attributes"
+msgstr ""
+
msgid "Custom CI configuration path"
msgstr ""
@@ -6936,12 +7115,18 @@ msgstr ""
msgid "DAG"
msgstr ""
+msgid "DAG visualization requires at least 3 dependent jobs."
+msgstr ""
+
msgid "DNS"
msgstr ""
msgid "Dashboard"
msgstr ""
+msgid "Dashboard uid not found"
+msgstr ""
+
msgid "DashboardProjects|All"
msgstr ""
@@ -6966,6 +7151,9 @@ msgstr ""
msgid "Data is still calculating..."
msgstr ""
+msgid "Datasource name not found"
+msgstr ""
+
msgid "Date"
msgstr ""
@@ -7146,6 +7334,9 @@ msgstr ""
msgid "Delete this attachment"
msgstr ""
+msgid "Delete user list"
+msgstr ""
+
msgid "Delete variable"
msgstr ""
@@ -7462,6 +7653,9 @@ msgstr ""
msgid "Deploying to"
msgstr ""
+msgid "Deploying to AWS is easy with GitLab"
+msgstr ""
+
msgid "Deployment Frequency"
msgstr ""
@@ -7824,6 +8018,9 @@ msgstr ""
msgid "Dismissed on pipeline %{pipelineLink} at %{projectLink}"
msgstr ""
+msgid "Display alerts from all your monitoring tools directly within GitLab."
+msgstr ""
+
msgid "Display name"
msgstr ""
@@ -7881,9 +8078,6 @@ msgstr ""
msgid "Don't show again"
msgstr ""
-msgid "Don't worry, you can access this tour by clicking on the help icon in the top right corner and choose <strong>Learn GitLab</strong>."
-msgstr ""
-
msgid "Done"
msgstr ""
@@ -8010,6 +8204,9 @@ msgstr ""
msgid "Edit Release"
msgstr ""
+msgid "Edit Slack integration"
+msgstr ""
+
msgid "Edit Snippet"
msgstr ""
@@ -8049,6 +8246,9 @@ msgstr ""
msgid "Edit issues"
msgstr ""
+msgid "Edit iteration"
+msgstr ""
+
msgid "Edit public deploy key"
msgstr ""
@@ -8097,6 +8297,9 @@ msgstr ""
msgid "Email"
msgstr ""
+msgid "Email Notification"
+msgstr ""
+
msgid "Email address"
msgstr ""
@@ -8109,6 +8312,9 @@ msgstr ""
msgid "Email not verified. Please verify your email in Salesforce."
msgstr ""
+msgid "Email notification for unknown sign-ins"
+msgstr ""
+
msgid "Email patch"
msgstr ""
@@ -8340,6 +8546,9 @@ msgstr ""
msgid "Enforce DNS rebinding attack protection"
msgstr ""
+msgid "Enforce personal access token expiration"
+msgstr ""
+
msgid "Ensure connectivity is available from the GitLab server to the Prometheus server"
msgstr ""
@@ -8409,6 +8618,9 @@ msgstr ""
msgid "Enter the name of your application, and we'll return a unique %{type}."
msgstr ""
+msgid "Enter weights for storages for new repositories."
+msgstr ""
+
msgid "Enter your password to approve"
msgstr ""
@@ -8418,15 +8630,27 @@ msgstr ""
msgid "Environment does not have deployments"
msgstr ""
+msgid "Environment is required for Stages::MetricEndpointInserter"
+msgstr ""
+
+msgid "Environment is required for Stages::VariableEndpointInserter"
+msgstr ""
+
msgid "Environment scope"
msgstr ""
+msgid "Environment variables are applied to all project environments in this instance via the Runner. You can use environment variables for passwords, secret keys, etc. Make variables available to the running application by prepending the variable key with <code>K8S_SECRET_</code>. You can set variables to be:"
+msgstr ""
+
msgid "Environment variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. Additionally, they can be masked so they are hidden in job logs, though they must match certain regexp requirements to do so. You can use environment variables for passwords, secret keys, or whatever you want."
msgstr ""
msgid "Environment variables are configured by your administrator to be %{link_start}protected%{link_end} by default"
msgstr ""
+msgid "Environment variables on this GitLab instance are configured to be %{link_start}protected%{link_end} by default"
+msgstr ""
+
msgid "Environment:"
msgstr ""
@@ -8958,6 +9182,9 @@ msgstr ""
msgid "EventFilterBy|Filter by comments"
msgstr ""
+msgid "EventFilterBy|Filter by designs"
+msgstr ""
+
msgid "EventFilterBy|Filter by epic events"
msgstr ""
@@ -9090,15 +9317,15 @@ msgstr ""
msgid "Expand approvers"
msgstr ""
-msgid "Expand child epics"
-msgstr ""
-
msgid "Expand down"
msgstr ""
msgid "Expand dropdown"
msgstr ""
+msgid "Expand milestones"
+msgstr ""
+
msgid "Expand sidebar"
msgstr ""
@@ -9114,6 +9341,9 @@ msgstr ""
msgid "Expiration date"
msgstr ""
+msgid "Expiration not enforced"
+msgstr ""
+
msgid "Expiration policy for the Container Registry is a perfect solution for keeping the Registry space down while still enjoying the full power of GitLab CI/CD."
msgstr ""
@@ -9234,6 +9464,9 @@ msgstr ""
msgid "Failed Jobs"
msgstr ""
+msgid "Failed on"
+msgstr ""
+
msgid "Failed to add a Zoom meeting"
msgstr ""
@@ -9294,13 +9527,19 @@ msgstr ""
msgid "Failed to install."
msgstr ""
+msgid "Failed to load assignees. Please try again."
+msgstr ""
+
+msgid "Failed to load authors. Please try again."
+msgstr ""
+
msgid "Failed to load emoji list."
msgstr ""
msgid "Failed to load error details from Sentry."
msgstr ""
-msgid "Failed to load errors from Sentry. Error message: %{errorMessage}"
+msgid "Failed to load errors from Sentry."
msgstr ""
msgid "Failed to load group activity metrics. Please try again."
@@ -9381,6 +9620,9 @@ msgstr ""
msgid "Failed to set due date because the date format is invalid."
msgstr ""
+msgid "Failed to set iteration on this issue. Please try again."
+msgstr ""
+
msgid "Failed to signing using smartcard authentication"
msgstr ""
@@ -9603,13 +9845,10 @@ msgstr ""
msgid "FeatureFlags|Target environments"
msgstr ""
-msgid "FeatureFlags|There are no active feature flags"
-msgstr ""
-
-msgid "FeatureFlags|There are no inactive feature flags"
+msgid "FeatureFlags|There was an error fetching the feature flags."
msgstr ""
-msgid "FeatureFlags|There was an error fetching the feature flags."
+msgid "FeatureFlags|There was an error retrieving user lists"
msgstr ""
msgid "FeatureFlags|Try again in a few moments or contact your support team."
@@ -9621,9 +9860,18 @@ msgstr ""
msgid "FeatureFlag|Delete strategy"
msgstr ""
+msgid "FeatureFlag|List"
+msgstr ""
+
msgid "FeatureFlag|Percentage"
msgstr ""
+msgid "FeatureFlag|Select a user list"
+msgstr ""
+
+msgid "FeatureFlag|There are no configured user lists"
+msgstr ""
+
msgid "FeatureFlag|Type"
msgstr ""
@@ -9780,6 +10028,12 @@ msgstr ""
msgid "Filter..."
msgstr ""
+msgid "Find File"
+msgstr ""
+
+msgid "Find bugs in your code with coverage-guided fuzzing"
+msgstr ""
+
msgid "Find by path"
msgstr ""
@@ -10044,6 +10298,9 @@ msgstr ""
msgid "Geo allows you to replicate your GitLab instance to other geographical locations."
msgstr ""
+msgid "Geo nodes are paused using a command run on the node"
+msgstr ""
+
msgid "GeoNodeStatusEvent|%{timeAgoStr} (%{pendingEvents} events)"
msgstr ""
@@ -10128,6 +10385,9 @@ msgstr ""
msgid "GeoNodes|Not checksummed"
msgstr ""
+msgid "GeoNodes|Package files"
+msgstr ""
+
msgid "GeoNodes|Pausing replication stops the sync process. Are you sure?"
msgstr ""
@@ -10146,6 +10406,9 @@ msgstr ""
msgid "GeoNodes|Replication slots"
msgstr ""
+msgid "GeoNodes|Replication status"
+msgstr ""
+
msgid "GeoNodes|Repositories"
msgstr ""
@@ -10356,6 +10619,9 @@ msgstr ""
msgid "Geo|Synchronization failed - %{error}"
msgstr ""
+msgid "Geo|Synchronization of %{itemTitle} is disabled."
+msgstr ""
+
msgid "Geo|The database is currently %{db_lag} behind the primary node."
msgstr ""
@@ -10668,6 +10934,9 @@ msgstr ""
msgid "Go to %{link_to_google_takeout}."
msgstr ""
+msgid "Go to <strong>Issues</strong> > <strong>Boards</strong> to access your personalized learning issue board."
+msgstr ""
+
msgid "Go to Pipelines"
msgstr ""
@@ -10767,9 +11036,6 @@ msgstr ""
msgid "Go to your snippets"
msgstr ""
-msgid "Golden Tanuki"
-msgstr ""
-
msgid "Google Cloud Platform"
msgstr ""
@@ -10791,6 +11057,9 @@ msgstr ""
msgid "Grafana URL"
msgstr ""
+msgid "Grafana response contains invalid json"
+msgstr ""
+
msgid "GrafanaIntegration|API Token"
msgstr ""
@@ -10839,9 +11108,6 @@ msgstr ""
msgid "Group %{group_name} was successfully created."
msgstr ""
-msgid "Group '%{group_name}' is being imported."
-msgstr ""
-
msgid "Group Audit Events"
msgstr ""
@@ -10878,9 +11144,6 @@ msgstr ""
msgid "Group by:"
msgstr ""
-msgid "Group could not be imported: %{errors}"
-msgstr ""
-
msgid "Group description"
msgstr ""
@@ -10908,9 +11171,15 @@ msgstr ""
msgid "Group has not been marked for deletion"
msgstr ""
+msgid "Group import could not be scheduled"
+msgstr ""
+
msgid "Group info:"
msgstr ""
+msgid "Group is required when cluster_type is :group"
+msgstr ""
+
msgid "Group maintainers can register group runners in the %{link}"
msgstr ""
@@ -10974,6 +11243,24 @@ msgstr ""
msgid "GroupActivyMetrics|Recent activity (last 90 days)"
msgstr ""
+msgid "GroupImport|Failed to import group."
+msgstr ""
+
+msgid "GroupImport|Group '%{group_name}' is being imported."
+msgstr ""
+
+msgid "GroupImport|Group could not be imported: %{errors}"
+msgstr ""
+
+msgid "GroupImport|Please wait while we import the group for you. Refresh at will."
+msgstr ""
+
+msgid "GroupImport|The group was successfully imported."
+msgstr ""
+
+msgid "GroupImport|Unable to process group import file"
+msgstr ""
+
msgid "GroupRoadmap|%{dateWord} – No end date"
msgstr ""
@@ -11241,6 +11528,9 @@ msgstr ""
msgid "Groups (%{groups})"
msgstr ""
+msgid "Groups and projects"
+msgstr ""
+
msgid "Groups can also be nested by creating %{subgroup_docs_link_start}subgroups%{subgroup_docs_link_end}."
msgstr ""
@@ -11286,6 +11576,33 @@ msgstr ""
msgid "GroupsEmptyState|You can manage your group member’s permissions and access to each project in the group."
msgstr ""
+msgid "GroupsNew|Contact an administrator to enable options for importing your group."
+msgstr ""
+
+msgid "GroupsNew|Create"
+msgstr ""
+
+msgid "GroupsNew|Create group"
+msgstr ""
+
+msgid "GroupsNew|GitLab group export"
+msgstr ""
+
+msgid "GroupsNew|Import"
+msgstr ""
+
+msgid "GroupsNew|Import group"
+msgstr ""
+
+msgid "GroupsNew|My Awesome Group"
+msgstr ""
+
+msgid "GroupsNew|No import options available"
+msgstr ""
+
+msgid "GroupsNew|To copy a GitLab group between installations, navigate to the group settings page for the original installation, generate an export file, and upload it here."
+msgstr ""
+
msgid "GroupsTree|Are you sure you want to leave the \"%{fullName}\" group?"
msgstr ""
@@ -11388,6 +11705,9 @@ msgstr ""
msgid "Helps reduce request volume for protected paths"
msgstr ""
+msgid "Here are all your projects in your group, including the one you just created. To start, let’s take a look at your personalized learning project which will help you learn about GitLab at your own pace."
+msgstr ""
+
msgid "Here you will find recent merge request activity"
msgstr ""
@@ -11402,6 +11722,9 @@ msgid_plural "Hide charts"
msgstr[0] ""
msgstr[1] ""
+msgid "Hide details"
+msgstr ""
+
msgid "Hide file browser"
msgstr ""
@@ -11411,6 +11734,9 @@ msgstr ""
msgid "Hide host keys manual input"
msgstr ""
+msgid "Hide list"
+msgstr ""
+
msgid "Hide marketing-related entries from help"
msgstr ""
@@ -11431,9 +11757,6 @@ msgstr[1] ""
msgid "Hide values"
msgstr ""
-msgid "Hiding all labels"
-msgstr ""
-
msgid "High or unknown vulnerabilities present"
msgstr ""
@@ -11518,6 +11841,9 @@ msgstr ""
msgid "ID:"
msgstr ""
+msgid "IDE"
+msgstr ""
+
msgid "IDE|Allow live previews of JavaScript projects in the Web IDE using CodeSandbox Live Preview."
msgstr ""
@@ -11608,10 +11934,10 @@ msgstr ""
msgid "If enabled"
msgstr ""
-msgid "If enabled, access to projects will be validated on an external service using their classification label."
+msgid "If enabled, GitLab will handle Object Storage replication using Geo. %{linkStart}More information%{linkEnd}"
msgstr ""
-msgid "If enabled, and if object storage is enabled, GitLab will handle Object Storage replication using Geo"
+msgid "If enabled, access to projects will be validated on an external service using their classification label."
msgstr ""
msgid "If there is no previous license or if the previous license has expired, some GitLab functionality will be blocked until a new, valid license is uploaded."
@@ -11973,6 +12299,9 @@ msgid_plural "Instances"
msgstr[0] ""
msgstr[1] ""
+msgid "Instance Configuration"
+msgstr ""
+
msgid "Instance Statistics visibility"
msgstr ""
@@ -11994,9 +12323,6 @@ msgstr ""
msgid "Integrations"
msgstr ""
-msgid "Integrations allow you to integrate GitLab with other applications"
-msgstr ""
-
msgid "Integrations|All details"
msgstr ""
@@ -12108,6 +12434,9 @@ msgstr ""
msgid "Invalid query"
msgstr ""
+msgid "Invalid repository bundle for snippet with id %{snippet_id}"
+msgstr ""
+
msgid "Invalid repository path"
msgstr ""
@@ -12126,6 +12455,9 @@ msgstr ""
msgid "Invalid two-factor code."
msgstr ""
+msgid "Invalid yaml"
+msgstr ""
+
msgid "Invitation"
msgstr ""
@@ -12345,15 +12677,24 @@ msgstr ""
msgid "It seems like the Dependency Scanning job ran successfully, but no dependencies have been detected in your project."
msgstr ""
+msgid "It seems that there is currently no available data for code coverage"
+msgstr ""
+
msgid "It's you"
msgstr ""
+msgid "Iteration"
+msgstr ""
+
msgid "Iteration changed to"
msgstr ""
msgid "Iteration removed"
msgstr ""
+msgid "Iteration updated"
+msgstr ""
+
msgid "Iterations"
msgstr ""
@@ -12384,12 +12725,18 @@ msgstr ""
msgid "January"
msgstr ""
+msgid "Jira Issues"
+msgstr ""
+
msgid "Jira import is already running."
msgstr ""
msgid "Jira integration not configured."
msgstr ""
+msgid "Jira project key is not configured"
+msgstr ""
+
msgid "Jira project: %{importProject}"
msgstr ""
@@ -12570,6 +12917,9 @@ msgstr ""
msgid "Keep divergent refs"
msgstr ""
+msgid "Kerberos access denied"
+msgstr ""
+
msgid "Key"
msgstr ""
@@ -12579,10 +12929,13 @@ msgstr ""
msgid "Key: %{key}"
msgstr ""
-msgid "Keyboard Shortcuts"
+msgid "Keyboard shortcuts"
msgstr ""
-msgid "Keyboard shortcuts"
+msgid "Keys"
+msgstr ""
+
+msgid "Ki"
msgstr ""
msgid "Kubernetes"
@@ -12624,6 +12977,9 @@ msgstr ""
msgid "LDAP"
msgstr ""
+msgid "LDAP Synchronization"
+msgstr ""
+
msgid "LDAP settings"
msgstr ""
@@ -12848,12 +13204,18 @@ msgstr ""
msgid "Learn more about custom project templates"
msgstr ""
+msgid "Learn more about deploying to AWS"
+msgstr ""
+
msgid "Learn more about deploying to a cluster"
msgstr ""
msgid "Learn more about group-level project templates"
msgstr ""
+msgid "Learn more about job dependencies"
+msgstr ""
+
msgid "Learn more about signing commits"
msgstr ""
@@ -12905,9 +13267,21 @@ msgstr ""
msgid "License History"
msgstr ""
+msgid "License ID:"
+msgstr ""
+
+msgid "License URL"
+msgstr ""
+
msgid "License-Check"
msgstr ""
+msgid "LicenseCompliance|%{docLinkStart}License Approvals%{docLinkEnd} are active"
+msgstr ""
+
+msgid "LicenseCompliance|%{docLinkStart}License Approvals%{docLinkEnd} are inactive"
+msgstr ""
+
msgid "LicenseCompliance|Add a license"
msgstr ""
@@ -12929,9 +13303,15 @@ msgstr ""
msgid "LicenseCompliance|Deny"
msgstr ""
+msgid "LicenseCompliance|Learn more about %{linkStart}License Approvals%{linkEnd}"
+msgstr ""
+
msgid "LicenseCompliance|License"
msgstr ""
+msgid "LicenseCompliance|License Approvals"
+msgstr ""
+
msgid "LicenseCompliance|License Compliance detected %d license and policy violation for the source branch only; approval required"
msgid_plural "LicenseCompliance|License Compliance detected %d licenses and policy violations for the source branch only; approval required"
msgstr[0] ""
@@ -13152,6 +13532,9 @@ msgstr ""
msgid "List your Bitbucket Server repositories"
msgstr ""
+msgid "Lists"
+msgstr ""
+
msgid "Live preview"
msgstr ""
@@ -13335,6 +13718,9 @@ msgstr ""
msgid "Manage project labels"
msgstr ""
+msgid "Manage storage usage"
+msgstr ""
+
msgid "Manage two-factor authentication"
msgstr ""
@@ -13350,6 +13736,9 @@ msgstr ""
msgid "Manifest file import"
msgstr ""
+msgid "Manifest import"
+msgstr ""
+
msgid "Manual job"
msgstr ""
@@ -13497,6 +13886,12 @@ msgstr ""
msgid "Maximum field length"
msgstr ""
+msgid "Maximum file size is 2MB. Please select a smaller file."
+msgstr ""
+
+msgid "Maximum import size (MB)"
+msgstr ""
+
msgid "Maximum job timeout"
msgstr ""
@@ -13536,6 +13931,9 @@ msgstr ""
msgid "Maximum size of Elasticsearch bulk indexing requests."
msgstr ""
+msgid "Maximum size of import files."
+msgstr ""
+
msgid "Maximum size of individual attachments in comments."
msgstr ""
@@ -13587,6 +13985,9 @@ msgstr ""
msgid "Merge (when the pipeline succeeds)"
msgstr ""
+msgid "Merge Conflicts"
+msgstr ""
+
msgid "Merge Request"
msgstr ""
@@ -13713,6 +14114,9 @@ msgstr ""
msgid "MergeRequests|Squash task canceled: another squash is already in progress."
msgstr ""
+msgid "MergeRequests|This project does not allow squashing commits when merge requests are accepted."
+msgstr ""
+
msgid "MergeRequests|Thread stays resolved"
msgstr ""
@@ -13866,6 +14270,12 @@ msgstr ""
msgid "MetricsSettings|Add a button to the metrics dashboard linking directly to your existing external dashboard."
msgstr ""
+msgid "MetricsSettings|Choose whether to display dashboard metrics in UTC or the user's local timezone."
+msgstr ""
+
+msgid "MetricsSettings|Dashboard timezone"
+msgstr ""
+
msgid "MetricsSettings|External dashboard URL"
msgstr ""
@@ -13875,6 +14285,12 @@ msgstr ""
msgid "MetricsSettings|Metrics Dashboard"
msgstr ""
+msgid "MetricsSettings|UTC (Coordinated Universal Time)"
+msgstr ""
+
+msgid "MetricsSettings|User's local timezone"
+msgstr ""
+
msgid "Metrics|Add metric"
msgstr ""
@@ -13890,6 +14306,9 @@ msgstr ""
msgid "Metrics|Create metric"
msgstr ""
+msgid "Metrics|Current"
+msgstr ""
+
msgid "Metrics|Delete metric"
msgstr ""
@@ -13940,6 +14359,9 @@ msgstr ""
msgid "Metrics|Max"
msgstr ""
+msgid "Metrics|Min"
+msgstr ""
+
msgid "Metrics|Must be a valid PromQL query."
msgstr ""
@@ -13955,6 +14377,9 @@ msgstr ""
msgid "Metrics|Refresh dashboard"
msgstr ""
+msgid "Metrics|Select a value"
+msgstr ""
+
msgid "Metrics|Star dashboard"
msgstr ""
@@ -13973,12 +14398,18 @@ msgstr ""
msgid "Metrics|There was an error getting annotations information."
msgstr ""
+msgid "Metrics|There was an error getting dashboard validation warnings information."
+msgstr ""
+
msgid "Metrics|There was an error getting deployment information."
msgstr ""
msgid "Metrics|There was an error getting environments information."
msgstr ""
+msgid "Metrics|There was an error getting options for variable \"%{name}\"."
+msgstr ""
+
msgid "Metrics|There was an error trying to validate your query"
msgstr ""
@@ -14021,6 +14452,9 @@ msgstr ""
msgid "Metrics|You're about to permanently delete this metric. This cannot be undone."
msgstr ""
+msgid "Metrics|Your dashboard schema is invalid. Edit the dashboard to correct the YAML schema."
+msgstr ""
+
msgid "Metrics|e.g. HTTP requests"
msgstr ""
@@ -14036,12 +14470,18 @@ msgstr ""
msgid "Metrics|e.g. req/sec"
msgstr ""
+msgid "Mi"
+msgstr ""
+
msgid "Microsoft Azure"
msgstr ""
msgid "Middleman project with Static Site Editor support"
msgstr ""
+msgid "Migrate your data from an external source like GitHub, Bitbucket, or another instance of GitLab."
+msgstr ""
+
msgid "Migrated %{success_count}/%{total_count} files."
msgstr ""
@@ -14176,6 +14616,9 @@ msgstr ""
msgid "Minimum capacity to be available before we schedule more mirrors preemptively."
msgstr ""
+msgid "Minimum interval in days"
+msgstr ""
+
msgid "Minimum length is %{minimum_password_length} characters"
msgstr ""
@@ -14362,6 +14805,12 @@ msgstr ""
msgid "Multiple uploaders found: %{uploader_types}"
msgstr ""
+msgid "Must match with the %{codeStart}external_url%{codeEnd} in %{codeStart}/etc/gitlab/gitlab.rb%{codeEnd}."
+msgstr ""
+
+msgid "Must match with the %{codeStart}geo_node_name%{codeEnd} in %{codeStart}/etc/gitlab/gitlab.rb%{codeEnd}. %{linkStart}More information%{linkEnd}"
+msgstr ""
+
msgid "My Awesome Group"
msgstr ""
@@ -14476,10 +14925,16 @@ msgstr ""
msgid "New Application"
msgstr ""
+msgid "New Branch"
+msgstr ""
+
+msgid "New Deploy Key"
+msgstr ""
+
msgid "New Environment"
msgstr ""
-msgid "New Geo Node"
+msgid "New File"
msgstr ""
msgid "New Group"
@@ -14496,15 +14951,15 @@ msgid_plural "New Issues"
msgstr[0] ""
msgstr[1] ""
-msgid "New Iteration"
-msgstr ""
-
msgid "New Jira import"
msgstr ""
msgid "New Label"
msgstr ""
+msgid "New Merge Request"
+msgstr ""
+
msgid "New Milestone"
msgstr ""
@@ -14523,6 +14978,9 @@ msgstr ""
msgid "New Snippet"
msgstr ""
+msgid "New User"
+msgstr ""
+
msgid "New branch"
msgstr ""
@@ -14565,6 +15023,9 @@ msgstr ""
msgid "New issue title"
msgstr ""
+msgid "New iteration"
+msgstr ""
+
msgid "New iteration created"
msgstr ""
@@ -14742,6 +15203,12 @@ msgstr ""
msgid "No grouping"
msgstr ""
+msgid "No iteration"
+msgstr ""
+
+msgid "No iterations to show"
+msgstr ""
+
msgid "No job log"
msgstr ""
@@ -14784,6 +15251,9 @@ msgstr ""
msgid "No other labels with such name or description"
msgstr ""
+msgid "No panels matching properties %{opts}"
+msgstr ""
+
msgid "No parent group"
msgstr ""
@@ -14817,6 +15287,9 @@ msgstr ""
msgid "No schedules"
msgstr ""
+msgid "No stack trace for this error"
+msgstr ""
+
msgid "No starrers matched your search"
msgstr ""
@@ -14829,7 +15302,7 @@ msgstr ""
msgid "No test coverage"
msgstr ""
-msgid "No thanks, don't show this again"
+msgid "No thanks"
msgstr ""
msgid "No vulnerabilities present"
@@ -14844,9 +15317,6 @@ msgstr ""
msgid "No, directly import the existing email addresses and usernames."
msgstr ""
-msgid "No, not interested right now"
-msgstr ""
-
msgid "No. of commits"
msgstr ""
@@ -14892,9 +15362,6 @@ msgstr ""
msgid "Not found."
msgstr ""
-msgid "Not helpful"
-msgstr ""
-
msgid "Not now"
msgstr ""
@@ -15051,6 +15518,9 @@ msgstr ""
msgid "Notifications on"
msgstr ""
+msgid "Notify users by email when sign-in location is not recognized"
+msgstr ""
+
msgid "Nov"
msgstr ""
@@ -15117,9 +15587,6 @@ msgstr ""
msgid "Oh no!"
msgstr ""
-msgid "Ok let's go"
-msgstr ""
-
msgid "Oldest first"
msgstr ""
@@ -15132,16 +15599,49 @@ msgstr ""
msgid "On track"
msgstr ""
+msgid "OnDemandScans|Attached branch"
+msgstr ""
+
+msgid "OnDemandScans|Attached branch is where the scan job runs."
+msgstr ""
+
+msgid "OnDemandScans|Could not run the scan. Please try again."
+msgstr ""
+
msgid "OnDemandScans|Create new DAST scan"
msgstr ""
+msgid "OnDemandScans|DAST will scan the target URL and any discovered sub URLs."
+msgstr ""
+
+msgid "OnDemandScans|New on-demand DAST scan"
+msgstr ""
+
msgid "OnDemandScans|On-demand Scans"
msgstr ""
+msgid "OnDemandScans|On-demand scans run outside the DevOps cycle and find vulnerabilities in your projects. %{learnMoreLinkStart}Learn more%{learnMoreLinkEnd}"
+msgstr ""
+
+msgid "OnDemandScans|Only a passive scan can be performed on demand."
+msgstr ""
+
+msgid "OnDemandScans|Passive DAST Scan"
+msgstr ""
+
+msgid "OnDemandScans|Please enter a valid URL format, ex: http://www.example.com/home"
+msgstr ""
+
+msgid "OnDemandScans|Run this scan"
+msgstr ""
+
+msgid "OnDemandScans|Scan mode"
+msgstr ""
+
msgid "OnDemandScans|Schedule or run scans immediately against target sites. Currently available on-demand scan type: DAST. %{helpLinkStart}More information%{helpLinkEnd}"
msgstr ""
-msgid "Onboarding"
+msgid "OnDemandScans|Target URL"
msgstr ""
msgid "Once imported, repositories can be mirrored over SSH. Read more %{link_start}here%{link_end}."
@@ -15167,6 +15667,9 @@ msgstr ""
msgid "One or more of you personal access tokens were revoked"
msgstr ""
+msgid "One or more of your %{provider} projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
+msgstr ""
+
msgid "One or more of your Bitbucket projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
msgstr ""
@@ -15668,6 +16171,9 @@ msgstr ""
msgid "Parent epic is not present."
msgstr ""
+msgid "Parsing error for param :embed_json. %{message}"
+msgstr ""
+
msgid "Part of merge request changes"
msgstr ""
@@ -15680,6 +16186,9 @@ msgstr ""
msgid "Passed"
msgstr ""
+msgid "Passed on"
+msgstr ""
+
msgid "Password"
msgstr ""
@@ -15764,6 +16273,9 @@ msgstr ""
msgid "Perform common operations on GitLab project"
msgstr ""
+msgid "Performance and resource management"
+msgstr ""
+
msgid "Performance optimization"
msgstr ""
@@ -15971,7 +16483,7 @@ msgstr ""
msgid "Pipelines|Something went wrong while cleaning runners cache."
msgstr ""
-msgid "Pipelines|There are currently no %{scope} pipelines."
+msgid "Pipelines|There are currently no finished pipelines."
msgstr ""
msgid "Pipelines|There are currently no pipelines."
@@ -16082,6 +16594,9 @@ msgstr ""
msgid "Pipeline|Stop pipeline #%{pipelineId}?"
msgstr ""
+msgid "Pipeline|Tag name"
+msgstr ""
+
msgid "Pipeline|Trigger author"
msgstr ""
@@ -16163,12 +16678,18 @@ msgstr ""
msgid "Please check your email (%{email}) to verify that you own this address and unlock the power of CI/CD. Didn't receive it? %{resend_link}. Wrong email address? %{update_link}."
msgstr ""
+msgid "Please choose a file"
+msgstr ""
+
msgid "Please choose a group URL with no special characters."
msgstr ""
msgid "Please complete your profile with email address"
msgstr ""
+msgid "Please convert %{linkStart}them to Git%{linkEnd}, and go through the %{linkToImportFlow} again."
+msgstr ""
+
msgid "Please convert them to %{link_to_git}, and go through the %{link_to_import_flow} again."
msgstr ""
@@ -16226,6 +16747,9 @@ msgstr ""
msgid "Please provide a valid email address."
msgstr ""
+msgid "Please provide attributes to update"
+msgstr ""
+
msgid "Please refer to <a href=\"%{docs_url}\">%{docs_url}</a>"
msgstr ""
@@ -16961,6 +17485,9 @@ msgstr ""
msgid "Project has too many %{label_for_message} to search"
msgstr ""
+msgid "Project is required when cluster_type is :project"
+msgstr ""
+
msgid "Project members"
msgstr ""
@@ -17078,7 +17605,40 @@ msgstr ""
msgid "ProjectService|Comment"
msgstr ""
-msgid "ProjectService|Comment will be posted on each event"
+msgid "ProjectService|Event will be triggered by a push to the repository"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a commit is created/updated"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a confidential issue is created/updated/closed"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a deployment finishes"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a merge request is created/updated/merged"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a new tag is pushed to the repository"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a new, unique alert is recorded"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a pipeline status changes"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a wiki page is created/updated"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when an issue is created/updated/closed"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when someone adds a comment"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when someone adds a comment on a confidential issue"
msgstr ""
msgid "ProjectService|Perform common operations on GitLab project: %{project_name}"
@@ -17129,6 +17689,9 @@ msgstr ""
msgid "ProjectSettings|Enable 'Delete source branch' option by default"
msgstr ""
+msgid "ProjectSettings|Enable merge trains and pipelines for merged results"
+msgstr ""
+
msgid "ProjectSettings|Every merge creates a merge commit"
msgstr ""
@@ -17159,7 +17722,7 @@ msgstr ""
msgid "ProjectSettings|Forks"
msgstr ""
-msgid "ProjectSettings|Git Large File Storage"
+msgid "ProjectSettings|Git Large File Storage (LFS)"
msgstr ""
msgid "ProjectSettings|Internal"
@@ -17195,9 +17758,6 @@ msgstr ""
msgid "ProjectSettings|Merge options"
msgstr ""
-msgid "ProjectSettings|Merge pipelines will try to validate the post-merge result prior to merging"
-msgstr ""
-
msgid "ProjectSettings|Merge requests"
msgstr ""
@@ -17225,6 +17785,9 @@ msgstr ""
msgid "ProjectSettings|Pipelines"
msgstr ""
+msgid "ProjectSettings|Pipelines for merge requests must be enabled in the CI/CD configuration file, or pipelines could be unresolvable or dropped"
+msgstr ""
+
msgid "ProjectSettings|Pipelines must succeed"
msgstr ""
@@ -17252,6 +17815,9 @@ msgstr ""
msgid "ProjectSettings|Show link to create/view merge request when pushing from the command line"
msgstr ""
+msgid "ProjectSettings|Skipped pipelines are considered successful"
+msgstr ""
+
msgid "ProjectSettings|Snippets"
msgstr ""
@@ -17267,6 +17833,9 @@ msgstr ""
msgid "ProjectSettings|These checks must pass before merge requests can be merged"
msgstr ""
+msgid "ProjectSettings|This introduces the risk of merging changes that will not pass the pipeline."
+msgstr ""
+
msgid "ProjectSettings|This setting is applied on the server level and can be overridden by an admin."
msgstr ""
@@ -17444,15 +18013,27 @@ msgstr ""
msgid "ProjectsNew|Blank project"
msgstr ""
+msgid "ProjectsNew|Connect your external repository to GitLab CI/CD."
+msgstr ""
+
msgid "ProjectsNew|Contact an administrator to enable options for importing your project."
msgstr ""
msgid "ProjectsNew|Create"
msgstr ""
+msgid "ProjectsNew|Create a blank project to house your files, plan your work, and collaborate on code, among other things."
+msgstr ""
+
+msgid "ProjectsNew|Create blank project"
+msgstr ""
+
msgid "ProjectsNew|Create from template"
msgstr ""
+msgid "ProjectsNew|Create new project"
+msgstr ""
+
msgid "ProjectsNew|Creating project & repository."
msgstr ""
@@ -17477,6 +18058,9 @@ msgstr ""
msgid "ProjectsNew|Project description %{tag_start}(optional)%{tag_end}"
msgstr ""
+msgid "ProjectsNew|Run CI/CD for external repository"
+msgstr ""
+
msgid "ProjectsNew|Template"
msgstr ""
@@ -17738,6 +18322,9 @@ msgstr ""
msgid "Protected Branch"
msgstr ""
+msgid "Protected Branches"
+msgstr ""
+
msgid "Protected Environment"
msgstr ""
@@ -17750,6 +18337,9 @@ msgstr ""
msgid "Protected Tag"
msgstr ""
+msgid "Protected Tags"
+msgstr ""
+
msgid "Protected branches"
msgstr ""
@@ -17849,6 +18439,9 @@ msgstr ""
msgid "Provider"
msgstr ""
+msgid "Proxy support for this API is not available currently"
+msgstr ""
+
msgid "Pseudonymizer data collection"
msgstr ""
@@ -17873,6 +18466,9 @@ msgstr ""
msgid "Publish to status page"
msgstr ""
+msgid "Published on status page"
+msgstr ""
+
msgid "Publishes this issue to the associated status page."
msgstr ""
@@ -18047,6 +18643,9 @@ msgstr ""
msgid "Recipe"
msgstr ""
+msgid "Reconfigure"
+msgstr ""
+
msgid "Recover hidden stage"
msgstr ""
@@ -18133,6 +18732,9 @@ msgstr ""
msgid "Registration|Your profile"
msgstr ""
+msgid "Regulate approvals by authors/committers, based on compliance frameworks. Can be changed only at the instance level."
+msgstr ""
+
msgid "Rejected (closed)"
msgstr ""
@@ -18183,15 +18785,24 @@ msgstr ""
msgid "ReleaseAssetLinkType|Image"
msgstr ""
+msgid "ReleaseAssetLinkType|Images"
+msgstr ""
+
msgid "ReleaseAssetLinkType|Other"
msgstr ""
msgid "ReleaseAssetLinkType|Package"
msgstr ""
+msgid "ReleaseAssetLinkType|Packages"
+msgstr ""
+
msgid "ReleaseAssetLinkType|Runbook"
msgstr ""
+msgid "ReleaseAssetLinkType|Runbooks"
+msgstr ""
+
msgid "Releases"
msgstr ""
@@ -18204,6 +18815,9 @@ msgstr ""
msgid "Releases documentation"
msgstr ""
+msgid "Releases|New Release"
+msgstr ""
+
msgid "Release|Something went wrong while getting the release details"
msgstr ""
@@ -18276,6 +18890,9 @@ msgstr ""
msgid "Remove fork relationship"
msgstr ""
+msgid "Remove from batch"
+msgstr ""
+
msgid "Remove from board"
msgstr ""
@@ -18465,6 +19082,12 @@ msgstr ""
msgid "Replication"
msgstr ""
+msgid "Replication enabled"
+msgstr ""
+
+msgid "Replication paused"
+msgstr ""
+
msgid "Reply by email"
msgstr ""
@@ -18492,15 +19115,15 @@ msgstr ""
msgid "Reported %{timeAgo} by %{reportedBy}"
msgstr ""
-msgid "Reported Resource Changes: %{addNum} to add, %{changeNum} to change, %{deleteNum} to delete"
-msgstr ""
-
msgid "Reporter"
msgstr ""
msgid "Reporting"
msgstr ""
+msgid "Reports"
+msgstr ""
+
msgid "Reports|%{combinedString} and %{resolvedString}"
msgstr ""
@@ -18539,6 +19162,9 @@ msgstr ""
msgid "Reports|Failure"
msgstr ""
+msgid "Reports|Identifier"
+msgstr ""
+
msgid "Reports|Metrics reports are loading"
msgstr ""
@@ -18551,6 +19177,9 @@ msgstr ""
msgid "Reports|Metrics reports failed loading results"
msgstr ""
+msgid "Reports|Scanner"
+msgstr ""
+
msgid "Reports|Severity"
msgstr ""
@@ -18599,15 +19228,33 @@ msgstr ""
msgid "Repository cleanup has started. You will receive an email once the cleanup operation is complete."
msgstr ""
+msgid "Repository files count over the limit"
+msgstr ""
+
+msgid "Repository has an invalid default branch name."
+msgstr ""
+
+msgid "Repository has more than one branch."
+msgstr ""
+
msgid "Repository has no locks."
msgstr ""
+msgid "Repository has tags."
+msgstr ""
+
msgid "Repository maintenance"
msgstr ""
msgid "Repository mirroring"
msgstr ""
+msgid "Repository must contain at least 1 file."
+msgstr ""
+
+msgid "Repository size is above the limit."
+msgstr ""
+
msgid "Repository static objects"
msgstr ""
@@ -18617,7 +19264,7 @@ msgstr ""
msgid "Repository sync capacity"
msgstr ""
-msgid "Repository: %{counter_repositories} / Wikis: %{counter_wikis} / Build Artifacts: %{counter_build_artifacts} / LFS: %{counter_lfs_objects}"
+msgid "Repository: %{counter_repositories} / Wikis: %{counter_wikis} / Build Artifacts: %{counter_build_artifacts} / LFS: %{counter_lfs_objects} / Snippets: %{counter_snippets}"
msgstr ""
msgid "RepositorySettingsAccessLevel|Select"
@@ -18629,6 +19276,9 @@ msgstr ""
msgid "Request Headers"
msgstr ""
+msgid "Request details"
+msgstr ""
+
msgid "Request parameter %{param} is missing."
msgstr ""
@@ -18735,6 +19385,9 @@ msgstr ""
msgid "Reset template"
msgstr ""
+msgid "Reset to project defaults"
+msgstr ""
+
msgid "Resetting the authorization key for this project will require updating the authorization key in every alert source it is enabled in."
msgstr ""
@@ -18831,9 +19484,6 @@ msgstr ""
msgid "Resume"
msgstr ""
-msgid "Resume replication"
-msgstr ""
-
msgid "Resync"
msgstr ""
@@ -19456,6 +20106,9 @@ msgstr ""
msgid "SecurityReports|False positive"
msgstr ""
+msgid "SecurityReports|Group Security Dashboard"
+msgstr ""
+
msgid "SecurityReports|Hide dismissed"
msgstr ""
@@ -19510,10 +20163,13 @@ msgstr ""
msgid "SecurityReports|Remove project from dashboard"
msgstr ""
-msgid "SecurityReports|Report type"
+msgid "SecurityReports|Return to dashboard"
msgstr ""
-msgid "SecurityReports|Return to dashboard"
+msgid "SecurityReports|Scan details"
+msgstr ""
+
+msgid "SecurityReports|Scanner type"
msgstr ""
msgid "SecurityReports|Security Dashboard"
@@ -19570,6 +20226,9 @@ msgstr ""
msgid "SecurityReports|There was an error while generating the report."
msgstr ""
+msgid "SecurityReports|Unable to add %{invalidProjectsMessage}"
+msgstr ""
+
msgid "SecurityReports|Unable to add %{invalidProjects}"
msgstr ""
@@ -19684,6 +20343,9 @@ msgstr ""
msgid "Select due date"
msgstr ""
+msgid "Select file"
+msgstr ""
+
msgid "Select group or project"
msgstr ""
@@ -19747,9 +20409,6 @@ msgstr ""
msgid "Select the branch you want to set as the default for this project. All merge requests and commits will automatically be made against this branch unless you specify a different one."
msgstr ""
-msgid "Select the configured storage available for new repositories to be placed on."
-msgstr ""
-
msgid "Select the custom project template source group."
msgstr ""
@@ -19942,12 +20601,6 @@ msgstr ""
msgid "Service Desk is enabled but not yet active"
msgstr ""
-msgid "Service Desk is off"
-msgstr ""
-
-msgid "Service Desk is on"
-msgstr ""
-
msgid "Service Templates"
msgstr ""
@@ -20035,6 +20688,15 @@ msgstr ""
msgid "Set the milestone to %{milestone_reference}."
msgstr ""
+msgid "Set the number of concurrent requests this secondary node will make to the primary node while backfilling."
+msgstr ""
+
+msgid "Set the synchronization and verification capacity for the secondary node."
+msgstr ""
+
+msgid "Set the timeout in seconds to send a secondary node status to the primary and IPs allowed for the secondary nodes."
+msgstr ""
+
msgid "Set time estimate"
msgstr ""
@@ -20074,6 +20736,9 @@ msgstr ""
msgid "Set weight to %{weight}."
msgstr ""
+msgid "Set what should be replicated by choosing specific projects or groups by the secondary node."
+msgstr ""
+
msgid "SetPasswordToCloneLink|set a password"
msgstr ""
@@ -20200,6 +20865,9 @@ msgstr ""
msgid "Show complete raw log"
msgstr ""
+msgid "Show details"
+msgstr ""
+
msgid "Show file browser"
msgstr ""
@@ -20209,10 +20877,13 @@ msgstr ""
msgid "Show latest version"
msgstr ""
+msgid "Show list"
+msgstr ""
+
msgid "Show me everything"
msgstr ""
-msgid "Show me how"
+msgid "Show me how to add a pipeline"
msgstr ""
msgid "Show me more advanced stuff"
@@ -20261,9 +20932,6 @@ msgstr ""
msgid "Showing all issues"
msgstr ""
-msgid "Showing all labels"
-msgstr ""
-
msgid "Showing last %{size} of log -"
msgstr ""
@@ -20375,9 +21043,6 @@ msgstr ""
msgid "Skip outdated deployment jobs"
msgstr ""
-msgid "Skip this for now"
-msgstr ""
-
msgid "Skipped"
msgstr ""
@@ -20522,6 +21187,9 @@ msgstr ""
msgid "Something went wrong while adding your award. Please try again."
msgstr ""
+msgid "Something went wrong while applying the batch of suggestions. Please try again."
+msgstr ""
+
msgid "Something went wrong while applying the suggestion. Please try again."
msgstr ""
@@ -20624,10 +21292,13 @@ msgstr ""
msgid "Something went wrong while updating a requirement."
msgstr ""
+msgid "Something went wrong while updating assignees"
+msgstr ""
+
msgid "Something went wrong while updating your list settings"
msgstr ""
-msgid "Something went wrong with your automatic subscription renewal"
+msgid "Something went wrong with your automatic subscription renewal."
msgstr ""
msgid "Something went wrong, unable to add %{project} to dashboard"
@@ -20645,6 +21316,9 @@ msgstr ""
msgid "Something went wrong, unable to search projects"
msgstr ""
+msgid "Something went wrong."
+msgstr ""
+
msgid "Something went wrong. Please try again."
msgstr ""
@@ -20828,6 +21502,9 @@ msgstr ""
msgid "Source code"
msgstr ""
+msgid "Source code (%{fileExtension})"
+msgstr ""
+
msgid "Source is not available"
msgstr ""
@@ -20912,6 +21589,9 @@ msgstr ""
msgid "Stack trace"
msgstr ""
+msgid "Stacktrace snippet"
+msgstr ""
+
msgid "Stage"
msgstr ""
@@ -21017,6 +21697,9 @@ msgstr ""
msgid "Start thread & reopen %{noteable_name}"
msgstr ""
+msgid "Start using Directed Acyclic Graphs (DAG)"
+msgstr ""
+
msgid "Start your Free Gold Trial"
msgstr ""
@@ -21479,6 +22162,21 @@ msgstr ""
msgid "SuggestedColors|Very pale orange"
msgstr ""
+msgid "Suggestion is not applicable as the suggestion was not found."
+msgstr ""
+
+msgid "Suggestions are not applicable as one or more suggestions were not found."
+msgstr ""
+
+msgid "Suggestions are not applicable as their lines cannot overlap."
+msgstr ""
+
+msgid "Suggestions must all be on the same branch."
+msgstr ""
+
+msgid "Suggestions that change line count can't be added to batches, yet."
+msgstr ""
+
msgid "Suggestions:"
msgstr ""
@@ -21521,9 +22219,6 @@ msgstr ""
msgid "Synchronization disabled"
msgstr ""
-msgid "Synchronization of container repositories is disabled."
-msgstr ""
-
msgid "System"
msgstr ""
@@ -21704,6 +22399,34 @@ msgstr ""
msgid "Terms of Service and Privacy Policy"
msgstr ""
+msgid "Terraform|%{number} Terraform report failed to generate"
+msgid_plural "Terraform|%{number} Terraform reports failed to generate"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Terraform|%{number} Terraform report was generated in your pipelines"
+msgid_plural "Terraform|%{number} Terraform reports were generated in your pipelines"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Terraform|A Terraform report failed to generate."
+msgstr ""
+
+msgid "Terraform|A Terraform report was generated in your pipelines."
+msgstr ""
+
+msgid "Terraform|Generating the report caused an error."
+msgstr ""
+
+msgid "Terraform|Reported Resource Changes: %{addNum} to add, %{changeNum} to change, %{deleteNum} to delete"
+msgstr ""
+
+msgid "Terraform|The Terraform report %{name} failed to generate."
+msgstr ""
+
+msgid "Terraform|The Terraform report %{name} was generated in your pipelines."
+msgstr ""
+
msgid "Test"
msgstr ""
@@ -21730,7 +22453,7 @@ msgstr ""
msgid "TestHooks|Ensure the project has CI pipelines."
msgstr ""
-msgid "TestHooks|Ensure the project has at least one commit."
+msgid "TestHooks|Ensure the project has deployments."
msgstr ""
msgid "TestHooks|Ensure the project has issues."
@@ -21831,7 +22554,7 @@ msgstr ""
msgid "The Prometheus server responded with \"bad request\". Please check your queries are correct and are supported in your Prometheus version. %{documentationLink}"
msgstr ""
-msgid "The URL defined on the primary node that secondary nodes should use to contact it. Defaults to URL"
+msgid "The URL defined on the primary node that secondary nodes should use to contact it."
msgstr ""
msgid "The URL to use for connecting to Elasticsearch. Use a comma-separated list to support clustering (e.g., \"http://localhost:9200, http://localhost:9201\")."
@@ -21840,6 +22563,9 @@ msgstr ""
msgid "The X509 Certificate to use when mutual TLS is required to communicate with the external authorization service. If left blank, the server certificate is still validated when accessing over HTTPS."
msgstr ""
+msgid "The above settings apply to all projects with the selected compliance framework(s)."
+msgstr ""
+
msgid "The amount of seconds after which a request to get a secondary node status will time out."
msgstr ""
@@ -22139,9 +22865,6 @@ msgstr ""
msgid "The total stage shows the time it takes between creating an issue and deploying the code to production. The data will be automatically added once you have completed the full idea to production cycle."
msgstr ""
-msgid "The unique identifier for the Geo node. Must match %{geoNodeName} if it is set in gitlab.rb, otherwise it must match %{externalUrl} with a trailing slash"
-msgstr ""
-
msgid "The update action will time out after %{number_of_minutes} minutes. For big repositories, use a clone/push combination."
msgstr ""
@@ -22166,9 +22889,6 @@ msgstr ""
msgid "The user you are trying to deactivate has been active in the past %{minimum_inactive_days} days and cannot be deactivated"
msgstr ""
-msgid "The user-facing URL of the Geo node"
-msgstr ""
-
msgid "The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6."
msgstr ""
@@ -22259,6 +22979,9 @@ msgstr ""
msgid "There was a problem fetching project branches."
msgstr ""
+msgid "There was a problem fetching project tags."
+msgstr ""
+
msgid "There was a problem fetching project users."
msgstr ""
@@ -22310,6 +23033,9 @@ msgstr ""
msgid "There was an error fetching the %{replicableType}"
msgstr ""
+msgid "There was an error fetching the Geo Settings"
+msgstr ""
+
msgid "There was an error fetching the Node's Groups"
msgstr ""
@@ -22430,6 +23156,12 @@ msgstr ""
msgid "This %{issuable} is locked. Only <strong>project members</strong> can comment."
msgstr ""
+msgid "This %{noteableTypeText} is %{confidentialLinkStart}confidential%{linkEnd} and %{lockedLinkStart}locked%{linkEnd}."
+msgstr ""
+
+msgid "This %{noteableTypeText} is locked."
+msgstr ""
+
msgid "This %{viewer} could not be displayed because %{reason}. You can %{options} instead."
msgstr ""
@@ -22448,6 +23180,9 @@ msgstr ""
msgid "This action can lead to data loss. To prevent accidental actions we ask you to confirm your intention."
msgstr ""
+msgid "This also resolves all related threads"
+msgstr ""
+
msgid "This also resolves the discussion"
msgstr ""
@@ -22538,6 +23273,9 @@ msgstr ""
msgid "This epic does not exist or you don't have sufficient permission."
msgstr ""
+msgid "This feature is currently in beta. We invite you to %{linkStart}give feedback%{linkEnd}."
+msgstr ""
+
msgid "This feature requires local storage to be enabled"
msgstr ""
@@ -22571,7 +23309,7 @@ msgstr ""
msgid "This is a Work in Progress"
msgstr ""
-msgid "This is a confidential issue."
+msgid "This is a confidential %{noteableTypeText}."
msgstr ""
msgid "This is a delayed job to run in %{remainingTime}"
@@ -22598,9 +23336,6 @@ msgstr ""
msgid "This is your current session"
msgstr ""
-msgid "This issue is %{confidentialLinkStart}confidential%{linkEnd} and %{lockedLinkStart}locked%{linkEnd}."
-msgstr ""
-
msgid "This issue is confidential"
msgstr ""
@@ -22610,9 +23345,6 @@ msgstr ""
msgid "This issue is in a child epic of the filtered epic"
msgstr ""
-msgid "This issue is locked."
-msgstr ""
-
msgid "This job depends on other jobs with expired/erased artifacts: %{invalid_dependencies}"
msgstr ""
@@ -22712,6 +23444,9 @@ msgstr ""
msgid "This license has already expired."
msgstr ""
+msgid "This link points to external content"
+msgstr ""
+
msgid "This may expose confidential information as the selected fork is in another namespace that can have other members."
msgstr ""
@@ -22739,6 +23474,9 @@ msgstr ""
msgid "This page will be removed in a future release."
msgstr ""
+msgid "This pipeline does not use the %{codeStart}needs%{codeEnd} keyword and can't be represented as a directed acyclic graph."
+msgstr ""
+
msgid "This pipeline makes use of a predefined CI/CD configuration enabled by %{strongStart}Auto DevOps.%{strongEnd}"
msgstr ""
@@ -22967,6 +23705,9 @@ msgstr ""
msgid "Time from last commit to merge"
msgstr ""
+msgid "Time in seconds"
+msgstr ""
+
msgid "Time in seconds GitLab will wait for a response from the external service. When the service does not respond in time, access will be denied."
msgstr ""
@@ -23388,9 +24129,15 @@ msgstr ""
msgid "Total artifacts size: %{total_size}"
msgstr ""
+msgid "Total cores (CPUs)"
+msgstr ""
+
msgid "Total issues"
msgstr ""
+msgid "Total memory (GB)"
+msgstr ""
+
msgid "Total test time for all commits/merges"
msgstr ""
@@ -23592,6 +24339,9 @@ msgstr ""
msgid "U2F only works with HTTPS-enabled websites. Contact your administrator for more details."
msgstr ""
+msgid "UI Development Kit"
+msgstr ""
+
msgid "URL"
msgstr ""
@@ -23664,9 +24414,6 @@ msgstr ""
msgid "Unable to load the merge request widget. Try reloading the page."
msgstr ""
-msgid "Unable to process group import file"
-msgstr ""
-
msgid "Unable to resolve"
msgstr ""
@@ -23682,6 +24429,9 @@ msgstr ""
msgid "Unable to sign you in to the group with SAML due to \"%{reason}\""
msgstr ""
+msgid "Unable to suggest a path. Please refresh and try again."
+msgstr ""
+
msgid "Unable to update label prioritization at this time"
msgstr ""
@@ -23697,6 +24447,9 @@ msgstr ""
msgid "Unarchiving the project will restore people's ability to make changes to it. The repository can be committed to, and issues, comments, and other entities can be created. %{strong_start}Once active, this project shows up in the search and on the dashboard.%{strong_end}"
msgstr ""
+msgid "Unassign from commenting user"
+msgstr ""
+
msgid "Unblock"
msgstr ""
@@ -23772,6 +24525,9 @@ msgstr ""
msgid "Unreachable"
msgstr ""
+msgid "Unrecognized cluster type"
+msgstr ""
+
msgid "Unresolve"
msgstr ""
@@ -23832,6 +24588,9 @@ msgstr ""
msgid "Unsubscribes from this %{quick_action_target}."
msgstr ""
+msgid "Unsupported todo type passed. Supported todo types are: %{todo_types}"
+msgstr ""
+
msgid "Until"
msgstr ""
@@ -23859,6 +24618,9 @@ msgstr ""
msgid "Update approval rule"
msgstr ""
+msgid "Update approvers"
+msgstr ""
+
msgid "Update failed"
msgstr ""
@@ -23868,6 +24630,9 @@ msgstr ""
msgid "Update it"
msgstr ""
+msgid "Update iteration"
+msgstr ""
+
msgid "Update now"
msgstr ""
@@ -23913,7 +24678,7 @@ msgstr ""
msgid "Updated at"
msgstr ""
-msgid "Updated to"
+msgid "Updated to %{linkStart}chart v%{linkEnd}"
msgstr ""
msgid "Updating"
@@ -23943,6 +24708,9 @@ msgstr ""
msgid "Upload CSV file"
msgstr ""
+msgid "Upload License"
+msgstr ""
+
msgid "Upload New File"
msgstr ""
@@ -24024,16 +24792,19 @@ msgstr ""
msgid "UsageQuota|Pipelines"
msgstr ""
+msgid "UsageQuota|Purchase more storage"
+msgstr ""
+
msgid "UsageQuota|Repositories"
msgstr ""
msgid "UsageQuota|Repository"
msgstr ""
-msgid "UsageQuota|Storage"
+msgid "UsageQuota|Snippets"
msgstr ""
-msgid "UsageQuota|Storage usage:"
+msgid "UsageQuota|Storage"
msgstr ""
msgid "UsageQuota|This namespace has no projects which use shared runners"
@@ -24066,6 +24837,12 @@ msgstr ""
msgid "UsageQuota|Wikis"
msgstr ""
+msgid "UsageQuota|You used: %{usage} %{limit}"
+msgstr ""
+
+msgid "UsageQuota|out of %{formattedLimit} of your namespace storage"
+msgstr ""
+
msgid "Use %{code_start}::%{code_end} to create a %{link_start}scoped label set%{link_end} (eg. %{code_start}priority::1%{code_end})"
msgstr ""
@@ -24132,6 +24909,9 @@ msgstr ""
msgid "User IDs"
msgstr ""
+msgid "User Lists can only be created and modified with %{linkStart}the API%{linkEnd}"
+msgstr ""
+
msgid "User OAuth applications"
msgstr ""
@@ -24156,6 +24936,9 @@ msgstr ""
msgid "User key was successfully removed."
msgstr ""
+msgid "User list %{name} will be removed. Are you sure?"
+msgstr ""
+
msgid "User map"
msgstr ""
@@ -24177,148 +24960,10 @@ msgstr ""
msgid "User was successfully updated."
msgstr ""
-msgid "UserOnboardingTour|%{activeTour}/%{totalTours}"
-msgstr ""
-
-msgid "UserOnboardingTour|%{completed}/%{total} steps completed"
-msgstr ""
-
-msgid "UserOnboardingTour|%{emphasisStart}Well done!%{emphasisEnd}%{lineBreak}%{lineBreak}That's it for our guided tour, congratulations for making it all the way to the end!%{lineBreak}%{lineBreak}We hope this gave you a good overview of GitLab and how it can help you. We'll now show you how to create your own project and invite your colleagues."
-msgstr ""
-
-msgid "UserOnboardingTour|Adding other members to a project is done through Project Settings. Click on %{emphasisStart}Settings%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Alright, that's it for Commits. Let's take a look at the %{emphasisStart}Branches%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Awesome! Now click on %{emphasisStart}Members%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Click on one of the %{emphasisStart}Compare%{emphasisEnd} buttons to compare a branch to master."
-msgstr ""
-
-msgid "UserOnboardingTour|Click on one of the %{emphasisStart}pipeline IDs%{emphasisEnd} to see the details of a pipeline."
-msgstr ""
-
-msgid "UserOnboardingTour|Click to open the latest commit to see its details."
-msgstr ""
-
-msgid "UserOnboardingTour|Close 'Learn GitLab'"
-msgstr ""
-
-msgid "UserOnboardingTour|Commits are shown in chronological order and can be filtered by the commit message or by the branch."
+msgid "UserList|Delete %{name}?"
msgstr ""
-msgid "UserOnboardingTour|Create a project"
-msgstr ""
-
-msgid "UserOnboardingTour|Exit 'Learn GitLab'"
-msgstr ""
-
-msgid "UserOnboardingTour|Got it"
-msgstr ""
-
-msgid "UserOnboardingTour|Great job! %{clapHands} We hope the tour was helpful and that you learned how to use GitLab.%{lineBreak}%{lineBreak}We'd love to get your feedback on this tour.%{lineBreak}%{lineBreak}%{emphasisStart}How helpful would you say this guided tour was?%{emphasisEnd}%{lineBreak}%{lineBreak}"
-msgstr ""
-
-msgid "UserOnboardingTour|Guided GitLab Tour"
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can compare the changes of this branch to another one. Changes are divided by files so that it's easier to see what was changed where."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can create a project from scratch, start with a template or import a repository from other platforms. Whatever you choose, we'll guide you through the process.%{lineBreak}%{lineBreak}Fill in your new project information and click on %{emphasisStart}Create Project%{emphasisEnd} to progress to the next step."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can see the breakdown of the pipelines: its stages and jobs in each of the stages and their status.%{lineBreak}%{lineBreak}Our CI/CD pipelines are quite complex, most of our users have fewer and simpler pipelines."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can see the current members of the project (just you at the moment) and invite new members.%{lineBreak}%{lineBreak}You can invite multiple members at once (existing GitLab users or invite by email) and you can also set their roles and permissions.%{lineBreak}%{lineBreak}Add a few members and click on %{emphasisStart}Add to project%{emphasisEnd} to complete this step."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can see what changes were made with this commit, on what branch and if there's a related merge request. The status of the pipeline will also show up if CI/CD is set up.%{lineBreak}%{lineBreak}You can also comment on the lines of code that were changed and start a discussion with your colleagues!"
-msgstr ""
-
-msgid "UserOnboardingTour|Here's an overview of branches in the %{emphasisStart}%{projectName}%{emphasisEnd} project. They're split into Active and Stale.%{lineBreak}%{lineBreak}From here, you can create a new merge request from a branch, or compare the branch to any other branch in the project. By default, it will compare it to the master branch."
-msgstr ""
-
-msgid "UserOnboardingTour|Invite colleagues"
-msgstr ""
-
-msgid "UserOnboardingTour|Issues are great for communicating and keeping track of progress in GitLab. These are all issues that are open in the %{emphasisStart}%{projectName}%{emphasisEnd}.%{lineBreak}%{lineBreak}You can help us improve GitLab by contributing work to issues that are labeled <span class=\"badge color-label accept-mr-label\">Accepting merge requests</span>.%{lineBreak}%{lineBreak}This list can be filtered by labels, milestones, assignees, authors... We'll show you how it looks when the list is filtered by a label."
-msgstr ""
-
-msgid "UserOnboardingTour|Learn GitLab"
-msgstr ""
-
-msgid "UserOnboardingTour|Let's take a closer look at a merge request. Click on the title of one."
-msgstr ""
-
-msgid "UserOnboardingTour|Let's take a closer look at all the commits. Click on %{emphasisStart}Commits%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Let's take a closer look at the repository of this project. Click on %{emphasisStart}Repository%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|No thanks"
-msgstr ""
-
-msgid "UserOnboardingTour|Ok, let's go"
-msgstr ""
-
-msgid "UserOnboardingTour|Ok, show me"
-msgstr ""
-
-msgid "UserOnboardingTour|Open one of the issues by clicking on its title."
-msgstr ""
-
-msgid "UserOnboardingTour|Restart this step"
-msgstr ""
-
-msgid "UserOnboardingTour|Skip this step"
-msgstr ""
-
-msgid "UserOnboardingTour|Sweet! Your project was created and is ready to be used.%{lineBreak}%{lineBreak}You can start adding files to the repository or clone it. One last thing we want to show you is how to invite your colleagues to your new project."
-msgstr ""
-
-msgid "UserOnboardingTour|Take a look. Here's a nifty menu for quickly creating issues, merge requests, snippets, projects and groups. Click on it and select \"New project\" from the \"GitLab\" section to get started."
-msgstr ""
-
-msgid "UserOnboardingTour|Thanks for taking the guided tour. Remember, if you want to go through it again, you can start %{emphasisStart}Learn GitLab%{emphasisEnd} in the help menu on the top right."
-msgstr ""
-
-msgid "UserOnboardingTour|Thanks for the feedback! %{thumbsUp}"
-msgstr ""
-
-msgid "UserOnboardingTour|That's it for issues. Let'st take a look at %{emphasisStart}Merge Requests%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|That's it for merge requests. Now for the final part of this guided tour - the %{emphasisStart}CI/CD%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|That's it for the Repository. Let's take a look at the %{emphasisStart}Issues%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|The structure of this page is very similar to issues. Status, description, discussion and the sidebar are all here.%{lineBreak}%{lineBreak}But take a look below the description and you'll notice that there's more information about the merge request, the CI/CD pipeline and the options for approving it.%{lineBreak}%{lineBreak}Alongside the discussion you can also see more information about commits in this merge request, the status of pipelines and review all changes that were made."
-msgstr ""
-
-msgid "UserOnboardingTour|There's a lot of information here but don't worry, we'll go through it.%{lineBreak}%{lineBreak}On the top you can see the status of the issue and when it was opened and by whom. Directly below it is the issue description and below that are other %{emphasisStart}related issues%{emphasisEnd} and %{emphasisStart}merge requests%{emphasisEnd} (if any). Then below that is the %{emphasisStart}discussion%{emphasisEnd}, that's where most of the communication happens.%{lineBreak}%{lineBreak}On the right, there's a sidebar where you can view/change the %{emphasisStart}assignee, milestone, due date, labels, weight%{emphasisEnd}, etc."
-msgstr ""
-
-msgid "UserOnboardingTour|These are all the CI/CD pipelines we have for our %{emphasisStart}%{projectName}%{emphasisEnd} project.%{lineBreak}%{lineBreak}Here you can see the status of each pipeline, for what commit it's running for, its stages and the status for them."
-msgstr ""
-
-msgid "UserOnboardingTour|These are all the issues that are available for community contributions. Let's take a closer look at one of them."
-msgstr ""
-
-msgid "UserOnboardingTour|This is an overview of all merge requests in this project. Similarly to the issues overview it can be filtered down by things like labels, milestones, authors, assignees, etc."
-msgstr ""
-
-msgid "UserOnboardingTour|This is the repository for the %{emphasisStart}%{projectName}%{emphasisEnd} project. All our code is stored here. Feel free to explore and take a closer look at folders and files.%{lineBreak}%{lineBreak}Above the file structure you can see the latest commit, who the author is and the status of the CI/CD pipeline.%{lineBreak}%{lineBreak}If you scroll down below the file structure, you'll find the Readme of this project. This is defined in the README.md file at the root of the repository."
-msgstr ""
-
-msgid "UserOnboardingTour|Welcome to the project overview of the %{emphasisStart}%{projectName}%{emphasisEnd} project. This is the project that we use to work on GitLab. At first, a project seems like a simple repository, but at GitLab, a project is so much more.%{lineBreak}%{lineBreak}You can create projects for hosting your codebase, use it as an issue tracker, collaborate on code, and continuously build, test, and deploy your app with built-in GitLab CI/CD."
+msgid "UserList|created %{timeago}"
msgstr ""
msgid "UserProfile|Activity"
@@ -24471,6 +25116,9 @@ msgstr ""
msgid "UsersSelect|Unassigned"
msgstr ""
+msgid "Using %{codeStart}needs%{codeEnd} allows jobs to run before their stage is reached, as soon as their individual dependencies are met, which speeds up your pipelines."
+msgstr ""
+
msgid "Using %{code_start}::%{code_end} denotes a %{link_start}scoped label set%{link_end}"
msgstr ""
@@ -24555,9 +25203,6 @@ msgstr ""
msgid "Versions"
msgstr ""
-msgid "Very helpful"
-msgstr ""
-
msgid "View Documentation"
msgstr ""
@@ -24706,6 +25351,9 @@ msgstr ""
msgid "VisibilityLevel|Unknown"
msgstr ""
+msgid "Visit settings page"
+msgstr ""
+
msgid "VisualReviewApp|%{stepStart}Step 1%{stepEnd}. Copy the following script:"
msgstr ""
@@ -24799,6 +25447,9 @@ msgstr ""
msgid "VulnerabilityManagement|Something went wrong while trying to delete the comment. Please try again later."
msgstr ""
+msgid "VulnerabilityManagement|Something went wrong while trying to refresh the vulnerability. Please try again later."
+msgstr ""
+
msgid "VulnerabilityManagement|Something went wrong while trying to retrieve the vulnerability history. Please try again later."
msgstr ""
@@ -24841,6 +25492,9 @@ msgstr ""
msgid "Vulnerability|Class"
msgstr ""
+msgid "Vulnerability|Crash Address"
+msgstr ""
+
msgid "Vulnerability|Description"
msgstr ""
@@ -24916,9 +25570,6 @@ msgstr ""
msgid "We couldn't reach the Prometheus server. Either the server no longer exists or the configuration details need updating."
msgstr ""
-msgid "We created a short guided tour that will help you learn the basics of GitLab and how it will help you be better at your job. It should only take a couple of minutes. You will be guided by two types of helpers, best recognized by their color."
-msgstr ""
-
msgid "We detected potential spam in the %{humanized_resource_name}. Please solve the reCAPTCHA to proceed."
msgstr ""
@@ -24952,6 +25603,9 @@ msgstr ""
msgid "We've found no vulnerabilities"
msgstr ""
+msgid "Web Application Firewall"
+msgstr ""
+
msgid "Web IDE"
msgstr ""
@@ -25012,9 +25666,6 @@ msgstr ""
msgid "Welcome to GitLab.com<br>@%{name}!"
msgstr ""
-msgid "Welcome to the Guided GitLab Tour"
-msgstr ""
-
msgid "Welcome to the guided GitLab tour"
msgstr ""
@@ -25062,9 +25713,6 @@ msgstr ""
msgid "While it's rare to have no vulnerabilities, it can happen. In any event, we ask that you please double check your settings to make sure you've set up your dashboard correctly."
msgstr ""
-msgid "White helpers give contextual information."
-msgstr ""
-
msgid "Who can be an approver?"
msgstr ""
@@ -25110,12 +25758,18 @@ msgstr ""
msgid "WikiEmptyIssueMessage|Suggest wiki improvement"
msgstr ""
+msgid "WikiEmptyIssueMessage|You must be a group member in order to add wiki pages. If you have suggestions for how to improve the wiki for this group, consider opening an issue in the %{issues_link}."
+msgstr ""
+
msgid "WikiEmptyIssueMessage|You must be a project member in order to add wiki pages. If you have suggestions for how to improve the wiki for this project, consider opening an issue in the %{issues_link}."
msgstr ""
msgid "WikiEmptyIssueMessage|issue tracker"
msgstr ""
+msgid "WikiEmpty|A wiki is where you can store all the details about your group. This can include why you've created it, its principles, how to use it, and so on."
+msgstr ""
+
msgid "WikiEmpty|A wiki is where you can store all the details about your project. This can include why you've created it, its principles, how to use it, and so on."
msgstr ""
@@ -25125,12 +25779,21 @@ msgstr ""
msgid "WikiEmpty|Suggest wiki improvement"
msgstr ""
+msgid "WikiEmpty|The wiki lets you write documentation for your group"
+msgstr ""
+
msgid "WikiEmpty|The wiki lets you write documentation for your project"
msgstr ""
+msgid "WikiEmpty|This group has no wiki pages"
+msgstr ""
+
msgid "WikiEmpty|This project has no wiki pages"
msgstr ""
+msgid "WikiEmpty|You must be a group member in order to add wiki pages."
+msgstr ""
+
msgid "WikiEmpty|You must be a project member in order to add wiki pages."
msgstr ""
@@ -25407,13 +26070,13 @@ msgstr ""
msgid "You can now export your security dashboard to a CSV report."
msgstr ""
-msgid "You can now submit a merge request to get this change into the original branch."
+msgid "You can now manage alert endpoint configuration in the Alerts section on the Operations settings page. Fields on this page have been deprecated."
msgstr ""
-msgid "You can now submit a merge request to get this change into the original project."
+msgid "You can now submit a merge request to get this change into the original branch."
msgstr ""
-msgid "You can only add files when you are on a branch"
+msgid "You can now submit a merge request to get this change into the original project."
msgstr ""
msgid "You can only edit files when you are on a branch"
@@ -25635,7 +26298,7 @@ msgstr ""
msgid "You need to upload a Google Takeout archive."
msgstr ""
-msgid "You reached %{usage_in_percent} of %{namespace_name}'s capacity (%{used_storage} of %{storage_limit})"
+msgid "You reached %{usage_in_percent} of %{namespace_name}'s storage capacity (%{used_storage} of %{storage_limit})"
msgstr ""
msgid "You tried to fork %{link_to_the_project} but it failed for the following reason:"
@@ -25938,13 +26601,13 @@ msgstr ""
msgid "Your subscription expired!"
msgstr ""
-msgid "Your subscription has been downgraded"
+msgid "Your subscription has been downgraded."
msgstr ""
-msgid "Your subscription will automatically renew in %{remaining_days}"
+msgid "Your subscription will automatically renew in %{remaining_days}."
msgstr ""
-msgid "Your subscription will expire in %{remaining_days}"
+msgid "Your subscription will expire in %{remaining_days}."
msgstr ""
msgid "Zoom meeting added"
@@ -26024,9 +26687,6 @@ msgstr ""
msgid "branch name"
msgstr ""
-msgid "build pipeline reference mismatch"
-msgstr ""
-
msgid "by"
msgstr ""
@@ -26045,6 +26705,9 @@ msgstr ""
msgid "cannot block others"
msgstr ""
+msgid "cannot contain HTML/XML tags, including any word between angle brackets (<,>)."
+msgstr ""
+
msgid "cannot include leading slash or directory traversal."
msgstr ""
@@ -26075,55 +26738,6 @@ msgstr ""
msgid "ciReport|%{remainingPackagesCount} more"
msgstr ""
-msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability"
-msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability for the source branch only"
-msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities for the source branch only"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerability"
-msgid_plural "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerabilities"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new vulnerability"
-msgid_plural "ciReport|%{reportType} %{status} detected %{newCount} new vulnerabilities"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities for the source branch only"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{fixedCount} fixed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} vulnerability for the source branch only"
-msgid_plural "ciReport|%{reportType} %{status} detected %{newCount} vulnerabilities for the source branch only"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "ciReport|%{reportType} %{status} detected no new vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected no vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected no vulnerabilities for the source branch only"
-msgstr ""
-
msgid "ciReport|%{reportType} is loading"
msgstr ""
@@ -26142,7 +26756,7 @@ msgstr ""
msgid "ciReport|All projects"
msgstr ""
-msgid "ciReport|All report types"
+msgid "ciReport|All scanner types"
msgstr ""
msgid "ciReport|All severities"
@@ -26166,6 +26780,9 @@ msgstr ""
msgid "ciReport|Container scanning detects known vulnerabilities in your docker images."
msgstr ""
+msgid "ciReport|Coverage Fuzzing"
+msgstr ""
+
msgid "ciReport|Create a merge request to implement this solution, or download and apply the patch manually."
msgstr ""
@@ -26324,6 +26941,9 @@ msgstr ""
msgid "customize"
msgstr ""
+msgid "data"
+msgstr ""
+
msgid "date must not be after 9999-12-31"
msgstr ""
@@ -26394,6 +27014,9 @@ msgstr ""
msgid "entries cannot contain HTML tags"
msgstr ""
+msgid "epic"
+msgstr ""
+
msgid "error"
msgstr ""
@@ -26415,9 +27038,6 @@ msgstr ""
msgid "expires on %{timebox_due_date}"
msgstr ""
-msgid "external_url"
-msgstr ""
-
msgid "failed"
msgstr ""
@@ -26453,9 +27073,6 @@ msgstr ""
msgid "from"
msgstr ""
-msgid "geo_node_name"
-msgstr ""
-
msgid "group"
msgstr ""
@@ -26477,9 +27094,6 @@ msgstr ""
msgid "https://your-bitbucket-server"
msgstr ""
-msgid "image"
-msgstr ""
-
msgid "image diff"
msgstr ""
@@ -26623,6 +27237,9 @@ msgstr ""
msgid "load it anyway"
msgstr ""
+msgid "loading"
+msgstr ""
+
msgid "locked by %{path_lock_user_name} %{created_at}"
msgstr ""
@@ -26751,9 +27368,6 @@ msgstr ""
msgid "mrWidget|Deployment statistics are not available currently"
msgstr ""
-msgid "mrWidget|Detect issues before deployment with a CI pipeline that continuously tests your code. We created a quick guide that will show you how to create one. Make your code more secure and more robust in just a minute."
-msgstr ""
-
msgid "mrWidget|Did not close"
msgstr ""
@@ -26916,6 +27530,9 @@ msgstr ""
msgid "mrWidget|To approve this merge request, please enter your password. This project requires all approvals to be authenticated."
msgstr ""
+msgid "mrWidget|Use %{linkStart}CI pipelines to test your code%{linkEnd}, simply add a GitLab CI configuration file to your project. It only takes a minute to make your code more secure and robust."
+msgstr ""
+
msgid "mrWidget|When this merge request is ready, remove the WIP: prefix from the title to allow it to be merged"
msgstr ""
@@ -26952,6 +27569,9 @@ msgstr ""
msgid "must be greater than start date"
msgstr ""
+msgid "must contain only valid frameworks"
+msgstr ""
+
msgid "my-awesome-group"
msgstr ""
@@ -26964,6 +27584,9 @@ msgstr ""
msgid "needs to be between 10 minutes and 1 month"
msgstr ""
+msgid "never"
+msgstr ""
+
msgid "never expires"
msgstr ""
@@ -26991,6 +27614,9 @@ msgstr ""
msgid "nounSeries|%{firstItem} and %{lastItem}"
msgstr ""
+msgid "nounSeries|%{item}"
+msgstr ""
+
msgid "nounSeries|%{item}, %{nextItem}"
msgstr ""
@@ -27127,6 +27753,9 @@ msgstr ""
msgid "revised"
msgstr ""
+msgid "satisfied"
+msgstr ""
+
msgid "score"
msgstr ""
@@ -27157,6 +27786,9 @@ msgstr ""
msgid "severity|Unknown"
msgstr ""
+msgid "should be an array of %{object_name} objects"
+msgstr ""
+
msgid "should be greater than or equal to %{access} inherited membership from group %{group_name}"
msgstr ""
@@ -27211,10 +27843,13 @@ msgstr ""
msgid "suggestPipeline|2/2: Commit your changes"
msgstr ""
-msgid "suggestPipeline|Commit the changes and your pipeline will automatically run for the first time."
+msgid "suggestPipeline|Choose %{boldStart}Code Quality%{boldEnd} to add a pipeline that tests the quality of your code."
msgstr ""
-msgid "suggestPipeline|We recommend the %{boldStart}Code Quality%{boldEnd} template, which will add a report widget to your Merge Requests. This way you’ll learn about code quality degradations much sooner. %{footerStart} Goodbye technical debt! %{footerEnd}"
+msgid "suggestPipeline|The template is ready! You can now commit it to create your first pipeline."
+msgstr ""
+
+msgid "suggestPipeline|We’re adding a GitLab CI configuration file to add a pipeline to the project. You could create it manually, but we recommend that you start with a GitLab template that works out of the box."
msgstr ""
msgid "syntax is correct"
@@ -27274,6 +27909,9 @@ msgstr ""
msgid "uses Kubernetes clusters to deploy your code!"
msgstr ""
+msgid "v%{version} published %{timeAgo}"
+msgstr ""
+
msgid "verify ownership"
msgstr ""
diff --git a/locale/bg/gitlab.po b/locale/bg/gitlab.po
index 3b400885dfb..4dfe1fcd220 100644
--- a/locale/bg/gitlab.po
+++ b/locale/bg/gitlab.po
@@ -2,7 +2,7 @@ msgid ""
msgstr ""
"Project-Id-Version: gitlab-ee\n"
"Report-Msgid-Bugs-To: \n"
-"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Last-Translator: \n"
"Language-Team: Bulgarian\n"
"Language: bg_BG\n"
"MIME-Version: 1.0\n"
@@ -10,9 +10,11 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Crowdin-Project: gitlab-ee\n"
+"X-Crowdin-Project-ID: 288872\n"
"X-Crowdin-Language: bg\n"
"X-Crowdin-File: /master/locale/gitlab.pot\n"
-"PO-Revision-Date: 2020-06-08 15:15\n"
+"X-Crowdin-File-ID: 6\n"
+"PO-Revision-Date: 2020-07-02 01:24\n"
msgid " %{start} to %{end}"
msgstr ""
@@ -197,6 +199,11 @@ msgid_plural "%d metrics"
msgstr[0] ""
msgstr[1] ""
+msgid "%d milestone"
+msgid_plural "%d milestones"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "%d minute"
msgid_plural "%d minutes"
msgstr[0] ""
@@ -242,11 +249,6 @@ msgid_plural "%d unresolved threads"
msgstr[0] ""
msgstr[1] ""
-msgid "%d url scanned"
-msgid_plural "%d urls scanned"
-msgstr[0] ""
-msgstr[1] ""
-
msgid "%d vulnerability"
msgid_plural "%d vulnerabilities"
msgstr[0] ""
@@ -324,9 +326,15 @@ msgstr[1] ""
msgid "%{count} related %{pluralized_subject}: %{links}"
msgstr ""
+msgid "%{dashboard_path} could not be found."
+msgstr ""
+
msgid "%{days} days until tags are automatically removed"
msgstr ""
+msgid "%{deployLinkStart}Use a template to deploy to ECS%{deployLinkEnd}, or use a docker image to %{commandsLinkStart}run AWS commands in GitLab CI/CD%{commandsLinkEnd}."
+msgstr ""
+
msgid "%{description}- Sentry event: %{errorUrl}- First seen: %{firstSeen}- Last seen: %{lastSeen} %{countLabel}: %{count}%{userCountLabel}: %{userCount}"
msgstr ""
@@ -369,12 +377,18 @@ msgstr ""
msgid "%{host} sign-in from new location"
msgstr ""
-msgid "%{icon}You are about to add %{usersTag} people to the discussion. Proceed with caution."
+msgid "%{icon}You are about to add %{usersTag} people to the discussion. They will all receive a notification."
+msgstr ""
+
+msgid "%{integrations_link_start}Integrations%{link_end} enable you to make third-party applications part of your GitLab workflow. If the available integrations don't meet your needs, consider using a %{webhooks_link_start}webhook%{link_end}."
msgstr ""
msgid "%{issuableType} will be removed! Are you sure?"
msgstr ""
+msgid "%{issuesCount} issues in this group"
+msgstr ""
+
msgid "%{issuesSize} issues"
msgstr ""
@@ -384,7 +398,7 @@ msgstr ""
msgid "%{labelStart}Class:%{labelEnd} %{class}"
msgstr ""
-msgid "%{labelStart}Confidence:%{labelEnd} %{confidence}"
+msgid "%{labelStart}Evidence:%{labelEnd} %{evidence}"
msgstr ""
msgid "%{labelStart}File:%{labelEnd} %{file}"
@@ -402,6 +416,9 @@ msgstr ""
msgid "%{labelStart}Report Type:%{labelEnd} %{reportType}"
msgstr ""
+msgid "%{labelStart}Scanner:%{labelEnd} %{scanner}"
+msgstr ""
+
msgid "%{labelStart}Severity:%{labelEnd} %{severity}"
msgstr ""
@@ -426,9 +443,6 @@ msgstr ""
msgid "%{link_start}Read more%{link_end} about role permissions"
msgstr ""
-msgid "%{link} can be used for binding events when something is happening within the project."
-msgstr ""
-
msgid "%{listToShow}, and %{awardsListLength} more."
msgstr ""
@@ -462,6 +476,9 @@ msgstr ""
msgid "%{name} found %{resultsString}"
msgstr ""
+msgid "%{name} is already being used for another emoji"
+msgstr ""
+
msgid "%{name} is scheduled for %{action}"
msgstr ""
@@ -509,6 +526,9 @@ msgstr ""
msgid "%{primary} (%{secondary})"
msgstr ""
+msgid "%{ref} cannot be added: %{error}"
+msgstr ""
+
msgid "%{releases} release"
msgid_plural "%{releases} releases"
msgstr[0] ""
@@ -517,13 +537,37 @@ msgstr[1] ""
msgid "%{remaining_approvals} left"
msgstr ""
-msgid "%{retryButtonStart}Try again%{retryButtonEnd} or %{newFileButtonStart}attach a new file%{newFileButtonEnd}"
+msgid "%{reportType} %{status} detected %{critical} critical and %{high} high severity vulnerabilities out of %{total}."
+msgstr ""
+
+msgid "%{reportType} %{status} detected %{critical} critical and %{high} high severity vulnerabilities."
+msgstr ""
+
+msgid "%{reportType} %{status} detected %{critical} critical severity vulnerabilities out of %{total}."
+msgstr ""
+
+msgid "%{reportType} %{status} detected %{critical} critical severity vulnerability."
+msgid_plural "%{reportType} %{status} detected %{critical} critical severity vulnerabilities."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{reportType} %{status} detected %{high} high severity vulnerabilities out of %{total}."
msgstr ""
-msgid "%{screenreaderOnlyStart}Keyboard shorcuts%{screenreaderOnlyEnd} Disabled"
+msgid "%{reportType} %{status} detected %{high} high severity vulnerability."
+msgid_plural "%{reportType} %{status} detected %{high} high severity vulnerabilities."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{reportType} %{status} detected %{other} vulnerability."
+msgid_plural "%{reportType} %{status} detected %{other} vulnerabilities."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{reportType} %{status} detected no new vulnerabilities."
msgstr ""
-msgid "%{screenreaderOnlyStart}Keyboard shorcuts%{screenreaderOnlyEnd} Enabled"
+msgid "%{retryButtonStart}Try again%{retryButtonEnd} or %{newFileButtonStart}attach a new file%{newFileButtonEnd}"
msgstr ""
msgid "%{service_title} %{message}."
@@ -620,6 +664,12 @@ msgstr ""
msgid "%{token}..."
msgstr ""
+msgid "%{totalCpu} (%{freeSpacePercentage}%{percentSymbol} free)"
+msgstr ""
+
+msgid "%{totalMemory} (%{freeSpacePercentage}%{percentSymbol} free)"
+msgstr ""
+
msgid "%{totalWeight} total weight"
msgstr ""
@@ -650,6 +700,12 @@ msgstr ""
msgid "%{verb} %{time_spent_value} spent time."
msgstr ""
+msgid "%{webhooks_link_start}%{webhook_type}%{link_end} enable you to send notifications to web applications in response to events in a group or project."
+msgstr ""
+
+msgid "%{webhooks_link_start}%{webhook_type}%{link_end} enable you to send notifications to web applications in response to events in a group or project. We recommend using an %{integrations_link_start}integration%{link_end} in preference to a webhook."
+msgstr ""
+
msgid "'%{level}' is not a valid visibility level"
msgstr ""
@@ -676,9 +732,6 @@ msgstr ""
msgid "(No changes)"
msgstr ""
-msgid "(Show all)"
-msgstr ""
-
msgid "(check progress)"
msgstr ""
@@ -745,6 +798,9 @@ msgstr ""
msgid "0 for unlimited"
msgstr ""
+msgid "0 for unlimited, only effective with remote storage enabled."
+msgstr ""
+
msgid "1 %{type} addition"
msgid_plural "%{count} %{type} additions"
msgstr[0] ""
@@ -892,6 +948,12 @@ msgstr ""
msgid "<code>\"johnsmith@example.com\": \"johnsmith@example.com\"</code> will add \"By <a href=\"#\">johnsmith@example.com</a>\" to all issues and comments originally created by johnsmith@example.com. By default, the email address or username is masked to ensure the user's privacy. Use this option if you want to show the full email address."
msgstr ""
+msgid "<code>Masked</code> to prevent the values from being displayed in job logs (must match certain regexp requirements)."
+msgstr ""
+
+msgid "<code>Protected</code> to expose them to protected branches or tags only."
+msgstr ""
+
msgid "<namespace / project>"
msgstr ""
@@ -919,9 +981,6 @@ msgstr ""
msgid "A .NET Core console application template, customizable for any .NET Core project"
msgstr ""
-msgid "A DAG must have two dependent jobs to be visualized on this tab."
-msgstr ""
-
msgid "A GitBook site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
msgstr ""
@@ -952,6 +1011,12 @@ msgstr ""
msgid "A deleted user"
msgstr ""
+msgid "A file has been changed."
+msgstr ""
+
+msgid "A file was not found."
+msgstr ""
+
msgid "A file with '%{file_name}' already exists in %{branch} branch"
msgstr ""
@@ -1018,7 +1083,7 @@ msgstr ""
msgid "A subscription will trigger a new pipeline on the default branch of this project when a pipeline successfully completes for a new tag on the %{default_branch_docs} of the subscribed project."
msgstr ""
-msgid "A terraform report was generated in your pipelines."
+msgid "A suggestion is not applicable."
msgstr ""
msgid "A user with write access to the source branch selected this option"
@@ -1189,6 +1254,9 @@ msgstr ""
msgid "Action to take when receiving an alert."
msgstr ""
+msgid "Actions"
+msgstr ""
+
msgid "Activate"
msgstr ""
@@ -1245,6 +1313,9 @@ msgstr ""
msgid "Add LICENSE"
msgstr ""
+msgid "Add New Node"
+msgstr ""
+
msgid "Add README"
msgstr ""
@@ -1320,6 +1391,9 @@ msgstr ""
msgid "Add approval rule"
msgstr ""
+msgid "Add approvers"
+msgstr ""
+
msgid "Add bold text"
msgstr ""
@@ -1377,6 +1451,9 @@ msgstr ""
msgid "Add strikethrough text"
msgstr ""
+msgid "Add suggestion to batch"
+msgstr ""
+
msgid "Add system hook"
msgstr ""
@@ -1476,6 +1553,9 @@ msgstr ""
msgid "Admin Note"
msgstr ""
+msgid "Admin Notifications"
+msgstr ""
+
msgid "Admin Overview"
msgstr ""
@@ -1587,6 +1667,9 @@ msgstr ""
msgid "AdminSettings|Integrations configured here will automatically apply to all projects on this instance."
msgstr ""
+msgid "AdminSettings|Moved to integrations"
+msgstr ""
+
msgid "AdminSettings|No required pipeline"
msgstr ""
@@ -1817,12 +1900,18 @@ msgid_plural "Alerts"
msgstr[0] ""
msgstr[1] ""
+msgid "AlertManagement| assign yourself"
+msgstr ""
+
msgid "AlertManagement|Acknowledged"
msgstr ""
msgid "AlertManagement|Alert"
msgstr ""
+msgid "AlertManagement|Alert assignee(s): %{assignees}"
+msgstr ""
+
msgid "AlertManagement|Alert detail"
msgstr ""
@@ -1838,9 +1927,15 @@ msgstr ""
msgid "AlertManagement|All alerts"
msgstr ""
+msgid "AlertManagement|Assign To"
+msgstr ""
+
msgid "AlertManagement|Assign status"
msgstr ""
+msgid "AlertManagement|Assignee"
+msgstr ""
+
msgid "AlertManagement|Assignees"
msgstr ""
@@ -1859,9 +1954,6 @@ msgstr ""
msgid "AlertManagement|Edit"
msgstr ""
-msgid "AlertManagement|End time"
-msgstr ""
-
msgid "AlertManagement|Events"
msgstr ""
@@ -1880,10 +1972,13 @@ msgstr ""
msgid "AlertManagement|More information"
msgstr ""
+msgid "AlertManagement|No Matching Results"
+msgstr ""
+
msgid "AlertManagement|No alert data to display."
msgstr ""
-msgid "AlertManagement|No alerts available to display. If you think you're seeing this message in error, refresh the page."
+msgid "AlertManagement|No alerts available to display. See %{linkStart}enabling alert management%{linkEnd} for more information on adding alerts to the list."
msgstr ""
msgid "AlertManagement|No alerts to display."
@@ -1892,6 +1987,9 @@ msgstr ""
msgid "AlertManagement|None"
msgstr ""
+msgid "AlertManagement|None -"
+msgstr ""
+
msgid "AlertManagement|Open"
msgstr ""
@@ -1928,9 +2026,18 @@ msgstr ""
msgid "AlertManagement|There was an error displaying the alerts. Confirm your endpoint's configuration details to ensure alerts appear."
msgstr ""
+msgid "AlertManagement|There was an error while updating the assignee(s) list. Please try again."
+msgstr ""
+
+msgid "AlertManagement|There was an error while updating the assignee(s) of the alert. Please try again."
+msgstr ""
+
msgid "AlertManagement|There was an error while updating the status of the alert. Please try again."
msgstr ""
+msgid "AlertManagement|This assignee cannot be assigned to this alert."
+msgstr ""
+
msgid "AlertManagement|Tool"
msgstr ""
@@ -2078,6 +2185,9 @@ msgstr ""
msgid "Allow users to request access (if visibility is public or internal)"
msgstr ""
+msgid "Allowed Geo IP"
+msgstr ""
+
msgid "Allowed email domain restriction only permitted for top-level groups"
msgstr ""
@@ -2111,6 +2221,9 @@ msgstr ""
msgid "Amazon Web Services"
msgstr ""
+msgid "Amazon Web Services Logo"
+msgstr ""
+
msgid "Amazon authentication is not %{link_start}correctly configured%{link_end}. Ask your GitLab administrator if you want to use this service."
msgstr ""
@@ -2168,10 +2281,16 @@ msgstr ""
msgid "An error occurred when updating the issue weight"
msgstr ""
+msgid "An error occurred while acknowledging the notification. Refresh the page and try again."
+msgstr ""
+
+msgid "An error occurred while adding approvers"
+msgstr ""
+
msgid "An error occurred while adding formatted title for epic"
msgstr ""
-msgid "An error occurred while checking group path"
+msgid "An error occurred while checking group path. Please refresh and try again."
msgstr ""
msgid "An error occurred while committing your changes."
@@ -2315,7 +2434,7 @@ msgstr ""
msgid "An error occurred while loading milestones"
msgstr ""
-msgid "An error occurred while loading terraform report"
+msgid "An error occurred while loading project creation UI"
msgstr ""
msgid "An error occurred while loading the data. Please try again."
@@ -2369,6 +2488,9 @@ msgstr ""
msgid "An error occurred while rendering preview broadcast message"
msgstr ""
+msgid "An error occurred while rendering the editor"
+msgstr ""
+
msgid "An error occurred while reordering issues."
msgstr ""
@@ -2465,7 +2587,7 @@ msgstr ""
msgid "Analyze your dependencies for known vulnerabilities."
msgstr ""
-msgid "Analyze your source code and git history for secrets"
+msgid "Analyze your source code and git history for secrets."
msgstr ""
msgid "Analyze your source code for known vulnerabilities."
@@ -2579,12 +2701,18 @@ msgstr ""
msgid "Apply suggestion"
msgstr ""
+msgid "Apply suggestions"
+msgstr ""
+
msgid "Apply template"
msgstr ""
msgid "Apply this approval rule to any branch or a specific protected branch."
msgstr ""
+msgid "Applying"
+msgstr ""
+
msgid "Applying a template will replace the existing issue description. Any changes you have made will be lost."
msgstr ""
@@ -2597,12 +2725,18 @@ msgstr ""
msgid "Applying multiple commands"
msgstr ""
-msgid "Applying suggestion"
+msgid "Applying suggestion..."
+msgstr ""
+
+msgid "Applying suggestions..."
msgstr ""
msgid "Approval rules"
msgstr ""
+msgid "Approval rules reset to project defaults"
+msgstr ""
+
msgid "ApprovalRuleRemove|%d member"
msgid_plural "ApprovalRuleRemove|%d members"
msgstr[0] ""
@@ -2656,6 +2790,9 @@ msgstr ""
msgid "Approved"
msgstr ""
+msgid "Approved MRs"
+msgstr ""
+
msgid "Approved by: "
msgstr ""
@@ -2869,6 +3006,9 @@ msgstr ""
msgid "Assign"
msgstr ""
+msgid "Assign Iteration"
+msgstr ""
+
msgid "Assign custom color like #FF0000"
msgstr ""
@@ -2887,6 +3027,9 @@ msgstr ""
msgid "Assign to"
msgstr ""
+msgid "Assign to commenting user"
+msgstr ""
+
msgid "Assign yourself to these issues"
msgstr ""
@@ -2913,6 +3056,9 @@ msgid_plural "%d Assignees"
msgstr[0] ""
msgstr[1] ""
+msgid "Assignee has no permissions"
+msgstr ""
+
msgid "Assignee lists not available with your current license"
msgstr ""
@@ -2922,6 +3068,9 @@ msgstr ""
msgid "Assignee(s)"
msgstr ""
+msgid "Assignees"
+msgstr ""
+
msgid "Assigns %{assignee_users_sentence}."
msgstr ""
@@ -2957,6 +3106,9 @@ msgstr ""
msgid "Audit Events is a way to keep track of important events that happened in GitLab."
msgstr ""
+msgid "Audit Log"
+msgstr ""
+
msgid "AuditEvents|(removed)"
msgstr ""
@@ -2993,6 +3145,9 @@ msgstr ""
msgid "AuditLogs|IP Address"
msgstr ""
+msgid "AuditLogs|Member Events"
+msgstr ""
+
msgid "AuditLogs|No matching %{type} found."
msgstr ""
@@ -3275,9 +3430,6 @@ msgstr ""
msgid "Badges|e.g. %{exampleUrl}"
msgstr ""
-msgid "Badge|New"
-msgstr ""
-
msgid "Balsamiq file could not be loaded."
msgstr ""
@@ -3326,6 +3478,12 @@ msgstr ""
msgid "Below you will find all the groups that are public."
msgstr ""
+msgid "Beta"
+msgstr ""
+
+msgid "Bi-weekly code coverage"
+msgstr ""
+
msgid "Billing"
msgstr ""
@@ -3386,9 +3544,15 @@ msgstr ""
msgid "Bitbucket Server Import"
msgstr ""
+msgid "Bitbucket Server import"
+msgstr ""
+
msgid "Bitbucket import"
msgstr ""
+msgid "Blame"
+msgstr ""
+
msgid "Blocked"
msgstr ""
@@ -3401,9 +3565,6 @@ msgstr ""
msgid "Blog"
msgstr ""
-msgid "Blue helpers indicate an action to be taken."
-msgstr ""
-
msgid "Board name"
msgstr ""
@@ -3602,6 +3763,9 @@ msgstr ""
msgid "Broadcast Message was successfully updated."
msgstr ""
+msgid "Broadcast Messages"
+msgstr ""
+
msgid "Browse Directory"
msgstr "Преглед на папката"
@@ -3656,6 +3820,9 @@ msgstr ""
msgid "By %{user_name}"
msgstr ""
+msgid "By URL"
+msgstr ""
+
msgid "By default GitLab sends emails in HTML and plain text formats so mail clients can choose what format to use. Disable this option if you only want to send emails in plain text format."
msgstr ""
@@ -3761,6 +3928,9 @@ msgstr ""
msgid "Can override approvers and approvals required per merge request"
msgstr ""
+msgid "Can't apply as this line has changed or the suggestion already matches its content."
+msgstr ""
+
msgid "Can't create snippet: %{err}"
msgstr ""
@@ -3947,9 +4117,6 @@ msgstr ""
msgid "Changes are still tracked. Useful for cluster/index migrations."
msgstr ""
-msgid "Changes are unknown"
-msgstr ""
-
msgid "Changes suppressed. Click to show."
msgstr ""
@@ -4256,15 +4423,9 @@ msgstr ""
msgid "Choose what content you want to see on a group’s overview page"
msgstr ""
-msgid "Choose which groups you wish to synchronize to this secondary node"
-msgstr ""
-
msgid "Choose which repositories you want to connect and run CI/CD pipelines."
msgstr ""
-msgid "Choose which shards you wish to synchronize to this secondary node"
-msgstr ""
-
msgid "Choose your framework"
msgstr ""
@@ -4562,9 +4723,15 @@ msgstr ""
msgid "Cluster does not exist"
msgstr ""
+msgid "Cluster is required for Stages::ClusterEndpointInserter"
+msgstr ""
+
msgid "Cluster level"
msgstr ""
+msgid "Cluster type must be specificed for Stages::ClusterEndpointInserter"
+msgstr ""
+
msgid "ClusterIntegration| %{custom_domain_start}More information%{custom_domain_end}."
msgstr ""
@@ -4676,7 +4843,7 @@ msgstr ""
msgid "ClusterIntegration|Cert-Manager"
msgstr ""
-msgid "ClusterIntegration|Cert-Manager is a native Kubernetes certificate management controller that helps with issuing certificates. Installing Cert-Manager on your cluster will issue a certificate by %{letsEncrypt} and ensure that certificates are valid and up-to-date."
+msgid "ClusterIntegration|Cert-Manager is a native Kubernetes certificate management controller that helps with issuing certificates. Installing Cert-Manager on your cluster will issue a certificate by %{linkStart}Let's Encrypt%{linkEnd} and ensure that certificates are valid and up-to-date."
msgstr ""
msgid "ClusterIntegration|Certificate Authority bundle (PEM format)"
@@ -4703,9 +4870,6 @@ msgstr ""
msgid "ClusterIntegration|Clear the local cache of namespace and service accounts. This is necessary if your integration has become out of sync. The cache is repopulated during the next CI job that requires namespace and service accounts."
msgstr ""
-msgid "ClusterIntegration|Cloud Run"
-msgstr ""
-
msgid "ClusterIntegration|Cluster being created"
msgstr ""
@@ -4790,7 +4954,7 @@ msgstr ""
msgid "ClusterIntegration|Crossplane"
msgstr ""
-msgid "ClusterIntegration|Crossplane enables declarative provisioning of managed services from your cloud of choice using %{codeStart}kubectl%{codeEnd} or %{gitlabIntegrationLink}. Crossplane runs inside your Kubernetes cluster and supports secure connectivity and secrets management between app containers and the cloud services they depend on."
+msgid "ClusterIntegration|Crossplane enables declarative provisioning of managed services from your cloud of choice using %{codeStart}kubectl%{codeEnd} or %{linkStart}GitLab Integration%{linkEnd}. Crossplane runs inside your Kubernetes cluster and supports secure connectivity and secrets management between app containers and the cloud services they depend on."
msgstr ""
msgid "ClusterIntegration|Deletes all GitLab resources attached to this cluster during removal"
@@ -4874,9 +5038,6 @@ msgstr ""
msgid "ClusterIntegration|GitLab-managed cluster"
msgstr ""
-msgid "ClusterIntegration|Gitlab Integration"
-msgstr ""
-
msgid "ClusterIntegration|Global default"
msgstr ""
@@ -4922,7 +5083,7 @@ msgstr ""
msgid "ClusterIntegration|Ingress gives you a way to route requests to services based on the request host or path, centralizing a number of services into a single entrypoint."
msgstr ""
-msgid "ClusterIntegration|Installing Ingress may incur additional costs. Learn more about %{pricingLink}."
+msgid "ClusterIntegration|Installing Ingress may incur additional costs. Learn more about %{linkStart}pricing%{linkEnd}."
msgstr ""
msgid "ClusterIntegration|Instance cluster"
@@ -4937,7 +5098,7 @@ msgstr ""
msgid "ClusterIntegration|Issuer Email"
msgstr ""
-msgid "ClusterIntegration|Issuers represent a certificate authority. You must provide an email address for your Issuer. "
+msgid "ClusterIntegration|Issuers represent a certificate authority. You must provide an email address for your Issuer."
msgstr ""
msgid "ClusterIntegration|Jupyter Hostname"
@@ -5009,9 +5170,6 @@ msgstr ""
msgid "ClusterIntegration|Learn more about instance Kubernetes clusters"
msgstr ""
-msgid "ClusterIntegration|Let's Encrypt"
-msgstr ""
-
msgid "ClusterIntegration|Loading IAM Roles"
msgstr ""
@@ -5123,7 +5281,7 @@ msgstr ""
msgid "ClusterIntegration|Prometheus"
msgstr ""
-msgid "ClusterIntegration|Prometheus is an open-source monitoring system with %{gitlabIntegrationLink} to monitor deployed applications."
+msgid "ClusterIntegration|Prometheus is an open-source monitoring system with %{linkStart}GitLab Integration%{linkEnd} to monitor deployed applications."
msgstr ""
msgid "ClusterIntegration|Provider details"
@@ -5279,10 +5437,10 @@ msgstr ""
msgid "ClusterIntegration|Select zone to choose machine type"
msgstr ""
-msgid "ClusterIntegration|Send Cilium Logs"
+msgid "ClusterIntegration|Send Container Network Policies Logs"
msgstr ""
-msgid "ClusterIntegration|Send ModSecurity Logs"
+msgid "ClusterIntegration|Send Web Application Firewall Logs"
msgstr ""
msgid "ClusterIntegration|Service Token"
@@ -5450,15 +5608,12 @@ msgstr ""
msgid "ClusterIntegration|documentation"
msgstr ""
-msgid "ClusterIntegration|installed via %{installed_via}"
+msgid "ClusterIntegration|installed via %{linkStart}Cloud Run%{linkEnd}"
msgstr ""
msgid "ClusterIntegration|meets the requirements"
msgstr ""
-msgid "ClusterIntegration|pricing"
-msgstr ""
-
msgid "ClusterIntegration|sign up"
msgstr ""
@@ -5495,6 +5650,15 @@ msgstr ""
msgid "Code"
msgstr ""
+msgid "Code Coverage: %{coveragePercentage}%{percentSymbol}"
+msgstr ""
+
+msgid "Code Coverage| Empty code coverage data"
+msgstr ""
+
+msgid "Code Coverage|Couldn't fetch the code coverage data"
+msgstr ""
+
msgid "Code Owners"
msgstr ""
@@ -5549,7 +5713,7 @@ msgstr ""
msgid "Collapse approvers"
msgstr ""
-msgid "Collapse child epics"
+msgid "Collapse milestones"
msgstr ""
msgid "Collapse replies"
@@ -5567,6 +5731,9 @@ msgstr ""
msgid "Coming soon"
msgstr ""
+msgid "Comma-separated, e.g. '1.1.1.1, 2.2.2.0/24'"
+msgstr ""
+
msgid "Command"
msgstr ""
@@ -5737,6 +5904,12 @@ msgstr ""
msgid "Compliance framework (optional)"
msgstr ""
+msgid "Compliance frameworks"
+msgstr ""
+
+msgid "ComplianceDashboard|created by:"
+msgstr ""
+
msgid "ComplianceFramework|GDPR"
msgstr ""
@@ -5875,6 +6048,9 @@ msgstr ""
msgid "Connection timed out"
msgstr ""
+msgid "Connection timeout"
+msgstr ""
+
msgid "Contact sales to upgrade"
msgstr ""
@@ -5930,9 +6106,6 @@ msgstr ""
msgid "ContainerRegistry|CLI Commands"
msgstr ""
-msgid "ContainerRegistry|Compressed Size"
-msgstr ""
-
msgid "ContainerRegistry|Container Registry"
msgstr ""
@@ -5948,6 +6121,9 @@ msgstr ""
msgid "ContainerRegistry|Copy push command"
msgstr ""
+msgid "ContainerRegistry|Delete selected"
+msgstr ""
+
msgid "ContainerRegistry|Docker connection error"
msgstr ""
@@ -5981,16 +6157,16 @@ msgstr ""
msgid "ContainerRegistry|If you are not already logged in, you need to authenticate to the Container Registry by using your GitLab username and password. If you have %{twofaDocLinkStart}Two-Factor Authentication%{twofaDocLinkEnd} enabled, use a %{personalAccessTokensDocLinkStart}Personal Access Token%{personalAccessTokensDocLinkEnd} instead of a password."
msgstr ""
-msgid "ContainerRegistry|Image ID"
+msgid "ContainerRegistry|Image ID: %{imageId}"
msgstr ""
msgid "ContainerRegistry|Image Repositories"
msgstr ""
-msgid "ContainerRegistry|Keep and protect the images that matter most."
+msgid "ContainerRegistry|Image tags"
msgstr ""
-msgid "ContainerRegistry|Last Updated"
+msgid "ContainerRegistry|Keep and protect the images that matter most."
msgstr ""
msgid "ContainerRegistry|Login"
@@ -6005,6 +6181,9 @@ msgstr ""
msgid "ContainerRegistry|Please contact your administrator."
msgstr ""
+msgid "ContainerRegistry|Published %{timeInfo}"
+msgstr ""
+
msgid "ContainerRegistry|Push an image"
msgstr ""
@@ -6020,9 +6199,6 @@ msgstr ""
msgid "ContainerRegistry|Remove repository"
msgstr ""
-msgid "ContainerRegistry|Remove selected tags"
-msgstr ""
-
msgid "ContainerRegistry|Remove tag"
msgid_plural "ContainerRegistry|Remove tags"
msgstr[0] ""
@@ -6052,9 +6228,6 @@ msgstr ""
msgid "ContainerRegistry|Sorry, your filter produced no results."
msgstr ""
-msgid "ContainerRegistry|Tag"
-msgstr ""
-
msgid "ContainerRegistry|Tag expiration policy"
msgstr ""
@@ -6199,21 +6372,6 @@ msgstr ""
msgid "Control the display of third party offers."
msgstr ""
-msgid "Control the maximum concurrency of LFS/attachment backfill for this secondary node"
-msgstr ""
-
-msgid "Control the maximum concurrency of container repository operations for this Geo node"
-msgstr ""
-
-msgid "Control the maximum concurrency of repository backfill for this secondary node"
-msgstr ""
-
-msgid "Control the maximum concurrency of verification operations for this Geo node"
-msgstr ""
-
-msgid "Control the minimum interval in days that a repository should be reverified for this primary node"
-msgstr ""
-
msgid "Cookie domain"
msgstr ""
@@ -6355,6 +6513,9 @@ msgstr ""
msgid "Could not find design."
msgstr ""
+msgid "Could not find iteration"
+msgstr ""
+
msgid "Could not remove the trigger."
msgstr ""
@@ -6391,6 +6552,12 @@ msgstr ""
msgid "Coverage"
msgstr ""
+msgid "Coverage Fuzzing"
+msgstr ""
+
+msgid "Crash State"
+msgstr ""
+
msgid "Create"
msgstr ""
@@ -6439,6 +6606,9 @@ msgstr ""
msgid "Create a personal access token on your account to pull or push via %{protocol}."
msgstr "Създайте Ñи личен жетон за доÑтъп в акаунта Ñи, за да можете да изтеглÑте и изпращате промени чрез %{protocol}."
+msgid "Create a project pre-populated with the necessary files to get you started quickly."
+msgstr ""
+
msgid "Create an account using:"
msgstr ""
@@ -6607,6 +6777,9 @@ msgstr ""
msgid "Creates branch '%{branch_name}' and a merge request to resolve this issue."
msgstr ""
+msgid "Creating"
+msgstr ""
+
msgid "Creating epic"
msgstr ""
@@ -6658,7 +6831,10 @@ msgstr ""
msgid "Current vulnerabilities count"
msgstr ""
-msgid "CurrentUser|Buy CI minutes"
+msgid "CurrentUser|Buy Pipeline minutes"
+msgstr ""
+
+msgid "CurrentUser|One of your groups is running out"
msgstr ""
msgid "CurrentUser|Profile"
@@ -6673,6 +6849,9 @@ msgstr ""
msgid "CurrentUser|Upgrade"
msgstr ""
+msgid "Custom Attributes"
+msgstr ""
+
msgid "Custom CI configuration path"
msgstr ""
@@ -6936,12 +7115,18 @@ msgstr ""
msgid "DAG"
msgstr ""
+msgid "DAG visualization requires at least 3 dependent jobs."
+msgstr ""
+
msgid "DNS"
msgstr ""
msgid "Dashboard"
msgstr ""
+msgid "Dashboard uid not found"
+msgstr ""
+
msgid "DashboardProjects|All"
msgstr ""
@@ -6966,6 +7151,9 @@ msgstr ""
msgid "Data is still calculating..."
msgstr ""
+msgid "Datasource name not found"
+msgstr ""
+
msgid "Date"
msgstr ""
@@ -7146,6 +7334,9 @@ msgstr ""
msgid "Delete this attachment"
msgstr ""
+msgid "Delete user list"
+msgstr ""
+
msgid "Delete variable"
msgstr ""
@@ -7462,6 +7653,9 @@ msgstr ""
msgid "Deploying to"
msgstr ""
+msgid "Deploying to AWS is easy with GitLab"
+msgstr ""
+
msgid "Deployment Frequency"
msgstr ""
@@ -7824,6 +8018,9 @@ msgstr ""
msgid "Dismissed on pipeline %{pipelineLink} at %{projectLink}"
msgstr ""
+msgid "Display alerts from all your monitoring tools directly within GitLab."
+msgstr ""
+
msgid "Display name"
msgstr ""
@@ -7881,9 +8078,6 @@ msgstr ""
msgid "Don't show again"
msgstr "Да не Ñе показва повече"
-msgid "Don't worry, you can access this tour by clicking on the help icon in the top right corner and choose <strong>Learn GitLab</strong>."
-msgstr ""
-
msgid "Done"
msgstr ""
@@ -8010,6 +8204,9 @@ msgstr "Редактиране на плана %{id} за Ñхема"
msgid "Edit Release"
msgstr ""
+msgid "Edit Slack integration"
+msgstr ""
+
msgid "Edit Snippet"
msgstr ""
@@ -8049,6 +8246,9 @@ msgstr ""
msgid "Edit issues"
msgstr ""
+msgid "Edit iteration"
+msgstr ""
+
msgid "Edit public deploy key"
msgstr ""
@@ -8097,6 +8297,9 @@ msgstr ""
msgid "Email"
msgstr ""
+msgid "Email Notification"
+msgstr ""
+
msgid "Email address"
msgstr ""
@@ -8109,6 +8312,9 @@ msgstr ""
msgid "Email not verified. Please verify your email in Salesforce."
msgstr ""
+msgid "Email notification for unknown sign-ins"
+msgstr ""
+
msgid "Email patch"
msgstr ""
@@ -8340,6 +8546,9 @@ msgstr ""
msgid "Enforce DNS rebinding attack protection"
msgstr ""
+msgid "Enforce personal access token expiration"
+msgstr ""
+
msgid "Ensure connectivity is available from the GitLab server to the Prometheus server"
msgstr ""
@@ -8409,6 +8618,9 @@ msgstr ""
msgid "Enter the name of your application, and we'll return a unique %{type}."
msgstr ""
+msgid "Enter weights for storages for new repositories."
+msgstr ""
+
msgid "Enter your password to approve"
msgstr ""
@@ -8418,15 +8630,27 @@ msgstr ""
msgid "Environment does not have deployments"
msgstr ""
+msgid "Environment is required for Stages::MetricEndpointInserter"
+msgstr ""
+
+msgid "Environment is required for Stages::VariableEndpointInserter"
+msgstr ""
+
msgid "Environment scope"
msgstr ""
+msgid "Environment variables are applied to all project environments in this instance via the Runner. You can use environment variables for passwords, secret keys, etc. Make variables available to the running application by prepending the variable key with <code>K8S_SECRET_</code>. You can set variables to be:"
+msgstr ""
+
msgid "Environment variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. Additionally, they can be masked so they are hidden in job logs, though they must match certain regexp requirements to do so. You can use environment variables for passwords, secret keys, or whatever you want."
msgstr ""
msgid "Environment variables are configured by your administrator to be %{link_start}protected%{link_end} by default"
msgstr ""
+msgid "Environment variables on this GitLab instance are configured to be %{link_start}protected%{link_end} by default"
+msgstr ""
+
msgid "Environment:"
msgstr ""
@@ -8958,6 +9182,9 @@ msgstr ""
msgid "EventFilterBy|Filter by comments"
msgstr ""
+msgid "EventFilterBy|Filter by designs"
+msgstr ""
+
msgid "EventFilterBy|Filter by epic events"
msgstr ""
@@ -9090,15 +9317,15 @@ msgstr ""
msgid "Expand approvers"
msgstr ""
-msgid "Expand child epics"
-msgstr ""
-
msgid "Expand down"
msgstr ""
msgid "Expand dropdown"
msgstr ""
+msgid "Expand milestones"
+msgstr ""
+
msgid "Expand sidebar"
msgstr ""
@@ -9114,6 +9341,9 @@ msgstr ""
msgid "Expiration date"
msgstr ""
+msgid "Expiration not enforced"
+msgstr ""
+
msgid "Expiration policy for the Container Registry is a perfect solution for keeping the Registry space down while still enjoying the full power of GitLab CI/CD."
msgstr ""
@@ -9234,6 +9464,9 @@ msgstr ""
msgid "Failed Jobs"
msgstr ""
+msgid "Failed on"
+msgstr ""
+
msgid "Failed to add a Zoom meeting"
msgstr ""
@@ -9294,13 +9527,19 @@ msgstr ""
msgid "Failed to install."
msgstr ""
+msgid "Failed to load assignees. Please try again."
+msgstr ""
+
+msgid "Failed to load authors. Please try again."
+msgstr ""
+
msgid "Failed to load emoji list."
msgstr ""
msgid "Failed to load error details from Sentry."
msgstr ""
-msgid "Failed to load errors from Sentry. Error message: %{errorMessage}"
+msgid "Failed to load errors from Sentry."
msgstr ""
msgid "Failed to load group activity metrics. Please try again."
@@ -9381,6 +9620,9 @@ msgstr ""
msgid "Failed to set due date because the date format is invalid."
msgstr ""
+msgid "Failed to set iteration on this issue. Please try again."
+msgstr ""
+
msgid "Failed to signing using smartcard authentication"
msgstr ""
@@ -9603,13 +9845,10 @@ msgstr ""
msgid "FeatureFlags|Target environments"
msgstr ""
-msgid "FeatureFlags|There are no active feature flags"
-msgstr ""
-
-msgid "FeatureFlags|There are no inactive feature flags"
+msgid "FeatureFlags|There was an error fetching the feature flags."
msgstr ""
-msgid "FeatureFlags|There was an error fetching the feature flags."
+msgid "FeatureFlags|There was an error retrieving user lists"
msgstr ""
msgid "FeatureFlags|Try again in a few moments or contact your support team."
@@ -9621,9 +9860,18 @@ msgstr ""
msgid "FeatureFlag|Delete strategy"
msgstr ""
+msgid "FeatureFlag|List"
+msgstr ""
+
msgid "FeatureFlag|Percentage"
msgstr ""
+msgid "FeatureFlag|Select a user list"
+msgstr ""
+
+msgid "FeatureFlag|There are no configured user lists"
+msgstr ""
+
msgid "FeatureFlag|Type"
msgstr ""
@@ -9780,6 +10028,12 @@ msgstr ""
msgid "Filter..."
msgstr ""
+msgid "Find File"
+msgstr ""
+
+msgid "Find bugs in your code with coverage-guided fuzzing"
+msgstr ""
+
msgid "Find by path"
msgstr "ТърÑене по път"
@@ -10044,6 +10298,9 @@ msgstr ""
msgid "Geo allows you to replicate your GitLab instance to other geographical locations."
msgstr ""
+msgid "Geo nodes are paused using a command run on the node"
+msgstr ""
+
msgid "GeoNodeStatusEvent|%{timeAgoStr} (%{pendingEvents} events)"
msgstr ""
@@ -10128,6 +10385,9 @@ msgstr ""
msgid "GeoNodes|Not checksummed"
msgstr ""
+msgid "GeoNodes|Package files"
+msgstr ""
+
msgid "GeoNodes|Pausing replication stops the sync process. Are you sure?"
msgstr ""
@@ -10146,6 +10406,9 @@ msgstr ""
msgid "GeoNodes|Replication slots"
msgstr ""
+msgid "GeoNodes|Replication status"
+msgstr ""
+
msgid "GeoNodes|Repositories"
msgstr ""
@@ -10356,6 +10619,9 @@ msgstr ""
msgid "Geo|Synchronization failed - %{error}"
msgstr ""
+msgid "Geo|Synchronization of %{itemTitle} is disabled."
+msgstr ""
+
msgid "Geo|The database is currently %{db_lag} behind the primary node."
msgstr ""
@@ -10668,6 +10934,9 @@ msgstr ""
msgid "Go to %{link_to_google_takeout}."
msgstr ""
+msgid "Go to <strong>Issues</strong> > <strong>Boards</strong> to access your personalized learning issue board."
+msgstr ""
+
msgid "Go to Pipelines"
msgstr ""
@@ -10767,9 +11036,6 @@ msgstr ""
msgid "Go to your snippets"
msgstr ""
-msgid "Golden Tanuki"
-msgstr ""
-
msgid "Google Cloud Platform"
msgstr ""
@@ -10791,6 +11057,9 @@ msgstr ""
msgid "Grafana URL"
msgstr ""
+msgid "Grafana response contains invalid json"
+msgstr ""
+
msgid "GrafanaIntegration|API Token"
msgstr ""
@@ -10839,9 +11108,6 @@ msgstr ""
msgid "Group %{group_name} was successfully created."
msgstr ""
-msgid "Group '%{group_name}' is being imported."
-msgstr ""
-
msgid "Group Audit Events"
msgstr ""
@@ -10878,9 +11144,6 @@ msgstr ""
msgid "Group by:"
msgstr ""
-msgid "Group could not be imported: %{errors}"
-msgstr ""
-
msgid "Group description"
msgstr ""
@@ -10908,9 +11171,15 @@ msgstr ""
msgid "Group has not been marked for deletion"
msgstr ""
+msgid "Group import could not be scheduled"
+msgstr ""
+
msgid "Group info:"
msgstr ""
+msgid "Group is required when cluster_type is :group"
+msgstr ""
+
msgid "Group maintainers can register group runners in the %{link}"
msgstr ""
@@ -10974,6 +11243,24 @@ msgstr ""
msgid "GroupActivyMetrics|Recent activity (last 90 days)"
msgstr ""
+msgid "GroupImport|Failed to import group."
+msgstr ""
+
+msgid "GroupImport|Group '%{group_name}' is being imported."
+msgstr ""
+
+msgid "GroupImport|Group could not be imported: %{errors}"
+msgstr ""
+
+msgid "GroupImport|Please wait while we import the group for you. Refresh at will."
+msgstr ""
+
+msgid "GroupImport|The group was successfully imported."
+msgstr ""
+
+msgid "GroupImport|Unable to process group import file"
+msgstr ""
+
msgid "GroupRoadmap|%{dateWord} – No end date"
msgstr ""
@@ -11241,6 +11528,9 @@ msgstr ""
msgid "Groups (%{groups})"
msgstr ""
+msgid "Groups and projects"
+msgstr ""
+
msgid "Groups can also be nested by creating %{subgroup_docs_link_start}subgroups%{subgroup_docs_link_end}."
msgstr ""
@@ -11286,6 +11576,33 @@ msgstr ""
msgid "GroupsEmptyState|You can manage your group member’s permissions and access to each project in the group."
msgstr ""
+msgid "GroupsNew|Contact an administrator to enable options for importing your group."
+msgstr ""
+
+msgid "GroupsNew|Create"
+msgstr ""
+
+msgid "GroupsNew|Create group"
+msgstr ""
+
+msgid "GroupsNew|GitLab group export"
+msgstr ""
+
+msgid "GroupsNew|Import"
+msgstr ""
+
+msgid "GroupsNew|Import group"
+msgstr ""
+
+msgid "GroupsNew|My Awesome Group"
+msgstr ""
+
+msgid "GroupsNew|No import options available"
+msgstr ""
+
+msgid "GroupsNew|To copy a GitLab group between installations, navigate to the group settings page for the original installation, generate an export file, and upload it here."
+msgstr ""
+
msgid "GroupsTree|Are you sure you want to leave the \"%{fullName}\" group?"
msgstr ""
@@ -11388,6 +11705,9 @@ msgstr ""
msgid "Helps reduce request volume for protected paths"
msgstr ""
+msgid "Here are all your projects in your group, including the one you just created. To start, let’s take a look at your personalized learning project which will help you learn about GitLab at your own pace."
+msgstr ""
+
msgid "Here you will find recent merge request activity"
msgstr ""
@@ -11402,6 +11722,9 @@ msgid_plural "Hide charts"
msgstr[0] ""
msgstr[1] ""
+msgid "Hide details"
+msgstr ""
+
msgid "Hide file browser"
msgstr ""
@@ -11411,6 +11734,9 @@ msgstr ""
msgid "Hide host keys manual input"
msgstr ""
+msgid "Hide list"
+msgstr ""
+
msgid "Hide marketing-related entries from help"
msgstr ""
@@ -11431,9 +11757,6 @@ msgstr[1] ""
msgid "Hide values"
msgstr ""
-msgid "Hiding all labels"
-msgstr ""
-
msgid "High or unknown vulnerabilities present"
msgstr ""
@@ -11518,6 +11841,9 @@ msgstr ""
msgid "ID:"
msgstr ""
+msgid "IDE"
+msgstr ""
+
msgid "IDE|Allow live previews of JavaScript projects in the Web IDE using CodeSandbox Live Preview."
msgstr ""
@@ -11608,10 +11934,10 @@ msgstr ""
msgid "If enabled"
msgstr ""
-msgid "If enabled, access to projects will be validated on an external service using their classification label."
+msgid "If enabled, GitLab will handle Object Storage replication using Geo. %{linkStart}More information%{linkEnd}"
msgstr ""
-msgid "If enabled, and if object storage is enabled, GitLab will handle Object Storage replication using Geo"
+msgid "If enabled, access to projects will be validated on an external service using their classification label."
msgstr ""
msgid "If there is no previous license or if the previous license has expired, some GitLab functionality will be blocked until a new, valid license is uploaded."
@@ -11973,6 +12299,9 @@ msgid_plural "Instances"
msgstr[0] ""
msgstr[1] ""
+msgid "Instance Configuration"
+msgstr ""
+
msgid "Instance Statistics visibility"
msgstr ""
@@ -11994,9 +12323,6 @@ msgstr ""
msgid "Integrations"
msgstr ""
-msgid "Integrations allow you to integrate GitLab with other applications"
-msgstr ""
-
msgid "Integrations|All details"
msgstr ""
@@ -12108,6 +12434,9 @@ msgstr ""
msgid "Invalid query"
msgstr ""
+msgid "Invalid repository bundle for snippet with id %{snippet_id}"
+msgstr ""
+
msgid "Invalid repository path"
msgstr ""
@@ -12126,6 +12455,9 @@ msgstr ""
msgid "Invalid two-factor code."
msgstr ""
+msgid "Invalid yaml"
+msgstr ""
+
msgid "Invitation"
msgstr ""
@@ -12345,15 +12677,24 @@ msgstr ""
msgid "It seems like the Dependency Scanning job ran successfully, but no dependencies have been detected in your project."
msgstr ""
+msgid "It seems that there is currently no available data for code coverage"
+msgstr ""
+
msgid "It's you"
msgstr ""
+msgid "Iteration"
+msgstr ""
+
msgid "Iteration changed to"
msgstr ""
msgid "Iteration removed"
msgstr ""
+msgid "Iteration updated"
+msgstr ""
+
msgid "Iterations"
msgstr ""
@@ -12384,12 +12725,18 @@ msgstr ""
msgid "January"
msgstr ""
+msgid "Jira Issues"
+msgstr ""
+
msgid "Jira import is already running."
msgstr ""
msgid "Jira integration not configured."
msgstr ""
+msgid "Jira project key is not configured"
+msgstr ""
+
msgid "Jira project: %{importProject}"
msgstr ""
@@ -12570,6 +12917,9 @@ msgstr ""
msgid "Keep divergent refs"
msgstr ""
+msgid "Kerberos access denied"
+msgstr ""
+
msgid "Key"
msgstr ""
@@ -12579,10 +12929,13 @@ msgstr ""
msgid "Key: %{key}"
msgstr ""
-msgid "Keyboard Shortcuts"
+msgid "Keyboard shortcuts"
msgstr ""
-msgid "Keyboard shortcuts"
+msgid "Keys"
+msgstr ""
+
+msgid "Ki"
msgstr ""
msgid "Kubernetes"
@@ -12624,6 +12977,9 @@ msgstr ""
msgid "LDAP"
msgstr ""
+msgid "LDAP Synchronization"
+msgstr ""
+
msgid "LDAP settings"
msgstr ""
@@ -12848,12 +13204,18 @@ msgstr ""
msgid "Learn more about custom project templates"
msgstr ""
+msgid "Learn more about deploying to AWS"
+msgstr ""
+
msgid "Learn more about deploying to a cluster"
msgstr ""
msgid "Learn more about group-level project templates"
msgstr ""
+msgid "Learn more about job dependencies"
+msgstr ""
+
msgid "Learn more about signing commits"
msgstr ""
@@ -12905,9 +13267,21 @@ msgstr ""
msgid "License History"
msgstr ""
+msgid "License ID:"
+msgstr ""
+
+msgid "License URL"
+msgstr ""
+
msgid "License-Check"
msgstr ""
+msgid "LicenseCompliance|%{docLinkStart}License Approvals%{docLinkEnd} are active"
+msgstr ""
+
+msgid "LicenseCompliance|%{docLinkStart}License Approvals%{docLinkEnd} are inactive"
+msgstr ""
+
msgid "LicenseCompliance|Add a license"
msgstr ""
@@ -12929,9 +13303,15 @@ msgstr ""
msgid "LicenseCompliance|Deny"
msgstr ""
+msgid "LicenseCompliance|Learn more about %{linkStart}License Approvals%{linkEnd}"
+msgstr ""
+
msgid "LicenseCompliance|License"
msgstr ""
+msgid "LicenseCompliance|License Approvals"
+msgstr ""
+
msgid "LicenseCompliance|License Compliance detected %d license and policy violation for the source branch only; approval required"
msgid_plural "LicenseCompliance|License Compliance detected %d licenses and policy violations for the source branch only; approval required"
msgstr[0] ""
@@ -13152,6 +13532,9 @@ msgstr ""
msgid "List your Bitbucket Server repositories"
msgstr ""
+msgid "Lists"
+msgstr ""
+
msgid "Live preview"
msgstr ""
@@ -13335,6 +13718,9 @@ msgstr ""
msgid "Manage project labels"
msgstr ""
+msgid "Manage storage usage"
+msgstr ""
+
msgid "Manage two-factor authentication"
msgstr ""
@@ -13350,6 +13736,9 @@ msgstr ""
msgid "Manifest file import"
msgstr ""
+msgid "Manifest import"
+msgstr ""
+
msgid "Manual job"
msgstr ""
@@ -13497,6 +13886,12 @@ msgstr ""
msgid "Maximum field length"
msgstr ""
+msgid "Maximum file size is 2MB. Please select a smaller file."
+msgstr ""
+
+msgid "Maximum import size (MB)"
+msgstr ""
+
msgid "Maximum job timeout"
msgstr ""
@@ -13536,6 +13931,9 @@ msgstr ""
msgid "Maximum size of Elasticsearch bulk indexing requests."
msgstr ""
+msgid "Maximum size of import files."
+msgstr ""
+
msgid "Maximum size of individual attachments in comments."
msgstr ""
@@ -13587,6 +13985,9 @@ msgstr ""
msgid "Merge (when the pipeline succeeds)"
msgstr ""
+msgid "Merge Conflicts"
+msgstr ""
+
msgid "Merge Request"
msgstr ""
@@ -13713,6 +14114,9 @@ msgstr ""
msgid "MergeRequests|Squash task canceled: another squash is already in progress."
msgstr ""
+msgid "MergeRequests|This project does not allow squashing commits when merge requests are accepted."
+msgstr ""
+
msgid "MergeRequests|Thread stays resolved"
msgstr ""
@@ -13866,6 +14270,12 @@ msgstr ""
msgid "MetricsSettings|Add a button to the metrics dashboard linking directly to your existing external dashboard."
msgstr ""
+msgid "MetricsSettings|Choose whether to display dashboard metrics in UTC or the user's local timezone."
+msgstr ""
+
+msgid "MetricsSettings|Dashboard timezone"
+msgstr ""
+
msgid "MetricsSettings|External dashboard URL"
msgstr ""
@@ -13875,6 +14285,12 @@ msgstr ""
msgid "MetricsSettings|Metrics Dashboard"
msgstr ""
+msgid "MetricsSettings|UTC (Coordinated Universal Time)"
+msgstr ""
+
+msgid "MetricsSettings|User's local timezone"
+msgstr ""
+
msgid "Metrics|Add metric"
msgstr ""
@@ -13890,6 +14306,9 @@ msgstr ""
msgid "Metrics|Create metric"
msgstr ""
+msgid "Metrics|Current"
+msgstr ""
+
msgid "Metrics|Delete metric"
msgstr ""
@@ -13940,6 +14359,9 @@ msgstr ""
msgid "Metrics|Max"
msgstr ""
+msgid "Metrics|Min"
+msgstr ""
+
msgid "Metrics|Must be a valid PromQL query."
msgstr ""
@@ -13955,6 +14377,9 @@ msgstr ""
msgid "Metrics|Refresh dashboard"
msgstr ""
+msgid "Metrics|Select a value"
+msgstr ""
+
msgid "Metrics|Star dashboard"
msgstr ""
@@ -13973,12 +14398,18 @@ msgstr ""
msgid "Metrics|There was an error getting annotations information."
msgstr ""
+msgid "Metrics|There was an error getting dashboard validation warnings information."
+msgstr ""
+
msgid "Metrics|There was an error getting deployment information."
msgstr ""
msgid "Metrics|There was an error getting environments information."
msgstr ""
+msgid "Metrics|There was an error getting options for variable \"%{name}\"."
+msgstr ""
+
msgid "Metrics|There was an error trying to validate your query"
msgstr ""
@@ -14021,6 +14452,9 @@ msgstr ""
msgid "Metrics|You're about to permanently delete this metric. This cannot be undone."
msgstr ""
+msgid "Metrics|Your dashboard schema is invalid. Edit the dashboard to correct the YAML schema."
+msgstr ""
+
msgid "Metrics|e.g. HTTP requests"
msgstr ""
@@ -14036,12 +14470,18 @@ msgstr ""
msgid "Metrics|e.g. req/sec"
msgstr ""
+msgid "Mi"
+msgstr ""
+
msgid "Microsoft Azure"
msgstr ""
msgid "Middleman project with Static Site Editor support"
msgstr ""
+msgid "Migrate your data from an external source like GitHub, Bitbucket, or another instance of GitLab."
+msgstr ""
+
msgid "Migrated %{success_count}/%{total_count} files."
msgstr ""
@@ -14176,6 +14616,9 @@ msgstr ""
msgid "Minimum capacity to be available before we schedule more mirrors preemptively."
msgstr ""
+msgid "Minimum interval in days"
+msgstr ""
+
msgid "Minimum length is %{minimum_password_length} characters"
msgstr ""
@@ -14362,6 +14805,12 @@ msgstr ""
msgid "Multiple uploaders found: %{uploader_types}"
msgstr ""
+msgid "Must match with the %{codeStart}external_url%{codeEnd} in %{codeStart}/etc/gitlab/gitlab.rb%{codeEnd}."
+msgstr ""
+
+msgid "Must match with the %{codeStart}geo_node_name%{codeEnd} in %{codeStart}/etc/gitlab/gitlab.rb%{codeEnd}. %{linkStart}More information%{linkEnd}"
+msgstr ""
+
msgid "My Awesome Group"
msgstr ""
@@ -14476,10 +14925,16 @@ msgstr ""
msgid "New Application"
msgstr ""
+msgid "New Branch"
+msgstr ""
+
+msgid "New Deploy Key"
+msgstr ""
+
msgid "New Environment"
msgstr ""
-msgid "New Geo Node"
+msgid "New File"
msgstr ""
msgid "New Group"
@@ -14496,15 +14951,15 @@ msgid_plural "New Issues"
msgstr[0] "Ðов проблем"
msgstr[1] "Ðови проблема"
-msgid "New Iteration"
-msgstr ""
-
msgid "New Jira import"
msgstr ""
msgid "New Label"
msgstr ""
+msgid "New Merge Request"
+msgstr ""
+
msgid "New Milestone"
msgstr ""
@@ -14523,6 +14978,9 @@ msgstr ""
msgid "New Snippet"
msgstr ""
+msgid "New User"
+msgstr ""
+
msgid "New branch"
msgstr "Ðов клон"
@@ -14565,6 +15023,9 @@ msgstr "Ðов проблем"
msgid "New issue title"
msgstr ""
+msgid "New iteration"
+msgstr ""
+
msgid "New iteration created"
msgstr ""
@@ -14742,6 +15203,12 @@ msgstr ""
msgid "No grouping"
msgstr ""
+msgid "No iteration"
+msgstr ""
+
+msgid "No iterations to show"
+msgstr ""
+
msgid "No job log"
msgstr ""
@@ -14784,6 +15251,9 @@ msgstr ""
msgid "No other labels with such name or description"
msgstr ""
+msgid "No panels matching properties %{opts}"
+msgstr ""
+
msgid "No parent group"
msgstr ""
@@ -14817,6 +15287,9 @@ msgstr ""
msgid "No schedules"
msgstr "ÐÑма планове"
+msgid "No stack trace for this error"
+msgstr ""
+
msgid "No starrers matched your search"
msgstr ""
@@ -14829,7 +15302,7 @@ msgstr ""
msgid "No test coverage"
msgstr ""
-msgid "No thanks, don't show this again"
+msgid "No thanks"
msgstr ""
msgid "No vulnerabilities present"
@@ -14844,9 +15317,6 @@ msgstr ""
msgid "No, directly import the existing email addresses and usernames."
msgstr ""
-msgid "No, not interested right now"
-msgstr ""
-
msgid "No. of commits"
msgstr ""
@@ -14892,9 +15362,6 @@ msgstr "ÐÑма доÑтатъчно данни"
msgid "Not found."
msgstr ""
-msgid "Not helpful"
-msgstr ""
-
msgid "Not now"
msgstr ""
@@ -15051,6 +15518,9 @@ msgstr ""
msgid "Notifications on"
msgstr ""
+msgid "Notify users by email when sign-in location is not recognized"
+msgstr ""
+
msgid "Nov"
msgstr ""
@@ -15117,9 +15587,6 @@ msgstr "Филтър"
msgid "Oh no!"
msgstr ""
-msgid "Ok let's go"
-msgstr ""
-
msgid "Oldest first"
msgstr ""
@@ -15132,16 +15599,49 @@ msgstr ""
msgid "On track"
msgstr ""
+msgid "OnDemandScans|Attached branch"
+msgstr ""
+
+msgid "OnDemandScans|Attached branch is where the scan job runs."
+msgstr ""
+
+msgid "OnDemandScans|Could not run the scan. Please try again."
+msgstr ""
+
msgid "OnDemandScans|Create new DAST scan"
msgstr ""
+msgid "OnDemandScans|DAST will scan the target URL and any discovered sub URLs."
+msgstr ""
+
+msgid "OnDemandScans|New on-demand DAST scan"
+msgstr ""
+
msgid "OnDemandScans|On-demand Scans"
msgstr ""
+msgid "OnDemandScans|On-demand scans run outside the DevOps cycle and find vulnerabilities in your projects. %{learnMoreLinkStart}Learn more%{learnMoreLinkEnd}"
+msgstr ""
+
+msgid "OnDemandScans|Only a passive scan can be performed on demand."
+msgstr ""
+
+msgid "OnDemandScans|Passive DAST Scan"
+msgstr ""
+
+msgid "OnDemandScans|Please enter a valid URL format, ex: http://www.example.com/home"
+msgstr ""
+
+msgid "OnDemandScans|Run this scan"
+msgstr ""
+
+msgid "OnDemandScans|Scan mode"
+msgstr ""
+
msgid "OnDemandScans|Schedule or run scans immediately against target sites. Currently available on-demand scan type: DAST. %{helpLinkStart}More information%{helpLinkEnd}"
msgstr ""
-msgid "Onboarding"
+msgid "OnDemandScans|Target URL"
msgstr ""
msgid "Once imported, repositories can be mirrored over SSH. Read more %{link_start}here%{link_end}."
@@ -15167,6 +15667,9 @@ msgstr ""
msgid "One or more of you personal access tokens were revoked"
msgstr ""
+msgid "One or more of your %{provider} projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
+msgstr ""
+
msgid "One or more of your Bitbucket projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
msgstr ""
@@ -15668,6 +16171,9 @@ msgstr ""
msgid "Parent epic is not present."
msgstr ""
+msgid "Parsing error for param :embed_json. %{message}"
+msgstr ""
+
msgid "Part of merge request changes"
msgstr ""
@@ -15680,6 +16186,9 @@ msgstr ""
msgid "Passed"
msgstr ""
+msgid "Passed on"
+msgstr ""
+
msgid "Password"
msgstr ""
@@ -15764,6 +16273,9 @@ msgstr ""
msgid "Perform common operations on GitLab project"
msgstr ""
+msgid "Performance and resource management"
+msgstr ""
+
msgid "Performance optimization"
msgstr ""
@@ -15971,7 +16483,7 @@ msgstr ""
msgid "Pipelines|Something went wrong while cleaning runners cache."
msgstr ""
-msgid "Pipelines|There are currently no %{scope} pipelines."
+msgid "Pipelines|There are currently no finished pipelines."
msgstr ""
msgid "Pipelines|There are currently no pipelines."
@@ -16082,6 +16594,9 @@ msgstr ""
msgid "Pipeline|Stop pipeline #%{pipelineId}?"
msgstr ""
+msgid "Pipeline|Tag name"
+msgstr ""
+
msgid "Pipeline|Trigger author"
msgstr ""
@@ -16163,12 +16678,18 @@ msgstr ""
msgid "Please check your email (%{email}) to verify that you own this address and unlock the power of CI/CD. Didn't receive it? %{resend_link}. Wrong email address? %{update_link}."
msgstr ""
+msgid "Please choose a file"
+msgstr ""
+
msgid "Please choose a group URL with no special characters."
msgstr ""
msgid "Please complete your profile with email address"
msgstr ""
+msgid "Please convert %{linkStart}them to Git%{linkEnd}, and go through the %{linkToImportFlow} again."
+msgstr ""
+
msgid "Please convert them to %{link_to_git}, and go through the %{link_to_import_flow} again."
msgstr ""
@@ -16226,6 +16747,9 @@ msgstr ""
msgid "Please provide a valid email address."
msgstr ""
+msgid "Please provide attributes to update"
+msgstr ""
+
msgid "Please refer to <a href=\"%{docs_url}\">%{docs_url}</a>"
msgstr ""
@@ -16961,6 +17485,9 @@ msgstr ""
msgid "Project has too many %{label_for_message} to search"
msgstr ""
+msgid "Project is required when cluster_type is :project"
+msgstr ""
+
msgid "Project members"
msgstr ""
@@ -17078,7 +17605,40 @@ msgstr ""
msgid "ProjectService|Comment"
msgstr ""
-msgid "ProjectService|Comment will be posted on each event"
+msgid "ProjectService|Event will be triggered by a push to the repository"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a commit is created/updated"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a confidential issue is created/updated/closed"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a deployment finishes"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a merge request is created/updated/merged"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a new tag is pushed to the repository"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a new, unique alert is recorded"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a pipeline status changes"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a wiki page is created/updated"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when an issue is created/updated/closed"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when someone adds a comment"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when someone adds a comment on a confidential issue"
msgstr ""
msgid "ProjectService|Perform common operations on GitLab project: %{project_name}"
@@ -17129,6 +17689,9 @@ msgstr ""
msgid "ProjectSettings|Enable 'Delete source branch' option by default"
msgstr ""
+msgid "ProjectSettings|Enable merge trains and pipelines for merged results"
+msgstr ""
+
msgid "ProjectSettings|Every merge creates a merge commit"
msgstr ""
@@ -17159,7 +17722,7 @@ msgstr ""
msgid "ProjectSettings|Forks"
msgstr ""
-msgid "ProjectSettings|Git Large File Storage"
+msgid "ProjectSettings|Git Large File Storage (LFS)"
msgstr ""
msgid "ProjectSettings|Internal"
@@ -17195,9 +17758,6 @@ msgstr ""
msgid "ProjectSettings|Merge options"
msgstr ""
-msgid "ProjectSettings|Merge pipelines will try to validate the post-merge result prior to merging"
-msgstr ""
-
msgid "ProjectSettings|Merge requests"
msgstr ""
@@ -17225,6 +17785,9 @@ msgstr ""
msgid "ProjectSettings|Pipelines"
msgstr ""
+msgid "ProjectSettings|Pipelines for merge requests must be enabled in the CI/CD configuration file, or pipelines could be unresolvable or dropped"
+msgstr ""
+
msgid "ProjectSettings|Pipelines must succeed"
msgstr ""
@@ -17252,6 +17815,9 @@ msgstr ""
msgid "ProjectSettings|Show link to create/view merge request when pushing from the command line"
msgstr ""
+msgid "ProjectSettings|Skipped pipelines are considered successful"
+msgstr ""
+
msgid "ProjectSettings|Snippets"
msgstr ""
@@ -17267,6 +17833,9 @@ msgstr ""
msgid "ProjectSettings|These checks must pass before merge requests can be merged"
msgstr ""
+msgid "ProjectSettings|This introduces the risk of merging changes that will not pass the pipeline."
+msgstr ""
+
msgid "ProjectSettings|This setting is applied on the server level and can be overridden by an admin."
msgstr ""
@@ -17444,15 +18013,27 @@ msgstr ""
msgid "ProjectsNew|Blank project"
msgstr ""
+msgid "ProjectsNew|Connect your external repository to GitLab CI/CD."
+msgstr ""
+
msgid "ProjectsNew|Contact an administrator to enable options for importing your project."
msgstr ""
msgid "ProjectsNew|Create"
msgstr ""
+msgid "ProjectsNew|Create a blank project to house your files, plan your work, and collaborate on code, among other things."
+msgstr ""
+
+msgid "ProjectsNew|Create blank project"
+msgstr ""
+
msgid "ProjectsNew|Create from template"
msgstr ""
+msgid "ProjectsNew|Create new project"
+msgstr ""
+
msgid "ProjectsNew|Creating project & repository."
msgstr ""
@@ -17477,6 +18058,9 @@ msgstr ""
msgid "ProjectsNew|Project description %{tag_start}(optional)%{tag_end}"
msgstr ""
+msgid "ProjectsNew|Run CI/CD for external repository"
+msgstr ""
+
msgid "ProjectsNew|Template"
msgstr ""
@@ -17738,6 +18322,9 @@ msgstr ""
msgid "Protected Branch"
msgstr ""
+msgid "Protected Branches"
+msgstr ""
+
msgid "Protected Environment"
msgstr ""
@@ -17750,6 +18337,9 @@ msgstr ""
msgid "Protected Tag"
msgstr ""
+msgid "Protected Tags"
+msgstr ""
+
msgid "Protected branches"
msgstr ""
@@ -17849,6 +18439,9 @@ msgstr ""
msgid "Provider"
msgstr ""
+msgid "Proxy support for this API is not available currently"
+msgstr ""
+
msgid "Pseudonymizer data collection"
msgstr ""
@@ -17873,6 +18466,9 @@ msgstr ""
msgid "Publish to status page"
msgstr ""
+msgid "Published on status page"
+msgstr ""
+
msgid "Publishes this issue to the associated status page."
msgstr ""
@@ -18047,6 +18643,9 @@ msgstr ""
msgid "Recipe"
msgstr ""
+msgid "Reconfigure"
+msgstr ""
+
msgid "Recover hidden stage"
msgstr ""
@@ -18133,6 +18732,9 @@ msgstr ""
msgid "Registration|Your profile"
msgstr ""
+msgid "Regulate approvals by authors/committers, based on compliance frameworks. Can be changed only at the instance level."
+msgstr ""
+
msgid "Rejected (closed)"
msgstr ""
@@ -18183,15 +18785,24 @@ msgstr ""
msgid "ReleaseAssetLinkType|Image"
msgstr ""
+msgid "ReleaseAssetLinkType|Images"
+msgstr ""
+
msgid "ReleaseAssetLinkType|Other"
msgstr ""
msgid "ReleaseAssetLinkType|Package"
msgstr ""
+msgid "ReleaseAssetLinkType|Packages"
+msgstr ""
+
msgid "ReleaseAssetLinkType|Runbook"
msgstr ""
+msgid "ReleaseAssetLinkType|Runbooks"
+msgstr ""
+
msgid "Releases"
msgstr ""
@@ -18204,6 +18815,9 @@ msgstr ""
msgid "Releases documentation"
msgstr ""
+msgid "Releases|New Release"
+msgstr ""
+
msgid "Release|Something went wrong while getting the release details"
msgstr ""
@@ -18276,6 +18890,9 @@ msgstr ""
msgid "Remove fork relationship"
msgstr ""
+msgid "Remove from batch"
+msgstr ""
+
msgid "Remove from board"
msgstr ""
@@ -18465,6 +19082,12 @@ msgstr ""
msgid "Replication"
msgstr ""
+msgid "Replication enabled"
+msgstr ""
+
+msgid "Replication paused"
+msgstr ""
+
msgid "Reply by email"
msgstr ""
@@ -18492,15 +19115,15 @@ msgstr ""
msgid "Reported %{timeAgo} by %{reportedBy}"
msgstr ""
-msgid "Reported Resource Changes: %{addNum} to add, %{changeNum} to change, %{deleteNum} to delete"
-msgstr ""
-
msgid "Reporter"
msgstr ""
msgid "Reporting"
msgstr ""
+msgid "Reports"
+msgstr ""
+
msgid "Reports|%{combinedString} and %{resolvedString}"
msgstr ""
@@ -18539,6 +19162,9 @@ msgstr ""
msgid "Reports|Failure"
msgstr ""
+msgid "Reports|Identifier"
+msgstr ""
+
msgid "Reports|Metrics reports are loading"
msgstr ""
@@ -18551,6 +19177,9 @@ msgstr ""
msgid "Reports|Metrics reports failed loading results"
msgstr ""
+msgid "Reports|Scanner"
+msgstr ""
+
msgid "Reports|Severity"
msgstr ""
@@ -18599,15 +19228,33 @@ msgstr ""
msgid "Repository cleanup has started. You will receive an email once the cleanup operation is complete."
msgstr ""
+msgid "Repository files count over the limit"
+msgstr ""
+
+msgid "Repository has an invalid default branch name."
+msgstr ""
+
+msgid "Repository has more than one branch."
+msgstr ""
+
msgid "Repository has no locks."
msgstr ""
+msgid "Repository has tags."
+msgstr ""
+
msgid "Repository maintenance"
msgstr ""
msgid "Repository mirroring"
msgstr ""
+msgid "Repository must contain at least 1 file."
+msgstr ""
+
+msgid "Repository size is above the limit."
+msgstr ""
+
msgid "Repository static objects"
msgstr ""
@@ -18617,7 +19264,7 @@ msgstr ""
msgid "Repository sync capacity"
msgstr ""
-msgid "Repository: %{counter_repositories} / Wikis: %{counter_wikis} / Build Artifacts: %{counter_build_artifacts} / LFS: %{counter_lfs_objects}"
+msgid "Repository: %{counter_repositories} / Wikis: %{counter_wikis} / Build Artifacts: %{counter_build_artifacts} / LFS: %{counter_lfs_objects} / Snippets: %{counter_snippets}"
msgstr ""
msgid "RepositorySettingsAccessLevel|Select"
@@ -18629,6 +19276,9 @@ msgstr "ЗаÑвка за доÑтъп"
msgid "Request Headers"
msgstr ""
+msgid "Request details"
+msgstr ""
+
msgid "Request parameter %{param} is missing."
msgstr ""
@@ -18735,6 +19385,9 @@ msgstr ""
msgid "Reset template"
msgstr ""
+msgid "Reset to project defaults"
+msgstr ""
+
msgid "Resetting the authorization key for this project will require updating the authorization key in every alert source it is enabled in."
msgstr ""
@@ -18831,9 +19484,6 @@ msgstr ""
msgid "Resume"
msgstr ""
-msgid "Resume replication"
-msgstr ""
-
msgid "Resync"
msgstr ""
@@ -19456,6 +20106,9 @@ msgstr ""
msgid "SecurityReports|False positive"
msgstr ""
+msgid "SecurityReports|Group Security Dashboard"
+msgstr ""
+
msgid "SecurityReports|Hide dismissed"
msgstr ""
@@ -19510,10 +20163,13 @@ msgstr ""
msgid "SecurityReports|Remove project from dashboard"
msgstr ""
-msgid "SecurityReports|Report type"
+msgid "SecurityReports|Return to dashboard"
msgstr ""
-msgid "SecurityReports|Return to dashboard"
+msgid "SecurityReports|Scan details"
+msgstr ""
+
+msgid "SecurityReports|Scanner type"
msgstr ""
msgid "SecurityReports|Security Dashboard"
@@ -19570,6 +20226,9 @@ msgstr ""
msgid "SecurityReports|There was an error while generating the report."
msgstr ""
+msgid "SecurityReports|Unable to add %{invalidProjectsMessage}"
+msgstr ""
+
msgid "SecurityReports|Unable to add %{invalidProjects}"
msgstr ""
@@ -19684,6 +20343,9 @@ msgstr ""
msgid "Select due date"
msgstr ""
+msgid "Select file"
+msgstr ""
+
msgid "Select group or project"
msgstr ""
@@ -19747,9 +20409,6 @@ msgstr "Изберете целеви клон"
msgid "Select the branch you want to set as the default for this project. All merge requests and commits will automatically be made against this branch unless you specify a different one."
msgstr ""
-msgid "Select the configured storage available for new repositories to be placed on."
-msgstr ""
-
msgid "Select the custom project template source group."
msgstr ""
@@ -19942,12 +20601,6 @@ msgstr ""
msgid "Service Desk is enabled but not yet active"
msgstr ""
-msgid "Service Desk is off"
-msgstr ""
-
-msgid "Service Desk is on"
-msgstr ""
-
msgid "Service Templates"
msgstr ""
@@ -20035,6 +20688,15 @@ msgstr ""
msgid "Set the milestone to %{milestone_reference}."
msgstr ""
+msgid "Set the number of concurrent requests this secondary node will make to the primary node while backfilling."
+msgstr ""
+
+msgid "Set the synchronization and verification capacity for the secondary node."
+msgstr ""
+
+msgid "Set the timeout in seconds to send a secondary node status to the primary and IPs allowed for the secondary nodes."
+msgstr ""
+
msgid "Set time estimate"
msgstr ""
@@ -20074,6 +20736,9 @@ msgstr ""
msgid "Set weight to %{weight}."
msgstr ""
+msgid "Set what should be replicated by choosing specific projects or groups by the secondary node."
+msgstr ""
+
msgid "SetPasswordToCloneLink|set a password"
msgstr "зададете парола"
@@ -20200,6 +20865,9 @@ msgstr ""
msgid "Show complete raw log"
msgstr ""
+msgid "Show details"
+msgstr ""
+
msgid "Show file browser"
msgstr ""
@@ -20209,10 +20877,13 @@ msgstr ""
msgid "Show latest version"
msgstr ""
+msgid "Show list"
+msgstr ""
+
msgid "Show me everything"
msgstr ""
-msgid "Show me how"
+msgid "Show me how to add a pipeline"
msgstr ""
msgid "Show me more advanced stuff"
@@ -20261,9 +20932,6 @@ msgstr ""
msgid "Showing all issues"
msgstr ""
-msgid "Showing all labels"
-msgstr ""
-
msgid "Showing last %{size} of log -"
msgstr ""
@@ -20375,9 +21043,6 @@ msgstr ""
msgid "Skip outdated deployment jobs"
msgstr ""
-msgid "Skip this for now"
-msgstr ""
-
msgid "Skipped"
msgstr ""
@@ -20522,6 +21187,9 @@ msgstr ""
msgid "Something went wrong while adding your award. Please try again."
msgstr ""
+msgid "Something went wrong while applying the batch of suggestions. Please try again."
+msgstr ""
+
msgid "Something went wrong while applying the suggestion. Please try again."
msgstr ""
@@ -20624,10 +21292,13 @@ msgstr ""
msgid "Something went wrong while updating a requirement."
msgstr ""
+msgid "Something went wrong while updating assignees"
+msgstr ""
+
msgid "Something went wrong while updating your list settings"
msgstr ""
-msgid "Something went wrong with your automatic subscription renewal"
+msgid "Something went wrong with your automatic subscription renewal."
msgstr ""
msgid "Something went wrong, unable to add %{project} to dashboard"
@@ -20645,6 +21316,9 @@ msgstr ""
msgid "Something went wrong, unable to search projects"
msgstr ""
+msgid "Something went wrong."
+msgstr ""
+
msgid "Something went wrong. Please try again."
msgstr ""
@@ -20828,6 +21502,9 @@ msgstr ""
msgid "Source code"
msgstr "Изходен код"
+msgid "Source code (%{fileExtension})"
+msgstr ""
+
msgid "Source is not available"
msgstr ""
@@ -20912,6 +21589,9 @@ msgstr ""
msgid "Stack trace"
msgstr ""
+msgid "Stacktrace snippet"
+msgstr ""
+
msgid "Stage"
msgstr ""
@@ -21017,6 +21697,9 @@ msgstr ""
msgid "Start thread & reopen %{noteable_name}"
msgstr ""
+msgid "Start using Directed Acyclic Graphs (DAG)"
+msgstr ""
+
msgid "Start your Free Gold Trial"
msgstr ""
@@ -21479,6 +22162,21 @@ msgstr ""
msgid "SuggestedColors|Very pale orange"
msgstr ""
+msgid "Suggestion is not applicable as the suggestion was not found."
+msgstr ""
+
+msgid "Suggestions are not applicable as one or more suggestions were not found."
+msgstr ""
+
+msgid "Suggestions are not applicable as their lines cannot overlap."
+msgstr ""
+
+msgid "Suggestions must all be on the same branch."
+msgstr ""
+
+msgid "Suggestions that change line count can't be added to batches, yet."
+msgstr ""
+
msgid "Suggestions:"
msgstr ""
@@ -21521,9 +22219,6 @@ msgstr ""
msgid "Synchronization disabled"
msgstr ""
-msgid "Synchronization of container repositories is disabled."
-msgstr ""
-
msgid "System"
msgstr ""
@@ -21704,6 +22399,34 @@ msgstr ""
msgid "Terms of Service and Privacy Policy"
msgstr ""
+msgid "Terraform|%{number} Terraform report failed to generate"
+msgid_plural "Terraform|%{number} Terraform reports failed to generate"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Terraform|%{number} Terraform report was generated in your pipelines"
+msgid_plural "Terraform|%{number} Terraform reports were generated in your pipelines"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Terraform|A Terraform report failed to generate."
+msgstr ""
+
+msgid "Terraform|A Terraform report was generated in your pipelines."
+msgstr ""
+
+msgid "Terraform|Generating the report caused an error."
+msgstr ""
+
+msgid "Terraform|Reported Resource Changes: %{addNum} to add, %{changeNum} to change, %{deleteNum} to delete"
+msgstr ""
+
+msgid "Terraform|The Terraform report %{name} failed to generate."
+msgstr ""
+
+msgid "Terraform|The Terraform report %{name} was generated in your pipelines."
+msgstr ""
+
msgid "Test"
msgstr ""
@@ -21730,7 +22453,7 @@ msgstr ""
msgid "TestHooks|Ensure the project has CI pipelines."
msgstr ""
-msgid "TestHooks|Ensure the project has at least one commit."
+msgid "TestHooks|Ensure the project has deployments."
msgstr ""
msgid "TestHooks|Ensure the project has issues."
@@ -21831,7 +22554,7 @@ msgstr ""
msgid "The Prometheus server responded with \"bad request\". Please check your queries are correct and are supported in your Prometheus version. %{documentationLink}"
msgstr ""
-msgid "The URL defined on the primary node that secondary nodes should use to contact it. Defaults to URL"
+msgid "The URL defined on the primary node that secondary nodes should use to contact it."
msgstr ""
msgid "The URL to use for connecting to Elasticsearch. Use a comma-separated list to support clustering (e.g., \"http://localhost:9200, http://localhost:9201\")."
@@ -21840,6 +22563,9 @@ msgstr ""
msgid "The X509 Certificate to use when mutual TLS is required to communicate with the external authorization service. If left blank, the server certificate is still validated when accessing over HTTPS."
msgstr ""
+msgid "The above settings apply to all projects with the selected compliance framework(s)."
+msgstr ""
+
msgid "The amount of seconds after which a request to get a secondary node status will time out."
msgstr ""
@@ -22139,9 +22865,6 @@ msgstr "Времето, което отнема вÑеки Ð·Ð°Ð¿Ð¸Ñ Ð¾Ñ‚ даÐ
msgid "The total stage shows the time it takes between creating an issue and deploying the code to production. The data will be automatically added once you have completed the full idea to production cycle."
msgstr ""
-msgid "The unique identifier for the Geo node. Must match %{geoNodeName} if it is set in gitlab.rb, otherwise it must match %{externalUrl} with a trailing slash"
-msgstr ""
-
msgid "The update action will time out after %{number_of_minutes} minutes. For big repositories, use a clone/push combination."
msgstr ""
@@ -22166,9 +22889,6 @@ msgstr ""
msgid "The user you are trying to deactivate has been active in the past %{minimum_inactive_days} days and cannot be deactivated"
msgstr ""
-msgid "The user-facing URL of the Geo node"
-msgstr ""
-
msgid "The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6."
msgstr "СтойноÑтта, коÑто Ñе намира в Ñредата на поÑледователноÑтта от наблюдавани данни. Ðапример: медианата на 3, 5 и 9 е 5, а медианата на 3, 5, 7 и 8 е (5+7)/2 = 6."
@@ -22259,6 +22979,9 @@ msgstr ""
msgid "There was a problem fetching project branches."
msgstr ""
+msgid "There was a problem fetching project tags."
+msgstr ""
+
msgid "There was a problem fetching project users."
msgstr ""
@@ -22310,6 +23033,9 @@ msgstr ""
msgid "There was an error fetching the %{replicableType}"
msgstr ""
+msgid "There was an error fetching the Geo Settings"
+msgstr ""
+
msgid "There was an error fetching the Node's Groups"
msgstr ""
@@ -22430,6 +23156,12 @@ msgstr ""
msgid "This %{issuable} is locked. Only <strong>project members</strong> can comment."
msgstr ""
+msgid "This %{noteableTypeText} is %{confidentialLinkStart}confidential%{linkEnd} and %{lockedLinkStart}locked%{linkEnd}."
+msgstr ""
+
+msgid "This %{noteableTypeText} is locked."
+msgstr ""
+
msgid "This %{viewer} could not be displayed because %{reason}. You can %{options} instead."
msgstr ""
@@ -22448,6 +23180,9 @@ msgstr ""
msgid "This action can lead to data loss. To prevent accidental actions we ask you to confirm your intention."
msgstr ""
+msgid "This also resolves all related threads"
+msgstr ""
+
msgid "This also resolves the discussion"
msgstr ""
@@ -22538,6 +23273,9 @@ msgstr ""
msgid "This epic does not exist or you don't have sufficient permission."
msgstr ""
+msgid "This feature is currently in beta. We invite you to %{linkStart}give feedback%{linkEnd}."
+msgstr ""
+
msgid "This feature requires local storage to be enabled"
msgstr ""
@@ -22571,7 +23309,7 @@ msgstr ""
msgid "This is a Work in Progress"
msgstr ""
-msgid "This is a confidential issue."
+msgid "This is a confidential %{noteableTypeText}."
msgstr ""
msgid "This is a delayed job to run in %{remainingTime}"
@@ -22598,9 +23336,6 @@ msgstr ""
msgid "This is your current session"
msgstr ""
-msgid "This issue is %{confidentialLinkStart}confidential%{linkEnd} and %{lockedLinkStart}locked%{linkEnd}."
-msgstr ""
-
msgid "This issue is confidential"
msgstr ""
@@ -22610,9 +23345,6 @@ msgstr ""
msgid "This issue is in a child epic of the filtered epic"
msgstr ""
-msgid "This issue is locked."
-msgstr ""
-
msgid "This job depends on other jobs with expired/erased artifacts: %{invalid_dependencies}"
msgstr ""
@@ -22712,6 +23444,9 @@ msgstr ""
msgid "This license has already expired."
msgstr ""
+msgid "This link points to external content"
+msgstr ""
+
msgid "This may expose confidential information as the selected fork is in another namespace that can have other members."
msgstr ""
@@ -22739,6 +23474,9 @@ msgstr ""
msgid "This page will be removed in a future release."
msgstr ""
+msgid "This pipeline does not use the %{codeStart}needs%{codeEnd} keyword and can't be represented as a directed acyclic graph."
+msgstr ""
+
msgid "This pipeline makes use of a predefined CI/CD configuration enabled by %{strongStart}Auto DevOps.%{strongEnd}"
msgstr ""
@@ -22967,6 +23705,9 @@ msgstr ""
msgid "Time from last commit to merge"
msgstr ""
+msgid "Time in seconds"
+msgstr ""
+
msgid "Time in seconds GitLab will wait for a response from the external service. When the service does not respond in time, access will be denied."
msgstr ""
@@ -23388,9 +24129,15 @@ msgstr ""
msgid "Total artifacts size: %{total_size}"
msgstr ""
+msgid "Total cores (CPUs)"
+msgstr ""
+
msgid "Total issues"
msgstr ""
+msgid "Total memory (GB)"
+msgstr ""
+
msgid "Total test time for all commits/merges"
msgstr "Общо време за теÑтване на вÑички подаваниÑ/ÑливаниÑ"
@@ -23592,6 +24339,9 @@ msgstr ""
msgid "U2F only works with HTTPS-enabled websites. Contact your administrator for more details."
msgstr ""
+msgid "UI Development Kit"
+msgstr ""
+
msgid "URL"
msgstr ""
@@ -23664,9 +24414,6 @@ msgstr ""
msgid "Unable to load the merge request widget. Try reloading the page."
msgstr ""
-msgid "Unable to process group import file"
-msgstr ""
-
msgid "Unable to resolve"
msgstr ""
@@ -23682,6 +24429,9 @@ msgstr ""
msgid "Unable to sign you in to the group with SAML due to \"%{reason}\""
msgstr ""
+msgid "Unable to suggest a path. Please refresh and try again."
+msgstr ""
+
msgid "Unable to update label prioritization at this time"
msgstr ""
@@ -23697,6 +24447,9 @@ msgstr ""
msgid "Unarchiving the project will restore people's ability to make changes to it. The repository can be committed to, and issues, comments, and other entities can be created. %{strong_start}Once active, this project shows up in the search and on the dashboard.%{strong_end}"
msgstr ""
+msgid "Unassign from commenting user"
+msgstr ""
+
msgid "Unblock"
msgstr ""
@@ -23772,6 +24525,9 @@ msgstr ""
msgid "Unreachable"
msgstr ""
+msgid "Unrecognized cluster type"
+msgstr ""
+
msgid "Unresolve"
msgstr ""
@@ -23832,6 +24588,9 @@ msgstr ""
msgid "Unsubscribes from this %{quick_action_target}."
msgstr ""
+msgid "Unsupported todo type passed. Supported todo types are: %{todo_types}"
+msgstr ""
+
msgid "Until"
msgstr ""
@@ -23859,6 +24618,9 @@ msgstr ""
msgid "Update approval rule"
msgstr ""
+msgid "Update approvers"
+msgstr ""
+
msgid "Update failed"
msgstr ""
@@ -23868,6 +24630,9 @@ msgstr ""
msgid "Update it"
msgstr ""
+msgid "Update iteration"
+msgstr ""
+
msgid "Update now"
msgstr ""
@@ -23913,7 +24678,7 @@ msgstr ""
msgid "Updated at"
msgstr ""
-msgid "Updated to"
+msgid "Updated to %{linkStart}chart v%{linkEnd}"
msgstr ""
msgid "Updating"
@@ -23943,6 +24708,9 @@ msgstr ""
msgid "Upload CSV file"
msgstr ""
+msgid "Upload License"
+msgstr ""
+
msgid "Upload New File"
msgstr "Качване на нов файл"
@@ -24024,16 +24792,19 @@ msgstr ""
msgid "UsageQuota|Pipelines"
msgstr ""
+msgid "UsageQuota|Purchase more storage"
+msgstr ""
+
msgid "UsageQuota|Repositories"
msgstr ""
msgid "UsageQuota|Repository"
msgstr ""
-msgid "UsageQuota|Storage"
+msgid "UsageQuota|Snippets"
msgstr ""
-msgid "UsageQuota|Storage usage:"
+msgid "UsageQuota|Storage"
msgstr ""
msgid "UsageQuota|This namespace has no projects which use shared runners"
@@ -24066,6 +24837,12 @@ msgstr ""
msgid "UsageQuota|Wikis"
msgstr ""
+msgid "UsageQuota|You used: %{usage} %{limit}"
+msgstr ""
+
+msgid "UsageQuota|out of %{formattedLimit} of your namespace storage"
+msgstr ""
+
msgid "Use %{code_start}::%{code_end} to create a %{link_start}scoped label set%{link_end} (eg. %{code_start}priority::1%{code_end})"
msgstr ""
@@ -24132,6 +24909,9 @@ msgstr ""
msgid "User IDs"
msgstr ""
+msgid "User Lists can only be created and modified with %{linkStart}the API%{linkEnd}"
+msgstr ""
+
msgid "User OAuth applications"
msgstr ""
@@ -24156,6 +24936,9 @@ msgstr ""
msgid "User key was successfully removed."
msgstr ""
+msgid "User list %{name} will be removed. Are you sure?"
+msgstr ""
+
msgid "User map"
msgstr ""
@@ -24177,148 +24960,10 @@ msgstr ""
msgid "User was successfully updated."
msgstr ""
-msgid "UserOnboardingTour|%{activeTour}/%{totalTours}"
-msgstr ""
-
-msgid "UserOnboardingTour|%{completed}/%{total} steps completed"
-msgstr ""
-
-msgid "UserOnboardingTour|%{emphasisStart}Well done!%{emphasisEnd}%{lineBreak}%{lineBreak}That's it for our guided tour, congratulations for making it all the way to the end!%{lineBreak}%{lineBreak}We hope this gave you a good overview of GitLab and how it can help you. We'll now show you how to create your own project and invite your colleagues."
-msgstr ""
-
-msgid "UserOnboardingTour|Adding other members to a project is done through Project Settings. Click on %{emphasisStart}Settings%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Alright, that's it for Commits. Let's take a look at the %{emphasisStart}Branches%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Awesome! Now click on %{emphasisStart}Members%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Click on one of the %{emphasisStart}Compare%{emphasisEnd} buttons to compare a branch to master."
-msgstr ""
-
-msgid "UserOnboardingTour|Click on one of the %{emphasisStart}pipeline IDs%{emphasisEnd} to see the details of a pipeline."
-msgstr ""
-
-msgid "UserOnboardingTour|Click to open the latest commit to see its details."
-msgstr ""
-
-msgid "UserOnboardingTour|Close 'Learn GitLab'"
-msgstr ""
-
-msgid "UserOnboardingTour|Commits are shown in chronological order and can be filtered by the commit message or by the branch."
-msgstr ""
-
-msgid "UserOnboardingTour|Create a project"
-msgstr ""
-
-msgid "UserOnboardingTour|Exit 'Learn GitLab'"
-msgstr ""
-
-msgid "UserOnboardingTour|Got it"
-msgstr ""
-
-msgid "UserOnboardingTour|Great job! %{clapHands} We hope the tour was helpful and that you learned how to use GitLab.%{lineBreak}%{lineBreak}We'd love to get your feedback on this tour.%{lineBreak}%{lineBreak}%{emphasisStart}How helpful would you say this guided tour was?%{emphasisEnd}%{lineBreak}%{lineBreak}"
-msgstr ""
-
-msgid "UserOnboardingTour|Guided GitLab Tour"
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can compare the changes of this branch to another one. Changes are divided by files so that it's easier to see what was changed where."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can create a project from scratch, start with a template or import a repository from other platforms. Whatever you choose, we'll guide you through the process.%{lineBreak}%{lineBreak}Fill in your new project information and click on %{emphasisStart}Create Project%{emphasisEnd} to progress to the next step."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can see the breakdown of the pipelines: its stages and jobs in each of the stages and their status.%{lineBreak}%{lineBreak}Our CI/CD pipelines are quite complex, most of our users have fewer and simpler pipelines."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can see the current members of the project (just you at the moment) and invite new members.%{lineBreak}%{lineBreak}You can invite multiple members at once (existing GitLab users or invite by email) and you can also set their roles and permissions.%{lineBreak}%{lineBreak}Add a few members and click on %{emphasisStart}Add to project%{emphasisEnd} to complete this step."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can see what changes were made with this commit, on what branch and if there's a related merge request. The status of the pipeline will also show up if CI/CD is set up.%{lineBreak}%{lineBreak}You can also comment on the lines of code that were changed and start a discussion with your colleagues!"
-msgstr ""
-
-msgid "UserOnboardingTour|Here's an overview of branches in the %{emphasisStart}%{projectName}%{emphasisEnd} project. They're split into Active and Stale.%{lineBreak}%{lineBreak}From here, you can create a new merge request from a branch, or compare the branch to any other branch in the project. By default, it will compare it to the master branch."
-msgstr ""
-
-msgid "UserOnboardingTour|Invite colleagues"
-msgstr ""
-
-msgid "UserOnboardingTour|Issues are great for communicating and keeping track of progress in GitLab. These are all issues that are open in the %{emphasisStart}%{projectName}%{emphasisEnd}.%{lineBreak}%{lineBreak}You can help us improve GitLab by contributing work to issues that are labeled <span class=\"badge color-label accept-mr-label\">Accepting merge requests</span>.%{lineBreak}%{lineBreak}This list can be filtered by labels, milestones, assignees, authors... We'll show you how it looks when the list is filtered by a label."
-msgstr ""
-
-msgid "UserOnboardingTour|Learn GitLab"
-msgstr ""
-
-msgid "UserOnboardingTour|Let's take a closer look at a merge request. Click on the title of one."
-msgstr ""
-
-msgid "UserOnboardingTour|Let's take a closer look at all the commits. Click on %{emphasisStart}Commits%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Let's take a closer look at the repository of this project. Click on %{emphasisStart}Repository%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|No thanks"
-msgstr ""
-
-msgid "UserOnboardingTour|Ok, let's go"
-msgstr ""
-
-msgid "UserOnboardingTour|Ok, show me"
-msgstr ""
-
-msgid "UserOnboardingTour|Open one of the issues by clicking on its title."
-msgstr ""
-
-msgid "UserOnboardingTour|Restart this step"
-msgstr ""
-
-msgid "UserOnboardingTour|Skip this step"
-msgstr ""
-
-msgid "UserOnboardingTour|Sweet! Your project was created and is ready to be used.%{lineBreak}%{lineBreak}You can start adding files to the repository or clone it. One last thing we want to show you is how to invite your colleagues to your new project."
-msgstr ""
-
-msgid "UserOnboardingTour|Take a look. Here's a nifty menu for quickly creating issues, merge requests, snippets, projects and groups. Click on it and select \"New project\" from the \"GitLab\" section to get started."
-msgstr ""
-
-msgid "UserOnboardingTour|Thanks for taking the guided tour. Remember, if you want to go through it again, you can start %{emphasisStart}Learn GitLab%{emphasisEnd} in the help menu on the top right."
-msgstr ""
-
-msgid "UserOnboardingTour|Thanks for the feedback! %{thumbsUp}"
-msgstr ""
-
-msgid "UserOnboardingTour|That's it for issues. Let'st take a look at %{emphasisStart}Merge Requests%{emphasisEnd}."
+msgid "UserList|Delete %{name}?"
msgstr ""
-msgid "UserOnboardingTour|That's it for merge requests. Now for the final part of this guided tour - the %{emphasisStart}CI/CD%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|That's it for the Repository. Let's take a look at the %{emphasisStart}Issues%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|The structure of this page is very similar to issues. Status, description, discussion and the sidebar are all here.%{lineBreak}%{lineBreak}But take a look below the description and you'll notice that there's more information about the merge request, the CI/CD pipeline and the options for approving it.%{lineBreak}%{lineBreak}Alongside the discussion you can also see more information about commits in this merge request, the status of pipelines and review all changes that were made."
-msgstr ""
-
-msgid "UserOnboardingTour|There's a lot of information here but don't worry, we'll go through it.%{lineBreak}%{lineBreak}On the top you can see the status of the issue and when it was opened and by whom. Directly below it is the issue description and below that are other %{emphasisStart}related issues%{emphasisEnd} and %{emphasisStart}merge requests%{emphasisEnd} (if any). Then below that is the %{emphasisStart}discussion%{emphasisEnd}, that's where most of the communication happens.%{lineBreak}%{lineBreak}On the right, there's a sidebar where you can view/change the %{emphasisStart}assignee, milestone, due date, labels, weight%{emphasisEnd}, etc."
-msgstr ""
-
-msgid "UserOnboardingTour|These are all the CI/CD pipelines we have for our %{emphasisStart}%{projectName}%{emphasisEnd} project.%{lineBreak}%{lineBreak}Here you can see the status of each pipeline, for what commit it's running for, its stages and the status for them."
-msgstr ""
-
-msgid "UserOnboardingTour|These are all the issues that are available for community contributions. Let's take a closer look at one of them."
-msgstr ""
-
-msgid "UserOnboardingTour|This is an overview of all merge requests in this project. Similarly to the issues overview it can be filtered down by things like labels, milestones, authors, assignees, etc."
-msgstr ""
-
-msgid "UserOnboardingTour|This is the repository for the %{emphasisStart}%{projectName}%{emphasisEnd} project. All our code is stored here. Feel free to explore and take a closer look at folders and files.%{lineBreak}%{lineBreak}Above the file structure you can see the latest commit, who the author is and the status of the CI/CD pipeline.%{lineBreak}%{lineBreak}If you scroll down below the file structure, you'll find the Readme of this project. This is defined in the README.md file at the root of the repository."
-msgstr ""
-
-msgid "UserOnboardingTour|Welcome to the project overview of the %{emphasisStart}%{projectName}%{emphasisEnd} project. This is the project that we use to work on GitLab. At first, a project seems like a simple repository, but at GitLab, a project is so much more.%{lineBreak}%{lineBreak}You can create projects for hosting your codebase, use it as an issue tracker, collaborate on code, and continuously build, test, and deploy your app with built-in GitLab CI/CD."
+msgid "UserList|created %{timeago}"
msgstr ""
msgid "UserProfile|Activity"
@@ -24471,6 +25116,9 @@ msgstr ""
msgid "UsersSelect|Unassigned"
msgstr ""
+msgid "Using %{codeStart}needs%{codeEnd} allows jobs to run before their stage is reached, as soon as their individual dependencies are met, which speeds up your pipelines."
+msgstr ""
+
msgid "Using %{code_start}::%{code_end} denotes a %{link_start}scoped label set%{link_end}"
msgstr ""
@@ -24555,9 +25203,6 @@ msgstr ""
msgid "Versions"
msgstr ""
-msgid "Very helpful"
-msgstr ""
-
msgid "View Documentation"
msgstr ""
@@ -24706,6 +25351,9 @@ msgstr "Публичен"
msgid "VisibilityLevel|Unknown"
msgstr "ÐеизвеÑтно"
+msgid "Visit settings page"
+msgstr ""
+
msgid "VisualReviewApp|%{stepStart}Step 1%{stepEnd}. Copy the following script:"
msgstr ""
@@ -24799,6 +25447,9 @@ msgstr ""
msgid "VulnerabilityManagement|Something went wrong while trying to delete the comment. Please try again later."
msgstr ""
+msgid "VulnerabilityManagement|Something went wrong while trying to refresh the vulnerability. Please try again later."
+msgstr ""
+
msgid "VulnerabilityManagement|Something went wrong while trying to retrieve the vulnerability history. Please try again later."
msgstr ""
@@ -24841,6 +25492,9 @@ msgstr ""
msgid "Vulnerability|Class"
msgstr ""
+msgid "Vulnerability|Crash Address"
+msgstr ""
+
msgid "Vulnerability|Description"
msgstr ""
@@ -24916,9 +25570,6 @@ msgstr ""
msgid "We couldn't reach the Prometheus server. Either the server no longer exists or the configuration details need updating."
msgstr ""
-msgid "We created a short guided tour that will help you learn the basics of GitLab and how it will help you be better at your job. It should only take a couple of minutes. You will be guided by two types of helpers, best recognized by their color."
-msgstr ""
-
msgid "We detected potential spam in the %{humanized_resource_name}. Please solve the reCAPTCHA to proceed."
msgstr ""
@@ -24952,6 +25603,9 @@ msgstr ""
msgid "We've found no vulnerabilities"
msgstr ""
+msgid "Web Application Firewall"
+msgstr ""
+
msgid "Web IDE"
msgstr ""
@@ -25012,9 +25666,6 @@ msgstr ""
msgid "Welcome to GitLab.com<br>@%{name}!"
msgstr ""
-msgid "Welcome to the Guided GitLab Tour"
-msgstr ""
-
msgid "Welcome to the guided GitLab tour"
msgstr ""
@@ -25062,9 +25713,6 @@ msgstr ""
msgid "While it's rare to have no vulnerabilities, it can happen. In any event, we ask that you please double check your settings to make sure you've set up your dashboard correctly."
msgstr ""
-msgid "White helpers give contextual information."
-msgstr ""
-
msgid "Who can be an approver?"
msgstr ""
@@ -25110,12 +25758,18 @@ msgstr ""
msgid "WikiEmptyIssueMessage|Suggest wiki improvement"
msgstr ""
+msgid "WikiEmptyIssueMessage|You must be a group member in order to add wiki pages. If you have suggestions for how to improve the wiki for this group, consider opening an issue in the %{issues_link}."
+msgstr ""
+
msgid "WikiEmptyIssueMessage|You must be a project member in order to add wiki pages. If you have suggestions for how to improve the wiki for this project, consider opening an issue in the %{issues_link}."
msgstr ""
msgid "WikiEmptyIssueMessage|issue tracker"
msgstr ""
+msgid "WikiEmpty|A wiki is where you can store all the details about your group. This can include why you've created it, its principles, how to use it, and so on."
+msgstr ""
+
msgid "WikiEmpty|A wiki is where you can store all the details about your project. This can include why you've created it, its principles, how to use it, and so on."
msgstr ""
@@ -25125,12 +25779,21 @@ msgstr ""
msgid "WikiEmpty|Suggest wiki improvement"
msgstr ""
+msgid "WikiEmpty|The wiki lets you write documentation for your group"
+msgstr ""
+
msgid "WikiEmpty|The wiki lets you write documentation for your project"
msgstr ""
+msgid "WikiEmpty|This group has no wiki pages"
+msgstr ""
+
msgid "WikiEmpty|This project has no wiki pages"
msgstr ""
+msgid "WikiEmpty|You must be a group member in order to add wiki pages."
+msgstr ""
+
msgid "WikiEmpty|You must be a project member in order to add wiki pages."
msgstr ""
@@ -25407,15 +26070,15 @@ msgstr ""
msgid "You can now export your security dashboard to a CSV report."
msgstr ""
+msgid "You can now manage alert endpoint configuration in the Alerts section on the Operations settings page. Fields on this page have been deprecated."
+msgstr ""
+
msgid "You can now submit a merge request to get this change into the original branch."
msgstr ""
msgid "You can now submit a merge request to get this change into the original project."
msgstr ""
-msgid "You can only add files when you are on a branch"
-msgstr "Можете да добавÑте файлове Ñамо когато Ñе намирате в клон"
-
msgid "You can only edit files when you are on a branch"
msgstr ""
@@ -25635,7 +26298,7 @@ msgstr ""
msgid "You need to upload a Google Takeout archive."
msgstr ""
-msgid "You reached %{usage_in_percent} of %{namespace_name}'s capacity (%{used_storage} of %{storage_limit})"
+msgid "You reached %{usage_in_percent} of %{namespace_name}'s storage capacity (%{used_storage} of %{storage_limit})"
msgstr ""
msgid "You tried to fork %{link_to_the_project} but it failed for the following reason:"
@@ -25938,13 +26601,13 @@ msgstr ""
msgid "Your subscription expired!"
msgstr ""
-msgid "Your subscription has been downgraded"
+msgid "Your subscription has been downgraded."
msgstr ""
-msgid "Your subscription will automatically renew in %{remaining_days}"
+msgid "Your subscription will automatically renew in %{remaining_days}."
msgstr ""
-msgid "Your subscription will expire in %{remaining_days}"
+msgid "Your subscription will expire in %{remaining_days}."
msgstr ""
msgid "Zoom meeting added"
@@ -26024,9 +26687,6 @@ msgstr ""
msgid "branch name"
msgstr ""
-msgid "build pipeline reference mismatch"
-msgstr ""
-
msgid "by"
msgstr ""
@@ -26045,6 +26705,9 @@ msgstr ""
msgid "cannot block others"
msgstr ""
+msgid "cannot contain HTML/XML tags, including any word between angle brackets (<,>)."
+msgstr ""
+
msgid "cannot include leading slash or directory traversal."
msgstr ""
@@ -26075,55 +26738,6 @@ msgstr ""
msgid "ciReport|%{remainingPackagesCount} more"
msgstr ""
-msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability"
-msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability for the source branch only"
-msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities for the source branch only"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerability"
-msgid_plural "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerabilities"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new vulnerability"
-msgid_plural "ciReport|%{reportType} %{status} detected %{newCount} new vulnerabilities"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities for the source branch only"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{fixedCount} fixed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} vulnerability for the source branch only"
-msgid_plural "ciReport|%{reportType} %{status} detected %{newCount} vulnerabilities for the source branch only"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "ciReport|%{reportType} %{status} detected no new vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected no vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected no vulnerabilities for the source branch only"
-msgstr ""
-
msgid "ciReport|%{reportType} is loading"
msgstr ""
@@ -26142,7 +26756,7 @@ msgstr ""
msgid "ciReport|All projects"
msgstr ""
-msgid "ciReport|All report types"
+msgid "ciReport|All scanner types"
msgstr ""
msgid "ciReport|All severities"
@@ -26166,6 +26780,9 @@ msgstr ""
msgid "ciReport|Container scanning detects known vulnerabilities in your docker images."
msgstr ""
+msgid "ciReport|Coverage Fuzzing"
+msgstr ""
+
msgid "ciReport|Create a merge request to implement this solution, or download and apply the patch manually."
msgstr ""
@@ -26324,6 +26941,9 @@ msgstr ""
msgid "customize"
msgstr ""
+msgid "data"
+msgstr ""
+
msgid "date must not be after 9999-12-31"
msgstr ""
@@ -26394,6 +27014,9 @@ msgstr ""
msgid "entries cannot contain HTML tags"
msgstr ""
+msgid "epic"
+msgstr ""
+
msgid "error"
msgstr ""
@@ -26415,9 +27038,6 @@ msgstr ""
msgid "expires on %{timebox_due_date}"
msgstr ""
-msgid "external_url"
-msgstr ""
-
msgid "failed"
msgstr ""
@@ -26453,9 +27073,6 @@ msgstr ""
msgid "from"
msgstr ""
-msgid "geo_node_name"
-msgstr ""
-
msgid "group"
msgstr ""
@@ -26477,9 +27094,6 @@ msgstr ""
msgid "https://your-bitbucket-server"
msgstr ""
-msgid "image"
-msgstr ""
-
msgid "image diff"
msgstr ""
@@ -26623,6 +27237,9 @@ msgstr ""
msgid "load it anyway"
msgstr ""
+msgid "loading"
+msgstr ""
+
msgid "locked by %{path_lock_user_name} %{created_at}"
msgstr ""
@@ -26751,9 +27368,6 @@ msgstr ""
msgid "mrWidget|Deployment statistics are not available currently"
msgstr ""
-msgid "mrWidget|Detect issues before deployment with a CI pipeline that continuously tests your code. We created a quick guide that will show you how to create one. Make your code more secure and more robust in just a minute."
-msgstr ""
-
msgid "mrWidget|Did not close"
msgstr ""
@@ -26916,6 +27530,9 @@ msgstr ""
msgid "mrWidget|To approve this merge request, please enter your password. This project requires all approvals to be authenticated."
msgstr ""
+msgid "mrWidget|Use %{linkStart}CI pipelines to test your code%{linkEnd}, simply add a GitLab CI configuration file to your project. It only takes a minute to make your code more secure and robust."
+msgstr ""
+
msgid "mrWidget|When this merge request is ready, remove the WIP: prefix from the title to allow it to be merged"
msgstr ""
@@ -26952,6 +27569,9 @@ msgstr ""
msgid "must be greater than start date"
msgstr ""
+msgid "must contain only valid frameworks"
+msgstr ""
+
msgid "my-awesome-group"
msgstr ""
@@ -26964,6 +27584,9 @@ msgstr ""
msgid "needs to be between 10 minutes and 1 month"
msgstr ""
+msgid "never"
+msgstr ""
+
msgid "never expires"
msgstr ""
@@ -26991,6 +27614,9 @@ msgstr "извеÑÑ‚Ð¸Ñ Ð¿Ð¾ е-поща"
msgid "nounSeries|%{firstItem} and %{lastItem}"
msgstr ""
+msgid "nounSeries|%{item}"
+msgstr ""
+
msgid "nounSeries|%{item}, %{nextItem}"
msgstr ""
@@ -27127,6 +27753,9 @@ msgstr ""
msgid "revised"
msgstr ""
+msgid "satisfied"
+msgstr ""
+
msgid "score"
msgstr ""
@@ -27157,6 +27786,9 @@ msgstr ""
msgid "severity|Unknown"
msgstr ""
+msgid "should be an array of %{object_name} objects"
+msgstr ""
+
msgid "should be greater than or equal to %{access} inherited membership from group %{group_name}"
msgstr ""
@@ -27211,10 +27843,13 @@ msgstr ""
msgid "suggestPipeline|2/2: Commit your changes"
msgstr ""
-msgid "suggestPipeline|Commit the changes and your pipeline will automatically run for the first time."
+msgid "suggestPipeline|Choose %{boldStart}Code Quality%{boldEnd} to add a pipeline that tests the quality of your code."
msgstr ""
-msgid "suggestPipeline|We recommend the %{boldStart}Code Quality%{boldEnd} template, which will add a report widget to your Merge Requests. This way you’ll learn about code quality degradations much sooner. %{footerStart} Goodbye technical debt! %{footerEnd}"
+msgid "suggestPipeline|The template is ready! You can now commit it to create your first pipeline."
+msgstr ""
+
+msgid "suggestPipeline|We’re adding a GitLab CI configuration file to add a pipeline to the project. You could create it manually, but we recommend that you start with a GitLab template that works out of the box."
msgstr ""
msgid "syntax is correct"
@@ -27274,6 +27909,9 @@ msgstr ""
msgid "uses Kubernetes clusters to deploy your code!"
msgstr ""
+msgid "v%{version} published %{timeAgo}"
+msgstr ""
+
msgid "verify ownership"
msgstr ""
diff --git a/locale/bn_BD/gitlab.po b/locale/bn_BD/gitlab.po
index 3ecb75f23e6..69cb4a9ca35 100644
--- a/locale/bn_BD/gitlab.po
+++ b/locale/bn_BD/gitlab.po
@@ -2,7 +2,7 @@ msgid ""
msgstr ""
"Project-Id-Version: gitlab-ee\n"
"Report-Msgid-Bugs-To: \n"
-"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Last-Translator: \n"
"Language-Team: Bengali\n"
"Language: bn_BD\n"
"MIME-Version: 1.0\n"
@@ -10,9 +10,11 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Crowdin-Project: gitlab-ee\n"
+"X-Crowdin-Project-ID: 288872\n"
"X-Crowdin-Language: bn\n"
"X-Crowdin-File: /master/locale/gitlab.pot\n"
-"PO-Revision-Date: 2020-06-08 15:10\n"
+"X-Crowdin-File-ID: 6\n"
+"PO-Revision-Date: 2020-07-02 01:20\n"
msgid " %{start} to %{end}"
msgstr ""
@@ -197,6 +199,11 @@ msgid_plural "%d metrics"
msgstr[0] ""
msgstr[1] ""
+msgid "%d milestone"
+msgid_plural "%d milestones"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "%d minute"
msgid_plural "%d minutes"
msgstr[0] ""
@@ -242,11 +249,6 @@ msgid_plural "%d unresolved threads"
msgstr[0] ""
msgstr[1] ""
-msgid "%d url scanned"
-msgid_plural "%d urls scanned"
-msgstr[0] ""
-msgstr[1] ""
-
msgid "%d vulnerability"
msgid_plural "%d vulnerabilities"
msgstr[0] ""
@@ -324,9 +326,15 @@ msgstr[1] ""
msgid "%{count} related %{pluralized_subject}: %{links}"
msgstr ""
+msgid "%{dashboard_path} could not be found."
+msgstr ""
+
msgid "%{days} days until tags are automatically removed"
msgstr ""
+msgid "%{deployLinkStart}Use a template to deploy to ECS%{deployLinkEnd}, or use a docker image to %{commandsLinkStart}run AWS commands in GitLab CI/CD%{commandsLinkEnd}."
+msgstr ""
+
msgid "%{description}- Sentry event: %{errorUrl}- First seen: %{firstSeen}- Last seen: %{lastSeen} %{countLabel}: %{count}%{userCountLabel}: %{userCount}"
msgstr ""
@@ -369,12 +377,18 @@ msgstr ""
msgid "%{host} sign-in from new location"
msgstr ""
-msgid "%{icon}You are about to add %{usersTag} people to the discussion. Proceed with caution."
+msgid "%{icon}You are about to add %{usersTag} people to the discussion. They will all receive a notification."
+msgstr ""
+
+msgid "%{integrations_link_start}Integrations%{link_end} enable you to make third-party applications part of your GitLab workflow. If the available integrations don't meet your needs, consider using a %{webhooks_link_start}webhook%{link_end}."
msgstr ""
msgid "%{issuableType} will be removed! Are you sure?"
msgstr ""
+msgid "%{issuesCount} issues in this group"
+msgstr ""
+
msgid "%{issuesSize} issues"
msgstr ""
@@ -384,7 +398,7 @@ msgstr ""
msgid "%{labelStart}Class:%{labelEnd} %{class}"
msgstr ""
-msgid "%{labelStart}Confidence:%{labelEnd} %{confidence}"
+msgid "%{labelStart}Evidence:%{labelEnd} %{evidence}"
msgstr ""
msgid "%{labelStart}File:%{labelEnd} %{file}"
@@ -402,6 +416,9 @@ msgstr ""
msgid "%{labelStart}Report Type:%{labelEnd} %{reportType}"
msgstr ""
+msgid "%{labelStart}Scanner:%{labelEnd} %{scanner}"
+msgstr ""
+
msgid "%{labelStart}Severity:%{labelEnd} %{severity}"
msgstr ""
@@ -426,9 +443,6 @@ msgstr ""
msgid "%{link_start}Read more%{link_end} about role permissions"
msgstr ""
-msgid "%{link} can be used for binding events when something is happening within the project."
-msgstr ""
-
msgid "%{listToShow}, and %{awardsListLength} more."
msgstr ""
@@ -462,6 +476,9 @@ msgstr ""
msgid "%{name} found %{resultsString}"
msgstr ""
+msgid "%{name} is already being used for another emoji"
+msgstr ""
+
msgid "%{name} is scheduled for %{action}"
msgstr ""
@@ -509,6 +526,9 @@ msgstr ""
msgid "%{primary} (%{secondary})"
msgstr ""
+msgid "%{ref} cannot be added: %{error}"
+msgstr ""
+
msgid "%{releases} release"
msgid_plural "%{releases} releases"
msgstr[0] ""
@@ -517,13 +537,37 @@ msgstr[1] ""
msgid "%{remaining_approvals} left"
msgstr ""
-msgid "%{retryButtonStart}Try again%{retryButtonEnd} or %{newFileButtonStart}attach a new file%{newFileButtonEnd}"
+msgid "%{reportType} %{status} detected %{critical} critical and %{high} high severity vulnerabilities out of %{total}."
+msgstr ""
+
+msgid "%{reportType} %{status} detected %{critical} critical and %{high} high severity vulnerabilities."
+msgstr ""
+
+msgid "%{reportType} %{status} detected %{critical} critical severity vulnerabilities out of %{total}."
+msgstr ""
+
+msgid "%{reportType} %{status} detected %{critical} critical severity vulnerability."
+msgid_plural "%{reportType} %{status} detected %{critical} critical severity vulnerabilities."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{reportType} %{status} detected %{high} high severity vulnerabilities out of %{total}."
msgstr ""
-msgid "%{screenreaderOnlyStart}Keyboard shorcuts%{screenreaderOnlyEnd} Disabled"
+msgid "%{reportType} %{status} detected %{high} high severity vulnerability."
+msgid_plural "%{reportType} %{status} detected %{high} high severity vulnerabilities."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{reportType} %{status} detected %{other} vulnerability."
+msgid_plural "%{reportType} %{status} detected %{other} vulnerabilities."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{reportType} %{status} detected no new vulnerabilities."
msgstr ""
-msgid "%{screenreaderOnlyStart}Keyboard shorcuts%{screenreaderOnlyEnd} Enabled"
+msgid "%{retryButtonStart}Try again%{retryButtonEnd} or %{newFileButtonStart}attach a new file%{newFileButtonEnd}"
msgstr ""
msgid "%{service_title} %{message}."
@@ -620,6 +664,12 @@ msgstr ""
msgid "%{token}..."
msgstr ""
+msgid "%{totalCpu} (%{freeSpacePercentage}%{percentSymbol} free)"
+msgstr ""
+
+msgid "%{totalMemory} (%{freeSpacePercentage}%{percentSymbol} free)"
+msgstr ""
+
msgid "%{totalWeight} total weight"
msgstr ""
@@ -650,6 +700,12 @@ msgstr ""
msgid "%{verb} %{time_spent_value} spent time."
msgstr ""
+msgid "%{webhooks_link_start}%{webhook_type}%{link_end} enable you to send notifications to web applications in response to events in a group or project."
+msgstr ""
+
+msgid "%{webhooks_link_start}%{webhook_type}%{link_end} enable you to send notifications to web applications in response to events in a group or project. We recommend using an %{integrations_link_start}integration%{link_end} in preference to a webhook."
+msgstr ""
+
msgid "'%{level}' is not a valid visibility level"
msgstr ""
@@ -676,9 +732,6 @@ msgstr ""
msgid "(No changes)"
msgstr ""
-msgid "(Show all)"
-msgstr ""
-
msgid "(check progress)"
msgstr ""
@@ -745,6 +798,9 @@ msgstr ""
msgid "0 for unlimited"
msgstr ""
+msgid "0 for unlimited, only effective with remote storage enabled."
+msgstr ""
+
msgid "1 %{type} addition"
msgid_plural "%{count} %{type} additions"
msgstr[0] ""
@@ -892,6 +948,12 @@ msgstr ""
msgid "<code>\"johnsmith@example.com\": \"johnsmith@example.com\"</code> will add \"By <a href=\"#\">johnsmith@example.com</a>\" to all issues and comments originally created by johnsmith@example.com. By default, the email address or username is masked to ensure the user's privacy. Use this option if you want to show the full email address."
msgstr ""
+msgid "<code>Masked</code> to prevent the values from being displayed in job logs (must match certain regexp requirements)."
+msgstr ""
+
+msgid "<code>Protected</code> to expose them to protected branches or tags only."
+msgstr ""
+
msgid "<namespace / project>"
msgstr ""
@@ -919,9 +981,6 @@ msgstr ""
msgid "A .NET Core console application template, customizable for any .NET Core project"
msgstr ""
-msgid "A DAG must have two dependent jobs to be visualized on this tab."
-msgstr ""
-
msgid "A GitBook site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
msgstr ""
@@ -952,6 +1011,12 @@ msgstr ""
msgid "A deleted user"
msgstr ""
+msgid "A file has been changed."
+msgstr ""
+
+msgid "A file was not found."
+msgstr ""
+
msgid "A file with '%{file_name}' already exists in %{branch} branch"
msgstr ""
@@ -1018,7 +1083,7 @@ msgstr ""
msgid "A subscription will trigger a new pipeline on the default branch of this project when a pipeline successfully completes for a new tag on the %{default_branch_docs} of the subscribed project."
msgstr ""
-msgid "A terraform report was generated in your pipelines."
+msgid "A suggestion is not applicable."
msgstr ""
msgid "A user with write access to the source branch selected this option"
@@ -1189,6 +1254,9 @@ msgstr ""
msgid "Action to take when receiving an alert."
msgstr ""
+msgid "Actions"
+msgstr ""
+
msgid "Activate"
msgstr ""
@@ -1245,6 +1313,9 @@ msgstr ""
msgid "Add LICENSE"
msgstr ""
+msgid "Add New Node"
+msgstr ""
+
msgid "Add README"
msgstr ""
@@ -1320,6 +1391,9 @@ msgstr ""
msgid "Add approval rule"
msgstr ""
+msgid "Add approvers"
+msgstr ""
+
msgid "Add bold text"
msgstr ""
@@ -1377,6 +1451,9 @@ msgstr ""
msgid "Add strikethrough text"
msgstr ""
+msgid "Add suggestion to batch"
+msgstr ""
+
msgid "Add system hook"
msgstr ""
@@ -1476,6 +1553,9 @@ msgstr ""
msgid "Admin Note"
msgstr ""
+msgid "Admin Notifications"
+msgstr ""
+
msgid "Admin Overview"
msgstr ""
@@ -1587,6 +1667,9 @@ msgstr ""
msgid "AdminSettings|Integrations configured here will automatically apply to all projects on this instance."
msgstr ""
+msgid "AdminSettings|Moved to integrations"
+msgstr ""
+
msgid "AdminSettings|No required pipeline"
msgstr ""
@@ -1817,12 +1900,18 @@ msgid_plural "Alerts"
msgstr[0] ""
msgstr[1] ""
+msgid "AlertManagement| assign yourself"
+msgstr ""
+
msgid "AlertManagement|Acknowledged"
msgstr ""
msgid "AlertManagement|Alert"
msgstr ""
+msgid "AlertManagement|Alert assignee(s): %{assignees}"
+msgstr ""
+
msgid "AlertManagement|Alert detail"
msgstr ""
@@ -1838,9 +1927,15 @@ msgstr ""
msgid "AlertManagement|All alerts"
msgstr ""
+msgid "AlertManagement|Assign To"
+msgstr ""
+
msgid "AlertManagement|Assign status"
msgstr ""
+msgid "AlertManagement|Assignee"
+msgstr ""
+
msgid "AlertManagement|Assignees"
msgstr ""
@@ -1859,9 +1954,6 @@ msgstr ""
msgid "AlertManagement|Edit"
msgstr ""
-msgid "AlertManagement|End time"
-msgstr ""
-
msgid "AlertManagement|Events"
msgstr ""
@@ -1880,10 +1972,13 @@ msgstr ""
msgid "AlertManagement|More information"
msgstr ""
+msgid "AlertManagement|No Matching Results"
+msgstr ""
+
msgid "AlertManagement|No alert data to display."
msgstr ""
-msgid "AlertManagement|No alerts available to display. If you think you're seeing this message in error, refresh the page."
+msgid "AlertManagement|No alerts available to display. See %{linkStart}enabling alert management%{linkEnd} for more information on adding alerts to the list."
msgstr ""
msgid "AlertManagement|No alerts to display."
@@ -1892,6 +1987,9 @@ msgstr ""
msgid "AlertManagement|None"
msgstr ""
+msgid "AlertManagement|None -"
+msgstr ""
+
msgid "AlertManagement|Open"
msgstr ""
@@ -1928,9 +2026,18 @@ msgstr ""
msgid "AlertManagement|There was an error displaying the alerts. Confirm your endpoint's configuration details to ensure alerts appear."
msgstr ""
+msgid "AlertManagement|There was an error while updating the assignee(s) list. Please try again."
+msgstr ""
+
+msgid "AlertManagement|There was an error while updating the assignee(s) of the alert. Please try again."
+msgstr ""
+
msgid "AlertManagement|There was an error while updating the status of the alert. Please try again."
msgstr ""
+msgid "AlertManagement|This assignee cannot be assigned to this alert."
+msgstr ""
+
msgid "AlertManagement|Tool"
msgstr ""
@@ -2078,6 +2185,9 @@ msgstr ""
msgid "Allow users to request access (if visibility is public or internal)"
msgstr ""
+msgid "Allowed Geo IP"
+msgstr ""
+
msgid "Allowed email domain restriction only permitted for top-level groups"
msgstr ""
@@ -2111,6 +2221,9 @@ msgstr ""
msgid "Amazon Web Services"
msgstr ""
+msgid "Amazon Web Services Logo"
+msgstr ""
+
msgid "Amazon authentication is not %{link_start}correctly configured%{link_end}. Ask your GitLab administrator if you want to use this service."
msgstr ""
@@ -2168,10 +2281,16 @@ msgstr ""
msgid "An error occurred when updating the issue weight"
msgstr ""
+msgid "An error occurred while acknowledging the notification. Refresh the page and try again."
+msgstr ""
+
+msgid "An error occurred while adding approvers"
+msgstr ""
+
msgid "An error occurred while adding formatted title for epic"
msgstr ""
-msgid "An error occurred while checking group path"
+msgid "An error occurred while checking group path. Please refresh and try again."
msgstr ""
msgid "An error occurred while committing your changes."
@@ -2315,7 +2434,7 @@ msgstr ""
msgid "An error occurred while loading milestones"
msgstr ""
-msgid "An error occurred while loading terraform report"
+msgid "An error occurred while loading project creation UI"
msgstr ""
msgid "An error occurred while loading the data. Please try again."
@@ -2369,6 +2488,9 @@ msgstr ""
msgid "An error occurred while rendering preview broadcast message"
msgstr ""
+msgid "An error occurred while rendering the editor"
+msgstr ""
+
msgid "An error occurred while reordering issues."
msgstr ""
@@ -2465,7 +2587,7 @@ msgstr ""
msgid "Analyze your dependencies for known vulnerabilities."
msgstr ""
-msgid "Analyze your source code and git history for secrets"
+msgid "Analyze your source code and git history for secrets."
msgstr ""
msgid "Analyze your source code for known vulnerabilities."
@@ -2579,12 +2701,18 @@ msgstr ""
msgid "Apply suggestion"
msgstr ""
+msgid "Apply suggestions"
+msgstr ""
+
msgid "Apply template"
msgstr ""
msgid "Apply this approval rule to any branch or a specific protected branch."
msgstr ""
+msgid "Applying"
+msgstr ""
+
msgid "Applying a template will replace the existing issue description. Any changes you have made will be lost."
msgstr ""
@@ -2597,12 +2725,18 @@ msgstr ""
msgid "Applying multiple commands"
msgstr ""
-msgid "Applying suggestion"
+msgid "Applying suggestion..."
+msgstr ""
+
+msgid "Applying suggestions..."
msgstr ""
msgid "Approval rules"
msgstr ""
+msgid "Approval rules reset to project defaults"
+msgstr ""
+
msgid "ApprovalRuleRemove|%d member"
msgid_plural "ApprovalRuleRemove|%d members"
msgstr[0] ""
@@ -2656,6 +2790,9 @@ msgstr ""
msgid "Approved"
msgstr ""
+msgid "Approved MRs"
+msgstr ""
+
msgid "Approved by: "
msgstr ""
@@ -2869,6 +3006,9 @@ msgstr ""
msgid "Assign"
msgstr ""
+msgid "Assign Iteration"
+msgstr ""
+
msgid "Assign custom color like #FF0000"
msgstr ""
@@ -2887,6 +3027,9 @@ msgstr ""
msgid "Assign to"
msgstr ""
+msgid "Assign to commenting user"
+msgstr ""
+
msgid "Assign yourself to these issues"
msgstr ""
@@ -2913,6 +3056,9 @@ msgid_plural "%d Assignees"
msgstr[0] ""
msgstr[1] ""
+msgid "Assignee has no permissions"
+msgstr ""
+
msgid "Assignee lists not available with your current license"
msgstr ""
@@ -2922,6 +3068,9 @@ msgstr ""
msgid "Assignee(s)"
msgstr ""
+msgid "Assignees"
+msgstr ""
+
msgid "Assigns %{assignee_users_sentence}."
msgstr ""
@@ -2957,6 +3106,9 @@ msgstr ""
msgid "Audit Events is a way to keep track of important events that happened in GitLab."
msgstr ""
+msgid "Audit Log"
+msgstr ""
+
msgid "AuditEvents|(removed)"
msgstr ""
@@ -2993,6 +3145,9 @@ msgstr ""
msgid "AuditLogs|IP Address"
msgstr ""
+msgid "AuditLogs|Member Events"
+msgstr ""
+
msgid "AuditLogs|No matching %{type} found."
msgstr ""
@@ -3275,9 +3430,6 @@ msgstr ""
msgid "Badges|e.g. %{exampleUrl}"
msgstr ""
-msgid "Badge|New"
-msgstr ""
-
msgid "Balsamiq file could not be loaded."
msgstr ""
@@ -3326,6 +3478,12 @@ msgstr ""
msgid "Below you will find all the groups that are public."
msgstr ""
+msgid "Beta"
+msgstr ""
+
+msgid "Bi-weekly code coverage"
+msgstr ""
+
msgid "Billing"
msgstr ""
@@ -3386,9 +3544,15 @@ msgstr ""
msgid "Bitbucket Server Import"
msgstr ""
+msgid "Bitbucket Server import"
+msgstr ""
+
msgid "Bitbucket import"
msgstr ""
+msgid "Blame"
+msgstr ""
+
msgid "Blocked"
msgstr ""
@@ -3401,9 +3565,6 @@ msgstr ""
msgid "Blog"
msgstr ""
-msgid "Blue helpers indicate an action to be taken."
-msgstr ""
-
msgid "Board name"
msgstr ""
@@ -3602,6 +3763,9 @@ msgstr ""
msgid "Broadcast Message was successfully updated."
msgstr ""
+msgid "Broadcast Messages"
+msgstr ""
+
msgid "Browse Directory"
msgstr ""
@@ -3656,6 +3820,9 @@ msgstr ""
msgid "By %{user_name}"
msgstr ""
+msgid "By URL"
+msgstr ""
+
msgid "By default GitLab sends emails in HTML and plain text formats so mail clients can choose what format to use. Disable this option if you only want to send emails in plain text format."
msgstr ""
@@ -3761,6 +3928,9 @@ msgstr ""
msgid "Can override approvers and approvals required per merge request"
msgstr ""
+msgid "Can't apply as this line has changed or the suggestion already matches its content."
+msgstr ""
+
msgid "Can't create snippet: %{err}"
msgstr ""
@@ -3947,9 +4117,6 @@ msgstr ""
msgid "Changes are still tracked. Useful for cluster/index migrations."
msgstr ""
-msgid "Changes are unknown"
-msgstr ""
-
msgid "Changes suppressed. Click to show."
msgstr ""
@@ -4256,15 +4423,9 @@ msgstr ""
msgid "Choose what content you want to see on a group’s overview page"
msgstr ""
-msgid "Choose which groups you wish to synchronize to this secondary node"
-msgstr ""
-
msgid "Choose which repositories you want to connect and run CI/CD pipelines."
msgstr ""
-msgid "Choose which shards you wish to synchronize to this secondary node"
-msgstr ""
-
msgid "Choose your framework"
msgstr ""
@@ -4562,9 +4723,15 @@ msgstr ""
msgid "Cluster does not exist"
msgstr ""
+msgid "Cluster is required for Stages::ClusterEndpointInserter"
+msgstr ""
+
msgid "Cluster level"
msgstr ""
+msgid "Cluster type must be specificed for Stages::ClusterEndpointInserter"
+msgstr ""
+
msgid "ClusterIntegration| %{custom_domain_start}More information%{custom_domain_end}."
msgstr ""
@@ -4676,7 +4843,7 @@ msgstr ""
msgid "ClusterIntegration|Cert-Manager"
msgstr ""
-msgid "ClusterIntegration|Cert-Manager is a native Kubernetes certificate management controller that helps with issuing certificates. Installing Cert-Manager on your cluster will issue a certificate by %{letsEncrypt} and ensure that certificates are valid and up-to-date."
+msgid "ClusterIntegration|Cert-Manager is a native Kubernetes certificate management controller that helps with issuing certificates. Installing Cert-Manager on your cluster will issue a certificate by %{linkStart}Let's Encrypt%{linkEnd} and ensure that certificates are valid and up-to-date."
msgstr ""
msgid "ClusterIntegration|Certificate Authority bundle (PEM format)"
@@ -4703,9 +4870,6 @@ msgstr ""
msgid "ClusterIntegration|Clear the local cache of namespace and service accounts. This is necessary if your integration has become out of sync. The cache is repopulated during the next CI job that requires namespace and service accounts."
msgstr ""
-msgid "ClusterIntegration|Cloud Run"
-msgstr ""
-
msgid "ClusterIntegration|Cluster being created"
msgstr ""
@@ -4790,7 +4954,7 @@ msgstr ""
msgid "ClusterIntegration|Crossplane"
msgstr ""
-msgid "ClusterIntegration|Crossplane enables declarative provisioning of managed services from your cloud of choice using %{codeStart}kubectl%{codeEnd} or %{gitlabIntegrationLink}. Crossplane runs inside your Kubernetes cluster and supports secure connectivity and secrets management between app containers and the cloud services they depend on."
+msgid "ClusterIntegration|Crossplane enables declarative provisioning of managed services from your cloud of choice using %{codeStart}kubectl%{codeEnd} or %{linkStart}GitLab Integration%{linkEnd}. Crossplane runs inside your Kubernetes cluster and supports secure connectivity and secrets management between app containers and the cloud services they depend on."
msgstr ""
msgid "ClusterIntegration|Deletes all GitLab resources attached to this cluster during removal"
@@ -4874,9 +5038,6 @@ msgstr ""
msgid "ClusterIntegration|GitLab-managed cluster"
msgstr ""
-msgid "ClusterIntegration|Gitlab Integration"
-msgstr ""
-
msgid "ClusterIntegration|Global default"
msgstr ""
@@ -4922,7 +5083,7 @@ msgstr ""
msgid "ClusterIntegration|Ingress gives you a way to route requests to services based on the request host or path, centralizing a number of services into a single entrypoint."
msgstr ""
-msgid "ClusterIntegration|Installing Ingress may incur additional costs. Learn more about %{pricingLink}."
+msgid "ClusterIntegration|Installing Ingress may incur additional costs. Learn more about %{linkStart}pricing%{linkEnd}."
msgstr ""
msgid "ClusterIntegration|Instance cluster"
@@ -4937,7 +5098,7 @@ msgstr ""
msgid "ClusterIntegration|Issuer Email"
msgstr ""
-msgid "ClusterIntegration|Issuers represent a certificate authority. You must provide an email address for your Issuer. "
+msgid "ClusterIntegration|Issuers represent a certificate authority. You must provide an email address for your Issuer."
msgstr ""
msgid "ClusterIntegration|Jupyter Hostname"
@@ -5009,9 +5170,6 @@ msgstr ""
msgid "ClusterIntegration|Learn more about instance Kubernetes clusters"
msgstr ""
-msgid "ClusterIntegration|Let's Encrypt"
-msgstr ""
-
msgid "ClusterIntegration|Loading IAM Roles"
msgstr ""
@@ -5123,7 +5281,7 @@ msgstr ""
msgid "ClusterIntegration|Prometheus"
msgstr ""
-msgid "ClusterIntegration|Prometheus is an open-source monitoring system with %{gitlabIntegrationLink} to monitor deployed applications."
+msgid "ClusterIntegration|Prometheus is an open-source monitoring system with %{linkStart}GitLab Integration%{linkEnd} to monitor deployed applications."
msgstr ""
msgid "ClusterIntegration|Provider details"
@@ -5279,10 +5437,10 @@ msgstr ""
msgid "ClusterIntegration|Select zone to choose machine type"
msgstr ""
-msgid "ClusterIntegration|Send Cilium Logs"
+msgid "ClusterIntegration|Send Container Network Policies Logs"
msgstr ""
-msgid "ClusterIntegration|Send ModSecurity Logs"
+msgid "ClusterIntegration|Send Web Application Firewall Logs"
msgstr ""
msgid "ClusterIntegration|Service Token"
@@ -5450,15 +5608,12 @@ msgstr ""
msgid "ClusterIntegration|documentation"
msgstr ""
-msgid "ClusterIntegration|installed via %{installed_via}"
+msgid "ClusterIntegration|installed via %{linkStart}Cloud Run%{linkEnd}"
msgstr ""
msgid "ClusterIntegration|meets the requirements"
msgstr ""
-msgid "ClusterIntegration|pricing"
-msgstr ""
-
msgid "ClusterIntegration|sign up"
msgstr ""
@@ -5495,6 +5650,15 @@ msgstr ""
msgid "Code"
msgstr ""
+msgid "Code Coverage: %{coveragePercentage}%{percentSymbol}"
+msgstr ""
+
+msgid "Code Coverage| Empty code coverage data"
+msgstr ""
+
+msgid "Code Coverage|Couldn't fetch the code coverage data"
+msgstr ""
+
msgid "Code Owners"
msgstr ""
@@ -5549,7 +5713,7 @@ msgstr ""
msgid "Collapse approvers"
msgstr ""
-msgid "Collapse child epics"
+msgid "Collapse milestones"
msgstr ""
msgid "Collapse replies"
@@ -5567,6 +5731,9 @@ msgstr ""
msgid "Coming soon"
msgstr ""
+msgid "Comma-separated, e.g. '1.1.1.1, 2.2.2.0/24'"
+msgstr ""
+
msgid "Command"
msgstr ""
@@ -5737,6 +5904,12 @@ msgstr ""
msgid "Compliance framework (optional)"
msgstr ""
+msgid "Compliance frameworks"
+msgstr ""
+
+msgid "ComplianceDashboard|created by:"
+msgstr ""
+
msgid "ComplianceFramework|GDPR"
msgstr ""
@@ -5875,6 +6048,9 @@ msgstr ""
msgid "Connection timed out"
msgstr ""
+msgid "Connection timeout"
+msgstr ""
+
msgid "Contact sales to upgrade"
msgstr ""
@@ -5930,9 +6106,6 @@ msgstr ""
msgid "ContainerRegistry|CLI Commands"
msgstr ""
-msgid "ContainerRegistry|Compressed Size"
-msgstr ""
-
msgid "ContainerRegistry|Container Registry"
msgstr ""
@@ -5948,6 +6121,9 @@ msgstr ""
msgid "ContainerRegistry|Copy push command"
msgstr ""
+msgid "ContainerRegistry|Delete selected"
+msgstr ""
+
msgid "ContainerRegistry|Docker connection error"
msgstr ""
@@ -5981,16 +6157,16 @@ msgstr ""
msgid "ContainerRegistry|If you are not already logged in, you need to authenticate to the Container Registry by using your GitLab username and password. If you have %{twofaDocLinkStart}Two-Factor Authentication%{twofaDocLinkEnd} enabled, use a %{personalAccessTokensDocLinkStart}Personal Access Token%{personalAccessTokensDocLinkEnd} instead of a password."
msgstr ""
-msgid "ContainerRegistry|Image ID"
+msgid "ContainerRegistry|Image ID: %{imageId}"
msgstr ""
msgid "ContainerRegistry|Image Repositories"
msgstr ""
-msgid "ContainerRegistry|Keep and protect the images that matter most."
+msgid "ContainerRegistry|Image tags"
msgstr ""
-msgid "ContainerRegistry|Last Updated"
+msgid "ContainerRegistry|Keep and protect the images that matter most."
msgstr ""
msgid "ContainerRegistry|Login"
@@ -6005,6 +6181,9 @@ msgstr ""
msgid "ContainerRegistry|Please contact your administrator."
msgstr ""
+msgid "ContainerRegistry|Published %{timeInfo}"
+msgstr ""
+
msgid "ContainerRegistry|Push an image"
msgstr ""
@@ -6020,9 +6199,6 @@ msgstr ""
msgid "ContainerRegistry|Remove repository"
msgstr ""
-msgid "ContainerRegistry|Remove selected tags"
-msgstr ""
-
msgid "ContainerRegistry|Remove tag"
msgid_plural "ContainerRegistry|Remove tags"
msgstr[0] ""
@@ -6052,9 +6228,6 @@ msgstr ""
msgid "ContainerRegistry|Sorry, your filter produced no results."
msgstr ""
-msgid "ContainerRegistry|Tag"
-msgstr ""
-
msgid "ContainerRegistry|Tag expiration policy"
msgstr ""
@@ -6199,21 +6372,6 @@ msgstr ""
msgid "Control the display of third party offers."
msgstr ""
-msgid "Control the maximum concurrency of LFS/attachment backfill for this secondary node"
-msgstr ""
-
-msgid "Control the maximum concurrency of container repository operations for this Geo node"
-msgstr ""
-
-msgid "Control the maximum concurrency of repository backfill for this secondary node"
-msgstr ""
-
-msgid "Control the maximum concurrency of verification operations for this Geo node"
-msgstr ""
-
-msgid "Control the minimum interval in days that a repository should be reverified for this primary node"
-msgstr ""
-
msgid "Cookie domain"
msgstr ""
@@ -6355,6 +6513,9 @@ msgstr ""
msgid "Could not find design."
msgstr ""
+msgid "Could not find iteration"
+msgstr ""
+
msgid "Could not remove the trigger."
msgstr ""
@@ -6391,6 +6552,12 @@ msgstr ""
msgid "Coverage"
msgstr ""
+msgid "Coverage Fuzzing"
+msgstr ""
+
+msgid "Crash State"
+msgstr ""
+
msgid "Create"
msgstr ""
@@ -6439,6 +6606,9 @@ msgstr ""
msgid "Create a personal access token on your account to pull or push via %{protocol}."
msgstr ""
+msgid "Create a project pre-populated with the necessary files to get you started quickly."
+msgstr ""
+
msgid "Create an account using:"
msgstr ""
@@ -6607,6 +6777,9 @@ msgstr ""
msgid "Creates branch '%{branch_name}' and a merge request to resolve this issue."
msgstr ""
+msgid "Creating"
+msgstr ""
+
msgid "Creating epic"
msgstr ""
@@ -6658,7 +6831,10 @@ msgstr ""
msgid "Current vulnerabilities count"
msgstr ""
-msgid "CurrentUser|Buy CI minutes"
+msgid "CurrentUser|Buy Pipeline minutes"
+msgstr ""
+
+msgid "CurrentUser|One of your groups is running out"
msgstr ""
msgid "CurrentUser|Profile"
@@ -6673,6 +6849,9 @@ msgstr ""
msgid "CurrentUser|Upgrade"
msgstr ""
+msgid "Custom Attributes"
+msgstr ""
+
msgid "Custom CI configuration path"
msgstr ""
@@ -6936,12 +7115,18 @@ msgstr ""
msgid "DAG"
msgstr ""
+msgid "DAG visualization requires at least 3 dependent jobs."
+msgstr ""
+
msgid "DNS"
msgstr ""
msgid "Dashboard"
msgstr ""
+msgid "Dashboard uid not found"
+msgstr ""
+
msgid "DashboardProjects|All"
msgstr ""
@@ -6966,6 +7151,9 @@ msgstr ""
msgid "Data is still calculating..."
msgstr ""
+msgid "Datasource name not found"
+msgstr ""
+
msgid "Date"
msgstr ""
@@ -7146,6 +7334,9 @@ msgstr ""
msgid "Delete this attachment"
msgstr ""
+msgid "Delete user list"
+msgstr ""
+
msgid "Delete variable"
msgstr ""
@@ -7462,6 +7653,9 @@ msgstr ""
msgid "Deploying to"
msgstr ""
+msgid "Deploying to AWS is easy with GitLab"
+msgstr ""
+
msgid "Deployment Frequency"
msgstr ""
@@ -7824,6 +8018,9 @@ msgstr ""
msgid "Dismissed on pipeline %{pipelineLink} at %{projectLink}"
msgstr ""
+msgid "Display alerts from all your monitoring tools directly within GitLab."
+msgstr ""
+
msgid "Display name"
msgstr ""
@@ -7881,9 +8078,6 @@ msgstr ""
msgid "Don't show again"
msgstr ""
-msgid "Don't worry, you can access this tour by clicking on the help icon in the top right corner and choose <strong>Learn GitLab</strong>."
-msgstr ""
-
msgid "Done"
msgstr ""
@@ -8010,6 +8204,9 @@ msgstr ""
msgid "Edit Release"
msgstr ""
+msgid "Edit Slack integration"
+msgstr ""
+
msgid "Edit Snippet"
msgstr ""
@@ -8049,6 +8246,9 @@ msgstr ""
msgid "Edit issues"
msgstr ""
+msgid "Edit iteration"
+msgstr ""
+
msgid "Edit public deploy key"
msgstr ""
@@ -8097,6 +8297,9 @@ msgstr ""
msgid "Email"
msgstr ""
+msgid "Email Notification"
+msgstr ""
+
msgid "Email address"
msgstr ""
@@ -8109,6 +8312,9 @@ msgstr ""
msgid "Email not verified. Please verify your email in Salesforce."
msgstr ""
+msgid "Email notification for unknown sign-ins"
+msgstr ""
+
msgid "Email patch"
msgstr ""
@@ -8340,6 +8546,9 @@ msgstr ""
msgid "Enforce DNS rebinding attack protection"
msgstr ""
+msgid "Enforce personal access token expiration"
+msgstr ""
+
msgid "Ensure connectivity is available from the GitLab server to the Prometheus server"
msgstr ""
@@ -8409,6 +8618,9 @@ msgstr ""
msgid "Enter the name of your application, and we'll return a unique %{type}."
msgstr ""
+msgid "Enter weights for storages for new repositories."
+msgstr ""
+
msgid "Enter your password to approve"
msgstr ""
@@ -8418,15 +8630,27 @@ msgstr ""
msgid "Environment does not have deployments"
msgstr ""
+msgid "Environment is required for Stages::MetricEndpointInserter"
+msgstr ""
+
+msgid "Environment is required for Stages::VariableEndpointInserter"
+msgstr ""
+
msgid "Environment scope"
msgstr ""
+msgid "Environment variables are applied to all project environments in this instance via the Runner. You can use environment variables for passwords, secret keys, etc. Make variables available to the running application by prepending the variable key with <code>K8S_SECRET_</code>. You can set variables to be:"
+msgstr ""
+
msgid "Environment variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. Additionally, they can be masked so they are hidden in job logs, though they must match certain regexp requirements to do so. You can use environment variables for passwords, secret keys, or whatever you want."
msgstr ""
msgid "Environment variables are configured by your administrator to be %{link_start}protected%{link_end} by default"
msgstr ""
+msgid "Environment variables on this GitLab instance are configured to be %{link_start}protected%{link_end} by default"
+msgstr ""
+
msgid "Environment:"
msgstr ""
@@ -8958,6 +9182,9 @@ msgstr ""
msgid "EventFilterBy|Filter by comments"
msgstr ""
+msgid "EventFilterBy|Filter by designs"
+msgstr ""
+
msgid "EventFilterBy|Filter by epic events"
msgstr ""
@@ -9090,15 +9317,15 @@ msgstr ""
msgid "Expand approvers"
msgstr ""
-msgid "Expand child epics"
-msgstr ""
-
msgid "Expand down"
msgstr ""
msgid "Expand dropdown"
msgstr ""
+msgid "Expand milestones"
+msgstr ""
+
msgid "Expand sidebar"
msgstr ""
@@ -9114,6 +9341,9 @@ msgstr ""
msgid "Expiration date"
msgstr ""
+msgid "Expiration not enforced"
+msgstr ""
+
msgid "Expiration policy for the Container Registry is a perfect solution for keeping the Registry space down while still enjoying the full power of GitLab CI/CD."
msgstr ""
@@ -9234,6 +9464,9 @@ msgstr ""
msgid "Failed Jobs"
msgstr ""
+msgid "Failed on"
+msgstr ""
+
msgid "Failed to add a Zoom meeting"
msgstr ""
@@ -9294,13 +9527,19 @@ msgstr ""
msgid "Failed to install."
msgstr ""
+msgid "Failed to load assignees. Please try again."
+msgstr ""
+
+msgid "Failed to load authors. Please try again."
+msgstr ""
+
msgid "Failed to load emoji list."
msgstr ""
msgid "Failed to load error details from Sentry."
msgstr ""
-msgid "Failed to load errors from Sentry. Error message: %{errorMessage}"
+msgid "Failed to load errors from Sentry."
msgstr ""
msgid "Failed to load group activity metrics. Please try again."
@@ -9381,6 +9620,9 @@ msgstr ""
msgid "Failed to set due date because the date format is invalid."
msgstr ""
+msgid "Failed to set iteration on this issue. Please try again."
+msgstr ""
+
msgid "Failed to signing using smartcard authentication"
msgstr ""
@@ -9603,13 +9845,10 @@ msgstr ""
msgid "FeatureFlags|Target environments"
msgstr ""
-msgid "FeatureFlags|There are no active feature flags"
-msgstr ""
-
-msgid "FeatureFlags|There are no inactive feature flags"
+msgid "FeatureFlags|There was an error fetching the feature flags."
msgstr ""
-msgid "FeatureFlags|There was an error fetching the feature flags."
+msgid "FeatureFlags|There was an error retrieving user lists"
msgstr ""
msgid "FeatureFlags|Try again in a few moments or contact your support team."
@@ -9621,9 +9860,18 @@ msgstr ""
msgid "FeatureFlag|Delete strategy"
msgstr ""
+msgid "FeatureFlag|List"
+msgstr ""
+
msgid "FeatureFlag|Percentage"
msgstr ""
+msgid "FeatureFlag|Select a user list"
+msgstr ""
+
+msgid "FeatureFlag|There are no configured user lists"
+msgstr ""
+
msgid "FeatureFlag|Type"
msgstr ""
@@ -9780,6 +10028,12 @@ msgstr ""
msgid "Filter..."
msgstr ""
+msgid "Find File"
+msgstr ""
+
+msgid "Find bugs in your code with coverage-guided fuzzing"
+msgstr ""
+
msgid "Find by path"
msgstr ""
@@ -10044,6 +10298,9 @@ msgstr ""
msgid "Geo allows you to replicate your GitLab instance to other geographical locations."
msgstr ""
+msgid "Geo nodes are paused using a command run on the node"
+msgstr ""
+
msgid "GeoNodeStatusEvent|%{timeAgoStr} (%{pendingEvents} events)"
msgstr ""
@@ -10128,6 +10385,9 @@ msgstr ""
msgid "GeoNodes|Not checksummed"
msgstr ""
+msgid "GeoNodes|Package files"
+msgstr ""
+
msgid "GeoNodes|Pausing replication stops the sync process. Are you sure?"
msgstr ""
@@ -10146,6 +10406,9 @@ msgstr ""
msgid "GeoNodes|Replication slots"
msgstr ""
+msgid "GeoNodes|Replication status"
+msgstr ""
+
msgid "GeoNodes|Repositories"
msgstr ""
@@ -10356,6 +10619,9 @@ msgstr ""
msgid "Geo|Synchronization failed - %{error}"
msgstr ""
+msgid "Geo|Synchronization of %{itemTitle} is disabled."
+msgstr ""
+
msgid "Geo|The database is currently %{db_lag} behind the primary node."
msgstr ""
@@ -10668,6 +10934,9 @@ msgstr ""
msgid "Go to %{link_to_google_takeout}."
msgstr ""
+msgid "Go to <strong>Issues</strong> > <strong>Boards</strong> to access your personalized learning issue board."
+msgstr ""
+
msgid "Go to Pipelines"
msgstr ""
@@ -10767,9 +11036,6 @@ msgstr ""
msgid "Go to your snippets"
msgstr ""
-msgid "Golden Tanuki"
-msgstr ""
-
msgid "Google Cloud Platform"
msgstr ""
@@ -10791,6 +11057,9 @@ msgstr ""
msgid "Grafana URL"
msgstr ""
+msgid "Grafana response contains invalid json"
+msgstr ""
+
msgid "GrafanaIntegration|API Token"
msgstr ""
@@ -10839,9 +11108,6 @@ msgstr ""
msgid "Group %{group_name} was successfully created."
msgstr ""
-msgid "Group '%{group_name}' is being imported."
-msgstr ""
-
msgid "Group Audit Events"
msgstr ""
@@ -10878,9 +11144,6 @@ msgstr ""
msgid "Group by:"
msgstr ""
-msgid "Group could not be imported: %{errors}"
-msgstr ""
-
msgid "Group description"
msgstr ""
@@ -10908,9 +11171,15 @@ msgstr ""
msgid "Group has not been marked for deletion"
msgstr ""
+msgid "Group import could not be scheduled"
+msgstr ""
+
msgid "Group info:"
msgstr ""
+msgid "Group is required when cluster_type is :group"
+msgstr ""
+
msgid "Group maintainers can register group runners in the %{link}"
msgstr ""
@@ -10974,6 +11243,24 @@ msgstr ""
msgid "GroupActivyMetrics|Recent activity (last 90 days)"
msgstr ""
+msgid "GroupImport|Failed to import group."
+msgstr ""
+
+msgid "GroupImport|Group '%{group_name}' is being imported."
+msgstr ""
+
+msgid "GroupImport|Group could not be imported: %{errors}"
+msgstr ""
+
+msgid "GroupImport|Please wait while we import the group for you. Refresh at will."
+msgstr ""
+
+msgid "GroupImport|The group was successfully imported."
+msgstr ""
+
+msgid "GroupImport|Unable to process group import file"
+msgstr ""
+
msgid "GroupRoadmap|%{dateWord} – No end date"
msgstr ""
@@ -11241,6 +11528,9 @@ msgstr ""
msgid "Groups (%{groups})"
msgstr ""
+msgid "Groups and projects"
+msgstr ""
+
msgid "Groups can also be nested by creating %{subgroup_docs_link_start}subgroups%{subgroup_docs_link_end}."
msgstr ""
@@ -11286,6 +11576,33 @@ msgstr ""
msgid "GroupsEmptyState|You can manage your group member’s permissions and access to each project in the group."
msgstr ""
+msgid "GroupsNew|Contact an administrator to enable options for importing your group."
+msgstr ""
+
+msgid "GroupsNew|Create"
+msgstr ""
+
+msgid "GroupsNew|Create group"
+msgstr ""
+
+msgid "GroupsNew|GitLab group export"
+msgstr ""
+
+msgid "GroupsNew|Import"
+msgstr ""
+
+msgid "GroupsNew|Import group"
+msgstr ""
+
+msgid "GroupsNew|My Awesome Group"
+msgstr ""
+
+msgid "GroupsNew|No import options available"
+msgstr ""
+
+msgid "GroupsNew|To copy a GitLab group between installations, navigate to the group settings page for the original installation, generate an export file, and upload it here."
+msgstr ""
+
msgid "GroupsTree|Are you sure you want to leave the \"%{fullName}\" group?"
msgstr ""
@@ -11388,6 +11705,9 @@ msgstr ""
msgid "Helps reduce request volume for protected paths"
msgstr ""
+msgid "Here are all your projects in your group, including the one you just created. To start, let’s take a look at your personalized learning project which will help you learn about GitLab at your own pace."
+msgstr ""
+
msgid "Here you will find recent merge request activity"
msgstr ""
@@ -11402,6 +11722,9 @@ msgid_plural "Hide charts"
msgstr[0] ""
msgstr[1] ""
+msgid "Hide details"
+msgstr ""
+
msgid "Hide file browser"
msgstr ""
@@ -11411,6 +11734,9 @@ msgstr ""
msgid "Hide host keys manual input"
msgstr ""
+msgid "Hide list"
+msgstr ""
+
msgid "Hide marketing-related entries from help"
msgstr ""
@@ -11431,9 +11757,6 @@ msgstr[1] ""
msgid "Hide values"
msgstr ""
-msgid "Hiding all labels"
-msgstr ""
-
msgid "High or unknown vulnerabilities present"
msgstr ""
@@ -11518,6 +11841,9 @@ msgstr ""
msgid "ID:"
msgstr ""
+msgid "IDE"
+msgstr ""
+
msgid "IDE|Allow live previews of JavaScript projects in the Web IDE using CodeSandbox Live Preview."
msgstr ""
@@ -11608,10 +11934,10 @@ msgstr ""
msgid "If enabled"
msgstr ""
-msgid "If enabled, access to projects will be validated on an external service using their classification label."
+msgid "If enabled, GitLab will handle Object Storage replication using Geo. %{linkStart}More information%{linkEnd}"
msgstr ""
-msgid "If enabled, and if object storage is enabled, GitLab will handle Object Storage replication using Geo"
+msgid "If enabled, access to projects will be validated on an external service using their classification label."
msgstr ""
msgid "If there is no previous license or if the previous license has expired, some GitLab functionality will be blocked until a new, valid license is uploaded."
@@ -11973,6 +12299,9 @@ msgid_plural "Instances"
msgstr[0] ""
msgstr[1] ""
+msgid "Instance Configuration"
+msgstr ""
+
msgid "Instance Statistics visibility"
msgstr ""
@@ -11994,9 +12323,6 @@ msgstr ""
msgid "Integrations"
msgstr ""
-msgid "Integrations allow you to integrate GitLab with other applications"
-msgstr ""
-
msgid "Integrations|All details"
msgstr ""
@@ -12108,6 +12434,9 @@ msgstr ""
msgid "Invalid query"
msgstr ""
+msgid "Invalid repository bundle for snippet with id %{snippet_id}"
+msgstr ""
+
msgid "Invalid repository path"
msgstr ""
@@ -12126,6 +12455,9 @@ msgstr ""
msgid "Invalid two-factor code."
msgstr ""
+msgid "Invalid yaml"
+msgstr ""
+
msgid "Invitation"
msgstr ""
@@ -12345,15 +12677,24 @@ msgstr ""
msgid "It seems like the Dependency Scanning job ran successfully, but no dependencies have been detected in your project."
msgstr ""
+msgid "It seems that there is currently no available data for code coverage"
+msgstr ""
+
msgid "It's you"
msgstr ""
+msgid "Iteration"
+msgstr ""
+
msgid "Iteration changed to"
msgstr ""
msgid "Iteration removed"
msgstr ""
+msgid "Iteration updated"
+msgstr ""
+
msgid "Iterations"
msgstr ""
@@ -12384,12 +12725,18 @@ msgstr ""
msgid "January"
msgstr ""
+msgid "Jira Issues"
+msgstr ""
+
msgid "Jira import is already running."
msgstr ""
msgid "Jira integration not configured."
msgstr ""
+msgid "Jira project key is not configured"
+msgstr ""
+
msgid "Jira project: %{importProject}"
msgstr ""
@@ -12570,6 +12917,9 @@ msgstr ""
msgid "Keep divergent refs"
msgstr ""
+msgid "Kerberos access denied"
+msgstr ""
+
msgid "Key"
msgstr ""
@@ -12579,10 +12929,13 @@ msgstr ""
msgid "Key: %{key}"
msgstr ""
-msgid "Keyboard Shortcuts"
+msgid "Keyboard shortcuts"
msgstr ""
-msgid "Keyboard shortcuts"
+msgid "Keys"
+msgstr ""
+
+msgid "Ki"
msgstr ""
msgid "Kubernetes"
@@ -12624,6 +12977,9 @@ msgstr ""
msgid "LDAP"
msgstr ""
+msgid "LDAP Synchronization"
+msgstr ""
+
msgid "LDAP settings"
msgstr ""
@@ -12848,12 +13204,18 @@ msgstr ""
msgid "Learn more about custom project templates"
msgstr ""
+msgid "Learn more about deploying to AWS"
+msgstr ""
+
msgid "Learn more about deploying to a cluster"
msgstr ""
msgid "Learn more about group-level project templates"
msgstr ""
+msgid "Learn more about job dependencies"
+msgstr ""
+
msgid "Learn more about signing commits"
msgstr ""
@@ -12905,9 +13267,21 @@ msgstr ""
msgid "License History"
msgstr ""
+msgid "License ID:"
+msgstr ""
+
+msgid "License URL"
+msgstr ""
+
msgid "License-Check"
msgstr ""
+msgid "LicenseCompliance|%{docLinkStart}License Approvals%{docLinkEnd} are active"
+msgstr ""
+
+msgid "LicenseCompliance|%{docLinkStart}License Approvals%{docLinkEnd} are inactive"
+msgstr ""
+
msgid "LicenseCompliance|Add a license"
msgstr ""
@@ -12929,9 +13303,15 @@ msgstr ""
msgid "LicenseCompliance|Deny"
msgstr ""
+msgid "LicenseCompliance|Learn more about %{linkStart}License Approvals%{linkEnd}"
+msgstr ""
+
msgid "LicenseCompliance|License"
msgstr ""
+msgid "LicenseCompliance|License Approvals"
+msgstr ""
+
msgid "LicenseCompliance|License Compliance detected %d license and policy violation for the source branch only; approval required"
msgid_plural "LicenseCompliance|License Compliance detected %d licenses and policy violations for the source branch only; approval required"
msgstr[0] ""
@@ -13152,6 +13532,9 @@ msgstr ""
msgid "List your Bitbucket Server repositories"
msgstr ""
+msgid "Lists"
+msgstr ""
+
msgid "Live preview"
msgstr ""
@@ -13335,6 +13718,9 @@ msgstr ""
msgid "Manage project labels"
msgstr ""
+msgid "Manage storage usage"
+msgstr ""
+
msgid "Manage two-factor authentication"
msgstr ""
@@ -13350,6 +13736,9 @@ msgstr ""
msgid "Manifest file import"
msgstr ""
+msgid "Manifest import"
+msgstr ""
+
msgid "Manual job"
msgstr ""
@@ -13497,6 +13886,12 @@ msgstr ""
msgid "Maximum field length"
msgstr ""
+msgid "Maximum file size is 2MB. Please select a smaller file."
+msgstr ""
+
+msgid "Maximum import size (MB)"
+msgstr ""
+
msgid "Maximum job timeout"
msgstr ""
@@ -13536,6 +13931,9 @@ msgstr ""
msgid "Maximum size of Elasticsearch bulk indexing requests."
msgstr ""
+msgid "Maximum size of import files."
+msgstr ""
+
msgid "Maximum size of individual attachments in comments."
msgstr ""
@@ -13587,6 +13985,9 @@ msgstr ""
msgid "Merge (when the pipeline succeeds)"
msgstr ""
+msgid "Merge Conflicts"
+msgstr ""
+
msgid "Merge Request"
msgstr ""
@@ -13713,6 +14114,9 @@ msgstr ""
msgid "MergeRequests|Squash task canceled: another squash is already in progress."
msgstr ""
+msgid "MergeRequests|This project does not allow squashing commits when merge requests are accepted."
+msgstr ""
+
msgid "MergeRequests|Thread stays resolved"
msgstr ""
@@ -13866,6 +14270,12 @@ msgstr ""
msgid "MetricsSettings|Add a button to the metrics dashboard linking directly to your existing external dashboard."
msgstr ""
+msgid "MetricsSettings|Choose whether to display dashboard metrics in UTC or the user's local timezone."
+msgstr ""
+
+msgid "MetricsSettings|Dashboard timezone"
+msgstr ""
+
msgid "MetricsSettings|External dashboard URL"
msgstr ""
@@ -13875,6 +14285,12 @@ msgstr ""
msgid "MetricsSettings|Metrics Dashboard"
msgstr ""
+msgid "MetricsSettings|UTC (Coordinated Universal Time)"
+msgstr ""
+
+msgid "MetricsSettings|User's local timezone"
+msgstr ""
+
msgid "Metrics|Add metric"
msgstr ""
@@ -13890,6 +14306,9 @@ msgstr ""
msgid "Metrics|Create metric"
msgstr ""
+msgid "Metrics|Current"
+msgstr ""
+
msgid "Metrics|Delete metric"
msgstr ""
@@ -13940,6 +14359,9 @@ msgstr ""
msgid "Metrics|Max"
msgstr ""
+msgid "Metrics|Min"
+msgstr ""
+
msgid "Metrics|Must be a valid PromQL query."
msgstr ""
@@ -13955,6 +14377,9 @@ msgstr ""
msgid "Metrics|Refresh dashboard"
msgstr ""
+msgid "Metrics|Select a value"
+msgstr ""
+
msgid "Metrics|Star dashboard"
msgstr ""
@@ -13973,12 +14398,18 @@ msgstr ""
msgid "Metrics|There was an error getting annotations information."
msgstr ""
+msgid "Metrics|There was an error getting dashboard validation warnings information."
+msgstr ""
+
msgid "Metrics|There was an error getting deployment information."
msgstr ""
msgid "Metrics|There was an error getting environments information."
msgstr ""
+msgid "Metrics|There was an error getting options for variable \"%{name}\"."
+msgstr ""
+
msgid "Metrics|There was an error trying to validate your query"
msgstr ""
@@ -14021,6 +14452,9 @@ msgstr ""
msgid "Metrics|You're about to permanently delete this metric. This cannot be undone."
msgstr ""
+msgid "Metrics|Your dashboard schema is invalid. Edit the dashboard to correct the YAML schema."
+msgstr ""
+
msgid "Metrics|e.g. HTTP requests"
msgstr ""
@@ -14036,12 +14470,18 @@ msgstr ""
msgid "Metrics|e.g. req/sec"
msgstr ""
+msgid "Mi"
+msgstr ""
+
msgid "Microsoft Azure"
msgstr ""
msgid "Middleman project with Static Site Editor support"
msgstr ""
+msgid "Migrate your data from an external source like GitHub, Bitbucket, or another instance of GitLab."
+msgstr ""
+
msgid "Migrated %{success_count}/%{total_count} files."
msgstr ""
@@ -14176,6 +14616,9 @@ msgstr ""
msgid "Minimum capacity to be available before we schedule more mirrors preemptively."
msgstr ""
+msgid "Minimum interval in days"
+msgstr ""
+
msgid "Minimum length is %{minimum_password_length} characters"
msgstr ""
@@ -14362,6 +14805,12 @@ msgstr ""
msgid "Multiple uploaders found: %{uploader_types}"
msgstr ""
+msgid "Must match with the %{codeStart}external_url%{codeEnd} in %{codeStart}/etc/gitlab/gitlab.rb%{codeEnd}."
+msgstr ""
+
+msgid "Must match with the %{codeStart}geo_node_name%{codeEnd} in %{codeStart}/etc/gitlab/gitlab.rb%{codeEnd}. %{linkStart}More information%{linkEnd}"
+msgstr ""
+
msgid "My Awesome Group"
msgstr ""
@@ -14476,10 +14925,16 @@ msgstr ""
msgid "New Application"
msgstr ""
+msgid "New Branch"
+msgstr ""
+
+msgid "New Deploy Key"
+msgstr ""
+
msgid "New Environment"
msgstr ""
-msgid "New Geo Node"
+msgid "New File"
msgstr ""
msgid "New Group"
@@ -14496,15 +14951,15 @@ msgid_plural "New Issues"
msgstr[0] ""
msgstr[1] ""
-msgid "New Iteration"
-msgstr ""
-
msgid "New Jira import"
msgstr ""
msgid "New Label"
msgstr ""
+msgid "New Merge Request"
+msgstr ""
+
msgid "New Milestone"
msgstr ""
@@ -14523,6 +14978,9 @@ msgstr ""
msgid "New Snippet"
msgstr ""
+msgid "New User"
+msgstr ""
+
msgid "New branch"
msgstr ""
@@ -14565,6 +15023,9 @@ msgstr ""
msgid "New issue title"
msgstr ""
+msgid "New iteration"
+msgstr ""
+
msgid "New iteration created"
msgstr ""
@@ -14742,6 +15203,12 @@ msgstr ""
msgid "No grouping"
msgstr ""
+msgid "No iteration"
+msgstr ""
+
+msgid "No iterations to show"
+msgstr ""
+
msgid "No job log"
msgstr ""
@@ -14784,6 +15251,9 @@ msgstr ""
msgid "No other labels with such name or description"
msgstr ""
+msgid "No panels matching properties %{opts}"
+msgstr ""
+
msgid "No parent group"
msgstr ""
@@ -14817,6 +15287,9 @@ msgstr ""
msgid "No schedules"
msgstr ""
+msgid "No stack trace for this error"
+msgstr ""
+
msgid "No starrers matched your search"
msgstr ""
@@ -14829,7 +15302,7 @@ msgstr ""
msgid "No test coverage"
msgstr ""
-msgid "No thanks, don't show this again"
+msgid "No thanks"
msgstr ""
msgid "No vulnerabilities present"
@@ -14844,9 +15317,6 @@ msgstr ""
msgid "No, directly import the existing email addresses and usernames."
msgstr ""
-msgid "No, not interested right now"
-msgstr ""
-
msgid "No. of commits"
msgstr ""
@@ -14892,9 +15362,6 @@ msgstr ""
msgid "Not found."
msgstr ""
-msgid "Not helpful"
-msgstr ""
-
msgid "Not now"
msgstr ""
@@ -15051,6 +15518,9 @@ msgstr ""
msgid "Notifications on"
msgstr ""
+msgid "Notify users by email when sign-in location is not recognized"
+msgstr ""
+
msgid "Nov"
msgstr ""
@@ -15117,9 +15587,6 @@ msgstr ""
msgid "Oh no!"
msgstr ""
-msgid "Ok let's go"
-msgstr ""
-
msgid "Oldest first"
msgstr ""
@@ -15132,16 +15599,49 @@ msgstr ""
msgid "On track"
msgstr ""
+msgid "OnDemandScans|Attached branch"
+msgstr ""
+
+msgid "OnDemandScans|Attached branch is where the scan job runs."
+msgstr ""
+
+msgid "OnDemandScans|Could not run the scan. Please try again."
+msgstr ""
+
msgid "OnDemandScans|Create new DAST scan"
msgstr ""
+msgid "OnDemandScans|DAST will scan the target URL and any discovered sub URLs."
+msgstr ""
+
+msgid "OnDemandScans|New on-demand DAST scan"
+msgstr ""
+
msgid "OnDemandScans|On-demand Scans"
msgstr ""
+msgid "OnDemandScans|On-demand scans run outside the DevOps cycle and find vulnerabilities in your projects. %{learnMoreLinkStart}Learn more%{learnMoreLinkEnd}"
+msgstr ""
+
+msgid "OnDemandScans|Only a passive scan can be performed on demand."
+msgstr ""
+
+msgid "OnDemandScans|Passive DAST Scan"
+msgstr ""
+
+msgid "OnDemandScans|Please enter a valid URL format, ex: http://www.example.com/home"
+msgstr ""
+
+msgid "OnDemandScans|Run this scan"
+msgstr ""
+
+msgid "OnDemandScans|Scan mode"
+msgstr ""
+
msgid "OnDemandScans|Schedule or run scans immediately against target sites. Currently available on-demand scan type: DAST. %{helpLinkStart}More information%{helpLinkEnd}"
msgstr ""
-msgid "Onboarding"
+msgid "OnDemandScans|Target URL"
msgstr ""
msgid "Once imported, repositories can be mirrored over SSH. Read more %{link_start}here%{link_end}."
@@ -15167,6 +15667,9 @@ msgstr ""
msgid "One or more of you personal access tokens were revoked"
msgstr ""
+msgid "One or more of your %{provider} projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
+msgstr ""
+
msgid "One or more of your Bitbucket projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
msgstr ""
@@ -15668,6 +16171,9 @@ msgstr ""
msgid "Parent epic is not present."
msgstr ""
+msgid "Parsing error for param :embed_json. %{message}"
+msgstr ""
+
msgid "Part of merge request changes"
msgstr ""
@@ -15680,6 +16186,9 @@ msgstr ""
msgid "Passed"
msgstr ""
+msgid "Passed on"
+msgstr ""
+
msgid "Password"
msgstr ""
@@ -15764,6 +16273,9 @@ msgstr ""
msgid "Perform common operations on GitLab project"
msgstr ""
+msgid "Performance and resource management"
+msgstr ""
+
msgid "Performance optimization"
msgstr ""
@@ -15971,7 +16483,7 @@ msgstr ""
msgid "Pipelines|Something went wrong while cleaning runners cache."
msgstr ""
-msgid "Pipelines|There are currently no %{scope} pipelines."
+msgid "Pipelines|There are currently no finished pipelines."
msgstr ""
msgid "Pipelines|There are currently no pipelines."
@@ -16082,6 +16594,9 @@ msgstr ""
msgid "Pipeline|Stop pipeline #%{pipelineId}?"
msgstr ""
+msgid "Pipeline|Tag name"
+msgstr ""
+
msgid "Pipeline|Trigger author"
msgstr ""
@@ -16163,12 +16678,18 @@ msgstr ""
msgid "Please check your email (%{email}) to verify that you own this address and unlock the power of CI/CD. Didn't receive it? %{resend_link}. Wrong email address? %{update_link}."
msgstr ""
+msgid "Please choose a file"
+msgstr ""
+
msgid "Please choose a group URL with no special characters."
msgstr ""
msgid "Please complete your profile with email address"
msgstr ""
+msgid "Please convert %{linkStart}them to Git%{linkEnd}, and go through the %{linkToImportFlow} again."
+msgstr ""
+
msgid "Please convert them to %{link_to_git}, and go through the %{link_to_import_flow} again."
msgstr ""
@@ -16226,6 +16747,9 @@ msgstr ""
msgid "Please provide a valid email address."
msgstr ""
+msgid "Please provide attributes to update"
+msgstr ""
+
msgid "Please refer to <a href=\"%{docs_url}\">%{docs_url}</a>"
msgstr ""
@@ -16961,6 +17485,9 @@ msgstr ""
msgid "Project has too many %{label_for_message} to search"
msgstr ""
+msgid "Project is required when cluster_type is :project"
+msgstr ""
+
msgid "Project members"
msgstr ""
@@ -17078,7 +17605,40 @@ msgstr ""
msgid "ProjectService|Comment"
msgstr ""
-msgid "ProjectService|Comment will be posted on each event"
+msgid "ProjectService|Event will be triggered by a push to the repository"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a commit is created/updated"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a confidential issue is created/updated/closed"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a deployment finishes"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a merge request is created/updated/merged"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a new tag is pushed to the repository"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a new, unique alert is recorded"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a pipeline status changes"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a wiki page is created/updated"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when an issue is created/updated/closed"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when someone adds a comment"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when someone adds a comment on a confidential issue"
msgstr ""
msgid "ProjectService|Perform common operations on GitLab project: %{project_name}"
@@ -17129,6 +17689,9 @@ msgstr ""
msgid "ProjectSettings|Enable 'Delete source branch' option by default"
msgstr ""
+msgid "ProjectSettings|Enable merge trains and pipelines for merged results"
+msgstr ""
+
msgid "ProjectSettings|Every merge creates a merge commit"
msgstr ""
@@ -17159,7 +17722,7 @@ msgstr ""
msgid "ProjectSettings|Forks"
msgstr ""
-msgid "ProjectSettings|Git Large File Storage"
+msgid "ProjectSettings|Git Large File Storage (LFS)"
msgstr ""
msgid "ProjectSettings|Internal"
@@ -17195,9 +17758,6 @@ msgstr ""
msgid "ProjectSettings|Merge options"
msgstr ""
-msgid "ProjectSettings|Merge pipelines will try to validate the post-merge result prior to merging"
-msgstr ""
-
msgid "ProjectSettings|Merge requests"
msgstr ""
@@ -17225,6 +17785,9 @@ msgstr ""
msgid "ProjectSettings|Pipelines"
msgstr ""
+msgid "ProjectSettings|Pipelines for merge requests must be enabled in the CI/CD configuration file, or pipelines could be unresolvable or dropped"
+msgstr ""
+
msgid "ProjectSettings|Pipelines must succeed"
msgstr ""
@@ -17252,6 +17815,9 @@ msgstr ""
msgid "ProjectSettings|Show link to create/view merge request when pushing from the command line"
msgstr ""
+msgid "ProjectSettings|Skipped pipelines are considered successful"
+msgstr ""
+
msgid "ProjectSettings|Snippets"
msgstr ""
@@ -17267,6 +17833,9 @@ msgstr ""
msgid "ProjectSettings|These checks must pass before merge requests can be merged"
msgstr ""
+msgid "ProjectSettings|This introduces the risk of merging changes that will not pass the pipeline."
+msgstr ""
+
msgid "ProjectSettings|This setting is applied on the server level and can be overridden by an admin."
msgstr ""
@@ -17444,15 +18013,27 @@ msgstr ""
msgid "ProjectsNew|Blank project"
msgstr ""
+msgid "ProjectsNew|Connect your external repository to GitLab CI/CD."
+msgstr ""
+
msgid "ProjectsNew|Contact an administrator to enable options for importing your project."
msgstr ""
msgid "ProjectsNew|Create"
msgstr ""
+msgid "ProjectsNew|Create a blank project to house your files, plan your work, and collaborate on code, among other things."
+msgstr ""
+
+msgid "ProjectsNew|Create blank project"
+msgstr ""
+
msgid "ProjectsNew|Create from template"
msgstr ""
+msgid "ProjectsNew|Create new project"
+msgstr ""
+
msgid "ProjectsNew|Creating project & repository."
msgstr ""
@@ -17477,6 +18058,9 @@ msgstr ""
msgid "ProjectsNew|Project description %{tag_start}(optional)%{tag_end}"
msgstr ""
+msgid "ProjectsNew|Run CI/CD for external repository"
+msgstr ""
+
msgid "ProjectsNew|Template"
msgstr ""
@@ -17738,6 +18322,9 @@ msgstr ""
msgid "Protected Branch"
msgstr ""
+msgid "Protected Branches"
+msgstr ""
+
msgid "Protected Environment"
msgstr ""
@@ -17750,6 +18337,9 @@ msgstr ""
msgid "Protected Tag"
msgstr ""
+msgid "Protected Tags"
+msgstr ""
+
msgid "Protected branches"
msgstr ""
@@ -17849,6 +18439,9 @@ msgstr ""
msgid "Provider"
msgstr ""
+msgid "Proxy support for this API is not available currently"
+msgstr ""
+
msgid "Pseudonymizer data collection"
msgstr ""
@@ -17873,6 +18466,9 @@ msgstr ""
msgid "Publish to status page"
msgstr ""
+msgid "Published on status page"
+msgstr ""
+
msgid "Publishes this issue to the associated status page."
msgstr ""
@@ -18047,6 +18643,9 @@ msgstr ""
msgid "Recipe"
msgstr ""
+msgid "Reconfigure"
+msgstr ""
+
msgid "Recover hidden stage"
msgstr ""
@@ -18133,6 +18732,9 @@ msgstr ""
msgid "Registration|Your profile"
msgstr ""
+msgid "Regulate approvals by authors/committers, based on compliance frameworks. Can be changed only at the instance level."
+msgstr ""
+
msgid "Rejected (closed)"
msgstr ""
@@ -18183,15 +18785,24 @@ msgstr ""
msgid "ReleaseAssetLinkType|Image"
msgstr ""
+msgid "ReleaseAssetLinkType|Images"
+msgstr ""
+
msgid "ReleaseAssetLinkType|Other"
msgstr ""
msgid "ReleaseAssetLinkType|Package"
msgstr ""
+msgid "ReleaseAssetLinkType|Packages"
+msgstr ""
+
msgid "ReleaseAssetLinkType|Runbook"
msgstr ""
+msgid "ReleaseAssetLinkType|Runbooks"
+msgstr ""
+
msgid "Releases"
msgstr ""
@@ -18204,6 +18815,9 @@ msgstr ""
msgid "Releases documentation"
msgstr ""
+msgid "Releases|New Release"
+msgstr ""
+
msgid "Release|Something went wrong while getting the release details"
msgstr ""
@@ -18276,6 +18890,9 @@ msgstr ""
msgid "Remove fork relationship"
msgstr ""
+msgid "Remove from batch"
+msgstr ""
+
msgid "Remove from board"
msgstr ""
@@ -18465,6 +19082,12 @@ msgstr ""
msgid "Replication"
msgstr ""
+msgid "Replication enabled"
+msgstr ""
+
+msgid "Replication paused"
+msgstr ""
+
msgid "Reply by email"
msgstr ""
@@ -18492,15 +19115,15 @@ msgstr ""
msgid "Reported %{timeAgo} by %{reportedBy}"
msgstr ""
-msgid "Reported Resource Changes: %{addNum} to add, %{changeNum} to change, %{deleteNum} to delete"
-msgstr ""
-
msgid "Reporter"
msgstr ""
msgid "Reporting"
msgstr ""
+msgid "Reports"
+msgstr ""
+
msgid "Reports|%{combinedString} and %{resolvedString}"
msgstr ""
@@ -18539,6 +19162,9 @@ msgstr ""
msgid "Reports|Failure"
msgstr ""
+msgid "Reports|Identifier"
+msgstr ""
+
msgid "Reports|Metrics reports are loading"
msgstr ""
@@ -18551,6 +19177,9 @@ msgstr ""
msgid "Reports|Metrics reports failed loading results"
msgstr ""
+msgid "Reports|Scanner"
+msgstr ""
+
msgid "Reports|Severity"
msgstr ""
@@ -18599,15 +19228,33 @@ msgstr ""
msgid "Repository cleanup has started. You will receive an email once the cleanup operation is complete."
msgstr ""
+msgid "Repository files count over the limit"
+msgstr ""
+
+msgid "Repository has an invalid default branch name."
+msgstr ""
+
+msgid "Repository has more than one branch."
+msgstr ""
+
msgid "Repository has no locks."
msgstr ""
+msgid "Repository has tags."
+msgstr ""
+
msgid "Repository maintenance"
msgstr ""
msgid "Repository mirroring"
msgstr ""
+msgid "Repository must contain at least 1 file."
+msgstr ""
+
+msgid "Repository size is above the limit."
+msgstr ""
+
msgid "Repository static objects"
msgstr ""
@@ -18617,7 +19264,7 @@ msgstr ""
msgid "Repository sync capacity"
msgstr ""
-msgid "Repository: %{counter_repositories} / Wikis: %{counter_wikis} / Build Artifacts: %{counter_build_artifacts} / LFS: %{counter_lfs_objects}"
+msgid "Repository: %{counter_repositories} / Wikis: %{counter_wikis} / Build Artifacts: %{counter_build_artifacts} / LFS: %{counter_lfs_objects} / Snippets: %{counter_snippets}"
msgstr ""
msgid "RepositorySettingsAccessLevel|Select"
@@ -18629,6 +19276,9 @@ msgstr ""
msgid "Request Headers"
msgstr ""
+msgid "Request details"
+msgstr ""
+
msgid "Request parameter %{param} is missing."
msgstr ""
@@ -18735,6 +19385,9 @@ msgstr ""
msgid "Reset template"
msgstr ""
+msgid "Reset to project defaults"
+msgstr ""
+
msgid "Resetting the authorization key for this project will require updating the authorization key in every alert source it is enabled in."
msgstr ""
@@ -18831,9 +19484,6 @@ msgstr ""
msgid "Resume"
msgstr ""
-msgid "Resume replication"
-msgstr ""
-
msgid "Resync"
msgstr ""
@@ -19456,6 +20106,9 @@ msgstr ""
msgid "SecurityReports|False positive"
msgstr ""
+msgid "SecurityReports|Group Security Dashboard"
+msgstr ""
+
msgid "SecurityReports|Hide dismissed"
msgstr ""
@@ -19510,10 +20163,13 @@ msgstr ""
msgid "SecurityReports|Remove project from dashboard"
msgstr ""
-msgid "SecurityReports|Report type"
+msgid "SecurityReports|Return to dashboard"
msgstr ""
-msgid "SecurityReports|Return to dashboard"
+msgid "SecurityReports|Scan details"
+msgstr ""
+
+msgid "SecurityReports|Scanner type"
msgstr ""
msgid "SecurityReports|Security Dashboard"
@@ -19570,6 +20226,9 @@ msgstr ""
msgid "SecurityReports|There was an error while generating the report."
msgstr ""
+msgid "SecurityReports|Unable to add %{invalidProjectsMessage}"
+msgstr ""
+
msgid "SecurityReports|Unable to add %{invalidProjects}"
msgstr ""
@@ -19684,6 +20343,9 @@ msgstr ""
msgid "Select due date"
msgstr ""
+msgid "Select file"
+msgstr ""
+
msgid "Select group or project"
msgstr ""
@@ -19747,9 +20409,6 @@ msgstr ""
msgid "Select the branch you want to set as the default for this project. All merge requests and commits will automatically be made against this branch unless you specify a different one."
msgstr ""
-msgid "Select the configured storage available for new repositories to be placed on."
-msgstr ""
-
msgid "Select the custom project template source group."
msgstr ""
@@ -19942,12 +20601,6 @@ msgstr ""
msgid "Service Desk is enabled but not yet active"
msgstr ""
-msgid "Service Desk is off"
-msgstr ""
-
-msgid "Service Desk is on"
-msgstr ""
-
msgid "Service Templates"
msgstr ""
@@ -20035,6 +20688,15 @@ msgstr ""
msgid "Set the milestone to %{milestone_reference}."
msgstr ""
+msgid "Set the number of concurrent requests this secondary node will make to the primary node while backfilling."
+msgstr ""
+
+msgid "Set the synchronization and verification capacity for the secondary node."
+msgstr ""
+
+msgid "Set the timeout in seconds to send a secondary node status to the primary and IPs allowed for the secondary nodes."
+msgstr ""
+
msgid "Set time estimate"
msgstr ""
@@ -20074,6 +20736,9 @@ msgstr ""
msgid "Set weight to %{weight}."
msgstr ""
+msgid "Set what should be replicated by choosing specific projects or groups by the secondary node."
+msgstr ""
+
msgid "SetPasswordToCloneLink|set a password"
msgstr ""
@@ -20200,6 +20865,9 @@ msgstr ""
msgid "Show complete raw log"
msgstr ""
+msgid "Show details"
+msgstr ""
+
msgid "Show file browser"
msgstr ""
@@ -20209,10 +20877,13 @@ msgstr ""
msgid "Show latest version"
msgstr ""
+msgid "Show list"
+msgstr ""
+
msgid "Show me everything"
msgstr ""
-msgid "Show me how"
+msgid "Show me how to add a pipeline"
msgstr ""
msgid "Show me more advanced stuff"
@@ -20261,9 +20932,6 @@ msgstr ""
msgid "Showing all issues"
msgstr ""
-msgid "Showing all labels"
-msgstr ""
-
msgid "Showing last %{size} of log -"
msgstr ""
@@ -20375,9 +21043,6 @@ msgstr ""
msgid "Skip outdated deployment jobs"
msgstr ""
-msgid "Skip this for now"
-msgstr ""
-
msgid "Skipped"
msgstr ""
@@ -20522,6 +21187,9 @@ msgstr ""
msgid "Something went wrong while adding your award. Please try again."
msgstr ""
+msgid "Something went wrong while applying the batch of suggestions. Please try again."
+msgstr ""
+
msgid "Something went wrong while applying the suggestion. Please try again."
msgstr ""
@@ -20624,10 +21292,13 @@ msgstr ""
msgid "Something went wrong while updating a requirement."
msgstr ""
+msgid "Something went wrong while updating assignees"
+msgstr ""
+
msgid "Something went wrong while updating your list settings"
msgstr ""
-msgid "Something went wrong with your automatic subscription renewal"
+msgid "Something went wrong with your automatic subscription renewal."
msgstr ""
msgid "Something went wrong, unable to add %{project} to dashboard"
@@ -20645,6 +21316,9 @@ msgstr ""
msgid "Something went wrong, unable to search projects"
msgstr ""
+msgid "Something went wrong."
+msgstr ""
+
msgid "Something went wrong. Please try again."
msgstr ""
@@ -20828,6 +21502,9 @@ msgstr ""
msgid "Source code"
msgstr ""
+msgid "Source code (%{fileExtension})"
+msgstr ""
+
msgid "Source is not available"
msgstr ""
@@ -20912,6 +21589,9 @@ msgstr ""
msgid "Stack trace"
msgstr ""
+msgid "Stacktrace snippet"
+msgstr ""
+
msgid "Stage"
msgstr ""
@@ -21017,6 +21697,9 @@ msgstr ""
msgid "Start thread & reopen %{noteable_name}"
msgstr ""
+msgid "Start using Directed Acyclic Graphs (DAG)"
+msgstr ""
+
msgid "Start your Free Gold Trial"
msgstr ""
@@ -21479,6 +22162,21 @@ msgstr ""
msgid "SuggestedColors|Very pale orange"
msgstr ""
+msgid "Suggestion is not applicable as the suggestion was not found."
+msgstr ""
+
+msgid "Suggestions are not applicable as one or more suggestions were not found."
+msgstr ""
+
+msgid "Suggestions are not applicable as their lines cannot overlap."
+msgstr ""
+
+msgid "Suggestions must all be on the same branch."
+msgstr ""
+
+msgid "Suggestions that change line count can't be added to batches, yet."
+msgstr ""
+
msgid "Suggestions:"
msgstr ""
@@ -21521,9 +22219,6 @@ msgstr ""
msgid "Synchronization disabled"
msgstr ""
-msgid "Synchronization of container repositories is disabled."
-msgstr ""
-
msgid "System"
msgstr ""
@@ -21704,6 +22399,34 @@ msgstr ""
msgid "Terms of Service and Privacy Policy"
msgstr ""
+msgid "Terraform|%{number} Terraform report failed to generate"
+msgid_plural "Terraform|%{number} Terraform reports failed to generate"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Terraform|%{number} Terraform report was generated in your pipelines"
+msgid_plural "Terraform|%{number} Terraform reports were generated in your pipelines"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Terraform|A Terraform report failed to generate."
+msgstr ""
+
+msgid "Terraform|A Terraform report was generated in your pipelines."
+msgstr ""
+
+msgid "Terraform|Generating the report caused an error."
+msgstr ""
+
+msgid "Terraform|Reported Resource Changes: %{addNum} to add, %{changeNum} to change, %{deleteNum} to delete"
+msgstr ""
+
+msgid "Terraform|The Terraform report %{name} failed to generate."
+msgstr ""
+
+msgid "Terraform|The Terraform report %{name} was generated in your pipelines."
+msgstr ""
+
msgid "Test"
msgstr ""
@@ -21730,7 +22453,7 @@ msgstr ""
msgid "TestHooks|Ensure the project has CI pipelines."
msgstr ""
-msgid "TestHooks|Ensure the project has at least one commit."
+msgid "TestHooks|Ensure the project has deployments."
msgstr ""
msgid "TestHooks|Ensure the project has issues."
@@ -21831,7 +22554,7 @@ msgstr ""
msgid "The Prometheus server responded with \"bad request\". Please check your queries are correct and are supported in your Prometheus version. %{documentationLink}"
msgstr ""
-msgid "The URL defined on the primary node that secondary nodes should use to contact it. Defaults to URL"
+msgid "The URL defined on the primary node that secondary nodes should use to contact it."
msgstr ""
msgid "The URL to use for connecting to Elasticsearch. Use a comma-separated list to support clustering (e.g., \"http://localhost:9200, http://localhost:9201\")."
@@ -21840,6 +22563,9 @@ msgstr ""
msgid "The X509 Certificate to use when mutual TLS is required to communicate with the external authorization service. If left blank, the server certificate is still validated when accessing over HTTPS."
msgstr ""
+msgid "The above settings apply to all projects with the selected compliance framework(s)."
+msgstr ""
+
msgid "The amount of seconds after which a request to get a secondary node status will time out."
msgstr ""
@@ -22139,9 +22865,6 @@ msgstr ""
msgid "The total stage shows the time it takes between creating an issue and deploying the code to production. The data will be automatically added once you have completed the full idea to production cycle."
msgstr ""
-msgid "The unique identifier for the Geo node. Must match %{geoNodeName} if it is set in gitlab.rb, otherwise it must match %{externalUrl} with a trailing slash"
-msgstr ""
-
msgid "The update action will time out after %{number_of_minutes} minutes. For big repositories, use a clone/push combination."
msgstr ""
@@ -22166,9 +22889,6 @@ msgstr ""
msgid "The user you are trying to deactivate has been active in the past %{minimum_inactive_days} days and cannot be deactivated"
msgstr ""
-msgid "The user-facing URL of the Geo node"
-msgstr ""
-
msgid "The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6."
msgstr ""
@@ -22259,6 +22979,9 @@ msgstr ""
msgid "There was a problem fetching project branches."
msgstr ""
+msgid "There was a problem fetching project tags."
+msgstr ""
+
msgid "There was a problem fetching project users."
msgstr ""
@@ -22310,6 +23033,9 @@ msgstr ""
msgid "There was an error fetching the %{replicableType}"
msgstr ""
+msgid "There was an error fetching the Geo Settings"
+msgstr ""
+
msgid "There was an error fetching the Node's Groups"
msgstr ""
@@ -22430,6 +23156,12 @@ msgstr ""
msgid "This %{issuable} is locked. Only <strong>project members</strong> can comment."
msgstr ""
+msgid "This %{noteableTypeText} is %{confidentialLinkStart}confidential%{linkEnd} and %{lockedLinkStart}locked%{linkEnd}."
+msgstr ""
+
+msgid "This %{noteableTypeText} is locked."
+msgstr ""
+
msgid "This %{viewer} could not be displayed because %{reason}. You can %{options} instead."
msgstr ""
@@ -22448,6 +23180,9 @@ msgstr ""
msgid "This action can lead to data loss. To prevent accidental actions we ask you to confirm your intention."
msgstr ""
+msgid "This also resolves all related threads"
+msgstr ""
+
msgid "This also resolves the discussion"
msgstr ""
@@ -22538,6 +23273,9 @@ msgstr ""
msgid "This epic does not exist or you don't have sufficient permission."
msgstr ""
+msgid "This feature is currently in beta. We invite you to %{linkStart}give feedback%{linkEnd}."
+msgstr ""
+
msgid "This feature requires local storage to be enabled"
msgstr ""
@@ -22571,7 +23309,7 @@ msgstr ""
msgid "This is a Work in Progress"
msgstr ""
-msgid "This is a confidential issue."
+msgid "This is a confidential %{noteableTypeText}."
msgstr ""
msgid "This is a delayed job to run in %{remainingTime}"
@@ -22598,9 +23336,6 @@ msgstr ""
msgid "This is your current session"
msgstr ""
-msgid "This issue is %{confidentialLinkStart}confidential%{linkEnd} and %{lockedLinkStart}locked%{linkEnd}."
-msgstr ""
-
msgid "This issue is confidential"
msgstr ""
@@ -22610,9 +23345,6 @@ msgstr ""
msgid "This issue is in a child epic of the filtered epic"
msgstr ""
-msgid "This issue is locked."
-msgstr ""
-
msgid "This job depends on other jobs with expired/erased artifacts: %{invalid_dependencies}"
msgstr ""
@@ -22712,6 +23444,9 @@ msgstr ""
msgid "This license has already expired."
msgstr ""
+msgid "This link points to external content"
+msgstr ""
+
msgid "This may expose confidential information as the selected fork is in another namespace that can have other members."
msgstr ""
@@ -22739,6 +23474,9 @@ msgstr ""
msgid "This page will be removed in a future release."
msgstr ""
+msgid "This pipeline does not use the %{codeStart}needs%{codeEnd} keyword and can't be represented as a directed acyclic graph."
+msgstr ""
+
msgid "This pipeline makes use of a predefined CI/CD configuration enabled by %{strongStart}Auto DevOps.%{strongEnd}"
msgstr ""
@@ -22967,6 +23705,9 @@ msgstr ""
msgid "Time from last commit to merge"
msgstr ""
+msgid "Time in seconds"
+msgstr ""
+
msgid "Time in seconds GitLab will wait for a response from the external service. When the service does not respond in time, access will be denied."
msgstr ""
@@ -23388,9 +24129,15 @@ msgstr ""
msgid "Total artifacts size: %{total_size}"
msgstr ""
+msgid "Total cores (CPUs)"
+msgstr ""
+
msgid "Total issues"
msgstr ""
+msgid "Total memory (GB)"
+msgstr ""
+
msgid "Total test time for all commits/merges"
msgstr ""
@@ -23592,6 +24339,9 @@ msgstr ""
msgid "U2F only works with HTTPS-enabled websites. Contact your administrator for more details."
msgstr ""
+msgid "UI Development Kit"
+msgstr ""
+
msgid "URL"
msgstr ""
@@ -23664,9 +24414,6 @@ msgstr ""
msgid "Unable to load the merge request widget. Try reloading the page."
msgstr ""
-msgid "Unable to process group import file"
-msgstr ""
-
msgid "Unable to resolve"
msgstr ""
@@ -23682,6 +24429,9 @@ msgstr ""
msgid "Unable to sign you in to the group with SAML due to \"%{reason}\""
msgstr ""
+msgid "Unable to suggest a path. Please refresh and try again."
+msgstr ""
+
msgid "Unable to update label prioritization at this time"
msgstr ""
@@ -23697,6 +24447,9 @@ msgstr ""
msgid "Unarchiving the project will restore people's ability to make changes to it. The repository can be committed to, and issues, comments, and other entities can be created. %{strong_start}Once active, this project shows up in the search and on the dashboard.%{strong_end}"
msgstr ""
+msgid "Unassign from commenting user"
+msgstr ""
+
msgid "Unblock"
msgstr ""
@@ -23772,6 +24525,9 @@ msgstr ""
msgid "Unreachable"
msgstr ""
+msgid "Unrecognized cluster type"
+msgstr ""
+
msgid "Unresolve"
msgstr ""
@@ -23832,6 +24588,9 @@ msgstr ""
msgid "Unsubscribes from this %{quick_action_target}."
msgstr ""
+msgid "Unsupported todo type passed. Supported todo types are: %{todo_types}"
+msgstr ""
+
msgid "Until"
msgstr ""
@@ -23859,6 +24618,9 @@ msgstr ""
msgid "Update approval rule"
msgstr ""
+msgid "Update approvers"
+msgstr ""
+
msgid "Update failed"
msgstr ""
@@ -23868,6 +24630,9 @@ msgstr ""
msgid "Update it"
msgstr ""
+msgid "Update iteration"
+msgstr ""
+
msgid "Update now"
msgstr ""
@@ -23913,7 +24678,7 @@ msgstr ""
msgid "Updated at"
msgstr ""
-msgid "Updated to"
+msgid "Updated to %{linkStart}chart v%{linkEnd}"
msgstr ""
msgid "Updating"
@@ -23943,6 +24708,9 @@ msgstr ""
msgid "Upload CSV file"
msgstr ""
+msgid "Upload License"
+msgstr ""
+
msgid "Upload New File"
msgstr ""
@@ -24024,16 +24792,19 @@ msgstr ""
msgid "UsageQuota|Pipelines"
msgstr ""
+msgid "UsageQuota|Purchase more storage"
+msgstr ""
+
msgid "UsageQuota|Repositories"
msgstr ""
msgid "UsageQuota|Repository"
msgstr ""
-msgid "UsageQuota|Storage"
+msgid "UsageQuota|Snippets"
msgstr ""
-msgid "UsageQuota|Storage usage:"
+msgid "UsageQuota|Storage"
msgstr ""
msgid "UsageQuota|This namespace has no projects which use shared runners"
@@ -24066,6 +24837,12 @@ msgstr ""
msgid "UsageQuota|Wikis"
msgstr ""
+msgid "UsageQuota|You used: %{usage} %{limit}"
+msgstr ""
+
+msgid "UsageQuota|out of %{formattedLimit} of your namespace storage"
+msgstr ""
+
msgid "Use %{code_start}::%{code_end} to create a %{link_start}scoped label set%{link_end} (eg. %{code_start}priority::1%{code_end})"
msgstr ""
@@ -24132,6 +24909,9 @@ msgstr ""
msgid "User IDs"
msgstr ""
+msgid "User Lists can only be created and modified with %{linkStart}the API%{linkEnd}"
+msgstr ""
+
msgid "User OAuth applications"
msgstr ""
@@ -24156,6 +24936,9 @@ msgstr ""
msgid "User key was successfully removed."
msgstr ""
+msgid "User list %{name} will be removed. Are you sure?"
+msgstr ""
+
msgid "User map"
msgstr ""
@@ -24177,148 +24960,10 @@ msgstr ""
msgid "User was successfully updated."
msgstr ""
-msgid "UserOnboardingTour|%{activeTour}/%{totalTours}"
-msgstr ""
-
-msgid "UserOnboardingTour|%{completed}/%{total} steps completed"
-msgstr ""
-
-msgid "UserOnboardingTour|%{emphasisStart}Well done!%{emphasisEnd}%{lineBreak}%{lineBreak}That's it for our guided tour, congratulations for making it all the way to the end!%{lineBreak}%{lineBreak}We hope this gave you a good overview of GitLab and how it can help you. We'll now show you how to create your own project and invite your colleagues."
-msgstr ""
-
-msgid "UserOnboardingTour|Adding other members to a project is done through Project Settings. Click on %{emphasisStart}Settings%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Alright, that's it for Commits. Let's take a look at the %{emphasisStart}Branches%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Awesome! Now click on %{emphasisStart}Members%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Click on one of the %{emphasisStart}Compare%{emphasisEnd} buttons to compare a branch to master."
-msgstr ""
-
-msgid "UserOnboardingTour|Click on one of the %{emphasisStart}pipeline IDs%{emphasisEnd} to see the details of a pipeline."
-msgstr ""
-
-msgid "UserOnboardingTour|Click to open the latest commit to see its details."
-msgstr ""
-
-msgid "UserOnboardingTour|Close 'Learn GitLab'"
-msgstr ""
-
-msgid "UserOnboardingTour|Commits are shown in chronological order and can be filtered by the commit message or by the branch."
+msgid "UserList|Delete %{name}?"
msgstr ""
-msgid "UserOnboardingTour|Create a project"
-msgstr ""
-
-msgid "UserOnboardingTour|Exit 'Learn GitLab'"
-msgstr ""
-
-msgid "UserOnboardingTour|Got it"
-msgstr ""
-
-msgid "UserOnboardingTour|Great job! %{clapHands} We hope the tour was helpful and that you learned how to use GitLab.%{lineBreak}%{lineBreak}We'd love to get your feedback on this tour.%{lineBreak}%{lineBreak}%{emphasisStart}How helpful would you say this guided tour was?%{emphasisEnd}%{lineBreak}%{lineBreak}"
-msgstr ""
-
-msgid "UserOnboardingTour|Guided GitLab Tour"
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can compare the changes of this branch to another one. Changes are divided by files so that it's easier to see what was changed where."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can create a project from scratch, start with a template or import a repository from other platforms. Whatever you choose, we'll guide you through the process.%{lineBreak}%{lineBreak}Fill in your new project information and click on %{emphasisStart}Create Project%{emphasisEnd} to progress to the next step."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can see the breakdown of the pipelines: its stages and jobs in each of the stages and their status.%{lineBreak}%{lineBreak}Our CI/CD pipelines are quite complex, most of our users have fewer and simpler pipelines."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can see the current members of the project (just you at the moment) and invite new members.%{lineBreak}%{lineBreak}You can invite multiple members at once (existing GitLab users or invite by email) and you can also set their roles and permissions.%{lineBreak}%{lineBreak}Add a few members and click on %{emphasisStart}Add to project%{emphasisEnd} to complete this step."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can see what changes were made with this commit, on what branch and if there's a related merge request. The status of the pipeline will also show up if CI/CD is set up.%{lineBreak}%{lineBreak}You can also comment on the lines of code that were changed and start a discussion with your colleagues!"
-msgstr ""
-
-msgid "UserOnboardingTour|Here's an overview of branches in the %{emphasisStart}%{projectName}%{emphasisEnd} project. They're split into Active and Stale.%{lineBreak}%{lineBreak}From here, you can create a new merge request from a branch, or compare the branch to any other branch in the project. By default, it will compare it to the master branch."
-msgstr ""
-
-msgid "UserOnboardingTour|Invite colleagues"
-msgstr ""
-
-msgid "UserOnboardingTour|Issues are great for communicating and keeping track of progress in GitLab. These are all issues that are open in the %{emphasisStart}%{projectName}%{emphasisEnd}.%{lineBreak}%{lineBreak}You can help us improve GitLab by contributing work to issues that are labeled <span class=\"badge color-label accept-mr-label\">Accepting merge requests</span>.%{lineBreak}%{lineBreak}This list can be filtered by labels, milestones, assignees, authors... We'll show you how it looks when the list is filtered by a label."
-msgstr ""
-
-msgid "UserOnboardingTour|Learn GitLab"
-msgstr ""
-
-msgid "UserOnboardingTour|Let's take a closer look at a merge request. Click on the title of one."
-msgstr ""
-
-msgid "UserOnboardingTour|Let's take a closer look at all the commits. Click on %{emphasisStart}Commits%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Let's take a closer look at the repository of this project. Click on %{emphasisStart}Repository%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|No thanks"
-msgstr ""
-
-msgid "UserOnboardingTour|Ok, let's go"
-msgstr ""
-
-msgid "UserOnboardingTour|Ok, show me"
-msgstr ""
-
-msgid "UserOnboardingTour|Open one of the issues by clicking on its title."
-msgstr ""
-
-msgid "UserOnboardingTour|Restart this step"
-msgstr ""
-
-msgid "UserOnboardingTour|Skip this step"
-msgstr ""
-
-msgid "UserOnboardingTour|Sweet! Your project was created and is ready to be used.%{lineBreak}%{lineBreak}You can start adding files to the repository or clone it. One last thing we want to show you is how to invite your colleagues to your new project."
-msgstr ""
-
-msgid "UserOnboardingTour|Take a look. Here's a nifty menu for quickly creating issues, merge requests, snippets, projects and groups. Click on it and select \"New project\" from the \"GitLab\" section to get started."
-msgstr ""
-
-msgid "UserOnboardingTour|Thanks for taking the guided tour. Remember, if you want to go through it again, you can start %{emphasisStart}Learn GitLab%{emphasisEnd} in the help menu on the top right."
-msgstr ""
-
-msgid "UserOnboardingTour|Thanks for the feedback! %{thumbsUp}"
-msgstr ""
-
-msgid "UserOnboardingTour|That's it for issues. Let'st take a look at %{emphasisStart}Merge Requests%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|That's it for merge requests. Now for the final part of this guided tour - the %{emphasisStart}CI/CD%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|That's it for the Repository. Let's take a look at the %{emphasisStart}Issues%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|The structure of this page is very similar to issues. Status, description, discussion and the sidebar are all here.%{lineBreak}%{lineBreak}But take a look below the description and you'll notice that there's more information about the merge request, the CI/CD pipeline and the options for approving it.%{lineBreak}%{lineBreak}Alongside the discussion you can also see more information about commits in this merge request, the status of pipelines and review all changes that were made."
-msgstr ""
-
-msgid "UserOnboardingTour|There's a lot of information here but don't worry, we'll go through it.%{lineBreak}%{lineBreak}On the top you can see the status of the issue and when it was opened and by whom. Directly below it is the issue description and below that are other %{emphasisStart}related issues%{emphasisEnd} and %{emphasisStart}merge requests%{emphasisEnd} (if any). Then below that is the %{emphasisStart}discussion%{emphasisEnd}, that's where most of the communication happens.%{lineBreak}%{lineBreak}On the right, there's a sidebar where you can view/change the %{emphasisStart}assignee, milestone, due date, labels, weight%{emphasisEnd}, etc."
-msgstr ""
-
-msgid "UserOnboardingTour|These are all the CI/CD pipelines we have for our %{emphasisStart}%{projectName}%{emphasisEnd} project.%{lineBreak}%{lineBreak}Here you can see the status of each pipeline, for what commit it's running for, its stages and the status for them."
-msgstr ""
-
-msgid "UserOnboardingTour|These are all the issues that are available for community contributions. Let's take a closer look at one of them."
-msgstr ""
-
-msgid "UserOnboardingTour|This is an overview of all merge requests in this project. Similarly to the issues overview it can be filtered down by things like labels, milestones, authors, assignees, etc."
-msgstr ""
-
-msgid "UserOnboardingTour|This is the repository for the %{emphasisStart}%{projectName}%{emphasisEnd} project. All our code is stored here. Feel free to explore and take a closer look at folders and files.%{lineBreak}%{lineBreak}Above the file structure you can see the latest commit, who the author is and the status of the CI/CD pipeline.%{lineBreak}%{lineBreak}If you scroll down below the file structure, you'll find the Readme of this project. This is defined in the README.md file at the root of the repository."
-msgstr ""
-
-msgid "UserOnboardingTour|Welcome to the project overview of the %{emphasisStart}%{projectName}%{emphasisEnd} project. This is the project that we use to work on GitLab. At first, a project seems like a simple repository, but at GitLab, a project is so much more.%{lineBreak}%{lineBreak}You can create projects for hosting your codebase, use it as an issue tracker, collaborate on code, and continuously build, test, and deploy your app with built-in GitLab CI/CD."
+msgid "UserList|created %{timeago}"
msgstr ""
msgid "UserProfile|Activity"
@@ -24471,6 +25116,9 @@ msgstr ""
msgid "UsersSelect|Unassigned"
msgstr ""
+msgid "Using %{codeStart}needs%{codeEnd} allows jobs to run before their stage is reached, as soon as their individual dependencies are met, which speeds up your pipelines."
+msgstr ""
+
msgid "Using %{code_start}::%{code_end} denotes a %{link_start}scoped label set%{link_end}"
msgstr ""
@@ -24555,9 +25203,6 @@ msgstr ""
msgid "Versions"
msgstr ""
-msgid "Very helpful"
-msgstr ""
-
msgid "View Documentation"
msgstr ""
@@ -24706,6 +25351,9 @@ msgstr ""
msgid "VisibilityLevel|Unknown"
msgstr ""
+msgid "Visit settings page"
+msgstr ""
+
msgid "VisualReviewApp|%{stepStart}Step 1%{stepEnd}. Copy the following script:"
msgstr ""
@@ -24799,6 +25447,9 @@ msgstr ""
msgid "VulnerabilityManagement|Something went wrong while trying to delete the comment. Please try again later."
msgstr ""
+msgid "VulnerabilityManagement|Something went wrong while trying to refresh the vulnerability. Please try again later."
+msgstr ""
+
msgid "VulnerabilityManagement|Something went wrong while trying to retrieve the vulnerability history. Please try again later."
msgstr ""
@@ -24841,6 +25492,9 @@ msgstr ""
msgid "Vulnerability|Class"
msgstr ""
+msgid "Vulnerability|Crash Address"
+msgstr ""
+
msgid "Vulnerability|Description"
msgstr ""
@@ -24916,9 +25570,6 @@ msgstr ""
msgid "We couldn't reach the Prometheus server. Either the server no longer exists or the configuration details need updating."
msgstr ""
-msgid "We created a short guided tour that will help you learn the basics of GitLab and how it will help you be better at your job. It should only take a couple of minutes. You will be guided by two types of helpers, best recognized by their color."
-msgstr ""
-
msgid "We detected potential spam in the %{humanized_resource_name}. Please solve the reCAPTCHA to proceed."
msgstr ""
@@ -24952,6 +25603,9 @@ msgstr ""
msgid "We've found no vulnerabilities"
msgstr ""
+msgid "Web Application Firewall"
+msgstr ""
+
msgid "Web IDE"
msgstr ""
@@ -25012,9 +25666,6 @@ msgstr ""
msgid "Welcome to GitLab.com<br>@%{name}!"
msgstr ""
-msgid "Welcome to the Guided GitLab Tour"
-msgstr ""
-
msgid "Welcome to the guided GitLab tour"
msgstr ""
@@ -25062,9 +25713,6 @@ msgstr ""
msgid "While it's rare to have no vulnerabilities, it can happen. In any event, we ask that you please double check your settings to make sure you've set up your dashboard correctly."
msgstr ""
-msgid "White helpers give contextual information."
-msgstr ""
-
msgid "Who can be an approver?"
msgstr ""
@@ -25110,12 +25758,18 @@ msgstr ""
msgid "WikiEmptyIssueMessage|Suggest wiki improvement"
msgstr ""
+msgid "WikiEmptyIssueMessage|You must be a group member in order to add wiki pages. If you have suggestions for how to improve the wiki for this group, consider opening an issue in the %{issues_link}."
+msgstr ""
+
msgid "WikiEmptyIssueMessage|You must be a project member in order to add wiki pages. If you have suggestions for how to improve the wiki for this project, consider opening an issue in the %{issues_link}."
msgstr ""
msgid "WikiEmptyIssueMessage|issue tracker"
msgstr ""
+msgid "WikiEmpty|A wiki is where you can store all the details about your group. This can include why you've created it, its principles, how to use it, and so on."
+msgstr ""
+
msgid "WikiEmpty|A wiki is where you can store all the details about your project. This can include why you've created it, its principles, how to use it, and so on."
msgstr ""
@@ -25125,12 +25779,21 @@ msgstr ""
msgid "WikiEmpty|Suggest wiki improvement"
msgstr ""
+msgid "WikiEmpty|The wiki lets you write documentation for your group"
+msgstr ""
+
msgid "WikiEmpty|The wiki lets you write documentation for your project"
msgstr ""
+msgid "WikiEmpty|This group has no wiki pages"
+msgstr ""
+
msgid "WikiEmpty|This project has no wiki pages"
msgstr ""
+msgid "WikiEmpty|You must be a group member in order to add wiki pages."
+msgstr ""
+
msgid "WikiEmpty|You must be a project member in order to add wiki pages."
msgstr ""
@@ -25407,13 +26070,13 @@ msgstr ""
msgid "You can now export your security dashboard to a CSV report."
msgstr ""
-msgid "You can now submit a merge request to get this change into the original branch."
+msgid "You can now manage alert endpoint configuration in the Alerts section on the Operations settings page. Fields on this page have been deprecated."
msgstr ""
-msgid "You can now submit a merge request to get this change into the original project."
+msgid "You can now submit a merge request to get this change into the original branch."
msgstr ""
-msgid "You can only add files when you are on a branch"
+msgid "You can now submit a merge request to get this change into the original project."
msgstr ""
msgid "You can only edit files when you are on a branch"
@@ -25635,7 +26298,7 @@ msgstr ""
msgid "You need to upload a Google Takeout archive."
msgstr ""
-msgid "You reached %{usage_in_percent} of %{namespace_name}'s capacity (%{used_storage} of %{storage_limit})"
+msgid "You reached %{usage_in_percent} of %{namespace_name}'s storage capacity (%{used_storage} of %{storage_limit})"
msgstr ""
msgid "You tried to fork %{link_to_the_project} but it failed for the following reason:"
@@ -25938,13 +26601,13 @@ msgstr ""
msgid "Your subscription expired!"
msgstr ""
-msgid "Your subscription has been downgraded"
+msgid "Your subscription has been downgraded."
msgstr ""
-msgid "Your subscription will automatically renew in %{remaining_days}"
+msgid "Your subscription will automatically renew in %{remaining_days}."
msgstr ""
-msgid "Your subscription will expire in %{remaining_days}"
+msgid "Your subscription will expire in %{remaining_days}."
msgstr ""
msgid "Zoom meeting added"
@@ -26024,9 +26687,6 @@ msgstr ""
msgid "branch name"
msgstr ""
-msgid "build pipeline reference mismatch"
-msgstr ""
-
msgid "by"
msgstr ""
@@ -26045,6 +26705,9 @@ msgstr ""
msgid "cannot block others"
msgstr ""
+msgid "cannot contain HTML/XML tags, including any word between angle brackets (<,>)."
+msgstr ""
+
msgid "cannot include leading slash or directory traversal."
msgstr ""
@@ -26075,55 +26738,6 @@ msgstr ""
msgid "ciReport|%{remainingPackagesCount} more"
msgstr ""
-msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability"
-msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability for the source branch only"
-msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities for the source branch only"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerability"
-msgid_plural "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerabilities"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new vulnerability"
-msgid_plural "ciReport|%{reportType} %{status} detected %{newCount} new vulnerabilities"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities for the source branch only"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{fixedCount} fixed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} vulnerability for the source branch only"
-msgid_plural "ciReport|%{reportType} %{status} detected %{newCount} vulnerabilities for the source branch only"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "ciReport|%{reportType} %{status} detected no new vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected no vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected no vulnerabilities for the source branch only"
-msgstr ""
-
msgid "ciReport|%{reportType} is loading"
msgstr ""
@@ -26142,7 +26756,7 @@ msgstr ""
msgid "ciReport|All projects"
msgstr ""
-msgid "ciReport|All report types"
+msgid "ciReport|All scanner types"
msgstr ""
msgid "ciReport|All severities"
@@ -26166,6 +26780,9 @@ msgstr ""
msgid "ciReport|Container scanning detects known vulnerabilities in your docker images."
msgstr ""
+msgid "ciReport|Coverage Fuzzing"
+msgstr ""
+
msgid "ciReport|Create a merge request to implement this solution, or download and apply the patch manually."
msgstr ""
@@ -26324,6 +26941,9 @@ msgstr ""
msgid "customize"
msgstr ""
+msgid "data"
+msgstr ""
+
msgid "date must not be after 9999-12-31"
msgstr ""
@@ -26394,6 +27014,9 @@ msgstr ""
msgid "entries cannot contain HTML tags"
msgstr ""
+msgid "epic"
+msgstr ""
+
msgid "error"
msgstr ""
@@ -26415,9 +27038,6 @@ msgstr ""
msgid "expires on %{timebox_due_date}"
msgstr ""
-msgid "external_url"
-msgstr ""
-
msgid "failed"
msgstr ""
@@ -26453,9 +27073,6 @@ msgstr ""
msgid "from"
msgstr ""
-msgid "geo_node_name"
-msgstr ""
-
msgid "group"
msgstr ""
@@ -26477,9 +27094,6 @@ msgstr ""
msgid "https://your-bitbucket-server"
msgstr ""
-msgid "image"
-msgstr ""
-
msgid "image diff"
msgstr ""
@@ -26623,6 +27237,9 @@ msgstr ""
msgid "load it anyway"
msgstr ""
+msgid "loading"
+msgstr ""
+
msgid "locked by %{path_lock_user_name} %{created_at}"
msgstr ""
@@ -26751,9 +27368,6 @@ msgstr ""
msgid "mrWidget|Deployment statistics are not available currently"
msgstr ""
-msgid "mrWidget|Detect issues before deployment with a CI pipeline that continuously tests your code. We created a quick guide that will show you how to create one. Make your code more secure and more robust in just a minute."
-msgstr ""
-
msgid "mrWidget|Did not close"
msgstr ""
@@ -26916,6 +27530,9 @@ msgstr ""
msgid "mrWidget|To approve this merge request, please enter your password. This project requires all approvals to be authenticated."
msgstr ""
+msgid "mrWidget|Use %{linkStart}CI pipelines to test your code%{linkEnd}, simply add a GitLab CI configuration file to your project. It only takes a minute to make your code more secure and robust."
+msgstr ""
+
msgid "mrWidget|When this merge request is ready, remove the WIP: prefix from the title to allow it to be merged"
msgstr ""
@@ -26952,6 +27569,9 @@ msgstr ""
msgid "must be greater than start date"
msgstr ""
+msgid "must contain only valid frameworks"
+msgstr ""
+
msgid "my-awesome-group"
msgstr ""
@@ -26964,6 +27584,9 @@ msgstr ""
msgid "needs to be between 10 minutes and 1 month"
msgstr ""
+msgid "never"
+msgstr ""
+
msgid "never expires"
msgstr ""
@@ -26991,6 +27614,9 @@ msgstr ""
msgid "nounSeries|%{firstItem} and %{lastItem}"
msgstr ""
+msgid "nounSeries|%{item}"
+msgstr ""
+
msgid "nounSeries|%{item}, %{nextItem}"
msgstr ""
@@ -27127,6 +27753,9 @@ msgstr ""
msgid "revised"
msgstr ""
+msgid "satisfied"
+msgstr ""
+
msgid "score"
msgstr ""
@@ -27157,6 +27786,9 @@ msgstr ""
msgid "severity|Unknown"
msgstr ""
+msgid "should be an array of %{object_name} objects"
+msgstr ""
+
msgid "should be greater than or equal to %{access} inherited membership from group %{group_name}"
msgstr ""
@@ -27211,10 +27843,13 @@ msgstr ""
msgid "suggestPipeline|2/2: Commit your changes"
msgstr ""
-msgid "suggestPipeline|Commit the changes and your pipeline will automatically run for the first time."
+msgid "suggestPipeline|Choose %{boldStart}Code Quality%{boldEnd} to add a pipeline that tests the quality of your code."
msgstr ""
-msgid "suggestPipeline|We recommend the %{boldStart}Code Quality%{boldEnd} template, which will add a report widget to your Merge Requests. This way you’ll learn about code quality degradations much sooner. %{footerStart} Goodbye technical debt! %{footerEnd}"
+msgid "suggestPipeline|The template is ready! You can now commit it to create your first pipeline."
+msgstr ""
+
+msgid "suggestPipeline|We’re adding a GitLab CI configuration file to add a pipeline to the project. You could create it manually, but we recommend that you start with a GitLab template that works out of the box."
msgstr ""
msgid "syntax is correct"
@@ -27274,6 +27909,9 @@ msgstr ""
msgid "uses Kubernetes clusters to deploy your code!"
msgstr ""
+msgid "v%{version} published %{timeAgo}"
+msgstr ""
+
msgid "verify ownership"
msgstr ""
diff --git a/locale/bn_IN/gitlab.po b/locale/bn_IN/gitlab.po
index f8d446f1eb9..0b5842ae32b 100644
--- a/locale/bn_IN/gitlab.po
+++ b/locale/bn_IN/gitlab.po
@@ -2,7 +2,7 @@ msgid ""
msgstr ""
"Project-Id-Version: gitlab-ee\n"
"Report-Msgid-Bugs-To: \n"
-"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Last-Translator: \n"
"Language-Team: Bengali, India\n"
"Language: bn_IN\n"
"MIME-Version: 1.0\n"
@@ -10,9 +10,11 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Crowdin-Project: gitlab-ee\n"
+"X-Crowdin-Project-ID: 288872\n"
"X-Crowdin-Language: bn-IN\n"
"X-Crowdin-File: /master/locale/gitlab.pot\n"
-"PO-Revision-Date: 2020-06-08 15:13\n"
+"X-Crowdin-File-ID: 6\n"
+"PO-Revision-Date: 2020-07-02 01:23\n"
msgid " %{start} to %{end}"
msgstr ""
@@ -197,6 +199,11 @@ msgid_plural "%d metrics"
msgstr[0] ""
msgstr[1] ""
+msgid "%d milestone"
+msgid_plural "%d milestones"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "%d minute"
msgid_plural "%d minutes"
msgstr[0] ""
@@ -242,11 +249,6 @@ msgid_plural "%d unresolved threads"
msgstr[0] ""
msgstr[1] ""
-msgid "%d url scanned"
-msgid_plural "%d urls scanned"
-msgstr[0] ""
-msgstr[1] ""
-
msgid "%d vulnerability"
msgid_plural "%d vulnerabilities"
msgstr[0] ""
@@ -324,9 +326,15 @@ msgstr[1] ""
msgid "%{count} related %{pluralized_subject}: %{links}"
msgstr ""
+msgid "%{dashboard_path} could not be found."
+msgstr ""
+
msgid "%{days} days until tags are automatically removed"
msgstr ""
+msgid "%{deployLinkStart}Use a template to deploy to ECS%{deployLinkEnd}, or use a docker image to %{commandsLinkStart}run AWS commands in GitLab CI/CD%{commandsLinkEnd}."
+msgstr ""
+
msgid "%{description}- Sentry event: %{errorUrl}- First seen: %{firstSeen}- Last seen: %{lastSeen} %{countLabel}: %{count}%{userCountLabel}: %{userCount}"
msgstr ""
@@ -369,12 +377,18 @@ msgstr ""
msgid "%{host} sign-in from new location"
msgstr ""
-msgid "%{icon}You are about to add %{usersTag} people to the discussion. Proceed with caution."
+msgid "%{icon}You are about to add %{usersTag} people to the discussion. They will all receive a notification."
+msgstr ""
+
+msgid "%{integrations_link_start}Integrations%{link_end} enable you to make third-party applications part of your GitLab workflow. If the available integrations don't meet your needs, consider using a %{webhooks_link_start}webhook%{link_end}."
msgstr ""
msgid "%{issuableType} will be removed! Are you sure?"
msgstr ""
+msgid "%{issuesCount} issues in this group"
+msgstr ""
+
msgid "%{issuesSize} issues"
msgstr ""
@@ -384,7 +398,7 @@ msgstr ""
msgid "%{labelStart}Class:%{labelEnd} %{class}"
msgstr ""
-msgid "%{labelStart}Confidence:%{labelEnd} %{confidence}"
+msgid "%{labelStart}Evidence:%{labelEnd} %{evidence}"
msgstr ""
msgid "%{labelStart}File:%{labelEnd} %{file}"
@@ -402,6 +416,9 @@ msgstr ""
msgid "%{labelStart}Report Type:%{labelEnd} %{reportType}"
msgstr ""
+msgid "%{labelStart}Scanner:%{labelEnd} %{scanner}"
+msgstr ""
+
msgid "%{labelStart}Severity:%{labelEnd} %{severity}"
msgstr ""
@@ -426,9 +443,6 @@ msgstr ""
msgid "%{link_start}Read more%{link_end} about role permissions"
msgstr ""
-msgid "%{link} can be used for binding events when something is happening within the project."
-msgstr ""
-
msgid "%{listToShow}, and %{awardsListLength} more."
msgstr ""
@@ -462,6 +476,9 @@ msgstr ""
msgid "%{name} found %{resultsString}"
msgstr ""
+msgid "%{name} is already being used for another emoji"
+msgstr ""
+
msgid "%{name} is scheduled for %{action}"
msgstr ""
@@ -509,6 +526,9 @@ msgstr ""
msgid "%{primary} (%{secondary})"
msgstr ""
+msgid "%{ref} cannot be added: %{error}"
+msgstr ""
+
msgid "%{releases} release"
msgid_plural "%{releases} releases"
msgstr[0] ""
@@ -517,13 +537,37 @@ msgstr[1] ""
msgid "%{remaining_approvals} left"
msgstr ""
-msgid "%{retryButtonStart}Try again%{retryButtonEnd} or %{newFileButtonStart}attach a new file%{newFileButtonEnd}"
+msgid "%{reportType} %{status} detected %{critical} critical and %{high} high severity vulnerabilities out of %{total}."
+msgstr ""
+
+msgid "%{reportType} %{status} detected %{critical} critical and %{high} high severity vulnerabilities."
+msgstr ""
+
+msgid "%{reportType} %{status} detected %{critical} critical severity vulnerabilities out of %{total}."
+msgstr ""
+
+msgid "%{reportType} %{status} detected %{critical} critical severity vulnerability."
+msgid_plural "%{reportType} %{status} detected %{critical} critical severity vulnerabilities."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{reportType} %{status} detected %{high} high severity vulnerabilities out of %{total}."
msgstr ""
-msgid "%{screenreaderOnlyStart}Keyboard shorcuts%{screenreaderOnlyEnd} Disabled"
+msgid "%{reportType} %{status} detected %{high} high severity vulnerability."
+msgid_plural "%{reportType} %{status} detected %{high} high severity vulnerabilities."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{reportType} %{status} detected %{other} vulnerability."
+msgid_plural "%{reportType} %{status} detected %{other} vulnerabilities."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{reportType} %{status} detected no new vulnerabilities."
msgstr ""
-msgid "%{screenreaderOnlyStart}Keyboard shorcuts%{screenreaderOnlyEnd} Enabled"
+msgid "%{retryButtonStart}Try again%{retryButtonEnd} or %{newFileButtonStart}attach a new file%{newFileButtonEnd}"
msgstr ""
msgid "%{service_title} %{message}."
@@ -620,6 +664,12 @@ msgstr ""
msgid "%{token}..."
msgstr ""
+msgid "%{totalCpu} (%{freeSpacePercentage}%{percentSymbol} free)"
+msgstr ""
+
+msgid "%{totalMemory} (%{freeSpacePercentage}%{percentSymbol} free)"
+msgstr ""
+
msgid "%{totalWeight} total weight"
msgstr ""
@@ -650,6 +700,12 @@ msgstr ""
msgid "%{verb} %{time_spent_value} spent time."
msgstr ""
+msgid "%{webhooks_link_start}%{webhook_type}%{link_end} enable you to send notifications to web applications in response to events in a group or project."
+msgstr ""
+
+msgid "%{webhooks_link_start}%{webhook_type}%{link_end} enable you to send notifications to web applications in response to events in a group or project. We recommend using an %{integrations_link_start}integration%{link_end} in preference to a webhook."
+msgstr ""
+
msgid "'%{level}' is not a valid visibility level"
msgstr ""
@@ -676,9 +732,6 @@ msgstr ""
msgid "(No changes)"
msgstr ""
-msgid "(Show all)"
-msgstr ""
-
msgid "(check progress)"
msgstr ""
@@ -745,6 +798,9 @@ msgstr ""
msgid "0 for unlimited"
msgstr ""
+msgid "0 for unlimited, only effective with remote storage enabled."
+msgstr ""
+
msgid "1 %{type} addition"
msgid_plural "%{count} %{type} additions"
msgstr[0] ""
@@ -892,6 +948,12 @@ msgstr ""
msgid "<code>\"johnsmith@example.com\": \"johnsmith@example.com\"</code> will add \"By <a href=\"#\">johnsmith@example.com</a>\" to all issues and comments originally created by johnsmith@example.com. By default, the email address or username is masked to ensure the user's privacy. Use this option if you want to show the full email address."
msgstr ""
+msgid "<code>Masked</code> to prevent the values from being displayed in job logs (must match certain regexp requirements)."
+msgstr ""
+
+msgid "<code>Protected</code> to expose them to protected branches or tags only."
+msgstr ""
+
msgid "<namespace / project>"
msgstr ""
@@ -919,9 +981,6 @@ msgstr ""
msgid "A .NET Core console application template, customizable for any .NET Core project"
msgstr ""
-msgid "A DAG must have two dependent jobs to be visualized on this tab."
-msgstr ""
-
msgid "A GitBook site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
msgstr ""
@@ -952,6 +1011,12 @@ msgstr ""
msgid "A deleted user"
msgstr ""
+msgid "A file has been changed."
+msgstr ""
+
+msgid "A file was not found."
+msgstr ""
+
msgid "A file with '%{file_name}' already exists in %{branch} branch"
msgstr ""
@@ -1018,7 +1083,7 @@ msgstr ""
msgid "A subscription will trigger a new pipeline on the default branch of this project when a pipeline successfully completes for a new tag on the %{default_branch_docs} of the subscribed project."
msgstr ""
-msgid "A terraform report was generated in your pipelines."
+msgid "A suggestion is not applicable."
msgstr ""
msgid "A user with write access to the source branch selected this option"
@@ -1189,6 +1254,9 @@ msgstr ""
msgid "Action to take when receiving an alert."
msgstr ""
+msgid "Actions"
+msgstr ""
+
msgid "Activate"
msgstr ""
@@ -1245,6 +1313,9 @@ msgstr ""
msgid "Add LICENSE"
msgstr ""
+msgid "Add New Node"
+msgstr ""
+
msgid "Add README"
msgstr ""
@@ -1320,6 +1391,9 @@ msgstr ""
msgid "Add approval rule"
msgstr ""
+msgid "Add approvers"
+msgstr ""
+
msgid "Add bold text"
msgstr ""
@@ -1377,6 +1451,9 @@ msgstr ""
msgid "Add strikethrough text"
msgstr ""
+msgid "Add suggestion to batch"
+msgstr ""
+
msgid "Add system hook"
msgstr ""
@@ -1476,6 +1553,9 @@ msgstr ""
msgid "Admin Note"
msgstr ""
+msgid "Admin Notifications"
+msgstr ""
+
msgid "Admin Overview"
msgstr ""
@@ -1587,6 +1667,9 @@ msgstr ""
msgid "AdminSettings|Integrations configured here will automatically apply to all projects on this instance."
msgstr ""
+msgid "AdminSettings|Moved to integrations"
+msgstr ""
+
msgid "AdminSettings|No required pipeline"
msgstr ""
@@ -1817,12 +1900,18 @@ msgid_plural "Alerts"
msgstr[0] ""
msgstr[1] ""
+msgid "AlertManagement| assign yourself"
+msgstr ""
+
msgid "AlertManagement|Acknowledged"
msgstr ""
msgid "AlertManagement|Alert"
msgstr ""
+msgid "AlertManagement|Alert assignee(s): %{assignees}"
+msgstr ""
+
msgid "AlertManagement|Alert detail"
msgstr ""
@@ -1838,9 +1927,15 @@ msgstr ""
msgid "AlertManagement|All alerts"
msgstr ""
+msgid "AlertManagement|Assign To"
+msgstr ""
+
msgid "AlertManagement|Assign status"
msgstr ""
+msgid "AlertManagement|Assignee"
+msgstr ""
+
msgid "AlertManagement|Assignees"
msgstr ""
@@ -1859,9 +1954,6 @@ msgstr ""
msgid "AlertManagement|Edit"
msgstr ""
-msgid "AlertManagement|End time"
-msgstr ""
-
msgid "AlertManagement|Events"
msgstr ""
@@ -1880,10 +1972,13 @@ msgstr ""
msgid "AlertManagement|More information"
msgstr ""
+msgid "AlertManagement|No Matching Results"
+msgstr ""
+
msgid "AlertManagement|No alert data to display."
msgstr ""
-msgid "AlertManagement|No alerts available to display. If you think you're seeing this message in error, refresh the page."
+msgid "AlertManagement|No alerts available to display. See %{linkStart}enabling alert management%{linkEnd} for more information on adding alerts to the list."
msgstr ""
msgid "AlertManagement|No alerts to display."
@@ -1892,6 +1987,9 @@ msgstr ""
msgid "AlertManagement|None"
msgstr ""
+msgid "AlertManagement|None -"
+msgstr ""
+
msgid "AlertManagement|Open"
msgstr ""
@@ -1928,9 +2026,18 @@ msgstr ""
msgid "AlertManagement|There was an error displaying the alerts. Confirm your endpoint's configuration details to ensure alerts appear."
msgstr ""
+msgid "AlertManagement|There was an error while updating the assignee(s) list. Please try again."
+msgstr ""
+
+msgid "AlertManagement|There was an error while updating the assignee(s) of the alert. Please try again."
+msgstr ""
+
msgid "AlertManagement|There was an error while updating the status of the alert. Please try again."
msgstr ""
+msgid "AlertManagement|This assignee cannot be assigned to this alert."
+msgstr ""
+
msgid "AlertManagement|Tool"
msgstr ""
@@ -2078,6 +2185,9 @@ msgstr ""
msgid "Allow users to request access (if visibility is public or internal)"
msgstr ""
+msgid "Allowed Geo IP"
+msgstr ""
+
msgid "Allowed email domain restriction only permitted for top-level groups"
msgstr ""
@@ -2111,6 +2221,9 @@ msgstr ""
msgid "Amazon Web Services"
msgstr ""
+msgid "Amazon Web Services Logo"
+msgstr ""
+
msgid "Amazon authentication is not %{link_start}correctly configured%{link_end}. Ask your GitLab administrator if you want to use this service."
msgstr ""
@@ -2168,10 +2281,16 @@ msgstr ""
msgid "An error occurred when updating the issue weight"
msgstr ""
+msgid "An error occurred while acknowledging the notification. Refresh the page and try again."
+msgstr ""
+
+msgid "An error occurred while adding approvers"
+msgstr ""
+
msgid "An error occurred while adding formatted title for epic"
msgstr ""
-msgid "An error occurred while checking group path"
+msgid "An error occurred while checking group path. Please refresh and try again."
msgstr ""
msgid "An error occurred while committing your changes."
@@ -2315,7 +2434,7 @@ msgstr ""
msgid "An error occurred while loading milestones"
msgstr ""
-msgid "An error occurred while loading terraform report"
+msgid "An error occurred while loading project creation UI"
msgstr ""
msgid "An error occurred while loading the data. Please try again."
@@ -2369,6 +2488,9 @@ msgstr ""
msgid "An error occurred while rendering preview broadcast message"
msgstr ""
+msgid "An error occurred while rendering the editor"
+msgstr ""
+
msgid "An error occurred while reordering issues."
msgstr ""
@@ -2465,7 +2587,7 @@ msgstr ""
msgid "Analyze your dependencies for known vulnerabilities."
msgstr ""
-msgid "Analyze your source code and git history for secrets"
+msgid "Analyze your source code and git history for secrets."
msgstr ""
msgid "Analyze your source code for known vulnerabilities."
@@ -2579,12 +2701,18 @@ msgstr ""
msgid "Apply suggestion"
msgstr ""
+msgid "Apply suggestions"
+msgstr ""
+
msgid "Apply template"
msgstr ""
msgid "Apply this approval rule to any branch or a specific protected branch."
msgstr ""
+msgid "Applying"
+msgstr ""
+
msgid "Applying a template will replace the existing issue description. Any changes you have made will be lost."
msgstr ""
@@ -2597,12 +2725,18 @@ msgstr ""
msgid "Applying multiple commands"
msgstr ""
-msgid "Applying suggestion"
+msgid "Applying suggestion..."
+msgstr ""
+
+msgid "Applying suggestions..."
msgstr ""
msgid "Approval rules"
msgstr ""
+msgid "Approval rules reset to project defaults"
+msgstr ""
+
msgid "ApprovalRuleRemove|%d member"
msgid_plural "ApprovalRuleRemove|%d members"
msgstr[0] ""
@@ -2656,6 +2790,9 @@ msgstr ""
msgid "Approved"
msgstr ""
+msgid "Approved MRs"
+msgstr ""
+
msgid "Approved by: "
msgstr ""
@@ -2869,6 +3006,9 @@ msgstr ""
msgid "Assign"
msgstr ""
+msgid "Assign Iteration"
+msgstr ""
+
msgid "Assign custom color like #FF0000"
msgstr ""
@@ -2887,6 +3027,9 @@ msgstr ""
msgid "Assign to"
msgstr ""
+msgid "Assign to commenting user"
+msgstr ""
+
msgid "Assign yourself to these issues"
msgstr ""
@@ -2913,6 +3056,9 @@ msgid_plural "%d Assignees"
msgstr[0] ""
msgstr[1] ""
+msgid "Assignee has no permissions"
+msgstr ""
+
msgid "Assignee lists not available with your current license"
msgstr ""
@@ -2922,6 +3068,9 @@ msgstr ""
msgid "Assignee(s)"
msgstr ""
+msgid "Assignees"
+msgstr ""
+
msgid "Assigns %{assignee_users_sentence}."
msgstr ""
@@ -2957,6 +3106,9 @@ msgstr ""
msgid "Audit Events is a way to keep track of important events that happened in GitLab."
msgstr ""
+msgid "Audit Log"
+msgstr ""
+
msgid "AuditEvents|(removed)"
msgstr ""
@@ -2993,6 +3145,9 @@ msgstr ""
msgid "AuditLogs|IP Address"
msgstr ""
+msgid "AuditLogs|Member Events"
+msgstr ""
+
msgid "AuditLogs|No matching %{type} found."
msgstr ""
@@ -3275,9 +3430,6 @@ msgstr ""
msgid "Badges|e.g. %{exampleUrl}"
msgstr ""
-msgid "Badge|New"
-msgstr ""
-
msgid "Balsamiq file could not be loaded."
msgstr ""
@@ -3326,6 +3478,12 @@ msgstr ""
msgid "Below you will find all the groups that are public."
msgstr ""
+msgid "Beta"
+msgstr ""
+
+msgid "Bi-weekly code coverage"
+msgstr ""
+
msgid "Billing"
msgstr ""
@@ -3386,9 +3544,15 @@ msgstr ""
msgid "Bitbucket Server Import"
msgstr ""
+msgid "Bitbucket Server import"
+msgstr ""
+
msgid "Bitbucket import"
msgstr ""
+msgid "Blame"
+msgstr ""
+
msgid "Blocked"
msgstr ""
@@ -3401,9 +3565,6 @@ msgstr ""
msgid "Blog"
msgstr ""
-msgid "Blue helpers indicate an action to be taken."
-msgstr ""
-
msgid "Board name"
msgstr ""
@@ -3602,6 +3763,9 @@ msgstr ""
msgid "Broadcast Message was successfully updated."
msgstr ""
+msgid "Broadcast Messages"
+msgstr ""
+
msgid "Browse Directory"
msgstr ""
@@ -3656,6 +3820,9 @@ msgstr ""
msgid "By %{user_name}"
msgstr ""
+msgid "By URL"
+msgstr ""
+
msgid "By default GitLab sends emails in HTML and plain text formats so mail clients can choose what format to use. Disable this option if you only want to send emails in plain text format."
msgstr ""
@@ -3761,6 +3928,9 @@ msgstr ""
msgid "Can override approvers and approvals required per merge request"
msgstr ""
+msgid "Can't apply as this line has changed or the suggestion already matches its content."
+msgstr ""
+
msgid "Can't create snippet: %{err}"
msgstr ""
@@ -3947,9 +4117,6 @@ msgstr ""
msgid "Changes are still tracked. Useful for cluster/index migrations."
msgstr ""
-msgid "Changes are unknown"
-msgstr ""
-
msgid "Changes suppressed. Click to show."
msgstr ""
@@ -4256,15 +4423,9 @@ msgstr ""
msgid "Choose what content you want to see on a group’s overview page"
msgstr ""
-msgid "Choose which groups you wish to synchronize to this secondary node"
-msgstr ""
-
msgid "Choose which repositories you want to connect and run CI/CD pipelines."
msgstr ""
-msgid "Choose which shards you wish to synchronize to this secondary node"
-msgstr ""
-
msgid "Choose your framework"
msgstr ""
@@ -4562,9 +4723,15 @@ msgstr ""
msgid "Cluster does not exist"
msgstr ""
+msgid "Cluster is required for Stages::ClusterEndpointInserter"
+msgstr ""
+
msgid "Cluster level"
msgstr ""
+msgid "Cluster type must be specificed for Stages::ClusterEndpointInserter"
+msgstr ""
+
msgid "ClusterIntegration| %{custom_domain_start}More information%{custom_domain_end}."
msgstr ""
@@ -4676,7 +4843,7 @@ msgstr ""
msgid "ClusterIntegration|Cert-Manager"
msgstr ""
-msgid "ClusterIntegration|Cert-Manager is a native Kubernetes certificate management controller that helps with issuing certificates. Installing Cert-Manager on your cluster will issue a certificate by %{letsEncrypt} and ensure that certificates are valid and up-to-date."
+msgid "ClusterIntegration|Cert-Manager is a native Kubernetes certificate management controller that helps with issuing certificates. Installing Cert-Manager on your cluster will issue a certificate by %{linkStart}Let's Encrypt%{linkEnd} and ensure that certificates are valid and up-to-date."
msgstr ""
msgid "ClusterIntegration|Certificate Authority bundle (PEM format)"
@@ -4703,9 +4870,6 @@ msgstr ""
msgid "ClusterIntegration|Clear the local cache of namespace and service accounts. This is necessary if your integration has become out of sync. The cache is repopulated during the next CI job that requires namespace and service accounts."
msgstr ""
-msgid "ClusterIntegration|Cloud Run"
-msgstr ""
-
msgid "ClusterIntegration|Cluster being created"
msgstr ""
@@ -4790,7 +4954,7 @@ msgstr ""
msgid "ClusterIntegration|Crossplane"
msgstr ""
-msgid "ClusterIntegration|Crossplane enables declarative provisioning of managed services from your cloud of choice using %{codeStart}kubectl%{codeEnd} or %{gitlabIntegrationLink}. Crossplane runs inside your Kubernetes cluster and supports secure connectivity and secrets management between app containers and the cloud services they depend on."
+msgid "ClusterIntegration|Crossplane enables declarative provisioning of managed services from your cloud of choice using %{codeStart}kubectl%{codeEnd} or %{linkStart}GitLab Integration%{linkEnd}. Crossplane runs inside your Kubernetes cluster and supports secure connectivity and secrets management between app containers and the cloud services they depend on."
msgstr ""
msgid "ClusterIntegration|Deletes all GitLab resources attached to this cluster during removal"
@@ -4874,9 +5038,6 @@ msgstr ""
msgid "ClusterIntegration|GitLab-managed cluster"
msgstr ""
-msgid "ClusterIntegration|Gitlab Integration"
-msgstr ""
-
msgid "ClusterIntegration|Global default"
msgstr ""
@@ -4922,7 +5083,7 @@ msgstr ""
msgid "ClusterIntegration|Ingress gives you a way to route requests to services based on the request host or path, centralizing a number of services into a single entrypoint."
msgstr ""
-msgid "ClusterIntegration|Installing Ingress may incur additional costs. Learn more about %{pricingLink}."
+msgid "ClusterIntegration|Installing Ingress may incur additional costs. Learn more about %{linkStart}pricing%{linkEnd}."
msgstr ""
msgid "ClusterIntegration|Instance cluster"
@@ -4937,7 +5098,7 @@ msgstr ""
msgid "ClusterIntegration|Issuer Email"
msgstr ""
-msgid "ClusterIntegration|Issuers represent a certificate authority. You must provide an email address for your Issuer. "
+msgid "ClusterIntegration|Issuers represent a certificate authority. You must provide an email address for your Issuer."
msgstr ""
msgid "ClusterIntegration|Jupyter Hostname"
@@ -5009,9 +5170,6 @@ msgstr ""
msgid "ClusterIntegration|Learn more about instance Kubernetes clusters"
msgstr ""
-msgid "ClusterIntegration|Let's Encrypt"
-msgstr ""
-
msgid "ClusterIntegration|Loading IAM Roles"
msgstr ""
@@ -5123,7 +5281,7 @@ msgstr ""
msgid "ClusterIntegration|Prometheus"
msgstr ""
-msgid "ClusterIntegration|Prometheus is an open-source monitoring system with %{gitlabIntegrationLink} to monitor deployed applications."
+msgid "ClusterIntegration|Prometheus is an open-source monitoring system with %{linkStart}GitLab Integration%{linkEnd} to monitor deployed applications."
msgstr ""
msgid "ClusterIntegration|Provider details"
@@ -5279,10 +5437,10 @@ msgstr ""
msgid "ClusterIntegration|Select zone to choose machine type"
msgstr ""
-msgid "ClusterIntegration|Send Cilium Logs"
+msgid "ClusterIntegration|Send Container Network Policies Logs"
msgstr ""
-msgid "ClusterIntegration|Send ModSecurity Logs"
+msgid "ClusterIntegration|Send Web Application Firewall Logs"
msgstr ""
msgid "ClusterIntegration|Service Token"
@@ -5450,15 +5608,12 @@ msgstr ""
msgid "ClusterIntegration|documentation"
msgstr ""
-msgid "ClusterIntegration|installed via %{installed_via}"
+msgid "ClusterIntegration|installed via %{linkStart}Cloud Run%{linkEnd}"
msgstr ""
msgid "ClusterIntegration|meets the requirements"
msgstr ""
-msgid "ClusterIntegration|pricing"
-msgstr ""
-
msgid "ClusterIntegration|sign up"
msgstr ""
@@ -5495,6 +5650,15 @@ msgstr ""
msgid "Code"
msgstr ""
+msgid "Code Coverage: %{coveragePercentage}%{percentSymbol}"
+msgstr ""
+
+msgid "Code Coverage| Empty code coverage data"
+msgstr ""
+
+msgid "Code Coverage|Couldn't fetch the code coverage data"
+msgstr ""
+
msgid "Code Owners"
msgstr ""
@@ -5549,7 +5713,7 @@ msgstr ""
msgid "Collapse approvers"
msgstr ""
-msgid "Collapse child epics"
+msgid "Collapse milestones"
msgstr ""
msgid "Collapse replies"
@@ -5567,6 +5731,9 @@ msgstr ""
msgid "Coming soon"
msgstr ""
+msgid "Comma-separated, e.g. '1.1.1.1, 2.2.2.0/24'"
+msgstr ""
+
msgid "Command"
msgstr ""
@@ -5737,6 +5904,12 @@ msgstr ""
msgid "Compliance framework (optional)"
msgstr ""
+msgid "Compliance frameworks"
+msgstr ""
+
+msgid "ComplianceDashboard|created by:"
+msgstr ""
+
msgid "ComplianceFramework|GDPR"
msgstr ""
@@ -5875,6 +6048,9 @@ msgstr ""
msgid "Connection timed out"
msgstr ""
+msgid "Connection timeout"
+msgstr ""
+
msgid "Contact sales to upgrade"
msgstr ""
@@ -5930,9 +6106,6 @@ msgstr ""
msgid "ContainerRegistry|CLI Commands"
msgstr ""
-msgid "ContainerRegistry|Compressed Size"
-msgstr ""
-
msgid "ContainerRegistry|Container Registry"
msgstr ""
@@ -5948,6 +6121,9 @@ msgstr ""
msgid "ContainerRegistry|Copy push command"
msgstr ""
+msgid "ContainerRegistry|Delete selected"
+msgstr ""
+
msgid "ContainerRegistry|Docker connection error"
msgstr ""
@@ -5981,16 +6157,16 @@ msgstr ""
msgid "ContainerRegistry|If you are not already logged in, you need to authenticate to the Container Registry by using your GitLab username and password. If you have %{twofaDocLinkStart}Two-Factor Authentication%{twofaDocLinkEnd} enabled, use a %{personalAccessTokensDocLinkStart}Personal Access Token%{personalAccessTokensDocLinkEnd} instead of a password."
msgstr ""
-msgid "ContainerRegistry|Image ID"
+msgid "ContainerRegistry|Image ID: %{imageId}"
msgstr ""
msgid "ContainerRegistry|Image Repositories"
msgstr ""
-msgid "ContainerRegistry|Keep and protect the images that matter most."
+msgid "ContainerRegistry|Image tags"
msgstr ""
-msgid "ContainerRegistry|Last Updated"
+msgid "ContainerRegistry|Keep and protect the images that matter most."
msgstr ""
msgid "ContainerRegistry|Login"
@@ -6005,6 +6181,9 @@ msgstr ""
msgid "ContainerRegistry|Please contact your administrator."
msgstr ""
+msgid "ContainerRegistry|Published %{timeInfo}"
+msgstr ""
+
msgid "ContainerRegistry|Push an image"
msgstr ""
@@ -6020,9 +6199,6 @@ msgstr ""
msgid "ContainerRegistry|Remove repository"
msgstr ""
-msgid "ContainerRegistry|Remove selected tags"
-msgstr ""
-
msgid "ContainerRegistry|Remove tag"
msgid_plural "ContainerRegistry|Remove tags"
msgstr[0] ""
@@ -6052,9 +6228,6 @@ msgstr ""
msgid "ContainerRegistry|Sorry, your filter produced no results."
msgstr ""
-msgid "ContainerRegistry|Tag"
-msgstr ""
-
msgid "ContainerRegistry|Tag expiration policy"
msgstr ""
@@ -6199,21 +6372,6 @@ msgstr ""
msgid "Control the display of third party offers."
msgstr ""
-msgid "Control the maximum concurrency of LFS/attachment backfill for this secondary node"
-msgstr ""
-
-msgid "Control the maximum concurrency of container repository operations for this Geo node"
-msgstr ""
-
-msgid "Control the maximum concurrency of repository backfill for this secondary node"
-msgstr ""
-
-msgid "Control the maximum concurrency of verification operations for this Geo node"
-msgstr ""
-
-msgid "Control the minimum interval in days that a repository should be reverified for this primary node"
-msgstr ""
-
msgid "Cookie domain"
msgstr ""
@@ -6355,6 +6513,9 @@ msgstr ""
msgid "Could not find design."
msgstr ""
+msgid "Could not find iteration"
+msgstr ""
+
msgid "Could not remove the trigger."
msgstr ""
@@ -6391,6 +6552,12 @@ msgstr ""
msgid "Coverage"
msgstr ""
+msgid "Coverage Fuzzing"
+msgstr ""
+
+msgid "Crash State"
+msgstr ""
+
msgid "Create"
msgstr ""
@@ -6439,6 +6606,9 @@ msgstr ""
msgid "Create a personal access token on your account to pull or push via %{protocol}."
msgstr ""
+msgid "Create a project pre-populated with the necessary files to get you started quickly."
+msgstr ""
+
msgid "Create an account using:"
msgstr ""
@@ -6607,6 +6777,9 @@ msgstr ""
msgid "Creates branch '%{branch_name}' and a merge request to resolve this issue."
msgstr ""
+msgid "Creating"
+msgstr ""
+
msgid "Creating epic"
msgstr ""
@@ -6658,7 +6831,10 @@ msgstr ""
msgid "Current vulnerabilities count"
msgstr ""
-msgid "CurrentUser|Buy CI minutes"
+msgid "CurrentUser|Buy Pipeline minutes"
+msgstr ""
+
+msgid "CurrentUser|One of your groups is running out"
msgstr ""
msgid "CurrentUser|Profile"
@@ -6673,6 +6849,9 @@ msgstr ""
msgid "CurrentUser|Upgrade"
msgstr ""
+msgid "Custom Attributes"
+msgstr ""
+
msgid "Custom CI configuration path"
msgstr ""
@@ -6936,12 +7115,18 @@ msgstr ""
msgid "DAG"
msgstr ""
+msgid "DAG visualization requires at least 3 dependent jobs."
+msgstr ""
+
msgid "DNS"
msgstr ""
msgid "Dashboard"
msgstr ""
+msgid "Dashboard uid not found"
+msgstr ""
+
msgid "DashboardProjects|All"
msgstr ""
@@ -6966,6 +7151,9 @@ msgstr ""
msgid "Data is still calculating..."
msgstr ""
+msgid "Datasource name not found"
+msgstr ""
+
msgid "Date"
msgstr ""
@@ -7146,6 +7334,9 @@ msgstr ""
msgid "Delete this attachment"
msgstr ""
+msgid "Delete user list"
+msgstr ""
+
msgid "Delete variable"
msgstr ""
@@ -7462,6 +7653,9 @@ msgstr ""
msgid "Deploying to"
msgstr ""
+msgid "Deploying to AWS is easy with GitLab"
+msgstr ""
+
msgid "Deployment Frequency"
msgstr ""
@@ -7824,6 +8018,9 @@ msgstr ""
msgid "Dismissed on pipeline %{pipelineLink} at %{projectLink}"
msgstr ""
+msgid "Display alerts from all your monitoring tools directly within GitLab."
+msgstr ""
+
msgid "Display name"
msgstr ""
@@ -7881,9 +8078,6 @@ msgstr ""
msgid "Don't show again"
msgstr ""
-msgid "Don't worry, you can access this tour by clicking on the help icon in the top right corner and choose <strong>Learn GitLab</strong>."
-msgstr ""
-
msgid "Done"
msgstr ""
@@ -8010,6 +8204,9 @@ msgstr ""
msgid "Edit Release"
msgstr ""
+msgid "Edit Slack integration"
+msgstr ""
+
msgid "Edit Snippet"
msgstr ""
@@ -8049,6 +8246,9 @@ msgstr ""
msgid "Edit issues"
msgstr ""
+msgid "Edit iteration"
+msgstr ""
+
msgid "Edit public deploy key"
msgstr ""
@@ -8097,6 +8297,9 @@ msgstr ""
msgid "Email"
msgstr ""
+msgid "Email Notification"
+msgstr ""
+
msgid "Email address"
msgstr ""
@@ -8109,6 +8312,9 @@ msgstr ""
msgid "Email not verified. Please verify your email in Salesforce."
msgstr ""
+msgid "Email notification for unknown sign-ins"
+msgstr ""
+
msgid "Email patch"
msgstr ""
@@ -8340,6 +8546,9 @@ msgstr ""
msgid "Enforce DNS rebinding attack protection"
msgstr ""
+msgid "Enforce personal access token expiration"
+msgstr ""
+
msgid "Ensure connectivity is available from the GitLab server to the Prometheus server"
msgstr ""
@@ -8409,6 +8618,9 @@ msgstr ""
msgid "Enter the name of your application, and we'll return a unique %{type}."
msgstr ""
+msgid "Enter weights for storages for new repositories."
+msgstr ""
+
msgid "Enter your password to approve"
msgstr ""
@@ -8418,15 +8630,27 @@ msgstr ""
msgid "Environment does not have deployments"
msgstr ""
+msgid "Environment is required for Stages::MetricEndpointInserter"
+msgstr ""
+
+msgid "Environment is required for Stages::VariableEndpointInserter"
+msgstr ""
+
msgid "Environment scope"
msgstr ""
+msgid "Environment variables are applied to all project environments in this instance via the Runner. You can use environment variables for passwords, secret keys, etc. Make variables available to the running application by prepending the variable key with <code>K8S_SECRET_</code>. You can set variables to be:"
+msgstr ""
+
msgid "Environment variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. Additionally, they can be masked so they are hidden in job logs, though they must match certain regexp requirements to do so. You can use environment variables for passwords, secret keys, or whatever you want."
msgstr ""
msgid "Environment variables are configured by your administrator to be %{link_start}protected%{link_end} by default"
msgstr ""
+msgid "Environment variables on this GitLab instance are configured to be %{link_start}protected%{link_end} by default"
+msgstr ""
+
msgid "Environment:"
msgstr ""
@@ -8958,6 +9182,9 @@ msgstr ""
msgid "EventFilterBy|Filter by comments"
msgstr ""
+msgid "EventFilterBy|Filter by designs"
+msgstr ""
+
msgid "EventFilterBy|Filter by epic events"
msgstr ""
@@ -9090,15 +9317,15 @@ msgstr ""
msgid "Expand approvers"
msgstr ""
-msgid "Expand child epics"
-msgstr ""
-
msgid "Expand down"
msgstr ""
msgid "Expand dropdown"
msgstr ""
+msgid "Expand milestones"
+msgstr ""
+
msgid "Expand sidebar"
msgstr ""
@@ -9114,6 +9341,9 @@ msgstr ""
msgid "Expiration date"
msgstr ""
+msgid "Expiration not enforced"
+msgstr ""
+
msgid "Expiration policy for the Container Registry is a perfect solution for keeping the Registry space down while still enjoying the full power of GitLab CI/CD."
msgstr ""
@@ -9234,6 +9464,9 @@ msgstr ""
msgid "Failed Jobs"
msgstr ""
+msgid "Failed on"
+msgstr ""
+
msgid "Failed to add a Zoom meeting"
msgstr ""
@@ -9294,13 +9527,19 @@ msgstr ""
msgid "Failed to install."
msgstr ""
+msgid "Failed to load assignees. Please try again."
+msgstr ""
+
+msgid "Failed to load authors. Please try again."
+msgstr ""
+
msgid "Failed to load emoji list."
msgstr ""
msgid "Failed to load error details from Sentry."
msgstr ""
-msgid "Failed to load errors from Sentry. Error message: %{errorMessage}"
+msgid "Failed to load errors from Sentry."
msgstr ""
msgid "Failed to load group activity metrics. Please try again."
@@ -9381,6 +9620,9 @@ msgstr ""
msgid "Failed to set due date because the date format is invalid."
msgstr ""
+msgid "Failed to set iteration on this issue. Please try again."
+msgstr ""
+
msgid "Failed to signing using smartcard authentication"
msgstr ""
@@ -9603,13 +9845,10 @@ msgstr ""
msgid "FeatureFlags|Target environments"
msgstr ""
-msgid "FeatureFlags|There are no active feature flags"
-msgstr ""
-
-msgid "FeatureFlags|There are no inactive feature flags"
+msgid "FeatureFlags|There was an error fetching the feature flags."
msgstr ""
-msgid "FeatureFlags|There was an error fetching the feature flags."
+msgid "FeatureFlags|There was an error retrieving user lists"
msgstr ""
msgid "FeatureFlags|Try again in a few moments or contact your support team."
@@ -9621,9 +9860,18 @@ msgstr ""
msgid "FeatureFlag|Delete strategy"
msgstr ""
+msgid "FeatureFlag|List"
+msgstr ""
+
msgid "FeatureFlag|Percentage"
msgstr ""
+msgid "FeatureFlag|Select a user list"
+msgstr ""
+
+msgid "FeatureFlag|There are no configured user lists"
+msgstr ""
+
msgid "FeatureFlag|Type"
msgstr ""
@@ -9780,6 +10028,12 @@ msgstr ""
msgid "Filter..."
msgstr ""
+msgid "Find File"
+msgstr ""
+
+msgid "Find bugs in your code with coverage-guided fuzzing"
+msgstr ""
+
msgid "Find by path"
msgstr ""
@@ -10044,6 +10298,9 @@ msgstr ""
msgid "Geo allows you to replicate your GitLab instance to other geographical locations."
msgstr ""
+msgid "Geo nodes are paused using a command run on the node"
+msgstr ""
+
msgid "GeoNodeStatusEvent|%{timeAgoStr} (%{pendingEvents} events)"
msgstr ""
@@ -10128,6 +10385,9 @@ msgstr ""
msgid "GeoNodes|Not checksummed"
msgstr ""
+msgid "GeoNodes|Package files"
+msgstr ""
+
msgid "GeoNodes|Pausing replication stops the sync process. Are you sure?"
msgstr ""
@@ -10146,6 +10406,9 @@ msgstr ""
msgid "GeoNodes|Replication slots"
msgstr ""
+msgid "GeoNodes|Replication status"
+msgstr ""
+
msgid "GeoNodes|Repositories"
msgstr ""
@@ -10356,6 +10619,9 @@ msgstr ""
msgid "Geo|Synchronization failed - %{error}"
msgstr ""
+msgid "Geo|Synchronization of %{itemTitle} is disabled."
+msgstr ""
+
msgid "Geo|The database is currently %{db_lag} behind the primary node."
msgstr ""
@@ -10668,6 +10934,9 @@ msgstr ""
msgid "Go to %{link_to_google_takeout}."
msgstr ""
+msgid "Go to <strong>Issues</strong> > <strong>Boards</strong> to access your personalized learning issue board."
+msgstr ""
+
msgid "Go to Pipelines"
msgstr ""
@@ -10767,9 +11036,6 @@ msgstr ""
msgid "Go to your snippets"
msgstr ""
-msgid "Golden Tanuki"
-msgstr ""
-
msgid "Google Cloud Platform"
msgstr ""
@@ -10791,6 +11057,9 @@ msgstr ""
msgid "Grafana URL"
msgstr ""
+msgid "Grafana response contains invalid json"
+msgstr ""
+
msgid "GrafanaIntegration|API Token"
msgstr ""
@@ -10839,9 +11108,6 @@ msgstr ""
msgid "Group %{group_name} was successfully created."
msgstr ""
-msgid "Group '%{group_name}' is being imported."
-msgstr ""
-
msgid "Group Audit Events"
msgstr ""
@@ -10878,9 +11144,6 @@ msgstr ""
msgid "Group by:"
msgstr ""
-msgid "Group could not be imported: %{errors}"
-msgstr ""
-
msgid "Group description"
msgstr ""
@@ -10908,9 +11171,15 @@ msgstr ""
msgid "Group has not been marked for deletion"
msgstr ""
+msgid "Group import could not be scheduled"
+msgstr ""
+
msgid "Group info:"
msgstr ""
+msgid "Group is required when cluster_type is :group"
+msgstr ""
+
msgid "Group maintainers can register group runners in the %{link}"
msgstr ""
@@ -10974,6 +11243,24 @@ msgstr ""
msgid "GroupActivyMetrics|Recent activity (last 90 days)"
msgstr ""
+msgid "GroupImport|Failed to import group."
+msgstr ""
+
+msgid "GroupImport|Group '%{group_name}' is being imported."
+msgstr ""
+
+msgid "GroupImport|Group could not be imported: %{errors}"
+msgstr ""
+
+msgid "GroupImport|Please wait while we import the group for you. Refresh at will."
+msgstr ""
+
+msgid "GroupImport|The group was successfully imported."
+msgstr ""
+
+msgid "GroupImport|Unable to process group import file"
+msgstr ""
+
msgid "GroupRoadmap|%{dateWord} – No end date"
msgstr ""
@@ -11241,6 +11528,9 @@ msgstr ""
msgid "Groups (%{groups})"
msgstr ""
+msgid "Groups and projects"
+msgstr ""
+
msgid "Groups can also be nested by creating %{subgroup_docs_link_start}subgroups%{subgroup_docs_link_end}."
msgstr ""
@@ -11286,6 +11576,33 @@ msgstr ""
msgid "GroupsEmptyState|You can manage your group member’s permissions and access to each project in the group."
msgstr ""
+msgid "GroupsNew|Contact an administrator to enable options for importing your group."
+msgstr ""
+
+msgid "GroupsNew|Create"
+msgstr ""
+
+msgid "GroupsNew|Create group"
+msgstr ""
+
+msgid "GroupsNew|GitLab group export"
+msgstr ""
+
+msgid "GroupsNew|Import"
+msgstr ""
+
+msgid "GroupsNew|Import group"
+msgstr ""
+
+msgid "GroupsNew|My Awesome Group"
+msgstr ""
+
+msgid "GroupsNew|No import options available"
+msgstr ""
+
+msgid "GroupsNew|To copy a GitLab group between installations, navigate to the group settings page for the original installation, generate an export file, and upload it here."
+msgstr ""
+
msgid "GroupsTree|Are you sure you want to leave the \"%{fullName}\" group?"
msgstr ""
@@ -11388,6 +11705,9 @@ msgstr ""
msgid "Helps reduce request volume for protected paths"
msgstr ""
+msgid "Here are all your projects in your group, including the one you just created. To start, let’s take a look at your personalized learning project which will help you learn about GitLab at your own pace."
+msgstr ""
+
msgid "Here you will find recent merge request activity"
msgstr ""
@@ -11402,6 +11722,9 @@ msgid_plural "Hide charts"
msgstr[0] ""
msgstr[1] ""
+msgid "Hide details"
+msgstr ""
+
msgid "Hide file browser"
msgstr ""
@@ -11411,6 +11734,9 @@ msgstr ""
msgid "Hide host keys manual input"
msgstr ""
+msgid "Hide list"
+msgstr ""
+
msgid "Hide marketing-related entries from help"
msgstr ""
@@ -11431,9 +11757,6 @@ msgstr[1] ""
msgid "Hide values"
msgstr ""
-msgid "Hiding all labels"
-msgstr ""
-
msgid "High or unknown vulnerabilities present"
msgstr ""
@@ -11518,6 +11841,9 @@ msgstr ""
msgid "ID:"
msgstr ""
+msgid "IDE"
+msgstr ""
+
msgid "IDE|Allow live previews of JavaScript projects in the Web IDE using CodeSandbox Live Preview."
msgstr ""
@@ -11608,10 +11934,10 @@ msgstr ""
msgid "If enabled"
msgstr ""
-msgid "If enabled, access to projects will be validated on an external service using their classification label."
+msgid "If enabled, GitLab will handle Object Storage replication using Geo. %{linkStart}More information%{linkEnd}"
msgstr ""
-msgid "If enabled, and if object storage is enabled, GitLab will handle Object Storage replication using Geo"
+msgid "If enabled, access to projects will be validated on an external service using their classification label."
msgstr ""
msgid "If there is no previous license or if the previous license has expired, some GitLab functionality will be blocked until a new, valid license is uploaded."
@@ -11973,6 +12299,9 @@ msgid_plural "Instances"
msgstr[0] ""
msgstr[1] ""
+msgid "Instance Configuration"
+msgstr ""
+
msgid "Instance Statistics visibility"
msgstr ""
@@ -11994,9 +12323,6 @@ msgstr ""
msgid "Integrations"
msgstr ""
-msgid "Integrations allow you to integrate GitLab with other applications"
-msgstr ""
-
msgid "Integrations|All details"
msgstr ""
@@ -12108,6 +12434,9 @@ msgstr ""
msgid "Invalid query"
msgstr ""
+msgid "Invalid repository bundle for snippet with id %{snippet_id}"
+msgstr ""
+
msgid "Invalid repository path"
msgstr ""
@@ -12126,6 +12455,9 @@ msgstr ""
msgid "Invalid two-factor code."
msgstr ""
+msgid "Invalid yaml"
+msgstr ""
+
msgid "Invitation"
msgstr ""
@@ -12345,15 +12677,24 @@ msgstr ""
msgid "It seems like the Dependency Scanning job ran successfully, but no dependencies have been detected in your project."
msgstr ""
+msgid "It seems that there is currently no available data for code coverage"
+msgstr ""
+
msgid "It's you"
msgstr ""
+msgid "Iteration"
+msgstr ""
+
msgid "Iteration changed to"
msgstr ""
msgid "Iteration removed"
msgstr ""
+msgid "Iteration updated"
+msgstr ""
+
msgid "Iterations"
msgstr ""
@@ -12384,12 +12725,18 @@ msgstr ""
msgid "January"
msgstr ""
+msgid "Jira Issues"
+msgstr ""
+
msgid "Jira import is already running."
msgstr ""
msgid "Jira integration not configured."
msgstr ""
+msgid "Jira project key is not configured"
+msgstr ""
+
msgid "Jira project: %{importProject}"
msgstr ""
@@ -12570,6 +12917,9 @@ msgstr ""
msgid "Keep divergent refs"
msgstr ""
+msgid "Kerberos access denied"
+msgstr ""
+
msgid "Key"
msgstr ""
@@ -12579,10 +12929,13 @@ msgstr ""
msgid "Key: %{key}"
msgstr ""
-msgid "Keyboard Shortcuts"
+msgid "Keyboard shortcuts"
msgstr ""
-msgid "Keyboard shortcuts"
+msgid "Keys"
+msgstr ""
+
+msgid "Ki"
msgstr ""
msgid "Kubernetes"
@@ -12624,6 +12977,9 @@ msgstr ""
msgid "LDAP"
msgstr ""
+msgid "LDAP Synchronization"
+msgstr ""
+
msgid "LDAP settings"
msgstr ""
@@ -12848,12 +13204,18 @@ msgstr ""
msgid "Learn more about custom project templates"
msgstr ""
+msgid "Learn more about deploying to AWS"
+msgstr ""
+
msgid "Learn more about deploying to a cluster"
msgstr ""
msgid "Learn more about group-level project templates"
msgstr ""
+msgid "Learn more about job dependencies"
+msgstr ""
+
msgid "Learn more about signing commits"
msgstr ""
@@ -12905,9 +13267,21 @@ msgstr ""
msgid "License History"
msgstr ""
+msgid "License ID:"
+msgstr ""
+
+msgid "License URL"
+msgstr ""
+
msgid "License-Check"
msgstr ""
+msgid "LicenseCompliance|%{docLinkStart}License Approvals%{docLinkEnd} are active"
+msgstr ""
+
+msgid "LicenseCompliance|%{docLinkStart}License Approvals%{docLinkEnd} are inactive"
+msgstr ""
+
msgid "LicenseCompliance|Add a license"
msgstr ""
@@ -12929,9 +13303,15 @@ msgstr ""
msgid "LicenseCompliance|Deny"
msgstr ""
+msgid "LicenseCompliance|Learn more about %{linkStart}License Approvals%{linkEnd}"
+msgstr ""
+
msgid "LicenseCompliance|License"
msgstr ""
+msgid "LicenseCompliance|License Approvals"
+msgstr ""
+
msgid "LicenseCompliance|License Compliance detected %d license and policy violation for the source branch only; approval required"
msgid_plural "LicenseCompliance|License Compliance detected %d licenses and policy violations for the source branch only; approval required"
msgstr[0] ""
@@ -13152,6 +13532,9 @@ msgstr ""
msgid "List your Bitbucket Server repositories"
msgstr ""
+msgid "Lists"
+msgstr ""
+
msgid "Live preview"
msgstr ""
@@ -13335,6 +13718,9 @@ msgstr ""
msgid "Manage project labels"
msgstr ""
+msgid "Manage storage usage"
+msgstr ""
+
msgid "Manage two-factor authentication"
msgstr ""
@@ -13350,6 +13736,9 @@ msgstr ""
msgid "Manifest file import"
msgstr ""
+msgid "Manifest import"
+msgstr ""
+
msgid "Manual job"
msgstr ""
@@ -13497,6 +13886,12 @@ msgstr ""
msgid "Maximum field length"
msgstr ""
+msgid "Maximum file size is 2MB. Please select a smaller file."
+msgstr ""
+
+msgid "Maximum import size (MB)"
+msgstr ""
+
msgid "Maximum job timeout"
msgstr ""
@@ -13536,6 +13931,9 @@ msgstr ""
msgid "Maximum size of Elasticsearch bulk indexing requests."
msgstr ""
+msgid "Maximum size of import files."
+msgstr ""
+
msgid "Maximum size of individual attachments in comments."
msgstr ""
@@ -13587,6 +13985,9 @@ msgstr ""
msgid "Merge (when the pipeline succeeds)"
msgstr ""
+msgid "Merge Conflicts"
+msgstr ""
+
msgid "Merge Request"
msgstr ""
@@ -13713,6 +14114,9 @@ msgstr ""
msgid "MergeRequests|Squash task canceled: another squash is already in progress."
msgstr ""
+msgid "MergeRequests|This project does not allow squashing commits when merge requests are accepted."
+msgstr ""
+
msgid "MergeRequests|Thread stays resolved"
msgstr ""
@@ -13866,6 +14270,12 @@ msgstr ""
msgid "MetricsSettings|Add a button to the metrics dashboard linking directly to your existing external dashboard."
msgstr ""
+msgid "MetricsSettings|Choose whether to display dashboard metrics in UTC or the user's local timezone."
+msgstr ""
+
+msgid "MetricsSettings|Dashboard timezone"
+msgstr ""
+
msgid "MetricsSettings|External dashboard URL"
msgstr ""
@@ -13875,6 +14285,12 @@ msgstr ""
msgid "MetricsSettings|Metrics Dashboard"
msgstr ""
+msgid "MetricsSettings|UTC (Coordinated Universal Time)"
+msgstr ""
+
+msgid "MetricsSettings|User's local timezone"
+msgstr ""
+
msgid "Metrics|Add metric"
msgstr ""
@@ -13890,6 +14306,9 @@ msgstr ""
msgid "Metrics|Create metric"
msgstr ""
+msgid "Metrics|Current"
+msgstr ""
+
msgid "Metrics|Delete metric"
msgstr ""
@@ -13940,6 +14359,9 @@ msgstr ""
msgid "Metrics|Max"
msgstr ""
+msgid "Metrics|Min"
+msgstr ""
+
msgid "Metrics|Must be a valid PromQL query."
msgstr ""
@@ -13955,6 +14377,9 @@ msgstr ""
msgid "Metrics|Refresh dashboard"
msgstr ""
+msgid "Metrics|Select a value"
+msgstr ""
+
msgid "Metrics|Star dashboard"
msgstr ""
@@ -13973,12 +14398,18 @@ msgstr ""
msgid "Metrics|There was an error getting annotations information."
msgstr ""
+msgid "Metrics|There was an error getting dashboard validation warnings information."
+msgstr ""
+
msgid "Metrics|There was an error getting deployment information."
msgstr ""
msgid "Metrics|There was an error getting environments information."
msgstr ""
+msgid "Metrics|There was an error getting options for variable \"%{name}\"."
+msgstr ""
+
msgid "Metrics|There was an error trying to validate your query"
msgstr ""
@@ -14021,6 +14452,9 @@ msgstr ""
msgid "Metrics|You're about to permanently delete this metric. This cannot be undone."
msgstr ""
+msgid "Metrics|Your dashboard schema is invalid. Edit the dashboard to correct the YAML schema."
+msgstr ""
+
msgid "Metrics|e.g. HTTP requests"
msgstr ""
@@ -14036,12 +14470,18 @@ msgstr ""
msgid "Metrics|e.g. req/sec"
msgstr ""
+msgid "Mi"
+msgstr ""
+
msgid "Microsoft Azure"
msgstr ""
msgid "Middleman project with Static Site Editor support"
msgstr ""
+msgid "Migrate your data from an external source like GitHub, Bitbucket, or another instance of GitLab."
+msgstr ""
+
msgid "Migrated %{success_count}/%{total_count} files."
msgstr ""
@@ -14176,6 +14616,9 @@ msgstr ""
msgid "Minimum capacity to be available before we schedule more mirrors preemptively."
msgstr ""
+msgid "Minimum interval in days"
+msgstr ""
+
msgid "Minimum length is %{minimum_password_length} characters"
msgstr ""
@@ -14362,6 +14805,12 @@ msgstr ""
msgid "Multiple uploaders found: %{uploader_types}"
msgstr ""
+msgid "Must match with the %{codeStart}external_url%{codeEnd} in %{codeStart}/etc/gitlab/gitlab.rb%{codeEnd}."
+msgstr ""
+
+msgid "Must match with the %{codeStart}geo_node_name%{codeEnd} in %{codeStart}/etc/gitlab/gitlab.rb%{codeEnd}. %{linkStart}More information%{linkEnd}"
+msgstr ""
+
msgid "My Awesome Group"
msgstr ""
@@ -14476,10 +14925,16 @@ msgstr ""
msgid "New Application"
msgstr ""
+msgid "New Branch"
+msgstr ""
+
+msgid "New Deploy Key"
+msgstr ""
+
msgid "New Environment"
msgstr ""
-msgid "New Geo Node"
+msgid "New File"
msgstr ""
msgid "New Group"
@@ -14496,15 +14951,15 @@ msgid_plural "New Issues"
msgstr[0] ""
msgstr[1] ""
-msgid "New Iteration"
-msgstr ""
-
msgid "New Jira import"
msgstr ""
msgid "New Label"
msgstr ""
+msgid "New Merge Request"
+msgstr ""
+
msgid "New Milestone"
msgstr ""
@@ -14523,6 +14978,9 @@ msgstr ""
msgid "New Snippet"
msgstr ""
+msgid "New User"
+msgstr ""
+
msgid "New branch"
msgstr ""
@@ -14565,6 +15023,9 @@ msgstr ""
msgid "New issue title"
msgstr ""
+msgid "New iteration"
+msgstr ""
+
msgid "New iteration created"
msgstr ""
@@ -14742,6 +15203,12 @@ msgstr ""
msgid "No grouping"
msgstr ""
+msgid "No iteration"
+msgstr ""
+
+msgid "No iterations to show"
+msgstr ""
+
msgid "No job log"
msgstr ""
@@ -14784,6 +15251,9 @@ msgstr ""
msgid "No other labels with such name or description"
msgstr ""
+msgid "No panels matching properties %{opts}"
+msgstr ""
+
msgid "No parent group"
msgstr ""
@@ -14817,6 +15287,9 @@ msgstr ""
msgid "No schedules"
msgstr ""
+msgid "No stack trace for this error"
+msgstr ""
+
msgid "No starrers matched your search"
msgstr ""
@@ -14829,7 +15302,7 @@ msgstr ""
msgid "No test coverage"
msgstr ""
-msgid "No thanks, don't show this again"
+msgid "No thanks"
msgstr ""
msgid "No vulnerabilities present"
@@ -14844,9 +15317,6 @@ msgstr ""
msgid "No, directly import the existing email addresses and usernames."
msgstr ""
-msgid "No, not interested right now"
-msgstr ""
-
msgid "No. of commits"
msgstr ""
@@ -14892,9 +15362,6 @@ msgstr ""
msgid "Not found."
msgstr ""
-msgid "Not helpful"
-msgstr ""
-
msgid "Not now"
msgstr ""
@@ -15051,6 +15518,9 @@ msgstr ""
msgid "Notifications on"
msgstr ""
+msgid "Notify users by email when sign-in location is not recognized"
+msgstr ""
+
msgid "Nov"
msgstr ""
@@ -15117,9 +15587,6 @@ msgstr ""
msgid "Oh no!"
msgstr ""
-msgid "Ok let's go"
-msgstr ""
-
msgid "Oldest first"
msgstr ""
@@ -15132,16 +15599,49 @@ msgstr ""
msgid "On track"
msgstr ""
+msgid "OnDemandScans|Attached branch"
+msgstr ""
+
+msgid "OnDemandScans|Attached branch is where the scan job runs."
+msgstr ""
+
+msgid "OnDemandScans|Could not run the scan. Please try again."
+msgstr ""
+
msgid "OnDemandScans|Create new DAST scan"
msgstr ""
+msgid "OnDemandScans|DAST will scan the target URL and any discovered sub URLs."
+msgstr ""
+
+msgid "OnDemandScans|New on-demand DAST scan"
+msgstr ""
+
msgid "OnDemandScans|On-demand Scans"
msgstr ""
+msgid "OnDemandScans|On-demand scans run outside the DevOps cycle and find vulnerabilities in your projects. %{learnMoreLinkStart}Learn more%{learnMoreLinkEnd}"
+msgstr ""
+
+msgid "OnDemandScans|Only a passive scan can be performed on demand."
+msgstr ""
+
+msgid "OnDemandScans|Passive DAST Scan"
+msgstr ""
+
+msgid "OnDemandScans|Please enter a valid URL format, ex: http://www.example.com/home"
+msgstr ""
+
+msgid "OnDemandScans|Run this scan"
+msgstr ""
+
+msgid "OnDemandScans|Scan mode"
+msgstr ""
+
msgid "OnDemandScans|Schedule or run scans immediately against target sites. Currently available on-demand scan type: DAST. %{helpLinkStart}More information%{helpLinkEnd}"
msgstr ""
-msgid "Onboarding"
+msgid "OnDemandScans|Target URL"
msgstr ""
msgid "Once imported, repositories can be mirrored over SSH. Read more %{link_start}here%{link_end}."
@@ -15167,6 +15667,9 @@ msgstr ""
msgid "One or more of you personal access tokens were revoked"
msgstr ""
+msgid "One or more of your %{provider} projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
+msgstr ""
+
msgid "One or more of your Bitbucket projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
msgstr ""
@@ -15668,6 +16171,9 @@ msgstr ""
msgid "Parent epic is not present."
msgstr ""
+msgid "Parsing error for param :embed_json. %{message}"
+msgstr ""
+
msgid "Part of merge request changes"
msgstr ""
@@ -15680,6 +16186,9 @@ msgstr ""
msgid "Passed"
msgstr ""
+msgid "Passed on"
+msgstr ""
+
msgid "Password"
msgstr ""
@@ -15764,6 +16273,9 @@ msgstr ""
msgid "Perform common operations on GitLab project"
msgstr ""
+msgid "Performance and resource management"
+msgstr ""
+
msgid "Performance optimization"
msgstr ""
@@ -15971,7 +16483,7 @@ msgstr ""
msgid "Pipelines|Something went wrong while cleaning runners cache."
msgstr ""
-msgid "Pipelines|There are currently no %{scope} pipelines."
+msgid "Pipelines|There are currently no finished pipelines."
msgstr ""
msgid "Pipelines|There are currently no pipelines."
@@ -16082,6 +16594,9 @@ msgstr ""
msgid "Pipeline|Stop pipeline #%{pipelineId}?"
msgstr ""
+msgid "Pipeline|Tag name"
+msgstr ""
+
msgid "Pipeline|Trigger author"
msgstr ""
@@ -16163,12 +16678,18 @@ msgstr ""
msgid "Please check your email (%{email}) to verify that you own this address and unlock the power of CI/CD. Didn't receive it? %{resend_link}. Wrong email address? %{update_link}."
msgstr ""
+msgid "Please choose a file"
+msgstr ""
+
msgid "Please choose a group URL with no special characters."
msgstr ""
msgid "Please complete your profile with email address"
msgstr ""
+msgid "Please convert %{linkStart}them to Git%{linkEnd}, and go through the %{linkToImportFlow} again."
+msgstr ""
+
msgid "Please convert them to %{link_to_git}, and go through the %{link_to_import_flow} again."
msgstr ""
@@ -16226,6 +16747,9 @@ msgstr ""
msgid "Please provide a valid email address."
msgstr ""
+msgid "Please provide attributes to update"
+msgstr ""
+
msgid "Please refer to <a href=\"%{docs_url}\">%{docs_url}</a>"
msgstr ""
@@ -16961,6 +17485,9 @@ msgstr ""
msgid "Project has too many %{label_for_message} to search"
msgstr ""
+msgid "Project is required when cluster_type is :project"
+msgstr ""
+
msgid "Project members"
msgstr ""
@@ -17078,7 +17605,40 @@ msgstr ""
msgid "ProjectService|Comment"
msgstr ""
-msgid "ProjectService|Comment will be posted on each event"
+msgid "ProjectService|Event will be triggered by a push to the repository"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a commit is created/updated"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a confidential issue is created/updated/closed"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a deployment finishes"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a merge request is created/updated/merged"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a new tag is pushed to the repository"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a new, unique alert is recorded"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a pipeline status changes"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a wiki page is created/updated"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when an issue is created/updated/closed"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when someone adds a comment"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when someone adds a comment on a confidential issue"
msgstr ""
msgid "ProjectService|Perform common operations on GitLab project: %{project_name}"
@@ -17129,6 +17689,9 @@ msgstr ""
msgid "ProjectSettings|Enable 'Delete source branch' option by default"
msgstr ""
+msgid "ProjectSettings|Enable merge trains and pipelines for merged results"
+msgstr ""
+
msgid "ProjectSettings|Every merge creates a merge commit"
msgstr ""
@@ -17159,7 +17722,7 @@ msgstr ""
msgid "ProjectSettings|Forks"
msgstr ""
-msgid "ProjectSettings|Git Large File Storage"
+msgid "ProjectSettings|Git Large File Storage (LFS)"
msgstr ""
msgid "ProjectSettings|Internal"
@@ -17195,9 +17758,6 @@ msgstr ""
msgid "ProjectSettings|Merge options"
msgstr ""
-msgid "ProjectSettings|Merge pipelines will try to validate the post-merge result prior to merging"
-msgstr ""
-
msgid "ProjectSettings|Merge requests"
msgstr ""
@@ -17225,6 +17785,9 @@ msgstr ""
msgid "ProjectSettings|Pipelines"
msgstr ""
+msgid "ProjectSettings|Pipelines for merge requests must be enabled in the CI/CD configuration file, or pipelines could be unresolvable or dropped"
+msgstr ""
+
msgid "ProjectSettings|Pipelines must succeed"
msgstr ""
@@ -17252,6 +17815,9 @@ msgstr ""
msgid "ProjectSettings|Show link to create/view merge request when pushing from the command line"
msgstr ""
+msgid "ProjectSettings|Skipped pipelines are considered successful"
+msgstr ""
+
msgid "ProjectSettings|Snippets"
msgstr ""
@@ -17267,6 +17833,9 @@ msgstr ""
msgid "ProjectSettings|These checks must pass before merge requests can be merged"
msgstr ""
+msgid "ProjectSettings|This introduces the risk of merging changes that will not pass the pipeline."
+msgstr ""
+
msgid "ProjectSettings|This setting is applied on the server level and can be overridden by an admin."
msgstr ""
@@ -17444,15 +18013,27 @@ msgstr ""
msgid "ProjectsNew|Blank project"
msgstr ""
+msgid "ProjectsNew|Connect your external repository to GitLab CI/CD."
+msgstr ""
+
msgid "ProjectsNew|Contact an administrator to enable options for importing your project."
msgstr ""
msgid "ProjectsNew|Create"
msgstr ""
+msgid "ProjectsNew|Create a blank project to house your files, plan your work, and collaborate on code, among other things."
+msgstr ""
+
+msgid "ProjectsNew|Create blank project"
+msgstr ""
+
msgid "ProjectsNew|Create from template"
msgstr ""
+msgid "ProjectsNew|Create new project"
+msgstr ""
+
msgid "ProjectsNew|Creating project & repository."
msgstr ""
@@ -17477,6 +18058,9 @@ msgstr ""
msgid "ProjectsNew|Project description %{tag_start}(optional)%{tag_end}"
msgstr ""
+msgid "ProjectsNew|Run CI/CD for external repository"
+msgstr ""
+
msgid "ProjectsNew|Template"
msgstr ""
@@ -17738,6 +18322,9 @@ msgstr ""
msgid "Protected Branch"
msgstr ""
+msgid "Protected Branches"
+msgstr ""
+
msgid "Protected Environment"
msgstr ""
@@ -17750,6 +18337,9 @@ msgstr ""
msgid "Protected Tag"
msgstr ""
+msgid "Protected Tags"
+msgstr ""
+
msgid "Protected branches"
msgstr ""
@@ -17849,6 +18439,9 @@ msgstr ""
msgid "Provider"
msgstr ""
+msgid "Proxy support for this API is not available currently"
+msgstr ""
+
msgid "Pseudonymizer data collection"
msgstr ""
@@ -17873,6 +18466,9 @@ msgstr ""
msgid "Publish to status page"
msgstr ""
+msgid "Published on status page"
+msgstr ""
+
msgid "Publishes this issue to the associated status page."
msgstr ""
@@ -18047,6 +18643,9 @@ msgstr ""
msgid "Recipe"
msgstr ""
+msgid "Reconfigure"
+msgstr ""
+
msgid "Recover hidden stage"
msgstr ""
@@ -18133,6 +18732,9 @@ msgstr ""
msgid "Registration|Your profile"
msgstr ""
+msgid "Regulate approvals by authors/committers, based on compliance frameworks. Can be changed only at the instance level."
+msgstr ""
+
msgid "Rejected (closed)"
msgstr ""
@@ -18183,15 +18785,24 @@ msgstr ""
msgid "ReleaseAssetLinkType|Image"
msgstr ""
+msgid "ReleaseAssetLinkType|Images"
+msgstr ""
+
msgid "ReleaseAssetLinkType|Other"
msgstr ""
msgid "ReleaseAssetLinkType|Package"
msgstr ""
+msgid "ReleaseAssetLinkType|Packages"
+msgstr ""
+
msgid "ReleaseAssetLinkType|Runbook"
msgstr ""
+msgid "ReleaseAssetLinkType|Runbooks"
+msgstr ""
+
msgid "Releases"
msgstr ""
@@ -18204,6 +18815,9 @@ msgstr ""
msgid "Releases documentation"
msgstr ""
+msgid "Releases|New Release"
+msgstr ""
+
msgid "Release|Something went wrong while getting the release details"
msgstr ""
@@ -18276,6 +18890,9 @@ msgstr ""
msgid "Remove fork relationship"
msgstr ""
+msgid "Remove from batch"
+msgstr ""
+
msgid "Remove from board"
msgstr ""
@@ -18465,6 +19082,12 @@ msgstr ""
msgid "Replication"
msgstr ""
+msgid "Replication enabled"
+msgstr ""
+
+msgid "Replication paused"
+msgstr ""
+
msgid "Reply by email"
msgstr ""
@@ -18492,15 +19115,15 @@ msgstr ""
msgid "Reported %{timeAgo} by %{reportedBy}"
msgstr ""
-msgid "Reported Resource Changes: %{addNum} to add, %{changeNum} to change, %{deleteNum} to delete"
-msgstr ""
-
msgid "Reporter"
msgstr ""
msgid "Reporting"
msgstr ""
+msgid "Reports"
+msgstr ""
+
msgid "Reports|%{combinedString} and %{resolvedString}"
msgstr ""
@@ -18539,6 +19162,9 @@ msgstr ""
msgid "Reports|Failure"
msgstr ""
+msgid "Reports|Identifier"
+msgstr ""
+
msgid "Reports|Metrics reports are loading"
msgstr ""
@@ -18551,6 +19177,9 @@ msgstr ""
msgid "Reports|Metrics reports failed loading results"
msgstr ""
+msgid "Reports|Scanner"
+msgstr ""
+
msgid "Reports|Severity"
msgstr ""
@@ -18599,15 +19228,33 @@ msgstr ""
msgid "Repository cleanup has started. You will receive an email once the cleanup operation is complete."
msgstr ""
+msgid "Repository files count over the limit"
+msgstr ""
+
+msgid "Repository has an invalid default branch name."
+msgstr ""
+
+msgid "Repository has more than one branch."
+msgstr ""
+
msgid "Repository has no locks."
msgstr ""
+msgid "Repository has tags."
+msgstr ""
+
msgid "Repository maintenance"
msgstr ""
msgid "Repository mirroring"
msgstr ""
+msgid "Repository must contain at least 1 file."
+msgstr ""
+
+msgid "Repository size is above the limit."
+msgstr ""
+
msgid "Repository static objects"
msgstr ""
@@ -18617,7 +19264,7 @@ msgstr ""
msgid "Repository sync capacity"
msgstr ""
-msgid "Repository: %{counter_repositories} / Wikis: %{counter_wikis} / Build Artifacts: %{counter_build_artifacts} / LFS: %{counter_lfs_objects}"
+msgid "Repository: %{counter_repositories} / Wikis: %{counter_wikis} / Build Artifacts: %{counter_build_artifacts} / LFS: %{counter_lfs_objects} / Snippets: %{counter_snippets}"
msgstr ""
msgid "RepositorySettingsAccessLevel|Select"
@@ -18629,6 +19276,9 @@ msgstr ""
msgid "Request Headers"
msgstr ""
+msgid "Request details"
+msgstr ""
+
msgid "Request parameter %{param} is missing."
msgstr ""
@@ -18735,6 +19385,9 @@ msgstr ""
msgid "Reset template"
msgstr ""
+msgid "Reset to project defaults"
+msgstr ""
+
msgid "Resetting the authorization key for this project will require updating the authorization key in every alert source it is enabled in."
msgstr ""
@@ -18831,9 +19484,6 @@ msgstr ""
msgid "Resume"
msgstr ""
-msgid "Resume replication"
-msgstr ""
-
msgid "Resync"
msgstr ""
@@ -19456,6 +20106,9 @@ msgstr ""
msgid "SecurityReports|False positive"
msgstr ""
+msgid "SecurityReports|Group Security Dashboard"
+msgstr ""
+
msgid "SecurityReports|Hide dismissed"
msgstr ""
@@ -19510,10 +20163,13 @@ msgstr ""
msgid "SecurityReports|Remove project from dashboard"
msgstr ""
-msgid "SecurityReports|Report type"
+msgid "SecurityReports|Return to dashboard"
msgstr ""
-msgid "SecurityReports|Return to dashboard"
+msgid "SecurityReports|Scan details"
+msgstr ""
+
+msgid "SecurityReports|Scanner type"
msgstr ""
msgid "SecurityReports|Security Dashboard"
@@ -19570,6 +20226,9 @@ msgstr ""
msgid "SecurityReports|There was an error while generating the report."
msgstr ""
+msgid "SecurityReports|Unable to add %{invalidProjectsMessage}"
+msgstr ""
+
msgid "SecurityReports|Unable to add %{invalidProjects}"
msgstr ""
@@ -19684,6 +20343,9 @@ msgstr ""
msgid "Select due date"
msgstr ""
+msgid "Select file"
+msgstr ""
+
msgid "Select group or project"
msgstr ""
@@ -19747,9 +20409,6 @@ msgstr ""
msgid "Select the branch you want to set as the default for this project. All merge requests and commits will automatically be made against this branch unless you specify a different one."
msgstr ""
-msgid "Select the configured storage available for new repositories to be placed on."
-msgstr ""
-
msgid "Select the custom project template source group."
msgstr ""
@@ -19942,12 +20601,6 @@ msgstr ""
msgid "Service Desk is enabled but not yet active"
msgstr ""
-msgid "Service Desk is off"
-msgstr ""
-
-msgid "Service Desk is on"
-msgstr ""
-
msgid "Service Templates"
msgstr ""
@@ -20035,6 +20688,15 @@ msgstr ""
msgid "Set the milestone to %{milestone_reference}."
msgstr ""
+msgid "Set the number of concurrent requests this secondary node will make to the primary node while backfilling."
+msgstr ""
+
+msgid "Set the synchronization and verification capacity for the secondary node."
+msgstr ""
+
+msgid "Set the timeout in seconds to send a secondary node status to the primary and IPs allowed for the secondary nodes."
+msgstr ""
+
msgid "Set time estimate"
msgstr ""
@@ -20074,6 +20736,9 @@ msgstr ""
msgid "Set weight to %{weight}."
msgstr ""
+msgid "Set what should be replicated by choosing specific projects or groups by the secondary node."
+msgstr ""
+
msgid "SetPasswordToCloneLink|set a password"
msgstr ""
@@ -20200,6 +20865,9 @@ msgstr ""
msgid "Show complete raw log"
msgstr ""
+msgid "Show details"
+msgstr ""
+
msgid "Show file browser"
msgstr ""
@@ -20209,10 +20877,13 @@ msgstr ""
msgid "Show latest version"
msgstr ""
+msgid "Show list"
+msgstr ""
+
msgid "Show me everything"
msgstr ""
-msgid "Show me how"
+msgid "Show me how to add a pipeline"
msgstr ""
msgid "Show me more advanced stuff"
@@ -20261,9 +20932,6 @@ msgstr ""
msgid "Showing all issues"
msgstr ""
-msgid "Showing all labels"
-msgstr ""
-
msgid "Showing last %{size} of log -"
msgstr ""
@@ -20375,9 +21043,6 @@ msgstr ""
msgid "Skip outdated deployment jobs"
msgstr ""
-msgid "Skip this for now"
-msgstr ""
-
msgid "Skipped"
msgstr ""
@@ -20522,6 +21187,9 @@ msgstr ""
msgid "Something went wrong while adding your award. Please try again."
msgstr ""
+msgid "Something went wrong while applying the batch of suggestions. Please try again."
+msgstr ""
+
msgid "Something went wrong while applying the suggestion. Please try again."
msgstr ""
@@ -20624,10 +21292,13 @@ msgstr ""
msgid "Something went wrong while updating a requirement."
msgstr ""
+msgid "Something went wrong while updating assignees"
+msgstr ""
+
msgid "Something went wrong while updating your list settings"
msgstr ""
-msgid "Something went wrong with your automatic subscription renewal"
+msgid "Something went wrong with your automatic subscription renewal."
msgstr ""
msgid "Something went wrong, unable to add %{project} to dashboard"
@@ -20645,6 +21316,9 @@ msgstr ""
msgid "Something went wrong, unable to search projects"
msgstr ""
+msgid "Something went wrong."
+msgstr ""
+
msgid "Something went wrong. Please try again."
msgstr ""
@@ -20828,6 +21502,9 @@ msgstr ""
msgid "Source code"
msgstr ""
+msgid "Source code (%{fileExtension})"
+msgstr ""
+
msgid "Source is not available"
msgstr ""
@@ -20912,6 +21589,9 @@ msgstr ""
msgid "Stack trace"
msgstr ""
+msgid "Stacktrace snippet"
+msgstr ""
+
msgid "Stage"
msgstr ""
@@ -21017,6 +21697,9 @@ msgstr ""
msgid "Start thread & reopen %{noteable_name}"
msgstr ""
+msgid "Start using Directed Acyclic Graphs (DAG)"
+msgstr ""
+
msgid "Start your Free Gold Trial"
msgstr ""
@@ -21479,6 +22162,21 @@ msgstr ""
msgid "SuggestedColors|Very pale orange"
msgstr ""
+msgid "Suggestion is not applicable as the suggestion was not found."
+msgstr ""
+
+msgid "Suggestions are not applicable as one or more suggestions were not found."
+msgstr ""
+
+msgid "Suggestions are not applicable as their lines cannot overlap."
+msgstr ""
+
+msgid "Suggestions must all be on the same branch."
+msgstr ""
+
+msgid "Suggestions that change line count can't be added to batches, yet."
+msgstr ""
+
msgid "Suggestions:"
msgstr ""
@@ -21521,9 +22219,6 @@ msgstr ""
msgid "Synchronization disabled"
msgstr ""
-msgid "Synchronization of container repositories is disabled."
-msgstr ""
-
msgid "System"
msgstr ""
@@ -21704,6 +22399,34 @@ msgstr ""
msgid "Terms of Service and Privacy Policy"
msgstr ""
+msgid "Terraform|%{number} Terraform report failed to generate"
+msgid_plural "Terraform|%{number} Terraform reports failed to generate"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Terraform|%{number} Terraform report was generated in your pipelines"
+msgid_plural "Terraform|%{number} Terraform reports were generated in your pipelines"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Terraform|A Terraform report failed to generate."
+msgstr ""
+
+msgid "Terraform|A Terraform report was generated in your pipelines."
+msgstr ""
+
+msgid "Terraform|Generating the report caused an error."
+msgstr ""
+
+msgid "Terraform|Reported Resource Changes: %{addNum} to add, %{changeNum} to change, %{deleteNum} to delete"
+msgstr ""
+
+msgid "Terraform|The Terraform report %{name} failed to generate."
+msgstr ""
+
+msgid "Terraform|The Terraform report %{name} was generated in your pipelines."
+msgstr ""
+
msgid "Test"
msgstr ""
@@ -21730,7 +22453,7 @@ msgstr ""
msgid "TestHooks|Ensure the project has CI pipelines."
msgstr ""
-msgid "TestHooks|Ensure the project has at least one commit."
+msgid "TestHooks|Ensure the project has deployments."
msgstr ""
msgid "TestHooks|Ensure the project has issues."
@@ -21831,7 +22554,7 @@ msgstr ""
msgid "The Prometheus server responded with \"bad request\". Please check your queries are correct and are supported in your Prometheus version. %{documentationLink}"
msgstr ""
-msgid "The URL defined on the primary node that secondary nodes should use to contact it. Defaults to URL"
+msgid "The URL defined on the primary node that secondary nodes should use to contact it."
msgstr ""
msgid "The URL to use for connecting to Elasticsearch. Use a comma-separated list to support clustering (e.g., \"http://localhost:9200, http://localhost:9201\")."
@@ -21840,6 +22563,9 @@ msgstr ""
msgid "The X509 Certificate to use when mutual TLS is required to communicate with the external authorization service. If left blank, the server certificate is still validated when accessing over HTTPS."
msgstr ""
+msgid "The above settings apply to all projects with the selected compliance framework(s)."
+msgstr ""
+
msgid "The amount of seconds after which a request to get a secondary node status will time out."
msgstr ""
@@ -22139,9 +22865,6 @@ msgstr ""
msgid "The total stage shows the time it takes between creating an issue and deploying the code to production. The data will be automatically added once you have completed the full idea to production cycle."
msgstr ""
-msgid "The unique identifier for the Geo node. Must match %{geoNodeName} if it is set in gitlab.rb, otherwise it must match %{externalUrl} with a trailing slash"
-msgstr ""
-
msgid "The update action will time out after %{number_of_minutes} minutes. For big repositories, use a clone/push combination."
msgstr ""
@@ -22166,9 +22889,6 @@ msgstr ""
msgid "The user you are trying to deactivate has been active in the past %{minimum_inactive_days} days and cannot be deactivated"
msgstr ""
-msgid "The user-facing URL of the Geo node"
-msgstr ""
-
msgid "The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6."
msgstr ""
@@ -22259,6 +22979,9 @@ msgstr ""
msgid "There was a problem fetching project branches."
msgstr ""
+msgid "There was a problem fetching project tags."
+msgstr ""
+
msgid "There was a problem fetching project users."
msgstr ""
@@ -22310,6 +23033,9 @@ msgstr ""
msgid "There was an error fetching the %{replicableType}"
msgstr ""
+msgid "There was an error fetching the Geo Settings"
+msgstr ""
+
msgid "There was an error fetching the Node's Groups"
msgstr ""
@@ -22430,6 +23156,12 @@ msgstr ""
msgid "This %{issuable} is locked. Only <strong>project members</strong> can comment."
msgstr ""
+msgid "This %{noteableTypeText} is %{confidentialLinkStart}confidential%{linkEnd} and %{lockedLinkStart}locked%{linkEnd}."
+msgstr ""
+
+msgid "This %{noteableTypeText} is locked."
+msgstr ""
+
msgid "This %{viewer} could not be displayed because %{reason}. You can %{options} instead."
msgstr ""
@@ -22448,6 +23180,9 @@ msgstr ""
msgid "This action can lead to data loss. To prevent accidental actions we ask you to confirm your intention."
msgstr ""
+msgid "This also resolves all related threads"
+msgstr ""
+
msgid "This also resolves the discussion"
msgstr ""
@@ -22538,6 +23273,9 @@ msgstr ""
msgid "This epic does not exist or you don't have sufficient permission."
msgstr ""
+msgid "This feature is currently in beta. We invite you to %{linkStart}give feedback%{linkEnd}."
+msgstr ""
+
msgid "This feature requires local storage to be enabled"
msgstr ""
@@ -22571,7 +23309,7 @@ msgstr ""
msgid "This is a Work in Progress"
msgstr ""
-msgid "This is a confidential issue."
+msgid "This is a confidential %{noteableTypeText}."
msgstr ""
msgid "This is a delayed job to run in %{remainingTime}"
@@ -22598,9 +23336,6 @@ msgstr ""
msgid "This is your current session"
msgstr ""
-msgid "This issue is %{confidentialLinkStart}confidential%{linkEnd} and %{lockedLinkStart}locked%{linkEnd}."
-msgstr ""
-
msgid "This issue is confidential"
msgstr ""
@@ -22610,9 +23345,6 @@ msgstr ""
msgid "This issue is in a child epic of the filtered epic"
msgstr ""
-msgid "This issue is locked."
-msgstr ""
-
msgid "This job depends on other jobs with expired/erased artifacts: %{invalid_dependencies}"
msgstr ""
@@ -22712,6 +23444,9 @@ msgstr ""
msgid "This license has already expired."
msgstr ""
+msgid "This link points to external content"
+msgstr ""
+
msgid "This may expose confidential information as the selected fork is in another namespace that can have other members."
msgstr ""
@@ -22739,6 +23474,9 @@ msgstr ""
msgid "This page will be removed in a future release."
msgstr ""
+msgid "This pipeline does not use the %{codeStart}needs%{codeEnd} keyword and can't be represented as a directed acyclic graph."
+msgstr ""
+
msgid "This pipeline makes use of a predefined CI/CD configuration enabled by %{strongStart}Auto DevOps.%{strongEnd}"
msgstr ""
@@ -22967,6 +23705,9 @@ msgstr ""
msgid "Time from last commit to merge"
msgstr ""
+msgid "Time in seconds"
+msgstr ""
+
msgid "Time in seconds GitLab will wait for a response from the external service. When the service does not respond in time, access will be denied."
msgstr ""
@@ -23388,9 +24129,15 @@ msgstr ""
msgid "Total artifacts size: %{total_size}"
msgstr ""
+msgid "Total cores (CPUs)"
+msgstr ""
+
msgid "Total issues"
msgstr ""
+msgid "Total memory (GB)"
+msgstr ""
+
msgid "Total test time for all commits/merges"
msgstr ""
@@ -23592,6 +24339,9 @@ msgstr ""
msgid "U2F only works with HTTPS-enabled websites. Contact your administrator for more details."
msgstr ""
+msgid "UI Development Kit"
+msgstr ""
+
msgid "URL"
msgstr ""
@@ -23664,9 +24414,6 @@ msgstr ""
msgid "Unable to load the merge request widget. Try reloading the page."
msgstr ""
-msgid "Unable to process group import file"
-msgstr ""
-
msgid "Unable to resolve"
msgstr ""
@@ -23682,6 +24429,9 @@ msgstr ""
msgid "Unable to sign you in to the group with SAML due to \"%{reason}\""
msgstr ""
+msgid "Unable to suggest a path. Please refresh and try again."
+msgstr ""
+
msgid "Unable to update label prioritization at this time"
msgstr ""
@@ -23697,6 +24447,9 @@ msgstr ""
msgid "Unarchiving the project will restore people's ability to make changes to it. The repository can be committed to, and issues, comments, and other entities can be created. %{strong_start}Once active, this project shows up in the search and on the dashboard.%{strong_end}"
msgstr ""
+msgid "Unassign from commenting user"
+msgstr ""
+
msgid "Unblock"
msgstr ""
@@ -23772,6 +24525,9 @@ msgstr ""
msgid "Unreachable"
msgstr ""
+msgid "Unrecognized cluster type"
+msgstr ""
+
msgid "Unresolve"
msgstr ""
@@ -23832,6 +24588,9 @@ msgstr ""
msgid "Unsubscribes from this %{quick_action_target}."
msgstr ""
+msgid "Unsupported todo type passed. Supported todo types are: %{todo_types}"
+msgstr ""
+
msgid "Until"
msgstr ""
@@ -23859,6 +24618,9 @@ msgstr ""
msgid "Update approval rule"
msgstr ""
+msgid "Update approvers"
+msgstr ""
+
msgid "Update failed"
msgstr ""
@@ -23868,6 +24630,9 @@ msgstr ""
msgid "Update it"
msgstr ""
+msgid "Update iteration"
+msgstr ""
+
msgid "Update now"
msgstr ""
@@ -23913,7 +24678,7 @@ msgstr ""
msgid "Updated at"
msgstr ""
-msgid "Updated to"
+msgid "Updated to %{linkStart}chart v%{linkEnd}"
msgstr ""
msgid "Updating"
@@ -23943,6 +24708,9 @@ msgstr ""
msgid "Upload CSV file"
msgstr ""
+msgid "Upload License"
+msgstr ""
+
msgid "Upload New File"
msgstr ""
@@ -24024,16 +24792,19 @@ msgstr ""
msgid "UsageQuota|Pipelines"
msgstr ""
+msgid "UsageQuota|Purchase more storage"
+msgstr ""
+
msgid "UsageQuota|Repositories"
msgstr ""
msgid "UsageQuota|Repository"
msgstr ""
-msgid "UsageQuota|Storage"
+msgid "UsageQuota|Snippets"
msgstr ""
-msgid "UsageQuota|Storage usage:"
+msgid "UsageQuota|Storage"
msgstr ""
msgid "UsageQuota|This namespace has no projects which use shared runners"
@@ -24066,6 +24837,12 @@ msgstr ""
msgid "UsageQuota|Wikis"
msgstr ""
+msgid "UsageQuota|You used: %{usage} %{limit}"
+msgstr ""
+
+msgid "UsageQuota|out of %{formattedLimit} of your namespace storage"
+msgstr ""
+
msgid "Use %{code_start}::%{code_end} to create a %{link_start}scoped label set%{link_end} (eg. %{code_start}priority::1%{code_end})"
msgstr ""
@@ -24132,6 +24909,9 @@ msgstr ""
msgid "User IDs"
msgstr ""
+msgid "User Lists can only be created and modified with %{linkStart}the API%{linkEnd}"
+msgstr ""
+
msgid "User OAuth applications"
msgstr ""
@@ -24156,6 +24936,9 @@ msgstr ""
msgid "User key was successfully removed."
msgstr ""
+msgid "User list %{name} will be removed. Are you sure?"
+msgstr ""
+
msgid "User map"
msgstr ""
@@ -24177,148 +24960,10 @@ msgstr ""
msgid "User was successfully updated."
msgstr ""
-msgid "UserOnboardingTour|%{activeTour}/%{totalTours}"
-msgstr ""
-
-msgid "UserOnboardingTour|%{completed}/%{total} steps completed"
-msgstr ""
-
-msgid "UserOnboardingTour|%{emphasisStart}Well done!%{emphasisEnd}%{lineBreak}%{lineBreak}That's it for our guided tour, congratulations for making it all the way to the end!%{lineBreak}%{lineBreak}We hope this gave you a good overview of GitLab and how it can help you. We'll now show you how to create your own project and invite your colleagues."
-msgstr ""
-
-msgid "UserOnboardingTour|Adding other members to a project is done through Project Settings. Click on %{emphasisStart}Settings%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Alright, that's it for Commits. Let's take a look at the %{emphasisStart}Branches%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Awesome! Now click on %{emphasisStart}Members%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Click on one of the %{emphasisStart}Compare%{emphasisEnd} buttons to compare a branch to master."
-msgstr ""
-
-msgid "UserOnboardingTour|Click on one of the %{emphasisStart}pipeline IDs%{emphasisEnd} to see the details of a pipeline."
-msgstr ""
-
-msgid "UserOnboardingTour|Click to open the latest commit to see its details."
-msgstr ""
-
-msgid "UserOnboardingTour|Close 'Learn GitLab'"
-msgstr ""
-
-msgid "UserOnboardingTour|Commits are shown in chronological order and can be filtered by the commit message or by the branch."
+msgid "UserList|Delete %{name}?"
msgstr ""
-msgid "UserOnboardingTour|Create a project"
-msgstr ""
-
-msgid "UserOnboardingTour|Exit 'Learn GitLab'"
-msgstr ""
-
-msgid "UserOnboardingTour|Got it"
-msgstr ""
-
-msgid "UserOnboardingTour|Great job! %{clapHands} We hope the tour was helpful and that you learned how to use GitLab.%{lineBreak}%{lineBreak}We'd love to get your feedback on this tour.%{lineBreak}%{lineBreak}%{emphasisStart}How helpful would you say this guided tour was?%{emphasisEnd}%{lineBreak}%{lineBreak}"
-msgstr ""
-
-msgid "UserOnboardingTour|Guided GitLab Tour"
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can compare the changes of this branch to another one. Changes are divided by files so that it's easier to see what was changed where."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can create a project from scratch, start with a template or import a repository from other platforms. Whatever you choose, we'll guide you through the process.%{lineBreak}%{lineBreak}Fill in your new project information and click on %{emphasisStart}Create Project%{emphasisEnd} to progress to the next step."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can see the breakdown of the pipelines: its stages and jobs in each of the stages and their status.%{lineBreak}%{lineBreak}Our CI/CD pipelines are quite complex, most of our users have fewer and simpler pipelines."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can see the current members of the project (just you at the moment) and invite new members.%{lineBreak}%{lineBreak}You can invite multiple members at once (existing GitLab users or invite by email) and you can also set their roles and permissions.%{lineBreak}%{lineBreak}Add a few members and click on %{emphasisStart}Add to project%{emphasisEnd} to complete this step."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can see what changes were made with this commit, on what branch and if there's a related merge request. The status of the pipeline will also show up if CI/CD is set up.%{lineBreak}%{lineBreak}You can also comment on the lines of code that were changed and start a discussion with your colleagues!"
-msgstr ""
-
-msgid "UserOnboardingTour|Here's an overview of branches in the %{emphasisStart}%{projectName}%{emphasisEnd} project. They're split into Active and Stale.%{lineBreak}%{lineBreak}From here, you can create a new merge request from a branch, or compare the branch to any other branch in the project. By default, it will compare it to the master branch."
-msgstr ""
-
-msgid "UserOnboardingTour|Invite colleagues"
-msgstr ""
-
-msgid "UserOnboardingTour|Issues are great for communicating and keeping track of progress in GitLab. These are all issues that are open in the %{emphasisStart}%{projectName}%{emphasisEnd}.%{lineBreak}%{lineBreak}You can help us improve GitLab by contributing work to issues that are labeled <span class=\"badge color-label accept-mr-label\">Accepting merge requests</span>.%{lineBreak}%{lineBreak}This list can be filtered by labels, milestones, assignees, authors... We'll show you how it looks when the list is filtered by a label."
-msgstr ""
-
-msgid "UserOnboardingTour|Learn GitLab"
-msgstr ""
-
-msgid "UserOnboardingTour|Let's take a closer look at a merge request. Click on the title of one."
-msgstr ""
-
-msgid "UserOnboardingTour|Let's take a closer look at all the commits. Click on %{emphasisStart}Commits%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Let's take a closer look at the repository of this project. Click on %{emphasisStart}Repository%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|No thanks"
-msgstr ""
-
-msgid "UserOnboardingTour|Ok, let's go"
-msgstr ""
-
-msgid "UserOnboardingTour|Ok, show me"
-msgstr ""
-
-msgid "UserOnboardingTour|Open one of the issues by clicking on its title."
-msgstr ""
-
-msgid "UserOnboardingTour|Restart this step"
-msgstr ""
-
-msgid "UserOnboardingTour|Skip this step"
-msgstr ""
-
-msgid "UserOnboardingTour|Sweet! Your project was created and is ready to be used.%{lineBreak}%{lineBreak}You can start adding files to the repository or clone it. One last thing we want to show you is how to invite your colleagues to your new project."
-msgstr ""
-
-msgid "UserOnboardingTour|Take a look. Here's a nifty menu for quickly creating issues, merge requests, snippets, projects and groups. Click on it and select \"New project\" from the \"GitLab\" section to get started."
-msgstr ""
-
-msgid "UserOnboardingTour|Thanks for taking the guided tour. Remember, if you want to go through it again, you can start %{emphasisStart}Learn GitLab%{emphasisEnd} in the help menu on the top right."
-msgstr ""
-
-msgid "UserOnboardingTour|Thanks for the feedback! %{thumbsUp}"
-msgstr ""
-
-msgid "UserOnboardingTour|That's it for issues. Let'st take a look at %{emphasisStart}Merge Requests%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|That's it for merge requests. Now for the final part of this guided tour - the %{emphasisStart}CI/CD%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|That's it for the Repository. Let's take a look at the %{emphasisStart}Issues%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|The structure of this page is very similar to issues. Status, description, discussion and the sidebar are all here.%{lineBreak}%{lineBreak}But take a look below the description and you'll notice that there's more information about the merge request, the CI/CD pipeline and the options for approving it.%{lineBreak}%{lineBreak}Alongside the discussion you can also see more information about commits in this merge request, the status of pipelines and review all changes that were made."
-msgstr ""
-
-msgid "UserOnboardingTour|There's a lot of information here but don't worry, we'll go through it.%{lineBreak}%{lineBreak}On the top you can see the status of the issue and when it was opened and by whom. Directly below it is the issue description and below that are other %{emphasisStart}related issues%{emphasisEnd} and %{emphasisStart}merge requests%{emphasisEnd} (if any). Then below that is the %{emphasisStart}discussion%{emphasisEnd}, that's where most of the communication happens.%{lineBreak}%{lineBreak}On the right, there's a sidebar where you can view/change the %{emphasisStart}assignee, milestone, due date, labels, weight%{emphasisEnd}, etc."
-msgstr ""
-
-msgid "UserOnboardingTour|These are all the CI/CD pipelines we have for our %{emphasisStart}%{projectName}%{emphasisEnd} project.%{lineBreak}%{lineBreak}Here you can see the status of each pipeline, for what commit it's running for, its stages and the status for them."
-msgstr ""
-
-msgid "UserOnboardingTour|These are all the issues that are available for community contributions. Let's take a closer look at one of them."
-msgstr ""
-
-msgid "UserOnboardingTour|This is an overview of all merge requests in this project. Similarly to the issues overview it can be filtered down by things like labels, milestones, authors, assignees, etc."
-msgstr ""
-
-msgid "UserOnboardingTour|This is the repository for the %{emphasisStart}%{projectName}%{emphasisEnd} project. All our code is stored here. Feel free to explore and take a closer look at folders and files.%{lineBreak}%{lineBreak}Above the file structure you can see the latest commit, who the author is and the status of the CI/CD pipeline.%{lineBreak}%{lineBreak}If you scroll down below the file structure, you'll find the Readme of this project. This is defined in the README.md file at the root of the repository."
-msgstr ""
-
-msgid "UserOnboardingTour|Welcome to the project overview of the %{emphasisStart}%{projectName}%{emphasisEnd} project. This is the project that we use to work on GitLab. At first, a project seems like a simple repository, but at GitLab, a project is so much more.%{lineBreak}%{lineBreak}You can create projects for hosting your codebase, use it as an issue tracker, collaborate on code, and continuously build, test, and deploy your app with built-in GitLab CI/CD."
+msgid "UserList|created %{timeago}"
msgstr ""
msgid "UserProfile|Activity"
@@ -24471,6 +25116,9 @@ msgstr ""
msgid "UsersSelect|Unassigned"
msgstr ""
+msgid "Using %{codeStart}needs%{codeEnd} allows jobs to run before their stage is reached, as soon as their individual dependencies are met, which speeds up your pipelines."
+msgstr ""
+
msgid "Using %{code_start}::%{code_end} denotes a %{link_start}scoped label set%{link_end}"
msgstr ""
@@ -24555,9 +25203,6 @@ msgstr ""
msgid "Versions"
msgstr ""
-msgid "Very helpful"
-msgstr ""
-
msgid "View Documentation"
msgstr ""
@@ -24706,6 +25351,9 @@ msgstr ""
msgid "VisibilityLevel|Unknown"
msgstr ""
+msgid "Visit settings page"
+msgstr ""
+
msgid "VisualReviewApp|%{stepStart}Step 1%{stepEnd}. Copy the following script:"
msgstr ""
@@ -24799,6 +25447,9 @@ msgstr ""
msgid "VulnerabilityManagement|Something went wrong while trying to delete the comment. Please try again later."
msgstr ""
+msgid "VulnerabilityManagement|Something went wrong while trying to refresh the vulnerability. Please try again later."
+msgstr ""
+
msgid "VulnerabilityManagement|Something went wrong while trying to retrieve the vulnerability history. Please try again later."
msgstr ""
@@ -24841,6 +25492,9 @@ msgstr ""
msgid "Vulnerability|Class"
msgstr ""
+msgid "Vulnerability|Crash Address"
+msgstr ""
+
msgid "Vulnerability|Description"
msgstr ""
@@ -24916,9 +25570,6 @@ msgstr ""
msgid "We couldn't reach the Prometheus server. Either the server no longer exists or the configuration details need updating."
msgstr ""
-msgid "We created a short guided tour that will help you learn the basics of GitLab and how it will help you be better at your job. It should only take a couple of minutes. You will be guided by two types of helpers, best recognized by their color."
-msgstr ""
-
msgid "We detected potential spam in the %{humanized_resource_name}. Please solve the reCAPTCHA to proceed."
msgstr ""
@@ -24952,6 +25603,9 @@ msgstr ""
msgid "We've found no vulnerabilities"
msgstr ""
+msgid "Web Application Firewall"
+msgstr ""
+
msgid "Web IDE"
msgstr ""
@@ -25012,9 +25666,6 @@ msgstr ""
msgid "Welcome to GitLab.com<br>@%{name}!"
msgstr ""
-msgid "Welcome to the Guided GitLab Tour"
-msgstr ""
-
msgid "Welcome to the guided GitLab tour"
msgstr ""
@@ -25062,9 +25713,6 @@ msgstr ""
msgid "While it's rare to have no vulnerabilities, it can happen. In any event, we ask that you please double check your settings to make sure you've set up your dashboard correctly."
msgstr ""
-msgid "White helpers give contextual information."
-msgstr ""
-
msgid "Who can be an approver?"
msgstr ""
@@ -25110,12 +25758,18 @@ msgstr ""
msgid "WikiEmptyIssueMessage|Suggest wiki improvement"
msgstr ""
+msgid "WikiEmptyIssueMessage|You must be a group member in order to add wiki pages. If you have suggestions for how to improve the wiki for this group, consider opening an issue in the %{issues_link}."
+msgstr ""
+
msgid "WikiEmptyIssueMessage|You must be a project member in order to add wiki pages. If you have suggestions for how to improve the wiki for this project, consider opening an issue in the %{issues_link}."
msgstr ""
msgid "WikiEmptyIssueMessage|issue tracker"
msgstr ""
+msgid "WikiEmpty|A wiki is where you can store all the details about your group. This can include why you've created it, its principles, how to use it, and so on."
+msgstr ""
+
msgid "WikiEmpty|A wiki is where you can store all the details about your project. This can include why you've created it, its principles, how to use it, and so on."
msgstr ""
@@ -25125,12 +25779,21 @@ msgstr ""
msgid "WikiEmpty|Suggest wiki improvement"
msgstr ""
+msgid "WikiEmpty|The wiki lets you write documentation for your group"
+msgstr ""
+
msgid "WikiEmpty|The wiki lets you write documentation for your project"
msgstr ""
+msgid "WikiEmpty|This group has no wiki pages"
+msgstr ""
+
msgid "WikiEmpty|This project has no wiki pages"
msgstr ""
+msgid "WikiEmpty|You must be a group member in order to add wiki pages."
+msgstr ""
+
msgid "WikiEmpty|You must be a project member in order to add wiki pages."
msgstr ""
@@ -25407,13 +26070,13 @@ msgstr ""
msgid "You can now export your security dashboard to a CSV report."
msgstr ""
-msgid "You can now submit a merge request to get this change into the original branch."
+msgid "You can now manage alert endpoint configuration in the Alerts section on the Operations settings page. Fields on this page have been deprecated."
msgstr ""
-msgid "You can now submit a merge request to get this change into the original project."
+msgid "You can now submit a merge request to get this change into the original branch."
msgstr ""
-msgid "You can only add files when you are on a branch"
+msgid "You can now submit a merge request to get this change into the original project."
msgstr ""
msgid "You can only edit files when you are on a branch"
@@ -25635,7 +26298,7 @@ msgstr ""
msgid "You need to upload a Google Takeout archive."
msgstr ""
-msgid "You reached %{usage_in_percent} of %{namespace_name}'s capacity (%{used_storage} of %{storage_limit})"
+msgid "You reached %{usage_in_percent} of %{namespace_name}'s storage capacity (%{used_storage} of %{storage_limit})"
msgstr ""
msgid "You tried to fork %{link_to_the_project} but it failed for the following reason:"
@@ -25938,13 +26601,13 @@ msgstr ""
msgid "Your subscription expired!"
msgstr ""
-msgid "Your subscription has been downgraded"
+msgid "Your subscription has been downgraded."
msgstr ""
-msgid "Your subscription will automatically renew in %{remaining_days}"
+msgid "Your subscription will automatically renew in %{remaining_days}."
msgstr ""
-msgid "Your subscription will expire in %{remaining_days}"
+msgid "Your subscription will expire in %{remaining_days}."
msgstr ""
msgid "Zoom meeting added"
@@ -26024,9 +26687,6 @@ msgstr ""
msgid "branch name"
msgstr ""
-msgid "build pipeline reference mismatch"
-msgstr ""
-
msgid "by"
msgstr ""
@@ -26045,6 +26705,9 @@ msgstr ""
msgid "cannot block others"
msgstr ""
+msgid "cannot contain HTML/XML tags, including any word between angle brackets (<,>)."
+msgstr ""
+
msgid "cannot include leading slash or directory traversal."
msgstr ""
@@ -26075,55 +26738,6 @@ msgstr ""
msgid "ciReport|%{remainingPackagesCount} more"
msgstr ""
-msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability"
-msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability for the source branch only"
-msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities for the source branch only"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerability"
-msgid_plural "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerabilities"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new vulnerability"
-msgid_plural "ciReport|%{reportType} %{status} detected %{newCount} new vulnerabilities"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities for the source branch only"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{fixedCount} fixed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} vulnerability for the source branch only"
-msgid_plural "ciReport|%{reportType} %{status} detected %{newCount} vulnerabilities for the source branch only"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "ciReport|%{reportType} %{status} detected no new vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected no vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected no vulnerabilities for the source branch only"
-msgstr ""
-
msgid "ciReport|%{reportType} is loading"
msgstr ""
@@ -26142,7 +26756,7 @@ msgstr ""
msgid "ciReport|All projects"
msgstr ""
-msgid "ciReport|All report types"
+msgid "ciReport|All scanner types"
msgstr ""
msgid "ciReport|All severities"
@@ -26166,6 +26780,9 @@ msgstr ""
msgid "ciReport|Container scanning detects known vulnerabilities in your docker images."
msgstr ""
+msgid "ciReport|Coverage Fuzzing"
+msgstr ""
+
msgid "ciReport|Create a merge request to implement this solution, or download and apply the patch manually."
msgstr ""
@@ -26324,6 +26941,9 @@ msgstr ""
msgid "customize"
msgstr ""
+msgid "data"
+msgstr ""
+
msgid "date must not be after 9999-12-31"
msgstr ""
@@ -26394,6 +27014,9 @@ msgstr ""
msgid "entries cannot contain HTML tags"
msgstr ""
+msgid "epic"
+msgstr ""
+
msgid "error"
msgstr ""
@@ -26415,9 +27038,6 @@ msgstr ""
msgid "expires on %{timebox_due_date}"
msgstr ""
-msgid "external_url"
-msgstr ""
-
msgid "failed"
msgstr ""
@@ -26453,9 +27073,6 @@ msgstr ""
msgid "from"
msgstr ""
-msgid "geo_node_name"
-msgstr ""
-
msgid "group"
msgstr ""
@@ -26477,9 +27094,6 @@ msgstr ""
msgid "https://your-bitbucket-server"
msgstr ""
-msgid "image"
-msgstr ""
-
msgid "image diff"
msgstr ""
@@ -26623,6 +27237,9 @@ msgstr ""
msgid "load it anyway"
msgstr ""
+msgid "loading"
+msgstr ""
+
msgid "locked by %{path_lock_user_name} %{created_at}"
msgstr ""
@@ -26751,9 +27368,6 @@ msgstr ""
msgid "mrWidget|Deployment statistics are not available currently"
msgstr ""
-msgid "mrWidget|Detect issues before deployment with a CI pipeline that continuously tests your code. We created a quick guide that will show you how to create one. Make your code more secure and more robust in just a minute."
-msgstr ""
-
msgid "mrWidget|Did not close"
msgstr ""
@@ -26916,6 +27530,9 @@ msgstr ""
msgid "mrWidget|To approve this merge request, please enter your password. This project requires all approvals to be authenticated."
msgstr ""
+msgid "mrWidget|Use %{linkStart}CI pipelines to test your code%{linkEnd}, simply add a GitLab CI configuration file to your project. It only takes a minute to make your code more secure and robust."
+msgstr ""
+
msgid "mrWidget|When this merge request is ready, remove the WIP: prefix from the title to allow it to be merged"
msgstr ""
@@ -26952,6 +27569,9 @@ msgstr ""
msgid "must be greater than start date"
msgstr ""
+msgid "must contain only valid frameworks"
+msgstr ""
+
msgid "my-awesome-group"
msgstr ""
@@ -26964,6 +27584,9 @@ msgstr ""
msgid "needs to be between 10 minutes and 1 month"
msgstr ""
+msgid "never"
+msgstr ""
+
msgid "never expires"
msgstr ""
@@ -26991,6 +27614,9 @@ msgstr ""
msgid "nounSeries|%{firstItem} and %{lastItem}"
msgstr ""
+msgid "nounSeries|%{item}"
+msgstr ""
+
msgid "nounSeries|%{item}, %{nextItem}"
msgstr ""
@@ -27127,6 +27753,9 @@ msgstr ""
msgid "revised"
msgstr ""
+msgid "satisfied"
+msgstr ""
+
msgid "score"
msgstr ""
@@ -27157,6 +27786,9 @@ msgstr ""
msgid "severity|Unknown"
msgstr ""
+msgid "should be an array of %{object_name} objects"
+msgstr ""
+
msgid "should be greater than or equal to %{access} inherited membership from group %{group_name}"
msgstr ""
@@ -27211,10 +27843,13 @@ msgstr ""
msgid "suggestPipeline|2/2: Commit your changes"
msgstr ""
-msgid "suggestPipeline|Commit the changes and your pipeline will automatically run for the first time."
+msgid "suggestPipeline|Choose %{boldStart}Code Quality%{boldEnd} to add a pipeline that tests the quality of your code."
msgstr ""
-msgid "suggestPipeline|We recommend the %{boldStart}Code Quality%{boldEnd} template, which will add a report widget to your Merge Requests. This way you’ll learn about code quality degradations much sooner. %{footerStart} Goodbye technical debt! %{footerEnd}"
+msgid "suggestPipeline|The template is ready! You can now commit it to create your first pipeline."
+msgstr ""
+
+msgid "suggestPipeline|We’re adding a GitLab CI configuration file to add a pipeline to the project. You could create it manually, but we recommend that you start with a GitLab template that works out of the box."
msgstr ""
msgid "syntax is correct"
@@ -27274,6 +27909,9 @@ msgstr ""
msgid "uses Kubernetes clusters to deploy your code!"
msgstr ""
+msgid "v%{version} published %{timeAgo}"
+msgstr ""
+
msgid "verify ownership"
msgstr ""
diff --git a/locale/bs_BA/gitlab.po b/locale/bs_BA/gitlab.po
index 6d09e32936f..474121075e9 100644
--- a/locale/bs_BA/gitlab.po
+++ b/locale/bs_BA/gitlab.po
@@ -2,7 +2,7 @@ msgid ""
msgstr ""
"Project-Id-Version: gitlab-ee\n"
"Report-Msgid-Bugs-To: \n"
-"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Last-Translator: \n"
"Language-Team: Bosnian\n"
"Language: bs_BA\n"
"MIME-Version: 1.0\n"
@@ -10,9 +10,11 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
"X-Crowdin-Project: gitlab-ee\n"
+"X-Crowdin-Project-ID: 288872\n"
"X-Crowdin-Language: bs\n"
"X-Crowdin-File: /master/locale/gitlab.pot\n"
-"PO-Revision-Date: 2020-06-08 15:12\n"
+"X-Crowdin-File-ID: 6\n"
+"PO-Revision-Date: 2020-07-02 01:22\n"
msgid " %{start} to %{end}"
msgstr ""
@@ -24,7 +26,7 @@ msgid " Collected %{time}"
msgstr ""
msgid " Please sign in."
-msgstr ""
+msgstr " Prijavi se."
msgid " Try to %{action} this file again."
msgstr ""
@@ -33,13 +35,13 @@ msgid " You need to do this before %{grace_period_deadline}."
msgstr ""
msgid " and"
-msgstr ""
+msgstr " i"
msgid " and "
-msgstr ""
+msgstr " i "
msgid " and %{sliced}"
-msgstr ""
+msgstr " i %{sliced}"
msgid " degraded on %d point"
msgid_plural " degraded on %d points"
@@ -148,9 +150,9 @@ msgstr[2] ""
msgid "%d day"
msgid_plural "%d days"
-msgstr[0] ""
-msgstr[1] ""
-msgstr[2] ""
+msgstr[0] "%d dan"
+msgstr[1] "%d dana"
+msgstr[2] "%d dana"
msgid "%d error"
msgid_plural "%d errors"
@@ -178,9 +180,9 @@ msgstr[2] ""
msgid "%d group selected"
msgid_plural "%d groups selected"
-msgstr[0] ""
-msgstr[1] ""
-msgstr[2] ""
+msgstr[0] "%d grupa odabrana"
+msgstr[1] "%d grupe odabrane"
+msgstr[2] "%d grupa odabrano"
msgid "%d inaccessible merge request"
msgid_plural "%d inaccessible merge requests"
@@ -224,6 +226,12 @@ msgstr[0] ""
msgstr[1] ""
msgstr[2] ""
+msgid "%d milestone"
+msgid_plural "%d milestones"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+
msgid "%d minute"
msgid_plural "%d minutes"
msgstr[0] "%d minuta"
@@ -244,9 +252,9 @@ msgstr[2] ""
msgid "%d project"
msgid_plural "%d projects"
-msgstr[0] ""
-msgstr[1] ""
-msgstr[2] ""
+msgstr[0] "%d projekat"
+msgstr[1] "%d projekta"
+msgstr[2] "%d projekata"
msgid "%d request with warnings"
msgid_plural "%d requests with warnings"
@@ -278,12 +286,6 @@ msgstr[0] ""
msgstr[1] ""
msgstr[2] ""
-msgid "%d url scanned"
-msgid_plural "%d urls scanned"
-msgstr[0] ""
-msgstr[1] ""
-msgstr[2] ""
-
msgid "%d vulnerability"
msgid_plural "%d vulnerabilities"
msgstr[0] ""
@@ -368,9 +370,15 @@ msgstr[2] ""
msgid "%{count} related %{pluralized_subject}: %{links}"
msgstr ""
+msgid "%{dashboard_path} could not be found."
+msgstr ""
+
msgid "%{days} days until tags are automatically removed"
msgstr ""
+msgid "%{deployLinkStart}Use a template to deploy to ECS%{deployLinkEnd}, or use a docker image to %{commandsLinkStart}run AWS commands in GitLab CI/CD%{commandsLinkEnd}."
+msgstr ""
+
msgid "%{description}- Sentry event: %{errorUrl}- First seen: %{firstSeen}- Last seen: %{lastSeen} %{countLabel}: %{count}%{userCountLabel}: %{userCount}"
msgstr ""
@@ -413,12 +421,18 @@ msgstr ""
msgid "%{host} sign-in from new location"
msgstr ""
-msgid "%{icon}You are about to add %{usersTag} people to the discussion. Proceed with caution."
+msgid "%{icon}You are about to add %{usersTag} people to the discussion. They will all receive a notification."
+msgstr ""
+
+msgid "%{integrations_link_start}Integrations%{link_end} enable you to make third-party applications part of your GitLab workflow. If the available integrations don't meet your needs, consider using a %{webhooks_link_start}webhook%{link_end}."
msgstr ""
msgid "%{issuableType} will be removed! Are you sure?"
msgstr ""
+msgid "%{issuesCount} issues in this group"
+msgstr ""
+
msgid "%{issuesSize} issues"
msgstr ""
@@ -428,7 +442,7 @@ msgstr ""
msgid "%{labelStart}Class:%{labelEnd} %{class}"
msgstr ""
-msgid "%{labelStart}Confidence:%{labelEnd} %{confidence}"
+msgid "%{labelStart}Evidence:%{labelEnd} %{evidence}"
msgstr ""
msgid "%{labelStart}File:%{labelEnd} %{file}"
@@ -446,6 +460,9 @@ msgstr ""
msgid "%{labelStart}Report Type:%{labelEnd} %{reportType}"
msgstr ""
+msgid "%{labelStart}Scanner:%{labelEnd} %{scanner}"
+msgstr ""
+
msgid "%{labelStart}Severity:%{labelEnd} %{severity}"
msgstr ""
@@ -470,9 +487,6 @@ msgstr ""
msgid "%{link_start}Read more%{link_end} about role permissions"
msgstr ""
-msgid "%{link} can be used for binding events when something is happening within the project."
-msgstr ""
-
msgid "%{listToShow}, and %{awardsListLength} more."
msgstr ""
@@ -506,6 +520,9 @@ msgstr ""
msgid "%{name} found %{resultsString}"
msgstr ""
+msgid "%{name} is already being used for another emoji"
+msgstr ""
+
msgid "%{name} is scheduled for %{action}"
msgstr ""
@@ -531,7 +548,7 @@ msgid "%{openOrClose} %{noteable}"
msgstr ""
msgid "%{openedEpics} open, %{closedEpics} closed"
-msgstr ""
+msgstr "%{openedEpics} otvoreno, %{closedEpics} zatvoreno"
msgid "%{openedIssues} open, %{closedIssues} closed"
msgstr ""
@@ -554,6 +571,9 @@ msgstr ""
msgid "%{primary} (%{secondary})"
msgstr ""
+msgid "%{ref} cannot be added: %{error}"
+msgstr ""
+
msgid "%{releases} release"
msgid_plural "%{releases} releases"
msgstr[0] ""
@@ -563,13 +583,40 @@ msgstr[2] ""
msgid "%{remaining_approvals} left"
msgstr ""
-msgid "%{retryButtonStart}Try again%{retryButtonEnd} or %{newFileButtonStart}attach a new file%{newFileButtonEnd}"
+msgid "%{reportType} %{status} detected %{critical} critical and %{high} high severity vulnerabilities out of %{total}."
+msgstr ""
+
+msgid "%{reportType} %{status} detected %{critical} critical and %{high} high severity vulnerabilities."
msgstr ""
-msgid "%{screenreaderOnlyStart}Keyboard shorcuts%{screenreaderOnlyEnd} Disabled"
+msgid "%{reportType} %{status} detected %{critical} critical severity vulnerabilities out of %{total}."
msgstr ""
-msgid "%{screenreaderOnlyStart}Keyboard shorcuts%{screenreaderOnlyEnd} Enabled"
+msgid "%{reportType} %{status} detected %{critical} critical severity vulnerability."
+msgid_plural "%{reportType} %{status} detected %{critical} critical severity vulnerabilities."
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+
+msgid "%{reportType} %{status} detected %{high} high severity vulnerabilities out of %{total}."
+msgstr ""
+
+msgid "%{reportType} %{status} detected %{high} high severity vulnerability."
+msgid_plural "%{reportType} %{status} detected %{high} high severity vulnerabilities."
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+
+msgid "%{reportType} %{status} detected %{other} vulnerability."
+msgid_plural "%{reportType} %{status} detected %{other} vulnerabilities."
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+
+msgid "%{reportType} %{status} detected no new vulnerabilities."
+msgstr ""
+
+msgid "%{retryButtonStart}Try again%{retryButtonEnd} or %{newFileButtonStart}attach a new file%{newFileButtonEnd}"
msgstr ""
msgid "%{service_title} %{message}."
@@ -671,6 +718,12 @@ msgstr ""
msgid "%{token}..."
msgstr ""
+msgid "%{totalCpu} (%{freeSpacePercentage}%{percentSymbol} free)"
+msgstr ""
+
+msgid "%{totalMemory} (%{freeSpacePercentage}%{percentSymbol} free)"
+msgstr ""
+
msgid "%{totalWeight} total weight"
msgstr ""
@@ -701,6 +754,12 @@ msgstr ""
msgid "%{verb} %{time_spent_value} spent time."
msgstr ""
+msgid "%{webhooks_link_start}%{webhook_type}%{link_end} enable you to send notifications to web applications in response to events in a group or project."
+msgstr ""
+
+msgid "%{webhooks_link_start}%{webhook_type}%{link_end} enable you to send notifications to web applications in response to events in a group or project. We recommend using an %{integrations_link_start}integration%{link_end} in preference to a webhook."
+msgstr ""
+
msgid "'%{level}' is not a valid visibility level"
msgstr ""
@@ -728,9 +787,6 @@ msgstr ""
msgid "(No changes)"
msgstr ""
-msgid "(Show all)"
-msgstr ""
-
msgid "(check progress)"
msgstr ""
@@ -771,7 +827,7 @@ msgid "+%{tags} more"
msgstr ""
msgid ", or "
-msgstr ""
+msgstr " ili "
msgid "- Event"
msgid_plural "- Events"
@@ -800,6 +856,9 @@ msgstr ""
msgid "0 for unlimited"
msgstr ""
+msgid "0 for unlimited, only effective with remote storage enabled."
+msgstr ""
+
msgid "1 %{type} addition"
msgid_plural "%{count} %{type} additions"
msgstr[0] ""
@@ -962,6 +1021,12 @@ msgstr ""
msgid "<code>\"johnsmith@example.com\": \"johnsmith@example.com\"</code> will add \"By <a href=\"#\">johnsmith@example.com</a>\" to all issues and comments originally created by johnsmith@example.com. By default, the email address or username is masked to ensure the user's privacy. Use this option if you want to show the full email address."
msgstr ""
+msgid "<code>Masked</code> to prevent the values from being displayed in job logs (must match certain regexp requirements)."
+msgstr ""
+
+msgid "<code>Protected</code> to expose them to protected branches or tags only."
+msgstr ""
+
msgid "<namespace / project>"
msgstr ""
@@ -972,7 +1037,7 @@ msgid "<no scopes selected>"
msgstr ""
msgid "<project name>"
-msgstr ""
+msgstr "<naziv projekta>"
msgid "<strong>%{group_name}</strong> group members"
msgstr ""
@@ -989,9 +1054,6 @@ msgstr ""
msgid "A .NET Core console application template, customizable for any .NET Core project"
msgstr ""
-msgid "A DAG must have two dependent jobs to be visualized on this tab."
-msgstr ""
-
msgid "A GitBook site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
msgstr ""
@@ -1022,6 +1084,12 @@ msgstr ""
msgid "A deleted user"
msgstr ""
+msgid "A file has been changed."
+msgstr ""
+
+msgid "A file was not found."
+msgstr ""
+
msgid "A file with '%{file_name}' already exists in %{branch} branch"
msgstr ""
@@ -1088,7 +1156,7 @@ msgstr ""
msgid "A subscription will trigger a new pipeline on the default branch of this project when a pipeline successfully completes for a new tag on the %{default_branch_docs} of the subscribed project."
msgstr ""
-msgid "A terraform report was generated in your pipelines."
+msgid "A suggestion is not applicable."
msgstr ""
msgid "A user with write access to the source branch selected this option"
@@ -1259,6 +1327,9 @@ msgstr ""
msgid "Action to take when receiving an alert."
msgstr ""
+msgid "Actions"
+msgstr ""
+
msgid "Activate"
msgstr ""
@@ -1316,6 +1387,9 @@ msgstr ""
msgid "Add LICENSE"
msgstr ""
+msgid "Add New Node"
+msgstr ""
+
msgid "Add README"
msgstr ""
@@ -1391,6 +1465,9 @@ msgstr ""
msgid "Add approval rule"
msgstr ""
+msgid "Add approvers"
+msgstr ""
+
msgid "Add bold text"
msgstr ""
@@ -1448,6 +1525,9 @@ msgstr ""
msgid "Add strikethrough text"
msgstr ""
+msgid "Add suggestion to batch"
+msgstr ""
+
msgid "Add system hook"
msgstr ""
@@ -1547,6 +1627,9 @@ msgstr ""
msgid "Admin Note"
msgstr ""
+msgid "Admin Notifications"
+msgstr ""
+
msgid "Admin Overview"
msgstr ""
@@ -1658,6 +1741,9 @@ msgstr ""
msgid "AdminSettings|Integrations configured here will automatically apply to all projects on this instance."
msgstr ""
+msgid "AdminSettings|Moved to integrations"
+msgstr ""
+
msgid "AdminSettings|No required pipeline"
msgstr ""
@@ -1889,12 +1975,18 @@ msgstr[0] ""
msgstr[1] ""
msgstr[2] ""
+msgid "AlertManagement| assign yourself"
+msgstr ""
+
msgid "AlertManagement|Acknowledged"
msgstr ""
msgid "AlertManagement|Alert"
msgstr ""
+msgid "AlertManagement|Alert assignee(s): %{assignees}"
+msgstr ""
+
msgid "AlertManagement|Alert detail"
msgstr ""
@@ -1910,9 +2002,15 @@ msgstr ""
msgid "AlertManagement|All alerts"
msgstr ""
+msgid "AlertManagement|Assign To"
+msgstr ""
+
msgid "AlertManagement|Assign status"
msgstr ""
+msgid "AlertManagement|Assignee"
+msgstr ""
+
msgid "AlertManagement|Assignees"
msgstr ""
@@ -1931,9 +2029,6 @@ msgstr ""
msgid "AlertManagement|Edit"
msgstr ""
-msgid "AlertManagement|End time"
-msgstr ""
-
msgid "AlertManagement|Events"
msgstr ""
@@ -1952,10 +2047,13 @@ msgstr ""
msgid "AlertManagement|More information"
msgstr ""
+msgid "AlertManagement|No Matching Results"
+msgstr ""
+
msgid "AlertManagement|No alert data to display."
msgstr ""
-msgid "AlertManagement|No alerts available to display. If you think you're seeing this message in error, refresh the page."
+msgid "AlertManagement|No alerts available to display. See %{linkStart}enabling alert management%{linkEnd} for more information on adding alerts to the list."
msgstr ""
msgid "AlertManagement|No alerts to display."
@@ -1964,6 +2062,9 @@ msgstr ""
msgid "AlertManagement|None"
msgstr ""
+msgid "AlertManagement|None -"
+msgstr ""
+
msgid "AlertManagement|Open"
msgstr ""
@@ -2000,9 +2101,18 @@ msgstr ""
msgid "AlertManagement|There was an error displaying the alerts. Confirm your endpoint's configuration details to ensure alerts appear."
msgstr ""
+msgid "AlertManagement|There was an error while updating the assignee(s) list. Please try again."
+msgstr ""
+
+msgid "AlertManagement|There was an error while updating the assignee(s) of the alert. Please try again."
+msgstr ""
+
msgid "AlertManagement|There was an error while updating the status of the alert. Please try again."
msgstr ""
+msgid "AlertManagement|This assignee cannot be assigned to this alert."
+msgstr ""
+
msgid "AlertManagement|Tool"
msgstr ""
@@ -2082,7 +2192,7 @@ msgid "All paths are relative to the GitLab URL. Do not include %{relative_url_l
msgstr ""
msgid "All projects"
-msgstr ""
+msgstr "Svi projekti"
msgid "All security scans are enabled because %{linkStart}Auto DevOps%{linkEnd} is enabled on this project"
msgstr ""
@@ -2150,6 +2260,9 @@ msgstr ""
msgid "Allow users to request access (if visibility is public or internal)"
msgstr ""
+msgid "Allowed Geo IP"
+msgstr ""
+
msgid "Allowed email domain restriction only permitted for top-level groups"
msgstr ""
@@ -2183,6 +2296,9 @@ msgstr ""
msgid "Amazon Web Services"
msgstr ""
+msgid "Amazon Web Services Logo"
+msgstr ""
+
msgid "Amazon authentication is not %{link_start}correctly configured%{link_end}. Ask your GitLab administrator if you want to use this service."
msgstr ""
@@ -2240,10 +2356,16 @@ msgstr ""
msgid "An error occurred when updating the issue weight"
msgstr ""
+msgid "An error occurred while acknowledging the notification. Refresh the page and try again."
+msgstr ""
+
+msgid "An error occurred while adding approvers"
+msgstr ""
+
msgid "An error occurred while adding formatted title for epic"
msgstr ""
-msgid "An error occurred while checking group path"
+msgid "An error occurred while checking group path. Please refresh and try again."
msgstr ""
msgid "An error occurred while committing your changes."
@@ -2387,7 +2509,7 @@ msgstr ""
msgid "An error occurred while loading milestones"
msgstr ""
-msgid "An error occurred while loading terraform report"
+msgid "An error occurred while loading project creation UI"
msgstr ""
msgid "An error occurred while loading the data. Please try again."
@@ -2441,6 +2563,9 @@ msgstr ""
msgid "An error occurred while rendering preview broadcast message"
msgstr ""
+msgid "An error occurred while rendering the editor"
+msgstr ""
+
msgid "An error occurred while reordering issues."
msgstr ""
@@ -2537,7 +2662,7 @@ msgstr ""
msgid "Analyze your dependencies for known vulnerabilities."
msgstr ""
-msgid "Analyze your source code and git history for secrets"
+msgid "Analyze your source code and git history for secrets."
msgstr ""
msgid "Analyze your source code for known vulnerabilities."
@@ -2651,12 +2776,18 @@ msgstr ""
msgid "Apply suggestion"
msgstr ""
+msgid "Apply suggestions"
+msgstr ""
+
msgid "Apply template"
msgstr ""
msgid "Apply this approval rule to any branch or a specific protected branch."
msgstr ""
+msgid "Applying"
+msgstr ""
+
msgid "Applying a template will replace the existing issue description. Any changes you have made will be lost."
msgstr ""
@@ -2669,12 +2800,18 @@ msgstr ""
msgid "Applying multiple commands"
msgstr ""
-msgid "Applying suggestion"
+msgid "Applying suggestion..."
+msgstr ""
+
+msgid "Applying suggestions..."
msgstr ""
msgid "Approval rules"
msgstr ""
+msgid "Approval rules reset to project defaults"
+msgstr ""
+
msgid "ApprovalRuleRemove|%d member"
msgid_plural "ApprovalRuleRemove|%d members"
msgstr[0] ""
@@ -2732,6 +2869,9 @@ msgstr ""
msgid "Approved"
msgstr ""
+msgid "Approved MRs"
+msgstr ""
+
msgid "Approved by: "
msgstr ""
@@ -2945,6 +3085,9 @@ msgstr ""
msgid "Assign"
msgstr ""
+msgid "Assign Iteration"
+msgstr ""
+
msgid "Assign custom color like #FF0000"
msgstr ""
@@ -2963,6 +3106,9 @@ msgstr ""
msgid "Assign to"
msgstr ""
+msgid "Assign to commenting user"
+msgstr ""
+
msgid "Assign yourself to these issues"
msgstr "Zaduži sebi ove zadatke"
@@ -2986,9 +3132,12 @@ msgstr ""
msgid "Assignee"
msgid_plural "%d Assignees"
-msgstr[0] ""
-msgstr[1] ""
-msgstr[2] ""
+msgstr[0] "Zaduženi"
+msgstr[1] "%d Zadužena"
+msgstr[2] "%d Zaduženih"
+
+msgid "Assignee has no permissions"
+msgstr ""
msgid "Assignee lists not available with your current license"
msgstr ""
@@ -2999,6 +3148,9 @@ msgstr ""
msgid "Assignee(s)"
msgstr ""
+msgid "Assignees"
+msgstr ""
+
msgid "Assigns %{assignee_users_sentence}."
msgstr ""
@@ -3035,6 +3187,9 @@ msgstr ""
msgid "Audit Events is a way to keep track of important events that happened in GitLab."
msgstr ""
+msgid "Audit Log"
+msgstr ""
+
msgid "AuditEvents|(removed)"
msgstr ""
@@ -3071,6 +3226,9 @@ msgstr ""
msgid "AuditLogs|IP Address"
msgstr ""
+msgid "AuditLogs|Member Events"
+msgstr ""
+
msgid "AuditLogs|No matching %{type} found."
msgstr ""
@@ -3159,7 +3317,7 @@ msgid "Authors: %{authors}"
msgstr ""
msgid "Auto DevOps"
-msgstr ""
+msgstr "Auto DevOps"
msgid "Auto DevOps enabled"
msgstr ""
@@ -3353,9 +3511,6 @@ msgstr ""
msgid "Badges|e.g. %{exampleUrl}"
msgstr ""
-msgid "Badge|New"
-msgstr ""
-
msgid "Balsamiq file could not be loaded."
msgstr ""
@@ -3404,9 +3559,15 @@ msgstr ""
msgid "Below you will find all the groups that are public."
msgstr ""
-msgid "Billing"
+msgid "Beta"
+msgstr ""
+
+msgid "Bi-weekly code coverage"
msgstr ""
+msgid "Billing"
+msgstr "Naplata"
+
msgid "BillingPlans|%{group_name} is currently using the %{plan_name} plan."
msgstr ""
@@ -3464,9 +3625,15 @@ msgstr ""
msgid "Bitbucket Server Import"
msgstr ""
+msgid "Bitbucket Server import"
+msgstr ""
+
msgid "Bitbucket import"
msgstr ""
+msgid "Blame"
+msgstr ""
+
msgid "Blocked"
msgstr ""
@@ -3479,9 +3646,6 @@ msgstr ""
msgid "Blog"
msgstr ""
-msgid "Blue helpers indicate an action to be taken."
-msgstr ""
-
msgid "Board name"
msgstr ""
@@ -3522,7 +3686,7 @@ msgid "Boards|View scope"
msgstr ""
msgid "Branch"
-msgstr ""
+msgstr "Grana"
msgid "Branch %{branchName} was not found in this project's repository."
msgstr ""
@@ -3552,7 +3716,7 @@ msgid "BranchSwitcherTitle|Switch branch"
msgstr ""
msgid "Branches"
-msgstr ""
+msgstr "Grane"
msgid "Branches|Active"
msgstr ""
@@ -3680,6 +3844,9 @@ msgstr ""
msgid "Broadcast Message was successfully updated."
msgstr ""
+msgid "Broadcast Messages"
+msgstr ""
+
msgid "Browse Directory"
msgstr ""
@@ -3734,6 +3901,9 @@ msgstr ""
msgid "By %{user_name}"
msgstr ""
+msgid "By URL"
+msgstr ""
+
msgid "By default GitLab sends emails in HTML and plain text formats so mail clients can choose what format to use. Disable this option if you only want to send emails in plain text format."
msgstr ""
@@ -3768,7 +3938,7 @@ msgid "CI will run using the credentials assigned above."
msgstr ""
msgid "CI/CD"
-msgstr ""
+msgstr "CI/CD"
msgid "CI/CD configuration"
msgstr ""
@@ -3839,6 +4009,9 @@ msgstr ""
msgid "Can override approvers and approvals required per merge request"
msgstr ""
+msgid "Can't apply as this line has changed or the suggestion already matches its content."
+msgstr ""
+
msgid "Can't create snippet: %{err}"
msgstr ""
@@ -4025,9 +4198,6 @@ msgstr ""
msgid "Changes are still tracked. Useful for cluster/index migrations."
msgstr ""
-msgid "Changes are unknown"
-msgstr ""
-
msgid "Changes suppressed. Click to show."
msgstr ""
@@ -4334,15 +4504,9 @@ msgstr ""
msgid "Choose what content you want to see on a group’s overview page"
msgstr ""
-msgid "Choose which groups you wish to synchronize to this secondary node"
-msgstr ""
-
msgid "Choose which repositories you want to connect and run CI/CD pipelines."
msgstr ""
-msgid "Choose which shards you wish to synchronize to this secondary node"
-msgstr ""
-
msgid "Choose your framework"
msgstr ""
@@ -4623,7 +4787,7 @@ msgid "Closed this %{quick_action_target}."
msgstr ""
msgid "Closed: %{closedIssuesCount}"
-msgstr ""
+msgstr "Zatvoreno: %{closedIssuesCount}"
msgid "Closes this %{quick_action_target}."
msgstr ""
@@ -4640,9 +4804,15 @@ msgstr ""
msgid "Cluster does not exist"
msgstr ""
+msgid "Cluster is required for Stages::ClusterEndpointInserter"
+msgstr ""
+
msgid "Cluster level"
msgstr ""
+msgid "Cluster type must be specificed for Stages::ClusterEndpointInserter"
+msgstr ""
+
msgid "ClusterIntegration| %{custom_domain_start}More information%{custom_domain_end}."
msgstr ""
@@ -4754,7 +4924,7 @@ msgstr ""
msgid "ClusterIntegration|Cert-Manager"
msgstr ""
-msgid "ClusterIntegration|Cert-Manager is a native Kubernetes certificate management controller that helps with issuing certificates. Installing Cert-Manager on your cluster will issue a certificate by %{letsEncrypt} and ensure that certificates are valid and up-to-date."
+msgid "ClusterIntegration|Cert-Manager is a native Kubernetes certificate management controller that helps with issuing certificates. Installing Cert-Manager on your cluster will issue a certificate by %{linkStart}Let's Encrypt%{linkEnd} and ensure that certificates are valid and up-to-date."
msgstr ""
msgid "ClusterIntegration|Certificate Authority bundle (PEM format)"
@@ -4781,9 +4951,6 @@ msgstr ""
msgid "ClusterIntegration|Clear the local cache of namespace and service accounts. This is necessary if your integration has become out of sync. The cache is repopulated during the next CI job that requires namespace and service accounts."
msgstr ""
-msgid "ClusterIntegration|Cloud Run"
-msgstr ""
-
msgid "ClusterIntegration|Cluster being created"
msgstr ""
@@ -4868,7 +5035,7 @@ msgstr ""
msgid "ClusterIntegration|Crossplane"
msgstr ""
-msgid "ClusterIntegration|Crossplane enables declarative provisioning of managed services from your cloud of choice using %{codeStart}kubectl%{codeEnd} or %{gitlabIntegrationLink}. Crossplane runs inside your Kubernetes cluster and supports secure connectivity and secrets management between app containers and the cloud services they depend on."
+msgid "ClusterIntegration|Crossplane enables declarative provisioning of managed services from your cloud of choice using %{codeStart}kubectl%{codeEnd} or %{linkStart}GitLab Integration%{linkEnd}. Crossplane runs inside your Kubernetes cluster and supports secure connectivity and secrets management between app containers and the cloud services they depend on."
msgstr ""
msgid "ClusterIntegration|Deletes all GitLab resources attached to this cluster during removal"
@@ -4952,9 +5119,6 @@ msgstr ""
msgid "ClusterIntegration|GitLab-managed cluster"
msgstr ""
-msgid "ClusterIntegration|Gitlab Integration"
-msgstr ""
-
msgid "ClusterIntegration|Global default"
msgstr ""
@@ -5000,7 +5164,7 @@ msgstr ""
msgid "ClusterIntegration|Ingress gives you a way to route requests to services based on the request host or path, centralizing a number of services into a single entrypoint."
msgstr ""
-msgid "ClusterIntegration|Installing Ingress may incur additional costs. Learn more about %{pricingLink}."
+msgid "ClusterIntegration|Installing Ingress may incur additional costs. Learn more about %{linkStart}pricing%{linkEnd}."
msgstr ""
msgid "ClusterIntegration|Instance cluster"
@@ -5015,7 +5179,7 @@ msgstr ""
msgid "ClusterIntegration|Issuer Email"
msgstr ""
-msgid "ClusterIntegration|Issuers represent a certificate authority. You must provide an email address for your Issuer. "
+msgid "ClusterIntegration|Issuers represent a certificate authority. You must provide an email address for your Issuer."
msgstr ""
msgid "ClusterIntegration|Jupyter Hostname"
@@ -5087,9 +5251,6 @@ msgstr ""
msgid "ClusterIntegration|Learn more about instance Kubernetes clusters"
msgstr ""
-msgid "ClusterIntegration|Let's Encrypt"
-msgstr ""
-
msgid "ClusterIntegration|Loading IAM Roles"
msgstr ""
@@ -5201,7 +5362,7 @@ msgstr ""
msgid "ClusterIntegration|Prometheus"
msgstr ""
-msgid "ClusterIntegration|Prometheus is an open-source monitoring system with %{gitlabIntegrationLink} to monitor deployed applications."
+msgid "ClusterIntegration|Prometheus is an open-source monitoring system with %{linkStart}GitLab Integration%{linkEnd} to monitor deployed applications."
msgstr ""
msgid "ClusterIntegration|Provider details"
@@ -5357,10 +5518,10 @@ msgstr ""
msgid "ClusterIntegration|Select zone to choose machine type"
msgstr ""
-msgid "ClusterIntegration|Send Cilium Logs"
+msgid "ClusterIntegration|Send Container Network Policies Logs"
msgstr ""
-msgid "ClusterIntegration|Send ModSecurity Logs"
+msgid "ClusterIntegration|Send Web Application Firewall Logs"
msgstr ""
msgid "ClusterIntegration|Service Token"
@@ -5528,15 +5689,12 @@ msgstr ""
msgid "ClusterIntegration|documentation"
msgstr ""
-msgid "ClusterIntegration|installed via %{installed_via}"
+msgid "ClusterIntegration|installed via %{linkStart}Cloud Run%{linkEnd}"
msgstr ""
msgid "ClusterIntegration|meets the requirements"
msgstr ""
-msgid "ClusterIntegration|pricing"
-msgstr ""
-
msgid "ClusterIntegration|sign up"
msgstr ""
@@ -5571,6 +5729,15 @@ msgid "Clusters|An error occurred while loading clusters"
msgstr ""
msgid "Code"
+msgstr "Kod"
+
+msgid "Code Coverage: %{coveragePercentage}%{percentSymbol}"
+msgstr ""
+
+msgid "Code Coverage| Empty code coverage data"
+msgstr ""
+
+msgid "Code Coverage|Couldn't fetch the code coverage data"
msgstr ""
msgid "Code Owners"
@@ -5627,7 +5794,7 @@ msgstr ""
msgid "Collapse approvers"
msgstr ""
-msgid "Collapse child epics"
+msgid "Collapse milestones"
msgstr ""
msgid "Collapse replies"
@@ -5645,6 +5812,9 @@ msgstr ""
msgid "Coming soon"
msgstr ""
+msgid "Comma-separated, e.g. '1.1.1.1, 2.2.2.0/24'"
+msgstr ""
+
msgid "Command"
msgstr ""
@@ -5816,6 +5986,12 @@ msgstr ""
msgid "Compliance framework (optional)"
msgstr ""
+msgid "Compliance frameworks"
+msgstr ""
+
+msgid "ComplianceDashboard|created by:"
+msgstr ""
+
msgid "ComplianceFramework|GDPR"
msgstr ""
@@ -5954,6 +6130,9 @@ msgstr ""
msgid "Connection timed out"
msgstr ""
+msgid "Connection timeout"
+msgstr ""
+
msgid "Contact sales to upgrade"
msgstr ""
@@ -6011,9 +6190,6 @@ msgstr ""
msgid "ContainerRegistry|CLI Commands"
msgstr ""
-msgid "ContainerRegistry|Compressed Size"
-msgstr ""
-
msgid "ContainerRegistry|Container Registry"
msgstr ""
@@ -6029,6 +6205,9 @@ msgstr ""
msgid "ContainerRegistry|Copy push command"
msgstr ""
+msgid "ContainerRegistry|Delete selected"
+msgstr ""
+
msgid "ContainerRegistry|Docker connection error"
msgstr ""
@@ -6062,16 +6241,16 @@ msgstr ""
msgid "ContainerRegistry|If you are not already logged in, you need to authenticate to the Container Registry by using your GitLab username and password. If you have %{twofaDocLinkStart}Two-Factor Authentication%{twofaDocLinkEnd} enabled, use a %{personalAccessTokensDocLinkStart}Personal Access Token%{personalAccessTokensDocLinkEnd} instead of a password."
msgstr ""
-msgid "ContainerRegistry|Image ID"
+msgid "ContainerRegistry|Image ID: %{imageId}"
msgstr ""
msgid "ContainerRegistry|Image Repositories"
msgstr ""
-msgid "ContainerRegistry|Keep and protect the images that matter most."
+msgid "ContainerRegistry|Image tags"
msgstr ""
-msgid "ContainerRegistry|Last Updated"
+msgid "ContainerRegistry|Keep and protect the images that matter most."
msgstr ""
msgid "ContainerRegistry|Login"
@@ -6086,6 +6265,9 @@ msgstr ""
msgid "ContainerRegistry|Please contact your administrator."
msgstr ""
+msgid "ContainerRegistry|Published %{timeInfo}"
+msgstr ""
+
msgid "ContainerRegistry|Push an image"
msgstr ""
@@ -6101,9 +6283,6 @@ msgstr ""
msgid "ContainerRegistry|Remove repository"
msgstr ""
-msgid "ContainerRegistry|Remove selected tags"
-msgstr ""
-
msgid "ContainerRegistry|Remove tag"
msgid_plural "ContainerRegistry|Remove tags"
msgstr[0] ""
@@ -6134,9 +6313,6 @@ msgstr ""
msgid "ContainerRegistry|Sorry, your filter produced no results."
msgstr ""
-msgid "ContainerRegistry|Tag"
-msgstr ""
-
msgid "ContainerRegistry|Tag expiration policy"
msgstr ""
@@ -6281,21 +6457,6 @@ msgstr ""
msgid "Control the display of third party offers."
msgstr ""
-msgid "Control the maximum concurrency of LFS/attachment backfill for this secondary node"
-msgstr ""
-
-msgid "Control the maximum concurrency of container repository operations for this Geo node"
-msgstr ""
-
-msgid "Control the maximum concurrency of repository backfill for this secondary node"
-msgstr ""
-
-msgid "Control the maximum concurrency of verification operations for this Geo node"
-msgstr ""
-
-msgid "Control the minimum interval in days that a repository should be reverified for this primary node"
-msgstr ""
-
msgid "Cookie domain"
msgstr ""
@@ -6437,6 +6598,9 @@ msgstr ""
msgid "Could not find design."
msgstr ""
+msgid "Could not find iteration"
+msgstr ""
+
msgid "Could not remove the trigger."
msgstr ""
@@ -6473,6 +6637,12 @@ msgstr ""
msgid "Coverage"
msgstr ""
+msgid "Coverage Fuzzing"
+msgstr ""
+
+msgid "Crash State"
+msgstr ""
+
msgid "Create"
msgstr ""
@@ -6521,6 +6691,9 @@ msgstr ""
msgid "Create a personal access token on your account to pull or push via %{protocol}."
msgstr ""
+msgid "Create a project pre-populated with the necessary files to get you started quickly."
+msgstr ""
+
msgid "Create an account using:"
msgstr ""
@@ -6642,7 +6815,7 @@ msgid "CreateTokenToCloneLink|create a personal access token"
msgstr ""
msgid "Created"
-msgstr ""
+msgstr "Kreirano"
msgid "Created %{timestamp}"
msgstr ""
@@ -6689,6 +6862,9 @@ msgstr ""
msgid "Creates branch '%{branch_name}' and a merge request to resolve this issue."
msgstr ""
+msgid "Creating"
+msgstr ""
+
msgid "Creating epic"
msgstr ""
@@ -6740,7 +6916,10 @@ msgstr ""
msgid "Current vulnerabilities count"
msgstr ""
-msgid "CurrentUser|Buy CI minutes"
+msgid "CurrentUser|Buy Pipeline minutes"
+msgstr ""
+
+msgid "CurrentUser|One of your groups is running out"
msgstr ""
msgid "CurrentUser|Profile"
@@ -6755,6 +6934,9 @@ msgstr ""
msgid "CurrentUser|Upgrade"
msgstr ""
+msgid "Custom Attributes"
+msgstr ""
+
msgid "Custom CI configuration path"
msgstr ""
@@ -7019,12 +7201,18 @@ msgstr ""
msgid "DAG"
msgstr ""
+msgid "DAG visualization requires at least 3 dependent jobs."
+msgstr ""
+
msgid "DNS"
msgstr ""
msgid "Dashboard"
msgstr ""
+msgid "Dashboard uid not found"
+msgstr ""
+
msgid "DashboardProjects|All"
msgstr ""
@@ -7049,6 +7237,9 @@ msgstr ""
msgid "Data is still calculating..."
msgstr ""
+msgid "Datasource name not found"
+msgstr ""
+
msgid "Date"
msgstr ""
@@ -7229,6 +7420,9 @@ msgstr ""
msgid "Delete this attachment"
msgstr ""
+msgid "Delete user list"
+msgstr ""
+
msgid "Delete variable"
msgstr ""
@@ -7550,6 +7744,9 @@ msgstr ""
msgid "Deploying to"
msgstr ""
+msgid "Deploying to AWS is easy with GitLab"
+msgstr ""
+
msgid "Deployment Frequency"
msgstr ""
@@ -7913,6 +8110,9 @@ msgstr ""
msgid "Dismissed on pipeline %{pipelineLink} at %{projectLink}"
msgstr ""
+msgid "Display alerts from all your monitoring tools directly within GitLab."
+msgstr ""
+
msgid "Display name"
msgstr ""
@@ -7970,14 +8170,11 @@ msgstr ""
msgid "Don't show again"
msgstr ""
-msgid "Don't worry, you can access this tour by clicking on the help icon in the top right corner and choose <strong>Learn GitLab</strong>."
-msgstr ""
-
msgid "Done"
msgstr ""
msgid "Download"
-msgstr ""
+msgstr "Preuzimanje"
msgid "Download %{format}"
msgstr ""
@@ -8099,6 +8296,9 @@ msgstr ""
msgid "Edit Release"
msgstr ""
+msgid "Edit Slack integration"
+msgstr ""
+
msgid "Edit Snippet"
msgstr ""
@@ -8138,6 +8338,9 @@ msgstr ""
msgid "Edit issues"
msgstr ""
+msgid "Edit iteration"
+msgstr ""
+
msgid "Edit public deploy key"
msgstr ""
@@ -8160,7 +8363,7 @@ msgid "Editing"
msgstr ""
msgid "Elasticsearch"
-msgstr ""
+msgstr "Elasticsearch"
msgid "Elasticsearch AWS IAM credentials"
msgstr ""
@@ -8186,6 +8389,9 @@ msgstr ""
msgid "Email"
msgstr ""
+msgid "Email Notification"
+msgstr ""
+
msgid "Email address"
msgstr ""
@@ -8198,6 +8404,9 @@ msgstr ""
msgid "Email not verified. Please verify your email in Salesforce."
msgstr ""
+msgid "Email notification for unknown sign-ins"
+msgstr ""
+
msgid "Email patch"
msgstr ""
@@ -8429,6 +8638,9 @@ msgstr ""
msgid "Enforce DNS rebinding attack protection"
msgstr ""
+msgid "Enforce personal access token expiration"
+msgstr ""
+
msgid "Ensure connectivity is available from the GitLab server to the Prometheus server"
msgstr ""
@@ -8498,6 +8710,9 @@ msgstr ""
msgid "Enter the name of your application, and we'll return a unique %{type}."
msgstr ""
+msgid "Enter weights for storages for new repositories."
+msgstr ""
+
msgid "Enter your password to approve"
msgstr ""
@@ -8507,15 +8722,27 @@ msgstr ""
msgid "Environment does not have deployments"
msgstr ""
+msgid "Environment is required for Stages::MetricEndpointInserter"
+msgstr ""
+
+msgid "Environment is required for Stages::VariableEndpointInserter"
+msgstr ""
+
msgid "Environment scope"
msgstr ""
+msgid "Environment variables are applied to all project environments in this instance via the Runner. You can use environment variables for passwords, secret keys, etc. Make variables available to the running application by prepending the variable key with <code>K8S_SECRET_</code>. You can set variables to be:"
+msgstr ""
+
msgid "Environment variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. Additionally, they can be masked so they are hidden in job logs, though they must match certain regexp requirements to do so. You can use environment variables for passwords, secret keys, or whatever you want."
msgstr ""
msgid "Environment variables are configured by your administrator to be %{link_start}protected%{link_end} by default"
msgstr ""
+msgid "Environment variables on this GitLab instance are configured to be %{link_start}protected%{link_end} by default"
+msgstr ""
+
msgid "Environment:"
msgstr ""
@@ -9047,6 +9274,9 @@ msgstr ""
msgid "EventFilterBy|Filter by comments"
msgstr ""
+msgid "EventFilterBy|Filter by designs"
+msgstr ""
+
msgid "EventFilterBy|Filter by epic events"
msgstr ""
@@ -9179,15 +9409,15 @@ msgstr ""
msgid "Expand approvers"
msgstr ""
-msgid "Expand child epics"
-msgstr ""
-
msgid "Expand down"
msgstr ""
msgid "Expand dropdown"
msgstr ""
+msgid "Expand milestones"
+msgstr ""
+
msgid "Expand sidebar"
msgstr ""
@@ -9203,6 +9433,9 @@ msgstr ""
msgid "Expiration date"
msgstr ""
+msgid "Expiration not enforced"
+msgstr ""
+
msgid "Expiration policy for the Container Registry is a perfect solution for keeping the Registry space down while still enjoying the full power of GitLab CI/CD."
msgstr ""
@@ -9323,6 +9556,9 @@ msgstr ""
msgid "Failed Jobs"
msgstr ""
+msgid "Failed on"
+msgstr ""
+
msgid "Failed to add a Zoom meeting"
msgstr ""
@@ -9383,13 +9619,19 @@ msgstr ""
msgid "Failed to install."
msgstr ""
+msgid "Failed to load assignees. Please try again."
+msgstr ""
+
+msgid "Failed to load authors. Please try again."
+msgstr ""
+
msgid "Failed to load emoji list."
msgstr ""
msgid "Failed to load error details from Sentry."
msgstr ""
-msgid "Failed to load errors from Sentry. Error message: %{errorMessage}"
+msgid "Failed to load errors from Sentry."
msgstr ""
msgid "Failed to load group activity metrics. Please try again."
@@ -9470,6 +9712,9 @@ msgstr ""
msgid "Failed to set due date because the date format is invalid."
msgstr ""
+msgid "Failed to set iteration on this issue. Please try again."
+msgstr ""
+
msgid "Failed to signing using smartcard authentication"
msgstr ""
@@ -9692,13 +9937,10 @@ msgstr ""
msgid "FeatureFlags|Target environments"
msgstr ""
-msgid "FeatureFlags|There are no active feature flags"
-msgstr ""
-
-msgid "FeatureFlags|There are no inactive feature flags"
+msgid "FeatureFlags|There was an error fetching the feature flags."
msgstr ""
-msgid "FeatureFlags|There was an error fetching the feature flags."
+msgid "FeatureFlags|There was an error retrieving user lists"
msgstr ""
msgid "FeatureFlags|Try again in a few moments or contact your support team."
@@ -9710,9 +9952,18 @@ msgstr ""
msgid "FeatureFlag|Delete strategy"
msgstr ""
+msgid "FeatureFlag|List"
+msgstr ""
+
msgid "FeatureFlag|Percentage"
msgstr ""
+msgid "FeatureFlag|Select a user list"
+msgstr ""
+
+msgid "FeatureFlag|There are no configured user lists"
+msgstr ""
+
msgid "FeatureFlag|Type"
msgstr ""
@@ -9869,6 +10120,12 @@ msgstr ""
msgid "Filter..."
msgstr ""
+msgid "Find File"
+msgstr ""
+
+msgid "Find bugs in your code with coverage-guided fuzzing"
+msgstr ""
+
msgid "Find by path"
msgstr ""
@@ -10133,6 +10390,9 @@ msgstr ""
msgid "Geo allows you to replicate your GitLab instance to other geographical locations."
msgstr ""
+msgid "Geo nodes are paused using a command run on the node"
+msgstr ""
+
msgid "GeoNodeStatusEvent|%{timeAgoStr} (%{pendingEvents} events)"
msgstr ""
@@ -10217,6 +10477,9 @@ msgstr ""
msgid "GeoNodes|Not checksummed"
msgstr ""
+msgid "GeoNodes|Package files"
+msgstr ""
+
msgid "GeoNodes|Pausing replication stops the sync process. Are you sure?"
msgstr ""
@@ -10235,6 +10498,9 @@ msgstr ""
msgid "GeoNodes|Replication slots"
msgstr ""
+msgid "GeoNodes|Replication status"
+msgstr ""
+
msgid "GeoNodes|Repositories"
msgstr ""
@@ -10445,6 +10711,9 @@ msgstr ""
msgid "Geo|Synchronization failed - %{error}"
msgstr ""
+msgid "Geo|Synchronization of %{itemTitle} is disabled."
+msgstr ""
+
msgid "Geo|The database is currently %{db_lag} behind the primary node."
msgstr ""
@@ -10707,7 +10976,7 @@ msgid "GitLabPages|Your pages are served under:"
msgstr ""
msgid "Gitaly"
-msgstr ""
+msgstr "Gitaly"
msgid "Gitaly Servers"
msgstr ""
@@ -10757,6 +11026,9 @@ msgstr ""
msgid "Go to %{link_to_google_takeout}."
msgstr ""
+msgid "Go to <strong>Issues</strong> > <strong>Boards</strong> to access your personalized learning issue board."
+msgstr ""
+
msgid "Go to Pipelines"
msgstr ""
@@ -10856,9 +11128,6 @@ msgstr ""
msgid "Go to your snippets"
msgstr ""
-msgid "Golden Tanuki"
-msgstr ""
-
msgid "Google Cloud Platform"
msgstr ""
@@ -10880,6 +11149,9 @@ msgstr ""
msgid "Grafana URL"
msgstr ""
+msgid "Grafana response contains invalid json"
+msgstr ""
+
msgid "GrafanaIntegration|API Token"
msgstr ""
@@ -10928,9 +11200,6 @@ msgstr ""
msgid "Group %{group_name} was successfully created."
msgstr ""
-msgid "Group '%{group_name}' is being imported."
-msgstr ""
-
msgid "Group Audit Events"
msgstr ""
@@ -10967,9 +11236,6 @@ msgstr ""
msgid "Group by:"
msgstr ""
-msgid "Group could not be imported: %{errors}"
-msgstr ""
-
msgid "Group description"
msgstr ""
@@ -10997,9 +11263,15 @@ msgstr ""
msgid "Group has not been marked for deletion"
msgstr ""
+msgid "Group import could not be scheduled"
+msgstr ""
+
msgid "Group info:"
msgstr ""
+msgid "Group is required when cluster_type is :group"
+msgstr ""
+
msgid "Group maintainers can register group runners in the %{link}"
msgstr ""
@@ -11063,6 +11335,24 @@ msgstr ""
msgid "GroupActivyMetrics|Recent activity (last 90 days)"
msgstr ""
+msgid "GroupImport|Failed to import group."
+msgstr ""
+
+msgid "GroupImport|Group '%{group_name}' is being imported."
+msgstr ""
+
+msgid "GroupImport|Group could not be imported: %{errors}"
+msgstr ""
+
+msgid "GroupImport|Please wait while we import the group for you. Refresh at will."
+msgstr ""
+
+msgid "GroupImport|The group was successfully imported."
+msgstr ""
+
+msgid "GroupImport|Unable to process group import file"
+msgstr ""
+
msgid "GroupRoadmap|%{dateWord} – No end date"
msgstr ""
@@ -11330,6 +11620,9 @@ msgstr ""
msgid "Groups (%{groups})"
msgstr ""
+msgid "Groups and projects"
+msgstr ""
+
msgid "Groups can also be nested by creating %{subgroup_docs_link_start}subgroups%{subgroup_docs_link_end}."
msgstr ""
@@ -11375,6 +11668,33 @@ msgstr ""
msgid "GroupsEmptyState|You can manage your group member’s permissions and access to each project in the group."
msgstr ""
+msgid "GroupsNew|Contact an administrator to enable options for importing your group."
+msgstr ""
+
+msgid "GroupsNew|Create"
+msgstr ""
+
+msgid "GroupsNew|Create group"
+msgstr ""
+
+msgid "GroupsNew|GitLab group export"
+msgstr ""
+
+msgid "GroupsNew|Import"
+msgstr ""
+
+msgid "GroupsNew|Import group"
+msgstr ""
+
+msgid "GroupsNew|My Awesome Group"
+msgstr ""
+
+msgid "GroupsNew|No import options available"
+msgstr ""
+
+msgid "GroupsNew|To copy a GitLab group between installations, navigate to the group settings page for the original installation, generate an export file, and upload it here."
+msgstr ""
+
msgid "GroupsTree|Are you sure you want to leave the \"%{fullName}\" group?"
msgstr ""
@@ -11477,6 +11797,9 @@ msgstr ""
msgid "Helps reduce request volume for protected paths"
msgstr ""
+msgid "Here are all your projects in your group, including the one you just created. To start, let’s take a look at your personalized learning project which will help you learn about GitLab at your own pace."
+msgstr ""
+
msgid "Here you will find recent merge request activity"
msgstr ""
@@ -11492,6 +11815,9 @@ msgstr[0] ""
msgstr[1] ""
msgstr[2] ""
+msgid "Hide details"
+msgstr ""
+
msgid "Hide file browser"
msgstr ""
@@ -11501,6 +11827,9 @@ msgstr ""
msgid "Hide host keys manual input"
msgstr ""
+msgid "Hide list"
+msgstr ""
+
msgid "Hide marketing-related entries from help"
msgstr ""
@@ -11522,9 +11851,6 @@ msgstr[2] ""
msgid "Hide values"
msgstr ""
-msgid "Hiding all labels"
-msgstr ""
-
msgid "High or unknown vulnerabilities present"
msgstr ""
@@ -11609,6 +11935,9 @@ msgstr ""
msgid "ID:"
msgstr ""
+msgid "IDE"
+msgstr ""
+
msgid "IDE|Allow live previews of JavaScript projects in the Web IDE using CodeSandbox Live Preview."
msgstr ""
@@ -11699,10 +12028,10 @@ msgstr ""
msgid "If enabled"
msgstr ""
-msgid "If enabled, access to projects will be validated on an external service using their classification label."
+msgid "If enabled, GitLab will handle Object Storage replication using Geo. %{linkStart}More information%{linkEnd}"
msgstr ""
-msgid "If enabled, and if object storage is enabled, GitLab will handle Object Storage replication using Geo"
+msgid "If enabled, access to projects will be validated on an external service using their classification label."
msgstr ""
msgid "If there is no previous license or if the previous license has expired, some GitLab functionality will be blocked until a new, valid license is uploaded."
@@ -12065,6 +12394,9 @@ msgstr[0] ""
msgstr[1] ""
msgstr[2] ""
+msgid "Instance Configuration"
+msgstr ""
+
msgid "Instance Statistics visibility"
msgstr ""
@@ -12086,9 +12418,6 @@ msgstr ""
msgid "Integrations"
msgstr ""
-msgid "Integrations allow you to integrate GitLab with other applications"
-msgstr ""
-
msgid "Integrations|All details"
msgstr ""
@@ -12200,6 +12529,9 @@ msgstr ""
msgid "Invalid query"
msgstr ""
+msgid "Invalid repository bundle for snippet with id %{snippet_id}"
+msgstr ""
+
msgid "Invalid repository path"
msgstr ""
@@ -12218,6 +12550,9 @@ msgstr ""
msgid "Invalid two-factor code."
msgstr ""
+msgid "Invalid yaml"
+msgstr ""
+
msgid "Invitation"
msgstr ""
@@ -12267,7 +12602,7 @@ msgid "IssuableStatus|promoted"
msgstr ""
msgid "Issue"
-msgstr ""
+msgstr "Zadatak"
msgid "Issue %{issue_reference} has already been added to epic %{epic_reference}."
msgstr ""
@@ -12437,15 +12772,24 @@ msgstr ""
msgid "It seems like the Dependency Scanning job ran successfully, but no dependencies have been detected in your project."
msgstr ""
+msgid "It seems that there is currently no available data for code coverage"
+msgstr ""
+
msgid "It's you"
msgstr ""
+msgid "Iteration"
+msgstr ""
+
msgid "Iteration changed to"
msgstr ""
msgid "Iteration removed"
msgstr ""
+msgid "Iteration updated"
+msgstr ""
+
msgid "Iterations"
msgstr ""
@@ -12476,12 +12820,18 @@ msgstr ""
msgid "January"
msgstr ""
+msgid "Jira Issues"
+msgstr ""
+
msgid "Jira import is already running."
msgstr ""
msgid "Jira integration not configured."
msgstr ""
+msgid "Jira project key is not configured"
+msgstr ""
+
msgid "Jira project: %{importProject}"
msgstr ""
@@ -12662,6 +13012,9 @@ msgstr ""
msgid "Keep divergent refs"
msgstr ""
+msgid "Kerberos access denied"
+msgstr ""
+
msgid "Key"
msgstr ""
@@ -12671,15 +13024,18 @@ msgstr ""
msgid "Key: %{key}"
msgstr ""
-msgid "Keyboard Shortcuts"
+msgid "Keyboard shortcuts"
msgstr ""
-msgid "Keyboard shortcuts"
+msgid "Keys"
msgstr ""
-msgid "Kubernetes"
+msgid "Ki"
msgstr ""
+msgid "Kubernetes"
+msgstr "Kubernetes"
+
msgid "Kubernetes API returned status code: %{error_code}"
msgstr ""
@@ -12716,6 +13072,9 @@ msgstr ""
msgid "LDAP"
msgstr ""
+msgid "LDAP Synchronization"
+msgstr ""
+
msgid "LDAP settings"
msgstr ""
@@ -12912,7 +13271,7 @@ msgid "Learn how to enable synchronization"
msgstr ""
msgid "Learn more"
-msgstr ""
+msgstr "Saznaj više"
msgid "Learn more about Auto DevOps"
msgstr ""
@@ -12941,12 +13300,18 @@ msgstr ""
msgid "Learn more about custom project templates"
msgstr ""
+msgid "Learn more about deploying to AWS"
+msgstr ""
+
msgid "Learn more about deploying to a cluster"
msgstr ""
msgid "Learn more about group-level project templates"
msgstr ""
+msgid "Learn more about job dependencies"
+msgstr ""
+
msgid "Learn more about signing commits"
msgstr ""
@@ -12998,9 +13363,21 @@ msgstr ""
msgid "License History"
msgstr ""
+msgid "License ID:"
+msgstr ""
+
+msgid "License URL"
+msgstr ""
+
msgid "License-Check"
msgstr ""
+msgid "LicenseCompliance|%{docLinkStart}License Approvals%{docLinkEnd} are active"
+msgstr ""
+
+msgid "LicenseCompliance|%{docLinkStart}License Approvals%{docLinkEnd} are inactive"
+msgstr ""
+
msgid "LicenseCompliance|Add a license"
msgstr ""
@@ -13022,9 +13399,15 @@ msgstr ""
msgid "LicenseCompliance|Deny"
msgstr ""
+msgid "LicenseCompliance|Learn more about %{linkStart}License Approvals%{linkEnd}"
+msgstr ""
+
msgid "LicenseCompliance|License"
msgstr ""
+msgid "LicenseCompliance|License Approvals"
+msgstr ""
+
msgid "LicenseCompliance|License Compliance detected %d license and policy violation for the source branch only; approval required"
msgid_plural "LicenseCompliance|License Compliance detected %d licenses and policy violations for the source branch only; approval required"
msgstr[0] ""
@@ -13250,6 +13633,9 @@ msgstr ""
msgid "List your Bitbucket Server repositories"
msgstr ""
+msgid "Lists"
+msgstr ""
+
msgid "Live preview"
msgstr ""
@@ -13433,6 +13819,9 @@ msgstr ""
msgid "Manage project labels"
msgstr ""
+msgid "Manage storage usage"
+msgstr ""
+
msgid "Manage two-factor authentication"
msgstr ""
@@ -13448,6 +13837,9 @@ msgstr ""
msgid "Manifest file import"
msgstr ""
+msgid "Manifest import"
+msgstr ""
+
msgid "Manual job"
msgstr ""
@@ -13595,6 +13987,12 @@ msgstr ""
msgid "Maximum field length"
msgstr ""
+msgid "Maximum file size is 2MB. Please select a smaller file."
+msgstr ""
+
+msgid "Maximum import size (MB)"
+msgstr ""
+
msgid "Maximum job timeout"
msgstr ""
@@ -13634,6 +14032,9 @@ msgstr ""
msgid "Maximum size of Elasticsearch bulk indexing requests."
msgstr ""
+msgid "Maximum size of import files."
+msgstr ""
+
msgid "Maximum size of individual attachments in comments."
msgstr ""
@@ -13685,6 +14086,9 @@ msgstr ""
msgid "Merge (when the pipeline succeeds)"
msgstr ""
+msgid "Merge Conflicts"
+msgstr ""
+
msgid "Merge Request"
msgstr ""
@@ -13811,6 +14215,9 @@ msgstr ""
msgid "MergeRequests|Squash task canceled: another squash is already in progress."
msgstr ""
+msgid "MergeRequests|This project does not allow squashing commits when merge requests are accepted."
+msgstr ""
+
msgid "MergeRequests|Thread stays resolved"
msgstr ""
@@ -13964,6 +14371,12 @@ msgstr ""
msgid "MetricsSettings|Add a button to the metrics dashboard linking directly to your existing external dashboard."
msgstr ""
+msgid "MetricsSettings|Choose whether to display dashboard metrics in UTC or the user's local timezone."
+msgstr ""
+
+msgid "MetricsSettings|Dashboard timezone"
+msgstr ""
+
msgid "MetricsSettings|External dashboard URL"
msgstr ""
@@ -13973,6 +14386,12 @@ msgstr ""
msgid "MetricsSettings|Metrics Dashboard"
msgstr ""
+msgid "MetricsSettings|UTC (Coordinated Universal Time)"
+msgstr ""
+
+msgid "MetricsSettings|User's local timezone"
+msgstr ""
+
msgid "Metrics|Add metric"
msgstr ""
@@ -13988,6 +14407,9 @@ msgstr ""
msgid "Metrics|Create metric"
msgstr ""
+msgid "Metrics|Current"
+msgstr ""
+
msgid "Metrics|Delete metric"
msgstr ""
@@ -14039,6 +14461,9 @@ msgstr ""
msgid "Metrics|Max"
msgstr ""
+msgid "Metrics|Min"
+msgstr ""
+
msgid "Metrics|Must be a valid PromQL query."
msgstr ""
@@ -14054,6 +14479,9 @@ msgstr ""
msgid "Metrics|Refresh dashboard"
msgstr ""
+msgid "Metrics|Select a value"
+msgstr ""
+
msgid "Metrics|Star dashboard"
msgstr ""
@@ -14072,12 +14500,18 @@ msgstr ""
msgid "Metrics|There was an error getting annotations information."
msgstr ""
+msgid "Metrics|There was an error getting dashboard validation warnings information."
+msgstr ""
+
msgid "Metrics|There was an error getting deployment information."
msgstr ""
msgid "Metrics|There was an error getting environments information."
msgstr ""
+msgid "Metrics|There was an error getting options for variable \"%{name}\"."
+msgstr ""
+
msgid "Metrics|There was an error trying to validate your query"
msgstr ""
@@ -14120,6 +14554,9 @@ msgstr ""
msgid "Metrics|You're about to permanently delete this metric. This cannot be undone."
msgstr ""
+msgid "Metrics|Your dashboard schema is invalid. Edit the dashboard to correct the YAML schema."
+msgstr ""
+
msgid "Metrics|e.g. HTTP requests"
msgstr ""
@@ -14135,12 +14572,18 @@ msgstr ""
msgid "Metrics|e.g. req/sec"
msgstr ""
+msgid "Mi"
+msgstr ""
+
msgid "Microsoft Azure"
msgstr ""
msgid "Middleman project with Static Site Editor support"
msgstr ""
+msgid "Migrate your data from an external source like GitHub, Bitbucket, or another instance of GitLab."
+msgstr ""
+
msgid "Migrated %{success_count}/%{total_count} files."
msgstr ""
@@ -14276,6 +14719,9 @@ msgstr ""
msgid "Minimum capacity to be available before we schedule more mirrors preemptively."
msgstr ""
+msgid "Minimum interval in days"
+msgstr ""
+
msgid "Minimum length is %{minimum_password_length} characters"
msgstr ""
@@ -14462,6 +14908,12 @@ msgstr ""
msgid "Multiple uploaders found: %{uploader_types}"
msgstr ""
+msgid "Must match with the %{codeStart}external_url%{codeEnd} in %{codeStart}/etc/gitlab/gitlab.rb%{codeEnd}."
+msgstr ""
+
+msgid "Must match with the %{codeStart}geo_node_name%{codeEnd} in %{codeStart}/etc/gitlab/gitlab.rb%{codeEnd}. %{linkStart}More information%{linkEnd}"
+msgstr ""
+
msgid "My Awesome Group"
msgstr ""
@@ -14576,10 +15028,16 @@ msgstr ""
msgid "New Application"
msgstr ""
+msgid "New Branch"
+msgstr ""
+
+msgid "New Deploy Key"
+msgstr ""
+
msgid "New Environment"
msgstr ""
-msgid "New Geo Node"
+msgid "New File"
msgstr ""
msgid "New Group"
@@ -14597,15 +15055,15 @@ msgstr[0] ""
msgstr[1] ""
msgstr[2] ""
-msgid "New Iteration"
-msgstr ""
-
msgid "New Jira import"
msgstr ""
msgid "New Label"
msgstr ""
+msgid "New Merge Request"
+msgstr ""
+
msgid "New Milestone"
msgstr ""
@@ -14624,6 +15082,9 @@ msgstr ""
msgid "New Snippet"
msgstr ""
+msgid "New User"
+msgstr ""
+
msgid "New branch"
msgstr ""
@@ -14666,6 +15127,9 @@ msgstr ""
msgid "New issue title"
msgstr ""
+msgid "New iteration"
+msgstr ""
+
msgid "New iteration created"
msgstr ""
@@ -14843,6 +15307,12 @@ msgstr ""
msgid "No grouping"
msgstr ""
+msgid "No iteration"
+msgstr ""
+
+msgid "No iterations to show"
+msgstr ""
+
msgid "No job log"
msgstr ""
@@ -14885,6 +15355,9 @@ msgstr ""
msgid "No other labels with such name or description"
msgstr ""
+msgid "No panels matching properties %{opts}"
+msgstr ""
+
msgid "No parent group"
msgstr ""
@@ -14918,6 +15391,9 @@ msgstr ""
msgid "No schedules"
msgstr ""
+msgid "No stack trace for this error"
+msgstr ""
+
msgid "No starrers matched your search"
msgstr ""
@@ -14930,7 +15406,7 @@ msgstr ""
msgid "No test coverage"
msgstr ""
-msgid "No thanks, don't show this again"
+msgid "No thanks"
msgstr ""
msgid "No vulnerabilities present"
@@ -14945,9 +15421,6 @@ msgstr ""
msgid "No, directly import the existing email addresses and usernames."
msgstr ""
-msgid "No, not interested right now"
-msgstr ""
-
msgid "No. of commits"
msgstr ""
@@ -14993,9 +15466,6 @@ msgstr ""
msgid "Not found."
msgstr ""
-msgid "Not helpful"
-msgstr ""
-
msgid "Not now"
msgstr ""
@@ -15152,6 +15622,9 @@ msgstr ""
msgid "Notifications on"
msgstr ""
+msgid "Notify users by email when sign-in location is not recognized"
+msgstr ""
+
msgid "Nov"
msgstr ""
@@ -15218,9 +15691,6 @@ msgstr ""
msgid "Oh no!"
msgstr ""
-msgid "Ok let's go"
-msgstr ""
-
msgid "Oldest first"
msgstr ""
@@ -15233,16 +15703,49 @@ msgstr ""
msgid "On track"
msgstr ""
+msgid "OnDemandScans|Attached branch"
+msgstr ""
+
+msgid "OnDemandScans|Attached branch is where the scan job runs."
+msgstr ""
+
+msgid "OnDemandScans|Could not run the scan. Please try again."
+msgstr ""
+
msgid "OnDemandScans|Create new DAST scan"
msgstr ""
+msgid "OnDemandScans|DAST will scan the target URL and any discovered sub URLs."
+msgstr ""
+
+msgid "OnDemandScans|New on-demand DAST scan"
+msgstr ""
+
msgid "OnDemandScans|On-demand Scans"
msgstr ""
+msgid "OnDemandScans|On-demand scans run outside the DevOps cycle and find vulnerabilities in your projects. %{learnMoreLinkStart}Learn more%{learnMoreLinkEnd}"
+msgstr ""
+
+msgid "OnDemandScans|Only a passive scan can be performed on demand."
+msgstr ""
+
+msgid "OnDemandScans|Passive DAST Scan"
+msgstr ""
+
+msgid "OnDemandScans|Please enter a valid URL format, ex: http://www.example.com/home"
+msgstr ""
+
+msgid "OnDemandScans|Run this scan"
+msgstr ""
+
+msgid "OnDemandScans|Scan mode"
+msgstr ""
+
msgid "OnDemandScans|Schedule or run scans immediately against target sites. Currently available on-demand scan type: DAST. %{helpLinkStart}More information%{helpLinkEnd}"
msgstr ""
-msgid "Onboarding"
+msgid "OnDemandScans|Target URL"
msgstr ""
msgid "Once imported, repositories can be mirrored over SSH. Read more %{link_start}here%{link_end}."
@@ -15269,6 +15772,9 @@ msgstr ""
msgid "One or more of you personal access tokens were revoked"
msgstr ""
+msgid "One or more of your %{provider} projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
+msgstr ""
+
msgid "One or more of your Bitbucket projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
msgstr ""
@@ -15465,7 +15971,7 @@ msgid "Overridden"
msgstr ""
msgid "Overview"
-msgstr ""
+msgstr "Pregled"
msgid "Overwrite diverged branches"
msgstr ""
@@ -15723,7 +16229,7 @@ msgid "Page was successfully deleted"
msgstr ""
msgid "Pages"
-msgstr ""
+msgstr "Stranice"
msgid "Pages Domain"
msgstr ""
@@ -15770,6 +16276,9 @@ msgstr ""
msgid "Parent epic is not present."
msgstr ""
+msgid "Parsing error for param :embed_json. %{message}"
+msgstr ""
+
msgid "Part of merge request changes"
msgstr ""
@@ -15782,6 +16291,9 @@ msgstr ""
msgid "Passed"
msgstr ""
+msgid "Passed on"
+msgstr ""
+
msgid "Password"
msgstr ""
@@ -15866,6 +16378,9 @@ msgstr ""
msgid "Perform common operations on GitLab project"
msgstr ""
+msgid "Performance and resource management"
+msgstr ""
+
msgid "Performance optimization"
msgstr ""
@@ -16073,7 +16588,7 @@ msgstr ""
msgid "Pipelines|Something went wrong while cleaning runners cache."
msgstr ""
-msgid "Pipelines|There are currently no %{scope} pipelines."
+msgid "Pipelines|There are currently no finished pipelines."
msgstr ""
msgid "Pipelines|There are currently no pipelines."
@@ -16184,6 +16699,9 @@ msgstr ""
msgid "Pipeline|Stop pipeline #%{pipelineId}?"
msgstr ""
+msgid "Pipeline|Tag name"
+msgstr ""
+
msgid "Pipeline|Trigger author"
msgstr ""
@@ -16265,12 +16783,18 @@ msgstr ""
msgid "Please check your email (%{email}) to verify that you own this address and unlock the power of CI/CD. Didn't receive it? %{resend_link}. Wrong email address? %{update_link}."
msgstr ""
+msgid "Please choose a file"
+msgstr ""
+
msgid "Please choose a group URL with no special characters."
msgstr ""
msgid "Please complete your profile with email address"
msgstr ""
+msgid "Please convert %{linkStart}them to Git%{linkEnd}, and go through the %{linkToImportFlow} again."
+msgstr ""
+
msgid "Please convert them to %{link_to_git}, and go through the %{link_to_import_flow} again."
msgstr ""
@@ -16328,6 +16852,9 @@ msgstr ""
msgid "Please provide a valid email address."
msgstr ""
+msgid "Please provide attributes to update"
+msgstr ""
+
msgid "Please refer to <a href=\"%{docs_url}\">%{docs_url}</a>"
msgstr ""
@@ -16365,7 +16892,7 @@ msgid "Please solve the reCAPTCHA"
msgstr ""
msgid "Please try again"
-msgstr ""
+msgstr "Pokušaj ponovo"
msgid "Please type %{phrase_code} to proceed or close this modal to cancel."
msgstr ""
@@ -17063,6 +17590,9 @@ msgstr ""
msgid "Project has too many %{label_for_message} to search"
msgstr ""
+msgid "Project is required when cluster_type is :project"
+msgstr ""
+
msgid "Project members"
msgstr ""
@@ -17180,7 +17710,40 @@ msgstr ""
msgid "ProjectService|Comment"
msgstr ""
-msgid "ProjectService|Comment will be posted on each event"
+msgid "ProjectService|Event will be triggered by a push to the repository"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a commit is created/updated"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a confidential issue is created/updated/closed"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a deployment finishes"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a merge request is created/updated/merged"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a new tag is pushed to the repository"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a new, unique alert is recorded"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a pipeline status changes"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a wiki page is created/updated"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when an issue is created/updated/closed"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when someone adds a comment"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when someone adds a comment on a confidential issue"
msgstr ""
msgid "ProjectService|Perform common operations on GitLab project: %{project_name}"
@@ -17231,6 +17794,9 @@ msgstr ""
msgid "ProjectSettings|Enable 'Delete source branch' option by default"
msgstr ""
+msgid "ProjectSettings|Enable merge trains and pipelines for merged results"
+msgstr ""
+
msgid "ProjectSettings|Every merge creates a merge commit"
msgstr ""
@@ -17261,7 +17827,7 @@ msgstr ""
msgid "ProjectSettings|Forks"
msgstr ""
-msgid "ProjectSettings|Git Large File Storage"
+msgid "ProjectSettings|Git Large File Storage (LFS)"
msgstr ""
msgid "ProjectSettings|Internal"
@@ -17297,9 +17863,6 @@ msgstr ""
msgid "ProjectSettings|Merge options"
msgstr ""
-msgid "ProjectSettings|Merge pipelines will try to validate the post-merge result prior to merging"
-msgstr ""
-
msgid "ProjectSettings|Merge requests"
msgstr ""
@@ -17327,6 +17890,9 @@ msgstr ""
msgid "ProjectSettings|Pipelines"
msgstr ""
+msgid "ProjectSettings|Pipelines for merge requests must be enabled in the CI/CD configuration file, or pipelines could be unresolvable or dropped"
+msgstr ""
+
msgid "ProjectSettings|Pipelines must succeed"
msgstr ""
@@ -17354,6 +17920,9 @@ msgstr ""
msgid "ProjectSettings|Show link to create/view merge request when pushing from the command line"
msgstr ""
+msgid "ProjectSettings|Skipped pipelines are considered successful"
+msgstr ""
+
msgid "ProjectSettings|Snippets"
msgstr ""
@@ -17369,6 +17938,9 @@ msgstr ""
msgid "ProjectSettings|These checks must pass before merge requests can be merged"
msgstr ""
+msgid "ProjectSettings|This introduces the risk of merging changes that will not pass the pipeline."
+msgstr ""
+
msgid "ProjectSettings|This setting is applied on the server level and can be overridden by an admin."
msgstr ""
@@ -17546,15 +18118,27 @@ msgstr ""
msgid "ProjectsNew|Blank project"
msgstr ""
+msgid "ProjectsNew|Connect your external repository to GitLab CI/CD."
+msgstr ""
+
msgid "ProjectsNew|Contact an administrator to enable options for importing your project."
msgstr ""
msgid "ProjectsNew|Create"
msgstr ""
+msgid "ProjectsNew|Create a blank project to house your files, plan your work, and collaborate on code, among other things."
+msgstr ""
+
+msgid "ProjectsNew|Create blank project"
+msgstr ""
+
msgid "ProjectsNew|Create from template"
msgstr ""
+msgid "ProjectsNew|Create new project"
+msgstr ""
+
msgid "ProjectsNew|Creating project & repository."
msgstr ""
@@ -17579,6 +18163,9 @@ msgstr ""
msgid "ProjectsNew|Project description %{tag_start}(optional)%{tag_end}"
msgstr ""
+msgid "ProjectsNew|Run CI/CD for external repository"
+msgstr ""
+
msgid "ProjectsNew|Template"
msgstr ""
@@ -17589,7 +18176,7 @@ msgid "ProjectsNew|Want to house several dependent projects under the same names
msgstr ""
msgid "Prometheus"
-msgstr ""
+msgstr "Prometheus"
msgid "PrometheusAlerts|%{count} alerts applied"
msgstr ""
@@ -17840,6 +18427,9 @@ msgstr ""
msgid "Protected Branch"
msgstr ""
+msgid "Protected Branches"
+msgstr ""
+
msgid "Protected Environment"
msgstr ""
@@ -17852,6 +18442,9 @@ msgstr ""
msgid "Protected Tag"
msgstr ""
+msgid "Protected Tags"
+msgstr ""
+
msgid "Protected branches"
msgstr ""
@@ -17951,11 +18544,14 @@ msgstr ""
msgid "Provider"
msgstr ""
+msgid "Proxy support for this API is not available currently"
+msgstr ""
+
msgid "Pseudonymizer data collection"
msgstr ""
msgid "Public"
-msgstr ""
+msgstr "Javno"
msgid "Public - The group and any public projects can be viewed without any authentication."
msgstr ""
@@ -17975,6 +18571,9 @@ msgstr ""
msgid "Publish to status page"
msgstr ""
+msgid "Published on status page"
+msgstr ""
+
msgid "Publishes this issue to the associated status page."
msgstr ""
@@ -18149,6 +18748,9 @@ msgstr ""
msgid "Recipe"
msgstr ""
+msgid "Reconfigure"
+msgstr ""
+
msgid "Recover hidden stage"
msgstr ""
@@ -18236,6 +18838,9 @@ msgstr ""
msgid "Registration|Your profile"
msgstr ""
+msgid "Regulate approvals by authors/committers, based on compliance frameworks. Can be changed only at the instance level."
+msgstr ""
+
msgid "Rejected (closed)"
msgstr ""
@@ -18287,15 +18892,24 @@ msgstr ""
msgid "ReleaseAssetLinkType|Image"
msgstr ""
+msgid "ReleaseAssetLinkType|Images"
+msgstr ""
+
msgid "ReleaseAssetLinkType|Other"
msgstr ""
msgid "ReleaseAssetLinkType|Package"
msgstr ""
+msgid "ReleaseAssetLinkType|Packages"
+msgstr ""
+
msgid "ReleaseAssetLinkType|Runbook"
msgstr ""
+msgid "ReleaseAssetLinkType|Runbooks"
+msgstr ""
+
msgid "Releases"
msgstr ""
@@ -18308,6 +18922,9 @@ msgstr ""
msgid "Releases documentation"
msgstr ""
+msgid "Releases|New Release"
+msgstr ""
+
msgid "Release|Something went wrong while getting the release details"
msgstr ""
@@ -18380,6 +18997,9 @@ msgstr ""
msgid "Remove fork relationship"
msgstr ""
+msgid "Remove from batch"
+msgstr ""
+
msgid "Remove from board"
msgstr ""
@@ -18569,6 +19189,12 @@ msgstr ""
msgid "Replication"
msgstr ""
+msgid "Replication enabled"
+msgstr ""
+
+msgid "Replication paused"
+msgstr ""
+
msgid "Reply by email"
msgstr ""
@@ -18596,15 +19222,15 @@ msgstr ""
msgid "Reported %{timeAgo} by %{reportedBy}"
msgstr ""
-msgid "Reported Resource Changes: %{addNum} to add, %{changeNum} to change, %{deleteNum} to delete"
-msgstr ""
-
msgid "Reporter"
msgstr ""
msgid "Reporting"
msgstr ""
+msgid "Reports"
+msgstr ""
+
msgid "Reports|%{combinedString} and %{resolvedString}"
msgstr ""
@@ -18644,6 +19270,9 @@ msgstr ""
msgid "Reports|Failure"
msgstr ""
+msgid "Reports|Identifier"
+msgstr ""
+
msgid "Reports|Metrics reports are loading"
msgstr ""
@@ -18656,6 +19285,9 @@ msgstr ""
msgid "Reports|Metrics reports failed loading results"
msgstr ""
+msgid "Reports|Scanner"
+msgstr ""
+
msgid "Reports|Severity"
msgstr ""
@@ -18704,15 +19336,33 @@ msgstr ""
msgid "Repository cleanup has started. You will receive an email once the cleanup operation is complete."
msgstr ""
+msgid "Repository files count over the limit"
+msgstr ""
+
+msgid "Repository has an invalid default branch name."
+msgstr ""
+
+msgid "Repository has more than one branch."
+msgstr ""
+
msgid "Repository has no locks."
msgstr ""
+msgid "Repository has tags."
+msgstr ""
+
msgid "Repository maintenance"
msgstr ""
msgid "Repository mirroring"
msgstr ""
+msgid "Repository must contain at least 1 file."
+msgstr ""
+
+msgid "Repository size is above the limit."
+msgstr ""
+
msgid "Repository static objects"
msgstr ""
@@ -18722,7 +19372,7 @@ msgstr ""
msgid "Repository sync capacity"
msgstr ""
-msgid "Repository: %{counter_repositories} / Wikis: %{counter_wikis} / Build Artifacts: %{counter_build_artifacts} / LFS: %{counter_lfs_objects}"
+msgid "Repository: %{counter_repositories} / Wikis: %{counter_wikis} / Build Artifacts: %{counter_build_artifacts} / LFS: %{counter_lfs_objects} / Snippets: %{counter_snippets}"
msgstr ""
msgid "RepositorySettingsAccessLevel|Select"
@@ -18734,6 +19384,9 @@ msgstr ""
msgid "Request Headers"
msgstr ""
+msgid "Request details"
+msgstr ""
+
msgid "Request parameter %{param} is missing."
msgstr ""
@@ -18842,6 +19495,9 @@ msgstr ""
msgid "Reset template"
msgstr ""
+msgid "Reset to project defaults"
+msgstr ""
+
msgid "Resetting the authorization key for this project will require updating the authorization key in every alert source it is enabled in."
msgstr ""
@@ -18938,9 +19594,6 @@ msgstr ""
msgid "Resume"
msgstr ""
-msgid "Resume replication"
-msgstr ""
-
msgid "Resync"
msgstr ""
@@ -19574,6 +20227,9 @@ msgstr ""
msgid "SecurityReports|False positive"
msgstr ""
+msgid "SecurityReports|Group Security Dashboard"
+msgstr ""
+
msgid "SecurityReports|Hide dismissed"
msgstr ""
@@ -19628,10 +20284,13 @@ msgstr ""
msgid "SecurityReports|Remove project from dashboard"
msgstr ""
-msgid "SecurityReports|Report type"
+msgid "SecurityReports|Return to dashboard"
msgstr ""
-msgid "SecurityReports|Return to dashboard"
+msgid "SecurityReports|Scan details"
+msgstr ""
+
+msgid "SecurityReports|Scanner type"
msgstr ""
msgid "SecurityReports|Security Dashboard"
@@ -19688,6 +20347,9 @@ msgstr ""
msgid "SecurityReports|There was an error while generating the report."
msgstr ""
+msgid "SecurityReports|Unable to add %{invalidProjectsMessage}"
+msgstr ""
+
msgid "SecurityReports|Unable to add %{invalidProjects}"
msgstr ""
@@ -19731,7 +20393,7 @@ msgid "See what's new at GitLab"
msgstr ""
msgid "Select"
-msgstr ""
+msgstr "Odaberi"
msgid "Select Archive Format"
msgstr ""
@@ -19802,6 +20464,9 @@ msgstr ""
msgid "Select due date"
msgstr ""
+msgid "Select file"
+msgstr ""
+
msgid "Select group or project"
msgstr ""
@@ -19865,9 +20530,6 @@ msgstr ""
msgid "Select the branch you want to set as the default for this project. All merge requests and commits will automatically be made against this branch unless you specify a different one."
msgstr ""
-msgid "Select the configured storage available for new repositories to be placed on."
-msgstr ""
-
msgid "Select the custom project template source group."
msgstr ""
@@ -20052,20 +20714,14 @@ msgid "Serverless|Your repository does not have a corresponding %{startTag}serve
msgstr ""
msgid "Service"
-msgstr ""
+msgstr "Usluga"
msgid "Service Desk"
-msgstr ""
+msgstr "TehniÄka PodrÅ¡ka"
msgid "Service Desk is enabled but not yet active"
msgstr ""
-msgid "Service Desk is off"
-msgstr ""
-
-msgid "Service Desk is on"
-msgstr ""
-
msgid "Service Templates"
msgstr ""
@@ -20153,6 +20809,15 @@ msgstr ""
msgid "Set the milestone to %{milestone_reference}."
msgstr ""
+msgid "Set the number of concurrent requests this secondary node will make to the primary node while backfilling."
+msgstr ""
+
+msgid "Set the synchronization and verification capacity for the secondary node."
+msgstr ""
+
+msgid "Set the timeout in seconds to send a secondary node status to the primary and IPs allowed for the secondary nodes."
+msgstr ""
+
msgid "Set time estimate"
msgstr ""
@@ -20192,6 +20857,9 @@ msgstr ""
msgid "Set weight to %{weight}."
msgstr ""
+msgid "Set what should be replicated by choosing specific projects or groups by the secondary node."
+msgstr ""
+
msgid "SetPasswordToCloneLink|set a password"
msgstr ""
@@ -20318,6 +20986,9 @@ msgstr ""
msgid "Show complete raw log"
msgstr ""
+msgid "Show details"
+msgstr ""
+
msgid "Show file browser"
msgstr ""
@@ -20327,10 +20998,13 @@ msgstr ""
msgid "Show latest version"
msgstr ""
+msgid "Show list"
+msgstr ""
+
msgid "Show me everything"
msgstr ""
-msgid "Show me how"
+msgid "Show me how to add a pipeline"
msgstr ""
msgid "Show me more advanced stuff"
@@ -20381,9 +21055,6 @@ msgstr ""
msgid "Showing all issues"
msgstr ""
-msgid "Showing all labels"
-msgstr ""
-
msgid "Showing last %{size} of log -"
msgstr ""
@@ -20495,9 +21166,6 @@ msgstr ""
msgid "Skip outdated deployment jobs"
msgstr ""
-msgid "Skip this for now"
-msgstr ""
-
msgid "Skipped"
msgstr ""
@@ -20642,6 +21310,9 @@ msgstr ""
msgid "Something went wrong while adding your award. Please try again."
msgstr ""
+msgid "Something went wrong while applying the batch of suggestions. Please try again."
+msgstr ""
+
msgid "Something went wrong while applying the suggestion. Please try again."
msgstr ""
@@ -20744,10 +21415,13 @@ msgstr ""
msgid "Something went wrong while updating a requirement."
msgstr ""
+msgid "Something went wrong while updating assignees"
+msgstr ""
+
msgid "Something went wrong while updating your list settings"
msgstr ""
-msgid "Something went wrong with your automatic subscription renewal"
+msgid "Something went wrong with your automatic subscription renewal."
msgstr ""
msgid "Something went wrong, unable to add %{project} to dashboard"
@@ -20765,6 +21439,9 @@ msgstr ""
msgid "Something went wrong, unable to search projects"
msgstr ""
+msgid "Something went wrong."
+msgstr ""
+
msgid "Something went wrong. Please try again."
msgstr ""
@@ -20948,6 +21625,9 @@ msgstr ""
msgid "Source code"
msgstr ""
+msgid "Source code (%{fileExtension})"
+msgstr ""
+
msgid "Source is not available"
msgstr ""
@@ -21032,6 +21712,9 @@ msgstr ""
msgid "Stack trace"
msgstr ""
+msgid "Stacktrace snippet"
+msgstr ""
+
msgid "Stage"
msgstr ""
@@ -21137,6 +21820,9 @@ msgstr ""
msgid "Start thread & reopen %{noteable_name}"
msgstr ""
+msgid "Start using Directed Acyclic Graphs (DAG)"
+msgstr ""
+
msgid "Start your Free Gold Trial"
msgstr ""
@@ -21599,6 +22285,21 @@ msgstr ""
msgid "SuggestedColors|Very pale orange"
msgstr ""
+msgid "Suggestion is not applicable as the suggestion was not found."
+msgstr ""
+
+msgid "Suggestions are not applicable as one or more suggestions were not found."
+msgstr ""
+
+msgid "Suggestions are not applicable as their lines cannot overlap."
+msgstr ""
+
+msgid "Suggestions must all be on the same branch."
+msgstr ""
+
+msgid "Suggestions that change line count can't be added to batches, yet."
+msgstr ""
+
msgid "Suggestions:"
msgstr ""
@@ -21641,9 +22342,6 @@ msgstr ""
msgid "Synchronization disabled"
msgstr ""
-msgid "Synchronization of container repositories is disabled."
-msgstr ""
-
msgid "System"
msgstr ""
@@ -21798,7 +22496,7 @@ msgid "Telephone number (Optional)"
msgstr ""
msgid "Template"
-msgstr ""
+msgstr "Å ablon"
msgid "Template to append to all Service Desk issues"
msgstr ""
@@ -21824,6 +22522,36 @@ msgstr ""
msgid "Terms of Service and Privacy Policy"
msgstr ""
+msgid "Terraform|%{number} Terraform report failed to generate"
+msgid_plural "Terraform|%{number} Terraform reports failed to generate"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+
+msgid "Terraform|%{number} Terraform report was generated in your pipelines"
+msgid_plural "Terraform|%{number} Terraform reports were generated in your pipelines"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+
+msgid "Terraform|A Terraform report failed to generate."
+msgstr ""
+
+msgid "Terraform|A Terraform report was generated in your pipelines."
+msgstr ""
+
+msgid "Terraform|Generating the report caused an error."
+msgstr ""
+
+msgid "Terraform|Reported Resource Changes: %{addNum} to add, %{changeNum} to change, %{deleteNum} to delete"
+msgstr ""
+
+msgid "Terraform|The Terraform report %{name} failed to generate."
+msgstr ""
+
+msgid "Terraform|The Terraform report %{name} was generated in your pipelines."
+msgstr ""
+
msgid "Test"
msgstr ""
@@ -21851,7 +22579,7 @@ msgstr ""
msgid "TestHooks|Ensure the project has CI pipelines."
msgstr ""
-msgid "TestHooks|Ensure the project has at least one commit."
+msgid "TestHooks|Ensure the project has deployments."
msgstr ""
msgid "TestHooks|Ensure the project has issues."
@@ -21953,7 +22681,7 @@ msgstr ""
msgid "The Prometheus server responded with \"bad request\". Please check your queries are correct and are supported in your Prometheus version. %{documentationLink}"
msgstr ""
-msgid "The URL defined on the primary node that secondary nodes should use to contact it. Defaults to URL"
+msgid "The URL defined on the primary node that secondary nodes should use to contact it."
msgstr ""
msgid "The URL to use for connecting to Elasticsearch. Use a comma-separated list to support clustering (e.g., \"http://localhost:9200, http://localhost:9201\")."
@@ -21962,6 +22690,9 @@ msgstr ""
msgid "The X509 Certificate to use when mutual TLS is required to communicate with the external authorization service. If left blank, the server certificate is still validated when accessing over HTTPS."
msgstr ""
+msgid "The above settings apply to all projects with the selected compliance framework(s)."
+msgstr ""
+
msgid "The amount of seconds after which a request to get a secondary node status will time out."
msgstr ""
@@ -22262,9 +22993,6 @@ msgstr ""
msgid "The total stage shows the time it takes between creating an issue and deploying the code to production. The data will be automatically added once you have completed the full idea to production cycle."
msgstr ""
-msgid "The unique identifier for the Geo node. Must match %{geoNodeName} if it is set in gitlab.rb, otherwise it must match %{externalUrl} with a trailing slash"
-msgstr ""
-
msgid "The update action will time out after %{number_of_minutes} minutes. For big repositories, use a clone/push combination."
msgstr ""
@@ -22289,9 +23017,6 @@ msgstr ""
msgid "The user you are trying to deactivate has been active in the past %{minimum_inactive_days} days and cannot be deactivated"
msgstr ""
-msgid "The user-facing URL of the Geo node"
-msgstr ""
-
msgid "The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6."
msgstr ""
@@ -22382,6 +23107,9 @@ msgstr ""
msgid "There was a problem fetching project branches."
msgstr ""
+msgid "There was a problem fetching project tags."
+msgstr ""
+
msgid "There was a problem fetching project users."
msgstr ""
@@ -22433,6 +23161,9 @@ msgstr ""
msgid "There was an error fetching the %{replicableType}"
msgstr ""
+msgid "There was an error fetching the Geo Settings"
+msgstr ""
+
msgid "There was an error fetching the Node's Groups"
msgstr ""
@@ -22553,6 +23284,12 @@ msgstr ""
msgid "This %{issuable} is locked. Only <strong>project members</strong> can comment."
msgstr ""
+msgid "This %{noteableTypeText} is %{confidentialLinkStart}confidential%{linkEnd} and %{lockedLinkStart}locked%{linkEnd}."
+msgstr ""
+
+msgid "This %{noteableTypeText} is locked."
+msgstr ""
+
msgid "This %{viewer} could not be displayed because %{reason}. You can %{options} instead."
msgstr ""
@@ -22571,6 +23308,9 @@ msgstr ""
msgid "This action can lead to data loss. To prevent accidental actions we ask you to confirm your intention."
msgstr ""
+msgid "This also resolves all related threads"
+msgstr ""
+
msgid "This also resolves the discussion"
msgstr ""
@@ -22661,6 +23401,9 @@ msgstr ""
msgid "This epic does not exist or you don't have sufficient permission."
msgstr ""
+msgid "This feature is currently in beta. We invite you to %{linkStart}give feedback%{linkEnd}."
+msgstr ""
+
msgid "This feature requires local storage to be enabled"
msgstr ""
@@ -22694,7 +23437,7 @@ msgstr ""
msgid "This is a Work in Progress"
msgstr ""
-msgid "This is a confidential issue."
+msgid "This is a confidential %{noteableTypeText}."
msgstr ""
msgid "This is a delayed job to run in %{remainingTime}"
@@ -22721,9 +23464,6 @@ msgstr ""
msgid "This is your current session"
msgstr ""
-msgid "This issue is %{confidentialLinkStart}confidential%{linkEnd} and %{lockedLinkStart}locked%{linkEnd}."
-msgstr ""
-
msgid "This issue is confidential"
msgstr ""
@@ -22733,9 +23473,6 @@ msgstr ""
msgid "This issue is in a child epic of the filtered epic"
msgstr ""
-msgid "This issue is locked."
-msgstr ""
-
msgid "This job depends on other jobs with expired/erased artifacts: %{invalid_dependencies}"
msgstr ""
@@ -22835,6 +23572,9 @@ msgstr ""
msgid "This license has already expired."
msgstr ""
+msgid "This link points to external content"
+msgstr ""
+
msgid "This may expose confidential information as the selected fork is in another namespace that can have other members."
msgstr ""
@@ -22862,6 +23602,9 @@ msgstr ""
msgid "This page will be removed in a future release."
msgstr ""
+msgid "This pipeline does not use the %{codeStart}needs%{codeEnd} keyword and can't be represented as a directed acyclic graph."
+msgstr ""
+
msgid "This pipeline makes use of a predefined CI/CD configuration enabled by %{strongStart}Auto DevOps.%{strongEnd}"
msgstr ""
@@ -23090,6 +23833,9 @@ msgstr ""
msgid "Time from last commit to merge"
msgstr ""
+msgid "Time in seconds"
+msgstr ""
+
msgid "Time in seconds GitLab will wait for a response from the external service. When the service does not respond in time, access will be denied."
msgstr ""
@@ -23513,9 +24259,15 @@ msgstr ""
msgid "Total artifacts size: %{total_size}"
msgstr ""
+msgid "Total cores (CPUs)"
+msgstr ""
+
msgid "Total issues"
msgstr ""
+msgid "Total memory (GB)"
+msgstr ""
+
msgid "Total test time for all commits/merges"
msgstr ""
@@ -23717,9 +24469,12 @@ msgstr ""
msgid "U2F only works with HTTPS-enabled websites. Contact your administrator for more details."
msgstr ""
-msgid "URL"
+msgid "UI Development Kit"
msgstr ""
+msgid "URL"
+msgstr "URL"
+
msgid "URL is required"
msgstr ""
@@ -23789,9 +24544,6 @@ msgstr ""
msgid "Unable to load the merge request widget. Try reloading the page."
msgstr ""
-msgid "Unable to process group import file"
-msgstr ""
-
msgid "Unable to resolve"
msgstr ""
@@ -23807,6 +24559,9 @@ msgstr ""
msgid "Unable to sign you in to the group with SAML due to \"%{reason}\""
msgstr ""
+msgid "Unable to suggest a path. Please refresh and try again."
+msgstr ""
+
msgid "Unable to update label prioritization at this time"
msgstr ""
@@ -23822,6 +24577,9 @@ msgstr ""
msgid "Unarchiving the project will restore people's ability to make changes to it. The repository can be committed to, and issues, comments, and other entities can be created. %{strong_start}Once active, this project shows up in the search and on the dashboard.%{strong_end}"
msgstr ""
+msgid "Unassign from commenting user"
+msgstr ""
+
msgid "Unblock"
msgstr ""
@@ -23897,6 +24655,9 @@ msgstr ""
msgid "Unreachable"
msgstr ""
+msgid "Unrecognized cluster type"
+msgstr ""
+
msgid "Unresolve"
msgstr ""
@@ -23957,6 +24718,9 @@ msgstr ""
msgid "Unsubscribes from this %{quick_action_target}."
msgstr ""
+msgid "Unsupported todo type passed. Supported todo types are: %{todo_types}"
+msgstr ""
+
msgid "Until"
msgstr ""
@@ -23984,6 +24748,9 @@ msgstr ""
msgid "Update approval rule"
msgstr ""
+msgid "Update approvers"
+msgstr ""
+
msgid "Update failed"
msgstr ""
@@ -23993,6 +24760,9 @@ msgstr ""
msgid "Update it"
msgstr ""
+msgid "Update iteration"
+msgstr ""
+
msgid "Update now"
msgstr ""
@@ -24038,7 +24808,7 @@ msgstr ""
msgid "Updated at"
msgstr ""
-msgid "Updated to"
+msgid "Updated to %{linkStart}chart v%{linkEnd}"
msgstr ""
msgid "Updating"
@@ -24068,6 +24838,9 @@ msgstr ""
msgid "Upload CSV file"
msgstr ""
+msgid "Upload License"
+msgstr ""
+
msgid "Upload New File"
msgstr ""
@@ -24149,16 +24922,19 @@ msgstr ""
msgid "UsageQuota|Pipelines"
msgstr ""
+msgid "UsageQuota|Purchase more storage"
+msgstr ""
+
msgid "UsageQuota|Repositories"
msgstr ""
msgid "UsageQuota|Repository"
msgstr ""
-msgid "UsageQuota|Storage"
+msgid "UsageQuota|Snippets"
msgstr ""
-msgid "UsageQuota|Storage usage:"
+msgid "UsageQuota|Storage"
msgstr ""
msgid "UsageQuota|This namespace has no projects which use shared runners"
@@ -24191,6 +24967,12 @@ msgstr ""
msgid "UsageQuota|Wikis"
msgstr ""
+msgid "UsageQuota|You used: %{usage} %{limit}"
+msgstr ""
+
+msgid "UsageQuota|out of %{formattedLimit} of your namespace storage"
+msgstr ""
+
msgid "Use %{code_start}::%{code_end} to create a %{link_start}scoped label set%{link_end} (eg. %{code_start}priority::1%{code_end})"
msgstr ""
@@ -24257,6 +25039,9 @@ msgstr ""
msgid "User IDs"
msgstr ""
+msgid "User Lists can only be created and modified with %{linkStart}the API%{linkEnd}"
+msgstr ""
+
msgid "User OAuth applications"
msgstr ""
@@ -24281,6 +25066,9 @@ msgstr ""
msgid "User key was successfully removed."
msgstr ""
+msgid "User list %{name} will be removed. Are you sure?"
+msgstr ""
+
msgid "User map"
msgstr ""
@@ -24302,148 +25090,10 @@ msgstr ""
msgid "User was successfully updated."
msgstr ""
-msgid "UserOnboardingTour|%{activeTour}/%{totalTours}"
-msgstr ""
-
-msgid "UserOnboardingTour|%{completed}/%{total} steps completed"
-msgstr ""
-
-msgid "UserOnboardingTour|%{emphasisStart}Well done!%{emphasisEnd}%{lineBreak}%{lineBreak}That's it for our guided tour, congratulations for making it all the way to the end!%{lineBreak}%{lineBreak}We hope this gave you a good overview of GitLab and how it can help you. We'll now show you how to create your own project and invite your colleagues."
-msgstr ""
-
-msgid "UserOnboardingTour|Adding other members to a project is done through Project Settings. Click on %{emphasisStart}Settings%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Alright, that's it for Commits. Let's take a look at the %{emphasisStart}Branches%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Awesome! Now click on %{emphasisStart}Members%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Click on one of the %{emphasisStart}Compare%{emphasisEnd} buttons to compare a branch to master."
-msgstr ""
-
-msgid "UserOnboardingTour|Click on one of the %{emphasisStart}pipeline IDs%{emphasisEnd} to see the details of a pipeline."
-msgstr ""
-
-msgid "UserOnboardingTour|Click to open the latest commit to see its details."
-msgstr ""
-
-msgid "UserOnboardingTour|Close 'Learn GitLab'"
-msgstr ""
-
-msgid "UserOnboardingTour|Commits are shown in chronological order and can be filtered by the commit message or by the branch."
-msgstr ""
-
-msgid "UserOnboardingTour|Create a project"
-msgstr ""
-
-msgid "UserOnboardingTour|Exit 'Learn GitLab'"
-msgstr ""
-
-msgid "UserOnboardingTour|Got it"
-msgstr ""
-
-msgid "UserOnboardingTour|Great job! %{clapHands} We hope the tour was helpful and that you learned how to use GitLab.%{lineBreak}%{lineBreak}We'd love to get your feedback on this tour.%{lineBreak}%{lineBreak}%{emphasisStart}How helpful would you say this guided tour was?%{emphasisEnd}%{lineBreak}%{lineBreak}"
-msgstr ""
-
-msgid "UserOnboardingTour|Guided GitLab Tour"
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can compare the changes of this branch to another one. Changes are divided by files so that it's easier to see what was changed where."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can create a project from scratch, start with a template or import a repository from other platforms. Whatever you choose, we'll guide you through the process.%{lineBreak}%{lineBreak}Fill in your new project information and click on %{emphasisStart}Create Project%{emphasisEnd} to progress to the next step."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can see the breakdown of the pipelines: its stages and jobs in each of the stages and their status.%{lineBreak}%{lineBreak}Our CI/CD pipelines are quite complex, most of our users have fewer and simpler pipelines."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can see the current members of the project (just you at the moment) and invite new members.%{lineBreak}%{lineBreak}You can invite multiple members at once (existing GitLab users or invite by email) and you can also set their roles and permissions.%{lineBreak}%{lineBreak}Add a few members and click on %{emphasisStart}Add to project%{emphasisEnd} to complete this step."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can see what changes were made with this commit, on what branch and if there's a related merge request. The status of the pipeline will also show up if CI/CD is set up.%{lineBreak}%{lineBreak}You can also comment on the lines of code that were changed and start a discussion with your colleagues!"
-msgstr ""
-
-msgid "UserOnboardingTour|Here's an overview of branches in the %{emphasisStart}%{projectName}%{emphasisEnd} project. They're split into Active and Stale.%{lineBreak}%{lineBreak}From here, you can create a new merge request from a branch, or compare the branch to any other branch in the project. By default, it will compare it to the master branch."
-msgstr ""
-
-msgid "UserOnboardingTour|Invite colleagues"
-msgstr ""
-
-msgid "UserOnboardingTour|Issues are great for communicating and keeping track of progress in GitLab. These are all issues that are open in the %{emphasisStart}%{projectName}%{emphasisEnd}.%{lineBreak}%{lineBreak}You can help us improve GitLab by contributing work to issues that are labeled <span class=\"badge color-label accept-mr-label\">Accepting merge requests</span>.%{lineBreak}%{lineBreak}This list can be filtered by labels, milestones, assignees, authors... We'll show you how it looks when the list is filtered by a label."
-msgstr ""
-
-msgid "UserOnboardingTour|Learn GitLab"
-msgstr ""
-
-msgid "UserOnboardingTour|Let's take a closer look at a merge request. Click on the title of one."
-msgstr ""
-
-msgid "UserOnboardingTour|Let's take a closer look at all the commits. Click on %{emphasisStart}Commits%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Let's take a closer look at the repository of this project. Click on %{emphasisStart}Repository%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|No thanks"
-msgstr ""
-
-msgid "UserOnboardingTour|Ok, let's go"
-msgstr ""
-
-msgid "UserOnboardingTour|Ok, show me"
-msgstr ""
-
-msgid "UserOnboardingTour|Open one of the issues by clicking on its title."
-msgstr ""
-
-msgid "UserOnboardingTour|Restart this step"
-msgstr ""
-
-msgid "UserOnboardingTour|Skip this step"
-msgstr ""
-
-msgid "UserOnboardingTour|Sweet! Your project was created and is ready to be used.%{lineBreak}%{lineBreak}You can start adding files to the repository or clone it. One last thing we want to show you is how to invite your colleagues to your new project."
-msgstr ""
-
-msgid "UserOnboardingTour|Take a look. Here's a nifty menu for quickly creating issues, merge requests, snippets, projects and groups. Click on it and select \"New project\" from the \"GitLab\" section to get started."
-msgstr ""
-
-msgid "UserOnboardingTour|Thanks for taking the guided tour. Remember, if you want to go through it again, you can start %{emphasisStart}Learn GitLab%{emphasisEnd} in the help menu on the top right."
-msgstr ""
-
-msgid "UserOnboardingTour|Thanks for the feedback! %{thumbsUp}"
-msgstr ""
-
-msgid "UserOnboardingTour|That's it for issues. Let'st take a look at %{emphasisStart}Merge Requests%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|That's it for merge requests. Now for the final part of this guided tour - the %{emphasisStart}CI/CD%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|That's it for the Repository. Let's take a look at the %{emphasisStart}Issues%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|The structure of this page is very similar to issues. Status, description, discussion and the sidebar are all here.%{lineBreak}%{lineBreak}But take a look below the description and you'll notice that there's more information about the merge request, the CI/CD pipeline and the options for approving it.%{lineBreak}%{lineBreak}Alongside the discussion you can also see more information about commits in this merge request, the status of pipelines and review all changes that were made."
-msgstr ""
-
-msgid "UserOnboardingTour|There's a lot of information here but don't worry, we'll go through it.%{lineBreak}%{lineBreak}On the top you can see the status of the issue and when it was opened and by whom. Directly below it is the issue description and below that are other %{emphasisStart}related issues%{emphasisEnd} and %{emphasisStart}merge requests%{emphasisEnd} (if any). Then below that is the %{emphasisStart}discussion%{emphasisEnd}, that's where most of the communication happens.%{lineBreak}%{lineBreak}On the right, there's a sidebar where you can view/change the %{emphasisStart}assignee, milestone, due date, labels, weight%{emphasisEnd}, etc."
-msgstr ""
-
-msgid "UserOnboardingTour|These are all the CI/CD pipelines we have for our %{emphasisStart}%{projectName}%{emphasisEnd} project.%{lineBreak}%{lineBreak}Here you can see the status of each pipeline, for what commit it's running for, its stages and the status for them."
-msgstr ""
-
-msgid "UserOnboardingTour|These are all the issues that are available for community contributions. Let's take a closer look at one of them."
-msgstr ""
-
-msgid "UserOnboardingTour|This is an overview of all merge requests in this project. Similarly to the issues overview it can be filtered down by things like labels, milestones, authors, assignees, etc."
+msgid "UserList|Delete %{name}?"
msgstr ""
-msgid "UserOnboardingTour|This is the repository for the %{emphasisStart}%{projectName}%{emphasisEnd} project. All our code is stored here. Feel free to explore and take a closer look at folders and files.%{lineBreak}%{lineBreak}Above the file structure you can see the latest commit, who the author is and the status of the CI/CD pipeline.%{lineBreak}%{lineBreak}If you scroll down below the file structure, you'll find the Readme of this project. This is defined in the README.md file at the root of the repository."
-msgstr ""
-
-msgid "UserOnboardingTour|Welcome to the project overview of the %{emphasisStart}%{projectName}%{emphasisEnd} project. This is the project that we use to work on GitLab. At first, a project seems like a simple repository, but at GitLab, a project is so much more.%{lineBreak}%{lineBreak}You can create projects for hosting your codebase, use it as an issue tracker, collaborate on code, and continuously build, test, and deploy your app with built-in GitLab CI/CD."
+msgid "UserList|created %{timeago}"
msgstr ""
msgid "UserProfile|Activity"
@@ -24596,6 +25246,9 @@ msgstr ""
msgid "UsersSelect|Unassigned"
msgstr ""
+msgid "Using %{codeStart}needs%{codeEnd} allows jobs to run before their stage is reached, as soon as their individual dependencies are met, which speeds up your pipelines."
+msgstr ""
+
msgid "Using %{code_start}::%{code_end} denotes a %{link_start}scoped label set%{link_end}"
msgstr ""
@@ -24680,9 +25333,6 @@ msgstr ""
msgid "Versions"
msgstr ""
-msgid "Very helpful"
-msgstr ""
-
msgid "View Documentation"
msgstr ""
@@ -24833,6 +25483,9 @@ msgstr ""
msgid "VisibilityLevel|Unknown"
msgstr ""
+msgid "Visit settings page"
+msgstr ""
+
msgid "VisualReviewApp|%{stepStart}Step 1%{stepEnd}. Copy the following script:"
msgstr ""
@@ -24926,6 +25579,9 @@ msgstr ""
msgid "VulnerabilityManagement|Something went wrong while trying to delete the comment. Please try again later."
msgstr ""
+msgid "VulnerabilityManagement|Something went wrong while trying to refresh the vulnerability. Please try again later."
+msgstr ""
+
msgid "VulnerabilityManagement|Something went wrong while trying to retrieve the vulnerability history. Please try again later."
msgstr ""
@@ -24968,6 +25624,9 @@ msgstr ""
msgid "Vulnerability|Class"
msgstr ""
+msgid "Vulnerability|Crash Address"
+msgstr ""
+
msgid "Vulnerability|Description"
msgstr ""
@@ -25043,9 +25702,6 @@ msgstr ""
msgid "We couldn't reach the Prometheus server. Either the server no longer exists or the configuration details need updating."
msgstr ""
-msgid "We created a short guided tour that will help you learn the basics of GitLab and how it will help you be better at your job. It should only take a couple of minutes. You will be guided by two types of helpers, best recognized by their color."
-msgstr ""
-
msgid "We detected potential spam in the %{humanized_resource_name}. Please solve the reCAPTCHA to proceed."
msgstr ""
@@ -25079,6 +25735,9 @@ msgstr ""
msgid "We've found no vulnerabilities"
msgstr ""
+msgid "Web Application Firewall"
+msgstr ""
+
msgid "Web IDE"
msgstr ""
@@ -25139,9 +25798,6 @@ msgstr ""
msgid "Welcome to GitLab.com<br>@%{name}!"
msgstr ""
-msgid "Welcome to the Guided GitLab Tour"
-msgstr ""
-
msgid "Welcome to the guided GitLab tour"
msgstr ""
@@ -25190,9 +25846,6 @@ msgstr ""
msgid "While it's rare to have no vulnerabilities, it can happen. In any event, we ask that you please double check your settings to make sure you've set up your dashboard correctly."
msgstr ""
-msgid "White helpers give contextual information."
-msgstr ""
-
msgid "Who can be an approver?"
msgstr ""
@@ -25238,12 +25891,18 @@ msgstr ""
msgid "WikiEmptyIssueMessage|Suggest wiki improvement"
msgstr ""
+msgid "WikiEmptyIssueMessage|You must be a group member in order to add wiki pages. If you have suggestions for how to improve the wiki for this group, consider opening an issue in the %{issues_link}."
+msgstr ""
+
msgid "WikiEmptyIssueMessage|You must be a project member in order to add wiki pages. If you have suggestions for how to improve the wiki for this project, consider opening an issue in the %{issues_link}."
msgstr ""
msgid "WikiEmptyIssueMessage|issue tracker"
msgstr ""
+msgid "WikiEmpty|A wiki is where you can store all the details about your group. This can include why you've created it, its principles, how to use it, and so on."
+msgstr ""
+
msgid "WikiEmpty|A wiki is where you can store all the details about your project. This can include why you've created it, its principles, how to use it, and so on."
msgstr ""
@@ -25253,12 +25912,21 @@ msgstr ""
msgid "WikiEmpty|Suggest wiki improvement"
msgstr ""
+msgid "WikiEmpty|The wiki lets you write documentation for your group"
+msgstr ""
+
msgid "WikiEmpty|The wiki lets you write documentation for your project"
msgstr ""
+msgid "WikiEmpty|This group has no wiki pages"
+msgstr ""
+
msgid "WikiEmpty|This project has no wiki pages"
msgstr ""
+msgid "WikiEmpty|You must be a group member in order to add wiki pages."
+msgstr ""
+
msgid "WikiEmpty|You must be a project member in order to add wiki pages."
msgstr ""
@@ -25535,13 +26203,13 @@ msgstr ""
msgid "You can now export your security dashboard to a CSV report."
msgstr ""
-msgid "You can now submit a merge request to get this change into the original branch."
+msgid "You can now manage alert endpoint configuration in the Alerts section on the Operations settings page. Fields on this page have been deprecated."
msgstr ""
-msgid "You can now submit a merge request to get this change into the original project."
+msgid "You can now submit a merge request to get this change into the original branch."
msgstr ""
-msgid "You can only add files when you are on a branch"
+msgid "You can now submit a merge request to get this change into the original project."
msgstr ""
msgid "You can only edit files when you are on a branch"
@@ -25763,7 +26431,7 @@ msgstr ""
msgid "You need to upload a Google Takeout archive."
msgstr ""
-msgid "You reached %{usage_in_percent} of %{namespace_name}'s capacity (%{used_storage} of %{storage_limit})"
+msgid "You reached %{usage_in_percent} of %{namespace_name}'s storage capacity (%{used_storage} of %{storage_limit})"
msgstr ""
msgid "You tried to fork %{link_to_the_project} but it failed for the following reason:"
@@ -26066,13 +26734,13 @@ msgstr ""
msgid "Your subscription expired!"
msgstr ""
-msgid "Your subscription has been downgraded"
+msgid "Your subscription has been downgraded."
msgstr ""
-msgid "Your subscription will automatically renew in %{remaining_days}"
+msgid "Your subscription will automatically renew in %{remaining_days}."
msgstr ""
-msgid "Your subscription will expire in %{remaining_days}"
+msgid "Your subscription will expire in %{remaining_days}."
msgstr ""
msgid "Zoom meeting added"
@@ -26109,7 +26777,7 @@ msgid "ago"
msgstr ""
msgid "alert"
-msgstr ""
+msgstr "upozorenje"
msgid "allowed to fail"
msgstr ""
@@ -26153,9 +26821,6 @@ msgstr ""
msgid "branch name"
msgstr ""
-msgid "build pipeline reference mismatch"
-msgstr ""
-
msgid "by"
msgstr ""
@@ -26174,6 +26839,9 @@ msgstr ""
msgid "cannot block others"
msgstr ""
+msgid "cannot contain HTML/XML tags, including any word between angle brackets (<,>)."
+msgstr ""
+
msgid "cannot include leading slash or directory traversal."
msgstr ""
@@ -26204,60 +26872,6 @@ msgstr ""
msgid "ciReport|%{remainingPackagesCount} more"
msgstr ""
-msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability"
-msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities"
-msgstr[0] ""
-msgstr[1] ""
-msgstr[2] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability for the source branch only"
-msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities for the source branch only"
-msgstr[0] ""
-msgstr[1] ""
-msgstr[2] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerability"
-msgid_plural "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerabilities"
-msgstr[0] ""
-msgstr[1] ""
-msgstr[2] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new vulnerability"
-msgid_plural "ciReport|%{reportType} %{status} detected %{newCount} new vulnerabilities"
-msgstr[0] ""
-msgstr[1] ""
-msgstr[2] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities for the source branch only"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{fixedCount} fixed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} vulnerability for the source branch only"
-msgid_plural "ciReport|%{reportType} %{status} detected %{newCount} vulnerabilities for the source branch only"
-msgstr[0] ""
-msgstr[1] ""
-msgstr[2] ""
-
-msgid "ciReport|%{reportType} %{status} detected no new vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected no vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected no vulnerabilities for the source branch only"
-msgstr ""
-
msgid "ciReport|%{reportType} is loading"
msgstr ""
@@ -26276,7 +26890,7 @@ msgstr ""
msgid "ciReport|All projects"
msgstr ""
-msgid "ciReport|All report types"
+msgid "ciReport|All scanner types"
msgstr ""
msgid "ciReport|All severities"
@@ -26300,6 +26914,9 @@ msgstr ""
msgid "ciReport|Container scanning detects known vulnerabilities in your docker images."
msgstr ""
+msgid "ciReport|Coverage Fuzzing"
+msgstr ""
+
msgid "ciReport|Create a merge request to implement this solution, or download and apply the patch manually."
msgstr ""
@@ -26421,7 +27038,7 @@ msgid "closed issue"
msgstr ""
msgid "comment"
-msgstr ""
+msgstr "komentar"
msgid "commented on %{link_to_project}"
msgstr ""
@@ -26451,7 +27068,7 @@ msgid "could not read private key, is the passphrase correct?"
msgstr ""
msgid "created"
-msgstr ""
+msgstr "kreirano"
msgid "created %{timeAgo}"
msgstr ""
@@ -26459,6 +27076,9 @@ msgstr ""
msgid "customize"
msgstr ""
+msgid "data"
+msgstr ""
+
msgid "date must not be after 9999-12-31"
msgstr ""
@@ -26472,13 +27092,13 @@ msgid "default branch"
msgstr ""
msgid "deleted"
-msgstr ""
+msgstr "izbrisano"
msgid "deploy"
-msgstr ""
+msgstr "postavi"
msgid "design"
-msgstr ""
+msgstr "dizajn"
msgid "designs"
msgstr ""
@@ -26500,9 +27120,9 @@ msgstr ""
msgid "draft"
msgid_plural "drafts"
-msgstr[0] ""
-msgstr[1] ""
-msgstr[2] ""
+msgstr[0] "nacrt"
+msgstr[1] "nacrti"
+msgstr[2] "nacrti"
msgid "e.g. %{token}"
msgstr ""
@@ -26531,9 +27151,12 @@ msgstr ""
msgid "entries cannot contain HTML tags"
msgstr ""
-msgid "error"
+msgid "epic"
msgstr ""
+msgid "error"
+msgstr "greška"
+
msgid "error code:"
msgstr ""
@@ -26552,9 +27175,6 @@ msgstr ""
msgid "expires on %{timebox_due_date}"
msgstr ""
-msgid "external_url"
-msgstr ""
-
msgid "failed"
msgstr ""
@@ -26591,11 +27211,8 @@ msgstr ""
msgid "from"
msgstr ""
-msgid "geo_node_name"
-msgstr ""
-
msgid "group"
-msgstr ""
+msgstr "grupa"
msgid "groups"
msgstr ""
@@ -26615,9 +27232,6 @@ msgstr ""
msgid "https://your-bitbucket-server"
msgstr ""
-msgid "image"
-msgstr ""
-
msgid "image diff"
msgstr ""
@@ -26762,6 +27376,9 @@ msgstr ""
msgid "load it anyway"
msgstr ""
+msgid "loading"
+msgstr ""
+
msgid "locked by %{path_lock_user_name} %{created_at}"
msgstr ""
@@ -26891,9 +27508,6 @@ msgstr ""
msgid "mrWidget|Deployment statistics are not available currently"
msgstr ""
-msgid "mrWidget|Detect issues before deployment with a CI pipeline that continuously tests your code. We created a quick guide that will show you how to create one. Make your code more secure and more robust in just a minute."
-msgstr ""
-
msgid "mrWidget|Did not close"
msgstr ""
@@ -27056,6 +27670,9 @@ msgstr ""
msgid "mrWidget|To approve this merge request, please enter your password. This project requires all approvals to be authenticated."
msgstr ""
+msgid "mrWidget|Use %{linkStart}CI pipelines to test your code%{linkEnd}, simply add a GitLab CI configuration file to your project. It only takes a minute to make your code more secure and robust."
+msgstr ""
+
msgid "mrWidget|When this merge request is ready, remove the WIP: prefix from the title to allow it to be merged"
msgstr ""
@@ -27092,6 +27709,9 @@ msgstr ""
msgid "must be greater than start date"
msgstr ""
+msgid "must contain only valid frameworks"
+msgstr ""
+
msgid "my-awesome-group"
msgstr ""
@@ -27104,6 +27724,9 @@ msgstr ""
msgid "needs to be between 10 minutes and 1 month"
msgstr ""
+msgid "never"
+msgstr ""
+
msgid "never expires"
msgstr ""
@@ -27131,6 +27754,9 @@ msgstr ""
msgid "nounSeries|%{firstItem} and %{lastItem}"
msgstr ""
+msgid "nounSeries|%{item}"
+msgstr ""
+
msgid "nounSeries|%{item}, %{nextItem}"
msgstr ""
@@ -27165,7 +27791,7 @@ msgstr[1] ""
msgstr[2] ""
msgid "password"
-msgstr ""
+msgstr "lozinka"
msgid "pending comment"
msgstr ""
@@ -27208,9 +27834,9 @@ msgstr ""
msgid "project"
msgid_plural "projects"
-msgstr[0] ""
-msgstr[1] ""
-msgstr[2] ""
+msgstr[0] "projekat"
+msgstr[1] "projekti"
+msgstr[2] "projekti"
msgid "project access token"
msgstr ""
@@ -27243,7 +27869,7 @@ msgid "remaining"
msgstr ""
msgid "remove"
-msgstr ""
+msgstr "ukloni"
msgid "remove due date"
msgstr ""
@@ -27272,6 +27898,9 @@ msgstr ""
msgid "revised"
msgstr ""
+msgid "satisfied"
+msgstr ""
+
msgid "score"
msgstr ""
@@ -27302,6 +27931,9 @@ msgstr ""
msgid "severity|Unknown"
msgstr ""
+msgid "should be an array of %{object_name} objects"
+msgstr ""
+
msgid "should be greater than or equal to %{access} inherited membership from group %{group_name}"
msgstr ""
@@ -27315,7 +27947,7 @@ msgid "show less"
msgstr ""
msgid "sign in"
-msgstr ""
+msgstr "prijavi se"
msgid "sort:"
msgstr ""
@@ -27356,10 +27988,13 @@ msgstr ""
msgid "suggestPipeline|2/2: Commit your changes"
msgstr ""
-msgid "suggestPipeline|Commit the changes and your pipeline will automatically run for the first time."
+msgid "suggestPipeline|Choose %{boldStart}Code Quality%{boldEnd} to add a pipeline that tests the quality of your code."
+msgstr ""
+
+msgid "suggestPipeline|The template is ready! You can now commit it to create your first pipeline."
msgstr ""
-msgid "suggestPipeline|We recommend the %{boldStart}Code Quality%{boldEnd} template, which will add a report widget to your Merge Requests. This way you’ll learn about code quality degradations much sooner. %{footerStart} Goodbye technical debt! %{footerEnd}"
+msgid "suggestPipeline|We’re adding a GitLab CI configuration file to add a pipeline to the project. You could create it manually, but we recommend that you start with a GitLab template that works out of the box."
msgstr ""
msgid "syntax is correct"
@@ -27414,11 +28049,14 @@ msgid "user avatar"
msgstr ""
msgid "username"
-msgstr ""
+msgstr "korisniÄko ime"
msgid "uses Kubernetes clusters to deploy your code!"
msgstr ""
+msgid "v%{version} published %{timeAgo}"
+msgstr ""
+
msgid "verify ownership"
msgstr ""
diff --git a/locale/ca_ES/gitlab.po b/locale/ca_ES/gitlab.po
index c664d2013c5..0887fcbb190 100644
--- a/locale/ca_ES/gitlab.po
+++ b/locale/ca_ES/gitlab.po
@@ -2,7 +2,7 @@ msgid ""
msgstr ""
"Project-Id-Version: gitlab-ee\n"
"Report-Msgid-Bugs-To: \n"
-"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Last-Translator: \n"
"Language-Team: Catalan\n"
"Language: ca_ES\n"
"MIME-Version: 1.0\n"
@@ -10,18 +10,20 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Crowdin-Project: gitlab-ee\n"
+"X-Crowdin-Project-ID: 288872\n"
"X-Crowdin-Language: ca\n"
"X-Crowdin-File: /master/locale/gitlab.pot\n"
-"PO-Revision-Date: 2020-06-08 15:15\n"
+"X-Crowdin-File-ID: 6\n"
+"PO-Revision-Date: 2020-07-02 01:24\n"
msgid " %{start} to %{end}"
-msgstr ""
+msgstr " Des de %{start} fins %{end}"
msgid " (from %{timeoutSource})"
-msgstr ""
+msgstr " (des de %{timeoutSource})"
msgid " Collected %{time}"
-msgstr ""
+msgstr " Recollit %{time}"
msgid " Please sign in."
msgstr " Inicieu la sessió."
@@ -61,7 +63,7 @@ msgid " or <#issue id>"
msgstr " o <#issue id>"
msgid " or <&epic id>"
-msgstr ""
+msgstr " o <&epic id>"
msgid " or references (e.g. path/to/project!merge_request_id)"
msgstr " o referències (per ex. el camí/al/projecte!merge_request_id)"
@@ -76,8 +78,8 @@ msgstr[1] "%d URL escanejades"
msgid "%d approver"
msgid_plural "%d approvers"
-msgstr[0] ""
-msgstr[1] ""
+msgstr[0] "%d aprovador"
+msgstr[1] "%d aprovadors"
msgid "%d approver (you've approved)"
msgid_plural "%d approvers (you've approved)"
@@ -86,8 +88,8 @@ msgstr[1] ""
msgid "%d changed file"
msgid_plural "%d changed files"
-msgstr[0] ""
-msgstr[1] ""
+msgstr[0] "%d fitxer modificat"
+msgstr[1] "%d fitxers modificats"
msgid "%d child epic"
msgid_plural "%d child epics"
@@ -101,8 +103,8 @@ msgstr[1] ""
msgid "%d comment"
msgid_plural "%d comments"
-msgstr[0] ""
-msgstr[1] ""
+msgstr[0] "%d comentari"
+msgstr[1] "%d comentaris"
msgid "%d comment on this commit"
msgid_plural "%d comments on this commit"
@@ -116,8 +118,8 @@ msgstr[1] "%d comissions"
msgid "%d commit behind"
msgid_plural "%d commits behind"
-msgstr[0] ""
-msgstr[1] ""
+msgstr[0] "%d comissió enrere"
+msgstr[1] "%d comissions enrere"
msgid "%d commit,"
msgid_plural "%d commits,"
@@ -164,8 +166,8 @@ msgstr[1] ""
msgid "%d inaccessible merge request"
msgid_plural "%d inaccessible merge requests"
-msgstr[0] ""
-msgstr[1] ""
+msgstr[0] "%d petició de fusió inaccessible"
+msgstr[1] "%d peticions de fusió inaccessibles"
msgid "%d issue"
msgid_plural "%d issues"
@@ -197,6 +199,11 @@ msgid_plural "%d metrics"
msgstr[0] "%d mètrica"
msgstr[1] "%d mètriques"
+msgid "%d milestone"
+msgid_plural "%d milestones"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "%d minute"
msgid_plural "%d minutes"
msgstr[0] ""
@@ -214,8 +221,8 @@ msgstr[1] ""
msgid "%d project"
msgid_plural "%d projects"
-msgstr[0] ""
-msgstr[1] ""
+msgstr[0] "%d projecte"
+msgstr[1] "%d projectes"
msgid "%d request with warnings"
msgid_plural "%d requests with warnings"
@@ -242,11 +249,6 @@ msgid_plural "%d unresolved threads"
msgstr[0] ""
msgstr[1] ""
-msgid "%d url scanned"
-msgid_plural "%d urls scanned"
-msgstr[0] ""
-msgstr[1] ""
-
msgid "%d vulnerability"
msgid_plural "%d vulnerabilities"
msgstr[0] ""
@@ -254,8 +256,8 @@ msgstr[1] ""
msgid "%d vulnerability dismissed"
msgid_plural "%d vulnerabilities dismissed"
-msgstr[0] ""
-msgstr[1] ""
+msgstr[0] "%d vulnerabilitat descartada"
+msgstr[1] "%d vulnerabilitats descartades"
msgid "%s additional commit has been omitted to prevent performance issues."
msgid_plural "%s additional commits have been omitted to prevent performance issues."
@@ -295,7 +297,7 @@ msgid "%{count} files touched"
msgstr ""
msgid "%{count} more"
-msgstr ""
+msgstr "%{count} més"
msgid "%{count} more assignees"
msgstr ""
@@ -313,8 +315,8 @@ msgstr ""
msgid "%{count} participant"
msgid_plural "%{count} participants"
-msgstr[0] ""
-msgstr[1] ""
+msgstr[0] "%{count} participant"
+msgstr[1] "%{count} participants"
msgid "%{count} pending comment"
msgid_plural "%{count} pending comments"
@@ -324,9 +326,15 @@ msgstr[1] ""
msgid "%{count} related %{pluralized_subject}: %{links}"
msgstr ""
+msgid "%{dashboard_path} could not be found."
+msgstr ""
+
msgid "%{days} days until tags are automatically removed"
msgstr ""
+msgid "%{deployLinkStart}Use a template to deploy to ECS%{deployLinkEnd}, or use a docker image to %{commandsLinkStart}run AWS commands in GitLab CI/CD%{commandsLinkEnd}."
+msgstr ""
+
msgid "%{description}- Sentry event: %{errorUrl}- First seen: %{firstSeen}- Last seen: %{lastSeen} %{countLabel}: %{count}%{userCountLabel}: %{userCount}"
msgstr ""
@@ -369,12 +377,18 @@ msgstr ""
msgid "%{host} sign-in from new location"
msgstr ""
-msgid "%{icon}You are about to add %{usersTag} people to the discussion. Proceed with caution."
+msgid "%{icon}You are about to add %{usersTag} people to the discussion. They will all receive a notification."
+msgstr ""
+
+msgid "%{integrations_link_start}Integrations%{link_end} enable you to make third-party applications part of your GitLab workflow. If the available integrations don't meet your needs, consider using a %{webhooks_link_start}webhook%{link_end}."
msgstr ""
msgid "%{issuableType} will be removed! Are you sure?"
msgstr ""
+msgid "%{issuesCount} issues in this group"
+msgstr ""
+
msgid "%{issuesSize} issues"
msgstr ""
@@ -384,7 +398,7 @@ msgstr ""
msgid "%{labelStart}Class:%{labelEnd} %{class}"
msgstr ""
-msgid "%{labelStart}Confidence:%{labelEnd} %{confidence}"
+msgid "%{labelStart}Evidence:%{labelEnd} %{evidence}"
msgstr ""
msgid "%{labelStart}File:%{labelEnd} %{file}"
@@ -402,11 +416,14 @@ msgstr ""
msgid "%{labelStart}Report Type:%{labelEnd} %{reportType}"
msgstr ""
+msgid "%{labelStart}Scanner:%{labelEnd} %{scanner}"
+msgstr ""
+
msgid "%{labelStart}Severity:%{labelEnd} %{severity}"
msgstr ""
msgid "%{label_for_message} unavailable"
-msgstr ""
+msgstr "%{label_for_message} no disponible"
msgid "%{lets_encrypt_link_start}Let's Encrypt%{lets_encrypt_link_end} is a free, automated, and open certificate authority (CA), that give digital certificates in order to enable HTTPS (SSL/TLS) for websites."
msgstr ""
@@ -426,9 +443,6 @@ msgstr ""
msgid "%{link_start}Read more%{link_end} about role permissions"
msgstr ""
-msgid "%{link} can be used for binding events when something is happening within the project."
-msgstr ""
-
msgid "%{listToShow}, and %{awardsListLength} more."
msgstr ""
@@ -462,11 +476,14 @@ msgstr ""
msgid "%{name} found %{resultsString}"
msgstr ""
+msgid "%{name} is already being used for another emoji"
+msgstr ""
+
msgid "%{name} is scheduled for %{action}"
msgstr ""
msgid "%{name}'s avatar"
-msgstr ""
+msgstr "Avatar de %{name}"
msgid "%{name}(%{url}) has %{percent} or less Shared Runner Pipeline minutes remaining. Once it runs out, no new jobs or pipelines in its projects will run."
msgstr ""
@@ -476,14 +493,14 @@ msgstr ""
msgid "%{no_of_days} day"
msgid_plural "%{no_of_days} days"
-msgstr[0] ""
-msgstr[1] ""
+msgstr[0] "%{no_of_days} dia"
+msgstr[1] "%{no_of_days} dies"
msgid "%{number_commits_behind} commits behind %{default_branch}, %{number_commits_ahead} commits ahead"
-msgstr ""
+msgstr "%{number_commits_behind} comissions per darrera de %{default_branch}, %{number_commits_ahead} comissions per davant"
msgid "%{openOrClose} %{noteable}"
-msgstr ""
+msgstr "%{openOrClose} %{noteable}"
msgid "%{openedEpics} open, %{closedEpics} closed"
msgstr ""
@@ -492,21 +509,24 @@ msgid "%{openedIssues} open, %{closedIssues} closed"
msgstr ""
msgid "%{percentage}%% weight completed"
-msgstr ""
+msgstr "%{percentage}%% del pes completat"
msgid "%{percent}%% complete"
-msgstr ""
+msgstr "%{percent}%% completat"
msgid "%{percent}%{percentSymbol} complete"
msgstr ""
msgid "%{placeholder} is not a valid color scheme"
-msgstr ""
+msgstr "%{placeholder} no és un esquema de colors vàlid"
msgid "%{placeholder} is not a valid theme"
-msgstr ""
+msgstr "%{placeholder} no és un tema vàlid"
msgid "%{primary} (%{secondary})"
+msgstr "%{primary} (%{secondary})"
+
+msgid "%{ref} cannot be added: %{error}"
msgstr ""
msgid "%{releases} release"
@@ -517,29 +537,53 @@ msgstr[1] ""
msgid "%{remaining_approvals} left"
msgstr ""
-msgid "%{retryButtonStart}Try again%{retryButtonEnd} or %{newFileButtonStart}attach a new file%{newFileButtonEnd}"
+msgid "%{reportType} %{status} detected %{critical} critical and %{high} high severity vulnerabilities out of %{total}."
+msgstr ""
+
+msgid "%{reportType} %{status} detected %{critical} critical and %{high} high severity vulnerabilities."
+msgstr ""
+
+msgid "%{reportType} %{status} detected %{critical} critical severity vulnerabilities out of %{total}."
+msgstr ""
+
+msgid "%{reportType} %{status} detected %{critical} critical severity vulnerability."
+msgid_plural "%{reportType} %{status} detected %{critical} critical severity vulnerabilities."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{reportType} %{status} detected %{high} high severity vulnerabilities out of %{total}."
msgstr ""
-msgid "%{screenreaderOnlyStart}Keyboard shorcuts%{screenreaderOnlyEnd} Disabled"
+msgid "%{reportType} %{status} detected %{high} high severity vulnerability."
+msgid_plural "%{reportType} %{status} detected %{high} high severity vulnerabilities."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{reportType} %{status} detected %{other} vulnerability."
+msgid_plural "%{reportType} %{status} detected %{other} vulnerabilities."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{reportType} %{status} detected no new vulnerabilities."
msgstr ""
-msgid "%{screenreaderOnlyStart}Keyboard shorcuts%{screenreaderOnlyEnd} Enabled"
+msgid "%{retryButtonStart}Try again%{retryButtonEnd} or %{newFileButtonStart}attach a new file%{newFileButtonEnd}"
msgstr ""
msgid "%{service_title} %{message}."
msgstr ""
msgid "%{size} GiB"
-msgstr ""
+msgstr "%{size} GB"
msgid "%{size} KiB"
-msgstr ""
+msgstr "%{size} KiB"
msgid "%{size} MiB"
-msgstr ""
+msgstr "%{size} MiB"
msgid "%{size} bytes"
-msgstr ""
+msgstr "%{size} bytes"
msgid "%{spammable_titlecase} was submitted to Akismet successfully."
msgstr ""
@@ -620,6 +664,12 @@ msgstr ""
msgid "%{token}..."
msgstr ""
+msgid "%{totalCpu} (%{freeSpacePercentage}%{percentSymbol} free)"
+msgstr ""
+
+msgid "%{totalMemory} (%{freeSpacePercentage}%{percentSymbol} free)"
+msgstr ""
+
msgid "%{totalWeight} total weight"
msgstr ""
@@ -650,6 +700,12 @@ msgstr ""
msgid "%{verb} %{time_spent_value} spent time."
msgstr ""
+msgid "%{webhooks_link_start}%{webhook_type}%{link_end} enable you to send notifications to web applications in response to events in a group or project."
+msgstr ""
+
+msgid "%{webhooks_link_start}%{webhook_type}%{link_end} enable you to send notifications to web applications in response to events in a group or project. We recommend using an %{integrations_link_start}integration%{link_end} in preference to a webhook."
+msgstr ""
+
msgid "'%{level}' is not a valid visibility level"
msgstr ""
@@ -676,9 +732,6 @@ msgstr ""
msgid "(No changes)"
msgstr ""
-msgid "(Show all)"
-msgstr ""
-
msgid "(check progress)"
msgstr ""
@@ -745,6 +798,9 @@ msgstr ""
msgid "0 for unlimited"
msgstr ""
+msgid "0 for unlimited, only effective with remote storage enabled."
+msgstr ""
+
msgid "1 %{type} addition"
msgid_plural "%{count} %{type} additions"
msgstr[0] ""
@@ -787,8 +843,8 @@ msgstr[1] ""
msgid "1 merged merge request"
msgid_plural "%{merge_requests} merged merge requests"
-msgstr[0] ""
-msgstr[1] ""
+msgstr[0] "S'ha fusionat 1 petició de fusió"
+msgstr[1] "S'han fusionat %{merge_requests} peticions de fusió"
msgid "1 minute"
msgid_plural "%d minutes"
@@ -805,8 +861,8 @@ msgstr[1] ""
msgid "1 open merge request"
msgid_plural "%{merge_requests} open merge requests"
-msgstr[0] ""
-msgstr[1] ""
+msgstr[0] "1 petició de fusió oberta"
+msgstr[1] "%{merge_requests} peticions de fusió obertes"
msgid "1 pipeline"
msgid_plural "%d pipelines"
@@ -892,6 +948,12 @@ msgstr ""
msgid "<code>\"johnsmith@example.com\": \"johnsmith@example.com\"</code> will add \"By <a href=\"#\">johnsmith@example.com</a>\" to all issues and comments originally created by johnsmith@example.com. By default, the email address or username is masked to ensure the user's privacy. Use this option if you want to show the full email address."
msgstr ""
+msgid "<code>Masked</code> to prevent the values from being displayed in job logs (must match certain regexp requirements)."
+msgstr ""
+
+msgid "<code>Protected</code> to expose them to protected branches or tags only."
+msgstr ""
+
msgid "<namespace / project>"
msgstr ""
@@ -919,9 +981,6 @@ msgstr ""
msgid "A .NET Core console application template, customizable for any .NET Core project"
msgstr ""
-msgid "A DAG must have two dependent jobs to be visualized on this tab."
-msgstr ""
-
msgid "A GitBook site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
msgstr ""
@@ -935,7 +994,7 @@ msgid "A Jekyll site that uses Netlify for CI/CD instead of GitLab, but still wi
msgstr ""
msgid "A Let's Encrypt SSL certificate can not be obtained until your domain is verified."
-msgstr ""
+msgstr "No es pot obtenir un certificat SSL de Let's Encrypt fins que no s'hagi verificat el vostre domini."
msgid "A Let's Encrypt account will be configured for this GitLab installation using your email address. You will receive emails to warn of expiring certificates."
msgstr ""
@@ -952,9 +1011,15 @@ msgstr ""
msgid "A deleted user"
msgstr ""
-msgid "A file with '%{file_name}' already exists in %{branch} branch"
+msgid "A file has been changed."
msgstr ""
+msgid "A file was not found."
+msgstr ""
+
+msgid "A file with '%{file_name}' already exists in %{branch} branch"
+msgstr "Ja existeix un fitxer amb \"%{file_name}\" a la branca %{branch}"
+
msgid "A fork is a copy of a project.<br />Forking a repository allows you to make changes without affecting the original project."
msgstr ""
@@ -1018,7 +1083,7 @@ msgstr ""
msgid "A subscription will trigger a new pipeline on the default branch of this project when a pipeline successfully completes for a new tag on the %{default_branch_docs} of the subscribed project."
msgstr ""
-msgid "A terraform report was generated in your pipelines."
+msgid "A suggestion is not applicable."
msgstr ""
msgid "A user with write access to the source branch selected this option"
@@ -1189,6 +1254,9 @@ msgstr ""
msgid "Action to take when receiving an alert."
msgstr ""
+msgid "Actions"
+msgstr ""
+
msgid "Activate"
msgstr ""
@@ -1245,6 +1313,9 @@ msgstr ""
msgid "Add LICENSE"
msgstr ""
+msgid "Add New Node"
+msgstr ""
+
msgid "Add README"
msgstr ""
@@ -1320,6 +1391,9 @@ msgstr ""
msgid "Add approval rule"
msgstr ""
+msgid "Add approvers"
+msgstr ""
+
msgid "Add bold text"
msgstr ""
@@ -1377,6 +1451,9 @@ msgstr ""
msgid "Add strikethrough text"
msgstr ""
+msgid "Add suggestion to batch"
+msgstr ""
+
msgid "Add system hook"
msgstr ""
@@ -1396,7 +1473,7 @@ msgid "Add to review"
msgstr ""
msgid "Add to tree"
-msgstr ""
+msgstr "Afegeix a l'arbre"
msgid "Add user(s) to the group:"
msgstr "Afegeix usuaris al grup:"
@@ -1411,7 +1488,7 @@ msgid "Add webhook"
msgstr ""
msgid "AddMember|No users specified."
-msgstr ""
+msgstr "AddMember|No s’ha especificat cap usuari."
msgid "AddMember|Too many users specified (limit is %{user_limit})"
msgstr ""
@@ -1476,6 +1553,9 @@ msgstr "Àrea d'administració"
msgid "Admin Note"
msgstr ""
+msgid "Admin Notifications"
+msgstr ""
+
msgid "Admin Overview"
msgstr "Informació general d'administració"
@@ -1587,6 +1667,9 @@ msgstr ""
msgid "AdminSettings|Integrations configured here will automatically apply to all projects on this instance."
msgstr ""
+msgid "AdminSettings|Moved to integrations"
+msgstr ""
+
msgid "AdminSettings|No required pipeline"
msgstr ""
@@ -1817,12 +1900,18 @@ msgid_plural "Alerts"
msgstr[0] ""
msgstr[1] ""
+msgid "AlertManagement| assign yourself"
+msgstr ""
+
msgid "AlertManagement|Acknowledged"
msgstr ""
msgid "AlertManagement|Alert"
msgstr ""
+msgid "AlertManagement|Alert assignee(s): %{assignees}"
+msgstr ""
+
msgid "AlertManagement|Alert detail"
msgstr ""
@@ -1838,9 +1927,15 @@ msgstr ""
msgid "AlertManagement|All alerts"
msgstr ""
+msgid "AlertManagement|Assign To"
+msgstr ""
+
msgid "AlertManagement|Assign status"
msgstr ""
+msgid "AlertManagement|Assignee"
+msgstr ""
+
msgid "AlertManagement|Assignees"
msgstr ""
@@ -1859,9 +1954,6 @@ msgstr ""
msgid "AlertManagement|Edit"
msgstr ""
-msgid "AlertManagement|End time"
-msgstr ""
-
msgid "AlertManagement|Events"
msgstr ""
@@ -1880,10 +1972,13 @@ msgstr ""
msgid "AlertManagement|More information"
msgstr ""
+msgid "AlertManagement|No Matching Results"
+msgstr ""
+
msgid "AlertManagement|No alert data to display."
msgstr ""
-msgid "AlertManagement|No alerts available to display. If you think you're seeing this message in error, refresh the page."
+msgid "AlertManagement|No alerts available to display. See %{linkStart}enabling alert management%{linkEnd} for more information on adding alerts to the list."
msgstr ""
msgid "AlertManagement|No alerts to display."
@@ -1892,6 +1987,9 @@ msgstr ""
msgid "AlertManagement|None"
msgstr ""
+msgid "AlertManagement|None -"
+msgstr ""
+
msgid "AlertManagement|Open"
msgstr ""
@@ -1928,9 +2026,18 @@ msgstr ""
msgid "AlertManagement|There was an error displaying the alerts. Confirm your endpoint's configuration details to ensure alerts appear."
msgstr ""
+msgid "AlertManagement|There was an error while updating the assignee(s) list. Please try again."
+msgstr ""
+
+msgid "AlertManagement|There was an error while updating the assignee(s) of the alert. Please try again."
+msgstr ""
+
msgid "AlertManagement|There was an error while updating the status of the alert. Please try again."
msgstr ""
+msgid "AlertManagement|This assignee cannot be assigned to this alert."
+msgstr ""
+
msgid "AlertManagement|Tool"
msgstr ""
@@ -2078,6 +2185,9 @@ msgstr ""
msgid "Allow users to request access (if visibility is public or internal)"
msgstr ""
+msgid "Allowed Geo IP"
+msgstr ""
+
msgid "Allowed email domain restriction only permitted for top-level groups"
msgstr ""
@@ -2111,6 +2221,9 @@ msgstr ""
msgid "Amazon Web Services"
msgstr ""
+msgid "Amazon Web Services Logo"
+msgstr ""
+
msgid "Amazon authentication is not %{link_start}correctly configured%{link_end}. Ask your GitLab administrator if you want to use this service."
msgstr ""
@@ -2168,10 +2281,16 @@ msgstr ""
msgid "An error occurred when updating the issue weight"
msgstr ""
+msgid "An error occurred while acknowledging the notification. Refresh the page and try again."
+msgstr ""
+
+msgid "An error occurred while adding approvers"
+msgstr ""
+
msgid "An error occurred while adding formatted title for epic"
msgstr ""
-msgid "An error occurred while checking group path"
+msgid "An error occurred while checking group path. Please refresh and try again."
msgstr ""
msgid "An error occurred while committing your changes."
@@ -2315,7 +2434,7 @@ msgstr ""
msgid "An error occurred while loading milestones"
msgstr ""
-msgid "An error occurred while loading terraform report"
+msgid "An error occurred while loading project creation UI"
msgstr ""
msgid "An error occurred while loading the data. Please try again."
@@ -2369,6 +2488,9 @@ msgstr ""
msgid "An error occurred while rendering preview broadcast message"
msgstr ""
+msgid "An error occurred while rendering the editor"
+msgstr ""
+
msgid "An error occurred while reordering issues."
msgstr ""
@@ -2465,7 +2587,7 @@ msgstr ""
msgid "Analyze your dependencies for known vulnerabilities."
msgstr ""
-msgid "Analyze your source code and git history for secrets"
+msgid "Analyze your source code and git history for secrets."
msgstr ""
msgid "Analyze your source code for known vulnerabilities."
@@ -2579,12 +2701,18 @@ msgstr ""
msgid "Apply suggestion"
msgstr ""
+msgid "Apply suggestions"
+msgstr ""
+
msgid "Apply template"
msgstr ""
msgid "Apply this approval rule to any branch or a specific protected branch."
msgstr ""
+msgid "Applying"
+msgstr ""
+
msgid "Applying a template will replace the existing issue description. Any changes you have made will be lost."
msgstr ""
@@ -2597,12 +2725,18 @@ msgstr ""
msgid "Applying multiple commands"
msgstr ""
-msgid "Applying suggestion"
+msgid "Applying suggestion..."
+msgstr ""
+
+msgid "Applying suggestions..."
msgstr ""
msgid "Approval rules"
msgstr ""
+msgid "Approval rules reset to project defaults"
+msgstr ""
+
msgid "ApprovalRuleRemove|%d member"
msgid_plural "ApprovalRuleRemove|%d members"
msgstr[0] ""
@@ -2656,6 +2790,9 @@ msgstr ""
msgid "Approved"
msgstr ""
+msgid "Approved MRs"
+msgstr ""
+
msgid "Approved by: "
msgstr ""
@@ -2869,6 +3006,9 @@ msgstr ""
msgid "Assign"
msgstr ""
+msgid "Assign Iteration"
+msgstr ""
+
msgid "Assign custom color like #FF0000"
msgstr ""
@@ -2887,6 +3027,9 @@ msgstr ""
msgid "Assign to"
msgstr ""
+msgid "Assign to commenting user"
+msgstr ""
+
msgid "Assign yourself to these issues"
msgstr ""
@@ -2913,6 +3056,9 @@ msgid_plural "%d Assignees"
msgstr[0] ""
msgstr[1] ""
+msgid "Assignee has no permissions"
+msgstr ""
+
msgid "Assignee lists not available with your current license"
msgstr ""
@@ -2922,6 +3068,9 @@ msgstr ""
msgid "Assignee(s)"
msgstr ""
+msgid "Assignees"
+msgstr ""
+
msgid "Assigns %{assignee_users_sentence}."
msgstr ""
@@ -2957,6 +3106,9 @@ msgstr ""
msgid "Audit Events is a way to keep track of important events that happened in GitLab."
msgstr ""
+msgid "Audit Log"
+msgstr ""
+
msgid "AuditEvents|(removed)"
msgstr ""
@@ -2993,6 +3145,9 @@ msgstr ""
msgid "AuditLogs|IP Address"
msgstr ""
+msgid "AuditLogs|Member Events"
+msgstr ""
+
msgid "AuditLogs|No matching %{type} found."
msgstr ""
@@ -3275,9 +3430,6 @@ msgstr ""
msgid "Badges|e.g. %{exampleUrl}"
msgstr ""
-msgid "Badge|New"
-msgstr ""
-
msgid "Balsamiq file could not be loaded."
msgstr ""
@@ -3326,6 +3478,12 @@ msgstr ""
msgid "Below you will find all the groups that are public."
msgstr ""
+msgid "Beta"
+msgstr ""
+
+msgid "Bi-weekly code coverage"
+msgstr ""
+
msgid "Billing"
msgstr "Facturació"
@@ -3386,9 +3544,15 @@ msgstr ""
msgid "Bitbucket Server Import"
msgstr ""
+msgid "Bitbucket Server import"
+msgstr ""
+
msgid "Bitbucket import"
msgstr ""
+msgid "Blame"
+msgstr ""
+
msgid "Blocked"
msgstr ""
@@ -3401,9 +3565,6 @@ msgstr ""
msgid "Blog"
msgstr ""
-msgid "Blue helpers indicate an action to be taken."
-msgstr ""
-
msgid "Board name"
msgstr ""
@@ -3602,6 +3763,9 @@ msgstr ""
msgid "Broadcast Message was successfully updated."
msgstr ""
+msgid "Broadcast Messages"
+msgstr ""
+
msgid "Browse Directory"
msgstr ""
@@ -3656,6 +3820,9 @@ msgstr ""
msgid "By %{user_name}"
msgstr ""
+msgid "By URL"
+msgstr ""
+
msgid "By default GitLab sends emails in HTML and plain text formats so mail clients can choose what format to use. Disable this option if you only want to send emails in plain text format."
msgstr ""
@@ -3761,6 +3928,9 @@ msgstr ""
msgid "Can override approvers and approvals required per merge request"
msgstr ""
+msgid "Can't apply as this line has changed or the suggestion already matches its content."
+msgstr ""
+
msgid "Can't create snippet: %{err}"
msgstr ""
@@ -3947,9 +4117,6 @@ msgstr ""
msgid "Changes are still tracked. Useful for cluster/index migrations."
msgstr ""
-msgid "Changes are unknown"
-msgstr ""
-
msgid "Changes suppressed. Click to show."
msgstr ""
@@ -4256,15 +4423,9 @@ msgstr ""
msgid "Choose what content you want to see on a group’s overview page"
msgstr ""
-msgid "Choose which groups you wish to synchronize to this secondary node"
-msgstr ""
-
msgid "Choose which repositories you want to connect and run CI/CD pipelines."
msgstr ""
-msgid "Choose which shards you wish to synchronize to this secondary node"
-msgstr ""
-
msgid "Choose your framework"
msgstr ""
@@ -4562,9 +4723,15 @@ msgstr ""
msgid "Cluster does not exist"
msgstr ""
+msgid "Cluster is required for Stages::ClusterEndpointInserter"
+msgstr ""
+
msgid "Cluster level"
msgstr ""
+msgid "Cluster type must be specificed for Stages::ClusterEndpointInserter"
+msgstr ""
+
msgid "ClusterIntegration| %{custom_domain_start}More information%{custom_domain_end}."
msgstr ""
@@ -4676,7 +4843,7 @@ msgstr ""
msgid "ClusterIntegration|Cert-Manager"
msgstr ""
-msgid "ClusterIntegration|Cert-Manager is a native Kubernetes certificate management controller that helps with issuing certificates. Installing Cert-Manager on your cluster will issue a certificate by %{letsEncrypt} and ensure that certificates are valid and up-to-date."
+msgid "ClusterIntegration|Cert-Manager is a native Kubernetes certificate management controller that helps with issuing certificates. Installing Cert-Manager on your cluster will issue a certificate by %{linkStart}Let's Encrypt%{linkEnd} and ensure that certificates are valid and up-to-date."
msgstr ""
msgid "ClusterIntegration|Certificate Authority bundle (PEM format)"
@@ -4703,9 +4870,6 @@ msgstr ""
msgid "ClusterIntegration|Clear the local cache of namespace and service accounts. This is necessary if your integration has become out of sync. The cache is repopulated during the next CI job that requires namespace and service accounts."
msgstr ""
-msgid "ClusterIntegration|Cloud Run"
-msgstr ""
-
msgid "ClusterIntegration|Cluster being created"
msgstr ""
@@ -4790,7 +4954,7 @@ msgstr ""
msgid "ClusterIntegration|Crossplane"
msgstr ""
-msgid "ClusterIntegration|Crossplane enables declarative provisioning of managed services from your cloud of choice using %{codeStart}kubectl%{codeEnd} or %{gitlabIntegrationLink}. Crossplane runs inside your Kubernetes cluster and supports secure connectivity and secrets management between app containers and the cloud services they depend on."
+msgid "ClusterIntegration|Crossplane enables declarative provisioning of managed services from your cloud of choice using %{codeStart}kubectl%{codeEnd} or %{linkStart}GitLab Integration%{linkEnd}. Crossplane runs inside your Kubernetes cluster and supports secure connectivity and secrets management between app containers and the cloud services they depend on."
msgstr ""
msgid "ClusterIntegration|Deletes all GitLab resources attached to this cluster during removal"
@@ -4874,9 +5038,6 @@ msgstr ""
msgid "ClusterIntegration|GitLab-managed cluster"
msgstr ""
-msgid "ClusterIntegration|Gitlab Integration"
-msgstr ""
-
msgid "ClusterIntegration|Global default"
msgstr ""
@@ -4922,7 +5083,7 @@ msgstr ""
msgid "ClusterIntegration|Ingress gives you a way to route requests to services based on the request host or path, centralizing a number of services into a single entrypoint."
msgstr ""
-msgid "ClusterIntegration|Installing Ingress may incur additional costs. Learn more about %{pricingLink}."
+msgid "ClusterIntegration|Installing Ingress may incur additional costs. Learn more about %{linkStart}pricing%{linkEnd}."
msgstr ""
msgid "ClusterIntegration|Instance cluster"
@@ -4937,7 +5098,7 @@ msgstr ""
msgid "ClusterIntegration|Issuer Email"
msgstr ""
-msgid "ClusterIntegration|Issuers represent a certificate authority. You must provide an email address for your Issuer. "
+msgid "ClusterIntegration|Issuers represent a certificate authority. You must provide an email address for your Issuer."
msgstr ""
msgid "ClusterIntegration|Jupyter Hostname"
@@ -5009,9 +5170,6 @@ msgstr ""
msgid "ClusterIntegration|Learn more about instance Kubernetes clusters"
msgstr ""
-msgid "ClusterIntegration|Let's Encrypt"
-msgstr ""
-
msgid "ClusterIntegration|Loading IAM Roles"
msgstr ""
@@ -5123,7 +5281,7 @@ msgstr ""
msgid "ClusterIntegration|Prometheus"
msgstr ""
-msgid "ClusterIntegration|Prometheus is an open-source monitoring system with %{gitlabIntegrationLink} to monitor deployed applications."
+msgid "ClusterIntegration|Prometheus is an open-source monitoring system with %{linkStart}GitLab Integration%{linkEnd} to monitor deployed applications."
msgstr ""
msgid "ClusterIntegration|Provider details"
@@ -5279,10 +5437,10 @@ msgstr ""
msgid "ClusterIntegration|Select zone to choose machine type"
msgstr ""
-msgid "ClusterIntegration|Send Cilium Logs"
+msgid "ClusterIntegration|Send Container Network Policies Logs"
msgstr ""
-msgid "ClusterIntegration|Send ModSecurity Logs"
+msgid "ClusterIntegration|Send Web Application Firewall Logs"
msgstr ""
msgid "ClusterIntegration|Service Token"
@@ -5450,15 +5608,12 @@ msgstr ""
msgid "ClusterIntegration|documentation"
msgstr ""
-msgid "ClusterIntegration|installed via %{installed_via}"
+msgid "ClusterIntegration|installed via %{linkStart}Cloud Run%{linkEnd}"
msgstr ""
msgid "ClusterIntegration|meets the requirements"
msgstr ""
-msgid "ClusterIntegration|pricing"
-msgstr ""
-
msgid "ClusterIntegration|sign up"
msgstr ""
@@ -5495,6 +5650,15 @@ msgstr ""
msgid "Code"
msgstr ""
+msgid "Code Coverage: %{coveragePercentage}%{percentSymbol}"
+msgstr ""
+
+msgid "Code Coverage| Empty code coverage data"
+msgstr ""
+
+msgid "Code Coverage|Couldn't fetch the code coverage data"
+msgstr ""
+
msgid "Code Owners"
msgstr ""
@@ -5549,7 +5713,7 @@ msgstr ""
msgid "Collapse approvers"
msgstr ""
-msgid "Collapse child epics"
+msgid "Collapse milestones"
msgstr ""
msgid "Collapse replies"
@@ -5567,6 +5731,9 @@ msgstr ""
msgid "Coming soon"
msgstr ""
+msgid "Comma-separated, e.g. '1.1.1.1, 2.2.2.0/24'"
+msgstr ""
+
msgid "Command"
msgstr ""
@@ -5737,6 +5904,12 @@ msgstr ""
msgid "Compliance framework (optional)"
msgstr ""
+msgid "Compliance frameworks"
+msgstr ""
+
+msgid "ComplianceDashboard|created by:"
+msgstr ""
+
msgid "ComplianceFramework|GDPR"
msgstr ""
@@ -5875,6 +6048,9 @@ msgstr ""
msgid "Connection timed out"
msgstr ""
+msgid "Connection timeout"
+msgstr ""
+
msgid "Contact sales to upgrade"
msgstr ""
@@ -5930,9 +6106,6 @@ msgstr ""
msgid "ContainerRegistry|CLI Commands"
msgstr ""
-msgid "ContainerRegistry|Compressed Size"
-msgstr ""
-
msgid "ContainerRegistry|Container Registry"
msgstr ""
@@ -5948,6 +6121,9 @@ msgstr ""
msgid "ContainerRegistry|Copy push command"
msgstr ""
+msgid "ContainerRegistry|Delete selected"
+msgstr ""
+
msgid "ContainerRegistry|Docker connection error"
msgstr ""
@@ -5981,16 +6157,16 @@ msgstr ""
msgid "ContainerRegistry|If you are not already logged in, you need to authenticate to the Container Registry by using your GitLab username and password. If you have %{twofaDocLinkStart}Two-Factor Authentication%{twofaDocLinkEnd} enabled, use a %{personalAccessTokensDocLinkStart}Personal Access Token%{personalAccessTokensDocLinkEnd} instead of a password."
msgstr ""
-msgid "ContainerRegistry|Image ID"
+msgid "ContainerRegistry|Image ID: %{imageId}"
msgstr ""
msgid "ContainerRegistry|Image Repositories"
msgstr ""
-msgid "ContainerRegistry|Keep and protect the images that matter most."
+msgid "ContainerRegistry|Image tags"
msgstr ""
-msgid "ContainerRegistry|Last Updated"
+msgid "ContainerRegistry|Keep and protect the images that matter most."
msgstr ""
msgid "ContainerRegistry|Login"
@@ -6005,6 +6181,9 @@ msgstr ""
msgid "ContainerRegistry|Please contact your administrator."
msgstr ""
+msgid "ContainerRegistry|Published %{timeInfo}"
+msgstr ""
+
msgid "ContainerRegistry|Push an image"
msgstr ""
@@ -6020,9 +6199,6 @@ msgstr ""
msgid "ContainerRegistry|Remove repository"
msgstr ""
-msgid "ContainerRegistry|Remove selected tags"
-msgstr ""
-
msgid "ContainerRegistry|Remove tag"
msgid_plural "ContainerRegistry|Remove tags"
msgstr[0] ""
@@ -6052,9 +6228,6 @@ msgstr ""
msgid "ContainerRegistry|Sorry, your filter produced no results."
msgstr ""
-msgid "ContainerRegistry|Tag"
-msgstr "Etiqueta"
-
msgid "ContainerRegistry|Tag expiration policy"
msgstr ""
@@ -6199,21 +6372,6 @@ msgstr ""
msgid "Control the display of third party offers."
msgstr ""
-msgid "Control the maximum concurrency of LFS/attachment backfill for this secondary node"
-msgstr ""
-
-msgid "Control the maximum concurrency of container repository operations for this Geo node"
-msgstr ""
-
-msgid "Control the maximum concurrency of repository backfill for this secondary node"
-msgstr ""
-
-msgid "Control the maximum concurrency of verification operations for this Geo node"
-msgstr ""
-
-msgid "Control the minimum interval in days that a repository should be reverified for this primary node"
-msgstr ""
-
msgid "Cookie domain"
msgstr ""
@@ -6355,6 +6513,9 @@ msgstr ""
msgid "Could not find design."
msgstr ""
+msgid "Could not find iteration"
+msgstr ""
+
msgid "Could not remove the trigger."
msgstr ""
@@ -6391,6 +6552,12 @@ msgstr ""
msgid "Coverage"
msgstr ""
+msgid "Coverage Fuzzing"
+msgstr ""
+
+msgid "Crash State"
+msgstr ""
+
msgid "Create"
msgstr ""
@@ -6439,6 +6606,9 @@ msgstr ""
msgid "Create a personal access token on your account to pull or push via %{protocol}."
msgstr ""
+msgid "Create a project pre-populated with the necessary files to get you started quickly."
+msgstr ""
+
msgid "Create an account using:"
msgstr ""
@@ -6607,6 +6777,9 @@ msgstr ""
msgid "Creates branch '%{branch_name}' and a merge request to resolve this issue."
msgstr ""
+msgid "Creating"
+msgstr ""
+
msgid "Creating epic"
msgstr ""
@@ -6658,7 +6831,10 @@ msgstr ""
msgid "Current vulnerabilities count"
msgstr ""
-msgid "CurrentUser|Buy CI minutes"
+msgid "CurrentUser|Buy Pipeline minutes"
+msgstr ""
+
+msgid "CurrentUser|One of your groups is running out"
msgstr ""
msgid "CurrentUser|Profile"
@@ -6673,6 +6849,9 @@ msgstr ""
msgid "CurrentUser|Upgrade"
msgstr ""
+msgid "Custom Attributes"
+msgstr ""
+
msgid "Custom CI configuration path"
msgstr ""
@@ -6936,12 +7115,18 @@ msgstr ""
msgid "DAG"
msgstr ""
+msgid "DAG visualization requires at least 3 dependent jobs."
+msgstr ""
+
msgid "DNS"
msgstr ""
msgid "Dashboard"
msgstr ""
+msgid "Dashboard uid not found"
+msgstr ""
+
msgid "DashboardProjects|All"
msgstr ""
@@ -6966,6 +7151,9 @@ msgstr ""
msgid "Data is still calculating..."
msgstr ""
+msgid "Datasource name not found"
+msgstr ""
+
msgid "Date"
msgstr ""
@@ -7146,6 +7334,9 @@ msgstr ""
msgid "Delete this attachment"
msgstr ""
+msgid "Delete user list"
+msgstr ""
+
msgid "Delete variable"
msgstr ""
@@ -7462,6 +7653,9 @@ msgstr ""
msgid "Deploying to"
msgstr ""
+msgid "Deploying to AWS is easy with GitLab"
+msgstr ""
+
msgid "Deployment Frequency"
msgstr ""
@@ -7824,6 +8018,9 @@ msgstr ""
msgid "Dismissed on pipeline %{pipelineLink} at %{projectLink}"
msgstr ""
+msgid "Display alerts from all your monitoring tools directly within GitLab."
+msgstr ""
+
msgid "Display name"
msgstr ""
@@ -7881,9 +8078,6 @@ msgstr ""
msgid "Don't show again"
msgstr "No ho mostris més"
-msgid "Don't worry, you can access this tour by clicking on the help icon in the top right corner and choose <strong>Learn GitLab</strong>."
-msgstr ""
-
msgid "Done"
msgstr "Fet"
@@ -8010,6 +8204,9 @@ msgstr ""
msgid "Edit Release"
msgstr ""
+msgid "Edit Slack integration"
+msgstr ""
+
msgid "Edit Snippet"
msgstr ""
@@ -8049,6 +8246,9 @@ msgstr ""
msgid "Edit issues"
msgstr ""
+msgid "Edit iteration"
+msgstr ""
+
msgid "Edit public deploy key"
msgstr ""
@@ -8097,6 +8297,9 @@ msgstr ""
msgid "Email"
msgstr "Adreça electrònica"
+msgid "Email Notification"
+msgstr ""
+
msgid "Email address"
msgstr ""
@@ -8109,6 +8312,9 @@ msgstr ""
msgid "Email not verified. Please verify your email in Salesforce."
msgstr ""
+msgid "Email notification for unknown sign-ins"
+msgstr ""
+
msgid "Email patch"
msgstr ""
@@ -8340,6 +8546,9 @@ msgstr ""
msgid "Enforce DNS rebinding attack protection"
msgstr ""
+msgid "Enforce personal access token expiration"
+msgstr ""
+
msgid "Ensure connectivity is available from the GitLab server to the Prometheus server"
msgstr ""
@@ -8409,6 +8618,9 @@ msgstr "Introduïu el títol de la petició de fusió"
msgid "Enter the name of your application, and we'll return a unique %{type}."
msgstr ""
+msgid "Enter weights for storages for new repositories."
+msgstr ""
+
msgid "Enter your password to approve"
msgstr ""
@@ -8418,15 +8630,27 @@ msgstr ""
msgid "Environment does not have deployments"
msgstr ""
+msgid "Environment is required for Stages::MetricEndpointInserter"
+msgstr ""
+
+msgid "Environment is required for Stages::VariableEndpointInserter"
+msgstr ""
+
msgid "Environment scope"
msgstr ""
+msgid "Environment variables are applied to all project environments in this instance via the Runner. You can use environment variables for passwords, secret keys, etc. Make variables available to the running application by prepending the variable key with <code>K8S_SECRET_</code>. You can set variables to be:"
+msgstr ""
+
msgid "Environment variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. Additionally, they can be masked so they are hidden in job logs, though they must match certain regexp requirements to do so. You can use environment variables for passwords, secret keys, or whatever you want."
msgstr ""
msgid "Environment variables are configured by your administrator to be %{link_start}protected%{link_end} by default"
msgstr ""
+msgid "Environment variables on this GitLab instance are configured to be %{link_start}protected%{link_end} by default"
+msgstr ""
+
msgid "Environment:"
msgstr ""
@@ -8958,6 +9182,9 @@ msgstr ""
msgid "EventFilterBy|Filter by comments"
msgstr ""
+msgid "EventFilterBy|Filter by designs"
+msgstr ""
+
msgid "EventFilterBy|Filter by epic events"
msgstr ""
@@ -9090,15 +9317,15 @@ msgstr ""
msgid "Expand approvers"
msgstr ""
-msgid "Expand child epics"
-msgstr ""
-
msgid "Expand down"
msgstr ""
msgid "Expand dropdown"
msgstr ""
+msgid "Expand milestones"
+msgstr ""
+
msgid "Expand sidebar"
msgstr ""
@@ -9114,6 +9341,9 @@ msgstr ""
msgid "Expiration date"
msgstr ""
+msgid "Expiration not enforced"
+msgstr ""
+
msgid "Expiration policy for the Container Registry is a perfect solution for keeping the Registry space down while still enjoying the full power of GitLab CI/CD."
msgstr ""
@@ -9234,6 +9464,9 @@ msgstr ""
msgid "Failed Jobs"
msgstr ""
+msgid "Failed on"
+msgstr ""
+
msgid "Failed to add a Zoom meeting"
msgstr ""
@@ -9294,13 +9527,19 @@ msgstr ""
msgid "Failed to install."
msgstr ""
+msgid "Failed to load assignees. Please try again."
+msgstr ""
+
+msgid "Failed to load authors. Please try again."
+msgstr ""
+
msgid "Failed to load emoji list."
msgstr ""
msgid "Failed to load error details from Sentry."
msgstr ""
-msgid "Failed to load errors from Sentry. Error message: %{errorMessage}"
+msgid "Failed to load errors from Sentry."
msgstr ""
msgid "Failed to load group activity metrics. Please try again."
@@ -9381,6 +9620,9 @@ msgstr ""
msgid "Failed to set due date because the date format is invalid."
msgstr ""
+msgid "Failed to set iteration on this issue. Please try again."
+msgstr ""
+
msgid "Failed to signing using smartcard authentication"
msgstr ""
@@ -9603,13 +9845,10 @@ msgstr ""
msgid "FeatureFlags|Target environments"
msgstr ""
-msgid "FeatureFlags|There are no active feature flags"
-msgstr ""
-
-msgid "FeatureFlags|There are no inactive feature flags"
+msgid "FeatureFlags|There was an error fetching the feature flags."
msgstr ""
-msgid "FeatureFlags|There was an error fetching the feature flags."
+msgid "FeatureFlags|There was an error retrieving user lists"
msgstr ""
msgid "FeatureFlags|Try again in a few moments or contact your support team."
@@ -9621,9 +9860,18 @@ msgstr ""
msgid "FeatureFlag|Delete strategy"
msgstr ""
+msgid "FeatureFlag|List"
+msgstr ""
+
msgid "FeatureFlag|Percentage"
msgstr ""
+msgid "FeatureFlag|Select a user list"
+msgstr ""
+
+msgid "FeatureFlag|There are no configured user lists"
+msgstr ""
+
msgid "FeatureFlag|Type"
msgstr ""
@@ -9780,6 +10028,12 @@ msgstr ""
msgid "Filter..."
msgstr "Filtra..."
+msgid "Find File"
+msgstr ""
+
+msgid "Find bugs in your code with coverage-guided fuzzing"
+msgstr ""
+
msgid "Find by path"
msgstr ""
@@ -10044,6 +10298,9 @@ msgstr ""
msgid "Geo allows you to replicate your GitLab instance to other geographical locations."
msgstr ""
+msgid "Geo nodes are paused using a command run on the node"
+msgstr ""
+
msgid "GeoNodeStatusEvent|%{timeAgoStr} (%{pendingEvents} events)"
msgstr ""
@@ -10128,6 +10385,9 @@ msgstr ""
msgid "GeoNodes|Not checksummed"
msgstr ""
+msgid "GeoNodes|Package files"
+msgstr ""
+
msgid "GeoNodes|Pausing replication stops the sync process. Are you sure?"
msgstr ""
@@ -10146,6 +10406,9 @@ msgstr ""
msgid "GeoNodes|Replication slots"
msgstr ""
+msgid "GeoNodes|Replication status"
+msgstr ""
+
msgid "GeoNodes|Repositories"
msgstr ""
@@ -10356,6 +10619,9 @@ msgstr ""
msgid "Geo|Synchronization failed - %{error}"
msgstr ""
+msgid "Geo|Synchronization of %{itemTitle} is disabled."
+msgstr ""
+
msgid "Geo|The database is currently %{db_lag} behind the primary node."
msgstr ""
@@ -10668,6 +10934,9 @@ msgstr ""
msgid "Go to %{link_to_google_takeout}."
msgstr ""
+msgid "Go to <strong>Issues</strong> > <strong>Boards</strong> to access your personalized learning issue board."
+msgstr ""
+
msgid "Go to Pipelines"
msgstr ""
@@ -10767,9 +11036,6 @@ msgstr ""
msgid "Go to your snippets"
msgstr ""
-msgid "Golden Tanuki"
-msgstr ""
-
msgid "Google Cloud Platform"
msgstr ""
@@ -10791,6 +11057,9 @@ msgstr ""
msgid "Grafana URL"
msgstr ""
+msgid "Grafana response contains invalid json"
+msgstr ""
+
msgid "GrafanaIntegration|API Token"
msgstr ""
@@ -10839,9 +11108,6 @@ msgstr ""
msgid "Group %{group_name} was successfully created."
msgstr ""
-msgid "Group '%{group_name}' is being imported."
-msgstr ""
-
msgid "Group Audit Events"
msgstr ""
@@ -10878,9 +11144,6 @@ msgstr ""
msgid "Group by:"
msgstr ""
-msgid "Group could not be imported: %{errors}"
-msgstr ""
-
msgid "Group description"
msgstr ""
@@ -10908,9 +11171,15 @@ msgstr ""
msgid "Group has not been marked for deletion"
msgstr ""
+msgid "Group import could not be scheduled"
+msgstr ""
+
msgid "Group info:"
msgstr "Informació del grup:"
+msgid "Group is required when cluster_type is :group"
+msgstr ""
+
msgid "Group maintainers can register group runners in the %{link}"
msgstr ""
@@ -10974,6 +11243,24 @@ msgstr ""
msgid "GroupActivyMetrics|Recent activity (last 90 days)"
msgstr ""
+msgid "GroupImport|Failed to import group."
+msgstr ""
+
+msgid "GroupImport|Group '%{group_name}' is being imported."
+msgstr ""
+
+msgid "GroupImport|Group could not be imported: %{errors}"
+msgstr ""
+
+msgid "GroupImport|Please wait while we import the group for you. Refresh at will."
+msgstr ""
+
+msgid "GroupImport|The group was successfully imported."
+msgstr ""
+
+msgid "GroupImport|Unable to process group import file"
+msgstr ""
+
msgid "GroupRoadmap|%{dateWord} – No end date"
msgstr ""
@@ -11241,6 +11528,9 @@ msgstr ""
msgid "Groups (%{groups})"
msgstr ""
+msgid "Groups and projects"
+msgstr ""
+
msgid "Groups can also be nested by creating %{subgroup_docs_link_start}subgroups%{subgroup_docs_link_end}."
msgstr ""
@@ -11286,6 +11576,33 @@ msgstr ""
msgid "GroupsEmptyState|You can manage your group member’s permissions and access to each project in the group."
msgstr ""
+msgid "GroupsNew|Contact an administrator to enable options for importing your group."
+msgstr ""
+
+msgid "GroupsNew|Create"
+msgstr ""
+
+msgid "GroupsNew|Create group"
+msgstr ""
+
+msgid "GroupsNew|GitLab group export"
+msgstr ""
+
+msgid "GroupsNew|Import"
+msgstr ""
+
+msgid "GroupsNew|Import group"
+msgstr ""
+
+msgid "GroupsNew|My Awesome Group"
+msgstr ""
+
+msgid "GroupsNew|No import options available"
+msgstr ""
+
+msgid "GroupsNew|To copy a GitLab group between installations, navigate to the group settings page for the original installation, generate an export file, and upload it here."
+msgstr ""
+
msgid "GroupsTree|Are you sure you want to leave the \"%{fullName}\" group?"
msgstr ""
@@ -11388,6 +11705,9 @@ msgstr ""
msgid "Helps reduce request volume for protected paths"
msgstr ""
+msgid "Here are all your projects in your group, including the one you just created. To start, let’s take a look at your personalized learning project which will help you learn about GitLab at your own pace."
+msgstr ""
+
msgid "Here you will find recent merge request activity"
msgstr ""
@@ -11402,6 +11722,9 @@ msgid_plural "Hide charts"
msgstr[0] ""
msgstr[1] ""
+msgid "Hide details"
+msgstr ""
+
msgid "Hide file browser"
msgstr ""
@@ -11411,6 +11734,9 @@ msgstr ""
msgid "Hide host keys manual input"
msgstr ""
+msgid "Hide list"
+msgstr ""
+
msgid "Hide marketing-related entries from help"
msgstr ""
@@ -11431,9 +11757,6 @@ msgstr[1] "Amaga els valors"
msgid "Hide values"
msgstr ""
-msgid "Hiding all labels"
-msgstr ""
-
msgid "High or unknown vulnerabilities present"
msgstr ""
@@ -11518,6 +11841,9 @@ msgstr "ID"
msgid "ID:"
msgstr ""
+msgid "IDE"
+msgstr ""
+
msgid "IDE|Allow live previews of JavaScript projects in the Web IDE using CodeSandbox Live Preview."
msgstr ""
@@ -11608,10 +11934,10 @@ msgstr ""
msgid "If enabled"
msgstr ""
-msgid "If enabled, access to projects will be validated on an external service using their classification label."
+msgid "If enabled, GitLab will handle Object Storage replication using Geo. %{linkStart}More information%{linkEnd}"
msgstr ""
-msgid "If enabled, and if object storage is enabled, GitLab will handle Object Storage replication using Geo"
+msgid "If enabled, access to projects will be validated on an external service using their classification label."
msgstr ""
msgid "If there is no previous license or if the previous license has expired, some GitLab functionality will be blocked until a new, valid license is uploaded."
@@ -11973,6 +12299,9 @@ msgid_plural "Instances"
msgstr[0] "Instància"
msgstr[1] "Instàncies"
+msgid "Instance Configuration"
+msgstr ""
+
msgid "Instance Statistics visibility"
msgstr ""
@@ -11994,9 +12323,6 @@ msgstr ""
msgid "Integrations"
msgstr ""
-msgid "Integrations allow you to integrate GitLab with other applications"
-msgstr ""
-
msgid "Integrations|All details"
msgstr ""
@@ -12108,6 +12434,9 @@ msgstr ""
msgid "Invalid query"
msgstr ""
+msgid "Invalid repository bundle for snippet with id %{snippet_id}"
+msgstr ""
+
msgid "Invalid repository path"
msgstr ""
@@ -12126,6 +12455,9 @@ msgstr ""
msgid "Invalid two-factor code."
msgstr ""
+msgid "Invalid yaml"
+msgstr ""
+
msgid "Invitation"
msgstr ""
@@ -12345,15 +12677,24 @@ msgstr ""
msgid "It seems like the Dependency Scanning job ran successfully, but no dependencies have been detected in your project."
msgstr ""
+msgid "It seems that there is currently no available data for code coverage"
+msgstr ""
+
msgid "It's you"
msgstr ""
+msgid "Iteration"
+msgstr ""
+
msgid "Iteration changed to"
msgstr ""
msgid "Iteration removed"
msgstr ""
+msgid "Iteration updated"
+msgstr ""
+
msgid "Iterations"
msgstr ""
@@ -12384,12 +12725,18 @@ msgstr ""
msgid "January"
msgstr ""
+msgid "Jira Issues"
+msgstr ""
+
msgid "Jira import is already running."
msgstr ""
msgid "Jira integration not configured."
msgstr ""
+msgid "Jira project key is not configured"
+msgstr ""
+
msgid "Jira project: %{importProject}"
msgstr ""
@@ -12570,6 +12917,9 @@ msgstr ""
msgid "Keep divergent refs"
msgstr ""
+msgid "Kerberos access denied"
+msgstr ""
+
msgid "Key"
msgstr ""
@@ -12579,10 +12929,13 @@ msgstr ""
msgid "Key: %{key}"
msgstr ""
-msgid "Keyboard Shortcuts"
+msgid "Keyboard shortcuts"
msgstr ""
-msgid "Keyboard shortcuts"
+msgid "Keys"
+msgstr ""
+
+msgid "Ki"
msgstr ""
msgid "Kubernetes"
@@ -12624,6 +12977,9 @@ msgstr ""
msgid "LDAP"
msgstr ""
+msgid "LDAP Synchronization"
+msgstr ""
+
msgid "LDAP settings"
msgstr ""
@@ -12848,12 +13204,18 @@ msgstr ""
msgid "Learn more about custom project templates"
msgstr ""
+msgid "Learn more about deploying to AWS"
+msgstr ""
+
msgid "Learn more about deploying to a cluster"
msgstr ""
msgid "Learn more about group-level project templates"
msgstr ""
+msgid "Learn more about job dependencies"
+msgstr ""
+
msgid "Learn more about signing commits"
msgstr ""
@@ -12905,9 +13267,21 @@ msgstr ""
msgid "License History"
msgstr ""
+msgid "License ID:"
+msgstr ""
+
+msgid "License URL"
+msgstr ""
+
msgid "License-Check"
msgstr ""
+msgid "LicenseCompliance|%{docLinkStart}License Approvals%{docLinkEnd} are active"
+msgstr ""
+
+msgid "LicenseCompliance|%{docLinkStart}License Approvals%{docLinkEnd} are inactive"
+msgstr ""
+
msgid "LicenseCompliance|Add a license"
msgstr ""
@@ -12929,9 +13303,15 @@ msgstr ""
msgid "LicenseCompliance|Deny"
msgstr ""
+msgid "LicenseCompliance|Learn more about %{linkStart}License Approvals%{linkEnd}"
+msgstr ""
+
msgid "LicenseCompliance|License"
msgstr ""
+msgid "LicenseCompliance|License Approvals"
+msgstr ""
+
msgid "LicenseCompliance|License Compliance detected %d license and policy violation for the source branch only; approval required"
msgid_plural "LicenseCompliance|License Compliance detected %d licenses and policy violations for the source branch only; approval required"
msgstr[0] ""
@@ -13152,6 +13532,9 @@ msgstr ""
msgid "List your Bitbucket Server repositories"
msgstr ""
+msgid "Lists"
+msgstr ""
+
msgid "Live preview"
msgstr ""
@@ -13335,6 +13718,9 @@ msgstr ""
msgid "Manage project labels"
msgstr ""
+msgid "Manage storage usage"
+msgstr ""
+
msgid "Manage two-factor authentication"
msgstr ""
@@ -13350,6 +13736,9 @@ msgstr ""
msgid "Manifest file import"
msgstr ""
+msgid "Manifest import"
+msgstr ""
+
msgid "Manual job"
msgstr ""
@@ -13497,6 +13886,12 @@ msgstr ""
msgid "Maximum field length"
msgstr ""
+msgid "Maximum file size is 2MB. Please select a smaller file."
+msgstr ""
+
+msgid "Maximum import size (MB)"
+msgstr ""
+
msgid "Maximum job timeout"
msgstr ""
@@ -13536,6 +13931,9 @@ msgstr ""
msgid "Maximum size of Elasticsearch bulk indexing requests."
msgstr ""
+msgid "Maximum size of import files."
+msgstr ""
+
msgid "Maximum size of individual attachments in comments."
msgstr ""
@@ -13587,6 +13985,9 @@ msgstr ""
msgid "Merge (when the pipeline succeeds)"
msgstr ""
+msgid "Merge Conflicts"
+msgstr ""
+
msgid "Merge Request"
msgstr "Petició de fusió"
@@ -13713,6 +14114,9 @@ msgstr ""
msgid "MergeRequests|Squash task canceled: another squash is already in progress."
msgstr ""
+msgid "MergeRequests|This project does not allow squashing commits when merge requests are accepted."
+msgstr ""
+
msgid "MergeRequests|Thread stays resolved"
msgstr ""
@@ -13866,6 +14270,12 @@ msgstr ""
msgid "MetricsSettings|Add a button to the metrics dashboard linking directly to your existing external dashboard."
msgstr ""
+msgid "MetricsSettings|Choose whether to display dashboard metrics in UTC or the user's local timezone."
+msgstr ""
+
+msgid "MetricsSettings|Dashboard timezone"
+msgstr ""
+
msgid "MetricsSettings|External dashboard URL"
msgstr ""
@@ -13875,6 +14285,12 @@ msgstr ""
msgid "MetricsSettings|Metrics Dashboard"
msgstr ""
+msgid "MetricsSettings|UTC (Coordinated Universal Time)"
+msgstr ""
+
+msgid "MetricsSettings|User's local timezone"
+msgstr ""
+
msgid "Metrics|Add metric"
msgstr ""
@@ -13890,6 +14306,9 @@ msgstr ""
msgid "Metrics|Create metric"
msgstr "Crea una mètrica"
+msgid "Metrics|Current"
+msgstr ""
+
msgid "Metrics|Delete metric"
msgstr ""
@@ -13940,6 +14359,9 @@ msgstr ""
msgid "Metrics|Max"
msgstr ""
+msgid "Metrics|Min"
+msgstr ""
+
msgid "Metrics|Must be a valid PromQL query."
msgstr ""
@@ -13955,6 +14377,9 @@ msgstr ""
msgid "Metrics|Refresh dashboard"
msgstr ""
+msgid "Metrics|Select a value"
+msgstr ""
+
msgid "Metrics|Star dashboard"
msgstr ""
@@ -13973,12 +14398,18 @@ msgstr ""
msgid "Metrics|There was an error getting annotations information."
msgstr ""
+msgid "Metrics|There was an error getting dashboard validation warnings information."
+msgstr ""
+
msgid "Metrics|There was an error getting deployment information."
msgstr ""
msgid "Metrics|There was an error getting environments information."
msgstr ""
+msgid "Metrics|There was an error getting options for variable \"%{name}\"."
+msgstr ""
+
msgid "Metrics|There was an error trying to validate your query"
msgstr ""
@@ -14021,6 +14452,9 @@ msgstr ""
msgid "Metrics|You're about to permanently delete this metric. This cannot be undone."
msgstr ""
+msgid "Metrics|Your dashboard schema is invalid. Edit the dashboard to correct the YAML schema."
+msgstr ""
+
msgid "Metrics|e.g. HTTP requests"
msgstr ""
@@ -14036,12 +14470,18 @@ msgstr ""
msgid "Metrics|e.g. req/sec"
msgstr ""
+msgid "Mi"
+msgstr ""
+
msgid "Microsoft Azure"
msgstr ""
msgid "Middleman project with Static Site Editor support"
msgstr ""
+msgid "Migrate your data from an external source like GitHub, Bitbucket, or another instance of GitLab."
+msgstr ""
+
msgid "Migrated %{success_count}/%{total_count} files."
msgstr ""
@@ -14176,6 +14616,9 @@ msgstr ""
msgid "Minimum capacity to be available before we schedule more mirrors preemptively."
msgstr ""
+msgid "Minimum interval in days"
+msgstr ""
+
msgid "Minimum length is %{minimum_password_length} characters"
msgstr ""
@@ -14362,6 +14805,12 @@ msgstr ""
msgid "Multiple uploaders found: %{uploader_types}"
msgstr ""
+msgid "Must match with the %{codeStart}external_url%{codeEnd} in %{codeStart}/etc/gitlab/gitlab.rb%{codeEnd}."
+msgstr ""
+
+msgid "Must match with the %{codeStart}geo_node_name%{codeEnd} in %{codeStart}/etc/gitlab/gitlab.rb%{codeEnd}. %{linkStart}More information%{linkEnd}"
+msgstr ""
+
msgid "My Awesome Group"
msgstr ""
@@ -14476,10 +14925,16 @@ msgstr "Nou"
msgid "New Application"
msgstr "Aplicació nova"
+msgid "New Branch"
+msgstr ""
+
+msgid "New Deploy Key"
+msgstr ""
+
msgid "New Environment"
msgstr ""
-msgid "New Geo Node"
+msgid "New File"
msgstr ""
msgid "New Group"
@@ -14496,15 +14951,15 @@ msgid_plural "New Issues"
msgstr[0] ""
msgstr[1] ""
-msgid "New Iteration"
-msgstr ""
-
msgid "New Jira import"
msgstr ""
msgid "New Label"
msgstr ""
+msgid "New Merge Request"
+msgstr ""
+
msgid "New Milestone"
msgstr ""
@@ -14523,6 +14978,9 @@ msgstr ""
msgid "New Snippet"
msgstr ""
+msgid "New User"
+msgstr ""
+
msgid "New branch"
msgstr ""
@@ -14565,6 +15023,9 @@ msgstr ""
msgid "New issue title"
msgstr ""
+msgid "New iteration"
+msgstr ""
+
msgid "New iteration created"
msgstr ""
@@ -14742,6 +15203,12 @@ msgstr ""
msgid "No grouping"
msgstr ""
+msgid "No iteration"
+msgstr ""
+
+msgid "No iterations to show"
+msgstr ""
+
msgid "No job log"
msgstr ""
@@ -14784,6 +15251,9 @@ msgstr ""
msgid "No other labels with such name or description"
msgstr ""
+msgid "No panels matching properties %{opts}"
+msgstr ""
+
msgid "No parent group"
msgstr ""
@@ -14817,6 +15287,9 @@ msgstr ""
msgid "No schedules"
msgstr ""
+msgid "No stack trace for this error"
+msgstr ""
+
msgid "No starrers matched your search"
msgstr ""
@@ -14829,7 +15302,7 @@ msgstr ""
msgid "No test coverage"
msgstr ""
-msgid "No thanks, don't show this again"
+msgid "No thanks"
msgstr ""
msgid "No vulnerabilities present"
@@ -14844,9 +15317,6 @@ msgstr ""
msgid "No, directly import the existing email addresses and usernames."
msgstr ""
-msgid "No, not interested right now"
-msgstr ""
-
msgid "No. of commits"
msgstr ""
@@ -14892,9 +15362,6 @@ msgstr ""
msgid "Not found."
msgstr ""
-msgid "Not helpful"
-msgstr ""
-
msgid "Not now"
msgstr ""
@@ -15051,6 +15518,9 @@ msgstr ""
msgid "Notifications on"
msgstr ""
+msgid "Notify users by email when sign-in location is not recognized"
+msgstr ""
+
msgid "Nov"
msgstr ""
@@ -15117,9 +15587,6 @@ msgstr ""
msgid "Oh no!"
msgstr ""
-msgid "Ok let's go"
-msgstr ""
-
msgid "Oldest first"
msgstr ""
@@ -15132,16 +15599,49 @@ msgstr ""
msgid "On track"
msgstr ""
+msgid "OnDemandScans|Attached branch"
+msgstr ""
+
+msgid "OnDemandScans|Attached branch is where the scan job runs."
+msgstr ""
+
+msgid "OnDemandScans|Could not run the scan. Please try again."
+msgstr ""
+
msgid "OnDemandScans|Create new DAST scan"
msgstr ""
+msgid "OnDemandScans|DAST will scan the target URL and any discovered sub URLs."
+msgstr ""
+
+msgid "OnDemandScans|New on-demand DAST scan"
+msgstr ""
+
msgid "OnDemandScans|On-demand Scans"
msgstr ""
+msgid "OnDemandScans|On-demand scans run outside the DevOps cycle and find vulnerabilities in your projects. %{learnMoreLinkStart}Learn more%{learnMoreLinkEnd}"
+msgstr ""
+
+msgid "OnDemandScans|Only a passive scan can be performed on demand."
+msgstr ""
+
+msgid "OnDemandScans|Passive DAST Scan"
+msgstr ""
+
+msgid "OnDemandScans|Please enter a valid URL format, ex: http://www.example.com/home"
+msgstr ""
+
+msgid "OnDemandScans|Run this scan"
+msgstr ""
+
+msgid "OnDemandScans|Scan mode"
+msgstr ""
+
msgid "OnDemandScans|Schedule or run scans immediately against target sites. Currently available on-demand scan type: DAST. %{helpLinkStart}More information%{helpLinkEnd}"
msgstr ""
-msgid "Onboarding"
+msgid "OnDemandScans|Target URL"
msgstr ""
msgid "Once imported, repositories can be mirrored over SSH. Read more %{link_start}here%{link_end}."
@@ -15167,6 +15667,9 @@ msgstr ""
msgid "One or more of you personal access tokens were revoked"
msgstr ""
+msgid "One or more of your %{provider} projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
+msgstr ""
+
msgid "One or more of your Bitbucket projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
msgstr ""
@@ -15668,6 +16171,9 @@ msgstr ""
msgid "Parent epic is not present."
msgstr ""
+msgid "Parsing error for param :embed_json. %{message}"
+msgstr ""
+
msgid "Part of merge request changes"
msgstr ""
@@ -15680,6 +16186,9 @@ msgstr ""
msgid "Passed"
msgstr ""
+msgid "Passed on"
+msgstr ""
+
msgid "Password"
msgstr ""
@@ -15764,6 +16273,9 @@ msgstr ""
msgid "Perform common operations on GitLab project"
msgstr ""
+msgid "Performance and resource management"
+msgstr ""
+
msgid "Performance optimization"
msgstr ""
@@ -15971,7 +16483,7 @@ msgstr ""
msgid "Pipelines|Something went wrong while cleaning runners cache."
msgstr ""
-msgid "Pipelines|There are currently no %{scope} pipelines."
+msgid "Pipelines|There are currently no finished pipelines."
msgstr ""
msgid "Pipelines|There are currently no pipelines."
@@ -16082,6 +16594,9 @@ msgstr ""
msgid "Pipeline|Stop pipeline #%{pipelineId}?"
msgstr ""
+msgid "Pipeline|Tag name"
+msgstr ""
+
msgid "Pipeline|Trigger author"
msgstr ""
@@ -16163,12 +16678,18 @@ msgstr ""
msgid "Please check your email (%{email}) to verify that you own this address and unlock the power of CI/CD. Didn't receive it? %{resend_link}. Wrong email address? %{update_link}."
msgstr ""
+msgid "Please choose a file"
+msgstr ""
+
msgid "Please choose a group URL with no special characters."
msgstr ""
msgid "Please complete your profile with email address"
msgstr ""
+msgid "Please convert %{linkStart}them to Git%{linkEnd}, and go through the %{linkToImportFlow} again."
+msgstr ""
+
msgid "Please convert them to %{link_to_git}, and go through the %{link_to_import_flow} again."
msgstr ""
@@ -16226,6 +16747,9 @@ msgstr ""
msgid "Please provide a valid email address."
msgstr ""
+msgid "Please provide attributes to update"
+msgstr ""
+
msgid "Please refer to <a href=\"%{docs_url}\">%{docs_url}</a>"
msgstr ""
@@ -16961,6 +17485,9 @@ msgstr ""
msgid "Project has too many %{label_for_message} to search"
msgstr ""
+msgid "Project is required when cluster_type is :project"
+msgstr ""
+
msgid "Project members"
msgstr ""
@@ -17078,7 +17605,40 @@ msgstr ""
msgid "ProjectService|Comment"
msgstr ""
-msgid "ProjectService|Comment will be posted on each event"
+msgid "ProjectService|Event will be triggered by a push to the repository"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a commit is created/updated"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a confidential issue is created/updated/closed"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a deployment finishes"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a merge request is created/updated/merged"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a new tag is pushed to the repository"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a new, unique alert is recorded"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a pipeline status changes"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a wiki page is created/updated"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when an issue is created/updated/closed"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when someone adds a comment"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when someone adds a comment on a confidential issue"
msgstr ""
msgid "ProjectService|Perform common operations on GitLab project: %{project_name}"
@@ -17129,6 +17689,9 @@ msgstr ""
msgid "ProjectSettings|Enable 'Delete source branch' option by default"
msgstr ""
+msgid "ProjectSettings|Enable merge trains and pipelines for merged results"
+msgstr ""
+
msgid "ProjectSettings|Every merge creates a merge commit"
msgstr ""
@@ -17159,7 +17722,7 @@ msgstr ""
msgid "ProjectSettings|Forks"
msgstr ""
-msgid "ProjectSettings|Git Large File Storage"
+msgid "ProjectSettings|Git Large File Storage (LFS)"
msgstr ""
msgid "ProjectSettings|Internal"
@@ -17195,9 +17758,6 @@ msgstr ""
msgid "ProjectSettings|Merge options"
msgstr ""
-msgid "ProjectSettings|Merge pipelines will try to validate the post-merge result prior to merging"
-msgstr ""
-
msgid "ProjectSettings|Merge requests"
msgstr ""
@@ -17225,6 +17785,9 @@ msgstr ""
msgid "ProjectSettings|Pipelines"
msgstr ""
+msgid "ProjectSettings|Pipelines for merge requests must be enabled in the CI/CD configuration file, or pipelines could be unresolvable or dropped"
+msgstr ""
+
msgid "ProjectSettings|Pipelines must succeed"
msgstr ""
@@ -17252,6 +17815,9 @@ msgstr ""
msgid "ProjectSettings|Show link to create/view merge request when pushing from the command line"
msgstr ""
+msgid "ProjectSettings|Skipped pipelines are considered successful"
+msgstr ""
+
msgid "ProjectSettings|Snippets"
msgstr ""
@@ -17267,6 +17833,9 @@ msgstr ""
msgid "ProjectSettings|These checks must pass before merge requests can be merged"
msgstr ""
+msgid "ProjectSettings|This introduces the risk of merging changes that will not pass the pipeline."
+msgstr ""
+
msgid "ProjectSettings|This setting is applied on the server level and can be overridden by an admin."
msgstr ""
@@ -17444,15 +18013,27 @@ msgstr ""
msgid "ProjectsNew|Blank project"
msgstr ""
+msgid "ProjectsNew|Connect your external repository to GitLab CI/CD."
+msgstr ""
+
msgid "ProjectsNew|Contact an administrator to enable options for importing your project."
msgstr ""
msgid "ProjectsNew|Create"
msgstr ""
+msgid "ProjectsNew|Create a blank project to house your files, plan your work, and collaborate on code, among other things."
+msgstr ""
+
+msgid "ProjectsNew|Create blank project"
+msgstr ""
+
msgid "ProjectsNew|Create from template"
msgstr ""
+msgid "ProjectsNew|Create new project"
+msgstr ""
+
msgid "ProjectsNew|Creating project & repository."
msgstr ""
@@ -17477,6 +18058,9 @@ msgstr ""
msgid "ProjectsNew|Project description %{tag_start}(optional)%{tag_end}"
msgstr ""
+msgid "ProjectsNew|Run CI/CD for external repository"
+msgstr ""
+
msgid "ProjectsNew|Template"
msgstr ""
@@ -17738,6 +18322,9 @@ msgstr ""
msgid "Protected Branch"
msgstr ""
+msgid "Protected Branches"
+msgstr ""
+
msgid "Protected Environment"
msgstr ""
@@ -17750,6 +18337,9 @@ msgstr ""
msgid "Protected Tag"
msgstr ""
+msgid "Protected Tags"
+msgstr ""
+
msgid "Protected branches"
msgstr ""
@@ -17849,6 +18439,9 @@ msgstr ""
msgid "Provider"
msgstr ""
+msgid "Proxy support for this API is not available currently"
+msgstr ""
+
msgid "Pseudonymizer data collection"
msgstr ""
@@ -17873,6 +18466,9 @@ msgstr ""
msgid "Publish to status page"
msgstr ""
+msgid "Published on status page"
+msgstr ""
+
msgid "Publishes this issue to the associated status page."
msgstr ""
@@ -18047,6 +18643,9 @@ msgstr ""
msgid "Recipe"
msgstr ""
+msgid "Reconfigure"
+msgstr ""
+
msgid "Recover hidden stage"
msgstr ""
@@ -18133,6 +18732,9 @@ msgstr ""
msgid "Registration|Your profile"
msgstr ""
+msgid "Regulate approvals by authors/committers, based on compliance frameworks. Can be changed only at the instance level."
+msgstr ""
+
msgid "Rejected (closed)"
msgstr ""
@@ -18183,15 +18785,24 @@ msgstr ""
msgid "ReleaseAssetLinkType|Image"
msgstr ""
+msgid "ReleaseAssetLinkType|Images"
+msgstr ""
+
msgid "ReleaseAssetLinkType|Other"
msgstr ""
msgid "ReleaseAssetLinkType|Package"
msgstr ""
+msgid "ReleaseAssetLinkType|Packages"
+msgstr ""
+
msgid "ReleaseAssetLinkType|Runbook"
msgstr ""
+msgid "ReleaseAssetLinkType|Runbooks"
+msgstr ""
+
msgid "Releases"
msgstr ""
@@ -18204,6 +18815,9 @@ msgstr ""
msgid "Releases documentation"
msgstr ""
+msgid "Releases|New Release"
+msgstr ""
+
msgid "Release|Something went wrong while getting the release details"
msgstr ""
@@ -18276,6 +18890,9 @@ msgstr ""
msgid "Remove fork relationship"
msgstr ""
+msgid "Remove from batch"
+msgstr ""
+
msgid "Remove from board"
msgstr ""
@@ -18465,6 +19082,12 @@ msgstr ""
msgid "Replication"
msgstr ""
+msgid "Replication enabled"
+msgstr ""
+
+msgid "Replication paused"
+msgstr ""
+
msgid "Reply by email"
msgstr ""
@@ -18492,15 +19115,15 @@ msgstr ""
msgid "Reported %{timeAgo} by %{reportedBy}"
msgstr ""
-msgid "Reported Resource Changes: %{addNum} to add, %{changeNum} to change, %{deleteNum} to delete"
-msgstr ""
-
msgid "Reporter"
msgstr ""
msgid "Reporting"
msgstr ""
+msgid "Reports"
+msgstr ""
+
msgid "Reports|%{combinedString} and %{resolvedString}"
msgstr ""
@@ -18539,6 +19162,9 @@ msgstr ""
msgid "Reports|Failure"
msgstr ""
+msgid "Reports|Identifier"
+msgstr ""
+
msgid "Reports|Metrics reports are loading"
msgstr ""
@@ -18551,6 +19177,9 @@ msgstr ""
msgid "Reports|Metrics reports failed loading results"
msgstr ""
+msgid "Reports|Scanner"
+msgstr ""
+
msgid "Reports|Severity"
msgstr ""
@@ -18599,15 +19228,33 @@ msgstr ""
msgid "Repository cleanup has started. You will receive an email once the cleanup operation is complete."
msgstr ""
+msgid "Repository files count over the limit"
+msgstr ""
+
+msgid "Repository has an invalid default branch name."
+msgstr ""
+
+msgid "Repository has more than one branch."
+msgstr ""
+
msgid "Repository has no locks."
msgstr ""
+msgid "Repository has tags."
+msgstr ""
+
msgid "Repository maintenance"
msgstr ""
msgid "Repository mirroring"
msgstr ""
+msgid "Repository must contain at least 1 file."
+msgstr ""
+
+msgid "Repository size is above the limit."
+msgstr ""
+
msgid "Repository static objects"
msgstr ""
@@ -18617,7 +19264,7 @@ msgstr ""
msgid "Repository sync capacity"
msgstr ""
-msgid "Repository: %{counter_repositories} / Wikis: %{counter_wikis} / Build Artifacts: %{counter_build_artifacts} / LFS: %{counter_lfs_objects}"
+msgid "Repository: %{counter_repositories} / Wikis: %{counter_wikis} / Build Artifacts: %{counter_build_artifacts} / LFS: %{counter_lfs_objects} / Snippets: %{counter_snippets}"
msgstr ""
msgid "RepositorySettingsAccessLevel|Select"
@@ -18629,6 +19276,9 @@ msgstr ""
msgid "Request Headers"
msgstr ""
+msgid "Request details"
+msgstr ""
+
msgid "Request parameter %{param} is missing."
msgstr ""
@@ -18735,6 +19385,9 @@ msgstr ""
msgid "Reset template"
msgstr ""
+msgid "Reset to project defaults"
+msgstr ""
+
msgid "Resetting the authorization key for this project will require updating the authorization key in every alert source it is enabled in."
msgstr ""
@@ -18831,9 +19484,6 @@ msgstr ""
msgid "Resume"
msgstr ""
-msgid "Resume replication"
-msgstr ""
-
msgid "Resync"
msgstr ""
@@ -19456,6 +20106,9 @@ msgstr ""
msgid "SecurityReports|False positive"
msgstr ""
+msgid "SecurityReports|Group Security Dashboard"
+msgstr ""
+
msgid "SecurityReports|Hide dismissed"
msgstr ""
@@ -19510,10 +20163,13 @@ msgstr ""
msgid "SecurityReports|Remove project from dashboard"
msgstr ""
-msgid "SecurityReports|Report type"
+msgid "SecurityReports|Return to dashboard"
msgstr ""
-msgid "SecurityReports|Return to dashboard"
+msgid "SecurityReports|Scan details"
+msgstr ""
+
+msgid "SecurityReports|Scanner type"
msgstr ""
msgid "SecurityReports|Security Dashboard"
@@ -19570,6 +20226,9 @@ msgstr ""
msgid "SecurityReports|There was an error while generating the report."
msgstr ""
+msgid "SecurityReports|Unable to add %{invalidProjectsMessage}"
+msgstr ""
+
msgid "SecurityReports|Unable to add %{invalidProjects}"
msgstr ""
@@ -19684,6 +20343,9 @@ msgstr ""
msgid "Select due date"
msgstr ""
+msgid "Select file"
+msgstr ""
+
msgid "Select group or project"
msgstr ""
@@ -19747,9 +20409,6 @@ msgstr ""
msgid "Select the branch you want to set as the default for this project. All merge requests and commits will automatically be made against this branch unless you specify a different one."
msgstr ""
-msgid "Select the configured storage available for new repositories to be placed on."
-msgstr ""
-
msgid "Select the custom project template source group."
msgstr ""
@@ -19942,12 +20601,6 @@ msgstr ""
msgid "Service Desk is enabled but not yet active"
msgstr ""
-msgid "Service Desk is off"
-msgstr ""
-
-msgid "Service Desk is on"
-msgstr ""
-
msgid "Service Templates"
msgstr ""
@@ -20035,6 +20688,15 @@ msgstr ""
msgid "Set the milestone to %{milestone_reference}."
msgstr ""
+msgid "Set the number of concurrent requests this secondary node will make to the primary node while backfilling."
+msgstr ""
+
+msgid "Set the synchronization and verification capacity for the secondary node."
+msgstr ""
+
+msgid "Set the timeout in seconds to send a secondary node status to the primary and IPs allowed for the secondary nodes."
+msgstr ""
+
msgid "Set time estimate"
msgstr ""
@@ -20074,6 +20736,9 @@ msgstr ""
msgid "Set weight to %{weight}."
msgstr ""
+msgid "Set what should be replicated by choosing specific projects or groups by the secondary node."
+msgstr ""
+
msgid "SetPasswordToCloneLink|set a password"
msgstr ""
@@ -20200,6 +20865,9 @@ msgstr ""
msgid "Show complete raw log"
msgstr ""
+msgid "Show details"
+msgstr ""
+
msgid "Show file browser"
msgstr ""
@@ -20209,10 +20877,13 @@ msgstr ""
msgid "Show latest version"
msgstr ""
+msgid "Show list"
+msgstr ""
+
msgid "Show me everything"
msgstr ""
-msgid "Show me how"
+msgid "Show me how to add a pipeline"
msgstr ""
msgid "Show me more advanced stuff"
@@ -20261,9 +20932,6 @@ msgstr ""
msgid "Showing all issues"
msgstr ""
-msgid "Showing all labels"
-msgstr ""
-
msgid "Showing last %{size} of log -"
msgstr ""
@@ -20375,9 +21043,6 @@ msgstr ""
msgid "Skip outdated deployment jobs"
msgstr ""
-msgid "Skip this for now"
-msgstr ""
-
msgid "Skipped"
msgstr ""
@@ -20522,6 +21187,9 @@ msgstr ""
msgid "Something went wrong while adding your award. Please try again."
msgstr ""
+msgid "Something went wrong while applying the batch of suggestions. Please try again."
+msgstr ""
+
msgid "Something went wrong while applying the suggestion. Please try again."
msgstr ""
@@ -20624,10 +21292,13 @@ msgstr ""
msgid "Something went wrong while updating a requirement."
msgstr ""
+msgid "Something went wrong while updating assignees"
+msgstr ""
+
msgid "Something went wrong while updating your list settings"
msgstr ""
-msgid "Something went wrong with your automatic subscription renewal"
+msgid "Something went wrong with your automatic subscription renewal."
msgstr ""
msgid "Something went wrong, unable to add %{project} to dashboard"
@@ -20645,6 +21316,9 @@ msgstr ""
msgid "Something went wrong, unable to search projects"
msgstr ""
+msgid "Something went wrong."
+msgstr ""
+
msgid "Something went wrong. Please try again."
msgstr ""
@@ -20828,6 +21502,9 @@ msgstr ""
msgid "Source code"
msgstr ""
+msgid "Source code (%{fileExtension})"
+msgstr ""
+
msgid "Source is not available"
msgstr ""
@@ -20912,6 +21589,9 @@ msgstr ""
msgid "Stack trace"
msgstr ""
+msgid "Stacktrace snippet"
+msgstr ""
+
msgid "Stage"
msgstr ""
@@ -21017,6 +21697,9 @@ msgstr ""
msgid "Start thread & reopen %{noteable_name}"
msgstr ""
+msgid "Start using Directed Acyclic Graphs (DAG)"
+msgstr ""
+
msgid "Start your Free Gold Trial"
msgstr ""
@@ -21479,6 +22162,21 @@ msgstr ""
msgid "SuggestedColors|Very pale orange"
msgstr ""
+msgid "Suggestion is not applicable as the suggestion was not found."
+msgstr ""
+
+msgid "Suggestions are not applicable as one or more suggestions were not found."
+msgstr ""
+
+msgid "Suggestions are not applicable as their lines cannot overlap."
+msgstr ""
+
+msgid "Suggestions must all be on the same branch."
+msgstr ""
+
+msgid "Suggestions that change line count can't be added to batches, yet."
+msgstr ""
+
msgid "Suggestions:"
msgstr ""
@@ -21521,9 +22219,6 @@ msgstr ""
msgid "Synchronization disabled"
msgstr ""
-msgid "Synchronization of container repositories is disabled."
-msgstr ""
-
msgid "System"
msgstr ""
@@ -21704,6 +22399,34 @@ msgstr ""
msgid "Terms of Service and Privacy Policy"
msgstr ""
+msgid "Terraform|%{number} Terraform report failed to generate"
+msgid_plural "Terraform|%{number} Terraform reports failed to generate"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Terraform|%{number} Terraform report was generated in your pipelines"
+msgid_plural "Terraform|%{number} Terraform reports were generated in your pipelines"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Terraform|A Terraform report failed to generate."
+msgstr ""
+
+msgid "Terraform|A Terraform report was generated in your pipelines."
+msgstr ""
+
+msgid "Terraform|Generating the report caused an error."
+msgstr ""
+
+msgid "Terraform|Reported Resource Changes: %{addNum} to add, %{changeNum} to change, %{deleteNum} to delete"
+msgstr ""
+
+msgid "Terraform|The Terraform report %{name} failed to generate."
+msgstr ""
+
+msgid "Terraform|The Terraform report %{name} was generated in your pipelines."
+msgstr ""
+
msgid "Test"
msgstr ""
@@ -21730,7 +22453,7 @@ msgstr ""
msgid "TestHooks|Ensure the project has CI pipelines."
msgstr ""
-msgid "TestHooks|Ensure the project has at least one commit."
+msgid "TestHooks|Ensure the project has deployments."
msgstr ""
msgid "TestHooks|Ensure the project has issues."
@@ -21831,7 +22554,7 @@ msgstr ""
msgid "The Prometheus server responded with \"bad request\". Please check your queries are correct and are supported in your Prometheus version. %{documentationLink}"
msgstr ""
-msgid "The URL defined on the primary node that secondary nodes should use to contact it. Defaults to URL"
+msgid "The URL defined on the primary node that secondary nodes should use to contact it."
msgstr ""
msgid "The URL to use for connecting to Elasticsearch. Use a comma-separated list to support clustering (e.g., \"http://localhost:9200, http://localhost:9201\")."
@@ -21840,6 +22563,9 @@ msgstr ""
msgid "The X509 Certificate to use when mutual TLS is required to communicate with the external authorization service. If left blank, the server certificate is still validated when accessing over HTTPS."
msgstr ""
+msgid "The above settings apply to all projects with the selected compliance framework(s)."
+msgstr ""
+
msgid "The amount of seconds after which a request to get a secondary node status will time out."
msgstr ""
@@ -22139,9 +22865,6 @@ msgstr ""
msgid "The total stage shows the time it takes between creating an issue and deploying the code to production. The data will be automatically added once you have completed the full idea to production cycle."
msgstr ""
-msgid "The unique identifier for the Geo node. Must match %{geoNodeName} if it is set in gitlab.rb, otherwise it must match %{externalUrl} with a trailing slash"
-msgstr ""
-
msgid "The update action will time out after %{number_of_minutes} minutes. For big repositories, use a clone/push combination."
msgstr ""
@@ -22166,9 +22889,6 @@ msgstr ""
msgid "The user you are trying to deactivate has been active in the past %{minimum_inactive_days} days and cannot be deactivated"
msgstr ""
-msgid "The user-facing URL of the Geo node"
-msgstr ""
-
msgid "The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6."
msgstr ""
@@ -22259,6 +22979,9 @@ msgstr ""
msgid "There was a problem fetching project branches."
msgstr ""
+msgid "There was a problem fetching project tags."
+msgstr ""
+
msgid "There was a problem fetching project users."
msgstr ""
@@ -22310,6 +23033,9 @@ msgstr ""
msgid "There was an error fetching the %{replicableType}"
msgstr ""
+msgid "There was an error fetching the Geo Settings"
+msgstr ""
+
msgid "There was an error fetching the Node's Groups"
msgstr ""
@@ -22430,6 +23156,12 @@ msgstr ""
msgid "This %{issuable} is locked. Only <strong>project members</strong> can comment."
msgstr ""
+msgid "This %{noteableTypeText} is %{confidentialLinkStart}confidential%{linkEnd} and %{lockedLinkStart}locked%{linkEnd}."
+msgstr ""
+
+msgid "This %{noteableTypeText} is locked."
+msgstr ""
+
msgid "This %{viewer} could not be displayed because %{reason}. You can %{options} instead."
msgstr ""
@@ -22448,6 +23180,9 @@ msgstr ""
msgid "This action can lead to data loss. To prevent accidental actions we ask you to confirm your intention."
msgstr ""
+msgid "This also resolves all related threads"
+msgstr ""
+
msgid "This also resolves the discussion"
msgstr ""
@@ -22538,6 +23273,9 @@ msgstr ""
msgid "This epic does not exist or you don't have sufficient permission."
msgstr ""
+msgid "This feature is currently in beta. We invite you to %{linkStart}give feedback%{linkEnd}."
+msgstr ""
+
msgid "This feature requires local storage to be enabled"
msgstr ""
@@ -22571,7 +23309,7 @@ msgstr ""
msgid "This is a Work in Progress"
msgstr ""
-msgid "This is a confidential issue."
+msgid "This is a confidential %{noteableTypeText}."
msgstr ""
msgid "This is a delayed job to run in %{remainingTime}"
@@ -22598,9 +23336,6 @@ msgstr ""
msgid "This is your current session"
msgstr ""
-msgid "This issue is %{confidentialLinkStart}confidential%{linkEnd} and %{lockedLinkStart}locked%{linkEnd}."
-msgstr ""
-
msgid "This issue is confidential"
msgstr ""
@@ -22610,9 +23345,6 @@ msgstr ""
msgid "This issue is in a child epic of the filtered epic"
msgstr ""
-msgid "This issue is locked."
-msgstr ""
-
msgid "This job depends on other jobs with expired/erased artifacts: %{invalid_dependencies}"
msgstr ""
@@ -22712,6 +23444,9 @@ msgstr ""
msgid "This license has already expired."
msgstr ""
+msgid "This link points to external content"
+msgstr ""
+
msgid "This may expose confidential information as the selected fork is in another namespace that can have other members."
msgstr ""
@@ -22739,6 +23474,9 @@ msgstr ""
msgid "This page will be removed in a future release."
msgstr ""
+msgid "This pipeline does not use the %{codeStart}needs%{codeEnd} keyword and can't be represented as a directed acyclic graph."
+msgstr ""
+
msgid "This pipeline makes use of a predefined CI/CD configuration enabled by %{strongStart}Auto DevOps.%{strongEnd}"
msgstr ""
@@ -22967,6 +23705,9 @@ msgstr ""
msgid "Time from last commit to merge"
msgstr ""
+msgid "Time in seconds"
+msgstr ""
+
msgid "Time in seconds GitLab will wait for a response from the external service. When the service does not respond in time, access will be denied."
msgstr ""
@@ -23388,9 +24129,15 @@ msgstr ""
msgid "Total artifacts size: %{total_size}"
msgstr ""
+msgid "Total cores (CPUs)"
+msgstr ""
+
msgid "Total issues"
msgstr ""
+msgid "Total memory (GB)"
+msgstr ""
+
msgid "Total test time for all commits/merges"
msgstr ""
@@ -23592,6 +24339,9 @@ msgstr ""
msgid "U2F only works with HTTPS-enabled websites. Contact your administrator for more details."
msgstr ""
+msgid "UI Development Kit"
+msgstr ""
+
msgid "URL"
msgstr ""
@@ -23664,9 +24414,6 @@ msgstr ""
msgid "Unable to load the merge request widget. Try reloading the page."
msgstr ""
-msgid "Unable to process group import file"
-msgstr ""
-
msgid "Unable to resolve"
msgstr ""
@@ -23682,6 +24429,9 @@ msgstr ""
msgid "Unable to sign you in to the group with SAML due to \"%{reason}\""
msgstr ""
+msgid "Unable to suggest a path. Please refresh and try again."
+msgstr ""
+
msgid "Unable to update label prioritization at this time"
msgstr ""
@@ -23697,6 +24447,9 @@ msgstr ""
msgid "Unarchiving the project will restore people's ability to make changes to it. The repository can be committed to, and issues, comments, and other entities can be created. %{strong_start}Once active, this project shows up in the search and on the dashboard.%{strong_end}"
msgstr ""
+msgid "Unassign from commenting user"
+msgstr ""
+
msgid "Unblock"
msgstr ""
@@ -23772,6 +24525,9 @@ msgstr ""
msgid "Unreachable"
msgstr ""
+msgid "Unrecognized cluster type"
+msgstr ""
+
msgid "Unresolve"
msgstr ""
@@ -23832,6 +24588,9 @@ msgstr ""
msgid "Unsubscribes from this %{quick_action_target}."
msgstr ""
+msgid "Unsupported todo type passed. Supported todo types are: %{todo_types}"
+msgstr ""
+
msgid "Until"
msgstr ""
@@ -23859,6 +24618,9 @@ msgstr ""
msgid "Update approval rule"
msgstr ""
+msgid "Update approvers"
+msgstr ""
+
msgid "Update failed"
msgstr ""
@@ -23868,6 +24630,9 @@ msgstr ""
msgid "Update it"
msgstr ""
+msgid "Update iteration"
+msgstr ""
+
msgid "Update now"
msgstr ""
@@ -23913,7 +24678,7 @@ msgstr ""
msgid "Updated at"
msgstr ""
-msgid "Updated to"
+msgid "Updated to %{linkStart}chart v%{linkEnd}"
msgstr ""
msgid "Updating"
@@ -23943,6 +24708,9 @@ msgstr ""
msgid "Upload CSV file"
msgstr ""
+msgid "Upload License"
+msgstr ""
+
msgid "Upload New File"
msgstr ""
@@ -24024,16 +24792,19 @@ msgstr ""
msgid "UsageQuota|Pipelines"
msgstr ""
+msgid "UsageQuota|Purchase more storage"
+msgstr ""
+
msgid "UsageQuota|Repositories"
msgstr ""
msgid "UsageQuota|Repository"
msgstr ""
-msgid "UsageQuota|Storage"
+msgid "UsageQuota|Snippets"
msgstr ""
-msgid "UsageQuota|Storage usage:"
+msgid "UsageQuota|Storage"
msgstr ""
msgid "UsageQuota|This namespace has no projects which use shared runners"
@@ -24066,6 +24837,12 @@ msgstr ""
msgid "UsageQuota|Wikis"
msgstr ""
+msgid "UsageQuota|You used: %{usage} %{limit}"
+msgstr ""
+
+msgid "UsageQuota|out of %{formattedLimit} of your namespace storage"
+msgstr ""
+
msgid "Use %{code_start}::%{code_end} to create a %{link_start}scoped label set%{link_end} (eg. %{code_start}priority::1%{code_end})"
msgstr ""
@@ -24132,6 +24909,9 @@ msgstr ""
msgid "User IDs"
msgstr ""
+msgid "User Lists can only be created and modified with %{linkStart}the API%{linkEnd}"
+msgstr ""
+
msgid "User OAuth applications"
msgstr ""
@@ -24156,6 +24936,9 @@ msgstr ""
msgid "User key was successfully removed."
msgstr ""
+msgid "User list %{name} will be removed. Are you sure?"
+msgstr ""
+
msgid "User map"
msgstr ""
@@ -24177,148 +24960,10 @@ msgstr ""
msgid "User was successfully updated."
msgstr ""
-msgid "UserOnboardingTour|%{activeTour}/%{totalTours}"
-msgstr ""
-
-msgid "UserOnboardingTour|%{completed}/%{total} steps completed"
-msgstr ""
-
-msgid "UserOnboardingTour|%{emphasisStart}Well done!%{emphasisEnd}%{lineBreak}%{lineBreak}That's it for our guided tour, congratulations for making it all the way to the end!%{lineBreak}%{lineBreak}We hope this gave you a good overview of GitLab and how it can help you. We'll now show you how to create your own project and invite your colleagues."
-msgstr ""
-
-msgid "UserOnboardingTour|Adding other members to a project is done through Project Settings. Click on %{emphasisStart}Settings%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Alright, that's it for Commits. Let's take a look at the %{emphasisStart}Branches%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Awesome! Now click on %{emphasisStart}Members%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Click on one of the %{emphasisStart}Compare%{emphasisEnd} buttons to compare a branch to master."
-msgstr ""
-
-msgid "UserOnboardingTour|Click on one of the %{emphasisStart}pipeline IDs%{emphasisEnd} to see the details of a pipeline."
-msgstr ""
-
-msgid "UserOnboardingTour|Click to open the latest commit to see its details."
-msgstr ""
-
-msgid "UserOnboardingTour|Close 'Learn GitLab'"
-msgstr ""
-
-msgid "UserOnboardingTour|Commits are shown in chronological order and can be filtered by the commit message or by the branch."
-msgstr ""
-
-msgid "UserOnboardingTour|Create a project"
-msgstr ""
-
-msgid "UserOnboardingTour|Exit 'Learn GitLab'"
-msgstr ""
-
-msgid "UserOnboardingTour|Got it"
-msgstr ""
-
-msgid "UserOnboardingTour|Great job! %{clapHands} We hope the tour was helpful and that you learned how to use GitLab.%{lineBreak}%{lineBreak}We'd love to get your feedback on this tour.%{lineBreak}%{lineBreak}%{emphasisStart}How helpful would you say this guided tour was?%{emphasisEnd}%{lineBreak}%{lineBreak}"
-msgstr ""
-
-msgid "UserOnboardingTour|Guided GitLab Tour"
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can compare the changes of this branch to another one. Changes are divided by files so that it's easier to see what was changed where."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can create a project from scratch, start with a template or import a repository from other platforms. Whatever you choose, we'll guide you through the process.%{lineBreak}%{lineBreak}Fill in your new project information and click on %{emphasisStart}Create Project%{emphasisEnd} to progress to the next step."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can see the breakdown of the pipelines: its stages and jobs in each of the stages and their status.%{lineBreak}%{lineBreak}Our CI/CD pipelines are quite complex, most of our users have fewer and simpler pipelines."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can see the current members of the project (just you at the moment) and invite new members.%{lineBreak}%{lineBreak}You can invite multiple members at once (existing GitLab users or invite by email) and you can also set their roles and permissions.%{lineBreak}%{lineBreak}Add a few members and click on %{emphasisStart}Add to project%{emphasisEnd} to complete this step."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can see what changes were made with this commit, on what branch and if there's a related merge request. The status of the pipeline will also show up if CI/CD is set up.%{lineBreak}%{lineBreak}You can also comment on the lines of code that were changed and start a discussion with your colleagues!"
-msgstr ""
-
-msgid "UserOnboardingTour|Here's an overview of branches in the %{emphasisStart}%{projectName}%{emphasisEnd} project. They're split into Active and Stale.%{lineBreak}%{lineBreak}From here, you can create a new merge request from a branch, or compare the branch to any other branch in the project. By default, it will compare it to the master branch."
-msgstr ""
-
-msgid "UserOnboardingTour|Invite colleagues"
-msgstr ""
-
-msgid "UserOnboardingTour|Issues are great for communicating and keeping track of progress in GitLab. These are all issues that are open in the %{emphasisStart}%{projectName}%{emphasisEnd}.%{lineBreak}%{lineBreak}You can help us improve GitLab by contributing work to issues that are labeled <span class=\"badge color-label accept-mr-label\">Accepting merge requests</span>.%{lineBreak}%{lineBreak}This list can be filtered by labels, milestones, assignees, authors... We'll show you how it looks when the list is filtered by a label."
-msgstr ""
-
-msgid "UserOnboardingTour|Learn GitLab"
-msgstr ""
-
-msgid "UserOnboardingTour|Let's take a closer look at a merge request. Click on the title of one."
-msgstr ""
-
-msgid "UserOnboardingTour|Let's take a closer look at all the commits. Click on %{emphasisStart}Commits%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Let's take a closer look at the repository of this project. Click on %{emphasisStart}Repository%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|No thanks"
-msgstr ""
-
-msgid "UserOnboardingTour|Ok, let's go"
-msgstr ""
-
-msgid "UserOnboardingTour|Ok, show me"
-msgstr ""
-
-msgid "UserOnboardingTour|Open one of the issues by clicking on its title."
-msgstr ""
-
-msgid "UserOnboardingTour|Restart this step"
-msgstr ""
-
-msgid "UserOnboardingTour|Skip this step"
-msgstr ""
-
-msgid "UserOnboardingTour|Sweet! Your project was created and is ready to be used.%{lineBreak}%{lineBreak}You can start adding files to the repository or clone it. One last thing we want to show you is how to invite your colleagues to your new project."
-msgstr ""
-
-msgid "UserOnboardingTour|Take a look. Here's a nifty menu for quickly creating issues, merge requests, snippets, projects and groups. Click on it and select \"New project\" from the \"GitLab\" section to get started."
-msgstr ""
-
-msgid "UserOnboardingTour|Thanks for taking the guided tour. Remember, if you want to go through it again, you can start %{emphasisStart}Learn GitLab%{emphasisEnd} in the help menu on the top right."
-msgstr ""
-
-msgid "UserOnboardingTour|Thanks for the feedback! %{thumbsUp}"
-msgstr ""
-
-msgid "UserOnboardingTour|That's it for issues. Let'st take a look at %{emphasisStart}Merge Requests%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|That's it for merge requests. Now for the final part of this guided tour - the %{emphasisStart}CI/CD%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|That's it for the Repository. Let's take a look at the %{emphasisStart}Issues%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|The structure of this page is very similar to issues. Status, description, discussion and the sidebar are all here.%{lineBreak}%{lineBreak}But take a look below the description and you'll notice that there's more information about the merge request, the CI/CD pipeline and the options for approving it.%{lineBreak}%{lineBreak}Alongside the discussion you can also see more information about commits in this merge request, the status of pipelines and review all changes that were made."
-msgstr ""
-
-msgid "UserOnboardingTour|There's a lot of information here but don't worry, we'll go through it.%{lineBreak}%{lineBreak}On the top you can see the status of the issue and when it was opened and by whom. Directly below it is the issue description and below that are other %{emphasisStart}related issues%{emphasisEnd} and %{emphasisStart}merge requests%{emphasisEnd} (if any). Then below that is the %{emphasisStart}discussion%{emphasisEnd}, that's where most of the communication happens.%{lineBreak}%{lineBreak}On the right, there's a sidebar where you can view/change the %{emphasisStart}assignee, milestone, due date, labels, weight%{emphasisEnd}, etc."
-msgstr ""
-
-msgid "UserOnboardingTour|These are all the CI/CD pipelines we have for our %{emphasisStart}%{projectName}%{emphasisEnd} project.%{lineBreak}%{lineBreak}Here you can see the status of each pipeline, for what commit it's running for, its stages and the status for them."
+msgid "UserList|Delete %{name}?"
msgstr ""
-msgid "UserOnboardingTour|These are all the issues that are available for community contributions. Let's take a closer look at one of them."
-msgstr ""
-
-msgid "UserOnboardingTour|This is an overview of all merge requests in this project. Similarly to the issues overview it can be filtered down by things like labels, milestones, authors, assignees, etc."
-msgstr ""
-
-msgid "UserOnboardingTour|This is the repository for the %{emphasisStart}%{projectName}%{emphasisEnd} project. All our code is stored here. Feel free to explore and take a closer look at folders and files.%{lineBreak}%{lineBreak}Above the file structure you can see the latest commit, who the author is and the status of the CI/CD pipeline.%{lineBreak}%{lineBreak}If you scroll down below the file structure, you'll find the Readme of this project. This is defined in the README.md file at the root of the repository."
-msgstr ""
-
-msgid "UserOnboardingTour|Welcome to the project overview of the %{emphasisStart}%{projectName}%{emphasisEnd} project. This is the project that we use to work on GitLab. At first, a project seems like a simple repository, but at GitLab, a project is so much more.%{lineBreak}%{lineBreak}You can create projects for hosting your codebase, use it as an issue tracker, collaborate on code, and continuously build, test, and deploy your app with built-in GitLab CI/CD."
+msgid "UserList|created %{timeago}"
msgstr ""
msgid "UserProfile|Activity"
@@ -24471,6 +25116,9 @@ msgstr ""
msgid "UsersSelect|Unassigned"
msgstr ""
+msgid "Using %{codeStart}needs%{codeEnd} allows jobs to run before their stage is reached, as soon as their individual dependencies are met, which speeds up your pipelines."
+msgstr ""
+
msgid "Using %{code_start}::%{code_end} denotes a %{link_start}scoped label set%{link_end}"
msgstr ""
@@ -24555,9 +25203,6 @@ msgstr ""
msgid "Versions"
msgstr ""
-msgid "Very helpful"
-msgstr ""
-
msgid "View Documentation"
msgstr ""
@@ -24706,6 +25351,9 @@ msgstr ""
msgid "VisibilityLevel|Unknown"
msgstr ""
+msgid "Visit settings page"
+msgstr ""
+
msgid "VisualReviewApp|%{stepStart}Step 1%{stepEnd}. Copy the following script:"
msgstr ""
@@ -24799,6 +25447,9 @@ msgstr ""
msgid "VulnerabilityManagement|Something went wrong while trying to delete the comment. Please try again later."
msgstr ""
+msgid "VulnerabilityManagement|Something went wrong while trying to refresh the vulnerability. Please try again later."
+msgstr ""
+
msgid "VulnerabilityManagement|Something went wrong while trying to retrieve the vulnerability history. Please try again later."
msgstr ""
@@ -24841,6 +25492,9 @@ msgstr ""
msgid "Vulnerability|Class"
msgstr ""
+msgid "Vulnerability|Crash Address"
+msgstr ""
+
msgid "Vulnerability|Description"
msgstr ""
@@ -24916,9 +25570,6 @@ msgstr ""
msgid "We couldn't reach the Prometheus server. Either the server no longer exists or the configuration details need updating."
msgstr ""
-msgid "We created a short guided tour that will help you learn the basics of GitLab and how it will help you be better at your job. It should only take a couple of minutes. You will be guided by two types of helpers, best recognized by their color."
-msgstr ""
-
msgid "We detected potential spam in the %{humanized_resource_name}. Please solve the reCAPTCHA to proceed."
msgstr ""
@@ -24952,6 +25603,9 @@ msgstr ""
msgid "We've found no vulnerabilities"
msgstr ""
+msgid "Web Application Firewall"
+msgstr ""
+
msgid "Web IDE"
msgstr ""
@@ -25012,9 +25666,6 @@ msgstr ""
msgid "Welcome to GitLab.com<br>@%{name}!"
msgstr ""
-msgid "Welcome to the Guided GitLab Tour"
-msgstr ""
-
msgid "Welcome to the guided GitLab tour"
msgstr ""
@@ -25062,9 +25713,6 @@ msgstr ""
msgid "While it's rare to have no vulnerabilities, it can happen. In any event, we ask that you please double check your settings to make sure you've set up your dashboard correctly."
msgstr ""
-msgid "White helpers give contextual information."
-msgstr ""
-
msgid "Who can be an approver?"
msgstr ""
@@ -25110,12 +25758,18 @@ msgstr ""
msgid "WikiEmptyIssueMessage|Suggest wiki improvement"
msgstr ""
+msgid "WikiEmptyIssueMessage|You must be a group member in order to add wiki pages. If you have suggestions for how to improve the wiki for this group, consider opening an issue in the %{issues_link}."
+msgstr ""
+
msgid "WikiEmptyIssueMessage|You must be a project member in order to add wiki pages. If you have suggestions for how to improve the wiki for this project, consider opening an issue in the %{issues_link}."
msgstr ""
msgid "WikiEmptyIssueMessage|issue tracker"
msgstr ""
+msgid "WikiEmpty|A wiki is where you can store all the details about your group. This can include why you've created it, its principles, how to use it, and so on."
+msgstr ""
+
msgid "WikiEmpty|A wiki is where you can store all the details about your project. This can include why you've created it, its principles, how to use it, and so on."
msgstr ""
@@ -25125,12 +25779,21 @@ msgstr ""
msgid "WikiEmpty|Suggest wiki improvement"
msgstr ""
+msgid "WikiEmpty|The wiki lets you write documentation for your group"
+msgstr ""
+
msgid "WikiEmpty|The wiki lets you write documentation for your project"
msgstr ""
+msgid "WikiEmpty|This group has no wiki pages"
+msgstr ""
+
msgid "WikiEmpty|This project has no wiki pages"
msgstr ""
+msgid "WikiEmpty|You must be a group member in order to add wiki pages."
+msgstr ""
+
msgid "WikiEmpty|You must be a project member in order to add wiki pages."
msgstr ""
@@ -25407,13 +26070,13 @@ msgstr ""
msgid "You can now export your security dashboard to a CSV report."
msgstr ""
-msgid "You can now submit a merge request to get this change into the original branch."
+msgid "You can now manage alert endpoint configuration in the Alerts section on the Operations settings page. Fields on this page have been deprecated."
msgstr ""
-msgid "You can now submit a merge request to get this change into the original project."
+msgid "You can now submit a merge request to get this change into the original branch."
msgstr ""
-msgid "You can only add files when you are on a branch"
+msgid "You can now submit a merge request to get this change into the original project."
msgstr ""
msgid "You can only edit files when you are on a branch"
@@ -25635,7 +26298,7 @@ msgstr ""
msgid "You need to upload a Google Takeout archive."
msgstr ""
-msgid "You reached %{usage_in_percent} of %{namespace_name}'s capacity (%{used_storage} of %{storage_limit})"
+msgid "You reached %{usage_in_percent} of %{namespace_name}'s storage capacity (%{used_storage} of %{storage_limit})"
msgstr ""
msgid "You tried to fork %{link_to_the_project} but it failed for the following reason:"
@@ -25938,13 +26601,13 @@ msgstr ""
msgid "Your subscription expired!"
msgstr ""
-msgid "Your subscription has been downgraded"
+msgid "Your subscription has been downgraded."
msgstr ""
-msgid "Your subscription will automatically renew in %{remaining_days}"
+msgid "Your subscription will automatically renew in %{remaining_days}."
msgstr ""
-msgid "Your subscription will expire in %{remaining_days}"
+msgid "Your subscription will expire in %{remaining_days}."
msgstr ""
msgid "Zoom meeting added"
@@ -26024,9 +26687,6 @@ msgstr ""
msgid "branch name"
msgstr ""
-msgid "build pipeline reference mismatch"
-msgstr ""
-
msgid "by"
msgstr ""
@@ -26045,6 +26705,9 @@ msgstr ""
msgid "cannot block others"
msgstr ""
+msgid "cannot contain HTML/XML tags, including any word between angle brackets (<,>)."
+msgstr ""
+
msgid "cannot include leading slash or directory traversal."
msgstr ""
@@ -26075,55 +26738,6 @@ msgstr ""
msgid "ciReport|%{remainingPackagesCount} more"
msgstr ""
-msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability"
-msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability for the source branch only"
-msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities for the source branch only"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerability"
-msgid_plural "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerabilities"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new vulnerability"
-msgid_plural "ciReport|%{reportType} %{status} detected %{newCount} new vulnerabilities"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities for the source branch only"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{fixedCount} fixed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} vulnerability for the source branch only"
-msgid_plural "ciReport|%{reportType} %{status} detected %{newCount} vulnerabilities for the source branch only"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "ciReport|%{reportType} %{status} detected no new vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected no vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected no vulnerabilities for the source branch only"
-msgstr ""
-
msgid "ciReport|%{reportType} is loading"
msgstr ""
@@ -26142,7 +26756,7 @@ msgstr ""
msgid "ciReport|All projects"
msgstr ""
-msgid "ciReport|All report types"
+msgid "ciReport|All scanner types"
msgstr ""
msgid "ciReport|All severities"
@@ -26166,6 +26780,9 @@ msgstr ""
msgid "ciReport|Container scanning detects known vulnerabilities in your docker images."
msgstr ""
+msgid "ciReport|Coverage Fuzzing"
+msgstr ""
+
msgid "ciReport|Create a merge request to implement this solution, or download and apply the patch manually."
msgstr ""
@@ -26324,6 +26941,9 @@ msgstr ""
msgid "customize"
msgstr ""
+msgid "data"
+msgstr ""
+
msgid "date must not be after 9999-12-31"
msgstr ""
@@ -26394,6 +27014,9 @@ msgstr ""
msgid "entries cannot contain HTML tags"
msgstr ""
+msgid "epic"
+msgstr ""
+
msgid "error"
msgstr ""
@@ -26415,9 +27038,6 @@ msgstr ""
msgid "expires on %{timebox_due_date}"
msgstr ""
-msgid "external_url"
-msgstr ""
-
msgid "failed"
msgstr ""
@@ -26453,9 +27073,6 @@ msgstr ""
msgid "from"
msgstr ""
-msgid "geo_node_name"
-msgstr ""
-
msgid "group"
msgstr ""
@@ -26477,9 +27094,6 @@ msgstr ""
msgid "https://your-bitbucket-server"
msgstr ""
-msgid "image"
-msgstr ""
-
msgid "image diff"
msgstr ""
@@ -26623,6 +27237,9 @@ msgstr ""
msgid "load it anyway"
msgstr ""
+msgid "loading"
+msgstr ""
+
msgid "locked by %{path_lock_user_name} %{created_at}"
msgstr ""
@@ -26751,9 +27368,6 @@ msgstr ""
msgid "mrWidget|Deployment statistics are not available currently"
msgstr ""
-msgid "mrWidget|Detect issues before deployment with a CI pipeline that continuously tests your code. We created a quick guide that will show you how to create one. Make your code more secure and more robust in just a minute."
-msgstr ""
-
msgid "mrWidget|Did not close"
msgstr ""
@@ -26916,6 +27530,9 @@ msgstr ""
msgid "mrWidget|To approve this merge request, please enter your password. This project requires all approvals to be authenticated."
msgstr ""
+msgid "mrWidget|Use %{linkStart}CI pipelines to test your code%{linkEnd}, simply add a GitLab CI configuration file to your project. It only takes a minute to make your code more secure and robust."
+msgstr ""
+
msgid "mrWidget|When this merge request is ready, remove the WIP: prefix from the title to allow it to be merged"
msgstr ""
@@ -26952,6 +27569,9 @@ msgstr ""
msgid "must be greater than start date"
msgstr ""
+msgid "must contain only valid frameworks"
+msgstr ""
+
msgid "my-awesome-group"
msgstr ""
@@ -26964,6 +27584,9 @@ msgstr ""
msgid "needs to be between 10 minutes and 1 month"
msgstr ""
+msgid "never"
+msgstr ""
+
msgid "never expires"
msgstr ""
@@ -26991,6 +27614,9 @@ msgstr ""
msgid "nounSeries|%{firstItem} and %{lastItem}"
msgstr ""
+msgid "nounSeries|%{item}"
+msgstr ""
+
msgid "nounSeries|%{item}, %{nextItem}"
msgstr ""
@@ -27127,6 +27753,9 @@ msgstr ""
msgid "revised"
msgstr ""
+msgid "satisfied"
+msgstr ""
+
msgid "score"
msgstr ""
@@ -27157,6 +27786,9 @@ msgstr ""
msgid "severity|Unknown"
msgstr ""
+msgid "should be an array of %{object_name} objects"
+msgstr ""
+
msgid "should be greater than or equal to %{access} inherited membership from group %{group_name}"
msgstr ""
@@ -27211,10 +27843,13 @@ msgstr ""
msgid "suggestPipeline|2/2: Commit your changes"
msgstr ""
-msgid "suggestPipeline|Commit the changes and your pipeline will automatically run for the first time."
+msgid "suggestPipeline|Choose %{boldStart}Code Quality%{boldEnd} to add a pipeline that tests the quality of your code."
msgstr ""
-msgid "suggestPipeline|We recommend the %{boldStart}Code Quality%{boldEnd} template, which will add a report widget to your Merge Requests. This way you’ll learn about code quality degradations much sooner. %{footerStart} Goodbye technical debt! %{footerEnd}"
+msgid "suggestPipeline|The template is ready! You can now commit it to create your first pipeline."
+msgstr ""
+
+msgid "suggestPipeline|We’re adding a GitLab CI configuration file to add a pipeline to the project. You could create it manually, but we recommend that you start with a GitLab template that works out of the box."
msgstr ""
msgid "syntax is correct"
@@ -27274,6 +27909,9 @@ msgstr ""
msgid "uses Kubernetes clusters to deploy your code!"
msgstr ""
+msgid "v%{version} published %{timeAgo}"
+msgstr ""
+
msgid "verify ownership"
msgstr ""
diff --git a/locale/cs_CZ/gitlab.po b/locale/cs_CZ/gitlab.po
index f3a82fa067c..7fadd990891 100644
--- a/locale/cs_CZ/gitlab.po
+++ b/locale/cs_CZ/gitlab.po
@@ -2,7 +2,7 @@ msgid ""
msgstr ""
"Project-Id-Version: gitlab-ee\n"
"Report-Msgid-Bugs-To: \n"
-"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Last-Translator: \n"
"Language-Team: Czech\n"
"Language: cs_CZ\n"
"MIME-Version: 1.0\n"
@@ -10,9 +10,11 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=4; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 3;\n"
"X-Crowdin-Project: gitlab-ee\n"
+"X-Crowdin-Project-ID: 288872\n"
"X-Crowdin-Language: cs\n"
"X-Crowdin-File: /master/locale/gitlab.pot\n"
-"PO-Revision-Date: 2020-06-08 15:15\n"
+"X-Crowdin-File-ID: 6\n"
+"PO-Revision-Date: 2020-07-02 01:24\n"
msgid " %{start} to %{end}"
msgstr ""
@@ -251,6 +253,13 @@ msgstr[1] ""
msgstr[2] ""
msgstr[3] ""
+msgid "%d milestone"
+msgid_plural "%d milestones"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
msgid "%d minute"
msgid_plural "%d minutes"
msgstr[0] ""
@@ -314,13 +323,6 @@ msgstr[1] ""
msgstr[2] ""
msgstr[3] ""
-msgid "%d url scanned"
-msgid_plural "%d urls scanned"
-msgstr[0] ""
-msgstr[1] ""
-msgstr[2] ""
-msgstr[3] ""
-
msgid "%d vulnerability"
msgid_plural "%d vulnerabilities"
msgstr[0] ""
@@ -412,9 +414,15 @@ msgstr[3] ""
msgid "%{count} related %{pluralized_subject}: %{links}"
msgstr ""
+msgid "%{dashboard_path} could not be found."
+msgstr ""
+
msgid "%{days} days until tags are automatically removed"
msgstr ""
+msgid "%{deployLinkStart}Use a template to deploy to ECS%{deployLinkEnd}, or use a docker image to %{commandsLinkStart}run AWS commands in GitLab CI/CD%{commandsLinkEnd}."
+msgstr ""
+
msgid "%{description}- Sentry event: %{errorUrl}- First seen: %{firstSeen}- Last seen: %{lastSeen} %{countLabel}: %{count}%{userCountLabel}: %{userCount}"
msgstr ""
@@ -457,12 +465,18 @@ msgstr ""
msgid "%{host} sign-in from new location"
msgstr ""
-msgid "%{icon}You are about to add %{usersTag} people to the discussion. Proceed with caution."
+msgid "%{icon}You are about to add %{usersTag} people to the discussion. They will all receive a notification."
+msgstr ""
+
+msgid "%{integrations_link_start}Integrations%{link_end} enable you to make third-party applications part of your GitLab workflow. If the available integrations don't meet your needs, consider using a %{webhooks_link_start}webhook%{link_end}."
msgstr ""
msgid "%{issuableType} will be removed! Are you sure?"
msgstr ""
+msgid "%{issuesCount} issues in this group"
+msgstr ""
+
msgid "%{issuesSize} issues"
msgstr ""
@@ -472,7 +486,7 @@ msgstr ""
msgid "%{labelStart}Class:%{labelEnd} %{class}"
msgstr ""
-msgid "%{labelStart}Confidence:%{labelEnd} %{confidence}"
+msgid "%{labelStart}Evidence:%{labelEnd} %{evidence}"
msgstr ""
msgid "%{labelStart}File:%{labelEnd} %{file}"
@@ -490,6 +504,9 @@ msgstr ""
msgid "%{labelStart}Report Type:%{labelEnd} %{reportType}"
msgstr ""
+msgid "%{labelStart}Scanner:%{labelEnd} %{scanner}"
+msgstr ""
+
msgid "%{labelStart}Severity:%{labelEnd} %{severity}"
msgstr ""
@@ -514,9 +531,6 @@ msgstr ""
msgid "%{link_start}Read more%{link_end} about role permissions"
msgstr ""
-msgid "%{link} can be used for binding events when something is happening within the project."
-msgstr ""
-
msgid "%{listToShow}, and %{awardsListLength} more."
msgstr ""
@@ -550,6 +564,9 @@ msgstr ""
msgid "%{name} found %{resultsString}"
msgstr ""
+msgid "%{name} is already being used for another emoji"
+msgstr ""
+
msgid "%{name} is scheduled for %{action}"
msgstr ""
@@ -599,6 +616,9 @@ msgstr ""
msgid "%{primary} (%{secondary})"
msgstr ""
+msgid "%{ref} cannot be added: %{error}"
+msgstr ""
+
msgid "%{releases} release"
msgid_plural "%{releases} releases"
msgstr[0] ""
@@ -609,13 +629,43 @@ msgstr[3] ""
msgid "%{remaining_approvals} left"
msgstr ""
-msgid "%{retryButtonStart}Try again%{retryButtonEnd} or %{newFileButtonStart}attach a new file%{newFileButtonEnd}"
+msgid "%{reportType} %{status} detected %{critical} critical and %{high} high severity vulnerabilities out of %{total}."
msgstr ""
-msgid "%{screenreaderOnlyStart}Keyboard shorcuts%{screenreaderOnlyEnd} Disabled"
+msgid "%{reportType} %{status} detected %{critical} critical and %{high} high severity vulnerabilities."
msgstr ""
-msgid "%{screenreaderOnlyStart}Keyboard shorcuts%{screenreaderOnlyEnd} Enabled"
+msgid "%{reportType} %{status} detected %{critical} critical severity vulnerabilities out of %{total}."
+msgstr ""
+
+msgid "%{reportType} %{status} detected %{critical} critical severity vulnerability."
+msgid_plural "%{reportType} %{status} detected %{critical} critical severity vulnerabilities."
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "%{reportType} %{status} detected %{high} high severity vulnerabilities out of %{total}."
+msgstr ""
+
+msgid "%{reportType} %{status} detected %{high} high severity vulnerability."
+msgid_plural "%{reportType} %{status} detected %{high} high severity vulnerabilities."
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "%{reportType} %{status} detected %{other} vulnerability."
+msgid_plural "%{reportType} %{status} detected %{other} vulnerabilities."
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "%{reportType} %{status} detected no new vulnerabilities."
+msgstr ""
+
+msgid "%{retryButtonStart}Try again%{retryButtonEnd} or %{newFileButtonStart}attach a new file%{newFileButtonEnd}"
msgstr ""
msgid "%{service_title} %{message}."
@@ -722,6 +772,12 @@ msgstr ""
msgid "%{token}..."
msgstr ""
+msgid "%{totalCpu} (%{freeSpacePercentage}%{percentSymbol} free)"
+msgstr ""
+
+msgid "%{totalMemory} (%{freeSpacePercentage}%{percentSymbol} free)"
+msgstr ""
+
msgid "%{totalWeight} total weight"
msgstr ""
@@ -752,6 +808,12 @@ msgstr ""
msgid "%{verb} %{time_spent_value} spent time."
msgstr ""
+msgid "%{webhooks_link_start}%{webhook_type}%{link_end} enable you to send notifications to web applications in response to events in a group or project."
+msgstr ""
+
+msgid "%{webhooks_link_start}%{webhook_type}%{link_end} enable you to send notifications to web applications in response to events in a group or project. We recommend using an %{integrations_link_start}integration%{link_end} in preference to a webhook."
+msgstr ""
+
msgid "'%{level}' is not a valid visibility level"
msgstr ""
@@ -780,9 +842,6 @@ msgstr ""
msgid "(No changes)"
msgstr ""
-msgid "(Show all)"
-msgstr ""
-
msgid "(check progress)"
msgstr ""
@@ -855,6 +914,9 @@ msgstr ""
msgid "0 for unlimited"
msgstr ""
+msgid "0 for unlimited, only effective with remote storage enabled."
+msgstr ""
+
msgid "1 %{type} addition"
msgid_plural "%{count} %{type} additions"
msgstr[0] ""
@@ -1032,6 +1094,12 @@ msgstr ""
msgid "<code>\"johnsmith@example.com\": \"johnsmith@example.com\"</code> will add \"By <a href=\"#\">johnsmith@example.com</a>\" to all issues and comments originally created by johnsmith@example.com. By default, the email address or username is masked to ensure the user's privacy. Use this option if you want to show the full email address."
msgstr ""
+msgid "<code>Masked</code> to prevent the values from being displayed in job logs (must match certain regexp requirements)."
+msgstr ""
+
+msgid "<code>Protected</code> to expose them to protected branches or tags only."
+msgstr ""
+
msgid "<namespace / project>"
msgstr ""
@@ -1059,9 +1127,6 @@ msgstr ""
msgid "A .NET Core console application template, customizable for any .NET Core project"
msgstr ""
-msgid "A DAG must have two dependent jobs to be visualized on this tab."
-msgstr ""
-
msgid "A GitBook site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
msgstr ""
@@ -1092,6 +1157,12 @@ msgstr ""
msgid "A deleted user"
msgstr ""
+msgid "A file has been changed."
+msgstr ""
+
+msgid "A file was not found."
+msgstr ""
+
msgid "A file with '%{file_name}' already exists in %{branch} branch"
msgstr ""
@@ -1158,7 +1229,7 @@ msgstr ""
msgid "A subscription will trigger a new pipeline on the default branch of this project when a pipeline successfully completes for a new tag on the %{default_branch_docs} of the subscribed project."
msgstr ""
-msgid "A terraform report was generated in your pipelines."
+msgid "A suggestion is not applicable."
msgstr ""
msgid "A user with write access to the source branch selected this option"
@@ -1329,6 +1400,9 @@ msgstr ""
msgid "Action to take when receiving an alert."
msgstr ""
+msgid "Actions"
+msgstr ""
+
msgid "Activate"
msgstr ""
@@ -1387,6 +1461,9 @@ msgstr ""
msgid "Add LICENSE"
msgstr ""
+msgid "Add New Node"
+msgstr ""
+
msgid "Add README"
msgstr ""
@@ -1462,6 +1539,9 @@ msgstr ""
msgid "Add approval rule"
msgstr ""
+msgid "Add approvers"
+msgstr ""
+
msgid "Add bold text"
msgstr ""
@@ -1519,6 +1599,9 @@ msgstr ""
msgid "Add strikethrough text"
msgstr ""
+msgid "Add suggestion to batch"
+msgstr ""
+
msgid "Add system hook"
msgstr ""
@@ -1618,6 +1701,9 @@ msgstr ""
msgid "Admin Note"
msgstr ""
+msgid "Admin Notifications"
+msgstr ""
+
msgid "Admin Overview"
msgstr ""
@@ -1729,6 +1815,9 @@ msgstr ""
msgid "AdminSettings|Integrations configured here will automatically apply to all projects on this instance."
msgstr ""
+msgid "AdminSettings|Moved to integrations"
+msgstr ""
+
msgid "AdminSettings|No required pipeline"
msgstr ""
@@ -1961,12 +2050,18 @@ msgstr[1] ""
msgstr[2] ""
msgstr[3] ""
+msgid "AlertManagement| assign yourself"
+msgstr ""
+
msgid "AlertManagement|Acknowledged"
msgstr ""
msgid "AlertManagement|Alert"
msgstr ""
+msgid "AlertManagement|Alert assignee(s): %{assignees}"
+msgstr ""
+
msgid "AlertManagement|Alert detail"
msgstr ""
@@ -1982,9 +2077,15 @@ msgstr ""
msgid "AlertManagement|All alerts"
msgstr ""
+msgid "AlertManagement|Assign To"
+msgstr ""
+
msgid "AlertManagement|Assign status"
msgstr ""
+msgid "AlertManagement|Assignee"
+msgstr ""
+
msgid "AlertManagement|Assignees"
msgstr ""
@@ -2003,9 +2104,6 @@ msgstr ""
msgid "AlertManagement|Edit"
msgstr ""
-msgid "AlertManagement|End time"
-msgstr ""
-
msgid "AlertManagement|Events"
msgstr ""
@@ -2024,10 +2122,13 @@ msgstr ""
msgid "AlertManagement|More information"
msgstr ""
+msgid "AlertManagement|No Matching Results"
+msgstr ""
+
msgid "AlertManagement|No alert data to display."
msgstr ""
-msgid "AlertManagement|No alerts available to display. If you think you're seeing this message in error, refresh the page."
+msgid "AlertManagement|No alerts available to display. See %{linkStart}enabling alert management%{linkEnd} for more information on adding alerts to the list."
msgstr ""
msgid "AlertManagement|No alerts to display."
@@ -2036,6 +2137,9 @@ msgstr ""
msgid "AlertManagement|None"
msgstr ""
+msgid "AlertManagement|None -"
+msgstr ""
+
msgid "AlertManagement|Open"
msgstr ""
@@ -2072,9 +2176,18 @@ msgstr ""
msgid "AlertManagement|There was an error displaying the alerts. Confirm your endpoint's configuration details to ensure alerts appear."
msgstr ""
+msgid "AlertManagement|There was an error while updating the assignee(s) list. Please try again."
+msgstr ""
+
+msgid "AlertManagement|There was an error while updating the assignee(s) of the alert. Please try again."
+msgstr ""
+
msgid "AlertManagement|There was an error while updating the status of the alert. Please try again."
msgstr ""
+msgid "AlertManagement|This assignee cannot be assigned to this alert."
+msgstr ""
+
msgid "AlertManagement|Tool"
msgstr ""
@@ -2222,6 +2335,9 @@ msgstr ""
msgid "Allow users to request access (if visibility is public or internal)"
msgstr ""
+msgid "Allowed Geo IP"
+msgstr ""
+
msgid "Allowed email domain restriction only permitted for top-level groups"
msgstr ""
@@ -2255,6 +2371,9 @@ msgstr ""
msgid "Amazon Web Services"
msgstr ""
+msgid "Amazon Web Services Logo"
+msgstr ""
+
msgid "Amazon authentication is not %{link_start}correctly configured%{link_end}. Ask your GitLab administrator if you want to use this service."
msgstr ""
@@ -2312,10 +2431,16 @@ msgstr ""
msgid "An error occurred when updating the issue weight"
msgstr ""
+msgid "An error occurred while acknowledging the notification. Refresh the page and try again."
+msgstr ""
+
+msgid "An error occurred while adding approvers"
+msgstr ""
+
msgid "An error occurred while adding formatted title for epic"
msgstr ""
-msgid "An error occurred while checking group path"
+msgid "An error occurred while checking group path. Please refresh and try again."
msgstr ""
msgid "An error occurred while committing your changes."
@@ -2459,7 +2584,7 @@ msgstr ""
msgid "An error occurred while loading milestones"
msgstr ""
-msgid "An error occurred while loading terraform report"
+msgid "An error occurred while loading project creation UI"
msgstr ""
msgid "An error occurred while loading the data. Please try again."
@@ -2513,6 +2638,9 @@ msgstr ""
msgid "An error occurred while rendering preview broadcast message"
msgstr ""
+msgid "An error occurred while rendering the editor"
+msgstr ""
+
msgid "An error occurred while reordering issues."
msgstr ""
@@ -2609,7 +2737,7 @@ msgstr ""
msgid "Analyze your dependencies for known vulnerabilities."
msgstr ""
-msgid "Analyze your source code and git history for secrets"
+msgid "Analyze your source code and git history for secrets."
msgstr ""
msgid "Analyze your source code for known vulnerabilities."
@@ -2723,12 +2851,18 @@ msgstr ""
msgid "Apply suggestion"
msgstr ""
+msgid "Apply suggestions"
+msgstr ""
+
msgid "Apply template"
msgstr ""
msgid "Apply this approval rule to any branch or a specific protected branch."
msgstr ""
+msgid "Applying"
+msgstr ""
+
msgid "Applying a template will replace the existing issue description. Any changes you have made will be lost."
msgstr ""
@@ -2741,12 +2875,18 @@ msgstr ""
msgid "Applying multiple commands"
msgstr ""
-msgid "Applying suggestion"
+msgid "Applying suggestion..."
+msgstr ""
+
+msgid "Applying suggestions..."
msgstr ""
msgid "Approval rules"
msgstr ""
+msgid "Approval rules reset to project defaults"
+msgstr ""
+
msgid "ApprovalRuleRemove|%d member"
msgid_plural "ApprovalRuleRemove|%d members"
msgstr[0] ""
@@ -2808,6 +2948,9 @@ msgstr ""
msgid "Approved"
msgstr ""
+msgid "Approved MRs"
+msgstr ""
+
msgid "Approved by: "
msgstr ""
@@ -3021,6 +3164,9 @@ msgstr ""
msgid "Assign"
msgstr ""
+msgid "Assign Iteration"
+msgstr ""
+
msgid "Assign custom color like #FF0000"
msgstr "PÅ™iÅ™aÄte vlastní barvu jako například #FF0000"
@@ -3039,6 +3185,9 @@ msgstr ""
msgid "Assign to"
msgstr "Přiřadit k"
+msgid "Assign to commenting user"
+msgstr ""
+
msgid "Assign yourself to these issues"
msgstr ""
@@ -3067,6 +3216,9 @@ msgstr[1] ""
msgstr[2] ""
msgstr[3] ""
+msgid "Assignee has no permissions"
+msgstr ""
+
msgid "Assignee lists not available with your current license"
msgstr ""
@@ -3076,6 +3228,9 @@ msgstr ""
msgid "Assignee(s)"
msgstr ""
+msgid "Assignees"
+msgstr ""
+
msgid "Assigns %{assignee_users_sentence}."
msgstr ""
@@ -3113,6 +3268,9 @@ msgstr ""
msgid "Audit Events is a way to keep track of important events that happened in GitLab."
msgstr ""
+msgid "Audit Log"
+msgstr ""
+
msgid "AuditEvents|(removed)"
msgstr ""
@@ -3149,6 +3307,9 @@ msgstr ""
msgid "AuditLogs|IP Address"
msgstr ""
+msgid "AuditLogs|Member Events"
+msgstr ""
+
msgid "AuditLogs|No matching %{type} found."
msgstr ""
@@ -3431,9 +3592,6 @@ msgstr ""
msgid "Badges|e.g. %{exampleUrl}"
msgstr ""
-msgid "Badge|New"
-msgstr ""
-
msgid "Balsamiq file could not be loaded."
msgstr ""
@@ -3482,6 +3640,12 @@ msgstr ""
msgid "Below you will find all the groups that are public."
msgstr ""
+msgid "Beta"
+msgstr ""
+
+msgid "Bi-weekly code coverage"
+msgstr ""
+
msgid "Billing"
msgstr ""
@@ -3542,9 +3706,15 @@ msgstr ""
msgid "Bitbucket Server Import"
msgstr ""
+msgid "Bitbucket Server import"
+msgstr ""
+
msgid "Bitbucket import"
msgstr ""
+msgid "Blame"
+msgstr ""
+
msgid "Blocked"
msgstr ""
@@ -3557,9 +3727,6 @@ msgstr ""
msgid "Blog"
msgstr ""
-msgid "Blue helpers indicate an action to be taken."
-msgstr ""
-
msgid "Board name"
msgstr ""
@@ -3758,6 +3925,9 @@ msgstr ""
msgid "Broadcast Message was successfully updated."
msgstr ""
+msgid "Broadcast Messages"
+msgstr ""
+
msgid "Browse Directory"
msgstr ""
@@ -3812,6 +3982,9 @@ msgstr ""
msgid "By %{user_name}"
msgstr ""
+msgid "By URL"
+msgstr ""
+
msgid "By default GitLab sends emails in HTML and plain text formats so mail clients can choose what format to use. Disable this option if you only want to send emails in plain text format."
msgstr ""
@@ -3917,6 +4090,9 @@ msgstr ""
msgid "Can override approvers and approvals required per merge request"
msgstr ""
+msgid "Can't apply as this line has changed or the suggestion already matches its content."
+msgstr ""
+
msgid "Can't create snippet: %{err}"
msgstr ""
@@ -4103,9 +4279,6 @@ msgstr ""
msgid "Changes are still tracked. Useful for cluster/index migrations."
msgstr ""
-msgid "Changes are unknown"
-msgstr ""
-
msgid "Changes suppressed. Click to show."
msgstr ""
@@ -4412,15 +4585,9 @@ msgstr ""
msgid "Choose what content you want to see on a group’s overview page"
msgstr ""
-msgid "Choose which groups you wish to synchronize to this secondary node"
-msgstr ""
-
msgid "Choose which repositories you want to connect and run CI/CD pipelines."
msgstr ""
-msgid "Choose which shards you wish to synchronize to this secondary node"
-msgstr ""
-
msgid "Choose your framework"
msgstr ""
@@ -4718,9 +4885,15 @@ msgstr ""
msgid "Cluster does not exist"
msgstr ""
+msgid "Cluster is required for Stages::ClusterEndpointInserter"
+msgstr ""
+
msgid "Cluster level"
msgstr ""
+msgid "Cluster type must be specificed for Stages::ClusterEndpointInserter"
+msgstr ""
+
msgid "ClusterIntegration| %{custom_domain_start}More information%{custom_domain_end}."
msgstr ""
@@ -4832,7 +5005,7 @@ msgstr ""
msgid "ClusterIntegration|Cert-Manager"
msgstr ""
-msgid "ClusterIntegration|Cert-Manager is a native Kubernetes certificate management controller that helps with issuing certificates. Installing Cert-Manager on your cluster will issue a certificate by %{letsEncrypt} and ensure that certificates are valid and up-to-date."
+msgid "ClusterIntegration|Cert-Manager is a native Kubernetes certificate management controller that helps with issuing certificates. Installing Cert-Manager on your cluster will issue a certificate by %{linkStart}Let's Encrypt%{linkEnd} and ensure that certificates are valid and up-to-date."
msgstr ""
msgid "ClusterIntegration|Certificate Authority bundle (PEM format)"
@@ -4859,9 +5032,6 @@ msgstr ""
msgid "ClusterIntegration|Clear the local cache of namespace and service accounts. This is necessary if your integration has become out of sync. The cache is repopulated during the next CI job that requires namespace and service accounts."
msgstr ""
-msgid "ClusterIntegration|Cloud Run"
-msgstr ""
-
msgid "ClusterIntegration|Cluster being created"
msgstr ""
@@ -4946,7 +5116,7 @@ msgstr ""
msgid "ClusterIntegration|Crossplane"
msgstr ""
-msgid "ClusterIntegration|Crossplane enables declarative provisioning of managed services from your cloud of choice using %{codeStart}kubectl%{codeEnd} or %{gitlabIntegrationLink}. Crossplane runs inside your Kubernetes cluster and supports secure connectivity and secrets management between app containers and the cloud services they depend on."
+msgid "ClusterIntegration|Crossplane enables declarative provisioning of managed services from your cloud of choice using %{codeStart}kubectl%{codeEnd} or %{linkStart}GitLab Integration%{linkEnd}. Crossplane runs inside your Kubernetes cluster and supports secure connectivity and secrets management between app containers and the cloud services they depend on."
msgstr ""
msgid "ClusterIntegration|Deletes all GitLab resources attached to this cluster during removal"
@@ -5030,9 +5200,6 @@ msgstr ""
msgid "ClusterIntegration|GitLab-managed cluster"
msgstr ""
-msgid "ClusterIntegration|Gitlab Integration"
-msgstr ""
-
msgid "ClusterIntegration|Global default"
msgstr ""
@@ -5078,7 +5245,7 @@ msgstr ""
msgid "ClusterIntegration|Ingress gives you a way to route requests to services based on the request host or path, centralizing a number of services into a single entrypoint."
msgstr ""
-msgid "ClusterIntegration|Installing Ingress may incur additional costs. Learn more about %{pricingLink}."
+msgid "ClusterIntegration|Installing Ingress may incur additional costs. Learn more about %{linkStart}pricing%{linkEnd}."
msgstr ""
msgid "ClusterIntegration|Instance cluster"
@@ -5093,7 +5260,7 @@ msgstr ""
msgid "ClusterIntegration|Issuer Email"
msgstr ""
-msgid "ClusterIntegration|Issuers represent a certificate authority. You must provide an email address for your Issuer. "
+msgid "ClusterIntegration|Issuers represent a certificate authority. You must provide an email address for your Issuer."
msgstr ""
msgid "ClusterIntegration|Jupyter Hostname"
@@ -5165,9 +5332,6 @@ msgstr ""
msgid "ClusterIntegration|Learn more about instance Kubernetes clusters"
msgstr ""
-msgid "ClusterIntegration|Let's Encrypt"
-msgstr ""
-
msgid "ClusterIntegration|Loading IAM Roles"
msgstr ""
@@ -5279,7 +5443,7 @@ msgstr ""
msgid "ClusterIntegration|Prometheus"
msgstr ""
-msgid "ClusterIntegration|Prometheus is an open-source monitoring system with %{gitlabIntegrationLink} to monitor deployed applications."
+msgid "ClusterIntegration|Prometheus is an open-source monitoring system with %{linkStart}GitLab Integration%{linkEnd} to monitor deployed applications."
msgstr ""
msgid "ClusterIntegration|Provider details"
@@ -5435,10 +5599,10 @@ msgstr ""
msgid "ClusterIntegration|Select zone to choose machine type"
msgstr ""
-msgid "ClusterIntegration|Send Cilium Logs"
+msgid "ClusterIntegration|Send Container Network Policies Logs"
msgstr ""
-msgid "ClusterIntegration|Send ModSecurity Logs"
+msgid "ClusterIntegration|Send Web Application Firewall Logs"
msgstr ""
msgid "ClusterIntegration|Service Token"
@@ -5606,15 +5770,12 @@ msgstr ""
msgid "ClusterIntegration|documentation"
msgstr ""
-msgid "ClusterIntegration|installed via %{installed_via}"
+msgid "ClusterIntegration|installed via %{linkStart}Cloud Run%{linkEnd}"
msgstr ""
msgid "ClusterIntegration|meets the requirements"
msgstr ""
-msgid "ClusterIntegration|pricing"
-msgstr ""
-
msgid "ClusterIntegration|sign up"
msgstr ""
@@ -5651,6 +5812,15 @@ msgstr ""
msgid "Code"
msgstr ""
+msgid "Code Coverage: %{coveragePercentage}%{percentSymbol}"
+msgstr ""
+
+msgid "Code Coverage| Empty code coverage data"
+msgstr ""
+
+msgid "Code Coverage|Couldn't fetch the code coverage data"
+msgstr ""
+
msgid "Code Owners"
msgstr ""
@@ -5705,7 +5875,7 @@ msgstr ""
msgid "Collapse approvers"
msgstr ""
-msgid "Collapse child epics"
+msgid "Collapse milestones"
msgstr ""
msgid "Collapse replies"
@@ -5723,6 +5893,9 @@ msgstr ""
msgid "Coming soon"
msgstr ""
+msgid "Comma-separated, e.g. '1.1.1.1, 2.2.2.0/24'"
+msgstr ""
+
msgid "Command"
msgstr ""
@@ -5895,6 +6068,12 @@ msgstr ""
msgid "Compliance framework (optional)"
msgstr ""
+msgid "Compliance frameworks"
+msgstr ""
+
+msgid "ComplianceDashboard|created by:"
+msgstr ""
+
msgid "ComplianceFramework|GDPR"
msgstr ""
@@ -6033,6 +6212,9 @@ msgstr ""
msgid "Connection timed out"
msgstr ""
+msgid "Connection timeout"
+msgstr ""
+
msgid "Contact sales to upgrade"
msgstr ""
@@ -6092,9 +6274,6 @@ msgstr ""
msgid "ContainerRegistry|CLI Commands"
msgstr ""
-msgid "ContainerRegistry|Compressed Size"
-msgstr ""
-
msgid "ContainerRegistry|Container Registry"
msgstr ""
@@ -6110,6 +6289,9 @@ msgstr ""
msgid "ContainerRegistry|Copy push command"
msgstr ""
+msgid "ContainerRegistry|Delete selected"
+msgstr ""
+
msgid "ContainerRegistry|Docker connection error"
msgstr ""
@@ -6143,16 +6325,16 @@ msgstr ""
msgid "ContainerRegistry|If you are not already logged in, you need to authenticate to the Container Registry by using your GitLab username and password. If you have %{twofaDocLinkStart}Two-Factor Authentication%{twofaDocLinkEnd} enabled, use a %{personalAccessTokensDocLinkStart}Personal Access Token%{personalAccessTokensDocLinkEnd} instead of a password."
msgstr ""
-msgid "ContainerRegistry|Image ID"
+msgid "ContainerRegistry|Image ID: %{imageId}"
msgstr ""
msgid "ContainerRegistry|Image Repositories"
msgstr ""
-msgid "ContainerRegistry|Keep and protect the images that matter most."
+msgid "ContainerRegistry|Image tags"
msgstr ""
-msgid "ContainerRegistry|Last Updated"
+msgid "ContainerRegistry|Keep and protect the images that matter most."
msgstr ""
msgid "ContainerRegistry|Login"
@@ -6167,6 +6349,9 @@ msgstr ""
msgid "ContainerRegistry|Please contact your administrator."
msgstr ""
+msgid "ContainerRegistry|Published %{timeInfo}"
+msgstr ""
+
msgid "ContainerRegistry|Push an image"
msgstr ""
@@ -6182,9 +6367,6 @@ msgstr ""
msgid "ContainerRegistry|Remove repository"
msgstr ""
-msgid "ContainerRegistry|Remove selected tags"
-msgstr ""
-
msgid "ContainerRegistry|Remove tag"
msgid_plural "ContainerRegistry|Remove tags"
msgstr[0] ""
@@ -6216,9 +6398,6 @@ msgstr ""
msgid "ContainerRegistry|Sorry, your filter produced no results."
msgstr ""
-msgid "ContainerRegistry|Tag"
-msgstr ""
-
msgid "ContainerRegistry|Tag expiration policy"
msgstr ""
@@ -6363,21 +6542,6 @@ msgstr ""
msgid "Control the display of third party offers."
msgstr ""
-msgid "Control the maximum concurrency of LFS/attachment backfill for this secondary node"
-msgstr ""
-
-msgid "Control the maximum concurrency of container repository operations for this Geo node"
-msgstr ""
-
-msgid "Control the maximum concurrency of repository backfill for this secondary node"
-msgstr ""
-
-msgid "Control the maximum concurrency of verification operations for this Geo node"
-msgstr ""
-
-msgid "Control the minimum interval in days that a repository should be reverified for this primary node"
-msgstr ""
-
msgid "Cookie domain"
msgstr ""
@@ -6519,6 +6683,9 @@ msgstr ""
msgid "Could not find design."
msgstr ""
+msgid "Could not find iteration"
+msgstr ""
+
msgid "Could not remove the trigger."
msgstr ""
@@ -6555,6 +6722,12 @@ msgstr ""
msgid "Coverage"
msgstr ""
+msgid "Coverage Fuzzing"
+msgstr ""
+
+msgid "Crash State"
+msgstr ""
+
msgid "Create"
msgstr ""
@@ -6603,6 +6776,9 @@ msgstr ""
msgid "Create a personal access token on your account to pull or push via %{protocol}."
msgstr ""
+msgid "Create a project pre-populated with the necessary files to get you started quickly."
+msgstr ""
+
msgid "Create an account using:"
msgstr ""
@@ -6771,6 +6947,9 @@ msgstr ""
msgid "Creates branch '%{branch_name}' and a merge request to resolve this issue."
msgstr ""
+msgid "Creating"
+msgstr ""
+
msgid "Creating epic"
msgstr ""
@@ -6822,7 +7001,10 @@ msgstr ""
msgid "Current vulnerabilities count"
msgstr ""
-msgid "CurrentUser|Buy CI minutes"
+msgid "CurrentUser|Buy Pipeline minutes"
+msgstr ""
+
+msgid "CurrentUser|One of your groups is running out"
msgstr ""
msgid "CurrentUser|Profile"
@@ -6837,6 +7019,9 @@ msgstr ""
msgid "CurrentUser|Upgrade"
msgstr ""
+msgid "Custom Attributes"
+msgstr ""
+
msgid "Custom CI configuration path"
msgstr ""
@@ -7102,12 +7287,18 @@ msgstr ""
msgid "DAG"
msgstr ""
+msgid "DAG visualization requires at least 3 dependent jobs."
+msgstr ""
+
msgid "DNS"
msgstr ""
msgid "Dashboard"
msgstr ""
+msgid "Dashboard uid not found"
+msgstr ""
+
msgid "DashboardProjects|All"
msgstr ""
@@ -7132,6 +7323,9 @@ msgstr ""
msgid "Data is still calculating..."
msgstr ""
+msgid "Datasource name not found"
+msgstr ""
+
msgid "Date"
msgstr ""
@@ -7312,6 +7506,9 @@ msgstr ""
msgid "Delete this attachment"
msgstr ""
+msgid "Delete user list"
+msgstr ""
+
msgid "Delete variable"
msgstr ""
@@ -7638,6 +7835,9 @@ msgstr ""
msgid "Deploying to"
msgstr ""
+msgid "Deploying to AWS is easy with GitLab"
+msgstr ""
+
msgid "Deployment Frequency"
msgstr ""
@@ -8002,6 +8202,9 @@ msgstr ""
msgid "Dismissed on pipeline %{pipelineLink} at %{projectLink}"
msgstr ""
+msgid "Display alerts from all your monitoring tools directly within GitLab."
+msgstr ""
+
msgid "Display name"
msgstr ""
@@ -8059,9 +8262,6 @@ msgstr ""
msgid "Don't show again"
msgstr ""
-msgid "Don't worry, you can access this tour by clicking on the help icon in the top right corner and choose <strong>Learn GitLab</strong>."
-msgstr ""
-
msgid "Done"
msgstr ""
@@ -8188,6 +8388,9 @@ msgstr ""
msgid "Edit Release"
msgstr ""
+msgid "Edit Slack integration"
+msgstr ""
+
msgid "Edit Snippet"
msgstr ""
@@ -8227,6 +8430,9 @@ msgstr ""
msgid "Edit issues"
msgstr ""
+msgid "Edit iteration"
+msgstr ""
+
msgid "Edit public deploy key"
msgstr ""
@@ -8275,6 +8481,9 @@ msgstr ""
msgid "Email"
msgstr ""
+msgid "Email Notification"
+msgstr ""
+
msgid "Email address"
msgstr ""
@@ -8287,6 +8496,9 @@ msgstr ""
msgid "Email not verified. Please verify your email in Salesforce."
msgstr ""
+msgid "Email notification for unknown sign-ins"
+msgstr ""
+
msgid "Email patch"
msgstr ""
@@ -8518,6 +8730,9 @@ msgstr ""
msgid "Enforce DNS rebinding attack protection"
msgstr ""
+msgid "Enforce personal access token expiration"
+msgstr ""
+
msgid "Ensure connectivity is available from the GitLab server to the Prometheus server"
msgstr ""
@@ -8587,6 +8802,9 @@ msgstr ""
msgid "Enter the name of your application, and we'll return a unique %{type}."
msgstr ""
+msgid "Enter weights for storages for new repositories."
+msgstr ""
+
msgid "Enter your password to approve"
msgstr ""
@@ -8596,15 +8814,27 @@ msgstr ""
msgid "Environment does not have deployments"
msgstr ""
+msgid "Environment is required for Stages::MetricEndpointInserter"
+msgstr ""
+
+msgid "Environment is required for Stages::VariableEndpointInserter"
+msgstr ""
+
msgid "Environment scope"
msgstr ""
+msgid "Environment variables are applied to all project environments in this instance via the Runner. You can use environment variables for passwords, secret keys, etc. Make variables available to the running application by prepending the variable key with <code>K8S_SECRET_</code>. You can set variables to be:"
+msgstr ""
+
msgid "Environment variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. Additionally, they can be masked so they are hidden in job logs, though they must match certain regexp requirements to do so. You can use environment variables for passwords, secret keys, or whatever you want."
msgstr ""
msgid "Environment variables are configured by your administrator to be %{link_start}protected%{link_end} by default"
msgstr ""
+msgid "Environment variables on this GitLab instance are configured to be %{link_start}protected%{link_end} by default"
+msgstr ""
+
msgid "Environment:"
msgstr ""
@@ -9136,6 +9366,9 @@ msgstr ""
msgid "EventFilterBy|Filter by comments"
msgstr ""
+msgid "EventFilterBy|Filter by designs"
+msgstr ""
+
msgid "EventFilterBy|Filter by epic events"
msgstr ""
@@ -9268,15 +9501,15 @@ msgstr ""
msgid "Expand approvers"
msgstr ""
-msgid "Expand child epics"
-msgstr ""
-
msgid "Expand down"
msgstr ""
msgid "Expand dropdown"
msgstr ""
+msgid "Expand milestones"
+msgstr ""
+
msgid "Expand sidebar"
msgstr ""
@@ -9292,6 +9525,9 @@ msgstr ""
msgid "Expiration date"
msgstr ""
+msgid "Expiration not enforced"
+msgstr ""
+
msgid "Expiration policy for the Container Registry is a perfect solution for keeping the Registry space down while still enjoying the full power of GitLab CI/CD."
msgstr ""
@@ -9412,6 +9648,9 @@ msgstr ""
msgid "Failed Jobs"
msgstr ""
+msgid "Failed on"
+msgstr ""
+
msgid "Failed to add a Zoom meeting"
msgstr ""
@@ -9472,13 +9711,19 @@ msgstr ""
msgid "Failed to install."
msgstr ""
+msgid "Failed to load assignees. Please try again."
+msgstr ""
+
+msgid "Failed to load authors. Please try again."
+msgstr ""
+
msgid "Failed to load emoji list."
msgstr ""
msgid "Failed to load error details from Sentry."
msgstr ""
-msgid "Failed to load errors from Sentry. Error message: %{errorMessage}"
+msgid "Failed to load errors from Sentry."
msgstr ""
msgid "Failed to load group activity metrics. Please try again."
@@ -9559,6 +9804,9 @@ msgstr ""
msgid "Failed to set due date because the date format is invalid."
msgstr ""
+msgid "Failed to set iteration on this issue. Please try again."
+msgstr ""
+
msgid "Failed to signing using smartcard authentication"
msgstr ""
@@ -9781,13 +10029,10 @@ msgstr ""
msgid "FeatureFlags|Target environments"
msgstr ""
-msgid "FeatureFlags|There are no active feature flags"
+msgid "FeatureFlags|There was an error fetching the feature flags."
msgstr ""
-msgid "FeatureFlags|There are no inactive feature flags"
-msgstr ""
-
-msgid "FeatureFlags|There was an error fetching the feature flags."
+msgid "FeatureFlags|There was an error retrieving user lists"
msgstr ""
msgid "FeatureFlags|Try again in a few moments or contact your support team."
@@ -9799,9 +10044,18 @@ msgstr ""
msgid "FeatureFlag|Delete strategy"
msgstr ""
+msgid "FeatureFlag|List"
+msgstr ""
+
msgid "FeatureFlag|Percentage"
msgstr ""
+msgid "FeatureFlag|Select a user list"
+msgstr ""
+
+msgid "FeatureFlag|There are no configured user lists"
+msgstr ""
+
msgid "FeatureFlag|Type"
msgstr ""
@@ -9958,6 +10212,12 @@ msgstr ""
msgid "Filter..."
msgstr ""
+msgid "Find File"
+msgstr ""
+
+msgid "Find bugs in your code with coverage-guided fuzzing"
+msgstr ""
+
msgid "Find by path"
msgstr ""
@@ -10222,6 +10482,9 @@ msgstr ""
msgid "Geo allows you to replicate your GitLab instance to other geographical locations."
msgstr ""
+msgid "Geo nodes are paused using a command run on the node"
+msgstr ""
+
msgid "GeoNodeStatusEvent|%{timeAgoStr} (%{pendingEvents} events)"
msgstr ""
@@ -10306,6 +10569,9 @@ msgstr ""
msgid "GeoNodes|Not checksummed"
msgstr ""
+msgid "GeoNodes|Package files"
+msgstr ""
+
msgid "GeoNodes|Pausing replication stops the sync process. Are you sure?"
msgstr ""
@@ -10324,6 +10590,9 @@ msgstr ""
msgid "GeoNodes|Replication slots"
msgstr ""
+msgid "GeoNodes|Replication status"
+msgstr ""
+
msgid "GeoNodes|Repositories"
msgstr ""
@@ -10534,6 +10803,9 @@ msgstr ""
msgid "Geo|Synchronization failed - %{error}"
msgstr ""
+msgid "Geo|Synchronization of %{itemTitle} is disabled."
+msgstr ""
+
msgid "Geo|The database is currently %{db_lag} behind the primary node."
msgstr ""
@@ -10846,6 +11118,9 @@ msgstr ""
msgid "Go to %{link_to_google_takeout}."
msgstr ""
+msgid "Go to <strong>Issues</strong> > <strong>Boards</strong> to access your personalized learning issue board."
+msgstr ""
+
msgid "Go to Pipelines"
msgstr ""
@@ -10945,9 +11220,6 @@ msgstr ""
msgid "Go to your snippets"
msgstr ""
-msgid "Golden Tanuki"
-msgstr ""
-
msgid "Google Cloud Platform"
msgstr ""
@@ -10969,6 +11241,9 @@ msgstr ""
msgid "Grafana URL"
msgstr ""
+msgid "Grafana response contains invalid json"
+msgstr ""
+
msgid "GrafanaIntegration|API Token"
msgstr ""
@@ -11017,9 +11292,6 @@ msgstr ""
msgid "Group %{group_name} was successfully created."
msgstr ""
-msgid "Group '%{group_name}' is being imported."
-msgstr ""
-
msgid "Group Audit Events"
msgstr ""
@@ -11056,9 +11328,6 @@ msgstr ""
msgid "Group by:"
msgstr ""
-msgid "Group could not be imported: %{errors}"
-msgstr ""
-
msgid "Group description"
msgstr ""
@@ -11086,9 +11355,15 @@ msgstr ""
msgid "Group has not been marked for deletion"
msgstr ""
+msgid "Group import could not be scheduled"
+msgstr ""
+
msgid "Group info:"
msgstr ""
+msgid "Group is required when cluster_type is :group"
+msgstr ""
+
msgid "Group maintainers can register group runners in the %{link}"
msgstr ""
@@ -11152,6 +11427,24 @@ msgstr ""
msgid "GroupActivyMetrics|Recent activity (last 90 days)"
msgstr ""
+msgid "GroupImport|Failed to import group."
+msgstr ""
+
+msgid "GroupImport|Group '%{group_name}' is being imported."
+msgstr ""
+
+msgid "GroupImport|Group could not be imported: %{errors}"
+msgstr ""
+
+msgid "GroupImport|Please wait while we import the group for you. Refresh at will."
+msgstr ""
+
+msgid "GroupImport|The group was successfully imported."
+msgstr ""
+
+msgid "GroupImport|Unable to process group import file"
+msgstr ""
+
msgid "GroupRoadmap|%{dateWord} – No end date"
msgstr ""
@@ -11419,6 +11712,9 @@ msgstr ""
msgid "Groups (%{groups})"
msgstr ""
+msgid "Groups and projects"
+msgstr ""
+
msgid "Groups can also be nested by creating %{subgroup_docs_link_start}subgroups%{subgroup_docs_link_end}."
msgstr ""
@@ -11464,6 +11760,33 @@ msgstr ""
msgid "GroupsEmptyState|You can manage your group member’s permissions and access to each project in the group."
msgstr ""
+msgid "GroupsNew|Contact an administrator to enable options for importing your group."
+msgstr ""
+
+msgid "GroupsNew|Create"
+msgstr ""
+
+msgid "GroupsNew|Create group"
+msgstr ""
+
+msgid "GroupsNew|GitLab group export"
+msgstr ""
+
+msgid "GroupsNew|Import"
+msgstr ""
+
+msgid "GroupsNew|Import group"
+msgstr ""
+
+msgid "GroupsNew|My Awesome Group"
+msgstr ""
+
+msgid "GroupsNew|No import options available"
+msgstr ""
+
+msgid "GroupsNew|To copy a GitLab group between installations, navigate to the group settings page for the original installation, generate an export file, and upload it here."
+msgstr ""
+
msgid "GroupsTree|Are you sure you want to leave the \"%{fullName}\" group?"
msgstr ""
@@ -11566,6 +11889,9 @@ msgstr ""
msgid "Helps reduce request volume for protected paths"
msgstr ""
+msgid "Here are all your projects in your group, including the one you just created. To start, let’s take a look at your personalized learning project which will help you learn about GitLab at your own pace."
+msgstr ""
+
msgid "Here you will find recent merge request activity"
msgstr ""
@@ -11582,6 +11908,9 @@ msgstr[1] ""
msgstr[2] ""
msgstr[3] ""
+msgid "Hide details"
+msgstr ""
+
msgid "Hide file browser"
msgstr ""
@@ -11591,6 +11920,9 @@ msgstr ""
msgid "Hide host keys manual input"
msgstr ""
+msgid "Hide list"
+msgstr ""
+
msgid "Hide marketing-related entries from help"
msgstr ""
@@ -11613,9 +11945,6 @@ msgstr[3] ""
msgid "Hide values"
msgstr ""
-msgid "Hiding all labels"
-msgstr ""
-
msgid "High or unknown vulnerabilities present"
msgstr ""
@@ -11700,6 +12029,9 @@ msgstr ""
msgid "ID:"
msgstr ""
+msgid "IDE"
+msgstr ""
+
msgid "IDE|Allow live previews of JavaScript projects in the Web IDE using CodeSandbox Live Preview."
msgstr ""
@@ -11790,10 +12122,10 @@ msgstr ""
msgid "If enabled"
msgstr ""
-msgid "If enabled, access to projects will be validated on an external service using their classification label."
+msgid "If enabled, GitLab will handle Object Storage replication using Geo. %{linkStart}More information%{linkEnd}"
msgstr ""
-msgid "If enabled, and if object storage is enabled, GitLab will handle Object Storage replication using Geo"
+msgid "If enabled, access to projects will be validated on an external service using their classification label."
msgstr ""
msgid "If there is no previous license or if the previous license has expired, some GitLab functionality will be blocked until a new, valid license is uploaded."
@@ -12157,6 +12489,9 @@ msgstr[1] ""
msgstr[2] ""
msgstr[3] ""
+msgid "Instance Configuration"
+msgstr ""
+
msgid "Instance Statistics visibility"
msgstr ""
@@ -12178,9 +12513,6 @@ msgstr ""
msgid "Integrations"
msgstr ""
-msgid "Integrations allow you to integrate GitLab with other applications"
-msgstr ""
-
msgid "Integrations|All details"
msgstr ""
@@ -12292,6 +12624,9 @@ msgstr ""
msgid "Invalid query"
msgstr ""
+msgid "Invalid repository bundle for snippet with id %{snippet_id}"
+msgstr ""
+
msgid "Invalid repository path"
msgstr ""
@@ -12310,6 +12645,9 @@ msgstr ""
msgid "Invalid two-factor code."
msgstr ""
+msgid "Invalid yaml"
+msgstr ""
+
msgid "Invitation"
msgstr ""
@@ -12529,15 +12867,24 @@ msgstr ""
msgid "It seems like the Dependency Scanning job ran successfully, but no dependencies have been detected in your project."
msgstr ""
+msgid "It seems that there is currently no available data for code coverage"
+msgstr ""
+
msgid "It's you"
msgstr ""
+msgid "Iteration"
+msgstr ""
+
msgid "Iteration changed to"
msgstr ""
msgid "Iteration removed"
msgstr ""
+msgid "Iteration updated"
+msgstr ""
+
msgid "Iterations"
msgstr ""
@@ -12568,12 +12915,18 @@ msgstr ""
msgid "January"
msgstr ""
+msgid "Jira Issues"
+msgstr ""
+
msgid "Jira import is already running."
msgstr ""
msgid "Jira integration not configured."
msgstr ""
+msgid "Jira project key is not configured"
+msgstr ""
+
msgid "Jira project: %{importProject}"
msgstr ""
@@ -12754,6 +13107,9 @@ msgstr ""
msgid "Keep divergent refs"
msgstr ""
+msgid "Kerberos access denied"
+msgstr ""
+
msgid "Key"
msgstr ""
@@ -12763,10 +13119,13 @@ msgstr ""
msgid "Key: %{key}"
msgstr ""
-msgid "Keyboard Shortcuts"
+msgid "Keyboard shortcuts"
msgstr ""
-msgid "Keyboard shortcuts"
+msgid "Keys"
+msgstr ""
+
+msgid "Ki"
msgstr ""
msgid "Kubernetes"
@@ -12808,6 +13167,9 @@ msgstr ""
msgid "LDAP"
msgstr ""
+msgid "LDAP Synchronization"
+msgstr ""
+
msgid "LDAP settings"
msgstr ""
@@ -13034,12 +13396,18 @@ msgstr ""
msgid "Learn more about custom project templates"
msgstr ""
+msgid "Learn more about deploying to AWS"
+msgstr ""
+
msgid "Learn more about deploying to a cluster"
msgstr ""
msgid "Learn more about group-level project templates"
msgstr ""
+msgid "Learn more about job dependencies"
+msgstr ""
+
msgid "Learn more about signing commits"
msgstr ""
@@ -13091,9 +13459,21 @@ msgstr ""
msgid "License History"
msgstr ""
+msgid "License ID:"
+msgstr ""
+
+msgid "License URL"
+msgstr ""
+
msgid "License-Check"
msgstr ""
+msgid "LicenseCompliance|%{docLinkStart}License Approvals%{docLinkEnd} are active"
+msgstr ""
+
+msgid "LicenseCompliance|%{docLinkStart}License Approvals%{docLinkEnd} are inactive"
+msgstr ""
+
msgid "LicenseCompliance|Add a license"
msgstr ""
@@ -13115,9 +13495,15 @@ msgstr ""
msgid "LicenseCompliance|Deny"
msgstr ""
+msgid "LicenseCompliance|Learn more about %{linkStart}License Approvals%{linkEnd}"
+msgstr ""
+
msgid "LicenseCompliance|License"
msgstr ""
+msgid "LicenseCompliance|License Approvals"
+msgstr ""
+
msgid "LicenseCompliance|License Compliance detected %d license and policy violation for the source branch only; approval required"
msgid_plural "LicenseCompliance|License Compliance detected %d licenses and policy violations for the source branch only; approval required"
msgstr[0] ""
@@ -13348,6 +13734,9 @@ msgstr ""
msgid "List your Bitbucket Server repositories"
msgstr ""
+msgid "Lists"
+msgstr ""
+
msgid "Live preview"
msgstr ""
@@ -13531,6 +13920,9 @@ msgstr ""
msgid "Manage project labels"
msgstr ""
+msgid "Manage storage usage"
+msgstr ""
+
msgid "Manage two-factor authentication"
msgstr ""
@@ -13546,6 +13938,9 @@ msgstr ""
msgid "Manifest file import"
msgstr ""
+msgid "Manifest import"
+msgstr ""
+
msgid "Manual job"
msgstr ""
@@ -13693,6 +14088,12 @@ msgstr ""
msgid "Maximum field length"
msgstr ""
+msgid "Maximum file size is 2MB. Please select a smaller file."
+msgstr ""
+
+msgid "Maximum import size (MB)"
+msgstr ""
+
msgid "Maximum job timeout"
msgstr ""
@@ -13732,6 +14133,9 @@ msgstr ""
msgid "Maximum size of Elasticsearch bulk indexing requests."
msgstr ""
+msgid "Maximum size of import files."
+msgstr ""
+
msgid "Maximum size of individual attachments in comments."
msgstr ""
@@ -13783,6 +14187,9 @@ msgstr ""
msgid "Merge (when the pipeline succeeds)"
msgstr ""
+msgid "Merge Conflicts"
+msgstr ""
+
msgid "Merge Request"
msgstr ""
@@ -13909,6 +14316,9 @@ msgstr ""
msgid "MergeRequests|Squash task canceled: another squash is already in progress."
msgstr ""
+msgid "MergeRequests|This project does not allow squashing commits when merge requests are accepted."
+msgstr ""
+
msgid "MergeRequests|Thread stays resolved"
msgstr ""
@@ -14062,6 +14472,12 @@ msgstr ""
msgid "MetricsSettings|Add a button to the metrics dashboard linking directly to your existing external dashboard."
msgstr ""
+msgid "MetricsSettings|Choose whether to display dashboard metrics in UTC or the user's local timezone."
+msgstr ""
+
+msgid "MetricsSettings|Dashboard timezone"
+msgstr ""
+
msgid "MetricsSettings|External dashboard URL"
msgstr ""
@@ -14071,6 +14487,12 @@ msgstr ""
msgid "MetricsSettings|Metrics Dashboard"
msgstr ""
+msgid "MetricsSettings|UTC (Coordinated Universal Time)"
+msgstr ""
+
+msgid "MetricsSettings|User's local timezone"
+msgstr ""
+
msgid "Metrics|Add metric"
msgstr ""
@@ -14086,6 +14508,9 @@ msgstr ""
msgid "Metrics|Create metric"
msgstr ""
+msgid "Metrics|Current"
+msgstr ""
+
msgid "Metrics|Delete metric"
msgstr ""
@@ -14138,6 +14563,9 @@ msgstr ""
msgid "Metrics|Max"
msgstr ""
+msgid "Metrics|Min"
+msgstr ""
+
msgid "Metrics|Must be a valid PromQL query."
msgstr ""
@@ -14153,6 +14581,9 @@ msgstr ""
msgid "Metrics|Refresh dashboard"
msgstr ""
+msgid "Metrics|Select a value"
+msgstr ""
+
msgid "Metrics|Star dashboard"
msgstr ""
@@ -14171,12 +14602,18 @@ msgstr ""
msgid "Metrics|There was an error getting annotations information."
msgstr ""
+msgid "Metrics|There was an error getting dashboard validation warnings information."
+msgstr ""
+
msgid "Metrics|There was an error getting deployment information."
msgstr ""
msgid "Metrics|There was an error getting environments information."
msgstr ""
+msgid "Metrics|There was an error getting options for variable \"%{name}\"."
+msgstr ""
+
msgid "Metrics|There was an error trying to validate your query"
msgstr ""
@@ -14219,6 +14656,9 @@ msgstr ""
msgid "Metrics|You're about to permanently delete this metric. This cannot be undone."
msgstr ""
+msgid "Metrics|Your dashboard schema is invalid. Edit the dashboard to correct the YAML schema."
+msgstr ""
+
msgid "Metrics|e.g. HTTP requests"
msgstr ""
@@ -14234,12 +14674,18 @@ msgstr ""
msgid "Metrics|e.g. req/sec"
msgstr ""
+msgid "Mi"
+msgstr ""
+
msgid "Microsoft Azure"
msgstr ""
msgid "Middleman project with Static Site Editor support"
msgstr ""
+msgid "Migrate your data from an external source like GitHub, Bitbucket, or another instance of GitLab."
+msgstr ""
+
msgid "Migrated %{success_count}/%{total_count} files."
msgstr ""
@@ -14376,6 +14822,9 @@ msgstr ""
msgid "Minimum capacity to be available before we schedule more mirrors preemptively."
msgstr ""
+msgid "Minimum interval in days"
+msgstr ""
+
msgid "Minimum length is %{minimum_password_length} characters"
msgstr ""
@@ -14562,6 +15011,12 @@ msgstr ""
msgid "Multiple uploaders found: %{uploader_types}"
msgstr ""
+msgid "Must match with the %{codeStart}external_url%{codeEnd} in %{codeStart}/etc/gitlab/gitlab.rb%{codeEnd}."
+msgstr ""
+
+msgid "Must match with the %{codeStart}geo_node_name%{codeEnd} in %{codeStart}/etc/gitlab/gitlab.rb%{codeEnd}. %{linkStart}More information%{linkEnd}"
+msgstr ""
+
msgid "My Awesome Group"
msgstr ""
@@ -14676,10 +15131,16 @@ msgstr ""
msgid "New Application"
msgstr ""
+msgid "New Branch"
+msgstr ""
+
+msgid "New Deploy Key"
+msgstr ""
+
msgid "New Environment"
msgstr ""
-msgid "New Geo Node"
+msgid "New File"
msgstr ""
msgid "New Group"
@@ -14698,15 +15159,15 @@ msgstr[1] ""
msgstr[2] ""
msgstr[3] ""
-msgid "New Iteration"
-msgstr ""
-
msgid "New Jira import"
msgstr ""
msgid "New Label"
msgstr ""
+msgid "New Merge Request"
+msgstr ""
+
msgid "New Milestone"
msgstr ""
@@ -14725,6 +15186,9 @@ msgstr ""
msgid "New Snippet"
msgstr ""
+msgid "New User"
+msgstr ""
+
msgid "New branch"
msgstr ""
@@ -14767,6 +15231,9 @@ msgstr ""
msgid "New issue title"
msgstr ""
+msgid "New iteration"
+msgstr ""
+
msgid "New iteration created"
msgstr ""
@@ -14944,6 +15411,12 @@ msgstr ""
msgid "No grouping"
msgstr ""
+msgid "No iteration"
+msgstr ""
+
+msgid "No iterations to show"
+msgstr ""
+
msgid "No job log"
msgstr ""
@@ -14986,6 +15459,9 @@ msgstr ""
msgid "No other labels with such name or description"
msgstr ""
+msgid "No panels matching properties %{opts}"
+msgstr ""
+
msgid "No parent group"
msgstr ""
@@ -15019,6 +15495,9 @@ msgstr ""
msgid "No schedules"
msgstr ""
+msgid "No stack trace for this error"
+msgstr ""
+
msgid "No starrers matched your search"
msgstr ""
@@ -15031,7 +15510,7 @@ msgstr ""
msgid "No test coverage"
msgstr ""
-msgid "No thanks, don't show this again"
+msgid "No thanks"
msgstr ""
msgid "No vulnerabilities present"
@@ -15046,9 +15525,6 @@ msgstr ""
msgid "No, directly import the existing email addresses and usernames."
msgstr ""
-msgid "No, not interested right now"
-msgstr ""
-
msgid "No. of commits"
msgstr ""
@@ -15094,9 +15570,6 @@ msgstr ""
msgid "Not found."
msgstr ""
-msgid "Not helpful"
-msgstr ""
-
msgid "Not now"
msgstr ""
@@ -15253,6 +15726,9 @@ msgstr ""
msgid "Notifications on"
msgstr ""
+msgid "Notify users by email when sign-in location is not recognized"
+msgstr ""
+
msgid "Nov"
msgstr ""
@@ -15319,9 +15795,6 @@ msgstr ""
msgid "Oh no!"
msgstr ""
-msgid "Ok let's go"
-msgstr ""
-
msgid "Oldest first"
msgstr ""
@@ -15334,16 +15807,49 @@ msgstr ""
msgid "On track"
msgstr ""
+msgid "OnDemandScans|Attached branch"
+msgstr ""
+
+msgid "OnDemandScans|Attached branch is where the scan job runs."
+msgstr ""
+
+msgid "OnDemandScans|Could not run the scan. Please try again."
+msgstr ""
+
msgid "OnDemandScans|Create new DAST scan"
msgstr ""
+msgid "OnDemandScans|DAST will scan the target URL and any discovered sub URLs."
+msgstr ""
+
+msgid "OnDemandScans|New on-demand DAST scan"
+msgstr ""
+
msgid "OnDemandScans|On-demand Scans"
msgstr ""
+msgid "OnDemandScans|On-demand scans run outside the DevOps cycle and find vulnerabilities in your projects. %{learnMoreLinkStart}Learn more%{learnMoreLinkEnd}"
+msgstr ""
+
+msgid "OnDemandScans|Only a passive scan can be performed on demand."
+msgstr ""
+
+msgid "OnDemandScans|Passive DAST Scan"
+msgstr ""
+
+msgid "OnDemandScans|Please enter a valid URL format, ex: http://www.example.com/home"
+msgstr ""
+
+msgid "OnDemandScans|Run this scan"
+msgstr ""
+
+msgid "OnDemandScans|Scan mode"
+msgstr ""
+
msgid "OnDemandScans|Schedule or run scans immediately against target sites. Currently available on-demand scan type: DAST. %{helpLinkStart}More information%{helpLinkEnd}"
msgstr ""
-msgid "Onboarding"
+msgid "OnDemandScans|Target URL"
msgstr ""
msgid "Once imported, repositories can be mirrored over SSH. Read more %{link_start}here%{link_end}."
@@ -15371,6 +15877,9 @@ msgstr ""
msgid "One or more of you personal access tokens were revoked"
msgstr ""
+msgid "One or more of your %{provider} projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
+msgstr ""
+
msgid "One or more of your Bitbucket projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
msgstr ""
@@ -15872,6 +16381,9 @@ msgstr ""
msgid "Parent epic is not present."
msgstr ""
+msgid "Parsing error for param :embed_json. %{message}"
+msgstr ""
+
msgid "Part of merge request changes"
msgstr ""
@@ -15884,6 +16396,9 @@ msgstr ""
msgid "Passed"
msgstr ""
+msgid "Passed on"
+msgstr ""
+
msgid "Password"
msgstr ""
@@ -15968,6 +16483,9 @@ msgstr ""
msgid "Perform common operations on GitLab project"
msgstr ""
+msgid "Performance and resource management"
+msgstr ""
+
msgid "Performance optimization"
msgstr ""
@@ -16175,7 +16693,7 @@ msgstr ""
msgid "Pipelines|Something went wrong while cleaning runners cache."
msgstr ""
-msgid "Pipelines|There are currently no %{scope} pipelines."
+msgid "Pipelines|There are currently no finished pipelines."
msgstr ""
msgid "Pipelines|There are currently no pipelines."
@@ -16286,6 +16804,9 @@ msgstr ""
msgid "Pipeline|Stop pipeline #%{pipelineId}?"
msgstr ""
+msgid "Pipeline|Tag name"
+msgstr ""
+
msgid "Pipeline|Trigger author"
msgstr ""
@@ -16367,12 +16888,18 @@ msgstr ""
msgid "Please check your email (%{email}) to verify that you own this address and unlock the power of CI/CD. Didn't receive it? %{resend_link}. Wrong email address? %{update_link}."
msgstr ""
+msgid "Please choose a file"
+msgstr ""
+
msgid "Please choose a group URL with no special characters."
msgstr ""
msgid "Please complete your profile with email address"
msgstr ""
+msgid "Please convert %{linkStart}them to Git%{linkEnd}, and go through the %{linkToImportFlow} again."
+msgstr ""
+
msgid "Please convert them to %{link_to_git}, and go through the %{link_to_import_flow} again."
msgstr ""
@@ -16430,6 +16957,9 @@ msgstr ""
msgid "Please provide a valid email address."
msgstr ""
+msgid "Please provide attributes to update"
+msgstr ""
+
msgid "Please refer to <a href=\"%{docs_url}\">%{docs_url}</a>"
msgstr ""
@@ -17165,6 +17695,9 @@ msgstr ""
msgid "Project has too many %{label_for_message} to search"
msgstr ""
+msgid "Project is required when cluster_type is :project"
+msgstr ""
+
msgid "Project members"
msgstr ""
@@ -17282,7 +17815,40 @@ msgstr ""
msgid "ProjectService|Comment"
msgstr ""
-msgid "ProjectService|Comment will be posted on each event"
+msgid "ProjectService|Event will be triggered by a push to the repository"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a commit is created/updated"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a confidential issue is created/updated/closed"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a deployment finishes"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a merge request is created/updated/merged"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a new tag is pushed to the repository"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a new, unique alert is recorded"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a pipeline status changes"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a wiki page is created/updated"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when an issue is created/updated/closed"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when someone adds a comment"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when someone adds a comment on a confidential issue"
msgstr ""
msgid "ProjectService|Perform common operations on GitLab project: %{project_name}"
@@ -17333,6 +17899,9 @@ msgstr ""
msgid "ProjectSettings|Enable 'Delete source branch' option by default"
msgstr ""
+msgid "ProjectSettings|Enable merge trains and pipelines for merged results"
+msgstr ""
+
msgid "ProjectSettings|Every merge creates a merge commit"
msgstr ""
@@ -17363,7 +17932,7 @@ msgstr ""
msgid "ProjectSettings|Forks"
msgstr ""
-msgid "ProjectSettings|Git Large File Storage"
+msgid "ProjectSettings|Git Large File Storage (LFS)"
msgstr ""
msgid "ProjectSettings|Internal"
@@ -17399,9 +17968,6 @@ msgstr ""
msgid "ProjectSettings|Merge options"
msgstr ""
-msgid "ProjectSettings|Merge pipelines will try to validate the post-merge result prior to merging"
-msgstr ""
-
msgid "ProjectSettings|Merge requests"
msgstr ""
@@ -17429,6 +17995,9 @@ msgstr ""
msgid "ProjectSettings|Pipelines"
msgstr ""
+msgid "ProjectSettings|Pipelines for merge requests must be enabled in the CI/CD configuration file, or pipelines could be unresolvable or dropped"
+msgstr ""
+
msgid "ProjectSettings|Pipelines must succeed"
msgstr ""
@@ -17456,6 +18025,9 @@ msgstr ""
msgid "ProjectSettings|Show link to create/view merge request when pushing from the command line"
msgstr ""
+msgid "ProjectSettings|Skipped pipelines are considered successful"
+msgstr ""
+
msgid "ProjectSettings|Snippets"
msgstr ""
@@ -17471,6 +18043,9 @@ msgstr ""
msgid "ProjectSettings|These checks must pass before merge requests can be merged"
msgstr ""
+msgid "ProjectSettings|This introduces the risk of merging changes that will not pass the pipeline."
+msgstr ""
+
msgid "ProjectSettings|This setting is applied on the server level and can be overridden by an admin."
msgstr ""
@@ -17648,15 +18223,27 @@ msgstr ""
msgid "ProjectsNew|Blank project"
msgstr ""
+msgid "ProjectsNew|Connect your external repository to GitLab CI/CD."
+msgstr ""
+
msgid "ProjectsNew|Contact an administrator to enable options for importing your project."
msgstr ""
msgid "ProjectsNew|Create"
msgstr ""
+msgid "ProjectsNew|Create a blank project to house your files, plan your work, and collaborate on code, among other things."
+msgstr ""
+
+msgid "ProjectsNew|Create blank project"
+msgstr ""
+
msgid "ProjectsNew|Create from template"
msgstr ""
+msgid "ProjectsNew|Create new project"
+msgstr ""
+
msgid "ProjectsNew|Creating project & repository."
msgstr ""
@@ -17681,6 +18268,9 @@ msgstr ""
msgid "ProjectsNew|Project description %{tag_start}(optional)%{tag_end}"
msgstr ""
+msgid "ProjectsNew|Run CI/CD for external repository"
+msgstr ""
+
msgid "ProjectsNew|Template"
msgstr ""
@@ -17942,6 +18532,9 @@ msgstr ""
msgid "Protected Branch"
msgstr ""
+msgid "Protected Branches"
+msgstr ""
+
msgid "Protected Environment"
msgstr ""
@@ -17954,6 +18547,9 @@ msgstr ""
msgid "Protected Tag"
msgstr ""
+msgid "Protected Tags"
+msgstr ""
+
msgid "Protected branches"
msgstr ""
@@ -18053,6 +18649,9 @@ msgstr ""
msgid "Provider"
msgstr ""
+msgid "Proxy support for this API is not available currently"
+msgstr ""
+
msgid "Pseudonymizer data collection"
msgstr ""
@@ -18077,6 +18676,9 @@ msgstr ""
msgid "Publish to status page"
msgstr ""
+msgid "Published on status page"
+msgstr ""
+
msgid "Publishes this issue to the associated status page."
msgstr ""
@@ -18251,6 +18853,9 @@ msgstr ""
msgid "Recipe"
msgstr ""
+msgid "Reconfigure"
+msgstr ""
+
msgid "Recover hidden stage"
msgstr ""
@@ -18339,6 +18944,9 @@ msgstr ""
msgid "Registration|Your profile"
msgstr ""
+msgid "Regulate approvals by authors/committers, based on compliance frameworks. Can be changed only at the instance level."
+msgstr ""
+
msgid "Rejected (closed)"
msgstr ""
@@ -18391,15 +18999,24 @@ msgstr ""
msgid "ReleaseAssetLinkType|Image"
msgstr ""
+msgid "ReleaseAssetLinkType|Images"
+msgstr ""
+
msgid "ReleaseAssetLinkType|Other"
msgstr ""
msgid "ReleaseAssetLinkType|Package"
msgstr ""
+msgid "ReleaseAssetLinkType|Packages"
+msgstr ""
+
msgid "ReleaseAssetLinkType|Runbook"
msgstr ""
+msgid "ReleaseAssetLinkType|Runbooks"
+msgstr ""
+
msgid "Releases"
msgstr ""
@@ -18412,6 +19029,9 @@ msgstr ""
msgid "Releases documentation"
msgstr ""
+msgid "Releases|New Release"
+msgstr ""
+
msgid "Release|Something went wrong while getting the release details"
msgstr ""
@@ -18484,6 +19104,9 @@ msgstr ""
msgid "Remove fork relationship"
msgstr ""
+msgid "Remove from batch"
+msgstr ""
+
msgid "Remove from board"
msgstr ""
@@ -18673,6 +19296,12 @@ msgstr ""
msgid "Replication"
msgstr ""
+msgid "Replication enabled"
+msgstr ""
+
+msgid "Replication paused"
+msgstr ""
+
msgid "Reply by email"
msgstr ""
@@ -18700,15 +19329,15 @@ msgstr ""
msgid "Reported %{timeAgo} by %{reportedBy}"
msgstr ""
-msgid "Reported Resource Changes: %{addNum} to add, %{changeNum} to change, %{deleteNum} to delete"
-msgstr ""
-
msgid "Reporter"
msgstr ""
msgid "Reporting"
msgstr ""
+msgid "Reports"
+msgstr ""
+
msgid "Reports|%{combinedString} and %{resolvedString}"
msgstr ""
@@ -18749,6 +19378,9 @@ msgstr ""
msgid "Reports|Failure"
msgstr ""
+msgid "Reports|Identifier"
+msgstr ""
+
msgid "Reports|Metrics reports are loading"
msgstr ""
@@ -18761,6 +19393,9 @@ msgstr ""
msgid "Reports|Metrics reports failed loading results"
msgstr ""
+msgid "Reports|Scanner"
+msgstr ""
+
msgid "Reports|Severity"
msgstr ""
@@ -18809,15 +19444,33 @@ msgstr ""
msgid "Repository cleanup has started. You will receive an email once the cleanup operation is complete."
msgstr ""
+msgid "Repository files count over the limit"
+msgstr ""
+
+msgid "Repository has an invalid default branch name."
+msgstr ""
+
+msgid "Repository has more than one branch."
+msgstr ""
+
msgid "Repository has no locks."
msgstr ""
+msgid "Repository has tags."
+msgstr ""
+
msgid "Repository maintenance"
msgstr ""
msgid "Repository mirroring"
msgstr ""
+msgid "Repository must contain at least 1 file."
+msgstr ""
+
+msgid "Repository size is above the limit."
+msgstr ""
+
msgid "Repository static objects"
msgstr ""
@@ -18827,7 +19480,7 @@ msgstr ""
msgid "Repository sync capacity"
msgstr ""
-msgid "Repository: %{counter_repositories} / Wikis: %{counter_wikis} / Build Artifacts: %{counter_build_artifacts} / LFS: %{counter_lfs_objects}"
+msgid "Repository: %{counter_repositories} / Wikis: %{counter_wikis} / Build Artifacts: %{counter_build_artifacts} / LFS: %{counter_lfs_objects} / Snippets: %{counter_snippets}"
msgstr ""
msgid "RepositorySettingsAccessLevel|Select"
@@ -18839,6 +19492,9 @@ msgstr ""
msgid "Request Headers"
msgstr ""
+msgid "Request details"
+msgstr ""
+
msgid "Request parameter %{param} is missing."
msgstr ""
@@ -18949,6 +19605,9 @@ msgstr ""
msgid "Reset template"
msgstr ""
+msgid "Reset to project defaults"
+msgstr ""
+
msgid "Resetting the authorization key for this project will require updating the authorization key in every alert source it is enabled in."
msgstr ""
@@ -19045,9 +19704,6 @@ msgstr ""
msgid "Resume"
msgstr ""
-msgid "Resume replication"
-msgstr ""
-
msgid "Resync"
msgstr ""
@@ -19692,6 +20348,9 @@ msgstr ""
msgid "SecurityReports|False positive"
msgstr ""
+msgid "SecurityReports|Group Security Dashboard"
+msgstr ""
+
msgid "SecurityReports|Hide dismissed"
msgstr ""
@@ -19746,10 +20405,13 @@ msgstr ""
msgid "SecurityReports|Remove project from dashboard"
msgstr ""
-msgid "SecurityReports|Report type"
+msgid "SecurityReports|Return to dashboard"
+msgstr ""
+
+msgid "SecurityReports|Scan details"
msgstr ""
-msgid "SecurityReports|Return to dashboard"
+msgid "SecurityReports|Scanner type"
msgstr ""
msgid "SecurityReports|Security Dashboard"
@@ -19806,6 +20468,9 @@ msgstr ""
msgid "SecurityReports|There was an error while generating the report."
msgstr ""
+msgid "SecurityReports|Unable to add %{invalidProjectsMessage}"
+msgstr ""
+
msgid "SecurityReports|Unable to add %{invalidProjects}"
msgstr ""
@@ -19920,6 +20585,9 @@ msgstr ""
msgid "Select due date"
msgstr ""
+msgid "Select file"
+msgstr ""
+
msgid "Select group or project"
msgstr ""
@@ -19983,9 +20651,6 @@ msgstr ""
msgid "Select the branch you want to set as the default for this project. All merge requests and commits will automatically be made against this branch unless you specify a different one."
msgstr ""
-msgid "Select the configured storage available for new repositories to be placed on."
-msgstr ""
-
msgid "Select the custom project template source group."
msgstr ""
@@ -20178,12 +20843,6 @@ msgstr ""
msgid "Service Desk is enabled but not yet active"
msgstr ""
-msgid "Service Desk is off"
-msgstr ""
-
-msgid "Service Desk is on"
-msgstr ""
-
msgid "Service Templates"
msgstr ""
@@ -20271,6 +20930,15 @@ msgstr ""
msgid "Set the milestone to %{milestone_reference}."
msgstr ""
+msgid "Set the number of concurrent requests this secondary node will make to the primary node while backfilling."
+msgstr ""
+
+msgid "Set the synchronization and verification capacity for the secondary node."
+msgstr ""
+
+msgid "Set the timeout in seconds to send a secondary node status to the primary and IPs allowed for the secondary nodes."
+msgstr ""
+
msgid "Set time estimate"
msgstr ""
@@ -20310,6 +20978,9 @@ msgstr ""
msgid "Set weight to %{weight}."
msgstr ""
+msgid "Set what should be replicated by choosing specific projects or groups by the secondary node."
+msgstr ""
+
msgid "SetPasswordToCloneLink|set a password"
msgstr ""
@@ -20436,6 +21107,9 @@ msgstr ""
msgid "Show complete raw log"
msgstr ""
+msgid "Show details"
+msgstr ""
+
msgid "Show file browser"
msgstr ""
@@ -20445,10 +21119,13 @@ msgstr ""
msgid "Show latest version"
msgstr ""
+msgid "Show list"
+msgstr ""
+
msgid "Show me everything"
msgstr ""
-msgid "Show me how"
+msgid "Show me how to add a pipeline"
msgstr ""
msgid "Show me more advanced stuff"
@@ -20501,9 +21178,6 @@ msgstr ""
msgid "Showing all issues"
msgstr ""
-msgid "Showing all labels"
-msgstr ""
-
msgid "Showing last %{size} of log -"
msgstr ""
@@ -20615,9 +21289,6 @@ msgstr ""
msgid "Skip outdated deployment jobs"
msgstr ""
-msgid "Skip this for now"
-msgstr ""
-
msgid "Skipped"
msgstr ""
@@ -20762,6 +21433,9 @@ msgstr ""
msgid "Something went wrong while adding your award. Please try again."
msgstr ""
+msgid "Something went wrong while applying the batch of suggestions. Please try again."
+msgstr ""
+
msgid "Something went wrong while applying the suggestion. Please try again."
msgstr ""
@@ -20864,10 +21538,13 @@ msgstr ""
msgid "Something went wrong while updating a requirement."
msgstr ""
+msgid "Something went wrong while updating assignees"
+msgstr ""
+
msgid "Something went wrong while updating your list settings"
msgstr ""
-msgid "Something went wrong with your automatic subscription renewal"
+msgid "Something went wrong with your automatic subscription renewal."
msgstr ""
msgid "Something went wrong, unable to add %{project} to dashboard"
@@ -20885,6 +21562,9 @@ msgstr ""
msgid "Something went wrong, unable to search projects"
msgstr ""
+msgid "Something went wrong."
+msgstr ""
+
msgid "Something went wrong. Please try again."
msgstr ""
@@ -21068,6 +21748,9 @@ msgstr ""
msgid "Source code"
msgstr ""
+msgid "Source code (%{fileExtension})"
+msgstr ""
+
msgid "Source is not available"
msgstr ""
@@ -21152,6 +21835,9 @@ msgstr ""
msgid "Stack trace"
msgstr ""
+msgid "Stacktrace snippet"
+msgstr ""
+
msgid "Stage"
msgstr ""
@@ -21257,6 +21943,9 @@ msgstr ""
msgid "Start thread & reopen %{noteable_name}"
msgstr ""
+msgid "Start using Directed Acyclic Graphs (DAG)"
+msgstr ""
+
msgid "Start your Free Gold Trial"
msgstr ""
@@ -21719,6 +22408,21 @@ msgstr ""
msgid "SuggestedColors|Very pale orange"
msgstr ""
+msgid "Suggestion is not applicable as the suggestion was not found."
+msgstr ""
+
+msgid "Suggestions are not applicable as one or more suggestions were not found."
+msgstr ""
+
+msgid "Suggestions are not applicable as their lines cannot overlap."
+msgstr ""
+
+msgid "Suggestions must all be on the same branch."
+msgstr ""
+
+msgid "Suggestions that change line count can't be added to batches, yet."
+msgstr ""
+
msgid "Suggestions:"
msgstr ""
@@ -21761,9 +22465,6 @@ msgstr ""
msgid "Synchronization disabled"
msgstr ""
-msgid "Synchronization of container repositories is disabled."
-msgstr ""
-
msgid "System"
msgstr ""
@@ -21944,6 +22645,38 @@ msgstr ""
msgid "Terms of Service and Privacy Policy"
msgstr ""
+msgid "Terraform|%{number} Terraform report failed to generate"
+msgid_plural "Terraform|%{number} Terraform reports failed to generate"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "Terraform|%{number} Terraform report was generated in your pipelines"
+msgid_plural "Terraform|%{number} Terraform reports were generated in your pipelines"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "Terraform|A Terraform report failed to generate."
+msgstr ""
+
+msgid "Terraform|A Terraform report was generated in your pipelines."
+msgstr ""
+
+msgid "Terraform|Generating the report caused an error."
+msgstr ""
+
+msgid "Terraform|Reported Resource Changes: %{addNum} to add, %{changeNum} to change, %{deleteNum} to delete"
+msgstr ""
+
+msgid "Terraform|The Terraform report %{name} failed to generate."
+msgstr ""
+
+msgid "Terraform|The Terraform report %{name} was generated in your pipelines."
+msgstr ""
+
msgid "Test"
msgstr ""
@@ -21972,7 +22705,7 @@ msgstr ""
msgid "TestHooks|Ensure the project has CI pipelines."
msgstr ""
-msgid "TestHooks|Ensure the project has at least one commit."
+msgid "TestHooks|Ensure the project has deployments."
msgstr ""
msgid "TestHooks|Ensure the project has issues."
@@ -22075,7 +22808,7 @@ msgstr ""
msgid "The Prometheus server responded with \"bad request\". Please check your queries are correct and are supported in your Prometheus version. %{documentationLink}"
msgstr ""
-msgid "The URL defined on the primary node that secondary nodes should use to contact it. Defaults to URL"
+msgid "The URL defined on the primary node that secondary nodes should use to contact it."
msgstr ""
msgid "The URL to use for connecting to Elasticsearch. Use a comma-separated list to support clustering (e.g., \"http://localhost:9200, http://localhost:9201\")."
@@ -22084,6 +22817,9 @@ msgstr ""
msgid "The X509 Certificate to use when mutual TLS is required to communicate with the external authorization service. If left blank, the server certificate is still validated when accessing over HTTPS."
msgstr ""
+msgid "The above settings apply to all projects with the selected compliance framework(s)."
+msgstr ""
+
msgid "The amount of seconds after which a request to get a secondary node status will time out."
msgstr ""
@@ -22385,9 +23121,6 @@ msgstr ""
msgid "The total stage shows the time it takes between creating an issue and deploying the code to production. The data will be automatically added once you have completed the full idea to production cycle."
msgstr ""
-msgid "The unique identifier for the Geo node. Must match %{geoNodeName} if it is set in gitlab.rb, otherwise it must match %{externalUrl} with a trailing slash"
-msgstr ""
-
msgid "The update action will time out after %{number_of_minutes} minutes. For big repositories, use a clone/push combination."
msgstr ""
@@ -22412,9 +23145,6 @@ msgstr ""
msgid "The user you are trying to deactivate has been active in the past %{minimum_inactive_days} days and cannot be deactivated"
msgstr ""
-msgid "The user-facing URL of the Geo node"
-msgstr ""
-
msgid "The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6."
msgstr ""
@@ -22505,6 +23235,9 @@ msgstr ""
msgid "There was a problem fetching project branches."
msgstr ""
+msgid "There was a problem fetching project tags."
+msgstr ""
+
msgid "There was a problem fetching project users."
msgstr ""
@@ -22556,6 +23289,9 @@ msgstr ""
msgid "There was an error fetching the %{replicableType}"
msgstr ""
+msgid "There was an error fetching the Geo Settings"
+msgstr ""
+
msgid "There was an error fetching the Node's Groups"
msgstr ""
@@ -22676,6 +23412,12 @@ msgstr ""
msgid "This %{issuable} is locked. Only <strong>project members</strong> can comment."
msgstr ""
+msgid "This %{noteableTypeText} is %{confidentialLinkStart}confidential%{linkEnd} and %{lockedLinkStart}locked%{linkEnd}."
+msgstr ""
+
+msgid "This %{noteableTypeText} is locked."
+msgstr ""
+
msgid "This %{viewer} could not be displayed because %{reason}. You can %{options} instead."
msgstr ""
@@ -22694,6 +23436,9 @@ msgstr ""
msgid "This action can lead to data loss. To prevent accidental actions we ask you to confirm your intention."
msgstr ""
+msgid "This also resolves all related threads"
+msgstr ""
+
msgid "This also resolves the discussion"
msgstr ""
@@ -22784,6 +23529,9 @@ msgstr ""
msgid "This epic does not exist or you don't have sufficient permission."
msgstr ""
+msgid "This feature is currently in beta. We invite you to %{linkStart}give feedback%{linkEnd}."
+msgstr ""
+
msgid "This feature requires local storage to be enabled"
msgstr ""
@@ -22817,7 +23565,7 @@ msgstr ""
msgid "This is a Work in Progress"
msgstr ""
-msgid "This is a confidential issue."
+msgid "This is a confidential %{noteableTypeText}."
msgstr ""
msgid "This is a delayed job to run in %{remainingTime}"
@@ -22844,9 +23592,6 @@ msgstr ""
msgid "This is your current session"
msgstr ""
-msgid "This issue is %{confidentialLinkStart}confidential%{linkEnd} and %{lockedLinkStart}locked%{linkEnd}."
-msgstr ""
-
msgid "This issue is confidential"
msgstr ""
@@ -22856,9 +23601,6 @@ msgstr ""
msgid "This issue is in a child epic of the filtered epic"
msgstr ""
-msgid "This issue is locked."
-msgstr ""
-
msgid "This job depends on other jobs with expired/erased artifacts: %{invalid_dependencies}"
msgstr ""
@@ -22958,6 +23700,9 @@ msgstr ""
msgid "This license has already expired."
msgstr ""
+msgid "This link points to external content"
+msgstr ""
+
msgid "This may expose confidential information as the selected fork is in another namespace that can have other members."
msgstr ""
@@ -22985,6 +23730,9 @@ msgstr ""
msgid "This page will be removed in a future release."
msgstr ""
+msgid "This pipeline does not use the %{codeStart}needs%{codeEnd} keyword and can't be represented as a directed acyclic graph."
+msgstr ""
+
msgid "This pipeline makes use of a predefined CI/CD configuration enabled by %{strongStart}Auto DevOps.%{strongEnd}"
msgstr ""
@@ -23213,6 +23961,9 @@ msgstr ""
msgid "Time from last commit to merge"
msgstr ""
+msgid "Time in seconds"
+msgstr ""
+
msgid "Time in seconds GitLab will wait for a response from the external service. When the service does not respond in time, access will be denied."
msgstr ""
@@ -23638,9 +24389,15 @@ msgstr ""
msgid "Total artifacts size: %{total_size}"
msgstr ""
+msgid "Total cores (CPUs)"
+msgstr ""
+
msgid "Total issues"
msgstr ""
+msgid "Total memory (GB)"
+msgstr ""
+
msgid "Total test time for all commits/merges"
msgstr ""
@@ -23842,6 +24599,9 @@ msgstr ""
msgid "U2F only works with HTTPS-enabled websites. Contact your administrator for more details."
msgstr ""
+msgid "UI Development Kit"
+msgstr ""
+
msgid "URL"
msgstr ""
@@ -23914,9 +24674,6 @@ msgstr ""
msgid "Unable to load the merge request widget. Try reloading the page."
msgstr ""
-msgid "Unable to process group import file"
-msgstr ""
-
msgid "Unable to resolve"
msgstr ""
@@ -23932,6 +24689,9 @@ msgstr ""
msgid "Unable to sign you in to the group with SAML due to \"%{reason}\""
msgstr ""
+msgid "Unable to suggest a path. Please refresh and try again."
+msgstr ""
+
msgid "Unable to update label prioritization at this time"
msgstr ""
@@ -23947,6 +24707,9 @@ msgstr ""
msgid "Unarchiving the project will restore people's ability to make changes to it. The repository can be committed to, and issues, comments, and other entities can be created. %{strong_start}Once active, this project shows up in the search and on the dashboard.%{strong_end}"
msgstr ""
+msgid "Unassign from commenting user"
+msgstr ""
+
msgid "Unblock"
msgstr ""
@@ -24022,6 +24785,9 @@ msgstr ""
msgid "Unreachable"
msgstr ""
+msgid "Unrecognized cluster type"
+msgstr ""
+
msgid "Unresolve"
msgstr ""
@@ -24082,6 +24848,9 @@ msgstr ""
msgid "Unsubscribes from this %{quick_action_target}."
msgstr ""
+msgid "Unsupported todo type passed. Supported todo types are: %{todo_types}"
+msgstr ""
+
msgid "Until"
msgstr ""
@@ -24109,6 +24878,9 @@ msgstr ""
msgid "Update approval rule"
msgstr ""
+msgid "Update approvers"
+msgstr ""
+
msgid "Update failed"
msgstr ""
@@ -24118,6 +24890,9 @@ msgstr ""
msgid "Update it"
msgstr ""
+msgid "Update iteration"
+msgstr ""
+
msgid "Update now"
msgstr ""
@@ -24163,7 +24938,7 @@ msgstr ""
msgid "Updated at"
msgstr ""
-msgid "Updated to"
+msgid "Updated to %{linkStart}chart v%{linkEnd}"
msgstr ""
msgid "Updating"
@@ -24193,6 +24968,9 @@ msgstr ""
msgid "Upload CSV file"
msgstr ""
+msgid "Upload License"
+msgstr ""
+
msgid "Upload New File"
msgstr ""
@@ -24274,16 +25052,19 @@ msgstr ""
msgid "UsageQuota|Pipelines"
msgstr ""
+msgid "UsageQuota|Purchase more storage"
+msgstr ""
+
msgid "UsageQuota|Repositories"
msgstr ""
msgid "UsageQuota|Repository"
msgstr ""
-msgid "UsageQuota|Storage"
+msgid "UsageQuota|Snippets"
msgstr ""
-msgid "UsageQuota|Storage usage:"
+msgid "UsageQuota|Storage"
msgstr ""
msgid "UsageQuota|This namespace has no projects which use shared runners"
@@ -24316,6 +25097,12 @@ msgstr ""
msgid "UsageQuota|Wikis"
msgstr ""
+msgid "UsageQuota|You used: %{usage} %{limit}"
+msgstr ""
+
+msgid "UsageQuota|out of %{formattedLimit} of your namespace storage"
+msgstr ""
+
msgid "Use %{code_start}::%{code_end} to create a %{link_start}scoped label set%{link_end} (eg. %{code_start}priority::1%{code_end})"
msgstr "Pomocí %{code_start}::%{code_end} vytvořte sadu %{link_start} štítků s rozsahem %{link_end} (např. Priorita %{code_start}::1%{code_end})"
@@ -24382,6 +25169,9 @@ msgstr ""
msgid "User IDs"
msgstr ""
+msgid "User Lists can only be created and modified with %{linkStart}the API%{linkEnd}"
+msgstr ""
+
msgid "User OAuth applications"
msgstr ""
@@ -24406,6 +25196,9 @@ msgstr ""
msgid "User key was successfully removed."
msgstr ""
+msgid "User list %{name} will be removed. Are you sure?"
+msgstr ""
+
msgid "User map"
msgstr ""
@@ -24427,148 +25220,10 @@ msgstr ""
msgid "User was successfully updated."
msgstr ""
-msgid "UserOnboardingTour|%{activeTour}/%{totalTours}"
-msgstr ""
-
-msgid "UserOnboardingTour|%{completed}/%{total} steps completed"
-msgstr ""
-
-msgid "UserOnboardingTour|%{emphasisStart}Well done!%{emphasisEnd}%{lineBreak}%{lineBreak}That's it for our guided tour, congratulations for making it all the way to the end!%{lineBreak}%{lineBreak}We hope this gave you a good overview of GitLab and how it can help you. We'll now show you how to create your own project and invite your colleagues."
-msgstr ""
-
-msgid "UserOnboardingTour|Adding other members to a project is done through Project Settings. Click on %{emphasisStart}Settings%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Alright, that's it for Commits. Let's take a look at the %{emphasisStart}Branches%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Awesome! Now click on %{emphasisStart}Members%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Click on one of the %{emphasisStart}Compare%{emphasisEnd} buttons to compare a branch to master."
-msgstr ""
-
-msgid "UserOnboardingTour|Click on one of the %{emphasisStart}pipeline IDs%{emphasisEnd} to see the details of a pipeline."
-msgstr ""
-
-msgid "UserOnboardingTour|Click to open the latest commit to see its details."
-msgstr ""
-
-msgid "UserOnboardingTour|Close 'Learn GitLab'"
-msgstr ""
-
-msgid "UserOnboardingTour|Commits are shown in chronological order and can be filtered by the commit message or by the branch."
-msgstr ""
-
-msgid "UserOnboardingTour|Create a project"
-msgstr ""
-
-msgid "UserOnboardingTour|Exit 'Learn GitLab'"
-msgstr ""
-
-msgid "UserOnboardingTour|Got it"
-msgstr ""
-
-msgid "UserOnboardingTour|Great job! %{clapHands} We hope the tour was helpful and that you learned how to use GitLab.%{lineBreak}%{lineBreak}We'd love to get your feedback on this tour.%{lineBreak}%{lineBreak}%{emphasisStart}How helpful would you say this guided tour was?%{emphasisEnd}%{lineBreak}%{lineBreak}"
-msgstr ""
-
-msgid "UserOnboardingTour|Guided GitLab Tour"
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can compare the changes of this branch to another one. Changes are divided by files so that it's easier to see what was changed where."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can create a project from scratch, start with a template or import a repository from other platforms. Whatever you choose, we'll guide you through the process.%{lineBreak}%{lineBreak}Fill in your new project information and click on %{emphasisStart}Create Project%{emphasisEnd} to progress to the next step."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can see the breakdown of the pipelines: its stages and jobs in each of the stages and their status.%{lineBreak}%{lineBreak}Our CI/CD pipelines are quite complex, most of our users have fewer and simpler pipelines."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can see the current members of the project (just you at the moment) and invite new members.%{lineBreak}%{lineBreak}You can invite multiple members at once (existing GitLab users or invite by email) and you can also set their roles and permissions.%{lineBreak}%{lineBreak}Add a few members and click on %{emphasisStart}Add to project%{emphasisEnd} to complete this step."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can see what changes were made with this commit, on what branch and if there's a related merge request. The status of the pipeline will also show up if CI/CD is set up.%{lineBreak}%{lineBreak}You can also comment on the lines of code that were changed and start a discussion with your colleagues!"
-msgstr ""
-
-msgid "UserOnboardingTour|Here's an overview of branches in the %{emphasisStart}%{projectName}%{emphasisEnd} project. They're split into Active and Stale.%{lineBreak}%{lineBreak}From here, you can create a new merge request from a branch, or compare the branch to any other branch in the project. By default, it will compare it to the master branch."
-msgstr ""
-
-msgid "UserOnboardingTour|Invite colleagues"
-msgstr ""
-
-msgid "UserOnboardingTour|Issues are great for communicating and keeping track of progress in GitLab. These are all issues that are open in the %{emphasisStart}%{projectName}%{emphasisEnd}.%{lineBreak}%{lineBreak}You can help us improve GitLab by contributing work to issues that are labeled <span class=\"badge color-label accept-mr-label\">Accepting merge requests</span>.%{lineBreak}%{lineBreak}This list can be filtered by labels, milestones, assignees, authors... We'll show you how it looks when the list is filtered by a label."
-msgstr ""
-
-msgid "UserOnboardingTour|Learn GitLab"
-msgstr ""
-
-msgid "UserOnboardingTour|Let's take a closer look at a merge request. Click on the title of one."
-msgstr ""
-
-msgid "UserOnboardingTour|Let's take a closer look at all the commits. Click on %{emphasisStart}Commits%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Let's take a closer look at the repository of this project. Click on %{emphasisStart}Repository%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|No thanks"
-msgstr ""
-
-msgid "UserOnboardingTour|Ok, let's go"
-msgstr ""
-
-msgid "UserOnboardingTour|Ok, show me"
-msgstr ""
-
-msgid "UserOnboardingTour|Open one of the issues by clicking on its title."
-msgstr ""
-
-msgid "UserOnboardingTour|Restart this step"
-msgstr ""
-
-msgid "UserOnboardingTour|Skip this step"
-msgstr ""
-
-msgid "UserOnboardingTour|Sweet! Your project was created and is ready to be used.%{lineBreak}%{lineBreak}You can start adding files to the repository or clone it. One last thing we want to show you is how to invite your colleagues to your new project."
-msgstr ""
-
-msgid "UserOnboardingTour|Take a look. Here's a nifty menu for quickly creating issues, merge requests, snippets, projects and groups. Click on it and select \"New project\" from the \"GitLab\" section to get started."
-msgstr ""
-
-msgid "UserOnboardingTour|Thanks for taking the guided tour. Remember, if you want to go through it again, you can start %{emphasisStart}Learn GitLab%{emphasisEnd} in the help menu on the top right."
-msgstr ""
-
-msgid "UserOnboardingTour|Thanks for the feedback! %{thumbsUp}"
-msgstr ""
-
-msgid "UserOnboardingTour|That's it for issues. Let'st take a look at %{emphasisStart}Merge Requests%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|That's it for merge requests. Now for the final part of this guided tour - the %{emphasisStart}CI/CD%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|That's it for the Repository. Let's take a look at the %{emphasisStart}Issues%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|The structure of this page is very similar to issues. Status, description, discussion and the sidebar are all here.%{lineBreak}%{lineBreak}But take a look below the description and you'll notice that there's more information about the merge request, the CI/CD pipeline and the options for approving it.%{lineBreak}%{lineBreak}Alongside the discussion you can also see more information about commits in this merge request, the status of pipelines and review all changes that were made."
-msgstr ""
-
-msgid "UserOnboardingTour|There's a lot of information here but don't worry, we'll go through it.%{lineBreak}%{lineBreak}On the top you can see the status of the issue and when it was opened and by whom. Directly below it is the issue description and below that are other %{emphasisStart}related issues%{emphasisEnd} and %{emphasisStart}merge requests%{emphasisEnd} (if any). Then below that is the %{emphasisStart}discussion%{emphasisEnd}, that's where most of the communication happens.%{lineBreak}%{lineBreak}On the right, there's a sidebar where you can view/change the %{emphasisStart}assignee, milestone, due date, labels, weight%{emphasisEnd}, etc."
+msgid "UserList|Delete %{name}?"
msgstr ""
-msgid "UserOnboardingTour|These are all the CI/CD pipelines we have for our %{emphasisStart}%{projectName}%{emphasisEnd} project.%{lineBreak}%{lineBreak}Here you can see the status of each pipeline, for what commit it's running for, its stages and the status for them."
-msgstr ""
-
-msgid "UserOnboardingTour|These are all the issues that are available for community contributions. Let's take a closer look at one of them."
-msgstr ""
-
-msgid "UserOnboardingTour|This is an overview of all merge requests in this project. Similarly to the issues overview it can be filtered down by things like labels, milestones, authors, assignees, etc."
-msgstr ""
-
-msgid "UserOnboardingTour|This is the repository for the %{emphasisStart}%{projectName}%{emphasisEnd} project. All our code is stored here. Feel free to explore and take a closer look at folders and files.%{lineBreak}%{lineBreak}Above the file structure you can see the latest commit, who the author is and the status of the CI/CD pipeline.%{lineBreak}%{lineBreak}If you scroll down below the file structure, you'll find the Readme of this project. This is defined in the README.md file at the root of the repository."
-msgstr ""
-
-msgid "UserOnboardingTour|Welcome to the project overview of the %{emphasisStart}%{projectName}%{emphasisEnd} project. This is the project that we use to work on GitLab. At first, a project seems like a simple repository, but at GitLab, a project is so much more.%{lineBreak}%{lineBreak}You can create projects for hosting your codebase, use it as an issue tracker, collaborate on code, and continuously build, test, and deploy your app with built-in GitLab CI/CD."
+msgid "UserList|created %{timeago}"
msgstr ""
msgid "UserProfile|Activity"
@@ -24721,6 +25376,9 @@ msgstr ""
msgid "UsersSelect|Unassigned"
msgstr ""
+msgid "Using %{codeStart}needs%{codeEnd} allows jobs to run before their stage is reached, as soon as their individual dependencies are met, which speeds up your pipelines."
+msgstr ""
+
msgid "Using %{code_start}::%{code_end} denotes a %{link_start}scoped label set%{link_end}"
msgstr ""
@@ -24805,9 +25463,6 @@ msgstr ""
msgid "Versions"
msgstr ""
-msgid "Very helpful"
-msgstr ""
-
msgid "View Documentation"
msgstr ""
@@ -24960,6 +25615,9 @@ msgstr ""
msgid "VisibilityLevel|Unknown"
msgstr ""
+msgid "Visit settings page"
+msgstr ""
+
msgid "VisualReviewApp|%{stepStart}Step 1%{stepEnd}. Copy the following script:"
msgstr ""
@@ -25053,6 +25711,9 @@ msgstr ""
msgid "VulnerabilityManagement|Something went wrong while trying to delete the comment. Please try again later."
msgstr ""
+msgid "VulnerabilityManagement|Something went wrong while trying to refresh the vulnerability. Please try again later."
+msgstr ""
+
msgid "VulnerabilityManagement|Something went wrong while trying to retrieve the vulnerability history. Please try again later."
msgstr ""
@@ -25095,6 +25756,9 @@ msgstr ""
msgid "Vulnerability|Class"
msgstr ""
+msgid "Vulnerability|Crash Address"
+msgstr ""
+
msgid "Vulnerability|Description"
msgstr ""
@@ -25170,9 +25834,6 @@ msgstr ""
msgid "We couldn't reach the Prometheus server. Either the server no longer exists or the configuration details need updating."
msgstr ""
-msgid "We created a short guided tour that will help you learn the basics of GitLab and how it will help you be better at your job. It should only take a couple of minutes. You will be guided by two types of helpers, best recognized by their color."
-msgstr ""
-
msgid "We detected potential spam in the %{humanized_resource_name}. Please solve the reCAPTCHA to proceed."
msgstr ""
@@ -25206,6 +25867,9 @@ msgstr ""
msgid "We've found no vulnerabilities"
msgstr ""
+msgid "Web Application Firewall"
+msgstr ""
+
msgid "Web IDE"
msgstr ""
@@ -25266,9 +25930,6 @@ msgstr ""
msgid "Welcome to GitLab.com<br>@%{name}!"
msgstr ""
-msgid "Welcome to the Guided GitLab Tour"
-msgstr ""
-
msgid "Welcome to the guided GitLab tour"
msgstr ""
@@ -25318,9 +25979,6 @@ msgstr ""
msgid "While it's rare to have no vulnerabilities, it can happen. In any event, we ask that you please double check your settings to make sure you've set up your dashboard correctly."
msgstr ""
-msgid "White helpers give contextual information."
-msgstr ""
-
msgid "Who can be an approver?"
msgstr ""
@@ -25366,12 +26024,18 @@ msgstr ""
msgid "WikiEmptyIssueMessage|Suggest wiki improvement"
msgstr ""
+msgid "WikiEmptyIssueMessage|You must be a group member in order to add wiki pages. If you have suggestions for how to improve the wiki for this group, consider opening an issue in the %{issues_link}."
+msgstr ""
+
msgid "WikiEmptyIssueMessage|You must be a project member in order to add wiki pages. If you have suggestions for how to improve the wiki for this project, consider opening an issue in the %{issues_link}."
msgstr ""
msgid "WikiEmptyIssueMessage|issue tracker"
msgstr ""
+msgid "WikiEmpty|A wiki is where you can store all the details about your group. This can include why you've created it, its principles, how to use it, and so on."
+msgstr ""
+
msgid "WikiEmpty|A wiki is where you can store all the details about your project. This can include why you've created it, its principles, how to use it, and so on."
msgstr ""
@@ -25381,12 +26045,21 @@ msgstr ""
msgid "WikiEmpty|Suggest wiki improvement"
msgstr ""
+msgid "WikiEmpty|The wiki lets you write documentation for your group"
+msgstr ""
+
msgid "WikiEmpty|The wiki lets you write documentation for your project"
msgstr ""
+msgid "WikiEmpty|This group has no wiki pages"
+msgstr ""
+
msgid "WikiEmpty|This project has no wiki pages"
msgstr ""
+msgid "WikiEmpty|You must be a group member in order to add wiki pages."
+msgstr ""
+
msgid "WikiEmpty|You must be a project member in order to add wiki pages."
msgstr ""
@@ -25663,13 +26336,13 @@ msgstr ""
msgid "You can now export your security dashboard to a CSV report."
msgstr ""
-msgid "You can now submit a merge request to get this change into the original branch."
+msgid "You can now manage alert endpoint configuration in the Alerts section on the Operations settings page. Fields on this page have been deprecated."
msgstr ""
-msgid "You can now submit a merge request to get this change into the original project."
+msgid "You can now submit a merge request to get this change into the original branch."
msgstr ""
-msgid "You can only add files when you are on a branch"
+msgid "You can now submit a merge request to get this change into the original project."
msgstr ""
msgid "You can only edit files when you are on a branch"
@@ -25891,7 +26564,7 @@ msgstr ""
msgid "You need to upload a Google Takeout archive."
msgstr ""
-msgid "You reached %{usage_in_percent} of %{namespace_name}'s capacity (%{used_storage} of %{storage_limit})"
+msgid "You reached %{usage_in_percent} of %{namespace_name}'s storage capacity (%{used_storage} of %{storage_limit})"
msgstr ""
msgid "You tried to fork %{link_to_the_project} but it failed for the following reason:"
@@ -26194,13 +26867,13 @@ msgstr ""
msgid "Your subscription expired!"
msgstr ""
-msgid "Your subscription has been downgraded"
+msgid "Your subscription has been downgraded."
msgstr ""
-msgid "Your subscription will automatically renew in %{remaining_days}"
+msgid "Your subscription will automatically renew in %{remaining_days}."
msgstr ""
-msgid "Your subscription will expire in %{remaining_days}"
+msgid "Your subscription will expire in %{remaining_days}."
msgstr ""
msgid "Zoom meeting added"
@@ -26282,9 +26955,6 @@ msgstr ""
msgid "branch name"
msgstr ""
-msgid "build pipeline reference mismatch"
-msgstr ""
-
msgid "by"
msgstr ""
@@ -26303,6 +26973,9 @@ msgstr ""
msgid "cannot block others"
msgstr ""
+msgid "cannot contain HTML/XML tags, including any word between angle brackets (<,>)."
+msgstr ""
+
msgid "cannot include leading slash or directory traversal."
msgstr ""
@@ -26333,65 +27006,6 @@ msgstr ""
msgid "ciReport|%{remainingPackagesCount} more"
msgstr ""
-msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability"
-msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities"
-msgstr[0] ""
-msgstr[1] ""
-msgstr[2] ""
-msgstr[3] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability for the source branch only"
-msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities for the source branch only"
-msgstr[0] ""
-msgstr[1] ""
-msgstr[2] ""
-msgstr[3] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerability"
-msgid_plural "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerabilities"
-msgstr[0] ""
-msgstr[1] ""
-msgstr[2] ""
-msgstr[3] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new vulnerability"
-msgid_plural "ciReport|%{reportType} %{status} detected %{newCount} new vulnerabilities"
-msgstr[0] ""
-msgstr[1] ""
-msgstr[2] ""
-msgstr[3] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities for the source branch only"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{fixedCount} fixed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} vulnerability for the source branch only"
-msgid_plural "ciReport|%{reportType} %{status} detected %{newCount} vulnerabilities for the source branch only"
-msgstr[0] ""
-msgstr[1] ""
-msgstr[2] ""
-msgstr[3] ""
-
-msgid "ciReport|%{reportType} %{status} detected no new vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected no vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected no vulnerabilities for the source branch only"
-msgstr ""
-
msgid "ciReport|%{reportType} is loading"
msgstr ""
@@ -26410,7 +27024,7 @@ msgstr ""
msgid "ciReport|All projects"
msgstr ""
-msgid "ciReport|All report types"
+msgid "ciReport|All scanner types"
msgstr ""
msgid "ciReport|All severities"
@@ -26434,6 +27048,9 @@ msgstr ""
msgid "ciReport|Container scanning detects known vulnerabilities in your docker images."
msgstr ""
+msgid "ciReport|Coverage Fuzzing"
+msgstr ""
+
msgid "ciReport|Create a merge request to implement this solution, or download and apply the patch manually."
msgstr ""
@@ -26594,6 +27211,9 @@ msgstr ""
msgid "customize"
msgstr ""
+msgid "data"
+msgstr ""
+
msgid "date must not be after 9999-12-31"
msgstr ""
@@ -26668,6 +27288,9 @@ msgstr ""
msgid "entries cannot contain HTML tags"
msgstr ""
+msgid "epic"
+msgstr ""
+
msgid "error"
msgstr ""
@@ -26689,9 +27312,6 @@ msgstr ""
msgid "expires on %{timebox_due_date}"
msgstr ""
-msgid "external_url"
-msgstr ""
-
msgid "failed"
msgstr ""
@@ -26729,9 +27349,6 @@ msgstr ""
msgid "from"
msgstr ""
-msgid "geo_node_name"
-msgstr ""
-
msgid "group"
msgstr ""
@@ -26753,9 +27370,6 @@ msgstr ""
msgid "https://your-bitbucket-server"
msgstr ""
-msgid "image"
-msgstr ""
-
msgid "image diff"
msgstr ""
@@ -26901,6 +27515,9 @@ msgstr ""
msgid "load it anyway"
msgstr ""
+msgid "loading"
+msgstr ""
+
msgid "locked by %{path_lock_user_name} %{created_at}"
msgstr ""
@@ -27031,9 +27648,6 @@ msgstr ""
msgid "mrWidget|Deployment statistics are not available currently"
msgstr ""
-msgid "mrWidget|Detect issues before deployment with a CI pipeline that continuously tests your code. We created a quick guide that will show you how to create one. Make your code more secure and more robust in just a minute."
-msgstr ""
-
msgid "mrWidget|Did not close"
msgstr ""
@@ -27196,6 +27810,9 @@ msgstr ""
msgid "mrWidget|To approve this merge request, please enter your password. This project requires all approvals to be authenticated."
msgstr ""
+msgid "mrWidget|Use %{linkStart}CI pipelines to test your code%{linkEnd}, simply add a GitLab CI configuration file to your project. It only takes a minute to make your code more secure and robust."
+msgstr ""
+
msgid "mrWidget|When this merge request is ready, remove the WIP: prefix from the title to allow it to be merged"
msgstr ""
@@ -27232,6 +27849,9 @@ msgstr ""
msgid "must be greater than start date"
msgstr ""
+msgid "must contain only valid frameworks"
+msgstr ""
+
msgid "my-awesome-group"
msgstr ""
@@ -27244,6 +27864,9 @@ msgstr ""
msgid "needs to be between 10 minutes and 1 month"
msgstr ""
+msgid "never"
+msgstr ""
+
msgid "never expires"
msgstr ""
@@ -27271,6 +27894,9 @@ msgstr ""
msgid "nounSeries|%{firstItem} and %{lastItem}"
msgstr ""
+msgid "nounSeries|%{item}"
+msgstr ""
+
msgid "nounSeries|%{item}, %{nextItem}"
msgstr ""
@@ -27417,6 +28043,9 @@ msgstr ""
msgid "revised"
msgstr ""
+msgid "satisfied"
+msgstr ""
+
msgid "score"
msgstr ""
@@ -27447,6 +28076,9 @@ msgstr ""
msgid "severity|Unknown"
msgstr ""
+msgid "should be an array of %{object_name} objects"
+msgstr ""
+
msgid "should be greater than or equal to %{access} inherited membership from group %{group_name}"
msgstr ""
@@ -27501,10 +28133,13 @@ msgstr ""
msgid "suggestPipeline|2/2: Commit your changes"
msgstr ""
-msgid "suggestPipeline|Commit the changes and your pipeline will automatically run for the first time."
+msgid "suggestPipeline|Choose %{boldStart}Code Quality%{boldEnd} to add a pipeline that tests the quality of your code."
msgstr ""
-msgid "suggestPipeline|We recommend the %{boldStart}Code Quality%{boldEnd} template, which will add a report widget to your Merge Requests. This way you’ll learn about code quality degradations much sooner. %{footerStart} Goodbye technical debt! %{footerEnd}"
+msgid "suggestPipeline|The template is ready! You can now commit it to create your first pipeline."
+msgstr ""
+
+msgid "suggestPipeline|We’re adding a GitLab CI configuration file to add a pipeline to the project. You could create it manually, but we recommend that you start with a GitLab template that works out of the box."
msgstr ""
msgid "syntax is correct"
@@ -27564,6 +28199,9 @@ msgstr ""
msgid "uses Kubernetes clusters to deploy your code!"
msgstr ""
+msgid "v%{version} published %{timeAgo}"
+msgstr ""
+
msgid "verify ownership"
msgstr ""
diff --git a/locale/cy_GB/gitlab.po b/locale/cy_GB/gitlab.po
index d82e768cebe..1d896a5954b 100644
--- a/locale/cy_GB/gitlab.po
+++ b/locale/cy_GB/gitlab.po
@@ -2,7 +2,7 @@ msgid ""
msgstr ""
"Project-Id-Version: gitlab-ee\n"
"Report-Msgid-Bugs-To: \n"
-"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Last-Translator: \n"
"Language-Team: Welsh\n"
"Language: cy_GB\n"
"MIME-Version: 1.0\n"
@@ -10,72 +10,74 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=6; plural=(n == 0) ? 0 : ((n == 1) ? 1 : ((n == 2) ? 2 : ((n == 3) ? 3 : ((n == 6) ? 4 : 5))));\n"
"X-Crowdin-Project: gitlab-ee\n"
+"X-Crowdin-Project-ID: 288872\n"
"X-Crowdin-Language: cy\n"
"X-Crowdin-File: /master/locale/gitlab.pot\n"
-"PO-Revision-Date: 2020-06-08 15:11\n"
+"X-Crowdin-File-ID: 6\n"
+"PO-Revision-Date: 2020-07-02 01:21\n"
msgid " %{start} to %{end}"
-msgstr ""
+msgstr " %{start} i %{end}"
msgid " (from %{timeoutSource})"
-msgstr ""
+msgstr " (oddi wrth %{timeoutSource})"
msgid " Collected %{time}"
-msgstr ""
+msgstr " Casglwyd %{time}"
msgid " Please sign in."
-msgstr ""
+msgstr " Mewngofnodwch."
msgid " Try to %{action} this file again."
-msgstr ""
+msgstr " Ceisiwch %{action} y ffeil hon eto."
msgid " You need to do this before %{grace_period_deadline}."
-msgstr ""
+msgstr " Mae angen i chi wneud hyn cyn %{grace_period_deadline}."
msgid " and"
-msgstr ""
+msgstr " a"
msgid " and "
-msgstr ""
+msgstr " a "
msgid " and %{sliced}"
msgstr ""
msgid " degraded on %d point"
msgid_plural " degraded on %d points"
-msgstr[0] ""
-msgstr[1] ""
-msgstr[2] ""
-msgstr[3] ""
-msgstr[4] ""
-msgstr[5] ""
+msgstr[0] " diraddiwyd ar %d pwyntiau"
+msgstr[1] " diraddiwyd ar %d pwynt"
+msgstr[2] " diraddiwyd ar %d bwynt "
+msgstr[3] " diraddiwyd ar %d pwynt "
+msgstr[4] " diraddiwyd ar %d phwynt "
+msgstr[5] " diraddiwyd ar %d pwynt"
msgid " improved on %d point"
msgid_plural " improved on %d points"
-msgstr[0] ""
-msgstr[1] ""
-msgstr[2] ""
-msgstr[3] ""
-msgstr[4] ""
-msgstr[5] ""
+msgstr[0] " wedi'i wella ar %d pwyntiau"
+msgstr[1] " wedi'i wella ar %d pwynt"
+msgstr[2] " wedi'i wella ar %d bwynt"
+msgstr[3] " wedi'i wella ar %d pwynt"
+msgstr[4] " wedi'i wella ar %d phwynt"
+msgstr[5] " wedi'i wella ar %d pwynt"
msgid " or "
-msgstr ""
+msgstr " neu "
msgid " or <!merge request id>"
-msgstr ""
+msgstr " neu <!merge request id>"
msgid " or <#issue id>"
-msgstr ""
+msgstr " neu <#issue id>"
msgid " or <&epic id>"
-msgstr ""
+msgstr " neu <#epic id>"
msgid " or references (e.g. path/to/project!merge_request_id)"
-msgstr ""
+msgstr " neu gyfeiriadau (ee path/to/project!merge_request_id)"
msgid "\"%{path}\" did not exist on \"%{ref}\""
-msgstr ""
+msgstr "Nid oedd \"%{path}\" yn bodoli ar \"%{ref}\""
msgid "%d URL scanned"
msgid_plural "%d URLs scanned"
@@ -305,6 +307,15 @@ msgstr[3] ""
msgstr[4] ""
msgstr[5] ""
+msgid "%d milestone"
+msgid_plural "%d milestones"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+msgstr[4] ""
+msgstr[5] ""
+
msgid "%d minute"
msgid_plural "%d minutes"
msgstr[0] ""
@@ -386,15 +397,6 @@ msgstr[3] ""
msgstr[4] ""
msgstr[5] ""
-msgid "%d url scanned"
-msgid_plural "%d urls scanned"
-msgstr[0] ""
-msgstr[1] ""
-msgstr[2] ""
-msgstr[3] ""
-msgstr[4] ""
-msgstr[5] ""
-
msgid "%d vulnerability"
msgid_plural "%d vulnerabilities"
msgstr[0] ""
@@ -477,49 +479,55 @@ msgid "%{count} of %{required} approvals from %{name}"
msgstr ""
msgid "%{count} of %{total}"
-msgstr ""
+msgstr "%{count} allan o %{total}"
msgid "%{count} participant"
msgid_plural "%{count} participants"
-msgstr[0] ""
-msgstr[1] ""
-msgstr[2] ""
-msgstr[3] ""
-msgstr[4] ""
-msgstr[5] ""
+msgstr[0] "%{count} cyfranogwyr"
+msgstr[1] "%{count} cyfranogwr"
+msgstr[2] "%{count} gyfranogwr"
+msgstr[3] "%{count} cyfranogwr"
+msgstr[4] "%{count} chyfranogwr"
+msgstr[5] "%{count} cyfranogwr"
msgid "%{count} pending comment"
msgid_plural "%{count} pending comments"
-msgstr[0] ""
-msgstr[1] ""
-msgstr[2] ""
-msgstr[3] ""
-msgstr[4] ""
-msgstr[5] ""
+msgstr[0] "%{count} sylwadau dan ystyriaeth"
+msgstr[1] "%{count} sylw dan ystyriaeth"
+msgstr[2] "%{count} sylw dan ystyriaeth"
+msgstr[3] "%{count} sylw dan ystyriaeth"
+msgstr[4] "%{count} sylw dan ystyriaeth"
+msgstr[5] "%{count} sylw dan ystyriaeth"
msgid "%{count} related %{pluralized_subject}: %{links}"
+msgstr "%{count} %{pluralized_subject} cysylltiedig: %{links}"
+
+msgid "%{dashboard_path} could not be found."
msgstr ""
msgid "%{days} days until tags are automatically removed"
+msgstr "%{days} diwrnod nes bod tagiau'n cael eu tynnu'n awtomatig"
+
+msgid "%{deployLinkStart}Use a template to deploy to ECS%{deployLinkEnd}, or use a docker image to %{commandsLinkStart}run AWS commands in GitLab CI/CD%{commandsLinkEnd}."
msgstr ""
msgid "%{description}- Sentry event: %{errorUrl}- First seen: %{firstSeen}- Last seen: %{lastSeen} %{countLabel}: %{count}%{userCountLabel}: %{userCount}"
-msgstr ""
+msgstr "%{description}- Digwyddiad sentry: %{errorUrl}- Gwelwyd gyntaf: %{firstSeen}- Gwelwyd ddiwethaf: %{lastSeen} %{countLabel}: %{count}%{userCountLabel}: %{userCount}"
msgid "%{duration}ms"
-msgstr ""
+msgstr "%{duration}ms"
msgid "%{edit_in_new_fork_notice} Try to cherry-pick this commit again."
-msgstr ""
+msgstr "%{edit_in_new_fork_notice} Ceisiwch ddewis a dethol y traddodyn hwn eto."
msgid "%{edit_in_new_fork_notice} Try to create a new directory again."
-msgstr ""
+msgstr "%{edit_in_new_fork_notice} Ceisiwch greu cyfeiriadur newydd eto."
msgid "%{edit_in_new_fork_notice} Try to revert this commit again."
msgstr ""
msgid "%{edit_in_new_fork_notice} Try to upload a file again."
-msgstr ""
+msgstr "%{edit_in_new_fork_notice} Ceisiwch lwytho ffeil eto."
msgid "%{extra} more downstream pipelines"
msgstr ""
@@ -545,12 +553,18 @@ msgstr ""
msgid "%{host} sign-in from new location"
msgstr ""
-msgid "%{icon}You are about to add %{usersTag} people to the discussion. Proceed with caution."
+msgid "%{icon}You are about to add %{usersTag} people to the discussion. They will all receive a notification."
+msgstr ""
+
+msgid "%{integrations_link_start}Integrations%{link_end} enable you to make third-party applications part of your GitLab workflow. If the available integrations don't meet your needs, consider using a %{webhooks_link_start}webhook%{link_end}."
msgstr ""
msgid "%{issuableType} will be removed! Are you sure?"
msgstr ""
+msgid "%{issuesCount} issues in this group"
+msgstr ""
+
msgid "%{issuesSize} issues"
msgstr ""
@@ -560,7 +574,7 @@ msgstr ""
msgid "%{labelStart}Class:%{labelEnd} %{class}"
msgstr ""
-msgid "%{labelStart}Confidence:%{labelEnd} %{confidence}"
+msgid "%{labelStart}Evidence:%{labelEnd} %{evidence}"
msgstr ""
msgid "%{labelStart}File:%{labelEnd} %{file}"
@@ -578,6 +592,9 @@ msgstr ""
msgid "%{labelStart}Report Type:%{labelEnd} %{reportType}"
msgstr ""
+msgid "%{labelStart}Scanner:%{labelEnd} %{scanner}"
+msgstr ""
+
msgid "%{labelStart}Severity:%{labelEnd} %{severity}"
msgstr ""
@@ -602,9 +619,6 @@ msgstr ""
msgid "%{link_start}Read more%{link_end} about role permissions"
msgstr ""
-msgid "%{link} can be used for binding events when something is happening within the project."
-msgstr ""
-
msgid "%{listToShow}, and %{awardsListLength} more."
msgstr ""
@@ -638,6 +652,9 @@ msgstr ""
msgid "%{name} found %{resultsString}"
msgstr ""
+msgid "%{name} is already being used for another emoji"
+msgstr ""
+
msgid "%{name} is scheduled for %{action}"
msgstr ""
@@ -689,6 +706,9 @@ msgstr ""
msgid "%{primary} (%{secondary})"
msgstr ""
+msgid "%{ref} cannot be added: %{error}"
+msgstr ""
+
msgid "%{releases} release"
msgid_plural "%{releases} releases"
msgstr[0] ""
@@ -701,13 +721,49 @@ msgstr[5] ""
msgid "%{remaining_approvals} left"
msgstr ""
-msgid "%{retryButtonStart}Try again%{retryButtonEnd} or %{newFileButtonStart}attach a new file%{newFileButtonEnd}"
+msgid "%{reportType} %{status} detected %{critical} critical and %{high} high severity vulnerabilities out of %{total}."
+msgstr ""
+
+msgid "%{reportType} %{status} detected %{critical} critical and %{high} high severity vulnerabilities."
msgstr ""
-msgid "%{screenreaderOnlyStart}Keyboard shorcuts%{screenreaderOnlyEnd} Disabled"
+msgid "%{reportType} %{status} detected %{critical} critical severity vulnerabilities out of %{total}."
msgstr ""
-msgid "%{screenreaderOnlyStart}Keyboard shorcuts%{screenreaderOnlyEnd} Enabled"
+msgid "%{reportType} %{status} detected %{critical} critical severity vulnerability."
+msgid_plural "%{reportType} %{status} detected %{critical} critical severity vulnerabilities."
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+msgstr[4] ""
+msgstr[5] ""
+
+msgid "%{reportType} %{status} detected %{high} high severity vulnerabilities out of %{total}."
+msgstr ""
+
+msgid "%{reportType} %{status} detected %{high} high severity vulnerability."
+msgid_plural "%{reportType} %{status} detected %{high} high severity vulnerabilities."
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+msgstr[4] ""
+msgstr[5] ""
+
+msgid "%{reportType} %{status} detected %{other} vulnerability."
+msgid_plural "%{reportType} %{status} detected %{other} vulnerabilities."
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+msgstr[4] ""
+msgstr[5] ""
+
+msgid "%{reportType} %{status} detected no new vulnerabilities."
+msgstr ""
+
+msgid "%{retryButtonStart}Try again%{retryButtonEnd} or %{newFileButtonStart}attach a new file%{newFileButtonEnd}"
msgstr ""
msgid "%{service_title} %{message}."
@@ -824,6 +880,12 @@ msgstr ""
msgid "%{token}..."
msgstr ""
+msgid "%{totalCpu} (%{freeSpacePercentage}%{percentSymbol} free)"
+msgstr ""
+
+msgid "%{totalMemory} (%{freeSpacePercentage}%{percentSymbol} free)"
+msgstr ""
+
msgid "%{totalWeight} total weight"
msgstr ""
@@ -854,6 +916,12 @@ msgstr ""
msgid "%{verb} %{time_spent_value} spent time."
msgstr ""
+msgid "%{webhooks_link_start}%{webhook_type}%{link_end} enable you to send notifications to web applications in response to events in a group or project."
+msgstr ""
+
+msgid "%{webhooks_link_start}%{webhook_type}%{link_end} enable you to send notifications to web applications in response to events in a group or project. We recommend using an %{integrations_link_start}integration%{link_end} in preference to a webhook."
+msgstr ""
+
msgid "'%{level}' is not a valid visibility level"
msgstr ""
@@ -884,9 +952,6 @@ msgstr ""
msgid "(No changes)"
msgstr ""
-msgid "(Show all)"
-msgstr ""
-
msgid "(check progress)"
msgstr ""
@@ -965,6 +1030,9 @@ msgstr ""
msgid "0 for unlimited"
msgstr ""
+msgid "0 for unlimited, only effective with remote storage enabled."
+msgstr ""
+
msgid "1 %{type} addition"
msgid_plural "%{count} %{type} additions"
msgstr[0] ""
@@ -1172,6 +1240,12 @@ msgstr ""
msgid "<code>\"johnsmith@example.com\": \"johnsmith@example.com\"</code> will add \"By <a href=\"#\">johnsmith@example.com</a>\" to all issues and comments originally created by johnsmith@example.com. By default, the email address or username is masked to ensure the user's privacy. Use this option if you want to show the full email address."
msgstr ""
+msgid "<code>Masked</code> to prevent the values from being displayed in job logs (must match certain regexp requirements)."
+msgstr ""
+
+msgid "<code>Protected</code> to expose them to protected branches or tags only."
+msgstr ""
+
msgid "<namespace / project>"
msgstr ""
@@ -1199,9 +1273,6 @@ msgstr ""
msgid "A .NET Core console application template, customizable for any .NET Core project"
msgstr ""
-msgid "A DAG must have two dependent jobs to be visualized on this tab."
-msgstr ""
-
msgid "A GitBook site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
msgstr ""
@@ -1232,6 +1303,12 @@ msgstr ""
msgid "A deleted user"
msgstr ""
+msgid "A file has been changed."
+msgstr ""
+
+msgid "A file was not found."
+msgstr ""
+
msgid "A file with '%{file_name}' already exists in %{branch} branch"
msgstr ""
@@ -1298,7 +1375,7 @@ msgstr ""
msgid "A subscription will trigger a new pipeline on the default branch of this project when a pipeline successfully completes for a new tag on the %{default_branch_docs} of the subscribed project."
msgstr ""
-msgid "A terraform report was generated in your pipelines."
+msgid "A suggestion is not applicable."
msgstr ""
msgid "A user with write access to the source branch selected this option"
@@ -1469,6 +1546,9 @@ msgstr ""
msgid "Action to take when receiving an alert."
msgstr ""
+msgid "Actions"
+msgstr ""
+
msgid "Activate"
msgstr ""
@@ -1529,6 +1609,9 @@ msgstr ""
msgid "Add LICENSE"
msgstr ""
+msgid "Add New Node"
+msgstr ""
+
msgid "Add README"
msgstr ""
@@ -1604,6 +1687,9 @@ msgstr ""
msgid "Add approval rule"
msgstr ""
+msgid "Add approvers"
+msgstr ""
+
msgid "Add bold text"
msgstr ""
@@ -1661,6 +1747,9 @@ msgstr ""
msgid "Add strikethrough text"
msgstr ""
+msgid "Add suggestion to batch"
+msgstr ""
+
msgid "Add system hook"
msgstr ""
@@ -1760,6 +1849,9 @@ msgstr ""
msgid "Admin Note"
msgstr ""
+msgid "Admin Notifications"
+msgstr ""
+
msgid "Admin Overview"
msgstr ""
@@ -1871,6 +1963,9 @@ msgstr ""
msgid "AdminSettings|Integrations configured here will automatically apply to all projects on this instance."
msgstr ""
+msgid "AdminSettings|Moved to integrations"
+msgstr ""
+
msgid "AdminSettings|No required pipeline"
msgstr ""
@@ -2105,12 +2200,18 @@ msgstr[3] ""
msgstr[4] ""
msgstr[5] ""
+msgid "AlertManagement| assign yourself"
+msgstr ""
+
msgid "AlertManagement|Acknowledged"
msgstr ""
msgid "AlertManagement|Alert"
msgstr ""
+msgid "AlertManagement|Alert assignee(s): %{assignees}"
+msgstr ""
+
msgid "AlertManagement|Alert detail"
msgstr ""
@@ -2126,9 +2227,15 @@ msgstr ""
msgid "AlertManagement|All alerts"
msgstr ""
+msgid "AlertManagement|Assign To"
+msgstr ""
+
msgid "AlertManagement|Assign status"
msgstr ""
+msgid "AlertManagement|Assignee"
+msgstr ""
+
msgid "AlertManagement|Assignees"
msgstr ""
@@ -2147,9 +2254,6 @@ msgstr ""
msgid "AlertManagement|Edit"
msgstr ""
-msgid "AlertManagement|End time"
-msgstr ""
-
msgid "AlertManagement|Events"
msgstr ""
@@ -2168,10 +2272,13 @@ msgstr ""
msgid "AlertManagement|More information"
msgstr ""
+msgid "AlertManagement|No Matching Results"
+msgstr ""
+
msgid "AlertManagement|No alert data to display."
msgstr ""
-msgid "AlertManagement|No alerts available to display. If you think you're seeing this message in error, refresh the page."
+msgid "AlertManagement|No alerts available to display. See %{linkStart}enabling alert management%{linkEnd} for more information on adding alerts to the list."
msgstr ""
msgid "AlertManagement|No alerts to display."
@@ -2180,6 +2287,9 @@ msgstr ""
msgid "AlertManagement|None"
msgstr ""
+msgid "AlertManagement|None -"
+msgstr ""
+
msgid "AlertManagement|Open"
msgstr ""
@@ -2216,9 +2326,18 @@ msgstr ""
msgid "AlertManagement|There was an error displaying the alerts. Confirm your endpoint's configuration details to ensure alerts appear."
msgstr ""
+msgid "AlertManagement|There was an error while updating the assignee(s) list. Please try again."
+msgstr ""
+
+msgid "AlertManagement|There was an error while updating the assignee(s) of the alert. Please try again."
+msgstr ""
+
msgid "AlertManagement|There was an error while updating the status of the alert. Please try again."
msgstr ""
+msgid "AlertManagement|This assignee cannot be assigned to this alert."
+msgstr ""
+
msgid "AlertManagement|Tool"
msgstr ""
@@ -2366,6 +2485,9 @@ msgstr ""
msgid "Allow users to request access (if visibility is public or internal)"
msgstr ""
+msgid "Allowed Geo IP"
+msgstr ""
+
msgid "Allowed email domain restriction only permitted for top-level groups"
msgstr ""
@@ -2399,6 +2521,9 @@ msgstr ""
msgid "Amazon Web Services"
msgstr ""
+msgid "Amazon Web Services Logo"
+msgstr ""
+
msgid "Amazon authentication is not %{link_start}correctly configured%{link_end}. Ask your GitLab administrator if you want to use this service."
msgstr ""
@@ -2456,10 +2581,16 @@ msgstr ""
msgid "An error occurred when updating the issue weight"
msgstr ""
+msgid "An error occurred while acknowledging the notification. Refresh the page and try again."
+msgstr ""
+
+msgid "An error occurred while adding approvers"
+msgstr ""
+
msgid "An error occurred while adding formatted title for epic"
msgstr ""
-msgid "An error occurred while checking group path"
+msgid "An error occurred while checking group path. Please refresh and try again."
msgstr ""
msgid "An error occurred while committing your changes."
@@ -2603,7 +2734,7 @@ msgstr ""
msgid "An error occurred while loading milestones"
msgstr ""
-msgid "An error occurred while loading terraform report"
+msgid "An error occurred while loading project creation UI"
msgstr ""
msgid "An error occurred while loading the data. Please try again."
@@ -2657,6 +2788,9 @@ msgstr ""
msgid "An error occurred while rendering preview broadcast message"
msgstr ""
+msgid "An error occurred while rendering the editor"
+msgstr ""
+
msgid "An error occurred while reordering issues."
msgstr ""
@@ -2753,7 +2887,7 @@ msgstr ""
msgid "Analyze your dependencies for known vulnerabilities."
msgstr ""
-msgid "Analyze your source code and git history for secrets"
+msgid "Analyze your source code and git history for secrets."
msgstr ""
msgid "Analyze your source code for known vulnerabilities."
@@ -2867,12 +3001,18 @@ msgstr ""
msgid "Apply suggestion"
msgstr ""
+msgid "Apply suggestions"
+msgstr ""
+
msgid "Apply template"
msgstr ""
msgid "Apply this approval rule to any branch or a specific protected branch."
msgstr ""
+msgid "Applying"
+msgstr ""
+
msgid "Applying a template will replace the existing issue description. Any changes you have made will be lost."
msgstr ""
@@ -2885,12 +3025,18 @@ msgstr ""
msgid "Applying multiple commands"
msgstr ""
-msgid "Applying suggestion"
+msgid "Applying suggestion..."
+msgstr ""
+
+msgid "Applying suggestions..."
msgstr ""
msgid "Approval rules"
msgstr ""
+msgid "Approval rules reset to project defaults"
+msgstr ""
+
msgid "ApprovalRuleRemove|%d member"
msgid_plural "ApprovalRuleRemove|%d members"
msgstr[0] ""
@@ -2960,6 +3106,9 @@ msgstr ""
msgid "Approved"
msgstr ""
+msgid "Approved MRs"
+msgstr ""
+
msgid "Approved by: "
msgstr ""
@@ -3173,6 +3322,9 @@ msgstr ""
msgid "Assign"
msgstr ""
+msgid "Assign Iteration"
+msgstr ""
+
msgid "Assign custom color like #FF0000"
msgstr ""
@@ -3191,6 +3343,9 @@ msgstr ""
msgid "Assign to"
msgstr ""
+msgid "Assign to commenting user"
+msgstr ""
+
msgid "Assign yourself to these issues"
msgstr ""
@@ -3221,6 +3376,9 @@ msgstr[3] ""
msgstr[4] ""
msgstr[5] ""
+msgid "Assignee has no permissions"
+msgstr ""
+
msgid "Assignee lists not available with your current license"
msgstr ""
@@ -3230,6 +3388,9 @@ msgstr ""
msgid "Assignee(s)"
msgstr ""
+msgid "Assignees"
+msgstr ""
+
msgid "Assigns %{assignee_users_sentence}."
msgstr ""
@@ -3269,6 +3430,9 @@ msgstr ""
msgid "Audit Events is a way to keep track of important events that happened in GitLab."
msgstr ""
+msgid "Audit Log"
+msgstr ""
+
msgid "AuditEvents|(removed)"
msgstr ""
@@ -3305,6 +3469,9 @@ msgstr ""
msgid "AuditLogs|IP Address"
msgstr ""
+msgid "AuditLogs|Member Events"
+msgstr ""
+
msgid "AuditLogs|No matching %{type} found."
msgstr ""
@@ -3587,9 +3754,6 @@ msgstr ""
msgid "Badges|e.g. %{exampleUrl}"
msgstr ""
-msgid "Badge|New"
-msgstr ""
-
msgid "Balsamiq file could not be loaded."
msgstr ""
@@ -3638,6 +3802,12 @@ msgstr ""
msgid "Below you will find all the groups that are public."
msgstr ""
+msgid "Beta"
+msgstr ""
+
+msgid "Bi-weekly code coverage"
+msgstr ""
+
msgid "Billing"
msgstr ""
@@ -3698,9 +3868,15 @@ msgstr ""
msgid "Bitbucket Server Import"
msgstr ""
+msgid "Bitbucket Server import"
+msgstr ""
+
msgid "Bitbucket import"
msgstr ""
+msgid "Blame"
+msgstr ""
+
msgid "Blocked"
msgstr ""
@@ -3713,9 +3889,6 @@ msgstr ""
msgid "Blog"
msgstr ""
-msgid "Blue helpers indicate an action to be taken."
-msgstr ""
-
msgid "Board name"
msgstr ""
@@ -3914,6 +4087,9 @@ msgstr ""
msgid "Broadcast Message was successfully updated."
msgstr ""
+msgid "Broadcast Messages"
+msgstr ""
+
msgid "Browse Directory"
msgstr ""
@@ -3968,6 +4144,9 @@ msgstr ""
msgid "By %{user_name}"
msgstr ""
+msgid "By URL"
+msgstr ""
+
msgid "By default GitLab sends emails in HTML and plain text formats so mail clients can choose what format to use. Disable this option if you only want to send emails in plain text format."
msgstr ""
@@ -4073,6 +4252,9 @@ msgstr ""
msgid "Can override approvers and approvals required per merge request"
msgstr ""
+msgid "Can't apply as this line has changed or the suggestion already matches its content."
+msgstr ""
+
msgid "Can't create snippet: %{err}"
msgstr ""
@@ -4259,9 +4441,6 @@ msgstr ""
msgid "Changes are still tracked. Useful for cluster/index migrations."
msgstr ""
-msgid "Changes are unknown"
-msgstr ""
-
msgid "Changes suppressed. Click to show."
msgstr ""
@@ -4568,15 +4747,9 @@ msgstr ""
msgid "Choose what content you want to see on a group’s overview page"
msgstr ""
-msgid "Choose which groups you wish to synchronize to this secondary node"
-msgstr ""
-
msgid "Choose which repositories you want to connect and run CI/CD pipelines."
msgstr ""
-msgid "Choose which shards you wish to synchronize to this secondary node"
-msgstr ""
-
msgid "Choose your framework"
msgstr ""
@@ -4874,9 +5047,15 @@ msgstr ""
msgid "Cluster does not exist"
msgstr ""
+msgid "Cluster is required for Stages::ClusterEndpointInserter"
+msgstr ""
+
msgid "Cluster level"
msgstr ""
+msgid "Cluster type must be specificed for Stages::ClusterEndpointInserter"
+msgstr ""
+
msgid "ClusterIntegration| %{custom_domain_start}More information%{custom_domain_end}."
msgstr ""
@@ -4988,7 +5167,7 @@ msgstr ""
msgid "ClusterIntegration|Cert-Manager"
msgstr ""
-msgid "ClusterIntegration|Cert-Manager is a native Kubernetes certificate management controller that helps with issuing certificates. Installing Cert-Manager on your cluster will issue a certificate by %{letsEncrypt} and ensure that certificates are valid and up-to-date."
+msgid "ClusterIntegration|Cert-Manager is a native Kubernetes certificate management controller that helps with issuing certificates. Installing Cert-Manager on your cluster will issue a certificate by %{linkStart}Let's Encrypt%{linkEnd} and ensure that certificates are valid and up-to-date."
msgstr ""
msgid "ClusterIntegration|Certificate Authority bundle (PEM format)"
@@ -5015,9 +5194,6 @@ msgstr ""
msgid "ClusterIntegration|Clear the local cache of namespace and service accounts. This is necessary if your integration has become out of sync. The cache is repopulated during the next CI job that requires namespace and service accounts."
msgstr ""
-msgid "ClusterIntegration|Cloud Run"
-msgstr ""
-
msgid "ClusterIntegration|Cluster being created"
msgstr ""
@@ -5102,7 +5278,7 @@ msgstr ""
msgid "ClusterIntegration|Crossplane"
msgstr ""
-msgid "ClusterIntegration|Crossplane enables declarative provisioning of managed services from your cloud of choice using %{codeStart}kubectl%{codeEnd} or %{gitlabIntegrationLink}. Crossplane runs inside your Kubernetes cluster and supports secure connectivity and secrets management between app containers and the cloud services they depend on."
+msgid "ClusterIntegration|Crossplane enables declarative provisioning of managed services from your cloud of choice using %{codeStart}kubectl%{codeEnd} or %{linkStart}GitLab Integration%{linkEnd}. Crossplane runs inside your Kubernetes cluster and supports secure connectivity and secrets management between app containers and the cloud services they depend on."
msgstr ""
msgid "ClusterIntegration|Deletes all GitLab resources attached to this cluster during removal"
@@ -5186,9 +5362,6 @@ msgstr ""
msgid "ClusterIntegration|GitLab-managed cluster"
msgstr ""
-msgid "ClusterIntegration|Gitlab Integration"
-msgstr ""
-
msgid "ClusterIntegration|Global default"
msgstr ""
@@ -5234,7 +5407,7 @@ msgstr ""
msgid "ClusterIntegration|Ingress gives you a way to route requests to services based on the request host or path, centralizing a number of services into a single entrypoint."
msgstr ""
-msgid "ClusterIntegration|Installing Ingress may incur additional costs. Learn more about %{pricingLink}."
+msgid "ClusterIntegration|Installing Ingress may incur additional costs. Learn more about %{linkStart}pricing%{linkEnd}."
msgstr ""
msgid "ClusterIntegration|Instance cluster"
@@ -5249,7 +5422,7 @@ msgstr ""
msgid "ClusterIntegration|Issuer Email"
msgstr ""
-msgid "ClusterIntegration|Issuers represent a certificate authority. You must provide an email address for your Issuer. "
+msgid "ClusterIntegration|Issuers represent a certificate authority. You must provide an email address for your Issuer."
msgstr ""
msgid "ClusterIntegration|Jupyter Hostname"
@@ -5321,9 +5494,6 @@ msgstr ""
msgid "ClusterIntegration|Learn more about instance Kubernetes clusters"
msgstr ""
-msgid "ClusterIntegration|Let's Encrypt"
-msgstr ""
-
msgid "ClusterIntegration|Loading IAM Roles"
msgstr ""
@@ -5435,7 +5605,7 @@ msgstr ""
msgid "ClusterIntegration|Prometheus"
msgstr ""
-msgid "ClusterIntegration|Prometheus is an open-source monitoring system with %{gitlabIntegrationLink} to monitor deployed applications."
+msgid "ClusterIntegration|Prometheus is an open-source monitoring system with %{linkStart}GitLab Integration%{linkEnd} to monitor deployed applications."
msgstr ""
msgid "ClusterIntegration|Provider details"
@@ -5591,10 +5761,10 @@ msgstr ""
msgid "ClusterIntegration|Select zone to choose machine type"
msgstr ""
-msgid "ClusterIntegration|Send Cilium Logs"
+msgid "ClusterIntegration|Send Container Network Policies Logs"
msgstr ""
-msgid "ClusterIntegration|Send ModSecurity Logs"
+msgid "ClusterIntegration|Send Web Application Firewall Logs"
msgstr ""
msgid "ClusterIntegration|Service Token"
@@ -5762,15 +5932,12 @@ msgstr ""
msgid "ClusterIntegration|documentation"
msgstr ""
-msgid "ClusterIntegration|installed via %{installed_via}"
+msgid "ClusterIntegration|installed via %{linkStart}Cloud Run%{linkEnd}"
msgstr ""
msgid "ClusterIntegration|meets the requirements"
msgstr ""
-msgid "ClusterIntegration|pricing"
-msgstr ""
-
msgid "ClusterIntegration|sign up"
msgstr ""
@@ -5807,6 +5974,15 @@ msgstr ""
msgid "Code"
msgstr ""
+msgid "Code Coverage: %{coveragePercentage}%{percentSymbol}"
+msgstr ""
+
+msgid "Code Coverage| Empty code coverage data"
+msgstr ""
+
+msgid "Code Coverage|Couldn't fetch the code coverage data"
+msgstr ""
+
msgid "Code Owners"
msgstr ""
@@ -5861,7 +6037,7 @@ msgstr ""
msgid "Collapse approvers"
msgstr ""
-msgid "Collapse child epics"
+msgid "Collapse milestones"
msgstr ""
msgid "Collapse replies"
@@ -5879,6 +6055,9 @@ msgstr ""
msgid "Coming soon"
msgstr ""
+msgid "Comma-separated, e.g. '1.1.1.1, 2.2.2.0/24'"
+msgstr ""
+
msgid "Command"
msgstr ""
@@ -6053,6 +6232,12 @@ msgstr ""
msgid "Compliance framework (optional)"
msgstr ""
+msgid "Compliance frameworks"
+msgstr ""
+
+msgid "ComplianceDashboard|created by:"
+msgstr ""
+
msgid "ComplianceFramework|GDPR"
msgstr ""
@@ -6191,6 +6376,9 @@ msgstr ""
msgid "Connection timed out"
msgstr ""
+msgid "Connection timeout"
+msgstr ""
+
msgid "Contact sales to upgrade"
msgstr ""
@@ -6254,9 +6442,6 @@ msgstr ""
msgid "ContainerRegistry|CLI Commands"
msgstr ""
-msgid "ContainerRegistry|Compressed Size"
-msgstr ""
-
msgid "ContainerRegistry|Container Registry"
msgstr ""
@@ -6272,6 +6457,9 @@ msgstr ""
msgid "ContainerRegistry|Copy push command"
msgstr ""
+msgid "ContainerRegistry|Delete selected"
+msgstr ""
+
msgid "ContainerRegistry|Docker connection error"
msgstr ""
@@ -6305,16 +6493,16 @@ msgstr ""
msgid "ContainerRegistry|If you are not already logged in, you need to authenticate to the Container Registry by using your GitLab username and password. If you have %{twofaDocLinkStart}Two-Factor Authentication%{twofaDocLinkEnd} enabled, use a %{personalAccessTokensDocLinkStart}Personal Access Token%{personalAccessTokensDocLinkEnd} instead of a password."
msgstr ""
-msgid "ContainerRegistry|Image ID"
+msgid "ContainerRegistry|Image ID: %{imageId}"
msgstr ""
msgid "ContainerRegistry|Image Repositories"
msgstr ""
-msgid "ContainerRegistry|Keep and protect the images that matter most."
+msgid "ContainerRegistry|Image tags"
msgstr ""
-msgid "ContainerRegistry|Last Updated"
+msgid "ContainerRegistry|Keep and protect the images that matter most."
msgstr ""
msgid "ContainerRegistry|Login"
@@ -6329,6 +6517,9 @@ msgstr ""
msgid "ContainerRegistry|Please contact your administrator."
msgstr ""
+msgid "ContainerRegistry|Published %{timeInfo}"
+msgstr ""
+
msgid "ContainerRegistry|Push an image"
msgstr ""
@@ -6344,9 +6535,6 @@ msgstr ""
msgid "ContainerRegistry|Remove repository"
msgstr ""
-msgid "ContainerRegistry|Remove selected tags"
-msgstr ""
-
msgid "ContainerRegistry|Remove tag"
msgid_plural "ContainerRegistry|Remove tags"
msgstr[0] ""
@@ -6380,9 +6568,6 @@ msgstr ""
msgid "ContainerRegistry|Sorry, your filter produced no results."
msgstr ""
-msgid "ContainerRegistry|Tag"
-msgstr ""
-
msgid "ContainerRegistry|Tag expiration policy"
msgstr ""
@@ -6527,21 +6712,6 @@ msgstr ""
msgid "Control the display of third party offers."
msgstr ""
-msgid "Control the maximum concurrency of LFS/attachment backfill for this secondary node"
-msgstr ""
-
-msgid "Control the maximum concurrency of container repository operations for this Geo node"
-msgstr ""
-
-msgid "Control the maximum concurrency of repository backfill for this secondary node"
-msgstr ""
-
-msgid "Control the maximum concurrency of verification operations for this Geo node"
-msgstr ""
-
-msgid "Control the minimum interval in days that a repository should be reverified for this primary node"
-msgstr ""
-
msgid "Cookie domain"
msgstr ""
@@ -6683,6 +6853,9 @@ msgstr ""
msgid "Could not find design."
msgstr ""
+msgid "Could not find iteration"
+msgstr ""
+
msgid "Could not remove the trigger."
msgstr ""
@@ -6719,6 +6892,12 @@ msgstr ""
msgid "Coverage"
msgstr ""
+msgid "Coverage Fuzzing"
+msgstr ""
+
+msgid "Crash State"
+msgstr ""
+
msgid "Create"
msgstr ""
@@ -6767,6 +6946,9 @@ msgstr ""
msgid "Create a personal access token on your account to pull or push via %{protocol}."
msgstr ""
+msgid "Create a project pre-populated with the necessary files to get you started quickly."
+msgstr ""
+
msgid "Create an account using:"
msgstr ""
@@ -6935,6 +7117,9 @@ msgstr ""
msgid "Creates branch '%{branch_name}' and a merge request to resolve this issue."
msgstr ""
+msgid "Creating"
+msgstr ""
+
msgid "Creating epic"
msgstr ""
@@ -6986,7 +7171,10 @@ msgstr ""
msgid "Current vulnerabilities count"
msgstr ""
-msgid "CurrentUser|Buy CI minutes"
+msgid "CurrentUser|Buy Pipeline minutes"
+msgstr ""
+
+msgid "CurrentUser|One of your groups is running out"
msgstr ""
msgid "CurrentUser|Profile"
@@ -7001,6 +7189,9 @@ msgstr ""
msgid "CurrentUser|Upgrade"
msgstr ""
+msgid "Custom Attributes"
+msgstr ""
+
msgid "Custom CI configuration path"
msgstr ""
@@ -7268,12 +7459,18 @@ msgstr ""
msgid "DAG"
msgstr ""
+msgid "DAG visualization requires at least 3 dependent jobs."
+msgstr ""
+
msgid "DNS"
msgstr ""
msgid "Dashboard"
msgstr ""
+msgid "Dashboard uid not found"
+msgstr ""
+
msgid "DashboardProjects|All"
msgstr ""
@@ -7298,6 +7495,9 @@ msgstr ""
msgid "Data is still calculating..."
msgstr ""
+msgid "Datasource name not found"
+msgstr ""
+
msgid "Date"
msgstr ""
@@ -7478,6 +7678,9 @@ msgstr ""
msgid "Delete this attachment"
msgstr ""
+msgid "Delete user list"
+msgstr ""
+
msgid "Delete variable"
msgstr ""
@@ -7814,6 +8017,9 @@ msgstr ""
msgid "Deploying to"
msgstr ""
+msgid "Deploying to AWS is easy with GitLab"
+msgstr ""
+
msgid "Deployment Frequency"
msgstr ""
@@ -8180,6 +8386,9 @@ msgstr ""
msgid "Dismissed on pipeline %{pipelineLink} at %{projectLink}"
msgstr ""
+msgid "Display alerts from all your monitoring tools directly within GitLab."
+msgstr ""
+
msgid "Display name"
msgstr ""
@@ -8237,9 +8446,6 @@ msgstr ""
msgid "Don't show again"
msgstr ""
-msgid "Don't worry, you can access this tour by clicking on the help icon in the top right corner and choose <strong>Learn GitLab</strong>."
-msgstr ""
-
msgid "Done"
msgstr ""
@@ -8366,6 +8572,9 @@ msgstr ""
msgid "Edit Release"
msgstr ""
+msgid "Edit Slack integration"
+msgstr ""
+
msgid "Edit Snippet"
msgstr ""
@@ -8405,6 +8614,9 @@ msgstr ""
msgid "Edit issues"
msgstr ""
+msgid "Edit iteration"
+msgstr ""
+
msgid "Edit public deploy key"
msgstr ""
@@ -8453,6 +8665,9 @@ msgstr ""
msgid "Email"
msgstr ""
+msgid "Email Notification"
+msgstr ""
+
msgid "Email address"
msgstr ""
@@ -8465,6 +8680,9 @@ msgstr ""
msgid "Email not verified. Please verify your email in Salesforce."
msgstr ""
+msgid "Email notification for unknown sign-ins"
+msgstr ""
+
msgid "Email patch"
msgstr ""
@@ -8696,6 +8914,9 @@ msgstr ""
msgid "Enforce DNS rebinding attack protection"
msgstr ""
+msgid "Enforce personal access token expiration"
+msgstr ""
+
msgid "Ensure connectivity is available from the GitLab server to the Prometheus server"
msgstr ""
@@ -8765,6 +8986,9 @@ msgstr ""
msgid "Enter the name of your application, and we'll return a unique %{type}."
msgstr ""
+msgid "Enter weights for storages for new repositories."
+msgstr ""
+
msgid "Enter your password to approve"
msgstr ""
@@ -8774,15 +8998,27 @@ msgstr ""
msgid "Environment does not have deployments"
msgstr ""
+msgid "Environment is required for Stages::MetricEndpointInserter"
+msgstr ""
+
+msgid "Environment is required for Stages::VariableEndpointInserter"
+msgstr ""
+
msgid "Environment scope"
msgstr ""
+msgid "Environment variables are applied to all project environments in this instance via the Runner. You can use environment variables for passwords, secret keys, etc. Make variables available to the running application by prepending the variable key with <code>K8S_SECRET_</code>. You can set variables to be:"
+msgstr ""
+
msgid "Environment variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. Additionally, they can be masked so they are hidden in job logs, though they must match certain regexp requirements to do so. You can use environment variables for passwords, secret keys, or whatever you want."
msgstr ""
msgid "Environment variables are configured by your administrator to be %{link_start}protected%{link_end} by default"
msgstr ""
+msgid "Environment variables on this GitLab instance are configured to be %{link_start}protected%{link_end} by default"
+msgstr ""
+
msgid "Environment:"
msgstr ""
@@ -9314,6 +9550,9 @@ msgstr ""
msgid "EventFilterBy|Filter by comments"
msgstr ""
+msgid "EventFilterBy|Filter by designs"
+msgstr ""
+
msgid "EventFilterBy|Filter by epic events"
msgstr ""
@@ -9446,15 +9685,15 @@ msgstr ""
msgid "Expand approvers"
msgstr ""
-msgid "Expand child epics"
-msgstr ""
-
msgid "Expand down"
msgstr ""
msgid "Expand dropdown"
msgstr ""
+msgid "Expand milestones"
+msgstr ""
+
msgid "Expand sidebar"
msgstr ""
@@ -9470,6 +9709,9 @@ msgstr ""
msgid "Expiration date"
msgstr ""
+msgid "Expiration not enforced"
+msgstr ""
+
msgid "Expiration policy for the Container Registry is a perfect solution for keeping the Registry space down while still enjoying the full power of GitLab CI/CD."
msgstr ""
@@ -9590,6 +9832,9 @@ msgstr ""
msgid "Failed Jobs"
msgstr ""
+msgid "Failed on"
+msgstr ""
+
msgid "Failed to add a Zoom meeting"
msgstr ""
@@ -9650,13 +9895,19 @@ msgstr ""
msgid "Failed to install."
msgstr ""
+msgid "Failed to load assignees. Please try again."
+msgstr ""
+
+msgid "Failed to load authors. Please try again."
+msgstr ""
+
msgid "Failed to load emoji list."
msgstr ""
msgid "Failed to load error details from Sentry."
msgstr ""
-msgid "Failed to load errors from Sentry. Error message: %{errorMessage}"
+msgid "Failed to load errors from Sentry."
msgstr ""
msgid "Failed to load group activity metrics. Please try again."
@@ -9737,6 +9988,9 @@ msgstr ""
msgid "Failed to set due date because the date format is invalid."
msgstr ""
+msgid "Failed to set iteration on this issue. Please try again."
+msgstr ""
+
msgid "Failed to signing using smartcard authentication"
msgstr ""
@@ -9959,13 +10213,10 @@ msgstr ""
msgid "FeatureFlags|Target environments"
msgstr ""
-msgid "FeatureFlags|There are no active feature flags"
-msgstr ""
-
-msgid "FeatureFlags|There are no inactive feature flags"
+msgid "FeatureFlags|There was an error fetching the feature flags."
msgstr ""
-msgid "FeatureFlags|There was an error fetching the feature flags."
+msgid "FeatureFlags|There was an error retrieving user lists"
msgstr ""
msgid "FeatureFlags|Try again in a few moments or contact your support team."
@@ -9977,9 +10228,18 @@ msgstr ""
msgid "FeatureFlag|Delete strategy"
msgstr ""
+msgid "FeatureFlag|List"
+msgstr ""
+
msgid "FeatureFlag|Percentage"
msgstr ""
+msgid "FeatureFlag|Select a user list"
+msgstr ""
+
+msgid "FeatureFlag|There are no configured user lists"
+msgstr ""
+
msgid "FeatureFlag|Type"
msgstr ""
@@ -10136,6 +10396,12 @@ msgstr ""
msgid "Filter..."
msgstr ""
+msgid "Find File"
+msgstr ""
+
+msgid "Find bugs in your code with coverage-guided fuzzing"
+msgstr ""
+
msgid "Find by path"
msgstr ""
@@ -10400,6 +10666,9 @@ msgstr ""
msgid "Geo allows you to replicate your GitLab instance to other geographical locations."
msgstr ""
+msgid "Geo nodes are paused using a command run on the node"
+msgstr ""
+
msgid "GeoNodeStatusEvent|%{timeAgoStr} (%{pendingEvents} events)"
msgstr ""
@@ -10484,6 +10753,9 @@ msgstr ""
msgid "GeoNodes|Not checksummed"
msgstr ""
+msgid "GeoNodes|Package files"
+msgstr ""
+
msgid "GeoNodes|Pausing replication stops the sync process. Are you sure?"
msgstr ""
@@ -10502,6 +10774,9 @@ msgstr ""
msgid "GeoNodes|Replication slots"
msgstr ""
+msgid "GeoNodes|Replication status"
+msgstr ""
+
msgid "GeoNodes|Repositories"
msgstr ""
@@ -10712,6 +10987,9 @@ msgstr ""
msgid "Geo|Synchronization failed - %{error}"
msgstr ""
+msgid "Geo|Synchronization of %{itemTitle} is disabled."
+msgstr ""
+
msgid "Geo|The database is currently %{db_lag} behind the primary node."
msgstr ""
@@ -11024,6 +11302,9 @@ msgstr ""
msgid "Go to %{link_to_google_takeout}."
msgstr ""
+msgid "Go to <strong>Issues</strong> > <strong>Boards</strong> to access your personalized learning issue board."
+msgstr ""
+
msgid "Go to Pipelines"
msgstr ""
@@ -11123,9 +11404,6 @@ msgstr ""
msgid "Go to your snippets"
msgstr ""
-msgid "Golden Tanuki"
-msgstr ""
-
msgid "Google Cloud Platform"
msgstr ""
@@ -11147,6 +11425,9 @@ msgstr ""
msgid "Grafana URL"
msgstr ""
+msgid "Grafana response contains invalid json"
+msgstr ""
+
msgid "GrafanaIntegration|API Token"
msgstr ""
@@ -11195,9 +11476,6 @@ msgstr ""
msgid "Group %{group_name} was successfully created."
msgstr ""
-msgid "Group '%{group_name}' is being imported."
-msgstr ""
-
msgid "Group Audit Events"
msgstr ""
@@ -11234,9 +11512,6 @@ msgstr ""
msgid "Group by:"
msgstr ""
-msgid "Group could not be imported: %{errors}"
-msgstr ""
-
msgid "Group description"
msgstr ""
@@ -11264,9 +11539,15 @@ msgstr ""
msgid "Group has not been marked for deletion"
msgstr ""
+msgid "Group import could not be scheduled"
+msgstr ""
+
msgid "Group info:"
msgstr ""
+msgid "Group is required when cluster_type is :group"
+msgstr ""
+
msgid "Group maintainers can register group runners in the %{link}"
msgstr ""
@@ -11330,6 +11611,24 @@ msgstr ""
msgid "GroupActivyMetrics|Recent activity (last 90 days)"
msgstr ""
+msgid "GroupImport|Failed to import group."
+msgstr ""
+
+msgid "GroupImport|Group '%{group_name}' is being imported."
+msgstr ""
+
+msgid "GroupImport|Group could not be imported: %{errors}"
+msgstr ""
+
+msgid "GroupImport|Please wait while we import the group for you. Refresh at will."
+msgstr ""
+
+msgid "GroupImport|The group was successfully imported."
+msgstr ""
+
+msgid "GroupImport|Unable to process group import file"
+msgstr ""
+
msgid "GroupRoadmap|%{dateWord} – No end date"
msgstr ""
@@ -11597,6 +11896,9 @@ msgstr ""
msgid "Groups (%{groups})"
msgstr ""
+msgid "Groups and projects"
+msgstr ""
+
msgid "Groups can also be nested by creating %{subgroup_docs_link_start}subgroups%{subgroup_docs_link_end}."
msgstr ""
@@ -11642,6 +11944,33 @@ msgstr ""
msgid "GroupsEmptyState|You can manage your group member’s permissions and access to each project in the group."
msgstr ""
+msgid "GroupsNew|Contact an administrator to enable options for importing your group."
+msgstr ""
+
+msgid "GroupsNew|Create"
+msgstr ""
+
+msgid "GroupsNew|Create group"
+msgstr ""
+
+msgid "GroupsNew|GitLab group export"
+msgstr ""
+
+msgid "GroupsNew|Import"
+msgstr ""
+
+msgid "GroupsNew|Import group"
+msgstr ""
+
+msgid "GroupsNew|My Awesome Group"
+msgstr ""
+
+msgid "GroupsNew|No import options available"
+msgstr ""
+
+msgid "GroupsNew|To copy a GitLab group between installations, navigate to the group settings page for the original installation, generate an export file, and upload it here."
+msgstr ""
+
msgid "GroupsTree|Are you sure you want to leave the \"%{fullName}\" group?"
msgstr ""
@@ -11744,6 +12073,9 @@ msgstr ""
msgid "Helps reduce request volume for protected paths"
msgstr ""
+msgid "Here are all your projects in your group, including the one you just created. To start, let’s take a look at your personalized learning project which will help you learn about GitLab at your own pace."
+msgstr ""
+
msgid "Here you will find recent merge request activity"
msgstr ""
@@ -11762,6 +12094,9 @@ msgstr[3] ""
msgstr[4] ""
msgstr[5] ""
+msgid "Hide details"
+msgstr ""
+
msgid "Hide file browser"
msgstr ""
@@ -11771,6 +12106,9 @@ msgstr ""
msgid "Hide host keys manual input"
msgstr ""
+msgid "Hide list"
+msgstr ""
+
msgid "Hide marketing-related entries from help"
msgstr ""
@@ -11795,9 +12133,6 @@ msgstr[5] ""
msgid "Hide values"
msgstr ""
-msgid "Hiding all labels"
-msgstr ""
-
msgid "High or unknown vulnerabilities present"
msgstr ""
@@ -11882,6 +12217,9 @@ msgstr ""
msgid "ID:"
msgstr ""
+msgid "IDE"
+msgstr ""
+
msgid "IDE|Allow live previews of JavaScript projects in the Web IDE using CodeSandbox Live Preview."
msgstr ""
@@ -11972,10 +12310,10 @@ msgstr ""
msgid "If enabled"
msgstr ""
-msgid "If enabled, access to projects will be validated on an external service using their classification label."
+msgid "If enabled, GitLab will handle Object Storage replication using Geo. %{linkStart}More information%{linkEnd}"
msgstr ""
-msgid "If enabled, and if object storage is enabled, GitLab will handle Object Storage replication using Geo"
+msgid "If enabled, access to projects will be validated on an external service using their classification label."
msgstr ""
msgid "If there is no previous license or if the previous license has expired, some GitLab functionality will be blocked until a new, valid license is uploaded."
@@ -12341,6 +12679,9 @@ msgstr[3] ""
msgstr[4] ""
msgstr[5] ""
+msgid "Instance Configuration"
+msgstr ""
+
msgid "Instance Statistics visibility"
msgstr ""
@@ -12362,9 +12703,6 @@ msgstr ""
msgid "Integrations"
msgstr ""
-msgid "Integrations allow you to integrate GitLab with other applications"
-msgstr ""
-
msgid "Integrations|All details"
msgstr ""
@@ -12476,6 +12814,9 @@ msgstr ""
msgid "Invalid query"
msgstr ""
+msgid "Invalid repository bundle for snippet with id %{snippet_id}"
+msgstr ""
+
msgid "Invalid repository path"
msgstr ""
@@ -12494,6 +12835,9 @@ msgstr ""
msgid "Invalid two-factor code."
msgstr ""
+msgid "Invalid yaml"
+msgstr ""
+
msgid "Invitation"
msgstr ""
@@ -12713,15 +13057,24 @@ msgstr ""
msgid "It seems like the Dependency Scanning job ran successfully, but no dependencies have been detected in your project."
msgstr ""
+msgid "It seems that there is currently no available data for code coverage"
+msgstr ""
+
msgid "It's you"
msgstr ""
+msgid "Iteration"
+msgstr ""
+
msgid "Iteration changed to"
msgstr ""
msgid "Iteration removed"
msgstr ""
+msgid "Iteration updated"
+msgstr ""
+
msgid "Iterations"
msgstr ""
@@ -12752,12 +13105,18 @@ msgstr ""
msgid "January"
msgstr ""
+msgid "Jira Issues"
+msgstr ""
+
msgid "Jira import is already running."
msgstr ""
msgid "Jira integration not configured."
msgstr ""
+msgid "Jira project key is not configured"
+msgstr ""
+
msgid "Jira project: %{importProject}"
msgstr ""
@@ -12938,6 +13297,9 @@ msgstr ""
msgid "Keep divergent refs"
msgstr ""
+msgid "Kerberos access denied"
+msgstr ""
+
msgid "Key"
msgstr ""
@@ -12947,10 +13309,13 @@ msgstr ""
msgid "Key: %{key}"
msgstr ""
-msgid "Keyboard Shortcuts"
+msgid "Keyboard shortcuts"
msgstr ""
-msgid "Keyboard shortcuts"
+msgid "Keys"
+msgstr ""
+
+msgid "Ki"
msgstr ""
msgid "Kubernetes"
@@ -12992,6 +13357,9 @@ msgstr ""
msgid "LDAP"
msgstr ""
+msgid "LDAP Synchronization"
+msgstr ""
+
msgid "LDAP settings"
msgstr ""
@@ -13220,12 +13588,18 @@ msgstr ""
msgid "Learn more about custom project templates"
msgstr ""
+msgid "Learn more about deploying to AWS"
+msgstr ""
+
msgid "Learn more about deploying to a cluster"
msgstr ""
msgid "Learn more about group-level project templates"
msgstr ""
+msgid "Learn more about job dependencies"
+msgstr ""
+
msgid "Learn more about signing commits"
msgstr ""
@@ -13277,9 +13651,21 @@ msgstr ""
msgid "License History"
msgstr ""
+msgid "License ID:"
+msgstr ""
+
+msgid "License URL"
+msgstr ""
+
msgid "License-Check"
msgstr ""
+msgid "LicenseCompliance|%{docLinkStart}License Approvals%{docLinkEnd} are active"
+msgstr ""
+
+msgid "LicenseCompliance|%{docLinkStart}License Approvals%{docLinkEnd} are inactive"
+msgstr ""
+
msgid "LicenseCompliance|Add a license"
msgstr ""
@@ -13301,9 +13687,15 @@ msgstr ""
msgid "LicenseCompliance|Deny"
msgstr ""
+msgid "LicenseCompliance|Learn more about %{linkStart}License Approvals%{linkEnd}"
+msgstr ""
+
msgid "LicenseCompliance|License"
msgstr ""
+msgid "LicenseCompliance|License Approvals"
+msgstr ""
+
msgid "LicenseCompliance|License Compliance detected %d license and policy violation for the source branch only; approval required"
msgid_plural "LicenseCompliance|License Compliance detected %d licenses and policy violations for the source branch only; approval required"
msgstr[0] ""
@@ -13544,6 +13936,9 @@ msgstr ""
msgid "List your Bitbucket Server repositories"
msgstr ""
+msgid "Lists"
+msgstr ""
+
msgid "Live preview"
msgstr ""
@@ -13727,6 +14122,9 @@ msgstr ""
msgid "Manage project labels"
msgstr ""
+msgid "Manage storage usage"
+msgstr ""
+
msgid "Manage two-factor authentication"
msgstr ""
@@ -13742,6 +14140,9 @@ msgstr ""
msgid "Manifest file import"
msgstr ""
+msgid "Manifest import"
+msgstr ""
+
msgid "Manual job"
msgstr ""
@@ -13889,6 +14290,12 @@ msgstr ""
msgid "Maximum field length"
msgstr ""
+msgid "Maximum file size is 2MB. Please select a smaller file."
+msgstr ""
+
+msgid "Maximum import size (MB)"
+msgstr ""
+
msgid "Maximum job timeout"
msgstr ""
@@ -13928,6 +14335,9 @@ msgstr ""
msgid "Maximum size of Elasticsearch bulk indexing requests."
msgstr ""
+msgid "Maximum size of import files."
+msgstr ""
+
msgid "Maximum size of individual attachments in comments."
msgstr ""
@@ -13979,6 +14389,9 @@ msgstr ""
msgid "Merge (when the pipeline succeeds)"
msgstr ""
+msgid "Merge Conflicts"
+msgstr ""
+
msgid "Merge Request"
msgstr ""
@@ -14105,6 +14518,9 @@ msgstr ""
msgid "MergeRequests|Squash task canceled: another squash is already in progress."
msgstr ""
+msgid "MergeRequests|This project does not allow squashing commits when merge requests are accepted."
+msgstr ""
+
msgid "MergeRequests|Thread stays resolved"
msgstr ""
@@ -14258,6 +14674,12 @@ msgstr ""
msgid "MetricsSettings|Add a button to the metrics dashboard linking directly to your existing external dashboard."
msgstr ""
+msgid "MetricsSettings|Choose whether to display dashboard metrics in UTC or the user's local timezone."
+msgstr ""
+
+msgid "MetricsSettings|Dashboard timezone"
+msgstr ""
+
msgid "MetricsSettings|External dashboard URL"
msgstr ""
@@ -14267,6 +14689,12 @@ msgstr ""
msgid "MetricsSettings|Metrics Dashboard"
msgstr ""
+msgid "MetricsSettings|UTC (Coordinated Universal Time)"
+msgstr ""
+
+msgid "MetricsSettings|User's local timezone"
+msgstr ""
+
msgid "Metrics|Add metric"
msgstr ""
@@ -14282,6 +14710,9 @@ msgstr ""
msgid "Metrics|Create metric"
msgstr ""
+msgid "Metrics|Current"
+msgstr ""
+
msgid "Metrics|Delete metric"
msgstr ""
@@ -14336,6 +14767,9 @@ msgstr ""
msgid "Metrics|Max"
msgstr ""
+msgid "Metrics|Min"
+msgstr ""
+
msgid "Metrics|Must be a valid PromQL query."
msgstr ""
@@ -14351,6 +14785,9 @@ msgstr ""
msgid "Metrics|Refresh dashboard"
msgstr ""
+msgid "Metrics|Select a value"
+msgstr ""
+
msgid "Metrics|Star dashboard"
msgstr ""
@@ -14369,12 +14806,18 @@ msgstr ""
msgid "Metrics|There was an error getting annotations information."
msgstr ""
+msgid "Metrics|There was an error getting dashboard validation warnings information."
+msgstr ""
+
msgid "Metrics|There was an error getting deployment information."
msgstr ""
msgid "Metrics|There was an error getting environments information."
msgstr ""
+msgid "Metrics|There was an error getting options for variable \"%{name}\"."
+msgstr ""
+
msgid "Metrics|There was an error trying to validate your query"
msgstr ""
@@ -14417,6 +14860,9 @@ msgstr ""
msgid "Metrics|You're about to permanently delete this metric. This cannot be undone."
msgstr ""
+msgid "Metrics|Your dashboard schema is invalid. Edit the dashboard to correct the YAML schema."
+msgstr ""
+
msgid "Metrics|e.g. HTTP requests"
msgstr ""
@@ -14432,12 +14878,18 @@ msgstr ""
msgid "Metrics|e.g. req/sec"
msgstr ""
+msgid "Mi"
+msgstr ""
+
msgid "Microsoft Azure"
msgstr ""
msgid "Middleman project with Static Site Editor support"
msgstr ""
+msgid "Migrate your data from an external source like GitHub, Bitbucket, or another instance of GitLab."
+msgstr ""
+
msgid "Migrated %{success_count}/%{total_count} files."
msgstr ""
@@ -14576,6 +15028,9 @@ msgstr ""
msgid "Minimum capacity to be available before we schedule more mirrors preemptively."
msgstr ""
+msgid "Minimum interval in days"
+msgstr ""
+
msgid "Minimum length is %{minimum_password_length} characters"
msgstr ""
@@ -14762,6 +15217,12 @@ msgstr ""
msgid "Multiple uploaders found: %{uploader_types}"
msgstr ""
+msgid "Must match with the %{codeStart}external_url%{codeEnd} in %{codeStart}/etc/gitlab/gitlab.rb%{codeEnd}."
+msgstr ""
+
+msgid "Must match with the %{codeStart}geo_node_name%{codeEnd} in %{codeStart}/etc/gitlab/gitlab.rb%{codeEnd}. %{linkStart}More information%{linkEnd}"
+msgstr ""
+
msgid "My Awesome Group"
msgstr ""
@@ -14876,10 +15337,16 @@ msgstr ""
msgid "New Application"
msgstr ""
+msgid "New Branch"
+msgstr ""
+
+msgid "New Deploy Key"
+msgstr ""
+
msgid "New Environment"
msgstr ""
-msgid "New Geo Node"
+msgid "New File"
msgstr ""
msgid "New Group"
@@ -14900,15 +15367,15 @@ msgstr[3] ""
msgstr[4] ""
msgstr[5] ""
-msgid "New Iteration"
-msgstr ""
-
msgid "New Jira import"
msgstr ""
msgid "New Label"
msgstr ""
+msgid "New Merge Request"
+msgstr ""
+
msgid "New Milestone"
msgstr ""
@@ -14927,6 +15394,9 @@ msgstr ""
msgid "New Snippet"
msgstr ""
+msgid "New User"
+msgstr ""
+
msgid "New branch"
msgstr ""
@@ -14969,6 +15439,9 @@ msgstr ""
msgid "New issue title"
msgstr ""
+msgid "New iteration"
+msgstr ""
+
msgid "New iteration created"
msgstr ""
@@ -15146,6 +15619,12 @@ msgstr ""
msgid "No grouping"
msgstr ""
+msgid "No iteration"
+msgstr ""
+
+msgid "No iterations to show"
+msgstr ""
+
msgid "No job log"
msgstr ""
@@ -15188,6 +15667,9 @@ msgstr ""
msgid "No other labels with such name or description"
msgstr ""
+msgid "No panels matching properties %{opts}"
+msgstr ""
+
msgid "No parent group"
msgstr ""
@@ -15221,6 +15703,9 @@ msgstr ""
msgid "No schedules"
msgstr ""
+msgid "No stack trace for this error"
+msgstr ""
+
msgid "No starrers matched your search"
msgstr ""
@@ -15233,7 +15718,7 @@ msgstr ""
msgid "No test coverage"
msgstr ""
-msgid "No thanks, don't show this again"
+msgid "No thanks"
msgstr ""
msgid "No vulnerabilities present"
@@ -15248,9 +15733,6 @@ msgstr ""
msgid "No, directly import the existing email addresses and usernames."
msgstr ""
-msgid "No, not interested right now"
-msgstr ""
-
msgid "No. of commits"
msgstr ""
@@ -15296,9 +15778,6 @@ msgstr ""
msgid "Not found."
msgstr ""
-msgid "Not helpful"
-msgstr ""
-
msgid "Not now"
msgstr ""
@@ -15455,6 +15934,9 @@ msgstr ""
msgid "Notifications on"
msgstr ""
+msgid "Notify users by email when sign-in location is not recognized"
+msgstr ""
+
msgid "Nov"
msgstr ""
@@ -15521,9 +16003,6 @@ msgstr ""
msgid "Oh no!"
msgstr ""
-msgid "Ok let's go"
-msgstr ""
-
msgid "Oldest first"
msgstr ""
@@ -15536,16 +16015,49 @@ msgstr ""
msgid "On track"
msgstr ""
+msgid "OnDemandScans|Attached branch"
+msgstr ""
+
+msgid "OnDemandScans|Attached branch is where the scan job runs."
+msgstr ""
+
+msgid "OnDemandScans|Could not run the scan. Please try again."
+msgstr ""
+
msgid "OnDemandScans|Create new DAST scan"
msgstr ""
+msgid "OnDemandScans|DAST will scan the target URL and any discovered sub URLs."
+msgstr ""
+
+msgid "OnDemandScans|New on-demand DAST scan"
+msgstr ""
+
msgid "OnDemandScans|On-demand Scans"
msgstr ""
+msgid "OnDemandScans|On-demand scans run outside the DevOps cycle and find vulnerabilities in your projects. %{learnMoreLinkStart}Learn more%{learnMoreLinkEnd}"
+msgstr ""
+
+msgid "OnDemandScans|Only a passive scan can be performed on demand."
+msgstr ""
+
+msgid "OnDemandScans|Passive DAST Scan"
+msgstr ""
+
+msgid "OnDemandScans|Please enter a valid URL format, ex: http://www.example.com/home"
+msgstr ""
+
+msgid "OnDemandScans|Run this scan"
+msgstr ""
+
+msgid "OnDemandScans|Scan mode"
+msgstr ""
+
msgid "OnDemandScans|Schedule or run scans immediately against target sites. Currently available on-demand scan type: DAST. %{helpLinkStart}More information%{helpLinkEnd}"
msgstr ""
-msgid "Onboarding"
+msgid "OnDemandScans|Target URL"
msgstr ""
msgid "Once imported, repositories can be mirrored over SSH. Read more %{link_start}here%{link_end}."
@@ -15575,6 +16087,9 @@ msgstr ""
msgid "One or more of you personal access tokens were revoked"
msgstr ""
+msgid "One or more of your %{provider} projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
+msgstr ""
+
msgid "One or more of your Bitbucket projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
msgstr ""
@@ -16076,6 +16591,9 @@ msgstr ""
msgid "Parent epic is not present."
msgstr ""
+msgid "Parsing error for param :embed_json. %{message}"
+msgstr ""
+
msgid "Part of merge request changes"
msgstr ""
@@ -16088,6 +16606,9 @@ msgstr ""
msgid "Passed"
msgstr ""
+msgid "Passed on"
+msgstr ""
+
msgid "Password"
msgstr ""
@@ -16172,6 +16693,9 @@ msgstr ""
msgid "Perform common operations on GitLab project"
msgstr ""
+msgid "Performance and resource management"
+msgstr ""
+
msgid "Performance optimization"
msgstr ""
@@ -16379,7 +16903,7 @@ msgstr ""
msgid "Pipelines|Something went wrong while cleaning runners cache."
msgstr ""
-msgid "Pipelines|There are currently no %{scope} pipelines."
+msgid "Pipelines|There are currently no finished pipelines."
msgstr ""
msgid "Pipelines|There are currently no pipelines."
@@ -16490,6 +17014,9 @@ msgstr ""
msgid "Pipeline|Stop pipeline #%{pipelineId}?"
msgstr ""
+msgid "Pipeline|Tag name"
+msgstr ""
+
msgid "Pipeline|Trigger author"
msgstr ""
@@ -16571,12 +17098,18 @@ msgstr ""
msgid "Please check your email (%{email}) to verify that you own this address and unlock the power of CI/CD. Didn't receive it? %{resend_link}. Wrong email address? %{update_link}."
msgstr ""
+msgid "Please choose a file"
+msgstr ""
+
msgid "Please choose a group URL with no special characters."
msgstr ""
msgid "Please complete your profile with email address"
msgstr ""
+msgid "Please convert %{linkStart}them to Git%{linkEnd}, and go through the %{linkToImportFlow} again."
+msgstr ""
+
msgid "Please convert them to %{link_to_git}, and go through the %{link_to_import_flow} again."
msgstr ""
@@ -16634,6 +17167,9 @@ msgstr ""
msgid "Please provide a valid email address."
msgstr ""
+msgid "Please provide attributes to update"
+msgstr ""
+
msgid "Please refer to <a href=\"%{docs_url}\">%{docs_url}</a>"
msgstr ""
@@ -17369,6 +17905,9 @@ msgstr ""
msgid "Project has too many %{label_for_message} to search"
msgstr ""
+msgid "Project is required when cluster_type is :project"
+msgstr ""
+
msgid "Project members"
msgstr ""
@@ -17486,7 +18025,40 @@ msgstr ""
msgid "ProjectService|Comment"
msgstr ""
-msgid "ProjectService|Comment will be posted on each event"
+msgid "ProjectService|Event will be triggered by a push to the repository"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a commit is created/updated"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a confidential issue is created/updated/closed"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a deployment finishes"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a merge request is created/updated/merged"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a new tag is pushed to the repository"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a new, unique alert is recorded"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a pipeline status changes"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a wiki page is created/updated"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when an issue is created/updated/closed"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when someone adds a comment"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when someone adds a comment on a confidential issue"
msgstr ""
msgid "ProjectService|Perform common operations on GitLab project: %{project_name}"
@@ -17537,6 +18109,9 @@ msgstr ""
msgid "ProjectSettings|Enable 'Delete source branch' option by default"
msgstr ""
+msgid "ProjectSettings|Enable merge trains and pipelines for merged results"
+msgstr ""
+
msgid "ProjectSettings|Every merge creates a merge commit"
msgstr ""
@@ -17567,7 +18142,7 @@ msgstr ""
msgid "ProjectSettings|Forks"
msgstr ""
-msgid "ProjectSettings|Git Large File Storage"
+msgid "ProjectSettings|Git Large File Storage (LFS)"
msgstr ""
msgid "ProjectSettings|Internal"
@@ -17603,9 +18178,6 @@ msgstr ""
msgid "ProjectSettings|Merge options"
msgstr ""
-msgid "ProjectSettings|Merge pipelines will try to validate the post-merge result prior to merging"
-msgstr ""
-
msgid "ProjectSettings|Merge requests"
msgstr ""
@@ -17633,6 +18205,9 @@ msgstr ""
msgid "ProjectSettings|Pipelines"
msgstr ""
+msgid "ProjectSettings|Pipelines for merge requests must be enabled in the CI/CD configuration file, or pipelines could be unresolvable or dropped"
+msgstr ""
+
msgid "ProjectSettings|Pipelines must succeed"
msgstr ""
@@ -17660,6 +18235,9 @@ msgstr ""
msgid "ProjectSettings|Show link to create/view merge request when pushing from the command line"
msgstr ""
+msgid "ProjectSettings|Skipped pipelines are considered successful"
+msgstr ""
+
msgid "ProjectSettings|Snippets"
msgstr ""
@@ -17675,6 +18253,9 @@ msgstr ""
msgid "ProjectSettings|These checks must pass before merge requests can be merged"
msgstr ""
+msgid "ProjectSettings|This introduces the risk of merging changes that will not pass the pipeline."
+msgstr ""
+
msgid "ProjectSettings|This setting is applied on the server level and can be overridden by an admin."
msgstr ""
@@ -17852,15 +18433,27 @@ msgstr ""
msgid "ProjectsNew|Blank project"
msgstr ""
+msgid "ProjectsNew|Connect your external repository to GitLab CI/CD."
+msgstr ""
+
msgid "ProjectsNew|Contact an administrator to enable options for importing your project."
msgstr ""
msgid "ProjectsNew|Create"
msgstr ""
+msgid "ProjectsNew|Create a blank project to house your files, plan your work, and collaborate on code, among other things."
+msgstr ""
+
+msgid "ProjectsNew|Create blank project"
+msgstr ""
+
msgid "ProjectsNew|Create from template"
msgstr ""
+msgid "ProjectsNew|Create new project"
+msgstr ""
+
msgid "ProjectsNew|Creating project & repository."
msgstr ""
@@ -17885,6 +18478,9 @@ msgstr ""
msgid "ProjectsNew|Project description %{tag_start}(optional)%{tag_end}"
msgstr ""
+msgid "ProjectsNew|Run CI/CD for external repository"
+msgstr ""
+
msgid "ProjectsNew|Template"
msgstr ""
@@ -18146,6 +18742,9 @@ msgstr ""
msgid "Protected Branch"
msgstr ""
+msgid "Protected Branches"
+msgstr ""
+
msgid "Protected Environment"
msgstr ""
@@ -18158,6 +18757,9 @@ msgstr ""
msgid "Protected Tag"
msgstr ""
+msgid "Protected Tags"
+msgstr ""
+
msgid "Protected branches"
msgstr ""
@@ -18257,6 +18859,9 @@ msgstr ""
msgid "Provider"
msgstr ""
+msgid "Proxy support for this API is not available currently"
+msgstr ""
+
msgid "Pseudonymizer data collection"
msgstr ""
@@ -18281,6 +18886,9 @@ msgstr ""
msgid "Publish to status page"
msgstr ""
+msgid "Published on status page"
+msgstr ""
+
msgid "Publishes this issue to the associated status page."
msgstr ""
@@ -18455,6 +19063,9 @@ msgstr ""
msgid "Recipe"
msgstr ""
+msgid "Reconfigure"
+msgstr ""
+
msgid "Recover hidden stage"
msgstr ""
@@ -18545,6 +19156,9 @@ msgstr ""
msgid "Registration|Your profile"
msgstr ""
+msgid "Regulate approvals by authors/committers, based on compliance frameworks. Can be changed only at the instance level."
+msgstr ""
+
msgid "Rejected (closed)"
msgstr ""
@@ -18599,15 +19213,24 @@ msgstr ""
msgid "ReleaseAssetLinkType|Image"
msgstr ""
+msgid "ReleaseAssetLinkType|Images"
+msgstr ""
+
msgid "ReleaseAssetLinkType|Other"
msgstr ""
msgid "ReleaseAssetLinkType|Package"
msgstr ""
+msgid "ReleaseAssetLinkType|Packages"
+msgstr ""
+
msgid "ReleaseAssetLinkType|Runbook"
msgstr ""
+msgid "ReleaseAssetLinkType|Runbooks"
+msgstr ""
+
msgid "Releases"
msgstr ""
@@ -18620,6 +19243,9 @@ msgstr ""
msgid "Releases documentation"
msgstr ""
+msgid "Releases|New Release"
+msgstr ""
+
msgid "Release|Something went wrong while getting the release details"
msgstr ""
@@ -18692,6 +19318,9 @@ msgstr ""
msgid "Remove fork relationship"
msgstr ""
+msgid "Remove from batch"
+msgstr ""
+
msgid "Remove from board"
msgstr ""
@@ -18881,6 +19510,12 @@ msgstr ""
msgid "Replication"
msgstr ""
+msgid "Replication enabled"
+msgstr ""
+
+msgid "Replication paused"
+msgstr ""
+
msgid "Reply by email"
msgstr ""
@@ -18908,15 +19543,15 @@ msgstr ""
msgid "Reported %{timeAgo} by %{reportedBy}"
msgstr ""
-msgid "Reported Resource Changes: %{addNum} to add, %{changeNum} to change, %{deleteNum} to delete"
-msgstr ""
-
msgid "Reporter"
msgstr ""
msgid "Reporting"
msgstr ""
+msgid "Reports"
+msgstr ""
+
msgid "Reports|%{combinedString} and %{resolvedString}"
msgstr ""
@@ -18959,6 +19594,9 @@ msgstr ""
msgid "Reports|Failure"
msgstr ""
+msgid "Reports|Identifier"
+msgstr ""
+
msgid "Reports|Metrics reports are loading"
msgstr ""
@@ -18971,6 +19609,9 @@ msgstr ""
msgid "Reports|Metrics reports failed loading results"
msgstr ""
+msgid "Reports|Scanner"
+msgstr ""
+
msgid "Reports|Severity"
msgstr ""
@@ -19019,15 +19660,33 @@ msgstr ""
msgid "Repository cleanup has started. You will receive an email once the cleanup operation is complete."
msgstr ""
+msgid "Repository files count over the limit"
+msgstr ""
+
+msgid "Repository has an invalid default branch name."
+msgstr ""
+
+msgid "Repository has more than one branch."
+msgstr ""
+
msgid "Repository has no locks."
msgstr ""
+msgid "Repository has tags."
+msgstr ""
+
msgid "Repository maintenance"
msgstr ""
msgid "Repository mirroring"
msgstr ""
+msgid "Repository must contain at least 1 file."
+msgstr ""
+
+msgid "Repository size is above the limit."
+msgstr ""
+
msgid "Repository static objects"
msgstr ""
@@ -19037,7 +19696,7 @@ msgstr ""
msgid "Repository sync capacity"
msgstr ""
-msgid "Repository: %{counter_repositories} / Wikis: %{counter_wikis} / Build Artifacts: %{counter_build_artifacts} / LFS: %{counter_lfs_objects}"
+msgid "Repository: %{counter_repositories} / Wikis: %{counter_wikis} / Build Artifacts: %{counter_build_artifacts} / LFS: %{counter_lfs_objects} / Snippets: %{counter_snippets}"
msgstr ""
msgid "RepositorySettingsAccessLevel|Select"
@@ -19049,6 +19708,9 @@ msgstr ""
msgid "Request Headers"
msgstr ""
+msgid "Request details"
+msgstr ""
+
msgid "Request parameter %{param} is missing."
msgstr ""
@@ -19163,6 +19825,9 @@ msgstr ""
msgid "Reset template"
msgstr ""
+msgid "Reset to project defaults"
+msgstr ""
+
msgid "Resetting the authorization key for this project will require updating the authorization key in every alert source it is enabled in."
msgstr ""
@@ -19259,9 +19924,6 @@ msgstr ""
msgid "Resume"
msgstr ""
-msgid "Resume replication"
-msgstr ""
-
msgid "Resync"
msgstr ""
@@ -19928,6 +20590,9 @@ msgstr ""
msgid "SecurityReports|False positive"
msgstr ""
+msgid "SecurityReports|Group Security Dashboard"
+msgstr ""
+
msgid "SecurityReports|Hide dismissed"
msgstr ""
@@ -19982,10 +20647,13 @@ msgstr ""
msgid "SecurityReports|Remove project from dashboard"
msgstr ""
-msgid "SecurityReports|Report type"
+msgid "SecurityReports|Return to dashboard"
msgstr ""
-msgid "SecurityReports|Return to dashboard"
+msgid "SecurityReports|Scan details"
+msgstr ""
+
+msgid "SecurityReports|Scanner type"
msgstr ""
msgid "SecurityReports|Security Dashboard"
@@ -20042,6 +20710,9 @@ msgstr ""
msgid "SecurityReports|There was an error while generating the report."
msgstr ""
+msgid "SecurityReports|Unable to add %{invalidProjectsMessage}"
+msgstr ""
+
msgid "SecurityReports|Unable to add %{invalidProjects}"
msgstr ""
@@ -20156,6 +20827,9 @@ msgstr ""
msgid "Select due date"
msgstr ""
+msgid "Select file"
+msgstr ""
+
msgid "Select group or project"
msgstr ""
@@ -20219,9 +20893,6 @@ msgstr ""
msgid "Select the branch you want to set as the default for this project. All merge requests and commits will automatically be made against this branch unless you specify a different one."
msgstr ""
-msgid "Select the configured storage available for new repositories to be placed on."
-msgstr ""
-
msgid "Select the custom project template source group."
msgstr ""
@@ -20414,12 +21085,6 @@ msgstr ""
msgid "Service Desk is enabled but not yet active"
msgstr ""
-msgid "Service Desk is off"
-msgstr ""
-
-msgid "Service Desk is on"
-msgstr ""
-
msgid "Service Templates"
msgstr ""
@@ -20507,6 +21172,15 @@ msgstr ""
msgid "Set the milestone to %{milestone_reference}."
msgstr ""
+msgid "Set the number of concurrent requests this secondary node will make to the primary node while backfilling."
+msgstr ""
+
+msgid "Set the synchronization and verification capacity for the secondary node."
+msgstr ""
+
+msgid "Set the timeout in seconds to send a secondary node status to the primary and IPs allowed for the secondary nodes."
+msgstr ""
+
msgid "Set time estimate"
msgstr ""
@@ -20546,6 +21220,9 @@ msgstr ""
msgid "Set weight to %{weight}."
msgstr ""
+msgid "Set what should be replicated by choosing specific projects or groups by the secondary node."
+msgstr ""
+
msgid "SetPasswordToCloneLink|set a password"
msgstr ""
@@ -20672,6 +21349,9 @@ msgstr ""
msgid "Show complete raw log"
msgstr ""
+msgid "Show details"
+msgstr ""
+
msgid "Show file browser"
msgstr ""
@@ -20681,10 +21361,13 @@ msgstr ""
msgid "Show latest version"
msgstr ""
+msgid "Show list"
+msgstr ""
+
msgid "Show me everything"
msgstr ""
-msgid "Show me how"
+msgid "Show me how to add a pipeline"
msgstr ""
msgid "Show me more advanced stuff"
@@ -20741,9 +21424,6 @@ msgstr ""
msgid "Showing all issues"
msgstr ""
-msgid "Showing all labels"
-msgstr ""
-
msgid "Showing last %{size} of log -"
msgstr ""
@@ -20855,9 +21535,6 @@ msgstr ""
msgid "Skip outdated deployment jobs"
msgstr ""
-msgid "Skip this for now"
-msgstr ""
-
msgid "Skipped"
msgstr ""
@@ -21002,6 +21679,9 @@ msgstr ""
msgid "Something went wrong while adding your award. Please try again."
msgstr ""
+msgid "Something went wrong while applying the batch of suggestions. Please try again."
+msgstr ""
+
msgid "Something went wrong while applying the suggestion. Please try again."
msgstr ""
@@ -21104,10 +21784,13 @@ msgstr ""
msgid "Something went wrong while updating a requirement."
msgstr ""
+msgid "Something went wrong while updating assignees"
+msgstr ""
+
msgid "Something went wrong while updating your list settings"
msgstr ""
-msgid "Something went wrong with your automatic subscription renewal"
+msgid "Something went wrong with your automatic subscription renewal."
msgstr ""
msgid "Something went wrong, unable to add %{project} to dashboard"
@@ -21125,6 +21808,9 @@ msgstr ""
msgid "Something went wrong, unable to search projects"
msgstr ""
+msgid "Something went wrong."
+msgstr ""
+
msgid "Something went wrong. Please try again."
msgstr ""
@@ -21308,6 +21994,9 @@ msgstr ""
msgid "Source code"
msgstr ""
+msgid "Source code (%{fileExtension})"
+msgstr ""
+
msgid "Source is not available"
msgstr ""
@@ -21392,6 +22081,9 @@ msgstr ""
msgid "Stack trace"
msgstr ""
+msgid "Stacktrace snippet"
+msgstr ""
+
msgid "Stage"
msgstr ""
@@ -21497,6 +22189,9 @@ msgstr ""
msgid "Start thread & reopen %{noteable_name}"
msgstr ""
+msgid "Start using Directed Acyclic Graphs (DAG)"
+msgstr ""
+
msgid "Start your Free Gold Trial"
msgstr ""
@@ -21959,6 +22654,21 @@ msgstr ""
msgid "SuggestedColors|Very pale orange"
msgstr ""
+msgid "Suggestion is not applicable as the suggestion was not found."
+msgstr ""
+
+msgid "Suggestions are not applicable as one or more suggestions were not found."
+msgstr ""
+
+msgid "Suggestions are not applicable as their lines cannot overlap."
+msgstr ""
+
+msgid "Suggestions must all be on the same branch."
+msgstr ""
+
+msgid "Suggestions that change line count can't be added to batches, yet."
+msgstr ""
+
msgid "Suggestions:"
msgstr ""
@@ -22001,9 +22711,6 @@ msgstr ""
msgid "Synchronization disabled"
msgstr ""
-msgid "Synchronization of container repositories is disabled."
-msgstr ""
-
msgid "System"
msgstr ""
@@ -22184,6 +22891,42 @@ msgstr ""
msgid "Terms of Service and Privacy Policy"
msgstr ""
+msgid "Terraform|%{number} Terraform report failed to generate"
+msgid_plural "Terraform|%{number} Terraform reports failed to generate"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+msgstr[4] ""
+msgstr[5] ""
+
+msgid "Terraform|%{number} Terraform report was generated in your pipelines"
+msgid_plural "Terraform|%{number} Terraform reports were generated in your pipelines"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+msgstr[4] ""
+msgstr[5] ""
+
+msgid "Terraform|A Terraform report failed to generate."
+msgstr ""
+
+msgid "Terraform|A Terraform report was generated in your pipelines."
+msgstr ""
+
+msgid "Terraform|Generating the report caused an error."
+msgstr ""
+
+msgid "Terraform|Reported Resource Changes: %{addNum} to add, %{changeNum} to change, %{deleteNum} to delete"
+msgstr ""
+
+msgid "Terraform|The Terraform report %{name} failed to generate."
+msgstr ""
+
+msgid "Terraform|The Terraform report %{name} was generated in your pipelines."
+msgstr ""
+
msgid "Test"
msgstr ""
@@ -22214,7 +22957,7 @@ msgstr ""
msgid "TestHooks|Ensure the project has CI pipelines."
msgstr ""
-msgid "TestHooks|Ensure the project has at least one commit."
+msgid "TestHooks|Ensure the project has deployments."
msgstr ""
msgid "TestHooks|Ensure the project has issues."
@@ -22319,7 +23062,7 @@ msgstr ""
msgid "The Prometheus server responded with \"bad request\". Please check your queries are correct and are supported in your Prometheus version. %{documentationLink}"
msgstr ""
-msgid "The URL defined on the primary node that secondary nodes should use to contact it. Defaults to URL"
+msgid "The URL defined on the primary node that secondary nodes should use to contact it."
msgstr ""
msgid "The URL to use for connecting to Elasticsearch. Use a comma-separated list to support clustering (e.g., \"http://localhost:9200, http://localhost:9201\")."
@@ -22328,6 +23071,9 @@ msgstr ""
msgid "The X509 Certificate to use when mutual TLS is required to communicate with the external authorization service. If left blank, the server certificate is still validated when accessing over HTTPS."
msgstr ""
+msgid "The above settings apply to all projects with the selected compliance framework(s)."
+msgstr ""
+
msgid "The amount of seconds after which a request to get a secondary node status will time out."
msgstr ""
@@ -22631,9 +23377,6 @@ msgstr ""
msgid "The total stage shows the time it takes between creating an issue and deploying the code to production. The data will be automatically added once you have completed the full idea to production cycle."
msgstr ""
-msgid "The unique identifier for the Geo node. Must match %{geoNodeName} if it is set in gitlab.rb, otherwise it must match %{externalUrl} with a trailing slash"
-msgstr ""
-
msgid "The update action will time out after %{number_of_minutes} minutes. For big repositories, use a clone/push combination."
msgstr ""
@@ -22658,9 +23401,6 @@ msgstr ""
msgid "The user you are trying to deactivate has been active in the past %{minimum_inactive_days} days and cannot be deactivated"
msgstr ""
-msgid "The user-facing URL of the Geo node"
-msgstr ""
-
msgid "The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6."
msgstr ""
@@ -22751,6 +23491,9 @@ msgstr ""
msgid "There was a problem fetching project branches."
msgstr ""
+msgid "There was a problem fetching project tags."
+msgstr ""
+
msgid "There was a problem fetching project users."
msgstr ""
@@ -22802,6 +23545,9 @@ msgstr ""
msgid "There was an error fetching the %{replicableType}"
msgstr ""
+msgid "There was an error fetching the Geo Settings"
+msgstr ""
+
msgid "There was an error fetching the Node's Groups"
msgstr ""
@@ -22922,6 +23668,12 @@ msgstr ""
msgid "This %{issuable} is locked. Only <strong>project members</strong> can comment."
msgstr ""
+msgid "This %{noteableTypeText} is %{confidentialLinkStart}confidential%{linkEnd} and %{lockedLinkStart}locked%{linkEnd}."
+msgstr ""
+
+msgid "This %{noteableTypeText} is locked."
+msgstr ""
+
msgid "This %{viewer} could not be displayed because %{reason}. You can %{options} instead."
msgstr ""
@@ -22940,6 +23692,9 @@ msgstr ""
msgid "This action can lead to data loss. To prevent accidental actions we ask you to confirm your intention."
msgstr ""
+msgid "This also resolves all related threads"
+msgstr ""
+
msgid "This also resolves the discussion"
msgstr ""
@@ -23030,6 +23785,9 @@ msgstr ""
msgid "This epic does not exist or you don't have sufficient permission."
msgstr ""
+msgid "This feature is currently in beta. We invite you to %{linkStart}give feedback%{linkEnd}."
+msgstr ""
+
msgid "This feature requires local storage to be enabled"
msgstr ""
@@ -23063,7 +23821,7 @@ msgstr ""
msgid "This is a Work in Progress"
msgstr ""
-msgid "This is a confidential issue."
+msgid "This is a confidential %{noteableTypeText}."
msgstr ""
msgid "This is a delayed job to run in %{remainingTime}"
@@ -23090,9 +23848,6 @@ msgstr ""
msgid "This is your current session"
msgstr ""
-msgid "This issue is %{confidentialLinkStart}confidential%{linkEnd} and %{lockedLinkStart}locked%{linkEnd}."
-msgstr ""
-
msgid "This issue is confidential"
msgstr ""
@@ -23102,9 +23857,6 @@ msgstr ""
msgid "This issue is in a child epic of the filtered epic"
msgstr ""
-msgid "This issue is locked."
-msgstr ""
-
msgid "This job depends on other jobs with expired/erased artifacts: %{invalid_dependencies}"
msgstr ""
@@ -23204,6 +23956,9 @@ msgstr ""
msgid "This license has already expired."
msgstr ""
+msgid "This link points to external content"
+msgstr ""
+
msgid "This may expose confidential information as the selected fork is in another namespace that can have other members."
msgstr ""
@@ -23231,6 +23986,9 @@ msgstr ""
msgid "This page will be removed in a future release."
msgstr ""
+msgid "This pipeline does not use the %{codeStart}needs%{codeEnd} keyword and can't be represented as a directed acyclic graph."
+msgstr ""
+
msgid "This pipeline makes use of a predefined CI/CD configuration enabled by %{strongStart}Auto DevOps.%{strongEnd}"
msgstr ""
@@ -23459,6 +24217,9 @@ msgstr ""
msgid "Time from last commit to merge"
msgstr ""
+msgid "Time in seconds"
+msgstr ""
+
msgid "Time in seconds GitLab will wait for a response from the external service. When the service does not respond in time, access will be denied."
msgstr ""
@@ -23888,9 +24649,15 @@ msgstr ""
msgid "Total artifacts size: %{total_size}"
msgstr ""
+msgid "Total cores (CPUs)"
+msgstr ""
+
msgid "Total issues"
msgstr ""
+msgid "Total memory (GB)"
+msgstr ""
+
msgid "Total test time for all commits/merges"
msgstr ""
@@ -24092,6 +24859,9 @@ msgstr ""
msgid "U2F only works with HTTPS-enabled websites. Contact your administrator for more details."
msgstr ""
+msgid "UI Development Kit"
+msgstr ""
+
msgid "URL"
msgstr ""
@@ -24164,9 +24934,6 @@ msgstr ""
msgid "Unable to load the merge request widget. Try reloading the page."
msgstr ""
-msgid "Unable to process group import file"
-msgstr ""
-
msgid "Unable to resolve"
msgstr ""
@@ -24182,6 +24949,9 @@ msgstr ""
msgid "Unable to sign you in to the group with SAML due to \"%{reason}\""
msgstr ""
+msgid "Unable to suggest a path. Please refresh and try again."
+msgstr ""
+
msgid "Unable to update label prioritization at this time"
msgstr ""
@@ -24197,6 +24967,9 @@ msgstr ""
msgid "Unarchiving the project will restore people's ability to make changes to it. The repository can be committed to, and issues, comments, and other entities can be created. %{strong_start}Once active, this project shows up in the search and on the dashboard.%{strong_end}"
msgstr ""
+msgid "Unassign from commenting user"
+msgstr ""
+
msgid "Unblock"
msgstr ""
@@ -24272,6 +25045,9 @@ msgstr ""
msgid "Unreachable"
msgstr ""
+msgid "Unrecognized cluster type"
+msgstr ""
+
msgid "Unresolve"
msgstr ""
@@ -24332,6 +25108,9 @@ msgstr ""
msgid "Unsubscribes from this %{quick_action_target}."
msgstr ""
+msgid "Unsupported todo type passed. Supported todo types are: %{todo_types}"
+msgstr ""
+
msgid "Until"
msgstr ""
@@ -24359,6 +25138,9 @@ msgstr ""
msgid "Update approval rule"
msgstr ""
+msgid "Update approvers"
+msgstr ""
+
msgid "Update failed"
msgstr ""
@@ -24368,6 +25150,9 @@ msgstr ""
msgid "Update it"
msgstr ""
+msgid "Update iteration"
+msgstr ""
+
msgid "Update now"
msgstr ""
@@ -24413,7 +25198,7 @@ msgstr ""
msgid "Updated at"
msgstr ""
-msgid "Updated to"
+msgid "Updated to %{linkStart}chart v%{linkEnd}"
msgstr ""
msgid "Updating"
@@ -24443,6 +25228,9 @@ msgstr ""
msgid "Upload CSV file"
msgstr ""
+msgid "Upload License"
+msgstr ""
+
msgid "Upload New File"
msgstr ""
@@ -24524,16 +25312,19 @@ msgstr ""
msgid "UsageQuota|Pipelines"
msgstr ""
+msgid "UsageQuota|Purchase more storage"
+msgstr ""
+
msgid "UsageQuota|Repositories"
msgstr ""
msgid "UsageQuota|Repository"
msgstr ""
-msgid "UsageQuota|Storage"
+msgid "UsageQuota|Snippets"
msgstr ""
-msgid "UsageQuota|Storage usage:"
+msgid "UsageQuota|Storage"
msgstr ""
msgid "UsageQuota|This namespace has no projects which use shared runners"
@@ -24566,6 +25357,12 @@ msgstr ""
msgid "UsageQuota|Wikis"
msgstr ""
+msgid "UsageQuota|You used: %{usage} %{limit}"
+msgstr ""
+
+msgid "UsageQuota|out of %{formattedLimit} of your namespace storage"
+msgstr ""
+
msgid "Use %{code_start}::%{code_end} to create a %{link_start}scoped label set%{link_end} (eg. %{code_start}priority::1%{code_end})"
msgstr ""
@@ -24632,6 +25429,9 @@ msgstr ""
msgid "User IDs"
msgstr ""
+msgid "User Lists can only be created and modified with %{linkStart}the API%{linkEnd}"
+msgstr ""
+
msgid "User OAuth applications"
msgstr ""
@@ -24656,6 +25456,9 @@ msgstr ""
msgid "User key was successfully removed."
msgstr ""
+msgid "User list %{name} will be removed. Are you sure?"
+msgstr ""
+
msgid "User map"
msgstr ""
@@ -24677,148 +25480,10 @@ msgstr ""
msgid "User was successfully updated."
msgstr ""
-msgid "UserOnboardingTour|%{activeTour}/%{totalTours}"
-msgstr ""
-
-msgid "UserOnboardingTour|%{completed}/%{total} steps completed"
-msgstr ""
-
-msgid "UserOnboardingTour|%{emphasisStart}Well done!%{emphasisEnd}%{lineBreak}%{lineBreak}That's it for our guided tour, congratulations for making it all the way to the end!%{lineBreak}%{lineBreak}We hope this gave you a good overview of GitLab and how it can help you. We'll now show you how to create your own project and invite your colleagues."
-msgstr ""
-
-msgid "UserOnboardingTour|Adding other members to a project is done through Project Settings. Click on %{emphasisStart}Settings%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Alright, that's it for Commits. Let's take a look at the %{emphasisStart}Branches%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Awesome! Now click on %{emphasisStart}Members%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Click on one of the %{emphasisStart}Compare%{emphasisEnd} buttons to compare a branch to master."
-msgstr ""
-
-msgid "UserOnboardingTour|Click on one of the %{emphasisStart}pipeline IDs%{emphasisEnd} to see the details of a pipeline."
-msgstr ""
-
-msgid "UserOnboardingTour|Click to open the latest commit to see its details."
-msgstr ""
-
-msgid "UserOnboardingTour|Close 'Learn GitLab'"
-msgstr ""
-
-msgid "UserOnboardingTour|Commits are shown in chronological order and can be filtered by the commit message or by the branch."
-msgstr ""
-
-msgid "UserOnboardingTour|Create a project"
-msgstr ""
-
-msgid "UserOnboardingTour|Exit 'Learn GitLab'"
-msgstr ""
-
-msgid "UserOnboardingTour|Got it"
-msgstr ""
-
-msgid "UserOnboardingTour|Great job! %{clapHands} We hope the tour was helpful and that you learned how to use GitLab.%{lineBreak}%{lineBreak}We'd love to get your feedback on this tour.%{lineBreak}%{lineBreak}%{emphasisStart}How helpful would you say this guided tour was?%{emphasisEnd}%{lineBreak}%{lineBreak}"
-msgstr ""
-
-msgid "UserOnboardingTour|Guided GitLab Tour"
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can compare the changes of this branch to another one. Changes are divided by files so that it's easier to see what was changed where."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can create a project from scratch, start with a template or import a repository from other platforms. Whatever you choose, we'll guide you through the process.%{lineBreak}%{lineBreak}Fill in your new project information and click on %{emphasisStart}Create Project%{emphasisEnd} to progress to the next step."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can see the breakdown of the pipelines: its stages and jobs in each of the stages and their status.%{lineBreak}%{lineBreak}Our CI/CD pipelines are quite complex, most of our users have fewer and simpler pipelines."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can see the current members of the project (just you at the moment) and invite new members.%{lineBreak}%{lineBreak}You can invite multiple members at once (existing GitLab users or invite by email) and you can also set their roles and permissions.%{lineBreak}%{lineBreak}Add a few members and click on %{emphasisStart}Add to project%{emphasisEnd} to complete this step."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can see what changes were made with this commit, on what branch and if there's a related merge request. The status of the pipeline will also show up if CI/CD is set up.%{lineBreak}%{lineBreak}You can also comment on the lines of code that were changed and start a discussion with your colleagues!"
-msgstr ""
-
-msgid "UserOnboardingTour|Here's an overview of branches in the %{emphasisStart}%{projectName}%{emphasisEnd} project. They're split into Active and Stale.%{lineBreak}%{lineBreak}From here, you can create a new merge request from a branch, or compare the branch to any other branch in the project. By default, it will compare it to the master branch."
-msgstr ""
-
-msgid "UserOnboardingTour|Invite colleagues"
-msgstr ""
-
-msgid "UserOnboardingTour|Issues are great for communicating and keeping track of progress in GitLab. These are all issues that are open in the %{emphasisStart}%{projectName}%{emphasisEnd}.%{lineBreak}%{lineBreak}You can help us improve GitLab by contributing work to issues that are labeled <span class=\"badge color-label accept-mr-label\">Accepting merge requests</span>.%{lineBreak}%{lineBreak}This list can be filtered by labels, milestones, assignees, authors... We'll show you how it looks when the list is filtered by a label."
-msgstr ""
-
-msgid "UserOnboardingTour|Learn GitLab"
-msgstr ""
-
-msgid "UserOnboardingTour|Let's take a closer look at a merge request. Click on the title of one."
-msgstr ""
-
-msgid "UserOnboardingTour|Let's take a closer look at all the commits. Click on %{emphasisStart}Commits%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Let's take a closer look at the repository of this project. Click on %{emphasisStart}Repository%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|No thanks"
-msgstr ""
-
-msgid "UserOnboardingTour|Ok, let's go"
-msgstr ""
-
-msgid "UserOnboardingTour|Ok, show me"
-msgstr ""
-
-msgid "UserOnboardingTour|Open one of the issues by clicking on its title."
-msgstr ""
-
-msgid "UserOnboardingTour|Restart this step"
-msgstr ""
-
-msgid "UserOnboardingTour|Skip this step"
-msgstr ""
-
-msgid "UserOnboardingTour|Sweet! Your project was created and is ready to be used.%{lineBreak}%{lineBreak}You can start adding files to the repository or clone it. One last thing we want to show you is how to invite your colleagues to your new project."
-msgstr ""
-
-msgid "UserOnboardingTour|Take a look. Here's a nifty menu for quickly creating issues, merge requests, snippets, projects and groups. Click on it and select \"New project\" from the \"GitLab\" section to get started."
-msgstr ""
-
-msgid "UserOnboardingTour|Thanks for taking the guided tour. Remember, if you want to go through it again, you can start %{emphasisStart}Learn GitLab%{emphasisEnd} in the help menu on the top right."
-msgstr ""
-
-msgid "UserOnboardingTour|Thanks for the feedback! %{thumbsUp}"
-msgstr ""
-
-msgid "UserOnboardingTour|That's it for issues. Let'st take a look at %{emphasisStart}Merge Requests%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|That's it for merge requests. Now for the final part of this guided tour - the %{emphasisStart}CI/CD%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|That's it for the Repository. Let's take a look at the %{emphasisStart}Issues%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|The structure of this page is very similar to issues. Status, description, discussion and the sidebar are all here.%{lineBreak}%{lineBreak}But take a look below the description and you'll notice that there's more information about the merge request, the CI/CD pipeline and the options for approving it.%{lineBreak}%{lineBreak}Alongside the discussion you can also see more information about commits in this merge request, the status of pipelines and review all changes that were made."
-msgstr ""
-
-msgid "UserOnboardingTour|There's a lot of information here but don't worry, we'll go through it.%{lineBreak}%{lineBreak}On the top you can see the status of the issue and when it was opened and by whom. Directly below it is the issue description and below that are other %{emphasisStart}related issues%{emphasisEnd} and %{emphasisStart}merge requests%{emphasisEnd} (if any). Then below that is the %{emphasisStart}discussion%{emphasisEnd}, that's where most of the communication happens.%{lineBreak}%{lineBreak}On the right, there's a sidebar where you can view/change the %{emphasisStart}assignee, milestone, due date, labels, weight%{emphasisEnd}, etc."
-msgstr ""
-
-msgid "UserOnboardingTour|These are all the CI/CD pipelines we have for our %{emphasisStart}%{projectName}%{emphasisEnd} project.%{lineBreak}%{lineBreak}Here you can see the status of each pipeline, for what commit it's running for, its stages and the status for them."
-msgstr ""
-
-msgid "UserOnboardingTour|These are all the issues that are available for community contributions. Let's take a closer look at one of them."
-msgstr ""
-
-msgid "UserOnboardingTour|This is an overview of all merge requests in this project. Similarly to the issues overview it can be filtered down by things like labels, milestones, authors, assignees, etc."
-msgstr ""
-
-msgid "UserOnboardingTour|This is the repository for the %{emphasisStart}%{projectName}%{emphasisEnd} project. All our code is stored here. Feel free to explore and take a closer look at folders and files.%{lineBreak}%{lineBreak}Above the file structure you can see the latest commit, who the author is and the status of the CI/CD pipeline.%{lineBreak}%{lineBreak}If you scroll down below the file structure, you'll find the Readme of this project. This is defined in the README.md file at the root of the repository."
+msgid "UserList|Delete %{name}?"
msgstr ""
-msgid "UserOnboardingTour|Welcome to the project overview of the %{emphasisStart}%{projectName}%{emphasisEnd} project. This is the project that we use to work on GitLab. At first, a project seems like a simple repository, but at GitLab, a project is so much more.%{lineBreak}%{lineBreak}You can create projects for hosting your codebase, use it as an issue tracker, collaborate on code, and continuously build, test, and deploy your app with built-in GitLab CI/CD."
+msgid "UserList|created %{timeago}"
msgstr ""
msgid "UserProfile|Activity"
@@ -24971,6 +25636,9 @@ msgstr ""
msgid "UsersSelect|Unassigned"
msgstr ""
+msgid "Using %{codeStart}needs%{codeEnd} allows jobs to run before their stage is reached, as soon as their individual dependencies are met, which speeds up your pipelines."
+msgstr ""
+
msgid "Using %{code_start}::%{code_end} denotes a %{link_start}scoped label set%{link_end}"
msgstr ""
@@ -25055,9 +25723,6 @@ msgstr ""
msgid "Versions"
msgstr ""
-msgid "Very helpful"
-msgstr ""
-
msgid "View Documentation"
msgstr ""
@@ -25214,6 +25879,9 @@ msgstr ""
msgid "VisibilityLevel|Unknown"
msgstr ""
+msgid "Visit settings page"
+msgstr ""
+
msgid "VisualReviewApp|%{stepStart}Step 1%{stepEnd}. Copy the following script:"
msgstr ""
@@ -25307,6 +25975,9 @@ msgstr ""
msgid "VulnerabilityManagement|Something went wrong while trying to delete the comment. Please try again later."
msgstr ""
+msgid "VulnerabilityManagement|Something went wrong while trying to refresh the vulnerability. Please try again later."
+msgstr ""
+
msgid "VulnerabilityManagement|Something went wrong while trying to retrieve the vulnerability history. Please try again later."
msgstr ""
@@ -25349,6 +26020,9 @@ msgstr ""
msgid "Vulnerability|Class"
msgstr ""
+msgid "Vulnerability|Crash Address"
+msgstr ""
+
msgid "Vulnerability|Description"
msgstr ""
@@ -25424,9 +26098,6 @@ msgstr ""
msgid "We couldn't reach the Prometheus server. Either the server no longer exists or the configuration details need updating."
msgstr ""
-msgid "We created a short guided tour that will help you learn the basics of GitLab and how it will help you be better at your job. It should only take a couple of minutes. You will be guided by two types of helpers, best recognized by their color."
-msgstr ""
-
msgid "We detected potential spam in the %{humanized_resource_name}. Please solve the reCAPTCHA to proceed."
msgstr ""
@@ -25460,6 +26131,9 @@ msgstr ""
msgid "We've found no vulnerabilities"
msgstr ""
+msgid "Web Application Firewall"
+msgstr ""
+
msgid "Web IDE"
msgstr ""
@@ -25520,9 +26194,6 @@ msgstr ""
msgid "Welcome to GitLab.com<br>@%{name}!"
msgstr ""
-msgid "Welcome to the Guided GitLab Tour"
-msgstr ""
-
msgid "Welcome to the guided GitLab tour"
msgstr ""
@@ -25574,9 +26245,6 @@ msgstr ""
msgid "While it's rare to have no vulnerabilities, it can happen. In any event, we ask that you please double check your settings to make sure you've set up your dashboard correctly."
msgstr ""
-msgid "White helpers give contextual information."
-msgstr ""
-
msgid "Who can be an approver?"
msgstr ""
@@ -25622,12 +26290,18 @@ msgstr ""
msgid "WikiEmptyIssueMessage|Suggest wiki improvement"
msgstr ""
+msgid "WikiEmptyIssueMessage|You must be a group member in order to add wiki pages. If you have suggestions for how to improve the wiki for this group, consider opening an issue in the %{issues_link}."
+msgstr ""
+
msgid "WikiEmptyIssueMessage|You must be a project member in order to add wiki pages. If you have suggestions for how to improve the wiki for this project, consider opening an issue in the %{issues_link}."
msgstr ""
msgid "WikiEmptyIssueMessage|issue tracker"
msgstr ""
+msgid "WikiEmpty|A wiki is where you can store all the details about your group. This can include why you've created it, its principles, how to use it, and so on."
+msgstr ""
+
msgid "WikiEmpty|A wiki is where you can store all the details about your project. This can include why you've created it, its principles, how to use it, and so on."
msgstr ""
@@ -25637,12 +26311,21 @@ msgstr ""
msgid "WikiEmpty|Suggest wiki improvement"
msgstr ""
+msgid "WikiEmpty|The wiki lets you write documentation for your group"
+msgstr ""
+
msgid "WikiEmpty|The wiki lets you write documentation for your project"
msgstr ""
+msgid "WikiEmpty|This group has no wiki pages"
+msgstr ""
+
msgid "WikiEmpty|This project has no wiki pages"
msgstr ""
+msgid "WikiEmpty|You must be a group member in order to add wiki pages."
+msgstr ""
+
msgid "WikiEmpty|You must be a project member in order to add wiki pages."
msgstr ""
@@ -25919,13 +26602,13 @@ msgstr ""
msgid "You can now export your security dashboard to a CSV report."
msgstr ""
-msgid "You can now submit a merge request to get this change into the original branch."
+msgid "You can now manage alert endpoint configuration in the Alerts section on the Operations settings page. Fields on this page have been deprecated."
msgstr ""
-msgid "You can now submit a merge request to get this change into the original project."
+msgid "You can now submit a merge request to get this change into the original branch."
msgstr ""
-msgid "You can only add files when you are on a branch"
+msgid "You can now submit a merge request to get this change into the original project."
msgstr ""
msgid "You can only edit files when you are on a branch"
@@ -26147,7 +26830,7 @@ msgstr ""
msgid "You need to upload a Google Takeout archive."
msgstr ""
-msgid "You reached %{usage_in_percent} of %{namespace_name}'s capacity (%{used_storage} of %{storage_limit})"
+msgid "You reached %{usage_in_percent} of %{namespace_name}'s storage capacity (%{used_storage} of %{storage_limit})"
msgstr ""
msgid "You tried to fork %{link_to_the_project} but it failed for the following reason:"
@@ -26450,13 +27133,13 @@ msgstr ""
msgid "Your subscription expired!"
msgstr ""
-msgid "Your subscription has been downgraded"
+msgid "Your subscription has been downgraded."
msgstr ""
-msgid "Your subscription will automatically renew in %{remaining_days}"
+msgid "Your subscription will automatically renew in %{remaining_days}."
msgstr ""
-msgid "Your subscription will expire in %{remaining_days}"
+msgid "Your subscription will expire in %{remaining_days}."
msgstr ""
msgid "Zoom meeting added"
@@ -26540,9 +27223,6 @@ msgstr ""
msgid "branch name"
msgstr ""
-msgid "build pipeline reference mismatch"
-msgstr ""
-
msgid "by"
msgstr ""
@@ -26561,6 +27241,9 @@ msgstr ""
msgid "cannot block others"
msgstr ""
+msgid "cannot contain HTML/XML tags, including any word between angle brackets (<,>)."
+msgstr ""
+
msgid "cannot include leading slash or directory traversal."
msgstr ""
@@ -26591,75 +27274,6 @@ msgstr ""
msgid "ciReport|%{remainingPackagesCount} more"
msgstr ""
-msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability"
-msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities"
-msgstr[0] ""
-msgstr[1] ""
-msgstr[2] ""
-msgstr[3] ""
-msgstr[4] ""
-msgstr[5] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability for the source branch only"
-msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities for the source branch only"
-msgstr[0] ""
-msgstr[1] ""
-msgstr[2] ""
-msgstr[3] ""
-msgstr[4] ""
-msgstr[5] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerability"
-msgid_plural "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerabilities"
-msgstr[0] ""
-msgstr[1] ""
-msgstr[2] ""
-msgstr[3] ""
-msgstr[4] ""
-msgstr[5] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new vulnerability"
-msgid_plural "ciReport|%{reportType} %{status} detected %{newCount} new vulnerabilities"
-msgstr[0] ""
-msgstr[1] ""
-msgstr[2] ""
-msgstr[3] ""
-msgstr[4] ""
-msgstr[5] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities for the source branch only"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{fixedCount} fixed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} vulnerability for the source branch only"
-msgid_plural "ciReport|%{reportType} %{status} detected %{newCount} vulnerabilities for the source branch only"
-msgstr[0] ""
-msgstr[1] ""
-msgstr[2] ""
-msgstr[3] ""
-msgstr[4] ""
-msgstr[5] ""
-
-msgid "ciReport|%{reportType} %{status} detected no new vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected no vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected no vulnerabilities for the source branch only"
-msgstr ""
-
msgid "ciReport|%{reportType} is loading"
msgstr ""
@@ -26678,7 +27292,7 @@ msgstr ""
msgid "ciReport|All projects"
msgstr ""
-msgid "ciReport|All report types"
+msgid "ciReport|All scanner types"
msgstr ""
msgid "ciReport|All severities"
@@ -26702,6 +27316,9 @@ msgstr ""
msgid "ciReport|Container scanning detects known vulnerabilities in your docker images."
msgstr ""
+msgid "ciReport|Coverage Fuzzing"
+msgstr ""
+
msgid "ciReport|Create a merge request to implement this solution, or download and apply the patch manually."
msgstr ""
@@ -26864,6 +27481,9 @@ msgstr ""
msgid "customize"
msgstr ""
+msgid "data"
+msgstr ""
+
msgid "date must not be after 9999-12-31"
msgstr ""
@@ -26942,6 +27562,9 @@ msgstr ""
msgid "entries cannot contain HTML tags"
msgstr ""
+msgid "epic"
+msgstr ""
+
msgid "error"
msgstr ""
@@ -26963,9 +27586,6 @@ msgstr ""
msgid "expires on %{timebox_due_date}"
msgstr ""
-msgid "external_url"
-msgstr ""
-
msgid "failed"
msgstr ""
@@ -27005,9 +27625,6 @@ msgstr ""
msgid "from"
msgstr ""
-msgid "geo_node_name"
-msgstr ""
-
msgid "group"
msgstr ""
@@ -27029,9 +27646,6 @@ msgstr ""
msgid "https://your-bitbucket-server"
msgstr ""
-msgid "image"
-msgstr ""
-
msgid "image diff"
msgstr ""
@@ -27179,6 +27793,9 @@ msgstr ""
msgid "load it anyway"
msgstr ""
+msgid "loading"
+msgstr ""
+
msgid "locked by %{path_lock_user_name} %{created_at}"
msgstr ""
@@ -27311,9 +27928,6 @@ msgstr ""
msgid "mrWidget|Deployment statistics are not available currently"
msgstr ""
-msgid "mrWidget|Detect issues before deployment with a CI pipeline that continuously tests your code. We created a quick guide that will show you how to create one. Make your code more secure and more robust in just a minute."
-msgstr ""
-
msgid "mrWidget|Did not close"
msgstr ""
@@ -27476,6 +28090,9 @@ msgstr ""
msgid "mrWidget|To approve this merge request, please enter your password. This project requires all approvals to be authenticated."
msgstr ""
+msgid "mrWidget|Use %{linkStart}CI pipelines to test your code%{linkEnd}, simply add a GitLab CI configuration file to your project. It only takes a minute to make your code more secure and robust."
+msgstr ""
+
msgid "mrWidget|When this merge request is ready, remove the WIP: prefix from the title to allow it to be merged"
msgstr ""
@@ -27512,6 +28129,9 @@ msgstr ""
msgid "must be greater than start date"
msgstr ""
+msgid "must contain only valid frameworks"
+msgstr ""
+
msgid "my-awesome-group"
msgstr ""
@@ -27524,6 +28144,9 @@ msgstr ""
msgid "needs to be between 10 minutes and 1 month"
msgstr ""
+msgid "never"
+msgstr ""
+
msgid "never expires"
msgstr ""
@@ -27551,6 +28174,9 @@ msgstr ""
msgid "nounSeries|%{firstItem} and %{lastItem}"
msgstr ""
+msgid "nounSeries|%{item}"
+msgstr ""
+
msgid "nounSeries|%{item}, %{nextItem}"
msgstr ""
@@ -27707,6 +28333,9 @@ msgstr ""
msgid "revised"
msgstr ""
+msgid "satisfied"
+msgstr ""
+
msgid "score"
msgstr ""
@@ -27737,6 +28366,9 @@ msgstr ""
msgid "severity|Unknown"
msgstr ""
+msgid "should be an array of %{object_name} objects"
+msgstr ""
+
msgid "should be greater than or equal to %{access} inherited membership from group %{group_name}"
msgstr ""
@@ -27791,10 +28423,13 @@ msgstr ""
msgid "suggestPipeline|2/2: Commit your changes"
msgstr ""
-msgid "suggestPipeline|Commit the changes and your pipeline will automatically run for the first time."
+msgid "suggestPipeline|Choose %{boldStart}Code Quality%{boldEnd} to add a pipeline that tests the quality of your code."
msgstr ""
-msgid "suggestPipeline|We recommend the %{boldStart}Code Quality%{boldEnd} template, which will add a report widget to your Merge Requests. This way you’ll learn about code quality degradations much sooner. %{footerStart} Goodbye technical debt! %{footerEnd}"
+msgid "suggestPipeline|The template is ready! You can now commit it to create your first pipeline."
+msgstr ""
+
+msgid "suggestPipeline|We’re adding a GitLab CI configuration file to add a pipeline to the project. You could create it manually, but we recommend that you start with a GitLab template that works out of the box."
msgstr ""
msgid "syntax is correct"
@@ -27854,6 +28489,9 @@ msgstr ""
msgid "uses Kubernetes clusters to deploy your code!"
msgstr ""
+msgid "v%{version} published %{timeAgo}"
+msgstr ""
+
msgid "verify ownership"
msgstr ""
diff --git a/locale/da_DK/gitlab.po b/locale/da_DK/gitlab.po
index 4a82e2d2927..7aa6a586c6d 100644
--- a/locale/da_DK/gitlab.po
+++ b/locale/da_DK/gitlab.po
@@ -2,7 +2,7 @@ msgid ""
msgstr ""
"Project-Id-Version: gitlab-ee\n"
"Report-Msgid-Bugs-To: \n"
-"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Last-Translator: \n"
"Language-Team: Danish\n"
"Language: da_DK\n"
"MIME-Version: 1.0\n"
@@ -10,9 +10,11 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Crowdin-Project: gitlab-ee\n"
+"X-Crowdin-Project-ID: 288872\n"
"X-Crowdin-Language: da\n"
"X-Crowdin-File: /master/locale/gitlab.pot\n"
-"PO-Revision-Date: 2020-06-08 15:15\n"
+"X-Crowdin-File-ID: 6\n"
+"PO-Revision-Date: 2020-07-02 01:25\n"
msgid " %{start} to %{end}"
msgstr ""
@@ -197,6 +199,11 @@ msgid_plural "%d metrics"
msgstr[0] ""
msgstr[1] ""
+msgid "%d milestone"
+msgid_plural "%d milestones"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "%d minute"
msgid_plural "%d minutes"
msgstr[0] ""
@@ -242,11 +249,6 @@ msgid_plural "%d unresolved threads"
msgstr[0] ""
msgstr[1] ""
-msgid "%d url scanned"
-msgid_plural "%d urls scanned"
-msgstr[0] ""
-msgstr[1] ""
-
msgid "%d vulnerability"
msgid_plural "%d vulnerabilities"
msgstr[0] ""
@@ -324,9 +326,15 @@ msgstr[1] ""
msgid "%{count} related %{pluralized_subject}: %{links}"
msgstr ""
+msgid "%{dashboard_path} could not be found."
+msgstr ""
+
msgid "%{days} days until tags are automatically removed"
msgstr ""
+msgid "%{deployLinkStart}Use a template to deploy to ECS%{deployLinkEnd}, or use a docker image to %{commandsLinkStart}run AWS commands in GitLab CI/CD%{commandsLinkEnd}."
+msgstr ""
+
msgid "%{description}- Sentry event: %{errorUrl}- First seen: %{firstSeen}- Last seen: %{lastSeen} %{countLabel}: %{count}%{userCountLabel}: %{userCount}"
msgstr ""
@@ -369,12 +377,18 @@ msgstr ""
msgid "%{host} sign-in from new location"
msgstr ""
-msgid "%{icon}You are about to add %{usersTag} people to the discussion. Proceed with caution."
+msgid "%{icon}You are about to add %{usersTag} people to the discussion. They will all receive a notification."
+msgstr ""
+
+msgid "%{integrations_link_start}Integrations%{link_end} enable you to make third-party applications part of your GitLab workflow. If the available integrations don't meet your needs, consider using a %{webhooks_link_start}webhook%{link_end}."
msgstr ""
msgid "%{issuableType} will be removed! Are you sure?"
msgstr ""
+msgid "%{issuesCount} issues in this group"
+msgstr ""
+
msgid "%{issuesSize} issues"
msgstr ""
@@ -384,7 +398,7 @@ msgstr ""
msgid "%{labelStart}Class:%{labelEnd} %{class}"
msgstr ""
-msgid "%{labelStart}Confidence:%{labelEnd} %{confidence}"
+msgid "%{labelStart}Evidence:%{labelEnd} %{evidence}"
msgstr ""
msgid "%{labelStart}File:%{labelEnd} %{file}"
@@ -402,6 +416,9 @@ msgstr ""
msgid "%{labelStart}Report Type:%{labelEnd} %{reportType}"
msgstr ""
+msgid "%{labelStart}Scanner:%{labelEnd} %{scanner}"
+msgstr ""
+
msgid "%{labelStart}Severity:%{labelEnd} %{severity}"
msgstr ""
@@ -426,9 +443,6 @@ msgstr ""
msgid "%{link_start}Read more%{link_end} about role permissions"
msgstr ""
-msgid "%{link} can be used for binding events when something is happening within the project."
-msgstr ""
-
msgid "%{listToShow}, and %{awardsListLength} more."
msgstr ""
@@ -462,6 +476,9 @@ msgstr ""
msgid "%{name} found %{resultsString}"
msgstr ""
+msgid "%{name} is already being used for another emoji"
+msgstr ""
+
msgid "%{name} is scheduled for %{action}"
msgstr ""
@@ -509,6 +526,9 @@ msgstr ""
msgid "%{primary} (%{secondary})"
msgstr ""
+msgid "%{ref} cannot be added: %{error}"
+msgstr ""
+
msgid "%{releases} release"
msgid_plural "%{releases} releases"
msgstr[0] ""
@@ -517,13 +537,37 @@ msgstr[1] ""
msgid "%{remaining_approvals} left"
msgstr ""
-msgid "%{retryButtonStart}Try again%{retryButtonEnd} or %{newFileButtonStart}attach a new file%{newFileButtonEnd}"
+msgid "%{reportType} %{status} detected %{critical} critical and %{high} high severity vulnerabilities out of %{total}."
+msgstr ""
+
+msgid "%{reportType} %{status} detected %{critical} critical and %{high} high severity vulnerabilities."
+msgstr ""
+
+msgid "%{reportType} %{status} detected %{critical} critical severity vulnerabilities out of %{total}."
+msgstr ""
+
+msgid "%{reportType} %{status} detected %{critical} critical severity vulnerability."
+msgid_plural "%{reportType} %{status} detected %{critical} critical severity vulnerabilities."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{reportType} %{status} detected %{high} high severity vulnerabilities out of %{total}."
msgstr ""
-msgid "%{screenreaderOnlyStart}Keyboard shorcuts%{screenreaderOnlyEnd} Disabled"
+msgid "%{reportType} %{status} detected %{high} high severity vulnerability."
+msgid_plural "%{reportType} %{status} detected %{high} high severity vulnerabilities."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{reportType} %{status} detected %{other} vulnerability."
+msgid_plural "%{reportType} %{status} detected %{other} vulnerabilities."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{reportType} %{status} detected no new vulnerabilities."
msgstr ""
-msgid "%{screenreaderOnlyStart}Keyboard shorcuts%{screenreaderOnlyEnd} Enabled"
+msgid "%{retryButtonStart}Try again%{retryButtonEnd} or %{newFileButtonStart}attach a new file%{newFileButtonEnd}"
msgstr ""
msgid "%{service_title} %{message}."
@@ -620,6 +664,12 @@ msgstr ""
msgid "%{token}..."
msgstr ""
+msgid "%{totalCpu} (%{freeSpacePercentage}%{percentSymbol} free)"
+msgstr ""
+
+msgid "%{totalMemory} (%{freeSpacePercentage}%{percentSymbol} free)"
+msgstr ""
+
msgid "%{totalWeight} total weight"
msgstr ""
@@ -650,6 +700,12 @@ msgstr ""
msgid "%{verb} %{time_spent_value} spent time."
msgstr ""
+msgid "%{webhooks_link_start}%{webhook_type}%{link_end} enable you to send notifications to web applications in response to events in a group or project."
+msgstr ""
+
+msgid "%{webhooks_link_start}%{webhook_type}%{link_end} enable you to send notifications to web applications in response to events in a group or project. We recommend using an %{integrations_link_start}integration%{link_end} in preference to a webhook."
+msgstr ""
+
msgid "'%{level}' is not a valid visibility level"
msgstr ""
@@ -676,9 +732,6 @@ msgstr ""
msgid "(No changes)"
msgstr ""
-msgid "(Show all)"
-msgstr ""
-
msgid "(check progress)"
msgstr ""
@@ -745,6 +798,9 @@ msgstr ""
msgid "0 for unlimited"
msgstr ""
+msgid "0 for unlimited, only effective with remote storage enabled."
+msgstr ""
+
msgid "1 %{type} addition"
msgid_plural "%{count} %{type} additions"
msgstr[0] ""
@@ -892,6 +948,12 @@ msgstr ""
msgid "<code>\"johnsmith@example.com\": \"johnsmith@example.com\"</code> will add \"By <a href=\"#\">johnsmith@example.com</a>\" to all issues and comments originally created by johnsmith@example.com. By default, the email address or username is masked to ensure the user's privacy. Use this option if you want to show the full email address."
msgstr ""
+msgid "<code>Masked</code> to prevent the values from being displayed in job logs (must match certain regexp requirements)."
+msgstr ""
+
+msgid "<code>Protected</code> to expose them to protected branches or tags only."
+msgstr ""
+
msgid "<namespace / project>"
msgstr ""
@@ -919,9 +981,6 @@ msgstr ""
msgid "A .NET Core console application template, customizable for any .NET Core project"
msgstr ""
-msgid "A DAG must have two dependent jobs to be visualized on this tab."
-msgstr ""
-
msgid "A GitBook site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
msgstr ""
@@ -952,6 +1011,12 @@ msgstr ""
msgid "A deleted user"
msgstr ""
+msgid "A file has been changed."
+msgstr ""
+
+msgid "A file was not found."
+msgstr ""
+
msgid "A file with '%{file_name}' already exists in %{branch} branch"
msgstr ""
@@ -1018,7 +1083,7 @@ msgstr ""
msgid "A subscription will trigger a new pipeline on the default branch of this project when a pipeline successfully completes for a new tag on the %{default_branch_docs} of the subscribed project."
msgstr ""
-msgid "A terraform report was generated in your pipelines."
+msgid "A suggestion is not applicable."
msgstr ""
msgid "A user with write access to the source branch selected this option"
@@ -1189,6 +1254,9 @@ msgstr ""
msgid "Action to take when receiving an alert."
msgstr ""
+msgid "Actions"
+msgstr ""
+
msgid "Activate"
msgstr ""
@@ -1245,6 +1313,9 @@ msgstr ""
msgid "Add LICENSE"
msgstr ""
+msgid "Add New Node"
+msgstr ""
+
msgid "Add README"
msgstr ""
@@ -1320,6 +1391,9 @@ msgstr ""
msgid "Add approval rule"
msgstr ""
+msgid "Add approvers"
+msgstr ""
+
msgid "Add bold text"
msgstr ""
@@ -1377,6 +1451,9 @@ msgstr ""
msgid "Add strikethrough text"
msgstr ""
+msgid "Add suggestion to batch"
+msgstr ""
+
msgid "Add system hook"
msgstr ""
@@ -1476,6 +1553,9 @@ msgstr ""
msgid "Admin Note"
msgstr ""
+msgid "Admin Notifications"
+msgstr ""
+
msgid "Admin Overview"
msgstr ""
@@ -1587,6 +1667,9 @@ msgstr ""
msgid "AdminSettings|Integrations configured here will automatically apply to all projects on this instance."
msgstr ""
+msgid "AdminSettings|Moved to integrations"
+msgstr ""
+
msgid "AdminSettings|No required pipeline"
msgstr ""
@@ -1817,12 +1900,18 @@ msgid_plural "Alerts"
msgstr[0] ""
msgstr[1] ""
+msgid "AlertManagement| assign yourself"
+msgstr ""
+
msgid "AlertManagement|Acknowledged"
msgstr ""
msgid "AlertManagement|Alert"
msgstr ""
+msgid "AlertManagement|Alert assignee(s): %{assignees}"
+msgstr ""
+
msgid "AlertManagement|Alert detail"
msgstr ""
@@ -1838,9 +1927,15 @@ msgstr ""
msgid "AlertManagement|All alerts"
msgstr ""
+msgid "AlertManagement|Assign To"
+msgstr ""
+
msgid "AlertManagement|Assign status"
msgstr ""
+msgid "AlertManagement|Assignee"
+msgstr ""
+
msgid "AlertManagement|Assignees"
msgstr ""
@@ -1859,9 +1954,6 @@ msgstr ""
msgid "AlertManagement|Edit"
msgstr ""
-msgid "AlertManagement|End time"
-msgstr ""
-
msgid "AlertManagement|Events"
msgstr ""
@@ -1880,10 +1972,13 @@ msgstr ""
msgid "AlertManagement|More information"
msgstr ""
+msgid "AlertManagement|No Matching Results"
+msgstr ""
+
msgid "AlertManagement|No alert data to display."
msgstr ""
-msgid "AlertManagement|No alerts available to display. If you think you're seeing this message in error, refresh the page."
+msgid "AlertManagement|No alerts available to display. See %{linkStart}enabling alert management%{linkEnd} for more information on adding alerts to the list."
msgstr ""
msgid "AlertManagement|No alerts to display."
@@ -1892,6 +1987,9 @@ msgstr ""
msgid "AlertManagement|None"
msgstr ""
+msgid "AlertManagement|None -"
+msgstr ""
+
msgid "AlertManagement|Open"
msgstr ""
@@ -1928,9 +2026,18 @@ msgstr ""
msgid "AlertManagement|There was an error displaying the alerts. Confirm your endpoint's configuration details to ensure alerts appear."
msgstr ""
+msgid "AlertManagement|There was an error while updating the assignee(s) list. Please try again."
+msgstr ""
+
+msgid "AlertManagement|There was an error while updating the assignee(s) of the alert. Please try again."
+msgstr ""
+
msgid "AlertManagement|There was an error while updating the status of the alert. Please try again."
msgstr ""
+msgid "AlertManagement|This assignee cannot be assigned to this alert."
+msgstr ""
+
msgid "AlertManagement|Tool"
msgstr ""
@@ -2078,6 +2185,9 @@ msgstr ""
msgid "Allow users to request access (if visibility is public or internal)"
msgstr ""
+msgid "Allowed Geo IP"
+msgstr ""
+
msgid "Allowed email domain restriction only permitted for top-level groups"
msgstr ""
@@ -2111,6 +2221,9 @@ msgstr ""
msgid "Amazon Web Services"
msgstr ""
+msgid "Amazon Web Services Logo"
+msgstr ""
+
msgid "Amazon authentication is not %{link_start}correctly configured%{link_end}. Ask your GitLab administrator if you want to use this service."
msgstr ""
@@ -2168,10 +2281,16 @@ msgstr ""
msgid "An error occurred when updating the issue weight"
msgstr ""
+msgid "An error occurred while acknowledging the notification. Refresh the page and try again."
+msgstr ""
+
+msgid "An error occurred while adding approvers"
+msgstr ""
+
msgid "An error occurred while adding formatted title for epic"
msgstr ""
-msgid "An error occurred while checking group path"
+msgid "An error occurred while checking group path. Please refresh and try again."
msgstr ""
msgid "An error occurred while committing your changes."
@@ -2315,7 +2434,7 @@ msgstr ""
msgid "An error occurred while loading milestones"
msgstr ""
-msgid "An error occurred while loading terraform report"
+msgid "An error occurred while loading project creation UI"
msgstr ""
msgid "An error occurred while loading the data. Please try again."
@@ -2369,6 +2488,9 @@ msgstr ""
msgid "An error occurred while rendering preview broadcast message"
msgstr ""
+msgid "An error occurred while rendering the editor"
+msgstr ""
+
msgid "An error occurred while reordering issues."
msgstr ""
@@ -2465,7 +2587,7 @@ msgstr ""
msgid "Analyze your dependencies for known vulnerabilities."
msgstr ""
-msgid "Analyze your source code and git history for secrets"
+msgid "Analyze your source code and git history for secrets."
msgstr ""
msgid "Analyze your source code for known vulnerabilities."
@@ -2579,12 +2701,18 @@ msgstr ""
msgid "Apply suggestion"
msgstr ""
+msgid "Apply suggestions"
+msgstr ""
+
msgid "Apply template"
msgstr ""
msgid "Apply this approval rule to any branch or a specific protected branch."
msgstr ""
+msgid "Applying"
+msgstr ""
+
msgid "Applying a template will replace the existing issue description. Any changes you have made will be lost."
msgstr ""
@@ -2597,12 +2725,18 @@ msgstr ""
msgid "Applying multiple commands"
msgstr ""
-msgid "Applying suggestion"
+msgid "Applying suggestion..."
+msgstr ""
+
+msgid "Applying suggestions..."
msgstr ""
msgid "Approval rules"
msgstr ""
+msgid "Approval rules reset to project defaults"
+msgstr ""
+
msgid "ApprovalRuleRemove|%d member"
msgid_plural "ApprovalRuleRemove|%d members"
msgstr[0] ""
@@ -2656,6 +2790,9 @@ msgstr ""
msgid "Approved"
msgstr ""
+msgid "Approved MRs"
+msgstr ""
+
msgid "Approved by: "
msgstr ""
@@ -2869,6 +3006,9 @@ msgstr ""
msgid "Assign"
msgstr ""
+msgid "Assign Iteration"
+msgstr ""
+
msgid "Assign custom color like #FF0000"
msgstr ""
@@ -2887,6 +3027,9 @@ msgstr ""
msgid "Assign to"
msgstr ""
+msgid "Assign to commenting user"
+msgstr ""
+
msgid "Assign yourself to these issues"
msgstr ""
@@ -2913,6 +3056,9 @@ msgid_plural "%d Assignees"
msgstr[0] ""
msgstr[1] ""
+msgid "Assignee has no permissions"
+msgstr ""
+
msgid "Assignee lists not available with your current license"
msgstr ""
@@ -2922,6 +3068,9 @@ msgstr ""
msgid "Assignee(s)"
msgstr ""
+msgid "Assignees"
+msgstr ""
+
msgid "Assigns %{assignee_users_sentence}."
msgstr ""
@@ -2957,6 +3106,9 @@ msgstr ""
msgid "Audit Events is a way to keep track of important events that happened in GitLab."
msgstr ""
+msgid "Audit Log"
+msgstr ""
+
msgid "AuditEvents|(removed)"
msgstr ""
@@ -2993,6 +3145,9 @@ msgstr ""
msgid "AuditLogs|IP Address"
msgstr ""
+msgid "AuditLogs|Member Events"
+msgstr ""
+
msgid "AuditLogs|No matching %{type} found."
msgstr ""
@@ -3275,9 +3430,6 @@ msgstr ""
msgid "Badges|e.g. %{exampleUrl}"
msgstr ""
-msgid "Badge|New"
-msgstr ""
-
msgid "Balsamiq file could not be loaded."
msgstr ""
@@ -3326,6 +3478,12 @@ msgstr ""
msgid "Below you will find all the groups that are public."
msgstr ""
+msgid "Beta"
+msgstr ""
+
+msgid "Bi-weekly code coverage"
+msgstr ""
+
msgid "Billing"
msgstr ""
@@ -3386,9 +3544,15 @@ msgstr ""
msgid "Bitbucket Server Import"
msgstr ""
+msgid "Bitbucket Server import"
+msgstr ""
+
msgid "Bitbucket import"
msgstr ""
+msgid "Blame"
+msgstr ""
+
msgid "Blocked"
msgstr ""
@@ -3401,9 +3565,6 @@ msgstr ""
msgid "Blog"
msgstr ""
-msgid "Blue helpers indicate an action to be taken."
-msgstr ""
-
msgid "Board name"
msgstr ""
@@ -3602,6 +3763,9 @@ msgstr ""
msgid "Broadcast Message was successfully updated."
msgstr ""
+msgid "Broadcast Messages"
+msgstr ""
+
msgid "Browse Directory"
msgstr ""
@@ -3656,6 +3820,9 @@ msgstr ""
msgid "By %{user_name}"
msgstr ""
+msgid "By URL"
+msgstr ""
+
msgid "By default GitLab sends emails in HTML and plain text formats so mail clients can choose what format to use. Disable this option if you only want to send emails in plain text format."
msgstr ""
@@ -3761,6 +3928,9 @@ msgstr ""
msgid "Can override approvers and approvals required per merge request"
msgstr ""
+msgid "Can't apply as this line has changed or the suggestion already matches its content."
+msgstr ""
+
msgid "Can't create snippet: %{err}"
msgstr ""
@@ -3947,9 +4117,6 @@ msgstr ""
msgid "Changes are still tracked. Useful for cluster/index migrations."
msgstr ""
-msgid "Changes are unknown"
-msgstr ""
-
msgid "Changes suppressed. Click to show."
msgstr ""
@@ -4256,15 +4423,9 @@ msgstr ""
msgid "Choose what content you want to see on a group’s overview page"
msgstr ""
-msgid "Choose which groups you wish to synchronize to this secondary node"
-msgstr ""
-
msgid "Choose which repositories you want to connect and run CI/CD pipelines."
msgstr ""
-msgid "Choose which shards you wish to synchronize to this secondary node"
-msgstr ""
-
msgid "Choose your framework"
msgstr ""
@@ -4562,9 +4723,15 @@ msgstr ""
msgid "Cluster does not exist"
msgstr ""
+msgid "Cluster is required for Stages::ClusterEndpointInserter"
+msgstr ""
+
msgid "Cluster level"
msgstr ""
+msgid "Cluster type must be specificed for Stages::ClusterEndpointInserter"
+msgstr ""
+
msgid "ClusterIntegration| %{custom_domain_start}More information%{custom_domain_end}."
msgstr ""
@@ -4676,7 +4843,7 @@ msgstr ""
msgid "ClusterIntegration|Cert-Manager"
msgstr ""
-msgid "ClusterIntegration|Cert-Manager is a native Kubernetes certificate management controller that helps with issuing certificates. Installing Cert-Manager on your cluster will issue a certificate by %{letsEncrypt} and ensure that certificates are valid and up-to-date."
+msgid "ClusterIntegration|Cert-Manager is a native Kubernetes certificate management controller that helps with issuing certificates. Installing Cert-Manager on your cluster will issue a certificate by %{linkStart}Let's Encrypt%{linkEnd} and ensure that certificates are valid and up-to-date."
msgstr ""
msgid "ClusterIntegration|Certificate Authority bundle (PEM format)"
@@ -4703,9 +4870,6 @@ msgstr ""
msgid "ClusterIntegration|Clear the local cache of namespace and service accounts. This is necessary if your integration has become out of sync. The cache is repopulated during the next CI job that requires namespace and service accounts."
msgstr ""
-msgid "ClusterIntegration|Cloud Run"
-msgstr ""
-
msgid "ClusterIntegration|Cluster being created"
msgstr ""
@@ -4790,7 +4954,7 @@ msgstr ""
msgid "ClusterIntegration|Crossplane"
msgstr ""
-msgid "ClusterIntegration|Crossplane enables declarative provisioning of managed services from your cloud of choice using %{codeStart}kubectl%{codeEnd} or %{gitlabIntegrationLink}. Crossplane runs inside your Kubernetes cluster and supports secure connectivity and secrets management between app containers and the cloud services they depend on."
+msgid "ClusterIntegration|Crossplane enables declarative provisioning of managed services from your cloud of choice using %{codeStart}kubectl%{codeEnd} or %{linkStart}GitLab Integration%{linkEnd}. Crossplane runs inside your Kubernetes cluster and supports secure connectivity and secrets management between app containers and the cloud services they depend on."
msgstr ""
msgid "ClusterIntegration|Deletes all GitLab resources attached to this cluster during removal"
@@ -4874,9 +5038,6 @@ msgstr ""
msgid "ClusterIntegration|GitLab-managed cluster"
msgstr ""
-msgid "ClusterIntegration|Gitlab Integration"
-msgstr ""
-
msgid "ClusterIntegration|Global default"
msgstr ""
@@ -4922,7 +5083,7 @@ msgstr ""
msgid "ClusterIntegration|Ingress gives you a way to route requests to services based on the request host or path, centralizing a number of services into a single entrypoint."
msgstr ""
-msgid "ClusterIntegration|Installing Ingress may incur additional costs. Learn more about %{pricingLink}."
+msgid "ClusterIntegration|Installing Ingress may incur additional costs. Learn more about %{linkStart}pricing%{linkEnd}."
msgstr ""
msgid "ClusterIntegration|Instance cluster"
@@ -4937,7 +5098,7 @@ msgstr ""
msgid "ClusterIntegration|Issuer Email"
msgstr ""
-msgid "ClusterIntegration|Issuers represent a certificate authority. You must provide an email address for your Issuer. "
+msgid "ClusterIntegration|Issuers represent a certificate authority. You must provide an email address for your Issuer."
msgstr ""
msgid "ClusterIntegration|Jupyter Hostname"
@@ -5009,9 +5170,6 @@ msgstr ""
msgid "ClusterIntegration|Learn more about instance Kubernetes clusters"
msgstr ""
-msgid "ClusterIntegration|Let's Encrypt"
-msgstr ""
-
msgid "ClusterIntegration|Loading IAM Roles"
msgstr ""
@@ -5123,7 +5281,7 @@ msgstr ""
msgid "ClusterIntegration|Prometheus"
msgstr ""
-msgid "ClusterIntegration|Prometheus is an open-source monitoring system with %{gitlabIntegrationLink} to monitor deployed applications."
+msgid "ClusterIntegration|Prometheus is an open-source monitoring system with %{linkStart}GitLab Integration%{linkEnd} to monitor deployed applications."
msgstr ""
msgid "ClusterIntegration|Provider details"
@@ -5279,10 +5437,10 @@ msgstr ""
msgid "ClusterIntegration|Select zone to choose machine type"
msgstr ""
-msgid "ClusterIntegration|Send Cilium Logs"
+msgid "ClusterIntegration|Send Container Network Policies Logs"
msgstr ""
-msgid "ClusterIntegration|Send ModSecurity Logs"
+msgid "ClusterIntegration|Send Web Application Firewall Logs"
msgstr ""
msgid "ClusterIntegration|Service Token"
@@ -5450,15 +5608,12 @@ msgstr ""
msgid "ClusterIntegration|documentation"
msgstr ""
-msgid "ClusterIntegration|installed via %{installed_via}"
+msgid "ClusterIntegration|installed via %{linkStart}Cloud Run%{linkEnd}"
msgstr ""
msgid "ClusterIntegration|meets the requirements"
msgstr ""
-msgid "ClusterIntegration|pricing"
-msgstr ""
-
msgid "ClusterIntegration|sign up"
msgstr ""
@@ -5495,6 +5650,15 @@ msgstr ""
msgid "Code"
msgstr ""
+msgid "Code Coverage: %{coveragePercentage}%{percentSymbol}"
+msgstr ""
+
+msgid "Code Coverage| Empty code coverage data"
+msgstr ""
+
+msgid "Code Coverage|Couldn't fetch the code coverage data"
+msgstr ""
+
msgid "Code Owners"
msgstr ""
@@ -5549,7 +5713,7 @@ msgstr ""
msgid "Collapse approvers"
msgstr ""
-msgid "Collapse child epics"
+msgid "Collapse milestones"
msgstr ""
msgid "Collapse replies"
@@ -5567,6 +5731,9 @@ msgstr ""
msgid "Coming soon"
msgstr ""
+msgid "Comma-separated, e.g. '1.1.1.1, 2.2.2.0/24'"
+msgstr ""
+
msgid "Command"
msgstr ""
@@ -5737,6 +5904,12 @@ msgstr ""
msgid "Compliance framework (optional)"
msgstr ""
+msgid "Compliance frameworks"
+msgstr ""
+
+msgid "ComplianceDashboard|created by:"
+msgstr ""
+
msgid "ComplianceFramework|GDPR"
msgstr ""
@@ -5875,6 +6048,9 @@ msgstr ""
msgid "Connection timed out"
msgstr ""
+msgid "Connection timeout"
+msgstr ""
+
msgid "Contact sales to upgrade"
msgstr ""
@@ -5930,9 +6106,6 @@ msgstr ""
msgid "ContainerRegistry|CLI Commands"
msgstr ""
-msgid "ContainerRegistry|Compressed Size"
-msgstr ""
-
msgid "ContainerRegistry|Container Registry"
msgstr ""
@@ -5948,6 +6121,9 @@ msgstr ""
msgid "ContainerRegistry|Copy push command"
msgstr ""
+msgid "ContainerRegistry|Delete selected"
+msgstr ""
+
msgid "ContainerRegistry|Docker connection error"
msgstr ""
@@ -5981,16 +6157,16 @@ msgstr ""
msgid "ContainerRegistry|If you are not already logged in, you need to authenticate to the Container Registry by using your GitLab username and password. If you have %{twofaDocLinkStart}Two-Factor Authentication%{twofaDocLinkEnd} enabled, use a %{personalAccessTokensDocLinkStart}Personal Access Token%{personalAccessTokensDocLinkEnd} instead of a password."
msgstr ""
-msgid "ContainerRegistry|Image ID"
+msgid "ContainerRegistry|Image ID: %{imageId}"
msgstr ""
msgid "ContainerRegistry|Image Repositories"
msgstr ""
-msgid "ContainerRegistry|Keep and protect the images that matter most."
+msgid "ContainerRegistry|Image tags"
msgstr ""
-msgid "ContainerRegistry|Last Updated"
+msgid "ContainerRegistry|Keep and protect the images that matter most."
msgstr ""
msgid "ContainerRegistry|Login"
@@ -6005,6 +6181,9 @@ msgstr ""
msgid "ContainerRegistry|Please contact your administrator."
msgstr ""
+msgid "ContainerRegistry|Published %{timeInfo}"
+msgstr ""
+
msgid "ContainerRegistry|Push an image"
msgstr ""
@@ -6020,9 +6199,6 @@ msgstr ""
msgid "ContainerRegistry|Remove repository"
msgstr ""
-msgid "ContainerRegistry|Remove selected tags"
-msgstr ""
-
msgid "ContainerRegistry|Remove tag"
msgid_plural "ContainerRegistry|Remove tags"
msgstr[0] ""
@@ -6052,9 +6228,6 @@ msgstr ""
msgid "ContainerRegistry|Sorry, your filter produced no results."
msgstr ""
-msgid "ContainerRegistry|Tag"
-msgstr ""
-
msgid "ContainerRegistry|Tag expiration policy"
msgstr ""
@@ -6199,21 +6372,6 @@ msgstr ""
msgid "Control the display of third party offers."
msgstr ""
-msgid "Control the maximum concurrency of LFS/attachment backfill for this secondary node"
-msgstr ""
-
-msgid "Control the maximum concurrency of container repository operations for this Geo node"
-msgstr ""
-
-msgid "Control the maximum concurrency of repository backfill for this secondary node"
-msgstr ""
-
-msgid "Control the maximum concurrency of verification operations for this Geo node"
-msgstr ""
-
-msgid "Control the minimum interval in days that a repository should be reverified for this primary node"
-msgstr ""
-
msgid "Cookie domain"
msgstr ""
@@ -6355,6 +6513,9 @@ msgstr ""
msgid "Could not find design."
msgstr ""
+msgid "Could not find iteration"
+msgstr ""
+
msgid "Could not remove the trigger."
msgstr ""
@@ -6391,6 +6552,12 @@ msgstr ""
msgid "Coverage"
msgstr ""
+msgid "Coverage Fuzzing"
+msgstr ""
+
+msgid "Crash State"
+msgstr ""
+
msgid "Create"
msgstr ""
@@ -6439,6 +6606,9 @@ msgstr ""
msgid "Create a personal access token on your account to pull or push via %{protocol}."
msgstr ""
+msgid "Create a project pre-populated with the necessary files to get you started quickly."
+msgstr ""
+
msgid "Create an account using:"
msgstr ""
@@ -6607,6 +6777,9 @@ msgstr ""
msgid "Creates branch '%{branch_name}' and a merge request to resolve this issue."
msgstr ""
+msgid "Creating"
+msgstr ""
+
msgid "Creating epic"
msgstr ""
@@ -6658,7 +6831,10 @@ msgstr ""
msgid "Current vulnerabilities count"
msgstr ""
-msgid "CurrentUser|Buy CI minutes"
+msgid "CurrentUser|Buy Pipeline minutes"
+msgstr ""
+
+msgid "CurrentUser|One of your groups is running out"
msgstr ""
msgid "CurrentUser|Profile"
@@ -6673,6 +6849,9 @@ msgstr ""
msgid "CurrentUser|Upgrade"
msgstr ""
+msgid "Custom Attributes"
+msgstr ""
+
msgid "Custom CI configuration path"
msgstr ""
@@ -6936,12 +7115,18 @@ msgstr ""
msgid "DAG"
msgstr ""
+msgid "DAG visualization requires at least 3 dependent jobs."
+msgstr ""
+
msgid "DNS"
msgstr ""
msgid "Dashboard"
msgstr ""
+msgid "Dashboard uid not found"
+msgstr ""
+
msgid "DashboardProjects|All"
msgstr ""
@@ -6966,6 +7151,9 @@ msgstr ""
msgid "Data is still calculating..."
msgstr ""
+msgid "Datasource name not found"
+msgstr ""
+
msgid "Date"
msgstr ""
@@ -7146,6 +7334,9 @@ msgstr ""
msgid "Delete this attachment"
msgstr ""
+msgid "Delete user list"
+msgstr ""
+
msgid "Delete variable"
msgstr ""
@@ -7462,6 +7653,9 @@ msgstr ""
msgid "Deploying to"
msgstr ""
+msgid "Deploying to AWS is easy with GitLab"
+msgstr ""
+
msgid "Deployment Frequency"
msgstr ""
@@ -7824,6 +8018,9 @@ msgstr ""
msgid "Dismissed on pipeline %{pipelineLink} at %{projectLink}"
msgstr ""
+msgid "Display alerts from all your monitoring tools directly within GitLab."
+msgstr ""
+
msgid "Display name"
msgstr ""
@@ -7881,9 +8078,6 @@ msgstr ""
msgid "Don't show again"
msgstr ""
-msgid "Don't worry, you can access this tour by clicking on the help icon in the top right corner and choose <strong>Learn GitLab</strong>."
-msgstr ""
-
msgid "Done"
msgstr ""
@@ -8010,6 +8204,9 @@ msgstr ""
msgid "Edit Release"
msgstr ""
+msgid "Edit Slack integration"
+msgstr ""
+
msgid "Edit Snippet"
msgstr ""
@@ -8049,6 +8246,9 @@ msgstr ""
msgid "Edit issues"
msgstr ""
+msgid "Edit iteration"
+msgstr ""
+
msgid "Edit public deploy key"
msgstr ""
@@ -8097,6 +8297,9 @@ msgstr ""
msgid "Email"
msgstr ""
+msgid "Email Notification"
+msgstr ""
+
msgid "Email address"
msgstr ""
@@ -8109,6 +8312,9 @@ msgstr ""
msgid "Email not verified. Please verify your email in Salesforce."
msgstr ""
+msgid "Email notification for unknown sign-ins"
+msgstr ""
+
msgid "Email patch"
msgstr ""
@@ -8340,6 +8546,9 @@ msgstr ""
msgid "Enforce DNS rebinding attack protection"
msgstr ""
+msgid "Enforce personal access token expiration"
+msgstr ""
+
msgid "Ensure connectivity is available from the GitLab server to the Prometheus server"
msgstr ""
@@ -8409,6 +8618,9 @@ msgstr ""
msgid "Enter the name of your application, and we'll return a unique %{type}."
msgstr ""
+msgid "Enter weights for storages for new repositories."
+msgstr ""
+
msgid "Enter your password to approve"
msgstr ""
@@ -8418,15 +8630,27 @@ msgstr ""
msgid "Environment does not have deployments"
msgstr ""
+msgid "Environment is required for Stages::MetricEndpointInserter"
+msgstr ""
+
+msgid "Environment is required for Stages::VariableEndpointInserter"
+msgstr ""
+
msgid "Environment scope"
msgstr ""
+msgid "Environment variables are applied to all project environments in this instance via the Runner. You can use environment variables for passwords, secret keys, etc. Make variables available to the running application by prepending the variable key with <code>K8S_SECRET_</code>. You can set variables to be:"
+msgstr ""
+
msgid "Environment variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. Additionally, they can be masked so they are hidden in job logs, though they must match certain regexp requirements to do so. You can use environment variables for passwords, secret keys, or whatever you want."
msgstr ""
msgid "Environment variables are configured by your administrator to be %{link_start}protected%{link_end} by default"
msgstr ""
+msgid "Environment variables on this GitLab instance are configured to be %{link_start}protected%{link_end} by default"
+msgstr ""
+
msgid "Environment:"
msgstr ""
@@ -8958,6 +9182,9 @@ msgstr ""
msgid "EventFilterBy|Filter by comments"
msgstr ""
+msgid "EventFilterBy|Filter by designs"
+msgstr ""
+
msgid "EventFilterBy|Filter by epic events"
msgstr ""
@@ -9090,15 +9317,15 @@ msgstr ""
msgid "Expand approvers"
msgstr ""
-msgid "Expand child epics"
-msgstr ""
-
msgid "Expand down"
msgstr ""
msgid "Expand dropdown"
msgstr ""
+msgid "Expand milestones"
+msgstr ""
+
msgid "Expand sidebar"
msgstr ""
@@ -9114,6 +9341,9 @@ msgstr ""
msgid "Expiration date"
msgstr ""
+msgid "Expiration not enforced"
+msgstr ""
+
msgid "Expiration policy for the Container Registry is a perfect solution for keeping the Registry space down while still enjoying the full power of GitLab CI/CD."
msgstr ""
@@ -9234,6 +9464,9 @@ msgstr ""
msgid "Failed Jobs"
msgstr ""
+msgid "Failed on"
+msgstr ""
+
msgid "Failed to add a Zoom meeting"
msgstr ""
@@ -9294,13 +9527,19 @@ msgstr ""
msgid "Failed to install."
msgstr ""
+msgid "Failed to load assignees. Please try again."
+msgstr ""
+
+msgid "Failed to load authors. Please try again."
+msgstr ""
+
msgid "Failed to load emoji list."
msgstr ""
msgid "Failed to load error details from Sentry."
msgstr ""
-msgid "Failed to load errors from Sentry. Error message: %{errorMessage}"
+msgid "Failed to load errors from Sentry."
msgstr ""
msgid "Failed to load group activity metrics. Please try again."
@@ -9381,6 +9620,9 @@ msgstr ""
msgid "Failed to set due date because the date format is invalid."
msgstr ""
+msgid "Failed to set iteration on this issue. Please try again."
+msgstr ""
+
msgid "Failed to signing using smartcard authentication"
msgstr ""
@@ -9603,13 +9845,10 @@ msgstr ""
msgid "FeatureFlags|Target environments"
msgstr ""
-msgid "FeatureFlags|There are no active feature flags"
-msgstr ""
-
-msgid "FeatureFlags|There are no inactive feature flags"
+msgid "FeatureFlags|There was an error fetching the feature flags."
msgstr ""
-msgid "FeatureFlags|There was an error fetching the feature flags."
+msgid "FeatureFlags|There was an error retrieving user lists"
msgstr ""
msgid "FeatureFlags|Try again in a few moments or contact your support team."
@@ -9621,9 +9860,18 @@ msgstr ""
msgid "FeatureFlag|Delete strategy"
msgstr ""
+msgid "FeatureFlag|List"
+msgstr ""
+
msgid "FeatureFlag|Percentage"
msgstr ""
+msgid "FeatureFlag|Select a user list"
+msgstr ""
+
+msgid "FeatureFlag|There are no configured user lists"
+msgstr ""
+
msgid "FeatureFlag|Type"
msgstr ""
@@ -9780,6 +10028,12 @@ msgstr ""
msgid "Filter..."
msgstr ""
+msgid "Find File"
+msgstr ""
+
+msgid "Find bugs in your code with coverage-guided fuzzing"
+msgstr ""
+
msgid "Find by path"
msgstr ""
@@ -10044,6 +10298,9 @@ msgstr ""
msgid "Geo allows you to replicate your GitLab instance to other geographical locations."
msgstr ""
+msgid "Geo nodes are paused using a command run on the node"
+msgstr ""
+
msgid "GeoNodeStatusEvent|%{timeAgoStr} (%{pendingEvents} events)"
msgstr ""
@@ -10128,6 +10385,9 @@ msgstr ""
msgid "GeoNodes|Not checksummed"
msgstr ""
+msgid "GeoNodes|Package files"
+msgstr ""
+
msgid "GeoNodes|Pausing replication stops the sync process. Are you sure?"
msgstr ""
@@ -10146,6 +10406,9 @@ msgstr ""
msgid "GeoNodes|Replication slots"
msgstr ""
+msgid "GeoNodes|Replication status"
+msgstr ""
+
msgid "GeoNodes|Repositories"
msgstr ""
@@ -10356,6 +10619,9 @@ msgstr ""
msgid "Geo|Synchronization failed - %{error}"
msgstr ""
+msgid "Geo|Synchronization of %{itemTitle} is disabled."
+msgstr ""
+
msgid "Geo|The database is currently %{db_lag} behind the primary node."
msgstr ""
@@ -10668,6 +10934,9 @@ msgstr ""
msgid "Go to %{link_to_google_takeout}."
msgstr ""
+msgid "Go to <strong>Issues</strong> > <strong>Boards</strong> to access your personalized learning issue board."
+msgstr ""
+
msgid "Go to Pipelines"
msgstr ""
@@ -10767,9 +11036,6 @@ msgstr ""
msgid "Go to your snippets"
msgstr ""
-msgid "Golden Tanuki"
-msgstr ""
-
msgid "Google Cloud Platform"
msgstr ""
@@ -10791,6 +11057,9 @@ msgstr ""
msgid "Grafana URL"
msgstr ""
+msgid "Grafana response contains invalid json"
+msgstr ""
+
msgid "GrafanaIntegration|API Token"
msgstr ""
@@ -10839,9 +11108,6 @@ msgstr ""
msgid "Group %{group_name} was successfully created."
msgstr ""
-msgid "Group '%{group_name}' is being imported."
-msgstr ""
-
msgid "Group Audit Events"
msgstr ""
@@ -10878,9 +11144,6 @@ msgstr ""
msgid "Group by:"
msgstr ""
-msgid "Group could not be imported: %{errors}"
-msgstr ""
-
msgid "Group description"
msgstr ""
@@ -10908,9 +11171,15 @@ msgstr ""
msgid "Group has not been marked for deletion"
msgstr ""
+msgid "Group import could not be scheduled"
+msgstr ""
+
msgid "Group info:"
msgstr ""
+msgid "Group is required when cluster_type is :group"
+msgstr ""
+
msgid "Group maintainers can register group runners in the %{link}"
msgstr ""
@@ -10974,6 +11243,24 @@ msgstr ""
msgid "GroupActivyMetrics|Recent activity (last 90 days)"
msgstr ""
+msgid "GroupImport|Failed to import group."
+msgstr ""
+
+msgid "GroupImport|Group '%{group_name}' is being imported."
+msgstr ""
+
+msgid "GroupImport|Group could not be imported: %{errors}"
+msgstr ""
+
+msgid "GroupImport|Please wait while we import the group for you. Refresh at will."
+msgstr ""
+
+msgid "GroupImport|The group was successfully imported."
+msgstr ""
+
+msgid "GroupImport|Unable to process group import file"
+msgstr ""
+
msgid "GroupRoadmap|%{dateWord} – No end date"
msgstr ""
@@ -11241,6 +11528,9 @@ msgstr ""
msgid "Groups (%{groups})"
msgstr ""
+msgid "Groups and projects"
+msgstr ""
+
msgid "Groups can also be nested by creating %{subgroup_docs_link_start}subgroups%{subgroup_docs_link_end}."
msgstr ""
@@ -11286,6 +11576,33 @@ msgstr ""
msgid "GroupsEmptyState|You can manage your group member’s permissions and access to each project in the group."
msgstr ""
+msgid "GroupsNew|Contact an administrator to enable options for importing your group."
+msgstr ""
+
+msgid "GroupsNew|Create"
+msgstr ""
+
+msgid "GroupsNew|Create group"
+msgstr ""
+
+msgid "GroupsNew|GitLab group export"
+msgstr ""
+
+msgid "GroupsNew|Import"
+msgstr ""
+
+msgid "GroupsNew|Import group"
+msgstr ""
+
+msgid "GroupsNew|My Awesome Group"
+msgstr ""
+
+msgid "GroupsNew|No import options available"
+msgstr ""
+
+msgid "GroupsNew|To copy a GitLab group between installations, navigate to the group settings page for the original installation, generate an export file, and upload it here."
+msgstr ""
+
msgid "GroupsTree|Are you sure you want to leave the \"%{fullName}\" group?"
msgstr ""
@@ -11388,6 +11705,9 @@ msgstr ""
msgid "Helps reduce request volume for protected paths"
msgstr ""
+msgid "Here are all your projects in your group, including the one you just created. To start, let’s take a look at your personalized learning project which will help you learn about GitLab at your own pace."
+msgstr ""
+
msgid "Here you will find recent merge request activity"
msgstr ""
@@ -11402,6 +11722,9 @@ msgid_plural "Hide charts"
msgstr[0] ""
msgstr[1] ""
+msgid "Hide details"
+msgstr ""
+
msgid "Hide file browser"
msgstr ""
@@ -11411,6 +11734,9 @@ msgstr ""
msgid "Hide host keys manual input"
msgstr ""
+msgid "Hide list"
+msgstr ""
+
msgid "Hide marketing-related entries from help"
msgstr ""
@@ -11431,9 +11757,6 @@ msgstr[1] ""
msgid "Hide values"
msgstr ""
-msgid "Hiding all labels"
-msgstr ""
-
msgid "High or unknown vulnerabilities present"
msgstr ""
@@ -11518,6 +11841,9 @@ msgstr ""
msgid "ID:"
msgstr ""
+msgid "IDE"
+msgstr ""
+
msgid "IDE|Allow live previews of JavaScript projects in the Web IDE using CodeSandbox Live Preview."
msgstr ""
@@ -11608,10 +11934,10 @@ msgstr ""
msgid "If enabled"
msgstr ""
-msgid "If enabled, access to projects will be validated on an external service using their classification label."
+msgid "If enabled, GitLab will handle Object Storage replication using Geo. %{linkStart}More information%{linkEnd}"
msgstr ""
-msgid "If enabled, and if object storage is enabled, GitLab will handle Object Storage replication using Geo"
+msgid "If enabled, access to projects will be validated on an external service using their classification label."
msgstr ""
msgid "If there is no previous license or if the previous license has expired, some GitLab functionality will be blocked until a new, valid license is uploaded."
@@ -11973,6 +12299,9 @@ msgid_plural "Instances"
msgstr[0] ""
msgstr[1] ""
+msgid "Instance Configuration"
+msgstr ""
+
msgid "Instance Statistics visibility"
msgstr ""
@@ -11994,9 +12323,6 @@ msgstr ""
msgid "Integrations"
msgstr ""
-msgid "Integrations allow you to integrate GitLab with other applications"
-msgstr ""
-
msgid "Integrations|All details"
msgstr ""
@@ -12108,6 +12434,9 @@ msgstr ""
msgid "Invalid query"
msgstr ""
+msgid "Invalid repository bundle for snippet with id %{snippet_id}"
+msgstr ""
+
msgid "Invalid repository path"
msgstr ""
@@ -12126,6 +12455,9 @@ msgstr ""
msgid "Invalid two-factor code."
msgstr ""
+msgid "Invalid yaml"
+msgstr ""
+
msgid "Invitation"
msgstr ""
@@ -12345,15 +12677,24 @@ msgstr ""
msgid "It seems like the Dependency Scanning job ran successfully, but no dependencies have been detected in your project."
msgstr ""
+msgid "It seems that there is currently no available data for code coverage"
+msgstr ""
+
msgid "It's you"
msgstr ""
+msgid "Iteration"
+msgstr ""
+
msgid "Iteration changed to"
msgstr ""
msgid "Iteration removed"
msgstr ""
+msgid "Iteration updated"
+msgstr ""
+
msgid "Iterations"
msgstr ""
@@ -12384,12 +12725,18 @@ msgstr ""
msgid "January"
msgstr ""
+msgid "Jira Issues"
+msgstr ""
+
msgid "Jira import is already running."
msgstr ""
msgid "Jira integration not configured."
msgstr ""
+msgid "Jira project key is not configured"
+msgstr ""
+
msgid "Jira project: %{importProject}"
msgstr ""
@@ -12570,6 +12917,9 @@ msgstr ""
msgid "Keep divergent refs"
msgstr ""
+msgid "Kerberos access denied"
+msgstr ""
+
msgid "Key"
msgstr ""
@@ -12579,10 +12929,13 @@ msgstr ""
msgid "Key: %{key}"
msgstr ""
-msgid "Keyboard Shortcuts"
+msgid "Keyboard shortcuts"
msgstr ""
-msgid "Keyboard shortcuts"
+msgid "Keys"
+msgstr ""
+
+msgid "Ki"
msgstr ""
msgid "Kubernetes"
@@ -12624,6 +12977,9 @@ msgstr ""
msgid "LDAP"
msgstr ""
+msgid "LDAP Synchronization"
+msgstr ""
+
msgid "LDAP settings"
msgstr ""
@@ -12848,12 +13204,18 @@ msgstr ""
msgid "Learn more about custom project templates"
msgstr ""
+msgid "Learn more about deploying to AWS"
+msgstr ""
+
msgid "Learn more about deploying to a cluster"
msgstr ""
msgid "Learn more about group-level project templates"
msgstr ""
+msgid "Learn more about job dependencies"
+msgstr ""
+
msgid "Learn more about signing commits"
msgstr ""
@@ -12905,9 +13267,21 @@ msgstr ""
msgid "License History"
msgstr ""
+msgid "License ID:"
+msgstr ""
+
+msgid "License URL"
+msgstr ""
+
msgid "License-Check"
msgstr ""
+msgid "LicenseCompliance|%{docLinkStart}License Approvals%{docLinkEnd} are active"
+msgstr ""
+
+msgid "LicenseCompliance|%{docLinkStart}License Approvals%{docLinkEnd} are inactive"
+msgstr ""
+
msgid "LicenseCompliance|Add a license"
msgstr ""
@@ -12929,9 +13303,15 @@ msgstr ""
msgid "LicenseCompliance|Deny"
msgstr ""
+msgid "LicenseCompliance|Learn more about %{linkStart}License Approvals%{linkEnd}"
+msgstr ""
+
msgid "LicenseCompliance|License"
msgstr ""
+msgid "LicenseCompliance|License Approvals"
+msgstr ""
+
msgid "LicenseCompliance|License Compliance detected %d license and policy violation for the source branch only; approval required"
msgid_plural "LicenseCompliance|License Compliance detected %d licenses and policy violations for the source branch only; approval required"
msgstr[0] ""
@@ -13152,6 +13532,9 @@ msgstr ""
msgid "List your Bitbucket Server repositories"
msgstr ""
+msgid "Lists"
+msgstr ""
+
msgid "Live preview"
msgstr ""
@@ -13335,6 +13718,9 @@ msgstr ""
msgid "Manage project labels"
msgstr ""
+msgid "Manage storage usage"
+msgstr ""
+
msgid "Manage two-factor authentication"
msgstr ""
@@ -13350,6 +13736,9 @@ msgstr ""
msgid "Manifest file import"
msgstr ""
+msgid "Manifest import"
+msgstr ""
+
msgid "Manual job"
msgstr ""
@@ -13497,6 +13886,12 @@ msgstr ""
msgid "Maximum field length"
msgstr ""
+msgid "Maximum file size is 2MB. Please select a smaller file."
+msgstr ""
+
+msgid "Maximum import size (MB)"
+msgstr ""
+
msgid "Maximum job timeout"
msgstr ""
@@ -13536,6 +13931,9 @@ msgstr ""
msgid "Maximum size of Elasticsearch bulk indexing requests."
msgstr ""
+msgid "Maximum size of import files."
+msgstr ""
+
msgid "Maximum size of individual attachments in comments."
msgstr ""
@@ -13587,6 +13985,9 @@ msgstr ""
msgid "Merge (when the pipeline succeeds)"
msgstr ""
+msgid "Merge Conflicts"
+msgstr ""
+
msgid "Merge Request"
msgstr ""
@@ -13713,6 +14114,9 @@ msgstr ""
msgid "MergeRequests|Squash task canceled: another squash is already in progress."
msgstr ""
+msgid "MergeRequests|This project does not allow squashing commits when merge requests are accepted."
+msgstr ""
+
msgid "MergeRequests|Thread stays resolved"
msgstr ""
@@ -13866,6 +14270,12 @@ msgstr ""
msgid "MetricsSettings|Add a button to the metrics dashboard linking directly to your existing external dashboard."
msgstr ""
+msgid "MetricsSettings|Choose whether to display dashboard metrics in UTC or the user's local timezone."
+msgstr ""
+
+msgid "MetricsSettings|Dashboard timezone"
+msgstr ""
+
msgid "MetricsSettings|External dashboard URL"
msgstr ""
@@ -13875,6 +14285,12 @@ msgstr ""
msgid "MetricsSettings|Metrics Dashboard"
msgstr ""
+msgid "MetricsSettings|UTC (Coordinated Universal Time)"
+msgstr ""
+
+msgid "MetricsSettings|User's local timezone"
+msgstr ""
+
msgid "Metrics|Add metric"
msgstr ""
@@ -13890,6 +14306,9 @@ msgstr ""
msgid "Metrics|Create metric"
msgstr ""
+msgid "Metrics|Current"
+msgstr ""
+
msgid "Metrics|Delete metric"
msgstr ""
@@ -13940,6 +14359,9 @@ msgstr ""
msgid "Metrics|Max"
msgstr ""
+msgid "Metrics|Min"
+msgstr ""
+
msgid "Metrics|Must be a valid PromQL query."
msgstr ""
@@ -13955,6 +14377,9 @@ msgstr ""
msgid "Metrics|Refresh dashboard"
msgstr ""
+msgid "Metrics|Select a value"
+msgstr ""
+
msgid "Metrics|Star dashboard"
msgstr ""
@@ -13973,12 +14398,18 @@ msgstr ""
msgid "Metrics|There was an error getting annotations information."
msgstr ""
+msgid "Metrics|There was an error getting dashboard validation warnings information."
+msgstr ""
+
msgid "Metrics|There was an error getting deployment information."
msgstr ""
msgid "Metrics|There was an error getting environments information."
msgstr ""
+msgid "Metrics|There was an error getting options for variable \"%{name}\"."
+msgstr ""
+
msgid "Metrics|There was an error trying to validate your query"
msgstr ""
@@ -14021,6 +14452,9 @@ msgstr ""
msgid "Metrics|You're about to permanently delete this metric. This cannot be undone."
msgstr ""
+msgid "Metrics|Your dashboard schema is invalid. Edit the dashboard to correct the YAML schema."
+msgstr ""
+
msgid "Metrics|e.g. HTTP requests"
msgstr ""
@@ -14036,12 +14470,18 @@ msgstr ""
msgid "Metrics|e.g. req/sec"
msgstr ""
+msgid "Mi"
+msgstr ""
+
msgid "Microsoft Azure"
msgstr ""
msgid "Middleman project with Static Site Editor support"
msgstr ""
+msgid "Migrate your data from an external source like GitHub, Bitbucket, or another instance of GitLab."
+msgstr ""
+
msgid "Migrated %{success_count}/%{total_count} files."
msgstr ""
@@ -14176,6 +14616,9 @@ msgstr ""
msgid "Minimum capacity to be available before we schedule more mirrors preemptively."
msgstr ""
+msgid "Minimum interval in days"
+msgstr ""
+
msgid "Minimum length is %{minimum_password_length} characters"
msgstr ""
@@ -14362,6 +14805,12 @@ msgstr ""
msgid "Multiple uploaders found: %{uploader_types}"
msgstr ""
+msgid "Must match with the %{codeStart}external_url%{codeEnd} in %{codeStart}/etc/gitlab/gitlab.rb%{codeEnd}."
+msgstr ""
+
+msgid "Must match with the %{codeStart}geo_node_name%{codeEnd} in %{codeStart}/etc/gitlab/gitlab.rb%{codeEnd}. %{linkStart}More information%{linkEnd}"
+msgstr ""
+
msgid "My Awesome Group"
msgstr ""
@@ -14476,10 +14925,16 @@ msgstr ""
msgid "New Application"
msgstr ""
+msgid "New Branch"
+msgstr ""
+
+msgid "New Deploy Key"
+msgstr ""
+
msgid "New Environment"
msgstr ""
-msgid "New Geo Node"
+msgid "New File"
msgstr ""
msgid "New Group"
@@ -14496,15 +14951,15 @@ msgid_plural "New Issues"
msgstr[0] ""
msgstr[1] ""
-msgid "New Iteration"
-msgstr ""
-
msgid "New Jira import"
msgstr ""
msgid "New Label"
msgstr ""
+msgid "New Merge Request"
+msgstr ""
+
msgid "New Milestone"
msgstr ""
@@ -14523,6 +14978,9 @@ msgstr ""
msgid "New Snippet"
msgstr ""
+msgid "New User"
+msgstr ""
+
msgid "New branch"
msgstr ""
@@ -14565,6 +15023,9 @@ msgstr ""
msgid "New issue title"
msgstr ""
+msgid "New iteration"
+msgstr ""
+
msgid "New iteration created"
msgstr ""
@@ -14742,6 +15203,12 @@ msgstr ""
msgid "No grouping"
msgstr ""
+msgid "No iteration"
+msgstr ""
+
+msgid "No iterations to show"
+msgstr ""
+
msgid "No job log"
msgstr ""
@@ -14784,6 +15251,9 @@ msgstr ""
msgid "No other labels with such name or description"
msgstr ""
+msgid "No panels matching properties %{opts}"
+msgstr ""
+
msgid "No parent group"
msgstr ""
@@ -14817,6 +15287,9 @@ msgstr ""
msgid "No schedules"
msgstr ""
+msgid "No stack trace for this error"
+msgstr ""
+
msgid "No starrers matched your search"
msgstr ""
@@ -14829,7 +15302,7 @@ msgstr ""
msgid "No test coverage"
msgstr ""
-msgid "No thanks, don't show this again"
+msgid "No thanks"
msgstr ""
msgid "No vulnerabilities present"
@@ -14844,9 +15317,6 @@ msgstr ""
msgid "No, directly import the existing email addresses and usernames."
msgstr ""
-msgid "No, not interested right now"
-msgstr ""
-
msgid "No. of commits"
msgstr ""
@@ -14892,9 +15362,6 @@ msgstr ""
msgid "Not found."
msgstr ""
-msgid "Not helpful"
-msgstr ""
-
msgid "Not now"
msgstr ""
@@ -15051,6 +15518,9 @@ msgstr ""
msgid "Notifications on"
msgstr ""
+msgid "Notify users by email when sign-in location is not recognized"
+msgstr ""
+
msgid "Nov"
msgstr ""
@@ -15117,9 +15587,6 @@ msgstr ""
msgid "Oh no!"
msgstr ""
-msgid "Ok let's go"
-msgstr ""
-
msgid "Oldest first"
msgstr ""
@@ -15132,16 +15599,49 @@ msgstr ""
msgid "On track"
msgstr ""
+msgid "OnDemandScans|Attached branch"
+msgstr ""
+
+msgid "OnDemandScans|Attached branch is where the scan job runs."
+msgstr ""
+
+msgid "OnDemandScans|Could not run the scan. Please try again."
+msgstr ""
+
msgid "OnDemandScans|Create new DAST scan"
msgstr ""
+msgid "OnDemandScans|DAST will scan the target URL and any discovered sub URLs."
+msgstr ""
+
+msgid "OnDemandScans|New on-demand DAST scan"
+msgstr ""
+
msgid "OnDemandScans|On-demand Scans"
msgstr ""
+msgid "OnDemandScans|On-demand scans run outside the DevOps cycle and find vulnerabilities in your projects. %{learnMoreLinkStart}Learn more%{learnMoreLinkEnd}"
+msgstr ""
+
+msgid "OnDemandScans|Only a passive scan can be performed on demand."
+msgstr ""
+
+msgid "OnDemandScans|Passive DAST Scan"
+msgstr ""
+
+msgid "OnDemandScans|Please enter a valid URL format, ex: http://www.example.com/home"
+msgstr ""
+
+msgid "OnDemandScans|Run this scan"
+msgstr ""
+
+msgid "OnDemandScans|Scan mode"
+msgstr ""
+
msgid "OnDemandScans|Schedule or run scans immediately against target sites. Currently available on-demand scan type: DAST. %{helpLinkStart}More information%{helpLinkEnd}"
msgstr ""
-msgid "Onboarding"
+msgid "OnDemandScans|Target URL"
msgstr ""
msgid "Once imported, repositories can be mirrored over SSH. Read more %{link_start}here%{link_end}."
@@ -15167,6 +15667,9 @@ msgstr ""
msgid "One or more of you personal access tokens were revoked"
msgstr ""
+msgid "One or more of your %{provider} projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
+msgstr ""
+
msgid "One or more of your Bitbucket projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
msgstr ""
@@ -15668,6 +16171,9 @@ msgstr ""
msgid "Parent epic is not present."
msgstr ""
+msgid "Parsing error for param :embed_json. %{message}"
+msgstr ""
+
msgid "Part of merge request changes"
msgstr ""
@@ -15680,6 +16186,9 @@ msgstr ""
msgid "Passed"
msgstr ""
+msgid "Passed on"
+msgstr ""
+
msgid "Password"
msgstr ""
@@ -15764,6 +16273,9 @@ msgstr ""
msgid "Perform common operations on GitLab project"
msgstr ""
+msgid "Performance and resource management"
+msgstr ""
+
msgid "Performance optimization"
msgstr ""
@@ -15971,7 +16483,7 @@ msgstr ""
msgid "Pipelines|Something went wrong while cleaning runners cache."
msgstr ""
-msgid "Pipelines|There are currently no %{scope} pipelines."
+msgid "Pipelines|There are currently no finished pipelines."
msgstr ""
msgid "Pipelines|There are currently no pipelines."
@@ -16082,6 +16594,9 @@ msgstr ""
msgid "Pipeline|Stop pipeline #%{pipelineId}?"
msgstr ""
+msgid "Pipeline|Tag name"
+msgstr ""
+
msgid "Pipeline|Trigger author"
msgstr ""
@@ -16163,12 +16678,18 @@ msgstr ""
msgid "Please check your email (%{email}) to verify that you own this address and unlock the power of CI/CD. Didn't receive it? %{resend_link}. Wrong email address? %{update_link}."
msgstr ""
+msgid "Please choose a file"
+msgstr ""
+
msgid "Please choose a group URL with no special characters."
msgstr ""
msgid "Please complete your profile with email address"
msgstr ""
+msgid "Please convert %{linkStart}them to Git%{linkEnd}, and go through the %{linkToImportFlow} again."
+msgstr ""
+
msgid "Please convert them to %{link_to_git}, and go through the %{link_to_import_flow} again."
msgstr ""
@@ -16226,6 +16747,9 @@ msgstr ""
msgid "Please provide a valid email address."
msgstr ""
+msgid "Please provide attributes to update"
+msgstr ""
+
msgid "Please refer to <a href=\"%{docs_url}\">%{docs_url}</a>"
msgstr ""
@@ -16961,6 +17485,9 @@ msgstr ""
msgid "Project has too many %{label_for_message} to search"
msgstr ""
+msgid "Project is required when cluster_type is :project"
+msgstr ""
+
msgid "Project members"
msgstr ""
@@ -17078,7 +17605,40 @@ msgstr ""
msgid "ProjectService|Comment"
msgstr ""
-msgid "ProjectService|Comment will be posted on each event"
+msgid "ProjectService|Event will be triggered by a push to the repository"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a commit is created/updated"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a confidential issue is created/updated/closed"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a deployment finishes"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a merge request is created/updated/merged"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a new tag is pushed to the repository"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a new, unique alert is recorded"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a pipeline status changes"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a wiki page is created/updated"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when an issue is created/updated/closed"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when someone adds a comment"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when someone adds a comment on a confidential issue"
msgstr ""
msgid "ProjectService|Perform common operations on GitLab project: %{project_name}"
@@ -17129,6 +17689,9 @@ msgstr ""
msgid "ProjectSettings|Enable 'Delete source branch' option by default"
msgstr ""
+msgid "ProjectSettings|Enable merge trains and pipelines for merged results"
+msgstr ""
+
msgid "ProjectSettings|Every merge creates a merge commit"
msgstr ""
@@ -17159,7 +17722,7 @@ msgstr ""
msgid "ProjectSettings|Forks"
msgstr ""
-msgid "ProjectSettings|Git Large File Storage"
+msgid "ProjectSettings|Git Large File Storage (LFS)"
msgstr ""
msgid "ProjectSettings|Internal"
@@ -17195,9 +17758,6 @@ msgstr ""
msgid "ProjectSettings|Merge options"
msgstr ""
-msgid "ProjectSettings|Merge pipelines will try to validate the post-merge result prior to merging"
-msgstr ""
-
msgid "ProjectSettings|Merge requests"
msgstr ""
@@ -17225,6 +17785,9 @@ msgstr ""
msgid "ProjectSettings|Pipelines"
msgstr ""
+msgid "ProjectSettings|Pipelines for merge requests must be enabled in the CI/CD configuration file, or pipelines could be unresolvable or dropped"
+msgstr ""
+
msgid "ProjectSettings|Pipelines must succeed"
msgstr ""
@@ -17252,6 +17815,9 @@ msgstr ""
msgid "ProjectSettings|Show link to create/view merge request when pushing from the command line"
msgstr ""
+msgid "ProjectSettings|Skipped pipelines are considered successful"
+msgstr ""
+
msgid "ProjectSettings|Snippets"
msgstr ""
@@ -17267,6 +17833,9 @@ msgstr ""
msgid "ProjectSettings|These checks must pass before merge requests can be merged"
msgstr ""
+msgid "ProjectSettings|This introduces the risk of merging changes that will not pass the pipeline."
+msgstr ""
+
msgid "ProjectSettings|This setting is applied on the server level and can be overridden by an admin."
msgstr ""
@@ -17444,15 +18013,27 @@ msgstr ""
msgid "ProjectsNew|Blank project"
msgstr ""
+msgid "ProjectsNew|Connect your external repository to GitLab CI/CD."
+msgstr ""
+
msgid "ProjectsNew|Contact an administrator to enable options for importing your project."
msgstr ""
msgid "ProjectsNew|Create"
msgstr ""
+msgid "ProjectsNew|Create a blank project to house your files, plan your work, and collaborate on code, among other things."
+msgstr ""
+
+msgid "ProjectsNew|Create blank project"
+msgstr ""
+
msgid "ProjectsNew|Create from template"
msgstr ""
+msgid "ProjectsNew|Create new project"
+msgstr ""
+
msgid "ProjectsNew|Creating project & repository."
msgstr ""
@@ -17477,6 +18058,9 @@ msgstr ""
msgid "ProjectsNew|Project description %{tag_start}(optional)%{tag_end}"
msgstr ""
+msgid "ProjectsNew|Run CI/CD for external repository"
+msgstr ""
+
msgid "ProjectsNew|Template"
msgstr ""
@@ -17738,6 +18322,9 @@ msgstr ""
msgid "Protected Branch"
msgstr ""
+msgid "Protected Branches"
+msgstr ""
+
msgid "Protected Environment"
msgstr ""
@@ -17750,6 +18337,9 @@ msgstr ""
msgid "Protected Tag"
msgstr ""
+msgid "Protected Tags"
+msgstr ""
+
msgid "Protected branches"
msgstr ""
@@ -17849,6 +18439,9 @@ msgstr ""
msgid "Provider"
msgstr ""
+msgid "Proxy support for this API is not available currently"
+msgstr ""
+
msgid "Pseudonymizer data collection"
msgstr ""
@@ -17873,6 +18466,9 @@ msgstr ""
msgid "Publish to status page"
msgstr ""
+msgid "Published on status page"
+msgstr ""
+
msgid "Publishes this issue to the associated status page."
msgstr ""
@@ -18047,6 +18643,9 @@ msgstr ""
msgid "Recipe"
msgstr ""
+msgid "Reconfigure"
+msgstr ""
+
msgid "Recover hidden stage"
msgstr ""
@@ -18133,6 +18732,9 @@ msgstr ""
msgid "Registration|Your profile"
msgstr ""
+msgid "Regulate approvals by authors/committers, based on compliance frameworks. Can be changed only at the instance level."
+msgstr ""
+
msgid "Rejected (closed)"
msgstr ""
@@ -18183,15 +18785,24 @@ msgstr ""
msgid "ReleaseAssetLinkType|Image"
msgstr ""
+msgid "ReleaseAssetLinkType|Images"
+msgstr ""
+
msgid "ReleaseAssetLinkType|Other"
msgstr ""
msgid "ReleaseAssetLinkType|Package"
msgstr ""
+msgid "ReleaseAssetLinkType|Packages"
+msgstr ""
+
msgid "ReleaseAssetLinkType|Runbook"
msgstr ""
+msgid "ReleaseAssetLinkType|Runbooks"
+msgstr ""
+
msgid "Releases"
msgstr ""
@@ -18204,6 +18815,9 @@ msgstr ""
msgid "Releases documentation"
msgstr ""
+msgid "Releases|New Release"
+msgstr ""
+
msgid "Release|Something went wrong while getting the release details"
msgstr ""
@@ -18276,6 +18890,9 @@ msgstr ""
msgid "Remove fork relationship"
msgstr ""
+msgid "Remove from batch"
+msgstr ""
+
msgid "Remove from board"
msgstr ""
@@ -18465,6 +19082,12 @@ msgstr ""
msgid "Replication"
msgstr ""
+msgid "Replication enabled"
+msgstr ""
+
+msgid "Replication paused"
+msgstr ""
+
msgid "Reply by email"
msgstr ""
@@ -18492,15 +19115,15 @@ msgstr ""
msgid "Reported %{timeAgo} by %{reportedBy}"
msgstr ""
-msgid "Reported Resource Changes: %{addNum} to add, %{changeNum} to change, %{deleteNum} to delete"
-msgstr ""
-
msgid "Reporter"
msgstr ""
msgid "Reporting"
msgstr ""
+msgid "Reports"
+msgstr ""
+
msgid "Reports|%{combinedString} and %{resolvedString}"
msgstr ""
@@ -18539,6 +19162,9 @@ msgstr ""
msgid "Reports|Failure"
msgstr ""
+msgid "Reports|Identifier"
+msgstr ""
+
msgid "Reports|Metrics reports are loading"
msgstr ""
@@ -18551,6 +19177,9 @@ msgstr ""
msgid "Reports|Metrics reports failed loading results"
msgstr ""
+msgid "Reports|Scanner"
+msgstr ""
+
msgid "Reports|Severity"
msgstr ""
@@ -18599,15 +19228,33 @@ msgstr ""
msgid "Repository cleanup has started. You will receive an email once the cleanup operation is complete."
msgstr ""
+msgid "Repository files count over the limit"
+msgstr ""
+
+msgid "Repository has an invalid default branch name."
+msgstr ""
+
+msgid "Repository has more than one branch."
+msgstr ""
+
msgid "Repository has no locks."
msgstr ""
+msgid "Repository has tags."
+msgstr ""
+
msgid "Repository maintenance"
msgstr ""
msgid "Repository mirroring"
msgstr ""
+msgid "Repository must contain at least 1 file."
+msgstr ""
+
+msgid "Repository size is above the limit."
+msgstr ""
+
msgid "Repository static objects"
msgstr ""
@@ -18617,7 +19264,7 @@ msgstr ""
msgid "Repository sync capacity"
msgstr ""
-msgid "Repository: %{counter_repositories} / Wikis: %{counter_wikis} / Build Artifacts: %{counter_build_artifacts} / LFS: %{counter_lfs_objects}"
+msgid "Repository: %{counter_repositories} / Wikis: %{counter_wikis} / Build Artifacts: %{counter_build_artifacts} / LFS: %{counter_lfs_objects} / Snippets: %{counter_snippets}"
msgstr ""
msgid "RepositorySettingsAccessLevel|Select"
@@ -18629,6 +19276,9 @@ msgstr ""
msgid "Request Headers"
msgstr ""
+msgid "Request details"
+msgstr ""
+
msgid "Request parameter %{param} is missing."
msgstr ""
@@ -18735,6 +19385,9 @@ msgstr ""
msgid "Reset template"
msgstr ""
+msgid "Reset to project defaults"
+msgstr ""
+
msgid "Resetting the authorization key for this project will require updating the authorization key in every alert source it is enabled in."
msgstr ""
@@ -18831,9 +19484,6 @@ msgstr ""
msgid "Resume"
msgstr ""
-msgid "Resume replication"
-msgstr ""
-
msgid "Resync"
msgstr ""
@@ -19456,6 +20106,9 @@ msgstr ""
msgid "SecurityReports|False positive"
msgstr ""
+msgid "SecurityReports|Group Security Dashboard"
+msgstr ""
+
msgid "SecurityReports|Hide dismissed"
msgstr ""
@@ -19510,10 +20163,13 @@ msgstr ""
msgid "SecurityReports|Remove project from dashboard"
msgstr ""
-msgid "SecurityReports|Report type"
+msgid "SecurityReports|Return to dashboard"
msgstr ""
-msgid "SecurityReports|Return to dashboard"
+msgid "SecurityReports|Scan details"
+msgstr ""
+
+msgid "SecurityReports|Scanner type"
msgstr ""
msgid "SecurityReports|Security Dashboard"
@@ -19570,6 +20226,9 @@ msgstr ""
msgid "SecurityReports|There was an error while generating the report."
msgstr ""
+msgid "SecurityReports|Unable to add %{invalidProjectsMessage}"
+msgstr ""
+
msgid "SecurityReports|Unable to add %{invalidProjects}"
msgstr ""
@@ -19684,6 +20343,9 @@ msgstr ""
msgid "Select due date"
msgstr ""
+msgid "Select file"
+msgstr ""
+
msgid "Select group or project"
msgstr ""
@@ -19747,9 +20409,6 @@ msgstr ""
msgid "Select the branch you want to set as the default for this project. All merge requests and commits will automatically be made against this branch unless you specify a different one."
msgstr ""
-msgid "Select the configured storage available for new repositories to be placed on."
-msgstr ""
-
msgid "Select the custom project template source group."
msgstr ""
@@ -19942,12 +20601,6 @@ msgstr ""
msgid "Service Desk is enabled but not yet active"
msgstr ""
-msgid "Service Desk is off"
-msgstr ""
-
-msgid "Service Desk is on"
-msgstr ""
-
msgid "Service Templates"
msgstr ""
@@ -20035,6 +20688,15 @@ msgstr ""
msgid "Set the milestone to %{milestone_reference}."
msgstr ""
+msgid "Set the number of concurrent requests this secondary node will make to the primary node while backfilling."
+msgstr ""
+
+msgid "Set the synchronization and verification capacity for the secondary node."
+msgstr ""
+
+msgid "Set the timeout in seconds to send a secondary node status to the primary and IPs allowed for the secondary nodes."
+msgstr ""
+
msgid "Set time estimate"
msgstr ""
@@ -20074,6 +20736,9 @@ msgstr ""
msgid "Set weight to %{weight}."
msgstr ""
+msgid "Set what should be replicated by choosing specific projects or groups by the secondary node."
+msgstr ""
+
msgid "SetPasswordToCloneLink|set a password"
msgstr ""
@@ -20200,6 +20865,9 @@ msgstr ""
msgid "Show complete raw log"
msgstr ""
+msgid "Show details"
+msgstr ""
+
msgid "Show file browser"
msgstr ""
@@ -20209,10 +20877,13 @@ msgstr ""
msgid "Show latest version"
msgstr ""
+msgid "Show list"
+msgstr ""
+
msgid "Show me everything"
msgstr ""
-msgid "Show me how"
+msgid "Show me how to add a pipeline"
msgstr ""
msgid "Show me more advanced stuff"
@@ -20261,9 +20932,6 @@ msgstr ""
msgid "Showing all issues"
msgstr ""
-msgid "Showing all labels"
-msgstr ""
-
msgid "Showing last %{size} of log -"
msgstr ""
@@ -20375,9 +21043,6 @@ msgstr ""
msgid "Skip outdated deployment jobs"
msgstr ""
-msgid "Skip this for now"
-msgstr ""
-
msgid "Skipped"
msgstr ""
@@ -20522,6 +21187,9 @@ msgstr ""
msgid "Something went wrong while adding your award. Please try again."
msgstr ""
+msgid "Something went wrong while applying the batch of suggestions. Please try again."
+msgstr ""
+
msgid "Something went wrong while applying the suggestion. Please try again."
msgstr ""
@@ -20624,10 +21292,13 @@ msgstr ""
msgid "Something went wrong while updating a requirement."
msgstr ""
+msgid "Something went wrong while updating assignees"
+msgstr ""
+
msgid "Something went wrong while updating your list settings"
msgstr ""
-msgid "Something went wrong with your automatic subscription renewal"
+msgid "Something went wrong with your automatic subscription renewal."
msgstr ""
msgid "Something went wrong, unable to add %{project} to dashboard"
@@ -20645,6 +21316,9 @@ msgstr ""
msgid "Something went wrong, unable to search projects"
msgstr ""
+msgid "Something went wrong."
+msgstr ""
+
msgid "Something went wrong. Please try again."
msgstr ""
@@ -20828,6 +21502,9 @@ msgstr ""
msgid "Source code"
msgstr ""
+msgid "Source code (%{fileExtension})"
+msgstr ""
+
msgid "Source is not available"
msgstr ""
@@ -20912,6 +21589,9 @@ msgstr ""
msgid "Stack trace"
msgstr ""
+msgid "Stacktrace snippet"
+msgstr ""
+
msgid "Stage"
msgstr ""
@@ -21017,6 +21697,9 @@ msgstr ""
msgid "Start thread & reopen %{noteable_name}"
msgstr ""
+msgid "Start using Directed Acyclic Graphs (DAG)"
+msgstr ""
+
msgid "Start your Free Gold Trial"
msgstr ""
@@ -21479,6 +22162,21 @@ msgstr ""
msgid "SuggestedColors|Very pale orange"
msgstr ""
+msgid "Suggestion is not applicable as the suggestion was not found."
+msgstr ""
+
+msgid "Suggestions are not applicable as one or more suggestions were not found."
+msgstr ""
+
+msgid "Suggestions are not applicable as their lines cannot overlap."
+msgstr ""
+
+msgid "Suggestions must all be on the same branch."
+msgstr ""
+
+msgid "Suggestions that change line count can't be added to batches, yet."
+msgstr ""
+
msgid "Suggestions:"
msgstr ""
@@ -21521,9 +22219,6 @@ msgstr ""
msgid "Synchronization disabled"
msgstr ""
-msgid "Synchronization of container repositories is disabled."
-msgstr ""
-
msgid "System"
msgstr ""
@@ -21704,6 +22399,34 @@ msgstr ""
msgid "Terms of Service and Privacy Policy"
msgstr ""
+msgid "Terraform|%{number} Terraform report failed to generate"
+msgid_plural "Terraform|%{number} Terraform reports failed to generate"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Terraform|%{number} Terraform report was generated in your pipelines"
+msgid_plural "Terraform|%{number} Terraform reports were generated in your pipelines"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Terraform|A Terraform report failed to generate."
+msgstr ""
+
+msgid "Terraform|A Terraform report was generated in your pipelines."
+msgstr ""
+
+msgid "Terraform|Generating the report caused an error."
+msgstr ""
+
+msgid "Terraform|Reported Resource Changes: %{addNum} to add, %{changeNum} to change, %{deleteNum} to delete"
+msgstr ""
+
+msgid "Terraform|The Terraform report %{name} failed to generate."
+msgstr ""
+
+msgid "Terraform|The Terraform report %{name} was generated in your pipelines."
+msgstr ""
+
msgid "Test"
msgstr ""
@@ -21730,7 +22453,7 @@ msgstr ""
msgid "TestHooks|Ensure the project has CI pipelines."
msgstr ""
-msgid "TestHooks|Ensure the project has at least one commit."
+msgid "TestHooks|Ensure the project has deployments."
msgstr ""
msgid "TestHooks|Ensure the project has issues."
@@ -21831,7 +22554,7 @@ msgstr ""
msgid "The Prometheus server responded with \"bad request\". Please check your queries are correct and are supported in your Prometheus version. %{documentationLink}"
msgstr ""
-msgid "The URL defined on the primary node that secondary nodes should use to contact it. Defaults to URL"
+msgid "The URL defined on the primary node that secondary nodes should use to contact it."
msgstr ""
msgid "The URL to use for connecting to Elasticsearch. Use a comma-separated list to support clustering (e.g., \"http://localhost:9200, http://localhost:9201\")."
@@ -21840,6 +22563,9 @@ msgstr ""
msgid "The X509 Certificate to use when mutual TLS is required to communicate with the external authorization service. If left blank, the server certificate is still validated when accessing over HTTPS."
msgstr ""
+msgid "The above settings apply to all projects with the selected compliance framework(s)."
+msgstr ""
+
msgid "The amount of seconds after which a request to get a secondary node status will time out."
msgstr ""
@@ -22139,9 +22865,6 @@ msgstr ""
msgid "The total stage shows the time it takes between creating an issue and deploying the code to production. The data will be automatically added once you have completed the full idea to production cycle."
msgstr ""
-msgid "The unique identifier for the Geo node. Must match %{geoNodeName} if it is set in gitlab.rb, otherwise it must match %{externalUrl} with a trailing slash"
-msgstr ""
-
msgid "The update action will time out after %{number_of_minutes} minutes. For big repositories, use a clone/push combination."
msgstr ""
@@ -22166,9 +22889,6 @@ msgstr ""
msgid "The user you are trying to deactivate has been active in the past %{minimum_inactive_days} days and cannot be deactivated"
msgstr ""
-msgid "The user-facing URL of the Geo node"
-msgstr ""
-
msgid "The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6."
msgstr ""
@@ -22259,6 +22979,9 @@ msgstr ""
msgid "There was a problem fetching project branches."
msgstr ""
+msgid "There was a problem fetching project tags."
+msgstr ""
+
msgid "There was a problem fetching project users."
msgstr ""
@@ -22310,6 +23033,9 @@ msgstr ""
msgid "There was an error fetching the %{replicableType}"
msgstr ""
+msgid "There was an error fetching the Geo Settings"
+msgstr ""
+
msgid "There was an error fetching the Node's Groups"
msgstr ""
@@ -22430,6 +23156,12 @@ msgstr ""
msgid "This %{issuable} is locked. Only <strong>project members</strong> can comment."
msgstr ""
+msgid "This %{noteableTypeText} is %{confidentialLinkStart}confidential%{linkEnd} and %{lockedLinkStart}locked%{linkEnd}."
+msgstr ""
+
+msgid "This %{noteableTypeText} is locked."
+msgstr ""
+
msgid "This %{viewer} could not be displayed because %{reason}. You can %{options} instead."
msgstr ""
@@ -22448,6 +23180,9 @@ msgstr ""
msgid "This action can lead to data loss. To prevent accidental actions we ask you to confirm your intention."
msgstr ""
+msgid "This also resolves all related threads"
+msgstr ""
+
msgid "This also resolves the discussion"
msgstr ""
@@ -22538,6 +23273,9 @@ msgstr ""
msgid "This epic does not exist or you don't have sufficient permission."
msgstr ""
+msgid "This feature is currently in beta. We invite you to %{linkStart}give feedback%{linkEnd}."
+msgstr ""
+
msgid "This feature requires local storage to be enabled"
msgstr ""
@@ -22571,7 +23309,7 @@ msgstr ""
msgid "This is a Work in Progress"
msgstr ""
-msgid "This is a confidential issue."
+msgid "This is a confidential %{noteableTypeText}."
msgstr ""
msgid "This is a delayed job to run in %{remainingTime}"
@@ -22598,9 +23336,6 @@ msgstr ""
msgid "This is your current session"
msgstr ""
-msgid "This issue is %{confidentialLinkStart}confidential%{linkEnd} and %{lockedLinkStart}locked%{linkEnd}."
-msgstr ""
-
msgid "This issue is confidential"
msgstr ""
@@ -22610,9 +23345,6 @@ msgstr ""
msgid "This issue is in a child epic of the filtered epic"
msgstr ""
-msgid "This issue is locked."
-msgstr ""
-
msgid "This job depends on other jobs with expired/erased artifacts: %{invalid_dependencies}"
msgstr ""
@@ -22712,6 +23444,9 @@ msgstr ""
msgid "This license has already expired."
msgstr ""
+msgid "This link points to external content"
+msgstr ""
+
msgid "This may expose confidential information as the selected fork is in another namespace that can have other members."
msgstr ""
@@ -22739,6 +23474,9 @@ msgstr ""
msgid "This page will be removed in a future release."
msgstr ""
+msgid "This pipeline does not use the %{codeStart}needs%{codeEnd} keyword and can't be represented as a directed acyclic graph."
+msgstr ""
+
msgid "This pipeline makes use of a predefined CI/CD configuration enabled by %{strongStart}Auto DevOps.%{strongEnd}"
msgstr ""
@@ -22967,6 +23705,9 @@ msgstr ""
msgid "Time from last commit to merge"
msgstr ""
+msgid "Time in seconds"
+msgstr ""
+
msgid "Time in seconds GitLab will wait for a response from the external service. When the service does not respond in time, access will be denied."
msgstr ""
@@ -23388,9 +24129,15 @@ msgstr ""
msgid "Total artifacts size: %{total_size}"
msgstr ""
+msgid "Total cores (CPUs)"
+msgstr ""
+
msgid "Total issues"
msgstr ""
+msgid "Total memory (GB)"
+msgstr ""
+
msgid "Total test time for all commits/merges"
msgstr ""
@@ -23592,6 +24339,9 @@ msgstr ""
msgid "U2F only works with HTTPS-enabled websites. Contact your administrator for more details."
msgstr ""
+msgid "UI Development Kit"
+msgstr ""
+
msgid "URL"
msgstr ""
@@ -23664,9 +24414,6 @@ msgstr ""
msgid "Unable to load the merge request widget. Try reloading the page."
msgstr ""
-msgid "Unable to process group import file"
-msgstr ""
-
msgid "Unable to resolve"
msgstr ""
@@ -23682,6 +24429,9 @@ msgstr ""
msgid "Unable to sign you in to the group with SAML due to \"%{reason}\""
msgstr ""
+msgid "Unable to suggest a path. Please refresh and try again."
+msgstr ""
+
msgid "Unable to update label prioritization at this time"
msgstr ""
@@ -23697,6 +24447,9 @@ msgstr ""
msgid "Unarchiving the project will restore people's ability to make changes to it. The repository can be committed to, and issues, comments, and other entities can be created. %{strong_start}Once active, this project shows up in the search and on the dashboard.%{strong_end}"
msgstr ""
+msgid "Unassign from commenting user"
+msgstr ""
+
msgid "Unblock"
msgstr ""
@@ -23772,6 +24525,9 @@ msgstr ""
msgid "Unreachable"
msgstr ""
+msgid "Unrecognized cluster type"
+msgstr ""
+
msgid "Unresolve"
msgstr ""
@@ -23832,6 +24588,9 @@ msgstr ""
msgid "Unsubscribes from this %{quick_action_target}."
msgstr ""
+msgid "Unsupported todo type passed. Supported todo types are: %{todo_types}"
+msgstr ""
+
msgid "Until"
msgstr ""
@@ -23859,6 +24618,9 @@ msgstr ""
msgid "Update approval rule"
msgstr ""
+msgid "Update approvers"
+msgstr ""
+
msgid "Update failed"
msgstr ""
@@ -23868,6 +24630,9 @@ msgstr ""
msgid "Update it"
msgstr ""
+msgid "Update iteration"
+msgstr ""
+
msgid "Update now"
msgstr ""
@@ -23913,7 +24678,7 @@ msgstr ""
msgid "Updated at"
msgstr ""
-msgid "Updated to"
+msgid "Updated to %{linkStart}chart v%{linkEnd}"
msgstr ""
msgid "Updating"
@@ -23943,6 +24708,9 @@ msgstr ""
msgid "Upload CSV file"
msgstr ""
+msgid "Upload License"
+msgstr ""
+
msgid "Upload New File"
msgstr ""
@@ -24024,16 +24792,19 @@ msgstr ""
msgid "UsageQuota|Pipelines"
msgstr ""
+msgid "UsageQuota|Purchase more storage"
+msgstr ""
+
msgid "UsageQuota|Repositories"
msgstr ""
msgid "UsageQuota|Repository"
msgstr ""
-msgid "UsageQuota|Storage"
+msgid "UsageQuota|Snippets"
msgstr ""
-msgid "UsageQuota|Storage usage:"
+msgid "UsageQuota|Storage"
msgstr ""
msgid "UsageQuota|This namespace has no projects which use shared runners"
@@ -24066,6 +24837,12 @@ msgstr ""
msgid "UsageQuota|Wikis"
msgstr ""
+msgid "UsageQuota|You used: %{usage} %{limit}"
+msgstr ""
+
+msgid "UsageQuota|out of %{formattedLimit} of your namespace storage"
+msgstr ""
+
msgid "Use %{code_start}::%{code_end} to create a %{link_start}scoped label set%{link_end} (eg. %{code_start}priority::1%{code_end})"
msgstr ""
@@ -24132,6 +24909,9 @@ msgstr ""
msgid "User IDs"
msgstr ""
+msgid "User Lists can only be created and modified with %{linkStart}the API%{linkEnd}"
+msgstr ""
+
msgid "User OAuth applications"
msgstr ""
@@ -24156,6 +24936,9 @@ msgstr ""
msgid "User key was successfully removed."
msgstr ""
+msgid "User list %{name} will be removed. Are you sure?"
+msgstr ""
+
msgid "User map"
msgstr ""
@@ -24177,148 +24960,10 @@ msgstr ""
msgid "User was successfully updated."
msgstr ""
-msgid "UserOnboardingTour|%{activeTour}/%{totalTours}"
-msgstr ""
-
-msgid "UserOnboardingTour|%{completed}/%{total} steps completed"
-msgstr ""
-
-msgid "UserOnboardingTour|%{emphasisStart}Well done!%{emphasisEnd}%{lineBreak}%{lineBreak}That's it for our guided tour, congratulations for making it all the way to the end!%{lineBreak}%{lineBreak}We hope this gave you a good overview of GitLab and how it can help you. We'll now show you how to create your own project and invite your colleagues."
-msgstr ""
-
-msgid "UserOnboardingTour|Adding other members to a project is done through Project Settings. Click on %{emphasisStart}Settings%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Alright, that's it for Commits. Let's take a look at the %{emphasisStart}Branches%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Awesome! Now click on %{emphasisStart}Members%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Click on one of the %{emphasisStart}Compare%{emphasisEnd} buttons to compare a branch to master."
-msgstr ""
-
-msgid "UserOnboardingTour|Click on one of the %{emphasisStart}pipeline IDs%{emphasisEnd} to see the details of a pipeline."
-msgstr ""
-
-msgid "UserOnboardingTour|Click to open the latest commit to see its details."
-msgstr ""
-
-msgid "UserOnboardingTour|Close 'Learn GitLab'"
-msgstr ""
-
-msgid "UserOnboardingTour|Commits are shown in chronological order and can be filtered by the commit message or by the branch."
+msgid "UserList|Delete %{name}?"
msgstr ""
-msgid "UserOnboardingTour|Create a project"
-msgstr ""
-
-msgid "UserOnboardingTour|Exit 'Learn GitLab'"
-msgstr ""
-
-msgid "UserOnboardingTour|Got it"
-msgstr ""
-
-msgid "UserOnboardingTour|Great job! %{clapHands} We hope the tour was helpful and that you learned how to use GitLab.%{lineBreak}%{lineBreak}We'd love to get your feedback on this tour.%{lineBreak}%{lineBreak}%{emphasisStart}How helpful would you say this guided tour was?%{emphasisEnd}%{lineBreak}%{lineBreak}"
-msgstr ""
-
-msgid "UserOnboardingTour|Guided GitLab Tour"
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can compare the changes of this branch to another one. Changes are divided by files so that it's easier to see what was changed where."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can create a project from scratch, start with a template or import a repository from other platforms. Whatever you choose, we'll guide you through the process.%{lineBreak}%{lineBreak}Fill in your new project information and click on %{emphasisStart}Create Project%{emphasisEnd} to progress to the next step."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can see the breakdown of the pipelines: its stages and jobs in each of the stages and their status.%{lineBreak}%{lineBreak}Our CI/CD pipelines are quite complex, most of our users have fewer and simpler pipelines."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can see the current members of the project (just you at the moment) and invite new members.%{lineBreak}%{lineBreak}You can invite multiple members at once (existing GitLab users or invite by email) and you can also set their roles and permissions.%{lineBreak}%{lineBreak}Add a few members and click on %{emphasisStart}Add to project%{emphasisEnd} to complete this step."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can see what changes were made with this commit, on what branch and if there's a related merge request. The status of the pipeline will also show up if CI/CD is set up.%{lineBreak}%{lineBreak}You can also comment on the lines of code that were changed and start a discussion with your colleagues!"
-msgstr ""
-
-msgid "UserOnboardingTour|Here's an overview of branches in the %{emphasisStart}%{projectName}%{emphasisEnd} project. They're split into Active and Stale.%{lineBreak}%{lineBreak}From here, you can create a new merge request from a branch, or compare the branch to any other branch in the project. By default, it will compare it to the master branch."
-msgstr ""
-
-msgid "UserOnboardingTour|Invite colleagues"
-msgstr ""
-
-msgid "UserOnboardingTour|Issues are great for communicating and keeping track of progress in GitLab. These are all issues that are open in the %{emphasisStart}%{projectName}%{emphasisEnd}.%{lineBreak}%{lineBreak}You can help us improve GitLab by contributing work to issues that are labeled <span class=\"badge color-label accept-mr-label\">Accepting merge requests</span>.%{lineBreak}%{lineBreak}This list can be filtered by labels, milestones, assignees, authors... We'll show you how it looks when the list is filtered by a label."
-msgstr ""
-
-msgid "UserOnboardingTour|Learn GitLab"
-msgstr ""
-
-msgid "UserOnboardingTour|Let's take a closer look at a merge request. Click on the title of one."
-msgstr ""
-
-msgid "UserOnboardingTour|Let's take a closer look at all the commits. Click on %{emphasisStart}Commits%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Let's take a closer look at the repository of this project. Click on %{emphasisStart}Repository%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|No thanks"
-msgstr ""
-
-msgid "UserOnboardingTour|Ok, let's go"
-msgstr ""
-
-msgid "UserOnboardingTour|Ok, show me"
-msgstr ""
-
-msgid "UserOnboardingTour|Open one of the issues by clicking on its title."
-msgstr ""
-
-msgid "UserOnboardingTour|Restart this step"
-msgstr ""
-
-msgid "UserOnboardingTour|Skip this step"
-msgstr ""
-
-msgid "UserOnboardingTour|Sweet! Your project was created and is ready to be used.%{lineBreak}%{lineBreak}You can start adding files to the repository or clone it. One last thing we want to show you is how to invite your colleagues to your new project."
-msgstr ""
-
-msgid "UserOnboardingTour|Take a look. Here's a nifty menu for quickly creating issues, merge requests, snippets, projects and groups. Click on it and select \"New project\" from the \"GitLab\" section to get started."
-msgstr ""
-
-msgid "UserOnboardingTour|Thanks for taking the guided tour. Remember, if you want to go through it again, you can start %{emphasisStart}Learn GitLab%{emphasisEnd} in the help menu on the top right."
-msgstr ""
-
-msgid "UserOnboardingTour|Thanks for the feedback! %{thumbsUp}"
-msgstr ""
-
-msgid "UserOnboardingTour|That's it for issues. Let'st take a look at %{emphasisStart}Merge Requests%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|That's it for merge requests. Now for the final part of this guided tour - the %{emphasisStart}CI/CD%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|That's it for the Repository. Let's take a look at the %{emphasisStart}Issues%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|The structure of this page is very similar to issues. Status, description, discussion and the sidebar are all here.%{lineBreak}%{lineBreak}But take a look below the description and you'll notice that there's more information about the merge request, the CI/CD pipeline and the options for approving it.%{lineBreak}%{lineBreak}Alongside the discussion you can also see more information about commits in this merge request, the status of pipelines and review all changes that were made."
-msgstr ""
-
-msgid "UserOnboardingTour|There's a lot of information here but don't worry, we'll go through it.%{lineBreak}%{lineBreak}On the top you can see the status of the issue and when it was opened and by whom. Directly below it is the issue description and below that are other %{emphasisStart}related issues%{emphasisEnd} and %{emphasisStart}merge requests%{emphasisEnd} (if any). Then below that is the %{emphasisStart}discussion%{emphasisEnd}, that's where most of the communication happens.%{lineBreak}%{lineBreak}On the right, there's a sidebar where you can view/change the %{emphasisStart}assignee, milestone, due date, labels, weight%{emphasisEnd}, etc."
-msgstr ""
-
-msgid "UserOnboardingTour|These are all the CI/CD pipelines we have for our %{emphasisStart}%{projectName}%{emphasisEnd} project.%{lineBreak}%{lineBreak}Here you can see the status of each pipeline, for what commit it's running for, its stages and the status for them."
-msgstr ""
-
-msgid "UserOnboardingTour|These are all the issues that are available for community contributions. Let's take a closer look at one of them."
-msgstr ""
-
-msgid "UserOnboardingTour|This is an overview of all merge requests in this project. Similarly to the issues overview it can be filtered down by things like labels, milestones, authors, assignees, etc."
-msgstr ""
-
-msgid "UserOnboardingTour|This is the repository for the %{emphasisStart}%{projectName}%{emphasisEnd} project. All our code is stored here. Feel free to explore and take a closer look at folders and files.%{lineBreak}%{lineBreak}Above the file structure you can see the latest commit, who the author is and the status of the CI/CD pipeline.%{lineBreak}%{lineBreak}If you scroll down below the file structure, you'll find the Readme of this project. This is defined in the README.md file at the root of the repository."
-msgstr ""
-
-msgid "UserOnboardingTour|Welcome to the project overview of the %{emphasisStart}%{projectName}%{emphasisEnd} project. This is the project that we use to work on GitLab. At first, a project seems like a simple repository, but at GitLab, a project is so much more.%{lineBreak}%{lineBreak}You can create projects for hosting your codebase, use it as an issue tracker, collaborate on code, and continuously build, test, and deploy your app with built-in GitLab CI/CD."
+msgid "UserList|created %{timeago}"
msgstr ""
msgid "UserProfile|Activity"
@@ -24471,6 +25116,9 @@ msgstr ""
msgid "UsersSelect|Unassigned"
msgstr ""
+msgid "Using %{codeStart}needs%{codeEnd} allows jobs to run before their stage is reached, as soon as their individual dependencies are met, which speeds up your pipelines."
+msgstr ""
+
msgid "Using %{code_start}::%{code_end} denotes a %{link_start}scoped label set%{link_end}"
msgstr ""
@@ -24555,9 +25203,6 @@ msgstr ""
msgid "Versions"
msgstr ""
-msgid "Very helpful"
-msgstr ""
-
msgid "View Documentation"
msgstr ""
@@ -24706,6 +25351,9 @@ msgstr ""
msgid "VisibilityLevel|Unknown"
msgstr ""
+msgid "Visit settings page"
+msgstr ""
+
msgid "VisualReviewApp|%{stepStart}Step 1%{stepEnd}. Copy the following script:"
msgstr ""
@@ -24799,6 +25447,9 @@ msgstr ""
msgid "VulnerabilityManagement|Something went wrong while trying to delete the comment. Please try again later."
msgstr ""
+msgid "VulnerabilityManagement|Something went wrong while trying to refresh the vulnerability. Please try again later."
+msgstr ""
+
msgid "VulnerabilityManagement|Something went wrong while trying to retrieve the vulnerability history. Please try again later."
msgstr ""
@@ -24841,6 +25492,9 @@ msgstr ""
msgid "Vulnerability|Class"
msgstr ""
+msgid "Vulnerability|Crash Address"
+msgstr ""
+
msgid "Vulnerability|Description"
msgstr ""
@@ -24916,9 +25570,6 @@ msgstr ""
msgid "We couldn't reach the Prometheus server. Either the server no longer exists or the configuration details need updating."
msgstr ""
-msgid "We created a short guided tour that will help you learn the basics of GitLab and how it will help you be better at your job. It should only take a couple of minutes. You will be guided by two types of helpers, best recognized by their color."
-msgstr ""
-
msgid "We detected potential spam in the %{humanized_resource_name}. Please solve the reCAPTCHA to proceed."
msgstr ""
@@ -24952,6 +25603,9 @@ msgstr ""
msgid "We've found no vulnerabilities"
msgstr ""
+msgid "Web Application Firewall"
+msgstr ""
+
msgid "Web IDE"
msgstr ""
@@ -25012,9 +25666,6 @@ msgstr ""
msgid "Welcome to GitLab.com<br>@%{name}!"
msgstr ""
-msgid "Welcome to the Guided GitLab Tour"
-msgstr ""
-
msgid "Welcome to the guided GitLab tour"
msgstr ""
@@ -25062,9 +25713,6 @@ msgstr ""
msgid "While it's rare to have no vulnerabilities, it can happen. In any event, we ask that you please double check your settings to make sure you've set up your dashboard correctly."
msgstr ""
-msgid "White helpers give contextual information."
-msgstr ""
-
msgid "Who can be an approver?"
msgstr ""
@@ -25110,12 +25758,18 @@ msgstr ""
msgid "WikiEmptyIssueMessage|Suggest wiki improvement"
msgstr ""
+msgid "WikiEmptyIssueMessage|You must be a group member in order to add wiki pages. If you have suggestions for how to improve the wiki for this group, consider opening an issue in the %{issues_link}."
+msgstr ""
+
msgid "WikiEmptyIssueMessage|You must be a project member in order to add wiki pages. If you have suggestions for how to improve the wiki for this project, consider opening an issue in the %{issues_link}."
msgstr ""
msgid "WikiEmptyIssueMessage|issue tracker"
msgstr ""
+msgid "WikiEmpty|A wiki is where you can store all the details about your group. This can include why you've created it, its principles, how to use it, and so on."
+msgstr ""
+
msgid "WikiEmpty|A wiki is where you can store all the details about your project. This can include why you've created it, its principles, how to use it, and so on."
msgstr ""
@@ -25125,12 +25779,21 @@ msgstr ""
msgid "WikiEmpty|Suggest wiki improvement"
msgstr ""
+msgid "WikiEmpty|The wiki lets you write documentation for your group"
+msgstr ""
+
msgid "WikiEmpty|The wiki lets you write documentation for your project"
msgstr ""
+msgid "WikiEmpty|This group has no wiki pages"
+msgstr ""
+
msgid "WikiEmpty|This project has no wiki pages"
msgstr ""
+msgid "WikiEmpty|You must be a group member in order to add wiki pages."
+msgstr ""
+
msgid "WikiEmpty|You must be a project member in order to add wiki pages."
msgstr ""
@@ -25407,13 +26070,13 @@ msgstr ""
msgid "You can now export your security dashboard to a CSV report."
msgstr ""
-msgid "You can now submit a merge request to get this change into the original branch."
+msgid "You can now manage alert endpoint configuration in the Alerts section on the Operations settings page. Fields on this page have been deprecated."
msgstr ""
-msgid "You can now submit a merge request to get this change into the original project."
+msgid "You can now submit a merge request to get this change into the original branch."
msgstr ""
-msgid "You can only add files when you are on a branch"
+msgid "You can now submit a merge request to get this change into the original project."
msgstr ""
msgid "You can only edit files when you are on a branch"
@@ -25635,7 +26298,7 @@ msgstr ""
msgid "You need to upload a Google Takeout archive."
msgstr ""
-msgid "You reached %{usage_in_percent} of %{namespace_name}'s capacity (%{used_storage} of %{storage_limit})"
+msgid "You reached %{usage_in_percent} of %{namespace_name}'s storage capacity (%{used_storage} of %{storage_limit})"
msgstr ""
msgid "You tried to fork %{link_to_the_project} but it failed for the following reason:"
@@ -25938,13 +26601,13 @@ msgstr ""
msgid "Your subscription expired!"
msgstr ""
-msgid "Your subscription has been downgraded"
+msgid "Your subscription has been downgraded."
msgstr ""
-msgid "Your subscription will automatically renew in %{remaining_days}"
+msgid "Your subscription will automatically renew in %{remaining_days}."
msgstr ""
-msgid "Your subscription will expire in %{remaining_days}"
+msgid "Your subscription will expire in %{remaining_days}."
msgstr ""
msgid "Zoom meeting added"
@@ -26024,9 +26687,6 @@ msgstr ""
msgid "branch name"
msgstr ""
-msgid "build pipeline reference mismatch"
-msgstr ""
-
msgid "by"
msgstr ""
@@ -26045,6 +26705,9 @@ msgstr ""
msgid "cannot block others"
msgstr ""
+msgid "cannot contain HTML/XML tags, including any word between angle brackets (<,>)."
+msgstr ""
+
msgid "cannot include leading slash or directory traversal."
msgstr ""
@@ -26075,55 +26738,6 @@ msgstr ""
msgid "ciReport|%{remainingPackagesCount} more"
msgstr ""
-msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability"
-msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability for the source branch only"
-msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities for the source branch only"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerability"
-msgid_plural "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerabilities"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new vulnerability"
-msgid_plural "ciReport|%{reportType} %{status} detected %{newCount} new vulnerabilities"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities for the source branch only"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{fixedCount} fixed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} vulnerability for the source branch only"
-msgid_plural "ciReport|%{reportType} %{status} detected %{newCount} vulnerabilities for the source branch only"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "ciReport|%{reportType} %{status} detected no new vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected no vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected no vulnerabilities for the source branch only"
-msgstr ""
-
msgid "ciReport|%{reportType} is loading"
msgstr ""
@@ -26142,7 +26756,7 @@ msgstr ""
msgid "ciReport|All projects"
msgstr ""
-msgid "ciReport|All report types"
+msgid "ciReport|All scanner types"
msgstr ""
msgid "ciReport|All severities"
@@ -26166,6 +26780,9 @@ msgstr ""
msgid "ciReport|Container scanning detects known vulnerabilities in your docker images."
msgstr ""
+msgid "ciReport|Coverage Fuzzing"
+msgstr ""
+
msgid "ciReport|Create a merge request to implement this solution, or download and apply the patch manually."
msgstr ""
@@ -26324,6 +26941,9 @@ msgstr ""
msgid "customize"
msgstr ""
+msgid "data"
+msgstr ""
+
msgid "date must not be after 9999-12-31"
msgstr ""
@@ -26394,6 +27014,9 @@ msgstr ""
msgid "entries cannot contain HTML tags"
msgstr ""
+msgid "epic"
+msgstr ""
+
msgid "error"
msgstr ""
@@ -26415,9 +27038,6 @@ msgstr ""
msgid "expires on %{timebox_due_date}"
msgstr ""
-msgid "external_url"
-msgstr ""
-
msgid "failed"
msgstr ""
@@ -26453,9 +27073,6 @@ msgstr ""
msgid "from"
msgstr ""
-msgid "geo_node_name"
-msgstr ""
-
msgid "group"
msgstr ""
@@ -26477,9 +27094,6 @@ msgstr ""
msgid "https://your-bitbucket-server"
msgstr ""
-msgid "image"
-msgstr ""
-
msgid "image diff"
msgstr ""
@@ -26623,6 +27237,9 @@ msgstr ""
msgid "load it anyway"
msgstr ""
+msgid "loading"
+msgstr ""
+
msgid "locked by %{path_lock_user_name} %{created_at}"
msgstr ""
@@ -26751,9 +27368,6 @@ msgstr ""
msgid "mrWidget|Deployment statistics are not available currently"
msgstr ""
-msgid "mrWidget|Detect issues before deployment with a CI pipeline that continuously tests your code. We created a quick guide that will show you how to create one. Make your code more secure and more robust in just a minute."
-msgstr ""
-
msgid "mrWidget|Did not close"
msgstr ""
@@ -26916,6 +27530,9 @@ msgstr ""
msgid "mrWidget|To approve this merge request, please enter your password. This project requires all approvals to be authenticated."
msgstr ""
+msgid "mrWidget|Use %{linkStart}CI pipelines to test your code%{linkEnd}, simply add a GitLab CI configuration file to your project. It only takes a minute to make your code more secure and robust."
+msgstr ""
+
msgid "mrWidget|When this merge request is ready, remove the WIP: prefix from the title to allow it to be merged"
msgstr ""
@@ -26952,6 +27569,9 @@ msgstr ""
msgid "must be greater than start date"
msgstr ""
+msgid "must contain only valid frameworks"
+msgstr ""
+
msgid "my-awesome-group"
msgstr ""
@@ -26964,6 +27584,9 @@ msgstr ""
msgid "needs to be between 10 minutes and 1 month"
msgstr ""
+msgid "never"
+msgstr ""
+
msgid "never expires"
msgstr ""
@@ -26991,6 +27614,9 @@ msgstr ""
msgid "nounSeries|%{firstItem} and %{lastItem}"
msgstr ""
+msgid "nounSeries|%{item}"
+msgstr ""
+
msgid "nounSeries|%{item}, %{nextItem}"
msgstr ""
@@ -27127,6 +27753,9 @@ msgstr ""
msgid "revised"
msgstr ""
+msgid "satisfied"
+msgstr ""
+
msgid "score"
msgstr ""
@@ -27157,6 +27786,9 @@ msgstr ""
msgid "severity|Unknown"
msgstr ""
+msgid "should be an array of %{object_name} objects"
+msgstr ""
+
msgid "should be greater than or equal to %{access} inherited membership from group %{group_name}"
msgstr ""
@@ -27211,10 +27843,13 @@ msgstr ""
msgid "suggestPipeline|2/2: Commit your changes"
msgstr ""
-msgid "suggestPipeline|Commit the changes and your pipeline will automatically run for the first time."
+msgid "suggestPipeline|Choose %{boldStart}Code Quality%{boldEnd} to add a pipeline that tests the quality of your code."
msgstr ""
-msgid "suggestPipeline|We recommend the %{boldStart}Code Quality%{boldEnd} template, which will add a report widget to your Merge Requests. This way you’ll learn about code quality degradations much sooner. %{footerStart} Goodbye technical debt! %{footerEnd}"
+msgid "suggestPipeline|The template is ready! You can now commit it to create your first pipeline."
+msgstr ""
+
+msgid "suggestPipeline|We’re adding a GitLab CI configuration file to add a pipeline to the project. You could create it manually, but we recommend that you start with a GitLab template that works out of the box."
msgstr ""
msgid "syntax is correct"
@@ -27274,6 +27909,9 @@ msgstr ""
msgid "uses Kubernetes clusters to deploy your code!"
msgstr ""
+msgid "v%{version} published %{timeAgo}"
+msgstr ""
+
msgid "verify ownership"
msgstr ""
diff --git a/locale/de/gitlab.po b/locale/de/gitlab.po
index 6f4e1d4a33d..3283da19e54 100644
--- a/locale/de/gitlab.po
+++ b/locale/de/gitlab.po
@@ -2,7 +2,7 @@ msgid ""
msgstr ""
"Project-Id-Version: gitlab-ee\n"
"Report-Msgid-Bugs-To: \n"
-"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Last-Translator: \n"
"Language-Team: German\n"
"Language: de_DE\n"
"MIME-Version: 1.0\n"
@@ -10,12 +10,14 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Crowdin-Project: gitlab-ee\n"
+"X-Crowdin-Project-ID: 288872\n"
"X-Crowdin-Language: de\n"
"X-Crowdin-File: /master/locale/gitlab.pot\n"
-"PO-Revision-Date: 2020-06-08 15:16\n"
+"X-Crowdin-File-ID: 6\n"
+"PO-Revision-Date: 2020-07-02 01:25\n"
msgid " %{start} to %{end}"
-msgstr ""
+msgstr " %{start} bis %{end}"
msgid " (from %{timeoutSource})"
msgstr " (von %{timeoutSource})"
@@ -197,6 +199,11 @@ msgid_plural "%d metrics"
msgstr[0] "%d Metrik"
msgstr[1] "%d Metriken"
+msgid "%d milestone"
+msgid_plural "%d milestones"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "%d minute"
msgid_plural "%d minutes"
msgstr[0] "%d Minute"
@@ -234,19 +241,14 @@ msgstr[1] ""
msgid "%d tag"
msgid_plural "%d tags"
-msgstr[0] ""
-msgstr[1] ""
+msgstr[0] "%d Tag"
+msgstr[1] "%d Tags"
msgid "%d unresolved thread"
msgid_plural "%d unresolved threads"
msgstr[0] ""
msgstr[1] ""
-msgid "%d url scanned"
-msgid_plural "%d urls scanned"
-msgstr[0] ""
-msgstr[1] ""
-
msgid "%d vulnerability"
msgid_plural "%d vulnerabilities"
msgstr[0] ""
@@ -324,9 +326,15 @@ msgstr[1] "%{count} ausstehende Kommentare"
msgid "%{count} related %{pluralized_subject}: %{links}"
msgstr "%{count} verwandte %{pluralized_subject}: %{links}"
+msgid "%{dashboard_path} could not be found."
+msgstr ""
+
msgid "%{days} days until tags are automatically removed"
msgstr "%{days} Tage, bis Tags automatisch entfernt werden"
+msgid "%{deployLinkStart}Use a template to deploy to ECS%{deployLinkEnd}, or use a docker image to %{commandsLinkStart}run AWS commands in GitLab CI/CD%{commandsLinkEnd}."
+msgstr ""
+
msgid "%{description}- Sentry event: %{errorUrl}- First seen: %{firstSeen}- Last seen: %{lastSeen} %{countLabel}: %{count}%{userCountLabel}: %{userCount}"
msgstr "%{description}- Sentry-Event: %{errorUrl}- Zuerst gesehen: %{firstSeen}- Zuletzt gesehen: %{lastSeen} %{countLabel}: %{count}%{userCountLabel}: %{userCount}"
@@ -358,7 +366,7 @@ msgid "%{firstMilestoneName} + %{numberOfOtherMilestones} more"
msgstr ""
msgid "%{global_id} is not a valid id for %{expected_type}."
-msgstr ""
+msgstr "%{global_id} ist keine gültige ID für %{expected_type}."
msgid "%{group_docs_link_start}Groups%{group_docs_link_end} allow you to manage and collaborate across multiple projects. Members of a group have access to all of its projects."
msgstr "%{group_docs_link_start}Gruppen%{group_docs_link_end} ermöglichen dir die Verwaltung und die Zusammenarbeit über mehrere Projekte hinweg. Mitglieder einer Gruppe haben Zugriff auf alle Projekte darin."
@@ -369,22 +377,28 @@ msgstr "%{group_name} nutzt Accounts, die von einer Gruppe verwaltet werden. Du
msgid "%{host} sign-in from new location"
msgstr ""
-msgid "%{icon}You are about to add %{usersTag} people to the discussion. Proceed with caution."
-msgstr "%{icon}Sie sind dabei, %{usersTag} Personen zur Diskussion hinzuzufügen. Lassen Sie Vorsicht walten."
+msgid "%{icon}You are about to add %{usersTag} people to the discussion. They will all receive a notification."
+msgstr ""
+
+msgid "%{integrations_link_start}Integrations%{link_end} enable you to make third-party applications part of your GitLab workflow. If the available integrations don't meet your needs, consider using a %{webhooks_link_start}webhook%{link_end}."
+msgstr ""
msgid "%{issuableType} will be removed! Are you sure?"
msgstr "%{issuableType} wird entfernt! Bist du sicher?"
-msgid "%{issuesSize} issues"
+msgid "%{issuesCount} issues in this group"
msgstr ""
+msgid "%{issuesSize} issues"
+msgstr "%{issuesSize} Tickets"
+
msgid "%{issuesSize} issues with a limit of %{maxIssueCount}"
-msgstr ""
+msgstr "%{issuesSize} Tickets mit einem Limit von %{maxIssueCount}"
msgid "%{labelStart}Class:%{labelEnd} %{class}"
msgstr ""
-msgid "%{labelStart}Confidence:%{labelEnd} %{confidence}"
+msgid "%{labelStart}Evidence:%{labelEnd} %{evidence}"
msgstr ""
msgid "%{labelStart}File:%{labelEnd} %{file}"
@@ -402,6 +416,9 @@ msgstr ""
msgid "%{labelStart}Report Type:%{labelEnd} %{reportType}"
msgstr ""
+msgid "%{labelStart}Scanner:%{labelEnd} %{scanner}"
+msgstr ""
+
msgid "%{labelStart}Severity:%{labelEnd} %{severity}"
msgstr ""
@@ -426,9 +443,6 @@ msgstr ""
msgid "%{link_start}Read more%{link_end} about role permissions"
msgstr "%{link_start}Erfahre mehr%{link_end} über Rollenberechtigungen"
-msgid "%{link} can be used for binding events when something is happening within the project."
-msgstr ""
-
msgid "%{listToShow}, and %{awardsListLength} more."
msgstr "%{listToShow} und %{awardsListLength} weitere."
@@ -462,6 +476,9 @@ msgstr "%{name} enthielt %{resultsString}"
msgid "%{name} found %{resultsString}"
msgstr "%{name} gefunden %{resultsString}"
+msgid "%{name} is already being used for another emoji"
+msgstr ""
+
msgid "%{name} is scheduled for %{action}"
msgstr ""
@@ -509,6 +526,9 @@ msgstr ""
msgid "%{primary} (%{secondary})"
msgstr "%{primary} (%{secondary})"
+msgid "%{ref} cannot be added: %{error}"
+msgstr ""
+
msgid "%{releases} release"
msgid_plural "%{releases} releases"
msgstr[0] "%{releases} Release"
@@ -517,13 +537,37 @@ msgstr[1] "%{releases} Releases"
msgid "%{remaining_approvals} left"
msgstr ""
-msgid "%{retryButtonStart}Try again%{retryButtonEnd} or %{newFileButtonStart}attach a new file%{newFileButtonEnd}"
+msgid "%{reportType} %{status} detected %{critical} critical and %{high} high severity vulnerabilities out of %{total}."
+msgstr ""
+
+msgid "%{reportType} %{status} detected %{critical} critical and %{high} high severity vulnerabilities."
+msgstr ""
+
+msgid "%{reportType} %{status} detected %{critical} critical severity vulnerabilities out of %{total}."
+msgstr ""
+
+msgid "%{reportType} %{status} detected %{critical} critical severity vulnerability."
+msgid_plural "%{reportType} %{status} detected %{critical} critical severity vulnerabilities."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{reportType} %{status} detected %{high} high severity vulnerabilities out of %{total}."
msgstr ""
-msgid "%{screenreaderOnlyStart}Keyboard shorcuts%{screenreaderOnlyEnd} Disabled"
+msgid "%{reportType} %{status} detected %{high} high severity vulnerability."
+msgid_plural "%{reportType} %{status} detected %{high} high severity vulnerabilities."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{reportType} %{status} detected %{other} vulnerability."
+msgid_plural "%{reportType} %{status} detected %{other} vulnerabilities."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{reportType} %{status} detected no new vulnerabilities."
msgstr ""
-msgid "%{screenreaderOnlyStart}Keyboard shorcuts%{screenreaderOnlyEnd} Enabled"
+msgid "%{retryButtonStart}Try again%{retryButtonEnd} or %{newFileButtonStart}attach a new file%{newFileButtonEnd}"
msgstr ""
msgid "%{service_title} %{message}."
@@ -551,7 +595,7 @@ msgid "%{spanStart}in%{spanEnd} %{errorFn}"
msgstr ""
msgid "%{start} to %{end}"
-msgstr ""
+msgstr "%{start} bis %{end}"
msgid "%{state} epics"
msgstr "%{state} Epics"
@@ -620,6 +664,12 @@ msgstr "%{title} Änderungen"
msgid "%{token}..."
msgstr ""
+msgid "%{totalCpu} (%{freeSpacePercentage}%{percentSymbol} free)"
+msgstr ""
+
+msgid "%{totalMemory} (%{freeSpacePercentage}%{percentSymbol} free)"
+msgstr ""
+
msgid "%{totalWeight} total weight"
msgstr ""
@@ -650,6 +700,12 @@ msgstr ""
msgid "%{verb} %{time_spent_value} spent time."
msgstr "%{verb} %{time_spent_value} verbrachte Zeit."
+msgid "%{webhooks_link_start}%{webhook_type}%{link_end} enable you to send notifications to web applications in response to events in a group or project."
+msgstr ""
+
+msgid "%{webhooks_link_start}%{webhook_type}%{link_end} enable you to send notifications to web applications in response to events in a group or project. We recommend using an %{integrations_link_start}integration%{link_end} in preference to a webhook."
+msgstr ""
+
msgid "'%{level}' is not a valid visibility level"
msgstr "'%{level}' ist kein gültiger Sichtbarkeitslevel"
@@ -676,9 +732,6 @@ msgstr "(%{mrCount} zusammengeführt)"
msgid "(No changes)"
msgstr "(Keine Änderungen)"
-msgid "(Show all)"
-msgstr "(Alle anzeigen)"
-
msgid "(check progress)"
msgstr "(Fortschritt überprüfen)"
@@ -745,6 +798,9 @@ msgstr "- weniger anzeigen"
msgid "0 for unlimited"
msgstr "0 für unbegrenzt"
+msgid "0 for unlimited, only effective with remote storage enabled."
+msgstr ""
+
msgid "1 %{type} addition"
msgid_plural "%{count} %{type} additions"
msgstr[0] "1 %{type} Zusatz"
@@ -772,8 +828,8 @@ msgstr[1] "%{merge_requests} geschlossene Merge Requests"
msgid "1 day"
msgid_plural "%d days"
-msgstr[0] ""
-msgstr[1] ""
+msgstr[0] "1 Tag"
+msgstr[1] "%d Tage"
msgid "1 group"
msgid_plural "%d groups"
@@ -782,8 +838,8 @@ msgstr[1] "%d Gruppen"
msgid "1 hour"
msgid_plural "%d hours"
-msgstr[0] ""
-msgstr[1] ""
+msgstr[0] "1 Stunde"
+msgstr[1] "%d Stunden"
msgid "1 merged merge request"
msgid_plural "%{merge_requests} merged merge requests"
@@ -792,8 +848,8 @@ msgstr[1] "%{merge_requests} zusammengeführte Merge Requests"
msgid "1 minute"
msgid_plural "%d minutes"
-msgstr[0] ""
-msgstr[1] ""
+msgstr[0] "1 Minute"
+msgstr[1] "%d Minuten"
msgid "1 month"
msgstr ""
@@ -878,7 +934,7 @@ msgid ":%{startLine} to %{endLine}"
msgstr ""
msgid "< 1 hour"
-msgstr ""
+msgstr "< 1 Stunde"
msgid "<code>\"johnsmith@example.com\": \"@johnsmith\"</code> will add \"By <a href=\"#\">@johnsmith</a>\" to all issues and comments originally created by johnsmith@example.com, and will set <a href=\"#\">@johnsmith</a> as the assignee on all issues originally assigned to johnsmith@example.com."
msgstr "<code>\"johnsmith@example.com\": \"@johnsmith\"</code> fügt allen von johnsmith@example.com erstellten Tickets und Kommentaren \"Von <a href=\"#\">@johnsmith</a>\" hinzu und setzt <a href=\"#\">@johnsmith</a> als Zuständigen für alle Tickets die ursprünglich johnsmith@example.com zugewiesen waren."
@@ -892,6 +948,12 @@ msgstr "<code>\"johnsmith@example.com\": \"johnsm...@example.com\"</code> fügt
msgid "<code>\"johnsmith@example.com\": \"johnsmith@example.com\"</code> will add \"By <a href=\"#\">johnsmith@example.com</a>\" to all issues and comments originally created by johnsmith@example.com. By default, the email address or username is masked to ensure the user's privacy. Use this option if you want to show the full email address."
msgstr "<code>\"johnsmith@example.com\": \"johnsmith@example.com\"</code> fügt \"Von <a href=\"#\">johnsmith@example.com</a>\" zu allen Tickets und Kommentaren hinzu, die ursprünglich von johnsmith@example.com erstellt wurden. Standardmäßig wird die E-Mail-Adresse maskiert, um den Datenschutz des Nutzers zu gewährleisten. Nutze diese Option, wenn du die volle E-Mail-Adresse anzeigen willst."
+msgid "<code>Masked</code> to prevent the values from being displayed in job logs (must match certain regexp requirements)."
+msgstr ""
+
+msgid "<code>Protected</code> to expose them to protected branches or tags only."
+msgstr ""
+
msgid "<namespace / project>"
msgstr ""
@@ -919,9 +981,6 @@ msgstr "Ein 'Runner' ist ein Prozess, welcher ein Job ausführt. Du kannst so vi
msgid "A .NET Core console application template, customizable for any .NET Core project"
msgstr "Eine .NET Core-Konsolenanwendungsvorlage, anpassbar für jedes .NET Core-Projekt"
-msgid "A DAG must have two dependent jobs to be visualized on this tab."
-msgstr ""
-
msgid "A GitBook site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
msgstr "Eine GitBook-Seite, welche statt GitLab Netlify für CI/CD nutzt, aber trotzdem von all den anderen nützlichen GitLab-Funktionen profitiert."
@@ -952,6 +1011,12 @@ msgstr "Ein Default-Branch kann nicht für ein leeres Projekt ausgewählt werden
msgid "A deleted user"
msgstr "Ein(e) gelöschte(r) Benutzer(in)"
+msgid "A file has been changed."
+msgstr ""
+
+msgid "A file was not found."
+msgstr ""
+
msgid "A file with '%{file_name}' already exists in %{branch} branch"
msgstr ""
@@ -1018,7 +1083,7 @@ msgstr ""
msgid "A subscription will trigger a new pipeline on the default branch of this project when a pipeline successfully completes for a new tag on the %{default_branch_docs} of the subscribed project."
msgstr ""
-msgid "A terraform report was generated in your pipelines."
+msgid "A suggestion is not applicable."
msgstr ""
msgid "A user with write access to the source branch selected this option"
@@ -1189,6 +1254,9 @@ msgstr "Konto: %{account}"
msgid "Action to take when receiving an alert."
msgstr "Aktion beim Empfang eines Alarms."
+msgid "Actions"
+msgstr ""
+
msgid "Activate"
msgstr "Aktivieren"
@@ -1243,6 +1311,9 @@ msgid "Add Kubernetes cluster"
msgstr "Kubernetes-Cluster hinzufügen"
msgid "Add LICENSE"
+msgstr "LIZENZ hinzufügen"
+
+msgid "Add New Node"
msgstr ""
msgid "Add README"
@@ -1320,6 +1391,9 @@ msgstr ""
msgid "Add approval rule"
msgstr "Füge Zustimmungsregel hinzu"
+msgid "Add approvers"
+msgstr ""
+
msgid "Add bold text"
msgstr "Fetten Text hinzufügen"
@@ -1377,6 +1451,9 @@ msgstr "Anfrage manuell hinzufügen"
msgid "Add strikethrough text"
msgstr ""
+msgid "Add suggestion to batch"
+msgstr ""
+
msgid "Add system hook"
msgstr "System-Hook hinzufügen"
@@ -1476,6 +1553,9 @@ msgstr "Adminbereich"
msgid "Admin Note"
msgstr ""
+msgid "Admin Notifications"
+msgstr ""
+
msgid "Admin Overview"
msgstr "Admin-Ãœbersicht"
@@ -1516,7 +1596,7 @@ msgid "AdminArea|Maintainer"
msgstr ""
msgid "AdminArea|Owner"
-msgstr ""
+msgstr "Inhaber(in)"
msgid "AdminArea|Reporter"
msgstr ""
@@ -1587,6 +1667,9 @@ msgstr ""
msgid "AdminSettings|Integrations configured here will automatically apply to all projects on this instance."
msgstr ""
+msgid "AdminSettings|Moved to integrations"
+msgstr ""
+
msgid "AdminSettings|No required pipeline"
msgstr "Keine Pipeline erforderlich"
@@ -1817,12 +1900,18 @@ msgid_plural "Alerts"
msgstr[0] "Alarm"
msgstr[1] "Alarme"
+msgid "AlertManagement| assign yourself"
+msgstr ""
+
msgid "AlertManagement|Acknowledged"
msgstr ""
msgid "AlertManagement|Alert"
msgstr ""
+msgid "AlertManagement|Alert assignee(s): %{assignees}"
+msgstr ""
+
msgid "AlertManagement|Alert detail"
msgstr ""
@@ -1838,9 +1927,15 @@ msgstr ""
msgid "AlertManagement|All alerts"
msgstr ""
+msgid "AlertManagement|Assign To"
+msgstr ""
+
msgid "AlertManagement|Assign status"
msgstr ""
+msgid "AlertManagement|Assignee"
+msgstr ""
+
msgid "AlertManagement|Assignees"
msgstr ""
@@ -1859,9 +1954,6 @@ msgstr ""
msgid "AlertManagement|Edit"
msgstr ""
-msgid "AlertManagement|End time"
-msgstr ""
-
msgid "AlertManagement|Events"
msgstr ""
@@ -1880,10 +1972,13 @@ msgstr ""
msgid "AlertManagement|More information"
msgstr ""
+msgid "AlertManagement|No Matching Results"
+msgstr ""
+
msgid "AlertManagement|No alert data to display."
msgstr ""
-msgid "AlertManagement|No alerts available to display. If you think you're seeing this message in error, refresh the page."
+msgid "AlertManagement|No alerts available to display. See %{linkStart}enabling alert management%{linkEnd} for more information on adding alerts to the list."
msgstr ""
msgid "AlertManagement|No alerts to display."
@@ -1892,6 +1987,9 @@ msgstr ""
msgid "AlertManagement|None"
msgstr ""
+msgid "AlertManagement|None -"
+msgstr ""
+
msgid "AlertManagement|Open"
msgstr ""
@@ -1928,9 +2026,18 @@ msgstr ""
msgid "AlertManagement|There was an error displaying the alerts. Confirm your endpoint's configuration details to ensure alerts appear."
msgstr ""
+msgid "AlertManagement|There was an error while updating the assignee(s) list. Please try again."
+msgstr ""
+
+msgid "AlertManagement|There was an error while updating the assignee(s) of the alert. Please try again."
+msgstr ""
+
msgid "AlertManagement|There was an error while updating the status of the alert. Please try again."
msgstr ""
+msgid "AlertManagement|This assignee cannot be assigned to this alert."
+msgstr ""
+
msgid "AlertManagement|Tool"
msgstr ""
@@ -2078,6 +2185,9 @@ msgstr "Erlaube Benutzer(innen), jede Anwendung zur Nutzung von GitLab als OAuth
msgid "Allow users to request access (if visibility is public or internal)"
msgstr ""
+msgid "Allowed Geo IP"
+msgstr ""
+
msgid "Allowed email domain restriction only permitted for top-level groups"
msgstr ""
@@ -2103,7 +2213,7 @@ msgid "Alternatively, you can convert your account to a managed account by the %
msgstr ""
msgid "Amazon EKS"
-msgstr ""
+msgstr "Amazon EKS"
msgid "Amazon EKS integration allows you to provision EKS clusters from GitLab."
msgstr ""
@@ -2111,6 +2221,9 @@ msgstr ""
msgid "Amazon Web Services"
msgstr "Amazon Web Services"
+msgid "Amazon Web Services Logo"
+msgstr ""
+
msgid "Amazon authentication is not %{link_start}correctly configured%{link_end}. Ask your GitLab administrator if you want to use this service."
msgstr ""
@@ -2168,11 +2281,17 @@ msgstr "Beim Versuch, eine Diskussion zu lösen, ist ein Fehler aufgetreten. Bit
msgid "An error occurred when updating the issue weight"
msgstr "Beim Aktualisieren der Ticket-Gewichtung ist ein Fehler aufgetreten"
+msgid "An error occurred while acknowledging the notification. Refresh the page and try again."
+msgstr ""
+
+msgid "An error occurred while adding approvers"
+msgstr ""
+
msgid "An error occurred while adding formatted title for epic"
msgstr ""
-msgid "An error occurred while checking group path"
-msgstr "Beim Aufrufen des Projektpfades ist ein Fehler aufgetreten"
+msgid "An error occurred while checking group path. Please refresh and try again."
+msgstr ""
msgid "An error occurred while committing your changes."
msgstr ""
@@ -2315,7 +2434,7 @@ msgstr ""
msgid "An error occurred while loading milestones"
msgstr ""
-msgid "An error occurred while loading terraform report"
+msgid "An error occurred while loading project creation UI"
msgstr ""
msgid "An error occurred while loading the data. Please try again."
@@ -2369,6 +2488,9 @@ msgstr "Beim Entfernen der Tickets ist ein Fehler aufgetreten."
msgid "An error occurred while rendering preview broadcast message"
msgstr "Beim Rendern der Vorschau der Broadcast-Nachricht ist ein Fehler aufgetreten"
+msgid "An error occurred while rendering the editor"
+msgstr ""
+
msgid "An error occurred while reordering issues."
msgstr "Beim Umordnen von Tickets ist ein Fehler aufgetreten."
@@ -2465,7 +2587,7 @@ msgstr ""
msgid "Analyze your dependencies for known vulnerabilities."
msgstr ""
-msgid "Analyze your source code and git history for secrets"
+msgid "Analyze your source code and git history for secrets."
msgstr ""
msgid "Analyze your source code for known vulnerabilities."
@@ -2517,7 +2639,7 @@ msgid "Any user"
msgstr "Jede(r) Benutzer(in)"
msgid "App ID"
-msgstr ""
+msgstr "App ID"
msgid "Appearance"
msgstr "Aussehen"
@@ -2565,7 +2687,7 @@ msgid "Applied"
msgstr "Angewendet"
msgid "Apply"
-msgstr ""
+msgstr "Anwenden"
msgid "Apply a label"
msgstr "Label hinzufügen"
@@ -2579,12 +2701,18 @@ msgstr ""
msgid "Apply suggestion"
msgstr "Vorschlag anwenden"
+msgid "Apply suggestions"
+msgstr ""
+
msgid "Apply template"
msgstr ""
msgid "Apply this approval rule to any branch or a specific protected branch."
msgstr ""
+msgid "Applying"
+msgstr ""
+
msgid "Applying a template will replace the existing issue description. Any changes you have made will be lost."
msgstr ""
@@ -2597,12 +2725,18 @@ msgstr "Befehl anwenden, um: %{commandDescription}"
msgid "Applying multiple commands"
msgstr "Mehrere Befehle anwenden"
-msgid "Applying suggestion"
-msgstr "Vorschlag anwenden"
+msgid "Applying suggestion..."
+msgstr ""
+
+msgid "Applying suggestions..."
+msgstr ""
msgid "Approval rules"
msgstr ""
+msgid "Approval rules reset to project defaults"
+msgstr ""
+
msgid "ApprovalRuleRemove|%d member"
msgid_plural "ApprovalRuleRemove|%d members"
msgstr[0] "%d Mitglied"
@@ -2656,6 +2790,9 @@ msgstr "Aktuellen Merge-Request genehmigen."
msgid "Approved"
msgstr ""
+msgid "Approved MRs"
+msgstr ""
+
msgid "Approved by: "
msgstr ""
@@ -2702,7 +2839,7 @@ msgid "Archiving the project will make it entirely read only. It is hidden from
msgstr ""
msgid "Are you setting up GitLab for a company?"
-msgstr ""
+msgstr "Richten Sie GitLab für ein Unternehmen ein?"
msgid "Are you sure that you want to archive this project?"
msgstr "Möchtest du wirklich dieses Projekt archivieren?"
@@ -2801,13 +2938,13 @@ msgid "Are you sure you want to unlock %{path_lock_path}?"
msgstr "Bist du sicher, dass du %{path_lock_path} entsperren willst?"
msgid "Are you sure you want to unsubscribe from the %{type}: %{link_to_noteable_text}?"
-msgstr ""
+msgstr "Möchtest du wirklich nicht mehr über %{type}: %{link_to_noteable_text} benachrichtigt werden?"
msgid "Are you sure?"
msgstr "Bist Du sicher?"
msgid "Are you sure? All commits that were signed with this GPG key will be unverified."
-msgstr ""
+msgstr "Bist du dir sicher? Alle Commits, die mit diesem GPG-Schlüssel signiert wurden, werden nicht mehr signiert sein."
msgid "Are you sure? Removing this GPG key does not affect already signed commits."
msgstr ""
@@ -2869,6 +3006,9 @@ msgstr "Assets:"
msgid "Assign"
msgstr "Zuweisen"
+msgid "Assign Iteration"
+msgstr ""
+
msgid "Assign custom color like #FF0000"
msgstr "Benutzerdefinierte Farbe wie #FF0000 zuweisen"
@@ -2887,6 +3027,9 @@ msgstr "Einige Tickets diesem Meilenstein zuordnen."
msgid "Assign to"
msgstr "Zuweisen an"
+msgid "Assign to commenting user"
+msgstr ""
+
msgid "Assign yourself to these issues"
msgstr ""
@@ -2913,6 +3056,9 @@ msgid_plural "%d Assignees"
msgstr[0] "Beauftragte(r)"
msgstr[1] "%d Beauftragte"
+msgid "Assignee has no permissions"
+msgstr ""
+
msgid "Assignee lists not available with your current license"
msgstr "Zuweisungslisten sind mit deiner momentanen Lizenz nicht verfügbar"
@@ -2922,6 +3068,9 @@ msgstr "Zuweisungslisten zeigen alle dem ausgewählten Benutzer zugewiesenen Tic
msgid "Assignee(s)"
msgstr "Zugewiesene Personen"
+msgid "Assignees"
+msgstr ""
+
msgid "Assigns %{assignee_users_sentence}."
msgstr "Weise %{assignee_users_sentence} zu."
@@ -2957,6 +3106,9 @@ msgstr "Audit-Ereignisse"
msgid "Audit Events is a way to keep track of important events that happened in GitLab."
msgstr "Mit Audit Events können Sie wichtige Ereignisse in GitLab nachverfolgen."
+msgid "Audit Log"
+msgstr ""
+
msgid "AuditEvents|(removed)"
msgstr ""
@@ -2976,7 +3128,7 @@ msgid "AuditLogs|Action"
msgstr ""
msgid "AuditLogs|Author"
-msgstr ""
+msgstr "Autor(in)"
msgid "AuditLogs|Date"
msgstr ""
@@ -2993,6 +3145,9 @@ msgstr ""
msgid "AuditLogs|IP Address"
msgstr ""
+msgid "AuditLogs|Member Events"
+msgstr ""
+
msgid "AuditLogs|No matching %{type} found."
msgstr ""
@@ -3042,7 +3197,7 @@ msgid "Authentication method updated"
msgstr "Authentifizierungsmethode aktualisiert"
msgid "Authentication via U2F device failed."
-msgstr ""
+msgstr "Authentifizierung mittels U2F-Gerät fehlgeschlagen."
msgid "Author"
msgstr "Autor(in)"
@@ -3120,7 +3275,7 @@ msgid "AutoDevOps|Learn more in the %{link_to_documentation}"
msgstr "Erfahre mehr in der %{link_to_documentation}"
msgid "AutoDevOps|The Auto DevOps pipeline has been enabled and will be used if no alternative CI configuration file is found. %{more_information_link}"
-msgstr ""
+msgstr "Die Auto-DevOps-Pipeline wurde aktiviert und wird verwendet, falls keine alternative CI-Konfigurationsdatei gefunden wurde. %{more_information_link}"
msgid "Autocomplete"
msgstr "Autovervollständigung"
@@ -3132,7 +3287,7 @@ msgid "Autocomplete hint"
msgstr ""
msgid "Autocomplete usage hint"
-msgstr ""
+msgstr "Hinweis zur Verwendung der automatischen Vervollständigung"
msgid "Automatic certificate management using %{lets_encrypt_link_start}Let's Encrypt%{lets_encrypt_link_end}"
msgstr "Automatische Zertifikatsverwaltung mit %{lets_encrypt_link_start}Let's Encrypt%{lets_encrypt_link_end}"
@@ -3171,7 +3326,7 @@ msgid "Available specific runners"
msgstr "Verfügbare spezifische Runner"
msgid "Avatar for %{assigneeName}"
-msgstr ""
+msgstr "Avatar von %{assigneeName}"
msgid "Avatar for %{name}"
msgstr "Avatar von %{name}"
@@ -3275,9 +3430,6 @@ msgstr "Deine Badges"
msgid "Badges|e.g. %{exampleUrl}"
msgstr "z. B. %{exampleUrl}"
-msgid "Badge|New"
-msgstr "Neu"
-
msgid "Balsamiq file could not be loaded."
msgstr "Balsamiq-Datei konnte nicht geladen werden."
@@ -3294,10 +3446,10 @@ msgid "BambooService|Bamboo build plan key like KEY"
msgstr "Bamboo-Build-Plan-Schlüssel, z.B. KEY"
msgid "BambooService|Bamboo root URL like https://bamboo.example.com"
-msgstr ""
+msgstr "Bamboo Root-URL, z.B. https://bamboo.example.com"
msgid "BambooService|You must set up automatic revision labeling and a repository trigger in Bamboo."
-msgstr ""
+msgstr "Du musst in Bamboo eine automatische Versionskennzeichnung und einen Repository-Trigger einrichten."
msgid "BatchComments|Delete all pending comments"
msgstr "Alle ausstehenden Kommentare löschen"
@@ -3326,6 +3478,12 @@ msgstr ""
msgid "Below you will find all the groups that are public."
msgstr "Unten findest du alle Gruppen, die öffentlich sind."
+msgid "Beta"
+msgstr ""
+
+msgid "Bi-weekly code coverage"
+msgstr ""
+
msgid "Billing"
msgstr "Abrechnung"
@@ -3345,7 +3503,7 @@ msgid "BillingPlans|Learn more about each plan by reading our %{faq_link}, or st
msgstr "Erfahre mehr über unsere Tarife, indem du unsere %{faq_link} liest, oder beginne einen kostenlosen 30-Tage Test von GitLab.com Gold."
msgid "BillingPlans|Learn more about each plan by visiting our %{pricing_page_link}."
-msgstr ""
+msgstr "Erfahre mehr über jeden einzelnen Tarif, indem du unsere %{pricing_page_link} besuchst."
msgid "BillingPlans|Manage plan"
msgstr "Tarif verwalten"
@@ -3386,9 +3544,15 @@ msgstr ""
msgid "Bitbucket Server Import"
msgstr "Bitbucket-Server-Import"
+msgid "Bitbucket Server import"
+msgstr ""
+
msgid "Bitbucket import"
msgstr "Bitbucket-Import"
+msgid "Blame"
+msgstr ""
+
msgid "Blocked"
msgstr "Gesperrt"
@@ -3401,9 +3565,6 @@ msgstr "Blöcke"
msgid "Blog"
msgstr "Blog"
-msgid "Blue helpers indicate an action to be taken."
-msgstr ""
-
msgid "Board name"
msgstr "Board Name"
@@ -3411,7 +3572,7 @@ msgid "Board scope"
msgstr "Board-Bereich"
msgid "Board scope affects which issues are displayed for anyone who visits this board"
-msgstr ""
+msgstr "Der Board-Bereich beeinflusst, welche Themen für jeden angezeigt werden, der dieses Board besucht"
msgid "BoardBlankState|Add default lists"
msgstr "Standardlisten hinzufügen"
@@ -3602,6 +3763,9 @@ msgstr "Broadcast-Nachricht wurde erfolgreich erstellt."
msgid "Broadcast Message was successfully updated."
msgstr "Broadcast-Nachricht wurde erfolgreich aktualisiert."
+msgid "Broadcast Messages"
+msgstr ""
+
msgid "Browse Directory"
msgstr "Verzeichnis durchsuchen"
@@ -3656,6 +3820,9 @@ msgstr ""
msgid "By %{user_name}"
msgstr "Von %{user_name}"
+msgid "By URL"
+msgstr ""
+
msgid "By default GitLab sends emails in HTML and plain text formats so mail clients can choose what format to use. Disable this option if you only want to send emails in plain text format."
msgstr "Standardmäßig versendet GitLab E-Mails in HTML- und Nur-Text-Formaten, sodass Mail-Clients das zu verwendende Format auswählen können. Deaktiviere diese Option, wenn E-Mails ausschließlich im Nur-Text-Format versendet werden sollen."
@@ -3761,6 +3928,9 @@ msgstr "Kann manuell bereitgestellt werden für"
msgid "Can override approvers and approvals required per merge request"
msgstr "Kann erforderliche Genehmigungsberechtigte und Genehmigungen pro Merge-Request überschreiben"
+msgid "Can't apply as this line has changed or the suggestion already matches its content."
+msgstr ""
+
msgid "Can't create snippet: %{err}"
msgstr ""
@@ -3777,7 +3947,7 @@ msgid "Can't load mermaid module: %{err}"
msgstr ""
msgid "Can't remove group members without group managed account"
-msgstr ""
+msgstr "Ohne ein gruppenverwaltetes Konto können keine Gruppenmitglieder entfernt werden"
msgid "Can't scan the code?"
msgstr ""
@@ -3789,7 +3959,7 @@ msgid "Canary"
msgstr ""
msgid "Canary Deployments is a popular CI strategy, where a small portion of the fleet is updated to the new version of your application."
-msgstr ""
+msgstr "Canary Deployments sind eine beliebte CI-Strategie, in der zunächst ein kleiner Teil deines Serverparks auf neue Anwendungsversionen aktualisiert werden."
msgid "Cancel"
msgstr "Abbrechen"
@@ -3947,9 +4117,6 @@ msgstr "Änderungen werden angezeigt, als ob die <b>Quell</b>-Revision in die <b
msgid "Changes are still tracked. Useful for cluster/index migrations."
msgstr ""
-msgid "Changes are unknown"
-msgstr ""
-
msgid "Changes suppressed. Click to show."
msgstr "Änderungen unterdrückt. Zum Anzeigen klicken."
@@ -4005,7 +4172,7 @@ msgid "ChatMessage|Tag"
msgstr "Tag"
msgid "ChatMessage|and [%{count} more](%{pipeline_failed_jobs_url})"
-msgstr ""
+msgstr "und [%{count} mehr](%{pipeline_failed_jobs_url})"
msgid "ChatMessage|has failed"
msgstr ""
@@ -4254,17 +4421,11 @@ msgid "Choose visibility level, enable/disable project features (issues, reposit
msgstr ""
msgid "Choose what content you want to see on a group’s overview page"
-msgstr ""
-
-msgid "Choose which groups you wish to synchronize to this secondary node"
-msgstr ""
+msgstr "Inhalte auswählen, die auf Gruppenübersichtsseiten angezeigt werden"
msgid "Choose which repositories you want to connect and run CI/CD pipelines."
msgstr "Wähle welche Repositories du verbinden und die CI/CD-Pipelines ausführen möchtest."
-msgid "Choose which shards you wish to synchronize to this secondary node"
-msgstr ""
-
msgid "Choose your framework"
msgstr "Wähle dein Framework"
@@ -4332,7 +4493,7 @@ msgid "CiStatusText|pending"
msgstr "ausstehend"
msgid "CiStatusText|preparing"
-msgstr ""
+msgstr "Vorbereitung"
msgid "CiStatusText|skipped"
msgstr "übersprungen"
@@ -4368,7 +4529,7 @@ msgid "CiVariables|Remove variable row"
msgstr "Variable jetzt löschen"
msgid "CiVariables|Scope"
-msgstr ""
+msgstr "Geltungsbereich"
msgid "CiVariables|Specify variable values to be used in this run. The values specified in %{linkStart}CI/CD settings%{linkEnd} will be used as default"
msgstr ""
@@ -4497,7 +4658,7 @@ msgid "Clients"
msgstr "Kunden"
msgid "Clone"
-msgstr ""
+msgstr "Klonen"
msgid "Clone repository"
msgstr "Repository klonen"
@@ -4562,9 +4723,15 @@ msgstr ""
msgid "Cluster does not exist"
msgstr ""
+msgid "Cluster is required for Stages::ClusterEndpointInserter"
+msgstr ""
+
msgid "Cluster level"
msgstr ""
+msgid "Cluster type must be specificed for Stages::ClusterEndpointInserter"
+msgstr ""
+
msgid "ClusterIntegration| %{custom_domain_start}More information%{custom_domain_end}."
msgstr "%{custom_domain_start}Mehr Informationen%{custom_domain_end}."
@@ -4635,7 +4802,7 @@ msgid "ClusterIntegration|Allow GitLab to manage namespace and service accounts
msgstr "Ermögliche GitLab, Namespace- und Service-Konten für diesen Cluster zu verwalten. %{startLink}Weitere Informationen%{endLink}"
msgid "ClusterIntegration|Alternatively"
-msgstr ""
+msgstr "Alternativ"
msgid "ClusterIntegration|Amazon EKS"
msgstr ""
@@ -4647,16 +4814,16 @@ msgid "ClusterIntegration|An error occurred while trying to fetch project zones:
msgstr ""
msgid "ClusterIntegration|An error occurred while trying to fetch your projects: %{error}"
-msgstr ""
+msgstr "Beim Fetchen deiner Projekte ist ein Fehler aufgetreten: %{error}"
msgid "ClusterIntegration|An error occurred while trying to fetch zone machine types: %{error}"
-msgstr ""
+msgstr "Beim Fetchen der Maschinentypen der Zone ist ein Fehler aufgetreten: %{error}"
msgid "ClusterIntegration|Any running pipelines will be canceled."
msgstr ""
msgid "ClusterIntegration|Apply for credit"
-msgstr ""
+msgstr "Guthaben beantragen"
msgid "ClusterIntegration|Authenticate with AWS"
msgstr "Mit AWS authentifizieren"
@@ -4665,7 +4832,7 @@ msgid "ClusterIntegration|Authenticate with Amazon Web Services"
msgstr ""
msgid "ClusterIntegration|Base domain"
-msgstr ""
+msgstr "Basisdomäne"
msgid "ClusterIntegration|Blocking mode"
msgstr ""
@@ -4674,9 +4841,9 @@ msgid "ClusterIntegration|CA Certificate"
msgstr "CA-Zertifikat"
msgid "ClusterIntegration|Cert-Manager"
-msgstr ""
+msgstr "Cert-Manager"
-msgid "ClusterIntegration|Cert-Manager is a native Kubernetes certificate management controller that helps with issuing certificates. Installing Cert-Manager on your cluster will issue a certificate by %{letsEncrypt} and ensure that certificates are valid and up-to-date."
+msgid "ClusterIntegration|Cert-Manager is a native Kubernetes certificate management controller that helps with issuing certificates. Installing Cert-Manager on your cluster will issue a certificate by %{linkStart}Let's Encrypt%{linkEnd} and ensure that certificates are valid and up-to-date."
msgstr ""
msgid "ClusterIntegration|Certificate Authority bundle (PEM format)"
@@ -4703,9 +4870,6 @@ msgstr ""
msgid "ClusterIntegration|Clear the local cache of namespace and service accounts. This is necessary if your integration has become out of sync. The cache is repopulated during the next CI job that requires namespace and service accounts."
msgstr ""
-msgid "ClusterIntegration|Cloud Run"
-msgstr ""
-
msgid "ClusterIntegration|Cluster being created"
msgstr ""
@@ -4790,7 +4954,7 @@ msgstr ""
msgid "ClusterIntegration|Crossplane"
msgstr ""
-msgid "ClusterIntegration|Crossplane enables declarative provisioning of managed services from your cloud of choice using %{codeStart}kubectl%{codeEnd} or %{gitlabIntegrationLink}. Crossplane runs inside your Kubernetes cluster and supports secure connectivity and secrets management between app containers and the cloud services they depend on."
+msgid "ClusterIntegration|Crossplane enables declarative provisioning of managed services from your cloud of choice using %{codeStart}kubectl%{codeEnd} or %{linkStart}GitLab Integration%{linkEnd}. Crossplane runs inside your Kubernetes cluster and supports secure connectivity and secrets management between app containers and the cloud services they depend on."
msgstr ""
msgid "ClusterIntegration|Deletes all GitLab resources attached to this cluster during removal"
@@ -4874,9 +5038,6 @@ msgstr ""
msgid "ClusterIntegration|GitLab-managed cluster"
msgstr ""
-msgid "ClusterIntegration|Gitlab Integration"
-msgstr ""
-
msgid "ClusterIntegration|Global default"
msgstr ""
@@ -4922,7 +5083,7 @@ msgstr ""
msgid "ClusterIntegration|Ingress gives you a way to route requests to services based on the request host or path, centralizing a number of services into a single entrypoint."
msgstr "Ingress bietet die Möglichkeit, Dienste-Anfragen je nach Anfragehost oder -pfad weiterzuleiten. So werden viele Dienste an einer Stelle zentral zusammengefasst."
-msgid "ClusterIntegration|Installing Ingress may incur additional costs. Learn more about %{pricingLink}."
+msgid "ClusterIntegration|Installing Ingress may incur additional costs. Learn more about %{linkStart}pricing%{linkEnd}."
msgstr ""
msgid "ClusterIntegration|Instance cluster"
@@ -4937,7 +5098,7 @@ msgstr "Kubernetes-Cluster-Automatisierung integrieren"
msgid "ClusterIntegration|Issuer Email"
msgstr ""
-msgid "ClusterIntegration|Issuers represent a certificate authority. You must provide an email address for your Issuer. "
+msgid "ClusterIntegration|Issuers represent a certificate authority. You must provide an email address for your Issuer."
msgstr ""
msgid "ClusterIntegration|Jupyter Hostname"
@@ -5009,9 +5170,6 @@ msgstr "Lerne mehr über die Kubernetes Gruppen-Cluster"
msgid "ClusterIntegration|Learn more about instance Kubernetes clusters"
msgstr ""
-msgid "ClusterIntegration|Let's Encrypt"
-msgstr ""
-
msgid "ClusterIntegration|Loading IAM Roles"
msgstr ""
@@ -5123,8 +5281,8 @@ msgstr ""
msgid "ClusterIntegration|Prometheus"
msgstr "Prometheus"
-msgid "ClusterIntegration|Prometheus is an open-source monitoring system with %{gitlabIntegrationLink} to monitor deployed applications."
-msgstr "Prometheus ist ein Open-Source-Ãœberwachungssystem mit %{gitlabIntegrationLink} zur Ãœberwachung von bereitgestellten Anwendungen."
+msgid "ClusterIntegration|Prometheus is an open-source monitoring system with %{linkStart}GitLab Integration%{linkEnd} to monitor deployed applications."
+msgstr ""
msgid "ClusterIntegration|Provider details"
msgstr ""
@@ -5178,10 +5336,10 @@ msgid "ClusterIntegration|SIEM Hostname"
msgstr ""
msgid "ClusterIntegration|SIEM Port"
-msgstr ""
+msgstr "SIEM-Port"
msgid "ClusterIntegration|SIEM Protocol"
-msgstr ""
+msgstr "SIEM-Protokoll"
msgid "ClusterIntegration|Save changes"
msgstr "Änderungen speichern"
@@ -5279,10 +5437,10 @@ msgstr "Wähle eine Zone aus"
msgid "ClusterIntegration|Select zone to choose machine type"
msgstr "Wähle eine Zone aus, um den Maschinentyp auszuwählen"
-msgid "ClusterIntegration|Send Cilium Logs"
+msgid "ClusterIntegration|Send Container Network Policies Logs"
msgstr ""
-msgid "ClusterIntegration|Send ModSecurity Logs"
+msgid "ClusterIntegration|Send Web Application Firewall Logs"
msgstr ""
msgid "ClusterIntegration|Service Token"
@@ -5450,15 +5608,12 @@ msgstr "Zugriff zur Google Kubernetes-Engine"
msgid "ClusterIntegration|documentation"
msgstr "Dokumentation"
-msgid "ClusterIntegration|installed via %{installed_via}"
+msgid "ClusterIntegration|installed via %{linkStart}Cloud Run%{linkEnd}"
msgstr ""
msgid "ClusterIntegration|meets the requirements"
msgstr "erfüllt die Anforderungen"
-msgid "ClusterIntegration|pricing"
-msgstr ""
-
msgid "ClusterIntegration|sign up"
msgstr "Registrierung"
@@ -5493,11 +5648,20 @@ msgid "Clusters|An error occurred while loading clusters"
msgstr ""
msgid "Code"
+msgstr "Code"
+
+msgid "Code Coverage: %{coveragePercentage}%{percentSymbol}"
msgstr ""
-msgid "Code Owners"
+msgid "Code Coverage| Empty code coverage data"
msgstr ""
+msgid "Code Coverage|Couldn't fetch the code coverage data"
+msgstr ""
+
+msgid "Code Owners"
+msgstr "Code-Eigentümer"
+
msgid "Code Owners to the merge request changes."
msgstr ""
@@ -5514,7 +5678,7 @@ msgid "Code coverage statistics for master %{start_date} - %{end_date}"
msgstr ""
msgid "Code owner approval is required"
-msgstr ""
+msgstr "Die Zustimmung des Code-Eigentümers ist erforderlich"
msgid "Code owners"
msgstr "Code-Besitzer(innen)"
@@ -5549,7 +5713,7 @@ msgstr "Reduzieren"
msgid "Collapse approvers"
msgstr "Genehmigungsberechtigte ausblenden"
-msgid "Collapse child epics"
+msgid "Collapse milestones"
msgstr ""
msgid "Collapse replies"
@@ -5567,26 +5731,29 @@ msgstr ""
msgid "Coming soon"
msgstr ""
+msgid "Comma-separated, e.g. '1.1.1.1, 2.2.2.0/24'"
+msgstr ""
+
msgid "Command"
msgstr "Befehl"
msgid "Command line instructions"
-msgstr ""
+msgstr "Kommandozeilenbefehle"
msgid "Commands applied"
msgstr "Befehle angewendet"
msgid "Commands did not apply"
-msgstr ""
+msgstr "Befehle wurden nicht angewendet"
msgid "Comment"
msgstr "Kommentieren"
msgid "Comment & close %{noteable_name}"
-msgstr ""
+msgstr "%{noteable_name} kommentieren und schließen"
msgid "Comment & reopen %{noteable_name}"
-msgstr ""
+msgstr "%{noteable_name} kommentieren und erneut öffnen"
msgid "Comment & resolve thread"
msgstr "Kommentiere & beende Diskussion"
@@ -5595,13 +5762,13 @@ msgid "Comment & unresolve thread"
msgstr "Kommentiere & starte Diskussion erneut"
msgid "Comment '%{label}' position"
-msgstr ""
+msgstr "Kommentar '%{label}' Position"
msgid "Comment form position"
msgstr "Position des Kommentarformulars"
msgid "Comment is being updated"
-msgstr ""
+msgstr "Kommentar wird aktualisiert"
msgid "Comment on lines %{startLine} to %{endLine}"
msgstr ""
@@ -5618,16 +5785,16 @@ msgstr[0] "Commit"
msgstr[1] "Commits"
msgid "Commit %{commit_id}"
-msgstr ""
+msgstr "Commit %{commit_id}"
msgid "Commit (when editing commit message)"
-msgstr ""
+msgstr "Commit (beim Bearbeiten der Commit-Nachricht)"
msgid "Commit Message"
msgstr "Commit-Nachricht"
msgid "Commit deleted"
-msgstr ""
+msgstr "Commit gelöscht"
msgid "Commit message"
msgstr "Commit Nachricht"
@@ -5684,7 +5851,7 @@ msgid "Commit…"
msgstr "Commit…"
msgid "Company"
-msgstr ""
+msgstr "Unternehmen"
msgid "Company name"
msgstr ""
@@ -5699,7 +5866,7 @@ msgid "Compare Revisions"
msgstr "Vergleiche Revisionen"
msgid "Compare changes"
-msgstr ""
+msgstr "Änderungen vergleichen"
msgid "Compare changes with the last commit"
msgstr "Vergleiche Änderungen mit dem letzten Commit"
@@ -5737,6 +5904,12 @@ msgstr ""
msgid "Compliance framework (optional)"
msgstr ""
+msgid "Compliance frameworks"
+msgstr ""
+
+msgid "ComplianceDashboard|created by:"
+msgstr ""
+
msgid "ComplianceFramework|GDPR"
msgstr "DSGVO"
@@ -5792,7 +5965,7 @@ msgid "Configure Let's Encrypt"
msgstr "Let's Encrypt konfigurieren"
msgid "Configure Prometheus"
-msgstr ""
+msgstr "Prometheus konfigurieren"
msgid "Configure Tracing"
msgstr "Tracing konfigurieren"
@@ -5831,13 +6004,13 @@ msgid "Configure the way a user creates a new account."
msgstr "Konfiguriere, wie ein(e) Benutzer(in) ein neues Konto erstellt."
msgid "Confirm"
-msgstr ""
+msgstr "Bestätigen"
msgid "Confirmation email sent to %{email}"
-msgstr ""
+msgstr "Bestätigung per E-mail an %{email} gesendet"
msgid "Confirmation required"
-msgstr ""
+msgstr "Bestätigung erforderlich"
msgid "Congratulations! You have enabled Two-factor Authentication!"
msgstr ""
@@ -5870,14 +6043,17 @@ msgid "Connection failed"
msgstr "Verbindung fehlgeschlagen"
msgid "Connection failure"
-msgstr ""
+msgstr "Verbindungsfehler"
msgid "Connection timed out"
msgstr "Zeitüberschreitung der Verbindung"
-msgid "Contact sales to upgrade"
+msgid "Connection timeout"
msgstr ""
+msgid "Contact sales to upgrade"
+msgstr "Wenden Sie sich an Sales, um ein Upgrade durchzuführen"
+
msgid "Contact support"
msgstr ""
@@ -5888,7 +6064,7 @@ msgid "Container Registry tag expiration policy"
msgstr "Tag-Ablaufrichtlinie für Container-Registry"
msgid "Container Scanning"
-msgstr ""
+msgstr "Container Scanning"
msgid "Container does not exist"
msgstr ""
@@ -5930,9 +6106,6 @@ msgstr ""
msgid "ContainerRegistry|CLI Commands"
msgstr ""
-msgid "ContainerRegistry|Compressed Size"
-msgstr ""
-
msgid "ContainerRegistry|Container Registry"
msgstr ""
@@ -5948,6 +6121,9 @@ msgstr ""
msgid "ContainerRegistry|Copy push command"
msgstr ""
+msgid "ContainerRegistry|Delete selected"
+msgstr ""
+
msgid "ContainerRegistry|Docker connection error"
msgstr ""
@@ -5981,16 +6157,16 @@ msgstr ""
msgid "ContainerRegistry|If you are not already logged in, you need to authenticate to the Container Registry by using your GitLab username and password. If you have %{twofaDocLinkStart}Two-Factor Authentication%{twofaDocLinkEnd} enabled, use a %{personalAccessTokensDocLinkStart}Personal Access Token%{personalAccessTokensDocLinkEnd} instead of a password."
msgstr ""
-msgid "ContainerRegistry|Image ID"
+msgid "ContainerRegistry|Image ID: %{imageId}"
msgstr ""
msgid "ContainerRegistry|Image Repositories"
msgstr ""
-msgid "ContainerRegistry|Keep and protect the images that matter most."
+msgid "ContainerRegistry|Image tags"
msgstr ""
-msgid "ContainerRegistry|Last Updated"
+msgid "ContainerRegistry|Keep and protect the images that matter most."
msgstr ""
msgid "ContainerRegistry|Login"
@@ -6003,6 +6179,9 @@ msgid "ContainerRegistry|Number of tags to retain:"
msgstr ""
msgid "ContainerRegistry|Please contact your administrator."
+msgstr "Bitte wende Dich an Deine(n) Administrator(in)."
+
+msgid "ContainerRegistry|Published %{timeInfo}"
msgstr ""
msgid "ContainerRegistry|Push an image"
@@ -6020,9 +6199,6 @@ msgstr ""
msgid "ContainerRegistry|Remove repository"
msgstr "Repository entfernen"
-msgid "ContainerRegistry|Remove selected tags"
-msgstr ""
-
msgid "ContainerRegistry|Remove tag"
msgid_plural "ContainerRegistry|Remove tags"
msgstr[0] ""
@@ -6052,9 +6228,6 @@ msgstr ""
msgid "ContainerRegistry|Sorry, your filter produced no results."
msgstr ""
-msgid "ContainerRegistry|Tag"
-msgstr "Tag"
-
msgid "ContainerRegistry|Tag expiration policy"
msgstr ""
@@ -6199,21 +6372,6 @@ msgstr ""
msgid "Control the display of third party offers."
msgstr "Steuere die Anzeige von Angeboten Dritter."
-msgid "Control the maximum concurrency of LFS/attachment backfill for this secondary node"
-msgstr ""
-
-msgid "Control the maximum concurrency of container repository operations for this Geo node"
-msgstr ""
-
-msgid "Control the maximum concurrency of repository backfill for this secondary node"
-msgstr "Steuere die maximale Parallelität des Repository-Backfills für diesen sekundären Knoten"
-
-msgid "Control the maximum concurrency of verification operations for this Geo node"
-msgstr ""
-
-msgid "Control the minimum interval in days that a repository should be reverified for this primary node"
-msgstr ""
-
msgid "Cookie domain"
msgstr "Cookie-Domain"
@@ -6224,13 +6382,13 @@ msgid "Copied labels and milestone from %{source_issuable_reference}."
msgstr ""
msgid "Copy"
-msgstr ""
+msgstr "Kopieren"
msgid "Copy %{field}"
msgstr ""
msgid "Copy %{http_label} clone URL"
-msgstr ""
+msgstr "Kopiere %{http_label} Clone-URL"
msgid "Copy %{protocol} clone URL"
msgstr "Kopiere %{protocol} clone-URL"
@@ -6248,7 +6406,7 @@ msgid "Copy External ID to clipboard"
msgstr ""
msgid "Copy ID"
-msgstr ""
+msgstr "ID kopieren"
msgid "Copy KRB5 clone URL"
msgstr ""
@@ -6257,22 +6415,22 @@ msgid "Copy SSH clone URL"
msgstr "Kopiere SSH clone-URL"
msgid "Copy SSH public key"
-msgstr ""
+msgstr "Öffentlichen SSH-Schlüssel kopieren"
msgid "Copy URL"
-msgstr ""
+msgstr "URL kopieren"
msgid "Copy branch name"
msgstr ""
msgid "Copy command"
-msgstr ""
+msgstr "Befehl kopieren"
msgid "Copy commands"
-msgstr ""
+msgstr "Befehle kopieren"
msgid "Copy commit SHA"
-msgstr ""
+msgstr "Commit-SHA kopieren"
msgid "Copy environment"
msgstr ""
@@ -6355,6 +6513,9 @@ msgstr ""
msgid "Could not find design."
msgstr ""
+msgid "Could not find iteration"
+msgstr ""
+
msgid "Could not remove the trigger."
msgstr ""
@@ -6386,9 +6547,15 @@ msgid "Could not upload your designs as one or more files uploaded are not suppo
msgstr ""
msgid "Country"
-msgstr ""
+msgstr "Land"
msgid "Coverage"
+msgstr "Abdeckung"
+
+msgid "Coverage Fuzzing"
+msgstr ""
+
+msgid "Crash State"
msgstr ""
msgid "Create"
@@ -6419,7 +6586,7 @@ msgid "Create a local proxy for storing frequently used upstream images. %{link_
msgstr ""
msgid "Create a merge request"
-msgstr ""
+msgstr "Merge-Request erzeugen"
msgid "Create a new branch"
msgstr "Erstelle einen neuen Branch"
@@ -6439,6 +6606,9 @@ msgstr ""
msgid "Create a personal access token on your account to pull or push via %{protocol}."
msgstr "Erstelle einen persönlichen Zugriffstoken in deinem Konto um mittels %{protocol} zu übertragen (push) oder abzurufen (pull)."
+msgid "Create a project pre-populated with the necessary files to get you started quickly."
+msgstr ""
+
msgid "Create an account using:"
msgstr ""
@@ -6449,7 +6619,7 @@ msgid "Create and provide your GitHub %{link_start}Personal Access Token%{link_e
msgstr ""
msgid "Create board"
-msgstr ""
+msgstr "Board erstellen"
msgid "Create branch"
msgstr "Branch erstellen"
@@ -6497,13 +6667,13 @@ msgid "Create merge request and branch"
msgstr "Merge-Request und Branch erstellen"
msgid "Create milestone"
-msgstr ""
+msgstr "Meilenstein erstellen"
msgid "Create new"
msgstr ""
msgid "Create new board"
-msgstr ""
+msgstr "Neues Board erstellen"
msgid "Create new branch"
msgstr "Neuen Branch erstellen"
@@ -6524,7 +6694,7 @@ msgid "Create new..."
msgstr "Neu erstellen..."
msgid "Create project"
-msgstr ""
+msgstr "Projekt anlegen"
msgid "Create project label"
msgstr "Projektlabel erstellen"
@@ -6607,6 +6777,9 @@ msgstr ""
msgid "Creates branch '%{branch_name}' and a merge request to resolve this issue."
msgstr ""
+msgid "Creating"
+msgstr ""
+
msgid "Creating epic"
msgstr "Epic wird angelegt"
@@ -6650,7 +6823,7 @@ msgid "Current node"
msgstr "Aktueller Knoten"
msgid "Current node must be the primary node or you will be locking yourself out"
-msgstr ""
+msgstr "Der aktuelle Knoten muss der primäre Knoten sein, sonst sperrst Du dich selbst aus"
msgid "Current password"
msgstr "Aktuelles Passwort"
@@ -6658,7 +6831,10 @@ msgstr "Aktuelles Passwort"
msgid "Current vulnerabilities count"
msgstr ""
-msgid "CurrentUser|Buy CI minutes"
+msgid "CurrentUser|Buy Pipeline minutes"
+msgstr ""
+
+msgid "CurrentUser|One of your groups is running out"
msgstr ""
msgid "CurrentUser|Profile"
@@ -6673,6 +6849,9 @@ msgstr ""
msgid "CurrentUser|Upgrade"
msgstr ""
+msgid "Custom Attributes"
+msgstr ""
+
msgid "Custom CI configuration path"
msgstr ""
@@ -6713,7 +6892,7 @@ msgid "CustomCycleAnalytics|Enter a name for the stage"
msgstr ""
msgid "CustomCycleAnalytics|Name"
-msgstr ""
+msgstr "Name"
msgid "CustomCycleAnalytics|New stage"
msgstr ""
@@ -6761,13 +6940,13 @@ msgid "Customize how Google Code email addresses and usernames are imported into
msgstr "Passe an, wie E-Mail-Adressen und Benutzernamen von Google Code in GitLab importiert werden. Im nächsten Schritt kannst du die Projekte auswählen, die du importieren möchtest."
msgid "Customize icon"
-msgstr ""
+msgstr "Symbol anpassen"
msgid "Customize language and region related settings."
msgstr "Sprach- und Region-bezogene Einstellungen anpassen."
msgid "Customize name"
-msgstr ""
+msgstr "Name anpassen"
msgid "Customize your pipeline configuration, view your pipeline status and coverage report."
msgstr "Passe deine Pipeline-Konfiguration an und zeige deinen Pipeline-Status und den Abdeckungsbericht an."
@@ -6936,12 +7115,18 @@ msgstr ""
msgid "DAG"
msgstr ""
+msgid "DAG visualization requires at least 3 dependent jobs."
+msgstr ""
+
msgid "DNS"
msgstr "DNS"
msgid "Dashboard"
msgstr "Dashboard"
+msgid "Dashboard uid not found"
+msgstr ""
+
msgid "DashboardProjects|All"
msgstr "Alle"
@@ -6952,13 +7137,13 @@ msgid "DashboardProjects|Trending"
msgstr ""
msgid "Dashboards"
-msgstr ""
+msgstr "Dashboards"
msgid "Dashboard|%{firstProject} and %{secondProject}"
-msgstr ""
+msgstr "%{firstProject} und %{secondProject}"
msgid "Dashboard|%{firstProject}, %{rest}, and %{secondProject}"
-msgstr ""
+msgstr "%{firstProject}, %{rest} und %{secondProject}"
msgid "Dashboard|Unable to add %{invalidProjects}. This dashboard is available for public projects, and private projects in groups with a Silver plan."
msgstr ""
@@ -6966,6 +7151,9 @@ msgstr ""
msgid "Data is still calculating..."
msgstr "Daten werden noch berechnet..."
+msgid "Datasource name not found"
+msgstr ""
+
msgid "Date"
msgstr ""
@@ -6979,16 +7167,16 @@ msgid "Day of month"
msgstr ""
msgid "DayTitle|F"
-msgstr ""
+msgstr "F"
msgid "DayTitle|M"
msgstr ""
msgid "DayTitle|S"
-msgstr ""
+msgstr "S"
msgid "DayTitle|W"
-msgstr ""
+msgstr "M"
msgid "Days"
msgstr ""
@@ -7021,7 +7209,7 @@ msgid "Default artifacts expiration"
msgstr ""
msgid "Default branch"
-msgstr ""
+msgstr "Standard-Branch"
msgid "Default branch and protected branches"
msgstr ""
@@ -7036,7 +7224,7 @@ msgid "Default deletion adjourned period"
msgstr ""
msgid "Default description template for issues"
-msgstr ""
+msgstr "Standard Beschreibungsvorlage für Tickets"
msgid "Default description template for merge requests"
msgstr ""
@@ -7141,9 +7329,12 @@ msgid "Delete snippet?"
msgstr "Codeausschnitt löschen?"
msgid "Delete source branch"
-msgstr ""
+msgstr "Quellbranch löschen"
msgid "Delete this attachment"
+msgstr "Diesen Anhang löschen"
+
+msgid "Delete user list"
msgstr ""
msgid "Delete variable"
@@ -7174,7 +7365,7 @@ msgid "Deleted chat nickname: %{chat_name}!"
msgstr ""
msgid "Deleted in this version"
-msgstr ""
+msgstr "In dieser Version gelöscht"
msgid "Deleting"
msgstr ""
@@ -7307,7 +7498,7 @@ msgid "Deploy progress not found. To see pods, ensure your environment matches %
msgstr ""
msgid "Deploy to..."
-msgstr ""
+msgstr "Bereitstellen auf..."
msgid "DeployBoard|Matching on the %{appLabel} label has been removed for deploy boards. To see all instances on your board, you must update your chart and redeploy."
msgstr ""
@@ -7454,7 +7645,7 @@ msgid "DeployTokens|Your new project deploy token has been created."
msgstr "Dein neuer Projektbereitstellungstoken wurde erstellt."
msgid "Deployed"
-msgstr ""
+msgstr "Bereitgestellt"
msgid "Deployed to"
msgstr "Bereitgestellt für"
@@ -7462,6 +7653,9 @@ msgstr "Bereitgestellt für"
msgid "Deploying to"
msgstr "Bereitstellung für"
+msgid "Deploying to AWS is easy with GitLab"
+msgstr ""
+
msgid "Deployment Frequency"
msgstr ""
@@ -7496,7 +7690,7 @@ msgid "Describe the goal of the changes and what reviewers should be aware of."
msgstr ""
msgid "Describe the requirement here"
-msgstr ""
+msgstr "Beschreibe die Anforderung hier"
msgid "Description"
msgstr "Beschreibung"
@@ -7649,7 +7843,7 @@ msgid "Details"
msgstr "Details"
msgid "Details (default)"
-msgstr ""
+msgstr "Details (Standard)"
msgid "Detect host keys"
msgstr "Hostschlüssel erkennen"
@@ -7706,7 +7900,7 @@ msgid "Disable two-factor authentication"
msgstr ""
msgid "Disabled"
-msgstr ""
+msgstr "Deaktiviert"
msgid "Disabled mirrors can only be enabled by instance owners. It is recommended that you delete them."
msgstr ""
@@ -7730,13 +7924,13 @@ msgid "Discard review"
msgstr "Review verwerfen"
msgid "DiscordService|Discord Notifications"
-msgstr ""
+msgstr "Discord-Benachrichtigungen"
msgid "DiscordService|Receive event notifications in Discord"
msgstr ""
msgid "Discover GitLab Geo"
-msgstr ""
+msgstr "Entdecke GitLab Geo"
msgid "Discover projects, groups and snippets. Share your projects with others"
msgstr "Entdecke Projekte, Gruppen und Codeausschnitte. Teile deine Projekte mit anderen"
@@ -7766,10 +7960,10 @@ msgid "Discover|Upgrade now"
msgstr ""
msgid "Discuss a specific suggestion or question"
-msgstr ""
+msgstr "Spezifische(n) Vorschlag oder Frage diskutieren"
msgid "Discuss a specific suggestion or question that needs to be resolved"
-msgstr ""
+msgstr "Spezifische(n) Vorschlag oder Frage diskutieren, um eine Lösung zu finden"
msgid "Discuss a specific suggestion or question that needs to be resolved."
msgstr "Diskutiere einen speziellen Vorschlag oder eine Frage, die gelöst werden muss."
@@ -7824,6 +8018,9 @@ msgstr ""
msgid "Dismissed on pipeline %{pipelineLink} at %{projectLink}"
msgstr ""
+msgid "Display alerts from all your monitoring tools directly within GitLab."
+msgstr ""
+
msgid "Display name"
msgstr ""
@@ -7840,7 +8037,7 @@ msgid "Do you want to customize how Google Code email addresses and usernames ar
msgstr "Möchtest du festlegen, wie die E-Mail-Adressen und Nutzernamen von Google Code in GitLab importiert werden?"
msgid "Dockerfile"
-msgstr ""
+msgstr "Dockerfile"
msgid "Documentation"
msgstr ""
@@ -7849,13 +8046,13 @@ msgid "Documentation for popular identity providers"
msgstr "Dokumentation für gängige Identitätsanbieter"
msgid "Doing"
-msgstr ""
+msgstr "In Bearbeitung"
msgid "Domain"
msgstr "Domäne"
msgid "Domain cannot be deleted while associated to one or more clusters."
-msgstr ""
+msgstr "Kann Domain nicht löschen, solange sie mit Cluster(n) verknüpft ist."
msgid "Domain verification is an essential security measure for public GitLab sites. Users are required to demonstrate they control a domain before it is enabled"
msgstr ""
@@ -7881,9 +8078,6 @@ msgstr ""
msgid "Don't show again"
msgstr "Nicht erneut anzeigen"
-msgid "Don't worry, you can access this tour by clicking on the help icon in the top right corner and choose <strong>Learn GitLab</strong>."
-msgstr ""
-
msgid "Done"
msgstr "Erledigt"
@@ -7906,7 +8100,7 @@ msgid "Download as"
msgstr ""
msgid "Download asset"
-msgstr ""
+msgstr "Asset herunterladen"
msgid "Download codes"
msgstr "Codes herunterladen"
@@ -7918,10 +8112,10 @@ msgid "Download export"
msgstr "Export herunterladen"
msgid "Download image"
-msgstr ""
+msgstr "Bild herunterladen"
msgid "Download license"
-msgstr ""
+msgstr "Lizenz herunterladen"
msgid "Download raw data (.csv)"
msgstr ""
@@ -7942,7 +8136,7 @@ msgid "DownloadSource|Download"
msgstr "Herunterladen"
msgid "Downstream"
-msgstr ""
+msgstr "Downstream"
msgid "Downvotes"
msgstr "Negativ bewertet"
@@ -7984,7 +8178,7 @@ msgid "Edit %{name}"
msgstr "%{name} bearbeiten"
msgid "Edit Comment"
-msgstr ""
+msgstr "Kommentar bearbeiten"
msgid "Edit Deploy Key"
msgstr ""
@@ -8002,7 +8196,7 @@ msgid "Edit Milestone"
msgstr "Meilenstein bearbeiten"
msgid "Edit Password"
-msgstr ""
+msgstr "Passwort ändern"
msgid "Edit Pipeline Schedule %{id}"
msgstr "Pipeline-Zeitplan bearbeiten %{id}"
@@ -8010,6 +8204,9 @@ msgstr "Pipeline-Zeitplan bearbeiten %{id}"
msgid "Edit Release"
msgstr "Release bearbeiten"
+msgid "Edit Slack integration"
+msgstr ""
+
msgid "Edit Snippet"
msgstr "Codeausschnitt bearbeiten"
@@ -8020,19 +8217,19 @@ msgid "Edit application"
msgstr "Anwendung bearbeiten"
msgid "Edit board"
-msgstr ""
+msgstr "Board bearbeiten"
msgid "Edit comment"
-msgstr ""
+msgstr "Kommentar bearbeiten"
msgid "Edit dashboard"
msgstr ""
msgid "Edit description"
-msgstr ""
+msgstr "Beschreibung bearbeiten"
msgid "Edit environment"
-msgstr ""
+msgstr "Umgebung bearbeiten"
msgid "Edit file"
msgstr "Datei bearbeiten"
@@ -8049,17 +8246,20 @@ msgstr "Identität für %{user_name} bearbeiten"
msgid "Edit issues"
msgstr "Tickets bearbeiten"
-msgid "Edit public deploy key"
+msgid "Edit iteration"
msgstr ""
+msgid "Edit public deploy key"
+msgstr "Öffentlichen Bereitstellungsschlüssel bearbeiten"
+
msgid "Edit stage"
-msgstr ""
+msgstr "Phase bearbeiten"
msgid "Edit this release"
msgstr "Dieses Release bearbeiten"
msgid "Edit wiki page"
-msgstr ""
+msgstr "Wiki-Seite bearbeiten"
msgid "Edit your most recent comment in a thread (from an empty textarea)"
msgstr ""
@@ -8077,10 +8277,10 @@ msgid "Elasticsearch AWS IAM credentials"
msgstr ""
msgid "Elasticsearch indexing restrictions"
-msgstr ""
+msgstr "Indizierungsbeschränkungen für Elasticsearch"
msgid "Elasticsearch indexing started"
-msgstr ""
+msgstr "Elasticsearch Indizierung gestartet"
msgid "Elasticsearch integration. Elasticsearch AWS IAM."
msgstr "Elasticsearch-Integration. Elasticsearch AWS IAM."
@@ -8097,9 +8297,12 @@ msgstr "Keine. Wähle Projekte zum Indexieren aus."
msgid "Email"
msgstr "E-Mail"
-msgid "Email address"
+msgid "Email Notification"
msgstr ""
+msgid "Email address"
+msgstr "E-Mail-Adresse"
+
msgid "Email could not be sent"
msgstr ""
@@ -8109,6 +8312,9 @@ msgstr "E-Mail-Anzeigename"
msgid "Email not verified. Please verify your email in Salesforce."
msgstr ""
+msgid "Email notification for unknown sign-ins"
+msgstr ""
+
msgid "Email patch"
msgstr "E-Mail-Patch"
@@ -8125,7 +8331,7 @@ msgid "Email the pipelines status to a list of recipients."
msgstr ""
msgid "EmailError|It appears that the email is blank. Make sure your reply is at the top of the email, we can't process inline replies."
-msgstr ""
+msgstr "Die E-Mail scheint leer zu sein. Stelle sicher, dass sich die Antwort oben in der E-Mail befindet. Wir können keine Inline-Antworten verarbeiten."
msgid "EmailError|The thread you are replying to no longer exists, perhaps it was deleted? If you believe this is in error, contact a staff member."
msgstr ""
@@ -8149,7 +8355,7 @@ msgid "EmailError|Your account has been blocked. If you believe this is in error
msgstr ""
msgid "EmailToken|reset it"
-msgstr ""
+msgstr "zurücksetzen"
msgid "EmailToken|resetting..."
msgstr ""
@@ -8340,6 +8546,9 @@ msgstr "Endet am (UTC)"
msgid "Enforce DNS rebinding attack protection"
msgstr ""
+msgid "Enforce personal access token expiration"
+msgstr ""
+
msgid "Ensure connectivity is available from the GitLab server to the Prometheus server"
msgstr ""
@@ -8409,6 +8618,9 @@ msgstr "Gib den Titel des Merge-Requests ein"
msgid "Enter the name of your application, and we'll return a unique %{type}."
msgstr ""
+msgid "Enter weights for storages for new repositories."
+msgstr ""
+
msgid "Enter your password to approve"
msgstr ""
@@ -8418,18 +8630,30 @@ msgstr ""
msgid "Environment does not have deployments"
msgstr "Umgebung hat keine Bereitstellungen"
+msgid "Environment is required for Stages::MetricEndpointInserter"
+msgstr ""
+
+msgid "Environment is required for Stages::VariableEndpointInserter"
+msgstr ""
+
msgid "Environment scope"
msgstr ""
+msgid "Environment variables are applied to all project environments in this instance via the Runner. You can use environment variables for passwords, secret keys, etc. Make variables available to the running application by prepending the variable key with <code>K8S_SECRET_</code>. You can set variables to be:"
+msgstr ""
+
msgid "Environment variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. Additionally, they can be masked so they are hidden in job logs, though they must match certain regexp requirements to do so. You can use environment variables for passwords, secret keys, or whatever you want."
msgstr ""
msgid "Environment variables are configured by your administrator to be %{link_start}protected%{link_end} by default"
msgstr ""
-msgid "Environment:"
+msgid "Environment variables on this GitLab instance are configured to be %{link_start}protected%{link_end} by default"
msgstr ""
+msgid "Environment:"
+msgstr "Umgebung:"
+
msgid "EnvironmentDashboard|API"
msgstr ""
@@ -8452,25 +8676,25 @@ msgid "Environments in %{name}"
msgstr ""
msgid "EnvironmentsDashboard|Add a project to the dashboard"
-msgstr ""
+msgstr "Ein Projekt zum Dashboard hinzufügen"
msgid "EnvironmentsDashboard|Add projects"
-msgstr ""
+msgstr "Projekte hinzufügen"
msgid "EnvironmentsDashboard|Environments Dashboard"
msgstr ""
msgid "EnvironmentsDashboard|Job: %{job}"
-msgstr ""
+msgstr "Job: %{job}"
msgid "EnvironmentsDashboard|More actions"
-msgstr ""
+msgstr "Weitere Aktionen"
msgid "EnvironmentsDashboard|More information"
msgstr ""
msgid "EnvironmentsDashboard|Remove"
-msgstr ""
+msgstr "Entfernen"
msgid "EnvironmentsDashboard|The environments dashboard provides a summary of each project's environments' status, including pipeline and alert statuses."
msgstr ""
@@ -8629,7 +8853,7 @@ msgid "Environments|Stop environment"
msgstr "Umgebung stoppen"
msgid "Environments|Stopping"
-msgstr ""
+msgstr "Stoppe"
msgid "Environments|There was an error fetching the logs. Please try again."
msgstr ""
@@ -8755,7 +8979,7 @@ msgid "Error Details"
msgstr ""
msgid "Error Tracking"
-msgstr ""
+msgstr "Fehlerverfolgung"
msgid "Error creating epic"
msgstr "Fehler beim Erstellen des Epics"
@@ -8809,13 +9033,13 @@ msgid "Error loading branches."
msgstr "Fehler beim Laden der Branches."
msgid "Error loading burndown chart data"
-msgstr ""
+msgstr "Fehler beim Laden der Burndown-Diagrammdaten"
msgid "Error loading countries data."
msgstr ""
msgid "Error loading file viewer."
-msgstr ""
+msgstr "Fehler beim Laden des Dateibetrachters."
msgid "Error loading last commit."
msgstr "Fehler beim Laden des letzten Commits."
@@ -8839,7 +9063,7 @@ msgid "Error loading template."
msgstr "Fehler beim Laden der Vorlage."
msgid "Error loading viewer"
-msgstr ""
+msgstr "Fehler beim Laden des Viewers"
msgid "Error occurred when fetching sidebar data"
msgstr ""
@@ -8958,6 +9182,9 @@ msgstr "Filtere alle"
msgid "EventFilterBy|Filter by comments"
msgstr "Filtere nach Kommentaren"
+msgid "EventFilterBy|Filter by designs"
+msgstr ""
+
msgid "EventFilterBy|Filter by epic events"
msgstr ""
@@ -9090,15 +9317,15 @@ msgstr "Alle erweitern"
msgid "Expand approvers"
msgstr "Genehmigungsberechtigte anzeigen"
-msgid "Expand child epics"
-msgstr ""
-
msgid "Expand down"
msgstr ""
msgid "Expand dropdown"
msgstr ""
+msgid "Expand milestones"
+msgstr ""
+
msgid "Expand sidebar"
msgstr "Menüleiste ausklappen"
@@ -9114,6 +9341,9 @@ msgstr "Ablaufzeitpunkt"
msgid "Expiration date"
msgstr "Ablaufdatum"
+msgid "Expiration not enforced"
+msgstr ""
+
msgid "Expiration policy for the Container Registry is a perfect solution for keeping the Registry space down while still enjoying the full power of GitLab CI/CD."
msgstr ""
@@ -9234,6 +9464,9 @@ msgstr "Fehlgeschlagen"
msgid "Failed Jobs"
msgstr "Fehlgeschlagene Jobs"
+msgid "Failed on"
+msgstr ""
+
msgid "Failed to add a Zoom meeting"
msgstr ""
@@ -9294,13 +9527,19 @@ msgstr ""
msgid "Failed to install."
msgstr ""
+msgid "Failed to load assignees. Please try again."
+msgstr ""
+
+msgid "Failed to load authors. Please try again."
+msgstr ""
+
msgid "Failed to load emoji list."
msgstr "Fehler beim Laden der Emoji-Liste."
msgid "Failed to load error details from Sentry."
msgstr ""
-msgid "Failed to load errors from Sentry. Error message: %{errorMessage}"
+msgid "Failed to load errors from Sentry."
msgstr ""
msgid "Failed to load group activity metrics. Please try again."
@@ -9381,6 +9620,9 @@ msgstr ""
msgid "Failed to set due date because the date format is invalid."
msgstr ""
+msgid "Failed to set iteration on this issue. Please try again."
+msgstr ""
+
msgid "Failed to signing using smartcard authentication"
msgstr "Signieren mit Smartcard-Authentifizierung fehlgeschlagen"
@@ -9409,7 +9651,7 @@ msgid "Failed to upload object map file"
msgstr ""
msgid "Failed to verify domain ownership"
-msgstr ""
+msgstr "Domain-Besitz konnte nicht überprüft werden."
msgid "Failure"
msgstr "Fehlschlag"
@@ -9603,13 +9845,10 @@ msgstr ""
msgid "FeatureFlags|Target environments"
msgstr ""
-msgid "FeatureFlags|There are no active feature flags"
+msgid "FeatureFlags|There was an error fetching the feature flags."
msgstr ""
-msgid "FeatureFlags|There are no inactive feature flags"
-msgstr ""
-
-msgid "FeatureFlags|There was an error fetching the feature flags."
+msgid "FeatureFlags|There was an error retrieving user lists"
msgstr ""
msgid "FeatureFlags|Try again in a few moments or contact your support team."
@@ -9621,9 +9860,18 @@ msgstr ""
msgid "FeatureFlag|Delete strategy"
msgstr ""
+msgid "FeatureFlag|List"
+msgstr ""
+
msgid "FeatureFlag|Percentage"
msgstr "Prozentsatz"
+msgid "FeatureFlag|Select a user list"
+msgstr ""
+
+msgid "FeatureFlag|There are no configured user lists"
+msgstr ""
+
msgid "FeatureFlag|Type"
msgstr ""
@@ -9780,6 +10028,12 @@ msgstr ""
msgid "Filter..."
msgstr "Filter..."
+msgid "Find File"
+msgstr ""
+
+msgid "Find bugs in your code with coverage-guided fuzzing"
+msgstr ""
+
msgid "Find by path"
msgstr "Finde über den Pfad"
@@ -10044,6 +10298,9 @@ msgstr ""
msgid "Geo allows you to replicate your GitLab instance to other geographical locations."
msgstr "Mit Geo kannst du deine GitLab-Instanz an andere geografische Orte replizieren."
+msgid "Geo nodes are paused using a command run on the node"
+msgstr ""
+
msgid "GeoNodeStatusEvent|%{timeAgoStr} (%{pendingEvents} events)"
msgstr ""
@@ -10128,6 +10385,9 @@ msgstr ""
msgid "GeoNodes|Not checksummed"
msgstr "Keine Prüfsumme"
+msgid "GeoNodes|Package files"
+msgstr ""
+
msgid "GeoNodes|Pausing replication stops the sync process. Are you sure?"
msgstr ""
@@ -10146,6 +10406,9 @@ msgstr "Replikations-Slot WAL"
msgid "GeoNodes|Replication slots"
msgstr "Replikations-Slots"
+msgid "GeoNodes|Replication status"
+msgstr ""
+
msgid "GeoNodes|Repositories"
msgstr "Repositorien"
@@ -10255,13 +10518,13 @@ msgid "Geo|Failed"
msgstr "Fehlgeschlagen"
msgid "Geo|Filter by status"
-msgstr ""
+msgstr "Nach Status filtern"
msgid "Geo|Geo Status"
msgstr "Geo-Status"
msgid "Geo|In progress"
-msgstr ""
+msgstr "In Bearbeitung"
msgid "Geo|In sync"
msgstr "Wird synchronisiert"
@@ -10324,7 +10587,7 @@ msgid "Geo|Remove entry"
msgstr ""
msgid "Geo|Remove tracking database entry"
-msgstr ""
+msgstr "Tracking-Datenbankeintrag entfernen"
msgid "Geo|Resync"
msgstr "Resynchronisation"
@@ -10356,6 +10619,9 @@ msgstr ""
msgid "Geo|Synchronization failed - %{error}"
msgstr "Synchronisierung fehlgeschlagen - %{error}"
+msgid "Geo|Synchronization of %{itemTitle} is disabled."
+msgstr ""
+
msgid "Geo|The database is currently %{db_lag} behind the primary node."
msgstr ""
@@ -10510,7 +10776,7 @@ msgid "GitLab project export"
msgstr "GitLab-Projekt exportieren"
msgid "GitLab restart is required to apply changes."
-msgstr ""
+msgstr "GitLab-Neustart nötig, um Änderungen anzuwenden."
msgid "GitLab single sign on URL"
msgstr "GitLab Single Sign On-URL"
@@ -10552,7 +10818,7 @@ msgid "GitLabPages|Edit"
msgstr ""
msgid "GitLabPages|Expired"
-msgstr ""
+msgstr "Abgelaufen"
msgid "GitLabPages|Force HTTPS (requires valid certificates)"
msgstr ""
@@ -10668,6 +10934,9 @@ msgstr ""
msgid "Go to %{link_to_google_takeout}."
msgstr "Gehe zu %{link_to_google_takeout}."
+msgid "Go to <strong>Issues</strong> > <strong>Boards</strong> to access your personalized learning issue board."
+msgstr ""
+
msgid "Go to Pipelines"
msgstr ""
@@ -10767,9 +11036,6 @@ msgstr ""
msgid "Go to your snippets"
msgstr ""
-msgid "Golden Tanuki"
-msgstr ""
-
msgid "Google Cloud Platform"
msgstr ""
@@ -10791,6 +11057,9 @@ msgstr "Verstanden!"
msgid "Grafana URL"
msgstr ""
+msgid "Grafana response contains invalid json"
+msgstr ""
+
msgid "GrafanaIntegration|API Token"
msgstr ""
@@ -10839,9 +11108,6 @@ msgstr ""
msgid "Group %{group_name} was successfully created."
msgstr ""
-msgid "Group '%{group_name}' is being imported."
-msgstr ""
-
msgid "Group Audit Events"
msgstr ""
@@ -10878,9 +11144,6 @@ msgstr "Gruppenavatar"
msgid "Group by:"
msgstr ""
-msgid "Group could not be imported: %{errors}"
-msgstr ""
-
msgid "Group description"
msgstr "Gruppenbeschreibung"
@@ -10908,9 +11171,15 @@ msgstr ""
msgid "Group has not been marked for deletion"
msgstr ""
+msgid "Group import could not be scheduled"
+msgstr ""
+
msgid "Group info:"
msgstr "Gruppeninfo:"
+msgid "Group is required when cluster_type is :group"
+msgstr ""
+
msgid "Group maintainers can register group runners in the %{link}"
msgstr "Gruppenbetreuer können Gruppen-Runner unter %{link} registrieren"
@@ -10974,6 +11243,24 @@ msgstr "Merge-Requests erstellt"
msgid "GroupActivyMetrics|Recent activity (last 90 days)"
msgstr ""
+msgid "GroupImport|Failed to import group."
+msgstr ""
+
+msgid "GroupImport|Group '%{group_name}' is being imported."
+msgstr ""
+
+msgid "GroupImport|Group could not be imported: %{errors}"
+msgstr ""
+
+msgid "GroupImport|Please wait while we import the group for you. Refresh at will."
+msgstr ""
+
+msgid "GroupImport|The group was successfully imported."
+msgstr ""
+
+msgid "GroupImport|Unable to process group import file"
+msgstr ""
+
msgid "GroupRoadmap|%{dateWord} – No end date"
msgstr ""
@@ -11155,7 +11442,7 @@ msgid "GroupSettings|Disable group mentions"
msgstr ""
msgid "GroupSettings|Export group"
-msgstr ""
+msgstr "Gruppe exportieren"
msgid "GroupSettings|If the parent group's visibility is lower than the group current visibility, visibility levels for subgroups and projects will be changed to match the new parent group's visibility."
msgstr ""
@@ -11241,6 +11528,9 @@ msgstr ""
msgid "Groups (%{groups})"
msgstr "Gruppen (%{groups})"
+msgid "Groups and projects"
+msgstr ""
+
msgid "Groups can also be nested by creating %{subgroup_docs_link_start}subgroups%{subgroup_docs_link_end}."
msgstr "Gruppen können durch %{subgroup_docs_link_start}Untergruppen%{subgroup_docs_link_end} verschachtelt werden."
@@ -11286,6 +11576,33 @@ msgstr "Keine Gruppen gefunden"
msgid "GroupsEmptyState|You can manage your group member’s permissions and access to each project in the group."
msgstr "Du kannst die Berechtigungen deiner Gruppenmitglieder und den Zugriff auf jedes Projekt in der Gruppe verwalten."
+msgid "GroupsNew|Contact an administrator to enable options for importing your group."
+msgstr ""
+
+msgid "GroupsNew|Create"
+msgstr ""
+
+msgid "GroupsNew|Create group"
+msgstr ""
+
+msgid "GroupsNew|GitLab group export"
+msgstr ""
+
+msgid "GroupsNew|Import"
+msgstr ""
+
+msgid "GroupsNew|Import group"
+msgstr ""
+
+msgid "GroupsNew|My Awesome Group"
+msgstr ""
+
+msgid "GroupsNew|No import options available"
+msgstr ""
+
+msgid "GroupsNew|To copy a GitLab group between installations, navigate to the group settings page for the original installation, generate an export file, and upload it here."
+msgstr ""
+
msgid "GroupsTree|Are you sure you want to leave the \"%{fullName}\" group?"
msgstr "Bist du sicher, dass du die Gruppe \"%{fullName}\" verlassen willst?"
@@ -11388,6 +11705,9 @@ msgstr ""
msgid "Helps reduce request volume for protected paths"
msgstr ""
+msgid "Here are all your projects in your group, including the one you just created. To start, let’s take a look at your personalized learning project which will help you learn about GitLab at your own pace."
+msgstr ""
+
msgid "Here you will find recent merge request activity"
msgstr ""
@@ -11402,6 +11722,9 @@ msgid_plural "Hide charts"
msgstr[0] ""
msgstr[1] ""
+msgid "Hide details"
+msgstr ""
+
msgid "Hide file browser"
msgstr ""
@@ -11411,6 +11734,9 @@ msgstr ""
msgid "Hide host keys manual input"
msgstr "Manuelle Eingabe der Host-Tasten ausblenden"
+msgid "Hide list"
+msgstr ""
+
msgid "Hide marketing-related entries from help"
msgstr ""
@@ -11431,9 +11757,6 @@ msgstr[1] "Werte ausblenden"
msgid "Hide values"
msgstr ""
-msgid "Hiding all labels"
-msgstr ""
-
msgid "High or unknown vulnerabilities present"
msgstr ""
@@ -11518,6 +11841,9 @@ msgstr "ID"
msgid "ID:"
msgstr ""
+msgid "IDE"
+msgstr ""
+
msgid "IDE|Allow live previews of JavaScript projects in the Web IDE using CodeSandbox Live Preview."
msgstr ""
@@ -11608,12 +11934,12 @@ msgstr "Wenn deaktiviert, hängt der Zugriffslevel von den Berechtigungen des/de
msgid "If enabled"
msgstr "When aktiv"
+msgid "If enabled, GitLab will handle Object Storage replication using Geo. %{linkStart}More information%{linkEnd}"
+msgstr ""
+
msgid "If enabled, access to projects will be validated on an external service using their classification label."
msgstr "Wenn diese Option aktiviert ist, wird der Zugriff auf Projekte mit einem externen Service anhand ihrer Klassifizierungslabels überprüft."
-msgid "If enabled, and if object storage is enabled, GitLab will handle Object Storage replication using Geo"
-msgstr ""
-
msgid "If there is no previous license or if the previous license has expired, some GitLab functionality will be blocked until a new, valid license is uploaded."
msgstr ""
@@ -11973,6 +12299,9 @@ msgid_plural "Instances"
msgstr[0] "Instanz"
msgstr[1] "Instanzen"
+msgid "Instance Configuration"
+msgstr ""
+
msgid "Instance Statistics visibility"
msgstr "Sichtbarkeit der Instanzstatistiken"
@@ -11994,9 +12323,6 @@ msgstr ""
msgid "Integrations"
msgstr "Integrationen"
-msgid "Integrations allow you to integrate GitLab with other applications"
-msgstr ""
-
msgid "Integrations|All details"
msgstr ""
@@ -12108,6 +12434,9 @@ msgstr ""
msgid "Invalid query"
msgstr ""
+msgid "Invalid repository bundle for snippet with id %{snippet_id}"
+msgstr ""
+
msgid "Invalid repository path"
msgstr ""
@@ -12126,6 +12455,9 @@ msgstr ""
msgid "Invalid two-factor code."
msgstr ""
+msgid "Invalid yaml"
+msgstr ""
+
msgid "Invitation"
msgstr ""
@@ -12157,7 +12489,7 @@ msgid "Is using license seat:"
msgstr ""
msgid "Is using seat"
-msgstr ""
+msgstr "Verwendet eine Lizenz"
msgid "IssuableStatus|Closed"
msgstr ""
@@ -12196,7 +12528,7 @@ msgid "Issue first depoloyed to production"
msgstr ""
msgid "Issue label"
-msgstr ""
+msgstr "Issue Label"
msgid "Issue or Merge Request ID is required"
msgstr ""
@@ -12316,10 +12648,10 @@ msgid "IssuesAnalytics|Avg/Month:"
msgstr ""
msgid "IssuesAnalytics|Issues opened"
-msgstr ""
+msgstr "Geöffnete Issues"
msgid "IssuesAnalytics|Issues opened per month"
-msgstr ""
+msgstr "Geöffnete Issues pro Monat"
msgid "IssuesAnalytics|Last 12 months"
msgstr "Letzten 12 Monate"
@@ -12345,15 +12677,24 @@ msgstr ""
msgid "It seems like the Dependency Scanning job ran successfully, but no dependencies have been detected in your project."
msgstr ""
+msgid "It seems that there is currently no available data for code coverage"
+msgstr ""
+
msgid "It's you"
msgstr ""
+msgid "Iteration"
+msgstr ""
+
msgid "Iteration changed to"
msgstr ""
msgid "Iteration removed"
msgstr ""
+msgid "Iteration updated"
+msgstr ""
+
msgid "Iterations"
msgstr ""
@@ -12384,12 +12725,18 @@ msgstr "Jan"
msgid "January"
msgstr "Januar"
+msgid "Jira Issues"
+msgstr ""
+
msgid "Jira import is already running."
msgstr ""
msgid "Jira integration not configured."
msgstr ""
+msgid "Jira project key is not configured"
+msgstr ""
+
msgid "Jira project: %{importProject}"
msgstr ""
@@ -12538,7 +12885,7 @@ msgid "Job|for"
msgstr ""
msgid "Job|into"
-msgstr ""
+msgstr "in"
msgid "Job|with"
msgstr ""
@@ -12570,6 +12917,9 @@ msgstr ""
msgid "Keep divergent refs"
msgstr ""
+msgid "Kerberos access denied"
+msgstr ""
+
msgid "Key"
msgstr ""
@@ -12579,10 +12929,13 @@ msgstr ""
msgid "Key: %{key}"
msgstr ""
-msgid "Keyboard Shortcuts"
+msgid "Keyboard shortcuts"
msgstr ""
-msgid "Keyboard shortcuts"
+msgid "Keys"
+msgstr ""
+
+msgid "Ki"
msgstr ""
msgid "Kubernetes"
@@ -12624,6 +12977,9 @@ msgstr ""
msgid "LDAP"
msgstr ""
+msgid "LDAP Synchronization"
+msgstr ""
+
msgid "LDAP settings"
msgstr ""
@@ -12848,12 +13204,18 @@ msgstr ""
msgid "Learn more about custom project templates"
msgstr ""
+msgid "Learn more about deploying to AWS"
+msgstr ""
+
msgid "Learn more about deploying to a cluster"
msgstr ""
msgid "Learn more about group-level project templates"
msgstr ""
+msgid "Learn more about job dependencies"
+msgstr ""
+
msgid "Learn more about signing commits"
msgstr ""
@@ -12905,9 +13267,21 @@ msgstr ""
msgid "License History"
msgstr ""
+msgid "License ID:"
+msgstr ""
+
+msgid "License URL"
+msgstr ""
+
msgid "License-Check"
msgstr ""
+msgid "LicenseCompliance|%{docLinkStart}License Approvals%{docLinkEnd} are active"
+msgstr ""
+
+msgid "LicenseCompliance|%{docLinkStart}License Approvals%{docLinkEnd} are inactive"
+msgstr ""
+
msgid "LicenseCompliance|Add a license"
msgstr ""
@@ -12929,9 +13303,15 @@ msgstr ""
msgid "LicenseCompliance|Deny"
msgstr ""
+msgid "LicenseCompliance|Learn more about %{linkStart}License Approvals%{linkEnd}"
+msgstr ""
+
msgid "LicenseCompliance|License"
msgstr ""
+msgid "LicenseCompliance|License Approvals"
+msgstr ""
+
msgid "LicenseCompliance|License Compliance detected %d license and policy violation for the source branch only; approval required"
msgid_plural "LicenseCompliance|License Compliance detected %d licenses and policy violations for the source branch only; approval required"
msgstr[0] ""
@@ -12965,7 +13345,7 @@ msgid "LicenseCompliance|License name"
msgstr ""
msgid "LicenseCompliance|License review"
-msgstr ""
+msgstr "Lizenz-Überprüfung"
msgid "LicenseCompliance|Packages"
msgstr "Pakete"
@@ -13102,7 +13482,7 @@ msgid "Line changes"
msgstr ""
msgid "Link Prometheus monitoring to GitLab."
-msgstr ""
+msgstr "Prometheus-Monitoring mit GitLab verknüpfen."
msgid "Link copied"
msgstr ""
@@ -13152,6 +13532,9 @@ msgstr "Listenansicht"
msgid "List your Bitbucket Server repositories"
msgstr "Liste deine Bitbucket-Server-Repositorys auf"
+msgid "Lists"
+msgstr ""
+
msgid "Live preview"
msgstr "Live-Vorschau"
@@ -13252,7 +13635,7 @@ msgid "MB"
msgstr "MB"
msgid "MD5"
-msgstr ""
+msgstr "MD5"
msgid "MERGED"
msgstr ""
@@ -13335,6 +13718,9 @@ msgstr ""
msgid "Manage project labels"
msgstr "Projektlabel verwalten"
+msgid "Manage storage usage"
+msgstr ""
+
msgid "Manage two-factor authentication"
msgstr ""
@@ -13350,6 +13736,9 @@ msgstr "Manifest"
msgid "Manifest file import"
msgstr "Manifestdateiimport"
+msgid "Manifest import"
+msgstr ""
+
msgid "Manual job"
msgstr ""
@@ -13492,11 +13881,17 @@ msgid "Maximum delay (Minutes)"
msgstr ""
msgid "Maximum duration of a session."
-msgstr ""
+msgstr "Maximale Sitzungsdauer."
msgid "Maximum field length"
msgstr ""
+msgid "Maximum file size is 2MB. Please select a smaller file."
+msgstr ""
+
+msgid "Maximum import size (MB)"
+msgstr ""
+
msgid "Maximum job timeout"
msgstr "Maximale Job-Zeitüberschreitung"
@@ -13536,6 +13931,9 @@ msgstr "Maximale Größenbeschränkung für jedes Repository."
msgid "Maximum size of Elasticsearch bulk indexing requests."
msgstr ""
+msgid "Maximum size of import files."
+msgstr ""
+
msgid "Maximum size of individual attachments in comments."
msgstr ""
@@ -13587,6 +13985,9 @@ msgstr ""
msgid "Merge (when the pipeline succeeds)"
msgstr ""
+msgid "Merge Conflicts"
+msgstr ""
+
msgid "Merge Request"
msgstr "Merge-Request"
@@ -13713,6 +14114,9 @@ msgstr "Speichern des Kommentars fehlgeschlagen"
msgid "MergeRequests|Squash task canceled: another squash is already in progress."
msgstr ""
+msgid "MergeRequests|This project does not allow squashing commits when merge requests are accepted."
+msgstr ""
+
msgid "MergeRequests|Thread stays resolved"
msgstr ""
@@ -13866,6 +14270,12 @@ msgstr ""
msgid "MetricsSettings|Add a button to the metrics dashboard linking directly to your existing external dashboard."
msgstr ""
+msgid "MetricsSettings|Choose whether to display dashboard metrics in UTC or the user's local timezone."
+msgstr ""
+
+msgid "MetricsSettings|Dashboard timezone"
+msgstr ""
+
msgid "MetricsSettings|External dashboard URL"
msgstr ""
@@ -13875,6 +14285,12 @@ msgstr ""
msgid "MetricsSettings|Metrics Dashboard"
msgstr ""
+msgid "MetricsSettings|UTC (Coordinated Universal Time)"
+msgstr ""
+
+msgid "MetricsSettings|User's local timezone"
+msgstr ""
+
msgid "Metrics|Add metric"
msgstr ""
@@ -13890,6 +14306,9 @@ msgstr ""
msgid "Metrics|Create metric"
msgstr "Metrik erstellen"
+msgid "Metrics|Current"
+msgstr ""
+
msgid "Metrics|Delete metric"
msgstr ""
@@ -13911,7 +14330,7 @@ msgstr[0] ""
msgstr[1] ""
msgid "Metrics|Expand panel"
-msgstr ""
+msgstr "Panel erweitern"
msgid "Metrics|For grouping similar metrics"
msgstr "Zum Gruppieren ähnlicher Metriken"
@@ -13940,6 +14359,9 @@ msgstr ""
msgid "Metrics|Max"
msgstr ""
+msgid "Metrics|Min"
+msgstr ""
+
msgid "Metrics|Must be a valid PromQL query."
msgstr "Muss eine gültige PromQL-Abfrage sein."
@@ -13955,6 +14377,9 @@ msgstr "Prometheus Query-Dokumentation"
msgid "Metrics|Refresh dashboard"
msgstr ""
+msgid "Metrics|Select a value"
+msgstr ""
+
msgid "Metrics|Star dashboard"
msgstr ""
@@ -13973,12 +14398,18 @@ msgstr "Beim Abrufen der Umgebungsdaten trat ein Fehler auf, bitte versuche es e
msgid "Metrics|There was an error getting annotations information."
msgstr ""
+msgid "Metrics|There was an error getting dashboard validation warnings information."
+msgstr ""
+
msgid "Metrics|There was an error getting deployment information."
msgstr "Es ist ein Fehler beim Abrufen der Bereitstellungsinformationen aufgetreten."
msgid "Metrics|There was an error getting environments information."
msgstr "Es gab ein Fehler beim Abrufen der Umgebungsinformationen."
+msgid "Metrics|There was an error getting options for variable \"%{name}\"."
+msgstr ""
+
msgid "Metrics|There was an error trying to validate your query"
msgstr ""
@@ -14021,6 +14452,9 @@ msgstr ""
msgid "Metrics|You're about to permanently delete this metric. This cannot be undone."
msgstr ""
+msgid "Metrics|Your dashboard schema is invalid. Edit the dashboard to correct the YAML schema."
+msgstr ""
+
msgid "Metrics|e.g. HTTP requests"
msgstr ""
@@ -14036,12 +14470,18 @@ msgstr ""
msgid "Metrics|e.g. req/sec"
msgstr ""
+msgid "Mi"
+msgstr ""
+
msgid "Microsoft Azure"
msgstr ""
msgid "Middleman project with Static Site Editor support"
msgstr ""
+msgid "Migrate your data from an external source like GitHub, Bitbucket, or another instance of GitLab."
+msgstr ""
+
msgid "Migrated %{success_count}/%{total_count} files."
msgstr ""
@@ -14176,6 +14616,9 @@ msgstr ""
msgid "Minimum capacity to be available before we schedule more mirrors preemptively."
msgstr ""
+msgid "Minimum interval in days"
+msgstr ""
+
msgid "Minimum length is %{minimum_password_length} characters"
msgstr ""
@@ -14362,6 +14805,12 @@ msgstr ""
msgid "Multiple uploaders found: %{uploader_types}"
msgstr ""
+msgid "Must match with the %{codeStart}external_url%{codeEnd} in %{codeStart}/etc/gitlab/gitlab.rb%{codeEnd}."
+msgstr ""
+
+msgid "Must match with the %{codeStart}geo_node_name%{codeEnd} in %{codeStart}/etc/gitlab/gitlab.rb%{codeEnd}. %{linkStart}More information%{linkEnd}"
+msgstr ""
+
msgid "My Awesome Group"
msgstr ""
@@ -14476,11 +14925,17 @@ msgstr "Neu"
msgid "New Application"
msgstr "Neue Anwendung"
+msgid "New Branch"
+msgstr ""
+
+msgid "New Deploy Key"
+msgstr ""
+
msgid "New Environment"
msgstr ""
-msgid "New Geo Node"
-msgstr "Neuer Geo-Knoten"
+msgid "New File"
+msgstr ""
msgid "New Group"
msgstr "Neue Gruppe"
@@ -14496,15 +14951,15 @@ msgid_plural "New Issues"
msgstr[0] "Neues Ticket"
msgstr[1] "Neue Tickets"
-msgid "New Iteration"
-msgstr ""
-
msgid "New Jira import"
msgstr ""
msgid "New Label"
msgstr "Neues Label"
+msgid "New Merge Request"
+msgstr ""
+
msgid "New Milestone"
msgstr ""
@@ -14523,6 +14978,9 @@ msgstr ""
msgid "New Snippet"
msgstr "Neuer Codeausschnitt"
+msgid "New User"
+msgstr ""
+
msgid "New branch"
msgstr "Neuer Branch"
@@ -14565,6 +15023,9 @@ msgstr "Neues Ticket"
msgid "New issue title"
msgstr "Neuer Ticket-Titel"
+msgid "New iteration"
+msgstr ""
+
msgid "New iteration created"
msgstr ""
@@ -14587,13 +15048,13 @@ msgid "New project"
msgstr "Neues Projekt"
msgid "New release"
-msgstr ""
+msgstr "Neues Release"
msgid "New requirement"
msgstr "Neue Anforderung"
msgid "New runners registration token has been generated!"
-msgstr ""
+msgstr "Neuer Registrierungstoken für Runner wurde generiert!"
msgid "New schedule"
msgstr "Neuer Zeitplan"
@@ -14742,6 +15203,12 @@ msgstr ""
msgid "No grouping"
msgstr ""
+msgid "No iteration"
+msgstr ""
+
+msgid "No iterations to show"
+msgstr ""
+
msgid "No job log"
msgstr "Kein Jobprotokoll"
@@ -14784,6 +15251,9 @@ msgstr ""
msgid "No other labels with such name or description"
msgstr "Keine anderen Tags mit einem solchen Namen oder einer solchen Beschreibung"
+msgid "No panels matching properties %{opts}"
+msgstr ""
+
msgid "No parent group"
msgstr ""
@@ -14817,6 +15287,9 @@ msgstr "Keine Runner gefunden"
msgid "No schedules"
msgstr "Keine Zeitpläne"
+msgid "No stack trace for this error"
+msgstr ""
+
msgid "No starrers matched your search"
msgstr ""
@@ -14829,7 +15302,7 @@ msgstr ""
msgid "No test coverage"
msgstr ""
-msgid "No thanks, don't show this again"
+msgid "No thanks"
msgstr ""
msgid "No vulnerabilities present"
@@ -14844,9 +15317,6 @@ msgstr ""
msgid "No, directly import the existing email addresses and usernames."
msgstr "Nein, importiere die vorhandenen E-Mail-Adressen und Benutzernamen direkt."
-msgid "No, not interested right now"
-msgstr ""
-
msgid "No. of commits"
msgstr ""
@@ -14892,9 +15362,6 @@ msgstr "Nicht genügend Daten"
msgid "Not found."
msgstr ""
-msgid "Not helpful"
-msgstr ""
-
msgid "Not now"
msgstr "Nicht jetzt"
@@ -15051,6 +15518,9 @@ msgstr "Benachrichtigungen aus"
msgid "Notifications on"
msgstr "Benachrichtigungen ein"
+msgid "Notify users by email when sign-in location is not recognized"
+msgstr ""
+
msgid "Nov"
msgstr "Nov"
@@ -15117,9 +15587,6 @@ msgstr "Filter"
msgid "Oh no!"
msgstr ""
-msgid "Ok let's go"
-msgstr ""
-
msgid "Oldest first"
msgstr ""
@@ -15132,16 +15599,49 @@ msgstr ""
msgid "On track"
msgstr ""
+msgid "OnDemandScans|Attached branch"
+msgstr ""
+
+msgid "OnDemandScans|Attached branch is where the scan job runs."
+msgstr ""
+
+msgid "OnDemandScans|Could not run the scan. Please try again."
+msgstr ""
+
msgid "OnDemandScans|Create new DAST scan"
msgstr ""
+msgid "OnDemandScans|DAST will scan the target URL and any discovered sub URLs."
+msgstr ""
+
+msgid "OnDemandScans|New on-demand DAST scan"
+msgstr ""
+
msgid "OnDemandScans|On-demand Scans"
msgstr ""
+msgid "OnDemandScans|On-demand scans run outside the DevOps cycle and find vulnerabilities in your projects. %{learnMoreLinkStart}Learn more%{learnMoreLinkEnd}"
+msgstr ""
+
+msgid "OnDemandScans|Only a passive scan can be performed on demand."
+msgstr ""
+
+msgid "OnDemandScans|Passive DAST Scan"
+msgstr ""
+
+msgid "OnDemandScans|Please enter a valid URL format, ex: http://www.example.com/home"
+msgstr ""
+
+msgid "OnDemandScans|Run this scan"
+msgstr ""
+
+msgid "OnDemandScans|Scan mode"
+msgstr ""
+
msgid "OnDemandScans|Schedule or run scans immediately against target sites. Currently available on-demand scan type: DAST. %{helpLinkStart}More information%{helpLinkEnd}"
msgstr ""
-msgid "Onboarding"
+msgid "OnDemandScans|Target URL"
msgstr ""
msgid "Once imported, repositories can be mirrored over SSH. Read more %{link_start}here%{link_end}."
@@ -15167,6 +15667,9 @@ msgstr ""
msgid "One or more of you personal access tokens were revoked"
msgstr ""
+msgid "One or more of your %{provider} projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
+msgstr ""
+
msgid "One or more of your Bitbucket projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
msgstr "Ein oder mehrere deiner Bitbucket-Projekte kann/können nicht direkt in GitLab importiert werden, da sie Subversion oder Mercurial für die Versionskontrolle anstelle von Git verwenden."
@@ -15285,7 +15788,7 @@ msgid "Operations"
msgstr "Vorgänge"
msgid "Operations Dashboard"
-msgstr ""
+msgstr "Operations Dashboard"
msgid "Operations Settings"
msgstr ""
@@ -15306,7 +15809,7 @@ msgid "OperationsDashboard|The operations dashboard provides a summary of each p
msgstr ""
msgid "Optional"
-msgstr ""
+msgstr "Optional"
msgid "Optional parameter \"variables\" must be a Hash. Ex: variables[key1]=value1"
msgstr ""
@@ -15453,10 +15956,10 @@ msgid "PackageRegistry|Copy and paste this inside your %{codeStart}pom.xml%{code
msgstr ""
msgid "PackageRegistry|Copy npm command"
-msgstr ""
+msgstr "npm Befehl kopieren"
msgid "PackageRegistry|Copy npm setup command"
-msgstr ""
+msgstr "npm Setup-Befehl kopieren"
msgid "PackageRegistry|Copy yarn command"
msgstr ""
@@ -15534,7 +16037,7 @@ msgid "PackageRegistry|Published to the repository at %{timestamp}"
msgstr ""
msgid "PackageRegistry|PyPi"
-msgstr ""
+msgstr "PyPi"
msgid "PackageRegistry|Registry Setup"
msgstr ""
@@ -15606,7 +16109,7 @@ msgid "PackageType|NuGet"
msgstr ""
msgid "PackageType|PyPi"
-msgstr ""
+msgstr "PyPi"
msgid "Packages"
msgstr "Pakete"
@@ -15615,7 +16118,7 @@ msgid "Packages & Registries"
msgstr ""
msgid "Page not found"
-msgstr ""
+msgstr "Seite nicht gefunden"
msgid "Page was successfully deleted"
msgstr ""
@@ -15668,6 +16171,9 @@ msgstr ""
msgid "Parent epic is not present."
msgstr "Ãœbergeordnetes Epic ist nicht vorhanden."
+msgid "Parsing error for param :embed_json. %{message}"
+msgstr ""
+
msgid "Part of merge request changes"
msgstr "Teil der Merge-Request-Änderungen"
@@ -15680,11 +16186,14 @@ msgstr ""
msgid "Passed"
msgstr "Bestanden"
+msgid "Passed on"
+msgstr ""
+
msgid "Password"
msgstr "Passwort"
msgid "Password (optional)"
-msgstr ""
+msgstr "Passwort (optional)"
msgid "Password Policy Guidelines"
msgstr "Passwortrichtlinien"
@@ -15764,6 +16273,9 @@ msgstr ""
msgid "Perform common operations on GitLab project"
msgstr ""
+msgid "Performance and resource management"
+msgstr ""
+
msgid "Performance optimization"
msgstr "Leistungsoptimierung"
@@ -15971,8 +16483,8 @@ msgstr "Pipeline ausführen"
msgid "Pipelines|Something went wrong while cleaning runners cache."
msgstr "Beim Löschen des Runner-Caches ist etwas schief gelaufen."
-msgid "Pipelines|There are currently no %{scope} pipelines."
-msgstr "Derzeit gibt es keine %{scope} Pipelines."
+msgid "Pipelines|There are currently no finished pipelines."
+msgstr ""
msgid "Pipelines|There are currently no pipelines."
msgstr "Derzeit gibt es keine Pipelines."
@@ -16082,6 +16594,9 @@ msgstr "Pipeline stoppen"
msgid "Pipeline|Stop pipeline #%{pipelineId}?"
msgstr "Pipeline #%{pipelineId} stoppen?"
+msgid "Pipeline|Tag name"
+msgstr ""
+
msgid "Pipeline|Trigger author"
msgstr ""
@@ -16163,12 +16678,18 @@ msgstr ""
msgid "Please check your email (%{email}) to verify that you own this address and unlock the power of CI/CD. Didn't receive it? %{resend_link}. Wrong email address? %{update_link}."
msgstr ""
+msgid "Please choose a file"
+msgstr ""
+
msgid "Please choose a group URL with no special characters."
msgstr "Bitte wähle eine Gruppen-URL ohne Sonderzeichen."
msgid "Please complete your profile with email address"
msgstr ""
+msgid "Please convert %{linkStart}them to Git%{linkEnd}, and go through the %{linkToImportFlow} again."
+msgstr ""
+
msgid "Please convert them to %{link_to_git}, and go through the %{link_to_import_flow} again."
msgstr "Bitte wandle sie in %{link_to_git} um und durchlaufe erneut die %{link_to_import_flow}."
@@ -16226,6 +16747,9 @@ msgstr ""
msgid "Please provide a valid email address."
msgstr ""
+msgid "Please provide attributes to update"
+msgstr ""
+
msgid "Please refer to <a href=\"%{docs_url}\">%{docs_url}</a>"
msgstr ""
@@ -16961,6 +17485,9 @@ msgstr ""
msgid "Project has too many %{label_for_message} to search"
msgstr ""
+msgid "Project is required when cluster_type is :project"
+msgstr ""
+
msgid "Project members"
msgstr ""
@@ -17078,7 +17605,40 @@ msgstr ""
msgid "ProjectService|Comment"
msgstr ""
-msgid "ProjectService|Comment will be posted on each event"
+msgid "ProjectService|Event will be triggered by a push to the repository"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a commit is created/updated"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a confidential issue is created/updated/closed"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a deployment finishes"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a merge request is created/updated/merged"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a new tag is pushed to the repository"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a new, unique alert is recorded"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a pipeline status changes"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a wiki page is created/updated"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when an issue is created/updated/closed"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when someone adds a comment"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when someone adds a comment on a confidential issue"
msgstr ""
msgid "ProjectService|Perform common operations on GitLab project: %{project_name}"
@@ -17129,6 +17689,9 @@ msgstr ""
msgid "ProjectSettings|Enable 'Delete source branch' option by default"
msgstr ""
+msgid "ProjectSettings|Enable merge trains and pipelines for merged results"
+msgstr ""
+
msgid "ProjectSettings|Every merge creates a merge commit"
msgstr ""
@@ -17157,9 +17720,9 @@ msgid "ProjectSettings|Fast-forward merges only"
msgstr "Nur Fast-Forward-Merges"
msgid "ProjectSettings|Forks"
-msgstr ""
+msgstr "Forks"
-msgid "ProjectSettings|Git Large File Storage"
+msgid "ProjectSettings|Git Large File Storage (LFS)"
msgstr ""
msgid "ProjectSettings|Internal"
@@ -17195,9 +17758,6 @@ msgstr ""
msgid "ProjectSettings|Merge options"
msgstr ""
-msgid "ProjectSettings|Merge pipelines will try to validate the post-merge result prior to merging"
-msgstr ""
-
msgid "ProjectSettings|Merge requests"
msgstr ""
@@ -17225,6 +17785,9 @@ msgstr ""
msgid "ProjectSettings|Pipelines"
msgstr ""
+msgid "ProjectSettings|Pipelines for merge requests must be enabled in the CI/CD configuration file, or pipelines could be unresolvable or dropped"
+msgstr ""
+
msgid "ProjectSettings|Pipelines must succeed"
msgstr ""
@@ -17252,6 +17815,9 @@ msgstr ""
msgid "ProjectSettings|Show link to create/view merge request when pushing from the command line"
msgstr ""
+msgid "ProjectSettings|Skipped pipelines are considered successful"
+msgstr ""
+
msgid "ProjectSettings|Snippets"
msgstr ""
@@ -17267,6 +17833,9 @@ msgstr ""
msgid "ProjectSettings|These checks must pass before merge requests can be merged"
msgstr ""
+msgid "ProjectSettings|This introduces the risk of merging changes that will not pass the pipeline."
+msgstr ""
+
msgid "ProjectSettings|This setting is applied on the server level and can be overridden by an admin."
msgstr "Diese Einstellung wird auf Serverebene angewendet und kann von einem Admin überschrieben werden."
@@ -17444,15 +18013,27 @@ msgstr ""
msgid "ProjectsNew|Blank project"
msgstr ""
+msgid "ProjectsNew|Connect your external repository to GitLab CI/CD."
+msgstr ""
+
msgid "ProjectsNew|Contact an administrator to enable options for importing your project."
msgstr ""
msgid "ProjectsNew|Create"
msgstr ""
+msgid "ProjectsNew|Create a blank project to house your files, plan your work, and collaborate on code, among other things."
+msgstr ""
+
+msgid "ProjectsNew|Create blank project"
+msgstr ""
+
msgid "ProjectsNew|Create from template"
msgstr ""
+msgid "ProjectsNew|Create new project"
+msgstr ""
+
msgid "ProjectsNew|Creating project & repository."
msgstr ""
@@ -17477,6 +18058,9 @@ msgstr "Bitte warte einen Moment. Diese Seite wird automatisch aktualisiert, wen
msgid "ProjectsNew|Project description %{tag_start}(optional)%{tag_end}"
msgstr ""
+msgid "ProjectsNew|Run CI/CD for external repository"
+msgstr ""
+
msgid "ProjectsNew|Template"
msgstr ""
@@ -17738,6 +18322,9 @@ msgstr "Geschützt"
msgid "Protected Branch"
msgstr ""
+msgid "Protected Branches"
+msgstr ""
+
msgid "Protected Environment"
msgstr ""
@@ -17750,6 +18337,9 @@ msgstr ""
msgid "Protected Tag"
msgstr ""
+msgid "Protected Tags"
+msgstr ""
+
msgid "Protected branches"
msgstr ""
@@ -17849,6 +18439,9 @@ msgstr "Protokoll"
msgid "Provider"
msgstr "Provider"
+msgid "Proxy support for this API is not available currently"
+msgstr ""
+
msgid "Pseudonymizer data collection"
msgstr "Pseudonymizer-Datensammlung"
@@ -17873,6 +18466,9 @@ msgstr ""
msgid "Publish to status page"
msgstr ""
+msgid "Published on status page"
+msgstr ""
+
msgid "Publishes this issue to the associated status page."
msgstr ""
@@ -18047,6 +18643,9 @@ msgstr "Zuletzt gesucht"
msgid "Recipe"
msgstr ""
+msgid "Reconfigure"
+msgstr ""
+
msgid "Recover hidden stage"
msgstr ""
@@ -18077,7 +18676,7 @@ msgid "Regenerate export"
msgstr ""
msgid "Regenerate instance ID"
-msgstr ""
+msgstr "Instanz-ID neu generieren"
msgid "Regenerate key"
msgstr "Schlüssel neu generieren"
@@ -18133,6 +18732,9 @@ msgstr ""
msgid "Registration|Your profile"
msgstr ""
+msgid "Regulate approvals by authors/committers, based on compliance frameworks. Can be changed only at the instance level."
+msgstr ""
+
msgid "Rejected (closed)"
msgstr ""
@@ -18155,7 +18757,7 @@ msgid "Related merge requests"
msgstr "Zugehörige Merge-Requests"
msgid "Relates to"
-msgstr ""
+msgstr "Bezieht sich auf"
msgid "Release"
msgid_plural "Releases"
@@ -18183,15 +18785,24 @@ msgstr ""
msgid "ReleaseAssetLinkType|Image"
msgstr ""
+msgid "ReleaseAssetLinkType|Images"
+msgstr ""
+
msgid "ReleaseAssetLinkType|Other"
msgstr ""
msgid "ReleaseAssetLinkType|Package"
msgstr ""
+msgid "ReleaseAssetLinkType|Packages"
+msgstr ""
+
msgid "ReleaseAssetLinkType|Runbook"
msgstr ""
+msgid "ReleaseAssetLinkType|Runbooks"
+msgstr ""
+
msgid "Releases"
msgstr "Releases"
@@ -18204,6 +18815,9 @@ msgstr ""
msgid "Releases documentation"
msgstr ""
+msgid "Releases|New Release"
+msgstr ""
+
msgid "Release|Something went wrong while getting the release details"
msgstr ""
@@ -18276,6 +18890,9 @@ msgstr ""
msgid "Remove fork relationship"
msgstr ""
+msgid "Remove from batch"
+msgstr ""
+
msgid "Remove from board"
msgstr ""
@@ -18465,6 +19082,12 @@ msgstr ""
msgid "Replication"
msgstr ""
+msgid "Replication enabled"
+msgstr ""
+
+msgid "Replication paused"
+msgstr ""
+
msgid "Reply by email"
msgstr ""
@@ -18492,15 +19115,15 @@ msgstr ""
msgid "Reported %{timeAgo} by %{reportedBy}"
msgstr ""
-msgid "Reported Resource Changes: %{addNum} to add, %{changeNum} to change, %{deleteNum} to delete"
-msgstr ""
-
msgid "Reporter"
msgstr ""
msgid "Reporting"
msgstr "Statusbericht"
+msgid "Reports"
+msgstr ""
+
msgid "Reports|%{combinedString} and %{resolvedString}"
msgstr ""
@@ -18539,6 +19162,9 @@ msgstr "Ausführungszeit"
msgid "Reports|Failure"
msgstr "Fehlschlag"
+msgid "Reports|Identifier"
+msgstr ""
+
msgid "Reports|Metrics reports are loading"
msgstr ""
@@ -18551,6 +19177,9 @@ msgstr ""
msgid "Reports|Metrics reports failed loading results"
msgstr ""
+msgid "Reports|Scanner"
+msgstr ""
+
msgid "Reports|Severity"
msgstr "Schweregrad"
@@ -18599,15 +19228,33 @@ msgstr ""
msgid "Repository cleanup has started. You will receive an email once the cleanup operation is complete."
msgstr ""
+msgid "Repository files count over the limit"
+msgstr ""
+
+msgid "Repository has an invalid default branch name."
+msgstr ""
+
+msgid "Repository has more than one branch."
+msgstr ""
+
msgid "Repository has no locks."
msgstr "Das Repository hat keine Sperren."
+msgid "Repository has tags."
+msgstr ""
+
msgid "Repository maintenance"
msgstr "Repository-Wartung"
msgid "Repository mirroring"
msgstr ""
+msgid "Repository must contain at least 1 file."
+msgstr ""
+
+msgid "Repository size is above the limit."
+msgstr ""
+
msgid "Repository static objects"
msgstr ""
@@ -18617,7 +19264,7 @@ msgstr "Repository-Speicher"
msgid "Repository sync capacity"
msgstr ""
-msgid "Repository: %{counter_repositories} / Wikis: %{counter_wikis} / Build Artifacts: %{counter_build_artifacts} / LFS: %{counter_lfs_objects}"
+msgid "Repository: %{counter_repositories} / Wikis: %{counter_wikis} / Build Artifacts: %{counter_build_artifacts} / LFS: %{counter_lfs_objects} / Snippets: %{counter_snippets}"
msgstr ""
msgid "RepositorySettingsAccessLevel|Select"
@@ -18629,6 +19276,9 @@ msgstr "Anfrage auf Zugriff"
msgid "Request Headers"
msgstr ""
+msgid "Request details"
+msgstr ""
+
msgid "Request parameter %{param} is missing."
msgstr ""
@@ -18735,6 +19385,9 @@ msgstr "Registrierungstoken für Runner zurücksetzen"
msgid "Reset template"
msgstr ""
+msgid "Reset to project defaults"
+msgstr ""
+
msgid "Resetting the authorization key for this project will require updating the authorization key in every alert source it is enabled in."
msgstr ""
@@ -18796,7 +19449,7 @@ msgid "Response metrics (HA Proxy)"
msgstr "Antwortmesswerte (HA Proxy)"
msgid "Response metrics (NGINX Ingress VTS)"
-msgstr ""
+msgstr "Antwort-Metriken (NGINX Ingress VTS)"
msgid "Response metrics (NGINX Ingress)"
msgstr "Antwortmesswerte (NGINX Ingress)"
@@ -18831,9 +19484,6 @@ msgstr ""
msgid "Resume"
msgstr "Fortsetzen"
-msgid "Resume replication"
-msgstr "Replikation fortsetzen"
-
msgid "Resync"
msgstr ""
@@ -18963,13 +19613,13 @@ msgid "Runner token"
msgstr "Runner-Token"
msgid "Runner tokens"
-msgstr ""
+msgstr "Runner-Token"
msgid "Runner was not updated."
-msgstr ""
+msgstr "Runner wurde nicht aktualisiert."
msgid "Runner was successfully updated."
-msgstr ""
+msgstr "Runner wurde erfolreich aktualisiert."
msgid "Runner will not receive any new jobs"
msgstr "Runner wird keine neuen Jobs annehmen"
@@ -18981,7 +19631,7 @@ msgid "Runners API"
msgstr "Runners-API"
msgid "Runners activated for this project"
-msgstr ""
+msgstr "Für dieses Projekt aktivierte Runner"
msgid "Runners are processes that pick up and execute jobs for GitLab. Here you can register and see your Runners for this project."
msgstr ""
@@ -19158,7 +19808,7 @@ msgid "Search"
msgstr "Suche"
msgid "Search Button"
-msgstr ""
+msgstr "Such-Button"
msgid "Search Milestones"
msgstr ""
@@ -19176,7 +19826,7 @@ msgid "Search branches and tags"
msgstr "Suche nach Branches und Tags"
msgid "Search by author"
-msgstr ""
+msgstr "Suche nach Autor(in)"
msgid "Search files"
msgstr "Dateien durchsuchen"
@@ -19323,8 +19973,8 @@ msgstr[1] ""
msgid "SearchResults|snippet"
msgid_plural "SearchResults|snippets"
-msgstr[0] ""
-msgstr[1] ""
+msgstr[0] "Code-Schnipsel"
+msgstr[1] "Code-Schnipsel"
msgid "SearchResults|user"
msgid_plural "SearchResults|users"
@@ -19456,6 +20106,9 @@ msgstr ""
msgid "SecurityReports|False positive"
msgstr ""
+msgid "SecurityReports|Group Security Dashboard"
+msgstr ""
+
msgid "SecurityReports|Hide dismissed"
msgstr ""
@@ -19510,10 +20163,13 @@ msgstr ""
msgid "SecurityReports|Remove project from dashboard"
msgstr ""
-msgid "SecurityReports|Report type"
+msgid "SecurityReports|Return to dashboard"
msgstr ""
-msgid "SecurityReports|Return to dashboard"
+msgid "SecurityReports|Scan details"
+msgstr ""
+
+msgid "SecurityReports|Scanner type"
msgstr ""
msgid "SecurityReports|Security Dashboard"
@@ -19570,6 +20226,9 @@ msgstr ""
msgid "SecurityReports|There was an error while generating the report."
msgstr ""
+msgid "SecurityReports|Unable to add %{invalidProjectsMessage}"
+msgstr ""
+
msgid "SecurityReports|Unable to add %{invalidProjects}"
msgstr ""
@@ -19637,7 +20296,7 @@ msgid "Select a group to invite"
msgstr "Wähle eine Gruppe aus, die du einladen möchtest"
msgid "Select a label"
-msgstr ""
+msgstr "Ein Label auswählen"
msgid "Select a namespace to fork the project"
msgstr "Wähle einen Namensraum aus, um einen Fork des Projekts zu erstellen"
@@ -19684,6 +20343,9 @@ msgstr "Branch/Tag auswählen"
msgid "Select due date"
msgstr ""
+msgid "Select file"
+msgstr ""
+
msgid "Select group or project"
msgstr ""
@@ -19747,9 +20409,6 @@ msgstr "Zielbranch auswählen"
msgid "Select the branch you want to set as the default for this project. All merge requests and commits will automatically be made against this branch unless you specify a different one."
msgstr "Wähle den Branch aus, den du als Standard für dieses Projekt festlegen möchtest. Alle Merge-Requests und Commits werden automatisch für diesen Branch ausgeführt, es sei denn, du gibst einen anderen an."
-msgid "Select the configured storage available for new repositories to be placed on."
-msgstr ""
-
msgid "Select the custom project template source group."
msgstr "Wähle die Quellgruppe der benutzerdefinierten Projektvorlage aus."
@@ -19942,12 +20601,6 @@ msgstr "Service-Desk"
msgid "Service Desk is enabled but not yet active"
msgstr ""
-msgid "Service Desk is off"
-msgstr ""
-
-msgid "Service Desk is on"
-msgstr ""
-
msgid "Service Templates"
msgstr "Servicevorlagen"
@@ -20035,6 +20688,15 @@ msgstr ""
msgid "Set the milestone to %{milestone_reference}."
msgstr ""
+msgid "Set the number of concurrent requests this secondary node will make to the primary node while backfilling."
+msgstr ""
+
+msgid "Set the synchronization and verification capacity for the secondary node."
+msgstr ""
+
+msgid "Set the timeout in seconds to send a secondary node status to the primary and IPs allowed for the secondary nodes."
+msgstr ""
+
msgid "Set time estimate"
msgstr ""
@@ -20074,6 +20736,9 @@ msgstr ""
msgid "Set weight to %{weight}."
msgstr ""
+msgid "Set what should be replicated by choosing specific projects or groups by the secondary node."
+msgstr ""
+
msgid "SetPasswordToCloneLink|set a password"
msgstr "ein Passwort festlegst"
@@ -20200,6 +20865,9 @@ msgstr ""
msgid "Show complete raw log"
msgstr "Zeige komplettes Rohprotokoll"
+msgid "Show details"
+msgstr ""
+
msgid "Show file browser"
msgstr ""
@@ -20209,17 +20877,20 @@ msgstr ""
msgid "Show latest version"
msgstr "Neuste Version zeigen"
+msgid "Show list"
+msgstr ""
+
msgid "Show me everything"
msgstr ""
-msgid "Show me how"
+msgid "Show me how to add a pipeline"
msgstr ""
msgid "Show me more advanced stuff"
msgstr ""
msgid "Show only direct members"
-msgstr ""
+msgstr "Zeige nur direkte Mitglieder"
msgid "Show only inherited members"
msgstr ""
@@ -20261,9 +20932,6 @@ msgstr ""
msgid "Showing all issues"
msgstr ""
-msgid "Showing all labels"
-msgstr ""
-
msgid "Showing last %{size} of log -"
msgstr ""
@@ -20375,9 +21043,6 @@ msgstr ""
msgid "Skip outdated deployment jobs"
msgstr ""
-msgid "Skip this for now"
-msgstr ""
-
msgid "Skipped"
msgstr ""
@@ -20522,6 +21187,9 @@ msgstr "Beim Umschalten des Buttons ist etwas schief gelaufen"
msgid "Something went wrong while adding your award. Please try again."
msgstr ""
+msgid "Something went wrong while applying the batch of suggestions. Please try again."
+msgstr ""
+
msgid "Something went wrong while applying the suggestion. Please try again."
msgstr ""
@@ -20624,10 +21292,13 @@ msgstr ""
msgid "Something went wrong while updating a requirement."
msgstr ""
+msgid "Something went wrong while updating assignees"
+msgstr ""
+
msgid "Something went wrong while updating your list settings"
msgstr ""
-msgid "Something went wrong with your automatic subscription renewal"
+msgid "Something went wrong with your automatic subscription renewal."
msgstr ""
msgid "Something went wrong, unable to add %{project} to dashboard"
@@ -20645,6 +21316,9 @@ msgstr "Etwas lief schief, Projekt konnte nicht entfernt werden"
msgid "Something went wrong, unable to search projects"
msgstr ""
+msgid "Something went wrong."
+msgstr ""
+
msgid "Something went wrong. Please try again."
msgstr "Etwas ist schief gelaufen. Bitte versuche es erneut."
@@ -20828,6 +21502,9 @@ msgstr "Quelle (Branch oder Tag)"
msgid "Source code"
msgstr "Quellcode"
+msgid "Source code (%{fileExtension})"
+msgstr ""
+
msgid "Source is not available"
msgstr "Quelle ist nicht verfügbar"
@@ -20912,6 +21589,9 @@ msgstr "Squash Commits"
msgid "Stack trace"
msgstr ""
+msgid "Stacktrace snippet"
+msgstr ""
+
msgid "Stage"
msgstr "Vormerken"
@@ -20922,10 +21602,10 @@ msgid "Stage data updated"
msgstr ""
msgid "Stage removed"
-msgstr ""
+msgstr "Stage entfernt"
msgid "Standard"
-msgstr ""
+msgstr "Standard"
msgid "Star a label to make it a priority label. Order the prioritized labels to change their relative priority, by dragging."
msgstr "Markiere ein Label, um es zu einem priorisierten Label zu machen. Ordne die priorisierten Labels durch Ziehen an, um ihre relative Priorität zu ändern."
@@ -21017,6 +21697,9 @@ msgstr ""
msgid "Start thread & reopen %{noteable_name}"
msgstr ""
+msgid "Start using Directed Acyclic Graphs (DAG)"
+msgstr ""
+
msgid "Start your Free Gold Trial"
msgstr ""
@@ -21132,7 +21815,7 @@ msgid "StatusPage|Active"
msgstr ""
msgid "StatusPage|Bucket %{docsLink}"
-msgstr ""
+msgstr "Bucket %{docsLink}"
msgid "StatusPage|Configure file storage settings to link issues in this project to an external status page."
msgstr ""
@@ -21141,7 +21824,7 @@ msgid "StatusPage|For help with configuration, visit %{docsLink}"
msgstr ""
msgid "StatusPage|S3 Bucket name"
-msgstr ""
+msgstr "S3 Bucket-Name"
msgid "StatusPage|Status page"
msgstr ""
@@ -21372,7 +22055,7 @@ msgid "Subtracts"
msgstr ""
msgid "Succeeded"
-msgstr ""
+msgstr "Erfolgreich"
msgid "Successfully activated"
msgstr ""
@@ -21479,11 +22162,26 @@ msgstr ""
msgid "SuggestedColors|Very pale orange"
msgstr ""
+msgid "Suggestion is not applicable as the suggestion was not found."
+msgstr ""
+
+msgid "Suggestions are not applicable as one or more suggestions were not found."
+msgstr ""
+
+msgid "Suggestions are not applicable as their lines cannot overlap."
+msgstr ""
+
+msgid "Suggestions must all be on the same branch."
+msgstr ""
+
+msgid "Suggestions that change line count can't be added to batches, yet."
+msgstr ""
+
msgid "Suggestions:"
msgstr ""
msgid "Suite"
-msgstr ""
+msgstr "Suite"
msgid "Summary"
msgstr ""
@@ -21521,9 +22219,6 @@ msgstr ""
msgid "Synchronization disabled"
msgstr ""
-msgid "Synchronization of container repositories is disabled."
-msgstr ""
-
msgid "System"
msgstr ""
@@ -21675,7 +22370,7 @@ msgid "Telephone number"
msgstr ""
msgid "Telephone number (Optional)"
-msgstr ""
+msgstr "Telefonnummer (optional)"
msgid "Template"
msgstr "Vorlage"
@@ -21704,6 +22399,34 @@ msgstr "Nutzungsbedingungen und Datenschutzerklärung"
msgid "Terms of Service and Privacy Policy"
msgstr "Nutzungsbedingungen und Datenschutzerklärung"
+msgid "Terraform|%{number} Terraform report failed to generate"
+msgid_plural "Terraform|%{number} Terraform reports failed to generate"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Terraform|%{number} Terraform report was generated in your pipelines"
+msgid_plural "Terraform|%{number} Terraform reports were generated in your pipelines"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Terraform|A Terraform report failed to generate."
+msgstr ""
+
+msgid "Terraform|A Terraform report was generated in your pipelines."
+msgstr ""
+
+msgid "Terraform|Generating the report caused an error."
+msgstr ""
+
+msgid "Terraform|Reported Resource Changes: %{addNum} to add, %{changeNum} to change, %{deleteNum} to delete"
+msgstr ""
+
+msgid "Terraform|The Terraform report %{name} failed to generate."
+msgstr ""
+
+msgid "Terraform|The Terraform report %{name} was generated in your pipelines."
+msgstr ""
+
msgid "Test"
msgstr "Test"
@@ -21730,7 +22453,7 @@ msgstr ""
msgid "TestHooks|Ensure the project has CI pipelines."
msgstr ""
-msgid "TestHooks|Ensure the project has at least one commit."
+msgid "TestHooks|Ensure the project has deployments."
msgstr ""
msgid "TestHooks|Ensure the project has issues."
@@ -21831,7 +22554,7 @@ msgstr "Das Ticketsystem ist der Ort, um Dinge hinzuzufügen, die in einem Proje
msgid "The Prometheus server responded with \"bad request\". Please check your queries are correct and are supported in your Prometheus version. %{documentationLink}"
msgstr ""
-msgid "The URL defined on the primary node that secondary nodes should use to contact it. Defaults to URL"
+msgid "The URL defined on the primary node that secondary nodes should use to contact it."
msgstr ""
msgid "The URL to use for connecting to Elasticsearch. Use a comma-separated list to support clustering (e.g., \"http://localhost:9200, http://localhost:9201\")."
@@ -21840,6 +22563,9 @@ msgstr ""
msgid "The X509 Certificate to use when mutual TLS is required to communicate with the external authorization service. If left blank, the server certificate is still validated when accessing over HTTPS."
msgstr "Das zu verwendende X.509-Zertifikat, wenn über MTLS mit dem externen Autorisierungsdienst kommunizieren muss. Wenn dieses Feld leer gelassen wird, wird das Serverzertifikat beim Zugriff über HTTPS weiterhin überprüft."
+msgid "The above settings apply to all projects with the selected compliance framework(s)."
+msgstr ""
+
msgid "The amount of seconds after which a request to get a secondary node status will time out."
msgstr ""
@@ -22023,7 +22749,7 @@ msgid "The number of times an upload record could not find its file"
msgstr ""
msgid "The one place for your designs"
-msgstr ""
+msgstr "Der Ort für Deine Designs"
msgid "The passphrase required to decrypt the private key. This is optional and the value is encrypted at rest."
msgstr "Das Kennwort, das zum Entschlüsseln des privaten Schlüssels erforderlich ist. Dies ist optional und der Wert wird im Ruhezustand verschlüsselt."
@@ -22139,9 +22865,6 @@ msgstr "Die Zeit, die jede Dateneingabe in dieser Phase benötigt."
msgid "The total stage shows the time it takes between creating an issue and deploying the code to production. The data will be automatically added once you have completed the full idea to production cycle."
msgstr ""
-msgid "The unique identifier for the Geo node. Must match %{geoNodeName} if it is set in gitlab.rb, otherwise it must match %{externalUrl} with a trailing slash"
-msgstr ""
-
msgid "The update action will time out after %{number_of_minutes} minutes. For big repositories, use a clone/push combination."
msgstr "Die Aktualisierungsaktion wird nach %{number_of_minutes} Minuten abgebrochen. Verwende für große Repositories eine Clone / Push-Kombination."
@@ -22166,9 +22889,6 @@ msgstr "Die Benutzerzuordnung legt fest, wie die E-Mail-Adressen und Benutzernam
msgid "The user you are trying to deactivate has been active in the past %{minimum_inactive_days} days and cannot be deactivated"
msgstr ""
-msgid "The user-facing URL of the Geo node"
-msgstr ""
-
msgid "The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6."
msgstr "Der mittlere aller erfassten Werte. Zum Beispiel ist für 3, 5, 9 der Median 5. Bei 3, 5, 7, 8 ist der Median (5+7)/2 = 6."
@@ -22259,6 +22979,9 @@ msgstr ""
msgid "There was a problem fetching project branches."
msgstr ""
+msgid "There was a problem fetching project tags."
+msgstr ""
+
msgid "There was a problem fetching project users."
msgstr ""
@@ -22310,6 +23033,9 @@ msgstr ""
msgid "There was an error fetching the %{replicableType}"
msgstr ""
+msgid "There was an error fetching the Geo Settings"
+msgstr ""
+
msgid "There was an error fetching the Node's Groups"
msgstr ""
@@ -22430,6 +23156,12 @@ msgstr ""
msgid "This %{issuable} is locked. Only <strong>project members</strong> can comment."
msgstr ""
+msgid "This %{noteableTypeText} is %{confidentialLinkStart}confidential%{linkEnd} and %{lockedLinkStart}locked%{linkEnd}."
+msgstr ""
+
+msgid "This %{noteableTypeText} is locked."
+msgstr ""
+
msgid "This %{viewer} could not be displayed because %{reason}. You can %{options} instead."
msgstr ""
@@ -22448,6 +23180,9 @@ msgstr ""
msgid "This action can lead to data loss. To prevent accidental actions we ask you to confirm your intention."
msgstr ""
+msgid "This also resolves all related threads"
+msgstr ""
+
msgid "This also resolves the discussion"
msgstr ""
@@ -22538,6 +23273,9 @@ msgstr ""
msgid "This epic does not exist or you don't have sufficient permission."
msgstr ""
+msgid "This feature is currently in beta. We invite you to %{linkStart}give feedback%{linkEnd}."
+msgstr ""
+
msgid "This feature requires local storage to be enabled"
msgstr ""
@@ -22571,8 +23309,8 @@ msgstr ""
msgid "This is a Work in Progress"
msgstr ""
-msgid "This is a confidential issue."
-msgstr "Dies ist ein vertrauliches Ticket."
+msgid "This is a confidential %{noteableTypeText}."
+msgstr ""
msgid "This is a delayed job to run in %{remainingTime}"
msgstr "Dies ist ein in %{remainingTime} auszuführender verzögerter Job"
@@ -22598,9 +23336,6 @@ msgstr ""
msgid "This is your current session"
msgstr ""
-msgid "This issue is %{confidentialLinkStart}confidential%{linkEnd} and %{lockedLinkStart}locked%{linkEnd}."
-msgstr ""
-
msgid "This issue is confidential"
msgstr "Dieses Ticket ist vertraulich"
@@ -22610,9 +23345,6 @@ msgstr ""
msgid "This issue is in a child epic of the filtered epic"
msgstr ""
-msgid "This issue is locked."
-msgstr "Dieses Ticket ist gesperrt."
-
msgid "This job depends on other jobs with expired/erased artifacts: %{invalid_dependencies}"
msgstr ""
@@ -22712,6 +23444,9 @@ msgstr ""
msgid "This license has already expired."
msgstr ""
+msgid "This link points to external content"
+msgstr ""
+
msgid "This may expose confidential information as the selected fork is in another namespace that can have other members."
msgstr ""
@@ -22739,6 +23474,9 @@ msgstr "Diese Seite ist nicht verfügbar, da du nicht Informationen über mehrer
msgid "This page will be removed in a future release."
msgstr "Diese Seite wird in einer zukünftigen Version entfernt."
+msgid "This pipeline does not use the %{codeStart}needs%{codeEnd} keyword and can't be represented as a directed acyclic graph."
+msgstr ""
+
msgid "This pipeline makes use of a predefined CI/CD configuration enabled by %{strongStart}Auto DevOps.%{strongEnd}"
msgstr ""
@@ -22967,6 +23705,9 @@ msgstr ""
msgid "Time from last commit to merge"
msgstr ""
+msgid "Time in seconds"
+msgstr ""
+
msgid "Time in seconds GitLab will wait for a response from the external service. When the service does not respond in time, access will be denied."
msgstr "Zeit in Sekunden die GitLab auf die Antwort des externen Dienstes warten wird. Wenn der Dienst nicht rechtzeitig antwortet, wird der Zugriff verweigert."
@@ -23388,9 +24129,15 @@ msgstr "Gesamte Beiträge"
msgid "Total artifacts size: %{total_size}"
msgstr ""
+msgid "Total cores (CPUs)"
+msgstr ""
+
msgid "Total issues"
msgstr ""
+msgid "Total memory (GB)"
+msgstr ""
+
msgid "Total test time for all commits/merges"
msgstr "Gesamte Testzeit für alle Commits/Merges"
@@ -23592,6 +24339,9 @@ msgstr ""
msgid "U2F only works with HTTPS-enabled websites. Contact your administrator for more details."
msgstr ""
+msgid "UI Development Kit"
+msgstr ""
+
msgid "URL"
msgstr ""
@@ -23664,9 +24414,6 @@ msgstr "Der Diff konnten nicht geladen werden. %{button_try_again}"
msgid "Unable to load the merge request widget. Try reloading the page."
msgstr "Das Merge-Request-Widget konnte nicht geladen werden. Versuchen Sie die Seite neu zu laden."
-msgid "Unable to process group import file"
-msgstr ""
-
msgid "Unable to resolve"
msgstr ""
@@ -23682,6 +24429,9 @@ msgstr ""
msgid "Unable to sign you in to the group with SAML due to \"%{reason}\""
msgstr "Aufgrund von \"%{reason}\" konntest du dich nicht mit SAML anmelden"
+msgid "Unable to suggest a path. Please refresh and try again."
+msgstr ""
+
msgid "Unable to update label prioritization at this time"
msgstr "Label-Priorisierung kann zu diesem Zeitpunkt nicht aktualisiert werden"
@@ -23697,6 +24447,9 @@ msgstr ""
msgid "Unarchiving the project will restore people's ability to make changes to it. The repository can be committed to, and issues, comments, and other entities can be created. %{strong_start}Once active, this project shows up in the search and on the dashboard.%{strong_end}"
msgstr ""
+msgid "Unassign from commenting user"
+msgstr ""
+
msgid "Unblock"
msgstr ""
@@ -23710,7 +24463,7 @@ msgid "Undo ignore"
msgstr ""
msgid "Unfortunately, your email message to GitLab could not be processed."
-msgstr ""
+msgstr "Leider konnte deine E-Mail an GitLab nicht verarbeitet werden."
msgid "Uninstall"
msgstr ""
@@ -23772,6 +24525,9 @@ msgstr ""
msgid "Unreachable"
msgstr ""
+msgid "Unrecognized cluster type"
+msgstr ""
+
msgid "Unresolve"
msgstr ""
@@ -23832,6 +24588,9 @@ msgstr ""
msgid "Unsubscribes from this %{quick_action_target}."
msgstr ""
+msgid "Unsupported todo type passed. Supported todo types are: %{todo_types}"
+msgstr ""
+
msgid "Until"
msgstr ""
@@ -23859,6 +24618,9 @@ msgstr ""
msgid "Update approval rule"
msgstr ""
+msgid "Update approvers"
+msgstr ""
+
msgid "Update failed"
msgstr ""
@@ -23868,6 +24630,9 @@ msgstr ""
msgid "Update it"
msgstr ""
+msgid "Update iteration"
+msgstr ""
+
msgid "Update now"
msgstr "Jetzt updaten"
@@ -23913,7 +24678,7 @@ msgstr ""
msgid "Updated at"
msgstr ""
-msgid "Updated to"
+msgid "Updated to %{linkStart}chart v%{linkEnd}"
msgstr ""
msgid "Updating"
@@ -23943,6 +24708,9 @@ msgstr "Lade <code>GoogleCodeProjectHosting.json</code> hier hoch:"
msgid "Upload CSV file"
msgstr ""
+msgid "Upload License"
+msgstr ""
+
msgid "Upload New File"
msgstr "Neue Datei hochladen"
@@ -24024,16 +24792,19 @@ msgstr ""
msgid "UsageQuota|Pipelines"
msgstr ""
+msgid "UsageQuota|Purchase more storage"
+msgstr ""
+
msgid "UsageQuota|Repositories"
msgstr ""
msgid "UsageQuota|Repository"
msgstr ""
-msgid "UsageQuota|Storage"
+msgid "UsageQuota|Snippets"
msgstr ""
-msgid "UsageQuota|Storage usage:"
+msgid "UsageQuota|Storage"
msgstr ""
msgid "UsageQuota|This namespace has no projects which use shared runners"
@@ -24066,6 +24837,12 @@ msgstr ""
msgid "UsageQuota|Wikis"
msgstr ""
+msgid "UsageQuota|You used: %{usage} %{limit}"
+msgstr ""
+
+msgid "UsageQuota|out of %{formattedLimit} of your namespace storage"
+msgstr ""
+
msgid "Use %{code_start}::%{code_end} to create a %{link_start}scoped label set%{link_end} (eg. %{code_start}priority::1%{code_end})"
msgstr ""
@@ -24132,6 +24909,9 @@ msgstr "Benutzer-Kohorten sind nur sichtbar, wenn der %{usage_ping_link_start}Nu
msgid "User IDs"
msgstr ""
+msgid "User Lists can only be created and modified with %{linkStart}the API%{linkEnd}"
+msgstr ""
+
msgid "User OAuth applications"
msgstr ""
@@ -24156,6 +24936,9 @@ msgstr ""
msgid "User key was successfully removed."
msgstr "Benutzerschlüssel wurde erfolgreich entfernt."
+msgid "User list %{name} will be removed. Are you sure?"
+msgstr ""
+
msgid "User map"
msgstr "Benutzerzuordnung"
@@ -24177,148 +24960,10 @@ msgstr ""
msgid "User was successfully updated."
msgstr ""
-msgid "UserOnboardingTour|%{activeTour}/%{totalTours}"
-msgstr ""
-
-msgid "UserOnboardingTour|%{completed}/%{total} steps completed"
-msgstr ""
-
-msgid "UserOnboardingTour|%{emphasisStart}Well done!%{emphasisEnd}%{lineBreak}%{lineBreak}That's it for our guided tour, congratulations for making it all the way to the end!%{lineBreak}%{lineBreak}We hope this gave you a good overview of GitLab and how it can help you. We'll now show you how to create your own project and invite your colleagues."
-msgstr ""
-
-msgid "UserOnboardingTour|Adding other members to a project is done through Project Settings. Click on %{emphasisStart}Settings%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Alright, that's it for Commits. Let's take a look at the %{emphasisStart}Branches%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Awesome! Now click on %{emphasisStart}Members%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Click on one of the %{emphasisStart}Compare%{emphasisEnd} buttons to compare a branch to master."
-msgstr ""
-
-msgid "UserOnboardingTour|Click on one of the %{emphasisStart}pipeline IDs%{emphasisEnd} to see the details of a pipeline."
-msgstr ""
-
-msgid "UserOnboardingTour|Click to open the latest commit to see its details."
-msgstr ""
-
-msgid "UserOnboardingTour|Close 'Learn GitLab'"
-msgstr ""
-
-msgid "UserOnboardingTour|Commits are shown in chronological order and can be filtered by the commit message or by the branch."
-msgstr ""
-
-msgid "UserOnboardingTour|Create a project"
-msgstr ""
-
-msgid "UserOnboardingTour|Exit 'Learn GitLab'"
+msgid "UserList|Delete %{name}?"
msgstr ""
-msgid "UserOnboardingTour|Got it"
-msgstr ""
-
-msgid "UserOnboardingTour|Great job! %{clapHands} We hope the tour was helpful and that you learned how to use GitLab.%{lineBreak}%{lineBreak}We'd love to get your feedback on this tour.%{lineBreak}%{lineBreak}%{emphasisStart}How helpful would you say this guided tour was?%{emphasisEnd}%{lineBreak}%{lineBreak}"
-msgstr ""
-
-msgid "UserOnboardingTour|Guided GitLab Tour"
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can compare the changes of this branch to another one. Changes are divided by files so that it's easier to see what was changed where."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can create a project from scratch, start with a template or import a repository from other platforms. Whatever you choose, we'll guide you through the process.%{lineBreak}%{lineBreak}Fill in your new project information and click on %{emphasisStart}Create Project%{emphasisEnd} to progress to the next step."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can see the breakdown of the pipelines: its stages and jobs in each of the stages and their status.%{lineBreak}%{lineBreak}Our CI/CD pipelines are quite complex, most of our users have fewer and simpler pipelines."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can see the current members of the project (just you at the moment) and invite new members.%{lineBreak}%{lineBreak}You can invite multiple members at once (existing GitLab users or invite by email) and you can also set their roles and permissions.%{lineBreak}%{lineBreak}Add a few members and click on %{emphasisStart}Add to project%{emphasisEnd} to complete this step."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can see what changes were made with this commit, on what branch and if there's a related merge request. The status of the pipeline will also show up if CI/CD is set up.%{lineBreak}%{lineBreak}You can also comment on the lines of code that were changed and start a discussion with your colleagues!"
-msgstr ""
-
-msgid "UserOnboardingTour|Here's an overview of branches in the %{emphasisStart}%{projectName}%{emphasisEnd} project. They're split into Active and Stale.%{lineBreak}%{lineBreak}From here, you can create a new merge request from a branch, or compare the branch to any other branch in the project. By default, it will compare it to the master branch."
-msgstr ""
-
-msgid "UserOnboardingTour|Invite colleagues"
-msgstr ""
-
-msgid "UserOnboardingTour|Issues are great for communicating and keeping track of progress in GitLab. These are all issues that are open in the %{emphasisStart}%{projectName}%{emphasisEnd}.%{lineBreak}%{lineBreak}You can help us improve GitLab by contributing work to issues that are labeled <span class=\"badge color-label accept-mr-label\">Accepting merge requests</span>.%{lineBreak}%{lineBreak}This list can be filtered by labels, milestones, assignees, authors... We'll show you how it looks when the list is filtered by a label."
-msgstr ""
-
-msgid "UserOnboardingTour|Learn GitLab"
-msgstr ""
-
-msgid "UserOnboardingTour|Let's take a closer look at a merge request. Click on the title of one."
-msgstr ""
-
-msgid "UserOnboardingTour|Let's take a closer look at all the commits. Click on %{emphasisStart}Commits%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Let's take a closer look at the repository of this project. Click on %{emphasisStart}Repository%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|No thanks"
-msgstr ""
-
-msgid "UserOnboardingTour|Ok, let's go"
-msgstr ""
-
-msgid "UserOnboardingTour|Ok, show me"
-msgstr ""
-
-msgid "UserOnboardingTour|Open one of the issues by clicking on its title."
-msgstr ""
-
-msgid "UserOnboardingTour|Restart this step"
-msgstr ""
-
-msgid "UserOnboardingTour|Skip this step"
-msgstr ""
-
-msgid "UserOnboardingTour|Sweet! Your project was created and is ready to be used.%{lineBreak}%{lineBreak}You can start adding files to the repository or clone it. One last thing we want to show you is how to invite your colleagues to your new project."
-msgstr ""
-
-msgid "UserOnboardingTour|Take a look. Here's a nifty menu for quickly creating issues, merge requests, snippets, projects and groups. Click on it and select \"New project\" from the \"GitLab\" section to get started."
-msgstr ""
-
-msgid "UserOnboardingTour|Thanks for taking the guided tour. Remember, if you want to go through it again, you can start %{emphasisStart}Learn GitLab%{emphasisEnd} in the help menu on the top right."
-msgstr ""
-
-msgid "UserOnboardingTour|Thanks for the feedback! %{thumbsUp}"
-msgstr ""
-
-msgid "UserOnboardingTour|That's it for issues. Let'st take a look at %{emphasisStart}Merge Requests%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|That's it for merge requests. Now for the final part of this guided tour - the %{emphasisStart}CI/CD%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|That's it for the Repository. Let's take a look at the %{emphasisStart}Issues%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|The structure of this page is very similar to issues. Status, description, discussion and the sidebar are all here.%{lineBreak}%{lineBreak}But take a look below the description and you'll notice that there's more information about the merge request, the CI/CD pipeline and the options for approving it.%{lineBreak}%{lineBreak}Alongside the discussion you can also see more information about commits in this merge request, the status of pipelines and review all changes that were made."
-msgstr ""
-
-msgid "UserOnboardingTour|There's a lot of information here but don't worry, we'll go through it.%{lineBreak}%{lineBreak}On the top you can see the status of the issue and when it was opened and by whom. Directly below it is the issue description and below that are other %{emphasisStart}related issues%{emphasisEnd} and %{emphasisStart}merge requests%{emphasisEnd} (if any). Then below that is the %{emphasisStart}discussion%{emphasisEnd}, that's where most of the communication happens.%{lineBreak}%{lineBreak}On the right, there's a sidebar where you can view/change the %{emphasisStart}assignee, milestone, due date, labels, weight%{emphasisEnd}, etc."
-msgstr ""
-
-msgid "UserOnboardingTour|These are all the CI/CD pipelines we have for our %{emphasisStart}%{projectName}%{emphasisEnd} project.%{lineBreak}%{lineBreak}Here you can see the status of each pipeline, for what commit it's running for, its stages and the status for them."
-msgstr ""
-
-msgid "UserOnboardingTour|These are all the issues that are available for community contributions. Let's take a closer look at one of them."
-msgstr ""
-
-msgid "UserOnboardingTour|This is an overview of all merge requests in this project. Similarly to the issues overview it can be filtered down by things like labels, milestones, authors, assignees, etc."
-msgstr ""
-
-msgid "UserOnboardingTour|This is the repository for the %{emphasisStart}%{projectName}%{emphasisEnd} project. All our code is stored here. Feel free to explore and take a closer look at folders and files.%{lineBreak}%{lineBreak}Above the file structure you can see the latest commit, who the author is and the status of the CI/CD pipeline.%{lineBreak}%{lineBreak}If you scroll down below the file structure, you'll find the Readme of this project. This is defined in the README.md file at the root of the repository."
-msgstr ""
-
-msgid "UserOnboardingTour|Welcome to the project overview of the %{emphasisStart}%{projectName}%{emphasisEnd} project. This is the project that we use to work on GitLab. At first, a project seems like a simple repository, but at GitLab, a project is so much more.%{lineBreak}%{lineBreak}You can create projects for hosting your codebase, use it as an issue tracker, collaborate on code, and continuously build, test, and deploy your app with built-in GitLab CI/CD."
+msgid "UserList|created %{timeago}"
msgstr ""
msgid "UserProfile|Activity"
@@ -24471,6 +25116,9 @@ msgstr ""
msgid "UsersSelect|Unassigned"
msgstr ""
+msgid "Using %{codeStart}needs%{codeEnd} allows jobs to run before their stage is reached, as soon as their individual dependencies are met, which speeds up your pipelines."
+msgstr ""
+
msgid "Using %{code_start}::%{code_end} denotes a %{link_start}scoped label set%{link_end}"
msgstr ""
@@ -24555,9 +25203,6 @@ msgstr "Version"
msgid "Versions"
msgstr ""
-msgid "Very helpful"
-msgstr ""
-
msgid "View Documentation"
msgstr ""
@@ -24706,6 +25351,9 @@ msgstr "Öffentlich"
msgid "VisibilityLevel|Unknown"
msgstr "Unbekannt"
+msgid "Visit settings page"
+msgstr ""
+
msgid "VisualReviewApp|%{stepStart}Step 1%{stepEnd}. Copy the following script:"
msgstr ""
@@ -24799,6 +25447,9 @@ msgstr ""
msgid "VulnerabilityManagement|Something went wrong while trying to delete the comment. Please try again later."
msgstr ""
+msgid "VulnerabilityManagement|Something went wrong while trying to refresh the vulnerability. Please try again later."
+msgstr ""
+
msgid "VulnerabilityManagement|Something went wrong while trying to retrieve the vulnerability history. Please try again later."
msgstr ""
@@ -24841,6 +25492,9 @@ msgstr ""
msgid "Vulnerability|Class"
msgstr "Klasse"
+msgid "Vulnerability|Crash Address"
+msgstr ""
+
msgid "Vulnerability|Description"
msgstr "Beschreibung"
@@ -24916,9 +25570,6 @@ msgstr ""
msgid "We couldn't reach the Prometheus server. Either the server no longer exists or the configuration details need updating."
msgstr ""
-msgid "We created a short guided tour that will help you learn the basics of GitLab and how it will help you be better at your job. It should only take a couple of minutes. You will be guided by two types of helpers, best recognized by their color."
-msgstr ""
-
msgid "We detected potential spam in the %{humanized_resource_name}. Please solve the reCAPTCHA to proceed."
msgstr "Wir haben potenziellen Spam in %{humanized_resource_name} gefunden. Bitte löse den reCAPTCHA um fortzufahren."
@@ -24952,6 +25603,9 @@ msgstr ""
msgid "We've found no vulnerabilities"
msgstr ""
+msgid "Web Application Firewall"
+msgstr ""
+
msgid "Web IDE"
msgstr "Web IDE"
@@ -25012,9 +25666,6 @@ msgstr ""
msgid "Welcome to GitLab.com<br>@%{name}!"
msgstr ""
-msgid "Welcome to the Guided GitLab Tour"
-msgstr ""
-
msgid "Welcome to the guided GitLab tour"
msgstr ""
@@ -25062,9 +25713,6 @@ msgstr ""
msgid "While it's rare to have no vulnerabilities, it can happen. In any event, we ask that you please double check your settings to make sure you've set up your dashboard correctly."
msgstr ""
-msgid "White helpers give contextual information."
-msgstr ""
-
msgid "Who can be an approver?"
msgstr ""
@@ -25110,12 +25758,18 @@ msgstr "Es gibt bereits eine Seite mit demselben Titel in diesem Pfad."
msgid "WikiEmptyIssueMessage|Suggest wiki improvement"
msgstr "Schlage eine Verbesserung des Wikis vor"
+msgid "WikiEmptyIssueMessage|You must be a group member in order to add wiki pages. If you have suggestions for how to improve the wiki for this group, consider opening an issue in the %{issues_link}."
+msgstr ""
+
msgid "WikiEmptyIssueMessage|You must be a project member in order to add wiki pages. If you have suggestions for how to improve the wiki for this project, consider opening an issue in the %{issues_link}."
msgstr "Du musst Projekt-Mitglied sein um Wiki-Seiten hinzufügen zu können. Hast du Verbesserungsvorschläge, wie du das Wiki für dieses Projekt verbessern kannst, dann öffne ein Thema in der %{issues_link}."
msgid "WikiEmptyIssueMessage|issue tracker"
msgstr "Ticketsystem"
+msgid "WikiEmpty|A wiki is where you can store all the details about your group. This can include why you've created it, its principles, how to use it, and so on."
+msgstr ""
+
msgid "WikiEmpty|A wiki is where you can store all the details about your project. This can include why you've created it, its principles, how to use it, and so on."
msgstr "In einem Wiki können alle Details zu deinem Projekt gespeichert werden. Dies kann z. B. beinhalten, warum du es erstellt hast, seine Grundlagen, wie das Projekt verwendet werden kann und so weiter."
@@ -25125,12 +25779,21 @@ msgstr "Erstelle deine erste Seite"
msgid "WikiEmpty|Suggest wiki improvement"
msgstr "Schlage eine Verbesserung des Wikis vor"
+msgid "WikiEmpty|The wiki lets you write documentation for your group"
+msgstr ""
+
msgid "WikiEmpty|The wiki lets you write documentation for your project"
msgstr "Mit dem Wiki kannst du eine Dokumentation für dein Projekt schreiben"
+msgid "WikiEmpty|This group has no wiki pages"
+msgstr ""
+
msgid "WikiEmpty|This project has no wiki pages"
msgstr "Dieses Projekt hat keine Wiki-Seiten"
+msgid "WikiEmpty|You must be a group member in order to add wiki pages."
+msgstr ""
+
msgid "WikiEmpty|You must be a project member in order to add wiki pages."
msgstr "Du musst ein Projektmitglied sein, um Wiki-Seiten hinzufügen zu können."
@@ -25407,15 +26070,15 @@ msgstr ""
msgid "You can now export your security dashboard to a CSV report."
msgstr ""
+msgid "You can now manage alert endpoint configuration in the Alerts section on the Operations settings page. Fields on this page have been deprecated."
+msgstr ""
+
msgid "You can now submit a merge request to get this change into the original branch."
msgstr ""
msgid "You can now submit a merge request to get this change into the original project."
msgstr ""
-msgid "You can only add files when you are on a branch"
-msgstr "Du kannst Dateien nur hinzufügen, wenn du dich auf einem Branch befindest"
-
msgid "You can only edit files when you are on a branch"
msgstr "Du kannst Dateien nur bearbeiten, wenn du dich auf einem Branch befindest"
@@ -25635,7 +26298,7 @@ msgstr ""
msgid "You need to upload a Google Takeout archive."
msgstr ""
-msgid "You reached %{usage_in_percent} of %{namespace_name}'s capacity (%{used_storage} of %{storage_limit})"
+msgid "You reached %{usage_in_percent} of %{namespace_name}'s storage capacity (%{used_storage} of %{storage_limit})"
msgstr ""
msgid "You tried to fork %{link_to_the_project} but it failed for the following reason:"
@@ -25938,13 +26601,13 @@ msgstr ""
msgid "Your subscription expired!"
msgstr ""
-msgid "Your subscription has been downgraded"
+msgid "Your subscription has been downgraded."
msgstr ""
-msgid "Your subscription will automatically renew in %{remaining_days}"
+msgid "Your subscription will automatically renew in %{remaining_days}."
msgstr ""
-msgid "Your subscription will expire in %{remaining_days}"
+msgid "Your subscription will expire in %{remaining_days}."
msgstr ""
msgid "Zoom meeting added"
@@ -26024,9 +26687,6 @@ msgstr ""
msgid "branch name"
msgstr "Branch-Name"
-msgid "build pipeline reference mismatch"
-msgstr ""
-
msgid "by"
msgstr "von"
@@ -26045,6 +26705,9 @@ msgstr ""
msgid "cannot block others"
msgstr ""
+msgid "cannot contain HTML/XML tags, including any word between angle brackets (<,>)."
+msgstr ""
+
msgid "cannot include leading slash or directory traversal."
msgstr ""
@@ -26075,55 +26738,6 @@ msgstr ""
msgid "ciReport|%{remainingPackagesCount} more"
msgstr "%{remainingPackagesCount} mehr"
-msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability"
-msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability for the source branch only"
-msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities for the source branch only"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerability"
-msgid_plural "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerabilities"
-msgstr[0] "%{reportType} %{status} hat %{fixedCount} behobene Sicherheitslücke entdeckt"
-msgstr[1] "%{reportType} %{status} hat %{fixedCount} behobene Sicherheitslücke entdeckt"
-
-msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new vulnerability"
-msgid_plural "ciReport|%{reportType} %{status} detected %{newCount} new vulnerabilities"
-msgstr[0] "%{reportType} %{status} hat %{newCount} neue Sicherheitslücke entdeckt"
-msgstr[1] "%{reportType} %{status} hat %{newCount} neue Sicherheitslücken entdeckt"
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities for the source branch only"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{fixedCount} fixed vulnerabilities"
-msgstr "%{reportType} %{status} hat %{newCount} neue und %{fixedCount} behobene Sicherheitslücken entdeckt"
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} vulnerability for the source branch only"
-msgid_plural "ciReport|%{reportType} %{status} detected %{newCount} vulnerabilities for the source branch only"
-msgstr[0] "%{reportType} %{status} hat %{newCount} Sicherheitslücke für den Quellbranch festgestellt"
-msgstr[1] "%{reportType} %{status} hat %{newCount} Sicherheitslücken für den Quellbranch festgestellt"
-
-msgid "ciReport|%{reportType} %{status} detected no new vulnerabilities"
-msgstr "%{reportType} %{status} hat keine neue Sicherheitslücke entdeckt"
-
-msgid "ciReport|%{reportType} %{status} detected no vulnerabilities"
-msgstr "%{reportType} %{status} hat keine Sicherheitslücken entdeckt"
-
-msgid "ciReport|%{reportType} %{status} detected no vulnerabilities for the source branch only"
-msgstr "%{reportType} %{status} hat nur für den Quellbranch keine Sicherheitslücken festgestellt"
-
msgid "ciReport|%{reportType} is loading"
msgstr "%{reportType} wird geladen"
@@ -26142,7 +26756,7 @@ msgstr "(Lädt, Fehler beim Laden der Ergebnisse)"
msgid "ciReport|All projects"
msgstr ""
-msgid "ciReport|All report types"
+msgid "ciReport|All scanner types"
msgstr ""
msgid "ciReport|All severities"
@@ -26166,6 +26780,9 @@ msgstr "Container-Scan"
msgid "ciReport|Container scanning detects known vulnerabilities in your docker images."
msgstr "Containerüberprüfung entdeckt bekannte Sicherheitslücken in deinen Docker-Images."
+msgid "ciReport|Coverage Fuzzing"
+msgstr ""
+
msgid "ciReport|Create a merge request to implement this solution, or download and apply the patch manually."
msgstr ""
@@ -26324,6 +26941,9 @@ msgstr ""
msgid "customize"
msgstr "anpassen"
+msgid "data"
+msgstr ""
+
msgid "date must not be after 9999-12-31"
msgstr ""
@@ -26394,6 +27014,9 @@ msgstr ""
msgid "entries cannot contain HTML tags"
msgstr ""
+msgid "epic"
+msgstr ""
+
msgid "error"
msgstr ""
@@ -26415,9 +27038,6 @@ msgstr ""
msgid "expires on %{timebox_due_date}"
msgstr ""
-msgid "external_url"
-msgstr "external_url"
-
msgid "failed"
msgstr ""
@@ -26442,22 +27062,19 @@ msgid "for %{link_to_pipeline_ref}"
msgstr ""
msgid "for %{ref}"
-msgstr ""
+msgstr "für %{ref}"
msgid "for this project"
msgstr "für dieses Projekt"
msgid "fork this project"
-msgstr ""
+msgstr "dieses Projekt forken"
msgid "from"
msgstr "von"
-msgid "geo_node_name"
-msgstr ""
-
msgid "group"
-msgstr ""
+msgstr "Gruppe"
msgid "groups"
msgstr "Gruppen"
@@ -26466,10 +27083,10 @@ msgid "has already been linked to another vulnerability"
msgstr ""
msgid "has already been taken"
-msgstr ""
+msgstr "ist bereits vergeben"
msgid "help"
-msgstr ""
+msgstr "Hilfe"
msgid "here"
msgstr "hier"
@@ -26477,11 +27094,8 @@ msgstr "hier"
msgid "https://your-bitbucket-server"
msgstr "https://dein-bitbucket-server"
-msgid "image"
-msgstr ""
-
msgid "image diff"
-msgstr ""
+msgstr "Bildunterschied"
msgid "impersonation token"
msgstr ""
@@ -26496,13 +27110,13 @@ msgid "importing"
msgstr "Importiere"
msgid "in group %{link_to_group}"
-msgstr ""
+msgstr "In Gruppe %{link_to_group}"
msgid "in project %{link_to_project}"
-msgstr ""
+msgstr "im Projekt %{link_to_project}"
msgid "index"
-msgstr ""
+msgstr "Index"
msgid "instance completed"
msgid_plural "instances completed"
@@ -26510,22 +27124,22 @@ msgstr[0] "Instanz abgeschlossen"
msgstr[1] "Instanzen abgeschlossen"
msgid "invalid milestone state `%{state}`"
-msgstr ""
+msgstr "ungültiger Meilensteinstatus `%{state}`"
msgid "is"
-msgstr ""
+msgstr "ist"
msgid "is already associated to a GitLab Issue. New issue will not be associated."
msgstr ""
msgid "is an invalid IP address range"
-msgstr ""
+msgstr "ist ein ungültiger IP-Adressbereich"
msgid "is blocked by"
msgstr ""
msgid "is enabled."
-msgstr ""
+msgstr "ist aktiviert."
msgid "is invalid because there is downstream lock"
msgstr "ist ungültig, weil es eine Downstream-Sperre gibt"
@@ -26534,7 +27148,7 @@ msgid "is invalid because there is upstream lock"
msgstr "ist aufgrund einer Upstream-Sperre ungültig"
msgid "is not"
-msgstr ""
+msgstr "ist nicht"
msgid "is not a descendant of the Group owning the template"
msgstr ""
@@ -26546,7 +27160,7 @@ msgid "is not allowed. Try again with a different email address, or contact your
msgstr ""
msgid "is not an email you own"
-msgstr ""
+msgstr "ist keine Ihrer E-Mails"
msgid "is not in the group enforcing Group Managed Account"
msgstr ""
@@ -26558,13 +27172,13 @@ msgid "is too long (%{current_value}). The maximum size is %{max_size}."
msgstr ""
msgid "is too long (maximum is 100 entries)"
-msgstr ""
+msgstr "ist zu lang (maximal 100 Einträge)"
msgid "is too long (maximum is 1000 entries)"
-msgstr ""
+msgstr "ist zu lang (mehr als 1000 Einträge)"
msgid "issue"
-msgstr ""
+msgstr "Ticket"
msgid "issues at risk"
msgstr ""
@@ -26585,13 +27199,13 @@ msgid "it is stored externally"
msgstr ""
msgid "it is stored in LFS"
-msgstr ""
+msgstr "es ist im LFS gespeichert"
msgid "it is too large"
-msgstr ""
+msgstr "ìst zu groß"
msgid "jigsaw is not defined"
-msgstr ""
+msgstr "jigsaw ist nicht definiert"
msgid "jira.issue.description.content"
msgstr ""
@@ -26600,7 +27214,7 @@ msgid "jira.issue.summary"
msgstr ""
msgid "latest"
-msgstr ""
+msgstr "letzte"
msgid "latest deployment"
msgstr "neuste Bereitstellung"
@@ -26609,28 +27223,31 @@ msgid "latest version"
msgstr "Neuste Version"
msgid "leave %{group_name}"
-msgstr ""
+msgstr "%{group_name} verlassen"
msgid "less than a minute"
-msgstr ""
+msgstr "weniger als eine Minute"
msgid "level: %{level}"
-msgstr ""
+msgstr "Level: %{level}"
msgid "limit of %{project_limit} reached"
-msgstr ""
+msgstr "Limit von %{project_limit} erreicht"
msgid "load it anyway"
msgstr ""
+msgid "loading"
+msgstr ""
+
msgid "locked by %{path_lock_user_name} %{created_at}"
msgstr "gesperrt durch %{path_lock_user_name} %{created_at}"
msgid "log in"
-msgstr ""
+msgstr "einloggen"
msgid "manual"
-msgstr ""
+msgstr "manuell"
msgid "math|The math in this entry is taking too long to render and may not be displayed as expected. For performance reasons, math blocks are also limited to %{maxChars} characters. Consider splitting up large formulae, splitting math blocks among multiple entries, or using an image instead."
msgstr ""
@@ -26647,10 +27264,10 @@ msgid "merged %{time_ago}"
msgstr ""
msgid "missing"
-msgstr ""
+msgstr "fehlt"
msgid "most recent deployment"
-msgstr ""
+msgstr "letzte Bereitstellung"
msgid "mrWidgetCommitsAdded|%{commitCount} and %{mergeCommitCount} will be added to %{targetBranch}."
msgstr ""
@@ -26751,9 +27368,6 @@ msgstr ""
msgid "mrWidget|Deployment statistics are not available currently"
msgstr "Bereitstellungsstatistiken derzeit nicht verfügbar"
-msgid "mrWidget|Detect issues before deployment with a CI pipeline that continuously tests your code. We created a quick guide that will show you how to create one. Make your code more secure and more robust in just a minute."
-msgstr ""
-
msgid "mrWidget|Did not close"
msgstr "Wurde nicht geschlossen"
@@ -26916,6 +27530,9 @@ msgstr "Dieses Projekt wurde archiviert, der Schreibzugriff wurde deaktiviert"
msgid "mrWidget|To approve this merge request, please enter your password. This project requires all approvals to be authenticated."
msgstr ""
+msgid "mrWidget|Use %{linkStart}CI pipelines to test your code%{linkEnd}, simply add a GitLab CI configuration file to your project. It only takes a minute to make your code more secure and robust."
+msgstr ""
+
msgid "mrWidget|When this merge request is ready, remove the WIP: prefix from the title to allow it to be merged"
msgstr ""
@@ -26952,6 +27569,9 @@ msgstr ""
msgid "must be greater than start date"
msgstr ""
+msgid "must contain only valid frameworks"
+msgstr ""
+
msgid "my-awesome-group"
msgstr ""
@@ -26964,9 +27584,12 @@ msgstr ""
msgid "needs to be between 10 minutes and 1 month"
msgstr ""
-msgid "never expires"
+msgid "never"
msgstr ""
+msgid "never expires"
+msgstr "verfällt nie"
+
msgid "new merge request"
msgstr "Neuer Merge-Request"
@@ -26991,6 +27614,9 @@ msgstr "Benachrichtungs-E-mails"
msgid "nounSeries|%{firstItem} and %{lastItem}"
msgstr ""
+msgid "nounSeries|%{item}"
+msgstr ""
+
msgid "nounSeries|%{item}, %{nextItem}"
msgstr ""
@@ -27127,14 +27753,17 @@ msgstr ""
msgid "revised"
msgstr ""
+msgid "satisfied"
+msgstr ""
+
msgid "score"
msgstr ""
msgid "security Reports|There was an error creating the merge request"
-msgstr ""
+msgstr "Fehler während der Merge-Request-Erstellung"
msgid "settings saved, but not activated"
-msgstr ""
+msgstr "Einstellungen gespeichert, aber nicht aktiviert"
msgid "severity|Critical"
msgstr "Kritisch"
@@ -27146,40 +27775,43 @@ msgid "severity|Info"
msgstr "Info"
msgid "severity|Low"
-msgstr ""
+msgstr "Niedrig"
msgid "severity|Medium"
-msgstr ""
+msgstr "Mittel"
msgid "severity|None"
msgstr "Keine"
msgid "severity|Unknown"
+msgstr "Unbekannt"
+
+msgid "should be an array of %{object_name} objects"
msgstr ""
msgid "should be greater than or equal to %{access} inherited membership from group %{group_name}"
msgstr ""
msgid "show %{count} more"
-msgstr ""
+msgstr "zeige %{count} weitere"
msgid "show fewer"
-msgstr ""
+msgstr "weniger anzeigen"
msgid "show less"
-msgstr ""
+msgstr "weniger anzeigen"
msgid "sign in"
-msgstr ""
+msgstr "Einloggen"
msgid "sort:"
-msgstr ""
+msgstr "Sortierung:"
msgid "source"
msgstr "Quelle"
msgid "source diff"
-msgstr ""
+msgstr "Quelldif"
msgid "specified top is not part of the tree"
msgstr ""
@@ -27203,31 +27835,34 @@ msgid "stuck"
msgstr ""
msgid "success"
-msgstr ""
+msgstr "Erfolg"
msgid "suggestPipeline|1/2: Choose a template"
-msgstr ""
+msgstr "1/2: Eine Vorlage auswählen"
msgid "suggestPipeline|2/2: Commit your changes"
+msgstr "2/2: Deine Änderungen committen"
+
+msgid "suggestPipeline|Choose %{boldStart}Code Quality%{boldEnd} to add a pipeline that tests the quality of your code."
msgstr ""
-msgid "suggestPipeline|Commit the changes and your pipeline will automatically run for the first time."
+msgid "suggestPipeline|The template is ready! You can now commit it to create your first pipeline."
msgstr ""
-msgid "suggestPipeline|We recommend the %{boldStart}Code Quality%{boldEnd} template, which will add a report widget to your Merge Requests. This way you’ll learn about code quality degradations much sooner. %{footerStart} Goodbye technical debt! %{footerEnd}"
+msgid "suggestPipeline|We’re adding a GitLab CI configuration file to add a pipeline to the project. You could create it manually, but we recommend that you start with a GitLab template that works out of the box."
msgstr ""
msgid "syntax is correct"
-msgstr ""
+msgstr "Syntax ist korrekt"
msgid "syntax is incorrect"
-msgstr ""
+msgstr "Syntax ist nicht korrekt"
msgid "tag name"
-msgstr ""
+msgstr "Tag-Name"
msgid "the following issue(s)"
-msgstr ""
+msgstr "die folgenden Ticket(s)"
msgid "this document"
msgstr "dieses Dokument"
@@ -27245,19 +27880,19 @@ msgid "toggle dropdown"
msgstr ""
msgid "triggered"
-msgstr ""
+msgstr "ausgelöst"
msgid "unicode domains should use IDNA encoding"
-msgstr ""
+msgstr "Unicode-Domains sollten IDNA-Kodierung verwenden"
msgid "updated"
-msgstr ""
+msgstr "aktualisiert"
msgid "updated %{timeAgo}"
-msgstr ""
+msgstr "aktualisierte %{timeAgo}"
msgid "updated %{time_ago}"
-msgstr ""
+msgstr "aktualisiert %{time_ago}"
msgid "uploaded"
msgstr ""
@@ -27266,7 +27901,7 @@ msgid "uploads"
msgstr ""
msgid "user avatar"
-msgstr ""
+msgstr "Benutzeravatar"
msgid "username"
msgstr "Benutzername"
@@ -27274,23 +27909,26 @@ msgstr "Benutzername"
msgid "uses Kubernetes clusters to deploy your code!"
msgstr "verwendet Kubernetes-Cluster, um deinen Code bereitzustellen!"
+msgid "v%{version} published %{timeAgo}"
+msgstr ""
+
msgid "verify ownership"
msgstr ""
msgid "version %{versionIndex}"
-msgstr ""
+msgstr "Version %{versionIndex}"
msgid "via %{closed_via}"
-msgstr ""
+msgstr "durch %{closed_via}"
msgid "via merge request %{link}"
-msgstr ""
+msgstr "durch Merge-Request %{link}"
msgid "view it on GitLab"
msgstr "sieh es auf GitLab an"
msgid "view the blob"
-msgstr ""
+msgstr "Blob anschauen"
msgid "view the source"
msgstr ""
@@ -27302,7 +27940,7 @@ msgid "vulnerability|Add a comment or reason for dismissal"
msgstr ""
msgid "vulnerability|Add comment"
-msgstr ""
+msgstr "Kommentar hinzufügen"
msgid "vulnerability|Add comment & dismiss"
msgstr ""
@@ -27311,7 +27949,7 @@ msgid "vulnerability|Dismiss vulnerability"
msgstr ""
msgid "vulnerability|Save comment"
-msgstr ""
+msgstr "Kommentar speichern"
msgid "vulnerability|Undo dismiss"
msgstr ""
@@ -27320,7 +27958,7 @@ msgid "vulnerability|dismissed"
msgstr "verworfen"
msgid "wiki page"
-msgstr ""
+msgstr "Wiki-Seite"
msgid "will be released %{time}"
msgstr ""
@@ -27335,5 +27973,5 @@ msgid "with expiry remaining unchanged at %{old_expiry}"
msgstr ""
msgid "yaml invalid"
-msgstr ""
+msgstr "yaml ungültig"
diff --git a/locale/el_GR/gitlab.po b/locale/el_GR/gitlab.po
index 12a2ec0292c..f9116be14b6 100644
--- a/locale/el_GR/gitlab.po
+++ b/locale/el_GR/gitlab.po
@@ -2,7 +2,7 @@ msgid ""
msgstr ""
"Project-Id-Version: gitlab-ee\n"
"Report-Msgid-Bugs-To: \n"
-"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Last-Translator: \n"
"Language-Team: Greek\n"
"Language: el_GR\n"
"MIME-Version: 1.0\n"
@@ -10,9 +10,11 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Crowdin-Project: gitlab-ee\n"
+"X-Crowdin-Project-ID: 288872\n"
"X-Crowdin-Language: el\n"
"X-Crowdin-File: /master/locale/gitlab.pot\n"
-"PO-Revision-Date: 2020-06-08 15:16\n"
+"X-Crowdin-File-ID: 6\n"
+"PO-Revision-Date: 2020-07-02 01:25\n"
msgid " %{start} to %{end}"
msgstr ""
@@ -197,6 +199,11 @@ msgid_plural "%d metrics"
msgstr[0] ""
msgstr[1] ""
+msgid "%d milestone"
+msgid_plural "%d milestones"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "%d minute"
msgid_plural "%d minutes"
msgstr[0] ""
@@ -242,11 +249,6 @@ msgid_plural "%d unresolved threads"
msgstr[0] ""
msgstr[1] ""
-msgid "%d url scanned"
-msgid_plural "%d urls scanned"
-msgstr[0] ""
-msgstr[1] ""
-
msgid "%d vulnerability"
msgid_plural "%d vulnerabilities"
msgstr[0] ""
@@ -324,9 +326,15 @@ msgstr[1] ""
msgid "%{count} related %{pluralized_subject}: %{links}"
msgstr ""
+msgid "%{dashboard_path} could not be found."
+msgstr ""
+
msgid "%{days} days until tags are automatically removed"
msgstr ""
+msgid "%{deployLinkStart}Use a template to deploy to ECS%{deployLinkEnd}, or use a docker image to %{commandsLinkStart}run AWS commands in GitLab CI/CD%{commandsLinkEnd}."
+msgstr ""
+
msgid "%{description}- Sentry event: %{errorUrl}- First seen: %{firstSeen}- Last seen: %{lastSeen} %{countLabel}: %{count}%{userCountLabel}: %{userCount}"
msgstr ""
@@ -369,12 +377,18 @@ msgstr ""
msgid "%{host} sign-in from new location"
msgstr ""
-msgid "%{icon}You are about to add %{usersTag} people to the discussion. Proceed with caution."
+msgid "%{icon}You are about to add %{usersTag} people to the discussion. They will all receive a notification."
+msgstr ""
+
+msgid "%{integrations_link_start}Integrations%{link_end} enable you to make third-party applications part of your GitLab workflow. If the available integrations don't meet your needs, consider using a %{webhooks_link_start}webhook%{link_end}."
msgstr ""
msgid "%{issuableType} will be removed! Are you sure?"
msgstr ""
+msgid "%{issuesCount} issues in this group"
+msgstr ""
+
msgid "%{issuesSize} issues"
msgstr ""
@@ -384,7 +398,7 @@ msgstr ""
msgid "%{labelStart}Class:%{labelEnd} %{class}"
msgstr ""
-msgid "%{labelStart}Confidence:%{labelEnd} %{confidence}"
+msgid "%{labelStart}Evidence:%{labelEnd} %{evidence}"
msgstr ""
msgid "%{labelStart}File:%{labelEnd} %{file}"
@@ -402,6 +416,9 @@ msgstr ""
msgid "%{labelStart}Report Type:%{labelEnd} %{reportType}"
msgstr ""
+msgid "%{labelStart}Scanner:%{labelEnd} %{scanner}"
+msgstr ""
+
msgid "%{labelStart}Severity:%{labelEnd} %{severity}"
msgstr ""
@@ -426,9 +443,6 @@ msgstr ""
msgid "%{link_start}Read more%{link_end} about role permissions"
msgstr ""
-msgid "%{link} can be used for binding events when something is happening within the project."
-msgstr ""
-
msgid "%{listToShow}, and %{awardsListLength} more."
msgstr ""
@@ -462,6 +476,9 @@ msgstr ""
msgid "%{name} found %{resultsString}"
msgstr ""
+msgid "%{name} is already being used for another emoji"
+msgstr ""
+
msgid "%{name} is scheduled for %{action}"
msgstr ""
@@ -509,6 +526,9 @@ msgstr ""
msgid "%{primary} (%{secondary})"
msgstr ""
+msgid "%{ref} cannot be added: %{error}"
+msgstr ""
+
msgid "%{releases} release"
msgid_plural "%{releases} releases"
msgstr[0] ""
@@ -517,13 +537,37 @@ msgstr[1] ""
msgid "%{remaining_approvals} left"
msgstr ""
-msgid "%{retryButtonStart}Try again%{retryButtonEnd} or %{newFileButtonStart}attach a new file%{newFileButtonEnd}"
+msgid "%{reportType} %{status} detected %{critical} critical and %{high} high severity vulnerabilities out of %{total}."
+msgstr ""
+
+msgid "%{reportType} %{status} detected %{critical} critical and %{high} high severity vulnerabilities."
+msgstr ""
+
+msgid "%{reportType} %{status} detected %{critical} critical severity vulnerabilities out of %{total}."
+msgstr ""
+
+msgid "%{reportType} %{status} detected %{critical} critical severity vulnerability."
+msgid_plural "%{reportType} %{status} detected %{critical} critical severity vulnerabilities."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{reportType} %{status} detected %{high} high severity vulnerabilities out of %{total}."
msgstr ""
-msgid "%{screenreaderOnlyStart}Keyboard shorcuts%{screenreaderOnlyEnd} Disabled"
+msgid "%{reportType} %{status} detected %{high} high severity vulnerability."
+msgid_plural "%{reportType} %{status} detected %{high} high severity vulnerabilities."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{reportType} %{status} detected %{other} vulnerability."
+msgid_plural "%{reportType} %{status} detected %{other} vulnerabilities."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{reportType} %{status} detected no new vulnerabilities."
msgstr ""
-msgid "%{screenreaderOnlyStart}Keyboard shorcuts%{screenreaderOnlyEnd} Enabled"
+msgid "%{retryButtonStart}Try again%{retryButtonEnd} or %{newFileButtonStart}attach a new file%{newFileButtonEnd}"
msgstr ""
msgid "%{service_title} %{message}."
@@ -620,6 +664,12 @@ msgstr ""
msgid "%{token}..."
msgstr ""
+msgid "%{totalCpu} (%{freeSpacePercentage}%{percentSymbol} free)"
+msgstr ""
+
+msgid "%{totalMemory} (%{freeSpacePercentage}%{percentSymbol} free)"
+msgstr ""
+
msgid "%{totalWeight} total weight"
msgstr ""
@@ -650,6 +700,12 @@ msgstr ""
msgid "%{verb} %{time_spent_value} spent time."
msgstr ""
+msgid "%{webhooks_link_start}%{webhook_type}%{link_end} enable you to send notifications to web applications in response to events in a group or project."
+msgstr ""
+
+msgid "%{webhooks_link_start}%{webhook_type}%{link_end} enable you to send notifications to web applications in response to events in a group or project. We recommend using an %{integrations_link_start}integration%{link_end} in preference to a webhook."
+msgstr ""
+
msgid "'%{level}' is not a valid visibility level"
msgstr ""
@@ -676,9 +732,6 @@ msgstr ""
msgid "(No changes)"
msgstr ""
-msgid "(Show all)"
-msgstr ""
-
msgid "(check progress)"
msgstr ""
@@ -745,6 +798,9 @@ msgstr ""
msgid "0 for unlimited"
msgstr ""
+msgid "0 for unlimited, only effective with remote storage enabled."
+msgstr ""
+
msgid "1 %{type} addition"
msgid_plural "%{count} %{type} additions"
msgstr[0] ""
@@ -892,6 +948,12 @@ msgstr ""
msgid "<code>\"johnsmith@example.com\": \"johnsmith@example.com\"</code> will add \"By <a href=\"#\">johnsmith@example.com</a>\" to all issues and comments originally created by johnsmith@example.com. By default, the email address or username is masked to ensure the user's privacy. Use this option if you want to show the full email address."
msgstr ""
+msgid "<code>Masked</code> to prevent the values from being displayed in job logs (must match certain regexp requirements)."
+msgstr ""
+
+msgid "<code>Protected</code> to expose them to protected branches or tags only."
+msgstr ""
+
msgid "<namespace / project>"
msgstr ""
@@ -919,9 +981,6 @@ msgstr ""
msgid "A .NET Core console application template, customizable for any .NET Core project"
msgstr ""
-msgid "A DAG must have two dependent jobs to be visualized on this tab."
-msgstr ""
-
msgid "A GitBook site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
msgstr ""
@@ -952,6 +1011,12 @@ msgstr ""
msgid "A deleted user"
msgstr ""
+msgid "A file has been changed."
+msgstr ""
+
+msgid "A file was not found."
+msgstr ""
+
msgid "A file with '%{file_name}' already exists in %{branch} branch"
msgstr ""
@@ -1018,7 +1083,7 @@ msgstr ""
msgid "A subscription will trigger a new pipeline on the default branch of this project when a pipeline successfully completes for a new tag on the %{default_branch_docs} of the subscribed project."
msgstr ""
-msgid "A terraform report was generated in your pipelines."
+msgid "A suggestion is not applicable."
msgstr ""
msgid "A user with write access to the source branch selected this option"
@@ -1189,6 +1254,9 @@ msgstr ""
msgid "Action to take when receiving an alert."
msgstr ""
+msgid "Actions"
+msgstr ""
+
msgid "Activate"
msgstr ""
@@ -1245,6 +1313,9 @@ msgstr ""
msgid "Add LICENSE"
msgstr ""
+msgid "Add New Node"
+msgstr ""
+
msgid "Add README"
msgstr ""
@@ -1320,6 +1391,9 @@ msgstr ""
msgid "Add approval rule"
msgstr ""
+msgid "Add approvers"
+msgstr ""
+
msgid "Add bold text"
msgstr ""
@@ -1377,6 +1451,9 @@ msgstr ""
msgid "Add strikethrough text"
msgstr ""
+msgid "Add suggestion to batch"
+msgstr ""
+
msgid "Add system hook"
msgstr ""
@@ -1476,6 +1553,9 @@ msgstr ""
msgid "Admin Note"
msgstr ""
+msgid "Admin Notifications"
+msgstr ""
+
msgid "Admin Overview"
msgstr ""
@@ -1587,6 +1667,9 @@ msgstr ""
msgid "AdminSettings|Integrations configured here will automatically apply to all projects on this instance."
msgstr ""
+msgid "AdminSettings|Moved to integrations"
+msgstr ""
+
msgid "AdminSettings|No required pipeline"
msgstr ""
@@ -1817,12 +1900,18 @@ msgid_plural "Alerts"
msgstr[0] ""
msgstr[1] ""
+msgid "AlertManagement| assign yourself"
+msgstr ""
+
msgid "AlertManagement|Acknowledged"
msgstr ""
msgid "AlertManagement|Alert"
msgstr ""
+msgid "AlertManagement|Alert assignee(s): %{assignees}"
+msgstr ""
+
msgid "AlertManagement|Alert detail"
msgstr ""
@@ -1838,9 +1927,15 @@ msgstr ""
msgid "AlertManagement|All alerts"
msgstr ""
+msgid "AlertManagement|Assign To"
+msgstr ""
+
msgid "AlertManagement|Assign status"
msgstr ""
+msgid "AlertManagement|Assignee"
+msgstr ""
+
msgid "AlertManagement|Assignees"
msgstr ""
@@ -1859,9 +1954,6 @@ msgstr ""
msgid "AlertManagement|Edit"
msgstr ""
-msgid "AlertManagement|End time"
-msgstr ""
-
msgid "AlertManagement|Events"
msgstr ""
@@ -1880,10 +1972,13 @@ msgstr ""
msgid "AlertManagement|More information"
msgstr ""
+msgid "AlertManagement|No Matching Results"
+msgstr ""
+
msgid "AlertManagement|No alert data to display."
msgstr ""
-msgid "AlertManagement|No alerts available to display. If you think you're seeing this message in error, refresh the page."
+msgid "AlertManagement|No alerts available to display. See %{linkStart}enabling alert management%{linkEnd} for more information on adding alerts to the list."
msgstr ""
msgid "AlertManagement|No alerts to display."
@@ -1892,6 +1987,9 @@ msgstr ""
msgid "AlertManagement|None"
msgstr ""
+msgid "AlertManagement|None -"
+msgstr ""
+
msgid "AlertManagement|Open"
msgstr ""
@@ -1928,9 +2026,18 @@ msgstr ""
msgid "AlertManagement|There was an error displaying the alerts. Confirm your endpoint's configuration details to ensure alerts appear."
msgstr ""
+msgid "AlertManagement|There was an error while updating the assignee(s) list. Please try again."
+msgstr ""
+
+msgid "AlertManagement|There was an error while updating the assignee(s) of the alert. Please try again."
+msgstr ""
+
msgid "AlertManagement|There was an error while updating the status of the alert. Please try again."
msgstr ""
+msgid "AlertManagement|This assignee cannot be assigned to this alert."
+msgstr ""
+
msgid "AlertManagement|Tool"
msgstr ""
@@ -2078,6 +2185,9 @@ msgstr ""
msgid "Allow users to request access (if visibility is public or internal)"
msgstr ""
+msgid "Allowed Geo IP"
+msgstr ""
+
msgid "Allowed email domain restriction only permitted for top-level groups"
msgstr ""
@@ -2111,6 +2221,9 @@ msgstr ""
msgid "Amazon Web Services"
msgstr ""
+msgid "Amazon Web Services Logo"
+msgstr ""
+
msgid "Amazon authentication is not %{link_start}correctly configured%{link_end}. Ask your GitLab administrator if you want to use this service."
msgstr ""
@@ -2168,10 +2281,16 @@ msgstr ""
msgid "An error occurred when updating the issue weight"
msgstr ""
+msgid "An error occurred while acknowledging the notification. Refresh the page and try again."
+msgstr ""
+
+msgid "An error occurred while adding approvers"
+msgstr ""
+
msgid "An error occurred while adding formatted title for epic"
msgstr ""
-msgid "An error occurred while checking group path"
+msgid "An error occurred while checking group path. Please refresh and try again."
msgstr ""
msgid "An error occurred while committing your changes."
@@ -2315,7 +2434,7 @@ msgstr ""
msgid "An error occurred while loading milestones"
msgstr ""
-msgid "An error occurred while loading terraform report"
+msgid "An error occurred while loading project creation UI"
msgstr ""
msgid "An error occurred while loading the data. Please try again."
@@ -2369,6 +2488,9 @@ msgstr ""
msgid "An error occurred while rendering preview broadcast message"
msgstr ""
+msgid "An error occurred while rendering the editor"
+msgstr ""
+
msgid "An error occurred while reordering issues."
msgstr ""
@@ -2465,7 +2587,7 @@ msgstr ""
msgid "Analyze your dependencies for known vulnerabilities."
msgstr ""
-msgid "Analyze your source code and git history for secrets"
+msgid "Analyze your source code and git history for secrets."
msgstr ""
msgid "Analyze your source code for known vulnerabilities."
@@ -2579,12 +2701,18 @@ msgstr ""
msgid "Apply suggestion"
msgstr ""
+msgid "Apply suggestions"
+msgstr ""
+
msgid "Apply template"
msgstr ""
msgid "Apply this approval rule to any branch or a specific protected branch."
msgstr ""
+msgid "Applying"
+msgstr ""
+
msgid "Applying a template will replace the existing issue description. Any changes you have made will be lost."
msgstr ""
@@ -2597,12 +2725,18 @@ msgstr ""
msgid "Applying multiple commands"
msgstr ""
-msgid "Applying suggestion"
+msgid "Applying suggestion..."
+msgstr ""
+
+msgid "Applying suggestions..."
msgstr ""
msgid "Approval rules"
msgstr ""
+msgid "Approval rules reset to project defaults"
+msgstr ""
+
msgid "ApprovalRuleRemove|%d member"
msgid_plural "ApprovalRuleRemove|%d members"
msgstr[0] ""
@@ -2656,6 +2790,9 @@ msgstr ""
msgid "Approved"
msgstr ""
+msgid "Approved MRs"
+msgstr ""
+
msgid "Approved by: "
msgstr ""
@@ -2869,6 +3006,9 @@ msgstr ""
msgid "Assign"
msgstr ""
+msgid "Assign Iteration"
+msgstr ""
+
msgid "Assign custom color like #FF0000"
msgstr ""
@@ -2887,6 +3027,9 @@ msgstr ""
msgid "Assign to"
msgstr ""
+msgid "Assign to commenting user"
+msgstr ""
+
msgid "Assign yourself to these issues"
msgstr ""
@@ -2913,6 +3056,9 @@ msgid_plural "%d Assignees"
msgstr[0] ""
msgstr[1] ""
+msgid "Assignee has no permissions"
+msgstr ""
+
msgid "Assignee lists not available with your current license"
msgstr ""
@@ -2922,6 +3068,9 @@ msgstr ""
msgid "Assignee(s)"
msgstr ""
+msgid "Assignees"
+msgstr ""
+
msgid "Assigns %{assignee_users_sentence}."
msgstr ""
@@ -2957,6 +3106,9 @@ msgstr ""
msgid "Audit Events is a way to keep track of important events that happened in GitLab."
msgstr ""
+msgid "Audit Log"
+msgstr ""
+
msgid "AuditEvents|(removed)"
msgstr ""
@@ -2993,6 +3145,9 @@ msgstr ""
msgid "AuditLogs|IP Address"
msgstr ""
+msgid "AuditLogs|Member Events"
+msgstr ""
+
msgid "AuditLogs|No matching %{type} found."
msgstr ""
@@ -3275,9 +3430,6 @@ msgstr ""
msgid "Badges|e.g. %{exampleUrl}"
msgstr ""
-msgid "Badge|New"
-msgstr ""
-
msgid "Balsamiq file could not be loaded."
msgstr ""
@@ -3326,6 +3478,12 @@ msgstr ""
msgid "Below you will find all the groups that are public."
msgstr ""
+msgid "Beta"
+msgstr ""
+
+msgid "Bi-weekly code coverage"
+msgstr ""
+
msgid "Billing"
msgstr ""
@@ -3386,9 +3544,15 @@ msgstr ""
msgid "Bitbucket Server Import"
msgstr ""
+msgid "Bitbucket Server import"
+msgstr ""
+
msgid "Bitbucket import"
msgstr ""
+msgid "Blame"
+msgstr ""
+
msgid "Blocked"
msgstr ""
@@ -3401,9 +3565,6 @@ msgstr ""
msgid "Blog"
msgstr ""
-msgid "Blue helpers indicate an action to be taken."
-msgstr ""
-
msgid "Board name"
msgstr ""
@@ -3602,6 +3763,9 @@ msgstr ""
msgid "Broadcast Message was successfully updated."
msgstr ""
+msgid "Broadcast Messages"
+msgstr ""
+
msgid "Browse Directory"
msgstr ""
@@ -3656,6 +3820,9 @@ msgstr ""
msgid "By %{user_name}"
msgstr ""
+msgid "By URL"
+msgstr ""
+
msgid "By default GitLab sends emails in HTML and plain text formats so mail clients can choose what format to use. Disable this option if you only want to send emails in plain text format."
msgstr ""
@@ -3761,6 +3928,9 @@ msgstr ""
msgid "Can override approvers and approvals required per merge request"
msgstr ""
+msgid "Can't apply as this line has changed or the suggestion already matches its content."
+msgstr ""
+
msgid "Can't create snippet: %{err}"
msgstr ""
@@ -3947,9 +4117,6 @@ msgstr ""
msgid "Changes are still tracked. Useful for cluster/index migrations."
msgstr ""
-msgid "Changes are unknown"
-msgstr ""
-
msgid "Changes suppressed. Click to show."
msgstr ""
@@ -4256,15 +4423,9 @@ msgstr ""
msgid "Choose what content you want to see on a group’s overview page"
msgstr ""
-msgid "Choose which groups you wish to synchronize to this secondary node"
-msgstr ""
-
msgid "Choose which repositories you want to connect and run CI/CD pipelines."
msgstr ""
-msgid "Choose which shards you wish to synchronize to this secondary node"
-msgstr ""
-
msgid "Choose your framework"
msgstr ""
@@ -4562,9 +4723,15 @@ msgstr ""
msgid "Cluster does not exist"
msgstr ""
+msgid "Cluster is required for Stages::ClusterEndpointInserter"
+msgstr ""
+
msgid "Cluster level"
msgstr ""
+msgid "Cluster type must be specificed for Stages::ClusterEndpointInserter"
+msgstr ""
+
msgid "ClusterIntegration| %{custom_domain_start}More information%{custom_domain_end}."
msgstr ""
@@ -4676,7 +4843,7 @@ msgstr ""
msgid "ClusterIntegration|Cert-Manager"
msgstr ""
-msgid "ClusterIntegration|Cert-Manager is a native Kubernetes certificate management controller that helps with issuing certificates. Installing Cert-Manager on your cluster will issue a certificate by %{letsEncrypt} and ensure that certificates are valid and up-to-date."
+msgid "ClusterIntegration|Cert-Manager is a native Kubernetes certificate management controller that helps with issuing certificates. Installing Cert-Manager on your cluster will issue a certificate by %{linkStart}Let's Encrypt%{linkEnd} and ensure that certificates are valid and up-to-date."
msgstr ""
msgid "ClusterIntegration|Certificate Authority bundle (PEM format)"
@@ -4703,9 +4870,6 @@ msgstr ""
msgid "ClusterIntegration|Clear the local cache of namespace and service accounts. This is necessary if your integration has become out of sync. The cache is repopulated during the next CI job that requires namespace and service accounts."
msgstr ""
-msgid "ClusterIntegration|Cloud Run"
-msgstr ""
-
msgid "ClusterIntegration|Cluster being created"
msgstr ""
@@ -4790,7 +4954,7 @@ msgstr ""
msgid "ClusterIntegration|Crossplane"
msgstr ""
-msgid "ClusterIntegration|Crossplane enables declarative provisioning of managed services from your cloud of choice using %{codeStart}kubectl%{codeEnd} or %{gitlabIntegrationLink}. Crossplane runs inside your Kubernetes cluster and supports secure connectivity and secrets management between app containers and the cloud services they depend on."
+msgid "ClusterIntegration|Crossplane enables declarative provisioning of managed services from your cloud of choice using %{codeStart}kubectl%{codeEnd} or %{linkStart}GitLab Integration%{linkEnd}. Crossplane runs inside your Kubernetes cluster and supports secure connectivity and secrets management between app containers and the cloud services they depend on."
msgstr ""
msgid "ClusterIntegration|Deletes all GitLab resources attached to this cluster during removal"
@@ -4874,9 +5038,6 @@ msgstr ""
msgid "ClusterIntegration|GitLab-managed cluster"
msgstr ""
-msgid "ClusterIntegration|Gitlab Integration"
-msgstr ""
-
msgid "ClusterIntegration|Global default"
msgstr ""
@@ -4922,7 +5083,7 @@ msgstr ""
msgid "ClusterIntegration|Ingress gives you a way to route requests to services based on the request host or path, centralizing a number of services into a single entrypoint."
msgstr ""
-msgid "ClusterIntegration|Installing Ingress may incur additional costs. Learn more about %{pricingLink}."
+msgid "ClusterIntegration|Installing Ingress may incur additional costs. Learn more about %{linkStart}pricing%{linkEnd}."
msgstr ""
msgid "ClusterIntegration|Instance cluster"
@@ -4937,7 +5098,7 @@ msgstr ""
msgid "ClusterIntegration|Issuer Email"
msgstr ""
-msgid "ClusterIntegration|Issuers represent a certificate authority. You must provide an email address for your Issuer. "
+msgid "ClusterIntegration|Issuers represent a certificate authority. You must provide an email address for your Issuer."
msgstr ""
msgid "ClusterIntegration|Jupyter Hostname"
@@ -5009,9 +5170,6 @@ msgstr ""
msgid "ClusterIntegration|Learn more about instance Kubernetes clusters"
msgstr ""
-msgid "ClusterIntegration|Let's Encrypt"
-msgstr ""
-
msgid "ClusterIntegration|Loading IAM Roles"
msgstr ""
@@ -5123,7 +5281,7 @@ msgstr ""
msgid "ClusterIntegration|Prometheus"
msgstr ""
-msgid "ClusterIntegration|Prometheus is an open-source monitoring system with %{gitlabIntegrationLink} to monitor deployed applications."
+msgid "ClusterIntegration|Prometheus is an open-source monitoring system with %{linkStart}GitLab Integration%{linkEnd} to monitor deployed applications."
msgstr ""
msgid "ClusterIntegration|Provider details"
@@ -5279,10 +5437,10 @@ msgstr ""
msgid "ClusterIntegration|Select zone to choose machine type"
msgstr ""
-msgid "ClusterIntegration|Send Cilium Logs"
+msgid "ClusterIntegration|Send Container Network Policies Logs"
msgstr ""
-msgid "ClusterIntegration|Send ModSecurity Logs"
+msgid "ClusterIntegration|Send Web Application Firewall Logs"
msgstr ""
msgid "ClusterIntegration|Service Token"
@@ -5450,15 +5608,12 @@ msgstr ""
msgid "ClusterIntegration|documentation"
msgstr ""
-msgid "ClusterIntegration|installed via %{installed_via}"
+msgid "ClusterIntegration|installed via %{linkStart}Cloud Run%{linkEnd}"
msgstr ""
msgid "ClusterIntegration|meets the requirements"
msgstr ""
-msgid "ClusterIntegration|pricing"
-msgstr ""
-
msgid "ClusterIntegration|sign up"
msgstr ""
@@ -5495,6 +5650,15 @@ msgstr ""
msgid "Code"
msgstr ""
+msgid "Code Coverage: %{coveragePercentage}%{percentSymbol}"
+msgstr ""
+
+msgid "Code Coverage| Empty code coverage data"
+msgstr ""
+
+msgid "Code Coverage|Couldn't fetch the code coverage data"
+msgstr ""
+
msgid "Code Owners"
msgstr ""
@@ -5549,7 +5713,7 @@ msgstr ""
msgid "Collapse approvers"
msgstr ""
-msgid "Collapse child epics"
+msgid "Collapse milestones"
msgstr ""
msgid "Collapse replies"
@@ -5567,6 +5731,9 @@ msgstr ""
msgid "Coming soon"
msgstr ""
+msgid "Comma-separated, e.g. '1.1.1.1, 2.2.2.0/24'"
+msgstr ""
+
msgid "Command"
msgstr ""
@@ -5737,6 +5904,12 @@ msgstr ""
msgid "Compliance framework (optional)"
msgstr ""
+msgid "Compliance frameworks"
+msgstr ""
+
+msgid "ComplianceDashboard|created by:"
+msgstr ""
+
msgid "ComplianceFramework|GDPR"
msgstr ""
@@ -5875,6 +6048,9 @@ msgstr ""
msgid "Connection timed out"
msgstr ""
+msgid "Connection timeout"
+msgstr ""
+
msgid "Contact sales to upgrade"
msgstr ""
@@ -5930,9 +6106,6 @@ msgstr ""
msgid "ContainerRegistry|CLI Commands"
msgstr ""
-msgid "ContainerRegistry|Compressed Size"
-msgstr ""
-
msgid "ContainerRegistry|Container Registry"
msgstr ""
@@ -5948,6 +6121,9 @@ msgstr ""
msgid "ContainerRegistry|Copy push command"
msgstr ""
+msgid "ContainerRegistry|Delete selected"
+msgstr ""
+
msgid "ContainerRegistry|Docker connection error"
msgstr ""
@@ -5981,16 +6157,16 @@ msgstr ""
msgid "ContainerRegistry|If you are not already logged in, you need to authenticate to the Container Registry by using your GitLab username and password. If you have %{twofaDocLinkStart}Two-Factor Authentication%{twofaDocLinkEnd} enabled, use a %{personalAccessTokensDocLinkStart}Personal Access Token%{personalAccessTokensDocLinkEnd} instead of a password."
msgstr ""
-msgid "ContainerRegistry|Image ID"
+msgid "ContainerRegistry|Image ID: %{imageId}"
msgstr ""
msgid "ContainerRegistry|Image Repositories"
msgstr ""
-msgid "ContainerRegistry|Keep and protect the images that matter most."
+msgid "ContainerRegistry|Image tags"
msgstr ""
-msgid "ContainerRegistry|Last Updated"
+msgid "ContainerRegistry|Keep and protect the images that matter most."
msgstr ""
msgid "ContainerRegistry|Login"
@@ -6005,6 +6181,9 @@ msgstr ""
msgid "ContainerRegistry|Please contact your administrator."
msgstr ""
+msgid "ContainerRegistry|Published %{timeInfo}"
+msgstr ""
+
msgid "ContainerRegistry|Push an image"
msgstr ""
@@ -6020,9 +6199,6 @@ msgstr ""
msgid "ContainerRegistry|Remove repository"
msgstr ""
-msgid "ContainerRegistry|Remove selected tags"
-msgstr ""
-
msgid "ContainerRegistry|Remove tag"
msgid_plural "ContainerRegistry|Remove tags"
msgstr[0] ""
@@ -6052,9 +6228,6 @@ msgstr ""
msgid "ContainerRegistry|Sorry, your filter produced no results."
msgstr ""
-msgid "ContainerRegistry|Tag"
-msgstr ""
-
msgid "ContainerRegistry|Tag expiration policy"
msgstr ""
@@ -6199,21 +6372,6 @@ msgstr ""
msgid "Control the display of third party offers."
msgstr ""
-msgid "Control the maximum concurrency of LFS/attachment backfill for this secondary node"
-msgstr ""
-
-msgid "Control the maximum concurrency of container repository operations for this Geo node"
-msgstr ""
-
-msgid "Control the maximum concurrency of repository backfill for this secondary node"
-msgstr ""
-
-msgid "Control the maximum concurrency of verification operations for this Geo node"
-msgstr ""
-
-msgid "Control the minimum interval in days that a repository should be reverified for this primary node"
-msgstr ""
-
msgid "Cookie domain"
msgstr ""
@@ -6355,6 +6513,9 @@ msgstr ""
msgid "Could not find design."
msgstr ""
+msgid "Could not find iteration"
+msgstr ""
+
msgid "Could not remove the trigger."
msgstr ""
@@ -6391,6 +6552,12 @@ msgstr ""
msgid "Coverage"
msgstr ""
+msgid "Coverage Fuzzing"
+msgstr ""
+
+msgid "Crash State"
+msgstr ""
+
msgid "Create"
msgstr ""
@@ -6439,6 +6606,9 @@ msgstr ""
msgid "Create a personal access token on your account to pull or push via %{protocol}."
msgstr ""
+msgid "Create a project pre-populated with the necessary files to get you started quickly."
+msgstr ""
+
msgid "Create an account using:"
msgstr ""
@@ -6607,6 +6777,9 @@ msgstr ""
msgid "Creates branch '%{branch_name}' and a merge request to resolve this issue."
msgstr ""
+msgid "Creating"
+msgstr ""
+
msgid "Creating epic"
msgstr ""
@@ -6658,7 +6831,10 @@ msgstr ""
msgid "Current vulnerabilities count"
msgstr ""
-msgid "CurrentUser|Buy CI minutes"
+msgid "CurrentUser|Buy Pipeline minutes"
+msgstr ""
+
+msgid "CurrentUser|One of your groups is running out"
msgstr ""
msgid "CurrentUser|Profile"
@@ -6673,6 +6849,9 @@ msgstr ""
msgid "CurrentUser|Upgrade"
msgstr ""
+msgid "Custom Attributes"
+msgstr ""
+
msgid "Custom CI configuration path"
msgstr ""
@@ -6936,12 +7115,18 @@ msgstr ""
msgid "DAG"
msgstr ""
+msgid "DAG visualization requires at least 3 dependent jobs."
+msgstr ""
+
msgid "DNS"
msgstr ""
msgid "Dashboard"
msgstr ""
+msgid "Dashboard uid not found"
+msgstr ""
+
msgid "DashboardProjects|All"
msgstr ""
@@ -6966,6 +7151,9 @@ msgstr ""
msgid "Data is still calculating..."
msgstr ""
+msgid "Datasource name not found"
+msgstr ""
+
msgid "Date"
msgstr ""
@@ -7146,6 +7334,9 @@ msgstr ""
msgid "Delete this attachment"
msgstr ""
+msgid "Delete user list"
+msgstr ""
+
msgid "Delete variable"
msgstr ""
@@ -7462,6 +7653,9 @@ msgstr ""
msgid "Deploying to"
msgstr ""
+msgid "Deploying to AWS is easy with GitLab"
+msgstr ""
+
msgid "Deployment Frequency"
msgstr ""
@@ -7824,6 +8018,9 @@ msgstr ""
msgid "Dismissed on pipeline %{pipelineLink} at %{projectLink}"
msgstr ""
+msgid "Display alerts from all your monitoring tools directly within GitLab."
+msgstr ""
+
msgid "Display name"
msgstr ""
@@ -7881,9 +8078,6 @@ msgstr ""
msgid "Don't show again"
msgstr ""
-msgid "Don't worry, you can access this tour by clicking on the help icon in the top right corner and choose <strong>Learn GitLab</strong>."
-msgstr ""
-
msgid "Done"
msgstr ""
@@ -8010,6 +8204,9 @@ msgstr ""
msgid "Edit Release"
msgstr ""
+msgid "Edit Slack integration"
+msgstr ""
+
msgid "Edit Snippet"
msgstr ""
@@ -8049,6 +8246,9 @@ msgstr ""
msgid "Edit issues"
msgstr ""
+msgid "Edit iteration"
+msgstr ""
+
msgid "Edit public deploy key"
msgstr ""
@@ -8097,6 +8297,9 @@ msgstr ""
msgid "Email"
msgstr ""
+msgid "Email Notification"
+msgstr ""
+
msgid "Email address"
msgstr ""
@@ -8109,6 +8312,9 @@ msgstr ""
msgid "Email not verified. Please verify your email in Salesforce."
msgstr ""
+msgid "Email notification for unknown sign-ins"
+msgstr ""
+
msgid "Email patch"
msgstr ""
@@ -8340,6 +8546,9 @@ msgstr ""
msgid "Enforce DNS rebinding attack protection"
msgstr ""
+msgid "Enforce personal access token expiration"
+msgstr ""
+
msgid "Ensure connectivity is available from the GitLab server to the Prometheus server"
msgstr ""
@@ -8409,6 +8618,9 @@ msgstr ""
msgid "Enter the name of your application, and we'll return a unique %{type}."
msgstr ""
+msgid "Enter weights for storages for new repositories."
+msgstr ""
+
msgid "Enter your password to approve"
msgstr ""
@@ -8418,15 +8630,27 @@ msgstr ""
msgid "Environment does not have deployments"
msgstr ""
+msgid "Environment is required for Stages::MetricEndpointInserter"
+msgstr ""
+
+msgid "Environment is required for Stages::VariableEndpointInserter"
+msgstr ""
+
msgid "Environment scope"
msgstr ""
+msgid "Environment variables are applied to all project environments in this instance via the Runner. You can use environment variables for passwords, secret keys, etc. Make variables available to the running application by prepending the variable key with <code>K8S_SECRET_</code>. You can set variables to be:"
+msgstr ""
+
msgid "Environment variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. Additionally, they can be masked so they are hidden in job logs, though they must match certain regexp requirements to do so. You can use environment variables for passwords, secret keys, or whatever you want."
msgstr ""
msgid "Environment variables are configured by your administrator to be %{link_start}protected%{link_end} by default"
msgstr ""
+msgid "Environment variables on this GitLab instance are configured to be %{link_start}protected%{link_end} by default"
+msgstr ""
+
msgid "Environment:"
msgstr ""
@@ -8958,6 +9182,9 @@ msgstr ""
msgid "EventFilterBy|Filter by comments"
msgstr ""
+msgid "EventFilterBy|Filter by designs"
+msgstr ""
+
msgid "EventFilterBy|Filter by epic events"
msgstr ""
@@ -9090,15 +9317,15 @@ msgstr ""
msgid "Expand approvers"
msgstr ""
-msgid "Expand child epics"
-msgstr ""
-
msgid "Expand down"
msgstr ""
msgid "Expand dropdown"
msgstr ""
+msgid "Expand milestones"
+msgstr ""
+
msgid "Expand sidebar"
msgstr ""
@@ -9114,6 +9341,9 @@ msgstr ""
msgid "Expiration date"
msgstr ""
+msgid "Expiration not enforced"
+msgstr ""
+
msgid "Expiration policy for the Container Registry is a perfect solution for keeping the Registry space down while still enjoying the full power of GitLab CI/CD."
msgstr ""
@@ -9234,6 +9464,9 @@ msgstr ""
msgid "Failed Jobs"
msgstr ""
+msgid "Failed on"
+msgstr ""
+
msgid "Failed to add a Zoom meeting"
msgstr ""
@@ -9294,13 +9527,19 @@ msgstr ""
msgid "Failed to install."
msgstr ""
+msgid "Failed to load assignees. Please try again."
+msgstr ""
+
+msgid "Failed to load authors. Please try again."
+msgstr ""
+
msgid "Failed to load emoji list."
msgstr ""
msgid "Failed to load error details from Sentry."
msgstr ""
-msgid "Failed to load errors from Sentry. Error message: %{errorMessage}"
+msgid "Failed to load errors from Sentry."
msgstr ""
msgid "Failed to load group activity metrics. Please try again."
@@ -9381,6 +9620,9 @@ msgstr ""
msgid "Failed to set due date because the date format is invalid."
msgstr ""
+msgid "Failed to set iteration on this issue. Please try again."
+msgstr ""
+
msgid "Failed to signing using smartcard authentication"
msgstr ""
@@ -9603,13 +9845,10 @@ msgstr ""
msgid "FeatureFlags|Target environments"
msgstr ""
-msgid "FeatureFlags|There are no active feature flags"
-msgstr ""
-
-msgid "FeatureFlags|There are no inactive feature flags"
+msgid "FeatureFlags|There was an error fetching the feature flags."
msgstr ""
-msgid "FeatureFlags|There was an error fetching the feature flags."
+msgid "FeatureFlags|There was an error retrieving user lists"
msgstr ""
msgid "FeatureFlags|Try again in a few moments or contact your support team."
@@ -9621,9 +9860,18 @@ msgstr ""
msgid "FeatureFlag|Delete strategy"
msgstr ""
+msgid "FeatureFlag|List"
+msgstr ""
+
msgid "FeatureFlag|Percentage"
msgstr ""
+msgid "FeatureFlag|Select a user list"
+msgstr ""
+
+msgid "FeatureFlag|There are no configured user lists"
+msgstr ""
+
msgid "FeatureFlag|Type"
msgstr ""
@@ -9780,6 +10028,12 @@ msgstr ""
msgid "Filter..."
msgstr ""
+msgid "Find File"
+msgstr ""
+
+msgid "Find bugs in your code with coverage-guided fuzzing"
+msgstr ""
+
msgid "Find by path"
msgstr ""
@@ -10044,6 +10298,9 @@ msgstr ""
msgid "Geo allows you to replicate your GitLab instance to other geographical locations."
msgstr ""
+msgid "Geo nodes are paused using a command run on the node"
+msgstr ""
+
msgid "GeoNodeStatusEvent|%{timeAgoStr} (%{pendingEvents} events)"
msgstr ""
@@ -10128,6 +10385,9 @@ msgstr ""
msgid "GeoNodes|Not checksummed"
msgstr ""
+msgid "GeoNodes|Package files"
+msgstr ""
+
msgid "GeoNodes|Pausing replication stops the sync process. Are you sure?"
msgstr ""
@@ -10146,6 +10406,9 @@ msgstr ""
msgid "GeoNodes|Replication slots"
msgstr ""
+msgid "GeoNodes|Replication status"
+msgstr ""
+
msgid "GeoNodes|Repositories"
msgstr ""
@@ -10356,6 +10619,9 @@ msgstr ""
msgid "Geo|Synchronization failed - %{error}"
msgstr ""
+msgid "Geo|Synchronization of %{itemTitle} is disabled."
+msgstr ""
+
msgid "Geo|The database is currently %{db_lag} behind the primary node."
msgstr ""
@@ -10668,6 +10934,9 @@ msgstr ""
msgid "Go to %{link_to_google_takeout}."
msgstr ""
+msgid "Go to <strong>Issues</strong> > <strong>Boards</strong> to access your personalized learning issue board."
+msgstr ""
+
msgid "Go to Pipelines"
msgstr ""
@@ -10767,9 +11036,6 @@ msgstr ""
msgid "Go to your snippets"
msgstr ""
-msgid "Golden Tanuki"
-msgstr ""
-
msgid "Google Cloud Platform"
msgstr ""
@@ -10791,6 +11057,9 @@ msgstr ""
msgid "Grafana URL"
msgstr ""
+msgid "Grafana response contains invalid json"
+msgstr ""
+
msgid "GrafanaIntegration|API Token"
msgstr ""
@@ -10839,9 +11108,6 @@ msgstr ""
msgid "Group %{group_name} was successfully created."
msgstr ""
-msgid "Group '%{group_name}' is being imported."
-msgstr ""
-
msgid "Group Audit Events"
msgstr ""
@@ -10878,9 +11144,6 @@ msgstr ""
msgid "Group by:"
msgstr ""
-msgid "Group could not be imported: %{errors}"
-msgstr ""
-
msgid "Group description"
msgstr ""
@@ -10908,9 +11171,15 @@ msgstr ""
msgid "Group has not been marked for deletion"
msgstr ""
+msgid "Group import could not be scheduled"
+msgstr ""
+
msgid "Group info:"
msgstr ""
+msgid "Group is required when cluster_type is :group"
+msgstr ""
+
msgid "Group maintainers can register group runners in the %{link}"
msgstr ""
@@ -10974,6 +11243,24 @@ msgstr ""
msgid "GroupActivyMetrics|Recent activity (last 90 days)"
msgstr ""
+msgid "GroupImport|Failed to import group."
+msgstr ""
+
+msgid "GroupImport|Group '%{group_name}' is being imported."
+msgstr ""
+
+msgid "GroupImport|Group could not be imported: %{errors}"
+msgstr ""
+
+msgid "GroupImport|Please wait while we import the group for you. Refresh at will."
+msgstr ""
+
+msgid "GroupImport|The group was successfully imported."
+msgstr ""
+
+msgid "GroupImport|Unable to process group import file"
+msgstr ""
+
msgid "GroupRoadmap|%{dateWord} – No end date"
msgstr ""
@@ -11241,6 +11528,9 @@ msgstr ""
msgid "Groups (%{groups})"
msgstr ""
+msgid "Groups and projects"
+msgstr ""
+
msgid "Groups can also be nested by creating %{subgroup_docs_link_start}subgroups%{subgroup_docs_link_end}."
msgstr ""
@@ -11286,6 +11576,33 @@ msgstr ""
msgid "GroupsEmptyState|You can manage your group member’s permissions and access to each project in the group."
msgstr ""
+msgid "GroupsNew|Contact an administrator to enable options for importing your group."
+msgstr ""
+
+msgid "GroupsNew|Create"
+msgstr ""
+
+msgid "GroupsNew|Create group"
+msgstr ""
+
+msgid "GroupsNew|GitLab group export"
+msgstr ""
+
+msgid "GroupsNew|Import"
+msgstr ""
+
+msgid "GroupsNew|Import group"
+msgstr ""
+
+msgid "GroupsNew|My Awesome Group"
+msgstr ""
+
+msgid "GroupsNew|No import options available"
+msgstr ""
+
+msgid "GroupsNew|To copy a GitLab group between installations, navigate to the group settings page for the original installation, generate an export file, and upload it here."
+msgstr ""
+
msgid "GroupsTree|Are you sure you want to leave the \"%{fullName}\" group?"
msgstr ""
@@ -11388,6 +11705,9 @@ msgstr ""
msgid "Helps reduce request volume for protected paths"
msgstr ""
+msgid "Here are all your projects in your group, including the one you just created. To start, let’s take a look at your personalized learning project which will help you learn about GitLab at your own pace."
+msgstr ""
+
msgid "Here you will find recent merge request activity"
msgstr ""
@@ -11402,6 +11722,9 @@ msgid_plural "Hide charts"
msgstr[0] ""
msgstr[1] ""
+msgid "Hide details"
+msgstr ""
+
msgid "Hide file browser"
msgstr ""
@@ -11411,6 +11734,9 @@ msgstr ""
msgid "Hide host keys manual input"
msgstr ""
+msgid "Hide list"
+msgstr ""
+
msgid "Hide marketing-related entries from help"
msgstr ""
@@ -11431,9 +11757,6 @@ msgstr[1] ""
msgid "Hide values"
msgstr ""
-msgid "Hiding all labels"
-msgstr ""
-
msgid "High or unknown vulnerabilities present"
msgstr ""
@@ -11518,6 +11841,9 @@ msgstr ""
msgid "ID:"
msgstr ""
+msgid "IDE"
+msgstr ""
+
msgid "IDE|Allow live previews of JavaScript projects in the Web IDE using CodeSandbox Live Preview."
msgstr ""
@@ -11608,10 +11934,10 @@ msgstr ""
msgid "If enabled"
msgstr ""
-msgid "If enabled, access to projects will be validated on an external service using their classification label."
+msgid "If enabled, GitLab will handle Object Storage replication using Geo. %{linkStart}More information%{linkEnd}"
msgstr ""
-msgid "If enabled, and if object storage is enabled, GitLab will handle Object Storage replication using Geo"
+msgid "If enabled, access to projects will be validated on an external service using their classification label."
msgstr ""
msgid "If there is no previous license or if the previous license has expired, some GitLab functionality will be blocked until a new, valid license is uploaded."
@@ -11973,6 +12299,9 @@ msgid_plural "Instances"
msgstr[0] ""
msgstr[1] ""
+msgid "Instance Configuration"
+msgstr ""
+
msgid "Instance Statistics visibility"
msgstr ""
@@ -11994,9 +12323,6 @@ msgstr ""
msgid "Integrations"
msgstr ""
-msgid "Integrations allow you to integrate GitLab with other applications"
-msgstr ""
-
msgid "Integrations|All details"
msgstr ""
@@ -12108,6 +12434,9 @@ msgstr ""
msgid "Invalid query"
msgstr ""
+msgid "Invalid repository bundle for snippet with id %{snippet_id}"
+msgstr ""
+
msgid "Invalid repository path"
msgstr ""
@@ -12126,6 +12455,9 @@ msgstr ""
msgid "Invalid two-factor code."
msgstr ""
+msgid "Invalid yaml"
+msgstr ""
+
msgid "Invitation"
msgstr ""
@@ -12345,15 +12677,24 @@ msgstr ""
msgid "It seems like the Dependency Scanning job ran successfully, but no dependencies have been detected in your project."
msgstr ""
+msgid "It seems that there is currently no available data for code coverage"
+msgstr ""
+
msgid "It's you"
msgstr ""
+msgid "Iteration"
+msgstr ""
+
msgid "Iteration changed to"
msgstr ""
msgid "Iteration removed"
msgstr ""
+msgid "Iteration updated"
+msgstr ""
+
msgid "Iterations"
msgstr ""
@@ -12384,12 +12725,18 @@ msgstr ""
msgid "January"
msgstr ""
+msgid "Jira Issues"
+msgstr ""
+
msgid "Jira import is already running."
msgstr ""
msgid "Jira integration not configured."
msgstr ""
+msgid "Jira project key is not configured"
+msgstr ""
+
msgid "Jira project: %{importProject}"
msgstr ""
@@ -12570,6 +12917,9 @@ msgstr ""
msgid "Keep divergent refs"
msgstr ""
+msgid "Kerberos access denied"
+msgstr ""
+
msgid "Key"
msgstr ""
@@ -12579,10 +12929,13 @@ msgstr ""
msgid "Key: %{key}"
msgstr ""
-msgid "Keyboard Shortcuts"
+msgid "Keyboard shortcuts"
msgstr ""
-msgid "Keyboard shortcuts"
+msgid "Keys"
+msgstr ""
+
+msgid "Ki"
msgstr ""
msgid "Kubernetes"
@@ -12624,6 +12977,9 @@ msgstr ""
msgid "LDAP"
msgstr ""
+msgid "LDAP Synchronization"
+msgstr ""
+
msgid "LDAP settings"
msgstr ""
@@ -12848,12 +13204,18 @@ msgstr ""
msgid "Learn more about custom project templates"
msgstr ""
+msgid "Learn more about deploying to AWS"
+msgstr ""
+
msgid "Learn more about deploying to a cluster"
msgstr ""
msgid "Learn more about group-level project templates"
msgstr ""
+msgid "Learn more about job dependencies"
+msgstr ""
+
msgid "Learn more about signing commits"
msgstr ""
@@ -12905,9 +13267,21 @@ msgstr ""
msgid "License History"
msgstr ""
+msgid "License ID:"
+msgstr ""
+
+msgid "License URL"
+msgstr ""
+
msgid "License-Check"
msgstr ""
+msgid "LicenseCompliance|%{docLinkStart}License Approvals%{docLinkEnd} are active"
+msgstr ""
+
+msgid "LicenseCompliance|%{docLinkStart}License Approvals%{docLinkEnd} are inactive"
+msgstr ""
+
msgid "LicenseCompliance|Add a license"
msgstr ""
@@ -12929,9 +13303,15 @@ msgstr ""
msgid "LicenseCompliance|Deny"
msgstr ""
+msgid "LicenseCompliance|Learn more about %{linkStart}License Approvals%{linkEnd}"
+msgstr ""
+
msgid "LicenseCompliance|License"
msgstr ""
+msgid "LicenseCompliance|License Approvals"
+msgstr ""
+
msgid "LicenseCompliance|License Compliance detected %d license and policy violation for the source branch only; approval required"
msgid_plural "LicenseCompliance|License Compliance detected %d licenses and policy violations for the source branch only; approval required"
msgstr[0] ""
@@ -13152,6 +13532,9 @@ msgstr ""
msgid "List your Bitbucket Server repositories"
msgstr ""
+msgid "Lists"
+msgstr ""
+
msgid "Live preview"
msgstr ""
@@ -13335,6 +13718,9 @@ msgstr ""
msgid "Manage project labels"
msgstr ""
+msgid "Manage storage usage"
+msgstr ""
+
msgid "Manage two-factor authentication"
msgstr ""
@@ -13350,6 +13736,9 @@ msgstr ""
msgid "Manifest file import"
msgstr ""
+msgid "Manifest import"
+msgstr ""
+
msgid "Manual job"
msgstr ""
@@ -13497,6 +13886,12 @@ msgstr ""
msgid "Maximum field length"
msgstr ""
+msgid "Maximum file size is 2MB. Please select a smaller file."
+msgstr ""
+
+msgid "Maximum import size (MB)"
+msgstr ""
+
msgid "Maximum job timeout"
msgstr ""
@@ -13536,6 +13931,9 @@ msgstr ""
msgid "Maximum size of Elasticsearch bulk indexing requests."
msgstr ""
+msgid "Maximum size of import files."
+msgstr ""
+
msgid "Maximum size of individual attachments in comments."
msgstr ""
@@ -13587,6 +13985,9 @@ msgstr ""
msgid "Merge (when the pipeline succeeds)"
msgstr ""
+msgid "Merge Conflicts"
+msgstr ""
+
msgid "Merge Request"
msgstr ""
@@ -13713,6 +14114,9 @@ msgstr ""
msgid "MergeRequests|Squash task canceled: another squash is already in progress."
msgstr ""
+msgid "MergeRequests|This project does not allow squashing commits when merge requests are accepted."
+msgstr ""
+
msgid "MergeRequests|Thread stays resolved"
msgstr ""
@@ -13866,6 +14270,12 @@ msgstr ""
msgid "MetricsSettings|Add a button to the metrics dashboard linking directly to your existing external dashboard."
msgstr ""
+msgid "MetricsSettings|Choose whether to display dashboard metrics in UTC or the user's local timezone."
+msgstr ""
+
+msgid "MetricsSettings|Dashboard timezone"
+msgstr ""
+
msgid "MetricsSettings|External dashboard URL"
msgstr ""
@@ -13875,6 +14285,12 @@ msgstr ""
msgid "MetricsSettings|Metrics Dashboard"
msgstr ""
+msgid "MetricsSettings|UTC (Coordinated Universal Time)"
+msgstr ""
+
+msgid "MetricsSettings|User's local timezone"
+msgstr ""
+
msgid "Metrics|Add metric"
msgstr ""
@@ -13890,6 +14306,9 @@ msgstr ""
msgid "Metrics|Create metric"
msgstr ""
+msgid "Metrics|Current"
+msgstr ""
+
msgid "Metrics|Delete metric"
msgstr ""
@@ -13940,6 +14359,9 @@ msgstr ""
msgid "Metrics|Max"
msgstr ""
+msgid "Metrics|Min"
+msgstr ""
+
msgid "Metrics|Must be a valid PromQL query."
msgstr ""
@@ -13955,6 +14377,9 @@ msgstr ""
msgid "Metrics|Refresh dashboard"
msgstr ""
+msgid "Metrics|Select a value"
+msgstr ""
+
msgid "Metrics|Star dashboard"
msgstr ""
@@ -13973,12 +14398,18 @@ msgstr ""
msgid "Metrics|There was an error getting annotations information."
msgstr ""
+msgid "Metrics|There was an error getting dashboard validation warnings information."
+msgstr ""
+
msgid "Metrics|There was an error getting deployment information."
msgstr ""
msgid "Metrics|There was an error getting environments information."
msgstr ""
+msgid "Metrics|There was an error getting options for variable \"%{name}\"."
+msgstr ""
+
msgid "Metrics|There was an error trying to validate your query"
msgstr ""
@@ -14021,6 +14452,9 @@ msgstr ""
msgid "Metrics|You're about to permanently delete this metric. This cannot be undone."
msgstr ""
+msgid "Metrics|Your dashboard schema is invalid. Edit the dashboard to correct the YAML schema."
+msgstr ""
+
msgid "Metrics|e.g. HTTP requests"
msgstr ""
@@ -14036,12 +14470,18 @@ msgstr ""
msgid "Metrics|e.g. req/sec"
msgstr ""
+msgid "Mi"
+msgstr ""
+
msgid "Microsoft Azure"
msgstr ""
msgid "Middleman project with Static Site Editor support"
msgstr ""
+msgid "Migrate your data from an external source like GitHub, Bitbucket, or another instance of GitLab."
+msgstr ""
+
msgid "Migrated %{success_count}/%{total_count} files."
msgstr ""
@@ -14176,6 +14616,9 @@ msgstr ""
msgid "Minimum capacity to be available before we schedule more mirrors preemptively."
msgstr ""
+msgid "Minimum interval in days"
+msgstr ""
+
msgid "Minimum length is %{minimum_password_length} characters"
msgstr ""
@@ -14362,6 +14805,12 @@ msgstr ""
msgid "Multiple uploaders found: %{uploader_types}"
msgstr ""
+msgid "Must match with the %{codeStart}external_url%{codeEnd} in %{codeStart}/etc/gitlab/gitlab.rb%{codeEnd}."
+msgstr ""
+
+msgid "Must match with the %{codeStart}geo_node_name%{codeEnd} in %{codeStart}/etc/gitlab/gitlab.rb%{codeEnd}. %{linkStart}More information%{linkEnd}"
+msgstr ""
+
msgid "My Awesome Group"
msgstr ""
@@ -14476,10 +14925,16 @@ msgstr ""
msgid "New Application"
msgstr ""
+msgid "New Branch"
+msgstr ""
+
+msgid "New Deploy Key"
+msgstr ""
+
msgid "New Environment"
msgstr ""
-msgid "New Geo Node"
+msgid "New File"
msgstr ""
msgid "New Group"
@@ -14496,15 +14951,15 @@ msgid_plural "New Issues"
msgstr[0] ""
msgstr[1] ""
-msgid "New Iteration"
-msgstr ""
-
msgid "New Jira import"
msgstr ""
msgid "New Label"
msgstr ""
+msgid "New Merge Request"
+msgstr ""
+
msgid "New Milestone"
msgstr ""
@@ -14523,6 +14978,9 @@ msgstr ""
msgid "New Snippet"
msgstr ""
+msgid "New User"
+msgstr ""
+
msgid "New branch"
msgstr ""
@@ -14565,6 +15023,9 @@ msgstr ""
msgid "New issue title"
msgstr ""
+msgid "New iteration"
+msgstr ""
+
msgid "New iteration created"
msgstr ""
@@ -14742,6 +15203,12 @@ msgstr ""
msgid "No grouping"
msgstr ""
+msgid "No iteration"
+msgstr ""
+
+msgid "No iterations to show"
+msgstr ""
+
msgid "No job log"
msgstr ""
@@ -14784,6 +15251,9 @@ msgstr ""
msgid "No other labels with such name or description"
msgstr ""
+msgid "No panels matching properties %{opts}"
+msgstr ""
+
msgid "No parent group"
msgstr ""
@@ -14817,6 +15287,9 @@ msgstr ""
msgid "No schedules"
msgstr ""
+msgid "No stack trace for this error"
+msgstr ""
+
msgid "No starrers matched your search"
msgstr ""
@@ -14829,7 +15302,7 @@ msgstr ""
msgid "No test coverage"
msgstr ""
-msgid "No thanks, don't show this again"
+msgid "No thanks"
msgstr ""
msgid "No vulnerabilities present"
@@ -14844,9 +15317,6 @@ msgstr ""
msgid "No, directly import the existing email addresses and usernames."
msgstr ""
-msgid "No, not interested right now"
-msgstr ""
-
msgid "No. of commits"
msgstr ""
@@ -14892,9 +15362,6 @@ msgstr ""
msgid "Not found."
msgstr ""
-msgid "Not helpful"
-msgstr ""
-
msgid "Not now"
msgstr ""
@@ -15051,6 +15518,9 @@ msgstr ""
msgid "Notifications on"
msgstr ""
+msgid "Notify users by email when sign-in location is not recognized"
+msgstr ""
+
msgid "Nov"
msgstr ""
@@ -15117,9 +15587,6 @@ msgstr ""
msgid "Oh no!"
msgstr ""
-msgid "Ok let's go"
-msgstr ""
-
msgid "Oldest first"
msgstr ""
@@ -15132,16 +15599,49 @@ msgstr ""
msgid "On track"
msgstr ""
+msgid "OnDemandScans|Attached branch"
+msgstr ""
+
+msgid "OnDemandScans|Attached branch is where the scan job runs."
+msgstr ""
+
+msgid "OnDemandScans|Could not run the scan. Please try again."
+msgstr ""
+
msgid "OnDemandScans|Create new DAST scan"
msgstr ""
+msgid "OnDemandScans|DAST will scan the target URL and any discovered sub URLs."
+msgstr ""
+
+msgid "OnDemandScans|New on-demand DAST scan"
+msgstr ""
+
msgid "OnDemandScans|On-demand Scans"
msgstr ""
+msgid "OnDemandScans|On-demand scans run outside the DevOps cycle and find vulnerabilities in your projects. %{learnMoreLinkStart}Learn more%{learnMoreLinkEnd}"
+msgstr ""
+
+msgid "OnDemandScans|Only a passive scan can be performed on demand."
+msgstr ""
+
+msgid "OnDemandScans|Passive DAST Scan"
+msgstr ""
+
+msgid "OnDemandScans|Please enter a valid URL format, ex: http://www.example.com/home"
+msgstr ""
+
+msgid "OnDemandScans|Run this scan"
+msgstr ""
+
+msgid "OnDemandScans|Scan mode"
+msgstr ""
+
msgid "OnDemandScans|Schedule or run scans immediately against target sites. Currently available on-demand scan type: DAST. %{helpLinkStart}More information%{helpLinkEnd}"
msgstr ""
-msgid "Onboarding"
+msgid "OnDemandScans|Target URL"
msgstr ""
msgid "Once imported, repositories can be mirrored over SSH. Read more %{link_start}here%{link_end}."
@@ -15167,6 +15667,9 @@ msgstr ""
msgid "One or more of you personal access tokens were revoked"
msgstr ""
+msgid "One or more of your %{provider} projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
+msgstr ""
+
msgid "One or more of your Bitbucket projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
msgstr ""
@@ -15668,6 +16171,9 @@ msgstr ""
msgid "Parent epic is not present."
msgstr ""
+msgid "Parsing error for param :embed_json. %{message}"
+msgstr ""
+
msgid "Part of merge request changes"
msgstr ""
@@ -15680,6 +16186,9 @@ msgstr ""
msgid "Passed"
msgstr ""
+msgid "Passed on"
+msgstr ""
+
msgid "Password"
msgstr ""
@@ -15764,6 +16273,9 @@ msgstr ""
msgid "Perform common operations on GitLab project"
msgstr ""
+msgid "Performance and resource management"
+msgstr ""
+
msgid "Performance optimization"
msgstr ""
@@ -15971,7 +16483,7 @@ msgstr ""
msgid "Pipelines|Something went wrong while cleaning runners cache."
msgstr ""
-msgid "Pipelines|There are currently no %{scope} pipelines."
+msgid "Pipelines|There are currently no finished pipelines."
msgstr ""
msgid "Pipelines|There are currently no pipelines."
@@ -16082,6 +16594,9 @@ msgstr ""
msgid "Pipeline|Stop pipeline #%{pipelineId}?"
msgstr ""
+msgid "Pipeline|Tag name"
+msgstr ""
+
msgid "Pipeline|Trigger author"
msgstr ""
@@ -16163,12 +16678,18 @@ msgstr ""
msgid "Please check your email (%{email}) to verify that you own this address and unlock the power of CI/CD. Didn't receive it? %{resend_link}. Wrong email address? %{update_link}."
msgstr ""
+msgid "Please choose a file"
+msgstr ""
+
msgid "Please choose a group URL with no special characters."
msgstr ""
msgid "Please complete your profile with email address"
msgstr ""
+msgid "Please convert %{linkStart}them to Git%{linkEnd}, and go through the %{linkToImportFlow} again."
+msgstr ""
+
msgid "Please convert them to %{link_to_git}, and go through the %{link_to_import_flow} again."
msgstr ""
@@ -16226,6 +16747,9 @@ msgstr ""
msgid "Please provide a valid email address."
msgstr ""
+msgid "Please provide attributes to update"
+msgstr ""
+
msgid "Please refer to <a href=\"%{docs_url}\">%{docs_url}</a>"
msgstr ""
@@ -16961,6 +17485,9 @@ msgstr ""
msgid "Project has too many %{label_for_message} to search"
msgstr ""
+msgid "Project is required when cluster_type is :project"
+msgstr ""
+
msgid "Project members"
msgstr ""
@@ -17078,7 +17605,40 @@ msgstr ""
msgid "ProjectService|Comment"
msgstr ""
-msgid "ProjectService|Comment will be posted on each event"
+msgid "ProjectService|Event will be triggered by a push to the repository"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a commit is created/updated"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a confidential issue is created/updated/closed"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a deployment finishes"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a merge request is created/updated/merged"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a new tag is pushed to the repository"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a new, unique alert is recorded"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a pipeline status changes"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a wiki page is created/updated"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when an issue is created/updated/closed"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when someone adds a comment"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when someone adds a comment on a confidential issue"
msgstr ""
msgid "ProjectService|Perform common operations on GitLab project: %{project_name}"
@@ -17129,6 +17689,9 @@ msgstr ""
msgid "ProjectSettings|Enable 'Delete source branch' option by default"
msgstr ""
+msgid "ProjectSettings|Enable merge trains and pipelines for merged results"
+msgstr ""
+
msgid "ProjectSettings|Every merge creates a merge commit"
msgstr ""
@@ -17159,7 +17722,7 @@ msgstr ""
msgid "ProjectSettings|Forks"
msgstr ""
-msgid "ProjectSettings|Git Large File Storage"
+msgid "ProjectSettings|Git Large File Storage (LFS)"
msgstr ""
msgid "ProjectSettings|Internal"
@@ -17195,9 +17758,6 @@ msgstr ""
msgid "ProjectSettings|Merge options"
msgstr ""
-msgid "ProjectSettings|Merge pipelines will try to validate the post-merge result prior to merging"
-msgstr ""
-
msgid "ProjectSettings|Merge requests"
msgstr ""
@@ -17225,6 +17785,9 @@ msgstr ""
msgid "ProjectSettings|Pipelines"
msgstr ""
+msgid "ProjectSettings|Pipelines for merge requests must be enabled in the CI/CD configuration file, or pipelines could be unresolvable or dropped"
+msgstr ""
+
msgid "ProjectSettings|Pipelines must succeed"
msgstr ""
@@ -17252,6 +17815,9 @@ msgstr ""
msgid "ProjectSettings|Show link to create/view merge request when pushing from the command line"
msgstr ""
+msgid "ProjectSettings|Skipped pipelines are considered successful"
+msgstr ""
+
msgid "ProjectSettings|Snippets"
msgstr ""
@@ -17267,6 +17833,9 @@ msgstr ""
msgid "ProjectSettings|These checks must pass before merge requests can be merged"
msgstr ""
+msgid "ProjectSettings|This introduces the risk of merging changes that will not pass the pipeline."
+msgstr ""
+
msgid "ProjectSettings|This setting is applied on the server level and can be overridden by an admin."
msgstr ""
@@ -17444,15 +18013,27 @@ msgstr ""
msgid "ProjectsNew|Blank project"
msgstr ""
+msgid "ProjectsNew|Connect your external repository to GitLab CI/CD."
+msgstr ""
+
msgid "ProjectsNew|Contact an administrator to enable options for importing your project."
msgstr ""
msgid "ProjectsNew|Create"
msgstr ""
+msgid "ProjectsNew|Create a blank project to house your files, plan your work, and collaborate on code, among other things."
+msgstr ""
+
+msgid "ProjectsNew|Create blank project"
+msgstr ""
+
msgid "ProjectsNew|Create from template"
msgstr ""
+msgid "ProjectsNew|Create new project"
+msgstr ""
+
msgid "ProjectsNew|Creating project & repository."
msgstr ""
@@ -17477,6 +18058,9 @@ msgstr ""
msgid "ProjectsNew|Project description %{tag_start}(optional)%{tag_end}"
msgstr ""
+msgid "ProjectsNew|Run CI/CD for external repository"
+msgstr ""
+
msgid "ProjectsNew|Template"
msgstr ""
@@ -17738,6 +18322,9 @@ msgstr ""
msgid "Protected Branch"
msgstr ""
+msgid "Protected Branches"
+msgstr ""
+
msgid "Protected Environment"
msgstr ""
@@ -17750,6 +18337,9 @@ msgstr ""
msgid "Protected Tag"
msgstr ""
+msgid "Protected Tags"
+msgstr ""
+
msgid "Protected branches"
msgstr ""
@@ -17849,6 +18439,9 @@ msgstr ""
msgid "Provider"
msgstr ""
+msgid "Proxy support for this API is not available currently"
+msgstr ""
+
msgid "Pseudonymizer data collection"
msgstr ""
@@ -17873,6 +18466,9 @@ msgstr ""
msgid "Publish to status page"
msgstr ""
+msgid "Published on status page"
+msgstr ""
+
msgid "Publishes this issue to the associated status page."
msgstr ""
@@ -18047,6 +18643,9 @@ msgstr ""
msgid "Recipe"
msgstr ""
+msgid "Reconfigure"
+msgstr ""
+
msgid "Recover hidden stage"
msgstr ""
@@ -18133,6 +18732,9 @@ msgstr ""
msgid "Registration|Your profile"
msgstr ""
+msgid "Regulate approvals by authors/committers, based on compliance frameworks. Can be changed only at the instance level."
+msgstr ""
+
msgid "Rejected (closed)"
msgstr ""
@@ -18183,15 +18785,24 @@ msgstr ""
msgid "ReleaseAssetLinkType|Image"
msgstr ""
+msgid "ReleaseAssetLinkType|Images"
+msgstr ""
+
msgid "ReleaseAssetLinkType|Other"
msgstr ""
msgid "ReleaseAssetLinkType|Package"
msgstr ""
+msgid "ReleaseAssetLinkType|Packages"
+msgstr ""
+
msgid "ReleaseAssetLinkType|Runbook"
msgstr ""
+msgid "ReleaseAssetLinkType|Runbooks"
+msgstr ""
+
msgid "Releases"
msgstr ""
@@ -18204,6 +18815,9 @@ msgstr ""
msgid "Releases documentation"
msgstr ""
+msgid "Releases|New Release"
+msgstr ""
+
msgid "Release|Something went wrong while getting the release details"
msgstr ""
@@ -18276,6 +18890,9 @@ msgstr ""
msgid "Remove fork relationship"
msgstr ""
+msgid "Remove from batch"
+msgstr ""
+
msgid "Remove from board"
msgstr ""
@@ -18465,6 +19082,12 @@ msgstr ""
msgid "Replication"
msgstr ""
+msgid "Replication enabled"
+msgstr ""
+
+msgid "Replication paused"
+msgstr ""
+
msgid "Reply by email"
msgstr ""
@@ -18492,15 +19115,15 @@ msgstr ""
msgid "Reported %{timeAgo} by %{reportedBy}"
msgstr ""
-msgid "Reported Resource Changes: %{addNum} to add, %{changeNum} to change, %{deleteNum} to delete"
-msgstr ""
-
msgid "Reporter"
msgstr ""
msgid "Reporting"
msgstr ""
+msgid "Reports"
+msgstr ""
+
msgid "Reports|%{combinedString} and %{resolvedString}"
msgstr ""
@@ -18539,6 +19162,9 @@ msgstr ""
msgid "Reports|Failure"
msgstr ""
+msgid "Reports|Identifier"
+msgstr ""
+
msgid "Reports|Metrics reports are loading"
msgstr ""
@@ -18551,6 +19177,9 @@ msgstr ""
msgid "Reports|Metrics reports failed loading results"
msgstr ""
+msgid "Reports|Scanner"
+msgstr ""
+
msgid "Reports|Severity"
msgstr ""
@@ -18599,15 +19228,33 @@ msgstr ""
msgid "Repository cleanup has started. You will receive an email once the cleanup operation is complete."
msgstr ""
+msgid "Repository files count over the limit"
+msgstr ""
+
+msgid "Repository has an invalid default branch name."
+msgstr ""
+
+msgid "Repository has more than one branch."
+msgstr ""
+
msgid "Repository has no locks."
msgstr ""
+msgid "Repository has tags."
+msgstr ""
+
msgid "Repository maintenance"
msgstr ""
msgid "Repository mirroring"
msgstr ""
+msgid "Repository must contain at least 1 file."
+msgstr ""
+
+msgid "Repository size is above the limit."
+msgstr ""
+
msgid "Repository static objects"
msgstr ""
@@ -18617,7 +19264,7 @@ msgstr ""
msgid "Repository sync capacity"
msgstr ""
-msgid "Repository: %{counter_repositories} / Wikis: %{counter_wikis} / Build Artifacts: %{counter_build_artifacts} / LFS: %{counter_lfs_objects}"
+msgid "Repository: %{counter_repositories} / Wikis: %{counter_wikis} / Build Artifacts: %{counter_build_artifacts} / LFS: %{counter_lfs_objects} / Snippets: %{counter_snippets}"
msgstr ""
msgid "RepositorySettingsAccessLevel|Select"
@@ -18629,6 +19276,9 @@ msgstr ""
msgid "Request Headers"
msgstr ""
+msgid "Request details"
+msgstr ""
+
msgid "Request parameter %{param} is missing."
msgstr ""
@@ -18735,6 +19385,9 @@ msgstr ""
msgid "Reset template"
msgstr ""
+msgid "Reset to project defaults"
+msgstr ""
+
msgid "Resetting the authorization key for this project will require updating the authorization key in every alert source it is enabled in."
msgstr ""
@@ -18831,9 +19484,6 @@ msgstr ""
msgid "Resume"
msgstr ""
-msgid "Resume replication"
-msgstr ""
-
msgid "Resync"
msgstr ""
@@ -19456,6 +20106,9 @@ msgstr ""
msgid "SecurityReports|False positive"
msgstr ""
+msgid "SecurityReports|Group Security Dashboard"
+msgstr ""
+
msgid "SecurityReports|Hide dismissed"
msgstr ""
@@ -19510,10 +20163,13 @@ msgstr ""
msgid "SecurityReports|Remove project from dashboard"
msgstr ""
-msgid "SecurityReports|Report type"
+msgid "SecurityReports|Return to dashboard"
msgstr ""
-msgid "SecurityReports|Return to dashboard"
+msgid "SecurityReports|Scan details"
+msgstr ""
+
+msgid "SecurityReports|Scanner type"
msgstr ""
msgid "SecurityReports|Security Dashboard"
@@ -19570,6 +20226,9 @@ msgstr ""
msgid "SecurityReports|There was an error while generating the report."
msgstr ""
+msgid "SecurityReports|Unable to add %{invalidProjectsMessage}"
+msgstr ""
+
msgid "SecurityReports|Unable to add %{invalidProjects}"
msgstr ""
@@ -19684,6 +20343,9 @@ msgstr ""
msgid "Select due date"
msgstr ""
+msgid "Select file"
+msgstr ""
+
msgid "Select group or project"
msgstr ""
@@ -19747,9 +20409,6 @@ msgstr ""
msgid "Select the branch you want to set as the default for this project. All merge requests and commits will automatically be made against this branch unless you specify a different one."
msgstr ""
-msgid "Select the configured storage available for new repositories to be placed on."
-msgstr ""
-
msgid "Select the custom project template source group."
msgstr ""
@@ -19942,12 +20601,6 @@ msgstr ""
msgid "Service Desk is enabled but not yet active"
msgstr ""
-msgid "Service Desk is off"
-msgstr ""
-
-msgid "Service Desk is on"
-msgstr ""
-
msgid "Service Templates"
msgstr ""
@@ -20035,6 +20688,15 @@ msgstr ""
msgid "Set the milestone to %{milestone_reference}."
msgstr ""
+msgid "Set the number of concurrent requests this secondary node will make to the primary node while backfilling."
+msgstr ""
+
+msgid "Set the synchronization and verification capacity for the secondary node."
+msgstr ""
+
+msgid "Set the timeout in seconds to send a secondary node status to the primary and IPs allowed for the secondary nodes."
+msgstr ""
+
msgid "Set time estimate"
msgstr ""
@@ -20074,6 +20736,9 @@ msgstr ""
msgid "Set weight to %{weight}."
msgstr ""
+msgid "Set what should be replicated by choosing specific projects or groups by the secondary node."
+msgstr ""
+
msgid "SetPasswordToCloneLink|set a password"
msgstr ""
@@ -20200,6 +20865,9 @@ msgstr ""
msgid "Show complete raw log"
msgstr ""
+msgid "Show details"
+msgstr ""
+
msgid "Show file browser"
msgstr ""
@@ -20209,10 +20877,13 @@ msgstr ""
msgid "Show latest version"
msgstr ""
+msgid "Show list"
+msgstr ""
+
msgid "Show me everything"
msgstr ""
-msgid "Show me how"
+msgid "Show me how to add a pipeline"
msgstr ""
msgid "Show me more advanced stuff"
@@ -20261,9 +20932,6 @@ msgstr ""
msgid "Showing all issues"
msgstr ""
-msgid "Showing all labels"
-msgstr ""
-
msgid "Showing last %{size} of log -"
msgstr ""
@@ -20375,9 +21043,6 @@ msgstr ""
msgid "Skip outdated deployment jobs"
msgstr ""
-msgid "Skip this for now"
-msgstr ""
-
msgid "Skipped"
msgstr ""
@@ -20522,6 +21187,9 @@ msgstr ""
msgid "Something went wrong while adding your award. Please try again."
msgstr ""
+msgid "Something went wrong while applying the batch of suggestions. Please try again."
+msgstr ""
+
msgid "Something went wrong while applying the suggestion. Please try again."
msgstr ""
@@ -20624,10 +21292,13 @@ msgstr ""
msgid "Something went wrong while updating a requirement."
msgstr ""
+msgid "Something went wrong while updating assignees"
+msgstr ""
+
msgid "Something went wrong while updating your list settings"
msgstr ""
-msgid "Something went wrong with your automatic subscription renewal"
+msgid "Something went wrong with your automatic subscription renewal."
msgstr ""
msgid "Something went wrong, unable to add %{project} to dashboard"
@@ -20645,6 +21316,9 @@ msgstr ""
msgid "Something went wrong, unable to search projects"
msgstr ""
+msgid "Something went wrong."
+msgstr ""
+
msgid "Something went wrong. Please try again."
msgstr ""
@@ -20828,6 +21502,9 @@ msgstr ""
msgid "Source code"
msgstr ""
+msgid "Source code (%{fileExtension})"
+msgstr ""
+
msgid "Source is not available"
msgstr ""
@@ -20912,6 +21589,9 @@ msgstr ""
msgid "Stack trace"
msgstr ""
+msgid "Stacktrace snippet"
+msgstr ""
+
msgid "Stage"
msgstr ""
@@ -21017,6 +21697,9 @@ msgstr ""
msgid "Start thread & reopen %{noteable_name}"
msgstr ""
+msgid "Start using Directed Acyclic Graphs (DAG)"
+msgstr ""
+
msgid "Start your Free Gold Trial"
msgstr ""
@@ -21479,6 +22162,21 @@ msgstr ""
msgid "SuggestedColors|Very pale orange"
msgstr ""
+msgid "Suggestion is not applicable as the suggestion was not found."
+msgstr ""
+
+msgid "Suggestions are not applicable as one or more suggestions were not found."
+msgstr ""
+
+msgid "Suggestions are not applicable as their lines cannot overlap."
+msgstr ""
+
+msgid "Suggestions must all be on the same branch."
+msgstr ""
+
+msgid "Suggestions that change line count can't be added to batches, yet."
+msgstr ""
+
msgid "Suggestions:"
msgstr ""
@@ -21521,9 +22219,6 @@ msgstr ""
msgid "Synchronization disabled"
msgstr ""
-msgid "Synchronization of container repositories is disabled."
-msgstr ""
-
msgid "System"
msgstr ""
@@ -21704,6 +22399,34 @@ msgstr ""
msgid "Terms of Service and Privacy Policy"
msgstr ""
+msgid "Terraform|%{number} Terraform report failed to generate"
+msgid_plural "Terraform|%{number} Terraform reports failed to generate"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Terraform|%{number} Terraform report was generated in your pipelines"
+msgid_plural "Terraform|%{number} Terraform reports were generated in your pipelines"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Terraform|A Terraform report failed to generate."
+msgstr ""
+
+msgid "Terraform|A Terraform report was generated in your pipelines."
+msgstr ""
+
+msgid "Terraform|Generating the report caused an error."
+msgstr ""
+
+msgid "Terraform|Reported Resource Changes: %{addNum} to add, %{changeNum} to change, %{deleteNum} to delete"
+msgstr ""
+
+msgid "Terraform|The Terraform report %{name} failed to generate."
+msgstr ""
+
+msgid "Terraform|The Terraform report %{name} was generated in your pipelines."
+msgstr ""
+
msgid "Test"
msgstr ""
@@ -21730,7 +22453,7 @@ msgstr ""
msgid "TestHooks|Ensure the project has CI pipelines."
msgstr ""
-msgid "TestHooks|Ensure the project has at least one commit."
+msgid "TestHooks|Ensure the project has deployments."
msgstr ""
msgid "TestHooks|Ensure the project has issues."
@@ -21831,7 +22554,7 @@ msgstr ""
msgid "The Prometheus server responded with \"bad request\". Please check your queries are correct and are supported in your Prometheus version. %{documentationLink}"
msgstr ""
-msgid "The URL defined on the primary node that secondary nodes should use to contact it. Defaults to URL"
+msgid "The URL defined on the primary node that secondary nodes should use to contact it."
msgstr ""
msgid "The URL to use for connecting to Elasticsearch. Use a comma-separated list to support clustering (e.g., \"http://localhost:9200, http://localhost:9201\")."
@@ -21840,6 +22563,9 @@ msgstr ""
msgid "The X509 Certificate to use when mutual TLS is required to communicate with the external authorization service. If left blank, the server certificate is still validated when accessing over HTTPS."
msgstr ""
+msgid "The above settings apply to all projects with the selected compliance framework(s)."
+msgstr ""
+
msgid "The amount of seconds after which a request to get a secondary node status will time out."
msgstr ""
@@ -22139,9 +22865,6 @@ msgstr ""
msgid "The total stage shows the time it takes between creating an issue and deploying the code to production. The data will be automatically added once you have completed the full idea to production cycle."
msgstr ""
-msgid "The unique identifier for the Geo node. Must match %{geoNodeName} if it is set in gitlab.rb, otherwise it must match %{externalUrl} with a trailing slash"
-msgstr ""
-
msgid "The update action will time out after %{number_of_minutes} minutes. For big repositories, use a clone/push combination."
msgstr ""
@@ -22166,9 +22889,6 @@ msgstr ""
msgid "The user you are trying to deactivate has been active in the past %{minimum_inactive_days} days and cannot be deactivated"
msgstr ""
-msgid "The user-facing URL of the Geo node"
-msgstr ""
-
msgid "The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6."
msgstr ""
@@ -22259,6 +22979,9 @@ msgstr ""
msgid "There was a problem fetching project branches."
msgstr ""
+msgid "There was a problem fetching project tags."
+msgstr ""
+
msgid "There was a problem fetching project users."
msgstr ""
@@ -22310,6 +23033,9 @@ msgstr ""
msgid "There was an error fetching the %{replicableType}"
msgstr ""
+msgid "There was an error fetching the Geo Settings"
+msgstr ""
+
msgid "There was an error fetching the Node's Groups"
msgstr ""
@@ -22430,6 +23156,12 @@ msgstr ""
msgid "This %{issuable} is locked. Only <strong>project members</strong> can comment."
msgstr ""
+msgid "This %{noteableTypeText} is %{confidentialLinkStart}confidential%{linkEnd} and %{lockedLinkStart}locked%{linkEnd}."
+msgstr ""
+
+msgid "This %{noteableTypeText} is locked."
+msgstr ""
+
msgid "This %{viewer} could not be displayed because %{reason}. You can %{options} instead."
msgstr ""
@@ -22448,6 +23180,9 @@ msgstr ""
msgid "This action can lead to data loss. To prevent accidental actions we ask you to confirm your intention."
msgstr ""
+msgid "This also resolves all related threads"
+msgstr ""
+
msgid "This also resolves the discussion"
msgstr ""
@@ -22538,6 +23273,9 @@ msgstr ""
msgid "This epic does not exist or you don't have sufficient permission."
msgstr ""
+msgid "This feature is currently in beta. We invite you to %{linkStart}give feedback%{linkEnd}."
+msgstr ""
+
msgid "This feature requires local storage to be enabled"
msgstr ""
@@ -22571,7 +23309,7 @@ msgstr ""
msgid "This is a Work in Progress"
msgstr ""
-msgid "This is a confidential issue."
+msgid "This is a confidential %{noteableTypeText}."
msgstr ""
msgid "This is a delayed job to run in %{remainingTime}"
@@ -22598,9 +23336,6 @@ msgstr ""
msgid "This is your current session"
msgstr ""
-msgid "This issue is %{confidentialLinkStart}confidential%{linkEnd} and %{lockedLinkStart}locked%{linkEnd}."
-msgstr ""
-
msgid "This issue is confidential"
msgstr ""
@@ -22610,9 +23345,6 @@ msgstr ""
msgid "This issue is in a child epic of the filtered epic"
msgstr ""
-msgid "This issue is locked."
-msgstr ""
-
msgid "This job depends on other jobs with expired/erased artifacts: %{invalid_dependencies}"
msgstr ""
@@ -22712,6 +23444,9 @@ msgstr ""
msgid "This license has already expired."
msgstr ""
+msgid "This link points to external content"
+msgstr ""
+
msgid "This may expose confidential information as the selected fork is in another namespace that can have other members."
msgstr ""
@@ -22739,6 +23474,9 @@ msgstr ""
msgid "This page will be removed in a future release."
msgstr ""
+msgid "This pipeline does not use the %{codeStart}needs%{codeEnd} keyword and can't be represented as a directed acyclic graph."
+msgstr ""
+
msgid "This pipeline makes use of a predefined CI/CD configuration enabled by %{strongStart}Auto DevOps.%{strongEnd}"
msgstr ""
@@ -22967,6 +23705,9 @@ msgstr ""
msgid "Time from last commit to merge"
msgstr ""
+msgid "Time in seconds"
+msgstr ""
+
msgid "Time in seconds GitLab will wait for a response from the external service. When the service does not respond in time, access will be denied."
msgstr ""
@@ -23388,9 +24129,15 @@ msgstr ""
msgid "Total artifacts size: %{total_size}"
msgstr ""
+msgid "Total cores (CPUs)"
+msgstr ""
+
msgid "Total issues"
msgstr ""
+msgid "Total memory (GB)"
+msgstr ""
+
msgid "Total test time for all commits/merges"
msgstr ""
@@ -23592,6 +24339,9 @@ msgstr ""
msgid "U2F only works with HTTPS-enabled websites. Contact your administrator for more details."
msgstr ""
+msgid "UI Development Kit"
+msgstr ""
+
msgid "URL"
msgstr ""
@@ -23664,9 +24414,6 @@ msgstr ""
msgid "Unable to load the merge request widget. Try reloading the page."
msgstr ""
-msgid "Unable to process group import file"
-msgstr ""
-
msgid "Unable to resolve"
msgstr ""
@@ -23682,6 +24429,9 @@ msgstr ""
msgid "Unable to sign you in to the group with SAML due to \"%{reason}\""
msgstr ""
+msgid "Unable to suggest a path. Please refresh and try again."
+msgstr ""
+
msgid "Unable to update label prioritization at this time"
msgstr ""
@@ -23697,6 +24447,9 @@ msgstr ""
msgid "Unarchiving the project will restore people's ability to make changes to it. The repository can be committed to, and issues, comments, and other entities can be created. %{strong_start}Once active, this project shows up in the search and on the dashboard.%{strong_end}"
msgstr ""
+msgid "Unassign from commenting user"
+msgstr ""
+
msgid "Unblock"
msgstr ""
@@ -23772,6 +24525,9 @@ msgstr ""
msgid "Unreachable"
msgstr ""
+msgid "Unrecognized cluster type"
+msgstr ""
+
msgid "Unresolve"
msgstr ""
@@ -23832,6 +24588,9 @@ msgstr ""
msgid "Unsubscribes from this %{quick_action_target}."
msgstr ""
+msgid "Unsupported todo type passed. Supported todo types are: %{todo_types}"
+msgstr ""
+
msgid "Until"
msgstr ""
@@ -23859,6 +24618,9 @@ msgstr ""
msgid "Update approval rule"
msgstr ""
+msgid "Update approvers"
+msgstr ""
+
msgid "Update failed"
msgstr ""
@@ -23868,6 +24630,9 @@ msgstr ""
msgid "Update it"
msgstr ""
+msgid "Update iteration"
+msgstr ""
+
msgid "Update now"
msgstr ""
@@ -23913,7 +24678,7 @@ msgstr ""
msgid "Updated at"
msgstr ""
-msgid "Updated to"
+msgid "Updated to %{linkStart}chart v%{linkEnd}"
msgstr ""
msgid "Updating"
@@ -23943,6 +24708,9 @@ msgstr ""
msgid "Upload CSV file"
msgstr ""
+msgid "Upload License"
+msgstr ""
+
msgid "Upload New File"
msgstr ""
@@ -24024,16 +24792,19 @@ msgstr ""
msgid "UsageQuota|Pipelines"
msgstr ""
+msgid "UsageQuota|Purchase more storage"
+msgstr ""
+
msgid "UsageQuota|Repositories"
msgstr ""
msgid "UsageQuota|Repository"
msgstr ""
-msgid "UsageQuota|Storage"
+msgid "UsageQuota|Snippets"
msgstr ""
-msgid "UsageQuota|Storage usage:"
+msgid "UsageQuota|Storage"
msgstr ""
msgid "UsageQuota|This namespace has no projects which use shared runners"
@@ -24066,6 +24837,12 @@ msgstr ""
msgid "UsageQuota|Wikis"
msgstr ""
+msgid "UsageQuota|You used: %{usage} %{limit}"
+msgstr ""
+
+msgid "UsageQuota|out of %{formattedLimit} of your namespace storage"
+msgstr ""
+
msgid "Use %{code_start}::%{code_end} to create a %{link_start}scoped label set%{link_end} (eg. %{code_start}priority::1%{code_end})"
msgstr ""
@@ -24132,6 +24909,9 @@ msgstr ""
msgid "User IDs"
msgstr ""
+msgid "User Lists can only be created and modified with %{linkStart}the API%{linkEnd}"
+msgstr ""
+
msgid "User OAuth applications"
msgstr ""
@@ -24156,6 +24936,9 @@ msgstr ""
msgid "User key was successfully removed."
msgstr ""
+msgid "User list %{name} will be removed. Are you sure?"
+msgstr ""
+
msgid "User map"
msgstr ""
@@ -24177,148 +24960,10 @@ msgstr ""
msgid "User was successfully updated."
msgstr ""
-msgid "UserOnboardingTour|%{activeTour}/%{totalTours}"
-msgstr ""
-
-msgid "UserOnboardingTour|%{completed}/%{total} steps completed"
-msgstr ""
-
-msgid "UserOnboardingTour|%{emphasisStart}Well done!%{emphasisEnd}%{lineBreak}%{lineBreak}That's it for our guided tour, congratulations for making it all the way to the end!%{lineBreak}%{lineBreak}We hope this gave you a good overview of GitLab and how it can help you. We'll now show you how to create your own project and invite your colleagues."
-msgstr ""
-
-msgid "UserOnboardingTour|Adding other members to a project is done through Project Settings. Click on %{emphasisStart}Settings%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Alright, that's it for Commits. Let's take a look at the %{emphasisStart}Branches%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Awesome! Now click on %{emphasisStart}Members%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Click on one of the %{emphasisStart}Compare%{emphasisEnd} buttons to compare a branch to master."
-msgstr ""
-
-msgid "UserOnboardingTour|Click on one of the %{emphasisStart}pipeline IDs%{emphasisEnd} to see the details of a pipeline."
-msgstr ""
-
-msgid "UserOnboardingTour|Click to open the latest commit to see its details."
-msgstr ""
-
-msgid "UserOnboardingTour|Close 'Learn GitLab'"
-msgstr ""
-
-msgid "UserOnboardingTour|Commits are shown in chronological order and can be filtered by the commit message or by the branch."
+msgid "UserList|Delete %{name}?"
msgstr ""
-msgid "UserOnboardingTour|Create a project"
-msgstr ""
-
-msgid "UserOnboardingTour|Exit 'Learn GitLab'"
-msgstr ""
-
-msgid "UserOnboardingTour|Got it"
-msgstr ""
-
-msgid "UserOnboardingTour|Great job! %{clapHands} We hope the tour was helpful and that you learned how to use GitLab.%{lineBreak}%{lineBreak}We'd love to get your feedback on this tour.%{lineBreak}%{lineBreak}%{emphasisStart}How helpful would you say this guided tour was?%{emphasisEnd}%{lineBreak}%{lineBreak}"
-msgstr ""
-
-msgid "UserOnboardingTour|Guided GitLab Tour"
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can compare the changes of this branch to another one. Changes are divided by files so that it's easier to see what was changed where."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can create a project from scratch, start with a template or import a repository from other platforms. Whatever you choose, we'll guide you through the process.%{lineBreak}%{lineBreak}Fill in your new project information and click on %{emphasisStart}Create Project%{emphasisEnd} to progress to the next step."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can see the breakdown of the pipelines: its stages and jobs in each of the stages and their status.%{lineBreak}%{lineBreak}Our CI/CD pipelines are quite complex, most of our users have fewer and simpler pipelines."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can see the current members of the project (just you at the moment) and invite new members.%{lineBreak}%{lineBreak}You can invite multiple members at once (existing GitLab users or invite by email) and you can also set their roles and permissions.%{lineBreak}%{lineBreak}Add a few members and click on %{emphasisStart}Add to project%{emphasisEnd} to complete this step."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can see what changes were made with this commit, on what branch and if there's a related merge request. The status of the pipeline will also show up if CI/CD is set up.%{lineBreak}%{lineBreak}You can also comment on the lines of code that were changed and start a discussion with your colleagues!"
-msgstr ""
-
-msgid "UserOnboardingTour|Here's an overview of branches in the %{emphasisStart}%{projectName}%{emphasisEnd} project. They're split into Active and Stale.%{lineBreak}%{lineBreak}From here, you can create a new merge request from a branch, or compare the branch to any other branch in the project. By default, it will compare it to the master branch."
-msgstr ""
-
-msgid "UserOnboardingTour|Invite colleagues"
-msgstr ""
-
-msgid "UserOnboardingTour|Issues are great for communicating and keeping track of progress in GitLab. These are all issues that are open in the %{emphasisStart}%{projectName}%{emphasisEnd}.%{lineBreak}%{lineBreak}You can help us improve GitLab by contributing work to issues that are labeled <span class=\"badge color-label accept-mr-label\">Accepting merge requests</span>.%{lineBreak}%{lineBreak}This list can be filtered by labels, milestones, assignees, authors... We'll show you how it looks when the list is filtered by a label."
-msgstr ""
-
-msgid "UserOnboardingTour|Learn GitLab"
-msgstr ""
-
-msgid "UserOnboardingTour|Let's take a closer look at a merge request. Click on the title of one."
-msgstr ""
-
-msgid "UserOnboardingTour|Let's take a closer look at all the commits. Click on %{emphasisStart}Commits%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Let's take a closer look at the repository of this project. Click on %{emphasisStart}Repository%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|No thanks"
-msgstr ""
-
-msgid "UserOnboardingTour|Ok, let's go"
-msgstr ""
-
-msgid "UserOnboardingTour|Ok, show me"
-msgstr ""
-
-msgid "UserOnboardingTour|Open one of the issues by clicking on its title."
-msgstr ""
-
-msgid "UserOnboardingTour|Restart this step"
-msgstr ""
-
-msgid "UserOnboardingTour|Skip this step"
-msgstr ""
-
-msgid "UserOnboardingTour|Sweet! Your project was created and is ready to be used.%{lineBreak}%{lineBreak}You can start adding files to the repository or clone it. One last thing we want to show you is how to invite your colleagues to your new project."
-msgstr ""
-
-msgid "UserOnboardingTour|Take a look. Here's a nifty menu for quickly creating issues, merge requests, snippets, projects and groups. Click on it and select \"New project\" from the \"GitLab\" section to get started."
-msgstr ""
-
-msgid "UserOnboardingTour|Thanks for taking the guided tour. Remember, if you want to go through it again, you can start %{emphasisStart}Learn GitLab%{emphasisEnd} in the help menu on the top right."
-msgstr ""
-
-msgid "UserOnboardingTour|Thanks for the feedback! %{thumbsUp}"
-msgstr ""
-
-msgid "UserOnboardingTour|That's it for issues. Let'st take a look at %{emphasisStart}Merge Requests%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|That's it for merge requests. Now for the final part of this guided tour - the %{emphasisStart}CI/CD%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|That's it for the Repository. Let's take a look at the %{emphasisStart}Issues%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|The structure of this page is very similar to issues. Status, description, discussion and the sidebar are all here.%{lineBreak}%{lineBreak}But take a look below the description and you'll notice that there's more information about the merge request, the CI/CD pipeline and the options for approving it.%{lineBreak}%{lineBreak}Alongside the discussion you can also see more information about commits in this merge request, the status of pipelines and review all changes that were made."
-msgstr ""
-
-msgid "UserOnboardingTour|There's a lot of information here but don't worry, we'll go through it.%{lineBreak}%{lineBreak}On the top you can see the status of the issue and when it was opened and by whom. Directly below it is the issue description and below that are other %{emphasisStart}related issues%{emphasisEnd} and %{emphasisStart}merge requests%{emphasisEnd} (if any). Then below that is the %{emphasisStart}discussion%{emphasisEnd}, that's where most of the communication happens.%{lineBreak}%{lineBreak}On the right, there's a sidebar where you can view/change the %{emphasisStart}assignee, milestone, due date, labels, weight%{emphasisEnd}, etc."
-msgstr ""
-
-msgid "UserOnboardingTour|These are all the CI/CD pipelines we have for our %{emphasisStart}%{projectName}%{emphasisEnd} project.%{lineBreak}%{lineBreak}Here you can see the status of each pipeline, for what commit it's running for, its stages and the status for them."
-msgstr ""
-
-msgid "UserOnboardingTour|These are all the issues that are available for community contributions. Let's take a closer look at one of them."
-msgstr ""
-
-msgid "UserOnboardingTour|This is an overview of all merge requests in this project. Similarly to the issues overview it can be filtered down by things like labels, milestones, authors, assignees, etc."
-msgstr ""
-
-msgid "UserOnboardingTour|This is the repository for the %{emphasisStart}%{projectName}%{emphasisEnd} project. All our code is stored here. Feel free to explore and take a closer look at folders and files.%{lineBreak}%{lineBreak}Above the file structure you can see the latest commit, who the author is and the status of the CI/CD pipeline.%{lineBreak}%{lineBreak}If you scroll down below the file structure, you'll find the Readme of this project. This is defined in the README.md file at the root of the repository."
-msgstr ""
-
-msgid "UserOnboardingTour|Welcome to the project overview of the %{emphasisStart}%{projectName}%{emphasisEnd} project. This is the project that we use to work on GitLab. At first, a project seems like a simple repository, but at GitLab, a project is so much more.%{lineBreak}%{lineBreak}You can create projects for hosting your codebase, use it as an issue tracker, collaborate on code, and continuously build, test, and deploy your app with built-in GitLab CI/CD."
+msgid "UserList|created %{timeago}"
msgstr ""
msgid "UserProfile|Activity"
@@ -24471,6 +25116,9 @@ msgstr ""
msgid "UsersSelect|Unassigned"
msgstr ""
+msgid "Using %{codeStart}needs%{codeEnd} allows jobs to run before their stage is reached, as soon as their individual dependencies are met, which speeds up your pipelines."
+msgstr ""
+
msgid "Using %{code_start}::%{code_end} denotes a %{link_start}scoped label set%{link_end}"
msgstr ""
@@ -24555,9 +25203,6 @@ msgstr ""
msgid "Versions"
msgstr ""
-msgid "Very helpful"
-msgstr ""
-
msgid "View Documentation"
msgstr ""
@@ -24706,6 +25351,9 @@ msgstr ""
msgid "VisibilityLevel|Unknown"
msgstr ""
+msgid "Visit settings page"
+msgstr ""
+
msgid "VisualReviewApp|%{stepStart}Step 1%{stepEnd}. Copy the following script:"
msgstr ""
@@ -24799,6 +25447,9 @@ msgstr ""
msgid "VulnerabilityManagement|Something went wrong while trying to delete the comment. Please try again later."
msgstr ""
+msgid "VulnerabilityManagement|Something went wrong while trying to refresh the vulnerability. Please try again later."
+msgstr ""
+
msgid "VulnerabilityManagement|Something went wrong while trying to retrieve the vulnerability history. Please try again later."
msgstr ""
@@ -24841,6 +25492,9 @@ msgstr ""
msgid "Vulnerability|Class"
msgstr ""
+msgid "Vulnerability|Crash Address"
+msgstr ""
+
msgid "Vulnerability|Description"
msgstr ""
@@ -24916,9 +25570,6 @@ msgstr ""
msgid "We couldn't reach the Prometheus server. Either the server no longer exists or the configuration details need updating."
msgstr ""
-msgid "We created a short guided tour that will help you learn the basics of GitLab and how it will help you be better at your job. It should only take a couple of minutes. You will be guided by two types of helpers, best recognized by their color."
-msgstr ""
-
msgid "We detected potential spam in the %{humanized_resource_name}. Please solve the reCAPTCHA to proceed."
msgstr ""
@@ -24952,6 +25603,9 @@ msgstr ""
msgid "We've found no vulnerabilities"
msgstr ""
+msgid "Web Application Firewall"
+msgstr ""
+
msgid "Web IDE"
msgstr ""
@@ -25012,9 +25666,6 @@ msgstr ""
msgid "Welcome to GitLab.com<br>@%{name}!"
msgstr ""
-msgid "Welcome to the Guided GitLab Tour"
-msgstr ""
-
msgid "Welcome to the guided GitLab tour"
msgstr ""
@@ -25062,9 +25713,6 @@ msgstr ""
msgid "While it's rare to have no vulnerabilities, it can happen. In any event, we ask that you please double check your settings to make sure you've set up your dashboard correctly."
msgstr ""
-msgid "White helpers give contextual information."
-msgstr ""
-
msgid "Who can be an approver?"
msgstr ""
@@ -25110,12 +25758,18 @@ msgstr ""
msgid "WikiEmptyIssueMessage|Suggest wiki improvement"
msgstr ""
+msgid "WikiEmptyIssueMessage|You must be a group member in order to add wiki pages. If you have suggestions for how to improve the wiki for this group, consider opening an issue in the %{issues_link}."
+msgstr ""
+
msgid "WikiEmptyIssueMessage|You must be a project member in order to add wiki pages. If you have suggestions for how to improve the wiki for this project, consider opening an issue in the %{issues_link}."
msgstr ""
msgid "WikiEmptyIssueMessage|issue tracker"
msgstr ""
+msgid "WikiEmpty|A wiki is where you can store all the details about your group. This can include why you've created it, its principles, how to use it, and so on."
+msgstr ""
+
msgid "WikiEmpty|A wiki is where you can store all the details about your project. This can include why you've created it, its principles, how to use it, and so on."
msgstr ""
@@ -25125,12 +25779,21 @@ msgstr ""
msgid "WikiEmpty|Suggest wiki improvement"
msgstr ""
+msgid "WikiEmpty|The wiki lets you write documentation for your group"
+msgstr ""
+
msgid "WikiEmpty|The wiki lets you write documentation for your project"
msgstr ""
+msgid "WikiEmpty|This group has no wiki pages"
+msgstr ""
+
msgid "WikiEmpty|This project has no wiki pages"
msgstr ""
+msgid "WikiEmpty|You must be a group member in order to add wiki pages."
+msgstr ""
+
msgid "WikiEmpty|You must be a project member in order to add wiki pages."
msgstr ""
@@ -25407,13 +26070,13 @@ msgstr ""
msgid "You can now export your security dashboard to a CSV report."
msgstr ""
-msgid "You can now submit a merge request to get this change into the original branch."
+msgid "You can now manage alert endpoint configuration in the Alerts section on the Operations settings page. Fields on this page have been deprecated."
msgstr ""
-msgid "You can now submit a merge request to get this change into the original project."
+msgid "You can now submit a merge request to get this change into the original branch."
msgstr ""
-msgid "You can only add files when you are on a branch"
+msgid "You can now submit a merge request to get this change into the original project."
msgstr ""
msgid "You can only edit files when you are on a branch"
@@ -25635,7 +26298,7 @@ msgstr ""
msgid "You need to upload a Google Takeout archive."
msgstr ""
-msgid "You reached %{usage_in_percent} of %{namespace_name}'s capacity (%{used_storage} of %{storage_limit})"
+msgid "You reached %{usage_in_percent} of %{namespace_name}'s storage capacity (%{used_storage} of %{storage_limit})"
msgstr ""
msgid "You tried to fork %{link_to_the_project} but it failed for the following reason:"
@@ -25938,13 +26601,13 @@ msgstr ""
msgid "Your subscription expired!"
msgstr ""
-msgid "Your subscription has been downgraded"
+msgid "Your subscription has been downgraded."
msgstr ""
-msgid "Your subscription will automatically renew in %{remaining_days}"
+msgid "Your subscription will automatically renew in %{remaining_days}."
msgstr ""
-msgid "Your subscription will expire in %{remaining_days}"
+msgid "Your subscription will expire in %{remaining_days}."
msgstr ""
msgid "Zoom meeting added"
@@ -26024,9 +26687,6 @@ msgstr ""
msgid "branch name"
msgstr ""
-msgid "build pipeline reference mismatch"
-msgstr ""
-
msgid "by"
msgstr ""
@@ -26045,6 +26705,9 @@ msgstr ""
msgid "cannot block others"
msgstr ""
+msgid "cannot contain HTML/XML tags, including any word between angle brackets (<,>)."
+msgstr ""
+
msgid "cannot include leading slash or directory traversal."
msgstr ""
@@ -26075,55 +26738,6 @@ msgstr ""
msgid "ciReport|%{remainingPackagesCount} more"
msgstr ""
-msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability"
-msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability for the source branch only"
-msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities for the source branch only"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerability"
-msgid_plural "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerabilities"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new vulnerability"
-msgid_plural "ciReport|%{reportType} %{status} detected %{newCount} new vulnerabilities"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities for the source branch only"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{fixedCount} fixed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} vulnerability for the source branch only"
-msgid_plural "ciReport|%{reportType} %{status} detected %{newCount} vulnerabilities for the source branch only"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "ciReport|%{reportType} %{status} detected no new vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected no vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected no vulnerabilities for the source branch only"
-msgstr ""
-
msgid "ciReport|%{reportType} is loading"
msgstr ""
@@ -26142,7 +26756,7 @@ msgstr ""
msgid "ciReport|All projects"
msgstr ""
-msgid "ciReport|All report types"
+msgid "ciReport|All scanner types"
msgstr ""
msgid "ciReport|All severities"
@@ -26166,6 +26780,9 @@ msgstr ""
msgid "ciReport|Container scanning detects known vulnerabilities in your docker images."
msgstr ""
+msgid "ciReport|Coverage Fuzzing"
+msgstr ""
+
msgid "ciReport|Create a merge request to implement this solution, or download and apply the patch manually."
msgstr ""
@@ -26324,6 +26941,9 @@ msgstr ""
msgid "customize"
msgstr ""
+msgid "data"
+msgstr ""
+
msgid "date must not be after 9999-12-31"
msgstr ""
@@ -26394,6 +27014,9 @@ msgstr ""
msgid "entries cannot contain HTML tags"
msgstr ""
+msgid "epic"
+msgstr ""
+
msgid "error"
msgstr ""
@@ -26415,9 +27038,6 @@ msgstr ""
msgid "expires on %{timebox_due_date}"
msgstr ""
-msgid "external_url"
-msgstr ""
-
msgid "failed"
msgstr ""
@@ -26453,9 +27073,6 @@ msgstr ""
msgid "from"
msgstr ""
-msgid "geo_node_name"
-msgstr ""
-
msgid "group"
msgstr ""
@@ -26477,9 +27094,6 @@ msgstr ""
msgid "https://your-bitbucket-server"
msgstr ""
-msgid "image"
-msgstr ""
-
msgid "image diff"
msgstr ""
@@ -26623,6 +27237,9 @@ msgstr ""
msgid "load it anyway"
msgstr ""
+msgid "loading"
+msgstr ""
+
msgid "locked by %{path_lock_user_name} %{created_at}"
msgstr ""
@@ -26751,9 +27368,6 @@ msgstr ""
msgid "mrWidget|Deployment statistics are not available currently"
msgstr ""
-msgid "mrWidget|Detect issues before deployment with a CI pipeline that continuously tests your code. We created a quick guide that will show you how to create one. Make your code more secure and more robust in just a minute."
-msgstr ""
-
msgid "mrWidget|Did not close"
msgstr ""
@@ -26916,6 +27530,9 @@ msgstr ""
msgid "mrWidget|To approve this merge request, please enter your password. This project requires all approvals to be authenticated."
msgstr ""
+msgid "mrWidget|Use %{linkStart}CI pipelines to test your code%{linkEnd}, simply add a GitLab CI configuration file to your project. It only takes a minute to make your code more secure and robust."
+msgstr ""
+
msgid "mrWidget|When this merge request is ready, remove the WIP: prefix from the title to allow it to be merged"
msgstr ""
@@ -26952,6 +27569,9 @@ msgstr ""
msgid "must be greater than start date"
msgstr ""
+msgid "must contain only valid frameworks"
+msgstr ""
+
msgid "my-awesome-group"
msgstr ""
@@ -26964,6 +27584,9 @@ msgstr ""
msgid "needs to be between 10 minutes and 1 month"
msgstr ""
+msgid "never"
+msgstr ""
+
msgid "never expires"
msgstr ""
@@ -26991,6 +27614,9 @@ msgstr ""
msgid "nounSeries|%{firstItem} and %{lastItem}"
msgstr ""
+msgid "nounSeries|%{item}"
+msgstr ""
+
msgid "nounSeries|%{item}, %{nextItem}"
msgstr ""
@@ -27127,6 +27753,9 @@ msgstr ""
msgid "revised"
msgstr ""
+msgid "satisfied"
+msgstr ""
+
msgid "score"
msgstr ""
@@ -27157,6 +27786,9 @@ msgstr ""
msgid "severity|Unknown"
msgstr ""
+msgid "should be an array of %{object_name} objects"
+msgstr ""
+
msgid "should be greater than or equal to %{access} inherited membership from group %{group_name}"
msgstr ""
@@ -27211,10 +27843,13 @@ msgstr ""
msgid "suggestPipeline|2/2: Commit your changes"
msgstr ""
-msgid "suggestPipeline|Commit the changes and your pipeline will automatically run for the first time."
+msgid "suggestPipeline|Choose %{boldStart}Code Quality%{boldEnd} to add a pipeline that tests the quality of your code."
msgstr ""
-msgid "suggestPipeline|We recommend the %{boldStart}Code Quality%{boldEnd} template, which will add a report widget to your Merge Requests. This way you’ll learn about code quality degradations much sooner. %{footerStart} Goodbye technical debt! %{footerEnd}"
+msgid "suggestPipeline|The template is ready! You can now commit it to create your first pipeline."
+msgstr ""
+
+msgid "suggestPipeline|We’re adding a GitLab CI configuration file to add a pipeline to the project. You could create it manually, but we recommend that you start with a GitLab template that works out of the box."
msgstr ""
msgid "syntax is correct"
@@ -27274,6 +27909,9 @@ msgstr ""
msgid "uses Kubernetes clusters to deploy your code!"
msgstr ""
+msgid "v%{version} published %{timeAgo}"
+msgstr ""
+
msgid "verify ownership"
msgstr ""
diff --git a/locale/eo/gitlab.po b/locale/eo/gitlab.po
index 56a72d10892..d04b4b8b878 100644
--- a/locale/eo/gitlab.po
+++ b/locale/eo/gitlab.po
@@ -2,7 +2,7 @@ msgid ""
msgstr ""
"Project-Id-Version: gitlab-ee\n"
"Report-Msgid-Bugs-To: \n"
-"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Last-Translator: \n"
"Language-Team: Esperanto\n"
"Language: eo_UY\n"
"MIME-Version: 1.0\n"
@@ -10,9 +10,11 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Crowdin-Project: gitlab-ee\n"
+"X-Crowdin-Project-ID: 288872\n"
"X-Crowdin-Language: eo\n"
"X-Crowdin-File: /master/locale/gitlab.pot\n"
-"PO-Revision-Date: 2020-06-08 15:12\n"
+"X-Crowdin-File-ID: 6\n"
+"PO-Revision-Date: 2020-07-02 01:21\n"
msgid " %{start} to %{end}"
msgstr ""
@@ -197,6 +199,11 @@ msgid_plural "%d metrics"
msgstr[0] ""
msgstr[1] ""
+msgid "%d milestone"
+msgid_plural "%d milestones"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "%d minute"
msgid_plural "%d minutes"
msgstr[0] ""
@@ -242,11 +249,6 @@ msgid_plural "%d unresolved threads"
msgstr[0] ""
msgstr[1] ""
-msgid "%d url scanned"
-msgid_plural "%d urls scanned"
-msgstr[0] ""
-msgstr[1] ""
-
msgid "%d vulnerability"
msgid_plural "%d vulnerabilities"
msgstr[0] ""
@@ -324,9 +326,15 @@ msgstr[1] ""
msgid "%{count} related %{pluralized_subject}: %{links}"
msgstr ""
+msgid "%{dashboard_path} could not be found."
+msgstr ""
+
msgid "%{days} days until tags are automatically removed"
msgstr ""
+msgid "%{deployLinkStart}Use a template to deploy to ECS%{deployLinkEnd}, or use a docker image to %{commandsLinkStart}run AWS commands in GitLab CI/CD%{commandsLinkEnd}."
+msgstr ""
+
msgid "%{description}- Sentry event: %{errorUrl}- First seen: %{firstSeen}- Last seen: %{lastSeen} %{countLabel}: %{count}%{userCountLabel}: %{userCount}"
msgstr ""
@@ -369,12 +377,18 @@ msgstr ""
msgid "%{host} sign-in from new location"
msgstr ""
-msgid "%{icon}You are about to add %{usersTag} people to the discussion. Proceed with caution."
+msgid "%{icon}You are about to add %{usersTag} people to the discussion. They will all receive a notification."
+msgstr ""
+
+msgid "%{integrations_link_start}Integrations%{link_end} enable you to make third-party applications part of your GitLab workflow. If the available integrations don't meet your needs, consider using a %{webhooks_link_start}webhook%{link_end}."
msgstr ""
msgid "%{issuableType} will be removed! Are you sure?"
msgstr ""
+msgid "%{issuesCount} issues in this group"
+msgstr ""
+
msgid "%{issuesSize} issues"
msgstr ""
@@ -384,7 +398,7 @@ msgstr ""
msgid "%{labelStart}Class:%{labelEnd} %{class}"
msgstr ""
-msgid "%{labelStart}Confidence:%{labelEnd} %{confidence}"
+msgid "%{labelStart}Evidence:%{labelEnd} %{evidence}"
msgstr ""
msgid "%{labelStart}File:%{labelEnd} %{file}"
@@ -402,6 +416,9 @@ msgstr ""
msgid "%{labelStart}Report Type:%{labelEnd} %{reportType}"
msgstr ""
+msgid "%{labelStart}Scanner:%{labelEnd} %{scanner}"
+msgstr ""
+
msgid "%{labelStart}Severity:%{labelEnd} %{severity}"
msgstr ""
@@ -426,9 +443,6 @@ msgstr ""
msgid "%{link_start}Read more%{link_end} about role permissions"
msgstr ""
-msgid "%{link} can be used for binding events when something is happening within the project."
-msgstr ""
-
msgid "%{listToShow}, and %{awardsListLength} more."
msgstr ""
@@ -462,6 +476,9 @@ msgstr ""
msgid "%{name} found %{resultsString}"
msgstr ""
+msgid "%{name} is already being used for another emoji"
+msgstr ""
+
msgid "%{name} is scheduled for %{action}"
msgstr ""
@@ -509,6 +526,9 @@ msgstr ""
msgid "%{primary} (%{secondary})"
msgstr ""
+msgid "%{ref} cannot be added: %{error}"
+msgstr ""
+
msgid "%{releases} release"
msgid_plural "%{releases} releases"
msgstr[0] ""
@@ -517,13 +537,37 @@ msgstr[1] ""
msgid "%{remaining_approvals} left"
msgstr ""
-msgid "%{retryButtonStart}Try again%{retryButtonEnd} or %{newFileButtonStart}attach a new file%{newFileButtonEnd}"
+msgid "%{reportType} %{status} detected %{critical} critical and %{high} high severity vulnerabilities out of %{total}."
+msgstr ""
+
+msgid "%{reportType} %{status} detected %{critical} critical and %{high} high severity vulnerabilities."
+msgstr ""
+
+msgid "%{reportType} %{status} detected %{critical} critical severity vulnerabilities out of %{total}."
+msgstr ""
+
+msgid "%{reportType} %{status} detected %{critical} critical severity vulnerability."
+msgid_plural "%{reportType} %{status} detected %{critical} critical severity vulnerabilities."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{reportType} %{status} detected %{high} high severity vulnerabilities out of %{total}."
msgstr ""
-msgid "%{screenreaderOnlyStart}Keyboard shorcuts%{screenreaderOnlyEnd} Disabled"
+msgid "%{reportType} %{status} detected %{high} high severity vulnerability."
+msgid_plural "%{reportType} %{status} detected %{high} high severity vulnerabilities."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{reportType} %{status} detected %{other} vulnerability."
+msgid_plural "%{reportType} %{status} detected %{other} vulnerabilities."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{reportType} %{status} detected no new vulnerabilities."
msgstr ""
-msgid "%{screenreaderOnlyStart}Keyboard shorcuts%{screenreaderOnlyEnd} Enabled"
+msgid "%{retryButtonStart}Try again%{retryButtonEnd} or %{newFileButtonStart}attach a new file%{newFileButtonEnd}"
msgstr ""
msgid "%{service_title} %{message}."
@@ -620,6 +664,12 @@ msgstr ""
msgid "%{token}..."
msgstr ""
+msgid "%{totalCpu} (%{freeSpacePercentage}%{percentSymbol} free)"
+msgstr ""
+
+msgid "%{totalMemory} (%{freeSpacePercentage}%{percentSymbol} free)"
+msgstr ""
+
msgid "%{totalWeight} total weight"
msgstr ""
@@ -650,6 +700,12 @@ msgstr ""
msgid "%{verb} %{time_spent_value} spent time."
msgstr ""
+msgid "%{webhooks_link_start}%{webhook_type}%{link_end} enable you to send notifications to web applications in response to events in a group or project."
+msgstr ""
+
+msgid "%{webhooks_link_start}%{webhook_type}%{link_end} enable you to send notifications to web applications in response to events in a group or project. We recommend using an %{integrations_link_start}integration%{link_end} in preference to a webhook."
+msgstr ""
+
msgid "'%{level}' is not a valid visibility level"
msgstr ""
@@ -676,9 +732,6 @@ msgstr ""
msgid "(No changes)"
msgstr ""
-msgid "(Show all)"
-msgstr ""
-
msgid "(check progress)"
msgstr ""
@@ -745,6 +798,9 @@ msgstr ""
msgid "0 for unlimited"
msgstr ""
+msgid "0 for unlimited, only effective with remote storage enabled."
+msgstr ""
+
msgid "1 %{type} addition"
msgid_plural "%{count} %{type} additions"
msgstr[0] ""
@@ -892,6 +948,12 @@ msgstr ""
msgid "<code>\"johnsmith@example.com\": \"johnsmith@example.com\"</code> will add \"By <a href=\"#\">johnsmith@example.com</a>\" to all issues and comments originally created by johnsmith@example.com. By default, the email address or username is masked to ensure the user's privacy. Use this option if you want to show the full email address."
msgstr ""
+msgid "<code>Masked</code> to prevent the values from being displayed in job logs (must match certain regexp requirements)."
+msgstr ""
+
+msgid "<code>Protected</code> to expose them to protected branches or tags only."
+msgstr ""
+
msgid "<namespace / project>"
msgstr ""
@@ -919,9 +981,6 @@ msgstr ""
msgid "A .NET Core console application template, customizable for any .NET Core project"
msgstr ""
-msgid "A DAG must have two dependent jobs to be visualized on this tab."
-msgstr ""
-
msgid "A GitBook site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
msgstr ""
@@ -952,6 +1011,12 @@ msgstr ""
msgid "A deleted user"
msgstr ""
+msgid "A file has been changed."
+msgstr ""
+
+msgid "A file was not found."
+msgstr ""
+
msgid "A file with '%{file_name}' already exists in %{branch} branch"
msgstr ""
@@ -1018,7 +1083,7 @@ msgstr ""
msgid "A subscription will trigger a new pipeline on the default branch of this project when a pipeline successfully completes for a new tag on the %{default_branch_docs} of the subscribed project."
msgstr ""
-msgid "A terraform report was generated in your pipelines."
+msgid "A suggestion is not applicable."
msgstr ""
msgid "A user with write access to the source branch selected this option"
@@ -1189,6 +1254,9 @@ msgstr ""
msgid "Action to take when receiving an alert."
msgstr ""
+msgid "Actions"
+msgstr ""
+
msgid "Activate"
msgstr ""
@@ -1245,6 +1313,9 @@ msgstr ""
msgid "Add LICENSE"
msgstr ""
+msgid "Add New Node"
+msgstr ""
+
msgid "Add README"
msgstr ""
@@ -1320,6 +1391,9 @@ msgstr ""
msgid "Add approval rule"
msgstr ""
+msgid "Add approvers"
+msgstr ""
+
msgid "Add bold text"
msgstr ""
@@ -1377,6 +1451,9 @@ msgstr ""
msgid "Add strikethrough text"
msgstr ""
+msgid "Add suggestion to batch"
+msgstr ""
+
msgid "Add system hook"
msgstr ""
@@ -1476,6 +1553,9 @@ msgstr ""
msgid "Admin Note"
msgstr ""
+msgid "Admin Notifications"
+msgstr ""
+
msgid "Admin Overview"
msgstr ""
@@ -1587,6 +1667,9 @@ msgstr ""
msgid "AdminSettings|Integrations configured here will automatically apply to all projects on this instance."
msgstr ""
+msgid "AdminSettings|Moved to integrations"
+msgstr ""
+
msgid "AdminSettings|No required pipeline"
msgstr ""
@@ -1817,12 +1900,18 @@ msgid_plural "Alerts"
msgstr[0] ""
msgstr[1] ""
+msgid "AlertManagement| assign yourself"
+msgstr ""
+
msgid "AlertManagement|Acknowledged"
msgstr ""
msgid "AlertManagement|Alert"
msgstr ""
+msgid "AlertManagement|Alert assignee(s): %{assignees}"
+msgstr ""
+
msgid "AlertManagement|Alert detail"
msgstr ""
@@ -1838,9 +1927,15 @@ msgstr ""
msgid "AlertManagement|All alerts"
msgstr ""
+msgid "AlertManagement|Assign To"
+msgstr ""
+
msgid "AlertManagement|Assign status"
msgstr ""
+msgid "AlertManagement|Assignee"
+msgstr ""
+
msgid "AlertManagement|Assignees"
msgstr ""
@@ -1859,9 +1954,6 @@ msgstr ""
msgid "AlertManagement|Edit"
msgstr ""
-msgid "AlertManagement|End time"
-msgstr ""
-
msgid "AlertManagement|Events"
msgstr ""
@@ -1880,10 +1972,13 @@ msgstr ""
msgid "AlertManagement|More information"
msgstr ""
+msgid "AlertManagement|No Matching Results"
+msgstr ""
+
msgid "AlertManagement|No alert data to display."
msgstr ""
-msgid "AlertManagement|No alerts available to display. If you think you're seeing this message in error, refresh the page."
+msgid "AlertManagement|No alerts available to display. See %{linkStart}enabling alert management%{linkEnd} for more information on adding alerts to the list."
msgstr ""
msgid "AlertManagement|No alerts to display."
@@ -1892,6 +1987,9 @@ msgstr ""
msgid "AlertManagement|None"
msgstr ""
+msgid "AlertManagement|None -"
+msgstr ""
+
msgid "AlertManagement|Open"
msgstr ""
@@ -1928,9 +2026,18 @@ msgstr ""
msgid "AlertManagement|There was an error displaying the alerts. Confirm your endpoint's configuration details to ensure alerts appear."
msgstr ""
+msgid "AlertManagement|There was an error while updating the assignee(s) list. Please try again."
+msgstr ""
+
+msgid "AlertManagement|There was an error while updating the assignee(s) of the alert. Please try again."
+msgstr ""
+
msgid "AlertManagement|There was an error while updating the status of the alert. Please try again."
msgstr ""
+msgid "AlertManagement|This assignee cannot be assigned to this alert."
+msgstr ""
+
msgid "AlertManagement|Tool"
msgstr ""
@@ -2078,6 +2185,9 @@ msgstr ""
msgid "Allow users to request access (if visibility is public or internal)"
msgstr ""
+msgid "Allowed Geo IP"
+msgstr ""
+
msgid "Allowed email domain restriction only permitted for top-level groups"
msgstr ""
@@ -2111,6 +2221,9 @@ msgstr ""
msgid "Amazon Web Services"
msgstr ""
+msgid "Amazon Web Services Logo"
+msgstr ""
+
msgid "Amazon authentication is not %{link_start}correctly configured%{link_end}. Ask your GitLab administrator if you want to use this service."
msgstr ""
@@ -2168,10 +2281,16 @@ msgstr ""
msgid "An error occurred when updating the issue weight"
msgstr ""
+msgid "An error occurred while acknowledging the notification. Refresh the page and try again."
+msgstr ""
+
+msgid "An error occurred while adding approvers"
+msgstr ""
+
msgid "An error occurred while adding formatted title for epic"
msgstr ""
-msgid "An error occurred while checking group path"
+msgid "An error occurred while checking group path. Please refresh and try again."
msgstr ""
msgid "An error occurred while committing your changes."
@@ -2315,7 +2434,7 @@ msgstr ""
msgid "An error occurred while loading milestones"
msgstr ""
-msgid "An error occurred while loading terraform report"
+msgid "An error occurred while loading project creation UI"
msgstr ""
msgid "An error occurred while loading the data. Please try again."
@@ -2369,6 +2488,9 @@ msgstr ""
msgid "An error occurred while rendering preview broadcast message"
msgstr ""
+msgid "An error occurred while rendering the editor"
+msgstr ""
+
msgid "An error occurred while reordering issues."
msgstr ""
@@ -2465,7 +2587,7 @@ msgstr ""
msgid "Analyze your dependencies for known vulnerabilities."
msgstr ""
-msgid "Analyze your source code and git history for secrets"
+msgid "Analyze your source code and git history for secrets."
msgstr ""
msgid "Analyze your source code for known vulnerabilities."
@@ -2579,12 +2701,18 @@ msgstr ""
msgid "Apply suggestion"
msgstr ""
+msgid "Apply suggestions"
+msgstr ""
+
msgid "Apply template"
msgstr ""
msgid "Apply this approval rule to any branch or a specific protected branch."
msgstr ""
+msgid "Applying"
+msgstr ""
+
msgid "Applying a template will replace the existing issue description. Any changes you have made will be lost."
msgstr ""
@@ -2597,12 +2725,18 @@ msgstr ""
msgid "Applying multiple commands"
msgstr ""
-msgid "Applying suggestion"
+msgid "Applying suggestion..."
+msgstr ""
+
+msgid "Applying suggestions..."
msgstr ""
msgid "Approval rules"
msgstr ""
+msgid "Approval rules reset to project defaults"
+msgstr ""
+
msgid "ApprovalRuleRemove|%d member"
msgid_plural "ApprovalRuleRemove|%d members"
msgstr[0] ""
@@ -2656,6 +2790,9 @@ msgstr ""
msgid "Approved"
msgstr ""
+msgid "Approved MRs"
+msgstr ""
+
msgid "Approved by: "
msgstr ""
@@ -2869,6 +3006,9 @@ msgstr ""
msgid "Assign"
msgstr ""
+msgid "Assign Iteration"
+msgstr ""
+
msgid "Assign custom color like #FF0000"
msgstr ""
@@ -2887,6 +3027,9 @@ msgstr ""
msgid "Assign to"
msgstr ""
+msgid "Assign to commenting user"
+msgstr ""
+
msgid "Assign yourself to these issues"
msgstr ""
@@ -2913,6 +3056,9 @@ msgid_plural "%d Assignees"
msgstr[0] ""
msgstr[1] ""
+msgid "Assignee has no permissions"
+msgstr ""
+
msgid "Assignee lists not available with your current license"
msgstr ""
@@ -2922,6 +3068,9 @@ msgstr ""
msgid "Assignee(s)"
msgstr ""
+msgid "Assignees"
+msgstr ""
+
msgid "Assigns %{assignee_users_sentence}."
msgstr ""
@@ -2957,6 +3106,9 @@ msgstr ""
msgid "Audit Events is a way to keep track of important events that happened in GitLab."
msgstr ""
+msgid "Audit Log"
+msgstr ""
+
msgid "AuditEvents|(removed)"
msgstr ""
@@ -2993,6 +3145,9 @@ msgstr ""
msgid "AuditLogs|IP Address"
msgstr ""
+msgid "AuditLogs|Member Events"
+msgstr ""
+
msgid "AuditLogs|No matching %{type} found."
msgstr ""
@@ -3275,9 +3430,6 @@ msgstr ""
msgid "Badges|e.g. %{exampleUrl}"
msgstr ""
-msgid "Badge|New"
-msgstr ""
-
msgid "Balsamiq file could not be loaded."
msgstr ""
@@ -3326,6 +3478,12 @@ msgstr ""
msgid "Below you will find all the groups that are public."
msgstr ""
+msgid "Beta"
+msgstr ""
+
+msgid "Bi-weekly code coverage"
+msgstr ""
+
msgid "Billing"
msgstr ""
@@ -3386,9 +3544,15 @@ msgstr ""
msgid "Bitbucket Server Import"
msgstr ""
+msgid "Bitbucket Server import"
+msgstr ""
+
msgid "Bitbucket import"
msgstr ""
+msgid "Blame"
+msgstr ""
+
msgid "Blocked"
msgstr ""
@@ -3401,9 +3565,6 @@ msgstr ""
msgid "Blog"
msgstr ""
-msgid "Blue helpers indicate an action to be taken."
-msgstr ""
-
msgid "Board name"
msgstr ""
@@ -3602,6 +3763,9 @@ msgstr ""
msgid "Broadcast Message was successfully updated."
msgstr ""
+msgid "Broadcast Messages"
+msgstr ""
+
msgid "Browse Directory"
msgstr "Foliumi dosierujon"
@@ -3656,6 +3820,9 @@ msgstr ""
msgid "By %{user_name}"
msgstr ""
+msgid "By URL"
+msgstr ""
+
msgid "By default GitLab sends emails in HTML and plain text formats so mail clients can choose what format to use. Disable this option if you only want to send emails in plain text format."
msgstr ""
@@ -3761,6 +3928,9 @@ msgstr ""
msgid "Can override approvers and approvals required per merge request"
msgstr ""
+msgid "Can't apply as this line has changed or the suggestion already matches its content."
+msgstr ""
+
msgid "Can't create snippet: %{err}"
msgstr ""
@@ -3947,9 +4117,6 @@ msgstr ""
msgid "Changes are still tracked. Useful for cluster/index migrations."
msgstr ""
-msgid "Changes are unknown"
-msgstr ""
-
msgid "Changes suppressed. Click to show."
msgstr ""
@@ -4256,15 +4423,9 @@ msgstr ""
msgid "Choose what content you want to see on a group’s overview page"
msgstr ""
-msgid "Choose which groups you wish to synchronize to this secondary node"
-msgstr ""
-
msgid "Choose which repositories you want to connect and run CI/CD pipelines."
msgstr ""
-msgid "Choose which shards you wish to synchronize to this secondary node"
-msgstr ""
-
msgid "Choose your framework"
msgstr ""
@@ -4562,9 +4723,15 @@ msgstr ""
msgid "Cluster does not exist"
msgstr ""
+msgid "Cluster is required for Stages::ClusterEndpointInserter"
+msgstr ""
+
msgid "Cluster level"
msgstr ""
+msgid "Cluster type must be specificed for Stages::ClusterEndpointInserter"
+msgstr ""
+
msgid "ClusterIntegration| %{custom_domain_start}More information%{custom_domain_end}."
msgstr ""
@@ -4676,7 +4843,7 @@ msgstr ""
msgid "ClusterIntegration|Cert-Manager"
msgstr ""
-msgid "ClusterIntegration|Cert-Manager is a native Kubernetes certificate management controller that helps with issuing certificates. Installing Cert-Manager on your cluster will issue a certificate by %{letsEncrypt} and ensure that certificates are valid and up-to-date."
+msgid "ClusterIntegration|Cert-Manager is a native Kubernetes certificate management controller that helps with issuing certificates. Installing Cert-Manager on your cluster will issue a certificate by %{linkStart}Let's Encrypt%{linkEnd} and ensure that certificates are valid and up-to-date."
msgstr ""
msgid "ClusterIntegration|Certificate Authority bundle (PEM format)"
@@ -4703,9 +4870,6 @@ msgstr ""
msgid "ClusterIntegration|Clear the local cache of namespace and service accounts. This is necessary if your integration has become out of sync. The cache is repopulated during the next CI job that requires namespace and service accounts."
msgstr ""
-msgid "ClusterIntegration|Cloud Run"
-msgstr ""
-
msgid "ClusterIntegration|Cluster being created"
msgstr ""
@@ -4790,7 +4954,7 @@ msgstr ""
msgid "ClusterIntegration|Crossplane"
msgstr ""
-msgid "ClusterIntegration|Crossplane enables declarative provisioning of managed services from your cloud of choice using %{codeStart}kubectl%{codeEnd} or %{gitlabIntegrationLink}. Crossplane runs inside your Kubernetes cluster and supports secure connectivity and secrets management between app containers and the cloud services they depend on."
+msgid "ClusterIntegration|Crossplane enables declarative provisioning of managed services from your cloud of choice using %{codeStart}kubectl%{codeEnd} or %{linkStart}GitLab Integration%{linkEnd}. Crossplane runs inside your Kubernetes cluster and supports secure connectivity and secrets management between app containers and the cloud services they depend on."
msgstr ""
msgid "ClusterIntegration|Deletes all GitLab resources attached to this cluster during removal"
@@ -4874,9 +5038,6 @@ msgstr ""
msgid "ClusterIntegration|GitLab-managed cluster"
msgstr ""
-msgid "ClusterIntegration|Gitlab Integration"
-msgstr ""
-
msgid "ClusterIntegration|Global default"
msgstr ""
@@ -4922,7 +5083,7 @@ msgstr ""
msgid "ClusterIntegration|Ingress gives you a way to route requests to services based on the request host or path, centralizing a number of services into a single entrypoint."
msgstr ""
-msgid "ClusterIntegration|Installing Ingress may incur additional costs. Learn more about %{pricingLink}."
+msgid "ClusterIntegration|Installing Ingress may incur additional costs. Learn more about %{linkStart}pricing%{linkEnd}."
msgstr ""
msgid "ClusterIntegration|Instance cluster"
@@ -4937,7 +5098,7 @@ msgstr ""
msgid "ClusterIntegration|Issuer Email"
msgstr ""
-msgid "ClusterIntegration|Issuers represent a certificate authority. You must provide an email address for your Issuer. "
+msgid "ClusterIntegration|Issuers represent a certificate authority. You must provide an email address for your Issuer."
msgstr ""
msgid "ClusterIntegration|Jupyter Hostname"
@@ -5009,9 +5170,6 @@ msgstr ""
msgid "ClusterIntegration|Learn more about instance Kubernetes clusters"
msgstr ""
-msgid "ClusterIntegration|Let's Encrypt"
-msgstr ""
-
msgid "ClusterIntegration|Loading IAM Roles"
msgstr ""
@@ -5123,7 +5281,7 @@ msgstr ""
msgid "ClusterIntegration|Prometheus"
msgstr ""
-msgid "ClusterIntegration|Prometheus is an open-source monitoring system with %{gitlabIntegrationLink} to monitor deployed applications."
+msgid "ClusterIntegration|Prometheus is an open-source monitoring system with %{linkStart}GitLab Integration%{linkEnd} to monitor deployed applications."
msgstr ""
msgid "ClusterIntegration|Provider details"
@@ -5279,10 +5437,10 @@ msgstr ""
msgid "ClusterIntegration|Select zone to choose machine type"
msgstr ""
-msgid "ClusterIntegration|Send Cilium Logs"
+msgid "ClusterIntegration|Send Container Network Policies Logs"
msgstr ""
-msgid "ClusterIntegration|Send ModSecurity Logs"
+msgid "ClusterIntegration|Send Web Application Firewall Logs"
msgstr ""
msgid "ClusterIntegration|Service Token"
@@ -5450,15 +5608,12 @@ msgstr ""
msgid "ClusterIntegration|documentation"
msgstr ""
-msgid "ClusterIntegration|installed via %{installed_via}"
+msgid "ClusterIntegration|installed via %{linkStart}Cloud Run%{linkEnd}"
msgstr ""
msgid "ClusterIntegration|meets the requirements"
msgstr ""
-msgid "ClusterIntegration|pricing"
-msgstr ""
-
msgid "ClusterIntegration|sign up"
msgstr ""
@@ -5495,6 +5650,15 @@ msgstr ""
msgid "Code"
msgstr ""
+msgid "Code Coverage: %{coveragePercentage}%{percentSymbol}"
+msgstr ""
+
+msgid "Code Coverage| Empty code coverage data"
+msgstr ""
+
+msgid "Code Coverage|Couldn't fetch the code coverage data"
+msgstr ""
+
msgid "Code Owners"
msgstr ""
@@ -5549,7 +5713,7 @@ msgstr ""
msgid "Collapse approvers"
msgstr ""
-msgid "Collapse child epics"
+msgid "Collapse milestones"
msgstr ""
msgid "Collapse replies"
@@ -5567,6 +5731,9 @@ msgstr ""
msgid "Coming soon"
msgstr ""
+msgid "Comma-separated, e.g. '1.1.1.1, 2.2.2.0/24'"
+msgstr ""
+
msgid "Command"
msgstr ""
@@ -5737,6 +5904,12 @@ msgstr ""
msgid "Compliance framework (optional)"
msgstr ""
+msgid "Compliance frameworks"
+msgstr ""
+
+msgid "ComplianceDashboard|created by:"
+msgstr ""
+
msgid "ComplianceFramework|GDPR"
msgstr ""
@@ -5875,6 +6048,9 @@ msgstr ""
msgid "Connection timed out"
msgstr ""
+msgid "Connection timeout"
+msgstr ""
+
msgid "Contact sales to upgrade"
msgstr ""
@@ -5930,9 +6106,6 @@ msgstr ""
msgid "ContainerRegistry|CLI Commands"
msgstr ""
-msgid "ContainerRegistry|Compressed Size"
-msgstr ""
-
msgid "ContainerRegistry|Container Registry"
msgstr ""
@@ -5948,6 +6121,9 @@ msgstr ""
msgid "ContainerRegistry|Copy push command"
msgstr ""
+msgid "ContainerRegistry|Delete selected"
+msgstr ""
+
msgid "ContainerRegistry|Docker connection error"
msgstr ""
@@ -5981,16 +6157,16 @@ msgstr ""
msgid "ContainerRegistry|If you are not already logged in, you need to authenticate to the Container Registry by using your GitLab username and password. If you have %{twofaDocLinkStart}Two-Factor Authentication%{twofaDocLinkEnd} enabled, use a %{personalAccessTokensDocLinkStart}Personal Access Token%{personalAccessTokensDocLinkEnd} instead of a password."
msgstr ""
-msgid "ContainerRegistry|Image ID"
+msgid "ContainerRegistry|Image ID: %{imageId}"
msgstr ""
msgid "ContainerRegistry|Image Repositories"
msgstr ""
-msgid "ContainerRegistry|Keep and protect the images that matter most."
+msgid "ContainerRegistry|Image tags"
msgstr ""
-msgid "ContainerRegistry|Last Updated"
+msgid "ContainerRegistry|Keep and protect the images that matter most."
msgstr ""
msgid "ContainerRegistry|Login"
@@ -6005,6 +6181,9 @@ msgstr ""
msgid "ContainerRegistry|Please contact your administrator."
msgstr ""
+msgid "ContainerRegistry|Published %{timeInfo}"
+msgstr ""
+
msgid "ContainerRegistry|Push an image"
msgstr ""
@@ -6020,9 +6199,6 @@ msgstr ""
msgid "ContainerRegistry|Remove repository"
msgstr ""
-msgid "ContainerRegistry|Remove selected tags"
-msgstr ""
-
msgid "ContainerRegistry|Remove tag"
msgid_plural "ContainerRegistry|Remove tags"
msgstr[0] ""
@@ -6052,9 +6228,6 @@ msgstr ""
msgid "ContainerRegistry|Sorry, your filter produced no results."
msgstr ""
-msgid "ContainerRegistry|Tag"
-msgstr ""
-
msgid "ContainerRegistry|Tag expiration policy"
msgstr ""
@@ -6199,21 +6372,6 @@ msgstr ""
msgid "Control the display of third party offers."
msgstr ""
-msgid "Control the maximum concurrency of LFS/attachment backfill for this secondary node"
-msgstr ""
-
-msgid "Control the maximum concurrency of container repository operations for this Geo node"
-msgstr ""
-
-msgid "Control the maximum concurrency of repository backfill for this secondary node"
-msgstr ""
-
-msgid "Control the maximum concurrency of verification operations for this Geo node"
-msgstr ""
-
-msgid "Control the minimum interval in days that a repository should be reverified for this primary node"
-msgstr ""
-
msgid "Cookie domain"
msgstr ""
@@ -6355,6 +6513,9 @@ msgstr ""
msgid "Could not find design."
msgstr ""
+msgid "Could not find iteration"
+msgstr ""
+
msgid "Could not remove the trigger."
msgstr ""
@@ -6391,6 +6552,12 @@ msgstr ""
msgid "Coverage"
msgstr ""
+msgid "Coverage Fuzzing"
+msgstr ""
+
+msgid "Crash State"
+msgstr ""
+
msgid "Create"
msgstr ""
@@ -6439,6 +6606,9 @@ msgstr ""
msgid "Create a personal access token on your account to pull or push via %{protocol}."
msgstr "Kreu propran atingoĵetonon en via konto por ebligi al vi eltiri kaj alpuÅi per %{protocol}."
+msgid "Create a project pre-populated with the necessary files to get you started quickly."
+msgstr ""
+
msgid "Create an account using:"
msgstr ""
@@ -6607,6 +6777,9 @@ msgstr ""
msgid "Creates branch '%{branch_name}' and a merge request to resolve this issue."
msgstr ""
+msgid "Creating"
+msgstr ""
+
msgid "Creating epic"
msgstr ""
@@ -6658,7 +6831,10 @@ msgstr ""
msgid "Current vulnerabilities count"
msgstr ""
-msgid "CurrentUser|Buy CI minutes"
+msgid "CurrentUser|Buy Pipeline minutes"
+msgstr ""
+
+msgid "CurrentUser|One of your groups is running out"
msgstr ""
msgid "CurrentUser|Profile"
@@ -6673,6 +6849,9 @@ msgstr ""
msgid "CurrentUser|Upgrade"
msgstr ""
+msgid "Custom Attributes"
+msgstr ""
+
msgid "Custom CI configuration path"
msgstr ""
@@ -6936,12 +7115,18 @@ msgstr ""
msgid "DAG"
msgstr ""
+msgid "DAG visualization requires at least 3 dependent jobs."
+msgstr ""
+
msgid "DNS"
msgstr ""
msgid "Dashboard"
msgstr ""
+msgid "Dashboard uid not found"
+msgstr ""
+
msgid "DashboardProjects|All"
msgstr ""
@@ -6966,6 +7151,9 @@ msgstr ""
msgid "Data is still calculating..."
msgstr ""
+msgid "Datasource name not found"
+msgstr ""
+
msgid "Date"
msgstr ""
@@ -7146,6 +7334,9 @@ msgstr ""
msgid "Delete this attachment"
msgstr ""
+msgid "Delete user list"
+msgstr ""
+
msgid "Delete variable"
msgstr ""
@@ -7462,6 +7653,9 @@ msgstr ""
msgid "Deploying to"
msgstr ""
+msgid "Deploying to AWS is easy with GitLab"
+msgstr ""
+
msgid "Deployment Frequency"
msgstr ""
@@ -7824,6 +8018,9 @@ msgstr ""
msgid "Dismissed on pipeline %{pipelineLink} at %{projectLink}"
msgstr ""
+msgid "Display alerts from all your monitoring tools directly within GitLab."
+msgstr ""
+
msgid "Display name"
msgstr ""
@@ -7881,9 +8078,6 @@ msgstr ""
msgid "Don't show again"
msgstr "Ne montru denove"
-msgid "Don't worry, you can access this tour by clicking on the help icon in the top right corner and choose <strong>Learn GitLab</strong>."
-msgstr ""
-
msgid "Done"
msgstr ""
@@ -8010,6 +8204,9 @@ msgstr "Redakti ĉenstablan planon %{id}"
msgid "Edit Release"
msgstr ""
+msgid "Edit Slack integration"
+msgstr ""
+
msgid "Edit Snippet"
msgstr ""
@@ -8049,6 +8246,9 @@ msgstr ""
msgid "Edit issues"
msgstr ""
+msgid "Edit iteration"
+msgstr ""
+
msgid "Edit public deploy key"
msgstr ""
@@ -8097,6 +8297,9 @@ msgstr ""
msgid "Email"
msgstr ""
+msgid "Email Notification"
+msgstr ""
+
msgid "Email address"
msgstr ""
@@ -8109,6 +8312,9 @@ msgstr ""
msgid "Email not verified. Please verify your email in Salesforce."
msgstr ""
+msgid "Email notification for unknown sign-ins"
+msgstr ""
+
msgid "Email patch"
msgstr ""
@@ -8340,6 +8546,9 @@ msgstr ""
msgid "Enforce DNS rebinding attack protection"
msgstr ""
+msgid "Enforce personal access token expiration"
+msgstr ""
+
msgid "Ensure connectivity is available from the GitLab server to the Prometheus server"
msgstr ""
@@ -8409,6 +8618,9 @@ msgstr ""
msgid "Enter the name of your application, and we'll return a unique %{type}."
msgstr ""
+msgid "Enter weights for storages for new repositories."
+msgstr ""
+
msgid "Enter your password to approve"
msgstr ""
@@ -8418,15 +8630,27 @@ msgstr ""
msgid "Environment does not have deployments"
msgstr ""
+msgid "Environment is required for Stages::MetricEndpointInserter"
+msgstr ""
+
+msgid "Environment is required for Stages::VariableEndpointInserter"
+msgstr ""
+
msgid "Environment scope"
msgstr ""
+msgid "Environment variables are applied to all project environments in this instance via the Runner. You can use environment variables for passwords, secret keys, etc. Make variables available to the running application by prepending the variable key with <code>K8S_SECRET_</code>. You can set variables to be:"
+msgstr ""
+
msgid "Environment variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. Additionally, they can be masked so they are hidden in job logs, though they must match certain regexp requirements to do so. You can use environment variables for passwords, secret keys, or whatever you want."
msgstr ""
msgid "Environment variables are configured by your administrator to be %{link_start}protected%{link_end} by default"
msgstr ""
+msgid "Environment variables on this GitLab instance are configured to be %{link_start}protected%{link_end} by default"
+msgstr ""
+
msgid "Environment:"
msgstr ""
@@ -8958,6 +9182,9 @@ msgstr ""
msgid "EventFilterBy|Filter by comments"
msgstr ""
+msgid "EventFilterBy|Filter by designs"
+msgstr ""
+
msgid "EventFilterBy|Filter by epic events"
msgstr ""
@@ -9090,15 +9317,15 @@ msgstr ""
msgid "Expand approvers"
msgstr ""
-msgid "Expand child epics"
-msgstr ""
-
msgid "Expand down"
msgstr ""
msgid "Expand dropdown"
msgstr ""
+msgid "Expand milestones"
+msgstr ""
+
msgid "Expand sidebar"
msgstr ""
@@ -9114,6 +9341,9 @@ msgstr ""
msgid "Expiration date"
msgstr ""
+msgid "Expiration not enforced"
+msgstr ""
+
msgid "Expiration policy for the Container Registry is a perfect solution for keeping the Registry space down while still enjoying the full power of GitLab CI/CD."
msgstr ""
@@ -9234,6 +9464,9 @@ msgstr ""
msgid "Failed Jobs"
msgstr ""
+msgid "Failed on"
+msgstr ""
+
msgid "Failed to add a Zoom meeting"
msgstr ""
@@ -9294,13 +9527,19 @@ msgstr ""
msgid "Failed to install."
msgstr ""
+msgid "Failed to load assignees. Please try again."
+msgstr ""
+
+msgid "Failed to load authors. Please try again."
+msgstr ""
+
msgid "Failed to load emoji list."
msgstr ""
msgid "Failed to load error details from Sentry."
msgstr ""
-msgid "Failed to load errors from Sentry. Error message: %{errorMessage}"
+msgid "Failed to load errors from Sentry."
msgstr ""
msgid "Failed to load group activity metrics. Please try again."
@@ -9381,6 +9620,9 @@ msgstr ""
msgid "Failed to set due date because the date format is invalid."
msgstr ""
+msgid "Failed to set iteration on this issue. Please try again."
+msgstr ""
+
msgid "Failed to signing using smartcard authentication"
msgstr ""
@@ -9603,13 +9845,10 @@ msgstr ""
msgid "FeatureFlags|Target environments"
msgstr ""
-msgid "FeatureFlags|There are no active feature flags"
-msgstr ""
-
-msgid "FeatureFlags|There are no inactive feature flags"
+msgid "FeatureFlags|There was an error fetching the feature flags."
msgstr ""
-msgid "FeatureFlags|There was an error fetching the feature flags."
+msgid "FeatureFlags|There was an error retrieving user lists"
msgstr ""
msgid "FeatureFlags|Try again in a few moments or contact your support team."
@@ -9621,9 +9860,18 @@ msgstr ""
msgid "FeatureFlag|Delete strategy"
msgstr ""
+msgid "FeatureFlag|List"
+msgstr ""
+
msgid "FeatureFlag|Percentage"
msgstr ""
+msgid "FeatureFlag|Select a user list"
+msgstr ""
+
+msgid "FeatureFlag|There are no configured user lists"
+msgstr ""
+
msgid "FeatureFlag|Type"
msgstr ""
@@ -9780,6 +10028,12 @@ msgstr ""
msgid "Filter..."
msgstr ""
+msgid "Find File"
+msgstr ""
+
+msgid "Find bugs in your code with coverage-guided fuzzing"
+msgstr ""
+
msgid "Find by path"
msgstr "Trovi per dosierindiko"
@@ -10044,6 +10298,9 @@ msgstr ""
msgid "Geo allows you to replicate your GitLab instance to other geographical locations."
msgstr ""
+msgid "Geo nodes are paused using a command run on the node"
+msgstr ""
+
msgid "GeoNodeStatusEvent|%{timeAgoStr} (%{pendingEvents} events)"
msgstr ""
@@ -10128,6 +10385,9 @@ msgstr ""
msgid "GeoNodes|Not checksummed"
msgstr ""
+msgid "GeoNodes|Package files"
+msgstr ""
+
msgid "GeoNodes|Pausing replication stops the sync process. Are you sure?"
msgstr ""
@@ -10146,6 +10406,9 @@ msgstr ""
msgid "GeoNodes|Replication slots"
msgstr ""
+msgid "GeoNodes|Replication status"
+msgstr ""
+
msgid "GeoNodes|Repositories"
msgstr ""
@@ -10356,6 +10619,9 @@ msgstr ""
msgid "Geo|Synchronization failed - %{error}"
msgstr ""
+msgid "Geo|Synchronization of %{itemTitle} is disabled."
+msgstr ""
+
msgid "Geo|The database is currently %{db_lag} behind the primary node."
msgstr ""
@@ -10668,6 +10934,9 @@ msgstr ""
msgid "Go to %{link_to_google_takeout}."
msgstr ""
+msgid "Go to <strong>Issues</strong> > <strong>Boards</strong> to access your personalized learning issue board."
+msgstr ""
+
msgid "Go to Pipelines"
msgstr ""
@@ -10767,9 +11036,6 @@ msgstr ""
msgid "Go to your snippets"
msgstr ""
-msgid "Golden Tanuki"
-msgstr ""
-
msgid "Google Cloud Platform"
msgstr ""
@@ -10791,6 +11057,9 @@ msgstr ""
msgid "Grafana URL"
msgstr ""
+msgid "Grafana response contains invalid json"
+msgstr ""
+
msgid "GrafanaIntegration|API Token"
msgstr ""
@@ -10839,9 +11108,6 @@ msgstr ""
msgid "Group %{group_name} was successfully created."
msgstr ""
-msgid "Group '%{group_name}' is being imported."
-msgstr ""
-
msgid "Group Audit Events"
msgstr ""
@@ -10878,9 +11144,6 @@ msgstr ""
msgid "Group by:"
msgstr ""
-msgid "Group could not be imported: %{errors}"
-msgstr ""
-
msgid "Group description"
msgstr ""
@@ -10908,9 +11171,15 @@ msgstr ""
msgid "Group has not been marked for deletion"
msgstr ""
+msgid "Group import could not be scheduled"
+msgstr ""
+
msgid "Group info:"
msgstr ""
+msgid "Group is required when cluster_type is :group"
+msgstr ""
+
msgid "Group maintainers can register group runners in the %{link}"
msgstr ""
@@ -10974,6 +11243,24 @@ msgstr ""
msgid "GroupActivyMetrics|Recent activity (last 90 days)"
msgstr ""
+msgid "GroupImport|Failed to import group."
+msgstr ""
+
+msgid "GroupImport|Group '%{group_name}' is being imported."
+msgstr ""
+
+msgid "GroupImport|Group could not be imported: %{errors}"
+msgstr ""
+
+msgid "GroupImport|Please wait while we import the group for you. Refresh at will."
+msgstr ""
+
+msgid "GroupImport|The group was successfully imported."
+msgstr ""
+
+msgid "GroupImport|Unable to process group import file"
+msgstr ""
+
msgid "GroupRoadmap|%{dateWord} – No end date"
msgstr ""
@@ -11241,6 +11528,9 @@ msgstr ""
msgid "Groups (%{groups})"
msgstr ""
+msgid "Groups and projects"
+msgstr ""
+
msgid "Groups can also be nested by creating %{subgroup_docs_link_start}subgroups%{subgroup_docs_link_end}."
msgstr ""
@@ -11286,6 +11576,33 @@ msgstr ""
msgid "GroupsEmptyState|You can manage your group member’s permissions and access to each project in the group."
msgstr ""
+msgid "GroupsNew|Contact an administrator to enable options for importing your group."
+msgstr ""
+
+msgid "GroupsNew|Create"
+msgstr ""
+
+msgid "GroupsNew|Create group"
+msgstr ""
+
+msgid "GroupsNew|GitLab group export"
+msgstr ""
+
+msgid "GroupsNew|Import"
+msgstr ""
+
+msgid "GroupsNew|Import group"
+msgstr ""
+
+msgid "GroupsNew|My Awesome Group"
+msgstr ""
+
+msgid "GroupsNew|No import options available"
+msgstr ""
+
+msgid "GroupsNew|To copy a GitLab group between installations, navigate to the group settings page for the original installation, generate an export file, and upload it here."
+msgstr ""
+
msgid "GroupsTree|Are you sure you want to leave the \"%{fullName}\" group?"
msgstr ""
@@ -11388,6 +11705,9 @@ msgstr ""
msgid "Helps reduce request volume for protected paths"
msgstr ""
+msgid "Here are all your projects in your group, including the one you just created. To start, let’s take a look at your personalized learning project which will help you learn about GitLab at your own pace."
+msgstr ""
+
msgid "Here you will find recent merge request activity"
msgstr ""
@@ -11402,6 +11722,9 @@ msgid_plural "Hide charts"
msgstr[0] ""
msgstr[1] ""
+msgid "Hide details"
+msgstr ""
+
msgid "Hide file browser"
msgstr ""
@@ -11411,6 +11734,9 @@ msgstr ""
msgid "Hide host keys manual input"
msgstr ""
+msgid "Hide list"
+msgstr ""
+
msgid "Hide marketing-related entries from help"
msgstr ""
@@ -11431,9 +11757,6 @@ msgstr[1] ""
msgid "Hide values"
msgstr ""
-msgid "Hiding all labels"
-msgstr ""
-
msgid "High or unknown vulnerabilities present"
msgstr ""
@@ -11518,6 +11841,9 @@ msgstr ""
msgid "ID:"
msgstr ""
+msgid "IDE"
+msgstr ""
+
msgid "IDE|Allow live previews of JavaScript projects in the Web IDE using CodeSandbox Live Preview."
msgstr ""
@@ -11608,10 +11934,10 @@ msgstr ""
msgid "If enabled"
msgstr ""
-msgid "If enabled, access to projects will be validated on an external service using their classification label."
+msgid "If enabled, GitLab will handle Object Storage replication using Geo. %{linkStart}More information%{linkEnd}"
msgstr ""
-msgid "If enabled, and if object storage is enabled, GitLab will handle Object Storage replication using Geo"
+msgid "If enabled, access to projects will be validated on an external service using their classification label."
msgstr ""
msgid "If there is no previous license or if the previous license has expired, some GitLab functionality will be blocked until a new, valid license is uploaded."
@@ -11973,6 +12299,9 @@ msgid_plural "Instances"
msgstr[0] ""
msgstr[1] ""
+msgid "Instance Configuration"
+msgstr ""
+
msgid "Instance Statistics visibility"
msgstr ""
@@ -11994,9 +12323,6 @@ msgstr ""
msgid "Integrations"
msgstr ""
-msgid "Integrations allow you to integrate GitLab with other applications"
-msgstr ""
-
msgid "Integrations|All details"
msgstr ""
@@ -12108,6 +12434,9 @@ msgstr ""
msgid "Invalid query"
msgstr ""
+msgid "Invalid repository bundle for snippet with id %{snippet_id}"
+msgstr ""
+
msgid "Invalid repository path"
msgstr ""
@@ -12126,6 +12455,9 @@ msgstr ""
msgid "Invalid two-factor code."
msgstr ""
+msgid "Invalid yaml"
+msgstr ""
+
msgid "Invitation"
msgstr ""
@@ -12345,15 +12677,24 @@ msgstr ""
msgid "It seems like the Dependency Scanning job ran successfully, but no dependencies have been detected in your project."
msgstr ""
+msgid "It seems that there is currently no available data for code coverage"
+msgstr ""
+
msgid "It's you"
msgstr ""
+msgid "Iteration"
+msgstr ""
+
msgid "Iteration changed to"
msgstr ""
msgid "Iteration removed"
msgstr ""
+msgid "Iteration updated"
+msgstr ""
+
msgid "Iterations"
msgstr ""
@@ -12384,12 +12725,18 @@ msgstr ""
msgid "January"
msgstr ""
+msgid "Jira Issues"
+msgstr ""
+
msgid "Jira import is already running."
msgstr ""
msgid "Jira integration not configured."
msgstr ""
+msgid "Jira project key is not configured"
+msgstr ""
+
msgid "Jira project: %{importProject}"
msgstr ""
@@ -12570,6 +12917,9 @@ msgstr ""
msgid "Keep divergent refs"
msgstr ""
+msgid "Kerberos access denied"
+msgstr ""
+
msgid "Key"
msgstr ""
@@ -12579,10 +12929,13 @@ msgstr ""
msgid "Key: %{key}"
msgstr ""
-msgid "Keyboard Shortcuts"
+msgid "Keyboard shortcuts"
msgstr ""
-msgid "Keyboard shortcuts"
+msgid "Keys"
+msgstr ""
+
+msgid "Ki"
msgstr ""
msgid "Kubernetes"
@@ -12624,6 +12977,9 @@ msgstr ""
msgid "LDAP"
msgstr ""
+msgid "LDAP Synchronization"
+msgstr ""
+
msgid "LDAP settings"
msgstr ""
@@ -12848,12 +13204,18 @@ msgstr ""
msgid "Learn more about custom project templates"
msgstr ""
+msgid "Learn more about deploying to AWS"
+msgstr ""
+
msgid "Learn more about deploying to a cluster"
msgstr ""
msgid "Learn more about group-level project templates"
msgstr ""
+msgid "Learn more about job dependencies"
+msgstr ""
+
msgid "Learn more about signing commits"
msgstr ""
@@ -12905,9 +13267,21 @@ msgstr ""
msgid "License History"
msgstr ""
+msgid "License ID:"
+msgstr ""
+
+msgid "License URL"
+msgstr ""
+
msgid "License-Check"
msgstr ""
+msgid "LicenseCompliance|%{docLinkStart}License Approvals%{docLinkEnd} are active"
+msgstr ""
+
+msgid "LicenseCompliance|%{docLinkStart}License Approvals%{docLinkEnd} are inactive"
+msgstr ""
+
msgid "LicenseCompliance|Add a license"
msgstr ""
@@ -12929,9 +13303,15 @@ msgstr ""
msgid "LicenseCompliance|Deny"
msgstr ""
+msgid "LicenseCompliance|Learn more about %{linkStart}License Approvals%{linkEnd}"
+msgstr ""
+
msgid "LicenseCompliance|License"
msgstr ""
+msgid "LicenseCompliance|License Approvals"
+msgstr ""
+
msgid "LicenseCompliance|License Compliance detected %d license and policy violation for the source branch only; approval required"
msgid_plural "LicenseCompliance|License Compliance detected %d licenses and policy violations for the source branch only; approval required"
msgstr[0] ""
@@ -13152,6 +13532,9 @@ msgstr ""
msgid "List your Bitbucket Server repositories"
msgstr ""
+msgid "Lists"
+msgstr ""
+
msgid "Live preview"
msgstr ""
@@ -13335,6 +13718,9 @@ msgstr ""
msgid "Manage project labels"
msgstr ""
+msgid "Manage storage usage"
+msgstr ""
+
msgid "Manage two-factor authentication"
msgstr ""
@@ -13350,6 +13736,9 @@ msgstr ""
msgid "Manifest file import"
msgstr ""
+msgid "Manifest import"
+msgstr ""
+
msgid "Manual job"
msgstr ""
@@ -13497,6 +13886,12 @@ msgstr ""
msgid "Maximum field length"
msgstr ""
+msgid "Maximum file size is 2MB. Please select a smaller file."
+msgstr ""
+
+msgid "Maximum import size (MB)"
+msgstr ""
+
msgid "Maximum job timeout"
msgstr ""
@@ -13536,6 +13931,9 @@ msgstr ""
msgid "Maximum size of Elasticsearch bulk indexing requests."
msgstr ""
+msgid "Maximum size of import files."
+msgstr ""
+
msgid "Maximum size of individual attachments in comments."
msgstr ""
@@ -13587,6 +13985,9 @@ msgstr ""
msgid "Merge (when the pipeline succeeds)"
msgstr ""
+msgid "Merge Conflicts"
+msgstr ""
+
msgid "Merge Request"
msgstr ""
@@ -13713,6 +14114,9 @@ msgstr ""
msgid "MergeRequests|Squash task canceled: another squash is already in progress."
msgstr ""
+msgid "MergeRequests|This project does not allow squashing commits when merge requests are accepted."
+msgstr ""
+
msgid "MergeRequests|Thread stays resolved"
msgstr ""
@@ -13866,6 +14270,12 @@ msgstr ""
msgid "MetricsSettings|Add a button to the metrics dashboard linking directly to your existing external dashboard."
msgstr ""
+msgid "MetricsSettings|Choose whether to display dashboard metrics in UTC or the user's local timezone."
+msgstr ""
+
+msgid "MetricsSettings|Dashboard timezone"
+msgstr ""
+
msgid "MetricsSettings|External dashboard URL"
msgstr ""
@@ -13875,6 +14285,12 @@ msgstr ""
msgid "MetricsSettings|Metrics Dashboard"
msgstr ""
+msgid "MetricsSettings|UTC (Coordinated Universal Time)"
+msgstr ""
+
+msgid "MetricsSettings|User's local timezone"
+msgstr ""
+
msgid "Metrics|Add metric"
msgstr ""
@@ -13890,6 +14306,9 @@ msgstr ""
msgid "Metrics|Create metric"
msgstr ""
+msgid "Metrics|Current"
+msgstr ""
+
msgid "Metrics|Delete metric"
msgstr ""
@@ -13940,6 +14359,9 @@ msgstr ""
msgid "Metrics|Max"
msgstr ""
+msgid "Metrics|Min"
+msgstr ""
+
msgid "Metrics|Must be a valid PromQL query."
msgstr ""
@@ -13955,6 +14377,9 @@ msgstr ""
msgid "Metrics|Refresh dashboard"
msgstr ""
+msgid "Metrics|Select a value"
+msgstr ""
+
msgid "Metrics|Star dashboard"
msgstr ""
@@ -13973,12 +14398,18 @@ msgstr ""
msgid "Metrics|There was an error getting annotations information."
msgstr ""
+msgid "Metrics|There was an error getting dashboard validation warnings information."
+msgstr ""
+
msgid "Metrics|There was an error getting deployment information."
msgstr ""
msgid "Metrics|There was an error getting environments information."
msgstr ""
+msgid "Metrics|There was an error getting options for variable \"%{name}\"."
+msgstr ""
+
msgid "Metrics|There was an error trying to validate your query"
msgstr ""
@@ -14021,6 +14452,9 @@ msgstr ""
msgid "Metrics|You're about to permanently delete this metric. This cannot be undone."
msgstr ""
+msgid "Metrics|Your dashboard schema is invalid. Edit the dashboard to correct the YAML schema."
+msgstr ""
+
msgid "Metrics|e.g. HTTP requests"
msgstr ""
@@ -14036,12 +14470,18 @@ msgstr ""
msgid "Metrics|e.g. req/sec"
msgstr ""
+msgid "Mi"
+msgstr ""
+
msgid "Microsoft Azure"
msgstr ""
msgid "Middleman project with Static Site Editor support"
msgstr ""
+msgid "Migrate your data from an external source like GitHub, Bitbucket, or another instance of GitLab."
+msgstr ""
+
msgid "Migrated %{success_count}/%{total_count} files."
msgstr ""
@@ -14176,6 +14616,9 @@ msgstr ""
msgid "Minimum capacity to be available before we schedule more mirrors preemptively."
msgstr ""
+msgid "Minimum interval in days"
+msgstr ""
+
msgid "Minimum length is %{minimum_password_length} characters"
msgstr ""
@@ -14362,6 +14805,12 @@ msgstr ""
msgid "Multiple uploaders found: %{uploader_types}"
msgstr ""
+msgid "Must match with the %{codeStart}external_url%{codeEnd} in %{codeStart}/etc/gitlab/gitlab.rb%{codeEnd}."
+msgstr ""
+
+msgid "Must match with the %{codeStart}geo_node_name%{codeEnd} in %{codeStart}/etc/gitlab/gitlab.rb%{codeEnd}. %{linkStart}More information%{linkEnd}"
+msgstr ""
+
msgid "My Awesome Group"
msgstr ""
@@ -14476,10 +14925,16 @@ msgstr ""
msgid "New Application"
msgstr ""
+msgid "New Branch"
+msgstr ""
+
+msgid "New Deploy Key"
+msgstr ""
+
msgid "New Environment"
msgstr ""
-msgid "New Geo Node"
+msgid "New File"
msgstr ""
msgid "New Group"
@@ -14496,15 +14951,15 @@ msgid_plural "New Issues"
msgstr[0] "Nova problemo"
msgstr[1] "Novaj problemoj"
-msgid "New Iteration"
-msgstr ""
-
msgid "New Jira import"
msgstr ""
msgid "New Label"
msgstr ""
+msgid "New Merge Request"
+msgstr ""
+
msgid "New Milestone"
msgstr ""
@@ -14523,6 +14978,9 @@ msgstr ""
msgid "New Snippet"
msgstr ""
+msgid "New User"
+msgstr ""
+
msgid "New branch"
msgstr "Nova branĉo"
@@ -14565,6 +15023,9 @@ msgstr "Nova problemo"
msgid "New issue title"
msgstr ""
+msgid "New iteration"
+msgstr ""
+
msgid "New iteration created"
msgstr ""
@@ -14742,6 +15203,12 @@ msgstr ""
msgid "No grouping"
msgstr ""
+msgid "No iteration"
+msgstr ""
+
+msgid "No iterations to show"
+msgstr ""
+
msgid "No job log"
msgstr ""
@@ -14784,6 +15251,9 @@ msgstr ""
msgid "No other labels with such name or description"
msgstr ""
+msgid "No panels matching properties %{opts}"
+msgstr ""
+
msgid "No parent group"
msgstr ""
@@ -14817,6 +15287,9 @@ msgstr ""
msgid "No schedules"
msgstr "Ne estas planoj"
+msgid "No stack trace for this error"
+msgstr ""
+
msgid "No starrers matched your search"
msgstr ""
@@ -14829,7 +15302,7 @@ msgstr ""
msgid "No test coverage"
msgstr ""
-msgid "No thanks, don't show this again"
+msgid "No thanks"
msgstr ""
msgid "No vulnerabilities present"
@@ -14844,9 +15317,6 @@ msgstr ""
msgid "No, directly import the existing email addresses and usernames."
msgstr ""
-msgid "No, not interested right now"
-msgstr ""
-
msgid "No. of commits"
msgstr ""
@@ -14892,9 +15362,6 @@ msgstr "Ne estas sufiĉe da datenoj"
msgid "Not found."
msgstr ""
-msgid "Not helpful"
-msgstr ""
-
msgid "Not now"
msgstr ""
@@ -15051,6 +15518,9 @@ msgstr ""
msgid "Notifications on"
msgstr ""
+msgid "Notify users by email when sign-in location is not recognized"
+msgstr ""
+
msgid "Nov"
msgstr ""
@@ -15117,9 +15587,6 @@ msgstr "Filtrilo"
msgid "Oh no!"
msgstr ""
-msgid "Ok let's go"
-msgstr ""
-
msgid "Oldest first"
msgstr ""
@@ -15132,16 +15599,49 @@ msgstr ""
msgid "On track"
msgstr ""
+msgid "OnDemandScans|Attached branch"
+msgstr ""
+
+msgid "OnDemandScans|Attached branch is where the scan job runs."
+msgstr ""
+
+msgid "OnDemandScans|Could not run the scan. Please try again."
+msgstr ""
+
msgid "OnDemandScans|Create new DAST scan"
msgstr ""
+msgid "OnDemandScans|DAST will scan the target URL and any discovered sub URLs."
+msgstr ""
+
+msgid "OnDemandScans|New on-demand DAST scan"
+msgstr ""
+
msgid "OnDemandScans|On-demand Scans"
msgstr ""
+msgid "OnDemandScans|On-demand scans run outside the DevOps cycle and find vulnerabilities in your projects. %{learnMoreLinkStart}Learn more%{learnMoreLinkEnd}"
+msgstr ""
+
+msgid "OnDemandScans|Only a passive scan can be performed on demand."
+msgstr ""
+
+msgid "OnDemandScans|Passive DAST Scan"
+msgstr ""
+
+msgid "OnDemandScans|Please enter a valid URL format, ex: http://www.example.com/home"
+msgstr ""
+
+msgid "OnDemandScans|Run this scan"
+msgstr ""
+
+msgid "OnDemandScans|Scan mode"
+msgstr ""
+
msgid "OnDemandScans|Schedule or run scans immediately against target sites. Currently available on-demand scan type: DAST. %{helpLinkStart}More information%{helpLinkEnd}"
msgstr ""
-msgid "Onboarding"
+msgid "OnDemandScans|Target URL"
msgstr ""
msgid "Once imported, repositories can be mirrored over SSH. Read more %{link_start}here%{link_end}."
@@ -15167,6 +15667,9 @@ msgstr ""
msgid "One or more of you personal access tokens were revoked"
msgstr ""
+msgid "One or more of your %{provider} projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
+msgstr ""
+
msgid "One or more of your Bitbucket projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
msgstr ""
@@ -15668,6 +16171,9 @@ msgstr ""
msgid "Parent epic is not present."
msgstr ""
+msgid "Parsing error for param :embed_json. %{message}"
+msgstr ""
+
msgid "Part of merge request changes"
msgstr ""
@@ -15680,6 +16186,9 @@ msgstr ""
msgid "Passed"
msgstr ""
+msgid "Passed on"
+msgstr ""
+
msgid "Password"
msgstr ""
@@ -15764,6 +16273,9 @@ msgstr ""
msgid "Perform common operations on GitLab project"
msgstr ""
+msgid "Performance and resource management"
+msgstr ""
+
msgid "Performance optimization"
msgstr ""
@@ -15971,7 +16483,7 @@ msgstr ""
msgid "Pipelines|Something went wrong while cleaning runners cache."
msgstr ""
-msgid "Pipelines|There are currently no %{scope} pipelines."
+msgid "Pipelines|There are currently no finished pipelines."
msgstr ""
msgid "Pipelines|There are currently no pipelines."
@@ -16082,6 +16594,9 @@ msgstr ""
msgid "Pipeline|Stop pipeline #%{pipelineId}?"
msgstr ""
+msgid "Pipeline|Tag name"
+msgstr ""
+
msgid "Pipeline|Trigger author"
msgstr ""
@@ -16163,12 +16678,18 @@ msgstr ""
msgid "Please check your email (%{email}) to verify that you own this address and unlock the power of CI/CD. Didn't receive it? %{resend_link}. Wrong email address? %{update_link}."
msgstr ""
+msgid "Please choose a file"
+msgstr ""
+
msgid "Please choose a group URL with no special characters."
msgstr ""
msgid "Please complete your profile with email address"
msgstr ""
+msgid "Please convert %{linkStart}them to Git%{linkEnd}, and go through the %{linkToImportFlow} again."
+msgstr ""
+
msgid "Please convert them to %{link_to_git}, and go through the %{link_to_import_flow} again."
msgstr ""
@@ -16226,6 +16747,9 @@ msgstr ""
msgid "Please provide a valid email address."
msgstr ""
+msgid "Please provide attributes to update"
+msgstr ""
+
msgid "Please refer to <a href=\"%{docs_url}\">%{docs_url}</a>"
msgstr ""
@@ -16961,6 +17485,9 @@ msgstr ""
msgid "Project has too many %{label_for_message} to search"
msgstr ""
+msgid "Project is required when cluster_type is :project"
+msgstr ""
+
msgid "Project members"
msgstr ""
@@ -17078,7 +17605,40 @@ msgstr ""
msgid "ProjectService|Comment"
msgstr ""
-msgid "ProjectService|Comment will be posted on each event"
+msgid "ProjectService|Event will be triggered by a push to the repository"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a commit is created/updated"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a confidential issue is created/updated/closed"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a deployment finishes"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a merge request is created/updated/merged"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a new tag is pushed to the repository"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a new, unique alert is recorded"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a pipeline status changes"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a wiki page is created/updated"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when an issue is created/updated/closed"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when someone adds a comment"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when someone adds a comment on a confidential issue"
msgstr ""
msgid "ProjectService|Perform common operations on GitLab project: %{project_name}"
@@ -17129,6 +17689,9 @@ msgstr ""
msgid "ProjectSettings|Enable 'Delete source branch' option by default"
msgstr ""
+msgid "ProjectSettings|Enable merge trains and pipelines for merged results"
+msgstr ""
+
msgid "ProjectSettings|Every merge creates a merge commit"
msgstr ""
@@ -17159,7 +17722,7 @@ msgstr ""
msgid "ProjectSettings|Forks"
msgstr ""
-msgid "ProjectSettings|Git Large File Storage"
+msgid "ProjectSettings|Git Large File Storage (LFS)"
msgstr ""
msgid "ProjectSettings|Internal"
@@ -17195,9 +17758,6 @@ msgstr ""
msgid "ProjectSettings|Merge options"
msgstr ""
-msgid "ProjectSettings|Merge pipelines will try to validate the post-merge result prior to merging"
-msgstr ""
-
msgid "ProjectSettings|Merge requests"
msgstr ""
@@ -17225,6 +17785,9 @@ msgstr ""
msgid "ProjectSettings|Pipelines"
msgstr ""
+msgid "ProjectSettings|Pipelines for merge requests must be enabled in the CI/CD configuration file, or pipelines could be unresolvable or dropped"
+msgstr ""
+
msgid "ProjectSettings|Pipelines must succeed"
msgstr ""
@@ -17252,6 +17815,9 @@ msgstr ""
msgid "ProjectSettings|Show link to create/view merge request when pushing from the command line"
msgstr ""
+msgid "ProjectSettings|Skipped pipelines are considered successful"
+msgstr ""
+
msgid "ProjectSettings|Snippets"
msgstr ""
@@ -17267,6 +17833,9 @@ msgstr ""
msgid "ProjectSettings|These checks must pass before merge requests can be merged"
msgstr ""
+msgid "ProjectSettings|This introduces the risk of merging changes that will not pass the pipeline."
+msgstr ""
+
msgid "ProjectSettings|This setting is applied on the server level and can be overridden by an admin."
msgstr ""
@@ -17444,15 +18013,27 @@ msgstr ""
msgid "ProjectsNew|Blank project"
msgstr ""
+msgid "ProjectsNew|Connect your external repository to GitLab CI/CD."
+msgstr ""
+
msgid "ProjectsNew|Contact an administrator to enable options for importing your project."
msgstr ""
msgid "ProjectsNew|Create"
msgstr ""
+msgid "ProjectsNew|Create a blank project to house your files, plan your work, and collaborate on code, among other things."
+msgstr ""
+
+msgid "ProjectsNew|Create blank project"
+msgstr ""
+
msgid "ProjectsNew|Create from template"
msgstr ""
+msgid "ProjectsNew|Create new project"
+msgstr ""
+
msgid "ProjectsNew|Creating project & repository."
msgstr ""
@@ -17477,6 +18058,9 @@ msgstr ""
msgid "ProjectsNew|Project description %{tag_start}(optional)%{tag_end}"
msgstr ""
+msgid "ProjectsNew|Run CI/CD for external repository"
+msgstr ""
+
msgid "ProjectsNew|Template"
msgstr ""
@@ -17738,6 +18322,9 @@ msgstr ""
msgid "Protected Branch"
msgstr ""
+msgid "Protected Branches"
+msgstr ""
+
msgid "Protected Environment"
msgstr ""
@@ -17750,6 +18337,9 @@ msgstr ""
msgid "Protected Tag"
msgstr ""
+msgid "Protected Tags"
+msgstr ""
+
msgid "Protected branches"
msgstr ""
@@ -17849,6 +18439,9 @@ msgstr ""
msgid "Provider"
msgstr ""
+msgid "Proxy support for this API is not available currently"
+msgstr ""
+
msgid "Pseudonymizer data collection"
msgstr ""
@@ -17873,6 +18466,9 @@ msgstr ""
msgid "Publish to status page"
msgstr ""
+msgid "Published on status page"
+msgstr ""
+
msgid "Publishes this issue to the associated status page."
msgstr ""
@@ -18047,6 +18643,9 @@ msgstr ""
msgid "Recipe"
msgstr ""
+msgid "Reconfigure"
+msgstr ""
+
msgid "Recover hidden stage"
msgstr ""
@@ -18133,6 +18732,9 @@ msgstr ""
msgid "Registration|Your profile"
msgstr ""
+msgid "Regulate approvals by authors/committers, based on compliance frameworks. Can be changed only at the instance level."
+msgstr ""
+
msgid "Rejected (closed)"
msgstr ""
@@ -18183,15 +18785,24 @@ msgstr ""
msgid "ReleaseAssetLinkType|Image"
msgstr ""
+msgid "ReleaseAssetLinkType|Images"
+msgstr ""
+
msgid "ReleaseAssetLinkType|Other"
msgstr ""
msgid "ReleaseAssetLinkType|Package"
msgstr ""
+msgid "ReleaseAssetLinkType|Packages"
+msgstr ""
+
msgid "ReleaseAssetLinkType|Runbook"
msgstr ""
+msgid "ReleaseAssetLinkType|Runbooks"
+msgstr ""
+
msgid "Releases"
msgstr ""
@@ -18204,6 +18815,9 @@ msgstr ""
msgid "Releases documentation"
msgstr ""
+msgid "Releases|New Release"
+msgstr ""
+
msgid "Release|Something went wrong while getting the release details"
msgstr ""
@@ -18276,6 +18890,9 @@ msgstr ""
msgid "Remove fork relationship"
msgstr ""
+msgid "Remove from batch"
+msgstr ""
+
msgid "Remove from board"
msgstr ""
@@ -18465,6 +19082,12 @@ msgstr ""
msgid "Replication"
msgstr ""
+msgid "Replication enabled"
+msgstr ""
+
+msgid "Replication paused"
+msgstr ""
+
msgid "Reply by email"
msgstr ""
@@ -18492,15 +19115,15 @@ msgstr ""
msgid "Reported %{timeAgo} by %{reportedBy}"
msgstr ""
-msgid "Reported Resource Changes: %{addNum} to add, %{changeNum} to change, %{deleteNum} to delete"
-msgstr ""
-
msgid "Reporter"
msgstr ""
msgid "Reporting"
msgstr ""
+msgid "Reports"
+msgstr ""
+
msgid "Reports|%{combinedString} and %{resolvedString}"
msgstr ""
@@ -18539,6 +19162,9 @@ msgstr ""
msgid "Reports|Failure"
msgstr ""
+msgid "Reports|Identifier"
+msgstr ""
+
msgid "Reports|Metrics reports are loading"
msgstr ""
@@ -18551,6 +19177,9 @@ msgstr ""
msgid "Reports|Metrics reports failed loading results"
msgstr ""
+msgid "Reports|Scanner"
+msgstr ""
+
msgid "Reports|Severity"
msgstr ""
@@ -18599,15 +19228,33 @@ msgstr ""
msgid "Repository cleanup has started. You will receive an email once the cleanup operation is complete."
msgstr ""
+msgid "Repository files count over the limit"
+msgstr ""
+
+msgid "Repository has an invalid default branch name."
+msgstr ""
+
+msgid "Repository has more than one branch."
+msgstr ""
+
msgid "Repository has no locks."
msgstr ""
+msgid "Repository has tags."
+msgstr ""
+
msgid "Repository maintenance"
msgstr ""
msgid "Repository mirroring"
msgstr ""
+msgid "Repository must contain at least 1 file."
+msgstr ""
+
+msgid "Repository size is above the limit."
+msgstr ""
+
msgid "Repository static objects"
msgstr ""
@@ -18617,7 +19264,7 @@ msgstr ""
msgid "Repository sync capacity"
msgstr ""
-msgid "Repository: %{counter_repositories} / Wikis: %{counter_wikis} / Build Artifacts: %{counter_build_artifacts} / LFS: %{counter_lfs_objects}"
+msgid "Repository: %{counter_repositories} / Wikis: %{counter_wikis} / Build Artifacts: %{counter_build_artifacts} / LFS: %{counter_lfs_objects} / Snippets: %{counter_snippets}"
msgstr ""
msgid "RepositorySettingsAccessLevel|Select"
@@ -18629,6 +19276,9 @@ msgstr "Peti atingeblon"
msgid "Request Headers"
msgstr ""
+msgid "Request details"
+msgstr ""
+
msgid "Request parameter %{param} is missing."
msgstr ""
@@ -18735,6 +19385,9 @@ msgstr ""
msgid "Reset template"
msgstr ""
+msgid "Reset to project defaults"
+msgstr ""
+
msgid "Resetting the authorization key for this project will require updating the authorization key in every alert source it is enabled in."
msgstr ""
@@ -18831,9 +19484,6 @@ msgstr ""
msgid "Resume"
msgstr ""
-msgid "Resume replication"
-msgstr ""
-
msgid "Resync"
msgstr ""
@@ -19456,6 +20106,9 @@ msgstr ""
msgid "SecurityReports|False positive"
msgstr ""
+msgid "SecurityReports|Group Security Dashboard"
+msgstr ""
+
msgid "SecurityReports|Hide dismissed"
msgstr ""
@@ -19510,10 +20163,13 @@ msgstr ""
msgid "SecurityReports|Remove project from dashboard"
msgstr ""
-msgid "SecurityReports|Report type"
+msgid "SecurityReports|Return to dashboard"
msgstr ""
-msgid "SecurityReports|Return to dashboard"
+msgid "SecurityReports|Scan details"
+msgstr ""
+
+msgid "SecurityReports|Scanner type"
msgstr ""
msgid "SecurityReports|Security Dashboard"
@@ -19570,6 +20226,9 @@ msgstr ""
msgid "SecurityReports|There was an error while generating the report."
msgstr ""
+msgid "SecurityReports|Unable to add %{invalidProjectsMessage}"
+msgstr ""
+
msgid "SecurityReports|Unable to add %{invalidProjects}"
msgstr ""
@@ -19684,6 +20343,9 @@ msgstr ""
msgid "Select due date"
msgstr ""
+msgid "Select file"
+msgstr ""
+
msgid "Select group or project"
msgstr ""
@@ -19747,9 +20409,6 @@ msgstr "Elektu celan branĉon"
msgid "Select the branch you want to set as the default for this project. All merge requests and commits will automatically be made against this branch unless you specify a different one."
msgstr ""
-msgid "Select the configured storage available for new repositories to be placed on."
-msgstr ""
-
msgid "Select the custom project template source group."
msgstr ""
@@ -19942,12 +20601,6 @@ msgstr ""
msgid "Service Desk is enabled but not yet active"
msgstr ""
-msgid "Service Desk is off"
-msgstr ""
-
-msgid "Service Desk is on"
-msgstr ""
-
msgid "Service Templates"
msgstr ""
@@ -20035,6 +20688,15 @@ msgstr ""
msgid "Set the milestone to %{milestone_reference}."
msgstr ""
+msgid "Set the number of concurrent requests this secondary node will make to the primary node while backfilling."
+msgstr ""
+
+msgid "Set the synchronization and verification capacity for the secondary node."
+msgstr ""
+
+msgid "Set the timeout in seconds to send a secondary node status to the primary and IPs allowed for the secondary nodes."
+msgstr ""
+
msgid "Set time estimate"
msgstr ""
@@ -20074,6 +20736,9 @@ msgstr ""
msgid "Set weight to %{weight}."
msgstr ""
+msgid "Set what should be replicated by choosing specific projects or groups by the secondary node."
+msgstr ""
+
msgid "SetPasswordToCloneLink|set a password"
msgstr "kreos pasvorton"
@@ -20200,6 +20865,9 @@ msgstr ""
msgid "Show complete raw log"
msgstr ""
+msgid "Show details"
+msgstr ""
+
msgid "Show file browser"
msgstr ""
@@ -20209,10 +20877,13 @@ msgstr ""
msgid "Show latest version"
msgstr ""
+msgid "Show list"
+msgstr ""
+
msgid "Show me everything"
msgstr ""
-msgid "Show me how"
+msgid "Show me how to add a pipeline"
msgstr ""
msgid "Show me more advanced stuff"
@@ -20261,9 +20932,6 @@ msgstr ""
msgid "Showing all issues"
msgstr ""
-msgid "Showing all labels"
-msgstr ""
-
msgid "Showing last %{size} of log -"
msgstr ""
@@ -20375,9 +21043,6 @@ msgstr ""
msgid "Skip outdated deployment jobs"
msgstr ""
-msgid "Skip this for now"
-msgstr ""
-
msgid "Skipped"
msgstr ""
@@ -20522,6 +21187,9 @@ msgstr ""
msgid "Something went wrong while adding your award. Please try again."
msgstr ""
+msgid "Something went wrong while applying the batch of suggestions. Please try again."
+msgstr ""
+
msgid "Something went wrong while applying the suggestion. Please try again."
msgstr ""
@@ -20624,10 +21292,13 @@ msgstr ""
msgid "Something went wrong while updating a requirement."
msgstr ""
+msgid "Something went wrong while updating assignees"
+msgstr ""
+
msgid "Something went wrong while updating your list settings"
msgstr ""
-msgid "Something went wrong with your automatic subscription renewal"
+msgid "Something went wrong with your automatic subscription renewal."
msgstr ""
msgid "Something went wrong, unable to add %{project} to dashboard"
@@ -20645,6 +21316,9 @@ msgstr ""
msgid "Something went wrong, unable to search projects"
msgstr ""
+msgid "Something went wrong."
+msgstr ""
+
msgid "Something went wrong. Please try again."
msgstr ""
@@ -20828,6 +21502,9 @@ msgstr ""
msgid "Source code"
msgstr "Kodo"
+msgid "Source code (%{fileExtension})"
+msgstr ""
+
msgid "Source is not available"
msgstr ""
@@ -20912,6 +21589,9 @@ msgstr ""
msgid "Stack trace"
msgstr ""
+msgid "Stacktrace snippet"
+msgstr ""
+
msgid "Stage"
msgstr ""
@@ -21017,6 +21697,9 @@ msgstr ""
msgid "Start thread & reopen %{noteable_name}"
msgstr ""
+msgid "Start using Directed Acyclic Graphs (DAG)"
+msgstr ""
+
msgid "Start your Free Gold Trial"
msgstr ""
@@ -21479,6 +22162,21 @@ msgstr ""
msgid "SuggestedColors|Very pale orange"
msgstr ""
+msgid "Suggestion is not applicable as the suggestion was not found."
+msgstr ""
+
+msgid "Suggestions are not applicable as one or more suggestions were not found."
+msgstr ""
+
+msgid "Suggestions are not applicable as their lines cannot overlap."
+msgstr ""
+
+msgid "Suggestions must all be on the same branch."
+msgstr ""
+
+msgid "Suggestions that change line count can't be added to batches, yet."
+msgstr ""
+
msgid "Suggestions:"
msgstr ""
@@ -21521,9 +22219,6 @@ msgstr ""
msgid "Synchronization disabled"
msgstr ""
-msgid "Synchronization of container repositories is disabled."
-msgstr ""
-
msgid "System"
msgstr ""
@@ -21704,6 +22399,34 @@ msgstr ""
msgid "Terms of Service and Privacy Policy"
msgstr ""
+msgid "Terraform|%{number} Terraform report failed to generate"
+msgid_plural "Terraform|%{number} Terraform reports failed to generate"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Terraform|%{number} Terraform report was generated in your pipelines"
+msgid_plural "Terraform|%{number} Terraform reports were generated in your pipelines"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Terraform|A Terraform report failed to generate."
+msgstr ""
+
+msgid "Terraform|A Terraform report was generated in your pipelines."
+msgstr ""
+
+msgid "Terraform|Generating the report caused an error."
+msgstr ""
+
+msgid "Terraform|Reported Resource Changes: %{addNum} to add, %{changeNum} to change, %{deleteNum} to delete"
+msgstr ""
+
+msgid "Terraform|The Terraform report %{name} failed to generate."
+msgstr ""
+
+msgid "Terraform|The Terraform report %{name} was generated in your pipelines."
+msgstr ""
+
msgid "Test"
msgstr ""
@@ -21730,7 +22453,7 @@ msgstr ""
msgid "TestHooks|Ensure the project has CI pipelines."
msgstr ""
-msgid "TestHooks|Ensure the project has at least one commit."
+msgid "TestHooks|Ensure the project has deployments."
msgstr ""
msgid "TestHooks|Ensure the project has issues."
@@ -21831,7 +22554,7 @@ msgstr ""
msgid "The Prometheus server responded with \"bad request\". Please check your queries are correct and are supported in your Prometheus version. %{documentationLink}"
msgstr ""
-msgid "The URL defined on the primary node that secondary nodes should use to contact it. Defaults to URL"
+msgid "The URL defined on the primary node that secondary nodes should use to contact it."
msgstr ""
msgid "The URL to use for connecting to Elasticsearch. Use a comma-separated list to support clustering (e.g., \"http://localhost:9200, http://localhost:9201\")."
@@ -21840,6 +22563,9 @@ msgstr ""
msgid "The X509 Certificate to use when mutual TLS is required to communicate with the external authorization service. If left blank, the server certificate is still validated when accessing over HTTPS."
msgstr ""
+msgid "The above settings apply to all projects with the selected compliance framework(s)."
+msgstr ""
+
msgid "The amount of seconds after which a request to get a secondary node status will time out."
msgstr ""
@@ -22139,9 +22865,6 @@ msgstr "La tempo, kiu estas necesa por ĉiu dateno kolektita de la etapo."
msgid "The total stage shows the time it takes between creating an issue and deploying the code to production. The data will be automatically added once you have completed the full idea to production cycle."
msgstr ""
-msgid "The unique identifier for the Geo node. Must match %{geoNodeName} if it is set in gitlab.rb, otherwise it must match %{externalUrl} with a trailing slash"
-msgstr ""
-
msgid "The update action will time out after %{number_of_minutes} minutes. For big repositories, use a clone/push combination."
msgstr ""
@@ -22166,9 +22889,6 @@ msgstr ""
msgid "The user you are trying to deactivate has been active in the past %{minimum_inactive_days} days and cannot be deactivated"
msgstr ""
-msgid "The user-facing URL of the Geo node"
-msgstr ""
-
msgid "The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6."
msgstr "La valoro, kiu troviÄas en la mezo de aro da rigardataj valoroj. Ekzemple: inter 3, 5 kaj 9, la mediano estas 5. Inter 3, 5, 7 kaj 8, la mediano estas (5+7)/2 = 6."
@@ -22259,6 +22979,9 @@ msgstr ""
msgid "There was a problem fetching project branches."
msgstr ""
+msgid "There was a problem fetching project tags."
+msgstr ""
+
msgid "There was a problem fetching project users."
msgstr ""
@@ -22310,6 +23033,9 @@ msgstr ""
msgid "There was an error fetching the %{replicableType}"
msgstr ""
+msgid "There was an error fetching the Geo Settings"
+msgstr ""
+
msgid "There was an error fetching the Node's Groups"
msgstr ""
@@ -22430,6 +23156,12 @@ msgstr ""
msgid "This %{issuable} is locked. Only <strong>project members</strong> can comment."
msgstr ""
+msgid "This %{noteableTypeText} is %{confidentialLinkStart}confidential%{linkEnd} and %{lockedLinkStart}locked%{linkEnd}."
+msgstr ""
+
+msgid "This %{noteableTypeText} is locked."
+msgstr ""
+
msgid "This %{viewer} could not be displayed because %{reason}. You can %{options} instead."
msgstr ""
@@ -22448,6 +23180,9 @@ msgstr ""
msgid "This action can lead to data loss. To prevent accidental actions we ask you to confirm your intention."
msgstr ""
+msgid "This also resolves all related threads"
+msgstr ""
+
msgid "This also resolves the discussion"
msgstr ""
@@ -22538,6 +23273,9 @@ msgstr ""
msgid "This epic does not exist or you don't have sufficient permission."
msgstr ""
+msgid "This feature is currently in beta. We invite you to %{linkStart}give feedback%{linkEnd}."
+msgstr ""
+
msgid "This feature requires local storage to be enabled"
msgstr ""
@@ -22571,7 +23309,7 @@ msgstr ""
msgid "This is a Work in Progress"
msgstr ""
-msgid "This is a confidential issue."
+msgid "This is a confidential %{noteableTypeText}."
msgstr ""
msgid "This is a delayed job to run in %{remainingTime}"
@@ -22598,9 +23336,6 @@ msgstr ""
msgid "This is your current session"
msgstr ""
-msgid "This issue is %{confidentialLinkStart}confidential%{linkEnd} and %{lockedLinkStart}locked%{linkEnd}."
-msgstr ""
-
msgid "This issue is confidential"
msgstr ""
@@ -22610,9 +23345,6 @@ msgstr ""
msgid "This issue is in a child epic of the filtered epic"
msgstr ""
-msgid "This issue is locked."
-msgstr ""
-
msgid "This job depends on other jobs with expired/erased artifacts: %{invalid_dependencies}"
msgstr ""
@@ -22712,6 +23444,9 @@ msgstr ""
msgid "This license has already expired."
msgstr ""
+msgid "This link points to external content"
+msgstr ""
+
msgid "This may expose confidential information as the selected fork is in another namespace that can have other members."
msgstr ""
@@ -22739,6 +23474,9 @@ msgstr ""
msgid "This page will be removed in a future release."
msgstr ""
+msgid "This pipeline does not use the %{codeStart}needs%{codeEnd} keyword and can't be represented as a directed acyclic graph."
+msgstr ""
+
msgid "This pipeline makes use of a predefined CI/CD configuration enabled by %{strongStart}Auto DevOps.%{strongEnd}"
msgstr ""
@@ -22967,6 +23705,9 @@ msgstr ""
msgid "Time from last commit to merge"
msgstr ""
+msgid "Time in seconds"
+msgstr ""
+
msgid "Time in seconds GitLab will wait for a response from the external service. When the service does not respond in time, access will be denied."
msgstr ""
@@ -23388,9 +24129,15 @@ msgstr ""
msgid "Total artifacts size: %{total_size}"
msgstr ""
+msgid "Total cores (CPUs)"
+msgstr ""
+
msgid "Total issues"
msgstr ""
+msgid "Total memory (GB)"
+msgstr ""
+
msgid "Total test time for all commits/merges"
msgstr "Totala tempo por la testado de ĉiuj enmetadoj/kunfandoj"
@@ -23592,6 +24339,9 @@ msgstr ""
msgid "U2F only works with HTTPS-enabled websites. Contact your administrator for more details."
msgstr ""
+msgid "UI Development Kit"
+msgstr ""
+
msgid "URL"
msgstr ""
@@ -23664,9 +24414,6 @@ msgstr ""
msgid "Unable to load the merge request widget. Try reloading the page."
msgstr ""
-msgid "Unable to process group import file"
-msgstr ""
-
msgid "Unable to resolve"
msgstr ""
@@ -23682,6 +24429,9 @@ msgstr ""
msgid "Unable to sign you in to the group with SAML due to \"%{reason}\""
msgstr ""
+msgid "Unable to suggest a path. Please refresh and try again."
+msgstr ""
+
msgid "Unable to update label prioritization at this time"
msgstr ""
@@ -23697,6 +24447,9 @@ msgstr ""
msgid "Unarchiving the project will restore people's ability to make changes to it. The repository can be committed to, and issues, comments, and other entities can be created. %{strong_start}Once active, this project shows up in the search and on the dashboard.%{strong_end}"
msgstr ""
+msgid "Unassign from commenting user"
+msgstr ""
+
msgid "Unblock"
msgstr ""
@@ -23772,6 +24525,9 @@ msgstr ""
msgid "Unreachable"
msgstr ""
+msgid "Unrecognized cluster type"
+msgstr ""
+
msgid "Unresolve"
msgstr ""
@@ -23832,6 +24588,9 @@ msgstr ""
msgid "Unsubscribes from this %{quick_action_target}."
msgstr ""
+msgid "Unsupported todo type passed. Supported todo types are: %{todo_types}"
+msgstr ""
+
msgid "Until"
msgstr ""
@@ -23859,6 +24618,9 @@ msgstr ""
msgid "Update approval rule"
msgstr ""
+msgid "Update approvers"
+msgstr ""
+
msgid "Update failed"
msgstr ""
@@ -23868,6 +24630,9 @@ msgstr ""
msgid "Update it"
msgstr ""
+msgid "Update iteration"
+msgstr ""
+
msgid "Update now"
msgstr ""
@@ -23913,7 +24678,7 @@ msgstr ""
msgid "Updated at"
msgstr ""
-msgid "Updated to"
+msgid "Updated to %{linkStart}chart v%{linkEnd}"
msgstr ""
msgid "Updating"
@@ -23943,6 +24708,9 @@ msgstr ""
msgid "Upload CSV file"
msgstr ""
+msgid "Upload License"
+msgstr ""
+
msgid "Upload New File"
msgstr "AlÅuti novan dosieron"
@@ -24024,16 +24792,19 @@ msgstr ""
msgid "UsageQuota|Pipelines"
msgstr ""
+msgid "UsageQuota|Purchase more storage"
+msgstr ""
+
msgid "UsageQuota|Repositories"
msgstr ""
msgid "UsageQuota|Repository"
msgstr ""
-msgid "UsageQuota|Storage"
+msgid "UsageQuota|Snippets"
msgstr ""
-msgid "UsageQuota|Storage usage:"
+msgid "UsageQuota|Storage"
msgstr ""
msgid "UsageQuota|This namespace has no projects which use shared runners"
@@ -24066,6 +24837,12 @@ msgstr ""
msgid "UsageQuota|Wikis"
msgstr ""
+msgid "UsageQuota|You used: %{usage} %{limit}"
+msgstr ""
+
+msgid "UsageQuota|out of %{formattedLimit} of your namespace storage"
+msgstr ""
+
msgid "Use %{code_start}::%{code_end} to create a %{link_start}scoped label set%{link_end} (eg. %{code_start}priority::1%{code_end})"
msgstr ""
@@ -24132,6 +24909,9 @@ msgstr ""
msgid "User IDs"
msgstr ""
+msgid "User Lists can only be created and modified with %{linkStart}the API%{linkEnd}"
+msgstr ""
+
msgid "User OAuth applications"
msgstr ""
@@ -24156,6 +24936,9 @@ msgstr ""
msgid "User key was successfully removed."
msgstr ""
+msgid "User list %{name} will be removed. Are you sure?"
+msgstr ""
+
msgid "User map"
msgstr ""
@@ -24177,148 +24960,10 @@ msgstr ""
msgid "User was successfully updated."
msgstr ""
-msgid "UserOnboardingTour|%{activeTour}/%{totalTours}"
-msgstr ""
-
-msgid "UserOnboardingTour|%{completed}/%{total} steps completed"
-msgstr ""
-
-msgid "UserOnboardingTour|%{emphasisStart}Well done!%{emphasisEnd}%{lineBreak}%{lineBreak}That's it for our guided tour, congratulations for making it all the way to the end!%{lineBreak}%{lineBreak}We hope this gave you a good overview of GitLab and how it can help you. We'll now show you how to create your own project and invite your colleagues."
-msgstr ""
-
-msgid "UserOnboardingTour|Adding other members to a project is done through Project Settings. Click on %{emphasisStart}Settings%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Alright, that's it for Commits. Let's take a look at the %{emphasisStart}Branches%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Awesome! Now click on %{emphasisStart}Members%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Click on one of the %{emphasisStart}Compare%{emphasisEnd} buttons to compare a branch to master."
-msgstr ""
-
-msgid "UserOnboardingTour|Click on one of the %{emphasisStart}pipeline IDs%{emphasisEnd} to see the details of a pipeline."
-msgstr ""
-
-msgid "UserOnboardingTour|Click to open the latest commit to see its details."
-msgstr ""
-
-msgid "UserOnboardingTour|Close 'Learn GitLab'"
-msgstr ""
-
-msgid "UserOnboardingTour|Commits are shown in chronological order and can be filtered by the commit message or by the branch."
-msgstr ""
-
-msgid "UserOnboardingTour|Create a project"
-msgstr ""
-
-msgid "UserOnboardingTour|Exit 'Learn GitLab'"
-msgstr ""
-
-msgid "UserOnboardingTour|Got it"
-msgstr ""
-
-msgid "UserOnboardingTour|Great job! %{clapHands} We hope the tour was helpful and that you learned how to use GitLab.%{lineBreak}%{lineBreak}We'd love to get your feedback on this tour.%{lineBreak}%{lineBreak}%{emphasisStart}How helpful would you say this guided tour was?%{emphasisEnd}%{lineBreak}%{lineBreak}"
-msgstr ""
-
-msgid "UserOnboardingTour|Guided GitLab Tour"
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can compare the changes of this branch to another one. Changes are divided by files so that it's easier to see what was changed where."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can create a project from scratch, start with a template or import a repository from other platforms. Whatever you choose, we'll guide you through the process.%{lineBreak}%{lineBreak}Fill in your new project information and click on %{emphasisStart}Create Project%{emphasisEnd} to progress to the next step."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can see the breakdown of the pipelines: its stages and jobs in each of the stages and their status.%{lineBreak}%{lineBreak}Our CI/CD pipelines are quite complex, most of our users have fewer and simpler pipelines."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can see the current members of the project (just you at the moment) and invite new members.%{lineBreak}%{lineBreak}You can invite multiple members at once (existing GitLab users or invite by email) and you can also set their roles and permissions.%{lineBreak}%{lineBreak}Add a few members and click on %{emphasisStart}Add to project%{emphasisEnd} to complete this step."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can see what changes were made with this commit, on what branch and if there's a related merge request. The status of the pipeline will also show up if CI/CD is set up.%{lineBreak}%{lineBreak}You can also comment on the lines of code that were changed and start a discussion with your colleagues!"
-msgstr ""
-
-msgid "UserOnboardingTour|Here's an overview of branches in the %{emphasisStart}%{projectName}%{emphasisEnd} project. They're split into Active and Stale.%{lineBreak}%{lineBreak}From here, you can create a new merge request from a branch, or compare the branch to any other branch in the project. By default, it will compare it to the master branch."
-msgstr ""
-
-msgid "UserOnboardingTour|Invite colleagues"
-msgstr ""
-
-msgid "UserOnboardingTour|Issues are great for communicating and keeping track of progress in GitLab. These are all issues that are open in the %{emphasisStart}%{projectName}%{emphasisEnd}.%{lineBreak}%{lineBreak}You can help us improve GitLab by contributing work to issues that are labeled <span class=\"badge color-label accept-mr-label\">Accepting merge requests</span>.%{lineBreak}%{lineBreak}This list can be filtered by labels, milestones, assignees, authors... We'll show you how it looks when the list is filtered by a label."
-msgstr ""
-
-msgid "UserOnboardingTour|Learn GitLab"
-msgstr ""
-
-msgid "UserOnboardingTour|Let's take a closer look at a merge request. Click on the title of one."
-msgstr ""
-
-msgid "UserOnboardingTour|Let's take a closer look at all the commits. Click on %{emphasisStart}Commits%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Let's take a closer look at the repository of this project. Click on %{emphasisStart}Repository%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|No thanks"
-msgstr ""
-
-msgid "UserOnboardingTour|Ok, let's go"
-msgstr ""
-
-msgid "UserOnboardingTour|Ok, show me"
-msgstr ""
-
-msgid "UserOnboardingTour|Open one of the issues by clicking on its title."
-msgstr ""
-
-msgid "UserOnboardingTour|Restart this step"
-msgstr ""
-
-msgid "UserOnboardingTour|Skip this step"
-msgstr ""
-
-msgid "UserOnboardingTour|Sweet! Your project was created and is ready to be used.%{lineBreak}%{lineBreak}You can start adding files to the repository or clone it. One last thing we want to show you is how to invite your colleagues to your new project."
-msgstr ""
-
-msgid "UserOnboardingTour|Take a look. Here's a nifty menu for quickly creating issues, merge requests, snippets, projects and groups. Click on it and select \"New project\" from the \"GitLab\" section to get started."
-msgstr ""
-
-msgid "UserOnboardingTour|Thanks for taking the guided tour. Remember, if you want to go through it again, you can start %{emphasisStart}Learn GitLab%{emphasisEnd} in the help menu on the top right."
-msgstr ""
-
-msgid "UserOnboardingTour|Thanks for the feedback! %{thumbsUp}"
-msgstr ""
-
-msgid "UserOnboardingTour|That's it for issues. Let'st take a look at %{emphasisStart}Merge Requests%{emphasisEnd}."
+msgid "UserList|Delete %{name}?"
msgstr ""
-msgid "UserOnboardingTour|That's it for merge requests. Now for the final part of this guided tour - the %{emphasisStart}CI/CD%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|That's it for the Repository. Let's take a look at the %{emphasisStart}Issues%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|The structure of this page is very similar to issues. Status, description, discussion and the sidebar are all here.%{lineBreak}%{lineBreak}But take a look below the description and you'll notice that there's more information about the merge request, the CI/CD pipeline and the options for approving it.%{lineBreak}%{lineBreak}Alongside the discussion you can also see more information about commits in this merge request, the status of pipelines and review all changes that were made."
-msgstr ""
-
-msgid "UserOnboardingTour|There's a lot of information here but don't worry, we'll go through it.%{lineBreak}%{lineBreak}On the top you can see the status of the issue and when it was opened and by whom. Directly below it is the issue description and below that are other %{emphasisStart}related issues%{emphasisEnd} and %{emphasisStart}merge requests%{emphasisEnd} (if any). Then below that is the %{emphasisStart}discussion%{emphasisEnd}, that's where most of the communication happens.%{lineBreak}%{lineBreak}On the right, there's a sidebar where you can view/change the %{emphasisStart}assignee, milestone, due date, labels, weight%{emphasisEnd}, etc."
-msgstr ""
-
-msgid "UserOnboardingTour|These are all the CI/CD pipelines we have for our %{emphasisStart}%{projectName}%{emphasisEnd} project.%{lineBreak}%{lineBreak}Here you can see the status of each pipeline, for what commit it's running for, its stages and the status for them."
-msgstr ""
-
-msgid "UserOnboardingTour|These are all the issues that are available for community contributions. Let's take a closer look at one of them."
-msgstr ""
-
-msgid "UserOnboardingTour|This is an overview of all merge requests in this project. Similarly to the issues overview it can be filtered down by things like labels, milestones, authors, assignees, etc."
-msgstr ""
-
-msgid "UserOnboardingTour|This is the repository for the %{emphasisStart}%{projectName}%{emphasisEnd} project. All our code is stored here. Feel free to explore and take a closer look at folders and files.%{lineBreak}%{lineBreak}Above the file structure you can see the latest commit, who the author is and the status of the CI/CD pipeline.%{lineBreak}%{lineBreak}If you scroll down below the file structure, you'll find the Readme of this project. This is defined in the README.md file at the root of the repository."
-msgstr ""
-
-msgid "UserOnboardingTour|Welcome to the project overview of the %{emphasisStart}%{projectName}%{emphasisEnd} project. This is the project that we use to work on GitLab. At first, a project seems like a simple repository, but at GitLab, a project is so much more.%{lineBreak}%{lineBreak}You can create projects for hosting your codebase, use it as an issue tracker, collaborate on code, and continuously build, test, and deploy your app with built-in GitLab CI/CD."
+msgid "UserList|created %{timeago}"
msgstr ""
msgid "UserProfile|Activity"
@@ -24471,6 +25116,9 @@ msgstr ""
msgid "UsersSelect|Unassigned"
msgstr ""
+msgid "Using %{codeStart}needs%{codeEnd} allows jobs to run before their stage is reached, as soon as their individual dependencies are met, which speeds up your pipelines."
+msgstr ""
+
msgid "Using %{code_start}::%{code_end} denotes a %{link_start}scoped label set%{link_end}"
msgstr ""
@@ -24555,9 +25203,6 @@ msgstr ""
msgid "Versions"
msgstr ""
-msgid "Very helpful"
-msgstr ""
-
msgid "View Documentation"
msgstr ""
@@ -24706,6 +25351,9 @@ msgstr "Publika"
msgid "VisibilityLevel|Unknown"
msgstr "Nekonata"
+msgid "Visit settings page"
+msgstr ""
+
msgid "VisualReviewApp|%{stepStart}Step 1%{stepEnd}. Copy the following script:"
msgstr ""
@@ -24799,6 +25447,9 @@ msgstr ""
msgid "VulnerabilityManagement|Something went wrong while trying to delete the comment. Please try again later."
msgstr ""
+msgid "VulnerabilityManagement|Something went wrong while trying to refresh the vulnerability. Please try again later."
+msgstr ""
+
msgid "VulnerabilityManagement|Something went wrong while trying to retrieve the vulnerability history. Please try again later."
msgstr ""
@@ -24841,6 +25492,9 @@ msgstr ""
msgid "Vulnerability|Class"
msgstr ""
+msgid "Vulnerability|Crash Address"
+msgstr ""
+
msgid "Vulnerability|Description"
msgstr ""
@@ -24916,9 +25570,6 @@ msgstr ""
msgid "We couldn't reach the Prometheus server. Either the server no longer exists or the configuration details need updating."
msgstr ""
-msgid "We created a short guided tour that will help you learn the basics of GitLab and how it will help you be better at your job. It should only take a couple of minutes. You will be guided by two types of helpers, best recognized by their color."
-msgstr ""
-
msgid "We detected potential spam in the %{humanized_resource_name}. Please solve the reCAPTCHA to proceed."
msgstr ""
@@ -24952,6 +25603,9 @@ msgstr ""
msgid "We've found no vulnerabilities"
msgstr ""
+msgid "Web Application Firewall"
+msgstr ""
+
msgid "Web IDE"
msgstr ""
@@ -25012,9 +25666,6 @@ msgstr ""
msgid "Welcome to GitLab.com<br>@%{name}!"
msgstr ""
-msgid "Welcome to the Guided GitLab Tour"
-msgstr ""
-
msgid "Welcome to the guided GitLab tour"
msgstr ""
@@ -25062,9 +25713,6 @@ msgstr ""
msgid "While it's rare to have no vulnerabilities, it can happen. In any event, we ask that you please double check your settings to make sure you've set up your dashboard correctly."
msgstr ""
-msgid "White helpers give contextual information."
-msgstr ""
-
msgid "Who can be an approver?"
msgstr ""
@@ -25110,12 +25758,18 @@ msgstr ""
msgid "WikiEmptyIssueMessage|Suggest wiki improvement"
msgstr ""
+msgid "WikiEmptyIssueMessage|You must be a group member in order to add wiki pages. If you have suggestions for how to improve the wiki for this group, consider opening an issue in the %{issues_link}."
+msgstr ""
+
msgid "WikiEmptyIssueMessage|You must be a project member in order to add wiki pages. If you have suggestions for how to improve the wiki for this project, consider opening an issue in the %{issues_link}."
msgstr ""
msgid "WikiEmptyIssueMessage|issue tracker"
msgstr ""
+msgid "WikiEmpty|A wiki is where you can store all the details about your group. This can include why you've created it, its principles, how to use it, and so on."
+msgstr ""
+
msgid "WikiEmpty|A wiki is where you can store all the details about your project. This can include why you've created it, its principles, how to use it, and so on."
msgstr ""
@@ -25125,12 +25779,21 @@ msgstr ""
msgid "WikiEmpty|Suggest wiki improvement"
msgstr ""
+msgid "WikiEmpty|The wiki lets you write documentation for your group"
+msgstr ""
+
msgid "WikiEmpty|The wiki lets you write documentation for your project"
msgstr ""
+msgid "WikiEmpty|This group has no wiki pages"
+msgstr ""
+
msgid "WikiEmpty|This project has no wiki pages"
msgstr ""
+msgid "WikiEmpty|You must be a group member in order to add wiki pages."
+msgstr ""
+
msgid "WikiEmpty|You must be a project member in order to add wiki pages."
msgstr ""
@@ -25407,15 +26070,15 @@ msgstr ""
msgid "You can now export your security dashboard to a CSV report."
msgstr ""
+msgid "You can now manage alert endpoint configuration in the Alerts section on the Operations settings page. Fields on this page have been deprecated."
+msgstr ""
+
msgid "You can now submit a merge request to get this change into the original branch."
msgstr ""
msgid "You can now submit a merge request to get this change into the original project."
msgstr ""
-msgid "You can only add files when you are on a branch"
-msgstr "Oni povas aldoni dosierojn nur kiam oni estas en branĉo"
-
msgid "You can only edit files when you are on a branch"
msgstr ""
@@ -25635,7 +26298,7 @@ msgstr ""
msgid "You need to upload a Google Takeout archive."
msgstr ""
-msgid "You reached %{usage_in_percent} of %{namespace_name}'s capacity (%{used_storage} of %{storage_limit})"
+msgid "You reached %{usage_in_percent} of %{namespace_name}'s storage capacity (%{used_storage} of %{storage_limit})"
msgstr ""
msgid "You tried to fork %{link_to_the_project} but it failed for the following reason:"
@@ -25938,13 +26601,13 @@ msgstr ""
msgid "Your subscription expired!"
msgstr ""
-msgid "Your subscription has been downgraded"
+msgid "Your subscription has been downgraded."
msgstr ""
-msgid "Your subscription will automatically renew in %{remaining_days}"
+msgid "Your subscription will automatically renew in %{remaining_days}."
msgstr ""
-msgid "Your subscription will expire in %{remaining_days}"
+msgid "Your subscription will expire in %{remaining_days}."
msgstr ""
msgid "Zoom meeting added"
@@ -26024,9 +26687,6 @@ msgstr ""
msgid "branch name"
msgstr ""
-msgid "build pipeline reference mismatch"
-msgstr ""
-
msgid "by"
msgstr ""
@@ -26045,6 +26705,9 @@ msgstr ""
msgid "cannot block others"
msgstr ""
+msgid "cannot contain HTML/XML tags, including any word between angle brackets (<,>)."
+msgstr ""
+
msgid "cannot include leading slash or directory traversal."
msgstr ""
@@ -26075,55 +26738,6 @@ msgstr ""
msgid "ciReport|%{remainingPackagesCount} more"
msgstr ""
-msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability"
-msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability for the source branch only"
-msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities for the source branch only"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerability"
-msgid_plural "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerabilities"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new vulnerability"
-msgid_plural "ciReport|%{reportType} %{status} detected %{newCount} new vulnerabilities"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities for the source branch only"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{fixedCount} fixed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} vulnerability for the source branch only"
-msgid_plural "ciReport|%{reportType} %{status} detected %{newCount} vulnerabilities for the source branch only"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "ciReport|%{reportType} %{status} detected no new vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected no vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected no vulnerabilities for the source branch only"
-msgstr ""
-
msgid "ciReport|%{reportType} is loading"
msgstr ""
@@ -26142,7 +26756,7 @@ msgstr ""
msgid "ciReport|All projects"
msgstr ""
-msgid "ciReport|All report types"
+msgid "ciReport|All scanner types"
msgstr ""
msgid "ciReport|All severities"
@@ -26166,6 +26780,9 @@ msgstr ""
msgid "ciReport|Container scanning detects known vulnerabilities in your docker images."
msgstr ""
+msgid "ciReport|Coverage Fuzzing"
+msgstr ""
+
msgid "ciReport|Create a merge request to implement this solution, or download and apply the patch manually."
msgstr ""
@@ -26324,6 +26941,9 @@ msgstr ""
msgid "customize"
msgstr ""
+msgid "data"
+msgstr ""
+
msgid "date must not be after 9999-12-31"
msgstr ""
@@ -26394,6 +27014,9 @@ msgstr ""
msgid "entries cannot contain HTML tags"
msgstr ""
+msgid "epic"
+msgstr ""
+
msgid "error"
msgstr ""
@@ -26415,9 +27038,6 @@ msgstr ""
msgid "expires on %{timebox_due_date}"
msgstr ""
-msgid "external_url"
-msgstr ""
-
msgid "failed"
msgstr ""
@@ -26453,9 +27073,6 @@ msgstr ""
msgid "from"
msgstr ""
-msgid "geo_node_name"
-msgstr ""
-
msgid "group"
msgstr ""
@@ -26477,9 +27094,6 @@ msgstr ""
msgid "https://your-bitbucket-server"
msgstr ""
-msgid "image"
-msgstr ""
-
msgid "image diff"
msgstr ""
@@ -26623,6 +27237,9 @@ msgstr ""
msgid "load it anyway"
msgstr ""
+msgid "loading"
+msgstr ""
+
msgid "locked by %{path_lock_user_name} %{created_at}"
msgstr ""
@@ -26751,9 +27368,6 @@ msgstr ""
msgid "mrWidget|Deployment statistics are not available currently"
msgstr ""
-msgid "mrWidget|Detect issues before deployment with a CI pipeline that continuously tests your code. We created a quick guide that will show you how to create one. Make your code more secure and more robust in just a minute."
-msgstr ""
-
msgid "mrWidget|Did not close"
msgstr ""
@@ -26916,6 +27530,9 @@ msgstr ""
msgid "mrWidget|To approve this merge request, please enter your password. This project requires all approvals to be authenticated."
msgstr ""
+msgid "mrWidget|Use %{linkStart}CI pipelines to test your code%{linkEnd}, simply add a GitLab CI configuration file to your project. It only takes a minute to make your code more secure and robust."
+msgstr ""
+
msgid "mrWidget|When this merge request is ready, remove the WIP: prefix from the title to allow it to be merged"
msgstr ""
@@ -26952,6 +27569,9 @@ msgstr ""
msgid "must be greater than start date"
msgstr ""
+msgid "must contain only valid frameworks"
+msgstr ""
+
msgid "my-awesome-group"
msgstr ""
@@ -26964,6 +27584,9 @@ msgstr ""
msgid "needs to be between 10 minutes and 1 month"
msgstr ""
+msgid "never"
+msgstr ""
+
msgid "never expires"
msgstr ""
@@ -26991,6 +27614,9 @@ msgstr "sciigoj per retpoÅto"
msgid "nounSeries|%{firstItem} and %{lastItem}"
msgstr ""
+msgid "nounSeries|%{item}"
+msgstr ""
+
msgid "nounSeries|%{item}, %{nextItem}"
msgstr ""
@@ -27127,6 +27753,9 @@ msgstr ""
msgid "revised"
msgstr ""
+msgid "satisfied"
+msgstr ""
+
msgid "score"
msgstr ""
@@ -27157,6 +27786,9 @@ msgstr ""
msgid "severity|Unknown"
msgstr ""
+msgid "should be an array of %{object_name} objects"
+msgstr ""
+
msgid "should be greater than or equal to %{access} inherited membership from group %{group_name}"
msgstr ""
@@ -27211,10 +27843,13 @@ msgstr ""
msgid "suggestPipeline|2/2: Commit your changes"
msgstr ""
-msgid "suggestPipeline|Commit the changes and your pipeline will automatically run for the first time."
+msgid "suggestPipeline|Choose %{boldStart}Code Quality%{boldEnd} to add a pipeline that tests the quality of your code."
msgstr ""
-msgid "suggestPipeline|We recommend the %{boldStart}Code Quality%{boldEnd} template, which will add a report widget to your Merge Requests. This way you’ll learn about code quality degradations much sooner. %{footerStart} Goodbye technical debt! %{footerEnd}"
+msgid "suggestPipeline|The template is ready! You can now commit it to create your first pipeline."
+msgstr ""
+
+msgid "suggestPipeline|We’re adding a GitLab CI configuration file to add a pipeline to the project. You could create it manually, but we recommend that you start with a GitLab template that works out of the box."
msgstr ""
msgid "syntax is correct"
@@ -27274,6 +27909,9 @@ msgstr ""
msgid "uses Kubernetes clusters to deploy your code!"
msgstr ""
+msgid "v%{version} published %{timeAgo}"
+msgstr ""
+
msgid "verify ownership"
msgstr ""
diff --git a/locale/es/gitlab.po b/locale/es/gitlab.po
index 63a85ad2419..f3ea0569688 100644
--- a/locale/es/gitlab.po
+++ b/locale/es/gitlab.po
@@ -2,7 +2,7 @@ msgid ""
msgstr ""
"Project-Id-Version: gitlab-ee\n"
"Report-Msgid-Bugs-To: \n"
-"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Last-Translator: \n"
"Language-Team: Spanish\n"
"Language: es_ES\n"
"MIME-Version: 1.0\n"
@@ -10,9 +10,11 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Crowdin-Project: gitlab-ee\n"
+"X-Crowdin-Project-ID: 288872\n"
"X-Crowdin-Language: es-ES\n"
"X-Crowdin-File: /master/locale/gitlab.pot\n"
-"PO-Revision-Date: 2020-06-08 15:14\n"
+"X-Crowdin-File-ID: 6\n"
+"PO-Revision-Date: 2020-07-02 01:24\n"
msgid " %{start} to %{end}"
msgstr " %{start} hasta %{end}"
@@ -21,7 +23,7 @@ msgid " (from %{timeoutSource})"
msgstr " (desde %{timeoutSource})"
msgid " Collected %{time}"
-msgstr ""
+msgstr " Capturado %{time}"
msgid " Please sign in."
msgstr " Por favor, inicie sesión."
@@ -76,8 +78,8 @@ msgstr[1] "%d URLs escaneadas"
msgid "%d approver"
msgid_plural "%d approvers"
-msgstr[0] ""
-msgstr[1] ""
+msgstr[0] "%d aprobador"
+msgstr[1] "%d aprobadores"
msgid "%d approver (you've approved)"
msgid_plural "%d approvers (you've approved)"
@@ -106,8 +108,8 @@ msgstr[1] "%d comentarios"
msgid "%d comment on this commit"
msgid_plural "%d comments on this commit"
-msgstr[0] ""
-msgstr[1] ""
+msgstr[0] "%d comentario en este commit"
+msgstr[1] "%d comentarios en este commit"
msgid "%d commit"
msgid_plural "%d commits"
@@ -129,13 +131,13 @@ msgstr "%d commits"
msgid "%d contribution"
msgid_plural "%d contributions"
-msgstr[0] "%d contribución"
-msgstr[1] "%d contribuciones"
+msgstr[0] "%d colaboración"
+msgstr[1] "%d colaboraciones"
msgid "%d day"
msgid_plural "%d days"
-msgstr[0] ""
-msgstr[1] ""
+msgstr[0] "%d día"
+msgstr[1] "%d días"
msgid "%d error"
msgid_plural "%d errors"
@@ -149,8 +151,8 @@ msgstr[1] "%d exportadores"
msgid "%d failed"
msgid_plural "%d failed"
-msgstr[0] ""
-msgstr[1] ""
+msgstr[0] "%d fallido"
+msgstr[1] "%d fallidos"
msgid "%d fixed test result"
msgid_plural "%d fixed test results"
@@ -197,6 +199,11 @@ msgid_plural "%d metrics"
msgstr[0] "%d métrica"
msgstr[1] "%d métricas"
+msgid "%d milestone"
+msgid_plural "%d milestones"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "%d minute"
msgid_plural "%d minutes"
msgstr[0] "%d minutos"
@@ -209,8 +216,8 @@ msgstr[1] "%d comentarios más"
msgid "%d personal project will be removed and cannot be restored."
msgid_plural "%d personal projects will be removed and cannot be restored."
-msgstr[0] ""
-msgstr[1] ""
+msgstr[0] "Se eliminará el proyecto personal %d y no podrá ser restaurado"
+msgstr[1] "Se eliminarán los proyectos personales %d y no podrán ser restaurados"
msgid "%d project"
msgid_plural "%d projects"
@@ -234,23 +241,18 @@ msgstr[1] "%d shards seleccionados"
msgid "%d tag"
msgid_plural "%d tags"
-msgstr[0] ""
-msgstr[1] ""
+msgstr[0] "%d etiqueta"
+msgstr[1] "%d etiquetas"
msgid "%d unresolved thread"
msgid_plural "%d unresolved threads"
msgstr[0] "%d hilo sin resolver"
msgstr[1] "%d hilos sin resolver"
-msgid "%d url scanned"
-msgid_plural "%d urls scanned"
-msgstr[0] ""
-msgstr[1] ""
-
msgid "%d vulnerability"
msgid_plural "%d vulnerabilities"
-msgstr[0] ""
-msgstr[1] ""
+msgstr[0] "%d vulnerabilidad"
+msgstr[1] "%d vulnerabilidades"
msgid "%d vulnerability dismissed"
msgid_plural "%d vulnerabilities dismissed"
@@ -324,9 +326,15 @@ msgstr[1] "%{count} comentarios pendientes"
msgid "%{count} related %{pluralized_subject}: %{links}"
msgstr "%{count} %{pluralized_subject} relacionados: %{links}"
+msgid "%{dashboard_path} could not be found."
+msgstr ""
+
msgid "%{days} days until tags are automatically removed"
msgstr "%{days} días hasta que las etiquetas se eliminen automáticamente"
+msgid "%{deployLinkStart}Use a template to deploy to ECS%{deployLinkEnd}, or use a docker image to %{commandsLinkStart}run AWS commands in GitLab CI/CD%{commandsLinkEnd}."
+msgstr ""
+
msgid "%{description}- Sentry event: %{errorUrl}- First seen: %{firstSeen}- Last seen: %{lastSeen} %{countLabel}: %{count}%{userCountLabel}: %{userCount}"
msgstr ""
@@ -367,14 +375,20 @@ msgid "%{group_name} uses group managed accounts. You need to create a new GitLa
msgstr "%{group_name} utiliza cuentas administradas de grupo. Debe crear una nueva cuenta de GitLab que será administrada por %{group_name}."
msgid "%{host} sign-in from new location"
+msgstr "%{host} inicia sesión desde una nueva ubicación"
+
+msgid "%{icon}You are about to add %{usersTag} people to the discussion. They will all receive a notification."
msgstr ""
-msgid "%{icon}You are about to add %{usersTag} people to the discussion. Proceed with caution."
-msgstr "%{icon} Está apunto de añadir %{usersTag} gente a la discusión.Por favor, proceda con precaución."
+msgid "%{integrations_link_start}Integrations%{link_end} enable you to make third-party applications part of your GitLab workflow. If the available integrations don't meet your needs, consider using a %{webhooks_link_start}webhook%{link_end}."
+msgstr ""
msgid "%{issuableType} will be removed! Are you sure?"
msgstr "¡Va a eliminar %{issuableType}! ¿Está seguro de que desea realizar esta acción?"
+msgid "%{issuesCount} issues in this group"
+msgstr ""
+
msgid "%{issuesSize} issues"
msgstr "%{issuesSize} incidencias"
@@ -382,28 +396,31 @@ msgid "%{issuesSize} issues with a limit of %{maxIssueCount}"
msgstr "%{issuesSize} incidencias con un límite de %{maxIssueCount}"
msgid "%{labelStart}Class:%{labelEnd} %{class}"
-msgstr ""
+msgstr "%{labelStart}Clase:%{labelEnd} %{class}"
-msgid "%{labelStart}Confidence:%{labelEnd} %{confidence}"
+msgid "%{labelStart}Evidence:%{labelEnd} %{evidence}"
msgstr ""
msgid "%{labelStart}File:%{labelEnd} %{file}"
-msgstr ""
+msgstr "%{labelStart}Archivo:%{labelEnd} %{file}"
msgid "%{labelStart}Image:%{labelEnd} %{image}"
-msgstr ""
+msgstr "%{labelStart}Imagen:%{labelEnd} %{image}"
msgid "%{labelStart}Method:%{labelEnd} %{method}"
-msgstr ""
+msgstr "%{labelStart}Método:%{labelEnd} %{method}"
msgid "%{labelStart}Namespace:%{labelEnd} %{namespace}"
-msgstr ""
+msgstr "%{labelStart}Espacio de nombres:%{labelEnd} %{namespace}"
msgid "%{labelStart}Report Type:%{labelEnd} %{reportType}"
+msgstr "%{labelStart}Tipo de informe:%{labelEnd} %{reportType}"
+
+msgid "%{labelStart}Scanner:%{labelEnd} %{scanner}"
msgstr ""
msgid "%{labelStart}Severity:%{labelEnd} %{severity}"
-msgstr ""
+msgstr "%{labelStart}Gravedad:%{labelEnd} %{severity}"
msgid "%{label_for_message} unavailable"
msgstr "%{label_for_message} no disponible"
@@ -426,9 +443,6 @@ msgstr ""
msgid "%{link_start}Read more%{link_end} about role permissions"
msgstr "%{link_start}Lea más%{link_end} acerca de los permisos de rol"
-msgid "%{link} can be used for binding events when something is happening within the project."
-msgstr ""
-
msgid "%{listToShow}, and %{awardsListLength} more."
msgstr "%{listToShow}, y %{awardsListLength} más."
@@ -462,9 +476,12 @@ msgstr "%{name} contenido en %{resultsString}"
msgid "%{name} found %{resultsString}"
msgstr "%{name} encontrado %{resultsString}"
-msgid "%{name} is scheduled for %{action}"
+msgid "%{name} is already being used for another emoji"
msgstr ""
+msgid "%{name} is scheduled for %{action}"
+msgstr "%{name} está programado para %{action}"
+
msgid "%{name}'s avatar"
msgstr "Avatar de %{name}"
@@ -492,7 +509,7 @@ msgid "%{openedIssues} open, %{closedIssues} closed"
msgstr "%{openedIssues} abiertas, %{closedIssues} cerradas"
msgid "%{percentage}%% weight completed"
-msgstr ""
+msgstr "%{percentage}%% del peso completado"
msgid "%{percent}%% complete"
msgstr "%{percent}%% completado"
@@ -509,21 +526,48 @@ msgstr "%{placeholder} no es un tema válido"
msgid "%{primary} (%{secondary})"
msgstr "%{primary} (%{secondary})"
+msgid "%{ref} cannot be added: %{error}"
+msgstr ""
+
msgid "%{releases} release"
msgid_plural "%{releases} releases"
msgstr[0] "%{releases} lanzamiento"
msgstr[1] "%{releases} lanzamientos"
msgid "%{remaining_approvals} left"
+msgstr "quedan %{remaining_approvals} "
+
+msgid "%{reportType} %{status} detected %{critical} critical and %{high} high severity vulnerabilities out of %{total}."
msgstr ""
-msgid "%{retryButtonStart}Try again%{retryButtonEnd} or %{newFileButtonStart}attach a new file%{newFileButtonEnd}"
+msgid "%{reportType} %{status} detected %{critical} critical and %{high} high severity vulnerabilities."
msgstr ""
-msgid "%{screenreaderOnlyStart}Keyboard shorcuts%{screenreaderOnlyEnd} Disabled"
+msgid "%{reportType} %{status} detected %{critical} critical severity vulnerabilities out of %{total}."
msgstr ""
-msgid "%{screenreaderOnlyStart}Keyboard shorcuts%{screenreaderOnlyEnd} Enabled"
+msgid "%{reportType} %{status} detected %{critical} critical severity vulnerability."
+msgid_plural "%{reportType} %{status} detected %{critical} critical severity vulnerabilities."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{reportType} %{status} detected %{high} high severity vulnerabilities out of %{total}."
+msgstr ""
+
+msgid "%{reportType} %{status} detected %{high} high severity vulnerability."
+msgid_plural "%{reportType} %{status} detected %{high} high severity vulnerabilities."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{reportType} %{status} detected %{other} vulnerability."
+msgid_plural "%{reportType} %{status} detected %{other} vulnerabilities."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{reportType} %{status} detected no new vulnerabilities."
+msgstr ""
+
+msgid "%{retryButtonStart}Try again%{retryButtonEnd} or %{newFileButtonStart}attach a new file%{newFileButtonEnd}"
msgstr ""
msgid "%{service_title} %{message}."
@@ -620,9 +664,15 @@ msgstr "%{title} cambios"
msgid "%{token}..."
msgstr "%{token}..."
-msgid "%{totalWeight} total weight"
+msgid "%{totalCpu} (%{freeSpacePercentage}%{percentSymbol} free)"
msgstr ""
+msgid "%{totalMemory} (%{freeSpacePercentage}%{percentSymbol} free)"
+msgstr ""
+
+msgid "%{totalWeight} total weight"
+msgstr "%{totalWeight} peso total"
+
msgid "%{total} open issue weight"
msgstr "%{total} incidencias abiertas"
@@ -650,11 +700,17 @@ msgstr "%{value} s"
msgid "%{verb} %{time_spent_value} spent time."
msgstr "%{verb} %{time_spent_value} tiempo gastado."
+msgid "%{webhooks_link_start}%{webhook_type}%{link_end} enable you to send notifications to web applications in response to events in a group or project."
+msgstr ""
+
+msgid "%{webhooks_link_start}%{webhook_type}%{link_end} enable you to send notifications to web applications in response to events in a group or project. We recommend using an %{integrations_link_start}integration%{link_end} in preference to a webhook."
+msgstr ""
+
msgid "'%{level}' is not a valid visibility level"
msgstr "'%{level}' no es un nivel de visibilidad válido"
msgid "'%{name}' stage already exists"
-msgstr ""
+msgstr "La etapa '%{name}' ya existe"
msgid "'%{source}' is not a import source"
msgstr "'%{source}' no es una fuente valida para importar"
@@ -676,9 +732,6 @@ msgstr "(%{mrCount} fusionado)"
msgid "(No changes)"
msgstr "(Sin cambios)"
-msgid "(Show all)"
-msgstr "(Mostrar todo)"
-
msgid "(check progress)"
msgstr "(comprobar el progreso)"
@@ -745,6 +798,9 @@ msgstr "- mostrar menos"
msgid "0 for unlimited"
msgstr "0 para ilimitado"
+msgid "0 for unlimited, only effective with remote storage enabled."
+msgstr ""
+
msgid "1 %{type} addition"
msgid_plural "%{count} %{type} additions"
msgstr[0] "1 adición de %{type}"
@@ -892,6 +948,12 @@ msgstr "<code>\"johnsmith@example.com\": \"johnsm...@example.com\"</code> añadi
msgid "<code>\"johnsmith@example.com\": \"johnsmith@example.com\"</code> will add \"By <a href=\"#\">johnsmith@example.com</a>\" to all issues and comments originally created by johnsmith@example.com. By default, the email address or username is masked to ensure the user's privacy. Use this option if you want to show the full email address."
msgstr "<code>\"johnsmith@example.com\": \"johnsmith@example.com\"</code> añadirá \"Por <a href=\"#\">johnsmith@example.com</a>\" a todas las incidencias y comentarios originalmente creados por johnsmith@example.com. Por defecto, el correo electrónico o el nombre de usuario está oculto para asegurar la privacidad del usuario. Utilice esta opción si desea mostrar la dirección de correo electrónico completa."
+msgid "<code>Masked</code> to prevent the values from being displayed in job logs (must match certain regexp requirements)."
+msgstr ""
+
+msgid "<code>Protected</code> to expose them to protected branches or tags only."
+msgstr ""
+
msgid "<namespace / project>"
msgstr "<namespace / project>"
@@ -919,9 +981,6 @@ msgstr "Un ejecutor es un proceso que ejecuta un trabajo. Puede configurar tanto
msgid "A .NET Core console application template, customizable for any .NET Core project"
msgstr "Una plantilla de aplicación de consola .NET Core, personalizable para cualquier proyecto .NET Core"
-msgid "A DAG must have two dependent jobs to be visualized on this tab."
-msgstr ""
-
msgid "A GitBook site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
msgstr "Un sitio basado en GitBook que utiliza Netlify como herramienta de CI/CD en lugar de GitLab, pero con las demás excelentes características de GitLab."
@@ -952,6 +1011,12 @@ msgstr "No se puede elegir una rama por defecto para un proyecto vacío."
msgid "A deleted user"
msgstr "Un usuario eliminado"
+msgid "A file has been changed."
+msgstr "Se ha modificado un archivo."
+
+msgid "A file was not found."
+msgstr "No se encontró un archivo."
+
msgid "A file with '%{file_name}' already exists in %{branch} branch"
msgstr "Ya existe un archivo con '%{file_name}' en %{branch} rama"
@@ -962,7 +1027,7 @@ msgid "A group is a collection of several projects"
msgstr ""
msgid "A group represents your organization in GitLab. Groups allow you to manage users and collaborate across multiple projects."
-msgstr ""
+msgstr "Un grupo representa a su organización en GitLab. Los grupos que le permiten administrar usuarios y colaborar a través de múltiples proyectos."
msgid "A member of the abuse team will review your report as soon as possible."
msgstr "Un miembro del equipo revisará su informe tan pronto como sea posible."
@@ -1018,8 +1083,8 @@ msgstr ""
msgid "A subscription will trigger a new pipeline on the default branch of this project when a pipeline successfully completes for a new tag on the %{default_branch_docs} of the subscribed project."
msgstr ""
-msgid "A terraform report was generated in your pipelines."
-msgstr ""
+msgid "A suggestion is not applicable."
+msgstr "No es aplicable una sugerencia."
msgid "A user with write access to the source branch selected this option"
msgstr "Un usuario con acceso de escritura a la rama origen seleccionó esta opción"
@@ -1189,6 +1254,9 @@ msgstr "Cuenta: %{account}"
msgid "Action to take when receiving an alert."
msgstr "Acción a realizar cuando se reciba una alerta."
+msgid "Actions"
+msgstr ""
+
msgid "Activate"
msgstr "Activar"
@@ -1208,7 +1276,7 @@ msgid "Active Users:"
msgstr "Usuarios activos:"
msgid "Active users"
-msgstr ""
+msgstr "Usuarios activos"
msgid "Activity"
msgstr "Actividad"
@@ -1245,6 +1313,9 @@ msgstr "Añadir clúster de Kubernetes"
msgid "Add LICENSE"
msgstr "Añadir LICENSE"
+msgid "Add New Node"
+msgstr ""
+
msgid "Add README"
msgstr "Añadir README"
@@ -1282,13 +1353,13 @@ msgid "Add a homepage to your wiki that contains information about your project
msgstr "Añada una página de inicio a su wiki que contenga información sobre su proyecto y GitLab la mostrará aquí en lugar de este mensaje."
msgid "Add a line"
-msgstr ""
+msgstr "Añadir una línea"
msgid "Add a link"
msgstr "Añadir un enlace"
msgid "Add a new issue"
-msgstr ""
+msgstr "Añadir una nueva incidencia"
msgid "Add a numbered list"
msgstr "Añadir una lista numerada"
@@ -1306,10 +1377,10 @@ msgid "Add an SSH key"
msgstr "Añadir una clave SSH"
msgid "Add an existing issue"
-msgstr ""
+msgstr "Añadir una incidencia existente"
msgid "Add an impersonation token"
-msgstr ""
+msgstr "Añadir un token de suplantación"
msgid "Add an issue"
msgstr "Agregar una incidencia"
@@ -1320,6 +1391,9 @@ msgstr "Añadir otro enlace"
msgid "Add approval rule"
msgstr "Agregar una regla de aprobación"
+msgid "Add approvers"
+msgstr ""
+
msgid "Add bold text"
msgstr "Añadir texto en negrita"
@@ -1377,6 +1451,9 @@ msgstr "Añadir solicitud manualmente"
msgid "Add strikethrough text"
msgstr ""
+msgid "Add suggestion to batch"
+msgstr "Añadir sugerencia para el lote"
+
msgid "Add system hook"
msgstr "Añadir hook del sistema"
@@ -1474,6 +1551,9 @@ msgid "Admin Area"
msgstr "Ãrea de administración"
msgid "Admin Note"
+msgstr "Nota del administrador"
+
+msgid "Admin Notifications"
msgstr ""
msgid "Admin Overview"
@@ -1513,13 +1593,13 @@ msgid "AdminArea|Included Free in license"
msgstr "AdminArea|Incluido gratis en la licencia"
msgid "AdminArea|Maintainer"
-msgstr ""
+msgstr "Mantenedor"
msgid "AdminArea|Owner"
msgstr "Propietario"
msgid "AdminArea|Reporter"
-msgstr ""
+msgstr "Reportador"
msgid "AdminArea|Stop all jobs"
msgstr "Detener todos los trabajos"
@@ -1537,13 +1617,13 @@ msgid "AdminArea|Total users"
msgstr "Usuarios totales"
msgid "AdminArea|Users statistics"
-msgstr ""
+msgstr "Estadísticas de usuarios"
msgid "AdminArea|Users with highest role"
-msgstr ""
+msgstr "Usuarios con el rol más alto"
msgid "AdminArea|Users without a Group and Project"
-msgstr ""
+msgstr "Usuarios sin grupo y proyecto"
msgid "AdminArea|You’re about to stop all jobs.This will halt all current jobs that are running."
msgstr "Está a punto de detener todos los trabajos. Esto detendrá todos los trabajos que se están ejecutando actualmente."
@@ -1567,7 +1647,7 @@ msgid "AdminProjects|Delete project"
msgstr "Eliminar proyecto"
msgid "AdminSettings|Apply integration settings to all Projects"
-msgstr ""
+msgstr "Aplicar la configuración de integración a todos los proyectos"
msgid "AdminSettings|Auto DevOps domain"
msgstr "Dominio Auto DevOps"
@@ -1582,9 +1662,12 @@ msgid "AdminSettings|Environment variables are protected by default"
msgstr "Las variables de entorno están protegidas por defecto"
msgid "AdminSettings|Go to General Settings"
-msgstr ""
+msgstr "Ir a los ajustes generales"
msgid "AdminSettings|Integrations configured here will automatically apply to all projects on this instance."
+msgstr "Las integraciones configuradas aquí se aplicarán automáticamente a todos los proyectos en esta instancia."
+
+msgid "AdminSettings|Moved to integrations"
msgstr ""
msgid "AdminSettings|No required pipeline"
@@ -1690,7 +1773,7 @@ msgid "AdminUsers|Deactivating a user has the following effects:"
msgstr "Desactivar un usuario tiene los siguientes efectos:"
msgid "AdminUsers|Delete User %{username} and contributions?"
-msgstr "¿Eliminar el usuario %{username} y sus contribuciones?"
+msgstr "¿Eliminar el usuario %{username} y sus colaboraciones?"
msgid "AdminUsers|Delete User %{username}?"
msgstr "¿Eliminar el usuario %{username}?"
@@ -1699,7 +1782,7 @@ msgid "AdminUsers|Delete user"
msgstr "Eliminar usuario"
msgid "AdminUsers|Delete user and contributions"
-msgstr "Eliminar el usuario y sus contribuciones"
+msgstr "Eliminar el usuario y sus colaboraciones"
msgid "AdminUsers|External"
msgstr "Externos"
@@ -1789,7 +1872,7 @@ msgid "Advanced"
msgstr "Avanzado"
msgid "Advanced Settings"
-msgstr ""
+msgstr "Configuración avanzada"
msgid "Advanced permissions, Large File Storage and Two-Factor authentication settings."
msgstr "Permisos avanzados, almacenamiento de grandes archivos y configuraciones de autenticación de dos factores."
@@ -1817,98 +1900,113 @@ msgid_plural "Alerts"
msgstr[0] "Alerta"
msgstr[1] "Alertas"
+msgid "AlertManagement| assign yourself"
+msgstr ""
+
msgid "AlertManagement|Acknowledged"
msgstr "Confirmado"
msgid "AlertManagement|Alert"
+msgstr "Alerta"
+
+msgid "AlertManagement|Alert assignee(s): %{assignees}"
msgstr ""
msgid "AlertManagement|Alert detail"
-msgstr ""
+msgstr "Detalle de alerta"
msgid "AlertManagement|Alert details"
-msgstr ""
+msgstr "Detalles de la alerta"
msgid "AlertManagement|Alert status: %{status}"
-msgstr ""
+msgstr "Estado de la alerta: %{status}"
msgid "AlertManagement|Alerts"
-msgstr ""
+msgstr "Alertas"
msgid "AlertManagement|All alerts"
+msgstr "Todas las alertas"
+
+msgid "AlertManagement|Assign To"
msgstr ""
msgid "AlertManagement|Assign status"
+msgstr "Asignar un estado"
+
+msgid "AlertManagement|Assignee"
msgstr ""
msgid "AlertManagement|Assignees"
-msgstr ""
+msgstr "Asignados"
msgid "AlertManagement|Authorize external service"
-msgstr ""
+msgstr "Autorizar un servicio externo"
msgid "AlertManagement|Create issue"
-msgstr ""
+msgstr "Crear una incidencia"
msgid "AlertManagement|Critical"
-msgstr ""
+msgstr "Crítico"
msgid "AlertManagement|Display alerts from all your monitoring tools directly within GitLab. Streamline the investigation of your alerts and the escalation of alerts to incidents."
msgstr ""
msgid "AlertManagement|Edit"
-msgstr ""
-
-msgid "AlertManagement|End time"
-msgstr "Hora de finalización"
+msgstr "Editar"
msgid "AlertManagement|Events"
msgstr "Eventos"
msgid "AlertManagement|High"
-msgstr ""
+msgstr "Alta"
msgid "AlertManagement|Info"
-msgstr ""
+msgstr "Información"
msgid "AlertManagement|Low"
-msgstr ""
+msgstr "Bajo"
msgid "AlertManagement|Medium"
-msgstr ""
+msgstr "Medio"
msgid "AlertManagement|More information"
msgstr "Más información"
-msgid "AlertManagement|No alert data to display."
+msgid "AlertManagement|No Matching Results"
msgstr ""
-msgid "AlertManagement|No alerts available to display. If you think you're seeing this message in error, refresh the page."
-msgstr "No hay alertas disponibles para mostrar. Si cree que estás viendo este mensaje por error, por favor actualice la página."
+msgid "AlertManagement|No alert data to display."
+msgstr "No hay datos de la alerta para mostrar."
+
+msgid "AlertManagement|No alerts available to display. See %{linkStart}enabling alert management%{linkEnd} for more information on adding alerts to the list."
+msgstr ""
msgid "AlertManagement|No alerts to display."
msgstr "No hay alertas que mostrar."
msgid "AlertManagement|None"
+msgstr "Ninguno"
+
+msgid "AlertManagement|None -"
msgstr ""
msgid "AlertManagement|Open"
-msgstr ""
+msgstr "Abrir"
msgid "AlertManagement|Overview"
msgstr "Descripción general"
msgid "AlertManagement|Reported %{when}"
-msgstr ""
+msgstr "Reportado %{when}"
msgid "AlertManagement|Reported %{when} by %{tool}"
-msgstr ""
+msgstr "Reportado %{when} por %{tool}"
msgid "AlertManagement|Resolved"
msgstr "Resuelto"
msgid "AlertManagement|Service"
-msgstr ""
+msgstr "Servicio"
msgid "AlertManagement|Severity"
msgstr "Gravedad"
@@ -1928,23 +2026,32 @@ msgstr ""
msgid "AlertManagement|There was an error displaying the alerts. Confirm your endpoint's configuration details to ensure alerts appear."
msgstr ""
+msgid "AlertManagement|There was an error while updating the assignee(s) list. Please try again."
+msgstr ""
+
+msgid "AlertManagement|There was an error while updating the assignee(s) of the alert. Please try again."
+msgstr ""
+
msgid "AlertManagement|There was an error while updating the status of the alert. Please try again."
msgstr ""
-msgid "AlertManagement|Tool"
+msgid "AlertManagement|This assignee cannot be assigned to this alert."
msgstr ""
+msgid "AlertManagement|Tool"
+msgstr "Herramienta"
+
msgid "AlertManagement|Triggered"
-msgstr ""
+msgstr "Activado"
msgid "AlertManagement|Unassigned"
-msgstr ""
+msgstr "Desasignado"
msgid "AlertManagement|Unknown"
-msgstr ""
+msgstr "Desconocido"
msgid "AlertManagement|View issue"
-msgstr ""
+msgstr "Ver una incidencia"
msgid "AlertService|Review your external service's documentation to learn where to provide this information to your external service, and the %{linkStart}GitLab documentation%{linkEnd} to learn more about configuring your endpoint."
msgstr ""
@@ -2016,7 +2123,7 @@ msgid "All security scans are enabled because %{linkStart}Auto DevOps%{linkEnd}
msgstr "Todas las exploraciones de seguridad están habilitadas porque %{linkStart}Auto DevOps%{linkEnd} está habilitado en este proyecto"
msgid "All threads resolved"
-msgstr ""
+msgstr "Todos los hilos resueltos"
msgid "All users"
msgstr "Todos los usuarios"
@@ -2078,6 +2185,9 @@ msgstr "Permitir a los usuarios registrar cualquier aplicación para usar GitLab
msgid "Allow users to request access (if visibility is public or internal)"
msgstr "Permitir a los usuarios solicitar acceso (si la visibilidad es pública o interna)"
+msgid "Allowed Geo IP"
+msgstr ""
+
msgid "Allowed email domain restriction only permitted for top-level groups"
msgstr "Sólo los grupos de nivel superior pueden restringir los dominios de correo electrónico permitidos"
@@ -2111,6 +2221,9 @@ msgstr "La integración con Amazon EKS le permite provisionar clústeres EKS des
msgid "Amazon Web Services"
msgstr "Amazon Web Services"
+msgid "Amazon Web Services Logo"
+msgstr ""
+
msgid "Amazon authentication is not %{link_start}correctly configured%{link_end}. Ask your GitLab administrator if you want to use this service."
msgstr "La autenticación de Amazon no está %{link_start}correctamente configurada%{link_end}. Preguntele a su administrador de GitLab sí desea utilizar este servicio."
@@ -2151,7 +2264,7 @@ msgid "An error occurred fetching the dropdown data."
msgstr "Se ha producido un error al recuperar los datos de la lista desplegable."
msgid "An error occurred fetching the project authors."
-msgstr ""
+msgstr "Se ha producido un error al recuperar los autores del proyecto."
msgid "An error occurred previewing the blob"
msgstr "Ha ocurrido un error visualizando el blob"
@@ -2168,11 +2281,17 @@ msgstr "Se ha producido un error al intentar resolver una discusión. Por favor,
msgid "An error occurred when updating the issue weight"
msgstr "Se ha producido un error al actualizar el tamaño de la incidencia"
+msgid "An error occurred while acknowledging the notification. Refresh the page and try again."
+msgstr ""
+
+msgid "An error occurred while adding approvers"
+msgstr ""
+
msgid "An error occurred while adding formatted title for epic"
msgstr "Se ha producido un error al añadir el título formateado para la tarea épica"
-msgid "An error occurred while checking group path"
-msgstr "Se ha producido un error al comprobar la ruta del grupo"
+msgid "An error occurred while checking group path. Please refresh and try again."
+msgstr ""
msgid "An error occurred while committing your changes."
msgstr "Se ha producido un error al hacer commit de sus cambios."
@@ -2313,10 +2432,10 @@ msgid "An error occurred while loading merge requests."
msgstr "Se ha producido un error al cargar los merge requests."
msgid "An error occurred while loading milestones"
-msgstr ""
+msgstr "Se ha producido un error al cargar los hitos"
-msgid "An error occurred while loading terraform report"
-msgstr "Se ha producido un error al cargar el informe de terraform"
+msgid "An error occurred while loading project creation UI"
+msgstr ""
msgid "An error occurred while loading the data. Please try again."
msgstr "Se ha producido un error al cargar los datos. Por favor, inténtelo de nuevo."
@@ -2358,7 +2477,7 @@ msgid "An error occurred while parsing recent searches"
msgstr "Se ha producido un error al analizar búsquedas recientes"
msgid "An error occurred while parsing the file."
-msgstr ""
+msgstr "Se ha producido un error al analizar el archivo."
msgid "An error occurred while removing epics."
msgstr "Se ha producido un error al eliminar las tareas épicas."
@@ -2369,6 +2488,9 @@ msgstr "Se ha producido un error al eliminar las incidencias."
msgid "An error occurred while rendering preview broadcast message"
msgstr ""
+msgid "An error occurred while rendering the editor"
+msgstr ""
+
msgid "An error occurred while reordering issues."
msgstr "Se ha producido un error al ordenar las incidencias."
@@ -2391,7 +2513,7 @@ msgid "An error occurred while saving the template. Please check if the template
msgstr "Se ha producido un error al guardar la plantilla. Por favor, compruebe si la plantilla existe."
msgid "An error occurred while searching for milestones"
-msgstr ""
+msgstr "Se ha producido un error durante la búsqueda de hitos"
msgid "An error occurred while subscribing to notifications."
msgstr "Se ha producido un error al suscribirse a las notificaciones."
@@ -2421,22 +2543,22 @@ msgid "An error occurred. Please try again."
msgstr "Se ha producido un error. Por favor inténtelo de nuevo."
msgid "An error ocurred while loading your content. Please try again."
-msgstr ""
+msgstr "Se ha producido un error al cargar su contenido. Por favor, inténtelo de nuevo."
msgid "An example project for managing Kubernetes clusters integrated with GitLab."
msgstr ""
msgid "An instance-level serverless domain already exists."
-msgstr ""
+msgstr "Ya existe un dominio serverless a nivel de instancia."
msgid "An issue already exists"
-msgstr ""
+msgstr "Ya existe una incidencia"
msgid "An issue can be a bug, a todo or a feature request that needs to be discussed in a project. Besides, issues are searchable and filterable."
msgstr "Una incidencia puede ser un error, una tarea pendiente o una solicitud de una nueva funcionalidad que debe tratarse en un proyecto. Además, recuerde que las incidencias se pueden buscar y filtrar."
msgid "An unauthenticated user"
-msgstr ""
+msgstr "Un usuario no autenticado"
msgid "An unexpected error occurred while checking the project environment."
msgstr "Se ha producido un error inesperado al comprobar el entorno del proyecto."
@@ -2454,7 +2576,7 @@ msgid "An unexpected error occurred while stopping the Web Terminal."
msgstr "Se ha producido un error inesperado al detener el Terminal Web."
msgid "An unknown error occurred while loading this graph."
-msgstr ""
+msgstr "Se ha producido un error desconocido mientras se cargaba este gráfico."
msgid "Analytics"
msgstr "Analíticas"
@@ -2465,7 +2587,7 @@ msgstr "Analizar una revisión de la versión de la aplicación web."
msgid "Analyze your dependencies for known vulnerabilities."
msgstr "Analizar sus dependencias en busca de vulnerabilidades conocidas."
-msgid "Analyze your source code and git history for secrets"
+msgid "Analyze your source code and git history for secrets."
msgstr ""
msgid "Analyze your source code for known vulnerabilities."
@@ -2502,13 +2624,13 @@ msgid "Any encrypted tokens"
msgstr "Cualquier token encriptado"
msgid "Any label"
-msgstr ""
+msgstr "Cualquier etiqueta"
msgid "Any member with Developer or higher permissions to the project."
msgstr "Cualquier miembro con permisos de desarrollador o con permisos superiores para el proyecto."
msgid "Any milestone"
-msgstr ""
+msgstr "Cualquier hito"
msgid "Any namespace"
msgstr "Cualquier espacio de nombres"
@@ -2574,17 +2696,23 @@ msgid "Apply a template"
msgstr "Aplicar una plantilla"
msgid "Apply changes"
-msgstr ""
+msgstr "Aplicar cambios"
msgid "Apply suggestion"
msgstr "Aplicar sugerencia"
+msgid "Apply suggestions"
+msgstr "Aplicar sugerencias"
+
msgid "Apply template"
msgstr "Aplicar plantilla"
msgid "Apply this approval rule to any branch or a specific protected branch."
msgstr ""
+msgid "Applying"
+msgstr ""
+
msgid "Applying a template will replace the existing issue description. Any changes you have made will be lost."
msgstr "Al aplicar una plantilla reemplazará la descripción de la incidencia existente. Cualquier cambio que haya realizado se perderá."
@@ -2597,12 +2725,18 @@ msgstr "Aplicando el comando a %{commandDescription}"
msgid "Applying multiple commands"
msgstr "Aplicando múltiples comandos"
-msgid "Applying suggestion"
-msgstr "Aplicando sugerencia"
+msgid "Applying suggestion..."
+msgstr "Aplicando sugerencia..."
+
+msgid "Applying suggestions..."
+msgstr "Aplicando sugerencias..."
msgid "Approval rules"
msgstr "Reglas de aprobación"
+msgid "Approval rules reset to project defaults"
+msgstr ""
+
msgid "ApprovalRuleRemove|%d member"
msgid_plural "ApprovalRuleRemove|%d members"
msgstr[0] "%d miembro"
@@ -2654,7 +2788,10 @@ msgid "Approve the current merge request."
msgstr "Aprobar el merge request actual."
msgid "Approved"
-msgstr ""
+msgstr "Aprobado"
+
+msgid "Approved MRs"
+msgstr "MRs aprobados"
msgid "Approved by: "
msgstr "Aprobado por: "
@@ -2663,13 +2800,13 @@ msgid "Approved the current merge request."
msgstr "Aprobado el merge request actual."
msgid "Approved-By"
-msgstr ""
+msgstr "Aprobado por"
msgid "Approver"
msgstr "Aprobador"
msgid "Approvers"
-msgstr ""
+msgstr "Aprobadores"
msgid "Apr"
msgstr "Abril"
@@ -2869,6 +3006,9 @@ msgstr "Recursos:"
msgid "Assign"
msgstr "Asignar"
+msgid "Assign Iteration"
+msgstr ""
+
msgid "Assign custom color like #FF0000"
msgstr "Asigne colores personalizados como #FF0000"
@@ -2887,6 +3027,9 @@ msgstr "Asignar algunas incidencias a este hito."
msgid "Assign to"
msgstr "Asignar a"
+msgid "Assign to commenting user"
+msgstr ""
+
msgid "Assign yourself to these issues"
msgstr "Asignarse estas incidencias"
@@ -2903,7 +3046,7 @@ msgid "Assigned Merge Requests"
msgstr "Merge requests asignados"
msgid "Assigned to %{assignee_name}"
-msgstr ""
+msgstr "Asignado a %{assignee_name}"
msgid "Assigned to me"
msgstr "Asignado a mí"
@@ -2913,6 +3056,9 @@ msgid_plural "%d Assignees"
msgstr[0] "Asignado"
msgstr[1] "Asignados %d"
+msgid "Assignee has no permissions"
+msgstr ""
+
msgid "Assignee lists not available with your current license"
msgstr "Las listas de asignaciones no se encuentran disponibles con su licencia actual"
@@ -2922,6 +3068,9 @@ msgstr "Las listas de asignados muestran todos las incidencias asignadas al usua
msgid "Assignee(s)"
msgstr "Asignado(s)"
+msgid "Assignees"
+msgstr ""
+
msgid "Assigns %{assignee_users_sentence}."
msgstr "Asigna %{assignee_users_sentence}."
@@ -2957,6 +3106,9 @@ msgstr "Eventos de auditoría"
msgid "Audit Events is a way to keep track of important events that happened in GitLab."
msgstr "Audit Events es una forma de realizar un seguimiento de los eventos importantes que ocurrieron en GitLab."
+msgid "Audit Log"
+msgstr ""
+
msgid "AuditEvents|(removed)"
msgstr "(eliminado)"
@@ -2982,17 +3134,20 @@ msgid "AuditLogs|Date"
msgstr "Fecha"
msgid "AuditLogs|Failed to find %{type}. Please search for another %{type}."
-msgstr ""
+msgstr "Se ha producido un error al buscar %{type}. Por favor, busque otro %{type}"
msgid "AuditLogs|Failed to find %{type}. Please try again."
-msgstr ""
+msgstr "Se ha producido un error al buscar %{type}. Por favor, inténtelo de nuevo."
msgid "AuditLogs|Group Events"
-msgstr ""
+msgstr "Grupo de Eventos"
msgid "AuditLogs|IP Address"
msgstr "Dirección IP"
+msgid "AuditLogs|Member Events"
+msgstr ""
+
msgid "AuditLogs|No matching %{type} found."
msgstr ""
@@ -3000,13 +3155,13 @@ msgid "AuditLogs|Object"
msgstr "Objeto"
msgid "AuditLogs|Project Events"
-msgstr ""
+msgstr "Eventos del proyecto"
msgid "AuditLogs|Target"
msgstr "Destino"
msgid "AuditLogs|User Events"
-msgstr ""
+msgstr "Eventos de usuario"
msgid "Aug"
msgstr "Ago"
@@ -3275,9 +3430,6 @@ msgstr "Sus insignias"
msgid "Badges|e.g. %{exampleUrl}"
msgstr "por ejemplo, %{exampleUrl}"
-msgid "Badge|New"
-msgstr "Nueva"
-
msgid "Balsamiq file could not be loaded."
msgstr "El archivo Balsamiq no se pudo cargar."
@@ -3326,6 +3478,12 @@ msgstr "A continuación se muestran las huellas de las claves SSH de la instanci
msgid "Below you will find all the groups that are public."
msgstr "A continuación encontrará todos los grupos públicos."
+msgid "Beta"
+msgstr "Beta"
+
+msgid "Bi-weekly code coverage"
+msgstr ""
+
msgid "Billing"
msgstr "Facturación"
@@ -3386,9 +3544,15 @@ msgstr "Actualizar"
msgid "Bitbucket Server Import"
msgstr "Importar desde un servidor de Bitbucket"
+msgid "Bitbucket Server import"
+msgstr ""
+
msgid "Bitbucket import"
msgstr "Importar desde Bitbucket"
+msgid "Blame"
+msgstr ""
+
msgid "Blocked"
msgstr "Bloqueado"
@@ -3401,9 +3565,6 @@ msgstr ""
msgid "Blog"
msgstr "Blog"
-msgid "Blue helpers indicate an action to be taken."
-msgstr "Los ayudantes azules indican una acción a tomar."
-
msgid "Board name"
msgstr "Nombre del tablero"
@@ -3429,7 +3590,7 @@ msgid "Boards"
msgstr "Tableros"
msgid "Boards and Board Lists"
-msgstr ""
+msgstr "Tableros y listas de tableros"
msgid "Boards|Collapse"
msgstr "Contraer"
@@ -3602,6 +3763,9 @@ msgstr "El mensaje de difusión se creó correctamente."
msgid "Broadcast Message was successfully updated."
msgstr "El mensaje de difusión se actualizó correctamente."
+msgid "Broadcast Messages"
+msgstr ""
+
msgid "Browse Directory"
msgstr "Examinar directorio"
@@ -3648,7 +3812,7 @@ msgid "Business metrics (Custom)"
msgstr "Métricas de negocio (personalizadas)"
msgid "Buy License"
-msgstr ""
+msgstr "Comprar Licencia"
msgid "Buy more Pipeline minutes"
msgstr "Compre más minutos de pipelines"
@@ -3656,6 +3820,9 @@ msgstr "Compre más minutos de pipelines"
msgid "By %{user_name}"
msgstr "Por %{user_name}"
+msgid "By URL"
+msgstr ""
+
msgid "By default GitLab sends emails in HTML and plain text formats so mail clients can choose what format to use. Disable this option if you only want to send emails in plain text format."
msgstr "De forma predeterminada, GitLab envía correos electrónicos en formatos HTML y de texto sin formato para que los clientes de correo puedan elegir qué formato utilizar. Desactive esta opción sí solo desea enviar correos electrónicos sin formato."
@@ -3672,7 +3839,7 @@ msgid "CI / CD"
msgstr "CI / CD"
msgid "CI / CD Analytics"
-msgstr ""
+msgstr "Analíticas de CI/CD"
msgid "CI / CD Settings"
msgstr "Configuración de CI/CD"
@@ -3681,7 +3848,7 @@ msgid "CI Lint"
msgstr "Cl Lint"
msgid "CI settings"
-msgstr ""
+msgstr "Configuración de CI"
msgid "CI variables"
msgstr "Variables de CI/CD"
@@ -3761,9 +3928,12 @@ msgstr "Puede ser desplegado manualmente en"
msgid "Can override approvers and approvals required per merge request"
msgstr "Los aprobadores y las aprobaciones requeridas se pueden sobreescribir por cada merge request"
-msgid "Can't create snippet: %{err}"
+msgid "Can't apply as this line has changed or the suggestion already matches its content."
msgstr ""
+msgid "Can't create snippet: %{err}"
+msgstr "No se puede crear el fragmento de código: %{err}"
+
msgid "Can't edit as source branch was deleted"
msgstr ""
@@ -3774,7 +3944,7 @@ msgid "Can't find variable: ZiteReader"
msgstr "No se puede encontrar la variable: ZiteReader"
msgid "Can't load mermaid module: %{err}"
-msgstr ""
+msgstr "No se puede cargar el módulo de Mermaid: %{err}"
msgid "Can't remove group members without group managed account"
msgstr "No se pueden eliminar miembros del grupo sin una cuenta administrada por grupo"
@@ -3834,10 +4004,10 @@ msgid "Cannot modify managed Kubernetes cluster"
msgstr "No se puede modificar el clúster de Kubernetes gestionado"
msgid "Cannot modify provider during creation"
-msgstr ""
+msgstr "No se puede modificar el proveedor durante la creación"
msgid "Cannot promote issue because it does not belong to a group."
-msgstr ""
+msgstr "No se puede promocionar la incidencia porque no pertenece a un grupo."
msgid "Cannot promote issue due to insufficient permissions."
msgstr ""
@@ -3867,10 +4037,10 @@ msgid "Certificate (PEM)"
msgstr "Certificado (PEM)"
msgid "Certificate Issuer"
-msgstr ""
+msgstr "Emisor del certificado"
msgid "Certificate Subject"
-msgstr ""
+msgstr "Asunto del certificado"
msgid "Change assignee"
msgstr "Cambiar el asignado"
@@ -3897,10 +4067,10 @@ msgid "Change permissions"
msgstr "Modificar los permisos"
msgid "Change status"
-msgstr ""
+msgstr "Cambiar estado"
msgid "Change subscription"
-msgstr ""
+msgstr "Cambiar suscripción"
msgid "Change template"
msgstr "Cambiar plantilla"
@@ -3947,9 +4117,6 @@ msgstr "Los cambios se muestran como si la revisión del <b>origen</b> se ha fus
msgid "Changes are still tracked. Useful for cluster/index migrations."
msgstr ""
-msgid "Changes are unknown"
-msgstr ""
-
msgid "Changes suppressed. Click to show."
msgstr "Cambios eliminados. Haga clic para mostrar."
@@ -4256,15 +4423,9 @@ msgstr "Escoja el nivel de visibilidad, habilite/deshabilite las característica
msgid "Choose what content you want to see on a group’s overview page"
msgstr "Elija que contenido desea ver en la página de información general de un grupo"
-msgid "Choose which groups you wish to synchronize to this secondary node"
-msgstr ""
-
msgid "Choose which repositories you want to connect and run CI/CD pipelines."
msgstr "Elija que repositorios quiere conectar y ejecutar los CI/CD pipelines."
-msgid "Choose which shards you wish to synchronize to this secondary node"
-msgstr ""
-
msgid "Choose your framework"
msgstr ""
@@ -4562,9 +4723,15 @@ msgstr "Borrada la caché del clúster."
msgid "Cluster does not exist"
msgstr "El clúster no existe"
+msgid "Cluster is required for Stages::ClusterEndpointInserter"
+msgstr ""
+
msgid "Cluster level"
msgstr "Nivel de clúster"
+msgid "Cluster type must be specificed for Stages::ClusterEndpointInserter"
+msgstr ""
+
msgid "ClusterIntegration| %{custom_domain_start}More information%{custom_domain_end}."
msgstr "ClusterIntegration | %{custom_domain_start}Más información%{custom_domain_end}."
@@ -4676,8 +4843,8 @@ msgstr "Certificado CA"
msgid "ClusterIntegration|Cert-Manager"
msgstr "Administrador de certificados"
-msgid "ClusterIntegration|Cert-Manager is a native Kubernetes certificate management controller that helps with issuing certificates. Installing Cert-Manager on your cluster will issue a certificate by %{letsEncrypt} and ensure that certificates are valid and up-to-date."
-msgstr "Cert-Manager es un controlador de administración de certificados de Kubernetes nativo, que ayuda a emitir certificados. La instalación de Cert-Manager en su cluster emitirá un certificado por %{letsEncrypt} y garantizará que los certificados sean válidos y estén actualizados."
+msgid "ClusterIntegration|Cert-Manager is a native Kubernetes certificate management controller that helps with issuing certificates. Installing Cert-Manager on your cluster will issue a certificate by %{linkStart}Let's Encrypt%{linkEnd} and ensure that certificates are valid and up-to-date."
+msgstr ""
msgid "ClusterIntegration|Certificate Authority bundle (PEM format)"
msgstr "paquete de certificado de autoridad (formato PEM)"
@@ -4703,9 +4870,6 @@ msgstr "Limpiar la caché de cluster"
msgid "ClusterIntegration|Clear the local cache of namespace and service accounts. This is necessary if your integration has become out of sync. The cache is repopulated during the next CI job that requires namespace and service accounts."
msgstr ""
-msgid "ClusterIntegration|Cloud Run"
-msgstr "Cloud Run"
-
msgid "ClusterIntegration|Cluster being created"
msgstr "Se está creando el clúster"
@@ -4790,7 +4954,7 @@ msgstr "Creando el cluster de Kubernetes"
msgid "ClusterIntegration|Crossplane"
msgstr "Crossplane"
-msgid "ClusterIntegration|Crossplane enables declarative provisioning of managed services from your cloud of choice using %{codeStart}kubectl%{codeEnd} or %{gitlabIntegrationLink}. Crossplane runs inside your Kubernetes cluster and supports secure connectivity and secrets management between app containers and the cloud services they depend on."
+msgid "ClusterIntegration|Crossplane enables declarative provisioning of managed services from your cloud of choice using %{codeStart}kubectl%{codeEnd} or %{linkStart}GitLab Integration%{linkEnd}. Crossplane runs inside your Kubernetes cluster and supports secure connectivity and secrets management between app containers and the cloud services they depend on."
msgstr ""
msgid "ClusterIntegration|Deletes all GitLab resources attached to this cluster during removal"
@@ -4874,9 +5038,6 @@ msgstr "Los ejecutores de GitLab se conectan al repositorio de código de este p
msgid "ClusterIntegration|GitLab-managed cluster"
msgstr "Cluster de GitLab administrado"
-msgid "ClusterIntegration|Gitlab Integration"
-msgstr "Integración de Gitlab"
-
msgid "ClusterIntegration|Global default"
msgstr ""
@@ -4922,8 +5083,8 @@ msgstr "Endpoint de Ingress"
msgid "ClusterIntegration|Ingress gives you a way to route requests to services based on the request host or path, centralizing a number of services into a single entrypoint."
msgstr "Ingress le ofrece una forma de encaminar las solicitudes a los servicios en función del host o la ruta de la solicitud, centralizando una serie de servicios en un solo punto de entrada."
-msgid "ClusterIntegration|Installing Ingress may incur additional costs. Learn more about %{pricingLink}."
-msgstr "La instalación de Ingress puede incurrir en costes adicionales. Obtenga más información sobre Ingress en el siguiente enlace %{pricingLink}."
+msgid "ClusterIntegration|Installing Ingress may incur additional costs. Learn more about %{linkStart}pricing%{linkEnd}."
+msgstr ""
msgid "ClusterIntegration|Instance cluster"
msgstr "Instancia del clúster"
@@ -4937,8 +5098,8 @@ msgstr "Integrar la automatización del clúster de Kubernetes"
msgid "ClusterIntegration|Issuer Email"
msgstr "Correo electrónico del emisor"
-msgid "ClusterIntegration|Issuers represent a certificate authority. You must provide an email address for your Issuer. "
-msgstr "Los emisores representan una autoridad de certificación. Debe proporcionar una dirección de correo electrónico para su emisor. "
+msgid "ClusterIntegration|Issuers represent a certificate authority. You must provide an email address for your Issuer."
+msgstr ""
msgid "ClusterIntegration|Jupyter Hostname"
msgstr "Nombre del host Jupyter"
@@ -5009,9 +5170,6 @@ msgstr "Más información sobre los grupos de clústeres de Kubernetes"
msgid "ClusterIntegration|Learn more about instance Kubernetes clusters"
msgstr "Más información sobre los grupos de clústeres de Kubernetes"
-msgid "ClusterIntegration|Let's Encrypt"
-msgstr "Let's Encrypt"
-
msgid "ClusterIntegration|Loading IAM Roles"
msgstr "Cargando roles de IAM"
@@ -5064,7 +5222,7 @@ msgid "ClusterIntegration|No deployment cluster found for this job"
msgstr ""
msgid "ClusterIntegration|No deployment found for this job"
-msgstr ""
+msgstr "No se encontró un despliegue para este trabajo"
msgid "ClusterIntegration|No instance type found"
msgstr "No se encontró ningún tipo de instancia"
@@ -5123,8 +5281,8 @@ msgstr "Prefijo del espacio de nombres del proyecto (opcional, único)"
msgid "ClusterIntegration|Prometheus"
msgstr "Prometheus"
-msgid "ClusterIntegration|Prometheus is an open-source monitoring system with %{gitlabIntegrationLink} to monitor deployed applications."
-msgstr "Prometheus es un sistema de monitorización de código abierto con %{gitlabIntegrationLink} para monitorizar las aplicaciones desplegadas."
+msgid "ClusterIntegration|Prometheus is an open-source monitoring system with %{linkStart}GitLab Integration%{linkEnd} to monitor deployed applications."
+msgstr ""
msgid "ClusterIntegration|Provider details"
msgstr "Detalles del proveedor"
@@ -5196,7 +5354,7 @@ msgid "ClusterIntegration|Search VPCs"
msgstr "Buscar VPCs"
msgid "ClusterIntegration|Search domains"
-msgstr ""
+msgstr "Buscar dominios"
msgid "ClusterIntegration|Search instance types"
msgstr "Buscar tipos de instancia"
@@ -5256,7 +5414,7 @@ msgid "ClusterIntegration|Select a zone to choose a network"
msgstr "Seleccione una zona para elegir una red"
msgid "ClusterIntegration|Select existing domain or use new"
-msgstr ""
+msgstr "Seleccione un dominio existente o utilice nuevo"
msgid "ClusterIntegration|Select machine type"
msgstr "Seleccione el tipo de máquina"
@@ -5279,11 +5437,11 @@ msgstr "Seleccione la zona"
msgid "ClusterIntegration|Select zone to choose machine type"
msgstr "Seleccione la zona para elegir el tipo de máquina"
-msgid "ClusterIntegration|Send Cilium Logs"
-msgstr "ClusterIntegration | Enviar registros de Cilium"
+msgid "ClusterIntegration|Send Container Network Policies Logs"
+msgstr ""
-msgid "ClusterIntegration|Send ModSecurity Logs"
-msgstr "ClusterIntegration | Enviar registros de ModSecurity"
+msgid "ClusterIntegration|Send Web Application Firewall Logs"
+msgstr ""
msgid "ClusterIntegration|Service Token"
msgstr "Token de servicio"
@@ -5379,7 +5537,7 @@ msgid "ClusterIntegration|Uninstall %{appTitle}"
msgstr "ClusterIntegration | Desinstalar %{appTitle}"
msgid "ClusterIntegration|Update %{appTitle}"
-msgstr ""
+msgstr "Actualizar %{appTitle}"
msgid "ClusterIntegration|Update failed. Please check the logs and try again."
msgstr "Se ha podroducido un erro al actualizar. Por favor revise los registros y vuelva a intentarlo."
@@ -5450,15 +5608,12 @@ msgstr "acceso a Google Kubernetes Engine"
msgid "ClusterIntegration|documentation"
msgstr "documentación"
-msgid "ClusterIntegration|installed via %{installed_via}"
-msgstr "instalado a través de %{installed_via}"
+msgid "ClusterIntegration|installed via %{linkStart}Cloud Run%{linkEnd}"
+msgstr ""
msgid "ClusterIntegration|meets the requirements"
msgstr "cumple con los requisitos"
-msgid "ClusterIntegration|pricing"
-msgstr "Precios"
-
msgid "ClusterIntegration|sign up"
msgstr "regístrese"
@@ -5495,6 +5650,15 @@ msgstr ""
msgid "Code"
msgstr "Código"
+msgid "Code Coverage: %{coveragePercentage}%{percentSymbol}"
+msgstr ""
+
+msgid "Code Coverage| Empty code coverage data"
+msgstr ""
+
+msgid "Code Coverage|Couldn't fetch the code coverage data"
+msgstr ""
+
msgid "Code Owners"
msgstr "Propietarios del código"
@@ -5505,7 +5669,7 @@ msgid "Code Quality"
msgstr "Calidad del código"
msgid "Code Review"
-msgstr ""
+msgstr "Revisión de código"
msgid "Code Review Analytics displays a table of open merge requests considered to be in code review. There are currently no merge requests in review for this project and/or filters."
msgstr ""
@@ -5549,7 +5713,7 @@ msgstr "Contraer"
msgid "Collapse approvers"
msgstr "Contraer aprobadores"
-msgid "Collapse child epics"
+msgid "Collapse milestones"
msgstr ""
msgid "Collapse replies"
@@ -5565,6 +5729,9 @@ msgid "ComboSearch is not defined"
msgstr "ComboSearch no está definido"
msgid "Coming soon"
+msgstr "Próximamente"
+
+msgid "Comma-separated, e.g. '1.1.1.1, 2.2.2.0/24'"
msgstr ""
msgid "Command"
@@ -5729,7 +5896,7 @@ msgid "Complete"
msgstr "Completado"
msgid "Compliance"
-msgstr ""
+msgstr "Cumplimiento"
msgid "Compliance Dashboard"
msgstr ""
@@ -5737,6 +5904,12 @@ msgstr ""
msgid "Compliance framework (optional)"
msgstr ""
+msgid "Compliance frameworks"
+msgstr ""
+
+msgid "ComplianceDashboard|created by:"
+msgstr ""
+
msgid "ComplianceFramework|GDPR"
msgstr "GDPR"
@@ -5762,7 +5935,7 @@ msgid "ComplianceFramework|SOC 2 - Service Organization Control 2"
msgstr ""
msgid "ComplianceFramework|SOX"
-msgstr "ComplianceFramework|SOX"
+msgstr "SOX"
msgid "ComplianceFramework|SOX - Sarbanes-Oxley"
msgstr "ComplianceFramework|SOX - ley Sarbanes-Oxley"
@@ -5771,7 +5944,7 @@ msgid "ComplianceFramework|This project is regulated by %{framework}."
msgstr ""
msgid "Confidence"
-msgstr ""
+msgstr "Confianza"
msgid "Confidential"
msgstr "Confidencial"
@@ -5875,11 +6048,14 @@ msgstr "Error de conexión"
msgid "Connection timed out"
msgstr "Tiempo de espera agotado"
+msgid "Connection timeout"
+msgstr ""
+
msgid "Contact sales to upgrade"
msgstr "Póngase en contacto con ventas para actualizar"
msgid "Contact support"
-msgstr ""
+msgstr "Contactar con soporte"
msgid "Container Registry"
msgstr "Registro de contenedores"
@@ -5907,16 +6083,16 @@ msgstr ""
msgid "ContainerRegistry|%{count} Image repository"
msgid_plural "ContainerRegistry|%{count} Image repositories"
-msgstr[0] ""
-msgstr[1] ""
+msgstr[0] "%{count} repositorio de imágenes"
+msgstr[1] "%{count} repositorios de imágenes"
msgid "ContainerRegistry|%{count} Tag"
msgid_plural "ContainerRegistry|%{count} Tags"
-msgstr[0] ""
-msgstr[1] ""
+msgstr[0] "%{count} Etiqueta"
+msgstr[1] "%{count} Etiquetas"
msgid "ContainerRegistry|%{imageName} tags"
-msgstr ""
+msgstr "%{imageName} etiquetas"
msgid "ContainerRegistry|%{title} was successfully scheduled for deletion"
msgstr ""
@@ -5925,13 +6101,10 @@ msgid "ContainerRegistry|Automatically remove extra images that aren't designed
msgstr "Eliminar automáticamente las imágenes adicionales que no están diseñados para ser conservadas."
msgid "ContainerRegistry|Build an image"
-msgstr ""
+msgstr "Construir una imagen"
msgid "ContainerRegistry|CLI Commands"
-msgstr ""
-
-msgid "ContainerRegistry|Compressed Size"
-msgstr "Tamaño comprimido"
+msgstr "Comandos del CLI"
msgid "ContainerRegistry|Container Registry"
msgstr "Registro de contenedores"
@@ -5948,6 +6121,9 @@ msgstr "Copiar el comando de inicio de sesión"
msgid "ContainerRegistry|Copy push command"
msgstr "Copiar el comando push"
+msgid "ContainerRegistry|Delete selected"
+msgstr ""
+
msgid "ContainerRegistry|Docker connection error"
msgstr "Error de conexión de Docker"
@@ -5970,29 +6146,29 @@ msgid "ContainerRegistry|Expiration policy will run in %{time}"
msgstr ""
msgid "ContainerRegistry|Expiration policy:"
-msgstr ""
+msgstr "Política de caducidad:"
msgid "ContainerRegistry|Expiration schedule:"
msgstr ""
msgid "ContainerRegistry|Filter by name"
-msgstr ""
+msgstr "Filtrar por nombre"
msgid "ContainerRegistry|If you are not already logged in, you need to authenticate to the Container Registry by using your GitLab username and password. If you have %{twofaDocLinkStart}Two-Factor Authentication%{twofaDocLinkEnd} enabled, use a %{personalAccessTokensDocLinkStart}Personal Access Token%{personalAccessTokensDocLinkEnd} instead of a password."
msgstr "Si no ha iniciado sesión, necesita autenticarse en el registro de contenedores utilizando su nombre de usuario y su contraseña de GitLab. Si tiene habilitada la %{twofaDocLinkStart}autenticación de dos factores%{twofaDocLinkEnd}, utilice un %{personalAccessTokensDocLinkStart}token de acceso personal%{personalAccessTokensDocLinkEnd} en lugar de una contraseña."
-msgid "ContainerRegistry|Image ID"
-msgstr "ID de la imagen"
+msgid "ContainerRegistry|Image ID: %{imageId}"
+msgstr ""
msgid "ContainerRegistry|Image Repositories"
+msgstr "Repositorios de imágenes"
+
+msgid "ContainerRegistry|Image tags"
msgstr ""
msgid "ContainerRegistry|Keep and protect the images that matter most."
msgstr ""
-msgid "ContainerRegistry|Last Updated"
-msgstr "Última actualización"
-
msgid "ContainerRegistry|Login"
msgstr "Iniciar sesión"
@@ -6005,9 +6181,12 @@ msgstr "ContainerRegistry|Número de etiquetas a retener:"
msgid "ContainerRegistry|Please contact your administrator."
msgstr "ContainerRegistry|Por favor, póngase en contacto con su administrador."
-msgid "ContainerRegistry|Push an image"
+msgid "ContainerRegistry|Published %{timeInfo}"
msgstr ""
+msgid "ContainerRegistry|Push an image"
+msgstr "Enviar una imagen"
+
msgid "ContainerRegistry|Regular expressions such as %{codeStart}.*-master%{codeEnd} or %{codeStart}release-.*%{codeEnd} are supported"
msgstr ""
@@ -6020,9 +6199,6 @@ msgstr ""
msgid "ContainerRegistry|Remove repository"
msgstr "Borrar repositorio"
-msgid "ContainerRegistry|Remove selected tags"
-msgstr "Eliminar las etiquetas seleccionadas"
-
msgid "ContainerRegistry|Remove tag"
msgid_plural "ContainerRegistry|Remove tags"
msgstr[0] "Eliminar etiqueta"
@@ -6052,9 +6228,6 @@ msgstr "Se ha producido un error al actualizar la política de caducidad."
msgid "ContainerRegistry|Sorry, your filter produced no results."
msgstr ""
-msgid "ContainerRegistry|Tag"
-msgstr "Etiqueta"
-
msgid "ContainerRegistry|Tag expiration policy"
msgstr "Política de caducidad de las etiquetas"
@@ -6143,10 +6316,10 @@ msgid "Contribute to GitLab"
msgstr "Contribuir a GitLab"
msgid "Contribution"
-msgstr "Contribución"
+msgstr "Colaboración"
msgid "Contribution Analytics"
-msgstr "Análisis de contribución"
+msgstr "Análisis de colaboración"
msgid "ContributionAnalytics|<strong>%{created_count}</strong> created, <strong>%{closed_count}</strong> closed."
msgstr "<strong>%{created_count}</strong> creado, <strong>%{closed_count}</strong> cerrado."
@@ -6199,21 +6372,6 @@ msgstr "Controle los correos electrónicos vinculados a su cuenta"
msgid "Control the display of third party offers."
msgstr "Controlar la visualización de las ofertas de terceros."
-msgid "Control the maximum concurrency of LFS/attachment backfill for this secondary node"
-msgstr ""
-
-msgid "Control the maximum concurrency of container repository operations for this Geo node"
-msgstr ""
-
-msgid "Control the maximum concurrency of repository backfill for this secondary node"
-msgstr "Controla la concurrencia máxima para el backfill del repositorio para este nodo secundario"
-
-msgid "Control the maximum concurrency of verification operations for this Geo node"
-msgstr ""
-
-msgid "Control the minimum interval in days that a repository should be reverified for this primary node"
-msgstr ""
-
msgid "Cookie domain"
msgstr "Dominio de la cookie"
@@ -6239,7 +6397,7 @@ msgid "Copy %{proxy_url}"
msgstr "Copiar %{proxy_url}"
msgid "Copy %{type}"
-msgstr ""
+msgstr "Copiar %{type}"
msgid "Copy Account ID to clipboard"
msgstr "Copiar ID de la cuenta al portapapeles"
@@ -6338,7 +6496,7 @@ msgid "Could not create Wiki Repository at this time. Please try again later."
msgstr "No es posible crear la Wiki del repositorio en este momento. Por favor, inténtelo de nuevo más tarde."
msgid "Could not create environment"
-msgstr ""
+msgstr "Se ha producido un error al crear el entorno"
msgid "Could not create group"
msgstr "Se ha producido un error al crear el grupo"
@@ -6353,6 +6511,9 @@ msgid "Could not delete chat nickname %{chat_name}."
msgstr "No es posible eliminar el apodo del chate %{chat_name}."
msgid "Could not find design."
+msgstr "No se pudo encontrar el diseño."
+
+msgid "Could not find iteration"
msgstr ""
msgid "Could not remove the trigger."
@@ -6368,7 +6529,7 @@ msgid "Could not revoke personal access token %{personal_access_token_name}."
msgstr "No se pudo revocar el token de acceso personal %{personal_access_token_name}."
msgid "Could not revoke project access token %{project_access_token_name}."
-msgstr ""
+msgstr "No se pudo revocar el token de acceso al proyecto %{project_access_token_name}."
msgid "Could not save group ID"
msgstr "Se ha producido un error al guardar el ID del grupo"
@@ -6380,7 +6541,7 @@ msgid "Could not save prometheus manual configuration"
msgstr "Se ha producido un error al guardar la configuración manual de Prometheus"
msgid "Could not update the LDAP settings"
-msgstr ""
+msgstr "No se pudo actualizar la configuración de LDAP"
msgid "Could not upload your designs as one or more files uploaded are not supported."
msgstr ""
@@ -6391,6 +6552,12 @@ msgstr "País"
msgid "Coverage"
msgstr "Cobertura"
+msgid "Coverage Fuzzing"
+msgstr ""
+
+msgid "Crash State"
+msgstr ""
+
msgid "Create"
msgstr "Crear"
@@ -6398,7 +6565,7 @@ msgid "Create %{environment}"
msgstr "Crear %{environment}"
msgid "Create %{type}"
-msgstr ""
+msgstr "Crear %{type}"
msgid "Create New Directory"
msgstr "Crear Nuevo Directorio"
@@ -6439,6 +6606,9 @@ msgstr "Crear un nuevo repositorio"
msgid "Create a personal access token on your account to pull or push via %{protocol}."
msgstr "Crear un token de acceso personal en tu cuenta para actualizar o enviar a través de %{protocol}."
+msgid "Create a project pre-populated with the necessary files to get you started quickly."
+msgstr ""
+
msgid "Create an account using:"
msgstr "Crear una cuenta utilizando:"
@@ -6485,7 +6655,7 @@ msgid "Create issue"
msgstr "Crear incidencia"
msgid "Create iteration"
-msgstr ""
+msgstr "Crear iteración"
msgid "Create lists from labels. Issues with that label appear in that list."
msgstr "Crear listas desde las etiquetas. Las incidencias que contengan esa etiqueta aparecerán en esa lista."
@@ -6500,7 +6670,7 @@ msgid "Create milestone"
msgstr "Crear hito"
msgid "Create new"
-msgstr ""
+msgstr "Crear nuevo"
msgid "Create new board"
msgstr "Crear nuevo tablero"
@@ -6542,10 +6712,10 @@ msgid "Create your first page"
msgstr "Crea tu primera página"
msgid "Create your group"
-msgstr ""
+msgstr "Cree su grupo"
msgid "Create/import your first project"
-msgstr ""
+msgstr "Cree/importe su primer proyecto"
msgid "CreateGroup|You don’t have permission to create a subgroup in this group."
msgstr "No tiene permiso para crear un subgrupo en este grupo."
@@ -6581,7 +6751,7 @@ msgid "Created by me"
msgstr "Creado por mí"
msgid "Created date"
-msgstr ""
+msgstr "Fecha de creación"
msgid "Created issue %{issueLink}"
msgstr "Incidencia creada %{issueLink}"
@@ -6607,6 +6777,9 @@ msgstr "Creada una rama y un merge request para resolver esta incidencia."
msgid "Creates branch '%{branch_name}' and a merge request to resolve this issue."
msgstr "Crea una rama '%{branch_name}' y un merge request para resolver esta incidencia."
+msgid "Creating"
+msgstr "Creando"
+
msgid "Creating epic"
msgstr "Creando épica"
@@ -6658,8 +6831,11 @@ msgstr "Contraseña actual"
msgid "Current vulnerabilities count"
msgstr "Contador de vulnerabilidades"
-msgid "CurrentUser|Buy CI minutes"
-msgstr "Comprar minutos de CI"
+msgid "CurrentUser|Buy Pipeline minutes"
+msgstr ""
+
+msgid "CurrentUser|One of your groups is running out"
+msgstr ""
msgid "CurrentUser|Profile"
msgstr "Perfil"
@@ -6671,7 +6847,10 @@ msgid "CurrentUser|Start a Gold trial"
msgstr "Inicie periodo de prueba Gold"
msgid "CurrentUser|Upgrade"
-msgstr ""
+msgstr "Actualizar"
+
+msgid "Custom Attributes"
+msgstr "Atributos personalizados"
msgid "Custom CI configuration path"
msgstr "Ruta de configuración CI personalizada"
@@ -6698,7 +6877,7 @@ msgid "Custom range"
msgstr "Rango personalizado"
msgid "Custom range (UTC)"
-msgstr ""
+msgstr "Rango personalizado (UTC)"
msgid "CustomCycleAnalytics|Add a stage"
msgstr "Añadir una etapa"
@@ -6728,28 +6907,28 @@ msgid "CustomCycleAnalytics|Select stop event"
msgstr "Seleccione un evento de detención"
msgid "CustomCycleAnalytics|Stage name already exists"
-msgstr ""
+msgstr "El nombre de la etapa ya existe"
msgid "CustomCycleAnalytics|Start event"
msgstr "Iniciar evento"
msgid "CustomCycleAnalytics|Start event changed, please select a valid stop event"
-msgstr ""
+msgstr "Se modifico el evento de inicio, se debe seleccionar un evento de salida válido"
msgid "CustomCycleAnalytics|Start event label"
-msgstr ""
+msgstr "Etiqueta del evento de inicio"
msgid "CustomCycleAnalytics|Stop event"
msgstr "Detener evento"
msgid "CustomCycleAnalytics|Stop event label"
-msgstr ""
+msgstr "Etiqueta del evento de detención"
msgid "CustomCycleAnalytics|Update stage"
msgstr "Actualizar etapa"
msgid "Customer Portal"
-msgstr ""
+msgstr "Portal de cliente"
msgid "Customize colors"
msgstr "Personalizar colores"
@@ -6776,7 +6955,7 @@ msgid "Customize your pipeline configuration."
msgstr "Personalice la configuración de su pipeline."
msgid "Cycle Time"
-msgstr ""
+msgstr "Tiempo del ciclo"
msgid "CycleAnalyticsEvent|Issue closed"
msgstr "Incidencia cerrada"
@@ -6936,12 +7115,18 @@ msgstr "Lista desplegable de las etapas"
msgid "DAG"
msgstr ""
+msgid "DAG visualization requires at least 3 dependent jobs."
+msgstr ""
+
msgid "DNS"
msgstr "DNS"
msgid "Dashboard"
msgstr "Panel de control"
+msgid "Dashboard uid not found"
+msgstr ""
+
msgid "DashboardProjects|All"
msgstr "Todos"
@@ -6966,6 +7151,9 @@ msgstr "No se puede agregar %{invalidProjects}. Este panel de control está disp
msgid "Data is still calculating..."
msgstr "Los datos aún se están calculando..."
+msgid "Datasource name not found"
+msgstr ""
+
msgid "Date"
msgstr "Fecha"
@@ -7030,7 +7218,7 @@ msgid "Default classification label"
msgstr "Etiqueta de clasificación por defecto"
msgid "Default dashboard"
-msgstr ""
+msgstr "Panel de control predeterminado"
msgid "Default deletion adjourned period"
msgstr ""
@@ -7054,10 +7242,10 @@ msgid "Default project deletion protection"
msgstr "Protección por defecto de eliminación de proyectos"
msgid "Default projects limit"
-msgstr ""
+msgstr "Límite predeterminado de proyectos"
msgid "Default stages"
-msgstr ""
+msgstr "Etapas predeterminadas"
msgid "Default: Directly import the Google Code email address or username"
msgstr "Por defecto: Importar directamente el nombre de usuario o la dirección de correo electrónico de Google Code"
@@ -7111,10 +7299,10 @@ msgid "Delete comment"
msgstr "Eliminar comentario"
msgid "Delete domain"
-msgstr ""
+msgstr "Eliminar dominio"
msgid "Delete label"
-msgstr ""
+msgstr "Eliminar etiqueta"
msgid "Delete label: %{label_name} ?"
msgstr ""
@@ -7146,6 +7334,9 @@ msgstr "Eliminar rama origen"
msgid "Delete this attachment"
msgstr "Eliminar este adjunto"
+msgid "Delete user list"
+msgstr ""
+
msgid "Delete variable"
msgstr "Eliminar variable"
@@ -7177,7 +7368,7 @@ msgid "Deleted in this version"
msgstr "Eliminado en esta versión"
msgid "Deleting"
-msgstr ""
+msgstr "Eliminando"
msgid "Deleting the license failed."
msgstr "Se ha producido un error al eliminar la licencia."
@@ -7210,8 +7401,8 @@ msgstr[1] "%d vulnerabilidades adicionales no mostradas"
msgid "Dependencies|%d vulnerability detected"
msgid_plural "Dependencies|%d vulnerabilities detected"
-msgstr[0] ""
-msgstr[1] ""
+msgstr[0] "detectada %d vulnerabilidad"
+msgstr[1] "detectadas %d vulnerabilidades"
msgid "Dependencies|%{remainingLicensesCount} more"
msgstr "%{remainingLicensesCount} más"
@@ -7462,6 +7653,9 @@ msgstr "Desplegado en"
msgid "Deploying to"
msgstr "Desplegando en"
+msgid "Deploying to AWS is easy with GitLab"
+msgstr ""
+
msgid "Deployment Frequency"
msgstr "Frecuencia de despliegue"
@@ -7538,7 +7732,7 @@ msgid "DesignManagement|Are you sure you want to delete the selected designs?"
msgstr "¿Está seguro de que desea eliminar los diseños seleccionados?"
msgid "DesignManagement|Cancel changes"
-msgstr ""
+msgstr "Cancelar los cambios"
msgid "DesignManagement|Cancel comment confirmation"
msgstr "Cancelar la confirmación de comentario"
@@ -7550,7 +7744,7 @@ msgid "DesignManagement|Click the image where you'd like to start a new discussi
msgstr ""
msgid "DesignManagement|Comment"
-msgstr ""
+msgstr "Comentario"
msgid "DesignManagement|Comments you resolve can be viewed and unresolved by going to the \"Resolved Comments\" section below"
msgstr ""
@@ -7565,7 +7759,7 @@ msgid "DesignManagement|Could not update discussion. Please try again."
msgstr ""
msgid "DesignManagement|Could not update note. Please try again."
-msgstr ""
+msgstr "Se ha producido un error al actualizar la nota. Por favor, inténtelo de nuevo."
msgid "DesignManagement|Delete"
msgstr "Eliminar"
@@ -7595,7 +7789,7 @@ msgid "DesignManagement|Go to previous design"
msgstr "Ir al diseño anterior"
msgid "DesignManagement|Keep changes"
-msgstr ""
+msgstr "Mantener los cambios"
msgid "DesignManagement|Keep comment"
msgstr "Mantener comentario"
@@ -7607,13 +7801,13 @@ msgid "DesignManagement|Requested design version does not exist. Showing latest
msgstr "La versión del diseño solicitada no existe. Mostrando la última versión en su lugar"
msgid "DesignManagement|Resolve thread"
-msgstr ""
+msgstr "Resolver hilo"
msgid "DesignManagement|Resolved Comments"
-msgstr ""
+msgstr "Comentarios resueltos"
msgid "DesignManagement|Save comment"
-msgstr ""
+msgstr "Guardar comentario"
msgid "DesignManagement|Select all"
msgstr "Seleccionar todo"
@@ -7628,7 +7822,7 @@ msgid "DesignManagement|Unresolve thread"
msgstr ""
msgid "DesignManagement|Upload designs"
-msgstr ""
+msgstr "Subir diseños"
msgid "DesignManagement|Upload skipped."
msgstr ""
@@ -7643,7 +7837,7 @@ msgid "Destroy"
msgstr "Destruir"
msgid "Detail"
-msgstr ""
+msgstr "Detalle"
msgid "Details"
msgstr "Detalles"
@@ -7667,7 +7861,7 @@ msgid "Difference between start date and now"
msgstr "Diferencia entre la fecha de inicio y ahora"
msgid "DiffsCompareBaseBranch|(HEAD)"
-msgstr ""
+msgstr "(HEAD)"
msgid "DiffsCompareBaseBranch|(base)"
msgstr "(base)"
@@ -7824,14 +8018,17 @@ msgstr "Descartado en el pipeline %{pipelineLink}"
msgid "Dismissed on pipeline %{pipelineLink} at %{projectLink}"
msgstr "Descartado en el pipeline %{pipelineLink} en %{projectLink}"
+msgid "Display alerts from all your monitoring tools directly within GitLab."
+msgstr ""
+
msgid "Display name"
msgstr "Nombre para mostrar"
msgid "Display rendered file"
-msgstr ""
+msgstr "Mostrar el archivo renderizado"
msgid "Display source"
-msgstr ""
+msgstr "Mostrar fuente"
msgid "Do not display offers from third parties within GitLab"
msgstr "No mostrar ofertas de terceros dentro de GitLab"
@@ -7881,9 +8078,6 @@ msgstr "No pegue la parte privada de la clave GPG. Pegue la parte pública que e
msgid "Don't show again"
msgstr "No mostrar de nuevo"
-msgid "Don't worry, you can access this tour by clicking on the help icon in the top right corner and choose <strong>Learn GitLab</strong>."
-msgstr "No se preocupe, puede acceder a este visita haciendo clic sobre el ícono de ayuda situado en la esquina superior derecha y seleccionando <strong>aprender GitLab</strong>."
-
msgid "Done"
msgstr "Hecho"
@@ -7951,7 +8145,7 @@ msgid "Drop your designs to start your upload."
msgstr ""
msgid "Due Date"
-msgstr ""
+msgstr "Fecha de vencimiento"
msgid "Due date"
msgstr "Fecha de vencimiento"
@@ -7960,7 +8154,7 @@ msgid "Duration"
msgstr "Duración"
msgid "Duration for the last 30 commits"
-msgstr ""
+msgstr "Duración de los últimos 30 commits"
msgid "During this process, you’ll be asked for URLs from GitLab’s side. Use the URLs shown below."
msgstr "Durante este proceso, le solicitaremos las URLs de parte de Gitlab.Por favor, utilice las URLs que se muestran a continuación."
@@ -8010,6 +8204,9 @@ msgstr "Editar Programación del Pipeline %{id}"
msgid "Edit Release"
msgstr "Editar versión"
+msgid "Edit Slack integration"
+msgstr ""
+
msgid "Edit Snippet"
msgstr "Editar fragmento de código"
@@ -8049,6 +8246,9 @@ msgstr "Editar la identidad para %{user_name}"
msgid "Edit issues"
msgstr "Editar incidencias"
+msgid "Edit iteration"
+msgstr ""
+
msgid "Edit public deploy key"
msgstr "Editar clave pública de despliegue"
@@ -8097,29 +8297,35 @@ msgstr "Ninguno. Seleccione los proyectos a indexar."
msgid "Email"
msgstr "Correo electrónico"
+msgid "Email Notification"
+msgstr ""
+
msgid "Email address"
msgstr "Correo electrónico"
msgid "Email could not be sent"
-msgstr ""
+msgstr "No se ha podido enviar el correo electrónico"
msgid "Email display name"
-msgstr ""
+msgstr "Nombre para mostrar en el correo electrónico"
msgid "Email not verified. Please verify your email in Salesforce."
msgstr "Correo electrónico no verificado. Por favor, verifique su correo electrónico en Salesforce."
+msgid "Email notification for unknown sign-ins"
+msgstr ""
+
msgid "Email patch"
msgstr "Enviar parche por correo electrónico"
msgid "Email restrictions"
-msgstr ""
+msgstr "Restricciones de correo electrónico"
msgid "Email restrictions for sign-ups"
-msgstr ""
+msgstr "Restricciones de correo electrónico para las suscripciones"
msgid "Email sent"
-msgstr ""
+msgstr "Correo enviado"
msgid "Email the pipelines status to a list of recipients."
msgstr "Envía el estado de los pipelines a una lista de destinatarios."
@@ -8158,7 +8364,7 @@ msgid "Emails"
msgstr "Correos electrónicos"
msgid "Emails sent from Service Desk will have this name"
-msgstr ""
+msgstr "Los correos electrónicos enviados desde Service Desk tendrán este nombre"
msgid "Emails separated by comma"
msgstr "Correos electrónicos separados por comas"
@@ -8254,7 +8460,7 @@ msgid "Enable header and footer in emails"
msgstr "Habilitar el encabezado y pie de página en los correos electrónicos"
msgid "Enable integration"
-msgstr ""
+msgstr "Habilitar integración"
msgid "Enable maintenance mode"
msgstr "Habilitar el modo de mantenimiento"
@@ -8340,6 +8546,9 @@ msgstr "Finaliza a las (UTC)"
msgid "Enforce DNS rebinding attack protection"
msgstr "Reforzar la protección de ataques a DNS"
+msgid "Enforce personal access token expiration"
+msgstr ""
+
msgid "Ensure connectivity is available from the GitLab server to the Prometheus server"
msgstr ""
@@ -8380,7 +8589,7 @@ msgid "Enter merge request URLs"
msgstr "Introduzca las URL de la solicitud de fusión"
msgid "Enter new %{field_title}"
-msgstr ""
+msgstr "Introduzca nuevo %{field_title}"
msgid "Enter new AWS Secret Access Key"
msgstr "Introduzca su nueva AWS Secret Acess Key"
@@ -8409,6 +8618,9 @@ msgstr "Introduzca el título del merge request"
msgid "Enter the name of your application, and we'll return a unique %{type}."
msgstr ""
+msgid "Enter weights for storages for new repositories."
+msgstr ""
+
msgid "Enter your password to approve"
msgstr "Introduzca su contraseña para aprobar"
@@ -8418,15 +8630,27 @@ msgstr "Entorno"
msgid "Environment does not have deployments"
msgstr ""
+msgid "Environment is required for Stages::MetricEndpointInserter"
+msgstr ""
+
+msgid "Environment is required for Stages::VariableEndpointInserter"
+msgstr ""
+
msgid "Environment scope"
msgstr ""
+msgid "Environment variables are applied to all project environments in this instance via the Runner. You can use environment variables for passwords, secret keys, etc. Make variables available to the running application by prepending the variable key with <code>K8S_SECRET_</code>. You can set variables to be:"
+msgstr ""
+
msgid "Environment variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. Additionally, they can be masked so they are hidden in job logs, though they must match certain regexp requirements to do so. You can use environment variables for passwords, secret keys, or whatever you want."
msgstr "Las variables de entorno se aplican a los entornos a través de los ejecutores. Sólo se pueden proteger exponiéndolas solo en ramas o etiquetas protegidas. Además, se pueden enmascarar para que estén ocultas en los logs de ejecución de los trabajos, aunque para ello deben coincidir con ciertos requisitos de expresiones regulares. Puede utilizar variables de entorno para almacenar contraseñas, claves secretas o lo que desee."
msgid "Environment variables are configured by your administrator to be %{link_start}protected%{link_end} by default"
msgstr "Las variables de entorno están configuradas por el administrador para estar %{link_start}protegidas%{link_end} por defecto"
+msgid "Environment variables on this GitLab instance are configured to be %{link_start}protected%{link_end} by default"
+msgstr ""
+
msgid "Environment:"
msgstr "Entorno:"
@@ -8545,7 +8769,7 @@ msgid "Environments|Environments are places where code gets deployed, such as st
msgstr "Los entornos son lugares en los que se despliega el código, como por ejemplo, los entornos de test o los entornos de producción."
msgid "Environments|Install Elastic Stack on your cluster to enable advanced querying capabilities such as full text search."
-msgstr ""
+msgstr "Instala Elastic Stack en su clúster para habilitar capacidades de consulta avanzadas, como búsqueda de texto completo."
msgid "Environments|Job"
msgstr "Trabajo"
@@ -8614,10 +8838,10 @@ msgid "Environments|Rollback environment %{name}?"
msgstr "¿Desea restaurar el entorno %{name}?"
msgid "Environments|Select environment"
-msgstr ""
+msgstr "Seleccionar entorno"
msgid "Environments|Select pod"
-msgstr ""
+msgstr "Seleccionar pod"
msgid "Environments|Show all"
msgstr "Mostrar todo"
@@ -8680,7 +8904,7 @@ msgid "Epics, Issues, and Merge Requests"
msgstr ""
msgid "Epics|Add a new epic"
-msgstr ""
+msgstr "Añadir una nueva tarea épica"
msgid "Epics|Add an existing epic"
msgstr ""
@@ -8947,7 +9171,7 @@ msgid "Errors:"
msgstr "Errores:"
msgid "Estimate"
-msgstr ""
+msgstr "Estimación"
msgid "Estimated"
msgstr "Estimado"
@@ -8958,6 +9182,9 @@ msgstr "Filtrar por todos"
msgid "EventFilterBy|Filter by comments"
msgstr "Filtrar por comentarios"
+msgid "EventFilterBy|Filter by designs"
+msgstr ""
+
msgid "EventFilterBy|Filter by epic events"
msgstr "Filtrar por eventos épicos"
@@ -8974,16 +9201,16 @@ msgid "EventFilterBy|Filter by team"
msgstr "Filtrar por equipo"
msgid "EventFilterBy|Filter by wiki"
-msgstr ""
+msgstr "Filtrar por wiki"
msgid "Events"
msgstr "Eventos"
msgid "Events in %{group_name}"
-msgstr ""
+msgstr "Eventos en %{group_name}"
msgid "Events in %{project_path}"
-msgstr ""
+msgstr "Eventos en %{project_path}"
msgid "Every %{action} attempt has failed: %{job_error_message}. Please try again."
msgstr "Cada intento de %{action} ha fallado: %{job_error_message}. Por favor, inténtalo de nuevo."
@@ -8992,13 +9219,13 @@ msgid "Every day"
msgstr "Diario"
msgid "Every day (at %{time})"
-msgstr ""
+msgstr "Todos los días (a %{time})"
msgid "Every month"
msgstr "Cada mes"
msgid "Every month (Day %{day} at %{time})"
-msgstr ""
+msgstr "Todos los meses (Día %{day} a %{time})"
msgid "Every three months"
msgstr "Cada tres meses"
@@ -9010,7 +9237,7 @@ msgid "Every week"
msgstr "Cada semana"
msgid "Every week (%{weekday} at %{time})"
-msgstr ""
+msgstr "Todas las semanas (%{weekday} a %{time})"
msgid "Everyone"
msgstr "Todo el mundo"
@@ -9046,7 +9273,7 @@ msgid "Evidence collection"
msgstr "Recopilación de evidencias"
msgid "Exactly one of %{attributes} is required"
-msgstr ""
+msgstr "Exactamente uno de los %{attributes} es necesario"
msgid "Example: <code>acme.com,acme.co.in,acme.uk</code>."
msgstr ""
@@ -9090,15 +9317,15 @@ msgstr "Expandir todo"
msgid "Expand approvers"
msgstr "Expandir aprobadores"
-msgid "Expand child epics"
-msgstr ""
-
msgid "Expand down"
msgstr "Expandir hacia abajo"
msgid "Expand dropdown"
msgstr "Expandir el menú desplegable"
+msgid "Expand milestones"
+msgstr ""
+
msgid "Expand sidebar"
msgstr "Expandir barra lateral"
@@ -9106,7 +9333,7 @@ msgid "Expand up"
msgstr "Expandir todo"
msgid "Experienced"
-msgstr ""
+msgstr "Experiencia"
msgid "Expiration"
msgstr "Vencimiento"
@@ -9114,6 +9341,9 @@ msgstr "Vencimiento"
msgid "Expiration date"
msgstr "Fecha de vencimiento"
+msgid "Expiration not enforced"
+msgstr ""
+
msgid "Expiration policy for the Container Registry is a perfect solution for keeping the Registry space down while still enjoying the full power of GitLab CI/CD."
msgstr ""
@@ -9124,7 +9354,7 @@ msgid "Expired %{expiredOn}"
msgstr "Caducado el %{expiredOn}"
msgid "Expired:"
-msgstr ""
+msgstr "Caducado:"
msgid "Expires"
msgstr "Caduca"
@@ -9136,7 +9366,7 @@ msgid "Expires in %{expires_at}"
msgstr "Caduca en %{expires_at}"
msgid "Expires on"
-msgstr ""
+msgstr "Caduca el"
msgid "Expires:"
msgstr "Caduca:"
@@ -9234,6 +9464,9 @@ msgstr "Fallido"
msgid "Failed Jobs"
msgstr "Trabajos fallidos"
+msgid "Failed on"
+msgstr ""
+
msgid "Failed to add a Zoom meeting"
msgstr "Se ha producido un error al agregar una reunión de Zoom"
@@ -9294,14 +9527,20 @@ msgstr "Se ha producido un error al obtener ref."
msgid "Failed to install."
msgstr "Se ha producido un error al instalar."
+msgid "Failed to load assignees. Please try again."
+msgstr ""
+
+msgid "Failed to load authors. Please try again."
+msgstr ""
+
msgid "Failed to load emoji list."
msgstr "Se ha producido un error al cargar la lista de emojis."
msgid "Failed to load error details from Sentry."
-msgstr "Se ha poducido un error al cargar los detalles de error desde Sentry."
+msgstr "Se ha producido un error al cargar los detalles de error desde Sentry."
-msgid "Failed to load errors from Sentry. Error message: %{errorMessage}"
-msgstr "Se ha producido un error al cargar los errores desde Sentry. El mensaje de error es: %{errorMessage}"
+msgid "Failed to load errors from Sentry."
+msgstr "Se ha producido un error al cargar los errores desde Sentry."
msgid "Failed to load group activity metrics. Please try again."
msgstr ""
@@ -9310,10 +9549,10 @@ msgid "Failed to load groups & users."
msgstr "Se ha producido un error al cargar grupos y usuarios."
msgid "Failed to load labels. Please try again."
-msgstr ""
+msgstr "Se ha producido un error al cargar las etiquetas. Por favor, inténtelo de nuevo."
msgid "Failed to load milestones. Please try again."
-msgstr ""
+msgstr "Se ha producido un error al cargar los hitos. Por favor, inténtelo de nuevo."
msgid "Failed to load related branches"
msgstr "Se ha producido un error al cargar ramas relacionadas"
@@ -9381,6 +9620,9 @@ msgstr "Se ha producido un error al guardar las preferencias."
msgid "Failed to set due date because the date format is invalid."
msgstr "Se ha producido un error al establecer la fecha de vencimiento debido a que el formato de fecha no es válido."
+msgid "Failed to set iteration on this issue. Please try again."
+msgstr ""
+
msgid "Failed to signing using smartcard authentication"
msgstr "Se ha producido un error al inciar sesión utilizando la autenticación mediante una tarjeta inteligente"
@@ -9457,7 +9699,7 @@ msgid "FeatureFlags|Active"
msgstr "Activo"
msgid "FeatureFlags|Add strategy"
-msgstr ""
+msgstr "Añadir una estrategia"
msgid "FeatureFlags|All users"
msgstr "Todos los usuarios"
@@ -9487,7 +9729,7 @@ msgid "FeatureFlags|Edit Feature Flag User List"
msgstr ""
msgid "FeatureFlags|Edit list"
-msgstr ""
+msgstr "Editar lista"
msgid "FeatureFlags|Enable features for specific users and specific environments by defining feature flag strategies. By default, features are available to all users in all environments."
msgstr ""
@@ -9550,7 +9792,7 @@ msgid "FeatureFlags|Instance ID"
msgstr "ID de instancia"
msgid "FeatureFlags|List details"
-msgstr ""
+msgstr "Detalles de la lista"
msgid "FeatureFlags|Loading feature flags"
msgstr ""
@@ -9574,7 +9816,7 @@ msgid "FeatureFlags|New feature flag"
msgstr ""
msgid "FeatureFlags|New list"
-msgstr ""
+msgstr "Nueva lista"
msgid "FeatureFlags|Percent rollout (logged in users)"
msgstr ""
@@ -9598,20 +9840,17 @@ msgid "FeatureFlags|Status"
msgstr "Estado"
msgid "FeatureFlags|Strategies"
-msgstr ""
+msgstr "Estrategias"
msgid "FeatureFlags|Target environments"
msgstr "Entornos de destino"
-msgid "FeatureFlags|There are no active feature flags"
-msgstr ""
-
-msgid "FeatureFlags|There are no inactive feature flags"
-msgstr ""
-
msgid "FeatureFlags|There was an error fetching the feature flags."
msgstr "Se ha producido un error al obtener las Feature Flag."
+msgid "FeatureFlags|There was an error retrieving user lists"
+msgstr ""
+
msgid "FeatureFlags|Try again in a few moments or contact your support team."
msgstr "Inténtelo de nuevo en un momento o contacte con su equipo de soporte."
@@ -9619,16 +9858,25 @@ msgid "FeatureFlags|User IDs"
msgstr "IDs de usuarios"
msgid "FeatureFlag|Delete strategy"
-msgstr ""
+msgstr "Eliminar estrategia"
+
+msgid "FeatureFlag|List"
+msgstr "Lista"
msgid "FeatureFlag|Percentage"
+msgstr "Porcentaje"
+
+msgid "FeatureFlag|Select a user list"
+msgstr "Seleccione una lista de usuarios"
+
+msgid "FeatureFlag|There are no configured user lists"
msgstr ""
msgid "FeatureFlag|Type"
-msgstr ""
+msgstr "Tipo"
msgid "FeatureFlag|User IDs"
-msgstr ""
+msgstr "Ids de usuario"
msgid "Feb"
msgstr "Feb"
@@ -9682,7 +9930,7 @@ msgid "File name"
msgstr "Nombre del archivo"
msgid "File renamed with no changes."
-msgstr ""
+msgstr "Archivo renombrado sin cambios."
msgid "File sync capacity"
msgstr ""
@@ -9718,7 +9966,7 @@ msgid "Filter by %{page_context_word} that are currently opened."
msgstr ""
msgid "Filter by Git revision"
-msgstr ""
+msgstr "Filtrar por revisión de Git"
msgid "Filter by commit message"
msgstr "Filtrar por mensaje del cambio"
@@ -9727,7 +9975,7 @@ msgid "Filter by issues that are currently closed."
msgstr ""
msgid "Filter by label"
-msgstr ""
+msgstr "Filtrar por etiqueta"
msgid "Filter by merge requests that are currently closed and unmerged."
msgstr ""
@@ -9780,6 +10028,12 @@ msgstr "Filtra sus proyectos por nombre"
msgid "Filter..."
msgstr "Filtrar..."
+msgid "Find File"
+msgstr ""
+
+msgid "Find bugs in your code with coverage-guided fuzzing"
+msgstr ""
+
msgid "Find by path"
msgstr "Buscar por ruta"
@@ -10044,6 +10298,9 @@ msgstr ""
msgid "Geo allows you to replicate your GitLab instance to other geographical locations."
msgstr "Geo le permite replicar su instancia de Gitlab a otras ubicaciones geográficas."
+msgid "Geo nodes are paused using a command run on the node"
+msgstr ""
+
msgid "GeoNodeStatusEvent|%{timeAgoStr} (%{pendingEvents} events)"
msgstr "%{timeAgoStr} (%{pendingEvents} eventos)"
@@ -10128,6 +10385,9 @@ msgstr ""
msgid "GeoNodes|Not checksummed"
msgstr "Sin suma de verificación"
+msgid "GeoNodes|Package files"
+msgstr ""
+
msgid "GeoNodes|Pausing replication stops the sync process. Are you sure?"
msgstr ""
@@ -10146,6 +10406,9 @@ msgstr "Slots de replicación WAL"
msgid "GeoNodes|Replication slots"
msgstr "Slots de replicación"
+msgid "GeoNodes|Replication status"
+msgstr ""
+
msgid "GeoNodes|Repositories"
msgstr "Repositorios"
@@ -10156,10 +10419,10 @@ msgid "GeoNodes|Repository verification progress"
msgstr "GeoNodes|Progreso de la verificación del repositorio"
msgid "GeoNodes|Selective (%{syncLabel})"
-msgstr ""
+msgstr "Selectivo (%{syncLabel})"
msgid "GeoNodes|Selective synchronization"
-msgstr ""
+msgstr "Sincronización selectiva"
msgid "GeoNodes|Something went wrong while changing node status"
msgstr "Se ha producido un error mientras se cambiaba el estado del nodo"
@@ -10186,7 +10449,7 @@ msgid "GeoNodes|Unverified"
msgstr " Sin verificar"
msgid "GeoNodes|Updated %{timeAgo}"
-msgstr ""
+msgstr "Actualizado %{timeAgo}"
msgid "GeoNodes|Used slots"
msgstr "Slots utilizados"
@@ -10210,16 +10473,16 @@ msgid "GeoNodes|You have configured Geo nodes using an insecure HTTP connection.
msgstr "Ha configurado los nodos Geo utilizando una conexión HTTP insegura. Le recomendamos la utilización de una conexión HTTPS."
msgid "GeoNodes|primary node"
-msgstr ""
+msgstr "nodo principal"
msgid "GeoNodes|secondary nodes"
-msgstr ""
+msgstr "nodos secundarios"
msgid "Geo|%{label} can't be blank"
-msgstr ""
+msgstr "%{label} no puede estar en blanco"
msgid "Geo|%{label} should be between 1-999"
-msgstr ""
+msgstr "%{label} debe estar entre 1-999"
msgid "Geo|%{name} is scheduled for forced re-download"
msgstr "%{name} está programado para una nueva descarga forzada"
@@ -10234,16 +10497,16 @@ msgid "Geo|Adjust your filters/search criteria above. If you believe this may be
msgstr ""
msgid "Geo|All %{replicable_name}"
-msgstr ""
+msgstr "Todo %{replicable_name}"
msgid "Geo|All projects"
msgstr "Todos los proyectos"
msgid "Geo|All projects are being scheduled for resync"
-msgstr ""
+msgstr "Todos los proyectos están programados para volver a sincronizar"
msgid "Geo|All projects are being scheduled for reverify"
-msgstr ""
+msgstr "Todos los proyectos están programados para volver a verificar"
msgid "Geo|Could not remove tracking entry for an existing project."
msgstr "No se puede eliminar la entrada de seguimiento para un proyecto existente."
@@ -10285,10 +10548,10 @@ msgid "Geo|Next sync scheduled at"
msgstr "Próxima sincronización programada en"
msgid "Geo|Node name can't be blank"
-msgstr ""
+msgstr "El nombre del nodo no puede estar en blanco"
msgid "Geo|Node name should be between 1 and 255 characters"
-msgstr ""
+msgstr "El nombre del nodo debe tener entre 1 y 255 caracteres"
msgid "Geo|Not synced yet"
msgstr "Sin sincronizar aún"
@@ -10324,13 +10587,13 @@ msgid "Geo|Remove entry"
msgstr "Eliminar entrada"
msgid "Geo|Remove tracking database entry"
-msgstr ""
+msgstr "Eliminar entrada de la base de datos de seguimiento"
msgid "Geo|Resync"
msgstr "Resincronizar"
msgid "Geo|Resync all"
-msgstr ""
+msgstr "Volver a sincronizar todo"
msgid "Geo|Retry count"
msgstr "Contador de reintentos"
@@ -10339,7 +10602,7 @@ msgid "Geo|Reverify"
msgstr "Volver a comprobar"
msgid "Geo|Reverify all"
-msgstr ""
+msgstr "Volver a comprobar todo"
msgid "Geo|Review replication status, and resynchronize and reverify items with the primary node."
msgstr ""
@@ -10356,6 +10619,9 @@ msgstr "Sincronizado en"
msgid "Geo|Synchronization failed - %{error}"
msgstr "Se ha producido un error durante la sincronización - %{error}"
+msgid "Geo|Synchronization of %{itemTitle} is disabled."
+msgstr ""
+
msgid "Geo|The database is currently %{db_lag} behind the primary node."
msgstr "La base de datos está %{db_lag} detrás del nodo primario."
@@ -10375,10 +10641,10 @@ msgid "Geo|Tracking entry for upload (%{type}/%{id}) was successfully removed."
msgstr "La entrada de seguimiento de carga (%{type}/%{id}) se ha eliminado correctamente."
msgid "Geo|URL can't be blank"
-msgstr ""
+msgstr "La URL no puede estar en blanco"
msgid "Geo|URL must be a valid url (ex: https://gitlab.com)"
-msgstr ""
+msgstr "La URL debe ser una URL válida (ej: https://gitlab.com)"
msgid "Geo|Unknown state"
msgstr "Estado desconocido"
@@ -10668,17 +10934,20 @@ msgstr "Ir a pantalla completa"
msgid "Go to %{link_to_google_takeout}."
msgstr "Ir a %{link_to_google_takeout}."
+msgid "Go to <strong>Issues</strong> > <strong>Boards</strong> to access your personalized learning issue board."
+msgstr ""
+
msgid "Go to Pipelines"
msgstr ""
msgid "Go to Webhooks"
-msgstr ""
+msgstr "Ir a los Webhooks"
msgid "Go to commits"
msgstr "Ir a commits"
msgid "Go to definition"
-msgstr ""
+msgstr "Ir a la definición"
msgid "Go to environments"
msgstr "Ir a los entornos"
@@ -10767,9 +11036,6 @@ msgstr "Ir a sus proyectos"
msgid "Go to your snippets"
msgstr "Ir a sus fragmentos de código"
-msgid "Golden Tanuki"
-msgstr "Tanuki dorado"
-
msgid "Google Cloud Platform"
msgstr "Google Cloud Platform"
@@ -10791,6 +11057,9 @@ msgstr "¡Lo tengo!"
msgid "Grafana URL"
msgstr "URL de Grafana"
+msgid "Grafana response contains invalid json"
+msgstr ""
+
msgid "GrafanaIntegration|API Token"
msgstr "Token API"
@@ -10839,9 +11108,6 @@ msgstr "Se ha programado el grupo %{group_name} para su eliminación."
msgid "Group %{group_name} was successfully created."
msgstr "El grupo %{group_name} se actualizó correctamente."
-msgid "Group '%{group_name}' is being imported."
-msgstr ""
-
msgid "Group Audit Events"
msgstr ""
@@ -10876,10 +11142,7 @@ msgid "Group avatar"
msgstr "Avatar del grupo"
msgid "Group by:"
-msgstr ""
-
-msgid "Group could not be imported: %{errors}"
-msgstr ""
+msgstr "Agrupar por:"
msgid "Group description"
msgstr "Descripción del grupo"
@@ -10908,9 +11171,15 @@ msgstr "Ya se ha marcado el grupo para su eliminación"
msgid "Group has not been marked for deletion"
msgstr "El grupo no se ha marcado para su eliminación"
+msgid "Group import could not be scheduled"
+msgstr ""
+
msgid "Group info:"
msgstr "Información del grupo:"
+msgid "Group is required when cluster_type is :group"
+msgstr ""
+
msgid "Group maintainers can register group runners in the %{link}"
msgstr "Los mantenedores de grupo pueden registrar grupos de ejecutores en el %{link}"
@@ -10974,6 +11243,24 @@ msgstr ""
msgid "GroupActivyMetrics|Recent activity (last 90 days)"
msgstr ""
+msgid "GroupImport|Failed to import group."
+msgstr ""
+
+msgid "GroupImport|Group '%{group_name}' is being imported."
+msgstr ""
+
+msgid "GroupImport|Group could not be imported: %{errors}"
+msgstr ""
+
+msgid "GroupImport|Please wait while we import the group for you. Refresh at will."
+msgstr ""
+
+msgid "GroupImport|The group was successfully imported."
+msgstr ""
+
+msgid "GroupImport|Unable to process group import file"
+msgstr ""
+
msgid "GroupRoadmap|%{dateWord} – No end date"
msgstr ""
@@ -11241,6 +11528,9 @@ msgstr "Grupos: (%{count})"
msgid "Groups (%{groups})"
msgstr "Grupos (%{groups})"
+msgid "Groups and projects"
+msgstr ""
+
msgid "Groups can also be nested by creating %{subgroup_docs_link_start}subgroups%{subgroup_docs_link_end}."
msgstr "Los grupos también se pueden anidar creando %{subgroup_docs_link_start}subgrupos%{subgroup_docs_link_end}."
@@ -11286,6 +11576,33 @@ msgstr "No se encuentran grupos"
msgid "GroupsEmptyState|You can manage your group member’s permissions and access to each project in the group."
msgstr "Puede administrar los permisos y el acceso de cada miembro del grupo a cada proyecto del grupo."
+msgid "GroupsNew|Contact an administrator to enable options for importing your group."
+msgstr ""
+
+msgid "GroupsNew|Create"
+msgstr ""
+
+msgid "GroupsNew|Create group"
+msgstr ""
+
+msgid "GroupsNew|GitLab group export"
+msgstr ""
+
+msgid "GroupsNew|Import"
+msgstr ""
+
+msgid "GroupsNew|Import group"
+msgstr ""
+
+msgid "GroupsNew|My Awesome Group"
+msgstr ""
+
+msgid "GroupsNew|No import options available"
+msgstr ""
+
+msgid "GroupsNew|To copy a GitLab group between installations, navigate to the group settings page for the original installation, generate an export file, and upload it here."
+msgstr ""
+
msgid "GroupsTree|Are you sure you want to leave the \"%{fullName}\" group?"
msgstr "¿Está seguro que desea dejar el grupo \"%{fullName}\"?"
@@ -11388,6 +11705,9 @@ msgstr "Ayuda a reducir el volumen de alertas (por ejemplo, si se crean demasiad
msgid "Helps reduce request volume for protected paths"
msgstr "Ayuda a reducir el volumen de solicitudes para rutas protegidas"
+msgid "Here are all your projects in your group, including the one you just created. To start, let’s take a look at your personalized learning project which will help you learn about GitLab at your own pace."
+msgstr ""
+
msgid "Here you will find recent merge request activity"
msgstr ""
@@ -11399,8 +11719,11 @@ msgstr "Ocultar proyectos archivados"
msgid "Hide chart"
msgid_plural "Hide charts"
-msgstr[0] ""
-msgstr[1] ""
+msgstr[0] "Ocultar gráfico"
+msgstr[1] "Ocultar gráficos"
+
+msgid "Hide details"
+msgstr ""
msgid "Hide file browser"
msgstr "Ocultar el explorador de archivos"
@@ -11411,6 +11734,9 @@ msgstr "Ocultar grupos de proyectos"
msgid "Hide host keys manual input"
msgstr "Ocultar la entrada manual de claves del host"
+msgid "Hide list"
+msgstr ""
+
msgid "Hide marketing-related entries from help"
msgstr "Ocultar en la ayuda las entradas relacionadas con marketing"
@@ -11431,11 +11757,8 @@ msgstr[1] "Ocultar valores"
msgid "Hide values"
msgstr "Ocultar valores"
-msgid "Hiding all labels"
-msgstr "Ocultar todas las etiquetas"
-
msgid "High or unknown vulnerabilities present"
-msgstr ""
+msgstr "Hay vulnerabilidades altas o desconocidas presentes"
msgid "Highest number of requests per minute for each raw path, default to 300. To disable throttling set to 0."
msgstr "Mayor número de solicitudes por minuto para cada ruta sin procesar, por defecto, 300. Para deshabilitar esta limitación, establezca en 0."
@@ -11450,7 +11773,7 @@ msgid "History of authentications"
msgstr "Historial de autentificación"
msgid "Homepage"
-msgstr ""
+msgstr "Página principal"
msgid "Hook execution failed. Ensure the group has a project with commits."
msgstr "Falló la ejecución del hook. Asegúrese de que el grupo tiene, al menos, un proyecto con commits."
@@ -11462,7 +11785,7 @@ msgid "Hook was successfully updated."
msgstr "El hook se actualizó correctamente."
msgid "Hostname"
-msgstr ""
+msgstr "Nombre del host"
msgid "Hour (UTC)"
msgstr "Hora (UTC)"
@@ -11518,6 +11841,9 @@ msgstr "ID"
msgid "ID:"
msgstr "ID:"
+msgid "IDE"
+msgstr ""
+
msgid "IDE|Allow live previews of JavaScript projects in the Web IDE using CodeSandbox Live Preview."
msgstr ""
@@ -11561,10 +11887,10 @@ msgid "IDE|This option is disabled because you don't have write permissions for
msgstr ""
msgid "INFO: Your SSH key has expired. Please generate a new key."
-msgstr ""
+msgstr "INFO: Su clave SSH ha caducado. Por favor, genere una nueva clave."
msgid "INFO: Your SSH key is expiring soon. Please generate a new key."
-msgstr ""
+msgstr "INFO: Su clave SSH caducará pronto. Por favor genere una nueva clave."
msgid "IP Address"
msgstr "Direccion IP"
@@ -11608,12 +11934,12 @@ msgstr "Si está deshabilitado, el nivel de acceso dependerá de los permisos de
msgid "If enabled"
msgstr "Si está habilitado"
+msgid "If enabled, GitLab will handle Object Storage replication using Geo. %{linkStart}More information%{linkEnd}"
+msgstr ""
+
msgid "If enabled, access to projects will be validated on an external service using their classification label."
msgstr "Si está habilitado, el acceso a los proyectos se validará en un servicio externo utilizando su etiqueta de clasificación."
-msgid "If enabled, and if object storage is enabled, GitLab will handle Object Storage replication using Geo"
-msgstr ""
-
msgid "If there is no previous license or if the previous license has expired, some GitLab functionality will be blocked until a new, valid license is uploaded."
msgstr ""
@@ -11657,10 +11983,10 @@ msgid "Ignored"
msgstr "Ignorado"
msgid "Image Details"
-msgstr ""
+msgstr "Detalles de la imagen"
msgid "Image URL"
-msgstr ""
+msgstr "URL de la imagen"
msgid "ImageDiffViewer|2-up"
msgstr "2-up"
@@ -11696,7 +12022,7 @@ msgid "Import all compatible projects"
msgstr "Importar todos los proyectos compatibles"
msgid "Import all compatible repositories"
-msgstr ""
+msgstr "Importar todos los repositorios compatibles"
msgid "Import all projects"
msgstr "Importar todos los proyectos"
@@ -11888,10 +12214,10 @@ msgid "Incompatible options set!"
msgstr "¡Conjunto de opciones incompatibles configuradas!"
msgid "Incompatible project"
-msgstr ""
+msgstr "Proyecto incompatible"
msgid "Indent"
-msgstr ""
+msgstr "Indentar"
msgid "Index all projects"
msgstr "Indexar todos los proyectos"
@@ -11918,22 +12244,22 @@ msgid "Input your repository URL"
msgstr "Introduzca la URL de su repositorio"
msgid "Insert"
-msgstr ""
+msgstr "Insertar"
msgid "Insert a code block"
-msgstr ""
+msgstr "Insertar un bloque de código"
msgid "Insert a quote"
msgstr "Insertar una cita"
msgid "Insert an image"
-msgstr ""
+msgstr "Insertar una imagen"
msgid "Insert code"
msgstr "Insertar código"
msgid "Insert inline code"
-msgstr ""
+msgstr "Insertar código en línea"
msgid "Insert suggestion"
msgstr "Insertar sugerencia"
@@ -11973,6 +12299,9 @@ msgid_plural "Instances"
msgstr[0] "Instancia"
msgstr[1] "Instancias"
+msgid "Instance Configuration"
+msgstr ""
+
msgid "Instance Statistics visibility"
msgstr "Visibilidad de las estadísticas de instancia"
@@ -11994,14 +12323,11 @@ msgstr "Ajustes de integración"
msgid "Integrations"
msgstr "Integraciones"
-msgid "Integrations allow you to integrate GitLab with other applications"
-msgstr "Las integraciones le permiten integrar GitLab con otras aplicaciones"
-
msgid "Integrations|All details"
-msgstr ""
+msgstr "Todos los detalles"
msgid "Integrations|Comment detail:"
-msgstr ""
+msgstr "Detalles del comentario:"
msgid "Integrations|Comment settings:"
msgstr ""
@@ -12016,7 +12342,7 @@ msgid "Integrations|Includes commit title and branch"
msgstr ""
msgid "Integrations|Standard"
-msgstr ""
+msgstr "Estándar"
msgid "Integrations|When a Jira issue is mentioned in a commit or merge request a remote link and comment (if enabled) will be created."
msgstr ""
@@ -12108,6 +12434,9 @@ msgstr ""
msgid "Invalid query"
msgstr "Consulta no válida"
+msgid "Invalid repository bundle for snippet with id %{snippet_id}"
+msgstr ""
+
msgid "Invalid repository path"
msgstr "Ruta del repositorio no válida"
@@ -12126,6 +12455,9 @@ msgstr ""
msgid "Invalid two-factor code."
msgstr "Código de dos factores no válido."
+msgid "Invalid yaml"
+msgstr ""
+
msgid "Invitation"
msgstr "Invitación"
@@ -12217,31 +12549,31 @@ msgid "Issue weight"
msgstr "Peso de la incidencia"
msgid "IssueAnalytics|Age"
-msgstr ""
+msgstr "Edad"
msgid "IssueAnalytics|Assignees"
-msgstr ""
+msgstr "Asignados"
msgid "IssueAnalytics|Due date"
-msgstr ""
+msgstr "Fecha de fin"
msgid "IssueAnalytics|Failed to load issues. Please try again."
-msgstr ""
+msgstr "Se ha producido un error al cargar las incidencias. Por favor, inténtelo de nuevo."
msgid "IssueAnalytics|Issue"
-msgstr ""
+msgstr "Incidencia"
msgid "IssueAnalytics|Milestone"
-msgstr ""
+msgstr "Hito"
msgid "IssueAnalytics|Opened by"
-msgstr ""
+msgstr "Abierto por"
msgid "IssueAnalytics|Status"
-msgstr ""
+msgstr "Estado"
msgid "IssueAnalytics|Weight"
-msgstr ""
+msgstr "Peso"
msgid "IssueBoards|Board"
msgstr "Tablero"
@@ -12283,7 +12615,7 @@ msgid "Issues"
msgstr "Incidencias"
msgid "Issues Analytics"
-msgstr ""
+msgstr "Análisis de incidencias"
msgid "Issues Rate Limits"
msgstr ""
@@ -12345,17 +12677,26 @@ msgstr ""
msgid "It seems like the Dependency Scanning job ran successfully, but no dependencies have been detected in your project."
msgstr ""
+msgid "It seems that there is currently no available data for code coverage"
+msgstr ""
+
msgid "It's you"
msgstr "¡Es usted!"
-msgid "Iteration changed to"
+msgid "Iteration"
msgstr ""
+msgid "Iteration changed to"
+msgstr "Iteración cambiada a"
+
msgid "Iteration removed"
+msgstr "Iteración eliminada"
+
+msgid "Iteration updated"
msgstr ""
msgid "Iterations"
-msgstr ""
+msgstr "Iteraciones"
msgid "Iteration|Dates cannot overlap with other existing Iterations"
msgstr ""
@@ -12384,12 +12725,18 @@ msgstr "Ene"
msgid "January"
msgstr "Enero"
+msgid "Jira Issues"
+msgstr ""
+
msgid "Jira import is already running."
msgstr ""
msgid "Jira integration not configured."
msgstr "Integración con Jira no configurada."
+msgid "Jira project key is not configured"
+msgstr ""
+
msgid "Jira project: %{importProject}"
msgstr "Proyecto Jira: %{importProject}"
@@ -12553,10 +12900,10 @@ msgid "July"
msgstr "Julio"
msgid "Jump to first unresolved thread"
-msgstr ""
+msgstr "Saltar al primer hilo sin resolver"
msgid "Jump to next unresolved thread"
-msgstr ""
+msgstr "Saltar al siguiente hilo sin resolver"
msgid "Jun"
msgstr "Jun"
@@ -12570,6 +12917,9 @@ msgstr "Solo yo"
msgid "Keep divergent refs"
msgstr ""
+msgid "Kerberos access denied"
+msgstr ""
+
msgid "Key"
msgstr "Clave"
@@ -12579,12 +12929,15 @@ msgstr "Clave (PEM)"
msgid "Key: %{key}"
msgstr "Clave: %{key}"
-msgid "Keyboard Shortcuts"
-msgstr "Atajos de teclado"
-
msgid "Keyboard shortcuts"
msgstr "Atajos de teclado"
+msgid "Keys"
+msgstr ""
+
+msgid "Ki"
+msgstr ""
+
msgid "Kubernetes"
msgstr "Kubernetes"
@@ -12624,6 +12977,9 @@ msgstr "Kubernetes popover"
msgid "LDAP"
msgstr "LDAP"
+msgid "LDAP Synchronization"
+msgstr ""
+
msgid "LDAP settings"
msgstr "Configuración LDAP"
@@ -12848,12 +13204,18 @@ msgstr "Obtenga más información sobre las aprobaciones."
msgid "Learn more about custom project templates"
msgstr "Más información sobre las plantillas de proyecto personalizadas"
+msgid "Learn more about deploying to AWS"
+msgstr ""
+
msgid "Learn more about deploying to a cluster"
msgstr "Obtenga más información sobre los despliegues en un clúster"
msgid "Learn more about group-level project templates"
msgstr "Obtenga más información sobre las plantillas de proyectos a nivel de grupo"
+msgid "Learn more about job dependencies"
+msgstr ""
+
msgid "Learn more about signing commits"
msgstr "Más información sobre los commits firmados"
@@ -12888,7 +13250,7 @@ msgid "Leave the \"File type\" and \"Delivery method\" options on their default
msgstr "Deja las opciones \"Tipo de archivo\" y \"Método de entrega\" con sus valores por defecto."
msgid "Leave zen mode"
-msgstr ""
+msgstr "Abandonar el modo zen"
msgid "Let's Encrypt does not accept emails on example.com"
msgstr "Let's Encrypt no acepta correos electrónicos de example.com"
@@ -12903,16 +13265,28 @@ msgid "License Compliance"
msgstr "License Compliance"
msgid "License History"
+msgstr "Historial de licencias"
+
+msgid "License ID:"
+msgstr ""
+
+msgid "License URL"
msgstr ""
msgid "License-Check"
msgstr "Comprobación de licencia"
+msgid "LicenseCompliance|%{docLinkStart}License Approvals%{docLinkEnd} are active"
+msgstr ""
+
+msgid "LicenseCompliance|%{docLinkStart}License Approvals%{docLinkEnd} are inactive"
+msgstr ""
+
msgid "LicenseCompliance|Add a license"
msgstr "Agregar una licencia"
msgid "LicenseCompliance|Add license and related policy"
-msgstr ""
+msgstr "Añadir una licencia y una política relacionada"
msgid "LicenseCompliance|Allow"
msgstr "Permitir"
@@ -12929,9 +13303,15 @@ msgstr "Denegado"
msgid "LicenseCompliance|Deny"
msgstr "Denegar"
+msgid "LicenseCompliance|Learn more about %{linkStart}License Approvals%{linkEnd}"
+msgstr ""
+
msgid "LicenseCompliance|License"
msgstr "Licencia"
+msgid "LicenseCompliance|License Approvals"
+msgstr ""
+
msgid "LicenseCompliance|License Compliance detected %d license and policy violation for the source branch only; approval required"
msgid_plural "LicenseCompliance|License Compliance detected %d licenses and policy violations for the source branch only; approval required"
msgstr[0] ""
@@ -12965,7 +13345,7 @@ msgid "LicenseCompliance|License name"
msgstr "Nombre de la licencia"
msgid "LicenseCompliance|License review"
-msgstr ""
+msgstr "Revisión de la licencia"
msgid "LicenseCompliance|Packages"
msgstr "Paquetes"
@@ -13099,7 +13479,7 @@ msgstr[0] "Limitado a mostrar %d evento como máximo"
msgstr[1] "Limitado a mostrar %d eventos como máximo"
msgid "Line changes"
-msgstr ""
+msgstr "Cambios de línea"
msgid "Link Prometheus monitoring to GitLab."
msgstr ""
@@ -13152,6 +13532,9 @@ msgstr "Vista de lista"
msgid "List your Bitbucket Server repositories"
msgstr "Listar sus repositorios de Bitbucket Server"
+msgid "Lists"
+msgstr ""
+
msgid "Live preview"
msgstr "Vista previa"
@@ -13264,7 +13647,7 @@ msgid "MR widget|The pipeline will now run automatically every time you commit c
msgstr ""
msgid "MRApprovals|Approvals"
-msgstr ""
+msgstr "Aprobaciones"
msgid "MRApprovals|Approved by"
msgstr "Aprobado por"
@@ -13300,7 +13683,7 @@ msgid "Make sure you're logged into the account that owns the projects you'd lik
msgstr "Asegúrese de haber iniciado sesión en la cuenta que posee los proyectos que desea importar."
msgid "Make this epic confidential"
-msgstr ""
+msgstr "Marcar esta tarea épica cómo confidencial"
msgid "Makes this issue confidential."
msgstr "Convierte la incidencia en confidencial."
@@ -13330,11 +13713,14 @@ msgid "Manage labels"
msgstr "Administrar etiquetas"
msgid "Manage milestones"
-msgstr ""
+msgstr "Administrar hitos"
msgid "Manage project labels"
msgstr "Administrar etiquetas de proyectos"
+msgid "Manage storage usage"
+msgstr ""
+
msgid "Manage two-factor authentication"
msgstr "Administrar autenticación de dos factores"
@@ -13350,6 +13736,9 @@ msgstr "Manifiesto"
msgid "Manifest file import"
msgstr "Importar fichero de manifiesto"
+msgid "Manifest import"
+msgstr ""
+
msgid "Manual job"
msgstr "Trabajo manual"
@@ -13497,6 +13886,12 @@ msgstr "Duración máxima de una sesión."
msgid "Maximum field length"
msgstr "Longitud máxima del campo"
+msgid "Maximum file size is 2MB. Please select a smaller file."
+msgstr ""
+
+msgid "Maximum import size (MB)"
+msgstr ""
+
msgid "Maximum job timeout"
msgstr "Tiempo de espera máximo para el trabajo"
@@ -13536,6 +13931,9 @@ msgstr ""
msgid "Maximum size of Elasticsearch bulk indexing requests."
msgstr ""
+msgid "Maximum size of import files."
+msgstr ""
+
msgid "Maximum size of individual attachments in comments."
msgstr ""
@@ -13587,6 +13985,9 @@ msgstr "Merge"
msgid "Merge (when the pipeline succeeds)"
msgstr "Merge (cuando el pipeline finalice correctamente)"
+msgid "Merge Conflicts"
+msgstr ""
+
msgid "Merge Request"
msgstr "Merge request"
@@ -13713,6 +14114,9 @@ msgstr "Se ha producido un error al guardar el comentario"
msgid "MergeRequests|Squash task canceled: another squash is already in progress."
msgstr "Tarea de squash cancelada: Ya hay otro squash en progreso."
+msgid "MergeRequests|This project does not allow squashing commits when merge requests are accepted."
+msgstr ""
+
msgid "MergeRequests|Thread stays resolved"
msgstr "El hilo permanece resuelto"
@@ -13866,6 +14270,12 @@ msgstr ""
msgid "MetricsSettings|Add a button to the metrics dashboard linking directly to your existing external dashboard."
msgstr ""
+msgid "MetricsSettings|Choose whether to display dashboard metrics in UTC or the user's local timezone."
+msgstr ""
+
+msgid "MetricsSettings|Dashboard timezone"
+msgstr ""
+
msgid "MetricsSettings|External dashboard URL"
msgstr ""
@@ -13875,6 +14285,12 @@ msgstr ""
msgid "MetricsSettings|Metrics Dashboard"
msgstr ""
+msgid "MetricsSettings|UTC (Coordinated Universal Time)"
+msgstr ""
+
+msgid "MetricsSettings|User's local timezone"
+msgstr ""
+
msgid "Metrics|Add metric"
msgstr "Añadir métrica"
@@ -13890,6 +14306,9 @@ msgstr ""
msgid "Metrics|Create metric"
msgstr "Crear métrica"
+msgid "Metrics|Current"
+msgstr ""
+
msgid "Metrics|Delete metric"
msgstr "Eliminar métrica"
@@ -13907,8 +14326,8 @@ msgstr "Duplicando..."
msgid "Metrics|Edit metric"
msgid_plural "Metrics|Edit metrics"
-msgstr[0] ""
-msgstr[1] ""
+msgstr[0] "Editar métrica"
+msgstr[1] "Editar métricas"
msgid "Metrics|Expand panel"
msgstr ""
@@ -13917,10 +14336,10 @@ msgid "Metrics|For grouping similar metrics"
msgstr "Para agrupar métricas similares"
msgid "Metrics|Go back (Esc)"
-msgstr ""
+msgstr "Volver (Esc)"
msgid "Metrics|Invalid time range, please verify."
-msgstr ""
+msgstr "Rango de tiempo inválido, por favor verifíquelo."
msgid "Metrics|Label of the y-axis (usually the unit). The x-axis always represents time."
msgstr "Etiqueta del eje Y (normalmente la unidad). El eje X siempre representa el tiempo."
@@ -13940,6 +14359,9 @@ msgstr ""
msgid "Metrics|Max"
msgstr "Max"
+msgid "Metrics|Min"
+msgstr ""
+
msgid "Metrics|Must be a valid PromQL query."
msgstr "Debe ser una consulta PromQL válida."
@@ -13953,6 +14375,9 @@ msgid "Metrics|Prometheus Query Documentation"
msgstr "Documentción sobre las consultas de Prometheus"
msgid "Metrics|Refresh dashboard"
+msgstr "Actualizar el panel de control"
+
+msgid "Metrics|Select a value"
msgstr ""
msgid "Metrics|Star dashboard"
@@ -13973,12 +14398,18 @@ msgstr "Se ha producido un error al recuperar los datos de los distintos entorno
msgid "Metrics|There was an error getting annotations information."
msgstr ""
+msgid "Metrics|There was an error getting dashboard validation warnings information."
+msgstr ""
+
msgid "Metrics|There was an error getting deployment information."
msgstr "Se ha producido un error al obtener información sobre los despliegues."
msgid "Metrics|There was an error getting environments information."
msgstr "Se ha producido un error al obtener información sobre los entornos."
+msgid "Metrics|There was an error getting options for variable \"%{name}\"."
+msgstr ""
+
msgid "Metrics|There was an error trying to validate your query"
msgstr "Se ha producido un error al intentar validar su consulta"
@@ -14021,6 +14452,9 @@ msgstr ""
msgid "Metrics|You're about to permanently delete this metric. This cannot be undone."
msgstr "Está a punto de eliminar permanentemente esta métrica. Esta acción no se puede deshacer."
+msgid "Metrics|Your dashboard schema is invalid. Edit the dashboard to correct the YAML schema."
+msgstr ""
+
msgid "Metrics|e.g. HTTP requests"
msgstr "Por ejemplo, solicitudes HTTP"
@@ -14036,12 +14470,18 @@ msgstr "por ejemplo, tasa (http_requests_total[5m])"
msgid "Metrics|e.g. req/sec"
msgstr "por ejemplo, req/seg."
+msgid "Mi"
+msgstr ""
+
msgid "Microsoft Azure"
msgstr "Microsoft Azure"
msgid "Middleman project with Static Site Editor support"
msgstr ""
+msgid "Migrate your data from an external source like GitHub, Bitbucket, or another instance of GitLab."
+msgstr ""
+
msgid "Migrated %{success_count}/%{total_count} files."
msgstr "Migrados %{success_count}/%{total_count} archivos."
@@ -14060,22 +14500,22 @@ msgid "Milestone lists show all issues from the selected milestone."
msgstr "Las listas de hitos muestran todas las incidencias desde el hito seleccionado."
msgid "MilestoneSidebar|Closed:"
-msgstr ""
+msgstr "Cerrado:"
msgid "MilestoneSidebar|Copy reference"
-msgstr ""
+msgstr "Copiar referencia"
msgid "MilestoneSidebar|Due date"
-msgstr ""
+msgstr "Fecha de fin"
msgid "MilestoneSidebar|Edit"
-msgstr ""
+msgstr "Editar"
msgid "MilestoneSidebar|From"
-msgstr ""
+msgstr "De"
msgid "MilestoneSidebar|Issues"
-msgstr ""
+msgstr "Incidencias"
msgid "MilestoneSidebar|Merge requests"
msgstr ""
@@ -14096,22 +14536,22 @@ msgid "MilestoneSidebar|No start date"
msgstr ""
msgid "MilestoneSidebar|None"
-msgstr ""
+msgstr "Ninguno"
msgid "MilestoneSidebar|Open:"
-msgstr ""
+msgstr "Abrir:"
msgid "MilestoneSidebar|Reference:"
-msgstr ""
+msgstr "Referencia:"
msgid "MilestoneSidebar|Start date"
-msgstr ""
+msgstr "Fecha de inicio"
msgid "MilestoneSidebar|Toggle sidebar"
-msgstr ""
+msgstr "Alternar barra lateral"
msgid "MilestoneSidebar|Until"
-msgstr ""
+msgstr "Hasta"
msgid "MilestoneSidebar|complete"
msgstr ""
@@ -14176,6 +14616,9 @@ msgstr ""
msgid "Minimum capacity to be available before we schedule more mirrors preemptively."
msgstr "Capacidad mínima que debe estar disponible antes de que programemos más mirrors de forma preventiva."
+msgid "Minimum interval in days"
+msgstr ""
+
msgid "Minimum length is %{minimum_password_length} characters"
msgstr "La longitud mínima es de %{minimum_password_length} caracteres"
@@ -14362,6 +14805,12 @@ msgstr "Se encontraron varios tipos de modelo: %{model_types}"
msgid "Multiple uploaders found: %{uploader_types}"
msgstr "Se encontraron varios tipos de cargadores: %{uploader_types}"
+msgid "Must match with the %{codeStart}external_url%{codeEnd} in %{codeStart}/etc/gitlab/gitlab.rb%{codeEnd}."
+msgstr ""
+
+msgid "Must match with the %{codeStart}geo_node_name%{codeEnd} in %{codeStart}/etc/gitlab/gitlab.rb%{codeEnd}. %{linkStart}More information%{linkEnd}"
+msgstr ""
+
msgid "My Awesome Group"
msgstr ""
@@ -14369,7 +14818,7 @@ msgid "My company or team"
msgstr "Mi empresa o equipo"
msgid "My-Reaction"
-msgstr ""
+msgstr "Mi reacción"
msgid "Name"
msgstr "Nombre"
@@ -14438,31 +14887,31 @@ msgid "NetworkPolicies|Invalid or empty policy"
msgstr ""
msgid "NetworkPolicies|Kubernetes error: %{error}"
-msgstr ""
+msgstr "Error de Kubernetes: %{error}"
msgid "NetworkPolicies|Last modified"
-msgstr ""
+msgstr "Última modificación"
msgid "NetworkPolicies|Name"
-msgstr ""
+msgstr "Nombre"
msgid "NetworkPolicies|No policies detected"
-msgstr ""
+msgstr "No se han detectado políticas"
msgid "NetworkPolicies|Policies are a specification of how groups of pods are allowed to communicate with each other's network endpoints."
-msgstr ""
+msgstr "Las políticas son una especificación de cómo se permite a los grupos de pods comunicarse con los extremos de red de los demás."
msgid "NetworkPolicies|Policy %{policyName} was successfully changed"
msgstr ""
msgid "NetworkPolicies|Policy definition"
-msgstr ""
+msgstr "Definición de la política"
msgid "NetworkPolicies|Something went wrong, failed to update policy"
-msgstr ""
+msgstr "Algo salió mal, se ha producido un error al actualizar la política"
msgid "NetworkPolicies|Something went wrong, unable to fetch policies"
-msgstr ""
+msgstr "Algo salió mal, se ha producido un error al obtener las políticas"
msgid "NetworkPolicies|Status"
msgstr ""
@@ -14476,10 +14925,16 @@ msgstr "Nuevo"
msgid "New Application"
msgstr "Nueva aplicación"
+msgid "New Branch"
+msgstr ""
+
+msgid "New Deploy Key"
+msgstr ""
+
msgid "New Environment"
msgstr "Nuevo entorno"
-msgid "New Geo Node"
+msgid "New File"
msgstr ""
msgid "New Group"
@@ -14496,15 +14951,15 @@ msgid_plural "New Issues"
msgstr[0] "Nueva incidencia"
msgstr[1] "Nuevas incidencias"
-msgid "New Iteration"
-msgstr ""
-
msgid "New Jira import"
-msgstr ""
+msgstr "Nueva importación de Jira"
msgid "New Label"
msgstr "Nueva etiqueta"
+msgid "New Merge Request"
+msgstr ""
+
msgid "New Milestone"
msgstr "Nuevo hito"
@@ -14523,6 +14978,9 @@ msgstr "Nuevo proyecto"
msgid "New Snippet"
msgstr "Nuevo fragmento de código"
+msgid "New User"
+msgstr ""
+
msgid "New branch"
msgstr "Nueva rama"
@@ -14565,9 +15023,12 @@ msgstr "Nueva incidencia"
msgid "New issue title"
msgstr "Nuevo título de la incidencia"
-msgid "New iteration created"
+msgid "New iteration"
msgstr ""
+msgid "New iteration created"
+msgstr "Nueva iteración creada"
+
msgid "New label"
msgstr "Nueva etiqueta"
@@ -14626,13 +15087,13 @@ msgid "Next"
msgstr "Siguiente"
msgid "Next commit"
-msgstr ""
+msgstr "Siguiente commit"
msgid "Next file in diff"
-msgstr ""
+msgstr "Siguiente archivo en diff"
msgid "Next unresolved discussion"
-msgstr ""
+msgstr "Siguiente discusión sin resolver"
msgid "Nickname"
msgstr "Seudónimo"
@@ -14650,7 +15111,7 @@ msgid "No Epic"
msgstr "No hay tarea épica"
msgid "No Scopes"
-msgstr ""
+msgstr "Sin alcances"
msgid "No Tag"
msgstr "Sin etiquetas"
@@ -14665,7 +15126,7 @@ msgid "No application_settings found"
msgstr "No se han encontrado application_settings"
msgid "No approvers"
-msgstr ""
+msgstr "Sin aprobadores"
msgid "No authentication methods configured."
msgstr "No hay métodos de autenticación configurados."
@@ -14742,6 +15203,12 @@ msgstr ""
msgid "No grouping"
msgstr ""
+msgid "No iteration"
+msgstr ""
+
+msgid "No iterations to show"
+msgstr ""
+
msgid "No job log"
msgstr "No hay registro de tareas"
@@ -14749,7 +15216,7 @@ msgid "No jobs to show"
msgstr "No hay trabajos para mostrar"
msgid "No label"
-msgstr ""
+msgstr "Sin etiqueta"
msgid "No labels with such name or description"
msgstr "No hay etiquetas con ese nombre o descripción"
@@ -14761,10 +15228,10 @@ msgid "No licenses found."
msgstr "No se encontraron licencias."
msgid "No matches found"
-msgstr ""
+msgstr "No hay coincidencias"
msgid "No matching labels"
-msgstr ""
+msgstr "No hay etiquetas coincidentes"
msgid "No matching results"
msgstr "No se han encontrado resultados"
@@ -14776,7 +15243,7 @@ msgid "No messages were logged"
msgstr "No se registraron mensajes"
msgid "No milestone"
-msgstr ""
+msgstr "Sin hito"
msgid "No milestones to show"
msgstr "No hay hitos para mostrar"
@@ -14784,6 +15251,9 @@ msgstr "No hay hitos para mostrar"
msgid "No other labels with such name or description"
msgstr "No hay otras etiquetas con ese nombre o descripción"
+msgid "No panels matching properties %{opts}"
+msgstr ""
+
msgid "No parent group"
msgstr "Ningún grupo padre"
@@ -14791,7 +15261,7 @@ msgid "No pods available"
msgstr "No hay pods disponibles"
msgid "No policy matches this license"
-msgstr ""
+msgstr "Ninguna política coincide con esta licencia"
msgid "No preview for this file type"
msgstr "No hay vista previa para este tipo de archivo"
@@ -14817,6 +15287,9 @@ msgstr "No se encontraron ejecutores"
msgid "No schedules"
msgstr "No hay programaciones"
+msgid "No stack trace for this error"
+msgstr ""
+
msgid "No starrers matched your search"
msgstr "No hay ningún favorito que coincida con su búsqueda"
@@ -14829,8 +15302,8 @@ msgstr "Ninguna plantilla"
msgid "No test coverage"
msgstr "No existe ninguna prueba de cobertura"
-msgid "No thanks, don't show this again"
-msgstr "¡Gracias! No mostrar de nuevo"
+msgid "No thanks"
+msgstr ""
msgid "No vulnerabilities present"
msgstr "No se han encontrado vulnerabilidades"
@@ -14844,9 +15317,6 @@ msgstr ""
msgid "No, directly import the existing email addresses and usernames."
msgstr "No, importe directamente las direcciones de correo electrónico y los nombres de usuario existentes."
-msgid "No, not interested right now"
-msgstr "No, no estoy interesado ahora mismo"
-
msgid "No. of commits"
msgstr "Número de commits"
@@ -14892,9 +15362,6 @@ msgstr "No hay suficientes datos"
msgid "Not found."
msgstr "No encontrado."
-msgid "Not helpful"
-msgstr "No es útil"
-
msgid "Not now"
msgstr "Ahora no"
@@ -15051,6 +15518,9 @@ msgstr "Notificaciones desactivadas"
msgid "Notifications on"
msgstr "Notificaciones activadas"
+msgid "Notify users by email when sign-in location is not recognized"
+msgstr ""
+
msgid "Nov"
msgstr "Nov"
@@ -15117,9 +15587,6 @@ msgstr "Filtrar"
msgid "Oh no!"
msgstr "¡Oh, no!"
-msgid "Ok let's go"
-msgstr "Adelante"
-
msgid "Oldest first"
msgstr "Más antiguo primero"
@@ -15132,17 +15599,50 @@ msgstr ""
msgid "On track"
msgstr ""
+msgid "OnDemandScans|Attached branch"
+msgstr ""
+
+msgid "OnDemandScans|Attached branch is where the scan job runs."
+msgstr ""
+
+msgid "OnDemandScans|Could not run the scan. Please try again."
+msgstr ""
+
msgid "OnDemandScans|Create new DAST scan"
+msgstr "Crear un nuevo escaneo DAST"
+
+msgid "OnDemandScans|DAST will scan the target URL and any discovered sub URLs."
+msgstr ""
+
+msgid "OnDemandScans|New on-demand DAST scan"
msgstr ""
msgid "OnDemandScans|On-demand Scans"
+msgstr "Escaneos bajo demanda"
+
+msgid "OnDemandScans|On-demand scans run outside the DevOps cycle and find vulnerabilities in your projects. %{learnMoreLinkStart}Learn more%{learnMoreLinkEnd}"
+msgstr ""
+
+msgid "OnDemandScans|Only a passive scan can be performed on demand."
+msgstr ""
+
+msgid "OnDemandScans|Passive DAST Scan"
+msgstr ""
+
+msgid "OnDemandScans|Please enter a valid URL format, ex: http://www.example.com/home"
+msgstr ""
+
+msgid "OnDemandScans|Run this scan"
+msgstr ""
+
+msgid "OnDemandScans|Scan mode"
msgstr ""
msgid "OnDemandScans|Schedule or run scans immediately against target sites. Currently available on-demand scan type: DAST. %{helpLinkStart}More information%{helpLinkEnd}"
msgstr ""
-msgid "Onboarding"
-msgstr "Primeros pasos"
+msgid "OnDemandScans|Target URL"
+msgstr ""
msgid "Once imported, repositories can be mirrored over SSH. Read more %{link_start}here%{link_end}."
msgstr "Una vez importados, los repositorios se pueden replicar vía de SSH. Para obtener más información, haga clic %{link_start}aquí%{link_end}."
@@ -15167,6 +15667,9 @@ msgstr "Uno o más grupos a los que no tiene acceso."
msgid "One or more of you personal access tokens were revoked"
msgstr "Uno o más de sus tokens de acceso personal han sido revocados"
+msgid "One or more of your %{provider} projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
+msgstr ""
+
msgid "One or more of your Bitbucket projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
msgstr "No ha sido posible importar uno o más de sus proyectos de Bitbucket a GitLab porque utilizan Subversion o Mercurial como sistema de control de versiones, en lugar de Git."
@@ -15252,7 +15755,7 @@ msgid "Open sidebar"
msgstr "Abrir barra lateral"
msgid "Open: %{openIssuesCount}"
-msgstr ""
+msgstr "Abiertos: %{openIssuesCount}"
msgid "Open: %{open} • Closed: %{closed}"
msgstr "Abierto: %{open} • Cerrado: %{closed}"
@@ -15261,7 +15764,7 @@ msgid "Opened"
msgstr "Abierto"
msgid "Opened MRs"
-msgstr ""
+msgstr "MRs abiertos"
msgid "Opened issues"
msgstr "Incidencias abiertas"
@@ -15276,7 +15779,7 @@ msgid "Operation failed. Check pod logs for %{pod_name} for more details."
msgstr "La operación ha fallado. Por favor, compruebe los registros del pod para %{pod_name} para más obtener más información."
msgid "Operation not allowed"
-msgstr ""
+msgstr "Operación no permitida"
msgid "Operation timed out. Check pod logs for %{pod_name} for more details."
msgstr "Se ha agotado el tiempo de la operación. Por favor, compruebe los registros del pod para %{pod_name} para obtener más información."
@@ -15357,10 +15860,10 @@ msgid "OutdatedBrowser|You can provide feedback %{feedback_link_start}on this is
msgstr ""
msgid "Outdent"
-msgstr ""
+msgstr "Deshacer indentación"
msgid "Overridden"
-msgstr ""
+msgstr "Reemplazado"
msgid "Overview"
msgstr "Resumen"
@@ -15378,13 +15881,13 @@ msgid "Owner"
msgstr "Propietario"
msgid "Package Files"
-msgstr ""
+msgstr "Archivos de paquete"
msgid "Package Registry"
-msgstr ""
+msgstr "Registro de paquetes"
msgid "Package already exists"
-msgstr ""
+msgstr "El paquete ya existe"
msgid "Package deleted successfully"
msgstr "Paquete eliminado correctamente"
@@ -15393,43 +15896,43 @@ msgid "Package information"
msgstr "Información del paquete"
msgid "Package recipe already exists"
-msgstr ""
+msgstr "La receta del paquete ya existe"
msgid "Package type must be Conan"
-msgstr ""
+msgstr "El tipo de paquete debe ser Conan"
msgid "Package type must be Maven"
-msgstr ""
+msgstr "El tipo de paquete debe ser Maven"
msgid "Package type must be NuGet"
-msgstr ""
+msgstr "El tipo de paquete debe ser NuGet"
msgid "Package type must be PyPi"
-msgstr ""
+msgstr "El tipo de paquete debe ser PyPi"
msgid "Package was removed"
msgstr "El paquete ha sido eliminado"
msgid "PackageRegistry|Add Conan Remote"
-msgstr ""
+msgstr "Añadir un remoto de Conan"
msgid "PackageRegistry|Add NuGet Source"
-msgstr ""
+msgstr "Añadir fuente de NuGet"
msgid "PackageRegistry|Conan"
-msgstr ""
+msgstr "Conan"
msgid "PackageRegistry|Conan Command"
-msgstr ""
+msgstr "Comando de Conan"
msgid "PackageRegistry|Copy .pypirc content"
-msgstr ""
+msgstr "Copiar el contenido de .pypirc"
msgid "PackageRegistry|Copy Conan Command"
msgstr "Copiar comando de Conan"
msgid "PackageRegistry|Copy Conan Setup Command"
-msgstr ""
+msgstr "Copiar el comando de configuración de Conan"
msgid "PackageRegistry|Copy Maven XML"
msgstr "Copiar Maven XML"
@@ -15450,7 +15953,7 @@ msgid "PackageRegistry|Copy Pip command"
msgstr "Copiar comando de Pip"
msgid "PackageRegistry|Copy and paste this inside your %{codeStart}pom.xml%{codeEnd} %{codeStart}dependencies%{codeEnd} block."
-msgstr ""
+msgstr "Copie y pegue esto dentro del bloque %{codeStart}dependencies%{codeEnd} en el fichero %{codeStart}pom.xml%{codeEnd} ."
msgid "PackageRegistry|Copy npm command"
msgstr "Copiar comando npm"
@@ -15471,22 +15974,22 @@ msgid "PackageRegistry|Delete package"
msgstr "Eliminar paquete"
msgid "PackageRegistry|Filter by name"
-msgstr ""
+msgstr "Filtrar por nombre"
msgid "PackageRegistry|For more information on the Conan registry, %{linkStart}see the documentation%{linkEnd}."
-msgstr ""
+msgstr "Para más información sobre el registro de Conan, %{linkStart}vea la documentación%{linkEnd}."
msgid "PackageRegistry|For more information on the Maven registry, %{linkStart}see the documentation%{linkEnd}."
msgstr "Para más información sobre el registro de Maven, %{linkStart}vea la documentación%{linkEnd}."
msgid "PackageRegistry|For more information on the NuGet registry, %{linkStart}see the documentation%{linkEnd}."
-msgstr ""
+msgstr "Para obtener más información sobre el registro de NuGet , %{linkStart}vea la documentación%{linkEnd}."
msgid "PackageRegistry|For more information on the PyPi registry, %{linkStart}see the documentation%{linkEnd}."
-msgstr ""
+msgstr "Para obtener más información sobre el registro PyPiPi, %{linkStart}vea la documentación%{linkEnd}."
msgid "PackageRegistry|If you haven't already done so, you will need to add the below to your %{codeStart}.pypirc%{codeEnd} file."
-msgstr ""
+msgstr "Si todavía no lo ha hecho, necesitará añadir lo siguiente a su archivo %{codeStart}.pypirc%{codeEnd}."
msgid "PackageRegistry|If you haven't already done so, you will need to add the below to your %{codeStart}pom.xml%{codeEnd} file."
msgstr "Si aún no lo ha hecho, debe añadir lo siguiente a su archivo %{codeStart}pom.xml%{codeEnd}."
@@ -15516,7 +16019,7 @@ msgid "PackageRegistry|NPM"
msgstr "NPM"
msgid "PackageRegistry|No upcoming issues"
-msgstr ""
+msgstr "No hay próximas incidencias"
msgid "PackageRegistry|NuGet"
msgstr "NuGet"
@@ -15573,7 +16076,7 @@ msgid "PackageRegistry|Unable to load package"
msgstr "Se ha producido un error al cargar el paquete"
msgid "PackageRegistry|Upcoming package managers"
-msgstr ""
+msgstr "Próximos gestores de paquetes"
msgid "PackageRegistry|You are about to delete %{name}, this operation is irreversible, are you sure?"
msgstr ""
@@ -15612,7 +16115,7 @@ msgid "Packages"
msgstr "Paquetes"
msgid "Packages & Registries"
-msgstr ""
+msgstr "Paquetes y registros"
msgid "Page not found"
msgstr "Página no encontrada"
@@ -15668,6 +16171,9 @@ msgstr "La tarea épica principal no existe."
msgid "Parent epic is not present."
msgstr "La tarea épica principal no está presente."
+msgid "Parsing error for param :embed_json. %{message}"
+msgstr ""
+
msgid "Part of merge request changes"
msgstr "Parte de los cambios de los merge requests"
@@ -15680,6 +16186,9 @@ msgstr "Participantes"
msgid "Passed"
msgstr "Pasado"
+msgid "Passed on"
+msgstr ""
+
msgid "Password"
msgstr "Contraseña"
@@ -15723,7 +16232,7 @@ msgid "Paste your public SSH key, which is usually contained in the file '~/.ssh
msgstr "Pegue su clave pública SSH, que generalmente está contenida en el archivo ~/.ssh/id_ed25519.pub o en el archivo '~/ .ssh/id_rsa.pub' y comienza con 'ssh-rsa'. No utilice su clave SSH privada."
msgid "Patch to apply"
-msgstr ""
+msgstr "Parche a aplicar"
msgid "Path"
msgstr "Ruta"
@@ -15753,10 +16262,10 @@ msgid "People without permission will never get a notification."
msgstr "Las personas sin permisos nunca recibirán una notificación."
msgid "Percent rollout (logged in users)"
-msgstr ""
+msgstr "Porcentaje de despliegue (usuarios conectados)"
msgid "Percentage"
-msgstr ""
+msgstr "Porcentaje"
msgid "Perform advanced options such as changing path, transferring, exporting, or removing the group."
msgstr ""
@@ -15764,6 +16273,9 @@ msgstr ""
msgid "Perform common operations on GitLab project"
msgstr "Realice operaciones comunes en el proyecto GitLab"
+msgid "Performance and resource management"
+msgstr ""
+
msgid "Performance optimization"
msgstr "Optimización del rendimiento"
@@ -15774,7 +16286,7 @@ msgid "PerformanceBar|Download"
msgstr "Descargar"
msgid "PerformanceBar|Elasticsearch calls"
-msgstr ""
+msgstr "Llamadas de Elasticsearch"
msgid "PerformanceBar|Frontend resources"
msgstr "Recursos de Frontend"
@@ -15971,8 +16483,8 @@ msgstr "Ejecutar Pipeline"
msgid "Pipelines|Something went wrong while cleaning runners cache."
msgstr "Se ha producido un error durante el proceso de limpieza de la memoria caché de los runners."
-msgid "Pipelines|There are currently no %{scope} pipelines."
-msgstr "Actualmente no hay %{scope} pipelines."
+msgid "Pipelines|There are currently no finished pipelines."
+msgstr ""
msgid "Pipelines|There are currently no pipelines."
msgstr "Actualmente no hay pipelines."
@@ -15993,7 +16505,7 @@ msgid "Pipeline|Branch name"
msgstr ""
msgid "Pipeline|Canceled"
-msgstr ""
+msgstr "Cancelado"
msgid "Pipeline|Commit"
msgstr "Commit"
@@ -16005,7 +16517,7 @@ msgid "Pipeline|Coverage"
msgstr "Cobertura"
msgid "Pipeline|Created"
-msgstr ""
+msgstr "Creado"
msgid "Pipeline|Date"
msgstr "Fecha"
@@ -16020,7 +16532,7 @@ msgid "Pipeline|Existing branch name or tag"
msgstr "Nombre de la rama o etiqueta existente"
msgid "Pipeline|Failed"
-msgstr ""
+msgstr "Fallido"
msgid "Pipeline|Key"
msgstr "Clave"
@@ -16082,6 +16594,9 @@ msgstr "Detener pipeline"
msgid "Pipeline|Stop pipeline #%{pipelineId}?"
msgstr "¿Desea detener la ejecución del pipeline #%{pipelineId}?"
+msgid "Pipeline|Tag name"
+msgstr ""
+
msgid "Pipeline|Trigger author"
msgstr ""
@@ -16122,10 +16637,10 @@ msgid "Plain diff"
msgstr "Dif simple"
msgid "Plan"
-msgstr ""
+msgstr "Plan"
msgid "Plan:"
-msgstr ""
+msgstr "Plan:"
msgid "PlantUML"
msgstr "PlantUML"
@@ -16163,12 +16678,18 @@ msgstr "Por favor, compruebe el archivo de configuración para asegurarse de que
msgid "Please check your email (%{email}) to verify that you own this address and unlock the power of CI/CD. Didn't receive it? %{resend_link}. Wrong email address? %{update_link}."
msgstr "Por favor revise su correo electrónico (%{email}) para verificar que es el propietario de esta dirección y desbloquear la potencia de CI/CD. ¿No lo ha recibido? %{resend_link}. ¿Ha utilizado una dirección de correo electrónico incorrecta? %{update_link}."
+msgid "Please choose a file"
+msgstr ""
+
msgid "Please choose a group URL with no special characters."
msgstr "Por favor, elija una URL de grupo sin caracteres especiales."
msgid "Please complete your profile with email address"
msgstr "Por favor, complete su perfil con una dirección de correo electrónico"
+msgid "Please convert %{linkStart}them to Git%{linkEnd}, and go through the %{linkToImportFlow} again."
+msgstr ""
+
msgid "Please convert them to %{link_to_git}, and go through the %{link_to_import_flow} again."
msgstr "Por favor, conviértalo a %{link_to_git}, y vaya de nuevo a través de %{link_to_import_flow}."
@@ -16226,6 +16747,9 @@ msgstr ""
msgid "Please provide a valid email address."
msgstr "Por favor proporcione una dirección de correo electrónico válida."
+msgid "Please provide attributes to update"
+msgstr ""
+
msgid "Please refer to <a href=\"%{docs_url}\">%{docs_url}</a>"
msgstr "Por favor, consulte <a href=\"%{docs_url}\">%{docs_url}</a>"
@@ -16392,7 +16916,7 @@ msgid "Press %{key}-C to copy"
msgstr "Presione %{key}-C para copiar"
msgid "Prev"
-msgstr ""
+msgstr "Previo"
msgid "Prevent adding new members to project membership within this group"
msgstr "Impedir que se añadan nuevos miembros al proyecto dentro de este grupo"
@@ -16458,7 +16982,7 @@ msgid "Private"
msgstr "Privado"
msgid "Private - Project access must be granted explicitly to each user. If this project is part of a group, access will be granted to members of the group."
-msgstr ""
+msgstr "Privado - El acceso al proyecto debe concederse explícitamente a cada usuario. Si este proyecto es parte de un grupo, el acceso se concederá a los miembros del grupo."
msgid "Private - The group and its projects can only be viewed by members."
msgstr "Privado - El grupo y sus proyectos sólo pueden ser vistos por sus miembros."
@@ -16650,7 +17174,7 @@ msgid "Profiles|Full name"
msgstr "Nombre completo"
msgid "Profiles|Give your individual key a title"
-msgstr ""
+msgstr "Dale un título a su clave individual"
msgid "Profiles|Include private contributions on my profile"
msgstr "Incluir las contribuciones privadas en mi perfil"
@@ -16671,7 +17195,7 @@ msgid "Profiles|Key"
msgstr "Clave"
msgid "Profiles|Last used:"
-msgstr ""
+msgstr "Último uso:"
msgid "Profiles|Learn more"
msgstr "Más información"
@@ -16961,11 +17485,14 @@ msgstr ""
msgid "Project has too many %{label_for_message} to search"
msgstr "El proyecto tiene demasiadas %{label_for_message} para buscar"
+msgid "Project is required when cluster_type is :project"
+msgstr ""
+
msgid "Project members"
msgstr "Miembros del proyecto"
msgid "Project milestone"
-msgstr ""
+msgstr "Hito del proyecto"
msgid "Project name"
msgstr "Nombre del proyecto"
@@ -16986,7 +17513,7 @@ msgid "Project path"
msgstr "Ruta del proyecto"
msgid "Project scanning help page"
-msgstr ""
+msgstr "Página de ayuda para el escaneo de proyectos"
msgid "Project security status"
msgstr "Estado de la seguridad del proyecto"
@@ -17078,8 +17605,41 @@ msgstr "%{service_title}: estado activado"
msgid "ProjectService|Comment"
msgstr "Comentar"
-msgid "ProjectService|Comment will be posted on each event"
-msgstr "El comentario se publicará en cada evento"
+msgid "ProjectService|Event will be triggered by a push to the repository"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a commit is created/updated"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a confidential issue is created/updated/closed"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a deployment finishes"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a merge request is created/updated/merged"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a new tag is pushed to the repository"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a new, unique alert is recorded"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a pipeline status changes"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a wiki page is created/updated"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when an issue is created/updated/closed"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when someone adds a comment"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when someone adds a comment on a confidential issue"
+msgstr ""
msgid "ProjectService|Perform common operations on GitLab project: %{project_name}"
msgstr "Realice operaciones comunes en el proyecto GitLab: %{project_name}"
@@ -17129,6 +17689,9 @@ msgstr "Desactivar notificaciones por correo electrónico"
msgid "ProjectSettings|Enable 'Delete source branch' option by default"
msgstr "Habilitar la opción 'Eliminar rama de origen' por defecto"
+msgid "ProjectSettings|Enable merge trains and pipelines for merged results"
+msgstr ""
+
msgid "ProjectSettings|Every merge creates a merge commit"
msgstr "Todo merge crea un merge commit"
@@ -17159,7 +17722,7 @@ msgstr "Solo fast-forward merges"
msgid "ProjectSettings|Forks"
msgstr ""
-msgid "ProjectSettings|Git Large File Storage"
+msgid "ProjectSettings|Git Large File Storage (LFS)"
msgstr ""
msgid "ProjectSettings|Internal"
@@ -17195,9 +17758,6 @@ msgstr "Método de Merge"
msgid "ProjectSettings|Merge options"
msgstr "Opciones de Merge"
-msgid "ProjectSettings|Merge pipelines will try to validate the post-merge result prior to merging"
-msgstr ""
-
msgid "ProjectSettings|Merge requests"
msgstr ""
@@ -17225,6 +17785,9 @@ msgstr "Pages para la documentación del proyecto"
msgid "ProjectSettings|Pipelines"
msgstr "Pipelines"
+msgid "ProjectSettings|Pipelines for merge requests must be enabled in the CI/CD configuration file, or pipelines could be unresolvable or dropped"
+msgstr ""
+
msgid "ProjectSettings|Pipelines must succeed"
msgstr "La ejecución debe finalizar correctamente"
@@ -17252,6 +17815,9 @@ msgstr ""
msgid "ProjectSettings|Show link to create/view merge request when pushing from the command line"
msgstr "Mostrar el enlace para crear/ver los merge requests cuando se haga push desde la línea de comandos"
+msgid "ProjectSettings|Skipped pipelines are considered successful"
+msgstr ""
+
msgid "ProjectSettings|Snippets"
msgstr "Fragmentos de código"
@@ -17262,11 +17828,14 @@ msgid "ProjectSettings|The commit message used to apply merge request suggestion
msgstr ""
msgid "ProjectSettings|The variables GitLab supports:"
-msgstr ""
+msgstr "Las variables que soporta GitLab:"
msgid "ProjectSettings|These checks must pass before merge requests can be merged"
msgstr "Estas comprobaciones deben pasar antes de que se pueda ejecutar el merge request"
+msgid "ProjectSettings|This introduces the risk of merging changes that will not pass the pipeline."
+msgstr ""
+
msgid "ProjectSettings|This setting is applied on the server level and can be overridden by an admin."
msgstr "Esta configuración se aplica a nivel del servidor y se puede sobreescribir por un administrador."
@@ -17340,7 +17909,7 @@ msgid "ProjectTemplates|NodeJS Express"
msgstr "NodeJS Express"
msgid "ProjectTemplates|Pages/Gatsby"
-msgstr ""
+msgstr "Pages/Gatsby"
msgid "ProjectTemplates|Pages/GitBook"
msgstr "Pages/GitBook"
@@ -17444,15 +18013,27 @@ msgstr "En blanco"
msgid "ProjectsNew|Blank project"
msgstr "Proyecto en blanco"
+msgid "ProjectsNew|Connect your external repository to GitLab CI/CD."
+msgstr ""
+
msgid "ProjectsNew|Contact an administrator to enable options for importing your project."
msgstr "Contacte con un administrador para habilitar opciones importantes para su proyecto."
msgid "ProjectsNew|Create"
+msgstr "Crear"
+
+msgid "ProjectsNew|Create a blank project to house your files, plan your work, and collaborate on code, among other things."
msgstr ""
+msgid "ProjectsNew|Create blank project"
+msgstr "Crear un proyecto en blanco"
+
msgid "ProjectsNew|Create from template"
msgstr "Crear desde una plantilla"
+msgid "ProjectsNew|Create new project"
+msgstr ""
+
msgid "ProjectsNew|Creating project & repository."
msgstr "Creando proyecto y repositorio."
@@ -17477,6 +18058,9 @@ msgstr "Por favor, espere un momento, está página se refrescara automáticamen
msgid "ProjectsNew|Project description %{tag_start}(optional)%{tag_end}"
msgstr "Descripción del proyecto %{tag_start}(opcional)%{tag_end}"
+msgid "ProjectsNew|Run CI/CD for external repository"
+msgstr ""
+
msgid "ProjectsNew|Template"
msgstr "Plantilla"
@@ -17652,10 +18236,10 @@ msgid "Promotions|Burndown Charts are visual representations of the progress of
msgstr ""
msgid "Promotions|Buy EE"
-msgstr ""
+msgstr "Comprar EE"
msgid "Promotions|Buy GitLab Enterprise Edition"
-msgstr ""
+msgstr "Comprar GitLab Enterprise Edition"
msgid "Promotions|Contact an owner of group %{namespace_name} to upgrade the plan."
msgstr ""
@@ -17700,13 +18284,13 @@ msgid "Promotions|Track activity with Contribution Analytics."
msgstr ""
msgid "Promotions|Try it for free"
-msgstr ""
+msgstr "Pruébelo gratis"
msgid "Promotions|Upgrade plan"
msgstr "Plan de actualización"
msgid "Promotions|Upgrade your plan"
-msgstr ""
+msgstr "Actualice su plan"
msgid "Promotions|Upgrade your plan to activate Contribution Analytics."
msgstr ""
@@ -17738,6 +18322,9 @@ msgstr "Protegido"
msgid "Protected Branch"
msgstr "Rama protegida"
+msgid "Protected Branches"
+msgstr ""
+
msgid "Protected Environment"
msgstr "Entorno protegido"
@@ -17750,6 +18337,9 @@ msgstr "Rutas protegidas"
msgid "Protected Tag"
msgstr "Etiqueta protegida"
+msgid "Protected Tags"
+msgstr ""
+
msgid "Protected branches"
msgstr "Ramas protegidas"
@@ -17849,6 +18439,9 @@ msgstr "Protocolo"
msgid "Provider"
msgstr "Proveedor"
+msgid "Proxy support for this API is not available currently"
+msgstr ""
+
msgid "Pseudonymizer data collection"
msgstr "Pseudonymizer data collection"
@@ -17873,6 +18466,9 @@ msgstr ""
msgid "Publish to status page"
msgstr ""
+msgid "Published on status page"
+msgstr ""
+
msgid "Publishes this issue to the associated status page."
msgstr ""
@@ -18047,6 +18643,9 @@ msgstr "Búsquedas recientes"
msgid "Recipe"
msgstr ""
+msgid "Reconfigure"
+msgstr ""
+
msgid "Recover hidden stage"
msgstr ""
@@ -18125,16 +18724,19 @@ msgid "Registration|Checkout"
msgstr ""
msgid "Registration|Your GitLab group"
-msgstr ""
+msgstr "Su grupo de GitLab"
msgid "Registration|Your first project"
-msgstr ""
+msgstr "Su primer proyecto"
msgid "Registration|Your profile"
+msgstr "Su perfil"
+
+msgid "Regulate approvals by authors/committers, based on compliance frameworks. Can be changed only at the instance level."
msgstr ""
msgid "Rejected (closed)"
-msgstr ""
+msgstr "Rechazado (cerrado)"
msgid "Related Deployed Jobs"
msgstr "Trabajos Desplegados Relacionados"
@@ -18183,15 +18785,24 @@ msgstr "Título de la versión"
msgid "ReleaseAssetLinkType|Image"
msgstr ""
+msgid "ReleaseAssetLinkType|Images"
+msgstr ""
+
msgid "ReleaseAssetLinkType|Other"
msgstr ""
msgid "ReleaseAssetLinkType|Package"
msgstr ""
+msgid "ReleaseAssetLinkType|Packages"
+msgstr ""
+
msgid "ReleaseAssetLinkType|Runbook"
msgstr ""
+msgid "ReleaseAssetLinkType|Runbooks"
+msgstr ""
+
msgid "Releases"
msgstr "Versiones"
@@ -18202,6 +18813,9 @@ msgid "Releases are based on Git tags. We recommend tags that use semantic versi
msgstr ""
msgid "Releases documentation"
+msgstr "Publicar documentación"
+
+msgid "Releases|New Release"
msgstr ""
msgid "Release|Something went wrong while getting the release details"
@@ -18211,7 +18825,7 @@ msgid "Release|Something went wrong while saving the release details"
msgstr "Se ha producido un error al guardar los detalles de la versión"
msgid "Remediated: needs review"
-msgstr ""
+msgstr "Solucionado: necesita revisión"
msgid "Remediations"
msgstr ""
@@ -18276,6 +18890,9 @@ msgstr "Eliminar la fecha de vencimiento"
msgid "Remove fork relationship"
msgstr "Eliminar la relación del fork"
+msgid "Remove from batch"
+msgstr ""
+
msgid "Remove from board"
msgstr "Eliminar del tablero"
@@ -18286,10 +18903,10 @@ msgid "Remove group"
msgstr "Eliminar grupo"
msgid "Remove iteration"
-msgstr ""
+msgstr "Eliminar la iteración"
msgid "Remove license"
-msgstr ""
+msgstr "Eliminar la licencia"
msgid "Remove limit"
msgstr "Eliminar límite"
@@ -18334,7 +18951,7 @@ msgid "Removed %{epic_ref} from child epics."
msgstr "Se eliminó %{epic_ref} de las tareas épicas hijas."
msgid "Removed %{iteration_reference} iteration."
-msgstr ""
+msgstr "Se eliminó la iteración %{iteration_reference}."
msgid "Removed %{label_references} %{label_text}."
msgstr "Eliminada %{label_references} %{label_text}."
@@ -18376,7 +18993,7 @@ msgid "Removes %{epic_ref} from child epics."
msgstr "Eliminar %{epic_ref} de las tareas épicas hijas."
msgid "Removes %{iteration_reference} iteration."
-msgstr ""
+msgstr "Elimina la iteración %{iteration_reference}."
msgid "Removes %{label_references} %{label_text}."
msgstr "Eliminar %{label_references} %{label_text}."
@@ -18427,10 +19044,10 @@ msgid "Rename/Move"
msgstr "Renombrar/Mover"
msgid "Reopen"
-msgstr ""
+msgstr "Volver a abrir"
msgid "Reopen %{display_issuable_type}"
-msgstr ""
+msgstr "Volver a abrir %{display_issuable_type}"
msgid "Reopen epic"
msgstr "Reabrir la tarea épica"
@@ -18463,6 +19080,12 @@ msgid "Replaces the clone URL root."
msgstr "Reemplaza la raíz de la URL de clonado."
msgid "Replication"
+msgstr "Replicación"
+
+msgid "Replication enabled"
+msgstr ""
+
+msgid "Replication paused"
msgstr ""
msgid "Reply by email"
@@ -18492,15 +19115,15 @@ msgstr "Informar de un abuso al administrador"
msgid "Reported %{timeAgo} by %{reportedBy}"
msgstr "Denunciado %{timeAgo} por %{reportedBy}"
-msgid "Reported Resource Changes: %{addNum} to add, %{changeNum} to change, %{deleteNum} to delete"
-msgstr ""
-
msgid "Reporter"
-msgstr ""
+msgstr "Reportador"
msgid "Reporting"
msgstr "Informes"
+msgid "Reports"
+msgstr ""
+
msgid "Reports|%{combinedString} and %{resolvedString}"
msgstr ""
@@ -18539,6 +19162,9 @@ msgstr "Tiempo de ejecución"
msgid "Reports|Failure"
msgstr "Fallo"
+msgid "Reports|Identifier"
+msgstr ""
+
msgid "Reports|Metrics reports are loading"
msgstr "Los informes de las métricas se están cargando"
@@ -18551,6 +19177,9 @@ msgstr "Los informes de las métricas no han cambiado"
msgid "Reports|Metrics reports failed loading results"
msgstr "Se ha producido un erro al cargar os resultados de los informes de métricas"
+msgid "Reports|Scanner"
+msgstr ""
+
msgid "Reports|Severity"
msgstr "Severidad"
@@ -18599,15 +19228,33 @@ msgstr "Limpieza del repositorio"
msgid "Repository cleanup has started. You will receive an email once the cleanup operation is complete."
msgstr "Ha comenzado la limpieza del repositorio. Recibirá un correo electrónico una vez que la operación de limpieza haya finalizado."
+msgid "Repository files count over the limit"
+msgstr ""
+
+msgid "Repository has an invalid default branch name."
+msgstr ""
+
+msgid "Repository has more than one branch."
+msgstr ""
+
msgid "Repository has no locks."
msgstr "El repositorio no tiene bloqueos."
+msgid "Repository has tags."
+msgstr ""
+
msgid "Repository maintenance"
msgstr "Mantenimiento del repositorio"
msgid "Repository mirroring"
msgstr ""
+msgid "Repository must contain at least 1 file."
+msgstr ""
+
+msgid "Repository size is above the limit."
+msgstr ""
+
msgid "Repository static objects"
msgstr "Repositorio de objetos estáticos"
@@ -18617,8 +19264,8 @@ msgstr "Almacenamiento del repositorio"
msgid "Repository sync capacity"
msgstr ""
-msgid "Repository: %{counter_repositories} / Wikis: %{counter_wikis} / Build Artifacts: %{counter_build_artifacts} / LFS: %{counter_lfs_objects}"
-msgstr "Repositorio: %{counter_repositories} / wikis:%{counter_wikis} / Artefactos construidos: %{counter_build_artifacts} / LFS: %{counter_lfs_objects}"
+msgid "Repository: %{counter_repositories} / Wikis: %{counter_wikis} / Build Artifacts: %{counter_build_artifacts} / LFS: %{counter_lfs_objects} / Snippets: %{counter_snippets}"
+msgstr ""
msgid "RepositorySettingsAccessLevel|Select"
msgstr "Seleccionar"
@@ -18629,6 +19276,9 @@ msgstr "Solicitar acceso"
msgid "Request Headers"
msgstr ""
+msgid "Request details"
+msgstr ""
+
msgid "Request parameter %{param} is missing."
msgstr ""
@@ -18735,6 +19385,9 @@ msgstr "Reinicializar el token de registro del runner"
msgid "Reset template"
msgstr "Restablecer plantilla"
+msgid "Reset to project defaults"
+msgstr ""
+
msgid "Resetting the authorization key for this project will require updating the authorization key in every alert source it is enabled in."
msgstr ""
@@ -18763,7 +19416,7 @@ msgid "Resolved all discussions."
msgstr "Resolver todas las discusiones."
msgid "Resolved by"
-msgstr ""
+msgstr "Resuelto por"
msgid "Resolved by %{name}"
msgstr "Resuelto por %{name}"
@@ -18831,9 +19484,6 @@ msgstr ""
msgid "Resume"
msgstr "Reanudar"
-msgid "Resume replication"
-msgstr "Reanudar la replicación"
-
msgid "Resync"
msgstr "Resincronizar"
@@ -19128,7 +19778,7 @@ msgid "Scoped issue boards"
msgstr "Tableros de incidencias con alcance limitado"
msgid "Scopes"
-msgstr ""
+msgstr "Alcances"
msgid "Scopes can't be blank"
msgstr ""
@@ -19161,13 +19811,13 @@ msgid "Search Button"
msgstr "Botón de búsqueda"
msgid "Search Milestones"
-msgstr ""
+msgstr "Buscar hitos"
msgid "Search an environment spec"
msgstr "Buscar un entorno específico"
msgid "Search authors"
-msgstr ""
+msgstr "Buscar autores"
msgid "Search branches"
msgstr "Buscar ramas"
@@ -19227,7 +19877,7 @@ msgid "Search projects..."
msgstr "Buscar proyectos..."
msgid "Search requirements"
-msgstr ""
+msgstr "Requisitos de búsqueda"
msgid "Search users"
msgstr "Buscar usuarios"
@@ -19352,13 +20002,13 @@ msgid "Seats in license"
msgstr "Puestos en la licencia"
msgid "Secondary"
-msgstr ""
+msgstr "Secundario"
msgid "Secret"
msgstr "Secreto"
msgid "Secret Detection"
-msgstr ""
+msgstr "Detección de secretos"
msgid "Security"
msgstr "Seguridad"
@@ -19382,22 +20032,22 @@ msgid "Security report is out of date. Run %{newPipelineLinkStart}a new pipeline
msgstr ""
msgid "SecurityConfiguration|Enabled"
-msgstr ""
+msgstr "Habilitado"
msgid "SecurityConfiguration|Feature documentation for %{featureName}"
-msgstr ""
+msgstr "Documentación de características para %{featureName}"
msgid "SecurityConfiguration|Not yet enabled"
-msgstr ""
+msgstr "Todavía no está habilitado"
msgid "SecurityConfiguration|Security Control"
-msgstr ""
+msgstr "Control de seguridad"
msgid "SecurityConfiguration|Status"
msgstr "Estado"
msgid "SecurityConfiguration|Testing & Compliance"
-msgstr ""
+msgstr "Pruebas y cumplimiento"
msgid "SecurityReports|%{firstProject} and %{secondProject}"
msgstr "%{firstProject} y %{secondProject}"
@@ -19427,13 +20077,13 @@ msgid "SecurityReports|Create issue"
msgstr "Crear incidencia"
msgid "SecurityReports|Dismiss Selected"
-msgstr ""
+msgstr "Descartar seleccionado"
msgid "SecurityReports|Dismiss vulnerability"
-msgstr ""
+msgstr "Descartar vulnerabilidad"
msgid "SecurityReports|Dismissed '%{vulnerabilityName}'"
-msgstr ""
+msgstr "Descartada '%{vulnerabilityName}'"
msgid "SecurityReports|Dismissed '%{vulnerabilityName}'. Turn off the hide dismissed toggle to view."
msgstr ""
@@ -19442,7 +20092,7 @@ msgid "SecurityReports|Each vulnerability now has a unique page that can be dire
msgstr ""
msgid "SecurityReports|Edit dashboard"
-msgstr ""
+msgstr "Editar panel de control"
msgid "SecurityReports|Either you don't have permission to view this dashboard or the dashboard has not been setup. Please check your permission settings with your administrator or check your dashboard configurations to proceed."
msgstr ""
@@ -19454,49 +20104,52 @@ msgid "SecurityReports|Error fetching the vulnerability list. Please check your
msgstr ""
msgid "SecurityReports|False positive"
+msgstr "False positivo"
+
+msgid "SecurityReports|Group Security Dashboard"
msgstr ""
msgid "SecurityReports|Hide dismissed"
-msgstr ""
+msgstr "Ocultar descartados"
msgid "SecurityReports|Introducing standalone vulnerabilities"
msgstr ""
msgid "SecurityReports|Issue Created"
-msgstr ""
+msgstr "Incidencia creada"
msgid "SecurityReports|Learn More"
-msgstr ""
+msgstr "Más información"
msgid "SecurityReports|Learn more about setting up your dashboard"
msgstr ""
msgid "SecurityReports|Load more vulnerabilities"
-msgstr ""
+msgstr "Cargar más vulnerabilidades"
msgid "SecurityReports|Monitor vulnerabilities in your code"
-msgstr ""
+msgstr "Monitorizar vulnerabilidades en su código"
msgid "SecurityReports|More info"
-msgstr ""
+msgstr "Más información"
msgid "SecurityReports|More information"
-msgstr ""
+msgstr "Más información"
msgid "SecurityReports|No vulnerabilities found for dashboard"
-msgstr ""
+msgstr "No se encontraron vulnerabilidades para el panel de control"
msgid "SecurityReports|No vulnerabilities found for this group"
-msgstr ""
+msgstr "No se encontraron vulnerabilidades para este grupo"
msgid "SecurityReports|No vulnerabilities found for this pipeline"
-msgstr ""
+msgstr "No se encontraron vulnerabilidades para este pipeline"
msgid "SecurityReports|No vulnerabilities found for this project"
-msgstr ""
+msgstr "No se encontraron vulnerabilidades para este proyecto"
msgid "SecurityReports|Oops, something doesn't seem right."
-msgstr ""
+msgstr "Oops, algo parece que no está correcto."
msgid "SecurityReports|Pipeline %{pipelineLink} triggered %{timeago} by %{user}"
msgstr ""
@@ -19510,10 +20163,13 @@ msgstr "Proyectos añadidos"
msgid "SecurityReports|Remove project from dashboard"
msgstr "Eliminar proyecto del panel de control"
-msgid "SecurityReports|Report type"
-msgstr "Tipo de informe"
-
msgid "SecurityReports|Return to dashboard"
+msgstr "Volver al panel de control"
+
+msgid "SecurityReports|Scan details"
+msgstr ""
+
+msgid "SecurityReports|Scanner type"
msgstr ""
msgid "SecurityReports|Security Dashboard"
@@ -19544,19 +20200,19 @@ msgid "SecurityReports|The security dashboard displays the latest security repor
msgstr ""
msgid "SecurityReports|There was an error adding the comment."
-msgstr ""
+msgstr "Se ha producido un error al añadir el comentario."
msgid "SecurityReports|There was an error creating the issue."
-msgstr ""
+msgstr "Se ha producido un error al crear la incidencia."
msgid "SecurityReports|There was an error creating the merge request."
msgstr ""
msgid "SecurityReports|There was an error deleting the comment."
-msgstr ""
+msgstr "Se ha producido un error al eliminar el comentario."
msgid "SecurityReports|There was an error dismissing the vulnerabilities."
-msgstr ""
+msgstr "Se ha producido un error al descartar las vulnerabilidades."
msgid "SecurityReports|There was an error dismissing the vulnerability."
msgstr ""
@@ -19570,9 +20226,12 @@ msgstr ""
msgid "SecurityReports|There was an error while generating the report."
msgstr ""
-msgid "SecurityReports|Unable to add %{invalidProjects}"
+msgid "SecurityReports|Unable to add %{invalidProjectsMessage}"
msgstr ""
+msgid "SecurityReports|Unable to add %{invalidProjects}"
+msgstr "No se puede añadir %{invalidProjects}"
+
msgid "SecurityReports|Undo dismiss"
msgstr ""
@@ -19684,6 +20343,9 @@ msgstr "Selecciona rama/etiqueta"
msgid "Select due date"
msgstr ""
+msgid "Select file"
+msgstr ""
+
msgid "Select group or project"
msgstr "Seleccione un grupo o proyecto"
@@ -19697,7 +20359,7 @@ msgid "Select labels"
msgstr "Seleccione las etiquetas"
msgid "Select merge moment"
-msgstr ""
+msgstr "Seleccionar el momento de fusión"
msgid "Select milestone"
msgstr "Seleccione los hitos de proyecto"
@@ -19733,13 +20395,13 @@ msgid "Select start date"
msgstr ""
msgid "Select status"
-msgstr ""
+msgstr "Seleccionar estado"
msgid "Select strategy activation method"
msgstr ""
msgid "Select subscription"
-msgstr ""
+msgstr "Seleccionar la suscripción"
msgid "Select target branch"
msgstr "Selecciona una rama de destino"
@@ -19747,11 +20409,8 @@ msgstr "Selecciona una rama de destino"
msgid "Select the branch you want to set as the default for this project. All merge requests and commits will automatically be made against this branch unless you specify a different one."
msgstr "Seleccione la rama que desea establecer como predeterminada para este proyecto. Todas los merge request y los commit se realizarán automáticamente contra ese branch a menos que especifique uno diferente."
-msgid "Select the configured storage available for new repositories to be placed on."
-msgstr ""
-
msgid "Select the custom project template source group."
-msgstr ""
+msgstr "Seleccione el grupo de origen de plantilla de proyecto personalizado."
msgid "Select timeframe"
msgstr "Seleccione el período de tiempo"
@@ -19814,10 +20473,10 @@ msgid "Send email"
msgstr "Enviar correo electrónico"
msgid "Send email notification"
-msgstr ""
+msgstr "Enviar notificación por correo electrónico"
msgid "Send message"
-msgstr ""
+msgstr "Enviar mensaje"
msgid "Send report"
msgstr "Enviar informe"
@@ -19844,7 +20503,7 @@ msgid "SeriesFinalConjunction|and"
msgstr "y"
msgid "Serve repository static objects (e.g. archives, blobs, ...) from an external storage (e.g. a CDN)."
-msgstr ""
+msgstr "Servir objetos estáticos del repositorio (p.e. archivos, blobs, ...) desde un almacenamiento externo (p.e. un CDN)."
msgid "Server supports batch API only, please update your Git LFS client to version 1.0.1 and up."
msgstr "El servidor solo admite API por lotes, actualice su cliente Git LFS a la versión 1.0.1 o superior."
@@ -19942,12 +20601,6 @@ msgstr "Service Desk"
msgid "Service Desk is enabled but not yet active"
msgstr ""
-msgid "Service Desk is off"
-msgstr "Service Desk está desactivado"
-
-msgid "Service Desk is on"
-msgstr "Service Desk esta activado"
-
msgid "Service Templates"
msgstr "Plantillas de Servicio"
@@ -19985,7 +20638,7 @@ msgid "Set instance-wide template repository"
msgstr ""
msgid "Set iteration"
-msgstr ""
+msgstr "Establecer iteración"
msgid "Set max session time for web terminal."
msgstr "Establecer el tiempo máximo de sesión para el terminal de web."
@@ -20024,7 +20677,7 @@ msgid "Set the duration for which the jobs will be considered as old and expired
msgstr "Establezca la duración durante la cual los trabajos se considerarán antiguos y vencidos. Una vez pasado ese tiempo, los trabajos se archivarán y ya no se podrá volver a reintentar su ejecución. Establezca este campo como nulo para que los trabajos no caduquen. Este valor, no debe ser inferior a 1 día, por ejemplo: <code>15 días</code>, <code>1 mes</code>, <code>2 años</code>."
msgid "Set the iteration to %{iteration_reference}."
-msgstr ""
+msgstr "Establece la iteración a %{iteration_reference}."
msgid "Set the maximum file size for each job's artifacts"
msgstr "Establecer el tamaño máximo de archivo para los artefactos de cada trabajo"
@@ -20035,6 +20688,15 @@ msgstr "Establece el número máximo de minutos de pipeline que un grupo puede u
msgid "Set the milestone to %{milestone_reference}."
msgstr "Establecer el hito a %{milestone_reference}."
+msgid "Set the number of concurrent requests this secondary node will make to the primary node while backfilling."
+msgstr ""
+
+msgid "Set the synchronization and verification capacity for the secondary node."
+msgstr ""
+
+msgid "Set the timeout in seconds to send a secondary node status to the primary and IPs allowed for the secondary nodes."
+msgstr ""
+
msgid "Set time estimate"
msgstr "Establecer el tiempo estimado"
@@ -20045,7 +20707,7 @@ msgid "Set up CI/CD"
msgstr "Configurar CI/CD"
msgid "Set up Jira Integration"
-msgstr ""
+msgstr "Configurar la integración con Jira"
msgid "Set up a %{type} Runner automatically"
msgstr "Configurar un %{type} ejecutor automáticamente"
@@ -20074,6 +20736,9 @@ msgstr "Establecer el peso"
msgid "Set weight to %{weight}."
msgstr "Establecer el peso a %{weight}."
+msgid "Set what should be replicated by choosing specific projects or groups by the secondary node."
+msgstr ""
+
msgid "SetPasswordToCloneLink|set a password"
msgstr "establecer una contraseña"
@@ -20111,7 +20776,7 @@ msgid "Sets the due date to %{due_date}."
msgstr "Establecer la fecha de vencimiento a %{due_date}."
msgid "Sets the iteration to %{iteration_reference}."
-msgstr ""
+msgstr "Establece la iteración a %{iteration_reference}."
msgid "Sets the milestone to %{milestone_reference}."
msgstr "Establece el hito en %{milestone_reference}."
@@ -20200,6 +20865,9 @@ msgstr "Mostrar la descripción del commit"
msgid "Show complete raw log"
msgstr "Mostrar el registro completo sin procesar"
+msgid "Show details"
+msgstr ""
+
msgid "Show file browser"
msgstr "Mostrar el explorador de archivos"
@@ -20209,14 +20877,17 @@ msgstr ""
msgid "Show latest version"
msgstr "Mostrar la última versión"
+msgid "Show list"
+msgstr ""
+
msgid "Show me everything"
msgstr ""
-msgid "Show me how"
+msgid "Show me how to add a pipeline"
msgstr ""
msgid "Show me more advanced stuff"
-msgstr ""
+msgstr "Muéstrame cosas más avanzadas"
msgid "Show only direct members"
msgstr "Mostrar solo los miembros directos"
@@ -20261,9 +20932,6 @@ msgstr "Mostrando la versión #%{versionNumber}"
msgid "Showing all issues"
msgstr "Mostrar todas las incidencias"
-msgid "Showing all labels"
-msgstr "Mostrando todas las etiquetas"
-
msgid "Showing last %{size} of log -"
msgstr "Mostrando los últimos %{size} del registro -"
@@ -20375,9 +21043,6 @@ msgstr "Ajustes de tamaño para sitios los web estáticos"
msgid "Skip outdated deployment jobs"
msgstr ""
-msgid "Skip this for now"
-msgstr "Omitir esto por ahora"
-
msgid "Skipped"
msgstr "Omitido"
@@ -20472,7 +21137,7 @@ msgid "Snowplow"
msgstr "Snowplow"
msgid "Solution"
-msgstr ""
+msgstr "Solución"
msgid "Some child epics may be hidden due to applied filters"
msgstr ""
@@ -20522,6 +21187,9 @@ msgstr "Algo salió mal al cambiar el estado del botón. ¡Por favor, inténtelo
msgid "Something went wrong while adding your award. Please try again."
msgstr "Se ha producido un error al agregar su premio. Por favor, inténtalo de nuevo."
+msgid "Something went wrong while applying the batch of suggestions. Please try again."
+msgstr ""
+
msgid "Something went wrong while applying the suggestion. Please try again."
msgstr "Se ha producido un error al aplicar la sugerencia. Por favor, inténtelo de nuevo."
@@ -20624,10 +21292,13 @@ msgstr ""
msgid "Something went wrong while updating a requirement."
msgstr ""
+msgid "Something went wrong while updating assignees"
+msgstr ""
+
msgid "Something went wrong while updating your list settings"
msgstr ""
-msgid "Something went wrong with your automatic subscription renewal"
+msgid "Something went wrong with your automatic subscription renewal."
msgstr ""
msgid "Something went wrong, unable to add %{project} to dashboard"
@@ -20645,6 +21316,9 @@ msgstr "Se ha producido un error al eliminar el proyecto"
msgid "Something went wrong, unable to search projects"
msgstr "Se ha producido un error al buscar los proyectos"
+msgid "Something went wrong."
+msgstr ""
+
msgid "Something went wrong. Please try again."
msgstr "Algo salió mal. ¡Por favor, inténtelo de nuevo!"
@@ -20670,10 +21344,10 @@ msgid "Sort direction"
msgstr "Dirección de clasificación"
msgid "Sort direction: Ascending"
-msgstr ""
+msgstr "Dirección de ordenación: Ascendente"
msgid "Sort direction: Descending"
-msgstr ""
+msgstr "Dirección de ordenación: Descendente"
msgid "SortOptions|Access level, ascending"
msgstr "Nivel de acceso, ascendente"
@@ -20793,7 +21467,7 @@ msgid "SortOptions|Size"
msgstr "Tamaño"
msgid "SortOptions|Sort by:"
-msgstr ""
+msgstr "Ordenar por:"
msgid "SortOptions|Sort direction"
msgstr "Dirección de clasificación"
@@ -20828,6 +21502,9 @@ msgstr "Origen (rama o tag)"
msgid "Source code"
msgstr "Código fuente"
+msgid "Source code (%{fileExtension})"
+msgstr ""
+
msgid "Source is not available"
msgstr "El origen no esta disponible"
@@ -20912,6 +21589,9 @@ msgstr "Squash commits"
msgid "Stack trace"
msgstr ""
+msgid "Stacktrace snippet"
+msgstr ""
+
msgid "Stage"
msgstr "Etapa"
@@ -21017,6 +21697,9 @@ msgstr "Iniciar el hilo y cerrar %{noteable_name}"
msgid "Start thread & reopen %{noteable_name}"
msgstr "Iniciar el hilo y volver a abrir %{noteable_name}"
+msgid "Start using Directed Acyclic Graphs (DAG)"
+msgstr ""
+
msgid "Start your Free Gold Trial"
msgstr "Comience su prueba Gold gratuita"
@@ -21036,7 +21719,7 @@ msgid "Started asynchronous removal of all repository check states."
msgstr "Se inició la eliminación asíncrona de todos los estados de verificación del repositorio."
msgid "Started:"
-msgstr ""
+msgstr "Iniciado:"
msgid "Starting..."
msgstr "Iniciando..."
@@ -21069,16 +21752,16 @@ msgid "StaticSiteEditor|Incompatible file content"
msgstr ""
msgid "StaticSiteEditor|Return to site"
-msgstr ""
+msgstr "Volver al sitio web"
msgid "StaticSiteEditor|Static site editor"
-msgstr ""
+msgstr "Editor estático del sitio web"
msgid "StaticSiteEditor|Success!"
-msgstr ""
+msgstr "¡Éxito!"
msgid "StaticSiteEditor|Summary of changes"
-msgstr ""
+msgstr "Resumen de cambios"
msgid "StaticSiteEditor|The Static Site Editor is currently configured to only edit Markdown content on pages generated from Middleman. Visit the documentation to learn more about configuring your site to use the Static Site Editor."
msgstr ""
@@ -21087,13 +21770,13 @@ msgid "StaticSiteEditor|Update %{sourcePath} file"
msgstr ""
msgid "StaticSiteEditor|View documentation"
-msgstr ""
+msgstr "Ver la documentación"
msgid "StaticSiteEditor|View merge request"
msgstr ""
msgid "StaticSiteEditor|You added a commit:"
-msgstr ""
+msgstr "Ha añadido un commit:"
msgid "StaticSiteEditor|You created a merge request:"
msgstr ""
@@ -21114,7 +21797,7 @@ msgid "Status:"
msgstr "Estado:"
msgid "Status: %{title}"
-msgstr ""
+msgstr "Estado: %{title}"
msgid "StatusPage|AWS Secret access key"
msgstr ""
@@ -21132,7 +21815,7 @@ msgid "StatusPage|Active"
msgstr "Activo"
msgid "StatusPage|Bucket %{docsLink}"
-msgstr ""
+msgstr "Bucket %{docsLink}"
msgid "StatusPage|Configure file storage settings to link issues in this project to an external status page."
msgstr ""
@@ -21195,7 +21878,7 @@ msgid "StorageSize|Unknown"
msgstr "Desconocido"
msgid "Subgroup milestone"
-msgstr ""
+msgstr "Hito del subgrupo"
msgid "Subgroup overview"
msgstr "Resumen del subgrupo"
@@ -21225,10 +21908,10 @@ msgid "Subkeys"
msgstr "Subclaves"
msgid "Submit %{humanized_resource_name}"
-msgstr ""
+msgstr "Enviar %{humanized_resource_name}"
msgid "Submit Changes"
-msgstr ""
+msgstr "Enviar los cambios"
msgid "Submit a review"
msgstr "Enviar una revisión"
@@ -21408,13 +22091,13 @@ msgid "Suggest code changes which can be immediately applied in one click. Try i
msgstr ""
msgid "Suggested Solutions"
-msgstr ""
+msgstr "Soluciones sugeridas"
msgid "Suggested change"
msgstr "Cambio sugerido"
msgid "Suggested solutions help link"
-msgstr ""
+msgstr "Enlace de ayuda de las soluciones sugeridas"
msgid "SuggestedColors|Bright green"
msgstr "Verde brillante"
@@ -21479,6 +22162,21 @@ msgstr "Verde lima muy oscuro"
msgid "SuggestedColors|Very pale orange"
msgstr "Naranja muy pálido"
+msgid "Suggestion is not applicable as the suggestion was not found."
+msgstr ""
+
+msgid "Suggestions are not applicable as one or more suggestions were not found."
+msgstr ""
+
+msgid "Suggestions are not applicable as their lines cannot overlap."
+msgstr ""
+
+msgid "Suggestions must all be on the same branch."
+msgstr ""
+
+msgid "Suggestions that change line count can't be added to batches, yet."
+msgstr ""
+
msgid "Suggestions:"
msgstr "Sugerencias:"
@@ -21521,9 +22219,6 @@ msgstr "Sincronizado"
msgid "Synchronization disabled"
msgstr "Sincronización deshabilitada"
-msgid "Synchronization of container repositories is disabled."
-msgstr "La sincronización de los repositorios de contenedores está deshabilitada."
-
msgid "System"
msgstr "Sistema"
@@ -21704,6 +22399,34 @@ msgstr "Términos del acuerdo de servicio y de la política de privacidad"
msgid "Terms of Service and Privacy Policy"
msgstr "Términos del servicio y política de privacidad"
+msgid "Terraform|%{number} Terraform report failed to generate"
+msgid_plural "Terraform|%{number} Terraform reports failed to generate"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Terraform|%{number} Terraform report was generated in your pipelines"
+msgid_plural "Terraform|%{number} Terraform reports were generated in your pipelines"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Terraform|A Terraform report failed to generate."
+msgstr ""
+
+msgid "Terraform|A Terraform report was generated in your pipelines."
+msgstr ""
+
+msgid "Terraform|Generating the report caused an error."
+msgstr ""
+
+msgid "Terraform|Reported Resource Changes: %{addNum} to add, %{changeNum} to change, %{deleteNum} to delete"
+msgstr ""
+
+msgid "Terraform|The Terraform report %{name} failed to generate."
+msgstr ""
+
+msgid "Terraform|The Terraform report %{name} was generated in your pipelines."
+msgstr ""
+
msgid "Test"
msgstr "Probar"
@@ -21730,8 +22453,8 @@ msgstr "Asegúrese de que el proyecto tenga trabajos de CI."
msgid "TestHooks|Ensure the project has CI pipelines."
msgstr "Asegúrese de que el proyecto tenga pipelines de CI."
-msgid "TestHooks|Ensure the project has at least one commit."
-msgstr "Asegúrese de que el proyecto tiene por lo menos un commit."
+msgid "TestHooks|Ensure the project has deployments."
+msgstr ""
msgid "TestHooks|Ensure the project has issues."
msgstr "Asegúrese de que el proyecto tenga incidencias."
@@ -21794,7 +22517,7 @@ msgid "Thanks! Don't show me this again"
msgstr "¡Gracias! No mostrar esto de nuevo"
msgid "That's it, well done!%{celebrate}"
-msgstr ""
+msgstr "Eso es todo, ¡bien hecho!%{celebrate}"
msgid "The \"%{group_path}\" group allows you to sign in with your Single Sign-On Account"
msgstr "El grupo \"%{group_path}\" le permite iniciar sesión utilizando su cuenta de inicio de sesión único"
@@ -21831,7 +22554,7 @@ msgstr "El gestor de incidencias es el lugar para agregar cosas que necesitan se
msgid "The Prometheus server responded with \"bad request\". Please check your queries are correct and are supported in your Prometheus version. %{documentationLink}"
msgstr ""
-msgid "The URL defined on the primary node that secondary nodes should use to contact it. Defaults to URL"
+msgid "The URL defined on the primary node that secondary nodes should use to contact it."
msgstr ""
msgid "The URL to use for connecting to Elasticsearch. Use a comma-separated list to support clustering (e.g., \"http://localhost:9200, http://localhost:9201\")."
@@ -21840,6 +22563,9 @@ msgstr "La URL a utilizar para conectarse a Elasticsearch. Utilice una lista sep
msgid "The X509 Certificate to use when mutual TLS is required to communicate with the external authorization service. If left blank, the server certificate is still validated when accessing over HTTPS."
msgstr "Un certificado X.509 se utiliza cuando se require una comunicación TLS con un servicio de autorización externo. Si se deja en blanco, el certificado utilizado desde el servidor es validado cuando se accedes a través del protocolo HTTPS."
+msgid "The above settings apply to all projects with the selected compliance framework(s)."
+msgstr ""
+
msgid "The amount of seconds after which a request to get a secondary node status will time out."
msgstr "Cantidad de segundos a partir de los cuales expirará una petición para obtener el estado de un nodo secundario."
@@ -22139,9 +22865,6 @@ msgstr "El tiempo utilizado por cada entrada de datos obtenido por esa etapa."
msgid "The total stage shows the time it takes between creating an issue and deploying the code to production. The data will be automatically added once you have completed the full idea to production cycle."
msgstr ""
-msgid "The unique identifier for the Geo node. Must match %{geoNodeName} if it is set in gitlab.rb, otherwise it must match %{externalUrl} with a trailing slash"
-msgstr ""
-
msgid "The update action will time out after %{number_of_minutes} minutes. For big repositories, use a clone/push combination."
msgstr "El proceso de actualización finalizará después de %{number_of_minutes} minutos. Para repositorios grandes, utilice una combinación de clone y push."
@@ -22166,9 +22889,6 @@ msgstr "El mapa del usuarios es una asignación de los usuarios de FogBugz que p
msgid "The user you are trying to deactivate has been active in the past %{minimum_inactive_days} days and cannot be deactivated"
msgstr "El usuario que está intentando desactivar ha estado activo en los últimos %{minimum_inactive_days} días y no puede ser desactivado"
-msgid "The user-facing URL of the Geo node"
-msgstr "La URL del usuario del nodo Geo"
-
msgid "The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6."
msgstr "El valor en el punto medio de una serie de valores observados. Por ejemplo, entre 3, 5, 9, la mediana es 5. Entre 3, 5, 7, 8, la mediana es (5 + 7) / 2 = 6."
@@ -22200,7 +22920,7 @@ msgid "There are no archived requirements"
msgstr ""
msgid "There are no changes"
-msgstr ""
+msgstr "No hay cambios"
msgid "There are no charts configured for this page"
msgstr "No hay gráficas configuradas para esta página"
@@ -22233,7 +22953,7 @@ msgid "There are no open merge requests"
msgstr "No hay merge requests abiertos"
msgid "There are no open requirements"
-msgstr ""
+msgstr "No hay requisitos abiertos"
msgid "There are no packages yet"
msgstr "Todavía no hay paquetes"
@@ -22242,7 +22962,7 @@ msgid "There are no projects shared with this group yet"
msgstr "Aún no hay proyectos compartidos con este grupo"
msgid "There are no variables yet."
-msgstr ""
+msgstr "Todavía no hay variables."
msgid "There is a limit of %{ci_project_subscriptions_limit} subscriptions from or to a project."
msgstr ""
@@ -22259,6 +22979,9 @@ msgstr "Se ha producido un problema al conectarse con su dispositivo."
msgid "There was a problem fetching project branches."
msgstr ""
+msgid "There was a problem fetching project tags."
+msgstr ""
+
msgid "There was a problem fetching project users."
msgstr ""
@@ -22310,6 +23033,9 @@ msgstr ""
msgid "There was an error fetching the %{replicableType}"
msgstr ""
+msgid "There was an error fetching the Geo Settings"
+msgstr ""
+
msgid "There was an error fetching the Node's Groups"
msgstr ""
@@ -22320,7 +23046,7 @@ msgid "There was an error fetching the top labels for the selected group"
msgstr ""
msgid "There was an error fetching the variables."
-msgstr ""
+msgstr "Se ha producido un error al recuperar las variables."
msgid "There was an error fetching value stream analytics stages."
msgstr ""
@@ -22430,6 +23156,12 @@ msgstr "Este %{issuableDisplayName} está bloqueado. Solo los miembros del proye
msgid "This %{issuable} is locked. Only <strong>project members</strong> can comment."
msgstr "Este %{issuable} está bloqueado. Solo los <strong>miembros del proyecto</strong> pueden comentar."
+msgid "This %{noteableTypeText} is %{confidentialLinkStart}confidential%{linkEnd} and %{lockedLinkStart}locked%{linkEnd}."
+msgstr ""
+
+msgid "This %{noteableTypeText} is locked."
+msgstr ""
+
msgid "This %{viewer} could not be displayed because %{reason}. You can %{options} instead."
msgstr "Este %{viewer} no se puede mostrar porque %{reason}. En su lugar, puedes %{options}."
@@ -22448,8 +23180,11 @@ msgstr ""
msgid "This action can lead to data loss. To prevent accidental actions we ask you to confirm your intention."
msgstr "Esta acción puede provocar la pérdida de datos. Para prevenir acciones accidentales, le pedimos que confirme su intención."
+msgid "This also resolves all related threads"
+msgstr ""
+
msgid "This also resolves the discussion"
-msgstr "Esto también resuelve la discusión"
+msgstr ""
msgid "This application was created by %{link_to_owner}."
msgstr "Esta aplicación fue creada por %{link_to_owner}."
@@ -22538,6 +23273,9 @@ msgstr ""
msgid "This epic does not exist or you don't have sufficient permission."
msgstr "Esta tarea épica no existe o no tiene permisos suficientes."
+msgid "This feature is currently in beta. We invite you to %{linkStart}give feedback%{linkEnd}."
+msgstr ""
+
msgid "This feature requires local storage to be enabled"
msgstr "Esta característica requiere que el almacenamiento local esté activado"
@@ -22571,8 +23309,8 @@ msgstr "Este es un usuario \"fantasma\", creado para mantener todas las incidenc
msgid "This is a Work in Progress"
msgstr "Este es un trabajo en curso"
-msgid "This is a confidential issue."
-msgstr "Esta incidencia es confidencial."
+msgid "This is a confidential %{noteableTypeText}."
+msgstr ""
msgid "This is a delayed job to run in %{remainingTime}"
msgstr ""
@@ -22598,9 +23336,6 @@ msgstr ""
msgid "This is your current session"
msgstr "Esta es su sesión actual"
-msgid "This issue is %{confidentialLinkStart}confidential%{linkEnd} and %{lockedLinkStart}locked%{linkEnd}."
-msgstr ""
-
msgid "This issue is confidential"
msgstr "Esta incidencia es confidencial"
@@ -22610,9 +23345,6 @@ msgstr ""
msgid "This issue is in a child epic of the filtered epic"
msgstr ""
-msgid "This issue is locked."
-msgstr "Esta incidencia está bloqueada."
-
msgid "This job depends on other jobs with expired/erased artifacts: %{invalid_dependencies}"
msgstr "Este trabajo depende de otros trabajos con artefactos caducados o borrados: %{invalid_dependencies}"
@@ -22710,6 +23442,9 @@ msgid "This job will automatically run after its timer finishes. Often they are
msgstr ""
msgid "This license has already expired."
+msgstr "Esta licencia ya ha caducado."
+
+msgid "This link points to external content"
msgstr ""
msgid "This may expose confidential information as the selected fork is in another namespace that can have other members."
@@ -22739,6 +23474,9 @@ msgstr "Esta página no está disponible porque no se le permite leer la informa
msgid "This page will be removed in a future release."
msgstr "Esta página se eliminará en una versión futura."
+msgid "This pipeline does not use the %{codeStart}needs%{codeEnd} keyword and can't be represented as a directed acyclic graph."
+msgstr ""
+
msgid "This pipeline makes use of a predefined CI/CD configuration enabled by %{strongStart}Auto DevOps.%{strongEnd}"
msgstr "Este pipeline utiliza una configuración de CI/CD predefinida habilitada por %{strongStart}Auto DevOps.%{strongEnd}"
@@ -22767,7 +23505,7 @@ msgid "This project does not have billing enabled. To create a cluster, <a href=
msgstr "Este proyecto no tiene la facturación habilitada. Para crear un clúster, <a href=%{linkToBilling} target=\"_blank\" rel=\"noopener noreferrer\">habilite la facturación <i class=\"fa fa-external-link\" aria-hidden=\"true\"></i></a> e inténtelo de nuevo."
msgid "This project has no active access tokens."
-msgstr ""
+msgstr "Este proyecto no tiene tokens de acceso activos."
msgid "This project is archived and cannot be commented on."
msgstr "Este proyecto está archivado y no se puede comentar en el."
@@ -22818,7 +23556,7 @@ msgid "This user cannot be unlocked manually from GitLab"
msgstr "Este usuario no puede ser desbloqueado manualmente desde GitLab"
msgid "This user has no active %{type}."
-msgstr ""
+msgstr "Este usuario no tiene ningún %{type} activo."
msgid "This user has no identities"
msgstr "El usuario no tiene ninguna identidad"
@@ -22827,10 +23565,10 @@ msgid "This user will be the author of all events in the activity feed that are
msgstr ""
msgid "This variable can not be masked."
-msgstr ""
+msgstr "Esta variable no se puede enmascarar."
msgid "This variable does not match the expected pattern."
-msgstr ""
+msgstr "Esta variable no coincide con el patrón esperado."
msgid "This will help us personalize your onboarding experience."
msgstr ""
@@ -22854,7 +23592,7 @@ msgid "Threat Monitoring"
msgstr "Threat Monitoring"
msgid "ThreatMonitoring|Anomalous Requests"
-msgstr ""
+msgstr "Peticiones anómalas"
msgid "ThreatMonitoring|Application firewall not detected"
msgstr ""
@@ -22875,19 +23613,19 @@ msgid "ThreatMonitoring|Environment"
msgstr "Entorno"
msgid "ThreatMonitoring|No environments detected"
-msgstr ""
+msgstr "No se han detectado entornos"
msgid "ThreatMonitoring|Operations Per Second"
-msgstr ""
+msgstr "Operaciones por segundo"
msgid "ThreatMonitoring|Overview"
-msgstr ""
+msgstr "Vista general"
msgid "ThreatMonitoring|Packet Activity"
-msgstr ""
+msgstr "Actividad del paquete"
msgid "ThreatMonitoring|Policies"
-msgstr ""
+msgstr "Políticas"
msgid "ThreatMonitoring|Requests"
msgstr "Peticiones"
@@ -22920,16 +23658,16 @@ msgid "ThreatMonitoring|To view this data, ensure you have configured an environ
msgstr ""
msgid "ThreatMonitoring|Total Packets"
-msgstr ""
+msgstr "Total de paquetes"
msgid "ThreatMonitoring|Total Requests"
-msgstr ""
+msgstr "Total de peticiones"
msgid "ThreatMonitoring|View documentation"
-msgstr ""
+msgstr "Ver documentación"
msgid "ThreatMonitoring|Web Application Firewall"
-msgstr ""
+msgstr "Web Application Firewall"
msgid "ThreatMonitoring|While it's rare to have no traffic coming to your application, it can happen. In any event, we ask that you double check your settings to make sure you've set up the WAF correctly."
msgstr ""
@@ -22967,11 +23705,14 @@ msgstr "Tiempo desde el primer cambio hasta el primer comentario"
msgid "Time from last commit to merge"
msgstr ""
+msgid "Time in seconds"
+msgstr "Tiempo en segundos"
+
msgid "Time in seconds GitLab will wait for a response from the external service. When the service does not respond in time, access will be denied."
msgstr "Tiempo en segundos que GitLab esperará hasta obtener una respuesta de un servicio externo. Cuando el servicio no responda a tiempo, se denegará el acceso."
msgid "Time of import: %{importTime}"
-msgstr ""
+msgstr "Tiempo de importación: %{importTime}"
msgid "Time remaining"
msgstr "Tiempo restante"
@@ -22980,7 +23721,7 @@ msgid "Time spent"
msgstr "Tiempo dedicado"
msgid "Time to merge"
-msgstr ""
+msgstr "Tiempo para fusionar"
msgid "Time to subtract exceeds the total time spent"
msgstr ""
@@ -23001,13 +23742,13 @@ msgid "TimeTracking|Estimated:"
msgstr "Estimado:"
msgid "TimeTracking|Over by %{timeRemainingHumanReadable}"
-msgstr ""
+msgstr "Más de %{timeRemainingHumanReadable}"
msgid "TimeTracking|Spent"
msgstr "Gastado"
msgid "TimeTracking|Time remaining: %{timeRemainingHumanReadable}"
-msgstr ""
+msgstr "Tiempo restante: %{timeRemainingHumanReadable}"
msgid "Timeago|%s days ago"
msgstr "hace %s días"
@@ -23161,7 +23902,7 @@ msgid "Title:"
msgstr "Título:"
msgid "Titles and Descriptions"
-msgstr ""
+msgstr "Títulos y descripciones"
msgid "To"
msgstr "Para"
@@ -23383,14 +24124,20 @@ msgid "Total"
msgstr "Total"
msgid "Total Contributions"
-msgstr "Contribuciones totales"
+msgstr "Colaboraciones totales"
msgid "Total artifacts size: %{total_size}"
msgstr "Tamaño total de los artefactos: %{total_size}"
+msgid "Total cores (CPUs)"
+msgstr "Núcleos totales (CPUs)"
+
msgid "Total issues"
msgstr "Total de incidencias"
+msgid "Total memory (GB)"
+msgstr ""
+
msgid "Total test time for all commits/merges"
msgstr "Tiempo total de pruebas para todos los cambios o integraciones"
@@ -23476,10 +24223,10 @@ msgid "Trending"
msgstr "Tendencia"
msgid "Trials|Go back to GitLab"
-msgstr ""
+msgstr "Volver a GitLab"
msgid "Trials|Skip Trial (Continue with Free Account)"
-msgstr ""
+msgstr "Saltar periodo de prueba (Continuar con cuenta gratuita)"
msgid "Trials|You can always resume this process by selecting your avatar and choosing 'Start a Gold trial'"
msgstr ""
@@ -23533,7 +24280,7 @@ msgid "Try all GitLab has to offer for 30 days."
msgstr "Pruebe todo lo que GitLab tiene para ofrecer durante 30 días."
msgid "Try changing or removing filters."
-msgstr ""
+msgstr "Intente cambiar o quitar los filtros."
msgid "Try to fork again"
msgstr "Intentar realizar el fork de nuevo"
@@ -23566,7 +24313,7 @@ msgid "Two-Factor Authentication"
msgstr "Autenticación de doble factor"
msgid "Two-Factor Authentication code"
-msgstr ""
+msgstr "Código de autenticación de dos factores"
msgid "Two-factor Authentication"
msgstr "Autenticación de doble factor"
@@ -23592,11 +24339,14 @@ msgstr "Dispositivos U2F (%{length})"
msgid "U2F only works with HTTPS-enabled websites. Contact your administrator for more details."
msgstr "U2F sólo funciona con sitios web habilitados con HTTPS. Contacte con su administrador para más detalles."
+msgid "UI Development Kit"
+msgstr ""
+
msgid "URL"
msgstr "URL"
msgid "URL is required"
-msgstr ""
+msgstr "Se requiere la URL"
msgid "URL must start with %{codeStart}http://%{codeEnd}, %{codeStart}https://%{codeEnd}, or %{codeStart}ftp://%{codeEnd}"
msgstr ""
@@ -23611,7 +24361,7 @@ msgid "URL or request ID"
msgstr "URL o ID de solicitud"
msgid "UTC"
-msgstr ""
+msgstr "UTC"
msgid "Unable to apply suggestions to a deleted line."
msgstr "No se puede aplicar sugerencias a una línea eliminada."
@@ -23626,7 +24376,7 @@ msgid "Unable to collect memory info"
msgstr "No se puede recopilar la información de la memoria"
msgid "Unable to connect to Elasticsearch"
-msgstr ""
+msgstr "Se ha producido un error al conectar a Elasticsearch"
msgid "Unable to connect to Prometheus server"
msgstr "No se ha podido conectar con el servidor de Prometheus"
@@ -23635,28 +24385,28 @@ msgid "Unable to connect to server: %{error}"
msgstr "Imposible conectar con el servidor: %{error}"
msgid "Unable to connect to the Jira instance. Please check your Jira integration configuration."
-msgstr ""
+msgstr "Se ha producido un error al conectar a la instancia de Jira. Por favor, compruebe la configuración de integración de Jira."
msgid "Unable to convert Kubernetes logs encoding to UTF-8"
-msgstr ""
+msgstr "No se puede convertir la codificación de registros de Kubernetes a UTF-8"
msgid "Unable to fetch unscanned projects"
-msgstr ""
+msgstr "No se pueden recuperar proyectos no escaneados"
msgid "Unable to fetch vulnerable projects"
msgstr "No se pueden obtener los proyectos vulnerables"
msgid "Unable to find Jira project to import data from."
-msgstr ""
+msgstr "No se puede encontrar el proyecto Jira para importar datos."
msgid "Unable to generate new instance ID"
msgstr "No se puede generar un nuevo ID de instancia"
msgid "Unable to load file contents. Try again later."
-msgstr ""
+msgstr "No se puede cargar el contenido del archivo. Por favor, inténtalo de nuevo más tarde."
msgid "Unable to load the diff"
-msgstr ""
+msgstr "No se puede cargar el diff"
msgid "Unable to load the diff. %{button_try_again}"
msgstr "No se puede cargar el fichero diff. %{button_try_again}"
@@ -23664,9 +24414,6 @@ msgstr "No se puede cargar el fichero diff. %{button_try_again}"
msgid "Unable to load the merge request widget. Try reloading the page."
msgstr ""
-msgid "Unable to process group import file"
-msgstr ""
-
msgid "Unable to resolve"
msgstr "No se puede resolver"
@@ -23682,6 +24429,9 @@ msgstr "No se puede programar un pipeline para que se ejecute inmediatamente"
msgid "Unable to sign you in to the group with SAML due to \"%{reason}\""
msgstr "Se ha producido un error al iniciar la sesión en el grupo mediante SAML debido a \"%{reason}\""
+msgid "Unable to suggest a path. Please refresh and try again."
+msgstr ""
+
msgid "Unable to update label prioritization at this time"
msgstr "No se puede actualizar la prioridad de la etiqueta en este momento"
@@ -23697,6 +24447,9 @@ msgstr "Desarchivar proyecto"
msgid "Unarchiving the project will restore people's ability to make changes to it. The repository can be committed to, and issues, comments, and other entities can be created. %{strong_start}Once active, this project shows up in the search and on the dashboard.%{strong_end}"
msgstr ""
+msgid "Unassign from commenting user"
+msgstr ""
+
msgid "Unblock"
msgstr "Desbloquear"
@@ -23704,10 +24457,10 @@ msgid "Undo"
msgstr "Deshacer"
msgid "Undo Ignore"
-msgstr ""
+msgstr "Deshacer ignorar"
msgid "Undo ignore"
-msgstr ""
+msgstr "Deshacer ignorar"
msgid "Unfortunately, your email message to GitLab could not be processed."
msgstr "Desafortunadamente, su mensaje de correo electrónico a GitLab no pudo ser procesado."
@@ -23719,10 +24472,10 @@ msgid "Uninstalling"
msgstr "Desinstalando"
msgid "Units|ms"
-msgstr ""
+msgstr "ms"
msgid "Units|s"
-msgstr ""
+msgstr "s"
msgid "Unknown"
msgstr "Desconocido"
@@ -23770,40 +24523,43 @@ msgid "Unmarks this %{noun} as Work In Progress."
msgstr "Desmarcar este %{noun} como trabajo en progreso."
msgid "Unreachable"
+msgstr "Inalcanzable"
+
+msgid "Unrecognized cluster type"
msgstr ""
msgid "Unresolve"
-msgstr ""
+msgstr "Sin resolver"
msgid "Unresolve thread"
msgstr "Hilo sin resolver"
msgid "Unresolved"
-msgstr ""
+msgstr "Sin resolver"
msgid "UnscannedProjects|15 or more days"
-msgstr ""
+msgstr "15 días o más"
msgid "UnscannedProjects|30 or more days"
-msgstr ""
+msgstr "30 días o más"
msgid "UnscannedProjects|5 or more days"
-msgstr ""
+msgstr "5 días o más"
msgid "UnscannedProjects|60 or more days"
-msgstr ""
+msgstr "60 días o más"
msgid "UnscannedProjects|Default branch scanning by project"
msgstr ""
msgid "UnscannedProjects|Out of date"
-msgstr ""
+msgstr "Fuera de fecha"
msgid "UnscannedProjects|Project scanning"
-msgstr ""
+msgstr "Análisis de proyectos"
msgid "UnscannedProjects|Untested"
-msgstr ""
+msgstr "No probado"
msgid "UnscannedProjects|Your projects are up do date! Nice job!"
msgstr ""
@@ -23832,6 +24588,9 @@ msgstr "Cancelar la suscripción de %{quick_action_target}."
msgid "Unsubscribes from this %{quick_action_target}."
msgstr "Cancelar la suscripción desde este %{quick_action_target}."
+msgid "Unsupported todo type passed. Supported todo types are: %{todo_types}"
+msgstr ""
+
msgid "Until"
msgstr "Hasta"
@@ -23859,6 +24618,9 @@ msgstr "Actualizar todo"
msgid "Update approval rule"
msgstr "Actualizar la regla de aprobación"
+msgid "Update approvers"
+msgstr ""
+
msgid "Update failed"
msgstr "Se ha producido un error en la actualización"
@@ -23868,11 +24630,14 @@ msgstr "Se ha producido un error en la actualización. Por favor, inténtelo de
msgid "Update it"
msgstr "Actualícelo"
+msgid "Update iteration"
+msgstr ""
+
msgid "Update now"
msgstr "Actualizar ahora"
msgid "Update variable"
-msgstr ""
+msgstr "Actualizar variable"
msgid "Update your bookmarked URLs as filtered/sorted branches URL has been changed."
msgstr ""
@@ -23913,8 +24678,8 @@ msgstr "Actualizado %{updated_at} por %{updated_by}"
msgid "Updated at"
msgstr "Actualizado el"
-msgid "Updated to"
-msgstr "Actualizado a"
+msgid "Updated to %{linkStart}chart v%{linkEnd}"
+msgstr ""
msgid "Updating"
msgstr "Actualizando"
@@ -23943,11 +24708,14 @@ msgstr "Subir el fichero <code>GoogleCodeProjectHosting.json</code> aquí:"
msgid "Upload CSV file"
msgstr "Subir fichero CSV"
+msgid "Upload License"
+msgstr "Subir licencia"
+
msgid "Upload New File"
msgstr "Subir nuevo archivo"
msgid "Upload New License"
-msgstr ""
+msgstr "Subir una nueva licencia"
msgid "Upload a certificate for your domain with all intermediates"
msgstr "Suba un certificado para su dominio con todos los certificados intermedios"
@@ -23968,7 +24736,7 @@ msgid "Uploaded on"
msgstr "Subido en"
msgid "Uploaded:"
-msgstr ""
+msgstr "Subido:"
msgid "Uploading changes to terminal"
msgstr "Subir los cambios al terminal"
@@ -24004,7 +24772,7 @@ msgid "UsageQuota|Artifacts"
msgstr "Artefactos"
msgid "UsageQuota|Build Artifacts"
-msgstr ""
+msgstr "Artefactos"
msgid "UsageQuota|Buy additional minutes"
msgstr "Comprar minutos adicionales"
@@ -24013,7 +24781,7 @@ msgid "UsageQuota|Current period usage"
msgstr "Periodo actual de uso"
msgid "UsageQuota|LFS Objects"
-msgstr ""
+msgstr "UsageQuota|LFS Objetos"
msgid "UsageQuota|LFS Storage"
msgstr "Almacenamiento LFS"
@@ -24024,18 +24792,21 @@ msgstr "Paquetes"
msgid "UsageQuota|Pipelines"
msgstr "Pipelines"
+msgid "UsageQuota|Purchase more storage"
+msgstr "Comprar más almacenamiento"
+
msgid "UsageQuota|Repositories"
-msgstr ""
+msgstr "Repositorios"
msgid "UsageQuota|Repository"
msgstr "Repositorio"
+msgid "UsageQuota|Snippets"
+msgstr "Fragmentos de código"
+
msgid "UsageQuota|Storage"
msgstr "Almacenamiento"
-msgid "UsageQuota|Storage usage:"
-msgstr ""
-
msgid "UsageQuota|This namespace has no projects which use shared runners"
msgstr "Este espacio de nombres no tiene proyectos que utilicen ejecutores compartidos"
@@ -24064,6 +24835,12 @@ msgid "UsageQuota|Wiki"
msgstr "Wiki"
msgid "UsageQuota|Wikis"
+msgstr "Wikis"
+
+msgid "UsageQuota|You used: %{usage} %{limit}"
+msgstr ""
+
+msgid "UsageQuota|out of %{formattedLimit} of your namespace storage"
msgstr ""
msgid "Use %{code_start}::%{code_end} to create a %{link_start}scoped label set%{link_end} (eg. %{code_start}priority::1%{code_end})"
@@ -24082,7 +24859,7 @@ msgid "Use an one time password authenticator on your mobile device or computer
msgstr "Utilice un autenticador de contraseña de un único uso en su dispositivo móvil u ordenador para habilitar la autenticación de dos factores (2FA)."
msgid "Use custom color #FF0000"
-msgstr ""
+msgstr "Utilizar un color personalizado #FF0000"
msgid "Use group milestones to manage issues from multiple projects in the same milestone."
msgstr "Use los hitos de grupo para administrar incidencias de múltiples proyectos en el mismo hito."
@@ -24112,7 +24889,7 @@ msgid "Used by members to sign in to your group in GitLab"
msgstr "Utilizado por los miembros para iniciar sesión en su grupo en GitLab"
msgid "Used programming language"
-msgstr ""
+msgstr "Lenguaje de programación utilizado"
msgid "Used to help configure your identity provider"
msgstr "Se utiliza para ayudar a configurar su proveedor de identidad"
@@ -24130,6 +24907,9 @@ msgid "User Cohorts are only shown when the %{usage_ping_link_start}usage ping%{
msgstr ""
msgid "User IDs"
+msgstr "IDs de usuario"
+
+msgid "User Lists can only be created and modified with %{linkStart}the API%{linkEnd}"
msgstr ""
msgid "User OAuth applications"
@@ -24156,6 +24936,9 @@ msgstr ""
msgid "User key was successfully removed."
msgstr "La clave de usuario se ha eliminado correctamente."
+msgid "User list %{name} will be removed. Are you sure?"
+msgstr ""
+
msgid "User map"
msgstr "Mapa de usuario"
@@ -24177,149 +24960,11 @@ msgstr "Se ha eliminado el usuario correctamente del proyecto."
msgid "User was successfully updated."
msgstr "Usuario actualizado correctamente."
-msgid "UserOnboardingTour|%{activeTour}/%{totalTours}"
-msgstr "%{activeTour}/%{totalTours}"
-
-msgid "UserOnboardingTour|%{completed}/%{total} steps completed"
-msgstr "%{completed}/%{total} de pasos completado"
-
-msgid "UserOnboardingTour|%{emphasisStart}Well done!%{emphasisEnd}%{lineBreak}%{lineBreak}That's it for our guided tour, congratulations for making it all the way to the end!%{lineBreak}%{lineBreak}We hope this gave you a good overview of GitLab and how it can help you. We'll now show you how to create your own project and invite your colleagues."
-msgstr ""
-
-msgid "UserOnboardingTour|Adding other members to a project is done through Project Settings. Click on %{emphasisStart}Settings%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Alright, that's it for Commits. Let's take a look at the %{emphasisStart}Branches%{emphasisEnd}."
-msgstr "Hemos terminado, esto es todo para los commits. Echemos un vistazo ahora a las %{emphasisStart}branches%{emphasisEnd}."
-
-msgid "UserOnboardingTour|Awesome! Now click on %{emphasisStart}Members%{emphasisEnd}."
-msgstr "¡Impresionante! Ahora haga clic en %{emphasisStart}Miembros%{emphasisEnd}."
-
-msgid "UserOnboardingTour|Click on one of the %{emphasisStart}Compare%{emphasisEnd} buttons to compare a branch to master."
-msgstr "Haga clic en uno de los botones %{emphasisStart}Comparar%{emphasisEnd} para comparar un rama con la rama master."
-
-msgid "UserOnboardingTour|Click on one of the %{emphasisStart}pipeline IDs%{emphasisEnd} to see the details of a pipeline."
-msgstr "Haga clic en uno de los %{emphasisStart}IDs del pipeline%{emphasisEnd} para ver los detalles de un pipeline."
-
-msgid "UserOnboardingTour|Click to open the latest commit to see its details."
-msgstr "Haga clic para abrir el último commit y ver sus detalles."
-
-msgid "UserOnboardingTour|Close 'Learn GitLab'"
-msgstr "Cerrar 'Aprender GitLab'"
-
-msgid "UserOnboardingTour|Commits are shown in chronological order and can be filtered by the commit message or by the branch."
-msgstr "Los commits se muestran en orden cronológico y se pueden filtrar por el mensaje del commit o por el branch donde se han realizado."
-
-msgid "UserOnboardingTour|Create a project"
-msgstr "Crear un proyecto"
-
-msgid "UserOnboardingTour|Exit 'Learn GitLab'"
-msgstr "Salir de 'Aprenda GitLab'"
-
-msgid "UserOnboardingTour|Got it"
-msgstr "Lo tengo"
-
-msgid "UserOnboardingTour|Great job! %{clapHands} We hope the tour was helpful and that you learned how to use GitLab.%{lineBreak}%{lineBreak}We'd love to get your feedback on this tour.%{lineBreak}%{lineBreak}%{emphasisStart}How helpful would you say this guided tour was?%{emphasisEnd}%{lineBreak}%{lineBreak}"
-msgstr "¡Buen trabajo! %{clapHands} Esperamos que este recorrido le haya resultado útil y que haya aprendido a utilizar GitLab.%{lineBreak}%{lineBreak}Nos encantaría recibir sus comentarios sobre este tour.%{lineBreak}%{lineBreak}%{emphasisStart}¿Diría que fue de utilidad esta visita guiada?%{emphasisEnd}%{lineBreak}%{lineBreak}"
-
-msgid "UserOnboardingTour|Guided GitLab Tour"
-msgstr "Tour de GitLab guiado"
-
-msgid "UserOnboardingTour|Here you can compare the changes of this branch to another one. Changes are divided by files so that it's easier to see what was changed where."
-msgstr "Aquí puede comparar los cambios de una rama con otra. Los cambios se dividen en archivos para que sea más fácil ver qué se cambió y en qué lugar."
-
-msgid "UserOnboardingTour|Here you can create a project from scratch, start with a template or import a repository from other platforms. Whatever you choose, we'll guide you through the process.%{lineBreak}%{lineBreak}Fill in your new project information and click on %{emphasisStart}Create Project%{emphasisEnd} to progress to the next step."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can see the breakdown of the pipelines: its stages and jobs in each of the stages and their status.%{lineBreak}%{lineBreak}Our CI/CD pipelines are quite complex, most of our users have fewer and simpler pipelines."
-msgstr "Aquí puede ver con detalle los pipelines: sus etapas y los trabajos en cada una de ellas así como su estado.%{lineBreak}%{lineBreak}Nuestros pipelines CI/CD son bastante complejos, la mayoría de nuestros usuarios tienen menos pipelines y son más sencillos."
-
-msgid "UserOnboardingTour|Here you can see the current members of the project (just you at the moment) and invite new members.%{lineBreak}%{lineBreak}You can invite multiple members at once (existing GitLab users or invite by email) and you can also set their roles and permissions.%{lineBreak}%{lineBreak}Add a few members and click on %{emphasisStart}Add to project%{emphasisEnd} to complete this step."
-msgstr "Aquí puede ver los miembros actuales del proyecto (sólo tú en este momento) y desde aquí, invitar a nuevos miembros.%{lineBreak}%{lineBreak}Puede invitar varios miembros a la vez (usuarios de GitLab existentes o invitarlos por correo electrónico) y también puede establecer sus roles y permisos.%{lineBreak}%{lineBreak}Agregue algunos miembros y haga clic sobre %{emphasisStart}Añadir al proyecto%{emphasisEnd} para completar este paso."
-
-msgid "UserOnboardingTour|Here you can see what changes were made with this commit, on what branch and if there's a related merge request. The status of the pipeline will also show up if CI/CD is set up.%{lineBreak}%{lineBreak}You can also comment on the lines of code that were changed and start a discussion with your colleagues!"
-msgstr "Aquí puede ver qué cambios se han realizado en este commit, en qué rama y si hay un merge request relacionado. También se mostrará el estado del pipeline si se ha configurado CI/CD.%{lineBreak}%{lineBreak}¡También puede comentar las líneas de código que han cambiado y empezar una discusión con sus colegas!"
-
-msgid "UserOnboardingTour|Here's an overview of branches in the %{emphasisStart}%{projectName}%{emphasisEnd} project. They're split into Active and Stale.%{lineBreak}%{lineBreak}From here, you can create a new merge request from a branch, or compare the branch to any other branch in the project. By default, it will compare it to the master branch."
-msgstr ""
-
-msgid "UserOnboardingTour|Invite colleagues"
-msgstr "Invitar a sus colegas"
-
-msgid "UserOnboardingTour|Issues are great for communicating and keeping track of progress in GitLab. These are all issues that are open in the %{emphasisStart}%{projectName}%{emphasisEnd}.%{lineBreak}%{lineBreak}You can help us improve GitLab by contributing work to issues that are labeled <span class=\"badge color-label accept-mr-label\">Accepting merge requests</span>.%{lineBreak}%{lineBreak}This list can be filtered by labels, milestones, assignees, authors... We'll show you how it looks when the list is filtered by a label."
-msgstr ""
-
-msgid "UserOnboardingTour|Learn GitLab"
-msgstr "Aprenda GitLab"
-
-msgid "UserOnboardingTour|Let's take a closer look at a merge request. Click on the title of one."
-msgstr "Echemos un vistazo más de cerca a los merge request. Haga clic sobre el título de uno."
-
-msgid "UserOnboardingTour|Let's take a closer look at all the commits. Click on %{emphasisStart}Commits%{emphasisEnd}."
-msgstr "Echemos un vistazo más de cerca a todos los commits. Haga clic sobre %{emphasisStart}Commits%{emphasisEnd}."
-
-msgid "UserOnboardingTour|Let's take a closer look at the repository of this project. Click on %{emphasisStart}Repository%{emphasisEnd}."
-msgstr "Echemos un vistazo más de cerca a todos los repositorio de este proyecto. Haga clic sobre %{emphasisStart}Repositorio%{emphasisEnd}."
-
-msgid "UserOnboardingTour|No thanks"
-msgstr "No gracias"
-
-msgid "UserOnboardingTour|Ok, let's go"
-msgstr "De acuerdo, vamos"
-
-msgid "UserOnboardingTour|Ok, show me"
-msgstr "De acuerdo, muéstralo"
-
-msgid "UserOnboardingTour|Open one of the issues by clicking on its title."
-msgstr "Abra una de las incidencias haciendo clic en su título."
-
-msgid "UserOnboardingTour|Restart this step"
-msgstr "Reiniciar este paso"
-
-msgid "UserOnboardingTour|Skip this step"
-msgstr "Saltar este paso"
-
-msgid "UserOnboardingTour|Sweet! Your project was created and is ready to be used.%{lineBreak}%{lineBreak}You can start adding files to the repository or clone it. One last thing we want to show you is how to invite your colleagues to your new project."
-msgstr ""
-
-msgid "UserOnboardingTour|Take a look. Here's a nifty menu for quickly creating issues, merge requests, snippets, projects and groups. Click on it and select \"New project\" from the \"GitLab\" section to get started."
-msgstr "Eche un vistazo. Aquí tiene un elegante menú para documentar incidencias, merge requests, fragmentos de código, proyectos y grupos. Para empezar, haga clic sobre él y seleccione «Nuevo proyecto» desde la sección «GitLab»."
-
-msgid "UserOnboardingTour|Thanks for taking the guided tour. Remember, if you want to go through it again, you can start %{emphasisStart}Learn GitLab%{emphasisEnd} in the help menu on the top right."
-msgstr ""
-
-msgid "UserOnboardingTour|Thanks for the feedback! %{thumbsUp}"
-msgstr "¡Gracias por los comentarios! %{thumbsUp}"
-
-msgid "UserOnboardingTour|That's it for issues. Let'st take a look at %{emphasisStart}Merge Requests%{emphasisEnd}."
-msgstr "Hemos terminado, esto es todo para las incidencias. Echemos un vistazo ahora a los %{emphasisStart}Merge Requests%{emphasisEnd}."
-
-msgid "UserOnboardingTour|That's it for merge requests. Now for the final part of this guided tour - the %{emphasisStart}CI/CD%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|That's it for the Repository. Let's take a look at the %{emphasisStart}Issues%{emphasisEnd}."
-msgstr "Hemos terminado con el repositorio. Vamos a echar un vistazo a las %{emphasisStart}incidencias%{emphasisEnd}."
-
-msgid "UserOnboardingTour|The structure of this page is very similar to issues. Status, description, discussion and the sidebar are all here.%{lineBreak}%{lineBreak}But take a look below the description and you'll notice that there's more information about the merge request, the CI/CD pipeline and the options for approving it.%{lineBreak}%{lineBreak}Alongside the discussion you can also see more information about commits in this merge request, the status of pipelines and review all changes that were made."
-msgstr ""
-
-msgid "UserOnboardingTour|There's a lot of information here but don't worry, we'll go through it.%{lineBreak}%{lineBreak}On the top you can see the status of the issue and when it was opened and by whom. Directly below it is the issue description and below that are other %{emphasisStart}related issues%{emphasisEnd} and %{emphasisStart}merge requests%{emphasisEnd} (if any). Then below that is the %{emphasisStart}discussion%{emphasisEnd}, that's where most of the communication happens.%{lineBreak}%{lineBreak}On the right, there's a sidebar where you can view/change the %{emphasisStart}assignee, milestone, due date, labels, weight%{emphasisEnd}, etc."
-msgstr ""
-
-msgid "UserOnboardingTour|These are all the CI/CD pipelines we have for our %{emphasisStart}%{projectName}%{emphasisEnd} project.%{lineBreak}%{lineBreak}Here you can see the status of each pipeline, for what commit it's running for, its stages and the status for them."
-msgstr ""
-
-msgid "UserOnboardingTour|These are all the issues that are available for community contributions. Let's take a closer look at one of them."
-msgstr ""
-
-msgid "UserOnboardingTour|This is an overview of all merge requests in this project. Similarly to the issues overview it can be filtered down by things like labels, milestones, authors, assignees, etc."
-msgstr ""
-
-msgid "UserOnboardingTour|This is the repository for the %{emphasisStart}%{projectName}%{emphasisEnd} project. All our code is stored here. Feel free to explore and take a closer look at folders and files.%{lineBreak}%{lineBreak}Above the file structure you can see the latest commit, who the author is and the status of the CI/CD pipeline.%{lineBreak}%{lineBreak}If you scroll down below the file structure, you'll find the Readme of this project. This is defined in the README.md file at the root of the repository."
-msgstr ""
+msgid "UserList|Delete %{name}?"
+msgstr "¿Eliminar %{name}?"
-msgid "UserOnboardingTour|Welcome to the project overview of the %{emphasisStart}%{projectName}%{emphasisEnd} project. This is the project that we use to work on GitLab. At first, a project seems like a simple repository, but at GitLab, a project is so much more.%{lineBreak}%{lineBreak}You can create projects for hosting your codebase, use it as an issue tracker, collaborate on code, and continuously build, test, and deploy your app with built-in GitLab CI/CD."
-msgstr ""
+msgid "UserList|created %{timeago}"
+msgstr "creado %{timeago}"
msgid "UserProfile|Activity"
msgstr "Actividad"
@@ -24412,10 +25057,10 @@ msgid "UserProfile|Your projects can be available publicly, internally, or priva
msgstr "A su elección, sus proyectos pueden estar disponible públicamente, internamente o ser privados."
msgid "UserProfile|at"
-msgstr ""
+msgstr "en"
msgid "UserProfile|made a private contribution"
-msgstr ""
+msgstr "hizo una colaboración privada"
msgid "Username (optional)"
msgstr "Nombre de usuario (opcional)"
@@ -24436,7 +25081,7 @@ msgid "Users"
msgstr "Usuarios"
msgid "Users in License:"
-msgstr ""
+msgstr "Usuarios en la licencia:"
msgid "Users or groups set as approvers in the project's or merge request's settings."
msgstr ""
@@ -24471,6 +25116,9 @@ msgstr "Ningún asignado - %{openingTag} asignarse a usted mismo %{closingTag}"
msgid "UsersSelect|Unassigned"
msgstr "Sin asignar"
+msgid "Using %{codeStart}needs%{codeEnd} allows jobs to run before their stage is reached, as soon as their individual dependencies are met, which speeds up your pipelines."
+msgstr ""
+
msgid "Using %{code_start}::%{code_end} denotes a %{link_start}scoped label set%{link_end}"
msgstr "Uso de %{code_start}::%{code_end} denota un %{link_start}con ámbito de etiqueta%{link_end}"
@@ -24478,7 +25126,7 @@ msgid "Using required encryption strategy when encrypted field is missing!"
msgstr "Utilizar la estrategia de cifrado requerida cuando falta el campo cifrado"
msgid "Valid from"
-msgstr ""
+msgstr "Válido desde"
msgid "Validate"
msgstr "Validar"
@@ -24511,7 +25159,7 @@ msgid "ValueStreamAnalytics|%{days}d"
msgstr ""
msgid "Variable"
-msgstr ""
+msgstr "Variable"
msgid "Variable will be masked in job logs."
msgstr ""
@@ -24553,10 +25201,7 @@ msgid "Version"
msgstr "Versión"
msgid "Versions"
-msgstr ""
-
-msgid "Very helpful"
-msgstr "Muy útil"
+msgstr "Versiones"
msgid "View Documentation"
msgstr "Ver documentación"
@@ -24569,8 +25214,8 @@ msgstr ""
msgid "View chart"
msgid_plural "View charts"
-msgstr[0] ""
-msgstr[1] ""
+msgstr[0] "Ver gráfico"
+msgstr[1] "Ver gráficos"
msgid "View dependency details for your project"
msgstr "Ver los detalles de las dependencia para su proyecto"
@@ -24579,7 +25224,7 @@ msgid "View deployment"
msgstr "Ver despliegue"
msgid "View details"
-msgstr ""
+msgstr "Ver detalles"
msgid "View details: %{details_url}"
msgstr "Ver detalles: %{details_url}"
@@ -24602,25 +25247,25 @@ msgid "View file @ "
msgstr "Ver archivo @ "
msgid "View file @ %{commitSha}"
-msgstr ""
+msgstr "Ver archivo @ %{commitSha}"
msgid "View full dashboard"
msgstr "Ver el panel de control completo"
msgid "View full log"
-msgstr ""
+msgstr "Ver registro completo"
msgid "View group labels"
msgstr "Ver etiquetas de grupo"
msgid "View incident issues."
-msgstr ""
+msgstr "Ver incidencias."
msgid "View issue"
msgstr "Ver incidencia"
msgid "View issues"
-msgstr ""
+msgstr "Ver incidencias"
msgid "View it on GitLab"
msgstr "Ver en GitLab"
@@ -24644,7 +25289,7 @@ msgid "View open merge request"
msgstr "Ver solicitud de fusión abierta"
msgid "View performance dashboard."
-msgstr ""
+msgstr "Ver panel de control de rendimiento."
msgid "View project"
msgstr "Ver proyecto"
@@ -24656,7 +25301,7 @@ msgid "View replaced file @ "
msgstr "Ver archivo reemplazado @ "
msgid "View supported languages and frameworks"
-msgstr ""
+msgstr "Ver idiomas y marcos soportados"
msgid "View the documentation"
msgstr "Ver documentación"
@@ -24665,10 +25310,10 @@ msgid "View the latest successful deployment to this environment"
msgstr ""
msgid "View the performance dashboard at"
-msgstr ""
+msgstr "Ver el panel de control del rendimiento en"
msgid "View users statistics"
-msgstr ""
+msgstr "Ver las estadísticas de usuarios"
msgid "Viewing commit"
msgstr "Ver commit"
@@ -24706,6 +25351,9 @@ msgstr "Público"
msgid "VisibilityLevel|Unknown"
msgstr "Desconocido"
+msgid "Visit settings page"
+msgstr ""
+
msgid "VisualReviewApp|%{stepStart}Step 1%{stepEnd}. Copy the following script:"
msgstr "%{stepStart}Paso 1%{stepEnd}. Copie el siguiente script:"
@@ -24719,7 +25367,7 @@ msgid "VisualReviewApp|%{stepStart}Step 4%{stepEnd}. Leave feedback in the Revie
msgstr ""
msgid "VisualReviewApp|Cancel"
-msgstr ""
+msgstr "Cancelar"
msgid "VisualReviewApp|Copy merge request ID"
msgstr "Copiar el id del merge request"
@@ -24773,32 +25421,35 @@ msgid "VulnerabilityManagement|A true-positive and will fix"
msgstr ""
msgid "VulnerabilityManagement|Change status"
-msgstr ""
+msgstr "Cambiar estado"
msgid "VulnerabilityManagement|Confirm"
-msgstr ""
+msgstr "Confirmar"
msgid "VulnerabilityManagement|Confirmed %{timeago} by %{user}"
-msgstr ""
+msgstr "Confirmado %{timeago} por %{user}"
msgid "VulnerabilityManagement|Detected %{timeago} in pipeline %{pipelineLink}"
-msgstr ""
+msgstr "Detectado %{timeago} en el pipeline %{pipelineLink}"
msgid "VulnerabilityManagement|Dismiss"
-msgstr ""
+msgstr "Descartar"
msgid "VulnerabilityManagement|Dismissed %{timeago} by %{user}"
-msgstr ""
+msgstr "Descartado %{timeago} por %{user}"
msgid "VulnerabilityManagement|Resolved"
-msgstr ""
+msgstr "Resuelta"
msgid "VulnerabilityManagement|Resolved %{timeago} by %{user}"
-msgstr ""
+msgstr "Resuelto %{timeago} por %{user}"
msgid "VulnerabilityManagement|Something went wrong while trying to delete the comment. Please try again later."
msgstr ""
+msgid "VulnerabilityManagement|Something went wrong while trying to refresh the vulnerability. Please try again later."
+msgstr ""
+
msgid "VulnerabilityManagement|Something went wrong while trying to retrieve the vulnerability history. Please try again later."
msgstr ""
@@ -24821,31 +25472,34 @@ msgid "VulnerabilityManagement|Will not fix or a false-positive"
msgstr ""
msgid "VulnerabilityStatusTypes|All"
-msgstr ""
+msgstr "Todas"
msgid "VulnerabilityStatusTypes|Confirmed"
-msgstr ""
+msgstr "Confirmadas"
msgid "VulnerabilityStatusTypes|Detected"
-msgstr ""
+msgstr "Detectadas"
msgid "VulnerabilityStatusTypes|Dismissed"
-msgstr ""
+msgstr "Descartadas"
msgid "VulnerabilityStatusTypes|Resolved"
-msgstr ""
+msgstr "Resueltas"
msgid "Vulnerability|%{scannerName} (version %{scannerVersion})"
-msgstr ""
+msgstr "%{scannerName} (versión %{scannerVersion})"
msgid "Vulnerability|Class"
msgstr "Clase"
+msgid "Vulnerability|Crash Address"
+msgstr ""
+
msgid "Vulnerability|Description"
msgstr "Descripción"
msgid "Vulnerability|Evidence"
-msgstr ""
+msgstr "Evidencia"
msgid "Vulnerability|File"
msgstr "Archivo"
@@ -24863,7 +25517,7 @@ msgid "Vulnerability|Links"
msgstr "Enlaces"
msgid "Vulnerability|Method"
-msgstr ""
+msgstr "Método"
msgid "Vulnerability|Namespace"
msgstr "Espacio de nombres"
@@ -24872,16 +25526,16 @@ msgid "Vulnerability|Project"
msgstr "Proyecto"
msgid "Vulnerability|Scanner Provider"
-msgstr ""
+msgstr "Proveedor del escáner"
msgid "Vulnerability|Scanner Type"
-msgstr ""
+msgstr "Tipo de escáner"
msgid "Vulnerability|Severity"
msgstr "Severidad"
msgid "Vulnerability|Status"
-msgstr ""
+msgstr "Estado"
msgid "WIP"
msgstr "Trabajo en curso"
@@ -24916,9 +25570,6 @@ msgstr "No es posible determinar la ruta para eliminar esta incidencia"
msgid "We couldn't reach the Prometheus server. Either the server no longer exists or the configuration details need updating."
msgstr "No ha sido posible conectar al servidor de Prometheus. O el servidor ya no existe o debe actualizar los detalles de configuración."
-msgid "We created a short guided tour that will help you learn the basics of GitLab and how it will help you be better at your job. It should only take a couple of minutes. You will be guided by two types of helpers, best recognized by their color."
-msgstr "Hemos creado una breve visita guiada que le ayudará a aprender los conceptos básicos de GitLab y cómo le ayudará a mejorar en su trabajo. Sólo debería llevarle un par de minutos. Será guiado por dos tipos de ayudantes, que podrá reconocer por su color."
-
msgid "We detected potential spam in the %{humanized_resource_name}. Please solve the reCAPTCHA to proceed."
msgstr "Hemos detectado spam potencial en %{humanized_resource_name}. Por favor resuelva el reCAPTCHA para poder continuar."
@@ -24952,6 +25603,9 @@ msgstr ""
msgid "We've found no vulnerabilities"
msgstr "No hemos encontrado vulnerabilidades"
+msgid "Web Application Firewall"
+msgstr ""
+
msgid "Web IDE"
msgstr "Web IDE"
@@ -24986,7 +25640,7 @@ msgid "Wednesday"
msgstr "Miércoles"
msgid "Weekday"
-msgstr ""
+msgstr "Día de la semana"
msgid "Weeks"
msgstr "Semanas"
@@ -25007,16 +25661,13 @@ msgid "Welcome to GitLab %{name}!"
msgstr "¡Bienvenido a GitLab %{name}!"
msgid "Welcome to GitLab, %{first_name}!"
-msgstr ""
+msgstr "¡Bienvenido a GitLab %{first_name}!"
msgid "Welcome to GitLab.com<br>@%{name}!"
-msgstr ""
-
-msgid "Welcome to the Guided GitLab Tour"
-msgstr "Bienvenido al tour guiado de GitLab"
+msgstr "¡Bienvenido a GitLab.com<br>@%{name}!"
msgid "Welcome to the guided GitLab tour"
-msgstr ""
+msgstr "Bienvenido a la visita guiada de GitLab"
msgid "Welcome to your Issue Board!"
msgstr "¡Bienvenido a su tablón de incidencias!"
@@ -25025,10 +25676,10 @@ msgid "What are you searching for?"
msgstr "¿Qué está buscando?"
msgid "What describes you best?"
-msgstr ""
+msgstr "¿Qué le describe mejor?"
msgid "What’s your experience level?"
-msgstr ""
+msgstr "¿Cuál es su nivel de experiencia?"
msgid "When a deployment job is successful, skip older deployment jobs that are still pending"
msgstr ""
@@ -25062,9 +25713,6 @@ msgstr "Cuando:"
msgid "While it's rare to have no vulnerabilities, it can happen. In any event, we ask that you please double check your settings to make sure you've set up your dashboard correctly."
msgstr ""
-msgid "White helpers give contextual information."
-msgstr "Los ayudantes blancos proporcionan información contextual."
-
msgid "Who can be an approver?"
msgstr "¿Quién puede ser un aprobador?"
@@ -25075,13 +25723,13 @@ msgid "Who will be able to see this group?"
msgstr "¿Quién podrá ver a este grupo?"
msgid "Who will be using GitLab?"
-msgstr ""
+msgstr "¿Quién utilizará GitLab?"
msgid "Who will be using this GitLab subscription?"
-msgstr ""
+msgstr "¿Quién utilizará esta suscripción de GitLab?"
msgid "Who will be using this GitLab trial?"
-msgstr ""
+msgstr "¿Quién utilizará esta versión de prueba de GitLab?"
msgid "Wiki"
msgstr "Wiki"
@@ -25110,12 +25758,18 @@ msgstr "Ya hay una página con el mismo título en la ruta."
msgid "WikiEmptyIssueMessage|Suggest wiki improvement"
msgstr "Sugerir una mejora en el wiki"
+msgid "WikiEmptyIssueMessage|You must be a group member in order to add wiki pages. If you have suggestions for how to improve the wiki for this group, consider opening an issue in the %{issues_link}."
+msgstr ""
+
msgid "WikiEmptyIssueMessage|You must be a project member in order to add wiki pages. If you have suggestions for how to improve the wiki for this project, consider opening an issue in the %{issues_link}."
msgstr "Debe ser un miembro del proyecto para poder agregar páginas al wiki. Si tiene sugerencias sobre cómo mejorar el wiki para este proyecto, considere abrir una incidencia en el enlace, %{issues_link}."
msgid "WikiEmptyIssueMessage|issue tracker"
msgstr "Gestor de incidencias"
+msgid "WikiEmpty|A wiki is where you can store all the details about your group. This can include why you've created it, its principles, how to use it, and so on."
+msgstr ""
+
msgid "WikiEmpty|A wiki is where you can store all the details about your project. This can include why you've created it, its principles, how to use it, and so on."
msgstr "Un wiki es una herramienta en la que puede almacenar todos los detalles sobre su proyecto. Puede indicar por qué lo ha creado, sus principios, instrucciones sobre cómo utilizarlo, etc."
@@ -25125,12 +25779,21 @@ msgstr "Cree su primera página"
msgid "WikiEmpty|Suggest wiki improvement"
msgstr "Sugerir una mejora del wiki"
+msgid "WikiEmpty|The wiki lets you write documentation for your group"
+msgstr ""
+
msgid "WikiEmpty|The wiki lets you write documentation for your project"
msgstr "El wiki le permite escribir documentación para su proyecto"
+msgid "WikiEmpty|This group has no wiki pages"
+msgstr "Este grupo no tiene páginas wiki"
+
msgid "WikiEmpty|This project has no wiki pages"
msgstr "Este proyecto no tiene páginas en el wiki"
+msgid "WikiEmpty|You must be a group member in order to add wiki pages."
+msgstr ""
+
msgid "WikiEmpty|You must be a project member in order to add wiki pages."
msgstr "Debe ser un miembro del proyecto para agregar páginas al wiki."
@@ -25213,13 +25876,13 @@ msgid "Wiki|Title"
msgstr "Título"
msgid "Wiki|View All Pages"
-msgstr ""
+msgstr "Ver todas las páginas"
msgid "Wiki|Wiki Pages"
msgstr "Páginas del Wiki"
msgid "Will be created"
-msgstr ""
+msgstr "Se creará"
msgid "Will deploy to"
msgstr "Se desplegará a"
@@ -25234,10 +25897,10 @@ msgid "Won't fix / Accept risk"
msgstr ""
msgid "Work in progress (open and unassigned)"
-msgstr ""
+msgstr "Trabajo en curso (abierto y sin asignar)"
msgid "Work in progress Limit"
-msgstr ""
+msgstr "Límite de trabajo en progreso"
msgid "Write"
msgstr "Escritura"
@@ -25270,7 +25933,7 @@ msgid "Yes, add it"
msgstr "Si, hazlo"
msgid "Yes, close issue"
-msgstr ""
+msgstr "Sí, cerrar la incidencia"
msgid "Yes, let me map Google Code users to full names or GitLab users."
msgstr "Sí, déjenme asignar usuarios de Google Code a nombres completos o usuarios de GitLab."
@@ -25357,7 +26020,7 @@ msgid "You can also use project access tokens to authenticate against Git over H
msgstr ""
msgid "You can always edit this later"
-msgstr ""
+msgstr "Siempre puede editar esto más tarde"
msgid "You can apply your Trial to your Personal account or create a New Group."
msgstr "Puedes aplicar su version de prueba a su cuenta personal o crear un grupo nuevo."
@@ -25407,15 +26070,15 @@ msgstr ""
msgid "You can now export your security dashboard to a CSV report."
msgstr ""
+msgid "You can now manage alert endpoint configuration in the Alerts section on the Operations settings page. Fields on this page have been deprecated."
+msgstr ""
+
msgid "You can now submit a merge request to get this change into the original branch."
msgstr "Puede enviar un merge request para tener este cambio en la rama original."
msgid "You can now submit a merge request to get this change into the original project."
msgstr "Puede enviar un merge request para tener este cambio en el proyecto original."
-msgid "You can only add files when you are on a branch"
-msgstr "Solo puedes agregar archivos cuando estás en una rama"
-
msgid "You can only edit files when you are on a branch"
msgstr "Solo puede agregar archivos cuando está en una rama"
@@ -25635,7 +26298,7 @@ msgstr "Es necesario subir un archivo de exportación de proyecto de GitLab (en
msgid "You need to upload a Google Takeout archive."
msgstr "Necesita subir un archivo de Google Takeout."
-msgid "You reached %{usage_in_percent} of %{namespace_name}'s capacity (%{used_storage} of %{storage_limit})"
+msgid "You reached %{usage_in_percent} of %{namespace_name}'s storage capacity (%{used_storage} of %{storage_limit})"
msgstr ""
msgid "You tried to fork %{link_to_the_project} but it failed for the following reason:"
@@ -25765,7 +26428,7 @@ msgid "Your GPG keys (%{count})"
msgstr "Sus claves GPG (%{count})"
msgid "Your GitLab group"
-msgstr ""
+msgstr "Su grupo de GitLab"
msgid "Your Gitlab Gold trial will last 30 days after which point you can keep your free Gitlab account forever. We just need some additional information to activate your trial."
msgstr ""
@@ -25774,7 +26437,7 @@ msgid "Your Groups"
msgstr "Sus grupos"
msgid "Your License"
-msgstr ""
+msgstr "Su licencia"
msgid "Your Personal Access Tokens will expire in %{days_to_expire} days or less"
msgstr "Sus tokens de acceso personal caducarán en %{days_to_expire} días o menos"
@@ -25813,7 +26476,7 @@ msgid "Your account has been deactivated by your administrator. Please log back
msgstr "Su cuenta ha sido desactivada por su administrador. Por favor, vuelva a iniciar la sesión para reactivar tu cuenta."
msgid "Your account is locked."
-msgstr ""
+msgstr "Su cuenta está bloqueada."
msgid "Your account uses dedicated credentials for the \"%{group_name}\" group and can only be updated through SSO."
msgstr "Su cuenta utiliza credenciales dedicadas para el grupo \"%{group_name}\" y sólo puede actualizarse a través de SSO."
@@ -25849,7 +26512,7 @@ msgid "Your comment could not be updated! Please check your network connection a
msgstr "Se ha producido un error al editar su comentario. Por favor, compruebe su conexión de red y vuelva a intentarlo."
msgid "Your comment will be discarded."
-msgstr ""
+msgstr "Su comentario será descartado."
msgid "Your custom stage '%{title}' was created"
msgstr ""
@@ -25867,7 +26530,7 @@ msgid "Your device was successfully set up! Give it a name and register it with
msgstr "¡El dispositivo se ha configurado correctamente!. Por favor, asígnele un nombre y regístrelo con el servidor de GitLab."
msgid "Your first project"
-msgstr ""
+msgstr "Su primer proyecto"
msgid "Your groups"
msgstr "Tus grupos"
@@ -25885,7 +26548,7 @@ msgid "Your issues will be imported in the background. Once finished, you'll get
msgstr "Sus incidencias serán importados como tarea de fondo. Al finalizar el proceso, recibirá un correo electrónico de confirmación."
msgid "Your license is valid from"
-msgstr ""
+msgstr "Su licencia es válida desde"
msgid "Your license will be included in your GitLab backup and will survive upgrades, so in normal usage you should never need to re-upload your <code>.gitlab-license</code> file."
msgstr ""
@@ -25897,7 +26560,7 @@ msgid "Your name"
msgstr "Tu nombre"
msgid "Your new %{type}"
-msgstr ""
+msgstr "Su nuevo %{type}"
msgid "Your new SCIM token"
msgstr "Su nuevo token SCIM"
@@ -25915,7 +26578,7 @@ msgid "Your password reset token has expired."
msgstr "Su token para restablecer la contraseña ha caducado."
msgid "Your profile"
-msgstr ""
+msgstr "Su perfil"
msgid "Your project limit is %{limit} projects! Please contact your administrator to increase it"
msgstr "¡Su límite de proyecto es %{limit} proyectos! Por favor, contacte con su administrador para aumentarlo"
@@ -25930,21 +26593,21 @@ msgid "Your request for access has been queued for review."
msgstr "Su solicitud de acceso ha sido puesta en cola para su revisión."
msgid "Your response has been recorded."
-msgstr ""
+msgstr "Su respuesta ha sido registrada."
msgid "Your search didn't match any commits."
-msgstr ""
+msgstr "Su búsqueda no coincide con ningún commit."
msgid "Your subscription expired!"
-msgstr ""
+msgstr "¡Su suscripción ha caducado!"
-msgid "Your subscription has been downgraded"
+msgid "Your subscription has been downgraded."
msgstr ""
-msgid "Your subscription will automatically renew in %{remaining_days}"
+msgid "Your subscription will automatically renew in %{remaining_days}."
msgstr ""
-msgid "Your subscription will expire in %{remaining_days}"
+msgid "Your subscription will expire in %{remaining_days}."
msgstr ""
msgid "Zoom meeting added"
@@ -25954,7 +26617,7 @@ msgid "Zoom meeting removed"
msgstr "Se ha eliminado la reunión de Zoom"
msgid "[No reason]"
-msgstr ""
+msgstr "[No hay razón]"
msgid "a deleted user"
msgstr "un usuario eliminado"
@@ -25968,7 +26631,7 @@ msgstr[0] "alrededor de 1 hora"
msgstr[1] "alrededor de %d horas"
msgid "activated"
-msgstr ""
+msgstr "activado"
msgid "added %{created_at_timeago}"
msgstr "añadido %{created_at_timeago}"
@@ -25980,13 +26643,13 @@ msgid "ago"
msgstr "hace"
msgid "alert"
-msgstr ""
+msgstr "alerta"
msgid "allowed to fail"
msgstr "permitido fallar"
msgid "already being used for another group or project %{timebox_name}."
-msgstr ""
+msgstr "ya se está utilizando para otro grupo o proyecto %{timebox_name}."
msgid "already has a \"created\" issue link"
msgstr ""
@@ -26010,7 +26673,7 @@ msgid "assign yourself"
msgstr "asignar a ti mismo"
msgid "at risk"
-msgstr ""
+msgstr "en riesgo"
msgid "attach a new file"
msgstr "adjuntar un nuevo archivo"
@@ -26019,19 +26682,16 @@ msgid "authored"
msgstr "autor"
msgid "blocks"
-msgstr ""
+msgstr "bloques"
msgid "branch name"
msgstr "nombre de la rama"
-msgid "build pipeline reference mismatch"
-msgstr ""
-
msgid "by"
msgstr "por"
msgid "by %{user}"
-msgstr ""
+msgstr "por %{user}"
msgid "cannot be changed if a personal project has container registry tags."
msgstr "no se puede modificar si un proyecto personal ya contiene etiquetas de registro de contenedor."
@@ -26045,6 +26705,9 @@ msgstr "no se puede modificar"
msgid "cannot block others"
msgstr "no puede bloquear a otros"
+msgid "cannot contain HTML/XML tags, including any word between angle brackets (<,>)."
+msgstr ""
+
msgid "cannot include leading slash or directory traversal."
msgstr ""
@@ -26075,55 +26738,6 @@ msgstr ""
msgid "ciReport|%{remainingPackagesCount} more"
msgstr "%{remainingPackagesCount} más"
-msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability"
-msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities"
-msgstr[0] "%{reportType} %{status} detectada %{dismissedCount} vulnerabilidad corregida"
-msgstr[1] "%{reportType} %{status} detectadsa %{dismissedCount} vulnerabilidades descartadas"
-
-msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability for the source branch only"
-msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities for the source branch only"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerability"
-msgid_plural "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerabilities"
-msgstr[0] "%{reportType} %{status} detectada %{fixedCount} vulnerabilidad corregida"
-msgstr[1] "%{reportType} %{status} detectado %{fixedCount} vulnerabilidad corregida"
-
-msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
-msgstr "%{reportType} %{status} detectadas %{fixedCount} arregladas, y %{dismissedCount} vulnerabilidades descartadas"
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new vulnerability"
-msgid_plural "ciReport|%{reportType} %{status} detected %{newCount} new vulnerabilities"
-msgstr[0] "%{reportType} %{status} detectada %{newCount} nueva vulnerabilidad"
-msgstr[1] "%{reportType} %{status} detectadas %{newCount} nuevas vulnerabilidades"
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
-msgstr "%{reportType} %{status} detectó %{newCount} vulnerabilidades nuevas, %{fixedCount} corregidas y %{dismissedCount} descartadas"
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities"
-msgstr "%{reportType} %{status} detectó %{newCount} nuevos, y descartó %{dismissedCount} vulnerabilidades"
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities for the source branch only"
-msgstr "%{reportType} %{status} detectó %{newCount} vulnerabilidades nuevas y corrigió %{dismissedCount} vulnerabilidades para la rama origen"
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{fixedCount} fixed vulnerabilities"
-msgstr "%{reportType} %{status} detectó %{newCount} vulnerabilidades nuevas y corrigió %{fixedCount}"
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} vulnerability for the source branch only"
-msgid_plural "ciReport|%{reportType} %{status} detected %{newCount} vulnerabilities for the source branch only"
-msgstr[0] "%{reportType} %{status} detectó %{newCount} vulnerabilidad para la rama fuente"
-msgstr[1] "%{reportType} %{status} detectó %{newCount} vulnerabilidades para la rama fuente"
-
-msgid "ciReport|%{reportType} %{status} detected no new vulnerabilities"
-msgstr "%{reportType} %{status} no detecto nuevas vulnerabilidades"
-
-msgid "ciReport|%{reportType} %{status} detected no vulnerabilities"
-msgstr "%{reportType} %{status} no detecto vulnerabilidades"
-
-msgid "ciReport|%{reportType} %{status} detected no vulnerabilities for the source branch only"
-msgstr "%{reportType} %{status} no detectó vulnerabilidades para la rama origen"
-
msgid "ciReport|%{reportType} is loading"
msgstr "%{reportType} está cargando"
@@ -26142,8 +26756,8 @@ msgstr "(está cargando, se han producido errores al cargar resultados)"
msgid "ciReport|All projects"
msgstr "Todos los proyectos"
-msgid "ciReport|All report types"
-msgstr "Todos los tipos de informes"
+msgid "ciReport|All scanner types"
+msgstr ""
msgid "ciReport|All severities"
msgstr "Todos los niveles de gravedad"
@@ -26166,6 +26780,9 @@ msgstr "Análisis de contenedores"
msgid "ciReport|Container scanning detects known vulnerabilities in your docker images."
msgstr "El análisis de contenedores detecta vulnerabilidades conocidas en sus imágenes de Docker."
+msgid "ciReport|Coverage Fuzzing"
+msgstr ""
+
msgid "ciReport|Create a merge request to implement this solution, or download and apply the patch manually."
msgstr "Cree un merge request para implementar esta solución, o descargue y aplique el parche manualmente."
@@ -26197,13 +26814,13 @@ msgid "ciReport|Failed to load %{reportName} report"
msgstr "Se ha producido un error al cargar el informe %{reportName}"
msgid "ciReport|Fixed"
-msgstr ""
+msgstr "Solucionado"
msgid "ciReport|Fixed:"
msgstr "Solucionado:"
msgid "ciReport|Found %{issuesWithCount}"
-msgstr ""
+msgstr "Encontrado %{issuesWithCount}"
msgid "ciReport|Investigate this vulnerability by creating an issue"
msgstr "Investigue esta vulnerabilidad creando una incidencia"
@@ -26218,7 +26835,7 @@ msgid "ciReport|Manage licenses"
msgstr "Administrar licencias"
msgid "ciReport|New"
-msgstr ""
+msgstr "Nuevo"
msgid "ciReport|No changes to code quality"
msgstr "No hay cambios en la calidad del código"
@@ -26227,7 +26844,7 @@ msgid "ciReport|No changes to performance metrics"
msgstr "No hay cambios en las métricas de rendimiento"
msgid "ciReport|No code quality issues found"
-msgstr ""
+msgstr "No se encontraron problemas de calidad de código"
msgid "ciReport|Performance metrics"
msgstr "Métricas de rendimiento"
@@ -26239,10 +26856,10 @@ msgid "ciReport|SAST"
msgstr "SAST"
msgid "ciReport|Secret Detection"
-msgstr ""
+msgstr "Detección de secretos"
msgid "ciReport|Secret scanning"
-msgstr ""
+msgstr "Escaneo de secretos"
msgid "ciReport|Secret scanning detects secrets and credentials vulnerabilities in your source code."
msgstr ""
@@ -26283,7 +26900,7 @@ msgid "ciReport|View full report"
msgstr "Ver informe completo"
msgid "closed issue"
-msgstr ""
+msgstr "incidencia cerrada"
msgid "comment"
msgstr "comentario"
@@ -26319,11 +26936,14 @@ msgid "created"
msgstr "creado"
msgid "created %{timeAgo}"
-msgstr ""
+msgstr "creado el %{timeAgo}"
msgid "customize"
msgstr "personalizar"
+msgid "data"
+msgstr ""
+
msgid "date must not be after 9999-12-31"
msgstr "la fecha no puede ser superior a 9999-12-31"
@@ -26360,7 +26980,7 @@ msgid "done"
msgstr "hecho"
msgid "download it"
-msgstr ""
+msgstr "descargarlo"
msgid "draft"
msgid_plural "drafts"
@@ -26394,6 +27014,9 @@ msgstr "las entradas no pueden estar vacías"
msgid "entries cannot contain HTML tags"
msgstr "las entradas no pueden contener etiquetas HTML"
+msgid "epic"
+msgstr ""
+
msgid "error"
msgstr "error"
@@ -26415,9 +27038,6 @@ msgstr ""
msgid "expires on %{timebox_due_date}"
msgstr ""
-msgid "external_url"
-msgstr ""
-
msgid "failed"
msgstr "fallido"
@@ -26426,8 +27046,8 @@ msgstr "no se puede descartar el hallazgo asociado (id =%{finding_id}): %{messag
msgid "file"
msgid_plural "files"
-msgstr[0] ""
-msgstr[1] ""
+msgstr[0] "archivo"
+msgstr[1] "archivos"
msgid "finding is not found or is already attached to a vulnerability"
msgstr ""
@@ -26453,14 +27073,11 @@ msgstr ""
msgid "from"
msgstr "de"
-msgid "geo_node_name"
-msgstr ""
-
msgid "group"
msgstr "grupo"
msgid "groups"
-msgstr ""
+msgstr "grupos"
msgid "has already been linked to another vulnerability"
msgstr "ya ha sido vinculado a otra vulnerabilidad"
@@ -26477,17 +27094,14 @@ msgstr "aquí"
msgid "https://your-bitbucket-server"
msgstr "https://tu-servidor-bitbucket"
-msgid "image"
-msgstr ""
-
msgid "image diff"
msgstr "imagen dif"
msgid "impersonation token"
-msgstr ""
+msgstr "token de suplantación"
msgid "impersonation tokens"
-msgstr ""
+msgstr "tokens de suplantación"
msgid "import flow"
msgstr "flujo de importación"
@@ -26513,7 +27127,7 @@ msgid "invalid milestone state `%{state}`"
msgstr "estado del hito invalido '%{state}'"
msgid "is"
-msgstr ""
+msgstr "es"
msgid "is already associated to a GitLab Issue. New issue will not be associated."
msgstr ""
@@ -26534,10 +27148,10 @@ msgid "is invalid because there is upstream lock"
msgstr "no es válido porque hay un bloqueo en sentido ascendente"
msgid "is not"
-msgstr ""
+msgstr "no es"
msgid "is not a descendant of the Group owning the template"
-msgstr ""
+msgstr "no es un descendiente del grupo que es propietario de la plantilla"
msgid "is not a valid X509 certificate."
msgstr "no es un certificado X509 válido."
@@ -26552,7 +27166,7 @@ msgid "is not in the group enforcing Group Managed Account"
msgstr ""
msgid "is read only"
-msgstr ""
+msgstr "es de solo lectura"
msgid "is too long (%{current_value}). The maximum size is %{max_size}."
msgstr "es demasiado largo (%{current_value}). El tamaño máximo permitido es de %{max_size}."
@@ -26567,19 +27181,19 @@ msgid "issue"
msgstr "incidencia"
msgid "issues at risk"
-msgstr ""
+msgstr "incidencias en riesgo"
msgid "issues need attention"
-msgstr ""
+msgstr "incidencias que requieren atención"
msgid "issues on track"
-msgstr ""
+msgstr "incidencias en tiempo"
msgid "it is larger than %{limit}"
-msgstr ""
+msgstr "es mayor que %{limit}"
msgid "it is stored as a job artifact"
-msgstr ""
+msgstr "se almacena como un artefacto de trabajo"
msgid "it is stored externally"
msgstr "es almacenado externamente"
@@ -26623,6 +27237,9 @@ msgstr "límite de %{project_limit} alcanzado"
msgid "load it anyway"
msgstr ""
+msgid "loading"
+msgstr "cargando"
+
msgid "locked by %{path_lock_user_name} %{created_at}"
msgstr "bloqueado por %{path_lock_user_name} %{created_at}"
@@ -26751,9 +27368,6 @@ msgstr "Eliminar la rama de origen"
msgid "mrWidget|Deployment statistics are not available currently"
msgstr "Las estadísticas de los despliegues no están disponibles actualmente"
-msgid "mrWidget|Detect issues before deployment with a CI pipeline that continuously tests your code. We created a quick guide that will show you how to create one. Make your code more secure and more robust in just a minute."
-msgstr ""
-
msgid "mrWidget|Did not close"
msgstr "No se cerró"
@@ -26902,7 +27516,7 @@ msgid "mrWidget|This merge request failed to be merged automatically"
msgstr "Esta merge request no se realizó automáticamente"
msgid "mrWidget|This merge request is in the process of being merged"
-msgstr "Este mergre request está en proceso de ser realizado"
+msgstr "Este merge request está en proceso de ser realizado"
msgid "mrWidget|This merge request will be added to the merge train when pipeline %{linkStart}#%{pipelineId}%{linkEnd} succeeds."
msgstr ""
@@ -26916,6 +27530,9 @@ msgstr "Este proyecto está archivado, se ha deshabilitado el acceso de escritur
msgid "mrWidget|To approve this merge request, please enter your password. This project requires all approvals to be authenticated."
msgstr "para aprobar este merge request, por favor introduzca su contraseña. Este proyecto requiere de todas las aprobaciones para ser autenticado."
+msgid "mrWidget|Use %{linkStart}CI pipelines to test your code%{linkEnd}, simply add a GitLab CI configuration file to your project. It only takes a minute to make your code more secure and robust."
+msgstr ""
+
msgid "mrWidget|When this merge request is ready, remove the WIP: prefix from the title to allow it to be merged"
msgstr "cuando este merge request esté lista, elimine el prefijo WIP: del título para permitir su fusionado"
@@ -26952,18 +27569,24 @@ msgstr ""
msgid "must be greater than start date"
msgstr "debe ser mayor que la fecha de inicio"
+msgid "must contain only valid frameworks"
+msgstr "debe contener sólo entornos de trabajo válidos"
+
msgid "my-awesome-group"
-msgstr ""
+msgstr "mi-asombroso-grupo"
msgid "n/a"
msgstr "n/a"
msgid "need attention"
-msgstr ""
+msgstr "necesita atención"
msgid "needs to be between 10 minutes and 1 month"
msgstr "el intervalo debe estar comprendido entre 10 minutos y 1 mes"
+msgid "never"
+msgstr "nunca"
+
msgid "never expires"
msgstr "nunca caduca"
@@ -26971,10 +27594,10 @@ msgid "new merge request"
msgstr "nueva solicitud de fusión"
msgid "no contributions"
-msgstr "sin contribuciones"
+msgstr "sin colaboraciones"
msgid "no expiration"
-msgstr ""
+msgstr "Sin fecha de caducidad"
msgid "no one can merge"
msgstr "nadie puede hacer merge"
@@ -26991,6 +27614,9 @@ msgstr "correos electrónicos de notificación"
msgid "nounSeries|%{firstItem} and %{lastItem}"
msgstr "%{firstItem}, y %{lastItem}"
+msgid "nounSeries|%{item}"
+msgstr "%{item}"
+
msgid "nounSeries|%{item}, %{nextItem}"
msgstr "%{item}, %{nextItem}"
@@ -26998,10 +27624,10 @@ msgid "nounSeries|%{item}, and %{lastItem}"
msgstr "%{item}, y %{lastItem}"
msgid "on track"
-msgstr ""
+msgstr "a tiempo"
msgid "open issue"
-msgstr ""
+msgstr "incidencia abierta"
msgid "opened %{timeAgoString} by %{user}"
msgstr "abierto el %{timeAgoString} por %{user}"
@@ -27032,13 +27658,13 @@ msgid "pending removal"
msgstr "pendiente de eliminación"
msgid "per day"
-msgstr ""
+msgstr "por día."
msgid "personal access token"
-msgstr ""
+msgstr "token de acceso personal"
msgid "personal access tokens"
-msgstr ""
+msgstr "tokens de acceso personal"
msgid "pipeline"
msgstr "pipeline"
@@ -27069,16 +27695,16 @@ msgstr[0] "proyecto"
msgstr[1] "proyectos"
msgid "project access token"
-msgstr ""
+msgstr "token de acceso al proyecto"
msgid "project access tokens"
-msgstr ""
+msgstr "tokens de acceso al proyecto"
msgid "project avatar"
msgstr "avatar del proyecto"
msgid "projects"
-msgstr ""
+msgstr "proyectos"
msgid "push to your repository, create pipelines, create issues or add comments. To reduce storage capacity, delete unused repositories, artifacts, wikis, issues, and pipelines."
msgstr ""
@@ -27125,7 +27751,10 @@ msgid "resolved the corresponding error and closed the issue."
msgstr "resolvió el error correspondiente y cerró la incidencia."
msgid "revised"
-msgstr ""
+msgstr "revisado"
+
+msgid "satisfied"
+msgstr "satisfecho"
msgid "score"
msgstr "puntuación"
@@ -27157,6 +27786,9 @@ msgstr "Ninguna"
msgid "severity|Unknown"
msgstr "Desconocida"
+msgid "should be an array of %{object_name} objects"
+msgstr ""
+
msgid "should be greater than or equal to %{access} inherited membership from group %{group_name}"
msgstr "debe ser mayor o igual que %{access} el nivel de acceso heredado del grupo %{group_name}"
@@ -27194,10 +27826,10 @@ msgid "started a discussion on %{design_link}"
msgstr "inició una discusión en %{design_link}"
msgid "started on %{timebox_start_date}"
-msgstr ""
+msgstr "comenzó el %{timebox_start_date}"
msgid "starts on %{timebox_start_date}"
-msgstr ""
+msgstr "comienza el %{timebox_start_date}"
msgid "stuck"
msgstr "bloqueado"
@@ -27206,15 +27838,18 @@ msgid "success"
msgstr "éxito"
msgid "suggestPipeline|1/2: Choose a template"
-msgstr ""
+msgstr "1/2: Seleccione una plantilla"
msgid "suggestPipeline|2/2: Commit your changes"
msgstr ""
-msgid "suggestPipeline|Commit the changes and your pipeline will automatically run for the first time."
+msgid "suggestPipeline|Choose %{boldStart}Code Quality%{boldEnd} to add a pipeline that tests the quality of your code."
msgstr ""
-msgid "suggestPipeline|We recommend the %{boldStart}Code Quality%{boldEnd} template, which will add a report widget to your Merge Requests. This way you’ll learn about code quality degradations much sooner. %{footerStart} Goodbye technical debt! %{footerEnd}"
+msgid "suggestPipeline|The template is ready! You can now commit it to create your first pipeline."
+msgstr ""
+
+msgid "suggestPipeline|We’re adding a GitLab CI configuration file to add a pipeline to the project. You could create it manually, but we recommend that you start with a GitLab template that works out of the box."
msgstr ""
msgid "syntax is correct"
@@ -27254,16 +27889,16 @@ msgid "updated"
msgstr "actualizado"
msgid "updated %{timeAgo}"
-msgstr ""
+msgstr "actualizado %{timeAgo}"
msgid "updated %{time_ago}"
msgstr "actualizado %{time_ago}"
msgid "uploaded"
-msgstr ""
+msgstr "subido"
msgid "uploads"
-msgstr ""
+msgstr "subidos"
msgid "user avatar"
msgstr "avatar del usuario"
@@ -27274,6 +27909,9 @@ msgstr "usuario"
msgid "uses Kubernetes clusters to deploy your code!"
msgstr "¡Utilizar los clústeres de Kubernetes para implementar tu código!"
+msgid "v%{version} published %{timeAgo}"
+msgstr ""
+
msgid "verify ownership"
msgstr "verificar el propietario"
@@ -27293,10 +27931,10 @@ msgid "view the blob"
msgstr "ver el blob"
msgid "view the source"
-msgstr ""
+msgstr "ver el código fuente"
msgid "vulnerability|Add a comment"
-msgstr ""
+msgstr "Añadir un comentario"
msgid "vulnerability|Add a comment or reason for dismissal"
msgstr "Añadir un comentario o motivo para el descarte"
@@ -27320,10 +27958,10 @@ msgid "vulnerability|dismissed"
msgstr "Descartado"
msgid "wiki page"
-msgstr ""
+msgstr "la página del wiki"
msgid "will be released %{time}"
-msgstr ""
+msgstr "será liberado %{time}"
msgid "with %{additions} additions, %{deletions} deletions."
msgstr "con %{additions} adiciones, %{deletions} eliminaciones."
diff --git a/locale/et_EE/gitlab.po b/locale/et_EE/gitlab.po
index cf8248f2989..fb2e5f96cce 100644
--- a/locale/et_EE/gitlab.po
+++ b/locale/et_EE/gitlab.po
@@ -2,7 +2,7 @@ msgid ""
msgstr ""
"Project-Id-Version: gitlab-ee\n"
"Report-Msgid-Bugs-To: \n"
-"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Last-Translator: \n"
"Language-Team: Estonian\n"
"Language: et_EE\n"
"MIME-Version: 1.0\n"
@@ -10,9 +10,11 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Crowdin-Project: gitlab-ee\n"
+"X-Crowdin-Project-ID: 288872\n"
"X-Crowdin-Language: et\n"
"X-Crowdin-File: /master/locale/gitlab.pot\n"
-"PO-Revision-Date: 2020-06-08 15:08\n"
+"X-Crowdin-File-ID: 6\n"
+"PO-Revision-Date: 2020-07-02 01:21\n"
msgid " %{start} to %{end}"
msgstr ""
@@ -197,6 +199,11 @@ msgid_plural "%d metrics"
msgstr[0] ""
msgstr[1] ""
+msgid "%d milestone"
+msgid_plural "%d milestones"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "%d minute"
msgid_plural "%d minutes"
msgstr[0] ""
@@ -242,11 +249,6 @@ msgid_plural "%d unresolved threads"
msgstr[0] ""
msgstr[1] ""
-msgid "%d url scanned"
-msgid_plural "%d urls scanned"
-msgstr[0] ""
-msgstr[1] ""
-
msgid "%d vulnerability"
msgid_plural "%d vulnerabilities"
msgstr[0] ""
@@ -324,9 +326,15 @@ msgstr[1] ""
msgid "%{count} related %{pluralized_subject}: %{links}"
msgstr ""
+msgid "%{dashboard_path} could not be found."
+msgstr ""
+
msgid "%{days} days until tags are automatically removed"
msgstr ""
+msgid "%{deployLinkStart}Use a template to deploy to ECS%{deployLinkEnd}, or use a docker image to %{commandsLinkStart}run AWS commands in GitLab CI/CD%{commandsLinkEnd}."
+msgstr ""
+
msgid "%{description}- Sentry event: %{errorUrl}- First seen: %{firstSeen}- Last seen: %{lastSeen} %{countLabel}: %{count}%{userCountLabel}: %{userCount}"
msgstr ""
@@ -369,12 +377,18 @@ msgstr ""
msgid "%{host} sign-in from new location"
msgstr ""
-msgid "%{icon}You are about to add %{usersTag} people to the discussion. Proceed with caution."
+msgid "%{icon}You are about to add %{usersTag} people to the discussion. They will all receive a notification."
+msgstr ""
+
+msgid "%{integrations_link_start}Integrations%{link_end} enable you to make third-party applications part of your GitLab workflow. If the available integrations don't meet your needs, consider using a %{webhooks_link_start}webhook%{link_end}."
msgstr ""
msgid "%{issuableType} will be removed! Are you sure?"
msgstr ""
+msgid "%{issuesCount} issues in this group"
+msgstr ""
+
msgid "%{issuesSize} issues"
msgstr ""
@@ -384,7 +398,7 @@ msgstr ""
msgid "%{labelStart}Class:%{labelEnd} %{class}"
msgstr ""
-msgid "%{labelStart}Confidence:%{labelEnd} %{confidence}"
+msgid "%{labelStart}Evidence:%{labelEnd} %{evidence}"
msgstr ""
msgid "%{labelStart}File:%{labelEnd} %{file}"
@@ -402,6 +416,9 @@ msgstr ""
msgid "%{labelStart}Report Type:%{labelEnd} %{reportType}"
msgstr ""
+msgid "%{labelStart}Scanner:%{labelEnd} %{scanner}"
+msgstr ""
+
msgid "%{labelStart}Severity:%{labelEnd} %{severity}"
msgstr ""
@@ -426,9 +443,6 @@ msgstr ""
msgid "%{link_start}Read more%{link_end} about role permissions"
msgstr ""
-msgid "%{link} can be used for binding events when something is happening within the project."
-msgstr ""
-
msgid "%{listToShow}, and %{awardsListLength} more."
msgstr ""
@@ -462,6 +476,9 @@ msgstr ""
msgid "%{name} found %{resultsString}"
msgstr ""
+msgid "%{name} is already being used for another emoji"
+msgstr ""
+
msgid "%{name} is scheduled for %{action}"
msgstr ""
@@ -509,6 +526,9 @@ msgstr ""
msgid "%{primary} (%{secondary})"
msgstr ""
+msgid "%{ref} cannot be added: %{error}"
+msgstr ""
+
msgid "%{releases} release"
msgid_plural "%{releases} releases"
msgstr[0] ""
@@ -517,13 +537,37 @@ msgstr[1] ""
msgid "%{remaining_approvals} left"
msgstr ""
-msgid "%{retryButtonStart}Try again%{retryButtonEnd} or %{newFileButtonStart}attach a new file%{newFileButtonEnd}"
+msgid "%{reportType} %{status} detected %{critical} critical and %{high} high severity vulnerabilities out of %{total}."
+msgstr ""
+
+msgid "%{reportType} %{status} detected %{critical} critical and %{high} high severity vulnerabilities."
+msgstr ""
+
+msgid "%{reportType} %{status} detected %{critical} critical severity vulnerabilities out of %{total}."
+msgstr ""
+
+msgid "%{reportType} %{status} detected %{critical} critical severity vulnerability."
+msgid_plural "%{reportType} %{status} detected %{critical} critical severity vulnerabilities."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{reportType} %{status} detected %{high} high severity vulnerabilities out of %{total}."
msgstr ""
-msgid "%{screenreaderOnlyStart}Keyboard shorcuts%{screenreaderOnlyEnd} Disabled"
+msgid "%{reportType} %{status} detected %{high} high severity vulnerability."
+msgid_plural "%{reportType} %{status} detected %{high} high severity vulnerabilities."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{reportType} %{status} detected %{other} vulnerability."
+msgid_plural "%{reportType} %{status} detected %{other} vulnerabilities."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{reportType} %{status} detected no new vulnerabilities."
msgstr ""
-msgid "%{screenreaderOnlyStart}Keyboard shorcuts%{screenreaderOnlyEnd} Enabled"
+msgid "%{retryButtonStart}Try again%{retryButtonEnd} or %{newFileButtonStart}attach a new file%{newFileButtonEnd}"
msgstr ""
msgid "%{service_title} %{message}."
@@ -620,6 +664,12 @@ msgstr ""
msgid "%{token}..."
msgstr ""
+msgid "%{totalCpu} (%{freeSpacePercentage}%{percentSymbol} free)"
+msgstr ""
+
+msgid "%{totalMemory} (%{freeSpacePercentage}%{percentSymbol} free)"
+msgstr ""
+
msgid "%{totalWeight} total weight"
msgstr ""
@@ -650,6 +700,12 @@ msgstr ""
msgid "%{verb} %{time_spent_value} spent time."
msgstr ""
+msgid "%{webhooks_link_start}%{webhook_type}%{link_end} enable you to send notifications to web applications in response to events in a group or project."
+msgstr ""
+
+msgid "%{webhooks_link_start}%{webhook_type}%{link_end} enable you to send notifications to web applications in response to events in a group or project. We recommend using an %{integrations_link_start}integration%{link_end} in preference to a webhook."
+msgstr ""
+
msgid "'%{level}' is not a valid visibility level"
msgstr ""
@@ -676,9 +732,6 @@ msgstr ""
msgid "(No changes)"
msgstr ""
-msgid "(Show all)"
-msgstr ""
-
msgid "(check progress)"
msgstr ""
@@ -745,6 +798,9 @@ msgstr ""
msgid "0 for unlimited"
msgstr ""
+msgid "0 for unlimited, only effective with remote storage enabled."
+msgstr ""
+
msgid "1 %{type} addition"
msgid_plural "%{count} %{type} additions"
msgstr[0] ""
@@ -892,6 +948,12 @@ msgstr ""
msgid "<code>\"johnsmith@example.com\": \"johnsmith@example.com\"</code> will add \"By <a href=\"#\">johnsmith@example.com</a>\" to all issues and comments originally created by johnsmith@example.com. By default, the email address or username is masked to ensure the user's privacy. Use this option if you want to show the full email address."
msgstr ""
+msgid "<code>Masked</code> to prevent the values from being displayed in job logs (must match certain regexp requirements)."
+msgstr ""
+
+msgid "<code>Protected</code> to expose them to protected branches or tags only."
+msgstr ""
+
msgid "<namespace / project>"
msgstr ""
@@ -919,9 +981,6 @@ msgstr ""
msgid "A .NET Core console application template, customizable for any .NET Core project"
msgstr ""
-msgid "A DAG must have two dependent jobs to be visualized on this tab."
-msgstr ""
-
msgid "A GitBook site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
msgstr ""
@@ -952,6 +1011,12 @@ msgstr ""
msgid "A deleted user"
msgstr ""
+msgid "A file has been changed."
+msgstr ""
+
+msgid "A file was not found."
+msgstr ""
+
msgid "A file with '%{file_name}' already exists in %{branch} branch"
msgstr ""
@@ -1018,7 +1083,7 @@ msgstr ""
msgid "A subscription will trigger a new pipeline on the default branch of this project when a pipeline successfully completes for a new tag on the %{default_branch_docs} of the subscribed project."
msgstr ""
-msgid "A terraform report was generated in your pipelines."
+msgid "A suggestion is not applicable."
msgstr ""
msgid "A user with write access to the source branch selected this option"
@@ -1189,6 +1254,9 @@ msgstr ""
msgid "Action to take when receiving an alert."
msgstr ""
+msgid "Actions"
+msgstr ""
+
msgid "Activate"
msgstr ""
@@ -1245,6 +1313,9 @@ msgstr ""
msgid "Add LICENSE"
msgstr ""
+msgid "Add New Node"
+msgstr ""
+
msgid "Add README"
msgstr ""
@@ -1320,6 +1391,9 @@ msgstr ""
msgid "Add approval rule"
msgstr ""
+msgid "Add approvers"
+msgstr ""
+
msgid "Add bold text"
msgstr ""
@@ -1377,6 +1451,9 @@ msgstr ""
msgid "Add strikethrough text"
msgstr ""
+msgid "Add suggestion to batch"
+msgstr ""
+
msgid "Add system hook"
msgstr ""
@@ -1476,6 +1553,9 @@ msgstr ""
msgid "Admin Note"
msgstr ""
+msgid "Admin Notifications"
+msgstr ""
+
msgid "Admin Overview"
msgstr ""
@@ -1587,6 +1667,9 @@ msgstr ""
msgid "AdminSettings|Integrations configured here will automatically apply to all projects on this instance."
msgstr ""
+msgid "AdminSettings|Moved to integrations"
+msgstr ""
+
msgid "AdminSettings|No required pipeline"
msgstr ""
@@ -1817,12 +1900,18 @@ msgid_plural "Alerts"
msgstr[0] ""
msgstr[1] ""
+msgid "AlertManagement| assign yourself"
+msgstr ""
+
msgid "AlertManagement|Acknowledged"
msgstr ""
msgid "AlertManagement|Alert"
msgstr ""
+msgid "AlertManagement|Alert assignee(s): %{assignees}"
+msgstr ""
+
msgid "AlertManagement|Alert detail"
msgstr ""
@@ -1838,9 +1927,15 @@ msgstr ""
msgid "AlertManagement|All alerts"
msgstr ""
+msgid "AlertManagement|Assign To"
+msgstr ""
+
msgid "AlertManagement|Assign status"
msgstr ""
+msgid "AlertManagement|Assignee"
+msgstr ""
+
msgid "AlertManagement|Assignees"
msgstr ""
@@ -1859,9 +1954,6 @@ msgstr ""
msgid "AlertManagement|Edit"
msgstr ""
-msgid "AlertManagement|End time"
-msgstr ""
-
msgid "AlertManagement|Events"
msgstr ""
@@ -1880,10 +1972,13 @@ msgstr ""
msgid "AlertManagement|More information"
msgstr ""
+msgid "AlertManagement|No Matching Results"
+msgstr ""
+
msgid "AlertManagement|No alert data to display."
msgstr ""
-msgid "AlertManagement|No alerts available to display. If you think you're seeing this message in error, refresh the page."
+msgid "AlertManagement|No alerts available to display. See %{linkStart}enabling alert management%{linkEnd} for more information on adding alerts to the list."
msgstr ""
msgid "AlertManagement|No alerts to display."
@@ -1892,6 +1987,9 @@ msgstr ""
msgid "AlertManagement|None"
msgstr ""
+msgid "AlertManagement|None -"
+msgstr ""
+
msgid "AlertManagement|Open"
msgstr ""
@@ -1928,9 +2026,18 @@ msgstr ""
msgid "AlertManagement|There was an error displaying the alerts. Confirm your endpoint's configuration details to ensure alerts appear."
msgstr ""
+msgid "AlertManagement|There was an error while updating the assignee(s) list. Please try again."
+msgstr ""
+
+msgid "AlertManagement|There was an error while updating the assignee(s) of the alert. Please try again."
+msgstr ""
+
msgid "AlertManagement|There was an error while updating the status of the alert. Please try again."
msgstr ""
+msgid "AlertManagement|This assignee cannot be assigned to this alert."
+msgstr ""
+
msgid "AlertManagement|Tool"
msgstr ""
@@ -2078,6 +2185,9 @@ msgstr ""
msgid "Allow users to request access (if visibility is public or internal)"
msgstr ""
+msgid "Allowed Geo IP"
+msgstr ""
+
msgid "Allowed email domain restriction only permitted for top-level groups"
msgstr ""
@@ -2111,6 +2221,9 @@ msgstr ""
msgid "Amazon Web Services"
msgstr ""
+msgid "Amazon Web Services Logo"
+msgstr ""
+
msgid "Amazon authentication is not %{link_start}correctly configured%{link_end}. Ask your GitLab administrator if you want to use this service."
msgstr ""
@@ -2168,10 +2281,16 @@ msgstr ""
msgid "An error occurred when updating the issue weight"
msgstr ""
+msgid "An error occurred while acknowledging the notification. Refresh the page and try again."
+msgstr ""
+
+msgid "An error occurred while adding approvers"
+msgstr ""
+
msgid "An error occurred while adding formatted title for epic"
msgstr ""
-msgid "An error occurred while checking group path"
+msgid "An error occurred while checking group path. Please refresh and try again."
msgstr ""
msgid "An error occurred while committing your changes."
@@ -2315,7 +2434,7 @@ msgstr ""
msgid "An error occurred while loading milestones"
msgstr ""
-msgid "An error occurred while loading terraform report"
+msgid "An error occurred while loading project creation UI"
msgstr ""
msgid "An error occurred while loading the data. Please try again."
@@ -2369,6 +2488,9 @@ msgstr ""
msgid "An error occurred while rendering preview broadcast message"
msgstr ""
+msgid "An error occurred while rendering the editor"
+msgstr ""
+
msgid "An error occurred while reordering issues."
msgstr ""
@@ -2465,7 +2587,7 @@ msgstr ""
msgid "Analyze your dependencies for known vulnerabilities."
msgstr ""
-msgid "Analyze your source code and git history for secrets"
+msgid "Analyze your source code and git history for secrets."
msgstr ""
msgid "Analyze your source code for known vulnerabilities."
@@ -2579,12 +2701,18 @@ msgstr ""
msgid "Apply suggestion"
msgstr ""
+msgid "Apply suggestions"
+msgstr ""
+
msgid "Apply template"
msgstr ""
msgid "Apply this approval rule to any branch or a specific protected branch."
msgstr ""
+msgid "Applying"
+msgstr ""
+
msgid "Applying a template will replace the existing issue description. Any changes you have made will be lost."
msgstr ""
@@ -2597,12 +2725,18 @@ msgstr ""
msgid "Applying multiple commands"
msgstr ""
-msgid "Applying suggestion"
+msgid "Applying suggestion..."
+msgstr ""
+
+msgid "Applying suggestions..."
msgstr ""
msgid "Approval rules"
msgstr ""
+msgid "Approval rules reset to project defaults"
+msgstr ""
+
msgid "ApprovalRuleRemove|%d member"
msgid_plural "ApprovalRuleRemove|%d members"
msgstr[0] ""
@@ -2656,6 +2790,9 @@ msgstr ""
msgid "Approved"
msgstr ""
+msgid "Approved MRs"
+msgstr ""
+
msgid "Approved by: "
msgstr ""
@@ -2869,6 +3006,9 @@ msgstr ""
msgid "Assign"
msgstr ""
+msgid "Assign Iteration"
+msgstr ""
+
msgid "Assign custom color like #FF0000"
msgstr ""
@@ -2887,6 +3027,9 @@ msgstr ""
msgid "Assign to"
msgstr ""
+msgid "Assign to commenting user"
+msgstr ""
+
msgid "Assign yourself to these issues"
msgstr ""
@@ -2913,6 +3056,9 @@ msgid_plural "%d Assignees"
msgstr[0] ""
msgstr[1] ""
+msgid "Assignee has no permissions"
+msgstr ""
+
msgid "Assignee lists not available with your current license"
msgstr ""
@@ -2922,6 +3068,9 @@ msgstr ""
msgid "Assignee(s)"
msgstr ""
+msgid "Assignees"
+msgstr ""
+
msgid "Assigns %{assignee_users_sentence}."
msgstr ""
@@ -2957,6 +3106,9 @@ msgstr ""
msgid "Audit Events is a way to keep track of important events that happened in GitLab."
msgstr ""
+msgid "Audit Log"
+msgstr ""
+
msgid "AuditEvents|(removed)"
msgstr ""
@@ -2993,6 +3145,9 @@ msgstr ""
msgid "AuditLogs|IP Address"
msgstr ""
+msgid "AuditLogs|Member Events"
+msgstr ""
+
msgid "AuditLogs|No matching %{type} found."
msgstr ""
@@ -3275,9 +3430,6 @@ msgstr ""
msgid "Badges|e.g. %{exampleUrl}"
msgstr ""
-msgid "Badge|New"
-msgstr ""
-
msgid "Balsamiq file could not be loaded."
msgstr ""
@@ -3326,6 +3478,12 @@ msgstr ""
msgid "Below you will find all the groups that are public."
msgstr ""
+msgid "Beta"
+msgstr ""
+
+msgid "Bi-weekly code coverage"
+msgstr ""
+
msgid "Billing"
msgstr ""
@@ -3386,9 +3544,15 @@ msgstr ""
msgid "Bitbucket Server Import"
msgstr ""
+msgid "Bitbucket Server import"
+msgstr ""
+
msgid "Bitbucket import"
msgstr ""
+msgid "Blame"
+msgstr ""
+
msgid "Blocked"
msgstr ""
@@ -3401,9 +3565,6 @@ msgstr ""
msgid "Blog"
msgstr ""
-msgid "Blue helpers indicate an action to be taken."
-msgstr ""
-
msgid "Board name"
msgstr ""
@@ -3602,6 +3763,9 @@ msgstr ""
msgid "Broadcast Message was successfully updated."
msgstr ""
+msgid "Broadcast Messages"
+msgstr ""
+
msgid "Browse Directory"
msgstr ""
@@ -3656,6 +3820,9 @@ msgstr ""
msgid "By %{user_name}"
msgstr ""
+msgid "By URL"
+msgstr ""
+
msgid "By default GitLab sends emails in HTML and plain text formats so mail clients can choose what format to use. Disable this option if you only want to send emails in plain text format."
msgstr ""
@@ -3761,6 +3928,9 @@ msgstr ""
msgid "Can override approvers and approvals required per merge request"
msgstr ""
+msgid "Can't apply as this line has changed or the suggestion already matches its content."
+msgstr ""
+
msgid "Can't create snippet: %{err}"
msgstr ""
@@ -3947,9 +4117,6 @@ msgstr ""
msgid "Changes are still tracked. Useful for cluster/index migrations."
msgstr ""
-msgid "Changes are unknown"
-msgstr ""
-
msgid "Changes suppressed. Click to show."
msgstr ""
@@ -4256,15 +4423,9 @@ msgstr ""
msgid "Choose what content you want to see on a group’s overview page"
msgstr ""
-msgid "Choose which groups you wish to synchronize to this secondary node"
-msgstr ""
-
msgid "Choose which repositories you want to connect and run CI/CD pipelines."
msgstr ""
-msgid "Choose which shards you wish to synchronize to this secondary node"
-msgstr ""
-
msgid "Choose your framework"
msgstr ""
@@ -4562,9 +4723,15 @@ msgstr ""
msgid "Cluster does not exist"
msgstr ""
+msgid "Cluster is required for Stages::ClusterEndpointInserter"
+msgstr ""
+
msgid "Cluster level"
msgstr ""
+msgid "Cluster type must be specificed for Stages::ClusterEndpointInserter"
+msgstr ""
+
msgid "ClusterIntegration| %{custom_domain_start}More information%{custom_domain_end}."
msgstr ""
@@ -4676,7 +4843,7 @@ msgstr ""
msgid "ClusterIntegration|Cert-Manager"
msgstr ""
-msgid "ClusterIntegration|Cert-Manager is a native Kubernetes certificate management controller that helps with issuing certificates. Installing Cert-Manager on your cluster will issue a certificate by %{letsEncrypt} and ensure that certificates are valid and up-to-date."
+msgid "ClusterIntegration|Cert-Manager is a native Kubernetes certificate management controller that helps with issuing certificates. Installing Cert-Manager on your cluster will issue a certificate by %{linkStart}Let's Encrypt%{linkEnd} and ensure that certificates are valid and up-to-date."
msgstr ""
msgid "ClusterIntegration|Certificate Authority bundle (PEM format)"
@@ -4703,9 +4870,6 @@ msgstr ""
msgid "ClusterIntegration|Clear the local cache of namespace and service accounts. This is necessary if your integration has become out of sync. The cache is repopulated during the next CI job that requires namespace and service accounts."
msgstr ""
-msgid "ClusterIntegration|Cloud Run"
-msgstr ""
-
msgid "ClusterIntegration|Cluster being created"
msgstr ""
@@ -4790,7 +4954,7 @@ msgstr ""
msgid "ClusterIntegration|Crossplane"
msgstr ""
-msgid "ClusterIntegration|Crossplane enables declarative provisioning of managed services from your cloud of choice using %{codeStart}kubectl%{codeEnd} or %{gitlabIntegrationLink}. Crossplane runs inside your Kubernetes cluster and supports secure connectivity and secrets management between app containers and the cloud services they depend on."
+msgid "ClusterIntegration|Crossplane enables declarative provisioning of managed services from your cloud of choice using %{codeStart}kubectl%{codeEnd} or %{linkStart}GitLab Integration%{linkEnd}. Crossplane runs inside your Kubernetes cluster and supports secure connectivity and secrets management between app containers and the cloud services they depend on."
msgstr ""
msgid "ClusterIntegration|Deletes all GitLab resources attached to this cluster during removal"
@@ -4874,9 +5038,6 @@ msgstr ""
msgid "ClusterIntegration|GitLab-managed cluster"
msgstr ""
-msgid "ClusterIntegration|Gitlab Integration"
-msgstr ""
-
msgid "ClusterIntegration|Global default"
msgstr ""
@@ -4922,7 +5083,7 @@ msgstr ""
msgid "ClusterIntegration|Ingress gives you a way to route requests to services based on the request host or path, centralizing a number of services into a single entrypoint."
msgstr ""
-msgid "ClusterIntegration|Installing Ingress may incur additional costs. Learn more about %{pricingLink}."
+msgid "ClusterIntegration|Installing Ingress may incur additional costs. Learn more about %{linkStart}pricing%{linkEnd}."
msgstr ""
msgid "ClusterIntegration|Instance cluster"
@@ -4937,7 +5098,7 @@ msgstr ""
msgid "ClusterIntegration|Issuer Email"
msgstr ""
-msgid "ClusterIntegration|Issuers represent a certificate authority. You must provide an email address for your Issuer. "
+msgid "ClusterIntegration|Issuers represent a certificate authority. You must provide an email address for your Issuer."
msgstr ""
msgid "ClusterIntegration|Jupyter Hostname"
@@ -5009,9 +5170,6 @@ msgstr ""
msgid "ClusterIntegration|Learn more about instance Kubernetes clusters"
msgstr ""
-msgid "ClusterIntegration|Let's Encrypt"
-msgstr ""
-
msgid "ClusterIntegration|Loading IAM Roles"
msgstr ""
@@ -5123,7 +5281,7 @@ msgstr ""
msgid "ClusterIntegration|Prometheus"
msgstr ""
-msgid "ClusterIntegration|Prometheus is an open-source monitoring system with %{gitlabIntegrationLink} to monitor deployed applications."
+msgid "ClusterIntegration|Prometheus is an open-source monitoring system with %{linkStart}GitLab Integration%{linkEnd} to monitor deployed applications."
msgstr ""
msgid "ClusterIntegration|Provider details"
@@ -5279,10 +5437,10 @@ msgstr ""
msgid "ClusterIntegration|Select zone to choose machine type"
msgstr ""
-msgid "ClusterIntegration|Send Cilium Logs"
+msgid "ClusterIntegration|Send Container Network Policies Logs"
msgstr ""
-msgid "ClusterIntegration|Send ModSecurity Logs"
+msgid "ClusterIntegration|Send Web Application Firewall Logs"
msgstr ""
msgid "ClusterIntegration|Service Token"
@@ -5450,15 +5608,12 @@ msgstr ""
msgid "ClusterIntegration|documentation"
msgstr ""
-msgid "ClusterIntegration|installed via %{installed_via}"
+msgid "ClusterIntegration|installed via %{linkStart}Cloud Run%{linkEnd}"
msgstr ""
msgid "ClusterIntegration|meets the requirements"
msgstr ""
-msgid "ClusterIntegration|pricing"
-msgstr ""
-
msgid "ClusterIntegration|sign up"
msgstr ""
@@ -5495,6 +5650,15 @@ msgstr ""
msgid "Code"
msgstr ""
+msgid "Code Coverage: %{coveragePercentage}%{percentSymbol}"
+msgstr ""
+
+msgid "Code Coverage| Empty code coverage data"
+msgstr ""
+
+msgid "Code Coverage|Couldn't fetch the code coverage data"
+msgstr ""
+
msgid "Code Owners"
msgstr ""
@@ -5549,7 +5713,7 @@ msgstr ""
msgid "Collapse approvers"
msgstr ""
-msgid "Collapse child epics"
+msgid "Collapse milestones"
msgstr ""
msgid "Collapse replies"
@@ -5567,6 +5731,9 @@ msgstr ""
msgid "Coming soon"
msgstr ""
+msgid "Comma-separated, e.g. '1.1.1.1, 2.2.2.0/24'"
+msgstr ""
+
msgid "Command"
msgstr ""
@@ -5737,6 +5904,12 @@ msgstr ""
msgid "Compliance framework (optional)"
msgstr ""
+msgid "Compliance frameworks"
+msgstr ""
+
+msgid "ComplianceDashboard|created by:"
+msgstr ""
+
msgid "ComplianceFramework|GDPR"
msgstr ""
@@ -5875,6 +6048,9 @@ msgstr ""
msgid "Connection timed out"
msgstr ""
+msgid "Connection timeout"
+msgstr ""
+
msgid "Contact sales to upgrade"
msgstr ""
@@ -5930,9 +6106,6 @@ msgstr ""
msgid "ContainerRegistry|CLI Commands"
msgstr ""
-msgid "ContainerRegistry|Compressed Size"
-msgstr ""
-
msgid "ContainerRegistry|Container Registry"
msgstr ""
@@ -5948,6 +6121,9 @@ msgstr ""
msgid "ContainerRegistry|Copy push command"
msgstr ""
+msgid "ContainerRegistry|Delete selected"
+msgstr ""
+
msgid "ContainerRegistry|Docker connection error"
msgstr ""
@@ -5981,16 +6157,16 @@ msgstr ""
msgid "ContainerRegistry|If you are not already logged in, you need to authenticate to the Container Registry by using your GitLab username and password. If you have %{twofaDocLinkStart}Two-Factor Authentication%{twofaDocLinkEnd} enabled, use a %{personalAccessTokensDocLinkStart}Personal Access Token%{personalAccessTokensDocLinkEnd} instead of a password."
msgstr ""
-msgid "ContainerRegistry|Image ID"
+msgid "ContainerRegistry|Image ID: %{imageId}"
msgstr ""
msgid "ContainerRegistry|Image Repositories"
msgstr ""
-msgid "ContainerRegistry|Keep and protect the images that matter most."
+msgid "ContainerRegistry|Image tags"
msgstr ""
-msgid "ContainerRegistry|Last Updated"
+msgid "ContainerRegistry|Keep and protect the images that matter most."
msgstr ""
msgid "ContainerRegistry|Login"
@@ -6005,6 +6181,9 @@ msgstr ""
msgid "ContainerRegistry|Please contact your administrator."
msgstr ""
+msgid "ContainerRegistry|Published %{timeInfo}"
+msgstr ""
+
msgid "ContainerRegistry|Push an image"
msgstr ""
@@ -6020,9 +6199,6 @@ msgstr ""
msgid "ContainerRegistry|Remove repository"
msgstr ""
-msgid "ContainerRegistry|Remove selected tags"
-msgstr ""
-
msgid "ContainerRegistry|Remove tag"
msgid_plural "ContainerRegistry|Remove tags"
msgstr[0] ""
@@ -6052,9 +6228,6 @@ msgstr ""
msgid "ContainerRegistry|Sorry, your filter produced no results."
msgstr ""
-msgid "ContainerRegistry|Tag"
-msgstr ""
-
msgid "ContainerRegistry|Tag expiration policy"
msgstr ""
@@ -6199,21 +6372,6 @@ msgstr ""
msgid "Control the display of third party offers."
msgstr ""
-msgid "Control the maximum concurrency of LFS/attachment backfill for this secondary node"
-msgstr ""
-
-msgid "Control the maximum concurrency of container repository operations for this Geo node"
-msgstr ""
-
-msgid "Control the maximum concurrency of repository backfill for this secondary node"
-msgstr ""
-
-msgid "Control the maximum concurrency of verification operations for this Geo node"
-msgstr ""
-
-msgid "Control the minimum interval in days that a repository should be reverified for this primary node"
-msgstr ""
-
msgid "Cookie domain"
msgstr ""
@@ -6355,6 +6513,9 @@ msgstr ""
msgid "Could not find design."
msgstr ""
+msgid "Could not find iteration"
+msgstr ""
+
msgid "Could not remove the trigger."
msgstr ""
@@ -6391,6 +6552,12 @@ msgstr ""
msgid "Coverage"
msgstr ""
+msgid "Coverage Fuzzing"
+msgstr ""
+
+msgid "Crash State"
+msgstr ""
+
msgid "Create"
msgstr ""
@@ -6439,6 +6606,9 @@ msgstr ""
msgid "Create a personal access token on your account to pull or push via %{protocol}."
msgstr ""
+msgid "Create a project pre-populated with the necessary files to get you started quickly."
+msgstr ""
+
msgid "Create an account using:"
msgstr ""
@@ -6607,6 +6777,9 @@ msgstr ""
msgid "Creates branch '%{branch_name}' and a merge request to resolve this issue."
msgstr ""
+msgid "Creating"
+msgstr ""
+
msgid "Creating epic"
msgstr ""
@@ -6658,7 +6831,10 @@ msgstr ""
msgid "Current vulnerabilities count"
msgstr ""
-msgid "CurrentUser|Buy CI minutes"
+msgid "CurrentUser|Buy Pipeline minutes"
+msgstr ""
+
+msgid "CurrentUser|One of your groups is running out"
msgstr ""
msgid "CurrentUser|Profile"
@@ -6673,6 +6849,9 @@ msgstr ""
msgid "CurrentUser|Upgrade"
msgstr ""
+msgid "Custom Attributes"
+msgstr ""
+
msgid "Custom CI configuration path"
msgstr ""
@@ -6936,12 +7115,18 @@ msgstr ""
msgid "DAG"
msgstr ""
+msgid "DAG visualization requires at least 3 dependent jobs."
+msgstr ""
+
msgid "DNS"
msgstr ""
msgid "Dashboard"
msgstr ""
+msgid "Dashboard uid not found"
+msgstr ""
+
msgid "DashboardProjects|All"
msgstr ""
@@ -6966,6 +7151,9 @@ msgstr ""
msgid "Data is still calculating..."
msgstr ""
+msgid "Datasource name not found"
+msgstr ""
+
msgid "Date"
msgstr ""
@@ -7146,6 +7334,9 @@ msgstr ""
msgid "Delete this attachment"
msgstr ""
+msgid "Delete user list"
+msgstr ""
+
msgid "Delete variable"
msgstr ""
@@ -7462,6 +7653,9 @@ msgstr ""
msgid "Deploying to"
msgstr ""
+msgid "Deploying to AWS is easy with GitLab"
+msgstr ""
+
msgid "Deployment Frequency"
msgstr ""
@@ -7824,6 +8018,9 @@ msgstr ""
msgid "Dismissed on pipeline %{pipelineLink} at %{projectLink}"
msgstr ""
+msgid "Display alerts from all your monitoring tools directly within GitLab."
+msgstr ""
+
msgid "Display name"
msgstr ""
@@ -7881,9 +8078,6 @@ msgstr ""
msgid "Don't show again"
msgstr ""
-msgid "Don't worry, you can access this tour by clicking on the help icon in the top right corner and choose <strong>Learn GitLab</strong>."
-msgstr ""
-
msgid "Done"
msgstr ""
@@ -8010,6 +8204,9 @@ msgstr ""
msgid "Edit Release"
msgstr ""
+msgid "Edit Slack integration"
+msgstr ""
+
msgid "Edit Snippet"
msgstr ""
@@ -8049,6 +8246,9 @@ msgstr ""
msgid "Edit issues"
msgstr ""
+msgid "Edit iteration"
+msgstr ""
+
msgid "Edit public deploy key"
msgstr ""
@@ -8097,6 +8297,9 @@ msgstr ""
msgid "Email"
msgstr ""
+msgid "Email Notification"
+msgstr ""
+
msgid "Email address"
msgstr ""
@@ -8109,6 +8312,9 @@ msgstr ""
msgid "Email not verified. Please verify your email in Salesforce."
msgstr ""
+msgid "Email notification for unknown sign-ins"
+msgstr ""
+
msgid "Email patch"
msgstr ""
@@ -8340,6 +8546,9 @@ msgstr ""
msgid "Enforce DNS rebinding attack protection"
msgstr ""
+msgid "Enforce personal access token expiration"
+msgstr ""
+
msgid "Ensure connectivity is available from the GitLab server to the Prometheus server"
msgstr ""
@@ -8409,6 +8618,9 @@ msgstr ""
msgid "Enter the name of your application, and we'll return a unique %{type}."
msgstr ""
+msgid "Enter weights for storages for new repositories."
+msgstr ""
+
msgid "Enter your password to approve"
msgstr ""
@@ -8418,15 +8630,27 @@ msgstr ""
msgid "Environment does not have deployments"
msgstr ""
+msgid "Environment is required for Stages::MetricEndpointInserter"
+msgstr ""
+
+msgid "Environment is required for Stages::VariableEndpointInserter"
+msgstr ""
+
msgid "Environment scope"
msgstr ""
+msgid "Environment variables are applied to all project environments in this instance via the Runner. You can use environment variables for passwords, secret keys, etc. Make variables available to the running application by prepending the variable key with <code>K8S_SECRET_</code>. You can set variables to be:"
+msgstr ""
+
msgid "Environment variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. Additionally, they can be masked so they are hidden in job logs, though they must match certain regexp requirements to do so. You can use environment variables for passwords, secret keys, or whatever you want."
msgstr ""
msgid "Environment variables are configured by your administrator to be %{link_start}protected%{link_end} by default"
msgstr ""
+msgid "Environment variables on this GitLab instance are configured to be %{link_start}protected%{link_end} by default"
+msgstr ""
+
msgid "Environment:"
msgstr ""
@@ -8958,6 +9182,9 @@ msgstr ""
msgid "EventFilterBy|Filter by comments"
msgstr ""
+msgid "EventFilterBy|Filter by designs"
+msgstr ""
+
msgid "EventFilterBy|Filter by epic events"
msgstr ""
@@ -9090,15 +9317,15 @@ msgstr ""
msgid "Expand approvers"
msgstr ""
-msgid "Expand child epics"
-msgstr ""
-
msgid "Expand down"
msgstr ""
msgid "Expand dropdown"
msgstr ""
+msgid "Expand milestones"
+msgstr ""
+
msgid "Expand sidebar"
msgstr ""
@@ -9114,6 +9341,9 @@ msgstr ""
msgid "Expiration date"
msgstr ""
+msgid "Expiration not enforced"
+msgstr ""
+
msgid "Expiration policy for the Container Registry is a perfect solution for keeping the Registry space down while still enjoying the full power of GitLab CI/CD."
msgstr ""
@@ -9234,6 +9464,9 @@ msgstr ""
msgid "Failed Jobs"
msgstr ""
+msgid "Failed on"
+msgstr ""
+
msgid "Failed to add a Zoom meeting"
msgstr ""
@@ -9294,13 +9527,19 @@ msgstr ""
msgid "Failed to install."
msgstr ""
+msgid "Failed to load assignees. Please try again."
+msgstr ""
+
+msgid "Failed to load authors. Please try again."
+msgstr ""
+
msgid "Failed to load emoji list."
msgstr ""
msgid "Failed to load error details from Sentry."
msgstr ""
-msgid "Failed to load errors from Sentry. Error message: %{errorMessage}"
+msgid "Failed to load errors from Sentry."
msgstr ""
msgid "Failed to load group activity metrics. Please try again."
@@ -9381,6 +9620,9 @@ msgstr ""
msgid "Failed to set due date because the date format is invalid."
msgstr ""
+msgid "Failed to set iteration on this issue. Please try again."
+msgstr ""
+
msgid "Failed to signing using smartcard authentication"
msgstr ""
@@ -9603,13 +9845,10 @@ msgstr ""
msgid "FeatureFlags|Target environments"
msgstr ""
-msgid "FeatureFlags|There are no active feature flags"
-msgstr ""
-
-msgid "FeatureFlags|There are no inactive feature flags"
+msgid "FeatureFlags|There was an error fetching the feature flags."
msgstr ""
-msgid "FeatureFlags|There was an error fetching the feature flags."
+msgid "FeatureFlags|There was an error retrieving user lists"
msgstr ""
msgid "FeatureFlags|Try again in a few moments or contact your support team."
@@ -9621,9 +9860,18 @@ msgstr ""
msgid "FeatureFlag|Delete strategy"
msgstr ""
+msgid "FeatureFlag|List"
+msgstr ""
+
msgid "FeatureFlag|Percentage"
msgstr ""
+msgid "FeatureFlag|Select a user list"
+msgstr ""
+
+msgid "FeatureFlag|There are no configured user lists"
+msgstr ""
+
msgid "FeatureFlag|Type"
msgstr ""
@@ -9780,6 +10028,12 @@ msgstr ""
msgid "Filter..."
msgstr ""
+msgid "Find File"
+msgstr ""
+
+msgid "Find bugs in your code with coverage-guided fuzzing"
+msgstr ""
+
msgid "Find by path"
msgstr ""
@@ -10044,6 +10298,9 @@ msgstr ""
msgid "Geo allows you to replicate your GitLab instance to other geographical locations."
msgstr ""
+msgid "Geo nodes are paused using a command run on the node"
+msgstr ""
+
msgid "GeoNodeStatusEvent|%{timeAgoStr} (%{pendingEvents} events)"
msgstr ""
@@ -10128,6 +10385,9 @@ msgstr ""
msgid "GeoNodes|Not checksummed"
msgstr ""
+msgid "GeoNodes|Package files"
+msgstr ""
+
msgid "GeoNodes|Pausing replication stops the sync process. Are you sure?"
msgstr ""
@@ -10146,6 +10406,9 @@ msgstr ""
msgid "GeoNodes|Replication slots"
msgstr ""
+msgid "GeoNodes|Replication status"
+msgstr ""
+
msgid "GeoNodes|Repositories"
msgstr ""
@@ -10356,6 +10619,9 @@ msgstr ""
msgid "Geo|Synchronization failed - %{error}"
msgstr ""
+msgid "Geo|Synchronization of %{itemTitle} is disabled."
+msgstr ""
+
msgid "Geo|The database is currently %{db_lag} behind the primary node."
msgstr ""
@@ -10668,6 +10934,9 @@ msgstr ""
msgid "Go to %{link_to_google_takeout}."
msgstr ""
+msgid "Go to <strong>Issues</strong> > <strong>Boards</strong> to access your personalized learning issue board."
+msgstr ""
+
msgid "Go to Pipelines"
msgstr ""
@@ -10767,9 +11036,6 @@ msgstr ""
msgid "Go to your snippets"
msgstr ""
-msgid "Golden Tanuki"
-msgstr ""
-
msgid "Google Cloud Platform"
msgstr ""
@@ -10791,6 +11057,9 @@ msgstr ""
msgid "Grafana URL"
msgstr ""
+msgid "Grafana response contains invalid json"
+msgstr ""
+
msgid "GrafanaIntegration|API Token"
msgstr ""
@@ -10839,9 +11108,6 @@ msgstr ""
msgid "Group %{group_name} was successfully created."
msgstr ""
-msgid "Group '%{group_name}' is being imported."
-msgstr ""
-
msgid "Group Audit Events"
msgstr ""
@@ -10878,9 +11144,6 @@ msgstr ""
msgid "Group by:"
msgstr ""
-msgid "Group could not be imported: %{errors}"
-msgstr ""
-
msgid "Group description"
msgstr ""
@@ -10908,9 +11171,15 @@ msgstr ""
msgid "Group has not been marked for deletion"
msgstr ""
+msgid "Group import could not be scheduled"
+msgstr ""
+
msgid "Group info:"
msgstr ""
+msgid "Group is required when cluster_type is :group"
+msgstr ""
+
msgid "Group maintainers can register group runners in the %{link}"
msgstr ""
@@ -10974,6 +11243,24 @@ msgstr ""
msgid "GroupActivyMetrics|Recent activity (last 90 days)"
msgstr ""
+msgid "GroupImport|Failed to import group."
+msgstr ""
+
+msgid "GroupImport|Group '%{group_name}' is being imported."
+msgstr ""
+
+msgid "GroupImport|Group could not be imported: %{errors}"
+msgstr ""
+
+msgid "GroupImport|Please wait while we import the group for you. Refresh at will."
+msgstr ""
+
+msgid "GroupImport|The group was successfully imported."
+msgstr ""
+
+msgid "GroupImport|Unable to process group import file"
+msgstr ""
+
msgid "GroupRoadmap|%{dateWord} – No end date"
msgstr ""
@@ -11241,6 +11528,9 @@ msgstr ""
msgid "Groups (%{groups})"
msgstr ""
+msgid "Groups and projects"
+msgstr ""
+
msgid "Groups can also be nested by creating %{subgroup_docs_link_start}subgroups%{subgroup_docs_link_end}."
msgstr ""
@@ -11286,6 +11576,33 @@ msgstr ""
msgid "GroupsEmptyState|You can manage your group member’s permissions and access to each project in the group."
msgstr ""
+msgid "GroupsNew|Contact an administrator to enable options for importing your group."
+msgstr ""
+
+msgid "GroupsNew|Create"
+msgstr ""
+
+msgid "GroupsNew|Create group"
+msgstr ""
+
+msgid "GroupsNew|GitLab group export"
+msgstr ""
+
+msgid "GroupsNew|Import"
+msgstr ""
+
+msgid "GroupsNew|Import group"
+msgstr ""
+
+msgid "GroupsNew|My Awesome Group"
+msgstr ""
+
+msgid "GroupsNew|No import options available"
+msgstr ""
+
+msgid "GroupsNew|To copy a GitLab group between installations, navigate to the group settings page for the original installation, generate an export file, and upload it here."
+msgstr ""
+
msgid "GroupsTree|Are you sure you want to leave the \"%{fullName}\" group?"
msgstr ""
@@ -11388,6 +11705,9 @@ msgstr ""
msgid "Helps reduce request volume for protected paths"
msgstr ""
+msgid "Here are all your projects in your group, including the one you just created. To start, let’s take a look at your personalized learning project which will help you learn about GitLab at your own pace."
+msgstr ""
+
msgid "Here you will find recent merge request activity"
msgstr ""
@@ -11402,6 +11722,9 @@ msgid_plural "Hide charts"
msgstr[0] ""
msgstr[1] ""
+msgid "Hide details"
+msgstr ""
+
msgid "Hide file browser"
msgstr ""
@@ -11411,6 +11734,9 @@ msgstr ""
msgid "Hide host keys manual input"
msgstr ""
+msgid "Hide list"
+msgstr ""
+
msgid "Hide marketing-related entries from help"
msgstr ""
@@ -11431,9 +11757,6 @@ msgstr[1] ""
msgid "Hide values"
msgstr ""
-msgid "Hiding all labels"
-msgstr ""
-
msgid "High or unknown vulnerabilities present"
msgstr ""
@@ -11518,6 +11841,9 @@ msgstr ""
msgid "ID:"
msgstr ""
+msgid "IDE"
+msgstr ""
+
msgid "IDE|Allow live previews of JavaScript projects in the Web IDE using CodeSandbox Live Preview."
msgstr ""
@@ -11608,10 +11934,10 @@ msgstr ""
msgid "If enabled"
msgstr ""
-msgid "If enabled, access to projects will be validated on an external service using their classification label."
+msgid "If enabled, GitLab will handle Object Storage replication using Geo. %{linkStart}More information%{linkEnd}"
msgstr ""
-msgid "If enabled, and if object storage is enabled, GitLab will handle Object Storage replication using Geo"
+msgid "If enabled, access to projects will be validated on an external service using their classification label."
msgstr ""
msgid "If there is no previous license or if the previous license has expired, some GitLab functionality will be blocked until a new, valid license is uploaded."
@@ -11973,6 +12299,9 @@ msgid_plural "Instances"
msgstr[0] ""
msgstr[1] ""
+msgid "Instance Configuration"
+msgstr ""
+
msgid "Instance Statistics visibility"
msgstr ""
@@ -11994,9 +12323,6 @@ msgstr ""
msgid "Integrations"
msgstr ""
-msgid "Integrations allow you to integrate GitLab with other applications"
-msgstr ""
-
msgid "Integrations|All details"
msgstr ""
@@ -12108,6 +12434,9 @@ msgstr ""
msgid "Invalid query"
msgstr ""
+msgid "Invalid repository bundle for snippet with id %{snippet_id}"
+msgstr ""
+
msgid "Invalid repository path"
msgstr ""
@@ -12126,6 +12455,9 @@ msgstr ""
msgid "Invalid two-factor code."
msgstr ""
+msgid "Invalid yaml"
+msgstr ""
+
msgid "Invitation"
msgstr ""
@@ -12345,15 +12677,24 @@ msgstr ""
msgid "It seems like the Dependency Scanning job ran successfully, but no dependencies have been detected in your project."
msgstr ""
+msgid "It seems that there is currently no available data for code coverage"
+msgstr ""
+
msgid "It's you"
msgstr ""
+msgid "Iteration"
+msgstr ""
+
msgid "Iteration changed to"
msgstr ""
msgid "Iteration removed"
msgstr ""
+msgid "Iteration updated"
+msgstr ""
+
msgid "Iterations"
msgstr ""
@@ -12384,12 +12725,18 @@ msgstr ""
msgid "January"
msgstr ""
+msgid "Jira Issues"
+msgstr ""
+
msgid "Jira import is already running."
msgstr ""
msgid "Jira integration not configured."
msgstr ""
+msgid "Jira project key is not configured"
+msgstr ""
+
msgid "Jira project: %{importProject}"
msgstr ""
@@ -12570,6 +12917,9 @@ msgstr ""
msgid "Keep divergent refs"
msgstr ""
+msgid "Kerberos access denied"
+msgstr ""
+
msgid "Key"
msgstr ""
@@ -12579,10 +12929,13 @@ msgstr ""
msgid "Key: %{key}"
msgstr ""
-msgid "Keyboard Shortcuts"
+msgid "Keyboard shortcuts"
msgstr ""
-msgid "Keyboard shortcuts"
+msgid "Keys"
+msgstr ""
+
+msgid "Ki"
msgstr ""
msgid "Kubernetes"
@@ -12624,6 +12977,9 @@ msgstr ""
msgid "LDAP"
msgstr ""
+msgid "LDAP Synchronization"
+msgstr ""
+
msgid "LDAP settings"
msgstr ""
@@ -12848,12 +13204,18 @@ msgstr ""
msgid "Learn more about custom project templates"
msgstr ""
+msgid "Learn more about deploying to AWS"
+msgstr ""
+
msgid "Learn more about deploying to a cluster"
msgstr ""
msgid "Learn more about group-level project templates"
msgstr ""
+msgid "Learn more about job dependencies"
+msgstr ""
+
msgid "Learn more about signing commits"
msgstr ""
@@ -12905,9 +13267,21 @@ msgstr ""
msgid "License History"
msgstr ""
+msgid "License ID:"
+msgstr ""
+
+msgid "License URL"
+msgstr ""
+
msgid "License-Check"
msgstr ""
+msgid "LicenseCompliance|%{docLinkStart}License Approvals%{docLinkEnd} are active"
+msgstr ""
+
+msgid "LicenseCompliance|%{docLinkStart}License Approvals%{docLinkEnd} are inactive"
+msgstr ""
+
msgid "LicenseCompliance|Add a license"
msgstr ""
@@ -12929,9 +13303,15 @@ msgstr ""
msgid "LicenseCompliance|Deny"
msgstr ""
+msgid "LicenseCompliance|Learn more about %{linkStart}License Approvals%{linkEnd}"
+msgstr ""
+
msgid "LicenseCompliance|License"
msgstr ""
+msgid "LicenseCompliance|License Approvals"
+msgstr ""
+
msgid "LicenseCompliance|License Compliance detected %d license and policy violation for the source branch only; approval required"
msgid_plural "LicenseCompliance|License Compliance detected %d licenses and policy violations for the source branch only; approval required"
msgstr[0] ""
@@ -13152,6 +13532,9 @@ msgstr ""
msgid "List your Bitbucket Server repositories"
msgstr ""
+msgid "Lists"
+msgstr ""
+
msgid "Live preview"
msgstr ""
@@ -13335,6 +13718,9 @@ msgstr ""
msgid "Manage project labels"
msgstr ""
+msgid "Manage storage usage"
+msgstr ""
+
msgid "Manage two-factor authentication"
msgstr ""
@@ -13350,6 +13736,9 @@ msgstr ""
msgid "Manifest file import"
msgstr ""
+msgid "Manifest import"
+msgstr ""
+
msgid "Manual job"
msgstr ""
@@ -13497,6 +13886,12 @@ msgstr ""
msgid "Maximum field length"
msgstr ""
+msgid "Maximum file size is 2MB. Please select a smaller file."
+msgstr ""
+
+msgid "Maximum import size (MB)"
+msgstr ""
+
msgid "Maximum job timeout"
msgstr ""
@@ -13536,6 +13931,9 @@ msgstr ""
msgid "Maximum size of Elasticsearch bulk indexing requests."
msgstr ""
+msgid "Maximum size of import files."
+msgstr ""
+
msgid "Maximum size of individual attachments in comments."
msgstr ""
@@ -13587,6 +13985,9 @@ msgstr ""
msgid "Merge (when the pipeline succeeds)"
msgstr ""
+msgid "Merge Conflicts"
+msgstr ""
+
msgid "Merge Request"
msgstr ""
@@ -13713,6 +14114,9 @@ msgstr ""
msgid "MergeRequests|Squash task canceled: another squash is already in progress."
msgstr ""
+msgid "MergeRequests|This project does not allow squashing commits when merge requests are accepted."
+msgstr ""
+
msgid "MergeRequests|Thread stays resolved"
msgstr ""
@@ -13866,6 +14270,12 @@ msgstr ""
msgid "MetricsSettings|Add a button to the metrics dashboard linking directly to your existing external dashboard."
msgstr ""
+msgid "MetricsSettings|Choose whether to display dashboard metrics in UTC or the user's local timezone."
+msgstr ""
+
+msgid "MetricsSettings|Dashboard timezone"
+msgstr ""
+
msgid "MetricsSettings|External dashboard URL"
msgstr ""
@@ -13875,6 +14285,12 @@ msgstr ""
msgid "MetricsSettings|Metrics Dashboard"
msgstr ""
+msgid "MetricsSettings|UTC (Coordinated Universal Time)"
+msgstr ""
+
+msgid "MetricsSettings|User's local timezone"
+msgstr ""
+
msgid "Metrics|Add metric"
msgstr ""
@@ -13890,6 +14306,9 @@ msgstr ""
msgid "Metrics|Create metric"
msgstr ""
+msgid "Metrics|Current"
+msgstr ""
+
msgid "Metrics|Delete metric"
msgstr ""
@@ -13940,6 +14359,9 @@ msgstr ""
msgid "Metrics|Max"
msgstr ""
+msgid "Metrics|Min"
+msgstr ""
+
msgid "Metrics|Must be a valid PromQL query."
msgstr ""
@@ -13955,6 +14377,9 @@ msgstr ""
msgid "Metrics|Refresh dashboard"
msgstr ""
+msgid "Metrics|Select a value"
+msgstr ""
+
msgid "Metrics|Star dashboard"
msgstr ""
@@ -13973,12 +14398,18 @@ msgstr ""
msgid "Metrics|There was an error getting annotations information."
msgstr ""
+msgid "Metrics|There was an error getting dashboard validation warnings information."
+msgstr ""
+
msgid "Metrics|There was an error getting deployment information."
msgstr ""
msgid "Metrics|There was an error getting environments information."
msgstr ""
+msgid "Metrics|There was an error getting options for variable \"%{name}\"."
+msgstr ""
+
msgid "Metrics|There was an error trying to validate your query"
msgstr ""
@@ -14021,6 +14452,9 @@ msgstr ""
msgid "Metrics|You're about to permanently delete this metric. This cannot be undone."
msgstr ""
+msgid "Metrics|Your dashboard schema is invalid. Edit the dashboard to correct the YAML schema."
+msgstr ""
+
msgid "Metrics|e.g. HTTP requests"
msgstr ""
@@ -14036,12 +14470,18 @@ msgstr ""
msgid "Metrics|e.g. req/sec"
msgstr ""
+msgid "Mi"
+msgstr ""
+
msgid "Microsoft Azure"
msgstr ""
msgid "Middleman project with Static Site Editor support"
msgstr ""
+msgid "Migrate your data from an external source like GitHub, Bitbucket, or another instance of GitLab."
+msgstr ""
+
msgid "Migrated %{success_count}/%{total_count} files."
msgstr ""
@@ -14176,6 +14616,9 @@ msgstr ""
msgid "Minimum capacity to be available before we schedule more mirrors preemptively."
msgstr ""
+msgid "Minimum interval in days"
+msgstr ""
+
msgid "Minimum length is %{minimum_password_length} characters"
msgstr ""
@@ -14362,6 +14805,12 @@ msgstr ""
msgid "Multiple uploaders found: %{uploader_types}"
msgstr ""
+msgid "Must match with the %{codeStart}external_url%{codeEnd} in %{codeStart}/etc/gitlab/gitlab.rb%{codeEnd}."
+msgstr ""
+
+msgid "Must match with the %{codeStart}geo_node_name%{codeEnd} in %{codeStart}/etc/gitlab/gitlab.rb%{codeEnd}. %{linkStart}More information%{linkEnd}"
+msgstr ""
+
msgid "My Awesome Group"
msgstr ""
@@ -14476,10 +14925,16 @@ msgstr ""
msgid "New Application"
msgstr ""
+msgid "New Branch"
+msgstr ""
+
+msgid "New Deploy Key"
+msgstr ""
+
msgid "New Environment"
msgstr ""
-msgid "New Geo Node"
+msgid "New File"
msgstr ""
msgid "New Group"
@@ -14496,15 +14951,15 @@ msgid_plural "New Issues"
msgstr[0] ""
msgstr[1] ""
-msgid "New Iteration"
-msgstr ""
-
msgid "New Jira import"
msgstr ""
msgid "New Label"
msgstr ""
+msgid "New Merge Request"
+msgstr ""
+
msgid "New Milestone"
msgstr ""
@@ -14523,6 +14978,9 @@ msgstr ""
msgid "New Snippet"
msgstr ""
+msgid "New User"
+msgstr ""
+
msgid "New branch"
msgstr ""
@@ -14565,6 +15023,9 @@ msgstr ""
msgid "New issue title"
msgstr ""
+msgid "New iteration"
+msgstr ""
+
msgid "New iteration created"
msgstr ""
@@ -14742,6 +15203,12 @@ msgstr ""
msgid "No grouping"
msgstr ""
+msgid "No iteration"
+msgstr ""
+
+msgid "No iterations to show"
+msgstr ""
+
msgid "No job log"
msgstr ""
@@ -14784,6 +15251,9 @@ msgstr ""
msgid "No other labels with such name or description"
msgstr ""
+msgid "No panels matching properties %{opts}"
+msgstr ""
+
msgid "No parent group"
msgstr ""
@@ -14817,6 +15287,9 @@ msgstr ""
msgid "No schedules"
msgstr ""
+msgid "No stack trace for this error"
+msgstr ""
+
msgid "No starrers matched your search"
msgstr ""
@@ -14829,7 +15302,7 @@ msgstr ""
msgid "No test coverage"
msgstr ""
-msgid "No thanks, don't show this again"
+msgid "No thanks"
msgstr ""
msgid "No vulnerabilities present"
@@ -14844,9 +15317,6 @@ msgstr ""
msgid "No, directly import the existing email addresses and usernames."
msgstr ""
-msgid "No, not interested right now"
-msgstr ""
-
msgid "No. of commits"
msgstr ""
@@ -14892,9 +15362,6 @@ msgstr ""
msgid "Not found."
msgstr ""
-msgid "Not helpful"
-msgstr ""
-
msgid "Not now"
msgstr ""
@@ -15051,6 +15518,9 @@ msgstr ""
msgid "Notifications on"
msgstr ""
+msgid "Notify users by email when sign-in location is not recognized"
+msgstr ""
+
msgid "Nov"
msgstr ""
@@ -15117,9 +15587,6 @@ msgstr ""
msgid "Oh no!"
msgstr ""
-msgid "Ok let's go"
-msgstr ""
-
msgid "Oldest first"
msgstr ""
@@ -15132,16 +15599,49 @@ msgstr ""
msgid "On track"
msgstr ""
+msgid "OnDemandScans|Attached branch"
+msgstr ""
+
+msgid "OnDemandScans|Attached branch is where the scan job runs."
+msgstr ""
+
+msgid "OnDemandScans|Could not run the scan. Please try again."
+msgstr ""
+
msgid "OnDemandScans|Create new DAST scan"
msgstr ""
+msgid "OnDemandScans|DAST will scan the target URL and any discovered sub URLs."
+msgstr ""
+
+msgid "OnDemandScans|New on-demand DAST scan"
+msgstr ""
+
msgid "OnDemandScans|On-demand Scans"
msgstr ""
+msgid "OnDemandScans|On-demand scans run outside the DevOps cycle and find vulnerabilities in your projects. %{learnMoreLinkStart}Learn more%{learnMoreLinkEnd}"
+msgstr ""
+
+msgid "OnDemandScans|Only a passive scan can be performed on demand."
+msgstr ""
+
+msgid "OnDemandScans|Passive DAST Scan"
+msgstr ""
+
+msgid "OnDemandScans|Please enter a valid URL format, ex: http://www.example.com/home"
+msgstr ""
+
+msgid "OnDemandScans|Run this scan"
+msgstr ""
+
+msgid "OnDemandScans|Scan mode"
+msgstr ""
+
msgid "OnDemandScans|Schedule or run scans immediately against target sites. Currently available on-demand scan type: DAST. %{helpLinkStart}More information%{helpLinkEnd}"
msgstr ""
-msgid "Onboarding"
+msgid "OnDemandScans|Target URL"
msgstr ""
msgid "Once imported, repositories can be mirrored over SSH. Read more %{link_start}here%{link_end}."
@@ -15167,6 +15667,9 @@ msgstr ""
msgid "One or more of you personal access tokens were revoked"
msgstr ""
+msgid "One or more of your %{provider} projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
+msgstr ""
+
msgid "One or more of your Bitbucket projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
msgstr ""
@@ -15668,6 +16171,9 @@ msgstr ""
msgid "Parent epic is not present."
msgstr ""
+msgid "Parsing error for param :embed_json. %{message}"
+msgstr ""
+
msgid "Part of merge request changes"
msgstr ""
@@ -15680,6 +16186,9 @@ msgstr ""
msgid "Passed"
msgstr ""
+msgid "Passed on"
+msgstr ""
+
msgid "Password"
msgstr ""
@@ -15764,6 +16273,9 @@ msgstr ""
msgid "Perform common operations on GitLab project"
msgstr ""
+msgid "Performance and resource management"
+msgstr ""
+
msgid "Performance optimization"
msgstr ""
@@ -15971,7 +16483,7 @@ msgstr ""
msgid "Pipelines|Something went wrong while cleaning runners cache."
msgstr ""
-msgid "Pipelines|There are currently no %{scope} pipelines."
+msgid "Pipelines|There are currently no finished pipelines."
msgstr ""
msgid "Pipelines|There are currently no pipelines."
@@ -16082,6 +16594,9 @@ msgstr ""
msgid "Pipeline|Stop pipeline #%{pipelineId}?"
msgstr ""
+msgid "Pipeline|Tag name"
+msgstr ""
+
msgid "Pipeline|Trigger author"
msgstr ""
@@ -16163,12 +16678,18 @@ msgstr ""
msgid "Please check your email (%{email}) to verify that you own this address and unlock the power of CI/CD. Didn't receive it? %{resend_link}. Wrong email address? %{update_link}."
msgstr ""
+msgid "Please choose a file"
+msgstr ""
+
msgid "Please choose a group URL with no special characters."
msgstr ""
msgid "Please complete your profile with email address"
msgstr ""
+msgid "Please convert %{linkStart}them to Git%{linkEnd}, and go through the %{linkToImportFlow} again."
+msgstr ""
+
msgid "Please convert them to %{link_to_git}, and go through the %{link_to_import_flow} again."
msgstr ""
@@ -16226,6 +16747,9 @@ msgstr ""
msgid "Please provide a valid email address."
msgstr ""
+msgid "Please provide attributes to update"
+msgstr ""
+
msgid "Please refer to <a href=\"%{docs_url}\">%{docs_url}</a>"
msgstr ""
@@ -16961,6 +17485,9 @@ msgstr ""
msgid "Project has too many %{label_for_message} to search"
msgstr ""
+msgid "Project is required when cluster_type is :project"
+msgstr ""
+
msgid "Project members"
msgstr ""
@@ -17078,7 +17605,40 @@ msgstr ""
msgid "ProjectService|Comment"
msgstr ""
-msgid "ProjectService|Comment will be posted on each event"
+msgid "ProjectService|Event will be triggered by a push to the repository"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a commit is created/updated"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a confidential issue is created/updated/closed"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a deployment finishes"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a merge request is created/updated/merged"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a new tag is pushed to the repository"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a new, unique alert is recorded"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a pipeline status changes"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a wiki page is created/updated"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when an issue is created/updated/closed"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when someone adds a comment"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when someone adds a comment on a confidential issue"
msgstr ""
msgid "ProjectService|Perform common operations on GitLab project: %{project_name}"
@@ -17129,6 +17689,9 @@ msgstr ""
msgid "ProjectSettings|Enable 'Delete source branch' option by default"
msgstr ""
+msgid "ProjectSettings|Enable merge trains and pipelines for merged results"
+msgstr ""
+
msgid "ProjectSettings|Every merge creates a merge commit"
msgstr ""
@@ -17159,7 +17722,7 @@ msgstr ""
msgid "ProjectSettings|Forks"
msgstr ""
-msgid "ProjectSettings|Git Large File Storage"
+msgid "ProjectSettings|Git Large File Storage (LFS)"
msgstr ""
msgid "ProjectSettings|Internal"
@@ -17195,9 +17758,6 @@ msgstr ""
msgid "ProjectSettings|Merge options"
msgstr ""
-msgid "ProjectSettings|Merge pipelines will try to validate the post-merge result prior to merging"
-msgstr ""
-
msgid "ProjectSettings|Merge requests"
msgstr ""
@@ -17225,6 +17785,9 @@ msgstr ""
msgid "ProjectSettings|Pipelines"
msgstr ""
+msgid "ProjectSettings|Pipelines for merge requests must be enabled in the CI/CD configuration file, or pipelines could be unresolvable or dropped"
+msgstr ""
+
msgid "ProjectSettings|Pipelines must succeed"
msgstr ""
@@ -17252,6 +17815,9 @@ msgstr ""
msgid "ProjectSettings|Show link to create/view merge request when pushing from the command line"
msgstr ""
+msgid "ProjectSettings|Skipped pipelines are considered successful"
+msgstr ""
+
msgid "ProjectSettings|Snippets"
msgstr ""
@@ -17267,6 +17833,9 @@ msgstr ""
msgid "ProjectSettings|These checks must pass before merge requests can be merged"
msgstr ""
+msgid "ProjectSettings|This introduces the risk of merging changes that will not pass the pipeline."
+msgstr ""
+
msgid "ProjectSettings|This setting is applied on the server level and can be overridden by an admin."
msgstr ""
@@ -17444,15 +18013,27 @@ msgstr ""
msgid "ProjectsNew|Blank project"
msgstr ""
+msgid "ProjectsNew|Connect your external repository to GitLab CI/CD."
+msgstr ""
+
msgid "ProjectsNew|Contact an administrator to enable options for importing your project."
msgstr ""
msgid "ProjectsNew|Create"
msgstr ""
+msgid "ProjectsNew|Create a blank project to house your files, plan your work, and collaborate on code, among other things."
+msgstr ""
+
+msgid "ProjectsNew|Create blank project"
+msgstr ""
+
msgid "ProjectsNew|Create from template"
msgstr ""
+msgid "ProjectsNew|Create new project"
+msgstr ""
+
msgid "ProjectsNew|Creating project & repository."
msgstr ""
@@ -17477,6 +18058,9 @@ msgstr ""
msgid "ProjectsNew|Project description %{tag_start}(optional)%{tag_end}"
msgstr ""
+msgid "ProjectsNew|Run CI/CD for external repository"
+msgstr ""
+
msgid "ProjectsNew|Template"
msgstr ""
@@ -17738,6 +18322,9 @@ msgstr ""
msgid "Protected Branch"
msgstr ""
+msgid "Protected Branches"
+msgstr ""
+
msgid "Protected Environment"
msgstr ""
@@ -17750,6 +18337,9 @@ msgstr ""
msgid "Protected Tag"
msgstr ""
+msgid "Protected Tags"
+msgstr ""
+
msgid "Protected branches"
msgstr ""
@@ -17849,6 +18439,9 @@ msgstr ""
msgid "Provider"
msgstr ""
+msgid "Proxy support for this API is not available currently"
+msgstr ""
+
msgid "Pseudonymizer data collection"
msgstr ""
@@ -17873,6 +18466,9 @@ msgstr ""
msgid "Publish to status page"
msgstr ""
+msgid "Published on status page"
+msgstr ""
+
msgid "Publishes this issue to the associated status page."
msgstr ""
@@ -18047,6 +18643,9 @@ msgstr ""
msgid "Recipe"
msgstr ""
+msgid "Reconfigure"
+msgstr ""
+
msgid "Recover hidden stage"
msgstr ""
@@ -18133,6 +18732,9 @@ msgstr ""
msgid "Registration|Your profile"
msgstr ""
+msgid "Regulate approvals by authors/committers, based on compliance frameworks. Can be changed only at the instance level."
+msgstr ""
+
msgid "Rejected (closed)"
msgstr ""
@@ -18183,15 +18785,24 @@ msgstr ""
msgid "ReleaseAssetLinkType|Image"
msgstr ""
+msgid "ReleaseAssetLinkType|Images"
+msgstr ""
+
msgid "ReleaseAssetLinkType|Other"
msgstr ""
msgid "ReleaseAssetLinkType|Package"
msgstr ""
+msgid "ReleaseAssetLinkType|Packages"
+msgstr ""
+
msgid "ReleaseAssetLinkType|Runbook"
msgstr ""
+msgid "ReleaseAssetLinkType|Runbooks"
+msgstr ""
+
msgid "Releases"
msgstr ""
@@ -18204,6 +18815,9 @@ msgstr ""
msgid "Releases documentation"
msgstr ""
+msgid "Releases|New Release"
+msgstr ""
+
msgid "Release|Something went wrong while getting the release details"
msgstr ""
@@ -18276,6 +18890,9 @@ msgstr ""
msgid "Remove fork relationship"
msgstr ""
+msgid "Remove from batch"
+msgstr ""
+
msgid "Remove from board"
msgstr ""
@@ -18465,6 +19082,12 @@ msgstr ""
msgid "Replication"
msgstr ""
+msgid "Replication enabled"
+msgstr ""
+
+msgid "Replication paused"
+msgstr ""
+
msgid "Reply by email"
msgstr ""
@@ -18492,15 +19115,15 @@ msgstr ""
msgid "Reported %{timeAgo} by %{reportedBy}"
msgstr ""
-msgid "Reported Resource Changes: %{addNum} to add, %{changeNum} to change, %{deleteNum} to delete"
-msgstr ""
-
msgid "Reporter"
msgstr ""
msgid "Reporting"
msgstr ""
+msgid "Reports"
+msgstr ""
+
msgid "Reports|%{combinedString} and %{resolvedString}"
msgstr ""
@@ -18539,6 +19162,9 @@ msgstr ""
msgid "Reports|Failure"
msgstr ""
+msgid "Reports|Identifier"
+msgstr ""
+
msgid "Reports|Metrics reports are loading"
msgstr ""
@@ -18551,6 +19177,9 @@ msgstr ""
msgid "Reports|Metrics reports failed loading results"
msgstr ""
+msgid "Reports|Scanner"
+msgstr ""
+
msgid "Reports|Severity"
msgstr ""
@@ -18599,15 +19228,33 @@ msgstr ""
msgid "Repository cleanup has started. You will receive an email once the cleanup operation is complete."
msgstr ""
+msgid "Repository files count over the limit"
+msgstr ""
+
+msgid "Repository has an invalid default branch name."
+msgstr ""
+
+msgid "Repository has more than one branch."
+msgstr ""
+
msgid "Repository has no locks."
msgstr ""
+msgid "Repository has tags."
+msgstr ""
+
msgid "Repository maintenance"
msgstr ""
msgid "Repository mirroring"
msgstr ""
+msgid "Repository must contain at least 1 file."
+msgstr ""
+
+msgid "Repository size is above the limit."
+msgstr ""
+
msgid "Repository static objects"
msgstr ""
@@ -18617,7 +19264,7 @@ msgstr ""
msgid "Repository sync capacity"
msgstr ""
-msgid "Repository: %{counter_repositories} / Wikis: %{counter_wikis} / Build Artifacts: %{counter_build_artifacts} / LFS: %{counter_lfs_objects}"
+msgid "Repository: %{counter_repositories} / Wikis: %{counter_wikis} / Build Artifacts: %{counter_build_artifacts} / LFS: %{counter_lfs_objects} / Snippets: %{counter_snippets}"
msgstr ""
msgid "RepositorySettingsAccessLevel|Select"
@@ -18629,6 +19276,9 @@ msgstr ""
msgid "Request Headers"
msgstr ""
+msgid "Request details"
+msgstr ""
+
msgid "Request parameter %{param} is missing."
msgstr ""
@@ -18735,6 +19385,9 @@ msgstr ""
msgid "Reset template"
msgstr ""
+msgid "Reset to project defaults"
+msgstr ""
+
msgid "Resetting the authorization key for this project will require updating the authorization key in every alert source it is enabled in."
msgstr ""
@@ -18831,9 +19484,6 @@ msgstr ""
msgid "Resume"
msgstr ""
-msgid "Resume replication"
-msgstr ""
-
msgid "Resync"
msgstr ""
@@ -19456,6 +20106,9 @@ msgstr ""
msgid "SecurityReports|False positive"
msgstr ""
+msgid "SecurityReports|Group Security Dashboard"
+msgstr ""
+
msgid "SecurityReports|Hide dismissed"
msgstr ""
@@ -19510,10 +20163,13 @@ msgstr ""
msgid "SecurityReports|Remove project from dashboard"
msgstr ""
-msgid "SecurityReports|Report type"
+msgid "SecurityReports|Return to dashboard"
msgstr ""
-msgid "SecurityReports|Return to dashboard"
+msgid "SecurityReports|Scan details"
+msgstr ""
+
+msgid "SecurityReports|Scanner type"
msgstr ""
msgid "SecurityReports|Security Dashboard"
@@ -19570,6 +20226,9 @@ msgstr ""
msgid "SecurityReports|There was an error while generating the report."
msgstr ""
+msgid "SecurityReports|Unable to add %{invalidProjectsMessage}"
+msgstr ""
+
msgid "SecurityReports|Unable to add %{invalidProjects}"
msgstr ""
@@ -19684,6 +20343,9 @@ msgstr ""
msgid "Select due date"
msgstr ""
+msgid "Select file"
+msgstr ""
+
msgid "Select group or project"
msgstr ""
@@ -19747,9 +20409,6 @@ msgstr ""
msgid "Select the branch you want to set as the default for this project. All merge requests and commits will automatically be made against this branch unless you specify a different one."
msgstr ""
-msgid "Select the configured storage available for new repositories to be placed on."
-msgstr ""
-
msgid "Select the custom project template source group."
msgstr ""
@@ -19942,12 +20601,6 @@ msgstr ""
msgid "Service Desk is enabled but not yet active"
msgstr ""
-msgid "Service Desk is off"
-msgstr ""
-
-msgid "Service Desk is on"
-msgstr ""
-
msgid "Service Templates"
msgstr ""
@@ -20035,6 +20688,15 @@ msgstr ""
msgid "Set the milestone to %{milestone_reference}."
msgstr ""
+msgid "Set the number of concurrent requests this secondary node will make to the primary node while backfilling."
+msgstr ""
+
+msgid "Set the synchronization and verification capacity for the secondary node."
+msgstr ""
+
+msgid "Set the timeout in seconds to send a secondary node status to the primary and IPs allowed for the secondary nodes."
+msgstr ""
+
msgid "Set time estimate"
msgstr ""
@@ -20074,6 +20736,9 @@ msgstr ""
msgid "Set weight to %{weight}."
msgstr ""
+msgid "Set what should be replicated by choosing specific projects or groups by the secondary node."
+msgstr ""
+
msgid "SetPasswordToCloneLink|set a password"
msgstr ""
@@ -20200,6 +20865,9 @@ msgstr ""
msgid "Show complete raw log"
msgstr ""
+msgid "Show details"
+msgstr ""
+
msgid "Show file browser"
msgstr ""
@@ -20209,10 +20877,13 @@ msgstr ""
msgid "Show latest version"
msgstr ""
+msgid "Show list"
+msgstr ""
+
msgid "Show me everything"
msgstr ""
-msgid "Show me how"
+msgid "Show me how to add a pipeline"
msgstr ""
msgid "Show me more advanced stuff"
@@ -20261,9 +20932,6 @@ msgstr ""
msgid "Showing all issues"
msgstr ""
-msgid "Showing all labels"
-msgstr ""
-
msgid "Showing last %{size} of log -"
msgstr ""
@@ -20375,9 +21043,6 @@ msgstr ""
msgid "Skip outdated deployment jobs"
msgstr ""
-msgid "Skip this for now"
-msgstr ""
-
msgid "Skipped"
msgstr ""
@@ -20522,6 +21187,9 @@ msgstr ""
msgid "Something went wrong while adding your award. Please try again."
msgstr ""
+msgid "Something went wrong while applying the batch of suggestions. Please try again."
+msgstr ""
+
msgid "Something went wrong while applying the suggestion. Please try again."
msgstr ""
@@ -20624,10 +21292,13 @@ msgstr ""
msgid "Something went wrong while updating a requirement."
msgstr ""
+msgid "Something went wrong while updating assignees"
+msgstr ""
+
msgid "Something went wrong while updating your list settings"
msgstr ""
-msgid "Something went wrong with your automatic subscription renewal"
+msgid "Something went wrong with your automatic subscription renewal."
msgstr ""
msgid "Something went wrong, unable to add %{project} to dashboard"
@@ -20645,6 +21316,9 @@ msgstr ""
msgid "Something went wrong, unable to search projects"
msgstr ""
+msgid "Something went wrong."
+msgstr ""
+
msgid "Something went wrong. Please try again."
msgstr ""
@@ -20828,6 +21502,9 @@ msgstr ""
msgid "Source code"
msgstr ""
+msgid "Source code (%{fileExtension})"
+msgstr ""
+
msgid "Source is not available"
msgstr ""
@@ -20912,6 +21589,9 @@ msgstr ""
msgid "Stack trace"
msgstr ""
+msgid "Stacktrace snippet"
+msgstr ""
+
msgid "Stage"
msgstr ""
@@ -21017,6 +21697,9 @@ msgstr ""
msgid "Start thread & reopen %{noteable_name}"
msgstr ""
+msgid "Start using Directed Acyclic Graphs (DAG)"
+msgstr ""
+
msgid "Start your Free Gold Trial"
msgstr ""
@@ -21479,6 +22162,21 @@ msgstr ""
msgid "SuggestedColors|Very pale orange"
msgstr ""
+msgid "Suggestion is not applicable as the suggestion was not found."
+msgstr ""
+
+msgid "Suggestions are not applicable as one or more suggestions were not found."
+msgstr ""
+
+msgid "Suggestions are not applicable as their lines cannot overlap."
+msgstr ""
+
+msgid "Suggestions must all be on the same branch."
+msgstr ""
+
+msgid "Suggestions that change line count can't be added to batches, yet."
+msgstr ""
+
msgid "Suggestions:"
msgstr ""
@@ -21521,9 +22219,6 @@ msgstr ""
msgid "Synchronization disabled"
msgstr ""
-msgid "Synchronization of container repositories is disabled."
-msgstr ""
-
msgid "System"
msgstr ""
@@ -21704,6 +22399,34 @@ msgstr ""
msgid "Terms of Service and Privacy Policy"
msgstr ""
+msgid "Terraform|%{number} Terraform report failed to generate"
+msgid_plural "Terraform|%{number} Terraform reports failed to generate"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Terraform|%{number} Terraform report was generated in your pipelines"
+msgid_plural "Terraform|%{number} Terraform reports were generated in your pipelines"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Terraform|A Terraform report failed to generate."
+msgstr ""
+
+msgid "Terraform|A Terraform report was generated in your pipelines."
+msgstr ""
+
+msgid "Terraform|Generating the report caused an error."
+msgstr ""
+
+msgid "Terraform|Reported Resource Changes: %{addNum} to add, %{changeNum} to change, %{deleteNum} to delete"
+msgstr ""
+
+msgid "Terraform|The Terraform report %{name} failed to generate."
+msgstr ""
+
+msgid "Terraform|The Terraform report %{name} was generated in your pipelines."
+msgstr ""
+
msgid "Test"
msgstr ""
@@ -21730,7 +22453,7 @@ msgstr ""
msgid "TestHooks|Ensure the project has CI pipelines."
msgstr ""
-msgid "TestHooks|Ensure the project has at least one commit."
+msgid "TestHooks|Ensure the project has deployments."
msgstr ""
msgid "TestHooks|Ensure the project has issues."
@@ -21831,7 +22554,7 @@ msgstr ""
msgid "The Prometheus server responded with \"bad request\". Please check your queries are correct and are supported in your Prometheus version. %{documentationLink}"
msgstr ""
-msgid "The URL defined on the primary node that secondary nodes should use to contact it. Defaults to URL"
+msgid "The URL defined on the primary node that secondary nodes should use to contact it."
msgstr ""
msgid "The URL to use for connecting to Elasticsearch. Use a comma-separated list to support clustering (e.g., \"http://localhost:9200, http://localhost:9201\")."
@@ -21840,6 +22563,9 @@ msgstr ""
msgid "The X509 Certificate to use when mutual TLS is required to communicate with the external authorization service. If left blank, the server certificate is still validated when accessing over HTTPS."
msgstr ""
+msgid "The above settings apply to all projects with the selected compliance framework(s)."
+msgstr ""
+
msgid "The amount of seconds after which a request to get a secondary node status will time out."
msgstr ""
@@ -22139,9 +22865,6 @@ msgstr ""
msgid "The total stage shows the time it takes between creating an issue and deploying the code to production. The data will be automatically added once you have completed the full idea to production cycle."
msgstr ""
-msgid "The unique identifier for the Geo node. Must match %{geoNodeName} if it is set in gitlab.rb, otherwise it must match %{externalUrl} with a trailing slash"
-msgstr ""
-
msgid "The update action will time out after %{number_of_minutes} minutes. For big repositories, use a clone/push combination."
msgstr ""
@@ -22166,9 +22889,6 @@ msgstr ""
msgid "The user you are trying to deactivate has been active in the past %{minimum_inactive_days} days and cannot be deactivated"
msgstr ""
-msgid "The user-facing URL of the Geo node"
-msgstr ""
-
msgid "The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6."
msgstr ""
@@ -22259,6 +22979,9 @@ msgstr ""
msgid "There was a problem fetching project branches."
msgstr ""
+msgid "There was a problem fetching project tags."
+msgstr ""
+
msgid "There was a problem fetching project users."
msgstr ""
@@ -22310,6 +23033,9 @@ msgstr ""
msgid "There was an error fetching the %{replicableType}"
msgstr ""
+msgid "There was an error fetching the Geo Settings"
+msgstr ""
+
msgid "There was an error fetching the Node's Groups"
msgstr ""
@@ -22430,6 +23156,12 @@ msgstr ""
msgid "This %{issuable} is locked. Only <strong>project members</strong> can comment."
msgstr ""
+msgid "This %{noteableTypeText} is %{confidentialLinkStart}confidential%{linkEnd} and %{lockedLinkStart}locked%{linkEnd}."
+msgstr ""
+
+msgid "This %{noteableTypeText} is locked."
+msgstr ""
+
msgid "This %{viewer} could not be displayed because %{reason}. You can %{options} instead."
msgstr ""
@@ -22448,6 +23180,9 @@ msgstr ""
msgid "This action can lead to data loss. To prevent accidental actions we ask you to confirm your intention."
msgstr ""
+msgid "This also resolves all related threads"
+msgstr ""
+
msgid "This also resolves the discussion"
msgstr ""
@@ -22538,6 +23273,9 @@ msgstr ""
msgid "This epic does not exist or you don't have sufficient permission."
msgstr ""
+msgid "This feature is currently in beta. We invite you to %{linkStart}give feedback%{linkEnd}."
+msgstr ""
+
msgid "This feature requires local storage to be enabled"
msgstr ""
@@ -22571,7 +23309,7 @@ msgstr ""
msgid "This is a Work in Progress"
msgstr ""
-msgid "This is a confidential issue."
+msgid "This is a confidential %{noteableTypeText}."
msgstr ""
msgid "This is a delayed job to run in %{remainingTime}"
@@ -22598,9 +23336,6 @@ msgstr ""
msgid "This is your current session"
msgstr ""
-msgid "This issue is %{confidentialLinkStart}confidential%{linkEnd} and %{lockedLinkStart}locked%{linkEnd}."
-msgstr ""
-
msgid "This issue is confidential"
msgstr ""
@@ -22610,9 +23345,6 @@ msgstr ""
msgid "This issue is in a child epic of the filtered epic"
msgstr ""
-msgid "This issue is locked."
-msgstr ""
-
msgid "This job depends on other jobs with expired/erased artifacts: %{invalid_dependencies}"
msgstr ""
@@ -22712,6 +23444,9 @@ msgstr ""
msgid "This license has already expired."
msgstr ""
+msgid "This link points to external content"
+msgstr ""
+
msgid "This may expose confidential information as the selected fork is in another namespace that can have other members."
msgstr ""
@@ -22739,6 +23474,9 @@ msgstr ""
msgid "This page will be removed in a future release."
msgstr ""
+msgid "This pipeline does not use the %{codeStart}needs%{codeEnd} keyword and can't be represented as a directed acyclic graph."
+msgstr ""
+
msgid "This pipeline makes use of a predefined CI/CD configuration enabled by %{strongStart}Auto DevOps.%{strongEnd}"
msgstr ""
@@ -22967,6 +23705,9 @@ msgstr ""
msgid "Time from last commit to merge"
msgstr ""
+msgid "Time in seconds"
+msgstr ""
+
msgid "Time in seconds GitLab will wait for a response from the external service. When the service does not respond in time, access will be denied."
msgstr ""
@@ -23388,9 +24129,15 @@ msgstr ""
msgid "Total artifacts size: %{total_size}"
msgstr ""
+msgid "Total cores (CPUs)"
+msgstr ""
+
msgid "Total issues"
msgstr ""
+msgid "Total memory (GB)"
+msgstr ""
+
msgid "Total test time for all commits/merges"
msgstr ""
@@ -23592,6 +24339,9 @@ msgstr ""
msgid "U2F only works with HTTPS-enabled websites. Contact your administrator for more details."
msgstr ""
+msgid "UI Development Kit"
+msgstr ""
+
msgid "URL"
msgstr ""
@@ -23664,9 +24414,6 @@ msgstr ""
msgid "Unable to load the merge request widget. Try reloading the page."
msgstr ""
-msgid "Unable to process group import file"
-msgstr ""
-
msgid "Unable to resolve"
msgstr ""
@@ -23682,6 +24429,9 @@ msgstr ""
msgid "Unable to sign you in to the group with SAML due to \"%{reason}\""
msgstr ""
+msgid "Unable to suggest a path. Please refresh and try again."
+msgstr ""
+
msgid "Unable to update label prioritization at this time"
msgstr ""
@@ -23697,6 +24447,9 @@ msgstr ""
msgid "Unarchiving the project will restore people's ability to make changes to it. The repository can be committed to, and issues, comments, and other entities can be created. %{strong_start}Once active, this project shows up in the search and on the dashboard.%{strong_end}"
msgstr ""
+msgid "Unassign from commenting user"
+msgstr ""
+
msgid "Unblock"
msgstr ""
@@ -23772,6 +24525,9 @@ msgstr ""
msgid "Unreachable"
msgstr ""
+msgid "Unrecognized cluster type"
+msgstr ""
+
msgid "Unresolve"
msgstr ""
@@ -23832,6 +24588,9 @@ msgstr ""
msgid "Unsubscribes from this %{quick_action_target}."
msgstr ""
+msgid "Unsupported todo type passed. Supported todo types are: %{todo_types}"
+msgstr ""
+
msgid "Until"
msgstr ""
@@ -23859,6 +24618,9 @@ msgstr ""
msgid "Update approval rule"
msgstr ""
+msgid "Update approvers"
+msgstr ""
+
msgid "Update failed"
msgstr ""
@@ -23868,6 +24630,9 @@ msgstr ""
msgid "Update it"
msgstr ""
+msgid "Update iteration"
+msgstr ""
+
msgid "Update now"
msgstr ""
@@ -23913,7 +24678,7 @@ msgstr ""
msgid "Updated at"
msgstr ""
-msgid "Updated to"
+msgid "Updated to %{linkStart}chart v%{linkEnd}"
msgstr ""
msgid "Updating"
@@ -23943,6 +24708,9 @@ msgstr ""
msgid "Upload CSV file"
msgstr ""
+msgid "Upload License"
+msgstr ""
+
msgid "Upload New File"
msgstr ""
@@ -24024,16 +24792,19 @@ msgstr ""
msgid "UsageQuota|Pipelines"
msgstr ""
+msgid "UsageQuota|Purchase more storage"
+msgstr ""
+
msgid "UsageQuota|Repositories"
msgstr ""
msgid "UsageQuota|Repository"
msgstr ""
-msgid "UsageQuota|Storage"
+msgid "UsageQuota|Snippets"
msgstr ""
-msgid "UsageQuota|Storage usage:"
+msgid "UsageQuota|Storage"
msgstr ""
msgid "UsageQuota|This namespace has no projects which use shared runners"
@@ -24066,6 +24837,12 @@ msgstr ""
msgid "UsageQuota|Wikis"
msgstr ""
+msgid "UsageQuota|You used: %{usage} %{limit}"
+msgstr ""
+
+msgid "UsageQuota|out of %{formattedLimit} of your namespace storage"
+msgstr ""
+
msgid "Use %{code_start}::%{code_end} to create a %{link_start}scoped label set%{link_end} (eg. %{code_start}priority::1%{code_end})"
msgstr ""
@@ -24132,6 +24909,9 @@ msgstr ""
msgid "User IDs"
msgstr ""
+msgid "User Lists can only be created and modified with %{linkStart}the API%{linkEnd}"
+msgstr ""
+
msgid "User OAuth applications"
msgstr ""
@@ -24156,6 +24936,9 @@ msgstr ""
msgid "User key was successfully removed."
msgstr ""
+msgid "User list %{name} will be removed. Are you sure?"
+msgstr ""
+
msgid "User map"
msgstr ""
@@ -24177,148 +24960,10 @@ msgstr ""
msgid "User was successfully updated."
msgstr ""
-msgid "UserOnboardingTour|%{activeTour}/%{totalTours}"
-msgstr ""
-
-msgid "UserOnboardingTour|%{completed}/%{total} steps completed"
-msgstr ""
-
-msgid "UserOnboardingTour|%{emphasisStart}Well done!%{emphasisEnd}%{lineBreak}%{lineBreak}That's it for our guided tour, congratulations for making it all the way to the end!%{lineBreak}%{lineBreak}We hope this gave you a good overview of GitLab and how it can help you. We'll now show you how to create your own project and invite your colleagues."
-msgstr ""
-
-msgid "UserOnboardingTour|Adding other members to a project is done through Project Settings. Click on %{emphasisStart}Settings%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Alright, that's it for Commits. Let's take a look at the %{emphasisStart}Branches%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Awesome! Now click on %{emphasisStart}Members%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Click on one of the %{emphasisStart}Compare%{emphasisEnd} buttons to compare a branch to master."
-msgstr ""
-
-msgid "UserOnboardingTour|Click on one of the %{emphasisStart}pipeline IDs%{emphasisEnd} to see the details of a pipeline."
-msgstr ""
-
-msgid "UserOnboardingTour|Click to open the latest commit to see its details."
-msgstr ""
-
-msgid "UserOnboardingTour|Close 'Learn GitLab'"
-msgstr ""
-
-msgid "UserOnboardingTour|Commits are shown in chronological order and can be filtered by the commit message or by the branch."
+msgid "UserList|Delete %{name}?"
msgstr ""
-msgid "UserOnboardingTour|Create a project"
-msgstr ""
-
-msgid "UserOnboardingTour|Exit 'Learn GitLab'"
-msgstr ""
-
-msgid "UserOnboardingTour|Got it"
-msgstr ""
-
-msgid "UserOnboardingTour|Great job! %{clapHands} We hope the tour was helpful and that you learned how to use GitLab.%{lineBreak}%{lineBreak}We'd love to get your feedback on this tour.%{lineBreak}%{lineBreak}%{emphasisStart}How helpful would you say this guided tour was?%{emphasisEnd}%{lineBreak}%{lineBreak}"
-msgstr ""
-
-msgid "UserOnboardingTour|Guided GitLab Tour"
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can compare the changes of this branch to another one. Changes are divided by files so that it's easier to see what was changed where."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can create a project from scratch, start with a template or import a repository from other platforms. Whatever you choose, we'll guide you through the process.%{lineBreak}%{lineBreak}Fill in your new project information and click on %{emphasisStart}Create Project%{emphasisEnd} to progress to the next step."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can see the breakdown of the pipelines: its stages and jobs in each of the stages and their status.%{lineBreak}%{lineBreak}Our CI/CD pipelines are quite complex, most of our users have fewer and simpler pipelines."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can see the current members of the project (just you at the moment) and invite new members.%{lineBreak}%{lineBreak}You can invite multiple members at once (existing GitLab users or invite by email) and you can also set their roles and permissions.%{lineBreak}%{lineBreak}Add a few members and click on %{emphasisStart}Add to project%{emphasisEnd} to complete this step."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can see what changes were made with this commit, on what branch and if there's a related merge request. The status of the pipeline will also show up if CI/CD is set up.%{lineBreak}%{lineBreak}You can also comment on the lines of code that were changed and start a discussion with your colleagues!"
-msgstr ""
-
-msgid "UserOnboardingTour|Here's an overview of branches in the %{emphasisStart}%{projectName}%{emphasisEnd} project. They're split into Active and Stale.%{lineBreak}%{lineBreak}From here, you can create a new merge request from a branch, or compare the branch to any other branch in the project. By default, it will compare it to the master branch."
-msgstr ""
-
-msgid "UserOnboardingTour|Invite colleagues"
-msgstr ""
-
-msgid "UserOnboardingTour|Issues are great for communicating and keeping track of progress in GitLab. These are all issues that are open in the %{emphasisStart}%{projectName}%{emphasisEnd}.%{lineBreak}%{lineBreak}You can help us improve GitLab by contributing work to issues that are labeled <span class=\"badge color-label accept-mr-label\">Accepting merge requests</span>.%{lineBreak}%{lineBreak}This list can be filtered by labels, milestones, assignees, authors... We'll show you how it looks when the list is filtered by a label."
-msgstr ""
-
-msgid "UserOnboardingTour|Learn GitLab"
-msgstr ""
-
-msgid "UserOnboardingTour|Let's take a closer look at a merge request. Click on the title of one."
-msgstr ""
-
-msgid "UserOnboardingTour|Let's take a closer look at all the commits. Click on %{emphasisStart}Commits%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Let's take a closer look at the repository of this project. Click on %{emphasisStart}Repository%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|No thanks"
-msgstr ""
-
-msgid "UserOnboardingTour|Ok, let's go"
-msgstr ""
-
-msgid "UserOnboardingTour|Ok, show me"
-msgstr ""
-
-msgid "UserOnboardingTour|Open one of the issues by clicking on its title."
-msgstr ""
-
-msgid "UserOnboardingTour|Restart this step"
-msgstr ""
-
-msgid "UserOnboardingTour|Skip this step"
-msgstr ""
-
-msgid "UserOnboardingTour|Sweet! Your project was created and is ready to be used.%{lineBreak}%{lineBreak}You can start adding files to the repository or clone it. One last thing we want to show you is how to invite your colleagues to your new project."
-msgstr ""
-
-msgid "UserOnboardingTour|Take a look. Here's a nifty menu for quickly creating issues, merge requests, snippets, projects and groups. Click on it and select \"New project\" from the \"GitLab\" section to get started."
-msgstr ""
-
-msgid "UserOnboardingTour|Thanks for taking the guided tour. Remember, if you want to go through it again, you can start %{emphasisStart}Learn GitLab%{emphasisEnd} in the help menu on the top right."
-msgstr ""
-
-msgid "UserOnboardingTour|Thanks for the feedback! %{thumbsUp}"
-msgstr ""
-
-msgid "UserOnboardingTour|That's it for issues. Let'st take a look at %{emphasisStart}Merge Requests%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|That's it for merge requests. Now for the final part of this guided tour - the %{emphasisStart}CI/CD%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|That's it for the Repository. Let's take a look at the %{emphasisStart}Issues%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|The structure of this page is very similar to issues. Status, description, discussion and the sidebar are all here.%{lineBreak}%{lineBreak}But take a look below the description and you'll notice that there's more information about the merge request, the CI/CD pipeline and the options for approving it.%{lineBreak}%{lineBreak}Alongside the discussion you can also see more information about commits in this merge request, the status of pipelines and review all changes that were made."
-msgstr ""
-
-msgid "UserOnboardingTour|There's a lot of information here but don't worry, we'll go through it.%{lineBreak}%{lineBreak}On the top you can see the status of the issue and when it was opened and by whom. Directly below it is the issue description and below that are other %{emphasisStart}related issues%{emphasisEnd} and %{emphasisStart}merge requests%{emphasisEnd} (if any). Then below that is the %{emphasisStart}discussion%{emphasisEnd}, that's where most of the communication happens.%{lineBreak}%{lineBreak}On the right, there's a sidebar where you can view/change the %{emphasisStart}assignee, milestone, due date, labels, weight%{emphasisEnd}, etc."
-msgstr ""
-
-msgid "UserOnboardingTour|These are all the CI/CD pipelines we have for our %{emphasisStart}%{projectName}%{emphasisEnd} project.%{lineBreak}%{lineBreak}Here you can see the status of each pipeline, for what commit it's running for, its stages and the status for them."
-msgstr ""
-
-msgid "UserOnboardingTour|These are all the issues that are available for community contributions. Let's take a closer look at one of them."
-msgstr ""
-
-msgid "UserOnboardingTour|This is an overview of all merge requests in this project. Similarly to the issues overview it can be filtered down by things like labels, milestones, authors, assignees, etc."
-msgstr ""
-
-msgid "UserOnboardingTour|This is the repository for the %{emphasisStart}%{projectName}%{emphasisEnd} project. All our code is stored here. Feel free to explore and take a closer look at folders and files.%{lineBreak}%{lineBreak}Above the file structure you can see the latest commit, who the author is and the status of the CI/CD pipeline.%{lineBreak}%{lineBreak}If you scroll down below the file structure, you'll find the Readme of this project. This is defined in the README.md file at the root of the repository."
-msgstr ""
-
-msgid "UserOnboardingTour|Welcome to the project overview of the %{emphasisStart}%{projectName}%{emphasisEnd} project. This is the project that we use to work on GitLab. At first, a project seems like a simple repository, but at GitLab, a project is so much more.%{lineBreak}%{lineBreak}You can create projects for hosting your codebase, use it as an issue tracker, collaborate on code, and continuously build, test, and deploy your app with built-in GitLab CI/CD."
+msgid "UserList|created %{timeago}"
msgstr ""
msgid "UserProfile|Activity"
@@ -24471,6 +25116,9 @@ msgstr ""
msgid "UsersSelect|Unassigned"
msgstr ""
+msgid "Using %{codeStart}needs%{codeEnd} allows jobs to run before their stage is reached, as soon as their individual dependencies are met, which speeds up your pipelines."
+msgstr ""
+
msgid "Using %{code_start}::%{code_end} denotes a %{link_start}scoped label set%{link_end}"
msgstr ""
@@ -24555,9 +25203,6 @@ msgstr ""
msgid "Versions"
msgstr ""
-msgid "Very helpful"
-msgstr ""
-
msgid "View Documentation"
msgstr ""
@@ -24706,6 +25351,9 @@ msgstr ""
msgid "VisibilityLevel|Unknown"
msgstr ""
+msgid "Visit settings page"
+msgstr ""
+
msgid "VisualReviewApp|%{stepStart}Step 1%{stepEnd}. Copy the following script:"
msgstr ""
@@ -24799,6 +25447,9 @@ msgstr ""
msgid "VulnerabilityManagement|Something went wrong while trying to delete the comment. Please try again later."
msgstr ""
+msgid "VulnerabilityManagement|Something went wrong while trying to refresh the vulnerability. Please try again later."
+msgstr ""
+
msgid "VulnerabilityManagement|Something went wrong while trying to retrieve the vulnerability history. Please try again later."
msgstr ""
@@ -24841,6 +25492,9 @@ msgstr ""
msgid "Vulnerability|Class"
msgstr ""
+msgid "Vulnerability|Crash Address"
+msgstr ""
+
msgid "Vulnerability|Description"
msgstr ""
@@ -24916,9 +25570,6 @@ msgstr ""
msgid "We couldn't reach the Prometheus server. Either the server no longer exists or the configuration details need updating."
msgstr ""
-msgid "We created a short guided tour that will help you learn the basics of GitLab and how it will help you be better at your job. It should only take a couple of minutes. You will be guided by two types of helpers, best recognized by their color."
-msgstr ""
-
msgid "We detected potential spam in the %{humanized_resource_name}. Please solve the reCAPTCHA to proceed."
msgstr ""
@@ -24952,6 +25603,9 @@ msgstr ""
msgid "We've found no vulnerabilities"
msgstr ""
+msgid "Web Application Firewall"
+msgstr ""
+
msgid "Web IDE"
msgstr ""
@@ -25012,9 +25666,6 @@ msgstr ""
msgid "Welcome to GitLab.com<br>@%{name}!"
msgstr ""
-msgid "Welcome to the Guided GitLab Tour"
-msgstr ""
-
msgid "Welcome to the guided GitLab tour"
msgstr ""
@@ -25062,9 +25713,6 @@ msgstr ""
msgid "While it's rare to have no vulnerabilities, it can happen. In any event, we ask that you please double check your settings to make sure you've set up your dashboard correctly."
msgstr ""
-msgid "White helpers give contextual information."
-msgstr ""
-
msgid "Who can be an approver?"
msgstr ""
@@ -25110,12 +25758,18 @@ msgstr ""
msgid "WikiEmptyIssueMessage|Suggest wiki improvement"
msgstr ""
+msgid "WikiEmptyIssueMessage|You must be a group member in order to add wiki pages. If you have suggestions for how to improve the wiki for this group, consider opening an issue in the %{issues_link}."
+msgstr ""
+
msgid "WikiEmptyIssueMessage|You must be a project member in order to add wiki pages. If you have suggestions for how to improve the wiki for this project, consider opening an issue in the %{issues_link}."
msgstr ""
msgid "WikiEmptyIssueMessage|issue tracker"
msgstr ""
+msgid "WikiEmpty|A wiki is where you can store all the details about your group. This can include why you've created it, its principles, how to use it, and so on."
+msgstr ""
+
msgid "WikiEmpty|A wiki is where you can store all the details about your project. This can include why you've created it, its principles, how to use it, and so on."
msgstr ""
@@ -25125,12 +25779,21 @@ msgstr ""
msgid "WikiEmpty|Suggest wiki improvement"
msgstr ""
+msgid "WikiEmpty|The wiki lets you write documentation for your group"
+msgstr ""
+
msgid "WikiEmpty|The wiki lets you write documentation for your project"
msgstr ""
+msgid "WikiEmpty|This group has no wiki pages"
+msgstr ""
+
msgid "WikiEmpty|This project has no wiki pages"
msgstr ""
+msgid "WikiEmpty|You must be a group member in order to add wiki pages."
+msgstr ""
+
msgid "WikiEmpty|You must be a project member in order to add wiki pages."
msgstr ""
@@ -25407,13 +26070,13 @@ msgstr ""
msgid "You can now export your security dashboard to a CSV report."
msgstr ""
-msgid "You can now submit a merge request to get this change into the original branch."
+msgid "You can now manage alert endpoint configuration in the Alerts section on the Operations settings page. Fields on this page have been deprecated."
msgstr ""
-msgid "You can now submit a merge request to get this change into the original project."
+msgid "You can now submit a merge request to get this change into the original branch."
msgstr ""
-msgid "You can only add files when you are on a branch"
+msgid "You can now submit a merge request to get this change into the original project."
msgstr ""
msgid "You can only edit files when you are on a branch"
@@ -25635,7 +26298,7 @@ msgstr ""
msgid "You need to upload a Google Takeout archive."
msgstr ""
-msgid "You reached %{usage_in_percent} of %{namespace_name}'s capacity (%{used_storage} of %{storage_limit})"
+msgid "You reached %{usage_in_percent} of %{namespace_name}'s storage capacity (%{used_storage} of %{storage_limit})"
msgstr ""
msgid "You tried to fork %{link_to_the_project} but it failed for the following reason:"
@@ -25938,13 +26601,13 @@ msgstr ""
msgid "Your subscription expired!"
msgstr ""
-msgid "Your subscription has been downgraded"
+msgid "Your subscription has been downgraded."
msgstr ""
-msgid "Your subscription will automatically renew in %{remaining_days}"
+msgid "Your subscription will automatically renew in %{remaining_days}."
msgstr ""
-msgid "Your subscription will expire in %{remaining_days}"
+msgid "Your subscription will expire in %{remaining_days}."
msgstr ""
msgid "Zoom meeting added"
@@ -26024,9 +26687,6 @@ msgstr ""
msgid "branch name"
msgstr ""
-msgid "build pipeline reference mismatch"
-msgstr ""
-
msgid "by"
msgstr ""
@@ -26045,6 +26705,9 @@ msgstr ""
msgid "cannot block others"
msgstr ""
+msgid "cannot contain HTML/XML tags, including any word between angle brackets (<,>)."
+msgstr ""
+
msgid "cannot include leading slash or directory traversal."
msgstr ""
@@ -26075,55 +26738,6 @@ msgstr ""
msgid "ciReport|%{remainingPackagesCount} more"
msgstr ""
-msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability"
-msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability for the source branch only"
-msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities for the source branch only"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerability"
-msgid_plural "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerabilities"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new vulnerability"
-msgid_plural "ciReport|%{reportType} %{status} detected %{newCount} new vulnerabilities"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities for the source branch only"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{fixedCount} fixed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} vulnerability for the source branch only"
-msgid_plural "ciReport|%{reportType} %{status} detected %{newCount} vulnerabilities for the source branch only"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "ciReport|%{reportType} %{status} detected no new vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected no vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected no vulnerabilities for the source branch only"
-msgstr ""
-
msgid "ciReport|%{reportType} is loading"
msgstr ""
@@ -26142,7 +26756,7 @@ msgstr ""
msgid "ciReport|All projects"
msgstr ""
-msgid "ciReport|All report types"
+msgid "ciReport|All scanner types"
msgstr ""
msgid "ciReport|All severities"
@@ -26166,6 +26780,9 @@ msgstr ""
msgid "ciReport|Container scanning detects known vulnerabilities in your docker images."
msgstr ""
+msgid "ciReport|Coverage Fuzzing"
+msgstr ""
+
msgid "ciReport|Create a merge request to implement this solution, or download and apply the patch manually."
msgstr ""
@@ -26324,6 +26941,9 @@ msgstr ""
msgid "customize"
msgstr ""
+msgid "data"
+msgstr ""
+
msgid "date must not be after 9999-12-31"
msgstr ""
@@ -26394,6 +27014,9 @@ msgstr ""
msgid "entries cannot contain HTML tags"
msgstr ""
+msgid "epic"
+msgstr ""
+
msgid "error"
msgstr ""
@@ -26415,9 +27038,6 @@ msgstr ""
msgid "expires on %{timebox_due_date}"
msgstr ""
-msgid "external_url"
-msgstr ""
-
msgid "failed"
msgstr ""
@@ -26453,9 +27073,6 @@ msgstr ""
msgid "from"
msgstr ""
-msgid "geo_node_name"
-msgstr ""
-
msgid "group"
msgstr ""
@@ -26477,9 +27094,6 @@ msgstr ""
msgid "https://your-bitbucket-server"
msgstr ""
-msgid "image"
-msgstr ""
-
msgid "image diff"
msgstr ""
@@ -26623,6 +27237,9 @@ msgstr ""
msgid "load it anyway"
msgstr ""
+msgid "loading"
+msgstr ""
+
msgid "locked by %{path_lock_user_name} %{created_at}"
msgstr ""
@@ -26751,9 +27368,6 @@ msgstr ""
msgid "mrWidget|Deployment statistics are not available currently"
msgstr ""
-msgid "mrWidget|Detect issues before deployment with a CI pipeline that continuously tests your code. We created a quick guide that will show you how to create one. Make your code more secure and more robust in just a minute."
-msgstr ""
-
msgid "mrWidget|Did not close"
msgstr ""
@@ -26916,6 +27530,9 @@ msgstr ""
msgid "mrWidget|To approve this merge request, please enter your password. This project requires all approvals to be authenticated."
msgstr ""
+msgid "mrWidget|Use %{linkStart}CI pipelines to test your code%{linkEnd}, simply add a GitLab CI configuration file to your project. It only takes a minute to make your code more secure and robust."
+msgstr ""
+
msgid "mrWidget|When this merge request is ready, remove the WIP: prefix from the title to allow it to be merged"
msgstr ""
@@ -26952,6 +27569,9 @@ msgstr ""
msgid "must be greater than start date"
msgstr ""
+msgid "must contain only valid frameworks"
+msgstr ""
+
msgid "my-awesome-group"
msgstr ""
@@ -26964,6 +27584,9 @@ msgstr ""
msgid "needs to be between 10 minutes and 1 month"
msgstr ""
+msgid "never"
+msgstr ""
+
msgid "never expires"
msgstr ""
@@ -26991,6 +27614,9 @@ msgstr ""
msgid "nounSeries|%{firstItem} and %{lastItem}"
msgstr ""
+msgid "nounSeries|%{item}"
+msgstr ""
+
msgid "nounSeries|%{item}, %{nextItem}"
msgstr ""
@@ -27127,6 +27753,9 @@ msgstr ""
msgid "revised"
msgstr ""
+msgid "satisfied"
+msgstr ""
+
msgid "score"
msgstr ""
@@ -27157,6 +27786,9 @@ msgstr ""
msgid "severity|Unknown"
msgstr ""
+msgid "should be an array of %{object_name} objects"
+msgstr ""
+
msgid "should be greater than or equal to %{access} inherited membership from group %{group_name}"
msgstr ""
@@ -27211,10 +27843,13 @@ msgstr ""
msgid "suggestPipeline|2/2: Commit your changes"
msgstr ""
-msgid "suggestPipeline|Commit the changes and your pipeline will automatically run for the first time."
+msgid "suggestPipeline|Choose %{boldStart}Code Quality%{boldEnd} to add a pipeline that tests the quality of your code."
msgstr ""
-msgid "suggestPipeline|We recommend the %{boldStart}Code Quality%{boldEnd} template, which will add a report widget to your Merge Requests. This way you’ll learn about code quality degradations much sooner. %{footerStart} Goodbye technical debt! %{footerEnd}"
+msgid "suggestPipeline|The template is ready! You can now commit it to create your first pipeline."
+msgstr ""
+
+msgid "suggestPipeline|We’re adding a GitLab CI configuration file to add a pipeline to the project. You could create it manually, but we recommend that you start with a GitLab template that works out of the box."
msgstr ""
msgid "syntax is correct"
@@ -27274,6 +27909,9 @@ msgstr ""
msgid "uses Kubernetes clusters to deploy your code!"
msgstr ""
+msgid "v%{version} published %{timeAgo}"
+msgstr ""
+
msgid "verify ownership"
msgstr ""
diff --git a/locale/fa_IR/gitlab.po b/locale/fa_IR/gitlab.po
index d16e8bf2da1..ad458d1df8f 100644
--- a/locale/fa_IR/gitlab.po
+++ b/locale/fa_IR/gitlab.po
@@ -2,7 +2,7 @@ msgid ""
msgstr ""
"Project-Id-Version: gitlab-ee\n"
"Report-Msgid-Bugs-To: \n"
-"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Last-Translator: \n"
"Language-Team: Persian\n"
"Language: fa_IR\n"
"MIME-Version: 1.0\n"
@@ -10,9 +10,11 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Crowdin-Project: gitlab-ee\n"
+"X-Crowdin-Project-ID: 288872\n"
"X-Crowdin-Language: fa\n"
"X-Crowdin-File: /master/locale/gitlab.pot\n"
-"PO-Revision-Date: 2020-06-08 15:10\n"
+"X-Crowdin-File-ID: 6\n"
+"PO-Revision-Date: 2020-07-02 01:20\n"
msgid " %{start} to %{end}"
msgstr ""
@@ -197,6 +199,11 @@ msgid_plural "%d metrics"
msgstr[0] ""
msgstr[1] ""
+msgid "%d milestone"
+msgid_plural "%d milestones"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "%d minute"
msgid_plural "%d minutes"
msgstr[0] ""
@@ -242,11 +249,6 @@ msgid_plural "%d unresolved threads"
msgstr[0] ""
msgstr[1] ""
-msgid "%d url scanned"
-msgid_plural "%d urls scanned"
-msgstr[0] ""
-msgstr[1] ""
-
msgid "%d vulnerability"
msgid_plural "%d vulnerabilities"
msgstr[0] ""
@@ -324,9 +326,15 @@ msgstr[1] ""
msgid "%{count} related %{pluralized_subject}: %{links}"
msgstr ""
+msgid "%{dashboard_path} could not be found."
+msgstr ""
+
msgid "%{days} days until tags are automatically removed"
msgstr ""
+msgid "%{deployLinkStart}Use a template to deploy to ECS%{deployLinkEnd}, or use a docker image to %{commandsLinkStart}run AWS commands in GitLab CI/CD%{commandsLinkEnd}."
+msgstr ""
+
msgid "%{description}- Sentry event: %{errorUrl}- First seen: %{firstSeen}- Last seen: %{lastSeen} %{countLabel}: %{count}%{userCountLabel}: %{userCount}"
msgstr ""
@@ -369,12 +377,18 @@ msgstr ""
msgid "%{host} sign-in from new location"
msgstr ""
-msgid "%{icon}You are about to add %{usersTag} people to the discussion. Proceed with caution."
+msgid "%{icon}You are about to add %{usersTag} people to the discussion. They will all receive a notification."
+msgstr ""
+
+msgid "%{integrations_link_start}Integrations%{link_end} enable you to make third-party applications part of your GitLab workflow. If the available integrations don't meet your needs, consider using a %{webhooks_link_start}webhook%{link_end}."
msgstr ""
msgid "%{issuableType} will be removed! Are you sure?"
msgstr ""
+msgid "%{issuesCount} issues in this group"
+msgstr ""
+
msgid "%{issuesSize} issues"
msgstr ""
@@ -384,7 +398,7 @@ msgstr ""
msgid "%{labelStart}Class:%{labelEnd} %{class}"
msgstr ""
-msgid "%{labelStart}Confidence:%{labelEnd} %{confidence}"
+msgid "%{labelStart}Evidence:%{labelEnd} %{evidence}"
msgstr ""
msgid "%{labelStart}File:%{labelEnd} %{file}"
@@ -402,6 +416,9 @@ msgstr ""
msgid "%{labelStart}Report Type:%{labelEnd} %{reportType}"
msgstr ""
+msgid "%{labelStart}Scanner:%{labelEnd} %{scanner}"
+msgstr ""
+
msgid "%{labelStart}Severity:%{labelEnd} %{severity}"
msgstr ""
@@ -426,9 +443,6 @@ msgstr ""
msgid "%{link_start}Read more%{link_end} about role permissions"
msgstr ""
-msgid "%{link} can be used for binding events when something is happening within the project."
-msgstr ""
-
msgid "%{listToShow}, and %{awardsListLength} more."
msgstr ""
@@ -462,6 +476,9 @@ msgstr ""
msgid "%{name} found %{resultsString}"
msgstr ""
+msgid "%{name} is already being used for another emoji"
+msgstr ""
+
msgid "%{name} is scheduled for %{action}"
msgstr ""
@@ -509,6 +526,9 @@ msgstr ""
msgid "%{primary} (%{secondary})"
msgstr ""
+msgid "%{ref} cannot be added: %{error}"
+msgstr ""
+
msgid "%{releases} release"
msgid_plural "%{releases} releases"
msgstr[0] ""
@@ -517,13 +537,37 @@ msgstr[1] ""
msgid "%{remaining_approvals} left"
msgstr ""
-msgid "%{retryButtonStart}Try again%{retryButtonEnd} or %{newFileButtonStart}attach a new file%{newFileButtonEnd}"
+msgid "%{reportType} %{status} detected %{critical} critical and %{high} high severity vulnerabilities out of %{total}."
+msgstr ""
+
+msgid "%{reportType} %{status} detected %{critical} critical and %{high} high severity vulnerabilities."
+msgstr ""
+
+msgid "%{reportType} %{status} detected %{critical} critical severity vulnerabilities out of %{total}."
+msgstr ""
+
+msgid "%{reportType} %{status} detected %{critical} critical severity vulnerability."
+msgid_plural "%{reportType} %{status} detected %{critical} critical severity vulnerabilities."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{reportType} %{status} detected %{high} high severity vulnerabilities out of %{total}."
msgstr ""
-msgid "%{screenreaderOnlyStart}Keyboard shorcuts%{screenreaderOnlyEnd} Disabled"
+msgid "%{reportType} %{status} detected %{high} high severity vulnerability."
+msgid_plural "%{reportType} %{status} detected %{high} high severity vulnerabilities."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{reportType} %{status} detected %{other} vulnerability."
+msgid_plural "%{reportType} %{status} detected %{other} vulnerabilities."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{reportType} %{status} detected no new vulnerabilities."
msgstr ""
-msgid "%{screenreaderOnlyStart}Keyboard shorcuts%{screenreaderOnlyEnd} Enabled"
+msgid "%{retryButtonStart}Try again%{retryButtonEnd} or %{newFileButtonStart}attach a new file%{newFileButtonEnd}"
msgstr ""
msgid "%{service_title} %{message}."
@@ -620,6 +664,12 @@ msgstr ""
msgid "%{token}..."
msgstr ""
+msgid "%{totalCpu} (%{freeSpacePercentage}%{percentSymbol} free)"
+msgstr ""
+
+msgid "%{totalMemory} (%{freeSpacePercentage}%{percentSymbol} free)"
+msgstr ""
+
msgid "%{totalWeight} total weight"
msgstr ""
@@ -650,6 +700,12 @@ msgstr ""
msgid "%{verb} %{time_spent_value} spent time."
msgstr ""
+msgid "%{webhooks_link_start}%{webhook_type}%{link_end} enable you to send notifications to web applications in response to events in a group or project."
+msgstr ""
+
+msgid "%{webhooks_link_start}%{webhook_type}%{link_end} enable you to send notifications to web applications in response to events in a group or project. We recommend using an %{integrations_link_start}integration%{link_end} in preference to a webhook."
+msgstr ""
+
msgid "'%{level}' is not a valid visibility level"
msgstr ""
@@ -676,9 +732,6 @@ msgstr ""
msgid "(No changes)"
msgstr ""
-msgid "(Show all)"
-msgstr ""
-
msgid "(check progress)"
msgstr ""
@@ -745,6 +798,9 @@ msgstr ""
msgid "0 for unlimited"
msgstr ""
+msgid "0 for unlimited, only effective with remote storage enabled."
+msgstr ""
+
msgid "1 %{type} addition"
msgid_plural "%{count} %{type} additions"
msgstr[0] ""
@@ -892,6 +948,12 @@ msgstr ""
msgid "<code>\"johnsmith@example.com\": \"johnsmith@example.com\"</code> will add \"By <a href=\"#\">johnsmith@example.com</a>\" to all issues and comments originally created by johnsmith@example.com. By default, the email address or username is masked to ensure the user's privacy. Use this option if you want to show the full email address."
msgstr ""
+msgid "<code>Masked</code> to prevent the values from being displayed in job logs (must match certain regexp requirements)."
+msgstr ""
+
+msgid "<code>Protected</code> to expose them to protected branches or tags only."
+msgstr ""
+
msgid "<namespace / project>"
msgstr ""
@@ -919,9 +981,6 @@ msgstr ""
msgid "A .NET Core console application template, customizable for any .NET Core project"
msgstr ""
-msgid "A DAG must have two dependent jobs to be visualized on this tab."
-msgstr ""
-
msgid "A GitBook site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
msgstr ""
@@ -952,6 +1011,12 @@ msgstr ""
msgid "A deleted user"
msgstr ""
+msgid "A file has been changed."
+msgstr ""
+
+msgid "A file was not found."
+msgstr ""
+
msgid "A file with '%{file_name}' already exists in %{branch} branch"
msgstr ""
@@ -1018,7 +1083,7 @@ msgstr ""
msgid "A subscription will trigger a new pipeline on the default branch of this project when a pipeline successfully completes for a new tag on the %{default_branch_docs} of the subscribed project."
msgstr ""
-msgid "A terraform report was generated in your pipelines."
+msgid "A suggestion is not applicable."
msgstr ""
msgid "A user with write access to the source branch selected this option"
@@ -1189,6 +1254,9 @@ msgstr ""
msgid "Action to take when receiving an alert."
msgstr ""
+msgid "Actions"
+msgstr ""
+
msgid "Activate"
msgstr ""
@@ -1245,6 +1313,9 @@ msgstr ""
msgid "Add LICENSE"
msgstr ""
+msgid "Add New Node"
+msgstr ""
+
msgid "Add README"
msgstr ""
@@ -1320,6 +1391,9 @@ msgstr ""
msgid "Add approval rule"
msgstr ""
+msgid "Add approvers"
+msgstr ""
+
msgid "Add bold text"
msgstr ""
@@ -1377,6 +1451,9 @@ msgstr ""
msgid "Add strikethrough text"
msgstr ""
+msgid "Add suggestion to batch"
+msgstr ""
+
msgid "Add system hook"
msgstr ""
@@ -1476,6 +1553,9 @@ msgstr ""
msgid "Admin Note"
msgstr ""
+msgid "Admin Notifications"
+msgstr ""
+
msgid "Admin Overview"
msgstr ""
@@ -1587,6 +1667,9 @@ msgstr ""
msgid "AdminSettings|Integrations configured here will automatically apply to all projects on this instance."
msgstr ""
+msgid "AdminSettings|Moved to integrations"
+msgstr ""
+
msgid "AdminSettings|No required pipeline"
msgstr ""
@@ -1817,12 +1900,18 @@ msgid_plural "Alerts"
msgstr[0] ""
msgstr[1] ""
+msgid "AlertManagement| assign yourself"
+msgstr ""
+
msgid "AlertManagement|Acknowledged"
msgstr ""
msgid "AlertManagement|Alert"
msgstr ""
+msgid "AlertManagement|Alert assignee(s): %{assignees}"
+msgstr ""
+
msgid "AlertManagement|Alert detail"
msgstr ""
@@ -1838,9 +1927,15 @@ msgstr ""
msgid "AlertManagement|All alerts"
msgstr ""
+msgid "AlertManagement|Assign To"
+msgstr ""
+
msgid "AlertManagement|Assign status"
msgstr ""
+msgid "AlertManagement|Assignee"
+msgstr ""
+
msgid "AlertManagement|Assignees"
msgstr ""
@@ -1859,9 +1954,6 @@ msgstr ""
msgid "AlertManagement|Edit"
msgstr ""
-msgid "AlertManagement|End time"
-msgstr ""
-
msgid "AlertManagement|Events"
msgstr ""
@@ -1880,10 +1972,13 @@ msgstr ""
msgid "AlertManagement|More information"
msgstr ""
+msgid "AlertManagement|No Matching Results"
+msgstr ""
+
msgid "AlertManagement|No alert data to display."
msgstr ""
-msgid "AlertManagement|No alerts available to display. If you think you're seeing this message in error, refresh the page."
+msgid "AlertManagement|No alerts available to display. See %{linkStart}enabling alert management%{linkEnd} for more information on adding alerts to the list."
msgstr ""
msgid "AlertManagement|No alerts to display."
@@ -1892,6 +1987,9 @@ msgstr ""
msgid "AlertManagement|None"
msgstr ""
+msgid "AlertManagement|None -"
+msgstr ""
+
msgid "AlertManagement|Open"
msgstr ""
@@ -1928,9 +2026,18 @@ msgstr ""
msgid "AlertManagement|There was an error displaying the alerts. Confirm your endpoint's configuration details to ensure alerts appear."
msgstr ""
+msgid "AlertManagement|There was an error while updating the assignee(s) list. Please try again."
+msgstr ""
+
+msgid "AlertManagement|There was an error while updating the assignee(s) of the alert. Please try again."
+msgstr ""
+
msgid "AlertManagement|There was an error while updating the status of the alert. Please try again."
msgstr ""
+msgid "AlertManagement|This assignee cannot be assigned to this alert."
+msgstr ""
+
msgid "AlertManagement|Tool"
msgstr ""
@@ -2078,6 +2185,9 @@ msgstr ""
msgid "Allow users to request access (if visibility is public or internal)"
msgstr ""
+msgid "Allowed Geo IP"
+msgstr ""
+
msgid "Allowed email domain restriction only permitted for top-level groups"
msgstr ""
@@ -2111,6 +2221,9 @@ msgstr ""
msgid "Amazon Web Services"
msgstr ""
+msgid "Amazon Web Services Logo"
+msgstr ""
+
msgid "Amazon authentication is not %{link_start}correctly configured%{link_end}. Ask your GitLab administrator if you want to use this service."
msgstr ""
@@ -2168,10 +2281,16 @@ msgstr ""
msgid "An error occurred when updating the issue weight"
msgstr ""
+msgid "An error occurred while acknowledging the notification. Refresh the page and try again."
+msgstr ""
+
+msgid "An error occurred while adding approvers"
+msgstr ""
+
msgid "An error occurred while adding formatted title for epic"
msgstr ""
-msgid "An error occurred while checking group path"
+msgid "An error occurred while checking group path. Please refresh and try again."
msgstr ""
msgid "An error occurred while committing your changes."
@@ -2315,7 +2434,7 @@ msgstr ""
msgid "An error occurred while loading milestones"
msgstr ""
-msgid "An error occurred while loading terraform report"
+msgid "An error occurred while loading project creation UI"
msgstr ""
msgid "An error occurred while loading the data. Please try again."
@@ -2369,6 +2488,9 @@ msgstr ""
msgid "An error occurred while rendering preview broadcast message"
msgstr ""
+msgid "An error occurred while rendering the editor"
+msgstr ""
+
msgid "An error occurred while reordering issues."
msgstr ""
@@ -2465,7 +2587,7 @@ msgstr ""
msgid "Analyze your dependencies for known vulnerabilities."
msgstr ""
-msgid "Analyze your source code and git history for secrets"
+msgid "Analyze your source code and git history for secrets."
msgstr ""
msgid "Analyze your source code for known vulnerabilities."
@@ -2579,12 +2701,18 @@ msgstr ""
msgid "Apply suggestion"
msgstr ""
+msgid "Apply suggestions"
+msgstr ""
+
msgid "Apply template"
msgstr ""
msgid "Apply this approval rule to any branch or a specific protected branch."
msgstr ""
+msgid "Applying"
+msgstr ""
+
msgid "Applying a template will replace the existing issue description. Any changes you have made will be lost."
msgstr ""
@@ -2597,12 +2725,18 @@ msgstr ""
msgid "Applying multiple commands"
msgstr ""
-msgid "Applying suggestion"
+msgid "Applying suggestion..."
+msgstr ""
+
+msgid "Applying suggestions..."
msgstr ""
msgid "Approval rules"
msgstr ""
+msgid "Approval rules reset to project defaults"
+msgstr ""
+
msgid "ApprovalRuleRemove|%d member"
msgid_plural "ApprovalRuleRemove|%d members"
msgstr[0] ""
@@ -2656,6 +2790,9 @@ msgstr ""
msgid "Approved"
msgstr ""
+msgid "Approved MRs"
+msgstr ""
+
msgid "Approved by: "
msgstr ""
@@ -2869,6 +3006,9 @@ msgstr ""
msgid "Assign"
msgstr ""
+msgid "Assign Iteration"
+msgstr ""
+
msgid "Assign custom color like #FF0000"
msgstr ""
@@ -2887,6 +3027,9 @@ msgstr ""
msgid "Assign to"
msgstr ""
+msgid "Assign to commenting user"
+msgstr ""
+
msgid "Assign yourself to these issues"
msgstr ""
@@ -2913,6 +3056,9 @@ msgid_plural "%d Assignees"
msgstr[0] ""
msgstr[1] ""
+msgid "Assignee has no permissions"
+msgstr ""
+
msgid "Assignee lists not available with your current license"
msgstr ""
@@ -2922,6 +3068,9 @@ msgstr ""
msgid "Assignee(s)"
msgstr ""
+msgid "Assignees"
+msgstr ""
+
msgid "Assigns %{assignee_users_sentence}."
msgstr ""
@@ -2957,6 +3106,9 @@ msgstr ""
msgid "Audit Events is a way to keep track of important events that happened in GitLab."
msgstr ""
+msgid "Audit Log"
+msgstr ""
+
msgid "AuditEvents|(removed)"
msgstr ""
@@ -2993,6 +3145,9 @@ msgstr ""
msgid "AuditLogs|IP Address"
msgstr ""
+msgid "AuditLogs|Member Events"
+msgstr ""
+
msgid "AuditLogs|No matching %{type} found."
msgstr ""
@@ -3275,9 +3430,6 @@ msgstr ""
msgid "Badges|e.g. %{exampleUrl}"
msgstr ""
-msgid "Badge|New"
-msgstr ""
-
msgid "Balsamiq file could not be loaded."
msgstr ""
@@ -3326,6 +3478,12 @@ msgstr ""
msgid "Below you will find all the groups that are public."
msgstr ""
+msgid "Beta"
+msgstr ""
+
+msgid "Bi-weekly code coverage"
+msgstr ""
+
msgid "Billing"
msgstr ""
@@ -3386,9 +3544,15 @@ msgstr ""
msgid "Bitbucket Server Import"
msgstr ""
+msgid "Bitbucket Server import"
+msgstr ""
+
msgid "Bitbucket import"
msgstr ""
+msgid "Blame"
+msgstr ""
+
msgid "Blocked"
msgstr ""
@@ -3401,9 +3565,6 @@ msgstr ""
msgid "Blog"
msgstr ""
-msgid "Blue helpers indicate an action to be taken."
-msgstr ""
-
msgid "Board name"
msgstr ""
@@ -3602,6 +3763,9 @@ msgstr ""
msgid "Broadcast Message was successfully updated."
msgstr ""
+msgid "Broadcast Messages"
+msgstr ""
+
msgid "Browse Directory"
msgstr ""
@@ -3656,6 +3820,9 @@ msgstr ""
msgid "By %{user_name}"
msgstr ""
+msgid "By URL"
+msgstr ""
+
msgid "By default GitLab sends emails in HTML and plain text formats so mail clients can choose what format to use. Disable this option if you only want to send emails in plain text format."
msgstr ""
@@ -3761,6 +3928,9 @@ msgstr ""
msgid "Can override approvers and approvals required per merge request"
msgstr ""
+msgid "Can't apply as this line has changed or the suggestion already matches its content."
+msgstr ""
+
msgid "Can't create snippet: %{err}"
msgstr ""
@@ -3947,9 +4117,6 @@ msgstr ""
msgid "Changes are still tracked. Useful for cluster/index migrations."
msgstr ""
-msgid "Changes are unknown"
-msgstr ""
-
msgid "Changes suppressed. Click to show."
msgstr ""
@@ -4256,15 +4423,9 @@ msgstr ""
msgid "Choose what content you want to see on a group’s overview page"
msgstr ""
-msgid "Choose which groups you wish to synchronize to this secondary node"
-msgstr ""
-
msgid "Choose which repositories you want to connect and run CI/CD pipelines."
msgstr ""
-msgid "Choose which shards you wish to synchronize to this secondary node"
-msgstr ""
-
msgid "Choose your framework"
msgstr ""
@@ -4562,9 +4723,15 @@ msgstr ""
msgid "Cluster does not exist"
msgstr ""
+msgid "Cluster is required for Stages::ClusterEndpointInserter"
+msgstr ""
+
msgid "Cluster level"
msgstr ""
+msgid "Cluster type must be specificed for Stages::ClusterEndpointInserter"
+msgstr ""
+
msgid "ClusterIntegration| %{custom_domain_start}More information%{custom_domain_end}."
msgstr ""
@@ -4676,7 +4843,7 @@ msgstr ""
msgid "ClusterIntegration|Cert-Manager"
msgstr ""
-msgid "ClusterIntegration|Cert-Manager is a native Kubernetes certificate management controller that helps with issuing certificates. Installing Cert-Manager on your cluster will issue a certificate by %{letsEncrypt} and ensure that certificates are valid and up-to-date."
+msgid "ClusterIntegration|Cert-Manager is a native Kubernetes certificate management controller that helps with issuing certificates. Installing Cert-Manager on your cluster will issue a certificate by %{linkStart}Let's Encrypt%{linkEnd} and ensure that certificates are valid and up-to-date."
msgstr ""
msgid "ClusterIntegration|Certificate Authority bundle (PEM format)"
@@ -4703,9 +4870,6 @@ msgstr ""
msgid "ClusterIntegration|Clear the local cache of namespace and service accounts. This is necessary if your integration has become out of sync. The cache is repopulated during the next CI job that requires namespace and service accounts."
msgstr ""
-msgid "ClusterIntegration|Cloud Run"
-msgstr ""
-
msgid "ClusterIntegration|Cluster being created"
msgstr ""
@@ -4790,7 +4954,7 @@ msgstr ""
msgid "ClusterIntegration|Crossplane"
msgstr ""
-msgid "ClusterIntegration|Crossplane enables declarative provisioning of managed services from your cloud of choice using %{codeStart}kubectl%{codeEnd} or %{gitlabIntegrationLink}. Crossplane runs inside your Kubernetes cluster and supports secure connectivity and secrets management between app containers and the cloud services they depend on."
+msgid "ClusterIntegration|Crossplane enables declarative provisioning of managed services from your cloud of choice using %{codeStart}kubectl%{codeEnd} or %{linkStart}GitLab Integration%{linkEnd}. Crossplane runs inside your Kubernetes cluster and supports secure connectivity and secrets management between app containers and the cloud services they depend on."
msgstr ""
msgid "ClusterIntegration|Deletes all GitLab resources attached to this cluster during removal"
@@ -4874,9 +5038,6 @@ msgstr ""
msgid "ClusterIntegration|GitLab-managed cluster"
msgstr ""
-msgid "ClusterIntegration|Gitlab Integration"
-msgstr ""
-
msgid "ClusterIntegration|Global default"
msgstr ""
@@ -4922,7 +5083,7 @@ msgstr ""
msgid "ClusterIntegration|Ingress gives you a way to route requests to services based on the request host or path, centralizing a number of services into a single entrypoint."
msgstr ""
-msgid "ClusterIntegration|Installing Ingress may incur additional costs. Learn more about %{pricingLink}."
+msgid "ClusterIntegration|Installing Ingress may incur additional costs. Learn more about %{linkStart}pricing%{linkEnd}."
msgstr ""
msgid "ClusterIntegration|Instance cluster"
@@ -4937,7 +5098,7 @@ msgstr ""
msgid "ClusterIntegration|Issuer Email"
msgstr ""
-msgid "ClusterIntegration|Issuers represent a certificate authority. You must provide an email address for your Issuer. "
+msgid "ClusterIntegration|Issuers represent a certificate authority. You must provide an email address for your Issuer."
msgstr ""
msgid "ClusterIntegration|Jupyter Hostname"
@@ -5009,9 +5170,6 @@ msgstr ""
msgid "ClusterIntegration|Learn more about instance Kubernetes clusters"
msgstr ""
-msgid "ClusterIntegration|Let's Encrypt"
-msgstr ""
-
msgid "ClusterIntegration|Loading IAM Roles"
msgstr ""
@@ -5123,7 +5281,7 @@ msgstr ""
msgid "ClusterIntegration|Prometheus"
msgstr ""
-msgid "ClusterIntegration|Prometheus is an open-source monitoring system with %{gitlabIntegrationLink} to monitor deployed applications."
+msgid "ClusterIntegration|Prometheus is an open-source monitoring system with %{linkStart}GitLab Integration%{linkEnd} to monitor deployed applications."
msgstr ""
msgid "ClusterIntegration|Provider details"
@@ -5279,10 +5437,10 @@ msgstr ""
msgid "ClusterIntegration|Select zone to choose machine type"
msgstr ""
-msgid "ClusterIntegration|Send Cilium Logs"
+msgid "ClusterIntegration|Send Container Network Policies Logs"
msgstr ""
-msgid "ClusterIntegration|Send ModSecurity Logs"
+msgid "ClusterIntegration|Send Web Application Firewall Logs"
msgstr ""
msgid "ClusterIntegration|Service Token"
@@ -5450,15 +5608,12 @@ msgstr ""
msgid "ClusterIntegration|documentation"
msgstr ""
-msgid "ClusterIntegration|installed via %{installed_via}"
+msgid "ClusterIntegration|installed via %{linkStart}Cloud Run%{linkEnd}"
msgstr ""
msgid "ClusterIntegration|meets the requirements"
msgstr ""
-msgid "ClusterIntegration|pricing"
-msgstr ""
-
msgid "ClusterIntegration|sign up"
msgstr ""
@@ -5495,6 +5650,15 @@ msgstr ""
msgid "Code"
msgstr ""
+msgid "Code Coverage: %{coveragePercentage}%{percentSymbol}"
+msgstr ""
+
+msgid "Code Coverage| Empty code coverage data"
+msgstr ""
+
+msgid "Code Coverage|Couldn't fetch the code coverage data"
+msgstr ""
+
msgid "Code Owners"
msgstr ""
@@ -5549,7 +5713,7 @@ msgstr ""
msgid "Collapse approvers"
msgstr ""
-msgid "Collapse child epics"
+msgid "Collapse milestones"
msgstr ""
msgid "Collapse replies"
@@ -5567,6 +5731,9 @@ msgstr ""
msgid "Coming soon"
msgstr ""
+msgid "Comma-separated, e.g. '1.1.1.1, 2.2.2.0/24'"
+msgstr ""
+
msgid "Command"
msgstr ""
@@ -5737,6 +5904,12 @@ msgstr ""
msgid "Compliance framework (optional)"
msgstr ""
+msgid "Compliance frameworks"
+msgstr ""
+
+msgid "ComplianceDashboard|created by:"
+msgstr ""
+
msgid "ComplianceFramework|GDPR"
msgstr ""
@@ -5875,6 +6048,9 @@ msgstr ""
msgid "Connection timed out"
msgstr ""
+msgid "Connection timeout"
+msgstr ""
+
msgid "Contact sales to upgrade"
msgstr ""
@@ -5930,9 +6106,6 @@ msgstr ""
msgid "ContainerRegistry|CLI Commands"
msgstr ""
-msgid "ContainerRegistry|Compressed Size"
-msgstr ""
-
msgid "ContainerRegistry|Container Registry"
msgstr ""
@@ -5948,6 +6121,9 @@ msgstr ""
msgid "ContainerRegistry|Copy push command"
msgstr ""
+msgid "ContainerRegistry|Delete selected"
+msgstr ""
+
msgid "ContainerRegistry|Docker connection error"
msgstr ""
@@ -5981,16 +6157,16 @@ msgstr ""
msgid "ContainerRegistry|If you are not already logged in, you need to authenticate to the Container Registry by using your GitLab username and password. If you have %{twofaDocLinkStart}Two-Factor Authentication%{twofaDocLinkEnd} enabled, use a %{personalAccessTokensDocLinkStart}Personal Access Token%{personalAccessTokensDocLinkEnd} instead of a password."
msgstr ""
-msgid "ContainerRegistry|Image ID"
+msgid "ContainerRegistry|Image ID: %{imageId}"
msgstr ""
msgid "ContainerRegistry|Image Repositories"
msgstr ""
-msgid "ContainerRegistry|Keep and protect the images that matter most."
+msgid "ContainerRegistry|Image tags"
msgstr ""
-msgid "ContainerRegistry|Last Updated"
+msgid "ContainerRegistry|Keep and protect the images that matter most."
msgstr ""
msgid "ContainerRegistry|Login"
@@ -6005,6 +6181,9 @@ msgstr ""
msgid "ContainerRegistry|Please contact your administrator."
msgstr ""
+msgid "ContainerRegistry|Published %{timeInfo}"
+msgstr ""
+
msgid "ContainerRegistry|Push an image"
msgstr ""
@@ -6020,9 +6199,6 @@ msgstr ""
msgid "ContainerRegistry|Remove repository"
msgstr ""
-msgid "ContainerRegistry|Remove selected tags"
-msgstr ""
-
msgid "ContainerRegistry|Remove tag"
msgid_plural "ContainerRegistry|Remove tags"
msgstr[0] ""
@@ -6052,9 +6228,6 @@ msgstr ""
msgid "ContainerRegistry|Sorry, your filter produced no results."
msgstr ""
-msgid "ContainerRegistry|Tag"
-msgstr ""
-
msgid "ContainerRegistry|Tag expiration policy"
msgstr ""
@@ -6199,21 +6372,6 @@ msgstr ""
msgid "Control the display of third party offers."
msgstr ""
-msgid "Control the maximum concurrency of LFS/attachment backfill for this secondary node"
-msgstr ""
-
-msgid "Control the maximum concurrency of container repository operations for this Geo node"
-msgstr ""
-
-msgid "Control the maximum concurrency of repository backfill for this secondary node"
-msgstr ""
-
-msgid "Control the maximum concurrency of verification operations for this Geo node"
-msgstr ""
-
-msgid "Control the minimum interval in days that a repository should be reverified for this primary node"
-msgstr ""
-
msgid "Cookie domain"
msgstr ""
@@ -6355,6 +6513,9 @@ msgstr ""
msgid "Could not find design."
msgstr ""
+msgid "Could not find iteration"
+msgstr ""
+
msgid "Could not remove the trigger."
msgstr ""
@@ -6391,6 +6552,12 @@ msgstr ""
msgid "Coverage"
msgstr ""
+msgid "Coverage Fuzzing"
+msgstr ""
+
+msgid "Crash State"
+msgstr ""
+
msgid "Create"
msgstr ""
@@ -6439,6 +6606,9 @@ msgstr ""
msgid "Create a personal access token on your account to pull or push via %{protocol}."
msgstr ""
+msgid "Create a project pre-populated with the necessary files to get you started quickly."
+msgstr ""
+
msgid "Create an account using:"
msgstr ""
@@ -6607,6 +6777,9 @@ msgstr ""
msgid "Creates branch '%{branch_name}' and a merge request to resolve this issue."
msgstr ""
+msgid "Creating"
+msgstr ""
+
msgid "Creating epic"
msgstr ""
@@ -6658,7 +6831,10 @@ msgstr ""
msgid "Current vulnerabilities count"
msgstr ""
-msgid "CurrentUser|Buy CI minutes"
+msgid "CurrentUser|Buy Pipeline minutes"
+msgstr ""
+
+msgid "CurrentUser|One of your groups is running out"
msgstr ""
msgid "CurrentUser|Profile"
@@ -6673,6 +6849,9 @@ msgstr ""
msgid "CurrentUser|Upgrade"
msgstr ""
+msgid "Custom Attributes"
+msgstr ""
+
msgid "Custom CI configuration path"
msgstr ""
@@ -6936,12 +7115,18 @@ msgstr ""
msgid "DAG"
msgstr ""
+msgid "DAG visualization requires at least 3 dependent jobs."
+msgstr ""
+
msgid "DNS"
msgstr ""
msgid "Dashboard"
msgstr ""
+msgid "Dashboard uid not found"
+msgstr ""
+
msgid "DashboardProjects|All"
msgstr ""
@@ -6966,6 +7151,9 @@ msgstr ""
msgid "Data is still calculating..."
msgstr ""
+msgid "Datasource name not found"
+msgstr ""
+
msgid "Date"
msgstr ""
@@ -7146,6 +7334,9 @@ msgstr ""
msgid "Delete this attachment"
msgstr ""
+msgid "Delete user list"
+msgstr ""
+
msgid "Delete variable"
msgstr ""
@@ -7462,6 +7653,9 @@ msgstr ""
msgid "Deploying to"
msgstr ""
+msgid "Deploying to AWS is easy with GitLab"
+msgstr ""
+
msgid "Deployment Frequency"
msgstr ""
@@ -7824,6 +8018,9 @@ msgstr ""
msgid "Dismissed on pipeline %{pipelineLink} at %{projectLink}"
msgstr ""
+msgid "Display alerts from all your monitoring tools directly within GitLab."
+msgstr ""
+
msgid "Display name"
msgstr ""
@@ -7881,9 +8078,6 @@ msgstr ""
msgid "Don't show again"
msgstr ""
-msgid "Don't worry, you can access this tour by clicking on the help icon in the top right corner and choose <strong>Learn GitLab</strong>."
-msgstr ""
-
msgid "Done"
msgstr ""
@@ -8010,6 +8204,9 @@ msgstr ""
msgid "Edit Release"
msgstr ""
+msgid "Edit Slack integration"
+msgstr ""
+
msgid "Edit Snippet"
msgstr ""
@@ -8049,6 +8246,9 @@ msgstr ""
msgid "Edit issues"
msgstr ""
+msgid "Edit iteration"
+msgstr ""
+
msgid "Edit public deploy key"
msgstr ""
@@ -8097,6 +8297,9 @@ msgstr ""
msgid "Email"
msgstr ""
+msgid "Email Notification"
+msgstr ""
+
msgid "Email address"
msgstr ""
@@ -8109,6 +8312,9 @@ msgstr ""
msgid "Email not verified. Please verify your email in Salesforce."
msgstr ""
+msgid "Email notification for unknown sign-ins"
+msgstr ""
+
msgid "Email patch"
msgstr ""
@@ -8340,6 +8546,9 @@ msgstr ""
msgid "Enforce DNS rebinding attack protection"
msgstr ""
+msgid "Enforce personal access token expiration"
+msgstr ""
+
msgid "Ensure connectivity is available from the GitLab server to the Prometheus server"
msgstr ""
@@ -8409,6 +8618,9 @@ msgstr ""
msgid "Enter the name of your application, and we'll return a unique %{type}."
msgstr ""
+msgid "Enter weights for storages for new repositories."
+msgstr ""
+
msgid "Enter your password to approve"
msgstr ""
@@ -8418,15 +8630,27 @@ msgstr ""
msgid "Environment does not have deployments"
msgstr ""
+msgid "Environment is required for Stages::MetricEndpointInserter"
+msgstr ""
+
+msgid "Environment is required for Stages::VariableEndpointInserter"
+msgstr ""
+
msgid "Environment scope"
msgstr ""
+msgid "Environment variables are applied to all project environments in this instance via the Runner. You can use environment variables for passwords, secret keys, etc. Make variables available to the running application by prepending the variable key with <code>K8S_SECRET_</code>. You can set variables to be:"
+msgstr ""
+
msgid "Environment variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. Additionally, they can be masked so they are hidden in job logs, though they must match certain regexp requirements to do so. You can use environment variables for passwords, secret keys, or whatever you want."
msgstr ""
msgid "Environment variables are configured by your administrator to be %{link_start}protected%{link_end} by default"
msgstr ""
+msgid "Environment variables on this GitLab instance are configured to be %{link_start}protected%{link_end} by default"
+msgstr ""
+
msgid "Environment:"
msgstr ""
@@ -8958,6 +9182,9 @@ msgstr ""
msgid "EventFilterBy|Filter by comments"
msgstr ""
+msgid "EventFilterBy|Filter by designs"
+msgstr ""
+
msgid "EventFilterBy|Filter by epic events"
msgstr ""
@@ -9090,15 +9317,15 @@ msgstr ""
msgid "Expand approvers"
msgstr ""
-msgid "Expand child epics"
-msgstr ""
-
msgid "Expand down"
msgstr ""
msgid "Expand dropdown"
msgstr ""
+msgid "Expand milestones"
+msgstr ""
+
msgid "Expand sidebar"
msgstr ""
@@ -9114,6 +9341,9 @@ msgstr ""
msgid "Expiration date"
msgstr ""
+msgid "Expiration not enforced"
+msgstr ""
+
msgid "Expiration policy for the Container Registry is a perfect solution for keeping the Registry space down while still enjoying the full power of GitLab CI/CD."
msgstr ""
@@ -9234,6 +9464,9 @@ msgstr ""
msgid "Failed Jobs"
msgstr ""
+msgid "Failed on"
+msgstr ""
+
msgid "Failed to add a Zoom meeting"
msgstr ""
@@ -9294,13 +9527,19 @@ msgstr ""
msgid "Failed to install."
msgstr ""
+msgid "Failed to load assignees. Please try again."
+msgstr ""
+
+msgid "Failed to load authors. Please try again."
+msgstr ""
+
msgid "Failed to load emoji list."
msgstr ""
msgid "Failed to load error details from Sentry."
msgstr ""
-msgid "Failed to load errors from Sentry. Error message: %{errorMessage}"
+msgid "Failed to load errors from Sentry."
msgstr ""
msgid "Failed to load group activity metrics. Please try again."
@@ -9381,6 +9620,9 @@ msgstr ""
msgid "Failed to set due date because the date format is invalid."
msgstr ""
+msgid "Failed to set iteration on this issue. Please try again."
+msgstr ""
+
msgid "Failed to signing using smartcard authentication"
msgstr ""
@@ -9603,13 +9845,10 @@ msgstr ""
msgid "FeatureFlags|Target environments"
msgstr ""
-msgid "FeatureFlags|There are no active feature flags"
-msgstr ""
-
-msgid "FeatureFlags|There are no inactive feature flags"
+msgid "FeatureFlags|There was an error fetching the feature flags."
msgstr ""
-msgid "FeatureFlags|There was an error fetching the feature flags."
+msgid "FeatureFlags|There was an error retrieving user lists"
msgstr ""
msgid "FeatureFlags|Try again in a few moments or contact your support team."
@@ -9621,9 +9860,18 @@ msgstr ""
msgid "FeatureFlag|Delete strategy"
msgstr ""
+msgid "FeatureFlag|List"
+msgstr ""
+
msgid "FeatureFlag|Percentage"
msgstr ""
+msgid "FeatureFlag|Select a user list"
+msgstr ""
+
+msgid "FeatureFlag|There are no configured user lists"
+msgstr ""
+
msgid "FeatureFlag|Type"
msgstr ""
@@ -9780,6 +10028,12 @@ msgstr ""
msgid "Filter..."
msgstr ""
+msgid "Find File"
+msgstr ""
+
+msgid "Find bugs in your code with coverage-guided fuzzing"
+msgstr ""
+
msgid "Find by path"
msgstr ""
@@ -10044,6 +10298,9 @@ msgstr ""
msgid "Geo allows you to replicate your GitLab instance to other geographical locations."
msgstr ""
+msgid "Geo nodes are paused using a command run on the node"
+msgstr ""
+
msgid "GeoNodeStatusEvent|%{timeAgoStr} (%{pendingEvents} events)"
msgstr ""
@@ -10128,6 +10385,9 @@ msgstr ""
msgid "GeoNodes|Not checksummed"
msgstr ""
+msgid "GeoNodes|Package files"
+msgstr ""
+
msgid "GeoNodes|Pausing replication stops the sync process. Are you sure?"
msgstr ""
@@ -10146,6 +10406,9 @@ msgstr ""
msgid "GeoNodes|Replication slots"
msgstr ""
+msgid "GeoNodes|Replication status"
+msgstr ""
+
msgid "GeoNodes|Repositories"
msgstr ""
@@ -10356,6 +10619,9 @@ msgstr ""
msgid "Geo|Synchronization failed - %{error}"
msgstr ""
+msgid "Geo|Synchronization of %{itemTitle} is disabled."
+msgstr ""
+
msgid "Geo|The database is currently %{db_lag} behind the primary node."
msgstr ""
@@ -10668,6 +10934,9 @@ msgstr ""
msgid "Go to %{link_to_google_takeout}."
msgstr ""
+msgid "Go to <strong>Issues</strong> > <strong>Boards</strong> to access your personalized learning issue board."
+msgstr ""
+
msgid "Go to Pipelines"
msgstr ""
@@ -10767,9 +11036,6 @@ msgstr ""
msgid "Go to your snippets"
msgstr ""
-msgid "Golden Tanuki"
-msgstr ""
-
msgid "Google Cloud Platform"
msgstr ""
@@ -10791,6 +11057,9 @@ msgstr ""
msgid "Grafana URL"
msgstr ""
+msgid "Grafana response contains invalid json"
+msgstr ""
+
msgid "GrafanaIntegration|API Token"
msgstr ""
@@ -10839,9 +11108,6 @@ msgstr ""
msgid "Group %{group_name} was successfully created."
msgstr ""
-msgid "Group '%{group_name}' is being imported."
-msgstr ""
-
msgid "Group Audit Events"
msgstr ""
@@ -10878,9 +11144,6 @@ msgstr ""
msgid "Group by:"
msgstr ""
-msgid "Group could not be imported: %{errors}"
-msgstr ""
-
msgid "Group description"
msgstr ""
@@ -10908,9 +11171,15 @@ msgstr ""
msgid "Group has not been marked for deletion"
msgstr ""
+msgid "Group import could not be scheduled"
+msgstr ""
+
msgid "Group info:"
msgstr ""
+msgid "Group is required when cluster_type is :group"
+msgstr ""
+
msgid "Group maintainers can register group runners in the %{link}"
msgstr ""
@@ -10974,6 +11243,24 @@ msgstr ""
msgid "GroupActivyMetrics|Recent activity (last 90 days)"
msgstr ""
+msgid "GroupImport|Failed to import group."
+msgstr ""
+
+msgid "GroupImport|Group '%{group_name}' is being imported."
+msgstr ""
+
+msgid "GroupImport|Group could not be imported: %{errors}"
+msgstr ""
+
+msgid "GroupImport|Please wait while we import the group for you. Refresh at will."
+msgstr ""
+
+msgid "GroupImport|The group was successfully imported."
+msgstr ""
+
+msgid "GroupImport|Unable to process group import file"
+msgstr ""
+
msgid "GroupRoadmap|%{dateWord} – No end date"
msgstr ""
@@ -11241,6 +11528,9 @@ msgstr ""
msgid "Groups (%{groups})"
msgstr ""
+msgid "Groups and projects"
+msgstr ""
+
msgid "Groups can also be nested by creating %{subgroup_docs_link_start}subgroups%{subgroup_docs_link_end}."
msgstr ""
@@ -11286,6 +11576,33 @@ msgstr ""
msgid "GroupsEmptyState|You can manage your group member’s permissions and access to each project in the group."
msgstr ""
+msgid "GroupsNew|Contact an administrator to enable options for importing your group."
+msgstr ""
+
+msgid "GroupsNew|Create"
+msgstr ""
+
+msgid "GroupsNew|Create group"
+msgstr ""
+
+msgid "GroupsNew|GitLab group export"
+msgstr ""
+
+msgid "GroupsNew|Import"
+msgstr ""
+
+msgid "GroupsNew|Import group"
+msgstr ""
+
+msgid "GroupsNew|My Awesome Group"
+msgstr ""
+
+msgid "GroupsNew|No import options available"
+msgstr ""
+
+msgid "GroupsNew|To copy a GitLab group between installations, navigate to the group settings page for the original installation, generate an export file, and upload it here."
+msgstr ""
+
msgid "GroupsTree|Are you sure you want to leave the \"%{fullName}\" group?"
msgstr ""
@@ -11388,6 +11705,9 @@ msgstr ""
msgid "Helps reduce request volume for protected paths"
msgstr ""
+msgid "Here are all your projects in your group, including the one you just created. To start, let’s take a look at your personalized learning project which will help you learn about GitLab at your own pace."
+msgstr ""
+
msgid "Here you will find recent merge request activity"
msgstr ""
@@ -11402,6 +11722,9 @@ msgid_plural "Hide charts"
msgstr[0] ""
msgstr[1] ""
+msgid "Hide details"
+msgstr ""
+
msgid "Hide file browser"
msgstr ""
@@ -11411,6 +11734,9 @@ msgstr ""
msgid "Hide host keys manual input"
msgstr ""
+msgid "Hide list"
+msgstr ""
+
msgid "Hide marketing-related entries from help"
msgstr ""
@@ -11431,9 +11757,6 @@ msgstr[1] ""
msgid "Hide values"
msgstr ""
-msgid "Hiding all labels"
-msgstr ""
-
msgid "High or unknown vulnerabilities present"
msgstr ""
@@ -11518,6 +11841,9 @@ msgstr ""
msgid "ID:"
msgstr ""
+msgid "IDE"
+msgstr ""
+
msgid "IDE|Allow live previews of JavaScript projects in the Web IDE using CodeSandbox Live Preview."
msgstr ""
@@ -11608,10 +11934,10 @@ msgstr ""
msgid "If enabled"
msgstr ""
-msgid "If enabled, access to projects will be validated on an external service using their classification label."
+msgid "If enabled, GitLab will handle Object Storage replication using Geo. %{linkStart}More information%{linkEnd}"
msgstr ""
-msgid "If enabled, and if object storage is enabled, GitLab will handle Object Storage replication using Geo"
+msgid "If enabled, access to projects will be validated on an external service using their classification label."
msgstr ""
msgid "If there is no previous license or if the previous license has expired, some GitLab functionality will be blocked until a new, valid license is uploaded."
@@ -11973,6 +12299,9 @@ msgid_plural "Instances"
msgstr[0] ""
msgstr[1] ""
+msgid "Instance Configuration"
+msgstr ""
+
msgid "Instance Statistics visibility"
msgstr ""
@@ -11994,9 +12323,6 @@ msgstr ""
msgid "Integrations"
msgstr ""
-msgid "Integrations allow you to integrate GitLab with other applications"
-msgstr ""
-
msgid "Integrations|All details"
msgstr ""
@@ -12108,6 +12434,9 @@ msgstr ""
msgid "Invalid query"
msgstr ""
+msgid "Invalid repository bundle for snippet with id %{snippet_id}"
+msgstr ""
+
msgid "Invalid repository path"
msgstr ""
@@ -12126,6 +12455,9 @@ msgstr ""
msgid "Invalid two-factor code."
msgstr ""
+msgid "Invalid yaml"
+msgstr ""
+
msgid "Invitation"
msgstr ""
@@ -12345,15 +12677,24 @@ msgstr ""
msgid "It seems like the Dependency Scanning job ran successfully, but no dependencies have been detected in your project."
msgstr ""
+msgid "It seems that there is currently no available data for code coverage"
+msgstr ""
+
msgid "It's you"
msgstr ""
+msgid "Iteration"
+msgstr ""
+
msgid "Iteration changed to"
msgstr ""
msgid "Iteration removed"
msgstr ""
+msgid "Iteration updated"
+msgstr ""
+
msgid "Iterations"
msgstr ""
@@ -12384,12 +12725,18 @@ msgstr ""
msgid "January"
msgstr ""
+msgid "Jira Issues"
+msgstr ""
+
msgid "Jira import is already running."
msgstr ""
msgid "Jira integration not configured."
msgstr ""
+msgid "Jira project key is not configured"
+msgstr ""
+
msgid "Jira project: %{importProject}"
msgstr ""
@@ -12570,6 +12917,9 @@ msgstr ""
msgid "Keep divergent refs"
msgstr ""
+msgid "Kerberos access denied"
+msgstr ""
+
msgid "Key"
msgstr ""
@@ -12579,10 +12929,13 @@ msgstr ""
msgid "Key: %{key}"
msgstr ""
-msgid "Keyboard Shortcuts"
+msgid "Keyboard shortcuts"
msgstr ""
-msgid "Keyboard shortcuts"
+msgid "Keys"
+msgstr ""
+
+msgid "Ki"
msgstr ""
msgid "Kubernetes"
@@ -12624,6 +12977,9 @@ msgstr ""
msgid "LDAP"
msgstr ""
+msgid "LDAP Synchronization"
+msgstr ""
+
msgid "LDAP settings"
msgstr ""
@@ -12848,12 +13204,18 @@ msgstr ""
msgid "Learn more about custom project templates"
msgstr ""
+msgid "Learn more about deploying to AWS"
+msgstr ""
+
msgid "Learn more about deploying to a cluster"
msgstr ""
msgid "Learn more about group-level project templates"
msgstr ""
+msgid "Learn more about job dependencies"
+msgstr ""
+
msgid "Learn more about signing commits"
msgstr ""
@@ -12905,9 +13267,21 @@ msgstr ""
msgid "License History"
msgstr ""
+msgid "License ID:"
+msgstr ""
+
+msgid "License URL"
+msgstr ""
+
msgid "License-Check"
msgstr ""
+msgid "LicenseCompliance|%{docLinkStart}License Approvals%{docLinkEnd} are active"
+msgstr ""
+
+msgid "LicenseCompliance|%{docLinkStart}License Approvals%{docLinkEnd} are inactive"
+msgstr ""
+
msgid "LicenseCompliance|Add a license"
msgstr ""
@@ -12929,9 +13303,15 @@ msgstr ""
msgid "LicenseCompliance|Deny"
msgstr ""
+msgid "LicenseCompliance|Learn more about %{linkStart}License Approvals%{linkEnd}"
+msgstr ""
+
msgid "LicenseCompliance|License"
msgstr ""
+msgid "LicenseCompliance|License Approvals"
+msgstr ""
+
msgid "LicenseCompliance|License Compliance detected %d license and policy violation for the source branch only; approval required"
msgid_plural "LicenseCompliance|License Compliance detected %d licenses and policy violations for the source branch only; approval required"
msgstr[0] ""
@@ -13152,6 +13532,9 @@ msgstr ""
msgid "List your Bitbucket Server repositories"
msgstr ""
+msgid "Lists"
+msgstr ""
+
msgid "Live preview"
msgstr ""
@@ -13335,6 +13718,9 @@ msgstr ""
msgid "Manage project labels"
msgstr ""
+msgid "Manage storage usage"
+msgstr ""
+
msgid "Manage two-factor authentication"
msgstr ""
@@ -13350,6 +13736,9 @@ msgstr ""
msgid "Manifest file import"
msgstr ""
+msgid "Manifest import"
+msgstr ""
+
msgid "Manual job"
msgstr ""
@@ -13497,6 +13886,12 @@ msgstr ""
msgid "Maximum field length"
msgstr ""
+msgid "Maximum file size is 2MB. Please select a smaller file."
+msgstr ""
+
+msgid "Maximum import size (MB)"
+msgstr ""
+
msgid "Maximum job timeout"
msgstr ""
@@ -13536,6 +13931,9 @@ msgstr ""
msgid "Maximum size of Elasticsearch bulk indexing requests."
msgstr ""
+msgid "Maximum size of import files."
+msgstr ""
+
msgid "Maximum size of individual attachments in comments."
msgstr ""
@@ -13587,6 +13985,9 @@ msgstr ""
msgid "Merge (when the pipeline succeeds)"
msgstr ""
+msgid "Merge Conflicts"
+msgstr ""
+
msgid "Merge Request"
msgstr ""
@@ -13713,6 +14114,9 @@ msgstr ""
msgid "MergeRequests|Squash task canceled: another squash is already in progress."
msgstr ""
+msgid "MergeRequests|This project does not allow squashing commits when merge requests are accepted."
+msgstr ""
+
msgid "MergeRequests|Thread stays resolved"
msgstr ""
@@ -13866,6 +14270,12 @@ msgstr ""
msgid "MetricsSettings|Add a button to the metrics dashboard linking directly to your existing external dashboard."
msgstr ""
+msgid "MetricsSettings|Choose whether to display dashboard metrics in UTC or the user's local timezone."
+msgstr ""
+
+msgid "MetricsSettings|Dashboard timezone"
+msgstr ""
+
msgid "MetricsSettings|External dashboard URL"
msgstr ""
@@ -13875,6 +14285,12 @@ msgstr ""
msgid "MetricsSettings|Metrics Dashboard"
msgstr ""
+msgid "MetricsSettings|UTC (Coordinated Universal Time)"
+msgstr ""
+
+msgid "MetricsSettings|User's local timezone"
+msgstr ""
+
msgid "Metrics|Add metric"
msgstr ""
@@ -13890,6 +14306,9 @@ msgstr ""
msgid "Metrics|Create metric"
msgstr ""
+msgid "Metrics|Current"
+msgstr ""
+
msgid "Metrics|Delete metric"
msgstr ""
@@ -13940,6 +14359,9 @@ msgstr ""
msgid "Metrics|Max"
msgstr ""
+msgid "Metrics|Min"
+msgstr ""
+
msgid "Metrics|Must be a valid PromQL query."
msgstr ""
@@ -13955,6 +14377,9 @@ msgstr ""
msgid "Metrics|Refresh dashboard"
msgstr ""
+msgid "Metrics|Select a value"
+msgstr ""
+
msgid "Metrics|Star dashboard"
msgstr ""
@@ -13973,12 +14398,18 @@ msgstr ""
msgid "Metrics|There was an error getting annotations information."
msgstr ""
+msgid "Metrics|There was an error getting dashboard validation warnings information."
+msgstr ""
+
msgid "Metrics|There was an error getting deployment information."
msgstr ""
msgid "Metrics|There was an error getting environments information."
msgstr ""
+msgid "Metrics|There was an error getting options for variable \"%{name}\"."
+msgstr ""
+
msgid "Metrics|There was an error trying to validate your query"
msgstr ""
@@ -14021,6 +14452,9 @@ msgstr ""
msgid "Metrics|You're about to permanently delete this metric. This cannot be undone."
msgstr ""
+msgid "Metrics|Your dashboard schema is invalid. Edit the dashboard to correct the YAML schema."
+msgstr ""
+
msgid "Metrics|e.g. HTTP requests"
msgstr ""
@@ -14036,12 +14470,18 @@ msgstr ""
msgid "Metrics|e.g. req/sec"
msgstr ""
+msgid "Mi"
+msgstr ""
+
msgid "Microsoft Azure"
msgstr ""
msgid "Middleman project with Static Site Editor support"
msgstr ""
+msgid "Migrate your data from an external source like GitHub, Bitbucket, or another instance of GitLab."
+msgstr ""
+
msgid "Migrated %{success_count}/%{total_count} files."
msgstr ""
@@ -14176,6 +14616,9 @@ msgstr ""
msgid "Minimum capacity to be available before we schedule more mirrors preemptively."
msgstr ""
+msgid "Minimum interval in days"
+msgstr ""
+
msgid "Minimum length is %{minimum_password_length} characters"
msgstr ""
@@ -14362,6 +14805,12 @@ msgstr ""
msgid "Multiple uploaders found: %{uploader_types}"
msgstr ""
+msgid "Must match with the %{codeStart}external_url%{codeEnd} in %{codeStart}/etc/gitlab/gitlab.rb%{codeEnd}."
+msgstr ""
+
+msgid "Must match with the %{codeStart}geo_node_name%{codeEnd} in %{codeStart}/etc/gitlab/gitlab.rb%{codeEnd}. %{linkStart}More information%{linkEnd}"
+msgstr ""
+
msgid "My Awesome Group"
msgstr ""
@@ -14476,10 +14925,16 @@ msgstr ""
msgid "New Application"
msgstr ""
+msgid "New Branch"
+msgstr ""
+
+msgid "New Deploy Key"
+msgstr ""
+
msgid "New Environment"
msgstr ""
-msgid "New Geo Node"
+msgid "New File"
msgstr ""
msgid "New Group"
@@ -14496,15 +14951,15 @@ msgid_plural "New Issues"
msgstr[0] ""
msgstr[1] ""
-msgid "New Iteration"
-msgstr ""
-
msgid "New Jira import"
msgstr ""
msgid "New Label"
msgstr ""
+msgid "New Merge Request"
+msgstr ""
+
msgid "New Milestone"
msgstr ""
@@ -14523,6 +14978,9 @@ msgstr ""
msgid "New Snippet"
msgstr ""
+msgid "New User"
+msgstr ""
+
msgid "New branch"
msgstr ""
@@ -14565,6 +15023,9 @@ msgstr ""
msgid "New issue title"
msgstr ""
+msgid "New iteration"
+msgstr ""
+
msgid "New iteration created"
msgstr ""
@@ -14742,6 +15203,12 @@ msgstr ""
msgid "No grouping"
msgstr ""
+msgid "No iteration"
+msgstr ""
+
+msgid "No iterations to show"
+msgstr ""
+
msgid "No job log"
msgstr ""
@@ -14784,6 +15251,9 @@ msgstr ""
msgid "No other labels with such name or description"
msgstr ""
+msgid "No panels matching properties %{opts}"
+msgstr ""
+
msgid "No parent group"
msgstr ""
@@ -14817,6 +15287,9 @@ msgstr ""
msgid "No schedules"
msgstr ""
+msgid "No stack trace for this error"
+msgstr ""
+
msgid "No starrers matched your search"
msgstr ""
@@ -14829,7 +15302,7 @@ msgstr ""
msgid "No test coverage"
msgstr ""
-msgid "No thanks, don't show this again"
+msgid "No thanks"
msgstr ""
msgid "No vulnerabilities present"
@@ -14844,9 +15317,6 @@ msgstr ""
msgid "No, directly import the existing email addresses and usernames."
msgstr ""
-msgid "No, not interested right now"
-msgstr ""
-
msgid "No. of commits"
msgstr ""
@@ -14892,9 +15362,6 @@ msgstr ""
msgid "Not found."
msgstr ""
-msgid "Not helpful"
-msgstr ""
-
msgid "Not now"
msgstr ""
@@ -15051,6 +15518,9 @@ msgstr ""
msgid "Notifications on"
msgstr ""
+msgid "Notify users by email when sign-in location is not recognized"
+msgstr ""
+
msgid "Nov"
msgstr ""
@@ -15117,9 +15587,6 @@ msgstr ""
msgid "Oh no!"
msgstr ""
-msgid "Ok let's go"
-msgstr ""
-
msgid "Oldest first"
msgstr ""
@@ -15132,16 +15599,49 @@ msgstr ""
msgid "On track"
msgstr ""
+msgid "OnDemandScans|Attached branch"
+msgstr ""
+
+msgid "OnDemandScans|Attached branch is where the scan job runs."
+msgstr ""
+
+msgid "OnDemandScans|Could not run the scan. Please try again."
+msgstr ""
+
msgid "OnDemandScans|Create new DAST scan"
msgstr ""
+msgid "OnDemandScans|DAST will scan the target URL and any discovered sub URLs."
+msgstr ""
+
+msgid "OnDemandScans|New on-demand DAST scan"
+msgstr ""
+
msgid "OnDemandScans|On-demand Scans"
msgstr ""
+msgid "OnDemandScans|On-demand scans run outside the DevOps cycle and find vulnerabilities in your projects. %{learnMoreLinkStart}Learn more%{learnMoreLinkEnd}"
+msgstr ""
+
+msgid "OnDemandScans|Only a passive scan can be performed on demand."
+msgstr ""
+
+msgid "OnDemandScans|Passive DAST Scan"
+msgstr ""
+
+msgid "OnDemandScans|Please enter a valid URL format, ex: http://www.example.com/home"
+msgstr ""
+
+msgid "OnDemandScans|Run this scan"
+msgstr ""
+
+msgid "OnDemandScans|Scan mode"
+msgstr ""
+
msgid "OnDemandScans|Schedule or run scans immediately against target sites. Currently available on-demand scan type: DAST. %{helpLinkStart}More information%{helpLinkEnd}"
msgstr ""
-msgid "Onboarding"
+msgid "OnDemandScans|Target URL"
msgstr ""
msgid "Once imported, repositories can be mirrored over SSH. Read more %{link_start}here%{link_end}."
@@ -15167,6 +15667,9 @@ msgstr ""
msgid "One or more of you personal access tokens were revoked"
msgstr ""
+msgid "One or more of your %{provider} projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
+msgstr ""
+
msgid "One or more of your Bitbucket projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
msgstr ""
@@ -15668,6 +16171,9 @@ msgstr ""
msgid "Parent epic is not present."
msgstr ""
+msgid "Parsing error for param :embed_json. %{message}"
+msgstr ""
+
msgid "Part of merge request changes"
msgstr ""
@@ -15680,6 +16186,9 @@ msgstr ""
msgid "Passed"
msgstr ""
+msgid "Passed on"
+msgstr ""
+
msgid "Password"
msgstr ""
@@ -15764,6 +16273,9 @@ msgstr ""
msgid "Perform common operations on GitLab project"
msgstr ""
+msgid "Performance and resource management"
+msgstr ""
+
msgid "Performance optimization"
msgstr ""
@@ -15971,7 +16483,7 @@ msgstr ""
msgid "Pipelines|Something went wrong while cleaning runners cache."
msgstr ""
-msgid "Pipelines|There are currently no %{scope} pipelines."
+msgid "Pipelines|There are currently no finished pipelines."
msgstr ""
msgid "Pipelines|There are currently no pipelines."
@@ -16082,6 +16594,9 @@ msgstr ""
msgid "Pipeline|Stop pipeline #%{pipelineId}?"
msgstr ""
+msgid "Pipeline|Tag name"
+msgstr ""
+
msgid "Pipeline|Trigger author"
msgstr ""
@@ -16163,12 +16678,18 @@ msgstr ""
msgid "Please check your email (%{email}) to verify that you own this address and unlock the power of CI/CD. Didn't receive it? %{resend_link}. Wrong email address? %{update_link}."
msgstr ""
+msgid "Please choose a file"
+msgstr ""
+
msgid "Please choose a group URL with no special characters."
msgstr ""
msgid "Please complete your profile with email address"
msgstr ""
+msgid "Please convert %{linkStart}them to Git%{linkEnd}, and go through the %{linkToImportFlow} again."
+msgstr ""
+
msgid "Please convert them to %{link_to_git}, and go through the %{link_to_import_flow} again."
msgstr ""
@@ -16226,6 +16747,9 @@ msgstr ""
msgid "Please provide a valid email address."
msgstr ""
+msgid "Please provide attributes to update"
+msgstr ""
+
msgid "Please refer to <a href=\"%{docs_url}\">%{docs_url}</a>"
msgstr ""
@@ -16961,6 +17485,9 @@ msgstr ""
msgid "Project has too many %{label_for_message} to search"
msgstr ""
+msgid "Project is required when cluster_type is :project"
+msgstr ""
+
msgid "Project members"
msgstr ""
@@ -17078,7 +17605,40 @@ msgstr ""
msgid "ProjectService|Comment"
msgstr ""
-msgid "ProjectService|Comment will be posted on each event"
+msgid "ProjectService|Event will be triggered by a push to the repository"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a commit is created/updated"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a confidential issue is created/updated/closed"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a deployment finishes"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a merge request is created/updated/merged"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a new tag is pushed to the repository"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a new, unique alert is recorded"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a pipeline status changes"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a wiki page is created/updated"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when an issue is created/updated/closed"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when someone adds a comment"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when someone adds a comment on a confidential issue"
msgstr ""
msgid "ProjectService|Perform common operations on GitLab project: %{project_name}"
@@ -17129,6 +17689,9 @@ msgstr ""
msgid "ProjectSettings|Enable 'Delete source branch' option by default"
msgstr ""
+msgid "ProjectSettings|Enable merge trains and pipelines for merged results"
+msgstr ""
+
msgid "ProjectSettings|Every merge creates a merge commit"
msgstr ""
@@ -17159,7 +17722,7 @@ msgstr ""
msgid "ProjectSettings|Forks"
msgstr ""
-msgid "ProjectSettings|Git Large File Storage"
+msgid "ProjectSettings|Git Large File Storage (LFS)"
msgstr ""
msgid "ProjectSettings|Internal"
@@ -17195,9 +17758,6 @@ msgstr ""
msgid "ProjectSettings|Merge options"
msgstr ""
-msgid "ProjectSettings|Merge pipelines will try to validate the post-merge result prior to merging"
-msgstr ""
-
msgid "ProjectSettings|Merge requests"
msgstr ""
@@ -17225,6 +17785,9 @@ msgstr ""
msgid "ProjectSettings|Pipelines"
msgstr ""
+msgid "ProjectSettings|Pipelines for merge requests must be enabled in the CI/CD configuration file, or pipelines could be unresolvable or dropped"
+msgstr ""
+
msgid "ProjectSettings|Pipelines must succeed"
msgstr ""
@@ -17252,6 +17815,9 @@ msgstr ""
msgid "ProjectSettings|Show link to create/view merge request when pushing from the command line"
msgstr ""
+msgid "ProjectSettings|Skipped pipelines are considered successful"
+msgstr ""
+
msgid "ProjectSettings|Snippets"
msgstr ""
@@ -17267,6 +17833,9 @@ msgstr ""
msgid "ProjectSettings|These checks must pass before merge requests can be merged"
msgstr ""
+msgid "ProjectSettings|This introduces the risk of merging changes that will not pass the pipeline."
+msgstr ""
+
msgid "ProjectSettings|This setting is applied on the server level and can be overridden by an admin."
msgstr ""
@@ -17444,15 +18013,27 @@ msgstr ""
msgid "ProjectsNew|Blank project"
msgstr ""
+msgid "ProjectsNew|Connect your external repository to GitLab CI/CD."
+msgstr ""
+
msgid "ProjectsNew|Contact an administrator to enable options for importing your project."
msgstr ""
msgid "ProjectsNew|Create"
msgstr ""
+msgid "ProjectsNew|Create a blank project to house your files, plan your work, and collaborate on code, among other things."
+msgstr ""
+
+msgid "ProjectsNew|Create blank project"
+msgstr ""
+
msgid "ProjectsNew|Create from template"
msgstr ""
+msgid "ProjectsNew|Create new project"
+msgstr ""
+
msgid "ProjectsNew|Creating project & repository."
msgstr ""
@@ -17477,6 +18058,9 @@ msgstr ""
msgid "ProjectsNew|Project description %{tag_start}(optional)%{tag_end}"
msgstr ""
+msgid "ProjectsNew|Run CI/CD for external repository"
+msgstr ""
+
msgid "ProjectsNew|Template"
msgstr ""
@@ -17738,6 +18322,9 @@ msgstr ""
msgid "Protected Branch"
msgstr ""
+msgid "Protected Branches"
+msgstr ""
+
msgid "Protected Environment"
msgstr ""
@@ -17750,6 +18337,9 @@ msgstr ""
msgid "Protected Tag"
msgstr ""
+msgid "Protected Tags"
+msgstr ""
+
msgid "Protected branches"
msgstr ""
@@ -17849,6 +18439,9 @@ msgstr ""
msgid "Provider"
msgstr ""
+msgid "Proxy support for this API is not available currently"
+msgstr ""
+
msgid "Pseudonymizer data collection"
msgstr ""
@@ -17873,6 +18466,9 @@ msgstr ""
msgid "Publish to status page"
msgstr ""
+msgid "Published on status page"
+msgstr ""
+
msgid "Publishes this issue to the associated status page."
msgstr ""
@@ -18047,6 +18643,9 @@ msgstr ""
msgid "Recipe"
msgstr ""
+msgid "Reconfigure"
+msgstr ""
+
msgid "Recover hidden stage"
msgstr ""
@@ -18133,6 +18732,9 @@ msgstr ""
msgid "Registration|Your profile"
msgstr ""
+msgid "Regulate approvals by authors/committers, based on compliance frameworks. Can be changed only at the instance level."
+msgstr ""
+
msgid "Rejected (closed)"
msgstr ""
@@ -18183,15 +18785,24 @@ msgstr ""
msgid "ReleaseAssetLinkType|Image"
msgstr ""
+msgid "ReleaseAssetLinkType|Images"
+msgstr ""
+
msgid "ReleaseAssetLinkType|Other"
msgstr ""
msgid "ReleaseAssetLinkType|Package"
msgstr ""
+msgid "ReleaseAssetLinkType|Packages"
+msgstr ""
+
msgid "ReleaseAssetLinkType|Runbook"
msgstr ""
+msgid "ReleaseAssetLinkType|Runbooks"
+msgstr ""
+
msgid "Releases"
msgstr ""
@@ -18204,6 +18815,9 @@ msgstr ""
msgid "Releases documentation"
msgstr ""
+msgid "Releases|New Release"
+msgstr ""
+
msgid "Release|Something went wrong while getting the release details"
msgstr ""
@@ -18276,6 +18890,9 @@ msgstr ""
msgid "Remove fork relationship"
msgstr ""
+msgid "Remove from batch"
+msgstr ""
+
msgid "Remove from board"
msgstr ""
@@ -18465,6 +19082,12 @@ msgstr ""
msgid "Replication"
msgstr ""
+msgid "Replication enabled"
+msgstr ""
+
+msgid "Replication paused"
+msgstr ""
+
msgid "Reply by email"
msgstr ""
@@ -18492,15 +19115,15 @@ msgstr ""
msgid "Reported %{timeAgo} by %{reportedBy}"
msgstr ""
-msgid "Reported Resource Changes: %{addNum} to add, %{changeNum} to change, %{deleteNum} to delete"
-msgstr ""
-
msgid "Reporter"
msgstr ""
msgid "Reporting"
msgstr ""
+msgid "Reports"
+msgstr ""
+
msgid "Reports|%{combinedString} and %{resolvedString}"
msgstr ""
@@ -18539,6 +19162,9 @@ msgstr ""
msgid "Reports|Failure"
msgstr ""
+msgid "Reports|Identifier"
+msgstr ""
+
msgid "Reports|Metrics reports are loading"
msgstr ""
@@ -18551,6 +19177,9 @@ msgstr ""
msgid "Reports|Metrics reports failed loading results"
msgstr ""
+msgid "Reports|Scanner"
+msgstr ""
+
msgid "Reports|Severity"
msgstr ""
@@ -18599,15 +19228,33 @@ msgstr ""
msgid "Repository cleanup has started. You will receive an email once the cleanup operation is complete."
msgstr ""
+msgid "Repository files count over the limit"
+msgstr ""
+
+msgid "Repository has an invalid default branch name."
+msgstr ""
+
+msgid "Repository has more than one branch."
+msgstr ""
+
msgid "Repository has no locks."
msgstr ""
+msgid "Repository has tags."
+msgstr ""
+
msgid "Repository maintenance"
msgstr ""
msgid "Repository mirroring"
msgstr ""
+msgid "Repository must contain at least 1 file."
+msgstr ""
+
+msgid "Repository size is above the limit."
+msgstr ""
+
msgid "Repository static objects"
msgstr ""
@@ -18617,7 +19264,7 @@ msgstr ""
msgid "Repository sync capacity"
msgstr ""
-msgid "Repository: %{counter_repositories} / Wikis: %{counter_wikis} / Build Artifacts: %{counter_build_artifacts} / LFS: %{counter_lfs_objects}"
+msgid "Repository: %{counter_repositories} / Wikis: %{counter_wikis} / Build Artifacts: %{counter_build_artifacts} / LFS: %{counter_lfs_objects} / Snippets: %{counter_snippets}"
msgstr ""
msgid "RepositorySettingsAccessLevel|Select"
@@ -18629,6 +19276,9 @@ msgstr ""
msgid "Request Headers"
msgstr ""
+msgid "Request details"
+msgstr ""
+
msgid "Request parameter %{param} is missing."
msgstr ""
@@ -18735,6 +19385,9 @@ msgstr ""
msgid "Reset template"
msgstr ""
+msgid "Reset to project defaults"
+msgstr ""
+
msgid "Resetting the authorization key for this project will require updating the authorization key in every alert source it is enabled in."
msgstr ""
@@ -18831,9 +19484,6 @@ msgstr ""
msgid "Resume"
msgstr ""
-msgid "Resume replication"
-msgstr ""
-
msgid "Resync"
msgstr ""
@@ -19456,6 +20106,9 @@ msgstr ""
msgid "SecurityReports|False positive"
msgstr ""
+msgid "SecurityReports|Group Security Dashboard"
+msgstr ""
+
msgid "SecurityReports|Hide dismissed"
msgstr ""
@@ -19510,10 +20163,13 @@ msgstr ""
msgid "SecurityReports|Remove project from dashboard"
msgstr ""
-msgid "SecurityReports|Report type"
+msgid "SecurityReports|Return to dashboard"
msgstr ""
-msgid "SecurityReports|Return to dashboard"
+msgid "SecurityReports|Scan details"
+msgstr ""
+
+msgid "SecurityReports|Scanner type"
msgstr ""
msgid "SecurityReports|Security Dashboard"
@@ -19570,6 +20226,9 @@ msgstr ""
msgid "SecurityReports|There was an error while generating the report."
msgstr ""
+msgid "SecurityReports|Unable to add %{invalidProjectsMessage}"
+msgstr ""
+
msgid "SecurityReports|Unable to add %{invalidProjects}"
msgstr ""
@@ -19684,6 +20343,9 @@ msgstr ""
msgid "Select due date"
msgstr ""
+msgid "Select file"
+msgstr ""
+
msgid "Select group or project"
msgstr ""
@@ -19747,9 +20409,6 @@ msgstr ""
msgid "Select the branch you want to set as the default for this project. All merge requests and commits will automatically be made against this branch unless you specify a different one."
msgstr ""
-msgid "Select the configured storage available for new repositories to be placed on."
-msgstr ""
-
msgid "Select the custom project template source group."
msgstr ""
@@ -19942,12 +20601,6 @@ msgstr ""
msgid "Service Desk is enabled but not yet active"
msgstr ""
-msgid "Service Desk is off"
-msgstr ""
-
-msgid "Service Desk is on"
-msgstr ""
-
msgid "Service Templates"
msgstr ""
@@ -20035,6 +20688,15 @@ msgstr ""
msgid "Set the milestone to %{milestone_reference}."
msgstr ""
+msgid "Set the number of concurrent requests this secondary node will make to the primary node while backfilling."
+msgstr ""
+
+msgid "Set the synchronization and verification capacity for the secondary node."
+msgstr ""
+
+msgid "Set the timeout in seconds to send a secondary node status to the primary and IPs allowed for the secondary nodes."
+msgstr ""
+
msgid "Set time estimate"
msgstr ""
@@ -20074,6 +20736,9 @@ msgstr ""
msgid "Set weight to %{weight}."
msgstr ""
+msgid "Set what should be replicated by choosing specific projects or groups by the secondary node."
+msgstr ""
+
msgid "SetPasswordToCloneLink|set a password"
msgstr ""
@@ -20200,6 +20865,9 @@ msgstr ""
msgid "Show complete raw log"
msgstr ""
+msgid "Show details"
+msgstr ""
+
msgid "Show file browser"
msgstr ""
@@ -20209,10 +20877,13 @@ msgstr ""
msgid "Show latest version"
msgstr ""
+msgid "Show list"
+msgstr ""
+
msgid "Show me everything"
msgstr ""
-msgid "Show me how"
+msgid "Show me how to add a pipeline"
msgstr ""
msgid "Show me more advanced stuff"
@@ -20261,9 +20932,6 @@ msgstr ""
msgid "Showing all issues"
msgstr ""
-msgid "Showing all labels"
-msgstr ""
-
msgid "Showing last %{size} of log -"
msgstr ""
@@ -20375,9 +21043,6 @@ msgstr ""
msgid "Skip outdated deployment jobs"
msgstr ""
-msgid "Skip this for now"
-msgstr ""
-
msgid "Skipped"
msgstr ""
@@ -20522,6 +21187,9 @@ msgstr ""
msgid "Something went wrong while adding your award. Please try again."
msgstr ""
+msgid "Something went wrong while applying the batch of suggestions. Please try again."
+msgstr ""
+
msgid "Something went wrong while applying the suggestion. Please try again."
msgstr ""
@@ -20624,10 +21292,13 @@ msgstr ""
msgid "Something went wrong while updating a requirement."
msgstr ""
+msgid "Something went wrong while updating assignees"
+msgstr ""
+
msgid "Something went wrong while updating your list settings"
msgstr ""
-msgid "Something went wrong with your automatic subscription renewal"
+msgid "Something went wrong with your automatic subscription renewal."
msgstr ""
msgid "Something went wrong, unable to add %{project} to dashboard"
@@ -20645,6 +21316,9 @@ msgstr ""
msgid "Something went wrong, unable to search projects"
msgstr ""
+msgid "Something went wrong."
+msgstr ""
+
msgid "Something went wrong. Please try again."
msgstr ""
@@ -20828,6 +21502,9 @@ msgstr ""
msgid "Source code"
msgstr ""
+msgid "Source code (%{fileExtension})"
+msgstr ""
+
msgid "Source is not available"
msgstr ""
@@ -20912,6 +21589,9 @@ msgstr ""
msgid "Stack trace"
msgstr ""
+msgid "Stacktrace snippet"
+msgstr ""
+
msgid "Stage"
msgstr ""
@@ -21017,6 +21697,9 @@ msgstr ""
msgid "Start thread & reopen %{noteable_name}"
msgstr ""
+msgid "Start using Directed Acyclic Graphs (DAG)"
+msgstr ""
+
msgid "Start your Free Gold Trial"
msgstr ""
@@ -21479,6 +22162,21 @@ msgstr ""
msgid "SuggestedColors|Very pale orange"
msgstr ""
+msgid "Suggestion is not applicable as the suggestion was not found."
+msgstr ""
+
+msgid "Suggestions are not applicable as one or more suggestions were not found."
+msgstr ""
+
+msgid "Suggestions are not applicable as their lines cannot overlap."
+msgstr ""
+
+msgid "Suggestions must all be on the same branch."
+msgstr ""
+
+msgid "Suggestions that change line count can't be added to batches, yet."
+msgstr ""
+
msgid "Suggestions:"
msgstr ""
@@ -21521,9 +22219,6 @@ msgstr ""
msgid "Synchronization disabled"
msgstr ""
-msgid "Synchronization of container repositories is disabled."
-msgstr ""
-
msgid "System"
msgstr ""
@@ -21704,6 +22399,34 @@ msgstr ""
msgid "Terms of Service and Privacy Policy"
msgstr ""
+msgid "Terraform|%{number} Terraform report failed to generate"
+msgid_plural "Terraform|%{number} Terraform reports failed to generate"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Terraform|%{number} Terraform report was generated in your pipelines"
+msgid_plural "Terraform|%{number} Terraform reports were generated in your pipelines"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Terraform|A Terraform report failed to generate."
+msgstr ""
+
+msgid "Terraform|A Terraform report was generated in your pipelines."
+msgstr ""
+
+msgid "Terraform|Generating the report caused an error."
+msgstr ""
+
+msgid "Terraform|Reported Resource Changes: %{addNum} to add, %{changeNum} to change, %{deleteNum} to delete"
+msgstr ""
+
+msgid "Terraform|The Terraform report %{name} failed to generate."
+msgstr ""
+
+msgid "Terraform|The Terraform report %{name} was generated in your pipelines."
+msgstr ""
+
msgid "Test"
msgstr ""
@@ -21730,7 +22453,7 @@ msgstr ""
msgid "TestHooks|Ensure the project has CI pipelines."
msgstr ""
-msgid "TestHooks|Ensure the project has at least one commit."
+msgid "TestHooks|Ensure the project has deployments."
msgstr ""
msgid "TestHooks|Ensure the project has issues."
@@ -21831,7 +22554,7 @@ msgstr ""
msgid "The Prometheus server responded with \"bad request\". Please check your queries are correct and are supported in your Prometheus version. %{documentationLink}"
msgstr ""
-msgid "The URL defined on the primary node that secondary nodes should use to contact it. Defaults to URL"
+msgid "The URL defined on the primary node that secondary nodes should use to contact it."
msgstr ""
msgid "The URL to use for connecting to Elasticsearch. Use a comma-separated list to support clustering (e.g., \"http://localhost:9200, http://localhost:9201\")."
@@ -21840,6 +22563,9 @@ msgstr ""
msgid "The X509 Certificate to use when mutual TLS is required to communicate with the external authorization service. If left blank, the server certificate is still validated when accessing over HTTPS."
msgstr ""
+msgid "The above settings apply to all projects with the selected compliance framework(s)."
+msgstr ""
+
msgid "The amount of seconds after which a request to get a secondary node status will time out."
msgstr ""
@@ -22139,9 +22865,6 @@ msgstr ""
msgid "The total stage shows the time it takes between creating an issue and deploying the code to production. The data will be automatically added once you have completed the full idea to production cycle."
msgstr ""
-msgid "The unique identifier for the Geo node. Must match %{geoNodeName} if it is set in gitlab.rb, otherwise it must match %{externalUrl} with a trailing slash"
-msgstr ""
-
msgid "The update action will time out after %{number_of_minutes} minutes. For big repositories, use a clone/push combination."
msgstr ""
@@ -22166,9 +22889,6 @@ msgstr ""
msgid "The user you are trying to deactivate has been active in the past %{minimum_inactive_days} days and cannot be deactivated"
msgstr ""
-msgid "The user-facing URL of the Geo node"
-msgstr ""
-
msgid "The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6."
msgstr ""
@@ -22259,6 +22979,9 @@ msgstr ""
msgid "There was a problem fetching project branches."
msgstr ""
+msgid "There was a problem fetching project tags."
+msgstr ""
+
msgid "There was a problem fetching project users."
msgstr ""
@@ -22310,6 +23033,9 @@ msgstr ""
msgid "There was an error fetching the %{replicableType}"
msgstr ""
+msgid "There was an error fetching the Geo Settings"
+msgstr ""
+
msgid "There was an error fetching the Node's Groups"
msgstr ""
@@ -22430,6 +23156,12 @@ msgstr ""
msgid "This %{issuable} is locked. Only <strong>project members</strong> can comment."
msgstr ""
+msgid "This %{noteableTypeText} is %{confidentialLinkStart}confidential%{linkEnd} and %{lockedLinkStart}locked%{linkEnd}."
+msgstr ""
+
+msgid "This %{noteableTypeText} is locked."
+msgstr ""
+
msgid "This %{viewer} could not be displayed because %{reason}. You can %{options} instead."
msgstr ""
@@ -22448,6 +23180,9 @@ msgstr ""
msgid "This action can lead to data loss. To prevent accidental actions we ask you to confirm your intention."
msgstr ""
+msgid "This also resolves all related threads"
+msgstr ""
+
msgid "This also resolves the discussion"
msgstr ""
@@ -22538,6 +23273,9 @@ msgstr ""
msgid "This epic does not exist or you don't have sufficient permission."
msgstr ""
+msgid "This feature is currently in beta. We invite you to %{linkStart}give feedback%{linkEnd}."
+msgstr ""
+
msgid "This feature requires local storage to be enabled"
msgstr ""
@@ -22571,7 +23309,7 @@ msgstr ""
msgid "This is a Work in Progress"
msgstr ""
-msgid "This is a confidential issue."
+msgid "This is a confidential %{noteableTypeText}."
msgstr ""
msgid "This is a delayed job to run in %{remainingTime}"
@@ -22598,9 +23336,6 @@ msgstr ""
msgid "This is your current session"
msgstr ""
-msgid "This issue is %{confidentialLinkStart}confidential%{linkEnd} and %{lockedLinkStart}locked%{linkEnd}."
-msgstr ""
-
msgid "This issue is confidential"
msgstr ""
@@ -22610,9 +23345,6 @@ msgstr ""
msgid "This issue is in a child epic of the filtered epic"
msgstr ""
-msgid "This issue is locked."
-msgstr ""
-
msgid "This job depends on other jobs with expired/erased artifacts: %{invalid_dependencies}"
msgstr ""
@@ -22712,6 +23444,9 @@ msgstr ""
msgid "This license has already expired."
msgstr ""
+msgid "This link points to external content"
+msgstr ""
+
msgid "This may expose confidential information as the selected fork is in another namespace that can have other members."
msgstr ""
@@ -22739,6 +23474,9 @@ msgstr ""
msgid "This page will be removed in a future release."
msgstr ""
+msgid "This pipeline does not use the %{codeStart}needs%{codeEnd} keyword and can't be represented as a directed acyclic graph."
+msgstr ""
+
msgid "This pipeline makes use of a predefined CI/CD configuration enabled by %{strongStart}Auto DevOps.%{strongEnd}"
msgstr ""
@@ -22967,6 +23705,9 @@ msgstr ""
msgid "Time from last commit to merge"
msgstr ""
+msgid "Time in seconds"
+msgstr ""
+
msgid "Time in seconds GitLab will wait for a response from the external service. When the service does not respond in time, access will be denied."
msgstr ""
@@ -23388,9 +24129,15 @@ msgstr ""
msgid "Total artifacts size: %{total_size}"
msgstr ""
+msgid "Total cores (CPUs)"
+msgstr ""
+
msgid "Total issues"
msgstr ""
+msgid "Total memory (GB)"
+msgstr ""
+
msgid "Total test time for all commits/merges"
msgstr ""
@@ -23592,6 +24339,9 @@ msgstr ""
msgid "U2F only works with HTTPS-enabled websites. Contact your administrator for more details."
msgstr ""
+msgid "UI Development Kit"
+msgstr ""
+
msgid "URL"
msgstr ""
@@ -23664,9 +24414,6 @@ msgstr ""
msgid "Unable to load the merge request widget. Try reloading the page."
msgstr ""
-msgid "Unable to process group import file"
-msgstr ""
-
msgid "Unable to resolve"
msgstr ""
@@ -23682,6 +24429,9 @@ msgstr ""
msgid "Unable to sign you in to the group with SAML due to \"%{reason}\""
msgstr ""
+msgid "Unable to suggest a path. Please refresh and try again."
+msgstr ""
+
msgid "Unable to update label prioritization at this time"
msgstr ""
@@ -23697,6 +24447,9 @@ msgstr ""
msgid "Unarchiving the project will restore people's ability to make changes to it. The repository can be committed to, and issues, comments, and other entities can be created. %{strong_start}Once active, this project shows up in the search and on the dashboard.%{strong_end}"
msgstr ""
+msgid "Unassign from commenting user"
+msgstr ""
+
msgid "Unblock"
msgstr ""
@@ -23772,6 +24525,9 @@ msgstr ""
msgid "Unreachable"
msgstr ""
+msgid "Unrecognized cluster type"
+msgstr ""
+
msgid "Unresolve"
msgstr ""
@@ -23832,6 +24588,9 @@ msgstr ""
msgid "Unsubscribes from this %{quick_action_target}."
msgstr ""
+msgid "Unsupported todo type passed. Supported todo types are: %{todo_types}"
+msgstr ""
+
msgid "Until"
msgstr ""
@@ -23859,6 +24618,9 @@ msgstr ""
msgid "Update approval rule"
msgstr ""
+msgid "Update approvers"
+msgstr ""
+
msgid "Update failed"
msgstr ""
@@ -23868,6 +24630,9 @@ msgstr ""
msgid "Update it"
msgstr ""
+msgid "Update iteration"
+msgstr ""
+
msgid "Update now"
msgstr ""
@@ -23913,7 +24678,7 @@ msgstr ""
msgid "Updated at"
msgstr ""
-msgid "Updated to"
+msgid "Updated to %{linkStart}chart v%{linkEnd}"
msgstr ""
msgid "Updating"
@@ -23943,6 +24708,9 @@ msgstr ""
msgid "Upload CSV file"
msgstr ""
+msgid "Upload License"
+msgstr ""
+
msgid "Upload New File"
msgstr ""
@@ -24024,16 +24792,19 @@ msgstr ""
msgid "UsageQuota|Pipelines"
msgstr ""
+msgid "UsageQuota|Purchase more storage"
+msgstr ""
+
msgid "UsageQuota|Repositories"
msgstr ""
msgid "UsageQuota|Repository"
msgstr ""
-msgid "UsageQuota|Storage"
+msgid "UsageQuota|Snippets"
msgstr ""
-msgid "UsageQuota|Storage usage:"
+msgid "UsageQuota|Storage"
msgstr ""
msgid "UsageQuota|This namespace has no projects which use shared runners"
@@ -24066,6 +24837,12 @@ msgstr ""
msgid "UsageQuota|Wikis"
msgstr ""
+msgid "UsageQuota|You used: %{usage} %{limit}"
+msgstr ""
+
+msgid "UsageQuota|out of %{formattedLimit} of your namespace storage"
+msgstr ""
+
msgid "Use %{code_start}::%{code_end} to create a %{link_start}scoped label set%{link_end} (eg. %{code_start}priority::1%{code_end})"
msgstr ""
@@ -24132,6 +24909,9 @@ msgstr ""
msgid "User IDs"
msgstr ""
+msgid "User Lists can only be created and modified with %{linkStart}the API%{linkEnd}"
+msgstr ""
+
msgid "User OAuth applications"
msgstr ""
@@ -24156,6 +24936,9 @@ msgstr ""
msgid "User key was successfully removed."
msgstr ""
+msgid "User list %{name} will be removed. Are you sure?"
+msgstr ""
+
msgid "User map"
msgstr ""
@@ -24177,148 +24960,10 @@ msgstr ""
msgid "User was successfully updated."
msgstr ""
-msgid "UserOnboardingTour|%{activeTour}/%{totalTours}"
-msgstr ""
-
-msgid "UserOnboardingTour|%{completed}/%{total} steps completed"
-msgstr ""
-
-msgid "UserOnboardingTour|%{emphasisStart}Well done!%{emphasisEnd}%{lineBreak}%{lineBreak}That's it for our guided tour, congratulations for making it all the way to the end!%{lineBreak}%{lineBreak}We hope this gave you a good overview of GitLab and how it can help you. We'll now show you how to create your own project and invite your colleagues."
-msgstr ""
-
-msgid "UserOnboardingTour|Adding other members to a project is done through Project Settings. Click on %{emphasisStart}Settings%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Alright, that's it for Commits. Let's take a look at the %{emphasisStart}Branches%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Awesome! Now click on %{emphasisStart}Members%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Click on one of the %{emphasisStart}Compare%{emphasisEnd} buttons to compare a branch to master."
-msgstr ""
-
-msgid "UserOnboardingTour|Click on one of the %{emphasisStart}pipeline IDs%{emphasisEnd} to see the details of a pipeline."
-msgstr ""
-
-msgid "UserOnboardingTour|Click to open the latest commit to see its details."
-msgstr ""
-
-msgid "UserOnboardingTour|Close 'Learn GitLab'"
-msgstr ""
-
-msgid "UserOnboardingTour|Commits are shown in chronological order and can be filtered by the commit message or by the branch."
+msgid "UserList|Delete %{name}?"
msgstr ""
-msgid "UserOnboardingTour|Create a project"
-msgstr ""
-
-msgid "UserOnboardingTour|Exit 'Learn GitLab'"
-msgstr ""
-
-msgid "UserOnboardingTour|Got it"
-msgstr ""
-
-msgid "UserOnboardingTour|Great job! %{clapHands} We hope the tour was helpful and that you learned how to use GitLab.%{lineBreak}%{lineBreak}We'd love to get your feedback on this tour.%{lineBreak}%{lineBreak}%{emphasisStart}How helpful would you say this guided tour was?%{emphasisEnd}%{lineBreak}%{lineBreak}"
-msgstr ""
-
-msgid "UserOnboardingTour|Guided GitLab Tour"
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can compare the changes of this branch to another one. Changes are divided by files so that it's easier to see what was changed where."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can create a project from scratch, start with a template or import a repository from other platforms. Whatever you choose, we'll guide you through the process.%{lineBreak}%{lineBreak}Fill in your new project information and click on %{emphasisStart}Create Project%{emphasisEnd} to progress to the next step."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can see the breakdown of the pipelines: its stages and jobs in each of the stages and their status.%{lineBreak}%{lineBreak}Our CI/CD pipelines are quite complex, most of our users have fewer and simpler pipelines."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can see the current members of the project (just you at the moment) and invite new members.%{lineBreak}%{lineBreak}You can invite multiple members at once (existing GitLab users or invite by email) and you can also set their roles and permissions.%{lineBreak}%{lineBreak}Add a few members and click on %{emphasisStart}Add to project%{emphasisEnd} to complete this step."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can see what changes were made with this commit, on what branch and if there's a related merge request. The status of the pipeline will also show up if CI/CD is set up.%{lineBreak}%{lineBreak}You can also comment on the lines of code that were changed and start a discussion with your colleagues!"
-msgstr ""
-
-msgid "UserOnboardingTour|Here's an overview of branches in the %{emphasisStart}%{projectName}%{emphasisEnd} project. They're split into Active and Stale.%{lineBreak}%{lineBreak}From here, you can create a new merge request from a branch, or compare the branch to any other branch in the project. By default, it will compare it to the master branch."
-msgstr ""
-
-msgid "UserOnboardingTour|Invite colleagues"
-msgstr ""
-
-msgid "UserOnboardingTour|Issues are great for communicating and keeping track of progress in GitLab. These are all issues that are open in the %{emphasisStart}%{projectName}%{emphasisEnd}.%{lineBreak}%{lineBreak}You can help us improve GitLab by contributing work to issues that are labeled <span class=\"badge color-label accept-mr-label\">Accepting merge requests</span>.%{lineBreak}%{lineBreak}This list can be filtered by labels, milestones, assignees, authors... We'll show you how it looks when the list is filtered by a label."
-msgstr ""
-
-msgid "UserOnboardingTour|Learn GitLab"
-msgstr ""
-
-msgid "UserOnboardingTour|Let's take a closer look at a merge request. Click on the title of one."
-msgstr ""
-
-msgid "UserOnboardingTour|Let's take a closer look at all the commits. Click on %{emphasisStart}Commits%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Let's take a closer look at the repository of this project. Click on %{emphasisStart}Repository%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|No thanks"
-msgstr ""
-
-msgid "UserOnboardingTour|Ok, let's go"
-msgstr ""
-
-msgid "UserOnboardingTour|Ok, show me"
-msgstr ""
-
-msgid "UserOnboardingTour|Open one of the issues by clicking on its title."
-msgstr ""
-
-msgid "UserOnboardingTour|Restart this step"
-msgstr ""
-
-msgid "UserOnboardingTour|Skip this step"
-msgstr ""
-
-msgid "UserOnboardingTour|Sweet! Your project was created and is ready to be used.%{lineBreak}%{lineBreak}You can start adding files to the repository or clone it. One last thing we want to show you is how to invite your colleagues to your new project."
-msgstr ""
-
-msgid "UserOnboardingTour|Take a look. Here's a nifty menu for quickly creating issues, merge requests, snippets, projects and groups. Click on it and select \"New project\" from the \"GitLab\" section to get started."
-msgstr ""
-
-msgid "UserOnboardingTour|Thanks for taking the guided tour. Remember, if you want to go through it again, you can start %{emphasisStart}Learn GitLab%{emphasisEnd} in the help menu on the top right."
-msgstr ""
-
-msgid "UserOnboardingTour|Thanks for the feedback! %{thumbsUp}"
-msgstr ""
-
-msgid "UserOnboardingTour|That's it for issues. Let'st take a look at %{emphasisStart}Merge Requests%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|That's it for merge requests. Now for the final part of this guided tour - the %{emphasisStart}CI/CD%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|That's it for the Repository. Let's take a look at the %{emphasisStart}Issues%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|The structure of this page is very similar to issues. Status, description, discussion and the sidebar are all here.%{lineBreak}%{lineBreak}But take a look below the description and you'll notice that there's more information about the merge request, the CI/CD pipeline and the options for approving it.%{lineBreak}%{lineBreak}Alongside the discussion you can also see more information about commits in this merge request, the status of pipelines and review all changes that were made."
-msgstr ""
-
-msgid "UserOnboardingTour|There's a lot of information here but don't worry, we'll go through it.%{lineBreak}%{lineBreak}On the top you can see the status of the issue and when it was opened and by whom. Directly below it is the issue description and below that are other %{emphasisStart}related issues%{emphasisEnd} and %{emphasisStart}merge requests%{emphasisEnd} (if any). Then below that is the %{emphasisStart}discussion%{emphasisEnd}, that's where most of the communication happens.%{lineBreak}%{lineBreak}On the right, there's a sidebar where you can view/change the %{emphasisStart}assignee, milestone, due date, labels, weight%{emphasisEnd}, etc."
-msgstr ""
-
-msgid "UserOnboardingTour|These are all the CI/CD pipelines we have for our %{emphasisStart}%{projectName}%{emphasisEnd} project.%{lineBreak}%{lineBreak}Here you can see the status of each pipeline, for what commit it's running for, its stages and the status for them."
-msgstr ""
-
-msgid "UserOnboardingTour|These are all the issues that are available for community contributions. Let's take a closer look at one of them."
-msgstr ""
-
-msgid "UserOnboardingTour|This is an overview of all merge requests in this project. Similarly to the issues overview it can be filtered down by things like labels, milestones, authors, assignees, etc."
-msgstr ""
-
-msgid "UserOnboardingTour|This is the repository for the %{emphasisStart}%{projectName}%{emphasisEnd} project. All our code is stored here. Feel free to explore and take a closer look at folders and files.%{lineBreak}%{lineBreak}Above the file structure you can see the latest commit, who the author is and the status of the CI/CD pipeline.%{lineBreak}%{lineBreak}If you scroll down below the file structure, you'll find the Readme of this project. This is defined in the README.md file at the root of the repository."
-msgstr ""
-
-msgid "UserOnboardingTour|Welcome to the project overview of the %{emphasisStart}%{projectName}%{emphasisEnd} project. This is the project that we use to work on GitLab. At first, a project seems like a simple repository, but at GitLab, a project is so much more.%{lineBreak}%{lineBreak}You can create projects for hosting your codebase, use it as an issue tracker, collaborate on code, and continuously build, test, and deploy your app with built-in GitLab CI/CD."
+msgid "UserList|created %{timeago}"
msgstr ""
msgid "UserProfile|Activity"
@@ -24471,6 +25116,9 @@ msgstr ""
msgid "UsersSelect|Unassigned"
msgstr ""
+msgid "Using %{codeStart}needs%{codeEnd} allows jobs to run before their stage is reached, as soon as their individual dependencies are met, which speeds up your pipelines."
+msgstr ""
+
msgid "Using %{code_start}::%{code_end} denotes a %{link_start}scoped label set%{link_end}"
msgstr ""
@@ -24555,9 +25203,6 @@ msgstr ""
msgid "Versions"
msgstr ""
-msgid "Very helpful"
-msgstr ""
-
msgid "View Documentation"
msgstr ""
@@ -24706,6 +25351,9 @@ msgstr ""
msgid "VisibilityLevel|Unknown"
msgstr ""
+msgid "Visit settings page"
+msgstr ""
+
msgid "VisualReviewApp|%{stepStart}Step 1%{stepEnd}. Copy the following script:"
msgstr ""
@@ -24799,6 +25447,9 @@ msgstr ""
msgid "VulnerabilityManagement|Something went wrong while trying to delete the comment. Please try again later."
msgstr ""
+msgid "VulnerabilityManagement|Something went wrong while trying to refresh the vulnerability. Please try again later."
+msgstr ""
+
msgid "VulnerabilityManagement|Something went wrong while trying to retrieve the vulnerability history. Please try again later."
msgstr ""
@@ -24841,6 +25492,9 @@ msgstr ""
msgid "Vulnerability|Class"
msgstr ""
+msgid "Vulnerability|Crash Address"
+msgstr ""
+
msgid "Vulnerability|Description"
msgstr ""
@@ -24916,9 +25570,6 @@ msgstr ""
msgid "We couldn't reach the Prometheus server. Either the server no longer exists or the configuration details need updating."
msgstr ""
-msgid "We created a short guided tour that will help you learn the basics of GitLab and how it will help you be better at your job. It should only take a couple of minutes. You will be guided by two types of helpers, best recognized by their color."
-msgstr ""
-
msgid "We detected potential spam in the %{humanized_resource_name}. Please solve the reCAPTCHA to proceed."
msgstr ""
@@ -24952,6 +25603,9 @@ msgstr ""
msgid "We've found no vulnerabilities"
msgstr ""
+msgid "Web Application Firewall"
+msgstr ""
+
msgid "Web IDE"
msgstr ""
@@ -25012,9 +25666,6 @@ msgstr ""
msgid "Welcome to GitLab.com<br>@%{name}!"
msgstr ""
-msgid "Welcome to the Guided GitLab Tour"
-msgstr ""
-
msgid "Welcome to the guided GitLab tour"
msgstr ""
@@ -25062,9 +25713,6 @@ msgstr ""
msgid "While it's rare to have no vulnerabilities, it can happen. In any event, we ask that you please double check your settings to make sure you've set up your dashboard correctly."
msgstr ""
-msgid "White helpers give contextual information."
-msgstr ""
-
msgid "Who can be an approver?"
msgstr ""
@@ -25110,12 +25758,18 @@ msgstr ""
msgid "WikiEmptyIssueMessage|Suggest wiki improvement"
msgstr ""
+msgid "WikiEmptyIssueMessage|You must be a group member in order to add wiki pages. If you have suggestions for how to improve the wiki for this group, consider opening an issue in the %{issues_link}."
+msgstr ""
+
msgid "WikiEmptyIssueMessage|You must be a project member in order to add wiki pages. If you have suggestions for how to improve the wiki for this project, consider opening an issue in the %{issues_link}."
msgstr ""
msgid "WikiEmptyIssueMessage|issue tracker"
msgstr ""
+msgid "WikiEmpty|A wiki is where you can store all the details about your group. This can include why you've created it, its principles, how to use it, and so on."
+msgstr ""
+
msgid "WikiEmpty|A wiki is where you can store all the details about your project. This can include why you've created it, its principles, how to use it, and so on."
msgstr ""
@@ -25125,12 +25779,21 @@ msgstr ""
msgid "WikiEmpty|Suggest wiki improvement"
msgstr ""
+msgid "WikiEmpty|The wiki lets you write documentation for your group"
+msgstr ""
+
msgid "WikiEmpty|The wiki lets you write documentation for your project"
msgstr ""
+msgid "WikiEmpty|This group has no wiki pages"
+msgstr ""
+
msgid "WikiEmpty|This project has no wiki pages"
msgstr ""
+msgid "WikiEmpty|You must be a group member in order to add wiki pages."
+msgstr ""
+
msgid "WikiEmpty|You must be a project member in order to add wiki pages."
msgstr ""
@@ -25407,13 +26070,13 @@ msgstr ""
msgid "You can now export your security dashboard to a CSV report."
msgstr ""
-msgid "You can now submit a merge request to get this change into the original branch."
+msgid "You can now manage alert endpoint configuration in the Alerts section on the Operations settings page. Fields on this page have been deprecated."
msgstr ""
-msgid "You can now submit a merge request to get this change into the original project."
+msgid "You can now submit a merge request to get this change into the original branch."
msgstr ""
-msgid "You can only add files when you are on a branch"
+msgid "You can now submit a merge request to get this change into the original project."
msgstr ""
msgid "You can only edit files when you are on a branch"
@@ -25635,7 +26298,7 @@ msgstr ""
msgid "You need to upload a Google Takeout archive."
msgstr ""
-msgid "You reached %{usage_in_percent} of %{namespace_name}'s capacity (%{used_storage} of %{storage_limit})"
+msgid "You reached %{usage_in_percent} of %{namespace_name}'s storage capacity (%{used_storage} of %{storage_limit})"
msgstr ""
msgid "You tried to fork %{link_to_the_project} but it failed for the following reason:"
@@ -25938,13 +26601,13 @@ msgstr ""
msgid "Your subscription expired!"
msgstr ""
-msgid "Your subscription has been downgraded"
+msgid "Your subscription has been downgraded."
msgstr ""
-msgid "Your subscription will automatically renew in %{remaining_days}"
+msgid "Your subscription will automatically renew in %{remaining_days}."
msgstr ""
-msgid "Your subscription will expire in %{remaining_days}"
+msgid "Your subscription will expire in %{remaining_days}."
msgstr ""
msgid "Zoom meeting added"
@@ -26024,9 +26687,6 @@ msgstr ""
msgid "branch name"
msgstr ""
-msgid "build pipeline reference mismatch"
-msgstr ""
-
msgid "by"
msgstr ""
@@ -26045,6 +26705,9 @@ msgstr ""
msgid "cannot block others"
msgstr ""
+msgid "cannot contain HTML/XML tags, including any word between angle brackets (<,>)."
+msgstr ""
+
msgid "cannot include leading slash or directory traversal."
msgstr ""
@@ -26075,55 +26738,6 @@ msgstr ""
msgid "ciReport|%{remainingPackagesCount} more"
msgstr ""
-msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability"
-msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability for the source branch only"
-msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities for the source branch only"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerability"
-msgid_plural "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerabilities"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new vulnerability"
-msgid_plural "ciReport|%{reportType} %{status} detected %{newCount} new vulnerabilities"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities for the source branch only"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{fixedCount} fixed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} vulnerability for the source branch only"
-msgid_plural "ciReport|%{reportType} %{status} detected %{newCount} vulnerabilities for the source branch only"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "ciReport|%{reportType} %{status} detected no new vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected no vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected no vulnerabilities for the source branch only"
-msgstr ""
-
msgid "ciReport|%{reportType} is loading"
msgstr ""
@@ -26142,7 +26756,7 @@ msgstr ""
msgid "ciReport|All projects"
msgstr ""
-msgid "ciReport|All report types"
+msgid "ciReport|All scanner types"
msgstr ""
msgid "ciReport|All severities"
@@ -26166,6 +26780,9 @@ msgstr ""
msgid "ciReport|Container scanning detects known vulnerabilities in your docker images."
msgstr ""
+msgid "ciReport|Coverage Fuzzing"
+msgstr ""
+
msgid "ciReport|Create a merge request to implement this solution, or download and apply the patch manually."
msgstr ""
@@ -26324,6 +26941,9 @@ msgstr ""
msgid "customize"
msgstr ""
+msgid "data"
+msgstr ""
+
msgid "date must not be after 9999-12-31"
msgstr ""
@@ -26394,6 +27014,9 @@ msgstr ""
msgid "entries cannot contain HTML tags"
msgstr ""
+msgid "epic"
+msgstr ""
+
msgid "error"
msgstr ""
@@ -26415,9 +27038,6 @@ msgstr ""
msgid "expires on %{timebox_due_date}"
msgstr ""
-msgid "external_url"
-msgstr ""
-
msgid "failed"
msgstr ""
@@ -26453,9 +27073,6 @@ msgstr ""
msgid "from"
msgstr ""
-msgid "geo_node_name"
-msgstr ""
-
msgid "group"
msgstr ""
@@ -26477,9 +27094,6 @@ msgstr ""
msgid "https://your-bitbucket-server"
msgstr ""
-msgid "image"
-msgstr ""
-
msgid "image diff"
msgstr ""
@@ -26623,6 +27237,9 @@ msgstr ""
msgid "load it anyway"
msgstr ""
+msgid "loading"
+msgstr ""
+
msgid "locked by %{path_lock_user_name} %{created_at}"
msgstr ""
@@ -26751,9 +27368,6 @@ msgstr ""
msgid "mrWidget|Deployment statistics are not available currently"
msgstr ""
-msgid "mrWidget|Detect issues before deployment with a CI pipeline that continuously tests your code. We created a quick guide that will show you how to create one. Make your code more secure and more robust in just a minute."
-msgstr ""
-
msgid "mrWidget|Did not close"
msgstr ""
@@ -26916,6 +27530,9 @@ msgstr ""
msgid "mrWidget|To approve this merge request, please enter your password. This project requires all approvals to be authenticated."
msgstr ""
+msgid "mrWidget|Use %{linkStart}CI pipelines to test your code%{linkEnd}, simply add a GitLab CI configuration file to your project. It only takes a minute to make your code more secure and robust."
+msgstr ""
+
msgid "mrWidget|When this merge request is ready, remove the WIP: prefix from the title to allow it to be merged"
msgstr ""
@@ -26952,6 +27569,9 @@ msgstr ""
msgid "must be greater than start date"
msgstr ""
+msgid "must contain only valid frameworks"
+msgstr ""
+
msgid "my-awesome-group"
msgstr ""
@@ -26964,6 +27584,9 @@ msgstr ""
msgid "needs to be between 10 minutes and 1 month"
msgstr ""
+msgid "never"
+msgstr ""
+
msgid "never expires"
msgstr ""
@@ -26991,6 +27614,9 @@ msgstr ""
msgid "nounSeries|%{firstItem} and %{lastItem}"
msgstr ""
+msgid "nounSeries|%{item}"
+msgstr ""
+
msgid "nounSeries|%{item}, %{nextItem}"
msgstr ""
@@ -27127,6 +27753,9 @@ msgstr ""
msgid "revised"
msgstr ""
+msgid "satisfied"
+msgstr ""
+
msgid "score"
msgstr ""
@@ -27157,6 +27786,9 @@ msgstr ""
msgid "severity|Unknown"
msgstr ""
+msgid "should be an array of %{object_name} objects"
+msgstr ""
+
msgid "should be greater than or equal to %{access} inherited membership from group %{group_name}"
msgstr ""
@@ -27211,10 +27843,13 @@ msgstr ""
msgid "suggestPipeline|2/2: Commit your changes"
msgstr ""
-msgid "suggestPipeline|Commit the changes and your pipeline will automatically run for the first time."
+msgid "suggestPipeline|Choose %{boldStart}Code Quality%{boldEnd} to add a pipeline that tests the quality of your code."
msgstr ""
-msgid "suggestPipeline|We recommend the %{boldStart}Code Quality%{boldEnd} template, which will add a report widget to your Merge Requests. This way you’ll learn about code quality degradations much sooner. %{footerStart} Goodbye technical debt! %{footerEnd}"
+msgid "suggestPipeline|The template is ready! You can now commit it to create your first pipeline."
+msgstr ""
+
+msgid "suggestPipeline|We’re adding a GitLab CI configuration file to add a pipeline to the project. You could create it manually, but we recommend that you start with a GitLab template that works out of the box."
msgstr ""
msgid "syntax is correct"
@@ -27274,6 +27909,9 @@ msgstr ""
msgid "uses Kubernetes clusters to deploy your code!"
msgstr ""
+msgid "v%{version} published %{timeAgo}"
+msgstr ""
+
msgid "verify ownership"
msgstr ""
diff --git a/locale/fi_FI/gitlab.po b/locale/fi_FI/gitlab.po
index 7b9c52d631d..20d7428c297 100644
--- a/locale/fi_FI/gitlab.po
+++ b/locale/fi_FI/gitlab.po
@@ -2,7 +2,7 @@ msgid ""
msgstr ""
"Project-Id-Version: gitlab-ee\n"
"Report-Msgid-Bugs-To: \n"
-"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Last-Translator: \n"
"Language-Team: Finnish\n"
"Language: fi_FI\n"
"MIME-Version: 1.0\n"
@@ -10,9 +10,11 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Crowdin-Project: gitlab-ee\n"
+"X-Crowdin-Project-ID: 288872\n"
"X-Crowdin-Language: fi\n"
"X-Crowdin-File: /master/locale/gitlab.pot\n"
-"PO-Revision-Date: 2020-06-08 15:16\n"
+"X-Crowdin-File-ID: 6\n"
+"PO-Revision-Date: 2020-07-02 01:25\n"
msgid " %{start} to %{end}"
msgstr ""
@@ -197,6 +199,11 @@ msgid_plural "%d metrics"
msgstr[0] ""
msgstr[1] ""
+msgid "%d milestone"
+msgid_plural "%d milestones"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "%d minute"
msgid_plural "%d minutes"
msgstr[0] ""
@@ -242,11 +249,6 @@ msgid_plural "%d unresolved threads"
msgstr[0] ""
msgstr[1] ""
-msgid "%d url scanned"
-msgid_plural "%d urls scanned"
-msgstr[0] ""
-msgstr[1] ""
-
msgid "%d vulnerability"
msgid_plural "%d vulnerabilities"
msgstr[0] ""
@@ -324,9 +326,15 @@ msgstr[1] ""
msgid "%{count} related %{pluralized_subject}: %{links}"
msgstr ""
+msgid "%{dashboard_path} could not be found."
+msgstr ""
+
msgid "%{days} days until tags are automatically removed"
msgstr ""
+msgid "%{deployLinkStart}Use a template to deploy to ECS%{deployLinkEnd}, or use a docker image to %{commandsLinkStart}run AWS commands in GitLab CI/CD%{commandsLinkEnd}."
+msgstr ""
+
msgid "%{description}- Sentry event: %{errorUrl}- First seen: %{firstSeen}- Last seen: %{lastSeen} %{countLabel}: %{count}%{userCountLabel}: %{userCount}"
msgstr ""
@@ -369,12 +377,18 @@ msgstr ""
msgid "%{host} sign-in from new location"
msgstr ""
-msgid "%{icon}You are about to add %{usersTag} people to the discussion. Proceed with caution."
+msgid "%{icon}You are about to add %{usersTag} people to the discussion. They will all receive a notification."
+msgstr ""
+
+msgid "%{integrations_link_start}Integrations%{link_end} enable you to make third-party applications part of your GitLab workflow. If the available integrations don't meet your needs, consider using a %{webhooks_link_start}webhook%{link_end}."
msgstr ""
msgid "%{issuableType} will be removed! Are you sure?"
msgstr ""
+msgid "%{issuesCount} issues in this group"
+msgstr ""
+
msgid "%{issuesSize} issues"
msgstr ""
@@ -384,7 +398,7 @@ msgstr ""
msgid "%{labelStart}Class:%{labelEnd} %{class}"
msgstr ""
-msgid "%{labelStart}Confidence:%{labelEnd} %{confidence}"
+msgid "%{labelStart}Evidence:%{labelEnd} %{evidence}"
msgstr ""
msgid "%{labelStart}File:%{labelEnd} %{file}"
@@ -402,6 +416,9 @@ msgstr ""
msgid "%{labelStart}Report Type:%{labelEnd} %{reportType}"
msgstr ""
+msgid "%{labelStart}Scanner:%{labelEnd} %{scanner}"
+msgstr ""
+
msgid "%{labelStart}Severity:%{labelEnd} %{severity}"
msgstr ""
@@ -426,9 +443,6 @@ msgstr ""
msgid "%{link_start}Read more%{link_end} about role permissions"
msgstr ""
-msgid "%{link} can be used for binding events when something is happening within the project."
-msgstr ""
-
msgid "%{listToShow}, and %{awardsListLength} more."
msgstr ""
@@ -462,6 +476,9 @@ msgstr ""
msgid "%{name} found %{resultsString}"
msgstr ""
+msgid "%{name} is already being used for another emoji"
+msgstr ""
+
msgid "%{name} is scheduled for %{action}"
msgstr ""
@@ -509,6 +526,9 @@ msgstr ""
msgid "%{primary} (%{secondary})"
msgstr ""
+msgid "%{ref} cannot be added: %{error}"
+msgstr ""
+
msgid "%{releases} release"
msgid_plural "%{releases} releases"
msgstr[0] ""
@@ -517,13 +537,37 @@ msgstr[1] ""
msgid "%{remaining_approvals} left"
msgstr ""
-msgid "%{retryButtonStart}Try again%{retryButtonEnd} or %{newFileButtonStart}attach a new file%{newFileButtonEnd}"
+msgid "%{reportType} %{status} detected %{critical} critical and %{high} high severity vulnerabilities out of %{total}."
+msgstr ""
+
+msgid "%{reportType} %{status} detected %{critical} critical and %{high} high severity vulnerabilities."
+msgstr ""
+
+msgid "%{reportType} %{status} detected %{critical} critical severity vulnerabilities out of %{total}."
+msgstr ""
+
+msgid "%{reportType} %{status} detected %{critical} critical severity vulnerability."
+msgid_plural "%{reportType} %{status} detected %{critical} critical severity vulnerabilities."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{reportType} %{status} detected %{high} high severity vulnerabilities out of %{total}."
msgstr ""
-msgid "%{screenreaderOnlyStart}Keyboard shorcuts%{screenreaderOnlyEnd} Disabled"
+msgid "%{reportType} %{status} detected %{high} high severity vulnerability."
+msgid_plural "%{reportType} %{status} detected %{high} high severity vulnerabilities."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{reportType} %{status} detected %{other} vulnerability."
+msgid_plural "%{reportType} %{status} detected %{other} vulnerabilities."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{reportType} %{status} detected no new vulnerabilities."
msgstr ""
-msgid "%{screenreaderOnlyStart}Keyboard shorcuts%{screenreaderOnlyEnd} Enabled"
+msgid "%{retryButtonStart}Try again%{retryButtonEnd} or %{newFileButtonStart}attach a new file%{newFileButtonEnd}"
msgstr ""
msgid "%{service_title} %{message}."
@@ -620,6 +664,12 @@ msgstr ""
msgid "%{token}..."
msgstr ""
+msgid "%{totalCpu} (%{freeSpacePercentage}%{percentSymbol} free)"
+msgstr ""
+
+msgid "%{totalMemory} (%{freeSpacePercentage}%{percentSymbol} free)"
+msgstr ""
+
msgid "%{totalWeight} total weight"
msgstr ""
@@ -650,6 +700,12 @@ msgstr ""
msgid "%{verb} %{time_spent_value} spent time."
msgstr ""
+msgid "%{webhooks_link_start}%{webhook_type}%{link_end} enable you to send notifications to web applications in response to events in a group or project."
+msgstr ""
+
+msgid "%{webhooks_link_start}%{webhook_type}%{link_end} enable you to send notifications to web applications in response to events in a group or project. We recommend using an %{integrations_link_start}integration%{link_end} in preference to a webhook."
+msgstr ""
+
msgid "'%{level}' is not a valid visibility level"
msgstr ""
@@ -676,9 +732,6 @@ msgstr ""
msgid "(No changes)"
msgstr ""
-msgid "(Show all)"
-msgstr ""
-
msgid "(check progress)"
msgstr ""
@@ -745,6 +798,9 @@ msgstr ""
msgid "0 for unlimited"
msgstr ""
+msgid "0 for unlimited, only effective with remote storage enabled."
+msgstr ""
+
msgid "1 %{type} addition"
msgid_plural "%{count} %{type} additions"
msgstr[0] ""
@@ -892,6 +948,12 @@ msgstr ""
msgid "<code>\"johnsmith@example.com\": \"johnsmith@example.com\"</code> will add \"By <a href=\"#\">johnsmith@example.com</a>\" to all issues and comments originally created by johnsmith@example.com. By default, the email address or username is masked to ensure the user's privacy. Use this option if you want to show the full email address."
msgstr ""
+msgid "<code>Masked</code> to prevent the values from being displayed in job logs (must match certain regexp requirements)."
+msgstr ""
+
+msgid "<code>Protected</code> to expose them to protected branches or tags only."
+msgstr ""
+
msgid "<namespace / project>"
msgstr ""
@@ -919,9 +981,6 @@ msgstr ""
msgid "A .NET Core console application template, customizable for any .NET Core project"
msgstr ""
-msgid "A DAG must have two dependent jobs to be visualized on this tab."
-msgstr ""
-
msgid "A GitBook site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
msgstr ""
@@ -952,6 +1011,12 @@ msgstr ""
msgid "A deleted user"
msgstr ""
+msgid "A file has been changed."
+msgstr ""
+
+msgid "A file was not found."
+msgstr ""
+
msgid "A file with '%{file_name}' already exists in %{branch} branch"
msgstr ""
@@ -1018,7 +1083,7 @@ msgstr ""
msgid "A subscription will trigger a new pipeline on the default branch of this project when a pipeline successfully completes for a new tag on the %{default_branch_docs} of the subscribed project."
msgstr ""
-msgid "A terraform report was generated in your pipelines."
+msgid "A suggestion is not applicable."
msgstr ""
msgid "A user with write access to the source branch selected this option"
@@ -1189,6 +1254,9 @@ msgstr ""
msgid "Action to take when receiving an alert."
msgstr ""
+msgid "Actions"
+msgstr ""
+
msgid "Activate"
msgstr ""
@@ -1245,6 +1313,9 @@ msgstr ""
msgid "Add LICENSE"
msgstr ""
+msgid "Add New Node"
+msgstr ""
+
msgid "Add README"
msgstr ""
@@ -1320,6 +1391,9 @@ msgstr ""
msgid "Add approval rule"
msgstr ""
+msgid "Add approvers"
+msgstr ""
+
msgid "Add bold text"
msgstr ""
@@ -1377,6 +1451,9 @@ msgstr ""
msgid "Add strikethrough text"
msgstr ""
+msgid "Add suggestion to batch"
+msgstr ""
+
msgid "Add system hook"
msgstr ""
@@ -1476,6 +1553,9 @@ msgstr ""
msgid "Admin Note"
msgstr ""
+msgid "Admin Notifications"
+msgstr ""
+
msgid "Admin Overview"
msgstr ""
@@ -1587,6 +1667,9 @@ msgstr ""
msgid "AdminSettings|Integrations configured here will automatically apply to all projects on this instance."
msgstr ""
+msgid "AdminSettings|Moved to integrations"
+msgstr ""
+
msgid "AdminSettings|No required pipeline"
msgstr ""
@@ -1817,12 +1900,18 @@ msgid_plural "Alerts"
msgstr[0] ""
msgstr[1] ""
+msgid "AlertManagement| assign yourself"
+msgstr ""
+
msgid "AlertManagement|Acknowledged"
msgstr ""
msgid "AlertManagement|Alert"
msgstr ""
+msgid "AlertManagement|Alert assignee(s): %{assignees}"
+msgstr ""
+
msgid "AlertManagement|Alert detail"
msgstr ""
@@ -1838,9 +1927,15 @@ msgstr ""
msgid "AlertManagement|All alerts"
msgstr ""
+msgid "AlertManagement|Assign To"
+msgstr ""
+
msgid "AlertManagement|Assign status"
msgstr ""
+msgid "AlertManagement|Assignee"
+msgstr ""
+
msgid "AlertManagement|Assignees"
msgstr ""
@@ -1859,9 +1954,6 @@ msgstr ""
msgid "AlertManagement|Edit"
msgstr ""
-msgid "AlertManagement|End time"
-msgstr ""
-
msgid "AlertManagement|Events"
msgstr ""
@@ -1880,10 +1972,13 @@ msgstr ""
msgid "AlertManagement|More information"
msgstr ""
+msgid "AlertManagement|No Matching Results"
+msgstr ""
+
msgid "AlertManagement|No alert data to display."
msgstr ""
-msgid "AlertManagement|No alerts available to display. If you think you're seeing this message in error, refresh the page."
+msgid "AlertManagement|No alerts available to display. See %{linkStart}enabling alert management%{linkEnd} for more information on adding alerts to the list."
msgstr ""
msgid "AlertManagement|No alerts to display."
@@ -1892,6 +1987,9 @@ msgstr ""
msgid "AlertManagement|None"
msgstr ""
+msgid "AlertManagement|None -"
+msgstr ""
+
msgid "AlertManagement|Open"
msgstr ""
@@ -1928,9 +2026,18 @@ msgstr ""
msgid "AlertManagement|There was an error displaying the alerts. Confirm your endpoint's configuration details to ensure alerts appear."
msgstr ""
+msgid "AlertManagement|There was an error while updating the assignee(s) list. Please try again."
+msgstr ""
+
+msgid "AlertManagement|There was an error while updating the assignee(s) of the alert. Please try again."
+msgstr ""
+
msgid "AlertManagement|There was an error while updating the status of the alert. Please try again."
msgstr ""
+msgid "AlertManagement|This assignee cannot be assigned to this alert."
+msgstr ""
+
msgid "AlertManagement|Tool"
msgstr ""
@@ -2078,6 +2185,9 @@ msgstr ""
msgid "Allow users to request access (if visibility is public or internal)"
msgstr ""
+msgid "Allowed Geo IP"
+msgstr ""
+
msgid "Allowed email domain restriction only permitted for top-level groups"
msgstr ""
@@ -2111,6 +2221,9 @@ msgstr ""
msgid "Amazon Web Services"
msgstr ""
+msgid "Amazon Web Services Logo"
+msgstr ""
+
msgid "Amazon authentication is not %{link_start}correctly configured%{link_end}. Ask your GitLab administrator if you want to use this service."
msgstr ""
@@ -2168,10 +2281,16 @@ msgstr ""
msgid "An error occurred when updating the issue weight"
msgstr ""
+msgid "An error occurred while acknowledging the notification. Refresh the page and try again."
+msgstr ""
+
+msgid "An error occurred while adding approvers"
+msgstr ""
+
msgid "An error occurred while adding formatted title for epic"
msgstr ""
-msgid "An error occurred while checking group path"
+msgid "An error occurred while checking group path. Please refresh and try again."
msgstr ""
msgid "An error occurred while committing your changes."
@@ -2315,7 +2434,7 @@ msgstr ""
msgid "An error occurred while loading milestones"
msgstr ""
-msgid "An error occurred while loading terraform report"
+msgid "An error occurred while loading project creation UI"
msgstr ""
msgid "An error occurred while loading the data. Please try again."
@@ -2369,6 +2488,9 @@ msgstr ""
msgid "An error occurred while rendering preview broadcast message"
msgstr ""
+msgid "An error occurred while rendering the editor"
+msgstr ""
+
msgid "An error occurred while reordering issues."
msgstr ""
@@ -2465,7 +2587,7 @@ msgstr ""
msgid "Analyze your dependencies for known vulnerabilities."
msgstr ""
-msgid "Analyze your source code and git history for secrets"
+msgid "Analyze your source code and git history for secrets."
msgstr ""
msgid "Analyze your source code for known vulnerabilities."
@@ -2579,12 +2701,18 @@ msgstr ""
msgid "Apply suggestion"
msgstr ""
+msgid "Apply suggestions"
+msgstr ""
+
msgid "Apply template"
msgstr ""
msgid "Apply this approval rule to any branch or a specific protected branch."
msgstr ""
+msgid "Applying"
+msgstr ""
+
msgid "Applying a template will replace the existing issue description. Any changes you have made will be lost."
msgstr ""
@@ -2597,12 +2725,18 @@ msgstr ""
msgid "Applying multiple commands"
msgstr ""
-msgid "Applying suggestion"
+msgid "Applying suggestion..."
+msgstr ""
+
+msgid "Applying suggestions..."
msgstr ""
msgid "Approval rules"
msgstr ""
+msgid "Approval rules reset to project defaults"
+msgstr ""
+
msgid "ApprovalRuleRemove|%d member"
msgid_plural "ApprovalRuleRemove|%d members"
msgstr[0] ""
@@ -2656,6 +2790,9 @@ msgstr ""
msgid "Approved"
msgstr ""
+msgid "Approved MRs"
+msgstr ""
+
msgid "Approved by: "
msgstr ""
@@ -2869,6 +3006,9 @@ msgstr ""
msgid "Assign"
msgstr ""
+msgid "Assign Iteration"
+msgstr ""
+
msgid "Assign custom color like #FF0000"
msgstr ""
@@ -2887,6 +3027,9 @@ msgstr ""
msgid "Assign to"
msgstr ""
+msgid "Assign to commenting user"
+msgstr ""
+
msgid "Assign yourself to these issues"
msgstr ""
@@ -2913,6 +3056,9 @@ msgid_plural "%d Assignees"
msgstr[0] ""
msgstr[1] ""
+msgid "Assignee has no permissions"
+msgstr ""
+
msgid "Assignee lists not available with your current license"
msgstr ""
@@ -2922,6 +3068,9 @@ msgstr ""
msgid "Assignee(s)"
msgstr ""
+msgid "Assignees"
+msgstr ""
+
msgid "Assigns %{assignee_users_sentence}."
msgstr ""
@@ -2957,6 +3106,9 @@ msgstr ""
msgid "Audit Events is a way to keep track of important events that happened in GitLab."
msgstr ""
+msgid "Audit Log"
+msgstr ""
+
msgid "AuditEvents|(removed)"
msgstr ""
@@ -2993,6 +3145,9 @@ msgstr ""
msgid "AuditLogs|IP Address"
msgstr ""
+msgid "AuditLogs|Member Events"
+msgstr ""
+
msgid "AuditLogs|No matching %{type} found."
msgstr ""
@@ -3275,9 +3430,6 @@ msgstr ""
msgid "Badges|e.g. %{exampleUrl}"
msgstr ""
-msgid "Badge|New"
-msgstr ""
-
msgid "Balsamiq file could not be loaded."
msgstr ""
@@ -3326,6 +3478,12 @@ msgstr ""
msgid "Below you will find all the groups that are public."
msgstr ""
+msgid "Beta"
+msgstr ""
+
+msgid "Bi-weekly code coverage"
+msgstr ""
+
msgid "Billing"
msgstr ""
@@ -3386,9 +3544,15 @@ msgstr ""
msgid "Bitbucket Server Import"
msgstr ""
+msgid "Bitbucket Server import"
+msgstr ""
+
msgid "Bitbucket import"
msgstr ""
+msgid "Blame"
+msgstr ""
+
msgid "Blocked"
msgstr ""
@@ -3401,9 +3565,6 @@ msgstr ""
msgid "Blog"
msgstr ""
-msgid "Blue helpers indicate an action to be taken."
-msgstr ""
-
msgid "Board name"
msgstr ""
@@ -3602,6 +3763,9 @@ msgstr ""
msgid "Broadcast Message was successfully updated."
msgstr ""
+msgid "Broadcast Messages"
+msgstr ""
+
msgid "Browse Directory"
msgstr ""
@@ -3656,6 +3820,9 @@ msgstr ""
msgid "By %{user_name}"
msgstr ""
+msgid "By URL"
+msgstr ""
+
msgid "By default GitLab sends emails in HTML and plain text formats so mail clients can choose what format to use. Disable this option if you only want to send emails in plain text format."
msgstr ""
@@ -3761,6 +3928,9 @@ msgstr ""
msgid "Can override approvers and approvals required per merge request"
msgstr ""
+msgid "Can't apply as this line has changed or the suggestion already matches its content."
+msgstr ""
+
msgid "Can't create snippet: %{err}"
msgstr ""
@@ -3947,9 +4117,6 @@ msgstr ""
msgid "Changes are still tracked. Useful for cluster/index migrations."
msgstr ""
-msgid "Changes are unknown"
-msgstr ""
-
msgid "Changes suppressed. Click to show."
msgstr ""
@@ -4256,15 +4423,9 @@ msgstr ""
msgid "Choose what content you want to see on a group’s overview page"
msgstr ""
-msgid "Choose which groups you wish to synchronize to this secondary node"
-msgstr ""
-
msgid "Choose which repositories you want to connect and run CI/CD pipelines."
msgstr ""
-msgid "Choose which shards you wish to synchronize to this secondary node"
-msgstr ""
-
msgid "Choose your framework"
msgstr ""
@@ -4562,9 +4723,15 @@ msgstr ""
msgid "Cluster does not exist"
msgstr ""
+msgid "Cluster is required for Stages::ClusterEndpointInserter"
+msgstr ""
+
msgid "Cluster level"
msgstr ""
+msgid "Cluster type must be specificed for Stages::ClusterEndpointInserter"
+msgstr ""
+
msgid "ClusterIntegration| %{custom_domain_start}More information%{custom_domain_end}."
msgstr ""
@@ -4676,7 +4843,7 @@ msgstr ""
msgid "ClusterIntegration|Cert-Manager"
msgstr ""
-msgid "ClusterIntegration|Cert-Manager is a native Kubernetes certificate management controller that helps with issuing certificates. Installing Cert-Manager on your cluster will issue a certificate by %{letsEncrypt} and ensure that certificates are valid and up-to-date."
+msgid "ClusterIntegration|Cert-Manager is a native Kubernetes certificate management controller that helps with issuing certificates. Installing Cert-Manager on your cluster will issue a certificate by %{linkStart}Let's Encrypt%{linkEnd} and ensure that certificates are valid and up-to-date."
msgstr ""
msgid "ClusterIntegration|Certificate Authority bundle (PEM format)"
@@ -4703,9 +4870,6 @@ msgstr ""
msgid "ClusterIntegration|Clear the local cache of namespace and service accounts. This is necessary if your integration has become out of sync. The cache is repopulated during the next CI job that requires namespace and service accounts."
msgstr ""
-msgid "ClusterIntegration|Cloud Run"
-msgstr ""
-
msgid "ClusterIntegration|Cluster being created"
msgstr ""
@@ -4790,7 +4954,7 @@ msgstr ""
msgid "ClusterIntegration|Crossplane"
msgstr ""
-msgid "ClusterIntegration|Crossplane enables declarative provisioning of managed services from your cloud of choice using %{codeStart}kubectl%{codeEnd} or %{gitlabIntegrationLink}. Crossplane runs inside your Kubernetes cluster and supports secure connectivity and secrets management between app containers and the cloud services they depend on."
+msgid "ClusterIntegration|Crossplane enables declarative provisioning of managed services from your cloud of choice using %{codeStart}kubectl%{codeEnd} or %{linkStart}GitLab Integration%{linkEnd}. Crossplane runs inside your Kubernetes cluster and supports secure connectivity and secrets management between app containers and the cloud services they depend on."
msgstr ""
msgid "ClusterIntegration|Deletes all GitLab resources attached to this cluster during removal"
@@ -4874,9 +5038,6 @@ msgstr ""
msgid "ClusterIntegration|GitLab-managed cluster"
msgstr ""
-msgid "ClusterIntegration|Gitlab Integration"
-msgstr ""
-
msgid "ClusterIntegration|Global default"
msgstr ""
@@ -4922,7 +5083,7 @@ msgstr ""
msgid "ClusterIntegration|Ingress gives you a way to route requests to services based on the request host or path, centralizing a number of services into a single entrypoint."
msgstr ""
-msgid "ClusterIntegration|Installing Ingress may incur additional costs. Learn more about %{pricingLink}."
+msgid "ClusterIntegration|Installing Ingress may incur additional costs. Learn more about %{linkStart}pricing%{linkEnd}."
msgstr ""
msgid "ClusterIntegration|Instance cluster"
@@ -4937,7 +5098,7 @@ msgstr ""
msgid "ClusterIntegration|Issuer Email"
msgstr ""
-msgid "ClusterIntegration|Issuers represent a certificate authority. You must provide an email address for your Issuer. "
+msgid "ClusterIntegration|Issuers represent a certificate authority. You must provide an email address for your Issuer."
msgstr ""
msgid "ClusterIntegration|Jupyter Hostname"
@@ -5009,9 +5170,6 @@ msgstr ""
msgid "ClusterIntegration|Learn more about instance Kubernetes clusters"
msgstr ""
-msgid "ClusterIntegration|Let's Encrypt"
-msgstr ""
-
msgid "ClusterIntegration|Loading IAM Roles"
msgstr ""
@@ -5123,7 +5281,7 @@ msgstr ""
msgid "ClusterIntegration|Prometheus"
msgstr ""
-msgid "ClusterIntegration|Prometheus is an open-source monitoring system with %{gitlabIntegrationLink} to monitor deployed applications."
+msgid "ClusterIntegration|Prometheus is an open-source monitoring system with %{linkStart}GitLab Integration%{linkEnd} to monitor deployed applications."
msgstr ""
msgid "ClusterIntegration|Provider details"
@@ -5279,10 +5437,10 @@ msgstr ""
msgid "ClusterIntegration|Select zone to choose machine type"
msgstr ""
-msgid "ClusterIntegration|Send Cilium Logs"
+msgid "ClusterIntegration|Send Container Network Policies Logs"
msgstr ""
-msgid "ClusterIntegration|Send ModSecurity Logs"
+msgid "ClusterIntegration|Send Web Application Firewall Logs"
msgstr ""
msgid "ClusterIntegration|Service Token"
@@ -5450,15 +5608,12 @@ msgstr ""
msgid "ClusterIntegration|documentation"
msgstr ""
-msgid "ClusterIntegration|installed via %{installed_via}"
+msgid "ClusterIntegration|installed via %{linkStart}Cloud Run%{linkEnd}"
msgstr ""
msgid "ClusterIntegration|meets the requirements"
msgstr ""
-msgid "ClusterIntegration|pricing"
-msgstr ""
-
msgid "ClusterIntegration|sign up"
msgstr ""
@@ -5495,6 +5650,15 @@ msgstr ""
msgid "Code"
msgstr ""
+msgid "Code Coverage: %{coveragePercentage}%{percentSymbol}"
+msgstr ""
+
+msgid "Code Coverage| Empty code coverage data"
+msgstr ""
+
+msgid "Code Coverage|Couldn't fetch the code coverage data"
+msgstr ""
+
msgid "Code Owners"
msgstr ""
@@ -5549,7 +5713,7 @@ msgstr ""
msgid "Collapse approvers"
msgstr ""
-msgid "Collapse child epics"
+msgid "Collapse milestones"
msgstr ""
msgid "Collapse replies"
@@ -5567,6 +5731,9 @@ msgstr ""
msgid "Coming soon"
msgstr ""
+msgid "Comma-separated, e.g. '1.1.1.1, 2.2.2.0/24'"
+msgstr ""
+
msgid "Command"
msgstr ""
@@ -5737,6 +5904,12 @@ msgstr ""
msgid "Compliance framework (optional)"
msgstr ""
+msgid "Compliance frameworks"
+msgstr ""
+
+msgid "ComplianceDashboard|created by:"
+msgstr ""
+
msgid "ComplianceFramework|GDPR"
msgstr ""
@@ -5875,6 +6048,9 @@ msgstr ""
msgid "Connection timed out"
msgstr ""
+msgid "Connection timeout"
+msgstr ""
+
msgid "Contact sales to upgrade"
msgstr ""
@@ -5930,9 +6106,6 @@ msgstr ""
msgid "ContainerRegistry|CLI Commands"
msgstr ""
-msgid "ContainerRegistry|Compressed Size"
-msgstr ""
-
msgid "ContainerRegistry|Container Registry"
msgstr ""
@@ -5948,6 +6121,9 @@ msgstr ""
msgid "ContainerRegistry|Copy push command"
msgstr ""
+msgid "ContainerRegistry|Delete selected"
+msgstr ""
+
msgid "ContainerRegistry|Docker connection error"
msgstr ""
@@ -5981,16 +6157,16 @@ msgstr ""
msgid "ContainerRegistry|If you are not already logged in, you need to authenticate to the Container Registry by using your GitLab username and password. If you have %{twofaDocLinkStart}Two-Factor Authentication%{twofaDocLinkEnd} enabled, use a %{personalAccessTokensDocLinkStart}Personal Access Token%{personalAccessTokensDocLinkEnd} instead of a password."
msgstr ""
-msgid "ContainerRegistry|Image ID"
+msgid "ContainerRegistry|Image ID: %{imageId}"
msgstr ""
msgid "ContainerRegistry|Image Repositories"
msgstr ""
-msgid "ContainerRegistry|Keep and protect the images that matter most."
+msgid "ContainerRegistry|Image tags"
msgstr ""
-msgid "ContainerRegistry|Last Updated"
+msgid "ContainerRegistry|Keep and protect the images that matter most."
msgstr ""
msgid "ContainerRegistry|Login"
@@ -6005,6 +6181,9 @@ msgstr ""
msgid "ContainerRegistry|Please contact your administrator."
msgstr ""
+msgid "ContainerRegistry|Published %{timeInfo}"
+msgstr ""
+
msgid "ContainerRegistry|Push an image"
msgstr ""
@@ -6020,9 +6199,6 @@ msgstr ""
msgid "ContainerRegistry|Remove repository"
msgstr ""
-msgid "ContainerRegistry|Remove selected tags"
-msgstr ""
-
msgid "ContainerRegistry|Remove tag"
msgid_plural "ContainerRegistry|Remove tags"
msgstr[0] ""
@@ -6052,9 +6228,6 @@ msgstr ""
msgid "ContainerRegistry|Sorry, your filter produced no results."
msgstr ""
-msgid "ContainerRegistry|Tag"
-msgstr ""
-
msgid "ContainerRegistry|Tag expiration policy"
msgstr ""
@@ -6199,21 +6372,6 @@ msgstr ""
msgid "Control the display of third party offers."
msgstr ""
-msgid "Control the maximum concurrency of LFS/attachment backfill for this secondary node"
-msgstr ""
-
-msgid "Control the maximum concurrency of container repository operations for this Geo node"
-msgstr ""
-
-msgid "Control the maximum concurrency of repository backfill for this secondary node"
-msgstr ""
-
-msgid "Control the maximum concurrency of verification operations for this Geo node"
-msgstr ""
-
-msgid "Control the minimum interval in days that a repository should be reverified for this primary node"
-msgstr ""
-
msgid "Cookie domain"
msgstr ""
@@ -6355,6 +6513,9 @@ msgstr ""
msgid "Could not find design."
msgstr ""
+msgid "Could not find iteration"
+msgstr ""
+
msgid "Could not remove the trigger."
msgstr ""
@@ -6391,6 +6552,12 @@ msgstr ""
msgid "Coverage"
msgstr ""
+msgid "Coverage Fuzzing"
+msgstr ""
+
+msgid "Crash State"
+msgstr ""
+
msgid "Create"
msgstr ""
@@ -6439,6 +6606,9 @@ msgstr ""
msgid "Create a personal access token on your account to pull or push via %{protocol}."
msgstr ""
+msgid "Create a project pre-populated with the necessary files to get you started quickly."
+msgstr ""
+
msgid "Create an account using:"
msgstr ""
@@ -6607,6 +6777,9 @@ msgstr ""
msgid "Creates branch '%{branch_name}' and a merge request to resolve this issue."
msgstr ""
+msgid "Creating"
+msgstr ""
+
msgid "Creating epic"
msgstr ""
@@ -6658,7 +6831,10 @@ msgstr ""
msgid "Current vulnerabilities count"
msgstr ""
-msgid "CurrentUser|Buy CI minutes"
+msgid "CurrentUser|Buy Pipeline minutes"
+msgstr ""
+
+msgid "CurrentUser|One of your groups is running out"
msgstr ""
msgid "CurrentUser|Profile"
@@ -6673,6 +6849,9 @@ msgstr ""
msgid "CurrentUser|Upgrade"
msgstr ""
+msgid "Custom Attributes"
+msgstr ""
+
msgid "Custom CI configuration path"
msgstr ""
@@ -6936,12 +7115,18 @@ msgstr ""
msgid "DAG"
msgstr ""
+msgid "DAG visualization requires at least 3 dependent jobs."
+msgstr ""
+
msgid "DNS"
msgstr ""
msgid "Dashboard"
msgstr ""
+msgid "Dashboard uid not found"
+msgstr ""
+
msgid "DashboardProjects|All"
msgstr ""
@@ -6966,6 +7151,9 @@ msgstr ""
msgid "Data is still calculating..."
msgstr ""
+msgid "Datasource name not found"
+msgstr ""
+
msgid "Date"
msgstr ""
@@ -7146,6 +7334,9 @@ msgstr ""
msgid "Delete this attachment"
msgstr ""
+msgid "Delete user list"
+msgstr ""
+
msgid "Delete variable"
msgstr ""
@@ -7462,6 +7653,9 @@ msgstr ""
msgid "Deploying to"
msgstr ""
+msgid "Deploying to AWS is easy with GitLab"
+msgstr ""
+
msgid "Deployment Frequency"
msgstr ""
@@ -7824,6 +8018,9 @@ msgstr ""
msgid "Dismissed on pipeline %{pipelineLink} at %{projectLink}"
msgstr ""
+msgid "Display alerts from all your monitoring tools directly within GitLab."
+msgstr ""
+
msgid "Display name"
msgstr ""
@@ -7881,9 +8078,6 @@ msgstr ""
msgid "Don't show again"
msgstr ""
-msgid "Don't worry, you can access this tour by clicking on the help icon in the top right corner and choose <strong>Learn GitLab</strong>."
-msgstr ""
-
msgid "Done"
msgstr ""
@@ -8010,6 +8204,9 @@ msgstr ""
msgid "Edit Release"
msgstr ""
+msgid "Edit Slack integration"
+msgstr ""
+
msgid "Edit Snippet"
msgstr ""
@@ -8049,6 +8246,9 @@ msgstr ""
msgid "Edit issues"
msgstr ""
+msgid "Edit iteration"
+msgstr ""
+
msgid "Edit public deploy key"
msgstr ""
@@ -8097,6 +8297,9 @@ msgstr ""
msgid "Email"
msgstr ""
+msgid "Email Notification"
+msgstr ""
+
msgid "Email address"
msgstr ""
@@ -8109,6 +8312,9 @@ msgstr ""
msgid "Email not verified. Please verify your email in Salesforce."
msgstr ""
+msgid "Email notification for unknown sign-ins"
+msgstr ""
+
msgid "Email patch"
msgstr ""
@@ -8340,6 +8546,9 @@ msgstr ""
msgid "Enforce DNS rebinding attack protection"
msgstr ""
+msgid "Enforce personal access token expiration"
+msgstr ""
+
msgid "Ensure connectivity is available from the GitLab server to the Prometheus server"
msgstr ""
@@ -8409,6 +8618,9 @@ msgstr ""
msgid "Enter the name of your application, and we'll return a unique %{type}."
msgstr ""
+msgid "Enter weights for storages for new repositories."
+msgstr ""
+
msgid "Enter your password to approve"
msgstr ""
@@ -8418,15 +8630,27 @@ msgstr ""
msgid "Environment does not have deployments"
msgstr ""
+msgid "Environment is required for Stages::MetricEndpointInserter"
+msgstr ""
+
+msgid "Environment is required for Stages::VariableEndpointInserter"
+msgstr ""
+
msgid "Environment scope"
msgstr ""
+msgid "Environment variables are applied to all project environments in this instance via the Runner. You can use environment variables for passwords, secret keys, etc. Make variables available to the running application by prepending the variable key with <code>K8S_SECRET_</code>. You can set variables to be:"
+msgstr ""
+
msgid "Environment variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. Additionally, they can be masked so they are hidden in job logs, though they must match certain regexp requirements to do so. You can use environment variables for passwords, secret keys, or whatever you want."
msgstr ""
msgid "Environment variables are configured by your administrator to be %{link_start}protected%{link_end} by default"
msgstr ""
+msgid "Environment variables on this GitLab instance are configured to be %{link_start}protected%{link_end} by default"
+msgstr ""
+
msgid "Environment:"
msgstr ""
@@ -8958,6 +9182,9 @@ msgstr ""
msgid "EventFilterBy|Filter by comments"
msgstr ""
+msgid "EventFilterBy|Filter by designs"
+msgstr ""
+
msgid "EventFilterBy|Filter by epic events"
msgstr ""
@@ -9090,15 +9317,15 @@ msgstr ""
msgid "Expand approvers"
msgstr ""
-msgid "Expand child epics"
-msgstr ""
-
msgid "Expand down"
msgstr ""
msgid "Expand dropdown"
msgstr ""
+msgid "Expand milestones"
+msgstr ""
+
msgid "Expand sidebar"
msgstr ""
@@ -9114,6 +9341,9 @@ msgstr ""
msgid "Expiration date"
msgstr ""
+msgid "Expiration not enforced"
+msgstr ""
+
msgid "Expiration policy for the Container Registry is a perfect solution for keeping the Registry space down while still enjoying the full power of GitLab CI/CD."
msgstr ""
@@ -9234,6 +9464,9 @@ msgstr ""
msgid "Failed Jobs"
msgstr ""
+msgid "Failed on"
+msgstr ""
+
msgid "Failed to add a Zoom meeting"
msgstr ""
@@ -9294,13 +9527,19 @@ msgstr ""
msgid "Failed to install."
msgstr ""
+msgid "Failed to load assignees. Please try again."
+msgstr ""
+
+msgid "Failed to load authors. Please try again."
+msgstr ""
+
msgid "Failed to load emoji list."
msgstr ""
msgid "Failed to load error details from Sentry."
msgstr ""
-msgid "Failed to load errors from Sentry. Error message: %{errorMessage}"
+msgid "Failed to load errors from Sentry."
msgstr ""
msgid "Failed to load group activity metrics. Please try again."
@@ -9381,6 +9620,9 @@ msgstr ""
msgid "Failed to set due date because the date format is invalid."
msgstr ""
+msgid "Failed to set iteration on this issue. Please try again."
+msgstr ""
+
msgid "Failed to signing using smartcard authentication"
msgstr ""
@@ -9603,13 +9845,10 @@ msgstr ""
msgid "FeatureFlags|Target environments"
msgstr ""
-msgid "FeatureFlags|There are no active feature flags"
-msgstr ""
-
-msgid "FeatureFlags|There are no inactive feature flags"
+msgid "FeatureFlags|There was an error fetching the feature flags."
msgstr ""
-msgid "FeatureFlags|There was an error fetching the feature flags."
+msgid "FeatureFlags|There was an error retrieving user lists"
msgstr ""
msgid "FeatureFlags|Try again in a few moments or contact your support team."
@@ -9621,9 +9860,18 @@ msgstr ""
msgid "FeatureFlag|Delete strategy"
msgstr ""
+msgid "FeatureFlag|List"
+msgstr ""
+
msgid "FeatureFlag|Percentage"
msgstr ""
+msgid "FeatureFlag|Select a user list"
+msgstr ""
+
+msgid "FeatureFlag|There are no configured user lists"
+msgstr ""
+
msgid "FeatureFlag|Type"
msgstr ""
@@ -9780,6 +10028,12 @@ msgstr ""
msgid "Filter..."
msgstr ""
+msgid "Find File"
+msgstr ""
+
+msgid "Find bugs in your code with coverage-guided fuzzing"
+msgstr ""
+
msgid "Find by path"
msgstr ""
@@ -10044,6 +10298,9 @@ msgstr ""
msgid "Geo allows you to replicate your GitLab instance to other geographical locations."
msgstr ""
+msgid "Geo nodes are paused using a command run on the node"
+msgstr ""
+
msgid "GeoNodeStatusEvent|%{timeAgoStr} (%{pendingEvents} events)"
msgstr ""
@@ -10128,6 +10385,9 @@ msgstr ""
msgid "GeoNodes|Not checksummed"
msgstr ""
+msgid "GeoNodes|Package files"
+msgstr ""
+
msgid "GeoNodes|Pausing replication stops the sync process. Are you sure?"
msgstr ""
@@ -10146,6 +10406,9 @@ msgstr ""
msgid "GeoNodes|Replication slots"
msgstr ""
+msgid "GeoNodes|Replication status"
+msgstr ""
+
msgid "GeoNodes|Repositories"
msgstr ""
@@ -10356,6 +10619,9 @@ msgstr ""
msgid "Geo|Synchronization failed - %{error}"
msgstr ""
+msgid "Geo|Synchronization of %{itemTitle} is disabled."
+msgstr ""
+
msgid "Geo|The database is currently %{db_lag} behind the primary node."
msgstr ""
@@ -10668,6 +10934,9 @@ msgstr ""
msgid "Go to %{link_to_google_takeout}."
msgstr ""
+msgid "Go to <strong>Issues</strong> > <strong>Boards</strong> to access your personalized learning issue board."
+msgstr ""
+
msgid "Go to Pipelines"
msgstr ""
@@ -10767,9 +11036,6 @@ msgstr ""
msgid "Go to your snippets"
msgstr ""
-msgid "Golden Tanuki"
-msgstr ""
-
msgid "Google Cloud Platform"
msgstr ""
@@ -10791,6 +11057,9 @@ msgstr ""
msgid "Grafana URL"
msgstr ""
+msgid "Grafana response contains invalid json"
+msgstr ""
+
msgid "GrafanaIntegration|API Token"
msgstr ""
@@ -10839,9 +11108,6 @@ msgstr ""
msgid "Group %{group_name} was successfully created."
msgstr ""
-msgid "Group '%{group_name}' is being imported."
-msgstr ""
-
msgid "Group Audit Events"
msgstr ""
@@ -10878,9 +11144,6 @@ msgstr ""
msgid "Group by:"
msgstr ""
-msgid "Group could not be imported: %{errors}"
-msgstr ""
-
msgid "Group description"
msgstr ""
@@ -10908,9 +11171,15 @@ msgstr ""
msgid "Group has not been marked for deletion"
msgstr ""
+msgid "Group import could not be scheduled"
+msgstr ""
+
msgid "Group info:"
msgstr ""
+msgid "Group is required when cluster_type is :group"
+msgstr ""
+
msgid "Group maintainers can register group runners in the %{link}"
msgstr ""
@@ -10974,6 +11243,24 @@ msgstr ""
msgid "GroupActivyMetrics|Recent activity (last 90 days)"
msgstr ""
+msgid "GroupImport|Failed to import group."
+msgstr ""
+
+msgid "GroupImport|Group '%{group_name}' is being imported."
+msgstr ""
+
+msgid "GroupImport|Group could not be imported: %{errors}"
+msgstr ""
+
+msgid "GroupImport|Please wait while we import the group for you. Refresh at will."
+msgstr ""
+
+msgid "GroupImport|The group was successfully imported."
+msgstr ""
+
+msgid "GroupImport|Unable to process group import file"
+msgstr ""
+
msgid "GroupRoadmap|%{dateWord} – No end date"
msgstr ""
@@ -11241,6 +11528,9 @@ msgstr ""
msgid "Groups (%{groups})"
msgstr ""
+msgid "Groups and projects"
+msgstr ""
+
msgid "Groups can also be nested by creating %{subgroup_docs_link_start}subgroups%{subgroup_docs_link_end}."
msgstr ""
@@ -11286,6 +11576,33 @@ msgstr ""
msgid "GroupsEmptyState|You can manage your group member’s permissions and access to each project in the group."
msgstr ""
+msgid "GroupsNew|Contact an administrator to enable options for importing your group."
+msgstr ""
+
+msgid "GroupsNew|Create"
+msgstr ""
+
+msgid "GroupsNew|Create group"
+msgstr ""
+
+msgid "GroupsNew|GitLab group export"
+msgstr ""
+
+msgid "GroupsNew|Import"
+msgstr ""
+
+msgid "GroupsNew|Import group"
+msgstr ""
+
+msgid "GroupsNew|My Awesome Group"
+msgstr ""
+
+msgid "GroupsNew|No import options available"
+msgstr ""
+
+msgid "GroupsNew|To copy a GitLab group between installations, navigate to the group settings page for the original installation, generate an export file, and upload it here."
+msgstr ""
+
msgid "GroupsTree|Are you sure you want to leave the \"%{fullName}\" group?"
msgstr ""
@@ -11388,6 +11705,9 @@ msgstr ""
msgid "Helps reduce request volume for protected paths"
msgstr ""
+msgid "Here are all your projects in your group, including the one you just created. To start, let’s take a look at your personalized learning project which will help you learn about GitLab at your own pace."
+msgstr ""
+
msgid "Here you will find recent merge request activity"
msgstr ""
@@ -11402,6 +11722,9 @@ msgid_plural "Hide charts"
msgstr[0] ""
msgstr[1] ""
+msgid "Hide details"
+msgstr ""
+
msgid "Hide file browser"
msgstr ""
@@ -11411,6 +11734,9 @@ msgstr ""
msgid "Hide host keys manual input"
msgstr ""
+msgid "Hide list"
+msgstr ""
+
msgid "Hide marketing-related entries from help"
msgstr ""
@@ -11431,9 +11757,6 @@ msgstr[1] ""
msgid "Hide values"
msgstr ""
-msgid "Hiding all labels"
-msgstr ""
-
msgid "High or unknown vulnerabilities present"
msgstr ""
@@ -11518,6 +11841,9 @@ msgstr ""
msgid "ID:"
msgstr ""
+msgid "IDE"
+msgstr ""
+
msgid "IDE|Allow live previews of JavaScript projects in the Web IDE using CodeSandbox Live Preview."
msgstr ""
@@ -11608,10 +11934,10 @@ msgstr ""
msgid "If enabled"
msgstr ""
-msgid "If enabled, access to projects will be validated on an external service using their classification label."
+msgid "If enabled, GitLab will handle Object Storage replication using Geo. %{linkStart}More information%{linkEnd}"
msgstr ""
-msgid "If enabled, and if object storage is enabled, GitLab will handle Object Storage replication using Geo"
+msgid "If enabled, access to projects will be validated on an external service using their classification label."
msgstr ""
msgid "If there is no previous license or if the previous license has expired, some GitLab functionality will be blocked until a new, valid license is uploaded."
@@ -11973,6 +12299,9 @@ msgid_plural "Instances"
msgstr[0] ""
msgstr[1] ""
+msgid "Instance Configuration"
+msgstr ""
+
msgid "Instance Statistics visibility"
msgstr ""
@@ -11994,9 +12323,6 @@ msgstr ""
msgid "Integrations"
msgstr ""
-msgid "Integrations allow you to integrate GitLab with other applications"
-msgstr ""
-
msgid "Integrations|All details"
msgstr ""
@@ -12108,6 +12434,9 @@ msgstr ""
msgid "Invalid query"
msgstr ""
+msgid "Invalid repository bundle for snippet with id %{snippet_id}"
+msgstr ""
+
msgid "Invalid repository path"
msgstr ""
@@ -12126,6 +12455,9 @@ msgstr ""
msgid "Invalid two-factor code."
msgstr ""
+msgid "Invalid yaml"
+msgstr ""
+
msgid "Invitation"
msgstr ""
@@ -12345,15 +12677,24 @@ msgstr ""
msgid "It seems like the Dependency Scanning job ran successfully, but no dependencies have been detected in your project."
msgstr ""
+msgid "It seems that there is currently no available data for code coverage"
+msgstr ""
+
msgid "It's you"
msgstr ""
+msgid "Iteration"
+msgstr ""
+
msgid "Iteration changed to"
msgstr ""
msgid "Iteration removed"
msgstr ""
+msgid "Iteration updated"
+msgstr ""
+
msgid "Iterations"
msgstr ""
@@ -12384,12 +12725,18 @@ msgstr ""
msgid "January"
msgstr ""
+msgid "Jira Issues"
+msgstr ""
+
msgid "Jira import is already running."
msgstr ""
msgid "Jira integration not configured."
msgstr ""
+msgid "Jira project key is not configured"
+msgstr ""
+
msgid "Jira project: %{importProject}"
msgstr ""
@@ -12570,6 +12917,9 @@ msgstr ""
msgid "Keep divergent refs"
msgstr ""
+msgid "Kerberos access denied"
+msgstr ""
+
msgid "Key"
msgstr ""
@@ -12579,10 +12929,13 @@ msgstr ""
msgid "Key: %{key}"
msgstr ""
-msgid "Keyboard Shortcuts"
+msgid "Keyboard shortcuts"
msgstr ""
-msgid "Keyboard shortcuts"
+msgid "Keys"
+msgstr ""
+
+msgid "Ki"
msgstr ""
msgid "Kubernetes"
@@ -12624,6 +12977,9 @@ msgstr ""
msgid "LDAP"
msgstr ""
+msgid "LDAP Synchronization"
+msgstr ""
+
msgid "LDAP settings"
msgstr ""
@@ -12848,12 +13204,18 @@ msgstr ""
msgid "Learn more about custom project templates"
msgstr ""
+msgid "Learn more about deploying to AWS"
+msgstr ""
+
msgid "Learn more about deploying to a cluster"
msgstr ""
msgid "Learn more about group-level project templates"
msgstr ""
+msgid "Learn more about job dependencies"
+msgstr ""
+
msgid "Learn more about signing commits"
msgstr ""
@@ -12905,9 +13267,21 @@ msgstr ""
msgid "License History"
msgstr ""
+msgid "License ID:"
+msgstr ""
+
+msgid "License URL"
+msgstr ""
+
msgid "License-Check"
msgstr ""
+msgid "LicenseCompliance|%{docLinkStart}License Approvals%{docLinkEnd} are active"
+msgstr ""
+
+msgid "LicenseCompliance|%{docLinkStart}License Approvals%{docLinkEnd} are inactive"
+msgstr ""
+
msgid "LicenseCompliance|Add a license"
msgstr ""
@@ -12929,9 +13303,15 @@ msgstr ""
msgid "LicenseCompliance|Deny"
msgstr ""
+msgid "LicenseCompliance|Learn more about %{linkStart}License Approvals%{linkEnd}"
+msgstr ""
+
msgid "LicenseCompliance|License"
msgstr ""
+msgid "LicenseCompliance|License Approvals"
+msgstr ""
+
msgid "LicenseCompliance|License Compliance detected %d license and policy violation for the source branch only; approval required"
msgid_plural "LicenseCompliance|License Compliance detected %d licenses and policy violations for the source branch only; approval required"
msgstr[0] ""
@@ -13152,6 +13532,9 @@ msgstr ""
msgid "List your Bitbucket Server repositories"
msgstr ""
+msgid "Lists"
+msgstr ""
+
msgid "Live preview"
msgstr ""
@@ -13335,6 +13718,9 @@ msgstr ""
msgid "Manage project labels"
msgstr ""
+msgid "Manage storage usage"
+msgstr ""
+
msgid "Manage two-factor authentication"
msgstr ""
@@ -13350,6 +13736,9 @@ msgstr ""
msgid "Manifest file import"
msgstr ""
+msgid "Manifest import"
+msgstr ""
+
msgid "Manual job"
msgstr ""
@@ -13497,6 +13886,12 @@ msgstr ""
msgid "Maximum field length"
msgstr ""
+msgid "Maximum file size is 2MB. Please select a smaller file."
+msgstr ""
+
+msgid "Maximum import size (MB)"
+msgstr ""
+
msgid "Maximum job timeout"
msgstr ""
@@ -13536,6 +13931,9 @@ msgstr ""
msgid "Maximum size of Elasticsearch bulk indexing requests."
msgstr ""
+msgid "Maximum size of import files."
+msgstr ""
+
msgid "Maximum size of individual attachments in comments."
msgstr ""
@@ -13587,6 +13985,9 @@ msgstr ""
msgid "Merge (when the pipeline succeeds)"
msgstr ""
+msgid "Merge Conflicts"
+msgstr ""
+
msgid "Merge Request"
msgstr ""
@@ -13713,6 +14114,9 @@ msgstr ""
msgid "MergeRequests|Squash task canceled: another squash is already in progress."
msgstr ""
+msgid "MergeRequests|This project does not allow squashing commits when merge requests are accepted."
+msgstr ""
+
msgid "MergeRequests|Thread stays resolved"
msgstr ""
@@ -13866,6 +14270,12 @@ msgstr ""
msgid "MetricsSettings|Add a button to the metrics dashboard linking directly to your existing external dashboard."
msgstr ""
+msgid "MetricsSettings|Choose whether to display dashboard metrics in UTC or the user's local timezone."
+msgstr ""
+
+msgid "MetricsSettings|Dashboard timezone"
+msgstr ""
+
msgid "MetricsSettings|External dashboard URL"
msgstr ""
@@ -13875,6 +14285,12 @@ msgstr ""
msgid "MetricsSettings|Metrics Dashboard"
msgstr ""
+msgid "MetricsSettings|UTC (Coordinated Universal Time)"
+msgstr ""
+
+msgid "MetricsSettings|User's local timezone"
+msgstr ""
+
msgid "Metrics|Add metric"
msgstr ""
@@ -13890,6 +14306,9 @@ msgstr ""
msgid "Metrics|Create metric"
msgstr ""
+msgid "Metrics|Current"
+msgstr ""
+
msgid "Metrics|Delete metric"
msgstr ""
@@ -13940,6 +14359,9 @@ msgstr ""
msgid "Metrics|Max"
msgstr ""
+msgid "Metrics|Min"
+msgstr ""
+
msgid "Metrics|Must be a valid PromQL query."
msgstr ""
@@ -13955,6 +14377,9 @@ msgstr ""
msgid "Metrics|Refresh dashboard"
msgstr ""
+msgid "Metrics|Select a value"
+msgstr ""
+
msgid "Metrics|Star dashboard"
msgstr ""
@@ -13973,12 +14398,18 @@ msgstr ""
msgid "Metrics|There was an error getting annotations information."
msgstr ""
+msgid "Metrics|There was an error getting dashboard validation warnings information."
+msgstr ""
+
msgid "Metrics|There was an error getting deployment information."
msgstr ""
msgid "Metrics|There was an error getting environments information."
msgstr ""
+msgid "Metrics|There was an error getting options for variable \"%{name}\"."
+msgstr ""
+
msgid "Metrics|There was an error trying to validate your query"
msgstr ""
@@ -14021,6 +14452,9 @@ msgstr ""
msgid "Metrics|You're about to permanently delete this metric. This cannot be undone."
msgstr ""
+msgid "Metrics|Your dashboard schema is invalid. Edit the dashboard to correct the YAML schema."
+msgstr ""
+
msgid "Metrics|e.g. HTTP requests"
msgstr ""
@@ -14036,12 +14470,18 @@ msgstr ""
msgid "Metrics|e.g. req/sec"
msgstr ""
+msgid "Mi"
+msgstr ""
+
msgid "Microsoft Azure"
msgstr ""
msgid "Middleman project with Static Site Editor support"
msgstr ""
+msgid "Migrate your data from an external source like GitHub, Bitbucket, or another instance of GitLab."
+msgstr ""
+
msgid "Migrated %{success_count}/%{total_count} files."
msgstr ""
@@ -14176,6 +14616,9 @@ msgstr ""
msgid "Minimum capacity to be available before we schedule more mirrors preemptively."
msgstr ""
+msgid "Minimum interval in days"
+msgstr ""
+
msgid "Minimum length is %{minimum_password_length} characters"
msgstr ""
@@ -14362,6 +14805,12 @@ msgstr ""
msgid "Multiple uploaders found: %{uploader_types}"
msgstr ""
+msgid "Must match with the %{codeStart}external_url%{codeEnd} in %{codeStart}/etc/gitlab/gitlab.rb%{codeEnd}."
+msgstr ""
+
+msgid "Must match with the %{codeStart}geo_node_name%{codeEnd} in %{codeStart}/etc/gitlab/gitlab.rb%{codeEnd}. %{linkStart}More information%{linkEnd}"
+msgstr ""
+
msgid "My Awesome Group"
msgstr ""
@@ -14476,10 +14925,16 @@ msgstr ""
msgid "New Application"
msgstr ""
+msgid "New Branch"
+msgstr ""
+
+msgid "New Deploy Key"
+msgstr ""
+
msgid "New Environment"
msgstr ""
-msgid "New Geo Node"
+msgid "New File"
msgstr ""
msgid "New Group"
@@ -14496,15 +14951,15 @@ msgid_plural "New Issues"
msgstr[0] ""
msgstr[1] ""
-msgid "New Iteration"
-msgstr ""
-
msgid "New Jira import"
msgstr ""
msgid "New Label"
msgstr ""
+msgid "New Merge Request"
+msgstr ""
+
msgid "New Milestone"
msgstr ""
@@ -14523,6 +14978,9 @@ msgstr ""
msgid "New Snippet"
msgstr ""
+msgid "New User"
+msgstr ""
+
msgid "New branch"
msgstr ""
@@ -14565,6 +15023,9 @@ msgstr ""
msgid "New issue title"
msgstr ""
+msgid "New iteration"
+msgstr ""
+
msgid "New iteration created"
msgstr ""
@@ -14742,6 +15203,12 @@ msgstr ""
msgid "No grouping"
msgstr ""
+msgid "No iteration"
+msgstr ""
+
+msgid "No iterations to show"
+msgstr ""
+
msgid "No job log"
msgstr ""
@@ -14784,6 +15251,9 @@ msgstr ""
msgid "No other labels with such name or description"
msgstr ""
+msgid "No panels matching properties %{opts}"
+msgstr ""
+
msgid "No parent group"
msgstr ""
@@ -14817,6 +15287,9 @@ msgstr ""
msgid "No schedules"
msgstr ""
+msgid "No stack trace for this error"
+msgstr ""
+
msgid "No starrers matched your search"
msgstr ""
@@ -14829,7 +15302,7 @@ msgstr ""
msgid "No test coverage"
msgstr ""
-msgid "No thanks, don't show this again"
+msgid "No thanks"
msgstr ""
msgid "No vulnerabilities present"
@@ -14844,9 +15317,6 @@ msgstr ""
msgid "No, directly import the existing email addresses and usernames."
msgstr ""
-msgid "No, not interested right now"
-msgstr ""
-
msgid "No. of commits"
msgstr ""
@@ -14892,9 +15362,6 @@ msgstr ""
msgid "Not found."
msgstr ""
-msgid "Not helpful"
-msgstr ""
-
msgid "Not now"
msgstr ""
@@ -15051,6 +15518,9 @@ msgstr ""
msgid "Notifications on"
msgstr ""
+msgid "Notify users by email when sign-in location is not recognized"
+msgstr ""
+
msgid "Nov"
msgstr ""
@@ -15117,9 +15587,6 @@ msgstr ""
msgid "Oh no!"
msgstr ""
-msgid "Ok let's go"
-msgstr ""
-
msgid "Oldest first"
msgstr ""
@@ -15132,16 +15599,49 @@ msgstr ""
msgid "On track"
msgstr ""
+msgid "OnDemandScans|Attached branch"
+msgstr ""
+
+msgid "OnDemandScans|Attached branch is where the scan job runs."
+msgstr ""
+
+msgid "OnDemandScans|Could not run the scan. Please try again."
+msgstr ""
+
msgid "OnDemandScans|Create new DAST scan"
msgstr ""
+msgid "OnDemandScans|DAST will scan the target URL and any discovered sub URLs."
+msgstr ""
+
+msgid "OnDemandScans|New on-demand DAST scan"
+msgstr ""
+
msgid "OnDemandScans|On-demand Scans"
msgstr ""
+msgid "OnDemandScans|On-demand scans run outside the DevOps cycle and find vulnerabilities in your projects. %{learnMoreLinkStart}Learn more%{learnMoreLinkEnd}"
+msgstr ""
+
+msgid "OnDemandScans|Only a passive scan can be performed on demand."
+msgstr ""
+
+msgid "OnDemandScans|Passive DAST Scan"
+msgstr ""
+
+msgid "OnDemandScans|Please enter a valid URL format, ex: http://www.example.com/home"
+msgstr ""
+
+msgid "OnDemandScans|Run this scan"
+msgstr ""
+
+msgid "OnDemandScans|Scan mode"
+msgstr ""
+
msgid "OnDemandScans|Schedule or run scans immediately against target sites. Currently available on-demand scan type: DAST. %{helpLinkStart}More information%{helpLinkEnd}"
msgstr ""
-msgid "Onboarding"
+msgid "OnDemandScans|Target URL"
msgstr ""
msgid "Once imported, repositories can be mirrored over SSH. Read more %{link_start}here%{link_end}."
@@ -15167,6 +15667,9 @@ msgstr ""
msgid "One or more of you personal access tokens were revoked"
msgstr ""
+msgid "One or more of your %{provider} projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
+msgstr ""
+
msgid "One or more of your Bitbucket projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
msgstr ""
@@ -15668,6 +16171,9 @@ msgstr ""
msgid "Parent epic is not present."
msgstr ""
+msgid "Parsing error for param :embed_json. %{message}"
+msgstr ""
+
msgid "Part of merge request changes"
msgstr ""
@@ -15680,6 +16186,9 @@ msgstr ""
msgid "Passed"
msgstr ""
+msgid "Passed on"
+msgstr ""
+
msgid "Password"
msgstr ""
@@ -15764,6 +16273,9 @@ msgstr ""
msgid "Perform common operations on GitLab project"
msgstr ""
+msgid "Performance and resource management"
+msgstr ""
+
msgid "Performance optimization"
msgstr ""
@@ -15971,7 +16483,7 @@ msgstr ""
msgid "Pipelines|Something went wrong while cleaning runners cache."
msgstr ""
-msgid "Pipelines|There are currently no %{scope} pipelines."
+msgid "Pipelines|There are currently no finished pipelines."
msgstr ""
msgid "Pipelines|There are currently no pipelines."
@@ -16082,6 +16594,9 @@ msgstr ""
msgid "Pipeline|Stop pipeline #%{pipelineId}?"
msgstr ""
+msgid "Pipeline|Tag name"
+msgstr ""
+
msgid "Pipeline|Trigger author"
msgstr ""
@@ -16163,12 +16678,18 @@ msgstr ""
msgid "Please check your email (%{email}) to verify that you own this address and unlock the power of CI/CD. Didn't receive it? %{resend_link}. Wrong email address? %{update_link}."
msgstr ""
+msgid "Please choose a file"
+msgstr ""
+
msgid "Please choose a group URL with no special characters."
msgstr ""
msgid "Please complete your profile with email address"
msgstr ""
+msgid "Please convert %{linkStart}them to Git%{linkEnd}, and go through the %{linkToImportFlow} again."
+msgstr ""
+
msgid "Please convert them to %{link_to_git}, and go through the %{link_to_import_flow} again."
msgstr ""
@@ -16226,6 +16747,9 @@ msgstr ""
msgid "Please provide a valid email address."
msgstr ""
+msgid "Please provide attributes to update"
+msgstr ""
+
msgid "Please refer to <a href=\"%{docs_url}\">%{docs_url}</a>"
msgstr ""
@@ -16961,6 +17485,9 @@ msgstr ""
msgid "Project has too many %{label_for_message} to search"
msgstr ""
+msgid "Project is required when cluster_type is :project"
+msgstr ""
+
msgid "Project members"
msgstr ""
@@ -17078,7 +17605,40 @@ msgstr ""
msgid "ProjectService|Comment"
msgstr ""
-msgid "ProjectService|Comment will be posted on each event"
+msgid "ProjectService|Event will be triggered by a push to the repository"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a commit is created/updated"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a confidential issue is created/updated/closed"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a deployment finishes"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a merge request is created/updated/merged"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a new tag is pushed to the repository"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a new, unique alert is recorded"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a pipeline status changes"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a wiki page is created/updated"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when an issue is created/updated/closed"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when someone adds a comment"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when someone adds a comment on a confidential issue"
msgstr ""
msgid "ProjectService|Perform common operations on GitLab project: %{project_name}"
@@ -17129,6 +17689,9 @@ msgstr ""
msgid "ProjectSettings|Enable 'Delete source branch' option by default"
msgstr ""
+msgid "ProjectSettings|Enable merge trains and pipelines for merged results"
+msgstr ""
+
msgid "ProjectSettings|Every merge creates a merge commit"
msgstr ""
@@ -17159,7 +17722,7 @@ msgstr ""
msgid "ProjectSettings|Forks"
msgstr ""
-msgid "ProjectSettings|Git Large File Storage"
+msgid "ProjectSettings|Git Large File Storage (LFS)"
msgstr ""
msgid "ProjectSettings|Internal"
@@ -17195,9 +17758,6 @@ msgstr ""
msgid "ProjectSettings|Merge options"
msgstr ""
-msgid "ProjectSettings|Merge pipelines will try to validate the post-merge result prior to merging"
-msgstr ""
-
msgid "ProjectSettings|Merge requests"
msgstr ""
@@ -17225,6 +17785,9 @@ msgstr ""
msgid "ProjectSettings|Pipelines"
msgstr ""
+msgid "ProjectSettings|Pipelines for merge requests must be enabled in the CI/CD configuration file, or pipelines could be unresolvable or dropped"
+msgstr ""
+
msgid "ProjectSettings|Pipelines must succeed"
msgstr ""
@@ -17252,6 +17815,9 @@ msgstr ""
msgid "ProjectSettings|Show link to create/view merge request when pushing from the command line"
msgstr ""
+msgid "ProjectSettings|Skipped pipelines are considered successful"
+msgstr ""
+
msgid "ProjectSettings|Snippets"
msgstr ""
@@ -17267,6 +17833,9 @@ msgstr ""
msgid "ProjectSettings|These checks must pass before merge requests can be merged"
msgstr ""
+msgid "ProjectSettings|This introduces the risk of merging changes that will not pass the pipeline."
+msgstr ""
+
msgid "ProjectSettings|This setting is applied on the server level and can be overridden by an admin."
msgstr ""
@@ -17444,15 +18013,27 @@ msgstr ""
msgid "ProjectsNew|Blank project"
msgstr ""
+msgid "ProjectsNew|Connect your external repository to GitLab CI/CD."
+msgstr ""
+
msgid "ProjectsNew|Contact an administrator to enable options for importing your project."
msgstr ""
msgid "ProjectsNew|Create"
msgstr ""
+msgid "ProjectsNew|Create a blank project to house your files, plan your work, and collaborate on code, among other things."
+msgstr ""
+
+msgid "ProjectsNew|Create blank project"
+msgstr ""
+
msgid "ProjectsNew|Create from template"
msgstr ""
+msgid "ProjectsNew|Create new project"
+msgstr ""
+
msgid "ProjectsNew|Creating project & repository."
msgstr ""
@@ -17477,6 +18058,9 @@ msgstr ""
msgid "ProjectsNew|Project description %{tag_start}(optional)%{tag_end}"
msgstr ""
+msgid "ProjectsNew|Run CI/CD for external repository"
+msgstr ""
+
msgid "ProjectsNew|Template"
msgstr ""
@@ -17738,6 +18322,9 @@ msgstr ""
msgid "Protected Branch"
msgstr ""
+msgid "Protected Branches"
+msgstr ""
+
msgid "Protected Environment"
msgstr ""
@@ -17750,6 +18337,9 @@ msgstr ""
msgid "Protected Tag"
msgstr ""
+msgid "Protected Tags"
+msgstr ""
+
msgid "Protected branches"
msgstr ""
@@ -17849,6 +18439,9 @@ msgstr ""
msgid "Provider"
msgstr ""
+msgid "Proxy support for this API is not available currently"
+msgstr ""
+
msgid "Pseudonymizer data collection"
msgstr ""
@@ -17873,6 +18466,9 @@ msgstr ""
msgid "Publish to status page"
msgstr ""
+msgid "Published on status page"
+msgstr ""
+
msgid "Publishes this issue to the associated status page."
msgstr ""
@@ -18047,6 +18643,9 @@ msgstr ""
msgid "Recipe"
msgstr ""
+msgid "Reconfigure"
+msgstr ""
+
msgid "Recover hidden stage"
msgstr ""
@@ -18133,6 +18732,9 @@ msgstr ""
msgid "Registration|Your profile"
msgstr ""
+msgid "Regulate approvals by authors/committers, based on compliance frameworks. Can be changed only at the instance level."
+msgstr ""
+
msgid "Rejected (closed)"
msgstr ""
@@ -18183,15 +18785,24 @@ msgstr ""
msgid "ReleaseAssetLinkType|Image"
msgstr ""
+msgid "ReleaseAssetLinkType|Images"
+msgstr ""
+
msgid "ReleaseAssetLinkType|Other"
msgstr ""
msgid "ReleaseAssetLinkType|Package"
msgstr ""
+msgid "ReleaseAssetLinkType|Packages"
+msgstr ""
+
msgid "ReleaseAssetLinkType|Runbook"
msgstr ""
+msgid "ReleaseAssetLinkType|Runbooks"
+msgstr ""
+
msgid "Releases"
msgstr ""
@@ -18204,6 +18815,9 @@ msgstr ""
msgid "Releases documentation"
msgstr ""
+msgid "Releases|New Release"
+msgstr ""
+
msgid "Release|Something went wrong while getting the release details"
msgstr ""
@@ -18276,6 +18890,9 @@ msgstr ""
msgid "Remove fork relationship"
msgstr ""
+msgid "Remove from batch"
+msgstr ""
+
msgid "Remove from board"
msgstr ""
@@ -18465,6 +19082,12 @@ msgstr ""
msgid "Replication"
msgstr ""
+msgid "Replication enabled"
+msgstr ""
+
+msgid "Replication paused"
+msgstr ""
+
msgid "Reply by email"
msgstr ""
@@ -18492,15 +19115,15 @@ msgstr ""
msgid "Reported %{timeAgo} by %{reportedBy}"
msgstr ""
-msgid "Reported Resource Changes: %{addNum} to add, %{changeNum} to change, %{deleteNum} to delete"
-msgstr ""
-
msgid "Reporter"
msgstr ""
msgid "Reporting"
msgstr ""
+msgid "Reports"
+msgstr ""
+
msgid "Reports|%{combinedString} and %{resolvedString}"
msgstr ""
@@ -18539,6 +19162,9 @@ msgstr ""
msgid "Reports|Failure"
msgstr ""
+msgid "Reports|Identifier"
+msgstr ""
+
msgid "Reports|Metrics reports are loading"
msgstr ""
@@ -18551,6 +19177,9 @@ msgstr ""
msgid "Reports|Metrics reports failed loading results"
msgstr ""
+msgid "Reports|Scanner"
+msgstr ""
+
msgid "Reports|Severity"
msgstr ""
@@ -18599,15 +19228,33 @@ msgstr ""
msgid "Repository cleanup has started. You will receive an email once the cleanup operation is complete."
msgstr ""
+msgid "Repository files count over the limit"
+msgstr ""
+
+msgid "Repository has an invalid default branch name."
+msgstr ""
+
+msgid "Repository has more than one branch."
+msgstr ""
+
msgid "Repository has no locks."
msgstr ""
+msgid "Repository has tags."
+msgstr ""
+
msgid "Repository maintenance"
msgstr ""
msgid "Repository mirroring"
msgstr ""
+msgid "Repository must contain at least 1 file."
+msgstr ""
+
+msgid "Repository size is above the limit."
+msgstr ""
+
msgid "Repository static objects"
msgstr ""
@@ -18617,7 +19264,7 @@ msgstr ""
msgid "Repository sync capacity"
msgstr ""
-msgid "Repository: %{counter_repositories} / Wikis: %{counter_wikis} / Build Artifacts: %{counter_build_artifacts} / LFS: %{counter_lfs_objects}"
+msgid "Repository: %{counter_repositories} / Wikis: %{counter_wikis} / Build Artifacts: %{counter_build_artifacts} / LFS: %{counter_lfs_objects} / Snippets: %{counter_snippets}"
msgstr ""
msgid "RepositorySettingsAccessLevel|Select"
@@ -18629,6 +19276,9 @@ msgstr ""
msgid "Request Headers"
msgstr ""
+msgid "Request details"
+msgstr ""
+
msgid "Request parameter %{param} is missing."
msgstr ""
@@ -18735,6 +19385,9 @@ msgstr ""
msgid "Reset template"
msgstr ""
+msgid "Reset to project defaults"
+msgstr ""
+
msgid "Resetting the authorization key for this project will require updating the authorization key in every alert source it is enabled in."
msgstr ""
@@ -18831,9 +19484,6 @@ msgstr ""
msgid "Resume"
msgstr ""
-msgid "Resume replication"
-msgstr ""
-
msgid "Resync"
msgstr ""
@@ -19456,6 +20106,9 @@ msgstr ""
msgid "SecurityReports|False positive"
msgstr ""
+msgid "SecurityReports|Group Security Dashboard"
+msgstr ""
+
msgid "SecurityReports|Hide dismissed"
msgstr ""
@@ -19510,10 +20163,13 @@ msgstr ""
msgid "SecurityReports|Remove project from dashboard"
msgstr ""
-msgid "SecurityReports|Report type"
+msgid "SecurityReports|Return to dashboard"
msgstr ""
-msgid "SecurityReports|Return to dashboard"
+msgid "SecurityReports|Scan details"
+msgstr ""
+
+msgid "SecurityReports|Scanner type"
msgstr ""
msgid "SecurityReports|Security Dashboard"
@@ -19570,6 +20226,9 @@ msgstr ""
msgid "SecurityReports|There was an error while generating the report."
msgstr ""
+msgid "SecurityReports|Unable to add %{invalidProjectsMessage}"
+msgstr ""
+
msgid "SecurityReports|Unable to add %{invalidProjects}"
msgstr ""
@@ -19684,6 +20343,9 @@ msgstr ""
msgid "Select due date"
msgstr ""
+msgid "Select file"
+msgstr ""
+
msgid "Select group or project"
msgstr ""
@@ -19747,9 +20409,6 @@ msgstr ""
msgid "Select the branch you want to set as the default for this project. All merge requests and commits will automatically be made against this branch unless you specify a different one."
msgstr ""
-msgid "Select the configured storage available for new repositories to be placed on."
-msgstr ""
-
msgid "Select the custom project template source group."
msgstr ""
@@ -19942,12 +20601,6 @@ msgstr ""
msgid "Service Desk is enabled but not yet active"
msgstr ""
-msgid "Service Desk is off"
-msgstr ""
-
-msgid "Service Desk is on"
-msgstr ""
-
msgid "Service Templates"
msgstr ""
@@ -20035,6 +20688,15 @@ msgstr ""
msgid "Set the milestone to %{milestone_reference}."
msgstr ""
+msgid "Set the number of concurrent requests this secondary node will make to the primary node while backfilling."
+msgstr ""
+
+msgid "Set the synchronization and verification capacity for the secondary node."
+msgstr ""
+
+msgid "Set the timeout in seconds to send a secondary node status to the primary and IPs allowed for the secondary nodes."
+msgstr ""
+
msgid "Set time estimate"
msgstr ""
@@ -20074,6 +20736,9 @@ msgstr ""
msgid "Set weight to %{weight}."
msgstr ""
+msgid "Set what should be replicated by choosing specific projects or groups by the secondary node."
+msgstr ""
+
msgid "SetPasswordToCloneLink|set a password"
msgstr ""
@@ -20200,6 +20865,9 @@ msgstr ""
msgid "Show complete raw log"
msgstr ""
+msgid "Show details"
+msgstr ""
+
msgid "Show file browser"
msgstr ""
@@ -20209,10 +20877,13 @@ msgstr ""
msgid "Show latest version"
msgstr ""
+msgid "Show list"
+msgstr ""
+
msgid "Show me everything"
msgstr ""
-msgid "Show me how"
+msgid "Show me how to add a pipeline"
msgstr ""
msgid "Show me more advanced stuff"
@@ -20261,9 +20932,6 @@ msgstr ""
msgid "Showing all issues"
msgstr ""
-msgid "Showing all labels"
-msgstr ""
-
msgid "Showing last %{size} of log -"
msgstr ""
@@ -20375,9 +21043,6 @@ msgstr ""
msgid "Skip outdated deployment jobs"
msgstr ""
-msgid "Skip this for now"
-msgstr ""
-
msgid "Skipped"
msgstr ""
@@ -20522,6 +21187,9 @@ msgstr ""
msgid "Something went wrong while adding your award. Please try again."
msgstr ""
+msgid "Something went wrong while applying the batch of suggestions. Please try again."
+msgstr ""
+
msgid "Something went wrong while applying the suggestion. Please try again."
msgstr ""
@@ -20624,10 +21292,13 @@ msgstr ""
msgid "Something went wrong while updating a requirement."
msgstr ""
+msgid "Something went wrong while updating assignees"
+msgstr ""
+
msgid "Something went wrong while updating your list settings"
msgstr ""
-msgid "Something went wrong with your automatic subscription renewal"
+msgid "Something went wrong with your automatic subscription renewal."
msgstr ""
msgid "Something went wrong, unable to add %{project} to dashboard"
@@ -20645,6 +21316,9 @@ msgstr ""
msgid "Something went wrong, unable to search projects"
msgstr ""
+msgid "Something went wrong."
+msgstr ""
+
msgid "Something went wrong. Please try again."
msgstr ""
@@ -20828,6 +21502,9 @@ msgstr ""
msgid "Source code"
msgstr ""
+msgid "Source code (%{fileExtension})"
+msgstr ""
+
msgid "Source is not available"
msgstr ""
@@ -20912,6 +21589,9 @@ msgstr ""
msgid "Stack trace"
msgstr ""
+msgid "Stacktrace snippet"
+msgstr ""
+
msgid "Stage"
msgstr ""
@@ -21017,6 +21697,9 @@ msgstr ""
msgid "Start thread & reopen %{noteable_name}"
msgstr ""
+msgid "Start using Directed Acyclic Graphs (DAG)"
+msgstr ""
+
msgid "Start your Free Gold Trial"
msgstr ""
@@ -21479,6 +22162,21 @@ msgstr ""
msgid "SuggestedColors|Very pale orange"
msgstr ""
+msgid "Suggestion is not applicable as the suggestion was not found."
+msgstr ""
+
+msgid "Suggestions are not applicable as one or more suggestions were not found."
+msgstr ""
+
+msgid "Suggestions are not applicable as their lines cannot overlap."
+msgstr ""
+
+msgid "Suggestions must all be on the same branch."
+msgstr ""
+
+msgid "Suggestions that change line count can't be added to batches, yet."
+msgstr ""
+
msgid "Suggestions:"
msgstr ""
@@ -21521,9 +22219,6 @@ msgstr ""
msgid "Synchronization disabled"
msgstr ""
-msgid "Synchronization of container repositories is disabled."
-msgstr ""
-
msgid "System"
msgstr ""
@@ -21704,6 +22399,34 @@ msgstr ""
msgid "Terms of Service and Privacy Policy"
msgstr ""
+msgid "Terraform|%{number} Terraform report failed to generate"
+msgid_plural "Terraform|%{number} Terraform reports failed to generate"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Terraform|%{number} Terraform report was generated in your pipelines"
+msgid_plural "Terraform|%{number} Terraform reports were generated in your pipelines"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Terraform|A Terraform report failed to generate."
+msgstr ""
+
+msgid "Terraform|A Terraform report was generated in your pipelines."
+msgstr ""
+
+msgid "Terraform|Generating the report caused an error."
+msgstr ""
+
+msgid "Terraform|Reported Resource Changes: %{addNum} to add, %{changeNum} to change, %{deleteNum} to delete"
+msgstr ""
+
+msgid "Terraform|The Terraform report %{name} failed to generate."
+msgstr ""
+
+msgid "Terraform|The Terraform report %{name} was generated in your pipelines."
+msgstr ""
+
msgid "Test"
msgstr ""
@@ -21730,7 +22453,7 @@ msgstr ""
msgid "TestHooks|Ensure the project has CI pipelines."
msgstr ""
-msgid "TestHooks|Ensure the project has at least one commit."
+msgid "TestHooks|Ensure the project has deployments."
msgstr ""
msgid "TestHooks|Ensure the project has issues."
@@ -21831,7 +22554,7 @@ msgstr ""
msgid "The Prometheus server responded with \"bad request\". Please check your queries are correct and are supported in your Prometheus version. %{documentationLink}"
msgstr ""
-msgid "The URL defined on the primary node that secondary nodes should use to contact it. Defaults to URL"
+msgid "The URL defined on the primary node that secondary nodes should use to contact it."
msgstr ""
msgid "The URL to use for connecting to Elasticsearch. Use a comma-separated list to support clustering (e.g., \"http://localhost:9200, http://localhost:9201\")."
@@ -21840,6 +22563,9 @@ msgstr ""
msgid "The X509 Certificate to use when mutual TLS is required to communicate with the external authorization service. If left blank, the server certificate is still validated when accessing over HTTPS."
msgstr ""
+msgid "The above settings apply to all projects with the selected compliance framework(s)."
+msgstr ""
+
msgid "The amount of seconds after which a request to get a secondary node status will time out."
msgstr ""
@@ -22139,9 +22865,6 @@ msgstr ""
msgid "The total stage shows the time it takes between creating an issue and deploying the code to production. The data will be automatically added once you have completed the full idea to production cycle."
msgstr ""
-msgid "The unique identifier for the Geo node. Must match %{geoNodeName} if it is set in gitlab.rb, otherwise it must match %{externalUrl} with a trailing slash"
-msgstr ""
-
msgid "The update action will time out after %{number_of_minutes} minutes. For big repositories, use a clone/push combination."
msgstr ""
@@ -22166,9 +22889,6 @@ msgstr ""
msgid "The user you are trying to deactivate has been active in the past %{minimum_inactive_days} days and cannot be deactivated"
msgstr ""
-msgid "The user-facing URL of the Geo node"
-msgstr ""
-
msgid "The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6."
msgstr ""
@@ -22259,6 +22979,9 @@ msgstr ""
msgid "There was a problem fetching project branches."
msgstr ""
+msgid "There was a problem fetching project tags."
+msgstr ""
+
msgid "There was a problem fetching project users."
msgstr ""
@@ -22310,6 +23033,9 @@ msgstr ""
msgid "There was an error fetching the %{replicableType}"
msgstr ""
+msgid "There was an error fetching the Geo Settings"
+msgstr ""
+
msgid "There was an error fetching the Node's Groups"
msgstr ""
@@ -22430,6 +23156,12 @@ msgstr ""
msgid "This %{issuable} is locked. Only <strong>project members</strong> can comment."
msgstr ""
+msgid "This %{noteableTypeText} is %{confidentialLinkStart}confidential%{linkEnd} and %{lockedLinkStart}locked%{linkEnd}."
+msgstr ""
+
+msgid "This %{noteableTypeText} is locked."
+msgstr ""
+
msgid "This %{viewer} could not be displayed because %{reason}. You can %{options} instead."
msgstr ""
@@ -22448,6 +23180,9 @@ msgstr ""
msgid "This action can lead to data loss. To prevent accidental actions we ask you to confirm your intention."
msgstr ""
+msgid "This also resolves all related threads"
+msgstr ""
+
msgid "This also resolves the discussion"
msgstr ""
@@ -22538,6 +23273,9 @@ msgstr ""
msgid "This epic does not exist or you don't have sufficient permission."
msgstr ""
+msgid "This feature is currently in beta. We invite you to %{linkStart}give feedback%{linkEnd}."
+msgstr ""
+
msgid "This feature requires local storage to be enabled"
msgstr ""
@@ -22571,7 +23309,7 @@ msgstr ""
msgid "This is a Work in Progress"
msgstr ""
-msgid "This is a confidential issue."
+msgid "This is a confidential %{noteableTypeText}."
msgstr ""
msgid "This is a delayed job to run in %{remainingTime}"
@@ -22598,9 +23336,6 @@ msgstr ""
msgid "This is your current session"
msgstr ""
-msgid "This issue is %{confidentialLinkStart}confidential%{linkEnd} and %{lockedLinkStart}locked%{linkEnd}."
-msgstr ""
-
msgid "This issue is confidential"
msgstr ""
@@ -22610,9 +23345,6 @@ msgstr ""
msgid "This issue is in a child epic of the filtered epic"
msgstr ""
-msgid "This issue is locked."
-msgstr ""
-
msgid "This job depends on other jobs with expired/erased artifacts: %{invalid_dependencies}"
msgstr ""
@@ -22712,6 +23444,9 @@ msgstr ""
msgid "This license has already expired."
msgstr ""
+msgid "This link points to external content"
+msgstr ""
+
msgid "This may expose confidential information as the selected fork is in another namespace that can have other members."
msgstr ""
@@ -22739,6 +23474,9 @@ msgstr ""
msgid "This page will be removed in a future release."
msgstr ""
+msgid "This pipeline does not use the %{codeStart}needs%{codeEnd} keyword and can't be represented as a directed acyclic graph."
+msgstr ""
+
msgid "This pipeline makes use of a predefined CI/CD configuration enabled by %{strongStart}Auto DevOps.%{strongEnd}"
msgstr ""
@@ -22967,6 +23705,9 @@ msgstr ""
msgid "Time from last commit to merge"
msgstr ""
+msgid "Time in seconds"
+msgstr ""
+
msgid "Time in seconds GitLab will wait for a response from the external service. When the service does not respond in time, access will be denied."
msgstr ""
@@ -23388,9 +24129,15 @@ msgstr ""
msgid "Total artifacts size: %{total_size}"
msgstr ""
+msgid "Total cores (CPUs)"
+msgstr ""
+
msgid "Total issues"
msgstr ""
+msgid "Total memory (GB)"
+msgstr ""
+
msgid "Total test time for all commits/merges"
msgstr ""
@@ -23592,6 +24339,9 @@ msgstr ""
msgid "U2F only works with HTTPS-enabled websites. Contact your administrator for more details."
msgstr ""
+msgid "UI Development Kit"
+msgstr ""
+
msgid "URL"
msgstr ""
@@ -23664,9 +24414,6 @@ msgstr ""
msgid "Unable to load the merge request widget. Try reloading the page."
msgstr ""
-msgid "Unable to process group import file"
-msgstr ""
-
msgid "Unable to resolve"
msgstr ""
@@ -23682,6 +24429,9 @@ msgstr ""
msgid "Unable to sign you in to the group with SAML due to \"%{reason}\""
msgstr ""
+msgid "Unable to suggest a path. Please refresh and try again."
+msgstr ""
+
msgid "Unable to update label prioritization at this time"
msgstr ""
@@ -23697,6 +24447,9 @@ msgstr ""
msgid "Unarchiving the project will restore people's ability to make changes to it. The repository can be committed to, and issues, comments, and other entities can be created. %{strong_start}Once active, this project shows up in the search and on the dashboard.%{strong_end}"
msgstr ""
+msgid "Unassign from commenting user"
+msgstr ""
+
msgid "Unblock"
msgstr ""
@@ -23772,6 +24525,9 @@ msgstr ""
msgid "Unreachable"
msgstr ""
+msgid "Unrecognized cluster type"
+msgstr ""
+
msgid "Unresolve"
msgstr ""
@@ -23832,6 +24588,9 @@ msgstr ""
msgid "Unsubscribes from this %{quick_action_target}."
msgstr ""
+msgid "Unsupported todo type passed. Supported todo types are: %{todo_types}"
+msgstr ""
+
msgid "Until"
msgstr ""
@@ -23859,6 +24618,9 @@ msgstr ""
msgid "Update approval rule"
msgstr ""
+msgid "Update approvers"
+msgstr ""
+
msgid "Update failed"
msgstr ""
@@ -23868,6 +24630,9 @@ msgstr ""
msgid "Update it"
msgstr ""
+msgid "Update iteration"
+msgstr ""
+
msgid "Update now"
msgstr ""
@@ -23913,7 +24678,7 @@ msgstr ""
msgid "Updated at"
msgstr ""
-msgid "Updated to"
+msgid "Updated to %{linkStart}chart v%{linkEnd}"
msgstr ""
msgid "Updating"
@@ -23943,6 +24708,9 @@ msgstr ""
msgid "Upload CSV file"
msgstr ""
+msgid "Upload License"
+msgstr ""
+
msgid "Upload New File"
msgstr ""
@@ -24024,16 +24792,19 @@ msgstr ""
msgid "UsageQuota|Pipelines"
msgstr ""
+msgid "UsageQuota|Purchase more storage"
+msgstr ""
+
msgid "UsageQuota|Repositories"
msgstr ""
msgid "UsageQuota|Repository"
msgstr ""
-msgid "UsageQuota|Storage"
+msgid "UsageQuota|Snippets"
msgstr ""
-msgid "UsageQuota|Storage usage:"
+msgid "UsageQuota|Storage"
msgstr ""
msgid "UsageQuota|This namespace has no projects which use shared runners"
@@ -24066,6 +24837,12 @@ msgstr ""
msgid "UsageQuota|Wikis"
msgstr ""
+msgid "UsageQuota|You used: %{usage} %{limit}"
+msgstr ""
+
+msgid "UsageQuota|out of %{formattedLimit} of your namespace storage"
+msgstr ""
+
msgid "Use %{code_start}::%{code_end} to create a %{link_start}scoped label set%{link_end} (eg. %{code_start}priority::1%{code_end})"
msgstr ""
@@ -24132,6 +24909,9 @@ msgstr ""
msgid "User IDs"
msgstr ""
+msgid "User Lists can only be created and modified with %{linkStart}the API%{linkEnd}"
+msgstr ""
+
msgid "User OAuth applications"
msgstr ""
@@ -24156,6 +24936,9 @@ msgstr ""
msgid "User key was successfully removed."
msgstr ""
+msgid "User list %{name} will be removed. Are you sure?"
+msgstr ""
+
msgid "User map"
msgstr ""
@@ -24177,148 +24960,10 @@ msgstr ""
msgid "User was successfully updated."
msgstr ""
-msgid "UserOnboardingTour|%{activeTour}/%{totalTours}"
-msgstr ""
-
-msgid "UserOnboardingTour|%{completed}/%{total} steps completed"
-msgstr ""
-
-msgid "UserOnboardingTour|%{emphasisStart}Well done!%{emphasisEnd}%{lineBreak}%{lineBreak}That's it for our guided tour, congratulations for making it all the way to the end!%{lineBreak}%{lineBreak}We hope this gave you a good overview of GitLab and how it can help you. We'll now show you how to create your own project and invite your colleagues."
-msgstr ""
-
-msgid "UserOnboardingTour|Adding other members to a project is done through Project Settings. Click on %{emphasisStart}Settings%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Alright, that's it for Commits. Let's take a look at the %{emphasisStart}Branches%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Awesome! Now click on %{emphasisStart}Members%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Click on one of the %{emphasisStart}Compare%{emphasisEnd} buttons to compare a branch to master."
-msgstr ""
-
-msgid "UserOnboardingTour|Click on one of the %{emphasisStart}pipeline IDs%{emphasisEnd} to see the details of a pipeline."
-msgstr ""
-
-msgid "UserOnboardingTour|Click to open the latest commit to see its details."
-msgstr ""
-
-msgid "UserOnboardingTour|Close 'Learn GitLab'"
-msgstr ""
-
-msgid "UserOnboardingTour|Commits are shown in chronological order and can be filtered by the commit message or by the branch."
+msgid "UserList|Delete %{name}?"
msgstr ""
-msgid "UserOnboardingTour|Create a project"
-msgstr ""
-
-msgid "UserOnboardingTour|Exit 'Learn GitLab'"
-msgstr ""
-
-msgid "UserOnboardingTour|Got it"
-msgstr ""
-
-msgid "UserOnboardingTour|Great job! %{clapHands} We hope the tour was helpful and that you learned how to use GitLab.%{lineBreak}%{lineBreak}We'd love to get your feedback on this tour.%{lineBreak}%{lineBreak}%{emphasisStart}How helpful would you say this guided tour was?%{emphasisEnd}%{lineBreak}%{lineBreak}"
-msgstr ""
-
-msgid "UserOnboardingTour|Guided GitLab Tour"
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can compare the changes of this branch to another one. Changes are divided by files so that it's easier to see what was changed where."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can create a project from scratch, start with a template or import a repository from other platforms. Whatever you choose, we'll guide you through the process.%{lineBreak}%{lineBreak}Fill in your new project information and click on %{emphasisStart}Create Project%{emphasisEnd} to progress to the next step."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can see the breakdown of the pipelines: its stages and jobs in each of the stages and their status.%{lineBreak}%{lineBreak}Our CI/CD pipelines are quite complex, most of our users have fewer and simpler pipelines."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can see the current members of the project (just you at the moment) and invite new members.%{lineBreak}%{lineBreak}You can invite multiple members at once (existing GitLab users or invite by email) and you can also set their roles and permissions.%{lineBreak}%{lineBreak}Add a few members and click on %{emphasisStart}Add to project%{emphasisEnd} to complete this step."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can see what changes were made with this commit, on what branch and if there's a related merge request. The status of the pipeline will also show up if CI/CD is set up.%{lineBreak}%{lineBreak}You can also comment on the lines of code that were changed and start a discussion with your colleagues!"
-msgstr ""
-
-msgid "UserOnboardingTour|Here's an overview of branches in the %{emphasisStart}%{projectName}%{emphasisEnd} project. They're split into Active and Stale.%{lineBreak}%{lineBreak}From here, you can create a new merge request from a branch, or compare the branch to any other branch in the project. By default, it will compare it to the master branch."
-msgstr ""
-
-msgid "UserOnboardingTour|Invite colleagues"
-msgstr ""
-
-msgid "UserOnboardingTour|Issues are great for communicating and keeping track of progress in GitLab. These are all issues that are open in the %{emphasisStart}%{projectName}%{emphasisEnd}.%{lineBreak}%{lineBreak}You can help us improve GitLab by contributing work to issues that are labeled <span class=\"badge color-label accept-mr-label\">Accepting merge requests</span>.%{lineBreak}%{lineBreak}This list can be filtered by labels, milestones, assignees, authors... We'll show you how it looks when the list is filtered by a label."
-msgstr ""
-
-msgid "UserOnboardingTour|Learn GitLab"
-msgstr ""
-
-msgid "UserOnboardingTour|Let's take a closer look at a merge request. Click on the title of one."
-msgstr ""
-
-msgid "UserOnboardingTour|Let's take a closer look at all the commits. Click on %{emphasisStart}Commits%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Let's take a closer look at the repository of this project. Click on %{emphasisStart}Repository%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|No thanks"
-msgstr ""
-
-msgid "UserOnboardingTour|Ok, let's go"
-msgstr ""
-
-msgid "UserOnboardingTour|Ok, show me"
-msgstr ""
-
-msgid "UserOnboardingTour|Open one of the issues by clicking on its title."
-msgstr ""
-
-msgid "UserOnboardingTour|Restart this step"
-msgstr ""
-
-msgid "UserOnboardingTour|Skip this step"
-msgstr ""
-
-msgid "UserOnboardingTour|Sweet! Your project was created and is ready to be used.%{lineBreak}%{lineBreak}You can start adding files to the repository or clone it. One last thing we want to show you is how to invite your colleagues to your new project."
-msgstr ""
-
-msgid "UserOnboardingTour|Take a look. Here's a nifty menu for quickly creating issues, merge requests, snippets, projects and groups. Click on it and select \"New project\" from the \"GitLab\" section to get started."
-msgstr ""
-
-msgid "UserOnboardingTour|Thanks for taking the guided tour. Remember, if you want to go through it again, you can start %{emphasisStart}Learn GitLab%{emphasisEnd} in the help menu on the top right."
-msgstr ""
-
-msgid "UserOnboardingTour|Thanks for the feedback! %{thumbsUp}"
-msgstr ""
-
-msgid "UserOnboardingTour|That's it for issues. Let'st take a look at %{emphasisStart}Merge Requests%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|That's it for merge requests. Now for the final part of this guided tour - the %{emphasisStart}CI/CD%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|That's it for the Repository. Let's take a look at the %{emphasisStart}Issues%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|The structure of this page is very similar to issues. Status, description, discussion and the sidebar are all here.%{lineBreak}%{lineBreak}But take a look below the description and you'll notice that there's more information about the merge request, the CI/CD pipeline and the options for approving it.%{lineBreak}%{lineBreak}Alongside the discussion you can also see more information about commits in this merge request, the status of pipelines and review all changes that were made."
-msgstr ""
-
-msgid "UserOnboardingTour|There's a lot of information here but don't worry, we'll go through it.%{lineBreak}%{lineBreak}On the top you can see the status of the issue and when it was opened and by whom. Directly below it is the issue description and below that are other %{emphasisStart}related issues%{emphasisEnd} and %{emphasisStart}merge requests%{emphasisEnd} (if any). Then below that is the %{emphasisStart}discussion%{emphasisEnd}, that's where most of the communication happens.%{lineBreak}%{lineBreak}On the right, there's a sidebar where you can view/change the %{emphasisStart}assignee, milestone, due date, labels, weight%{emphasisEnd}, etc."
-msgstr ""
-
-msgid "UserOnboardingTour|These are all the CI/CD pipelines we have for our %{emphasisStart}%{projectName}%{emphasisEnd} project.%{lineBreak}%{lineBreak}Here you can see the status of each pipeline, for what commit it's running for, its stages and the status for them."
-msgstr ""
-
-msgid "UserOnboardingTour|These are all the issues that are available for community contributions. Let's take a closer look at one of them."
-msgstr ""
-
-msgid "UserOnboardingTour|This is an overview of all merge requests in this project. Similarly to the issues overview it can be filtered down by things like labels, milestones, authors, assignees, etc."
-msgstr ""
-
-msgid "UserOnboardingTour|This is the repository for the %{emphasisStart}%{projectName}%{emphasisEnd} project. All our code is stored here. Feel free to explore and take a closer look at folders and files.%{lineBreak}%{lineBreak}Above the file structure you can see the latest commit, who the author is and the status of the CI/CD pipeline.%{lineBreak}%{lineBreak}If you scroll down below the file structure, you'll find the Readme of this project. This is defined in the README.md file at the root of the repository."
-msgstr ""
-
-msgid "UserOnboardingTour|Welcome to the project overview of the %{emphasisStart}%{projectName}%{emphasisEnd} project. This is the project that we use to work on GitLab. At first, a project seems like a simple repository, but at GitLab, a project is so much more.%{lineBreak}%{lineBreak}You can create projects for hosting your codebase, use it as an issue tracker, collaborate on code, and continuously build, test, and deploy your app with built-in GitLab CI/CD."
+msgid "UserList|created %{timeago}"
msgstr ""
msgid "UserProfile|Activity"
@@ -24471,6 +25116,9 @@ msgstr ""
msgid "UsersSelect|Unassigned"
msgstr ""
+msgid "Using %{codeStart}needs%{codeEnd} allows jobs to run before their stage is reached, as soon as their individual dependencies are met, which speeds up your pipelines."
+msgstr ""
+
msgid "Using %{code_start}::%{code_end} denotes a %{link_start}scoped label set%{link_end}"
msgstr ""
@@ -24555,9 +25203,6 @@ msgstr ""
msgid "Versions"
msgstr ""
-msgid "Very helpful"
-msgstr ""
-
msgid "View Documentation"
msgstr ""
@@ -24706,6 +25351,9 @@ msgstr ""
msgid "VisibilityLevel|Unknown"
msgstr ""
+msgid "Visit settings page"
+msgstr ""
+
msgid "VisualReviewApp|%{stepStart}Step 1%{stepEnd}. Copy the following script:"
msgstr ""
@@ -24799,6 +25447,9 @@ msgstr ""
msgid "VulnerabilityManagement|Something went wrong while trying to delete the comment. Please try again later."
msgstr ""
+msgid "VulnerabilityManagement|Something went wrong while trying to refresh the vulnerability. Please try again later."
+msgstr ""
+
msgid "VulnerabilityManagement|Something went wrong while trying to retrieve the vulnerability history. Please try again later."
msgstr ""
@@ -24841,6 +25492,9 @@ msgstr ""
msgid "Vulnerability|Class"
msgstr ""
+msgid "Vulnerability|Crash Address"
+msgstr ""
+
msgid "Vulnerability|Description"
msgstr ""
@@ -24916,9 +25570,6 @@ msgstr ""
msgid "We couldn't reach the Prometheus server. Either the server no longer exists or the configuration details need updating."
msgstr ""
-msgid "We created a short guided tour that will help you learn the basics of GitLab and how it will help you be better at your job. It should only take a couple of minutes. You will be guided by two types of helpers, best recognized by their color."
-msgstr ""
-
msgid "We detected potential spam in the %{humanized_resource_name}. Please solve the reCAPTCHA to proceed."
msgstr ""
@@ -24952,6 +25603,9 @@ msgstr ""
msgid "We've found no vulnerabilities"
msgstr ""
+msgid "Web Application Firewall"
+msgstr ""
+
msgid "Web IDE"
msgstr ""
@@ -25012,9 +25666,6 @@ msgstr ""
msgid "Welcome to GitLab.com<br>@%{name}!"
msgstr ""
-msgid "Welcome to the Guided GitLab Tour"
-msgstr ""
-
msgid "Welcome to the guided GitLab tour"
msgstr ""
@@ -25062,9 +25713,6 @@ msgstr ""
msgid "While it's rare to have no vulnerabilities, it can happen. In any event, we ask that you please double check your settings to make sure you've set up your dashboard correctly."
msgstr ""
-msgid "White helpers give contextual information."
-msgstr ""
-
msgid "Who can be an approver?"
msgstr ""
@@ -25110,12 +25758,18 @@ msgstr ""
msgid "WikiEmptyIssueMessage|Suggest wiki improvement"
msgstr ""
+msgid "WikiEmptyIssueMessage|You must be a group member in order to add wiki pages. If you have suggestions for how to improve the wiki for this group, consider opening an issue in the %{issues_link}."
+msgstr ""
+
msgid "WikiEmptyIssueMessage|You must be a project member in order to add wiki pages. If you have suggestions for how to improve the wiki for this project, consider opening an issue in the %{issues_link}."
msgstr ""
msgid "WikiEmptyIssueMessage|issue tracker"
msgstr ""
+msgid "WikiEmpty|A wiki is where you can store all the details about your group. This can include why you've created it, its principles, how to use it, and so on."
+msgstr ""
+
msgid "WikiEmpty|A wiki is where you can store all the details about your project. This can include why you've created it, its principles, how to use it, and so on."
msgstr ""
@@ -25125,12 +25779,21 @@ msgstr ""
msgid "WikiEmpty|Suggest wiki improvement"
msgstr ""
+msgid "WikiEmpty|The wiki lets you write documentation for your group"
+msgstr ""
+
msgid "WikiEmpty|The wiki lets you write documentation for your project"
msgstr ""
+msgid "WikiEmpty|This group has no wiki pages"
+msgstr ""
+
msgid "WikiEmpty|This project has no wiki pages"
msgstr ""
+msgid "WikiEmpty|You must be a group member in order to add wiki pages."
+msgstr ""
+
msgid "WikiEmpty|You must be a project member in order to add wiki pages."
msgstr ""
@@ -25407,13 +26070,13 @@ msgstr ""
msgid "You can now export your security dashboard to a CSV report."
msgstr ""
-msgid "You can now submit a merge request to get this change into the original branch."
+msgid "You can now manage alert endpoint configuration in the Alerts section on the Operations settings page. Fields on this page have been deprecated."
msgstr ""
-msgid "You can now submit a merge request to get this change into the original project."
+msgid "You can now submit a merge request to get this change into the original branch."
msgstr ""
-msgid "You can only add files when you are on a branch"
+msgid "You can now submit a merge request to get this change into the original project."
msgstr ""
msgid "You can only edit files when you are on a branch"
@@ -25635,7 +26298,7 @@ msgstr ""
msgid "You need to upload a Google Takeout archive."
msgstr ""
-msgid "You reached %{usage_in_percent} of %{namespace_name}'s capacity (%{used_storage} of %{storage_limit})"
+msgid "You reached %{usage_in_percent} of %{namespace_name}'s storage capacity (%{used_storage} of %{storage_limit})"
msgstr ""
msgid "You tried to fork %{link_to_the_project} but it failed for the following reason:"
@@ -25938,13 +26601,13 @@ msgstr ""
msgid "Your subscription expired!"
msgstr ""
-msgid "Your subscription has been downgraded"
+msgid "Your subscription has been downgraded."
msgstr ""
-msgid "Your subscription will automatically renew in %{remaining_days}"
+msgid "Your subscription will automatically renew in %{remaining_days}."
msgstr ""
-msgid "Your subscription will expire in %{remaining_days}"
+msgid "Your subscription will expire in %{remaining_days}."
msgstr ""
msgid "Zoom meeting added"
@@ -26024,9 +26687,6 @@ msgstr ""
msgid "branch name"
msgstr ""
-msgid "build pipeline reference mismatch"
-msgstr ""
-
msgid "by"
msgstr ""
@@ -26045,6 +26705,9 @@ msgstr ""
msgid "cannot block others"
msgstr ""
+msgid "cannot contain HTML/XML tags, including any word between angle brackets (<,>)."
+msgstr ""
+
msgid "cannot include leading slash or directory traversal."
msgstr ""
@@ -26075,55 +26738,6 @@ msgstr ""
msgid "ciReport|%{remainingPackagesCount} more"
msgstr ""
-msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability"
-msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability for the source branch only"
-msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities for the source branch only"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerability"
-msgid_plural "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerabilities"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new vulnerability"
-msgid_plural "ciReport|%{reportType} %{status} detected %{newCount} new vulnerabilities"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities for the source branch only"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{fixedCount} fixed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} vulnerability for the source branch only"
-msgid_plural "ciReport|%{reportType} %{status} detected %{newCount} vulnerabilities for the source branch only"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "ciReport|%{reportType} %{status} detected no new vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected no vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected no vulnerabilities for the source branch only"
-msgstr ""
-
msgid "ciReport|%{reportType} is loading"
msgstr ""
@@ -26142,7 +26756,7 @@ msgstr ""
msgid "ciReport|All projects"
msgstr ""
-msgid "ciReport|All report types"
+msgid "ciReport|All scanner types"
msgstr ""
msgid "ciReport|All severities"
@@ -26166,6 +26780,9 @@ msgstr ""
msgid "ciReport|Container scanning detects known vulnerabilities in your docker images."
msgstr ""
+msgid "ciReport|Coverage Fuzzing"
+msgstr ""
+
msgid "ciReport|Create a merge request to implement this solution, or download and apply the patch manually."
msgstr ""
@@ -26324,6 +26941,9 @@ msgstr ""
msgid "customize"
msgstr ""
+msgid "data"
+msgstr ""
+
msgid "date must not be after 9999-12-31"
msgstr ""
@@ -26394,6 +27014,9 @@ msgstr ""
msgid "entries cannot contain HTML tags"
msgstr ""
+msgid "epic"
+msgstr ""
+
msgid "error"
msgstr ""
@@ -26415,9 +27038,6 @@ msgstr ""
msgid "expires on %{timebox_due_date}"
msgstr ""
-msgid "external_url"
-msgstr ""
-
msgid "failed"
msgstr ""
@@ -26453,9 +27073,6 @@ msgstr ""
msgid "from"
msgstr ""
-msgid "geo_node_name"
-msgstr ""
-
msgid "group"
msgstr ""
@@ -26477,9 +27094,6 @@ msgstr ""
msgid "https://your-bitbucket-server"
msgstr ""
-msgid "image"
-msgstr ""
-
msgid "image diff"
msgstr ""
@@ -26623,6 +27237,9 @@ msgstr ""
msgid "load it anyway"
msgstr ""
+msgid "loading"
+msgstr ""
+
msgid "locked by %{path_lock_user_name} %{created_at}"
msgstr ""
@@ -26751,9 +27368,6 @@ msgstr ""
msgid "mrWidget|Deployment statistics are not available currently"
msgstr ""
-msgid "mrWidget|Detect issues before deployment with a CI pipeline that continuously tests your code. We created a quick guide that will show you how to create one. Make your code more secure and more robust in just a minute."
-msgstr ""
-
msgid "mrWidget|Did not close"
msgstr ""
@@ -26916,6 +27530,9 @@ msgstr ""
msgid "mrWidget|To approve this merge request, please enter your password. This project requires all approvals to be authenticated."
msgstr ""
+msgid "mrWidget|Use %{linkStart}CI pipelines to test your code%{linkEnd}, simply add a GitLab CI configuration file to your project. It only takes a minute to make your code more secure and robust."
+msgstr ""
+
msgid "mrWidget|When this merge request is ready, remove the WIP: prefix from the title to allow it to be merged"
msgstr ""
@@ -26952,6 +27569,9 @@ msgstr ""
msgid "must be greater than start date"
msgstr ""
+msgid "must contain only valid frameworks"
+msgstr ""
+
msgid "my-awesome-group"
msgstr ""
@@ -26964,6 +27584,9 @@ msgstr ""
msgid "needs to be between 10 minutes and 1 month"
msgstr ""
+msgid "never"
+msgstr ""
+
msgid "never expires"
msgstr ""
@@ -26991,6 +27614,9 @@ msgstr ""
msgid "nounSeries|%{firstItem} and %{lastItem}"
msgstr ""
+msgid "nounSeries|%{item}"
+msgstr ""
+
msgid "nounSeries|%{item}, %{nextItem}"
msgstr ""
@@ -27127,6 +27753,9 @@ msgstr ""
msgid "revised"
msgstr ""
+msgid "satisfied"
+msgstr ""
+
msgid "score"
msgstr ""
@@ -27157,6 +27786,9 @@ msgstr ""
msgid "severity|Unknown"
msgstr ""
+msgid "should be an array of %{object_name} objects"
+msgstr ""
+
msgid "should be greater than or equal to %{access} inherited membership from group %{group_name}"
msgstr ""
@@ -27211,10 +27843,13 @@ msgstr ""
msgid "suggestPipeline|2/2: Commit your changes"
msgstr ""
-msgid "suggestPipeline|Commit the changes and your pipeline will automatically run for the first time."
+msgid "suggestPipeline|Choose %{boldStart}Code Quality%{boldEnd} to add a pipeline that tests the quality of your code."
msgstr ""
-msgid "suggestPipeline|We recommend the %{boldStart}Code Quality%{boldEnd} template, which will add a report widget to your Merge Requests. This way you’ll learn about code quality degradations much sooner. %{footerStart} Goodbye technical debt! %{footerEnd}"
+msgid "suggestPipeline|The template is ready! You can now commit it to create your first pipeline."
+msgstr ""
+
+msgid "suggestPipeline|We’re adding a GitLab CI configuration file to add a pipeline to the project. You could create it manually, but we recommend that you start with a GitLab template that works out of the box."
msgstr ""
msgid "syntax is correct"
@@ -27274,6 +27909,9 @@ msgstr ""
msgid "uses Kubernetes clusters to deploy your code!"
msgstr ""
+msgid "v%{version} published %{timeAgo}"
+msgstr ""
+
msgid "verify ownership"
msgstr ""
diff --git a/locale/fil_PH/gitlab.po b/locale/fil_PH/gitlab.po
index 75ef8bbce44..03748a32f2f 100644
--- a/locale/fil_PH/gitlab.po
+++ b/locale/fil_PH/gitlab.po
@@ -2,7 +2,7 @@ msgid ""
msgstr ""
"Project-Id-Version: gitlab-ee\n"
"Report-Msgid-Bugs-To: \n"
-"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Last-Translator: \n"
"Language-Team: Filipino\n"
"Language: fil_PH\n"
"MIME-Version: 1.0\n"
@@ -10,9 +10,11 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
"X-Crowdin-Project: gitlab-ee\n"
+"X-Crowdin-Project-ID: 288872\n"
"X-Crowdin-Language: fil\n"
"X-Crowdin-File: /master/locale/gitlab.pot\n"
-"PO-Revision-Date: 2020-06-08 15:12\n"
+"X-Crowdin-File-ID: 6\n"
+"PO-Revision-Date: 2020-07-02 01:21\n"
msgid " %{start} to %{end}"
msgstr ""
@@ -197,6 +199,11 @@ msgid_plural "%d metrics"
msgstr[0] ""
msgstr[1] ""
+msgid "%d milestone"
+msgid_plural "%d milestones"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "%d minute"
msgid_plural "%d minutes"
msgstr[0] ""
@@ -242,11 +249,6 @@ msgid_plural "%d unresolved threads"
msgstr[0] ""
msgstr[1] ""
-msgid "%d url scanned"
-msgid_plural "%d urls scanned"
-msgstr[0] ""
-msgstr[1] ""
-
msgid "%d vulnerability"
msgid_plural "%d vulnerabilities"
msgstr[0] ""
@@ -324,9 +326,15 @@ msgstr[1] ""
msgid "%{count} related %{pluralized_subject}: %{links}"
msgstr ""
+msgid "%{dashboard_path} could not be found."
+msgstr ""
+
msgid "%{days} days until tags are automatically removed"
msgstr ""
+msgid "%{deployLinkStart}Use a template to deploy to ECS%{deployLinkEnd}, or use a docker image to %{commandsLinkStart}run AWS commands in GitLab CI/CD%{commandsLinkEnd}."
+msgstr ""
+
msgid "%{description}- Sentry event: %{errorUrl}- First seen: %{firstSeen}- Last seen: %{lastSeen} %{countLabel}: %{count}%{userCountLabel}: %{userCount}"
msgstr ""
@@ -369,12 +377,18 @@ msgstr ""
msgid "%{host} sign-in from new location"
msgstr ""
-msgid "%{icon}You are about to add %{usersTag} people to the discussion. Proceed with caution."
+msgid "%{icon}You are about to add %{usersTag} people to the discussion. They will all receive a notification."
+msgstr ""
+
+msgid "%{integrations_link_start}Integrations%{link_end} enable you to make third-party applications part of your GitLab workflow. If the available integrations don't meet your needs, consider using a %{webhooks_link_start}webhook%{link_end}."
msgstr ""
msgid "%{issuableType} will be removed! Are you sure?"
msgstr ""
+msgid "%{issuesCount} issues in this group"
+msgstr ""
+
msgid "%{issuesSize} issues"
msgstr ""
@@ -384,7 +398,7 @@ msgstr ""
msgid "%{labelStart}Class:%{labelEnd} %{class}"
msgstr ""
-msgid "%{labelStart}Confidence:%{labelEnd} %{confidence}"
+msgid "%{labelStart}Evidence:%{labelEnd} %{evidence}"
msgstr ""
msgid "%{labelStart}File:%{labelEnd} %{file}"
@@ -402,6 +416,9 @@ msgstr ""
msgid "%{labelStart}Report Type:%{labelEnd} %{reportType}"
msgstr ""
+msgid "%{labelStart}Scanner:%{labelEnd} %{scanner}"
+msgstr ""
+
msgid "%{labelStart}Severity:%{labelEnd} %{severity}"
msgstr ""
@@ -426,9 +443,6 @@ msgstr ""
msgid "%{link_start}Read more%{link_end} about role permissions"
msgstr ""
-msgid "%{link} can be used for binding events when something is happening within the project."
-msgstr ""
-
msgid "%{listToShow}, and %{awardsListLength} more."
msgstr ""
@@ -462,6 +476,9 @@ msgstr ""
msgid "%{name} found %{resultsString}"
msgstr ""
+msgid "%{name} is already being used for another emoji"
+msgstr ""
+
msgid "%{name} is scheduled for %{action}"
msgstr ""
@@ -509,6 +526,9 @@ msgstr ""
msgid "%{primary} (%{secondary})"
msgstr ""
+msgid "%{ref} cannot be added: %{error}"
+msgstr ""
+
msgid "%{releases} release"
msgid_plural "%{releases} releases"
msgstr[0] ""
@@ -517,13 +537,37 @@ msgstr[1] ""
msgid "%{remaining_approvals} left"
msgstr ""
-msgid "%{retryButtonStart}Try again%{retryButtonEnd} or %{newFileButtonStart}attach a new file%{newFileButtonEnd}"
+msgid "%{reportType} %{status} detected %{critical} critical and %{high} high severity vulnerabilities out of %{total}."
+msgstr ""
+
+msgid "%{reportType} %{status} detected %{critical} critical and %{high} high severity vulnerabilities."
+msgstr ""
+
+msgid "%{reportType} %{status} detected %{critical} critical severity vulnerabilities out of %{total}."
+msgstr ""
+
+msgid "%{reportType} %{status} detected %{critical} critical severity vulnerability."
+msgid_plural "%{reportType} %{status} detected %{critical} critical severity vulnerabilities."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{reportType} %{status} detected %{high} high severity vulnerabilities out of %{total}."
msgstr ""
-msgid "%{screenreaderOnlyStart}Keyboard shorcuts%{screenreaderOnlyEnd} Disabled"
+msgid "%{reportType} %{status} detected %{high} high severity vulnerability."
+msgid_plural "%{reportType} %{status} detected %{high} high severity vulnerabilities."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{reportType} %{status} detected %{other} vulnerability."
+msgid_plural "%{reportType} %{status} detected %{other} vulnerabilities."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{reportType} %{status} detected no new vulnerabilities."
msgstr ""
-msgid "%{screenreaderOnlyStart}Keyboard shorcuts%{screenreaderOnlyEnd} Enabled"
+msgid "%{retryButtonStart}Try again%{retryButtonEnd} or %{newFileButtonStart}attach a new file%{newFileButtonEnd}"
msgstr ""
msgid "%{service_title} %{message}."
@@ -620,6 +664,12 @@ msgstr ""
msgid "%{token}..."
msgstr ""
+msgid "%{totalCpu} (%{freeSpacePercentage}%{percentSymbol} free)"
+msgstr ""
+
+msgid "%{totalMemory} (%{freeSpacePercentage}%{percentSymbol} free)"
+msgstr ""
+
msgid "%{totalWeight} total weight"
msgstr ""
@@ -650,6 +700,12 @@ msgstr ""
msgid "%{verb} %{time_spent_value} spent time."
msgstr ""
+msgid "%{webhooks_link_start}%{webhook_type}%{link_end} enable you to send notifications to web applications in response to events in a group or project."
+msgstr ""
+
+msgid "%{webhooks_link_start}%{webhook_type}%{link_end} enable you to send notifications to web applications in response to events in a group or project. We recommend using an %{integrations_link_start}integration%{link_end} in preference to a webhook."
+msgstr ""
+
msgid "'%{level}' is not a valid visibility level"
msgstr ""
@@ -676,9 +732,6 @@ msgstr ""
msgid "(No changes)"
msgstr ""
-msgid "(Show all)"
-msgstr ""
-
msgid "(check progress)"
msgstr ""
@@ -745,6 +798,9 @@ msgstr ""
msgid "0 for unlimited"
msgstr ""
+msgid "0 for unlimited, only effective with remote storage enabled."
+msgstr ""
+
msgid "1 %{type} addition"
msgid_plural "%{count} %{type} additions"
msgstr[0] ""
@@ -892,6 +948,12 @@ msgstr ""
msgid "<code>\"johnsmith@example.com\": \"johnsmith@example.com\"</code> will add \"By <a href=\"#\">johnsmith@example.com</a>\" to all issues and comments originally created by johnsmith@example.com. By default, the email address or username is masked to ensure the user's privacy. Use this option if you want to show the full email address."
msgstr ""
+msgid "<code>Masked</code> to prevent the values from being displayed in job logs (must match certain regexp requirements)."
+msgstr ""
+
+msgid "<code>Protected</code> to expose them to protected branches or tags only."
+msgstr ""
+
msgid "<namespace / project>"
msgstr ""
@@ -919,9 +981,6 @@ msgstr ""
msgid "A .NET Core console application template, customizable for any .NET Core project"
msgstr ""
-msgid "A DAG must have two dependent jobs to be visualized on this tab."
-msgstr ""
-
msgid "A GitBook site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
msgstr ""
@@ -952,6 +1011,12 @@ msgstr ""
msgid "A deleted user"
msgstr ""
+msgid "A file has been changed."
+msgstr ""
+
+msgid "A file was not found."
+msgstr ""
+
msgid "A file with '%{file_name}' already exists in %{branch} branch"
msgstr ""
@@ -1018,7 +1083,7 @@ msgstr ""
msgid "A subscription will trigger a new pipeline on the default branch of this project when a pipeline successfully completes for a new tag on the %{default_branch_docs} of the subscribed project."
msgstr ""
-msgid "A terraform report was generated in your pipelines."
+msgid "A suggestion is not applicable."
msgstr ""
msgid "A user with write access to the source branch selected this option"
@@ -1189,6 +1254,9 @@ msgstr ""
msgid "Action to take when receiving an alert."
msgstr ""
+msgid "Actions"
+msgstr ""
+
msgid "Activate"
msgstr ""
@@ -1245,6 +1313,9 @@ msgstr ""
msgid "Add LICENSE"
msgstr ""
+msgid "Add New Node"
+msgstr ""
+
msgid "Add README"
msgstr ""
@@ -1320,6 +1391,9 @@ msgstr ""
msgid "Add approval rule"
msgstr ""
+msgid "Add approvers"
+msgstr ""
+
msgid "Add bold text"
msgstr ""
@@ -1377,6 +1451,9 @@ msgstr ""
msgid "Add strikethrough text"
msgstr ""
+msgid "Add suggestion to batch"
+msgstr ""
+
msgid "Add system hook"
msgstr ""
@@ -1476,6 +1553,9 @@ msgstr ""
msgid "Admin Note"
msgstr ""
+msgid "Admin Notifications"
+msgstr ""
+
msgid "Admin Overview"
msgstr ""
@@ -1587,6 +1667,9 @@ msgstr ""
msgid "AdminSettings|Integrations configured here will automatically apply to all projects on this instance."
msgstr ""
+msgid "AdminSettings|Moved to integrations"
+msgstr ""
+
msgid "AdminSettings|No required pipeline"
msgstr ""
@@ -1817,12 +1900,18 @@ msgid_plural "Alerts"
msgstr[0] ""
msgstr[1] ""
+msgid "AlertManagement| assign yourself"
+msgstr ""
+
msgid "AlertManagement|Acknowledged"
msgstr ""
msgid "AlertManagement|Alert"
msgstr ""
+msgid "AlertManagement|Alert assignee(s): %{assignees}"
+msgstr ""
+
msgid "AlertManagement|Alert detail"
msgstr ""
@@ -1838,9 +1927,15 @@ msgstr ""
msgid "AlertManagement|All alerts"
msgstr ""
+msgid "AlertManagement|Assign To"
+msgstr ""
+
msgid "AlertManagement|Assign status"
msgstr ""
+msgid "AlertManagement|Assignee"
+msgstr ""
+
msgid "AlertManagement|Assignees"
msgstr ""
@@ -1859,9 +1954,6 @@ msgstr ""
msgid "AlertManagement|Edit"
msgstr ""
-msgid "AlertManagement|End time"
-msgstr ""
-
msgid "AlertManagement|Events"
msgstr ""
@@ -1880,10 +1972,13 @@ msgstr ""
msgid "AlertManagement|More information"
msgstr ""
+msgid "AlertManagement|No Matching Results"
+msgstr ""
+
msgid "AlertManagement|No alert data to display."
msgstr ""
-msgid "AlertManagement|No alerts available to display. If you think you're seeing this message in error, refresh the page."
+msgid "AlertManagement|No alerts available to display. See %{linkStart}enabling alert management%{linkEnd} for more information on adding alerts to the list."
msgstr ""
msgid "AlertManagement|No alerts to display."
@@ -1892,6 +1987,9 @@ msgstr ""
msgid "AlertManagement|None"
msgstr ""
+msgid "AlertManagement|None -"
+msgstr ""
+
msgid "AlertManagement|Open"
msgstr ""
@@ -1928,9 +2026,18 @@ msgstr ""
msgid "AlertManagement|There was an error displaying the alerts. Confirm your endpoint's configuration details to ensure alerts appear."
msgstr ""
+msgid "AlertManagement|There was an error while updating the assignee(s) list. Please try again."
+msgstr ""
+
+msgid "AlertManagement|There was an error while updating the assignee(s) of the alert. Please try again."
+msgstr ""
+
msgid "AlertManagement|There was an error while updating the status of the alert. Please try again."
msgstr ""
+msgid "AlertManagement|This assignee cannot be assigned to this alert."
+msgstr ""
+
msgid "AlertManagement|Tool"
msgstr ""
@@ -2078,6 +2185,9 @@ msgstr ""
msgid "Allow users to request access (if visibility is public or internal)"
msgstr ""
+msgid "Allowed Geo IP"
+msgstr ""
+
msgid "Allowed email domain restriction only permitted for top-level groups"
msgstr ""
@@ -2111,6 +2221,9 @@ msgstr ""
msgid "Amazon Web Services"
msgstr ""
+msgid "Amazon Web Services Logo"
+msgstr ""
+
msgid "Amazon authentication is not %{link_start}correctly configured%{link_end}. Ask your GitLab administrator if you want to use this service."
msgstr ""
@@ -2168,10 +2281,16 @@ msgstr ""
msgid "An error occurred when updating the issue weight"
msgstr ""
+msgid "An error occurred while acknowledging the notification. Refresh the page and try again."
+msgstr ""
+
+msgid "An error occurred while adding approvers"
+msgstr ""
+
msgid "An error occurred while adding formatted title for epic"
msgstr ""
-msgid "An error occurred while checking group path"
+msgid "An error occurred while checking group path. Please refresh and try again."
msgstr ""
msgid "An error occurred while committing your changes."
@@ -2315,7 +2434,7 @@ msgstr ""
msgid "An error occurred while loading milestones"
msgstr ""
-msgid "An error occurred while loading terraform report"
+msgid "An error occurred while loading project creation UI"
msgstr ""
msgid "An error occurred while loading the data. Please try again."
@@ -2369,6 +2488,9 @@ msgstr ""
msgid "An error occurred while rendering preview broadcast message"
msgstr ""
+msgid "An error occurred while rendering the editor"
+msgstr ""
+
msgid "An error occurred while reordering issues."
msgstr ""
@@ -2465,7 +2587,7 @@ msgstr ""
msgid "Analyze your dependencies for known vulnerabilities."
msgstr ""
-msgid "Analyze your source code and git history for secrets"
+msgid "Analyze your source code and git history for secrets."
msgstr ""
msgid "Analyze your source code for known vulnerabilities."
@@ -2579,12 +2701,18 @@ msgstr ""
msgid "Apply suggestion"
msgstr ""
+msgid "Apply suggestions"
+msgstr ""
+
msgid "Apply template"
msgstr ""
msgid "Apply this approval rule to any branch or a specific protected branch."
msgstr ""
+msgid "Applying"
+msgstr ""
+
msgid "Applying a template will replace the existing issue description. Any changes you have made will be lost."
msgstr ""
@@ -2597,12 +2725,18 @@ msgstr ""
msgid "Applying multiple commands"
msgstr ""
-msgid "Applying suggestion"
+msgid "Applying suggestion..."
+msgstr ""
+
+msgid "Applying suggestions..."
msgstr ""
msgid "Approval rules"
msgstr ""
+msgid "Approval rules reset to project defaults"
+msgstr ""
+
msgid "ApprovalRuleRemove|%d member"
msgid_plural "ApprovalRuleRemove|%d members"
msgstr[0] ""
@@ -2656,6 +2790,9 @@ msgstr ""
msgid "Approved"
msgstr ""
+msgid "Approved MRs"
+msgstr ""
+
msgid "Approved by: "
msgstr ""
@@ -2869,6 +3006,9 @@ msgstr ""
msgid "Assign"
msgstr ""
+msgid "Assign Iteration"
+msgstr ""
+
msgid "Assign custom color like #FF0000"
msgstr ""
@@ -2887,6 +3027,9 @@ msgstr ""
msgid "Assign to"
msgstr ""
+msgid "Assign to commenting user"
+msgstr ""
+
msgid "Assign yourself to these issues"
msgstr ""
@@ -2913,6 +3056,9 @@ msgid_plural "%d Assignees"
msgstr[0] ""
msgstr[1] ""
+msgid "Assignee has no permissions"
+msgstr ""
+
msgid "Assignee lists not available with your current license"
msgstr ""
@@ -2922,6 +3068,9 @@ msgstr ""
msgid "Assignee(s)"
msgstr ""
+msgid "Assignees"
+msgstr ""
+
msgid "Assigns %{assignee_users_sentence}."
msgstr ""
@@ -2957,6 +3106,9 @@ msgstr ""
msgid "Audit Events is a way to keep track of important events that happened in GitLab."
msgstr ""
+msgid "Audit Log"
+msgstr ""
+
msgid "AuditEvents|(removed)"
msgstr ""
@@ -2993,6 +3145,9 @@ msgstr ""
msgid "AuditLogs|IP Address"
msgstr ""
+msgid "AuditLogs|Member Events"
+msgstr ""
+
msgid "AuditLogs|No matching %{type} found."
msgstr ""
@@ -3275,9 +3430,6 @@ msgstr ""
msgid "Badges|e.g. %{exampleUrl}"
msgstr ""
-msgid "Badge|New"
-msgstr ""
-
msgid "Balsamiq file could not be loaded."
msgstr ""
@@ -3326,6 +3478,12 @@ msgstr ""
msgid "Below you will find all the groups that are public."
msgstr ""
+msgid "Beta"
+msgstr ""
+
+msgid "Bi-weekly code coverage"
+msgstr ""
+
msgid "Billing"
msgstr ""
@@ -3386,9 +3544,15 @@ msgstr ""
msgid "Bitbucket Server Import"
msgstr ""
+msgid "Bitbucket Server import"
+msgstr ""
+
msgid "Bitbucket import"
msgstr ""
+msgid "Blame"
+msgstr ""
+
msgid "Blocked"
msgstr ""
@@ -3401,9 +3565,6 @@ msgstr ""
msgid "Blog"
msgstr ""
-msgid "Blue helpers indicate an action to be taken."
-msgstr ""
-
msgid "Board name"
msgstr ""
@@ -3602,6 +3763,9 @@ msgstr ""
msgid "Broadcast Message was successfully updated."
msgstr ""
+msgid "Broadcast Messages"
+msgstr ""
+
msgid "Browse Directory"
msgstr ""
@@ -3656,6 +3820,9 @@ msgstr ""
msgid "By %{user_name}"
msgstr ""
+msgid "By URL"
+msgstr ""
+
msgid "By default GitLab sends emails in HTML and plain text formats so mail clients can choose what format to use. Disable this option if you only want to send emails in plain text format."
msgstr ""
@@ -3761,6 +3928,9 @@ msgstr ""
msgid "Can override approvers and approvals required per merge request"
msgstr ""
+msgid "Can't apply as this line has changed or the suggestion already matches its content."
+msgstr ""
+
msgid "Can't create snippet: %{err}"
msgstr ""
@@ -3947,9 +4117,6 @@ msgstr ""
msgid "Changes are still tracked. Useful for cluster/index migrations."
msgstr ""
-msgid "Changes are unknown"
-msgstr ""
-
msgid "Changes suppressed. Click to show."
msgstr ""
@@ -4256,15 +4423,9 @@ msgstr ""
msgid "Choose what content you want to see on a group’s overview page"
msgstr ""
-msgid "Choose which groups you wish to synchronize to this secondary node"
-msgstr ""
-
msgid "Choose which repositories you want to connect and run CI/CD pipelines."
msgstr ""
-msgid "Choose which shards you wish to synchronize to this secondary node"
-msgstr ""
-
msgid "Choose your framework"
msgstr ""
@@ -4562,9 +4723,15 @@ msgstr ""
msgid "Cluster does not exist"
msgstr ""
+msgid "Cluster is required for Stages::ClusterEndpointInserter"
+msgstr ""
+
msgid "Cluster level"
msgstr ""
+msgid "Cluster type must be specificed for Stages::ClusterEndpointInserter"
+msgstr ""
+
msgid "ClusterIntegration| %{custom_domain_start}More information%{custom_domain_end}."
msgstr ""
@@ -4676,7 +4843,7 @@ msgstr ""
msgid "ClusterIntegration|Cert-Manager"
msgstr ""
-msgid "ClusterIntegration|Cert-Manager is a native Kubernetes certificate management controller that helps with issuing certificates. Installing Cert-Manager on your cluster will issue a certificate by %{letsEncrypt} and ensure that certificates are valid and up-to-date."
+msgid "ClusterIntegration|Cert-Manager is a native Kubernetes certificate management controller that helps with issuing certificates. Installing Cert-Manager on your cluster will issue a certificate by %{linkStart}Let's Encrypt%{linkEnd} and ensure that certificates are valid and up-to-date."
msgstr ""
msgid "ClusterIntegration|Certificate Authority bundle (PEM format)"
@@ -4703,9 +4870,6 @@ msgstr ""
msgid "ClusterIntegration|Clear the local cache of namespace and service accounts. This is necessary if your integration has become out of sync. The cache is repopulated during the next CI job that requires namespace and service accounts."
msgstr ""
-msgid "ClusterIntegration|Cloud Run"
-msgstr ""
-
msgid "ClusterIntegration|Cluster being created"
msgstr ""
@@ -4790,7 +4954,7 @@ msgstr ""
msgid "ClusterIntegration|Crossplane"
msgstr ""
-msgid "ClusterIntegration|Crossplane enables declarative provisioning of managed services from your cloud of choice using %{codeStart}kubectl%{codeEnd} or %{gitlabIntegrationLink}. Crossplane runs inside your Kubernetes cluster and supports secure connectivity and secrets management between app containers and the cloud services they depend on."
+msgid "ClusterIntegration|Crossplane enables declarative provisioning of managed services from your cloud of choice using %{codeStart}kubectl%{codeEnd} or %{linkStart}GitLab Integration%{linkEnd}. Crossplane runs inside your Kubernetes cluster and supports secure connectivity and secrets management between app containers and the cloud services they depend on."
msgstr ""
msgid "ClusterIntegration|Deletes all GitLab resources attached to this cluster during removal"
@@ -4874,9 +5038,6 @@ msgstr ""
msgid "ClusterIntegration|GitLab-managed cluster"
msgstr ""
-msgid "ClusterIntegration|Gitlab Integration"
-msgstr ""
-
msgid "ClusterIntegration|Global default"
msgstr ""
@@ -4922,7 +5083,7 @@ msgstr ""
msgid "ClusterIntegration|Ingress gives you a way to route requests to services based on the request host or path, centralizing a number of services into a single entrypoint."
msgstr ""
-msgid "ClusterIntegration|Installing Ingress may incur additional costs. Learn more about %{pricingLink}."
+msgid "ClusterIntegration|Installing Ingress may incur additional costs. Learn more about %{linkStart}pricing%{linkEnd}."
msgstr ""
msgid "ClusterIntegration|Instance cluster"
@@ -4937,7 +5098,7 @@ msgstr ""
msgid "ClusterIntegration|Issuer Email"
msgstr ""
-msgid "ClusterIntegration|Issuers represent a certificate authority. You must provide an email address for your Issuer. "
+msgid "ClusterIntegration|Issuers represent a certificate authority. You must provide an email address for your Issuer."
msgstr ""
msgid "ClusterIntegration|Jupyter Hostname"
@@ -5009,9 +5170,6 @@ msgstr ""
msgid "ClusterIntegration|Learn more about instance Kubernetes clusters"
msgstr ""
-msgid "ClusterIntegration|Let's Encrypt"
-msgstr ""
-
msgid "ClusterIntegration|Loading IAM Roles"
msgstr ""
@@ -5123,7 +5281,7 @@ msgstr ""
msgid "ClusterIntegration|Prometheus"
msgstr ""
-msgid "ClusterIntegration|Prometheus is an open-source monitoring system with %{gitlabIntegrationLink} to monitor deployed applications."
+msgid "ClusterIntegration|Prometheus is an open-source monitoring system with %{linkStart}GitLab Integration%{linkEnd} to monitor deployed applications."
msgstr ""
msgid "ClusterIntegration|Provider details"
@@ -5279,10 +5437,10 @@ msgstr ""
msgid "ClusterIntegration|Select zone to choose machine type"
msgstr ""
-msgid "ClusterIntegration|Send Cilium Logs"
+msgid "ClusterIntegration|Send Container Network Policies Logs"
msgstr ""
-msgid "ClusterIntegration|Send ModSecurity Logs"
+msgid "ClusterIntegration|Send Web Application Firewall Logs"
msgstr ""
msgid "ClusterIntegration|Service Token"
@@ -5450,15 +5608,12 @@ msgstr ""
msgid "ClusterIntegration|documentation"
msgstr ""
-msgid "ClusterIntegration|installed via %{installed_via}"
+msgid "ClusterIntegration|installed via %{linkStart}Cloud Run%{linkEnd}"
msgstr ""
msgid "ClusterIntegration|meets the requirements"
msgstr ""
-msgid "ClusterIntegration|pricing"
-msgstr ""
-
msgid "ClusterIntegration|sign up"
msgstr ""
@@ -5495,6 +5650,15 @@ msgstr ""
msgid "Code"
msgstr ""
+msgid "Code Coverage: %{coveragePercentage}%{percentSymbol}"
+msgstr ""
+
+msgid "Code Coverage| Empty code coverage data"
+msgstr ""
+
+msgid "Code Coverage|Couldn't fetch the code coverage data"
+msgstr ""
+
msgid "Code Owners"
msgstr ""
@@ -5549,7 +5713,7 @@ msgstr ""
msgid "Collapse approvers"
msgstr ""
-msgid "Collapse child epics"
+msgid "Collapse milestones"
msgstr ""
msgid "Collapse replies"
@@ -5567,6 +5731,9 @@ msgstr ""
msgid "Coming soon"
msgstr ""
+msgid "Comma-separated, e.g. '1.1.1.1, 2.2.2.0/24'"
+msgstr ""
+
msgid "Command"
msgstr ""
@@ -5737,6 +5904,12 @@ msgstr ""
msgid "Compliance framework (optional)"
msgstr ""
+msgid "Compliance frameworks"
+msgstr ""
+
+msgid "ComplianceDashboard|created by:"
+msgstr ""
+
msgid "ComplianceFramework|GDPR"
msgstr ""
@@ -5875,6 +6048,9 @@ msgstr ""
msgid "Connection timed out"
msgstr ""
+msgid "Connection timeout"
+msgstr ""
+
msgid "Contact sales to upgrade"
msgstr ""
@@ -5930,9 +6106,6 @@ msgstr ""
msgid "ContainerRegistry|CLI Commands"
msgstr ""
-msgid "ContainerRegistry|Compressed Size"
-msgstr ""
-
msgid "ContainerRegistry|Container Registry"
msgstr ""
@@ -5948,6 +6121,9 @@ msgstr ""
msgid "ContainerRegistry|Copy push command"
msgstr ""
+msgid "ContainerRegistry|Delete selected"
+msgstr ""
+
msgid "ContainerRegistry|Docker connection error"
msgstr ""
@@ -5981,16 +6157,16 @@ msgstr ""
msgid "ContainerRegistry|If you are not already logged in, you need to authenticate to the Container Registry by using your GitLab username and password. If you have %{twofaDocLinkStart}Two-Factor Authentication%{twofaDocLinkEnd} enabled, use a %{personalAccessTokensDocLinkStart}Personal Access Token%{personalAccessTokensDocLinkEnd} instead of a password."
msgstr ""
-msgid "ContainerRegistry|Image ID"
+msgid "ContainerRegistry|Image ID: %{imageId}"
msgstr ""
msgid "ContainerRegistry|Image Repositories"
msgstr ""
-msgid "ContainerRegistry|Keep and protect the images that matter most."
+msgid "ContainerRegistry|Image tags"
msgstr ""
-msgid "ContainerRegistry|Last Updated"
+msgid "ContainerRegistry|Keep and protect the images that matter most."
msgstr ""
msgid "ContainerRegistry|Login"
@@ -6005,6 +6181,9 @@ msgstr ""
msgid "ContainerRegistry|Please contact your administrator."
msgstr ""
+msgid "ContainerRegistry|Published %{timeInfo}"
+msgstr ""
+
msgid "ContainerRegistry|Push an image"
msgstr ""
@@ -6020,9 +6199,6 @@ msgstr ""
msgid "ContainerRegistry|Remove repository"
msgstr ""
-msgid "ContainerRegistry|Remove selected tags"
-msgstr ""
-
msgid "ContainerRegistry|Remove tag"
msgid_plural "ContainerRegistry|Remove tags"
msgstr[0] ""
@@ -6052,9 +6228,6 @@ msgstr ""
msgid "ContainerRegistry|Sorry, your filter produced no results."
msgstr ""
-msgid "ContainerRegistry|Tag"
-msgstr ""
-
msgid "ContainerRegistry|Tag expiration policy"
msgstr ""
@@ -6199,21 +6372,6 @@ msgstr ""
msgid "Control the display of third party offers."
msgstr ""
-msgid "Control the maximum concurrency of LFS/attachment backfill for this secondary node"
-msgstr ""
-
-msgid "Control the maximum concurrency of container repository operations for this Geo node"
-msgstr ""
-
-msgid "Control the maximum concurrency of repository backfill for this secondary node"
-msgstr ""
-
-msgid "Control the maximum concurrency of verification operations for this Geo node"
-msgstr ""
-
-msgid "Control the minimum interval in days that a repository should be reverified for this primary node"
-msgstr ""
-
msgid "Cookie domain"
msgstr ""
@@ -6355,6 +6513,9 @@ msgstr ""
msgid "Could not find design."
msgstr ""
+msgid "Could not find iteration"
+msgstr ""
+
msgid "Could not remove the trigger."
msgstr ""
@@ -6391,6 +6552,12 @@ msgstr ""
msgid "Coverage"
msgstr ""
+msgid "Coverage Fuzzing"
+msgstr ""
+
+msgid "Crash State"
+msgstr ""
+
msgid "Create"
msgstr ""
@@ -6439,6 +6606,9 @@ msgstr ""
msgid "Create a personal access token on your account to pull or push via %{protocol}."
msgstr ""
+msgid "Create a project pre-populated with the necessary files to get you started quickly."
+msgstr ""
+
msgid "Create an account using:"
msgstr ""
@@ -6607,6 +6777,9 @@ msgstr ""
msgid "Creates branch '%{branch_name}' and a merge request to resolve this issue."
msgstr ""
+msgid "Creating"
+msgstr ""
+
msgid "Creating epic"
msgstr ""
@@ -6658,7 +6831,10 @@ msgstr ""
msgid "Current vulnerabilities count"
msgstr ""
-msgid "CurrentUser|Buy CI minutes"
+msgid "CurrentUser|Buy Pipeline minutes"
+msgstr ""
+
+msgid "CurrentUser|One of your groups is running out"
msgstr ""
msgid "CurrentUser|Profile"
@@ -6673,6 +6849,9 @@ msgstr ""
msgid "CurrentUser|Upgrade"
msgstr ""
+msgid "Custom Attributes"
+msgstr ""
+
msgid "Custom CI configuration path"
msgstr ""
@@ -6936,12 +7115,18 @@ msgstr ""
msgid "DAG"
msgstr ""
+msgid "DAG visualization requires at least 3 dependent jobs."
+msgstr ""
+
msgid "DNS"
msgstr ""
msgid "Dashboard"
msgstr ""
+msgid "Dashboard uid not found"
+msgstr ""
+
msgid "DashboardProjects|All"
msgstr ""
@@ -6966,6 +7151,9 @@ msgstr ""
msgid "Data is still calculating..."
msgstr ""
+msgid "Datasource name not found"
+msgstr ""
+
msgid "Date"
msgstr ""
@@ -7146,6 +7334,9 @@ msgstr ""
msgid "Delete this attachment"
msgstr ""
+msgid "Delete user list"
+msgstr ""
+
msgid "Delete variable"
msgstr ""
@@ -7462,6 +7653,9 @@ msgstr ""
msgid "Deploying to"
msgstr ""
+msgid "Deploying to AWS is easy with GitLab"
+msgstr ""
+
msgid "Deployment Frequency"
msgstr ""
@@ -7824,6 +8018,9 @@ msgstr ""
msgid "Dismissed on pipeline %{pipelineLink} at %{projectLink}"
msgstr ""
+msgid "Display alerts from all your monitoring tools directly within GitLab."
+msgstr ""
+
msgid "Display name"
msgstr ""
@@ -7881,9 +8078,6 @@ msgstr ""
msgid "Don't show again"
msgstr ""
-msgid "Don't worry, you can access this tour by clicking on the help icon in the top right corner and choose <strong>Learn GitLab</strong>."
-msgstr ""
-
msgid "Done"
msgstr ""
@@ -8010,6 +8204,9 @@ msgstr ""
msgid "Edit Release"
msgstr ""
+msgid "Edit Slack integration"
+msgstr ""
+
msgid "Edit Snippet"
msgstr ""
@@ -8049,6 +8246,9 @@ msgstr ""
msgid "Edit issues"
msgstr ""
+msgid "Edit iteration"
+msgstr ""
+
msgid "Edit public deploy key"
msgstr ""
@@ -8097,6 +8297,9 @@ msgstr ""
msgid "Email"
msgstr ""
+msgid "Email Notification"
+msgstr ""
+
msgid "Email address"
msgstr ""
@@ -8109,6 +8312,9 @@ msgstr ""
msgid "Email not verified. Please verify your email in Salesforce."
msgstr ""
+msgid "Email notification for unknown sign-ins"
+msgstr ""
+
msgid "Email patch"
msgstr ""
@@ -8340,6 +8546,9 @@ msgstr ""
msgid "Enforce DNS rebinding attack protection"
msgstr ""
+msgid "Enforce personal access token expiration"
+msgstr ""
+
msgid "Ensure connectivity is available from the GitLab server to the Prometheus server"
msgstr ""
@@ -8409,6 +8618,9 @@ msgstr ""
msgid "Enter the name of your application, and we'll return a unique %{type}."
msgstr ""
+msgid "Enter weights for storages for new repositories."
+msgstr ""
+
msgid "Enter your password to approve"
msgstr ""
@@ -8418,15 +8630,27 @@ msgstr ""
msgid "Environment does not have deployments"
msgstr ""
+msgid "Environment is required for Stages::MetricEndpointInserter"
+msgstr ""
+
+msgid "Environment is required for Stages::VariableEndpointInserter"
+msgstr ""
+
msgid "Environment scope"
msgstr ""
+msgid "Environment variables are applied to all project environments in this instance via the Runner. You can use environment variables for passwords, secret keys, etc. Make variables available to the running application by prepending the variable key with <code>K8S_SECRET_</code>. You can set variables to be:"
+msgstr ""
+
msgid "Environment variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. Additionally, they can be masked so they are hidden in job logs, though they must match certain regexp requirements to do so. You can use environment variables for passwords, secret keys, or whatever you want."
msgstr ""
msgid "Environment variables are configured by your administrator to be %{link_start}protected%{link_end} by default"
msgstr ""
+msgid "Environment variables on this GitLab instance are configured to be %{link_start}protected%{link_end} by default"
+msgstr ""
+
msgid "Environment:"
msgstr ""
@@ -8958,6 +9182,9 @@ msgstr ""
msgid "EventFilterBy|Filter by comments"
msgstr ""
+msgid "EventFilterBy|Filter by designs"
+msgstr ""
+
msgid "EventFilterBy|Filter by epic events"
msgstr ""
@@ -9090,15 +9317,15 @@ msgstr ""
msgid "Expand approvers"
msgstr ""
-msgid "Expand child epics"
-msgstr ""
-
msgid "Expand down"
msgstr ""
msgid "Expand dropdown"
msgstr ""
+msgid "Expand milestones"
+msgstr ""
+
msgid "Expand sidebar"
msgstr ""
@@ -9114,6 +9341,9 @@ msgstr ""
msgid "Expiration date"
msgstr ""
+msgid "Expiration not enforced"
+msgstr ""
+
msgid "Expiration policy for the Container Registry is a perfect solution for keeping the Registry space down while still enjoying the full power of GitLab CI/CD."
msgstr ""
@@ -9234,6 +9464,9 @@ msgstr ""
msgid "Failed Jobs"
msgstr ""
+msgid "Failed on"
+msgstr ""
+
msgid "Failed to add a Zoom meeting"
msgstr ""
@@ -9294,13 +9527,19 @@ msgstr ""
msgid "Failed to install."
msgstr ""
+msgid "Failed to load assignees. Please try again."
+msgstr ""
+
+msgid "Failed to load authors. Please try again."
+msgstr ""
+
msgid "Failed to load emoji list."
msgstr ""
msgid "Failed to load error details from Sentry."
msgstr ""
-msgid "Failed to load errors from Sentry. Error message: %{errorMessage}"
+msgid "Failed to load errors from Sentry."
msgstr ""
msgid "Failed to load group activity metrics. Please try again."
@@ -9381,6 +9620,9 @@ msgstr ""
msgid "Failed to set due date because the date format is invalid."
msgstr ""
+msgid "Failed to set iteration on this issue. Please try again."
+msgstr ""
+
msgid "Failed to signing using smartcard authentication"
msgstr ""
@@ -9603,13 +9845,10 @@ msgstr ""
msgid "FeatureFlags|Target environments"
msgstr ""
-msgid "FeatureFlags|There are no active feature flags"
-msgstr ""
-
-msgid "FeatureFlags|There are no inactive feature flags"
+msgid "FeatureFlags|There was an error fetching the feature flags."
msgstr ""
-msgid "FeatureFlags|There was an error fetching the feature flags."
+msgid "FeatureFlags|There was an error retrieving user lists"
msgstr ""
msgid "FeatureFlags|Try again in a few moments or contact your support team."
@@ -9621,9 +9860,18 @@ msgstr ""
msgid "FeatureFlag|Delete strategy"
msgstr ""
+msgid "FeatureFlag|List"
+msgstr ""
+
msgid "FeatureFlag|Percentage"
msgstr ""
+msgid "FeatureFlag|Select a user list"
+msgstr ""
+
+msgid "FeatureFlag|There are no configured user lists"
+msgstr ""
+
msgid "FeatureFlag|Type"
msgstr ""
@@ -9780,6 +10028,12 @@ msgstr ""
msgid "Filter..."
msgstr ""
+msgid "Find File"
+msgstr ""
+
+msgid "Find bugs in your code with coverage-guided fuzzing"
+msgstr ""
+
msgid "Find by path"
msgstr ""
@@ -10044,6 +10298,9 @@ msgstr ""
msgid "Geo allows you to replicate your GitLab instance to other geographical locations."
msgstr ""
+msgid "Geo nodes are paused using a command run on the node"
+msgstr ""
+
msgid "GeoNodeStatusEvent|%{timeAgoStr} (%{pendingEvents} events)"
msgstr ""
@@ -10128,6 +10385,9 @@ msgstr ""
msgid "GeoNodes|Not checksummed"
msgstr ""
+msgid "GeoNodes|Package files"
+msgstr ""
+
msgid "GeoNodes|Pausing replication stops the sync process. Are you sure?"
msgstr ""
@@ -10146,6 +10406,9 @@ msgstr ""
msgid "GeoNodes|Replication slots"
msgstr ""
+msgid "GeoNodes|Replication status"
+msgstr ""
+
msgid "GeoNodes|Repositories"
msgstr ""
@@ -10356,6 +10619,9 @@ msgstr ""
msgid "Geo|Synchronization failed - %{error}"
msgstr ""
+msgid "Geo|Synchronization of %{itemTitle} is disabled."
+msgstr ""
+
msgid "Geo|The database is currently %{db_lag} behind the primary node."
msgstr ""
@@ -10668,6 +10934,9 @@ msgstr ""
msgid "Go to %{link_to_google_takeout}."
msgstr ""
+msgid "Go to <strong>Issues</strong> > <strong>Boards</strong> to access your personalized learning issue board."
+msgstr ""
+
msgid "Go to Pipelines"
msgstr ""
@@ -10767,9 +11036,6 @@ msgstr ""
msgid "Go to your snippets"
msgstr ""
-msgid "Golden Tanuki"
-msgstr ""
-
msgid "Google Cloud Platform"
msgstr ""
@@ -10791,6 +11057,9 @@ msgstr ""
msgid "Grafana URL"
msgstr ""
+msgid "Grafana response contains invalid json"
+msgstr ""
+
msgid "GrafanaIntegration|API Token"
msgstr ""
@@ -10839,9 +11108,6 @@ msgstr ""
msgid "Group %{group_name} was successfully created."
msgstr ""
-msgid "Group '%{group_name}' is being imported."
-msgstr ""
-
msgid "Group Audit Events"
msgstr ""
@@ -10878,9 +11144,6 @@ msgstr ""
msgid "Group by:"
msgstr ""
-msgid "Group could not be imported: %{errors}"
-msgstr ""
-
msgid "Group description"
msgstr ""
@@ -10908,9 +11171,15 @@ msgstr ""
msgid "Group has not been marked for deletion"
msgstr ""
+msgid "Group import could not be scheduled"
+msgstr ""
+
msgid "Group info:"
msgstr ""
+msgid "Group is required when cluster_type is :group"
+msgstr ""
+
msgid "Group maintainers can register group runners in the %{link}"
msgstr ""
@@ -10974,6 +11243,24 @@ msgstr ""
msgid "GroupActivyMetrics|Recent activity (last 90 days)"
msgstr ""
+msgid "GroupImport|Failed to import group."
+msgstr ""
+
+msgid "GroupImport|Group '%{group_name}' is being imported."
+msgstr ""
+
+msgid "GroupImport|Group could not be imported: %{errors}"
+msgstr ""
+
+msgid "GroupImport|Please wait while we import the group for you. Refresh at will."
+msgstr ""
+
+msgid "GroupImport|The group was successfully imported."
+msgstr ""
+
+msgid "GroupImport|Unable to process group import file"
+msgstr ""
+
msgid "GroupRoadmap|%{dateWord} – No end date"
msgstr ""
@@ -11241,6 +11528,9 @@ msgstr ""
msgid "Groups (%{groups})"
msgstr ""
+msgid "Groups and projects"
+msgstr ""
+
msgid "Groups can also be nested by creating %{subgroup_docs_link_start}subgroups%{subgroup_docs_link_end}."
msgstr ""
@@ -11286,6 +11576,33 @@ msgstr ""
msgid "GroupsEmptyState|You can manage your group member’s permissions and access to each project in the group."
msgstr ""
+msgid "GroupsNew|Contact an administrator to enable options for importing your group."
+msgstr ""
+
+msgid "GroupsNew|Create"
+msgstr ""
+
+msgid "GroupsNew|Create group"
+msgstr ""
+
+msgid "GroupsNew|GitLab group export"
+msgstr ""
+
+msgid "GroupsNew|Import"
+msgstr ""
+
+msgid "GroupsNew|Import group"
+msgstr ""
+
+msgid "GroupsNew|My Awesome Group"
+msgstr ""
+
+msgid "GroupsNew|No import options available"
+msgstr ""
+
+msgid "GroupsNew|To copy a GitLab group between installations, navigate to the group settings page for the original installation, generate an export file, and upload it here."
+msgstr ""
+
msgid "GroupsTree|Are you sure you want to leave the \"%{fullName}\" group?"
msgstr ""
@@ -11388,6 +11705,9 @@ msgstr ""
msgid "Helps reduce request volume for protected paths"
msgstr ""
+msgid "Here are all your projects in your group, including the one you just created. To start, let’s take a look at your personalized learning project which will help you learn about GitLab at your own pace."
+msgstr ""
+
msgid "Here you will find recent merge request activity"
msgstr ""
@@ -11402,6 +11722,9 @@ msgid_plural "Hide charts"
msgstr[0] ""
msgstr[1] ""
+msgid "Hide details"
+msgstr ""
+
msgid "Hide file browser"
msgstr ""
@@ -11411,6 +11734,9 @@ msgstr ""
msgid "Hide host keys manual input"
msgstr ""
+msgid "Hide list"
+msgstr ""
+
msgid "Hide marketing-related entries from help"
msgstr ""
@@ -11431,9 +11757,6 @@ msgstr[1] ""
msgid "Hide values"
msgstr ""
-msgid "Hiding all labels"
-msgstr ""
-
msgid "High or unknown vulnerabilities present"
msgstr ""
@@ -11518,6 +11841,9 @@ msgstr ""
msgid "ID:"
msgstr ""
+msgid "IDE"
+msgstr ""
+
msgid "IDE|Allow live previews of JavaScript projects in the Web IDE using CodeSandbox Live Preview."
msgstr ""
@@ -11608,10 +11934,10 @@ msgstr ""
msgid "If enabled"
msgstr ""
-msgid "If enabled, access to projects will be validated on an external service using their classification label."
+msgid "If enabled, GitLab will handle Object Storage replication using Geo. %{linkStart}More information%{linkEnd}"
msgstr ""
-msgid "If enabled, and if object storage is enabled, GitLab will handle Object Storage replication using Geo"
+msgid "If enabled, access to projects will be validated on an external service using their classification label."
msgstr ""
msgid "If there is no previous license or if the previous license has expired, some GitLab functionality will be blocked until a new, valid license is uploaded."
@@ -11973,6 +12299,9 @@ msgid_plural "Instances"
msgstr[0] ""
msgstr[1] ""
+msgid "Instance Configuration"
+msgstr ""
+
msgid "Instance Statistics visibility"
msgstr ""
@@ -11994,9 +12323,6 @@ msgstr ""
msgid "Integrations"
msgstr ""
-msgid "Integrations allow you to integrate GitLab with other applications"
-msgstr ""
-
msgid "Integrations|All details"
msgstr ""
@@ -12108,6 +12434,9 @@ msgstr ""
msgid "Invalid query"
msgstr ""
+msgid "Invalid repository bundle for snippet with id %{snippet_id}"
+msgstr ""
+
msgid "Invalid repository path"
msgstr ""
@@ -12126,6 +12455,9 @@ msgstr ""
msgid "Invalid two-factor code."
msgstr ""
+msgid "Invalid yaml"
+msgstr ""
+
msgid "Invitation"
msgstr ""
@@ -12345,15 +12677,24 @@ msgstr ""
msgid "It seems like the Dependency Scanning job ran successfully, but no dependencies have been detected in your project."
msgstr ""
+msgid "It seems that there is currently no available data for code coverage"
+msgstr ""
+
msgid "It's you"
msgstr ""
+msgid "Iteration"
+msgstr ""
+
msgid "Iteration changed to"
msgstr ""
msgid "Iteration removed"
msgstr ""
+msgid "Iteration updated"
+msgstr ""
+
msgid "Iterations"
msgstr ""
@@ -12384,12 +12725,18 @@ msgstr ""
msgid "January"
msgstr ""
+msgid "Jira Issues"
+msgstr ""
+
msgid "Jira import is already running."
msgstr ""
msgid "Jira integration not configured."
msgstr ""
+msgid "Jira project key is not configured"
+msgstr ""
+
msgid "Jira project: %{importProject}"
msgstr ""
@@ -12570,6 +12917,9 @@ msgstr ""
msgid "Keep divergent refs"
msgstr ""
+msgid "Kerberos access denied"
+msgstr ""
+
msgid "Key"
msgstr ""
@@ -12579,10 +12929,13 @@ msgstr ""
msgid "Key: %{key}"
msgstr ""
-msgid "Keyboard Shortcuts"
+msgid "Keyboard shortcuts"
msgstr ""
-msgid "Keyboard shortcuts"
+msgid "Keys"
+msgstr ""
+
+msgid "Ki"
msgstr ""
msgid "Kubernetes"
@@ -12624,6 +12977,9 @@ msgstr ""
msgid "LDAP"
msgstr ""
+msgid "LDAP Synchronization"
+msgstr ""
+
msgid "LDAP settings"
msgstr ""
@@ -12848,12 +13204,18 @@ msgstr ""
msgid "Learn more about custom project templates"
msgstr ""
+msgid "Learn more about deploying to AWS"
+msgstr ""
+
msgid "Learn more about deploying to a cluster"
msgstr ""
msgid "Learn more about group-level project templates"
msgstr ""
+msgid "Learn more about job dependencies"
+msgstr ""
+
msgid "Learn more about signing commits"
msgstr ""
@@ -12905,9 +13267,21 @@ msgstr ""
msgid "License History"
msgstr ""
+msgid "License ID:"
+msgstr ""
+
+msgid "License URL"
+msgstr ""
+
msgid "License-Check"
msgstr ""
+msgid "LicenseCompliance|%{docLinkStart}License Approvals%{docLinkEnd} are active"
+msgstr ""
+
+msgid "LicenseCompliance|%{docLinkStart}License Approvals%{docLinkEnd} are inactive"
+msgstr ""
+
msgid "LicenseCompliance|Add a license"
msgstr ""
@@ -12929,9 +13303,15 @@ msgstr ""
msgid "LicenseCompliance|Deny"
msgstr ""
+msgid "LicenseCompliance|Learn more about %{linkStart}License Approvals%{linkEnd}"
+msgstr ""
+
msgid "LicenseCompliance|License"
msgstr ""
+msgid "LicenseCompliance|License Approvals"
+msgstr ""
+
msgid "LicenseCompliance|License Compliance detected %d license and policy violation for the source branch only; approval required"
msgid_plural "LicenseCompliance|License Compliance detected %d licenses and policy violations for the source branch only; approval required"
msgstr[0] ""
@@ -13152,6 +13532,9 @@ msgstr ""
msgid "List your Bitbucket Server repositories"
msgstr ""
+msgid "Lists"
+msgstr ""
+
msgid "Live preview"
msgstr ""
@@ -13335,6 +13718,9 @@ msgstr ""
msgid "Manage project labels"
msgstr ""
+msgid "Manage storage usage"
+msgstr ""
+
msgid "Manage two-factor authentication"
msgstr ""
@@ -13350,6 +13736,9 @@ msgstr ""
msgid "Manifest file import"
msgstr ""
+msgid "Manifest import"
+msgstr ""
+
msgid "Manual job"
msgstr ""
@@ -13497,6 +13886,12 @@ msgstr ""
msgid "Maximum field length"
msgstr ""
+msgid "Maximum file size is 2MB. Please select a smaller file."
+msgstr ""
+
+msgid "Maximum import size (MB)"
+msgstr ""
+
msgid "Maximum job timeout"
msgstr ""
@@ -13536,6 +13931,9 @@ msgstr ""
msgid "Maximum size of Elasticsearch bulk indexing requests."
msgstr ""
+msgid "Maximum size of import files."
+msgstr ""
+
msgid "Maximum size of individual attachments in comments."
msgstr ""
@@ -13587,6 +13985,9 @@ msgstr ""
msgid "Merge (when the pipeline succeeds)"
msgstr ""
+msgid "Merge Conflicts"
+msgstr ""
+
msgid "Merge Request"
msgstr ""
@@ -13713,6 +14114,9 @@ msgstr ""
msgid "MergeRequests|Squash task canceled: another squash is already in progress."
msgstr ""
+msgid "MergeRequests|This project does not allow squashing commits when merge requests are accepted."
+msgstr ""
+
msgid "MergeRequests|Thread stays resolved"
msgstr ""
@@ -13866,6 +14270,12 @@ msgstr ""
msgid "MetricsSettings|Add a button to the metrics dashboard linking directly to your existing external dashboard."
msgstr ""
+msgid "MetricsSettings|Choose whether to display dashboard metrics in UTC or the user's local timezone."
+msgstr ""
+
+msgid "MetricsSettings|Dashboard timezone"
+msgstr ""
+
msgid "MetricsSettings|External dashboard URL"
msgstr ""
@@ -13875,6 +14285,12 @@ msgstr ""
msgid "MetricsSettings|Metrics Dashboard"
msgstr ""
+msgid "MetricsSettings|UTC (Coordinated Universal Time)"
+msgstr ""
+
+msgid "MetricsSettings|User's local timezone"
+msgstr ""
+
msgid "Metrics|Add metric"
msgstr ""
@@ -13890,6 +14306,9 @@ msgstr ""
msgid "Metrics|Create metric"
msgstr ""
+msgid "Metrics|Current"
+msgstr ""
+
msgid "Metrics|Delete metric"
msgstr ""
@@ -13940,6 +14359,9 @@ msgstr ""
msgid "Metrics|Max"
msgstr ""
+msgid "Metrics|Min"
+msgstr ""
+
msgid "Metrics|Must be a valid PromQL query."
msgstr ""
@@ -13955,6 +14377,9 @@ msgstr ""
msgid "Metrics|Refresh dashboard"
msgstr ""
+msgid "Metrics|Select a value"
+msgstr ""
+
msgid "Metrics|Star dashboard"
msgstr ""
@@ -13973,12 +14398,18 @@ msgstr ""
msgid "Metrics|There was an error getting annotations information."
msgstr ""
+msgid "Metrics|There was an error getting dashboard validation warnings information."
+msgstr ""
+
msgid "Metrics|There was an error getting deployment information."
msgstr ""
msgid "Metrics|There was an error getting environments information."
msgstr ""
+msgid "Metrics|There was an error getting options for variable \"%{name}\"."
+msgstr ""
+
msgid "Metrics|There was an error trying to validate your query"
msgstr ""
@@ -14021,6 +14452,9 @@ msgstr ""
msgid "Metrics|You're about to permanently delete this metric. This cannot be undone."
msgstr ""
+msgid "Metrics|Your dashboard schema is invalid. Edit the dashboard to correct the YAML schema."
+msgstr ""
+
msgid "Metrics|e.g. HTTP requests"
msgstr ""
@@ -14036,12 +14470,18 @@ msgstr ""
msgid "Metrics|e.g. req/sec"
msgstr ""
+msgid "Mi"
+msgstr ""
+
msgid "Microsoft Azure"
msgstr ""
msgid "Middleman project with Static Site Editor support"
msgstr ""
+msgid "Migrate your data from an external source like GitHub, Bitbucket, or another instance of GitLab."
+msgstr ""
+
msgid "Migrated %{success_count}/%{total_count} files."
msgstr ""
@@ -14176,6 +14616,9 @@ msgstr ""
msgid "Minimum capacity to be available before we schedule more mirrors preemptively."
msgstr ""
+msgid "Minimum interval in days"
+msgstr ""
+
msgid "Minimum length is %{minimum_password_length} characters"
msgstr ""
@@ -14362,6 +14805,12 @@ msgstr ""
msgid "Multiple uploaders found: %{uploader_types}"
msgstr ""
+msgid "Must match with the %{codeStart}external_url%{codeEnd} in %{codeStart}/etc/gitlab/gitlab.rb%{codeEnd}."
+msgstr ""
+
+msgid "Must match with the %{codeStart}geo_node_name%{codeEnd} in %{codeStart}/etc/gitlab/gitlab.rb%{codeEnd}. %{linkStart}More information%{linkEnd}"
+msgstr ""
+
msgid "My Awesome Group"
msgstr ""
@@ -14476,10 +14925,16 @@ msgstr ""
msgid "New Application"
msgstr ""
+msgid "New Branch"
+msgstr ""
+
+msgid "New Deploy Key"
+msgstr ""
+
msgid "New Environment"
msgstr ""
-msgid "New Geo Node"
+msgid "New File"
msgstr ""
msgid "New Group"
@@ -14496,15 +14951,15 @@ msgid_plural "New Issues"
msgstr[0] ""
msgstr[1] ""
-msgid "New Iteration"
-msgstr ""
-
msgid "New Jira import"
msgstr ""
msgid "New Label"
msgstr ""
+msgid "New Merge Request"
+msgstr ""
+
msgid "New Milestone"
msgstr ""
@@ -14523,6 +14978,9 @@ msgstr ""
msgid "New Snippet"
msgstr ""
+msgid "New User"
+msgstr ""
+
msgid "New branch"
msgstr ""
@@ -14565,6 +15023,9 @@ msgstr ""
msgid "New issue title"
msgstr ""
+msgid "New iteration"
+msgstr ""
+
msgid "New iteration created"
msgstr ""
@@ -14742,6 +15203,12 @@ msgstr ""
msgid "No grouping"
msgstr ""
+msgid "No iteration"
+msgstr ""
+
+msgid "No iterations to show"
+msgstr ""
+
msgid "No job log"
msgstr ""
@@ -14784,6 +15251,9 @@ msgstr ""
msgid "No other labels with such name or description"
msgstr ""
+msgid "No panels matching properties %{opts}"
+msgstr ""
+
msgid "No parent group"
msgstr ""
@@ -14817,6 +15287,9 @@ msgstr ""
msgid "No schedules"
msgstr ""
+msgid "No stack trace for this error"
+msgstr ""
+
msgid "No starrers matched your search"
msgstr ""
@@ -14829,7 +15302,7 @@ msgstr ""
msgid "No test coverage"
msgstr ""
-msgid "No thanks, don't show this again"
+msgid "No thanks"
msgstr ""
msgid "No vulnerabilities present"
@@ -14844,9 +15317,6 @@ msgstr ""
msgid "No, directly import the existing email addresses and usernames."
msgstr ""
-msgid "No, not interested right now"
-msgstr ""
-
msgid "No. of commits"
msgstr ""
@@ -14892,9 +15362,6 @@ msgstr ""
msgid "Not found."
msgstr ""
-msgid "Not helpful"
-msgstr ""
-
msgid "Not now"
msgstr ""
@@ -15051,6 +15518,9 @@ msgstr ""
msgid "Notifications on"
msgstr ""
+msgid "Notify users by email when sign-in location is not recognized"
+msgstr ""
+
msgid "Nov"
msgstr ""
@@ -15117,9 +15587,6 @@ msgstr ""
msgid "Oh no!"
msgstr ""
-msgid "Ok let's go"
-msgstr ""
-
msgid "Oldest first"
msgstr ""
@@ -15132,16 +15599,49 @@ msgstr ""
msgid "On track"
msgstr ""
+msgid "OnDemandScans|Attached branch"
+msgstr ""
+
+msgid "OnDemandScans|Attached branch is where the scan job runs."
+msgstr ""
+
+msgid "OnDemandScans|Could not run the scan. Please try again."
+msgstr ""
+
msgid "OnDemandScans|Create new DAST scan"
msgstr ""
+msgid "OnDemandScans|DAST will scan the target URL and any discovered sub URLs."
+msgstr ""
+
+msgid "OnDemandScans|New on-demand DAST scan"
+msgstr ""
+
msgid "OnDemandScans|On-demand Scans"
msgstr ""
+msgid "OnDemandScans|On-demand scans run outside the DevOps cycle and find vulnerabilities in your projects. %{learnMoreLinkStart}Learn more%{learnMoreLinkEnd}"
+msgstr ""
+
+msgid "OnDemandScans|Only a passive scan can be performed on demand."
+msgstr ""
+
+msgid "OnDemandScans|Passive DAST Scan"
+msgstr ""
+
+msgid "OnDemandScans|Please enter a valid URL format, ex: http://www.example.com/home"
+msgstr ""
+
+msgid "OnDemandScans|Run this scan"
+msgstr ""
+
+msgid "OnDemandScans|Scan mode"
+msgstr ""
+
msgid "OnDemandScans|Schedule or run scans immediately against target sites. Currently available on-demand scan type: DAST. %{helpLinkStart}More information%{helpLinkEnd}"
msgstr ""
-msgid "Onboarding"
+msgid "OnDemandScans|Target URL"
msgstr ""
msgid "Once imported, repositories can be mirrored over SSH. Read more %{link_start}here%{link_end}."
@@ -15167,6 +15667,9 @@ msgstr ""
msgid "One or more of you personal access tokens were revoked"
msgstr ""
+msgid "One or more of your %{provider} projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
+msgstr ""
+
msgid "One or more of your Bitbucket projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
msgstr ""
@@ -15668,6 +16171,9 @@ msgstr ""
msgid "Parent epic is not present."
msgstr ""
+msgid "Parsing error for param :embed_json. %{message}"
+msgstr ""
+
msgid "Part of merge request changes"
msgstr ""
@@ -15680,6 +16186,9 @@ msgstr ""
msgid "Passed"
msgstr ""
+msgid "Passed on"
+msgstr ""
+
msgid "Password"
msgstr ""
@@ -15764,6 +16273,9 @@ msgstr ""
msgid "Perform common operations on GitLab project"
msgstr ""
+msgid "Performance and resource management"
+msgstr ""
+
msgid "Performance optimization"
msgstr ""
@@ -15971,7 +16483,7 @@ msgstr ""
msgid "Pipelines|Something went wrong while cleaning runners cache."
msgstr ""
-msgid "Pipelines|There are currently no %{scope} pipelines."
+msgid "Pipelines|There are currently no finished pipelines."
msgstr ""
msgid "Pipelines|There are currently no pipelines."
@@ -16082,6 +16594,9 @@ msgstr ""
msgid "Pipeline|Stop pipeline #%{pipelineId}?"
msgstr ""
+msgid "Pipeline|Tag name"
+msgstr ""
+
msgid "Pipeline|Trigger author"
msgstr ""
@@ -16163,12 +16678,18 @@ msgstr ""
msgid "Please check your email (%{email}) to verify that you own this address and unlock the power of CI/CD. Didn't receive it? %{resend_link}. Wrong email address? %{update_link}."
msgstr ""
+msgid "Please choose a file"
+msgstr ""
+
msgid "Please choose a group URL with no special characters."
msgstr ""
msgid "Please complete your profile with email address"
msgstr ""
+msgid "Please convert %{linkStart}them to Git%{linkEnd}, and go through the %{linkToImportFlow} again."
+msgstr ""
+
msgid "Please convert them to %{link_to_git}, and go through the %{link_to_import_flow} again."
msgstr ""
@@ -16226,6 +16747,9 @@ msgstr ""
msgid "Please provide a valid email address."
msgstr ""
+msgid "Please provide attributes to update"
+msgstr ""
+
msgid "Please refer to <a href=\"%{docs_url}\">%{docs_url}</a>"
msgstr ""
@@ -16961,6 +17485,9 @@ msgstr ""
msgid "Project has too many %{label_for_message} to search"
msgstr ""
+msgid "Project is required when cluster_type is :project"
+msgstr ""
+
msgid "Project members"
msgstr ""
@@ -17078,7 +17605,40 @@ msgstr ""
msgid "ProjectService|Comment"
msgstr ""
-msgid "ProjectService|Comment will be posted on each event"
+msgid "ProjectService|Event will be triggered by a push to the repository"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a commit is created/updated"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a confidential issue is created/updated/closed"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a deployment finishes"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a merge request is created/updated/merged"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a new tag is pushed to the repository"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a new, unique alert is recorded"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a pipeline status changes"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a wiki page is created/updated"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when an issue is created/updated/closed"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when someone adds a comment"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when someone adds a comment on a confidential issue"
msgstr ""
msgid "ProjectService|Perform common operations on GitLab project: %{project_name}"
@@ -17129,6 +17689,9 @@ msgstr ""
msgid "ProjectSettings|Enable 'Delete source branch' option by default"
msgstr ""
+msgid "ProjectSettings|Enable merge trains and pipelines for merged results"
+msgstr ""
+
msgid "ProjectSettings|Every merge creates a merge commit"
msgstr ""
@@ -17159,7 +17722,7 @@ msgstr ""
msgid "ProjectSettings|Forks"
msgstr ""
-msgid "ProjectSettings|Git Large File Storage"
+msgid "ProjectSettings|Git Large File Storage (LFS)"
msgstr ""
msgid "ProjectSettings|Internal"
@@ -17195,9 +17758,6 @@ msgstr ""
msgid "ProjectSettings|Merge options"
msgstr ""
-msgid "ProjectSettings|Merge pipelines will try to validate the post-merge result prior to merging"
-msgstr ""
-
msgid "ProjectSettings|Merge requests"
msgstr ""
@@ -17225,6 +17785,9 @@ msgstr ""
msgid "ProjectSettings|Pipelines"
msgstr ""
+msgid "ProjectSettings|Pipelines for merge requests must be enabled in the CI/CD configuration file, or pipelines could be unresolvable or dropped"
+msgstr ""
+
msgid "ProjectSettings|Pipelines must succeed"
msgstr ""
@@ -17252,6 +17815,9 @@ msgstr ""
msgid "ProjectSettings|Show link to create/view merge request when pushing from the command line"
msgstr ""
+msgid "ProjectSettings|Skipped pipelines are considered successful"
+msgstr ""
+
msgid "ProjectSettings|Snippets"
msgstr ""
@@ -17267,6 +17833,9 @@ msgstr ""
msgid "ProjectSettings|These checks must pass before merge requests can be merged"
msgstr ""
+msgid "ProjectSettings|This introduces the risk of merging changes that will not pass the pipeline."
+msgstr ""
+
msgid "ProjectSettings|This setting is applied on the server level and can be overridden by an admin."
msgstr ""
@@ -17444,15 +18013,27 @@ msgstr ""
msgid "ProjectsNew|Blank project"
msgstr ""
+msgid "ProjectsNew|Connect your external repository to GitLab CI/CD."
+msgstr ""
+
msgid "ProjectsNew|Contact an administrator to enable options for importing your project."
msgstr ""
msgid "ProjectsNew|Create"
msgstr ""
+msgid "ProjectsNew|Create a blank project to house your files, plan your work, and collaborate on code, among other things."
+msgstr ""
+
+msgid "ProjectsNew|Create blank project"
+msgstr ""
+
msgid "ProjectsNew|Create from template"
msgstr ""
+msgid "ProjectsNew|Create new project"
+msgstr ""
+
msgid "ProjectsNew|Creating project & repository."
msgstr ""
@@ -17477,6 +18058,9 @@ msgstr ""
msgid "ProjectsNew|Project description %{tag_start}(optional)%{tag_end}"
msgstr ""
+msgid "ProjectsNew|Run CI/CD for external repository"
+msgstr ""
+
msgid "ProjectsNew|Template"
msgstr ""
@@ -17738,6 +18322,9 @@ msgstr ""
msgid "Protected Branch"
msgstr ""
+msgid "Protected Branches"
+msgstr ""
+
msgid "Protected Environment"
msgstr ""
@@ -17750,6 +18337,9 @@ msgstr ""
msgid "Protected Tag"
msgstr ""
+msgid "Protected Tags"
+msgstr ""
+
msgid "Protected branches"
msgstr ""
@@ -17849,6 +18439,9 @@ msgstr ""
msgid "Provider"
msgstr ""
+msgid "Proxy support for this API is not available currently"
+msgstr ""
+
msgid "Pseudonymizer data collection"
msgstr ""
@@ -17873,6 +18466,9 @@ msgstr ""
msgid "Publish to status page"
msgstr ""
+msgid "Published on status page"
+msgstr ""
+
msgid "Publishes this issue to the associated status page."
msgstr ""
@@ -18047,6 +18643,9 @@ msgstr ""
msgid "Recipe"
msgstr ""
+msgid "Reconfigure"
+msgstr ""
+
msgid "Recover hidden stage"
msgstr ""
@@ -18133,6 +18732,9 @@ msgstr ""
msgid "Registration|Your profile"
msgstr ""
+msgid "Regulate approvals by authors/committers, based on compliance frameworks. Can be changed only at the instance level."
+msgstr ""
+
msgid "Rejected (closed)"
msgstr ""
@@ -18183,15 +18785,24 @@ msgstr ""
msgid "ReleaseAssetLinkType|Image"
msgstr ""
+msgid "ReleaseAssetLinkType|Images"
+msgstr ""
+
msgid "ReleaseAssetLinkType|Other"
msgstr ""
msgid "ReleaseAssetLinkType|Package"
msgstr ""
+msgid "ReleaseAssetLinkType|Packages"
+msgstr ""
+
msgid "ReleaseAssetLinkType|Runbook"
msgstr ""
+msgid "ReleaseAssetLinkType|Runbooks"
+msgstr ""
+
msgid "Releases"
msgstr ""
@@ -18204,6 +18815,9 @@ msgstr ""
msgid "Releases documentation"
msgstr ""
+msgid "Releases|New Release"
+msgstr ""
+
msgid "Release|Something went wrong while getting the release details"
msgstr ""
@@ -18276,6 +18890,9 @@ msgstr ""
msgid "Remove fork relationship"
msgstr ""
+msgid "Remove from batch"
+msgstr ""
+
msgid "Remove from board"
msgstr ""
@@ -18465,6 +19082,12 @@ msgstr ""
msgid "Replication"
msgstr ""
+msgid "Replication enabled"
+msgstr ""
+
+msgid "Replication paused"
+msgstr ""
+
msgid "Reply by email"
msgstr ""
@@ -18492,15 +19115,15 @@ msgstr ""
msgid "Reported %{timeAgo} by %{reportedBy}"
msgstr ""
-msgid "Reported Resource Changes: %{addNum} to add, %{changeNum} to change, %{deleteNum} to delete"
-msgstr ""
-
msgid "Reporter"
msgstr ""
msgid "Reporting"
msgstr ""
+msgid "Reports"
+msgstr ""
+
msgid "Reports|%{combinedString} and %{resolvedString}"
msgstr ""
@@ -18539,6 +19162,9 @@ msgstr ""
msgid "Reports|Failure"
msgstr ""
+msgid "Reports|Identifier"
+msgstr ""
+
msgid "Reports|Metrics reports are loading"
msgstr ""
@@ -18551,6 +19177,9 @@ msgstr ""
msgid "Reports|Metrics reports failed loading results"
msgstr ""
+msgid "Reports|Scanner"
+msgstr ""
+
msgid "Reports|Severity"
msgstr ""
@@ -18599,15 +19228,33 @@ msgstr ""
msgid "Repository cleanup has started. You will receive an email once the cleanup operation is complete."
msgstr ""
+msgid "Repository files count over the limit"
+msgstr ""
+
+msgid "Repository has an invalid default branch name."
+msgstr ""
+
+msgid "Repository has more than one branch."
+msgstr ""
+
msgid "Repository has no locks."
msgstr ""
+msgid "Repository has tags."
+msgstr ""
+
msgid "Repository maintenance"
msgstr ""
msgid "Repository mirroring"
msgstr ""
+msgid "Repository must contain at least 1 file."
+msgstr ""
+
+msgid "Repository size is above the limit."
+msgstr ""
+
msgid "Repository static objects"
msgstr ""
@@ -18617,7 +19264,7 @@ msgstr ""
msgid "Repository sync capacity"
msgstr ""
-msgid "Repository: %{counter_repositories} / Wikis: %{counter_wikis} / Build Artifacts: %{counter_build_artifacts} / LFS: %{counter_lfs_objects}"
+msgid "Repository: %{counter_repositories} / Wikis: %{counter_wikis} / Build Artifacts: %{counter_build_artifacts} / LFS: %{counter_lfs_objects} / Snippets: %{counter_snippets}"
msgstr ""
msgid "RepositorySettingsAccessLevel|Select"
@@ -18629,6 +19276,9 @@ msgstr ""
msgid "Request Headers"
msgstr ""
+msgid "Request details"
+msgstr ""
+
msgid "Request parameter %{param} is missing."
msgstr ""
@@ -18735,6 +19385,9 @@ msgstr ""
msgid "Reset template"
msgstr ""
+msgid "Reset to project defaults"
+msgstr ""
+
msgid "Resetting the authorization key for this project will require updating the authorization key in every alert source it is enabled in."
msgstr ""
@@ -18831,9 +19484,6 @@ msgstr ""
msgid "Resume"
msgstr ""
-msgid "Resume replication"
-msgstr ""
-
msgid "Resync"
msgstr ""
@@ -19456,6 +20106,9 @@ msgstr ""
msgid "SecurityReports|False positive"
msgstr ""
+msgid "SecurityReports|Group Security Dashboard"
+msgstr ""
+
msgid "SecurityReports|Hide dismissed"
msgstr ""
@@ -19510,10 +20163,13 @@ msgstr ""
msgid "SecurityReports|Remove project from dashboard"
msgstr ""
-msgid "SecurityReports|Report type"
+msgid "SecurityReports|Return to dashboard"
msgstr ""
-msgid "SecurityReports|Return to dashboard"
+msgid "SecurityReports|Scan details"
+msgstr ""
+
+msgid "SecurityReports|Scanner type"
msgstr ""
msgid "SecurityReports|Security Dashboard"
@@ -19570,6 +20226,9 @@ msgstr ""
msgid "SecurityReports|There was an error while generating the report."
msgstr ""
+msgid "SecurityReports|Unable to add %{invalidProjectsMessage}"
+msgstr ""
+
msgid "SecurityReports|Unable to add %{invalidProjects}"
msgstr ""
@@ -19684,6 +20343,9 @@ msgstr ""
msgid "Select due date"
msgstr ""
+msgid "Select file"
+msgstr ""
+
msgid "Select group or project"
msgstr ""
@@ -19747,9 +20409,6 @@ msgstr ""
msgid "Select the branch you want to set as the default for this project. All merge requests and commits will automatically be made against this branch unless you specify a different one."
msgstr ""
-msgid "Select the configured storage available for new repositories to be placed on."
-msgstr ""
-
msgid "Select the custom project template source group."
msgstr ""
@@ -19942,12 +20601,6 @@ msgstr ""
msgid "Service Desk is enabled but not yet active"
msgstr ""
-msgid "Service Desk is off"
-msgstr ""
-
-msgid "Service Desk is on"
-msgstr ""
-
msgid "Service Templates"
msgstr ""
@@ -20035,6 +20688,15 @@ msgstr ""
msgid "Set the milestone to %{milestone_reference}."
msgstr ""
+msgid "Set the number of concurrent requests this secondary node will make to the primary node while backfilling."
+msgstr ""
+
+msgid "Set the synchronization and verification capacity for the secondary node."
+msgstr ""
+
+msgid "Set the timeout in seconds to send a secondary node status to the primary and IPs allowed for the secondary nodes."
+msgstr ""
+
msgid "Set time estimate"
msgstr ""
@@ -20074,6 +20736,9 @@ msgstr ""
msgid "Set weight to %{weight}."
msgstr ""
+msgid "Set what should be replicated by choosing specific projects or groups by the secondary node."
+msgstr ""
+
msgid "SetPasswordToCloneLink|set a password"
msgstr ""
@@ -20200,6 +20865,9 @@ msgstr ""
msgid "Show complete raw log"
msgstr ""
+msgid "Show details"
+msgstr ""
+
msgid "Show file browser"
msgstr ""
@@ -20209,10 +20877,13 @@ msgstr ""
msgid "Show latest version"
msgstr ""
+msgid "Show list"
+msgstr ""
+
msgid "Show me everything"
msgstr ""
-msgid "Show me how"
+msgid "Show me how to add a pipeline"
msgstr ""
msgid "Show me more advanced stuff"
@@ -20261,9 +20932,6 @@ msgstr ""
msgid "Showing all issues"
msgstr ""
-msgid "Showing all labels"
-msgstr ""
-
msgid "Showing last %{size} of log -"
msgstr ""
@@ -20375,9 +21043,6 @@ msgstr ""
msgid "Skip outdated deployment jobs"
msgstr ""
-msgid "Skip this for now"
-msgstr ""
-
msgid "Skipped"
msgstr ""
@@ -20522,6 +21187,9 @@ msgstr ""
msgid "Something went wrong while adding your award. Please try again."
msgstr ""
+msgid "Something went wrong while applying the batch of suggestions. Please try again."
+msgstr ""
+
msgid "Something went wrong while applying the suggestion. Please try again."
msgstr ""
@@ -20624,10 +21292,13 @@ msgstr ""
msgid "Something went wrong while updating a requirement."
msgstr ""
+msgid "Something went wrong while updating assignees"
+msgstr ""
+
msgid "Something went wrong while updating your list settings"
msgstr ""
-msgid "Something went wrong with your automatic subscription renewal"
+msgid "Something went wrong with your automatic subscription renewal."
msgstr ""
msgid "Something went wrong, unable to add %{project} to dashboard"
@@ -20645,6 +21316,9 @@ msgstr ""
msgid "Something went wrong, unable to search projects"
msgstr ""
+msgid "Something went wrong."
+msgstr ""
+
msgid "Something went wrong. Please try again."
msgstr ""
@@ -20828,6 +21502,9 @@ msgstr ""
msgid "Source code"
msgstr ""
+msgid "Source code (%{fileExtension})"
+msgstr ""
+
msgid "Source is not available"
msgstr ""
@@ -20912,6 +21589,9 @@ msgstr ""
msgid "Stack trace"
msgstr ""
+msgid "Stacktrace snippet"
+msgstr ""
+
msgid "Stage"
msgstr ""
@@ -21017,6 +21697,9 @@ msgstr ""
msgid "Start thread & reopen %{noteable_name}"
msgstr ""
+msgid "Start using Directed Acyclic Graphs (DAG)"
+msgstr ""
+
msgid "Start your Free Gold Trial"
msgstr ""
@@ -21479,6 +22162,21 @@ msgstr ""
msgid "SuggestedColors|Very pale orange"
msgstr ""
+msgid "Suggestion is not applicable as the suggestion was not found."
+msgstr ""
+
+msgid "Suggestions are not applicable as one or more suggestions were not found."
+msgstr ""
+
+msgid "Suggestions are not applicable as their lines cannot overlap."
+msgstr ""
+
+msgid "Suggestions must all be on the same branch."
+msgstr ""
+
+msgid "Suggestions that change line count can't be added to batches, yet."
+msgstr ""
+
msgid "Suggestions:"
msgstr ""
@@ -21521,9 +22219,6 @@ msgstr ""
msgid "Synchronization disabled"
msgstr ""
-msgid "Synchronization of container repositories is disabled."
-msgstr ""
-
msgid "System"
msgstr ""
@@ -21704,6 +22399,34 @@ msgstr ""
msgid "Terms of Service and Privacy Policy"
msgstr ""
+msgid "Terraform|%{number} Terraform report failed to generate"
+msgid_plural "Terraform|%{number} Terraform reports failed to generate"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Terraform|%{number} Terraform report was generated in your pipelines"
+msgid_plural "Terraform|%{number} Terraform reports were generated in your pipelines"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Terraform|A Terraform report failed to generate."
+msgstr ""
+
+msgid "Terraform|A Terraform report was generated in your pipelines."
+msgstr ""
+
+msgid "Terraform|Generating the report caused an error."
+msgstr ""
+
+msgid "Terraform|Reported Resource Changes: %{addNum} to add, %{changeNum} to change, %{deleteNum} to delete"
+msgstr ""
+
+msgid "Terraform|The Terraform report %{name} failed to generate."
+msgstr ""
+
+msgid "Terraform|The Terraform report %{name} was generated in your pipelines."
+msgstr ""
+
msgid "Test"
msgstr ""
@@ -21730,7 +22453,7 @@ msgstr ""
msgid "TestHooks|Ensure the project has CI pipelines."
msgstr ""
-msgid "TestHooks|Ensure the project has at least one commit."
+msgid "TestHooks|Ensure the project has deployments."
msgstr ""
msgid "TestHooks|Ensure the project has issues."
@@ -21831,7 +22554,7 @@ msgstr ""
msgid "The Prometheus server responded with \"bad request\". Please check your queries are correct and are supported in your Prometheus version. %{documentationLink}"
msgstr ""
-msgid "The URL defined on the primary node that secondary nodes should use to contact it. Defaults to URL"
+msgid "The URL defined on the primary node that secondary nodes should use to contact it."
msgstr ""
msgid "The URL to use for connecting to Elasticsearch. Use a comma-separated list to support clustering (e.g., \"http://localhost:9200, http://localhost:9201\")."
@@ -21840,6 +22563,9 @@ msgstr ""
msgid "The X509 Certificate to use when mutual TLS is required to communicate with the external authorization service. If left blank, the server certificate is still validated when accessing over HTTPS."
msgstr ""
+msgid "The above settings apply to all projects with the selected compliance framework(s)."
+msgstr ""
+
msgid "The amount of seconds after which a request to get a secondary node status will time out."
msgstr ""
@@ -22139,9 +22865,6 @@ msgstr ""
msgid "The total stage shows the time it takes between creating an issue and deploying the code to production. The data will be automatically added once you have completed the full idea to production cycle."
msgstr ""
-msgid "The unique identifier for the Geo node. Must match %{geoNodeName} if it is set in gitlab.rb, otherwise it must match %{externalUrl} with a trailing slash"
-msgstr ""
-
msgid "The update action will time out after %{number_of_minutes} minutes. For big repositories, use a clone/push combination."
msgstr ""
@@ -22166,9 +22889,6 @@ msgstr ""
msgid "The user you are trying to deactivate has been active in the past %{minimum_inactive_days} days and cannot be deactivated"
msgstr ""
-msgid "The user-facing URL of the Geo node"
-msgstr ""
-
msgid "The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6."
msgstr ""
@@ -22259,6 +22979,9 @@ msgstr ""
msgid "There was a problem fetching project branches."
msgstr ""
+msgid "There was a problem fetching project tags."
+msgstr ""
+
msgid "There was a problem fetching project users."
msgstr ""
@@ -22310,6 +23033,9 @@ msgstr ""
msgid "There was an error fetching the %{replicableType}"
msgstr ""
+msgid "There was an error fetching the Geo Settings"
+msgstr ""
+
msgid "There was an error fetching the Node's Groups"
msgstr ""
@@ -22430,6 +23156,12 @@ msgstr ""
msgid "This %{issuable} is locked. Only <strong>project members</strong> can comment."
msgstr ""
+msgid "This %{noteableTypeText} is %{confidentialLinkStart}confidential%{linkEnd} and %{lockedLinkStart}locked%{linkEnd}."
+msgstr ""
+
+msgid "This %{noteableTypeText} is locked."
+msgstr ""
+
msgid "This %{viewer} could not be displayed because %{reason}. You can %{options} instead."
msgstr ""
@@ -22448,6 +23180,9 @@ msgstr ""
msgid "This action can lead to data loss. To prevent accidental actions we ask you to confirm your intention."
msgstr ""
+msgid "This also resolves all related threads"
+msgstr ""
+
msgid "This also resolves the discussion"
msgstr ""
@@ -22538,6 +23273,9 @@ msgstr ""
msgid "This epic does not exist or you don't have sufficient permission."
msgstr ""
+msgid "This feature is currently in beta. We invite you to %{linkStart}give feedback%{linkEnd}."
+msgstr ""
+
msgid "This feature requires local storage to be enabled"
msgstr ""
@@ -22571,7 +23309,7 @@ msgstr ""
msgid "This is a Work in Progress"
msgstr ""
-msgid "This is a confidential issue."
+msgid "This is a confidential %{noteableTypeText}."
msgstr ""
msgid "This is a delayed job to run in %{remainingTime}"
@@ -22598,9 +23336,6 @@ msgstr ""
msgid "This is your current session"
msgstr ""
-msgid "This issue is %{confidentialLinkStart}confidential%{linkEnd} and %{lockedLinkStart}locked%{linkEnd}."
-msgstr ""
-
msgid "This issue is confidential"
msgstr ""
@@ -22610,9 +23345,6 @@ msgstr ""
msgid "This issue is in a child epic of the filtered epic"
msgstr ""
-msgid "This issue is locked."
-msgstr ""
-
msgid "This job depends on other jobs with expired/erased artifacts: %{invalid_dependencies}"
msgstr ""
@@ -22712,6 +23444,9 @@ msgstr ""
msgid "This license has already expired."
msgstr ""
+msgid "This link points to external content"
+msgstr ""
+
msgid "This may expose confidential information as the selected fork is in another namespace that can have other members."
msgstr ""
@@ -22739,6 +23474,9 @@ msgstr ""
msgid "This page will be removed in a future release."
msgstr ""
+msgid "This pipeline does not use the %{codeStart}needs%{codeEnd} keyword and can't be represented as a directed acyclic graph."
+msgstr ""
+
msgid "This pipeline makes use of a predefined CI/CD configuration enabled by %{strongStart}Auto DevOps.%{strongEnd}"
msgstr ""
@@ -22967,6 +23705,9 @@ msgstr ""
msgid "Time from last commit to merge"
msgstr ""
+msgid "Time in seconds"
+msgstr ""
+
msgid "Time in seconds GitLab will wait for a response from the external service. When the service does not respond in time, access will be denied."
msgstr ""
@@ -23388,9 +24129,15 @@ msgstr ""
msgid "Total artifacts size: %{total_size}"
msgstr ""
+msgid "Total cores (CPUs)"
+msgstr ""
+
msgid "Total issues"
msgstr ""
+msgid "Total memory (GB)"
+msgstr ""
+
msgid "Total test time for all commits/merges"
msgstr ""
@@ -23592,6 +24339,9 @@ msgstr ""
msgid "U2F only works with HTTPS-enabled websites. Contact your administrator for more details."
msgstr ""
+msgid "UI Development Kit"
+msgstr ""
+
msgid "URL"
msgstr ""
@@ -23664,9 +24414,6 @@ msgstr ""
msgid "Unable to load the merge request widget. Try reloading the page."
msgstr ""
-msgid "Unable to process group import file"
-msgstr ""
-
msgid "Unable to resolve"
msgstr ""
@@ -23682,6 +24429,9 @@ msgstr ""
msgid "Unable to sign you in to the group with SAML due to \"%{reason}\""
msgstr ""
+msgid "Unable to suggest a path. Please refresh and try again."
+msgstr ""
+
msgid "Unable to update label prioritization at this time"
msgstr ""
@@ -23697,6 +24447,9 @@ msgstr ""
msgid "Unarchiving the project will restore people's ability to make changes to it. The repository can be committed to, and issues, comments, and other entities can be created. %{strong_start}Once active, this project shows up in the search and on the dashboard.%{strong_end}"
msgstr ""
+msgid "Unassign from commenting user"
+msgstr ""
+
msgid "Unblock"
msgstr ""
@@ -23772,6 +24525,9 @@ msgstr ""
msgid "Unreachable"
msgstr ""
+msgid "Unrecognized cluster type"
+msgstr ""
+
msgid "Unresolve"
msgstr ""
@@ -23832,6 +24588,9 @@ msgstr ""
msgid "Unsubscribes from this %{quick_action_target}."
msgstr ""
+msgid "Unsupported todo type passed. Supported todo types are: %{todo_types}"
+msgstr ""
+
msgid "Until"
msgstr ""
@@ -23859,6 +24618,9 @@ msgstr ""
msgid "Update approval rule"
msgstr ""
+msgid "Update approvers"
+msgstr ""
+
msgid "Update failed"
msgstr ""
@@ -23868,6 +24630,9 @@ msgstr ""
msgid "Update it"
msgstr ""
+msgid "Update iteration"
+msgstr ""
+
msgid "Update now"
msgstr ""
@@ -23913,7 +24678,7 @@ msgstr ""
msgid "Updated at"
msgstr ""
-msgid "Updated to"
+msgid "Updated to %{linkStart}chart v%{linkEnd}"
msgstr ""
msgid "Updating"
@@ -23943,6 +24708,9 @@ msgstr ""
msgid "Upload CSV file"
msgstr ""
+msgid "Upload License"
+msgstr ""
+
msgid "Upload New File"
msgstr ""
@@ -24024,16 +24792,19 @@ msgstr ""
msgid "UsageQuota|Pipelines"
msgstr ""
+msgid "UsageQuota|Purchase more storage"
+msgstr ""
+
msgid "UsageQuota|Repositories"
msgstr ""
msgid "UsageQuota|Repository"
msgstr ""
-msgid "UsageQuota|Storage"
+msgid "UsageQuota|Snippets"
msgstr ""
-msgid "UsageQuota|Storage usage:"
+msgid "UsageQuota|Storage"
msgstr ""
msgid "UsageQuota|This namespace has no projects which use shared runners"
@@ -24066,6 +24837,12 @@ msgstr ""
msgid "UsageQuota|Wikis"
msgstr ""
+msgid "UsageQuota|You used: %{usage} %{limit}"
+msgstr ""
+
+msgid "UsageQuota|out of %{formattedLimit} of your namespace storage"
+msgstr ""
+
msgid "Use %{code_start}::%{code_end} to create a %{link_start}scoped label set%{link_end} (eg. %{code_start}priority::1%{code_end})"
msgstr ""
@@ -24132,6 +24909,9 @@ msgstr ""
msgid "User IDs"
msgstr ""
+msgid "User Lists can only be created and modified with %{linkStart}the API%{linkEnd}"
+msgstr ""
+
msgid "User OAuth applications"
msgstr ""
@@ -24156,6 +24936,9 @@ msgstr ""
msgid "User key was successfully removed."
msgstr ""
+msgid "User list %{name} will be removed. Are you sure?"
+msgstr ""
+
msgid "User map"
msgstr ""
@@ -24177,148 +24960,10 @@ msgstr ""
msgid "User was successfully updated."
msgstr ""
-msgid "UserOnboardingTour|%{activeTour}/%{totalTours}"
-msgstr ""
-
-msgid "UserOnboardingTour|%{completed}/%{total} steps completed"
-msgstr ""
-
-msgid "UserOnboardingTour|%{emphasisStart}Well done!%{emphasisEnd}%{lineBreak}%{lineBreak}That's it for our guided tour, congratulations for making it all the way to the end!%{lineBreak}%{lineBreak}We hope this gave you a good overview of GitLab and how it can help you. We'll now show you how to create your own project and invite your colleagues."
-msgstr ""
-
-msgid "UserOnboardingTour|Adding other members to a project is done through Project Settings. Click on %{emphasisStart}Settings%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Alright, that's it for Commits. Let's take a look at the %{emphasisStart}Branches%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Awesome! Now click on %{emphasisStart}Members%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Click on one of the %{emphasisStart}Compare%{emphasisEnd} buttons to compare a branch to master."
-msgstr ""
-
-msgid "UserOnboardingTour|Click on one of the %{emphasisStart}pipeline IDs%{emphasisEnd} to see the details of a pipeline."
-msgstr ""
-
-msgid "UserOnboardingTour|Click to open the latest commit to see its details."
-msgstr ""
-
-msgid "UserOnboardingTour|Close 'Learn GitLab'"
-msgstr ""
-
-msgid "UserOnboardingTour|Commits are shown in chronological order and can be filtered by the commit message or by the branch."
+msgid "UserList|Delete %{name}?"
msgstr ""
-msgid "UserOnboardingTour|Create a project"
-msgstr ""
-
-msgid "UserOnboardingTour|Exit 'Learn GitLab'"
-msgstr ""
-
-msgid "UserOnboardingTour|Got it"
-msgstr ""
-
-msgid "UserOnboardingTour|Great job! %{clapHands} We hope the tour was helpful and that you learned how to use GitLab.%{lineBreak}%{lineBreak}We'd love to get your feedback on this tour.%{lineBreak}%{lineBreak}%{emphasisStart}How helpful would you say this guided tour was?%{emphasisEnd}%{lineBreak}%{lineBreak}"
-msgstr ""
-
-msgid "UserOnboardingTour|Guided GitLab Tour"
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can compare the changes of this branch to another one. Changes are divided by files so that it's easier to see what was changed where."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can create a project from scratch, start with a template or import a repository from other platforms. Whatever you choose, we'll guide you through the process.%{lineBreak}%{lineBreak}Fill in your new project information and click on %{emphasisStart}Create Project%{emphasisEnd} to progress to the next step."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can see the breakdown of the pipelines: its stages and jobs in each of the stages and their status.%{lineBreak}%{lineBreak}Our CI/CD pipelines are quite complex, most of our users have fewer and simpler pipelines."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can see the current members of the project (just you at the moment) and invite new members.%{lineBreak}%{lineBreak}You can invite multiple members at once (existing GitLab users or invite by email) and you can also set their roles and permissions.%{lineBreak}%{lineBreak}Add a few members and click on %{emphasisStart}Add to project%{emphasisEnd} to complete this step."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can see what changes were made with this commit, on what branch and if there's a related merge request. The status of the pipeline will also show up if CI/CD is set up.%{lineBreak}%{lineBreak}You can also comment on the lines of code that were changed and start a discussion with your colleagues!"
-msgstr ""
-
-msgid "UserOnboardingTour|Here's an overview of branches in the %{emphasisStart}%{projectName}%{emphasisEnd} project. They're split into Active and Stale.%{lineBreak}%{lineBreak}From here, you can create a new merge request from a branch, or compare the branch to any other branch in the project. By default, it will compare it to the master branch."
-msgstr ""
-
-msgid "UserOnboardingTour|Invite colleagues"
-msgstr ""
-
-msgid "UserOnboardingTour|Issues are great for communicating and keeping track of progress in GitLab. These are all issues that are open in the %{emphasisStart}%{projectName}%{emphasisEnd}.%{lineBreak}%{lineBreak}You can help us improve GitLab by contributing work to issues that are labeled <span class=\"badge color-label accept-mr-label\">Accepting merge requests</span>.%{lineBreak}%{lineBreak}This list can be filtered by labels, milestones, assignees, authors... We'll show you how it looks when the list is filtered by a label."
-msgstr ""
-
-msgid "UserOnboardingTour|Learn GitLab"
-msgstr ""
-
-msgid "UserOnboardingTour|Let's take a closer look at a merge request. Click on the title of one."
-msgstr ""
-
-msgid "UserOnboardingTour|Let's take a closer look at all the commits. Click on %{emphasisStart}Commits%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Let's take a closer look at the repository of this project. Click on %{emphasisStart}Repository%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|No thanks"
-msgstr ""
-
-msgid "UserOnboardingTour|Ok, let's go"
-msgstr ""
-
-msgid "UserOnboardingTour|Ok, show me"
-msgstr ""
-
-msgid "UserOnboardingTour|Open one of the issues by clicking on its title."
-msgstr ""
-
-msgid "UserOnboardingTour|Restart this step"
-msgstr ""
-
-msgid "UserOnboardingTour|Skip this step"
-msgstr ""
-
-msgid "UserOnboardingTour|Sweet! Your project was created and is ready to be used.%{lineBreak}%{lineBreak}You can start adding files to the repository or clone it. One last thing we want to show you is how to invite your colleagues to your new project."
-msgstr ""
-
-msgid "UserOnboardingTour|Take a look. Here's a nifty menu for quickly creating issues, merge requests, snippets, projects and groups. Click on it and select \"New project\" from the \"GitLab\" section to get started."
-msgstr ""
-
-msgid "UserOnboardingTour|Thanks for taking the guided tour. Remember, if you want to go through it again, you can start %{emphasisStart}Learn GitLab%{emphasisEnd} in the help menu on the top right."
-msgstr ""
-
-msgid "UserOnboardingTour|Thanks for the feedback! %{thumbsUp}"
-msgstr ""
-
-msgid "UserOnboardingTour|That's it for issues. Let'st take a look at %{emphasisStart}Merge Requests%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|That's it for merge requests. Now for the final part of this guided tour - the %{emphasisStart}CI/CD%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|That's it for the Repository. Let's take a look at the %{emphasisStart}Issues%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|The structure of this page is very similar to issues. Status, description, discussion and the sidebar are all here.%{lineBreak}%{lineBreak}But take a look below the description and you'll notice that there's more information about the merge request, the CI/CD pipeline and the options for approving it.%{lineBreak}%{lineBreak}Alongside the discussion you can also see more information about commits in this merge request, the status of pipelines and review all changes that were made."
-msgstr ""
-
-msgid "UserOnboardingTour|There's a lot of information here but don't worry, we'll go through it.%{lineBreak}%{lineBreak}On the top you can see the status of the issue and when it was opened and by whom. Directly below it is the issue description and below that are other %{emphasisStart}related issues%{emphasisEnd} and %{emphasisStart}merge requests%{emphasisEnd} (if any). Then below that is the %{emphasisStart}discussion%{emphasisEnd}, that's where most of the communication happens.%{lineBreak}%{lineBreak}On the right, there's a sidebar where you can view/change the %{emphasisStart}assignee, milestone, due date, labels, weight%{emphasisEnd}, etc."
-msgstr ""
-
-msgid "UserOnboardingTour|These are all the CI/CD pipelines we have for our %{emphasisStart}%{projectName}%{emphasisEnd} project.%{lineBreak}%{lineBreak}Here you can see the status of each pipeline, for what commit it's running for, its stages and the status for them."
-msgstr ""
-
-msgid "UserOnboardingTour|These are all the issues that are available for community contributions. Let's take a closer look at one of them."
-msgstr ""
-
-msgid "UserOnboardingTour|This is an overview of all merge requests in this project. Similarly to the issues overview it can be filtered down by things like labels, milestones, authors, assignees, etc."
-msgstr ""
-
-msgid "UserOnboardingTour|This is the repository for the %{emphasisStart}%{projectName}%{emphasisEnd} project. All our code is stored here. Feel free to explore and take a closer look at folders and files.%{lineBreak}%{lineBreak}Above the file structure you can see the latest commit, who the author is and the status of the CI/CD pipeline.%{lineBreak}%{lineBreak}If you scroll down below the file structure, you'll find the Readme of this project. This is defined in the README.md file at the root of the repository."
-msgstr ""
-
-msgid "UserOnboardingTour|Welcome to the project overview of the %{emphasisStart}%{projectName}%{emphasisEnd} project. This is the project that we use to work on GitLab. At first, a project seems like a simple repository, but at GitLab, a project is so much more.%{lineBreak}%{lineBreak}You can create projects for hosting your codebase, use it as an issue tracker, collaborate on code, and continuously build, test, and deploy your app with built-in GitLab CI/CD."
+msgid "UserList|created %{timeago}"
msgstr ""
msgid "UserProfile|Activity"
@@ -24471,6 +25116,9 @@ msgstr ""
msgid "UsersSelect|Unassigned"
msgstr ""
+msgid "Using %{codeStart}needs%{codeEnd} allows jobs to run before their stage is reached, as soon as their individual dependencies are met, which speeds up your pipelines."
+msgstr ""
+
msgid "Using %{code_start}::%{code_end} denotes a %{link_start}scoped label set%{link_end}"
msgstr ""
@@ -24555,9 +25203,6 @@ msgstr ""
msgid "Versions"
msgstr ""
-msgid "Very helpful"
-msgstr ""
-
msgid "View Documentation"
msgstr ""
@@ -24706,6 +25351,9 @@ msgstr ""
msgid "VisibilityLevel|Unknown"
msgstr ""
+msgid "Visit settings page"
+msgstr ""
+
msgid "VisualReviewApp|%{stepStart}Step 1%{stepEnd}. Copy the following script:"
msgstr ""
@@ -24799,6 +25447,9 @@ msgstr ""
msgid "VulnerabilityManagement|Something went wrong while trying to delete the comment. Please try again later."
msgstr ""
+msgid "VulnerabilityManagement|Something went wrong while trying to refresh the vulnerability. Please try again later."
+msgstr ""
+
msgid "VulnerabilityManagement|Something went wrong while trying to retrieve the vulnerability history. Please try again later."
msgstr ""
@@ -24841,6 +25492,9 @@ msgstr ""
msgid "Vulnerability|Class"
msgstr ""
+msgid "Vulnerability|Crash Address"
+msgstr ""
+
msgid "Vulnerability|Description"
msgstr ""
@@ -24916,9 +25570,6 @@ msgstr ""
msgid "We couldn't reach the Prometheus server. Either the server no longer exists or the configuration details need updating."
msgstr ""
-msgid "We created a short guided tour that will help you learn the basics of GitLab and how it will help you be better at your job. It should only take a couple of minutes. You will be guided by two types of helpers, best recognized by their color."
-msgstr ""
-
msgid "We detected potential spam in the %{humanized_resource_name}. Please solve the reCAPTCHA to proceed."
msgstr ""
@@ -24952,6 +25603,9 @@ msgstr ""
msgid "We've found no vulnerabilities"
msgstr ""
+msgid "Web Application Firewall"
+msgstr ""
+
msgid "Web IDE"
msgstr ""
@@ -25012,9 +25666,6 @@ msgstr ""
msgid "Welcome to GitLab.com<br>@%{name}!"
msgstr ""
-msgid "Welcome to the Guided GitLab Tour"
-msgstr ""
-
msgid "Welcome to the guided GitLab tour"
msgstr ""
@@ -25062,9 +25713,6 @@ msgstr ""
msgid "While it's rare to have no vulnerabilities, it can happen. In any event, we ask that you please double check your settings to make sure you've set up your dashboard correctly."
msgstr ""
-msgid "White helpers give contextual information."
-msgstr ""
-
msgid "Who can be an approver?"
msgstr ""
@@ -25110,12 +25758,18 @@ msgstr ""
msgid "WikiEmptyIssueMessage|Suggest wiki improvement"
msgstr ""
+msgid "WikiEmptyIssueMessage|You must be a group member in order to add wiki pages. If you have suggestions for how to improve the wiki for this group, consider opening an issue in the %{issues_link}."
+msgstr ""
+
msgid "WikiEmptyIssueMessage|You must be a project member in order to add wiki pages. If you have suggestions for how to improve the wiki for this project, consider opening an issue in the %{issues_link}."
msgstr ""
msgid "WikiEmptyIssueMessage|issue tracker"
msgstr ""
+msgid "WikiEmpty|A wiki is where you can store all the details about your group. This can include why you've created it, its principles, how to use it, and so on."
+msgstr ""
+
msgid "WikiEmpty|A wiki is where you can store all the details about your project. This can include why you've created it, its principles, how to use it, and so on."
msgstr ""
@@ -25125,12 +25779,21 @@ msgstr ""
msgid "WikiEmpty|Suggest wiki improvement"
msgstr ""
+msgid "WikiEmpty|The wiki lets you write documentation for your group"
+msgstr ""
+
msgid "WikiEmpty|The wiki lets you write documentation for your project"
msgstr ""
+msgid "WikiEmpty|This group has no wiki pages"
+msgstr ""
+
msgid "WikiEmpty|This project has no wiki pages"
msgstr ""
+msgid "WikiEmpty|You must be a group member in order to add wiki pages."
+msgstr ""
+
msgid "WikiEmpty|You must be a project member in order to add wiki pages."
msgstr ""
@@ -25407,13 +26070,13 @@ msgstr ""
msgid "You can now export your security dashboard to a CSV report."
msgstr ""
-msgid "You can now submit a merge request to get this change into the original branch."
+msgid "You can now manage alert endpoint configuration in the Alerts section on the Operations settings page. Fields on this page have been deprecated."
msgstr ""
-msgid "You can now submit a merge request to get this change into the original project."
+msgid "You can now submit a merge request to get this change into the original branch."
msgstr ""
-msgid "You can only add files when you are on a branch"
+msgid "You can now submit a merge request to get this change into the original project."
msgstr ""
msgid "You can only edit files when you are on a branch"
@@ -25635,7 +26298,7 @@ msgstr ""
msgid "You need to upload a Google Takeout archive."
msgstr ""
-msgid "You reached %{usage_in_percent} of %{namespace_name}'s capacity (%{used_storage} of %{storage_limit})"
+msgid "You reached %{usage_in_percent} of %{namespace_name}'s storage capacity (%{used_storage} of %{storage_limit})"
msgstr ""
msgid "You tried to fork %{link_to_the_project} but it failed for the following reason:"
@@ -25938,13 +26601,13 @@ msgstr ""
msgid "Your subscription expired!"
msgstr ""
-msgid "Your subscription has been downgraded"
+msgid "Your subscription has been downgraded."
msgstr ""
-msgid "Your subscription will automatically renew in %{remaining_days}"
+msgid "Your subscription will automatically renew in %{remaining_days}."
msgstr ""
-msgid "Your subscription will expire in %{remaining_days}"
+msgid "Your subscription will expire in %{remaining_days}."
msgstr ""
msgid "Zoom meeting added"
@@ -26024,9 +26687,6 @@ msgstr ""
msgid "branch name"
msgstr ""
-msgid "build pipeline reference mismatch"
-msgstr ""
-
msgid "by"
msgstr ""
@@ -26045,6 +26705,9 @@ msgstr ""
msgid "cannot block others"
msgstr ""
+msgid "cannot contain HTML/XML tags, including any word between angle brackets (<,>)."
+msgstr ""
+
msgid "cannot include leading slash or directory traversal."
msgstr ""
@@ -26075,55 +26738,6 @@ msgstr ""
msgid "ciReport|%{remainingPackagesCount} more"
msgstr ""
-msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability"
-msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability for the source branch only"
-msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities for the source branch only"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerability"
-msgid_plural "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerabilities"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new vulnerability"
-msgid_plural "ciReport|%{reportType} %{status} detected %{newCount} new vulnerabilities"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities for the source branch only"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{fixedCount} fixed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} vulnerability for the source branch only"
-msgid_plural "ciReport|%{reportType} %{status} detected %{newCount} vulnerabilities for the source branch only"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "ciReport|%{reportType} %{status} detected no new vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected no vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected no vulnerabilities for the source branch only"
-msgstr ""
-
msgid "ciReport|%{reportType} is loading"
msgstr ""
@@ -26142,7 +26756,7 @@ msgstr ""
msgid "ciReport|All projects"
msgstr ""
-msgid "ciReport|All report types"
+msgid "ciReport|All scanner types"
msgstr ""
msgid "ciReport|All severities"
@@ -26166,6 +26780,9 @@ msgstr ""
msgid "ciReport|Container scanning detects known vulnerabilities in your docker images."
msgstr ""
+msgid "ciReport|Coverage Fuzzing"
+msgstr ""
+
msgid "ciReport|Create a merge request to implement this solution, or download and apply the patch manually."
msgstr ""
@@ -26324,6 +26941,9 @@ msgstr ""
msgid "customize"
msgstr ""
+msgid "data"
+msgstr ""
+
msgid "date must not be after 9999-12-31"
msgstr ""
@@ -26394,6 +27014,9 @@ msgstr ""
msgid "entries cannot contain HTML tags"
msgstr ""
+msgid "epic"
+msgstr ""
+
msgid "error"
msgstr ""
@@ -26415,9 +27038,6 @@ msgstr ""
msgid "expires on %{timebox_due_date}"
msgstr ""
-msgid "external_url"
-msgstr ""
-
msgid "failed"
msgstr ""
@@ -26453,9 +27073,6 @@ msgstr ""
msgid "from"
msgstr ""
-msgid "geo_node_name"
-msgstr ""
-
msgid "group"
msgstr ""
@@ -26477,9 +27094,6 @@ msgstr ""
msgid "https://your-bitbucket-server"
msgstr ""
-msgid "image"
-msgstr ""
-
msgid "image diff"
msgstr ""
@@ -26623,6 +27237,9 @@ msgstr ""
msgid "load it anyway"
msgstr ""
+msgid "loading"
+msgstr ""
+
msgid "locked by %{path_lock_user_name} %{created_at}"
msgstr ""
@@ -26751,9 +27368,6 @@ msgstr ""
msgid "mrWidget|Deployment statistics are not available currently"
msgstr ""
-msgid "mrWidget|Detect issues before deployment with a CI pipeline that continuously tests your code. We created a quick guide that will show you how to create one. Make your code more secure and more robust in just a minute."
-msgstr ""
-
msgid "mrWidget|Did not close"
msgstr ""
@@ -26916,6 +27530,9 @@ msgstr ""
msgid "mrWidget|To approve this merge request, please enter your password. This project requires all approvals to be authenticated."
msgstr ""
+msgid "mrWidget|Use %{linkStart}CI pipelines to test your code%{linkEnd}, simply add a GitLab CI configuration file to your project. It only takes a minute to make your code more secure and robust."
+msgstr ""
+
msgid "mrWidget|When this merge request is ready, remove the WIP: prefix from the title to allow it to be merged"
msgstr ""
@@ -26952,6 +27569,9 @@ msgstr ""
msgid "must be greater than start date"
msgstr ""
+msgid "must contain only valid frameworks"
+msgstr ""
+
msgid "my-awesome-group"
msgstr ""
@@ -26964,6 +27584,9 @@ msgstr ""
msgid "needs to be between 10 minutes and 1 month"
msgstr ""
+msgid "never"
+msgstr ""
+
msgid "never expires"
msgstr ""
@@ -26991,6 +27614,9 @@ msgstr ""
msgid "nounSeries|%{firstItem} and %{lastItem}"
msgstr ""
+msgid "nounSeries|%{item}"
+msgstr ""
+
msgid "nounSeries|%{item}, %{nextItem}"
msgstr ""
@@ -27127,6 +27753,9 @@ msgstr ""
msgid "revised"
msgstr ""
+msgid "satisfied"
+msgstr ""
+
msgid "score"
msgstr ""
@@ -27157,6 +27786,9 @@ msgstr ""
msgid "severity|Unknown"
msgstr ""
+msgid "should be an array of %{object_name} objects"
+msgstr ""
+
msgid "should be greater than or equal to %{access} inherited membership from group %{group_name}"
msgstr ""
@@ -27211,10 +27843,13 @@ msgstr ""
msgid "suggestPipeline|2/2: Commit your changes"
msgstr ""
-msgid "suggestPipeline|Commit the changes and your pipeline will automatically run for the first time."
+msgid "suggestPipeline|Choose %{boldStart}Code Quality%{boldEnd} to add a pipeline that tests the quality of your code."
msgstr ""
-msgid "suggestPipeline|We recommend the %{boldStart}Code Quality%{boldEnd} template, which will add a report widget to your Merge Requests. This way you’ll learn about code quality degradations much sooner. %{footerStart} Goodbye technical debt! %{footerEnd}"
+msgid "suggestPipeline|The template is ready! You can now commit it to create your first pipeline."
+msgstr ""
+
+msgid "suggestPipeline|We’re adding a GitLab CI configuration file to add a pipeline to the project. You could create it manually, but we recommend that you start with a GitLab template that works out of the box."
msgstr ""
msgid "syntax is correct"
@@ -27274,6 +27909,9 @@ msgstr ""
msgid "uses Kubernetes clusters to deploy your code!"
msgstr ""
+msgid "v%{version} published %{timeAgo}"
+msgstr ""
+
msgid "verify ownership"
msgstr ""
diff --git a/locale/fr/gitlab.po b/locale/fr/gitlab.po
index 2454a76cfb0..2cd216498a4 100644
--- a/locale/fr/gitlab.po
+++ b/locale/fr/gitlab.po
@@ -2,7 +2,7 @@ msgid ""
msgstr ""
"Project-Id-Version: gitlab-ee\n"
"Report-Msgid-Bugs-To: \n"
-"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Last-Translator: \n"
"Language-Team: French\n"
"Language: fr_FR\n"
"MIME-Version: 1.0\n"
@@ -10,9 +10,11 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
"X-Crowdin-Project: gitlab-ee\n"
+"X-Crowdin-Project-ID: 288872\n"
"X-Crowdin-Language: fr\n"
"X-Crowdin-File: /master/locale/gitlab.pot\n"
-"PO-Revision-Date: 2020-06-08 15:14\n"
+"X-Crowdin-File-ID: 6\n"
+"PO-Revision-Date: 2020-07-02 01:23\n"
msgid " %{start} to %{end}"
msgstr ""
@@ -197,6 +199,11 @@ msgid_plural "%d metrics"
msgstr[0] "%d métrique"
msgstr[1] "%d métriques"
+msgid "%d milestone"
+msgid_plural "%d milestones"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "%d minute"
msgid_plural "%d minutes"
msgstr[0] ""
@@ -242,11 +249,6 @@ msgid_plural "%d unresolved threads"
msgstr[0] ""
msgstr[1] ""
-msgid "%d url scanned"
-msgid_plural "%d urls scanned"
-msgstr[0] ""
-msgstr[1] ""
-
msgid "%d vulnerability"
msgid_plural "%d vulnerabilities"
msgstr[0] ""
@@ -324,9 +326,15 @@ msgstr[1] "%{count} commentaires en attente"
msgid "%{count} related %{pluralized_subject}: %{links}"
msgstr ""
+msgid "%{dashboard_path} could not be found."
+msgstr ""
+
msgid "%{days} days until tags are automatically removed"
msgstr ""
+msgid "%{deployLinkStart}Use a template to deploy to ECS%{deployLinkEnd}, or use a docker image to %{commandsLinkStart}run AWS commands in GitLab CI/CD%{commandsLinkEnd}."
+msgstr ""
+
msgid "%{description}- Sentry event: %{errorUrl}- First seen: %{firstSeen}- Last seen: %{lastSeen} %{countLabel}: %{count}%{userCountLabel}: %{userCount}"
msgstr ""
@@ -369,12 +377,18 @@ msgstr ""
msgid "%{host} sign-in from new location"
msgstr ""
-msgid "%{icon}You are about to add %{usersTag} people to the discussion. Proceed with caution."
+msgid "%{icon}You are about to add %{usersTag} people to the discussion. They will all receive a notification."
+msgstr ""
+
+msgid "%{integrations_link_start}Integrations%{link_end} enable you to make third-party applications part of your GitLab workflow. If the available integrations don't meet your needs, consider using a %{webhooks_link_start}webhook%{link_end}."
msgstr ""
msgid "%{issuableType} will be removed! Are you sure?"
msgstr "%{issuableType} sera supprimé ! Êtesâ€vous sûr ?"
+msgid "%{issuesCount} issues in this group"
+msgstr ""
+
msgid "%{issuesSize} issues"
msgstr ""
@@ -384,7 +398,7 @@ msgstr ""
msgid "%{labelStart}Class:%{labelEnd} %{class}"
msgstr ""
-msgid "%{labelStart}Confidence:%{labelEnd} %{confidence}"
+msgid "%{labelStart}Evidence:%{labelEnd} %{evidence}"
msgstr ""
msgid "%{labelStart}File:%{labelEnd} %{file}"
@@ -402,6 +416,9 @@ msgstr ""
msgid "%{labelStart}Report Type:%{labelEnd} %{reportType}"
msgstr ""
+msgid "%{labelStart}Scanner:%{labelEnd} %{scanner}"
+msgstr ""
+
msgid "%{labelStart}Severity:%{labelEnd} %{severity}"
msgstr ""
@@ -426,9 +443,6 @@ msgstr ""
msgid "%{link_start}Read more%{link_end} about role permissions"
msgstr "%{link_start}En savoir plus%{link_end} sur les droits des rôles"
-msgid "%{link} can be used for binding events when something is happening within the project."
-msgstr ""
-
msgid "%{listToShow}, and %{awardsListLength} more."
msgstr ""
@@ -462,6 +476,9 @@ msgstr ""
msgid "%{name} found %{resultsString}"
msgstr ""
+msgid "%{name} is already being used for another emoji"
+msgstr ""
+
msgid "%{name} is scheduled for %{action}"
msgstr ""
@@ -509,6 +526,9 @@ msgstr ""
msgid "%{primary} (%{secondary})"
msgstr ""
+msgid "%{ref} cannot be added: %{error}"
+msgstr ""
+
msgid "%{releases} release"
msgid_plural "%{releases} releases"
msgstr[0] ""
@@ -517,13 +537,37 @@ msgstr[1] ""
msgid "%{remaining_approvals} left"
msgstr ""
-msgid "%{retryButtonStart}Try again%{retryButtonEnd} or %{newFileButtonStart}attach a new file%{newFileButtonEnd}"
+msgid "%{reportType} %{status} detected %{critical} critical and %{high} high severity vulnerabilities out of %{total}."
msgstr ""
-msgid "%{screenreaderOnlyStart}Keyboard shorcuts%{screenreaderOnlyEnd} Disabled"
+msgid "%{reportType} %{status} detected %{critical} critical and %{high} high severity vulnerabilities."
msgstr ""
-msgid "%{screenreaderOnlyStart}Keyboard shorcuts%{screenreaderOnlyEnd} Enabled"
+msgid "%{reportType} %{status} detected %{critical} critical severity vulnerabilities out of %{total}."
+msgstr ""
+
+msgid "%{reportType} %{status} detected %{critical} critical severity vulnerability."
+msgid_plural "%{reportType} %{status} detected %{critical} critical severity vulnerabilities."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{reportType} %{status} detected %{high} high severity vulnerabilities out of %{total}."
+msgstr ""
+
+msgid "%{reportType} %{status} detected %{high} high severity vulnerability."
+msgid_plural "%{reportType} %{status} detected %{high} high severity vulnerabilities."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{reportType} %{status} detected %{other} vulnerability."
+msgid_plural "%{reportType} %{status} detected %{other} vulnerabilities."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{reportType} %{status} detected no new vulnerabilities."
+msgstr ""
+
+msgid "%{retryButtonStart}Try again%{retryButtonEnd} or %{newFileButtonStart}attach a new file%{newFileButtonEnd}"
msgstr ""
msgid "%{service_title} %{message}."
@@ -620,6 +664,12 @@ msgstr "Changements %{title}"
msgid "%{token}..."
msgstr ""
+msgid "%{totalCpu} (%{freeSpacePercentage}%{percentSymbol} free)"
+msgstr ""
+
+msgid "%{totalMemory} (%{freeSpacePercentage}%{percentSymbol} free)"
+msgstr ""
+
msgid "%{totalWeight} total weight"
msgstr ""
@@ -650,6 +700,12 @@ msgstr ""
msgid "%{verb} %{time_spent_value} spent time."
msgstr ""
+msgid "%{webhooks_link_start}%{webhook_type}%{link_end} enable you to send notifications to web applications in response to events in a group or project."
+msgstr ""
+
+msgid "%{webhooks_link_start}%{webhook_type}%{link_end} enable you to send notifications to web applications in response to events in a group or project. We recommend using an %{integrations_link_start}integration%{link_end} in preference to a webhook."
+msgstr ""
+
msgid "'%{level}' is not a valid visibility level"
msgstr ""
@@ -676,9 +732,6 @@ msgstr ""
msgid "(No changes)"
msgstr ""
-msgid "(Show all)"
-msgstr ""
-
msgid "(check progress)"
msgstr ""
@@ -745,6 +798,9 @@ msgstr "- en montrer moins"
msgid "0 for unlimited"
msgstr ""
+msgid "0 for unlimited, only effective with remote storage enabled."
+msgstr ""
+
msgid "1 %{type} addition"
msgid_plural "%{count} %{type} additions"
msgstr[0] "un ajout de %{type}"
@@ -892,6 +948,12 @@ msgstr "<code>\"johnsmith@example.com\": \"johnsm...@example.com\"</code> ajoute
msgid "<code>\"johnsmith@example.com\": \"johnsmith@example.com\"</code> will add \"By <a href=\"#\">johnsmith@example.com</a>\" to all issues and comments originally created by johnsmith@example.com. By default, the email address or username is masked to ensure the user's privacy. Use this option if you want to show the full email address."
msgstr "<code>\"johnsmith@example.com\": \"johnsmith@example.com\"</code> ajoutera « Par <a href=\"#\">johnsmith@example.com</a> » à tous les tickets et commentaires créés à l’origine par johnsmith@example.com. Par défaut, l’adresse de courriel ou le nom d’utilisateur est masqué pour garantir la confidentialité de l’utilisateur. Utilisez cette option si vous souhaitez afficher l’adresse de courriel complète."
+msgid "<code>Masked</code> to prevent the values from being displayed in job logs (must match certain regexp requirements)."
+msgstr ""
+
+msgid "<code>Protected</code> to expose them to protected branches or tags only."
+msgstr ""
+
msgid "<namespace / project>"
msgstr ""
@@ -919,9 +981,6 @@ msgstr "Un « exécuteur » est un processus qui exécute une tâche. Vous pou
msgid "A .NET Core console application template, customizable for any .NET Core project"
msgstr "Un modèle d’application de console .NET Core, personnalisable pour tout projet .NET Core"
-msgid "A DAG must have two dependent jobs to be visualized on this tab."
-msgstr ""
-
msgid "A GitBook site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
msgstr "Un site GitBook qui utilise Netlify comme intégration et livraison continues (CI/CD) au lieu de GitLab, mais en gardant toutes les autres fonctionnalités géniales de GitLab."
@@ -952,6 +1011,12 @@ msgstr "Une branche par défaut ne peut pas être choisie pour un projet vide."
msgid "A deleted user"
msgstr "Un utilisateur supprimé"
+msgid "A file has been changed."
+msgstr ""
+
+msgid "A file was not found."
+msgstr ""
+
msgid "A file with '%{file_name}' already exists in %{branch} branch"
msgstr ""
@@ -1018,7 +1083,7 @@ msgstr ""
msgid "A subscription will trigger a new pipeline on the default branch of this project when a pipeline successfully completes for a new tag on the %{default_branch_docs} of the subscribed project."
msgstr ""
-msgid "A terraform report was generated in your pipelines."
+msgid "A suggestion is not applicable."
msgstr ""
msgid "A user with write access to the source branch selected this option"
@@ -1189,6 +1254,9 @@ msgstr ""
msgid "Action to take when receiving an alert."
msgstr "Action à réaliser lors de la réception d’une alerte."
+msgid "Actions"
+msgstr ""
+
msgid "Activate"
msgstr ""
@@ -1245,6 +1313,9 @@ msgstr "Ajouter une grappe de serveurs Kubernetes"
msgid "Add LICENSE"
msgstr ""
+msgid "Add New Node"
+msgstr ""
+
msgid "Add README"
msgstr "Ajouter un README"
@@ -1320,6 +1391,9 @@ msgstr ""
msgid "Add approval rule"
msgstr ""
+msgid "Add approvers"
+msgstr ""
+
msgid "Add bold text"
msgstr "Ajouter du texte en gras"
@@ -1377,6 +1451,9 @@ msgstr ""
msgid "Add strikethrough text"
msgstr ""
+msgid "Add suggestion to batch"
+msgstr ""
+
msgid "Add system hook"
msgstr ""
@@ -1476,6 +1553,9 @@ msgstr "Espace d’administration"
msgid "Admin Note"
msgstr ""
+msgid "Admin Notifications"
+msgstr ""
+
msgid "Admin Overview"
msgstr "Vue administrateur"
@@ -1587,6 +1667,9 @@ msgstr ""
msgid "AdminSettings|Integrations configured here will automatically apply to all projects on this instance."
msgstr ""
+msgid "AdminSettings|Moved to integrations"
+msgstr ""
+
msgid "AdminSettings|No required pipeline"
msgstr ""
@@ -1817,12 +1900,18 @@ msgid_plural "Alerts"
msgstr[0] "Alerte"
msgstr[1] "Alertes"
+msgid "AlertManagement| assign yourself"
+msgstr ""
+
msgid "AlertManagement|Acknowledged"
msgstr ""
msgid "AlertManagement|Alert"
msgstr ""
+msgid "AlertManagement|Alert assignee(s): %{assignees}"
+msgstr ""
+
msgid "AlertManagement|Alert detail"
msgstr ""
@@ -1838,9 +1927,15 @@ msgstr ""
msgid "AlertManagement|All alerts"
msgstr ""
+msgid "AlertManagement|Assign To"
+msgstr ""
+
msgid "AlertManagement|Assign status"
msgstr ""
+msgid "AlertManagement|Assignee"
+msgstr ""
+
msgid "AlertManagement|Assignees"
msgstr ""
@@ -1859,9 +1954,6 @@ msgstr ""
msgid "AlertManagement|Edit"
msgstr ""
-msgid "AlertManagement|End time"
-msgstr ""
-
msgid "AlertManagement|Events"
msgstr ""
@@ -1880,10 +1972,13 @@ msgstr ""
msgid "AlertManagement|More information"
msgstr ""
+msgid "AlertManagement|No Matching Results"
+msgstr ""
+
msgid "AlertManagement|No alert data to display."
msgstr ""
-msgid "AlertManagement|No alerts available to display. If you think you're seeing this message in error, refresh the page."
+msgid "AlertManagement|No alerts available to display. See %{linkStart}enabling alert management%{linkEnd} for more information on adding alerts to the list."
msgstr ""
msgid "AlertManagement|No alerts to display."
@@ -1892,6 +1987,9 @@ msgstr ""
msgid "AlertManagement|None"
msgstr ""
+msgid "AlertManagement|None -"
+msgstr ""
+
msgid "AlertManagement|Open"
msgstr ""
@@ -1928,9 +2026,18 @@ msgstr ""
msgid "AlertManagement|There was an error displaying the alerts. Confirm your endpoint's configuration details to ensure alerts appear."
msgstr ""
+msgid "AlertManagement|There was an error while updating the assignee(s) list. Please try again."
+msgstr ""
+
+msgid "AlertManagement|There was an error while updating the assignee(s) of the alert. Please try again."
+msgstr ""
+
msgid "AlertManagement|There was an error while updating the status of the alert. Please try again."
msgstr ""
+msgid "AlertManagement|This assignee cannot be assigned to this alert."
+msgstr ""
+
msgid "AlertManagement|Tool"
msgstr ""
@@ -2078,6 +2185,9 @@ msgstr ""
msgid "Allow users to request access (if visibility is public or internal)"
msgstr ""
+msgid "Allowed Geo IP"
+msgstr ""
+
msgid "Allowed email domain restriction only permitted for top-level groups"
msgstr ""
@@ -2111,6 +2221,9 @@ msgstr ""
msgid "Amazon Web Services"
msgstr ""
+msgid "Amazon Web Services Logo"
+msgstr ""
+
msgid "Amazon authentication is not %{link_start}correctly configured%{link_end}. Ask your GitLab administrator if you want to use this service."
msgstr ""
@@ -2168,10 +2281,16 @@ msgstr ""
msgid "An error occurred when updating the issue weight"
msgstr "Une erreur est survenue lors de la mise à jour du poids du ticket"
+msgid "An error occurred while acknowledging the notification. Refresh the page and try again."
+msgstr ""
+
+msgid "An error occurred while adding approvers"
+msgstr ""
+
msgid "An error occurred while adding formatted title for epic"
msgstr ""
-msgid "An error occurred while checking group path"
+msgid "An error occurred while checking group path. Please refresh and try again."
msgstr ""
msgid "An error occurred while committing your changes."
@@ -2315,7 +2434,7 @@ msgstr ""
msgid "An error occurred while loading milestones"
msgstr ""
-msgid "An error occurred while loading terraform report"
+msgid "An error occurred while loading project creation UI"
msgstr ""
msgid "An error occurred while loading the data. Please try again."
@@ -2369,6 +2488,9 @@ msgstr ""
msgid "An error occurred while rendering preview broadcast message"
msgstr "Une erreur s’est produite lors de la prévisualisation de la bannière"
+msgid "An error occurred while rendering the editor"
+msgstr ""
+
msgid "An error occurred while reordering issues."
msgstr ""
@@ -2465,7 +2587,7 @@ msgstr ""
msgid "Analyze your dependencies for known vulnerabilities."
msgstr ""
-msgid "Analyze your source code and git history for secrets"
+msgid "Analyze your source code and git history for secrets."
msgstr ""
msgid "Analyze your source code for known vulnerabilities."
@@ -2579,12 +2701,18 @@ msgstr ""
msgid "Apply suggestion"
msgstr ""
+msgid "Apply suggestions"
+msgstr ""
+
msgid "Apply template"
msgstr ""
msgid "Apply this approval rule to any branch or a specific protected branch."
msgstr ""
+msgid "Applying"
+msgstr ""
+
msgid "Applying a template will replace the existing issue description. Any changes you have made will be lost."
msgstr ""
@@ -2597,12 +2725,18 @@ msgstr ""
msgid "Applying multiple commands"
msgstr ""
-msgid "Applying suggestion"
+msgid "Applying suggestion..."
+msgstr ""
+
+msgid "Applying suggestions..."
msgstr ""
msgid "Approval rules"
msgstr ""
+msgid "Approval rules reset to project defaults"
+msgstr ""
+
msgid "ApprovalRuleRemove|%d member"
msgid_plural "ApprovalRuleRemove|%d members"
msgstr[0] ""
@@ -2656,6 +2790,9 @@ msgstr ""
msgid "Approved"
msgstr ""
+msgid "Approved MRs"
+msgstr ""
+
msgid "Approved by: "
msgstr ""
@@ -2869,6 +3006,9 @@ msgstr ""
msgid "Assign"
msgstr ""
+msgid "Assign Iteration"
+msgstr ""
+
msgid "Assign custom color like #FF0000"
msgstr "Attribuer une couleur personnalisée comme #FF0000"
@@ -2887,6 +3027,9 @@ msgstr ""
msgid "Assign to"
msgstr "Assigner à"
+msgid "Assign to commenting user"
+msgstr ""
+
msgid "Assign yourself to these issues"
msgstr ""
@@ -2913,6 +3056,9 @@ msgid_plural "%d Assignees"
msgstr[0] ""
msgstr[1] ""
+msgid "Assignee has no permissions"
+msgstr ""
+
msgid "Assignee lists not available with your current license"
msgstr "La liste des personnes assignées n’est pas disponible avec votre licence actuelle"
@@ -2922,6 +3068,9 @@ msgstr "Les listes d’assignation montrent tous les bogues assignés à l’uti
msgid "Assignee(s)"
msgstr "Assigné·e(s)"
+msgid "Assignees"
+msgstr ""
+
msgid "Assigns %{assignee_users_sentence}."
msgstr ""
@@ -2957,6 +3106,9 @@ msgstr "Événements d’audit"
msgid "Audit Events is a way to keep track of important events that happened in GitLab."
msgstr ""
+msgid "Audit Log"
+msgstr ""
+
msgid "AuditEvents|(removed)"
msgstr ""
@@ -2993,6 +3145,9 @@ msgstr ""
msgid "AuditLogs|IP Address"
msgstr ""
+msgid "AuditLogs|Member Events"
+msgstr ""
+
msgid "AuditLogs|No matching %{type} found."
msgstr ""
@@ -3275,9 +3430,6 @@ msgstr "Vos badges numériques"
msgid "Badges|e.g. %{exampleUrl}"
msgstr "p. ex. %{exampleUrl}"
-msgid "Badge|New"
-msgstr ""
-
msgid "Balsamiq file could not be loaded."
msgstr ""
@@ -3326,6 +3478,12 @@ msgstr ""
msgid "Below you will find all the groups that are public."
msgstr "Vous trouverez ciâ€dessous tous les groupes publics."
+msgid "Beta"
+msgstr ""
+
+msgid "Bi-weekly code coverage"
+msgstr ""
+
msgid "Billing"
msgstr "Facturation"
@@ -3386,9 +3544,15 @@ msgstr ""
msgid "Bitbucket Server Import"
msgstr "Importation d’un serveur Bitbucket"
+msgid "Bitbucket Server import"
+msgstr ""
+
msgid "Bitbucket import"
msgstr "Importation de Bitbucket"
+msgid "Blame"
+msgstr ""
+
msgid "Blocked"
msgstr ""
@@ -3401,9 +3565,6 @@ msgstr ""
msgid "Blog"
msgstr "Blog"
-msgid "Blue helpers indicate an action to be taken."
-msgstr ""
-
msgid "Board name"
msgstr ""
@@ -3602,6 +3763,9 @@ msgstr ""
msgid "Broadcast Message was successfully updated."
msgstr ""
+msgid "Broadcast Messages"
+msgstr ""
+
msgid "Browse Directory"
msgstr "Parcourir le dossier"
@@ -3656,6 +3820,9 @@ msgstr ""
msgid "By %{user_name}"
msgstr ""
+msgid "By URL"
+msgstr ""
+
msgid "By default GitLab sends emails in HTML and plain text formats so mail clients can choose what format to use. Disable this option if you only want to send emails in plain text format."
msgstr ""
@@ -3761,6 +3928,9 @@ msgstr ""
msgid "Can override approvers and approvals required per merge request"
msgstr ""
+msgid "Can't apply as this line has changed or the suggestion already matches its content."
+msgstr ""
+
msgid "Can't create snippet: %{err}"
msgstr ""
@@ -3947,9 +4117,6 @@ msgstr "Les modifications sont affichées comme si la révision <b>source</b> é
msgid "Changes are still tracked. Useful for cluster/index migrations."
msgstr ""
-msgid "Changes are unknown"
-msgstr ""
-
msgid "Changes suppressed. Click to show."
msgstr ""
@@ -4256,15 +4423,9 @@ msgstr ""
msgid "Choose what content you want to see on a group’s overview page"
msgstr ""
-msgid "Choose which groups you wish to synchronize to this secondary node"
-msgstr ""
-
msgid "Choose which repositories you want to connect and run CI/CD pipelines."
msgstr "Choisissez quels dépôts vous voulez connecter pour exécuter des pipelines d’intégration et de livraison continues (CI/CD)."
-msgid "Choose which shards you wish to synchronize to this secondary node"
-msgstr ""
-
msgid "Choose your framework"
msgstr ""
@@ -4562,9 +4723,15 @@ msgstr ""
msgid "Cluster does not exist"
msgstr ""
+msgid "Cluster is required for Stages::ClusterEndpointInserter"
+msgstr ""
+
msgid "Cluster level"
msgstr ""
+msgid "Cluster type must be specificed for Stages::ClusterEndpointInserter"
+msgstr ""
+
msgid "ClusterIntegration| %{custom_domain_start}More information%{custom_domain_end}."
msgstr ""
@@ -4676,7 +4843,7 @@ msgstr "Certificat d’autorité de certification"
msgid "ClusterIntegration|Cert-Manager"
msgstr ""
-msgid "ClusterIntegration|Cert-Manager is a native Kubernetes certificate management controller that helps with issuing certificates. Installing Cert-Manager on your cluster will issue a certificate by %{letsEncrypt} and ensure that certificates are valid and up-to-date."
+msgid "ClusterIntegration|Cert-Manager is a native Kubernetes certificate management controller that helps with issuing certificates. Installing Cert-Manager on your cluster will issue a certificate by %{linkStart}Let's Encrypt%{linkEnd} and ensure that certificates are valid and up-to-date."
msgstr ""
msgid "ClusterIntegration|Certificate Authority bundle (PEM format)"
@@ -4703,9 +4870,6 @@ msgstr ""
msgid "ClusterIntegration|Clear the local cache of namespace and service accounts. This is necessary if your integration has become out of sync. The cache is repopulated during the next CI job that requires namespace and service accounts."
msgstr ""
-msgid "ClusterIntegration|Cloud Run"
-msgstr ""
-
msgid "ClusterIntegration|Cluster being created"
msgstr ""
@@ -4790,7 +4954,7 @@ msgstr ""
msgid "ClusterIntegration|Crossplane"
msgstr ""
-msgid "ClusterIntegration|Crossplane enables declarative provisioning of managed services from your cloud of choice using %{codeStart}kubectl%{codeEnd} or %{gitlabIntegrationLink}. Crossplane runs inside your Kubernetes cluster and supports secure connectivity and secrets management between app containers and the cloud services they depend on."
+msgid "ClusterIntegration|Crossplane enables declarative provisioning of managed services from your cloud of choice using %{codeStart}kubectl%{codeEnd} or %{linkStart}GitLab Integration%{linkEnd}. Crossplane runs inside your Kubernetes cluster and supports secure connectivity and secrets management between app containers and the cloud services they depend on."
msgstr ""
msgid "ClusterIntegration|Deletes all GitLab resources attached to this cluster during removal"
@@ -4874,9 +5038,6 @@ msgstr ""
msgid "ClusterIntegration|GitLab-managed cluster"
msgstr ""
-msgid "ClusterIntegration|Gitlab Integration"
-msgstr ""
-
msgid "ClusterIntegration|Global default"
msgstr ""
@@ -4922,7 +5083,7 @@ msgstr ""
msgid "ClusterIntegration|Ingress gives you a way to route requests to services based on the request host or path, centralizing a number of services into a single entrypoint."
msgstr "Ingress vous permet de router les requêtes vers des services en fonction de l’hôte ou du chemin de la requête, en centralisant un certain nombre de services vers un seul point d’entrée."
-msgid "ClusterIntegration|Installing Ingress may incur additional costs. Learn more about %{pricingLink}."
+msgid "ClusterIntegration|Installing Ingress may incur additional costs. Learn more about %{linkStart}pricing%{linkEnd}."
msgstr ""
msgid "ClusterIntegration|Instance cluster"
@@ -4937,7 +5098,7 @@ msgstr "Intégrez l’automatisation de la grappe de serveurs Kubernetes"
msgid "ClusterIntegration|Issuer Email"
msgstr ""
-msgid "ClusterIntegration|Issuers represent a certificate authority. You must provide an email address for your Issuer. "
+msgid "ClusterIntegration|Issuers represent a certificate authority. You must provide an email address for your Issuer."
msgstr ""
msgid "ClusterIntegration|Jupyter Hostname"
@@ -5009,9 +5170,6 @@ msgstr "En savoir plus sur les grappes de serveurs Kubernetes de groupe"
msgid "ClusterIntegration|Learn more about instance Kubernetes clusters"
msgstr ""
-msgid "ClusterIntegration|Let's Encrypt"
-msgstr ""
-
msgid "ClusterIntegration|Loading IAM Roles"
msgstr ""
@@ -5123,8 +5281,8 @@ msgstr ""
msgid "ClusterIntegration|Prometheus"
msgstr "Prometheus"
-msgid "ClusterIntegration|Prometheus is an open-source monitoring system with %{gitlabIntegrationLink} to monitor deployed applications."
-msgstr "Prometheus est un système de supervision libre avec %{gitlabIntegrationLink} permettant de surveiller les applications déployées."
+msgid "ClusterIntegration|Prometheus is an open-source monitoring system with %{linkStart}GitLab Integration%{linkEnd} to monitor deployed applications."
+msgstr ""
msgid "ClusterIntegration|Provider details"
msgstr ""
@@ -5279,10 +5437,10 @@ msgstr "Sélectionnez la zone"
msgid "ClusterIntegration|Select zone to choose machine type"
msgstr "Sélectionnez la zone afin de choisir le type de machine"
-msgid "ClusterIntegration|Send Cilium Logs"
+msgid "ClusterIntegration|Send Container Network Policies Logs"
msgstr ""
-msgid "ClusterIntegration|Send ModSecurity Logs"
+msgid "ClusterIntegration|Send Web Application Firewall Logs"
msgstr ""
msgid "ClusterIntegration|Service Token"
@@ -5450,15 +5608,12 @@ msgstr "Accèder à Google Kubernetes Engine"
msgid "ClusterIntegration|documentation"
msgstr "documentation"
-msgid "ClusterIntegration|installed via %{installed_via}"
+msgid "ClusterIntegration|installed via %{linkStart}Cloud Run%{linkEnd}"
msgstr ""
msgid "ClusterIntegration|meets the requirements"
msgstr "répond aux exigences"
-msgid "ClusterIntegration|pricing"
-msgstr ""
-
msgid "ClusterIntegration|sign up"
msgstr "s’inscrire"
@@ -5495,6 +5650,15 @@ msgstr ""
msgid "Code"
msgstr ""
+msgid "Code Coverage: %{coveragePercentage}%{percentSymbol}"
+msgstr ""
+
+msgid "Code Coverage| Empty code coverage data"
+msgstr ""
+
+msgid "Code Coverage|Couldn't fetch the code coverage data"
+msgstr ""
+
msgid "Code Owners"
msgstr ""
@@ -5549,7 +5713,7 @@ msgstr "Réduire"
msgid "Collapse approvers"
msgstr ""
-msgid "Collapse child epics"
+msgid "Collapse milestones"
msgstr ""
msgid "Collapse replies"
@@ -5567,6 +5731,9 @@ msgstr ""
msgid "Coming soon"
msgstr ""
+msgid "Comma-separated, e.g. '1.1.1.1, 2.2.2.0/24'"
+msgstr ""
+
msgid "Command"
msgstr ""
@@ -5737,6 +5904,12 @@ msgstr ""
msgid "Compliance framework (optional)"
msgstr ""
+msgid "Compliance frameworks"
+msgstr ""
+
+msgid "ComplianceDashboard|created by:"
+msgstr ""
+
msgid "ComplianceFramework|GDPR"
msgstr ""
@@ -5875,6 +6048,9 @@ msgstr ""
msgid "Connection timed out"
msgstr ""
+msgid "Connection timeout"
+msgstr ""
+
msgid "Contact sales to upgrade"
msgstr ""
@@ -5930,9 +6106,6 @@ msgstr ""
msgid "ContainerRegistry|CLI Commands"
msgstr ""
-msgid "ContainerRegistry|Compressed Size"
-msgstr ""
-
msgid "ContainerRegistry|Container Registry"
msgstr ""
@@ -5948,6 +6121,9 @@ msgstr ""
msgid "ContainerRegistry|Copy push command"
msgstr ""
+msgid "ContainerRegistry|Delete selected"
+msgstr ""
+
msgid "ContainerRegistry|Docker connection error"
msgstr ""
@@ -5981,16 +6157,16 @@ msgstr ""
msgid "ContainerRegistry|If you are not already logged in, you need to authenticate to the Container Registry by using your GitLab username and password. If you have %{twofaDocLinkStart}Two-Factor Authentication%{twofaDocLinkEnd} enabled, use a %{personalAccessTokensDocLinkStart}Personal Access Token%{personalAccessTokensDocLinkEnd} instead of a password."
msgstr ""
-msgid "ContainerRegistry|Image ID"
+msgid "ContainerRegistry|Image ID: %{imageId}"
msgstr ""
msgid "ContainerRegistry|Image Repositories"
msgstr ""
-msgid "ContainerRegistry|Keep and protect the images that matter most."
+msgid "ContainerRegistry|Image tags"
msgstr ""
-msgid "ContainerRegistry|Last Updated"
+msgid "ContainerRegistry|Keep and protect the images that matter most."
msgstr ""
msgid "ContainerRegistry|Login"
@@ -6005,6 +6181,9 @@ msgstr ""
msgid "ContainerRegistry|Please contact your administrator."
msgstr ""
+msgid "ContainerRegistry|Published %{timeInfo}"
+msgstr ""
+
msgid "ContainerRegistry|Push an image"
msgstr ""
@@ -6020,9 +6199,6 @@ msgstr ""
msgid "ContainerRegistry|Remove repository"
msgstr "Supprimer le dépôt"
-msgid "ContainerRegistry|Remove selected tags"
-msgstr ""
-
msgid "ContainerRegistry|Remove tag"
msgid_plural "ContainerRegistry|Remove tags"
msgstr[0] ""
@@ -6052,9 +6228,6 @@ msgstr ""
msgid "ContainerRegistry|Sorry, your filter produced no results."
msgstr ""
-msgid "ContainerRegistry|Tag"
-msgstr "Étiquette"
-
msgid "ContainerRegistry|Tag expiration policy"
msgstr ""
@@ -6199,21 +6372,6 @@ msgstr ""
msgid "Control the display of third party offers."
msgstr "Contrôle l’affichage des offres tierces."
-msgid "Control the maximum concurrency of LFS/attachment backfill for this secondary node"
-msgstr ""
-
-msgid "Control the maximum concurrency of container repository operations for this Geo node"
-msgstr ""
-
-msgid "Control the maximum concurrency of repository backfill for this secondary node"
-msgstr "Contrôle la concurrence maximale des remplacements de dépôt pour ce nœud secondaire"
-
-msgid "Control the maximum concurrency of verification operations for this Geo node"
-msgstr ""
-
-msgid "Control the minimum interval in days that a repository should be reverified for this primary node"
-msgstr ""
-
msgid "Cookie domain"
msgstr ""
@@ -6355,6 +6513,9 @@ msgstr ""
msgid "Could not find design."
msgstr ""
+msgid "Could not find iteration"
+msgstr ""
+
msgid "Could not remove the trigger."
msgstr ""
@@ -6391,6 +6552,12 @@ msgstr ""
msgid "Coverage"
msgstr ""
+msgid "Coverage Fuzzing"
+msgstr ""
+
+msgid "Crash State"
+msgstr ""
+
msgid "Create"
msgstr "Créer"
@@ -6439,6 +6606,9 @@ msgstr ""
msgid "Create a personal access token on your account to pull or push via %{protocol}."
msgstr "Créer un jeton d’accès personnel pour votre compte afin de récupérer ou pousser par %{protocol}."
+msgid "Create a project pre-populated with the necessary files to get you started quickly."
+msgstr ""
+
msgid "Create an account using:"
msgstr ""
@@ -6607,6 +6777,9 @@ msgstr ""
msgid "Creates branch '%{branch_name}' and a merge request to resolve this issue."
msgstr ""
+msgid "Creating"
+msgstr ""
+
msgid "Creating epic"
msgstr "Création de l’épopée en cours"
@@ -6658,7 +6831,10 @@ msgstr ""
msgid "Current vulnerabilities count"
msgstr ""
-msgid "CurrentUser|Buy CI minutes"
+msgid "CurrentUser|Buy Pipeline minutes"
+msgstr ""
+
+msgid "CurrentUser|One of your groups is running out"
msgstr ""
msgid "CurrentUser|Profile"
@@ -6673,6 +6849,9 @@ msgstr ""
msgid "CurrentUser|Upgrade"
msgstr ""
+msgid "Custom Attributes"
+msgstr ""
+
msgid "Custom CI configuration path"
msgstr ""
@@ -6936,12 +7115,18 @@ msgstr ""
msgid "DAG"
msgstr ""
+msgid "DAG visualization requires at least 3 dependent jobs."
+msgstr ""
+
msgid "DNS"
msgstr ""
msgid "Dashboard"
msgstr "Tableau de bord"
+msgid "Dashboard uid not found"
+msgstr ""
+
msgid "DashboardProjects|All"
msgstr "Tous"
@@ -6966,6 +7151,9 @@ msgstr ""
msgid "Data is still calculating..."
msgstr ""
+msgid "Datasource name not found"
+msgstr ""
+
msgid "Date"
msgstr ""
@@ -7146,6 +7334,9 @@ msgstr ""
msgid "Delete this attachment"
msgstr ""
+msgid "Delete user list"
+msgstr ""
+
msgid "Delete variable"
msgstr ""
@@ -7462,6 +7653,9 @@ msgstr "Déployé sur"
msgid "Deploying to"
msgstr "En cours de déploiement sur"
+msgid "Deploying to AWS is easy with GitLab"
+msgstr ""
+
msgid "Deployment Frequency"
msgstr ""
@@ -7824,6 +8018,9 @@ msgstr ""
msgid "Dismissed on pipeline %{pipelineLink} at %{projectLink}"
msgstr ""
+msgid "Display alerts from all your monitoring tools directly within GitLab."
+msgstr ""
+
msgid "Display name"
msgstr ""
@@ -7881,9 +8078,6 @@ msgstr ""
msgid "Don't show again"
msgstr "Ne plus afficher"
-msgid "Don't worry, you can access this tour by clicking on the help icon in the top right corner and choose <strong>Learn GitLab</strong>."
-msgstr ""
-
msgid "Done"
msgstr "Effectué"
@@ -8010,6 +8204,9 @@ msgstr "Éditer le pipeline programmé %{id}"
msgid "Edit Release"
msgstr ""
+msgid "Edit Slack integration"
+msgstr ""
+
msgid "Edit Snippet"
msgstr "Modifier le fragment de code"
@@ -8049,6 +8246,9 @@ msgstr "Modifier l’identité de %{user_name}"
msgid "Edit issues"
msgstr ""
+msgid "Edit iteration"
+msgstr ""
+
msgid "Edit public deploy key"
msgstr ""
@@ -8097,6 +8297,9 @@ msgstr ""
msgid "Email"
msgstr "Courriel"
+msgid "Email Notification"
+msgstr ""
+
msgid "Email address"
msgstr ""
@@ -8109,6 +8312,9 @@ msgstr ""
msgid "Email not verified. Please verify your email in Salesforce."
msgstr ""
+msgid "Email notification for unknown sign-ins"
+msgstr ""
+
msgid "Email patch"
msgstr "Correctif par courriel"
@@ -8340,6 +8546,9 @@ msgstr "Se termine à (UTC)"
msgid "Enforce DNS rebinding attack protection"
msgstr ""
+msgid "Enforce personal access token expiration"
+msgstr ""
+
msgid "Ensure connectivity is available from the GitLab server to the Prometheus server"
msgstr ""
@@ -8409,6 +8618,9 @@ msgstr "Entrez l’intitulé de la demande de fusion"
msgid "Enter the name of your application, and we'll return a unique %{type}."
msgstr ""
+msgid "Enter weights for storages for new repositories."
+msgstr ""
+
msgid "Enter your password to approve"
msgstr ""
@@ -8418,15 +8630,27 @@ msgstr ""
msgid "Environment does not have deployments"
msgstr ""
+msgid "Environment is required for Stages::MetricEndpointInserter"
+msgstr ""
+
+msgid "Environment is required for Stages::VariableEndpointInserter"
+msgstr ""
+
msgid "Environment scope"
msgstr ""
+msgid "Environment variables are applied to all project environments in this instance via the Runner. You can use environment variables for passwords, secret keys, etc. Make variables available to the running application by prepending the variable key with <code>K8S_SECRET_</code>. You can set variables to be:"
+msgstr ""
+
msgid "Environment variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. Additionally, they can be masked so they are hidden in job logs, though they must match certain regexp requirements to do so. You can use environment variables for passwords, secret keys, or whatever you want."
msgstr ""
msgid "Environment variables are configured by your administrator to be %{link_start}protected%{link_end} by default"
msgstr ""
+msgid "Environment variables on this GitLab instance are configured to be %{link_start}protected%{link_end} by default"
+msgstr ""
+
msgid "Environment:"
msgstr ""
@@ -8958,6 +9182,9 @@ msgstr "Tout filtrer"
msgid "EventFilterBy|Filter by comments"
msgstr "Filtrer par commentaires"
+msgid "EventFilterBy|Filter by designs"
+msgstr ""
+
msgid "EventFilterBy|Filter by epic events"
msgstr ""
@@ -9090,15 +9317,15 @@ msgstr "Tout étendre"
msgid "Expand approvers"
msgstr ""
-msgid "Expand child epics"
-msgstr ""
-
msgid "Expand down"
msgstr ""
msgid "Expand dropdown"
msgstr ""
+msgid "Expand milestones"
+msgstr ""
+
msgid "Expand sidebar"
msgstr "Étendre la barre latérale"
@@ -9114,6 +9341,9 @@ msgstr ""
msgid "Expiration date"
msgstr "Date d’expiration"
+msgid "Expiration not enforced"
+msgstr ""
+
msgid "Expiration policy for the Container Registry is a perfect solution for keeping the Registry space down while still enjoying the full power of GitLab CI/CD."
msgstr ""
@@ -9234,6 +9464,9 @@ msgstr "Échec"
msgid "Failed Jobs"
msgstr "Tâches ayant échoué"
+msgid "Failed on"
+msgstr ""
+
msgid "Failed to add a Zoom meeting"
msgstr ""
@@ -9294,13 +9527,19 @@ msgstr ""
msgid "Failed to install."
msgstr ""
+msgid "Failed to load assignees. Please try again."
+msgstr ""
+
+msgid "Failed to load authors. Please try again."
+msgstr ""
+
msgid "Failed to load emoji list."
msgstr "Impossible de charger la liste des émojis."
msgid "Failed to load error details from Sentry."
msgstr ""
-msgid "Failed to load errors from Sentry. Error message: %{errorMessage}"
+msgid "Failed to load errors from Sentry."
msgstr ""
msgid "Failed to load group activity metrics. Please try again."
@@ -9381,6 +9620,9 @@ msgstr ""
msgid "Failed to set due date because the date format is invalid."
msgstr ""
+msgid "Failed to set iteration on this issue. Please try again."
+msgstr ""
+
msgid "Failed to signing using smartcard authentication"
msgstr "Échec de la signature via authentification par carte à puce"
@@ -9603,13 +9845,10 @@ msgstr ""
msgid "FeatureFlags|Target environments"
msgstr ""
-msgid "FeatureFlags|There are no active feature flags"
-msgstr ""
-
-msgid "FeatureFlags|There are no inactive feature flags"
+msgid "FeatureFlags|There was an error fetching the feature flags."
msgstr ""
-msgid "FeatureFlags|There was an error fetching the feature flags."
+msgid "FeatureFlags|There was an error retrieving user lists"
msgstr ""
msgid "FeatureFlags|Try again in a few moments or contact your support team."
@@ -9621,9 +9860,18 @@ msgstr ""
msgid "FeatureFlag|Delete strategy"
msgstr ""
+msgid "FeatureFlag|List"
+msgstr ""
+
msgid "FeatureFlag|Percentage"
msgstr ""
+msgid "FeatureFlag|Select a user list"
+msgstr ""
+
+msgid "FeatureFlag|There are no configured user lists"
+msgstr ""
+
msgid "FeatureFlag|Type"
msgstr ""
@@ -9780,6 +10028,12 @@ msgstr ""
msgid "Filter..."
msgstr "Filtrer…"
+msgid "Find File"
+msgstr ""
+
+msgid "Find bugs in your code with coverage-guided fuzzing"
+msgstr ""
+
msgid "Find by path"
msgstr "Rechercher par chemin d’accès"
@@ -10044,6 +10298,9 @@ msgstr ""
msgid "Geo allows you to replicate your GitLab instance to other geographical locations."
msgstr "Geo vous permet de répliquer votre instance GitLab vers d’autres emplacements géographiques."
+msgid "Geo nodes are paused using a command run on the node"
+msgstr ""
+
msgid "GeoNodeStatusEvent|%{timeAgoStr} (%{pendingEvents} events)"
msgstr ""
@@ -10128,6 +10385,9 @@ msgstr ""
msgid "GeoNodes|Not checksummed"
msgstr "Non vérifié par somme de contrôle"
+msgid "GeoNodes|Package files"
+msgstr ""
+
msgid "GeoNodes|Pausing replication stops the sync process. Are you sure?"
msgstr ""
@@ -10146,6 +10406,9 @@ msgstr ""
msgid "GeoNodes|Replication slots"
msgstr "Emplacements de réplication"
+msgid "GeoNodes|Replication status"
+msgstr ""
+
msgid "GeoNodes|Repositories"
msgstr "Dépôts"
@@ -10356,6 +10619,9 @@ msgstr ""
msgid "Geo|Synchronization failed - %{error}"
msgstr "Synchro en échec — %{error}"
+msgid "Geo|Synchronization of %{itemTitle} is disabled."
+msgstr ""
+
msgid "Geo|The database is currently %{db_lag} behind the primary node."
msgstr ""
@@ -10668,6 +10934,9 @@ msgstr ""
msgid "Go to %{link_to_google_takeout}."
msgstr "Consultez le site de %{link_to_google_takeout}."
+msgid "Go to <strong>Issues</strong> > <strong>Boards</strong> to access your personalized learning issue board."
+msgstr ""
+
msgid "Go to Pipelines"
msgstr ""
@@ -10767,9 +11036,6 @@ msgstr ""
msgid "Go to your snippets"
msgstr ""
-msgid "Golden Tanuki"
-msgstr ""
-
msgid "Google Cloud Platform"
msgstr ""
@@ -10791,6 +11057,9 @@ msgstr "Compris !"
msgid "Grafana URL"
msgstr ""
+msgid "Grafana response contains invalid json"
+msgstr ""
+
msgid "GrafanaIntegration|API Token"
msgstr ""
@@ -10839,9 +11108,6 @@ msgstr ""
msgid "Group %{group_name} was successfully created."
msgstr ""
-msgid "Group '%{group_name}' is being imported."
-msgstr ""
-
msgid "Group Audit Events"
msgstr ""
@@ -10878,9 +11144,6 @@ msgstr "Avatar de groupe"
msgid "Group by:"
msgstr ""
-msgid "Group could not be imported: %{errors}"
-msgstr ""
-
msgid "Group description"
msgstr "Description du groupe"
@@ -10908,9 +11171,15 @@ msgstr ""
msgid "Group has not been marked for deletion"
msgstr ""
+msgid "Group import could not be scheduled"
+msgstr ""
+
msgid "Group info:"
msgstr "Informations du groupe :"
+msgid "Group is required when cluster_type is :group"
+msgstr ""
+
msgid "Group maintainers can register group runners in the %{link}"
msgstr "Les responsables de groupe peuvent créer des exécuteurs de groupe via %{link}"
@@ -10974,6 +11243,24 @@ msgstr ""
msgid "GroupActivyMetrics|Recent activity (last 90 days)"
msgstr ""
+msgid "GroupImport|Failed to import group."
+msgstr ""
+
+msgid "GroupImport|Group '%{group_name}' is being imported."
+msgstr ""
+
+msgid "GroupImport|Group could not be imported: %{errors}"
+msgstr ""
+
+msgid "GroupImport|Please wait while we import the group for you. Refresh at will."
+msgstr ""
+
+msgid "GroupImport|The group was successfully imported."
+msgstr ""
+
+msgid "GroupImport|Unable to process group import file"
+msgstr ""
+
msgid "GroupRoadmap|%{dateWord} – No end date"
msgstr ""
@@ -11241,6 +11528,9 @@ msgstr ""
msgid "Groups (%{groups})"
msgstr ""
+msgid "Groups and projects"
+msgstr ""
+
msgid "Groups can also be nested by creating %{subgroup_docs_link_start}subgroups%{subgroup_docs_link_end}."
msgstr "Les groupes peuvent également être imbriqués en créant des %{subgroup_docs_link_start}sousâ€groupes%{subgroup_docs_link_end}."
@@ -11286,6 +11576,33 @@ msgstr "Aucun groupe trouvé"
msgid "GroupsEmptyState|You can manage your group member’s permissions and access to each project in the group."
msgstr "Vous pouvez gérer les autorisations des membres de votre groupe et accéder à chacun de ses projets."
+msgid "GroupsNew|Contact an administrator to enable options for importing your group."
+msgstr ""
+
+msgid "GroupsNew|Create"
+msgstr ""
+
+msgid "GroupsNew|Create group"
+msgstr ""
+
+msgid "GroupsNew|GitLab group export"
+msgstr ""
+
+msgid "GroupsNew|Import"
+msgstr ""
+
+msgid "GroupsNew|Import group"
+msgstr ""
+
+msgid "GroupsNew|My Awesome Group"
+msgstr ""
+
+msgid "GroupsNew|No import options available"
+msgstr ""
+
+msgid "GroupsNew|To copy a GitLab group between installations, navigate to the group settings page for the original installation, generate an export file, and upload it here."
+msgstr ""
+
msgid "GroupsTree|Are you sure you want to leave the \"%{fullName}\" group?"
msgstr "Êtes-vous sûr·e de vouloir quitter le groupe « %{fullName} » ?"
@@ -11388,6 +11705,9 @@ msgstr ""
msgid "Helps reduce request volume for protected paths"
msgstr ""
+msgid "Here are all your projects in your group, including the one you just created. To start, let’s take a look at your personalized learning project which will help you learn about GitLab at your own pace."
+msgstr ""
+
msgid "Here you will find recent merge request activity"
msgstr ""
@@ -11402,6 +11722,9 @@ msgid_plural "Hide charts"
msgstr[0] ""
msgstr[1] ""
+msgid "Hide details"
+msgstr ""
+
msgid "Hide file browser"
msgstr ""
@@ -11411,6 +11734,9 @@ msgstr ""
msgid "Hide host keys manual input"
msgstr "Masquer la saisie manuelle des clefs d’hôtes"
+msgid "Hide list"
+msgstr ""
+
msgid "Hide marketing-related entries from help"
msgstr ""
@@ -11431,9 +11757,6 @@ msgstr[1] "Masquer les valeurs"
msgid "Hide values"
msgstr ""
-msgid "Hiding all labels"
-msgstr ""
-
msgid "High or unknown vulnerabilities present"
msgstr ""
@@ -11518,6 +11841,9 @@ msgstr "Identifiant"
msgid "ID:"
msgstr ""
+msgid "IDE"
+msgstr ""
+
msgid "IDE|Allow live previews of JavaScript projects in the Web IDE using CodeSandbox Live Preview."
msgstr ""
@@ -11608,12 +11934,12 @@ msgstr "Si désactivé, le niveau d’accès dépendra des autorisations de l’
msgid "If enabled"
msgstr "Si activé"
+msgid "If enabled, GitLab will handle Object Storage replication using Geo. %{linkStart}More information%{linkEnd}"
+msgstr ""
+
msgid "If enabled, access to projects will be validated on an external service using their classification label."
msgstr "Si activé, l’accès aux projets sera validé sur un service externe en se basant sur leurs étiquettes de classification respectives."
-msgid "If enabled, and if object storage is enabled, GitLab will handle Object Storage replication using Geo"
-msgstr ""
-
msgid "If there is no previous license or if the previous license has expired, some GitLab functionality will be blocked until a new, valid license is uploaded."
msgstr ""
@@ -11973,6 +12299,9 @@ msgid_plural "Instances"
msgstr[0] "Instance"
msgstr[1] "Instances"
+msgid "Instance Configuration"
+msgstr ""
+
msgid "Instance Statistics visibility"
msgstr "Visibilité des statistiques de l’instance"
@@ -11994,9 +12323,6 @@ msgstr ""
msgid "Integrations"
msgstr "Intégrations"
-msgid "Integrations allow you to integrate GitLab with other applications"
-msgstr ""
-
msgid "Integrations|All details"
msgstr ""
@@ -12108,6 +12434,9 @@ msgstr ""
msgid "Invalid query"
msgstr ""
+msgid "Invalid repository bundle for snippet with id %{snippet_id}"
+msgstr ""
+
msgid "Invalid repository path"
msgstr ""
@@ -12126,6 +12455,9 @@ msgstr ""
msgid "Invalid two-factor code."
msgstr ""
+msgid "Invalid yaml"
+msgstr ""
+
msgid "Invitation"
msgstr ""
@@ -12345,15 +12677,24 @@ msgstr ""
msgid "It seems like the Dependency Scanning job ran successfully, but no dependencies have been detected in your project."
msgstr ""
+msgid "It seems that there is currently no available data for code coverage"
+msgstr ""
+
msgid "It's you"
msgstr ""
+msgid "Iteration"
+msgstr ""
+
msgid "Iteration changed to"
msgstr ""
msgid "Iteration removed"
msgstr ""
+msgid "Iteration updated"
+msgstr ""
+
msgid "Iterations"
msgstr ""
@@ -12384,12 +12725,18 @@ msgstr "janv."
msgid "January"
msgstr "janvier"
+msgid "Jira Issues"
+msgstr ""
+
msgid "Jira import is already running."
msgstr ""
msgid "Jira integration not configured."
msgstr ""
+msgid "Jira project key is not configured"
+msgstr ""
+
msgid "Jira project: %{importProject}"
msgstr ""
@@ -12570,6 +12917,9 @@ msgstr ""
msgid "Keep divergent refs"
msgstr ""
+msgid "Kerberos access denied"
+msgstr ""
+
msgid "Key"
msgstr ""
@@ -12579,10 +12929,13 @@ msgstr ""
msgid "Key: %{key}"
msgstr ""
-msgid "Keyboard Shortcuts"
+msgid "Keyboard shortcuts"
+msgstr ""
+
+msgid "Keys"
msgstr ""
-msgid "Keyboard shortcuts"
+msgid "Ki"
msgstr ""
msgid "Kubernetes"
@@ -12624,6 +12977,9 @@ msgstr ""
msgid "LDAP"
msgstr ""
+msgid "LDAP Synchronization"
+msgstr ""
+
msgid "LDAP settings"
msgstr ""
@@ -12848,12 +13204,18 @@ msgstr ""
msgid "Learn more about custom project templates"
msgstr ""
+msgid "Learn more about deploying to AWS"
+msgstr ""
+
msgid "Learn more about deploying to a cluster"
msgstr ""
msgid "Learn more about group-level project templates"
msgstr ""
+msgid "Learn more about job dependencies"
+msgstr ""
+
msgid "Learn more about signing commits"
msgstr ""
@@ -12905,9 +13267,21 @@ msgstr ""
msgid "License History"
msgstr ""
+msgid "License ID:"
+msgstr ""
+
+msgid "License URL"
+msgstr ""
+
msgid "License-Check"
msgstr ""
+msgid "LicenseCompliance|%{docLinkStart}License Approvals%{docLinkEnd} are active"
+msgstr ""
+
+msgid "LicenseCompliance|%{docLinkStart}License Approvals%{docLinkEnd} are inactive"
+msgstr ""
+
msgid "LicenseCompliance|Add a license"
msgstr ""
@@ -12929,9 +13303,15 @@ msgstr ""
msgid "LicenseCompliance|Deny"
msgstr ""
+msgid "LicenseCompliance|Learn more about %{linkStart}License Approvals%{linkEnd}"
+msgstr ""
+
msgid "LicenseCompliance|License"
msgstr ""
+msgid "LicenseCompliance|License Approvals"
+msgstr ""
+
msgid "LicenseCompliance|License Compliance detected %d license and policy violation for the source branch only; approval required"
msgid_plural "LicenseCompliance|License Compliance detected %d licenses and policy violations for the source branch only; approval required"
msgstr[0] ""
@@ -13152,6 +13532,9 @@ msgstr "Vue en liste"
msgid "List your Bitbucket Server repositories"
msgstr "Lister vos dépôts BitBucket Server"
+msgid "Lists"
+msgstr ""
+
msgid "Live preview"
msgstr "Prévisualisation"
@@ -13335,6 +13718,9 @@ msgstr ""
msgid "Manage project labels"
msgstr "Gérer les étiquettes de projet"
+msgid "Manage storage usage"
+msgstr ""
+
msgid "Manage two-factor authentication"
msgstr ""
@@ -13350,6 +13736,9 @@ msgstr "Manifeste"
msgid "Manifest file import"
msgstr "Importation de fichier manifeste"
+msgid "Manifest import"
+msgstr ""
+
msgid "Manual job"
msgstr ""
@@ -13497,6 +13886,12 @@ msgstr ""
msgid "Maximum field length"
msgstr ""
+msgid "Maximum file size is 2MB. Please select a smaller file."
+msgstr ""
+
+msgid "Maximum import size (MB)"
+msgstr ""
+
msgid "Maximum job timeout"
msgstr "Durée maximale d’exécution de la tâche"
@@ -13536,6 +13931,9 @@ msgstr ""
msgid "Maximum size of Elasticsearch bulk indexing requests."
msgstr ""
+msgid "Maximum size of import files."
+msgstr ""
+
msgid "Maximum size of individual attachments in comments."
msgstr ""
@@ -13587,6 +13985,9 @@ msgstr ""
msgid "Merge (when the pipeline succeeds)"
msgstr ""
+msgid "Merge Conflicts"
+msgstr ""
+
msgid "Merge Request"
msgstr "Demande de fusion"
@@ -13713,6 +14114,9 @@ msgstr "L’enregistrement du commentaire a échoué"
msgid "MergeRequests|Squash task canceled: another squash is already in progress."
msgstr ""
+msgid "MergeRequests|This project does not allow squashing commits when merge requests are accepted."
+msgstr ""
+
msgid "MergeRequests|Thread stays resolved"
msgstr ""
@@ -13866,6 +14270,12 @@ msgstr ""
msgid "MetricsSettings|Add a button to the metrics dashboard linking directly to your existing external dashboard."
msgstr ""
+msgid "MetricsSettings|Choose whether to display dashboard metrics in UTC or the user's local timezone."
+msgstr ""
+
+msgid "MetricsSettings|Dashboard timezone"
+msgstr ""
+
msgid "MetricsSettings|External dashboard URL"
msgstr ""
@@ -13875,6 +14285,12 @@ msgstr ""
msgid "MetricsSettings|Metrics Dashboard"
msgstr ""
+msgid "MetricsSettings|UTC (Coordinated Universal Time)"
+msgstr ""
+
+msgid "MetricsSettings|User's local timezone"
+msgstr ""
+
msgid "Metrics|Add metric"
msgstr ""
@@ -13890,6 +14306,9 @@ msgstr ""
msgid "Metrics|Create metric"
msgstr "Créer une métrique"
+msgid "Metrics|Current"
+msgstr ""
+
msgid "Metrics|Delete metric"
msgstr ""
@@ -13940,6 +14359,9 @@ msgstr ""
msgid "Metrics|Max"
msgstr ""
+msgid "Metrics|Min"
+msgstr ""
+
msgid "Metrics|Must be a valid PromQL query."
msgstr "La requête doit être une requête PromQL valide."
@@ -13955,6 +14377,9 @@ msgstr "Documentation des requêtes Prometheus"
msgid "Metrics|Refresh dashboard"
msgstr ""
+msgid "Metrics|Select a value"
+msgstr ""
+
msgid "Metrics|Star dashboard"
msgstr ""
@@ -13973,12 +14398,18 @@ msgstr "Une erreur s’est produite lors de la récupération des données d’e
msgid "Metrics|There was an error getting annotations information."
msgstr ""
+msgid "Metrics|There was an error getting dashboard validation warnings information."
+msgstr ""
+
msgid "Metrics|There was an error getting deployment information."
msgstr "Une erreur s’est produite lors de l’obtention des informations de déploiement."
msgid "Metrics|There was an error getting environments information."
msgstr "Une erreur s’est produite lors de l’obtention des informations d’environnement."
+msgid "Metrics|There was an error getting options for variable \"%{name}\"."
+msgstr ""
+
msgid "Metrics|There was an error trying to validate your query"
msgstr ""
@@ -14021,6 +14452,9 @@ msgstr ""
msgid "Metrics|You're about to permanently delete this metric. This cannot be undone."
msgstr ""
+msgid "Metrics|Your dashboard schema is invalid. Edit the dashboard to correct the YAML schema."
+msgstr ""
+
msgid "Metrics|e.g. HTTP requests"
msgstr ""
@@ -14036,12 +14470,18 @@ msgstr ""
msgid "Metrics|e.g. req/sec"
msgstr ""
+msgid "Mi"
+msgstr ""
+
msgid "Microsoft Azure"
msgstr ""
msgid "Middleman project with Static Site Editor support"
msgstr ""
+msgid "Migrate your data from an external source like GitHub, Bitbucket, or another instance of GitLab."
+msgstr ""
+
msgid "Migrated %{success_count}/%{total_count} files."
msgstr ""
@@ -14176,6 +14616,9 @@ msgstr ""
msgid "Minimum capacity to be available before we schedule more mirrors preemptively."
msgstr ""
+msgid "Minimum interval in days"
+msgstr ""
+
msgid "Minimum length is %{minimum_password_length} characters"
msgstr ""
@@ -14362,6 +14805,12 @@ msgstr ""
msgid "Multiple uploaders found: %{uploader_types}"
msgstr ""
+msgid "Must match with the %{codeStart}external_url%{codeEnd} in %{codeStart}/etc/gitlab/gitlab.rb%{codeEnd}."
+msgstr ""
+
+msgid "Must match with the %{codeStart}geo_node_name%{codeEnd} in %{codeStart}/etc/gitlab/gitlab.rb%{codeEnd}. %{linkStart}More information%{linkEnd}"
+msgstr ""
+
msgid "My Awesome Group"
msgstr ""
@@ -14476,10 +14925,16 @@ msgstr "Nouveau"
msgid "New Application"
msgstr "Nouvelle application"
+msgid "New Branch"
+msgstr ""
+
+msgid "New Deploy Key"
+msgstr ""
+
msgid "New Environment"
msgstr ""
-msgid "New Geo Node"
+msgid "New File"
msgstr ""
msgid "New Group"
@@ -14496,15 +14951,15 @@ msgid_plural "New Issues"
msgstr[0] "Nouveau ticket"
msgstr[1] "Nouveaux tickets"
-msgid "New Iteration"
-msgstr ""
-
msgid "New Jira import"
msgstr ""
msgid "New Label"
msgstr "Nouvelle étiquette"
+msgid "New Merge Request"
+msgstr ""
+
msgid "New Milestone"
msgstr ""
@@ -14523,6 +14978,9 @@ msgstr ""
msgid "New Snippet"
msgstr "Nouvel extrait de code"
+msgid "New User"
+msgstr ""
+
msgid "New branch"
msgstr "Nouvelle branche"
@@ -14565,6 +15023,9 @@ msgstr "Nouveau ticket"
msgid "New issue title"
msgstr ""
+msgid "New iteration"
+msgstr ""
+
msgid "New iteration created"
msgstr ""
@@ -14742,6 +15203,12 @@ msgstr ""
msgid "No grouping"
msgstr ""
+msgid "No iteration"
+msgstr ""
+
+msgid "No iterations to show"
+msgstr ""
+
msgid "No job log"
msgstr ""
@@ -14784,6 +15251,9 @@ msgstr ""
msgid "No other labels with such name or description"
msgstr "Aucune autre étiquette avec un tel nom ou une telle description"
+msgid "No panels matching properties %{opts}"
+msgstr ""
+
msgid "No parent group"
msgstr ""
@@ -14817,6 +15287,9 @@ msgstr "Aucun exécuteur trouvé"
msgid "No schedules"
msgstr "Aucune planification"
+msgid "No stack trace for this error"
+msgstr ""
+
msgid "No starrers matched your search"
msgstr ""
@@ -14829,7 +15302,7 @@ msgstr ""
msgid "No test coverage"
msgstr ""
-msgid "No thanks, don't show this again"
+msgid "No thanks"
msgstr ""
msgid "No vulnerabilities present"
@@ -14844,9 +15317,6 @@ msgstr ""
msgid "No, directly import the existing email addresses and usernames."
msgstr "Non, importer directement les adresses de courriel et les noms d’utilisateur existants."
-msgid "No, not interested right now"
-msgstr ""
-
msgid "No. of commits"
msgstr ""
@@ -14892,9 +15362,6 @@ msgstr "Données insuffisantes"
msgid "Not found."
msgstr ""
-msgid "Not helpful"
-msgstr ""
-
msgid "Not now"
msgstr "Pas maintenant"
@@ -15051,6 +15518,9 @@ msgstr "Notifications désactivées"
msgid "Notifications on"
msgstr "Notifications activées"
+msgid "Notify users by email when sign-in location is not recognized"
+msgstr ""
+
msgid "Nov"
msgstr "nov."
@@ -15117,9 +15587,6 @@ msgstr "Filtre"
msgid "Oh no!"
msgstr ""
-msgid "Ok let's go"
-msgstr ""
-
msgid "Oldest first"
msgstr ""
@@ -15132,16 +15599,49 @@ msgstr ""
msgid "On track"
msgstr ""
+msgid "OnDemandScans|Attached branch"
+msgstr ""
+
+msgid "OnDemandScans|Attached branch is where the scan job runs."
+msgstr ""
+
+msgid "OnDemandScans|Could not run the scan. Please try again."
+msgstr ""
+
msgid "OnDemandScans|Create new DAST scan"
msgstr ""
+msgid "OnDemandScans|DAST will scan the target URL and any discovered sub URLs."
+msgstr ""
+
+msgid "OnDemandScans|New on-demand DAST scan"
+msgstr ""
+
msgid "OnDemandScans|On-demand Scans"
msgstr ""
+msgid "OnDemandScans|On-demand scans run outside the DevOps cycle and find vulnerabilities in your projects. %{learnMoreLinkStart}Learn more%{learnMoreLinkEnd}"
+msgstr ""
+
+msgid "OnDemandScans|Only a passive scan can be performed on demand."
+msgstr ""
+
+msgid "OnDemandScans|Passive DAST Scan"
+msgstr ""
+
+msgid "OnDemandScans|Please enter a valid URL format, ex: http://www.example.com/home"
+msgstr ""
+
+msgid "OnDemandScans|Run this scan"
+msgstr ""
+
+msgid "OnDemandScans|Scan mode"
+msgstr ""
+
msgid "OnDemandScans|Schedule or run scans immediately against target sites. Currently available on-demand scan type: DAST. %{helpLinkStart}More information%{helpLinkEnd}"
msgstr ""
-msgid "Onboarding"
+msgid "OnDemandScans|Target URL"
msgstr ""
msgid "Once imported, repositories can be mirrored over SSH. Read more %{link_start}here%{link_end}."
@@ -15167,6 +15667,9 @@ msgstr ""
msgid "One or more of you personal access tokens were revoked"
msgstr ""
+msgid "One or more of your %{provider} projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
+msgstr ""
+
msgid "One or more of your Bitbucket projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
msgstr "Un ou plusieurs de vos projets Bitbucket ne peuvent être importés directement dans GitLab parce qu’ils utilisent Subversion ou Mercurial comme gestionnaire de versions au lieu de Git."
@@ -15668,6 +16171,9 @@ msgstr ""
msgid "Parent epic is not present."
msgstr ""
+msgid "Parsing error for param :embed_json. %{message}"
+msgstr ""
+
msgid "Part of merge request changes"
msgstr "Partie des modifications de la demande de fusion"
@@ -15680,6 +16186,9 @@ msgstr ""
msgid "Passed"
msgstr ""
+msgid "Passed on"
+msgstr ""
+
msgid "Password"
msgstr "Mot de Passe"
@@ -15764,6 +16273,9 @@ msgstr ""
msgid "Perform common operations on GitLab project"
msgstr ""
+msgid "Performance and resource management"
+msgstr ""
+
msgid "Performance optimization"
msgstr "Optimisation des performances"
@@ -15971,8 +16483,8 @@ msgstr "Exécuter un pipeline"
msgid "Pipelines|Something went wrong while cleaning runners cache."
msgstr "Une erreur s’est produite lors du nettoyage du cache des exécuteurs."
-msgid "Pipelines|There are currently no %{scope} pipelines."
-msgstr "Il n’y a actuellement pas de pipelines %{scope}."
+msgid "Pipelines|There are currently no finished pipelines."
+msgstr ""
msgid "Pipelines|There are currently no pipelines."
msgstr "Il n’y a actuellement aucun pipeline."
@@ -16082,6 +16594,9 @@ msgstr "Arrêter le pipeline"
msgid "Pipeline|Stop pipeline #%{pipelineId}?"
msgstr "Arrêter le pipeline numéro %{pipelineId} ?"
+msgid "Pipeline|Tag name"
+msgstr ""
+
msgid "Pipeline|Trigger author"
msgstr ""
@@ -16163,12 +16678,18 @@ msgstr ""
msgid "Please check your email (%{email}) to verify that you own this address and unlock the power of CI/CD. Didn't receive it? %{resend_link}. Wrong email address? %{update_link}."
msgstr ""
+msgid "Please choose a file"
+msgstr ""
+
msgid "Please choose a group URL with no special characters."
msgstr "Veuillez choisir une URL de groupe sans caractères spéciaux."
msgid "Please complete your profile with email address"
msgstr ""
+msgid "Please convert %{linkStart}them to Git%{linkEnd}, and go through the %{linkToImportFlow} again."
+msgstr ""
+
msgid "Please convert them to %{link_to_git}, and go through the %{link_to_import_flow} again."
msgstr "Veuillez les convertir en %{link_to_git} et repasser par %{link_to_import_flow}."
@@ -16226,6 +16747,9 @@ msgstr ""
msgid "Please provide a valid email address."
msgstr ""
+msgid "Please provide attributes to update"
+msgstr ""
+
msgid "Please refer to <a href=\"%{docs_url}\">%{docs_url}</a>"
msgstr ""
@@ -16961,6 +17485,9 @@ msgstr ""
msgid "Project has too many %{label_for_message} to search"
msgstr ""
+msgid "Project is required when cluster_type is :project"
+msgstr ""
+
msgid "Project members"
msgstr ""
@@ -17078,7 +17605,40 @@ msgstr ""
msgid "ProjectService|Comment"
msgstr ""
-msgid "ProjectService|Comment will be posted on each event"
+msgid "ProjectService|Event will be triggered by a push to the repository"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a commit is created/updated"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a confidential issue is created/updated/closed"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a deployment finishes"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a merge request is created/updated/merged"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a new tag is pushed to the repository"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a new, unique alert is recorded"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a pipeline status changes"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a wiki page is created/updated"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when an issue is created/updated/closed"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when someone adds a comment"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when someone adds a comment on a confidential issue"
msgstr ""
msgid "ProjectService|Perform common operations on GitLab project: %{project_name}"
@@ -17129,6 +17689,9 @@ msgstr ""
msgid "ProjectSettings|Enable 'Delete source branch' option by default"
msgstr ""
+msgid "ProjectSettings|Enable merge trains and pipelines for merged results"
+msgstr ""
+
msgid "ProjectSettings|Every merge creates a merge commit"
msgstr ""
@@ -17159,7 +17722,7 @@ msgstr ""
msgid "ProjectSettings|Forks"
msgstr ""
-msgid "ProjectSettings|Git Large File Storage"
+msgid "ProjectSettings|Git Large File Storage (LFS)"
msgstr ""
msgid "ProjectSettings|Internal"
@@ -17195,9 +17758,6 @@ msgstr ""
msgid "ProjectSettings|Merge options"
msgstr ""
-msgid "ProjectSettings|Merge pipelines will try to validate the post-merge result prior to merging"
-msgstr ""
-
msgid "ProjectSettings|Merge requests"
msgstr ""
@@ -17225,6 +17785,9 @@ msgstr ""
msgid "ProjectSettings|Pipelines"
msgstr ""
+msgid "ProjectSettings|Pipelines for merge requests must be enabled in the CI/CD configuration file, or pipelines could be unresolvable or dropped"
+msgstr ""
+
msgid "ProjectSettings|Pipelines must succeed"
msgstr ""
@@ -17252,6 +17815,9 @@ msgstr ""
msgid "ProjectSettings|Show link to create/view merge request when pushing from the command line"
msgstr ""
+msgid "ProjectSettings|Skipped pipelines are considered successful"
+msgstr ""
+
msgid "ProjectSettings|Snippets"
msgstr ""
@@ -17267,6 +17833,9 @@ msgstr ""
msgid "ProjectSettings|These checks must pass before merge requests can be merged"
msgstr ""
+msgid "ProjectSettings|This introduces the risk of merging changes that will not pass the pipeline."
+msgstr ""
+
msgid "ProjectSettings|This setting is applied on the server level and can be overridden by an admin."
msgstr "Ce paramètre s’applique au niveau du serveur mais il peut être outrepassé par un administrateur."
@@ -17444,15 +18013,27 @@ msgstr ""
msgid "ProjectsNew|Blank project"
msgstr ""
+msgid "ProjectsNew|Connect your external repository to GitLab CI/CD."
+msgstr ""
+
msgid "ProjectsNew|Contact an administrator to enable options for importing your project."
msgstr ""
msgid "ProjectsNew|Create"
msgstr ""
+msgid "ProjectsNew|Create a blank project to house your files, plan your work, and collaborate on code, among other things."
+msgstr ""
+
+msgid "ProjectsNew|Create blank project"
+msgstr ""
+
msgid "ProjectsNew|Create from template"
msgstr ""
+msgid "ProjectsNew|Create new project"
+msgstr ""
+
msgid "ProjectsNew|Creating project & repository."
msgstr ""
@@ -17477,6 +18058,9 @@ msgstr ""
msgid "ProjectsNew|Project description %{tag_start}(optional)%{tag_end}"
msgstr ""
+msgid "ProjectsNew|Run CI/CD for external repository"
+msgstr ""
+
msgid "ProjectsNew|Template"
msgstr ""
@@ -17738,6 +18322,9 @@ msgstr "Protégé"
msgid "Protected Branch"
msgstr ""
+msgid "Protected Branches"
+msgstr ""
+
msgid "Protected Environment"
msgstr ""
@@ -17750,6 +18337,9 @@ msgstr ""
msgid "Protected Tag"
msgstr ""
+msgid "Protected Tags"
+msgstr ""
+
msgid "Protected branches"
msgstr ""
@@ -17849,6 +18439,9 @@ msgstr ""
msgid "Provider"
msgstr "Fournisseur"
+msgid "Proxy support for this API is not available currently"
+msgstr ""
+
msgid "Pseudonymizer data collection"
msgstr "Collecte de données Pseudonymizer"
@@ -17873,6 +18466,9 @@ msgstr ""
msgid "Publish to status page"
msgstr ""
+msgid "Published on status page"
+msgstr ""
+
msgid "Publishes this issue to the associated status page."
msgstr ""
@@ -18047,6 +18643,9 @@ msgstr "Recherches récentes"
msgid "Recipe"
msgstr ""
+msgid "Reconfigure"
+msgstr ""
+
msgid "Recover hidden stage"
msgstr ""
@@ -18133,6 +18732,9 @@ msgstr ""
msgid "Registration|Your profile"
msgstr ""
+msgid "Regulate approvals by authors/committers, based on compliance frameworks. Can be changed only at the instance level."
+msgstr ""
+
msgid "Rejected (closed)"
msgstr ""
@@ -18183,15 +18785,24 @@ msgstr ""
msgid "ReleaseAssetLinkType|Image"
msgstr ""
+msgid "ReleaseAssetLinkType|Images"
+msgstr ""
+
msgid "ReleaseAssetLinkType|Other"
msgstr ""
msgid "ReleaseAssetLinkType|Package"
msgstr ""
+msgid "ReleaseAssetLinkType|Packages"
+msgstr ""
+
msgid "ReleaseAssetLinkType|Runbook"
msgstr ""
+msgid "ReleaseAssetLinkType|Runbooks"
+msgstr ""
+
msgid "Releases"
msgstr ""
@@ -18204,6 +18815,9 @@ msgstr ""
msgid "Releases documentation"
msgstr ""
+msgid "Releases|New Release"
+msgstr ""
+
msgid "Release|Something went wrong while getting the release details"
msgstr ""
@@ -18276,6 +18890,9 @@ msgstr ""
msgid "Remove fork relationship"
msgstr ""
+msgid "Remove from batch"
+msgstr ""
+
msgid "Remove from board"
msgstr ""
@@ -18465,6 +19082,12 @@ msgstr ""
msgid "Replication"
msgstr ""
+msgid "Replication enabled"
+msgstr ""
+
+msgid "Replication paused"
+msgstr ""
+
msgid "Reply by email"
msgstr ""
@@ -18492,15 +19115,15 @@ msgstr ""
msgid "Reported %{timeAgo} by %{reportedBy}"
msgstr ""
-msgid "Reported Resource Changes: %{addNum} to add, %{changeNum} to change, %{deleteNum} to delete"
-msgstr ""
-
msgid "Reporter"
msgstr ""
msgid "Reporting"
msgstr "Rapports"
+msgid "Reports"
+msgstr ""
+
msgid "Reports|%{combinedString} and %{resolvedString}"
msgstr ""
@@ -18539,6 +19162,9 @@ msgstr "Durée d’exécution"
msgid "Reports|Failure"
msgstr "Échec"
+msgid "Reports|Identifier"
+msgstr ""
+
msgid "Reports|Metrics reports are loading"
msgstr ""
@@ -18551,6 +19177,9 @@ msgstr ""
msgid "Reports|Metrics reports failed loading results"
msgstr ""
+msgid "Reports|Scanner"
+msgstr ""
+
msgid "Reports|Severity"
msgstr "Sévérité"
@@ -18599,15 +19228,33 @@ msgstr ""
msgid "Repository cleanup has started. You will receive an email once the cleanup operation is complete."
msgstr ""
+msgid "Repository files count over the limit"
+msgstr ""
+
+msgid "Repository has an invalid default branch name."
+msgstr ""
+
+msgid "Repository has more than one branch."
+msgstr ""
+
msgid "Repository has no locks."
msgstr "Le dépôt n’a aucun verrou."
+msgid "Repository has tags."
+msgstr ""
+
msgid "Repository maintenance"
msgstr "Maintenance du dépôt"
msgid "Repository mirroring"
msgstr ""
+msgid "Repository must contain at least 1 file."
+msgstr ""
+
+msgid "Repository size is above the limit."
+msgstr ""
+
msgid "Repository static objects"
msgstr ""
@@ -18617,7 +19264,7 @@ msgstr "Stockage du dépôt"
msgid "Repository sync capacity"
msgstr ""
-msgid "Repository: %{counter_repositories} / Wikis: %{counter_wikis} / Build Artifacts: %{counter_build_artifacts} / LFS: %{counter_lfs_objects}"
+msgid "Repository: %{counter_repositories} / Wikis: %{counter_wikis} / Build Artifacts: %{counter_build_artifacts} / LFS: %{counter_lfs_objects} / Snippets: %{counter_snippets}"
msgstr ""
msgid "RepositorySettingsAccessLevel|Select"
@@ -18629,6 +19276,9 @@ msgstr "Demander l’accès"
msgid "Request Headers"
msgstr ""
+msgid "Request details"
+msgstr ""
+
msgid "Request parameter %{param} is missing."
msgstr ""
@@ -18735,6 +19385,9 @@ msgstr "Réinitialiser le jeton d’inscription des exécuteurs"
msgid "Reset template"
msgstr ""
+msgid "Reset to project defaults"
+msgstr ""
+
msgid "Resetting the authorization key for this project will require updating the authorization key in every alert source it is enabled in."
msgstr ""
@@ -18831,9 +19484,6 @@ msgstr ""
msgid "Resume"
msgstr "Reprendre"
-msgid "Resume replication"
-msgstr ""
-
msgid "Resync"
msgstr ""
@@ -19456,6 +20106,9 @@ msgstr ""
msgid "SecurityReports|False positive"
msgstr ""
+msgid "SecurityReports|Group Security Dashboard"
+msgstr ""
+
msgid "SecurityReports|Hide dismissed"
msgstr ""
@@ -19510,10 +20163,13 @@ msgstr ""
msgid "SecurityReports|Remove project from dashboard"
msgstr ""
-msgid "SecurityReports|Report type"
+msgid "SecurityReports|Return to dashboard"
msgstr ""
-msgid "SecurityReports|Return to dashboard"
+msgid "SecurityReports|Scan details"
+msgstr ""
+
+msgid "SecurityReports|Scanner type"
msgstr ""
msgid "SecurityReports|Security Dashboard"
@@ -19570,6 +20226,9 @@ msgstr ""
msgid "SecurityReports|There was an error while generating the report."
msgstr ""
+msgid "SecurityReports|Unable to add %{invalidProjectsMessage}"
+msgstr ""
+
msgid "SecurityReports|Unable to add %{invalidProjects}"
msgstr ""
@@ -19684,6 +20343,9 @@ msgstr "Sélectionner une branche ou une étiquette"
msgid "Select due date"
msgstr ""
+msgid "Select file"
+msgstr ""
+
msgid "Select group or project"
msgstr ""
@@ -19747,9 +20409,6 @@ msgstr "Sélectionner une branche cible"
msgid "Select the branch you want to set as the default for this project. All merge requests and commits will automatically be made against this branch unless you specify a different one."
msgstr "Sélectionnez la branche que vous souhaitez définir comme branche par défaut pour ce projet. Toutes les demandes de fusion et les commits seront automatiquement effectués sur cette branche, à moins que vous n’en spécifiez une autre."
-msgid "Select the configured storage available for new repositories to be placed on."
-msgstr ""
-
msgid "Select the custom project template source group."
msgstr "Sélectionnez le groupe source de modèles de projet personnalisés."
@@ -19942,12 +20601,6 @@ msgstr "Service d’assistance"
msgid "Service Desk is enabled but not yet active"
msgstr ""
-msgid "Service Desk is off"
-msgstr ""
-
-msgid "Service Desk is on"
-msgstr ""
-
msgid "Service Templates"
msgstr "Modèles de service"
@@ -20035,6 +20688,15 @@ msgstr ""
msgid "Set the milestone to %{milestone_reference}."
msgstr ""
+msgid "Set the number of concurrent requests this secondary node will make to the primary node while backfilling."
+msgstr ""
+
+msgid "Set the synchronization and verification capacity for the secondary node."
+msgstr ""
+
+msgid "Set the timeout in seconds to send a secondary node status to the primary and IPs allowed for the secondary nodes."
+msgstr ""
+
msgid "Set time estimate"
msgstr ""
@@ -20074,6 +20736,9 @@ msgstr ""
msgid "Set weight to %{weight}."
msgstr ""
+msgid "Set what should be replicated by choosing specific projects or groups by the secondary node."
+msgstr ""
+
msgid "SetPasswordToCloneLink|set a password"
msgstr "définir un mot de passe"
@@ -20200,6 +20865,9 @@ msgstr ""
msgid "Show complete raw log"
msgstr "Afficher le journal brut complet"
+msgid "Show details"
+msgstr ""
+
msgid "Show file browser"
msgstr ""
@@ -20209,10 +20877,13 @@ msgstr ""
msgid "Show latest version"
msgstr "Afficher la dernière version"
+msgid "Show list"
+msgstr ""
+
msgid "Show me everything"
msgstr ""
-msgid "Show me how"
+msgid "Show me how to add a pipeline"
msgstr ""
msgid "Show me more advanced stuff"
@@ -20261,9 +20932,6 @@ msgstr ""
msgid "Showing all issues"
msgstr ""
-msgid "Showing all labels"
-msgstr ""
-
msgid "Showing last %{size} of log -"
msgstr ""
@@ -20375,9 +21043,6 @@ msgstr ""
msgid "Skip outdated deployment jobs"
msgstr ""
-msgid "Skip this for now"
-msgstr ""
-
msgid "Skipped"
msgstr ""
@@ -20522,6 +21187,9 @@ msgstr "Une erreur s’est produite lors du basculement du bouton"
msgid "Something went wrong while adding your award. Please try again."
msgstr ""
+msgid "Something went wrong while applying the batch of suggestions. Please try again."
+msgstr ""
+
msgid "Something went wrong while applying the suggestion. Please try again."
msgstr ""
@@ -20624,10 +21292,13 @@ msgstr ""
msgid "Something went wrong while updating a requirement."
msgstr ""
+msgid "Something went wrong while updating assignees"
+msgstr ""
+
msgid "Something went wrong while updating your list settings"
msgstr ""
-msgid "Something went wrong with your automatic subscription renewal"
+msgid "Something went wrong with your automatic subscription renewal."
msgstr ""
msgid "Something went wrong, unable to add %{project} to dashboard"
@@ -20645,6 +21316,9 @@ msgstr "Une erreur est survenue, impossible de supprimer le projet"
msgid "Something went wrong, unable to search projects"
msgstr ""
+msgid "Something went wrong."
+msgstr ""
+
msgid "Something went wrong. Please try again."
msgstr "Quelque chose s’est mal passé. Veuillez réessayer."
@@ -20828,6 +21502,9 @@ msgstr "Source (branche ou étiquette)"
msgid "Source code"
msgstr "Code source"
+msgid "Source code (%{fileExtension})"
+msgstr ""
+
msgid "Source is not available"
msgstr "La source n’est pas disponible"
@@ -20912,6 +21589,9 @@ msgstr "Combiner (squash) les commits"
msgid "Stack trace"
msgstr ""
+msgid "Stacktrace snippet"
+msgstr ""
+
msgid "Stage"
msgstr "Étape"
@@ -21017,6 +21697,9 @@ msgstr ""
msgid "Start thread & reopen %{noteable_name}"
msgstr ""
+msgid "Start using Directed Acyclic Graphs (DAG)"
+msgstr ""
+
msgid "Start your Free Gold Trial"
msgstr ""
@@ -21479,6 +22162,21 @@ msgstr ""
msgid "SuggestedColors|Very pale orange"
msgstr ""
+msgid "Suggestion is not applicable as the suggestion was not found."
+msgstr ""
+
+msgid "Suggestions are not applicable as one or more suggestions were not found."
+msgstr ""
+
+msgid "Suggestions are not applicable as their lines cannot overlap."
+msgstr ""
+
+msgid "Suggestions must all be on the same branch."
+msgstr ""
+
+msgid "Suggestions that change line count can't be added to batches, yet."
+msgstr ""
+
msgid "Suggestions:"
msgstr ""
@@ -21521,9 +22219,6 @@ msgstr ""
msgid "Synchronization disabled"
msgstr ""
-msgid "Synchronization of container repositories is disabled."
-msgstr ""
-
msgid "System"
msgstr ""
@@ -21704,6 +22399,34 @@ msgstr "Conditions générales d’utilisation et politique de confidentialité"
msgid "Terms of Service and Privacy Policy"
msgstr "Conditions générales d’utilisation et politique de confidentialité"
+msgid "Terraform|%{number} Terraform report failed to generate"
+msgid_plural "Terraform|%{number} Terraform reports failed to generate"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Terraform|%{number} Terraform report was generated in your pipelines"
+msgid_plural "Terraform|%{number} Terraform reports were generated in your pipelines"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Terraform|A Terraform report failed to generate."
+msgstr ""
+
+msgid "Terraform|A Terraform report was generated in your pipelines."
+msgstr ""
+
+msgid "Terraform|Generating the report caused an error."
+msgstr ""
+
+msgid "Terraform|Reported Resource Changes: %{addNum} to add, %{changeNum} to change, %{deleteNum} to delete"
+msgstr ""
+
+msgid "Terraform|The Terraform report %{name} failed to generate."
+msgstr ""
+
+msgid "Terraform|The Terraform report %{name} was generated in your pipelines."
+msgstr ""
+
msgid "Test"
msgstr ""
@@ -21730,7 +22453,7 @@ msgstr ""
msgid "TestHooks|Ensure the project has CI pipelines."
msgstr ""
-msgid "TestHooks|Ensure the project has at least one commit."
+msgid "TestHooks|Ensure the project has deployments."
msgstr ""
msgid "TestHooks|Ensure the project has issues."
@@ -21831,7 +22554,7 @@ msgstr "Le système de suivi est un endroit où l’on peut ouvrir un ticket pou
msgid "The Prometheus server responded with \"bad request\". Please check your queries are correct and are supported in your Prometheus version. %{documentationLink}"
msgstr ""
-msgid "The URL defined on the primary node that secondary nodes should use to contact it. Defaults to URL"
+msgid "The URL defined on the primary node that secondary nodes should use to contact it."
msgstr ""
msgid "The URL to use for connecting to Elasticsearch. Use a comma-separated list to support clustering (e.g., \"http://localhost:9200, http://localhost:9201\")."
@@ -21840,6 +22563,9 @@ msgstr ""
msgid "The X509 Certificate to use when mutual TLS is required to communicate with the external authorization service. If left blank, the server certificate is still validated when accessing over HTTPS."
msgstr "Le certificat X.509 à utiliser lorsque l’authentification TLS mutuelle est requise pour communiquer avec le service d’autorisation externe. Si ce champ est vide, le certificat du serveur est tout de même validé lors de l’accès via HTTPS."
+msgid "The above settings apply to all projects with the selected compliance framework(s)."
+msgstr ""
+
msgid "The amount of seconds after which a request to get a secondary node status will time out."
msgstr ""
@@ -22139,9 +22865,6 @@ msgstr "Le temps pris par chaque entrée récoltée durant cette étape."
msgid "The total stage shows the time it takes between creating an issue and deploying the code to production. The data will be automatically added once you have completed the full idea to production cycle."
msgstr ""
-msgid "The unique identifier for the Geo node. Must match %{geoNodeName} if it is set in gitlab.rb, otherwise it must match %{externalUrl} with a trailing slash"
-msgstr ""
-
msgid "The update action will time out after %{number_of_minutes} minutes. For big repositories, use a clone/push combination."
msgstr "L’action de mise à jour expirera au bout de %{number_of_minutes} minutes. Pour les gros dépôts, utilisez une combinaison de clone et push."
@@ -22166,9 +22889,6 @@ msgstr "La carte des utilisateurs met en correspondance les utilisateurs de FogB
msgid "The user you are trying to deactivate has been active in the past %{minimum_inactive_days} days and cannot be deactivated"
msgstr ""
-msgid "The user-facing URL of the Geo node"
-msgstr ""
-
msgid "The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6."
msgstr "La valeur située au point médian d’une série de valeur observée. Par exemple., entre 3, 5 et 9, le médian est 5. Entre 3, 5, 7 et 8, le médian est (5+7)/2 = 6."
@@ -22259,6 +22979,9 @@ msgstr ""
msgid "There was a problem fetching project branches."
msgstr ""
+msgid "There was a problem fetching project tags."
+msgstr ""
+
msgid "There was a problem fetching project users."
msgstr ""
@@ -22310,6 +23033,9 @@ msgstr ""
msgid "There was an error fetching the %{replicableType}"
msgstr ""
+msgid "There was an error fetching the Geo Settings"
+msgstr ""
+
msgid "There was an error fetching the Node's Groups"
msgstr ""
@@ -22430,6 +23156,12 @@ msgstr ""
msgid "This %{issuable} is locked. Only <strong>project members</strong> can comment."
msgstr ""
+msgid "This %{noteableTypeText} is %{confidentialLinkStart}confidential%{linkEnd} and %{lockedLinkStart}locked%{linkEnd}."
+msgstr ""
+
+msgid "This %{noteableTypeText} is locked."
+msgstr ""
+
msgid "This %{viewer} could not be displayed because %{reason}. You can %{options} instead."
msgstr ""
@@ -22448,6 +23180,9 @@ msgstr ""
msgid "This action can lead to data loss. To prevent accidental actions we ask you to confirm your intention."
msgstr ""
+msgid "This also resolves all related threads"
+msgstr ""
+
msgid "This also resolves the discussion"
msgstr ""
@@ -22538,6 +23273,9 @@ msgstr ""
msgid "This epic does not exist or you don't have sufficient permission."
msgstr ""
+msgid "This feature is currently in beta. We invite you to %{linkStart}give feedback%{linkEnd}."
+msgstr ""
+
msgid "This feature requires local storage to be enabled"
msgstr ""
@@ -22571,8 +23309,8 @@ msgstr ""
msgid "This is a Work in Progress"
msgstr ""
-msgid "This is a confidential issue."
-msgstr "Ce ticket est confidentiel."
+msgid "This is a confidential %{noteableTypeText}."
+msgstr ""
msgid "This is a delayed job to run in %{remainingTime}"
msgstr "Il s’agit d’une tâche différée devant être exécutée dans %{remainingTime}"
@@ -22598,9 +23336,6 @@ msgstr ""
msgid "This is your current session"
msgstr ""
-msgid "This issue is %{confidentialLinkStart}confidential%{linkEnd} and %{lockedLinkStart}locked%{linkEnd}."
-msgstr ""
-
msgid "This issue is confidential"
msgstr "Ce ticket est confidentiel"
@@ -22610,9 +23345,6 @@ msgstr ""
msgid "This issue is in a child epic of the filtered epic"
msgstr ""
-msgid "This issue is locked."
-msgstr "Ce ticket est verrouillé."
-
msgid "This job depends on other jobs with expired/erased artifacts: %{invalid_dependencies}"
msgstr ""
@@ -22712,6 +23444,9 @@ msgstr ""
msgid "This license has already expired."
msgstr ""
+msgid "This link points to external content"
+msgstr ""
+
msgid "This may expose confidential information as the selected fork is in another namespace that can have other members."
msgstr ""
@@ -22739,6 +23474,9 @@ msgstr "Cette page n’est pas disponible car vous n’êtes pas autorisé à li
msgid "This page will be removed in a future release."
msgstr "Cette page sera supprimée dans une version ultérieure."
+msgid "This pipeline does not use the %{codeStart}needs%{codeEnd} keyword and can't be represented as a directed acyclic graph."
+msgstr ""
+
msgid "This pipeline makes use of a predefined CI/CD configuration enabled by %{strongStart}Auto DevOps.%{strongEnd}"
msgstr ""
@@ -22967,6 +23705,9 @@ msgstr ""
msgid "Time from last commit to merge"
msgstr ""
+msgid "Time in seconds"
+msgstr ""
+
msgid "Time in seconds GitLab will wait for a response from the external service. When the service does not respond in time, access will be denied."
msgstr "Délai d’attente, en secondes, d’une réponse du service externe. Lorsque le service ne répond pas à temps, l’accès sera refusé."
@@ -23388,9 +24129,15 @@ msgstr "Total des contributions"
msgid "Total artifacts size: %{total_size}"
msgstr ""
+msgid "Total cores (CPUs)"
+msgstr ""
+
msgid "Total issues"
msgstr ""
+msgid "Total memory (GB)"
+msgstr ""
+
msgid "Total test time for all commits/merges"
msgstr "Temps total de test pour tous les commits/fusions"
@@ -23592,6 +24339,9 @@ msgstr ""
msgid "U2F only works with HTTPS-enabled websites. Contact your administrator for more details."
msgstr ""
+msgid "UI Development Kit"
+msgstr ""
+
msgid "URL"
msgstr ""
@@ -23664,9 +24414,6 @@ msgstr "Impossible de charger le diff. %{button_try_again}"
msgid "Unable to load the merge request widget. Try reloading the page."
msgstr ""
-msgid "Unable to process group import file"
-msgstr ""
-
msgid "Unable to resolve"
msgstr ""
@@ -23682,6 +24429,9 @@ msgstr ""
msgid "Unable to sign you in to the group with SAML due to \"%{reason}\""
msgstr "Impossible de vous connecter au groupe via SAML en raison de « %{reason} »"
+msgid "Unable to suggest a path. Please refresh and try again."
+msgstr ""
+
msgid "Unable to update label prioritization at this time"
msgstr ""
@@ -23697,6 +24447,9 @@ msgstr ""
msgid "Unarchiving the project will restore people's ability to make changes to it. The repository can be committed to, and issues, comments, and other entities can be created. %{strong_start}Once active, this project shows up in the search and on the dashboard.%{strong_end}"
msgstr ""
+msgid "Unassign from commenting user"
+msgstr ""
+
msgid "Unblock"
msgstr ""
@@ -23772,6 +24525,9 @@ msgstr ""
msgid "Unreachable"
msgstr ""
+msgid "Unrecognized cluster type"
+msgstr ""
+
msgid "Unresolve"
msgstr ""
@@ -23832,6 +24588,9 @@ msgstr ""
msgid "Unsubscribes from this %{quick_action_target}."
msgstr ""
+msgid "Unsupported todo type passed. Supported todo types are: %{todo_types}"
+msgstr ""
+
msgid "Until"
msgstr ""
@@ -23859,6 +24618,9 @@ msgstr ""
msgid "Update approval rule"
msgstr ""
+msgid "Update approvers"
+msgstr ""
+
msgid "Update failed"
msgstr ""
@@ -23868,6 +24630,9 @@ msgstr ""
msgid "Update it"
msgstr ""
+msgid "Update iteration"
+msgstr ""
+
msgid "Update now"
msgstr "Mettre à jour maintenant"
@@ -23913,7 +24678,7 @@ msgstr ""
msgid "Updated at"
msgstr ""
-msgid "Updated to"
+msgid "Updated to %{linkStart}chart v%{linkEnd}"
msgstr ""
msgid "Updating"
@@ -23943,6 +24708,9 @@ msgstr "Téléversez le fichier <code>GoogleCodeProjectHosting.json</code> ici 
msgid "Upload CSV file"
msgstr ""
+msgid "Upload License"
+msgstr ""
+
msgid "Upload New File"
msgstr "Téléverser un nouveau fichier"
@@ -24024,16 +24792,19 @@ msgstr ""
msgid "UsageQuota|Pipelines"
msgstr ""
+msgid "UsageQuota|Purchase more storage"
+msgstr ""
+
msgid "UsageQuota|Repositories"
msgstr ""
msgid "UsageQuota|Repository"
msgstr ""
-msgid "UsageQuota|Storage"
+msgid "UsageQuota|Snippets"
msgstr ""
-msgid "UsageQuota|Storage usage:"
+msgid "UsageQuota|Storage"
msgstr ""
msgid "UsageQuota|This namespace has no projects which use shared runners"
@@ -24066,6 +24837,12 @@ msgstr ""
msgid "UsageQuota|Wikis"
msgstr ""
+msgid "UsageQuota|You used: %{usage} %{limit}"
+msgstr ""
+
+msgid "UsageQuota|out of %{formattedLimit} of your namespace storage"
+msgstr ""
+
msgid "Use %{code_start}::%{code_end} to create a %{link_start}scoped label set%{link_end} (eg. %{code_start}priority::1%{code_end})"
msgstr ""
@@ -24132,6 +24909,9 @@ msgstr "Les cohortes d’utilisateurs ne sont affichées que lorsque la %{usage_
msgid "User IDs"
msgstr ""
+msgid "User Lists can only be created and modified with %{linkStart}the API%{linkEnd}"
+msgstr ""
+
msgid "User OAuth applications"
msgstr ""
@@ -24156,6 +24936,9 @@ msgstr ""
msgid "User key was successfully removed."
msgstr ""
+msgid "User list %{name} will be removed. Are you sure?"
+msgstr ""
+
msgid "User map"
msgstr "Correspondance entre utilisateurs"
@@ -24177,148 +24960,10 @@ msgstr ""
msgid "User was successfully updated."
msgstr ""
-msgid "UserOnboardingTour|%{activeTour}/%{totalTours}"
-msgstr ""
-
-msgid "UserOnboardingTour|%{completed}/%{total} steps completed"
-msgstr ""
-
-msgid "UserOnboardingTour|%{emphasisStart}Well done!%{emphasisEnd}%{lineBreak}%{lineBreak}That's it for our guided tour, congratulations for making it all the way to the end!%{lineBreak}%{lineBreak}We hope this gave you a good overview of GitLab and how it can help you. We'll now show you how to create your own project and invite your colleagues."
-msgstr ""
-
-msgid "UserOnboardingTour|Adding other members to a project is done through Project Settings. Click on %{emphasisStart}Settings%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Alright, that's it for Commits. Let's take a look at the %{emphasisStart}Branches%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Awesome! Now click on %{emphasisStart}Members%{emphasisEnd}."
+msgid "UserList|Delete %{name}?"
msgstr ""
-msgid "UserOnboardingTour|Click on one of the %{emphasisStart}Compare%{emphasisEnd} buttons to compare a branch to master."
-msgstr ""
-
-msgid "UserOnboardingTour|Click on one of the %{emphasisStart}pipeline IDs%{emphasisEnd} to see the details of a pipeline."
-msgstr ""
-
-msgid "UserOnboardingTour|Click to open the latest commit to see its details."
-msgstr ""
-
-msgid "UserOnboardingTour|Close 'Learn GitLab'"
-msgstr ""
-
-msgid "UserOnboardingTour|Commits are shown in chronological order and can be filtered by the commit message or by the branch."
-msgstr ""
-
-msgid "UserOnboardingTour|Create a project"
-msgstr ""
-
-msgid "UserOnboardingTour|Exit 'Learn GitLab'"
-msgstr ""
-
-msgid "UserOnboardingTour|Got it"
-msgstr ""
-
-msgid "UserOnboardingTour|Great job! %{clapHands} We hope the tour was helpful and that you learned how to use GitLab.%{lineBreak}%{lineBreak}We'd love to get your feedback on this tour.%{lineBreak}%{lineBreak}%{emphasisStart}How helpful would you say this guided tour was?%{emphasisEnd}%{lineBreak}%{lineBreak}"
-msgstr ""
-
-msgid "UserOnboardingTour|Guided GitLab Tour"
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can compare the changes of this branch to another one. Changes are divided by files so that it's easier to see what was changed where."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can create a project from scratch, start with a template or import a repository from other platforms. Whatever you choose, we'll guide you through the process.%{lineBreak}%{lineBreak}Fill in your new project information and click on %{emphasisStart}Create Project%{emphasisEnd} to progress to the next step."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can see the breakdown of the pipelines: its stages and jobs in each of the stages and their status.%{lineBreak}%{lineBreak}Our CI/CD pipelines are quite complex, most of our users have fewer and simpler pipelines."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can see the current members of the project (just you at the moment) and invite new members.%{lineBreak}%{lineBreak}You can invite multiple members at once (existing GitLab users or invite by email) and you can also set their roles and permissions.%{lineBreak}%{lineBreak}Add a few members and click on %{emphasisStart}Add to project%{emphasisEnd} to complete this step."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can see what changes were made with this commit, on what branch and if there's a related merge request. The status of the pipeline will also show up if CI/CD is set up.%{lineBreak}%{lineBreak}You can also comment on the lines of code that were changed and start a discussion with your colleagues!"
-msgstr ""
-
-msgid "UserOnboardingTour|Here's an overview of branches in the %{emphasisStart}%{projectName}%{emphasisEnd} project. They're split into Active and Stale.%{lineBreak}%{lineBreak}From here, you can create a new merge request from a branch, or compare the branch to any other branch in the project. By default, it will compare it to the master branch."
-msgstr ""
-
-msgid "UserOnboardingTour|Invite colleagues"
-msgstr ""
-
-msgid "UserOnboardingTour|Issues are great for communicating and keeping track of progress in GitLab. These are all issues that are open in the %{emphasisStart}%{projectName}%{emphasisEnd}.%{lineBreak}%{lineBreak}You can help us improve GitLab by contributing work to issues that are labeled <span class=\"badge color-label accept-mr-label\">Accepting merge requests</span>.%{lineBreak}%{lineBreak}This list can be filtered by labels, milestones, assignees, authors... We'll show you how it looks when the list is filtered by a label."
-msgstr ""
-
-msgid "UserOnboardingTour|Learn GitLab"
-msgstr ""
-
-msgid "UserOnboardingTour|Let's take a closer look at a merge request. Click on the title of one."
-msgstr ""
-
-msgid "UserOnboardingTour|Let's take a closer look at all the commits. Click on %{emphasisStart}Commits%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Let's take a closer look at the repository of this project. Click on %{emphasisStart}Repository%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|No thanks"
-msgstr ""
-
-msgid "UserOnboardingTour|Ok, let's go"
-msgstr ""
-
-msgid "UserOnboardingTour|Ok, show me"
-msgstr ""
-
-msgid "UserOnboardingTour|Open one of the issues by clicking on its title."
-msgstr ""
-
-msgid "UserOnboardingTour|Restart this step"
-msgstr ""
-
-msgid "UserOnboardingTour|Skip this step"
-msgstr ""
-
-msgid "UserOnboardingTour|Sweet! Your project was created and is ready to be used.%{lineBreak}%{lineBreak}You can start adding files to the repository or clone it. One last thing we want to show you is how to invite your colleagues to your new project."
-msgstr ""
-
-msgid "UserOnboardingTour|Take a look. Here's a nifty menu for quickly creating issues, merge requests, snippets, projects and groups. Click on it and select \"New project\" from the \"GitLab\" section to get started."
-msgstr ""
-
-msgid "UserOnboardingTour|Thanks for taking the guided tour. Remember, if you want to go through it again, you can start %{emphasisStart}Learn GitLab%{emphasisEnd} in the help menu on the top right."
-msgstr ""
-
-msgid "UserOnboardingTour|Thanks for the feedback! %{thumbsUp}"
-msgstr ""
-
-msgid "UserOnboardingTour|That's it for issues. Let'st take a look at %{emphasisStart}Merge Requests%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|That's it for merge requests. Now for the final part of this guided tour - the %{emphasisStart}CI/CD%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|That's it for the Repository. Let's take a look at the %{emphasisStart}Issues%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|The structure of this page is very similar to issues. Status, description, discussion and the sidebar are all here.%{lineBreak}%{lineBreak}But take a look below the description and you'll notice that there's more information about the merge request, the CI/CD pipeline and the options for approving it.%{lineBreak}%{lineBreak}Alongside the discussion you can also see more information about commits in this merge request, the status of pipelines and review all changes that were made."
-msgstr ""
-
-msgid "UserOnboardingTour|There's a lot of information here but don't worry, we'll go through it.%{lineBreak}%{lineBreak}On the top you can see the status of the issue and when it was opened and by whom. Directly below it is the issue description and below that are other %{emphasisStart}related issues%{emphasisEnd} and %{emphasisStart}merge requests%{emphasisEnd} (if any). Then below that is the %{emphasisStart}discussion%{emphasisEnd}, that's where most of the communication happens.%{lineBreak}%{lineBreak}On the right, there's a sidebar where you can view/change the %{emphasisStart}assignee, milestone, due date, labels, weight%{emphasisEnd}, etc."
-msgstr ""
-
-msgid "UserOnboardingTour|These are all the CI/CD pipelines we have for our %{emphasisStart}%{projectName}%{emphasisEnd} project.%{lineBreak}%{lineBreak}Here you can see the status of each pipeline, for what commit it's running for, its stages and the status for them."
-msgstr ""
-
-msgid "UserOnboardingTour|These are all the issues that are available for community contributions. Let's take a closer look at one of them."
-msgstr ""
-
-msgid "UserOnboardingTour|This is an overview of all merge requests in this project. Similarly to the issues overview it can be filtered down by things like labels, milestones, authors, assignees, etc."
-msgstr ""
-
-msgid "UserOnboardingTour|This is the repository for the %{emphasisStart}%{projectName}%{emphasisEnd} project. All our code is stored here. Feel free to explore and take a closer look at folders and files.%{lineBreak}%{lineBreak}Above the file structure you can see the latest commit, who the author is and the status of the CI/CD pipeline.%{lineBreak}%{lineBreak}If you scroll down below the file structure, you'll find the Readme of this project. This is defined in the README.md file at the root of the repository."
-msgstr ""
-
-msgid "UserOnboardingTour|Welcome to the project overview of the %{emphasisStart}%{projectName}%{emphasisEnd} project. This is the project that we use to work on GitLab. At first, a project seems like a simple repository, but at GitLab, a project is so much more.%{lineBreak}%{lineBreak}You can create projects for hosting your codebase, use it as an issue tracker, collaborate on code, and continuously build, test, and deploy your app with built-in GitLab CI/CD."
+msgid "UserList|created %{timeago}"
msgstr ""
msgid "UserProfile|Activity"
@@ -24471,6 +25116,9 @@ msgstr ""
msgid "UsersSelect|Unassigned"
msgstr ""
+msgid "Using %{codeStart}needs%{codeEnd} allows jobs to run before their stage is reached, as soon as their individual dependencies are met, which speeds up your pipelines."
+msgstr ""
+
msgid "Using %{code_start}::%{code_end} denotes a %{link_start}scoped label set%{link_end}"
msgstr ""
@@ -24555,9 +25203,6 @@ msgstr "Version"
msgid "Versions"
msgstr ""
-msgid "Very helpful"
-msgstr ""
-
msgid "View Documentation"
msgstr ""
@@ -24706,6 +25351,9 @@ msgstr "Public"
msgid "VisibilityLevel|Unknown"
msgstr "Inconnu"
+msgid "Visit settings page"
+msgstr ""
+
msgid "VisualReviewApp|%{stepStart}Step 1%{stepEnd}. Copy the following script:"
msgstr ""
@@ -24799,6 +25447,9 @@ msgstr ""
msgid "VulnerabilityManagement|Something went wrong while trying to delete the comment. Please try again later."
msgstr ""
+msgid "VulnerabilityManagement|Something went wrong while trying to refresh the vulnerability. Please try again later."
+msgstr ""
+
msgid "VulnerabilityManagement|Something went wrong while trying to retrieve the vulnerability history. Please try again later."
msgstr ""
@@ -24841,6 +25492,9 @@ msgstr ""
msgid "Vulnerability|Class"
msgstr "Classe"
+msgid "Vulnerability|Crash Address"
+msgstr ""
+
msgid "Vulnerability|Description"
msgstr "Description"
@@ -24916,9 +25570,6 @@ msgstr ""
msgid "We couldn't reach the Prometheus server. Either the server no longer exists or the configuration details need updating."
msgstr ""
-msgid "We created a short guided tour that will help you learn the basics of GitLab and how it will help you be better at your job. It should only take a couple of minutes. You will be guided by two types of helpers, best recognized by their color."
-msgstr ""
-
msgid "We detected potential spam in the %{humanized_resource_name}. Please solve the reCAPTCHA to proceed."
msgstr "Nous avons détecté un potentiel courriel indésirable dans %{humanized_resource_name}. Veuillez résoudre le reCAPTCHA pour continuer."
@@ -24952,6 +25603,9 @@ msgstr ""
msgid "We've found no vulnerabilities"
msgstr ""
+msgid "Web Application Firewall"
+msgstr ""
+
msgid "Web IDE"
msgstr "EDI Web"
@@ -25012,9 +25666,6 @@ msgstr ""
msgid "Welcome to GitLab.com<br>@%{name}!"
msgstr ""
-msgid "Welcome to the Guided GitLab Tour"
-msgstr ""
-
msgid "Welcome to the guided GitLab tour"
msgstr ""
@@ -25062,9 +25713,6 @@ msgstr ""
msgid "While it's rare to have no vulnerabilities, it can happen. In any event, we ask that you please double check your settings to make sure you've set up your dashboard correctly."
msgstr ""
-msgid "White helpers give contextual information."
-msgstr ""
-
msgid "Who can be an approver?"
msgstr ""
@@ -25110,12 +25758,18 @@ msgstr "Il y a déjà une page avec le même titre pour ce chemin."
msgid "WikiEmptyIssueMessage|Suggest wiki improvement"
msgstr "Suggérer une amélioration du wiki"
+msgid "WikiEmptyIssueMessage|You must be a group member in order to add wiki pages. If you have suggestions for how to improve the wiki for this group, consider opening an issue in the %{issues_link}."
+msgstr ""
+
msgid "WikiEmptyIssueMessage|You must be a project member in order to add wiki pages. If you have suggestions for how to improve the wiki for this project, consider opening an issue in the %{issues_link}."
msgstr "Vous devez être un membre du projet pour ajouter des pages wiki. Si vous avez des suggestions sur la manière d’améliorer le wiki pour ce projet, veuillez envisager l’ouverture d’un ticket sur %{issues_link}."
msgid "WikiEmptyIssueMessage|issue tracker"
msgstr "Suivi d’incidents"
+msgid "WikiEmpty|A wiki is where you can store all the details about your group. This can include why you've created it, its principles, how to use it, and so on."
+msgstr ""
+
msgid "WikiEmpty|A wiki is where you can store all the details about your project. This can include why you've created it, its principles, how to use it, and so on."
msgstr "Un wiki est un endroit où vous pouvez stocker tous les détails concernant votre projet. Ceci peut inclure sa raison d’être, ses principes, comment l’utiliser, etc."
@@ -25125,12 +25779,21 @@ msgstr "Créer votre première page"
msgid "WikiEmpty|Suggest wiki improvement"
msgstr "Suggérer une amélioration du wiki"
+msgid "WikiEmpty|The wiki lets you write documentation for your group"
+msgstr ""
+
msgid "WikiEmpty|The wiki lets you write documentation for your project"
msgstr "Le wiki vous permet d’écrire la documentation de votre projet"
+msgid "WikiEmpty|This group has no wiki pages"
+msgstr ""
+
msgid "WikiEmpty|This project has no wiki pages"
msgstr "Ce projet n’a pas de pages wiki"
+msgid "WikiEmpty|You must be a group member in order to add wiki pages."
+msgstr ""
+
msgid "WikiEmpty|You must be a project member in order to add wiki pages."
msgstr "Vous devez être un membre du projet pour ajouter des pages wiki."
@@ -25407,15 +26070,15 @@ msgstr ""
msgid "You can now export your security dashboard to a CSV report."
msgstr ""
+msgid "You can now manage alert endpoint configuration in the Alerts section on the Operations settings page. Fields on this page have been deprecated."
+msgstr ""
+
msgid "You can now submit a merge request to get this change into the original branch."
msgstr ""
msgid "You can now submit a merge request to get this change into the original project."
msgstr ""
-msgid "You can only add files when you are on a branch"
-msgstr "Vous ne pouvez ajouter de fichier que dans une branche"
-
msgid "You can only edit files when you are on a branch"
msgstr "Vous ne pouvez modifier des fichiers que dans une branche"
@@ -25635,7 +26298,7 @@ msgstr ""
msgid "You need to upload a Google Takeout archive."
msgstr ""
-msgid "You reached %{usage_in_percent} of %{namespace_name}'s capacity (%{used_storage} of %{storage_limit})"
+msgid "You reached %{usage_in_percent} of %{namespace_name}'s storage capacity (%{used_storage} of %{storage_limit})"
msgstr ""
msgid "You tried to fork %{link_to_the_project} but it failed for the following reason:"
@@ -25938,13 +26601,13 @@ msgstr ""
msgid "Your subscription expired!"
msgstr ""
-msgid "Your subscription has been downgraded"
+msgid "Your subscription has been downgraded."
msgstr ""
-msgid "Your subscription will automatically renew in %{remaining_days}"
+msgid "Your subscription will automatically renew in %{remaining_days}."
msgstr ""
-msgid "Your subscription will expire in %{remaining_days}"
+msgid "Your subscription will expire in %{remaining_days}."
msgstr ""
msgid "Zoom meeting added"
@@ -26024,9 +26687,6 @@ msgstr ""
msgid "branch name"
msgstr "nom de la branche"
-msgid "build pipeline reference mismatch"
-msgstr ""
-
msgid "by"
msgstr "par"
@@ -26045,6 +26705,9 @@ msgstr ""
msgid "cannot block others"
msgstr ""
+msgid "cannot contain HTML/XML tags, including any word between angle brackets (<,>)."
+msgstr ""
+
msgid "cannot include leading slash or directory traversal."
msgstr ""
@@ -26075,55 +26738,6 @@ msgstr ""
msgid "ciReport|%{remainingPackagesCount} more"
msgstr "%{remainingPackagesCount} restant(s)"
-msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability"
-msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability for the source branch only"
-msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities for the source branch only"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerability"
-msgid_plural "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerabilities"
-msgstr[0] "%{reportType} %{status} a détecté %{fixedCount} vulnérabilité corrigée"
-msgstr[1] "%{reportType} %{status} a détecté %{fixedCount} vulnérabilités corrigées"
-
-msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new vulnerability"
-msgid_plural "ciReport|%{reportType} %{status} detected %{newCount} new vulnerabilities"
-msgstr[0] "%{reportType} %{status} a détecté %{newCount} nouvelle vulnérabilité"
-msgstr[1] "%{reportType} %{status} a détecté %{newCount} nouvelles vulnérabilités"
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities for the source branch only"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{fixedCount} fixed vulnerabilities"
-msgstr "%{reportType} %{status} a détecté de nouvelles vulnérabilités (%{newCount}) et des vulnérabilités corrigées (%{fixedCount})"
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} vulnerability for the source branch only"
-msgid_plural "ciReport|%{reportType} %{status} detected %{newCount} vulnerabilities for the source branch only"
-msgstr[0] "%{reportType} %{status} a détecté %{newCount} vulnérabilité dans la branche source"
-msgstr[1] "%{reportType} %{status} a détecté %{newCount} vulnérabilités dans la branche source"
-
-msgid "ciReport|%{reportType} %{status} detected no new vulnerabilities"
-msgstr "%{reportType} %{status} n’a détecté aucune nouvelle vulnérabilité"
-
-msgid "ciReport|%{reportType} %{status} detected no vulnerabilities"
-msgstr "%{reportType} %{status} n’a détecté aucune vulnérabilité"
-
-msgid "ciReport|%{reportType} %{status} detected no vulnerabilities for the source branch only"
-msgstr "%{reportType} %{status} n’a détecté aucune vulnérabilité dans la branche source"
-
msgid "ciReport|%{reportType} is loading"
msgstr "%{reportType} est en cours de chargement"
@@ -26142,7 +26756,7 @@ msgstr "(en cours de chargement, erreurs lors du chargement des résultats)"
msgid "ciReport|All projects"
msgstr ""
-msgid "ciReport|All report types"
+msgid "ciReport|All scanner types"
msgstr ""
msgid "ciReport|All severities"
@@ -26166,6 +26780,9 @@ msgstr "Analyse du conteneur"
msgid "ciReport|Container scanning detects known vulnerabilities in your docker images."
msgstr "L’analyse des conteneurs permet la détection de vulnérabilités connues dans vos images Docker."
+msgid "ciReport|Coverage Fuzzing"
+msgstr ""
+
msgid "ciReport|Create a merge request to implement this solution, or download and apply the patch manually."
msgstr ""
@@ -26324,6 +26941,9 @@ msgstr ""
msgid "customize"
msgstr "personnaliser"
+msgid "data"
+msgstr ""
+
msgid "date must not be after 9999-12-31"
msgstr ""
@@ -26394,6 +27014,9 @@ msgstr ""
msgid "entries cannot contain HTML tags"
msgstr ""
+msgid "epic"
+msgstr ""
+
msgid "error"
msgstr ""
@@ -26415,9 +27038,6 @@ msgstr ""
msgid "expires on %{timebox_due_date}"
msgstr ""
-msgid "external_url"
-msgstr ""
-
msgid "failed"
msgstr ""
@@ -26453,9 +27073,6 @@ msgstr ""
msgid "from"
msgstr "de"
-msgid "geo_node_name"
-msgstr ""
-
msgid "group"
msgstr ""
@@ -26477,9 +27094,6 @@ msgstr "ici"
msgid "https://your-bitbucket-server"
msgstr "https://votre-serveur-bitbucket"
-msgid "image"
-msgstr ""
-
msgid "image diff"
msgstr ""
@@ -26623,6 +27237,9 @@ msgstr ""
msgid "load it anyway"
msgstr ""
+msgid "loading"
+msgstr ""
+
msgid "locked by %{path_lock_user_name} %{created_at}"
msgstr "verrouillé par %{path_lock_user_name} %{created_at}"
@@ -26751,9 +27368,6 @@ msgstr ""
msgid "mrWidget|Deployment statistics are not available currently"
msgstr "Les statistiques de déploiement ne sont pas disponibles pour le moment"
-msgid "mrWidget|Detect issues before deployment with a CI pipeline that continuously tests your code. We created a quick guide that will show you how to create one. Make your code more secure and more robust in just a minute."
-msgstr ""
-
msgid "mrWidget|Did not close"
msgstr "N’a pas résolu"
@@ -26916,6 +27530,9 @@ msgstr "Ce projet est archivé, l’accès en écriture a été désactivé"
msgid "mrWidget|To approve this merge request, please enter your password. This project requires all approvals to be authenticated."
msgstr ""
+msgid "mrWidget|Use %{linkStart}CI pipelines to test your code%{linkEnd}, simply add a GitLab CI configuration file to your project. It only takes a minute to make your code more secure and robust."
+msgstr ""
+
msgid "mrWidget|When this merge request is ready, remove the WIP: prefix from the title to allow it to be merged"
msgstr ""
@@ -26952,6 +27569,9 @@ msgstr ""
msgid "must be greater than start date"
msgstr ""
+msgid "must contain only valid frameworks"
+msgstr ""
+
msgid "my-awesome-group"
msgstr ""
@@ -26964,6 +27584,9 @@ msgstr ""
msgid "needs to be between 10 minutes and 1 month"
msgstr ""
+msgid "never"
+msgstr ""
+
msgid "never expires"
msgstr ""
@@ -26991,6 +27614,9 @@ msgstr "courriels de notification"
msgid "nounSeries|%{firstItem} and %{lastItem}"
msgstr ""
+msgid "nounSeries|%{item}"
+msgstr ""
+
msgid "nounSeries|%{item}, %{nextItem}"
msgstr ""
@@ -27127,6 +27753,9 @@ msgstr ""
msgid "revised"
msgstr ""
+msgid "satisfied"
+msgstr ""
+
msgid "score"
msgstr ""
@@ -27157,6 +27786,9 @@ msgstr ""
msgid "severity|Unknown"
msgstr ""
+msgid "should be an array of %{object_name} objects"
+msgstr ""
+
msgid "should be greater than or equal to %{access} inherited membership from group %{group_name}"
msgstr ""
@@ -27211,10 +27843,13 @@ msgstr ""
msgid "suggestPipeline|2/2: Commit your changes"
msgstr ""
-msgid "suggestPipeline|Commit the changes and your pipeline will automatically run for the first time."
+msgid "suggestPipeline|Choose %{boldStart}Code Quality%{boldEnd} to add a pipeline that tests the quality of your code."
msgstr ""
-msgid "suggestPipeline|We recommend the %{boldStart}Code Quality%{boldEnd} template, which will add a report widget to your Merge Requests. This way you’ll learn about code quality degradations much sooner. %{footerStart} Goodbye technical debt! %{footerEnd}"
+msgid "suggestPipeline|The template is ready! You can now commit it to create your first pipeline."
+msgstr ""
+
+msgid "suggestPipeline|We’re adding a GitLab CI configuration file to add a pipeline to the project. You could create it manually, but we recommend that you start with a GitLab template that works out of the box."
msgstr ""
msgid "syntax is correct"
@@ -27274,6 +27909,9 @@ msgstr "nom d’utilisateur"
msgid "uses Kubernetes clusters to deploy your code!"
msgstr "utilise les grappes de serveurs Kubernetes pour déployer votre code !"
+msgid "v%{version} published %{timeAgo}"
+msgstr ""
+
msgid "verify ownership"
msgstr ""
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 77eb977633d..314bb6466c9 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -71,6 +71,11 @@ msgstr ""
msgid "\"%{path}\" did not exist on \"%{ref}\""
msgstr ""
+msgid "%d Scanned URL"
+msgid_plural "%d Scanned URLs"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "%d URL scanned"
msgid_plural "%d URLs scanned"
msgstr[0] ""
@@ -164,6 +169,11 @@ msgid_plural "%d groups selected"
msgstr[0] ""
msgstr[1] ""
+msgid "%d hour"
+msgid_plural "%d hours"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "%d inaccessible merge request"
msgid_plural "%d inaccessible merge requests"
msgstr[0] ""
@@ -174,11 +184,21 @@ msgid_plural "%d issues"
msgstr[0] ""
msgstr[1] ""
+msgid "%d issue in this group"
+msgid_plural "%d issues in this group"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "%d issue selected"
msgid_plural "%d issues selected"
msgstr[0] ""
msgstr[1] ""
+msgid "%d issue successfully imported with the label"
+msgid_plural "%d issues successfully imported with the label"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "%d layer"
msgid_plural "%d layers"
msgstr[0] ""
@@ -199,6 +219,11 @@ msgid_plural "%d metrics"
msgstr[0] ""
msgstr[1] ""
+msgid "%d milestone"
+msgid_plural "%d milestones"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "%d minute"
msgid_plural "%d minutes"
msgstr[0] ""
@@ -239,13 +264,13 @@ msgid_plural "%d tags"
msgstr[0] ""
msgstr[1] ""
-msgid "%d unresolved thread"
-msgid_plural "%d unresolved threads"
+msgid "%d unassigned issue"
+msgid_plural "%d unassigned issues"
msgstr[0] ""
msgstr[1] ""
-msgid "%d url scanned"
-msgid_plural "%d urls scanned"
+msgid "%d unresolved thread"
+msgid_plural "%d unresolved threads"
msgstr[0] ""
msgstr[1] ""
@@ -326,6 +351,9 @@ msgstr[1] ""
msgid "%{count} related %{pluralized_subject}: %{links}"
msgstr ""
+msgid "%{dashboard_path} could not be found."
+msgstr ""
+
msgid "%{days} days until tags are automatically removed"
msgstr ""
@@ -383,24 +411,24 @@ msgstr ""
msgid "%{issuableType} will be removed! Are you sure?"
msgstr ""
-msgid "%{issuesCount} issues in this group"
-msgstr ""
-
-msgid "%{issuesSize} issues"
+msgid "%{issuesSize} issues with a limit of %{maxIssueCount}"
msgstr ""
-msgid "%{issuesSize} issues with a limit of %{maxIssueCount}"
+msgid "%{issuesSize} with a limit of %{maxIssueCount}"
msgstr ""
msgid "%{labelStart}Class:%{labelEnd} %{class}"
msgstr ""
-msgid "%{labelStart}Confidence:%{labelEnd} %{confidence}"
+msgid "%{labelStart}Evidence:%{labelEnd} %{evidence}"
msgstr ""
msgid "%{labelStart}File:%{labelEnd} %{file}"
msgstr ""
+msgid "%{labelStart}Headers:%{labelEnd} %{headers}"
+msgstr ""
+
msgid "%{labelStart}Image:%{labelEnd} %{image}"
msgstr ""
@@ -413,9 +441,18 @@ msgstr ""
msgid "%{labelStart}Report Type:%{labelEnd} %{reportType}"
msgstr ""
+msgid "%{labelStart}Scanner:%{labelEnd} %{scanner}"
+msgstr ""
+
msgid "%{labelStart}Severity:%{labelEnd} %{severity}"
msgstr ""
+msgid "%{labelStart}Status:%{labelEnd} %{status}"
+msgstr ""
+
+msgid "%{labelStart}URL:%{labelEnd} %{url}"
+msgstr ""
+
msgid "%{label_for_message} unavailable"
msgstr ""
@@ -470,6 +507,9 @@ msgstr ""
msgid "%{name} found %{resultsString}"
msgstr ""
+msgid "%{name} is already being used for another emoji"
+msgstr ""
+
msgid "%{name} is scheduled for %{action}"
msgstr ""
@@ -517,6 +557,9 @@ msgstr ""
msgid "%{primary} (%{secondary})"
msgstr ""
+msgid "%{ref} cannot be added: %{error}"
+msgstr ""
+
msgid "%{releases} release"
msgid_plural "%{releases} releases"
msgstr[0] ""
@@ -525,15 +568,49 @@ msgstr[1] ""
msgid "%{remaining_approvals} left"
msgstr ""
-msgid "%{retryButtonStart}Try again%{retryButtonEnd} or %{newFileButtonStart}attach a new file%{newFileButtonEnd}"
+msgid "%{reportType} %{status} detected %{criticalStart}%{critical} new critical%{criticalEnd} and %{highStart}%{high} new high%{highEnd} severity vulnerabilities out of %{total}."
msgstr ""
-msgid "%{screenreaderOnlyStart}Keyboard shorcuts%{screenreaderOnlyEnd} Disabled"
+msgid "%{reportType} %{status} detected %{criticalStart}%{critical} new critical%{criticalEnd} and %{highStart}%{high} new high%{highEnd} severity vulnerabilities."
msgstr ""
-msgid "%{screenreaderOnlyStart}Keyboard shorcuts%{screenreaderOnlyEnd} Enabled"
+msgid "%{reportType} %{status} detected %{criticalStart}%{critical} new critical%{criticalEnd} severity vulnerabilities out of %{total}."
msgstr ""
+msgid "%{reportType} %{status} detected %{criticalStart}%{critical} new critical%{criticalEnd} severity vulnerability."
+msgid_plural "%{reportType} %{status} detected %{criticalStart}%{critical} new critical%{criticalEnd} severity vulnerabilities."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{reportType} %{status} detected %{highStart}%{high} new high%{highEnd} severity vulnerabilities out of %{total}."
+msgstr ""
+
+msgid "%{reportType} %{status} detected %{highStart}%{high} new high%{highEnd} severity vulnerability."
+msgid_plural "%{reportType} %{status} detected %{highStart}%{high} new high%{highEnd} severity vulnerabilities."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{reportType} %{status} detected %{other} vulnerability."
+msgid_plural "%{reportType} %{status} detected %{other} vulnerabilities."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{reportType} %{status} detected no new vulnerabilities."
+msgstr ""
+
+msgid "%{retryButtonStart}Try again%{retryButtonEnd} or %{newFileButtonStart}attach a new file%{newFileButtonEnd}"
+msgstr ""
+
+msgid "%{securityScanner} is not enabled for this project. %{linkStart}More information%{linkEnd}"
+msgid_plural "%{securityScanner} are not enabled for this project. %{linkStart}More information%{linkEnd}"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{securityScanner} result is not available because a pipeline has not been run since it was enabled. %{linkStart}Run a pipeline%{linkEnd}"
+msgid_plural "%{securityScanner} results are not available because a pipeline has not been run since it was enabled. %{linkStart}Run a pipeline%{linkEnd}"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "%{service_title} %{message}."
msgstr ""
@@ -673,6 +750,9 @@ msgstr ""
msgid "'%{level}' is not a valid visibility level"
msgstr ""
+msgid "'%{name}' Value Stream created"
+msgstr ""
+
msgid "'%{name}' stage already exists"
msgstr ""
@@ -687,31 +767,28 @@ msgid_plural "(%d closed)"
msgstr[0] ""
msgstr[1] ""
-msgid "(%{linkStart}Cron syntax%{linkEnd})"
-msgstr ""
-
msgid "(%{mrCount} merged)"
msgstr ""
msgid "(No changes)"
msgstr ""
-msgid "(Show all)"
+msgid "(check progress)"
msgstr ""
-msgid "(check progress)"
+msgid "(deleted)"
msgstr ""
msgid "(external source)"
msgstr ""
-msgid "(removed)"
+msgid "(line: %{startLine})"
msgstr ""
-msgid "(revoked)"
+msgid "(removed)"
msgstr ""
-msgid "*"
+msgid "(revoked)"
msgstr ""
msgid "+ %{amount} more"
@@ -762,6 +839,9 @@ msgstr ""
msgid "- show less"
msgstr ""
+msgid "0 bytes"
+msgstr ""
+
msgid "0 for unlimited"
msgstr ""
@@ -921,9 +1001,6 @@ msgstr ""
msgid "<code>Protected</code> to expose them to protected branches or tags only."
msgstr ""
-msgid "<namespace / project>"
-msgstr ""
-
msgid "<no name set>"
msgstr ""
@@ -948,7 +1025,7 @@ msgstr ""
msgid "A .NET Core console application template, customizable for any .NET Core project"
msgstr ""
-msgid "A DAG must have two dependent jobs to be visualized on this tab."
+msgid "A CI/CD pipeline must run and be successful before merge."
msgstr ""
msgid "A GitBook site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
@@ -1056,15 +1133,15 @@ msgstr ""
msgid "A suggestion is not applicable."
msgstr ""
-msgid "A terraform report was generated in your pipelines."
-msgstr ""
-
msgid "A user with write access to the source branch selected this option"
msgstr ""
msgid "ACTION REQUIRED: Something went wrong while obtaining the Let's Encrypt certificate for GitLab Pages domain '%{domain}'"
msgstr ""
+msgid "API Help"
+msgstr ""
+
msgid "API Token"
msgstr ""
@@ -1200,7 +1277,7 @@ msgstr ""
msgid "AccessTokens|reset it"
msgstr ""
-msgid "AccessibilityReport|Learn More"
+msgid "AccessibilityReport|Learn more"
msgstr ""
msgid "AccessibilityReport|Message: %{message}"
@@ -1224,7 +1301,10 @@ msgstr ""
msgid "Account: %{account}"
msgstr ""
-msgid "Action to take when receiving an alert."
+msgid "Action to take when receiving an alert. %{docsLink}"
+msgstr ""
+
+msgid "Actions"
msgstr ""
msgid "Activate"
@@ -1254,6 +1334,9 @@ msgstr ""
msgid "Add"
msgstr ""
+msgid "Add \"%{value}\""
+msgstr ""
+
msgid "Add %d issue"
msgid_plural "Add %d issues"
msgstr[0] ""
@@ -1283,6 +1366,9 @@ msgstr ""
msgid "Add LICENSE"
msgstr ""
+msgid "Add New Node"
+msgstr ""
+
msgid "Add README"
msgstr ""
@@ -1358,6 +1444,9 @@ msgstr ""
msgid "Add approval rule"
msgstr ""
+msgid "Add approvers"
+msgstr ""
+
msgid "Add bold text"
msgstr ""
@@ -1517,9 +1606,15 @@ msgstr ""
msgid "Admin Note"
msgstr ""
+msgid "Admin Notifications"
+msgstr ""
+
msgid "Admin Overview"
msgstr ""
+msgid "Admin Section"
+msgstr ""
+
msgid "Admin mode already enabled"
msgstr ""
@@ -1628,6 +1723,9 @@ msgstr ""
msgid "AdminSettings|Integrations configured here will automatically apply to all projects on this instance."
msgstr ""
+msgid "AdminSettings|Moved to integrations"
+msgstr ""
+
msgid "AdminSettings|No required pipeline"
msgstr ""
@@ -1688,15 +1786,24 @@ msgstr ""
msgid "AdminUsers|2FA Enabled"
msgstr ""
+msgid "AdminUsers|Access"
+msgstr ""
+
msgid "AdminUsers|Active"
msgstr ""
msgid "AdminUsers|Admin"
msgstr ""
+msgid "AdminUsers|Administrators have access to all groups, projects and users and can manage all features in this installation"
+msgstr ""
+
msgid "AdminUsers|Admins"
msgstr ""
+msgid "AdminUsers|Automatically marked as default internal user"
+msgstr ""
+
msgid "AdminUsers|Block"
msgstr ""
@@ -1745,6 +1852,9 @@ msgstr ""
msgid "AdminUsers|External"
msgstr ""
+msgid "AdminUsers|External users cannot see internal or private projects unless access is explicitly granted. Also, external users cannot create projects, groups, or personal snippets."
+msgstr ""
+
msgid "AdminUsers|Is using seat"
msgstr ""
@@ -1769,6 +1879,12 @@ msgstr ""
msgid "AdminUsers|Reactivating a user will:"
msgstr ""
+msgid "AdminUsers|Regular"
+msgstr ""
+
+msgid "AdminUsers|Regular users have access to their groups and projects"
+msgstr ""
+
msgid "AdminUsers|Restore user access to the account, including web, Git and API."
msgstr ""
@@ -1823,6 +1939,9 @@ msgstr ""
msgid "AdminUsers|You are about to permanently delete the user %{username}. This will delete all of the issues, merge requests, and groups linked to them. To avoid data loss, consider using the %{strong_start}block user%{strong_end} feature instead. Once you %{strong_start}Delete user%{strong_end}, it cannot be undone or recovered."
msgstr ""
+msgid "AdminUsers|You cannot remove your own admin rights."
+msgstr ""
+
msgid "Administration"
msgstr ""
@@ -1858,9 +1977,6 @@ msgid_plural "Alerts"
msgstr[0] ""
msgstr[1] ""
-msgid "AlertManagement| assign yourself"
-msgstr ""
-
msgid "AlertManagement|Acknowledged"
msgstr ""
@@ -1885,15 +2001,9 @@ msgstr ""
msgid "AlertManagement|All alerts"
msgstr ""
-msgid "AlertManagement|Assign To"
-msgstr ""
-
msgid "AlertManagement|Assign status"
msgstr ""
-msgid "AlertManagement|Assignee"
-msgstr ""
-
msgid "AlertManagement|Assignees"
msgstr ""
@@ -1912,9 +2022,6 @@ msgstr ""
msgid "AlertManagement|Edit"
msgstr ""
-msgid "AlertManagement|End time"
-msgstr ""
-
msgid "AlertManagement|Events"
msgstr ""
@@ -1924,22 +2031,28 @@ msgstr ""
msgid "AlertManagement|Info"
msgstr ""
+msgid "AlertManagement|Issue"
+msgstr ""
+
msgid "AlertManagement|Low"
msgstr ""
msgid "AlertManagement|Medium"
msgstr ""
-msgid "AlertManagement|More information"
+msgid "AlertManagement|Metrics"
msgstr ""
-msgid "AlertManagement|No Matching Results"
+msgid "AlertManagement|Metrics weren't available in the alerts payload."
+msgstr ""
+
+msgid "AlertManagement|More information"
msgstr ""
msgid "AlertManagement|No alert data to display."
msgstr ""
-msgid "AlertManagement|No alerts available to display. If you think you're seeing this message in error, refresh the page."
+msgid "AlertManagement|No alerts available to display. See %{linkStart}enabling alert management%{linkEnd} for more information on adding alerts to the list."
msgstr ""
msgid "AlertManagement|No alerts to display."
@@ -1948,15 +2061,18 @@ msgstr ""
msgid "AlertManagement|None"
msgstr ""
-msgid "AlertManagement|None -"
+msgid "AlertManagement|Open"
msgstr ""
-msgid "AlertManagement|Open"
+msgid "AlertManagement|Opsgenie is enabled"
msgstr ""
msgid "AlertManagement|Overview"
msgstr ""
+msgid "AlertManagement|Please try again."
+msgstr ""
+
msgid "AlertManagement|Reported %{when}"
msgstr ""
@@ -1987,13 +2103,19 @@ msgstr ""
msgid "AlertManagement|There was an error displaying the alerts. Confirm your endpoint's configuration details to ensure alerts appear."
msgstr ""
+msgid "AlertManagement|There was an error while updating the To Do of the alert."
+msgstr ""
+
msgid "AlertManagement|There was an error while updating the assignee(s) list. Please try again."
msgstr ""
msgid "AlertManagement|There was an error while updating the assignee(s) of the alert. Please try again."
msgstr ""
-msgid "AlertManagement|There was an error while updating the status of the alert. Please try again."
+msgid "AlertManagement|There was an error while updating the status of the alert."
+msgstr ""
+
+msgid "AlertManagement|This assignee cannot be assigned to this alert."
msgstr ""
msgid "AlertManagement|Tool"
@@ -2008,15 +2130,102 @@ msgstr ""
msgid "AlertManagement|Unknown"
msgstr ""
+msgid "AlertManagement|View alerts in Opsgenie"
+msgstr ""
+
msgid "AlertManagement|View issue"
msgstr ""
+msgid "AlertManagement|You have enabled the Opsgenie integration. Your alerts will be visible directly in Opsgenie."
+msgstr ""
+
msgid "AlertService|Review your external service's documentation to learn where to provide this information to your external service, and the %{linkStart}GitLab documentation%{linkEnd} to learn more about configuring your endpoint."
msgstr ""
msgid "AlertService|You must provide this URL and authorization key to authorize an external service to send alerts to GitLab. You can provide this URL and key to multiple services. After configuring an external service, alerts from your service will display on the GitLab %{linkStart}Alerts%{linkEnd} page."
msgstr ""
+msgid "AlertSettings|API URL"
+msgstr ""
+
+msgid "AlertSettings|Active"
+msgstr ""
+
+msgid "AlertSettings|Add URL and auth key to your Prometheus config file"
+msgstr ""
+
+msgid "AlertSettings|Alert test payload"
+msgstr ""
+
+msgid "AlertSettings|Alerts endpoint successfully activated."
+msgstr ""
+
+msgid "AlertSettings|Authorization key"
+msgstr ""
+
+msgid "AlertSettings|Authorization key has been successfully reset"
+msgstr ""
+
+msgid "AlertSettings|Copy"
+msgstr ""
+
+msgid "AlertSettings|Enter test alert JSON...."
+msgstr ""
+
+msgid "AlertSettings|External Prometheus"
+msgstr ""
+
+msgid "AlertSettings|Generic"
+msgstr ""
+
+msgid "AlertSettings|Integrations"
+msgstr ""
+
+msgid "AlertSettings|Learn more about our %{linkStart}upcoming integrations%{linkEnd}"
+msgstr ""
+
+msgid "AlertSettings|Opsgenie"
+msgstr ""
+
+msgid "AlertSettings|Reset key"
+msgstr ""
+
+msgid "AlertSettings|Resetting the authorization key for this project will require updating the authorization key in every alert source it is enabled in."
+msgstr ""
+
+msgid "AlertSettings|Review your external service's documentation to learn where to provide this information to your external service, and the %{linkStart}GitLab documentation%{linkEnd} to learn more about configuring your endpoint."
+msgstr ""
+
+msgid "AlertSettings|Test alert payload"
+msgstr ""
+
+msgid "AlertSettings|Test alert sent successfully. If you have made other changes, please save them now."
+msgstr ""
+
+msgid "AlertSettings|Test failed. Do you still want to save your changes anyway?"
+msgstr ""
+
+msgid "AlertSettings|There was an error updating the alert settings"
+msgstr ""
+
+msgid "AlertSettings|There was an error while trying to reset the key. Please refresh the page to try again."
+msgstr ""
+
+msgid "AlertSettings|URL cannot be blank and must start with http or https"
+msgstr ""
+
+msgid "AlertSettings|Webhook URL"
+msgstr ""
+
+msgid "AlertSettings|You can now set up alert endpoints for manually configured Prometheus instances in the Alerts section on the Operations settings page. Alert endpoint fields on this page have been deprecated."
+msgstr ""
+
+msgid "AlertSettings|You must provide this URL and authorization key to authorize an external service to send alerts to GitLab. You can provide this URL and key to multiple services. After configuring an external service, alerts from your service will display on the GitLab %{linkStart}Alerts%{linkEnd} page."
+msgstr ""
+
+msgid "AlertSettings|Your changes were successfully updated."
+msgstr ""
+
msgid "Alerts"
msgstr ""
@@ -2092,6 +2301,9 @@ msgstr ""
msgid "Allow \"%{group_name}\" to sign you in"
msgstr ""
+msgid "Allow access to the following IP addresses"
+msgstr ""
+
msgid "Allow commits from members who can merge to the target branch."
msgstr ""
@@ -2143,6 +2355,9 @@ msgstr ""
msgid "Allow users to request access (if visibility is public or internal)"
msgstr ""
+msgid "Allowed"
+msgstr ""
+
msgid "Allowed Geo IP"
msgstr ""
@@ -2164,6 +2379,9 @@ msgstr ""
msgid "Also called \"Relying party service URL\" or \"Reply URL\""
msgstr ""
+msgid "Also unassign this user from related issues and merge requests"
+msgstr ""
+
msgid "Alternate support URL for help page and help dropdown"
msgstr ""
@@ -2188,6 +2406,9 @@ msgstr ""
msgid "Amount of time (in hours) that users are allowed to skip forced configuration of two-factor authentication"
msgstr ""
+msgid "An %{link_start}alert%{link_end} with the same fingerprint is already open. To change the status of this alert, resolve the linked alert."
+msgstr ""
+
msgid "An alert has been triggered in %{project_path}."
msgstr ""
@@ -2239,6 +2460,12 @@ msgstr ""
msgid "An error occurred when updating the issue weight"
msgstr ""
+msgid "An error occurred while acknowledging the notification. Refresh the page and try again."
+msgstr ""
+
+msgid "An error occurred while adding approvers"
+msgstr ""
+
msgid "An error occurred while adding formatted title for epic"
msgstr ""
@@ -2275,6 +2502,12 @@ msgstr ""
msgid "An error occurred while enabling Service Desk."
msgstr ""
+msgid "An error occurred while fetching branches. Retry the search."
+msgstr ""
+
+msgid "An error occurred while fetching commits. Retry the search."
+msgstr ""
+
msgid "An error occurred while fetching coverage reports."
msgstr ""
@@ -2305,6 +2538,9 @@ msgstr ""
msgid "An error occurred while fetching sidebar data"
msgstr ""
+msgid "An error occurred while fetching tags. Retry the search."
+msgstr ""
+
msgid "An error occurred while fetching terraform reports."
msgstr ""
@@ -2389,9 +2625,6 @@ msgstr ""
msgid "An error occurred while loading project creation UI"
msgstr ""
-msgid "An error occurred while loading terraform report"
-msgstr ""
-
msgid "An error occurred while loading the data. Please try again."
msgstr ""
@@ -2443,9 +2676,15 @@ msgstr ""
msgid "An error occurred while rendering preview broadcast message"
msgstr ""
+msgid "An error occurred while rendering the editor"
+msgstr ""
+
msgid "An error occurred while reordering issues."
msgstr ""
+msgid "An error occurred while requesting data from the Jira service"
+msgstr ""
+
msgid "An error occurred while retrieving calendar activity"
msgstr ""
@@ -2500,6 +2739,9 @@ msgstr ""
msgid "An example project for managing Kubernetes clusters integrated with GitLab."
msgstr ""
+msgid "An example showing how to use Jsonnet with GitLab dynamic child pipelines"
+msgstr ""
+
msgid "An instance-level serverless domain already exists."
msgstr ""
@@ -2539,7 +2781,7 @@ msgstr ""
msgid "Analyze your dependencies for known vulnerabilities."
msgstr ""
-msgid "Analyze your source code and git history for secrets"
+msgid "Analyze your source code and git history for secrets."
msgstr ""
msgid "Analyze your source code for known vulnerabilities."
@@ -2662,6 +2904,9 @@ msgstr ""
msgid "Apply this approval rule to any branch or a specific protected branch."
msgstr ""
+msgid "Applying"
+msgstr ""
+
msgid "Applying a template will replace the existing issue description. Any changes you have made will be lost."
msgstr ""
@@ -2727,6 +2972,9 @@ msgstr ""
msgid "ApprovalRule|e.g. QA, Security, etc."
msgstr ""
+msgid "Approvals|Section: %section"
+msgstr ""
+
msgid "Approve"
msgstr ""
@@ -2742,9 +2990,6 @@ msgstr ""
msgid "Approved MRs"
msgstr ""
-msgid "Approved by: "
-msgstr ""
-
msgid "Approved the current merge request."
msgstr ""
@@ -2958,6 +3203,9 @@ msgstr ""
msgid "Assign Iteration"
msgstr ""
+msgid "Assign To"
+msgstr ""
+
msgid "Assign custom color like #FF0000"
msgstr ""
@@ -2994,6 +3242,9 @@ msgstr ""
msgid "Assigned Merge Requests"
msgstr ""
+msgid "Assigned to %{assigneeName}"
+msgstr ""
+
msgid "Assigned to %{assignee_name}"
msgstr ""
@@ -3005,6 +3256,9 @@ msgid_plural "%d Assignees"
msgstr[0] ""
msgstr[1] ""
+msgid "Assignee has no permissions"
+msgstr ""
+
msgid "Assignee lists not available with your current license"
msgstr ""
@@ -3029,6 +3283,12 @@ msgstr ""
msgid "At least one of group_id or project_id must be specified"
msgstr ""
+msgid "At least one of your Personal Access Tokens is expired, but expiration enforcement is disabled. %{generate_new}"
+msgstr ""
+
+msgid "At least one of your Personal Access Tokens will expire soon, but expiration enforcement is disabled. %{generate_new}"
+msgstr ""
+
msgid "At risk"
msgstr ""
@@ -3052,16 +3312,7 @@ msgstr ""
msgid "Audit Events is a way to keep track of important events that happened in GitLab."
msgstr ""
-msgid "AuditEvents|(removed)"
-msgstr ""
-
-msgid "AuditEvents|Action"
-msgstr ""
-
-msgid "AuditEvents|At"
-msgstr ""
-
-msgid "AuditEvents|Target"
+msgid "Audit Log"
msgstr ""
msgid "AuditLogs|(removed)"
@@ -3088,6 +3339,9 @@ msgstr ""
msgid "AuditLogs|IP Address"
msgstr ""
+msgid "AuditLogs|Member Events"
+msgstr ""
+
msgid "AuditLogs|No matching %{type} found."
msgstr ""
@@ -3238,9 +3492,6 @@ msgstr ""
msgid "Automatically create merge requests for vulnerabilities that have fixes available."
msgstr ""
-msgid "Automatically marked as default internal user"
-msgstr ""
-
msgid "Automatically resolved"
msgstr ""
@@ -3253,6 +3504,9 @@ msgstr ""
msgid "Available"
msgstr ""
+msgid "Available Runners: %{runners}"
+msgstr ""
+
msgid "Available for dependency and container scanning"
msgstr ""
@@ -3370,9 +3624,6 @@ msgstr ""
msgid "Badges|e.g. %{exampleUrl}"
msgstr ""
-msgid "Badge|New"
-msgstr ""
-
msgid "Balsamiq file could not be loaded."
msgstr ""
@@ -3487,9 +3738,15 @@ msgstr ""
msgid "Bitbucket Server Import"
msgstr ""
+msgid "Bitbucket Server import"
+msgstr ""
+
msgid "Bitbucket import"
msgstr ""
+msgid "Blame"
+msgstr ""
+
msgid "Blocked"
msgstr ""
@@ -3502,9 +3759,6 @@ msgstr ""
msgid "Blog"
msgstr ""
-msgid "Blue helpers indicate an action to be taken."
-msgstr ""
-
msgid "Board name"
msgstr ""
@@ -3703,6 +3957,9 @@ msgstr ""
msgid "Broadcast Message was successfully updated."
msgstr ""
+msgid "Broadcast Messages"
+msgstr ""
+
msgid "Browse Directory"
msgstr ""
@@ -3757,6 +4014,9 @@ msgstr ""
msgid "By %{user_name}"
msgstr ""
+msgid "By URL"
+msgstr ""
+
msgid "By default GitLab sends emails in HTML and plain text formats so mail clients can choose what format to use. Disable this option if you only want to send emails in plain text format."
msgstr ""
@@ -3862,7 +4122,13 @@ msgstr ""
msgid "Can override approvers and approvals required per merge request"
msgstr ""
-msgid "Can't apply as this line has changed or the suggestion already matches its content."
+msgid "Can't apply as %{phrase} changed in a more recent version."
+msgstr ""
+
+msgid "Can't apply as the source branch was deleted."
+msgstr ""
+
+msgid "Can't apply this suggestion."
msgstr ""
msgid "Can't create snippet: %{err}"
@@ -3871,6 +4137,9 @@ msgstr ""
msgid "Can't edit as source branch was deleted"
msgstr ""
+msgid "Can't fetch content for the blob: %{err}"
+msgstr ""
+
msgid "Can't find HEAD commit for this branch"
msgstr ""
@@ -3922,6 +4191,9 @@ msgstr ""
msgid "Cannot have multiple Jira imports running at the same time"
msgstr ""
+msgid "Cannot have multiple unresolved alerts"
+msgstr ""
+
msgid "Cannot import because issues are not available in this project."
msgstr ""
@@ -4045,13 +4317,13 @@ msgstr ""
msgid "Changes"
msgstr ""
-msgid "Changes are shown as if the <b>source</b> revision was being merged into the <b>target</b> revision."
+msgid "Changes affect new repositories only. If not specified, Git's default name %{branch_name_default} will be used."
msgstr ""
-msgid "Changes are still tracked. Useful for cluster/index migrations."
+msgid "Changes are shown as if the <b>source</b> revision was being merged into the <b>target</b> revision."
msgstr ""
-msgid "Changes are unknown"
+msgid "Changes are still tracked. Useful for cluster/index migrations."
msgstr ""
msgid "Changes suppressed. Click to show."
@@ -4351,6 +4623,9 @@ msgstr ""
msgid "Choose file…"
msgstr ""
+msgid "Choose labels"
+msgstr ""
+
msgid "Choose the top-level group for your repository imports."
msgstr ""
@@ -4360,15 +4635,9 @@ msgstr ""
msgid "Choose what content you want to see on a group’s overview page"
msgstr ""
-msgid "Choose which groups you wish to synchronize to this secondary node"
-msgstr ""
-
msgid "Choose which repositories you want to connect and run CI/CD pipelines."
msgstr ""
-msgid "Choose which shards you wish to synchronize to this secondary node"
-msgstr ""
-
msgid "Choose your framework"
msgstr ""
@@ -4522,15 +4791,15 @@ msgstr ""
msgid "CiVariable|Validation failed"
msgstr ""
-msgid "Class"
-msgstr ""
-
msgid "Classification Label (optional)"
msgstr ""
msgid "ClassificationLabelUnavailable|is unavailable: %{reason}"
msgstr ""
+msgid "Cleanup policy for tags"
+msgstr ""
+
msgid "Clear"
msgstr ""
@@ -4642,6 +4911,9 @@ msgstr ""
msgid "Closed"
msgstr ""
+msgid "Closed %{epicTimeagoDate}"
+msgstr ""
+
msgid "Closed issues"
msgstr ""
@@ -4666,9 +4938,15 @@ msgstr ""
msgid "Cluster does not exist"
msgstr ""
+msgid "Cluster is required for Stages::ClusterEndpointInserter"
+msgstr ""
+
msgid "Cluster level"
msgstr ""
+msgid "Cluster type must be specificed for Stages::ClusterEndpointInserter"
+msgstr ""
+
msgid "ClusterIntegration| %{custom_domain_start}More information%{custom_domain_end}."
msgstr ""
@@ -4678,9 +4956,6 @@ msgstr ""
msgid "ClusterIntegration| can be used instead of a custom domain."
msgstr ""
-msgid "ClusterIntegration| is the default environment scope for this cluster. This means that all jobs, regardless of their environment, will use this cluster. %{environment_scope_start}More information%{environment_scope_end}"
-msgstr ""
-
msgid "ClusterIntegration|%{appList} was successfully installed on your Kubernetes cluster"
msgstr ""
@@ -4801,6 +5076,9 @@ msgstr ""
msgid "ClusterIntegration|Choose which of your environments will use this cluster."
msgstr ""
+msgid "ClusterIntegration|Choose which of your environments will use this cluster. %{environment_scope_start}More information%{environment_scope_end}"
+msgstr ""
+
msgid "ClusterIntegration|Clear cluster cache"
msgstr ""
@@ -4822,6 +5100,9 @@ msgstr ""
msgid "ClusterIntegration|Clusters are utilized by selecting the nearest ancestor with a matching environment scope. For example, project clusters will override group clusters."
msgstr ""
+msgid "ClusterIntegration|Clusters are utilized by selecting the nearest ancestor with a matching environment scope. For example, project clusters will override group clusters. %{linkStart}More information%{linkEnd}"
+msgstr ""
+
msgid "ClusterIntegration|Copy API URL"
msgstr ""
@@ -4930,6 +5211,9 @@ msgstr ""
msgid "ClusterIntegration|Environment scope"
msgstr ""
+msgid "ClusterIntegration|Environment scope is required."
+msgstr ""
+
msgid "ClusterIntegration|Every new Google Cloud Platform (GCP) account receives $300 in credit upon %{sign_up_link}. In partnership with Google, GitLab is able to offer an additional $200 for both new and existing GCP accounts to get started with GitLab's Google Kubernetes Engine Integration."
msgstr ""
@@ -5620,6 +5904,12 @@ msgstr ""
msgid "Code owners"
msgstr ""
+msgid "CodeIntelligence|This is the definition"
+msgstr ""
+
+msgid "CodeNavigation|No references found"
+msgstr ""
+
msgid "CodeOwner|Pattern"
msgstr ""
@@ -5650,7 +5940,7 @@ msgstr ""
msgid "Collapse approvers"
msgstr ""
-msgid "Collapse child epics"
+msgid "Collapse milestones"
msgstr ""
msgid "Collapse replies"
@@ -5832,6 +6122,9 @@ msgstr ""
msgid "Complete"
msgstr ""
+msgid "Completed"
+msgstr ""
+
msgid "Compliance"
msgstr ""
@@ -5841,6 +6134,12 @@ msgstr ""
msgid "Compliance framework (optional)"
msgstr ""
+msgid "Compliance frameworks"
+msgstr ""
+
+msgid "ComplianceDashboard|created by:"
+msgstr ""
+
msgid "ComplianceFramework|GDPR"
msgstr ""
@@ -5913,6 +6212,9 @@ msgstr ""
msgid "Configure limit for issues created per minute by web and API requests."
msgstr ""
+msgid "Configure limits for Project/Group Import/Export."
+msgstr ""
+
msgid "Configure limits for web and API requests."
msgstr ""
@@ -5943,6 +6245,24 @@ msgstr ""
msgid "Confirmation required"
msgstr ""
+msgid "Confluence"
+msgstr ""
+
+msgid "ConfluenceService|Confluence Workspace"
+msgstr ""
+
+msgid "ConfluenceService|Connect a Confluence Cloud Workspace to your GitLab project"
+msgstr ""
+
+msgid "ConfluenceService|Enabling the Confluence Workspace will disable the default GitLab Wiki. Your GitLab Wiki data will be saved and you can always re-enable it later by turning off this integration"
+msgstr ""
+
+msgid "ConfluenceService|The URL of the Confluence Workspace"
+msgstr ""
+
+msgid "ConfluenceService|Your GitLab Wiki can be accessed here: %{wiki_link}. To re-enable your GitLab Wiki, disable this integration"
+msgstr ""
+
msgid "Congratulations! You have enabled Two-factor Authentication!"
msgstr ""
@@ -5991,9 +6311,6 @@ msgstr ""
msgid "Container Registry"
msgstr ""
-msgid "Container Registry tag expiration policy"
-msgstr ""
-
msgid "Container Scanning"
msgstr ""
@@ -6028,7 +6345,7 @@ msgstr ""
msgid "ContainerRegistry|%{title} was successfully scheduled for deletion"
msgstr ""
-msgid "ContainerRegistry|Automatically remove extra images that aren't designed to be kept."
+msgid "ContainerRegistry|%{toggleStatus} - Tags matching the patterns defined below will be scheduled for deletion"
msgstr ""
msgid "ContainerRegistry|Build an image"
@@ -6037,13 +6354,19 @@ msgstr ""
msgid "ContainerRegistry|CLI Commands"
msgstr ""
-msgid "ContainerRegistry|Compressed Size"
+msgid "ContainerRegistry|Cleanup policy for tags is disabled"
msgstr ""
-msgid "ContainerRegistry|Container Registry"
+msgid "ContainerRegistry|Cleanup policy successfully saved."
msgstr ""
-msgid "ContainerRegistry|Container Registry tag expiration and retention policy is disabled"
+msgid "ContainerRegistry|Cleanup policy:"
+msgstr ""
+
+msgid "ContainerRegistry|Configuration digest: %{digest}"
+msgstr ""
+
+msgid "ContainerRegistry|Container Registry"
msgstr ""
msgid "ContainerRegistry|Copy build command"
@@ -6055,28 +6378,28 @@ msgstr ""
msgid "ContainerRegistry|Copy push command"
msgstr ""
-msgid "ContainerRegistry|Docker connection error"
+msgid "ContainerRegistry|Delete selected"
msgstr ""
-msgid "ContainerRegistry|Docker tag expiration policy is %{toggleStatus}"
+msgid "ContainerRegistry|Deletion disabled due to missing or insufficient permissions."
msgstr ""
-msgid "ContainerRegistry|Expiration interval:"
+msgid "ContainerRegistry|Digest: %{imageId}"
msgstr ""
-msgid "ContainerRegistry|Expiration policies help manage the storage space used by the Container Registry, but the expiration policies for this registry are disabled. Contact your administrator to enable. %{docLinkStart}More information%{docLinkEnd}"
+msgid "ContainerRegistry|Docker connection error"
msgstr ""
-msgid "ContainerRegistry|Expiration policy is disabled"
+msgid "ContainerRegistry|Expiration interval:"
msgstr ""
-msgid "ContainerRegistry|Expiration policy successfully saved."
+msgid "ContainerRegistry|Expiration policies help manage the storage space used by the Container Registry, but the expiration policies for this registry are disabled. Contact your administrator to enable. %{docLinkStart}More information%{docLinkEnd}"
msgstr ""
-msgid "ContainerRegistry|Expiration policy will run in %{time}"
+msgid "ContainerRegistry|Expiration policy is disabled"
msgstr ""
-msgid "ContainerRegistry|Expiration policy:"
+msgid "ContainerRegistry|Expiration policy will run in %{time}"
msgstr ""
msgid "ContainerRegistry|Expiration schedule:"
@@ -6088,37 +6411,34 @@ msgstr ""
msgid "ContainerRegistry|If you are not already logged in, you need to authenticate to the Container Registry by using your GitLab username and password. If you have %{twofaDocLinkStart}Two-Factor Authentication%{twofaDocLinkEnd} enabled, use a %{personalAccessTokensDocLinkStart}Personal Access Token%{personalAccessTokensDocLinkEnd} instead of a password."
msgstr ""
-msgid "ContainerRegistry|Image ID"
-msgstr ""
-
msgid "ContainerRegistry|Image Repositories"
msgstr ""
-msgid "ContainerRegistry|Keep and protect the images that matter most."
+msgid "ContainerRegistry|Image tags"
msgstr ""
-msgid "ContainerRegistry|Last Updated"
+msgid "ContainerRegistry|Invalid tag: missing manifest digest"
msgstr ""
msgid "ContainerRegistry|Login"
msgstr ""
-msgid "ContainerRegistry|Missing or insufficient permission, delete button disabled"
+msgid "ContainerRegistry|Manifest digest: %{digest}"
msgstr ""
-msgid "ContainerRegistry|Number of tags to retain:"
+msgid "ContainerRegistry|Missing or insufficient permission, delete button disabled"
msgstr ""
-msgid "ContainerRegistry|Please contact your administrator."
+msgid "ContainerRegistry|Number of tags to retain:"
msgstr ""
-msgid "ContainerRegistry|Push an image"
+msgid "ContainerRegistry|Published %{timeInfo}"
msgstr ""
-msgid "ContainerRegistry|Regular expressions such as %{codeStart}.*-master%{codeEnd} or %{codeStart}release-.*%{codeEnd} are supported"
+msgid "ContainerRegistry|Published to the %{repositoryPath} image repository at %{time} on %{date}"
msgstr ""
-msgid "ContainerRegistry|Regular expressions such as %{codeStart}.*-test%{codeEnd} or %{codeStart}dev-.*%{codeEnd} are supported. To select all tags, use %{codeStart}.*%{codeEnd}"
+msgid "ContainerRegistry|Push an image"
msgstr ""
msgid "ContainerRegistry|Remember to run %{docLinkStart}garbage collection%{docLinkEnd} to remove the stale data from storage."
@@ -6127,15 +6447,15 @@ msgstr ""
msgid "ContainerRegistry|Remove repository"
msgstr ""
-msgid "ContainerRegistry|Remove selected tags"
-msgstr ""
-
msgid "ContainerRegistry|Remove tag"
msgid_plural "ContainerRegistry|Remove tags"
msgstr[0] ""
msgstr[1] ""
-msgid "ContainerRegistry|Something went wrong while fetching the expiration policy."
+msgid "ContainerRegistry|Set cleanup policy"
+msgstr ""
+
+msgid "ContainerRegistry|Something went wrong while fetching the cleanup policy."
msgstr ""
msgid "ContainerRegistry|Something went wrong while fetching the repository list."
@@ -6153,21 +6473,15 @@ msgstr ""
msgid "ContainerRegistry|Something went wrong while scheduling %{title} for deletion. Please try again."
msgstr ""
-msgid "ContainerRegistry|Something went wrong while updating the expiration policy."
+msgid "ContainerRegistry|Something went wrong while updating the cleanup policy."
msgstr ""
msgid "ContainerRegistry|Sorry, your filter produced no results."
msgstr ""
-msgid "ContainerRegistry|Tag"
-msgstr ""
-
msgid "ContainerRegistry|Tag expiration policy"
msgstr ""
-msgid "ContainerRegistry|Tag expiration policy is designed to:"
-msgstr ""
-
msgid "ContainerRegistry|Tag successfully marked for deletion."
msgstr ""
@@ -6180,13 +6494,10 @@ msgstr ""
msgid "ContainerRegistry|Tags with names matching this regex pattern will %{italicStart}expire:%{italicEnd}"
msgstr ""
-msgid "ContainerRegistry|The Container Registry tag expiration and retention policies for this project have not been enabled."
-msgstr ""
-
msgid "ContainerRegistry|The last tag related to this image was recently removed. This empty image and any associated data will be automatically removed as part of the regular Garbage Collection process. If you have any questions, contact your administrator."
msgstr ""
-msgid "ContainerRegistry|The value of this input should be less than 255 characters"
+msgid "ContainerRegistry|The value of this input should be less than 256 characters"
msgstr ""
msgid "ContainerRegistry|There are no container images available in this group"
@@ -6204,12 +6515,21 @@ msgstr ""
msgid "ContainerRegistry|This image repository is scheduled for deletion"
msgstr ""
+msgid "ContainerRegistry|This project's cleanup policy for tags is not enabled."
+msgstr ""
+
msgid "ContainerRegistry|To widen your search, change or remove the filters above."
msgstr ""
msgid "ContainerRegistry|We are having trouble connecting to the Registry, which could be due to an issue with your project name or path. %{docLinkStart}More information%{docLinkEnd}"
msgstr ""
+msgid "ContainerRegistry|Wildcards such as %{codeStart}.*-master%{codeEnd} or %{codeStart}release-.*%{codeEnd} are supported"
+msgstr ""
+
+msgid "ContainerRegistry|Wildcards such as %{codeStart}.*-test%{codeEnd} or %{codeStart}dev-.*%{codeEnd} are supported. To select all tags, use %{codeStart}.*%{codeEnd}"
+msgstr ""
+
msgid "ContainerRegistry|With the Container Registry, every project can have its own space to store its Docker images. %{docLinkStart}More Information%{docLinkEnd}"
msgstr ""
@@ -6306,21 +6626,6 @@ msgstr ""
msgid "Control the display of third party offers."
msgstr ""
-msgid "Control the maximum concurrency of LFS/attachment backfill for this secondary node"
-msgstr ""
-
-msgid "Control the maximum concurrency of container repository operations for this Geo node"
-msgstr ""
-
-msgid "Control the maximum concurrency of repository backfill for this secondary node"
-msgstr ""
-
-msgid "Control the maximum concurrency of verification operations for this Geo node"
-msgstr ""
-
-msgid "Control the minimum interval in days that a repository should be reverified for this primary node"
-msgstr ""
-
msgid "Cookie domain"
msgstr ""
@@ -6414,6 +6719,9 @@ msgstr ""
msgid "Copy secret"
msgstr ""
+msgid "Copy source branch name"
+msgstr ""
+
msgid "Copy token"
msgstr ""
@@ -6462,6 +6770,9 @@ msgstr ""
msgid "Could not find design."
msgstr ""
+msgid "Could not find iteration"
+msgstr ""
+
msgid "Could not remove the trigger."
msgstr ""
@@ -6498,6 +6809,12 @@ msgstr ""
msgid "Coverage"
msgstr ""
+msgid "Coverage Fuzzing"
+msgstr ""
+
+msgid "Crash State"
+msgstr ""
+
msgid "Create"
msgstr ""
@@ -6627,9 +6944,15 @@ msgstr ""
msgid "Create new file or directory"
msgstr ""
+msgid "Create new issue in Jira"
+msgstr ""
+
msgid "Create new label"
msgstr ""
+msgid "Create new value stream"
+msgstr ""
+
msgid "Create new..."
msgstr ""
@@ -6645,6 +6968,9 @@ msgstr ""
msgid "Create snippet"
msgstr ""
+msgid "Create value stream"
+msgstr ""
+
msgid "Create wildcard: %{searchTerm}"
msgstr ""
@@ -6687,9 +7013,15 @@ msgstr ""
msgid "Created branch '%{branch_name}' and a merge request to resolve this issue."
msgstr ""
+msgid "Created by %{job}"
+msgstr ""
+
msgid "Created by me"
msgstr ""
+msgid "Created by:"
+msgstr ""
+
msgid "Created date"
msgstr ""
@@ -6774,6 +7106,9 @@ msgstr ""
msgid "CurrentUser|Buy Pipeline minutes"
msgstr ""
+msgid "CurrentUser|One of your groups is running out"
+msgstr ""
+
msgid "CurrentUser|Profile"
msgstr ""
@@ -7052,12 +7387,18 @@ msgstr ""
msgid "DAG"
msgstr ""
+msgid "DAG visualization requires at least 3 dependent jobs."
+msgstr ""
+
msgid "DNS"
msgstr ""
msgid "Dashboard"
msgstr ""
+msgid "Dashboard uid not found"
+msgstr ""
+
msgid "DashboardProjects|All"
msgstr ""
@@ -7079,9 +7420,36 @@ msgstr ""
msgid "Dashboard|Unable to add %{invalidProjects}. This dashboard is available for public projects, and private projects in groups with a Silver plan."
msgstr ""
+msgid "DastProfiles|Could not create the site profile. Please try again."
+msgstr ""
+
+msgid "DastProfiles|Do you want to discard this site profile?"
+msgstr ""
+
+msgid "DastProfiles|Manage profiles"
+msgstr ""
+
+msgid "DastProfiles|New site profile"
+msgstr ""
+
+msgid "DastProfiles|Please enter a valid URL format, ex: http://www.example.com/home"
+msgstr ""
+
+msgid "DastProfiles|Profile name"
+msgstr ""
+
+msgid "DastProfiles|Save profile"
+msgstr ""
+
+msgid "DastProfiles|Target URL"
+msgstr ""
+
msgid "Data is still calculating..."
msgstr ""
+msgid "Datasource name not found"
+msgstr ""
+
msgid "Date"
msgstr ""
@@ -7163,6 +7531,9 @@ msgstr ""
msgid "Default first day of the week in calendars and date pickers."
msgstr ""
+msgid "Default initial branch name"
+msgstr ""
+
msgid "Default issue template"
msgstr ""
@@ -7181,6 +7552,9 @@ msgstr ""
msgid "Default: Map a FogBugz account ID to a full name"
msgstr ""
+msgid "DefaultBranchLabel|default"
+msgstr ""
+
msgid "Define a custom pattern with cron syntax"
msgstr ""
@@ -7190,6 +7564,12 @@ msgstr ""
msgid "Define environments in the deploy stage(s) in <code>.gitlab-ci.yml</code> to track deployments here."
msgstr ""
+msgid "Definition"
+msgstr ""
+
+msgid "Delayed Project Deletion (%{adjourned_deletion})"
+msgstr ""
+
msgid "DelayedJobs|Are you sure you want to run %{jobName} immediately? Otherwise this job will run automatically after it's timer finishes."
msgstr ""
@@ -7262,6 +7642,9 @@ msgstr ""
msgid "Delete this attachment"
msgstr ""
+msgid "Delete user list"
+msgstr ""
+
msgid "Delete variable"
msgstr ""
@@ -7307,12 +7690,18 @@ msgstr ""
msgid "Deletion pending. This project will be removed on %{date}. Repository and other project resources are read-only."
msgstr ""
+msgid "Denied"
+msgstr ""
+
msgid "Denied authorization of chat nickname %{user_name}."
msgstr ""
msgid "Deny"
msgstr ""
+msgid "Deny access request"
+msgstr ""
+
msgid "Dependencies"
msgstr ""
@@ -7698,6 +8087,9 @@ msgstr ""
msgid "DesignManagement|Deselect all"
msgstr ""
+msgid "DesignManagement|Designs"
+msgstr ""
+
msgid "DesignManagement|Discard comment"
msgstr ""
@@ -7740,7 +8132,7 @@ msgstr ""
msgid "DesignManagement|The maximum number of designs allowed to be uploaded is %{upload_limit}. Please try again."
msgstr ""
-msgid "DesignManagement|To enable design management, you'll need to %{requirements_link_start}meet the requirements%{requirements_link_end}. If you need help, reach out to our %{support_link_start}support team%{support_link_end} for assistance."
+msgid "DesignManagement|To enable design management, you'll need to %{requirements_link_start}meet the requirements%{requirements_link_end}. If you need help, reach out to our %{support_link_start}support team%{support_link_end} for assistance."
msgstr ""
msgid "DesignManagement|Unresolve thread"
@@ -7830,6 +8222,9 @@ msgstr ""
msgid "Disabled mirrors can only be enabled by instance owners. It is recommended that you delete them."
msgstr ""
+msgid "Discard"
+msgstr ""
+
msgid "Discard all changes"
msgstr ""
@@ -7943,6 +8338,9 @@ msgstr ""
msgid "Dismissed on pipeline %{pipelineLink} at %{projectLink}"
msgstr ""
+msgid "Display alerts from all your monitoring tools directly within GitLab."
+msgstr ""
+
msgid "Display name"
msgstr ""
@@ -7967,6 +8365,9 @@ msgstr ""
msgid "Documentation for popular identity providers"
msgstr ""
+msgid "Documents reindexed: %{processed_documents} (%{percentage}%%)"
+msgstr ""
+
msgid "Doing"
msgstr ""
@@ -8000,9 +8401,6 @@ msgstr ""
msgid "Don't show again"
msgstr ""
-msgid "Don't worry, you can access this tour by clicking on the help icon in the top right corner and choose <strong>Learn GitLab</strong>."
-msgstr ""
-
msgid "Done"
msgstr ""
@@ -8024,6 +8422,9 @@ msgstr ""
msgid "Download as"
msgstr ""
+msgid "Download as CSV"
+msgstr ""
+
msgid "Download asset"
msgstr ""
@@ -8066,6 +8467,9 @@ msgstr ""
msgid "Downvotes"
msgstr ""
+msgid "Drop or %{linkStart}upload%{linkEnd} Designs to attach"
+msgstr ""
+
msgid "Drop your designs to start your upload."
msgstr ""
@@ -8129,6 +8533,9 @@ msgstr ""
msgid "Edit Release"
msgstr ""
+msgid "Edit Slack integration"
+msgstr ""
+
msgid "Edit Snippet"
msgstr ""
@@ -8159,6 +8566,9 @@ msgstr ""
msgid "Edit files in the editor and commit changes here"
msgstr ""
+msgid "Edit fork in Web IDE"
+msgstr ""
+
msgid "Edit group: %{group_name}"
msgstr ""
@@ -8168,6 +8578,9 @@ msgstr ""
msgid "Edit issues"
msgstr ""
+msgid "Edit iteration"
+msgstr ""
+
msgid "Edit public deploy key"
msgstr ""
@@ -8204,9 +8617,18 @@ msgstr ""
msgid "Elasticsearch integration. Elasticsearch AWS IAM."
msgstr ""
+msgid "Elasticsearch reindexing is already in progress"
+msgstr ""
+
+msgid "Elasticsearch reindexing triggered"
+msgstr ""
+
msgid "Elasticsearch returned status code: %{status_code}"
msgstr ""
+msgid "Elasticsearch zero-downtime reindexing"
+msgstr ""
+
msgid "Elastic|None. Select namespaces to index."
msgstr ""
@@ -8216,6 +8638,9 @@ msgstr ""
msgid "Email"
msgstr ""
+msgid "Email Notification"
+msgstr ""
+
msgid "Email address"
msgstr ""
@@ -8228,6 +8653,9 @@ msgstr ""
msgid "Email not verified. Please verify your email in Salesforce."
msgstr ""
+msgid "Email notification for unknown sign-ins"
+msgstr ""
+
msgid "Email patch"
msgstr ""
@@ -8531,6 +8959,9 @@ msgstr ""
msgid "Enter the name of your application, and we'll return a unique %{type}."
msgstr ""
+msgid "Enter weights for storages for new repositories."
+msgstr ""
+
msgid "Enter your password to approve"
msgstr ""
@@ -8540,6 +8971,12 @@ msgstr ""
msgid "Environment does not have deployments"
msgstr ""
+msgid "Environment is required for Stages::MetricEndpointInserter"
+msgstr ""
+
+msgid "Environment is required for Stages::VariableEndpointInserter"
+msgstr ""
+
msgid "Environment scope"
msgstr ""
@@ -8687,6 +9124,12 @@ msgstr ""
msgid "Environments|Logs from %{start} to %{end}."
msgstr ""
+msgid "Environments|Managed apps"
+msgstr ""
+
+msgid "Environments|More information"
+msgstr ""
+
msgid "Environments|New environment"
msgstr ""
@@ -8726,9 +9169,6 @@ msgstr ""
msgid "Environments|Re-deploy to environment"
msgstr ""
-msgid "Environments|Read more about environments"
-msgstr ""
-
msgid "Environments|Rollback"
msgstr ""
@@ -8741,9 +9181,6 @@ msgstr ""
msgid "Environments|Rollback environment %{name}?"
msgstr ""
-msgid "Environments|Select environment"
-msgstr ""
-
msgid "Environments|Select pod"
msgstr ""
@@ -8792,6 +9229,9 @@ msgstr ""
msgid "Epic events"
msgstr ""
+msgid "Epic not found for given params"
+msgstr ""
+
msgid "Epics"
msgstr ""
@@ -8822,9 +9262,15 @@ msgstr ""
msgid "Epics|Are you sure you want to remove %{bStart}%{targetIssueTitle}%{bEnd} from %{bStart}%{parentEpicTitle}%{bEnd}?"
msgstr ""
+msgid "Epics|Enter a title for your epic"
+msgstr ""
+
msgid "Epics|How can I solve this?"
msgstr ""
+msgid "Epics|Leave empty to inherit from milestone dates"
+msgstr ""
+
msgid "Epics|More information"
msgstr ""
@@ -8864,12 +9310,18 @@ msgstr ""
msgid "Epics|These dates affect how your epics appear in the roadmap. Dates from milestones come from the milestones assigned to issues in the epic. You can also set fixed dates or remove them entirely."
msgstr ""
+msgid "Epics|This epic and any containing child epics are confidential and should only be visible to team members with at least Reporter access."
+msgstr ""
+
msgid "Epics|This will also remove any descendents of %{bStart}%{targetEpicTitle}%{bEnd} from %{bStart}%{parentEpicTitle}%{bEnd}. Are you sure?"
msgstr ""
msgid "Epics|To schedule your epic's %{epicDateType} date based on milestones, assign a milestone with a %{epicDateType} date to any issue in the epic."
msgstr ""
+msgid "Epics|Unable to save epic. Please try again"
+msgstr ""
+
msgid "Epics|due"
msgstr ""
@@ -8945,6 +9397,12 @@ msgstr ""
msgid "Error loading file viewer."
msgstr ""
+msgid "Error loading issues"
+msgstr ""
+
+msgid "Error loading iterations"
+msgstr ""
+
msgid "Error loading last commit."
msgstr ""
@@ -8969,6 +9427,9 @@ msgstr ""
msgid "Error loading viewer"
msgstr ""
+msgid "Error message:"
+msgstr ""
+
msgid "Error occurred when fetching sidebar data"
msgstr ""
@@ -9041,6 +9502,9 @@ msgstr ""
msgid "Error with Akismet. Please check the logs for more info."
msgstr ""
+msgid "Error: %{error_message}"
+msgstr ""
+
msgid "ErrorTracking|Active"
msgstr ""
@@ -9086,6 +9550,9 @@ msgstr ""
msgid "EventFilterBy|Filter by comments"
msgstr ""
+msgid "EventFilterBy|Filter by designs"
+msgstr ""
+
msgid "EventFilterBy|Filter by epic events"
msgstr ""
@@ -9107,9 +9574,6 @@ msgstr ""
msgid "Events"
msgstr ""
-msgid "Events in %{group_name}"
-msgstr ""
-
msgid "Events in %{project_path}"
msgstr ""
@@ -9176,12 +9640,15 @@ msgstr ""
msgid "Exactly one of %{attributes} is required"
msgstr ""
-msgid "Example: <code>acme.com,acme.co.in,acme.uk</code>."
+msgid "Example: <code>192.168.0.0/24</code>. %{read_more_link}."
msgstr ""
msgid "Example: @sub\\.company\\.com$"
msgstr ""
+msgid "Example: My value stream"
+msgstr ""
+
msgid "Example: Usage = single query. (Requested) / (Capacity) = multiple queries combined into a formula."
msgstr ""
@@ -9218,21 +9685,24 @@ msgstr ""
msgid "Expand approvers"
msgstr ""
-msgid "Expand child epics"
-msgstr ""
-
msgid "Expand down"
msgstr ""
msgid "Expand dropdown"
msgstr ""
+msgid "Expand milestones"
+msgstr ""
+
msgid "Expand sidebar"
msgstr ""
msgid "Expand up"
msgstr ""
+msgid "Expected documents: %{expected_documents}"
+msgstr ""
+
msgid "Experienced"
msgstr ""
@@ -9242,7 +9712,7 @@ msgstr ""
msgid "Expiration date"
msgstr ""
-msgid "Expiration policy for the Container Registry is a perfect solution for keeping the Registry space down while still enjoying the full power of GitLab CI/CD."
+msgid "Expiration not enforced"
msgstr ""
msgid "Expired"
@@ -9425,6 +9895,12 @@ msgstr ""
msgid "Failed to install."
msgstr ""
+msgid "Failed to load assignees. Please try again."
+msgstr ""
+
+msgid "Failed to load authors. Please try again."
+msgstr ""
+
msgid "Failed to load emoji list."
msgstr ""
@@ -9623,7 +10099,7 @@ msgstr ""
msgid "FeatureFlags|Edit list"
msgstr ""
-msgid "FeatureFlags|Enable features for specific users and specific environments by defining feature flag strategies. By default, features are available to all users in all environments."
+msgid "FeatureFlags|Enable features for specific users and specific environments by defining feature flag strategies."
msgstr ""
msgid "FeatureFlags|Environment Spec"
@@ -9860,9 +10336,6 @@ msgstr ""
msgid "Filter by Git revision"
msgstr ""
-msgid "Filter by commit message"
-msgstr ""
-
msgid "Filter by issues that are currently closed."
msgstr ""
@@ -9899,9 +10372,6 @@ msgstr ""
msgid "Filter pipelines"
msgstr ""
-msgid "Filter projects"
-msgstr ""
-
msgid "Filter results"
msgstr ""
@@ -9920,6 +10390,12 @@ msgstr ""
msgid "Filter..."
msgstr ""
+msgid "Find File"
+msgstr ""
+
+msgid "Find bugs in your code with coverage-guided fuzzing"
+msgstr ""
+
msgid "Find by path"
msgstr ""
@@ -10016,9 +10492,6 @@ msgstr ""
msgid "Footer message"
msgstr ""
-msgid "For each Jira issue successfully imported, we'll create a new GitLab issue with the following data:"
-msgstr ""
-
msgid "For internal projects, any logged in user can view pipelines and access job details (output logs and artifacts)"
msgstr ""
@@ -10073,9 +10546,6 @@ msgstr ""
msgid "Forks"
msgstr ""
-msgid "Format"
-msgstr ""
-
msgid "Format: %{dateFormat}"
msgstr ""
@@ -10109,18 +10579,6 @@ msgstr ""
msgid "From <code>%{source_title}</code> into"
msgstr ""
-msgid "From Bitbucket"
-msgstr ""
-
-msgid "From Bitbucket Server"
-msgstr ""
-
-msgid "From FogBugz"
-msgstr ""
-
-msgid "From GitLab.com"
-msgstr ""
-
msgid "From Google Code"
msgstr ""
@@ -10166,6 +10624,12 @@ msgstr ""
msgid "Generate new export"
msgstr ""
+msgid "Generate new token"
+msgstr ""
+
+msgid "GenericReports|Report"
+msgstr ""
+
msgid "Geo"
msgstr ""
@@ -10181,7 +10645,7 @@ msgstr ""
msgid "Geo Settings"
msgstr ""
-msgid "Geo allows you to replicate your GitLab instance to other geographical locations."
+msgid "Geo nodes are paused using a command run on the node"
msgstr ""
msgid "GeoNodeStatusEvent|%{timeAgoStr} (%{pendingEvents} events)"
@@ -10289,6 +10753,9 @@ msgstr ""
msgid "GeoNodes|Replication slots"
msgstr ""
+msgid "GeoNodes|Replication status"
+msgstr ""
+
msgid "GeoNodes|Repositories"
msgstr ""
@@ -10388,6 +10855,24 @@ msgstr ""
msgid "Geo|All projects are being scheduled for reverify"
msgstr ""
+msgid "Geo|Allowed Geo IP can't be blank"
+msgstr ""
+
+msgid "Geo|Allowed Geo IP should be between 1 and 255 characters"
+msgstr ""
+
+msgid "Geo|Allowed Geo IP should contain valid IP addresses"
+msgstr ""
+
+msgid "Geo|Connection timeout can't be blank"
+msgstr ""
+
+msgid "Geo|Connection timeout must be a number"
+msgstr ""
+
+msgid "Geo|Connection timeout should be between 1-120"
+msgstr ""
+
msgid "Geo|Could not remove tracking entry for an existing project."
msgstr ""
@@ -10568,15 +11053,15 @@ msgstr ""
msgid "Getting started with releases"
msgstr ""
-msgid "Git"
-msgstr ""
-
msgid "Git LFS is not enabled on this GitLab server, contact your admin."
msgstr ""
msgid "Git LFS objects will be synced in pull mirrors if LFS is %{docs_link_start}enabled for the project%{docs_link_end}. They will <strong>not</strong> be synced in push mirrors."
msgstr ""
+msgid "Git LFS status:"
+msgstr ""
+
msgid "Git global setup"
msgstr ""
@@ -10634,6 +11119,9 @@ msgstr ""
msgid "GitLab commit"
msgstr ""
+msgid "GitLab export"
+msgstr ""
+
msgid "GitLab for Slack"
msgstr ""
@@ -10658,7 +11146,10 @@ msgstr ""
msgid "GitLab restart is required to apply changes."
msgstr ""
-msgid "GitLab single sign on URL"
+msgid "GitLab single sign-on URL"
+msgstr ""
+
+msgid "GitLab username"
msgstr ""
msgid "GitLab uses %{jaeger_link} to monitor distributed systems."
@@ -10667,6 +11158,9 @@ msgstr ""
msgid "GitLab will run a background job that will produce pseudonymized CSVs of the GitLab database that will be uploaded to your configured object storage directory."
msgstr ""
+msgid "GitLab.com"
+msgstr ""
+
msgid "GitLab.com import"
msgstr ""
@@ -10769,6 +11263,12 @@ msgstr ""
msgid "Gitaly Servers"
msgstr ""
+msgid "Gitaly relative path:"
+msgstr ""
+
+msgid "Gitaly storage name:"
+msgstr ""
+
msgid "Gitaly|Address"
msgstr ""
@@ -10832,6 +11332,9 @@ msgstr ""
msgid "Go to environments"
msgstr ""
+msgid "Go to epic"
+msgstr ""
+
msgid "Go to file"
msgstr ""
@@ -10844,6 +11347,9 @@ msgstr ""
msgid "Go to find file"
msgstr ""
+msgid "Go to fork"
+msgstr ""
+
msgid "Go to issue boards"
msgstr ""
@@ -10916,9 +11422,6 @@ msgstr ""
msgid "Go to your snippets"
msgstr ""
-msgid "Golden Tanuki"
-msgstr ""
-
msgid "Google Cloud Platform"
msgstr ""
@@ -10940,6 +11443,9 @@ msgstr ""
msgid "Grafana URL"
msgstr ""
+msgid "Grafana response contains invalid json"
+msgstr ""
+
msgid "GrafanaIntegration|API Token"
msgstr ""
@@ -11057,6 +11563,9 @@ msgstr ""
msgid "Group info:"
msgstr ""
+msgid "Group is required when cluster_type is :group"
+msgstr ""
+
msgid "Group maintainers can register group runners in the %{link}"
msgstr ""
@@ -11102,22 +11611,25 @@ msgstr ""
msgid "Group was successfully updated."
msgstr ""
+msgid "Group-level events in %{group_name} (no project-level events)"
+msgstr ""
+
msgid "Group: %{group_name}"
msgstr ""
msgid "Group: %{name}"
msgstr ""
-msgid "GroupActivityMetrics|New Members created"
+msgid "GroupActivityMetrics|Issues opened"
msgstr ""
-msgid "GroupActivyMetrics|Issues created"
+msgid "GroupActivityMetrics|Members added"
msgstr ""
-msgid "GroupActivyMetrics|Merge Requests created"
+msgid "GroupActivityMetrics|Merge Requests opened"
msgstr ""
-msgid "GroupActivyMetrics|Recent activity (last 90 days)"
+msgid "GroupActivityMetrics|Recent activity (last 90 days)"
msgstr ""
msgid "GroupImport|Failed to import group."
@@ -11198,7 +11710,7 @@ msgstr ""
msgid "GroupSAML|Identity"
msgstr ""
-msgid "GroupSAML|Identity provider single sign on URL"
+msgid "GroupSAML|Identity provider single sign-on URL"
msgstr ""
msgid "GroupSAML|Make sure you save this token — you won't be able to access it again."
@@ -11318,6 +11830,9 @@ msgstr ""
msgid "GroupSettings|Disable group mentions"
msgstr ""
+msgid "GroupSettings|Enable delayed project removal"
+msgstr ""
+
msgid "GroupSettings|Export group"
msgstr ""
@@ -11345,6 +11860,9 @@ msgstr ""
msgid "GroupSettings|Prevent sharing a project within %{group} with other groups"
msgstr ""
+msgid "GroupSettings|Projects will be permanently deleted after a %{waiting_period}-day waiting period. This period can be %{customization_link} in instance settings"
+msgstr ""
+
msgid "GroupSettings|Select a sub-group as the custom project template source for this group."
msgstr ""
@@ -11405,6 +11923,12 @@ msgstr ""
msgid "Groups (%{groups})"
msgstr ""
+msgid "Groups and projects"
+msgstr ""
+
+msgid "Groups and subgroups"
+msgstr ""
+
msgid "Groups can also be nested by creating %{subgroup_docs_link_start}subgroups%{subgroup_docs_link_end}."
msgstr ""
@@ -11543,6 +12067,9 @@ msgstr ""
msgid "Health information can be retrieved from the following endpoints. More information is available"
msgstr ""
+msgid "Health status"
+msgstr ""
+
msgid "HealthCheck|Access token is"
msgstr ""
@@ -11596,6 +12123,9 @@ msgid_plural "Hide charts"
msgstr[0] ""
msgstr[1] ""
+msgid "Hide details"
+msgstr ""
+
msgid "Hide file browser"
msgstr ""
@@ -11605,6 +12135,9 @@ msgstr ""
msgid "Hide host keys manual input"
msgstr ""
+msgid "Hide list"
+msgstr ""
+
msgid "Hide marketing-related entries from help"
msgstr ""
@@ -11625,9 +12158,6 @@ msgstr[1] ""
msgid "Hide values"
msgstr ""
-msgid "Hiding all labels"
-msgstr ""
-
msgid "High or unknown vulnerabilities present"
msgstr ""
@@ -11712,6 +12242,9 @@ msgstr ""
msgid "ID:"
msgstr ""
+msgid "IDE"
+msgstr ""
+
msgid "IDE|Allow live previews of JavaScript projects in the Web IDE using CodeSandbox Live Preview."
msgstr ""
@@ -11802,10 +12335,10 @@ msgstr ""
msgid "If enabled"
msgstr ""
-msgid "If enabled, access to projects will be validated on an external service using their classification label."
+msgid "If enabled, GitLab will handle Object Storage replication using Geo. %{linkStart}More information%{linkEnd}"
msgstr ""
-msgid "If enabled, and if object storage is enabled, GitLab will handle Object Storage replication using Geo"
+msgid "If enabled, access to projects will be validated on an external service using their classification label."
msgstr ""
msgid "If there is no previous license or if the previous license has expired, some GitLab functionality will be blocked until a new, valid license is uploaded."
@@ -11970,6 +12503,9 @@ msgstr ""
msgid "Import timed out. Import took longer than %{import_jobs_expiration} seconds"
msgstr ""
+msgid "Import/Export Rate Limits"
+msgstr ""
+
msgid "Import/Export illustration"
msgstr ""
@@ -11985,6 +12521,9 @@ msgstr ""
msgid "ImportProjects|Importing the project failed"
msgstr ""
+msgid "ImportProjects|Importing the project failed: %{reason}"
+msgstr ""
+
msgid "ImportProjects|Requesting your %{provider} repositories failed"
msgstr ""
@@ -12036,7 +12575,19 @@ msgstr ""
msgid "Incident Management Limits"
msgstr ""
-msgid "Incidents"
+msgid "IncidentSettings|Alert integration"
+msgstr ""
+
+msgid "IncidentSettings|Grafana integration"
+msgstr ""
+
+msgid "IncidentSettings|Incidents"
+msgstr ""
+
+msgid "IncidentSettings|PagerDuty integration"
+msgstr ""
+
+msgid "IncidentSettings|Set up integrations with external tools to help better manage incidents."
msgstr ""
msgid "Include a Terms of Service agreement and Privacy Policy that all users must accept."
@@ -12167,13 +12718,13 @@ msgid_plural "Instances"
msgstr[0] ""
msgstr[1] ""
-msgid "Instance Statistics visibility"
+msgid "Instance Configuration"
msgstr ""
-msgid "Instance administrators group already exists"
+msgid "Instance Statistics visibility"
msgstr ""
-msgid "Instance does not support multiple Kubernetes clusters"
+msgid "Instance administrators group already exists"
msgstr ""
msgid "Instance license"
@@ -12209,6 +12760,15 @@ msgstr ""
msgid "Integrations|Standard"
msgstr ""
+msgid "Integrations|This integration has multiple settings available."
+msgstr ""
+
+msgid "Integrations|Use custom settings"
+msgstr ""
+
+msgid "Integrations|Use instance level settings"
+msgstr ""
+
msgid "Integrations|When a Jira issue is mentioned in a commit or merge request a remote link and comment (if enabled) will be created."
msgstr ""
@@ -12320,6 +12880,9 @@ msgstr ""
msgid "Invalid two-factor code."
msgstr ""
+msgid "Invalid yaml"
+msgstr ""
+
msgid "Invitation"
msgstr ""
@@ -12494,10 +13057,10 @@ msgstr ""
msgid "Issues referenced by merge requests and commits within the default branch will be closed automatically"
msgstr ""
-msgid "Issues successfully imported with the label"
+msgid "Issues with comments, merge requests with diffs and comments, labels, milestones, snippets, and other project entities"
msgstr ""
-msgid "Issues with comments, merge requests with diffs and comments, labels, milestones, snippets, and other project entities"
+msgid "Issues with no epic assigned"
msgstr ""
msgid "Issues, merge requests, pushes, and comments."
@@ -12554,6 +13117,9 @@ msgstr ""
msgid "Iteration removed"
msgstr ""
+msgid "Iteration updated"
+msgstr ""
+
msgid "Iterations"
msgstr ""
@@ -12584,33 +13150,60 @@ msgstr ""
msgid "January"
msgstr ""
+msgid "Jira Issues"
+msgstr ""
+
+msgid "Jira display name"
+msgstr ""
+
msgid "Jira import is already running."
msgstr ""
msgid "Jira integration not configured."
msgstr ""
+msgid "Jira project key is not configured"
+msgstr ""
+
msgid "Jira project: %{importProject}"
msgstr ""
msgid "Jira service not configured."
msgstr ""
+msgid "Jira users have been matched with similar GitLab users. This can be overwritten by selecting a GitLab user from the dropdown in the \"GitLab username\" column. If it wasn't possible to match a Jira user with a GitLab user, the dropdown defaults to the user conducting the import."
+msgstr ""
+
+msgid "Jira-GitLab user mapping template"
+msgstr ""
+
msgid "JiraService| on branch %{branch_link}"
msgstr ""
msgid "JiraService|%{user_link} mentioned this issue in %{entity_link} of %{project_link}%{branch}:{quote}%{entity_message}{quote}"
msgstr ""
+msgid "JiraService|Displaying Jira issues while leaving the GitLab issue functionality enabled might be confusing. Consider %{linkStart}disabling GitLab issues%{linkEnd} if they won’t otherwise be used."
+msgstr ""
+
+msgid "JiraService|Enable Jira issues"
+msgstr ""
+
msgid "JiraService|Events for %{noteable_model_name} are disabled."
msgstr ""
msgid "JiraService|If different from Web URL"
msgstr ""
+msgid "JiraService|Issue List"
+msgstr ""
+
msgid "JiraService|Jira API URL"
msgstr ""
+msgid "JiraService|Jira Issues"
+msgstr ""
+
msgid "JiraService|Jira comments will be created when an issue gets referenced in a commit."
msgstr ""
@@ -12620,6 +13213,12 @@ msgstr ""
msgid "JiraService|Jira issue tracker"
msgstr ""
+msgid "JiraService|Jira project key"
+msgstr ""
+
+msgid "JiraService|Open Jira"
+msgstr ""
+
msgid "JiraService|Password or API token"
msgstr ""
@@ -12638,9 +13237,21 @@ msgstr ""
msgid "JiraService|Username or Email"
msgstr ""
+msgid "JiraService|View Jira issues in GitLab"
+msgstr ""
+
+msgid "JiraService|Warning: All GitLab users that have access to this GitLab project will be able to view all issues from the Jira project specified below."
+msgstr ""
+
msgid "JiraService|Web URL"
msgstr ""
+msgid "JiraService|Work on Jira issues without leaving GitLab. Adds a Jira menu to access your list of Jira issues and view any issue as read-only."
+msgstr ""
+
+msgid "JiraService|e.g. AB"
+msgstr ""
+
msgid "JiraService|transition ids can have only numbers which can be split with , or ;"
msgstr ""
@@ -12770,6 +13381,9 @@ msgstr ""
msgid "Keep divergent refs"
msgstr ""
+msgid "Kerberos access denied"
+msgstr ""
+
msgid "Key"
msgstr ""
@@ -12779,10 +13393,10 @@ msgstr ""
msgid "Key: %{key}"
msgstr ""
-msgid "Keyboard Shortcuts"
+msgid "Keyboard shortcuts"
msgstr ""
-msgid "Keyboard shortcuts"
+msgid "Keys"
msgstr ""
msgid "Ki"
@@ -12827,6 +13441,9 @@ msgstr ""
msgid "LDAP"
msgstr ""
+msgid "LDAP Synchronization"
+msgstr ""
+
msgid "LDAP settings"
msgstr ""
@@ -12928,6 +13545,9 @@ msgstr ""
msgid "Last Seen"
msgstr ""
+msgid "Last Used"
+msgstr ""
+
msgid "Last accessed on"
msgstr ""
@@ -13009,9 +13629,6 @@ msgstr ""
msgid "Learn GitLab"
msgstr ""
-msgid "Learn More"
-msgstr ""
-
msgid "Learn how to %{link_start}contribute to the built-in templates%{link_end}"
msgstr ""
@@ -13060,6 +13677,9 @@ msgstr ""
msgid "Learn more about group-level project templates"
msgstr ""
+msgid "Learn more about job dependencies"
+msgstr ""
+
msgid "Learn more about signing commits"
msgstr ""
@@ -13111,12 +13731,24 @@ msgstr ""
msgid "License History"
msgstr ""
+msgid "License ID:"
+msgstr ""
+
msgid "License URL"
msgstr ""
msgid "License-Check"
msgstr ""
+msgid "LicenseCompliance|%{docLinkStart}License Approvals%{docLinkEnd} are active"
+msgstr ""
+
+msgid "LicenseCompliance|%{docLinkStart}License Approvals%{docLinkEnd} are inactive"
+msgstr ""
+
+msgid "LicenseCompliance|Acceptable license to be used in the project"
+msgstr ""
+
msgid "LicenseCompliance|Add a license"
msgstr ""
@@ -13138,9 +13770,23 @@ msgstr ""
msgid "LicenseCompliance|Deny"
msgstr ""
+msgid "LicenseCompliance|Disallow merge request if detected and will instruct developer to remove"
+msgstr ""
+
+msgid "LicenseCompliance|Learn more about %{linkStart}License Approvals%{linkEnd}"
+msgstr ""
+
msgid "LicenseCompliance|License"
msgstr ""
+msgid "LicenseCompliance|License Approvals"
+msgstr ""
+
+msgid "LicenseCompliance|License Compliance detected %d license and policy violation for the source branch only"
+msgid_plural "LicenseCompliance|License Compliance detected %d licenses and policy violations for the source branch only"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "LicenseCompliance|License Compliance detected %d license and policy violation for the source branch only; approval required"
msgid_plural "LicenseCompliance|License Compliance detected %d licenses and policy violations for the source branch only; approval required"
msgstr[0] ""
@@ -13156,6 +13802,11 @@ msgid_plural "LicenseCompliance|License Compliance detected %d new licenses"
msgstr[0] ""
msgstr[1] ""
+msgid "LicenseCompliance|License Compliance detected %d new license and policy violation"
+msgid_plural "LicenseCompliance|License Compliance detected %d new licenses and policy violations"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "LicenseCompliance|License Compliance detected %d new license and policy violation; approval required"
msgid_plural "LicenseCompliance|License Compliance detected %d new licenses and policy violations; approval required"
msgstr[0] ""
@@ -13224,6 +13875,9 @@ msgstr ""
msgid "Licenses|%{remainingComponentsCount} more"
msgstr ""
+msgid "Licenses|Acceptable license to be used in the project"
+msgstr ""
+
msgid "Licenses|Component"
msgstr ""
@@ -13236,6 +13890,9 @@ msgstr ""
msgid "Licenses|Detected licenses that are out-of-compliance with the project's assigned policies"
msgstr ""
+msgid "Licenses|Disallow Merge request if detected and will instruct the developer to remove"
+msgstr ""
+
msgid "Licenses|Displays licenses detected in the project, based on the %{linkStart}latest successful%{linkEnd} scan"
msgstr ""
@@ -13322,6 +13979,9 @@ msgstr ""
msgid "Link title is required"
msgstr ""
+msgid "Link to go to GitLab pipeline documentation"
+msgstr ""
+
msgid "Linked emails (%{email_count})"
msgstr ""
@@ -13346,9 +14006,6 @@ msgstr ""
msgid "List available repositories"
msgstr ""
-msgid "List of IPs and CIDRs of allowed secondary nodes. Comma-separated, e.g. \"1.1.1.1, 2.2.2.0/24\""
-msgstr ""
-
msgid "List settings"
msgstr ""
@@ -13565,6 +14222,9 @@ msgstr ""
msgid "Manifest file import"
msgstr ""
+msgid "Manifest import"
+msgstr ""
+
msgid "Manual job"
msgstr ""
@@ -13607,6 +14267,9 @@ msgstr ""
msgid "Markdown"
msgstr ""
+msgid "Markdown Help"
+msgstr ""
+
msgid "Markdown enabled"
msgstr ""
@@ -13676,6 +14339,24 @@ msgstr ""
msgid "Maven Metadata"
msgstr ""
+msgid "Max Group Export Download requests per minute per user"
+msgstr ""
+
+msgid "Max Group Export requests per minute per user"
+msgstr ""
+
+msgid "Max Group Import requests per minute per user"
+msgstr ""
+
+msgid "Max Project Export Download requests per minute per user"
+msgstr ""
+
+msgid "Max Project Export requests per minute per user"
+msgstr ""
+
+msgid "Max Project Import requests per minute per user"
+msgstr ""
+
msgid "Max access level"
msgstr ""
@@ -13712,6 +14393,9 @@ msgstr ""
msgid "Maximum field length"
msgstr ""
+msgid "Maximum file size is 2MB. Please select a smaller file."
+msgstr ""
+
msgid "Maximum import size (MB)"
msgstr ""
@@ -13721,6 +14405,9 @@ msgstr ""
msgid "Maximum job timeout has a value which could not be accepted"
msgstr ""
+msgid "Maximum length 100 characters"
+msgstr ""
+
msgid "Maximum lifetime allowable for Personal Access Tokens is active, your expire date must be set before %{maximum_allowable_date}."
msgstr ""
@@ -13808,6 +14495,9 @@ msgstr ""
msgid "Merge (when the pipeline succeeds)"
msgstr ""
+msgid "Merge Conflicts"
+msgstr ""
+
msgid "Merge Request"
msgstr ""
@@ -13862,6 +14552,9 @@ msgstr ""
msgid "Merge request dependencies"
msgstr ""
+msgid "Merge request was scheduled to merge after pipeline succeeds"
+msgstr ""
+
msgid "Merge requests"
msgstr ""
@@ -13934,6 +14627,9 @@ msgstr ""
msgid "MergeRequests|Squash task canceled: another squash is already in progress."
msgstr ""
+msgid "MergeRequests|This project does not allow squashing commits when merge requests are accepted."
+msgstr ""
+
msgid "MergeRequests|Thread stays resolved"
msgstr ""
@@ -14114,15 +14810,27 @@ msgstr ""
msgid "Metrics|Avg"
msgstr ""
+msgid "Metrics|Cancel"
+msgstr ""
+
msgid "Metrics|Check out the CI/CD documentation on deploying to an environment"
msgstr ""
msgid "Metrics|Create custom dashboard %{fileName}"
msgstr ""
+msgid "Metrics|Create dashboard"
+msgstr ""
+
msgid "Metrics|Create metric"
msgstr ""
+msgid "Metrics|Create new dashboard"
+msgstr ""
+
+msgid "Metrics|Create your dashboard configuration file"
+msgstr ""
+
msgid "Metrics|Current"
msgstr ""
@@ -14135,6 +14843,9 @@ msgstr ""
msgid "Metrics|Duplicate"
msgstr ""
+msgid "Metrics|Duplicate current dashboard"
+msgstr ""
+
msgid "Metrics|Duplicate dashboard"
msgstr ""
@@ -14176,6 +14887,9 @@ msgstr ""
msgid "Metrics|Max"
msgstr ""
+msgid "Metrics|Metrics Settings"
+msgstr ""
+
msgid "Metrics|Min"
msgstr ""
@@ -14185,6 +14899,9 @@ msgstr ""
msgid "Metrics|New metric"
msgstr ""
+msgid "Metrics|Open repository"
+msgstr ""
+
msgid "Metrics|PromQL query is valid"
msgstr ""
@@ -14194,6 +14911,12 @@ msgstr ""
msgid "Metrics|Refresh dashboard"
msgstr ""
+msgid "Metrics|Select a value"
+msgstr ""
+
+msgid "Metrics|Set refresh rate"
+msgstr ""
+
msgid "Metrics|Star dashboard"
msgstr ""
@@ -14212,12 +14935,18 @@ msgstr ""
msgid "Metrics|There was an error getting annotations information."
msgstr ""
+msgid "Metrics|There was an error getting dashboard validation warnings information."
+msgstr ""
+
msgid "Metrics|There was an error getting deployment information."
msgstr ""
msgid "Metrics|There was an error getting environments information."
msgstr ""
+msgid "Metrics|There was an error getting options for variable \"%{name}\"."
+msgstr ""
+
msgid "Metrics|There was an error trying to validate your query"
msgstr ""
@@ -14227,6 +14956,9 @@ msgstr ""
msgid "Metrics|There was an error while retrieving metrics. %{message}"
msgstr ""
+msgid "Metrics|To create a new dashboard, add a new YAML file to %{codeStart}.gitlab/dashboards%{codeEnd} at the root of this project."
+msgstr ""
+
msgid "Metrics|Unexpected deployment data response from prometheus endpoint"
msgstr ""
@@ -14248,6 +14980,9 @@ msgstr ""
msgid "Metrics|Values"
msgstr ""
+msgid "Metrics|View documentation"
+msgstr ""
+
msgid "Metrics|View logs"
msgstr ""
@@ -14260,6 +14995,9 @@ msgstr ""
msgid "Metrics|You're about to permanently delete this metric. This cannot be undone."
msgstr ""
+msgid "Metrics|Your dashboard schema is invalid. Edit the dashboard to correct the YAML schema."
+msgstr ""
+
msgid "Metrics|e.g. HTTP requests"
msgstr ""
@@ -14421,6 +15159,9 @@ msgstr ""
msgid "Minimum capacity to be available before we schedule more mirrors preemptively."
msgstr ""
+msgid "Minimum interval in days"
+msgstr ""
+
msgid "Minimum length is %{minimum_password_length} characters"
msgstr ""
@@ -14541,6 +15282,9 @@ msgstr ""
msgid "More information is available|here"
msgstr ""
+msgid "More information."
+msgstr ""
+
msgid "More than %{number_commits_distance} commits different with %{default_branch}"
msgstr ""
@@ -14595,7 +15339,16 @@ msgstr ""
msgid "MrDeploymentActions|Stop environment"
msgstr ""
-msgid "Multiple domains are supported with comma delimiters."
+msgid "Multi-project"
+msgstr ""
+
+msgid "Multi-project Runners cannot be removed"
+msgstr ""
+
+msgid "Multiple IP address ranges are supported."
+msgstr ""
+
+msgid "Multiple domains are supported."
msgstr ""
msgid "Multiple issue boards"
@@ -14607,6 +15360,12 @@ msgstr ""
msgid "Multiple uploaders found: %{uploader_types}"
msgstr ""
+msgid "Must match with the %{codeStart}external_url%{codeEnd} in %{codeStart}/etc/gitlab/gitlab.rb%{codeEnd}."
+msgstr ""
+
+msgid "Must match with the %{codeStart}geo_node_name%{codeEnd} in %{codeStart}/etc/gitlab/gitlab.rb%{codeEnd}. %{linkStart}More information%{linkEnd}"
+msgstr ""
+
msgid "My Awesome Group"
msgstr ""
@@ -14616,21 +15375,36 @@ msgstr ""
msgid "My-Reaction"
msgstr ""
+msgid "N/A"
+msgstr ""
+
msgid "Name"
msgstr ""
msgid "Name has already been taken"
msgstr ""
+msgid "Name is required"
+msgstr ""
+
msgid "Name new label"
msgstr ""
msgid "Name:"
msgstr ""
+msgid "Namespace"
+msgstr ""
+
msgid "Namespace is empty"
msgstr ""
+msgid "Namespace:"
+msgstr ""
+
+msgid "Namespaces"
+msgstr ""
+
msgid "Namespaces to index"
msgstr ""
@@ -14721,10 +15495,19 @@ msgstr ""
msgid "New Application"
msgstr ""
+msgid "New Branch"
+msgstr ""
+
+msgid "New Deploy Key"
+msgstr ""
+
msgid "New Environment"
msgstr ""
-msgid "New Geo Node"
+msgid "New Epic"
+msgstr ""
+
+msgid "New File"
msgstr ""
msgid "New Group"
@@ -14741,15 +15524,15 @@ msgid_plural "New Issues"
msgstr[0] ""
msgstr[1] ""
-msgid "New Iteration"
-msgstr ""
-
msgid "New Jira import"
msgstr ""
msgid "New Label"
msgstr ""
+msgid "New Merge Request"
+msgstr ""
+
msgid "New Milestone"
msgstr ""
@@ -14768,6 +15551,9 @@ msgstr ""
msgid "New Snippet"
msgstr ""
+msgid "New User"
+msgstr ""
+
msgid "New branch"
msgstr ""
@@ -14777,6 +15563,12 @@ msgstr ""
msgid "New changes were added. %{linkStart}Reload the page to review them%{linkEnd}"
msgstr ""
+msgid "New confidential epic title "
+msgstr ""
+
+msgid "New confidential issue title"
+msgstr ""
+
msgid "New deploy key"
msgstr ""
@@ -14897,6 +15689,9 @@ msgstr ""
msgid "No Epic"
msgstr ""
+msgid "No Matching Results"
+msgstr ""
+
msgid "No Scopes"
msgstr ""
@@ -14912,10 +15707,10 @@ msgstr ""
msgid "No application_settings found"
msgstr ""
-msgid "No approvers"
+msgid "No authentication methods configured."
msgstr ""
-msgid "No authentication methods configured."
+msgid "No available groups to fork the project."
msgstr ""
msgid "No available namespaces to fork the project."
@@ -14990,6 +15785,9 @@ msgstr ""
msgid "No grouping"
msgstr ""
+msgid "No issues found"
+msgstr ""
+
msgid "No iteration"
msgstr ""
@@ -15023,6 +15821,9 @@ msgstr ""
msgid "No matching results"
msgstr ""
+msgid "No matching results for \"%{query}\""
+msgstr ""
+
msgid "No merge requests found"
msgstr ""
@@ -15038,6 +15839,9 @@ msgstr ""
msgid "No other labels with such name or description"
msgstr ""
+msgid "No panels matching properties %{opts}"
+msgstr ""
+
msgid "No parent group"
msgstr ""
@@ -15056,6 +15860,9 @@ msgstr ""
msgid "No public groups"
msgstr ""
+msgid "No ref selected"
+msgstr ""
+
msgid "No related merge requests found."
msgstr ""
@@ -15071,19 +15878,25 @@ msgstr ""
msgid "No schedules"
msgstr ""
+msgid "No stack trace for this error"
+msgstr ""
+
msgid "No starrers matched your search"
msgstr ""
msgid "No start date"
msgstr ""
+msgid "No status"
+msgstr ""
+
msgid "No template"
msgstr ""
-msgid "No test coverage"
+msgid "No template selected"
msgstr ""
-msgid "No thanks, don't show this again"
+msgid "No test coverage"
msgstr ""
msgid "No vulnerabilities present"
@@ -15098,9 +15911,6 @@ msgstr ""
msgid "No, directly import the existing email addresses and usernames."
msgstr ""
-msgid "No, not interested right now"
-msgstr ""
-
msgid "No. of commits"
msgstr ""
@@ -15146,9 +15956,6 @@ msgstr ""
msgid "Not found."
msgstr ""
-msgid "Not helpful"
-msgstr ""
-
msgid "Not now"
msgstr ""
@@ -15167,7 +15974,7 @@ msgstr ""
msgid "Note parameters are invalid: %{errors}"
msgstr ""
-msgid "Note that PostgreSQL 11 will become the minimum required PostgreSQL version in GitLab 13.0 (May 2020). PostgreSQL 9.6 and PostgreSQL 10 will no longer be supported in GitLab 13.0. Please consider upgrading your PostgreSQL version (%{db_version}) soon."
+msgid "Note that PostgreSQL %{pg_version_upcoming} will become the minimum required version in GitLab %{gl_version_upcoming} (%{gl_version_upcoming_date}). Please consider upgrading your environment to a supported PostgreSQL version soon, see <a href=\"%{pg_version_upcoming_url}\">the related epic</a> for details."
msgstr ""
msgid "Note that this invitation was sent to %{mail_to_invite_email}, but you are signed in as %{link_to_current_user} with email %{mail_to_current_user}."
@@ -15305,6 +16112,9 @@ msgstr ""
msgid "Notifications on"
msgstr ""
+msgid "Notify users by email when sign-in location is not recognized"
+msgstr ""
+
msgid "Nov"
msgstr ""
@@ -15368,10 +16178,10 @@ msgstr ""
msgid "OfSearchInADropdown|Filter"
msgstr ""
-msgid "Oh no!"
+msgid "Off"
msgstr ""
-msgid "Ok let's go"
+msgid "Oh no!"
msgstr ""
msgid "Oldest first"
@@ -15431,9 +16241,6 @@ msgstr ""
msgid "OnDemandScans|Target URL"
msgstr ""
-msgid "Onboarding"
-msgstr ""
-
msgid "Once imported, repositories can be mirrored over SSH. Read more %{link_start}here%{link_end}."
msgstr ""
@@ -15460,9 +16267,6 @@ msgstr ""
msgid "One or more of your %{provider} projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
msgstr ""
-msgid "One or more of your Bitbucket projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
-msgstr ""
-
msgid "One or more of your Google Code projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
msgstr ""
@@ -15532,6 +16336,9 @@ msgstr ""
msgid "Open in Xcode"
msgstr ""
+msgid "Open in file view"
+msgstr ""
+
msgid "Open issues"
msgstr ""
@@ -15553,6 +16360,9 @@ msgstr ""
msgid "Opened"
msgstr ""
+msgid "Opened %{epicTimeagoDate}"
+msgstr ""
+
msgid "Opened MRs"
msgstr ""
@@ -15667,10 +16477,10 @@ msgstr ""
msgid "Owned by me"
msgstr ""
-msgid "Owner"
+msgid "Owned by:"
msgstr ""
-msgid "Package Files"
+msgid "Owner"
msgstr ""
msgid "Package Registry"
@@ -15913,6 +16723,33 @@ msgstr ""
msgid "Page was successfully deleted"
msgstr ""
+msgid "PagerDutySettings|Active"
+msgstr ""
+
+msgid "PagerDutySettings|Create a GitLab issue for each PagerDuty incident by %{docsLink}"
+msgstr ""
+
+msgid "PagerDutySettings|Failed to update Webhook URL"
+msgstr ""
+
+msgid "PagerDutySettings|Reset webhook URL"
+msgstr ""
+
+msgid "PagerDutySettings|Resetting the webhook URL for this project will require updating this integration's settings in PagerDuty."
+msgstr ""
+
+msgid "PagerDutySettings|Setting up a webhook with PagerDuty will automatically create a GitLab issue for each PagerDuty incident."
+msgstr ""
+
+msgid "PagerDutySettings|Webhook URL"
+msgstr ""
+
+msgid "PagerDutySettings|Webhook URL update was successful"
+msgstr ""
+
+msgid "PagerDutySettings|configuring a webhook in PagerDuty"
+msgstr ""
+
msgid "Pages"
msgstr ""
@@ -15961,6 +16798,9 @@ msgstr ""
msgid "Parent epic is not present."
msgstr ""
+msgid "Parsing error for param :embed_json. %{message}"
+msgstr ""
+
msgid "Part of merge request changes"
msgstr ""
@@ -16009,6 +16849,12 @@ msgstr ""
msgid "Paste a machine public key here. Read more about how to generate it %{link_start}here%{link_end}"
msgstr ""
+msgid "Paste confidential epic link"
+msgstr ""
+
+msgid "Paste confidential issue link"
+msgstr ""
+
msgid "Paste epic link"
msgstr ""
@@ -16048,7 +16894,7 @@ msgstr ""
msgid "People without permission will never get a notification."
msgstr ""
-msgid "Percent rollout (logged in users)"
+msgid "Percent of users"
msgstr ""
msgid "Percentage"
@@ -16060,6 +16906,9 @@ msgstr ""
msgid "Perform common operations on GitLab project"
msgstr ""
+msgid "Performance and resource management"
+msgstr ""
+
msgid "Performance optimization"
msgstr ""
@@ -16093,6 +16942,9 @@ msgstr ""
msgid "Permissions"
msgstr ""
+msgid "Permissions Help"
+msgstr ""
+
msgid "Permissions, LFS, 2FA"
msgstr ""
@@ -16162,6 +17014,9 @@ msgstr ""
msgid "PipelineCharts|Total:"
msgstr ""
+msgid "PipelineScheduleIntervalPattern|Custom (%{linkStart}Cron syntax%{linkEnd})"
+msgstr ""
+
msgid "PipelineSchedules|Activated"
msgstr ""
@@ -16192,9 +17047,6 @@ msgstr ""
msgid "PipelineSchedules|Variables"
msgstr ""
-msgid "PipelineSheduleIntervalPattern|Custom"
-msgstr ""
-
msgid "PipelineStatusTooltip|Pipeline: %{ciStatus}"
msgstr ""
@@ -16222,9 +17074,6 @@ msgstr ""
msgid "Pipelines for merge requests are configured. A detached pipeline runs in the context of the merge request, and not against the merged result. Learn more in the documentation for Pipelines for Merged Results."
msgstr ""
-msgid "Pipelines must succeed for merge requests to be eligible to merge. Please enable pipelines for this project to continue. For more information, see the %{linkStart}documentation.%{linkEnd}"
-msgstr ""
-
msgid "Pipelines settings for '%{project_name}' were successfully updated."
msgstr ""
@@ -16267,7 +17116,7 @@ msgstr ""
msgid "Pipelines|Something went wrong while cleaning runners cache."
msgstr ""
-msgid "Pipelines|There are currently no %{scope} pipelines."
+msgid "Pipelines|There are currently no finished pipelines."
msgstr ""
msgid "Pipelines|There are currently no pipelines."
@@ -16291,6 +17140,9 @@ msgstr ""
msgid "Pipeline|Canceled"
msgstr ""
+msgid "Pipeline|Checking pipeline status."
+msgstr ""
+
msgid "Pipeline|Commit"
msgstr ""
@@ -16330,9 +17182,6 @@ msgstr ""
msgid "Pipeline|Merged result pipeline"
msgstr ""
-msgid "Pipeline|No pipeline has been run for this commit."
-msgstr ""
-
msgid "Pipeline|Passed"
msgstr ""
@@ -16462,16 +17311,19 @@ msgstr ""
msgid "Please check your email (%{email}) to verify that you own this address and unlock the power of CI/CD. Didn't receive it? %{resend_link}. Wrong email address? %{update_link}."
msgstr ""
+msgid "Please choose a file"
+msgstr ""
+
msgid "Please choose a group URL with no special characters."
msgstr ""
msgid "Please complete your profile with email address"
msgstr ""
-msgid "Please convert %{linkStart}them to Git%{linkEnd}, and go through the %{linkToImportFlow} again."
+msgid "Please contact your administrator."
msgstr ""
-msgid "Please convert them to %{link_to_git}, and go through the %{link_to_import_flow} again."
+msgid "Please convert %{linkStart}them to Git%{linkEnd}, and go through the %{linkToImportFlow} again."
msgstr ""
msgid "Please convert them to Git on Google Code, and go through the %{link_to_import_flow} again."
@@ -16573,9 +17425,6 @@ msgstr ""
msgid "Please type %{phrase_code} to proceed or close this modal to cancel."
msgstr ""
-msgid "Please upgrade PostgreSQL to version 9.6 or greater. The status of the replication cannot be determined reliably with the current version."
-msgstr ""
-
msgid "Please use this form to report to the admin users who create spam issues, comments or behave inappropriately."
msgstr ""
@@ -16639,6 +17488,9 @@ msgstr ""
msgid "Preferences|For example: 30 mins ago."
msgstr ""
+msgid "Preferences|Instead of all the files changed, show only one file at a time. To switch between files, use the file browser."
+msgstr ""
+
msgid "Preferences|Integrations"
msgstr ""
@@ -16657,6 +17509,9 @@ msgstr ""
msgid "Preferences|Render whitespace characters in the Web IDE"
msgstr ""
+msgid "Preferences|Show one file at a time on merge request's Changes tab"
+msgstr ""
+
msgid "Preferences|Show whitespace changes in diffs"
msgstr ""
@@ -16954,7 +17809,7 @@ msgstr ""
msgid "Profiles|Full name"
msgstr ""
-msgid "Profiles|Give your individual key a title"
+msgid "Profiles|Give your individual key a title. This will be publically visible."
msgstr ""
msgid "Profiles|Include private contributions on my profile"
@@ -17236,7 +18091,7 @@ msgstr ""
msgid "Project clone URL"
msgstr ""
-msgid "Project configuration, including services"
+msgid "Project configuration, excluding integrations"
msgstr ""
msgid "Project description (optional)"
@@ -17266,6 +18121,12 @@ msgstr ""
msgid "Project has too many %{label_for_message} to search"
msgstr ""
+msgid "Project info:"
+msgstr ""
+
+msgid "Project is required when cluster_type is :project"
+msgstr ""
+
msgid "Project members"
msgstr ""
@@ -17383,7 +18244,40 @@ msgstr ""
msgid "ProjectService|Comment"
msgstr ""
-msgid "ProjectService|Comment will be posted on each event"
+msgid "ProjectService|Event will be triggered by a push to the repository"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a commit is created/updated"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a confidential issue is created/updated/closed"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a deployment finishes"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a merge request is created/updated/merged"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a new tag is pushed to the repository"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a new, unique alert is recorded"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a pipeline status changes"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a wiki page is created/updated"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when an issue is created/updated/closed"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when someone adds a comment"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when someone adds a comment on a confidential issue"
msgstr ""
msgid "ProjectService|Perform common operations on GitLab project: %{project_name}"
@@ -17398,6 +18292,9 @@ msgstr ""
msgid "ProjectSettings|All discussions must be resolved"
msgstr ""
+msgid "ProjectSettings|Allow"
+msgstr ""
+
msgid "ProjectSettings|Allow users to make copies of your repository to a new project"
msgstr ""
@@ -17413,6 +18310,12 @@ msgstr ""
msgid "ProjectSettings|Build, test, and deploy your changes"
msgstr ""
+msgid "ProjectSettings|Checkbox is visible and selected by default."
+msgstr ""
+
+msgid "ProjectSettings|Checkbox is visible and unselected by default."
+msgstr ""
+
msgid "ProjectSettings|Choose your merge method, merge options, merge checks, and merge suggestions."
msgstr ""
@@ -17431,12 +18334,18 @@ msgstr ""
msgid "ProjectSettings|Disable email notifications"
msgstr ""
+msgid "ProjectSettings|Do not allow"
+msgstr ""
+
msgid "ProjectSettings|Enable 'Delete source branch' option by default"
msgstr ""
msgid "ProjectSettings|Enable merge trains and pipelines for merged results"
msgstr ""
+msgid "ProjectSettings|Encourage"
+msgstr ""
+
msgid "ProjectSettings|Every merge creates a merge commit"
msgstr ""
@@ -17467,7 +18376,10 @@ msgstr ""
msgid "ProjectSettings|Forks"
msgstr ""
-msgid "ProjectSettings|Git Large File Storage"
+msgid "ProjectSettings|Git Large File Storage (LFS)"
+msgstr ""
+
+msgid "ProjectSettings|Global"
msgstr ""
msgid "ProjectSettings|Internal"
@@ -17551,6 +18463,12 @@ msgstr ""
msgid "ProjectSettings|Repository"
msgstr ""
+msgid "ProjectSettings|Require"
+msgstr ""
+
+msgid "ProjectSettings|Set the default behavior and availability of this option in merge requests. Changes made are also applied to existing merge requests."
+msgstr ""
+
msgid "ProjectSettings|Share code pastes with others out of Git repository"
msgstr ""
@@ -17566,6 +18484,15 @@ msgstr ""
msgid "ProjectSettings|Snippets"
msgstr ""
+msgid "ProjectSettings|Squash commits when merging"
+msgstr ""
+
+msgid "ProjectSettings|Squashing is always performed. Checkbox is visible and selected, and users cannot change it."
+msgstr ""
+
+msgid "ProjectSettings|Squashing is never performed and the checkbox is hidden."
+msgstr ""
+
msgid "ProjectSettings|Submit changes to be merged upstream"
msgstr ""
@@ -17596,6 +18523,9 @@ msgstr ""
msgid "ProjectSettings|This will dictate the commit history when you merge a merge request"
msgstr ""
+msgid "ProjectSettings|Transfer project"
+msgstr ""
+
msgid "ProjectSettings|Users can only push commits to this repository that were committed with one of their own verified emails."
msgstr ""
@@ -18067,6 +18997,9 @@ msgstr ""
msgid "Protected Branch"
msgstr ""
+msgid "Protected Branches"
+msgstr ""
+
msgid "Protected Environment"
msgstr ""
@@ -18079,6 +19012,9 @@ msgstr ""
msgid "Protected Tag"
msgstr ""
+msgid "Protected Tags"
+msgstr ""
+
msgid "Protected branches"
msgstr ""
@@ -18178,6 +19114,9 @@ msgstr ""
msgid "Provider"
msgstr ""
+msgid "Proxy support for this API is not available currently"
+msgstr ""
+
msgid "Pseudonymizer data collection"
msgstr ""
@@ -18190,6 +19129,9 @@ msgstr ""
msgid "Public - The project can be accessed without any authentication."
msgstr ""
+msgid "Public Access Help"
+msgstr ""
+
msgid "Public deploy keys (%{deploy_keys_count})"
msgstr ""
@@ -18310,9 +19252,6 @@ msgstr ""
msgid "Queued"
msgstr ""
-msgid "Quick actions"
-msgstr ""
-
msgid "Quick actions can be used in the issues description and comment boxes."
msgstr ""
@@ -18322,6 +19261,9 @@ msgstr ""
msgid "README"
msgstr ""
+msgid "Rake Tasks Help"
+msgstr ""
+
msgid "Raw blob request rate limit per minute"
msgstr ""
@@ -18337,9 +19279,6 @@ msgstr ""
msgid "Read more"
msgstr ""
-msgid "Read more about environments"
-msgstr ""
-
msgid "Read more about project permissions <strong>%{link_to_help}</strong>"
msgstr ""
@@ -18400,6 +19339,9 @@ msgstr ""
msgid "Reference:"
msgstr ""
+msgid "References"
+msgstr ""
+
msgid "Refresh"
msgstr ""
@@ -18468,6 +19410,12 @@ msgstr ""
msgid "Registration|Your profile"
msgstr ""
+msgid "Regulate approvals by authors/committers, based on compliance frameworks. Can be changed only at the instance level."
+msgstr ""
+
+msgid "Reindexing status"
+msgstr ""
+
msgid "Rejected (closed)"
msgstr ""
@@ -18548,6 +19496,9 @@ msgstr ""
msgid "Releases documentation"
msgstr ""
+msgid "Releases|New Release"
+msgstr ""
+
msgid "Release|Something went wrong while getting the release details"
msgstr ""
@@ -18641,6 +19592,9 @@ msgstr ""
msgid "Remove limit"
msgstr ""
+msgid "Remove member"
+msgstr ""
+
msgid "Remove milestone"
msgstr ""
@@ -18749,12 +19703,15 @@ msgstr ""
msgid "Removes time estimate."
msgstr ""
-msgid "Removing a project places it into a read-only state until %{date}, at which point the project will be permanantly removed. Are you ABSOLUTELY sure?"
+msgid "Removing a project deletes it immediately, there will be no delay before the project is permanently removed."
msgstr ""
msgid "Removing a project places it into a read-only state until %{date}, at which point the project will be permanently removed."
msgstr ""
+msgid "Removing a project places it into a read-only state until %{date}, at which point the project will be permanently removed. Are you ABSOLUTELY sure?"
+msgstr ""
+
msgid "Removing license…"
msgstr ""
@@ -18812,6 +19769,12 @@ msgstr ""
msgid "Replication"
msgstr ""
+msgid "Replication enabled"
+msgstr ""
+
+msgid "Replication paused"
+msgstr ""
+
msgid "Reply by email"
msgstr ""
@@ -18839,13 +19802,10 @@ msgstr ""
msgid "Reported %{timeAgo} by %{reportedBy}"
msgstr ""
-msgid "Reported Resource Changes: %{addNum} to add, %{changeNum} to change, %{deleteNum} to delete"
-msgstr ""
-
-msgid "Reporter"
+msgid "Reporting"
msgstr ""
-msgid "Reporting"
+msgid "Reports"
msgstr ""
msgid "Reports|%{combinedString} and %{resolvedString}"
@@ -18886,6 +19846,9 @@ msgstr ""
msgid "Reports|Failure"
msgstr ""
+msgid "Reports|Identifier"
+msgstr ""
+
msgid "Reports|Metrics reports are loading"
msgstr ""
@@ -18898,6 +19861,9 @@ msgstr ""
msgid "Reports|Metrics reports failed loading results"
msgstr ""
+msgid "Reports|Scanner"
+msgstr ""
+
msgid "Reports|Severity"
msgstr ""
@@ -18982,7 +19948,7 @@ msgstr ""
msgid "Repository sync capacity"
msgstr ""
-msgid "Repository: %{counter_repositories} / Wikis: %{counter_wikis} / Build Artifacts: %{counter_build_artifacts} / LFS: %{counter_lfs_objects}"
+msgid "Repository: %{counter_repositories} / Wikis: %{counter_wikis} / Build Artifacts: %{counter_build_artifacts} / LFS: %{counter_lfs_objects} / Snippets: %{counter_snippets}"
msgstr ""
msgid "RepositorySettingsAccessLevel|Select"
@@ -18994,6 +19960,9 @@ msgstr ""
msgid "Request Headers"
msgstr ""
+msgid "Request details"
+msgstr ""
+
msgid "Request parameter %{param} is missing."
msgstr ""
@@ -19036,6 +20005,9 @@ msgstr ""
msgid "Required approvals (%{approvals_given} given, you've approved)"
msgstr ""
+msgid "Required in this project."
+msgstr ""
+
msgid "Requirement"
msgstr ""
@@ -19187,10 +20159,7 @@ msgstr ""
msgid "Restoring the project will prevent the project from being removed on this date and restore people's ability to make changes to it."
msgstr ""
-msgid "Restrict access by IP address"
-msgstr ""
-
-msgid "Restrict membership by email"
+msgid "Restrict membership by email domain"
msgstr ""
msgid "Restricts sign-ups for email addresses that match the given regex. See the %{supported_syntax_link_start}supported syntax%{supported_syntax_link_end} for more information."
@@ -19199,9 +20168,6 @@ msgstr ""
msgid "Resume"
msgstr ""
-msgid "Resume replication"
-msgstr ""
-
msgid "Resync"
msgstr ""
@@ -19270,6 +20236,9 @@ msgstr ""
msgid "Revoke"
msgstr ""
+msgid "Revoked"
+msgstr ""
+
msgid "Revoked impersonation token %{token_name}!"
msgstr ""
@@ -19333,6 +20302,9 @@ msgstr ""
msgid "Runner tokens"
msgstr ""
+msgid "Runner was not deleted because it is assigned to multiple projects."
+msgstr ""
+
msgid "Runner was not updated."
msgstr ""
@@ -19390,6 +20362,9 @@ msgstr ""
msgid "SAML for %{group_name}"
msgstr ""
+msgid "SAST Configuration"
+msgstr ""
+
msgid "SHA256"
msgstr ""
@@ -19399,6 +20374,9 @@ msgstr ""
msgid "SSH Keys"
msgstr ""
+msgid "SSH Keys Help"
+msgstr ""
+
msgid "SSH host key fingerprints"
msgstr ""
@@ -19441,15 +20419,15 @@ msgstr ""
msgid "Save comment"
msgstr ""
-msgid "Save expiration policy"
-msgstr ""
-
msgid "Save password"
msgstr ""
msgid "Save pipeline schedule"
msgstr ""
+msgid "Save space and find tags in the Container Registry more easily. Enable the cleanup policy to remove stale tags and keep only the ones you need."
+msgstr ""
+
msgid "Save template"
msgstr ""
@@ -19525,7 +20503,7 @@ msgstr ""
msgid "Search"
msgstr ""
-msgid "Search Button"
+msgid "Search Jira issues"
msgstr ""
msgid "Search Milestones"
@@ -19543,12 +20521,24 @@ msgstr ""
msgid "Search branches and tags"
msgstr ""
+msgid "Search by Git revision"
+msgstr ""
+
msgid "Search by author"
msgstr ""
+msgid "Search by message"
+msgstr ""
+
+msgid "Search by name"
+msgstr ""
+
msgid "Search files"
msgstr ""
+msgid "Search for Namespace"
+msgstr ""
+
msgid "Search for a LDAP group"
msgstr ""
@@ -19749,24 +20739,45 @@ msgstr ""
msgid "Security report is out of date. Run %{newPipelineLinkStart}a new pipeline%{newPipelineLinkEnd} for the target branch (%{targetBranchName})"
msgstr ""
+msgid "SecurityConfiguration|An error occurred while creating the merge request."
+msgstr ""
+
+msgid "SecurityConfiguration|Configure"
+msgstr ""
+
+msgid "SecurityConfiguration|Enable via Merge Request"
+msgstr ""
+
msgid "SecurityConfiguration|Enabled"
msgstr ""
+msgid "SecurityConfiguration|Enabled with Auto DevOps"
+msgstr ""
+
msgid "SecurityConfiguration|Feature documentation for %{featureName}"
msgstr ""
-msgid "SecurityConfiguration|Not yet enabled"
+msgid "SecurityConfiguration|Manage"
+msgstr ""
+
+msgid "SecurityConfiguration|Not enabled"
msgstr ""
msgid "SecurityConfiguration|Security Control"
msgstr ""
+msgid "SecurityConfiguration|See documentation"
+msgstr ""
+
msgid "SecurityConfiguration|Status"
msgstr ""
msgid "SecurityConfiguration|Testing & Compliance"
msgstr ""
+msgid "SecurityConfiguration|You can quickly enable all security scanning tools by enabling %{linkStart}Auto DevOps%{linkEnd}."
+msgstr ""
+
msgid "SecurityReports|%{firstProject} and %{secondProject}"
msgstr ""
@@ -19782,6 +20793,9 @@ msgstr ""
msgid "SecurityReports|Add projects"
msgstr ""
+msgid "SecurityReports|Add projects to your group"
+msgstr ""
+
msgid "SecurityReports|Comment added to '%{vulnerabilityName}'"
msgstr ""
@@ -19806,6 +20820,9 @@ msgstr ""
msgid "SecurityReports|Dismissed '%{vulnerabilityName}'. Turn off the hide dismissed toggle to view."
msgstr ""
+msgid "SecurityReports|Download Report"
+msgstr ""
+
msgid "SecurityReports|Each vulnerability now has a unique page that can be directly linked to, shared, referenced, and tracked as the single source of truth. Vulnerability occurrences also persist across scanner runs, which improves tracking and visibility and reduces duplicates between scans."
msgstr ""
@@ -19824,6 +20841,9 @@ msgstr ""
msgid "SecurityReports|False positive"
msgstr ""
+msgid "SecurityReports|Fuzzing artifacts"
+msgstr ""
+
msgid "SecurityReports|Group Security Dashboard"
msgstr ""
@@ -19836,7 +20856,7 @@ msgstr ""
msgid "SecurityReports|Issue Created"
msgstr ""
-msgid "SecurityReports|Learn More"
+msgid "SecurityReports|Learn more"
msgstr ""
msgid "SecurityReports|Learn more about setting up your dashboard"
@@ -19854,7 +20874,7 @@ msgstr ""
msgid "SecurityReports|More information"
msgstr ""
-msgid "SecurityReports|No vulnerabilities found for dashboard"
+msgid "SecurityReports|No vulnerabilities found"
msgstr ""
msgid "SecurityReports|No vulnerabilities found for this group"
@@ -19881,10 +20901,13 @@ msgstr ""
msgid "SecurityReports|Remove project from dashboard"
msgstr ""
-msgid "SecurityReports|Report type"
+msgid "SecurityReports|Return to dashboard"
msgstr ""
-msgid "SecurityReports|Return to dashboard"
+msgid "SecurityReports|Scan details"
+msgstr ""
+
+msgid "SecurityReports|Scanner"
msgstr ""
msgid "SecurityReports|Security Dashboard"
@@ -19902,12 +20925,18 @@ msgstr ""
msgid "SecurityReports|Severity"
msgstr ""
+msgid "SecurityReports|Sorry, your filter produced no results"
+msgstr ""
+
msgid "SecurityReports|Status"
msgstr ""
msgid "SecurityReports|The rating \"unknown\" indicates that the underlying scanner doesn’t contain or provide a severity rating."
msgstr ""
+msgid "SecurityReports|The security dashboard displays the latest security findings for projects you wish to monitor. Add projects to your group to view their vulnerabilities here."
+msgstr ""
+
msgid "SecurityReports|The security dashboard displays the latest security findings for projects you wish to monitor. Select \"Edit dashboard\" to add and remove projects."
msgstr ""
@@ -19941,6 +20970,12 @@ msgstr ""
msgid "SecurityReports|There was an error while generating the report."
msgstr ""
+msgid "SecurityReports|To widen your search, change or remove filters above"
+msgstr ""
+
+msgid "SecurityReports|Unable to add %{invalidProjectsMessage}"
+msgstr ""
+
msgid "SecurityReports|Unable to add %{invalidProjects}"
msgstr ""
@@ -19956,7 +20991,7 @@ msgstr ""
msgid "SecurityReports|While it's rare to have no vulnerabilities for your project, it can happen. In any event, we ask that you double check your settings to make sure you've set up your dashboard correctly."
msgstr ""
-msgid "SecurityReports|While it's rare to have no vulnerabilities, it can happen. In any event, we ask that you please double check your settings to make sure you've set up your dashboard correctly."
+msgid "SecurityReports|While it's rare to have no vulnerabilities, it can happen. In any event, we ask that you double check your settings to make sure you've set up your dashboard correctly."
msgstr ""
msgid "SecurityReports|Won't fix / Accept risk"
@@ -20037,9 +21072,6 @@ msgstr ""
msgid "Select a timezone"
msgstr ""
-msgid "Select a weight for the storage new repositories will be placed on."
-msgstr ""
-
msgid "Select all"
msgstr ""
@@ -20058,6 +21090,12 @@ msgstr ""
msgid "Select due date"
msgstr ""
+msgid "Select epic"
+msgstr ""
+
+msgid "Select file"
+msgstr ""
+
msgid "Select group or project"
msgstr ""
@@ -20067,6 +21105,9 @@ msgstr ""
msgid "Select health status"
msgstr ""
+msgid "Select label"
+msgstr ""
+
msgid "Select labels"
msgstr ""
@@ -20313,12 +21354,6 @@ msgstr ""
msgid "Service Desk is enabled but not yet active"
msgstr ""
-msgid "Service Desk is off"
-msgstr ""
-
-msgid "Service Desk is on"
-msgstr ""
-
msgid "Service Templates"
msgstr ""
@@ -20388,6 +21423,9 @@ msgstr ""
msgid "Set the default expiration time for each job's artifacts. 0 for unlimited. The default unit is in seconds, but you can define an alternative. For example: <code>4 mins 2 sec</code>, <code>2h42min</code>."
msgstr ""
+msgid "Set the default name of the initial branch when creating new repositories through the user interface."
+msgstr ""
+
msgid "Set the due date to %{due_date}."
msgstr ""
@@ -20406,6 +21444,12 @@ msgstr ""
msgid "Set the milestone to %{milestone_reference}."
msgstr ""
+msgid "Set the number of concurrent requests this secondary node will make to the primary node while backfilling."
+msgstr ""
+
+msgid "Set the synchronization and verification capacity for the secondary node."
+msgstr ""
+
msgid "Set the timeout in seconds to send a secondary node status to the primary and IPs allowed for the secondary nodes."
msgstr ""
@@ -20448,6 +21492,9 @@ msgstr ""
msgid "Set weight to %{weight}."
msgstr ""
+msgid "Set what should be replicated by choosing specific projects or groups by the secondary node."
+msgstr ""
+
msgid "SetPasswordToCloneLink|set a password"
msgstr ""
@@ -20574,6 +21621,9 @@ msgstr ""
msgid "Show complete raw log"
msgstr ""
+msgid "Show details"
+msgstr ""
+
msgid "Show file browser"
msgstr ""
@@ -20583,10 +21633,13 @@ msgstr ""
msgid "Show latest version"
msgstr ""
+msgid "Show list"
+msgstr ""
+
msgid "Show me everything"
msgstr ""
-msgid "Show me how"
+msgid "Show me how to add a pipeline"
msgstr ""
msgid "Show me more advanced stuff"
@@ -20635,9 +21688,6 @@ msgstr ""
msgid "Showing all issues"
msgstr ""
-msgid "Showing all labels"
-msgstr ""
-
msgid "Showing last %{size} of log -"
msgstr ""
@@ -20716,6 +21766,9 @@ msgstr ""
msgid "SignUp|Username is too long (maximum is %{max_length} characters)."
msgstr ""
+msgid "SignUp|Username is too short (minimum is %{min_length} characters)."
+msgstr ""
+
msgid "Signed in"
msgstr ""
@@ -20749,9 +21802,6 @@ msgstr ""
msgid "Skip outdated deployment jobs"
msgstr ""
-msgid "Skip this for now"
-msgstr ""
-
msgid "Skipped"
msgstr ""
@@ -20971,6 +22021,9 @@ msgstr ""
msgid "Something went wrong while initializing the OpenAPI viewer"
msgstr ""
+msgid "Something went wrong while inserting your image. Please try again."
+msgstr ""
+
msgid "Something went wrong while merging this merge request. Please try again."
msgstr ""
@@ -21025,6 +22078,9 @@ msgstr ""
msgid "Something went wrong, unable to search projects"
msgstr ""
+msgid "Something went wrong."
+msgstr ""
+
msgid "Something went wrong. Please try again."
msgstr ""
@@ -21295,6 +22351,9 @@ msgstr ""
msgid "Stack trace"
msgstr ""
+msgid "Stacktrace snippet"
+msgstr ""
+
msgid "Stage"
msgstr ""
@@ -21400,6 +22459,9 @@ msgstr ""
msgid "Start thread & reopen %{noteable_name}"
msgstr ""
+msgid "Start using Directed Acyclic Graphs (DAG)"
+msgstr ""
+
msgid "Start your Free Gold Trial"
msgstr ""
@@ -21433,6 +22495,9 @@ msgstr ""
msgid "State your message to activate"
msgstr ""
+msgid "State: %{last_reindexing_task_state}"
+msgstr ""
+
msgid "Static Application Security Testing (SAST)"
msgstr ""
@@ -21874,9 +22939,6 @@ msgstr ""
msgid "Suggestions must all be on the same branch."
msgstr ""
-msgid "Suggestions that change line count can't be added to batches, yet."
-msgstr ""
-
msgid "Suggestions:"
msgstr ""
@@ -21925,6 +22987,9 @@ msgstr ""
msgid "System Hooks"
msgstr ""
+msgid "System Hooks Help"
+msgstr ""
+
msgid "System Info"
msgstr ""
@@ -22060,6 +23125,9 @@ msgstr ""
msgid "Target-Branch"
msgstr ""
+msgid "Task ID: %{elastic_task}"
+msgstr ""
+
msgid "Team"
msgstr ""
@@ -22099,6 +23167,34 @@ msgstr ""
msgid "Terms of Service and Privacy Policy"
msgstr ""
+msgid "Terraform|%{number} Terraform report failed to generate"
+msgid_plural "Terraform|%{number} Terraform reports failed to generate"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Terraform|%{number} Terraform report was generated in your pipelines"
+msgid_plural "Terraform|%{number} Terraform reports were generated in your pipelines"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Terraform|A Terraform report failed to generate."
+msgstr ""
+
+msgid "Terraform|A Terraform report was generated in your pipelines."
+msgstr ""
+
+msgid "Terraform|Generating the report caused an error."
+msgstr ""
+
+msgid "Terraform|Reported Resource Changes: %{addNum} to add, %{changeNum} to change, %{deleteNum} to delete"
+msgstr ""
+
+msgid "Terraform|The Terraform report %{name} failed to generate."
+msgstr ""
+
+msgid "Terraform|The Terraform report %{name} was generated in your pipelines."
+msgstr ""
+
msgid "Test"
msgstr ""
@@ -22146,13 +23242,13 @@ msgstr ""
msgid "TestReports|%{count} failures"
msgstr ""
-msgid "TestReports|%{count} jobs"
+msgid "TestReports|%{count} tests"
msgstr ""
msgid "TestReports|%{rate}%{sign} success rate"
msgstr ""
-msgid "TestReports|Test suites"
+msgid "TestReports|Jobs"
msgstr ""
msgid "TestReports|Tests"
@@ -22167,6 +23263,9 @@ msgstr ""
msgid "TestReports|There are no tests to show."
msgstr ""
+msgid "TestReports|There was an error fetching the summary."
+msgstr ""
+
msgid "TestReports|There was an error fetching the test reports."
msgstr ""
@@ -22208,6 +23307,9 @@ msgid_plural "The %{type} contains the following errors:"
msgstr[0] ""
msgstr[1] ""
+msgid "The .gitlab-ci.yml has been successfully created."
+msgstr ""
+
msgid "The Advanced Global Search in GitLab is a powerful search service that saves you time. Instead of creating duplicate code and wasting time, you can now search for code within other teams that can help your own project."
msgstr ""
@@ -22217,6 +23319,9 @@ msgstr ""
msgid "The Git LFS objects will <strong>not</strong> be synced."
msgstr ""
+msgid "The GitLab user to which the Jira user %{jiraDisplayName} will be mapped"
+msgstr ""
+
msgid "The Issue Tracker is the place to add things that need to be improved or solved in a project"
msgstr ""
@@ -22226,7 +23331,7 @@ msgstr ""
msgid "The Prometheus server responded with \"bad request\". Please check your queries are correct and are supported in your Prometheus version. %{documentationLink}"
msgstr ""
-msgid "The URL defined on the primary node that secondary nodes should use to contact it. Defaults to URL"
+msgid "The URL defined on the primary node that secondary nodes should use to contact it."
msgstr ""
msgid "The URL to use for connecting to Elasticsearch. Use a comma-separated list to support clustering (e.g., \"http://localhost:9200, http://localhost:9201\")."
@@ -22235,7 +23340,7 @@ msgstr ""
msgid "The X509 Certificate to use when mutual TLS is required to communicate with the external authorization service. If left blank, the server certificate is still validated when accessing over HTTPS."
msgstr ""
-msgid "The amount of seconds after which a request to get a secondary node status will time out."
+msgid "The above settings apply to all projects with the selected compliance framework(s)."
msgstr ""
msgid "The application will be used where the client secret can be kept confidential. Native mobile apps and Single Page Apps are considered non-confidential."
@@ -22534,9 +23639,6 @@ msgstr ""
msgid "The total stage shows the time it takes between creating an issue and deploying the code to production. The data will be automatically added once you have completed the full idea to production cycle."
msgstr ""
-msgid "The unique identifier for the Geo node. Must match %{geoNodeName} if it is set in gitlab.rb, otherwise it must match %{externalUrl} with a trailing slash"
-msgstr ""
-
msgid "The update action will time out after %{number_of_minutes} minutes. For big repositories, use a clone/push combination."
msgstr ""
@@ -22561,9 +23663,6 @@ msgstr ""
msgid "The user you are trying to deactivate has been active in the past %{minimum_inactive_days} days and cannot be deactivated"
msgstr ""
-msgid "The user-facing URL of the Geo node"
-msgstr ""
-
msgid "The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6."
msgstr ""
@@ -22651,6 +23750,9 @@ msgstr ""
msgid "There was a problem communicating with your device."
msgstr ""
+msgid "There was a problem fetching groups."
+msgstr ""
+
msgid "There was a problem fetching project branches."
msgstr ""
@@ -22708,6 +23810,9 @@ msgstr ""
msgid "There was an error fetching the %{replicableType}"
msgstr ""
+msgid "There was an error fetching the Geo Settings"
+msgstr ""
+
msgid "There was an error fetching the Node's Groups"
msgstr ""
@@ -22750,6 +23855,9 @@ msgstr ""
msgid "There was an error resetting user pipeline minutes."
msgstr ""
+msgid "There was an error retrieving the Jira users."
+msgstr ""
+
msgid "There was an error saving this Geo Node."
msgstr ""
@@ -22771,6 +23879,9 @@ msgstr ""
msgid "There was an error trying to validate your query"
msgstr ""
+msgid "There was an error updating the Geo Settings"
+msgstr ""
+
msgid "There was an error updating the dashboard, branch name is invalid."
msgstr ""
@@ -22789,6 +23900,9 @@ msgstr ""
msgid "There was an error when unsubscribing from this label."
msgstr ""
+msgid "There was an error while fetching configuration data."
+msgstr ""
+
msgid "There was an error while fetching value stream analytics data."
msgstr ""
@@ -22828,6 +23942,12 @@ msgstr ""
msgid "This %{issuable} is locked. Only <strong>project members</strong> can comment."
msgstr ""
+msgid "This %{noteableTypeText} is %{confidentialLinkStart}confidential%{linkEnd} and %{lockedLinkStart}locked%{linkEnd}."
+msgstr ""
+
+msgid "This %{noteableTypeText} is locked."
+msgstr ""
+
msgid "This %{viewer} could not be displayed because %{reason}. You can %{options} instead."
msgstr ""
@@ -22849,7 +23969,7 @@ msgstr ""
msgid "This also resolves all related threads"
msgstr ""
-msgid "This also resolves the discussion"
+msgid "This also resolves this thread"
msgstr ""
msgid "This application was created by %{link_to_owner}."
@@ -22894,6 +24014,9 @@ msgstr ""
msgid "This content could not be displayed because %{reason}. You can %{options} instead."
msgstr ""
+msgid "This credential has expired"
+msgstr ""
+
msgid "This date is after the due date, so this epic won't appear in the roadmap."
msgstr ""
@@ -22945,6 +24068,9 @@ msgstr ""
msgid "This feature requires local storage to be enabled"
msgstr ""
+msgid "This feature should be used with an index that was created after 13.0"
+msgstr ""
+
msgid "This field is required."
msgstr ""
@@ -22960,7 +24086,7 @@ msgstr ""
msgid "This group has been scheduled for permanent removal on %{date}"
msgstr ""
-msgid "This group, including all subgroups, projects and git repositories, will only be reachable from the specified IP address range. Multiple addresses are supported with comma delimiters.<br>Example: <code>192.168.0.0/24,192.168.1.0/24</code>. %{read_more_link}."
+msgid "This group, including all subgroups, projects and git repositories, will be reachable from only the specified IP address ranges."
msgstr ""
msgid "This group, its subgroups and projects has been scheduled for removal on %{date}."
@@ -22972,13 +24098,13 @@ msgstr ""
msgid "This is a \"Ghost User\", created to hold all issues authored by users that have since been deleted. This user cannot be removed."
msgstr ""
-msgid "This is a Work in Progress"
+msgid "This is a Premium feature"
msgstr ""
-msgid "This is a confidential epic."
+msgid "This is a Work in Progress"
msgstr ""
-msgid "This is a confidential issue."
+msgid "This is a confidential %{noteableTypeText}."
msgstr ""
msgid "This is a delayed job to run in %{remainingTime}"
@@ -23005,9 +24131,6 @@ msgstr ""
msgid "This is your current session"
msgstr ""
-msgid "This issue is %{confidentialLinkStart}confidential%{linkEnd} and %{lockedLinkStart}locked%{linkEnd}."
-msgstr ""
-
msgid "This issue is confidential"
msgstr ""
@@ -23017,9 +24140,6 @@ msgstr ""
msgid "This issue is in a child epic of the filtered epic"
msgstr ""
-msgid "This issue is locked."
-msgstr ""
-
msgid "This job depends on other jobs with expired/erased artifacts: %{invalid_dependencies}"
msgstr ""
@@ -23131,9 +24251,15 @@ msgstr ""
msgid "This merge request does not have accessibility reports"
msgstr ""
+msgid "This merge request is closed. To apply this suggestion, edit this file directly."
+msgstr ""
+
msgid "This merge request is locked."
msgstr ""
+msgid "This merge request was merged. To apply this suggestion, edit this file directly."
+msgstr ""
+
msgid "This namespace has already been taken! Please choose another one."
msgstr ""
@@ -23149,16 +24275,16 @@ msgstr ""
msgid "This page will be removed in a future release."
msgstr ""
-msgid "This pipeline makes use of a predefined CI/CD configuration enabled by %{strongStart}Auto DevOps.%{strongEnd}"
+msgid "This pipeline does not use the %{codeStart}needs%{codeEnd} keyword and can't be represented as a directed acyclic graph."
msgstr ""
-msgid "This pipeline makes use of a predefined CI/CD configuration enabled by <b>Auto DevOps.</b>"
+msgid "This pipeline makes use of a predefined CI/CD configuration enabled by %{strongStart}Auto DevOps.%{strongEnd}"
msgstr ""
-msgid "This pipeline triggered a child pipeline"
+msgid "This pipeline makes use of a predefined CI/CD configuration enabled by <b>Auto DevOps.</b>"
msgstr ""
-msgid "This pipeline was triggered by a parent pipeline"
+msgid "This pipeline was triggered by a schedule."
msgstr ""
msgid "This project"
@@ -23221,6 +24347,9 @@ msgstr ""
msgid "This subscription is for"
msgstr ""
+msgid "This suggestion already matches its content."
+msgstr ""
+
msgid "This timeout will take precedence when lower than project-defined timeout and accepts a human readable time input language like \"1 hour\". Values without specification represent seconds."
msgstr ""
@@ -23239,9 +24368,6 @@ msgstr ""
msgid "This variable can not be masked."
msgstr ""
-msgid "This variable does not match the expected pattern."
-msgstr ""
-
msgid "This will help us personalize your onboarding experience."
msgstr ""
@@ -23696,7 +24822,7 @@ msgstr ""
msgid "To start serving your jobs you can either add specific Runners to your project or use shared Runners"
msgstr ""
-msgid "To this GitLab instance"
+msgid "To view all %{scannedResourcesCount} scanned URLs, please download the CSV file"
msgstr ""
msgid "To view the roadmap, add a start or due date to one of your epics in this group or its subgroups. In the months view, only epics in the past month, current month, and next 5 months are shown."
@@ -23798,6 +24924,9 @@ msgstr ""
msgid "Total Contributions"
msgstr ""
+msgid "Total Score"
+msgstr ""
+
msgid "Total artifacts size: %{total_size}"
msgstr ""
@@ -23819,6 +24948,9 @@ msgstr ""
msgid "Total: %{total}"
msgstr ""
+msgid "TotalRefCountIndicator|1000+"
+msgstr ""
+
msgid "Trace"
msgstr ""
@@ -23837,6 +24969,9 @@ msgstr ""
msgid "Track your project with Audit Events."
msgstr ""
+msgid "Transfer"
+msgstr ""
+
msgid "Transfer ownership"
msgstr ""
@@ -23909,6 +25044,9 @@ msgstr ""
msgid "Trigger"
msgstr ""
+msgid "Trigger cluster reindexing"
+msgstr ""
+
msgid "Trigger pipelines for mirror updates"
msgstr ""
@@ -23918,6 +25056,9 @@ msgstr ""
msgid "Trigger removed."
msgstr ""
+msgid "Trigger repository check"
+msgstr ""
+
msgid "Trigger this manual action"
msgstr ""
@@ -24011,6 +25152,9 @@ msgstr ""
msgid "U2F only works with HTTPS-enabled websites. Contact your administrator for more details."
msgstr ""
+msgid "UI Development Kit"
+msgstr ""
+
msgid "URL"
msgstr ""
@@ -24119,6 +25263,9 @@ msgstr ""
msgid "Unassign from commenting user"
msgstr ""
+msgid "Unassigned"
+msgstr ""
+
msgid "Unblock"
msgstr ""
@@ -24194,6 +25341,9 @@ msgstr ""
msgid "Unreachable"
msgstr ""
+msgid "Unrecognized cluster type"
+msgstr ""
+
msgid "Unresolve"
msgstr ""
@@ -24254,6 +25404,9 @@ msgstr ""
msgid "Unsubscribes from this %{quick_action_target}."
msgstr ""
+msgid "Unsupported todo type passed. Supported todo types are: %{todo_types}"
+msgstr ""
+
msgid "Until"
msgstr ""
@@ -24281,6 +25434,9 @@ msgstr ""
msgid "Update approval rule"
msgstr ""
+msgid "Update approvers"
+msgstr ""
+
msgid "Update failed"
msgstr ""
@@ -24290,6 +25446,9 @@ msgstr ""
msgid "Update it"
msgstr ""
+msgid "Update iteration"
+msgstr ""
+
msgid "Update now"
msgstr ""
@@ -24338,12 +25497,18 @@ msgstr ""
msgid "Updated to %{linkStart}chart v%{linkEnd}"
msgstr ""
+msgid "Updates"
+msgstr ""
+
msgid "Updating"
msgstr ""
msgid "Upgrade plan to unlock Canary Deployments feature"
msgstr ""
+msgid "Upgrade your plan"
+msgstr ""
+
msgid "Upgrade your plan to activate Advanced Global Search."
msgstr ""
@@ -24353,6 +25518,9 @@ msgstr ""
msgid "Upgrade your plan to activate Group Webhooks."
msgstr ""
+msgid "Upgrade your plan to enable this feature of the Jira Integration."
+msgstr ""
+
msgid "Upgrade your plan to improve Issue boards."
msgstr ""
@@ -24365,6 +25533,9 @@ msgstr ""
msgid "Upload CSV file"
msgstr ""
+msgid "Upload License"
+msgstr ""
+
msgid "Upload New File"
msgstr ""
@@ -24446,16 +25617,19 @@ msgstr ""
msgid "UsageQuota|Pipelines"
msgstr ""
+msgid "UsageQuota|Purchase more storage"
+msgstr ""
+
msgid "UsageQuota|Repositories"
msgstr ""
msgid "UsageQuota|Repository"
msgstr ""
-msgid "UsageQuota|Storage"
+msgid "UsageQuota|Snippets"
msgstr ""
-msgid "UsageQuota|Storage usage:"
+msgid "UsageQuota|Storage"
msgstr ""
msgid "UsageQuota|This namespace has no projects which use shared runners"
@@ -24488,6 +25662,12 @@ msgstr ""
msgid "UsageQuota|Wikis"
msgstr ""
+msgid "UsageQuota|You used: %{usage} %{limit}"
+msgstr ""
+
+msgid "UsageQuota|out of %{formattedLimit} of your namespace storage"
+msgstr ""
+
msgid "Use %{code_start}::%{code_end} to create a %{link_start}scoped label set%{link_end} (eg. %{code_start}priority::1%{code_end})"
msgstr ""
@@ -24554,6 +25734,9 @@ msgstr ""
msgid "User IDs"
msgstr ""
+msgid "User List"
+msgstr ""
+
msgid "User Lists can only be created and modified with %{linkStart}the API%{linkEnd}"
msgstr ""
@@ -24581,6 +25764,9 @@ msgstr ""
msgid "User key was successfully removed."
msgstr ""
+msgid "User list %{name} will be removed. Are you sure?"
+msgstr ""
+
msgid "User map"
msgstr ""
@@ -24590,6 +25776,9 @@ msgstr ""
msgid "User restrictions"
msgstr ""
+msgid "User settings"
+msgstr ""
+
msgid "User was successfully created."
msgstr ""
@@ -24602,148 +25791,37 @@ msgstr ""
msgid "User was successfully updated."
msgstr ""
-msgid "UserOnboardingTour|%{activeTour}/%{totalTours}"
-msgstr ""
-
-msgid "UserOnboardingTour|%{completed}/%{total} steps completed"
-msgstr ""
-
-msgid "UserOnboardingTour|%{emphasisStart}Well done!%{emphasisEnd}%{lineBreak}%{lineBreak}That's it for our guided tour, congratulations for making it all the way to the end!%{lineBreak}%{lineBreak}We hope this gave you a good overview of GitLab and how it can help you. We'll now show you how to create your own project and invite your colleagues."
-msgstr ""
-
-msgid "UserOnboardingTour|Adding other members to a project is done through Project Settings. Click on %{emphasisStart}Settings%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Alright, that's it for Commits. Let's take a look at the %{emphasisStart}Branches%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Awesome! Now click on %{emphasisStart}Members%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Click on one of the %{emphasisStart}Compare%{emphasisEnd} buttons to compare a branch to master."
-msgstr ""
-
-msgid "UserOnboardingTour|Click on one of the %{emphasisStart}pipeline IDs%{emphasisEnd} to see the details of a pipeline."
-msgstr ""
-
-msgid "UserOnboardingTour|Click to open the latest commit to see its details."
-msgstr ""
-
-msgid "UserOnboardingTour|Close 'Learn GitLab'"
-msgstr ""
-
-msgid "UserOnboardingTour|Commits are shown in chronological order and can be filtered by the commit message or by the branch."
-msgstr ""
-
-msgid "UserOnboardingTour|Create a project"
-msgstr ""
-
-msgid "UserOnboardingTour|Exit 'Learn GitLab'"
-msgstr ""
-
-msgid "UserOnboardingTour|Got it"
-msgstr ""
-
-msgid "UserOnboardingTour|Great job! %{clapHands} We hope the tour was helpful and that you learned how to use GitLab.%{lineBreak}%{lineBreak}We'd love to get your feedback on this tour.%{lineBreak}%{lineBreak}%{emphasisStart}How helpful would you say this guided tour was?%{emphasisEnd}%{lineBreak}%{lineBreak}"
-msgstr ""
-
-msgid "UserOnboardingTour|Guided GitLab Tour"
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can compare the changes of this branch to another one. Changes are divided by files so that it's easier to see what was changed where."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can create a project from scratch, start with a template or import a repository from other platforms. Whatever you choose, we'll guide you through the process.%{lineBreak}%{lineBreak}Fill in your new project information and click on %{emphasisStart}Create Project%{emphasisEnd} to progress to the next step."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can see the breakdown of the pipelines: its stages and jobs in each of the stages and their status.%{lineBreak}%{lineBreak}Our CI/CD pipelines are quite complex, most of our users have fewer and simpler pipelines."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can see the current members of the project (just you at the moment) and invite new members.%{lineBreak}%{lineBreak}You can invite multiple members at once (existing GitLab users or invite by email) and you can also set their roles and permissions.%{lineBreak}%{lineBreak}Add a few members and click on %{emphasisStart}Add to project%{emphasisEnd} to complete this step."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can see what changes were made with this commit, on what branch and if there's a related merge request. The status of the pipeline will also show up if CI/CD is set up.%{lineBreak}%{lineBreak}You can also comment on the lines of code that were changed and start a discussion with your colleagues!"
-msgstr ""
-
-msgid "UserOnboardingTour|Here's an overview of branches in the %{emphasisStart}%{projectName}%{emphasisEnd} project. They're split into Active and Stale.%{lineBreak}%{lineBreak}From here, you can create a new merge request from a branch, or compare the branch to any other branch in the project. By default, it will compare it to the master branch."
-msgstr ""
-
-msgid "UserOnboardingTour|Invite colleagues"
-msgstr ""
-
-msgid "UserOnboardingTour|Issues are great for communicating and keeping track of progress in GitLab. These are all issues that are open in the %{emphasisStart}%{projectName}%{emphasisEnd}.%{lineBreak}%{lineBreak}You can help us improve GitLab by contributing work to issues that are labeled <span class=\"badge color-label accept-mr-label\">Accepting merge requests</span>.%{lineBreak}%{lineBreak}This list can be filtered by labels, milestones, assignees, authors... We'll show you how it looks when the list is filtered by a label."
-msgstr ""
-
-msgid "UserOnboardingTour|Learn GitLab"
-msgstr ""
-
-msgid "UserOnboardingTour|Let's take a closer look at a merge request. Click on the title of one."
-msgstr ""
-
-msgid "UserOnboardingTour|Let's take a closer look at all the commits. Click on %{emphasisStart}Commits%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Let's take a closer look at the repository of this project. Click on %{emphasisStart}Repository%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|No thanks"
-msgstr ""
-
-msgid "UserOnboardingTour|Ok, let's go"
-msgstr ""
-
-msgid "UserOnboardingTour|Ok, show me"
-msgstr ""
-
-msgid "UserOnboardingTour|Open one of the issues by clicking on its title."
-msgstr ""
-
-msgid "UserOnboardingTour|Restart this step"
-msgstr ""
-
-msgid "UserOnboardingTour|Skip this step"
+msgid "UserLists|Add"
msgstr ""
-msgid "UserOnboardingTour|Sweet! Your project was created and is ready to be used.%{lineBreak}%{lineBreak}You can start adding files to the repository or clone it. One last thing we want to show you is how to invite your colleagues to your new project."
+msgid "UserLists|Add Users"
msgstr ""
-msgid "UserOnboardingTour|Take a look. Here's a nifty menu for quickly creating issues, merge requests, snippets, projects and groups. Click on it and select \"New project\" from the \"GitLab\" section to get started."
+msgid "UserLists|Add users"
msgstr ""
-msgid "UserOnboardingTour|Thanks for taking the guided tour. Remember, if you want to go through it again, you can start %{emphasisStart}Learn GitLab%{emphasisEnd} in the help menu on the top right."
+msgid "UserLists|Cancel"
msgstr ""
-msgid "UserOnboardingTour|Thanks for the feedback! %{thumbsUp}"
+msgid "UserLists|Define a set of users to be used within feature flag strategies"
msgstr ""
-msgid "UserOnboardingTour|That's it for issues. Let'st take a look at %{emphasisStart}Merge Requests%{emphasisEnd}."
+msgid "UserLists|Enter a comma separated list of user IDs. These IDs should be the users of the system in which the feature flag is set, not GitLab IDs"
msgstr ""
-msgid "UserOnboardingTour|That's it for merge requests. Now for the final part of this guided tour - the %{emphasisStart}CI/CD%{emphasisEnd}."
+msgid "UserLists|There are no users"
msgstr ""
-msgid "UserOnboardingTour|That's it for the Repository. Let's take a look at the %{emphasisStart}Issues%{emphasisEnd}."
+msgid "UserLists|User ID"
msgstr ""
-msgid "UserOnboardingTour|The structure of this page is very similar to issues. Status, description, discussion and the sidebar are all here.%{lineBreak}%{lineBreak}But take a look below the description and you'll notice that there's more information about the merge request, the CI/CD pipeline and the options for approving it.%{lineBreak}%{lineBreak}Alongside the discussion you can also see more information about commits in this merge request, the status of pipelines and review all changes that were made."
+msgid "UserLists|User IDs"
msgstr ""
-msgid "UserOnboardingTour|There's a lot of information here but don't worry, we'll go through it.%{lineBreak}%{lineBreak}On the top you can see the status of the issue and when it was opened and by whom. Directly below it is the issue description and below that are other %{emphasisStart}related issues%{emphasisEnd} and %{emphasisStart}merge requests%{emphasisEnd} (if any). Then below that is the %{emphasisStart}discussion%{emphasisEnd}, that's where most of the communication happens.%{lineBreak}%{lineBreak}On the right, there's a sidebar where you can view/change the %{emphasisStart}assignee, milestone, due date, labels, weight%{emphasisEnd}, etc."
+msgid "UserList|Delete %{name}?"
msgstr ""
-msgid "UserOnboardingTour|These are all the CI/CD pipelines we have for our %{emphasisStart}%{projectName}%{emphasisEnd} project.%{lineBreak}%{lineBreak}Here you can see the status of each pipeline, for what commit it's running for, its stages and the status for them."
-msgstr ""
-
-msgid "UserOnboardingTour|These are all the issues that are available for community contributions. Let's take a closer look at one of them."
-msgstr ""
-
-msgid "UserOnboardingTour|This is an overview of all merge requests in this project. Similarly to the issues overview it can be filtered down by things like labels, milestones, authors, assignees, etc."
-msgstr ""
-
-msgid "UserOnboardingTour|This is the repository for the %{emphasisStart}%{projectName}%{emphasisEnd} project. All our code is stored here. Feel free to explore and take a closer look at folders and files.%{lineBreak}%{lineBreak}Above the file structure you can see the latest commit, who the author is and the status of the CI/CD pipeline.%{lineBreak}%{lineBreak}If you scroll down below the file structure, you'll find the Readme of this project. This is defined in the README.md file at the root of the repository."
-msgstr ""
-
-msgid "UserOnboardingTour|Welcome to the project overview of the %{emphasisStart}%{projectName}%{emphasisEnd} project. This is the project that we use to work on GitLab. At first, a project seems like a simple repository, but at GitLab, a project is so much more.%{lineBreak}%{lineBreak}You can create projects for hosting your codebase, use it as an issue tracker, collaborate on code, and continuously build, test, and deploy your app with built-in GitLab CI/CD."
+msgid "UserList|created %{timeago}"
msgstr ""
msgid "UserProfile|Activity"
@@ -24896,6 +25974,9 @@ msgstr ""
msgid "UsersSelect|Unassigned"
msgstr ""
+msgid "Using %{codeStart}needs%{codeEnd} allows jobs to run before their stage is reached, as soon as their individual dependencies are met, which speeds up your pipelines."
+msgstr ""
+
msgid "Using %{code_start}::%{code_end} denotes a %{link_start}scoped label set%{link_end}"
msgstr ""
@@ -24932,6 +26013,9 @@ msgstr ""
msgid "Value Stream Analytics gives an overview of how much time it takes to go from idea to production in your project."
msgstr ""
+msgid "Value Stream Name"
+msgstr ""
+
msgid "ValueStreamAnalytics|%{days}d"
msgstr ""
@@ -24980,9 +26064,6 @@ msgstr ""
msgid "Versions"
msgstr ""
-msgid "Very helpful"
-msgstr ""
-
msgid "View Documentation"
msgstr ""
@@ -25068,6 +26149,9 @@ msgstr ""
msgid "View open merge request"
msgstr ""
+msgid "View page @ "
+msgstr ""
+
msgid "View performance dashboard."
msgstr ""
@@ -25131,6 +26215,9 @@ msgstr ""
msgid "VisibilityLevel|Unknown"
msgstr ""
+msgid "Visit settings page"
+msgstr ""
+
msgid "VisualReviewApp|%{stepStart}Step 1%{stepEnd}. Copy the following script:"
msgstr ""
@@ -25224,6 +26311,9 @@ msgstr ""
msgid "VulnerabilityManagement|Something went wrong while trying to delete the comment. Please try again later."
msgstr ""
+msgid "VulnerabilityManagement|Something went wrong while trying to refresh the vulnerability. Please try again later."
+msgstr ""
+
msgid "VulnerabilityManagement|Something went wrong while trying to retrieve the vulnerability history. Please try again later."
msgstr ""
@@ -25266,6 +26356,12 @@ msgstr ""
msgid "Vulnerability|Class"
msgstr ""
+msgid "Vulnerability|Comments"
+msgstr ""
+
+msgid "Vulnerability|Crash Address"
+msgstr ""
+
msgid "Vulnerability|Description"
msgstr ""
@@ -25275,6 +26371,9 @@ msgstr ""
msgid "Vulnerability|File"
msgstr ""
+msgid "Vulnerability|Identifier"
+msgstr ""
+
msgid "Vulnerability|Identifiers"
msgstr ""
@@ -25296,10 +26395,16 @@ msgstr ""
msgid "Vulnerability|Project"
msgstr ""
-msgid "Vulnerability|Scanner Provider"
+msgid "Vulnerability|Request"
msgstr ""
-msgid "Vulnerability|Scanner Type"
+msgid "Vulnerability|Response"
+msgstr ""
+
+msgid "Vulnerability|Scanner"
+msgstr ""
+
+msgid "Vulnerability|Scanner Provider"
msgstr ""
msgid "Vulnerability|Severity"
@@ -25341,9 +26446,6 @@ msgstr ""
msgid "We couldn't reach the Prometheus server. Either the server no longer exists or the configuration details need updating."
msgstr ""
-msgid "We created a short guided tour that will help you learn the basics of GitLab and how it will help you be better at your job. It should only take a couple of minutes. You will be guided by two types of helpers, best recognized by their color."
-msgstr ""
-
msgid "We detected potential spam in the %{humanized_resource_name}. Please solve the reCAPTCHA to proceed."
msgstr ""
@@ -25404,6 +26506,9 @@ msgstr ""
msgid "Webhooks"
msgstr ""
+msgid "Webhooks Help"
+msgstr ""
+
msgid "Webhooks allow you to trigger a URL if, for example, new code is pushed or a new issue is created. You can configure webhooks to listen for specific events like pushes, issues or merge requests. Group webhooks will apply to all projects in a group, allowing you to standardize webhook functionality across your entire group."
msgstr ""
@@ -25440,9 +26545,6 @@ msgstr ""
msgid "Welcome to GitLab.com<br>@%{name}!"
msgstr ""
-msgid "Welcome to the Guided GitLab Tour"
-msgstr ""
-
msgid "Welcome to the guided GitLab tour"
msgstr ""
@@ -25490,9 +26592,6 @@ msgstr ""
msgid "While it's rare to have no vulnerabilities, it can happen. In any event, we ask that you please double check your settings to make sure you've set up your dashboard correctly."
msgstr ""
-msgid "White helpers give contextual information."
-msgstr ""
-
msgid "Who can be an approver?"
msgstr ""
@@ -25547,15 +26646,27 @@ msgstr ""
msgid "WikiEmptyIssueMessage|issue tracker"
msgstr ""
+msgid "WikiEmpty| Have a Confluence wiki already? Use that instead."
+msgstr ""
+
msgid "WikiEmpty|A wiki is where you can store all the details about your group. This can include why you've created it, its principles, how to use it, and so on."
msgstr ""
msgid "WikiEmpty|A wiki is where you can store all the details about your project. This can include why you've created it, its principles, how to use it, and so on."
msgstr ""
+msgid "WikiEmpty|Confluence is enabled"
+msgstr ""
+
msgid "WikiEmpty|Create your first page"
msgstr ""
+msgid "WikiEmpty|Enable the Confluence Wiki integration"
+msgstr ""
+
+msgid "WikiEmpty|Go to Confluence"
+msgstr ""
+
msgid "WikiEmpty|Suggest wiki improvement"
msgstr ""
@@ -25577,6 +26688,9 @@ msgstr ""
msgid "WikiEmpty|You must be a project member in order to add wiki pages."
msgstr ""
+msgid "WikiEmpty|You've enabled the Confluence Workspace integration. Your wiki will be viewable directly within Confluence. We are hard at work integrating Confluence more seamlessly into GitLab. If you'd like to stay up to date, follow our %{wiki_confluence_epic_link_start}Confluence epic%{wiki_confluence_epic_link_end}."
+msgstr ""
+
msgid "WikiHistoricalPage|This is an old version of this page."
msgstr ""
@@ -25664,6 +26778,9 @@ msgstr ""
msgid "Will be created"
msgstr ""
+msgid "Will be mapped to"
+msgstr ""
+
msgid "Will deploy to"
msgstr ""
@@ -25682,6 +26799,9 @@ msgstr ""
msgid "Work in progress Limit"
msgstr ""
+msgid "Workflow Help"
+msgstr ""
+
msgid "Write"
msgstr ""
@@ -25724,6 +26844,9 @@ msgstr ""
msgid "You"
msgstr ""
+msgid "You already have pending todo for this alert"
+msgstr ""
+
msgid "You are about to delete %{domain} from your instance. This domain will no longer be available to any Knative application."
msgstr ""
@@ -25775,6 +26898,9 @@ msgstr ""
msgid "You are trying to upload something other than an image. Please upload a .png, .jpg, .jpeg, .gif, .bmp, .tiff or .ico."
msgstr ""
+msgid "You are using PostgreSQL %{pg_version_current}, but PostgreSQL %{pg_version_minimum} is required for this version of GitLab. Please upgrade your environment to a supported PostgreSQL version, see %{pg_requirements_url} for details."
+msgstr ""
+
msgid "You can %{linkStart}view the blob%{linkEnd} instead."
msgstr ""
@@ -25850,13 +26976,13 @@ msgstr ""
msgid "You can now export your security dashboard to a CSV report."
msgstr ""
-msgid "You can now submit a merge request to get this change into the original branch."
+msgid "You can now manage alert endpoint configuration in the Alerts section on the Operations settings page. Fields on this page have been deprecated."
msgstr ""
-msgid "You can now submit a merge request to get this change into the original project."
+msgid "You can now submit a merge request to get this change into the original branch."
msgstr ""
-msgid "You can only add files when you are on a branch"
+msgid "You can now submit a merge request to get this change into the original project."
msgstr ""
msgid "You can only edit files when you are on a branch"
@@ -25970,6 +27096,9 @@ msgstr ""
msgid "You don't have sufficient permission to perform this action."
msgstr ""
+msgid "You don't have write access to the source branch."
+msgstr ""
+
msgid "You don’t have access to Productivity Analytics in this group"
msgstr ""
@@ -25997,6 +27126,9 @@ msgstr ""
msgid "You have imported from this project %{numberOfPreviousImports} times before. Each new import will create duplicate issues."
msgstr ""
+msgid "You have insufficient permissions to create a Todo for this alert"
+msgstr ""
+
msgid "You have no permissions"
msgstr ""
@@ -26033,6 +27165,9 @@ msgstr ""
msgid "You must have maintainer access to force delete a lock"
msgstr ""
+msgid "You must have permission to create a project in a group before forking."
+msgstr ""
+
msgid "You must have permission to create a project in a namespace before forking."
msgstr ""
@@ -26410,6 +27545,9 @@ msgid_plural "about %d hours"
msgstr[0] ""
msgstr[1] ""
+msgid "access:"
+msgstr ""
+
msgid "activated"
msgstr ""
@@ -26446,9 +27584,15 @@ msgstr ""
msgid "any-approver for the project already exists"
msgstr ""
+msgid "approved by: "
+msgstr ""
+
msgid "archived"
msgstr ""
+msgid "archived:"
+msgstr ""
+
msgid "assign yourself"
msgstr ""
@@ -26467,9 +27611,6 @@ msgstr ""
msgid "branch name"
msgstr ""
-msgid "build pipeline reference mismatch"
-msgstr ""
-
msgid "by"
msgstr ""
@@ -26500,6 +27641,15 @@ msgstr ""
msgid "cannot merge"
msgstr ""
+msgid "child-pipeline"
+msgstr ""
+
+msgid "ciReport|%{degradedNum} degraded"
+msgstr ""
+
+msgid "ciReport|%{improvedNum} improved"
+msgstr ""
+
msgid "ciReport|%{linkStartTag}Learn more about Container Scanning %{linkEndTag}"
msgstr ""
@@ -26521,61 +27671,15 @@ msgstr ""
msgid "ciReport|%{remainingPackagesCount} more"
msgstr ""
-msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability"
-msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability for the source branch only"
-msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities for the source branch only"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerability"
-msgid_plural "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerabilities"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new vulnerability"
-msgid_plural "ciReport|%{reportType} %{status} detected %{newCount} new vulnerabilities"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities for the source branch only"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{fixedCount} fixed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} vulnerability for the source branch only"
-msgid_plural "ciReport|%{reportType} %{status} detected %{newCount} vulnerabilities for the source branch only"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "ciReport|%{reportType} %{status} detected no new vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected no vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected no vulnerabilities for the source branch only"
-msgstr ""
-
msgid "ciReport|%{reportType} is loading"
msgstr ""
msgid "ciReport|%{reportType}: Loading resulted in an error"
msgstr ""
+msgid "ciReport|%{sameNum} same"
+msgstr ""
+
msgid "ciReport|(errors when loading results)"
msgstr ""
@@ -26588,7 +27692,7 @@ msgstr ""
msgid "ciReport|All projects"
msgstr ""
-msgid "ciReport|All report types"
+msgid "ciReport|All scanners"
msgstr ""
msgid "ciReport|All severities"
@@ -26600,6 +27704,15 @@ msgstr ""
msgid "ciReport|Base pipeline codequality artifact not found"
msgstr ""
+msgid "ciReport|Browser performance test metrics: "
+msgstr ""
+
+msgid "ciReport|Browser performance test metrics: No changes"
+msgstr ""
+
+msgid "ciReport|Checks"
+msgstr ""
+
msgid "ciReport|Code quality"
msgstr ""
@@ -26612,6 +27725,9 @@ msgstr ""
msgid "ciReport|Container scanning detects known vulnerabilities in your docker images."
msgstr ""
+msgid "ciReport|Coverage Fuzzing"
+msgstr ""
+
msgid "ciReport|Create a merge request to implement this solution, or download and apply the patch manually."
msgstr ""
@@ -26657,6 +27773,12 @@ msgstr ""
msgid "ciReport|Learn more about interacting with security reports"
msgstr ""
+msgid "ciReport|Load performance test metrics: "
+msgstr ""
+
+msgid "ciReport|Load performance test metrics: No changes"
+msgstr ""
+
msgid "ciReport|Loading %{reportName} report"
msgstr ""
@@ -26669,13 +27791,10 @@ msgstr ""
msgid "ciReport|No changes to code quality"
msgstr ""
-msgid "ciReport|No changes to performance metrics"
-msgstr ""
-
msgid "ciReport|No code quality issues found"
msgstr ""
-msgid "ciReport|Performance metrics"
+msgid "ciReport|RPS"
msgstr ""
msgid "ciReport|Resolve with merge request"
@@ -26705,6 +27824,12 @@ msgstr ""
msgid "ciReport|Static Application Security Testing (SAST) detects known vulnerabilities in your source code."
msgstr ""
+msgid "ciReport|TTFB P90"
+msgstr ""
+
+msgid "ciReport|TTFB P95"
+msgstr ""
+
msgid "ciReport|There was an error creating the issue. Please try again."
msgstr ""
@@ -26767,9 +27892,6 @@ msgstr ""
msgid "created %{timeAgo}"
msgstr ""
-msgid "created %{timeago}"
-msgstr ""
-
msgid "customize"
msgstr ""
@@ -26805,6 +27927,9 @@ msgstr ""
msgid "disabled"
msgstr ""
+msgid "does not exist"
+msgstr ""
+
msgid "does not have a supported extension. Only %{extension_list} are supported"
msgstr ""
@@ -26846,6 +27971,9 @@ msgstr ""
msgid "entries cannot contain HTML tags"
msgstr ""
+msgid "epic"
+msgstr ""
+
msgid "error"
msgstr ""
@@ -26867,9 +27995,6 @@ msgstr ""
msgid "expires on %{timebox_due_date}"
msgstr ""
-msgid "external_url"
-msgstr ""
-
msgid "failed"
msgstr ""
@@ -26905,10 +28030,10 @@ msgstr ""
msgid "from"
msgstr ""
-msgid "geo_node_name"
+msgid "group"
msgstr ""
-msgid "group"
+msgid "group members"
msgstr ""
msgid "groups"
@@ -26926,10 +28051,10 @@ msgstr ""
msgid "here"
msgstr ""
-msgid "https://your-bitbucket-server"
+msgid "http:"
msgstr ""
-msgid "image"
+msgid "https://your-bitbucket-server"
msgstr ""
msgid "image diff"
@@ -27045,10 +28170,7 @@ msgstr ""
msgid "jigsaw is not defined"
msgstr ""
-msgid "jira.issue.description.content"
-msgstr ""
-
-msgid "jira.issue.summary"
+msgid "last commit:"
msgstr ""
msgid "latest"
@@ -27075,6 +28197,12 @@ msgstr ""
msgid "load it anyway"
msgstr ""
+msgid "loading"
+msgstr ""
+
+msgid "locked"
+msgstr ""
+
msgid "locked by %{path_lock_user_name} %{created_at}"
msgstr ""
@@ -27095,7 +28223,7 @@ msgid_plural "merge requests"
msgstr[0] ""
msgstr[1] ""
-msgid "merged %{time_ago}"
+msgid "merged %{timeAgo}"
msgstr ""
msgid "missing"
@@ -27176,7 +28304,7 @@ msgstr ""
msgid "mrWidget|Check out branch"
msgstr ""
-msgid "mrWidget|Checking ability to merge automatically…"
+msgid "mrWidget|Checking if merge request can be merged…"
msgstr ""
msgid "mrWidget|Cherry-pick"
@@ -27203,9 +28331,6 @@ msgstr ""
msgid "mrWidget|Deployment statistics are not available currently"
msgstr ""
-msgid "mrWidget|Detect issues before deployment with a CI pipeline that continuously tests your code. We created a quick guide that will show you how to create one. Make your code more secure and more robust in just a minute."
-msgstr ""
-
msgid "mrWidget|Did not close"
msgstr ""
@@ -27368,6 +28493,9 @@ msgstr ""
msgid "mrWidget|To approve this merge request, please enter your password. This project requires all approvals to be authenticated."
msgstr ""
+msgid "mrWidget|Use %{linkStart}CI pipelines to test your code%{linkEnd} by simply adding a GitLab CI configuration file to your project. It only takes a minute to make your code more secure and robust."
+msgstr ""
+
msgid "mrWidget|When this merge request is ready, remove the WIP: prefix from the title to allow it to be merged"
msgstr ""
@@ -27380,6 +28508,9 @@ msgstr ""
msgid "mrWidget|You can merge this merge request manually using the"
msgstr ""
+msgid "mrWidget|You can only merge once the denied license is removed"
+msgstr ""
+
msgid "mrWidget|Your password"
msgstr ""
@@ -27404,6 +28535,9 @@ msgstr ""
msgid "must be greater than start date"
msgstr ""
+msgid "must contain only valid frameworks"
+msgstr ""
+
msgid "my-awesome-group"
msgstr ""
@@ -27416,12 +28550,18 @@ msgstr ""
msgid "needs to be between 10 minutes and 1 month"
msgstr ""
+msgid "never"
+msgstr ""
+
msgid "never expires"
msgstr ""
msgid "new merge request"
msgstr ""
+msgid "no approvers"
+msgstr ""
+
msgid "no contributions"
msgstr ""
@@ -27443,6 +28583,9 @@ msgstr ""
msgid "nounSeries|%{firstItem} and %{lastItem}"
msgstr ""
+msgid "nounSeries|%{item}"
+msgstr ""
+
msgid "nounSeries|%{item}, %{nextItem}"
msgstr ""
@@ -27458,6 +28601,9 @@ msgstr ""
msgid "opened %{timeAgoString} by %{user}"
msgstr ""
+msgid "opened %{timeAgoString} by %{user} in Jira"
+msgstr ""
+
msgid "opened %{timeAgo}"
msgstr ""
@@ -27477,6 +28623,9 @@ msgstr[1] ""
msgid "password"
msgstr ""
+msgid "paused"
+msgstr ""
+
msgid "pending comment"
msgstr ""
@@ -27529,6 +28678,15 @@ msgstr ""
msgid "project avatar"
msgstr ""
+msgid "project bots cannot be added to other groups / projects"
+msgstr ""
+
+msgid "project is read-only"
+msgstr ""
+
+msgid "project members"
+msgstr ""
+
msgid "projects"
msgstr ""
@@ -27570,10 +28728,10 @@ msgid_plural "replies"
msgstr[0] ""
msgstr[1] ""
-msgid "reset it."
+msgid "repository:"
msgstr ""
-msgid "resolved the corresponding error and closed the issue."
+msgid "reset it."
msgstr ""
msgid "revised"
@@ -27612,6 +28770,9 @@ msgstr ""
msgid "severity|Unknown"
msgstr ""
+msgid "should be an array of %{object_name} objects"
+msgstr ""
+
msgid "should be greater than or equal to %{access} inherited membership from group %{group_name}"
msgstr ""
@@ -27636,12 +28797,18 @@ msgstr ""
msgid "source diff"
msgstr ""
+msgid "specific"
+msgstr ""
+
msgid "specified top is not part of the tree"
msgstr ""
msgid "spendCommand|%{slash_command} will update the sum of the time spent."
msgstr ""
+msgid "ssh:"
+msgstr ""
+
msgid "started"
msgstr ""
@@ -27666,10 +28833,13 @@ msgstr ""
msgid "suggestPipeline|2/2: Commit your changes"
msgstr ""
-msgid "suggestPipeline|Commit the changes and your pipeline will automatically run for the first time."
+msgid "suggestPipeline|Choose %{boldStart}Code Quality%{boldEnd} to add a pipeline that tests the quality of your code."
+msgstr ""
+
+msgid "suggestPipeline|The template is ready! You can now commit it to create your first pipeline."
msgstr ""
-msgid "suggestPipeline|We recommend the %{boldStart}Code Quality%{boldEnd} template, which will add a report widget to your Merge Requests. This way you’ll learn about code quality degradations much sooner. %{footerStart} Goodbye technical debt! %{footerEnd}"
+msgid "suggestPipeline|We’re adding a GitLab CI configuration file to add a pipeline to the project. You could create it manually, but we recommend that you start with a GitLab template that works out of the box."
msgstr ""
msgid "syntax is correct"
@@ -27777,6 +28947,9 @@ msgstr ""
msgid "vulnerability|dismissed"
msgstr ""
+msgid "was scheduled to merge after pipeline succeeds by"
+msgstr ""
+
msgid "wiki page"
msgstr ""
diff --git a/locale/gl_ES/gitlab.po b/locale/gl_ES/gitlab.po
index 842686f7eb1..ae1eee49df8 100644
--- a/locale/gl_ES/gitlab.po
+++ b/locale/gl_ES/gitlab.po
@@ -2,7 +2,7 @@ msgid ""
msgstr ""
"Project-Id-Version: gitlab-ee\n"
"Report-Msgid-Bugs-To: \n"
-"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Last-Translator: \n"
"Language-Team: Galician\n"
"Language: gl_ES\n"
"MIME-Version: 1.0\n"
@@ -10,9 +10,11 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Crowdin-Project: gitlab-ee\n"
+"X-Crowdin-Project-ID: 288872\n"
"X-Crowdin-Language: gl\n"
"X-Crowdin-File: /master/locale/gitlab.pot\n"
-"PO-Revision-Date: 2020-06-08 15:09\n"
+"X-Crowdin-File-ID: 6\n"
+"PO-Revision-Date: 2020-07-02 01:19\n"
msgid " %{start} to %{end}"
msgstr ""
@@ -197,6 +199,11 @@ msgid_plural "%d metrics"
msgstr[0] ""
msgstr[1] ""
+msgid "%d milestone"
+msgid_plural "%d milestones"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "%d minute"
msgid_plural "%d minutes"
msgstr[0] ""
@@ -242,11 +249,6 @@ msgid_plural "%d unresolved threads"
msgstr[0] ""
msgstr[1] ""
-msgid "%d url scanned"
-msgid_plural "%d urls scanned"
-msgstr[0] ""
-msgstr[1] ""
-
msgid "%d vulnerability"
msgid_plural "%d vulnerabilities"
msgstr[0] ""
@@ -324,9 +326,15 @@ msgstr[1] ""
msgid "%{count} related %{pluralized_subject}: %{links}"
msgstr ""
+msgid "%{dashboard_path} could not be found."
+msgstr ""
+
msgid "%{days} days until tags are automatically removed"
msgstr ""
+msgid "%{deployLinkStart}Use a template to deploy to ECS%{deployLinkEnd}, or use a docker image to %{commandsLinkStart}run AWS commands in GitLab CI/CD%{commandsLinkEnd}."
+msgstr ""
+
msgid "%{description}- Sentry event: %{errorUrl}- First seen: %{firstSeen}- Last seen: %{lastSeen} %{countLabel}: %{count}%{userCountLabel}: %{userCount}"
msgstr ""
@@ -369,12 +377,18 @@ msgstr ""
msgid "%{host} sign-in from new location"
msgstr ""
-msgid "%{icon}You are about to add %{usersTag} people to the discussion. Proceed with caution."
+msgid "%{icon}You are about to add %{usersTag} people to the discussion. They will all receive a notification."
+msgstr ""
+
+msgid "%{integrations_link_start}Integrations%{link_end} enable you to make third-party applications part of your GitLab workflow. If the available integrations don't meet your needs, consider using a %{webhooks_link_start}webhook%{link_end}."
msgstr ""
msgid "%{issuableType} will be removed! Are you sure?"
msgstr ""
+msgid "%{issuesCount} issues in this group"
+msgstr ""
+
msgid "%{issuesSize} issues"
msgstr ""
@@ -384,7 +398,7 @@ msgstr ""
msgid "%{labelStart}Class:%{labelEnd} %{class}"
msgstr ""
-msgid "%{labelStart}Confidence:%{labelEnd} %{confidence}"
+msgid "%{labelStart}Evidence:%{labelEnd} %{evidence}"
msgstr ""
msgid "%{labelStart}File:%{labelEnd} %{file}"
@@ -402,6 +416,9 @@ msgstr ""
msgid "%{labelStart}Report Type:%{labelEnd} %{reportType}"
msgstr ""
+msgid "%{labelStart}Scanner:%{labelEnd} %{scanner}"
+msgstr ""
+
msgid "%{labelStart}Severity:%{labelEnd} %{severity}"
msgstr ""
@@ -426,9 +443,6 @@ msgstr ""
msgid "%{link_start}Read more%{link_end} about role permissions"
msgstr ""
-msgid "%{link} can be used for binding events when something is happening within the project."
-msgstr ""
-
msgid "%{listToShow}, and %{awardsListLength} more."
msgstr ""
@@ -462,6 +476,9 @@ msgstr ""
msgid "%{name} found %{resultsString}"
msgstr ""
+msgid "%{name} is already being used for another emoji"
+msgstr ""
+
msgid "%{name} is scheduled for %{action}"
msgstr ""
@@ -509,6 +526,9 @@ msgstr ""
msgid "%{primary} (%{secondary})"
msgstr ""
+msgid "%{ref} cannot be added: %{error}"
+msgstr ""
+
msgid "%{releases} release"
msgid_plural "%{releases} releases"
msgstr[0] ""
@@ -517,13 +537,37 @@ msgstr[1] ""
msgid "%{remaining_approvals} left"
msgstr ""
-msgid "%{retryButtonStart}Try again%{retryButtonEnd} or %{newFileButtonStart}attach a new file%{newFileButtonEnd}"
+msgid "%{reportType} %{status} detected %{critical} critical and %{high} high severity vulnerabilities out of %{total}."
+msgstr ""
+
+msgid "%{reportType} %{status} detected %{critical} critical and %{high} high severity vulnerabilities."
+msgstr ""
+
+msgid "%{reportType} %{status} detected %{critical} critical severity vulnerabilities out of %{total}."
+msgstr ""
+
+msgid "%{reportType} %{status} detected %{critical} critical severity vulnerability."
+msgid_plural "%{reportType} %{status} detected %{critical} critical severity vulnerabilities."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{reportType} %{status} detected %{high} high severity vulnerabilities out of %{total}."
msgstr ""
-msgid "%{screenreaderOnlyStart}Keyboard shorcuts%{screenreaderOnlyEnd} Disabled"
+msgid "%{reportType} %{status} detected %{high} high severity vulnerability."
+msgid_plural "%{reportType} %{status} detected %{high} high severity vulnerabilities."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{reportType} %{status} detected %{other} vulnerability."
+msgid_plural "%{reportType} %{status} detected %{other} vulnerabilities."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{reportType} %{status} detected no new vulnerabilities."
msgstr ""
-msgid "%{screenreaderOnlyStart}Keyboard shorcuts%{screenreaderOnlyEnd} Enabled"
+msgid "%{retryButtonStart}Try again%{retryButtonEnd} or %{newFileButtonStart}attach a new file%{newFileButtonEnd}"
msgstr ""
msgid "%{service_title} %{message}."
@@ -620,6 +664,12 @@ msgstr ""
msgid "%{token}..."
msgstr ""
+msgid "%{totalCpu} (%{freeSpacePercentage}%{percentSymbol} free)"
+msgstr ""
+
+msgid "%{totalMemory} (%{freeSpacePercentage}%{percentSymbol} free)"
+msgstr ""
+
msgid "%{totalWeight} total weight"
msgstr ""
@@ -650,6 +700,12 @@ msgstr ""
msgid "%{verb} %{time_spent_value} spent time."
msgstr ""
+msgid "%{webhooks_link_start}%{webhook_type}%{link_end} enable you to send notifications to web applications in response to events in a group or project."
+msgstr ""
+
+msgid "%{webhooks_link_start}%{webhook_type}%{link_end} enable you to send notifications to web applications in response to events in a group or project. We recommend using an %{integrations_link_start}integration%{link_end} in preference to a webhook."
+msgstr ""
+
msgid "'%{level}' is not a valid visibility level"
msgstr ""
@@ -676,9 +732,6 @@ msgstr ""
msgid "(No changes)"
msgstr ""
-msgid "(Show all)"
-msgstr ""
-
msgid "(check progress)"
msgstr ""
@@ -745,6 +798,9 @@ msgstr ""
msgid "0 for unlimited"
msgstr ""
+msgid "0 for unlimited, only effective with remote storage enabled."
+msgstr ""
+
msgid "1 %{type} addition"
msgid_plural "%{count} %{type} additions"
msgstr[0] ""
@@ -892,6 +948,12 @@ msgstr ""
msgid "<code>\"johnsmith@example.com\": \"johnsmith@example.com\"</code> will add \"By <a href=\"#\">johnsmith@example.com</a>\" to all issues and comments originally created by johnsmith@example.com. By default, the email address or username is masked to ensure the user's privacy. Use this option if you want to show the full email address."
msgstr ""
+msgid "<code>Masked</code> to prevent the values from being displayed in job logs (must match certain regexp requirements)."
+msgstr ""
+
+msgid "<code>Protected</code> to expose them to protected branches or tags only."
+msgstr ""
+
msgid "<namespace / project>"
msgstr ""
@@ -919,9 +981,6 @@ msgstr ""
msgid "A .NET Core console application template, customizable for any .NET Core project"
msgstr ""
-msgid "A DAG must have two dependent jobs to be visualized on this tab."
-msgstr ""
-
msgid "A GitBook site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
msgstr ""
@@ -952,6 +1011,12 @@ msgstr ""
msgid "A deleted user"
msgstr ""
+msgid "A file has been changed."
+msgstr ""
+
+msgid "A file was not found."
+msgstr ""
+
msgid "A file with '%{file_name}' already exists in %{branch} branch"
msgstr ""
@@ -1018,7 +1083,7 @@ msgstr ""
msgid "A subscription will trigger a new pipeline on the default branch of this project when a pipeline successfully completes for a new tag on the %{default_branch_docs} of the subscribed project."
msgstr ""
-msgid "A terraform report was generated in your pipelines."
+msgid "A suggestion is not applicable."
msgstr ""
msgid "A user with write access to the source branch selected this option"
@@ -1189,6 +1254,9 @@ msgstr ""
msgid "Action to take when receiving an alert."
msgstr ""
+msgid "Actions"
+msgstr ""
+
msgid "Activate"
msgstr ""
@@ -1245,6 +1313,9 @@ msgstr ""
msgid "Add LICENSE"
msgstr ""
+msgid "Add New Node"
+msgstr ""
+
msgid "Add README"
msgstr ""
@@ -1320,6 +1391,9 @@ msgstr ""
msgid "Add approval rule"
msgstr ""
+msgid "Add approvers"
+msgstr ""
+
msgid "Add bold text"
msgstr ""
@@ -1377,6 +1451,9 @@ msgstr ""
msgid "Add strikethrough text"
msgstr ""
+msgid "Add suggestion to batch"
+msgstr ""
+
msgid "Add system hook"
msgstr ""
@@ -1476,6 +1553,9 @@ msgstr ""
msgid "Admin Note"
msgstr ""
+msgid "Admin Notifications"
+msgstr ""
+
msgid "Admin Overview"
msgstr ""
@@ -1587,6 +1667,9 @@ msgstr ""
msgid "AdminSettings|Integrations configured here will automatically apply to all projects on this instance."
msgstr ""
+msgid "AdminSettings|Moved to integrations"
+msgstr ""
+
msgid "AdminSettings|No required pipeline"
msgstr ""
@@ -1817,12 +1900,18 @@ msgid_plural "Alerts"
msgstr[0] ""
msgstr[1] ""
+msgid "AlertManagement| assign yourself"
+msgstr ""
+
msgid "AlertManagement|Acknowledged"
msgstr ""
msgid "AlertManagement|Alert"
msgstr ""
+msgid "AlertManagement|Alert assignee(s): %{assignees}"
+msgstr ""
+
msgid "AlertManagement|Alert detail"
msgstr ""
@@ -1838,9 +1927,15 @@ msgstr ""
msgid "AlertManagement|All alerts"
msgstr ""
+msgid "AlertManagement|Assign To"
+msgstr ""
+
msgid "AlertManagement|Assign status"
msgstr ""
+msgid "AlertManagement|Assignee"
+msgstr ""
+
msgid "AlertManagement|Assignees"
msgstr ""
@@ -1859,9 +1954,6 @@ msgstr ""
msgid "AlertManagement|Edit"
msgstr ""
-msgid "AlertManagement|End time"
-msgstr ""
-
msgid "AlertManagement|Events"
msgstr ""
@@ -1880,10 +1972,13 @@ msgstr ""
msgid "AlertManagement|More information"
msgstr ""
+msgid "AlertManagement|No Matching Results"
+msgstr ""
+
msgid "AlertManagement|No alert data to display."
msgstr ""
-msgid "AlertManagement|No alerts available to display. If you think you're seeing this message in error, refresh the page."
+msgid "AlertManagement|No alerts available to display. See %{linkStart}enabling alert management%{linkEnd} for more information on adding alerts to the list."
msgstr ""
msgid "AlertManagement|No alerts to display."
@@ -1892,6 +1987,9 @@ msgstr ""
msgid "AlertManagement|None"
msgstr ""
+msgid "AlertManagement|None -"
+msgstr ""
+
msgid "AlertManagement|Open"
msgstr ""
@@ -1928,9 +2026,18 @@ msgstr ""
msgid "AlertManagement|There was an error displaying the alerts. Confirm your endpoint's configuration details to ensure alerts appear."
msgstr ""
+msgid "AlertManagement|There was an error while updating the assignee(s) list. Please try again."
+msgstr ""
+
+msgid "AlertManagement|There was an error while updating the assignee(s) of the alert. Please try again."
+msgstr ""
+
msgid "AlertManagement|There was an error while updating the status of the alert. Please try again."
msgstr ""
+msgid "AlertManagement|This assignee cannot be assigned to this alert."
+msgstr ""
+
msgid "AlertManagement|Tool"
msgstr ""
@@ -2078,6 +2185,9 @@ msgstr ""
msgid "Allow users to request access (if visibility is public or internal)"
msgstr ""
+msgid "Allowed Geo IP"
+msgstr ""
+
msgid "Allowed email domain restriction only permitted for top-level groups"
msgstr ""
@@ -2111,6 +2221,9 @@ msgstr ""
msgid "Amazon Web Services"
msgstr ""
+msgid "Amazon Web Services Logo"
+msgstr ""
+
msgid "Amazon authentication is not %{link_start}correctly configured%{link_end}. Ask your GitLab administrator if you want to use this service."
msgstr ""
@@ -2168,10 +2281,16 @@ msgstr ""
msgid "An error occurred when updating the issue weight"
msgstr ""
+msgid "An error occurred while acknowledging the notification. Refresh the page and try again."
+msgstr ""
+
+msgid "An error occurred while adding approvers"
+msgstr ""
+
msgid "An error occurred while adding formatted title for epic"
msgstr ""
-msgid "An error occurred while checking group path"
+msgid "An error occurred while checking group path. Please refresh and try again."
msgstr ""
msgid "An error occurred while committing your changes."
@@ -2315,7 +2434,7 @@ msgstr ""
msgid "An error occurred while loading milestones"
msgstr ""
-msgid "An error occurred while loading terraform report"
+msgid "An error occurred while loading project creation UI"
msgstr ""
msgid "An error occurred while loading the data. Please try again."
@@ -2369,6 +2488,9 @@ msgstr ""
msgid "An error occurred while rendering preview broadcast message"
msgstr ""
+msgid "An error occurred while rendering the editor"
+msgstr ""
+
msgid "An error occurred while reordering issues."
msgstr ""
@@ -2465,7 +2587,7 @@ msgstr ""
msgid "Analyze your dependencies for known vulnerabilities."
msgstr ""
-msgid "Analyze your source code and git history for secrets"
+msgid "Analyze your source code and git history for secrets."
msgstr ""
msgid "Analyze your source code for known vulnerabilities."
@@ -2579,12 +2701,18 @@ msgstr ""
msgid "Apply suggestion"
msgstr ""
+msgid "Apply suggestions"
+msgstr ""
+
msgid "Apply template"
msgstr ""
msgid "Apply this approval rule to any branch or a specific protected branch."
msgstr ""
+msgid "Applying"
+msgstr ""
+
msgid "Applying a template will replace the existing issue description. Any changes you have made will be lost."
msgstr ""
@@ -2597,12 +2725,18 @@ msgstr ""
msgid "Applying multiple commands"
msgstr ""
-msgid "Applying suggestion"
+msgid "Applying suggestion..."
+msgstr ""
+
+msgid "Applying suggestions..."
msgstr ""
msgid "Approval rules"
msgstr ""
+msgid "Approval rules reset to project defaults"
+msgstr ""
+
msgid "ApprovalRuleRemove|%d member"
msgid_plural "ApprovalRuleRemove|%d members"
msgstr[0] ""
@@ -2656,6 +2790,9 @@ msgstr ""
msgid "Approved"
msgstr ""
+msgid "Approved MRs"
+msgstr ""
+
msgid "Approved by: "
msgstr ""
@@ -2869,6 +3006,9 @@ msgstr ""
msgid "Assign"
msgstr ""
+msgid "Assign Iteration"
+msgstr ""
+
msgid "Assign custom color like #FF0000"
msgstr ""
@@ -2887,6 +3027,9 @@ msgstr ""
msgid "Assign to"
msgstr ""
+msgid "Assign to commenting user"
+msgstr ""
+
msgid "Assign yourself to these issues"
msgstr ""
@@ -2913,6 +3056,9 @@ msgid_plural "%d Assignees"
msgstr[0] ""
msgstr[1] ""
+msgid "Assignee has no permissions"
+msgstr ""
+
msgid "Assignee lists not available with your current license"
msgstr ""
@@ -2922,6 +3068,9 @@ msgstr ""
msgid "Assignee(s)"
msgstr ""
+msgid "Assignees"
+msgstr ""
+
msgid "Assigns %{assignee_users_sentence}."
msgstr ""
@@ -2957,6 +3106,9 @@ msgstr ""
msgid "Audit Events is a way to keep track of important events that happened in GitLab."
msgstr ""
+msgid "Audit Log"
+msgstr ""
+
msgid "AuditEvents|(removed)"
msgstr ""
@@ -2993,6 +3145,9 @@ msgstr ""
msgid "AuditLogs|IP Address"
msgstr ""
+msgid "AuditLogs|Member Events"
+msgstr ""
+
msgid "AuditLogs|No matching %{type} found."
msgstr ""
@@ -3275,9 +3430,6 @@ msgstr ""
msgid "Badges|e.g. %{exampleUrl}"
msgstr ""
-msgid "Badge|New"
-msgstr ""
-
msgid "Balsamiq file could not be loaded."
msgstr ""
@@ -3326,6 +3478,12 @@ msgstr ""
msgid "Below you will find all the groups that are public."
msgstr ""
+msgid "Beta"
+msgstr ""
+
+msgid "Bi-weekly code coverage"
+msgstr ""
+
msgid "Billing"
msgstr ""
@@ -3386,9 +3544,15 @@ msgstr ""
msgid "Bitbucket Server Import"
msgstr ""
+msgid "Bitbucket Server import"
+msgstr ""
+
msgid "Bitbucket import"
msgstr ""
+msgid "Blame"
+msgstr ""
+
msgid "Blocked"
msgstr ""
@@ -3401,9 +3565,6 @@ msgstr ""
msgid "Blog"
msgstr ""
-msgid "Blue helpers indicate an action to be taken."
-msgstr ""
-
msgid "Board name"
msgstr ""
@@ -3602,6 +3763,9 @@ msgstr ""
msgid "Broadcast Message was successfully updated."
msgstr ""
+msgid "Broadcast Messages"
+msgstr ""
+
msgid "Browse Directory"
msgstr ""
@@ -3656,6 +3820,9 @@ msgstr ""
msgid "By %{user_name}"
msgstr ""
+msgid "By URL"
+msgstr ""
+
msgid "By default GitLab sends emails in HTML and plain text formats so mail clients can choose what format to use. Disable this option if you only want to send emails in plain text format."
msgstr ""
@@ -3761,6 +3928,9 @@ msgstr ""
msgid "Can override approvers and approvals required per merge request"
msgstr ""
+msgid "Can't apply as this line has changed or the suggestion already matches its content."
+msgstr ""
+
msgid "Can't create snippet: %{err}"
msgstr ""
@@ -3947,9 +4117,6 @@ msgstr ""
msgid "Changes are still tracked. Useful for cluster/index migrations."
msgstr ""
-msgid "Changes are unknown"
-msgstr ""
-
msgid "Changes suppressed. Click to show."
msgstr ""
@@ -4256,15 +4423,9 @@ msgstr ""
msgid "Choose what content you want to see on a group’s overview page"
msgstr ""
-msgid "Choose which groups you wish to synchronize to this secondary node"
-msgstr ""
-
msgid "Choose which repositories you want to connect and run CI/CD pipelines."
msgstr ""
-msgid "Choose which shards you wish to synchronize to this secondary node"
-msgstr ""
-
msgid "Choose your framework"
msgstr ""
@@ -4562,9 +4723,15 @@ msgstr ""
msgid "Cluster does not exist"
msgstr ""
+msgid "Cluster is required for Stages::ClusterEndpointInserter"
+msgstr ""
+
msgid "Cluster level"
msgstr ""
+msgid "Cluster type must be specificed for Stages::ClusterEndpointInserter"
+msgstr ""
+
msgid "ClusterIntegration| %{custom_domain_start}More information%{custom_domain_end}."
msgstr ""
@@ -4676,7 +4843,7 @@ msgstr ""
msgid "ClusterIntegration|Cert-Manager"
msgstr ""
-msgid "ClusterIntegration|Cert-Manager is a native Kubernetes certificate management controller that helps with issuing certificates. Installing Cert-Manager on your cluster will issue a certificate by %{letsEncrypt} and ensure that certificates are valid and up-to-date."
+msgid "ClusterIntegration|Cert-Manager is a native Kubernetes certificate management controller that helps with issuing certificates. Installing Cert-Manager on your cluster will issue a certificate by %{linkStart}Let's Encrypt%{linkEnd} and ensure that certificates are valid and up-to-date."
msgstr ""
msgid "ClusterIntegration|Certificate Authority bundle (PEM format)"
@@ -4703,9 +4870,6 @@ msgstr ""
msgid "ClusterIntegration|Clear the local cache of namespace and service accounts. This is necessary if your integration has become out of sync. The cache is repopulated during the next CI job that requires namespace and service accounts."
msgstr ""
-msgid "ClusterIntegration|Cloud Run"
-msgstr ""
-
msgid "ClusterIntegration|Cluster being created"
msgstr ""
@@ -4790,7 +4954,7 @@ msgstr ""
msgid "ClusterIntegration|Crossplane"
msgstr ""
-msgid "ClusterIntegration|Crossplane enables declarative provisioning of managed services from your cloud of choice using %{codeStart}kubectl%{codeEnd} or %{gitlabIntegrationLink}. Crossplane runs inside your Kubernetes cluster and supports secure connectivity and secrets management between app containers and the cloud services they depend on."
+msgid "ClusterIntegration|Crossplane enables declarative provisioning of managed services from your cloud of choice using %{codeStart}kubectl%{codeEnd} or %{linkStart}GitLab Integration%{linkEnd}. Crossplane runs inside your Kubernetes cluster and supports secure connectivity and secrets management between app containers and the cloud services they depend on."
msgstr ""
msgid "ClusterIntegration|Deletes all GitLab resources attached to this cluster during removal"
@@ -4874,9 +5038,6 @@ msgstr ""
msgid "ClusterIntegration|GitLab-managed cluster"
msgstr ""
-msgid "ClusterIntegration|Gitlab Integration"
-msgstr ""
-
msgid "ClusterIntegration|Global default"
msgstr ""
@@ -4922,7 +5083,7 @@ msgstr ""
msgid "ClusterIntegration|Ingress gives you a way to route requests to services based on the request host or path, centralizing a number of services into a single entrypoint."
msgstr ""
-msgid "ClusterIntegration|Installing Ingress may incur additional costs. Learn more about %{pricingLink}."
+msgid "ClusterIntegration|Installing Ingress may incur additional costs. Learn more about %{linkStart}pricing%{linkEnd}."
msgstr ""
msgid "ClusterIntegration|Instance cluster"
@@ -4937,7 +5098,7 @@ msgstr ""
msgid "ClusterIntegration|Issuer Email"
msgstr ""
-msgid "ClusterIntegration|Issuers represent a certificate authority. You must provide an email address for your Issuer. "
+msgid "ClusterIntegration|Issuers represent a certificate authority. You must provide an email address for your Issuer."
msgstr ""
msgid "ClusterIntegration|Jupyter Hostname"
@@ -5009,9 +5170,6 @@ msgstr ""
msgid "ClusterIntegration|Learn more about instance Kubernetes clusters"
msgstr ""
-msgid "ClusterIntegration|Let's Encrypt"
-msgstr ""
-
msgid "ClusterIntegration|Loading IAM Roles"
msgstr ""
@@ -5123,7 +5281,7 @@ msgstr ""
msgid "ClusterIntegration|Prometheus"
msgstr ""
-msgid "ClusterIntegration|Prometheus is an open-source monitoring system with %{gitlabIntegrationLink} to monitor deployed applications."
+msgid "ClusterIntegration|Prometheus is an open-source monitoring system with %{linkStart}GitLab Integration%{linkEnd} to monitor deployed applications."
msgstr ""
msgid "ClusterIntegration|Provider details"
@@ -5279,10 +5437,10 @@ msgstr ""
msgid "ClusterIntegration|Select zone to choose machine type"
msgstr ""
-msgid "ClusterIntegration|Send Cilium Logs"
+msgid "ClusterIntegration|Send Container Network Policies Logs"
msgstr ""
-msgid "ClusterIntegration|Send ModSecurity Logs"
+msgid "ClusterIntegration|Send Web Application Firewall Logs"
msgstr ""
msgid "ClusterIntegration|Service Token"
@@ -5450,15 +5608,12 @@ msgstr ""
msgid "ClusterIntegration|documentation"
msgstr ""
-msgid "ClusterIntegration|installed via %{installed_via}"
+msgid "ClusterIntegration|installed via %{linkStart}Cloud Run%{linkEnd}"
msgstr ""
msgid "ClusterIntegration|meets the requirements"
msgstr ""
-msgid "ClusterIntegration|pricing"
-msgstr ""
-
msgid "ClusterIntegration|sign up"
msgstr ""
@@ -5495,6 +5650,15 @@ msgstr ""
msgid "Code"
msgstr ""
+msgid "Code Coverage: %{coveragePercentage}%{percentSymbol}"
+msgstr ""
+
+msgid "Code Coverage| Empty code coverage data"
+msgstr ""
+
+msgid "Code Coverage|Couldn't fetch the code coverage data"
+msgstr ""
+
msgid "Code Owners"
msgstr ""
@@ -5549,7 +5713,7 @@ msgstr ""
msgid "Collapse approvers"
msgstr ""
-msgid "Collapse child epics"
+msgid "Collapse milestones"
msgstr ""
msgid "Collapse replies"
@@ -5567,6 +5731,9 @@ msgstr ""
msgid "Coming soon"
msgstr ""
+msgid "Comma-separated, e.g. '1.1.1.1, 2.2.2.0/24'"
+msgstr ""
+
msgid "Command"
msgstr ""
@@ -5737,6 +5904,12 @@ msgstr ""
msgid "Compliance framework (optional)"
msgstr ""
+msgid "Compliance frameworks"
+msgstr ""
+
+msgid "ComplianceDashboard|created by:"
+msgstr ""
+
msgid "ComplianceFramework|GDPR"
msgstr ""
@@ -5875,6 +6048,9 @@ msgstr ""
msgid "Connection timed out"
msgstr ""
+msgid "Connection timeout"
+msgstr ""
+
msgid "Contact sales to upgrade"
msgstr ""
@@ -5930,9 +6106,6 @@ msgstr ""
msgid "ContainerRegistry|CLI Commands"
msgstr ""
-msgid "ContainerRegistry|Compressed Size"
-msgstr ""
-
msgid "ContainerRegistry|Container Registry"
msgstr ""
@@ -5948,6 +6121,9 @@ msgstr ""
msgid "ContainerRegistry|Copy push command"
msgstr ""
+msgid "ContainerRegistry|Delete selected"
+msgstr ""
+
msgid "ContainerRegistry|Docker connection error"
msgstr ""
@@ -5981,16 +6157,16 @@ msgstr ""
msgid "ContainerRegistry|If you are not already logged in, you need to authenticate to the Container Registry by using your GitLab username and password. If you have %{twofaDocLinkStart}Two-Factor Authentication%{twofaDocLinkEnd} enabled, use a %{personalAccessTokensDocLinkStart}Personal Access Token%{personalAccessTokensDocLinkEnd} instead of a password."
msgstr ""
-msgid "ContainerRegistry|Image ID"
+msgid "ContainerRegistry|Image ID: %{imageId}"
msgstr ""
msgid "ContainerRegistry|Image Repositories"
msgstr ""
-msgid "ContainerRegistry|Keep and protect the images that matter most."
+msgid "ContainerRegistry|Image tags"
msgstr ""
-msgid "ContainerRegistry|Last Updated"
+msgid "ContainerRegistry|Keep and protect the images that matter most."
msgstr ""
msgid "ContainerRegistry|Login"
@@ -6005,6 +6181,9 @@ msgstr ""
msgid "ContainerRegistry|Please contact your administrator."
msgstr ""
+msgid "ContainerRegistry|Published %{timeInfo}"
+msgstr ""
+
msgid "ContainerRegistry|Push an image"
msgstr ""
@@ -6020,9 +6199,6 @@ msgstr ""
msgid "ContainerRegistry|Remove repository"
msgstr ""
-msgid "ContainerRegistry|Remove selected tags"
-msgstr ""
-
msgid "ContainerRegistry|Remove tag"
msgid_plural "ContainerRegistry|Remove tags"
msgstr[0] ""
@@ -6052,9 +6228,6 @@ msgstr ""
msgid "ContainerRegistry|Sorry, your filter produced no results."
msgstr ""
-msgid "ContainerRegistry|Tag"
-msgstr ""
-
msgid "ContainerRegistry|Tag expiration policy"
msgstr ""
@@ -6199,21 +6372,6 @@ msgstr ""
msgid "Control the display of third party offers."
msgstr ""
-msgid "Control the maximum concurrency of LFS/attachment backfill for this secondary node"
-msgstr ""
-
-msgid "Control the maximum concurrency of container repository operations for this Geo node"
-msgstr ""
-
-msgid "Control the maximum concurrency of repository backfill for this secondary node"
-msgstr ""
-
-msgid "Control the maximum concurrency of verification operations for this Geo node"
-msgstr ""
-
-msgid "Control the minimum interval in days that a repository should be reverified for this primary node"
-msgstr ""
-
msgid "Cookie domain"
msgstr ""
@@ -6355,6 +6513,9 @@ msgstr ""
msgid "Could not find design."
msgstr ""
+msgid "Could not find iteration"
+msgstr ""
+
msgid "Could not remove the trigger."
msgstr ""
@@ -6391,6 +6552,12 @@ msgstr ""
msgid "Coverage"
msgstr ""
+msgid "Coverage Fuzzing"
+msgstr ""
+
+msgid "Crash State"
+msgstr ""
+
msgid "Create"
msgstr ""
@@ -6439,6 +6606,9 @@ msgstr ""
msgid "Create a personal access token on your account to pull or push via %{protocol}."
msgstr ""
+msgid "Create a project pre-populated with the necessary files to get you started quickly."
+msgstr ""
+
msgid "Create an account using:"
msgstr ""
@@ -6607,6 +6777,9 @@ msgstr ""
msgid "Creates branch '%{branch_name}' and a merge request to resolve this issue."
msgstr ""
+msgid "Creating"
+msgstr ""
+
msgid "Creating epic"
msgstr ""
@@ -6658,7 +6831,10 @@ msgstr ""
msgid "Current vulnerabilities count"
msgstr ""
-msgid "CurrentUser|Buy CI minutes"
+msgid "CurrentUser|Buy Pipeline minutes"
+msgstr ""
+
+msgid "CurrentUser|One of your groups is running out"
msgstr ""
msgid "CurrentUser|Profile"
@@ -6673,6 +6849,9 @@ msgstr ""
msgid "CurrentUser|Upgrade"
msgstr ""
+msgid "Custom Attributes"
+msgstr ""
+
msgid "Custom CI configuration path"
msgstr ""
@@ -6936,12 +7115,18 @@ msgstr ""
msgid "DAG"
msgstr ""
+msgid "DAG visualization requires at least 3 dependent jobs."
+msgstr ""
+
msgid "DNS"
msgstr ""
msgid "Dashboard"
msgstr ""
+msgid "Dashboard uid not found"
+msgstr ""
+
msgid "DashboardProjects|All"
msgstr ""
@@ -6966,6 +7151,9 @@ msgstr ""
msgid "Data is still calculating..."
msgstr ""
+msgid "Datasource name not found"
+msgstr ""
+
msgid "Date"
msgstr ""
@@ -7146,6 +7334,9 @@ msgstr ""
msgid "Delete this attachment"
msgstr ""
+msgid "Delete user list"
+msgstr ""
+
msgid "Delete variable"
msgstr ""
@@ -7462,6 +7653,9 @@ msgstr ""
msgid "Deploying to"
msgstr ""
+msgid "Deploying to AWS is easy with GitLab"
+msgstr ""
+
msgid "Deployment Frequency"
msgstr ""
@@ -7824,6 +8018,9 @@ msgstr ""
msgid "Dismissed on pipeline %{pipelineLink} at %{projectLink}"
msgstr ""
+msgid "Display alerts from all your monitoring tools directly within GitLab."
+msgstr ""
+
msgid "Display name"
msgstr ""
@@ -7881,9 +8078,6 @@ msgstr ""
msgid "Don't show again"
msgstr ""
-msgid "Don't worry, you can access this tour by clicking on the help icon in the top right corner and choose <strong>Learn GitLab</strong>."
-msgstr ""
-
msgid "Done"
msgstr ""
@@ -8010,6 +8204,9 @@ msgstr ""
msgid "Edit Release"
msgstr ""
+msgid "Edit Slack integration"
+msgstr ""
+
msgid "Edit Snippet"
msgstr ""
@@ -8049,6 +8246,9 @@ msgstr ""
msgid "Edit issues"
msgstr ""
+msgid "Edit iteration"
+msgstr ""
+
msgid "Edit public deploy key"
msgstr ""
@@ -8097,6 +8297,9 @@ msgstr ""
msgid "Email"
msgstr ""
+msgid "Email Notification"
+msgstr ""
+
msgid "Email address"
msgstr ""
@@ -8109,6 +8312,9 @@ msgstr ""
msgid "Email not verified. Please verify your email in Salesforce."
msgstr ""
+msgid "Email notification for unknown sign-ins"
+msgstr ""
+
msgid "Email patch"
msgstr ""
@@ -8340,6 +8546,9 @@ msgstr ""
msgid "Enforce DNS rebinding attack protection"
msgstr ""
+msgid "Enforce personal access token expiration"
+msgstr ""
+
msgid "Ensure connectivity is available from the GitLab server to the Prometheus server"
msgstr ""
@@ -8409,6 +8618,9 @@ msgstr ""
msgid "Enter the name of your application, and we'll return a unique %{type}."
msgstr ""
+msgid "Enter weights for storages for new repositories."
+msgstr ""
+
msgid "Enter your password to approve"
msgstr ""
@@ -8418,15 +8630,27 @@ msgstr ""
msgid "Environment does not have deployments"
msgstr ""
+msgid "Environment is required for Stages::MetricEndpointInserter"
+msgstr ""
+
+msgid "Environment is required for Stages::VariableEndpointInserter"
+msgstr ""
+
msgid "Environment scope"
msgstr ""
+msgid "Environment variables are applied to all project environments in this instance via the Runner. You can use environment variables for passwords, secret keys, etc. Make variables available to the running application by prepending the variable key with <code>K8S_SECRET_</code>. You can set variables to be:"
+msgstr ""
+
msgid "Environment variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. Additionally, they can be masked so they are hidden in job logs, though they must match certain regexp requirements to do so. You can use environment variables for passwords, secret keys, or whatever you want."
msgstr ""
msgid "Environment variables are configured by your administrator to be %{link_start}protected%{link_end} by default"
msgstr ""
+msgid "Environment variables on this GitLab instance are configured to be %{link_start}protected%{link_end} by default"
+msgstr ""
+
msgid "Environment:"
msgstr ""
@@ -8958,6 +9182,9 @@ msgstr ""
msgid "EventFilterBy|Filter by comments"
msgstr ""
+msgid "EventFilterBy|Filter by designs"
+msgstr ""
+
msgid "EventFilterBy|Filter by epic events"
msgstr ""
@@ -9090,15 +9317,15 @@ msgstr ""
msgid "Expand approvers"
msgstr ""
-msgid "Expand child epics"
-msgstr ""
-
msgid "Expand down"
msgstr ""
msgid "Expand dropdown"
msgstr ""
+msgid "Expand milestones"
+msgstr ""
+
msgid "Expand sidebar"
msgstr ""
@@ -9114,6 +9341,9 @@ msgstr ""
msgid "Expiration date"
msgstr ""
+msgid "Expiration not enforced"
+msgstr ""
+
msgid "Expiration policy for the Container Registry is a perfect solution for keeping the Registry space down while still enjoying the full power of GitLab CI/CD."
msgstr ""
@@ -9234,6 +9464,9 @@ msgstr ""
msgid "Failed Jobs"
msgstr ""
+msgid "Failed on"
+msgstr ""
+
msgid "Failed to add a Zoom meeting"
msgstr ""
@@ -9294,13 +9527,19 @@ msgstr ""
msgid "Failed to install."
msgstr ""
+msgid "Failed to load assignees. Please try again."
+msgstr ""
+
+msgid "Failed to load authors. Please try again."
+msgstr ""
+
msgid "Failed to load emoji list."
msgstr ""
msgid "Failed to load error details from Sentry."
msgstr ""
-msgid "Failed to load errors from Sentry. Error message: %{errorMessage}"
+msgid "Failed to load errors from Sentry."
msgstr ""
msgid "Failed to load group activity metrics. Please try again."
@@ -9381,6 +9620,9 @@ msgstr ""
msgid "Failed to set due date because the date format is invalid."
msgstr ""
+msgid "Failed to set iteration on this issue. Please try again."
+msgstr ""
+
msgid "Failed to signing using smartcard authentication"
msgstr ""
@@ -9603,13 +9845,10 @@ msgstr ""
msgid "FeatureFlags|Target environments"
msgstr ""
-msgid "FeatureFlags|There are no active feature flags"
-msgstr ""
-
-msgid "FeatureFlags|There are no inactive feature flags"
+msgid "FeatureFlags|There was an error fetching the feature flags."
msgstr ""
-msgid "FeatureFlags|There was an error fetching the feature flags."
+msgid "FeatureFlags|There was an error retrieving user lists"
msgstr ""
msgid "FeatureFlags|Try again in a few moments or contact your support team."
@@ -9621,9 +9860,18 @@ msgstr ""
msgid "FeatureFlag|Delete strategy"
msgstr ""
+msgid "FeatureFlag|List"
+msgstr ""
+
msgid "FeatureFlag|Percentage"
msgstr ""
+msgid "FeatureFlag|Select a user list"
+msgstr ""
+
+msgid "FeatureFlag|There are no configured user lists"
+msgstr ""
+
msgid "FeatureFlag|Type"
msgstr ""
@@ -9780,6 +10028,12 @@ msgstr ""
msgid "Filter..."
msgstr ""
+msgid "Find File"
+msgstr ""
+
+msgid "Find bugs in your code with coverage-guided fuzzing"
+msgstr ""
+
msgid "Find by path"
msgstr ""
@@ -10044,6 +10298,9 @@ msgstr ""
msgid "Geo allows you to replicate your GitLab instance to other geographical locations."
msgstr ""
+msgid "Geo nodes are paused using a command run on the node"
+msgstr ""
+
msgid "GeoNodeStatusEvent|%{timeAgoStr} (%{pendingEvents} events)"
msgstr ""
@@ -10128,6 +10385,9 @@ msgstr ""
msgid "GeoNodes|Not checksummed"
msgstr ""
+msgid "GeoNodes|Package files"
+msgstr ""
+
msgid "GeoNodes|Pausing replication stops the sync process. Are you sure?"
msgstr ""
@@ -10146,6 +10406,9 @@ msgstr ""
msgid "GeoNodes|Replication slots"
msgstr ""
+msgid "GeoNodes|Replication status"
+msgstr ""
+
msgid "GeoNodes|Repositories"
msgstr ""
@@ -10356,6 +10619,9 @@ msgstr ""
msgid "Geo|Synchronization failed - %{error}"
msgstr ""
+msgid "Geo|Synchronization of %{itemTitle} is disabled."
+msgstr ""
+
msgid "Geo|The database is currently %{db_lag} behind the primary node."
msgstr ""
@@ -10668,6 +10934,9 @@ msgstr ""
msgid "Go to %{link_to_google_takeout}."
msgstr ""
+msgid "Go to <strong>Issues</strong> > <strong>Boards</strong> to access your personalized learning issue board."
+msgstr ""
+
msgid "Go to Pipelines"
msgstr ""
@@ -10767,9 +11036,6 @@ msgstr ""
msgid "Go to your snippets"
msgstr ""
-msgid "Golden Tanuki"
-msgstr ""
-
msgid "Google Cloud Platform"
msgstr ""
@@ -10791,6 +11057,9 @@ msgstr ""
msgid "Grafana URL"
msgstr ""
+msgid "Grafana response contains invalid json"
+msgstr ""
+
msgid "GrafanaIntegration|API Token"
msgstr ""
@@ -10839,9 +11108,6 @@ msgstr ""
msgid "Group %{group_name} was successfully created."
msgstr ""
-msgid "Group '%{group_name}' is being imported."
-msgstr ""
-
msgid "Group Audit Events"
msgstr ""
@@ -10878,9 +11144,6 @@ msgstr ""
msgid "Group by:"
msgstr ""
-msgid "Group could not be imported: %{errors}"
-msgstr ""
-
msgid "Group description"
msgstr ""
@@ -10908,9 +11171,15 @@ msgstr ""
msgid "Group has not been marked for deletion"
msgstr ""
+msgid "Group import could not be scheduled"
+msgstr ""
+
msgid "Group info:"
msgstr ""
+msgid "Group is required when cluster_type is :group"
+msgstr ""
+
msgid "Group maintainers can register group runners in the %{link}"
msgstr ""
@@ -10974,6 +11243,24 @@ msgstr ""
msgid "GroupActivyMetrics|Recent activity (last 90 days)"
msgstr ""
+msgid "GroupImport|Failed to import group."
+msgstr ""
+
+msgid "GroupImport|Group '%{group_name}' is being imported."
+msgstr ""
+
+msgid "GroupImport|Group could not be imported: %{errors}"
+msgstr ""
+
+msgid "GroupImport|Please wait while we import the group for you. Refresh at will."
+msgstr ""
+
+msgid "GroupImport|The group was successfully imported."
+msgstr ""
+
+msgid "GroupImport|Unable to process group import file"
+msgstr ""
+
msgid "GroupRoadmap|%{dateWord} – No end date"
msgstr ""
@@ -11241,6 +11528,9 @@ msgstr ""
msgid "Groups (%{groups})"
msgstr ""
+msgid "Groups and projects"
+msgstr ""
+
msgid "Groups can also be nested by creating %{subgroup_docs_link_start}subgroups%{subgroup_docs_link_end}."
msgstr ""
@@ -11286,6 +11576,33 @@ msgstr ""
msgid "GroupsEmptyState|You can manage your group member’s permissions and access to each project in the group."
msgstr ""
+msgid "GroupsNew|Contact an administrator to enable options for importing your group."
+msgstr ""
+
+msgid "GroupsNew|Create"
+msgstr ""
+
+msgid "GroupsNew|Create group"
+msgstr ""
+
+msgid "GroupsNew|GitLab group export"
+msgstr ""
+
+msgid "GroupsNew|Import"
+msgstr ""
+
+msgid "GroupsNew|Import group"
+msgstr ""
+
+msgid "GroupsNew|My Awesome Group"
+msgstr ""
+
+msgid "GroupsNew|No import options available"
+msgstr ""
+
+msgid "GroupsNew|To copy a GitLab group between installations, navigate to the group settings page for the original installation, generate an export file, and upload it here."
+msgstr ""
+
msgid "GroupsTree|Are you sure you want to leave the \"%{fullName}\" group?"
msgstr ""
@@ -11388,6 +11705,9 @@ msgstr ""
msgid "Helps reduce request volume for protected paths"
msgstr ""
+msgid "Here are all your projects in your group, including the one you just created. To start, let’s take a look at your personalized learning project which will help you learn about GitLab at your own pace."
+msgstr ""
+
msgid "Here you will find recent merge request activity"
msgstr ""
@@ -11402,6 +11722,9 @@ msgid_plural "Hide charts"
msgstr[0] ""
msgstr[1] ""
+msgid "Hide details"
+msgstr ""
+
msgid "Hide file browser"
msgstr ""
@@ -11411,6 +11734,9 @@ msgstr ""
msgid "Hide host keys manual input"
msgstr ""
+msgid "Hide list"
+msgstr ""
+
msgid "Hide marketing-related entries from help"
msgstr ""
@@ -11431,9 +11757,6 @@ msgstr[1] ""
msgid "Hide values"
msgstr ""
-msgid "Hiding all labels"
-msgstr ""
-
msgid "High or unknown vulnerabilities present"
msgstr ""
@@ -11518,6 +11841,9 @@ msgstr ""
msgid "ID:"
msgstr ""
+msgid "IDE"
+msgstr ""
+
msgid "IDE|Allow live previews of JavaScript projects in the Web IDE using CodeSandbox Live Preview."
msgstr ""
@@ -11608,10 +11934,10 @@ msgstr ""
msgid "If enabled"
msgstr ""
-msgid "If enabled, access to projects will be validated on an external service using their classification label."
+msgid "If enabled, GitLab will handle Object Storage replication using Geo. %{linkStart}More information%{linkEnd}"
msgstr ""
-msgid "If enabled, and if object storage is enabled, GitLab will handle Object Storage replication using Geo"
+msgid "If enabled, access to projects will be validated on an external service using their classification label."
msgstr ""
msgid "If there is no previous license or if the previous license has expired, some GitLab functionality will be blocked until a new, valid license is uploaded."
@@ -11973,6 +12299,9 @@ msgid_plural "Instances"
msgstr[0] ""
msgstr[1] ""
+msgid "Instance Configuration"
+msgstr ""
+
msgid "Instance Statistics visibility"
msgstr ""
@@ -11994,9 +12323,6 @@ msgstr ""
msgid "Integrations"
msgstr ""
-msgid "Integrations allow you to integrate GitLab with other applications"
-msgstr ""
-
msgid "Integrations|All details"
msgstr ""
@@ -12108,6 +12434,9 @@ msgstr ""
msgid "Invalid query"
msgstr ""
+msgid "Invalid repository bundle for snippet with id %{snippet_id}"
+msgstr ""
+
msgid "Invalid repository path"
msgstr ""
@@ -12126,6 +12455,9 @@ msgstr ""
msgid "Invalid two-factor code."
msgstr ""
+msgid "Invalid yaml"
+msgstr ""
+
msgid "Invitation"
msgstr ""
@@ -12345,15 +12677,24 @@ msgstr ""
msgid "It seems like the Dependency Scanning job ran successfully, but no dependencies have been detected in your project."
msgstr ""
+msgid "It seems that there is currently no available data for code coverage"
+msgstr ""
+
msgid "It's you"
msgstr ""
+msgid "Iteration"
+msgstr ""
+
msgid "Iteration changed to"
msgstr ""
msgid "Iteration removed"
msgstr ""
+msgid "Iteration updated"
+msgstr ""
+
msgid "Iterations"
msgstr ""
@@ -12384,12 +12725,18 @@ msgstr ""
msgid "January"
msgstr ""
+msgid "Jira Issues"
+msgstr ""
+
msgid "Jira import is already running."
msgstr ""
msgid "Jira integration not configured."
msgstr ""
+msgid "Jira project key is not configured"
+msgstr ""
+
msgid "Jira project: %{importProject}"
msgstr ""
@@ -12570,6 +12917,9 @@ msgstr ""
msgid "Keep divergent refs"
msgstr ""
+msgid "Kerberos access denied"
+msgstr ""
+
msgid "Key"
msgstr ""
@@ -12579,10 +12929,13 @@ msgstr ""
msgid "Key: %{key}"
msgstr ""
-msgid "Keyboard Shortcuts"
+msgid "Keyboard shortcuts"
msgstr ""
-msgid "Keyboard shortcuts"
+msgid "Keys"
+msgstr ""
+
+msgid "Ki"
msgstr ""
msgid "Kubernetes"
@@ -12624,6 +12977,9 @@ msgstr ""
msgid "LDAP"
msgstr ""
+msgid "LDAP Synchronization"
+msgstr ""
+
msgid "LDAP settings"
msgstr ""
@@ -12848,12 +13204,18 @@ msgstr ""
msgid "Learn more about custom project templates"
msgstr ""
+msgid "Learn more about deploying to AWS"
+msgstr ""
+
msgid "Learn more about deploying to a cluster"
msgstr ""
msgid "Learn more about group-level project templates"
msgstr ""
+msgid "Learn more about job dependencies"
+msgstr ""
+
msgid "Learn more about signing commits"
msgstr ""
@@ -12905,9 +13267,21 @@ msgstr ""
msgid "License History"
msgstr ""
+msgid "License ID:"
+msgstr ""
+
+msgid "License URL"
+msgstr ""
+
msgid "License-Check"
msgstr ""
+msgid "LicenseCompliance|%{docLinkStart}License Approvals%{docLinkEnd} are active"
+msgstr ""
+
+msgid "LicenseCompliance|%{docLinkStart}License Approvals%{docLinkEnd} are inactive"
+msgstr ""
+
msgid "LicenseCompliance|Add a license"
msgstr ""
@@ -12929,9 +13303,15 @@ msgstr ""
msgid "LicenseCompliance|Deny"
msgstr ""
+msgid "LicenseCompliance|Learn more about %{linkStart}License Approvals%{linkEnd}"
+msgstr ""
+
msgid "LicenseCompliance|License"
msgstr ""
+msgid "LicenseCompliance|License Approvals"
+msgstr ""
+
msgid "LicenseCompliance|License Compliance detected %d license and policy violation for the source branch only; approval required"
msgid_plural "LicenseCompliance|License Compliance detected %d licenses and policy violations for the source branch only; approval required"
msgstr[0] ""
@@ -13152,6 +13532,9 @@ msgstr ""
msgid "List your Bitbucket Server repositories"
msgstr ""
+msgid "Lists"
+msgstr ""
+
msgid "Live preview"
msgstr ""
@@ -13335,6 +13718,9 @@ msgstr ""
msgid "Manage project labels"
msgstr ""
+msgid "Manage storage usage"
+msgstr ""
+
msgid "Manage two-factor authentication"
msgstr ""
@@ -13350,6 +13736,9 @@ msgstr ""
msgid "Manifest file import"
msgstr ""
+msgid "Manifest import"
+msgstr ""
+
msgid "Manual job"
msgstr ""
@@ -13497,6 +13886,12 @@ msgstr ""
msgid "Maximum field length"
msgstr ""
+msgid "Maximum file size is 2MB. Please select a smaller file."
+msgstr ""
+
+msgid "Maximum import size (MB)"
+msgstr ""
+
msgid "Maximum job timeout"
msgstr ""
@@ -13536,6 +13931,9 @@ msgstr ""
msgid "Maximum size of Elasticsearch bulk indexing requests."
msgstr ""
+msgid "Maximum size of import files."
+msgstr ""
+
msgid "Maximum size of individual attachments in comments."
msgstr ""
@@ -13587,6 +13985,9 @@ msgstr ""
msgid "Merge (when the pipeline succeeds)"
msgstr ""
+msgid "Merge Conflicts"
+msgstr ""
+
msgid "Merge Request"
msgstr ""
@@ -13713,6 +14114,9 @@ msgstr ""
msgid "MergeRequests|Squash task canceled: another squash is already in progress."
msgstr ""
+msgid "MergeRequests|This project does not allow squashing commits when merge requests are accepted."
+msgstr ""
+
msgid "MergeRequests|Thread stays resolved"
msgstr ""
@@ -13866,6 +14270,12 @@ msgstr ""
msgid "MetricsSettings|Add a button to the metrics dashboard linking directly to your existing external dashboard."
msgstr ""
+msgid "MetricsSettings|Choose whether to display dashboard metrics in UTC or the user's local timezone."
+msgstr ""
+
+msgid "MetricsSettings|Dashboard timezone"
+msgstr ""
+
msgid "MetricsSettings|External dashboard URL"
msgstr ""
@@ -13875,6 +14285,12 @@ msgstr ""
msgid "MetricsSettings|Metrics Dashboard"
msgstr ""
+msgid "MetricsSettings|UTC (Coordinated Universal Time)"
+msgstr ""
+
+msgid "MetricsSettings|User's local timezone"
+msgstr ""
+
msgid "Metrics|Add metric"
msgstr ""
@@ -13890,6 +14306,9 @@ msgstr ""
msgid "Metrics|Create metric"
msgstr ""
+msgid "Metrics|Current"
+msgstr ""
+
msgid "Metrics|Delete metric"
msgstr ""
@@ -13940,6 +14359,9 @@ msgstr ""
msgid "Metrics|Max"
msgstr ""
+msgid "Metrics|Min"
+msgstr ""
+
msgid "Metrics|Must be a valid PromQL query."
msgstr ""
@@ -13955,6 +14377,9 @@ msgstr ""
msgid "Metrics|Refresh dashboard"
msgstr ""
+msgid "Metrics|Select a value"
+msgstr ""
+
msgid "Metrics|Star dashboard"
msgstr ""
@@ -13973,12 +14398,18 @@ msgstr ""
msgid "Metrics|There was an error getting annotations information."
msgstr ""
+msgid "Metrics|There was an error getting dashboard validation warnings information."
+msgstr ""
+
msgid "Metrics|There was an error getting deployment information."
msgstr ""
msgid "Metrics|There was an error getting environments information."
msgstr ""
+msgid "Metrics|There was an error getting options for variable \"%{name}\"."
+msgstr ""
+
msgid "Metrics|There was an error trying to validate your query"
msgstr ""
@@ -14021,6 +14452,9 @@ msgstr ""
msgid "Metrics|You're about to permanently delete this metric. This cannot be undone."
msgstr ""
+msgid "Metrics|Your dashboard schema is invalid. Edit the dashboard to correct the YAML schema."
+msgstr ""
+
msgid "Metrics|e.g. HTTP requests"
msgstr ""
@@ -14036,12 +14470,18 @@ msgstr ""
msgid "Metrics|e.g. req/sec"
msgstr ""
+msgid "Mi"
+msgstr ""
+
msgid "Microsoft Azure"
msgstr ""
msgid "Middleman project with Static Site Editor support"
msgstr ""
+msgid "Migrate your data from an external source like GitHub, Bitbucket, or another instance of GitLab."
+msgstr ""
+
msgid "Migrated %{success_count}/%{total_count} files."
msgstr ""
@@ -14176,6 +14616,9 @@ msgstr ""
msgid "Minimum capacity to be available before we schedule more mirrors preemptively."
msgstr ""
+msgid "Minimum interval in days"
+msgstr ""
+
msgid "Minimum length is %{minimum_password_length} characters"
msgstr ""
@@ -14362,6 +14805,12 @@ msgstr ""
msgid "Multiple uploaders found: %{uploader_types}"
msgstr ""
+msgid "Must match with the %{codeStart}external_url%{codeEnd} in %{codeStart}/etc/gitlab/gitlab.rb%{codeEnd}."
+msgstr ""
+
+msgid "Must match with the %{codeStart}geo_node_name%{codeEnd} in %{codeStart}/etc/gitlab/gitlab.rb%{codeEnd}. %{linkStart}More information%{linkEnd}"
+msgstr ""
+
msgid "My Awesome Group"
msgstr ""
@@ -14476,10 +14925,16 @@ msgstr ""
msgid "New Application"
msgstr ""
+msgid "New Branch"
+msgstr ""
+
+msgid "New Deploy Key"
+msgstr ""
+
msgid "New Environment"
msgstr ""
-msgid "New Geo Node"
+msgid "New File"
msgstr ""
msgid "New Group"
@@ -14496,15 +14951,15 @@ msgid_plural "New Issues"
msgstr[0] ""
msgstr[1] ""
-msgid "New Iteration"
-msgstr ""
-
msgid "New Jira import"
msgstr ""
msgid "New Label"
msgstr ""
+msgid "New Merge Request"
+msgstr ""
+
msgid "New Milestone"
msgstr ""
@@ -14523,6 +14978,9 @@ msgstr ""
msgid "New Snippet"
msgstr ""
+msgid "New User"
+msgstr ""
+
msgid "New branch"
msgstr ""
@@ -14565,6 +15023,9 @@ msgstr ""
msgid "New issue title"
msgstr ""
+msgid "New iteration"
+msgstr ""
+
msgid "New iteration created"
msgstr ""
@@ -14742,6 +15203,12 @@ msgstr ""
msgid "No grouping"
msgstr ""
+msgid "No iteration"
+msgstr ""
+
+msgid "No iterations to show"
+msgstr ""
+
msgid "No job log"
msgstr ""
@@ -14784,6 +15251,9 @@ msgstr ""
msgid "No other labels with such name or description"
msgstr ""
+msgid "No panels matching properties %{opts}"
+msgstr ""
+
msgid "No parent group"
msgstr ""
@@ -14817,6 +15287,9 @@ msgstr ""
msgid "No schedules"
msgstr ""
+msgid "No stack trace for this error"
+msgstr ""
+
msgid "No starrers matched your search"
msgstr ""
@@ -14829,7 +15302,7 @@ msgstr ""
msgid "No test coverage"
msgstr ""
-msgid "No thanks, don't show this again"
+msgid "No thanks"
msgstr ""
msgid "No vulnerabilities present"
@@ -14844,9 +15317,6 @@ msgstr ""
msgid "No, directly import the existing email addresses and usernames."
msgstr ""
-msgid "No, not interested right now"
-msgstr ""
-
msgid "No. of commits"
msgstr ""
@@ -14892,9 +15362,6 @@ msgstr ""
msgid "Not found."
msgstr ""
-msgid "Not helpful"
-msgstr ""
-
msgid "Not now"
msgstr ""
@@ -15051,6 +15518,9 @@ msgstr ""
msgid "Notifications on"
msgstr ""
+msgid "Notify users by email when sign-in location is not recognized"
+msgstr ""
+
msgid "Nov"
msgstr ""
@@ -15117,9 +15587,6 @@ msgstr ""
msgid "Oh no!"
msgstr ""
-msgid "Ok let's go"
-msgstr ""
-
msgid "Oldest first"
msgstr ""
@@ -15132,16 +15599,49 @@ msgstr ""
msgid "On track"
msgstr ""
+msgid "OnDemandScans|Attached branch"
+msgstr ""
+
+msgid "OnDemandScans|Attached branch is where the scan job runs."
+msgstr ""
+
+msgid "OnDemandScans|Could not run the scan. Please try again."
+msgstr ""
+
msgid "OnDemandScans|Create new DAST scan"
msgstr ""
+msgid "OnDemandScans|DAST will scan the target URL and any discovered sub URLs."
+msgstr ""
+
+msgid "OnDemandScans|New on-demand DAST scan"
+msgstr ""
+
msgid "OnDemandScans|On-demand Scans"
msgstr ""
+msgid "OnDemandScans|On-demand scans run outside the DevOps cycle and find vulnerabilities in your projects. %{learnMoreLinkStart}Learn more%{learnMoreLinkEnd}"
+msgstr ""
+
+msgid "OnDemandScans|Only a passive scan can be performed on demand."
+msgstr ""
+
+msgid "OnDemandScans|Passive DAST Scan"
+msgstr ""
+
+msgid "OnDemandScans|Please enter a valid URL format, ex: http://www.example.com/home"
+msgstr ""
+
+msgid "OnDemandScans|Run this scan"
+msgstr ""
+
+msgid "OnDemandScans|Scan mode"
+msgstr ""
+
msgid "OnDemandScans|Schedule or run scans immediately against target sites. Currently available on-demand scan type: DAST. %{helpLinkStart}More information%{helpLinkEnd}"
msgstr ""
-msgid "Onboarding"
+msgid "OnDemandScans|Target URL"
msgstr ""
msgid "Once imported, repositories can be mirrored over SSH. Read more %{link_start}here%{link_end}."
@@ -15167,6 +15667,9 @@ msgstr ""
msgid "One or more of you personal access tokens were revoked"
msgstr ""
+msgid "One or more of your %{provider} projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
+msgstr ""
+
msgid "One or more of your Bitbucket projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
msgstr ""
@@ -15668,6 +16171,9 @@ msgstr ""
msgid "Parent epic is not present."
msgstr ""
+msgid "Parsing error for param :embed_json. %{message}"
+msgstr ""
+
msgid "Part of merge request changes"
msgstr ""
@@ -15680,6 +16186,9 @@ msgstr ""
msgid "Passed"
msgstr ""
+msgid "Passed on"
+msgstr ""
+
msgid "Password"
msgstr ""
@@ -15764,6 +16273,9 @@ msgstr ""
msgid "Perform common operations on GitLab project"
msgstr ""
+msgid "Performance and resource management"
+msgstr ""
+
msgid "Performance optimization"
msgstr ""
@@ -15971,7 +16483,7 @@ msgstr ""
msgid "Pipelines|Something went wrong while cleaning runners cache."
msgstr ""
-msgid "Pipelines|There are currently no %{scope} pipelines."
+msgid "Pipelines|There are currently no finished pipelines."
msgstr ""
msgid "Pipelines|There are currently no pipelines."
@@ -16082,6 +16594,9 @@ msgstr ""
msgid "Pipeline|Stop pipeline #%{pipelineId}?"
msgstr ""
+msgid "Pipeline|Tag name"
+msgstr ""
+
msgid "Pipeline|Trigger author"
msgstr ""
@@ -16163,12 +16678,18 @@ msgstr ""
msgid "Please check your email (%{email}) to verify that you own this address and unlock the power of CI/CD. Didn't receive it? %{resend_link}. Wrong email address? %{update_link}."
msgstr ""
+msgid "Please choose a file"
+msgstr ""
+
msgid "Please choose a group URL with no special characters."
msgstr ""
msgid "Please complete your profile with email address"
msgstr ""
+msgid "Please convert %{linkStart}them to Git%{linkEnd}, and go through the %{linkToImportFlow} again."
+msgstr ""
+
msgid "Please convert them to %{link_to_git}, and go through the %{link_to_import_flow} again."
msgstr ""
@@ -16226,6 +16747,9 @@ msgstr ""
msgid "Please provide a valid email address."
msgstr ""
+msgid "Please provide attributes to update"
+msgstr ""
+
msgid "Please refer to <a href=\"%{docs_url}\">%{docs_url}</a>"
msgstr ""
@@ -16961,6 +17485,9 @@ msgstr ""
msgid "Project has too many %{label_for_message} to search"
msgstr ""
+msgid "Project is required when cluster_type is :project"
+msgstr ""
+
msgid "Project members"
msgstr ""
@@ -17078,7 +17605,40 @@ msgstr ""
msgid "ProjectService|Comment"
msgstr ""
-msgid "ProjectService|Comment will be posted on each event"
+msgid "ProjectService|Event will be triggered by a push to the repository"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a commit is created/updated"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a confidential issue is created/updated/closed"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a deployment finishes"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a merge request is created/updated/merged"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a new tag is pushed to the repository"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a new, unique alert is recorded"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a pipeline status changes"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a wiki page is created/updated"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when an issue is created/updated/closed"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when someone adds a comment"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when someone adds a comment on a confidential issue"
msgstr ""
msgid "ProjectService|Perform common operations on GitLab project: %{project_name}"
@@ -17129,6 +17689,9 @@ msgstr ""
msgid "ProjectSettings|Enable 'Delete source branch' option by default"
msgstr ""
+msgid "ProjectSettings|Enable merge trains and pipelines for merged results"
+msgstr ""
+
msgid "ProjectSettings|Every merge creates a merge commit"
msgstr ""
@@ -17159,7 +17722,7 @@ msgstr ""
msgid "ProjectSettings|Forks"
msgstr ""
-msgid "ProjectSettings|Git Large File Storage"
+msgid "ProjectSettings|Git Large File Storage (LFS)"
msgstr ""
msgid "ProjectSettings|Internal"
@@ -17195,9 +17758,6 @@ msgstr ""
msgid "ProjectSettings|Merge options"
msgstr ""
-msgid "ProjectSettings|Merge pipelines will try to validate the post-merge result prior to merging"
-msgstr ""
-
msgid "ProjectSettings|Merge requests"
msgstr ""
@@ -17225,6 +17785,9 @@ msgstr ""
msgid "ProjectSettings|Pipelines"
msgstr ""
+msgid "ProjectSettings|Pipelines for merge requests must be enabled in the CI/CD configuration file, or pipelines could be unresolvable or dropped"
+msgstr ""
+
msgid "ProjectSettings|Pipelines must succeed"
msgstr ""
@@ -17252,6 +17815,9 @@ msgstr ""
msgid "ProjectSettings|Show link to create/view merge request when pushing from the command line"
msgstr ""
+msgid "ProjectSettings|Skipped pipelines are considered successful"
+msgstr ""
+
msgid "ProjectSettings|Snippets"
msgstr ""
@@ -17267,6 +17833,9 @@ msgstr ""
msgid "ProjectSettings|These checks must pass before merge requests can be merged"
msgstr ""
+msgid "ProjectSettings|This introduces the risk of merging changes that will not pass the pipeline."
+msgstr ""
+
msgid "ProjectSettings|This setting is applied on the server level and can be overridden by an admin."
msgstr ""
@@ -17444,15 +18013,27 @@ msgstr ""
msgid "ProjectsNew|Blank project"
msgstr ""
+msgid "ProjectsNew|Connect your external repository to GitLab CI/CD."
+msgstr ""
+
msgid "ProjectsNew|Contact an administrator to enable options for importing your project."
msgstr ""
msgid "ProjectsNew|Create"
msgstr ""
+msgid "ProjectsNew|Create a blank project to house your files, plan your work, and collaborate on code, among other things."
+msgstr ""
+
+msgid "ProjectsNew|Create blank project"
+msgstr ""
+
msgid "ProjectsNew|Create from template"
msgstr ""
+msgid "ProjectsNew|Create new project"
+msgstr ""
+
msgid "ProjectsNew|Creating project & repository."
msgstr ""
@@ -17477,6 +18058,9 @@ msgstr ""
msgid "ProjectsNew|Project description %{tag_start}(optional)%{tag_end}"
msgstr ""
+msgid "ProjectsNew|Run CI/CD for external repository"
+msgstr ""
+
msgid "ProjectsNew|Template"
msgstr ""
@@ -17738,6 +18322,9 @@ msgstr ""
msgid "Protected Branch"
msgstr ""
+msgid "Protected Branches"
+msgstr ""
+
msgid "Protected Environment"
msgstr ""
@@ -17750,6 +18337,9 @@ msgstr ""
msgid "Protected Tag"
msgstr ""
+msgid "Protected Tags"
+msgstr ""
+
msgid "Protected branches"
msgstr ""
@@ -17849,6 +18439,9 @@ msgstr ""
msgid "Provider"
msgstr ""
+msgid "Proxy support for this API is not available currently"
+msgstr ""
+
msgid "Pseudonymizer data collection"
msgstr ""
@@ -17873,6 +18466,9 @@ msgstr ""
msgid "Publish to status page"
msgstr ""
+msgid "Published on status page"
+msgstr ""
+
msgid "Publishes this issue to the associated status page."
msgstr ""
@@ -18047,6 +18643,9 @@ msgstr ""
msgid "Recipe"
msgstr ""
+msgid "Reconfigure"
+msgstr ""
+
msgid "Recover hidden stage"
msgstr ""
@@ -18133,6 +18732,9 @@ msgstr ""
msgid "Registration|Your profile"
msgstr ""
+msgid "Regulate approvals by authors/committers, based on compliance frameworks. Can be changed only at the instance level."
+msgstr ""
+
msgid "Rejected (closed)"
msgstr ""
@@ -18183,15 +18785,24 @@ msgstr ""
msgid "ReleaseAssetLinkType|Image"
msgstr ""
+msgid "ReleaseAssetLinkType|Images"
+msgstr ""
+
msgid "ReleaseAssetLinkType|Other"
msgstr ""
msgid "ReleaseAssetLinkType|Package"
msgstr ""
+msgid "ReleaseAssetLinkType|Packages"
+msgstr ""
+
msgid "ReleaseAssetLinkType|Runbook"
msgstr ""
+msgid "ReleaseAssetLinkType|Runbooks"
+msgstr ""
+
msgid "Releases"
msgstr ""
@@ -18204,6 +18815,9 @@ msgstr ""
msgid "Releases documentation"
msgstr ""
+msgid "Releases|New Release"
+msgstr ""
+
msgid "Release|Something went wrong while getting the release details"
msgstr ""
@@ -18276,6 +18890,9 @@ msgstr ""
msgid "Remove fork relationship"
msgstr ""
+msgid "Remove from batch"
+msgstr ""
+
msgid "Remove from board"
msgstr ""
@@ -18465,6 +19082,12 @@ msgstr ""
msgid "Replication"
msgstr ""
+msgid "Replication enabled"
+msgstr ""
+
+msgid "Replication paused"
+msgstr ""
+
msgid "Reply by email"
msgstr ""
@@ -18492,15 +19115,15 @@ msgstr ""
msgid "Reported %{timeAgo} by %{reportedBy}"
msgstr ""
-msgid "Reported Resource Changes: %{addNum} to add, %{changeNum} to change, %{deleteNum} to delete"
-msgstr ""
-
msgid "Reporter"
msgstr ""
msgid "Reporting"
msgstr ""
+msgid "Reports"
+msgstr ""
+
msgid "Reports|%{combinedString} and %{resolvedString}"
msgstr ""
@@ -18539,6 +19162,9 @@ msgstr ""
msgid "Reports|Failure"
msgstr ""
+msgid "Reports|Identifier"
+msgstr ""
+
msgid "Reports|Metrics reports are loading"
msgstr ""
@@ -18551,6 +19177,9 @@ msgstr ""
msgid "Reports|Metrics reports failed loading results"
msgstr ""
+msgid "Reports|Scanner"
+msgstr ""
+
msgid "Reports|Severity"
msgstr ""
@@ -18599,15 +19228,33 @@ msgstr ""
msgid "Repository cleanup has started. You will receive an email once the cleanup operation is complete."
msgstr ""
+msgid "Repository files count over the limit"
+msgstr ""
+
+msgid "Repository has an invalid default branch name."
+msgstr ""
+
+msgid "Repository has more than one branch."
+msgstr ""
+
msgid "Repository has no locks."
msgstr ""
+msgid "Repository has tags."
+msgstr ""
+
msgid "Repository maintenance"
msgstr ""
msgid "Repository mirroring"
msgstr ""
+msgid "Repository must contain at least 1 file."
+msgstr ""
+
+msgid "Repository size is above the limit."
+msgstr ""
+
msgid "Repository static objects"
msgstr ""
@@ -18617,7 +19264,7 @@ msgstr ""
msgid "Repository sync capacity"
msgstr ""
-msgid "Repository: %{counter_repositories} / Wikis: %{counter_wikis} / Build Artifacts: %{counter_build_artifacts} / LFS: %{counter_lfs_objects}"
+msgid "Repository: %{counter_repositories} / Wikis: %{counter_wikis} / Build Artifacts: %{counter_build_artifacts} / LFS: %{counter_lfs_objects} / Snippets: %{counter_snippets}"
msgstr ""
msgid "RepositorySettingsAccessLevel|Select"
@@ -18629,6 +19276,9 @@ msgstr ""
msgid "Request Headers"
msgstr ""
+msgid "Request details"
+msgstr ""
+
msgid "Request parameter %{param} is missing."
msgstr ""
@@ -18735,6 +19385,9 @@ msgstr ""
msgid "Reset template"
msgstr ""
+msgid "Reset to project defaults"
+msgstr ""
+
msgid "Resetting the authorization key for this project will require updating the authorization key in every alert source it is enabled in."
msgstr ""
@@ -18831,9 +19484,6 @@ msgstr ""
msgid "Resume"
msgstr ""
-msgid "Resume replication"
-msgstr ""
-
msgid "Resync"
msgstr ""
@@ -19456,6 +20106,9 @@ msgstr ""
msgid "SecurityReports|False positive"
msgstr ""
+msgid "SecurityReports|Group Security Dashboard"
+msgstr ""
+
msgid "SecurityReports|Hide dismissed"
msgstr ""
@@ -19510,10 +20163,13 @@ msgstr ""
msgid "SecurityReports|Remove project from dashboard"
msgstr ""
-msgid "SecurityReports|Report type"
+msgid "SecurityReports|Return to dashboard"
msgstr ""
-msgid "SecurityReports|Return to dashboard"
+msgid "SecurityReports|Scan details"
+msgstr ""
+
+msgid "SecurityReports|Scanner type"
msgstr ""
msgid "SecurityReports|Security Dashboard"
@@ -19570,6 +20226,9 @@ msgstr ""
msgid "SecurityReports|There was an error while generating the report."
msgstr ""
+msgid "SecurityReports|Unable to add %{invalidProjectsMessage}"
+msgstr ""
+
msgid "SecurityReports|Unable to add %{invalidProjects}"
msgstr ""
@@ -19684,6 +20343,9 @@ msgstr ""
msgid "Select due date"
msgstr ""
+msgid "Select file"
+msgstr ""
+
msgid "Select group or project"
msgstr ""
@@ -19747,9 +20409,6 @@ msgstr ""
msgid "Select the branch you want to set as the default for this project. All merge requests and commits will automatically be made against this branch unless you specify a different one."
msgstr ""
-msgid "Select the configured storage available for new repositories to be placed on."
-msgstr ""
-
msgid "Select the custom project template source group."
msgstr ""
@@ -19942,12 +20601,6 @@ msgstr ""
msgid "Service Desk is enabled but not yet active"
msgstr ""
-msgid "Service Desk is off"
-msgstr ""
-
-msgid "Service Desk is on"
-msgstr ""
-
msgid "Service Templates"
msgstr ""
@@ -20035,6 +20688,15 @@ msgstr ""
msgid "Set the milestone to %{milestone_reference}."
msgstr ""
+msgid "Set the number of concurrent requests this secondary node will make to the primary node while backfilling."
+msgstr ""
+
+msgid "Set the synchronization and verification capacity for the secondary node."
+msgstr ""
+
+msgid "Set the timeout in seconds to send a secondary node status to the primary and IPs allowed for the secondary nodes."
+msgstr ""
+
msgid "Set time estimate"
msgstr ""
@@ -20074,6 +20736,9 @@ msgstr ""
msgid "Set weight to %{weight}."
msgstr ""
+msgid "Set what should be replicated by choosing specific projects or groups by the secondary node."
+msgstr ""
+
msgid "SetPasswordToCloneLink|set a password"
msgstr ""
@@ -20200,6 +20865,9 @@ msgstr ""
msgid "Show complete raw log"
msgstr ""
+msgid "Show details"
+msgstr ""
+
msgid "Show file browser"
msgstr ""
@@ -20209,10 +20877,13 @@ msgstr ""
msgid "Show latest version"
msgstr ""
+msgid "Show list"
+msgstr ""
+
msgid "Show me everything"
msgstr ""
-msgid "Show me how"
+msgid "Show me how to add a pipeline"
msgstr ""
msgid "Show me more advanced stuff"
@@ -20261,9 +20932,6 @@ msgstr ""
msgid "Showing all issues"
msgstr ""
-msgid "Showing all labels"
-msgstr ""
-
msgid "Showing last %{size} of log -"
msgstr ""
@@ -20375,9 +21043,6 @@ msgstr ""
msgid "Skip outdated deployment jobs"
msgstr ""
-msgid "Skip this for now"
-msgstr ""
-
msgid "Skipped"
msgstr ""
@@ -20522,6 +21187,9 @@ msgstr ""
msgid "Something went wrong while adding your award. Please try again."
msgstr ""
+msgid "Something went wrong while applying the batch of suggestions. Please try again."
+msgstr ""
+
msgid "Something went wrong while applying the suggestion. Please try again."
msgstr ""
@@ -20624,10 +21292,13 @@ msgstr ""
msgid "Something went wrong while updating a requirement."
msgstr ""
+msgid "Something went wrong while updating assignees"
+msgstr ""
+
msgid "Something went wrong while updating your list settings"
msgstr ""
-msgid "Something went wrong with your automatic subscription renewal"
+msgid "Something went wrong with your automatic subscription renewal."
msgstr ""
msgid "Something went wrong, unable to add %{project} to dashboard"
@@ -20645,6 +21316,9 @@ msgstr ""
msgid "Something went wrong, unable to search projects"
msgstr ""
+msgid "Something went wrong."
+msgstr ""
+
msgid "Something went wrong. Please try again."
msgstr ""
@@ -20828,6 +21502,9 @@ msgstr ""
msgid "Source code"
msgstr ""
+msgid "Source code (%{fileExtension})"
+msgstr ""
+
msgid "Source is not available"
msgstr ""
@@ -20912,6 +21589,9 @@ msgstr ""
msgid "Stack trace"
msgstr ""
+msgid "Stacktrace snippet"
+msgstr ""
+
msgid "Stage"
msgstr ""
@@ -21017,6 +21697,9 @@ msgstr ""
msgid "Start thread & reopen %{noteable_name}"
msgstr ""
+msgid "Start using Directed Acyclic Graphs (DAG)"
+msgstr ""
+
msgid "Start your Free Gold Trial"
msgstr ""
@@ -21479,6 +22162,21 @@ msgstr ""
msgid "SuggestedColors|Very pale orange"
msgstr ""
+msgid "Suggestion is not applicable as the suggestion was not found."
+msgstr ""
+
+msgid "Suggestions are not applicable as one or more suggestions were not found."
+msgstr ""
+
+msgid "Suggestions are not applicable as their lines cannot overlap."
+msgstr ""
+
+msgid "Suggestions must all be on the same branch."
+msgstr ""
+
+msgid "Suggestions that change line count can't be added to batches, yet."
+msgstr ""
+
msgid "Suggestions:"
msgstr ""
@@ -21521,9 +22219,6 @@ msgstr ""
msgid "Synchronization disabled"
msgstr ""
-msgid "Synchronization of container repositories is disabled."
-msgstr ""
-
msgid "System"
msgstr ""
@@ -21704,6 +22399,34 @@ msgstr ""
msgid "Terms of Service and Privacy Policy"
msgstr ""
+msgid "Terraform|%{number} Terraform report failed to generate"
+msgid_plural "Terraform|%{number} Terraform reports failed to generate"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Terraform|%{number} Terraform report was generated in your pipelines"
+msgid_plural "Terraform|%{number} Terraform reports were generated in your pipelines"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Terraform|A Terraform report failed to generate."
+msgstr ""
+
+msgid "Terraform|A Terraform report was generated in your pipelines."
+msgstr ""
+
+msgid "Terraform|Generating the report caused an error."
+msgstr ""
+
+msgid "Terraform|Reported Resource Changes: %{addNum} to add, %{changeNum} to change, %{deleteNum} to delete"
+msgstr ""
+
+msgid "Terraform|The Terraform report %{name} failed to generate."
+msgstr ""
+
+msgid "Terraform|The Terraform report %{name} was generated in your pipelines."
+msgstr ""
+
msgid "Test"
msgstr ""
@@ -21730,7 +22453,7 @@ msgstr ""
msgid "TestHooks|Ensure the project has CI pipelines."
msgstr ""
-msgid "TestHooks|Ensure the project has at least one commit."
+msgid "TestHooks|Ensure the project has deployments."
msgstr ""
msgid "TestHooks|Ensure the project has issues."
@@ -21831,7 +22554,7 @@ msgstr ""
msgid "The Prometheus server responded with \"bad request\". Please check your queries are correct and are supported in your Prometheus version. %{documentationLink}"
msgstr ""
-msgid "The URL defined on the primary node that secondary nodes should use to contact it. Defaults to URL"
+msgid "The URL defined on the primary node that secondary nodes should use to contact it."
msgstr ""
msgid "The URL to use for connecting to Elasticsearch. Use a comma-separated list to support clustering (e.g., \"http://localhost:9200, http://localhost:9201\")."
@@ -21840,6 +22563,9 @@ msgstr ""
msgid "The X509 Certificate to use when mutual TLS is required to communicate with the external authorization service. If left blank, the server certificate is still validated when accessing over HTTPS."
msgstr ""
+msgid "The above settings apply to all projects with the selected compliance framework(s)."
+msgstr ""
+
msgid "The amount of seconds after which a request to get a secondary node status will time out."
msgstr ""
@@ -22139,9 +22865,6 @@ msgstr ""
msgid "The total stage shows the time it takes between creating an issue and deploying the code to production. The data will be automatically added once you have completed the full idea to production cycle."
msgstr ""
-msgid "The unique identifier for the Geo node. Must match %{geoNodeName} if it is set in gitlab.rb, otherwise it must match %{externalUrl} with a trailing slash"
-msgstr ""
-
msgid "The update action will time out after %{number_of_minutes} minutes. For big repositories, use a clone/push combination."
msgstr ""
@@ -22166,9 +22889,6 @@ msgstr ""
msgid "The user you are trying to deactivate has been active in the past %{minimum_inactive_days} days and cannot be deactivated"
msgstr ""
-msgid "The user-facing URL of the Geo node"
-msgstr ""
-
msgid "The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6."
msgstr ""
@@ -22259,6 +22979,9 @@ msgstr ""
msgid "There was a problem fetching project branches."
msgstr ""
+msgid "There was a problem fetching project tags."
+msgstr ""
+
msgid "There was a problem fetching project users."
msgstr ""
@@ -22310,6 +23033,9 @@ msgstr ""
msgid "There was an error fetching the %{replicableType}"
msgstr ""
+msgid "There was an error fetching the Geo Settings"
+msgstr ""
+
msgid "There was an error fetching the Node's Groups"
msgstr ""
@@ -22430,6 +23156,12 @@ msgstr ""
msgid "This %{issuable} is locked. Only <strong>project members</strong> can comment."
msgstr ""
+msgid "This %{noteableTypeText} is %{confidentialLinkStart}confidential%{linkEnd} and %{lockedLinkStart}locked%{linkEnd}."
+msgstr ""
+
+msgid "This %{noteableTypeText} is locked."
+msgstr ""
+
msgid "This %{viewer} could not be displayed because %{reason}. You can %{options} instead."
msgstr ""
@@ -22448,6 +23180,9 @@ msgstr ""
msgid "This action can lead to data loss. To prevent accidental actions we ask you to confirm your intention."
msgstr ""
+msgid "This also resolves all related threads"
+msgstr ""
+
msgid "This also resolves the discussion"
msgstr ""
@@ -22538,6 +23273,9 @@ msgstr ""
msgid "This epic does not exist or you don't have sufficient permission."
msgstr ""
+msgid "This feature is currently in beta. We invite you to %{linkStart}give feedback%{linkEnd}."
+msgstr ""
+
msgid "This feature requires local storage to be enabled"
msgstr ""
@@ -22571,7 +23309,7 @@ msgstr ""
msgid "This is a Work in Progress"
msgstr ""
-msgid "This is a confidential issue."
+msgid "This is a confidential %{noteableTypeText}."
msgstr ""
msgid "This is a delayed job to run in %{remainingTime}"
@@ -22598,9 +23336,6 @@ msgstr ""
msgid "This is your current session"
msgstr ""
-msgid "This issue is %{confidentialLinkStart}confidential%{linkEnd} and %{lockedLinkStart}locked%{linkEnd}."
-msgstr ""
-
msgid "This issue is confidential"
msgstr ""
@@ -22610,9 +23345,6 @@ msgstr ""
msgid "This issue is in a child epic of the filtered epic"
msgstr ""
-msgid "This issue is locked."
-msgstr ""
-
msgid "This job depends on other jobs with expired/erased artifacts: %{invalid_dependencies}"
msgstr ""
@@ -22712,6 +23444,9 @@ msgstr ""
msgid "This license has already expired."
msgstr ""
+msgid "This link points to external content"
+msgstr ""
+
msgid "This may expose confidential information as the selected fork is in another namespace that can have other members."
msgstr ""
@@ -22739,6 +23474,9 @@ msgstr ""
msgid "This page will be removed in a future release."
msgstr ""
+msgid "This pipeline does not use the %{codeStart}needs%{codeEnd} keyword and can't be represented as a directed acyclic graph."
+msgstr ""
+
msgid "This pipeline makes use of a predefined CI/CD configuration enabled by %{strongStart}Auto DevOps.%{strongEnd}"
msgstr ""
@@ -22967,6 +23705,9 @@ msgstr ""
msgid "Time from last commit to merge"
msgstr ""
+msgid "Time in seconds"
+msgstr ""
+
msgid "Time in seconds GitLab will wait for a response from the external service. When the service does not respond in time, access will be denied."
msgstr ""
@@ -23388,9 +24129,15 @@ msgstr ""
msgid "Total artifacts size: %{total_size}"
msgstr ""
+msgid "Total cores (CPUs)"
+msgstr ""
+
msgid "Total issues"
msgstr ""
+msgid "Total memory (GB)"
+msgstr ""
+
msgid "Total test time for all commits/merges"
msgstr ""
@@ -23592,6 +24339,9 @@ msgstr ""
msgid "U2F only works with HTTPS-enabled websites. Contact your administrator for more details."
msgstr ""
+msgid "UI Development Kit"
+msgstr ""
+
msgid "URL"
msgstr ""
@@ -23664,9 +24414,6 @@ msgstr ""
msgid "Unable to load the merge request widget. Try reloading the page."
msgstr ""
-msgid "Unable to process group import file"
-msgstr ""
-
msgid "Unable to resolve"
msgstr ""
@@ -23682,6 +24429,9 @@ msgstr ""
msgid "Unable to sign you in to the group with SAML due to \"%{reason}\""
msgstr ""
+msgid "Unable to suggest a path. Please refresh and try again."
+msgstr ""
+
msgid "Unable to update label prioritization at this time"
msgstr ""
@@ -23697,6 +24447,9 @@ msgstr ""
msgid "Unarchiving the project will restore people's ability to make changes to it. The repository can be committed to, and issues, comments, and other entities can be created. %{strong_start}Once active, this project shows up in the search and on the dashboard.%{strong_end}"
msgstr ""
+msgid "Unassign from commenting user"
+msgstr ""
+
msgid "Unblock"
msgstr ""
@@ -23772,6 +24525,9 @@ msgstr ""
msgid "Unreachable"
msgstr ""
+msgid "Unrecognized cluster type"
+msgstr ""
+
msgid "Unresolve"
msgstr ""
@@ -23832,6 +24588,9 @@ msgstr ""
msgid "Unsubscribes from this %{quick_action_target}."
msgstr ""
+msgid "Unsupported todo type passed. Supported todo types are: %{todo_types}"
+msgstr ""
+
msgid "Until"
msgstr ""
@@ -23859,6 +24618,9 @@ msgstr ""
msgid "Update approval rule"
msgstr ""
+msgid "Update approvers"
+msgstr ""
+
msgid "Update failed"
msgstr ""
@@ -23868,6 +24630,9 @@ msgstr ""
msgid "Update it"
msgstr ""
+msgid "Update iteration"
+msgstr ""
+
msgid "Update now"
msgstr ""
@@ -23913,7 +24678,7 @@ msgstr ""
msgid "Updated at"
msgstr ""
-msgid "Updated to"
+msgid "Updated to %{linkStart}chart v%{linkEnd}"
msgstr ""
msgid "Updating"
@@ -23943,6 +24708,9 @@ msgstr ""
msgid "Upload CSV file"
msgstr ""
+msgid "Upload License"
+msgstr ""
+
msgid "Upload New File"
msgstr ""
@@ -24024,16 +24792,19 @@ msgstr ""
msgid "UsageQuota|Pipelines"
msgstr ""
+msgid "UsageQuota|Purchase more storage"
+msgstr ""
+
msgid "UsageQuota|Repositories"
msgstr ""
msgid "UsageQuota|Repository"
msgstr ""
-msgid "UsageQuota|Storage"
+msgid "UsageQuota|Snippets"
msgstr ""
-msgid "UsageQuota|Storage usage:"
+msgid "UsageQuota|Storage"
msgstr ""
msgid "UsageQuota|This namespace has no projects which use shared runners"
@@ -24066,6 +24837,12 @@ msgstr ""
msgid "UsageQuota|Wikis"
msgstr ""
+msgid "UsageQuota|You used: %{usage} %{limit}"
+msgstr ""
+
+msgid "UsageQuota|out of %{formattedLimit} of your namespace storage"
+msgstr ""
+
msgid "Use %{code_start}::%{code_end} to create a %{link_start}scoped label set%{link_end} (eg. %{code_start}priority::1%{code_end})"
msgstr ""
@@ -24132,6 +24909,9 @@ msgstr ""
msgid "User IDs"
msgstr ""
+msgid "User Lists can only be created and modified with %{linkStart}the API%{linkEnd}"
+msgstr ""
+
msgid "User OAuth applications"
msgstr ""
@@ -24156,6 +24936,9 @@ msgstr ""
msgid "User key was successfully removed."
msgstr ""
+msgid "User list %{name} will be removed. Are you sure?"
+msgstr ""
+
msgid "User map"
msgstr ""
@@ -24177,148 +24960,10 @@ msgstr ""
msgid "User was successfully updated."
msgstr ""
-msgid "UserOnboardingTour|%{activeTour}/%{totalTours}"
-msgstr ""
-
-msgid "UserOnboardingTour|%{completed}/%{total} steps completed"
-msgstr ""
-
-msgid "UserOnboardingTour|%{emphasisStart}Well done!%{emphasisEnd}%{lineBreak}%{lineBreak}That's it for our guided tour, congratulations for making it all the way to the end!%{lineBreak}%{lineBreak}We hope this gave you a good overview of GitLab and how it can help you. We'll now show you how to create your own project and invite your colleagues."
-msgstr ""
-
-msgid "UserOnboardingTour|Adding other members to a project is done through Project Settings. Click on %{emphasisStart}Settings%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Alright, that's it for Commits. Let's take a look at the %{emphasisStart}Branches%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Awesome! Now click on %{emphasisStart}Members%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Click on one of the %{emphasisStart}Compare%{emphasisEnd} buttons to compare a branch to master."
-msgstr ""
-
-msgid "UserOnboardingTour|Click on one of the %{emphasisStart}pipeline IDs%{emphasisEnd} to see the details of a pipeline."
-msgstr ""
-
-msgid "UserOnboardingTour|Click to open the latest commit to see its details."
-msgstr ""
-
-msgid "UserOnboardingTour|Close 'Learn GitLab'"
-msgstr ""
-
-msgid "UserOnboardingTour|Commits are shown in chronological order and can be filtered by the commit message or by the branch."
+msgid "UserList|Delete %{name}?"
msgstr ""
-msgid "UserOnboardingTour|Create a project"
-msgstr ""
-
-msgid "UserOnboardingTour|Exit 'Learn GitLab'"
-msgstr ""
-
-msgid "UserOnboardingTour|Got it"
-msgstr ""
-
-msgid "UserOnboardingTour|Great job! %{clapHands} We hope the tour was helpful and that you learned how to use GitLab.%{lineBreak}%{lineBreak}We'd love to get your feedback on this tour.%{lineBreak}%{lineBreak}%{emphasisStart}How helpful would you say this guided tour was?%{emphasisEnd}%{lineBreak}%{lineBreak}"
-msgstr ""
-
-msgid "UserOnboardingTour|Guided GitLab Tour"
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can compare the changes of this branch to another one. Changes are divided by files so that it's easier to see what was changed where."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can create a project from scratch, start with a template or import a repository from other platforms. Whatever you choose, we'll guide you through the process.%{lineBreak}%{lineBreak}Fill in your new project information and click on %{emphasisStart}Create Project%{emphasisEnd} to progress to the next step."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can see the breakdown of the pipelines: its stages and jobs in each of the stages and their status.%{lineBreak}%{lineBreak}Our CI/CD pipelines are quite complex, most of our users have fewer and simpler pipelines."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can see the current members of the project (just you at the moment) and invite new members.%{lineBreak}%{lineBreak}You can invite multiple members at once (existing GitLab users or invite by email) and you can also set their roles and permissions.%{lineBreak}%{lineBreak}Add a few members and click on %{emphasisStart}Add to project%{emphasisEnd} to complete this step."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can see what changes were made with this commit, on what branch and if there's a related merge request. The status of the pipeline will also show up if CI/CD is set up.%{lineBreak}%{lineBreak}You can also comment on the lines of code that were changed and start a discussion with your colleagues!"
-msgstr ""
-
-msgid "UserOnboardingTour|Here's an overview of branches in the %{emphasisStart}%{projectName}%{emphasisEnd} project. They're split into Active and Stale.%{lineBreak}%{lineBreak}From here, you can create a new merge request from a branch, or compare the branch to any other branch in the project. By default, it will compare it to the master branch."
-msgstr ""
-
-msgid "UserOnboardingTour|Invite colleagues"
-msgstr ""
-
-msgid "UserOnboardingTour|Issues are great for communicating and keeping track of progress in GitLab. These are all issues that are open in the %{emphasisStart}%{projectName}%{emphasisEnd}.%{lineBreak}%{lineBreak}You can help us improve GitLab by contributing work to issues that are labeled <span class=\"badge color-label accept-mr-label\">Accepting merge requests</span>.%{lineBreak}%{lineBreak}This list can be filtered by labels, milestones, assignees, authors... We'll show you how it looks when the list is filtered by a label."
-msgstr ""
-
-msgid "UserOnboardingTour|Learn GitLab"
-msgstr ""
-
-msgid "UserOnboardingTour|Let's take a closer look at a merge request. Click on the title of one."
-msgstr ""
-
-msgid "UserOnboardingTour|Let's take a closer look at all the commits. Click on %{emphasisStart}Commits%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Let's take a closer look at the repository of this project. Click on %{emphasisStart}Repository%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|No thanks"
-msgstr ""
-
-msgid "UserOnboardingTour|Ok, let's go"
-msgstr ""
-
-msgid "UserOnboardingTour|Ok, show me"
-msgstr ""
-
-msgid "UserOnboardingTour|Open one of the issues by clicking on its title."
-msgstr ""
-
-msgid "UserOnboardingTour|Restart this step"
-msgstr ""
-
-msgid "UserOnboardingTour|Skip this step"
-msgstr ""
-
-msgid "UserOnboardingTour|Sweet! Your project was created and is ready to be used.%{lineBreak}%{lineBreak}You can start adding files to the repository or clone it. One last thing we want to show you is how to invite your colleagues to your new project."
-msgstr ""
-
-msgid "UserOnboardingTour|Take a look. Here's a nifty menu for quickly creating issues, merge requests, snippets, projects and groups. Click on it and select \"New project\" from the \"GitLab\" section to get started."
-msgstr ""
-
-msgid "UserOnboardingTour|Thanks for taking the guided tour. Remember, if you want to go through it again, you can start %{emphasisStart}Learn GitLab%{emphasisEnd} in the help menu on the top right."
-msgstr ""
-
-msgid "UserOnboardingTour|Thanks for the feedback! %{thumbsUp}"
-msgstr ""
-
-msgid "UserOnboardingTour|That's it for issues. Let'st take a look at %{emphasisStart}Merge Requests%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|That's it for merge requests. Now for the final part of this guided tour - the %{emphasisStart}CI/CD%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|That's it for the Repository. Let's take a look at the %{emphasisStart}Issues%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|The structure of this page is very similar to issues. Status, description, discussion and the sidebar are all here.%{lineBreak}%{lineBreak}But take a look below the description and you'll notice that there's more information about the merge request, the CI/CD pipeline and the options for approving it.%{lineBreak}%{lineBreak}Alongside the discussion you can also see more information about commits in this merge request, the status of pipelines and review all changes that were made."
-msgstr ""
-
-msgid "UserOnboardingTour|There's a lot of information here but don't worry, we'll go through it.%{lineBreak}%{lineBreak}On the top you can see the status of the issue and when it was opened and by whom. Directly below it is the issue description and below that are other %{emphasisStart}related issues%{emphasisEnd} and %{emphasisStart}merge requests%{emphasisEnd} (if any). Then below that is the %{emphasisStart}discussion%{emphasisEnd}, that's where most of the communication happens.%{lineBreak}%{lineBreak}On the right, there's a sidebar where you can view/change the %{emphasisStart}assignee, milestone, due date, labels, weight%{emphasisEnd}, etc."
-msgstr ""
-
-msgid "UserOnboardingTour|These are all the CI/CD pipelines we have for our %{emphasisStart}%{projectName}%{emphasisEnd} project.%{lineBreak}%{lineBreak}Here you can see the status of each pipeline, for what commit it's running for, its stages and the status for them."
-msgstr ""
-
-msgid "UserOnboardingTour|These are all the issues that are available for community contributions. Let's take a closer look at one of them."
-msgstr ""
-
-msgid "UserOnboardingTour|This is an overview of all merge requests in this project. Similarly to the issues overview it can be filtered down by things like labels, milestones, authors, assignees, etc."
-msgstr ""
-
-msgid "UserOnboardingTour|This is the repository for the %{emphasisStart}%{projectName}%{emphasisEnd} project. All our code is stored here. Feel free to explore and take a closer look at folders and files.%{lineBreak}%{lineBreak}Above the file structure you can see the latest commit, who the author is and the status of the CI/CD pipeline.%{lineBreak}%{lineBreak}If you scroll down below the file structure, you'll find the Readme of this project. This is defined in the README.md file at the root of the repository."
-msgstr ""
-
-msgid "UserOnboardingTour|Welcome to the project overview of the %{emphasisStart}%{projectName}%{emphasisEnd} project. This is the project that we use to work on GitLab. At first, a project seems like a simple repository, but at GitLab, a project is so much more.%{lineBreak}%{lineBreak}You can create projects for hosting your codebase, use it as an issue tracker, collaborate on code, and continuously build, test, and deploy your app with built-in GitLab CI/CD."
+msgid "UserList|created %{timeago}"
msgstr ""
msgid "UserProfile|Activity"
@@ -24471,6 +25116,9 @@ msgstr ""
msgid "UsersSelect|Unassigned"
msgstr ""
+msgid "Using %{codeStart}needs%{codeEnd} allows jobs to run before their stage is reached, as soon as their individual dependencies are met, which speeds up your pipelines."
+msgstr ""
+
msgid "Using %{code_start}::%{code_end} denotes a %{link_start}scoped label set%{link_end}"
msgstr ""
@@ -24555,9 +25203,6 @@ msgstr ""
msgid "Versions"
msgstr ""
-msgid "Very helpful"
-msgstr ""
-
msgid "View Documentation"
msgstr ""
@@ -24706,6 +25351,9 @@ msgstr ""
msgid "VisibilityLevel|Unknown"
msgstr ""
+msgid "Visit settings page"
+msgstr ""
+
msgid "VisualReviewApp|%{stepStart}Step 1%{stepEnd}. Copy the following script:"
msgstr ""
@@ -24799,6 +25447,9 @@ msgstr ""
msgid "VulnerabilityManagement|Something went wrong while trying to delete the comment. Please try again later."
msgstr ""
+msgid "VulnerabilityManagement|Something went wrong while trying to refresh the vulnerability. Please try again later."
+msgstr ""
+
msgid "VulnerabilityManagement|Something went wrong while trying to retrieve the vulnerability history. Please try again later."
msgstr ""
@@ -24841,6 +25492,9 @@ msgstr ""
msgid "Vulnerability|Class"
msgstr ""
+msgid "Vulnerability|Crash Address"
+msgstr ""
+
msgid "Vulnerability|Description"
msgstr ""
@@ -24916,9 +25570,6 @@ msgstr ""
msgid "We couldn't reach the Prometheus server. Either the server no longer exists or the configuration details need updating."
msgstr ""
-msgid "We created a short guided tour that will help you learn the basics of GitLab and how it will help you be better at your job. It should only take a couple of minutes. You will be guided by two types of helpers, best recognized by their color."
-msgstr ""
-
msgid "We detected potential spam in the %{humanized_resource_name}. Please solve the reCAPTCHA to proceed."
msgstr ""
@@ -24952,6 +25603,9 @@ msgstr ""
msgid "We've found no vulnerabilities"
msgstr ""
+msgid "Web Application Firewall"
+msgstr ""
+
msgid "Web IDE"
msgstr ""
@@ -25012,9 +25666,6 @@ msgstr ""
msgid "Welcome to GitLab.com<br>@%{name}!"
msgstr ""
-msgid "Welcome to the Guided GitLab Tour"
-msgstr ""
-
msgid "Welcome to the guided GitLab tour"
msgstr ""
@@ -25062,9 +25713,6 @@ msgstr ""
msgid "While it's rare to have no vulnerabilities, it can happen. In any event, we ask that you please double check your settings to make sure you've set up your dashboard correctly."
msgstr ""
-msgid "White helpers give contextual information."
-msgstr ""
-
msgid "Who can be an approver?"
msgstr ""
@@ -25110,12 +25758,18 @@ msgstr ""
msgid "WikiEmptyIssueMessage|Suggest wiki improvement"
msgstr ""
+msgid "WikiEmptyIssueMessage|You must be a group member in order to add wiki pages. If you have suggestions for how to improve the wiki for this group, consider opening an issue in the %{issues_link}."
+msgstr ""
+
msgid "WikiEmptyIssueMessage|You must be a project member in order to add wiki pages. If you have suggestions for how to improve the wiki for this project, consider opening an issue in the %{issues_link}."
msgstr ""
msgid "WikiEmptyIssueMessage|issue tracker"
msgstr ""
+msgid "WikiEmpty|A wiki is where you can store all the details about your group. This can include why you've created it, its principles, how to use it, and so on."
+msgstr ""
+
msgid "WikiEmpty|A wiki is where you can store all the details about your project. This can include why you've created it, its principles, how to use it, and so on."
msgstr ""
@@ -25125,12 +25779,21 @@ msgstr ""
msgid "WikiEmpty|Suggest wiki improvement"
msgstr ""
+msgid "WikiEmpty|The wiki lets you write documentation for your group"
+msgstr ""
+
msgid "WikiEmpty|The wiki lets you write documentation for your project"
msgstr ""
+msgid "WikiEmpty|This group has no wiki pages"
+msgstr ""
+
msgid "WikiEmpty|This project has no wiki pages"
msgstr ""
+msgid "WikiEmpty|You must be a group member in order to add wiki pages."
+msgstr ""
+
msgid "WikiEmpty|You must be a project member in order to add wiki pages."
msgstr ""
@@ -25407,13 +26070,13 @@ msgstr ""
msgid "You can now export your security dashboard to a CSV report."
msgstr ""
-msgid "You can now submit a merge request to get this change into the original branch."
+msgid "You can now manage alert endpoint configuration in the Alerts section on the Operations settings page. Fields on this page have been deprecated."
msgstr ""
-msgid "You can now submit a merge request to get this change into the original project."
+msgid "You can now submit a merge request to get this change into the original branch."
msgstr ""
-msgid "You can only add files when you are on a branch"
+msgid "You can now submit a merge request to get this change into the original project."
msgstr ""
msgid "You can only edit files when you are on a branch"
@@ -25635,7 +26298,7 @@ msgstr ""
msgid "You need to upload a Google Takeout archive."
msgstr ""
-msgid "You reached %{usage_in_percent} of %{namespace_name}'s capacity (%{used_storage} of %{storage_limit})"
+msgid "You reached %{usage_in_percent} of %{namespace_name}'s storage capacity (%{used_storage} of %{storage_limit})"
msgstr ""
msgid "You tried to fork %{link_to_the_project} but it failed for the following reason:"
@@ -25938,13 +26601,13 @@ msgstr ""
msgid "Your subscription expired!"
msgstr ""
-msgid "Your subscription has been downgraded"
+msgid "Your subscription has been downgraded."
msgstr ""
-msgid "Your subscription will automatically renew in %{remaining_days}"
+msgid "Your subscription will automatically renew in %{remaining_days}."
msgstr ""
-msgid "Your subscription will expire in %{remaining_days}"
+msgid "Your subscription will expire in %{remaining_days}."
msgstr ""
msgid "Zoom meeting added"
@@ -26024,9 +26687,6 @@ msgstr ""
msgid "branch name"
msgstr ""
-msgid "build pipeline reference mismatch"
-msgstr ""
-
msgid "by"
msgstr ""
@@ -26045,6 +26705,9 @@ msgstr ""
msgid "cannot block others"
msgstr ""
+msgid "cannot contain HTML/XML tags, including any word between angle brackets (<,>)."
+msgstr ""
+
msgid "cannot include leading slash or directory traversal."
msgstr ""
@@ -26075,55 +26738,6 @@ msgstr ""
msgid "ciReport|%{remainingPackagesCount} more"
msgstr ""
-msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability"
-msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability for the source branch only"
-msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities for the source branch only"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerability"
-msgid_plural "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerabilities"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new vulnerability"
-msgid_plural "ciReport|%{reportType} %{status} detected %{newCount} new vulnerabilities"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities for the source branch only"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{fixedCount} fixed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} vulnerability for the source branch only"
-msgid_plural "ciReport|%{reportType} %{status} detected %{newCount} vulnerabilities for the source branch only"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "ciReport|%{reportType} %{status} detected no new vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected no vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected no vulnerabilities for the source branch only"
-msgstr ""
-
msgid "ciReport|%{reportType} is loading"
msgstr ""
@@ -26142,7 +26756,7 @@ msgstr ""
msgid "ciReport|All projects"
msgstr ""
-msgid "ciReport|All report types"
+msgid "ciReport|All scanner types"
msgstr ""
msgid "ciReport|All severities"
@@ -26166,6 +26780,9 @@ msgstr ""
msgid "ciReport|Container scanning detects known vulnerabilities in your docker images."
msgstr ""
+msgid "ciReport|Coverage Fuzzing"
+msgstr ""
+
msgid "ciReport|Create a merge request to implement this solution, or download and apply the patch manually."
msgstr ""
@@ -26324,6 +26941,9 @@ msgstr ""
msgid "customize"
msgstr ""
+msgid "data"
+msgstr ""
+
msgid "date must not be after 9999-12-31"
msgstr ""
@@ -26394,6 +27014,9 @@ msgstr ""
msgid "entries cannot contain HTML tags"
msgstr ""
+msgid "epic"
+msgstr ""
+
msgid "error"
msgstr ""
@@ -26415,9 +27038,6 @@ msgstr ""
msgid "expires on %{timebox_due_date}"
msgstr ""
-msgid "external_url"
-msgstr ""
-
msgid "failed"
msgstr ""
@@ -26453,9 +27073,6 @@ msgstr ""
msgid "from"
msgstr ""
-msgid "geo_node_name"
-msgstr ""
-
msgid "group"
msgstr ""
@@ -26477,9 +27094,6 @@ msgstr ""
msgid "https://your-bitbucket-server"
msgstr ""
-msgid "image"
-msgstr ""
-
msgid "image diff"
msgstr ""
@@ -26623,6 +27237,9 @@ msgstr ""
msgid "load it anyway"
msgstr ""
+msgid "loading"
+msgstr ""
+
msgid "locked by %{path_lock_user_name} %{created_at}"
msgstr ""
@@ -26751,9 +27368,6 @@ msgstr ""
msgid "mrWidget|Deployment statistics are not available currently"
msgstr ""
-msgid "mrWidget|Detect issues before deployment with a CI pipeline that continuously tests your code. We created a quick guide that will show you how to create one. Make your code more secure and more robust in just a minute."
-msgstr ""
-
msgid "mrWidget|Did not close"
msgstr ""
@@ -26916,6 +27530,9 @@ msgstr ""
msgid "mrWidget|To approve this merge request, please enter your password. This project requires all approvals to be authenticated."
msgstr ""
+msgid "mrWidget|Use %{linkStart}CI pipelines to test your code%{linkEnd}, simply add a GitLab CI configuration file to your project. It only takes a minute to make your code more secure and robust."
+msgstr ""
+
msgid "mrWidget|When this merge request is ready, remove the WIP: prefix from the title to allow it to be merged"
msgstr ""
@@ -26952,6 +27569,9 @@ msgstr ""
msgid "must be greater than start date"
msgstr ""
+msgid "must contain only valid frameworks"
+msgstr ""
+
msgid "my-awesome-group"
msgstr ""
@@ -26964,6 +27584,9 @@ msgstr ""
msgid "needs to be between 10 minutes and 1 month"
msgstr ""
+msgid "never"
+msgstr ""
+
msgid "never expires"
msgstr ""
@@ -26991,6 +27614,9 @@ msgstr ""
msgid "nounSeries|%{firstItem} and %{lastItem}"
msgstr ""
+msgid "nounSeries|%{item}"
+msgstr ""
+
msgid "nounSeries|%{item}, %{nextItem}"
msgstr ""
@@ -27127,6 +27753,9 @@ msgstr ""
msgid "revised"
msgstr ""
+msgid "satisfied"
+msgstr ""
+
msgid "score"
msgstr ""
@@ -27157,6 +27786,9 @@ msgstr ""
msgid "severity|Unknown"
msgstr ""
+msgid "should be an array of %{object_name} objects"
+msgstr ""
+
msgid "should be greater than or equal to %{access} inherited membership from group %{group_name}"
msgstr ""
@@ -27211,10 +27843,13 @@ msgstr ""
msgid "suggestPipeline|2/2: Commit your changes"
msgstr ""
-msgid "suggestPipeline|Commit the changes and your pipeline will automatically run for the first time."
+msgid "suggestPipeline|Choose %{boldStart}Code Quality%{boldEnd} to add a pipeline that tests the quality of your code."
msgstr ""
-msgid "suggestPipeline|We recommend the %{boldStart}Code Quality%{boldEnd} template, which will add a report widget to your Merge Requests. This way you’ll learn about code quality degradations much sooner. %{footerStart} Goodbye technical debt! %{footerEnd}"
+msgid "suggestPipeline|The template is ready! You can now commit it to create your first pipeline."
+msgstr ""
+
+msgid "suggestPipeline|We’re adding a GitLab CI configuration file to add a pipeline to the project. You could create it manually, but we recommend that you start with a GitLab template that works out of the box."
msgstr ""
msgid "syntax is correct"
@@ -27274,6 +27909,9 @@ msgstr ""
msgid "uses Kubernetes clusters to deploy your code!"
msgstr ""
+msgid "v%{version} published %{timeAgo}"
+msgstr ""
+
msgid "verify ownership"
msgstr ""
diff --git a/locale/he_IL/gitlab.po b/locale/he_IL/gitlab.po
index e9d1d2e23c0..a6a067109a8 100644
--- a/locale/he_IL/gitlab.po
+++ b/locale/he_IL/gitlab.po
@@ -2,7 +2,7 @@ msgid ""
msgstr ""
"Project-Id-Version: gitlab-ee\n"
"Report-Msgid-Bugs-To: \n"
-"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Last-Translator: \n"
"Language-Team: Hebrew\n"
"Language: he_IL\n"
"MIME-Version: 1.0\n"
@@ -10,9 +10,11 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=4; plural=n%100==1 ? 0 : n%100==2 ? 1 : n%100==3 || n%100==4 ? 2 : 3;\n"
"X-Crowdin-Project: gitlab-ee\n"
+"X-Crowdin-Project-ID: 288872\n"
"X-Crowdin-Language: he\n"
"X-Crowdin-File: /master/locale/gitlab.pot\n"
-"PO-Revision-Date: 2020-06-08 15:16\n"
+"X-Crowdin-File-ID: 6\n"
+"PO-Revision-Date: 2020-07-02 01:25\n"
msgid " %{start} to %{end}"
msgstr ""
@@ -251,6 +253,13 @@ msgstr[1] ""
msgstr[2] ""
msgstr[3] ""
+msgid "%d milestone"
+msgid_plural "%d milestones"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
msgid "%d minute"
msgid_plural "%d minutes"
msgstr[0] ""
@@ -314,13 +323,6 @@ msgstr[1] ""
msgstr[2] ""
msgstr[3] ""
-msgid "%d url scanned"
-msgid_plural "%d urls scanned"
-msgstr[0] ""
-msgstr[1] ""
-msgstr[2] ""
-msgstr[3] ""
-
msgid "%d vulnerability"
msgid_plural "%d vulnerabilities"
msgstr[0] ""
@@ -412,9 +414,15 @@ msgstr[3] ""
msgid "%{count} related %{pluralized_subject}: %{links}"
msgstr ""
+msgid "%{dashboard_path} could not be found."
+msgstr ""
+
msgid "%{days} days until tags are automatically removed"
msgstr ""
+msgid "%{deployLinkStart}Use a template to deploy to ECS%{deployLinkEnd}, or use a docker image to %{commandsLinkStart}run AWS commands in GitLab CI/CD%{commandsLinkEnd}."
+msgstr ""
+
msgid "%{description}- Sentry event: %{errorUrl}- First seen: %{firstSeen}- Last seen: %{lastSeen} %{countLabel}: %{count}%{userCountLabel}: %{userCount}"
msgstr ""
@@ -457,12 +465,18 @@ msgstr ""
msgid "%{host} sign-in from new location"
msgstr ""
-msgid "%{icon}You are about to add %{usersTag} people to the discussion. Proceed with caution."
+msgid "%{icon}You are about to add %{usersTag} people to the discussion. They will all receive a notification."
+msgstr ""
+
+msgid "%{integrations_link_start}Integrations%{link_end} enable you to make third-party applications part of your GitLab workflow. If the available integrations don't meet your needs, consider using a %{webhooks_link_start}webhook%{link_end}."
msgstr ""
msgid "%{issuableType} will be removed! Are you sure?"
msgstr ""
+msgid "%{issuesCount} issues in this group"
+msgstr ""
+
msgid "%{issuesSize} issues"
msgstr ""
@@ -472,7 +486,7 @@ msgstr ""
msgid "%{labelStart}Class:%{labelEnd} %{class}"
msgstr ""
-msgid "%{labelStart}Confidence:%{labelEnd} %{confidence}"
+msgid "%{labelStart}Evidence:%{labelEnd} %{evidence}"
msgstr ""
msgid "%{labelStart}File:%{labelEnd} %{file}"
@@ -490,6 +504,9 @@ msgstr ""
msgid "%{labelStart}Report Type:%{labelEnd} %{reportType}"
msgstr ""
+msgid "%{labelStart}Scanner:%{labelEnd} %{scanner}"
+msgstr ""
+
msgid "%{labelStart}Severity:%{labelEnd} %{severity}"
msgstr ""
@@ -514,9 +531,6 @@ msgstr ""
msgid "%{link_start}Read more%{link_end} about role permissions"
msgstr ""
-msgid "%{link} can be used for binding events when something is happening within the project."
-msgstr ""
-
msgid "%{listToShow}, and %{awardsListLength} more."
msgstr ""
@@ -550,6 +564,9 @@ msgstr ""
msgid "%{name} found %{resultsString}"
msgstr ""
+msgid "%{name} is already being used for another emoji"
+msgstr ""
+
msgid "%{name} is scheduled for %{action}"
msgstr ""
@@ -599,6 +616,9 @@ msgstr ""
msgid "%{primary} (%{secondary})"
msgstr ""
+msgid "%{ref} cannot be added: %{error}"
+msgstr ""
+
msgid "%{releases} release"
msgid_plural "%{releases} releases"
msgstr[0] ""
@@ -609,13 +629,43 @@ msgstr[3] ""
msgid "%{remaining_approvals} left"
msgstr ""
-msgid "%{retryButtonStart}Try again%{retryButtonEnd} or %{newFileButtonStart}attach a new file%{newFileButtonEnd}"
+msgid "%{reportType} %{status} detected %{critical} critical and %{high} high severity vulnerabilities out of %{total}."
msgstr ""
-msgid "%{screenreaderOnlyStart}Keyboard shorcuts%{screenreaderOnlyEnd} Disabled"
+msgid "%{reportType} %{status} detected %{critical} critical and %{high} high severity vulnerabilities."
msgstr ""
-msgid "%{screenreaderOnlyStart}Keyboard shorcuts%{screenreaderOnlyEnd} Enabled"
+msgid "%{reportType} %{status} detected %{critical} critical severity vulnerabilities out of %{total}."
+msgstr ""
+
+msgid "%{reportType} %{status} detected %{critical} critical severity vulnerability."
+msgid_plural "%{reportType} %{status} detected %{critical} critical severity vulnerabilities."
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "%{reportType} %{status} detected %{high} high severity vulnerabilities out of %{total}."
+msgstr ""
+
+msgid "%{reportType} %{status} detected %{high} high severity vulnerability."
+msgid_plural "%{reportType} %{status} detected %{high} high severity vulnerabilities."
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "%{reportType} %{status} detected %{other} vulnerability."
+msgid_plural "%{reportType} %{status} detected %{other} vulnerabilities."
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "%{reportType} %{status} detected no new vulnerabilities."
+msgstr ""
+
+msgid "%{retryButtonStart}Try again%{retryButtonEnd} or %{newFileButtonStart}attach a new file%{newFileButtonEnd}"
msgstr ""
msgid "%{service_title} %{message}."
@@ -722,6 +772,12 @@ msgstr ""
msgid "%{token}..."
msgstr ""
+msgid "%{totalCpu} (%{freeSpacePercentage}%{percentSymbol} free)"
+msgstr ""
+
+msgid "%{totalMemory} (%{freeSpacePercentage}%{percentSymbol} free)"
+msgstr ""
+
msgid "%{totalWeight} total weight"
msgstr ""
@@ -752,6 +808,12 @@ msgstr ""
msgid "%{verb} %{time_spent_value} spent time."
msgstr ""
+msgid "%{webhooks_link_start}%{webhook_type}%{link_end} enable you to send notifications to web applications in response to events in a group or project."
+msgstr ""
+
+msgid "%{webhooks_link_start}%{webhook_type}%{link_end} enable you to send notifications to web applications in response to events in a group or project. We recommend using an %{integrations_link_start}integration%{link_end} in preference to a webhook."
+msgstr ""
+
msgid "'%{level}' is not a valid visibility level"
msgstr ""
@@ -780,9 +842,6 @@ msgstr ""
msgid "(No changes)"
msgstr ""
-msgid "(Show all)"
-msgstr ""
-
msgid "(check progress)"
msgstr ""
@@ -855,6 +914,9 @@ msgstr ""
msgid "0 for unlimited"
msgstr ""
+msgid "0 for unlimited, only effective with remote storage enabled."
+msgstr ""
+
msgid "1 %{type} addition"
msgid_plural "%{count} %{type} additions"
msgstr[0] ""
@@ -1032,6 +1094,12 @@ msgstr ""
msgid "<code>\"johnsmith@example.com\": \"johnsmith@example.com\"</code> will add \"By <a href=\"#\">johnsmith@example.com</a>\" to all issues and comments originally created by johnsmith@example.com. By default, the email address or username is masked to ensure the user's privacy. Use this option if you want to show the full email address."
msgstr ""
+msgid "<code>Masked</code> to prevent the values from being displayed in job logs (must match certain regexp requirements)."
+msgstr ""
+
+msgid "<code>Protected</code> to expose them to protected branches or tags only."
+msgstr ""
+
msgid "<namespace / project>"
msgstr ""
@@ -1059,9 +1127,6 @@ msgstr ""
msgid "A .NET Core console application template, customizable for any .NET Core project"
msgstr ""
-msgid "A DAG must have two dependent jobs to be visualized on this tab."
-msgstr ""
-
msgid "A GitBook site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
msgstr ""
@@ -1092,6 +1157,12 @@ msgstr ""
msgid "A deleted user"
msgstr ""
+msgid "A file has been changed."
+msgstr ""
+
+msgid "A file was not found."
+msgstr ""
+
msgid "A file with '%{file_name}' already exists in %{branch} branch"
msgstr ""
@@ -1158,7 +1229,7 @@ msgstr ""
msgid "A subscription will trigger a new pipeline on the default branch of this project when a pipeline successfully completes for a new tag on the %{default_branch_docs} of the subscribed project."
msgstr ""
-msgid "A terraform report was generated in your pipelines."
+msgid "A suggestion is not applicable."
msgstr ""
msgid "A user with write access to the source branch selected this option"
@@ -1329,6 +1400,9 @@ msgstr ""
msgid "Action to take when receiving an alert."
msgstr ""
+msgid "Actions"
+msgstr ""
+
msgid "Activate"
msgstr ""
@@ -1387,6 +1461,9 @@ msgstr ""
msgid "Add LICENSE"
msgstr ""
+msgid "Add New Node"
+msgstr ""
+
msgid "Add README"
msgstr ""
@@ -1462,6 +1539,9 @@ msgstr ""
msgid "Add approval rule"
msgstr ""
+msgid "Add approvers"
+msgstr ""
+
msgid "Add bold text"
msgstr "הוספת טקסט מודגש"
@@ -1519,6 +1599,9 @@ msgstr ""
msgid "Add strikethrough text"
msgstr ""
+msgid "Add suggestion to batch"
+msgstr ""
+
msgid "Add system hook"
msgstr ""
@@ -1618,6 +1701,9 @@ msgstr ""
msgid "Admin Note"
msgstr ""
+msgid "Admin Notifications"
+msgstr ""
+
msgid "Admin Overview"
msgstr ""
@@ -1729,6 +1815,9 @@ msgstr ""
msgid "AdminSettings|Integrations configured here will automatically apply to all projects on this instance."
msgstr ""
+msgid "AdminSettings|Moved to integrations"
+msgstr ""
+
msgid "AdminSettings|No required pipeline"
msgstr ""
@@ -1961,12 +2050,18 @@ msgstr[1] ""
msgstr[2] ""
msgstr[3] ""
+msgid "AlertManagement| assign yourself"
+msgstr ""
+
msgid "AlertManagement|Acknowledged"
msgstr ""
msgid "AlertManagement|Alert"
msgstr ""
+msgid "AlertManagement|Alert assignee(s): %{assignees}"
+msgstr ""
+
msgid "AlertManagement|Alert detail"
msgstr ""
@@ -1982,9 +2077,15 @@ msgstr ""
msgid "AlertManagement|All alerts"
msgstr ""
+msgid "AlertManagement|Assign To"
+msgstr ""
+
msgid "AlertManagement|Assign status"
msgstr ""
+msgid "AlertManagement|Assignee"
+msgstr ""
+
msgid "AlertManagement|Assignees"
msgstr ""
@@ -2003,9 +2104,6 @@ msgstr ""
msgid "AlertManagement|Edit"
msgstr ""
-msgid "AlertManagement|End time"
-msgstr ""
-
msgid "AlertManagement|Events"
msgstr ""
@@ -2024,10 +2122,13 @@ msgstr ""
msgid "AlertManagement|More information"
msgstr ""
+msgid "AlertManagement|No Matching Results"
+msgstr ""
+
msgid "AlertManagement|No alert data to display."
msgstr ""
-msgid "AlertManagement|No alerts available to display. If you think you're seeing this message in error, refresh the page."
+msgid "AlertManagement|No alerts available to display. See %{linkStart}enabling alert management%{linkEnd} for more information on adding alerts to the list."
msgstr ""
msgid "AlertManagement|No alerts to display."
@@ -2036,6 +2137,9 @@ msgstr ""
msgid "AlertManagement|None"
msgstr ""
+msgid "AlertManagement|None -"
+msgstr ""
+
msgid "AlertManagement|Open"
msgstr ""
@@ -2072,9 +2176,18 @@ msgstr ""
msgid "AlertManagement|There was an error displaying the alerts. Confirm your endpoint's configuration details to ensure alerts appear."
msgstr ""
+msgid "AlertManagement|There was an error while updating the assignee(s) list. Please try again."
+msgstr ""
+
+msgid "AlertManagement|There was an error while updating the assignee(s) of the alert. Please try again."
+msgstr ""
+
msgid "AlertManagement|There was an error while updating the status of the alert. Please try again."
msgstr ""
+msgid "AlertManagement|This assignee cannot be assigned to this alert."
+msgstr ""
+
msgid "AlertManagement|Tool"
msgstr ""
@@ -2222,6 +2335,9 @@ msgstr ""
msgid "Allow users to request access (if visibility is public or internal)"
msgstr ""
+msgid "Allowed Geo IP"
+msgstr ""
+
msgid "Allowed email domain restriction only permitted for top-level groups"
msgstr ""
@@ -2255,6 +2371,9 @@ msgstr ""
msgid "Amazon Web Services"
msgstr ""
+msgid "Amazon Web Services Logo"
+msgstr ""
+
msgid "Amazon authentication is not %{link_start}correctly configured%{link_end}. Ask your GitLab administrator if you want to use this service."
msgstr ""
@@ -2312,10 +2431,16 @@ msgstr ""
msgid "An error occurred when updating the issue weight"
msgstr ""
+msgid "An error occurred while acknowledging the notification. Refresh the page and try again."
+msgstr ""
+
+msgid "An error occurred while adding approvers"
+msgstr ""
+
msgid "An error occurred while adding formatted title for epic"
msgstr ""
-msgid "An error occurred while checking group path"
+msgid "An error occurred while checking group path. Please refresh and try again."
msgstr ""
msgid "An error occurred while committing your changes."
@@ -2459,7 +2584,7 @@ msgstr ""
msgid "An error occurred while loading milestones"
msgstr ""
-msgid "An error occurred while loading terraform report"
+msgid "An error occurred while loading project creation UI"
msgstr ""
msgid "An error occurred while loading the data. Please try again."
@@ -2513,6 +2638,9 @@ msgstr ""
msgid "An error occurred while rendering preview broadcast message"
msgstr ""
+msgid "An error occurred while rendering the editor"
+msgstr ""
+
msgid "An error occurred while reordering issues."
msgstr ""
@@ -2609,7 +2737,7 @@ msgstr ""
msgid "Analyze your dependencies for known vulnerabilities."
msgstr ""
-msgid "Analyze your source code and git history for secrets"
+msgid "Analyze your source code and git history for secrets."
msgstr ""
msgid "Analyze your source code for known vulnerabilities."
@@ -2723,12 +2851,18 @@ msgstr ""
msgid "Apply suggestion"
msgstr ""
+msgid "Apply suggestions"
+msgstr ""
+
msgid "Apply template"
msgstr ""
msgid "Apply this approval rule to any branch or a specific protected branch."
msgstr ""
+msgid "Applying"
+msgstr ""
+
msgid "Applying a template will replace the existing issue description. Any changes you have made will be lost."
msgstr ""
@@ -2741,12 +2875,18 @@ msgstr ""
msgid "Applying multiple commands"
msgstr ""
-msgid "Applying suggestion"
+msgid "Applying suggestion..."
+msgstr ""
+
+msgid "Applying suggestions..."
msgstr ""
msgid "Approval rules"
msgstr ""
+msgid "Approval rules reset to project defaults"
+msgstr ""
+
msgid "ApprovalRuleRemove|%d member"
msgid_plural "ApprovalRuleRemove|%d members"
msgstr[0] ""
@@ -2808,6 +2948,9 @@ msgstr ""
msgid "Approved"
msgstr ""
+msgid "Approved MRs"
+msgstr ""
+
msgid "Approved by: "
msgstr ""
@@ -3021,6 +3164,9 @@ msgstr ""
msgid "Assign"
msgstr ""
+msgid "Assign Iteration"
+msgstr ""
+
msgid "Assign custom color like #FF0000"
msgstr ""
@@ -3039,6 +3185,9 @@ msgstr ""
msgid "Assign to"
msgstr ""
+msgid "Assign to commenting user"
+msgstr ""
+
msgid "Assign yourself to these issues"
msgstr ""
@@ -3067,6 +3216,9 @@ msgstr[1] ""
msgstr[2] ""
msgstr[3] ""
+msgid "Assignee has no permissions"
+msgstr ""
+
msgid "Assignee lists not available with your current license"
msgstr ""
@@ -3076,6 +3228,9 @@ msgstr ""
msgid "Assignee(s)"
msgstr ""
+msgid "Assignees"
+msgstr ""
+
msgid "Assigns %{assignee_users_sentence}."
msgstr ""
@@ -3113,6 +3268,9 @@ msgstr ""
msgid "Audit Events is a way to keep track of important events that happened in GitLab."
msgstr ""
+msgid "Audit Log"
+msgstr ""
+
msgid "AuditEvents|(removed)"
msgstr ""
@@ -3149,6 +3307,9 @@ msgstr ""
msgid "AuditLogs|IP Address"
msgstr ""
+msgid "AuditLogs|Member Events"
+msgstr ""
+
msgid "AuditLogs|No matching %{type} found."
msgstr ""
@@ -3431,9 +3592,6 @@ msgstr ""
msgid "Badges|e.g. %{exampleUrl}"
msgstr ""
-msgid "Badge|New"
-msgstr ""
-
msgid "Balsamiq file could not be loaded."
msgstr ""
@@ -3482,6 +3640,12 @@ msgstr ""
msgid "Below you will find all the groups that are public."
msgstr ""
+msgid "Beta"
+msgstr ""
+
+msgid "Bi-weekly code coverage"
+msgstr ""
+
msgid "Billing"
msgstr ""
@@ -3542,9 +3706,15 @@ msgstr ""
msgid "Bitbucket Server Import"
msgstr ""
+msgid "Bitbucket Server import"
+msgstr ""
+
msgid "Bitbucket import"
msgstr ""
+msgid "Blame"
+msgstr ""
+
msgid "Blocked"
msgstr ""
@@ -3557,9 +3727,6 @@ msgstr ""
msgid "Blog"
msgstr ""
-msgid "Blue helpers indicate an action to be taken."
-msgstr ""
-
msgid "Board name"
msgstr ""
@@ -3758,6 +3925,9 @@ msgstr ""
msgid "Broadcast Message was successfully updated."
msgstr ""
+msgid "Broadcast Messages"
+msgstr ""
+
msgid "Browse Directory"
msgstr ""
@@ -3812,6 +3982,9 @@ msgstr ""
msgid "By %{user_name}"
msgstr ""
+msgid "By URL"
+msgstr ""
+
msgid "By default GitLab sends emails in HTML and plain text formats so mail clients can choose what format to use. Disable this option if you only want to send emails in plain text format."
msgstr ""
@@ -3917,6 +4090,9 @@ msgstr ""
msgid "Can override approvers and approvals required per merge request"
msgstr ""
+msgid "Can't apply as this line has changed or the suggestion already matches its content."
+msgstr ""
+
msgid "Can't create snippet: %{err}"
msgstr ""
@@ -4103,9 +4279,6 @@ msgstr ""
msgid "Changes are still tracked. Useful for cluster/index migrations."
msgstr ""
-msgid "Changes are unknown"
-msgstr ""
-
msgid "Changes suppressed. Click to show."
msgstr ""
@@ -4412,15 +4585,9 @@ msgstr ""
msgid "Choose what content you want to see on a group’s overview page"
msgstr ""
-msgid "Choose which groups you wish to synchronize to this secondary node"
-msgstr ""
-
msgid "Choose which repositories you want to connect and run CI/CD pipelines."
msgstr ""
-msgid "Choose which shards you wish to synchronize to this secondary node"
-msgstr ""
-
msgid "Choose your framework"
msgstr ""
@@ -4718,9 +4885,15 @@ msgstr ""
msgid "Cluster does not exist"
msgstr ""
+msgid "Cluster is required for Stages::ClusterEndpointInserter"
+msgstr ""
+
msgid "Cluster level"
msgstr ""
+msgid "Cluster type must be specificed for Stages::ClusterEndpointInserter"
+msgstr ""
+
msgid "ClusterIntegration| %{custom_domain_start}More information%{custom_domain_end}."
msgstr ""
@@ -4832,7 +5005,7 @@ msgstr ""
msgid "ClusterIntegration|Cert-Manager"
msgstr ""
-msgid "ClusterIntegration|Cert-Manager is a native Kubernetes certificate management controller that helps with issuing certificates. Installing Cert-Manager on your cluster will issue a certificate by %{letsEncrypt} and ensure that certificates are valid and up-to-date."
+msgid "ClusterIntegration|Cert-Manager is a native Kubernetes certificate management controller that helps with issuing certificates. Installing Cert-Manager on your cluster will issue a certificate by %{linkStart}Let's Encrypt%{linkEnd} and ensure that certificates are valid and up-to-date."
msgstr ""
msgid "ClusterIntegration|Certificate Authority bundle (PEM format)"
@@ -4859,9 +5032,6 @@ msgstr ""
msgid "ClusterIntegration|Clear the local cache of namespace and service accounts. This is necessary if your integration has become out of sync. The cache is repopulated during the next CI job that requires namespace and service accounts."
msgstr ""
-msgid "ClusterIntegration|Cloud Run"
-msgstr ""
-
msgid "ClusterIntegration|Cluster being created"
msgstr ""
@@ -4946,7 +5116,7 @@ msgstr ""
msgid "ClusterIntegration|Crossplane"
msgstr ""
-msgid "ClusterIntegration|Crossplane enables declarative provisioning of managed services from your cloud of choice using %{codeStart}kubectl%{codeEnd} or %{gitlabIntegrationLink}. Crossplane runs inside your Kubernetes cluster and supports secure connectivity and secrets management between app containers and the cloud services they depend on."
+msgid "ClusterIntegration|Crossplane enables declarative provisioning of managed services from your cloud of choice using %{codeStart}kubectl%{codeEnd} or %{linkStart}GitLab Integration%{linkEnd}. Crossplane runs inside your Kubernetes cluster and supports secure connectivity and secrets management between app containers and the cloud services they depend on."
msgstr ""
msgid "ClusterIntegration|Deletes all GitLab resources attached to this cluster during removal"
@@ -5030,9 +5200,6 @@ msgstr ""
msgid "ClusterIntegration|GitLab-managed cluster"
msgstr ""
-msgid "ClusterIntegration|Gitlab Integration"
-msgstr ""
-
msgid "ClusterIntegration|Global default"
msgstr ""
@@ -5078,7 +5245,7 @@ msgstr ""
msgid "ClusterIntegration|Ingress gives you a way to route requests to services based on the request host or path, centralizing a number of services into a single entrypoint."
msgstr ""
-msgid "ClusterIntegration|Installing Ingress may incur additional costs. Learn more about %{pricingLink}."
+msgid "ClusterIntegration|Installing Ingress may incur additional costs. Learn more about %{linkStart}pricing%{linkEnd}."
msgstr ""
msgid "ClusterIntegration|Instance cluster"
@@ -5093,7 +5260,7 @@ msgstr ""
msgid "ClusterIntegration|Issuer Email"
msgstr ""
-msgid "ClusterIntegration|Issuers represent a certificate authority. You must provide an email address for your Issuer. "
+msgid "ClusterIntegration|Issuers represent a certificate authority. You must provide an email address for your Issuer."
msgstr ""
msgid "ClusterIntegration|Jupyter Hostname"
@@ -5165,9 +5332,6 @@ msgstr ""
msgid "ClusterIntegration|Learn more about instance Kubernetes clusters"
msgstr ""
-msgid "ClusterIntegration|Let's Encrypt"
-msgstr ""
-
msgid "ClusterIntegration|Loading IAM Roles"
msgstr ""
@@ -5279,7 +5443,7 @@ msgstr ""
msgid "ClusterIntegration|Prometheus"
msgstr ""
-msgid "ClusterIntegration|Prometheus is an open-source monitoring system with %{gitlabIntegrationLink} to monitor deployed applications."
+msgid "ClusterIntegration|Prometheus is an open-source monitoring system with %{linkStart}GitLab Integration%{linkEnd} to monitor deployed applications."
msgstr ""
msgid "ClusterIntegration|Provider details"
@@ -5435,10 +5599,10 @@ msgstr ""
msgid "ClusterIntegration|Select zone to choose machine type"
msgstr ""
-msgid "ClusterIntegration|Send Cilium Logs"
+msgid "ClusterIntegration|Send Container Network Policies Logs"
msgstr ""
-msgid "ClusterIntegration|Send ModSecurity Logs"
+msgid "ClusterIntegration|Send Web Application Firewall Logs"
msgstr ""
msgid "ClusterIntegration|Service Token"
@@ -5606,15 +5770,12 @@ msgstr ""
msgid "ClusterIntegration|documentation"
msgstr ""
-msgid "ClusterIntegration|installed via %{installed_via}"
+msgid "ClusterIntegration|installed via %{linkStart}Cloud Run%{linkEnd}"
msgstr ""
msgid "ClusterIntegration|meets the requirements"
msgstr ""
-msgid "ClusterIntegration|pricing"
-msgstr ""
-
msgid "ClusterIntegration|sign up"
msgstr ""
@@ -5651,6 +5812,15 @@ msgstr ""
msgid "Code"
msgstr ""
+msgid "Code Coverage: %{coveragePercentage}%{percentSymbol}"
+msgstr ""
+
+msgid "Code Coverage| Empty code coverage data"
+msgstr ""
+
+msgid "Code Coverage|Couldn't fetch the code coverage data"
+msgstr ""
+
msgid "Code Owners"
msgstr ""
@@ -5705,7 +5875,7 @@ msgstr ""
msgid "Collapse approvers"
msgstr ""
-msgid "Collapse child epics"
+msgid "Collapse milestones"
msgstr ""
msgid "Collapse replies"
@@ -5723,6 +5893,9 @@ msgstr ""
msgid "Coming soon"
msgstr ""
+msgid "Comma-separated, e.g. '1.1.1.1, 2.2.2.0/24'"
+msgstr ""
+
msgid "Command"
msgstr ""
@@ -5895,6 +6068,12 @@ msgstr ""
msgid "Compliance framework (optional)"
msgstr ""
+msgid "Compliance frameworks"
+msgstr ""
+
+msgid "ComplianceDashboard|created by:"
+msgstr ""
+
msgid "ComplianceFramework|GDPR"
msgstr ""
@@ -6033,6 +6212,9 @@ msgstr ""
msgid "Connection timed out"
msgstr ""
+msgid "Connection timeout"
+msgstr ""
+
msgid "Contact sales to upgrade"
msgstr ""
@@ -6092,9 +6274,6 @@ msgstr ""
msgid "ContainerRegistry|CLI Commands"
msgstr ""
-msgid "ContainerRegistry|Compressed Size"
-msgstr ""
-
msgid "ContainerRegistry|Container Registry"
msgstr ""
@@ -6110,6 +6289,9 @@ msgstr ""
msgid "ContainerRegistry|Copy push command"
msgstr ""
+msgid "ContainerRegistry|Delete selected"
+msgstr ""
+
msgid "ContainerRegistry|Docker connection error"
msgstr ""
@@ -6143,16 +6325,16 @@ msgstr ""
msgid "ContainerRegistry|If you are not already logged in, you need to authenticate to the Container Registry by using your GitLab username and password. If you have %{twofaDocLinkStart}Two-Factor Authentication%{twofaDocLinkEnd} enabled, use a %{personalAccessTokensDocLinkStart}Personal Access Token%{personalAccessTokensDocLinkEnd} instead of a password."
msgstr ""
-msgid "ContainerRegistry|Image ID"
+msgid "ContainerRegistry|Image ID: %{imageId}"
msgstr ""
msgid "ContainerRegistry|Image Repositories"
msgstr ""
-msgid "ContainerRegistry|Keep and protect the images that matter most."
+msgid "ContainerRegistry|Image tags"
msgstr ""
-msgid "ContainerRegistry|Last Updated"
+msgid "ContainerRegistry|Keep and protect the images that matter most."
msgstr ""
msgid "ContainerRegistry|Login"
@@ -6167,6 +6349,9 @@ msgstr ""
msgid "ContainerRegistry|Please contact your administrator."
msgstr ""
+msgid "ContainerRegistry|Published %{timeInfo}"
+msgstr ""
+
msgid "ContainerRegistry|Push an image"
msgstr ""
@@ -6182,9 +6367,6 @@ msgstr ""
msgid "ContainerRegistry|Remove repository"
msgstr ""
-msgid "ContainerRegistry|Remove selected tags"
-msgstr ""
-
msgid "ContainerRegistry|Remove tag"
msgid_plural "ContainerRegistry|Remove tags"
msgstr[0] ""
@@ -6216,9 +6398,6 @@ msgstr ""
msgid "ContainerRegistry|Sorry, your filter produced no results."
msgstr ""
-msgid "ContainerRegistry|Tag"
-msgstr ""
-
msgid "ContainerRegistry|Tag expiration policy"
msgstr ""
@@ -6363,21 +6542,6 @@ msgstr ""
msgid "Control the display of third party offers."
msgstr ""
-msgid "Control the maximum concurrency of LFS/attachment backfill for this secondary node"
-msgstr ""
-
-msgid "Control the maximum concurrency of container repository operations for this Geo node"
-msgstr ""
-
-msgid "Control the maximum concurrency of repository backfill for this secondary node"
-msgstr ""
-
-msgid "Control the maximum concurrency of verification operations for this Geo node"
-msgstr ""
-
-msgid "Control the minimum interval in days that a repository should be reverified for this primary node"
-msgstr ""
-
msgid "Cookie domain"
msgstr ""
@@ -6519,6 +6683,9 @@ msgstr ""
msgid "Could not find design."
msgstr ""
+msgid "Could not find iteration"
+msgstr ""
+
msgid "Could not remove the trigger."
msgstr ""
@@ -6555,6 +6722,12 @@ msgstr ""
msgid "Coverage"
msgstr ""
+msgid "Coverage Fuzzing"
+msgstr ""
+
+msgid "Crash State"
+msgstr ""
+
msgid "Create"
msgstr ""
@@ -6603,6 +6776,9 @@ msgstr ""
msgid "Create a personal access token on your account to pull or push via %{protocol}."
msgstr ""
+msgid "Create a project pre-populated with the necessary files to get you started quickly."
+msgstr ""
+
msgid "Create an account using:"
msgstr ""
@@ -6771,6 +6947,9 @@ msgstr ""
msgid "Creates branch '%{branch_name}' and a merge request to resolve this issue."
msgstr ""
+msgid "Creating"
+msgstr ""
+
msgid "Creating epic"
msgstr ""
@@ -6822,7 +7001,10 @@ msgstr ""
msgid "Current vulnerabilities count"
msgstr ""
-msgid "CurrentUser|Buy CI minutes"
+msgid "CurrentUser|Buy Pipeline minutes"
+msgstr ""
+
+msgid "CurrentUser|One of your groups is running out"
msgstr ""
msgid "CurrentUser|Profile"
@@ -6837,6 +7019,9 @@ msgstr ""
msgid "CurrentUser|Upgrade"
msgstr ""
+msgid "Custom Attributes"
+msgstr ""
+
msgid "Custom CI configuration path"
msgstr ""
@@ -7102,12 +7287,18 @@ msgstr ""
msgid "DAG"
msgstr ""
+msgid "DAG visualization requires at least 3 dependent jobs."
+msgstr ""
+
msgid "DNS"
msgstr ""
msgid "Dashboard"
msgstr ""
+msgid "Dashboard uid not found"
+msgstr ""
+
msgid "DashboardProjects|All"
msgstr ""
@@ -7132,6 +7323,9 @@ msgstr ""
msgid "Data is still calculating..."
msgstr ""
+msgid "Datasource name not found"
+msgstr ""
+
msgid "Date"
msgstr ""
@@ -7312,6 +7506,9 @@ msgstr ""
msgid "Delete this attachment"
msgstr ""
+msgid "Delete user list"
+msgstr ""
+
msgid "Delete variable"
msgstr ""
@@ -7638,6 +7835,9 @@ msgstr ""
msgid "Deploying to"
msgstr ""
+msgid "Deploying to AWS is easy with GitLab"
+msgstr ""
+
msgid "Deployment Frequency"
msgstr ""
@@ -8002,6 +8202,9 @@ msgstr ""
msgid "Dismissed on pipeline %{pipelineLink} at %{projectLink}"
msgstr ""
+msgid "Display alerts from all your monitoring tools directly within GitLab."
+msgstr ""
+
msgid "Display name"
msgstr ""
@@ -8059,9 +8262,6 @@ msgstr ""
msgid "Don't show again"
msgstr ""
-msgid "Don't worry, you can access this tour by clicking on the help icon in the top right corner and choose <strong>Learn GitLab</strong>."
-msgstr ""
-
msgid "Done"
msgstr ""
@@ -8188,6 +8388,9 @@ msgstr ""
msgid "Edit Release"
msgstr ""
+msgid "Edit Slack integration"
+msgstr ""
+
msgid "Edit Snippet"
msgstr ""
@@ -8227,6 +8430,9 @@ msgstr ""
msgid "Edit issues"
msgstr ""
+msgid "Edit iteration"
+msgstr ""
+
msgid "Edit public deploy key"
msgstr ""
@@ -8275,6 +8481,9 @@ msgstr ""
msgid "Email"
msgstr ""
+msgid "Email Notification"
+msgstr ""
+
msgid "Email address"
msgstr ""
@@ -8287,6 +8496,9 @@ msgstr ""
msgid "Email not verified. Please verify your email in Salesforce."
msgstr ""
+msgid "Email notification for unknown sign-ins"
+msgstr ""
+
msgid "Email patch"
msgstr ""
@@ -8518,6 +8730,9 @@ msgstr ""
msgid "Enforce DNS rebinding attack protection"
msgstr ""
+msgid "Enforce personal access token expiration"
+msgstr ""
+
msgid "Ensure connectivity is available from the GitLab server to the Prometheus server"
msgstr ""
@@ -8587,6 +8802,9 @@ msgstr ""
msgid "Enter the name of your application, and we'll return a unique %{type}."
msgstr ""
+msgid "Enter weights for storages for new repositories."
+msgstr ""
+
msgid "Enter your password to approve"
msgstr ""
@@ -8596,15 +8814,27 @@ msgstr ""
msgid "Environment does not have deployments"
msgstr ""
+msgid "Environment is required for Stages::MetricEndpointInserter"
+msgstr ""
+
+msgid "Environment is required for Stages::VariableEndpointInserter"
+msgstr ""
+
msgid "Environment scope"
msgstr ""
+msgid "Environment variables are applied to all project environments in this instance via the Runner. You can use environment variables for passwords, secret keys, etc. Make variables available to the running application by prepending the variable key with <code>K8S_SECRET_</code>. You can set variables to be:"
+msgstr ""
+
msgid "Environment variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. Additionally, they can be masked so they are hidden in job logs, though they must match certain regexp requirements to do so. You can use environment variables for passwords, secret keys, or whatever you want."
msgstr ""
msgid "Environment variables are configured by your administrator to be %{link_start}protected%{link_end} by default"
msgstr ""
+msgid "Environment variables on this GitLab instance are configured to be %{link_start}protected%{link_end} by default"
+msgstr ""
+
msgid "Environment:"
msgstr ""
@@ -9136,6 +9366,9 @@ msgstr ""
msgid "EventFilterBy|Filter by comments"
msgstr ""
+msgid "EventFilterBy|Filter by designs"
+msgstr ""
+
msgid "EventFilterBy|Filter by epic events"
msgstr ""
@@ -9268,15 +9501,15 @@ msgstr ""
msgid "Expand approvers"
msgstr ""
-msgid "Expand child epics"
-msgstr ""
-
msgid "Expand down"
msgstr ""
msgid "Expand dropdown"
msgstr ""
+msgid "Expand milestones"
+msgstr ""
+
msgid "Expand sidebar"
msgstr ""
@@ -9292,6 +9525,9 @@ msgstr ""
msgid "Expiration date"
msgstr ""
+msgid "Expiration not enforced"
+msgstr ""
+
msgid "Expiration policy for the Container Registry is a perfect solution for keeping the Registry space down while still enjoying the full power of GitLab CI/CD."
msgstr ""
@@ -9412,6 +9648,9 @@ msgstr ""
msgid "Failed Jobs"
msgstr ""
+msgid "Failed on"
+msgstr ""
+
msgid "Failed to add a Zoom meeting"
msgstr ""
@@ -9472,13 +9711,19 @@ msgstr ""
msgid "Failed to install."
msgstr ""
+msgid "Failed to load assignees. Please try again."
+msgstr ""
+
+msgid "Failed to load authors. Please try again."
+msgstr ""
+
msgid "Failed to load emoji list."
msgstr ""
msgid "Failed to load error details from Sentry."
msgstr ""
-msgid "Failed to load errors from Sentry. Error message: %{errorMessage}"
+msgid "Failed to load errors from Sentry."
msgstr ""
msgid "Failed to load group activity metrics. Please try again."
@@ -9559,6 +9804,9 @@ msgstr ""
msgid "Failed to set due date because the date format is invalid."
msgstr ""
+msgid "Failed to set iteration on this issue. Please try again."
+msgstr ""
+
msgid "Failed to signing using smartcard authentication"
msgstr ""
@@ -9781,13 +10029,10 @@ msgstr ""
msgid "FeatureFlags|Target environments"
msgstr ""
-msgid "FeatureFlags|There are no active feature flags"
+msgid "FeatureFlags|There was an error fetching the feature flags."
msgstr ""
-msgid "FeatureFlags|There are no inactive feature flags"
-msgstr ""
-
-msgid "FeatureFlags|There was an error fetching the feature flags."
+msgid "FeatureFlags|There was an error retrieving user lists"
msgstr ""
msgid "FeatureFlags|Try again in a few moments or contact your support team."
@@ -9799,9 +10044,18 @@ msgstr ""
msgid "FeatureFlag|Delete strategy"
msgstr ""
+msgid "FeatureFlag|List"
+msgstr ""
+
msgid "FeatureFlag|Percentage"
msgstr ""
+msgid "FeatureFlag|Select a user list"
+msgstr ""
+
+msgid "FeatureFlag|There are no configured user lists"
+msgstr ""
+
msgid "FeatureFlag|Type"
msgstr ""
@@ -9958,6 +10212,12 @@ msgstr ""
msgid "Filter..."
msgstr ""
+msgid "Find File"
+msgstr ""
+
+msgid "Find bugs in your code with coverage-guided fuzzing"
+msgstr ""
+
msgid "Find by path"
msgstr ""
@@ -10222,6 +10482,9 @@ msgstr ""
msgid "Geo allows you to replicate your GitLab instance to other geographical locations."
msgstr ""
+msgid "Geo nodes are paused using a command run on the node"
+msgstr ""
+
msgid "GeoNodeStatusEvent|%{timeAgoStr} (%{pendingEvents} events)"
msgstr ""
@@ -10306,6 +10569,9 @@ msgstr ""
msgid "GeoNodes|Not checksummed"
msgstr ""
+msgid "GeoNodes|Package files"
+msgstr ""
+
msgid "GeoNodes|Pausing replication stops the sync process. Are you sure?"
msgstr ""
@@ -10324,6 +10590,9 @@ msgstr ""
msgid "GeoNodes|Replication slots"
msgstr ""
+msgid "GeoNodes|Replication status"
+msgstr ""
+
msgid "GeoNodes|Repositories"
msgstr ""
@@ -10534,6 +10803,9 @@ msgstr ""
msgid "Geo|Synchronization failed - %{error}"
msgstr ""
+msgid "Geo|Synchronization of %{itemTitle} is disabled."
+msgstr ""
+
msgid "Geo|The database is currently %{db_lag} behind the primary node."
msgstr ""
@@ -10846,6 +11118,9 @@ msgstr ""
msgid "Go to %{link_to_google_takeout}."
msgstr ""
+msgid "Go to <strong>Issues</strong> > <strong>Boards</strong> to access your personalized learning issue board."
+msgstr ""
+
msgid "Go to Pipelines"
msgstr ""
@@ -10945,9 +11220,6 @@ msgstr ""
msgid "Go to your snippets"
msgstr ""
-msgid "Golden Tanuki"
-msgstr ""
-
msgid "Google Cloud Platform"
msgstr ""
@@ -10969,6 +11241,9 @@ msgstr ""
msgid "Grafana URL"
msgstr ""
+msgid "Grafana response contains invalid json"
+msgstr ""
+
msgid "GrafanaIntegration|API Token"
msgstr ""
@@ -11017,9 +11292,6 @@ msgstr ""
msgid "Group %{group_name} was successfully created."
msgstr ""
-msgid "Group '%{group_name}' is being imported."
-msgstr ""
-
msgid "Group Audit Events"
msgstr ""
@@ -11056,9 +11328,6 @@ msgstr ""
msgid "Group by:"
msgstr ""
-msgid "Group could not be imported: %{errors}"
-msgstr ""
-
msgid "Group description"
msgstr ""
@@ -11086,9 +11355,15 @@ msgstr ""
msgid "Group has not been marked for deletion"
msgstr ""
+msgid "Group import could not be scheduled"
+msgstr ""
+
msgid "Group info:"
msgstr ""
+msgid "Group is required when cluster_type is :group"
+msgstr ""
+
msgid "Group maintainers can register group runners in the %{link}"
msgstr ""
@@ -11152,6 +11427,24 @@ msgstr ""
msgid "GroupActivyMetrics|Recent activity (last 90 days)"
msgstr ""
+msgid "GroupImport|Failed to import group."
+msgstr ""
+
+msgid "GroupImport|Group '%{group_name}' is being imported."
+msgstr ""
+
+msgid "GroupImport|Group could not be imported: %{errors}"
+msgstr ""
+
+msgid "GroupImport|Please wait while we import the group for you. Refresh at will."
+msgstr ""
+
+msgid "GroupImport|The group was successfully imported."
+msgstr ""
+
+msgid "GroupImport|Unable to process group import file"
+msgstr ""
+
msgid "GroupRoadmap|%{dateWord} – No end date"
msgstr ""
@@ -11419,6 +11712,9 @@ msgstr ""
msgid "Groups (%{groups})"
msgstr ""
+msgid "Groups and projects"
+msgstr ""
+
msgid "Groups can also be nested by creating %{subgroup_docs_link_start}subgroups%{subgroup_docs_link_end}."
msgstr ""
@@ -11464,6 +11760,33 @@ msgstr ""
msgid "GroupsEmptyState|You can manage your group member’s permissions and access to each project in the group."
msgstr ""
+msgid "GroupsNew|Contact an administrator to enable options for importing your group."
+msgstr ""
+
+msgid "GroupsNew|Create"
+msgstr ""
+
+msgid "GroupsNew|Create group"
+msgstr ""
+
+msgid "GroupsNew|GitLab group export"
+msgstr ""
+
+msgid "GroupsNew|Import"
+msgstr ""
+
+msgid "GroupsNew|Import group"
+msgstr ""
+
+msgid "GroupsNew|My Awesome Group"
+msgstr ""
+
+msgid "GroupsNew|No import options available"
+msgstr ""
+
+msgid "GroupsNew|To copy a GitLab group between installations, navigate to the group settings page for the original installation, generate an export file, and upload it here."
+msgstr ""
+
msgid "GroupsTree|Are you sure you want to leave the \"%{fullName}\" group?"
msgstr ""
@@ -11566,6 +11889,9 @@ msgstr ""
msgid "Helps reduce request volume for protected paths"
msgstr ""
+msgid "Here are all your projects in your group, including the one you just created. To start, let’s take a look at your personalized learning project which will help you learn about GitLab at your own pace."
+msgstr ""
+
msgid "Here you will find recent merge request activity"
msgstr ""
@@ -11582,6 +11908,9 @@ msgstr[1] ""
msgstr[2] ""
msgstr[3] ""
+msgid "Hide details"
+msgstr ""
+
msgid "Hide file browser"
msgstr ""
@@ -11591,6 +11920,9 @@ msgstr ""
msgid "Hide host keys manual input"
msgstr ""
+msgid "Hide list"
+msgstr ""
+
msgid "Hide marketing-related entries from help"
msgstr ""
@@ -11613,9 +11945,6 @@ msgstr[3] ""
msgid "Hide values"
msgstr ""
-msgid "Hiding all labels"
-msgstr ""
-
msgid "High or unknown vulnerabilities present"
msgstr ""
@@ -11700,6 +12029,9 @@ msgstr ""
msgid "ID:"
msgstr ""
+msgid "IDE"
+msgstr ""
+
msgid "IDE|Allow live previews of JavaScript projects in the Web IDE using CodeSandbox Live Preview."
msgstr ""
@@ -11790,10 +12122,10 @@ msgstr ""
msgid "If enabled"
msgstr ""
-msgid "If enabled, access to projects will be validated on an external service using their classification label."
+msgid "If enabled, GitLab will handle Object Storage replication using Geo. %{linkStart}More information%{linkEnd}"
msgstr ""
-msgid "If enabled, and if object storage is enabled, GitLab will handle Object Storage replication using Geo"
+msgid "If enabled, access to projects will be validated on an external service using their classification label."
msgstr ""
msgid "If there is no previous license or if the previous license has expired, some GitLab functionality will be blocked until a new, valid license is uploaded."
@@ -12157,6 +12489,9 @@ msgstr[1] ""
msgstr[2] ""
msgstr[3] ""
+msgid "Instance Configuration"
+msgstr ""
+
msgid "Instance Statistics visibility"
msgstr ""
@@ -12178,9 +12513,6 @@ msgstr ""
msgid "Integrations"
msgstr ""
-msgid "Integrations allow you to integrate GitLab with other applications"
-msgstr ""
-
msgid "Integrations|All details"
msgstr ""
@@ -12292,6 +12624,9 @@ msgstr ""
msgid "Invalid query"
msgstr ""
+msgid "Invalid repository bundle for snippet with id %{snippet_id}"
+msgstr ""
+
msgid "Invalid repository path"
msgstr ""
@@ -12310,6 +12645,9 @@ msgstr ""
msgid "Invalid two-factor code."
msgstr ""
+msgid "Invalid yaml"
+msgstr ""
+
msgid "Invitation"
msgstr ""
@@ -12529,15 +12867,24 @@ msgstr ""
msgid "It seems like the Dependency Scanning job ran successfully, but no dependencies have been detected in your project."
msgstr ""
+msgid "It seems that there is currently no available data for code coverage"
+msgstr ""
+
msgid "It's you"
msgstr ""
+msgid "Iteration"
+msgstr ""
+
msgid "Iteration changed to"
msgstr ""
msgid "Iteration removed"
msgstr ""
+msgid "Iteration updated"
+msgstr ""
+
msgid "Iterations"
msgstr ""
@@ -12568,12 +12915,18 @@ msgstr ""
msgid "January"
msgstr ""
+msgid "Jira Issues"
+msgstr ""
+
msgid "Jira import is already running."
msgstr ""
msgid "Jira integration not configured."
msgstr ""
+msgid "Jira project key is not configured"
+msgstr ""
+
msgid "Jira project: %{importProject}"
msgstr ""
@@ -12754,6 +13107,9 @@ msgstr ""
msgid "Keep divergent refs"
msgstr ""
+msgid "Kerberos access denied"
+msgstr ""
+
msgid "Key"
msgstr ""
@@ -12763,10 +13119,13 @@ msgstr ""
msgid "Key: %{key}"
msgstr ""
-msgid "Keyboard Shortcuts"
+msgid "Keyboard shortcuts"
msgstr ""
-msgid "Keyboard shortcuts"
+msgid "Keys"
+msgstr ""
+
+msgid "Ki"
msgstr ""
msgid "Kubernetes"
@@ -12808,6 +13167,9 @@ msgstr ""
msgid "LDAP"
msgstr ""
+msgid "LDAP Synchronization"
+msgstr ""
+
msgid "LDAP settings"
msgstr ""
@@ -13034,12 +13396,18 @@ msgstr ""
msgid "Learn more about custom project templates"
msgstr ""
+msgid "Learn more about deploying to AWS"
+msgstr ""
+
msgid "Learn more about deploying to a cluster"
msgstr ""
msgid "Learn more about group-level project templates"
msgstr ""
+msgid "Learn more about job dependencies"
+msgstr ""
+
msgid "Learn more about signing commits"
msgstr ""
@@ -13091,9 +13459,21 @@ msgstr ""
msgid "License History"
msgstr ""
+msgid "License ID:"
+msgstr ""
+
+msgid "License URL"
+msgstr ""
+
msgid "License-Check"
msgstr ""
+msgid "LicenseCompliance|%{docLinkStart}License Approvals%{docLinkEnd} are active"
+msgstr ""
+
+msgid "LicenseCompliance|%{docLinkStart}License Approvals%{docLinkEnd} are inactive"
+msgstr ""
+
msgid "LicenseCompliance|Add a license"
msgstr ""
@@ -13115,9 +13495,15 @@ msgstr ""
msgid "LicenseCompliance|Deny"
msgstr ""
+msgid "LicenseCompliance|Learn more about %{linkStart}License Approvals%{linkEnd}"
+msgstr ""
+
msgid "LicenseCompliance|License"
msgstr ""
+msgid "LicenseCompliance|License Approvals"
+msgstr ""
+
msgid "LicenseCompliance|License Compliance detected %d license and policy violation for the source branch only; approval required"
msgid_plural "LicenseCompliance|License Compliance detected %d licenses and policy violations for the source branch only; approval required"
msgstr[0] ""
@@ -13348,6 +13734,9 @@ msgstr ""
msgid "List your Bitbucket Server repositories"
msgstr ""
+msgid "Lists"
+msgstr ""
+
msgid "Live preview"
msgstr ""
@@ -13531,6 +13920,9 @@ msgstr ""
msgid "Manage project labels"
msgstr ""
+msgid "Manage storage usage"
+msgstr ""
+
msgid "Manage two-factor authentication"
msgstr ""
@@ -13546,6 +13938,9 @@ msgstr ""
msgid "Manifest file import"
msgstr ""
+msgid "Manifest import"
+msgstr ""
+
msgid "Manual job"
msgstr ""
@@ -13693,6 +14088,12 @@ msgstr ""
msgid "Maximum field length"
msgstr ""
+msgid "Maximum file size is 2MB. Please select a smaller file."
+msgstr ""
+
+msgid "Maximum import size (MB)"
+msgstr ""
+
msgid "Maximum job timeout"
msgstr ""
@@ -13732,6 +14133,9 @@ msgstr ""
msgid "Maximum size of Elasticsearch bulk indexing requests."
msgstr ""
+msgid "Maximum size of import files."
+msgstr ""
+
msgid "Maximum size of individual attachments in comments."
msgstr ""
@@ -13783,6 +14187,9 @@ msgstr ""
msgid "Merge (when the pipeline succeeds)"
msgstr ""
+msgid "Merge Conflicts"
+msgstr ""
+
msgid "Merge Request"
msgstr ""
@@ -13909,6 +14316,9 @@ msgstr ""
msgid "MergeRequests|Squash task canceled: another squash is already in progress."
msgstr ""
+msgid "MergeRequests|This project does not allow squashing commits when merge requests are accepted."
+msgstr ""
+
msgid "MergeRequests|Thread stays resolved"
msgstr ""
@@ -14062,6 +14472,12 @@ msgstr ""
msgid "MetricsSettings|Add a button to the metrics dashboard linking directly to your existing external dashboard."
msgstr ""
+msgid "MetricsSettings|Choose whether to display dashboard metrics in UTC or the user's local timezone."
+msgstr ""
+
+msgid "MetricsSettings|Dashboard timezone"
+msgstr ""
+
msgid "MetricsSettings|External dashboard URL"
msgstr ""
@@ -14071,6 +14487,12 @@ msgstr ""
msgid "MetricsSettings|Metrics Dashboard"
msgstr ""
+msgid "MetricsSettings|UTC (Coordinated Universal Time)"
+msgstr ""
+
+msgid "MetricsSettings|User's local timezone"
+msgstr ""
+
msgid "Metrics|Add metric"
msgstr ""
@@ -14086,6 +14508,9 @@ msgstr ""
msgid "Metrics|Create metric"
msgstr ""
+msgid "Metrics|Current"
+msgstr ""
+
msgid "Metrics|Delete metric"
msgstr ""
@@ -14138,6 +14563,9 @@ msgstr ""
msgid "Metrics|Max"
msgstr ""
+msgid "Metrics|Min"
+msgstr ""
+
msgid "Metrics|Must be a valid PromQL query."
msgstr ""
@@ -14153,6 +14581,9 @@ msgstr ""
msgid "Metrics|Refresh dashboard"
msgstr ""
+msgid "Metrics|Select a value"
+msgstr ""
+
msgid "Metrics|Star dashboard"
msgstr ""
@@ -14171,12 +14602,18 @@ msgstr ""
msgid "Metrics|There was an error getting annotations information."
msgstr ""
+msgid "Metrics|There was an error getting dashboard validation warnings information."
+msgstr ""
+
msgid "Metrics|There was an error getting deployment information."
msgstr ""
msgid "Metrics|There was an error getting environments information."
msgstr ""
+msgid "Metrics|There was an error getting options for variable \"%{name}\"."
+msgstr ""
+
msgid "Metrics|There was an error trying to validate your query"
msgstr ""
@@ -14219,6 +14656,9 @@ msgstr ""
msgid "Metrics|You're about to permanently delete this metric. This cannot be undone."
msgstr ""
+msgid "Metrics|Your dashboard schema is invalid. Edit the dashboard to correct the YAML schema."
+msgstr ""
+
msgid "Metrics|e.g. HTTP requests"
msgstr ""
@@ -14234,12 +14674,18 @@ msgstr ""
msgid "Metrics|e.g. req/sec"
msgstr ""
+msgid "Mi"
+msgstr ""
+
msgid "Microsoft Azure"
msgstr ""
msgid "Middleman project with Static Site Editor support"
msgstr ""
+msgid "Migrate your data from an external source like GitHub, Bitbucket, or another instance of GitLab."
+msgstr ""
+
msgid "Migrated %{success_count}/%{total_count} files."
msgstr ""
@@ -14376,6 +14822,9 @@ msgstr ""
msgid "Minimum capacity to be available before we schedule more mirrors preemptively."
msgstr ""
+msgid "Minimum interval in days"
+msgstr ""
+
msgid "Minimum length is %{minimum_password_length} characters"
msgstr ""
@@ -14562,6 +15011,12 @@ msgstr ""
msgid "Multiple uploaders found: %{uploader_types}"
msgstr ""
+msgid "Must match with the %{codeStart}external_url%{codeEnd} in %{codeStart}/etc/gitlab/gitlab.rb%{codeEnd}."
+msgstr ""
+
+msgid "Must match with the %{codeStart}geo_node_name%{codeEnd} in %{codeStart}/etc/gitlab/gitlab.rb%{codeEnd}. %{linkStart}More information%{linkEnd}"
+msgstr ""
+
msgid "My Awesome Group"
msgstr ""
@@ -14676,10 +15131,16 @@ msgstr ""
msgid "New Application"
msgstr ""
+msgid "New Branch"
+msgstr ""
+
+msgid "New Deploy Key"
+msgstr ""
+
msgid "New Environment"
msgstr ""
-msgid "New Geo Node"
+msgid "New File"
msgstr ""
msgid "New Group"
@@ -14698,15 +15159,15 @@ msgstr[1] ""
msgstr[2] ""
msgstr[3] ""
-msgid "New Iteration"
-msgstr ""
-
msgid "New Jira import"
msgstr ""
msgid "New Label"
msgstr ""
+msgid "New Merge Request"
+msgstr ""
+
msgid "New Milestone"
msgstr ""
@@ -14725,6 +15186,9 @@ msgstr ""
msgid "New Snippet"
msgstr ""
+msgid "New User"
+msgstr ""
+
msgid "New branch"
msgstr ""
@@ -14767,6 +15231,9 @@ msgstr ""
msgid "New issue title"
msgstr ""
+msgid "New iteration"
+msgstr ""
+
msgid "New iteration created"
msgstr ""
@@ -14944,6 +15411,12 @@ msgstr ""
msgid "No grouping"
msgstr ""
+msgid "No iteration"
+msgstr ""
+
+msgid "No iterations to show"
+msgstr ""
+
msgid "No job log"
msgstr ""
@@ -14986,6 +15459,9 @@ msgstr ""
msgid "No other labels with such name or description"
msgstr ""
+msgid "No panels matching properties %{opts}"
+msgstr ""
+
msgid "No parent group"
msgstr ""
@@ -15019,6 +15495,9 @@ msgstr ""
msgid "No schedules"
msgstr ""
+msgid "No stack trace for this error"
+msgstr ""
+
msgid "No starrers matched your search"
msgstr ""
@@ -15031,7 +15510,7 @@ msgstr ""
msgid "No test coverage"
msgstr ""
-msgid "No thanks, don't show this again"
+msgid "No thanks"
msgstr ""
msgid "No vulnerabilities present"
@@ -15046,9 +15525,6 @@ msgstr ""
msgid "No, directly import the existing email addresses and usernames."
msgstr ""
-msgid "No, not interested right now"
-msgstr ""
-
msgid "No. of commits"
msgstr ""
@@ -15094,9 +15570,6 @@ msgstr ""
msgid "Not found."
msgstr ""
-msgid "Not helpful"
-msgstr ""
-
msgid "Not now"
msgstr ""
@@ -15253,6 +15726,9 @@ msgstr ""
msgid "Notifications on"
msgstr ""
+msgid "Notify users by email when sign-in location is not recognized"
+msgstr ""
+
msgid "Nov"
msgstr ""
@@ -15319,9 +15795,6 @@ msgstr ""
msgid "Oh no!"
msgstr ""
-msgid "Ok let's go"
-msgstr ""
-
msgid "Oldest first"
msgstr ""
@@ -15334,16 +15807,49 @@ msgstr ""
msgid "On track"
msgstr ""
+msgid "OnDemandScans|Attached branch"
+msgstr ""
+
+msgid "OnDemandScans|Attached branch is where the scan job runs."
+msgstr ""
+
+msgid "OnDemandScans|Could not run the scan. Please try again."
+msgstr ""
+
msgid "OnDemandScans|Create new DAST scan"
msgstr ""
+msgid "OnDemandScans|DAST will scan the target URL and any discovered sub URLs."
+msgstr ""
+
+msgid "OnDemandScans|New on-demand DAST scan"
+msgstr ""
+
msgid "OnDemandScans|On-demand Scans"
msgstr ""
+msgid "OnDemandScans|On-demand scans run outside the DevOps cycle and find vulnerabilities in your projects. %{learnMoreLinkStart}Learn more%{learnMoreLinkEnd}"
+msgstr ""
+
+msgid "OnDemandScans|Only a passive scan can be performed on demand."
+msgstr ""
+
+msgid "OnDemandScans|Passive DAST Scan"
+msgstr ""
+
+msgid "OnDemandScans|Please enter a valid URL format, ex: http://www.example.com/home"
+msgstr ""
+
+msgid "OnDemandScans|Run this scan"
+msgstr ""
+
+msgid "OnDemandScans|Scan mode"
+msgstr ""
+
msgid "OnDemandScans|Schedule or run scans immediately against target sites. Currently available on-demand scan type: DAST. %{helpLinkStart}More information%{helpLinkEnd}"
msgstr ""
-msgid "Onboarding"
+msgid "OnDemandScans|Target URL"
msgstr ""
msgid "Once imported, repositories can be mirrored over SSH. Read more %{link_start}here%{link_end}."
@@ -15371,6 +15877,9 @@ msgstr ""
msgid "One or more of you personal access tokens were revoked"
msgstr ""
+msgid "One or more of your %{provider} projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
+msgstr ""
+
msgid "One or more of your Bitbucket projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
msgstr ""
@@ -15872,6 +16381,9 @@ msgstr ""
msgid "Parent epic is not present."
msgstr ""
+msgid "Parsing error for param :embed_json. %{message}"
+msgstr ""
+
msgid "Part of merge request changes"
msgstr ""
@@ -15884,6 +16396,9 @@ msgstr ""
msgid "Passed"
msgstr ""
+msgid "Passed on"
+msgstr ""
+
msgid "Password"
msgstr ""
@@ -15968,6 +16483,9 @@ msgstr ""
msgid "Perform common operations on GitLab project"
msgstr ""
+msgid "Performance and resource management"
+msgstr ""
+
msgid "Performance optimization"
msgstr ""
@@ -16175,7 +16693,7 @@ msgstr ""
msgid "Pipelines|Something went wrong while cleaning runners cache."
msgstr ""
-msgid "Pipelines|There are currently no %{scope} pipelines."
+msgid "Pipelines|There are currently no finished pipelines."
msgstr ""
msgid "Pipelines|There are currently no pipelines."
@@ -16286,6 +16804,9 @@ msgstr ""
msgid "Pipeline|Stop pipeline #%{pipelineId}?"
msgstr ""
+msgid "Pipeline|Tag name"
+msgstr ""
+
msgid "Pipeline|Trigger author"
msgstr ""
@@ -16367,12 +16888,18 @@ msgstr ""
msgid "Please check your email (%{email}) to verify that you own this address and unlock the power of CI/CD. Didn't receive it? %{resend_link}. Wrong email address? %{update_link}."
msgstr ""
+msgid "Please choose a file"
+msgstr ""
+
msgid "Please choose a group URL with no special characters."
msgstr ""
msgid "Please complete your profile with email address"
msgstr ""
+msgid "Please convert %{linkStart}them to Git%{linkEnd}, and go through the %{linkToImportFlow} again."
+msgstr ""
+
msgid "Please convert them to %{link_to_git}, and go through the %{link_to_import_flow} again."
msgstr ""
@@ -16430,6 +16957,9 @@ msgstr ""
msgid "Please provide a valid email address."
msgstr ""
+msgid "Please provide attributes to update"
+msgstr ""
+
msgid "Please refer to <a href=\"%{docs_url}\">%{docs_url}</a>"
msgstr ""
@@ -17165,6 +17695,9 @@ msgstr ""
msgid "Project has too many %{label_for_message} to search"
msgstr ""
+msgid "Project is required when cluster_type is :project"
+msgstr ""
+
msgid "Project members"
msgstr ""
@@ -17282,7 +17815,40 @@ msgstr ""
msgid "ProjectService|Comment"
msgstr ""
-msgid "ProjectService|Comment will be posted on each event"
+msgid "ProjectService|Event will be triggered by a push to the repository"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a commit is created/updated"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a confidential issue is created/updated/closed"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a deployment finishes"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a merge request is created/updated/merged"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a new tag is pushed to the repository"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a new, unique alert is recorded"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a pipeline status changes"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a wiki page is created/updated"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when an issue is created/updated/closed"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when someone adds a comment"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when someone adds a comment on a confidential issue"
msgstr ""
msgid "ProjectService|Perform common operations on GitLab project: %{project_name}"
@@ -17333,6 +17899,9 @@ msgstr ""
msgid "ProjectSettings|Enable 'Delete source branch' option by default"
msgstr ""
+msgid "ProjectSettings|Enable merge trains and pipelines for merged results"
+msgstr ""
+
msgid "ProjectSettings|Every merge creates a merge commit"
msgstr ""
@@ -17363,7 +17932,7 @@ msgstr ""
msgid "ProjectSettings|Forks"
msgstr ""
-msgid "ProjectSettings|Git Large File Storage"
+msgid "ProjectSettings|Git Large File Storage (LFS)"
msgstr ""
msgid "ProjectSettings|Internal"
@@ -17399,9 +17968,6 @@ msgstr ""
msgid "ProjectSettings|Merge options"
msgstr ""
-msgid "ProjectSettings|Merge pipelines will try to validate the post-merge result prior to merging"
-msgstr ""
-
msgid "ProjectSettings|Merge requests"
msgstr ""
@@ -17429,6 +17995,9 @@ msgstr ""
msgid "ProjectSettings|Pipelines"
msgstr ""
+msgid "ProjectSettings|Pipelines for merge requests must be enabled in the CI/CD configuration file, or pipelines could be unresolvable or dropped"
+msgstr ""
+
msgid "ProjectSettings|Pipelines must succeed"
msgstr ""
@@ -17456,6 +18025,9 @@ msgstr ""
msgid "ProjectSettings|Show link to create/view merge request when pushing from the command line"
msgstr ""
+msgid "ProjectSettings|Skipped pipelines are considered successful"
+msgstr ""
+
msgid "ProjectSettings|Snippets"
msgstr ""
@@ -17471,6 +18043,9 @@ msgstr ""
msgid "ProjectSettings|These checks must pass before merge requests can be merged"
msgstr ""
+msgid "ProjectSettings|This introduces the risk of merging changes that will not pass the pipeline."
+msgstr ""
+
msgid "ProjectSettings|This setting is applied on the server level and can be overridden by an admin."
msgstr ""
@@ -17648,15 +18223,27 @@ msgstr ""
msgid "ProjectsNew|Blank project"
msgstr ""
+msgid "ProjectsNew|Connect your external repository to GitLab CI/CD."
+msgstr ""
+
msgid "ProjectsNew|Contact an administrator to enable options for importing your project."
msgstr ""
msgid "ProjectsNew|Create"
msgstr ""
+msgid "ProjectsNew|Create a blank project to house your files, plan your work, and collaborate on code, among other things."
+msgstr ""
+
+msgid "ProjectsNew|Create blank project"
+msgstr ""
+
msgid "ProjectsNew|Create from template"
msgstr ""
+msgid "ProjectsNew|Create new project"
+msgstr ""
+
msgid "ProjectsNew|Creating project & repository."
msgstr ""
@@ -17681,6 +18268,9 @@ msgstr ""
msgid "ProjectsNew|Project description %{tag_start}(optional)%{tag_end}"
msgstr ""
+msgid "ProjectsNew|Run CI/CD for external repository"
+msgstr ""
+
msgid "ProjectsNew|Template"
msgstr ""
@@ -17942,6 +18532,9 @@ msgstr ""
msgid "Protected Branch"
msgstr ""
+msgid "Protected Branches"
+msgstr ""
+
msgid "Protected Environment"
msgstr ""
@@ -17954,6 +18547,9 @@ msgstr ""
msgid "Protected Tag"
msgstr ""
+msgid "Protected Tags"
+msgstr ""
+
msgid "Protected branches"
msgstr ""
@@ -18053,6 +18649,9 @@ msgstr ""
msgid "Provider"
msgstr ""
+msgid "Proxy support for this API is not available currently"
+msgstr ""
+
msgid "Pseudonymizer data collection"
msgstr ""
@@ -18077,6 +18676,9 @@ msgstr ""
msgid "Publish to status page"
msgstr ""
+msgid "Published on status page"
+msgstr ""
+
msgid "Publishes this issue to the associated status page."
msgstr ""
@@ -18251,6 +18853,9 @@ msgstr ""
msgid "Recipe"
msgstr ""
+msgid "Reconfigure"
+msgstr ""
+
msgid "Recover hidden stage"
msgstr ""
@@ -18339,6 +18944,9 @@ msgstr ""
msgid "Registration|Your profile"
msgstr ""
+msgid "Regulate approvals by authors/committers, based on compliance frameworks. Can be changed only at the instance level."
+msgstr ""
+
msgid "Rejected (closed)"
msgstr ""
@@ -18391,15 +18999,24 @@ msgstr ""
msgid "ReleaseAssetLinkType|Image"
msgstr ""
+msgid "ReleaseAssetLinkType|Images"
+msgstr ""
+
msgid "ReleaseAssetLinkType|Other"
msgstr ""
msgid "ReleaseAssetLinkType|Package"
msgstr ""
+msgid "ReleaseAssetLinkType|Packages"
+msgstr ""
+
msgid "ReleaseAssetLinkType|Runbook"
msgstr ""
+msgid "ReleaseAssetLinkType|Runbooks"
+msgstr ""
+
msgid "Releases"
msgstr ""
@@ -18412,6 +19029,9 @@ msgstr ""
msgid "Releases documentation"
msgstr ""
+msgid "Releases|New Release"
+msgstr ""
+
msgid "Release|Something went wrong while getting the release details"
msgstr ""
@@ -18484,6 +19104,9 @@ msgstr ""
msgid "Remove fork relationship"
msgstr ""
+msgid "Remove from batch"
+msgstr ""
+
msgid "Remove from board"
msgstr ""
@@ -18673,6 +19296,12 @@ msgstr ""
msgid "Replication"
msgstr ""
+msgid "Replication enabled"
+msgstr ""
+
+msgid "Replication paused"
+msgstr ""
+
msgid "Reply by email"
msgstr ""
@@ -18700,15 +19329,15 @@ msgstr ""
msgid "Reported %{timeAgo} by %{reportedBy}"
msgstr ""
-msgid "Reported Resource Changes: %{addNum} to add, %{changeNum} to change, %{deleteNum} to delete"
-msgstr ""
-
msgid "Reporter"
msgstr ""
msgid "Reporting"
msgstr ""
+msgid "Reports"
+msgstr ""
+
msgid "Reports|%{combinedString} and %{resolvedString}"
msgstr ""
@@ -18749,6 +19378,9 @@ msgstr ""
msgid "Reports|Failure"
msgstr ""
+msgid "Reports|Identifier"
+msgstr ""
+
msgid "Reports|Metrics reports are loading"
msgstr ""
@@ -18761,6 +19393,9 @@ msgstr ""
msgid "Reports|Metrics reports failed loading results"
msgstr ""
+msgid "Reports|Scanner"
+msgstr ""
+
msgid "Reports|Severity"
msgstr ""
@@ -18809,15 +19444,33 @@ msgstr ""
msgid "Repository cleanup has started. You will receive an email once the cleanup operation is complete."
msgstr ""
+msgid "Repository files count over the limit"
+msgstr ""
+
+msgid "Repository has an invalid default branch name."
+msgstr ""
+
+msgid "Repository has more than one branch."
+msgstr ""
+
msgid "Repository has no locks."
msgstr ""
+msgid "Repository has tags."
+msgstr ""
+
msgid "Repository maintenance"
msgstr ""
msgid "Repository mirroring"
msgstr ""
+msgid "Repository must contain at least 1 file."
+msgstr ""
+
+msgid "Repository size is above the limit."
+msgstr ""
+
msgid "Repository static objects"
msgstr ""
@@ -18827,7 +19480,7 @@ msgstr ""
msgid "Repository sync capacity"
msgstr ""
-msgid "Repository: %{counter_repositories} / Wikis: %{counter_wikis} / Build Artifacts: %{counter_build_artifacts} / LFS: %{counter_lfs_objects}"
+msgid "Repository: %{counter_repositories} / Wikis: %{counter_wikis} / Build Artifacts: %{counter_build_artifacts} / LFS: %{counter_lfs_objects} / Snippets: %{counter_snippets}"
msgstr ""
msgid "RepositorySettingsAccessLevel|Select"
@@ -18839,6 +19492,9 @@ msgstr ""
msgid "Request Headers"
msgstr ""
+msgid "Request details"
+msgstr ""
+
msgid "Request parameter %{param} is missing."
msgstr ""
@@ -18949,6 +19605,9 @@ msgstr ""
msgid "Reset template"
msgstr ""
+msgid "Reset to project defaults"
+msgstr ""
+
msgid "Resetting the authorization key for this project will require updating the authorization key in every alert source it is enabled in."
msgstr ""
@@ -19045,9 +19704,6 @@ msgstr ""
msgid "Resume"
msgstr ""
-msgid "Resume replication"
-msgstr ""
-
msgid "Resync"
msgstr ""
@@ -19692,6 +20348,9 @@ msgstr ""
msgid "SecurityReports|False positive"
msgstr ""
+msgid "SecurityReports|Group Security Dashboard"
+msgstr ""
+
msgid "SecurityReports|Hide dismissed"
msgstr ""
@@ -19746,10 +20405,13 @@ msgstr ""
msgid "SecurityReports|Remove project from dashboard"
msgstr ""
-msgid "SecurityReports|Report type"
+msgid "SecurityReports|Return to dashboard"
+msgstr ""
+
+msgid "SecurityReports|Scan details"
msgstr ""
-msgid "SecurityReports|Return to dashboard"
+msgid "SecurityReports|Scanner type"
msgstr ""
msgid "SecurityReports|Security Dashboard"
@@ -19806,6 +20468,9 @@ msgstr ""
msgid "SecurityReports|There was an error while generating the report."
msgstr ""
+msgid "SecurityReports|Unable to add %{invalidProjectsMessage}"
+msgstr ""
+
msgid "SecurityReports|Unable to add %{invalidProjects}"
msgstr ""
@@ -19920,6 +20585,9 @@ msgstr ""
msgid "Select due date"
msgstr ""
+msgid "Select file"
+msgstr ""
+
msgid "Select group or project"
msgstr ""
@@ -19983,9 +20651,6 @@ msgstr ""
msgid "Select the branch you want to set as the default for this project. All merge requests and commits will automatically be made against this branch unless you specify a different one."
msgstr ""
-msgid "Select the configured storage available for new repositories to be placed on."
-msgstr ""
-
msgid "Select the custom project template source group."
msgstr ""
@@ -20178,12 +20843,6 @@ msgstr ""
msgid "Service Desk is enabled but not yet active"
msgstr ""
-msgid "Service Desk is off"
-msgstr ""
-
-msgid "Service Desk is on"
-msgstr ""
-
msgid "Service Templates"
msgstr ""
@@ -20271,6 +20930,15 @@ msgstr ""
msgid "Set the milestone to %{milestone_reference}."
msgstr ""
+msgid "Set the number of concurrent requests this secondary node will make to the primary node while backfilling."
+msgstr ""
+
+msgid "Set the synchronization and verification capacity for the secondary node."
+msgstr ""
+
+msgid "Set the timeout in seconds to send a secondary node status to the primary and IPs allowed for the secondary nodes."
+msgstr ""
+
msgid "Set time estimate"
msgstr ""
@@ -20310,6 +20978,9 @@ msgstr ""
msgid "Set weight to %{weight}."
msgstr ""
+msgid "Set what should be replicated by choosing specific projects or groups by the secondary node."
+msgstr ""
+
msgid "SetPasswordToCloneLink|set a password"
msgstr ""
@@ -20436,6 +21107,9 @@ msgstr ""
msgid "Show complete raw log"
msgstr ""
+msgid "Show details"
+msgstr ""
+
msgid "Show file browser"
msgstr ""
@@ -20445,10 +21119,13 @@ msgstr ""
msgid "Show latest version"
msgstr ""
+msgid "Show list"
+msgstr ""
+
msgid "Show me everything"
msgstr ""
-msgid "Show me how"
+msgid "Show me how to add a pipeline"
msgstr ""
msgid "Show me more advanced stuff"
@@ -20501,9 +21178,6 @@ msgstr ""
msgid "Showing all issues"
msgstr ""
-msgid "Showing all labels"
-msgstr ""
-
msgid "Showing last %{size} of log -"
msgstr ""
@@ -20615,9 +21289,6 @@ msgstr ""
msgid "Skip outdated deployment jobs"
msgstr ""
-msgid "Skip this for now"
-msgstr ""
-
msgid "Skipped"
msgstr ""
@@ -20762,6 +21433,9 @@ msgstr ""
msgid "Something went wrong while adding your award. Please try again."
msgstr ""
+msgid "Something went wrong while applying the batch of suggestions. Please try again."
+msgstr ""
+
msgid "Something went wrong while applying the suggestion. Please try again."
msgstr ""
@@ -20864,10 +21538,13 @@ msgstr ""
msgid "Something went wrong while updating a requirement."
msgstr ""
+msgid "Something went wrong while updating assignees"
+msgstr ""
+
msgid "Something went wrong while updating your list settings"
msgstr ""
-msgid "Something went wrong with your automatic subscription renewal"
+msgid "Something went wrong with your automatic subscription renewal."
msgstr ""
msgid "Something went wrong, unable to add %{project} to dashboard"
@@ -20885,6 +21562,9 @@ msgstr ""
msgid "Something went wrong, unable to search projects"
msgstr ""
+msgid "Something went wrong."
+msgstr ""
+
msgid "Something went wrong. Please try again."
msgstr ""
@@ -21068,6 +21748,9 @@ msgstr ""
msgid "Source code"
msgstr ""
+msgid "Source code (%{fileExtension})"
+msgstr ""
+
msgid "Source is not available"
msgstr ""
@@ -21152,6 +21835,9 @@ msgstr ""
msgid "Stack trace"
msgstr ""
+msgid "Stacktrace snippet"
+msgstr ""
+
msgid "Stage"
msgstr ""
@@ -21257,6 +21943,9 @@ msgstr ""
msgid "Start thread & reopen %{noteable_name}"
msgstr ""
+msgid "Start using Directed Acyclic Graphs (DAG)"
+msgstr ""
+
msgid "Start your Free Gold Trial"
msgstr ""
@@ -21719,6 +22408,21 @@ msgstr ""
msgid "SuggestedColors|Very pale orange"
msgstr ""
+msgid "Suggestion is not applicable as the suggestion was not found."
+msgstr ""
+
+msgid "Suggestions are not applicable as one or more suggestions were not found."
+msgstr ""
+
+msgid "Suggestions are not applicable as their lines cannot overlap."
+msgstr ""
+
+msgid "Suggestions must all be on the same branch."
+msgstr ""
+
+msgid "Suggestions that change line count can't be added to batches, yet."
+msgstr ""
+
msgid "Suggestions:"
msgstr ""
@@ -21761,9 +22465,6 @@ msgstr ""
msgid "Synchronization disabled"
msgstr ""
-msgid "Synchronization of container repositories is disabled."
-msgstr ""
-
msgid "System"
msgstr ""
@@ -21944,6 +22645,38 @@ msgstr ""
msgid "Terms of Service and Privacy Policy"
msgstr ""
+msgid "Terraform|%{number} Terraform report failed to generate"
+msgid_plural "Terraform|%{number} Terraform reports failed to generate"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "Terraform|%{number} Terraform report was generated in your pipelines"
+msgid_plural "Terraform|%{number} Terraform reports were generated in your pipelines"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "Terraform|A Terraform report failed to generate."
+msgstr ""
+
+msgid "Terraform|A Terraform report was generated in your pipelines."
+msgstr ""
+
+msgid "Terraform|Generating the report caused an error."
+msgstr ""
+
+msgid "Terraform|Reported Resource Changes: %{addNum} to add, %{changeNum} to change, %{deleteNum} to delete"
+msgstr ""
+
+msgid "Terraform|The Terraform report %{name} failed to generate."
+msgstr ""
+
+msgid "Terraform|The Terraform report %{name} was generated in your pipelines."
+msgstr ""
+
msgid "Test"
msgstr ""
@@ -21972,7 +22705,7 @@ msgstr ""
msgid "TestHooks|Ensure the project has CI pipelines."
msgstr ""
-msgid "TestHooks|Ensure the project has at least one commit."
+msgid "TestHooks|Ensure the project has deployments."
msgstr ""
msgid "TestHooks|Ensure the project has issues."
@@ -22075,7 +22808,7 @@ msgstr ""
msgid "The Prometheus server responded with \"bad request\". Please check your queries are correct and are supported in your Prometheus version. %{documentationLink}"
msgstr ""
-msgid "The URL defined on the primary node that secondary nodes should use to contact it. Defaults to URL"
+msgid "The URL defined on the primary node that secondary nodes should use to contact it."
msgstr ""
msgid "The URL to use for connecting to Elasticsearch. Use a comma-separated list to support clustering (e.g., \"http://localhost:9200, http://localhost:9201\")."
@@ -22084,6 +22817,9 @@ msgstr ""
msgid "The X509 Certificate to use when mutual TLS is required to communicate with the external authorization service. If left blank, the server certificate is still validated when accessing over HTTPS."
msgstr ""
+msgid "The above settings apply to all projects with the selected compliance framework(s)."
+msgstr ""
+
msgid "The amount of seconds after which a request to get a secondary node status will time out."
msgstr ""
@@ -22385,9 +23121,6 @@ msgstr ""
msgid "The total stage shows the time it takes between creating an issue and deploying the code to production. The data will be automatically added once you have completed the full idea to production cycle."
msgstr ""
-msgid "The unique identifier for the Geo node. Must match %{geoNodeName} if it is set in gitlab.rb, otherwise it must match %{externalUrl} with a trailing slash"
-msgstr ""
-
msgid "The update action will time out after %{number_of_minutes} minutes. For big repositories, use a clone/push combination."
msgstr ""
@@ -22412,9 +23145,6 @@ msgstr ""
msgid "The user you are trying to deactivate has been active in the past %{minimum_inactive_days} days and cannot be deactivated"
msgstr ""
-msgid "The user-facing URL of the Geo node"
-msgstr ""
-
msgid "The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6."
msgstr ""
@@ -22505,6 +23235,9 @@ msgstr ""
msgid "There was a problem fetching project branches."
msgstr ""
+msgid "There was a problem fetching project tags."
+msgstr ""
+
msgid "There was a problem fetching project users."
msgstr ""
@@ -22556,6 +23289,9 @@ msgstr ""
msgid "There was an error fetching the %{replicableType}"
msgstr ""
+msgid "There was an error fetching the Geo Settings"
+msgstr ""
+
msgid "There was an error fetching the Node's Groups"
msgstr ""
@@ -22676,6 +23412,12 @@ msgstr ""
msgid "This %{issuable} is locked. Only <strong>project members</strong> can comment."
msgstr ""
+msgid "This %{noteableTypeText} is %{confidentialLinkStart}confidential%{linkEnd} and %{lockedLinkStart}locked%{linkEnd}."
+msgstr ""
+
+msgid "This %{noteableTypeText} is locked."
+msgstr ""
+
msgid "This %{viewer} could not be displayed because %{reason}. You can %{options} instead."
msgstr ""
@@ -22694,6 +23436,9 @@ msgstr ""
msgid "This action can lead to data loss. To prevent accidental actions we ask you to confirm your intention."
msgstr ""
+msgid "This also resolves all related threads"
+msgstr ""
+
msgid "This also resolves the discussion"
msgstr ""
@@ -22784,6 +23529,9 @@ msgstr ""
msgid "This epic does not exist or you don't have sufficient permission."
msgstr ""
+msgid "This feature is currently in beta. We invite you to %{linkStart}give feedback%{linkEnd}."
+msgstr ""
+
msgid "This feature requires local storage to be enabled"
msgstr ""
@@ -22817,7 +23565,7 @@ msgstr ""
msgid "This is a Work in Progress"
msgstr ""
-msgid "This is a confidential issue."
+msgid "This is a confidential %{noteableTypeText}."
msgstr ""
msgid "This is a delayed job to run in %{remainingTime}"
@@ -22844,9 +23592,6 @@ msgstr ""
msgid "This is your current session"
msgstr ""
-msgid "This issue is %{confidentialLinkStart}confidential%{linkEnd} and %{lockedLinkStart}locked%{linkEnd}."
-msgstr ""
-
msgid "This issue is confidential"
msgstr ""
@@ -22856,9 +23601,6 @@ msgstr ""
msgid "This issue is in a child epic of the filtered epic"
msgstr ""
-msgid "This issue is locked."
-msgstr ""
-
msgid "This job depends on other jobs with expired/erased artifacts: %{invalid_dependencies}"
msgstr ""
@@ -22958,6 +23700,9 @@ msgstr ""
msgid "This license has already expired."
msgstr ""
+msgid "This link points to external content"
+msgstr ""
+
msgid "This may expose confidential information as the selected fork is in another namespace that can have other members."
msgstr ""
@@ -22985,6 +23730,9 @@ msgstr ""
msgid "This page will be removed in a future release."
msgstr ""
+msgid "This pipeline does not use the %{codeStart}needs%{codeEnd} keyword and can't be represented as a directed acyclic graph."
+msgstr ""
+
msgid "This pipeline makes use of a predefined CI/CD configuration enabled by %{strongStart}Auto DevOps.%{strongEnd}"
msgstr ""
@@ -23213,6 +23961,9 @@ msgstr ""
msgid "Time from last commit to merge"
msgstr ""
+msgid "Time in seconds"
+msgstr ""
+
msgid "Time in seconds GitLab will wait for a response from the external service. When the service does not respond in time, access will be denied."
msgstr ""
@@ -23638,9 +24389,15 @@ msgstr ""
msgid "Total artifacts size: %{total_size}"
msgstr ""
+msgid "Total cores (CPUs)"
+msgstr ""
+
msgid "Total issues"
msgstr ""
+msgid "Total memory (GB)"
+msgstr ""
+
msgid "Total test time for all commits/merges"
msgstr ""
@@ -23842,6 +24599,9 @@ msgstr ""
msgid "U2F only works with HTTPS-enabled websites. Contact your administrator for more details."
msgstr ""
+msgid "UI Development Kit"
+msgstr ""
+
msgid "URL"
msgstr ""
@@ -23914,9 +24674,6 @@ msgstr ""
msgid "Unable to load the merge request widget. Try reloading the page."
msgstr ""
-msgid "Unable to process group import file"
-msgstr ""
-
msgid "Unable to resolve"
msgstr ""
@@ -23932,6 +24689,9 @@ msgstr ""
msgid "Unable to sign you in to the group with SAML due to \"%{reason}\""
msgstr ""
+msgid "Unable to suggest a path. Please refresh and try again."
+msgstr ""
+
msgid "Unable to update label prioritization at this time"
msgstr ""
@@ -23947,6 +24707,9 @@ msgstr ""
msgid "Unarchiving the project will restore people's ability to make changes to it. The repository can be committed to, and issues, comments, and other entities can be created. %{strong_start}Once active, this project shows up in the search and on the dashboard.%{strong_end}"
msgstr ""
+msgid "Unassign from commenting user"
+msgstr ""
+
msgid "Unblock"
msgstr ""
@@ -24022,6 +24785,9 @@ msgstr ""
msgid "Unreachable"
msgstr ""
+msgid "Unrecognized cluster type"
+msgstr ""
+
msgid "Unresolve"
msgstr ""
@@ -24082,6 +24848,9 @@ msgstr ""
msgid "Unsubscribes from this %{quick_action_target}."
msgstr ""
+msgid "Unsupported todo type passed. Supported todo types are: %{todo_types}"
+msgstr ""
+
msgid "Until"
msgstr ""
@@ -24109,6 +24878,9 @@ msgstr ""
msgid "Update approval rule"
msgstr ""
+msgid "Update approvers"
+msgstr ""
+
msgid "Update failed"
msgstr ""
@@ -24118,6 +24890,9 @@ msgstr ""
msgid "Update it"
msgstr ""
+msgid "Update iteration"
+msgstr ""
+
msgid "Update now"
msgstr ""
@@ -24163,7 +24938,7 @@ msgstr ""
msgid "Updated at"
msgstr ""
-msgid "Updated to"
+msgid "Updated to %{linkStart}chart v%{linkEnd}"
msgstr ""
msgid "Updating"
@@ -24193,6 +24968,9 @@ msgstr ""
msgid "Upload CSV file"
msgstr ""
+msgid "Upload License"
+msgstr ""
+
msgid "Upload New File"
msgstr ""
@@ -24274,16 +25052,19 @@ msgstr ""
msgid "UsageQuota|Pipelines"
msgstr ""
+msgid "UsageQuota|Purchase more storage"
+msgstr ""
+
msgid "UsageQuota|Repositories"
msgstr ""
msgid "UsageQuota|Repository"
msgstr ""
-msgid "UsageQuota|Storage"
+msgid "UsageQuota|Snippets"
msgstr ""
-msgid "UsageQuota|Storage usage:"
+msgid "UsageQuota|Storage"
msgstr ""
msgid "UsageQuota|This namespace has no projects which use shared runners"
@@ -24316,6 +25097,12 @@ msgstr ""
msgid "UsageQuota|Wikis"
msgstr ""
+msgid "UsageQuota|You used: %{usage} %{limit}"
+msgstr ""
+
+msgid "UsageQuota|out of %{formattedLimit} of your namespace storage"
+msgstr ""
+
msgid "Use %{code_start}::%{code_end} to create a %{link_start}scoped label set%{link_end} (eg. %{code_start}priority::1%{code_end})"
msgstr ""
@@ -24382,6 +25169,9 @@ msgstr ""
msgid "User IDs"
msgstr ""
+msgid "User Lists can only be created and modified with %{linkStart}the API%{linkEnd}"
+msgstr ""
+
msgid "User OAuth applications"
msgstr ""
@@ -24406,6 +25196,9 @@ msgstr ""
msgid "User key was successfully removed."
msgstr ""
+msgid "User list %{name} will be removed. Are you sure?"
+msgstr ""
+
msgid "User map"
msgstr ""
@@ -24427,148 +25220,10 @@ msgstr ""
msgid "User was successfully updated."
msgstr ""
-msgid "UserOnboardingTour|%{activeTour}/%{totalTours}"
-msgstr ""
-
-msgid "UserOnboardingTour|%{completed}/%{total} steps completed"
-msgstr ""
-
-msgid "UserOnboardingTour|%{emphasisStart}Well done!%{emphasisEnd}%{lineBreak}%{lineBreak}That's it for our guided tour, congratulations for making it all the way to the end!%{lineBreak}%{lineBreak}We hope this gave you a good overview of GitLab and how it can help you. We'll now show you how to create your own project and invite your colleagues."
-msgstr ""
-
-msgid "UserOnboardingTour|Adding other members to a project is done through Project Settings. Click on %{emphasisStart}Settings%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Alright, that's it for Commits. Let's take a look at the %{emphasisStart}Branches%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Awesome! Now click on %{emphasisStart}Members%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Click on one of the %{emphasisStart}Compare%{emphasisEnd} buttons to compare a branch to master."
-msgstr ""
-
-msgid "UserOnboardingTour|Click on one of the %{emphasisStart}pipeline IDs%{emphasisEnd} to see the details of a pipeline."
-msgstr ""
-
-msgid "UserOnboardingTour|Click to open the latest commit to see its details."
-msgstr ""
-
-msgid "UserOnboardingTour|Close 'Learn GitLab'"
-msgstr ""
-
-msgid "UserOnboardingTour|Commits are shown in chronological order and can be filtered by the commit message or by the branch."
-msgstr ""
-
-msgid "UserOnboardingTour|Create a project"
-msgstr ""
-
-msgid "UserOnboardingTour|Exit 'Learn GitLab'"
-msgstr ""
-
-msgid "UserOnboardingTour|Got it"
-msgstr ""
-
-msgid "UserOnboardingTour|Great job! %{clapHands} We hope the tour was helpful and that you learned how to use GitLab.%{lineBreak}%{lineBreak}We'd love to get your feedback on this tour.%{lineBreak}%{lineBreak}%{emphasisStart}How helpful would you say this guided tour was?%{emphasisEnd}%{lineBreak}%{lineBreak}"
-msgstr ""
-
-msgid "UserOnboardingTour|Guided GitLab Tour"
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can compare the changes of this branch to another one. Changes are divided by files so that it's easier to see what was changed where."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can create a project from scratch, start with a template or import a repository from other platforms. Whatever you choose, we'll guide you through the process.%{lineBreak}%{lineBreak}Fill in your new project information and click on %{emphasisStart}Create Project%{emphasisEnd} to progress to the next step."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can see the breakdown of the pipelines: its stages and jobs in each of the stages and their status.%{lineBreak}%{lineBreak}Our CI/CD pipelines are quite complex, most of our users have fewer and simpler pipelines."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can see the current members of the project (just you at the moment) and invite new members.%{lineBreak}%{lineBreak}You can invite multiple members at once (existing GitLab users or invite by email) and you can also set their roles and permissions.%{lineBreak}%{lineBreak}Add a few members and click on %{emphasisStart}Add to project%{emphasisEnd} to complete this step."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can see what changes were made with this commit, on what branch and if there's a related merge request. The status of the pipeline will also show up if CI/CD is set up.%{lineBreak}%{lineBreak}You can also comment on the lines of code that were changed and start a discussion with your colleagues!"
-msgstr ""
-
-msgid "UserOnboardingTour|Here's an overview of branches in the %{emphasisStart}%{projectName}%{emphasisEnd} project. They're split into Active and Stale.%{lineBreak}%{lineBreak}From here, you can create a new merge request from a branch, or compare the branch to any other branch in the project. By default, it will compare it to the master branch."
-msgstr ""
-
-msgid "UserOnboardingTour|Invite colleagues"
-msgstr ""
-
-msgid "UserOnboardingTour|Issues are great for communicating and keeping track of progress in GitLab. These are all issues that are open in the %{emphasisStart}%{projectName}%{emphasisEnd}.%{lineBreak}%{lineBreak}You can help us improve GitLab by contributing work to issues that are labeled <span class=\"badge color-label accept-mr-label\">Accepting merge requests</span>.%{lineBreak}%{lineBreak}This list can be filtered by labels, milestones, assignees, authors... We'll show you how it looks when the list is filtered by a label."
-msgstr ""
-
-msgid "UserOnboardingTour|Learn GitLab"
-msgstr ""
-
-msgid "UserOnboardingTour|Let's take a closer look at a merge request. Click on the title of one."
-msgstr ""
-
-msgid "UserOnboardingTour|Let's take a closer look at all the commits. Click on %{emphasisStart}Commits%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Let's take a closer look at the repository of this project. Click on %{emphasisStart}Repository%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|No thanks"
-msgstr ""
-
-msgid "UserOnboardingTour|Ok, let's go"
-msgstr ""
-
-msgid "UserOnboardingTour|Ok, show me"
-msgstr ""
-
-msgid "UserOnboardingTour|Open one of the issues by clicking on its title."
-msgstr ""
-
-msgid "UserOnboardingTour|Restart this step"
-msgstr ""
-
-msgid "UserOnboardingTour|Skip this step"
-msgstr ""
-
-msgid "UserOnboardingTour|Sweet! Your project was created and is ready to be used.%{lineBreak}%{lineBreak}You can start adding files to the repository or clone it. One last thing we want to show you is how to invite your colleagues to your new project."
-msgstr ""
-
-msgid "UserOnboardingTour|Take a look. Here's a nifty menu for quickly creating issues, merge requests, snippets, projects and groups. Click on it and select \"New project\" from the \"GitLab\" section to get started."
-msgstr ""
-
-msgid "UserOnboardingTour|Thanks for taking the guided tour. Remember, if you want to go through it again, you can start %{emphasisStart}Learn GitLab%{emphasisEnd} in the help menu on the top right."
-msgstr ""
-
-msgid "UserOnboardingTour|Thanks for the feedback! %{thumbsUp}"
-msgstr ""
-
-msgid "UserOnboardingTour|That's it for issues. Let'st take a look at %{emphasisStart}Merge Requests%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|That's it for merge requests. Now for the final part of this guided tour - the %{emphasisStart}CI/CD%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|That's it for the Repository. Let's take a look at the %{emphasisStart}Issues%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|The structure of this page is very similar to issues. Status, description, discussion and the sidebar are all here.%{lineBreak}%{lineBreak}But take a look below the description and you'll notice that there's more information about the merge request, the CI/CD pipeline and the options for approving it.%{lineBreak}%{lineBreak}Alongside the discussion you can also see more information about commits in this merge request, the status of pipelines and review all changes that were made."
-msgstr ""
-
-msgid "UserOnboardingTour|There's a lot of information here but don't worry, we'll go through it.%{lineBreak}%{lineBreak}On the top you can see the status of the issue and when it was opened and by whom. Directly below it is the issue description and below that are other %{emphasisStart}related issues%{emphasisEnd} and %{emphasisStart}merge requests%{emphasisEnd} (if any). Then below that is the %{emphasisStart}discussion%{emphasisEnd}, that's where most of the communication happens.%{lineBreak}%{lineBreak}On the right, there's a sidebar where you can view/change the %{emphasisStart}assignee, milestone, due date, labels, weight%{emphasisEnd}, etc."
+msgid "UserList|Delete %{name}?"
msgstr ""
-msgid "UserOnboardingTour|These are all the CI/CD pipelines we have for our %{emphasisStart}%{projectName}%{emphasisEnd} project.%{lineBreak}%{lineBreak}Here you can see the status of each pipeline, for what commit it's running for, its stages and the status for them."
-msgstr ""
-
-msgid "UserOnboardingTour|These are all the issues that are available for community contributions. Let's take a closer look at one of them."
-msgstr ""
-
-msgid "UserOnboardingTour|This is an overview of all merge requests in this project. Similarly to the issues overview it can be filtered down by things like labels, milestones, authors, assignees, etc."
-msgstr ""
-
-msgid "UserOnboardingTour|This is the repository for the %{emphasisStart}%{projectName}%{emphasisEnd} project. All our code is stored here. Feel free to explore and take a closer look at folders and files.%{lineBreak}%{lineBreak}Above the file structure you can see the latest commit, who the author is and the status of the CI/CD pipeline.%{lineBreak}%{lineBreak}If you scroll down below the file structure, you'll find the Readme of this project. This is defined in the README.md file at the root of the repository."
-msgstr ""
-
-msgid "UserOnboardingTour|Welcome to the project overview of the %{emphasisStart}%{projectName}%{emphasisEnd} project. This is the project that we use to work on GitLab. At first, a project seems like a simple repository, but at GitLab, a project is so much more.%{lineBreak}%{lineBreak}You can create projects for hosting your codebase, use it as an issue tracker, collaborate on code, and continuously build, test, and deploy your app with built-in GitLab CI/CD."
+msgid "UserList|created %{timeago}"
msgstr ""
msgid "UserProfile|Activity"
@@ -24721,6 +25376,9 @@ msgstr ""
msgid "UsersSelect|Unassigned"
msgstr ""
+msgid "Using %{codeStart}needs%{codeEnd} allows jobs to run before their stage is reached, as soon as their individual dependencies are met, which speeds up your pipelines."
+msgstr ""
+
msgid "Using %{code_start}::%{code_end} denotes a %{link_start}scoped label set%{link_end}"
msgstr ""
@@ -24805,9 +25463,6 @@ msgstr ""
msgid "Versions"
msgstr ""
-msgid "Very helpful"
-msgstr ""
-
msgid "View Documentation"
msgstr ""
@@ -24960,6 +25615,9 @@ msgstr ""
msgid "VisibilityLevel|Unknown"
msgstr ""
+msgid "Visit settings page"
+msgstr ""
+
msgid "VisualReviewApp|%{stepStart}Step 1%{stepEnd}. Copy the following script:"
msgstr ""
@@ -25053,6 +25711,9 @@ msgstr ""
msgid "VulnerabilityManagement|Something went wrong while trying to delete the comment. Please try again later."
msgstr ""
+msgid "VulnerabilityManagement|Something went wrong while trying to refresh the vulnerability. Please try again later."
+msgstr ""
+
msgid "VulnerabilityManagement|Something went wrong while trying to retrieve the vulnerability history. Please try again later."
msgstr ""
@@ -25095,6 +25756,9 @@ msgstr ""
msgid "Vulnerability|Class"
msgstr ""
+msgid "Vulnerability|Crash Address"
+msgstr ""
+
msgid "Vulnerability|Description"
msgstr ""
@@ -25170,9 +25834,6 @@ msgstr ""
msgid "We couldn't reach the Prometheus server. Either the server no longer exists or the configuration details need updating."
msgstr ""
-msgid "We created a short guided tour that will help you learn the basics of GitLab and how it will help you be better at your job. It should only take a couple of minutes. You will be guided by two types of helpers, best recognized by their color."
-msgstr ""
-
msgid "We detected potential spam in the %{humanized_resource_name}. Please solve the reCAPTCHA to proceed."
msgstr ""
@@ -25206,6 +25867,9 @@ msgstr ""
msgid "We've found no vulnerabilities"
msgstr ""
+msgid "Web Application Firewall"
+msgstr ""
+
msgid "Web IDE"
msgstr ""
@@ -25266,9 +25930,6 @@ msgstr ""
msgid "Welcome to GitLab.com<br>@%{name}!"
msgstr ""
-msgid "Welcome to the Guided GitLab Tour"
-msgstr ""
-
msgid "Welcome to the guided GitLab tour"
msgstr ""
@@ -25318,9 +25979,6 @@ msgstr ""
msgid "While it's rare to have no vulnerabilities, it can happen. In any event, we ask that you please double check your settings to make sure you've set up your dashboard correctly."
msgstr ""
-msgid "White helpers give contextual information."
-msgstr ""
-
msgid "Who can be an approver?"
msgstr ""
@@ -25366,12 +26024,18 @@ msgstr ""
msgid "WikiEmptyIssueMessage|Suggest wiki improvement"
msgstr ""
+msgid "WikiEmptyIssueMessage|You must be a group member in order to add wiki pages. If you have suggestions for how to improve the wiki for this group, consider opening an issue in the %{issues_link}."
+msgstr ""
+
msgid "WikiEmptyIssueMessage|You must be a project member in order to add wiki pages. If you have suggestions for how to improve the wiki for this project, consider opening an issue in the %{issues_link}."
msgstr ""
msgid "WikiEmptyIssueMessage|issue tracker"
msgstr ""
+msgid "WikiEmpty|A wiki is where you can store all the details about your group. This can include why you've created it, its principles, how to use it, and so on."
+msgstr ""
+
msgid "WikiEmpty|A wiki is where you can store all the details about your project. This can include why you've created it, its principles, how to use it, and so on."
msgstr ""
@@ -25381,12 +26045,21 @@ msgstr ""
msgid "WikiEmpty|Suggest wiki improvement"
msgstr ""
+msgid "WikiEmpty|The wiki lets you write documentation for your group"
+msgstr ""
+
msgid "WikiEmpty|The wiki lets you write documentation for your project"
msgstr ""
+msgid "WikiEmpty|This group has no wiki pages"
+msgstr ""
+
msgid "WikiEmpty|This project has no wiki pages"
msgstr ""
+msgid "WikiEmpty|You must be a group member in order to add wiki pages."
+msgstr ""
+
msgid "WikiEmpty|You must be a project member in order to add wiki pages."
msgstr ""
@@ -25663,13 +26336,13 @@ msgstr ""
msgid "You can now export your security dashboard to a CSV report."
msgstr ""
-msgid "You can now submit a merge request to get this change into the original branch."
+msgid "You can now manage alert endpoint configuration in the Alerts section on the Operations settings page. Fields on this page have been deprecated."
msgstr ""
-msgid "You can now submit a merge request to get this change into the original project."
+msgid "You can now submit a merge request to get this change into the original branch."
msgstr ""
-msgid "You can only add files when you are on a branch"
+msgid "You can now submit a merge request to get this change into the original project."
msgstr ""
msgid "You can only edit files when you are on a branch"
@@ -25891,7 +26564,7 @@ msgstr ""
msgid "You need to upload a Google Takeout archive."
msgstr ""
-msgid "You reached %{usage_in_percent} of %{namespace_name}'s capacity (%{used_storage} of %{storage_limit})"
+msgid "You reached %{usage_in_percent} of %{namespace_name}'s storage capacity (%{used_storage} of %{storage_limit})"
msgstr ""
msgid "You tried to fork %{link_to_the_project} but it failed for the following reason:"
@@ -26194,13 +26867,13 @@ msgstr ""
msgid "Your subscription expired!"
msgstr ""
-msgid "Your subscription has been downgraded"
+msgid "Your subscription has been downgraded."
msgstr ""
-msgid "Your subscription will automatically renew in %{remaining_days}"
+msgid "Your subscription will automatically renew in %{remaining_days}."
msgstr ""
-msgid "Your subscription will expire in %{remaining_days}"
+msgid "Your subscription will expire in %{remaining_days}."
msgstr ""
msgid "Zoom meeting added"
@@ -26282,9 +26955,6 @@ msgstr ""
msgid "branch name"
msgstr ""
-msgid "build pipeline reference mismatch"
-msgstr ""
-
msgid "by"
msgstr ""
@@ -26303,6 +26973,9 @@ msgstr ""
msgid "cannot block others"
msgstr ""
+msgid "cannot contain HTML/XML tags, including any word between angle brackets (<,>)."
+msgstr ""
+
msgid "cannot include leading slash or directory traversal."
msgstr ""
@@ -26333,65 +27006,6 @@ msgstr ""
msgid "ciReport|%{remainingPackagesCount} more"
msgstr ""
-msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability"
-msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities"
-msgstr[0] ""
-msgstr[1] ""
-msgstr[2] ""
-msgstr[3] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability for the source branch only"
-msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities for the source branch only"
-msgstr[0] ""
-msgstr[1] ""
-msgstr[2] ""
-msgstr[3] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerability"
-msgid_plural "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerabilities"
-msgstr[0] ""
-msgstr[1] ""
-msgstr[2] ""
-msgstr[3] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new vulnerability"
-msgid_plural "ciReport|%{reportType} %{status} detected %{newCount} new vulnerabilities"
-msgstr[0] ""
-msgstr[1] ""
-msgstr[2] ""
-msgstr[3] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities for the source branch only"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{fixedCount} fixed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} vulnerability for the source branch only"
-msgid_plural "ciReport|%{reportType} %{status} detected %{newCount} vulnerabilities for the source branch only"
-msgstr[0] ""
-msgstr[1] ""
-msgstr[2] ""
-msgstr[3] ""
-
-msgid "ciReport|%{reportType} %{status} detected no new vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected no vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected no vulnerabilities for the source branch only"
-msgstr ""
-
msgid "ciReport|%{reportType} is loading"
msgstr ""
@@ -26410,7 +27024,7 @@ msgstr ""
msgid "ciReport|All projects"
msgstr ""
-msgid "ciReport|All report types"
+msgid "ciReport|All scanner types"
msgstr ""
msgid "ciReport|All severities"
@@ -26434,6 +27048,9 @@ msgstr ""
msgid "ciReport|Container scanning detects known vulnerabilities in your docker images."
msgstr ""
+msgid "ciReport|Coverage Fuzzing"
+msgstr ""
+
msgid "ciReport|Create a merge request to implement this solution, or download and apply the patch manually."
msgstr ""
@@ -26594,6 +27211,9 @@ msgstr ""
msgid "customize"
msgstr ""
+msgid "data"
+msgstr ""
+
msgid "date must not be after 9999-12-31"
msgstr ""
@@ -26668,6 +27288,9 @@ msgstr ""
msgid "entries cannot contain HTML tags"
msgstr ""
+msgid "epic"
+msgstr ""
+
msgid "error"
msgstr ""
@@ -26689,9 +27312,6 @@ msgstr ""
msgid "expires on %{timebox_due_date}"
msgstr ""
-msgid "external_url"
-msgstr ""
-
msgid "failed"
msgstr ""
@@ -26729,9 +27349,6 @@ msgstr ""
msgid "from"
msgstr ""
-msgid "geo_node_name"
-msgstr ""
-
msgid "group"
msgstr ""
@@ -26753,9 +27370,6 @@ msgstr ""
msgid "https://your-bitbucket-server"
msgstr ""
-msgid "image"
-msgstr ""
-
msgid "image diff"
msgstr ""
@@ -26901,6 +27515,9 @@ msgstr ""
msgid "load it anyway"
msgstr ""
+msgid "loading"
+msgstr ""
+
msgid "locked by %{path_lock_user_name} %{created_at}"
msgstr ""
@@ -27031,9 +27648,6 @@ msgstr ""
msgid "mrWidget|Deployment statistics are not available currently"
msgstr ""
-msgid "mrWidget|Detect issues before deployment with a CI pipeline that continuously tests your code. We created a quick guide that will show you how to create one. Make your code more secure and more robust in just a minute."
-msgstr ""
-
msgid "mrWidget|Did not close"
msgstr ""
@@ -27196,6 +27810,9 @@ msgstr ""
msgid "mrWidget|To approve this merge request, please enter your password. This project requires all approvals to be authenticated."
msgstr ""
+msgid "mrWidget|Use %{linkStart}CI pipelines to test your code%{linkEnd}, simply add a GitLab CI configuration file to your project. It only takes a minute to make your code more secure and robust."
+msgstr ""
+
msgid "mrWidget|When this merge request is ready, remove the WIP: prefix from the title to allow it to be merged"
msgstr ""
@@ -27232,6 +27849,9 @@ msgstr ""
msgid "must be greater than start date"
msgstr ""
+msgid "must contain only valid frameworks"
+msgstr ""
+
msgid "my-awesome-group"
msgstr ""
@@ -27244,6 +27864,9 @@ msgstr ""
msgid "needs to be between 10 minutes and 1 month"
msgstr ""
+msgid "never"
+msgstr ""
+
msgid "never expires"
msgstr ""
@@ -27271,6 +27894,9 @@ msgstr ""
msgid "nounSeries|%{firstItem} and %{lastItem}"
msgstr ""
+msgid "nounSeries|%{item}"
+msgstr ""
+
msgid "nounSeries|%{item}, %{nextItem}"
msgstr ""
@@ -27417,6 +28043,9 @@ msgstr ""
msgid "revised"
msgstr ""
+msgid "satisfied"
+msgstr ""
+
msgid "score"
msgstr ""
@@ -27447,6 +28076,9 @@ msgstr ""
msgid "severity|Unknown"
msgstr ""
+msgid "should be an array of %{object_name} objects"
+msgstr ""
+
msgid "should be greater than or equal to %{access} inherited membership from group %{group_name}"
msgstr ""
@@ -27501,10 +28133,13 @@ msgstr ""
msgid "suggestPipeline|2/2: Commit your changes"
msgstr ""
-msgid "suggestPipeline|Commit the changes and your pipeline will automatically run for the first time."
+msgid "suggestPipeline|Choose %{boldStart}Code Quality%{boldEnd} to add a pipeline that tests the quality of your code."
msgstr ""
-msgid "suggestPipeline|We recommend the %{boldStart}Code Quality%{boldEnd} template, which will add a report widget to your Merge Requests. This way you’ll learn about code quality degradations much sooner. %{footerStart} Goodbye technical debt! %{footerEnd}"
+msgid "suggestPipeline|The template is ready! You can now commit it to create your first pipeline."
+msgstr ""
+
+msgid "suggestPipeline|We’re adding a GitLab CI configuration file to add a pipeline to the project. You could create it manually, but we recommend that you start with a GitLab template that works out of the box."
msgstr ""
msgid "syntax is correct"
@@ -27564,6 +28199,9 @@ msgstr ""
msgid "uses Kubernetes clusters to deploy your code!"
msgstr ""
+msgid "v%{version} published %{timeAgo}"
+msgstr ""
+
msgid "verify ownership"
msgstr ""
diff --git a/locale/hi_IN/gitlab.po b/locale/hi_IN/gitlab.po
index 41fcd919403..e0f57a99a5f 100644
--- a/locale/hi_IN/gitlab.po
+++ b/locale/hi_IN/gitlab.po
@@ -2,7 +2,7 @@ msgid ""
msgstr ""
"Project-Id-Version: gitlab-ee\n"
"Report-Msgid-Bugs-To: \n"
-"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Last-Translator: \n"
"Language-Team: Hindi\n"
"Language: hi_IN\n"
"MIME-Version: 1.0\n"
@@ -10,9 +10,11 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Crowdin-Project: gitlab-ee\n"
+"X-Crowdin-Project-ID: 288872\n"
"X-Crowdin-Language: hi\n"
"X-Crowdin-File: /master/locale/gitlab.pot\n"
-"PO-Revision-Date: 2020-06-08 15:11\n"
+"X-Crowdin-File-ID: 6\n"
+"PO-Revision-Date: 2020-07-02 01:19\n"
msgid " %{start} to %{end}"
msgstr ""
@@ -197,6 +199,11 @@ msgid_plural "%d metrics"
msgstr[0] ""
msgstr[1] ""
+msgid "%d milestone"
+msgid_plural "%d milestones"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "%d minute"
msgid_plural "%d minutes"
msgstr[0] ""
@@ -242,11 +249,6 @@ msgid_plural "%d unresolved threads"
msgstr[0] ""
msgstr[1] ""
-msgid "%d url scanned"
-msgid_plural "%d urls scanned"
-msgstr[0] ""
-msgstr[1] ""
-
msgid "%d vulnerability"
msgid_plural "%d vulnerabilities"
msgstr[0] ""
@@ -324,9 +326,15 @@ msgstr[1] ""
msgid "%{count} related %{pluralized_subject}: %{links}"
msgstr ""
+msgid "%{dashboard_path} could not be found."
+msgstr ""
+
msgid "%{days} days until tags are automatically removed"
msgstr ""
+msgid "%{deployLinkStart}Use a template to deploy to ECS%{deployLinkEnd}, or use a docker image to %{commandsLinkStart}run AWS commands in GitLab CI/CD%{commandsLinkEnd}."
+msgstr ""
+
msgid "%{description}- Sentry event: %{errorUrl}- First seen: %{firstSeen}- Last seen: %{lastSeen} %{countLabel}: %{count}%{userCountLabel}: %{userCount}"
msgstr ""
@@ -369,12 +377,18 @@ msgstr ""
msgid "%{host} sign-in from new location"
msgstr ""
-msgid "%{icon}You are about to add %{usersTag} people to the discussion. Proceed with caution."
+msgid "%{icon}You are about to add %{usersTag} people to the discussion. They will all receive a notification."
+msgstr ""
+
+msgid "%{integrations_link_start}Integrations%{link_end} enable you to make third-party applications part of your GitLab workflow. If the available integrations don't meet your needs, consider using a %{webhooks_link_start}webhook%{link_end}."
msgstr ""
msgid "%{issuableType} will be removed! Are you sure?"
msgstr ""
+msgid "%{issuesCount} issues in this group"
+msgstr ""
+
msgid "%{issuesSize} issues"
msgstr ""
@@ -384,7 +398,7 @@ msgstr ""
msgid "%{labelStart}Class:%{labelEnd} %{class}"
msgstr ""
-msgid "%{labelStart}Confidence:%{labelEnd} %{confidence}"
+msgid "%{labelStart}Evidence:%{labelEnd} %{evidence}"
msgstr ""
msgid "%{labelStart}File:%{labelEnd} %{file}"
@@ -402,6 +416,9 @@ msgstr ""
msgid "%{labelStart}Report Type:%{labelEnd} %{reportType}"
msgstr ""
+msgid "%{labelStart}Scanner:%{labelEnd} %{scanner}"
+msgstr ""
+
msgid "%{labelStart}Severity:%{labelEnd} %{severity}"
msgstr ""
@@ -426,9 +443,6 @@ msgstr ""
msgid "%{link_start}Read more%{link_end} about role permissions"
msgstr ""
-msgid "%{link} can be used for binding events when something is happening within the project."
-msgstr ""
-
msgid "%{listToShow}, and %{awardsListLength} more."
msgstr ""
@@ -462,6 +476,9 @@ msgstr ""
msgid "%{name} found %{resultsString}"
msgstr ""
+msgid "%{name} is already being used for another emoji"
+msgstr ""
+
msgid "%{name} is scheduled for %{action}"
msgstr ""
@@ -509,6 +526,9 @@ msgstr ""
msgid "%{primary} (%{secondary})"
msgstr ""
+msgid "%{ref} cannot be added: %{error}"
+msgstr ""
+
msgid "%{releases} release"
msgid_plural "%{releases} releases"
msgstr[0] ""
@@ -517,13 +537,37 @@ msgstr[1] ""
msgid "%{remaining_approvals} left"
msgstr ""
-msgid "%{retryButtonStart}Try again%{retryButtonEnd} or %{newFileButtonStart}attach a new file%{newFileButtonEnd}"
+msgid "%{reportType} %{status} detected %{critical} critical and %{high} high severity vulnerabilities out of %{total}."
+msgstr ""
+
+msgid "%{reportType} %{status} detected %{critical} critical and %{high} high severity vulnerabilities."
+msgstr ""
+
+msgid "%{reportType} %{status} detected %{critical} critical severity vulnerabilities out of %{total}."
+msgstr ""
+
+msgid "%{reportType} %{status} detected %{critical} critical severity vulnerability."
+msgid_plural "%{reportType} %{status} detected %{critical} critical severity vulnerabilities."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{reportType} %{status} detected %{high} high severity vulnerabilities out of %{total}."
msgstr ""
-msgid "%{screenreaderOnlyStart}Keyboard shorcuts%{screenreaderOnlyEnd} Disabled"
+msgid "%{reportType} %{status} detected %{high} high severity vulnerability."
+msgid_plural "%{reportType} %{status} detected %{high} high severity vulnerabilities."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{reportType} %{status} detected %{other} vulnerability."
+msgid_plural "%{reportType} %{status} detected %{other} vulnerabilities."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{reportType} %{status} detected no new vulnerabilities."
msgstr ""
-msgid "%{screenreaderOnlyStart}Keyboard shorcuts%{screenreaderOnlyEnd} Enabled"
+msgid "%{retryButtonStart}Try again%{retryButtonEnd} or %{newFileButtonStart}attach a new file%{newFileButtonEnd}"
msgstr ""
msgid "%{service_title} %{message}."
@@ -620,6 +664,12 @@ msgstr ""
msgid "%{token}..."
msgstr ""
+msgid "%{totalCpu} (%{freeSpacePercentage}%{percentSymbol} free)"
+msgstr ""
+
+msgid "%{totalMemory} (%{freeSpacePercentage}%{percentSymbol} free)"
+msgstr ""
+
msgid "%{totalWeight} total weight"
msgstr ""
@@ -650,6 +700,12 @@ msgstr ""
msgid "%{verb} %{time_spent_value} spent time."
msgstr ""
+msgid "%{webhooks_link_start}%{webhook_type}%{link_end} enable you to send notifications to web applications in response to events in a group or project."
+msgstr ""
+
+msgid "%{webhooks_link_start}%{webhook_type}%{link_end} enable you to send notifications to web applications in response to events in a group or project. We recommend using an %{integrations_link_start}integration%{link_end} in preference to a webhook."
+msgstr ""
+
msgid "'%{level}' is not a valid visibility level"
msgstr ""
@@ -676,9 +732,6 @@ msgstr ""
msgid "(No changes)"
msgstr ""
-msgid "(Show all)"
-msgstr ""
-
msgid "(check progress)"
msgstr ""
@@ -745,6 +798,9 @@ msgstr ""
msgid "0 for unlimited"
msgstr ""
+msgid "0 for unlimited, only effective with remote storage enabled."
+msgstr ""
+
msgid "1 %{type} addition"
msgid_plural "%{count} %{type} additions"
msgstr[0] ""
@@ -892,6 +948,12 @@ msgstr ""
msgid "<code>\"johnsmith@example.com\": \"johnsmith@example.com\"</code> will add \"By <a href=\"#\">johnsmith@example.com</a>\" to all issues and comments originally created by johnsmith@example.com. By default, the email address or username is masked to ensure the user's privacy. Use this option if you want to show the full email address."
msgstr ""
+msgid "<code>Masked</code> to prevent the values from being displayed in job logs (must match certain regexp requirements)."
+msgstr ""
+
+msgid "<code>Protected</code> to expose them to protected branches or tags only."
+msgstr ""
+
msgid "<namespace / project>"
msgstr ""
@@ -919,9 +981,6 @@ msgstr ""
msgid "A .NET Core console application template, customizable for any .NET Core project"
msgstr ""
-msgid "A DAG must have two dependent jobs to be visualized on this tab."
-msgstr ""
-
msgid "A GitBook site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
msgstr ""
@@ -952,6 +1011,12 @@ msgstr ""
msgid "A deleted user"
msgstr ""
+msgid "A file has been changed."
+msgstr ""
+
+msgid "A file was not found."
+msgstr ""
+
msgid "A file with '%{file_name}' already exists in %{branch} branch"
msgstr ""
@@ -1018,7 +1083,7 @@ msgstr ""
msgid "A subscription will trigger a new pipeline on the default branch of this project when a pipeline successfully completes for a new tag on the %{default_branch_docs} of the subscribed project."
msgstr ""
-msgid "A terraform report was generated in your pipelines."
+msgid "A suggestion is not applicable."
msgstr ""
msgid "A user with write access to the source branch selected this option"
@@ -1189,6 +1254,9 @@ msgstr ""
msgid "Action to take when receiving an alert."
msgstr ""
+msgid "Actions"
+msgstr ""
+
msgid "Activate"
msgstr ""
@@ -1245,6 +1313,9 @@ msgstr ""
msgid "Add LICENSE"
msgstr ""
+msgid "Add New Node"
+msgstr ""
+
msgid "Add README"
msgstr ""
@@ -1320,6 +1391,9 @@ msgstr ""
msgid "Add approval rule"
msgstr ""
+msgid "Add approvers"
+msgstr ""
+
msgid "Add bold text"
msgstr ""
@@ -1377,6 +1451,9 @@ msgstr ""
msgid "Add strikethrough text"
msgstr ""
+msgid "Add suggestion to batch"
+msgstr ""
+
msgid "Add system hook"
msgstr ""
@@ -1476,6 +1553,9 @@ msgstr ""
msgid "Admin Note"
msgstr ""
+msgid "Admin Notifications"
+msgstr ""
+
msgid "Admin Overview"
msgstr ""
@@ -1587,6 +1667,9 @@ msgstr ""
msgid "AdminSettings|Integrations configured here will automatically apply to all projects on this instance."
msgstr ""
+msgid "AdminSettings|Moved to integrations"
+msgstr ""
+
msgid "AdminSettings|No required pipeline"
msgstr ""
@@ -1817,12 +1900,18 @@ msgid_plural "Alerts"
msgstr[0] ""
msgstr[1] ""
+msgid "AlertManagement| assign yourself"
+msgstr ""
+
msgid "AlertManagement|Acknowledged"
msgstr ""
msgid "AlertManagement|Alert"
msgstr ""
+msgid "AlertManagement|Alert assignee(s): %{assignees}"
+msgstr ""
+
msgid "AlertManagement|Alert detail"
msgstr ""
@@ -1838,9 +1927,15 @@ msgstr ""
msgid "AlertManagement|All alerts"
msgstr ""
+msgid "AlertManagement|Assign To"
+msgstr ""
+
msgid "AlertManagement|Assign status"
msgstr ""
+msgid "AlertManagement|Assignee"
+msgstr ""
+
msgid "AlertManagement|Assignees"
msgstr ""
@@ -1859,9 +1954,6 @@ msgstr ""
msgid "AlertManagement|Edit"
msgstr ""
-msgid "AlertManagement|End time"
-msgstr ""
-
msgid "AlertManagement|Events"
msgstr ""
@@ -1880,10 +1972,13 @@ msgstr ""
msgid "AlertManagement|More information"
msgstr ""
+msgid "AlertManagement|No Matching Results"
+msgstr ""
+
msgid "AlertManagement|No alert data to display."
msgstr ""
-msgid "AlertManagement|No alerts available to display. If you think you're seeing this message in error, refresh the page."
+msgid "AlertManagement|No alerts available to display. See %{linkStart}enabling alert management%{linkEnd} for more information on adding alerts to the list."
msgstr ""
msgid "AlertManagement|No alerts to display."
@@ -1892,6 +1987,9 @@ msgstr ""
msgid "AlertManagement|None"
msgstr ""
+msgid "AlertManagement|None -"
+msgstr ""
+
msgid "AlertManagement|Open"
msgstr ""
@@ -1928,9 +2026,18 @@ msgstr ""
msgid "AlertManagement|There was an error displaying the alerts. Confirm your endpoint's configuration details to ensure alerts appear."
msgstr ""
+msgid "AlertManagement|There was an error while updating the assignee(s) list. Please try again."
+msgstr ""
+
+msgid "AlertManagement|There was an error while updating the assignee(s) of the alert. Please try again."
+msgstr ""
+
msgid "AlertManagement|There was an error while updating the status of the alert. Please try again."
msgstr ""
+msgid "AlertManagement|This assignee cannot be assigned to this alert."
+msgstr ""
+
msgid "AlertManagement|Tool"
msgstr ""
@@ -2078,6 +2185,9 @@ msgstr ""
msgid "Allow users to request access (if visibility is public or internal)"
msgstr ""
+msgid "Allowed Geo IP"
+msgstr ""
+
msgid "Allowed email domain restriction only permitted for top-level groups"
msgstr ""
@@ -2111,6 +2221,9 @@ msgstr ""
msgid "Amazon Web Services"
msgstr ""
+msgid "Amazon Web Services Logo"
+msgstr ""
+
msgid "Amazon authentication is not %{link_start}correctly configured%{link_end}. Ask your GitLab administrator if you want to use this service."
msgstr ""
@@ -2168,10 +2281,16 @@ msgstr ""
msgid "An error occurred when updating the issue weight"
msgstr ""
+msgid "An error occurred while acknowledging the notification. Refresh the page and try again."
+msgstr ""
+
+msgid "An error occurred while adding approvers"
+msgstr ""
+
msgid "An error occurred while adding formatted title for epic"
msgstr ""
-msgid "An error occurred while checking group path"
+msgid "An error occurred while checking group path. Please refresh and try again."
msgstr ""
msgid "An error occurred while committing your changes."
@@ -2315,7 +2434,7 @@ msgstr ""
msgid "An error occurred while loading milestones"
msgstr ""
-msgid "An error occurred while loading terraform report"
+msgid "An error occurred while loading project creation UI"
msgstr ""
msgid "An error occurred while loading the data. Please try again."
@@ -2369,6 +2488,9 @@ msgstr ""
msgid "An error occurred while rendering preview broadcast message"
msgstr ""
+msgid "An error occurred while rendering the editor"
+msgstr ""
+
msgid "An error occurred while reordering issues."
msgstr ""
@@ -2465,7 +2587,7 @@ msgstr ""
msgid "Analyze your dependencies for known vulnerabilities."
msgstr ""
-msgid "Analyze your source code and git history for secrets"
+msgid "Analyze your source code and git history for secrets."
msgstr ""
msgid "Analyze your source code for known vulnerabilities."
@@ -2579,12 +2701,18 @@ msgstr ""
msgid "Apply suggestion"
msgstr ""
+msgid "Apply suggestions"
+msgstr ""
+
msgid "Apply template"
msgstr ""
msgid "Apply this approval rule to any branch or a specific protected branch."
msgstr ""
+msgid "Applying"
+msgstr ""
+
msgid "Applying a template will replace the existing issue description. Any changes you have made will be lost."
msgstr ""
@@ -2597,12 +2725,18 @@ msgstr ""
msgid "Applying multiple commands"
msgstr ""
-msgid "Applying suggestion"
+msgid "Applying suggestion..."
+msgstr ""
+
+msgid "Applying suggestions..."
msgstr ""
msgid "Approval rules"
msgstr ""
+msgid "Approval rules reset to project defaults"
+msgstr ""
+
msgid "ApprovalRuleRemove|%d member"
msgid_plural "ApprovalRuleRemove|%d members"
msgstr[0] ""
@@ -2656,6 +2790,9 @@ msgstr ""
msgid "Approved"
msgstr ""
+msgid "Approved MRs"
+msgstr ""
+
msgid "Approved by: "
msgstr ""
@@ -2869,6 +3006,9 @@ msgstr ""
msgid "Assign"
msgstr ""
+msgid "Assign Iteration"
+msgstr ""
+
msgid "Assign custom color like #FF0000"
msgstr ""
@@ -2887,6 +3027,9 @@ msgstr ""
msgid "Assign to"
msgstr ""
+msgid "Assign to commenting user"
+msgstr ""
+
msgid "Assign yourself to these issues"
msgstr ""
@@ -2913,6 +3056,9 @@ msgid_plural "%d Assignees"
msgstr[0] ""
msgstr[1] ""
+msgid "Assignee has no permissions"
+msgstr ""
+
msgid "Assignee lists not available with your current license"
msgstr ""
@@ -2922,6 +3068,9 @@ msgstr ""
msgid "Assignee(s)"
msgstr ""
+msgid "Assignees"
+msgstr ""
+
msgid "Assigns %{assignee_users_sentence}."
msgstr ""
@@ -2957,6 +3106,9 @@ msgstr ""
msgid "Audit Events is a way to keep track of important events that happened in GitLab."
msgstr ""
+msgid "Audit Log"
+msgstr ""
+
msgid "AuditEvents|(removed)"
msgstr ""
@@ -2993,6 +3145,9 @@ msgstr ""
msgid "AuditLogs|IP Address"
msgstr ""
+msgid "AuditLogs|Member Events"
+msgstr ""
+
msgid "AuditLogs|No matching %{type} found."
msgstr ""
@@ -3275,9 +3430,6 @@ msgstr ""
msgid "Badges|e.g. %{exampleUrl}"
msgstr ""
-msgid "Badge|New"
-msgstr ""
-
msgid "Balsamiq file could not be loaded."
msgstr ""
@@ -3326,6 +3478,12 @@ msgstr ""
msgid "Below you will find all the groups that are public."
msgstr ""
+msgid "Beta"
+msgstr ""
+
+msgid "Bi-weekly code coverage"
+msgstr ""
+
msgid "Billing"
msgstr ""
@@ -3386,9 +3544,15 @@ msgstr ""
msgid "Bitbucket Server Import"
msgstr ""
+msgid "Bitbucket Server import"
+msgstr ""
+
msgid "Bitbucket import"
msgstr ""
+msgid "Blame"
+msgstr ""
+
msgid "Blocked"
msgstr ""
@@ -3401,9 +3565,6 @@ msgstr ""
msgid "Blog"
msgstr ""
-msgid "Blue helpers indicate an action to be taken."
-msgstr ""
-
msgid "Board name"
msgstr ""
@@ -3602,6 +3763,9 @@ msgstr ""
msgid "Broadcast Message was successfully updated."
msgstr ""
+msgid "Broadcast Messages"
+msgstr ""
+
msgid "Browse Directory"
msgstr ""
@@ -3656,6 +3820,9 @@ msgstr ""
msgid "By %{user_name}"
msgstr ""
+msgid "By URL"
+msgstr ""
+
msgid "By default GitLab sends emails in HTML and plain text formats so mail clients can choose what format to use. Disable this option if you only want to send emails in plain text format."
msgstr ""
@@ -3761,6 +3928,9 @@ msgstr ""
msgid "Can override approvers and approvals required per merge request"
msgstr ""
+msgid "Can't apply as this line has changed or the suggestion already matches its content."
+msgstr ""
+
msgid "Can't create snippet: %{err}"
msgstr ""
@@ -3947,9 +4117,6 @@ msgstr ""
msgid "Changes are still tracked. Useful for cluster/index migrations."
msgstr ""
-msgid "Changes are unknown"
-msgstr ""
-
msgid "Changes suppressed. Click to show."
msgstr ""
@@ -4256,15 +4423,9 @@ msgstr ""
msgid "Choose what content you want to see on a group’s overview page"
msgstr ""
-msgid "Choose which groups you wish to synchronize to this secondary node"
-msgstr ""
-
msgid "Choose which repositories you want to connect and run CI/CD pipelines."
msgstr ""
-msgid "Choose which shards you wish to synchronize to this secondary node"
-msgstr ""
-
msgid "Choose your framework"
msgstr ""
@@ -4562,9 +4723,15 @@ msgstr ""
msgid "Cluster does not exist"
msgstr ""
+msgid "Cluster is required for Stages::ClusterEndpointInserter"
+msgstr ""
+
msgid "Cluster level"
msgstr ""
+msgid "Cluster type must be specificed for Stages::ClusterEndpointInserter"
+msgstr ""
+
msgid "ClusterIntegration| %{custom_domain_start}More information%{custom_domain_end}."
msgstr ""
@@ -4676,7 +4843,7 @@ msgstr ""
msgid "ClusterIntegration|Cert-Manager"
msgstr ""
-msgid "ClusterIntegration|Cert-Manager is a native Kubernetes certificate management controller that helps with issuing certificates. Installing Cert-Manager on your cluster will issue a certificate by %{letsEncrypt} and ensure that certificates are valid and up-to-date."
+msgid "ClusterIntegration|Cert-Manager is a native Kubernetes certificate management controller that helps with issuing certificates. Installing Cert-Manager on your cluster will issue a certificate by %{linkStart}Let's Encrypt%{linkEnd} and ensure that certificates are valid and up-to-date."
msgstr ""
msgid "ClusterIntegration|Certificate Authority bundle (PEM format)"
@@ -4703,9 +4870,6 @@ msgstr ""
msgid "ClusterIntegration|Clear the local cache of namespace and service accounts. This is necessary if your integration has become out of sync. The cache is repopulated during the next CI job that requires namespace and service accounts."
msgstr ""
-msgid "ClusterIntegration|Cloud Run"
-msgstr ""
-
msgid "ClusterIntegration|Cluster being created"
msgstr ""
@@ -4790,7 +4954,7 @@ msgstr ""
msgid "ClusterIntegration|Crossplane"
msgstr ""
-msgid "ClusterIntegration|Crossplane enables declarative provisioning of managed services from your cloud of choice using %{codeStart}kubectl%{codeEnd} or %{gitlabIntegrationLink}. Crossplane runs inside your Kubernetes cluster and supports secure connectivity and secrets management between app containers and the cloud services they depend on."
+msgid "ClusterIntegration|Crossplane enables declarative provisioning of managed services from your cloud of choice using %{codeStart}kubectl%{codeEnd} or %{linkStart}GitLab Integration%{linkEnd}. Crossplane runs inside your Kubernetes cluster and supports secure connectivity and secrets management between app containers and the cloud services they depend on."
msgstr ""
msgid "ClusterIntegration|Deletes all GitLab resources attached to this cluster during removal"
@@ -4874,9 +5038,6 @@ msgstr ""
msgid "ClusterIntegration|GitLab-managed cluster"
msgstr ""
-msgid "ClusterIntegration|Gitlab Integration"
-msgstr ""
-
msgid "ClusterIntegration|Global default"
msgstr ""
@@ -4922,7 +5083,7 @@ msgstr ""
msgid "ClusterIntegration|Ingress gives you a way to route requests to services based on the request host or path, centralizing a number of services into a single entrypoint."
msgstr ""
-msgid "ClusterIntegration|Installing Ingress may incur additional costs. Learn more about %{pricingLink}."
+msgid "ClusterIntegration|Installing Ingress may incur additional costs. Learn more about %{linkStart}pricing%{linkEnd}."
msgstr ""
msgid "ClusterIntegration|Instance cluster"
@@ -4937,7 +5098,7 @@ msgstr ""
msgid "ClusterIntegration|Issuer Email"
msgstr ""
-msgid "ClusterIntegration|Issuers represent a certificate authority. You must provide an email address for your Issuer. "
+msgid "ClusterIntegration|Issuers represent a certificate authority. You must provide an email address for your Issuer."
msgstr ""
msgid "ClusterIntegration|Jupyter Hostname"
@@ -5009,9 +5170,6 @@ msgstr ""
msgid "ClusterIntegration|Learn more about instance Kubernetes clusters"
msgstr ""
-msgid "ClusterIntegration|Let's Encrypt"
-msgstr ""
-
msgid "ClusterIntegration|Loading IAM Roles"
msgstr ""
@@ -5123,7 +5281,7 @@ msgstr ""
msgid "ClusterIntegration|Prometheus"
msgstr ""
-msgid "ClusterIntegration|Prometheus is an open-source monitoring system with %{gitlabIntegrationLink} to monitor deployed applications."
+msgid "ClusterIntegration|Prometheus is an open-source monitoring system with %{linkStart}GitLab Integration%{linkEnd} to monitor deployed applications."
msgstr ""
msgid "ClusterIntegration|Provider details"
@@ -5279,10 +5437,10 @@ msgstr ""
msgid "ClusterIntegration|Select zone to choose machine type"
msgstr ""
-msgid "ClusterIntegration|Send Cilium Logs"
+msgid "ClusterIntegration|Send Container Network Policies Logs"
msgstr ""
-msgid "ClusterIntegration|Send ModSecurity Logs"
+msgid "ClusterIntegration|Send Web Application Firewall Logs"
msgstr ""
msgid "ClusterIntegration|Service Token"
@@ -5450,15 +5608,12 @@ msgstr ""
msgid "ClusterIntegration|documentation"
msgstr ""
-msgid "ClusterIntegration|installed via %{installed_via}"
+msgid "ClusterIntegration|installed via %{linkStart}Cloud Run%{linkEnd}"
msgstr ""
msgid "ClusterIntegration|meets the requirements"
msgstr ""
-msgid "ClusterIntegration|pricing"
-msgstr ""
-
msgid "ClusterIntegration|sign up"
msgstr ""
@@ -5495,6 +5650,15 @@ msgstr ""
msgid "Code"
msgstr ""
+msgid "Code Coverage: %{coveragePercentage}%{percentSymbol}"
+msgstr ""
+
+msgid "Code Coverage| Empty code coverage data"
+msgstr ""
+
+msgid "Code Coverage|Couldn't fetch the code coverage data"
+msgstr ""
+
msgid "Code Owners"
msgstr ""
@@ -5549,7 +5713,7 @@ msgstr ""
msgid "Collapse approvers"
msgstr ""
-msgid "Collapse child epics"
+msgid "Collapse milestones"
msgstr ""
msgid "Collapse replies"
@@ -5567,6 +5731,9 @@ msgstr ""
msgid "Coming soon"
msgstr ""
+msgid "Comma-separated, e.g. '1.1.1.1, 2.2.2.0/24'"
+msgstr ""
+
msgid "Command"
msgstr ""
@@ -5737,6 +5904,12 @@ msgstr ""
msgid "Compliance framework (optional)"
msgstr ""
+msgid "Compliance frameworks"
+msgstr ""
+
+msgid "ComplianceDashboard|created by:"
+msgstr ""
+
msgid "ComplianceFramework|GDPR"
msgstr ""
@@ -5875,6 +6048,9 @@ msgstr ""
msgid "Connection timed out"
msgstr ""
+msgid "Connection timeout"
+msgstr ""
+
msgid "Contact sales to upgrade"
msgstr ""
@@ -5930,9 +6106,6 @@ msgstr ""
msgid "ContainerRegistry|CLI Commands"
msgstr ""
-msgid "ContainerRegistry|Compressed Size"
-msgstr ""
-
msgid "ContainerRegistry|Container Registry"
msgstr ""
@@ -5948,6 +6121,9 @@ msgstr ""
msgid "ContainerRegistry|Copy push command"
msgstr ""
+msgid "ContainerRegistry|Delete selected"
+msgstr ""
+
msgid "ContainerRegistry|Docker connection error"
msgstr ""
@@ -5981,16 +6157,16 @@ msgstr ""
msgid "ContainerRegistry|If you are not already logged in, you need to authenticate to the Container Registry by using your GitLab username and password. If you have %{twofaDocLinkStart}Two-Factor Authentication%{twofaDocLinkEnd} enabled, use a %{personalAccessTokensDocLinkStart}Personal Access Token%{personalAccessTokensDocLinkEnd} instead of a password."
msgstr ""
-msgid "ContainerRegistry|Image ID"
+msgid "ContainerRegistry|Image ID: %{imageId}"
msgstr ""
msgid "ContainerRegistry|Image Repositories"
msgstr ""
-msgid "ContainerRegistry|Keep and protect the images that matter most."
+msgid "ContainerRegistry|Image tags"
msgstr ""
-msgid "ContainerRegistry|Last Updated"
+msgid "ContainerRegistry|Keep and protect the images that matter most."
msgstr ""
msgid "ContainerRegistry|Login"
@@ -6005,6 +6181,9 @@ msgstr ""
msgid "ContainerRegistry|Please contact your administrator."
msgstr ""
+msgid "ContainerRegistry|Published %{timeInfo}"
+msgstr ""
+
msgid "ContainerRegistry|Push an image"
msgstr ""
@@ -6020,9 +6199,6 @@ msgstr ""
msgid "ContainerRegistry|Remove repository"
msgstr ""
-msgid "ContainerRegistry|Remove selected tags"
-msgstr ""
-
msgid "ContainerRegistry|Remove tag"
msgid_plural "ContainerRegistry|Remove tags"
msgstr[0] ""
@@ -6052,9 +6228,6 @@ msgstr ""
msgid "ContainerRegistry|Sorry, your filter produced no results."
msgstr ""
-msgid "ContainerRegistry|Tag"
-msgstr ""
-
msgid "ContainerRegistry|Tag expiration policy"
msgstr ""
@@ -6199,21 +6372,6 @@ msgstr ""
msgid "Control the display of third party offers."
msgstr ""
-msgid "Control the maximum concurrency of LFS/attachment backfill for this secondary node"
-msgstr ""
-
-msgid "Control the maximum concurrency of container repository operations for this Geo node"
-msgstr ""
-
-msgid "Control the maximum concurrency of repository backfill for this secondary node"
-msgstr ""
-
-msgid "Control the maximum concurrency of verification operations for this Geo node"
-msgstr ""
-
-msgid "Control the minimum interval in days that a repository should be reverified for this primary node"
-msgstr ""
-
msgid "Cookie domain"
msgstr ""
@@ -6355,6 +6513,9 @@ msgstr ""
msgid "Could not find design."
msgstr ""
+msgid "Could not find iteration"
+msgstr ""
+
msgid "Could not remove the trigger."
msgstr ""
@@ -6391,6 +6552,12 @@ msgstr ""
msgid "Coverage"
msgstr ""
+msgid "Coverage Fuzzing"
+msgstr ""
+
+msgid "Crash State"
+msgstr ""
+
msgid "Create"
msgstr ""
@@ -6439,6 +6606,9 @@ msgstr ""
msgid "Create a personal access token on your account to pull or push via %{protocol}."
msgstr ""
+msgid "Create a project pre-populated with the necessary files to get you started quickly."
+msgstr ""
+
msgid "Create an account using:"
msgstr ""
@@ -6607,6 +6777,9 @@ msgstr ""
msgid "Creates branch '%{branch_name}' and a merge request to resolve this issue."
msgstr ""
+msgid "Creating"
+msgstr ""
+
msgid "Creating epic"
msgstr ""
@@ -6658,7 +6831,10 @@ msgstr ""
msgid "Current vulnerabilities count"
msgstr ""
-msgid "CurrentUser|Buy CI minutes"
+msgid "CurrentUser|Buy Pipeline minutes"
+msgstr ""
+
+msgid "CurrentUser|One of your groups is running out"
msgstr ""
msgid "CurrentUser|Profile"
@@ -6673,6 +6849,9 @@ msgstr ""
msgid "CurrentUser|Upgrade"
msgstr ""
+msgid "Custom Attributes"
+msgstr ""
+
msgid "Custom CI configuration path"
msgstr ""
@@ -6936,12 +7115,18 @@ msgstr ""
msgid "DAG"
msgstr ""
+msgid "DAG visualization requires at least 3 dependent jobs."
+msgstr ""
+
msgid "DNS"
msgstr ""
msgid "Dashboard"
msgstr ""
+msgid "Dashboard uid not found"
+msgstr ""
+
msgid "DashboardProjects|All"
msgstr ""
@@ -6966,6 +7151,9 @@ msgstr ""
msgid "Data is still calculating..."
msgstr ""
+msgid "Datasource name not found"
+msgstr ""
+
msgid "Date"
msgstr ""
@@ -7146,6 +7334,9 @@ msgstr ""
msgid "Delete this attachment"
msgstr ""
+msgid "Delete user list"
+msgstr ""
+
msgid "Delete variable"
msgstr ""
@@ -7462,6 +7653,9 @@ msgstr ""
msgid "Deploying to"
msgstr ""
+msgid "Deploying to AWS is easy with GitLab"
+msgstr ""
+
msgid "Deployment Frequency"
msgstr ""
@@ -7824,6 +8018,9 @@ msgstr ""
msgid "Dismissed on pipeline %{pipelineLink} at %{projectLink}"
msgstr ""
+msgid "Display alerts from all your monitoring tools directly within GitLab."
+msgstr ""
+
msgid "Display name"
msgstr ""
@@ -7881,9 +8078,6 @@ msgstr ""
msgid "Don't show again"
msgstr ""
-msgid "Don't worry, you can access this tour by clicking on the help icon in the top right corner and choose <strong>Learn GitLab</strong>."
-msgstr ""
-
msgid "Done"
msgstr ""
@@ -8010,6 +8204,9 @@ msgstr ""
msgid "Edit Release"
msgstr ""
+msgid "Edit Slack integration"
+msgstr ""
+
msgid "Edit Snippet"
msgstr ""
@@ -8049,6 +8246,9 @@ msgstr ""
msgid "Edit issues"
msgstr ""
+msgid "Edit iteration"
+msgstr ""
+
msgid "Edit public deploy key"
msgstr ""
@@ -8097,6 +8297,9 @@ msgstr ""
msgid "Email"
msgstr ""
+msgid "Email Notification"
+msgstr ""
+
msgid "Email address"
msgstr ""
@@ -8109,6 +8312,9 @@ msgstr ""
msgid "Email not verified. Please verify your email in Salesforce."
msgstr ""
+msgid "Email notification for unknown sign-ins"
+msgstr ""
+
msgid "Email patch"
msgstr ""
@@ -8340,6 +8546,9 @@ msgstr ""
msgid "Enforce DNS rebinding attack protection"
msgstr ""
+msgid "Enforce personal access token expiration"
+msgstr ""
+
msgid "Ensure connectivity is available from the GitLab server to the Prometheus server"
msgstr ""
@@ -8409,6 +8618,9 @@ msgstr ""
msgid "Enter the name of your application, and we'll return a unique %{type}."
msgstr ""
+msgid "Enter weights for storages for new repositories."
+msgstr ""
+
msgid "Enter your password to approve"
msgstr ""
@@ -8418,15 +8630,27 @@ msgstr ""
msgid "Environment does not have deployments"
msgstr ""
+msgid "Environment is required for Stages::MetricEndpointInserter"
+msgstr ""
+
+msgid "Environment is required for Stages::VariableEndpointInserter"
+msgstr ""
+
msgid "Environment scope"
msgstr ""
+msgid "Environment variables are applied to all project environments in this instance via the Runner. You can use environment variables for passwords, secret keys, etc. Make variables available to the running application by prepending the variable key with <code>K8S_SECRET_</code>. You can set variables to be:"
+msgstr ""
+
msgid "Environment variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. Additionally, they can be masked so they are hidden in job logs, though they must match certain regexp requirements to do so. You can use environment variables for passwords, secret keys, or whatever you want."
msgstr ""
msgid "Environment variables are configured by your administrator to be %{link_start}protected%{link_end} by default"
msgstr ""
+msgid "Environment variables on this GitLab instance are configured to be %{link_start}protected%{link_end} by default"
+msgstr ""
+
msgid "Environment:"
msgstr ""
@@ -8958,6 +9182,9 @@ msgstr ""
msgid "EventFilterBy|Filter by comments"
msgstr ""
+msgid "EventFilterBy|Filter by designs"
+msgstr ""
+
msgid "EventFilterBy|Filter by epic events"
msgstr ""
@@ -9090,15 +9317,15 @@ msgstr ""
msgid "Expand approvers"
msgstr ""
-msgid "Expand child epics"
-msgstr ""
-
msgid "Expand down"
msgstr ""
msgid "Expand dropdown"
msgstr ""
+msgid "Expand milestones"
+msgstr ""
+
msgid "Expand sidebar"
msgstr ""
@@ -9114,6 +9341,9 @@ msgstr ""
msgid "Expiration date"
msgstr ""
+msgid "Expiration not enforced"
+msgstr ""
+
msgid "Expiration policy for the Container Registry is a perfect solution for keeping the Registry space down while still enjoying the full power of GitLab CI/CD."
msgstr ""
@@ -9234,6 +9464,9 @@ msgstr ""
msgid "Failed Jobs"
msgstr ""
+msgid "Failed on"
+msgstr ""
+
msgid "Failed to add a Zoom meeting"
msgstr ""
@@ -9294,13 +9527,19 @@ msgstr ""
msgid "Failed to install."
msgstr ""
+msgid "Failed to load assignees. Please try again."
+msgstr ""
+
+msgid "Failed to load authors. Please try again."
+msgstr ""
+
msgid "Failed to load emoji list."
msgstr ""
msgid "Failed to load error details from Sentry."
msgstr ""
-msgid "Failed to load errors from Sentry. Error message: %{errorMessage}"
+msgid "Failed to load errors from Sentry."
msgstr ""
msgid "Failed to load group activity metrics. Please try again."
@@ -9381,6 +9620,9 @@ msgstr ""
msgid "Failed to set due date because the date format is invalid."
msgstr ""
+msgid "Failed to set iteration on this issue. Please try again."
+msgstr ""
+
msgid "Failed to signing using smartcard authentication"
msgstr ""
@@ -9603,13 +9845,10 @@ msgstr ""
msgid "FeatureFlags|Target environments"
msgstr ""
-msgid "FeatureFlags|There are no active feature flags"
-msgstr ""
-
-msgid "FeatureFlags|There are no inactive feature flags"
+msgid "FeatureFlags|There was an error fetching the feature flags."
msgstr ""
-msgid "FeatureFlags|There was an error fetching the feature flags."
+msgid "FeatureFlags|There was an error retrieving user lists"
msgstr ""
msgid "FeatureFlags|Try again in a few moments or contact your support team."
@@ -9621,9 +9860,18 @@ msgstr ""
msgid "FeatureFlag|Delete strategy"
msgstr ""
+msgid "FeatureFlag|List"
+msgstr ""
+
msgid "FeatureFlag|Percentage"
msgstr ""
+msgid "FeatureFlag|Select a user list"
+msgstr ""
+
+msgid "FeatureFlag|There are no configured user lists"
+msgstr ""
+
msgid "FeatureFlag|Type"
msgstr ""
@@ -9780,6 +10028,12 @@ msgstr ""
msgid "Filter..."
msgstr ""
+msgid "Find File"
+msgstr ""
+
+msgid "Find bugs in your code with coverage-guided fuzzing"
+msgstr ""
+
msgid "Find by path"
msgstr ""
@@ -10044,6 +10298,9 @@ msgstr ""
msgid "Geo allows you to replicate your GitLab instance to other geographical locations."
msgstr ""
+msgid "Geo nodes are paused using a command run on the node"
+msgstr ""
+
msgid "GeoNodeStatusEvent|%{timeAgoStr} (%{pendingEvents} events)"
msgstr ""
@@ -10128,6 +10385,9 @@ msgstr ""
msgid "GeoNodes|Not checksummed"
msgstr ""
+msgid "GeoNodes|Package files"
+msgstr ""
+
msgid "GeoNodes|Pausing replication stops the sync process. Are you sure?"
msgstr ""
@@ -10146,6 +10406,9 @@ msgstr ""
msgid "GeoNodes|Replication slots"
msgstr ""
+msgid "GeoNodes|Replication status"
+msgstr ""
+
msgid "GeoNodes|Repositories"
msgstr ""
@@ -10356,6 +10619,9 @@ msgstr ""
msgid "Geo|Synchronization failed - %{error}"
msgstr ""
+msgid "Geo|Synchronization of %{itemTitle} is disabled."
+msgstr ""
+
msgid "Geo|The database is currently %{db_lag} behind the primary node."
msgstr ""
@@ -10668,6 +10934,9 @@ msgstr ""
msgid "Go to %{link_to_google_takeout}."
msgstr ""
+msgid "Go to <strong>Issues</strong> > <strong>Boards</strong> to access your personalized learning issue board."
+msgstr ""
+
msgid "Go to Pipelines"
msgstr ""
@@ -10767,9 +11036,6 @@ msgstr ""
msgid "Go to your snippets"
msgstr ""
-msgid "Golden Tanuki"
-msgstr ""
-
msgid "Google Cloud Platform"
msgstr ""
@@ -10791,6 +11057,9 @@ msgstr ""
msgid "Grafana URL"
msgstr ""
+msgid "Grafana response contains invalid json"
+msgstr ""
+
msgid "GrafanaIntegration|API Token"
msgstr ""
@@ -10839,9 +11108,6 @@ msgstr ""
msgid "Group %{group_name} was successfully created."
msgstr ""
-msgid "Group '%{group_name}' is being imported."
-msgstr ""
-
msgid "Group Audit Events"
msgstr ""
@@ -10878,9 +11144,6 @@ msgstr ""
msgid "Group by:"
msgstr ""
-msgid "Group could not be imported: %{errors}"
-msgstr ""
-
msgid "Group description"
msgstr ""
@@ -10908,9 +11171,15 @@ msgstr ""
msgid "Group has not been marked for deletion"
msgstr ""
+msgid "Group import could not be scheduled"
+msgstr ""
+
msgid "Group info:"
msgstr ""
+msgid "Group is required when cluster_type is :group"
+msgstr ""
+
msgid "Group maintainers can register group runners in the %{link}"
msgstr ""
@@ -10974,6 +11243,24 @@ msgstr ""
msgid "GroupActivyMetrics|Recent activity (last 90 days)"
msgstr ""
+msgid "GroupImport|Failed to import group."
+msgstr ""
+
+msgid "GroupImport|Group '%{group_name}' is being imported."
+msgstr ""
+
+msgid "GroupImport|Group could not be imported: %{errors}"
+msgstr ""
+
+msgid "GroupImport|Please wait while we import the group for you. Refresh at will."
+msgstr ""
+
+msgid "GroupImport|The group was successfully imported."
+msgstr ""
+
+msgid "GroupImport|Unable to process group import file"
+msgstr ""
+
msgid "GroupRoadmap|%{dateWord} – No end date"
msgstr ""
@@ -11241,6 +11528,9 @@ msgstr ""
msgid "Groups (%{groups})"
msgstr ""
+msgid "Groups and projects"
+msgstr ""
+
msgid "Groups can also be nested by creating %{subgroup_docs_link_start}subgroups%{subgroup_docs_link_end}."
msgstr ""
@@ -11286,6 +11576,33 @@ msgstr ""
msgid "GroupsEmptyState|You can manage your group member’s permissions and access to each project in the group."
msgstr ""
+msgid "GroupsNew|Contact an administrator to enable options for importing your group."
+msgstr ""
+
+msgid "GroupsNew|Create"
+msgstr ""
+
+msgid "GroupsNew|Create group"
+msgstr ""
+
+msgid "GroupsNew|GitLab group export"
+msgstr ""
+
+msgid "GroupsNew|Import"
+msgstr ""
+
+msgid "GroupsNew|Import group"
+msgstr ""
+
+msgid "GroupsNew|My Awesome Group"
+msgstr ""
+
+msgid "GroupsNew|No import options available"
+msgstr ""
+
+msgid "GroupsNew|To copy a GitLab group between installations, navigate to the group settings page for the original installation, generate an export file, and upload it here."
+msgstr ""
+
msgid "GroupsTree|Are you sure you want to leave the \"%{fullName}\" group?"
msgstr ""
@@ -11388,6 +11705,9 @@ msgstr ""
msgid "Helps reduce request volume for protected paths"
msgstr ""
+msgid "Here are all your projects in your group, including the one you just created. To start, let’s take a look at your personalized learning project which will help you learn about GitLab at your own pace."
+msgstr ""
+
msgid "Here you will find recent merge request activity"
msgstr ""
@@ -11402,6 +11722,9 @@ msgid_plural "Hide charts"
msgstr[0] ""
msgstr[1] ""
+msgid "Hide details"
+msgstr ""
+
msgid "Hide file browser"
msgstr ""
@@ -11411,6 +11734,9 @@ msgstr ""
msgid "Hide host keys manual input"
msgstr ""
+msgid "Hide list"
+msgstr ""
+
msgid "Hide marketing-related entries from help"
msgstr ""
@@ -11431,9 +11757,6 @@ msgstr[1] ""
msgid "Hide values"
msgstr ""
-msgid "Hiding all labels"
-msgstr ""
-
msgid "High or unknown vulnerabilities present"
msgstr ""
@@ -11518,6 +11841,9 @@ msgstr ""
msgid "ID:"
msgstr ""
+msgid "IDE"
+msgstr ""
+
msgid "IDE|Allow live previews of JavaScript projects in the Web IDE using CodeSandbox Live Preview."
msgstr ""
@@ -11608,10 +11934,10 @@ msgstr ""
msgid "If enabled"
msgstr ""
-msgid "If enabled, access to projects will be validated on an external service using their classification label."
+msgid "If enabled, GitLab will handle Object Storage replication using Geo. %{linkStart}More information%{linkEnd}"
msgstr ""
-msgid "If enabled, and if object storage is enabled, GitLab will handle Object Storage replication using Geo"
+msgid "If enabled, access to projects will be validated on an external service using their classification label."
msgstr ""
msgid "If there is no previous license or if the previous license has expired, some GitLab functionality will be blocked until a new, valid license is uploaded."
@@ -11973,6 +12299,9 @@ msgid_plural "Instances"
msgstr[0] ""
msgstr[1] ""
+msgid "Instance Configuration"
+msgstr ""
+
msgid "Instance Statistics visibility"
msgstr ""
@@ -11994,9 +12323,6 @@ msgstr ""
msgid "Integrations"
msgstr ""
-msgid "Integrations allow you to integrate GitLab with other applications"
-msgstr ""
-
msgid "Integrations|All details"
msgstr ""
@@ -12108,6 +12434,9 @@ msgstr ""
msgid "Invalid query"
msgstr ""
+msgid "Invalid repository bundle for snippet with id %{snippet_id}"
+msgstr ""
+
msgid "Invalid repository path"
msgstr ""
@@ -12126,6 +12455,9 @@ msgstr ""
msgid "Invalid two-factor code."
msgstr ""
+msgid "Invalid yaml"
+msgstr ""
+
msgid "Invitation"
msgstr ""
@@ -12345,15 +12677,24 @@ msgstr ""
msgid "It seems like the Dependency Scanning job ran successfully, but no dependencies have been detected in your project."
msgstr ""
+msgid "It seems that there is currently no available data for code coverage"
+msgstr ""
+
msgid "It's you"
msgstr ""
+msgid "Iteration"
+msgstr ""
+
msgid "Iteration changed to"
msgstr ""
msgid "Iteration removed"
msgstr ""
+msgid "Iteration updated"
+msgstr ""
+
msgid "Iterations"
msgstr ""
@@ -12384,12 +12725,18 @@ msgstr ""
msgid "January"
msgstr ""
+msgid "Jira Issues"
+msgstr ""
+
msgid "Jira import is already running."
msgstr ""
msgid "Jira integration not configured."
msgstr ""
+msgid "Jira project key is not configured"
+msgstr ""
+
msgid "Jira project: %{importProject}"
msgstr ""
@@ -12570,6 +12917,9 @@ msgstr ""
msgid "Keep divergent refs"
msgstr ""
+msgid "Kerberos access denied"
+msgstr ""
+
msgid "Key"
msgstr ""
@@ -12579,10 +12929,13 @@ msgstr ""
msgid "Key: %{key}"
msgstr ""
-msgid "Keyboard Shortcuts"
+msgid "Keyboard shortcuts"
msgstr ""
-msgid "Keyboard shortcuts"
+msgid "Keys"
+msgstr ""
+
+msgid "Ki"
msgstr ""
msgid "Kubernetes"
@@ -12624,6 +12977,9 @@ msgstr ""
msgid "LDAP"
msgstr ""
+msgid "LDAP Synchronization"
+msgstr ""
+
msgid "LDAP settings"
msgstr ""
@@ -12848,12 +13204,18 @@ msgstr ""
msgid "Learn more about custom project templates"
msgstr ""
+msgid "Learn more about deploying to AWS"
+msgstr ""
+
msgid "Learn more about deploying to a cluster"
msgstr ""
msgid "Learn more about group-level project templates"
msgstr ""
+msgid "Learn more about job dependencies"
+msgstr ""
+
msgid "Learn more about signing commits"
msgstr ""
@@ -12905,9 +13267,21 @@ msgstr ""
msgid "License History"
msgstr ""
+msgid "License ID:"
+msgstr ""
+
+msgid "License URL"
+msgstr ""
+
msgid "License-Check"
msgstr ""
+msgid "LicenseCompliance|%{docLinkStart}License Approvals%{docLinkEnd} are active"
+msgstr ""
+
+msgid "LicenseCompliance|%{docLinkStart}License Approvals%{docLinkEnd} are inactive"
+msgstr ""
+
msgid "LicenseCompliance|Add a license"
msgstr ""
@@ -12929,9 +13303,15 @@ msgstr ""
msgid "LicenseCompliance|Deny"
msgstr ""
+msgid "LicenseCompliance|Learn more about %{linkStart}License Approvals%{linkEnd}"
+msgstr ""
+
msgid "LicenseCompliance|License"
msgstr ""
+msgid "LicenseCompliance|License Approvals"
+msgstr ""
+
msgid "LicenseCompliance|License Compliance detected %d license and policy violation for the source branch only; approval required"
msgid_plural "LicenseCompliance|License Compliance detected %d licenses and policy violations for the source branch only; approval required"
msgstr[0] ""
@@ -13152,6 +13532,9 @@ msgstr ""
msgid "List your Bitbucket Server repositories"
msgstr ""
+msgid "Lists"
+msgstr ""
+
msgid "Live preview"
msgstr ""
@@ -13335,6 +13718,9 @@ msgstr ""
msgid "Manage project labels"
msgstr ""
+msgid "Manage storage usage"
+msgstr ""
+
msgid "Manage two-factor authentication"
msgstr ""
@@ -13350,6 +13736,9 @@ msgstr ""
msgid "Manifest file import"
msgstr ""
+msgid "Manifest import"
+msgstr ""
+
msgid "Manual job"
msgstr ""
@@ -13497,6 +13886,12 @@ msgstr ""
msgid "Maximum field length"
msgstr ""
+msgid "Maximum file size is 2MB. Please select a smaller file."
+msgstr ""
+
+msgid "Maximum import size (MB)"
+msgstr ""
+
msgid "Maximum job timeout"
msgstr ""
@@ -13536,6 +13931,9 @@ msgstr ""
msgid "Maximum size of Elasticsearch bulk indexing requests."
msgstr ""
+msgid "Maximum size of import files."
+msgstr ""
+
msgid "Maximum size of individual attachments in comments."
msgstr ""
@@ -13587,6 +13985,9 @@ msgstr ""
msgid "Merge (when the pipeline succeeds)"
msgstr ""
+msgid "Merge Conflicts"
+msgstr ""
+
msgid "Merge Request"
msgstr ""
@@ -13713,6 +14114,9 @@ msgstr ""
msgid "MergeRequests|Squash task canceled: another squash is already in progress."
msgstr ""
+msgid "MergeRequests|This project does not allow squashing commits when merge requests are accepted."
+msgstr ""
+
msgid "MergeRequests|Thread stays resolved"
msgstr ""
@@ -13866,6 +14270,12 @@ msgstr ""
msgid "MetricsSettings|Add a button to the metrics dashboard linking directly to your existing external dashboard."
msgstr ""
+msgid "MetricsSettings|Choose whether to display dashboard metrics in UTC or the user's local timezone."
+msgstr ""
+
+msgid "MetricsSettings|Dashboard timezone"
+msgstr ""
+
msgid "MetricsSettings|External dashboard URL"
msgstr ""
@@ -13875,6 +14285,12 @@ msgstr ""
msgid "MetricsSettings|Metrics Dashboard"
msgstr ""
+msgid "MetricsSettings|UTC (Coordinated Universal Time)"
+msgstr ""
+
+msgid "MetricsSettings|User's local timezone"
+msgstr ""
+
msgid "Metrics|Add metric"
msgstr ""
@@ -13890,6 +14306,9 @@ msgstr ""
msgid "Metrics|Create metric"
msgstr ""
+msgid "Metrics|Current"
+msgstr ""
+
msgid "Metrics|Delete metric"
msgstr ""
@@ -13940,6 +14359,9 @@ msgstr ""
msgid "Metrics|Max"
msgstr ""
+msgid "Metrics|Min"
+msgstr ""
+
msgid "Metrics|Must be a valid PromQL query."
msgstr ""
@@ -13955,6 +14377,9 @@ msgstr ""
msgid "Metrics|Refresh dashboard"
msgstr ""
+msgid "Metrics|Select a value"
+msgstr ""
+
msgid "Metrics|Star dashboard"
msgstr ""
@@ -13973,12 +14398,18 @@ msgstr ""
msgid "Metrics|There was an error getting annotations information."
msgstr ""
+msgid "Metrics|There was an error getting dashboard validation warnings information."
+msgstr ""
+
msgid "Metrics|There was an error getting deployment information."
msgstr ""
msgid "Metrics|There was an error getting environments information."
msgstr ""
+msgid "Metrics|There was an error getting options for variable \"%{name}\"."
+msgstr ""
+
msgid "Metrics|There was an error trying to validate your query"
msgstr ""
@@ -14021,6 +14452,9 @@ msgstr ""
msgid "Metrics|You're about to permanently delete this metric. This cannot be undone."
msgstr ""
+msgid "Metrics|Your dashboard schema is invalid. Edit the dashboard to correct the YAML schema."
+msgstr ""
+
msgid "Metrics|e.g. HTTP requests"
msgstr ""
@@ -14036,12 +14470,18 @@ msgstr ""
msgid "Metrics|e.g. req/sec"
msgstr ""
+msgid "Mi"
+msgstr ""
+
msgid "Microsoft Azure"
msgstr ""
msgid "Middleman project with Static Site Editor support"
msgstr ""
+msgid "Migrate your data from an external source like GitHub, Bitbucket, or another instance of GitLab."
+msgstr ""
+
msgid "Migrated %{success_count}/%{total_count} files."
msgstr ""
@@ -14176,6 +14616,9 @@ msgstr ""
msgid "Minimum capacity to be available before we schedule more mirrors preemptively."
msgstr ""
+msgid "Minimum interval in days"
+msgstr ""
+
msgid "Minimum length is %{minimum_password_length} characters"
msgstr ""
@@ -14362,6 +14805,12 @@ msgstr ""
msgid "Multiple uploaders found: %{uploader_types}"
msgstr ""
+msgid "Must match with the %{codeStart}external_url%{codeEnd} in %{codeStart}/etc/gitlab/gitlab.rb%{codeEnd}."
+msgstr ""
+
+msgid "Must match with the %{codeStart}geo_node_name%{codeEnd} in %{codeStart}/etc/gitlab/gitlab.rb%{codeEnd}. %{linkStart}More information%{linkEnd}"
+msgstr ""
+
msgid "My Awesome Group"
msgstr ""
@@ -14476,10 +14925,16 @@ msgstr ""
msgid "New Application"
msgstr ""
+msgid "New Branch"
+msgstr ""
+
+msgid "New Deploy Key"
+msgstr ""
+
msgid "New Environment"
msgstr ""
-msgid "New Geo Node"
+msgid "New File"
msgstr ""
msgid "New Group"
@@ -14496,15 +14951,15 @@ msgid_plural "New Issues"
msgstr[0] ""
msgstr[1] ""
-msgid "New Iteration"
-msgstr ""
-
msgid "New Jira import"
msgstr ""
msgid "New Label"
msgstr ""
+msgid "New Merge Request"
+msgstr ""
+
msgid "New Milestone"
msgstr ""
@@ -14523,6 +14978,9 @@ msgstr ""
msgid "New Snippet"
msgstr ""
+msgid "New User"
+msgstr ""
+
msgid "New branch"
msgstr ""
@@ -14565,6 +15023,9 @@ msgstr ""
msgid "New issue title"
msgstr ""
+msgid "New iteration"
+msgstr ""
+
msgid "New iteration created"
msgstr ""
@@ -14742,6 +15203,12 @@ msgstr ""
msgid "No grouping"
msgstr ""
+msgid "No iteration"
+msgstr ""
+
+msgid "No iterations to show"
+msgstr ""
+
msgid "No job log"
msgstr ""
@@ -14784,6 +15251,9 @@ msgstr ""
msgid "No other labels with such name or description"
msgstr ""
+msgid "No panels matching properties %{opts}"
+msgstr ""
+
msgid "No parent group"
msgstr ""
@@ -14817,6 +15287,9 @@ msgstr ""
msgid "No schedules"
msgstr ""
+msgid "No stack trace for this error"
+msgstr ""
+
msgid "No starrers matched your search"
msgstr ""
@@ -14829,7 +15302,7 @@ msgstr ""
msgid "No test coverage"
msgstr ""
-msgid "No thanks, don't show this again"
+msgid "No thanks"
msgstr ""
msgid "No vulnerabilities present"
@@ -14844,9 +15317,6 @@ msgstr ""
msgid "No, directly import the existing email addresses and usernames."
msgstr ""
-msgid "No, not interested right now"
-msgstr ""
-
msgid "No. of commits"
msgstr ""
@@ -14892,9 +15362,6 @@ msgstr ""
msgid "Not found."
msgstr ""
-msgid "Not helpful"
-msgstr ""
-
msgid "Not now"
msgstr ""
@@ -15051,6 +15518,9 @@ msgstr ""
msgid "Notifications on"
msgstr ""
+msgid "Notify users by email when sign-in location is not recognized"
+msgstr ""
+
msgid "Nov"
msgstr ""
@@ -15117,9 +15587,6 @@ msgstr ""
msgid "Oh no!"
msgstr ""
-msgid "Ok let's go"
-msgstr ""
-
msgid "Oldest first"
msgstr ""
@@ -15132,16 +15599,49 @@ msgstr ""
msgid "On track"
msgstr ""
+msgid "OnDemandScans|Attached branch"
+msgstr ""
+
+msgid "OnDemandScans|Attached branch is where the scan job runs."
+msgstr ""
+
+msgid "OnDemandScans|Could not run the scan. Please try again."
+msgstr ""
+
msgid "OnDemandScans|Create new DAST scan"
msgstr ""
+msgid "OnDemandScans|DAST will scan the target URL and any discovered sub URLs."
+msgstr ""
+
+msgid "OnDemandScans|New on-demand DAST scan"
+msgstr ""
+
msgid "OnDemandScans|On-demand Scans"
msgstr ""
+msgid "OnDemandScans|On-demand scans run outside the DevOps cycle and find vulnerabilities in your projects. %{learnMoreLinkStart}Learn more%{learnMoreLinkEnd}"
+msgstr ""
+
+msgid "OnDemandScans|Only a passive scan can be performed on demand."
+msgstr ""
+
+msgid "OnDemandScans|Passive DAST Scan"
+msgstr ""
+
+msgid "OnDemandScans|Please enter a valid URL format, ex: http://www.example.com/home"
+msgstr ""
+
+msgid "OnDemandScans|Run this scan"
+msgstr ""
+
+msgid "OnDemandScans|Scan mode"
+msgstr ""
+
msgid "OnDemandScans|Schedule or run scans immediately against target sites. Currently available on-demand scan type: DAST. %{helpLinkStart}More information%{helpLinkEnd}"
msgstr ""
-msgid "Onboarding"
+msgid "OnDemandScans|Target URL"
msgstr ""
msgid "Once imported, repositories can be mirrored over SSH. Read more %{link_start}here%{link_end}."
@@ -15167,6 +15667,9 @@ msgstr ""
msgid "One or more of you personal access tokens were revoked"
msgstr ""
+msgid "One or more of your %{provider} projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
+msgstr ""
+
msgid "One or more of your Bitbucket projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
msgstr ""
@@ -15668,6 +16171,9 @@ msgstr ""
msgid "Parent epic is not present."
msgstr ""
+msgid "Parsing error for param :embed_json. %{message}"
+msgstr ""
+
msgid "Part of merge request changes"
msgstr ""
@@ -15680,6 +16186,9 @@ msgstr ""
msgid "Passed"
msgstr ""
+msgid "Passed on"
+msgstr ""
+
msgid "Password"
msgstr ""
@@ -15764,6 +16273,9 @@ msgstr ""
msgid "Perform common operations on GitLab project"
msgstr ""
+msgid "Performance and resource management"
+msgstr ""
+
msgid "Performance optimization"
msgstr ""
@@ -15971,7 +16483,7 @@ msgstr ""
msgid "Pipelines|Something went wrong while cleaning runners cache."
msgstr ""
-msgid "Pipelines|There are currently no %{scope} pipelines."
+msgid "Pipelines|There are currently no finished pipelines."
msgstr ""
msgid "Pipelines|There are currently no pipelines."
@@ -16082,6 +16594,9 @@ msgstr ""
msgid "Pipeline|Stop pipeline #%{pipelineId}?"
msgstr ""
+msgid "Pipeline|Tag name"
+msgstr ""
+
msgid "Pipeline|Trigger author"
msgstr ""
@@ -16163,12 +16678,18 @@ msgstr ""
msgid "Please check your email (%{email}) to verify that you own this address and unlock the power of CI/CD. Didn't receive it? %{resend_link}. Wrong email address? %{update_link}."
msgstr ""
+msgid "Please choose a file"
+msgstr ""
+
msgid "Please choose a group URL with no special characters."
msgstr ""
msgid "Please complete your profile with email address"
msgstr ""
+msgid "Please convert %{linkStart}them to Git%{linkEnd}, and go through the %{linkToImportFlow} again."
+msgstr ""
+
msgid "Please convert them to %{link_to_git}, and go through the %{link_to_import_flow} again."
msgstr ""
@@ -16226,6 +16747,9 @@ msgstr ""
msgid "Please provide a valid email address."
msgstr ""
+msgid "Please provide attributes to update"
+msgstr ""
+
msgid "Please refer to <a href=\"%{docs_url}\">%{docs_url}</a>"
msgstr ""
@@ -16961,6 +17485,9 @@ msgstr ""
msgid "Project has too many %{label_for_message} to search"
msgstr ""
+msgid "Project is required when cluster_type is :project"
+msgstr ""
+
msgid "Project members"
msgstr ""
@@ -17078,7 +17605,40 @@ msgstr ""
msgid "ProjectService|Comment"
msgstr ""
-msgid "ProjectService|Comment will be posted on each event"
+msgid "ProjectService|Event will be triggered by a push to the repository"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a commit is created/updated"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a confidential issue is created/updated/closed"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a deployment finishes"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a merge request is created/updated/merged"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a new tag is pushed to the repository"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a new, unique alert is recorded"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a pipeline status changes"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a wiki page is created/updated"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when an issue is created/updated/closed"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when someone adds a comment"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when someone adds a comment on a confidential issue"
msgstr ""
msgid "ProjectService|Perform common operations on GitLab project: %{project_name}"
@@ -17129,6 +17689,9 @@ msgstr ""
msgid "ProjectSettings|Enable 'Delete source branch' option by default"
msgstr ""
+msgid "ProjectSettings|Enable merge trains and pipelines for merged results"
+msgstr ""
+
msgid "ProjectSettings|Every merge creates a merge commit"
msgstr ""
@@ -17159,7 +17722,7 @@ msgstr ""
msgid "ProjectSettings|Forks"
msgstr ""
-msgid "ProjectSettings|Git Large File Storage"
+msgid "ProjectSettings|Git Large File Storage (LFS)"
msgstr ""
msgid "ProjectSettings|Internal"
@@ -17195,9 +17758,6 @@ msgstr ""
msgid "ProjectSettings|Merge options"
msgstr ""
-msgid "ProjectSettings|Merge pipelines will try to validate the post-merge result prior to merging"
-msgstr ""
-
msgid "ProjectSettings|Merge requests"
msgstr ""
@@ -17225,6 +17785,9 @@ msgstr ""
msgid "ProjectSettings|Pipelines"
msgstr ""
+msgid "ProjectSettings|Pipelines for merge requests must be enabled in the CI/CD configuration file, or pipelines could be unresolvable or dropped"
+msgstr ""
+
msgid "ProjectSettings|Pipelines must succeed"
msgstr ""
@@ -17252,6 +17815,9 @@ msgstr ""
msgid "ProjectSettings|Show link to create/view merge request when pushing from the command line"
msgstr ""
+msgid "ProjectSettings|Skipped pipelines are considered successful"
+msgstr ""
+
msgid "ProjectSettings|Snippets"
msgstr ""
@@ -17267,6 +17833,9 @@ msgstr ""
msgid "ProjectSettings|These checks must pass before merge requests can be merged"
msgstr ""
+msgid "ProjectSettings|This introduces the risk of merging changes that will not pass the pipeline."
+msgstr ""
+
msgid "ProjectSettings|This setting is applied on the server level and can be overridden by an admin."
msgstr ""
@@ -17444,15 +18013,27 @@ msgstr ""
msgid "ProjectsNew|Blank project"
msgstr ""
+msgid "ProjectsNew|Connect your external repository to GitLab CI/CD."
+msgstr ""
+
msgid "ProjectsNew|Contact an administrator to enable options for importing your project."
msgstr ""
msgid "ProjectsNew|Create"
msgstr ""
+msgid "ProjectsNew|Create a blank project to house your files, plan your work, and collaborate on code, among other things."
+msgstr ""
+
+msgid "ProjectsNew|Create blank project"
+msgstr ""
+
msgid "ProjectsNew|Create from template"
msgstr ""
+msgid "ProjectsNew|Create new project"
+msgstr ""
+
msgid "ProjectsNew|Creating project & repository."
msgstr ""
@@ -17477,6 +18058,9 @@ msgstr ""
msgid "ProjectsNew|Project description %{tag_start}(optional)%{tag_end}"
msgstr ""
+msgid "ProjectsNew|Run CI/CD for external repository"
+msgstr ""
+
msgid "ProjectsNew|Template"
msgstr ""
@@ -17738,6 +18322,9 @@ msgstr ""
msgid "Protected Branch"
msgstr ""
+msgid "Protected Branches"
+msgstr ""
+
msgid "Protected Environment"
msgstr ""
@@ -17750,6 +18337,9 @@ msgstr ""
msgid "Protected Tag"
msgstr ""
+msgid "Protected Tags"
+msgstr ""
+
msgid "Protected branches"
msgstr ""
@@ -17849,6 +18439,9 @@ msgstr ""
msgid "Provider"
msgstr ""
+msgid "Proxy support for this API is not available currently"
+msgstr ""
+
msgid "Pseudonymizer data collection"
msgstr ""
@@ -17873,6 +18466,9 @@ msgstr ""
msgid "Publish to status page"
msgstr ""
+msgid "Published on status page"
+msgstr ""
+
msgid "Publishes this issue to the associated status page."
msgstr ""
@@ -18047,6 +18643,9 @@ msgstr ""
msgid "Recipe"
msgstr ""
+msgid "Reconfigure"
+msgstr ""
+
msgid "Recover hidden stage"
msgstr ""
@@ -18133,6 +18732,9 @@ msgstr ""
msgid "Registration|Your profile"
msgstr ""
+msgid "Regulate approvals by authors/committers, based on compliance frameworks. Can be changed only at the instance level."
+msgstr ""
+
msgid "Rejected (closed)"
msgstr ""
@@ -18183,15 +18785,24 @@ msgstr ""
msgid "ReleaseAssetLinkType|Image"
msgstr ""
+msgid "ReleaseAssetLinkType|Images"
+msgstr ""
+
msgid "ReleaseAssetLinkType|Other"
msgstr ""
msgid "ReleaseAssetLinkType|Package"
msgstr ""
+msgid "ReleaseAssetLinkType|Packages"
+msgstr ""
+
msgid "ReleaseAssetLinkType|Runbook"
msgstr ""
+msgid "ReleaseAssetLinkType|Runbooks"
+msgstr ""
+
msgid "Releases"
msgstr ""
@@ -18204,6 +18815,9 @@ msgstr ""
msgid "Releases documentation"
msgstr ""
+msgid "Releases|New Release"
+msgstr ""
+
msgid "Release|Something went wrong while getting the release details"
msgstr ""
@@ -18276,6 +18890,9 @@ msgstr ""
msgid "Remove fork relationship"
msgstr ""
+msgid "Remove from batch"
+msgstr ""
+
msgid "Remove from board"
msgstr ""
@@ -18465,6 +19082,12 @@ msgstr ""
msgid "Replication"
msgstr ""
+msgid "Replication enabled"
+msgstr ""
+
+msgid "Replication paused"
+msgstr ""
+
msgid "Reply by email"
msgstr ""
@@ -18492,15 +19115,15 @@ msgstr ""
msgid "Reported %{timeAgo} by %{reportedBy}"
msgstr ""
-msgid "Reported Resource Changes: %{addNum} to add, %{changeNum} to change, %{deleteNum} to delete"
-msgstr ""
-
msgid "Reporter"
msgstr ""
msgid "Reporting"
msgstr ""
+msgid "Reports"
+msgstr ""
+
msgid "Reports|%{combinedString} and %{resolvedString}"
msgstr ""
@@ -18539,6 +19162,9 @@ msgstr ""
msgid "Reports|Failure"
msgstr ""
+msgid "Reports|Identifier"
+msgstr ""
+
msgid "Reports|Metrics reports are loading"
msgstr ""
@@ -18551,6 +19177,9 @@ msgstr ""
msgid "Reports|Metrics reports failed loading results"
msgstr ""
+msgid "Reports|Scanner"
+msgstr ""
+
msgid "Reports|Severity"
msgstr ""
@@ -18599,15 +19228,33 @@ msgstr ""
msgid "Repository cleanup has started. You will receive an email once the cleanup operation is complete."
msgstr ""
+msgid "Repository files count over the limit"
+msgstr ""
+
+msgid "Repository has an invalid default branch name."
+msgstr ""
+
+msgid "Repository has more than one branch."
+msgstr ""
+
msgid "Repository has no locks."
msgstr ""
+msgid "Repository has tags."
+msgstr ""
+
msgid "Repository maintenance"
msgstr ""
msgid "Repository mirroring"
msgstr ""
+msgid "Repository must contain at least 1 file."
+msgstr ""
+
+msgid "Repository size is above the limit."
+msgstr ""
+
msgid "Repository static objects"
msgstr ""
@@ -18617,7 +19264,7 @@ msgstr ""
msgid "Repository sync capacity"
msgstr ""
-msgid "Repository: %{counter_repositories} / Wikis: %{counter_wikis} / Build Artifacts: %{counter_build_artifacts} / LFS: %{counter_lfs_objects}"
+msgid "Repository: %{counter_repositories} / Wikis: %{counter_wikis} / Build Artifacts: %{counter_build_artifacts} / LFS: %{counter_lfs_objects} / Snippets: %{counter_snippets}"
msgstr ""
msgid "RepositorySettingsAccessLevel|Select"
@@ -18629,6 +19276,9 @@ msgstr ""
msgid "Request Headers"
msgstr ""
+msgid "Request details"
+msgstr ""
+
msgid "Request parameter %{param} is missing."
msgstr ""
@@ -18735,6 +19385,9 @@ msgstr ""
msgid "Reset template"
msgstr ""
+msgid "Reset to project defaults"
+msgstr ""
+
msgid "Resetting the authorization key for this project will require updating the authorization key in every alert source it is enabled in."
msgstr ""
@@ -18831,9 +19484,6 @@ msgstr ""
msgid "Resume"
msgstr ""
-msgid "Resume replication"
-msgstr ""
-
msgid "Resync"
msgstr ""
@@ -19456,6 +20106,9 @@ msgstr ""
msgid "SecurityReports|False positive"
msgstr ""
+msgid "SecurityReports|Group Security Dashboard"
+msgstr ""
+
msgid "SecurityReports|Hide dismissed"
msgstr ""
@@ -19510,10 +20163,13 @@ msgstr ""
msgid "SecurityReports|Remove project from dashboard"
msgstr ""
-msgid "SecurityReports|Report type"
+msgid "SecurityReports|Return to dashboard"
msgstr ""
-msgid "SecurityReports|Return to dashboard"
+msgid "SecurityReports|Scan details"
+msgstr ""
+
+msgid "SecurityReports|Scanner type"
msgstr ""
msgid "SecurityReports|Security Dashboard"
@@ -19570,6 +20226,9 @@ msgstr ""
msgid "SecurityReports|There was an error while generating the report."
msgstr ""
+msgid "SecurityReports|Unable to add %{invalidProjectsMessage}"
+msgstr ""
+
msgid "SecurityReports|Unable to add %{invalidProjects}"
msgstr ""
@@ -19684,6 +20343,9 @@ msgstr ""
msgid "Select due date"
msgstr ""
+msgid "Select file"
+msgstr ""
+
msgid "Select group or project"
msgstr ""
@@ -19747,9 +20409,6 @@ msgstr ""
msgid "Select the branch you want to set as the default for this project. All merge requests and commits will automatically be made against this branch unless you specify a different one."
msgstr ""
-msgid "Select the configured storage available for new repositories to be placed on."
-msgstr ""
-
msgid "Select the custom project template source group."
msgstr ""
@@ -19942,12 +20601,6 @@ msgstr ""
msgid "Service Desk is enabled but not yet active"
msgstr ""
-msgid "Service Desk is off"
-msgstr ""
-
-msgid "Service Desk is on"
-msgstr ""
-
msgid "Service Templates"
msgstr ""
@@ -20035,6 +20688,15 @@ msgstr ""
msgid "Set the milestone to %{milestone_reference}."
msgstr ""
+msgid "Set the number of concurrent requests this secondary node will make to the primary node while backfilling."
+msgstr ""
+
+msgid "Set the synchronization and verification capacity for the secondary node."
+msgstr ""
+
+msgid "Set the timeout in seconds to send a secondary node status to the primary and IPs allowed for the secondary nodes."
+msgstr ""
+
msgid "Set time estimate"
msgstr ""
@@ -20074,6 +20736,9 @@ msgstr ""
msgid "Set weight to %{weight}."
msgstr ""
+msgid "Set what should be replicated by choosing specific projects or groups by the secondary node."
+msgstr ""
+
msgid "SetPasswordToCloneLink|set a password"
msgstr ""
@@ -20200,6 +20865,9 @@ msgstr ""
msgid "Show complete raw log"
msgstr ""
+msgid "Show details"
+msgstr ""
+
msgid "Show file browser"
msgstr ""
@@ -20209,10 +20877,13 @@ msgstr ""
msgid "Show latest version"
msgstr ""
+msgid "Show list"
+msgstr ""
+
msgid "Show me everything"
msgstr ""
-msgid "Show me how"
+msgid "Show me how to add a pipeline"
msgstr ""
msgid "Show me more advanced stuff"
@@ -20261,9 +20932,6 @@ msgstr ""
msgid "Showing all issues"
msgstr ""
-msgid "Showing all labels"
-msgstr ""
-
msgid "Showing last %{size} of log -"
msgstr ""
@@ -20375,9 +21043,6 @@ msgstr ""
msgid "Skip outdated deployment jobs"
msgstr ""
-msgid "Skip this for now"
-msgstr ""
-
msgid "Skipped"
msgstr ""
@@ -20522,6 +21187,9 @@ msgstr ""
msgid "Something went wrong while adding your award. Please try again."
msgstr ""
+msgid "Something went wrong while applying the batch of suggestions. Please try again."
+msgstr ""
+
msgid "Something went wrong while applying the suggestion. Please try again."
msgstr ""
@@ -20624,10 +21292,13 @@ msgstr ""
msgid "Something went wrong while updating a requirement."
msgstr ""
+msgid "Something went wrong while updating assignees"
+msgstr ""
+
msgid "Something went wrong while updating your list settings"
msgstr ""
-msgid "Something went wrong with your automatic subscription renewal"
+msgid "Something went wrong with your automatic subscription renewal."
msgstr ""
msgid "Something went wrong, unable to add %{project} to dashboard"
@@ -20645,6 +21316,9 @@ msgstr ""
msgid "Something went wrong, unable to search projects"
msgstr ""
+msgid "Something went wrong."
+msgstr ""
+
msgid "Something went wrong. Please try again."
msgstr ""
@@ -20828,6 +21502,9 @@ msgstr ""
msgid "Source code"
msgstr ""
+msgid "Source code (%{fileExtension})"
+msgstr ""
+
msgid "Source is not available"
msgstr ""
@@ -20912,6 +21589,9 @@ msgstr ""
msgid "Stack trace"
msgstr ""
+msgid "Stacktrace snippet"
+msgstr ""
+
msgid "Stage"
msgstr ""
@@ -21017,6 +21697,9 @@ msgstr ""
msgid "Start thread & reopen %{noteable_name}"
msgstr ""
+msgid "Start using Directed Acyclic Graphs (DAG)"
+msgstr ""
+
msgid "Start your Free Gold Trial"
msgstr ""
@@ -21479,6 +22162,21 @@ msgstr ""
msgid "SuggestedColors|Very pale orange"
msgstr ""
+msgid "Suggestion is not applicable as the suggestion was not found."
+msgstr ""
+
+msgid "Suggestions are not applicable as one or more suggestions were not found."
+msgstr ""
+
+msgid "Suggestions are not applicable as their lines cannot overlap."
+msgstr ""
+
+msgid "Suggestions must all be on the same branch."
+msgstr ""
+
+msgid "Suggestions that change line count can't be added to batches, yet."
+msgstr ""
+
msgid "Suggestions:"
msgstr ""
@@ -21521,9 +22219,6 @@ msgstr ""
msgid "Synchronization disabled"
msgstr ""
-msgid "Synchronization of container repositories is disabled."
-msgstr ""
-
msgid "System"
msgstr ""
@@ -21704,6 +22399,34 @@ msgstr ""
msgid "Terms of Service and Privacy Policy"
msgstr ""
+msgid "Terraform|%{number} Terraform report failed to generate"
+msgid_plural "Terraform|%{number} Terraform reports failed to generate"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Terraform|%{number} Terraform report was generated in your pipelines"
+msgid_plural "Terraform|%{number} Terraform reports were generated in your pipelines"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Terraform|A Terraform report failed to generate."
+msgstr ""
+
+msgid "Terraform|A Terraform report was generated in your pipelines."
+msgstr ""
+
+msgid "Terraform|Generating the report caused an error."
+msgstr ""
+
+msgid "Terraform|Reported Resource Changes: %{addNum} to add, %{changeNum} to change, %{deleteNum} to delete"
+msgstr ""
+
+msgid "Terraform|The Terraform report %{name} failed to generate."
+msgstr ""
+
+msgid "Terraform|The Terraform report %{name} was generated in your pipelines."
+msgstr ""
+
msgid "Test"
msgstr ""
@@ -21730,7 +22453,7 @@ msgstr ""
msgid "TestHooks|Ensure the project has CI pipelines."
msgstr ""
-msgid "TestHooks|Ensure the project has at least one commit."
+msgid "TestHooks|Ensure the project has deployments."
msgstr ""
msgid "TestHooks|Ensure the project has issues."
@@ -21831,7 +22554,7 @@ msgstr ""
msgid "The Prometheus server responded with \"bad request\". Please check your queries are correct and are supported in your Prometheus version. %{documentationLink}"
msgstr ""
-msgid "The URL defined on the primary node that secondary nodes should use to contact it. Defaults to URL"
+msgid "The URL defined on the primary node that secondary nodes should use to contact it."
msgstr ""
msgid "The URL to use for connecting to Elasticsearch. Use a comma-separated list to support clustering (e.g., \"http://localhost:9200, http://localhost:9201\")."
@@ -21840,6 +22563,9 @@ msgstr ""
msgid "The X509 Certificate to use when mutual TLS is required to communicate with the external authorization service. If left blank, the server certificate is still validated when accessing over HTTPS."
msgstr ""
+msgid "The above settings apply to all projects with the selected compliance framework(s)."
+msgstr ""
+
msgid "The amount of seconds after which a request to get a secondary node status will time out."
msgstr ""
@@ -22139,9 +22865,6 @@ msgstr ""
msgid "The total stage shows the time it takes between creating an issue and deploying the code to production. The data will be automatically added once you have completed the full idea to production cycle."
msgstr ""
-msgid "The unique identifier for the Geo node. Must match %{geoNodeName} if it is set in gitlab.rb, otherwise it must match %{externalUrl} with a trailing slash"
-msgstr ""
-
msgid "The update action will time out after %{number_of_minutes} minutes. For big repositories, use a clone/push combination."
msgstr ""
@@ -22166,9 +22889,6 @@ msgstr ""
msgid "The user you are trying to deactivate has been active in the past %{minimum_inactive_days} days and cannot be deactivated"
msgstr ""
-msgid "The user-facing URL of the Geo node"
-msgstr ""
-
msgid "The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6."
msgstr ""
@@ -22259,6 +22979,9 @@ msgstr ""
msgid "There was a problem fetching project branches."
msgstr ""
+msgid "There was a problem fetching project tags."
+msgstr ""
+
msgid "There was a problem fetching project users."
msgstr ""
@@ -22310,6 +23033,9 @@ msgstr ""
msgid "There was an error fetching the %{replicableType}"
msgstr ""
+msgid "There was an error fetching the Geo Settings"
+msgstr ""
+
msgid "There was an error fetching the Node's Groups"
msgstr ""
@@ -22430,6 +23156,12 @@ msgstr ""
msgid "This %{issuable} is locked. Only <strong>project members</strong> can comment."
msgstr ""
+msgid "This %{noteableTypeText} is %{confidentialLinkStart}confidential%{linkEnd} and %{lockedLinkStart}locked%{linkEnd}."
+msgstr ""
+
+msgid "This %{noteableTypeText} is locked."
+msgstr ""
+
msgid "This %{viewer} could not be displayed because %{reason}. You can %{options} instead."
msgstr ""
@@ -22448,6 +23180,9 @@ msgstr ""
msgid "This action can lead to data loss. To prevent accidental actions we ask you to confirm your intention."
msgstr ""
+msgid "This also resolves all related threads"
+msgstr ""
+
msgid "This also resolves the discussion"
msgstr ""
@@ -22538,6 +23273,9 @@ msgstr ""
msgid "This epic does not exist or you don't have sufficient permission."
msgstr ""
+msgid "This feature is currently in beta. We invite you to %{linkStart}give feedback%{linkEnd}."
+msgstr ""
+
msgid "This feature requires local storage to be enabled"
msgstr ""
@@ -22571,7 +23309,7 @@ msgstr ""
msgid "This is a Work in Progress"
msgstr ""
-msgid "This is a confidential issue."
+msgid "This is a confidential %{noteableTypeText}."
msgstr ""
msgid "This is a delayed job to run in %{remainingTime}"
@@ -22598,9 +23336,6 @@ msgstr ""
msgid "This is your current session"
msgstr ""
-msgid "This issue is %{confidentialLinkStart}confidential%{linkEnd} and %{lockedLinkStart}locked%{linkEnd}."
-msgstr ""
-
msgid "This issue is confidential"
msgstr ""
@@ -22610,9 +23345,6 @@ msgstr ""
msgid "This issue is in a child epic of the filtered epic"
msgstr ""
-msgid "This issue is locked."
-msgstr ""
-
msgid "This job depends on other jobs with expired/erased artifacts: %{invalid_dependencies}"
msgstr ""
@@ -22712,6 +23444,9 @@ msgstr ""
msgid "This license has already expired."
msgstr ""
+msgid "This link points to external content"
+msgstr ""
+
msgid "This may expose confidential information as the selected fork is in another namespace that can have other members."
msgstr ""
@@ -22739,6 +23474,9 @@ msgstr ""
msgid "This page will be removed in a future release."
msgstr ""
+msgid "This pipeline does not use the %{codeStart}needs%{codeEnd} keyword and can't be represented as a directed acyclic graph."
+msgstr ""
+
msgid "This pipeline makes use of a predefined CI/CD configuration enabled by %{strongStart}Auto DevOps.%{strongEnd}"
msgstr ""
@@ -22967,6 +23705,9 @@ msgstr ""
msgid "Time from last commit to merge"
msgstr ""
+msgid "Time in seconds"
+msgstr ""
+
msgid "Time in seconds GitLab will wait for a response from the external service. When the service does not respond in time, access will be denied."
msgstr ""
@@ -23388,9 +24129,15 @@ msgstr ""
msgid "Total artifacts size: %{total_size}"
msgstr ""
+msgid "Total cores (CPUs)"
+msgstr ""
+
msgid "Total issues"
msgstr ""
+msgid "Total memory (GB)"
+msgstr ""
+
msgid "Total test time for all commits/merges"
msgstr ""
@@ -23592,6 +24339,9 @@ msgstr ""
msgid "U2F only works with HTTPS-enabled websites. Contact your administrator for more details."
msgstr ""
+msgid "UI Development Kit"
+msgstr ""
+
msgid "URL"
msgstr ""
@@ -23664,9 +24414,6 @@ msgstr ""
msgid "Unable to load the merge request widget. Try reloading the page."
msgstr ""
-msgid "Unable to process group import file"
-msgstr ""
-
msgid "Unable to resolve"
msgstr ""
@@ -23682,6 +24429,9 @@ msgstr ""
msgid "Unable to sign you in to the group with SAML due to \"%{reason}\""
msgstr ""
+msgid "Unable to suggest a path. Please refresh and try again."
+msgstr ""
+
msgid "Unable to update label prioritization at this time"
msgstr ""
@@ -23697,6 +24447,9 @@ msgstr ""
msgid "Unarchiving the project will restore people's ability to make changes to it. The repository can be committed to, and issues, comments, and other entities can be created. %{strong_start}Once active, this project shows up in the search and on the dashboard.%{strong_end}"
msgstr ""
+msgid "Unassign from commenting user"
+msgstr ""
+
msgid "Unblock"
msgstr ""
@@ -23772,6 +24525,9 @@ msgstr ""
msgid "Unreachable"
msgstr ""
+msgid "Unrecognized cluster type"
+msgstr ""
+
msgid "Unresolve"
msgstr ""
@@ -23832,6 +24588,9 @@ msgstr ""
msgid "Unsubscribes from this %{quick_action_target}."
msgstr ""
+msgid "Unsupported todo type passed. Supported todo types are: %{todo_types}"
+msgstr ""
+
msgid "Until"
msgstr ""
@@ -23859,6 +24618,9 @@ msgstr ""
msgid "Update approval rule"
msgstr ""
+msgid "Update approvers"
+msgstr ""
+
msgid "Update failed"
msgstr ""
@@ -23868,6 +24630,9 @@ msgstr ""
msgid "Update it"
msgstr ""
+msgid "Update iteration"
+msgstr ""
+
msgid "Update now"
msgstr ""
@@ -23913,7 +24678,7 @@ msgstr ""
msgid "Updated at"
msgstr ""
-msgid "Updated to"
+msgid "Updated to %{linkStart}chart v%{linkEnd}"
msgstr ""
msgid "Updating"
@@ -23943,6 +24708,9 @@ msgstr ""
msgid "Upload CSV file"
msgstr ""
+msgid "Upload License"
+msgstr ""
+
msgid "Upload New File"
msgstr ""
@@ -24024,16 +24792,19 @@ msgstr ""
msgid "UsageQuota|Pipelines"
msgstr ""
+msgid "UsageQuota|Purchase more storage"
+msgstr ""
+
msgid "UsageQuota|Repositories"
msgstr ""
msgid "UsageQuota|Repository"
msgstr ""
-msgid "UsageQuota|Storage"
+msgid "UsageQuota|Snippets"
msgstr ""
-msgid "UsageQuota|Storage usage:"
+msgid "UsageQuota|Storage"
msgstr ""
msgid "UsageQuota|This namespace has no projects which use shared runners"
@@ -24066,6 +24837,12 @@ msgstr ""
msgid "UsageQuota|Wikis"
msgstr ""
+msgid "UsageQuota|You used: %{usage} %{limit}"
+msgstr ""
+
+msgid "UsageQuota|out of %{formattedLimit} of your namespace storage"
+msgstr ""
+
msgid "Use %{code_start}::%{code_end} to create a %{link_start}scoped label set%{link_end} (eg. %{code_start}priority::1%{code_end})"
msgstr ""
@@ -24132,6 +24909,9 @@ msgstr ""
msgid "User IDs"
msgstr ""
+msgid "User Lists can only be created and modified with %{linkStart}the API%{linkEnd}"
+msgstr ""
+
msgid "User OAuth applications"
msgstr ""
@@ -24156,6 +24936,9 @@ msgstr ""
msgid "User key was successfully removed."
msgstr ""
+msgid "User list %{name} will be removed. Are you sure?"
+msgstr ""
+
msgid "User map"
msgstr ""
@@ -24177,148 +24960,10 @@ msgstr ""
msgid "User was successfully updated."
msgstr ""
-msgid "UserOnboardingTour|%{activeTour}/%{totalTours}"
-msgstr ""
-
-msgid "UserOnboardingTour|%{completed}/%{total} steps completed"
-msgstr ""
-
-msgid "UserOnboardingTour|%{emphasisStart}Well done!%{emphasisEnd}%{lineBreak}%{lineBreak}That's it for our guided tour, congratulations for making it all the way to the end!%{lineBreak}%{lineBreak}We hope this gave you a good overview of GitLab and how it can help you. We'll now show you how to create your own project and invite your colleagues."
-msgstr ""
-
-msgid "UserOnboardingTour|Adding other members to a project is done through Project Settings. Click on %{emphasisStart}Settings%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Alright, that's it for Commits. Let's take a look at the %{emphasisStart}Branches%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Awesome! Now click on %{emphasisStart}Members%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Click on one of the %{emphasisStart}Compare%{emphasisEnd} buttons to compare a branch to master."
-msgstr ""
-
-msgid "UserOnboardingTour|Click on one of the %{emphasisStart}pipeline IDs%{emphasisEnd} to see the details of a pipeline."
-msgstr ""
-
-msgid "UserOnboardingTour|Click to open the latest commit to see its details."
-msgstr ""
-
-msgid "UserOnboardingTour|Close 'Learn GitLab'"
-msgstr ""
-
-msgid "UserOnboardingTour|Commits are shown in chronological order and can be filtered by the commit message or by the branch."
+msgid "UserList|Delete %{name}?"
msgstr ""
-msgid "UserOnboardingTour|Create a project"
-msgstr ""
-
-msgid "UserOnboardingTour|Exit 'Learn GitLab'"
-msgstr ""
-
-msgid "UserOnboardingTour|Got it"
-msgstr ""
-
-msgid "UserOnboardingTour|Great job! %{clapHands} We hope the tour was helpful and that you learned how to use GitLab.%{lineBreak}%{lineBreak}We'd love to get your feedback on this tour.%{lineBreak}%{lineBreak}%{emphasisStart}How helpful would you say this guided tour was?%{emphasisEnd}%{lineBreak}%{lineBreak}"
-msgstr ""
-
-msgid "UserOnboardingTour|Guided GitLab Tour"
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can compare the changes of this branch to another one. Changes are divided by files so that it's easier to see what was changed where."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can create a project from scratch, start with a template or import a repository from other platforms. Whatever you choose, we'll guide you through the process.%{lineBreak}%{lineBreak}Fill in your new project information and click on %{emphasisStart}Create Project%{emphasisEnd} to progress to the next step."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can see the breakdown of the pipelines: its stages and jobs in each of the stages and their status.%{lineBreak}%{lineBreak}Our CI/CD pipelines are quite complex, most of our users have fewer and simpler pipelines."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can see the current members of the project (just you at the moment) and invite new members.%{lineBreak}%{lineBreak}You can invite multiple members at once (existing GitLab users or invite by email) and you can also set their roles and permissions.%{lineBreak}%{lineBreak}Add a few members and click on %{emphasisStart}Add to project%{emphasisEnd} to complete this step."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can see what changes were made with this commit, on what branch and if there's a related merge request. The status of the pipeline will also show up if CI/CD is set up.%{lineBreak}%{lineBreak}You can also comment on the lines of code that were changed and start a discussion with your colleagues!"
-msgstr ""
-
-msgid "UserOnboardingTour|Here's an overview of branches in the %{emphasisStart}%{projectName}%{emphasisEnd} project. They're split into Active and Stale.%{lineBreak}%{lineBreak}From here, you can create a new merge request from a branch, or compare the branch to any other branch in the project. By default, it will compare it to the master branch."
-msgstr ""
-
-msgid "UserOnboardingTour|Invite colleagues"
-msgstr ""
-
-msgid "UserOnboardingTour|Issues are great for communicating and keeping track of progress in GitLab. These are all issues that are open in the %{emphasisStart}%{projectName}%{emphasisEnd}.%{lineBreak}%{lineBreak}You can help us improve GitLab by contributing work to issues that are labeled <span class=\"badge color-label accept-mr-label\">Accepting merge requests</span>.%{lineBreak}%{lineBreak}This list can be filtered by labels, milestones, assignees, authors... We'll show you how it looks when the list is filtered by a label."
-msgstr ""
-
-msgid "UserOnboardingTour|Learn GitLab"
-msgstr ""
-
-msgid "UserOnboardingTour|Let's take a closer look at a merge request. Click on the title of one."
-msgstr ""
-
-msgid "UserOnboardingTour|Let's take a closer look at all the commits. Click on %{emphasisStart}Commits%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Let's take a closer look at the repository of this project. Click on %{emphasisStart}Repository%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|No thanks"
-msgstr ""
-
-msgid "UserOnboardingTour|Ok, let's go"
-msgstr ""
-
-msgid "UserOnboardingTour|Ok, show me"
-msgstr ""
-
-msgid "UserOnboardingTour|Open one of the issues by clicking on its title."
-msgstr ""
-
-msgid "UserOnboardingTour|Restart this step"
-msgstr ""
-
-msgid "UserOnboardingTour|Skip this step"
-msgstr ""
-
-msgid "UserOnboardingTour|Sweet! Your project was created and is ready to be used.%{lineBreak}%{lineBreak}You can start adding files to the repository or clone it. One last thing we want to show you is how to invite your colleagues to your new project."
-msgstr ""
-
-msgid "UserOnboardingTour|Take a look. Here's a nifty menu for quickly creating issues, merge requests, snippets, projects and groups. Click on it and select \"New project\" from the \"GitLab\" section to get started."
-msgstr ""
-
-msgid "UserOnboardingTour|Thanks for taking the guided tour. Remember, if you want to go through it again, you can start %{emphasisStart}Learn GitLab%{emphasisEnd} in the help menu on the top right."
-msgstr ""
-
-msgid "UserOnboardingTour|Thanks for the feedback! %{thumbsUp}"
-msgstr ""
-
-msgid "UserOnboardingTour|That's it for issues. Let'st take a look at %{emphasisStart}Merge Requests%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|That's it for merge requests. Now for the final part of this guided tour - the %{emphasisStart}CI/CD%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|That's it for the Repository. Let's take a look at the %{emphasisStart}Issues%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|The structure of this page is very similar to issues. Status, description, discussion and the sidebar are all here.%{lineBreak}%{lineBreak}But take a look below the description and you'll notice that there's more information about the merge request, the CI/CD pipeline and the options for approving it.%{lineBreak}%{lineBreak}Alongside the discussion you can also see more information about commits in this merge request, the status of pipelines and review all changes that were made."
-msgstr ""
-
-msgid "UserOnboardingTour|There's a lot of information here but don't worry, we'll go through it.%{lineBreak}%{lineBreak}On the top you can see the status of the issue and when it was opened and by whom. Directly below it is the issue description and below that are other %{emphasisStart}related issues%{emphasisEnd} and %{emphasisStart}merge requests%{emphasisEnd} (if any). Then below that is the %{emphasisStart}discussion%{emphasisEnd}, that's where most of the communication happens.%{lineBreak}%{lineBreak}On the right, there's a sidebar where you can view/change the %{emphasisStart}assignee, milestone, due date, labels, weight%{emphasisEnd}, etc."
-msgstr ""
-
-msgid "UserOnboardingTour|These are all the CI/CD pipelines we have for our %{emphasisStart}%{projectName}%{emphasisEnd} project.%{lineBreak}%{lineBreak}Here you can see the status of each pipeline, for what commit it's running for, its stages and the status for them."
-msgstr ""
-
-msgid "UserOnboardingTour|These are all the issues that are available for community contributions. Let's take a closer look at one of them."
-msgstr ""
-
-msgid "UserOnboardingTour|This is an overview of all merge requests in this project. Similarly to the issues overview it can be filtered down by things like labels, milestones, authors, assignees, etc."
-msgstr ""
-
-msgid "UserOnboardingTour|This is the repository for the %{emphasisStart}%{projectName}%{emphasisEnd} project. All our code is stored here. Feel free to explore and take a closer look at folders and files.%{lineBreak}%{lineBreak}Above the file structure you can see the latest commit, who the author is and the status of the CI/CD pipeline.%{lineBreak}%{lineBreak}If you scroll down below the file structure, you'll find the Readme of this project. This is defined in the README.md file at the root of the repository."
-msgstr ""
-
-msgid "UserOnboardingTour|Welcome to the project overview of the %{emphasisStart}%{projectName}%{emphasisEnd} project. This is the project that we use to work on GitLab. At first, a project seems like a simple repository, but at GitLab, a project is so much more.%{lineBreak}%{lineBreak}You can create projects for hosting your codebase, use it as an issue tracker, collaborate on code, and continuously build, test, and deploy your app with built-in GitLab CI/CD."
+msgid "UserList|created %{timeago}"
msgstr ""
msgid "UserProfile|Activity"
@@ -24471,6 +25116,9 @@ msgstr ""
msgid "UsersSelect|Unassigned"
msgstr ""
+msgid "Using %{codeStart}needs%{codeEnd} allows jobs to run before their stage is reached, as soon as their individual dependencies are met, which speeds up your pipelines."
+msgstr ""
+
msgid "Using %{code_start}::%{code_end} denotes a %{link_start}scoped label set%{link_end}"
msgstr ""
@@ -24555,9 +25203,6 @@ msgstr ""
msgid "Versions"
msgstr ""
-msgid "Very helpful"
-msgstr ""
-
msgid "View Documentation"
msgstr ""
@@ -24706,6 +25351,9 @@ msgstr ""
msgid "VisibilityLevel|Unknown"
msgstr ""
+msgid "Visit settings page"
+msgstr ""
+
msgid "VisualReviewApp|%{stepStart}Step 1%{stepEnd}. Copy the following script:"
msgstr ""
@@ -24799,6 +25447,9 @@ msgstr ""
msgid "VulnerabilityManagement|Something went wrong while trying to delete the comment. Please try again later."
msgstr ""
+msgid "VulnerabilityManagement|Something went wrong while trying to refresh the vulnerability. Please try again later."
+msgstr ""
+
msgid "VulnerabilityManagement|Something went wrong while trying to retrieve the vulnerability history. Please try again later."
msgstr ""
@@ -24841,6 +25492,9 @@ msgstr ""
msgid "Vulnerability|Class"
msgstr ""
+msgid "Vulnerability|Crash Address"
+msgstr ""
+
msgid "Vulnerability|Description"
msgstr ""
@@ -24916,9 +25570,6 @@ msgstr ""
msgid "We couldn't reach the Prometheus server. Either the server no longer exists or the configuration details need updating."
msgstr ""
-msgid "We created a short guided tour that will help you learn the basics of GitLab and how it will help you be better at your job. It should only take a couple of minutes. You will be guided by two types of helpers, best recognized by their color."
-msgstr ""
-
msgid "We detected potential spam in the %{humanized_resource_name}. Please solve the reCAPTCHA to proceed."
msgstr ""
@@ -24952,6 +25603,9 @@ msgstr ""
msgid "We've found no vulnerabilities"
msgstr ""
+msgid "Web Application Firewall"
+msgstr ""
+
msgid "Web IDE"
msgstr ""
@@ -25012,9 +25666,6 @@ msgstr ""
msgid "Welcome to GitLab.com<br>@%{name}!"
msgstr ""
-msgid "Welcome to the Guided GitLab Tour"
-msgstr ""
-
msgid "Welcome to the guided GitLab tour"
msgstr ""
@@ -25062,9 +25713,6 @@ msgstr ""
msgid "While it's rare to have no vulnerabilities, it can happen. In any event, we ask that you please double check your settings to make sure you've set up your dashboard correctly."
msgstr ""
-msgid "White helpers give contextual information."
-msgstr ""
-
msgid "Who can be an approver?"
msgstr ""
@@ -25110,12 +25758,18 @@ msgstr ""
msgid "WikiEmptyIssueMessage|Suggest wiki improvement"
msgstr ""
+msgid "WikiEmptyIssueMessage|You must be a group member in order to add wiki pages. If you have suggestions for how to improve the wiki for this group, consider opening an issue in the %{issues_link}."
+msgstr ""
+
msgid "WikiEmptyIssueMessage|You must be a project member in order to add wiki pages. If you have suggestions for how to improve the wiki for this project, consider opening an issue in the %{issues_link}."
msgstr ""
msgid "WikiEmptyIssueMessage|issue tracker"
msgstr ""
+msgid "WikiEmpty|A wiki is where you can store all the details about your group. This can include why you've created it, its principles, how to use it, and so on."
+msgstr ""
+
msgid "WikiEmpty|A wiki is where you can store all the details about your project. This can include why you've created it, its principles, how to use it, and so on."
msgstr ""
@@ -25125,12 +25779,21 @@ msgstr ""
msgid "WikiEmpty|Suggest wiki improvement"
msgstr ""
+msgid "WikiEmpty|The wiki lets you write documentation for your group"
+msgstr ""
+
msgid "WikiEmpty|The wiki lets you write documentation for your project"
msgstr ""
+msgid "WikiEmpty|This group has no wiki pages"
+msgstr ""
+
msgid "WikiEmpty|This project has no wiki pages"
msgstr ""
+msgid "WikiEmpty|You must be a group member in order to add wiki pages."
+msgstr ""
+
msgid "WikiEmpty|You must be a project member in order to add wiki pages."
msgstr ""
@@ -25407,13 +26070,13 @@ msgstr ""
msgid "You can now export your security dashboard to a CSV report."
msgstr ""
-msgid "You can now submit a merge request to get this change into the original branch."
+msgid "You can now manage alert endpoint configuration in the Alerts section on the Operations settings page. Fields on this page have been deprecated."
msgstr ""
-msgid "You can now submit a merge request to get this change into the original project."
+msgid "You can now submit a merge request to get this change into the original branch."
msgstr ""
-msgid "You can only add files when you are on a branch"
+msgid "You can now submit a merge request to get this change into the original project."
msgstr ""
msgid "You can only edit files when you are on a branch"
@@ -25635,7 +26298,7 @@ msgstr ""
msgid "You need to upload a Google Takeout archive."
msgstr ""
-msgid "You reached %{usage_in_percent} of %{namespace_name}'s capacity (%{used_storage} of %{storage_limit})"
+msgid "You reached %{usage_in_percent} of %{namespace_name}'s storage capacity (%{used_storage} of %{storage_limit})"
msgstr ""
msgid "You tried to fork %{link_to_the_project} but it failed for the following reason:"
@@ -25938,13 +26601,13 @@ msgstr ""
msgid "Your subscription expired!"
msgstr ""
-msgid "Your subscription has been downgraded"
+msgid "Your subscription has been downgraded."
msgstr ""
-msgid "Your subscription will automatically renew in %{remaining_days}"
+msgid "Your subscription will automatically renew in %{remaining_days}."
msgstr ""
-msgid "Your subscription will expire in %{remaining_days}"
+msgid "Your subscription will expire in %{remaining_days}."
msgstr ""
msgid "Zoom meeting added"
@@ -26024,9 +26687,6 @@ msgstr ""
msgid "branch name"
msgstr ""
-msgid "build pipeline reference mismatch"
-msgstr ""
-
msgid "by"
msgstr ""
@@ -26045,6 +26705,9 @@ msgstr ""
msgid "cannot block others"
msgstr ""
+msgid "cannot contain HTML/XML tags, including any word between angle brackets (<,>)."
+msgstr ""
+
msgid "cannot include leading slash or directory traversal."
msgstr ""
@@ -26075,55 +26738,6 @@ msgstr ""
msgid "ciReport|%{remainingPackagesCount} more"
msgstr ""
-msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability"
-msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability for the source branch only"
-msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities for the source branch only"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerability"
-msgid_plural "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerabilities"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new vulnerability"
-msgid_plural "ciReport|%{reportType} %{status} detected %{newCount} new vulnerabilities"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities for the source branch only"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{fixedCount} fixed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} vulnerability for the source branch only"
-msgid_plural "ciReport|%{reportType} %{status} detected %{newCount} vulnerabilities for the source branch only"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "ciReport|%{reportType} %{status} detected no new vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected no vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected no vulnerabilities for the source branch only"
-msgstr ""
-
msgid "ciReport|%{reportType} is loading"
msgstr ""
@@ -26142,7 +26756,7 @@ msgstr ""
msgid "ciReport|All projects"
msgstr ""
-msgid "ciReport|All report types"
+msgid "ciReport|All scanner types"
msgstr ""
msgid "ciReport|All severities"
@@ -26166,6 +26780,9 @@ msgstr ""
msgid "ciReport|Container scanning detects known vulnerabilities in your docker images."
msgstr ""
+msgid "ciReport|Coverage Fuzzing"
+msgstr ""
+
msgid "ciReport|Create a merge request to implement this solution, or download and apply the patch manually."
msgstr ""
@@ -26324,6 +26941,9 @@ msgstr ""
msgid "customize"
msgstr ""
+msgid "data"
+msgstr ""
+
msgid "date must not be after 9999-12-31"
msgstr ""
@@ -26394,6 +27014,9 @@ msgstr ""
msgid "entries cannot contain HTML tags"
msgstr ""
+msgid "epic"
+msgstr ""
+
msgid "error"
msgstr ""
@@ -26415,9 +27038,6 @@ msgstr ""
msgid "expires on %{timebox_due_date}"
msgstr ""
-msgid "external_url"
-msgstr ""
-
msgid "failed"
msgstr ""
@@ -26453,9 +27073,6 @@ msgstr ""
msgid "from"
msgstr ""
-msgid "geo_node_name"
-msgstr ""
-
msgid "group"
msgstr ""
@@ -26477,9 +27094,6 @@ msgstr ""
msgid "https://your-bitbucket-server"
msgstr ""
-msgid "image"
-msgstr ""
-
msgid "image diff"
msgstr ""
@@ -26623,6 +27237,9 @@ msgstr ""
msgid "load it anyway"
msgstr ""
+msgid "loading"
+msgstr ""
+
msgid "locked by %{path_lock_user_name} %{created_at}"
msgstr ""
@@ -26751,9 +27368,6 @@ msgstr ""
msgid "mrWidget|Deployment statistics are not available currently"
msgstr ""
-msgid "mrWidget|Detect issues before deployment with a CI pipeline that continuously tests your code. We created a quick guide that will show you how to create one. Make your code more secure and more robust in just a minute."
-msgstr ""
-
msgid "mrWidget|Did not close"
msgstr ""
@@ -26916,6 +27530,9 @@ msgstr ""
msgid "mrWidget|To approve this merge request, please enter your password. This project requires all approvals to be authenticated."
msgstr ""
+msgid "mrWidget|Use %{linkStart}CI pipelines to test your code%{linkEnd}, simply add a GitLab CI configuration file to your project. It only takes a minute to make your code more secure and robust."
+msgstr ""
+
msgid "mrWidget|When this merge request is ready, remove the WIP: prefix from the title to allow it to be merged"
msgstr ""
@@ -26952,6 +27569,9 @@ msgstr ""
msgid "must be greater than start date"
msgstr ""
+msgid "must contain only valid frameworks"
+msgstr ""
+
msgid "my-awesome-group"
msgstr ""
@@ -26964,6 +27584,9 @@ msgstr ""
msgid "needs to be between 10 minutes and 1 month"
msgstr ""
+msgid "never"
+msgstr ""
+
msgid "never expires"
msgstr ""
@@ -26991,6 +27614,9 @@ msgstr ""
msgid "nounSeries|%{firstItem} and %{lastItem}"
msgstr ""
+msgid "nounSeries|%{item}"
+msgstr ""
+
msgid "nounSeries|%{item}, %{nextItem}"
msgstr ""
@@ -27127,6 +27753,9 @@ msgstr ""
msgid "revised"
msgstr ""
+msgid "satisfied"
+msgstr ""
+
msgid "score"
msgstr ""
@@ -27157,6 +27786,9 @@ msgstr ""
msgid "severity|Unknown"
msgstr ""
+msgid "should be an array of %{object_name} objects"
+msgstr ""
+
msgid "should be greater than or equal to %{access} inherited membership from group %{group_name}"
msgstr ""
@@ -27211,10 +27843,13 @@ msgstr ""
msgid "suggestPipeline|2/2: Commit your changes"
msgstr ""
-msgid "suggestPipeline|Commit the changes and your pipeline will automatically run for the first time."
+msgid "suggestPipeline|Choose %{boldStart}Code Quality%{boldEnd} to add a pipeline that tests the quality of your code."
msgstr ""
-msgid "suggestPipeline|We recommend the %{boldStart}Code Quality%{boldEnd} template, which will add a report widget to your Merge Requests. This way you’ll learn about code quality degradations much sooner. %{footerStart} Goodbye technical debt! %{footerEnd}"
+msgid "suggestPipeline|The template is ready! You can now commit it to create your first pipeline."
+msgstr ""
+
+msgid "suggestPipeline|We’re adding a GitLab CI configuration file to add a pipeline to the project. You could create it manually, but we recommend that you start with a GitLab template that works out of the box."
msgstr ""
msgid "syntax is correct"
@@ -27274,6 +27909,9 @@ msgstr ""
msgid "uses Kubernetes clusters to deploy your code!"
msgstr ""
+msgid "v%{version} published %{timeAgo}"
+msgstr ""
+
msgid "verify ownership"
msgstr ""
diff --git a/locale/hr_HR/gitlab.po b/locale/hr_HR/gitlab.po
index f209887ac99..62e143f1332 100644
--- a/locale/hr_HR/gitlab.po
+++ b/locale/hr_HR/gitlab.po
@@ -2,7 +2,7 @@ msgid ""
msgstr ""
"Project-Id-Version: gitlab-ee\n"
"Report-Msgid-Bugs-To: \n"
-"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Last-Translator: \n"
"Language-Team: Croatian\n"
"Language: hr_HR\n"
"MIME-Version: 1.0\n"
@@ -10,9 +10,11 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
"X-Crowdin-Project: gitlab-ee\n"
+"X-Crowdin-Project-ID: 288872\n"
"X-Crowdin-Language: hr\n"
"X-Crowdin-File: /master/locale/gitlab.pot\n"
-"PO-Revision-Date: 2020-06-08 15:10\n"
+"X-Crowdin-File-ID: 6\n"
+"PO-Revision-Date: 2020-07-02 01:20\n"
msgid " %{start} to %{end}"
msgstr ""
@@ -224,6 +226,12 @@ msgstr[0] ""
msgstr[1] ""
msgstr[2] ""
+msgid "%d milestone"
+msgid_plural "%d milestones"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+
msgid "%d minute"
msgid_plural "%d minutes"
msgstr[0] ""
@@ -278,12 +286,6 @@ msgstr[0] ""
msgstr[1] ""
msgstr[2] ""
-msgid "%d url scanned"
-msgid_plural "%d urls scanned"
-msgstr[0] ""
-msgstr[1] ""
-msgstr[2] ""
-
msgid "%d vulnerability"
msgid_plural "%d vulnerabilities"
msgstr[0] ""
@@ -368,9 +370,15 @@ msgstr[2] ""
msgid "%{count} related %{pluralized_subject}: %{links}"
msgstr ""
+msgid "%{dashboard_path} could not be found."
+msgstr ""
+
msgid "%{days} days until tags are automatically removed"
msgstr ""
+msgid "%{deployLinkStart}Use a template to deploy to ECS%{deployLinkEnd}, or use a docker image to %{commandsLinkStart}run AWS commands in GitLab CI/CD%{commandsLinkEnd}."
+msgstr ""
+
msgid "%{description}- Sentry event: %{errorUrl}- First seen: %{firstSeen}- Last seen: %{lastSeen} %{countLabel}: %{count}%{userCountLabel}: %{userCount}"
msgstr ""
@@ -413,12 +421,18 @@ msgstr ""
msgid "%{host} sign-in from new location"
msgstr ""
-msgid "%{icon}You are about to add %{usersTag} people to the discussion. Proceed with caution."
+msgid "%{icon}You are about to add %{usersTag} people to the discussion. They will all receive a notification."
+msgstr ""
+
+msgid "%{integrations_link_start}Integrations%{link_end} enable you to make third-party applications part of your GitLab workflow. If the available integrations don't meet your needs, consider using a %{webhooks_link_start}webhook%{link_end}."
msgstr ""
msgid "%{issuableType} will be removed! Are you sure?"
msgstr ""
+msgid "%{issuesCount} issues in this group"
+msgstr ""
+
msgid "%{issuesSize} issues"
msgstr ""
@@ -428,7 +442,7 @@ msgstr ""
msgid "%{labelStart}Class:%{labelEnd} %{class}"
msgstr ""
-msgid "%{labelStart}Confidence:%{labelEnd} %{confidence}"
+msgid "%{labelStart}Evidence:%{labelEnd} %{evidence}"
msgstr ""
msgid "%{labelStart}File:%{labelEnd} %{file}"
@@ -446,6 +460,9 @@ msgstr ""
msgid "%{labelStart}Report Type:%{labelEnd} %{reportType}"
msgstr ""
+msgid "%{labelStart}Scanner:%{labelEnd} %{scanner}"
+msgstr ""
+
msgid "%{labelStart}Severity:%{labelEnd} %{severity}"
msgstr ""
@@ -470,9 +487,6 @@ msgstr ""
msgid "%{link_start}Read more%{link_end} about role permissions"
msgstr ""
-msgid "%{link} can be used for binding events when something is happening within the project."
-msgstr ""
-
msgid "%{listToShow}, and %{awardsListLength} more."
msgstr ""
@@ -506,6 +520,9 @@ msgstr ""
msgid "%{name} found %{resultsString}"
msgstr ""
+msgid "%{name} is already being used for another emoji"
+msgstr ""
+
msgid "%{name} is scheduled for %{action}"
msgstr ""
@@ -554,6 +571,9 @@ msgstr ""
msgid "%{primary} (%{secondary})"
msgstr ""
+msgid "%{ref} cannot be added: %{error}"
+msgstr ""
+
msgid "%{releases} release"
msgid_plural "%{releases} releases"
msgstr[0] ""
@@ -563,13 +583,40 @@ msgstr[2] ""
msgid "%{remaining_approvals} left"
msgstr ""
-msgid "%{retryButtonStart}Try again%{retryButtonEnd} or %{newFileButtonStart}attach a new file%{newFileButtonEnd}"
+msgid "%{reportType} %{status} detected %{critical} critical and %{high} high severity vulnerabilities out of %{total}."
msgstr ""
-msgid "%{screenreaderOnlyStart}Keyboard shorcuts%{screenreaderOnlyEnd} Disabled"
+msgid "%{reportType} %{status} detected %{critical} critical and %{high} high severity vulnerabilities."
msgstr ""
-msgid "%{screenreaderOnlyStart}Keyboard shorcuts%{screenreaderOnlyEnd} Enabled"
+msgid "%{reportType} %{status} detected %{critical} critical severity vulnerabilities out of %{total}."
+msgstr ""
+
+msgid "%{reportType} %{status} detected %{critical} critical severity vulnerability."
+msgid_plural "%{reportType} %{status} detected %{critical} critical severity vulnerabilities."
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+
+msgid "%{reportType} %{status} detected %{high} high severity vulnerabilities out of %{total}."
+msgstr ""
+
+msgid "%{reportType} %{status} detected %{high} high severity vulnerability."
+msgid_plural "%{reportType} %{status} detected %{high} high severity vulnerabilities."
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+
+msgid "%{reportType} %{status} detected %{other} vulnerability."
+msgid_plural "%{reportType} %{status} detected %{other} vulnerabilities."
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+
+msgid "%{reportType} %{status} detected no new vulnerabilities."
+msgstr ""
+
+msgid "%{retryButtonStart}Try again%{retryButtonEnd} or %{newFileButtonStart}attach a new file%{newFileButtonEnd}"
msgstr ""
msgid "%{service_title} %{message}."
@@ -671,6 +718,12 @@ msgstr ""
msgid "%{token}..."
msgstr ""
+msgid "%{totalCpu} (%{freeSpacePercentage}%{percentSymbol} free)"
+msgstr ""
+
+msgid "%{totalMemory} (%{freeSpacePercentage}%{percentSymbol} free)"
+msgstr ""
+
msgid "%{totalWeight} total weight"
msgstr ""
@@ -701,6 +754,12 @@ msgstr ""
msgid "%{verb} %{time_spent_value} spent time."
msgstr ""
+msgid "%{webhooks_link_start}%{webhook_type}%{link_end} enable you to send notifications to web applications in response to events in a group or project."
+msgstr ""
+
+msgid "%{webhooks_link_start}%{webhook_type}%{link_end} enable you to send notifications to web applications in response to events in a group or project. We recommend using an %{integrations_link_start}integration%{link_end} in preference to a webhook."
+msgstr ""
+
msgid "'%{level}' is not a valid visibility level"
msgstr ""
@@ -728,9 +787,6 @@ msgstr ""
msgid "(No changes)"
msgstr ""
-msgid "(Show all)"
-msgstr ""
-
msgid "(check progress)"
msgstr ""
@@ -800,6 +856,9 @@ msgstr ""
msgid "0 for unlimited"
msgstr ""
+msgid "0 for unlimited, only effective with remote storage enabled."
+msgstr ""
+
msgid "1 %{type} addition"
msgid_plural "%{count} %{type} additions"
msgstr[0] ""
@@ -889,9 +948,9 @@ msgstr[2] ""
msgid "1 user"
msgid_plural "%{num} users"
-msgstr[0] ""
-msgstr[1] ""
-msgstr[2] ""
+msgstr[0] "1 korisnik"
+msgstr[1] "%{num} korisnika"
+msgstr[2] "%{num} korisnika"
msgid "1 week"
msgstr ""
@@ -962,6 +1021,12 @@ msgstr ""
msgid "<code>\"johnsmith@example.com\": \"johnsmith@example.com\"</code> will add \"By <a href=\"#\">johnsmith@example.com</a>\" to all issues and comments originally created by johnsmith@example.com. By default, the email address or username is masked to ensure the user's privacy. Use this option if you want to show the full email address."
msgstr ""
+msgid "<code>Masked</code> to prevent the values from being displayed in job logs (must match certain regexp requirements)."
+msgstr ""
+
+msgid "<code>Protected</code> to expose them to protected branches or tags only."
+msgstr ""
+
msgid "<namespace / project>"
msgstr ""
@@ -989,9 +1054,6 @@ msgstr ""
msgid "A .NET Core console application template, customizable for any .NET Core project"
msgstr ""
-msgid "A DAG must have two dependent jobs to be visualized on this tab."
-msgstr ""
-
msgid "A GitBook site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
msgstr ""
@@ -1022,6 +1084,12 @@ msgstr ""
msgid "A deleted user"
msgstr ""
+msgid "A file has been changed."
+msgstr ""
+
+msgid "A file was not found."
+msgstr ""
+
msgid "A file with '%{file_name}' already exists in %{branch} branch"
msgstr ""
@@ -1088,7 +1156,7 @@ msgstr ""
msgid "A subscription will trigger a new pipeline on the default branch of this project when a pipeline successfully completes for a new tag on the %{default_branch_docs} of the subscribed project."
msgstr ""
-msgid "A terraform report was generated in your pipelines."
+msgid "A suggestion is not applicable."
msgstr ""
msgid "A user with write access to the source branch selected this option"
@@ -1259,6 +1327,9 @@ msgstr ""
msgid "Action to take when receiving an alert."
msgstr ""
+msgid "Actions"
+msgstr ""
+
msgid "Activate"
msgstr ""
@@ -1316,6 +1387,9 @@ msgstr ""
msgid "Add LICENSE"
msgstr ""
+msgid "Add New Node"
+msgstr ""
+
msgid "Add README"
msgstr ""
@@ -1391,6 +1465,9 @@ msgstr ""
msgid "Add approval rule"
msgstr ""
+msgid "Add approvers"
+msgstr ""
+
msgid "Add bold text"
msgstr ""
@@ -1448,6 +1525,9 @@ msgstr ""
msgid "Add strikethrough text"
msgstr ""
+msgid "Add suggestion to batch"
+msgstr ""
+
msgid "Add system hook"
msgstr ""
@@ -1547,6 +1627,9 @@ msgstr ""
msgid "Admin Note"
msgstr ""
+msgid "Admin Notifications"
+msgstr ""
+
msgid "Admin Overview"
msgstr ""
@@ -1658,6 +1741,9 @@ msgstr ""
msgid "AdminSettings|Integrations configured here will automatically apply to all projects on this instance."
msgstr ""
+msgid "AdminSettings|Moved to integrations"
+msgstr ""
+
msgid "AdminSettings|No required pipeline"
msgstr ""
@@ -1889,12 +1975,18 @@ msgstr[0] ""
msgstr[1] ""
msgstr[2] ""
+msgid "AlertManagement| assign yourself"
+msgstr ""
+
msgid "AlertManagement|Acknowledged"
msgstr ""
msgid "AlertManagement|Alert"
msgstr ""
+msgid "AlertManagement|Alert assignee(s): %{assignees}"
+msgstr ""
+
msgid "AlertManagement|Alert detail"
msgstr ""
@@ -1910,9 +2002,15 @@ msgstr ""
msgid "AlertManagement|All alerts"
msgstr ""
+msgid "AlertManagement|Assign To"
+msgstr ""
+
msgid "AlertManagement|Assign status"
msgstr ""
+msgid "AlertManagement|Assignee"
+msgstr ""
+
msgid "AlertManagement|Assignees"
msgstr ""
@@ -1931,9 +2029,6 @@ msgstr ""
msgid "AlertManagement|Edit"
msgstr ""
-msgid "AlertManagement|End time"
-msgstr ""
-
msgid "AlertManagement|Events"
msgstr ""
@@ -1952,10 +2047,13 @@ msgstr ""
msgid "AlertManagement|More information"
msgstr ""
+msgid "AlertManagement|No Matching Results"
+msgstr ""
+
msgid "AlertManagement|No alert data to display."
msgstr ""
-msgid "AlertManagement|No alerts available to display. If you think you're seeing this message in error, refresh the page."
+msgid "AlertManagement|No alerts available to display. See %{linkStart}enabling alert management%{linkEnd} for more information on adding alerts to the list."
msgstr ""
msgid "AlertManagement|No alerts to display."
@@ -1964,6 +2062,9 @@ msgstr ""
msgid "AlertManagement|None"
msgstr ""
+msgid "AlertManagement|None -"
+msgstr ""
+
msgid "AlertManagement|Open"
msgstr ""
@@ -2000,9 +2101,18 @@ msgstr ""
msgid "AlertManagement|There was an error displaying the alerts. Confirm your endpoint's configuration details to ensure alerts appear."
msgstr ""
+msgid "AlertManagement|There was an error while updating the assignee(s) list. Please try again."
+msgstr ""
+
+msgid "AlertManagement|There was an error while updating the assignee(s) of the alert. Please try again."
+msgstr ""
+
msgid "AlertManagement|There was an error while updating the status of the alert. Please try again."
msgstr ""
+msgid "AlertManagement|This assignee cannot be assigned to this alert."
+msgstr ""
+
msgid "AlertManagement|Tool"
msgstr ""
@@ -2150,6 +2260,9 @@ msgstr ""
msgid "Allow users to request access (if visibility is public or internal)"
msgstr ""
+msgid "Allowed Geo IP"
+msgstr ""
+
msgid "Allowed email domain restriction only permitted for top-level groups"
msgstr ""
@@ -2183,6 +2296,9 @@ msgstr ""
msgid "Amazon Web Services"
msgstr ""
+msgid "Amazon Web Services Logo"
+msgstr ""
+
msgid "Amazon authentication is not %{link_start}correctly configured%{link_end}. Ask your GitLab administrator if you want to use this service."
msgstr ""
@@ -2240,10 +2356,16 @@ msgstr ""
msgid "An error occurred when updating the issue weight"
msgstr ""
+msgid "An error occurred while acknowledging the notification. Refresh the page and try again."
+msgstr ""
+
+msgid "An error occurred while adding approvers"
+msgstr ""
+
msgid "An error occurred while adding formatted title for epic"
msgstr ""
-msgid "An error occurred while checking group path"
+msgid "An error occurred while checking group path. Please refresh and try again."
msgstr ""
msgid "An error occurred while committing your changes."
@@ -2387,7 +2509,7 @@ msgstr ""
msgid "An error occurred while loading milestones"
msgstr ""
-msgid "An error occurred while loading terraform report"
+msgid "An error occurred while loading project creation UI"
msgstr ""
msgid "An error occurred while loading the data. Please try again."
@@ -2441,6 +2563,9 @@ msgstr ""
msgid "An error occurred while rendering preview broadcast message"
msgstr ""
+msgid "An error occurred while rendering the editor"
+msgstr ""
+
msgid "An error occurred while reordering issues."
msgstr ""
@@ -2537,7 +2662,7 @@ msgstr ""
msgid "Analyze your dependencies for known vulnerabilities."
msgstr ""
-msgid "Analyze your source code and git history for secrets"
+msgid "Analyze your source code and git history for secrets."
msgstr ""
msgid "Analyze your source code for known vulnerabilities."
@@ -2651,12 +2776,18 @@ msgstr ""
msgid "Apply suggestion"
msgstr ""
+msgid "Apply suggestions"
+msgstr ""
+
msgid "Apply template"
msgstr ""
msgid "Apply this approval rule to any branch or a specific protected branch."
msgstr ""
+msgid "Applying"
+msgstr ""
+
msgid "Applying a template will replace the existing issue description. Any changes you have made will be lost."
msgstr ""
@@ -2669,12 +2800,18 @@ msgstr ""
msgid "Applying multiple commands"
msgstr ""
-msgid "Applying suggestion"
+msgid "Applying suggestion..."
+msgstr ""
+
+msgid "Applying suggestions..."
msgstr ""
msgid "Approval rules"
msgstr ""
+msgid "Approval rules reset to project defaults"
+msgstr ""
+
msgid "ApprovalRuleRemove|%d member"
msgid_plural "ApprovalRuleRemove|%d members"
msgstr[0] ""
@@ -2732,6 +2869,9 @@ msgstr ""
msgid "Approved"
msgstr ""
+msgid "Approved MRs"
+msgstr ""
+
msgid "Approved by: "
msgstr ""
@@ -2945,6 +3085,9 @@ msgstr ""
msgid "Assign"
msgstr ""
+msgid "Assign Iteration"
+msgstr ""
+
msgid "Assign custom color like #FF0000"
msgstr ""
@@ -2963,6 +3106,9 @@ msgstr ""
msgid "Assign to"
msgstr ""
+msgid "Assign to commenting user"
+msgstr ""
+
msgid "Assign yourself to these issues"
msgstr ""
@@ -2990,6 +3136,9 @@ msgstr[0] ""
msgstr[1] ""
msgstr[2] ""
+msgid "Assignee has no permissions"
+msgstr ""
+
msgid "Assignee lists not available with your current license"
msgstr ""
@@ -2999,6 +3148,9 @@ msgstr ""
msgid "Assignee(s)"
msgstr ""
+msgid "Assignees"
+msgstr ""
+
msgid "Assigns %{assignee_users_sentence}."
msgstr ""
@@ -3035,6 +3187,9 @@ msgstr ""
msgid "Audit Events is a way to keep track of important events that happened in GitLab."
msgstr ""
+msgid "Audit Log"
+msgstr ""
+
msgid "AuditEvents|(removed)"
msgstr ""
@@ -3071,6 +3226,9 @@ msgstr ""
msgid "AuditLogs|IP Address"
msgstr ""
+msgid "AuditLogs|Member Events"
+msgstr ""
+
msgid "AuditLogs|No matching %{type} found."
msgstr ""
@@ -3353,9 +3511,6 @@ msgstr ""
msgid "Badges|e.g. %{exampleUrl}"
msgstr ""
-msgid "Badge|New"
-msgstr ""
-
msgid "Balsamiq file could not be loaded."
msgstr ""
@@ -3404,6 +3559,12 @@ msgstr ""
msgid "Below you will find all the groups that are public."
msgstr ""
+msgid "Beta"
+msgstr ""
+
+msgid "Bi-weekly code coverage"
+msgstr ""
+
msgid "Billing"
msgstr ""
@@ -3464,9 +3625,15 @@ msgstr ""
msgid "Bitbucket Server Import"
msgstr ""
+msgid "Bitbucket Server import"
+msgstr ""
+
msgid "Bitbucket import"
msgstr ""
+msgid "Blame"
+msgstr ""
+
msgid "Blocked"
msgstr ""
@@ -3479,9 +3646,6 @@ msgstr ""
msgid "Blog"
msgstr ""
-msgid "Blue helpers indicate an action to be taken."
-msgstr ""
-
msgid "Board name"
msgstr ""
@@ -3680,6 +3844,9 @@ msgstr ""
msgid "Broadcast Message was successfully updated."
msgstr ""
+msgid "Broadcast Messages"
+msgstr ""
+
msgid "Browse Directory"
msgstr ""
@@ -3734,6 +3901,9 @@ msgstr ""
msgid "By %{user_name}"
msgstr ""
+msgid "By URL"
+msgstr ""
+
msgid "By default GitLab sends emails in HTML and plain text formats so mail clients can choose what format to use. Disable this option if you only want to send emails in plain text format."
msgstr ""
@@ -3839,6 +4009,9 @@ msgstr ""
msgid "Can override approvers and approvals required per merge request"
msgstr ""
+msgid "Can't apply as this line has changed or the suggestion already matches its content."
+msgstr ""
+
msgid "Can't create snippet: %{err}"
msgstr ""
@@ -4025,9 +4198,6 @@ msgstr ""
msgid "Changes are still tracked. Useful for cluster/index migrations."
msgstr ""
-msgid "Changes are unknown"
-msgstr ""
-
msgid "Changes suppressed. Click to show."
msgstr ""
@@ -4334,15 +4504,9 @@ msgstr ""
msgid "Choose what content you want to see on a group’s overview page"
msgstr ""
-msgid "Choose which groups you wish to synchronize to this secondary node"
-msgstr ""
-
msgid "Choose which repositories you want to connect and run CI/CD pipelines."
msgstr ""
-msgid "Choose which shards you wish to synchronize to this secondary node"
-msgstr ""
-
msgid "Choose your framework"
msgstr ""
@@ -4640,9 +4804,15 @@ msgstr ""
msgid "Cluster does not exist"
msgstr ""
+msgid "Cluster is required for Stages::ClusterEndpointInserter"
+msgstr ""
+
msgid "Cluster level"
msgstr ""
+msgid "Cluster type must be specificed for Stages::ClusterEndpointInserter"
+msgstr ""
+
msgid "ClusterIntegration| %{custom_domain_start}More information%{custom_domain_end}."
msgstr ""
@@ -4754,7 +4924,7 @@ msgstr ""
msgid "ClusterIntegration|Cert-Manager"
msgstr ""
-msgid "ClusterIntegration|Cert-Manager is a native Kubernetes certificate management controller that helps with issuing certificates. Installing Cert-Manager on your cluster will issue a certificate by %{letsEncrypt} and ensure that certificates are valid and up-to-date."
+msgid "ClusterIntegration|Cert-Manager is a native Kubernetes certificate management controller that helps with issuing certificates. Installing Cert-Manager on your cluster will issue a certificate by %{linkStart}Let's Encrypt%{linkEnd} and ensure that certificates are valid and up-to-date."
msgstr ""
msgid "ClusterIntegration|Certificate Authority bundle (PEM format)"
@@ -4781,9 +4951,6 @@ msgstr ""
msgid "ClusterIntegration|Clear the local cache of namespace and service accounts. This is necessary if your integration has become out of sync. The cache is repopulated during the next CI job that requires namespace and service accounts."
msgstr ""
-msgid "ClusterIntegration|Cloud Run"
-msgstr ""
-
msgid "ClusterIntegration|Cluster being created"
msgstr ""
@@ -4868,7 +5035,7 @@ msgstr ""
msgid "ClusterIntegration|Crossplane"
msgstr ""
-msgid "ClusterIntegration|Crossplane enables declarative provisioning of managed services from your cloud of choice using %{codeStart}kubectl%{codeEnd} or %{gitlabIntegrationLink}. Crossplane runs inside your Kubernetes cluster and supports secure connectivity and secrets management between app containers and the cloud services they depend on."
+msgid "ClusterIntegration|Crossplane enables declarative provisioning of managed services from your cloud of choice using %{codeStart}kubectl%{codeEnd} or %{linkStart}GitLab Integration%{linkEnd}. Crossplane runs inside your Kubernetes cluster and supports secure connectivity and secrets management between app containers and the cloud services they depend on."
msgstr ""
msgid "ClusterIntegration|Deletes all GitLab resources attached to this cluster during removal"
@@ -4952,9 +5119,6 @@ msgstr ""
msgid "ClusterIntegration|GitLab-managed cluster"
msgstr ""
-msgid "ClusterIntegration|Gitlab Integration"
-msgstr ""
-
msgid "ClusterIntegration|Global default"
msgstr ""
@@ -5000,7 +5164,7 @@ msgstr ""
msgid "ClusterIntegration|Ingress gives you a way to route requests to services based on the request host or path, centralizing a number of services into a single entrypoint."
msgstr ""
-msgid "ClusterIntegration|Installing Ingress may incur additional costs. Learn more about %{pricingLink}."
+msgid "ClusterIntegration|Installing Ingress may incur additional costs. Learn more about %{linkStart}pricing%{linkEnd}."
msgstr ""
msgid "ClusterIntegration|Instance cluster"
@@ -5015,7 +5179,7 @@ msgstr ""
msgid "ClusterIntegration|Issuer Email"
msgstr ""
-msgid "ClusterIntegration|Issuers represent a certificate authority. You must provide an email address for your Issuer. "
+msgid "ClusterIntegration|Issuers represent a certificate authority. You must provide an email address for your Issuer."
msgstr ""
msgid "ClusterIntegration|Jupyter Hostname"
@@ -5087,9 +5251,6 @@ msgstr ""
msgid "ClusterIntegration|Learn more about instance Kubernetes clusters"
msgstr ""
-msgid "ClusterIntegration|Let's Encrypt"
-msgstr ""
-
msgid "ClusterIntegration|Loading IAM Roles"
msgstr ""
@@ -5201,7 +5362,7 @@ msgstr ""
msgid "ClusterIntegration|Prometheus"
msgstr ""
-msgid "ClusterIntegration|Prometheus is an open-source monitoring system with %{gitlabIntegrationLink} to monitor deployed applications."
+msgid "ClusterIntegration|Prometheus is an open-source monitoring system with %{linkStart}GitLab Integration%{linkEnd} to monitor deployed applications."
msgstr ""
msgid "ClusterIntegration|Provider details"
@@ -5357,10 +5518,10 @@ msgstr ""
msgid "ClusterIntegration|Select zone to choose machine type"
msgstr ""
-msgid "ClusterIntegration|Send Cilium Logs"
+msgid "ClusterIntegration|Send Container Network Policies Logs"
msgstr ""
-msgid "ClusterIntegration|Send ModSecurity Logs"
+msgid "ClusterIntegration|Send Web Application Firewall Logs"
msgstr ""
msgid "ClusterIntegration|Service Token"
@@ -5528,15 +5689,12 @@ msgstr ""
msgid "ClusterIntegration|documentation"
msgstr ""
-msgid "ClusterIntegration|installed via %{installed_via}"
+msgid "ClusterIntegration|installed via %{linkStart}Cloud Run%{linkEnd}"
msgstr ""
msgid "ClusterIntegration|meets the requirements"
msgstr ""
-msgid "ClusterIntegration|pricing"
-msgstr ""
-
msgid "ClusterIntegration|sign up"
msgstr ""
@@ -5573,6 +5731,15 @@ msgstr ""
msgid "Code"
msgstr ""
+msgid "Code Coverage: %{coveragePercentage}%{percentSymbol}"
+msgstr ""
+
+msgid "Code Coverage| Empty code coverage data"
+msgstr ""
+
+msgid "Code Coverage|Couldn't fetch the code coverage data"
+msgstr ""
+
msgid "Code Owners"
msgstr ""
@@ -5627,7 +5794,7 @@ msgstr ""
msgid "Collapse approvers"
msgstr ""
-msgid "Collapse child epics"
+msgid "Collapse milestones"
msgstr ""
msgid "Collapse replies"
@@ -5645,6 +5812,9 @@ msgstr ""
msgid "Coming soon"
msgstr ""
+msgid "Comma-separated, e.g. '1.1.1.1, 2.2.2.0/24'"
+msgstr ""
+
msgid "Command"
msgstr ""
@@ -5816,6 +5986,12 @@ msgstr ""
msgid "Compliance framework (optional)"
msgstr ""
+msgid "Compliance frameworks"
+msgstr ""
+
+msgid "ComplianceDashboard|created by:"
+msgstr ""
+
msgid "ComplianceFramework|GDPR"
msgstr ""
@@ -5954,6 +6130,9 @@ msgstr ""
msgid "Connection timed out"
msgstr ""
+msgid "Connection timeout"
+msgstr ""
+
msgid "Contact sales to upgrade"
msgstr ""
@@ -6011,9 +6190,6 @@ msgstr ""
msgid "ContainerRegistry|CLI Commands"
msgstr ""
-msgid "ContainerRegistry|Compressed Size"
-msgstr ""
-
msgid "ContainerRegistry|Container Registry"
msgstr ""
@@ -6029,6 +6205,9 @@ msgstr ""
msgid "ContainerRegistry|Copy push command"
msgstr ""
+msgid "ContainerRegistry|Delete selected"
+msgstr ""
+
msgid "ContainerRegistry|Docker connection error"
msgstr ""
@@ -6062,16 +6241,16 @@ msgstr ""
msgid "ContainerRegistry|If you are not already logged in, you need to authenticate to the Container Registry by using your GitLab username and password. If you have %{twofaDocLinkStart}Two-Factor Authentication%{twofaDocLinkEnd} enabled, use a %{personalAccessTokensDocLinkStart}Personal Access Token%{personalAccessTokensDocLinkEnd} instead of a password."
msgstr ""
-msgid "ContainerRegistry|Image ID"
+msgid "ContainerRegistry|Image ID: %{imageId}"
msgstr ""
msgid "ContainerRegistry|Image Repositories"
msgstr ""
-msgid "ContainerRegistry|Keep and protect the images that matter most."
+msgid "ContainerRegistry|Image tags"
msgstr ""
-msgid "ContainerRegistry|Last Updated"
+msgid "ContainerRegistry|Keep and protect the images that matter most."
msgstr ""
msgid "ContainerRegistry|Login"
@@ -6086,6 +6265,9 @@ msgstr ""
msgid "ContainerRegistry|Please contact your administrator."
msgstr ""
+msgid "ContainerRegistry|Published %{timeInfo}"
+msgstr ""
+
msgid "ContainerRegistry|Push an image"
msgstr ""
@@ -6101,9 +6283,6 @@ msgstr ""
msgid "ContainerRegistry|Remove repository"
msgstr ""
-msgid "ContainerRegistry|Remove selected tags"
-msgstr ""
-
msgid "ContainerRegistry|Remove tag"
msgid_plural "ContainerRegistry|Remove tags"
msgstr[0] ""
@@ -6134,9 +6313,6 @@ msgstr ""
msgid "ContainerRegistry|Sorry, your filter produced no results."
msgstr ""
-msgid "ContainerRegistry|Tag"
-msgstr ""
-
msgid "ContainerRegistry|Tag expiration policy"
msgstr ""
@@ -6281,21 +6457,6 @@ msgstr ""
msgid "Control the display of third party offers."
msgstr ""
-msgid "Control the maximum concurrency of LFS/attachment backfill for this secondary node"
-msgstr ""
-
-msgid "Control the maximum concurrency of container repository operations for this Geo node"
-msgstr ""
-
-msgid "Control the maximum concurrency of repository backfill for this secondary node"
-msgstr ""
-
-msgid "Control the maximum concurrency of verification operations for this Geo node"
-msgstr ""
-
-msgid "Control the minimum interval in days that a repository should be reverified for this primary node"
-msgstr ""
-
msgid "Cookie domain"
msgstr ""
@@ -6437,6 +6598,9 @@ msgstr ""
msgid "Could not find design."
msgstr ""
+msgid "Could not find iteration"
+msgstr ""
+
msgid "Could not remove the trigger."
msgstr ""
@@ -6473,6 +6637,12 @@ msgstr ""
msgid "Coverage"
msgstr ""
+msgid "Coverage Fuzzing"
+msgstr ""
+
+msgid "Crash State"
+msgstr ""
+
msgid "Create"
msgstr ""
@@ -6521,6 +6691,9 @@ msgstr ""
msgid "Create a personal access token on your account to pull or push via %{protocol}."
msgstr ""
+msgid "Create a project pre-populated with the necessary files to get you started quickly."
+msgstr ""
+
msgid "Create an account using:"
msgstr ""
@@ -6689,6 +6862,9 @@ msgstr ""
msgid "Creates branch '%{branch_name}' and a merge request to resolve this issue."
msgstr ""
+msgid "Creating"
+msgstr ""
+
msgid "Creating epic"
msgstr ""
@@ -6740,7 +6916,10 @@ msgstr ""
msgid "Current vulnerabilities count"
msgstr ""
-msgid "CurrentUser|Buy CI minutes"
+msgid "CurrentUser|Buy Pipeline minutes"
+msgstr ""
+
+msgid "CurrentUser|One of your groups is running out"
msgstr ""
msgid "CurrentUser|Profile"
@@ -6755,6 +6934,9 @@ msgstr ""
msgid "CurrentUser|Upgrade"
msgstr ""
+msgid "Custom Attributes"
+msgstr ""
+
msgid "Custom CI configuration path"
msgstr ""
@@ -7019,12 +7201,18 @@ msgstr ""
msgid "DAG"
msgstr ""
+msgid "DAG visualization requires at least 3 dependent jobs."
+msgstr ""
+
msgid "DNS"
msgstr ""
msgid "Dashboard"
msgstr ""
+msgid "Dashboard uid not found"
+msgstr ""
+
msgid "DashboardProjects|All"
msgstr ""
@@ -7049,6 +7237,9 @@ msgstr ""
msgid "Data is still calculating..."
msgstr ""
+msgid "Datasource name not found"
+msgstr ""
+
msgid "Date"
msgstr ""
@@ -7229,6 +7420,9 @@ msgstr ""
msgid "Delete this attachment"
msgstr ""
+msgid "Delete user list"
+msgstr ""
+
msgid "Delete variable"
msgstr ""
@@ -7550,6 +7744,9 @@ msgstr ""
msgid "Deploying to"
msgstr ""
+msgid "Deploying to AWS is easy with GitLab"
+msgstr ""
+
msgid "Deployment Frequency"
msgstr ""
@@ -7913,6 +8110,9 @@ msgstr ""
msgid "Dismissed on pipeline %{pipelineLink} at %{projectLink}"
msgstr ""
+msgid "Display alerts from all your monitoring tools directly within GitLab."
+msgstr ""
+
msgid "Display name"
msgstr ""
@@ -7970,9 +8170,6 @@ msgstr ""
msgid "Don't show again"
msgstr ""
-msgid "Don't worry, you can access this tour by clicking on the help icon in the top right corner and choose <strong>Learn GitLab</strong>."
-msgstr ""
-
msgid "Done"
msgstr ""
@@ -8099,6 +8296,9 @@ msgstr ""
msgid "Edit Release"
msgstr ""
+msgid "Edit Slack integration"
+msgstr ""
+
msgid "Edit Snippet"
msgstr ""
@@ -8138,6 +8338,9 @@ msgstr ""
msgid "Edit issues"
msgstr ""
+msgid "Edit iteration"
+msgstr ""
+
msgid "Edit public deploy key"
msgstr ""
@@ -8186,6 +8389,9 @@ msgstr ""
msgid "Email"
msgstr ""
+msgid "Email Notification"
+msgstr ""
+
msgid "Email address"
msgstr ""
@@ -8198,6 +8404,9 @@ msgstr ""
msgid "Email not verified. Please verify your email in Salesforce."
msgstr ""
+msgid "Email notification for unknown sign-ins"
+msgstr ""
+
msgid "Email patch"
msgstr ""
@@ -8429,6 +8638,9 @@ msgstr ""
msgid "Enforce DNS rebinding attack protection"
msgstr ""
+msgid "Enforce personal access token expiration"
+msgstr ""
+
msgid "Ensure connectivity is available from the GitLab server to the Prometheus server"
msgstr ""
@@ -8498,6 +8710,9 @@ msgstr ""
msgid "Enter the name of your application, and we'll return a unique %{type}."
msgstr ""
+msgid "Enter weights for storages for new repositories."
+msgstr ""
+
msgid "Enter your password to approve"
msgstr ""
@@ -8507,15 +8722,27 @@ msgstr ""
msgid "Environment does not have deployments"
msgstr ""
+msgid "Environment is required for Stages::MetricEndpointInserter"
+msgstr ""
+
+msgid "Environment is required for Stages::VariableEndpointInserter"
+msgstr ""
+
msgid "Environment scope"
msgstr ""
+msgid "Environment variables are applied to all project environments in this instance via the Runner. You can use environment variables for passwords, secret keys, etc. Make variables available to the running application by prepending the variable key with <code>K8S_SECRET_</code>. You can set variables to be:"
+msgstr ""
+
msgid "Environment variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. Additionally, they can be masked so they are hidden in job logs, though they must match certain regexp requirements to do so. You can use environment variables for passwords, secret keys, or whatever you want."
msgstr ""
msgid "Environment variables are configured by your administrator to be %{link_start}protected%{link_end} by default"
msgstr ""
+msgid "Environment variables on this GitLab instance are configured to be %{link_start}protected%{link_end} by default"
+msgstr ""
+
msgid "Environment:"
msgstr ""
@@ -9047,6 +9274,9 @@ msgstr ""
msgid "EventFilterBy|Filter by comments"
msgstr ""
+msgid "EventFilterBy|Filter by designs"
+msgstr ""
+
msgid "EventFilterBy|Filter by epic events"
msgstr ""
@@ -9179,15 +9409,15 @@ msgstr ""
msgid "Expand approvers"
msgstr ""
-msgid "Expand child epics"
-msgstr ""
-
msgid "Expand down"
msgstr ""
msgid "Expand dropdown"
msgstr ""
+msgid "Expand milestones"
+msgstr ""
+
msgid "Expand sidebar"
msgstr ""
@@ -9203,6 +9433,9 @@ msgstr ""
msgid "Expiration date"
msgstr ""
+msgid "Expiration not enforced"
+msgstr ""
+
msgid "Expiration policy for the Container Registry is a perfect solution for keeping the Registry space down while still enjoying the full power of GitLab CI/CD."
msgstr ""
@@ -9323,6 +9556,9 @@ msgstr ""
msgid "Failed Jobs"
msgstr ""
+msgid "Failed on"
+msgstr ""
+
msgid "Failed to add a Zoom meeting"
msgstr ""
@@ -9383,13 +9619,19 @@ msgstr ""
msgid "Failed to install."
msgstr ""
+msgid "Failed to load assignees. Please try again."
+msgstr ""
+
+msgid "Failed to load authors. Please try again."
+msgstr ""
+
msgid "Failed to load emoji list."
msgstr ""
msgid "Failed to load error details from Sentry."
msgstr ""
-msgid "Failed to load errors from Sentry. Error message: %{errorMessage}"
+msgid "Failed to load errors from Sentry."
msgstr ""
msgid "Failed to load group activity metrics. Please try again."
@@ -9470,6 +9712,9 @@ msgstr ""
msgid "Failed to set due date because the date format is invalid."
msgstr ""
+msgid "Failed to set iteration on this issue. Please try again."
+msgstr ""
+
msgid "Failed to signing using smartcard authentication"
msgstr ""
@@ -9692,13 +9937,10 @@ msgstr ""
msgid "FeatureFlags|Target environments"
msgstr ""
-msgid "FeatureFlags|There are no active feature flags"
+msgid "FeatureFlags|There was an error fetching the feature flags."
msgstr ""
-msgid "FeatureFlags|There are no inactive feature flags"
-msgstr ""
-
-msgid "FeatureFlags|There was an error fetching the feature flags."
+msgid "FeatureFlags|There was an error retrieving user lists"
msgstr ""
msgid "FeatureFlags|Try again in a few moments or contact your support team."
@@ -9710,9 +9952,18 @@ msgstr ""
msgid "FeatureFlag|Delete strategy"
msgstr ""
+msgid "FeatureFlag|List"
+msgstr ""
+
msgid "FeatureFlag|Percentage"
msgstr ""
+msgid "FeatureFlag|Select a user list"
+msgstr ""
+
+msgid "FeatureFlag|There are no configured user lists"
+msgstr ""
+
msgid "FeatureFlag|Type"
msgstr ""
@@ -9869,6 +10120,12 @@ msgstr ""
msgid "Filter..."
msgstr ""
+msgid "Find File"
+msgstr ""
+
+msgid "Find bugs in your code with coverage-guided fuzzing"
+msgstr ""
+
msgid "Find by path"
msgstr ""
@@ -10133,6 +10390,9 @@ msgstr ""
msgid "Geo allows you to replicate your GitLab instance to other geographical locations."
msgstr ""
+msgid "Geo nodes are paused using a command run on the node"
+msgstr ""
+
msgid "GeoNodeStatusEvent|%{timeAgoStr} (%{pendingEvents} events)"
msgstr ""
@@ -10217,6 +10477,9 @@ msgstr ""
msgid "GeoNodes|Not checksummed"
msgstr ""
+msgid "GeoNodes|Package files"
+msgstr ""
+
msgid "GeoNodes|Pausing replication stops the sync process. Are you sure?"
msgstr ""
@@ -10235,6 +10498,9 @@ msgstr ""
msgid "GeoNodes|Replication slots"
msgstr ""
+msgid "GeoNodes|Replication status"
+msgstr ""
+
msgid "GeoNodes|Repositories"
msgstr ""
@@ -10445,6 +10711,9 @@ msgstr ""
msgid "Geo|Synchronization failed - %{error}"
msgstr ""
+msgid "Geo|Synchronization of %{itemTitle} is disabled."
+msgstr ""
+
msgid "Geo|The database is currently %{db_lag} behind the primary node."
msgstr ""
@@ -10757,6 +11026,9 @@ msgstr ""
msgid "Go to %{link_to_google_takeout}."
msgstr ""
+msgid "Go to <strong>Issues</strong> > <strong>Boards</strong> to access your personalized learning issue board."
+msgstr ""
+
msgid "Go to Pipelines"
msgstr ""
@@ -10856,9 +11128,6 @@ msgstr ""
msgid "Go to your snippets"
msgstr ""
-msgid "Golden Tanuki"
-msgstr ""
-
msgid "Google Cloud Platform"
msgstr ""
@@ -10880,6 +11149,9 @@ msgstr ""
msgid "Grafana URL"
msgstr ""
+msgid "Grafana response contains invalid json"
+msgstr ""
+
msgid "GrafanaIntegration|API Token"
msgstr ""
@@ -10928,9 +11200,6 @@ msgstr ""
msgid "Group %{group_name} was successfully created."
msgstr ""
-msgid "Group '%{group_name}' is being imported."
-msgstr ""
-
msgid "Group Audit Events"
msgstr ""
@@ -10967,9 +11236,6 @@ msgstr ""
msgid "Group by:"
msgstr ""
-msgid "Group could not be imported: %{errors}"
-msgstr ""
-
msgid "Group description"
msgstr ""
@@ -10997,9 +11263,15 @@ msgstr ""
msgid "Group has not been marked for deletion"
msgstr ""
+msgid "Group import could not be scheduled"
+msgstr ""
+
msgid "Group info:"
msgstr ""
+msgid "Group is required when cluster_type is :group"
+msgstr ""
+
msgid "Group maintainers can register group runners in the %{link}"
msgstr ""
@@ -11063,6 +11335,24 @@ msgstr ""
msgid "GroupActivyMetrics|Recent activity (last 90 days)"
msgstr ""
+msgid "GroupImport|Failed to import group."
+msgstr ""
+
+msgid "GroupImport|Group '%{group_name}' is being imported."
+msgstr ""
+
+msgid "GroupImport|Group could not be imported: %{errors}"
+msgstr ""
+
+msgid "GroupImport|Please wait while we import the group for you. Refresh at will."
+msgstr ""
+
+msgid "GroupImport|The group was successfully imported."
+msgstr ""
+
+msgid "GroupImport|Unable to process group import file"
+msgstr ""
+
msgid "GroupRoadmap|%{dateWord} – No end date"
msgstr ""
@@ -11330,6 +11620,9 @@ msgstr ""
msgid "Groups (%{groups})"
msgstr ""
+msgid "Groups and projects"
+msgstr ""
+
msgid "Groups can also be nested by creating %{subgroup_docs_link_start}subgroups%{subgroup_docs_link_end}."
msgstr ""
@@ -11375,6 +11668,33 @@ msgstr ""
msgid "GroupsEmptyState|You can manage your group member’s permissions and access to each project in the group."
msgstr ""
+msgid "GroupsNew|Contact an administrator to enable options for importing your group."
+msgstr ""
+
+msgid "GroupsNew|Create"
+msgstr ""
+
+msgid "GroupsNew|Create group"
+msgstr ""
+
+msgid "GroupsNew|GitLab group export"
+msgstr ""
+
+msgid "GroupsNew|Import"
+msgstr ""
+
+msgid "GroupsNew|Import group"
+msgstr ""
+
+msgid "GroupsNew|My Awesome Group"
+msgstr ""
+
+msgid "GroupsNew|No import options available"
+msgstr ""
+
+msgid "GroupsNew|To copy a GitLab group between installations, navigate to the group settings page for the original installation, generate an export file, and upload it here."
+msgstr ""
+
msgid "GroupsTree|Are you sure you want to leave the \"%{fullName}\" group?"
msgstr ""
@@ -11477,6 +11797,9 @@ msgstr ""
msgid "Helps reduce request volume for protected paths"
msgstr ""
+msgid "Here are all your projects in your group, including the one you just created. To start, let’s take a look at your personalized learning project which will help you learn about GitLab at your own pace."
+msgstr ""
+
msgid "Here you will find recent merge request activity"
msgstr ""
@@ -11492,6 +11815,9 @@ msgstr[0] ""
msgstr[1] ""
msgstr[2] ""
+msgid "Hide details"
+msgstr ""
+
msgid "Hide file browser"
msgstr ""
@@ -11501,6 +11827,9 @@ msgstr ""
msgid "Hide host keys manual input"
msgstr ""
+msgid "Hide list"
+msgstr ""
+
msgid "Hide marketing-related entries from help"
msgstr ""
@@ -11522,9 +11851,6 @@ msgstr[2] ""
msgid "Hide values"
msgstr ""
-msgid "Hiding all labels"
-msgstr ""
-
msgid "High or unknown vulnerabilities present"
msgstr ""
@@ -11609,6 +11935,9 @@ msgstr ""
msgid "ID:"
msgstr ""
+msgid "IDE"
+msgstr ""
+
msgid "IDE|Allow live previews of JavaScript projects in the Web IDE using CodeSandbox Live Preview."
msgstr ""
@@ -11699,10 +12028,10 @@ msgstr ""
msgid "If enabled"
msgstr ""
-msgid "If enabled, access to projects will be validated on an external service using their classification label."
+msgid "If enabled, GitLab will handle Object Storage replication using Geo. %{linkStart}More information%{linkEnd}"
msgstr ""
-msgid "If enabled, and if object storage is enabled, GitLab will handle Object Storage replication using Geo"
+msgid "If enabled, access to projects will be validated on an external service using their classification label."
msgstr ""
msgid "If there is no previous license or if the previous license has expired, some GitLab functionality will be blocked until a new, valid license is uploaded."
@@ -12065,6 +12394,9 @@ msgstr[0] ""
msgstr[1] ""
msgstr[2] ""
+msgid "Instance Configuration"
+msgstr ""
+
msgid "Instance Statistics visibility"
msgstr ""
@@ -12086,9 +12418,6 @@ msgstr ""
msgid "Integrations"
msgstr ""
-msgid "Integrations allow you to integrate GitLab with other applications"
-msgstr ""
-
msgid "Integrations|All details"
msgstr ""
@@ -12200,6 +12529,9 @@ msgstr ""
msgid "Invalid query"
msgstr ""
+msgid "Invalid repository bundle for snippet with id %{snippet_id}"
+msgstr ""
+
msgid "Invalid repository path"
msgstr ""
@@ -12218,6 +12550,9 @@ msgstr ""
msgid "Invalid two-factor code."
msgstr ""
+msgid "Invalid yaml"
+msgstr ""
+
msgid "Invitation"
msgstr ""
@@ -12437,15 +12772,24 @@ msgstr ""
msgid "It seems like the Dependency Scanning job ran successfully, but no dependencies have been detected in your project."
msgstr ""
+msgid "It seems that there is currently no available data for code coverage"
+msgstr ""
+
msgid "It's you"
msgstr ""
+msgid "Iteration"
+msgstr ""
+
msgid "Iteration changed to"
msgstr ""
msgid "Iteration removed"
msgstr ""
+msgid "Iteration updated"
+msgstr ""
+
msgid "Iterations"
msgstr ""
@@ -12476,12 +12820,18 @@ msgstr ""
msgid "January"
msgstr ""
+msgid "Jira Issues"
+msgstr ""
+
msgid "Jira import is already running."
msgstr ""
msgid "Jira integration not configured."
msgstr ""
+msgid "Jira project key is not configured"
+msgstr ""
+
msgid "Jira project: %{importProject}"
msgstr ""
@@ -12662,6 +13012,9 @@ msgstr ""
msgid "Keep divergent refs"
msgstr ""
+msgid "Kerberos access denied"
+msgstr ""
+
msgid "Key"
msgstr ""
@@ -12671,10 +13024,13 @@ msgstr ""
msgid "Key: %{key}"
msgstr ""
-msgid "Keyboard Shortcuts"
+msgid "Keyboard shortcuts"
msgstr ""
-msgid "Keyboard shortcuts"
+msgid "Keys"
+msgstr ""
+
+msgid "Ki"
msgstr ""
msgid "Kubernetes"
@@ -12716,6 +13072,9 @@ msgstr ""
msgid "LDAP"
msgstr ""
+msgid "LDAP Synchronization"
+msgstr ""
+
msgid "LDAP settings"
msgstr ""
@@ -12941,12 +13300,18 @@ msgstr ""
msgid "Learn more about custom project templates"
msgstr ""
+msgid "Learn more about deploying to AWS"
+msgstr ""
+
msgid "Learn more about deploying to a cluster"
msgstr ""
msgid "Learn more about group-level project templates"
msgstr ""
+msgid "Learn more about job dependencies"
+msgstr ""
+
msgid "Learn more about signing commits"
msgstr ""
@@ -12998,9 +13363,21 @@ msgstr ""
msgid "License History"
msgstr ""
+msgid "License ID:"
+msgstr ""
+
+msgid "License URL"
+msgstr ""
+
msgid "License-Check"
msgstr ""
+msgid "LicenseCompliance|%{docLinkStart}License Approvals%{docLinkEnd} are active"
+msgstr ""
+
+msgid "LicenseCompliance|%{docLinkStart}License Approvals%{docLinkEnd} are inactive"
+msgstr ""
+
msgid "LicenseCompliance|Add a license"
msgstr ""
@@ -13022,9 +13399,15 @@ msgstr ""
msgid "LicenseCompliance|Deny"
msgstr ""
+msgid "LicenseCompliance|Learn more about %{linkStart}License Approvals%{linkEnd}"
+msgstr ""
+
msgid "LicenseCompliance|License"
msgstr ""
+msgid "LicenseCompliance|License Approvals"
+msgstr ""
+
msgid "LicenseCompliance|License Compliance detected %d license and policy violation for the source branch only; approval required"
msgid_plural "LicenseCompliance|License Compliance detected %d licenses and policy violations for the source branch only; approval required"
msgstr[0] ""
@@ -13250,6 +13633,9 @@ msgstr ""
msgid "List your Bitbucket Server repositories"
msgstr ""
+msgid "Lists"
+msgstr ""
+
msgid "Live preview"
msgstr ""
@@ -13433,6 +13819,9 @@ msgstr ""
msgid "Manage project labels"
msgstr ""
+msgid "Manage storage usage"
+msgstr ""
+
msgid "Manage two-factor authentication"
msgstr ""
@@ -13448,6 +13837,9 @@ msgstr ""
msgid "Manifest file import"
msgstr ""
+msgid "Manifest import"
+msgstr ""
+
msgid "Manual job"
msgstr ""
@@ -13595,6 +13987,12 @@ msgstr ""
msgid "Maximum field length"
msgstr ""
+msgid "Maximum file size is 2MB. Please select a smaller file."
+msgstr ""
+
+msgid "Maximum import size (MB)"
+msgstr ""
+
msgid "Maximum job timeout"
msgstr ""
@@ -13634,6 +14032,9 @@ msgstr ""
msgid "Maximum size of Elasticsearch bulk indexing requests."
msgstr ""
+msgid "Maximum size of import files."
+msgstr ""
+
msgid "Maximum size of individual attachments in comments."
msgstr ""
@@ -13685,6 +14086,9 @@ msgstr ""
msgid "Merge (when the pipeline succeeds)"
msgstr ""
+msgid "Merge Conflicts"
+msgstr ""
+
msgid "Merge Request"
msgstr ""
@@ -13811,6 +14215,9 @@ msgstr ""
msgid "MergeRequests|Squash task canceled: another squash is already in progress."
msgstr ""
+msgid "MergeRequests|This project does not allow squashing commits when merge requests are accepted."
+msgstr ""
+
msgid "MergeRequests|Thread stays resolved"
msgstr ""
@@ -13964,6 +14371,12 @@ msgstr ""
msgid "MetricsSettings|Add a button to the metrics dashboard linking directly to your existing external dashboard."
msgstr ""
+msgid "MetricsSettings|Choose whether to display dashboard metrics in UTC or the user's local timezone."
+msgstr ""
+
+msgid "MetricsSettings|Dashboard timezone"
+msgstr ""
+
msgid "MetricsSettings|External dashboard URL"
msgstr ""
@@ -13973,6 +14386,12 @@ msgstr ""
msgid "MetricsSettings|Metrics Dashboard"
msgstr ""
+msgid "MetricsSettings|UTC (Coordinated Universal Time)"
+msgstr ""
+
+msgid "MetricsSettings|User's local timezone"
+msgstr ""
+
msgid "Metrics|Add metric"
msgstr ""
@@ -13988,6 +14407,9 @@ msgstr ""
msgid "Metrics|Create metric"
msgstr ""
+msgid "Metrics|Current"
+msgstr ""
+
msgid "Metrics|Delete metric"
msgstr ""
@@ -14039,6 +14461,9 @@ msgstr ""
msgid "Metrics|Max"
msgstr ""
+msgid "Metrics|Min"
+msgstr ""
+
msgid "Metrics|Must be a valid PromQL query."
msgstr ""
@@ -14054,6 +14479,9 @@ msgstr ""
msgid "Metrics|Refresh dashboard"
msgstr ""
+msgid "Metrics|Select a value"
+msgstr ""
+
msgid "Metrics|Star dashboard"
msgstr ""
@@ -14072,12 +14500,18 @@ msgstr ""
msgid "Metrics|There was an error getting annotations information."
msgstr ""
+msgid "Metrics|There was an error getting dashboard validation warnings information."
+msgstr ""
+
msgid "Metrics|There was an error getting deployment information."
msgstr ""
msgid "Metrics|There was an error getting environments information."
msgstr ""
+msgid "Metrics|There was an error getting options for variable \"%{name}\"."
+msgstr ""
+
msgid "Metrics|There was an error trying to validate your query"
msgstr ""
@@ -14120,6 +14554,9 @@ msgstr ""
msgid "Metrics|You're about to permanently delete this metric. This cannot be undone."
msgstr ""
+msgid "Metrics|Your dashboard schema is invalid. Edit the dashboard to correct the YAML schema."
+msgstr ""
+
msgid "Metrics|e.g. HTTP requests"
msgstr ""
@@ -14135,12 +14572,18 @@ msgstr ""
msgid "Metrics|e.g. req/sec"
msgstr ""
+msgid "Mi"
+msgstr ""
+
msgid "Microsoft Azure"
msgstr ""
msgid "Middleman project with Static Site Editor support"
msgstr ""
+msgid "Migrate your data from an external source like GitHub, Bitbucket, or another instance of GitLab."
+msgstr ""
+
msgid "Migrated %{success_count}/%{total_count} files."
msgstr ""
@@ -14276,6 +14719,9 @@ msgstr ""
msgid "Minimum capacity to be available before we schedule more mirrors preemptively."
msgstr ""
+msgid "Minimum interval in days"
+msgstr ""
+
msgid "Minimum length is %{minimum_password_length} characters"
msgstr ""
@@ -14462,6 +14908,12 @@ msgstr ""
msgid "Multiple uploaders found: %{uploader_types}"
msgstr ""
+msgid "Must match with the %{codeStart}external_url%{codeEnd} in %{codeStart}/etc/gitlab/gitlab.rb%{codeEnd}."
+msgstr ""
+
+msgid "Must match with the %{codeStart}geo_node_name%{codeEnd} in %{codeStart}/etc/gitlab/gitlab.rb%{codeEnd}. %{linkStart}More information%{linkEnd}"
+msgstr ""
+
msgid "My Awesome Group"
msgstr ""
@@ -14576,10 +15028,16 @@ msgstr ""
msgid "New Application"
msgstr ""
+msgid "New Branch"
+msgstr ""
+
+msgid "New Deploy Key"
+msgstr ""
+
msgid "New Environment"
msgstr ""
-msgid "New Geo Node"
+msgid "New File"
msgstr ""
msgid "New Group"
@@ -14597,15 +15055,15 @@ msgstr[0] ""
msgstr[1] ""
msgstr[2] ""
-msgid "New Iteration"
-msgstr ""
-
msgid "New Jira import"
msgstr ""
msgid "New Label"
msgstr ""
+msgid "New Merge Request"
+msgstr ""
+
msgid "New Milestone"
msgstr ""
@@ -14624,6 +15082,9 @@ msgstr ""
msgid "New Snippet"
msgstr ""
+msgid "New User"
+msgstr ""
+
msgid "New branch"
msgstr ""
@@ -14666,6 +15127,9 @@ msgstr ""
msgid "New issue title"
msgstr ""
+msgid "New iteration"
+msgstr ""
+
msgid "New iteration created"
msgstr ""
@@ -14843,6 +15307,12 @@ msgstr ""
msgid "No grouping"
msgstr ""
+msgid "No iteration"
+msgstr ""
+
+msgid "No iterations to show"
+msgstr ""
+
msgid "No job log"
msgstr ""
@@ -14885,6 +15355,9 @@ msgstr ""
msgid "No other labels with such name or description"
msgstr ""
+msgid "No panels matching properties %{opts}"
+msgstr ""
+
msgid "No parent group"
msgstr ""
@@ -14918,6 +15391,9 @@ msgstr ""
msgid "No schedules"
msgstr ""
+msgid "No stack trace for this error"
+msgstr ""
+
msgid "No starrers matched your search"
msgstr ""
@@ -14930,7 +15406,7 @@ msgstr ""
msgid "No test coverage"
msgstr ""
-msgid "No thanks, don't show this again"
+msgid "No thanks"
msgstr ""
msgid "No vulnerabilities present"
@@ -14945,9 +15421,6 @@ msgstr ""
msgid "No, directly import the existing email addresses and usernames."
msgstr ""
-msgid "No, not interested right now"
-msgstr ""
-
msgid "No. of commits"
msgstr ""
@@ -14993,9 +15466,6 @@ msgstr ""
msgid "Not found."
msgstr ""
-msgid "Not helpful"
-msgstr ""
-
msgid "Not now"
msgstr ""
@@ -15152,6 +15622,9 @@ msgstr ""
msgid "Notifications on"
msgstr ""
+msgid "Notify users by email when sign-in location is not recognized"
+msgstr ""
+
msgid "Nov"
msgstr ""
@@ -15218,9 +15691,6 @@ msgstr ""
msgid "Oh no!"
msgstr ""
-msgid "Ok let's go"
-msgstr ""
-
msgid "Oldest first"
msgstr ""
@@ -15233,16 +15703,49 @@ msgstr ""
msgid "On track"
msgstr ""
+msgid "OnDemandScans|Attached branch"
+msgstr ""
+
+msgid "OnDemandScans|Attached branch is where the scan job runs."
+msgstr ""
+
+msgid "OnDemandScans|Could not run the scan. Please try again."
+msgstr ""
+
msgid "OnDemandScans|Create new DAST scan"
msgstr ""
+msgid "OnDemandScans|DAST will scan the target URL and any discovered sub URLs."
+msgstr ""
+
+msgid "OnDemandScans|New on-demand DAST scan"
+msgstr ""
+
msgid "OnDemandScans|On-demand Scans"
msgstr ""
+msgid "OnDemandScans|On-demand scans run outside the DevOps cycle and find vulnerabilities in your projects. %{learnMoreLinkStart}Learn more%{learnMoreLinkEnd}"
+msgstr ""
+
+msgid "OnDemandScans|Only a passive scan can be performed on demand."
+msgstr ""
+
+msgid "OnDemandScans|Passive DAST Scan"
+msgstr ""
+
+msgid "OnDemandScans|Please enter a valid URL format, ex: http://www.example.com/home"
+msgstr ""
+
+msgid "OnDemandScans|Run this scan"
+msgstr ""
+
+msgid "OnDemandScans|Scan mode"
+msgstr ""
+
msgid "OnDemandScans|Schedule or run scans immediately against target sites. Currently available on-demand scan type: DAST. %{helpLinkStart}More information%{helpLinkEnd}"
msgstr ""
-msgid "Onboarding"
+msgid "OnDemandScans|Target URL"
msgstr ""
msgid "Once imported, repositories can be mirrored over SSH. Read more %{link_start}here%{link_end}."
@@ -15269,6 +15772,9 @@ msgstr ""
msgid "One or more of you personal access tokens were revoked"
msgstr ""
+msgid "One or more of your %{provider} projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
+msgstr ""
+
msgid "One or more of your Bitbucket projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
msgstr ""
@@ -15770,6 +16276,9 @@ msgstr ""
msgid "Parent epic is not present."
msgstr ""
+msgid "Parsing error for param :embed_json. %{message}"
+msgstr ""
+
msgid "Part of merge request changes"
msgstr ""
@@ -15782,6 +16291,9 @@ msgstr ""
msgid "Passed"
msgstr ""
+msgid "Passed on"
+msgstr ""
+
msgid "Password"
msgstr ""
@@ -15866,6 +16378,9 @@ msgstr ""
msgid "Perform common operations on GitLab project"
msgstr ""
+msgid "Performance and resource management"
+msgstr ""
+
msgid "Performance optimization"
msgstr ""
@@ -16073,7 +16588,7 @@ msgstr ""
msgid "Pipelines|Something went wrong while cleaning runners cache."
msgstr ""
-msgid "Pipelines|There are currently no %{scope} pipelines."
+msgid "Pipelines|There are currently no finished pipelines."
msgstr ""
msgid "Pipelines|There are currently no pipelines."
@@ -16184,6 +16699,9 @@ msgstr ""
msgid "Pipeline|Stop pipeline #%{pipelineId}?"
msgstr ""
+msgid "Pipeline|Tag name"
+msgstr ""
+
msgid "Pipeline|Trigger author"
msgstr ""
@@ -16265,12 +16783,18 @@ msgstr ""
msgid "Please check your email (%{email}) to verify that you own this address and unlock the power of CI/CD. Didn't receive it? %{resend_link}. Wrong email address? %{update_link}."
msgstr ""
+msgid "Please choose a file"
+msgstr ""
+
msgid "Please choose a group URL with no special characters."
msgstr ""
msgid "Please complete your profile with email address"
msgstr ""
+msgid "Please convert %{linkStart}them to Git%{linkEnd}, and go through the %{linkToImportFlow} again."
+msgstr ""
+
msgid "Please convert them to %{link_to_git}, and go through the %{link_to_import_flow} again."
msgstr ""
@@ -16328,6 +16852,9 @@ msgstr ""
msgid "Please provide a valid email address."
msgstr ""
+msgid "Please provide attributes to update"
+msgstr ""
+
msgid "Please refer to <a href=\"%{docs_url}\">%{docs_url}</a>"
msgstr ""
@@ -17063,6 +17590,9 @@ msgstr ""
msgid "Project has too many %{label_for_message} to search"
msgstr ""
+msgid "Project is required when cluster_type is :project"
+msgstr ""
+
msgid "Project members"
msgstr ""
@@ -17180,7 +17710,40 @@ msgstr ""
msgid "ProjectService|Comment"
msgstr ""
-msgid "ProjectService|Comment will be posted on each event"
+msgid "ProjectService|Event will be triggered by a push to the repository"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a commit is created/updated"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a confidential issue is created/updated/closed"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a deployment finishes"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a merge request is created/updated/merged"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a new tag is pushed to the repository"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a new, unique alert is recorded"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a pipeline status changes"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a wiki page is created/updated"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when an issue is created/updated/closed"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when someone adds a comment"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when someone adds a comment on a confidential issue"
msgstr ""
msgid "ProjectService|Perform common operations on GitLab project: %{project_name}"
@@ -17231,6 +17794,9 @@ msgstr ""
msgid "ProjectSettings|Enable 'Delete source branch' option by default"
msgstr ""
+msgid "ProjectSettings|Enable merge trains and pipelines for merged results"
+msgstr ""
+
msgid "ProjectSettings|Every merge creates a merge commit"
msgstr ""
@@ -17261,7 +17827,7 @@ msgstr ""
msgid "ProjectSettings|Forks"
msgstr ""
-msgid "ProjectSettings|Git Large File Storage"
+msgid "ProjectSettings|Git Large File Storage (LFS)"
msgstr ""
msgid "ProjectSettings|Internal"
@@ -17297,9 +17863,6 @@ msgstr ""
msgid "ProjectSettings|Merge options"
msgstr ""
-msgid "ProjectSettings|Merge pipelines will try to validate the post-merge result prior to merging"
-msgstr ""
-
msgid "ProjectSettings|Merge requests"
msgstr ""
@@ -17327,6 +17890,9 @@ msgstr ""
msgid "ProjectSettings|Pipelines"
msgstr ""
+msgid "ProjectSettings|Pipelines for merge requests must be enabled in the CI/CD configuration file, or pipelines could be unresolvable or dropped"
+msgstr ""
+
msgid "ProjectSettings|Pipelines must succeed"
msgstr ""
@@ -17354,6 +17920,9 @@ msgstr ""
msgid "ProjectSettings|Show link to create/view merge request when pushing from the command line"
msgstr ""
+msgid "ProjectSettings|Skipped pipelines are considered successful"
+msgstr ""
+
msgid "ProjectSettings|Snippets"
msgstr ""
@@ -17369,6 +17938,9 @@ msgstr ""
msgid "ProjectSettings|These checks must pass before merge requests can be merged"
msgstr ""
+msgid "ProjectSettings|This introduces the risk of merging changes that will not pass the pipeline."
+msgstr ""
+
msgid "ProjectSettings|This setting is applied on the server level and can be overridden by an admin."
msgstr ""
@@ -17546,15 +18118,27 @@ msgstr ""
msgid "ProjectsNew|Blank project"
msgstr ""
+msgid "ProjectsNew|Connect your external repository to GitLab CI/CD."
+msgstr ""
+
msgid "ProjectsNew|Contact an administrator to enable options for importing your project."
msgstr ""
msgid "ProjectsNew|Create"
msgstr ""
+msgid "ProjectsNew|Create a blank project to house your files, plan your work, and collaborate on code, among other things."
+msgstr ""
+
+msgid "ProjectsNew|Create blank project"
+msgstr ""
+
msgid "ProjectsNew|Create from template"
msgstr ""
+msgid "ProjectsNew|Create new project"
+msgstr ""
+
msgid "ProjectsNew|Creating project & repository."
msgstr ""
@@ -17579,6 +18163,9 @@ msgstr ""
msgid "ProjectsNew|Project description %{tag_start}(optional)%{tag_end}"
msgstr ""
+msgid "ProjectsNew|Run CI/CD for external repository"
+msgstr ""
+
msgid "ProjectsNew|Template"
msgstr ""
@@ -17840,6 +18427,9 @@ msgstr ""
msgid "Protected Branch"
msgstr ""
+msgid "Protected Branches"
+msgstr ""
+
msgid "Protected Environment"
msgstr ""
@@ -17852,6 +18442,9 @@ msgstr ""
msgid "Protected Tag"
msgstr ""
+msgid "Protected Tags"
+msgstr ""
+
msgid "Protected branches"
msgstr ""
@@ -17951,6 +18544,9 @@ msgstr ""
msgid "Provider"
msgstr ""
+msgid "Proxy support for this API is not available currently"
+msgstr ""
+
msgid "Pseudonymizer data collection"
msgstr ""
@@ -17975,6 +18571,9 @@ msgstr ""
msgid "Publish to status page"
msgstr ""
+msgid "Published on status page"
+msgstr ""
+
msgid "Publishes this issue to the associated status page."
msgstr ""
@@ -18149,6 +18748,9 @@ msgstr ""
msgid "Recipe"
msgstr ""
+msgid "Reconfigure"
+msgstr ""
+
msgid "Recover hidden stage"
msgstr ""
@@ -18236,6 +18838,9 @@ msgstr ""
msgid "Registration|Your profile"
msgstr ""
+msgid "Regulate approvals by authors/committers, based on compliance frameworks. Can be changed only at the instance level."
+msgstr ""
+
msgid "Rejected (closed)"
msgstr ""
@@ -18287,15 +18892,24 @@ msgstr ""
msgid "ReleaseAssetLinkType|Image"
msgstr ""
+msgid "ReleaseAssetLinkType|Images"
+msgstr ""
+
msgid "ReleaseAssetLinkType|Other"
msgstr ""
msgid "ReleaseAssetLinkType|Package"
msgstr ""
+msgid "ReleaseAssetLinkType|Packages"
+msgstr ""
+
msgid "ReleaseAssetLinkType|Runbook"
msgstr ""
+msgid "ReleaseAssetLinkType|Runbooks"
+msgstr ""
+
msgid "Releases"
msgstr ""
@@ -18308,6 +18922,9 @@ msgstr ""
msgid "Releases documentation"
msgstr ""
+msgid "Releases|New Release"
+msgstr ""
+
msgid "Release|Something went wrong while getting the release details"
msgstr ""
@@ -18380,6 +18997,9 @@ msgstr ""
msgid "Remove fork relationship"
msgstr ""
+msgid "Remove from batch"
+msgstr ""
+
msgid "Remove from board"
msgstr ""
@@ -18569,6 +19189,12 @@ msgstr ""
msgid "Replication"
msgstr ""
+msgid "Replication enabled"
+msgstr ""
+
+msgid "Replication paused"
+msgstr ""
+
msgid "Reply by email"
msgstr ""
@@ -18596,15 +19222,15 @@ msgstr ""
msgid "Reported %{timeAgo} by %{reportedBy}"
msgstr ""
-msgid "Reported Resource Changes: %{addNum} to add, %{changeNum} to change, %{deleteNum} to delete"
-msgstr ""
-
msgid "Reporter"
msgstr ""
msgid "Reporting"
msgstr ""
+msgid "Reports"
+msgstr ""
+
msgid "Reports|%{combinedString} and %{resolvedString}"
msgstr ""
@@ -18644,6 +19270,9 @@ msgstr ""
msgid "Reports|Failure"
msgstr ""
+msgid "Reports|Identifier"
+msgstr ""
+
msgid "Reports|Metrics reports are loading"
msgstr ""
@@ -18656,6 +19285,9 @@ msgstr ""
msgid "Reports|Metrics reports failed loading results"
msgstr ""
+msgid "Reports|Scanner"
+msgstr ""
+
msgid "Reports|Severity"
msgstr ""
@@ -18704,15 +19336,33 @@ msgstr ""
msgid "Repository cleanup has started. You will receive an email once the cleanup operation is complete."
msgstr ""
+msgid "Repository files count over the limit"
+msgstr ""
+
+msgid "Repository has an invalid default branch name."
+msgstr ""
+
+msgid "Repository has more than one branch."
+msgstr ""
+
msgid "Repository has no locks."
msgstr ""
+msgid "Repository has tags."
+msgstr ""
+
msgid "Repository maintenance"
msgstr ""
msgid "Repository mirroring"
msgstr ""
+msgid "Repository must contain at least 1 file."
+msgstr ""
+
+msgid "Repository size is above the limit."
+msgstr ""
+
msgid "Repository static objects"
msgstr ""
@@ -18722,7 +19372,7 @@ msgstr ""
msgid "Repository sync capacity"
msgstr ""
-msgid "Repository: %{counter_repositories} / Wikis: %{counter_wikis} / Build Artifacts: %{counter_build_artifacts} / LFS: %{counter_lfs_objects}"
+msgid "Repository: %{counter_repositories} / Wikis: %{counter_wikis} / Build Artifacts: %{counter_build_artifacts} / LFS: %{counter_lfs_objects} / Snippets: %{counter_snippets}"
msgstr ""
msgid "RepositorySettingsAccessLevel|Select"
@@ -18734,6 +19384,9 @@ msgstr ""
msgid "Request Headers"
msgstr ""
+msgid "Request details"
+msgstr ""
+
msgid "Request parameter %{param} is missing."
msgstr ""
@@ -18842,6 +19495,9 @@ msgstr ""
msgid "Reset template"
msgstr ""
+msgid "Reset to project defaults"
+msgstr ""
+
msgid "Resetting the authorization key for this project will require updating the authorization key in every alert source it is enabled in."
msgstr ""
@@ -18938,9 +19594,6 @@ msgstr ""
msgid "Resume"
msgstr ""
-msgid "Resume replication"
-msgstr ""
-
msgid "Resync"
msgstr ""
@@ -19574,6 +20227,9 @@ msgstr ""
msgid "SecurityReports|False positive"
msgstr ""
+msgid "SecurityReports|Group Security Dashboard"
+msgstr ""
+
msgid "SecurityReports|Hide dismissed"
msgstr ""
@@ -19628,10 +20284,13 @@ msgstr ""
msgid "SecurityReports|Remove project from dashboard"
msgstr ""
-msgid "SecurityReports|Report type"
+msgid "SecurityReports|Return to dashboard"
msgstr ""
-msgid "SecurityReports|Return to dashboard"
+msgid "SecurityReports|Scan details"
+msgstr ""
+
+msgid "SecurityReports|Scanner type"
msgstr ""
msgid "SecurityReports|Security Dashboard"
@@ -19688,6 +20347,9 @@ msgstr ""
msgid "SecurityReports|There was an error while generating the report."
msgstr ""
+msgid "SecurityReports|Unable to add %{invalidProjectsMessage}"
+msgstr ""
+
msgid "SecurityReports|Unable to add %{invalidProjects}"
msgstr ""
@@ -19802,6 +20464,9 @@ msgstr ""
msgid "Select due date"
msgstr ""
+msgid "Select file"
+msgstr ""
+
msgid "Select group or project"
msgstr ""
@@ -19865,9 +20530,6 @@ msgstr ""
msgid "Select the branch you want to set as the default for this project. All merge requests and commits will automatically be made against this branch unless you specify a different one."
msgstr ""
-msgid "Select the configured storage available for new repositories to be placed on."
-msgstr ""
-
msgid "Select the custom project template source group."
msgstr ""
@@ -20060,12 +20722,6 @@ msgstr ""
msgid "Service Desk is enabled but not yet active"
msgstr ""
-msgid "Service Desk is off"
-msgstr ""
-
-msgid "Service Desk is on"
-msgstr ""
-
msgid "Service Templates"
msgstr ""
@@ -20153,6 +20809,15 @@ msgstr ""
msgid "Set the milestone to %{milestone_reference}."
msgstr ""
+msgid "Set the number of concurrent requests this secondary node will make to the primary node while backfilling."
+msgstr ""
+
+msgid "Set the synchronization and verification capacity for the secondary node."
+msgstr ""
+
+msgid "Set the timeout in seconds to send a secondary node status to the primary and IPs allowed for the secondary nodes."
+msgstr ""
+
msgid "Set time estimate"
msgstr ""
@@ -20192,6 +20857,9 @@ msgstr ""
msgid "Set weight to %{weight}."
msgstr ""
+msgid "Set what should be replicated by choosing specific projects or groups by the secondary node."
+msgstr ""
+
msgid "SetPasswordToCloneLink|set a password"
msgstr ""
@@ -20318,6 +20986,9 @@ msgstr ""
msgid "Show complete raw log"
msgstr ""
+msgid "Show details"
+msgstr ""
+
msgid "Show file browser"
msgstr ""
@@ -20327,10 +20998,13 @@ msgstr ""
msgid "Show latest version"
msgstr ""
+msgid "Show list"
+msgstr ""
+
msgid "Show me everything"
msgstr ""
-msgid "Show me how"
+msgid "Show me how to add a pipeline"
msgstr ""
msgid "Show me more advanced stuff"
@@ -20381,9 +21055,6 @@ msgstr ""
msgid "Showing all issues"
msgstr ""
-msgid "Showing all labels"
-msgstr ""
-
msgid "Showing last %{size} of log -"
msgstr ""
@@ -20495,9 +21166,6 @@ msgstr ""
msgid "Skip outdated deployment jobs"
msgstr ""
-msgid "Skip this for now"
-msgstr ""
-
msgid "Skipped"
msgstr ""
@@ -20642,6 +21310,9 @@ msgstr ""
msgid "Something went wrong while adding your award. Please try again."
msgstr ""
+msgid "Something went wrong while applying the batch of suggestions. Please try again."
+msgstr ""
+
msgid "Something went wrong while applying the suggestion. Please try again."
msgstr ""
@@ -20744,10 +21415,13 @@ msgstr ""
msgid "Something went wrong while updating a requirement."
msgstr ""
+msgid "Something went wrong while updating assignees"
+msgstr ""
+
msgid "Something went wrong while updating your list settings"
msgstr ""
-msgid "Something went wrong with your automatic subscription renewal"
+msgid "Something went wrong with your automatic subscription renewal."
msgstr ""
msgid "Something went wrong, unable to add %{project} to dashboard"
@@ -20765,6 +21439,9 @@ msgstr ""
msgid "Something went wrong, unable to search projects"
msgstr ""
+msgid "Something went wrong."
+msgstr ""
+
msgid "Something went wrong. Please try again."
msgstr ""
@@ -20948,6 +21625,9 @@ msgstr ""
msgid "Source code"
msgstr ""
+msgid "Source code (%{fileExtension})"
+msgstr ""
+
msgid "Source is not available"
msgstr ""
@@ -21032,6 +21712,9 @@ msgstr ""
msgid "Stack trace"
msgstr ""
+msgid "Stacktrace snippet"
+msgstr ""
+
msgid "Stage"
msgstr ""
@@ -21137,6 +21820,9 @@ msgstr ""
msgid "Start thread & reopen %{noteable_name}"
msgstr ""
+msgid "Start using Directed Acyclic Graphs (DAG)"
+msgstr ""
+
msgid "Start your Free Gold Trial"
msgstr ""
@@ -21599,6 +22285,21 @@ msgstr ""
msgid "SuggestedColors|Very pale orange"
msgstr ""
+msgid "Suggestion is not applicable as the suggestion was not found."
+msgstr ""
+
+msgid "Suggestions are not applicable as one or more suggestions were not found."
+msgstr ""
+
+msgid "Suggestions are not applicable as their lines cannot overlap."
+msgstr ""
+
+msgid "Suggestions must all be on the same branch."
+msgstr ""
+
+msgid "Suggestions that change line count can't be added to batches, yet."
+msgstr ""
+
msgid "Suggestions:"
msgstr ""
@@ -21641,9 +22342,6 @@ msgstr ""
msgid "Synchronization disabled"
msgstr ""
-msgid "Synchronization of container repositories is disabled."
-msgstr ""
-
msgid "System"
msgstr ""
@@ -21824,6 +22522,36 @@ msgstr ""
msgid "Terms of Service and Privacy Policy"
msgstr ""
+msgid "Terraform|%{number} Terraform report failed to generate"
+msgid_plural "Terraform|%{number} Terraform reports failed to generate"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+
+msgid "Terraform|%{number} Terraform report was generated in your pipelines"
+msgid_plural "Terraform|%{number} Terraform reports were generated in your pipelines"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+
+msgid "Terraform|A Terraform report failed to generate."
+msgstr ""
+
+msgid "Terraform|A Terraform report was generated in your pipelines."
+msgstr ""
+
+msgid "Terraform|Generating the report caused an error."
+msgstr ""
+
+msgid "Terraform|Reported Resource Changes: %{addNum} to add, %{changeNum} to change, %{deleteNum} to delete"
+msgstr ""
+
+msgid "Terraform|The Terraform report %{name} failed to generate."
+msgstr ""
+
+msgid "Terraform|The Terraform report %{name} was generated in your pipelines."
+msgstr ""
+
msgid "Test"
msgstr ""
@@ -21851,7 +22579,7 @@ msgstr ""
msgid "TestHooks|Ensure the project has CI pipelines."
msgstr ""
-msgid "TestHooks|Ensure the project has at least one commit."
+msgid "TestHooks|Ensure the project has deployments."
msgstr ""
msgid "TestHooks|Ensure the project has issues."
@@ -21953,7 +22681,7 @@ msgstr ""
msgid "The Prometheus server responded with \"bad request\". Please check your queries are correct and are supported in your Prometheus version. %{documentationLink}"
msgstr ""
-msgid "The URL defined on the primary node that secondary nodes should use to contact it. Defaults to URL"
+msgid "The URL defined on the primary node that secondary nodes should use to contact it."
msgstr ""
msgid "The URL to use for connecting to Elasticsearch. Use a comma-separated list to support clustering (e.g., \"http://localhost:9200, http://localhost:9201\")."
@@ -21962,6 +22690,9 @@ msgstr ""
msgid "The X509 Certificate to use when mutual TLS is required to communicate with the external authorization service. If left blank, the server certificate is still validated when accessing over HTTPS."
msgstr ""
+msgid "The above settings apply to all projects with the selected compliance framework(s)."
+msgstr ""
+
msgid "The amount of seconds after which a request to get a secondary node status will time out."
msgstr ""
@@ -22262,9 +22993,6 @@ msgstr ""
msgid "The total stage shows the time it takes between creating an issue and deploying the code to production. The data will be automatically added once you have completed the full idea to production cycle."
msgstr ""
-msgid "The unique identifier for the Geo node. Must match %{geoNodeName} if it is set in gitlab.rb, otherwise it must match %{externalUrl} with a trailing slash"
-msgstr ""
-
msgid "The update action will time out after %{number_of_minutes} minutes. For big repositories, use a clone/push combination."
msgstr ""
@@ -22289,9 +23017,6 @@ msgstr ""
msgid "The user you are trying to deactivate has been active in the past %{minimum_inactive_days} days and cannot be deactivated"
msgstr ""
-msgid "The user-facing URL of the Geo node"
-msgstr ""
-
msgid "The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6."
msgstr ""
@@ -22382,6 +23107,9 @@ msgstr ""
msgid "There was a problem fetching project branches."
msgstr ""
+msgid "There was a problem fetching project tags."
+msgstr ""
+
msgid "There was a problem fetching project users."
msgstr ""
@@ -22433,6 +23161,9 @@ msgstr ""
msgid "There was an error fetching the %{replicableType}"
msgstr ""
+msgid "There was an error fetching the Geo Settings"
+msgstr ""
+
msgid "There was an error fetching the Node's Groups"
msgstr ""
@@ -22553,6 +23284,12 @@ msgstr ""
msgid "This %{issuable} is locked. Only <strong>project members</strong> can comment."
msgstr ""
+msgid "This %{noteableTypeText} is %{confidentialLinkStart}confidential%{linkEnd} and %{lockedLinkStart}locked%{linkEnd}."
+msgstr ""
+
+msgid "This %{noteableTypeText} is locked."
+msgstr ""
+
msgid "This %{viewer} could not be displayed because %{reason}. You can %{options} instead."
msgstr ""
@@ -22571,6 +23308,9 @@ msgstr ""
msgid "This action can lead to data loss. To prevent accidental actions we ask you to confirm your intention."
msgstr ""
+msgid "This also resolves all related threads"
+msgstr ""
+
msgid "This also resolves the discussion"
msgstr ""
@@ -22661,6 +23401,9 @@ msgstr ""
msgid "This epic does not exist or you don't have sufficient permission."
msgstr ""
+msgid "This feature is currently in beta. We invite you to %{linkStart}give feedback%{linkEnd}."
+msgstr ""
+
msgid "This feature requires local storage to be enabled"
msgstr ""
@@ -22694,7 +23437,7 @@ msgstr ""
msgid "This is a Work in Progress"
msgstr ""
-msgid "This is a confidential issue."
+msgid "This is a confidential %{noteableTypeText}."
msgstr ""
msgid "This is a delayed job to run in %{remainingTime}"
@@ -22721,9 +23464,6 @@ msgstr ""
msgid "This is your current session"
msgstr ""
-msgid "This issue is %{confidentialLinkStart}confidential%{linkEnd} and %{lockedLinkStart}locked%{linkEnd}."
-msgstr ""
-
msgid "This issue is confidential"
msgstr ""
@@ -22733,9 +23473,6 @@ msgstr ""
msgid "This issue is in a child epic of the filtered epic"
msgstr ""
-msgid "This issue is locked."
-msgstr ""
-
msgid "This job depends on other jobs with expired/erased artifacts: %{invalid_dependencies}"
msgstr ""
@@ -22835,6 +23572,9 @@ msgstr ""
msgid "This license has already expired."
msgstr ""
+msgid "This link points to external content"
+msgstr ""
+
msgid "This may expose confidential information as the selected fork is in another namespace that can have other members."
msgstr ""
@@ -22862,6 +23602,9 @@ msgstr ""
msgid "This page will be removed in a future release."
msgstr ""
+msgid "This pipeline does not use the %{codeStart}needs%{codeEnd} keyword and can't be represented as a directed acyclic graph."
+msgstr ""
+
msgid "This pipeline makes use of a predefined CI/CD configuration enabled by %{strongStart}Auto DevOps.%{strongEnd}"
msgstr ""
@@ -23090,6 +23833,9 @@ msgstr ""
msgid "Time from last commit to merge"
msgstr ""
+msgid "Time in seconds"
+msgstr ""
+
msgid "Time in seconds GitLab will wait for a response from the external service. When the service does not respond in time, access will be denied."
msgstr ""
@@ -23513,9 +24259,15 @@ msgstr ""
msgid "Total artifacts size: %{total_size}"
msgstr ""
+msgid "Total cores (CPUs)"
+msgstr ""
+
msgid "Total issues"
msgstr ""
+msgid "Total memory (GB)"
+msgstr ""
+
msgid "Total test time for all commits/merges"
msgstr ""
@@ -23717,6 +24469,9 @@ msgstr ""
msgid "U2F only works with HTTPS-enabled websites. Contact your administrator for more details."
msgstr ""
+msgid "UI Development Kit"
+msgstr ""
+
msgid "URL"
msgstr ""
@@ -23789,9 +24544,6 @@ msgstr ""
msgid "Unable to load the merge request widget. Try reloading the page."
msgstr ""
-msgid "Unable to process group import file"
-msgstr ""
-
msgid "Unable to resolve"
msgstr ""
@@ -23807,6 +24559,9 @@ msgstr ""
msgid "Unable to sign you in to the group with SAML due to \"%{reason}\""
msgstr ""
+msgid "Unable to suggest a path. Please refresh and try again."
+msgstr ""
+
msgid "Unable to update label prioritization at this time"
msgstr ""
@@ -23822,6 +24577,9 @@ msgstr ""
msgid "Unarchiving the project will restore people's ability to make changes to it. The repository can be committed to, and issues, comments, and other entities can be created. %{strong_start}Once active, this project shows up in the search and on the dashboard.%{strong_end}"
msgstr ""
+msgid "Unassign from commenting user"
+msgstr ""
+
msgid "Unblock"
msgstr ""
@@ -23897,6 +24655,9 @@ msgstr ""
msgid "Unreachable"
msgstr ""
+msgid "Unrecognized cluster type"
+msgstr ""
+
msgid "Unresolve"
msgstr ""
@@ -23957,6 +24718,9 @@ msgstr ""
msgid "Unsubscribes from this %{quick_action_target}."
msgstr ""
+msgid "Unsupported todo type passed. Supported todo types are: %{todo_types}"
+msgstr ""
+
msgid "Until"
msgstr ""
@@ -23984,6 +24748,9 @@ msgstr ""
msgid "Update approval rule"
msgstr ""
+msgid "Update approvers"
+msgstr ""
+
msgid "Update failed"
msgstr ""
@@ -23993,6 +24760,9 @@ msgstr ""
msgid "Update it"
msgstr ""
+msgid "Update iteration"
+msgstr ""
+
msgid "Update now"
msgstr ""
@@ -24038,7 +24808,7 @@ msgstr ""
msgid "Updated at"
msgstr ""
-msgid "Updated to"
+msgid "Updated to %{linkStart}chart v%{linkEnd}"
msgstr ""
msgid "Updating"
@@ -24068,6 +24838,9 @@ msgstr ""
msgid "Upload CSV file"
msgstr ""
+msgid "Upload License"
+msgstr ""
+
msgid "Upload New File"
msgstr ""
@@ -24149,16 +24922,19 @@ msgstr ""
msgid "UsageQuota|Pipelines"
msgstr ""
+msgid "UsageQuota|Purchase more storage"
+msgstr ""
+
msgid "UsageQuota|Repositories"
msgstr ""
msgid "UsageQuota|Repository"
msgstr ""
-msgid "UsageQuota|Storage"
+msgid "UsageQuota|Snippets"
msgstr ""
-msgid "UsageQuota|Storage usage:"
+msgid "UsageQuota|Storage"
msgstr ""
msgid "UsageQuota|This namespace has no projects which use shared runners"
@@ -24191,6 +24967,12 @@ msgstr ""
msgid "UsageQuota|Wikis"
msgstr ""
+msgid "UsageQuota|You used: %{usage} %{limit}"
+msgstr ""
+
+msgid "UsageQuota|out of %{formattedLimit} of your namespace storage"
+msgstr ""
+
msgid "Use %{code_start}::%{code_end} to create a %{link_start}scoped label set%{link_end} (eg. %{code_start}priority::1%{code_end})"
msgstr ""
@@ -24257,6 +25039,9 @@ msgstr ""
msgid "User IDs"
msgstr ""
+msgid "User Lists can only be created and modified with %{linkStart}the API%{linkEnd}"
+msgstr ""
+
msgid "User OAuth applications"
msgstr ""
@@ -24281,6 +25066,9 @@ msgstr ""
msgid "User key was successfully removed."
msgstr ""
+msgid "User list %{name} will be removed. Are you sure?"
+msgstr ""
+
msgid "User map"
msgstr ""
@@ -24302,148 +25090,10 @@ msgstr ""
msgid "User was successfully updated."
msgstr ""
-msgid "UserOnboardingTour|%{activeTour}/%{totalTours}"
-msgstr ""
-
-msgid "UserOnboardingTour|%{completed}/%{total} steps completed"
-msgstr ""
-
-msgid "UserOnboardingTour|%{emphasisStart}Well done!%{emphasisEnd}%{lineBreak}%{lineBreak}That's it for our guided tour, congratulations for making it all the way to the end!%{lineBreak}%{lineBreak}We hope this gave you a good overview of GitLab and how it can help you. We'll now show you how to create your own project and invite your colleagues."
-msgstr ""
-
-msgid "UserOnboardingTour|Adding other members to a project is done through Project Settings. Click on %{emphasisStart}Settings%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Alright, that's it for Commits. Let's take a look at the %{emphasisStart}Branches%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Awesome! Now click on %{emphasisStart}Members%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Click on one of the %{emphasisStart}Compare%{emphasisEnd} buttons to compare a branch to master."
-msgstr ""
-
-msgid "UserOnboardingTour|Click on one of the %{emphasisStart}pipeline IDs%{emphasisEnd} to see the details of a pipeline."
-msgstr ""
-
-msgid "UserOnboardingTour|Click to open the latest commit to see its details."
-msgstr ""
-
-msgid "UserOnboardingTour|Close 'Learn GitLab'"
-msgstr ""
-
-msgid "UserOnboardingTour|Commits are shown in chronological order and can be filtered by the commit message or by the branch."
-msgstr ""
-
-msgid "UserOnboardingTour|Create a project"
-msgstr ""
-
-msgid "UserOnboardingTour|Exit 'Learn GitLab'"
-msgstr ""
-
-msgid "UserOnboardingTour|Got it"
-msgstr ""
-
-msgid "UserOnboardingTour|Great job! %{clapHands} We hope the tour was helpful and that you learned how to use GitLab.%{lineBreak}%{lineBreak}We'd love to get your feedback on this tour.%{lineBreak}%{lineBreak}%{emphasisStart}How helpful would you say this guided tour was?%{emphasisEnd}%{lineBreak}%{lineBreak}"
-msgstr ""
-
-msgid "UserOnboardingTour|Guided GitLab Tour"
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can compare the changes of this branch to another one. Changes are divided by files so that it's easier to see what was changed where."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can create a project from scratch, start with a template or import a repository from other platforms. Whatever you choose, we'll guide you through the process.%{lineBreak}%{lineBreak}Fill in your new project information and click on %{emphasisStart}Create Project%{emphasisEnd} to progress to the next step."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can see the breakdown of the pipelines: its stages and jobs in each of the stages and their status.%{lineBreak}%{lineBreak}Our CI/CD pipelines are quite complex, most of our users have fewer and simpler pipelines."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can see the current members of the project (just you at the moment) and invite new members.%{lineBreak}%{lineBreak}You can invite multiple members at once (existing GitLab users or invite by email) and you can also set their roles and permissions.%{lineBreak}%{lineBreak}Add a few members and click on %{emphasisStart}Add to project%{emphasisEnd} to complete this step."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can see what changes were made with this commit, on what branch and if there's a related merge request. The status of the pipeline will also show up if CI/CD is set up.%{lineBreak}%{lineBreak}You can also comment on the lines of code that were changed and start a discussion with your colleagues!"
-msgstr ""
-
-msgid "UserOnboardingTour|Here's an overview of branches in the %{emphasisStart}%{projectName}%{emphasisEnd} project. They're split into Active and Stale.%{lineBreak}%{lineBreak}From here, you can create a new merge request from a branch, or compare the branch to any other branch in the project. By default, it will compare it to the master branch."
-msgstr ""
-
-msgid "UserOnboardingTour|Invite colleagues"
-msgstr ""
-
-msgid "UserOnboardingTour|Issues are great for communicating and keeping track of progress in GitLab. These are all issues that are open in the %{emphasisStart}%{projectName}%{emphasisEnd}.%{lineBreak}%{lineBreak}You can help us improve GitLab by contributing work to issues that are labeled <span class=\"badge color-label accept-mr-label\">Accepting merge requests</span>.%{lineBreak}%{lineBreak}This list can be filtered by labels, milestones, assignees, authors... We'll show you how it looks when the list is filtered by a label."
-msgstr ""
-
-msgid "UserOnboardingTour|Learn GitLab"
-msgstr ""
-
-msgid "UserOnboardingTour|Let's take a closer look at a merge request. Click on the title of one."
-msgstr ""
-
-msgid "UserOnboardingTour|Let's take a closer look at all the commits. Click on %{emphasisStart}Commits%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Let's take a closer look at the repository of this project. Click on %{emphasisStart}Repository%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|No thanks"
-msgstr ""
-
-msgid "UserOnboardingTour|Ok, let's go"
-msgstr ""
-
-msgid "UserOnboardingTour|Ok, show me"
-msgstr ""
-
-msgid "UserOnboardingTour|Open one of the issues by clicking on its title."
-msgstr ""
-
-msgid "UserOnboardingTour|Restart this step"
-msgstr ""
-
-msgid "UserOnboardingTour|Skip this step"
-msgstr ""
-
-msgid "UserOnboardingTour|Sweet! Your project was created and is ready to be used.%{lineBreak}%{lineBreak}You can start adding files to the repository or clone it. One last thing we want to show you is how to invite your colleagues to your new project."
-msgstr ""
-
-msgid "UserOnboardingTour|Take a look. Here's a nifty menu for quickly creating issues, merge requests, snippets, projects and groups. Click on it and select \"New project\" from the \"GitLab\" section to get started."
-msgstr ""
-
-msgid "UserOnboardingTour|Thanks for taking the guided tour. Remember, if you want to go through it again, you can start %{emphasisStart}Learn GitLab%{emphasisEnd} in the help menu on the top right."
-msgstr ""
-
-msgid "UserOnboardingTour|Thanks for the feedback! %{thumbsUp}"
-msgstr ""
-
-msgid "UserOnboardingTour|That's it for issues. Let'st take a look at %{emphasisStart}Merge Requests%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|That's it for merge requests. Now for the final part of this guided tour - the %{emphasisStart}CI/CD%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|That's it for the Repository. Let's take a look at the %{emphasisStart}Issues%{emphasisEnd}."
+msgid "UserList|Delete %{name}?"
msgstr ""
-msgid "UserOnboardingTour|The structure of this page is very similar to issues. Status, description, discussion and the sidebar are all here.%{lineBreak}%{lineBreak}But take a look below the description and you'll notice that there's more information about the merge request, the CI/CD pipeline and the options for approving it.%{lineBreak}%{lineBreak}Alongside the discussion you can also see more information about commits in this merge request, the status of pipelines and review all changes that were made."
-msgstr ""
-
-msgid "UserOnboardingTour|There's a lot of information here but don't worry, we'll go through it.%{lineBreak}%{lineBreak}On the top you can see the status of the issue and when it was opened and by whom. Directly below it is the issue description and below that are other %{emphasisStart}related issues%{emphasisEnd} and %{emphasisStart}merge requests%{emphasisEnd} (if any). Then below that is the %{emphasisStart}discussion%{emphasisEnd}, that's where most of the communication happens.%{lineBreak}%{lineBreak}On the right, there's a sidebar where you can view/change the %{emphasisStart}assignee, milestone, due date, labels, weight%{emphasisEnd}, etc."
-msgstr ""
-
-msgid "UserOnboardingTour|These are all the CI/CD pipelines we have for our %{emphasisStart}%{projectName}%{emphasisEnd} project.%{lineBreak}%{lineBreak}Here you can see the status of each pipeline, for what commit it's running for, its stages and the status for them."
-msgstr ""
-
-msgid "UserOnboardingTour|These are all the issues that are available for community contributions. Let's take a closer look at one of them."
-msgstr ""
-
-msgid "UserOnboardingTour|This is an overview of all merge requests in this project. Similarly to the issues overview it can be filtered down by things like labels, milestones, authors, assignees, etc."
-msgstr ""
-
-msgid "UserOnboardingTour|This is the repository for the %{emphasisStart}%{projectName}%{emphasisEnd} project. All our code is stored here. Feel free to explore and take a closer look at folders and files.%{lineBreak}%{lineBreak}Above the file structure you can see the latest commit, who the author is and the status of the CI/CD pipeline.%{lineBreak}%{lineBreak}If you scroll down below the file structure, you'll find the Readme of this project. This is defined in the README.md file at the root of the repository."
-msgstr ""
-
-msgid "UserOnboardingTour|Welcome to the project overview of the %{emphasisStart}%{projectName}%{emphasisEnd} project. This is the project that we use to work on GitLab. At first, a project seems like a simple repository, but at GitLab, a project is so much more.%{lineBreak}%{lineBreak}You can create projects for hosting your codebase, use it as an issue tracker, collaborate on code, and continuously build, test, and deploy your app with built-in GitLab CI/CD."
+msgid "UserList|created %{timeago}"
msgstr ""
msgid "UserProfile|Activity"
@@ -24596,6 +25246,9 @@ msgstr ""
msgid "UsersSelect|Unassigned"
msgstr ""
+msgid "Using %{codeStart}needs%{codeEnd} allows jobs to run before their stage is reached, as soon as their individual dependencies are met, which speeds up your pipelines."
+msgstr ""
+
msgid "Using %{code_start}::%{code_end} denotes a %{link_start}scoped label set%{link_end}"
msgstr ""
@@ -24680,9 +25333,6 @@ msgstr ""
msgid "Versions"
msgstr ""
-msgid "Very helpful"
-msgstr ""
-
msgid "View Documentation"
msgstr ""
@@ -24833,6 +25483,9 @@ msgstr ""
msgid "VisibilityLevel|Unknown"
msgstr ""
+msgid "Visit settings page"
+msgstr ""
+
msgid "VisualReviewApp|%{stepStart}Step 1%{stepEnd}. Copy the following script:"
msgstr ""
@@ -24926,6 +25579,9 @@ msgstr ""
msgid "VulnerabilityManagement|Something went wrong while trying to delete the comment. Please try again later."
msgstr ""
+msgid "VulnerabilityManagement|Something went wrong while trying to refresh the vulnerability. Please try again later."
+msgstr ""
+
msgid "VulnerabilityManagement|Something went wrong while trying to retrieve the vulnerability history. Please try again later."
msgstr ""
@@ -24968,6 +25624,9 @@ msgstr ""
msgid "Vulnerability|Class"
msgstr ""
+msgid "Vulnerability|Crash Address"
+msgstr ""
+
msgid "Vulnerability|Description"
msgstr ""
@@ -25043,9 +25702,6 @@ msgstr ""
msgid "We couldn't reach the Prometheus server. Either the server no longer exists or the configuration details need updating."
msgstr ""
-msgid "We created a short guided tour that will help you learn the basics of GitLab and how it will help you be better at your job. It should only take a couple of minutes. You will be guided by two types of helpers, best recognized by their color."
-msgstr ""
-
msgid "We detected potential spam in the %{humanized_resource_name}. Please solve the reCAPTCHA to proceed."
msgstr ""
@@ -25079,6 +25735,9 @@ msgstr ""
msgid "We've found no vulnerabilities"
msgstr ""
+msgid "Web Application Firewall"
+msgstr ""
+
msgid "Web IDE"
msgstr ""
@@ -25139,9 +25798,6 @@ msgstr ""
msgid "Welcome to GitLab.com<br>@%{name}!"
msgstr ""
-msgid "Welcome to the Guided GitLab Tour"
-msgstr ""
-
msgid "Welcome to the guided GitLab tour"
msgstr ""
@@ -25190,9 +25846,6 @@ msgstr ""
msgid "While it's rare to have no vulnerabilities, it can happen. In any event, we ask that you please double check your settings to make sure you've set up your dashboard correctly."
msgstr ""
-msgid "White helpers give contextual information."
-msgstr ""
-
msgid "Who can be an approver?"
msgstr ""
@@ -25238,12 +25891,18 @@ msgstr ""
msgid "WikiEmptyIssueMessage|Suggest wiki improvement"
msgstr ""
+msgid "WikiEmptyIssueMessage|You must be a group member in order to add wiki pages. If you have suggestions for how to improve the wiki for this group, consider opening an issue in the %{issues_link}."
+msgstr ""
+
msgid "WikiEmptyIssueMessage|You must be a project member in order to add wiki pages. If you have suggestions for how to improve the wiki for this project, consider opening an issue in the %{issues_link}."
msgstr ""
msgid "WikiEmptyIssueMessage|issue tracker"
msgstr ""
+msgid "WikiEmpty|A wiki is where you can store all the details about your group. This can include why you've created it, its principles, how to use it, and so on."
+msgstr ""
+
msgid "WikiEmpty|A wiki is where you can store all the details about your project. This can include why you've created it, its principles, how to use it, and so on."
msgstr ""
@@ -25253,12 +25912,21 @@ msgstr ""
msgid "WikiEmpty|Suggest wiki improvement"
msgstr ""
+msgid "WikiEmpty|The wiki lets you write documentation for your group"
+msgstr ""
+
msgid "WikiEmpty|The wiki lets you write documentation for your project"
msgstr ""
+msgid "WikiEmpty|This group has no wiki pages"
+msgstr ""
+
msgid "WikiEmpty|This project has no wiki pages"
msgstr ""
+msgid "WikiEmpty|You must be a group member in order to add wiki pages."
+msgstr ""
+
msgid "WikiEmpty|You must be a project member in order to add wiki pages."
msgstr ""
@@ -25535,13 +26203,13 @@ msgstr ""
msgid "You can now export your security dashboard to a CSV report."
msgstr ""
-msgid "You can now submit a merge request to get this change into the original branch."
+msgid "You can now manage alert endpoint configuration in the Alerts section on the Operations settings page. Fields on this page have been deprecated."
msgstr ""
-msgid "You can now submit a merge request to get this change into the original project."
+msgid "You can now submit a merge request to get this change into the original branch."
msgstr ""
-msgid "You can only add files when you are on a branch"
+msgid "You can now submit a merge request to get this change into the original project."
msgstr ""
msgid "You can only edit files when you are on a branch"
@@ -25763,7 +26431,7 @@ msgstr ""
msgid "You need to upload a Google Takeout archive."
msgstr ""
-msgid "You reached %{usage_in_percent} of %{namespace_name}'s capacity (%{used_storage} of %{storage_limit})"
+msgid "You reached %{usage_in_percent} of %{namespace_name}'s storage capacity (%{used_storage} of %{storage_limit})"
msgstr ""
msgid "You tried to fork %{link_to_the_project} but it failed for the following reason:"
@@ -26066,13 +26734,13 @@ msgstr ""
msgid "Your subscription expired!"
msgstr ""
-msgid "Your subscription has been downgraded"
+msgid "Your subscription has been downgraded."
msgstr ""
-msgid "Your subscription will automatically renew in %{remaining_days}"
+msgid "Your subscription will automatically renew in %{remaining_days}."
msgstr ""
-msgid "Your subscription will expire in %{remaining_days}"
+msgid "Your subscription will expire in %{remaining_days}."
msgstr ""
msgid "Zoom meeting added"
@@ -26153,9 +26821,6 @@ msgstr ""
msgid "branch name"
msgstr ""
-msgid "build pipeline reference mismatch"
-msgstr ""
-
msgid "by"
msgstr ""
@@ -26174,6 +26839,9 @@ msgstr ""
msgid "cannot block others"
msgstr ""
+msgid "cannot contain HTML/XML tags, including any word between angle brackets (<,>)."
+msgstr ""
+
msgid "cannot include leading slash or directory traversal."
msgstr ""
@@ -26204,60 +26872,6 @@ msgstr ""
msgid "ciReport|%{remainingPackagesCount} more"
msgstr ""
-msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability"
-msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities"
-msgstr[0] ""
-msgstr[1] ""
-msgstr[2] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability for the source branch only"
-msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities for the source branch only"
-msgstr[0] ""
-msgstr[1] ""
-msgstr[2] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerability"
-msgid_plural "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerabilities"
-msgstr[0] ""
-msgstr[1] ""
-msgstr[2] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new vulnerability"
-msgid_plural "ciReport|%{reportType} %{status} detected %{newCount} new vulnerabilities"
-msgstr[0] ""
-msgstr[1] ""
-msgstr[2] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities for the source branch only"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{fixedCount} fixed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} vulnerability for the source branch only"
-msgid_plural "ciReport|%{reportType} %{status} detected %{newCount} vulnerabilities for the source branch only"
-msgstr[0] ""
-msgstr[1] ""
-msgstr[2] ""
-
-msgid "ciReport|%{reportType} %{status} detected no new vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected no vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected no vulnerabilities for the source branch only"
-msgstr ""
-
msgid "ciReport|%{reportType} is loading"
msgstr ""
@@ -26276,7 +26890,7 @@ msgstr ""
msgid "ciReport|All projects"
msgstr ""
-msgid "ciReport|All report types"
+msgid "ciReport|All scanner types"
msgstr ""
msgid "ciReport|All severities"
@@ -26300,6 +26914,9 @@ msgstr ""
msgid "ciReport|Container scanning detects known vulnerabilities in your docker images."
msgstr ""
+msgid "ciReport|Coverage Fuzzing"
+msgstr ""
+
msgid "ciReport|Create a merge request to implement this solution, or download and apply the patch manually."
msgstr ""
@@ -26459,6 +27076,9 @@ msgstr ""
msgid "customize"
msgstr ""
+msgid "data"
+msgstr ""
+
msgid "date must not be after 9999-12-31"
msgstr ""
@@ -26531,6 +27151,9 @@ msgstr ""
msgid "entries cannot contain HTML tags"
msgstr ""
+msgid "epic"
+msgstr ""
+
msgid "error"
msgstr ""
@@ -26552,9 +27175,6 @@ msgstr ""
msgid "expires on %{timebox_due_date}"
msgstr ""
-msgid "external_url"
-msgstr ""
-
msgid "failed"
msgstr ""
@@ -26591,9 +27211,6 @@ msgstr ""
msgid "from"
msgstr ""
-msgid "geo_node_name"
-msgstr ""
-
msgid "group"
msgstr ""
@@ -26615,9 +27232,6 @@ msgstr ""
msgid "https://your-bitbucket-server"
msgstr ""
-msgid "image"
-msgstr ""
-
msgid "image diff"
msgstr ""
@@ -26762,6 +27376,9 @@ msgstr ""
msgid "load it anyway"
msgstr ""
+msgid "loading"
+msgstr ""
+
msgid "locked by %{path_lock_user_name} %{created_at}"
msgstr ""
@@ -26891,9 +27508,6 @@ msgstr ""
msgid "mrWidget|Deployment statistics are not available currently"
msgstr ""
-msgid "mrWidget|Detect issues before deployment with a CI pipeline that continuously tests your code. We created a quick guide that will show you how to create one. Make your code more secure and more robust in just a minute."
-msgstr ""
-
msgid "mrWidget|Did not close"
msgstr ""
@@ -27056,6 +27670,9 @@ msgstr ""
msgid "mrWidget|To approve this merge request, please enter your password. This project requires all approvals to be authenticated."
msgstr ""
+msgid "mrWidget|Use %{linkStart}CI pipelines to test your code%{linkEnd}, simply add a GitLab CI configuration file to your project. It only takes a minute to make your code more secure and robust."
+msgstr ""
+
msgid "mrWidget|When this merge request is ready, remove the WIP: prefix from the title to allow it to be merged"
msgstr ""
@@ -27092,6 +27709,9 @@ msgstr ""
msgid "must be greater than start date"
msgstr ""
+msgid "must contain only valid frameworks"
+msgstr ""
+
msgid "my-awesome-group"
msgstr ""
@@ -27104,6 +27724,9 @@ msgstr ""
msgid "needs to be between 10 minutes and 1 month"
msgstr ""
+msgid "never"
+msgstr ""
+
msgid "never expires"
msgstr ""
@@ -27131,6 +27754,9 @@ msgstr ""
msgid "nounSeries|%{firstItem} and %{lastItem}"
msgstr ""
+msgid "nounSeries|%{item}"
+msgstr ""
+
msgid "nounSeries|%{item}, %{nextItem}"
msgstr ""
@@ -27272,6 +27898,9 @@ msgstr ""
msgid "revised"
msgstr ""
+msgid "satisfied"
+msgstr ""
+
msgid "score"
msgstr ""
@@ -27302,6 +27931,9 @@ msgstr ""
msgid "severity|Unknown"
msgstr ""
+msgid "should be an array of %{object_name} objects"
+msgstr ""
+
msgid "should be greater than or equal to %{access} inherited membership from group %{group_name}"
msgstr ""
@@ -27356,10 +27988,13 @@ msgstr ""
msgid "suggestPipeline|2/2: Commit your changes"
msgstr ""
-msgid "suggestPipeline|Commit the changes and your pipeline will automatically run for the first time."
+msgid "suggestPipeline|Choose %{boldStart}Code Quality%{boldEnd} to add a pipeline that tests the quality of your code."
msgstr ""
-msgid "suggestPipeline|We recommend the %{boldStart}Code Quality%{boldEnd} template, which will add a report widget to your Merge Requests. This way you’ll learn about code quality degradations much sooner. %{footerStart} Goodbye technical debt! %{footerEnd}"
+msgid "suggestPipeline|The template is ready! You can now commit it to create your first pipeline."
+msgstr ""
+
+msgid "suggestPipeline|We’re adding a GitLab CI configuration file to add a pipeline to the project. You could create it manually, but we recommend that you start with a GitLab template that works out of the box."
msgstr ""
msgid "syntax is correct"
@@ -27419,6 +28054,9 @@ msgstr ""
msgid "uses Kubernetes clusters to deploy your code!"
msgstr ""
+msgid "v%{version} published %{timeAgo}"
+msgstr ""
+
msgid "verify ownership"
msgstr ""
diff --git a/locale/hu_HU/gitlab.po b/locale/hu_HU/gitlab.po
index c0a1414f43a..934a374f209 100644
--- a/locale/hu_HU/gitlab.po
+++ b/locale/hu_HU/gitlab.po
@@ -2,7 +2,7 @@ msgid ""
msgstr ""
"Project-Id-Version: gitlab-ee\n"
"Report-Msgid-Bugs-To: \n"
-"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Last-Translator: \n"
"Language-Team: Hungarian\n"
"Language: hu_HU\n"
"MIME-Version: 1.0\n"
@@ -10,9 +10,11 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Crowdin-Project: gitlab-ee\n"
+"X-Crowdin-Project-ID: 288872\n"
"X-Crowdin-Language: hu\n"
"X-Crowdin-File: /master/locale/gitlab.pot\n"
-"PO-Revision-Date: 2020-06-08 15:16\n"
+"X-Crowdin-File-ID: 6\n"
+"PO-Revision-Date: 2020-07-02 01:25\n"
msgid " %{start} to %{end}"
msgstr ""
@@ -197,6 +199,11 @@ msgid_plural "%d metrics"
msgstr[0] ""
msgstr[1] ""
+msgid "%d milestone"
+msgid_plural "%d milestones"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "%d minute"
msgid_plural "%d minutes"
msgstr[0] ""
@@ -242,11 +249,6 @@ msgid_plural "%d unresolved threads"
msgstr[0] ""
msgstr[1] ""
-msgid "%d url scanned"
-msgid_plural "%d urls scanned"
-msgstr[0] ""
-msgstr[1] ""
-
msgid "%d vulnerability"
msgid_plural "%d vulnerabilities"
msgstr[0] ""
@@ -324,9 +326,15 @@ msgstr[1] ""
msgid "%{count} related %{pluralized_subject}: %{links}"
msgstr ""
+msgid "%{dashboard_path} could not be found."
+msgstr ""
+
msgid "%{days} days until tags are automatically removed"
msgstr ""
+msgid "%{deployLinkStart}Use a template to deploy to ECS%{deployLinkEnd}, or use a docker image to %{commandsLinkStart}run AWS commands in GitLab CI/CD%{commandsLinkEnd}."
+msgstr ""
+
msgid "%{description}- Sentry event: %{errorUrl}- First seen: %{firstSeen}- Last seen: %{lastSeen} %{countLabel}: %{count}%{userCountLabel}: %{userCount}"
msgstr ""
@@ -369,12 +377,18 @@ msgstr ""
msgid "%{host} sign-in from new location"
msgstr ""
-msgid "%{icon}You are about to add %{usersTag} people to the discussion. Proceed with caution."
+msgid "%{icon}You are about to add %{usersTag} people to the discussion. They will all receive a notification."
+msgstr ""
+
+msgid "%{integrations_link_start}Integrations%{link_end} enable you to make third-party applications part of your GitLab workflow. If the available integrations don't meet your needs, consider using a %{webhooks_link_start}webhook%{link_end}."
msgstr ""
msgid "%{issuableType} will be removed! Are you sure?"
msgstr ""
+msgid "%{issuesCount} issues in this group"
+msgstr ""
+
msgid "%{issuesSize} issues"
msgstr ""
@@ -384,7 +398,7 @@ msgstr ""
msgid "%{labelStart}Class:%{labelEnd} %{class}"
msgstr ""
-msgid "%{labelStart}Confidence:%{labelEnd} %{confidence}"
+msgid "%{labelStart}Evidence:%{labelEnd} %{evidence}"
msgstr ""
msgid "%{labelStart}File:%{labelEnd} %{file}"
@@ -402,6 +416,9 @@ msgstr ""
msgid "%{labelStart}Report Type:%{labelEnd} %{reportType}"
msgstr ""
+msgid "%{labelStart}Scanner:%{labelEnd} %{scanner}"
+msgstr ""
+
msgid "%{labelStart}Severity:%{labelEnd} %{severity}"
msgstr ""
@@ -426,9 +443,6 @@ msgstr ""
msgid "%{link_start}Read more%{link_end} about role permissions"
msgstr ""
-msgid "%{link} can be used for binding events when something is happening within the project."
-msgstr ""
-
msgid "%{listToShow}, and %{awardsListLength} more."
msgstr ""
@@ -462,6 +476,9 @@ msgstr ""
msgid "%{name} found %{resultsString}"
msgstr ""
+msgid "%{name} is already being used for another emoji"
+msgstr ""
+
msgid "%{name} is scheduled for %{action}"
msgstr ""
@@ -509,6 +526,9 @@ msgstr ""
msgid "%{primary} (%{secondary})"
msgstr ""
+msgid "%{ref} cannot be added: %{error}"
+msgstr ""
+
msgid "%{releases} release"
msgid_plural "%{releases} releases"
msgstr[0] ""
@@ -517,13 +537,37 @@ msgstr[1] ""
msgid "%{remaining_approvals} left"
msgstr ""
-msgid "%{retryButtonStart}Try again%{retryButtonEnd} or %{newFileButtonStart}attach a new file%{newFileButtonEnd}"
+msgid "%{reportType} %{status} detected %{critical} critical and %{high} high severity vulnerabilities out of %{total}."
+msgstr ""
+
+msgid "%{reportType} %{status} detected %{critical} critical and %{high} high severity vulnerabilities."
+msgstr ""
+
+msgid "%{reportType} %{status} detected %{critical} critical severity vulnerabilities out of %{total}."
+msgstr ""
+
+msgid "%{reportType} %{status} detected %{critical} critical severity vulnerability."
+msgid_plural "%{reportType} %{status} detected %{critical} critical severity vulnerabilities."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{reportType} %{status} detected %{high} high severity vulnerabilities out of %{total}."
msgstr ""
-msgid "%{screenreaderOnlyStart}Keyboard shorcuts%{screenreaderOnlyEnd} Disabled"
+msgid "%{reportType} %{status} detected %{high} high severity vulnerability."
+msgid_plural "%{reportType} %{status} detected %{high} high severity vulnerabilities."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{reportType} %{status} detected %{other} vulnerability."
+msgid_plural "%{reportType} %{status} detected %{other} vulnerabilities."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{reportType} %{status} detected no new vulnerabilities."
msgstr ""
-msgid "%{screenreaderOnlyStart}Keyboard shorcuts%{screenreaderOnlyEnd} Enabled"
+msgid "%{retryButtonStart}Try again%{retryButtonEnd} or %{newFileButtonStart}attach a new file%{newFileButtonEnd}"
msgstr ""
msgid "%{service_title} %{message}."
@@ -620,6 +664,12 @@ msgstr ""
msgid "%{token}..."
msgstr ""
+msgid "%{totalCpu} (%{freeSpacePercentage}%{percentSymbol} free)"
+msgstr ""
+
+msgid "%{totalMemory} (%{freeSpacePercentage}%{percentSymbol} free)"
+msgstr ""
+
msgid "%{totalWeight} total weight"
msgstr ""
@@ -650,6 +700,12 @@ msgstr ""
msgid "%{verb} %{time_spent_value} spent time."
msgstr ""
+msgid "%{webhooks_link_start}%{webhook_type}%{link_end} enable you to send notifications to web applications in response to events in a group or project."
+msgstr ""
+
+msgid "%{webhooks_link_start}%{webhook_type}%{link_end} enable you to send notifications to web applications in response to events in a group or project. We recommend using an %{integrations_link_start}integration%{link_end} in preference to a webhook."
+msgstr ""
+
msgid "'%{level}' is not a valid visibility level"
msgstr ""
@@ -676,9 +732,6 @@ msgstr ""
msgid "(No changes)"
msgstr ""
-msgid "(Show all)"
-msgstr ""
-
msgid "(check progress)"
msgstr ""
@@ -745,6 +798,9 @@ msgstr ""
msgid "0 for unlimited"
msgstr ""
+msgid "0 for unlimited, only effective with remote storage enabled."
+msgstr ""
+
msgid "1 %{type} addition"
msgid_plural "%{count} %{type} additions"
msgstr[0] ""
@@ -892,6 +948,12 @@ msgstr ""
msgid "<code>\"johnsmith@example.com\": \"johnsmith@example.com\"</code> will add \"By <a href=\"#\">johnsmith@example.com</a>\" to all issues and comments originally created by johnsmith@example.com. By default, the email address or username is masked to ensure the user's privacy. Use this option if you want to show the full email address."
msgstr ""
+msgid "<code>Masked</code> to prevent the values from being displayed in job logs (must match certain regexp requirements)."
+msgstr ""
+
+msgid "<code>Protected</code> to expose them to protected branches or tags only."
+msgstr ""
+
msgid "<namespace / project>"
msgstr ""
@@ -919,9 +981,6 @@ msgstr ""
msgid "A .NET Core console application template, customizable for any .NET Core project"
msgstr ""
-msgid "A DAG must have two dependent jobs to be visualized on this tab."
-msgstr ""
-
msgid "A GitBook site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
msgstr ""
@@ -952,6 +1011,12 @@ msgstr ""
msgid "A deleted user"
msgstr ""
+msgid "A file has been changed."
+msgstr ""
+
+msgid "A file was not found."
+msgstr ""
+
msgid "A file with '%{file_name}' already exists in %{branch} branch"
msgstr ""
@@ -1018,7 +1083,7 @@ msgstr ""
msgid "A subscription will trigger a new pipeline on the default branch of this project when a pipeline successfully completes for a new tag on the %{default_branch_docs} of the subscribed project."
msgstr ""
-msgid "A terraform report was generated in your pipelines."
+msgid "A suggestion is not applicable."
msgstr ""
msgid "A user with write access to the source branch selected this option"
@@ -1189,6 +1254,9 @@ msgstr ""
msgid "Action to take when receiving an alert."
msgstr ""
+msgid "Actions"
+msgstr ""
+
msgid "Activate"
msgstr ""
@@ -1245,6 +1313,9 @@ msgstr ""
msgid "Add LICENSE"
msgstr ""
+msgid "Add New Node"
+msgstr ""
+
msgid "Add README"
msgstr ""
@@ -1320,6 +1391,9 @@ msgstr ""
msgid "Add approval rule"
msgstr ""
+msgid "Add approvers"
+msgstr ""
+
msgid "Add bold text"
msgstr ""
@@ -1377,6 +1451,9 @@ msgstr ""
msgid "Add strikethrough text"
msgstr ""
+msgid "Add suggestion to batch"
+msgstr ""
+
msgid "Add system hook"
msgstr ""
@@ -1476,6 +1553,9 @@ msgstr ""
msgid "Admin Note"
msgstr ""
+msgid "Admin Notifications"
+msgstr ""
+
msgid "Admin Overview"
msgstr ""
@@ -1587,6 +1667,9 @@ msgstr ""
msgid "AdminSettings|Integrations configured here will automatically apply to all projects on this instance."
msgstr ""
+msgid "AdminSettings|Moved to integrations"
+msgstr ""
+
msgid "AdminSettings|No required pipeline"
msgstr ""
@@ -1817,12 +1900,18 @@ msgid_plural "Alerts"
msgstr[0] ""
msgstr[1] ""
+msgid "AlertManagement| assign yourself"
+msgstr ""
+
msgid "AlertManagement|Acknowledged"
msgstr ""
msgid "AlertManagement|Alert"
msgstr ""
+msgid "AlertManagement|Alert assignee(s): %{assignees}"
+msgstr ""
+
msgid "AlertManagement|Alert detail"
msgstr ""
@@ -1838,9 +1927,15 @@ msgstr ""
msgid "AlertManagement|All alerts"
msgstr ""
+msgid "AlertManagement|Assign To"
+msgstr ""
+
msgid "AlertManagement|Assign status"
msgstr ""
+msgid "AlertManagement|Assignee"
+msgstr ""
+
msgid "AlertManagement|Assignees"
msgstr ""
@@ -1859,9 +1954,6 @@ msgstr ""
msgid "AlertManagement|Edit"
msgstr ""
-msgid "AlertManagement|End time"
-msgstr ""
-
msgid "AlertManagement|Events"
msgstr ""
@@ -1880,10 +1972,13 @@ msgstr ""
msgid "AlertManagement|More information"
msgstr ""
+msgid "AlertManagement|No Matching Results"
+msgstr ""
+
msgid "AlertManagement|No alert data to display."
msgstr ""
-msgid "AlertManagement|No alerts available to display. If you think you're seeing this message in error, refresh the page."
+msgid "AlertManagement|No alerts available to display. See %{linkStart}enabling alert management%{linkEnd} for more information on adding alerts to the list."
msgstr ""
msgid "AlertManagement|No alerts to display."
@@ -1892,6 +1987,9 @@ msgstr ""
msgid "AlertManagement|None"
msgstr ""
+msgid "AlertManagement|None -"
+msgstr ""
+
msgid "AlertManagement|Open"
msgstr ""
@@ -1928,9 +2026,18 @@ msgstr ""
msgid "AlertManagement|There was an error displaying the alerts. Confirm your endpoint's configuration details to ensure alerts appear."
msgstr ""
+msgid "AlertManagement|There was an error while updating the assignee(s) list. Please try again."
+msgstr ""
+
+msgid "AlertManagement|There was an error while updating the assignee(s) of the alert. Please try again."
+msgstr ""
+
msgid "AlertManagement|There was an error while updating the status of the alert. Please try again."
msgstr ""
+msgid "AlertManagement|This assignee cannot be assigned to this alert."
+msgstr ""
+
msgid "AlertManagement|Tool"
msgstr ""
@@ -2078,6 +2185,9 @@ msgstr ""
msgid "Allow users to request access (if visibility is public or internal)"
msgstr ""
+msgid "Allowed Geo IP"
+msgstr ""
+
msgid "Allowed email domain restriction only permitted for top-level groups"
msgstr ""
@@ -2111,6 +2221,9 @@ msgstr ""
msgid "Amazon Web Services"
msgstr ""
+msgid "Amazon Web Services Logo"
+msgstr ""
+
msgid "Amazon authentication is not %{link_start}correctly configured%{link_end}. Ask your GitLab administrator if you want to use this service."
msgstr ""
@@ -2168,10 +2281,16 @@ msgstr ""
msgid "An error occurred when updating the issue weight"
msgstr ""
+msgid "An error occurred while acknowledging the notification. Refresh the page and try again."
+msgstr ""
+
+msgid "An error occurred while adding approvers"
+msgstr ""
+
msgid "An error occurred while adding formatted title for epic"
msgstr ""
-msgid "An error occurred while checking group path"
+msgid "An error occurred while checking group path. Please refresh and try again."
msgstr ""
msgid "An error occurred while committing your changes."
@@ -2315,7 +2434,7 @@ msgstr ""
msgid "An error occurred while loading milestones"
msgstr ""
-msgid "An error occurred while loading terraform report"
+msgid "An error occurred while loading project creation UI"
msgstr ""
msgid "An error occurred while loading the data. Please try again."
@@ -2369,6 +2488,9 @@ msgstr ""
msgid "An error occurred while rendering preview broadcast message"
msgstr ""
+msgid "An error occurred while rendering the editor"
+msgstr ""
+
msgid "An error occurred while reordering issues."
msgstr ""
@@ -2465,7 +2587,7 @@ msgstr ""
msgid "Analyze your dependencies for known vulnerabilities."
msgstr ""
-msgid "Analyze your source code and git history for secrets"
+msgid "Analyze your source code and git history for secrets."
msgstr ""
msgid "Analyze your source code for known vulnerabilities."
@@ -2579,12 +2701,18 @@ msgstr ""
msgid "Apply suggestion"
msgstr ""
+msgid "Apply suggestions"
+msgstr ""
+
msgid "Apply template"
msgstr ""
msgid "Apply this approval rule to any branch or a specific protected branch."
msgstr ""
+msgid "Applying"
+msgstr ""
+
msgid "Applying a template will replace the existing issue description. Any changes you have made will be lost."
msgstr ""
@@ -2597,12 +2725,18 @@ msgstr ""
msgid "Applying multiple commands"
msgstr ""
-msgid "Applying suggestion"
+msgid "Applying suggestion..."
+msgstr ""
+
+msgid "Applying suggestions..."
msgstr ""
msgid "Approval rules"
msgstr ""
+msgid "Approval rules reset to project defaults"
+msgstr ""
+
msgid "ApprovalRuleRemove|%d member"
msgid_plural "ApprovalRuleRemove|%d members"
msgstr[0] ""
@@ -2656,6 +2790,9 @@ msgstr ""
msgid "Approved"
msgstr ""
+msgid "Approved MRs"
+msgstr ""
+
msgid "Approved by: "
msgstr ""
@@ -2869,6 +3006,9 @@ msgstr ""
msgid "Assign"
msgstr ""
+msgid "Assign Iteration"
+msgstr ""
+
msgid "Assign custom color like #FF0000"
msgstr ""
@@ -2887,6 +3027,9 @@ msgstr ""
msgid "Assign to"
msgstr ""
+msgid "Assign to commenting user"
+msgstr ""
+
msgid "Assign yourself to these issues"
msgstr ""
@@ -2913,6 +3056,9 @@ msgid_plural "%d Assignees"
msgstr[0] ""
msgstr[1] ""
+msgid "Assignee has no permissions"
+msgstr ""
+
msgid "Assignee lists not available with your current license"
msgstr ""
@@ -2922,6 +3068,9 @@ msgstr ""
msgid "Assignee(s)"
msgstr ""
+msgid "Assignees"
+msgstr ""
+
msgid "Assigns %{assignee_users_sentence}."
msgstr ""
@@ -2957,6 +3106,9 @@ msgstr ""
msgid "Audit Events is a way to keep track of important events that happened in GitLab."
msgstr ""
+msgid "Audit Log"
+msgstr ""
+
msgid "AuditEvents|(removed)"
msgstr ""
@@ -2993,6 +3145,9 @@ msgstr ""
msgid "AuditLogs|IP Address"
msgstr ""
+msgid "AuditLogs|Member Events"
+msgstr ""
+
msgid "AuditLogs|No matching %{type} found."
msgstr ""
@@ -3275,9 +3430,6 @@ msgstr ""
msgid "Badges|e.g. %{exampleUrl}"
msgstr ""
-msgid "Badge|New"
-msgstr ""
-
msgid "Balsamiq file could not be loaded."
msgstr ""
@@ -3326,6 +3478,12 @@ msgstr ""
msgid "Below you will find all the groups that are public."
msgstr ""
+msgid "Beta"
+msgstr ""
+
+msgid "Bi-weekly code coverage"
+msgstr ""
+
msgid "Billing"
msgstr ""
@@ -3386,9 +3544,15 @@ msgstr ""
msgid "Bitbucket Server Import"
msgstr ""
+msgid "Bitbucket Server import"
+msgstr ""
+
msgid "Bitbucket import"
msgstr ""
+msgid "Blame"
+msgstr ""
+
msgid "Blocked"
msgstr ""
@@ -3401,9 +3565,6 @@ msgstr ""
msgid "Blog"
msgstr ""
-msgid "Blue helpers indicate an action to be taken."
-msgstr ""
-
msgid "Board name"
msgstr ""
@@ -3602,6 +3763,9 @@ msgstr ""
msgid "Broadcast Message was successfully updated."
msgstr ""
+msgid "Broadcast Messages"
+msgstr ""
+
msgid "Browse Directory"
msgstr ""
@@ -3656,6 +3820,9 @@ msgstr ""
msgid "By %{user_name}"
msgstr ""
+msgid "By URL"
+msgstr ""
+
msgid "By default GitLab sends emails in HTML and plain text formats so mail clients can choose what format to use. Disable this option if you only want to send emails in plain text format."
msgstr ""
@@ -3761,6 +3928,9 @@ msgstr ""
msgid "Can override approvers and approvals required per merge request"
msgstr ""
+msgid "Can't apply as this line has changed or the suggestion already matches its content."
+msgstr ""
+
msgid "Can't create snippet: %{err}"
msgstr ""
@@ -3947,9 +4117,6 @@ msgstr ""
msgid "Changes are still tracked. Useful for cluster/index migrations."
msgstr ""
-msgid "Changes are unknown"
-msgstr ""
-
msgid "Changes suppressed. Click to show."
msgstr ""
@@ -4256,15 +4423,9 @@ msgstr ""
msgid "Choose what content you want to see on a group’s overview page"
msgstr ""
-msgid "Choose which groups you wish to synchronize to this secondary node"
-msgstr ""
-
msgid "Choose which repositories you want to connect and run CI/CD pipelines."
msgstr ""
-msgid "Choose which shards you wish to synchronize to this secondary node"
-msgstr ""
-
msgid "Choose your framework"
msgstr ""
@@ -4562,9 +4723,15 @@ msgstr ""
msgid "Cluster does not exist"
msgstr ""
+msgid "Cluster is required for Stages::ClusterEndpointInserter"
+msgstr ""
+
msgid "Cluster level"
msgstr ""
+msgid "Cluster type must be specificed for Stages::ClusterEndpointInserter"
+msgstr ""
+
msgid "ClusterIntegration| %{custom_domain_start}More information%{custom_domain_end}."
msgstr ""
@@ -4676,7 +4843,7 @@ msgstr ""
msgid "ClusterIntegration|Cert-Manager"
msgstr ""
-msgid "ClusterIntegration|Cert-Manager is a native Kubernetes certificate management controller that helps with issuing certificates. Installing Cert-Manager on your cluster will issue a certificate by %{letsEncrypt} and ensure that certificates are valid and up-to-date."
+msgid "ClusterIntegration|Cert-Manager is a native Kubernetes certificate management controller that helps with issuing certificates. Installing Cert-Manager on your cluster will issue a certificate by %{linkStart}Let's Encrypt%{linkEnd} and ensure that certificates are valid and up-to-date."
msgstr ""
msgid "ClusterIntegration|Certificate Authority bundle (PEM format)"
@@ -4703,9 +4870,6 @@ msgstr ""
msgid "ClusterIntegration|Clear the local cache of namespace and service accounts. This is necessary if your integration has become out of sync. The cache is repopulated during the next CI job that requires namespace and service accounts."
msgstr ""
-msgid "ClusterIntegration|Cloud Run"
-msgstr ""
-
msgid "ClusterIntegration|Cluster being created"
msgstr ""
@@ -4790,7 +4954,7 @@ msgstr ""
msgid "ClusterIntegration|Crossplane"
msgstr ""
-msgid "ClusterIntegration|Crossplane enables declarative provisioning of managed services from your cloud of choice using %{codeStart}kubectl%{codeEnd} or %{gitlabIntegrationLink}. Crossplane runs inside your Kubernetes cluster and supports secure connectivity and secrets management between app containers and the cloud services they depend on."
+msgid "ClusterIntegration|Crossplane enables declarative provisioning of managed services from your cloud of choice using %{codeStart}kubectl%{codeEnd} or %{linkStart}GitLab Integration%{linkEnd}. Crossplane runs inside your Kubernetes cluster and supports secure connectivity and secrets management between app containers and the cloud services they depend on."
msgstr ""
msgid "ClusterIntegration|Deletes all GitLab resources attached to this cluster during removal"
@@ -4874,9 +5038,6 @@ msgstr ""
msgid "ClusterIntegration|GitLab-managed cluster"
msgstr ""
-msgid "ClusterIntegration|Gitlab Integration"
-msgstr ""
-
msgid "ClusterIntegration|Global default"
msgstr ""
@@ -4922,7 +5083,7 @@ msgstr ""
msgid "ClusterIntegration|Ingress gives you a way to route requests to services based on the request host or path, centralizing a number of services into a single entrypoint."
msgstr ""
-msgid "ClusterIntegration|Installing Ingress may incur additional costs. Learn more about %{pricingLink}."
+msgid "ClusterIntegration|Installing Ingress may incur additional costs. Learn more about %{linkStart}pricing%{linkEnd}."
msgstr ""
msgid "ClusterIntegration|Instance cluster"
@@ -4937,7 +5098,7 @@ msgstr ""
msgid "ClusterIntegration|Issuer Email"
msgstr ""
-msgid "ClusterIntegration|Issuers represent a certificate authority. You must provide an email address for your Issuer. "
+msgid "ClusterIntegration|Issuers represent a certificate authority. You must provide an email address for your Issuer."
msgstr ""
msgid "ClusterIntegration|Jupyter Hostname"
@@ -5009,9 +5170,6 @@ msgstr ""
msgid "ClusterIntegration|Learn more about instance Kubernetes clusters"
msgstr ""
-msgid "ClusterIntegration|Let's Encrypt"
-msgstr ""
-
msgid "ClusterIntegration|Loading IAM Roles"
msgstr ""
@@ -5123,7 +5281,7 @@ msgstr ""
msgid "ClusterIntegration|Prometheus"
msgstr ""
-msgid "ClusterIntegration|Prometheus is an open-source monitoring system with %{gitlabIntegrationLink} to monitor deployed applications."
+msgid "ClusterIntegration|Prometheus is an open-source monitoring system with %{linkStart}GitLab Integration%{linkEnd} to monitor deployed applications."
msgstr ""
msgid "ClusterIntegration|Provider details"
@@ -5279,10 +5437,10 @@ msgstr ""
msgid "ClusterIntegration|Select zone to choose machine type"
msgstr ""
-msgid "ClusterIntegration|Send Cilium Logs"
+msgid "ClusterIntegration|Send Container Network Policies Logs"
msgstr ""
-msgid "ClusterIntegration|Send ModSecurity Logs"
+msgid "ClusterIntegration|Send Web Application Firewall Logs"
msgstr ""
msgid "ClusterIntegration|Service Token"
@@ -5450,15 +5608,12 @@ msgstr ""
msgid "ClusterIntegration|documentation"
msgstr ""
-msgid "ClusterIntegration|installed via %{installed_via}"
+msgid "ClusterIntegration|installed via %{linkStart}Cloud Run%{linkEnd}"
msgstr ""
msgid "ClusterIntegration|meets the requirements"
msgstr ""
-msgid "ClusterIntegration|pricing"
-msgstr ""
-
msgid "ClusterIntegration|sign up"
msgstr ""
@@ -5495,6 +5650,15 @@ msgstr ""
msgid "Code"
msgstr ""
+msgid "Code Coverage: %{coveragePercentage}%{percentSymbol}"
+msgstr ""
+
+msgid "Code Coverage| Empty code coverage data"
+msgstr ""
+
+msgid "Code Coverage|Couldn't fetch the code coverage data"
+msgstr ""
+
msgid "Code Owners"
msgstr ""
@@ -5549,7 +5713,7 @@ msgstr ""
msgid "Collapse approvers"
msgstr ""
-msgid "Collapse child epics"
+msgid "Collapse milestones"
msgstr ""
msgid "Collapse replies"
@@ -5567,6 +5731,9 @@ msgstr ""
msgid "Coming soon"
msgstr ""
+msgid "Comma-separated, e.g. '1.1.1.1, 2.2.2.0/24'"
+msgstr ""
+
msgid "Command"
msgstr ""
@@ -5737,6 +5904,12 @@ msgstr ""
msgid "Compliance framework (optional)"
msgstr ""
+msgid "Compliance frameworks"
+msgstr ""
+
+msgid "ComplianceDashboard|created by:"
+msgstr ""
+
msgid "ComplianceFramework|GDPR"
msgstr ""
@@ -5875,6 +6048,9 @@ msgstr ""
msgid "Connection timed out"
msgstr ""
+msgid "Connection timeout"
+msgstr ""
+
msgid "Contact sales to upgrade"
msgstr ""
@@ -5930,9 +6106,6 @@ msgstr ""
msgid "ContainerRegistry|CLI Commands"
msgstr ""
-msgid "ContainerRegistry|Compressed Size"
-msgstr ""
-
msgid "ContainerRegistry|Container Registry"
msgstr ""
@@ -5948,6 +6121,9 @@ msgstr ""
msgid "ContainerRegistry|Copy push command"
msgstr ""
+msgid "ContainerRegistry|Delete selected"
+msgstr ""
+
msgid "ContainerRegistry|Docker connection error"
msgstr ""
@@ -5981,16 +6157,16 @@ msgstr ""
msgid "ContainerRegistry|If you are not already logged in, you need to authenticate to the Container Registry by using your GitLab username and password. If you have %{twofaDocLinkStart}Two-Factor Authentication%{twofaDocLinkEnd} enabled, use a %{personalAccessTokensDocLinkStart}Personal Access Token%{personalAccessTokensDocLinkEnd} instead of a password."
msgstr ""
-msgid "ContainerRegistry|Image ID"
+msgid "ContainerRegistry|Image ID: %{imageId}"
msgstr ""
msgid "ContainerRegistry|Image Repositories"
msgstr ""
-msgid "ContainerRegistry|Keep and protect the images that matter most."
+msgid "ContainerRegistry|Image tags"
msgstr ""
-msgid "ContainerRegistry|Last Updated"
+msgid "ContainerRegistry|Keep and protect the images that matter most."
msgstr ""
msgid "ContainerRegistry|Login"
@@ -6005,6 +6181,9 @@ msgstr ""
msgid "ContainerRegistry|Please contact your administrator."
msgstr ""
+msgid "ContainerRegistry|Published %{timeInfo}"
+msgstr ""
+
msgid "ContainerRegistry|Push an image"
msgstr ""
@@ -6020,9 +6199,6 @@ msgstr ""
msgid "ContainerRegistry|Remove repository"
msgstr ""
-msgid "ContainerRegistry|Remove selected tags"
-msgstr ""
-
msgid "ContainerRegistry|Remove tag"
msgid_plural "ContainerRegistry|Remove tags"
msgstr[0] ""
@@ -6052,9 +6228,6 @@ msgstr ""
msgid "ContainerRegistry|Sorry, your filter produced no results."
msgstr ""
-msgid "ContainerRegistry|Tag"
-msgstr ""
-
msgid "ContainerRegistry|Tag expiration policy"
msgstr ""
@@ -6199,21 +6372,6 @@ msgstr ""
msgid "Control the display of third party offers."
msgstr ""
-msgid "Control the maximum concurrency of LFS/attachment backfill for this secondary node"
-msgstr ""
-
-msgid "Control the maximum concurrency of container repository operations for this Geo node"
-msgstr ""
-
-msgid "Control the maximum concurrency of repository backfill for this secondary node"
-msgstr ""
-
-msgid "Control the maximum concurrency of verification operations for this Geo node"
-msgstr ""
-
-msgid "Control the minimum interval in days that a repository should be reverified for this primary node"
-msgstr ""
-
msgid "Cookie domain"
msgstr ""
@@ -6355,6 +6513,9 @@ msgstr ""
msgid "Could not find design."
msgstr ""
+msgid "Could not find iteration"
+msgstr ""
+
msgid "Could not remove the trigger."
msgstr ""
@@ -6391,6 +6552,12 @@ msgstr ""
msgid "Coverage"
msgstr ""
+msgid "Coverage Fuzzing"
+msgstr ""
+
+msgid "Crash State"
+msgstr ""
+
msgid "Create"
msgstr ""
@@ -6439,6 +6606,9 @@ msgstr ""
msgid "Create a personal access token on your account to pull or push via %{protocol}."
msgstr ""
+msgid "Create a project pre-populated with the necessary files to get you started quickly."
+msgstr ""
+
msgid "Create an account using:"
msgstr ""
@@ -6607,6 +6777,9 @@ msgstr ""
msgid "Creates branch '%{branch_name}' and a merge request to resolve this issue."
msgstr ""
+msgid "Creating"
+msgstr ""
+
msgid "Creating epic"
msgstr ""
@@ -6658,7 +6831,10 @@ msgstr ""
msgid "Current vulnerabilities count"
msgstr ""
-msgid "CurrentUser|Buy CI minutes"
+msgid "CurrentUser|Buy Pipeline minutes"
+msgstr ""
+
+msgid "CurrentUser|One of your groups is running out"
msgstr ""
msgid "CurrentUser|Profile"
@@ -6673,6 +6849,9 @@ msgstr ""
msgid "CurrentUser|Upgrade"
msgstr ""
+msgid "Custom Attributes"
+msgstr ""
+
msgid "Custom CI configuration path"
msgstr ""
@@ -6936,12 +7115,18 @@ msgstr ""
msgid "DAG"
msgstr ""
+msgid "DAG visualization requires at least 3 dependent jobs."
+msgstr ""
+
msgid "DNS"
msgstr ""
msgid "Dashboard"
msgstr ""
+msgid "Dashboard uid not found"
+msgstr ""
+
msgid "DashboardProjects|All"
msgstr ""
@@ -6966,6 +7151,9 @@ msgstr ""
msgid "Data is still calculating..."
msgstr ""
+msgid "Datasource name not found"
+msgstr ""
+
msgid "Date"
msgstr ""
@@ -7146,6 +7334,9 @@ msgstr ""
msgid "Delete this attachment"
msgstr ""
+msgid "Delete user list"
+msgstr ""
+
msgid "Delete variable"
msgstr ""
@@ -7462,6 +7653,9 @@ msgstr ""
msgid "Deploying to"
msgstr ""
+msgid "Deploying to AWS is easy with GitLab"
+msgstr ""
+
msgid "Deployment Frequency"
msgstr ""
@@ -7824,6 +8018,9 @@ msgstr ""
msgid "Dismissed on pipeline %{pipelineLink} at %{projectLink}"
msgstr ""
+msgid "Display alerts from all your monitoring tools directly within GitLab."
+msgstr ""
+
msgid "Display name"
msgstr ""
@@ -7881,9 +8078,6 @@ msgstr ""
msgid "Don't show again"
msgstr ""
-msgid "Don't worry, you can access this tour by clicking on the help icon in the top right corner and choose <strong>Learn GitLab</strong>."
-msgstr ""
-
msgid "Done"
msgstr ""
@@ -8010,6 +8204,9 @@ msgstr ""
msgid "Edit Release"
msgstr ""
+msgid "Edit Slack integration"
+msgstr ""
+
msgid "Edit Snippet"
msgstr ""
@@ -8049,6 +8246,9 @@ msgstr ""
msgid "Edit issues"
msgstr ""
+msgid "Edit iteration"
+msgstr ""
+
msgid "Edit public deploy key"
msgstr ""
@@ -8097,6 +8297,9 @@ msgstr ""
msgid "Email"
msgstr ""
+msgid "Email Notification"
+msgstr ""
+
msgid "Email address"
msgstr ""
@@ -8109,6 +8312,9 @@ msgstr ""
msgid "Email not verified. Please verify your email in Salesforce."
msgstr ""
+msgid "Email notification for unknown sign-ins"
+msgstr ""
+
msgid "Email patch"
msgstr ""
@@ -8340,6 +8546,9 @@ msgstr ""
msgid "Enforce DNS rebinding attack protection"
msgstr ""
+msgid "Enforce personal access token expiration"
+msgstr ""
+
msgid "Ensure connectivity is available from the GitLab server to the Prometheus server"
msgstr ""
@@ -8409,6 +8618,9 @@ msgstr ""
msgid "Enter the name of your application, and we'll return a unique %{type}."
msgstr ""
+msgid "Enter weights for storages for new repositories."
+msgstr ""
+
msgid "Enter your password to approve"
msgstr ""
@@ -8418,15 +8630,27 @@ msgstr ""
msgid "Environment does not have deployments"
msgstr ""
+msgid "Environment is required for Stages::MetricEndpointInserter"
+msgstr ""
+
+msgid "Environment is required for Stages::VariableEndpointInserter"
+msgstr ""
+
msgid "Environment scope"
msgstr ""
+msgid "Environment variables are applied to all project environments in this instance via the Runner. You can use environment variables for passwords, secret keys, etc. Make variables available to the running application by prepending the variable key with <code>K8S_SECRET_</code>. You can set variables to be:"
+msgstr ""
+
msgid "Environment variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. Additionally, they can be masked so they are hidden in job logs, though they must match certain regexp requirements to do so. You can use environment variables for passwords, secret keys, or whatever you want."
msgstr ""
msgid "Environment variables are configured by your administrator to be %{link_start}protected%{link_end} by default"
msgstr ""
+msgid "Environment variables on this GitLab instance are configured to be %{link_start}protected%{link_end} by default"
+msgstr ""
+
msgid "Environment:"
msgstr ""
@@ -8958,6 +9182,9 @@ msgstr ""
msgid "EventFilterBy|Filter by comments"
msgstr ""
+msgid "EventFilterBy|Filter by designs"
+msgstr ""
+
msgid "EventFilterBy|Filter by epic events"
msgstr ""
@@ -9090,15 +9317,15 @@ msgstr ""
msgid "Expand approvers"
msgstr ""
-msgid "Expand child epics"
-msgstr ""
-
msgid "Expand down"
msgstr ""
msgid "Expand dropdown"
msgstr ""
+msgid "Expand milestones"
+msgstr ""
+
msgid "Expand sidebar"
msgstr ""
@@ -9114,6 +9341,9 @@ msgstr ""
msgid "Expiration date"
msgstr ""
+msgid "Expiration not enforced"
+msgstr ""
+
msgid "Expiration policy for the Container Registry is a perfect solution for keeping the Registry space down while still enjoying the full power of GitLab CI/CD."
msgstr ""
@@ -9234,6 +9464,9 @@ msgstr ""
msgid "Failed Jobs"
msgstr ""
+msgid "Failed on"
+msgstr ""
+
msgid "Failed to add a Zoom meeting"
msgstr ""
@@ -9294,13 +9527,19 @@ msgstr ""
msgid "Failed to install."
msgstr ""
+msgid "Failed to load assignees. Please try again."
+msgstr ""
+
+msgid "Failed to load authors. Please try again."
+msgstr ""
+
msgid "Failed to load emoji list."
msgstr ""
msgid "Failed to load error details from Sentry."
msgstr ""
-msgid "Failed to load errors from Sentry. Error message: %{errorMessage}"
+msgid "Failed to load errors from Sentry."
msgstr ""
msgid "Failed to load group activity metrics. Please try again."
@@ -9381,6 +9620,9 @@ msgstr ""
msgid "Failed to set due date because the date format is invalid."
msgstr ""
+msgid "Failed to set iteration on this issue. Please try again."
+msgstr ""
+
msgid "Failed to signing using smartcard authentication"
msgstr ""
@@ -9603,13 +9845,10 @@ msgstr ""
msgid "FeatureFlags|Target environments"
msgstr ""
-msgid "FeatureFlags|There are no active feature flags"
-msgstr ""
-
-msgid "FeatureFlags|There are no inactive feature flags"
+msgid "FeatureFlags|There was an error fetching the feature flags."
msgstr ""
-msgid "FeatureFlags|There was an error fetching the feature flags."
+msgid "FeatureFlags|There was an error retrieving user lists"
msgstr ""
msgid "FeatureFlags|Try again in a few moments or contact your support team."
@@ -9621,9 +9860,18 @@ msgstr ""
msgid "FeatureFlag|Delete strategy"
msgstr ""
+msgid "FeatureFlag|List"
+msgstr ""
+
msgid "FeatureFlag|Percentage"
msgstr ""
+msgid "FeatureFlag|Select a user list"
+msgstr ""
+
+msgid "FeatureFlag|There are no configured user lists"
+msgstr ""
+
msgid "FeatureFlag|Type"
msgstr ""
@@ -9780,6 +10028,12 @@ msgstr ""
msgid "Filter..."
msgstr ""
+msgid "Find File"
+msgstr ""
+
+msgid "Find bugs in your code with coverage-guided fuzzing"
+msgstr ""
+
msgid "Find by path"
msgstr ""
@@ -10044,6 +10298,9 @@ msgstr ""
msgid "Geo allows you to replicate your GitLab instance to other geographical locations."
msgstr ""
+msgid "Geo nodes are paused using a command run on the node"
+msgstr ""
+
msgid "GeoNodeStatusEvent|%{timeAgoStr} (%{pendingEvents} events)"
msgstr ""
@@ -10128,6 +10385,9 @@ msgstr ""
msgid "GeoNodes|Not checksummed"
msgstr ""
+msgid "GeoNodes|Package files"
+msgstr ""
+
msgid "GeoNodes|Pausing replication stops the sync process. Are you sure?"
msgstr ""
@@ -10146,6 +10406,9 @@ msgstr ""
msgid "GeoNodes|Replication slots"
msgstr ""
+msgid "GeoNodes|Replication status"
+msgstr ""
+
msgid "GeoNodes|Repositories"
msgstr ""
@@ -10356,6 +10619,9 @@ msgstr ""
msgid "Geo|Synchronization failed - %{error}"
msgstr ""
+msgid "Geo|Synchronization of %{itemTitle} is disabled."
+msgstr ""
+
msgid "Geo|The database is currently %{db_lag} behind the primary node."
msgstr ""
@@ -10668,6 +10934,9 @@ msgstr ""
msgid "Go to %{link_to_google_takeout}."
msgstr ""
+msgid "Go to <strong>Issues</strong> > <strong>Boards</strong> to access your personalized learning issue board."
+msgstr ""
+
msgid "Go to Pipelines"
msgstr ""
@@ -10767,9 +11036,6 @@ msgstr ""
msgid "Go to your snippets"
msgstr ""
-msgid "Golden Tanuki"
-msgstr ""
-
msgid "Google Cloud Platform"
msgstr ""
@@ -10791,6 +11057,9 @@ msgstr ""
msgid "Grafana URL"
msgstr ""
+msgid "Grafana response contains invalid json"
+msgstr ""
+
msgid "GrafanaIntegration|API Token"
msgstr ""
@@ -10839,9 +11108,6 @@ msgstr ""
msgid "Group %{group_name} was successfully created."
msgstr ""
-msgid "Group '%{group_name}' is being imported."
-msgstr ""
-
msgid "Group Audit Events"
msgstr ""
@@ -10878,9 +11144,6 @@ msgstr ""
msgid "Group by:"
msgstr ""
-msgid "Group could not be imported: %{errors}"
-msgstr ""
-
msgid "Group description"
msgstr ""
@@ -10908,9 +11171,15 @@ msgstr ""
msgid "Group has not been marked for deletion"
msgstr ""
+msgid "Group import could not be scheduled"
+msgstr ""
+
msgid "Group info:"
msgstr ""
+msgid "Group is required when cluster_type is :group"
+msgstr ""
+
msgid "Group maintainers can register group runners in the %{link}"
msgstr ""
@@ -10974,6 +11243,24 @@ msgstr ""
msgid "GroupActivyMetrics|Recent activity (last 90 days)"
msgstr ""
+msgid "GroupImport|Failed to import group."
+msgstr ""
+
+msgid "GroupImport|Group '%{group_name}' is being imported."
+msgstr ""
+
+msgid "GroupImport|Group could not be imported: %{errors}"
+msgstr ""
+
+msgid "GroupImport|Please wait while we import the group for you. Refresh at will."
+msgstr ""
+
+msgid "GroupImport|The group was successfully imported."
+msgstr ""
+
+msgid "GroupImport|Unable to process group import file"
+msgstr ""
+
msgid "GroupRoadmap|%{dateWord} – No end date"
msgstr ""
@@ -11241,6 +11528,9 @@ msgstr ""
msgid "Groups (%{groups})"
msgstr ""
+msgid "Groups and projects"
+msgstr ""
+
msgid "Groups can also be nested by creating %{subgroup_docs_link_start}subgroups%{subgroup_docs_link_end}."
msgstr ""
@@ -11286,6 +11576,33 @@ msgstr ""
msgid "GroupsEmptyState|You can manage your group member’s permissions and access to each project in the group."
msgstr ""
+msgid "GroupsNew|Contact an administrator to enable options for importing your group."
+msgstr ""
+
+msgid "GroupsNew|Create"
+msgstr ""
+
+msgid "GroupsNew|Create group"
+msgstr ""
+
+msgid "GroupsNew|GitLab group export"
+msgstr ""
+
+msgid "GroupsNew|Import"
+msgstr ""
+
+msgid "GroupsNew|Import group"
+msgstr ""
+
+msgid "GroupsNew|My Awesome Group"
+msgstr ""
+
+msgid "GroupsNew|No import options available"
+msgstr ""
+
+msgid "GroupsNew|To copy a GitLab group between installations, navigate to the group settings page for the original installation, generate an export file, and upload it here."
+msgstr ""
+
msgid "GroupsTree|Are you sure you want to leave the \"%{fullName}\" group?"
msgstr ""
@@ -11388,6 +11705,9 @@ msgstr ""
msgid "Helps reduce request volume for protected paths"
msgstr ""
+msgid "Here are all your projects in your group, including the one you just created. To start, let’s take a look at your personalized learning project which will help you learn about GitLab at your own pace."
+msgstr ""
+
msgid "Here you will find recent merge request activity"
msgstr ""
@@ -11402,6 +11722,9 @@ msgid_plural "Hide charts"
msgstr[0] ""
msgstr[1] ""
+msgid "Hide details"
+msgstr ""
+
msgid "Hide file browser"
msgstr ""
@@ -11411,6 +11734,9 @@ msgstr ""
msgid "Hide host keys manual input"
msgstr ""
+msgid "Hide list"
+msgstr ""
+
msgid "Hide marketing-related entries from help"
msgstr ""
@@ -11431,9 +11757,6 @@ msgstr[1] ""
msgid "Hide values"
msgstr ""
-msgid "Hiding all labels"
-msgstr ""
-
msgid "High or unknown vulnerabilities present"
msgstr ""
@@ -11518,6 +11841,9 @@ msgstr ""
msgid "ID:"
msgstr ""
+msgid "IDE"
+msgstr ""
+
msgid "IDE|Allow live previews of JavaScript projects in the Web IDE using CodeSandbox Live Preview."
msgstr ""
@@ -11608,10 +11934,10 @@ msgstr ""
msgid "If enabled"
msgstr ""
-msgid "If enabled, access to projects will be validated on an external service using their classification label."
+msgid "If enabled, GitLab will handle Object Storage replication using Geo. %{linkStart}More information%{linkEnd}"
msgstr ""
-msgid "If enabled, and if object storage is enabled, GitLab will handle Object Storage replication using Geo"
+msgid "If enabled, access to projects will be validated on an external service using their classification label."
msgstr ""
msgid "If there is no previous license or if the previous license has expired, some GitLab functionality will be blocked until a new, valid license is uploaded."
@@ -11973,6 +12299,9 @@ msgid_plural "Instances"
msgstr[0] ""
msgstr[1] ""
+msgid "Instance Configuration"
+msgstr ""
+
msgid "Instance Statistics visibility"
msgstr ""
@@ -11994,9 +12323,6 @@ msgstr ""
msgid "Integrations"
msgstr ""
-msgid "Integrations allow you to integrate GitLab with other applications"
-msgstr ""
-
msgid "Integrations|All details"
msgstr ""
@@ -12108,6 +12434,9 @@ msgstr ""
msgid "Invalid query"
msgstr ""
+msgid "Invalid repository bundle for snippet with id %{snippet_id}"
+msgstr ""
+
msgid "Invalid repository path"
msgstr ""
@@ -12126,6 +12455,9 @@ msgstr ""
msgid "Invalid two-factor code."
msgstr ""
+msgid "Invalid yaml"
+msgstr ""
+
msgid "Invitation"
msgstr ""
@@ -12345,15 +12677,24 @@ msgstr ""
msgid "It seems like the Dependency Scanning job ran successfully, but no dependencies have been detected in your project."
msgstr ""
+msgid "It seems that there is currently no available data for code coverage"
+msgstr ""
+
msgid "It's you"
msgstr ""
+msgid "Iteration"
+msgstr ""
+
msgid "Iteration changed to"
msgstr ""
msgid "Iteration removed"
msgstr ""
+msgid "Iteration updated"
+msgstr ""
+
msgid "Iterations"
msgstr ""
@@ -12384,12 +12725,18 @@ msgstr ""
msgid "January"
msgstr ""
+msgid "Jira Issues"
+msgstr ""
+
msgid "Jira import is already running."
msgstr ""
msgid "Jira integration not configured."
msgstr ""
+msgid "Jira project key is not configured"
+msgstr ""
+
msgid "Jira project: %{importProject}"
msgstr ""
@@ -12570,6 +12917,9 @@ msgstr ""
msgid "Keep divergent refs"
msgstr ""
+msgid "Kerberos access denied"
+msgstr ""
+
msgid "Key"
msgstr ""
@@ -12579,10 +12929,13 @@ msgstr ""
msgid "Key: %{key}"
msgstr ""
-msgid "Keyboard Shortcuts"
+msgid "Keyboard shortcuts"
msgstr ""
-msgid "Keyboard shortcuts"
+msgid "Keys"
+msgstr ""
+
+msgid "Ki"
msgstr ""
msgid "Kubernetes"
@@ -12624,6 +12977,9 @@ msgstr ""
msgid "LDAP"
msgstr ""
+msgid "LDAP Synchronization"
+msgstr ""
+
msgid "LDAP settings"
msgstr ""
@@ -12848,12 +13204,18 @@ msgstr ""
msgid "Learn more about custom project templates"
msgstr ""
+msgid "Learn more about deploying to AWS"
+msgstr ""
+
msgid "Learn more about deploying to a cluster"
msgstr ""
msgid "Learn more about group-level project templates"
msgstr ""
+msgid "Learn more about job dependencies"
+msgstr ""
+
msgid "Learn more about signing commits"
msgstr ""
@@ -12905,9 +13267,21 @@ msgstr ""
msgid "License History"
msgstr ""
+msgid "License ID:"
+msgstr ""
+
+msgid "License URL"
+msgstr ""
+
msgid "License-Check"
msgstr ""
+msgid "LicenseCompliance|%{docLinkStart}License Approvals%{docLinkEnd} are active"
+msgstr ""
+
+msgid "LicenseCompliance|%{docLinkStart}License Approvals%{docLinkEnd} are inactive"
+msgstr ""
+
msgid "LicenseCompliance|Add a license"
msgstr ""
@@ -12929,9 +13303,15 @@ msgstr ""
msgid "LicenseCompliance|Deny"
msgstr ""
+msgid "LicenseCompliance|Learn more about %{linkStart}License Approvals%{linkEnd}"
+msgstr ""
+
msgid "LicenseCompliance|License"
msgstr ""
+msgid "LicenseCompliance|License Approvals"
+msgstr ""
+
msgid "LicenseCompliance|License Compliance detected %d license and policy violation for the source branch only; approval required"
msgid_plural "LicenseCompliance|License Compliance detected %d licenses and policy violations for the source branch only; approval required"
msgstr[0] ""
@@ -13152,6 +13532,9 @@ msgstr ""
msgid "List your Bitbucket Server repositories"
msgstr ""
+msgid "Lists"
+msgstr ""
+
msgid "Live preview"
msgstr ""
@@ -13335,6 +13718,9 @@ msgstr ""
msgid "Manage project labels"
msgstr ""
+msgid "Manage storage usage"
+msgstr ""
+
msgid "Manage two-factor authentication"
msgstr ""
@@ -13350,6 +13736,9 @@ msgstr ""
msgid "Manifest file import"
msgstr ""
+msgid "Manifest import"
+msgstr ""
+
msgid "Manual job"
msgstr ""
@@ -13497,6 +13886,12 @@ msgstr ""
msgid "Maximum field length"
msgstr ""
+msgid "Maximum file size is 2MB. Please select a smaller file."
+msgstr ""
+
+msgid "Maximum import size (MB)"
+msgstr ""
+
msgid "Maximum job timeout"
msgstr ""
@@ -13536,6 +13931,9 @@ msgstr ""
msgid "Maximum size of Elasticsearch bulk indexing requests."
msgstr ""
+msgid "Maximum size of import files."
+msgstr ""
+
msgid "Maximum size of individual attachments in comments."
msgstr ""
@@ -13587,6 +13985,9 @@ msgstr ""
msgid "Merge (when the pipeline succeeds)"
msgstr ""
+msgid "Merge Conflicts"
+msgstr ""
+
msgid "Merge Request"
msgstr ""
@@ -13713,6 +14114,9 @@ msgstr ""
msgid "MergeRequests|Squash task canceled: another squash is already in progress."
msgstr ""
+msgid "MergeRequests|This project does not allow squashing commits when merge requests are accepted."
+msgstr ""
+
msgid "MergeRequests|Thread stays resolved"
msgstr ""
@@ -13866,6 +14270,12 @@ msgstr ""
msgid "MetricsSettings|Add a button to the metrics dashboard linking directly to your existing external dashboard."
msgstr ""
+msgid "MetricsSettings|Choose whether to display dashboard metrics in UTC or the user's local timezone."
+msgstr ""
+
+msgid "MetricsSettings|Dashboard timezone"
+msgstr ""
+
msgid "MetricsSettings|External dashboard URL"
msgstr ""
@@ -13875,6 +14285,12 @@ msgstr ""
msgid "MetricsSettings|Metrics Dashboard"
msgstr ""
+msgid "MetricsSettings|UTC (Coordinated Universal Time)"
+msgstr ""
+
+msgid "MetricsSettings|User's local timezone"
+msgstr ""
+
msgid "Metrics|Add metric"
msgstr ""
@@ -13890,6 +14306,9 @@ msgstr ""
msgid "Metrics|Create metric"
msgstr ""
+msgid "Metrics|Current"
+msgstr ""
+
msgid "Metrics|Delete metric"
msgstr ""
@@ -13940,6 +14359,9 @@ msgstr ""
msgid "Metrics|Max"
msgstr ""
+msgid "Metrics|Min"
+msgstr ""
+
msgid "Metrics|Must be a valid PromQL query."
msgstr ""
@@ -13955,6 +14377,9 @@ msgstr ""
msgid "Metrics|Refresh dashboard"
msgstr ""
+msgid "Metrics|Select a value"
+msgstr ""
+
msgid "Metrics|Star dashboard"
msgstr ""
@@ -13973,12 +14398,18 @@ msgstr ""
msgid "Metrics|There was an error getting annotations information."
msgstr ""
+msgid "Metrics|There was an error getting dashboard validation warnings information."
+msgstr ""
+
msgid "Metrics|There was an error getting deployment information."
msgstr ""
msgid "Metrics|There was an error getting environments information."
msgstr ""
+msgid "Metrics|There was an error getting options for variable \"%{name}\"."
+msgstr ""
+
msgid "Metrics|There was an error trying to validate your query"
msgstr ""
@@ -14021,6 +14452,9 @@ msgstr ""
msgid "Metrics|You're about to permanently delete this metric. This cannot be undone."
msgstr ""
+msgid "Metrics|Your dashboard schema is invalid. Edit the dashboard to correct the YAML schema."
+msgstr ""
+
msgid "Metrics|e.g. HTTP requests"
msgstr ""
@@ -14036,12 +14470,18 @@ msgstr ""
msgid "Metrics|e.g. req/sec"
msgstr ""
+msgid "Mi"
+msgstr ""
+
msgid "Microsoft Azure"
msgstr ""
msgid "Middleman project with Static Site Editor support"
msgstr ""
+msgid "Migrate your data from an external source like GitHub, Bitbucket, or another instance of GitLab."
+msgstr ""
+
msgid "Migrated %{success_count}/%{total_count} files."
msgstr ""
@@ -14176,6 +14616,9 @@ msgstr ""
msgid "Minimum capacity to be available before we schedule more mirrors preemptively."
msgstr ""
+msgid "Minimum interval in days"
+msgstr ""
+
msgid "Minimum length is %{minimum_password_length} characters"
msgstr ""
@@ -14362,6 +14805,12 @@ msgstr ""
msgid "Multiple uploaders found: %{uploader_types}"
msgstr ""
+msgid "Must match with the %{codeStart}external_url%{codeEnd} in %{codeStart}/etc/gitlab/gitlab.rb%{codeEnd}."
+msgstr ""
+
+msgid "Must match with the %{codeStart}geo_node_name%{codeEnd} in %{codeStart}/etc/gitlab/gitlab.rb%{codeEnd}. %{linkStart}More information%{linkEnd}"
+msgstr ""
+
msgid "My Awesome Group"
msgstr ""
@@ -14476,10 +14925,16 @@ msgstr ""
msgid "New Application"
msgstr ""
+msgid "New Branch"
+msgstr ""
+
+msgid "New Deploy Key"
+msgstr ""
+
msgid "New Environment"
msgstr ""
-msgid "New Geo Node"
+msgid "New File"
msgstr ""
msgid "New Group"
@@ -14496,15 +14951,15 @@ msgid_plural "New Issues"
msgstr[0] ""
msgstr[1] ""
-msgid "New Iteration"
-msgstr ""
-
msgid "New Jira import"
msgstr ""
msgid "New Label"
msgstr ""
+msgid "New Merge Request"
+msgstr ""
+
msgid "New Milestone"
msgstr ""
@@ -14523,6 +14978,9 @@ msgstr ""
msgid "New Snippet"
msgstr ""
+msgid "New User"
+msgstr ""
+
msgid "New branch"
msgstr ""
@@ -14565,6 +15023,9 @@ msgstr ""
msgid "New issue title"
msgstr ""
+msgid "New iteration"
+msgstr ""
+
msgid "New iteration created"
msgstr ""
@@ -14742,6 +15203,12 @@ msgstr ""
msgid "No grouping"
msgstr ""
+msgid "No iteration"
+msgstr ""
+
+msgid "No iterations to show"
+msgstr ""
+
msgid "No job log"
msgstr ""
@@ -14784,6 +15251,9 @@ msgstr ""
msgid "No other labels with such name or description"
msgstr ""
+msgid "No panels matching properties %{opts}"
+msgstr ""
+
msgid "No parent group"
msgstr ""
@@ -14817,6 +15287,9 @@ msgstr ""
msgid "No schedules"
msgstr ""
+msgid "No stack trace for this error"
+msgstr ""
+
msgid "No starrers matched your search"
msgstr ""
@@ -14829,7 +15302,7 @@ msgstr ""
msgid "No test coverage"
msgstr ""
-msgid "No thanks, don't show this again"
+msgid "No thanks"
msgstr ""
msgid "No vulnerabilities present"
@@ -14844,9 +15317,6 @@ msgstr ""
msgid "No, directly import the existing email addresses and usernames."
msgstr ""
-msgid "No, not interested right now"
-msgstr ""
-
msgid "No. of commits"
msgstr ""
@@ -14892,9 +15362,6 @@ msgstr ""
msgid "Not found."
msgstr ""
-msgid "Not helpful"
-msgstr ""
-
msgid "Not now"
msgstr ""
@@ -15051,6 +15518,9 @@ msgstr ""
msgid "Notifications on"
msgstr ""
+msgid "Notify users by email when sign-in location is not recognized"
+msgstr ""
+
msgid "Nov"
msgstr ""
@@ -15117,9 +15587,6 @@ msgstr ""
msgid "Oh no!"
msgstr ""
-msgid "Ok let's go"
-msgstr ""
-
msgid "Oldest first"
msgstr ""
@@ -15132,16 +15599,49 @@ msgstr ""
msgid "On track"
msgstr ""
+msgid "OnDemandScans|Attached branch"
+msgstr ""
+
+msgid "OnDemandScans|Attached branch is where the scan job runs."
+msgstr ""
+
+msgid "OnDemandScans|Could not run the scan. Please try again."
+msgstr ""
+
msgid "OnDemandScans|Create new DAST scan"
msgstr ""
+msgid "OnDemandScans|DAST will scan the target URL and any discovered sub URLs."
+msgstr ""
+
+msgid "OnDemandScans|New on-demand DAST scan"
+msgstr ""
+
msgid "OnDemandScans|On-demand Scans"
msgstr ""
+msgid "OnDemandScans|On-demand scans run outside the DevOps cycle and find vulnerabilities in your projects. %{learnMoreLinkStart}Learn more%{learnMoreLinkEnd}"
+msgstr ""
+
+msgid "OnDemandScans|Only a passive scan can be performed on demand."
+msgstr ""
+
+msgid "OnDemandScans|Passive DAST Scan"
+msgstr ""
+
+msgid "OnDemandScans|Please enter a valid URL format, ex: http://www.example.com/home"
+msgstr ""
+
+msgid "OnDemandScans|Run this scan"
+msgstr ""
+
+msgid "OnDemandScans|Scan mode"
+msgstr ""
+
msgid "OnDemandScans|Schedule or run scans immediately against target sites. Currently available on-demand scan type: DAST. %{helpLinkStart}More information%{helpLinkEnd}"
msgstr ""
-msgid "Onboarding"
+msgid "OnDemandScans|Target URL"
msgstr ""
msgid "Once imported, repositories can be mirrored over SSH. Read more %{link_start}here%{link_end}."
@@ -15167,6 +15667,9 @@ msgstr ""
msgid "One or more of you personal access tokens were revoked"
msgstr ""
+msgid "One or more of your %{provider} projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
+msgstr ""
+
msgid "One or more of your Bitbucket projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
msgstr ""
@@ -15668,6 +16171,9 @@ msgstr ""
msgid "Parent epic is not present."
msgstr ""
+msgid "Parsing error for param :embed_json. %{message}"
+msgstr ""
+
msgid "Part of merge request changes"
msgstr ""
@@ -15680,6 +16186,9 @@ msgstr ""
msgid "Passed"
msgstr ""
+msgid "Passed on"
+msgstr ""
+
msgid "Password"
msgstr ""
@@ -15764,6 +16273,9 @@ msgstr ""
msgid "Perform common operations on GitLab project"
msgstr ""
+msgid "Performance and resource management"
+msgstr ""
+
msgid "Performance optimization"
msgstr ""
@@ -15971,7 +16483,7 @@ msgstr ""
msgid "Pipelines|Something went wrong while cleaning runners cache."
msgstr ""
-msgid "Pipelines|There are currently no %{scope} pipelines."
+msgid "Pipelines|There are currently no finished pipelines."
msgstr ""
msgid "Pipelines|There are currently no pipelines."
@@ -16082,6 +16594,9 @@ msgstr ""
msgid "Pipeline|Stop pipeline #%{pipelineId}?"
msgstr ""
+msgid "Pipeline|Tag name"
+msgstr ""
+
msgid "Pipeline|Trigger author"
msgstr ""
@@ -16163,12 +16678,18 @@ msgstr ""
msgid "Please check your email (%{email}) to verify that you own this address and unlock the power of CI/CD. Didn't receive it? %{resend_link}. Wrong email address? %{update_link}."
msgstr ""
+msgid "Please choose a file"
+msgstr ""
+
msgid "Please choose a group URL with no special characters."
msgstr ""
msgid "Please complete your profile with email address"
msgstr ""
+msgid "Please convert %{linkStart}them to Git%{linkEnd}, and go through the %{linkToImportFlow} again."
+msgstr ""
+
msgid "Please convert them to %{link_to_git}, and go through the %{link_to_import_flow} again."
msgstr ""
@@ -16226,6 +16747,9 @@ msgstr ""
msgid "Please provide a valid email address."
msgstr ""
+msgid "Please provide attributes to update"
+msgstr ""
+
msgid "Please refer to <a href=\"%{docs_url}\">%{docs_url}</a>"
msgstr ""
@@ -16961,6 +17485,9 @@ msgstr ""
msgid "Project has too many %{label_for_message} to search"
msgstr ""
+msgid "Project is required when cluster_type is :project"
+msgstr ""
+
msgid "Project members"
msgstr ""
@@ -17078,7 +17605,40 @@ msgstr ""
msgid "ProjectService|Comment"
msgstr ""
-msgid "ProjectService|Comment will be posted on each event"
+msgid "ProjectService|Event will be triggered by a push to the repository"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a commit is created/updated"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a confidential issue is created/updated/closed"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a deployment finishes"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a merge request is created/updated/merged"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a new tag is pushed to the repository"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a new, unique alert is recorded"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a pipeline status changes"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a wiki page is created/updated"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when an issue is created/updated/closed"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when someone adds a comment"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when someone adds a comment on a confidential issue"
msgstr ""
msgid "ProjectService|Perform common operations on GitLab project: %{project_name}"
@@ -17129,6 +17689,9 @@ msgstr ""
msgid "ProjectSettings|Enable 'Delete source branch' option by default"
msgstr ""
+msgid "ProjectSettings|Enable merge trains and pipelines for merged results"
+msgstr ""
+
msgid "ProjectSettings|Every merge creates a merge commit"
msgstr ""
@@ -17159,7 +17722,7 @@ msgstr ""
msgid "ProjectSettings|Forks"
msgstr ""
-msgid "ProjectSettings|Git Large File Storage"
+msgid "ProjectSettings|Git Large File Storage (LFS)"
msgstr ""
msgid "ProjectSettings|Internal"
@@ -17195,9 +17758,6 @@ msgstr ""
msgid "ProjectSettings|Merge options"
msgstr ""
-msgid "ProjectSettings|Merge pipelines will try to validate the post-merge result prior to merging"
-msgstr ""
-
msgid "ProjectSettings|Merge requests"
msgstr ""
@@ -17225,6 +17785,9 @@ msgstr ""
msgid "ProjectSettings|Pipelines"
msgstr ""
+msgid "ProjectSettings|Pipelines for merge requests must be enabled in the CI/CD configuration file, or pipelines could be unresolvable or dropped"
+msgstr ""
+
msgid "ProjectSettings|Pipelines must succeed"
msgstr ""
@@ -17252,6 +17815,9 @@ msgstr ""
msgid "ProjectSettings|Show link to create/view merge request when pushing from the command line"
msgstr ""
+msgid "ProjectSettings|Skipped pipelines are considered successful"
+msgstr ""
+
msgid "ProjectSettings|Snippets"
msgstr ""
@@ -17267,6 +17833,9 @@ msgstr ""
msgid "ProjectSettings|These checks must pass before merge requests can be merged"
msgstr ""
+msgid "ProjectSettings|This introduces the risk of merging changes that will not pass the pipeline."
+msgstr ""
+
msgid "ProjectSettings|This setting is applied on the server level and can be overridden by an admin."
msgstr ""
@@ -17444,15 +18013,27 @@ msgstr ""
msgid "ProjectsNew|Blank project"
msgstr ""
+msgid "ProjectsNew|Connect your external repository to GitLab CI/CD."
+msgstr ""
+
msgid "ProjectsNew|Contact an administrator to enable options for importing your project."
msgstr ""
msgid "ProjectsNew|Create"
msgstr ""
+msgid "ProjectsNew|Create a blank project to house your files, plan your work, and collaborate on code, among other things."
+msgstr ""
+
+msgid "ProjectsNew|Create blank project"
+msgstr ""
+
msgid "ProjectsNew|Create from template"
msgstr ""
+msgid "ProjectsNew|Create new project"
+msgstr ""
+
msgid "ProjectsNew|Creating project & repository."
msgstr ""
@@ -17477,6 +18058,9 @@ msgstr ""
msgid "ProjectsNew|Project description %{tag_start}(optional)%{tag_end}"
msgstr ""
+msgid "ProjectsNew|Run CI/CD for external repository"
+msgstr ""
+
msgid "ProjectsNew|Template"
msgstr ""
@@ -17738,6 +18322,9 @@ msgstr ""
msgid "Protected Branch"
msgstr ""
+msgid "Protected Branches"
+msgstr ""
+
msgid "Protected Environment"
msgstr ""
@@ -17750,6 +18337,9 @@ msgstr ""
msgid "Protected Tag"
msgstr ""
+msgid "Protected Tags"
+msgstr ""
+
msgid "Protected branches"
msgstr ""
@@ -17849,6 +18439,9 @@ msgstr ""
msgid "Provider"
msgstr ""
+msgid "Proxy support for this API is not available currently"
+msgstr ""
+
msgid "Pseudonymizer data collection"
msgstr ""
@@ -17873,6 +18466,9 @@ msgstr ""
msgid "Publish to status page"
msgstr ""
+msgid "Published on status page"
+msgstr ""
+
msgid "Publishes this issue to the associated status page."
msgstr ""
@@ -18047,6 +18643,9 @@ msgstr ""
msgid "Recipe"
msgstr ""
+msgid "Reconfigure"
+msgstr ""
+
msgid "Recover hidden stage"
msgstr ""
@@ -18133,6 +18732,9 @@ msgstr ""
msgid "Registration|Your profile"
msgstr ""
+msgid "Regulate approvals by authors/committers, based on compliance frameworks. Can be changed only at the instance level."
+msgstr ""
+
msgid "Rejected (closed)"
msgstr ""
@@ -18183,15 +18785,24 @@ msgstr ""
msgid "ReleaseAssetLinkType|Image"
msgstr ""
+msgid "ReleaseAssetLinkType|Images"
+msgstr ""
+
msgid "ReleaseAssetLinkType|Other"
msgstr ""
msgid "ReleaseAssetLinkType|Package"
msgstr ""
+msgid "ReleaseAssetLinkType|Packages"
+msgstr ""
+
msgid "ReleaseAssetLinkType|Runbook"
msgstr ""
+msgid "ReleaseAssetLinkType|Runbooks"
+msgstr ""
+
msgid "Releases"
msgstr ""
@@ -18204,6 +18815,9 @@ msgstr ""
msgid "Releases documentation"
msgstr ""
+msgid "Releases|New Release"
+msgstr ""
+
msgid "Release|Something went wrong while getting the release details"
msgstr ""
@@ -18276,6 +18890,9 @@ msgstr ""
msgid "Remove fork relationship"
msgstr ""
+msgid "Remove from batch"
+msgstr ""
+
msgid "Remove from board"
msgstr ""
@@ -18465,6 +19082,12 @@ msgstr ""
msgid "Replication"
msgstr ""
+msgid "Replication enabled"
+msgstr ""
+
+msgid "Replication paused"
+msgstr ""
+
msgid "Reply by email"
msgstr ""
@@ -18492,15 +19115,15 @@ msgstr ""
msgid "Reported %{timeAgo} by %{reportedBy}"
msgstr ""
-msgid "Reported Resource Changes: %{addNum} to add, %{changeNum} to change, %{deleteNum} to delete"
-msgstr ""
-
msgid "Reporter"
msgstr ""
msgid "Reporting"
msgstr ""
+msgid "Reports"
+msgstr ""
+
msgid "Reports|%{combinedString} and %{resolvedString}"
msgstr ""
@@ -18539,6 +19162,9 @@ msgstr ""
msgid "Reports|Failure"
msgstr ""
+msgid "Reports|Identifier"
+msgstr ""
+
msgid "Reports|Metrics reports are loading"
msgstr ""
@@ -18551,6 +19177,9 @@ msgstr ""
msgid "Reports|Metrics reports failed loading results"
msgstr ""
+msgid "Reports|Scanner"
+msgstr ""
+
msgid "Reports|Severity"
msgstr ""
@@ -18599,15 +19228,33 @@ msgstr ""
msgid "Repository cleanup has started. You will receive an email once the cleanup operation is complete."
msgstr ""
+msgid "Repository files count over the limit"
+msgstr ""
+
+msgid "Repository has an invalid default branch name."
+msgstr ""
+
+msgid "Repository has more than one branch."
+msgstr ""
+
msgid "Repository has no locks."
msgstr ""
+msgid "Repository has tags."
+msgstr ""
+
msgid "Repository maintenance"
msgstr ""
msgid "Repository mirroring"
msgstr ""
+msgid "Repository must contain at least 1 file."
+msgstr ""
+
+msgid "Repository size is above the limit."
+msgstr ""
+
msgid "Repository static objects"
msgstr ""
@@ -18617,7 +19264,7 @@ msgstr ""
msgid "Repository sync capacity"
msgstr ""
-msgid "Repository: %{counter_repositories} / Wikis: %{counter_wikis} / Build Artifacts: %{counter_build_artifacts} / LFS: %{counter_lfs_objects}"
+msgid "Repository: %{counter_repositories} / Wikis: %{counter_wikis} / Build Artifacts: %{counter_build_artifacts} / LFS: %{counter_lfs_objects} / Snippets: %{counter_snippets}"
msgstr ""
msgid "RepositorySettingsAccessLevel|Select"
@@ -18629,6 +19276,9 @@ msgstr ""
msgid "Request Headers"
msgstr ""
+msgid "Request details"
+msgstr ""
+
msgid "Request parameter %{param} is missing."
msgstr ""
@@ -18735,6 +19385,9 @@ msgstr ""
msgid "Reset template"
msgstr ""
+msgid "Reset to project defaults"
+msgstr ""
+
msgid "Resetting the authorization key for this project will require updating the authorization key in every alert source it is enabled in."
msgstr ""
@@ -18831,9 +19484,6 @@ msgstr ""
msgid "Resume"
msgstr ""
-msgid "Resume replication"
-msgstr ""
-
msgid "Resync"
msgstr ""
@@ -19456,6 +20106,9 @@ msgstr ""
msgid "SecurityReports|False positive"
msgstr ""
+msgid "SecurityReports|Group Security Dashboard"
+msgstr ""
+
msgid "SecurityReports|Hide dismissed"
msgstr ""
@@ -19510,10 +20163,13 @@ msgstr ""
msgid "SecurityReports|Remove project from dashboard"
msgstr ""
-msgid "SecurityReports|Report type"
+msgid "SecurityReports|Return to dashboard"
msgstr ""
-msgid "SecurityReports|Return to dashboard"
+msgid "SecurityReports|Scan details"
+msgstr ""
+
+msgid "SecurityReports|Scanner type"
msgstr ""
msgid "SecurityReports|Security Dashboard"
@@ -19570,6 +20226,9 @@ msgstr ""
msgid "SecurityReports|There was an error while generating the report."
msgstr ""
+msgid "SecurityReports|Unable to add %{invalidProjectsMessage}"
+msgstr ""
+
msgid "SecurityReports|Unable to add %{invalidProjects}"
msgstr ""
@@ -19684,6 +20343,9 @@ msgstr ""
msgid "Select due date"
msgstr ""
+msgid "Select file"
+msgstr ""
+
msgid "Select group or project"
msgstr ""
@@ -19747,9 +20409,6 @@ msgstr ""
msgid "Select the branch you want to set as the default for this project. All merge requests and commits will automatically be made against this branch unless you specify a different one."
msgstr ""
-msgid "Select the configured storage available for new repositories to be placed on."
-msgstr ""
-
msgid "Select the custom project template source group."
msgstr ""
@@ -19942,12 +20601,6 @@ msgstr ""
msgid "Service Desk is enabled but not yet active"
msgstr ""
-msgid "Service Desk is off"
-msgstr ""
-
-msgid "Service Desk is on"
-msgstr ""
-
msgid "Service Templates"
msgstr ""
@@ -20035,6 +20688,15 @@ msgstr ""
msgid "Set the milestone to %{milestone_reference}."
msgstr ""
+msgid "Set the number of concurrent requests this secondary node will make to the primary node while backfilling."
+msgstr ""
+
+msgid "Set the synchronization and verification capacity for the secondary node."
+msgstr ""
+
+msgid "Set the timeout in seconds to send a secondary node status to the primary and IPs allowed for the secondary nodes."
+msgstr ""
+
msgid "Set time estimate"
msgstr ""
@@ -20074,6 +20736,9 @@ msgstr ""
msgid "Set weight to %{weight}."
msgstr ""
+msgid "Set what should be replicated by choosing specific projects or groups by the secondary node."
+msgstr ""
+
msgid "SetPasswordToCloneLink|set a password"
msgstr ""
@@ -20200,6 +20865,9 @@ msgstr ""
msgid "Show complete raw log"
msgstr ""
+msgid "Show details"
+msgstr ""
+
msgid "Show file browser"
msgstr ""
@@ -20209,10 +20877,13 @@ msgstr ""
msgid "Show latest version"
msgstr ""
+msgid "Show list"
+msgstr ""
+
msgid "Show me everything"
msgstr ""
-msgid "Show me how"
+msgid "Show me how to add a pipeline"
msgstr ""
msgid "Show me more advanced stuff"
@@ -20261,9 +20932,6 @@ msgstr ""
msgid "Showing all issues"
msgstr ""
-msgid "Showing all labels"
-msgstr ""
-
msgid "Showing last %{size} of log -"
msgstr ""
@@ -20375,9 +21043,6 @@ msgstr ""
msgid "Skip outdated deployment jobs"
msgstr ""
-msgid "Skip this for now"
-msgstr ""
-
msgid "Skipped"
msgstr ""
@@ -20522,6 +21187,9 @@ msgstr ""
msgid "Something went wrong while adding your award. Please try again."
msgstr ""
+msgid "Something went wrong while applying the batch of suggestions. Please try again."
+msgstr ""
+
msgid "Something went wrong while applying the suggestion. Please try again."
msgstr ""
@@ -20624,10 +21292,13 @@ msgstr ""
msgid "Something went wrong while updating a requirement."
msgstr ""
+msgid "Something went wrong while updating assignees"
+msgstr ""
+
msgid "Something went wrong while updating your list settings"
msgstr ""
-msgid "Something went wrong with your automatic subscription renewal"
+msgid "Something went wrong with your automatic subscription renewal."
msgstr ""
msgid "Something went wrong, unable to add %{project} to dashboard"
@@ -20645,6 +21316,9 @@ msgstr ""
msgid "Something went wrong, unable to search projects"
msgstr ""
+msgid "Something went wrong."
+msgstr ""
+
msgid "Something went wrong. Please try again."
msgstr ""
@@ -20828,6 +21502,9 @@ msgstr ""
msgid "Source code"
msgstr ""
+msgid "Source code (%{fileExtension})"
+msgstr ""
+
msgid "Source is not available"
msgstr ""
@@ -20912,6 +21589,9 @@ msgstr ""
msgid "Stack trace"
msgstr ""
+msgid "Stacktrace snippet"
+msgstr ""
+
msgid "Stage"
msgstr ""
@@ -21017,6 +21697,9 @@ msgstr ""
msgid "Start thread & reopen %{noteable_name}"
msgstr ""
+msgid "Start using Directed Acyclic Graphs (DAG)"
+msgstr ""
+
msgid "Start your Free Gold Trial"
msgstr ""
@@ -21479,6 +22162,21 @@ msgstr ""
msgid "SuggestedColors|Very pale orange"
msgstr ""
+msgid "Suggestion is not applicable as the suggestion was not found."
+msgstr ""
+
+msgid "Suggestions are not applicable as one or more suggestions were not found."
+msgstr ""
+
+msgid "Suggestions are not applicable as their lines cannot overlap."
+msgstr ""
+
+msgid "Suggestions must all be on the same branch."
+msgstr ""
+
+msgid "Suggestions that change line count can't be added to batches, yet."
+msgstr ""
+
msgid "Suggestions:"
msgstr ""
@@ -21521,9 +22219,6 @@ msgstr ""
msgid "Synchronization disabled"
msgstr ""
-msgid "Synchronization of container repositories is disabled."
-msgstr ""
-
msgid "System"
msgstr ""
@@ -21704,6 +22399,34 @@ msgstr ""
msgid "Terms of Service and Privacy Policy"
msgstr ""
+msgid "Terraform|%{number} Terraform report failed to generate"
+msgid_plural "Terraform|%{number} Terraform reports failed to generate"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Terraform|%{number} Terraform report was generated in your pipelines"
+msgid_plural "Terraform|%{number} Terraform reports were generated in your pipelines"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Terraform|A Terraform report failed to generate."
+msgstr ""
+
+msgid "Terraform|A Terraform report was generated in your pipelines."
+msgstr ""
+
+msgid "Terraform|Generating the report caused an error."
+msgstr ""
+
+msgid "Terraform|Reported Resource Changes: %{addNum} to add, %{changeNum} to change, %{deleteNum} to delete"
+msgstr ""
+
+msgid "Terraform|The Terraform report %{name} failed to generate."
+msgstr ""
+
+msgid "Terraform|The Terraform report %{name} was generated in your pipelines."
+msgstr ""
+
msgid "Test"
msgstr ""
@@ -21730,7 +22453,7 @@ msgstr ""
msgid "TestHooks|Ensure the project has CI pipelines."
msgstr ""
-msgid "TestHooks|Ensure the project has at least one commit."
+msgid "TestHooks|Ensure the project has deployments."
msgstr ""
msgid "TestHooks|Ensure the project has issues."
@@ -21831,7 +22554,7 @@ msgstr ""
msgid "The Prometheus server responded with \"bad request\". Please check your queries are correct and are supported in your Prometheus version. %{documentationLink}"
msgstr ""
-msgid "The URL defined on the primary node that secondary nodes should use to contact it. Defaults to URL"
+msgid "The URL defined on the primary node that secondary nodes should use to contact it."
msgstr ""
msgid "The URL to use for connecting to Elasticsearch. Use a comma-separated list to support clustering (e.g., \"http://localhost:9200, http://localhost:9201\")."
@@ -21840,6 +22563,9 @@ msgstr ""
msgid "The X509 Certificate to use when mutual TLS is required to communicate with the external authorization service. If left blank, the server certificate is still validated when accessing over HTTPS."
msgstr ""
+msgid "The above settings apply to all projects with the selected compliance framework(s)."
+msgstr ""
+
msgid "The amount of seconds after which a request to get a secondary node status will time out."
msgstr ""
@@ -22139,9 +22865,6 @@ msgstr ""
msgid "The total stage shows the time it takes between creating an issue and deploying the code to production. The data will be automatically added once you have completed the full idea to production cycle."
msgstr ""
-msgid "The unique identifier for the Geo node. Must match %{geoNodeName} if it is set in gitlab.rb, otherwise it must match %{externalUrl} with a trailing slash"
-msgstr ""
-
msgid "The update action will time out after %{number_of_minutes} minutes. For big repositories, use a clone/push combination."
msgstr ""
@@ -22166,9 +22889,6 @@ msgstr ""
msgid "The user you are trying to deactivate has been active in the past %{minimum_inactive_days} days and cannot be deactivated"
msgstr ""
-msgid "The user-facing URL of the Geo node"
-msgstr ""
-
msgid "The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6."
msgstr ""
@@ -22259,6 +22979,9 @@ msgstr ""
msgid "There was a problem fetching project branches."
msgstr ""
+msgid "There was a problem fetching project tags."
+msgstr ""
+
msgid "There was a problem fetching project users."
msgstr ""
@@ -22310,6 +23033,9 @@ msgstr ""
msgid "There was an error fetching the %{replicableType}"
msgstr ""
+msgid "There was an error fetching the Geo Settings"
+msgstr ""
+
msgid "There was an error fetching the Node's Groups"
msgstr ""
@@ -22430,6 +23156,12 @@ msgstr ""
msgid "This %{issuable} is locked. Only <strong>project members</strong> can comment."
msgstr ""
+msgid "This %{noteableTypeText} is %{confidentialLinkStart}confidential%{linkEnd} and %{lockedLinkStart}locked%{linkEnd}."
+msgstr ""
+
+msgid "This %{noteableTypeText} is locked."
+msgstr ""
+
msgid "This %{viewer} could not be displayed because %{reason}. You can %{options} instead."
msgstr ""
@@ -22448,6 +23180,9 @@ msgstr ""
msgid "This action can lead to data loss. To prevent accidental actions we ask you to confirm your intention."
msgstr ""
+msgid "This also resolves all related threads"
+msgstr ""
+
msgid "This also resolves the discussion"
msgstr ""
@@ -22538,6 +23273,9 @@ msgstr ""
msgid "This epic does not exist or you don't have sufficient permission."
msgstr ""
+msgid "This feature is currently in beta. We invite you to %{linkStart}give feedback%{linkEnd}."
+msgstr ""
+
msgid "This feature requires local storage to be enabled"
msgstr ""
@@ -22571,7 +23309,7 @@ msgstr ""
msgid "This is a Work in Progress"
msgstr ""
-msgid "This is a confidential issue."
+msgid "This is a confidential %{noteableTypeText}."
msgstr ""
msgid "This is a delayed job to run in %{remainingTime}"
@@ -22598,9 +23336,6 @@ msgstr ""
msgid "This is your current session"
msgstr ""
-msgid "This issue is %{confidentialLinkStart}confidential%{linkEnd} and %{lockedLinkStart}locked%{linkEnd}."
-msgstr ""
-
msgid "This issue is confidential"
msgstr ""
@@ -22610,9 +23345,6 @@ msgstr ""
msgid "This issue is in a child epic of the filtered epic"
msgstr ""
-msgid "This issue is locked."
-msgstr ""
-
msgid "This job depends on other jobs with expired/erased artifacts: %{invalid_dependencies}"
msgstr ""
@@ -22712,6 +23444,9 @@ msgstr ""
msgid "This license has already expired."
msgstr ""
+msgid "This link points to external content"
+msgstr ""
+
msgid "This may expose confidential information as the selected fork is in another namespace that can have other members."
msgstr ""
@@ -22739,6 +23474,9 @@ msgstr ""
msgid "This page will be removed in a future release."
msgstr ""
+msgid "This pipeline does not use the %{codeStart}needs%{codeEnd} keyword and can't be represented as a directed acyclic graph."
+msgstr ""
+
msgid "This pipeline makes use of a predefined CI/CD configuration enabled by %{strongStart}Auto DevOps.%{strongEnd}"
msgstr ""
@@ -22967,6 +23705,9 @@ msgstr ""
msgid "Time from last commit to merge"
msgstr ""
+msgid "Time in seconds"
+msgstr ""
+
msgid "Time in seconds GitLab will wait for a response from the external service. When the service does not respond in time, access will be denied."
msgstr ""
@@ -23388,9 +24129,15 @@ msgstr ""
msgid "Total artifacts size: %{total_size}"
msgstr ""
+msgid "Total cores (CPUs)"
+msgstr ""
+
msgid "Total issues"
msgstr ""
+msgid "Total memory (GB)"
+msgstr ""
+
msgid "Total test time for all commits/merges"
msgstr ""
@@ -23592,6 +24339,9 @@ msgstr ""
msgid "U2F only works with HTTPS-enabled websites. Contact your administrator for more details."
msgstr ""
+msgid "UI Development Kit"
+msgstr ""
+
msgid "URL"
msgstr ""
@@ -23664,9 +24414,6 @@ msgstr ""
msgid "Unable to load the merge request widget. Try reloading the page."
msgstr ""
-msgid "Unable to process group import file"
-msgstr ""
-
msgid "Unable to resolve"
msgstr ""
@@ -23682,6 +24429,9 @@ msgstr ""
msgid "Unable to sign you in to the group with SAML due to \"%{reason}\""
msgstr ""
+msgid "Unable to suggest a path. Please refresh and try again."
+msgstr ""
+
msgid "Unable to update label prioritization at this time"
msgstr ""
@@ -23697,6 +24447,9 @@ msgstr ""
msgid "Unarchiving the project will restore people's ability to make changes to it. The repository can be committed to, and issues, comments, and other entities can be created. %{strong_start}Once active, this project shows up in the search and on the dashboard.%{strong_end}"
msgstr ""
+msgid "Unassign from commenting user"
+msgstr ""
+
msgid "Unblock"
msgstr ""
@@ -23772,6 +24525,9 @@ msgstr ""
msgid "Unreachable"
msgstr ""
+msgid "Unrecognized cluster type"
+msgstr ""
+
msgid "Unresolve"
msgstr ""
@@ -23832,6 +24588,9 @@ msgstr ""
msgid "Unsubscribes from this %{quick_action_target}."
msgstr ""
+msgid "Unsupported todo type passed. Supported todo types are: %{todo_types}"
+msgstr ""
+
msgid "Until"
msgstr ""
@@ -23859,6 +24618,9 @@ msgstr ""
msgid "Update approval rule"
msgstr ""
+msgid "Update approvers"
+msgstr ""
+
msgid "Update failed"
msgstr ""
@@ -23868,6 +24630,9 @@ msgstr ""
msgid "Update it"
msgstr ""
+msgid "Update iteration"
+msgstr ""
+
msgid "Update now"
msgstr ""
@@ -23913,7 +24678,7 @@ msgstr ""
msgid "Updated at"
msgstr ""
-msgid "Updated to"
+msgid "Updated to %{linkStart}chart v%{linkEnd}"
msgstr ""
msgid "Updating"
@@ -23943,6 +24708,9 @@ msgstr ""
msgid "Upload CSV file"
msgstr ""
+msgid "Upload License"
+msgstr ""
+
msgid "Upload New File"
msgstr ""
@@ -24024,16 +24792,19 @@ msgstr ""
msgid "UsageQuota|Pipelines"
msgstr ""
+msgid "UsageQuota|Purchase more storage"
+msgstr ""
+
msgid "UsageQuota|Repositories"
msgstr ""
msgid "UsageQuota|Repository"
msgstr ""
-msgid "UsageQuota|Storage"
+msgid "UsageQuota|Snippets"
msgstr ""
-msgid "UsageQuota|Storage usage:"
+msgid "UsageQuota|Storage"
msgstr ""
msgid "UsageQuota|This namespace has no projects which use shared runners"
@@ -24066,6 +24837,12 @@ msgstr ""
msgid "UsageQuota|Wikis"
msgstr ""
+msgid "UsageQuota|You used: %{usage} %{limit}"
+msgstr ""
+
+msgid "UsageQuota|out of %{formattedLimit} of your namespace storage"
+msgstr ""
+
msgid "Use %{code_start}::%{code_end} to create a %{link_start}scoped label set%{link_end} (eg. %{code_start}priority::1%{code_end})"
msgstr ""
@@ -24132,6 +24909,9 @@ msgstr ""
msgid "User IDs"
msgstr ""
+msgid "User Lists can only be created and modified with %{linkStart}the API%{linkEnd}"
+msgstr ""
+
msgid "User OAuth applications"
msgstr ""
@@ -24156,6 +24936,9 @@ msgstr ""
msgid "User key was successfully removed."
msgstr ""
+msgid "User list %{name} will be removed. Are you sure?"
+msgstr ""
+
msgid "User map"
msgstr ""
@@ -24177,148 +24960,10 @@ msgstr ""
msgid "User was successfully updated."
msgstr ""
-msgid "UserOnboardingTour|%{activeTour}/%{totalTours}"
-msgstr ""
-
-msgid "UserOnboardingTour|%{completed}/%{total} steps completed"
-msgstr ""
-
-msgid "UserOnboardingTour|%{emphasisStart}Well done!%{emphasisEnd}%{lineBreak}%{lineBreak}That's it for our guided tour, congratulations for making it all the way to the end!%{lineBreak}%{lineBreak}We hope this gave you a good overview of GitLab and how it can help you. We'll now show you how to create your own project and invite your colleagues."
-msgstr ""
-
-msgid "UserOnboardingTour|Adding other members to a project is done through Project Settings. Click on %{emphasisStart}Settings%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Alright, that's it for Commits. Let's take a look at the %{emphasisStart}Branches%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Awesome! Now click on %{emphasisStart}Members%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Click on one of the %{emphasisStart}Compare%{emphasisEnd} buttons to compare a branch to master."
-msgstr ""
-
-msgid "UserOnboardingTour|Click on one of the %{emphasisStart}pipeline IDs%{emphasisEnd} to see the details of a pipeline."
-msgstr ""
-
-msgid "UserOnboardingTour|Click to open the latest commit to see its details."
-msgstr ""
-
-msgid "UserOnboardingTour|Close 'Learn GitLab'"
-msgstr ""
-
-msgid "UserOnboardingTour|Commits are shown in chronological order and can be filtered by the commit message or by the branch."
+msgid "UserList|Delete %{name}?"
msgstr ""
-msgid "UserOnboardingTour|Create a project"
-msgstr ""
-
-msgid "UserOnboardingTour|Exit 'Learn GitLab'"
-msgstr ""
-
-msgid "UserOnboardingTour|Got it"
-msgstr ""
-
-msgid "UserOnboardingTour|Great job! %{clapHands} We hope the tour was helpful and that you learned how to use GitLab.%{lineBreak}%{lineBreak}We'd love to get your feedback on this tour.%{lineBreak}%{lineBreak}%{emphasisStart}How helpful would you say this guided tour was?%{emphasisEnd}%{lineBreak}%{lineBreak}"
-msgstr ""
-
-msgid "UserOnboardingTour|Guided GitLab Tour"
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can compare the changes of this branch to another one. Changes are divided by files so that it's easier to see what was changed where."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can create a project from scratch, start with a template or import a repository from other platforms. Whatever you choose, we'll guide you through the process.%{lineBreak}%{lineBreak}Fill in your new project information and click on %{emphasisStart}Create Project%{emphasisEnd} to progress to the next step."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can see the breakdown of the pipelines: its stages and jobs in each of the stages and their status.%{lineBreak}%{lineBreak}Our CI/CD pipelines are quite complex, most of our users have fewer and simpler pipelines."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can see the current members of the project (just you at the moment) and invite new members.%{lineBreak}%{lineBreak}You can invite multiple members at once (existing GitLab users or invite by email) and you can also set their roles and permissions.%{lineBreak}%{lineBreak}Add a few members and click on %{emphasisStart}Add to project%{emphasisEnd} to complete this step."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can see what changes were made with this commit, on what branch and if there's a related merge request. The status of the pipeline will also show up if CI/CD is set up.%{lineBreak}%{lineBreak}You can also comment on the lines of code that were changed and start a discussion with your colleagues!"
-msgstr ""
-
-msgid "UserOnboardingTour|Here's an overview of branches in the %{emphasisStart}%{projectName}%{emphasisEnd} project. They're split into Active and Stale.%{lineBreak}%{lineBreak}From here, you can create a new merge request from a branch, or compare the branch to any other branch in the project. By default, it will compare it to the master branch."
-msgstr ""
-
-msgid "UserOnboardingTour|Invite colleagues"
-msgstr ""
-
-msgid "UserOnboardingTour|Issues are great for communicating and keeping track of progress in GitLab. These are all issues that are open in the %{emphasisStart}%{projectName}%{emphasisEnd}.%{lineBreak}%{lineBreak}You can help us improve GitLab by contributing work to issues that are labeled <span class=\"badge color-label accept-mr-label\">Accepting merge requests</span>.%{lineBreak}%{lineBreak}This list can be filtered by labels, milestones, assignees, authors... We'll show you how it looks when the list is filtered by a label."
-msgstr ""
-
-msgid "UserOnboardingTour|Learn GitLab"
-msgstr ""
-
-msgid "UserOnboardingTour|Let's take a closer look at a merge request. Click on the title of one."
-msgstr ""
-
-msgid "UserOnboardingTour|Let's take a closer look at all the commits. Click on %{emphasisStart}Commits%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Let's take a closer look at the repository of this project. Click on %{emphasisStart}Repository%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|No thanks"
-msgstr ""
-
-msgid "UserOnboardingTour|Ok, let's go"
-msgstr ""
-
-msgid "UserOnboardingTour|Ok, show me"
-msgstr ""
-
-msgid "UserOnboardingTour|Open one of the issues by clicking on its title."
-msgstr ""
-
-msgid "UserOnboardingTour|Restart this step"
-msgstr ""
-
-msgid "UserOnboardingTour|Skip this step"
-msgstr ""
-
-msgid "UserOnboardingTour|Sweet! Your project was created and is ready to be used.%{lineBreak}%{lineBreak}You can start adding files to the repository or clone it. One last thing we want to show you is how to invite your colleagues to your new project."
-msgstr ""
-
-msgid "UserOnboardingTour|Take a look. Here's a nifty menu for quickly creating issues, merge requests, snippets, projects and groups. Click on it and select \"New project\" from the \"GitLab\" section to get started."
-msgstr ""
-
-msgid "UserOnboardingTour|Thanks for taking the guided tour. Remember, if you want to go through it again, you can start %{emphasisStart}Learn GitLab%{emphasisEnd} in the help menu on the top right."
-msgstr ""
-
-msgid "UserOnboardingTour|Thanks for the feedback! %{thumbsUp}"
-msgstr ""
-
-msgid "UserOnboardingTour|That's it for issues. Let'st take a look at %{emphasisStart}Merge Requests%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|That's it for merge requests. Now for the final part of this guided tour - the %{emphasisStart}CI/CD%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|That's it for the Repository. Let's take a look at the %{emphasisStart}Issues%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|The structure of this page is very similar to issues. Status, description, discussion and the sidebar are all here.%{lineBreak}%{lineBreak}But take a look below the description and you'll notice that there's more information about the merge request, the CI/CD pipeline and the options for approving it.%{lineBreak}%{lineBreak}Alongside the discussion you can also see more information about commits in this merge request, the status of pipelines and review all changes that were made."
-msgstr ""
-
-msgid "UserOnboardingTour|There's a lot of information here but don't worry, we'll go through it.%{lineBreak}%{lineBreak}On the top you can see the status of the issue and when it was opened and by whom. Directly below it is the issue description and below that are other %{emphasisStart}related issues%{emphasisEnd} and %{emphasisStart}merge requests%{emphasisEnd} (if any). Then below that is the %{emphasisStart}discussion%{emphasisEnd}, that's where most of the communication happens.%{lineBreak}%{lineBreak}On the right, there's a sidebar where you can view/change the %{emphasisStart}assignee, milestone, due date, labels, weight%{emphasisEnd}, etc."
-msgstr ""
-
-msgid "UserOnboardingTour|These are all the CI/CD pipelines we have for our %{emphasisStart}%{projectName}%{emphasisEnd} project.%{lineBreak}%{lineBreak}Here you can see the status of each pipeline, for what commit it's running for, its stages and the status for them."
-msgstr ""
-
-msgid "UserOnboardingTour|These are all the issues that are available for community contributions. Let's take a closer look at one of them."
-msgstr ""
-
-msgid "UserOnboardingTour|This is an overview of all merge requests in this project. Similarly to the issues overview it can be filtered down by things like labels, milestones, authors, assignees, etc."
-msgstr ""
-
-msgid "UserOnboardingTour|This is the repository for the %{emphasisStart}%{projectName}%{emphasisEnd} project. All our code is stored here. Feel free to explore and take a closer look at folders and files.%{lineBreak}%{lineBreak}Above the file structure you can see the latest commit, who the author is and the status of the CI/CD pipeline.%{lineBreak}%{lineBreak}If you scroll down below the file structure, you'll find the Readme of this project. This is defined in the README.md file at the root of the repository."
-msgstr ""
-
-msgid "UserOnboardingTour|Welcome to the project overview of the %{emphasisStart}%{projectName}%{emphasisEnd} project. This is the project that we use to work on GitLab. At first, a project seems like a simple repository, but at GitLab, a project is so much more.%{lineBreak}%{lineBreak}You can create projects for hosting your codebase, use it as an issue tracker, collaborate on code, and continuously build, test, and deploy your app with built-in GitLab CI/CD."
+msgid "UserList|created %{timeago}"
msgstr ""
msgid "UserProfile|Activity"
@@ -24471,6 +25116,9 @@ msgstr ""
msgid "UsersSelect|Unassigned"
msgstr ""
+msgid "Using %{codeStart}needs%{codeEnd} allows jobs to run before their stage is reached, as soon as their individual dependencies are met, which speeds up your pipelines."
+msgstr ""
+
msgid "Using %{code_start}::%{code_end} denotes a %{link_start}scoped label set%{link_end}"
msgstr ""
@@ -24555,9 +25203,6 @@ msgstr ""
msgid "Versions"
msgstr ""
-msgid "Very helpful"
-msgstr ""
-
msgid "View Documentation"
msgstr ""
@@ -24706,6 +25351,9 @@ msgstr ""
msgid "VisibilityLevel|Unknown"
msgstr ""
+msgid "Visit settings page"
+msgstr ""
+
msgid "VisualReviewApp|%{stepStart}Step 1%{stepEnd}. Copy the following script:"
msgstr ""
@@ -24799,6 +25447,9 @@ msgstr ""
msgid "VulnerabilityManagement|Something went wrong while trying to delete the comment. Please try again later."
msgstr ""
+msgid "VulnerabilityManagement|Something went wrong while trying to refresh the vulnerability. Please try again later."
+msgstr ""
+
msgid "VulnerabilityManagement|Something went wrong while trying to retrieve the vulnerability history. Please try again later."
msgstr ""
@@ -24841,6 +25492,9 @@ msgstr ""
msgid "Vulnerability|Class"
msgstr ""
+msgid "Vulnerability|Crash Address"
+msgstr ""
+
msgid "Vulnerability|Description"
msgstr ""
@@ -24916,9 +25570,6 @@ msgstr ""
msgid "We couldn't reach the Prometheus server. Either the server no longer exists or the configuration details need updating."
msgstr ""
-msgid "We created a short guided tour that will help you learn the basics of GitLab and how it will help you be better at your job. It should only take a couple of minutes. You will be guided by two types of helpers, best recognized by their color."
-msgstr ""
-
msgid "We detected potential spam in the %{humanized_resource_name}. Please solve the reCAPTCHA to proceed."
msgstr ""
@@ -24952,6 +25603,9 @@ msgstr ""
msgid "We've found no vulnerabilities"
msgstr ""
+msgid "Web Application Firewall"
+msgstr ""
+
msgid "Web IDE"
msgstr ""
@@ -25012,9 +25666,6 @@ msgstr ""
msgid "Welcome to GitLab.com<br>@%{name}!"
msgstr ""
-msgid "Welcome to the Guided GitLab Tour"
-msgstr ""
-
msgid "Welcome to the guided GitLab tour"
msgstr ""
@@ -25062,9 +25713,6 @@ msgstr ""
msgid "While it's rare to have no vulnerabilities, it can happen. In any event, we ask that you please double check your settings to make sure you've set up your dashboard correctly."
msgstr ""
-msgid "White helpers give contextual information."
-msgstr ""
-
msgid "Who can be an approver?"
msgstr ""
@@ -25110,12 +25758,18 @@ msgstr ""
msgid "WikiEmptyIssueMessage|Suggest wiki improvement"
msgstr ""
+msgid "WikiEmptyIssueMessage|You must be a group member in order to add wiki pages. If you have suggestions for how to improve the wiki for this group, consider opening an issue in the %{issues_link}."
+msgstr ""
+
msgid "WikiEmptyIssueMessage|You must be a project member in order to add wiki pages. If you have suggestions for how to improve the wiki for this project, consider opening an issue in the %{issues_link}."
msgstr ""
msgid "WikiEmptyIssueMessage|issue tracker"
msgstr ""
+msgid "WikiEmpty|A wiki is where you can store all the details about your group. This can include why you've created it, its principles, how to use it, and so on."
+msgstr ""
+
msgid "WikiEmpty|A wiki is where you can store all the details about your project. This can include why you've created it, its principles, how to use it, and so on."
msgstr ""
@@ -25125,12 +25779,21 @@ msgstr ""
msgid "WikiEmpty|Suggest wiki improvement"
msgstr ""
+msgid "WikiEmpty|The wiki lets you write documentation for your group"
+msgstr ""
+
msgid "WikiEmpty|The wiki lets you write documentation for your project"
msgstr ""
+msgid "WikiEmpty|This group has no wiki pages"
+msgstr ""
+
msgid "WikiEmpty|This project has no wiki pages"
msgstr ""
+msgid "WikiEmpty|You must be a group member in order to add wiki pages."
+msgstr ""
+
msgid "WikiEmpty|You must be a project member in order to add wiki pages."
msgstr ""
@@ -25407,13 +26070,13 @@ msgstr ""
msgid "You can now export your security dashboard to a CSV report."
msgstr ""
-msgid "You can now submit a merge request to get this change into the original branch."
+msgid "You can now manage alert endpoint configuration in the Alerts section on the Operations settings page. Fields on this page have been deprecated."
msgstr ""
-msgid "You can now submit a merge request to get this change into the original project."
+msgid "You can now submit a merge request to get this change into the original branch."
msgstr ""
-msgid "You can only add files when you are on a branch"
+msgid "You can now submit a merge request to get this change into the original project."
msgstr ""
msgid "You can only edit files when you are on a branch"
@@ -25635,7 +26298,7 @@ msgstr ""
msgid "You need to upload a Google Takeout archive."
msgstr ""
-msgid "You reached %{usage_in_percent} of %{namespace_name}'s capacity (%{used_storage} of %{storage_limit})"
+msgid "You reached %{usage_in_percent} of %{namespace_name}'s storage capacity (%{used_storage} of %{storage_limit})"
msgstr ""
msgid "You tried to fork %{link_to_the_project} but it failed for the following reason:"
@@ -25938,13 +26601,13 @@ msgstr ""
msgid "Your subscription expired!"
msgstr ""
-msgid "Your subscription has been downgraded"
+msgid "Your subscription has been downgraded."
msgstr ""
-msgid "Your subscription will automatically renew in %{remaining_days}"
+msgid "Your subscription will automatically renew in %{remaining_days}."
msgstr ""
-msgid "Your subscription will expire in %{remaining_days}"
+msgid "Your subscription will expire in %{remaining_days}."
msgstr ""
msgid "Zoom meeting added"
@@ -26024,9 +26687,6 @@ msgstr ""
msgid "branch name"
msgstr ""
-msgid "build pipeline reference mismatch"
-msgstr ""
-
msgid "by"
msgstr ""
@@ -26045,6 +26705,9 @@ msgstr ""
msgid "cannot block others"
msgstr ""
+msgid "cannot contain HTML/XML tags, including any word between angle brackets (<,>)."
+msgstr ""
+
msgid "cannot include leading slash or directory traversal."
msgstr ""
@@ -26075,55 +26738,6 @@ msgstr ""
msgid "ciReport|%{remainingPackagesCount} more"
msgstr ""
-msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability"
-msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability for the source branch only"
-msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities for the source branch only"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerability"
-msgid_plural "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerabilities"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new vulnerability"
-msgid_plural "ciReport|%{reportType} %{status} detected %{newCount} new vulnerabilities"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities for the source branch only"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{fixedCount} fixed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} vulnerability for the source branch only"
-msgid_plural "ciReport|%{reportType} %{status} detected %{newCount} vulnerabilities for the source branch only"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "ciReport|%{reportType} %{status} detected no new vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected no vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected no vulnerabilities for the source branch only"
-msgstr ""
-
msgid "ciReport|%{reportType} is loading"
msgstr ""
@@ -26142,7 +26756,7 @@ msgstr ""
msgid "ciReport|All projects"
msgstr ""
-msgid "ciReport|All report types"
+msgid "ciReport|All scanner types"
msgstr ""
msgid "ciReport|All severities"
@@ -26166,6 +26780,9 @@ msgstr ""
msgid "ciReport|Container scanning detects known vulnerabilities in your docker images."
msgstr ""
+msgid "ciReport|Coverage Fuzzing"
+msgstr ""
+
msgid "ciReport|Create a merge request to implement this solution, or download and apply the patch manually."
msgstr ""
@@ -26324,6 +26941,9 @@ msgstr ""
msgid "customize"
msgstr ""
+msgid "data"
+msgstr ""
+
msgid "date must not be after 9999-12-31"
msgstr ""
@@ -26394,6 +27014,9 @@ msgstr ""
msgid "entries cannot contain HTML tags"
msgstr ""
+msgid "epic"
+msgstr ""
+
msgid "error"
msgstr ""
@@ -26415,9 +27038,6 @@ msgstr ""
msgid "expires on %{timebox_due_date}"
msgstr ""
-msgid "external_url"
-msgstr ""
-
msgid "failed"
msgstr ""
@@ -26453,9 +27073,6 @@ msgstr ""
msgid "from"
msgstr ""
-msgid "geo_node_name"
-msgstr ""
-
msgid "group"
msgstr ""
@@ -26477,9 +27094,6 @@ msgstr ""
msgid "https://your-bitbucket-server"
msgstr ""
-msgid "image"
-msgstr ""
-
msgid "image diff"
msgstr ""
@@ -26623,6 +27237,9 @@ msgstr ""
msgid "load it anyway"
msgstr ""
+msgid "loading"
+msgstr ""
+
msgid "locked by %{path_lock_user_name} %{created_at}"
msgstr ""
@@ -26751,9 +27368,6 @@ msgstr ""
msgid "mrWidget|Deployment statistics are not available currently"
msgstr ""
-msgid "mrWidget|Detect issues before deployment with a CI pipeline that continuously tests your code. We created a quick guide that will show you how to create one. Make your code more secure and more robust in just a minute."
-msgstr ""
-
msgid "mrWidget|Did not close"
msgstr ""
@@ -26916,6 +27530,9 @@ msgstr ""
msgid "mrWidget|To approve this merge request, please enter your password. This project requires all approvals to be authenticated."
msgstr ""
+msgid "mrWidget|Use %{linkStart}CI pipelines to test your code%{linkEnd}, simply add a GitLab CI configuration file to your project. It only takes a minute to make your code more secure and robust."
+msgstr ""
+
msgid "mrWidget|When this merge request is ready, remove the WIP: prefix from the title to allow it to be merged"
msgstr ""
@@ -26952,6 +27569,9 @@ msgstr ""
msgid "must be greater than start date"
msgstr ""
+msgid "must contain only valid frameworks"
+msgstr ""
+
msgid "my-awesome-group"
msgstr ""
@@ -26964,6 +27584,9 @@ msgstr ""
msgid "needs to be between 10 minutes and 1 month"
msgstr ""
+msgid "never"
+msgstr ""
+
msgid "never expires"
msgstr ""
@@ -26991,6 +27614,9 @@ msgstr ""
msgid "nounSeries|%{firstItem} and %{lastItem}"
msgstr ""
+msgid "nounSeries|%{item}"
+msgstr ""
+
msgid "nounSeries|%{item}, %{nextItem}"
msgstr ""
@@ -27127,6 +27753,9 @@ msgstr ""
msgid "revised"
msgstr ""
+msgid "satisfied"
+msgstr ""
+
msgid "score"
msgstr ""
@@ -27157,6 +27786,9 @@ msgstr ""
msgid "severity|Unknown"
msgstr ""
+msgid "should be an array of %{object_name} objects"
+msgstr ""
+
msgid "should be greater than or equal to %{access} inherited membership from group %{group_name}"
msgstr ""
@@ -27211,10 +27843,13 @@ msgstr ""
msgid "suggestPipeline|2/2: Commit your changes"
msgstr ""
-msgid "suggestPipeline|Commit the changes and your pipeline will automatically run for the first time."
+msgid "suggestPipeline|Choose %{boldStart}Code Quality%{boldEnd} to add a pipeline that tests the quality of your code."
msgstr ""
-msgid "suggestPipeline|We recommend the %{boldStart}Code Quality%{boldEnd} template, which will add a report widget to your Merge Requests. This way you’ll learn about code quality degradations much sooner. %{footerStart} Goodbye technical debt! %{footerEnd}"
+msgid "suggestPipeline|The template is ready! You can now commit it to create your first pipeline."
+msgstr ""
+
+msgid "suggestPipeline|We’re adding a GitLab CI configuration file to add a pipeline to the project. You could create it manually, but we recommend that you start with a GitLab template that works out of the box."
msgstr ""
msgid "syntax is correct"
@@ -27274,6 +27909,9 @@ msgstr ""
msgid "uses Kubernetes clusters to deploy your code!"
msgstr ""
+msgid "v%{version} published %{timeAgo}"
+msgstr ""
+
msgid "verify ownership"
msgstr ""
diff --git a/locale/id_ID/gitlab.po b/locale/id_ID/gitlab.po
index 509cfa6426e..bf01dbca502 100644
--- a/locale/id_ID/gitlab.po
+++ b/locale/id_ID/gitlab.po
@@ -2,7 +2,7 @@ msgid ""
msgstr ""
"Project-Id-Version: gitlab-ee\n"
"Report-Msgid-Bugs-To: \n"
-"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Last-Translator: \n"
"Language-Team: Indonesian\n"
"Language: id_ID\n"
"MIME-Version: 1.0\n"
@@ -10,9 +10,11 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=1; plural=0;\n"
"X-Crowdin-Project: gitlab-ee\n"
+"X-Crowdin-Project-ID: 288872\n"
"X-Crowdin-Language: id\n"
"X-Crowdin-File: /master/locale/gitlab.pot\n"
-"PO-Revision-Date: 2020-06-08 15:09\n"
+"X-Crowdin-File-ID: 6\n"
+"PO-Revision-Date: 2020-07-02 01:20\n"
msgid " %{start} to %{end}"
msgstr ""
@@ -170,6 +172,10 @@ msgid "%d metric"
msgid_plural "%d metrics"
msgstr[0] ""
+msgid "%d milestone"
+msgid_plural "%d milestones"
+msgstr[0] ""
+
msgid "%d minute"
msgid_plural "%d minutes"
msgstr[0] ""
@@ -206,10 +212,6 @@ msgid "%d unresolved thread"
msgid_plural "%d unresolved threads"
msgstr[0] ""
-msgid "%d url scanned"
-msgid_plural "%d urls scanned"
-msgstr[0] ""
-
msgid "%d vulnerability"
msgid_plural "%d vulnerabilities"
msgstr[0] ""
@@ -280,9 +282,15 @@ msgstr[0] ""
msgid "%{count} related %{pluralized_subject}: %{links}"
msgstr ""
+msgid "%{dashboard_path} could not be found."
+msgstr ""
+
msgid "%{days} days until tags are automatically removed"
msgstr ""
+msgid "%{deployLinkStart}Use a template to deploy to ECS%{deployLinkEnd}, or use a docker image to %{commandsLinkStart}run AWS commands in GitLab CI/CD%{commandsLinkEnd}."
+msgstr ""
+
msgid "%{description}- Sentry event: %{errorUrl}- First seen: %{firstSeen}- Last seen: %{lastSeen} %{countLabel}: %{count}%{userCountLabel}: %{userCount}"
msgstr ""
@@ -325,12 +333,18 @@ msgstr ""
msgid "%{host} sign-in from new location"
msgstr ""
-msgid "%{icon}You are about to add %{usersTag} people to the discussion. Proceed with caution."
+msgid "%{icon}You are about to add %{usersTag} people to the discussion. They will all receive a notification."
+msgstr ""
+
+msgid "%{integrations_link_start}Integrations%{link_end} enable you to make third-party applications part of your GitLab workflow. If the available integrations don't meet your needs, consider using a %{webhooks_link_start}webhook%{link_end}."
msgstr ""
msgid "%{issuableType} will be removed! Are you sure?"
msgstr ""
+msgid "%{issuesCount} issues in this group"
+msgstr ""
+
msgid "%{issuesSize} issues"
msgstr ""
@@ -340,7 +354,7 @@ msgstr ""
msgid "%{labelStart}Class:%{labelEnd} %{class}"
msgstr ""
-msgid "%{labelStart}Confidence:%{labelEnd} %{confidence}"
+msgid "%{labelStart}Evidence:%{labelEnd} %{evidence}"
msgstr ""
msgid "%{labelStart}File:%{labelEnd} %{file}"
@@ -358,6 +372,9 @@ msgstr ""
msgid "%{labelStart}Report Type:%{labelEnd} %{reportType}"
msgstr ""
+msgid "%{labelStart}Scanner:%{labelEnd} %{scanner}"
+msgstr ""
+
msgid "%{labelStart}Severity:%{labelEnd} %{severity}"
msgstr ""
@@ -382,9 +399,6 @@ msgstr ""
msgid "%{link_start}Read more%{link_end} about role permissions"
msgstr ""
-msgid "%{link} can be used for binding events when something is happening within the project."
-msgstr ""
-
msgid "%{listToShow}, and %{awardsListLength} more."
msgstr ""
@@ -418,6 +432,9 @@ msgstr ""
msgid "%{name} found %{resultsString}"
msgstr ""
+msgid "%{name} is already being used for another emoji"
+msgstr ""
+
msgid "%{name} is scheduled for %{action}"
msgstr ""
@@ -464,6 +481,9 @@ msgstr ""
msgid "%{primary} (%{secondary})"
msgstr ""
+msgid "%{ref} cannot be added: %{error}"
+msgstr ""
+
msgid "%{releases} release"
msgid_plural "%{releases} releases"
msgstr[0] ""
@@ -471,13 +491,34 @@ msgstr[0] ""
msgid "%{remaining_approvals} left"
msgstr ""
-msgid "%{retryButtonStart}Try again%{retryButtonEnd} or %{newFileButtonStart}attach a new file%{newFileButtonEnd}"
+msgid "%{reportType} %{status} detected %{critical} critical and %{high} high severity vulnerabilities out of %{total}."
+msgstr ""
+
+msgid "%{reportType} %{status} detected %{critical} critical and %{high} high severity vulnerabilities."
msgstr ""
-msgid "%{screenreaderOnlyStart}Keyboard shorcuts%{screenreaderOnlyEnd} Disabled"
+msgid "%{reportType} %{status} detected %{critical} critical severity vulnerabilities out of %{total}."
msgstr ""
-msgid "%{screenreaderOnlyStart}Keyboard shorcuts%{screenreaderOnlyEnd} Enabled"
+msgid "%{reportType} %{status} detected %{critical} critical severity vulnerability."
+msgid_plural "%{reportType} %{status} detected %{critical} critical severity vulnerabilities."
+msgstr[0] ""
+
+msgid "%{reportType} %{status} detected %{high} high severity vulnerabilities out of %{total}."
+msgstr ""
+
+msgid "%{reportType} %{status} detected %{high} high severity vulnerability."
+msgid_plural "%{reportType} %{status} detected %{high} high severity vulnerabilities."
+msgstr[0] ""
+
+msgid "%{reportType} %{status} detected %{other} vulnerability."
+msgid_plural "%{reportType} %{status} detected %{other} vulnerabilities."
+msgstr[0] ""
+
+msgid "%{reportType} %{status} detected no new vulnerabilities."
+msgstr ""
+
+msgid "%{retryButtonStart}Try again%{retryButtonEnd} or %{newFileButtonStart}attach a new file%{newFileButtonEnd}"
msgstr ""
msgid "%{service_title} %{message}."
@@ -569,6 +610,12 @@ msgstr ""
msgid "%{token}..."
msgstr ""
+msgid "%{totalCpu} (%{freeSpacePercentage}%{percentSymbol} free)"
+msgstr ""
+
+msgid "%{totalMemory} (%{freeSpacePercentage}%{percentSymbol} free)"
+msgstr ""
+
msgid "%{totalWeight} total weight"
msgstr ""
@@ -599,6 +646,12 @@ msgstr ""
msgid "%{verb} %{time_spent_value} spent time."
msgstr ""
+msgid "%{webhooks_link_start}%{webhook_type}%{link_end} enable you to send notifications to web applications in response to events in a group or project."
+msgstr ""
+
+msgid "%{webhooks_link_start}%{webhook_type}%{link_end} enable you to send notifications to web applications in response to events in a group or project. We recommend using an %{integrations_link_start}integration%{link_end} in preference to a webhook."
+msgstr ""
+
msgid "'%{level}' is not a valid visibility level"
msgstr ""
@@ -624,9 +677,6 @@ msgstr ""
msgid "(No changes)"
msgstr ""
-msgid "(Show all)"
-msgstr ""
-
msgid "(check progress)"
msgstr ""
@@ -690,6 +740,9 @@ msgstr ""
msgid "0 for unlimited"
msgstr ""
+msgid "0 for unlimited, only effective with remote storage enabled."
+msgstr ""
+
msgid "1 %{type} addition"
msgid_plural "%{count} %{type} additions"
msgstr[0] ""
@@ -822,6 +875,12 @@ msgstr ""
msgid "<code>\"johnsmith@example.com\": \"johnsmith@example.com\"</code> will add \"By <a href=\"#\">johnsmith@example.com</a>\" to all issues and comments originally created by johnsmith@example.com. By default, the email address or username is masked to ensure the user's privacy. Use this option if you want to show the full email address."
msgstr ""
+msgid "<code>Masked</code> to prevent the values from being displayed in job logs (must match certain regexp requirements)."
+msgstr ""
+
+msgid "<code>Protected</code> to expose them to protected branches or tags only."
+msgstr ""
+
msgid "<namespace / project>"
msgstr ""
@@ -849,9 +908,6 @@ msgstr ""
msgid "A .NET Core console application template, customizable for any .NET Core project"
msgstr ""
-msgid "A DAG must have two dependent jobs to be visualized on this tab."
-msgstr ""
-
msgid "A GitBook site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
msgstr ""
@@ -882,6 +938,12 @@ msgstr ""
msgid "A deleted user"
msgstr ""
+msgid "A file has been changed."
+msgstr ""
+
+msgid "A file was not found."
+msgstr ""
+
msgid "A file with '%{file_name}' already exists in %{branch} branch"
msgstr ""
@@ -948,7 +1010,7 @@ msgstr ""
msgid "A subscription will trigger a new pipeline on the default branch of this project when a pipeline successfully completes for a new tag on the %{default_branch_docs} of the subscribed project."
msgstr ""
-msgid "A terraform report was generated in your pipelines."
+msgid "A suggestion is not applicable."
msgstr ""
msgid "A user with write access to the source branch selected this option"
@@ -1119,6 +1181,9 @@ msgstr ""
msgid "Action to take when receiving an alert."
msgstr ""
+msgid "Actions"
+msgstr ""
+
msgid "Activate"
msgstr ""
@@ -1174,6 +1239,9 @@ msgstr ""
msgid "Add LICENSE"
msgstr ""
+msgid "Add New Node"
+msgstr ""
+
msgid "Add README"
msgstr ""
@@ -1249,6 +1317,9 @@ msgstr ""
msgid "Add approval rule"
msgstr ""
+msgid "Add approvers"
+msgstr ""
+
msgid "Add bold text"
msgstr ""
@@ -1306,6 +1377,9 @@ msgstr ""
msgid "Add strikethrough text"
msgstr ""
+msgid "Add suggestion to batch"
+msgstr ""
+
msgid "Add system hook"
msgstr ""
@@ -1405,6 +1479,9 @@ msgstr ""
msgid "Admin Note"
msgstr ""
+msgid "Admin Notifications"
+msgstr ""
+
msgid "Admin Overview"
msgstr ""
@@ -1516,6 +1593,9 @@ msgstr ""
msgid "AdminSettings|Integrations configured here will automatically apply to all projects on this instance."
msgstr ""
+msgid "AdminSettings|Moved to integrations"
+msgstr ""
+
msgid "AdminSettings|No required pipeline"
msgstr ""
@@ -1745,12 +1825,18 @@ msgid "Alert"
msgid_plural "Alerts"
msgstr[0] ""
+msgid "AlertManagement| assign yourself"
+msgstr ""
+
msgid "AlertManagement|Acknowledged"
msgstr ""
msgid "AlertManagement|Alert"
msgstr ""
+msgid "AlertManagement|Alert assignee(s): %{assignees}"
+msgstr ""
+
msgid "AlertManagement|Alert detail"
msgstr ""
@@ -1766,9 +1852,15 @@ msgstr ""
msgid "AlertManagement|All alerts"
msgstr ""
+msgid "AlertManagement|Assign To"
+msgstr ""
+
msgid "AlertManagement|Assign status"
msgstr ""
+msgid "AlertManagement|Assignee"
+msgstr ""
+
msgid "AlertManagement|Assignees"
msgstr ""
@@ -1787,9 +1879,6 @@ msgstr ""
msgid "AlertManagement|Edit"
msgstr ""
-msgid "AlertManagement|End time"
-msgstr ""
-
msgid "AlertManagement|Events"
msgstr ""
@@ -1808,10 +1897,13 @@ msgstr ""
msgid "AlertManagement|More information"
msgstr ""
+msgid "AlertManagement|No Matching Results"
+msgstr ""
+
msgid "AlertManagement|No alert data to display."
msgstr ""
-msgid "AlertManagement|No alerts available to display. If you think you're seeing this message in error, refresh the page."
+msgid "AlertManagement|No alerts available to display. See %{linkStart}enabling alert management%{linkEnd} for more information on adding alerts to the list."
msgstr ""
msgid "AlertManagement|No alerts to display."
@@ -1820,6 +1912,9 @@ msgstr ""
msgid "AlertManagement|None"
msgstr ""
+msgid "AlertManagement|None -"
+msgstr ""
+
msgid "AlertManagement|Open"
msgstr ""
@@ -1856,9 +1951,18 @@ msgstr ""
msgid "AlertManagement|There was an error displaying the alerts. Confirm your endpoint's configuration details to ensure alerts appear."
msgstr ""
+msgid "AlertManagement|There was an error while updating the assignee(s) list. Please try again."
+msgstr ""
+
+msgid "AlertManagement|There was an error while updating the assignee(s) of the alert. Please try again."
+msgstr ""
+
msgid "AlertManagement|There was an error while updating the status of the alert. Please try again."
msgstr ""
+msgid "AlertManagement|This assignee cannot be assigned to this alert."
+msgstr ""
+
msgid "AlertManagement|Tool"
msgstr ""
@@ -2006,6 +2110,9 @@ msgstr ""
msgid "Allow users to request access (if visibility is public or internal)"
msgstr ""
+msgid "Allowed Geo IP"
+msgstr ""
+
msgid "Allowed email domain restriction only permitted for top-level groups"
msgstr ""
@@ -2039,6 +2146,9 @@ msgstr ""
msgid "Amazon Web Services"
msgstr ""
+msgid "Amazon Web Services Logo"
+msgstr ""
+
msgid "Amazon authentication is not %{link_start}correctly configured%{link_end}. Ask your GitLab administrator if you want to use this service."
msgstr ""
@@ -2096,10 +2206,16 @@ msgstr ""
msgid "An error occurred when updating the issue weight"
msgstr ""
+msgid "An error occurred while acknowledging the notification. Refresh the page and try again."
+msgstr ""
+
+msgid "An error occurred while adding approvers"
+msgstr ""
+
msgid "An error occurred while adding formatted title for epic"
msgstr ""
-msgid "An error occurred while checking group path"
+msgid "An error occurred while checking group path. Please refresh and try again."
msgstr ""
msgid "An error occurred while committing your changes."
@@ -2243,7 +2359,7 @@ msgstr ""
msgid "An error occurred while loading milestones"
msgstr ""
-msgid "An error occurred while loading terraform report"
+msgid "An error occurred while loading project creation UI"
msgstr ""
msgid "An error occurred while loading the data. Please try again."
@@ -2297,6 +2413,9 @@ msgstr ""
msgid "An error occurred while rendering preview broadcast message"
msgstr ""
+msgid "An error occurred while rendering the editor"
+msgstr ""
+
msgid "An error occurred while reordering issues."
msgstr ""
@@ -2393,7 +2512,7 @@ msgstr ""
msgid "Analyze your dependencies for known vulnerabilities."
msgstr ""
-msgid "Analyze your source code and git history for secrets"
+msgid "Analyze your source code and git history for secrets."
msgstr ""
msgid "Analyze your source code for known vulnerabilities."
@@ -2507,12 +2626,18 @@ msgstr ""
msgid "Apply suggestion"
msgstr ""
+msgid "Apply suggestions"
+msgstr ""
+
msgid "Apply template"
msgstr ""
msgid "Apply this approval rule to any branch or a specific protected branch."
msgstr ""
+msgid "Applying"
+msgstr ""
+
msgid "Applying a template will replace the existing issue description. Any changes you have made will be lost."
msgstr ""
@@ -2525,12 +2650,18 @@ msgstr ""
msgid "Applying multiple commands"
msgstr ""
-msgid "Applying suggestion"
+msgid "Applying suggestion..."
+msgstr ""
+
+msgid "Applying suggestions..."
msgstr ""
msgid "Approval rules"
msgstr ""
+msgid "Approval rules reset to project defaults"
+msgstr ""
+
msgid "ApprovalRuleRemove|%d member"
msgid_plural "ApprovalRuleRemove|%d members"
msgstr[0] ""
@@ -2580,6 +2711,9 @@ msgstr ""
msgid "Approved"
msgstr ""
+msgid "Approved MRs"
+msgstr ""
+
msgid "Approved by: "
msgstr ""
@@ -2793,6 +2927,9 @@ msgstr ""
msgid "Assign"
msgstr ""
+msgid "Assign Iteration"
+msgstr ""
+
msgid "Assign custom color like #FF0000"
msgstr ""
@@ -2811,6 +2948,9 @@ msgstr ""
msgid "Assign to"
msgstr ""
+msgid "Assign to commenting user"
+msgstr ""
+
msgid "Assign yourself to these issues"
msgstr ""
@@ -2836,6 +2976,9 @@ msgid "Assignee"
msgid_plural "%d Assignees"
msgstr[0] ""
+msgid "Assignee has no permissions"
+msgstr ""
+
msgid "Assignee lists not available with your current license"
msgstr ""
@@ -2845,6 +2988,9 @@ msgstr ""
msgid "Assignee(s)"
msgstr ""
+msgid "Assignees"
+msgstr ""
+
msgid "Assigns %{assignee_users_sentence}."
msgstr ""
@@ -2879,6 +3025,9 @@ msgstr ""
msgid "Audit Events is a way to keep track of important events that happened in GitLab."
msgstr ""
+msgid "Audit Log"
+msgstr ""
+
msgid "AuditEvents|(removed)"
msgstr ""
@@ -2915,6 +3064,9 @@ msgstr ""
msgid "AuditLogs|IP Address"
msgstr ""
+msgid "AuditLogs|Member Events"
+msgstr ""
+
msgid "AuditLogs|No matching %{type} found."
msgstr ""
@@ -3197,9 +3349,6 @@ msgstr ""
msgid "Badges|e.g. %{exampleUrl}"
msgstr ""
-msgid "Badge|New"
-msgstr ""
-
msgid "Balsamiq file could not be loaded."
msgstr ""
@@ -3248,6 +3397,12 @@ msgstr ""
msgid "Below you will find all the groups that are public."
msgstr ""
+msgid "Beta"
+msgstr ""
+
+msgid "Bi-weekly code coverage"
+msgstr ""
+
msgid "Billing"
msgstr ""
@@ -3308,9 +3463,15 @@ msgstr ""
msgid "Bitbucket Server Import"
msgstr ""
+msgid "Bitbucket Server import"
+msgstr ""
+
msgid "Bitbucket import"
msgstr ""
+msgid "Blame"
+msgstr ""
+
msgid "Blocked"
msgstr ""
@@ -3323,9 +3484,6 @@ msgstr ""
msgid "Blog"
msgstr ""
-msgid "Blue helpers indicate an action to be taken."
-msgstr ""
-
msgid "Board name"
msgstr ""
@@ -3524,6 +3682,9 @@ msgstr ""
msgid "Broadcast Message was successfully updated."
msgstr ""
+msgid "Broadcast Messages"
+msgstr ""
+
msgid "Browse Directory"
msgstr ""
@@ -3578,6 +3739,9 @@ msgstr ""
msgid "By %{user_name}"
msgstr ""
+msgid "By URL"
+msgstr ""
+
msgid "By default GitLab sends emails in HTML and plain text formats so mail clients can choose what format to use. Disable this option if you only want to send emails in plain text format."
msgstr ""
@@ -3683,6 +3847,9 @@ msgstr ""
msgid "Can override approvers and approvals required per merge request"
msgstr ""
+msgid "Can't apply as this line has changed or the suggestion already matches its content."
+msgstr ""
+
msgid "Can't create snippet: %{err}"
msgstr ""
@@ -3869,9 +4036,6 @@ msgstr ""
msgid "Changes are still tracked. Useful for cluster/index migrations."
msgstr ""
-msgid "Changes are unknown"
-msgstr ""
-
msgid "Changes suppressed. Click to show."
msgstr ""
@@ -4178,15 +4342,9 @@ msgstr ""
msgid "Choose what content you want to see on a group’s overview page"
msgstr ""
-msgid "Choose which groups you wish to synchronize to this secondary node"
-msgstr ""
-
msgid "Choose which repositories you want to connect and run CI/CD pipelines."
msgstr ""
-msgid "Choose which shards you wish to synchronize to this secondary node"
-msgstr ""
-
msgid "Choose your framework"
msgstr ""
@@ -4484,9 +4642,15 @@ msgstr ""
msgid "Cluster does not exist"
msgstr ""
+msgid "Cluster is required for Stages::ClusterEndpointInserter"
+msgstr ""
+
msgid "Cluster level"
msgstr ""
+msgid "Cluster type must be specificed for Stages::ClusterEndpointInserter"
+msgstr ""
+
msgid "ClusterIntegration| %{custom_domain_start}More information%{custom_domain_end}."
msgstr ""
@@ -4598,7 +4762,7 @@ msgstr ""
msgid "ClusterIntegration|Cert-Manager"
msgstr ""
-msgid "ClusterIntegration|Cert-Manager is a native Kubernetes certificate management controller that helps with issuing certificates. Installing Cert-Manager on your cluster will issue a certificate by %{letsEncrypt} and ensure that certificates are valid and up-to-date."
+msgid "ClusterIntegration|Cert-Manager is a native Kubernetes certificate management controller that helps with issuing certificates. Installing Cert-Manager on your cluster will issue a certificate by %{linkStart}Let's Encrypt%{linkEnd} and ensure that certificates are valid and up-to-date."
msgstr ""
msgid "ClusterIntegration|Certificate Authority bundle (PEM format)"
@@ -4625,9 +4789,6 @@ msgstr ""
msgid "ClusterIntegration|Clear the local cache of namespace and service accounts. This is necessary if your integration has become out of sync. The cache is repopulated during the next CI job that requires namespace and service accounts."
msgstr ""
-msgid "ClusterIntegration|Cloud Run"
-msgstr ""
-
msgid "ClusterIntegration|Cluster being created"
msgstr ""
@@ -4712,7 +4873,7 @@ msgstr ""
msgid "ClusterIntegration|Crossplane"
msgstr ""
-msgid "ClusterIntegration|Crossplane enables declarative provisioning of managed services from your cloud of choice using %{codeStart}kubectl%{codeEnd} or %{gitlabIntegrationLink}. Crossplane runs inside your Kubernetes cluster and supports secure connectivity and secrets management between app containers and the cloud services they depend on."
+msgid "ClusterIntegration|Crossplane enables declarative provisioning of managed services from your cloud of choice using %{codeStart}kubectl%{codeEnd} or %{linkStart}GitLab Integration%{linkEnd}. Crossplane runs inside your Kubernetes cluster and supports secure connectivity and secrets management between app containers and the cloud services they depend on."
msgstr ""
msgid "ClusterIntegration|Deletes all GitLab resources attached to this cluster during removal"
@@ -4796,9 +4957,6 @@ msgstr ""
msgid "ClusterIntegration|GitLab-managed cluster"
msgstr ""
-msgid "ClusterIntegration|Gitlab Integration"
-msgstr ""
-
msgid "ClusterIntegration|Global default"
msgstr ""
@@ -4844,7 +5002,7 @@ msgstr ""
msgid "ClusterIntegration|Ingress gives you a way to route requests to services based on the request host or path, centralizing a number of services into a single entrypoint."
msgstr ""
-msgid "ClusterIntegration|Installing Ingress may incur additional costs. Learn more about %{pricingLink}."
+msgid "ClusterIntegration|Installing Ingress may incur additional costs. Learn more about %{linkStart}pricing%{linkEnd}."
msgstr ""
msgid "ClusterIntegration|Instance cluster"
@@ -4859,7 +5017,7 @@ msgstr ""
msgid "ClusterIntegration|Issuer Email"
msgstr ""
-msgid "ClusterIntegration|Issuers represent a certificate authority. You must provide an email address for your Issuer. "
+msgid "ClusterIntegration|Issuers represent a certificate authority. You must provide an email address for your Issuer."
msgstr ""
msgid "ClusterIntegration|Jupyter Hostname"
@@ -4931,9 +5089,6 @@ msgstr ""
msgid "ClusterIntegration|Learn more about instance Kubernetes clusters"
msgstr ""
-msgid "ClusterIntegration|Let's Encrypt"
-msgstr ""
-
msgid "ClusterIntegration|Loading IAM Roles"
msgstr ""
@@ -5045,7 +5200,7 @@ msgstr ""
msgid "ClusterIntegration|Prometheus"
msgstr ""
-msgid "ClusterIntegration|Prometheus is an open-source monitoring system with %{gitlabIntegrationLink} to monitor deployed applications."
+msgid "ClusterIntegration|Prometheus is an open-source monitoring system with %{linkStart}GitLab Integration%{linkEnd} to monitor deployed applications."
msgstr ""
msgid "ClusterIntegration|Provider details"
@@ -5201,10 +5356,10 @@ msgstr ""
msgid "ClusterIntegration|Select zone to choose machine type"
msgstr ""
-msgid "ClusterIntegration|Send Cilium Logs"
+msgid "ClusterIntegration|Send Container Network Policies Logs"
msgstr ""
-msgid "ClusterIntegration|Send ModSecurity Logs"
+msgid "ClusterIntegration|Send Web Application Firewall Logs"
msgstr ""
msgid "ClusterIntegration|Service Token"
@@ -5372,15 +5527,12 @@ msgstr ""
msgid "ClusterIntegration|documentation"
msgstr ""
-msgid "ClusterIntegration|installed via %{installed_via}"
+msgid "ClusterIntegration|installed via %{linkStart}Cloud Run%{linkEnd}"
msgstr ""
msgid "ClusterIntegration|meets the requirements"
msgstr ""
-msgid "ClusterIntegration|pricing"
-msgstr ""
-
msgid "ClusterIntegration|sign up"
msgstr ""
@@ -5417,6 +5569,15 @@ msgstr ""
msgid "Code"
msgstr ""
+msgid "Code Coverage: %{coveragePercentage}%{percentSymbol}"
+msgstr ""
+
+msgid "Code Coverage| Empty code coverage data"
+msgstr ""
+
+msgid "Code Coverage|Couldn't fetch the code coverage data"
+msgstr ""
+
msgid "Code Owners"
msgstr ""
@@ -5471,7 +5632,7 @@ msgstr ""
msgid "Collapse approvers"
msgstr ""
-msgid "Collapse child epics"
+msgid "Collapse milestones"
msgstr ""
msgid "Collapse replies"
@@ -5489,6 +5650,9 @@ msgstr ""
msgid "Coming soon"
msgstr ""
+msgid "Comma-separated, e.g. '1.1.1.1, 2.2.2.0/24'"
+msgstr ""
+
msgid "Command"
msgstr ""
@@ -5658,6 +5822,12 @@ msgstr ""
msgid "Compliance framework (optional)"
msgstr ""
+msgid "Compliance frameworks"
+msgstr ""
+
+msgid "ComplianceDashboard|created by:"
+msgstr ""
+
msgid "ComplianceFramework|GDPR"
msgstr ""
@@ -5796,6 +5966,9 @@ msgstr ""
msgid "Connection timed out"
msgstr ""
+msgid "Connection timeout"
+msgstr ""
+
msgid "Contact sales to upgrade"
msgstr ""
@@ -5849,9 +6022,6 @@ msgstr ""
msgid "ContainerRegistry|CLI Commands"
msgstr ""
-msgid "ContainerRegistry|Compressed Size"
-msgstr ""
-
msgid "ContainerRegistry|Container Registry"
msgstr ""
@@ -5867,6 +6037,9 @@ msgstr ""
msgid "ContainerRegistry|Copy push command"
msgstr ""
+msgid "ContainerRegistry|Delete selected"
+msgstr ""
+
msgid "ContainerRegistry|Docker connection error"
msgstr ""
@@ -5900,16 +6073,16 @@ msgstr ""
msgid "ContainerRegistry|If you are not already logged in, you need to authenticate to the Container Registry by using your GitLab username and password. If you have %{twofaDocLinkStart}Two-Factor Authentication%{twofaDocLinkEnd} enabled, use a %{personalAccessTokensDocLinkStart}Personal Access Token%{personalAccessTokensDocLinkEnd} instead of a password."
msgstr ""
-msgid "ContainerRegistry|Image ID"
+msgid "ContainerRegistry|Image ID: %{imageId}"
msgstr ""
msgid "ContainerRegistry|Image Repositories"
msgstr ""
-msgid "ContainerRegistry|Keep and protect the images that matter most."
+msgid "ContainerRegistry|Image tags"
msgstr ""
-msgid "ContainerRegistry|Last Updated"
+msgid "ContainerRegistry|Keep and protect the images that matter most."
msgstr ""
msgid "ContainerRegistry|Login"
@@ -5924,6 +6097,9 @@ msgstr ""
msgid "ContainerRegistry|Please contact your administrator."
msgstr ""
+msgid "ContainerRegistry|Published %{timeInfo}"
+msgstr ""
+
msgid "ContainerRegistry|Push an image"
msgstr ""
@@ -5939,9 +6115,6 @@ msgstr ""
msgid "ContainerRegistry|Remove repository"
msgstr ""
-msgid "ContainerRegistry|Remove selected tags"
-msgstr ""
-
msgid "ContainerRegistry|Remove tag"
msgid_plural "ContainerRegistry|Remove tags"
msgstr[0] ""
@@ -5970,9 +6143,6 @@ msgstr ""
msgid "ContainerRegistry|Sorry, your filter produced no results."
msgstr ""
-msgid "ContainerRegistry|Tag"
-msgstr ""
-
msgid "ContainerRegistry|Tag expiration policy"
msgstr ""
@@ -6117,21 +6287,6 @@ msgstr ""
msgid "Control the display of third party offers."
msgstr ""
-msgid "Control the maximum concurrency of LFS/attachment backfill for this secondary node"
-msgstr ""
-
-msgid "Control the maximum concurrency of container repository operations for this Geo node"
-msgstr ""
-
-msgid "Control the maximum concurrency of repository backfill for this secondary node"
-msgstr ""
-
-msgid "Control the maximum concurrency of verification operations for this Geo node"
-msgstr ""
-
-msgid "Control the minimum interval in days that a repository should be reverified for this primary node"
-msgstr ""
-
msgid "Cookie domain"
msgstr ""
@@ -6273,6 +6428,9 @@ msgstr ""
msgid "Could not find design."
msgstr ""
+msgid "Could not find iteration"
+msgstr ""
+
msgid "Could not remove the trigger."
msgstr ""
@@ -6309,6 +6467,12 @@ msgstr ""
msgid "Coverage"
msgstr ""
+msgid "Coverage Fuzzing"
+msgstr ""
+
+msgid "Crash State"
+msgstr ""
+
msgid "Create"
msgstr ""
@@ -6357,6 +6521,9 @@ msgstr ""
msgid "Create a personal access token on your account to pull or push via %{protocol}."
msgstr ""
+msgid "Create a project pre-populated with the necessary files to get you started quickly."
+msgstr ""
+
msgid "Create an account using:"
msgstr ""
@@ -6525,6 +6692,9 @@ msgstr ""
msgid "Creates branch '%{branch_name}' and a merge request to resolve this issue."
msgstr ""
+msgid "Creating"
+msgstr ""
+
msgid "Creating epic"
msgstr ""
@@ -6576,7 +6746,10 @@ msgstr ""
msgid "Current vulnerabilities count"
msgstr ""
-msgid "CurrentUser|Buy CI minutes"
+msgid "CurrentUser|Buy Pipeline minutes"
+msgstr ""
+
+msgid "CurrentUser|One of your groups is running out"
msgstr ""
msgid "CurrentUser|Profile"
@@ -6591,6 +6764,9 @@ msgstr ""
msgid "CurrentUser|Upgrade"
msgstr ""
+msgid "Custom Attributes"
+msgstr ""
+
msgid "Custom CI configuration path"
msgstr ""
@@ -6853,12 +7029,18 @@ msgstr ""
msgid "DAG"
msgstr ""
+msgid "DAG visualization requires at least 3 dependent jobs."
+msgstr ""
+
msgid "DNS"
msgstr ""
msgid "Dashboard"
msgstr ""
+msgid "Dashboard uid not found"
+msgstr ""
+
msgid "DashboardProjects|All"
msgstr ""
@@ -6883,6 +7065,9 @@ msgstr ""
msgid "Data is still calculating..."
msgstr ""
+msgid "Datasource name not found"
+msgstr ""
+
msgid "Date"
msgstr ""
@@ -7063,6 +7248,9 @@ msgstr ""
msgid "Delete this attachment"
msgstr ""
+msgid "Delete user list"
+msgstr ""
+
msgid "Delete variable"
msgstr ""
@@ -7374,6 +7562,9 @@ msgstr ""
msgid "Deploying to"
msgstr ""
+msgid "Deploying to AWS is easy with GitLab"
+msgstr ""
+
msgid "Deployment Frequency"
msgstr ""
@@ -7735,6 +7926,9 @@ msgstr ""
msgid "Dismissed on pipeline %{pipelineLink} at %{projectLink}"
msgstr ""
+msgid "Display alerts from all your monitoring tools directly within GitLab."
+msgstr ""
+
msgid "Display name"
msgstr ""
@@ -7792,9 +7986,6 @@ msgstr ""
msgid "Don't show again"
msgstr ""
-msgid "Don't worry, you can access this tour by clicking on the help icon in the top right corner and choose <strong>Learn GitLab</strong>."
-msgstr ""
-
msgid "Done"
msgstr ""
@@ -7921,6 +8112,9 @@ msgstr ""
msgid "Edit Release"
msgstr ""
+msgid "Edit Slack integration"
+msgstr ""
+
msgid "Edit Snippet"
msgstr ""
@@ -7960,6 +8154,9 @@ msgstr ""
msgid "Edit issues"
msgstr ""
+msgid "Edit iteration"
+msgstr ""
+
msgid "Edit public deploy key"
msgstr ""
@@ -8008,6 +8205,9 @@ msgstr ""
msgid "Email"
msgstr ""
+msgid "Email Notification"
+msgstr ""
+
msgid "Email address"
msgstr ""
@@ -8020,6 +8220,9 @@ msgstr ""
msgid "Email not verified. Please verify your email in Salesforce."
msgstr ""
+msgid "Email notification for unknown sign-ins"
+msgstr ""
+
msgid "Email patch"
msgstr ""
@@ -8251,6 +8454,9 @@ msgstr ""
msgid "Enforce DNS rebinding attack protection"
msgstr ""
+msgid "Enforce personal access token expiration"
+msgstr ""
+
msgid "Ensure connectivity is available from the GitLab server to the Prometheus server"
msgstr ""
@@ -8320,6 +8526,9 @@ msgstr ""
msgid "Enter the name of your application, and we'll return a unique %{type}."
msgstr ""
+msgid "Enter weights for storages for new repositories."
+msgstr ""
+
msgid "Enter your password to approve"
msgstr ""
@@ -8329,15 +8538,27 @@ msgstr ""
msgid "Environment does not have deployments"
msgstr ""
+msgid "Environment is required for Stages::MetricEndpointInserter"
+msgstr ""
+
+msgid "Environment is required for Stages::VariableEndpointInserter"
+msgstr ""
+
msgid "Environment scope"
msgstr ""
+msgid "Environment variables are applied to all project environments in this instance via the Runner. You can use environment variables for passwords, secret keys, etc. Make variables available to the running application by prepending the variable key with <code>K8S_SECRET_</code>. You can set variables to be:"
+msgstr ""
+
msgid "Environment variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. Additionally, they can be masked so they are hidden in job logs, though they must match certain regexp requirements to do so. You can use environment variables for passwords, secret keys, or whatever you want."
msgstr ""
msgid "Environment variables are configured by your administrator to be %{link_start}protected%{link_end} by default"
msgstr ""
+msgid "Environment variables on this GitLab instance are configured to be %{link_start}protected%{link_end} by default"
+msgstr ""
+
msgid "Environment:"
msgstr ""
@@ -8869,6 +9090,9 @@ msgstr ""
msgid "EventFilterBy|Filter by comments"
msgstr ""
+msgid "EventFilterBy|Filter by designs"
+msgstr ""
+
msgid "EventFilterBy|Filter by epic events"
msgstr ""
@@ -9001,15 +9225,15 @@ msgstr ""
msgid "Expand approvers"
msgstr ""
-msgid "Expand child epics"
-msgstr ""
-
msgid "Expand down"
msgstr ""
msgid "Expand dropdown"
msgstr ""
+msgid "Expand milestones"
+msgstr ""
+
msgid "Expand sidebar"
msgstr ""
@@ -9025,6 +9249,9 @@ msgstr ""
msgid "Expiration date"
msgstr ""
+msgid "Expiration not enforced"
+msgstr ""
+
msgid "Expiration policy for the Container Registry is a perfect solution for keeping the Registry space down while still enjoying the full power of GitLab CI/CD."
msgstr ""
@@ -9145,6 +9372,9 @@ msgstr ""
msgid "Failed Jobs"
msgstr ""
+msgid "Failed on"
+msgstr ""
+
msgid "Failed to add a Zoom meeting"
msgstr ""
@@ -9205,13 +9435,19 @@ msgstr ""
msgid "Failed to install."
msgstr ""
+msgid "Failed to load assignees. Please try again."
+msgstr ""
+
+msgid "Failed to load authors. Please try again."
+msgstr ""
+
msgid "Failed to load emoji list."
msgstr ""
msgid "Failed to load error details from Sentry."
msgstr ""
-msgid "Failed to load errors from Sentry. Error message: %{errorMessage}"
+msgid "Failed to load errors from Sentry."
msgstr ""
msgid "Failed to load group activity metrics. Please try again."
@@ -9292,6 +9528,9 @@ msgstr ""
msgid "Failed to set due date because the date format is invalid."
msgstr ""
+msgid "Failed to set iteration on this issue. Please try again."
+msgstr ""
+
msgid "Failed to signing using smartcard authentication"
msgstr ""
@@ -9514,13 +9753,10 @@ msgstr ""
msgid "FeatureFlags|Target environments"
msgstr ""
-msgid "FeatureFlags|There are no active feature flags"
-msgstr ""
-
-msgid "FeatureFlags|There are no inactive feature flags"
+msgid "FeatureFlags|There was an error fetching the feature flags."
msgstr ""
-msgid "FeatureFlags|There was an error fetching the feature flags."
+msgid "FeatureFlags|There was an error retrieving user lists"
msgstr ""
msgid "FeatureFlags|Try again in a few moments or contact your support team."
@@ -9532,9 +9768,18 @@ msgstr ""
msgid "FeatureFlag|Delete strategy"
msgstr ""
+msgid "FeatureFlag|List"
+msgstr ""
+
msgid "FeatureFlag|Percentage"
msgstr ""
+msgid "FeatureFlag|Select a user list"
+msgstr ""
+
+msgid "FeatureFlag|There are no configured user lists"
+msgstr ""
+
msgid "FeatureFlag|Type"
msgstr ""
@@ -9691,6 +9936,12 @@ msgstr ""
msgid "Filter..."
msgstr ""
+msgid "Find File"
+msgstr ""
+
+msgid "Find bugs in your code with coverage-guided fuzzing"
+msgstr ""
+
msgid "Find by path"
msgstr ""
@@ -9955,6 +10206,9 @@ msgstr ""
msgid "Geo allows you to replicate your GitLab instance to other geographical locations."
msgstr ""
+msgid "Geo nodes are paused using a command run on the node"
+msgstr ""
+
msgid "GeoNodeStatusEvent|%{timeAgoStr} (%{pendingEvents} events)"
msgstr ""
@@ -10039,6 +10293,9 @@ msgstr ""
msgid "GeoNodes|Not checksummed"
msgstr ""
+msgid "GeoNodes|Package files"
+msgstr ""
+
msgid "GeoNodes|Pausing replication stops the sync process. Are you sure?"
msgstr ""
@@ -10057,6 +10314,9 @@ msgstr ""
msgid "GeoNodes|Replication slots"
msgstr ""
+msgid "GeoNodes|Replication status"
+msgstr ""
+
msgid "GeoNodes|Repositories"
msgstr ""
@@ -10267,6 +10527,9 @@ msgstr ""
msgid "Geo|Synchronization failed - %{error}"
msgstr ""
+msgid "Geo|Synchronization of %{itemTitle} is disabled."
+msgstr ""
+
msgid "Geo|The database is currently %{db_lag} behind the primary node."
msgstr ""
@@ -10579,6 +10842,9 @@ msgstr ""
msgid "Go to %{link_to_google_takeout}."
msgstr ""
+msgid "Go to <strong>Issues</strong> > <strong>Boards</strong> to access your personalized learning issue board."
+msgstr ""
+
msgid "Go to Pipelines"
msgstr ""
@@ -10678,9 +10944,6 @@ msgstr ""
msgid "Go to your snippets"
msgstr ""
-msgid "Golden Tanuki"
-msgstr ""
-
msgid "Google Cloud Platform"
msgstr ""
@@ -10702,6 +10965,9 @@ msgstr ""
msgid "Grafana URL"
msgstr ""
+msgid "Grafana response contains invalid json"
+msgstr ""
+
msgid "GrafanaIntegration|API Token"
msgstr ""
@@ -10750,9 +11016,6 @@ msgstr ""
msgid "Group %{group_name} was successfully created."
msgstr ""
-msgid "Group '%{group_name}' is being imported."
-msgstr ""
-
msgid "Group Audit Events"
msgstr ""
@@ -10789,9 +11052,6 @@ msgstr ""
msgid "Group by:"
msgstr ""
-msgid "Group could not be imported: %{errors}"
-msgstr ""
-
msgid "Group description"
msgstr ""
@@ -10819,9 +11079,15 @@ msgstr ""
msgid "Group has not been marked for deletion"
msgstr ""
+msgid "Group import could not be scheduled"
+msgstr ""
+
msgid "Group info:"
msgstr ""
+msgid "Group is required when cluster_type is :group"
+msgstr ""
+
msgid "Group maintainers can register group runners in the %{link}"
msgstr ""
@@ -10885,6 +11151,24 @@ msgstr ""
msgid "GroupActivyMetrics|Recent activity (last 90 days)"
msgstr ""
+msgid "GroupImport|Failed to import group."
+msgstr ""
+
+msgid "GroupImport|Group '%{group_name}' is being imported."
+msgstr ""
+
+msgid "GroupImport|Group could not be imported: %{errors}"
+msgstr ""
+
+msgid "GroupImport|Please wait while we import the group for you. Refresh at will."
+msgstr ""
+
+msgid "GroupImport|The group was successfully imported."
+msgstr ""
+
+msgid "GroupImport|Unable to process group import file"
+msgstr ""
+
msgid "GroupRoadmap|%{dateWord} – No end date"
msgstr ""
@@ -11152,6 +11436,9 @@ msgstr ""
msgid "Groups (%{groups})"
msgstr ""
+msgid "Groups and projects"
+msgstr ""
+
msgid "Groups can also be nested by creating %{subgroup_docs_link_start}subgroups%{subgroup_docs_link_end}."
msgstr ""
@@ -11197,6 +11484,33 @@ msgstr ""
msgid "GroupsEmptyState|You can manage your group member’s permissions and access to each project in the group."
msgstr ""
+msgid "GroupsNew|Contact an administrator to enable options for importing your group."
+msgstr ""
+
+msgid "GroupsNew|Create"
+msgstr ""
+
+msgid "GroupsNew|Create group"
+msgstr ""
+
+msgid "GroupsNew|GitLab group export"
+msgstr ""
+
+msgid "GroupsNew|Import"
+msgstr ""
+
+msgid "GroupsNew|Import group"
+msgstr ""
+
+msgid "GroupsNew|My Awesome Group"
+msgstr ""
+
+msgid "GroupsNew|No import options available"
+msgstr ""
+
+msgid "GroupsNew|To copy a GitLab group between installations, navigate to the group settings page for the original installation, generate an export file, and upload it here."
+msgstr ""
+
msgid "GroupsTree|Are you sure you want to leave the \"%{fullName}\" group?"
msgstr ""
@@ -11299,6 +11613,9 @@ msgstr ""
msgid "Helps reduce request volume for protected paths"
msgstr ""
+msgid "Here are all your projects in your group, including the one you just created. To start, let’s take a look at your personalized learning project which will help you learn about GitLab at your own pace."
+msgstr ""
+
msgid "Here you will find recent merge request activity"
msgstr ""
@@ -11312,6 +11629,9 @@ msgid "Hide chart"
msgid_plural "Hide charts"
msgstr[0] ""
+msgid "Hide details"
+msgstr ""
+
msgid "Hide file browser"
msgstr ""
@@ -11321,6 +11641,9 @@ msgstr ""
msgid "Hide host keys manual input"
msgstr ""
+msgid "Hide list"
+msgstr ""
+
msgid "Hide marketing-related entries from help"
msgstr ""
@@ -11340,9 +11663,6 @@ msgstr[0] ""
msgid "Hide values"
msgstr ""
-msgid "Hiding all labels"
-msgstr ""
-
msgid "High or unknown vulnerabilities present"
msgstr ""
@@ -11427,6 +11747,9 @@ msgstr ""
msgid "ID:"
msgstr ""
+msgid "IDE"
+msgstr ""
+
msgid "IDE|Allow live previews of JavaScript projects in the Web IDE using CodeSandbox Live Preview."
msgstr ""
@@ -11517,10 +11840,10 @@ msgstr ""
msgid "If enabled"
msgstr ""
-msgid "If enabled, access to projects will be validated on an external service using their classification label."
+msgid "If enabled, GitLab will handle Object Storage replication using Geo. %{linkStart}More information%{linkEnd}"
msgstr ""
-msgid "If enabled, and if object storage is enabled, GitLab will handle Object Storage replication using Geo"
+msgid "If enabled, access to projects will be validated on an external service using their classification label."
msgstr ""
msgid "If there is no previous license or if the previous license has expired, some GitLab functionality will be blocked until a new, valid license is uploaded."
@@ -11881,6 +12204,9 @@ msgid "Instance"
msgid_plural "Instances"
msgstr[0] ""
+msgid "Instance Configuration"
+msgstr ""
+
msgid "Instance Statistics visibility"
msgstr ""
@@ -11902,9 +12228,6 @@ msgstr ""
msgid "Integrations"
msgstr ""
-msgid "Integrations allow you to integrate GitLab with other applications"
-msgstr ""
-
msgid "Integrations|All details"
msgstr ""
@@ -12016,6 +12339,9 @@ msgstr ""
msgid "Invalid query"
msgstr ""
+msgid "Invalid repository bundle for snippet with id %{snippet_id}"
+msgstr ""
+
msgid "Invalid repository path"
msgstr ""
@@ -12034,6 +12360,9 @@ msgstr ""
msgid "Invalid two-factor code."
msgstr ""
+msgid "Invalid yaml"
+msgstr ""
+
msgid "Invitation"
msgstr ""
@@ -12253,15 +12582,24 @@ msgstr ""
msgid "It seems like the Dependency Scanning job ran successfully, but no dependencies have been detected in your project."
msgstr ""
+msgid "It seems that there is currently no available data for code coverage"
+msgstr ""
+
msgid "It's you"
msgstr ""
+msgid "Iteration"
+msgstr ""
+
msgid "Iteration changed to"
msgstr ""
msgid "Iteration removed"
msgstr ""
+msgid "Iteration updated"
+msgstr ""
+
msgid "Iterations"
msgstr ""
@@ -12292,12 +12630,18 @@ msgstr ""
msgid "January"
msgstr ""
+msgid "Jira Issues"
+msgstr ""
+
msgid "Jira import is already running."
msgstr ""
msgid "Jira integration not configured."
msgstr ""
+msgid "Jira project key is not configured"
+msgstr ""
+
msgid "Jira project: %{importProject}"
msgstr ""
@@ -12478,6 +12822,9 @@ msgstr ""
msgid "Keep divergent refs"
msgstr ""
+msgid "Kerberos access denied"
+msgstr ""
+
msgid "Key"
msgstr ""
@@ -12487,10 +12834,13 @@ msgstr ""
msgid "Key: %{key}"
msgstr ""
-msgid "Keyboard Shortcuts"
+msgid "Keyboard shortcuts"
msgstr ""
-msgid "Keyboard shortcuts"
+msgid "Keys"
+msgstr ""
+
+msgid "Ki"
msgstr ""
msgid "Kubernetes"
@@ -12532,6 +12882,9 @@ msgstr ""
msgid "LDAP"
msgstr ""
+msgid "LDAP Synchronization"
+msgstr ""
+
msgid "LDAP settings"
msgstr ""
@@ -12755,12 +13108,18 @@ msgstr ""
msgid "Learn more about custom project templates"
msgstr ""
+msgid "Learn more about deploying to AWS"
+msgstr ""
+
msgid "Learn more about deploying to a cluster"
msgstr ""
msgid "Learn more about group-level project templates"
msgstr ""
+msgid "Learn more about job dependencies"
+msgstr ""
+
msgid "Learn more about signing commits"
msgstr ""
@@ -12812,9 +13171,21 @@ msgstr ""
msgid "License History"
msgstr ""
+msgid "License ID:"
+msgstr ""
+
+msgid "License URL"
+msgstr ""
+
msgid "License-Check"
msgstr ""
+msgid "LicenseCompliance|%{docLinkStart}License Approvals%{docLinkEnd} are active"
+msgstr ""
+
+msgid "LicenseCompliance|%{docLinkStart}License Approvals%{docLinkEnd} are inactive"
+msgstr ""
+
msgid "LicenseCompliance|Add a license"
msgstr ""
@@ -12836,9 +13207,15 @@ msgstr ""
msgid "LicenseCompliance|Deny"
msgstr ""
+msgid "LicenseCompliance|Learn more about %{linkStart}License Approvals%{linkEnd}"
+msgstr ""
+
msgid "LicenseCompliance|License"
msgstr ""
+msgid "LicenseCompliance|License Approvals"
+msgstr ""
+
msgid "LicenseCompliance|License Compliance detected %d license and policy violation for the source branch only; approval required"
msgid_plural "LicenseCompliance|License Compliance detected %d licenses and policy violations for the source branch only; approval required"
msgstr[0] ""
@@ -13054,6 +13431,9 @@ msgstr ""
msgid "List your Bitbucket Server repositories"
msgstr ""
+msgid "Lists"
+msgstr ""
+
msgid "Live preview"
msgstr ""
@@ -13237,6 +13617,9 @@ msgstr ""
msgid "Manage project labels"
msgstr ""
+msgid "Manage storage usage"
+msgstr ""
+
msgid "Manage two-factor authentication"
msgstr ""
@@ -13252,6 +13635,9 @@ msgstr ""
msgid "Manifest file import"
msgstr ""
+msgid "Manifest import"
+msgstr ""
+
msgid "Manual job"
msgstr ""
@@ -13399,6 +13785,12 @@ msgstr ""
msgid "Maximum field length"
msgstr ""
+msgid "Maximum file size is 2MB. Please select a smaller file."
+msgstr ""
+
+msgid "Maximum import size (MB)"
+msgstr ""
+
msgid "Maximum job timeout"
msgstr ""
@@ -13438,6 +13830,9 @@ msgstr ""
msgid "Maximum size of Elasticsearch bulk indexing requests."
msgstr ""
+msgid "Maximum size of import files."
+msgstr ""
+
msgid "Maximum size of individual attachments in comments."
msgstr ""
@@ -13489,6 +13884,9 @@ msgstr ""
msgid "Merge (when the pipeline succeeds)"
msgstr ""
+msgid "Merge Conflicts"
+msgstr ""
+
msgid "Merge Request"
msgstr ""
@@ -13615,6 +14013,9 @@ msgstr ""
msgid "MergeRequests|Squash task canceled: another squash is already in progress."
msgstr ""
+msgid "MergeRequests|This project does not allow squashing commits when merge requests are accepted."
+msgstr ""
+
msgid "MergeRequests|Thread stays resolved"
msgstr ""
@@ -13768,6 +14169,12 @@ msgstr ""
msgid "MetricsSettings|Add a button to the metrics dashboard linking directly to your existing external dashboard."
msgstr ""
+msgid "MetricsSettings|Choose whether to display dashboard metrics in UTC or the user's local timezone."
+msgstr ""
+
+msgid "MetricsSettings|Dashboard timezone"
+msgstr ""
+
msgid "MetricsSettings|External dashboard URL"
msgstr ""
@@ -13777,6 +14184,12 @@ msgstr ""
msgid "MetricsSettings|Metrics Dashboard"
msgstr ""
+msgid "MetricsSettings|UTC (Coordinated Universal Time)"
+msgstr ""
+
+msgid "MetricsSettings|User's local timezone"
+msgstr ""
+
msgid "Metrics|Add metric"
msgstr ""
@@ -13792,6 +14205,9 @@ msgstr ""
msgid "Metrics|Create metric"
msgstr ""
+msgid "Metrics|Current"
+msgstr ""
+
msgid "Metrics|Delete metric"
msgstr ""
@@ -13841,6 +14257,9 @@ msgstr ""
msgid "Metrics|Max"
msgstr ""
+msgid "Metrics|Min"
+msgstr ""
+
msgid "Metrics|Must be a valid PromQL query."
msgstr ""
@@ -13856,6 +14275,9 @@ msgstr ""
msgid "Metrics|Refresh dashboard"
msgstr ""
+msgid "Metrics|Select a value"
+msgstr ""
+
msgid "Metrics|Star dashboard"
msgstr ""
@@ -13874,12 +14296,18 @@ msgstr ""
msgid "Metrics|There was an error getting annotations information."
msgstr ""
+msgid "Metrics|There was an error getting dashboard validation warnings information."
+msgstr ""
+
msgid "Metrics|There was an error getting deployment information."
msgstr ""
msgid "Metrics|There was an error getting environments information."
msgstr ""
+msgid "Metrics|There was an error getting options for variable \"%{name}\"."
+msgstr ""
+
msgid "Metrics|There was an error trying to validate your query"
msgstr ""
@@ -13922,6 +14350,9 @@ msgstr ""
msgid "Metrics|You're about to permanently delete this metric. This cannot be undone."
msgstr ""
+msgid "Metrics|Your dashboard schema is invalid. Edit the dashboard to correct the YAML schema."
+msgstr ""
+
msgid "Metrics|e.g. HTTP requests"
msgstr ""
@@ -13937,12 +14368,18 @@ msgstr ""
msgid "Metrics|e.g. req/sec"
msgstr ""
+msgid "Mi"
+msgstr ""
+
msgid "Microsoft Azure"
msgstr ""
msgid "Middleman project with Static Site Editor support"
msgstr ""
+msgid "Migrate your data from an external source like GitHub, Bitbucket, or another instance of GitLab."
+msgstr ""
+
msgid "Migrated %{success_count}/%{total_count} files."
msgstr ""
@@ -14076,6 +14513,9 @@ msgstr ""
msgid "Minimum capacity to be available before we schedule more mirrors preemptively."
msgstr ""
+msgid "Minimum interval in days"
+msgstr ""
+
msgid "Minimum length is %{minimum_password_length} characters"
msgstr ""
@@ -14262,6 +14702,12 @@ msgstr ""
msgid "Multiple uploaders found: %{uploader_types}"
msgstr ""
+msgid "Must match with the %{codeStart}external_url%{codeEnd} in %{codeStart}/etc/gitlab/gitlab.rb%{codeEnd}."
+msgstr ""
+
+msgid "Must match with the %{codeStart}geo_node_name%{codeEnd} in %{codeStart}/etc/gitlab/gitlab.rb%{codeEnd}. %{linkStart}More information%{linkEnd}"
+msgstr ""
+
msgid "My Awesome Group"
msgstr ""
@@ -14376,10 +14822,16 @@ msgstr ""
msgid "New Application"
msgstr ""
+msgid "New Branch"
+msgstr ""
+
+msgid "New Deploy Key"
+msgstr ""
+
msgid "New Environment"
msgstr ""
-msgid "New Geo Node"
+msgid "New File"
msgstr ""
msgid "New Group"
@@ -14395,15 +14847,15 @@ msgid "New Issue"
msgid_plural "New Issues"
msgstr[0] ""
-msgid "New Iteration"
-msgstr ""
-
msgid "New Jira import"
msgstr ""
msgid "New Label"
msgstr ""
+msgid "New Merge Request"
+msgstr ""
+
msgid "New Milestone"
msgstr ""
@@ -14422,6 +14874,9 @@ msgstr ""
msgid "New Snippet"
msgstr ""
+msgid "New User"
+msgstr ""
+
msgid "New branch"
msgstr ""
@@ -14464,6 +14919,9 @@ msgstr ""
msgid "New issue title"
msgstr ""
+msgid "New iteration"
+msgstr ""
+
msgid "New iteration created"
msgstr ""
@@ -14641,6 +15099,12 @@ msgstr ""
msgid "No grouping"
msgstr ""
+msgid "No iteration"
+msgstr ""
+
+msgid "No iterations to show"
+msgstr ""
+
msgid "No job log"
msgstr ""
@@ -14683,6 +15147,9 @@ msgstr ""
msgid "No other labels with such name or description"
msgstr ""
+msgid "No panels matching properties %{opts}"
+msgstr ""
+
msgid "No parent group"
msgstr ""
@@ -14716,6 +15183,9 @@ msgstr ""
msgid "No schedules"
msgstr ""
+msgid "No stack trace for this error"
+msgstr ""
+
msgid "No starrers matched your search"
msgstr ""
@@ -14728,7 +15198,7 @@ msgstr ""
msgid "No test coverage"
msgstr ""
-msgid "No thanks, don't show this again"
+msgid "No thanks"
msgstr ""
msgid "No vulnerabilities present"
@@ -14743,9 +15213,6 @@ msgstr ""
msgid "No, directly import the existing email addresses and usernames."
msgstr ""
-msgid "No, not interested right now"
-msgstr ""
-
msgid "No. of commits"
msgstr ""
@@ -14791,9 +15258,6 @@ msgstr ""
msgid "Not found."
msgstr ""
-msgid "Not helpful"
-msgstr ""
-
msgid "Not now"
msgstr ""
@@ -14950,6 +15414,9 @@ msgstr ""
msgid "Notifications on"
msgstr ""
+msgid "Notify users by email when sign-in location is not recognized"
+msgstr ""
+
msgid "Nov"
msgstr ""
@@ -15016,9 +15483,6 @@ msgstr ""
msgid "Oh no!"
msgstr ""
-msgid "Ok let's go"
-msgstr ""
-
msgid "Oldest first"
msgstr ""
@@ -15031,16 +15495,49 @@ msgstr ""
msgid "On track"
msgstr ""
+msgid "OnDemandScans|Attached branch"
+msgstr ""
+
+msgid "OnDemandScans|Attached branch is where the scan job runs."
+msgstr ""
+
+msgid "OnDemandScans|Could not run the scan. Please try again."
+msgstr ""
+
msgid "OnDemandScans|Create new DAST scan"
msgstr ""
+msgid "OnDemandScans|DAST will scan the target URL and any discovered sub URLs."
+msgstr ""
+
+msgid "OnDemandScans|New on-demand DAST scan"
+msgstr ""
+
msgid "OnDemandScans|On-demand Scans"
msgstr ""
+msgid "OnDemandScans|On-demand scans run outside the DevOps cycle and find vulnerabilities in your projects. %{learnMoreLinkStart}Learn more%{learnMoreLinkEnd}"
+msgstr ""
+
+msgid "OnDemandScans|Only a passive scan can be performed on demand."
+msgstr ""
+
+msgid "OnDemandScans|Passive DAST Scan"
+msgstr ""
+
+msgid "OnDemandScans|Please enter a valid URL format, ex: http://www.example.com/home"
+msgstr ""
+
+msgid "OnDemandScans|Run this scan"
+msgstr ""
+
+msgid "OnDemandScans|Scan mode"
+msgstr ""
+
msgid "OnDemandScans|Schedule or run scans immediately against target sites. Currently available on-demand scan type: DAST. %{helpLinkStart}More information%{helpLinkEnd}"
msgstr ""
-msgid "Onboarding"
+msgid "OnDemandScans|Target URL"
msgstr ""
msgid "Once imported, repositories can be mirrored over SSH. Read more %{link_start}here%{link_end}."
@@ -15065,6 +15562,9 @@ msgstr ""
msgid "One or more of you personal access tokens were revoked"
msgstr ""
+msgid "One or more of your %{provider} projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
+msgstr ""
+
msgid "One or more of your Bitbucket projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
msgstr ""
@@ -15566,6 +16066,9 @@ msgstr ""
msgid "Parent epic is not present."
msgstr ""
+msgid "Parsing error for param :embed_json. %{message}"
+msgstr ""
+
msgid "Part of merge request changes"
msgstr ""
@@ -15578,6 +16081,9 @@ msgstr ""
msgid "Passed"
msgstr ""
+msgid "Passed on"
+msgstr ""
+
msgid "Password"
msgstr ""
@@ -15662,6 +16168,9 @@ msgstr ""
msgid "Perform common operations on GitLab project"
msgstr ""
+msgid "Performance and resource management"
+msgstr ""
+
msgid "Performance optimization"
msgstr ""
@@ -15869,7 +16378,7 @@ msgstr ""
msgid "Pipelines|Something went wrong while cleaning runners cache."
msgstr ""
-msgid "Pipelines|There are currently no %{scope} pipelines."
+msgid "Pipelines|There are currently no finished pipelines."
msgstr ""
msgid "Pipelines|There are currently no pipelines."
@@ -15980,6 +16489,9 @@ msgstr ""
msgid "Pipeline|Stop pipeline #%{pipelineId}?"
msgstr ""
+msgid "Pipeline|Tag name"
+msgstr ""
+
msgid "Pipeline|Trigger author"
msgstr ""
@@ -16061,12 +16573,18 @@ msgstr ""
msgid "Please check your email (%{email}) to verify that you own this address and unlock the power of CI/CD. Didn't receive it? %{resend_link}. Wrong email address? %{update_link}."
msgstr ""
+msgid "Please choose a file"
+msgstr ""
+
msgid "Please choose a group URL with no special characters."
msgstr ""
msgid "Please complete your profile with email address"
msgstr ""
+msgid "Please convert %{linkStart}them to Git%{linkEnd}, and go through the %{linkToImportFlow} again."
+msgstr ""
+
msgid "Please convert them to %{link_to_git}, and go through the %{link_to_import_flow} again."
msgstr ""
@@ -16124,6 +16642,9 @@ msgstr ""
msgid "Please provide a valid email address."
msgstr ""
+msgid "Please provide attributes to update"
+msgstr ""
+
msgid "Please refer to <a href=\"%{docs_url}\">%{docs_url}</a>"
msgstr ""
@@ -16859,6 +17380,9 @@ msgstr ""
msgid "Project has too many %{label_for_message} to search"
msgstr ""
+msgid "Project is required when cluster_type is :project"
+msgstr ""
+
msgid "Project members"
msgstr ""
@@ -16976,7 +17500,40 @@ msgstr ""
msgid "ProjectService|Comment"
msgstr ""
-msgid "ProjectService|Comment will be posted on each event"
+msgid "ProjectService|Event will be triggered by a push to the repository"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a commit is created/updated"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a confidential issue is created/updated/closed"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a deployment finishes"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a merge request is created/updated/merged"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a new tag is pushed to the repository"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a new, unique alert is recorded"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a pipeline status changes"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a wiki page is created/updated"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when an issue is created/updated/closed"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when someone adds a comment"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when someone adds a comment on a confidential issue"
msgstr ""
msgid "ProjectService|Perform common operations on GitLab project: %{project_name}"
@@ -17027,6 +17584,9 @@ msgstr ""
msgid "ProjectSettings|Enable 'Delete source branch' option by default"
msgstr ""
+msgid "ProjectSettings|Enable merge trains and pipelines for merged results"
+msgstr ""
+
msgid "ProjectSettings|Every merge creates a merge commit"
msgstr ""
@@ -17057,7 +17617,7 @@ msgstr ""
msgid "ProjectSettings|Forks"
msgstr ""
-msgid "ProjectSettings|Git Large File Storage"
+msgid "ProjectSettings|Git Large File Storage (LFS)"
msgstr ""
msgid "ProjectSettings|Internal"
@@ -17093,9 +17653,6 @@ msgstr ""
msgid "ProjectSettings|Merge options"
msgstr ""
-msgid "ProjectSettings|Merge pipelines will try to validate the post-merge result prior to merging"
-msgstr ""
-
msgid "ProjectSettings|Merge requests"
msgstr ""
@@ -17123,6 +17680,9 @@ msgstr ""
msgid "ProjectSettings|Pipelines"
msgstr ""
+msgid "ProjectSettings|Pipelines for merge requests must be enabled in the CI/CD configuration file, or pipelines could be unresolvable or dropped"
+msgstr ""
+
msgid "ProjectSettings|Pipelines must succeed"
msgstr ""
@@ -17150,6 +17710,9 @@ msgstr ""
msgid "ProjectSettings|Show link to create/view merge request when pushing from the command line"
msgstr ""
+msgid "ProjectSettings|Skipped pipelines are considered successful"
+msgstr ""
+
msgid "ProjectSettings|Snippets"
msgstr ""
@@ -17165,6 +17728,9 @@ msgstr ""
msgid "ProjectSettings|These checks must pass before merge requests can be merged"
msgstr ""
+msgid "ProjectSettings|This introduces the risk of merging changes that will not pass the pipeline."
+msgstr ""
+
msgid "ProjectSettings|This setting is applied on the server level and can be overridden by an admin."
msgstr ""
@@ -17342,15 +17908,27 @@ msgstr ""
msgid "ProjectsNew|Blank project"
msgstr ""
+msgid "ProjectsNew|Connect your external repository to GitLab CI/CD."
+msgstr ""
+
msgid "ProjectsNew|Contact an administrator to enable options for importing your project."
msgstr ""
msgid "ProjectsNew|Create"
msgstr ""
+msgid "ProjectsNew|Create a blank project to house your files, plan your work, and collaborate on code, among other things."
+msgstr ""
+
+msgid "ProjectsNew|Create blank project"
+msgstr ""
+
msgid "ProjectsNew|Create from template"
msgstr ""
+msgid "ProjectsNew|Create new project"
+msgstr ""
+
msgid "ProjectsNew|Creating project & repository."
msgstr ""
@@ -17375,6 +17953,9 @@ msgstr ""
msgid "ProjectsNew|Project description %{tag_start}(optional)%{tag_end}"
msgstr ""
+msgid "ProjectsNew|Run CI/CD for external repository"
+msgstr ""
+
msgid "ProjectsNew|Template"
msgstr ""
@@ -17636,6 +18217,9 @@ msgstr ""
msgid "Protected Branch"
msgstr ""
+msgid "Protected Branches"
+msgstr ""
+
msgid "Protected Environment"
msgstr ""
@@ -17648,6 +18232,9 @@ msgstr ""
msgid "Protected Tag"
msgstr ""
+msgid "Protected Tags"
+msgstr ""
+
msgid "Protected branches"
msgstr ""
@@ -17747,6 +18334,9 @@ msgstr ""
msgid "Provider"
msgstr ""
+msgid "Proxy support for this API is not available currently"
+msgstr ""
+
msgid "Pseudonymizer data collection"
msgstr ""
@@ -17771,6 +18361,9 @@ msgstr ""
msgid "Publish to status page"
msgstr ""
+msgid "Published on status page"
+msgstr ""
+
msgid "Publishes this issue to the associated status page."
msgstr ""
@@ -17945,6 +18538,9 @@ msgstr ""
msgid "Recipe"
msgstr ""
+msgid "Reconfigure"
+msgstr ""
+
msgid "Recover hidden stage"
msgstr ""
@@ -18030,6 +18626,9 @@ msgstr ""
msgid "Registration|Your profile"
msgstr ""
+msgid "Regulate approvals by authors/committers, based on compliance frameworks. Can be changed only at the instance level."
+msgstr ""
+
msgid "Rejected (closed)"
msgstr ""
@@ -18079,15 +18678,24 @@ msgstr ""
msgid "ReleaseAssetLinkType|Image"
msgstr ""
+msgid "ReleaseAssetLinkType|Images"
+msgstr ""
+
msgid "ReleaseAssetLinkType|Other"
msgstr ""
msgid "ReleaseAssetLinkType|Package"
msgstr ""
+msgid "ReleaseAssetLinkType|Packages"
+msgstr ""
+
msgid "ReleaseAssetLinkType|Runbook"
msgstr ""
+msgid "ReleaseAssetLinkType|Runbooks"
+msgstr ""
+
msgid "Releases"
msgstr ""
@@ -18100,6 +18708,9 @@ msgstr ""
msgid "Releases documentation"
msgstr ""
+msgid "Releases|New Release"
+msgstr ""
+
msgid "Release|Something went wrong while getting the release details"
msgstr ""
@@ -18172,6 +18783,9 @@ msgstr ""
msgid "Remove fork relationship"
msgstr ""
+msgid "Remove from batch"
+msgstr ""
+
msgid "Remove from board"
msgstr ""
@@ -18361,6 +18975,12 @@ msgstr ""
msgid "Replication"
msgstr ""
+msgid "Replication enabled"
+msgstr ""
+
+msgid "Replication paused"
+msgstr ""
+
msgid "Reply by email"
msgstr ""
@@ -18388,15 +19008,15 @@ msgstr ""
msgid "Reported %{timeAgo} by %{reportedBy}"
msgstr ""
-msgid "Reported Resource Changes: %{addNum} to add, %{changeNum} to change, %{deleteNum} to delete"
-msgstr ""
-
msgid "Reporter"
msgstr ""
msgid "Reporting"
msgstr ""
+msgid "Reports"
+msgstr ""
+
msgid "Reports|%{combinedString} and %{resolvedString}"
msgstr ""
@@ -18434,6 +19054,9 @@ msgstr ""
msgid "Reports|Failure"
msgstr ""
+msgid "Reports|Identifier"
+msgstr ""
+
msgid "Reports|Metrics reports are loading"
msgstr ""
@@ -18446,6 +19069,9 @@ msgstr ""
msgid "Reports|Metrics reports failed loading results"
msgstr ""
+msgid "Reports|Scanner"
+msgstr ""
+
msgid "Reports|Severity"
msgstr ""
@@ -18494,15 +19120,33 @@ msgstr ""
msgid "Repository cleanup has started. You will receive an email once the cleanup operation is complete."
msgstr ""
+msgid "Repository files count over the limit"
+msgstr ""
+
+msgid "Repository has an invalid default branch name."
+msgstr ""
+
+msgid "Repository has more than one branch."
+msgstr ""
+
msgid "Repository has no locks."
msgstr ""
+msgid "Repository has tags."
+msgstr ""
+
msgid "Repository maintenance"
msgstr ""
msgid "Repository mirroring"
msgstr ""
+msgid "Repository must contain at least 1 file."
+msgstr ""
+
+msgid "Repository size is above the limit."
+msgstr ""
+
msgid "Repository static objects"
msgstr ""
@@ -18512,7 +19156,7 @@ msgstr ""
msgid "Repository sync capacity"
msgstr ""
-msgid "Repository: %{counter_repositories} / Wikis: %{counter_wikis} / Build Artifacts: %{counter_build_artifacts} / LFS: %{counter_lfs_objects}"
+msgid "Repository: %{counter_repositories} / Wikis: %{counter_wikis} / Build Artifacts: %{counter_build_artifacts} / LFS: %{counter_lfs_objects} / Snippets: %{counter_snippets}"
msgstr ""
msgid "RepositorySettingsAccessLevel|Select"
@@ -18524,6 +19168,9 @@ msgstr ""
msgid "Request Headers"
msgstr ""
+msgid "Request details"
+msgstr ""
+
msgid "Request parameter %{param} is missing."
msgstr ""
@@ -18628,6 +19275,9 @@ msgstr ""
msgid "Reset template"
msgstr ""
+msgid "Reset to project defaults"
+msgstr ""
+
msgid "Resetting the authorization key for this project will require updating the authorization key in every alert source it is enabled in."
msgstr ""
@@ -18724,9 +19374,6 @@ msgstr ""
msgid "Resume"
msgstr ""
-msgid "Resume replication"
-msgstr ""
-
msgid "Resync"
msgstr ""
@@ -19338,6 +19985,9 @@ msgstr ""
msgid "SecurityReports|False positive"
msgstr ""
+msgid "SecurityReports|Group Security Dashboard"
+msgstr ""
+
msgid "SecurityReports|Hide dismissed"
msgstr ""
@@ -19392,10 +20042,13 @@ msgstr ""
msgid "SecurityReports|Remove project from dashboard"
msgstr ""
-msgid "SecurityReports|Report type"
+msgid "SecurityReports|Return to dashboard"
+msgstr ""
+
+msgid "SecurityReports|Scan details"
msgstr ""
-msgid "SecurityReports|Return to dashboard"
+msgid "SecurityReports|Scanner type"
msgstr ""
msgid "SecurityReports|Security Dashboard"
@@ -19452,6 +20105,9 @@ msgstr ""
msgid "SecurityReports|There was an error while generating the report."
msgstr ""
+msgid "SecurityReports|Unable to add %{invalidProjectsMessage}"
+msgstr ""
+
msgid "SecurityReports|Unable to add %{invalidProjects}"
msgstr ""
@@ -19566,6 +20222,9 @@ msgstr ""
msgid "Select due date"
msgstr ""
+msgid "Select file"
+msgstr ""
+
msgid "Select group or project"
msgstr ""
@@ -19629,9 +20288,6 @@ msgstr ""
msgid "Select the branch you want to set as the default for this project. All merge requests and commits will automatically be made against this branch unless you specify a different one."
msgstr ""
-msgid "Select the configured storage available for new repositories to be placed on."
-msgstr ""
-
msgid "Select the custom project template source group."
msgstr ""
@@ -19824,12 +20480,6 @@ msgstr ""
msgid "Service Desk is enabled but not yet active"
msgstr ""
-msgid "Service Desk is off"
-msgstr ""
-
-msgid "Service Desk is on"
-msgstr ""
-
msgid "Service Templates"
msgstr ""
@@ -19917,6 +20567,15 @@ msgstr ""
msgid "Set the milestone to %{milestone_reference}."
msgstr ""
+msgid "Set the number of concurrent requests this secondary node will make to the primary node while backfilling."
+msgstr ""
+
+msgid "Set the synchronization and verification capacity for the secondary node."
+msgstr ""
+
+msgid "Set the timeout in seconds to send a secondary node status to the primary and IPs allowed for the secondary nodes."
+msgstr ""
+
msgid "Set time estimate"
msgstr ""
@@ -19956,6 +20615,9 @@ msgstr ""
msgid "Set weight to %{weight}."
msgstr ""
+msgid "Set what should be replicated by choosing specific projects or groups by the secondary node."
+msgstr ""
+
msgid "SetPasswordToCloneLink|set a password"
msgstr ""
@@ -20082,6 +20744,9 @@ msgstr ""
msgid "Show complete raw log"
msgstr ""
+msgid "Show details"
+msgstr ""
+
msgid "Show file browser"
msgstr ""
@@ -20091,10 +20756,13 @@ msgstr ""
msgid "Show latest version"
msgstr ""
+msgid "Show list"
+msgstr ""
+
msgid "Show me everything"
msgstr ""
-msgid "Show me how"
+msgid "Show me how to add a pipeline"
msgstr ""
msgid "Show me more advanced stuff"
@@ -20141,9 +20809,6 @@ msgstr ""
msgid "Showing all issues"
msgstr ""
-msgid "Showing all labels"
-msgstr ""
-
msgid "Showing last %{size} of log -"
msgstr ""
@@ -20255,9 +20920,6 @@ msgstr ""
msgid "Skip outdated deployment jobs"
msgstr ""
-msgid "Skip this for now"
-msgstr ""
-
msgid "Skipped"
msgstr ""
@@ -20402,6 +21064,9 @@ msgstr ""
msgid "Something went wrong while adding your award. Please try again."
msgstr ""
+msgid "Something went wrong while applying the batch of suggestions. Please try again."
+msgstr ""
+
msgid "Something went wrong while applying the suggestion. Please try again."
msgstr ""
@@ -20504,10 +21169,13 @@ msgstr ""
msgid "Something went wrong while updating a requirement."
msgstr ""
+msgid "Something went wrong while updating assignees"
+msgstr ""
+
msgid "Something went wrong while updating your list settings"
msgstr ""
-msgid "Something went wrong with your automatic subscription renewal"
+msgid "Something went wrong with your automatic subscription renewal."
msgstr ""
msgid "Something went wrong, unable to add %{project} to dashboard"
@@ -20525,6 +21193,9 @@ msgstr ""
msgid "Something went wrong, unable to search projects"
msgstr ""
+msgid "Something went wrong."
+msgstr ""
+
msgid "Something went wrong. Please try again."
msgstr ""
@@ -20708,6 +21379,9 @@ msgstr ""
msgid "Source code"
msgstr ""
+msgid "Source code (%{fileExtension})"
+msgstr ""
+
msgid "Source is not available"
msgstr ""
@@ -20792,6 +21466,9 @@ msgstr ""
msgid "Stack trace"
msgstr ""
+msgid "Stacktrace snippet"
+msgstr ""
+
msgid "Stage"
msgstr ""
@@ -20897,6 +21574,9 @@ msgstr ""
msgid "Start thread & reopen %{noteable_name}"
msgstr ""
+msgid "Start using Directed Acyclic Graphs (DAG)"
+msgstr ""
+
msgid "Start your Free Gold Trial"
msgstr ""
@@ -21359,6 +22039,21 @@ msgstr ""
msgid "SuggestedColors|Very pale orange"
msgstr ""
+msgid "Suggestion is not applicable as the suggestion was not found."
+msgstr ""
+
+msgid "Suggestions are not applicable as one or more suggestions were not found."
+msgstr ""
+
+msgid "Suggestions are not applicable as their lines cannot overlap."
+msgstr ""
+
+msgid "Suggestions must all be on the same branch."
+msgstr ""
+
+msgid "Suggestions that change line count can't be added to batches, yet."
+msgstr ""
+
msgid "Suggestions:"
msgstr ""
@@ -21401,9 +22096,6 @@ msgstr ""
msgid "Synchronization disabled"
msgstr ""
-msgid "Synchronization of container repositories is disabled."
-msgstr ""
-
msgid "System"
msgstr ""
@@ -21584,6 +22276,32 @@ msgstr ""
msgid "Terms of Service and Privacy Policy"
msgstr ""
+msgid "Terraform|%{number} Terraform report failed to generate"
+msgid_plural "Terraform|%{number} Terraform reports failed to generate"
+msgstr[0] ""
+
+msgid "Terraform|%{number} Terraform report was generated in your pipelines"
+msgid_plural "Terraform|%{number} Terraform reports were generated in your pipelines"
+msgstr[0] ""
+
+msgid "Terraform|A Terraform report failed to generate."
+msgstr ""
+
+msgid "Terraform|A Terraform report was generated in your pipelines."
+msgstr ""
+
+msgid "Terraform|Generating the report caused an error."
+msgstr ""
+
+msgid "Terraform|Reported Resource Changes: %{addNum} to add, %{changeNum} to change, %{deleteNum} to delete"
+msgstr ""
+
+msgid "Terraform|The Terraform report %{name} failed to generate."
+msgstr ""
+
+msgid "Terraform|The Terraform report %{name} was generated in your pipelines."
+msgstr ""
+
msgid "Test"
msgstr ""
@@ -21609,7 +22327,7 @@ msgstr ""
msgid "TestHooks|Ensure the project has CI pipelines."
msgstr ""
-msgid "TestHooks|Ensure the project has at least one commit."
+msgid "TestHooks|Ensure the project has deployments."
msgstr ""
msgid "TestHooks|Ensure the project has issues."
@@ -21709,7 +22427,7 @@ msgstr ""
msgid "The Prometheus server responded with \"bad request\". Please check your queries are correct and are supported in your Prometheus version. %{documentationLink}"
msgstr ""
-msgid "The URL defined on the primary node that secondary nodes should use to contact it. Defaults to URL"
+msgid "The URL defined on the primary node that secondary nodes should use to contact it."
msgstr ""
msgid "The URL to use for connecting to Elasticsearch. Use a comma-separated list to support clustering (e.g., \"http://localhost:9200, http://localhost:9201\")."
@@ -21718,6 +22436,9 @@ msgstr ""
msgid "The X509 Certificate to use when mutual TLS is required to communicate with the external authorization service. If left blank, the server certificate is still validated when accessing over HTTPS."
msgstr ""
+msgid "The above settings apply to all projects with the selected compliance framework(s)."
+msgstr ""
+
msgid "The amount of seconds after which a request to get a secondary node status will time out."
msgstr ""
@@ -22016,9 +22737,6 @@ msgstr ""
msgid "The total stage shows the time it takes between creating an issue and deploying the code to production. The data will be automatically added once you have completed the full idea to production cycle."
msgstr ""
-msgid "The unique identifier for the Geo node. Must match %{geoNodeName} if it is set in gitlab.rb, otherwise it must match %{externalUrl} with a trailing slash"
-msgstr ""
-
msgid "The update action will time out after %{number_of_minutes} minutes. For big repositories, use a clone/push combination."
msgstr ""
@@ -22043,9 +22761,6 @@ msgstr ""
msgid "The user you are trying to deactivate has been active in the past %{minimum_inactive_days} days and cannot be deactivated"
msgstr ""
-msgid "The user-facing URL of the Geo node"
-msgstr ""
-
msgid "The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6."
msgstr ""
@@ -22136,6 +22851,9 @@ msgstr ""
msgid "There was a problem fetching project branches."
msgstr ""
+msgid "There was a problem fetching project tags."
+msgstr ""
+
msgid "There was a problem fetching project users."
msgstr ""
@@ -22187,6 +22905,9 @@ msgstr ""
msgid "There was an error fetching the %{replicableType}"
msgstr ""
+msgid "There was an error fetching the Geo Settings"
+msgstr ""
+
msgid "There was an error fetching the Node's Groups"
msgstr ""
@@ -22307,6 +23028,12 @@ msgstr ""
msgid "This %{issuable} is locked. Only <strong>project members</strong> can comment."
msgstr ""
+msgid "This %{noteableTypeText} is %{confidentialLinkStart}confidential%{linkEnd} and %{lockedLinkStart}locked%{linkEnd}."
+msgstr ""
+
+msgid "This %{noteableTypeText} is locked."
+msgstr ""
+
msgid "This %{viewer} could not be displayed because %{reason}. You can %{options} instead."
msgstr ""
@@ -22325,6 +23052,9 @@ msgstr ""
msgid "This action can lead to data loss. To prevent accidental actions we ask you to confirm your intention."
msgstr ""
+msgid "This also resolves all related threads"
+msgstr ""
+
msgid "This also resolves the discussion"
msgstr ""
@@ -22415,6 +23145,9 @@ msgstr ""
msgid "This epic does not exist or you don't have sufficient permission."
msgstr ""
+msgid "This feature is currently in beta. We invite you to %{linkStart}give feedback%{linkEnd}."
+msgstr ""
+
msgid "This feature requires local storage to be enabled"
msgstr ""
@@ -22448,7 +23181,7 @@ msgstr ""
msgid "This is a Work in Progress"
msgstr ""
-msgid "This is a confidential issue."
+msgid "This is a confidential %{noteableTypeText}."
msgstr ""
msgid "This is a delayed job to run in %{remainingTime}"
@@ -22475,9 +23208,6 @@ msgstr ""
msgid "This is your current session"
msgstr ""
-msgid "This issue is %{confidentialLinkStart}confidential%{linkEnd} and %{lockedLinkStart}locked%{linkEnd}."
-msgstr ""
-
msgid "This issue is confidential"
msgstr ""
@@ -22487,9 +23217,6 @@ msgstr ""
msgid "This issue is in a child epic of the filtered epic"
msgstr ""
-msgid "This issue is locked."
-msgstr ""
-
msgid "This job depends on other jobs with expired/erased artifacts: %{invalid_dependencies}"
msgstr ""
@@ -22589,6 +23316,9 @@ msgstr ""
msgid "This license has already expired."
msgstr ""
+msgid "This link points to external content"
+msgstr ""
+
msgid "This may expose confidential information as the selected fork is in another namespace that can have other members."
msgstr ""
@@ -22616,6 +23346,9 @@ msgstr ""
msgid "This page will be removed in a future release."
msgstr ""
+msgid "This pipeline does not use the %{codeStart}needs%{codeEnd} keyword and can't be represented as a directed acyclic graph."
+msgstr ""
+
msgid "This pipeline makes use of a predefined CI/CD configuration enabled by %{strongStart}Auto DevOps.%{strongEnd}"
msgstr ""
@@ -22844,6 +23577,9 @@ msgstr ""
msgid "Time from last commit to merge"
msgstr ""
+msgid "Time in seconds"
+msgstr ""
+
msgid "Time in seconds GitLab will wait for a response from the external service. When the service does not respond in time, access will be denied."
msgstr ""
@@ -23263,9 +23999,15 @@ msgstr ""
msgid "Total artifacts size: %{total_size}"
msgstr ""
+msgid "Total cores (CPUs)"
+msgstr ""
+
msgid "Total issues"
msgstr ""
+msgid "Total memory (GB)"
+msgstr ""
+
msgid "Total test time for all commits/merges"
msgstr ""
@@ -23467,6 +24209,9 @@ msgstr ""
msgid "U2F only works with HTTPS-enabled websites. Contact your administrator for more details."
msgstr ""
+msgid "UI Development Kit"
+msgstr ""
+
msgid "URL"
msgstr ""
@@ -23539,9 +24284,6 @@ msgstr ""
msgid "Unable to load the merge request widget. Try reloading the page."
msgstr ""
-msgid "Unable to process group import file"
-msgstr ""
-
msgid "Unable to resolve"
msgstr ""
@@ -23557,6 +24299,9 @@ msgstr ""
msgid "Unable to sign you in to the group with SAML due to \"%{reason}\""
msgstr ""
+msgid "Unable to suggest a path. Please refresh and try again."
+msgstr ""
+
msgid "Unable to update label prioritization at this time"
msgstr ""
@@ -23572,6 +24317,9 @@ msgstr ""
msgid "Unarchiving the project will restore people's ability to make changes to it. The repository can be committed to, and issues, comments, and other entities can be created. %{strong_start}Once active, this project shows up in the search and on the dashboard.%{strong_end}"
msgstr ""
+msgid "Unassign from commenting user"
+msgstr ""
+
msgid "Unblock"
msgstr ""
@@ -23647,6 +24395,9 @@ msgstr ""
msgid "Unreachable"
msgstr ""
+msgid "Unrecognized cluster type"
+msgstr ""
+
msgid "Unresolve"
msgstr ""
@@ -23707,6 +24458,9 @@ msgstr ""
msgid "Unsubscribes from this %{quick_action_target}."
msgstr ""
+msgid "Unsupported todo type passed. Supported todo types are: %{todo_types}"
+msgstr ""
+
msgid "Until"
msgstr ""
@@ -23734,6 +24488,9 @@ msgstr ""
msgid "Update approval rule"
msgstr ""
+msgid "Update approvers"
+msgstr ""
+
msgid "Update failed"
msgstr ""
@@ -23743,6 +24500,9 @@ msgstr ""
msgid "Update it"
msgstr ""
+msgid "Update iteration"
+msgstr ""
+
msgid "Update now"
msgstr ""
@@ -23788,7 +24548,7 @@ msgstr ""
msgid "Updated at"
msgstr ""
-msgid "Updated to"
+msgid "Updated to %{linkStart}chart v%{linkEnd}"
msgstr ""
msgid "Updating"
@@ -23818,6 +24578,9 @@ msgstr ""
msgid "Upload CSV file"
msgstr ""
+msgid "Upload License"
+msgstr ""
+
msgid "Upload New File"
msgstr ""
@@ -23899,16 +24662,19 @@ msgstr ""
msgid "UsageQuota|Pipelines"
msgstr ""
+msgid "UsageQuota|Purchase more storage"
+msgstr ""
+
msgid "UsageQuota|Repositories"
msgstr ""
msgid "UsageQuota|Repository"
msgstr ""
-msgid "UsageQuota|Storage"
+msgid "UsageQuota|Snippets"
msgstr ""
-msgid "UsageQuota|Storage usage:"
+msgid "UsageQuota|Storage"
msgstr ""
msgid "UsageQuota|This namespace has no projects which use shared runners"
@@ -23941,6 +24707,12 @@ msgstr ""
msgid "UsageQuota|Wikis"
msgstr ""
+msgid "UsageQuota|You used: %{usage} %{limit}"
+msgstr ""
+
+msgid "UsageQuota|out of %{formattedLimit} of your namespace storage"
+msgstr ""
+
msgid "Use %{code_start}::%{code_end} to create a %{link_start}scoped label set%{link_end} (eg. %{code_start}priority::1%{code_end})"
msgstr ""
@@ -24007,6 +24779,9 @@ msgstr ""
msgid "User IDs"
msgstr ""
+msgid "User Lists can only be created and modified with %{linkStart}the API%{linkEnd}"
+msgstr ""
+
msgid "User OAuth applications"
msgstr ""
@@ -24031,6 +24806,9 @@ msgstr ""
msgid "User key was successfully removed."
msgstr ""
+msgid "User list %{name} will be removed. Are you sure?"
+msgstr ""
+
msgid "User map"
msgstr ""
@@ -24052,148 +24830,10 @@ msgstr ""
msgid "User was successfully updated."
msgstr ""
-msgid "UserOnboardingTour|%{activeTour}/%{totalTours}"
-msgstr ""
-
-msgid "UserOnboardingTour|%{completed}/%{total} steps completed"
-msgstr ""
-
-msgid "UserOnboardingTour|%{emphasisStart}Well done!%{emphasisEnd}%{lineBreak}%{lineBreak}That's it for our guided tour, congratulations for making it all the way to the end!%{lineBreak}%{lineBreak}We hope this gave you a good overview of GitLab and how it can help you. We'll now show you how to create your own project and invite your colleagues."
-msgstr ""
-
-msgid "UserOnboardingTour|Adding other members to a project is done through Project Settings. Click on %{emphasisStart}Settings%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Alright, that's it for Commits. Let's take a look at the %{emphasisStart}Branches%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Awesome! Now click on %{emphasisStart}Members%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Click on one of the %{emphasisStart}Compare%{emphasisEnd} buttons to compare a branch to master."
-msgstr ""
-
-msgid "UserOnboardingTour|Click on one of the %{emphasisStart}pipeline IDs%{emphasisEnd} to see the details of a pipeline."
-msgstr ""
-
-msgid "UserOnboardingTour|Click to open the latest commit to see its details."
-msgstr ""
-
-msgid "UserOnboardingTour|Close 'Learn GitLab'"
-msgstr ""
-
-msgid "UserOnboardingTour|Commits are shown in chronological order and can be filtered by the commit message or by the branch."
-msgstr ""
-
-msgid "UserOnboardingTour|Create a project"
-msgstr ""
-
-msgid "UserOnboardingTour|Exit 'Learn GitLab'"
-msgstr ""
-
-msgid "UserOnboardingTour|Got it"
-msgstr ""
-
-msgid "UserOnboardingTour|Great job! %{clapHands} We hope the tour was helpful and that you learned how to use GitLab.%{lineBreak}%{lineBreak}We'd love to get your feedback on this tour.%{lineBreak}%{lineBreak}%{emphasisStart}How helpful would you say this guided tour was?%{emphasisEnd}%{lineBreak}%{lineBreak}"
-msgstr ""
-
-msgid "UserOnboardingTour|Guided GitLab Tour"
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can compare the changes of this branch to another one. Changes are divided by files so that it's easier to see what was changed where."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can create a project from scratch, start with a template or import a repository from other platforms. Whatever you choose, we'll guide you through the process.%{lineBreak}%{lineBreak}Fill in your new project information and click on %{emphasisStart}Create Project%{emphasisEnd} to progress to the next step."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can see the breakdown of the pipelines: its stages and jobs in each of the stages and their status.%{lineBreak}%{lineBreak}Our CI/CD pipelines are quite complex, most of our users have fewer and simpler pipelines."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can see the current members of the project (just you at the moment) and invite new members.%{lineBreak}%{lineBreak}You can invite multiple members at once (existing GitLab users or invite by email) and you can also set their roles and permissions.%{lineBreak}%{lineBreak}Add a few members and click on %{emphasisStart}Add to project%{emphasisEnd} to complete this step."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can see what changes were made with this commit, on what branch and if there's a related merge request. The status of the pipeline will also show up if CI/CD is set up.%{lineBreak}%{lineBreak}You can also comment on the lines of code that were changed and start a discussion with your colleagues!"
-msgstr ""
-
-msgid "UserOnboardingTour|Here's an overview of branches in the %{emphasisStart}%{projectName}%{emphasisEnd} project. They're split into Active and Stale.%{lineBreak}%{lineBreak}From here, you can create a new merge request from a branch, or compare the branch to any other branch in the project. By default, it will compare it to the master branch."
-msgstr ""
-
-msgid "UserOnboardingTour|Invite colleagues"
-msgstr ""
-
-msgid "UserOnboardingTour|Issues are great for communicating and keeping track of progress in GitLab. These are all issues that are open in the %{emphasisStart}%{projectName}%{emphasisEnd}.%{lineBreak}%{lineBreak}You can help us improve GitLab by contributing work to issues that are labeled <span class=\"badge color-label accept-mr-label\">Accepting merge requests</span>.%{lineBreak}%{lineBreak}This list can be filtered by labels, milestones, assignees, authors... We'll show you how it looks when the list is filtered by a label."
-msgstr ""
-
-msgid "UserOnboardingTour|Learn GitLab"
-msgstr ""
-
-msgid "UserOnboardingTour|Let's take a closer look at a merge request. Click on the title of one."
-msgstr ""
-
-msgid "UserOnboardingTour|Let's take a closer look at all the commits. Click on %{emphasisStart}Commits%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Let's take a closer look at the repository of this project. Click on %{emphasisStart}Repository%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|No thanks"
-msgstr ""
-
-msgid "UserOnboardingTour|Ok, let's go"
-msgstr ""
-
-msgid "UserOnboardingTour|Ok, show me"
-msgstr ""
-
-msgid "UserOnboardingTour|Open one of the issues by clicking on its title."
-msgstr ""
-
-msgid "UserOnboardingTour|Restart this step"
-msgstr ""
-
-msgid "UserOnboardingTour|Skip this step"
-msgstr ""
-
-msgid "UserOnboardingTour|Sweet! Your project was created and is ready to be used.%{lineBreak}%{lineBreak}You can start adding files to the repository or clone it. One last thing we want to show you is how to invite your colleagues to your new project."
-msgstr ""
-
-msgid "UserOnboardingTour|Take a look. Here's a nifty menu for quickly creating issues, merge requests, snippets, projects and groups. Click on it and select \"New project\" from the \"GitLab\" section to get started."
-msgstr ""
-
-msgid "UserOnboardingTour|Thanks for taking the guided tour. Remember, if you want to go through it again, you can start %{emphasisStart}Learn GitLab%{emphasisEnd} in the help menu on the top right."
-msgstr ""
-
-msgid "UserOnboardingTour|Thanks for the feedback! %{thumbsUp}"
-msgstr ""
-
-msgid "UserOnboardingTour|That's it for issues. Let'st take a look at %{emphasisStart}Merge Requests%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|That's it for merge requests. Now for the final part of this guided tour - the %{emphasisStart}CI/CD%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|That's it for the Repository. Let's take a look at the %{emphasisStart}Issues%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|The structure of this page is very similar to issues. Status, description, discussion and the sidebar are all here.%{lineBreak}%{lineBreak}But take a look below the description and you'll notice that there's more information about the merge request, the CI/CD pipeline and the options for approving it.%{lineBreak}%{lineBreak}Alongside the discussion you can also see more information about commits in this merge request, the status of pipelines and review all changes that were made."
-msgstr ""
-
-msgid "UserOnboardingTour|There's a lot of information here but don't worry, we'll go through it.%{lineBreak}%{lineBreak}On the top you can see the status of the issue and when it was opened and by whom. Directly below it is the issue description and below that are other %{emphasisStart}related issues%{emphasisEnd} and %{emphasisStart}merge requests%{emphasisEnd} (if any). Then below that is the %{emphasisStart}discussion%{emphasisEnd}, that's where most of the communication happens.%{lineBreak}%{lineBreak}On the right, there's a sidebar where you can view/change the %{emphasisStart}assignee, milestone, due date, labels, weight%{emphasisEnd}, etc."
-msgstr ""
-
-msgid "UserOnboardingTour|These are all the CI/CD pipelines we have for our %{emphasisStart}%{projectName}%{emphasisEnd} project.%{lineBreak}%{lineBreak}Here you can see the status of each pipeline, for what commit it's running for, its stages and the status for them."
-msgstr ""
-
-msgid "UserOnboardingTour|These are all the issues that are available for community contributions. Let's take a closer look at one of them."
+msgid "UserList|Delete %{name}?"
msgstr ""
-msgid "UserOnboardingTour|This is an overview of all merge requests in this project. Similarly to the issues overview it can be filtered down by things like labels, milestones, authors, assignees, etc."
-msgstr ""
-
-msgid "UserOnboardingTour|This is the repository for the %{emphasisStart}%{projectName}%{emphasisEnd} project. All our code is stored here. Feel free to explore and take a closer look at folders and files.%{lineBreak}%{lineBreak}Above the file structure you can see the latest commit, who the author is and the status of the CI/CD pipeline.%{lineBreak}%{lineBreak}If you scroll down below the file structure, you'll find the Readme of this project. This is defined in the README.md file at the root of the repository."
-msgstr ""
-
-msgid "UserOnboardingTour|Welcome to the project overview of the %{emphasisStart}%{projectName}%{emphasisEnd} project. This is the project that we use to work on GitLab. At first, a project seems like a simple repository, but at GitLab, a project is so much more.%{lineBreak}%{lineBreak}You can create projects for hosting your codebase, use it as an issue tracker, collaborate on code, and continuously build, test, and deploy your app with built-in GitLab CI/CD."
+msgid "UserList|created %{timeago}"
msgstr ""
msgid "UserProfile|Activity"
@@ -24346,6 +24986,9 @@ msgstr ""
msgid "UsersSelect|Unassigned"
msgstr ""
+msgid "Using %{codeStart}needs%{codeEnd} allows jobs to run before their stage is reached, as soon as their individual dependencies are met, which speeds up your pipelines."
+msgstr ""
+
msgid "Using %{code_start}::%{code_end} denotes a %{link_start}scoped label set%{link_end}"
msgstr ""
@@ -24430,9 +25073,6 @@ msgstr ""
msgid "Versions"
msgstr ""
-msgid "Very helpful"
-msgstr ""
-
msgid "View Documentation"
msgstr ""
@@ -24579,6 +25219,9 @@ msgstr ""
msgid "VisibilityLevel|Unknown"
msgstr ""
+msgid "Visit settings page"
+msgstr ""
+
msgid "VisualReviewApp|%{stepStart}Step 1%{stepEnd}. Copy the following script:"
msgstr ""
@@ -24672,6 +25315,9 @@ msgstr ""
msgid "VulnerabilityManagement|Something went wrong while trying to delete the comment. Please try again later."
msgstr ""
+msgid "VulnerabilityManagement|Something went wrong while trying to refresh the vulnerability. Please try again later."
+msgstr ""
+
msgid "VulnerabilityManagement|Something went wrong while trying to retrieve the vulnerability history. Please try again later."
msgstr ""
@@ -24714,6 +25360,9 @@ msgstr ""
msgid "Vulnerability|Class"
msgstr ""
+msgid "Vulnerability|Crash Address"
+msgstr ""
+
msgid "Vulnerability|Description"
msgstr ""
@@ -24789,9 +25438,6 @@ msgstr ""
msgid "We couldn't reach the Prometheus server. Either the server no longer exists or the configuration details need updating."
msgstr ""
-msgid "We created a short guided tour that will help you learn the basics of GitLab and how it will help you be better at your job. It should only take a couple of minutes. You will be guided by two types of helpers, best recognized by their color."
-msgstr ""
-
msgid "We detected potential spam in the %{humanized_resource_name}. Please solve the reCAPTCHA to proceed."
msgstr ""
@@ -24825,6 +25471,9 @@ msgstr ""
msgid "We've found no vulnerabilities"
msgstr ""
+msgid "Web Application Firewall"
+msgstr ""
+
msgid "Web IDE"
msgstr ""
@@ -24885,9 +25534,6 @@ msgstr ""
msgid "Welcome to GitLab.com<br>@%{name}!"
msgstr ""
-msgid "Welcome to the Guided GitLab Tour"
-msgstr ""
-
msgid "Welcome to the guided GitLab tour"
msgstr ""
@@ -24934,9 +25580,6 @@ msgstr ""
msgid "While it's rare to have no vulnerabilities, it can happen. In any event, we ask that you please double check your settings to make sure you've set up your dashboard correctly."
msgstr ""
-msgid "White helpers give contextual information."
-msgstr ""
-
msgid "Who can be an approver?"
msgstr ""
@@ -24982,12 +25625,18 @@ msgstr ""
msgid "WikiEmptyIssueMessage|Suggest wiki improvement"
msgstr ""
+msgid "WikiEmptyIssueMessage|You must be a group member in order to add wiki pages. If you have suggestions for how to improve the wiki for this group, consider opening an issue in the %{issues_link}."
+msgstr ""
+
msgid "WikiEmptyIssueMessage|You must be a project member in order to add wiki pages. If you have suggestions for how to improve the wiki for this project, consider opening an issue in the %{issues_link}."
msgstr ""
msgid "WikiEmptyIssueMessage|issue tracker"
msgstr ""
+msgid "WikiEmpty|A wiki is where you can store all the details about your group. This can include why you've created it, its principles, how to use it, and so on."
+msgstr ""
+
msgid "WikiEmpty|A wiki is where you can store all the details about your project. This can include why you've created it, its principles, how to use it, and so on."
msgstr ""
@@ -24997,12 +25646,21 @@ msgstr ""
msgid "WikiEmpty|Suggest wiki improvement"
msgstr ""
+msgid "WikiEmpty|The wiki lets you write documentation for your group"
+msgstr ""
+
msgid "WikiEmpty|The wiki lets you write documentation for your project"
msgstr ""
+msgid "WikiEmpty|This group has no wiki pages"
+msgstr ""
+
msgid "WikiEmpty|This project has no wiki pages"
msgstr ""
+msgid "WikiEmpty|You must be a group member in order to add wiki pages."
+msgstr ""
+
msgid "WikiEmpty|You must be a project member in order to add wiki pages."
msgstr ""
@@ -25279,13 +25937,13 @@ msgstr ""
msgid "You can now export your security dashboard to a CSV report."
msgstr ""
-msgid "You can now submit a merge request to get this change into the original branch."
+msgid "You can now manage alert endpoint configuration in the Alerts section on the Operations settings page. Fields on this page have been deprecated."
msgstr ""
-msgid "You can now submit a merge request to get this change into the original project."
+msgid "You can now submit a merge request to get this change into the original branch."
msgstr ""
-msgid "You can only add files when you are on a branch"
+msgid "You can now submit a merge request to get this change into the original project."
msgstr ""
msgid "You can only edit files when you are on a branch"
@@ -25507,7 +26165,7 @@ msgstr ""
msgid "You need to upload a Google Takeout archive."
msgstr ""
-msgid "You reached %{usage_in_percent} of %{namespace_name}'s capacity (%{used_storage} of %{storage_limit})"
+msgid "You reached %{usage_in_percent} of %{namespace_name}'s storage capacity (%{used_storage} of %{storage_limit})"
msgstr ""
msgid "You tried to fork %{link_to_the_project} but it failed for the following reason:"
@@ -25810,13 +26468,13 @@ msgstr ""
msgid "Your subscription expired!"
msgstr ""
-msgid "Your subscription has been downgraded"
+msgid "Your subscription has been downgraded."
msgstr ""
-msgid "Your subscription will automatically renew in %{remaining_days}"
+msgid "Your subscription will automatically renew in %{remaining_days}."
msgstr ""
-msgid "Your subscription will expire in %{remaining_days}"
+msgid "Your subscription will expire in %{remaining_days}."
msgstr ""
msgid "Zoom meeting added"
@@ -25895,9 +26553,6 @@ msgstr ""
msgid "branch name"
msgstr ""
-msgid "build pipeline reference mismatch"
-msgstr ""
-
msgid "by"
msgstr ""
@@ -25916,6 +26571,9 @@ msgstr ""
msgid "cannot block others"
msgstr ""
+msgid "cannot contain HTML/XML tags, including any word between angle brackets (<,>)."
+msgstr ""
+
msgid "cannot include leading slash or directory traversal."
msgstr ""
@@ -25946,50 +26604,6 @@ msgstr ""
msgid "ciReport|%{remainingPackagesCount} more"
msgstr ""
-msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability"
-msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities"
-msgstr[0] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability for the source branch only"
-msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities for the source branch only"
-msgstr[0] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerability"
-msgid_plural "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerabilities"
-msgstr[0] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new vulnerability"
-msgid_plural "ciReport|%{reportType} %{status} detected %{newCount} new vulnerabilities"
-msgstr[0] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities for the source branch only"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{fixedCount} fixed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} vulnerability for the source branch only"
-msgid_plural "ciReport|%{reportType} %{status} detected %{newCount} vulnerabilities for the source branch only"
-msgstr[0] ""
-
-msgid "ciReport|%{reportType} %{status} detected no new vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected no vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected no vulnerabilities for the source branch only"
-msgstr ""
-
msgid "ciReport|%{reportType} is loading"
msgstr ""
@@ -26008,7 +26622,7 @@ msgstr ""
msgid "ciReport|All projects"
msgstr ""
-msgid "ciReport|All report types"
+msgid "ciReport|All scanner types"
msgstr ""
msgid "ciReport|All severities"
@@ -26032,6 +26646,9 @@ msgstr ""
msgid "ciReport|Container scanning detects known vulnerabilities in your docker images."
msgstr ""
+msgid "ciReport|Coverage Fuzzing"
+msgstr ""
+
msgid "ciReport|Create a merge request to implement this solution, or download and apply the patch manually."
msgstr ""
@@ -26189,6 +26806,9 @@ msgstr ""
msgid "customize"
msgstr ""
+msgid "data"
+msgstr ""
+
msgid "date must not be after 9999-12-31"
msgstr ""
@@ -26257,6 +26877,9 @@ msgstr ""
msgid "entries cannot contain HTML tags"
msgstr ""
+msgid "epic"
+msgstr ""
+
msgid "error"
msgstr ""
@@ -26278,9 +26901,6 @@ msgstr ""
msgid "expires on %{timebox_due_date}"
msgstr ""
-msgid "external_url"
-msgstr ""
-
msgid "failed"
msgstr ""
@@ -26315,9 +26935,6 @@ msgstr ""
msgid "from"
msgstr ""
-msgid "geo_node_name"
-msgstr ""
-
msgid "group"
msgstr ""
@@ -26339,9 +26956,6 @@ msgstr ""
msgid "https://your-bitbucket-server"
msgstr ""
-msgid "image"
-msgstr ""
-
msgid "image diff"
msgstr ""
@@ -26484,6 +27098,9 @@ msgstr ""
msgid "load it anyway"
msgstr ""
+msgid "loading"
+msgstr ""
+
msgid "locked by %{path_lock_user_name} %{created_at}"
msgstr ""
@@ -26611,9 +27228,6 @@ msgstr ""
msgid "mrWidget|Deployment statistics are not available currently"
msgstr ""
-msgid "mrWidget|Detect issues before deployment with a CI pipeline that continuously tests your code. We created a quick guide that will show you how to create one. Make your code more secure and more robust in just a minute."
-msgstr ""
-
msgid "mrWidget|Did not close"
msgstr ""
@@ -26776,6 +27390,9 @@ msgstr ""
msgid "mrWidget|To approve this merge request, please enter your password. This project requires all approvals to be authenticated."
msgstr ""
+msgid "mrWidget|Use %{linkStart}CI pipelines to test your code%{linkEnd}, simply add a GitLab CI configuration file to your project. It only takes a minute to make your code more secure and robust."
+msgstr ""
+
msgid "mrWidget|When this merge request is ready, remove the WIP: prefix from the title to allow it to be merged"
msgstr ""
@@ -26812,6 +27429,9 @@ msgstr ""
msgid "must be greater than start date"
msgstr ""
+msgid "must contain only valid frameworks"
+msgstr ""
+
msgid "my-awesome-group"
msgstr ""
@@ -26824,6 +27444,9 @@ msgstr ""
msgid "needs to be between 10 minutes and 1 month"
msgstr ""
+msgid "never"
+msgstr ""
+
msgid "never expires"
msgstr ""
@@ -26851,6 +27474,9 @@ msgstr ""
msgid "nounSeries|%{firstItem} and %{lastItem}"
msgstr ""
+msgid "nounSeries|%{item}"
+msgstr ""
+
msgid "nounSeries|%{item}, %{nextItem}"
msgstr ""
@@ -26982,6 +27608,9 @@ msgstr ""
msgid "revised"
msgstr ""
+msgid "satisfied"
+msgstr ""
+
msgid "score"
msgstr ""
@@ -27012,6 +27641,9 @@ msgstr ""
msgid "severity|Unknown"
msgstr ""
+msgid "should be an array of %{object_name} objects"
+msgstr ""
+
msgid "should be greater than or equal to %{access} inherited membership from group %{group_name}"
msgstr ""
@@ -27066,10 +27698,13 @@ msgstr ""
msgid "suggestPipeline|2/2: Commit your changes"
msgstr ""
-msgid "suggestPipeline|Commit the changes and your pipeline will automatically run for the first time."
+msgid "suggestPipeline|Choose %{boldStart}Code Quality%{boldEnd} to add a pipeline that tests the quality of your code."
msgstr ""
-msgid "suggestPipeline|We recommend the %{boldStart}Code Quality%{boldEnd} template, which will add a report widget to your Merge Requests. This way you’ll learn about code quality degradations much sooner. %{footerStart} Goodbye technical debt! %{footerEnd}"
+msgid "suggestPipeline|The template is ready! You can now commit it to create your first pipeline."
+msgstr ""
+
+msgid "suggestPipeline|We’re adding a GitLab CI configuration file to add a pipeline to the project. You could create it manually, but we recommend that you start with a GitLab template that works out of the box."
msgstr ""
msgid "syntax is correct"
@@ -27129,6 +27764,9 @@ msgstr ""
msgid "uses Kubernetes clusters to deploy your code!"
msgstr ""
+msgid "v%{version} published %{timeAgo}"
+msgstr ""
+
msgid "verify ownership"
msgstr ""
diff --git a/locale/ig_NG/gitlab.po b/locale/ig_NG/gitlab.po
new file mode 100644
index 00000000000..d3c98ba8315
--- /dev/null
+++ b/locale/ig_NG/gitlab.po
@@ -0,0 +1,27832 @@
+msgid ""
+msgstr ""
+"Project-Id-Version: gitlab-ee\n"
+"Report-Msgid-Bugs-To: \n"
+"Last-Translator: \n"
+"Language-Team: Igbo\n"
+"Language: ig_NG\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=1; plural=0;\n"
+"X-Crowdin-Project: gitlab-ee\n"
+"X-Crowdin-Project-ID: 288872\n"
+"X-Crowdin-Language: ig\n"
+"X-Crowdin-File: /master/locale/gitlab.pot\n"
+"X-Crowdin-File-ID: 6\n"
+"PO-Revision-Date: 2020-07-02 01:28\n"
+
+msgid " %{start} to %{end}"
+msgstr ""
+
+msgid " (from %{timeoutSource})"
+msgstr ""
+
+msgid " Collected %{time}"
+msgstr ""
+
+msgid " Please sign in."
+msgstr ""
+
+msgid " Try to %{action} this file again."
+msgstr ""
+
+msgid " You need to do this before %{grace_period_deadline}."
+msgstr ""
+
+msgid " and"
+msgstr ""
+
+msgid " and "
+msgstr ""
+
+msgid " and %{sliced}"
+msgstr ""
+
+msgid " degraded on %d point"
+msgid_plural " degraded on %d points"
+msgstr[0] ""
+
+msgid " improved on %d point"
+msgid_plural " improved on %d points"
+msgstr[0] ""
+
+msgid " or "
+msgstr ""
+
+msgid " or <!merge request id>"
+msgstr ""
+
+msgid " or <#issue id>"
+msgstr ""
+
+msgid " or <&epic id>"
+msgstr ""
+
+msgid " or references (e.g. path/to/project!merge_request_id)"
+msgstr ""
+
+msgid "\"%{path}\" did not exist on \"%{ref}\""
+msgstr ""
+
+msgid "%d URL scanned"
+msgid_plural "%d URLs scanned"
+msgstr[0] ""
+
+msgid "%d approver"
+msgid_plural "%d approvers"
+msgstr[0] ""
+
+msgid "%d approver (you've approved)"
+msgid_plural "%d approvers (you've approved)"
+msgstr[0] ""
+
+msgid "%d changed file"
+msgid_plural "%d changed files"
+msgstr[0] ""
+
+msgid "%d child epic"
+msgid_plural "%d child epics"
+msgstr[0] ""
+
+msgid "%d code quality issue"
+msgid_plural "%d code quality issues"
+msgstr[0] ""
+
+msgid "%d comment"
+msgid_plural "%d comments"
+msgstr[0] ""
+
+msgid "%d comment on this commit"
+msgid_plural "%d comments on this commit"
+msgstr[0] ""
+
+msgid "%d commit"
+msgid_plural "%d commits"
+msgstr[0] ""
+
+msgid "%d commit behind"
+msgid_plural "%d commits behind"
+msgstr[0] ""
+
+msgid "%d commit,"
+msgid_plural "%d commits,"
+msgstr[0] ""
+
+msgid "%d commits"
+msgstr ""
+
+msgid "%d contribution"
+msgid_plural "%d contributions"
+msgstr[0] ""
+
+msgid "%d day"
+msgid_plural "%d days"
+msgstr[0] ""
+
+msgid "%d error"
+msgid_plural "%d errors"
+msgstr[0] ""
+
+msgid "%d exporter"
+msgid_plural "%d exporters"
+msgstr[0] ""
+
+msgid "%d failed"
+msgid_plural "%d failed"
+msgstr[0] ""
+
+msgid "%d fixed test result"
+msgid_plural "%d fixed test results"
+msgstr[0] ""
+
+msgid "%d group selected"
+msgid_plural "%d groups selected"
+msgstr[0] ""
+
+msgid "%d inaccessible merge request"
+msgid_plural "%d inaccessible merge requests"
+msgstr[0] ""
+
+msgid "%d issue"
+msgid_plural "%d issues"
+msgstr[0] ""
+
+msgid "%d issue selected"
+msgid_plural "%d issues selected"
+msgstr[0] ""
+
+msgid "%d layer"
+msgid_plural "%d layers"
+msgstr[0] ""
+
+msgid "%d merge request"
+msgid_plural "%d merge requests"
+msgstr[0] ""
+
+msgid "%d merge request that you don't have access to."
+msgid_plural "%d merge requests that you don't have access to."
+msgstr[0] ""
+
+msgid "%d metric"
+msgid_plural "%d metrics"
+msgstr[0] ""
+
+msgid "%d milestone"
+msgid_plural "%d milestones"
+msgstr[0] ""
+
+msgid "%d minute"
+msgid_plural "%d minutes"
+msgstr[0] ""
+
+msgid "%d more comment"
+msgid_plural "%d more comments"
+msgstr[0] ""
+
+msgid "%d personal project will be removed and cannot be restored."
+msgid_plural "%d personal projects will be removed and cannot be restored."
+msgstr[0] ""
+
+msgid "%d project"
+msgid_plural "%d projects"
+msgstr[0] ""
+
+msgid "%d request with warnings"
+msgid_plural "%d requests with warnings"
+msgstr[0] ""
+
+msgid "%d second"
+msgid_plural "%d seconds"
+msgstr[0] ""
+
+msgid "%d shard selected"
+msgid_plural "%d shards selected"
+msgstr[0] ""
+
+msgid "%d tag"
+msgid_plural "%d tags"
+msgstr[0] ""
+
+msgid "%d unresolved thread"
+msgid_plural "%d unresolved threads"
+msgstr[0] ""
+
+msgid "%d vulnerability"
+msgid_plural "%d vulnerabilities"
+msgstr[0] ""
+
+msgid "%d vulnerability dismissed"
+msgid_plural "%d vulnerabilities dismissed"
+msgstr[0] ""
+
+msgid "%s additional commit has been omitted to prevent performance issues."
+msgid_plural "%s additional commits have been omitted to prevent performance issues."
+msgstr[0] ""
+
+msgid "%{actionText} & %{openOrClose} %{noteable}"
+msgstr ""
+
+msgid "%{authorsName}'s thread"
+msgstr ""
+
+msgid "%{commit_author_link} authored %{commit_timeago}"
+msgstr ""
+
+msgid "%{completedWeight} of %{totalWeight} weight completed"
+msgstr ""
+
+msgid "%{containerScanningLinkStart}Container Scanning%{containerScanningLinkEnd} and/or %{dependencyScanningLinkStart}Dependency Scanning%{dependencyScanningLinkEnd} must be enabled. %{securityBotLinkStart}GitLab-Security-Bot%{securityBotLinkEnd} will be the author of the auto-created merge request. %{moreInfoLinkStart}More information%{moreInfoLinkEnd}."
+msgstr ""
+
+msgid "%{cores} cores"
+msgstr ""
+
+msgid "%{count} LOC/commit"
+msgstr ""
+
+msgid "%{count} approval required from %{name}"
+msgid_plural "%{count} approvals required from %{name}"
+msgstr[0] ""
+
+msgid "%{count} approvals from %{name}"
+msgstr ""
+
+msgid "%{count} files touched"
+msgstr ""
+
+msgid "%{count} more"
+msgstr ""
+
+msgid "%{count} more assignees"
+msgstr ""
+
+msgid "%{count} more release"
+msgid_plural "%{count} more releases"
+msgstr[0] ""
+
+msgid "%{count} of %{required} approvals from %{name}"
+msgstr ""
+
+msgid "%{count} of %{total}"
+msgstr ""
+
+msgid "%{count} participant"
+msgid_plural "%{count} participants"
+msgstr[0] ""
+
+msgid "%{count} pending comment"
+msgid_plural "%{count} pending comments"
+msgstr[0] ""
+
+msgid "%{count} related %{pluralized_subject}: %{links}"
+msgstr ""
+
+msgid "%{dashboard_path} could not be found."
+msgstr ""
+
+msgid "%{days} days until tags are automatically removed"
+msgstr ""
+
+msgid "%{deployLinkStart}Use a template to deploy to ECS%{deployLinkEnd}, or use a docker image to %{commandsLinkStart}run AWS commands in GitLab CI/CD%{commandsLinkEnd}."
+msgstr ""
+
+msgid "%{description}- Sentry event: %{errorUrl}- First seen: %{firstSeen}- Last seen: %{lastSeen} %{countLabel}: %{count}%{userCountLabel}: %{userCount}"
+msgstr ""
+
+msgid "%{duration}ms"
+msgstr ""
+
+msgid "%{edit_in_new_fork_notice} Try to cherry-pick this commit again."
+msgstr ""
+
+msgid "%{edit_in_new_fork_notice} Try to create a new directory again."
+msgstr ""
+
+msgid "%{edit_in_new_fork_notice} Try to revert this commit again."
+msgstr ""
+
+msgid "%{edit_in_new_fork_notice} Try to upload a file again."
+msgstr ""
+
+msgid "%{extra} more downstream pipelines"
+msgstr ""
+
+msgid "%{filePath} deleted"
+msgstr ""
+
+msgid "%{firstLabel} +%{labelCount} more"
+msgstr ""
+
+msgid "%{firstMilestoneName} + %{numberOfOtherMilestones} more"
+msgstr ""
+
+msgid "%{global_id} is not a valid id for %{expected_type}."
+msgstr ""
+
+msgid "%{group_docs_link_start}Groups%{group_docs_link_end} allow you to manage and collaborate across multiple projects. Members of a group have access to all of its projects."
+msgstr ""
+
+msgid "%{group_name} uses group managed accounts. You need to create a new GitLab account which will be managed by %{group_name}."
+msgstr ""
+
+msgid "%{host} sign-in from new location"
+msgstr ""
+
+msgid "%{icon}You are about to add %{usersTag} people to the discussion. They will all receive a notification."
+msgstr ""
+
+msgid "%{integrations_link_start}Integrations%{link_end} enable you to make third-party applications part of your GitLab workflow. If the available integrations don't meet your needs, consider using a %{webhooks_link_start}webhook%{link_end}."
+msgstr ""
+
+msgid "%{issuableType} will be removed! Are you sure?"
+msgstr ""
+
+msgid "%{issuesCount} issues in this group"
+msgstr ""
+
+msgid "%{issuesSize} issues"
+msgstr ""
+
+msgid "%{issuesSize} issues with a limit of %{maxIssueCount}"
+msgstr ""
+
+msgid "%{labelStart}Class:%{labelEnd} %{class}"
+msgstr ""
+
+msgid "%{labelStart}Evidence:%{labelEnd} %{evidence}"
+msgstr ""
+
+msgid "%{labelStart}File:%{labelEnd} %{file}"
+msgstr ""
+
+msgid "%{labelStart}Image:%{labelEnd} %{image}"
+msgstr ""
+
+msgid "%{labelStart}Method:%{labelEnd} %{method}"
+msgstr ""
+
+msgid "%{labelStart}Namespace:%{labelEnd} %{namespace}"
+msgstr ""
+
+msgid "%{labelStart}Report Type:%{labelEnd} %{reportType}"
+msgstr ""
+
+msgid "%{labelStart}Scanner:%{labelEnd} %{scanner}"
+msgstr ""
+
+msgid "%{labelStart}Severity:%{labelEnd} %{severity}"
+msgstr ""
+
+msgid "%{label_for_message} unavailable"
+msgstr ""
+
+msgid "%{lets_encrypt_link_start}Let's Encrypt%{lets_encrypt_link_end} is a free, automated, and open certificate authority (CA), that give digital certificates in order to enable HTTPS (SSL/TLS) for websites."
+msgstr ""
+
+msgid "%{level_name} is not allowed in a %{group_level_name} group."
+msgstr ""
+
+msgid "%{level_name} is not allowed since the fork source project has lower visibility."
+msgstr ""
+
+msgid "%{lineOneStart}Drag and drop to upload your designs%{lineOneEnd} or %{linkStart}click to upload%{linkEnd}."
+msgstr ""
+
+msgid "%{link_start}Learn more%{link_end} about what information is shared with GitLab Inc."
+msgstr ""
+
+msgid "%{link_start}Read more%{link_end} about role permissions"
+msgstr ""
+
+msgid "%{listToShow}, and %{awardsListLength} more."
+msgstr ""
+
+msgid "%{loadingIcon} Started"
+msgstr ""
+
+msgid "%{lock_path} is locked by GitLab User %{lock_user_id}"
+msgstr ""
+
+msgid "%{markdownDocsLinkStart}Markdown%{markdownDocsLinkEnd} and %{quickActionsDocsLinkStart}quick actions%{quickActionsDocsLinkEnd} are supported"
+msgstr ""
+
+msgid "%{mergeLength}/%{usersLength} can merge"
+msgstr ""
+
+msgid "%{mrText}, this issue will be closed automatically."
+msgstr ""
+
+msgid "%{name_with_link} has %{percent} or less Shared Runner Pipeline minutes remaining. Once it runs out, no new jobs or pipelines in its projects will run."
+msgstr ""
+
+msgid "%{name_with_link} has run out of Shared Runner Pipeline minutes so no new jobs or pipelines in its projects will run."
+msgstr ""
+
+msgid "%{namespace_name} is now read-only. You cannot: %{base_message}"
+msgstr ""
+
+msgid "%{name} contained %{resultsString}"
+msgstr ""
+
+msgid "%{name} found %{resultsString}"
+msgstr ""
+
+msgid "%{name} is already being used for another emoji"
+msgstr ""
+
+msgid "%{name} is scheduled for %{action}"
+msgstr ""
+
+msgid "%{name}'s avatar"
+msgstr ""
+
+msgid "%{name}(%{url}) has %{percent} or less Shared Runner Pipeline minutes remaining. Once it runs out, no new jobs or pipelines in its projects will run."
+msgstr ""
+
+msgid "%{name}(%{url}) has run out of Shared Runner Pipeline minutes so no new jobs or pipelines in its projects will run."
+msgstr ""
+
+msgid "%{no_of_days} day"
+msgid_plural "%{no_of_days} days"
+msgstr[0] ""
+
+msgid "%{number_commits_behind} commits behind %{default_branch}, %{number_commits_ahead} commits ahead"
+msgstr ""
+
+msgid "%{openOrClose} %{noteable}"
+msgstr ""
+
+msgid "%{openedEpics} open, %{closedEpics} closed"
+msgstr ""
+
+msgid "%{openedIssues} open, %{closedIssues} closed"
+msgstr ""
+
+msgid "%{percentage}%% weight completed"
+msgstr ""
+
+msgid "%{percent}%% complete"
+msgstr ""
+
+msgid "%{percent}%{percentSymbol} complete"
+msgstr ""
+
+msgid "%{placeholder} is not a valid color scheme"
+msgstr ""
+
+msgid "%{placeholder} is not a valid theme"
+msgstr ""
+
+msgid "%{primary} (%{secondary})"
+msgstr ""
+
+msgid "%{ref} cannot be added: %{error}"
+msgstr ""
+
+msgid "%{releases} release"
+msgid_plural "%{releases} releases"
+msgstr[0] ""
+
+msgid "%{remaining_approvals} left"
+msgstr ""
+
+msgid "%{reportType} %{status} detected %{critical} critical and %{high} high severity vulnerabilities out of %{total}."
+msgstr ""
+
+msgid "%{reportType} %{status} detected %{critical} critical and %{high} high severity vulnerabilities."
+msgstr ""
+
+msgid "%{reportType} %{status} detected %{critical} critical severity vulnerabilities out of %{total}."
+msgstr ""
+
+msgid "%{reportType} %{status} detected %{critical} critical severity vulnerability."
+msgid_plural "%{reportType} %{status} detected %{critical} critical severity vulnerabilities."
+msgstr[0] ""
+
+msgid "%{reportType} %{status} detected %{high} high severity vulnerabilities out of %{total}."
+msgstr ""
+
+msgid "%{reportType} %{status} detected %{high} high severity vulnerability."
+msgid_plural "%{reportType} %{status} detected %{high} high severity vulnerabilities."
+msgstr[0] ""
+
+msgid "%{reportType} %{status} detected %{other} vulnerability."
+msgid_plural "%{reportType} %{status} detected %{other} vulnerabilities."
+msgstr[0] ""
+
+msgid "%{reportType} %{status} detected no new vulnerabilities."
+msgstr ""
+
+msgid "%{retryButtonStart}Try again%{retryButtonEnd} or %{newFileButtonStart}attach a new file%{newFileButtonEnd}"
+msgstr ""
+
+msgid "%{service_title} %{message}."
+msgstr ""
+
+msgid "%{size} GiB"
+msgstr ""
+
+msgid "%{size} KiB"
+msgstr ""
+
+msgid "%{size} MiB"
+msgstr ""
+
+msgid "%{size} bytes"
+msgstr ""
+
+msgid "%{spammable_titlecase} was submitted to Akismet successfully."
+msgstr ""
+
+msgid "%{spanStart}at line%{spanEnd} %{errorLine}%{errorColumn}"
+msgstr ""
+
+msgid "%{spanStart}in%{spanEnd} %{errorFn}"
+msgstr ""
+
+msgid "%{start} to %{end}"
+msgstr ""
+
+msgid "%{state} epics"
+msgstr ""
+
+msgid "%{strongStart}Note:%{strongEnd} Once a custom stage has been added you can re-order stages by dragging them into the desired position."
+msgstr ""
+
+msgid "%{strong_start}%{branch_count}%{strong_end} Branch"
+msgid_plural "%{strong_start}%{branch_count}%{strong_end} Branches"
+msgstr[0] ""
+
+msgid "%{strong_start}%{commit_count}%{strong_end} Commit"
+msgid_plural "%{strong_start}%{commit_count}%{strong_end} Commits"
+msgstr[0] ""
+
+msgid "%{strong_start}%{human_size}%{strong_end} Files"
+msgstr ""
+
+msgid "%{strong_start}%{human_size}%{strong_end} Storage"
+msgstr ""
+
+msgid "%{strong_start}%{release_count}%{strong_end} Release"
+msgid_plural "%{strong_start}%{release_count}%{strong_end} Releases"
+msgstr[0] ""
+
+msgid "%{strong_start}%{tag_count}%{strong_end} Tag"
+msgid_plural "%{strong_start}%{tag_count}%{strong_end} Tags"
+msgstr[0] ""
+
+msgid "%{tabname} changed"
+msgstr ""
+
+msgid "%{tags} tag per image name"
+msgstr ""
+
+msgid "%{tags} tags per image name"
+msgstr ""
+
+msgid "%{tag}-%{evidence}-%{filename}"
+msgstr ""
+
+msgid "%{template_project_id} is unknown or invalid"
+msgstr ""
+
+msgid "%{text} %{files}"
+msgid_plural "%{text} %{files} files"
+msgstr[0] ""
+
+msgid "%{text} is available"
+msgstr ""
+
+msgid "%{timebox_name} should belong either to a project or a group."
+msgstr ""
+
+msgid "%{title} %{operator} %{threshold}"
+msgstr ""
+
+msgid "%{title} changes"
+msgstr ""
+
+msgid "%{token}..."
+msgstr ""
+
+msgid "%{totalCpu} (%{freeSpacePercentage}%{percentSymbol} free)"
+msgstr ""
+
+msgid "%{totalMemory} (%{freeSpacePercentage}%{percentSymbol} free)"
+msgstr ""
+
+msgid "%{totalWeight} total weight"
+msgstr ""
+
+msgid "%{total} open issue weight"
+msgstr ""
+
+msgid "%{total} open issues"
+msgstr ""
+
+msgid "%{usage_ping_link_start}Learn more%{usage_ping_link_end} about what information is shared with GitLab Inc."
+msgstr ""
+
+msgid "%{userName} (cannot merge)"
+msgstr ""
+
+msgid "%{userName}'s avatar"
+msgstr ""
+
+msgid "%{user_name} profile page"
+msgstr ""
+
+msgid "%{username}'s avatar"
+msgstr ""
+
+msgid "%{value} s"
+msgstr ""
+
+msgid "%{verb} %{time_spent_value} spent time."
+msgstr ""
+
+msgid "%{webhooks_link_start}%{webhook_type}%{link_end} enable you to send notifications to web applications in response to events in a group or project."
+msgstr ""
+
+msgid "%{webhooks_link_start}%{webhook_type}%{link_end} enable you to send notifications to web applications in response to events in a group or project. We recommend using an %{integrations_link_start}integration%{link_end} in preference to a webhook."
+msgstr ""
+
+msgid "'%{level}' is not a valid visibility level"
+msgstr ""
+
+msgid "'%{name}' stage already exists"
+msgstr ""
+
+msgid "'%{source}' is not a import source"
+msgstr ""
+
+msgid "'%{template_name}' is unknown or invalid"
+msgstr ""
+
+msgid "(%d closed)"
+msgid_plural "(%d closed)"
+msgstr[0] ""
+
+msgid "(%{linkStart}Cron syntax%{linkEnd})"
+msgstr ""
+
+msgid "(%{mrCount} merged)"
+msgstr ""
+
+msgid "(No changes)"
+msgstr ""
+
+msgid "(check progress)"
+msgstr ""
+
+msgid "(external source)"
+msgstr ""
+
+msgid "(removed)"
+msgstr ""
+
+msgid "(revoked)"
+msgstr ""
+
+msgid "*"
+msgstr ""
+
+msgid "+ %{amount} more"
+msgstr ""
+
+msgid "+ %{count} more"
+msgstr ""
+
+msgid "+ %{moreCount} more"
+msgstr ""
+
+msgid "+ %{numberOfHiddenAssignees} more"
+msgstr ""
+
+msgid "+%d more"
+msgid_plural "+%d more"
+msgstr[0] ""
+
+msgid "+%{approvers} more approvers"
+msgstr ""
+
+msgid "+%{tags} more"
+msgstr ""
+
+msgid ", or "
+msgstr ""
+
+msgid "- Event"
+msgid_plural "- Events"
+msgstr[0] ""
+
+msgid "- Runner is active and can process any new jobs"
+msgstr ""
+
+msgid "- Runner is paused and will not receive any new jobs"
+msgstr ""
+
+msgid "- User"
+msgid_plural "- Users"
+msgstr[0] ""
+
+msgid "- of - weight completed"
+msgstr ""
+
+msgid "- show less"
+msgstr ""
+
+msgid "0 for unlimited"
+msgstr ""
+
+msgid "0 for unlimited, only effective with remote storage enabled."
+msgstr ""
+
+msgid "1 %{type} addition"
+msgid_plural "%{count} %{type} additions"
+msgstr[0] ""
+
+msgid "1 %{type} modification"
+msgid_plural "%{count} %{type} modifications"
+msgstr[0] ""
+
+msgid "1 Day"
+msgid_plural "%d Days"
+msgstr[0] ""
+
+msgid "1 closed issue"
+msgid_plural "%{issues} closed issues"
+msgstr[0] ""
+
+msgid "1 closed merge request"
+msgid_plural "%{merge_requests} closed merge requests"
+msgstr[0] ""
+
+msgid "1 day"
+msgid_plural "%d days"
+msgstr[0] ""
+
+msgid "1 group"
+msgid_plural "%d groups"
+msgstr[0] ""
+
+msgid "1 hour"
+msgid_plural "%d hours"
+msgstr[0] ""
+
+msgid "1 merged merge request"
+msgid_plural "%{merge_requests} merged merge requests"
+msgstr[0] ""
+
+msgid "1 minute"
+msgid_plural "%d minutes"
+msgstr[0] ""
+
+msgid "1 month"
+msgstr ""
+
+msgid "1 open issue"
+msgid_plural "%{issues} open issues"
+msgstr[0] ""
+
+msgid "1 open merge request"
+msgid_plural "%{merge_requests} open merge requests"
+msgstr[0] ""
+
+msgid "1 pipeline"
+msgid_plural "%d pipelines"
+msgstr[0] ""
+
+msgid "1 role"
+msgid_plural "%d roles"
+msgstr[0] ""
+
+msgid "1 user"
+msgid_plural "%{num} users"
+msgstr[0] ""
+
+msgid "1 week"
+msgstr ""
+
+msgid "1-9 contributions"
+msgstr ""
+
+msgid "10-19 contributions"
+msgstr ""
+
+msgid "1st contribution!"
+msgstr ""
+
+msgid "20-29 contributions"
+msgstr ""
+
+msgid "2FA"
+msgstr ""
+
+msgid "2FADevice|Registered On"
+msgstr ""
+
+msgid "3 days"
+msgstr ""
+
+msgid "3 hours"
+msgstr ""
+
+msgid "30 minutes"
+msgstr ""
+
+msgid "30+ contributions"
+msgstr ""
+
+msgid "403|Please contact your GitLab administrator to get permission."
+msgstr ""
+
+msgid "403|You don't have the permission to access this page."
+msgstr ""
+
+msgid "404|Make sure the address is correct and the page hasn't moved."
+msgstr ""
+
+msgid "404|Page Not Found"
+msgstr ""
+
+msgid "404|Please contact your GitLab administrator if you think this is a mistake."
+msgstr ""
+
+msgid "8 hours"
+msgstr ""
+
+msgid ":%{startLine} to %{endLine}"
+msgstr ""
+
+msgid "< 1 hour"
+msgstr ""
+
+msgid "<code>\"johnsmith@example.com\": \"@johnsmith\"</code> will add \"By <a href=\"#\">@johnsmith</a>\" to all issues and comments originally created by johnsmith@example.com, and will set <a href=\"#\">@johnsmith</a> as the assignee on all issues originally assigned to johnsmith@example.com."
+msgstr ""
+
+msgid "<code>\"johnsmith@example.com\": \"John Smith\"</code> will add \"By John Smith\" to all issues and comments originally created by johnsmith@example.com."
+msgstr ""
+
+msgid "<code>\"johnsmith@example.com\": \"johnsm...@example.com\"</code> will add \"By johnsm...@example.com\" to all issues and comments originally created by johnsmith@example.com. The email address or username is masked to ensure the user's privacy."
+msgstr ""
+
+msgid "<code>\"johnsmith@example.com\": \"johnsmith@example.com\"</code> will add \"By <a href=\"#\">johnsmith@example.com</a>\" to all issues and comments originally created by johnsmith@example.com. By default, the email address or username is masked to ensure the user's privacy. Use this option if you want to show the full email address."
+msgstr ""
+
+msgid "<code>Masked</code> to prevent the values from being displayed in job logs (must match certain regexp requirements)."
+msgstr ""
+
+msgid "<code>Protected</code> to expose them to protected branches or tags only."
+msgstr ""
+
+msgid "<namespace / project>"
+msgstr ""
+
+msgid "<no name set>"
+msgstr ""
+
+msgid "<no scopes selected>"
+msgstr ""
+
+msgid "<project name>"
+msgstr ""
+
+msgid "<strong>%{group_name}</strong> group members"
+msgstr ""
+
+msgid "<strong>%{label_name}</strong> <span>will be permanently deleted from %{subject_name}. This cannot be undone.</span>"
+msgstr ""
+
+msgid "<strong>Deletes</strong> source branch"
+msgstr ""
+
+msgid "A 'Runner' is a process which runs a job. You can set up as many Runners as you need."
+msgstr ""
+
+msgid "A .NET Core console application template, customizable for any .NET Core project"
+msgstr ""
+
+msgid "A GitBook site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
+msgid "A Hexo site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
+msgid "A Hugo site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
+msgid "A Jekyll site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
+msgid "A Let's Encrypt SSL certificate can not be obtained until your domain is verified."
+msgstr ""
+
+msgid "A Let's Encrypt account will be configured for this GitLab installation using your email address. You will receive emails to warn of expiring certificates."
+msgstr ""
+
+msgid "A basic page and serverless function that uses AWS Lambda, AWS API Gateway, and GitLab Pages"
+msgstr ""
+
+msgid "A complete DevOps platform"
+msgstr ""
+
+msgid "A default branch cannot be chosen for an empty project."
+msgstr ""
+
+msgid "A deleted user"
+msgstr ""
+
+msgid "A file has been changed."
+msgstr ""
+
+msgid "A file was not found."
+msgstr ""
+
+msgid "A file with '%{file_name}' already exists in %{branch} branch"
+msgstr ""
+
+msgid "A fork is a copy of a project.<br />Forking a repository allows you to make changes without affecting the original project."
+msgstr ""
+
+msgid "A group is a collection of several projects"
+msgstr ""
+
+msgid "A group represents your organization in GitLab. Groups allow you to manage users and collaborate across multiple projects."
+msgstr ""
+
+msgid "A member of the abuse team will review your report as soon as possible."
+msgstr ""
+
+msgid "A merge request approval is required when a security report contains a new vulnerability of high, critical, or unknown severity."
+msgstr ""
+
+msgid "A merge request approval is required when the license compliance report contains a blacklisted license."
+msgstr ""
+
+msgid "A new Auto DevOps pipeline has been created, go to %{pipelines_link_start}Pipelines page%{pipelines_link_end} for details"
+msgstr ""
+
+msgid "A new Release %{tag} for %{name} was published. Visit the %{release_link_start}Releases page%{release_link_end} to read more about it."
+msgstr ""
+
+msgid "A new Release %{tag} for %{name} was published. Visit the Releases page to read more about it:"
+msgstr ""
+
+msgid "A new branch will be created in your fork and a new merge request will be started."
+msgstr ""
+
+msgid "A new impersonation token has been created."
+msgstr ""
+
+msgid "A plain HTML site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
+msgid "A project boilerplate for Salesforce App development with Salesforce Developer tools."
+msgstr ""
+
+msgid "A project containing issues for each audit inquiry in the HIPAA Audit Protocol published by the U.S. Department of Health & Human Services"
+msgstr ""
+
+msgid "A project is where you house your files (repository), plan your work (issues), and publish your documentation (wiki), %{among_other_things_link}."
+msgstr ""
+
+msgid "A ready-to-go template for use with Android apps."
+msgstr ""
+
+msgid "A ready-to-go template for use with iOS Swift apps."
+msgstr ""
+
+msgid "A regular expression that will be used to find the test coverage output in the job log. Leave blank to disable"
+msgstr ""
+
+msgid "A secure token that identifies an external storage request."
+msgstr ""
+
+msgid "A sign-in to your account has been made from the following IP address: %{ip}"
+msgstr ""
+
+msgid "A subscription will trigger a new pipeline on the default branch of this project when a pipeline successfully completes for a new tag on the %{default_branch_docs} of the subscribed project."
+msgstr ""
+
+msgid "A suggestion is not applicable."
+msgstr ""
+
+msgid "A user with write access to the source branch selected this option"
+msgstr ""
+
+msgid "ACTION REQUIRED: Something went wrong while obtaining the Let's Encrypt certificate for GitLab Pages domain '%{domain}'"
+msgstr ""
+
+msgid "API Token"
+msgstr ""
+
+msgid "AWS Access Key"
+msgstr ""
+
+msgid "AWS Access Key. Only required if not using role instance credentials"
+msgstr ""
+
+msgid "AWS Secret Access Key"
+msgstr ""
+
+msgid "AWS Secret Access Key. Only required if not using role instance credentials"
+msgstr ""
+
+msgid "Abort"
+msgstr ""
+
+msgid "About GitLab"
+msgstr ""
+
+msgid "About GitLab CE"
+msgstr ""
+
+msgid "About auto deploy"
+msgstr ""
+
+msgid "About this feature"
+msgstr ""
+
+msgid "Abuse Reports"
+msgstr ""
+
+msgid "Abuse reports"
+msgstr ""
+
+msgid "Accept invitation"
+msgstr ""
+
+msgid "Accept terms"
+msgstr ""
+
+msgid "Acceptable for use in this project"
+msgstr ""
+
+msgid "Access Tokens"
+msgstr ""
+
+msgid "Access denied for your LDAP account."
+msgstr ""
+
+msgid "Access denied! Please verify you can add deploy keys to this repository."
+msgstr ""
+
+msgid "Access expiration date"
+msgstr ""
+
+msgid "Access forbidden. Check your access level."
+msgstr ""
+
+msgid "Access to '%{classification_label}' not allowed"
+msgstr ""
+
+msgid "Access to Pages websites are controlled based on the user's membership to a given project. By checking this box, users will be required to be logged in to have access to all Pages websites in your instance."
+msgstr ""
+
+msgid "AccessDropdown|Groups"
+msgstr ""
+
+msgid "AccessDropdown|Roles"
+msgstr ""
+
+msgid "AccessDropdown|Users"
+msgstr ""
+
+msgid "AccessTokens|Access Tokens"
+msgstr ""
+
+msgid "AccessTokens|Are you sure?"
+msgstr ""
+
+msgid "AccessTokens|Are you sure? Any RSS or calendar URLs currently in use will stop working."
+msgstr ""
+
+msgid "AccessTokens|Are you sure? Any issue email addresses currently in use will stop working."
+msgstr ""
+
+msgid "AccessTokens|Created"
+msgstr ""
+
+msgid "AccessTokens|Feed token"
+msgstr ""
+
+msgid "AccessTokens|Incoming email token"
+msgstr ""
+
+msgid "AccessTokens|It cannot be used to access any other data."
+msgstr ""
+
+msgid "AccessTokens|Keep this token secret. Anyone who gets ahold of it can access repository static objects as if they were you. You should %{reset_link_start}reset it%{reset_link_end} if that ever happens."
+msgstr ""
+
+msgid "AccessTokens|Keep this token secret. Anyone who gets ahold of it can create issues as if they were you. You should %{link_reset_it} if that ever happens."
+msgstr ""
+
+msgid "AccessTokens|Keep this token secret. Anyone who gets ahold of it can read activity and issue RSS feeds or your calendar feed as if they were you. You should %{link_reset_it} if that ever happens."
+msgstr ""
+
+msgid "AccessTokens|Personal Access Tokens"
+msgstr ""
+
+msgid "AccessTokens|Static object token"
+msgstr ""
+
+msgid "AccessTokens|They are the only accepted password when you have Two-Factor Authentication (2FA) enabled."
+msgstr ""
+
+msgid "AccessTokens|You can also use personal access tokens to authenticate against Git over HTTP."
+msgstr ""
+
+msgid "AccessTokens|You can generate a personal access token for each application you use that needs access to the GitLab API."
+msgstr ""
+
+msgid "AccessTokens|Your feed token is used to authenticate you when your RSS reader loads a personalized RSS feed or when your calendar application loads a personalized calendar, and is included in those feed URLs."
+msgstr ""
+
+msgid "AccessTokens|Your incoming email token is used to authenticate you when you create a new issue by email, and is included in your personal project-specific email addresses."
+msgstr ""
+
+msgid "AccessTokens|Your static object token is used to authenticate you when repository static objects (e.g. archives, blobs, ...) are being served from an external storage."
+msgstr ""
+
+msgid "AccessTokens|reset it"
+msgstr ""
+
+msgid "AccessibilityReport|Learn More"
+msgstr ""
+
+msgid "AccessibilityReport|Message: %{message}"
+msgstr ""
+
+msgid "AccessibilityReport|New"
+msgstr ""
+
+msgid "AccessibilityReport|The accessibility scanning found an error of the following type: %{code}"
+msgstr ""
+
+msgid "Account"
+msgstr ""
+
+msgid "Account ID"
+msgstr ""
+
+msgid "Account and limit"
+msgstr ""
+
+msgid "Account: %{account}"
+msgstr ""
+
+msgid "Action to take when receiving an alert."
+msgstr ""
+
+msgid "Actions"
+msgstr ""
+
+msgid "Activate"
+msgstr ""
+
+msgid "Activate Service Desk"
+msgstr ""
+
+msgid "Active"
+msgstr ""
+
+msgid "Active %{type} (%{token_length})"
+msgstr ""
+
+msgid "Active Sessions"
+msgstr ""
+
+msgid "Active Users:"
+msgstr ""
+
+msgid "Active users"
+msgstr ""
+
+msgid "Activity"
+msgstr ""
+
+msgid "Add"
+msgstr ""
+
+msgid "Add %d issue"
+msgid_plural "Add %d issues"
+msgstr[0] ""
+
+msgid "Add %{linkStart}assets%{linkEnd} to your Release. GitLab automatically includes read-only assets, like source code and release evidence."
+msgstr ""
+
+msgid "Add CHANGELOG"
+msgstr ""
+
+msgid "Add CONTRIBUTING"
+msgstr ""
+
+msgid "Add GitLab to Slack"
+msgstr ""
+
+msgid "Add Group Webhooks and GitLab Enterprise Edition."
+msgstr ""
+
+msgid "Add Jaeger URL"
+msgstr ""
+
+msgid "Add Kubernetes cluster"
+msgstr ""
+
+msgid "Add LICENSE"
+msgstr ""
+
+msgid "Add New Node"
+msgstr ""
+
+msgid "Add README"
+msgstr ""
+
+msgid "Add Variable"
+msgstr ""
+
+msgid "Add Zoom meeting"
+msgstr ""
+
+msgid "Add a %{type}"
+msgstr ""
+
+msgid "Add a GPG key"
+msgstr ""
+
+msgid "Add a Grafana button in the admin sidebar, monitoring section, to access a variety of statistics on the health and performance of GitLab."
+msgstr ""
+
+msgid "Add a To Do"
+msgstr ""
+
+msgid "Add a bullet list"
+msgstr ""
+
+msgid "Add a comment to this line"
+msgstr ""
+
+msgid "Add a general comment to this %{noteableDisplayName}."
+msgstr ""
+
+msgid "Add a general comment to this %{noteable_name}."
+msgstr ""
+
+msgid "Add a homepage to your wiki that contains information about your project and GitLab will display it here instead of this message."
+msgstr ""
+
+msgid "Add a line"
+msgstr ""
+
+msgid "Add a link"
+msgstr ""
+
+msgid "Add a new issue"
+msgstr ""
+
+msgid "Add a numbered list"
+msgstr ""
+
+msgid "Add a table"
+msgstr ""
+
+msgid "Add a task list"
+msgstr ""
+
+msgid "Add additional text to appear in all email communications. %{character_limit} character limit"
+msgstr ""
+
+msgid "Add an SSH key"
+msgstr ""
+
+msgid "Add an existing issue"
+msgstr ""
+
+msgid "Add an impersonation token"
+msgstr ""
+
+msgid "Add an issue"
+msgstr ""
+
+msgid "Add another link"
+msgstr ""
+
+msgid "Add approval rule"
+msgstr ""
+
+msgid "Add approvers"
+msgstr ""
+
+msgid "Add bold text"
+msgstr ""
+
+msgid "Add child epic to an epic"
+msgstr ""
+
+msgid "Add comment now"
+msgstr ""
+
+msgid "Add domain"
+msgstr ""
+
+msgid "Add email address"
+msgstr ""
+
+msgid "Add environment"
+msgstr ""
+
+msgid "Add header and footer to emails. Please note that color settings will only be applied within the application interface"
+msgstr ""
+
+msgid "Add image comment"
+msgstr ""
+
+msgid "Add issues"
+msgstr ""
+
+msgid "Add italic text"
+msgstr ""
+
+msgid "Add label(s)"
+msgstr ""
+
+msgid "Add license"
+msgstr ""
+
+msgid "Add list"
+msgstr ""
+
+msgid "Add new application"
+msgstr ""
+
+msgid "Add new directory"
+msgstr ""
+
+msgid "Add or subtract spent time"
+msgstr ""
+
+msgid "Add reaction"
+msgstr ""
+
+msgid "Add request manually"
+msgstr ""
+
+msgid "Add strikethrough text"
+msgstr ""
+
+msgid "Add suggestion to batch"
+msgstr ""
+
+msgid "Add system hook"
+msgstr ""
+
+msgid "Add to Slack"
+msgstr ""
+
+msgid "Add to epic"
+msgstr ""
+
+msgid "Add to merge train"
+msgstr ""
+
+msgid "Add to merge train when pipeline succeeds"
+msgstr ""
+
+msgid "Add to review"
+msgstr ""
+
+msgid "Add to tree"
+msgstr ""
+
+msgid "Add user(s) to the group:"
+msgstr ""
+
+msgid "Add users to group"
+msgstr ""
+
+msgid "Add variable"
+msgstr ""
+
+msgid "Add webhook"
+msgstr ""
+
+msgid "AddMember|No users specified."
+msgstr ""
+
+msgid "AddMember|Too many users specified (limit is %{user_limit})"
+msgstr ""
+
+msgid "Added"
+msgstr ""
+
+msgid "Added %{epic_ref} as a child epic."
+msgstr ""
+
+msgid "Added %{label_references} %{label_text}."
+msgstr ""
+
+msgid "Added a To Do."
+msgstr ""
+
+msgid "Added an issue to an epic."
+msgstr ""
+
+msgid "Added at"
+msgstr ""
+
+msgid "Added for this merge request"
+msgstr ""
+
+msgid "Added in this version"
+msgstr ""
+
+msgid "Adding new applications is disabled in your GitLab instance. Please contact your GitLab administrator to get the permission"
+msgstr ""
+
+msgid "Additional minutes"
+msgstr ""
+
+msgid "Additional text"
+msgstr ""
+
+msgid "Adds"
+msgstr ""
+
+msgid "Adds %{epic_ref} as child epic."
+msgstr ""
+
+msgid "Adds %{labels} %{label_text}."
+msgstr ""
+
+msgid "Adds a To Do."
+msgstr ""
+
+msgid "Adds a Zoom meeting"
+msgstr ""
+
+msgid "Adds an issue to an epic."
+msgstr ""
+
+msgid "Adjust your filters/search criteria above. If you believe this may be an error, please refer to the %{linkStart}Geo Troubleshooting%{linkEnd} documentation for more information."
+msgstr ""
+
+msgid "Admin Area"
+msgstr ""
+
+msgid "Admin Note"
+msgstr ""
+
+msgid "Admin Notifications"
+msgstr ""
+
+msgid "Admin Overview"
+msgstr ""
+
+msgid "Admin mode already enabled"
+msgstr ""
+
+msgid "Admin mode disabled"
+msgstr ""
+
+msgid "Admin mode enabled"
+msgstr ""
+
+msgid "Admin notes"
+msgstr ""
+
+msgid "AdminArea|Active users"
+msgstr ""
+
+msgid "AdminArea|Billable users"
+msgstr ""
+
+msgid "AdminArea|Blocked users"
+msgstr ""
+
+msgid "AdminArea|Bots"
+msgstr ""
+
+msgid "AdminArea|Developer"
+msgstr ""
+
+msgid "AdminArea|Guest"
+msgstr ""
+
+msgid "AdminArea|Included Free in license"
+msgstr ""
+
+msgid "AdminArea|Maintainer"
+msgstr ""
+
+msgid "AdminArea|Owner"
+msgstr ""
+
+msgid "AdminArea|Reporter"
+msgstr ""
+
+msgid "AdminArea|Stop all jobs"
+msgstr ""
+
+msgid "AdminArea|Stop all jobs?"
+msgstr ""
+
+msgid "AdminArea|Stop jobs"
+msgstr ""
+
+msgid "AdminArea|Stopping jobs failed"
+msgstr ""
+
+msgid "AdminArea|Total users"
+msgstr ""
+
+msgid "AdminArea|Users statistics"
+msgstr ""
+
+msgid "AdminArea|Users with highest role"
+msgstr ""
+
+msgid "AdminArea|Users without a Group and Project"
+msgstr ""
+
+msgid "AdminArea|You’re about to stop all jobs.This will halt all current jobs that are running."
+msgstr ""
+
+msgid "AdminDashboard|Error loading the statistics. Please try again"
+msgstr ""
+
+msgid "AdminNote|Note"
+msgstr ""
+
+msgid "AdminProjects| You’re about to permanently delete the project %{projectName}, its repository, and all related resources including issues, merge requests, etc.. Once you confirm and press %{strong_start}Delete project%{strong_end}, it cannot be undone or recovered."
+msgstr ""
+
+msgid "AdminProjects|Delete"
+msgstr ""
+
+msgid "AdminProjects|Delete Project %{projectName}?"
+msgstr ""
+
+msgid "AdminProjects|Delete project"
+msgstr ""
+
+msgid "AdminSettings|Apply integration settings to all Projects"
+msgstr ""
+
+msgid "AdminSettings|Auto DevOps domain"
+msgstr ""
+
+msgid "AdminSettings|Elasticsearch, PlantUML, Slack application, Third party offers, Snowplow, Amazon EKS have moved to Settings > General."
+msgstr ""
+
+msgid "AdminSettings|Enable shared runners for new projects"
+msgstr ""
+
+msgid "AdminSettings|Environment variables are protected by default"
+msgstr ""
+
+msgid "AdminSettings|Go to General Settings"
+msgstr ""
+
+msgid "AdminSettings|Integrations configured here will automatically apply to all projects on this instance."
+msgstr ""
+
+msgid "AdminSettings|Moved to integrations"
+msgstr ""
+
+msgid "AdminSettings|No required pipeline"
+msgstr ""
+
+msgid "AdminSettings|Required pipeline configuration"
+msgstr ""
+
+msgid "AdminSettings|Select a pipeline configuration file"
+msgstr ""
+
+msgid "AdminSettings|Select a template"
+msgstr ""
+
+msgid "AdminSettings|Service template allows you to set default values for integrations"
+msgstr ""
+
+msgid "AdminSettings|Set an instance-wide auto included %{link_start}pipeline configuration%{link_end}. This pipeline configuration will be run after the project's own configuration."
+msgstr ""
+
+msgid "AdminSettings|Some settings have moved"
+msgstr ""
+
+msgid "AdminSettings|Specify a domain to use by default for every project's Auto Review Apps and Auto Deploy stages."
+msgstr ""
+
+msgid "AdminSettings|The required pipeline configuration can be selected from the %{code_start}gitlab-ci%{code_end} directory inside of the configured %{link_start}instance template repository%{link_end} or from GitLab provided configurations."
+msgstr ""
+
+msgid "AdminSettings|When creating a new environment variable it will be protected by default."
+msgstr ""
+
+msgid "AdminStatistics|Active Users"
+msgstr ""
+
+msgid "AdminStatistics|Forks"
+msgstr ""
+
+msgid "AdminStatistics|Issues"
+msgstr ""
+
+msgid "AdminStatistics|Merge Requests"
+msgstr ""
+
+msgid "AdminStatistics|Milestones"
+msgstr ""
+
+msgid "AdminStatistics|Notes"
+msgstr ""
+
+msgid "AdminStatistics|SSH Keys"
+msgstr ""
+
+msgid "AdminStatistics|Snippets"
+msgstr ""
+
+msgid "AdminUsers|2FA Disabled"
+msgstr ""
+
+msgid "AdminUsers|2FA Enabled"
+msgstr ""
+
+msgid "AdminUsers|Active"
+msgstr ""
+
+msgid "AdminUsers|Admin"
+msgstr ""
+
+msgid "AdminUsers|Admins"
+msgstr ""
+
+msgid "AdminUsers|Block"
+msgstr ""
+
+msgid "AdminUsers|Block user"
+msgstr ""
+
+msgid "AdminUsers|Block user %{username}?"
+msgstr ""
+
+msgid "AdminUsers|Blocked"
+msgstr ""
+
+msgid "AdminUsers|Blocking user has the following effects:"
+msgstr ""
+
+msgid "AdminUsers|Cannot unblock LDAP blocked users"
+msgstr ""
+
+msgid "AdminUsers|Deactivate"
+msgstr ""
+
+msgid "AdminUsers|Deactivate User %{username}?"
+msgstr ""
+
+msgid "AdminUsers|Deactivate user"
+msgstr ""
+
+msgid "AdminUsers|Deactivated"
+msgstr ""
+
+msgid "AdminUsers|Deactivating a user has the following effects:"
+msgstr ""
+
+msgid "AdminUsers|Delete User %{username} and contributions?"
+msgstr ""
+
+msgid "AdminUsers|Delete User %{username}?"
+msgstr ""
+
+msgid "AdminUsers|Delete user"
+msgstr ""
+
+msgid "AdminUsers|Delete user and contributions"
+msgstr ""
+
+msgid "AdminUsers|External"
+msgstr ""
+
+msgid "AdminUsers|Is using seat"
+msgstr ""
+
+msgid "AdminUsers|It's you!"
+msgstr ""
+
+msgid "AdminUsers|New user"
+msgstr ""
+
+msgid "AdminUsers|No users found"
+msgstr ""
+
+msgid "AdminUsers|Owned groups will be left"
+msgstr ""
+
+msgid "AdminUsers|Personal projects will be left"
+msgstr ""
+
+msgid "AdminUsers|Personal projects, group and user history will be left intact"
+msgstr ""
+
+msgid "AdminUsers|Reactivating a user will:"
+msgstr ""
+
+msgid "AdminUsers|Restore user access to the account, including web, Git and API."
+msgstr ""
+
+msgid "AdminUsers|Search by name, email or username"
+msgstr ""
+
+msgid "AdminUsers|Search users"
+msgstr ""
+
+msgid "AdminUsers|Send email to users"
+msgstr ""
+
+msgid "AdminUsers|Sort by"
+msgstr ""
+
+msgid "AdminUsers|The user will be logged out"
+msgstr ""
+
+msgid "AdminUsers|The user will not be able to access git repositories"
+msgstr ""
+
+msgid "AdminUsers|The user will not be able to access the API"
+msgstr ""
+
+msgid "AdminUsers|The user will not be able to use slash commands"
+msgstr ""
+
+msgid "AdminUsers|The user will not receive any notifications"
+msgstr ""
+
+msgid "AdminUsers|To confirm, type %{projectName}"
+msgstr ""
+
+msgid "AdminUsers|To confirm, type %{username}"
+msgstr ""
+
+msgid "AdminUsers|User will not be able to access git repositories"
+msgstr ""
+
+msgid "AdminUsers|User will not be able to login"
+msgstr ""
+
+msgid "AdminUsers|When the user logs back in, their account will reactivate as a fully active account"
+msgstr ""
+
+msgid "AdminUsers|Without projects"
+msgstr ""
+
+msgid "AdminUsers|You are about to permanently delete the user %{username}. Issues, merge requests, and groups linked to them will be transferred to a system-wide \"Ghost-user\". To avoid data loss, consider using the %{strong_start}block user%{strong_end} feature instead. Once you %{strong_start}Delete user%{strong_end}, it cannot be undone or recovered."
+msgstr ""
+
+msgid "AdminUsers|You are about to permanently delete the user %{username}. This will delete all of the issues, merge requests, and groups linked to them. To avoid data loss, consider using the %{strong_start}block user%{strong_end} feature instead. Once you %{strong_start}Delete user%{strong_end}, it cannot be undone or recovered."
+msgstr ""
+
+msgid "Administration"
+msgstr ""
+
+msgid "Advanced"
+msgstr ""
+
+msgid "Advanced Settings"
+msgstr ""
+
+msgid "Advanced permissions, Large File Storage and Two-Factor authentication settings."
+msgstr ""
+
+msgid "Advanced search functionality"
+msgstr ""
+
+msgid "After a successful password update you will be redirected to login screen."
+msgstr ""
+
+msgid "After a successful password update, you will be redirected to the login page where you can log in with your new password."
+msgstr ""
+
+msgid "After that, you will not to be able to use merge approvals or code quality as well as many other features."
+msgstr ""
+
+msgid "After that, you will not to be able to use merge approvals or epics as well as many other features."
+msgstr ""
+
+msgid "After that, you will not to be able to use merge approvals or epics as well as many security features."
+msgstr ""
+
+msgid "Alert"
+msgid_plural "Alerts"
+msgstr[0] ""
+
+msgid "AlertManagement| assign yourself"
+msgstr ""
+
+msgid "AlertManagement|Acknowledged"
+msgstr ""
+
+msgid "AlertManagement|Alert"
+msgstr ""
+
+msgid "AlertManagement|Alert assignee(s): %{assignees}"
+msgstr ""
+
+msgid "AlertManagement|Alert detail"
+msgstr ""
+
+msgid "AlertManagement|Alert details"
+msgstr ""
+
+msgid "AlertManagement|Alert status: %{status}"
+msgstr ""
+
+msgid "AlertManagement|Alerts"
+msgstr ""
+
+msgid "AlertManagement|All alerts"
+msgstr ""
+
+msgid "AlertManagement|Assign To"
+msgstr ""
+
+msgid "AlertManagement|Assign status"
+msgstr ""
+
+msgid "AlertManagement|Assignee"
+msgstr ""
+
+msgid "AlertManagement|Assignees"
+msgstr ""
+
+msgid "AlertManagement|Authorize external service"
+msgstr ""
+
+msgid "AlertManagement|Create issue"
+msgstr ""
+
+msgid "AlertManagement|Critical"
+msgstr ""
+
+msgid "AlertManagement|Display alerts from all your monitoring tools directly within GitLab. Streamline the investigation of your alerts and the escalation of alerts to incidents."
+msgstr ""
+
+msgid "AlertManagement|Edit"
+msgstr ""
+
+msgid "AlertManagement|Events"
+msgstr ""
+
+msgid "AlertManagement|High"
+msgstr ""
+
+msgid "AlertManagement|Info"
+msgstr ""
+
+msgid "AlertManagement|Low"
+msgstr ""
+
+msgid "AlertManagement|Medium"
+msgstr ""
+
+msgid "AlertManagement|More information"
+msgstr ""
+
+msgid "AlertManagement|No Matching Results"
+msgstr ""
+
+msgid "AlertManagement|No alert data to display."
+msgstr ""
+
+msgid "AlertManagement|No alerts available to display. See %{linkStart}enabling alert management%{linkEnd} for more information on adding alerts to the list."
+msgstr ""
+
+msgid "AlertManagement|No alerts to display."
+msgstr ""
+
+msgid "AlertManagement|None"
+msgstr ""
+
+msgid "AlertManagement|None -"
+msgstr ""
+
+msgid "AlertManagement|Open"
+msgstr ""
+
+msgid "AlertManagement|Overview"
+msgstr ""
+
+msgid "AlertManagement|Reported %{when}"
+msgstr ""
+
+msgid "AlertManagement|Reported %{when} by %{tool}"
+msgstr ""
+
+msgid "AlertManagement|Resolved"
+msgstr ""
+
+msgid "AlertManagement|Service"
+msgstr ""
+
+msgid "AlertManagement|Severity"
+msgstr ""
+
+msgid "AlertManagement|Start time"
+msgstr ""
+
+msgid "AlertManagement|Status"
+msgstr ""
+
+msgid "AlertManagement|Surface alerts in GitLab"
+msgstr ""
+
+msgid "AlertManagement|There was an error displaying the alert. Please refresh the page to try again."
+msgstr ""
+
+msgid "AlertManagement|There was an error displaying the alerts. Confirm your endpoint's configuration details to ensure alerts appear."
+msgstr ""
+
+msgid "AlertManagement|There was an error while updating the assignee(s) list. Please try again."
+msgstr ""
+
+msgid "AlertManagement|There was an error while updating the assignee(s) of the alert. Please try again."
+msgstr ""
+
+msgid "AlertManagement|There was an error while updating the status of the alert. Please try again."
+msgstr ""
+
+msgid "AlertManagement|This assignee cannot be assigned to this alert."
+msgstr ""
+
+msgid "AlertManagement|Tool"
+msgstr ""
+
+msgid "AlertManagement|Triggered"
+msgstr ""
+
+msgid "AlertManagement|Unassigned"
+msgstr ""
+
+msgid "AlertManagement|Unknown"
+msgstr ""
+
+msgid "AlertManagement|View issue"
+msgstr ""
+
+msgid "AlertService|Review your external service's documentation to learn where to provide this information to your external service, and the %{linkStart}GitLab documentation%{linkEnd} to learn more about configuring your endpoint."
+msgstr ""
+
+msgid "AlertService|You must provide this URL and authorization key to authorize an external service to send alerts to GitLab. You can provide this URL and key to multiple services. After configuring an external service, alerts from your service will display on the GitLab %{linkStart}Alerts%{linkEnd} page."
+msgstr ""
+
+msgid "Alerts"
+msgstr ""
+
+msgid "Alerts endpoint"
+msgstr ""
+
+msgid "Algorithm"
+msgstr ""
+
+msgid "All"
+msgstr ""
+
+msgid "All %{replicableType} are being scheduled for %{action}"
+msgstr ""
+
+msgid "All (default)"
+msgstr ""
+
+msgid "All Members"
+msgstr ""
+
+msgid "All branches"
+msgstr ""
+
+msgid "All changes are committed"
+msgstr ""
+
+msgid "All default stages are currently visible"
+msgstr ""
+
+msgid "All email addresses will be used to identify your commits."
+msgstr ""
+
+msgid "All environments"
+msgstr ""
+
+msgid "All features are enabled for blank projects, from templates, or when importing, but you can disable them afterward in the project settings."
+msgstr ""
+
+msgid "All groups and projects"
+msgstr ""
+
+msgid "All issues for this milestone are closed."
+msgstr ""
+
+msgid "All issues for this milestone are closed. You may close this milestone now."
+msgstr ""
+
+msgid "All merge conflicts were resolved. The merge request can now be merged."
+msgstr ""
+
+msgid "All merge request dependencies have been merged"
+msgstr ""
+
+msgid "All paths are relative to the GitLab URL. Do not include %{relative_url_link_start}relative URL%{relative_url_link_end}."
+msgstr ""
+
+msgid "All projects"
+msgstr ""
+
+msgid "All security scans are enabled because %{linkStart}Auto DevOps%{linkEnd} is enabled on this project"
+msgstr ""
+
+msgid "All threads resolved"
+msgstr ""
+
+msgid "All users"
+msgstr ""
+
+msgid "All users must have a name."
+msgstr ""
+
+msgid "Allow \"%{group_name}\" to sign you in"
+msgstr ""
+
+msgid "Allow commits from members who can merge to the target branch."
+msgstr ""
+
+msgid "Allow group owners to manage LDAP-related settings"
+msgstr ""
+
+msgid "Allow only the selected protocols to be used for Git access."
+msgstr ""
+
+msgid "Allow owners to manage default branch protection per group"
+msgstr ""
+
+msgid "Allow owners to manually add users outside of LDAP"
+msgstr ""
+
+msgid "Allow projects within this group to use Git LFS"
+msgstr ""
+
+msgid "Allow public access to pipelines and job details, including output logs and artifacts"
+msgstr ""
+
+msgid "Allow rendering of PlantUML diagrams in Asciidoc documents."
+msgstr ""
+
+msgid "Allow repository mirroring to be configured by project maintainers"
+msgstr ""
+
+msgid "Allow requests to the local network from hooks and services."
+msgstr ""
+
+msgid "Allow requests to the local network from system hooks"
+msgstr ""
+
+msgid "Allow requests to the local network from web hooks and services"
+msgstr ""
+
+msgid "Allow this key to push to repository as well? (Default only allows pull access.)"
+msgstr ""
+
+msgid "Allow this secondary node to replicate content on Object Storage"
+msgstr ""
+
+msgid "Allow users to dismiss the broadcast message"
+msgstr ""
+
+msgid "Allow users to register any application to use GitLab as an OAuth provider"
+msgstr ""
+
+msgid "Allow users to request access (if visibility is public or internal)"
+msgstr ""
+
+msgid "Allowed Geo IP"
+msgstr ""
+
+msgid "Allowed email domain restriction only permitted for top-level groups"
+msgstr ""
+
+msgid "Allowed to fail"
+msgstr ""
+
+msgid "Allows you to add and manage Kubernetes clusters."
+msgstr ""
+
+msgid "Almost there"
+msgstr ""
+
+msgid "Also called \"Issuer\" or \"Relying party trust identifier\""
+msgstr ""
+
+msgid "Also called \"Relying party service URL\" or \"Reply URL\""
+msgstr ""
+
+msgid "Alternate support URL for help page and help dropdown"
+msgstr ""
+
+msgid "Alternatively, you can convert your account to a managed account by the %{group_name} group."
+msgstr ""
+
+msgid "Amazon EKS"
+msgstr ""
+
+msgid "Amazon EKS integration allows you to provision EKS clusters from GitLab."
+msgstr ""
+
+msgid "Amazon Web Services"
+msgstr ""
+
+msgid "Amazon Web Services Logo"
+msgstr ""
+
+msgid "Amazon authentication is not %{link_start}correctly configured%{link_end}. Ask your GitLab administrator if you want to use this service."
+msgstr ""
+
+msgid "Amount of time (in hours) that users are allowed to skip forced configuration of two-factor authentication"
+msgstr ""
+
+msgid "An alert has been triggered in %{project_path}."
+msgstr ""
+
+msgid "An application called %{link_to_client} is requesting access to your GitLab account."
+msgstr ""
+
+msgid "An email notification was recently sent from the admin panel. Please wait %{wait_time_in_words} before attempting to send another message."
+msgstr ""
+
+msgid "An empty GitLab User field will add the FogBugz user's full name (e.g. \"By John Smith\") in the description of all issues and comments. It will also associate and/or assign these issues and comments with the project creator."
+msgstr ""
+
+msgid "An error has occurred"
+msgstr ""
+
+msgid "An error occurred adding a draft to the thread."
+msgstr ""
+
+msgid "An error occurred adding a new draft."
+msgstr ""
+
+msgid "An error occurred creating the new branch."
+msgstr ""
+
+msgid "An error occurred fetching the approval rules."
+msgstr ""
+
+msgid "An error occurred fetching the approvers for the new rule."
+msgstr ""
+
+msgid "An error occurred fetching the dropdown data."
+msgstr ""
+
+msgid "An error occurred fetching the project authors."
+msgstr ""
+
+msgid "An error occurred previewing the blob"
+msgstr ""
+
+msgid "An error occurred when toggling the notification subscription"
+msgstr ""
+
+msgid "An error occurred when trying to resolve a comment. Please try again."
+msgstr ""
+
+msgid "An error occurred when trying to resolve a discussion. Please try again."
+msgstr ""
+
+msgid "An error occurred when updating the issue weight"
+msgstr ""
+
+msgid "An error occurred while acknowledging the notification. Refresh the page and try again."
+msgstr ""
+
+msgid "An error occurred while adding approvers"
+msgstr ""
+
+msgid "An error occurred while adding formatted title for epic"
+msgstr ""
+
+msgid "An error occurred while checking group path. Please refresh and try again."
+msgstr ""
+
+msgid "An error occurred while committing your changes."
+msgstr ""
+
+msgid "An error occurred while decoding the file."
+msgstr ""
+
+msgid "An error occurred while deleting the approvers group"
+msgstr ""
+
+msgid "An error occurred while deleting the comment"
+msgstr ""
+
+msgid "An error occurred while deleting the pipeline."
+msgstr ""
+
+msgid "An error occurred while detecting host keys"
+msgstr ""
+
+msgid "An error occurred while disabling Service Desk."
+msgstr ""
+
+msgid "An error occurred while dismissing the alert. Refresh the page and try again."
+msgstr ""
+
+msgid "An error occurred while dismissing the feature highlight. Refresh the page and try dismissing again."
+msgstr ""
+
+msgid "An error occurred while enabling Service Desk."
+msgstr ""
+
+msgid "An error occurred while fetching coverage reports."
+msgstr ""
+
+msgid "An error occurred while fetching environments."
+msgstr ""
+
+msgid "An error occurred while fetching exposed artifacts."
+msgstr ""
+
+msgid "An error occurred while fetching folder content."
+msgstr ""
+
+msgid "An error occurred while fetching issues."
+msgstr ""
+
+msgid "An error occurred while fetching label colors."
+msgstr ""
+
+msgid "An error occurred while fetching markdown preview"
+msgstr ""
+
+msgid "An error occurred while fetching pending comments"
+msgstr ""
+
+msgid "An error occurred while fetching projects autocomplete."
+msgstr ""
+
+msgid "An error occurred while fetching sidebar data"
+msgstr ""
+
+msgid "An error occurred while fetching terraform reports."
+msgstr ""
+
+msgid "An error occurred while fetching the Service Desk address."
+msgstr ""
+
+msgid "An error occurred while fetching the board lists. Please try again."
+msgstr ""
+
+msgid "An error occurred while fetching the builds."
+msgstr ""
+
+msgid "An error occurred while fetching the job log."
+msgstr ""
+
+msgid "An error occurred while fetching the job trace."
+msgstr ""
+
+msgid "An error occurred while fetching the job."
+msgstr ""
+
+msgid "An error occurred while fetching the jobs."
+msgstr ""
+
+msgid "An error occurred while fetching the latest pipeline."
+msgstr ""
+
+msgid "An error occurred while fetching the pipeline."
+msgstr ""
+
+msgid "An error occurred while fetching the releases. Please try again."
+msgstr ""
+
+msgid "An error occurred while fetching this tab."
+msgstr ""
+
+msgid "An error occurred while generating a username. Please try again."
+msgstr ""
+
+msgid "An error occurred while getting files for - %{branchId}"
+msgstr ""
+
+msgid "An error occurred while getting projects"
+msgstr ""
+
+msgid "An error occurred while importing project: %{details}"
+msgstr ""
+
+msgid "An error occurred while initializing path locks"
+msgstr ""
+
+msgid "An error occurred while loading all the files."
+msgstr ""
+
+msgid "An error occurred while loading chart data"
+msgstr ""
+
+msgid "An error occurred while loading commit signatures"
+msgstr ""
+
+msgid "An error occurred while loading designs. Please try again."
+msgstr ""
+
+msgid "An error occurred while loading diff"
+msgstr ""
+
+msgid "An error occurred while loading filenames"
+msgstr ""
+
+msgid "An error occurred while loading group members."
+msgstr ""
+
+msgid "An error occurred while loading issues"
+msgstr ""
+
+msgid "An error occurred while loading merge requests."
+msgstr ""
+
+msgid "An error occurred while loading milestones"
+msgstr ""
+
+msgid "An error occurred while loading project creation UI"
+msgstr ""
+
+msgid "An error occurred while loading the data. Please try again."
+msgstr ""
+
+msgid "An error occurred while loading the file"
+msgstr ""
+
+msgid "An error occurred while loading the file content."
+msgstr ""
+
+msgid "An error occurred while loading the file."
+msgstr ""
+
+msgid "An error occurred while loading the file. Please try again later."
+msgstr ""
+
+msgid "An error occurred while loading the merge request changes."
+msgstr ""
+
+msgid "An error occurred while loading the merge request version data."
+msgstr ""
+
+msgid "An error occurred while loading the merge request."
+msgstr ""
+
+msgid "An error occurred while loading the pipelines jobs."
+msgstr ""
+
+msgid "An error occurred while loading the subscription details."
+msgstr ""
+
+msgid "An error occurred while making the request."
+msgstr ""
+
+msgid "An error occurred while moving the issue."
+msgstr ""
+
+msgid "An error occurred while parsing recent searches"
+msgstr ""
+
+msgid "An error occurred while parsing the file."
+msgstr ""
+
+msgid "An error occurred while removing epics."
+msgstr ""
+
+msgid "An error occurred while removing issues."
+msgstr ""
+
+msgid "An error occurred while rendering preview broadcast message"
+msgstr ""
+
+msgid "An error occurred while rendering the editor"
+msgstr ""
+
+msgid "An error occurred while reordering issues."
+msgstr ""
+
+msgid "An error occurred while retrieving calendar activity"
+msgstr ""
+
+msgid "An error occurred while retrieving diff"
+msgstr ""
+
+msgid "An error occurred while saving LDAP override status. Please try again."
+msgstr ""
+
+msgid "An error occurred while saving assignees"
+msgstr ""
+
+msgid "An error occurred while saving the approval settings"
+msgstr ""
+
+msgid "An error occurred while saving the template. Please check if the template exists."
+msgstr ""
+
+msgid "An error occurred while searching for milestones"
+msgstr ""
+
+msgid "An error occurred while subscribing to notifications."
+msgstr ""
+
+msgid "An error occurred while triggering the job."
+msgstr ""
+
+msgid "An error occurred while trying to run a new pipeline for this Merge Request."
+msgstr ""
+
+msgid "An error occurred while unsubscribing to notifications."
+msgstr ""
+
+msgid "An error occurred while updating approvers"
+msgstr ""
+
+msgid "An error occurred while updating the comment"
+msgstr ""
+
+msgid "An error occurred while validating group path"
+msgstr ""
+
+msgid "An error occurred while validating username"
+msgstr ""
+
+msgid "An error occurred. Please try again."
+msgstr ""
+
+msgid "An error ocurred while loading your content. Please try again."
+msgstr ""
+
+msgid "An example project for managing Kubernetes clusters integrated with GitLab."
+msgstr ""
+
+msgid "An instance-level serverless domain already exists."
+msgstr ""
+
+msgid "An issue already exists"
+msgstr ""
+
+msgid "An issue can be a bug, a todo or a feature request that needs to be discussed in a project. Besides, issues are searchable and filterable."
+msgstr ""
+
+msgid "An unauthenticated user"
+msgstr ""
+
+msgid "An unexpected error occurred while checking the project environment."
+msgstr ""
+
+msgid "An unexpected error occurred while checking the project runners."
+msgstr ""
+
+msgid "An unexpected error occurred while communicating with the Web Terminal."
+msgstr ""
+
+msgid "An unexpected error occurred while starting the Web Terminal."
+msgstr ""
+
+msgid "An unexpected error occurred while stopping the Web Terminal."
+msgstr ""
+
+msgid "An unknown error occurred while loading this graph."
+msgstr ""
+
+msgid "Analytics"
+msgstr ""
+
+msgid "Analyze a review version of your web application."
+msgstr ""
+
+msgid "Analyze your dependencies for known vulnerabilities."
+msgstr ""
+
+msgid "Analyze your source code and git history for secrets."
+msgstr ""
+
+msgid "Analyze your source code for known vulnerabilities."
+msgstr ""
+
+msgid "Ancestors"
+msgstr ""
+
+msgid "Anonymous"
+msgstr ""
+
+msgid "Another action is currently in progress"
+msgstr ""
+
+msgid "Another issue tracker is already in use. Only one issue tracker service can be active at a time"
+msgstr ""
+
+msgid "Anti-spam verification"
+msgstr ""
+
+msgid "Any"
+msgstr ""
+
+msgid "Any Author"
+msgstr ""
+
+msgid "Any branch"
+msgstr ""
+
+msgid "Any eligible user"
+msgstr ""
+
+msgid "Any encrypted tokens"
+msgstr ""
+
+msgid "Any label"
+msgstr ""
+
+msgid "Any member with Developer or higher permissions to the project."
+msgstr ""
+
+msgid "Any milestone"
+msgstr ""
+
+msgid "Any namespace"
+msgstr ""
+
+msgid "Any user"
+msgstr ""
+
+msgid "App ID"
+msgstr ""
+
+msgid "Appearance"
+msgstr ""
+
+msgid "Appearance was successfully created."
+msgstr ""
+
+msgid "Appearance was successfully updated."
+msgstr ""
+
+msgid "Append the comment with %{shrug}"
+msgstr ""
+
+msgid "Append the comment with %{tableflip}"
+msgstr ""
+
+msgid "Application"
+msgstr ""
+
+msgid "Application ID"
+msgstr ""
+
+msgid "Application settings saved successfully"
+msgstr ""
+
+msgid "Application settings update failed"
+msgstr ""
+
+msgid "Application uninstalled but failed to destroy: %{error_message}"
+msgstr ""
+
+msgid "Application was successfully destroyed."
+msgstr ""
+
+msgid "Application was successfully updated."
+msgstr ""
+
+msgid "Application: %{name}"
+msgstr ""
+
+msgid "Applications"
+msgstr ""
+
+msgid "Applied"
+msgstr ""
+
+msgid "Apply"
+msgstr ""
+
+msgid "Apply a label"
+msgstr ""
+
+msgid "Apply a template"
+msgstr ""
+
+msgid "Apply changes"
+msgstr ""
+
+msgid "Apply suggestion"
+msgstr ""
+
+msgid "Apply suggestions"
+msgstr ""
+
+msgid "Apply template"
+msgstr ""
+
+msgid "Apply this approval rule to any branch or a specific protected branch."
+msgstr ""
+
+msgid "Applying"
+msgstr ""
+
+msgid "Applying a template will replace the existing issue description. Any changes you have made will be lost."
+msgstr ""
+
+msgid "Applying command"
+msgstr ""
+
+msgid "Applying command to %{commandDescription}"
+msgstr ""
+
+msgid "Applying multiple commands"
+msgstr ""
+
+msgid "Applying suggestion..."
+msgstr ""
+
+msgid "Applying suggestions..."
+msgstr ""
+
+msgid "Approval rules"
+msgstr ""
+
+msgid "Approval rules reset to project defaults"
+msgstr ""
+
+msgid "ApprovalRuleRemove|%d member"
+msgid_plural "ApprovalRuleRemove|%d members"
+msgstr[0] ""
+
+msgid "ApprovalRuleRemove|Approvals from this member are not revoked."
+msgid_plural "ApprovalRuleRemove|Approvals from these members are not revoked."
+msgstr[0] ""
+
+msgid "ApprovalRuleRemove|You are about to remove the %{name} approver group which has %{nMembers}."
+msgstr ""
+
+msgid "ApprovalRuleSummary|%d member"
+msgid_plural "ApprovalRuleSummary|%d members"
+msgstr[0] ""
+
+msgid "ApprovalRuleSummary|%{count} approval required from %{membersCount}"
+msgid_plural "ApprovalRuleSummary|%{count} approvals required from %{membersCount}"
+msgstr[0] ""
+
+msgid "ApprovalRule|Approvers"
+msgstr ""
+
+msgid "ApprovalRule|Name"
+msgstr ""
+
+msgid "ApprovalRule|No. approvals required"
+msgstr ""
+
+msgid "ApprovalRule|Rule name"
+msgstr ""
+
+msgid "ApprovalRule|Target branch"
+msgstr ""
+
+msgid "ApprovalRule|e.g. QA, Security, etc."
+msgstr ""
+
+msgid "Approve"
+msgstr ""
+
+msgid "Approve a merge request"
+msgstr ""
+
+msgid "Approve the current merge request."
+msgstr ""
+
+msgid "Approved"
+msgstr ""
+
+msgid "Approved MRs"
+msgstr ""
+
+msgid "Approved by: "
+msgstr ""
+
+msgid "Approved the current merge request."
+msgstr ""
+
+msgid "Approved-By"
+msgstr ""
+
+msgid "Approver"
+msgstr ""
+
+msgid "Approvers"
+msgstr ""
+
+msgid "Apr"
+msgstr ""
+
+msgid "April"
+msgstr ""
+
+msgid "Archive"
+msgstr ""
+
+msgid "Archive jobs"
+msgstr ""
+
+msgid "Archive project"
+msgstr ""
+
+msgid "Archived"
+msgstr ""
+
+msgid "Archived project! Repository and other project resources are read only"
+msgstr ""
+
+msgid "Archived project! Repository and other project resources are read-only"
+msgstr ""
+
+msgid "Archived projects"
+msgstr ""
+
+msgid "Archiving the project will make it entirely read only. It is hidden from the dashboard and doesn't show up in searches. %{strong_start}The repository cannot be committed to, and no issues, comments, or other entities can be created.%{strong_end}"
+msgstr ""
+
+msgid "Are you setting up GitLab for a company?"
+msgstr ""
+
+msgid "Are you sure that you want to archive this project?"
+msgstr ""
+
+msgid "Are you sure that you want to unarchive this project?"
+msgstr ""
+
+msgid "Are you sure you want to cancel editing this comment?"
+msgstr ""
+
+msgid "Are you sure you want to close this blocked issue?"
+msgstr ""
+
+msgid "Are you sure you want to delete %{name}?"
+msgstr ""
+
+msgid "Are you sure you want to delete these artifacts?"
+msgstr ""
+
+msgid "Are you sure you want to delete this %{typeOfComment}?"
+msgstr ""
+
+msgid "Are you sure you want to delete this board?"
+msgstr ""
+
+msgid "Are you sure you want to delete this device? This action cannot be undone."
+msgstr ""
+
+msgid "Are you sure you want to delete this list?"
+msgstr ""
+
+msgid "Are you sure you want to delete this pipeline schedule?"
+msgstr ""
+
+msgid "Are you sure you want to delete this pipeline? Doing so will expire all pipeline caches and delete all related objects, such as builds, logs, artifacts, and triggers. This action cannot be undone."
+msgstr ""
+
+msgid "Are you sure you want to deploy this environment?"
+msgstr ""
+
+msgid "Are you sure you want to discard this comment?"
+msgstr ""
+
+msgid "Are you sure you want to erase this build?"
+msgstr ""
+
+msgid "Are you sure you want to lose unsaved changes?"
+msgstr ""
+
+msgid "Are you sure you want to lose your issue information?"
+msgstr ""
+
+msgid "Are you sure you want to merge immediately?"
+msgstr ""
+
+msgid "Are you sure you want to permanently delete this license?"
+msgstr ""
+
+msgid "Are you sure you want to re-deploy this environment?"
+msgstr ""
+
+msgid "Are you sure you want to regenerate the public key? You will have to update the public key on the remote server before mirroring will work again."
+msgstr ""
+
+msgid "Are you sure you want to remove %{group_name}?"
+msgstr ""
+
+msgid "Are you sure you want to remove the attachment?"
+msgstr ""
+
+msgid "Are you sure you want to remove the license?"
+msgstr ""
+
+msgid "Are you sure you want to remove this identity?"
+msgstr ""
+
+msgid "Are you sure you want to reset registration token?"
+msgstr ""
+
+msgid "Are you sure you want to reset the SCIM token? SCIM provisioning will stop working until the new token is updated."
+msgstr ""
+
+msgid "Are you sure you want to reset the health check token?"
+msgstr ""
+
+msgid "Are you sure you want to revoke this %{type}? This action cannot be undone."
+msgstr ""
+
+msgid "Are you sure you want to revoke this nickname?"
+msgstr ""
+
+msgid "Are you sure you want to stop this environment?"
+msgstr ""
+
+msgid "Are you sure you want to unlock %{path_lock_path}?"
+msgstr ""
+
+msgid "Are you sure you want to unsubscribe from the %{type}: %{link_to_noteable_text}?"
+msgstr ""
+
+msgid "Are you sure?"
+msgstr ""
+
+msgid "Are you sure? All commits that were signed with this GPG key will be unverified."
+msgstr ""
+
+msgid "Are you sure? Removing this GPG key does not affect already signed commits."
+msgstr ""
+
+msgid "Are you sure? The device will be signed out of GitLab."
+msgstr ""
+
+msgid "Are you sure? This will invalidate your registered applications and U2F devices."
+msgstr ""
+
+msgid "Arrange charts"
+msgstr ""
+
+msgid "Artifact"
+msgstr ""
+
+msgid "Artifact ID"
+msgstr ""
+
+msgid "Artifact could not be deleted."
+msgstr ""
+
+msgid "Artifact was successfully deleted."
+msgstr ""
+
+msgid "Artifacts"
+msgstr ""
+
+msgid "As U2F devices are only supported by a few browsers, we require that you set up a two-factor authentication app before a U2F device. That way you'll always be able to log in - even when you're using an unsupported browser."
+msgstr ""
+
+msgid "AsanaService|%{user} pushed to branch %{branch} of %{project_name} ( %{commit_url} ):"
+msgstr ""
+
+msgid "AsanaService|Asana - Teamwork without email"
+msgstr ""
+
+msgid "AsanaService|Comma-separated list of branches which will be automatically inspected. Leave blank to include all branches."
+msgstr ""
+
+msgid "AsanaService|User Personal Access Token. User must have access to task, all comments will be attributed to this user."
+msgstr ""
+
+msgid "Ascending"
+msgstr ""
+
+msgid "Ask your group maintainer to set up a group Runner."
+msgstr ""
+
+msgid "Assertion consumer service URL"
+msgstr ""
+
+msgid "Assets"
+msgstr ""
+
+msgid "Assets:"
+msgstr ""
+
+msgid "Assign"
+msgstr ""
+
+msgid "Assign Iteration"
+msgstr ""
+
+msgid "Assign custom color like #FF0000"
+msgstr ""
+
+msgid "Assign epic"
+msgstr ""
+
+msgid "Assign labels"
+msgstr ""
+
+msgid "Assign milestone"
+msgstr ""
+
+msgid "Assign some issues to this milestone."
+msgstr ""
+
+msgid "Assign to"
+msgstr ""
+
+msgid "Assign to commenting user"
+msgstr ""
+
+msgid "Assign yourself to these issues"
+msgstr ""
+
+msgid "Assign yourself to this issue"
+msgstr ""
+
+msgid "Assigned %{assignee_users_sentence}."
+msgstr ""
+
+msgid "Assigned Issues"
+msgstr ""
+
+msgid "Assigned Merge Requests"
+msgstr ""
+
+msgid "Assigned to %{assignee_name}"
+msgstr ""
+
+msgid "Assigned to me"
+msgstr ""
+
+msgid "Assignee"
+msgid_plural "%d Assignees"
+msgstr[0] ""
+
+msgid "Assignee has no permissions"
+msgstr ""
+
+msgid "Assignee lists not available with your current license"
+msgstr ""
+
+msgid "Assignee lists show all issues assigned to the selected user."
+msgstr ""
+
+msgid "Assignee(s)"
+msgstr ""
+
+msgid "Assignees"
+msgstr ""
+
+msgid "Assigns %{assignee_users_sentence}."
+msgstr ""
+
+msgid "At least one approval from a code owner is required to change files matching the respective CODEOWNER rules."
+msgstr ""
+
+msgid "At least one logging option is required to be enabled"
+msgstr ""
+
+msgid "At least one of group_id or project_id must be specified"
+msgstr ""
+
+msgid "At risk"
+msgstr ""
+
+msgid "Attach a file"
+msgstr ""
+
+msgid "Attach a file by drag &amp; drop or %{upload_link}"
+msgstr ""
+
+msgid "Attaching a file"
+msgid_plural "Attaching %d files"
+msgstr[0] ""
+
+msgid "Attaching the file failed."
+msgstr ""
+
+msgid "Audit Events"
+msgstr ""
+
+msgid "Audit Events is a way to keep track of important events that happened in GitLab."
+msgstr ""
+
+msgid "Audit Log"
+msgstr ""
+
+msgid "AuditEvents|(removed)"
+msgstr ""
+
+msgid "AuditEvents|Action"
+msgstr ""
+
+msgid "AuditEvents|At"
+msgstr ""
+
+msgid "AuditEvents|Target"
+msgstr ""
+
+msgid "AuditLogs|(removed)"
+msgstr ""
+
+msgid "AuditLogs|Action"
+msgstr ""
+
+msgid "AuditLogs|Author"
+msgstr ""
+
+msgid "AuditLogs|Date"
+msgstr ""
+
+msgid "AuditLogs|Failed to find %{type}. Please search for another %{type}."
+msgstr ""
+
+msgid "AuditLogs|Failed to find %{type}. Please try again."
+msgstr ""
+
+msgid "AuditLogs|Group Events"
+msgstr ""
+
+msgid "AuditLogs|IP Address"
+msgstr ""
+
+msgid "AuditLogs|Member Events"
+msgstr ""
+
+msgid "AuditLogs|No matching %{type} found."
+msgstr ""
+
+msgid "AuditLogs|Object"
+msgstr ""
+
+msgid "AuditLogs|Project Events"
+msgstr ""
+
+msgid "AuditLogs|Target"
+msgstr ""
+
+msgid "AuditLogs|User Events"
+msgstr ""
+
+msgid "Aug"
+msgstr ""
+
+msgid "August"
+msgstr ""
+
+msgid "Authenticate"
+msgstr ""
+
+msgid "Authenticate with GitHub"
+msgstr ""
+
+msgid "Authenticating"
+msgstr ""
+
+msgid "Authentication Failure"
+msgstr ""
+
+msgid "Authentication Log"
+msgstr ""
+
+msgid "Authentication failed: %{error_message}"
+msgstr ""
+
+msgid "Authentication log"
+msgstr ""
+
+msgid "Authentication method"
+msgstr ""
+
+msgid "Authentication method updated"
+msgstr ""
+
+msgid "Authentication via U2F device failed."
+msgstr ""
+
+msgid "Author"
+msgstr ""
+
+msgid "Authored %{timeago} by %{author}"
+msgstr ""
+
+msgid "Authorization code:"
+msgstr ""
+
+msgid "Authorization key"
+msgstr ""
+
+msgid "Authorization was granted by entering your username and password in the application."
+msgstr ""
+
+msgid "Authorize"
+msgstr ""
+
+msgid "Authorize %{link_to_client} to use your account?"
+msgstr ""
+
+msgid "Authorize external services to send alerts to GitLab"
+msgstr ""
+
+msgid "Authorized %{new_chat_name}"
+msgstr ""
+
+msgid "Authorized At"
+msgstr ""
+
+msgid "Authorized applications (%{size})"
+msgstr ""
+
+msgid "Authors: %{authors}"
+msgstr ""
+
+msgid "Auto DevOps"
+msgstr ""
+
+msgid "Auto DevOps enabled"
+msgstr ""
+
+msgid "Auto DevOps, runners and job artifacts"
+msgstr ""
+
+msgid "Auto stop successfully canceled."
+msgstr ""
+
+msgid "Auto-cancel redundant, pending pipelines"
+msgstr ""
+
+msgid "Auto-close referenced issues on default branch"
+msgstr ""
+
+msgid "AutoDevOps|Auto DevOps"
+msgstr ""
+
+msgid "AutoDevOps|Auto DevOps can automatically build, test, and deploy applications based on predefined continuous integration and delivery configuration. %{auto_devops_start}Learn more about Auto DevOps%{auto_devops_end} or use our %{quickstart_start}quick start guide%{quickstart_end} to get started right away."
+msgstr ""
+
+msgid "AutoDevOps|Auto DevOps documentation"
+msgstr ""
+
+msgid "AutoDevOps|Dismiss Auto DevOps box"
+msgstr ""
+
+msgid "AutoDevOps|Enable in settings"
+msgstr ""
+
+msgid "AutoDevOps|It will automatically build, test, and deploy your application based on a predefined CI/CD configuration."
+msgstr ""
+
+msgid "AutoDevOps|Learn more in the %{link_to_documentation}"
+msgstr ""
+
+msgid "AutoDevOps|The Auto DevOps pipeline has been enabled and will be used if no alternative CI configuration file is found. %{more_information_link}"
+msgstr ""
+
+msgid "Autocomplete"
+msgstr ""
+
+msgid "Autocomplete description"
+msgstr ""
+
+msgid "Autocomplete hint"
+msgstr ""
+
+msgid "Autocomplete usage hint"
+msgstr ""
+
+msgid "Automatic certificate management using %{lets_encrypt_link_start}Let's Encrypt%{lets_encrypt_link_end}"
+msgstr ""
+
+msgid "Automatic certificate management using Let's Encrypt"
+msgstr ""
+
+msgid "Automatically create merge requests for vulnerabilities that have fixes available."
+msgstr ""
+
+msgid "Automatically marked as default internal user"
+msgstr ""
+
+msgid "Automatically resolved"
+msgstr ""
+
+msgid "Automatically update this project's branches and tags from the upstream repository every hour."
+msgstr ""
+
+msgid "Autosave|Note"
+msgstr ""
+
+msgid "Available"
+msgstr ""
+
+msgid "Available for dependency and container scanning"
+msgstr ""
+
+msgid "Available group Runners: %{runners}"
+msgstr ""
+
+msgid "Available shared Runners:"
+msgstr ""
+
+msgid "Available specific runners"
+msgstr ""
+
+msgid "Avatar for %{assigneeName}"
+msgstr ""
+
+msgid "Avatar for %{name}"
+msgstr ""
+
+msgid "Avatar will be removed. Are you sure?"
+msgstr ""
+
+msgid "Average per day: %{average}"
+msgstr ""
+
+msgid "Back to page %{number}"
+msgstr ""
+
+msgid "Background Color"
+msgstr ""
+
+msgid "Background Jobs"
+msgstr ""
+
+msgid "Background color"
+msgstr ""
+
+msgid "Badges"
+msgstr ""
+
+msgid "Badges|A new badge was added."
+msgstr ""
+
+msgid "Badges|Add badge"
+msgstr ""
+
+msgid "Badges|Adding the badge failed, please check the entered URLs and try again."
+msgstr ""
+
+msgid "Badges|Badge image URL"
+msgstr ""
+
+msgid "Badges|Badge image preview"
+msgstr ""
+
+msgid "Badges|Delete badge"
+msgstr ""
+
+msgid "Badges|Delete badge?"
+msgstr ""
+
+msgid "Badges|Deleting the badge failed, please try again."
+msgstr ""
+
+msgid "Badges|Group Badge"
+msgstr ""
+
+msgid "Badges|Link"
+msgstr ""
+
+msgid "Badges|Name"
+msgstr ""
+
+msgid "Badges|No badge image"
+msgstr ""
+
+msgid "Badges|No image to preview"
+msgstr ""
+
+msgid "Badges|Please fill in a valid URL"
+msgstr ""
+
+msgid "Badges|Project Badge"
+msgstr ""
+
+msgid "Badges|Reload badge image"
+msgstr ""
+
+msgid "Badges|Save changes"
+msgstr ""
+
+msgid "Badges|Saving the badge failed, please check the entered URLs and try again."
+msgstr ""
+
+msgid "Badges|The %{docsLinkStart}variables%{docsLinkEnd} GitLab supports: %{placeholders}"
+msgstr ""
+
+msgid "Badges|The badge was deleted."
+msgstr ""
+
+msgid "Badges|The badge was saved."
+msgstr ""
+
+msgid "Badges|This group has no badges"
+msgstr ""
+
+msgid "Badges|This project has no badges"
+msgstr ""
+
+msgid "Badges|You are going to delete this badge. Deleted badges <strong>cannot</strong> be restored."
+msgstr ""
+
+msgid "Badges|Your badges"
+msgstr ""
+
+msgid "Badges|e.g. %{exampleUrl}"
+msgstr ""
+
+msgid "Balsamiq file could not be loaded."
+msgstr ""
+
+msgid "BambooService|A continuous integration and build server"
+msgstr ""
+
+msgid "BambooService|A user with API access, if applicable"
+msgstr ""
+
+msgid "BambooService|Atlassian Bamboo CI"
+msgstr ""
+
+msgid "BambooService|Bamboo build plan key like KEY"
+msgstr ""
+
+msgid "BambooService|Bamboo root URL like https://bamboo.example.com"
+msgstr ""
+
+msgid "BambooService|You must set up automatic revision labeling and a repository trigger in Bamboo."
+msgstr ""
+
+msgid "BatchComments|Delete all pending comments"
+msgstr ""
+
+msgid "BatchComments|Discard review?"
+msgstr ""
+
+msgid "BatchComments|You're about to discard your review which will delete all of your pending comments. The deleted comments %{strong_start}cannot%{strong_end} be restored."
+msgstr ""
+
+msgid "Be careful. Changing the project's namespace can have unintended side effects."
+msgstr ""
+
+msgid "Be careful. Renaming a project's repository can have unintended side effects."
+msgstr ""
+
+msgid "Begin with the selected commit"
+msgstr ""
+
+msgid "Below are examples of regex for existing tools:"
+msgstr ""
+
+msgid "Below are the fingerprints for the current instance SSH host keys."
+msgstr ""
+
+msgid "Below you will find all the groups that are public."
+msgstr ""
+
+msgid "Beta"
+msgstr ""
+
+msgid "Bi-weekly code coverage"
+msgstr ""
+
+msgid "Billing"
+msgstr ""
+
+msgid "BillingPlans|%{group_name} is currently using the %{plan_name} plan."
+msgstr ""
+
+msgid "BillingPlans|@%{user_name} you are currently using the %{plan_name} plan."
+msgstr ""
+
+msgid "BillingPlans|Congratulations, your new trial is activated"
+msgstr ""
+
+msgid "BillingPlans|If you would like to downgrade your plan please contact %{support_link_start}Customer Support%{support_link_end}."
+msgstr ""
+
+msgid "BillingPlans|Learn more about each plan by reading our %{faq_link}, or start a free 30-day trial of GitLab.com Gold."
+msgstr ""
+
+msgid "BillingPlans|Learn more about each plan by visiting our %{pricing_page_link}."
+msgstr ""
+
+msgid "BillingPlans|Manage plan"
+msgstr ""
+
+msgid "BillingPlans|Pricing page"
+msgstr ""
+
+msgid "BillingPlans|See all %{plan_name} features"
+msgstr ""
+
+msgid "BillingPlans|This group uses the plan associated with its parent group."
+msgstr ""
+
+msgid "BillingPlans|To manage the plan for this group, visit the billing section of %{parent_billing_page_link}."
+msgstr ""
+
+msgid "BillingPlans|Your GitLab.com %{plan} trial will <strong>expire after %{expiration_date}</strong>. You can retain access to the %{plan} features by upgrading below."
+msgstr ""
+
+msgid "BillingPlans|Your GitLab.com trial expired on %{expiration_date}. You can restore access to the features at any time by upgrading below."
+msgstr ""
+
+msgid "BillingPlans|billed annually at %{price_per_year}"
+msgstr ""
+
+msgid "BillingPlans|frequently asked questions"
+msgstr ""
+
+msgid "BillingPlans|monthly"
+msgstr ""
+
+msgid "BillingPlans|per user"
+msgstr ""
+
+msgid "BillingPlan|Upgrade"
+msgstr ""
+
+msgid "Bitbucket Server Import"
+msgstr ""
+
+msgid "Bitbucket Server import"
+msgstr ""
+
+msgid "Bitbucket import"
+msgstr ""
+
+msgid "Blame"
+msgstr ""
+
+msgid "Blocked"
+msgstr ""
+
+msgid "Blocked issue"
+msgstr ""
+
+msgid "Blocks"
+msgstr ""
+
+msgid "Blog"
+msgstr ""
+
+msgid "Board name"
+msgstr ""
+
+msgid "Board scope"
+msgstr ""
+
+msgid "Board scope affects which issues are displayed for anyone who visits this board"
+msgstr ""
+
+msgid "BoardBlankState|Add default lists"
+msgstr ""
+
+msgid "BoardBlankState|Add the following default lists to your Issue Board with one click:"
+msgstr ""
+
+msgid "BoardBlankState|Nevermind, I'll use my own"
+msgstr ""
+
+msgid "BoardBlankState|Starting out with the default set of lists will get you right on the way to making the most of your board."
+msgstr ""
+
+msgid "Boards"
+msgstr ""
+
+msgid "Boards and Board Lists"
+msgstr ""
+
+msgid "Boards|Collapse"
+msgstr ""
+
+msgid "Boards|Edit board"
+msgstr ""
+
+msgid "Boards|Expand"
+msgstr ""
+
+msgid "Boards|View scope"
+msgstr ""
+
+msgid "Branch"
+msgstr ""
+
+msgid "Branch %{branchName} was not found in this project's repository."
+msgstr ""
+
+msgid "Branch <strong>%{branch_name}</strong> was created. To set up auto deploy, choose a GitLab CI Yaml template and commit your changes. %{link_to_autodeploy_doc}"
+msgstr ""
+
+msgid "Branch has changed"
+msgstr ""
+
+msgid "Branch is already taken"
+msgstr ""
+
+msgid "Branch name"
+msgstr ""
+
+msgid "Branch not loaded - %{branchId}"
+msgstr ""
+
+msgid "Branch prefix"
+msgstr ""
+
+msgid "BranchSwitcherPlaceholder|Search branches"
+msgstr ""
+
+msgid "BranchSwitcherTitle|Switch branch"
+msgstr ""
+
+msgid "Branches"
+msgstr ""
+
+msgid "Branches|Active"
+msgstr ""
+
+msgid "Branches|Active branches"
+msgstr ""
+
+msgid "Branches|All"
+msgstr ""
+
+msgid "Branches|Cant find HEAD commit for this branch"
+msgstr ""
+
+msgid "Branches|Compare"
+msgstr ""
+
+msgid "Branches|Delete all branches that are merged into '%{default_branch}'"
+msgstr ""
+
+msgid "Branches|Delete branch"
+msgstr ""
+
+msgid "Branches|Delete merged branches"
+msgstr ""
+
+msgid "Branches|Delete protected branch"
+msgstr ""
+
+msgid "Branches|Delete protected branch '%{branch_name}'?"
+msgstr ""
+
+msgid "Branches|Deleting the '%{branch_name}' branch cannot be undone. Are you sure?"
+msgstr ""
+
+msgid "Branches|Deleting the merged branches cannot be undone. Are you sure?"
+msgstr ""
+
+msgid "Branches|Filter by branch name"
+msgstr ""
+
+msgid "Branches|Merged into %{default_branch}"
+msgstr ""
+
+msgid "Branches|New branch"
+msgstr ""
+
+msgid "Branches|No branches to show"
+msgstr ""
+
+msgid "Branches|Once you confirm and press %{delete_protected_branch}, it cannot be undone or recovered."
+msgstr ""
+
+msgid "Branches|Only a project maintainer or owner can delete a protected branch"
+msgstr ""
+
+msgid "Branches|Overview"
+msgstr ""
+
+msgid "Branches|Protected branches can be managed in %{project_settings_link}."
+msgstr ""
+
+msgid "Branches|Show active branches"
+msgstr ""
+
+msgid "Branches|Show all branches"
+msgstr ""
+
+msgid "Branches|Show more active branches"
+msgstr ""
+
+msgid "Branches|Show more stale branches"
+msgstr ""
+
+msgid "Branches|Show overview of the branches"
+msgstr ""
+
+msgid "Branches|Show stale branches"
+msgstr ""
+
+msgid "Branches|Sort by"
+msgstr ""
+
+msgid "Branches|Stale"
+msgstr ""
+
+msgid "Branches|Stale branches"
+msgstr ""
+
+msgid "Branches|The branch could not be updated automatically because it has diverged from its upstream counterpart."
+msgstr ""
+
+msgid "Branches|The default branch cannot be deleted"
+msgstr ""
+
+msgid "Branches|This branch hasn’t been merged into %{default_branch}."
+msgstr ""
+
+msgid "Branches|To avoid data loss, consider merging this branch before deleting it."
+msgstr ""
+
+msgid "Branches|To confirm, type %{branch_name_confirmation}:"
+msgstr ""
+
+msgid "Branches|To discard the local changes and overwrite the branch with the upstream version, delete it here and choose 'Update Now' above."
+msgstr ""
+
+msgid "Branches|You’re about to permanently delete the protected branch %{branch_name}."
+msgstr ""
+
+msgid "Branches|diverged from upstream"
+msgstr ""
+
+msgid "Branches|merged"
+msgstr ""
+
+msgid "Branches|project settings"
+msgstr ""
+
+msgid "Branches|protected"
+msgstr ""
+
+msgid "Broadcast Message was successfully created."
+msgstr ""
+
+msgid "Broadcast Message was successfully updated."
+msgstr ""
+
+msgid "Broadcast Messages"
+msgstr ""
+
+msgid "Browse Directory"
+msgstr ""
+
+msgid "Browse File"
+msgstr ""
+
+msgid "Browse Files"
+msgstr ""
+
+msgid "Browse artifacts"
+msgstr ""
+
+msgid "Browse files"
+msgstr ""
+
+msgid "BuildArtifacts|An error occurred while fetching the artifacts"
+msgstr ""
+
+msgid "BuildArtifacts|Loading artifacts"
+msgstr ""
+
+msgid "Built-in"
+msgstr ""
+
+msgid "Bulk request concurrency"
+msgstr ""
+
+msgid "Burndown chart"
+msgstr ""
+
+msgid "BurndownChartLabel|Open issue weight"
+msgstr ""
+
+msgid "BurndownChartLabel|Open issues"
+msgstr ""
+
+msgid "Burnup chart"
+msgstr ""
+
+msgid "Business"
+msgstr ""
+
+msgid "Business metrics (Custom)"
+msgstr ""
+
+msgid "Buy License"
+msgstr ""
+
+msgid "Buy more Pipeline minutes"
+msgstr ""
+
+msgid "By %{user_name}"
+msgstr ""
+
+msgid "By URL"
+msgstr ""
+
+msgid "By default GitLab sends emails in HTML and plain text formats so mail clients can choose what format to use. Disable this option if you only want to send emails in plain text format."
+msgstr ""
+
+msgid "By default, all projects and groups will use the global notifications setting."
+msgstr ""
+
+msgid "ByAuthor|by"
+msgstr ""
+
+msgid "CHANGELOG"
+msgstr ""
+
+msgid "CI / CD"
+msgstr ""
+
+msgid "CI / CD Analytics"
+msgstr ""
+
+msgid "CI / CD Settings"
+msgstr ""
+
+msgid "CI Lint"
+msgstr ""
+
+msgid "CI settings"
+msgstr ""
+
+msgid "CI variables"
+msgstr ""
+
+msgid "CI will run using the credentials assigned above."
+msgstr ""
+
+msgid "CI/CD"
+msgstr ""
+
+msgid "CI/CD configuration"
+msgstr ""
+
+msgid "CI/CD for external repo"
+msgstr ""
+
+msgid "CI/CD settings"
+msgstr ""
+
+msgid "CICD|Add a %{kubernetes_cluster_link_start}Kubernetes cluster integration%{link_end} with a domain or create an AUTO_DEVOPS_PLATFORM_TARGET CI variable."
+msgstr ""
+
+msgid "CICD|Auto DevOps"
+msgstr ""
+
+msgid "CICD|Automatic deployment to staging, manual deployment to production"
+msgstr ""
+
+msgid "CICD|Continuous deployment to production"
+msgstr ""
+
+msgid "CICD|Continuous deployment to production using timed incremental rollout"
+msgstr ""
+
+msgid "CICD|Default to Auto DevOps pipeline"
+msgstr ""
+
+msgid "CICD|Default to Auto DevOps pipeline for all projects"
+msgstr ""
+
+msgid "CICD|Deployment strategy"
+msgstr ""
+
+msgid "CICD|Jobs"
+msgstr ""
+
+msgid "CICD|The Auto DevOps pipeline will run if no alternative CI configuration file is found."
+msgstr ""
+
+msgid "CICD|You must add a %{base_domain_link_start}base domain%{link_end} to your %{kubernetes_cluster_link_start}Kubernetes cluster%{link_end} in order for your deployment strategy to work."
+msgstr ""
+
+msgid "CICD|group enabled"
+msgstr ""
+
+msgid "CICD|instance enabled"
+msgstr ""
+
+msgid "CLOSED"
+msgstr ""
+
+msgid "CLOSED (MOVED)"
+msgstr ""
+
+msgid "CONTRIBUTING"
+msgstr ""
+
+msgid "CPU"
+msgstr ""
+
+msgid "Callback URL"
+msgstr ""
+
+msgid "Can be manually deployed to"
+msgstr ""
+
+msgid "Can override approvers and approvals required per merge request"
+msgstr ""
+
+msgid "Can't apply as this line has changed or the suggestion already matches its content."
+msgstr ""
+
+msgid "Can't create snippet: %{err}"
+msgstr ""
+
+msgid "Can't edit as source branch was deleted"
+msgstr ""
+
+msgid "Can't find HEAD commit for this branch"
+msgstr ""
+
+msgid "Can't find variable: ZiteReader"
+msgstr ""
+
+msgid "Can't load mermaid module: %{err}"
+msgstr ""
+
+msgid "Can't remove group members without group managed account"
+msgstr ""
+
+msgid "Can't scan the code?"
+msgstr ""
+
+msgid "Can't update snippet: %{err}"
+msgstr ""
+
+msgid "Canary"
+msgstr ""
+
+msgid "Canary Deployments is a popular CI strategy, where a small portion of the fleet is updated to the new version of your application."
+msgstr ""
+
+msgid "Cancel"
+msgstr ""
+
+msgid "Cancel running"
+msgstr ""
+
+msgid "Cancel this job"
+msgstr ""
+
+msgid "Canceled deployment to"
+msgstr ""
+
+msgid "Cancelling Preview"
+msgstr ""
+
+msgid "Cannot be merged automatically"
+msgstr ""
+
+msgid "Cannot create the abuse report. The user has been deleted."
+msgstr ""
+
+msgid "Cannot create the abuse report. This user has been blocked."
+msgstr ""
+
+msgid "Cannot have multiple Jira imports running at the same time"
+msgstr ""
+
+msgid "Cannot import because issues are not available in this project."
+msgstr ""
+
+msgid "Cannot make epic confidential if it contains not-confidential issues"
+msgstr ""
+
+msgid "Cannot make epic confidential if it contains not-confidential sub-epics"
+msgstr ""
+
+msgid "Cannot merge"
+msgstr ""
+
+msgid "Cannot modify managed Kubernetes cluster"
+msgstr ""
+
+msgid "Cannot modify provider during creation"
+msgstr ""
+
+msgid "Cannot promote issue because it does not belong to a group."
+msgstr ""
+
+msgid "Cannot promote issue due to insufficient permissions."
+msgstr ""
+
+msgid "Cannot refer to a group %{timebox_type} by an internal id!"
+msgstr ""
+
+msgid "Cannot set confidential epic for not-confidential issue"
+msgstr ""
+
+msgid "Cannot show preview. For previews on sketch files, they must have the file format introduced by Sketch version 43 and above."
+msgstr ""
+
+msgid "Cannot skip two factor authentication setup"
+msgstr ""
+
+msgid "Capacity threshold"
+msgstr ""
+
+msgid "Certain user content will be moved to a system-wide \"Ghost User\" in order to maintain content for posterity. For further information, please refer to the %{link_start}user account deletion documentation.%{link_end}"
+msgstr ""
+
+msgid "Certificate"
+msgstr ""
+
+msgid "Certificate (PEM)"
+msgstr ""
+
+msgid "Certificate Issuer"
+msgstr ""
+
+msgid "Certificate Subject"
+msgstr ""
+
+msgid "Change assignee"
+msgstr ""
+
+msgid "Change assignee(s)"
+msgstr ""
+
+msgid "Change assignee(s)."
+msgstr ""
+
+msgid "Change branches"
+msgstr ""
+
+msgid "Change label"
+msgstr ""
+
+msgid "Change milestone"
+msgstr ""
+
+msgid "Change path"
+msgstr ""
+
+msgid "Change permissions"
+msgstr ""
+
+msgid "Change status"
+msgstr ""
+
+msgid "Change subscription"
+msgstr ""
+
+msgid "Change template"
+msgstr ""
+
+msgid "Change this value to influence how frequently the GitLab UI polls for updates."
+msgstr ""
+
+msgid "Change title"
+msgstr ""
+
+msgid "Change your password"
+msgstr ""
+
+msgid "Change your password or recover your current one"
+msgstr ""
+
+msgid "ChangeTypeActionLabel|Pick into branch"
+msgstr ""
+
+msgid "ChangeTypeActionLabel|Revert in branch"
+msgstr ""
+
+msgid "ChangeTypeAction|Cherry-pick"
+msgstr ""
+
+msgid "ChangeTypeAction|Revert"
+msgstr ""
+
+msgid "ChangeTypeAction|This will create a new commit in order to revert the existing changes."
+msgstr ""
+
+msgid "Changed assignee(s)."
+msgstr ""
+
+msgid "Changed the title to \"%{title_param}\"."
+msgstr ""
+
+msgid "Changes"
+msgstr ""
+
+msgid "Changes are shown as if the <b>source</b> revision was being merged into the <b>target</b> revision."
+msgstr ""
+
+msgid "Changes are still tracked. Useful for cluster/index migrations."
+msgstr ""
+
+msgid "Changes suppressed. Click to show."
+msgstr ""
+
+msgid "Changes the title to \"%{title_param}\"."
+msgstr ""
+
+msgid "Changes won't take place until the index is %{link_start}recreated%{link_end}."
+msgstr ""
+
+msgid "Changing a Release tag is only supported via Releases API. %{linkStart}More information%{linkEnd}"
+msgstr ""
+
+msgid "Changing group path can have unintended side effects."
+msgstr ""
+
+msgid "Channel handle (e.g. town-square)"
+msgstr ""
+
+msgid "Charts"
+msgstr ""
+
+msgid "Charts can't be displayed as the request for data has timed out. %{documentationLink}"
+msgstr ""
+
+msgid "Chat"
+msgstr ""
+
+msgid "ChatMessage|%{project_link}: Pipeline %{pipeline_link} of %{ref_type} %{ref_link} by %{user_combined_name} %{humanized_status} in %{duration}"
+msgstr ""
+
+msgid "ChatMessage|Branch"
+msgstr ""
+
+msgid "ChatMessage|Commit"
+msgstr ""
+
+msgid "ChatMessage|Failed job"
+msgstr ""
+
+msgid "ChatMessage|Failed stage"
+msgstr ""
+
+msgid "ChatMessage|Invalid CI config YAML file"
+msgstr ""
+
+msgid "ChatMessage|Pipeline #%{pipeline_id} %{humanized_status} in %{duration}"
+msgstr ""
+
+msgid "ChatMessage|Pipeline %{pipeline_link} of %{ref_type} %{ref_link} by %{user_combined_name} %{humanized_status}"
+msgstr ""
+
+msgid "ChatMessage|Tag"
+msgstr ""
+
+msgid "ChatMessage|and [%{count} more](%{pipeline_failed_jobs_url})"
+msgstr ""
+
+msgid "ChatMessage|has failed"
+msgstr ""
+
+msgid "ChatMessage|has passed"
+msgstr ""
+
+msgid "ChatMessage|has passed with warnings"
+msgstr ""
+
+msgid "ChatMessage|in %{duration}"
+msgstr ""
+
+msgid "ChatMessage|in %{project_link}"
+msgstr ""
+
+msgid "Check again"
+msgstr ""
+
+msgid "Check feature availability on namespace plan"
+msgstr ""
+
+msgid "Check the %{docs_link_start}documentation%{docs_link_end}."
+msgstr ""
+
+msgid "Check your .gitlab-ci.yml"
+msgstr ""
+
+msgid "Check your Docker images for known vulnerabilities."
+msgstr ""
+
+msgid "Checking %{text} availability…"
+msgstr ""
+
+msgid "Checking approval status"
+msgstr ""
+
+msgid "Checking branch availability..."
+msgstr ""
+
+msgid "Checking group path availability..."
+msgstr ""
+
+msgid "Checking username availability..."
+msgstr ""
+
+msgid "Checkout"
+msgstr ""
+
+msgid "Checkout|$%{selectedPlanPrice} per user per year"
+msgstr ""
+
+msgid "Checkout|%{cardType} ending in %{lastFourDigits}"
+msgstr ""
+
+msgid "Checkout|%{name}'s GitLab subscription"
+msgstr ""
+
+msgid "Checkout|%{selectedPlanText} plan"
+msgstr ""
+
+msgid "Checkout|%{startDate} - %{endDate}"
+msgstr ""
+
+msgid "Checkout|(x%{numberOfUsers})"
+msgstr ""
+
+msgid "Checkout|Billing address"
+msgstr ""
+
+msgid "Checkout|Checkout"
+msgstr ""
+
+msgid "Checkout|City"
+msgstr ""
+
+msgid "Checkout|Confirm purchase"
+msgstr ""
+
+msgid "Checkout|Confirming..."
+msgstr ""
+
+msgid "Checkout|Continue to billing"
+msgstr ""
+
+msgid "Checkout|Continue to payment"
+msgstr ""
+
+msgid "Checkout|Country"
+msgstr ""
+
+msgid "Checkout|Create a new group"
+msgstr ""
+
+msgid "Checkout|Credit card form failed to load. Please try again."
+msgstr ""
+
+msgid "Checkout|Credit card form failed to load: %{message}"
+msgstr ""
+
+msgid "Checkout|Edit"
+msgstr ""
+
+msgid "Checkout|Exp %{expirationMonth}/%{expirationYear}"
+msgstr ""
+
+msgid "Checkout|Failed to confirm your order! Please try again."
+msgstr ""
+
+msgid "Checkout|Failed to confirm your order: %{message}. Please try again."
+msgstr ""
+
+msgid "Checkout|Failed to load countries. Please try again."
+msgstr ""
+
+msgid "Checkout|Failed to load states. Please try again."
+msgstr ""
+
+msgid "Checkout|Failed to register credit card. Please try again."
+msgstr ""
+
+msgid "Checkout|GitLab group"
+msgstr ""
+
+msgid "Checkout|GitLab plan"
+msgstr ""
+
+msgid "Checkout|Group"
+msgstr ""
+
+msgid "Checkout|Name of company or organization using GitLab"
+msgstr ""
+
+msgid "Checkout|Need more users? Purchase GitLab for your %{company}."
+msgstr ""
+
+msgid "Checkout|Number of users"
+msgstr ""
+
+msgid "Checkout|Payment method"
+msgstr ""
+
+msgid "Checkout|Please select a country"
+msgstr ""
+
+msgid "Checkout|Please select a state"
+msgstr ""
+
+msgid "Checkout|Select"
+msgstr ""
+
+msgid "Checkout|State"
+msgstr ""
+
+msgid "Checkout|Street address"
+msgstr ""
+
+msgid "Checkout|Submitting the credit card form failed with code %{errorCode}: %{errorMessage}"
+msgstr ""
+
+msgid "Checkout|Subscription details"
+msgstr ""
+
+msgid "Checkout|Subtotal"
+msgstr ""
+
+msgid "Checkout|Tax"
+msgstr ""
+
+msgid "Checkout|Total"
+msgstr ""
+
+msgid "Checkout|Users"
+msgstr ""
+
+msgid "Checkout|You'll create your new group after checkout"
+msgstr ""
+
+msgid "Checkout|Your organization"
+msgstr ""
+
+msgid "Checkout|Your subscription will be applied to this group"
+msgstr ""
+
+msgid "Checkout|Zip code"
+msgstr ""
+
+msgid "Checkout|company or team"
+msgstr ""
+
+msgid "Cherry-pick this commit"
+msgstr ""
+
+msgid "Cherry-pick this merge request"
+msgstr ""
+
+msgid "Child"
+msgstr ""
+
+msgid "Child epic does not exist."
+msgstr ""
+
+msgid "Child epic doesn't exist."
+msgstr ""
+
+msgid "Choose <strong>Create archive</strong> and wait for archiving to complete."
+msgstr ""
+
+msgid "Choose <strong>Next</strong> at the bottom of the page."
+msgstr ""
+
+msgid "Choose a branch/tag (e.g. %{master}) or enter a commit (e.g. %{sha}) to see what's changed or to create a merge request."
+msgstr ""
+
+msgid "Choose a file"
+msgstr ""
+
+msgid "Choose a group"
+msgstr ""
+
+msgid "Choose a role permission"
+msgstr ""
+
+msgid "Choose a template"
+msgstr ""
+
+msgid "Choose a template..."
+msgstr ""
+
+msgid "Choose a type..."
+msgstr ""
+
+msgid "Choose any color."
+msgstr ""
+
+msgid "Choose between <code>clone</code> or <code>fetch</code> to get the recent application code"
+msgstr ""
+
+msgid "Choose file…"
+msgstr ""
+
+msgid "Choose the top-level group for your repository imports."
+msgstr ""
+
+msgid "Choose visibility level, enable/disable project features (issues, repository, wiki, snippets) and set permissions."
+msgstr ""
+
+msgid "Choose what content you want to see on a group’s overview page"
+msgstr ""
+
+msgid "Choose which repositories you want to connect and run CI/CD pipelines."
+msgstr ""
+
+msgid "Choose your framework"
+msgstr ""
+
+msgid "CiStatusLabel|canceled"
+msgstr ""
+
+msgid "CiStatusLabel|created"
+msgstr ""
+
+msgid "CiStatusLabel|delayed"
+msgstr ""
+
+msgid "CiStatusLabel|failed"
+msgstr ""
+
+msgid "CiStatusLabel|manual action"
+msgstr ""
+
+msgid "CiStatusLabel|passed"
+msgstr ""
+
+msgid "CiStatusLabel|passed with warnings"
+msgstr ""
+
+msgid "CiStatusLabel|pending"
+msgstr ""
+
+msgid "CiStatusLabel|preparing"
+msgstr ""
+
+msgid "CiStatusLabel|skipped"
+msgstr ""
+
+msgid "CiStatusLabel|waiting for delayed job"
+msgstr ""
+
+msgid "CiStatusLabel|waiting for manual action"
+msgstr ""
+
+msgid "CiStatusLabel|waiting for resource"
+msgstr ""
+
+msgid "CiStatusText|blocked"
+msgstr ""
+
+msgid "CiStatusText|canceled"
+msgstr ""
+
+msgid "CiStatusText|created"
+msgstr ""
+
+msgid "CiStatusText|delayed"
+msgstr ""
+
+msgid "CiStatusText|failed"
+msgstr ""
+
+msgid "CiStatusText|manual"
+msgstr ""
+
+msgid "CiStatusText|passed"
+msgstr ""
+
+msgid "CiStatusText|pending"
+msgstr ""
+
+msgid "CiStatusText|preparing"
+msgstr ""
+
+msgid "CiStatusText|skipped"
+msgstr ""
+
+msgid "CiStatusText|waiting"
+msgstr ""
+
+msgid "CiStatus|running"
+msgstr ""
+
+msgid "CiVariables|Cannot use Masked Variable with current value"
+msgstr ""
+
+msgid "CiVariables|Environments"
+msgstr ""
+
+msgid "CiVariables|Input variable key"
+msgstr ""
+
+msgid "CiVariables|Input variable value"
+msgstr ""
+
+msgid "CiVariables|Key"
+msgstr ""
+
+msgid "CiVariables|Masked"
+msgstr ""
+
+msgid "CiVariables|Protected"
+msgstr ""
+
+msgid "CiVariables|Remove variable row"
+msgstr ""
+
+msgid "CiVariables|Scope"
+msgstr ""
+
+msgid "CiVariables|Specify variable values to be used in this run. The values specified in %{linkStart}CI/CD settings%{linkEnd} will be used as default"
+msgstr ""
+
+msgid "CiVariables|State"
+msgstr ""
+
+msgid "CiVariables|Type"
+msgstr ""
+
+msgid "CiVariables|Value"
+msgstr ""
+
+msgid "CiVariables|Variables"
+msgstr ""
+
+msgid "CiVariable|* (All environments)"
+msgstr ""
+
+msgid "CiVariable|All environments"
+msgstr ""
+
+msgid "CiVariable|Create wildcard"
+msgstr ""
+
+msgid "CiVariable|Error occurred while saving variables"
+msgstr ""
+
+msgid "CiVariable|Masked"
+msgstr ""
+
+msgid "CiVariable|New environment"
+msgstr ""
+
+msgid "CiVariable|Protected"
+msgstr ""
+
+msgid "CiVariable|Search environments"
+msgstr ""
+
+msgid "CiVariable|Toggle masked"
+msgstr ""
+
+msgid "CiVariable|Toggle protected"
+msgstr ""
+
+msgid "CiVariable|Validation failed"
+msgstr ""
+
+msgid "Class"
+msgstr ""
+
+msgid "Classification Label (optional)"
+msgstr ""
+
+msgid "ClassificationLabelUnavailable|is unavailable: %{reason}"
+msgstr ""
+
+msgid "Clear"
+msgstr ""
+
+msgid "Clear chart filters"
+msgstr ""
+
+msgid "Clear due date"
+msgstr ""
+
+msgid "Clear input"
+msgstr ""
+
+msgid "Clear recent searches"
+msgstr ""
+
+msgid "Clear search"
+msgstr ""
+
+msgid "Clear search input"
+msgstr ""
+
+msgid "Clear start date"
+msgstr ""
+
+msgid "Clear templates search input"
+msgstr ""
+
+msgid "Clear weight"
+msgstr ""
+
+msgid "Cleared weight."
+msgstr ""
+
+msgid "Clears weight."
+msgstr ""
+
+msgid "Click any <strong>project name</strong> in the project list below to navigate to the project milestone."
+msgstr ""
+
+msgid "Click the <strong>Download</strong> button and wait for downloading to complete."
+msgstr ""
+
+msgid "Click the <strong>Promote</strong> button in the top right corner to promote it to a group milestone."
+msgstr ""
+
+msgid "Click the <strong>Select none</strong> button on the right, since we only need \"Google Code Project Hosting\"."
+msgstr ""
+
+msgid "Click the button below to begin the install process by navigating to the Kubernetes page"
+msgstr ""
+
+msgid "Click to expand it."
+msgstr ""
+
+msgid "Click to expand text"
+msgstr ""
+
+msgid "Client authentication certificate"
+msgstr ""
+
+msgid "Client authentication key"
+msgstr ""
+
+msgid "Client authentication key password"
+msgstr ""
+
+msgid "Clients"
+msgstr ""
+
+msgid "Clone"
+msgstr ""
+
+msgid "Clone repository"
+msgstr ""
+
+msgid "Clone with %{http_label}"
+msgstr ""
+
+msgid "Clone with %{protocol}"
+msgstr ""
+
+msgid "Clone with KRB5"
+msgstr ""
+
+msgid "Clone with SSH"
+msgstr ""
+
+msgid "Close"
+msgstr ""
+
+msgid "Close %{display_issuable_type}"
+msgstr ""
+
+msgid "Close %{tabname}"
+msgstr ""
+
+msgid "Close epic"
+msgstr ""
+
+msgid "Close milestone"
+msgstr ""
+
+msgid "Close sidebar"
+msgstr ""
+
+msgid "Close this %{quick_action_target}"
+msgstr ""
+
+msgid "Closed"
+msgstr ""
+
+msgid "Closed issues"
+msgstr ""
+
+msgid "Closed this %{quick_action_target}."
+msgstr ""
+
+msgid "Closed: %{closedIssuesCount}"
+msgstr ""
+
+msgid "Closes this %{quick_action_target}."
+msgstr ""
+
+msgid "Cluster"
+msgstr ""
+
+msgid "Cluster Health"
+msgstr ""
+
+msgid "Cluster cache cleared."
+msgstr ""
+
+msgid "Cluster does not exist"
+msgstr ""
+
+msgid "Cluster is required for Stages::ClusterEndpointInserter"
+msgstr ""
+
+msgid "Cluster level"
+msgstr ""
+
+msgid "Cluster type must be specificed for Stages::ClusterEndpointInserter"
+msgstr ""
+
+msgid "ClusterIntegration| %{custom_domain_start}More information%{custom_domain_end}."
+msgstr ""
+
+msgid "ClusterIntegration| This will permanently delete the following resources: <ul> <li>All installed applications and related resources</li> <li>The <code>gitlab-managed-apps</code> namespace</li> <li>Any project namespaces</li> <li><code>clusterroles</code></li> <li><code>clusterrolebindings</code></li> </ul>"
+msgstr ""
+
+msgid "ClusterIntegration| can be used instead of a custom domain."
+msgstr ""
+
+msgid "ClusterIntegration| is the default environment scope for this cluster. This means that all jobs, regardless of their environment, will use this cluster. %{environment_scope_start}More information%{environment_scope_end}"
+msgstr ""
+
+msgid "ClusterIntegration|%{appList} was successfully installed on your Kubernetes cluster"
+msgstr ""
+
+msgid "ClusterIntegration|%{external_ip}.nip.io"
+msgstr ""
+
+msgid "ClusterIntegration|%{title} uninstalled successfully."
+msgstr ""
+
+msgid "ClusterIntegration|%{title} updated successfully."
+msgstr ""
+
+msgid "ClusterIntegration|A cluster management project can be used to run deployment jobs with Kubernetes <code>cluster-admin</code> privileges."
+msgstr ""
+
+msgid "ClusterIntegration|A service token scoped to %{code}kube-system%{end_code} with %{code}cluster-admin%{end_code} privileges."
+msgstr ""
+
+msgid "ClusterIntegration|API URL"
+msgstr ""
+
+msgid "ClusterIntegration|API URL should be a valid http/https url."
+msgstr ""
+
+msgid "ClusterIntegration|Add Kubernetes cluster"
+msgstr ""
+
+msgid "ClusterIntegration|Add a Kubernetes cluster integration"
+msgstr ""
+
+msgid "ClusterIntegration|Adding a Kubernetes cluster to your group will automatically share the cluster across all your projects. Use review apps, deploy your applications, and easily run your pipelines for all projects using the same cluster."
+msgstr ""
+
+msgid "ClusterIntegration|Adding a Kubernetes cluster will automatically share the cluster across all projects. Use review apps, deploy your applications, and easily run your pipelines for all projects using the same cluster."
+msgstr ""
+
+msgid "ClusterIntegration|Adding an integration to your group will share the cluster across all your projects."
+msgstr ""
+
+msgid "ClusterIntegration|Adding an integration will share the cluster across all projects."
+msgstr ""
+
+msgid "ClusterIntegration|Advanced options on this Kubernetes cluster’s integration"
+msgstr ""
+
+msgid "ClusterIntegration|All data not committed to GitLab will be deleted and cannot be restored."
+msgstr ""
+
+msgid "ClusterIntegration|All data will be deleted and cannot be restored."
+msgstr ""
+
+msgid "ClusterIntegration|Allow GitLab to manage namespace and service accounts for this cluster."
+msgstr ""
+
+msgid "ClusterIntegration|Allow GitLab to manage namespace and service accounts for this cluster. %{startLink}More information%{endLink}"
+msgstr ""
+
+msgid "ClusterIntegration|Alternatively"
+msgstr ""
+
+msgid "ClusterIntegration|Amazon EKS"
+msgstr ""
+
+msgid "ClusterIntegration|An error occurred when trying to contact the Google Cloud API. Please try again later."
+msgstr ""
+
+msgid "ClusterIntegration|An error occurred while trying to fetch project zones: %{error}"
+msgstr ""
+
+msgid "ClusterIntegration|An error occurred while trying to fetch your projects: %{error}"
+msgstr ""
+
+msgid "ClusterIntegration|An error occurred while trying to fetch zone machine types: %{error}"
+msgstr ""
+
+msgid "ClusterIntegration|Any running pipelines will be canceled."
+msgstr ""
+
+msgid "ClusterIntegration|Apply for credit"
+msgstr ""
+
+msgid "ClusterIntegration|Authenticate with AWS"
+msgstr ""
+
+msgid "ClusterIntegration|Authenticate with Amazon Web Services"
+msgstr ""
+
+msgid "ClusterIntegration|Base domain"
+msgstr ""
+
+msgid "ClusterIntegration|Blocking mode"
+msgstr ""
+
+msgid "ClusterIntegration|CA Certificate"
+msgstr ""
+
+msgid "ClusterIntegration|Cert-Manager"
+msgstr ""
+
+msgid "ClusterIntegration|Cert-Manager is a native Kubernetes certificate management controller that helps with issuing certificates. Installing Cert-Manager on your cluster will issue a certificate by %{linkStart}Let's Encrypt%{linkEnd} and ensure that certificates are valid and up-to-date."
+msgstr ""
+
+msgid "ClusterIntegration|Certificate Authority bundle (PEM format)"
+msgstr ""
+
+msgid "ClusterIntegration|Choose the %{startLink}security group %{externalLinkIcon} %{endLink} to apply to the EKS-managed Elastic Network Interfaces that are created in your worker node subnets."
+msgstr ""
+
+msgid "ClusterIntegration|Choose the %{startLink}subnets %{externalLinkIcon} %{endLink} in your VPC where your worker nodes will run."
+msgstr ""
+
+msgid "ClusterIntegration|Choose the worker node %{startLink}instance type %{externalLinkIcon} %{endLink}."
+msgstr ""
+
+msgid "ClusterIntegration|Choose which applications to install on your Kubernetes cluster. Helm Tiller is required to install any of the following applications."
+msgstr ""
+
+msgid "ClusterIntegration|Choose which of your environments will use this cluster."
+msgstr ""
+
+msgid "ClusterIntegration|Clear cluster cache"
+msgstr ""
+
+msgid "ClusterIntegration|Clear the local cache of namespace and service accounts. This is necessary if your integration has become out of sync. The cache is repopulated during the next CI job that requires namespace and service accounts."
+msgstr ""
+
+msgid "ClusterIntegration|Cluster being created"
+msgstr ""
+
+msgid "ClusterIntegration|Cluster management project (alpha)"
+msgstr ""
+
+msgid "ClusterIntegration|Cluster name is required."
+msgstr ""
+
+msgid "ClusterIntegration|Cluster_applications artifact too big. Maximum allowable size: %{human_size}"
+msgstr ""
+
+msgid "ClusterIntegration|Clusters are utilized by selecting the nearest ancestor with a matching environment scope. For example, project clusters will override group clusters."
+msgstr ""
+
+msgid "ClusterIntegration|Copy API URL"
+msgstr ""
+
+msgid "ClusterIntegration|Copy CA Certificate"
+msgstr ""
+
+msgid "ClusterIntegration|Copy Ingress Endpoint"
+msgstr ""
+
+msgid "ClusterIntegration|Copy Jupyter Hostname"
+msgstr ""
+
+msgid "ClusterIntegration|Copy Knative Endpoint"
+msgstr ""
+
+msgid "ClusterIntegration|Copy Kubernetes cluster name"
+msgstr ""
+
+msgid "ClusterIntegration|Could not load IAM roles"
+msgstr ""
+
+msgid "ClusterIntegration|Could not load Key Pairs"
+msgstr ""
+
+msgid "ClusterIntegration|Could not load VPCs for the selected region"
+msgstr ""
+
+msgid "ClusterIntegration|Could not load instance types"
+msgstr ""
+
+msgid "ClusterIntegration|Could not load networks"
+msgstr ""
+
+msgid "ClusterIntegration|Could not load regions from your AWS account"
+msgstr ""
+
+msgid "ClusterIntegration|Could not load security groups for the selected VPC"
+msgstr ""
+
+msgid "ClusterIntegration|Could not load subnets for the selected VPC"
+msgstr ""
+
+msgid "ClusterIntegration|Could not load subnetworks"
+msgstr ""
+
+msgid "ClusterIntegration|Create Kubernetes cluster"
+msgstr ""
+
+msgid "ClusterIntegration|Create a provision role on %{startAwsLink}Amazon Web Services %{externalLinkIcon}%{endLink} using the account and external ID above. %{startMoreInfoLink}More information%{endLink}"
+msgstr ""
+
+msgid "ClusterIntegration|Create cluster on"
+msgstr ""
+
+msgid "ClusterIntegration|Create new cluster"
+msgstr ""
+
+msgid "ClusterIntegration|Create new cluster on EKS"
+msgstr ""
+
+msgid "ClusterIntegration|Create new cluster on GKE"
+msgstr ""
+
+msgid "ClusterIntegration|Creating Kubernetes cluster"
+msgstr ""
+
+msgid "ClusterIntegration|Crossplane"
+msgstr ""
+
+msgid "ClusterIntegration|Crossplane enables declarative provisioning of managed services from your cloud of choice using %{codeStart}kubectl%{codeEnd} or %{linkStart}GitLab Integration%{linkEnd}. Crossplane runs inside your Kubernetes cluster and supports secure connectivity and secrets management between app containers and the cloud services they depend on."
+msgstr ""
+
+msgid "ClusterIntegration|Deletes all GitLab resources attached to this cluster during removal"
+msgstr ""
+
+msgid "ClusterIntegration|Did you know?"
+msgstr ""
+
+msgid "ClusterIntegration|Elastic Kubernetes Service"
+msgstr ""
+
+msgid "ClusterIntegration|Elastic Stack"
+msgstr ""
+
+msgid "ClusterIntegration|Enable Cloud Run for Anthos"
+msgstr ""
+
+msgid "ClusterIntegration|Enable or disable GitLab's connection to your Kubernetes cluster."
+msgstr ""
+
+msgid "ClusterIntegration|Enable this setting if using role-based access control (RBAC)."
+msgstr ""
+
+msgid "ClusterIntegration|Enabled stack"
+msgstr ""
+
+msgid "ClusterIntegration|Enter new Service Token"
+msgstr ""
+
+msgid "ClusterIntegration|Enter the details for your Amazon EKS Kubernetes cluster"
+msgstr ""
+
+msgid "ClusterIntegration|Enter the details for your Kubernetes cluster"
+msgstr ""
+
+msgid "ClusterIntegration|Environment scope"
+msgstr ""
+
+msgid "ClusterIntegration|Every new Google Cloud Platform (GCP) account receives $300 in credit upon %{sign_up_link}. In partnership with Google, GitLab is able to offer an additional $200 for both new and existing GCP accounts to get started with GitLab's Google Kubernetes Engine Integration."
+msgstr ""
+
+msgid "ClusterIntegration|Failed to configure EKS provider: %{message}"
+msgstr ""
+
+msgid "ClusterIntegration|Failed to configure Google Kubernetes Engine Cluster: %{message}"
+msgstr ""
+
+msgid "ClusterIntegration|Failed to fetch CloudFormation stack: %{message}"
+msgstr ""
+
+msgid "ClusterIntegration|Failed to request to Google Cloud Platform: %{message}"
+msgstr ""
+
+msgid "ClusterIntegration|Failed to run Kubeclient: %{message}"
+msgstr ""
+
+msgid "ClusterIntegration|Fetching machine types"
+msgstr ""
+
+msgid "ClusterIntegration|Fetching projects"
+msgstr ""
+
+msgid "ClusterIntegration|Fetching zones"
+msgstr ""
+
+msgid "ClusterIntegration|Fluentd"
+msgstr ""
+
+msgid "ClusterIntegration|Fluentd is an open source data collector, which lets you unify the data collection and consumption for a better use and understanding of data. It requires at least one of the following logs to be successfully installed."
+msgstr ""
+
+msgid "ClusterIntegration|GitLab Integration"
+msgstr ""
+
+msgid "ClusterIntegration|GitLab Runner"
+msgstr ""
+
+msgid "ClusterIntegration|GitLab Runner connects to the repository and executes CI/CD jobs, pushing results back and deploying applications to production."
+msgstr ""
+
+msgid "ClusterIntegration|GitLab-managed cluster"
+msgstr ""
+
+msgid "ClusterIntegration|Global default"
+msgstr ""
+
+msgid "ClusterIntegration|Google Cloud Platform project"
+msgstr ""
+
+msgid "ClusterIntegration|Google GKE"
+msgstr ""
+
+msgid "ClusterIntegration|Google Kubernetes Engine"
+msgstr ""
+
+msgid "ClusterIntegration|Google Kubernetes Engine project"
+msgstr ""
+
+msgid "ClusterIntegration|Group cluster"
+msgstr ""
+
+msgid "ClusterIntegration|Helm Tiller"
+msgstr ""
+
+msgid "ClusterIntegration|Helm release failed to install"
+msgstr ""
+
+msgid "ClusterIntegration|Helm streamlines installing and managing Kubernetes applications. Tiller runs inside of your Kubernetes Cluster, and manages releases of your charts."
+msgstr ""
+
+msgid "ClusterIntegration|If you are setting up multiple clusters and are using Auto DevOps, %{help_link_start}read this first%{help_link_end}."
+msgstr ""
+
+msgid "ClusterIntegration|If you do not wish to delete all associated GitLab resources, you can simply remove the integration."
+msgstr ""
+
+msgid "ClusterIntegration|In order to view the health of your cluster, you must first install Prometheus in the Applications tab."
+msgstr ""
+
+msgid "ClusterIntegration|Ingress"
+msgstr ""
+
+msgid "ClusterIntegration|Ingress Endpoint"
+msgstr ""
+
+msgid "ClusterIntegration|Ingress gives you a way to route requests to services based on the request host or path, centralizing a number of services into a single entrypoint."
+msgstr ""
+
+msgid "ClusterIntegration|Installing Ingress may incur additional costs. Learn more about %{linkStart}pricing%{linkEnd}."
+msgstr ""
+
+msgid "ClusterIntegration|Instance cluster"
+msgstr ""
+
+msgid "ClusterIntegration|Instance type"
+msgstr ""
+
+msgid "ClusterIntegration|Integrate Kubernetes cluster automation"
+msgstr ""
+
+msgid "ClusterIntegration|Issuer Email"
+msgstr ""
+
+msgid "ClusterIntegration|Issuers represent a certificate authority. You must provide an email address for your Issuer."
+msgstr ""
+
+msgid "ClusterIntegration|Jupyter Hostname"
+msgstr ""
+
+msgid "ClusterIntegration|JupyterHub"
+msgstr ""
+
+msgid "ClusterIntegration|JupyterHub, a multi-user Hub, spawns, manages, and proxies multiple instances of the single-user Jupyter notebook server. JupyterHub can be used to serve notebooks to a class of students, a corporate data science group, or a scientific research group."
+msgstr ""
+
+msgid "ClusterIntegration|Key pair name"
+msgstr ""
+
+msgid "ClusterIntegration|Knative"
+msgstr ""
+
+msgid "ClusterIntegration|Knative Domain Name:"
+msgstr ""
+
+msgid "ClusterIntegration|Knative Endpoint:"
+msgstr ""
+
+msgid "ClusterIntegration|Knative domain name was updated successfully."
+msgstr ""
+
+msgid "ClusterIntegration|Knative extends Kubernetes to provide a set of middleware components that are essential to build modern, source-centric, and container-based applications that can run anywhere: on premises, in the cloud, or even in a third-party data center."
+msgstr ""
+
+msgid "ClusterIntegration|Kubernetes cluster"
+msgstr ""
+
+msgid "ClusterIntegration|Kubernetes cluster is being created..."
+msgstr ""
+
+msgid "ClusterIntegration|Kubernetes cluster name"
+msgstr ""
+
+msgid "ClusterIntegration|Kubernetes cluster was successfully created."
+msgstr ""
+
+msgid "ClusterIntegration|Kubernetes clusters allow you to use review apps, deploy your applications, run your pipelines, and much more in an easy way."
+msgstr ""
+
+msgid "ClusterIntegration|Kubernetes clusters can be used to deploy applications and to provide Review Apps for this project"
+msgstr ""
+
+msgid "ClusterIntegration|Kubernetes version"
+msgstr ""
+
+msgid "ClusterIntegration|Kubernetes version not found"
+msgstr ""
+
+msgid "ClusterIntegration|Learn more about %{help_link_start_machine_type}machine types%{help_link_end} and %{help_link_start_pricing}pricing%{help_link_end}."
+msgstr ""
+
+msgid "ClusterIntegration|Learn more about %{help_link_start}zones%{help_link_end}."
+msgstr ""
+
+msgid "ClusterIntegration|Learn more about %{startLink}Regions %{externalLinkIcon}%{endLink}."
+msgstr ""
+
+msgid "ClusterIntegration|Learn more about Kubernetes"
+msgstr ""
+
+msgid "ClusterIntegration|Learn more about group Kubernetes clusters"
+msgstr ""
+
+msgid "ClusterIntegration|Learn more about instance Kubernetes clusters"
+msgstr ""
+
+msgid "ClusterIntegration|Loading IAM Roles"
+msgstr ""
+
+msgid "ClusterIntegration|Loading Key Pairs"
+msgstr ""
+
+msgid "ClusterIntegration|Loading Regions"
+msgstr ""
+
+msgid "ClusterIntegration|Loading VPCs"
+msgstr ""
+
+msgid "ClusterIntegration|Loading instance types"
+msgstr ""
+
+msgid "ClusterIntegration|Loading networks"
+msgstr ""
+
+msgid "ClusterIntegration|Loading security groups"
+msgstr ""
+
+msgid "ClusterIntegration|Loading subnets"
+msgstr ""
+
+msgid "ClusterIntegration|Loading subnetworks"
+msgstr ""
+
+msgid "ClusterIntegration|Logging mode"
+msgstr ""
+
+msgid "ClusterIntegration|Machine type"
+msgstr ""
+
+msgid "ClusterIntegration|Make sure your account %{link_to_requirements} to create Kubernetes clusters"
+msgstr ""
+
+msgid "ClusterIntegration|Manage your Kubernetes cluster by visiting %{provider_link}"
+msgstr ""
+
+msgid "ClusterIntegration|No IAM Roles found"
+msgstr ""
+
+msgid "ClusterIntegration|No Key Pairs found"
+msgstr ""
+
+msgid "ClusterIntegration|No VPCs found"
+msgstr ""
+
+msgid "ClusterIntegration|No deployment cluster found for this job"
+msgstr ""
+
+msgid "ClusterIntegration|No deployment found for this job"
+msgstr ""
+
+msgid "ClusterIntegration|No instance type found"
+msgstr ""
+
+msgid "ClusterIntegration|No machine types matched your search"
+msgstr ""
+
+msgid "ClusterIntegration|No networks found"
+msgstr ""
+
+msgid "ClusterIntegration|No projects found"
+msgstr ""
+
+msgid "ClusterIntegration|No projects matched your search"
+msgstr ""
+
+msgid "ClusterIntegration|No region found"
+msgstr ""
+
+msgid "ClusterIntegration|No security group found"
+msgstr ""
+
+msgid "ClusterIntegration|No subnet found"
+msgstr ""
+
+msgid "ClusterIntegration|No subnetworks found"
+msgstr ""
+
+msgid "ClusterIntegration|No zones matched your search"
+msgstr ""
+
+msgid "ClusterIntegration|Number of nodes"
+msgstr ""
+
+msgid "ClusterIntegration|Number of nodes must be a numerical value."
+msgstr ""
+
+msgid "ClusterIntegration|Please enter access information for your Kubernetes cluster. If you need help, you can read our %{link_to_help_page} on Kubernetes"
+msgstr ""
+
+msgid "ClusterIntegration|Please make sure that your Google account meets the following requirements:"
+msgstr ""
+
+msgid "ClusterIntegration|Point a wildcard DNS to this generated endpoint in order to access your application after it has been deployed."
+msgstr ""
+
+msgid "ClusterIntegration|Project cluster"
+msgstr ""
+
+msgid "ClusterIntegration|Project namespace (optional, unique)"
+msgstr ""
+
+msgid "ClusterIntegration|Project namespace prefix (optional, unique)"
+msgstr ""
+
+msgid "ClusterIntegration|Prometheus"
+msgstr ""
+
+msgid "ClusterIntegration|Prometheus is an open-source monitoring system with %{linkStart}GitLab Integration%{linkEnd} to monitor deployed applications."
+msgstr ""
+
+msgid "ClusterIntegration|Provider details"
+msgstr ""
+
+msgid "ClusterIntegration|Provision Role ARN"
+msgstr ""
+
+msgid "ClusterIntegration|RBAC-enabled cluster"
+msgstr ""
+
+msgid "ClusterIntegration|Read our %{link_start}help page%{link_end} on Kubernetes cluster integration."
+msgstr ""
+
+msgid "ClusterIntegration|Real-time web application monitoring, logging and access control. %{linkStart}More information%{linkEnd}"
+msgstr ""
+
+msgid "ClusterIntegration|Region"
+msgstr ""
+
+msgid "ClusterIntegration|Remove Kubernetes cluster integration"
+msgstr ""
+
+msgid "ClusterIntegration|Remove integration"
+msgstr ""
+
+msgid "ClusterIntegration|Remove integration and resources"
+msgstr ""
+
+msgid "ClusterIntegration|Remove integration and resources?"
+msgstr ""
+
+msgid "ClusterIntegration|Remove integration?"
+msgstr ""
+
+msgid "ClusterIntegration|Remove this Kubernetes cluster's configuration from this project. This will not delete your actual Kubernetes cluster."
+msgstr ""
+
+msgid "ClusterIntegration|Removes cluster from project but keeps associated resources"
+msgstr ""
+
+msgid "ClusterIntegration|Replace this with your own hostname if you want. If you do so, point hostname to Ingress IP Address from above."
+msgstr ""
+
+msgid "ClusterIntegration|Request to begin installing failed"
+msgstr ""
+
+msgid "ClusterIntegration|Request to begin uninstalling failed"
+msgstr ""
+
+msgid "ClusterIntegration|SIEM Hostname"
+msgstr ""
+
+msgid "ClusterIntegration|SIEM Port"
+msgstr ""
+
+msgid "ClusterIntegration|SIEM Protocol"
+msgstr ""
+
+msgid "ClusterIntegration|Save changes"
+msgstr ""
+
+msgid "ClusterIntegration|Search IAM Roles"
+msgstr ""
+
+msgid "ClusterIntegration|Search Key Pairs"
+msgstr ""
+
+msgid "ClusterIntegration|Search VPCs"
+msgstr ""
+
+msgid "ClusterIntegration|Search domains"
+msgstr ""
+
+msgid "ClusterIntegration|Search instance types"
+msgstr ""
+
+msgid "ClusterIntegration|Search machine types"
+msgstr ""
+
+msgid "ClusterIntegration|Search networks"
+msgstr ""
+
+msgid "ClusterIntegration|Search projects"
+msgstr ""
+
+msgid "ClusterIntegration|Search regions"
+msgstr ""
+
+msgid "ClusterIntegration|Search security groups"
+msgstr ""
+
+msgid "ClusterIntegration|Search subnets"
+msgstr ""
+
+msgid "ClusterIntegration|Search subnetworks"
+msgstr ""
+
+msgid "ClusterIntegration|Search zones"
+msgstr ""
+
+msgid "ClusterIntegration|Security group"
+msgstr ""
+
+msgid "ClusterIntegration|See and edit the details for your Kubernetes cluster"
+msgstr ""
+
+msgid "ClusterIntegration|Select a VPC to choose a security group"
+msgstr ""
+
+msgid "ClusterIntegration|Select a VPC to choose a subnet"
+msgstr ""
+
+msgid "ClusterIntegration|Select a VPC to use for your EKS Cluster resources. To use a new VPC, first create one on %{startLink}Amazon Web Services %{externalLinkIcon} %{endLink}."
+msgstr ""
+
+msgid "ClusterIntegration|Select a network to choose a subnetwork"
+msgstr ""
+
+msgid "ClusterIntegration|Select a region to choose a Key Pair"
+msgstr ""
+
+msgid "ClusterIntegration|Select a region to choose a VPC"
+msgstr ""
+
+msgid "ClusterIntegration|Select a stack to install Crossplane."
+msgstr ""
+
+msgid "ClusterIntegration|Select a zone to choose a network"
+msgstr ""
+
+msgid "ClusterIntegration|Select existing domain or use new"
+msgstr ""
+
+msgid "ClusterIntegration|Select machine type"
+msgstr ""
+
+msgid "ClusterIntegration|Select project"
+msgstr ""
+
+msgid "ClusterIntegration|Select project and zone to choose machine type"
+msgstr ""
+
+msgid "ClusterIntegration|Select project to choose zone"
+msgstr ""
+
+msgid "ClusterIntegration|Select the key pair name that will be used to create EC2 nodes. To use a new key pair name, first create one on %{startLink}Amazon Web Services %{externalLinkIcon} %{endLink}."
+msgstr ""
+
+msgid "ClusterIntegration|Select zone"
+msgstr ""
+
+msgid "ClusterIntegration|Select zone to choose machine type"
+msgstr ""
+
+msgid "ClusterIntegration|Send Container Network Policies Logs"
+msgstr ""
+
+msgid "ClusterIntegration|Send Web Application Firewall Logs"
+msgstr ""
+
+msgid "ClusterIntegration|Service Token"
+msgstr ""
+
+msgid "ClusterIntegration|Service role"
+msgstr ""
+
+msgid "ClusterIntegration|Service token is required."
+msgstr ""
+
+msgid "ClusterIntegration|Set a prefix for your namespaces. If not set, defaults to your project path. If modified, existing environments will use their current namespaces until the cluster cache is cleared."
+msgstr ""
+
+msgid "ClusterIntegration|Set the global mode for the WAF in this cluster. This can be overridden at the environmental level."
+msgstr ""
+
+msgid "ClusterIntegration|Something went wrong on our end."
+msgstr ""
+
+msgid "ClusterIntegration|Something went wrong while creating your Kubernetes cluster"
+msgstr ""
+
+msgid "ClusterIntegration|Something went wrong while installing %{title}"
+msgstr ""
+
+msgid "ClusterIntegration|Something went wrong while trying to save your settings. Please try again."
+msgstr ""
+
+msgid "ClusterIntegration|Something went wrong while uninstalling %{title}"
+msgstr ""
+
+msgid "ClusterIntegration|Something went wrong while updating Knative domain name."
+msgstr ""
+
+msgid "ClusterIntegration|Specifying a domain will allow you to use Auto Review Apps and Auto Deploy stages for %{auto_devops_start}Auto DevOps%{auto_devops_end}. The domain should have a wildcard DNS configured matching the domain."
+msgstr ""
+
+msgid "ClusterIntegration|Subnets"
+msgstr ""
+
+msgid "ClusterIntegration|The Amazon Resource Name (ARN) associated with your role. If you do not have a provision role, first create one on %{startAwsLink}Amazon Web Services %{externalLinkIcon}%{endLink} using the above account and external IDs. %{startMoreInfoLink}More information%{endLink}"
+msgstr ""
+
+msgid "ClusterIntegration|The Kubernetes certificate used to authenticate to the cluster."
+msgstr ""
+
+msgid "ClusterIntegration|The URL used to access the Kubernetes API."
+msgstr ""
+
+msgid "ClusterIntegration|The associated IP and all deployed services will be deleted and cannot be restored. Uninstalling Knative will also remove Istio from your cluster. This will not effect any other applications."
+msgstr ""
+
+msgid "ClusterIntegration|The associated Tiller pod, the %{gitlabManagedAppsNamespace} namespace, and all of its resources will be deleted and cannot be restored."
+msgstr ""
+
+msgid "ClusterIntegration|The associated load balancer and IP will be deleted and cannot be restored."
+msgstr ""
+
+msgid "ClusterIntegration|The associated private key will be deleted and cannot be restored."
+msgstr ""
+
+msgid "ClusterIntegration|The elastic stack collects logs from all pods in your cluster"
+msgstr ""
+
+msgid "ClusterIntegration|The endpoint is in the process of being assigned. Please check your Kubernetes cluster or Quotas on Google Kubernetes Engine if it takes a long time."
+msgstr ""
+
+msgid "ClusterIntegration|The namespace associated with your project. This will be used for deploy boards, logs, and Web terminals."
+msgstr ""
+
+msgid "ClusterIntegration|There was a problem authenticating with your cluster. Please ensure your CA Certificate and Token are valid."
+msgstr ""
+
+msgid "ClusterIntegration|This account must have permissions to create a Kubernetes cluster in the %{link_to_container_project} specified below"
+msgstr ""
+
+msgid "ClusterIntegration|This option will allow you to install applications on RBAC clusters."
+msgstr ""
+
+msgid "ClusterIntegration|To access your application after deployment, point a wildcard DNS to the Knative Endpoint."
+msgstr ""
+
+msgid "ClusterIntegration|To remove your integration and resources, type %{clusterName} to confirm:"
+msgstr ""
+
+msgid "ClusterIntegration|To remove your integration, type %{clusterName} to confirm:"
+msgstr ""
+
+msgid "ClusterIntegration|Toggle Kubernetes cluster"
+msgstr ""
+
+msgid "ClusterIntegration|Uninstall %{appTitle}"
+msgstr ""
+
+msgid "ClusterIntegration|Update %{appTitle}"
+msgstr ""
+
+msgid "ClusterIntegration|Update failed. Please check the logs and try again."
+msgstr ""
+
+msgid "ClusterIntegration|Use %{query}"
+msgstr ""
+
+msgid "ClusterIntegration|Uses the Cloud Run, Istio, and HTTP Load Balancing addons for this cluster."
+msgstr ""
+
+msgid "ClusterIntegration|VPC"
+msgstr ""
+
+msgid "ClusterIntegration|Validating project billing status"
+msgstr ""
+
+msgid "ClusterIntegration|We could not verify that one of your projects on GCP has billing enabled. Please try again."
+msgstr ""
+
+msgid "ClusterIntegration|With a Kubernetes cluster associated to this project, you can use review apps, deploy your applications, run your pipelines, and much more in an easy way."
+msgstr ""
+
+msgid "ClusterIntegration|You are about to remove your cluster integration and all GitLab-created resources associated with this cluster."
+msgstr ""
+
+msgid "ClusterIntegration|You are about to remove your cluster integration."
+msgstr ""
+
+msgid "ClusterIntegration|You are about to uninstall %{appTitle} from your cluster."
+msgstr ""
+
+msgid "ClusterIntegration|You are about to update %{appTitle} on your cluster."
+msgstr ""
+
+msgid "ClusterIntegration|You must first install Helm Tiller before installing the applications below"
+msgstr ""
+
+msgid "ClusterIntegration|You must grant access to your organization’s AWS resources in order to create a new EKS cluster. To grant access, create a provision role using the account and external ID below and provide us the ARN."
+msgstr ""
+
+msgid "ClusterIntegration|You must have an RBAC-enabled cluster to install Knative."
+msgstr ""
+
+msgid "ClusterIntegration|You must specify a domain before you can install Knative."
+msgstr ""
+
+msgid "ClusterIntegration|You should select at least two subnets"
+msgstr ""
+
+msgid "ClusterIntegration|Your Elasticsearch cluster will be re-created during this upgrade. Your logs will be re-indexed, and you will lose historical logs from hosts terminated in the last 30 days."
+msgstr ""
+
+msgid "ClusterIntegration|Your account must have %{link_to_kubernetes_engine}"
+msgstr ""
+
+msgid "ClusterIntegration|Your cluster API is unreachable. Please ensure your API URL is correct."
+msgstr ""
+
+msgid "ClusterIntegration|Your service role is distinct from the provision role used when authenticating. It will allow Amazon EKS and the Kubernetes control plane to manage AWS resources on your behalf. To use a new role, first create one on %{startLink}Amazon Web Services %{externalLinkIcon} %{endLink}."
+msgstr ""
+
+msgid "ClusterIntegration|Zone"
+msgstr ""
+
+msgid "ClusterIntegration|access to Google Kubernetes Engine"
+msgstr ""
+
+msgid "ClusterIntegration|documentation"
+msgstr ""
+
+msgid "ClusterIntegration|installed via %{linkStart}Cloud Run%{linkEnd}"
+msgstr ""
+
+msgid "ClusterIntegration|meets the requirements"
+msgstr ""
+
+msgid "ClusterIntegration|sign up"
+msgstr ""
+
+msgid "ClusterIntergation|Select a VPC"
+msgstr ""
+
+msgid "ClusterIntergation|Select a network"
+msgstr ""
+
+msgid "ClusterIntergation|Select a region"
+msgstr ""
+
+msgid "ClusterIntergation|Select a security group"
+msgstr ""
+
+msgid "ClusterIntergation|Select a subnet"
+msgstr ""
+
+msgid "ClusterIntergation|Select a subnetwork"
+msgstr ""
+
+msgid "ClusterIntergation|Select an instance type"
+msgstr ""
+
+msgid "ClusterIntergation|Select key pair"
+msgstr ""
+
+msgid "ClusterIntergation|Select service role"
+msgstr ""
+
+msgid "Clusters|An error occurred while loading clusters"
+msgstr ""
+
+msgid "Code"
+msgstr ""
+
+msgid "Code Coverage: %{coveragePercentage}%{percentSymbol}"
+msgstr ""
+
+msgid "Code Coverage| Empty code coverage data"
+msgstr ""
+
+msgid "Code Coverage|Couldn't fetch the code coverage data"
+msgstr ""
+
+msgid "Code Owners"
+msgstr ""
+
+msgid "Code Owners to the merge request changes."
+msgstr ""
+
+msgid "Code Quality"
+msgstr ""
+
+msgid "Code Review"
+msgstr ""
+
+msgid "Code Review Analytics displays a table of open merge requests considered to be in code review. There are currently no merge requests in review for this project and/or filters."
+msgstr ""
+
+msgid "Code coverage statistics for master %{start_date} - %{end_date}"
+msgstr ""
+
+msgid "Code owner approval is required"
+msgstr ""
+
+msgid "Code owners"
+msgstr ""
+
+msgid "CodeOwner|Pattern"
+msgstr ""
+
+msgid "Cohorts"
+msgstr ""
+
+msgid "Cohorts|Inactive users"
+msgstr ""
+
+msgid "Cohorts|Month %{month_index}"
+msgstr ""
+
+msgid "Cohorts|New users"
+msgstr ""
+
+msgid "Cohorts|Registration month"
+msgstr ""
+
+msgid "Cohorts|Returning users"
+msgstr ""
+
+msgid "Cohorts|User cohorts are shown for the last %{months_included} months. Only users with activity are counted in the 'New users' column; inactive users are counted separately."
+msgstr ""
+
+msgid "Collapse"
+msgstr ""
+
+msgid "Collapse approvers"
+msgstr ""
+
+msgid "Collapse milestones"
+msgstr ""
+
+msgid "Collapse replies"
+msgstr ""
+
+msgid "Collapse sidebar"
+msgstr ""
+
+msgid "Collector hostname"
+msgstr ""
+
+msgid "ComboSearch is not defined"
+msgstr ""
+
+msgid "Coming soon"
+msgstr ""
+
+msgid "Comma-separated, e.g. '1.1.1.1, 2.2.2.0/24'"
+msgstr ""
+
+msgid "Command"
+msgstr ""
+
+msgid "Command line instructions"
+msgstr ""
+
+msgid "Commands applied"
+msgstr ""
+
+msgid "Commands did not apply"
+msgstr ""
+
+msgid "Comment"
+msgstr ""
+
+msgid "Comment & close %{noteable_name}"
+msgstr ""
+
+msgid "Comment & reopen %{noteable_name}"
+msgstr ""
+
+msgid "Comment & resolve thread"
+msgstr ""
+
+msgid "Comment & unresolve thread"
+msgstr ""
+
+msgid "Comment '%{label}' position"
+msgstr ""
+
+msgid "Comment form position"
+msgstr ""
+
+msgid "Comment is being updated"
+msgstr ""
+
+msgid "Comment on lines %{startLine} to %{endLine}"
+msgstr ""
+
+msgid "Comment/Reply (quoting selected text)"
+msgstr ""
+
+msgid "Comments"
+msgstr ""
+
+msgid "Commit"
+msgid_plural "Commits"
+msgstr[0] ""
+
+msgid "Commit %{commit_id}"
+msgstr ""
+
+msgid "Commit (when editing commit message)"
+msgstr ""
+
+msgid "Commit Message"
+msgstr ""
+
+msgid "Commit deleted"
+msgstr ""
+
+msgid "Commit message"
+msgstr ""
+
+msgid "Commit message (optional)"
+msgstr ""
+
+msgid "Commit statistics for %{ref} %{start_time} - %{end_time}"
+msgstr ""
+
+msgid "Commit to %{branchName} branch"
+msgstr ""
+
+msgid "CommitBoxTitle|Commit"
+msgstr ""
+
+msgid "CommitMessage|Add %{file_name}"
+msgstr ""
+
+msgid "CommitWidget|authored"
+msgstr ""
+
+msgid "Commits"
+msgstr ""
+
+msgid "Commits feed"
+msgstr ""
+
+msgid "Commits per day hour (UTC)"
+msgstr ""
+
+msgid "Commits per day of month"
+msgstr ""
+
+msgid "Commits per weekday"
+msgstr ""
+
+msgid "Commits to"
+msgstr ""
+
+msgid "Commits|An error occurred while fetching merge requests data."
+msgstr ""
+
+msgid "Commits|History"
+msgstr ""
+
+msgid "Commits|No related merge requests found"
+msgstr ""
+
+msgid "Committed by"
+msgstr ""
+
+msgid "Commit…"
+msgstr ""
+
+msgid "Company"
+msgstr ""
+
+msgid "Company name"
+msgstr ""
+
+msgid "Compare"
+msgstr ""
+
+msgid "Compare Git revisions"
+msgstr ""
+
+msgid "Compare Revisions"
+msgstr ""
+
+msgid "Compare changes"
+msgstr ""
+
+msgid "Compare changes with the last commit"
+msgstr ""
+
+msgid "Compare changes with the merge request target branch"
+msgstr ""
+
+msgid "Compare with previous version"
+msgstr ""
+
+msgid "CompareBranches|%{source_branch} and %{target_branch} are the same."
+msgstr ""
+
+msgid "CompareBranches|Compare"
+msgstr ""
+
+msgid "CompareBranches|Source"
+msgstr ""
+
+msgid "CompareBranches|Target"
+msgstr ""
+
+msgid "CompareBranches|There isn't anything to compare."
+msgstr ""
+
+msgid "Complete"
+msgstr ""
+
+msgid "Compliance"
+msgstr ""
+
+msgid "Compliance Dashboard"
+msgstr ""
+
+msgid "Compliance framework (optional)"
+msgstr ""
+
+msgid "Compliance frameworks"
+msgstr ""
+
+msgid "ComplianceDashboard|created by:"
+msgstr ""
+
+msgid "ComplianceFramework|GDPR"
+msgstr ""
+
+msgid "ComplianceFramework|GDPR - General Data Protection Regulation"
+msgstr ""
+
+msgid "ComplianceFramework|HIPAA"
+msgstr ""
+
+msgid "ComplianceFramework|HIPAA - Health Insurance Portability and Accountability Act"
+msgstr ""
+
+msgid "ComplianceFramework|PCI-DSS"
+msgstr ""
+
+msgid "ComplianceFramework|PCI-DSS - Payment Card Industry-Data Security Standard"
+msgstr ""
+
+msgid "ComplianceFramework|SOC 2"
+msgstr ""
+
+msgid "ComplianceFramework|SOC 2 - Service Organization Control 2"
+msgstr ""
+
+msgid "ComplianceFramework|SOX"
+msgstr ""
+
+msgid "ComplianceFramework|SOX - Sarbanes-Oxley"
+msgstr ""
+
+msgid "ComplianceFramework|This project is regulated by %{framework}."
+msgstr ""
+
+msgid "Confidence"
+msgstr ""
+
+msgid "Confidential"
+msgstr ""
+
+msgid "Confidentiality"
+msgstr ""
+
+msgid "Configuration"
+msgstr ""
+
+msgid "Configure GitLab runners to start using the Web Terminal. %{helpStart}Learn more.%{helpEnd}"
+msgstr ""
+
+msgid "Configure Gitaly timeouts."
+msgstr ""
+
+msgid "Configure Let's Encrypt"
+msgstr ""
+
+msgid "Configure Prometheus"
+msgstr ""
+
+msgid "Configure Tracing"
+msgstr ""
+
+msgid "Configure a <code>.gitlab-webide.yml</code> file in the <code>.gitlab</code> directory to start using the Web Terminal. %{helpStart}Learn more.%{helpEnd}"
+msgstr ""
+
+msgid "Configure automatic git checks and housekeeping on repositories."
+msgstr ""
+
+msgid "Configure existing installation"
+msgstr ""
+
+msgid "Configure limit for issues created per minute by web and API requests."
+msgstr ""
+
+msgid "Configure limits for web and API requests."
+msgstr ""
+
+msgid "Configure limits on the number of inbound alerts able to be sent to a project."
+msgstr ""
+
+msgid "Configure paths to be protected by Rack Attack."
+msgstr ""
+
+msgid "Configure repository mirroring."
+msgstr ""
+
+msgid "Configure storage path settings."
+msgstr ""
+
+msgid "Configure the %{link} integration."
+msgstr ""
+
+msgid "Configure the way a user creates a new account."
+msgstr ""
+
+msgid "Confirm"
+msgstr ""
+
+msgid "Confirmation email sent to %{email}"
+msgstr ""
+
+msgid "Confirmation required"
+msgstr ""
+
+msgid "Congratulations! You have enabled Two-factor Authentication!"
+msgstr ""
+
+msgid "Connect"
+msgstr ""
+
+msgid "Connect all repositories"
+msgstr ""
+
+msgid "Connect repositories from GitHub"
+msgstr ""
+
+msgid "Connect your external repositories, and CI/CD pipelines will run for new commits. A GitLab project will be created with only CI/CD features enabled."
+msgstr ""
+
+msgid "Connected"
+msgstr ""
+
+msgid "Connecting"
+msgstr ""
+
+msgid "Connecting to terminal sync service"
+msgstr ""
+
+msgid "Connecting..."
+msgstr ""
+
+msgid "Connection failed"
+msgstr ""
+
+msgid "Connection failure"
+msgstr ""
+
+msgid "Connection timed out"
+msgstr ""
+
+msgid "Connection timeout"
+msgstr ""
+
+msgid "Contact sales to upgrade"
+msgstr ""
+
+msgid "Contact support"
+msgstr ""
+
+msgid "Container Registry"
+msgstr ""
+
+msgid "Container Registry tag expiration policy"
+msgstr ""
+
+msgid "Container Scanning"
+msgstr ""
+
+msgid "Container does not exist"
+msgstr ""
+
+msgid "Container registry images"
+msgstr ""
+
+msgid "Container registry is not enabled on this GitLab instance. Ask an administrator to enable it in order for Auto DevOps to work."
+msgstr ""
+
+msgid "Container repositories sync capacity"
+msgstr ""
+
+msgid "ContainerRegistry| Please visit the %{linkStart}administration settings%{linkEnd} to enable this feature."
+msgstr ""
+
+msgid "ContainerRegistry|%{count} Image repository"
+msgid_plural "ContainerRegistry|%{count} Image repositories"
+msgstr[0] ""
+
+msgid "ContainerRegistry|%{count} Tag"
+msgid_plural "ContainerRegistry|%{count} Tags"
+msgstr[0] ""
+
+msgid "ContainerRegistry|%{imageName} tags"
+msgstr ""
+
+msgid "ContainerRegistry|%{title} was successfully scheduled for deletion"
+msgstr ""
+
+msgid "ContainerRegistry|Automatically remove extra images that aren't designed to be kept."
+msgstr ""
+
+msgid "ContainerRegistry|Build an image"
+msgstr ""
+
+msgid "ContainerRegistry|CLI Commands"
+msgstr ""
+
+msgid "ContainerRegistry|Container Registry"
+msgstr ""
+
+msgid "ContainerRegistry|Container Registry tag expiration and retention policy is disabled"
+msgstr ""
+
+msgid "ContainerRegistry|Copy build command"
+msgstr ""
+
+msgid "ContainerRegistry|Copy login command"
+msgstr ""
+
+msgid "ContainerRegistry|Copy push command"
+msgstr ""
+
+msgid "ContainerRegistry|Delete selected"
+msgstr ""
+
+msgid "ContainerRegistry|Docker connection error"
+msgstr ""
+
+msgid "ContainerRegistry|Docker tag expiration policy is %{toggleStatus}"
+msgstr ""
+
+msgid "ContainerRegistry|Expiration interval:"
+msgstr ""
+
+msgid "ContainerRegistry|Expiration policies help manage the storage space used by the Container Registry, but the expiration policies for this registry are disabled. Contact your administrator to enable. %{docLinkStart}More information%{docLinkEnd}"
+msgstr ""
+
+msgid "ContainerRegistry|Expiration policy is disabled"
+msgstr ""
+
+msgid "ContainerRegistry|Expiration policy successfully saved."
+msgstr ""
+
+msgid "ContainerRegistry|Expiration policy will run in %{time}"
+msgstr ""
+
+msgid "ContainerRegistry|Expiration policy:"
+msgstr ""
+
+msgid "ContainerRegistry|Expiration schedule:"
+msgstr ""
+
+msgid "ContainerRegistry|Filter by name"
+msgstr ""
+
+msgid "ContainerRegistry|If you are not already logged in, you need to authenticate to the Container Registry by using your GitLab username and password. If you have %{twofaDocLinkStart}Two-Factor Authentication%{twofaDocLinkEnd} enabled, use a %{personalAccessTokensDocLinkStart}Personal Access Token%{personalAccessTokensDocLinkEnd} instead of a password."
+msgstr ""
+
+msgid "ContainerRegistry|Image ID: %{imageId}"
+msgstr ""
+
+msgid "ContainerRegistry|Image Repositories"
+msgstr ""
+
+msgid "ContainerRegistry|Image tags"
+msgstr ""
+
+msgid "ContainerRegistry|Keep and protect the images that matter most."
+msgstr ""
+
+msgid "ContainerRegistry|Login"
+msgstr ""
+
+msgid "ContainerRegistry|Missing or insufficient permission, delete button disabled"
+msgstr ""
+
+msgid "ContainerRegistry|Number of tags to retain:"
+msgstr ""
+
+msgid "ContainerRegistry|Please contact your administrator."
+msgstr ""
+
+msgid "ContainerRegistry|Published %{timeInfo}"
+msgstr ""
+
+msgid "ContainerRegistry|Push an image"
+msgstr ""
+
+msgid "ContainerRegistry|Regular expressions such as %{codeStart}.*-master%{codeEnd} or %{codeStart}release-.*%{codeEnd} are supported"
+msgstr ""
+
+msgid "ContainerRegistry|Regular expressions such as %{codeStart}.*-test%{codeEnd} or %{codeStart}dev-.*%{codeEnd} are supported. To select all tags, use %{codeStart}.*%{codeEnd}"
+msgstr ""
+
+msgid "ContainerRegistry|Remember to run %{docLinkStart}garbage collection%{docLinkEnd} to remove the stale data from storage."
+msgstr ""
+
+msgid "ContainerRegistry|Remove repository"
+msgstr ""
+
+msgid "ContainerRegistry|Remove tag"
+msgid_plural "ContainerRegistry|Remove tags"
+msgstr[0] ""
+
+msgid "ContainerRegistry|Something went wrong while fetching the expiration policy."
+msgstr ""
+
+msgid "ContainerRegistry|Something went wrong while fetching the repository list."
+msgstr ""
+
+msgid "ContainerRegistry|Something went wrong while fetching the tags list."
+msgstr ""
+
+msgid "ContainerRegistry|Something went wrong while marking the tag for deletion."
+msgstr ""
+
+msgid "ContainerRegistry|Something went wrong while marking the tags for deletion."
+msgstr ""
+
+msgid "ContainerRegistry|Something went wrong while scheduling %{title} for deletion. Please try again."
+msgstr ""
+
+msgid "ContainerRegistry|Something went wrong while updating the expiration policy."
+msgstr ""
+
+msgid "ContainerRegistry|Sorry, your filter produced no results."
+msgstr ""
+
+msgid "ContainerRegistry|Tag expiration policy"
+msgstr ""
+
+msgid "ContainerRegistry|Tag expiration policy is designed to:"
+msgstr ""
+
+msgid "ContainerRegistry|Tag successfully marked for deletion."
+msgstr ""
+
+msgid "ContainerRegistry|Tags successfully marked for deletion."
+msgstr ""
+
+msgid "ContainerRegistry|Tags with names matching this regex pattern will %{italicStart}be preserved:%{italicEnd}"
+msgstr ""
+
+msgid "ContainerRegistry|Tags with names matching this regex pattern will %{italicStart}expire:%{italicEnd}"
+msgstr ""
+
+msgid "ContainerRegistry|The Container Registry tag expiration and retention policies for this project have not been enabled."
+msgstr ""
+
+msgid "ContainerRegistry|The last tag related to this image was recently removed. This empty image and any associated data will be automatically removed as part of the regular Garbage Collection process. If you have any questions, contact your administrator."
+msgstr ""
+
+msgid "ContainerRegistry|The value of this input should be less than 255 characters"
+msgstr ""
+
+msgid "ContainerRegistry|There are no container images available in this group"
+msgstr ""
+
+msgid "ContainerRegistry|There are no container images stored for this project"
+msgstr ""
+
+msgid "ContainerRegistry|There was an error during the deletion of this image repository, please try again."
+msgstr ""
+
+msgid "ContainerRegistry|This image has no active tags"
+msgstr ""
+
+msgid "ContainerRegistry|This image repository is scheduled for deletion"
+msgstr ""
+
+msgid "ContainerRegistry|To widen your search, change or remove the filters above."
+msgstr ""
+
+msgid "ContainerRegistry|We are having trouble connecting to the Registry, which could be due to an issue with your project name or path. %{docLinkStart}More information%{docLinkEnd}"
+msgstr ""
+
+msgid "ContainerRegistry|With the Container Registry, every project can have its own space to store its Docker images. %{docLinkStart}More Information%{docLinkEnd}"
+msgstr ""
+
+msgid "ContainerRegistry|With the Container Registry, every project can have its own space to store its Docker images. Push at least one Docker image in one of this group's projects in order to show up here. %{docLinkStart}More Information%{docLinkEnd}"
+msgstr ""
+
+msgid "ContainerRegistry|With the GitLab Container Registry, every project can have its own space to store images. %{docLinkStart}More information%{docLinkEnd}"
+msgstr ""
+
+msgid "ContainerRegistry|You are about to remove %{item} tags. Are you sure?"
+msgstr ""
+
+msgid "ContainerRegistry|You are about to remove %{item}. Are you sure?"
+msgstr ""
+
+msgid "ContainerRegistry|You are about to remove repository %{title}. Once you confirm, this repository will be permanently deleted."
+msgstr ""
+
+msgid "ContainerRegistry|You can add an image to this registry with the following commands:"
+msgstr ""
+
+msgid "Contains %{count} blobs of images (%{size})"
+msgstr ""
+
+msgid "Contents of .gitlab-ci.yml"
+msgstr ""
+
+msgid "Continue"
+msgstr ""
+
+msgid "Continue to the next step"
+msgstr ""
+
+msgid "Continuous Integration and Deployment"
+msgstr ""
+
+msgid "Contribute to GitLab"
+msgstr ""
+
+msgid "Contribution"
+msgstr ""
+
+msgid "Contribution Analytics"
+msgstr ""
+
+msgid "ContributionAnalytics|<strong>%{created_count}</strong> created, <strong>%{closed_count}</strong> closed."
+msgstr ""
+
+msgid "ContributionAnalytics|<strong>%{created_count}</strong> created, <strong>%{merged_count}</strong> merged."
+msgstr ""
+
+msgid "ContributionAnalytics|<strong>%{pushes}</strong> pushes, more than <strong>%{commits}</strong> commits by <strong>%{people}</strong> contributors."
+msgstr ""
+
+msgid "ContributionAnalytics|Contribution analytics for issues, merge requests and push events since %{start_date}"
+msgstr ""
+
+msgid "ContributionAnalytics|Issues"
+msgstr ""
+
+msgid "ContributionAnalytics|Last 3 months"
+msgstr ""
+
+msgid "ContributionAnalytics|Last month"
+msgstr ""
+
+msgid "ContributionAnalytics|Last week"
+msgstr ""
+
+msgid "ContributionAnalytics|Merge Requests"
+msgstr ""
+
+msgid "ContributionAnalytics|No issues for the selected time period."
+msgstr ""
+
+msgid "ContributionAnalytics|No merge requests for the selected time period."
+msgstr ""
+
+msgid "ContributionAnalytics|No pushes for the selected time period."
+msgstr ""
+
+msgid "Contributions for <strong>%{calendar_date}</strong>"
+msgstr ""
+
+msgid "Contributions per group member"
+msgstr ""
+
+msgid "Contributors"
+msgstr ""
+
+msgid "Control emails linked to your account"
+msgstr ""
+
+msgid "Control the display of third party offers."
+msgstr ""
+
+msgid "Cookie domain"
+msgstr ""
+
+msgid "Copied"
+msgstr ""
+
+msgid "Copied labels and milestone from %{source_issuable_reference}."
+msgstr ""
+
+msgid "Copy"
+msgstr ""
+
+msgid "Copy %{field}"
+msgstr ""
+
+msgid "Copy %{http_label} clone URL"
+msgstr ""
+
+msgid "Copy %{protocol} clone URL"
+msgstr ""
+
+msgid "Copy %{proxy_url}"
+msgstr ""
+
+msgid "Copy %{type}"
+msgstr ""
+
+msgid "Copy Account ID to clipboard"
+msgstr ""
+
+msgid "Copy External ID to clipboard"
+msgstr ""
+
+msgid "Copy ID"
+msgstr ""
+
+msgid "Copy KRB5 clone URL"
+msgstr ""
+
+msgid "Copy SSH clone URL"
+msgstr ""
+
+msgid "Copy SSH public key"
+msgstr ""
+
+msgid "Copy URL"
+msgstr ""
+
+msgid "Copy branch name"
+msgstr ""
+
+msgid "Copy command"
+msgstr ""
+
+msgid "Copy commands"
+msgstr ""
+
+msgid "Copy commit SHA"
+msgstr ""
+
+msgid "Copy environment"
+msgstr ""
+
+msgid "Copy evidence SHA"
+msgstr ""
+
+msgid "Copy file contents"
+msgstr ""
+
+msgid "Copy file path"
+msgstr ""
+
+msgid "Copy key"
+msgstr ""
+
+msgid "Copy labels and milestone from %{source_issuable_reference}."
+msgstr ""
+
+msgid "Copy labels and milestone from other issue or merge request in this project"
+msgstr ""
+
+msgid "Copy link"
+msgstr ""
+
+msgid "Copy link to chart"
+msgstr ""
+
+msgid "Copy reference"
+msgstr ""
+
+msgid "Copy secret"
+msgstr ""
+
+msgid "Copy token"
+msgstr ""
+
+msgid "Copy trigger token"
+msgstr ""
+
+msgid "Copy value"
+msgstr ""
+
+msgid "Could not add admins as members"
+msgstr ""
+
+msgid "Could not authorize chat nickname. Try again!"
+msgstr ""
+
+msgid "Could not change HEAD: branch '%{branch}' does not exist"
+msgstr ""
+
+msgid "Could not connect to FogBugz, check your URL"
+msgstr ""
+
+msgid "Could not connect to Sentry. Refresh the page to try again."
+msgstr ""
+
+msgid "Could not connect to Web IDE file mirror service."
+msgstr ""
+
+msgid "Could not create Wiki Repository at this time. Please try again later."
+msgstr ""
+
+msgid "Could not create environment"
+msgstr ""
+
+msgid "Could not create group"
+msgstr ""
+
+msgid "Could not create project"
+msgstr ""
+
+msgid "Could not delete %{design}. Please try again."
+msgstr ""
+
+msgid "Could not delete chat nickname %{chat_name}."
+msgstr ""
+
+msgid "Could not find design."
+msgstr ""
+
+msgid "Could not find iteration"
+msgstr ""
+
+msgid "Could not remove the trigger."
+msgstr ""
+
+msgid "Could not restore the group"
+msgstr ""
+
+msgid "Could not revoke impersonation token %{token_name}."
+msgstr ""
+
+msgid "Could not revoke personal access token %{personal_access_token_name}."
+msgstr ""
+
+msgid "Could not revoke project access token %{project_access_token_name}."
+msgstr ""
+
+msgid "Could not save group ID"
+msgstr ""
+
+msgid "Could not save project ID"
+msgstr ""
+
+msgid "Could not save prometheus manual configuration"
+msgstr ""
+
+msgid "Could not update the LDAP settings"
+msgstr ""
+
+msgid "Could not upload your designs as one or more files uploaded are not supported."
+msgstr ""
+
+msgid "Country"
+msgstr ""
+
+msgid "Coverage"
+msgstr ""
+
+msgid "Coverage Fuzzing"
+msgstr ""
+
+msgid "Crash State"
+msgstr ""
+
+msgid "Create"
+msgstr ""
+
+msgid "Create %{environment}"
+msgstr ""
+
+msgid "Create %{type}"
+msgstr ""
+
+msgid "Create New Directory"
+msgstr ""
+
+msgid "Create New Domain"
+msgstr ""
+
+msgid "Create Project"
+msgstr ""
+
+msgid "Create a GitLab account first, and then connect it to your %{label} account."
+msgstr ""
+
+msgid "Create a Mattermost team for this group"
+msgstr ""
+
+msgid "Create a local proxy for storing frequently used upstream images. %{link_start}Learn more%{link_end} about dependency proxies."
+msgstr ""
+
+msgid "Create a merge request"
+msgstr ""
+
+msgid "Create a new branch"
+msgstr ""
+
+msgid "Create a new deploy key for this project"
+msgstr ""
+
+msgid "Create a new file as there are no files yet. Afterwards, you'll be able to commit your changes."
+msgstr ""
+
+msgid "Create a new issue"
+msgstr ""
+
+msgid "Create a new repository"
+msgstr ""
+
+msgid "Create a personal access token on your account to pull or push via %{protocol}."
+msgstr ""
+
+msgid "Create a project pre-populated with the necessary files to get you started quickly."
+msgstr ""
+
+msgid "Create an account using:"
+msgstr ""
+
+msgid "Create an issue. Issues are created for each alert triggered."
+msgstr ""
+
+msgid "Create and provide your GitHub %{link_start}Personal Access Token%{link_end}. You will need to select the <code>repo</code> scope, so we can display a list of your public and private repositories which are available to import."
+msgstr ""
+
+msgid "Create board"
+msgstr ""
+
+msgid "Create branch"
+msgstr ""
+
+msgid "Create commit"
+msgstr ""
+
+msgid "Create confidential merge request"
+msgstr ""
+
+msgid "Create confidential merge request and branch"
+msgstr ""
+
+msgid "Create directory"
+msgstr ""
+
+msgid "Create empty repository"
+msgstr ""
+
+msgid "Create epic"
+msgstr ""
+
+msgid "Create file"
+msgstr ""
+
+msgid "Create group"
+msgstr ""
+
+msgid "Create group label"
+msgstr ""
+
+msgid "Create issue"
+msgstr ""
+
+msgid "Create iteration"
+msgstr ""
+
+msgid "Create lists from labels. Issues with that label appear in that list."
+msgstr ""
+
+msgid "Create merge request"
+msgstr ""
+
+msgid "Create merge request and branch"
+msgstr ""
+
+msgid "Create milestone"
+msgstr ""
+
+msgid "Create new"
+msgstr ""
+
+msgid "Create new board"
+msgstr ""
+
+msgid "Create new branch"
+msgstr ""
+
+msgid "Create new directory"
+msgstr ""
+
+msgid "Create new file"
+msgstr ""
+
+msgid "Create new file or directory"
+msgstr ""
+
+msgid "Create new label"
+msgstr ""
+
+msgid "Create new..."
+msgstr ""
+
+msgid "Create project"
+msgstr ""
+
+msgid "Create project label"
+msgstr ""
+
+msgid "Create requirement"
+msgstr ""
+
+msgid "Create snippet"
+msgstr ""
+
+msgid "Create wildcard: %{searchTerm}"
+msgstr ""
+
+msgid "Create your first page"
+msgstr ""
+
+msgid "Create your group"
+msgstr ""
+
+msgid "Create/import your first project"
+msgstr ""
+
+msgid "CreateGroup|You don’t have permission to create a subgroup in this group."
+msgstr ""
+
+msgid "CreateGroup|You don’t have permission to create groups."
+msgstr ""
+
+msgid "CreateTag|Tag"
+msgstr ""
+
+msgid "CreateTokenToCloneLink|create a personal access token"
+msgstr ""
+
+msgid "Created"
+msgstr ""
+
+msgid "Created %{timestamp}"
+msgstr ""
+
+msgid "Created At"
+msgstr ""
+
+msgid "Created On"
+msgstr ""
+
+msgid "Created a branch and a merge request to resolve this issue."
+msgstr ""
+
+msgid "Created branch '%{branch_name}' and a merge request to resolve this issue."
+msgstr ""
+
+msgid "Created by me"
+msgstr ""
+
+msgid "Created date"
+msgstr ""
+
+msgid "Created issue %{issueLink}"
+msgstr ""
+
+msgid "Created issue %{issueLink} at %{projectLink}"
+msgstr ""
+
+msgid "Created merge request %{mergeRequestLink}"
+msgstr ""
+
+msgid "Created merge request %{mergeRequestLink} at %{projectLink}"
+msgstr ""
+
+msgid "Created on"
+msgstr ""
+
+msgid "Created on:"
+msgstr ""
+
+msgid "Creates a branch and a merge request to resolve this issue."
+msgstr ""
+
+msgid "Creates branch '%{branch_name}' and a merge request to resolve this issue."
+msgstr ""
+
+msgid "Creating"
+msgstr ""
+
+msgid "Creating epic"
+msgstr ""
+
+msgid "Creating graphs uses the data from the Prometheus server. If this takes a long time, ensure that data is available."
+msgstr ""
+
+msgid "Creation date"
+msgstr ""
+
+msgid "Credentials"
+msgstr ""
+
+msgid "CredentialsInventory|No credentials found"
+msgstr ""
+
+msgid "CredentialsInventory|Personal Access Tokens"
+msgstr ""
+
+msgid "CredentialsInventory|SSH Keys"
+msgstr ""
+
+msgid "Critical vulnerabilities present"
+msgstr ""
+
+msgid "Cron Timezone"
+msgstr ""
+
+msgid "Crossplane"
+msgstr ""
+
+msgid "Current Branch"
+msgstr ""
+
+msgid "Current Plan"
+msgstr ""
+
+msgid "Current Project"
+msgstr ""
+
+msgid "Current node"
+msgstr ""
+
+msgid "Current node must be the primary node or you will be locking yourself out"
+msgstr ""
+
+msgid "Current password"
+msgstr ""
+
+msgid "Current vulnerabilities count"
+msgstr ""
+
+msgid "CurrentUser|Buy Pipeline minutes"
+msgstr ""
+
+msgid "CurrentUser|One of your groups is running out"
+msgstr ""
+
+msgid "CurrentUser|Profile"
+msgstr ""
+
+msgid "CurrentUser|Settings"
+msgstr ""
+
+msgid "CurrentUser|Start a Gold trial"
+msgstr ""
+
+msgid "CurrentUser|Upgrade"
+msgstr ""
+
+msgid "Custom Attributes"
+msgstr ""
+
+msgid "Custom CI configuration path"
+msgstr ""
+
+msgid "Custom Git clone URL for HTTP(S)"
+msgstr ""
+
+msgid "Custom hostname (for private commit emails)"
+msgstr ""
+
+msgid "Custom notification events"
+msgstr ""
+
+msgid "Custom notification levels are the same as participating levels. With custom notification levels you will also receive notifications for select events. To find out more, check out %{notification_link}."
+msgstr ""
+
+msgid "Custom project templates"
+msgstr ""
+
+msgid "Custom project templates have not been set up for groups that you are a member of. They are enabled from a group’s settings page. Contact your group’s Owner or Maintainer to setup custom project templates."
+msgstr ""
+
+msgid "Custom range"
+msgstr ""
+
+msgid "Custom range (UTC)"
+msgstr ""
+
+msgid "CustomCycleAnalytics|Add a stage"
+msgstr ""
+
+msgid "CustomCycleAnalytics|Add stage"
+msgstr ""
+
+msgid "CustomCycleAnalytics|Editing stage"
+msgstr ""
+
+msgid "CustomCycleAnalytics|Enter a name for the stage"
+msgstr ""
+
+msgid "CustomCycleAnalytics|Name"
+msgstr ""
+
+msgid "CustomCycleAnalytics|New stage"
+msgstr ""
+
+msgid "CustomCycleAnalytics|Please select a start event first"
+msgstr ""
+
+msgid "CustomCycleAnalytics|Select start event"
+msgstr ""
+
+msgid "CustomCycleAnalytics|Select stop event"
+msgstr ""
+
+msgid "CustomCycleAnalytics|Stage name already exists"
+msgstr ""
+
+msgid "CustomCycleAnalytics|Start event"
+msgstr ""
+
+msgid "CustomCycleAnalytics|Start event changed, please select a valid stop event"
+msgstr ""
+
+msgid "CustomCycleAnalytics|Start event label"
+msgstr ""
+
+msgid "CustomCycleAnalytics|Stop event"
+msgstr ""
+
+msgid "CustomCycleAnalytics|Stop event label"
+msgstr ""
+
+msgid "CustomCycleAnalytics|Update stage"
+msgstr ""
+
+msgid "Customer Portal"
+msgstr ""
+
+msgid "Customize colors"
+msgstr ""
+
+msgid "Customize how FogBugz email addresses and usernames are imported into GitLab. In the next step, you'll be able to select the projects you want to import."
+msgstr ""
+
+msgid "Customize how Google Code email addresses and usernames are imported into GitLab. In the next step, you'll be able to select the projects you want to import."
+msgstr ""
+
+msgid "Customize icon"
+msgstr ""
+
+msgid "Customize language and region related settings."
+msgstr ""
+
+msgid "Customize name"
+msgstr ""
+
+msgid "Customize your pipeline configuration, view your pipeline status and coverage report."
+msgstr ""
+
+msgid "Customize your pipeline configuration."
+msgstr ""
+
+msgid "Cycle Time"
+msgstr ""
+
+msgid "CycleAnalyticsEvent|Issue closed"
+msgstr ""
+
+msgid "CycleAnalyticsEvent|Issue created"
+msgstr ""
+
+msgid "CycleAnalyticsEvent|Issue first added to a board"
+msgstr ""
+
+msgid "CycleAnalyticsEvent|Issue first associated with a milestone"
+msgstr ""
+
+msgid "CycleAnalyticsEvent|Issue first associated with a milestone or issue first added to a board"
+msgstr ""
+
+msgid "CycleAnalyticsEvent|Issue first mentioned in a commit"
+msgstr ""
+
+msgid "CycleAnalyticsEvent|Issue label was added"
+msgstr ""
+
+msgid "CycleAnalyticsEvent|Issue label was removed"
+msgstr ""
+
+msgid "CycleAnalyticsEvent|Issue last edited"
+msgstr ""
+
+msgid "CycleAnalyticsEvent|Merge request closed"
+msgstr ""
+
+msgid "CycleAnalyticsEvent|Merge request created"
+msgstr ""
+
+msgid "CycleAnalyticsEvent|Merge request first deployed to production"
+msgstr ""
+
+msgid "CycleAnalyticsEvent|Merge request label was added"
+msgstr ""
+
+msgid "CycleAnalyticsEvent|Merge request label was removed"
+msgstr ""
+
+msgid "CycleAnalyticsEvent|Merge request last build finish time"
+msgstr ""
+
+msgid "CycleAnalyticsEvent|Merge request last build start time"
+msgstr ""
+
+msgid "CycleAnalyticsEvent|Merge request last edited"
+msgstr ""
+
+msgid "CycleAnalyticsEvent|Merge request merged"
+msgstr ""
+
+msgid "CycleAnalyticsStage|Code"
+msgstr ""
+
+msgid "CycleAnalyticsStage|Issue"
+msgstr ""
+
+msgid "CycleAnalyticsStage|Plan"
+msgstr ""
+
+msgid "CycleAnalyticsStage|Review"
+msgstr ""
+
+msgid "CycleAnalyticsStage|Staging"
+msgstr ""
+
+msgid "CycleAnalyticsStage|Test"
+msgstr ""
+
+msgid "CycleAnalyticsStage|Total"
+msgstr ""
+
+msgid "CycleAnalyticsStage|is not available for the selected group"
+msgstr ""
+
+msgid "CycleAnalyticsStage|should be under a group"
+msgstr ""
+
+msgid "CycleAnalytics|%{selectedLabelsCount} selected (%{maxLabels} max)"
+msgstr ""
+
+msgid "CycleAnalytics|%{stageCount} stages selected"
+msgstr ""
+
+msgid "CycleAnalytics|All stages"
+msgstr ""
+
+msgid "CycleAnalytics|Date"
+msgstr ""
+
+msgid "CycleAnalytics|Days to completion"
+msgstr ""
+
+msgid "CycleAnalytics|Display chart filters"
+msgstr ""
+
+msgid "CycleAnalytics|No stages selected"
+msgstr ""
+
+msgid "CycleAnalytics|Number of tasks"
+msgstr ""
+
+msgid "CycleAnalytics|Only %{maxLabels} labels can be selected at this time"
+msgstr ""
+
+msgid "CycleAnalytics|Project selected"
+msgid_plural "CycleAnalytics|%d projects selected"
+msgstr[0] ""
+
+msgid "CycleAnalytics|Select labels"
+msgstr ""
+
+msgid "CycleAnalytics|Show"
+msgstr ""
+
+msgid "CycleAnalytics|Showing %{subjectFilterText} and %{selectedLabelsCount} labels"
+msgstr ""
+
+msgid "CycleAnalytics|Showing data for group '%{groupName}' and %{selectedProjectCount} projects from %{startDate} to %{endDate}"
+msgstr ""
+
+msgid "CycleAnalytics|Showing data for group '%{groupName}' from %{startDate} to %{endDate}"
+msgstr ""
+
+msgid "CycleAnalytics|Stages"
+msgstr ""
+
+msgid "CycleAnalytics|Tasks by type"
+msgstr ""
+
+msgid "CycleAnalytics|The given date range is larger than 180 days"
+msgstr ""
+
+msgid "CycleAnalytics|Total days to completion"
+msgstr ""
+
+msgid "CycleAnalytics|Type of work"
+msgstr ""
+
+msgid "CycleAnalytics|group dropdown filter"
+msgstr ""
+
+msgid "CycleAnalytics|not allowed for the given start event"
+msgstr ""
+
+msgid "CycleAnalytics|project dropdown filter"
+msgstr ""
+
+msgid "CycleAnalytics|stage dropdown"
+msgstr ""
+
+msgid "DAG"
+msgstr ""
+
+msgid "DAG visualization requires at least 3 dependent jobs."
+msgstr ""
+
+msgid "DNS"
+msgstr ""
+
+msgid "Dashboard"
+msgstr ""
+
+msgid "Dashboard uid not found"
+msgstr ""
+
+msgid "DashboardProjects|All"
+msgstr ""
+
+msgid "DashboardProjects|Personal"
+msgstr ""
+
+msgid "DashboardProjects|Trending"
+msgstr ""
+
+msgid "Dashboards"
+msgstr ""
+
+msgid "Dashboard|%{firstProject} and %{secondProject}"
+msgstr ""
+
+msgid "Dashboard|%{firstProject}, %{rest}, and %{secondProject}"
+msgstr ""
+
+msgid "Dashboard|Unable to add %{invalidProjects}. This dashboard is available for public projects, and private projects in groups with a Silver plan."
+msgstr ""
+
+msgid "Data is still calculating..."
+msgstr ""
+
+msgid "Datasource name not found"
+msgstr ""
+
+msgid "Date"
+msgstr ""
+
+msgid "Date picker"
+msgstr ""
+
+msgid "Date range cannot exceed %{maxDateRange} days."
+msgstr ""
+
+msgid "Day of month"
+msgstr ""
+
+msgid "DayTitle|F"
+msgstr ""
+
+msgid "DayTitle|M"
+msgstr ""
+
+msgid "DayTitle|S"
+msgstr ""
+
+msgid "DayTitle|W"
+msgstr ""
+
+msgid "Days"
+msgstr ""
+
+msgid "Days to merge"
+msgstr ""
+
+msgid "Debug"
+msgstr ""
+
+msgid "Dec"
+msgstr ""
+
+msgid "December"
+msgstr ""
+
+msgid "Decline"
+msgstr ""
+
+msgid "Decline and sign out"
+msgstr ""
+
+msgid "Default Branch"
+msgstr ""
+
+msgid "Default CI configuration path"
+msgstr ""
+
+msgid "Default artifacts expiration"
+msgstr ""
+
+msgid "Default branch"
+msgstr ""
+
+msgid "Default branch and protected branches"
+msgstr ""
+
+msgid "Default classification label"
+msgstr ""
+
+msgid "Default dashboard"
+msgstr ""
+
+msgid "Default deletion adjourned period"
+msgstr ""
+
+msgid "Default description template for issues"
+msgstr ""
+
+msgid "Default description template for merge requests"
+msgstr ""
+
+msgid "Default first day of the week"
+msgstr ""
+
+msgid "Default first day of the week in calendars and date pickers."
+msgstr ""
+
+msgid "Default issue template"
+msgstr ""
+
+msgid "Default project deletion protection"
+msgstr ""
+
+msgid "Default projects limit"
+msgstr ""
+
+msgid "Default stages"
+msgstr ""
+
+msgid "Default: Directly import the Google Code email address or username"
+msgstr ""
+
+msgid "Default: Map a FogBugz account ID to a full name"
+msgstr ""
+
+msgid "Define a custom pattern with cron syntax"
+msgstr ""
+
+msgid "Define custom rules for what constitutes spam, independent of Akismet"
+msgstr ""
+
+msgid "Define environments in the deploy stage(s) in <code>.gitlab-ci.yml</code> to track deployments here."
+msgstr ""
+
+msgid "DelayedJobs|Are you sure you want to run %{jobName} immediately? Otherwise this job will run automatically after it's timer finishes."
+msgstr ""
+
+msgid "DelayedJobs|Are you sure you want to run %{job_name} immediately? This job will run automatically after it's timer finishes."
+msgstr ""
+
+msgid "DelayedJobs|Start now"
+msgstr ""
+
+msgid "DelayedJobs|Unschedule"
+msgstr ""
+
+msgid "DelayedJobs|delayed"
+msgstr ""
+
+msgid "Delete"
+msgstr ""
+
+msgid "Delete Comment"
+msgstr ""
+
+msgid "Delete Package"
+msgstr ""
+
+msgid "Delete Snippet"
+msgstr ""
+
+msgid "Delete artifacts"
+msgstr ""
+
+msgid "Delete board"
+msgstr ""
+
+msgid "Delete comment"
+msgstr ""
+
+msgid "Delete domain"
+msgstr ""
+
+msgid "Delete label"
+msgstr ""
+
+msgid "Delete label: %{label_name} ?"
+msgstr ""
+
+msgid "Delete license"
+msgstr ""
+
+msgid "Delete list"
+msgstr ""
+
+msgid "Delete pipeline"
+msgstr ""
+
+msgid "Delete project"
+msgstr ""
+
+msgid "Delete serverless domain?"
+msgstr ""
+
+msgid "Delete snippet"
+msgstr ""
+
+msgid "Delete snippet?"
+msgstr ""
+
+msgid "Delete source branch"
+msgstr ""
+
+msgid "Delete this attachment"
+msgstr ""
+
+msgid "Delete user list"
+msgstr ""
+
+msgid "Delete variable"
+msgstr ""
+
+msgid "DeleteProject|Failed to remove project repository. Please try again or contact administrator."
+msgstr ""
+
+msgid "DeleteProject|Failed to remove project snippets. Please try again or contact administrator."
+msgstr ""
+
+msgid "DeleteProject|Failed to remove some tags in project container registry. Please try again or contact administrator."
+msgstr ""
+
+msgid "DeleteProject|Failed to remove wiki repository. Please try again or contact administrator."
+msgstr ""
+
+msgid "DeleteProject|Failed to restore project repository. Please contact the administrator."
+msgstr ""
+
+msgid "DeleteProject|Failed to restore wiki repository. Please contact the administrator."
+msgstr ""
+
+msgid "Deleted"
+msgstr ""
+
+msgid "Deleted chat nickname: %{chat_name}!"
+msgstr ""
+
+msgid "Deleted in this version"
+msgstr ""
+
+msgid "Deleting"
+msgstr ""
+
+msgid "Deleting the license failed."
+msgstr ""
+
+msgid "Deleting the license failed. The license was not found."
+msgstr ""
+
+msgid "Deleting the license failed. You are not permitted to perform this action."
+msgstr ""
+
+msgid "Deletion pending. This project will be removed on %{date}. Repository and other project resources are read-only."
+msgstr ""
+
+msgid "Denied authorization of chat nickname %{user_name}."
+msgstr ""
+
+msgid "Deny"
+msgstr ""
+
+msgid "Dependencies"
+msgstr ""
+
+msgid "Dependencies help page link"
+msgstr ""
+
+msgid "Dependencies|%d additional vulnerability not shown"
+msgid_plural "Dependencies|%d additional vulnerabilities not shown"
+msgstr[0] ""
+
+msgid "Dependencies|%d vulnerability detected"
+msgid_plural "Dependencies|%d vulnerabilities detected"
+msgstr[0] ""
+
+msgid "Dependencies|%{remainingLicensesCount} more"
+msgstr ""
+
+msgid "Dependencies|All"
+msgstr ""
+
+msgid "Dependencies|Based on the %{linkStart}latest successful%{linkEnd} scan"
+msgstr ""
+
+msgid "Dependencies|Component"
+msgstr ""
+
+msgid "Dependencies|Component name"
+msgstr ""
+
+msgid "Dependencies|Export as JSON"
+msgstr ""
+
+msgid "Dependencies|Job failed to generate the dependency list"
+msgstr ""
+
+msgid "Dependencies|License"
+msgstr ""
+
+msgid "Dependencies|Location"
+msgstr ""
+
+msgid "Dependencies|Packager"
+msgstr ""
+
+msgid "Dependencies|The %{codeStartTag}dependency_scanning%{codeEndTag} job has failed and cannot generate the list. Please ensure the job is running properly and run the pipeline again."
+msgstr ""
+
+msgid "Dependencies|Toggle vulnerability list"
+msgstr ""
+
+msgid "Dependencies|Unsupported file(s) detected"
+msgstr ""
+
+msgid "Dependencies|Vulnerable components"
+msgstr ""
+
+msgid "Dependency List"
+msgstr ""
+
+msgid "Dependency List has no entries"
+msgstr ""
+
+msgid "Dependency Proxy"
+msgstr ""
+
+msgid "Dependency Scanning"
+msgstr ""
+
+msgid "Dependency proxy"
+msgstr ""
+
+msgid "Dependency proxy URL"
+msgstr ""
+
+msgid "Dependency proxy feature is limited to public groups for now."
+msgstr ""
+
+msgid "DependencyProxy|Toggle Dependency Proxy"
+msgstr ""
+
+msgid "Depends on %d merge request being merged"
+msgid_plural "Depends on %d merge requests being merged"
+msgstr[0] ""
+
+msgid "Depends on <strong>%d closed</strong> merge request."
+msgid_plural "Depends on <strong>%d closed</strong> merge requests."
+msgstr[0] ""
+
+msgid "Deploy"
+msgid_plural "Deploys"
+msgstr[0] ""
+
+msgid "Deploy Keys"
+msgstr ""
+
+msgid "Deploy key was successfully updated."
+msgstr ""
+
+msgid "Deploy keys allow read-only or read-write (if enabled) access to your repository. Deploy keys can be used for CI, staging or production servers. You can create a deploy key or add an existing one."
+msgstr ""
+
+msgid "Deploy progress not found. To see pods, ensure your environment matches %{linkStart}deploy board criteria%{linkEnd}."
+msgstr ""
+
+msgid "Deploy to..."
+msgstr ""
+
+msgid "DeployBoard|Matching on the %{appLabel} label has been removed for deploy boards. To see all instances on your board, you must update your chart and redeploy."
+msgstr ""
+
+msgid "DeployKeys|+%{count} others"
+msgstr ""
+
+msgid "DeployKeys|Current project"
+msgstr ""
+
+msgid "DeployKeys|Deploy key"
+msgstr ""
+
+msgid "DeployKeys|Enabled deploy keys"
+msgstr ""
+
+msgid "DeployKeys|Error enabling deploy key"
+msgstr ""
+
+msgid "DeployKeys|Error getting deploy keys"
+msgstr ""
+
+msgid "DeployKeys|Error removing deploy key"
+msgstr ""
+
+msgid "DeployKeys|Expand %{count} other projects"
+msgstr ""
+
+msgid "DeployKeys|Loading deploy keys"
+msgstr ""
+
+msgid "DeployKeys|No deploy keys found. Create one with the form above."
+msgstr ""
+
+msgid "DeployKeys|Privately accessible deploy keys"
+msgstr ""
+
+msgid "DeployKeys|Project usage"
+msgstr ""
+
+msgid "DeployKeys|Publicly accessible deploy keys"
+msgstr ""
+
+msgid "DeployKeys|Read access only"
+msgstr ""
+
+msgid "DeployKeys|Write access allowed"
+msgstr ""
+
+msgid "DeployKeys|You are going to remove this deploy key. Are you sure?"
+msgstr ""
+
+msgid "DeployTokens|Active Deploy Tokens (%{active_tokens})"
+msgstr ""
+
+msgid "DeployTokens|Add a deploy token"
+msgstr ""
+
+msgid "DeployTokens|Allows read access to the package registry"
+msgstr ""
+
+msgid "DeployTokens|Allows read-only access to the registry images"
+msgstr ""
+
+msgid "DeployTokens|Allows read-only access to the repository"
+msgstr ""
+
+msgid "DeployTokens|Allows write access to the package registry"
+msgstr ""
+
+msgid "DeployTokens|Allows write access to the registry images"
+msgstr ""
+
+msgid "DeployTokens|Copy deploy token"
+msgstr ""
+
+msgid "DeployTokens|Copy username"
+msgstr ""
+
+msgid "DeployTokens|Create deploy token"
+msgstr ""
+
+msgid "DeployTokens|Created"
+msgstr ""
+
+msgid "DeployTokens|Default format is \"gitlab+deploy-token-{n}\". Enter custom username if you want to change it."
+msgstr ""
+
+msgid "DeployTokens|Deploy Tokens"
+msgstr ""
+
+msgid "DeployTokens|Deploy tokens allow access to packages, your repository, and registry images."
+msgstr ""
+
+msgid "DeployTokens|Expires"
+msgstr ""
+
+msgid "DeployTokens|Group deploy tokens allow access to the packages, repositories, and registry images within the group."
+msgstr ""
+
+msgid "DeployTokens|Name"
+msgstr ""
+
+msgid "DeployTokens|Pick a name for the application, and we'll give you a unique deploy token."
+msgstr ""
+
+msgid "DeployTokens|Revoke"
+msgstr ""
+
+msgid "DeployTokens|Revoke %{b_start}%{name}%{b_end}?"
+msgstr ""
+
+msgid "DeployTokens|Revoke %{name}"
+msgstr ""
+
+msgid "DeployTokens|Scopes"
+msgstr ""
+
+msgid "DeployTokens|This %{entity_type} has no active Deploy Tokens."
+msgstr ""
+
+msgid "DeployTokens|This action cannot be undone."
+msgstr ""
+
+msgid "DeployTokens|Use this token as a password. Make sure you save it - you won't be able to access it again."
+msgstr ""
+
+msgid "DeployTokens|Use this username as a login."
+msgstr ""
+
+msgid "DeployTokens|Username"
+msgstr ""
+
+msgid "DeployTokens|You are about to revoke %{b_start}%{name}%{b_end}."
+msgstr ""
+
+msgid "DeployTokens|Your New Deploy Token"
+msgstr ""
+
+msgid "DeployTokens|Your new group deploy token has been created."
+msgstr ""
+
+msgid "DeployTokens|Your new project deploy token has been created."
+msgstr ""
+
+msgid "Deployed"
+msgstr ""
+
+msgid "Deployed to"
+msgstr ""
+
+msgid "Deploying to"
+msgstr ""
+
+msgid "Deploying to AWS is easy with GitLab"
+msgstr ""
+
+msgid "Deployment Frequency"
+msgstr ""
+
+msgid "Deployment|API"
+msgstr ""
+
+msgid "Deployment|This deployment was created using the API"
+msgstr ""
+
+msgid "Deployment|canceled"
+msgstr ""
+
+msgid "Deployment|created"
+msgstr ""
+
+msgid "Deployment|failed"
+msgstr ""
+
+msgid "Deployment|running"
+msgstr ""
+
+msgid "Deployment|success"
+msgstr ""
+
+msgid "Deprioritize label"
+msgstr ""
+
+msgid "Descending"
+msgstr ""
+
+msgid "Describe the goal of the changes and what reviewers should be aware of."
+msgstr ""
+
+msgid "Describe the requirement here"
+msgstr ""
+
+msgid "Description"
+msgstr ""
+
+msgid "Description parsed with %{link_start}GitLab Flavored Markdown%{link_end}"
+msgstr ""
+
+msgid "Description templates allow you to define context-specific templates for issue and merge request description fields for your project."
+msgstr ""
+
+msgid "Description:"
+msgstr ""
+
+msgid "Descriptive label"
+msgstr ""
+
+msgid "Deselect all"
+msgstr ""
+
+msgid "Design Management files and data"
+msgstr ""
+
+msgid "DesignManagement|%{current_design} of %{designs_count}"
+msgstr ""
+
+msgid "DesignManagement|%{filename} did not change."
+msgstr ""
+
+msgid "DesignManagement|Adding a design with the same filename replaces the file in a new version."
+msgstr ""
+
+msgid "DesignManagement|Are you sure you want to cancel changes to this comment?"
+msgstr ""
+
+msgid "DesignManagement|Are you sure you want to cancel creating this comment?"
+msgstr ""
+
+msgid "DesignManagement|Are you sure you want to delete the selected designs?"
+msgstr ""
+
+msgid "DesignManagement|Cancel changes"
+msgstr ""
+
+msgid "DesignManagement|Cancel comment confirmation"
+msgstr ""
+
+msgid "DesignManagement|Cancel comment update confirmation"
+msgstr ""
+
+msgid "DesignManagement|Click the image where you'd like to start a new discussion"
+msgstr ""
+
+msgid "DesignManagement|Comment"
+msgstr ""
+
+msgid "DesignManagement|Comments you resolve can be viewed and unresolved by going to the \"Resolved Comments\" section below"
+msgstr ""
+
+msgid "DesignManagement|Could not add a new comment. Please try again."
+msgstr ""
+
+msgid "DesignManagement|Could not create new discussion. Please try again."
+msgstr ""
+
+msgid "DesignManagement|Could not update discussion. Please try again."
+msgstr ""
+
+msgid "DesignManagement|Could not update note. Please try again."
+msgstr ""
+
+msgid "DesignManagement|Delete"
+msgstr ""
+
+msgid "DesignManagement|Delete designs confirmation"
+msgstr ""
+
+msgid "DesignManagement|Delete selected"
+msgstr ""
+
+msgid "DesignManagement|Deselect all"
+msgstr ""
+
+msgid "DesignManagement|Discard comment"
+msgstr ""
+
+msgid "DesignManagement|Error uploading a new design. Please try again."
+msgstr ""
+
+msgid "DesignManagement|Go back to designs"
+msgstr ""
+
+msgid "DesignManagement|Go to next design"
+msgstr ""
+
+msgid "DesignManagement|Go to previous design"
+msgstr ""
+
+msgid "DesignManagement|Keep changes"
+msgstr ""
+
+msgid "DesignManagement|Keep comment"
+msgstr ""
+
+msgid "DesignManagement|Learn more about resolving comments"
+msgstr ""
+
+msgid "DesignManagement|Requested design version does not exist. Showing latest version instead"
+msgstr ""
+
+msgid "DesignManagement|Resolve thread"
+msgstr ""
+
+msgid "DesignManagement|Resolved Comments"
+msgstr ""
+
+msgid "DesignManagement|Save comment"
+msgstr ""
+
+msgid "DesignManagement|Select all"
+msgstr ""
+
+msgid "DesignManagement|The maximum number of designs allowed to be uploaded is %{upload_limit}. Please try again."
+msgstr ""
+
+msgid "DesignManagement|To enable design management, you'll need to %{requirements_link_start}meet the requirements%{requirements_link_end}. If you need help, reach out to our %{support_link_start}support team%{support_link_end} for assistance."
+msgstr ""
+
+msgid "DesignManagement|Unresolve thread"
+msgstr ""
+
+msgid "DesignManagement|Upload designs"
+msgstr ""
+
+msgid "DesignManagement|Upload skipped."
+msgstr ""
+
+msgid "DesignManagement|and %{moreCount} more."
+msgstr ""
+
+msgid "Designs"
+msgstr ""
+
+msgid "Destroy"
+msgstr ""
+
+msgid "Detail"
+msgstr ""
+
+msgid "Details"
+msgstr ""
+
+msgid "Details (default)"
+msgstr ""
+
+msgid "Detect host keys"
+msgstr ""
+
+msgid "DevOps Score"
+msgstr ""
+
+msgid "Diff content limits"
+msgstr ""
+
+msgid "Diff limits"
+msgstr ""
+
+msgid "Difference between start date and now"
+msgstr ""
+
+msgid "DiffsCompareBaseBranch|(HEAD)"
+msgstr ""
+
+msgid "DiffsCompareBaseBranch|(base)"
+msgstr ""
+
+msgid "Diffs|No file name available"
+msgstr ""
+
+msgid "Diffs|Show unchanged lines"
+msgstr ""
+
+msgid "Diffs|Something went wrong while fetching diff lines."
+msgstr ""
+
+msgid "Direction"
+msgstr ""
+
+msgid "Directory name"
+msgstr ""
+
+msgid "Disable"
+msgstr ""
+
+msgid "Disable for this project"
+msgstr ""
+
+msgid "Disable group Runners"
+msgstr ""
+
+msgid "Disable public access to Pages sites"
+msgstr ""
+
+msgid "Disable shared Runners"
+msgstr ""
+
+msgid "Disable two-factor authentication"
+msgstr ""
+
+msgid "Disabled"
+msgstr ""
+
+msgid "Disabled mirrors can only be enabled by instance owners. It is recommended that you delete them."
+msgstr ""
+
+msgid "Discard all changes"
+msgstr ""
+
+msgid "Discard all changes?"
+msgstr ""
+
+msgid "Discard changes"
+msgstr ""
+
+msgid "Discard changes to %{path}?"
+msgstr ""
+
+msgid "Discard draft"
+msgstr ""
+
+msgid "Discard review"
+msgstr ""
+
+msgid "DiscordService|Discord Notifications"
+msgstr ""
+
+msgid "DiscordService|Receive event notifications in Discord"
+msgstr ""
+
+msgid "Discover GitLab Geo"
+msgstr ""
+
+msgid "Discover projects, groups and snippets. Share your projects with others"
+msgstr ""
+
+msgid "Discover|Check your application for security vulnerabilities that may lead to unauthorized access, data leaks, and denial of services."
+msgstr ""
+
+msgid "Discover|For code that's already live in production, our dashboards give you an easy way to prioritize any issues that are found, empowering your team to ship quickly and securely."
+msgstr ""
+
+msgid "Discover|GitLab will perform static and dynamic tests on the code of your application, looking for known flaws and report them in the merge request so you can fix them before merging."
+msgstr ""
+
+msgid "Discover|Give feedback for this page"
+msgstr ""
+
+msgid "Discover|Security capabilities, integrated into your development lifecycle"
+msgstr ""
+
+msgid "Discover|See the other features of the %{linkStart}gold plan%{linkEnd}"
+msgstr ""
+
+msgid "Discover|Start a free trial"
+msgstr ""
+
+msgid "Discover|Upgrade now"
+msgstr ""
+
+msgid "Discuss a specific suggestion or question"
+msgstr ""
+
+msgid "Discuss a specific suggestion or question that needs to be resolved"
+msgstr ""
+
+msgid "Discuss a specific suggestion or question that needs to be resolved."
+msgstr ""
+
+msgid "Discuss a specific suggestion or question."
+msgstr ""
+
+msgid "Discussion"
+msgstr ""
+
+msgid "Discussion to reply to cannot be found"
+msgstr ""
+
+msgid "Disk Usage"
+msgstr ""
+
+msgid "Dismiss"
+msgstr ""
+
+msgid "Dismiss %d selected vulnerability as"
+msgid_plural "Dismiss %d selected vulnerabilities as"
+msgstr[0] ""
+
+msgid "Dismiss DevOps Score introduction"
+msgstr ""
+
+msgid "Dismiss Merge Request promotion"
+msgstr ""
+
+msgid "Dismiss Selected"
+msgstr ""
+
+msgid "Dismiss Value Stream Analytics introduction box"
+msgstr ""
+
+msgid "Dismiss trial promotion"
+msgstr ""
+
+msgid "Dismissable"
+msgstr ""
+
+msgid "Dismissed"
+msgstr ""
+
+msgid "Dismissed at %{projectLink}"
+msgstr ""
+
+msgid "Dismissed on pipeline %{pipelineLink}"
+msgstr ""
+
+msgid "Dismissed on pipeline %{pipelineLink} at %{projectLink}"
+msgstr ""
+
+msgid "Display alerts from all your monitoring tools directly within GitLab."
+msgstr ""
+
+msgid "Display name"
+msgstr ""
+
+msgid "Display rendered file"
+msgstr ""
+
+msgid "Display source"
+msgstr ""
+
+msgid "Do not display offers from third parties within GitLab"
+msgstr ""
+
+msgid "Do you want to customize how Google Code email addresses and usernames are imported into GitLab?"
+msgstr ""
+
+msgid "Dockerfile"
+msgstr ""
+
+msgid "Documentation"
+msgstr ""
+
+msgid "Documentation for popular identity providers"
+msgstr ""
+
+msgid "Doing"
+msgstr ""
+
+msgid "Domain"
+msgstr ""
+
+msgid "Domain cannot be deleted while associated to one or more clusters."
+msgstr ""
+
+msgid "Domain verification is an essential security measure for public GitLab sites. Users are required to demonstrate they control a domain before it is enabled"
+msgstr ""
+
+msgid "Domain was successfully created."
+msgstr ""
+
+msgid "Domain was successfully deleted."
+msgstr ""
+
+msgid "Domain was successfully updated."
+msgstr ""
+
+msgid "Don't have an account yet?"
+msgstr ""
+
+msgid "Don't include description in commit message"
+msgstr ""
+
+msgid "Don't paste the private part of the GPG key. Paste the public part which begins with '-----BEGIN PGP PUBLIC KEY BLOCK-----'."
+msgstr ""
+
+msgid "Don't show again"
+msgstr ""
+
+msgid "Done"
+msgstr ""
+
+msgid "Download"
+msgstr ""
+
+msgid "Download %{format}"
+msgstr ""
+
+msgid "Download %{format}:"
+msgstr ""
+
+msgid "Download CSV"
+msgstr ""
+
+msgid "Download artifacts"
+msgstr ""
+
+msgid "Download as"
+msgstr ""
+
+msgid "Download asset"
+msgstr ""
+
+msgid "Download codes"
+msgstr ""
+
+msgid "Download evidence JSON"
+msgstr ""
+
+msgid "Download export"
+msgstr ""
+
+msgid "Download image"
+msgstr ""
+
+msgid "Download license"
+msgstr ""
+
+msgid "Download raw data (.csv)"
+msgstr ""
+
+msgid "Download source code"
+msgstr ""
+
+msgid "Download this directory"
+msgstr ""
+
+msgid "DownloadCommit|Email Patches"
+msgstr ""
+
+msgid "DownloadCommit|Plain Diff"
+msgstr ""
+
+msgid "DownloadSource|Download"
+msgstr ""
+
+msgid "Downstream"
+msgstr ""
+
+msgid "Downvotes"
+msgstr ""
+
+msgid "Drop your designs to start your upload."
+msgstr ""
+
+msgid "Due Date"
+msgstr ""
+
+msgid "Due date"
+msgstr ""
+
+msgid "Duration"
+msgstr ""
+
+msgid "Duration for the last 30 commits"
+msgstr ""
+
+msgid "During this process, you’ll be asked for URLs from GitLab’s side. Use the URLs shown below."
+msgstr ""
+
+msgid "Dynamic Application Security Testing (DAST)"
+msgstr ""
+
+msgid "Each Runner can be in one of the following states and/or belong to one of the following types:"
+msgstr ""
+
+msgid "Each Runner can be in one of the following states:"
+msgstr ""
+
+msgid "Edit"
+msgstr ""
+
+msgid "Edit %{issuable}"
+msgstr ""
+
+msgid "Edit %{name}"
+msgstr ""
+
+msgid "Edit Comment"
+msgstr ""
+
+msgid "Edit Deploy Key"
+msgstr ""
+
+msgid "Edit Geo Node"
+msgstr ""
+
+msgid "Edit Group Hook"
+msgstr ""
+
+msgid "Edit Label"
+msgstr ""
+
+msgid "Edit Milestone"
+msgstr ""
+
+msgid "Edit Password"
+msgstr ""
+
+msgid "Edit Pipeline Schedule %{id}"
+msgstr ""
+
+msgid "Edit Release"
+msgstr ""
+
+msgid "Edit Slack integration"
+msgstr ""
+
+msgid "Edit Snippet"
+msgstr ""
+
+msgid "Edit System Hook"
+msgstr ""
+
+msgid "Edit application"
+msgstr ""
+
+msgid "Edit board"
+msgstr ""
+
+msgid "Edit comment"
+msgstr ""
+
+msgid "Edit dashboard"
+msgstr ""
+
+msgid "Edit description"
+msgstr ""
+
+msgid "Edit environment"
+msgstr ""
+
+msgid "Edit file"
+msgstr ""
+
+msgid "Edit files in the editor and commit changes here"
+msgstr ""
+
+msgid "Edit group: %{group_name}"
+msgstr ""
+
+msgid "Edit identity for %{user_name}"
+msgstr ""
+
+msgid "Edit issues"
+msgstr ""
+
+msgid "Edit iteration"
+msgstr ""
+
+msgid "Edit public deploy key"
+msgstr ""
+
+msgid "Edit stage"
+msgstr ""
+
+msgid "Edit this release"
+msgstr ""
+
+msgid "Edit wiki page"
+msgstr ""
+
+msgid "Edit your most recent comment in a thread (from an empty textarea)"
+msgstr ""
+
+msgid "Edited %{timeago}"
+msgstr ""
+
+msgid "Editing"
+msgstr ""
+
+msgid "Elasticsearch"
+msgstr ""
+
+msgid "Elasticsearch AWS IAM credentials"
+msgstr ""
+
+msgid "Elasticsearch indexing restrictions"
+msgstr ""
+
+msgid "Elasticsearch indexing started"
+msgstr ""
+
+msgid "Elasticsearch integration. Elasticsearch AWS IAM."
+msgstr ""
+
+msgid "Elasticsearch returned status code: %{status_code}"
+msgstr ""
+
+msgid "Elastic|None. Select namespaces to index."
+msgstr ""
+
+msgid "Elastic|None. Select projects to index."
+msgstr ""
+
+msgid "Email"
+msgstr ""
+
+msgid "Email Notification"
+msgstr ""
+
+msgid "Email address"
+msgstr ""
+
+msgid "Email could not be sent"
+msgstr ""
+
+msgid "Email display name"
+msgstr ""
+
+msgid "Email not verified. Please verify your email in Salesforce."
+msgstr ""
+
+msgid "Email notification for unknown sign-ins"
+msgstr ""
+
+msgid "Email patch"
+msgstr ""
+
+msgid "Email restrictions"
+msgstr ""
+
+msgid "Email restrictions for sign-ups"
+msgstr ""
+
+msgid "Email sent"
+msgstr ""
+
+msgid "Email the pipelines status to a list of recipients."
+msgstr ""
+
+msgid "EmailError|It appears that the email is blank. Make sure your reply is at the top of the email, we can't process inline replies."
+msgstr ""
+
+msgid "EmailError|The thread you are replying to no longer exists, perhaps it was deleted? If you believe this is in error, contact a staff member."
+msgstr ""
+
+msgid "EmailError|We couldn't figure out what the email is for. Please create your issue or comment through the web interface."
+msgstr ""
+
+msgid "EmailError|We couldn't figure out what the email is in reply to. Please create your comment through the web interface."
+msgstr ""
+
+msgid "EmailError|We couldn't figure out what user corresponds to the email. Please create your comment through the web interface."
+msgstr ""
+
+msgid "EmailError|We couldn't find the project. Please check if there's any typo."
+msgstr ""
+
+msgid "EmailError|You are not allowed to perform this action. If you believe this is in error, contact a staff member."
+msgstr ""
+
+msgid "EmailError|Your account has been blocked. If you believe this is in error, contact a staff member."
+msgstr ""
+
+msgid "EmailToken|reset it"
+msgstr ""
+
+msgid "EmailToken|resetting..."
+msgstr ""
+
+msgid "Emails"
+msgstr ""
+
+msgid "Emails sent from Service Desk will have this name"
+msgstr ""
+
+msgid "Emails separated by comma"
+msgstr ""
+
+msgid "EmailsOnPushService|Disable code diffs"
+msgstr ""
+
+msgid "EmailsOnPushService|Don't include possibly sensitive code diffs in notification body."
+msgstr ""
+
+msgid "EmailsOnPushService|Email the commits and diff of each push to a list of recipients."
+msgstr ""
+
+msgid "EmailsOnPushService|Emails on push"
+msgstr ""
+
+msgid "EmailsOnPushService|Emails separated by whitespace"
+msgstr ""
+
+msgid "EmailsOnPushService|Send from committer"
+msgstr ""
+
+msgid "EmailsOnPushService|Send notifications from the committer's email address if the domain is part of the domain GitLab is running on (e.g. %{domains})."
+msgstr ""
+
+msgid "Embed"
+msgstr ""
+
+msgid "Empty file"
+msgstr ""
+
+msgid "Enable"
+msgstr ""
+
+msgid "Enable Auto DevOps"
+msgstr ""
+
+msgid "Enable HTML emails"
+msgstr ""
+
+msgid "Enable Incident Management inbound alert limit"
+msgstr ""
+
+msgid "Enable PlantUML"
+msgstr ""
+
+msgid "Enable Pseudonymizer data collection"
+msgstr ""
+
+msgid "Enable SAML authentication for this group"
+msgstr ""
+
+msgid "Enable Seat Link"
+msgstr ""
+
+msgid "Enable Spam Check via external API endpoint"
+msgstr ""
+
+msgid "Enable access to Grafana"
+msgstr ""
+
+msgid "Enable access to the Performance Bar for a given group."
+msgstr ""
+
+msgid "Enable and configure Grafana."
+msgstr ""
+
+msgid "Enable and configure Prometheus metrics."
+msgstr ""
+
+msgid "Enable classification control using an external service"
+msgstr ""
+
+msgid "Enable container expiration and retention policies for projects created earlier than GitLab 12.7."
+msgstr ""
+
+msgid "Enable email restrictions for sign ups"
+msgstr ""
+
+msgid "Enable error tracking"
+msgstr ""
+
+msgid "Enable feature to choose access level"
+msgstr ""
+
+msgid "Enable for this project"
+msgstr ""
+
+msgid "Enable group Runners"
+msgstr ""
+
+msgid "Enable header and footer in emails"
+msgstr ""
+
+msgid "Enable integration"
+msgstr ""
+
+msgid "Enable maintenance mode"
+msgstr ""
+
+msgid "Enable mirror configuration"
+msgstr ""
+
+msgid "Enable or disable Seat Link."
+msgstr ""
+
+msgid "Enable or disable keyboard shortcuts"
+msgstr ""
+
+msgid "Enable or disable the Pseudonymizer data collection."
+msgstr ""
+
+msgid "Enable or disable version check and usage ping."
+msgstr ""
+
+msgid "Enable protected paths rate limit"
+msgstr ""
+
+msgid "Enable proxy"
+msgstr ""
+
+msgid "Enable reCAPTCHA or Akismet and set IP limits. For reCAPTCHA, we currently only support %{recaptcha_v2_link_start}v2%{recaptcha_v2_link_end}"
+msgstr ""
+
+msgid "Enable shared Runners"
+msgstr ""
+
+msgid "Enable snowplow tracking"
+msgstr ""
+
+msgid "Enable two-factor authentication"
+msgstr ""
+
+msgid "Enable usage ping"
+msgstr ""
+
+msgid "Enable usage ping to get an overview of how you are using GitLab from a feature perspective."
+msgstr ""
+
+msgid "Enable/disable your service desk. %{link_start}Learn more about service desk%{link_end}."
+msgstr ""
+
+msgid "EnableReviewApp|%{stepStart}Step 1%{stepEnd}. Ensure you have Kubernetes set up and have a base domain for your %{linkStart}cluster%{linkEnd}."
+msgstr ""
+
+msgid "EnableReviewApp|%{stepStart}Step 2%{stepEnd}. Copy the following snippet:"
+msgstr ""
+
+msgid "EnableReviewApp|%{stepStart}Step 3%{stepEnd}. Add it to the project %{linkStart}gitlab-ci.yml%{linkEnd} file."
+msgstr ""
+
+msgid "EnableReviewApp|Close"
+msgstr ""
+
+msgid "EnableReviewApp|Copy snippet text"
+msgstr ""
+
+msgid "Enabled"
+msgstr ""
+
+msgid "Enabled Git access protocols"
+msgstr ""
+
+msgid "Enabled sources for code import during project creation. OmniAuth must be configured for GitHub"
+msgstr ""
+
+msgid "Enabling this will only make licensed EE features available to projects if the project namespace's plan includes the feature or if the project is public."
+msgstr ""
+
+msgid "Encountered an error while rendering: %{err}"
+msgstr ""
+
+msgid "End date"
+msgstr ""
+
+msgid "Ends at (UTC)"
+msgstr ""
+
+msgid "Enforce DNS rebinding attack protection"
+msgstr ""
+
+msgid "Enforce personal access token expiration"
+msgstr ""
+
+msgid "Ensure connectivity is available from the GitLab server to the Prometheus server"
+msgstr ""
+
+msgid "Ensure your %{linkStart}environment is part of the deploy stage%{linkEnd} of your CI pipeline to track deployments to your cluster."
+msgstr ""
+
+msgid "Enter 2FA for Admin Mode"
+msgstr ""
+
+msgid "Enter Admin Mode"
+msgstr ""
+
+msgid "Enter IP address range"
+msgstr ""
+
+msgid "Enter a number"
+msgstr ""
+
+msgid "Enter a whole number between 0 and 100"
+msgstr ""
+
+msgid "Enter at least three characters to search"
+msgstr ""
+
+msgid "Enter board name"
+msgstr ""
+
+msgid "Enter domain"
+msgstr ""
+
+msgid "Enter in your Bitbucket Server URL and personal access token below"
+msgstr ""
+
+msgid "Enter in your Phabricator Server URL and personal access token below"
+msgstr ""
+
+msgid "Enter merge request URLs"
+msgstr ""
+
+msgid "Enter new %{field_title}"
+msgstr ""
+
+msgid "Enter new AWS Secret Access Key"
+msgstr ""
+
+msgid "Enter number of issues"
+msgstr ""
+
+msgid "Enter one or more user ID separated by commas"
+msgstr ""
+
+msgid "Enter the code from the two-factor app on your mobile device. If you've lost your device, you may enter one of your recovery codes."
+msgstr ""
+
+msgid "Enter the issue description"
+msgstr ""
+
+msgid "Enter the issue title"
+msgstr ""
+
+msgid "Enter the merge request description"
+msgstr ""
+
+msgid "Enter the merge request title"
+msgstr ""
+
+msgid "Enter the name of your application, and we'll return a unique %{type}."
+msgstr ""
+
+msgid "Enter weights for storages for new repositories."
+msgstr ""
+
+msgid "Enter your password to approve"
+msgstr ""
+
+msgid "Environment"
+msgstr ""
+
+msgid "Environment does not have deployments"
+msgstr ""
+
+msgid "Environment is required for Stages::MetricEndpointInserter"
+msgstr ""
+
+msgid "Environment is required for Stages::VariableEndpointInserter"
+msgstr ""
+
+msgid "Environment scope"
+msgstr ""
+
+msgid "Environment variables are applied to all project environments in this instance via the Runner. You can use environment variables for passwords, secret keys, etc. Make variables available to the running application by prepending the variable key with <code>K8S_SECRET_</code>. You can set variables to be:"
+msgstr ""
+
+msgid "Environment variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. Additionally, they can be masked so they are hidden in job logs, though they must match certain regexp requirements to do so. You can use environment variables for passwords, secret keys, or whatever you want."
+msgstr ""
+
+msgid "Environment variables are configured by your administrator to be %{link_start}protected%{link_end} by default"
+msgstr ""
+
+msgid "Environment variables on this GitLab instance are configured to be %{link_start}protected%{link_end} by default"
+msgstr ""
+
+msgid "Environment:"
+msgstr ""
+
+msgid "EnvironmentDashboard|API"
+msgstr ""
+
+msgid "EnvironmentDashboard|Created through the Deployment API"
+msgstr ""
+
+msgid "EnvironmentDashboard|You are looking at the last updated environment"
+msgstr ""
+
+msgid "Environments"
+msgstr ""
+
+msgid "Environments Dashboard"
+msgstr ""
+
+msgid "Environments allow you to track deployments of your application %{link_to_read_more}."
+msgstr ""
+
+msgid "Environments in %{name}"
+msgstr ""
+
+msgid "EnvironmentsDashboard|Add a project to the dashboard"
+msgstr ""
+
+msgid "EnvironmentsDashboard|Add projects"
+msgstr ""
+
+msgid "EnvironmentsDashboard|Environments Dashboard"
+msgstr ""
+
+msgid "EnvironmentsDashboard|Job: %{job}"
+msgstr ""
+
+msgid "EnvironmentsDashboard|More actions"
+msgstr ""
+
+msgid "EnvironmentsDashboard|More information"
+msgstr ""
+
+msgid "EnvironmentsDashboard|Remove"
+msgstr ""
+
+msgid "EnvironmentsDashboard|The environments dashboard provides a summary of each project's environments' status, including pipeline and alert statuses."
+msgstr ""
+
+msgid "EnvironmentsDashboard|This dashboard displays a maximum of 7 projects and 3 environments per project. %{readMoreLink}"
+msgstr ""
+
+msgid "Environments|An error occurred while canceling the auto stop, please try again"
+msgstr ""
+
+msgid "Environments|An error occurred while deleting the environment. Check if the environment stopped; if not, stop it and try again."
+msgstr ""
+
+msgid "Environments|An error occurred while fetching the environments."
+msgstr ""
+
+msgid "Environments|An error occurred while making the request."
+msgstr ""
+
+msgid "Environments|An error occurred while re-deploying the environment, please try again"
+msgstr ""
+
+msgid "Environments|An error occurred while rolling back the environment, please try again"
+msgstr ""
+
+msgid "Environments|An error occurred while stopping the environment, please try again"
+msgstr ""
+
+msgid "Environments|Are you sure you want to stop this environment?"
+msgstr ""
+
+msgid "Environments|Auto stop in"
+msgstr ""
+
+msgid "Environments|Auto stops %{auto_stop_time}"
+msgstr ""
+
+msgid "Environments|Commit"
+msgstr ""
+
+msgid "Environments|Currently showing %{fetched} results."
+msgstr ""
+
+msgid "Environments|Currently showing all results."
+msgstr ""
+
+msgid "Environments|Delete"
+msgstr ""
+
+msgid "Environments|Delete environment"
+msgstr ""
+
+msgid "Environments|Deleting the '%{environmentName}' environment cannot be undone. Do you want to delete it anyway?"
+msgstr ""
+
+msgid "Environments|Deploy to..."
+msgstr ""
+
+msgid "Environments|Deployment"
+msgstr ""
+
+msgid "Environments|Enable review app"
+msgstr ""
+
+msgid "Environments|Environment"
+msgstr ""
+
+msgid "Environments|Environments"
+msgstr ""
+
+msgid "Environments|Environments are places where code gets deployed, such as staging or production."
+msgstr ""
+
+msgid "Environments|Install Elastic Stack on your cluster to enable advanced querying capabilities such as full text search."
+msgstr ""
+
+msgid "Environments|Job"
+msgstr ""
+
+msgid "Environments|Learn about environments"
+msgstr ""
+
+msgid "Environments|Learn more about stopping environments"
+msgstr ""
+
+msgid "Environments|Logs from %{start} to %{end}."
+msgstr ""
+
+msgid "Environments|New environment"
+msgstr ""
+
+msgid "Environments|No deployed environments"
+msgstr ""
+
+msgid "Environments|No deployments yet"
+msgstr ""
+
+msgid "Environments|No pod selected"
+msgstr ""
+
+msgid "Environments|No pods to display"
+msgstr ""
+
+msgid "Environments|Note that this action will stop the environment, but it will %{emphasisStart}not%{emphasisEnd} have an effect on any existing deployment due to no “stop environment action†being defined in the %{ciConfigLinkStart}.gitlab-ci.yml%{ciConfigLinkEnd} file."
+msgstr ""
+
+msgid "Environments|Note that this action will stop the environment, but it will %{emphasis_start}not%{emphasis_end} have an effect on any existing deployment due to no “stop environment action†being defined in the %{ci_config_link_start}.gitlab-ci.yml%{ci_config_link_end} file."
+msgstr ""
+
+msgid "Environments|Open live environment"
+msgstr ""
+
+msgid "Environments|Pod name"
+msgstr ""
+
+msgid "Environments|Re-deploy"
+msgstr ""
+
+msgid "Environments|Re-deploy environment %{environment_name}?"
+msgstr ""
+
+msgid "Environments|Re-deploy environment %{name}?"
+msgstr ""
+
+msgid "Environments|Re-deploy to environment"
+msgstr ""
+
+msgid "Environments|Read more about environments"
+msgstr ""
+
+msgid "Environments|Rollback"
+msgstr ""
+
+msgid "Environments|Rollback environment"
+msgstr ""
+
+msgid "Environments|Rollback environment %{environment_name}?"
+msgstr ""
+
+msgid "Environments|Rollback environment %{name}?"
+msgstr ""
+
+msgid "Environments|Select environment"
+msgstr ""
+
+msgid "Environments|Select pod"
+msgstr ""
+
+msgid "Environments|Show all"
+msgstr ""
+
+msgid "Environments|Stop"
+msgstr ""
+
+msgid "Environments|Stop environment"
+msgstr ""
+
+msgid "Environments|Stopping"
+msgstr ""
+
+msgid "Environments|There was an error fetching the logs. Please try again."
+msgstr ""
+
+msgid "Environments|This action will relaunch the job for commit %{commit_id}, putting the environment in a previous version. Are you sure you want to continue?"
+msgstr ""
+
+msgid "Environments|This action will relaunch the job for commit %{linkStart}%{commitId}%{linkEnd}, putting the environment in a previous version. Are you sure you want to continue?"
+msgstr ""
+
+msgid "Environments|This action will run the job defined by %{environment_name} for commit %{commit_id}, putting the environment in a previous version. You can revert it by re-deploying the latest version of your application. Are you sure you want to continue?"
+msgstr ""
+
+msgid "Environments|This action will run the job defined by %{name} for commit %{linkStart}%{commitId}%{linkEnd} putting the environment in a previous version. You can revert it by re-deploying the latest version of your application. Are you sure you want to continue?"
+msgstr ""
+
+msgid "Environments|Updated"
+msgstr ""
+
+msgid "Environments|You don't have any environments right now"
+msgstr ""
+
+msgid "Environments|protected"
+msgstr ""
+
+msgid "Epic"
+msgstr ""
+
+msgid "Epic cannot be found."
+msgstr ""
+
+msgid "Epic events"
+msgstr ""
+
+msgid "Epics"
+msgstr ""
+
+msgid "Epics Roadmap"
+msgstr ""
+
+msgid "Epics and Issues"
+msgstr ""
+
+msgid "Epics let you manage your portfolio of projects more efficiently and with less effort"
+msgstr ""
+
+msgid "Epics, Issues, and Merge Requests"
+msgstr ""
+
+msgid "Epics|Add a new epic"
+msgstr ""
+
+msgid "Epics|Add an existing epic"
+msgstr ""
+
+msgid "Epics|An error occurred while saving the %{epicDateType} date"
+msgstr ""
+
+msgid "Epics|An error occurred while updating labels."
+msgstr ""
+
+msgid "Epics|Are you sure you want to remove %{bStart}%{targetIssueTitle}%{bEnd} from %{bStart}%{parentEpicTitle}%{bEnd}?"
+msgstr ""
+
+msgid "Epics|How can I solve this?"
+msgstr ""
+
+msgid "Epics|More information"
+msgstr ""
+
+msgid "Epics|Remove epic"
+msgstr ""
+
+msgid "Epics|Remove issue"
+msgstr ""
+
+msgid "Epics|Show more"
+msgstr ""
+
+msgid "Epics|Something went wrong while assigning issue to epic."
+msgstr ""
+
+msgid "Epics|Something went wrong while creating child epics."
+msgstr ""
+
+msgid "Epics|Something went wrong while creating issue."
+msgstr ""
+
+msgid "Epics|Something went wrong while fetching child epics."
+msgstr ""
+
+msgid "Epics|Something went wrong while fetching group epics."
+msgstr ""
+
+msgid "Epics|Something went wrong while moving item."
+msgstr ""
+
+msgid "Epics|Something went wrong while ordering item."
+msgstr ""
+
+msgid "Epics|Something went wrong while removing issue from epic."
+msgstr ""
+
+msgid "Epics|These dates affect how your epics appear in the roadmap. Dates from milestones come from the milestones assigned to issues in the epic. You can also set fixed dates or remove them entirely."
+msgstr ""
+
+msgid "Epics|This will also remove any descendents of %{bStart}%{targetEpicTitle}%{bEnd} from %{bStart}%{parentEpicTitle}%{bEnd}. Are you sure?"
+msgstr ""
+
+msgid "Epics|To schedule your epic's %{epicDateType} date based on milestones, assign a milestone with a %{epicDateType} date to any issue in the epic."
+msgstr ""
+
+msgid "Epics|due"
+msgstr ""
+
+msgid "Epics|start"
+msgstr ""
+
+msgid "Error"
+msgstr ""
+
+msgid "Error Details"
+msgstr ""
+
+msgid "Error Tracking"
+msgstr ""
+
+msgid "Error creating epic"
+msgstr ""
+
+msgid "Error creating label."
+msgstr ""
+
+msgid "Error creating new iteration"
+msgstr ""
+
+msgid "Error creating repository for snippet with id %{snippet_id}"
+msgstr ""
+
+msgid "Error creating the snippet"
+msgstr ""
+
+msgid "Error deleting %{issuableType}"
+msgstr ""
+
+msgid "Error deleting project. Check logs for error details."
+msgstr ""
+
+msgid "Error fetching diverging counts for branches. Please try again."
+msgstr ""
+
+msgid "Error fetching forked projects. Please try again."
+msgstr ""
+
+msgid "Error fetching labels."
+msgstr ""
+
+msgid "Error fetching network graph."
+msgstr ""
+
+msgid "Error fetching payload data."
+msgstr ""
+
+msgid "Error fetching projects"
+msgstr ""
+
+msgid "Error fetching refs"
+msgstr ""
+
+msgid "Error fetching the dependency list. Please check your network connection and try again."
+msgstr ""
+
+msgid "Error loading branch data. Please try again."
+msgstr ""
+
+msgid "Error loading branches."
+msgstr ""
+
+msgid "Error loading burndown chart data"
+msgstr ""
+
+msgid "Error loading countries data."
+msgstr ""
+
+msgid "Error loading file viewer."
+msgstr ""
+
+msgid "Error loading last commit."
+msgstr ""
+
+msgid "Error loading markdown preview"
+msgstr ""
+
+msgid "Error loading merge requests."
+msgstr ""
+
+msgid "Error loading milestone tab"
+msgstr ""
+
+msgid "Error loading project data. Please try again."
+msgstr ""
+
+msgid "Error loading template types."
+msgstr ""
+
+msgid "Error loading template."
+msgstr ""
+
+msgid "Error loading viewer"
+msgstr ""
+
+msgid "Error occurred when fetching sidebar data"
+msgstr ""
+
+msgid "Error occurred when saving assignees"
+msgstr ""
+
+msgid "Error occurred when toggling the notification subscription"
+msgstr ""
+
+msgid "Error occurred while updating the issue status"
+msgstr ""
+
+msgid "Error occurred while updating the issue weight"
+msgstr ""
+
+msgid "Error occurred. A blocked user cannot be deactivated"
+msgstr ""
+
+msgid "Error occurred. A blocked user must be unblocked to be activated"
+msgstr ""
+
+msgid "Error occurred. User was not blocked"
+msgstr ""
+
+msgid "Error occurred. User was not confirmed"
+msgstr ""
+
+msgid "Error occurred. User was not unblocked"
+msgstr ""
+
+msgid "Error occurred. User was not unlocked"
+msgstr ""
+
+msgid "Error rendering markdown preview"
+msgstr ""
+
+msgid "Error saving label update."
+msgstr ""
+
+msgid "Error setting up editor. Please try again."
+msgstr ""
+
+msgid "Error updating %{issuableType}"
+msgstr ""
+
+msgid "Error updating status for all to-do items."
+msgstr ""
+
+msgid "Error updating status of to-do item."
+msgstr ""
+
+msgid "Error updating the snippet"
+msgstr ""
+
+msgid "Error uploading file"
+msgstr ""
+
+msgid "Error uploading file: %{stripped}"
+msgstr ""
+
+msgid "Error while loading the merge request. Please try again."
+msgstr ""
+
+msgid "Error while loading the project data. Please try again."
+msgstr ""
+
+msgid "Error while migrating %{upload_id}: %{error_message}"
+msgstr ""
+
+msgid "Error with Akismet. Please check the logs for more info."
+msgstr ""
+
+msgid "ErrorTracking|Active"
+msgstr ""
+
+msgid "ErrorTracking|After adding your Auth Token, use the 'Connect' button to load projects"
+msgstr ""
+
+msgid "ErrorTracking|Auth Token"
+msgstr ""
+
+msgid "ErrorTracking|Click 'Connect' to re-establish the connection to Sentry and activate the dropdown."
+msgstr ""
+
+msgid "ErrorTracking|Connection has failed. Re-check Auth Token and try again."
+msgstr ""
+
+msgid "ErrorTracking|If you self-host Sentry, enter the full URL of your Sentry instance. If you're using Sentry's hosted solution, enter https://sentry.io"
+msgstr ""
+
+msgid "ErrorTracking|No projects available"
+msgstr ""
+
+msgid "ErrorTracking|Select project"
+msgstr ""
+
+msgid "ErrorTracking|To enable project selection, enter a valid Auth Token"
+msgstr ""
+
+msgid "Errors"
+msgstr ""
+
+msgid "Errors:"
+msgstr ""
+
+msgid "Estimate"
+msgstr ""
+
+msgid "Estimated"
+msgstr ""
+
+msgid "EventFilterBy|Filter by all"
+msgstr ""
+
+msgid "EventFilterBy|Filter by comments"
+msgstr ""
+
+msgid "EventFilterBy|Filter by designs"
+msgstr ""
+
+msgid "EventFilterBy|Filter by epic events"
+msgstr ""
+
+msgid "EventFilterBy|Filter by issue events"
+msgstr ""
+
+msgid "EventFilterBy|Filter by merge events"
+msgstr ""
+
+msgid "EventFilterBy|Filter by push events"
+msgstr ""
+
+msgid "EventFilterBy|Filter by team"
+msgstr ""
+
+msgid "EventFilterBy|Filter by wiki"
+msgstr ""
+
+msgid "Events"
+msgstr ""
+
+msgid "Events in %{group_name}"
+msgstr ""
+
+msgid "Events in %{project_path}"
+msgstr ""
+
+msgid "Every %{action} attempt has failed: %{job_error_message}. Please try again."
+msgstr ""
+
+msgid "Every day"
+msgstr ""
+
+msgid "Every day (at %{time})"
+msgstr ""
+
+msgid "Every month"
+msgstr ""
+
+msgid "Every month (Day %{day} at %{time})"
+msgstr ""
+
+msgid "Every three months"
+msgstr ""
+
+msgid "Every two weeks"
+msgstr ""
+
+msgid "Every week"
+msgstr ""
+
+msgid "Every week (%{weekday} at %{time})"
+msgstr ""
+
+msgid "Everyone"
+msgstr ""
+
+msgid "Everyone With Access"
+msgstr ""
+
+msgid "Everyone can contribute"
+msgstr ""
+
+msgid "Everything on your to-do list is marked as done."
+msgstr ""
+
+msgid "Everything you need to create a GitLab Pages site using Gatsby."
+msgstr ""
+
+msgid "Everything you need to create a GitLab Pages site using GitBook."
+msgstr ""
+
+msgid "Everything you need to create a GitLab Pages site using Hexo."
+msgstr ""
+
+msgid "Everything you need to create a GitLab Pages site using Hugo."
+msgstr ""
+
+msgid "Everything you need to create a GitLab Pages site using Jekyll."
+msgstr ""
+
+msgid "Everything you need to create a GitLab Pages site using plain HTML."
+msgstr ""
+
+msgid "Evidence collection"
+msgstr ""
+
+msgid "Exactly one of %{attributes} is required"
+msgstr ""
+
+msgid "Example: <code>acme.com,acme.co.in,acme.uk</code>."
+msgstr ""
+
+msgid "Example: @sub\\.company\\.com$"
+msgstr ""
+
+msgid "Example: Usage = single query. (Requested) / (Capacity) = multiple queries combined into a formula."
+msgstr ""
+
+msgid "Except policy:"
+msgstr ""
+
+msgid "Excluding merge commits. Limited to %{limit} commits."
+msgstr ""
+
+msgid "Excluding merge commits. Limited to 6,000 commits."
+msgstr ""
+
+msgid "Existing members and groups"
+msgstr ""
+
+msgid "Existing projects may be moved into a group"
+msgstr ""
+
+msgid "Existing projects will be able to use expiration policies. Avoid enabling this if an external Container Registry is being used, as there is a performance risk if many images exist on one project."
+msgstr ""
+
+msgid "Existing shares"
+msgstr ""
+
+msgid "Existing sign in methods may be removed"
+msgstr ""
+
+msgid "Expand"
+msgstr ""
+
+msgid "Expand all"
+msgstr ""
+
+msgid "Expand approvers"
+msgstr ""
+
+msgid "Expand down"
+msgstr ""
+
+msgid "Expand dropdown"
+msgstr ""
+
+msgid "Expand milestones"
+msgstr ""
+
+msgid "Expand sidebar"
+msgstr ""
+
+msgid "Expand up"
+msgstr ""
+
+msgid "Experienced"
+msgstr ""
+
+msgid "Expiration"
+msgstr ""
+
+msgid "Expiration date"
+msgstr ""
+
+msgid "Expiration not enforced"
+msgstr ""
+
+msgid "Expiration policy for the Container Registry is a perfect solution for keeping the Registry space down while still enjoying the full power of GitLab CI/CD."
+msgstr ""
+
+msgid "Expired"
+msgstr ""
+
+msgid "Expired %{expiredOn}"
+msgstr ""
+
+msgid "Expired:"
+msgstr ""
+
+msgid "Expires"
+msgstr ""
+
+msgid "Expires at"
+msgstr ""
+
+msgid "Expires in %{expires_at}"
+msgstr ""
+
+msgid "Expires on"
+msgstr ""
+
+msgid "Expires:"
+msgstr ""
+
+msgid "Explain the problem. If appropriate, provide a link to the relevant issue or comment."
+msgstr ""
+
+msgid "Explore"
+msgstr ""
+
+msgid "Explore GitLab"
+msgstr ""
+
+msgid "Explore Groups"
+msgstr ""
+
+msgid "Explore groups"
+msgstr ""
+
+msgid "Explore projects"
+msgstr ""
+
+msgid "Explore public groups"
+msgstr ""
+
+msgid "Export as CSV"
+msgstr ""
+
+msgid "Export group"
+msgstr ""
+
+msgid "Export issues"
+msgstr ""
+
+msgid "Export project"
+msgstr ""
+
+msgid "Export this group with all related data to a new GitLab instance. Once complete, you can import the data file from the \"New Group\" page."
+msgstr ""
+
+msgid "Export this project with all its related data in order to move your project to a new GitLab instance. Once the export is finished, you can import the file from the \"New Project\" page."
+msgstr ""
+
+msgid "Export variable to pipelines running on protected branches and tags only."
+msgstr ""
+
+msgid "External Classification Policy Authorization"
+msgstr ""
+
+msgid "External ID"
+msgstr ""
+
+msgid "External URL"
+msgstr ""
+
+msgid "External Wiki"
+msgstr ""
+
+msgid "External authentication"
+msgstr ""
+
+msgid "External authorization denied access to this project"
+msgstr ""
+
+msgid "External authorization request timeout"
+msgstr ""
+
+msgid "External storage URL"
+msgstr ""
+
+msgid "External storage authentication token"
+msgstr ""
+
+msgid "ExternalAuthorizationService|Classification label"
+msgstr ""
+
+msgid "ExternalAuthorizationService|When no classification label is set the default label `%{default_label}` will be used."
+msgstr ""
+
+msgid "ExternalWikiService|External Wiki"
+msgstr ""
+
+msgid "ExternalWikiService|Replaces the link to the internal wiki with a link to an external wiki."
+msgstr ""
+
+msgid "ExternalWikiService|The URL of the external Wiki"
+msgstr ""
+
+msgid "Facebook"
+msgstr ""
+
+msgid "Failed"
+msgstr ""
+
+msgid "Failed Jobs"
+msgstr ""
+
+msgid "Failed on"
+msgstr ""
+
+msgid "Failed to add a Zoom meeting"
+msgstr ""
+
+msgid "Failed to apply commands."
+msgstr ""
+
+msgid "Failed to assign a user because no user was found."
+msgstr ""
+
+msgid "Failed to cancel auto stop because failed to update the environment."
+msgstr ""
+
+msgid "Failed to cancel auto stop because the environment is not set as auto stop."
+msgstr ""
+
+msgid "Failed to cancel auto stop because you do not have permission to update the environment."
+msgstr ""
+
+msgid "Failed to change the owner"
+msgstr ""
+
+msgid "Failed to check related branches."
+msgstr ""
+
+msgid "Failed to create Merge Request. Please try again."
+msgstr ""
+
+msgid "Failed to create a branch for this issue. Please try again."
+msgstr ""
+
+msgid "Failed to create import label for jira import."
+msgstr ""
+
+msgid "Failed to create repository"
+msgstr ""
+
+msgid "Failed to create resources"
+msgstr ""
+
+msgid "Failed to create wiki"
+msgstr ""
+
+msgid "Failed to delete board. Please try again."
+msgstr ""
+
+msgid "Failed to deploy to"
+msgstr ""
+
+msgid "Failed to enqueue the rebase operation, possibly due to a long-lived transaction. Try again later."
+msgstr ""
+
+msgid "Failed to find import label for Jira import."
+msgstr ""
+
+msgid "Failed to get ref."
+msgstr ""
+
+msgid "Failed to install."
+msgstr ""
+
+msgid "Failed to load assignees. Please try again."
+msgstr ""
+
+msgid "Failed to load authors. Please try again."
+msgstr ""
+
+msgid "Failed to load emoji list."
+msgstr ""
+
+msgid "Failed to load error details from Sentry."
+msgstr ""
+
+msgid "Failed to load errors from Sentry."
+msgstr ""
+
+msgid "Failed to load group activity metrics. Please try again."
+msgstr ""
+
+msgid "Failed to load groups & users."
+msgstr ""
+
+msgid "Failed to load labels. Please try again."
+msgstr ""
+
+msgid "Failed to load milestones. Please try again."
+msgstr ""
+
+msgid "Failed to load related branches"
+msgstr ""
+
+msgid "Failed to load stacktrace."
+msgstr ""
+
+msgid "Failed to mark this issue as a duplicate because referenced issue was not found."
+msgstr ""
+
+msgid "Failed to move this issue because label was not found."
+msgstr ""
+
+msgid "Failed to move this issue because only a single label can be provided."
+msgstr ""
+
+msgid "Failed to move this issue because target project doesn't exist."
+msgstr ""
+
+msgid "Failed to promote label due to internal error. Please contact administrators."
+msgstr ""
+
+msgid "Failed to protect the branch"
+msgstr ""
+
+msgid "Failed to protect the environment"
+msgstr ""
+
+msgid "Failed to publish issue on status page."
+msgstr ""
+
+msgid "Failed to remove a Zoom meeting"
+msgstr ""
+
+msgid "Failed to remove issue from board, please try again."
+msgstr ""
+
+msgid "Failed to remove mirror."
+msgstr ""
+
+msgid "Failed to remove the pipeline schedule"
+msgstr ""
+
+msgid "Failed to remove user identity."
+msgstr ""
+
+msgid "Failed to remove user key."
+msgstr ""
+
+msgid "Failed to reset key. Please try again."
+msgstr ""
+
+msgid "Failed to save merge conflicts resolutions. Please try again!"
+msgstr ""
+
+msgid "Failed to save new settings"
+msgstr ""
+
+msgid "Failed to save preferences (%{error_message})."
+msgstr ""
+
+msgid "Failed to save preferences."
+msgstr ""
+
+msgid "Failed to set due date because the date format is invalid."
+msgstr ""
+
+msgid "Failed to set iteration on this issue. Please try again."
+msgstr ""
+
+msgid "Failed to signing using smartcard authentication"
+msgstr ""
+
+msgid "Failed to update branch!"
+msgstr ""
+
+msgid "Failed to update environment!"
+msgstr ""
+
+msgid "Failed to update issue status"
+msgstr ""
+
+msgid "Failed to update issues, please try again."
+msgstr ""
+
+msgid "Failed to update tag!"
+msgstr ""
+
+msgid "Failed to update."
+msgstr ""
+
+msgid "Failed to upgrade."
+msgstr ""
+
+msgid "Failed to upload object map file"
+msgstr ""
+
+msgid "Failed to verify domain ownership"
+msgstr ""
+
+msgid "Failure"
+msgstr ""
+
+msgid "False positive"
+msgstr ""
+
+msgid "Fast-forward merge is not possible. Rebase the source branch onto %{targetBranch} to allow this merge request to be merged."
+msgstr ""
+
+msgid "Fast-forward merge is not possible. Rebase the source branch onto the target branch."
+msgstr ""
+
+msgid "Fast-forward merge without a merge commit"
+msgstr ""
+
+msgid "Faster as it re-uses the project workspace (falling back to clone if it doesn't exist)"
+msgstr ""
+
+msgid "Faster releases. Better code. Less pain."
+msgstr ""
+
+msgid "Favicon was successfully removed."
+msgstr ""
+
+msgid "Feature Flags"
+msgstr ""
+
+msgid "Feature flag was not removed."
+msgstr ""
+
+msgid "Feature flag was successfully removed."
+msgstr ""
+
+msgid "FeatureFlags|* (All Environments)"
+msgstr ""
+
+msgid "FeatureFlags|* (All environments)"
+msgstr ""
+
+msgid "FeatureFlags|API URL"
+msgstr ""
+
+msgid "FeatureFlags|Active"
+msgstr ""
+
+msgid "FeatureFlags|Add strategy"
+msgstr ""
+
+msgid "FeatureFlags|All users"
+msgstr ""
+
+msgid "FeatureFlags|Configure"
+msgstr ""
+
+msgid "FeatureFlags|Configure feature flags"
+msgstr ""
+
+msgid "FeatureFlags|Create feature flag"
+msgstr ""
+
+msgid "FeatureFlags|Delete %{name}?"
+msgstr ""
+
+msgid "FeatureFlags|Delete feature flag"
+msgstr ""
+
+msgid "FeatureFlags|Description"
+msgstr ""
+
+msgid "FeatureFlags|Edit Feature Flag"
+msgstr ""
+
+msgid "FeatureFlags|Edit Feature Flag User List"
+msgstr ""
+
+msgid "FeatureFlags|Edit list"
+msgstr ""
+
+msgid "FeatureFlags|Enable features for specific users and specific environments by defining feature flag strategies. By default, features are available to all users in all environments."
+msgstr ""
+
+msgid "FeatureFlags|Environment Spec"
+msgstr ""
+
+msgid "FeatureFlags|Environment Specs"
+msgstr ""
+
+msgid "FeatureFlags|Feature Flag"
+msgstr ""
+
+msgid "FeatureFlags|Feature Flag User List Details"
+msgstr ""
+
+msgid "FeatureFlags|Feature Flag behavior is built up by creating a set of rules to define the status of target environments. A default wildcard rule %{codeStart}*%{codeEnd} for %{boldStart}All Environments%{boldEnd} is set, and you are able to add as many rules as you need by choosing environment specs below. You can toggle the behavior for each of your rules to set them %{boldStart}Active%{boldEnd} or %{boldStart}Inactive%{boldEnd}."
+msgstr ""
+
+msgid "FeatureFlags|Feature Flag has no strategies"
+msgstr ""
+
+msgid "FeatureFlags|Feature Flags"
+msgstr ""
+
+msgid "FeatureFlags|Feature Flags will look different in the next milestone. No action is needed, but you may notice the functionality was changed to improve the workflow."
+msgstr ""
+
+msgid "FeatureFlags|Feature flag %{name} will be removed. Are you sure?"
+msgstr ""
+
+msgid "FeatureFlags|Feature flags allow you to configure your code into different flavors by dynamically toggling certain functionality."
+msgstr ""
+
+msgid "FeatureFlags|Flag becomes read only soon"
+msgstr ""
+
+msgid "FeatureFlags|Get started with feature flags"
+msgstr ""
+
+msgid "FeatureFlags|GitLab is moving to a new way of managing feature flags, and in 13.4, this feature flag will become read-only. Please create a new feature flag."
+msgstr ""
+
+msgid "FeatureFlags|ID"
+msgstr ""
+
+msgid "FeatureFlags|Inactive"
+msgstr ""
+
+msgid "FeatureFlags|Inactive flag for %{scope}"
+msgstr ""
+
+msgid "FeatureFlags|Include additional user IDs"
+msgstr ""
+
+msgid "FeatureFlags|Install a %{docs_link_anchored_start}compatible client library%{docs_link_anchored_end} and specify the API URL, application name, and instance ID during the configuration setup. %{docs_link_start}More Information%{docs_link_end}"
+msgstr ""
+
+msgid "FeatureFlags|Instance ID"
+msgstr ""
+
+msgid "FeatureFlags|List details"
+msgstr ""
+
+msgid "FeatureFlags|Loading feature flags"
+msgstr ""
+
+msgid "FeatureFlags|More information"
+msgstr ""
+
+msgid "FeatureFlags|Name"
+msgstr ""
+
+msgid "FeatureFlags|New"
+msgstr ""
+
+msgid "FeatureFlags|New Feature Flag"
+msgstr ""
+
+msgid "FeatureFlags|New Feature Flag User List"
+msgstr ""
+
+msgid "FeatureFlags|New feature flag"
+msgstr ""
+
+msgid "FeatureFlags|New list"
+msgstr ""
+
+msgid "FeatureFlags|Percent rollout (logged in users)"
+msgstr ""
+
+msgid "FeatureFlags|Percent rollout must be a whole number between 0 and 100"
+msgstr ""
+
+msgid "FeatureFlags|Protected"
+msgstr ""
+
+msgid "FeatureFlags|Remove"
+msgstr ""
+
+msgid "FeatureFlags|Rollout Percentage"
+msgstr ""
+
+msgid "FeatureFlags|Rollout Strategy"
+msgstr ""
+
+msgid "FeatureFlags|Status"
+msgstr ""
+
+msgid "FeatureFlags|Strategies"
+msgstr ""
+
+msgid "FeatureFlags|Target environments"
+msgstr ""
+
+msgid "FeatureFlags|There was an error fetching the feature flags."
+msgstr ""
+
+msgid "FeatureFlags|There was an error retrieving user lists"
+msgstr ""
+
+msgid "FeatureFlags|Try again in a few moments or contact your support team."
+msgstr ""
+
+msgid "FeatureFlags|User IDs"
+msgstr ""
+
+msgid "FeatureFlag|Delete strategy"
+msgstr ""
+
+msgid "FeatureFlag|List"
+msgstr ""
+
+msgid "FeatureFlag|Percentage"
+msgstr ""
+
+msgid "FeatureFlag|Select a user list"
+msgstr ""
+
+msgid "FeatureFlag|There are no configured user lists"
+msgstr ""
+
+msgid "FeatureFlag|Type"
+msgstr ""
+
+msgid "FeatureFlag|User IDs"
+msgstr ""
+
+msgid "Feb"
+msgstr ""
+
+msgid "February"
+msgstr ""
+
+msgid "Fetching incoming email"
+msgstr ""
+
+msgid "Fetching licenses failed."
+msgstr ""
+
+msgid "Fetching licenses failed. The request endpoint was not found."
+msgstr ""
+
+msgid "Fetching licenses failed. You are not permitted to perform this action."
+msgstr ""
+
+msgid "File"
+msgstr ""
+
+msgid "File Hooks"
+msgstr ""
+
+msgid "File Hooks (%{count})"
+msgstr ""
+
+msgid "File added"
+msgstr ""
+
+msgid "File browser"
+msgstr ""
+
+msgid "File deleted"
+msgstr ""
+
+msgid "File format is no longer supported"
+msgstr ""
+
+msgid "File hooks are similar to system hooks but are executed as files instead of sending data to a URL."
+msgstr ""
+
+msgid "File mode changed from %{a_mode} to %{b_mode}"
+msgstr ""
+
+msgid "File moved"
+msgstr ""
+
+msgid "File name"
+msgstr ""
+
+msgid "File renamed with no changes."
+msgstr ""
+
+msgid "File sync capacity"
+msgstr ""
+
+msgid "File templates"
+msgstr ""
+
+msgid "File upload error."
+msgstr ""
+
+msgid "Files"
+msgstr ""
+
+msgid "Files breadcrumb"
+msgstr ""
+
+msgid "Files, directories, and submodules in the path %{path} for commit reference %{ref}"
+msgstr ""
+
+msgid "Fill in the fields below, turn on <strong>%{enable_label}</strong>, and press <strong>%{save_changes}</strong>"
+msgstr ""
+
+msgid "Filter"
+msgstr ""
+
+msgid "Filter by %{issuable_type} that are currently closed."
+msgstr ""
+
+msgid "Filter by %{issuable_type} that are currently opened."
+msgstr ""
+
+msgid "Filter by %{page_context_word} that are currently opened."
+msgstr ""
+
+msgid "Filter by Git revision"
+msgstr ""
+
+msgid "Filter by commit message"
+msgstr ""
+
+msgid "Filter by issues that are currently closed."
+msgstr ""
+
+msgid "Filter by label"
+msgstr ""
+
+msgid "Filter by merge requests that are currently closed and unmerged."
+msgstr ""
+
+msgid "Filter by merge requests that are currently merged."
+msgstr ""
+
+msgid "Filter by milestone name"
+msgstr ""
+
+msgid "Filter by name"
+msgstr ""
+
+msgid "Filter by requirements that are currently archived."
+msgstr ""
+
+msgid "Filter by requirements that are currently opened."
+msgstr ""
+
+msgid "Filter by status"
+msgstr ""
+
+msgid "Filter by two-factor authentication"
+msgstr ""
+
+msgid "Filter by user"
+msgstr ""
+
+msgid "Filter pipelines"
+msgstr ""
+
+msgid "Filter projects"
+msgstr ""
+
+msgid "Filter results"
+msgstr ""
+
+msgid "Filter results by group"
+msgstr ""
+
+msgid "Filter results by project"
+msgstr ""
+
+msgid "Filter results..."
+msgstr ""
+
+msgid "Filter your projects by name"
+msgstr ""
+
+msgid "Filter..."
+msgstr ""
+
+msgid "Find File"
+msgstr ""
+
+msgid "Find bugs in your code with coverage-guided fuzzing"
+msgstr ""
+
+msgid "Find by path"
+msgstr ""
+
+msgid "Find existing members by name"
+msgstr ""
+
+msgid "Find file"
+msgstr ""
+
+msgid "Find the downloaded ZIP file and decompress it."
+msgstr ""
+
+msgid "Find the newly extracted <code>Takeout/Google Code Project Hosting/GoogleCodeProjectHosting.json</code> file."
+msgstr ""
+
+msgid "Fingerprint"
+msgstr ""
+
+msgid "Fingerprints"
+msgstr ""
+
+msgid "Finish editing this message first!"
+msgstr ""
+
+msgid "Finish review"
+msgstr ""
+
+msgid "Finish setting up your dedicated account for <strong>%{group_name}</strong>."
+msgstr ""
+
+msgid "Finished"
+msgstr ""
+
+msgid "First Name is too long (maximum is %{max_length} characters)."
+msgstr ""
+
+msgid "First Seen"
+msgstr ""
+
+msgid "First day of the week"
+msgstr ""
+
+msgid "First name"
+msgstr ""
+
+msgid "First seen"
+msgstr ""
+
+msgid "Fixed date"
+msgstr ""
+
+msgid "Fixed due date"
+msgstr ""
+
+msgid "Fixed start date"
+msgstr ""
+
+msgid "Fixed:"
+msgstr ""
+
+msgid "Flags"
+msgstr ""
+
+msgid "FlowdockService|Flowdock Git source token"
+msgstr ""
+
+msgid "FlowdockService|Flowdock is a collaboration web app for technical teams."
+msgstr ""
+
+msgid "FogBugz Email"
+msgstr ""
+
+msgid "FogBugz Import"
+msgstr ""
+
+msgid "FogBugz Password"
+msgstr ""
+
+msgid "FogBugz URL"
+msgstr ""
+
+msgid "FogBugz import"
+msgstr ""
+
+msgid "Folder/%{name}"
+msgstr ""
+
+msgid "Follow the steps below to export your Google Code project data."
+msgstr ""
+
+msgid "Font Color"
+msgstr ""
+
+msgid "Footer message"
+msgstr ""
+
+msgid "For each Jira issue successfully imported, we'll create a new GitLab issue with the following data:"
+msgstr ""
+
+msgid "For internal projects, any logged in user can view pipelines and access job details (output logs and artifacts)"
+msgstr ""
+
+msgid "For more info, read the documentation."
+msgstr ""
+
+msgid "For more information, go to the "
+msgstr ""
+
+msgid "For more information, please review %{link_start_tag}Jaeger's configuration doc%{link_end_tag}"
+msgstr ""
+
+msgid "For more information, see the File Hooks documentation."
+msgstr ""
+
+msgid "For more information, see the documentation on %{deactivating_usage_ping_link_start}deactivating the usage ping%{deactivating_usage_ping_link_end}."
+msgstr ""
+
+msgid "For more information, see the documentation on %{link_start}disabling Seat Link%{link_end}."
+msgstr ""
+
+msgid "For private projects, any member (guest or higher) can view pipelines and access job details (output logs and artifacts)"
+msgstr ""
+
+msgid "For public projects, anyone can view pipelines and access job details (output logs and artifacts)"
+msgstr ""
+
+msgid "Forgot your password?"
+msgstr ""
+
+msgid "Fork"
+msgstr ""
+
+msgid "Fork Error!"
+msgstr ""
+
+msgid "Fork project"
+msgstr ""
+
+msgid "Fork project?"
+msgstr ""
+
+msgid "ForkedFromProjectPath|Forked from"
+msgstr ""
+
+msgid "ForkedFromProjectPath|Forked from an inaccessible project"
+msgstr ""
+
+msgid "Forking in progress"
+msgstr ""
+
+msgid "Forks"
+msgstr ""
+
+msgid "Format"
+msgstr ""
+
+msgid "Format: %{dateFormat}"
+msgstr ""
+
+msgid "Forward external support email address to"
+msgstr ""
+
+msgid "Found errors in your %{gitlab_ci_yml}:"
+msgstr ""
+
+msgid "Found errors in your .gitlab-ci.yml:"
+msgstr ""
+
+msgid "Free Trial"
+msgstr ""
+
+msgid "Free Trial of GitLab.com Gold"
+msgstr ""
+
+msgid "Frequency"
+msgstr ""
+
+msgid "Friday"
+msgstr ""
+
+msgid "From"
+msgstr ""
+
+msgid "From %{providerTitle}"
+msgstr ""
+
+msgid "From <code>%{source_title}</code> into"
+msgstr ""
+
+msgid "From Bitbucket"
+msgstr ""
+
+msgid "From Bitbucket Server"
+msgstr ""
+
+msgid "From FogBugz"
+msgstr ""
+
+msgid "From GitLab.com"
+msgstr ""
+
+msgid "From Google Code"
+msgstr ""
+
+msgid "From issue creation until deploy to production"
+msgstr ""
+
+msgid "From merge request merge until deploy to production"
+msgstr ""
+
+msgid "From the Kubernetes cluster details view, install Runner from the applications list"
+msgstr ""
+
+msgid "Full name"
+msgstr ""
+
+msgid "GPG Key ID:"
+msgstr ""
+
+msgid "GPG Keys"
+msgstr ""
+
+msgid "GPG keys allow you to verify signed commits."
+msgstr ""
+
+msgid "GPG signature (loading...)"
+msgstr ""
+
+msgid "General"
+msgstr ""
+
+msgid "General Settings"
+msgstr ""
+
+msgid "General pipelines"
+msgstr ""
+
+msgid "Generate a default set of labels"
+msgstr ""
+
+msgid "Generate key"
+msgstr ""
+
+msgid "Generate new export"
+msgstr ""
+
+msgid "Geo"
+msgstr ""
+
+msgid "Geo Nodes"
+msgstr ""
+
+msgid "Geo Nodes|Cannot remove a primary node if there is a secondary node"
+msgstr ""
+
+msgid "Geo Replication"
+msgstr ""
+
+msgid "Geo Settings"
+msgstr ""
+
+msgid "Geo allows you to replicate your GitLab instance to other geographical locations."
+msgstr ""
+
+msgid "Geo nodes are paused using a command run on the node"
+msgstr ""
+
+msgid "GeoNodeStatusEvent|%{timeAgoStr} (%{pendingEvents} events)"
+msgstr ""
+
+msgid "GeoNodeSyncStatus|Node is failing or broken."
+msgstr ""
+
+msgid "GeoNodeSyncStatus|Node is slow, overloaded, or it just recovered after an outage."
+msgstr ""
+
+msgid "GeoNodes|Attachments"
+msgstr ""
+
+msgid "GeoNodes|Checksummed"
+msgstr ""
+
+msgid "GeoNodes|Consult Geo troubleshooting information"
+msgstr ""
+
+msgid "GeoNodes|Container repositories"
+msgstr ""
+
+msgid "GeoNodes|Data replication lag"
+msgstr ""
+
+msgid "GeoNodes|Design repositories"
+msgstr ""
+
+msgid "GeoNodes|Does not match the primary storage configuration"
+msgstr ""
+
+msgid "GeoNodes|Failed"
+msgstr ""
+
+msgid "GeoNodes|Full"
+msgstr ""
+
+msgid "GeoNodes|GitLab version"
+msgstr ""
+
+msgid "GeoNodes|GitLab version does not match the primary node version"
+msgstr ""
+
+msgid "GeoNodes|Health status"
+msgstr ""
+
+msgid "GeoNodes|Internal URL"
+msgstr ""
+
+msgid "GeoNodes|Job artifacts"
+msgstr ""
+
+msgid "GeoNodes|LFS objects"
+msgstr ""
+
+msgid "GeoNodes|Last event ID processed by cursor"
+msgstr ""
+
+msgid "GeoNodes|Last event ID seen from primary"
+msgstr ""
+
+msgid "GeoNodes|Learn more about Geo node statuses"
+msgstr ""
+
+msgid "GeoNodes|Loading nodes"
+msgstr ""
+
+msgid "GeoNodes|New node"
+msgstr ""
+
+msgid "GeoNodes|Node Authentication was successfully repaired."
+msgstr ""
+
+msgid "GeoNodes|Node URL"
+msgstr ""
+
+msgid "GeoNodes|Node was successfully removed."
+msgstr ""
+
+msgid "GeoNodes|Node's status was updated %{timeAgo}."
+msgstr ""
+
+msgid "GeoNodes|Not checksummed"
+msgstr ""
+
+msgid "GeoNodes|Package files"
+msgstr ""
+
+msgid "GeoNodes|Pausing replication stops the sync process. Are you sure?"
+msgstr ""
+
+msgid "GeoNodes|Removing a Geo primary node stops the synchronization to all nodes. Are you sure?"
+msgstr ""
+
+msgid "GeoNodes|Removing a Geo secondary node stops the synchronization to that node. Are you sure?"
+msgstr ""
+
+msgid "GeoNodes|Replicated data is verified with the %{nodeText} using checksums"
+msgstr ""
+
+msgid "GeoNodes|Replication slot WAL"
+msgstr ""
+
+msgid "GeoNodes|Replication slots"
+msgstr ""
+
+msgid "GeoNodes|Replication status"
+msgstr ""
+
+msgid "GeoNodes|Repositories"
+msgstr ""
+
+msgid "GeoNodes|Repository checksum progress"
+msgstr ""
+
+msgid "GeoNodes|Repository verification progress"
+msgstr ""
+
+msgid "GeoNodes|Selective (%{syncLabel})"
+msgstr ""
+
+msgid "GeoNodes|Selective synchronization"
+msgstr ""
+
+msgid "GeoNodes|Something went wrong while changing node status"
+msgstr ""
+
+msgid "GeoNodes|Something went wrong while fetching nodes"
+msgstr ""
+
+msgid "GeoNodes|Something went wrong while removing node"
+msgstr ""
+
+msgid "GeoNodes|Something went wrong while repairing node"
+msgstr ""
+
+msgid "GeoNodes|Storage config"
+msgstr ""
+
+msgid "GeoNodes|Sync settings"
+msgstr ""
+
+msgid "GeoNodes|Unused slots"
+msgstr ""
+
+msgid "GeoNodes|Unverified"
+msgstr ""
+
+msgid "GeoNodes|Updated %{timeAgo}"
+msgstr ""
+
+msgid "GeoNodes|Used slots"
+msgstr ""
+
+msgid "GeoNodes|Verified"
+msgstr ""
+
+msgid "GeoNodes|Wiki checksum progress"
+msgstr ""
+
+msgid "GeoNodes|Wiki verification progress"
+msgstr ""
+
+msgid "GeoNodes|Wikis"
+msgstr ""
+
+msgid "GeoNodes|With %{geo} you can install a special read-only and replicated instance anywhere. Before you add nodes, follow the %{instructions} in the exact order they appear."
+msgstr ""
+
+msgid "GeoNodes|You have configured Geo nodes using an insecure HTTP connection. We recommend the use of HTTPS."
+msgstr ""
+
+msgid "GeoNodes|primary node"
+msgstr ""
+
+msgid "GeoNodes|secondary nodes"
+msgstr ""
+
+msgid "Geo|%{label} can't be blank"
+msgstr ""
+
+msgid "Geo|%{label} should be between 1-999"
+msgstr ""
+
+msgid "Geo|%{name} is scheduled for forced re-download"
+msgstr ""
+
+msgid "Geo|%{name} is scheduled for re-sync"
+msgstr ""
+
+msgid "Geo|%{name} is scheduled for re-verify"
+msgstr ""
+
+msgid "Geo|Adjust your filters/search criteria above. If you believe this may be an error, please refer to the %{linkStart}Geo Troubleshooting%{linkEnd} documentation for more information."
+msgstr ""
+
+msgid "Geo|All %{replicable_name}"
+msgstr ""
+
+msgid "Geo|All projects"
+msgstr ""
+
+msgid "Geo|All projects are being scheduled for resync"
+msgstr ""
+
+msgid "Geo|All projects are being scheduled for reverify"
+msgstr ""
+
+msgid "Geo|Could not remove tracking entry for an existing project."
+msgstr ""
+
+msgid "Geo|Could not remove tracking entry for an existing upload."
+msgstr ""
+
+msgid "Geo|Failed"
+msgstr ""
+
+msgid "Geo|Filter by status"
+msgstr ""
+
+msgid "Geo|Geo Status"
+msgstr ""
+
+msgid "Geo|In progress"
+msgstr ""
+
+msgid "Geo|In sync"
+msgstr ""
+
+msgid "Geo|Last repository check run"
+msgstr ""
+
+msgid "Geo|Last successful sync"
+msgstr ""
+
+msgid "Geo|Last sync attempt"
+msgstr ""
+
+msgid "Geo|Last time verified"
+msgstr ""
+
+msgid "Geo|Never"
+msgstr ""
+
+msgid "Geo|Next sync scheduled at"
+msgstr ""
+
+msgid "Geo|Node name can't be blank"
+msgstr ""
+
+msgid "Geo|Node name should be between 1 and 255 characters"
+msgstr ""
+
+msgid "Geo|Not synced yet"
+msgstr ""
+
+msgid "Geo|Pending synchronization"
+msgstr ""
+
+msgid "Geo|Pending verification"
+msgstr ""
+
+msgid "Geo|Please refer to Geo Troubleshooting."
+msgstr ""
+
+msgid "Geo|Project"
+msgstr ""
+
+msgid "Geo|Project (ID: %{project_id}) no longer exists on the primary. It is safe to remove this entry, as this will not remove any data on disk."
+msgstr ""
+
+msgid "Geo|Projects in certain groups"
+msgstr ""
+
+msgid "Geo|Projects in certain storage shards"
+msgstr ""
+
+msgid "Geo|Redownload"
+msgstr ""
+
+msgid "Geo|Remove"
+msgstr ""
+
+msgid "Geo|Remove entry"
+msgstr ""
+
+msgid "Geo|Remove tracking database entry"
+msgstr ""
+
+msgid "Geo|Resync"
+msgstr ""
+
+msgid "Geo|Resync all"
+msgstr ""
+
+msgid "Geo|Retry count"
+msgstr ""
+
+msgid "Geo|Reverify"
+msgstr ""
+
+msgid "Geo|Reverify all"
+msgstr ""
+
+msgid "Geo|Review replication status, and resynchronize and reverify items with the primary node."
+msgstr ""
+
+msgid "Geo|Status"
+msgstr ""
+
+msgid "Geo|Synced"
+msgstr ""
+
+msgid "Geo|Synced at"
+msgstr ""
+
+msgid "Geo|Synchronization failed - %{error}"
+msgstr ""
+
+msgid "Geo|Synchronization of %{itemTitle} is disabled."
+msgstr ""
+
+msgid "Geo|The database is currently %{db_lag} behind the primary node."
+msgstr ""
+
+msgid "Geo|The node is currently %{minutes_behind} behind the primary node."
+msgstr ""
+
+msgid "Geo|There are no %{replicable_type} to show"
+msgstr ""
+
+msgid "Geo|Tracking database entry will be removed. Are you sure?"
+msgstr ""
+
+msgid "Geo|Tracking entry for project (%{project_id}) was successfully removed."
+msgstr ""
+
+msgid "Geo|Tracking entry for upload (%{type}/%{id}) was successfully removed."
+msgstr ""
+
+msgid "Geo|URL can't be blank"
+msgstr ""
+
+msgid "Geo|URL must be a valid url (ex: https://gitlab.com)"
+msgstr ""
+
+msgid "Geo|Unknown state"
+msgstr ""
+
+msgid "Geo|Verification failed - %{error}"
+msgstr ""
+
+msgid "Geo|Waiting for scheduler"
+msgstr ""
+
+msgid "Geo|You are on a secondary, <b>read-only</b> Geo node. If you want to make changes, you must visit this page on the %{primary_node}."
+msgstr ""
+
+msgid "Geo|You are on a secondary, <b>read-only</b> Geo node. You may be able to make a limited amount of changes or perform a limited amount of actions on this page."
+msgstr ""
+
+msgid "Geo|misconfigured"
+msgstr ""
+
+msgid "Geo|primary"
+msgstr ""
+
+msgid "Geo|secondary"
+msgstr ""
+
+msgid "Get a free instance review"
+msgstr ""
+
+msgid "Get started"
+msgstr ""
+
+msgid "Get started with error tracking"
+msgstr ""
+
+msgid "Get started with performance monitoring"
+msgstr ""
+
+msgid "Get started!"
+msgstr ""
+
+msgid "Getting started with releases"
+msgstr ""
+
+msgid "Git"
+msgstr ""
+
+msgid "Git LFS is not enabled on this GitLab server, contact your admin."
+msgstr ""
+
+msgid "Git LFS objects will be synced in pull mirrors if LFS is %{docs_link_start}enabled for the project%{docs_link_end}. They will <strong>not</strong> be synced in push mirrors."
+msgstr ""
+
+msgid "Git global setup"
+msgstr ""
+
+msgid "Git repository URL"
+msgstr ""
+
+msgid "Git revision"
+msgstr ""
+
+msgid "Git shallow clone"
+msgstr ""
+
+msgid "Git strategy for pipelines"
+msgstr ""
+
+msgid "Git version"
+msgstr ""
+
+msgid "GitHub API rate limit exceeded. Try again after %{reset_time}"
+msgstr ""
+
+msgid "GitHub import"
+msgstr ""
+
+msgid "GitLab / Unsubscribe"
+msgstr ""
+
+msgid "GitLab Enterprise Edition %{plan}"
+msgstr ""
+
+msgid "GitLab Group Runners can execute code for all the projects in this group."
+msgstr ""
+
+msgid "GitLab Import"
+msgstr ""
+
+msgid "GitLab Issue"
+msgstr ""
+
+msgid "GitLab Shared Runners execute code of different projects on the same Runner unless you configure GitLab Runner Autoscale with MaxBuilds 1 (which it is on GitLab.com)."
+msgstr ""
+
+msgid "GitLab Support Bot"
+msgstr ""
+
+msgid "GitLab Team Member"
+msgstr ""
+
+msgid "GitLab User"
+msgstr ""
+
+msgid "GitLab allows you to continue using your license even if you exceed the number of seats you purchased. You will be required to pay for these seats when you renew your license."
+msgstr ""
+
+msgid "GitLab commit"
+msgstr ""
+
+msgid "GitLab for Slack"
+msgstr ""
+
+msgid "GitLab is a single application for the entire software development lifecycle. From project planning and source code management to CI/CD, monitoring, and security."
+msgstr ""
+
+msgid "GitLab is obtaining a Let's Encrypt SSL certificate for this domain. This process can take some time. Please try again later."
+msgstr ""
+
+msgid "GitLab is undergoing maintenance and is operating in a read-only mode."
+msgstr ""
+
+msgid "GitLab member or Email address"
+msgstr ""
+
+msgid "GitLab metadata URL"
+msgstr ""
+
+msgid "GitLab project export"
+msgstr ""
+
+msgid "GitLab restart is required to apply changes."
+msgstr ""
+
+msgid "GitLab single sign on URL"
+msgstr ""
+
+msgid "GitLab uses %{jaeger_link} to monitor distributed systems."
+msgstr ""
+
+msgid "GitLab will run a background job that will produce pseudonymized CSVs of the GitLab database that will be uploaded to your configured object storage directory."
+msgstr ""
+
+msgid "GitLab.com import"
+msgstr ""
+
+msgid "GitLabPagesDomains|Retry"
+msgstr ""
+
+msgid "GitLabPages|%{domain} is not verified. To learn how to verify ownership, visit your %{link_start}domain details%{link_end}."
+msgstr ""
+
+msgid "GitLabPages|Access Control is enabled for this Pages website; only authorized users will be able to access it. To make your website publicly available, navigate to your project's %{strong_start}Settings > General > Visibility%{strong_end} and select %{strong_start}Everyone%{strong_end} in pages section. Read the %{link_start}documentation%{link_end} for more information."
+msgstr ""
+
+msgid "GitLabPages|Access pages"
+msgstr ""
+
+msgid "GitLabPages|Are you sure?"
+msgstr ""
+
+msgid "GitLabPages|Certificate: %{subject}"
+msgstr ""
+
+msgid "GitLabPages|Configure pages"
+msgstr ""
+
+msgid "GitLabPages|Domains"
+msgstr ""
+
+msgid "GitLabPages|Edit"
+msgstr ""
+
+msgid "GitLabPages|Expired"
+msgstr ""
+
+msgid "GitLabPages|Force HTTPS (requires valid certificates)"
+msgstr ""
+
+msgid "GitLabPages|GitLab Pages are disabled for this project. You can enable them on your project's %{strong_start}Settings > General > Visibility%{strong_end} page."
+msgstr ""
+
+msgid "GitLabPages|It may take up to 30 minutes before the site is available after the first deployment."
+msgstr ""
+
+msgid "GitLabPages|Learn how to upload your static site and have it served by GitLab by following the %{link_start}documentation on GitLab Pages%{link_end}."
+msgstr ""
+
+msgid "GitLabPages|Learn more."
+msgstr ""
+
+msgid "GitLabPages|Maximum size of pages (MB)"
+msgstr ""
+
+msgid "GitLabPages|New Domain"
+msgstr ""
+
+msgid "GitLabPages|Only project maintainers can remove pages"
+msgstr ""
+
+msgid "GitLabPages|Pages"
+msgstr ""
+
+msgid "GitLabPages|Remove"
+msgstr ""
+
+msgid "GitLabPages|Remove pages"
+msgstr ""
+
+msgid "GitLabPages|Removing pages will prevent them from being exposed to the outside world."
+msgstr ""
+
+msgid "GitLabPages|Save"
+msgstr ""
+
+msgid "GitLabPages|Something went wrong while obtaining the Let's Encrypt certificate for %{domain}. To retry visit your %{link_start}domain details%{link_end}."
+msgstr ""
+
+msgid "GitLabPages|Support for domains and certificates is disabled. Ask your system's administrator to enable it."
+msgstr ""
+
+msgid "GitLabPages|The total size of deployed static content will be limited to this size. 0 for unlimited. Leave empty to inherit the global value."
+msgstr ""
+
+msgid "GitLabPages|Unverified"
+msgstr ""
+
+msgid "GitLabPages|Verified"
+msgstr ""
+
+msgid "GitLabPages|When using Pages under the general domain of a GitLab instance (%{pages_host}), you cannot use HTTPS with sub-subdomains. This means that if your username/groupname contains a dot it will not work. This is a limitation of the HTTP Over TLS protocol. HTTP pages will continue to work provided you don't redirect HTTP to HTTPS."
+msgstr ""
+
+msgid "GitLabPages|With GitLab Pages you can host your static websites on GitLab. Combined with the power of GitLab CI and the help of GitLab Runner you can deploy static pages for your individual projects, your user or your group."
+msgstr ""
+
+msgid "GitLabPages|Your pages are served under:"
+msgstr ""
+
+msgid "Gitaly"
+msgstr ""
+
+msgid "Gitaly Servers"
+msgstr ""
+
+msgid "Gitaly|Address"
+msgstr ""
+
+msgid "Gitea Host URL"
+msgstr ""
+
+msgid "Gitea Import"
+msgstr ""
+
+msgid "Gitlab Pages"
+msgstr ""
+
+msgid "Given access %{time_ago}"
+msgstr ""
+
+msgid "Given epic is already related to this epic."
+msgstr ""
+
+msgid "Global Shortcuts"
+msgstr ""
+
+msgid "Global notification settings"
+msgstr ""
+
+msgid "Go Back"
+msgstr ""
+
+msgid "Go Micro is a framework for micro service development."
+msgstr ""
+
+msgid "Go back"
+msgstr ""
+
+msgid "Go back (while searching for files)"
+msgstr ""
+
+msgid "Go back to %{startTag}Open issues%{endTag} and select some issues to add to your board."
+msgstr ""
+
+msgid "Go full screen"
+msgstr ""
+
+msgid "Go to %{link_to_google_takeout}."
+msgstr ""
+
+msgid "Go to <strong>Issues</strong> > <strong>Boards</strong> to access your personalized learning issue board."
+msgstr ""
+
+msgid "Go to Pipelines"
+msgstr ""
+
+msgid "Go to Webhooks"
+msgstr ""
+
+msgid "Go to commits"
+msgstr ""
+
+msgid "Go to definition"
+msgstr ""
+
+msgid "Go to environments"
+msgstr ""
+
+msgid "Go to file"
+msgstr ""
+
+msgid "Go to file permalink (while viewing a file)"
+msgstr ""
+
+msgid "Go to files"
+msgstr ""
+
+msgid "Go to find file"
+msgstr ""
+
+msgid "Go to issue boards"
+msgstr ""
+
+msgid "Go to issues"
+msgstr ""
+
+msgid "Go to jobs"
+msgstr ""
+
+msgid "Go to kubernetes"
+msgstr ""
+
+msgid "Go to merge requests"
+msgstr ""
+
+msgid "Go to metrics"
+msgstr ""
+
+msgid "Go to parent"
+msgstr ""
+
+msgid "Go to project"
+msgstr ""
+
+msgid "Go to releases"
+msgstr ""
+
+msgid "Go to repository charts"
+msgstr ""
+
+msgid "Go to repository graph"
+msgstr ""
+
+msgid "Go to snippets"
+msgstr ""
+
+msgid "Go to the activity feed"
+msgstr ""
+
+msgid "Go to the milestone list"
+msgstr ""
+
+msgid "Go to the project's activity feed"
+msgstr ""
+
+msgid "Go to the project's overview page"
+msgstr ""
+
+msgid "Go to wiki"
+msgstr ""
+
+msgid "Go to your To-Do list"
+msgstr ""
+
+msgid "Go to your fork"
+msgstr ""
+
+msgid "Go to your groups"
+msgstr ""
+
+msgid "Go to your issues"
+msgstr ""
+
+msgid "Go to your merge requests"
+msgstr ""
+
+msgid "Go to your projects"
+msgstr ""
+
+msgid "Go to your snippets"
+msgstr ""
+
+msgid "Google Cloud Platform"
+msgstr ""
+
+msgid "Google Code import"
+msgstr ""
+
+msgid "Google Takeout"
+msgstr ""
+
+msgid "Google authentication is not %{link_start}properly configured%{link_end}. Ask your GitLab administrator if you want to use this service."
+msgstr ""
+
+msgid "Got it"
+msgstr ""
+
+msgid "Got it!"
+msgstr ""
+
+msgid "Grafana URL"
+msgstr ""
+
+msgid "Grafana response contains invalid json"
+msgstr ""
+
+msgid "GrafanaIntegration|API Token"
+msgstr ""
+
+msgid "GrafanaIntegration|Active"
+msgstr ""
+
+msgid "GrafanaIntegration|Embed Grafana charts in GitLab issues."
+msgstr ""
+
+msgid "GrafanaIntegration|Enter the Grafana API Token."
+msgstr ""
+
+msgid "GrafanaIntegration|Enter the base URL of the Grafana instance."
+msgstr ""
+
+msgid "GrafanaIntegration|Grafana Authentication"
+msgstr ""
+
+msgid "GrafanaIntegration|Grafana URL"
+msgstr ""
+
+msgid "Grant access"
+msgstr ""
+
+msgid "Graph"
+msgstr ""
+
+msgid "Gravatar"
+msgstr ""
+
+msgid "Gravatar enabled"
+msgstr ""
+
+msgid "Group"
+msgstr ""
+
+msgid "Group %{group_name} couldn't be exported."
+msgstr ""
+
+msgid "Group %{group_name} was exported successfully."
+msgstr ""
+
+msgid "Group %{group_name} was scheduled for deletion."
+msgstr ""
+
+msgid "Group %{group_name} was successfully created."
+msgstr ""
+
+msgid "Group Audit Events"
+msgstr ""
+
+msgid "Group CI/CD settings"
+msgstr ""
+
+msgid "Group Git LFS status:"
+msgstr ""
+
+msgid "Group Hooks"
+msgstr ""
+
+msgid "Group ID"
+msgstr ""
+
+msgid "Group ID: %{group_id}"
+msgstr ""
+
+msgid "Group Owner must have signed in with SAML before enabling Group Managed Accounts"
+msgstr ""
+
+msgid "Group Runners"
+msgstr ""
+
+msgid "Group SAML must be enabled to test"
+msgstr ""
+
+msgid "Group URL"
+msgstr ""
+
+msgid "Group avatar"
+msgstr ""
+
+msgid "Group by:"
+msgstr ""
+
+msgid "Group description"
+msgstr ""
+
+msgid "Group description (optional)"
+msgstr ""
+
+msgid "Group details"
+msgstr ""
+
+msgid "Group export could not be started."
+msgstr ""
+
+msgid "Group export error"
+msgstr ""
+
+msgid "Group export link has expired. Please generate a new export from your group settings."
+msgstr ""
+
+msgid "Group export started. A download link will be sent by email and made available on this page."
+msgstr ""
+
+msgid "Group has been already marked for deletion"
+msgstr ""
+
+msgid "Group has not been marked for deletion"
+msgstr ""
+
+msgid "Group import could not be scheduled"
+msgstr ""
+
+msgid "Group info:"
+msgstr ""
+
+msgid "Group is required when cluster_type is :group"
+msgstr ""
+
+msgid "Group maintainers can register group runners in the %{link}"
+msgstr ""
+
+msgid "Group members"
+msgstr ""
+
+msgid "Group milestone"
+msgstr ""
+
+msgid "Group name"
+msgstr ""
+
+msgid "Group name (your organization)"
+msgstr ""
+
+msgid "Group overview"
+msgstr ""
+
+msgid "Group overview content"
+msgstr ""
+
+msgid "Group path is already taken. Suggestions: "
+msgstr ""
+
+msgid "Group path is available."
+msgstr ""
+
+msgid "Group pipeline minutes were successfully reset."
+msgstr ""
+
+msgid "Group project URLs are prefixed with the group namespace"
+msgstr ""
+
+msgid "Group requires separate account"
+msgstr ""
+
+msgid "Group variables (inherited)"
+msgstr ""
+
+msgid "Group was exported"
+msgstr ""
+
+msgid "Group was successfully updated."
+msgstr ""
+
+msgid "Group: %{group_name}"
+msgstr ""
+
+msgid "Group: %{name}"
+msgstr ""
+
+msgid "GroupActivityMetrics|New Members created"
+msgstr ""
+
+msgid "GroupActivyMetrics|Issues created"
+msgstr ""
+
+msgid "GroupActivyMetrics|Merge Requests created"
+msgstr ""
+
+msgid "GroupActivyMetrics|Recent activity (last 90 days)"
+msgstr ""
+
+msgid "GroupImport|Failed to import group."
+msgstr ""
+
+msgid "GroupImport|Group '%{group_name}' is being imported."
+msgstr ""
+
+msgid "GroupImport|Group could not be imported: %{errors}"
+msgstr ""
+
+msgid "GroupImport|Please wait while we import the group for you. Refresh at will."
+msgstr ""
+
+msgid "GroupImport|The group was successfully imported."
+msgstr ""
+
+msgid "GroupImport|Unable to process group import file"
+msgstr ""
+
+msgid "GroupRoadmap|%{dateWord} – No end date"
+msgstr ""
+
+msgid "GroupRoadmap|%{startDateInWords} – %{endDateInWords}"
+msgstr ""
+
+msgid "GroupRoadmap|No start date – %{dateWord}"
+msgstr ""
+
+msgid "GroupRoadmap|Something went wrong while fetching epics"
+msgstr ""
+
+msgid "GroupRoadmap|Something went wrong while fetching milestones"
+msgstr ""
+
+msgid "GroupRoadmap|Sorry, no epics matched your search"
+msgstr ""
+
+msgid "GroupRoadmap|The roadmap shows the progress of your epics along a timeline"
+msgstr ""
+
+msgid "GroupRoadmap|To view the roadmap, add a start or due date to one of the %{linkStart}child epics%{linkEnd}."
+msgstr ""
+
+msgid "GroupRoadmap|To view the roadmap, add a start or due date to one of your epics in this group or its subgroups; from %{startDate} to %{endDate}."
+msgstr ""
+
+msgid "GroupRoadmap|To widen your search, change or remove filters; from %{startDate} to %{endDate}."
+msgstr ""
+
+msgid "GroupSAML|Certificate fingerprint"
+msgstr ""
+
+msgid "GroupSAML|Configuration"
+msgstr ""
+
+msgid "GroupSAML|Copy SAML Response XML"
+msgstr ""
+
+msgid "GroupSAML|Enable SAML authentication for this group."
+msgstr ""
+
+msgid "GroupSAML|Enforce SSO-only authentication for this group."
+msgstr ""
+
+msgid "GroupSAML|Enforce users to have dedicated group managed accounts for this group."
+msgstr ""
+
+msgid "GroupSAML|Enforced SSO"
+msgstr ""
+
+msgid "GroupSAML|Generate a SCIM token"
+msgstr ""
+
+msgid "GroupSAML|Generate a SCIM token to set up your System for Cross-Domain Identity Management."
+msgstr ""
+
+msgid "GroupSAML|Identity"
+msgstr ""
+
+msgid "GroupSAML|Identity provider single sign on URL"
+msgstr ""
+
+msgid "GroupSAML|Make sure you save this token — you won't be able to access it again."
+msgstr ""
+
+msgid "GroupSAML|Manage your group’s membership while adding another level of security with SAML."
+msgstr ""
+
+msgid "GroupSAML|Members"
+msgstr ""
+
+msgid "GroupSAML|Members will be forwarded here when signing in to your group. Get this from your identity provider, where it can also be called \"SSO Service Location\", \"SAML Token Issuance Endpoint\", or \"SAML 2.0/W-Federation URL\"."
+msgstr ""
+
+msgid "GroupSAML|NameID"
+msgstr ""
+
+msgid "GroupSAML|NameID Format"
+msgstr ""
+
+msgid "GroupSAML|Prohibit outer forks"
+msgstr ""
+
+msgid "GroupSAML|Prohibit outer forks for this group."
+msgstr ""
+
+msgid "GroupSAML|SAML Response Output"
+msgstr ""
+
+msgid "GroupSAML|SAML Response XML"
+msgstr ""
+
+msgid "GroupSAML|SAML Single Sign On"
+msgstr ""
+
+msgid "GroupSAML|SAML Single Sign On Settings"
+msgstr ""
+
+msgid "GroupSAML|SCIM API endpoint URL"
+msgstr ""
+
+msgid "GroupSAML|SCIM Token"
+msgstr ""
+
+msgid "GroupSAML|SHA1 fingerprint of the SAML token signing certificate. Get this from your identity provider, where it can also be called \"Thumbprint\"."
+msgstr ""
+
+msgid "GroupSAML|The SCIM token is now hidden. To see the value of the token again, you need to "
+msgstr ""
+
+msgid "GroupSAML|To be able to enable enforced SSO, you first need to enable SAML authentication."
+msgstr ""
+
+msgid "GroupSAML|To be able to enable group managed accounts, you first need to enable enforced SSO."
+msgstr ""
+
+msgid "GroupSAML|To be able to prohibit outer forks, you first need to enforce dedicate group managed accounts."
+msgstr ""
+
+msgid "GroupSAML|Toggle SAML authentication"
+msgstr ""
+
+msgid "GroupSAML|Valid SAML Response"
+msgstr ""
+
+msgid "GroupSAML|With group managed accounts enabled, all the users without a group managed account will be excluded from the group."
+msgstr ""
+
+msgid "GroupSAML|With prohibit outer forks flag enabled group members will be able to fork project only inside your group."
+msgstr ""
+
+msgid "GroupSAML|Your SCIM token"
+msgstr ""
+
+msgid "GroupSAML|must match stored NameID of \"%{extern_uid}\" as we use this to identify users. If the NameID changes users will be unable to sign in."
+msgstr ""
+
+msgid "GroupSAML|should be \"persistent\""
+msgstr ""
+
+msgid "GroupSAML|should be a random persistent ID, emails are discouraged"
+msgstr ""
+
+msgid "GroupSettings|Apply integration settings to all Projects"
+msgstr ""
+
+msgid "GroupSettings|Auto DevOps pipeline was updated for the group"
+msgstr ""
+
+msgid "GroupSettings|Badges"
+msgstr ""
+
+msgid "GroupSettings|Be careful. Changing a group's parent can have unintended %{side_effects_link_start}side effects%{side_effects_link_end}."
+msgstr ""
+
+msgid "GroupSettings|Cannot update the path because there are projects under this group that contain Docker images in their Container Registry. Please remove the images from your projects first and try again."
+msgstr ""
+
+msgid "GroupSettings|Change group path"
+msgstr ""
+
+msgid "GroupSettings|Changing group path can have unintended side effects."
+msgstr ""
+
+msgid "GroupSettings|Custom project templates"
+msgstr ""
+
+msgid "GroupSettings|Customize your group badges."
+msgstr ""
+
+msgid "GroupSettings|Default to Auto DevOps pipeline for all projects within this group"
+msgstr ""
+
+msgid "GroupSettings|Disable email notifications"
+msgstr ""
+
+msgid "GroupSettings|Disable group mentions"
+msgstr ""
+
+msgid "GroupSettings|Export group"
+msgstr ""
+
+msgid "GroupSettings|If the parent group's visibility is lower than the group current visibility, visibility levels for subgroups and projects will be changed to match the new parent group's visibility."
+msgstr ""
+
+msgid "GroupSettings|Integrations configured here will automatically apply to all projects in this group."
+msgstr ""
+
+msgid "GroupSettings|Learn more about badges."
+msgstr ""
+
+msgid "GroupSettings|Learn more about group-level project templates."
+msgstr ""
+
+msgid "GroupSettings|New runners registration token has been generated!"
+msgstr ""
+
+msgid "GroupSettings|Pipeline settings was updated for the group"
+msgstr ""
+
+msgid "GroupSettings|Please choose a group path with no special characters."
+msgstr ""
+
+msgid "GroupSettings|Prevent sharing a project within %{group} with other groups"
+msgstr ""
+
+msgid "GroupSettings|Select a sub-group as the custom project template source for this group."
+msgstr ""
+
+msgid "GroupSettings|The Auto DevOps pipeline will run if no alternative CI configuration file is found."
+msgstr ""
+
+msgid "GroupSettings|There was a problem updating Auto DevOps pipeline: %{error_messages}."
+msgstr ""
+
+msgid "GroupSettings|There was a problem updating the pipeline settings: %{error_messages}."
+msgstr ""
+
+msgid "GroupSettings|This setting is applied on %{ancestor_group} and has been overridden on this subgroup."
+msgstr ""
+
+msgid "GroupSettings|This setting is applied on %{ancestor_group}. To share projects in this group with another group, ask the owner to override the setting or %{remove_ancestor_share_with_group_lock}."
+msgstr ""
+
+msgid "GroupSettings|This setting is applied on %{ancestor_group}. You can override the setting or %{remove_ancestor_share_with_group_lock}."
+msgstr ""
+
+msgid "GroupSettings|This setting will be applied to all subgroups unless overridden by a group owner. Groups that already have access to the project will continue to have access unless removed manually."
+msgstr ""
+
+msgid "GroupSettings|This setting will override user notification preferences for all members of the group, subgroups, and projects."
+msgstr ""
+
+msgid "GroupSettings|This setting will prevent group members from being notified if the group is mentioned."
+msgstr ""
+
+msgid "GroupSettings|Transfer group"
+msgstr ""
+
+msgid "GroupSettings|You can only transfer the group to a group you manage."
+msgstr ""
+
+msgid "GroupSettings|You will need to update your local repositories to point to the new location."
+msgstr ""
+
+msgid "GroupSettings|cannot be changed by you"
+msgstr ""
+
+msgid "GroupSettings|cannot be disabled when the parent group \"Share with group lock\" is enabled, except by the owner of the parent group"
+msgstr ""
+
+msgid "GroupSettings|cannot change when group contains projects with NPM packages"
+msgstr ""
+
+msgid "GroupSettings|remove the share with group lock from %{ancestor_group_name}"
+msgstr ""
+
+msgid "Groups"
+msgstr ""
+
+msgid "Groups (%{count})"
+msgstr ""
+
+msgid "Groups (%{groups})"
+msgstr ""
+
+msgid "Groups and projects"
+msgstr ""
+
+msgid "Groups can also be nested by creating %{subgroup_docs_link_start}subgroups%{subgroup_docs_link_end}."
+msgstr ""
+
+msgid "Groups to synchronize"
+msgstr ""
+
+msgid "Groups with access to %{strong_start}%{group_name}%{strong_end}"
+msgstr ""
+
+msgid "Groups with access to <strong>%{project_name}</strong>"
+msgstr ""
+
+msgid "GroupsDropdown|Frequently visited"
+msgstr ""
+
+msgid "GroupsDropdown|Groups you visit often will appear here"
+msgstr ""
+
+msgid "GroupsDropdown|Loading groups"
+msgstr ""
+
+msgid "GroupsDropdown|Search your groups"
+msgstr ""
+
+msgid "GroupsDropdown|Something went wrong on our end."
+msgstr ""
+
+msgid "GroupsDropdown|Sorry, no groups matched your search"
+msgstr ""
+
+msgid "GroupsDropdown|This feature requires browser localStorage support"
+msgstr ""
+
+msgid "GroupsEmptyState|A group is a collection of several projects."
+msgstr ""
+
+msgid "GroupsEmptyState|If you organize your projects under a group, it works like a folder."
+msgstr ""
+
+msgid "GroupsEmptyState|No groups found"
+msgstr ""
+
+msgid "GroupsEmptyState|You can manage your group member’s permissions and access to each project in the group."
+msgstr ""
+
+msgid "GroupsNew|Contact an administrator to enable options for importing your group."
+msgstr ""
+
+msgid "GroupsNew|Create"
+msgstr ""
+
+msgid "GroupsNew|Create group"
+msgstr ""
+
+msgid "GroupsNew|GitLab group export"
+msgstr ""
+
+msgid "GroupsNew|Import"
+msgstr ""
+
+msgid "GroupsNew|Import group"
+msgstr ""
+
+msgid "GroupsNew|My Awesome Group"
+msgstr ""
+
+msgid "GroupsNew|No import options available"
+msgstr ""
+
+msgid "GroupsNew|To copy a GitLab group between installations, navigate to the group settings page for the original installation, generate an export file, and upload it here."
+msgstr ""
+
+msgid "GroupsTree|Are you sure you want to leave the \"%{fullName}\" group?"
+msgstr ""
+
+msgid "GroupsTree|Create a project in this group."
+msgstr ""
+
+msgid "GroupsTree|Create a subgroup in this group."
+msgstr ""
+
+msgid "GroupsTree|Edit group"
+msgstr ""
+
+msgid "GroupsTree|Failed to leave the group. Please make sure you are not the only owner."
+msgstr ""
+
+msgid "GroupsTree|Leave this group"
+msgstr ""
+
+msgid "GroupsTree|Loading groups"
+msgstr ""
+
+msgid "GroupsTree|No groups matched your search"
+msgstr ""
+
+msgid "GroupsTree|No groups or projects matched your search"
+msgstr ""
+
+msgid "GroupsTree|Search by name"
+msgstr ""
+
+msgid "Guideline"
+msgstr ""
+
+msgid "HTTP Basic: Access denied\\nYou must use a personal access token with 'api' scope for Git over HTTP.\\nYou can generate one at %{profile_personal_access_tokens_url}"
+msgstr ""
+
+msgid "Hashed Storage must be enabled to use Geo"
+msgstr ""
+
+msgid "Hashed repository storage paths"
+msgstr ""
+
+msgid "Hashed storage can't be disabled anymore for new projects"
+msgstr ""
+
+msgid "Have your users email"
+msgstr ""
+
+msgid "Header logo was successfully removed."
+msgstr ""
+
+msgid "Header message"
+msgstr ""
+
+msgid "Headings"
+msgstr ""
+
+msgid "Health"
+msgstr ""
+
+msgid "Health Check"
+msgstr ""
+
+msgid "Health information can be retrieved from the following endpoints. More information is available"
+msgstr ""
+
+msgid "HealthCheck|Access token is"
+msgstr ""
+
+msgid "HealthCheck|Healthy"
+msgstr ""
+
+msgid "HealthCheck|No Health Problems Detected"
+msgstr ""
+
+msgid "HealthCheck|Unhealthy"
+msgstr ""
+
+msgid "Hello there"
+msgstr ""
+
+msgid "Help"
+msgstr ""
+
+msgid "Help page"
+msgstr ""
+
+msgid "Help page text and support page url."
+msgstr ""
+
+msgid "Helps prevent bots from brute-force attacks."
+msgstr ""
+
+msgid "Helps prevent bots from creating accounts."
+msgstr ""
+
+msgid "Helps reduce alert volume (e.g. if creating too many issues)"
+msgstr ""
+
+msgid "Helps reduce request volume for protected paths"
+msgstr ""
+
+msgid "Here are all your projects in your group, including the one you just created. To start, let’s take a look at your personalized learning project which will help you learn about GitLab at your own pace."
+msgstr ""
+
+msgid "Here you will find recent merge request activity"
+msgstr ""
+
+msgid "Hi %{username}!"
+msgstr ""
+
+msgid "Hide archived projects"
+msgstr ""
+
+msgid "Hide chart"
+msgid_plural "Hide charts"
+msgstr[0] ""
+
+msgid "Hide details"
+msgstr ""
+
+msgid "Hide file browser"
+msgstr ""
+
+msgid "Hide group projects"
+msgstr ""
+
+msgid "Hide host keys manual input"
+msgstr ""
+
+msgid "Hide list"
+msgstr ""
+
+msgid "Hide marketing-related entries from help"
+msgstr ""
+
+msgid "Hide payload"
+msgstr ""
+
+msgid "Hide shared projects"
+msgstr ""
+
+msgid "Hide stage"
+msgstr ""
+
+msgid "Hide value"
+msgid_plural "Hide values"
+msgstr[0] ""
+
+msgid "Hide values"
+msgstr ""
+
+msgid "High or unknown vulnerabilities present"
+msgstr ""
+
+msgid "Highest number of requests per minute for each raw path, default to 300. To disable throttling set to 0."
+msgstr ""
+
+msgid "Highest role:"
+msgstr ""
+
+msgid "History"
+msgstr ""
+
+msgid "History of authentications"
+msgstr ""
+
+msgid "Homepage"
+msgstr ""
+
+msgid "Hook execution failed. Ensure the group has a project with commits."
+msgstr ""
+
+msgid "Hook was successfully created."
+msgstr ""
+
+msgid "Hook was successfully updated."
+msgstr ""
+
+msgid "Hostname"
+msgstr ""
+
+msgid "Hour (UTC)"
+msgstr ""
+
+msgid "Housekeeping"
+msgstr ""
+
+msgid "Housekeeping successfully started"
+msgstr ""
+
+msgid "Housekeeping, export, path, transfer, remove, archive."
+msgstr ""
+
+msgid "How it works"
+msgstr ""
+
+msgid "How many days need to pass between marking entity for deletion and actual removing it."
+msgstr ""
+
+msgid "How many replicas each Elasticsearch shard has."
+msgstr ""
+
+msgid "How many shards to split the Elasticsearch index over."
+msgstr ""
+
+msgid "How many users will be evaluating the trial?"
+msgstr ""
+
+msgid "However, you are already a member of this %{member_source}. Sign in using a different account to accept the invitation."
+msgstr ""
+
+msgid "I accept the %{terms_link_start}Terms of Service and Privacy Policy%{terms_link_end}"
+msgstr ""
+
+msgid "I accept the %{terms_link}"
+msgstr ""
+
+msgid "I accept the|Terms of Service and Privacy Policy"
+msgstr ""
+
+msgid "I forgot my password"
+msgstr ""
+
+msgid "I have read and agree to the Let's Encrypt %{link_start}Terms of Service%{link_end} (PDF)"
+msgstr ""
+
+msgid "I'd like to receive updates via email about GitLab"
+msgstr ""
+
+msgid "ID"
+msgstr ""
+
+msgid "ID:"
+msgstr ""
+
+msgid "IDE"
+msgstr ""
+
+msgid "IDE|Allow live previews of JavaScript projects in the Web IDE using CodeSandbox Live Preview."
+msgstr ""
+
+msgid "IDE|Back"
+msgstr ""
+
+msgid "IDE|Commit"
+msgstr ""
+
+msgid "IDE|Commit to %{branchName} branch"
+msgstr ""
+
+msgid "IDE|Edit"
+msgstr ""
+
+msgid "IDE|Get started with Live Preview"
+msgstr ""
+
+msgid "IDE|Go to project"
+msgstr ""
+
+msgid "IDE|Live Preview"
+msgstr ""
+
+msgid "IDE|Preview your web application using Web IDE client-side evaluation."
+msgstr ""
+
+msgid "IDE|Refresh preview"
+msgstr ""
+
+msgid "IDE|Review"
+msgstr ""
+
+msgid "IDE|Successful commit"
+msgstr ""
+
+msgid "IDE|This option is disabled because you are not allowed to create merge requests in this project."
+msgstr ""
+
+msgid "IDE|This option is disabled because you don't have write permissions for the current branch."
+msgstr ""
+
+msgid "INFO: Your SSH key has expired. Please generate a new key."
+msgstr ""
+
+msgid "INFO: Your SSH key is expiring soon. Please generate a new key."
+msgstr ""
+
+msgid "IP Address"
+msgstr ""
+
+msgid "IP subnet restriction only allowed for top-level groups"
+msgstr ""
+
+msgid "Identifier"
+msgstr ""
+
+msgid "Identifiers"
+msgstr ""
+
+msgid "Identities"
+msgstr ""
+
+msgid "If any indexed field exceeds this limit it will be truncated to this number of characters and the rest will not be indexed or searchable. This does not apply to repository and wiki indexing. Setting this to 0 means it is unlimited."
+msgstr ""
+
+msgid "If any job surpasses this timeout threshold, it will be marked as failed. Human readable time input language is accepted like \"1 hour\". Values without specification represent seconds."
+msgstr ""
+
+msgid "If blank, set allowable lifetime to %{instance_level_policy_in_words}, as defined by the instance admin. Once set, existing tokens for users in this group may be revoked."
+msgstr ""
+
+msgid "If checked, group owners can manage LDAP group links and LDAP member overrides"
+msgstr ""
+
+msgid "If checked, new group memberships and permissions can only be added via LDAP synchronization"
+msgstr ""
+
+msgid "If disabled, a diverged local branch will not be automatically updated with commits from its remote counterpart, to prevent local data loss. If the default branch (%{default_branch}) has diverged and cannot be updated, mirroring will fail. Other diverged branches are silently ignored."
+msgstr ""
+
+msgid "If disabled, only admins will be able to configure repository mirroring."
+msgstr ""
+
+msgid "If disabled, the access level will depend on the user's permissions in the project."
+msgstr ""
+
+msgid "If enabled"
+msgstr ""
+
+msgid "If enabled, GitLab will handle Object Storage replication using Geo. %{linkStart}More information%{linkEnd}"
+msgstr ""
+
+msgid "If enabled, access to projects will be validated on an external service using their classification label."
+msgstr ""
+
+msgid "If there is no previous license or if the previous license has expired, some GitLab functionality will be blocked until a new, valid license is uploaded."
+msgstr ""
+
+msgid "If this was a mistake you can %{leave_link_start}leave the %{source_type}%{link_end}."
+msgstr ""
+
+msgid "If this was a mistake you can leave the %{source_type}."
+msgstr ""
+
+msgid "If using GitHub, you’ll see pipeline statuses on GitHub for your commits and pull requests. %{more_info_link}"
+msgstr ""
+
+msgid "If you did not recently sign in, you should immediately %{password_link_start}change your password%{password_link_end}."
+msgstr ""
+
+msgid "If you did not recently sign in, you should immediately change your password: %{password_link}."
+msgstr ""
+
+msgid "If you lose your recovery codes you can generate new ones, invalidating all previous codes."
+msgstr ""
+
+msgid "If you reach 100%% storage capacity, you will not be able to: %{base_message}"
+msgstr ""
+
+msgid "If you recently signed in and recognize the IP address, you may disregard this email."
+msgstr ""
+
+msgid "If you remove this license, GitLab will fall back on the previous license, if any."
+msgstr ""
+
+msgid "If your HTTP repository is not publicly accessible, add your credentials."
+msgstr ""
+
+msgid "Iglu registry URL (optional)"
+msgstr ""
+
+msgid "Ignore"
+msgstr ""
+
+msgid "Ignored"
+msgstr ""
+
+msgid "Image Details"
+msgstr ""
+
+msgid "Image URL"
+msgstr ""
+
+msgid "ImageDiffViewer|2-up"
+msgstr ""
+
+msgid "ImageDiffViewer|Onion skin"
+msgstr ""
+
+msgid "ImageDiffViewer|Swipe"
+msgstr ""
+
+msgid "ImageViewerDimensions|H"
+msgstr ""
+
+msgid "ImageViewerDimensions|W"
+msgstr ""
+
+msgid "Impersonation Tokens"
+msgstr ""
+
+msgid "Impersonation has been disabled"
+msgstr ""
+
+msgid "Import"
+msgstr ""
+
+msgid "Import CSV"
+msgstr ""
+
+msgid "Import Projects from Gitea"
+msgstr ""
+
+msgid "Import all compatible projects"
+msgstr ""
+
+msgid "Import all compatible repositories"
+msgstr ""
+
+msgid "Import all projects"
+msgstr ""
+
+msgid "Import all repositories"
+msgstr ""
+
+msgid "Import an exported GitLab project"
+msgstr ""
+
+msgid "Import failed due to a GitHub error: %{original}"
+msgstr ""
+
+msgid "Import from"
+msgstr ""
+
+msgid "Import from Jira"
+msgstr ""
+
+msgid "Import in progress"
+msgstr ""
+
+msgid "Import in progress. Refresh page to see newly added issues."
+msgstr ""
+
+msgid "Import issues"
+msgstr ""
+
+msgid "Import members"
+msgstr ""
+
+msgid "Import members from another project"
+msgstr ""
+
+msgid "Import multiple repositories by uploading a manifest file."
+msgstr ""
+
+msgid "Import project"
+msgstr ""
+
+msgid "Import project members"
+msgstr ""
+
+msgid "Import projects from Bitbucket"
+msgstr ""
+
+msgid "Import projects from Bitbucket Server"
+msgstr ""
+
+msgid "Import projects from FogBugz"
+msgstr ""
+
+msgid "Import projects from GitLab.com"
+msgstr ""
+
+msgid "Import projects from Google Code"
+msgstr ""
+
+msgid "Import repositories from Bitbucket Server"
+msgstr ""
+
+msgid "Import repositories from GitHub"
+msgstr ""
+
+msgid "Import repository"
+msgstr ""
+
+msgid "Import started by: %{importInitiator}"
+msgstr ""
+
+msgid "Import tasks"
+msgstr ""
+
+msgid "Import tasks from Phabricator into issues"
+msgstr ""
+
+msgid "Import timed out. Import took longer than %{import_jobs_expiration} seconds"
+msgstr ""
+
+msgid "Import/Export illustration"
+msgstr ""
+
+msgid "ImportButtons|Connect repositories from"
+msgstr ""
+
+msgid "ImportProjects|Blocked import URL: %{message}"
+msgstr ""
+
+msgid "ImportProjects|Error importing repository %{project_safe_import_url} into %{project_full_path} - %{message}"
+msgstr ""
+
+msgid "ImportProjects|Importing the project failed"
+msgstr ""
+
+msgid "ImportProjects|Requesting your %{provider} repositories failed"
+msgstr ""
+
+msgid "ImportProjects|Select the projects you want to import"
+msgstr ""
+
+msgid "ImportProjects|The remote data could not be imported."
+msgstr ""
+
+msgid "ImportProjects|The repository could not be created."
+msgstr ""
+
+msgid "ImportProjects|Update of imported projects with realtime changes failed"
+msgstr ""
+
+msgid "Improve Issue boards"
+msgstr ""
+
+msgid "Improve Issue boards with GitLab Enterprise Edition."
+msgstr ""
+
+msgid "Improve Merge Requests and customer support with GitLab Enterprise Edition."
+msgstr ""
+
+msgid "Improve search with Advanced Global Search and GitLab Enterprise Edition."
+msgstr ""
+
+msgid "In %{time_to_now}"
+msgstr ""
+
+msgid "In order to enable instance-level analytics, please ask an admin to enable %{usage_ping_link_start}usage ping%{usage_ping_link_end}."
+msgstr ""
+
+msgid "In order to gather accurate feature usage data, it can take 1 to 2 weeks to see your index."
+msgstr ""
+
+msgid "In order to personalize your experience with GitLab<br>we would like to know a bit more about you."
+msgstr ""
+
+msgid "In order to tailor your experience with GitLab we<br>would like to know a bit more about you."
+msgstr ""
+
+msgid "In progress"
+msgstr ""
+
+msgid "In the next step, you'll be able to select the projects you want to import."
+msgstr ""
+
+msgid "Incident Management Limits"
+msgstr ""
+
+msgid "Incidents"
+msgstr ""
+
+msgid "Include a Terms of Service agreement and Privacy Policy that all users must accept."
+msgstr ""
+
+msgid "Include author name in notification email body"
+msgstr ""
+
+msgid "Include description in commit message"
+msgstr ""
+
+msgid "Include merge request description"
+msgstr ""
+
+msgid "Include the username in the URL if required: <code>https://username@gitlab.company.com/group/project.git</code>."
+msgstr ""
+
+msgid "Includes LFS objects. It can be overridden per group, or per project. 0 for unlimited."
+msgstr ""
+
+msgid "Includes an MVC structure to help you get started."
+msgstr ""
+
+msgid "Includes an MVC structure, Gemfile, Rakefile, along with many others, to help you get started."
+msgstr ""
+
+msgid "Includes an MVC structure, mvnw and pom.xml to help you get started."
+msgstr ""
+
+msgid "Includes repository storage, wiki storage, LFS objects, build artifacts and packages. 0 for unlimited."
+msgstr ""
+
+msgid "Incoming email"
+msgstr ""
+
+msgid "Incoming!"
+msgstr ""
+
+msgid "Incompatible Project"
+msgstr ""
+
+msgid "Incompatible options set!"
+msgstr ""
+
+msgid "Incompatible project"
+msgstr ""
+
+msgid "Indent"
+msgstr ""
+
+msgid "Index all projects"
+msgstr ""
+
+msgid "Indicates whether this runner can pick jobs without tags"
+msgstr ""
+
+msgid "Inform users without uploaded SSH keys that they can't push over SSH until one is added"
+msgstr ""
+
+msgid "Information about additional Pages templates and how to install them can be found in our %{pages_getting_started_guide}."
+msgstr ""
+
+msgid "Inherited:"
+msgstr ""
+
+msgid "Inline"
+msgstr ""
+
+msgid "Input host keys manually"
+msgstr ""
+
+msgid "Input your repository URL"
+msgstr ""
+
+msgid "Insert"
+msgstr ""
+
+msgid "Insert a code block"
+msgstr ""
+
+msgid "Insert a quote"
+msgstr ""
+
+msgid "Insert an image"
+msgstr ""
+
+msgid "Insert code"
+msgstr ""
+
+msgid "Insert inline code"
+msgstr ""
+
+msgid "Insert suggestion"
+msgstr ""
+
+msgid "Insights"
+msgstr ""
+
+msgid "Insights|Some items are not visible beacuse the project was filtered out in the insights.yml file (see the projects.only config for more information)."
+msgstr ""
+
+msgid "Insights|This project is filtered out in the insights.yml file (see the projects.only config for more information)."
+msgstr ""
+
+msgid "Install"
+msgstr ""
+
+msgid "Install GitLab Runner"
+msgstr ""
+
+msgid "Install Runner on Kubernetes"
+msgstr ""
+
+msgid "Install a soft token authenticator like %{free_otp_link} or Google Authenticator from your application repository and use that app to scan this QR code. More information is available in the %{help_link_start}documentation%{help_link_end}."
+msgstr ""
+
+msgid "Install on clusters"
+msgstr ""
+
+msgid "Installed"
+msgstr ""
+
+msgid "Installing"
+msgstr ""
+
+msgid "Instance"
+msgid_plural "Instances"
+msgstr[0] ""
+
+msgid "Instance Configuration"
+msgstr ""
+
+msgid "Instance Statistics visibility"
+msgstr ""
+
+msgid "Instance administrators group already exists"
+msgstr ""
+
+msgid "Instance does not support multiple Kubernetes clusters"
+msgstr ""
+
+msgid "Instance license"
+msgstr ""
+
+msgid "Integration"
+msgstr ""
+
+msgid "Integration Settings"
+msgstr ""
+
+msgid "Integrations"
+msgstr ""
+
+msgid "Integrations|All details"
+msgstr ""
+
+msgid "Integrations|Comment detail:"
+msgstr ""
+
+msgid "Integrations|Comment settings:"
+msgstr ""
+
+msgid "Integrations|Enable comments"
+msgstr ""
+
+msgid "Integrations|Includes Standard plus entire commit message, commit hash, and issue IDs"
+msgstr ""
+
+msgid "Integrations|Includes commit title and branch"
+msgstr ""
+
+msgid "Integrations|Standard"
+msgstr ""
+
+msgid "Integrations|When a Jira issue is mentioned in a commit or merge request a remote link and comment (if enabled) will be created."
+msgstr ""
+
+msgid "Interested parties can even contribute by pushing commits if they want to."
+msgstr ""
+
+msgid "Internal"
+msgstr ""
+
+msgid "Internal - The group and any internal projects can be viewed by any logged in user."
+msgstr ""
+
+msgid "Internal - The project can be accessed by any logged in user."
+msgstr ""
+
+msgid "Internal URL (optional)"
+msgstr ""
+
+msgid "Internal users"
+msgstr ""
+
+msgid "Interval Pattern"
+msgstr ""
+
+msgid "Introducing Value Stream Analytics"
+msgstr ""
+
+msgid "Introducing Your DevOps Score"
+msgstr ""
+
+msgid "Invalid Git ref"
+msgstr ""
+
+msgid "Invalid Insights config file detected"
+msgstr ""
+
+msgid "Invalid Login or password"
+msgstr ""
+
+msgid "Invalid URL"
+msgstr ""
+
+msgid "Invalid container_name"
+msgstr ""
+
+msgid "Invalid cursor parameter"
+msgstr ""
+
+msgid "Invalid cursor value provided"
+msgstr ""
+
+msgid "Invalid date"
+msgstr ""
+
+msgid "Invalid date format. Please use UTC format as YYYY-MM-DD"
+msgstr ""
+
+msgid "Invalid date range"
+msgstr ""
+
+msgid "Invalid feature"
+msgstr ""
+
+msgid "Invalid field"
+msgstr ""
+
+msgid "Invalid file format with specified file type"
+msgstr ""
+
+msgid "Invalid file."
+msgstr ""
+
+msgid "Invalid import params"
+msgstr ""
+
+msgid "Invalid input, please avoid emojis"
+msgstr ""
+
+msgid "Invalid login or password"
+msgstr ""
+
+msgid "Invalid pin code"
+msgstr ""
+
+msgid "Invalid pod_name"
+msgstr ""
+
+msgid "Invalid query"
+msgstr ""
+
+msgid "Invalid repository bundle for snippet with id %{snippet_id}"
+msgstr ""
+
+msgid "Invalid repository path"
+msgstr ""
+
+msgid "Invalid search parameter"
+msgstr ""
+
+msgid "Invalid server response"
+msgstr ""
+
+msgid "Invalid start or end time format"
+msgstr ""
+
+msgid "Invalid status"
+msgstr ""
+
+msgid "Invalid two-factor code."
+msgstr ""
+
+msgid "Invalid yaml"
+msgstr ""
+
+msgid "Invitation"
+msgstr ""
+
+msgid "Invite"
+msgstr ""
+
+msgid "Invite \"%{trimmed}\" by email"
+msgstr ""
+
+msgid "Invite Members"
+msgstr ""
+
+msgid "Invite group"
+msgstr ""
+
+msgid "Invite member"
+msgstr ""
+
+msgid "Invocations"
+msgstr ""
+
+msgid "Is blocked by"
+msgstr ""
+
+msgid "Is this GitLab trial for your company?"
+msgstr ""
+
+msgid "Is using license seat:"
+msgstr ""
+
+msgid "Is using seat"
+msgstr ""
+
+msgid "IssuableStatus|Closed"
+msgstr ""
+
+msgid "IssuableStatus|Closed (%{link})"
+msgstr ""
+
+msgid "IssuableStatus|duplicated"
+msgstr ""
+
+msgid "IssuableStatus|moved"
+msgstr ""
+
+msgid "IssuableStatus|promoted"
+msgstr ""
+
+msgid "Issue"
+msgstr ""
+
+msgid "Issue %{issue_reference} has already been added to epic %{epic_reference}."
+msgstr ""
+
+msgid "Issue Boards"
+msgstr ""
+
+msgid "Issue already promoted to epic."
+msgstr ""
+
+msgid "Issue cannot be found."
+msgstr ""
+
+msgid "Issue events"
+msgstr ""
+
+msgid "Issue first depoloyed to production"
+msgstr ""
+
+msgid "Issue label"
+msgstr ""
+
+msgid "Issue or Merge Request ID is required"
+msgstr ""
+
+msgid "Issue published on status page."
+msgstr ""
+
+msgid "Issue template (optional)"
+msgstr ""
+
+msgid "Issue update failed"
+msgstr ""
+
+msgid "Issue was closed by %{name} %{reason}"
+msgstr ""
+
+msgid "Issue weight"
+msgstr ""
+
+msgid "IssueAnalytics|Age"
+msgstr ""
+
+msgid "IssueAnalytics|Assignees"
+msgstr ""
+
+msgid "IssueAnalytics|Due date"
+msgstr ""
+
+msgid "IssueAnalytics|Failed to load issues. Please try again."
+msgstr ""
+
+msgid "IssueAnalytics|Issue"
+msgstr ""
+
+msgid "IssueAnalytics|Milestone"
+msgstr ""
+
+msgid "IssueAnalytics|Opened by"
+msgstr ""
+
+msgid "IssueAnalytics|Status"
+msgstr ""
+
+msgid "IssueAnalytics|Weight"
+msgstr ""
+
+msgid "IssueBoards|Board"
+msgstr ""
+
+msgid "IssueBoards|Boards"
+msgstr ""
+
+msgid "IssueBoards|Create new board"
+msgstr ""
+
+msgid "IssueBoards|Delete board"
+msgstr ""
+
+msgid "IssueBoards|No matching boards found"
+msgstr ""
+
+msgid "IssueBoards|Some of your boards are hidden, activate a license to see them again."
+msgstr ""
+
+msgid "IssueBoards|Switch board"
+msgstr ""
+
+msgid "IssueTracker|Bugzilla issue tracker"
+msgstr ""
+
+msgid "IssueTracker|Custom issue tracker"
+msgstr ""
+
+msgid "IssueTracker|GitLab issue tracker"
+msgstr ""
+
+msgid "IssueTracker|Redmine issue tracker"
+msgstr ""
+
+msgid "IssueTracker|YouTrack issue tracker"
+msgstr ""
+
+msgid "Issues"
+msgstr ""
+
+msgid "Issues Analytics"
+msgstr ""
+
+msgid "Issues Rate Limits"
+msgstr ""
+
+msgid "Issues and Merge Requests"
+msgstr ""
+
+msgid "Issues can be bugs, tasks or ideas to be discussed. Also, issues are searchable and filterable."
+msgstr ""
+
+msgid "Issues closed"
+msgstr ""
+
+msgid "Issues referenced by merge requests and commits within the default branch will be closed automatically"
+msgstr ""
+
+msgid "Issues successfully imported with the label"
+msgstr ""
+
+msgid "Issues with comments, merge requests with diffs and comments, labels, milestones, snippets, and other project entities"
+msgstr ""
+
+msgid "Issues, merge requests, pushes, and comments."
+msgstr ""
+
+msgid "IssuesAnalytics|After you begin creating issues for your projects, we can start tracking and displaying metrics for them"
+msgstr ""
+
+msgid "IssuesAnalytics|Avg/Month:"
+msgstr ""
+
+msgid "IssuesAnalytics|Issues opened"
+msgstr ""
+
+msgid "IssuesAnalytics|Issues opened per month"
+msgstr ""
+
+msgid "IssuesAnalytics|Last 12 months"
+msgstr ""
+
+msgid "IssuesAnalytics|Sorry, your filter produced no results"
+msgstr ""
+
+msgid "IssuesAnalytics|There are no issues for the projects in your group"
+msgstr ""
+
+msgid "IssuesAnalytics|To widen your search, change or remove filters in the filter bar above"
+msgstr ""
+
+msgid "IssuesAnalytics|Total:"
+msgstr ""
+
+msgid "Issue|Title"
+msgstr ""
+
+msgid "It must have a header row and at least two columns: the first column is the issue title and the second column is the issue description. The separator is automatically detected."
+msgstr ""
+
+msgid "It seems like the Dependency Scanning job ran successfully, but no dependencies have been detected in your project."
+msgstr ""
+
+msgid "It seems that there is currently no available data for code coverage"
+msgstr ""
+
+msgid "It's you"
+msgstr ""
+
+msgid "Iteration"
+msgstr ""
+
+msgid "Iteration changed to"
+msgstr ""
+
+msgid "Iteration removed"
+msgstr ""
+
+msgid "Iteration updated"
+msgstr ""
+
+msgid "Iterations"
+msgstr ""
+
+msgid "Iteration|Dates cannot overlap with other existing Iterations"
+msgstr ""
+
+msgid "Iteration|cannot be in the past"
+msgstr ""
+
+msgid "Iteration|cannot be more than 500 years in the future"
+msgstr ""
+
+msgid "I’m familiar with the basics of project management and DevOps."
+msgstr ""
+
+msgid "I’m not very familiar with the basics of project management and DevOps."
+msgstr ""
+
+msgid "Jaeger URL"
+msgstr ""
+
+msgid "Jaeger tracing"
+msgstr ""
+
+msgid "Jan"
+msgstr ""
+
+msgid "January"
+msgstr ""
+
+msgid "Jira Issues"
+msgstr ""
+
+msgid "Jira import is already running."
+msgstr ""
+
+msgid "Jira integration not configured."
+msgstr ""
+
+msgid "Jira project key is not configured"
+msgstr ""
+
+msgid "Jira project: %{importProject}"
+msgstr ""
+
+msgid "Jira service not configured."
+msgstr ""
+
+msgid "JiraService| on branch %{branch_link}"
+msgstr ""
+
+msgid "JiraService|%{user_link} mentioned this issue in %{entity_link} of %{project_link}%{branch}:{quote}%{entity_message}{quote}"
+msgstr ""
+
+msgid "JiraService|Events for %{noteable_model_name} are disabled."
+msgstr ""
+
+msgid "JiraService|If different from Web URL"
+msgstr ""
+
+msgid "JiraService|Jira API URL"
+msgstr ""
+
+msgid "JiraService|Jira comments will be created when an issue gets referenced in a commit."
+msgstr ""
+
+msgid "JiraService|Jira comments will be created when an issue gets referenced in a merge request."
+msgstr ""
+
+msgid "JiraService|Jira issue tracker"
+msgstr ""
+
+msgid "JiraService|Password or API token"
+msgstr ""
+
+msgid "JiraService|Transition ID(s)"
+msgstr ""
+
+msgid "JiraService|Use , or ; to separate multiple transition IDs"
+msgstr ""
+
+msgid "JiraService|Use a password for server version and an API token for cloud version"
+msgstr ""
+
+msgid "JiraService|Use a username for server version and an email for cloud version"
+msgstr ""
+
+msgid "JiraService|Username or Email"
+msgstr ""
+
+msgid "JiraService|Web URL"
+msgstr ""
+
+msgid "JiraService|transition ids can have only numbers which can be split with , or ;"
+msgstr ""
+
+msgid "Job"
+msgstr ""
+
+msgid "Job Failed #%{build_id}"
+msgstr ""
+
+msgid "Job ID"
+msgstr ""
+
+msgid "Job has been erased"
+msgstr ""
+
+msgid "Job has been successfully erased!"
+msgstr ""
+
+msgid "Job has wrong arguments format."
+msgstr ""
+
+msgid "Job is missing the `model_type` argument."
+msgstr ""
+
+msgid "Job is stuck. Check runners."
+msgstr ""
+
+msgid "Job logs and artifacts"
+msgstr ""
+
+msgid "Job to create self-monitoring project is in progress"
+msgstr ""
+
+msgid "Job to delete self-monitoring project is in progress"
+msgstr ""
+
+msgid "Job was retried"
+msgstr ""
+
+msgid "Jobs"
+msgstr ""
+
+msgid "Job|Browse"
+msgstr ""
+
+msgid "Job|Complete Raw"
+msgstr ""
+
+msgid "Job|Download"
+msgstr ""
+
+msgid "Job|Erase job log"
+msgstr ""
+
+msgid "Job|Job artifacts"
+msgstr ""
+
+msgid "Job|Job has been erased"
+msgstr ""
+
+msgid "Job|Job has been erased by"
+msgstr ""
+
+msgid "Job|Keep"
+msgstr ""
+
+msgid "Job|Pipeline"
+msgstr ""
+
+msgid "Job|Scroll to bottom"
+msgstr ""
+
+msgid "Job|Scroll to top"
+msgstr ""
+
+msgid "Job|Show complete raw"
+msgstr ""
+
+msgid "Job|The artifacts were removed"
+msgstr ""
+
+msgid "Job|The artifacts will be removed"
+msgstr ""
+
+msgid "Job|These artifacts are the latest. They will not be deleted (even if expired) until newer artifacts are available."
+msgstr ""
+
+msgid "Job|This job failed because the necessary resources were not successfully created."
+msgstr ""
+
+msgid "Job|This job is stuck because the project doesn't have any runners online assigned to it."
+msgstr ""
+
+msgid "Job|for"
+msgstr ""
+
+msgid "Job|into"
+msgstr ""
+
+msgid "Job|with"
+msgstr ""
+
+msgid "Join Zoom meeting"
+msgstr ""
+
+msgid "Jul"
+msgstr ""
+
+msgid "July"
+msgstr ""
+
+msgid "Jump to first unresolved thread"
+msgstr ""
+
+msgid "Jump to next unresolved thread"
+msgstr ""
+
+msgid "Jun"
+msgstr ""
+
+msgid "June"
+msgstr ""
+
+msgid "Just me"
+msgstr ""
+
+msgid "Keep divergent refs"
+msgstr ""
+
+msgid "Kerberos access denied"
+msgstr ""
+
+msgid "Key"
+msgstr ""
+
+msgid "Key (PEM)"
+msgstr ""
+
+msgid "Key: %{key}"
+msgstr ""
+
+msgid "Keyboard shortcuts"
+msgstr ""
+
+msgid "Keys"
+msgstr ""
+
+msgid "Ki"
+msgstr ""
+
+msgid "Kubernetes"
+msgstr ""
+
+msgid "Kubernetes API returned status code: %{error_code}"
+msgstr ""
+
+msgid "Kubernetes Cluster"
+msgstr ""
+
+msgid "Kubernetes Clusters"
+msgstr ""
+
+msgid "Kubernetes cluster"
+msgstr ""
+
+msgid "Kubernetes cluster creation time exceeds timeout; %{timeout}"
+msgstr ""
+
+msgid "Kubernetes cluster integration and resources are being removed."
+msgstr ""
+
+msgid "Kubernetes cluster integration was successfully removed."
+msgstr ""
+
+msgid "Kubernetes cluster was successfully updated."
+msgstr ""
+
+msgid "Kubernetes deployment not found"
+msgstr ""
+
+msgid "Kubernetes error: %{error_code}"
+msgstr ""
+
+msgid "Kubernetes popover"
+msgstr ""
+
+msgid "LDAP"
+msgstr ""
+
+msgid "LDAP Synchronization"
+msgstr ""
+
+msgid "LDAP settings"
+msgstr ""
+
+msgid "LDAP settings updated"
+msgstr ""
+
+msgid "LDAP sync in progress. This could take a few minutes. Refresh the page to see the changes."
+msgstr ""
+
+msgid "LFS"
+msgstr ""
+
+msgid "LFS objects"
+msgstr ""
+
+msgid "LFSStatus|Disabled"
+msgstr ""
+
+msgid "LFSStatus|Enabled"
+msgstr ""
+
+msgid "LICENSE"
+msgstr ""
+
+msgid "Label"
+msgstr ""
+
+msgid "Label actions dropdown"
+msgstr ""
+
+msgid "Label lists show all issues with the selected label."
+msgstr ""
+
+msgid "Label was created"
+msgstr ""
+
+msgid "Label was removed"
+msgstr ""
+
+msgid "Label was successfully updated."
+msgstr ""
+
+msgid "LabelSelect|%{firstLabelName} +%{remainingLabelCount} more"
+msgstr ""
+
+msgid "LabelSelect|%{labelsString}, and %{remainingLabelCount} more"
+msgstr ""
+
+msgid "LabelSelect|Labels"
+msgstr ""
+
+msgid "Labels"
+msgstr ""
+
+msgid "Labels can be applied to %{features}. Group labels are available for any project within the group."
+msgstr ""
+
+msgid "Labels can be applied to issues and merge requests to categorize them."
+msgstr ""
+
+msgid "Labels can be applied to issues and merge requests."
+msgstr ""
+
+msgid "Labels|<span>Promote label</span> %{labelTitle} <span>to Group Label?</span>"
+msgstr ""
+
+msgid "Labels|Promote Label"
+msgstr ""
+
+msgid "Labels|Promoting %{labelTitle} will make it available for all projects inside %{groupName}. Existing project labels with the same title will be merged. If a group label with the same title exists, it will also be merged. This action cannot be reversed."
+msgstr ""
+
+msgid "Labels|and %{count} more"
+msgstr ""
+
+msgid "Language"
+msgstr ""
+
+msgid "Large File Storage"
+msgstr ""
+
+msgid "Last %d day"
+msgid_plural "Last %d days"
+msgstr[0] ""
+
+msgid "Last %{days} days"
+msgstr ""
+
+msgid "Last Accessed On"
+msgstr ""
+
+msgid "Last Name is too long (maximum is %{max_length} characters)."
+msgstr ""
+
+msgid "Last Pipeline"
+msgstr ""
+
+msgid "Last Seen"
+msgstr ""
+
+msgid "Last accessed on"
+msgstr ""
+
+msgid "Last activity"
+msgstr ""
+
+msgid "Last commit"
+msgstr ""
+
+msgid "Last contact"
+msgstr ""
+
+msgid "Last edited %{date}"
+msgstr ""
+
+msgid "Last edited by %{name}"
+msgstr ""
+
+msgid "Last name"
+msgstr ""
+
+msgid "Last reply by"
+msgstr ""
+
+msgid "Last repository check (%{last_check_timestamp}) failed. See the 'repocheck.log' file for error messages."
+msgstr ""
+
+msgid "Last repository check run"
+msgstr ""
+
+msgid "Last seen"
+msgstr ""
+
+msgid "Last successful sync"
+msgstr ""
+
+msgid "Last successful update"
+msgstr ""
+
+msgid "Last time verified"
+msgstr ""
+
+msgid "Last update"
+msgstr ""
+
+msgid "Last update attempt"
+msgstr ""
+
+msgid "Last updated"
+msgstr ""
+
+msgid "Last used"
+msgstr ""
+
+msgid "Last used on:"
+msgstr ""
+
+msgid "LastCommit|authored"
+msgstr ""
+
+msgid "LastPushEvent|You pushed to"
+msgstr ""
+
+msgid "LastPushEvent|at"
+msgstr ""
+
+msgid "Latest changes"
+msgstr ""
+
+msgid "Latest pipeline for the most recent commit on this branch"
+msgstr ""
+
+msgid "Lead"
+msgstr ""
+
+msgid "Lead Time"
+msgstr ""
+
+msgid "Learn GitLab"
+msgstr ""
+
+msgid "Learn More"
+msgstr ""
+
+msgid "Learn how to %{link_start}contribute to the built-in templates%{link_end}"
+msgstr ""
+
+msgid "Learn how to %{no_packages_link_start}publish and share your packages%{no_packages_link_end} with GitLab."
+msgstr ""
+
+msgid "Learn how to enable synchronization"
+msgstr ""
+
+msgid "Learn more"
+msgstr ""
+
+msgid "Learn more about Auto DevOps"
+msgstr ""
+
+msgid "Learn more about Kubernetes"
+msgstr ""
+
+msgid "Learn more about License-Check"
+msgstr ""
+
+msgid "Learn more about Vulnerability-Check"
+msgstr ""
+
+msgid "Learn more about Web Terminal"
+msgstr ""
+
+msgid "Learn more about X.509 signed commits"
+msgstr ""
+
+msgid "Learn more about adding certificates to your project by following the %{docs_link_start}documentation on GitLab Pages%{docs_link_end}."
+msgstr ""
+
+msgid "Learn more about approvals."
+msgstr ""
+
+msgid "Learn more about custom project templates"
+msgstr ""
+
+msgid "Learn more about deploying to AWS"
+msgstr ""
+
+msgid "Learn more about deploying to a cluster"
+msgstr ""
+
+msgid "Learn more about group-level project templates"
+msgstr ""
+
+msgid "Learn more about job dependencies"
+msgstr ""
+
+msgid "Learn more about signing commits"
+msgstr ""
+
+msgid "Learn more about the dependency list"
+msgstr ""
+
+msgid "Learn more in the"
+msgstr ""
+
+msgid "Learn more in the|pipeline schedules documentation"
+msgstr ""
+
+msgid "Leave"
+msgstr ""
+
+msgid "Leave Admin Mode"
+msgstr ""
+
+msgid "Leave blank for no limit. Once set, existing personal access tokens may be revoked."
+msgstr ""
+
+msgid "Leave edit mode? All unsaved changes will be lost."
+msgstr ""
+
+msgid "Leave group"
+msgstr ""
+
+msgid "Leave project"
+msgstr ""
+
+msgid "Leave the \"File type\" and \"Delivery method\" options on their default values."
+msgstr ""
+
+msgid "Leave zen mode"
+msgstr ""
+
+msgid "Let's Encrypt does not accept emails on example.com"
+msgstr ""
+
+msgid "Let's Encrypt is a free, automated, and open certificate authority (CA) that gives digital certificates in order to enable HTTPS (SSL/TLS) for websites. Learn more about Let's Encrypt configuration by following the %{docs_link_start}documentation on GitLab Pages%{docs_link_end}."
+msgstr ""
+
+msgid "License"
+msgstr ""
+
+msgid "License Compliance"
+msgstr ""
+
+msgid "License History"
+msgstr ""
+
+msgid "License ID:"
+msgstr ""
+
+msgid "License URL"
+msgstr ""
+
+msgid "License-Check"
+msgstr ""
+
+msgid "LicenseCompliance|%{docLinkStart}License Approvals%{docLinkEnd} are active"
+msgstr ""
+
+msgid "LicenseCompliance|%{docLinkStart}License Approvals%{docLinkEnd} are inactive"
+msgstr ""
+
+msgid "LicenseCompliance|Add a license"
+msgstr ""
+
+msgid "LicenseCompliance|Add license and related policy"
+msgstr ""
+
+msgid "LicenseCompliance|Allow"
+msgstr ""
+
+msgid "LicenseCompliance|Allowed"
+msgstr ""
+
+msgid "LicenseCompliance|Cancel"
+msgstr ""
+
+msgid "LicenseCompliance|Denied"
+msgstr ""
+
+msgid "LicenseCompliance|Deny"
+msgstr ""
+
+msgid "LicenseCompliance|Learn more about %{linkStart}License Approvals%{linkEnd}"
+msgstr ""
+
+msgid "LicenseCompliance|License"
+msgstr ""
+
+msgid "LicenseCompliance|License Approvals"
+msgstr ""
+
+msgid "LicenseCompliance|License Compliance detected %d license and policy violation for the source branch only; approval required"
+msgid_plural "LicenseCompliance|License Compliance detected %d licenses and policy violations for the source branch only; approval required"
+msgstr[0] ""
+
+msgid "LicenseCompliance|License Compliance detected %d license for the source branch only"
+msgid_plural "LicenseCompliance|License Compliance detected %d licenses for the source branch only"
+msgstr[0] ""
+
+msgid "LicenseCompliance|License Compliance detected %d new license"
+msgid_plural "LicenseCompliance|License Compliance detected %d new licenses"
+msgstr[0] ""
+
+msgid "LicenseCompliance|License Compliance detected %d new license and policy violation; approval required"
+msgid_plural "LicenseCompliance|License Compliance detected %d new licenses and policy violations; approval required"
+msgstr[0] ""
+
+msgid "LicenseCompliance|License Compliance detected no licenses for the source branch only"
+msgstr ""
+
+msgid "LicenseCompliance|License Compliance detected no new licenses"
+msgstr ""
+
+msgid "LicenseCompliance|License details"
+msgstr ""
+
+msgid "LicenseCompliance|License name"
+msgstr ""
+
+msgid "LicenseCompliance|License review"
+msgstr ""
+
+msgid "LicenseCompliance|Packages"
+msgstr ""
+
+msgid "LicenseCompliance|Remove license"
+msgstr ""
+
+msgid "LicenseCompliance|Remove license?"
+msgstr ""
+
+msgid "LicenseCompliance|Submit"
+msgstr ""
+
+msgid "LicenseCompliance|There are currently no approved or blacklisted licenses in this project."
+msgstr ""
+
+msgid "LicenseCompliance|There are currently no approved or blacklisted licenses that match in this project."
+msgstr ""
+
+msgid "LicenseCompliance|This license already exists in this project."
+msgstr ""
+
+msgid "LicenseCompliance|URL"
+msgstr ""
+
+msgid "LicenseCompliance|You are about to remove the license, %{name}, from this project."
+msgstr ""
+
+msgid "LicenseManagement|Allowed"
+msgstr ""
+
+msgid "LicenseManagement|Denied"
+msgstr ""
+
+msgid "LicenseManagement|Uncategorized"
+msgstr ""
+
+msgid "Licensed Features"
+msgstr ""
+
+msgid "Licensed to"
+msgstr ""
+
+msgid "Licenses"
+msgstr ""
+
+msgid "Licenses|%{remainingComponentsCount} more"
+msgstr ""
+
+msgid "Licenses|Component"
+msgstr ""
+
+msgid "Licenses|Components"
+msgstr ""
+
+msgid "Licenses|Detected in Project"
+msgstr ""
+
+msgid "Licenses|Detected licenses that are out-of-compliance with the project's assigned policies"
+msgstr ""
+
+msgid "Licenses|Displays licenses detected in the project, based on the %{linkStart}latest successful%{linkEnd} scan"
+msgstr ""
+
+msgid "Licenses|Error fetching the license list. Please check your network connection and try again."
+msgstr ""
+
+msgid "Licenses|Learn more about license compliance"
+msgstr ""
+
+msgid "Licenses|License Compliance"
+msgstr ""
+
+msgid "Licenses|Name"
+msgstr ""
+
+msgid "Licenses|Policies"
+msgstr ""
+
+msgid "Licenses|Policy"
+msgstr ""
+
+msgid "Licenses|Policy violation: denied"
+msgstr ""
+
+msgid "Licenses|Specified policies in this project"
+msgstr ""
+
+msgid "Licenses|The license list details information about the licenses used within your project."
+msgstr ""
+
+msgid "Licenses|View license details for your project"
+msgstr ""
+
+msgid "License|Buy license"
+msgstr ""
+
+msgid "License|License"
+msgstr ""
+
+msgid "License|Licensed user count exceeded"
+msgstr ""
+
+msgid "License|You can restore access to the Gold features at any time by upgrading."
+msgstr ""
+
+msgid "License|You can start a free trial of GitLab Ultimate without any obligation or payment details."
+msgstr ""
+
+msgid "License|You do not have a license."
+msgstr ""
+
+msgid "License|Your License"
+msgstr ""
+
+msgid "License|Your free trial of GitLab Ultimate expired on %{trial_ends_on}."
+msgstr ""
+
+msgid "License|Your instance has exceeded your subscription's number of licensed users by %{extra_users_count}. You can continue to add more users and we'll include the overage in your next bill."
+msgstr ""
+
+msgid "Limit display of time tracking units to hours."
+msgstr ""
+
+msgid "Limit namespaces and projects that can be indexed"
+msgstr ""
+
+msgid "Limited to showing %d event at most"
+msgid_plural "Limited to showing %d events at most"
+msgstr[0] ""
+
+msgid "Line changes"
+msgstr ""
+
+msgid "Link Prometheus monitoring to GitLab."
+msgstr ""
+
+msgid "Link copied"
+msgstr ""
+
+msgid "Link title"
+msgstr ""
+
+msgid "Link title is required"
+msgstr ""
+
+msgid "Linked emails (%{email_count})"
+msgstr ""
+
+msgid "Linked issues"
+msgstr ""
+
+msgid "LinkedIn"
+msgstr ""
+
+msgid "LinkedPipelines|%{counterLabel} more downstream pipelines"
+msgstr ""
+
+msgid "Links"
+msgstr ""
+
+msgid "List"
+msgstr ""
+
+msgid "List Your Gitea Repositories"
+msgstr ""
+
+msgid "List available repositories"
+msgstr ""
+
+msgid "List of IPs and CIDRs of allowed secondary nodes. Comma-separated, e.g. \"1.1.1.1, 2.2.2.0/24\""
+msgstr ""
+
+msgid "List settings"
+msgstr ""
+
+msgid "List the merge requests that must be merged before this one."
+msgstr ""
+
+msgid "List view"
+msgstr ""
+
+msgid "List your Bitbucket Server repositories"
+msgstr ""
+
+msgid "Lists"
+msgstr ""
+
+msgid "Live preview"
+msgstr ""
+
+msgid "Loading"
+msgstr ""
+
+msgid "Loading contribution stats for group members"
+msgstr ""
+
+msgid "Loading files, directories, and submodules in the path %{path} for commit reference %{ref}"
+msgstr ""
+
+msgid "Loading functions timed out. Please reload the page to try again."
+msgstr ""
+
+msgid "Loading issues"
+msgstr ""
+
+msgid "Loading snippet"
+msgstr ""
+
+msgid "Loading the GitLab IDE..."
+msgstr ""
+
+msgid "Loading..."
+msgstr ""
+
+msgid "Loading…"
+msgstr ""
+
+msgid "Local IP addresses and domain names that hooks and services may access."
+msgstr ""
+
+msgid "Localization"
+msgstr ""
+
+msgid "Location"
+msgstr ""
+
+msgid "Lock"
+msgstr ""
+
+msgid "Lock %{issuableDisplayName}"
+msgstr ""
+
+msgid "Lock memberships to LDAP synchronization"
+msgstr ""
+
+msgid "Lock not found"
+msgstr ""
+
+msgid "Lock the discussion"
+msgstr ""
+
+msgid "Lock this %{issuableDisplayName}? Only <strong>project members</strong> will be able to comment."
+msgstr ""
+
+msgid "Lock to current projects"
+msgstr ""
+
+msgid "Locked"
+msgstr ""
+
+msgid "Locked Files"
+msgstr ""
+
+msgid "Locked by %{fileLockUserName}"
+msgstr ""
+
+msgid "Locked the discussion."
+msgstr ""
+
+msgid "Locked to current projects"
+msgstr ""
+
+msgid "Locks give the ability to lock specific file or folder."
+msgstr ""
+
+msgid "Locks the discussion."
+msgstr ""
+
+msgid "Login with smartcard"
+msgstr ""
+
+msgid "Logo was successfully removed."
+msgstr ""
+
+msgid "Logs"
+msgstr ""
+
+msgid "Logs|To see the logs, deploy your code to an environment."
+msgstr ""
+
+msgid "Low vulnerabilities present"
+msgstr ""
+
+msgid "MB"
+msgstr ""
+
+msgid "MD5"
+msgstr ""
+
+msgid "MERGED"
+msgstr ""
+
+msgid "MR widget|Take a look at our %{beginnerLinkStart}Beginner's Guide to Continuous Integration%{beginnerLinkEnd} and our %{exampleLinkStart}examples of GitLab CI/CD%{exampleLinkEnd} to see all the cool stuff you can do with it."
+msgstr ""
+
+msgid "MR widget|The pipeline will now run automatically every time you commit code. Pipelines are useful for deploying static web pages, detecting vulnerabilities in dependencies, static or dynamic application security testing (SAST and DAST), and so much more!"
+msgstr ""
+
+msgid "MRApprovals|Approvals"
+msgstr ""
+
+msgid "MRApprovals|Approved by"
+msgstr ""
+
+msgid "MRApprovals|Approvers"
+msgstr ""
+
+msgid "MRDiff|Show changes only"
+msgstr ""
+
+msgid "MRDiff|Show full file"
+msgstr ""
+
+msgid "Made this issue confidential."
+msgstr ""
+
+msgid "Maintenance mode"
+msgstr ""
+
+msgid "Make and review changes in the browser with the Web IDE"
+msgstr ""
+
+msgid "Make everyone on your team more productive regardless of their location. GitLab Geo creates read-only mirrors of your GitLab instance so you can reduce the time it takes to clone and fetch large repos."
+msgstr ""
+
+msgid "Make issue confidential"
+msgstr ""
+
+msgid "Make sure you save it - you won't be able to access it again."
+msgstr ""
+
+msgid "Make sure you're logged into the account that owns the projects you'd like to import."
+msgstr ""
+
+msgid "Make this epic confidential"
+msgstr ""
+
+msgid "Makes this issue confidential."
+msgstr ""
+
+msgid "Manage"
+msgstr ""
+
+msgid "Manage Web IDE features"
+msgstr ""
+
+msgid "Manage access"
+msgstr ""
+
+msgid "Manage all notifications"
+msgstr ""
+
+msgid "Manage applications that can use GitLab as an OAuth provider, and applications that you've authorized to use your account."
+msgstr ""
+
+msgid "Manage applications that you've authorized to use your account."
+msgstr ""
+
+msgid "Manage group labels"
+msgstr ""
+
+msgid "Manage labels"
+msgstr ""
+
+msgid "Manage milestones"
+msgstr ""
+
+msgid "Manage project labels"
+msgstr ""
+
+msgid "Manage storage usage"
+msgstr ""
+
+msgid "Manage two-factor authentication"
+msgstr ""
+
+msgid "Manage your license"
+msgstr ""
+
+msgid "Managed Account"
+msgstr ""
+
+msgid "Manifest"
+msgstr ""
+
+msgid "Manifest file import"
+msgstr ""
+
+msgid "Manifest import"
+msgstr ""
+
+msgid "Manual job"
+msgstr ""
+
+msgid "ManualOrdering|Couldn't save the order of the issues"
+msgstr ""
+
+msgid "Map a FogBugz account ID to a GitLab user"
+msgstr ""
+
+msgid "Map a Google Code user to a GitLab user"
+msgstr ""
+
+msgid "Map a Google Code user to a full email address"
+msgstr ""
+
+msgid "Map a Google Code user to a full name"
+msgstr ""
+
+msgid "Mar"
+msgstr ""
+
+msgid "March"
+msgstr ""
+
+msgid "Mark To Do as done"
+msgstr ""
+
+msgid "Mark as done"
+msgstr ""
+
+msgid "Mark as resolved"
+msgstr ""
+
+msgid "Mark this issue as a duplicate of another issue"
+msgstr ""
+
+msgid "Mark this issue as related to another issue"
+msgstr ""
+
+msgid "Markdown"
+msgstr ""
+
+msgid "Markdown enabled"
+msgstr ""
+
+msgid "Markdown is supported"
+msgstr ""
+
+msgid "Marked To Do as done."
+msgstr ""
+
+msgid "Marked this %{noun} as Work In Progress."
+msgstr ""
+
+msgid "Marked this issue as a duplicate of %{duplicate_param}."
+msgstr ""
+
+msgid "Marked this issue as related to %{issue_ref}."
+msgstr ""
+
+msgid "Marks To Do as done."
+msgstr ""
+
+msgid "Marks this %{noun} as Work In Progress."
+msgstr ""
+
+msgid "Marks this issue as a duplicate of %{duplicate_reference}."
+msgstr ""
+
+msgid "Marks this issue as related to %{issue_ref}."
+msgstr ""
+
+msgid "Mask variable"
+msgstr ""
+
+msgid "Match not found; try refining your search query."
+msgstr ""
+
+msgid "MattermostService|Add to Mattermost"
+msgstr ""
+
+msgid "MattermostService|Command trigger word"
+msgstr ""
+
+msgid "MattermostService|Fill in the word that works best for your team."
+msgstr ""
+
+msgid "MattermostService|Request URL"
+msgstr ""
+
+msgid "MattermostService|Request method"
+msgstr ""
+
+msgid "MattermostService|Response icon"
+msgstr ""
+
+msgid "MattermostService|Response username"
+msgstr ""
+
+msgid "MattermostService|See list of available commands in Mattermost after setting up this service, by entering"
+msgstr ""
+
+msgid "MattermostService|Suggestions:"
+msgstr ""
+
+msgid "MattermostService|This service allows users to perform common operations on this project by entering slash commands in Mattermost."
+msgstr ""
+
+msgid "Maven Metadata"
+msgstr ""
+
+msgid "Max access level"
+msgstr ""
+
+msgid "Max seats used"
+msgstr ""
+
+msgid "Maximum Users:"
+msgstr ""
+
+msgid "Maximum allowable lifetime for personal access token (days)"
+msgstr ""
+
+msgid "Maximum artifacts size (MB)"
+msgstr ""
+
+msgid "Maximum attachment size (MB)"
+msgstr ""
+
+msgid "Maximum bulk request size (MiB)"
+msgstr ""
+
+msgid "Maximum capacity"
+msgstr ""
+
+msgid "Maximum concurrency of Elasticsearch bulk requests per indexing operation."
+msgstr ""
+
+msgid "Maximum delay (Minutes)"
+msgstr ""
+
+msgid "Maximum duration of a session."
+msgstr ""
+
+msgid "Maximum field length"
+msgstr ""
+
+msgid "Maximum file size is 2MB. Please select a smaller file."
+msgstr ""
+
+msgid "Maximum import size (MB)"
+msgstr ""
+
+msgid "Maximum job timeout"
+msgstr ""
+
+msgid "Maximum job timeout has a value which could not be accepted"
+msgstr ""
+
+msgid "Maximum lifetime allowable for Personal Access Tokens is active, your expire date must be set before %{maximum_allowable_date}."
+msgstr ""
+
+msgid "Maximum namespace storage (MB)"
+msgstr ""
+
+msgid "Maximum number of %{name} (%{count}) exceeded"
+msgstr ""
+
+msgid "Maximum number of comments exceeded"
+msgstr ""
+
+msgid "Maximum number of mirrors that can be synchronizing at the same time."
+msgstr ""
+
+msgid "Maximum number of projects."
+msgstr ""
+
+msgid "Maximum page reached"
+msgstr ""
+
+msgid "Maximum push size (MB)"
+msgstr ""
+
+msgid "Maximum size limit for a single commit."
+msgstr ""
+
+msgid "Maximum size limit for each repository."
+msgstr ""
+
+msgid "Maximum size of Elasticsearch bulk indexing requests."
+msgstr ""
+
+msgid "Maximum size of import files."
+msgstr ""
+
+msgid "Maximum size of individual attachments in comments."
+msgstr ""
+
+msgid "Maximum time between updates that a mirror can have when scheduled to synchronize."
+msgstr ""
+
+msgid "May"
+msgstr ""
+
+msgid "Measured in bytes of code. Excludes generated and vendored code."
+msgstr ""
+
+msgid "Median"
+msgstr ""
+
+msgid "Medium vulnerabilities present"
+msgstr ""
+
+msgid "Member lock"
+msgstr ""
+
+msgid "Member since %{date}"
+msgstr ""
+
+msgid "Members"
+msgstr ""
+
+msgid "Members can be added by project <i>Maintainers</i> or <i>Owners</i>"
+msgstr ""
+
+msgid "Members of <strong>%{project_name}</strong>"
+msgstr ""
+
+msgid "Members of a group may only view projects they have permission to access"
+msgstr ""
+
+msgid "Members with access to %{strong_start}%{group_name}%{strong_end}"
+msgstr ""
+
+msgid "Members with pending access to %{strong_start}%{group_name}%{strong_end}"
+msgstr ""
+
+msgid "Memory Usage"
+msgstr ""
+
+msgid "Merge"
+msgstr ""
+
+msgid "Merge (when the pipeline succeeds)"
+msgstr ""
+
+msgid "Merge Conflicts"
+msgstr ""
+
+msgid "Merge Request"
+msgstr ""
+
+msgid "Merge Request Approvals"
+msgstr ""
+
+msgid "Merge Request Commits"
+msgstr ""
+
+msgid "Merge Requests"
+msgstr ""
+
+msgid "Merge Requests created"
+msgstr ""
+
+msgid "Merge Requests in Review"
+msgstr ""
+
+msgid "Merge automatically (%{strategy})"
+msgstr ""
+
+msgid "Merge commit message"
+msgstr ""
+
+msgid "Merge events"
+msgstr ""
+
+msgid "Merge immediately"
+msgstr ""
+
+msgid "Merge in progress"
+msgstr ""
+
+msgid "Merge options"
+msgstr ""
+
+msgid "Merge request"
+msgstr ""
+
+msgid "Merge request %{iid} authored by %{authorName}"
+msgstr ""
+
+msgid "Merge request %{mr_link} was reviewed by %{mr_author}"
+msgstr ""
+
+msgid "Merge request approvals"
+msgstr ""
+
+msgid "Merge request approvals allow you to set the number of necessary approvals and predefine a list of approvers that will need to approve every merge request in a project."
+msgstr ""
+
+msgid "Merge request dependencies"
+msgstr ""
+
+msgid "Merge requests"
+msgstr ""
+
+msgid "Merge requests approvals"
+msgstr ""
+
+msgid "Merge requests are a place to propose changes you've made to a project and discuss those changes with others"
+msgstr ""
+
+msgid "Merge requests are read-only in a secondary Geo node"
+msgstr ""
+
+msgid "Merge when pipeline succeeds"
+msgstr ""
+
+msgid "MergeConflict|Commit to source branch"
+msgstr ""
+
+msgid "MergeConflict|Committing..."
+msgstr ""
+
+msgid "MergeConflict|HEAD//our changes"
+msgstr ""
+
+msgid "MergeConflict|Use ours"
+msgstr ""
+
+msgid "MergeConflict|Use theirs"
+msgstr ""
+
+msgid "MergeConflict|conflict"
+msgstr ""
+
+msgid "MergeConflict|conflicts"
+msgstr ""
+
+msgid "MergeConflict|origin//their changes"
+msgstr ""
+
+msgid "MergeRequestDiffs|Commenting on lines %{selectStart}start%{selectEnd} to %{end}"
+msgstr ""
+
+msgid "MergeRequestDiffs|Select comment starting line"
+msgstr ""
+
+msgid "MergeRequests|Add a reply"
+msgstr ""
+
+msgid "MergeRequests|An error occurred while checking whether another squash is in progress."
+msgstr ""
+
+msgid "MergeRequests|An error occurred while saving the draft comment."
+msgstr ""
+
+msgid "MergeRequests|Failed to squash. Should be done manually."
+msgstr ""
+
+msgid "MergeRequests|Jump to next unresolved thread"
+msgstr ""
+
+msgid "MergeRequests|Reply..."
+msgstr ""
+
+msgid "MergeRequests|Resolve this thread in a new issue"
+msgstr ""
+
+msgid "MergeRequests|Saving the comment failed"
+msgstr ""
+
+msgid "MergeRequests|Squash task canceled: another squash is already in progress."
+msgstr ""
+
+msgid "MergeRequests|This project does not allow squashing commits when merge requests are accepted."
+msgstr ""
+
+msgid "MergeRequests|Thread stays resolved"
+msgstr ""
+
+msgid "MergeRequests|Thread stays unresolved"
+msgstr ""
+
+msgid "MergeRequests|Thread will be resolved"
+msgstr ""
+
+msgid "MergeRequests|Thread will be unresolved"
+msgstr ""
+
+msgid "MergeRequests|Toggle comments for this file"
+msgstr ""
+
+msgid "MergeRequests|View file @ %{commitId}"
+msgstr ""
+
+msgid "MergeRequests|View replaced file @ %{commitId}"
+msgstr ""
+
+msgid "MergeRequests|commented on commit %{commitLink}"
+msgstr ""
+
+msgid "MergeRequests|started a thread"
+msgstr ""
+
+msgid "MergeRequests|started a thread on %{linkStart}an old version of the diff%{linkEnd}"
+msgstr ""
+
+msgid "MergeRequests|started a thread on %{linkStart}the diff%{linkEnd}"
+msgstr ""
+
+msgid "MergeRequests|started a thread on an outdated change in commit %{linkStart}%{commitDisplay}%{linkEnd}"
+msgstr ""
+
+msgid "MergeRequests|started a thread on commit %{linkStart}%{commitDisplay}%{linkEnd}"
+msgstr ""
+
+msgid "MergeRequest|Compare %{target} and %{source}"
+msgstr ""
+
+msgid "MergeRequest|Error dismissing suggestion popover. Please try again."
+msgstr ""
+
+msgid "MergeRequest|Error loading full diff. Please try again."
+msgstr ""
+
+msgid "MergeRequest|No files found"
+msgstr ""
+
+msgid "MergeRequest|Search files (%{modifier_key}P)"
+msgstr ""
+
+msgid "Merged"
+msgstr ""
+
+msgid "Merged MRs"
+msgstr ""
+
+msgid "Merged branches are being deleted. This can take some time depending on the number of branches. Please refresh the page to see changes."
+msgstr ""
+
+msgid "Merged this merge request."
+msgstr ""
+
+msgid "Merges this merge request immediately."
+msgstr ""
+
+msgid "Merges this merge request when the pipeline succeeds."
+msgstr ""
+
+msgid "Merging immediately isn't recommended as it may negatively impact the existing merge train. Read the %{docsLinkStart}documentation%{docsLinkEnd} for more information."
+msgstr ""
+
+msgid "Messages"
+msgstr ""
+
+msgid "Method"
+msgstr ""
+
+msgid "Metric was successfully added."
+msgstr ""
+
+msgid "Metric was successfully updated."
+msgstr ""
+
+msgid "Metric:"
+msgstr ""
+
+msgid "MetricChart|Please select a metric"
+msgstr ""
+
+msgid "MetricChart|Selected"
+msgstr ""
+
+msgid "Metrics"
+msgstr ""
+
+msgid "Metrics - Grafana"
+msgstr ""
+
+msgid "Metrics - Prometheus"
+msgstr ""
+
+msgid "Metrics Dashboard"
+msgstr ""
+
+msgid "Metrics Dashboard YAML definition"
+msgstr ""
+
+msgid "Metrics Dashboard YAML definition is invalid:"
+msgstr ""
+
+msgid "Metrics Dashboard YAML definition is valid."
+msgstr ""
+
+msgid "Metrics and profiling"
+msgstr ""
+
+msgid "Metrics::Dashboard::Annotation|Annotation can't belong to both a cluster and an environment at the same time"
+msgstr ""
+
+msgid "Metrics::Dashboard::Annotation|Annotation has not been deleted"
+msgstr ""
+
+msgid "Metrics::Dashboard::Annotation|Annotation must belong to a cluster or an environment"
+msgstr ""
+
+msgid "Metrics::Dashboard::Annotation|Dashboard with requested path can not be found"
+msgstr ""
+
+msgid "Metrics::Dashboard::Annotation|You are not authorized to create annotation for selected cluster"
+msgstr ""
+
+msgid "Metrics::Dashboard::Annotation|You are not authorized to create annotation for selected environment"
+msgstr ""
+
+msgid "Metrics::Dashboard::Annotation|You are not authorized to delete this annotation"
+msgstr ""
+
+msgid "Metrics::Dashboard::Annotation|can't be before starting_at time"
+msgstr ""
+
+msgid "Metrics::UsersStarredDashboards|Dashboard with requested path can not be found"
+msgstr ""
+
+msgid "Metrics::UsersStarredDashboards|You are not authorized to add star to this dashboard"
+msgstr ""
+
+msgid "MetricsSettings|Add a button to the metrics dashboard linking directly to your existing external dashboard."
+msgstr ""
+
+msgid "MetricsSettings|Choose whether to display dashboard metrics in UTC or the user's local timezone."
+msgstr ""
+
+msgid "MetricsSettings|Dashboard timezone"
+msgstr ""
+
+msgid "MetricsSettings|External dashboard URL"
+msgstr ""
+
+msgid "MetricsSettings|Manage Metrics Dashboard settings."
+msgstr ""
+
+msgid "MetricsSettings|Metrics Dashboard"
+msgstr ""
+
+msgid "MetricsSettings|UTC (Coordinated Universal Time)"
+msgstr ""
+
+msgid "MetricsSettings|User's local timezone"
+msgstr ""
+
+msgid "Metrics|Add metric"
+msgstr ""
+
+msgid "Metrics|Avg"
+msgstr ""
+
+msgid "Metrics|Check out the CI/CD documentation on deploying to an environment"
+msgstr ""
+
+msgid "Metrics|Create custom dashboard %{fileName}"
+msgstr ""
+
+msgid "Metrics|Create metric"
+msgstr ""
+
+msgid "Metrics|Current"
+msgstr ""
+
+msgid "Metrics|Delete metric"
+msgstr ""
+
+msgid "Metrics|Delete metric?"
+msgstr ""
+
+msgid "Metrics|Duplicate"
+msgstr ""
+
+msgid "Metrics|Duplicate dashboard"
+msgstr ""
+
+msgid "Metrics|Duplicating..."
+msgstr ""
+
+msgid "Metrics|Edit metric"
+msgid_plural "Metrics|Edit metrics"
+msgstr[0] ""
+
+msgid "Metrics|Expand panel"
+msgstr ""
+
+msgid "Metrics|For grouping similar metrics"
+msgstr ""
+
+msgid "Metrics|Go back (Esc)"
+msgstr ""
+
+msgid "Metrics|Invalid time range, please verify."
+msgstr ""
+
+msgid "Metrics|Label of the y-axis (usually the unit). The x-axis always represents time."
+msgstr ""
+
+msgid "Metrics|Legend label (optional)"
+msgstr ""
+
+msgid "Metrics|Link contains an invalid time window, please verify the link to see the requested time range."
+msgstr ""
+
+msgid "Metrics|Link contains invalid chart information, please verify the link to see the expanded panel."
+msgstr ""
+
+msgid "Metrics|Manage chart links"
+msgstr ""
+
+msgid "Metrics|Max"
+msgstr ""
+
+msgid "Metrics|Min"
+msgstr ""
+
+msgid "Metrics|Must be a valid PromQL query."
+msgstr ""
+
+msgid "Metrics|New metric"
+msgstr ""
+
+msgid "Metrics|PromQL query is valid"
+msgstr ""
+
+msgid "Metrics|Prometheus Query Documentation"
+msgstr ""
+
+msgid "Metrics|Refresh dashboard"
+msgstr ""
+
+msgid "Metrics|Select a value"
+msgstr ""
+
+msgid "Metrics|Star dashboard"
+msgstr ""
+
+msgid "Metrics|There was an error creating the dashboard."
+msgstr ""
+
+msgid "Metrics|There was an error creating the dashboard. %{error}"
+msgstr ""
+
+msgid "Metrics|There was an error fetching annotations. Please try again."
+msgstr ""
+
+msgid "Metrics|There was an error fetching the environments data, please try again"
+msgstr ""
+
+msgid "Metrics|There was an error getting annotations information."
+msgstr ""
+
+msgid "Metrics|There was an error getting dashboard validation warnings information."
+msgstr ""
+
+msgid "Metrics|There was an error getting deployment information."
+msgstr ""
+
+msgid "Metrics|There was an error getting environments information."
+msgstr ""
+
+msgid "Metrics|There was an error getting options for variable \"%{name}\"."
+msgstr ""
+
+msgid "Metrics|There was an error trying to validate your query"
+msgstr ""
+
+msgid "Metrics|There was an error while retrieving metrics"
+msgstr ""
+
+msgid "Metrics|There was an error while retrieving metrics. %{message}"
+msgstr ""
+
+msgid "Metrics|Unexpected deployment data response from prometheus endpoint"
+msgstr ""
+
+msgid "Metrics|Unit label"
+msgstr ""
+
+msgid "Metrics|Unstar dashboard"
+msgstr ""
+
+msgid "Metrics|Used as a title for the chart"
+msgstr ""
+
+msgid "Metrics|Used if the query returns a single series. If it returns multiple series, their legend labels will be picked up from the response."
+msgstr ""
+
+msgid "Metrics|Validating query"
+msgstr ""
+
+msgid "Metrics|Values"
+msgstr ""
+
+msgid "Metrics|View logs"
+msgstr ""
+
+msgid "Metrics|Y-axis label"
+msgstr ""
+
+msgid "Metrics|You can save a copy of this dashboard to your repository so it can be customized. Select a file name and branch to save it."
+msgstr ""
+
+msgid "Metrics|You're about to permanently delete this metric. This cannot be undone."
+msgstr ""
+
+msgid "Metrics|Your dashboard schema is invalid. Edit the dashboard to correct the YAML schema."
+msgstr ""
+
+msgid "Metrics|e.g. HTTP requests"
+msgstr ""
+
+msgid "Metrics|e.g. Requests/second"
+msgstr ""
+
+msgid "Metrics|e.g. Throughput"
+msgstr ""
+
+msgid "Metrics|e.g. rate(http_requests_total[5m])"
+msgstr ""
+
+msgid "Metrics|e.g. req/sec"
+msgstr ""
+
+msgid "Mi"
+msgstr ""
+
+msgid "Microsoft Azure"
+msgstr ""
+
+msgid "Middleman project with Static Site Editor support"
+msgstr ""
+
+msgid "Migrate your data from an external source like GitHub, Bitbucket, or another instance of GitLab."
+msgstr ""
+
+msgid "Migrated %{success_count}/%{total_count} files."
+msgstr ""
+
+msgid "Migration successful."
+msgstr ""
+
+msgid "Milestone"
+msgid_plural "Milestones"
+msgstr[0] ""
+
+msgid "Milestone lists not available with your current license"
+msgstr ""
+
+msgid "Milestone lists show all issues from the selected milestone."
+msgstr ""
+
+msgid "MilestoneSidebar|Closed:"
+msgstr ""
+
+msgid "MilestoneSidebar|Copy reference"
+msgstr ""
+
+msgid "MilestoneSidebar|Due date"
+msgstr ""
+
+msgid "MilestoneSidebar|Edit"
+msgstr ""
+
+msgid "MilestoneSidebar|From"
+msgstr ""
+
+msgid "MilestoneSidebar|Issues"
+msgstr ""
+
+msgid "MilestoneSidebar|Merge requests"
+msgstr ""
+
+msgid "MilestoneSidebar|Merged:"
+msgstr ""
+
+msgid "MilestoneSidebar|New Issue"
+msgstr ""
+
+msgid "MilestoneSidebar|New issue"
+msgstr ""
+
+msgid "MilestoneSidebar|No due date"
+msgstr ""
+
+msgid "MilestoneSidebar|No start date"
+msgstr ""
+
+msgid "MilestoneSidebar|None"
+msgstr ""
+
+msgid "MilestoneSidebar|Open:"
+msgstr ""
+
+msgid "MilestoneSidebar|Reference:"
+msgstr ""
+
+msgid "MilestoneSidebar|Start date"
+msgstr ""
+
+msgid "MilestoneSidebar|Toggle sidebar"
+msgstr ""
+
+msgid "MilestoneSidebar|Until"
+msgstr ""
+
+msgid "MilestoneSidebar|complete"
+msgstr ""
+
+msgid "Milestones"
+msgstr ""
+
+msgid "Milestones| You’re about to permanently delete the milestone %{milestoneTitle} and remove it from %{issuesWithCount} and %{mergeRequestsWithCount}. Once deleted, it cannot be undone or recovered."
+msgstr ""
+
+msgid "Milestones| You’re about to permanently delete the milestone %{milestoneTitle}. This milestone is not currently used in any issues or merge requests."
+msgstr ""
+
+msgid "Milestones|Close Milestone"
+msgstr ""
+
+msgid "Milestones|Completed Issues (closed)"
+msgstr ""
+
+msgid "Milestones|Delete milestone"
+msgstr ""
+
+msgid "Milestones|Delete milestone %{milestoneTitle}?"
+msgstr ""
+
+msgid "Milestones|Failed to delete milestone %{milestoneTitle}"
+msgstr ""
+
+msgid "Milestones|Group Milestone"
+msgstr ""
+
+msgid "Milestones|Milestone %{milestoneTitle} was not found"
+msgstr ""
+
+msgid "Milestones|Ongoing Issues (open and assigned)"
+msgstr ""
+
+msgid "Milestones|Project Milestone"
+msgstr ""
+
+msgid "Milestones|Promote %{milestoneTitle} to group milestone?"
+msgstr ""
+
+msgid "Milestones|Promote Milestone"
+msgstr ""
+
+msgid "Milestones|Promote to Group Milestone"
+msgstr ""
+
+msgid "Milestones|Promoting %{milestoneTitle} will make it available for all projects inside %{groupName}. Existing project milestones with the same title will be merged."
+msgstr ""
+
+msgid "Milestones|Reopen Milestone"
+msgstr ""
+
+msgid "Milestones|This action cannot be reversed."
+msgstr ""
+
+msgid "Milestones|Unstarted Issues (open and unassigned)"
+msgstr ""
+
+msgid "Minimum capacity to be available before we schedule more mirrors preemptively."
+msgstr ""
+
+msgid "Minimum interval in days"
+msgstr ""
+
+msgid "Minimum length is %{minimum_password_length} characters"
+msgstr ""
+
+msgid "Minimum length is %{minimum_password_length} characters."
+msgstr ""
+
+msgid "Minimum password length (number of characters)"
+msgstr ""
+
+msgid "Minutes"
+msgstr ""
+
+msgid "Mirror direction"
+msgstr ""
+
+msgid "Mirror repository"
+msgstr ""
+
+msgid "Mirror settings are only available to GitLab administrators."
+msgstr ""
+
+msgid "Mirror user"
+msgstr ""
+
+msgid "Mirrored branches will have this prefix. If you enabled 'Only mirror protected branches' you need to include this prefix on protected branches in this project or nothing will be mirrored."
+msgstr ""
+
+msgid "Mirrored repositories"
+msgstr ""
+
+msgid "Mirroring repositories"
+msgstr ""
+
+msgid "Mirroring settings were successfully updated."
+msgstr ""
+
+msgid "Mirroring settings were successfully updated. The project is being updated."
+msgstr ""
+
+msgid "Mirroring was successfully disabled."
+msgstr ""
+
+msgid "Mirroring will only be available if the feature is included in the plan of the selected group or user."
+msgstr ""
+
+msgid "Missing commit signatures endpoint!"
+msgstr ""
+
+msgid "MissingSSHKeyWarningLink|Add SSH key"
+msgstr ""
+
+msgid "MissingSSHKeyWarningLink|Don't show again"
+msgstr ""
+
+msgid "MissingSSHKeyWarningLink|You won't be able to pull or push project code via SSH until you add an SSH key to your profile"
+msgstr ""
+
+msgid "ModalButton|Add projects"
+msgstr ""
+
+msgid "Modal|Cancel"
+msgstr ""
+
+msgid "Modal|Close"
+msgstr ""
+
+msgid "Modified"
+msgstr ""
+
+msgid "Modified in this version"
+msgstr ""
+
+msgid "Modify commit message"
+msgstr ""
+
+msgid "Modify commit messages"
+msgstr ""
+
+msgid "Modify merge commit"
+msgstr ""
+
+msgid "Monday"
+msgstr ""
+
+msgid "Monitor your errors by integrating with Sentry."
+msgstr ""
+
+msgid "Monitoring"
+msgstr ""
+
+msgid "Months"
+msgstr ""
+
+msgid "More"
+msgstr ""
+
+msgid "More Information"
+msgstr ""
+
+msgid "More Slack commands"
+msgstr ""
+
+msgid "More actions"
+msgstr ""
+
+msgid "More details"
+msgstr ""
+
+msgid "More info"
+msgstr ""
+
+msgid "More information"
+msgstr ""
+
+msgid "More information and share feedback"
+msgstr ""
+
+msgid "More information is available|here"
+msgstr ""
+
+msgid "More than %{number_commits_distance} commits different with %{default_branch}"
+msgstr ""
+
+msgid "Most stars"
+msgstr ""
+
+msgid "Mount point %{mounted_as} not found in %{model_class}."
+msgstr ""
+
+msgid "Move"
+msgstr ""
+
+msgid "Move issue"
+msgstr ""
+
+msgid "Move issue from one column of the board to another"
+msgstr ""
+
+msgid "Move selection down"
+msgstr ""
+
+msgid "Move selection up"
+msgstr ""
+
+msgid "Move this issue to another project."
+msgstr ""
+
+msgid "MoveIssue|Cannot move issue due to insufficient permissions!"
+msgstr ""
+
+msgid "MoveIssue|Cannot move issue to project it originates from!"
+msgstr ""
+
+msgid "Moved issue to %{label} column in the board."
+msgstr ""
+
+msgid "Moved this issue to %{path_to_project}."
+msgstr ""
+
+msgid "Moves issue to %{label} column in the board."
+msgstr ""
+
+msgid "Moves this issue to %{path_to_project}."
+msgstr ""
+
+msgid "MrDeploymentActions|Deploy"
+msgstr ""
+
+msgid "MrDeploymentActions|Re-deploy"
+msgstr ""
+
+msgid "MrDeploymentActions|Stop environment"
+msgstr ""
+
+msgid "Multiple domains are supported with comma delimiters."
+msgstr ""
+
+msgid "Multiple issue boards"
+msgstr ""
+
+msgid "Multiple model types found: %{model_types}"
+msgstr ""
+
+msgid "Multiple uploaders found: %{uploader_types}"
+msgstr ""
+
+msgid "Must match with the %{codeStart}external_url%{codeEnd} in %{codeStart}/etc/gitlab/gitlab.rb%{codeEnd}."
+msgstr ""
+
+msgid "Must match with the %{codeStart}geo_node_name%{codeEnd} in %{codeStart}/etc/gitlab/gitlab.rb%{codeEnd}. %{linkStart}More information%{linkEnd}"
+msgstr ""
+
+msgid "My Awesome Group"
+msgstr ""
+
+msgid "My company or team"
+msgstr ""
+
+msgid "My-Reaction"
+msgstr ""
+
+msgid "Name"
+msgstr ""
+
+msgid "Name has already been taken"
+msgstr ""
+
+msgid "Name new label"
+msgstr ""
+
+msgid "Name:"
+msgstr ""
+
+msgid "Namespace is empty"
+msgstr ""
+
+msgid "Namespaces to index"
+msgstr ""
+
+msgid "Naming, topics, avatar"
+msgstr ""
+
+msgid "Naming, visibility"
+msgstr ""
+
+msgid "Navigate to the project to close the milestone."
+msgstr ""
+
+msgid "Nav|Help"
+msgstr ""
+
+msgid "Nav|Home"
+msgstr ""
+
+msgid "Nav|Sign In / Register"
+msgstr ""
+
+msgid "Nav|Sign out and sign in with a different account"
+msgstr ""
+
+msgid "Need help?"
+msgstr ""
+
+msgid "Needs attention"
+msgstr ""
+
+msgid "Network"
+msgstr ""
+
+msgid "NetworkPolicies|Choose whether to enforce this policy."
+msgstr ""
+
+msgid "NetworkPolicies|Define this policy's location, conditions and actions."
+msgstr ""
+
+msgid "NetworkPolicies|Enforcement status"
+msgstr ""
+
+msgid "NetworkPolicies|Environment does not have deployment platform"
+msgstr ""
+
+msgid "NetworkPolicies|If you are using Auto DevOps, your %{monospacedStart}auto-deploy-values.yaml%{monospacedEnd} file will not be updated if you change a policy in this section. Auto DevOps users should make changes by following the %{linkStart}Container Network Policy documentation%{linkEnd}."
+msgstr ""
+
+msgid "NetworkPolicies|Invalid or empty policy"
+msgstr ""
+
+msgid "NetworkPolicies|Kubernetes error: %{error}"
+msgstr ""
+
+msgid "NetworkPolicies|Last modified"
+msgstr ""
+
+msgid "NetworkPolicies|Name"
+msgstr ""
+
+msgid "NetworkPolicies|No policies detected"
+msgstr ""
+
+msgid "NetworkPolicies|Policies are a specification of how groups of pods are allowed to communicate with each other's network endpoints."
+msgstr ""
+
+msgid "NetworkPolicies|Policy %{policyName} was successfully changed"
+msgstr ""
+
+msgid "NetworkPolicies|Policy definition"
+msgstr ""
+
+msgid "NetworkPolicies|Something went wrong, failed to update policy"
+msgstr ""
+
+msgid "NetworkPolicies|Something went wrong, unable to fetch policies"
+msgstr ""
+
+msgid "NetworkPolicies|Status"
+msgstr ""
+
+msgid "Never"
+msgstr ""
+
+msgid "New"
+msgstr ""
+
+msgid "New Application"
+msgstr ""
+
+msgid "New Branch"
+msgstr ""
+
+msgid "New Deploy Key"
+msgstr ""
+
+msgid "New Environment"
+msgstr ""
+
+msgid "New File"
+msgstr ""
+
+msgid "New Group"
+msgstr ""
+
+msgid "New Group Name"
+msgstr ""
+
+msgid "New Identity"
+msgstr ""
+
+msgid "New Issue"
+msgid_plural "New Issues"
+msgstr[0] ""
+
+msgid "New Jira import"
+msgstr ""
+
+msgid "New Label"
+msgstr ""
+
+msgid "New Merge Request"
+msgstr ""
+
+msgid "New Milestone"
+msgstr ""
+
+msgid "New Pages Domain"
+msgstr ""
+
+msgid "New Password"
+msgstr ""
+
+msgid "New Pipeline Schedule"
+msgstr ""
+
+msgid "New Project"
+msgstr ""
+
+msgid "New Snippet"
+msgstr ""
+
+msgid "New User"
+msgstr ""
+
+msgid "New branch"
+msgstr ""
+
+msgid "New branch unavailable"
+msgstr ""
+
+msgid "New changes were added. %{linkStart}Reload the page to review them%{linkEnd}"
+msgstr ""
+
+msgid "New deploy key"
+msgstr ""
+
+msgid "New directory"
+msgstr ""
+
+msgid "New environment"
+msgstr ""
+
+msgid "New epic"
+msgstr ""
+
+msgid "New epic title"
+msgstr ""
+
+msgid "New file"
+msgstr ""
+
+msgid "New group"
+msgstr ""
+
+msgid "New health check access token has been generated!"
+msgstr ""
+
+msgid "New identity"
+msgstr ""
+
+msgid "New issue"
+msgstr ""
+
+msgid "New issue title"
+msgstr ""
+
+msgid "New iteration"
+msgstr ""
+
+msgid "New iteration created"
+msgstr ""
+
+msgid "New label"
+msgstr ""
+
+msgid "New merge request"
+msgstr ""
+
+msgid "New milestone"
+msgstr ""
+
+msgid "New password"
+msgstr ""
+
+msgid "New pipelines will cancel older, pending pipelines on the same branch"
+msgstr ""
+
+msgid "New project"
+msgstr ""
+
+msgid "New release"
+msgstr ""
+
+msgid "New requirement"
+msgstr ""
+
+msgid "New runners registration token has been generated!"
+msgstr ""
+
+msgid "New schedule"
+msgstr ""
+
+msgid "New snippet"
+msgstr ""
+
+msgid "New subgroup"
+msgstr ""
+
+msgid "New tag"
+msgstr ""
+
+msgid "New users set to external"
+msgstr ""
+
+msgid "New! Suggest changes directly"
+msgstr ""
+
+msgid "New..."
+msgstr ""
+
+msgid "Newest first"
+msgstr ""
+
+msgid "Newly registered users will by default be external"
+msgstr ""
+
+msgid "Next"
+msgstr ""
+
+msgid "Next commit"
+msgstr ""
+
+msgid "Next file in diff"
+msgstr ""
+
+msgid "Next unresolved discussion"
+msgstr ""
+
+msgid "Nickname"
+msgstr ""
+
+msgid "No"
+msgstr ""
+
+msgid "No %{header} for this request."
+msgstr ""
+
+msgid "No %{providerTitle} repositories found"
+msgstr ""
+
+msgid "No Epic"
+msgstr ""
+
+msgid "No Scopes"
+msgstr ""
+
+msgid "No Tag"
+msgstr ""
+
+msgid "No active admin user found"
+msgstr ""
+
+msgid "No activities found"
+msgstr ""
+
+msgid "No application_settings found"
+msgstr ""
+
+msgid "No approvers"
+msgstr ""
+
+msgid "No authentication methods configured."
+msgstr ""
+
+msgid "No available namespaces to fork the project."
+msgstr ""
+
+msgid "No branches found"
+msgstr ""
+
+msgid "No changes"
+msgstr ""
+
+msgid "No changes between %{ref_start}%{source_branch}%{ref_end} and %{ref_start}%{target_branch}%{ref_end}"
+msgstr ""
+
+msgid "No child epics match applied filters"
+msgstr ""
+
+msgid "No connection could be made to a Gitaly Server, please check your logs!"
+msgstr ""
+
+msgid "No containers available"
+msgstr ""
+
+msgid "No contributions"
+msgstr ""
+
+msgid "No contributions were found"
+msgstr ""
+
+msgid "No credit card required."
+msgstr ""
+
+msgid "No data found"
+msgstr ""
+
+msgid "No data to display"
+msgstr ""
+
+msgid "No deployments found"
+msgstr ""
+
+msgid "No due date"
+msgstr ""
+
+msgid "No endpoint provided"
+msgstr ""
+
+msgid "No errors to display."
+msgstr ""
+
+msgid "No estimate or time spent"
+msgstr ""
+
+msgid "No file chosen"
+msgstr ""
+
+msgid "No file hooks found."
+msgstr ""
+
+msgid "No file selected"
+msgstr ""
+
+msgid "No files"
+msgstr ""
+
+msgid "No files found."
+msgstr ""
+
+msgid "No forks are available to you."
+msgstr ""
+
+msgid "No grouping"
+msgstr ""
+
+msgid "No iteration"
+msgstr ""
+
+msgid "No iterations to show"
+msgstr ""
+
+msgid "No job log"
+msgstr ""
+
+msgid "No jobs to show"
+msgstr ""
+
+msgid "No label"
+msgstr ""
+
+msgid "No labels with such name or description"
+msgstr ""
+
+msgid "No license. All rights reserved"
+msgstr ""
+
+msgid "No licenses found."
+msgstr ""
+
+msgid "No matches found"
+msgstr ""
+
+msgid "No matching labels"
+msgstr ""
+
+msgid "No matching results"
+msgstr ""
+
+msgid "No merge requests found"
+msgstr ""
+
+msgid "No messages were logged"
+msgstr ""
+
+msgid "No milestone"
+msgstr ""
+
+msgid "No milestones to show"
+msgstr ""
+
+msgid "No other labels with such name or description"
+msgstr ""
+
+msgid "No panels matching properties %{opts}"
+msgstr ""
+
+msgid "No parent group"
+msgstr ""
+
+msgid "No pods available"
+msgstr ""
+
+msgid "No policy matches this license"
+msgstr ""
+
+msgid "No preview for this file type"
+msgstr ""
+
+msgid "No prioritized labels with such name or description"
+msgstr ""
+
+msgid "No public groups"
+msgstr ""
+
+msgid "No related merge requests found."
+msgstr ""
+
+msgid "No repository"
+msgstr ""
+
+msgid "No required pipeline"
+msgstr ""
+
+msgid "No runners found"
+msgstr ""
+
+msgid "No schedules"
+msgstr ""
+
+msgid "No stack trace for this error"
+msgstr ""
+
+msgid "No starrers matched your search"
+msgstr ""
+
+msgid "No start date"
+msgstr ""
+
+msgid "No template"
+msgstr ""
+
+msgid "No test coverage"
+msgstr ""
+
+msgid "No thanks"
+msgstr ""
+
+msgid "No vulnerabilities present"
+msgstr ""
+
+msgid "No webhooks found, add one in the form above."
+msgstr ""
+
+msgid "No worries, you can still use all the %{strong}%{plan_name}%{strong_close} features for now. You have %{remaining_days} to renew your subscription."
+msgstr ""
+
+msgid "No, directly import the existing email addresses and usernames."
+msgstr ""
+
+msgid "No. of commits"
+msgstr ""
+
+msgid "Nobody has starred this repository yet"
+msgstr ""
+
+msgid "Node was successfully created."
+msgstr ""
+
+msgid "Node was successfully updated."
+msgstr ""
+
+msgid "Nodes"
+msgstr ""
+
+msgid "Non-admin users can sign in with read-only access and make read-only API requests."
+msgstr ""
+
+msgid "None"
+msgstr ""
+
+msgid "Not Implemented"
+msgstr ""
+
+msgid "Not all data has been processed yet, the accuracy of the chart for the selected timeframe is limited."
+msgstr ""
+
+msgid "Not available"
+msgstr ""
+
+msgid "Not available for private projects"
+msgstr ""
+
+msgid "Not available for protected branches"
+msgstr ""
+
+msgid "Not confidential"
+msgstr ""
+
+msgid "Not enough data"
+msgstr ""
+
+msgid "Not found."
+msgstr ""
+
+msgid "Not now"
+msgstr ""
+
+msgid "Not ready yet. Try again later."
+msgstr ""
+
+msgid "Not started"
+msgstr ""
+
+msgid "Not-confidential epic cannot be assigned to a confidential parent epic"
+msgstr ""
+
+msgid "Note"
+msgstr ""
+
+msgid "Note parameters are invalid: %{errors}"
+msgstr ""
+
+msgid "Note that PostgreSQL 11 will become the minimum required PostgreSQL version in GitLab 13.0 (May 2020). PostgreSQL 9.6 and PostgreSQL 10 will no longer be supported in GitLab 13.0. Please consider upgrading your PostgreSQL version (%{db_version}) soon."
+msgstr ""
+
+msgid "Note that this invitation was sent to %{mail_to_invite_email}, but you are signed in as %{link_to_current_user} with email %{mail_to_current_user}."
+msgstr ""
+
+msgid "Note: As an administrator you may like to configure %{github_integration_link}, which will allow login via GitHub and allow connecting repositories without generating a Personal Access Token."
+msgstr ""
+
+msgid "Note: As an administrator you may like to configure %{github_integration_link}, which will allow login via GitHub and allow importing repositories without generating a Personal Access Token."
+msgstr ""
+
+msgid "Note: Consider asking your GitLab administrator to configure %{github_integration_link}, which will allow login via GitHub and allow connecting repositories without generating a Personal Access Token."
+msgstr ""
+
+msgid "Note: Consider asking your GitLab administrator to configure %{github_integration_link}, which will allow login via GitHub and allow importing repositories without generating a Personal Access Token."
+msgstr ""
+
+msgid "NoteForm|Note"
+msgstr ""
+
+msgid "Notes|Are you sure you want to cancel creating this comment?"
+msgstr ""
+
+msgid "Notes|Collapse replies"
+msgstr ""
+
+msgid "Notes|Private comments are accessible by internal staff only"
+msgstr ""
+
+msgid "Notes|Show all activity"
+msgstr ""
+
+msgid "Notes|Show comments only"
+msgstr ""
+
+msgid "Notes|Show history only"
+msgstr ""
+
+msgid "Notes|This comment has changed since you started editing, please review the %{open_link}updated comment%{close_link} to ensure information is not lost"
+msgstr ""
+
+msgid "Nothing found…"
+msgstr ""
+
+msgid "Nothing to preview."
+msgstr ""
+
+msgid "Nothing to synchronize"
+msgstr ""
+
+msgid "Notification events"
+msgstr ""
+
+msgid "Notification setting"
+msgstr ""
+
+msgid "Notification setting - %{notification_title}"
+msgstr ""
+
+msgid "Notification settings saved"
+msgstr ""
+
+msgid "NotificationEvent|Close issue"
+msgstr ""
+
+msgid "NotificationEvent|Close merge request"
+msgstr ""
+
+msgid "NotificationEvent|Failed pipeline"
+msgstr ""
+
+msgid "NotificationEvent|Fixed pipeline"
+msgstr ""
+
+msgid "NotificationEvent|Merge merge request"
+msgstr ""
+
+msgid "NotificationEvent|New epic"
+msgstr ""
+
+msgid "NotificationEvent|New issue"
+msgstr ""
+
+msgid "NotificationEvent|New merge request"
+msgstr ""
+
+msgid "NotificationEvent|New note"
+msgstr ""
+
+msgid "NotificationEvent|New release"
+msgstr ""
+
+msgid "NotificationEvent|Reassign issue"
+msgstr ""
+
+msgid "NotificationEvent|Reassign merge request"
+msgstr ""
+
+msgid "NotificationEvent|Reopen issue"
+msgstr ""
+
+msgid "NotificationEvent|Successful pipeline"
+msgstr ""
+
+msgid "NotificationLevel|Custom"
+msgstr ""
+
+msgid "NotificationLevel|Disabled"
+msgstr ""
+
+msgid "NotificationLevel|Global"
+msgstr ""
+
+msgid "NotificationLevel|On mention"
+msgstr ""
+
+msgid "NotificationLevel|Participate"
+msgstr ""
+
+msgid "NotificationLevel|Watch"
+msgstr ""
+
+msgid "NotificationSetting|Custom"
+msgstr ""
+
+msgid "Notifications"
+msgstr ""
+
+msgid "Notifications have been disabled by the project or group owner"
+msgstr ""
+
+msgid "Notifications off"
+msgstr ""
+
+msgid "Notifications on"
+msgstr ""
+
+msgid "Notify users by email when sign-in location is not recognized"
+msgstr ""
+
+msgid "Nov"
+msgstr ""
+
+msgid "November"
+msgstr ""
+
+msgid "Novice"
+msgstr ""
+
+msgid "Now you can access the merge request navigation tabs at the top, where they’re easier to find."
+msgstr ""
+
+msgid "Nuget metadatum must have at least license_url, project_url or icon_url set"
+msgstr ""
+
+msgid "Number of %{itemTitle}"
+msgstr ""
+
+msgid "Number of Elasticsearch replicas"
+msgstr ""
+
+msgid "Number of Elasticsearch shards"
+msgstr ""
+
+msgid "Number of LOCs per commit"
+msgstr ""
+
+msgid "Number of changes (branches or tags) in a single push to determine whether individual push events or bulk push event will be created. Bulk push event will be created if it surpasses that value."
+msgstr ""
+
+msgid "Number of changes (branches or tags) in a single push to determine whether webhooks and services will be fired or not. Webhooks and services won't be submitted if it surpasses that value."
+msgstr ""
+
+msgid "Number of commits"
+msgstr ""
+
+msgid "Number of commits per MR"
+msgstr ""
+
+msgid "Number of employees"
+msgstr ""
+
+msgid "Number of files touched"
+msgstr ""
+
+msgid "OK"
+msgstr ""
+
+msgid "Object Storage replication"
+msgstr ""
+
+msgid "Object does not exist on the server or you don't have permissions to access it"
+msgstr ""
+
+msgid "Oct"
+msgstr ""
+
+msgid "October"
+msgstr ""
+
+msgid "OfSearchInADropdown|Filter"
+msgstr ""
+
+msgid "Oh no!"
+msgstr ""
+
+msgid "Oldest first"
+msgstr ""
+
+msgid "OmniAuth"
+msgstr ""
+
+msgid "Omnibus Protected Paths throttle is active, and takes priority over these settings. From 12.4, Omnibus throttle is deprecated and will be removed in a future release. Please read the %{relative_url_link_start}Migrating Protected Paths documentation%{relative_url_link_end}."
+msgstr ""
+
+msgid "On track"
+msgstr ""
+
+msgid "OnDemandScans|Attached branch"
+msgstr ""
+
+msgid "OnDemandScans|Attached branch is where the scan job runs."
+msgstr ""
+
+msgid "OnDemandScans|Could not run the scan. Please try again."
+msgstr ""
+
+msgid "OnDemandScans|Create new DAST scan"
+msgstr ""
+
+msgid "OnDemandScans|DAST will scan the target URL and any discovered sub URLs."
+msgstr ""
+
+msgid "OnDemandScans|New on-demand DAST scan"
+msgstr ""
+
+msgid "OnDemandScans|On-demand Scans"
+msgstr ""
+
+msgid "OnDemandScans|On-demand scans run outside the DevOps cycle and find vulnerabilities in your projects. %{learnMoreLinkStart}Learn more%{learnMoreLinkEnd}"
+msgstr ""
+
+msgid "OnDemandScans|Only a passive scan can be performed on demand."
+msgstr ""
+
+msgid "OnDemandScans|Passive DAST Scan"
+msgstr ""
+
+msgid "OnDemandScans|Please enter a valid URL format, ex: http://www.example.com/home"
+msgstr ""
+
+msgid "OnDemandScans|Run this scan"
+msgstr ""
+
+msgid "OnDemandScans|Scan mode"
+msgstr ""
+
+msgid "OnDemandScans|Schedule or run scans immediately against target sites. Currently available on-demand scan type: DAST. %{helpLinkStart}More information%{helpLinkEnd}"
+msgstr ""
+
+msgid "OnDemandScans|Target URL"
+msgstr ""
+
+msgid "Once imported, repositories can be mirrored over SSH. Read more %{link_start}here%{link_end}."
+msgstr ""
+
+msgid "Once removed, the fork relationship cannot be restored and you will no longer be able to send merge requests to the source."
+msgstr ""
+
+msgid "Once the exported file is ready, you will receive a notification email with a download link, or you can download it from this page."
+msgstr ""
+
+msgid "Once you confirm and press \"Reduce project visibility\":"
+msgstr ""
+
+msgid "One more item"
+msgid_plural "%d more items"
+msgstr[0] ""
+
+msgid "One or more groups that you don't have access to."
+msgstr ""
+
+msgid "One or more of you personal access tokens were revoked"
+msgstr ""
+
+msgid "One or more of your %{provider} projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
+msgstr ""
+
+msgid "One or more of your Bitbucket projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
+msgstr ""
+
+msgid "One or more of your Google Code projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
+msgstr ""
+
+msgid "One or more of your dependency files are not supported, and the dependency list may be incomplete. Below is a list of supported file types."
+msgstr ""
+
+msgid "One or more of your personal access tokens will expire in %{days_to_expire} days or less."
+msgstr ""
+
+msgid "Only 'Reporter' roles and above on tiers Premium / Silver and above can see Value Stream Analytics."
+msgstr ""
+
+msgid "Only 1 appearances row can exist"
+msgstr ""
+
+msgid "Only Issue ID or Merge Request ID is required"
+msgstr ""
+
+msgid "Only Project Members"
+msgstr ""
+
+msgid "Only active this projects shows up in the search and on the dashboard."
+msgstr ""
+
+msgid "Only admins"
+msgstr ""
+
+msgid "Only admins can delete project"
+msgstr ""
+
+msgid "Only mirror protected branches"
+msgstr ""
+
+msgid "Only policy:"
+msgstr ""
+
+msgid "Only proceed if you trust %{idp_url} to control your GitLab account sign in."
+msgstr ""
+
+msgid "Only project members can comment."
+msgstr ""
+
+msgid "Only project members will be imported. Group members will be skipped."
+msgstr ""
+
+msgid "Only verified users with an email address in any of these domains can be added to the group."
+msgstr ""
+
+msgid "Only ‘Reporter’ roles and above on tiers Premium / Silver and above can see Productivity Analytics."
+msgstr ""
+
+msgid "Oops, are you sure?"
+msgstr ""
+
+msgid "Open"
+msgstr ""
+
+msgid "Open Selection"
+msgstr ""
+
+msgid "Open comment type dropdown"
+msgstr ""
+
+msgid "Open errors"
+msgstr ""
+
+msgid "Open in Xcode"
+msgstr ""
+
+msgid "Open issues"
+msgstr ""
+
+msgid "Open projects"
+msgstr ""
+
+msgid "Open raw"
+msgstr ""
+
+msgid "Open sidebar"
+msgstr ""
+
+msgid "Open: %{openIssuesCount}"
+msgstr ""
+
+msgid "Open: %{open} • Closed: %{closed}"
+msgstr ""
+
+msgid "Opened"
+msgstr ""
+
+msgid "Opened MRs"
+msgstr ""
+
+msgid "Opened issues"
+msgstr ""
+
+msgid "OpenedNDaysAgo|Opened"
+msgstr ""
+
+msgid "Opens in a new window"
+msgstr ""
+
+msgid "Operation failed. Check pod logs for %{pod_name} for more details."
+msgstr ""
+
+msgid "Operation not allowed"
+msgstr ""
+
+msgid "Operation timed out. Check pod logs for %{pod_name} for more details."
+msgstr ""
+
+msgid "Operations"
+msgstr ""
+
+msgid "Operations Dashboard"
+msgstr ""
+
+msgid "Operations Settings"
+msgstr ""
+
+msgid "OperationsDashboard|Add a project to the dashboard"
+msgstr ""
+
+msgid "OperationsDashboard|Add projects"
+msgstr ""
+
+msgid "OperationsDashboard|More information"
+msgstr ""
+
+msgid "OperationsDashboard|Operations Dashboard"
+msgstr ""
+
+msgid "OperationsDashboard|The operations dashboard provides a summary of each project's operational health, including pipeline and alert statuses."
+msgstr ""
+
+msgid "Optional"
+msgstr ""
+
+msgid "Optional parameter \"variables\" must be a Hash. Ex: variables[key1]=value1"
+msgstr ""
+
+msgid "Optionally, you can %{link_to_customize} how FogBugz email addresses and usernames are imported into GitLab."
+msgstr ""
+
+msgid "Optionally, you can %{link_to_customize} how Google Code email addresses and usernames are imported into GitLab."
+msgstr ""
+
+msgid "Options"
+msgstr ""
+
+msgid "Or you can choose one of the suggested colors below"
+msgstr ""
+
+msgid "Origin"
+msgstr ""
+
+msgid "Other Labels"
+msgstr ""
+
+msgid "Other information"
+msgstr ""
+
+msgid "Other merge requests block this MR"
+msgstr ""
+
+msgid "Other visibility settings have been disabled by the administrator."
+msgstr ""
+
+msgid "Out-of-compliance with this project's policies and should be removed"
+msgstr ""
+
+msgid "Outbound requests"
+msgstr ""
+
+msgid "OutdatedBrowser|From May 2020 GitLab no longer supports Internet Explorer 11."
+msgstr ""
+
+msgid "OutdatedBrowser|GitLab may not work properly, because you are using an outdated web browser."
+msgstr ""
+
+msgid "OutdatedBrowser|Please install a %{browser_link_start}supported web browser%{browser_link_end} for a better experience."
+msgstr ""
+
+msgid "OutdatedBrowser|You can provide feedback %{feedback_link_start}on this issue%{feedback_link_end} or via your usual support channels."
+msgstr ""
+
+msgid "Outdent"
+msgstr ""
+
+msgid "Overridden"
+msgstr ""
+
+msgid "Overview"
+msgstr ""
+
+msgid "Overwrite diverged branches"
+msgstr ""
+
+msgid "Owned by anyone"
+msgstr ""
+
+msgid "Owned by me"
+msgstr ""
+
+msgid "Owner"
+msgstr ""
+
+msgid "Package Files"
+msgstr ""
+
+msgid "Package Registry"
+msgstr ""
+
+msgid "Package already exists"
+msgstr ""
+
+msgid "Package deleted successfully"
+msgstr ""
+
+msgid "Package information"
+msgstr ""
+
+msgid "Package recipe already exists"
+msgstr ""
+
+msgid "Package type must be Conan"
+msgstr ""
+
+msgid "Package type must be Maven"
+msgstr ""
+
+msgid "Package type must be NuGet"
+msgstr ""
+
+msgid "Package type must be PyPi"
+msgstr ""
+
+msgid "Package was removed"
+msgstr ""
+
+msgid "PackageRegistry|Add Conan Remote"
+msgstr ""
+
+msgid "PackageRegistry|Add NuGet Source"
+msgstr ""
+
+msgid "PackageRegistry|Conan"
+msgstr ""
+
+msgid "PackageRegistry|Conan Command"
+msgstr ""
+
+msgid "PackageRegistry|Copy .pypirc content"
+msgstr ""
+
+msgid "PackageRegistry|Copy Conan Command"
+msgstr ""
+
+msgid "PackageRegistry|Copy Conan Setup Command"
+msgstr ""
+
+msgid "PackageRegistry|Copy Maven XML"
+msgstr ""
+
+msgid "PackageRegistry|Copy Maven command"
+msgstr ""
+
+msgid "PackageRegistry|Copy Maven registry XML"
+msgstr ""
+
+msgid "PackageRegistry|Copy NuGet Command"
+msgstr ""
+
+msgid "PackageRegistry|Copy NuGet Setup Command"
+msgstr ""
+
+msgid "PackageRegistry|Copy Pip command"
+msgstr ""
+
+msgid "PackageRegistry|Copy and paste this inside your %{codeStart}pom.xml%{codeEnd} %{codeStart}dependencies%{codeEnd} block."
+msgstr ""
+
+msgid "PackageRegistry|Copy npm command"
+msgstr ""
+
+msgid "PackageRegistry|Copy npm setup command"
+msgstr ""
+
+msgid "PackageRegistry|Copy yarn command"
+msgstr ""
+
+msgid "PackageRegistry|Copy yarn setup command"
+msgstr ""
+
+msgid "PackageRegistry|Delete Package Version"
+msgstr ""
+
+msgid "PackageRegistry|Delete package"
+msgstr ""
+
+msgid "PackageRegistry|Filter by name"
+msgstr ""
+
+msgid "PackageRegistry|For more information on the Conan registry, %{linkStart}see the documentation%{linkEnd}."
+msgstr ""
+
+msgid "PackageRegistry|For more information on the Maven registry, %{linkStart}see the documentation%{linkEnd}."
+msgstr ""
+
+msgid "PackageRegistry|For more information on the NuGet registry, %{linkStart}see the documentation%{linkEnd}."
+msgstr ""
+
+msgid "PackageRegistry|For more information on the PyPi registry, %{linkStart}see the documentation%{linkEnd}."
+msgstr ""
+
+msgid "PackageRegistry|If you haven't already done so, you will need to add the below to your %{codeStart}.pypirc%{codeEnd} file."
+msgstr ""
+
+msgid "PackageRegistry|If you haven't already done so, you will need to add the below to your %{codeStart}pom.xml%{codeEnd} file."
+msgstr ""
+
+msgid "PackageRegistry|Installation"
+msgstr ""
+
+msgid "PackageRegistry|Is your favorite package manager missing? We'd love your help in building first-class support for it into GitLab! %{contributionLinkStart}Visit the contribution documentation%{contributionLinkEnd} to learn more about how to build support for new package managers into GitLab. Below is a list of package managers that are on our radar."
+msgstr ""
+
+msgid "PackageRegistry|Learn how to %{noPackagesLinkStart}publish and share your packages%{noPackagesLinkEnd} with GitLab."
+msgstr ""
+
+msgid "PackageRegistry|Manually Published"
+msgstr ""
+
+msgid "PackageRegistry|Maven"
+msgstr ""
+
+msgid "PackageRegistry|Maven Command"
+msgstr ""
+
+msgid "PackageRegistry|Maven XML"
+msgstr ""
+
+msgid "PackageRegistry|NPM"
+msgstr ""
+
+msgid "PackageRegistry|No upcoming issues"
+msgstr ""
+
+msgid "PackageRegistry|NuGet"
+msgstr ""
+
+msgid "PackageRegistry|NuGet Command"
+msgstr ""
+
+msgid "PackageRegistry|Pip Command"
+msgstr ""
+
+msgid "PackageRegistry|Pipeline %{linkStart}%{linkEnd} triggered %{timestamp} by %{author}"
+msgstr ""
+
+msgid "PackageRegistry|Published to the repository at %{timestamp}"
+msgstr ""
+
+msgid "PackageRegistry|PyPi"
+msgstr ""
+
+msgid "PackageRegistry|Registry Setup"
+msgstr ""
+
+msgid "PackageRegistry|Remove package"
+msgstr ""
+
+msgid "PackageRegistry|Sorry, your filter produced no results"
+msgstr ""
+
+msgid "PackageRegistry|There are no %{packageType} packages yet"
+msgstr ""
+
+msgid "PackageRegistry|There are no other versions of this package."
+msgstr ""
+
+msgid "PackageRegistry|There are no packages yet"
+msgstr ""
+
+msgid "PackageRegistry|There are no upcoming issues to display."
+msgstr ""
+
+msgid "PackageRegistry|There was a problem fetching the details for this package."
+msgstr ""
+
+msgid "PackageRegistry|This NuGet package has no dependencies."
+msgstr ""
+
+msgid "PackageRegistry|To widen your search, change or remove the filters above."
+msgstr ""
+
+msgid "PackageRegistry|Unable to fetch package version information."
+msgstr ""
+
+msgid "PackageRegistry|Unable to load package"
+msgstr ""
+
+msgid "PackageRegistry|Upcoming package managers"
+msgstr ""
+
+msgid "PackageRegistry|You are about to delete %{name}, this operation is irreversible, are you sure?"
+msgstr ""
+
+msgid "PackageRegistry|You are about to delete version %{version} of %{name}. Are you sure?"
+msgstr ""
+
+msgid "PackageRegistry|You may also need to setup authentication using an auth token. %{linkStart}See the documentation%{linkEnd} to find out more."
+msgstr ""
+
+msgid "PackageRegistry|npm"
+msgstr ""
+
+msgid "PackageRegistry|published by %{author}"
+msgstr ""
+
+msgid "PackageRegistry|yarn"
+msgstr ""
+
+msgid "PackageType|Conan"
+msgstr ""
+
+msgid "PackageType|Maven"
+msgstr ""
+
+msgid "PackageType|NPM"
+msgstr ""
+
+msgid "PackageType|NuGet"
+msgstr ""
+
+msgid "PackageType|PyPi"
+msgstr ""
+
+msgid "Packages"
+msgstr ""
+
+msgid "Packages & Registries"
+msgstr ""
+
+msgid "Page not found"
+msgstr ""
+
+msgid "Page was successfully deleted"
+msgstr ""
+
+msgid "Pages"
+msgstr ""
+
+msgid "Pages Domain"
+msgstr ""
+
+msgid "Pages getting started guide"
+msgstr ""
+
+msgid "Pagination|Go to first page"
+msgstr ""
+
+msgid "Pagination|Go to last page"
+msgstr ""
+
+msgid "Pagination|Go to next page"
+msgstr ""
+
+msgid "Pagination|Go to previous page"
+msgstr ""
+
+msgid "Pagination|Last »"
+msgstr ""
+
+msgid "Pagination|Next"
+msgstr ""
+
+msgid "Pagination|Prev"
+msgstr ""
+
+msgid "Pagination|« First"
+msgstr ""
+
+msgid "Parameter"
+msgstr ""
+
+msgid "Parameter \"job_id\" cannot exceed length of %{job_id_max_size}"
+msgstr ""
+
+msgid "Parent"
+msgstr ""
+
+msgid "Parent epic doesn't exist."
+msgstr ""
+
+msgid "Parent epic is not present."
+msgstr ""
+
+msgid "Parsing error for param :embed_json. %{message}"
+msgstr ""
+
+msgid "Part of merge request changes"
+msgstr ""
+
+msgid "Partial token for reference only"
+msgstr ""
+
+msgid "Participants"
+msgstr ""
+
+msgid "Passed"
+msgstr ""
+
+msgid "Passed on"
+msgstr ""
+
+msgid "Password"
+msgstr ""
+
+msgid "Password (optional)"
+msgstr ""
+
+msgid "Password Policy Guidelines"
+msgstr ""
+
+msgid "Password authentication is unavailable."
+msgstr ""
+
+msgid "Password confirmation"
+msgstr ""
+
+msgid "Password successfully changed"
+msgstr ""
+
+msgid "Password was successfully updated. Please login with it"
+msgstr ""
+
+msgid "Passwords should be unique and not used for any other sites or services."
+msgstr ""
+
+msgid "Past due"
+msgstr ""
+
+msgid "Paste a machine public key here. Read more about how to generate it"
+msgstr ""
+
+msgid "Paste a machine public key here. Read more about how to generate it %{link_start}here%{link_end}"
+msgstr ""
+
+msgid "Paste epic link"
+msgstr ""
+
+msgid "Paste issue link"
+msgstr ""
+
+msgid "Paste your public SSH key, which is usually contained in the file '~/.ssh/id_ed25519.pub' or '~/.ssh/id_rsa.pub' and begins with 'ssh-ed25519' or 'ssh-rsa'. Don't use your private SSH key."
+msgstr ""
+
+msgid "Patch to apply"
+msgstr ""
+
+msgid "Path"
+msgstr ""
+
+msgid "Path:"
+msgstr ""
+
+msgid "Paths can contain wildcards, like */welcome"
+msgstr ""
+
+msgid "Pause"
+msgstr ""
+
+msgid "Pause replication"
+msgstr ""
+
+msgid "Paused Runners don't accept new jobs"
+msgstr ""
+
+msgid "Pending"
+msgstr ""
+
+msgid "People without permission will never get a notification and won't be able to comment."
+msgstr ""
+
+msgid "People without permission will never get a notification."
+msgstr ""
+
+msgid "Percent rollout (logged in users)"
+msgstr ""
+
+msgid "Percentage"
+msgstr ""
+
+msgid "Perform advanced options such as changing path, transferring, exporting, or removing the group."
+msgstr ""
+
+msgid "Perform common operations on GitLab project"
+msgstr ""
+
+msgid "Performance and resource management"
+msgstr ""
+
+msgid "Performance optimization"
+msgstr ""
+
+msgid "PerformanceBar|Bullet notifications"
+msgstr ""
+
+msgid "PerformanceBar|Download"
+msgstr ""
+
+msgid "PerformanceBar|Elasticsearch calls"
+msgstr ""
+
+msgid "PerformanceBar|Frontend resources"
+msgstr ""
+
+msgid "PerformanceBar|Gitaly calls"
+msgstr ""
+
+msgid "PerformanceBar|Redis calls"
+msgstr ""
+
+msgid "PerformanceBar|Rugged calls"
+msgstr ""
+
+msgid "PerformanceBar|SQL queries"
+msgstr ""
+
+msgid "PerformanceBar|trace"
+msgstr ""
+
+msgid "Permissions"
+msgstr ""
+
+msgid "Permissions, LFS, 2FA"
+msgstr ""
+
+msgid "Personal Access Token"
+msgstr ""
+
+msgid "Personal project creation is not allowed. Please contact your administrator with questions"
+msgstr ""
+
+msgid "Phabricator Server Import"
+msgstr ""
+
+msgid "Phabricator Server URL"
+msgstr ""
+
+msgid "Phabricator Tasks"
+msgstr ""
+
+msgid "Pick a name"
+msgstr ""
+
+msgid "Pin code"
+msgstr ""
+
+msgid "Pipeline"
+msgstr ""
+
+msgid "Pipeline %{label}"
+msgstr ""
+
+msgid "Pipeline %{label} for \"%{dataTitle}\""
+msgstr ""
+
+msgid "Pipeline Schedule"
+msgstr ""
+
+msgid "Pipeline Schedules"
+msgstr ""
+
+msgid "Pipeline minutes quota"
+msgstr ""
+
+msgid "Pipeline subscriptions"
+msgstr ""
+
+msgid "Pipeline triggers"
+msgstr ""
+
+msgid "Pipeline: %{status}"
+msgstr ""
+
+msgid "PipelineCharts|CI / CD Analytics"
+msgstr ""
+
+msgid "PipelineCharts|Failed:"
+msgstr ""
+
+msgid "PipelineCharts|Overall statistics"
+msgstr ""
+
+msgid "PipelineCharts|Success ratio:"
+msgstr ""
+
+msgid "PipelineCharts|Successful:"
+msgstr ""
+
+msgid "PipelineCharts|Total:"
+msgstr ""
+
+msgid "PipelineSchedules|Activated"
+msgstr ""
+
+msgid "PipelineSchedules|Active"
+msgstr ""
+
+msgid "PipelineSchedules|All"
+msgstr ""
+
+msgid "PipelineSchedules|Inactive"
+msgstr ""
+
+msgid "PipelineSchedules|Next Run"
+msgstr ""
+
+msgid "PipelineSchedules|None"
+msgstr ""
+
+msgid "PipelineSchedules|Provide a short description for this pipeline"
+msgstr ""
+
+msgid "PipelineSchedules|Take ownership"
+msgstr ""
+
+msgid "PipelineSchedules|Target"
+msgstr ""
+
+msgid "PipelineSchedules|Variables"
+msgstr ""
+
+msgid "PipelineSheduleIntervalPattern|Custom"
+msgstr ""
+
+msgid "PipelineStatusTooltip|Pipeline: %{ciStatus}"
+msgstr ""
+
+msgid "PipelineStatusTooltip|Pipeline: %{ci_status}"
+msgstr ""
+
+msgid "Pipelines"
+msgstr ""
+
+msgid "Pipelines charts"
+msgstr ""
+
+msgid "Pipelines emails"
+msgstr ""
+
+msgid "Pipelines for last month (%{oneMonthAgo} - %{today})"
+msgstr ""
+
+msgid "Pipelines for last week (%{oneWeekAgo} - %{today})"
+msgstr ""
+
+msgid "Pipelines for last year"
+msgstr ""
+
+msgid "Pipelines for merge requests are configured. A detached pipeline runs in the context of the merge request, and not against the merged result. Learn more in the documentation for Pipelines for Merged Results."
+msgstr ""
+
+msgid "Pipelines must succeed for merge requests to be eligible to merge. Please enable pipelines for this project to continue. For more information, see the %{linkStart}documentation.%{linkEnd}"
+msgstr ""
+
+msgid "Pipelines settings for '%{project_name}' were successfully updated."
+msgstr ""
+
+msgid "Pipelines|API"
+msgstr ""
+
+msgid "Pipelines|Build with confidence"
+msgstr ""
+
+msgid "Pipelines|CI Lint"
+msgstr ""
+
+msgid "Pipelines|Child pipeline"
+msgstr ""
+
+msgid "Pipelines|Clear Runner Caches"
+msgstr ""
+
+msgid "Pipelines|Continuous Integration can help catch bugs by running your tests automatically, while Continuous Deployment can help you deliver code to your product environment."
+msgstr ""
+
+msgid "Pipelines|Get started with Pipelines"
+msgstr ""
+
+msgid "Pipelines|Group %{namespace_name} has %{percentage}%% or less Shared Runner Pipeline minutes remaining. Once it runs out, no new jobs or pipelines in its projects will run."
+msgstr ""
+
+msgid "Pipelines|Group %{namespace_name} has exceeded its pipeline minutes quota. Unless you buy additional pipeline minutes, no new jobs or pipelines in its projects will run."
+msgstr ""
+
+msgid "Pipelines|Loading Pipelines"
+msgstr ""
+
+msgid "Pipelines|Project cache successfully reset."
+msgstr ""
+
+msgid "Pipelines|Run Pipeline"
+msgstr ""
+
+msgid "Pipelines|Something went wrong while cleaning runners cache."
+msgstr ""
+
+msgid "Pipelines|There are currently no finished pipelines."
+msgstr ""
+
+msgid "Pipelines|There are currently no pipelines."
+msgstr ""
+
+msgid "Pipelines|There was an error fetching the pipelines. Try again in a few moments or contact your support team."
+msgstr ""
+
+msgid "Pipelines|This is a child pipeline within the parent pipeline"
+msgstr ""
+
+msgid "Pipelines|This project is not currently set up to run pipelines."
+msgstr ""
+
+msgid "Pipelines|parent"
+msgstr ""
+
+msgid "Pipeline|Branch name"
+msgstr ""
+
+msgid "Pipeline|Canceled"
+msgstr ""
+
+msgid "Pipeline|Commit"
+msgstr ""
+
+msgid "Pipeline|Could not retrieve the pipeline status. For troubleshooting steps, read the %{linkStart}documentation%{linkEnd}."
+msgstr ""
+
+msgid "Pipeline|Coverage"
+msgstr ""
+
+msgid "Pipeline|Created"
+msgstr ""
+
+msgid "Pipeline|Date"
+msgstr ""
+
+msgid "Pipeline|Detached merge request pipeline"
+msgstr ""
+
+msgid "Pipeline|Duration"
+msgstr ""
+
+msgid "Pipeline|Existing branch name or tag"
+msgstr ""
+
+msgid "Pipeline|Failed"
+msgstr ""
+
+msgid "Pipeline|Key"
+msgstr ""
+
+msgid "Pipeline|Manual"
+msgstr ""
+
+msgid "Pipeline|Merge train pipeline"
+msgstr ""
+
+msgid "Pipeline|Merged result pipeline"
+msgstr ""
+
+msgid "Pipeline|No pipeline has been run for this commit."
+msgstr ""
+
+msgid "Pipeline|Passed"
+msgstr ""
+
+msgid "Pipeline|Pending"
+msgstr ""
+
+msgid "Pipeline|Pipeline"
+msgstr ""
+
+msgid "Pipeline|Pipelines"
+msgstr ""
+
+msgid "Pipeline|Raw text search is not currently supported. Please use the available search tokens."
+msgstr ""
+
+msgid "Pipeline|Run Pipeline"
+msgstr ""
+
+msgid "Pipeline|Run for"
+msgstr ""
+
+msgid "Pipeline|Running"
+msgstr ""
+
+msgid "Pipeline|Search branches"
+msgstr ""
+
+msgid "Pipeline|Skipped"
+msgstr ""
+
+msgid "Pipeline|Specify variable values to be used in this run. The values specified in %{settings_link} will be used by default."
+msgstr ""
+
+msgid "Pipeline|Stages"
+msgstr ""
+
+msgid "Pipeline|Status"
+msgstr ""
+
+msgid "Pipeline|Stop pipeline"
+msgstr ""
+
+msgid "Pipeline|Stop pipeline #%{pipelineId}?"
+msgstr ""
+
+msgid "Pipeline|Tag name"
+msgstr ""
+
+msgid "Pipeline|Trigger author"
+msgstr ""
+
+msgid "Pipeline|Triggerer"
+msgstr ""
+
+msgid "Pipeline|Value"
+msgstr ""
+
+msgid "Pipeline|Variables"
+msgstr ""
+
+msgid "Pipeline|You’re about to stop pipeline %{pipelineId}."
+msgstr ""
+
+msgid "Pipeline|for"
+msgstr ""
+
+msgid "Pipeline|on"
+msgstr ""
+
+msgid "Pipeline|with stage"
+msgstr ""
+
+msgid "Pipeline|with stages"
+msgstr ""
+
+msgid "PivotalTrackerService|Comma-separated list of branches which will be automatically inspected. Leave blank to include all branches."
+msgstr ""
+
+msgid "PivotalTrackerService|Pivotal Tracker API token."
+msgstr ""
+
+msgid "PivotalTrackerService|Project Management Software (Source Commits Endpoint)"
+msgstr ""
+
+msgid "Plain diff"
+msgstr ""
+
+msgid "Plan"
+msgstr ""
+
+msgid "Plan:"
+msgstr ""
+
+msgid "PlantUML"
+msgstr ""
+
+msgid "Play"
+msgstr ""
+
+msgid "Play all manual"
+msgstr ""
+
+msgid "Please %{link_to_register} or %{link_to_sign_in} to comment"
+msgstr ""
+
+msgid "Please %{startTagRegister}register%{endRegisterTag} or %{startTagSignIn}sign in%{endSignInTag} to reply"
+msgstr ""
+
+msgid "Please accept the Terms of Service before continuing."
+msgstr ""
+
+msgid "Please add a comment in the text area above"
+msgstr ""
+
+msgid "Please add a list to your board first"
+msgstr ""
+
+msgid "Please check the configuration file for this chart"
+msgstr ""
+
+msgid "Please check the configuration file to ensure that a collection of charts has been declared."
+msgstr ""
+
+msgid "Please check the configuration file to ensure that it is available and the YAML is valid"
+msgstr ""
+
+msgid "Please check your email (%{email}) to verify that you own this address and unlock the power of CI/CD. Didn't receive it? %{resend_link}. Wrong email address? %{update_link}."
+msgstr ""
+
+msgid "Please choose a file"
+msgstr ""
+
+msgid "Please choose a group URL with no special characters."
+msgstr ""
+
+msgid "Please complete your profile with email address"
+msgstr ""
+
+msgid "Please convert %{linkStart}them to Git%{linkEnd}, and go through the %{linkToImportFlow} again."
+msgstr ""
+
+msgid "Please convert them to %{link_to_git}, and go through the %{link_to_import_flow} again."
+msgstr ""
+
+msgid "Please convert them to Git on Google Code, and go through the %{link_to_import_flow} again."
+msgstr ""
+
+msgid "Please create a password for your new account."
+msgstr ""
+
+msgid "Please create a username with only alphanumeric characters."
+msgstr ""
+
+msgid "Please create an index before enabling indexing"
+msgstr ""
+
+msgid "Please enable and migrate to hashed storage to avoid security issues and ensure data integrity. %{migrate_link}"
+msgstr ""
+
+msgid "Please ensure your account's %{account_link_start}recovery settings%{account_link_end} are up to date."
+msgstr ""
+
+msgid "Please enter a non-negative number"
+msgstr ""
+
+msgid "Please enter a number greater than %{number} (from the project settings)"
+msgstr ""
+
+msgid "Please enter a valid number"
+msgstr ""
+
+msgid "Please enter or upload a license."
+msgstr ""
+
+msgid "Please fill in a descriptive name for your group."
+msgstr ""
+
+msgid "Please follow the %{link_start}Let's Encrypt troubleshooting instructions%{link_end} to re-obtain your Let's Encrypt certificate."
+msgstr ""
+
+msgid "Please follow the Let's Encrypt troubleshooting instructions to re-obtain your Let's Encrypt certificate: %{docs_url}."
+msgstr ""
+
+msgid "Please migrate all existing projects to hashed storage to avoid security issues and ensure data integrity. %{migrate_link}"
+msgstr ""
+
+msgid "Please note that this application is not provided by GitLab and you should verify its authenticity before allowing access."
+msgstr ""
+
+msgid "Please provide a name"
+msgstr ""
+
+msgid "Please provide a valid URL"
+msgstr ""
+
+msgid "Please provide a valid email address."
+msgstr ""
+
+msgid "Please provide attributes to update"
+msgstr ""
+
+msgid "Please refer to <a href=\"%{docs_url}\">%{docs_url}</a>"
+msgstr ""
+
+msgid "Please retype the email address."
+msgstr ""
+
+msgid "Please select"
+msgstr ""
+
+msgid "Please select a Jira project"
+msgstr ""
+
+msgid "Please select a country"
+msgstr ""
+
+msgid "Please select a file"
+msgstr ""
+
+msgid "Please select a group."
+msgstr ""
+
+msgid "Please select a valid target branch"
+msgstr ""
+
+msgid "Please select and add a member"
+msgstr ""
+
+msgid "Please select at least one filter to see results"
+msgstr ""
+
+msgid "Please set a new password before proceeding."
+msgstr ""
+
+msgid "Please solve the reCAPTCHA"
+msgstr ""
+
+msgid "Please try again"
+msgstr ""
+
+msgid "Please type %{phrase_code} to proceed or close this modal to cancel."
+msgstr ""
+
+msgid "Please upgrade PostgreSQL to version 9.6 or greater. The status of the replication cannot be determined reliably with the current version."
+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 moment, this page will automatically refresh when ready."
+msgstr ""
+
+msgid "Please wait while we connect to your repository. Refresh at will."
+msgstr ""
+
+msgid "Please wait while we import the repository for you. Refresh at will."
+msgstr ""
+
+msgid "Plugins directory is deprecated and will be removed in 14.0. Please move this file into /file_hooks directory."
+msgstr ""
+
+msgid "Pod does not exist"
+msgstr ""
+
+msgid "Pod not found"
+msgstr ""
+
+msgid "Pods in use"
+msgstr ""
+
+msgid "Point to any links you like: documentation, built binaries, or other related materials. These can be internal or external links from your GitLab instance. Duplicate URLs are not allowed."
+msgstr ""
+
+msgid "Pre-defined push rules."
+msgstr ""
+
+msgid "Preferences"
+msgstr ""
+
+msgid "Preferences saved."
+msgstr ""
+
+msgid "Preferences|Behavior"
+msgstr ""
+
+msgid "Preferences|Choose between fixed (max. 1280px) and fluid (%{percentage}) application layout."
+msgstr ""
+
+msgid "Preferences|Choose what content you want to see on a project’s overview page."
+msgstr ""
+
+msgid "Preferences|Customize integrations with third party services."
+msgstr ""
+
+msgid "Preferences|Customize the appearance of the application header and navigation sidebar."
+msgstr ""
+
+msgid "Preferences|Default dashboard"
+msgstr ""
+
+msgid "Preferences|Display time in 24-hour format"
+msgstr ""
+
+msgid "Preferences|Enable integrated code intelligence on code views"
+msgstr ""
+
+msgid "Preferences|For example: 30 mins ago."
+msgstr ""
+
+msgid "Preferences|Integrations"
+msgstr ""
+
+msgid "Preferences|Layout width"
+msgstr ""
+
+msgid "Preferences|Must be a number between %{min} and %{max}"
+msgstr ""
+
+msgid "Preferences|Navigation theme"
+msgstr ""
+
+msgid "Preferences|Project overview content"
+msgstr ""
+
+msgid "Preferences|Render whitespace characters in the Web IDE"
+msgstr ""
+
+msgid "Preferences|Show whitespace changes in diffs"
+msgstr ""
+
+msgid "Preferences|Sourcegraph"
+msgstr ""
+
+msgid "Preferences|Syntax highlighting theme"
+msgstr ""
+
+msgid "Preferences|Tab width"
+msgstr ""
+
+msgid "Preferences|These settings will update how dates and times are displayed for you."
+msgstr ""
+
+msgid "Preferences|This feature is experimental and translations are not complete yet"
+msgstr ""
+
+msgid "Preferences|This setting allows you to customize the appearance of the syntax."
+msgstr ""
+
+msgid "Preferences|This setting allows you to customize the behavior of the system layout and default views."
+msgstr ""
+
+msgid "Preferences|Time display"
+msgstr ""
+
+msgid "Preferences|Time format"
+msgstr ""
+
+msgid "Preferences|Time preferences"
+msgstr ""
+
+msgid "Preferences|Use relative times"
+msgstr ""
+
+msgid "Press %{key}-C to copy"
+msgstr ""
+
+msgid "Prev"
+msgstr ""
+
+msgid "Prevent adding new members to project membership within this group"
+msgstr ""
+
+msgid "Prevent approval of merge requests by merge request author"
+msgstr ""
+
+msgid "Prevent approval of merge requests by merge request committers"
+msgstr ""
+
+msgid "Prevent environment from auto-stopping"
+msgstr ""
+
+msgid "Prevent users from changing their profile name"
+msgstr ""
+
+msgid "Prevent users from modifying merge request approvers list"
+msgstr ""
+
+msgid "Prevent users from performing write operations on GitLab while performing maintenance."
+msgstr ""
+
+msgid "Preview"
+msgstr ""
+
+msgid "Preview Markdown"
+msgstr ""
+
+msgid "Preview changes"
+msgstr ""
+
+msgid "Preview payload"
+msgstr ""
+
+msgid "Previous Artifacts"
+msgstr ""
+
+msgid "Previous commit"
+msgstr ""
+
+msgid "Previous file in diff"
+msgstr ""
+
+msgid "Previous unresolved discussion"
+msgstr ""
+
+msgid "Primary"
+msgstr ""
+
+msgid "Prioritize"
+msgstr ""
+
+msgid "Prioritize label"
+msgstr ""
+
+msgid "Prioritized Labels"
+msgstr ""
+
+msgid "Prioritized label"
+msgstr ""
+
+msgid "Private"
+msgstr ""
+
+msgid "Private - Project access must be granted explicitly to each user. If this project is part of a group, access will be granted to members of the group."
+msgstr ""
+
+msgid "Private - The group and its projects can only be viewed by members."
+msgstr ""
+
+msgid "Private group(s)"
+msgstr ""
+
+msgid "Private profile"
+msgstr ""
+
+msgid "Private projects Minutes cost factor"
+msgstr ""
+
+msgid "Private projects can be created in your personal namespace with:"
+msgstr ""
+
+msgid "Proceed"
+msgstr ""
+
+msgid "Productivity"
+msgstr ""
+
+msgid "Productivity Analytics"
+msgstr ""
+
+msgid "Productivity analytics can help identify the problems that are delaying your team"
+msgstr ""
+
+msgid "ProductivityAanalytics|Merge requests"
+msgstr ""
+
+msgid "ProductivityAanalytics|is earlier than the allowed minimum date"
+msgstr ""
+
+msgid "ProductivityAnalytics|Ascending"
+msgstr ""
+
+msgid "ProductivityAnalytics|Days"
+msgstr ""
+
+msgid "ProductivityAnalytics|Days to merge"
+msgstr ""
+
+msgid "ProductivityAnalytics|Descending"
+msgstr ""
+
+msgid "ProductivityAnalytics|Hours"
+msgstr ""
+
+msgid "ProductivityAnalytics|List"
+msgstr ""
+
+msgid "ProductivityAnalytics|Merge Requests"
+msgstr ""
+
+msgid "ProductivityAnalytics|Merge date"
+msgstr ""
+
+msgid "ProductivityAnalytics|Merge requests"
+msgstr ""
+
+msgid "ProductivityAnalytics|Time to merge"
+msgstr ""
+
+msgid "ProductivityAnalytics|Trendline"
+msgstr ""
+
+msgid "ProductivityAnalytics|is earlier than the given merged at after date"
+msgstr ""
+
+msgid "Profile"
+msgstr ""
+
+msgid "Profile Settings"
+msgstr ""
+
+msgid "ProfileSession|on"
+msgstr ""
+
+msgid "Profiles| You are about to permanently delete %{yourAccount}, and all of the issues, merge requests, and groups linked to your account. Once you confirm %{deleteAccount}, it cannot be undone or recovered."
+msgstr ""
+
+msgid "Profiles| You are going to change the username %{currentUsernameBold} to %{newUsernameBold}. Profile and projects will be redirected to the %{newUsername} namespace but this redirect will expire once the %{currentUsername} namespace is registered by another user or group. Please update your Git repository remotes as soon as possible."
+msgstr ""
+
+msgid "Profiles|@username"
+msgstr ""
+
+msgid "Profiles|Account scheduled for removal."
+msgstr ""
+
+msgid "Profiles|Activate signin with one of the following services"
+msgstr ""
+
+msgid "Profiles|Active"
+msgstr ""
+
+msgid "Profiles|Add key"
+msgstr ""
+
+msgid "Profiles|Add status emoji"
+msgstr ""
+
+msgid "Profiles|Avatar cropper"
+msgstr ""
+
+msgid "Profiles|Avatar will be removed. Are you sure?"
+msgstr ""
+
+msgid "Profiles|Bio"
+msgstr ""
+
+msgid "Profiles|Change username"
+msgstr ""
+
+msgid "Profiles|Changing your username can have unintended side effects."
+msgstr ""
+
+msgid "Profiles|Choose file..."
+msgstr ""
+
+msgid "Profiles|Choose to show contributions of private projects on your public profile without any project, repository or organization information"
+msgstr ""
+
+msgid "Profiles|City, country"
+msgstr ""
+
+msgid "Profiles|Clear status"
+msgstr ""
+
+msgid "Profiles|Click on icon to activate signin with one of the following services"
+msgstr ""
+
+msgid "Profiles|Commit email"
+msgstr ""
+
+msgid "Profiles|Connect"
+msgstr ""
+
+msgid "Profiles|Connected Accounts"
+msgstr ""
+
+msgid "Profiles|Current path: %{path}"
+msgstr ""
+
+msgid "Profiles|Current status"
+msgstr ""
+
+msgid "Profiles|Default notification email"
+msgstr ""
+
+msgid "Profiles|Delete Account"
+msgstr ""
+
+msgid "Profiles|Delete account"
+msgstr ""
+
+msgid "Profiles|Delete your account?"
+msgstr ""
+
+msgid "Profiles|Deleting an account has the following effects:"
+msgstr ""
+
+msgid "Profiles|Disconnect"
+msgstr ""
+
+msgid "Profiles|Do not show on profile"
+msgstr ""
+
+msgid "Profiles|Don't display activity-related personal information on your profiles"
+msgstr ""
+
+msgid "Profiles|Edit Profile"
+msgstr ""
+
+msgid "Profiles|Enter your name, so people you know can recognize you"
+msgstr ""
+
+msgid "Profiles|Expires at"
+msgstr ""
+
+msgid "Profiles|Expires:"
+msgstr ""
+
+msgid "Profiles|Feed token was successfully reset"
+msgstr ""
+
+msgid "Profiles|Full name"
+msgstr ""
+
+msgid "Profiles|Give your individual key a title"
+msgstr ""
+
+msgid "Profiles|Include private contributions on my profile"
+msgstr ""
+
+msgid "Profiles|Incoming email token was successfully reset"
+msgstr ""
+
+msgid "Profiles|Increase your account's security by enabling Two-Factor Authentication (2FA)"
+msgstr ""
+
+msgid "Profiles|Invalid password"
+msgstr ""
+
+msgid "Profiles|Invalid username"
+msgstr ""
+
+msgid "Profiles|Key"
+msgstr ""
+
+msgid "Profiles|Last used:"
+msgstr ""
+
+msgid "Profiles|Learn more"
+msgstr ""
+
+msgid "Profiles|Location"
+msgstr ""
+
+msgid "Profiles|Made a private contribution"
+msgstr ""
+
+msgid "Profiles|Main settings"
+msgstr ""
+
+msgid "Profiles|No file chosen"
+msgstr ""
+
+msgid "Profiles|Notification email"
+msgstr ""
+
+msgid "Profiles|Organization"
+msgstr ""
+
+msgid "Profiles|Path"
+msgstr ""
+
+msgid "Profiles|Position and size your new avatar"
+msgstr ""
+
+msgid "Profiles|Primary email"
+msgstr ""
+
+msgid "Profiles|Private contributions"
+msgstr ""
+
+msgid "Profiles|Profile was successfully updated"
+msgstr ""
+
+msgid "Profiles|Public Avatar"
+msgstr ""
+
+msgid "Profiles|Public email"
+msgstr ""
+
+msgid "Profiles|Remove avatar"
+msgstr ""
+
+msgid "Profiles|Set new profile picture"
+msgstr ""
+
+msgid "Profiles|Social sign-in"
+msgstr ""
+
+msgid "Profiles|Some options are unavailable for LDAP accounts"
+msgstr ""
+
+msgid "Profiles|Static object token was successfully reset"
+msgstr ""
+
+msgid "Profiles|Tell us about yourself in fewer than 250 characters"
+msgstr ""
+
+msgid "Profiles|The ability to update your name has been disabled by your administrator."
+msgstr ""
+
+msgid "Profiles|The maximum file size allowed is 200KB."
+msgstr ""
+
+msgid "Profiles|This doesn't look like a public SSH key, are you sure you want to add it?"
+msgstr ""
+
+msgid "Profiles|This email will be displayed on your public profile"
+msgstr ""
+
+msgid "Profiles|This email will be used for web based operations, such as edits and merges. %{commit_email_link_start}Learn more%{commit_email_link_end}"
+msgstr ""
+
+msgid "Profiles|This emoji and message will appear on your profile and throughout the interface."
+msgstr ""
+
+msgid "Profiles|This information will appear on your profile"
+msgstr ""
+
+msgid "Profiles|Time settings"
+msgstr ""
+
+msgid "Profiles|Two-Factor Authentication"
+msgstr ""
+
+msgid "Profiles|Type your %{confirmationValue} to confirm:"
+msgstr ""
+
+msgid "Profiles|Typically starts with \"ssh-ed25519 …\" or \"ssh-rsa …\""
+msgstr ""
+
+msgid "Profiles|Update profile settings"
+msgstr ""
+
+msgid "Profiles|Update username"
+msgstr ""
+
+msgid "Profiles|Upload new avatar"
+msgstr ""
+
+msgid "Profiles|Use a private email - %{email}"
+msgstr ""
+
+msgid "Profiles|User ID"
+msgstr ""
+
+msgid "Profiles|Username change failed - %{message}"
+msgstr ""
+
+msgid "Profiles|Username successfully changed"
+msgstr ""
+
+msgid "Profiles|Using emojis in names seems fun, but please try to set a status message instead"
+msgstr ""
+
+msgid "Profiles|What's your status?"
+msgstr ""
+
+msgid "Profiles|Who you represent or work for"
+msgstr ""
+
+msgid "Profiles|You can change your avatar here"
+msgstr ""
+
+msgid "Profiles|You can change your avatar here or remove the current avatar to revert to %{gravatar_link}"
+msgstr ""
+
+msgid "Profiles|You can set your current timezone here"
+msgstr ""
+
+msgid "Profiles|You can upload your avatar here"
+msgstr ""
+
+msgid "Profiles|You can upload your avatar here or change it at %{gravatar_link}"
+msgstr ""
+
+msgid "Profiles|You don't have access to delete this user."
+msgstr ""
+
+msgid "Profiles|You must transfer ownership or delete these groups before you can delete your account."
+msgstr ""
+
+msgid "Profiles|Your LinkedIn profile name from linkedin.com/in/profilename"
+msgstr ""
+
+msgid "Profiles|Your account is currently an owner in these groups:"
+msgstr ""
+
+msgid "Profiles|Your email address was automatically set based on your %{provider_label} account"
+msgstr ""
+
+msgid "Profiles|Your key has expired"
+msgstr ""
+
+msgid "Profiles|Your location was automatically set based on your %{provider_label} account"
+msgstr ""
+
+msgid "Profiles|Your name was automatically set based on your %{provider_label} account, so people you know can recognize you"
+msgstr ""
+
+msgid "Profiles|Your status"
+msgstr ""
+
+msgid "Profiles|e.g. My MacBook key"
+msgstr ""
+
+msgid "Profiles|username"
+msgstr ""
+
+msgid "Profiles|website.com"
+msgstr ""
+
+msgid "Profiles|your account"
+msgstr ""
+
+msgid "Profile|%{job_title} at %{organization}"
+msgstr ""
+
+msgid "Profiling - Performance bar"
+msgstr ""
+
+msgid "Programming languages used in this repository"
+msgstr ""
+
+msgid "Progress"
+msgstr ""
+
+msgid "Project"
+msgstr ""
+
+msgid "Project \"%{name}\" is no longer available. Select another project to continue."
+msgstr ""
+
+msgid "Project %{project_repo} could not be found"
+msgstr ""
+
+msgid "Project & Group can not be assigned at the same time"
+msgstr ""
+
+msgid "Project '%{project_name}' is being imported."
+msgstr ""
+
+msgid "Project '%{project_name}' is in the process of being deleted."
+msgstr ""
+
+msgid "Project '%{project_name}' is restored."
+msgstr ""
+
+msgid "Project '%{project_name}' queued for deletion."
+msgstr ""
+
+msgid "Project '%{project_name}' was successfully created."
+msgstr ""
+
+msgid "Project '%{project_name}' was successfully updated."
+msgstr ""
+
+msgid "Project '%{project_name}' will be deleted on %{date}"
+msgstr ""
+
+msgid "Project Access Tokens"
+msgstr ""
+
+msgid "Project Audit Events"
+msgstr ""
+
+msgid "Project Badges"
+msgstr ""
+
+msgid "Project Files"
+msgstr ""
+
+msgid "Project ID"
+msgstr ""
+
+msgid "Project URL"
+msgstr ""
+
+msgid "Project access must be granted explicitly to each user. If this project is part of a group, access will be granted to members of the group."
+msgstr ""
+
+msgid "Project already deleted"
+msgstr ""
+
+msgid "Project and wiki repositories"
+msgstr ""
+
+msgid "Project avatar"
+msgstr ""
+
+msgid "Project cannot be shared with the group it is in or one of its ancestors."
+msgstr ""
+
+msgid "Project clone URL"
+msgstr ""
+
+msgid "Project configuration, including services"
+msgstr ""
+
+msgid "Project description (optional)"
+msgstr ""
+
+msgid "Project details"
+msgstr ""
+
+msgid "Project does not exist or you don't have permission to perform this action"
+msgstr ""
+
+msgid "Project export could not be deleted."
+msgstr ""
+
+msgid "Project export enabled"
+msgstr ""
+
+msgid "Project export has been deleted."
+msgstr ""
+
+msgid "Project export link has expired. Please generate a new export from your project settings."
+msgstr ""
+
+msgid "Project export started. A download link will be sent by email and made available on this page."
+msgstr ""
+
+msgid "Project has too many %{label_for_message} to search"
+msgstr ""
+
+msgid "Project is required when cluster_type is :project"
+msgstr ""
+
+msgid "Project members"
+msgstr ""
+
+msgid "Project milestone"
+msgstr ""
+
+msgid "Project name"
+msgstr ""
+
+msgid "Project name suffix"
+msgstr ""
+
+msgid "Project name suffix is a user-defined string which will be appended to the project path, and will form the Service Desk email address."
+msgstr ""
+
+msgid "Project order will not be saved as local storage is not available."
+msgstr ""
+
+msgid "Project overview"
+msgstr ""
+
+msgid "Project path"
+msgstr ""
+
+msgid "Project scanning help page"
+msgstr ""
+
+msgid "Project security status"
+msgstr ""
+
+msgid "Project security status help page"
+msgstr ""
+
+msgid "Project slug"
+msgstr ""
+
+msgid "Project uploads"
+msgstr ""
+
+msgid "Project visibility level will be changed to match namespace rules when transferring to a group."
+msgstr ""
+
+msgid "Project: %{name}"
+msgstr ""
+
+msgid "ProjectActivityRSS|Subscribe"
+msgstr ""
+
+msgid "ProjectCreationLevel|Allowed to create projects"
+msgstr ""
+
+msgid "ProjectCreationLevel|Default project creation protection"
+msgstr ""
+
+msgid "ProjectCreationLevel|Developers + Maintainers"
+msgstr ""
+
+msgid "ProjectCreationLevel|Maintainers"
+msgstr ""
+
+msgid "ProjectCreationLevel|No one"
+msgstr ""
+
+msgid "ProjectFileTree|Name"
+msgstr ""
+
+msgid "ProjectLastActivity|Never"
+msgstr ""
+
+msgid "ProjectLifecycle|Stage"
+msgstr ""
+
+msgid "ProjectOverview|Fork"
+msgstr ""
+
+msgid "ProjectOverview|Forks"
+msgstr ""
+
+msgid "ProjectOverview|Go to your fork"
+msgstr ""
+
+msgid "ProjectOverview|Star"
+msgstr ""
+
+msgid "ProjectOverview|Starrer"
+msgstr ""
+
+msgid "ProjectOverview|Starrers"
+msgstr ""
+
+msgid "ProjectOverview|Unstar"
+msgstr ""
+
+msgid "ProjectOverview|You have reached your project limit"
+msgstr ""
+
+msgid "ProjectOverview|You must sign in to star a project"
+msgstr ""
+
+msgid "ProjectPage|Project ID: %{project_id}"
+msgstr ""
+
+msgid "ProjectSelect| or group"
+msgstr ""
+
+msgid "ProjectSelect|Search for project"
+msgstr ""
+
+msgid "ProjectService|%{service_title}: status off"
+msgstr ""
+
+msgid "ProjectService|%{service_title}: status on"
+msgstr ""
+
+msgid "ProjectService|Comment"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered by a push to the repository"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a commit is created/updated"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a confidential issue is created/updated/closed"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a deployment finishes"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a merge request is created/updated/merged"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a new tag is pushed to the repository"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a new, unique alert is recorded"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a pipeline status changes"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a wiki page is created/updated"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when an issue is created/updated/closed"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when someone adds a comment"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when someone adds a comment on a confidential issue"
+msgstr ""
+
+msgid "ProjectService|Perform common operations on GitLab project: %{project_name}"
+msgstr ""
+
+msgid "ProjectService|To set up this service:"
+msgstr ""
+
+msgid "ProjectSettings|Additional merge request capabilities that influence how and when merges will be performed"
+msgstr ""
+
+msgid "ProjectSettings|All discussions must be resolved"
+msgstr ""
+
+msgid "ProjectSettings|Allow users to make copies of your repository to a new project"
+msgstr ""
+
+msgid "ProjectSettings|Allow users to request access"
+msgstr ""
+
+msgid "ProjectSettings|Automatically resolve merge request diff discussions when they become outdated"
+msgstr ""
+
+msgid "ProjectSettings|Badges"
+msgstr ""
+
+msgid "ProjectSettings|Build, test, and deploy your changes"
+msgstr ""
+
+msgid "ProjectSettings|Choose your merge method, merge options, merge checks, and merge suggestions."
+msgstr ""
+
+msgid "ProjectSettings|Choose your merge method, merge options, merge checks, merge suggestions, and set up a default description template for merge requests."
+msgstr ""
+
+msgid "ProjectSettings|Contact an admin to change this setting."
+msgstr ""
+
+msgid "ProjectSettings|Container registry"
+msgstr ""
+
+msgid "ProjectSettings|Customize your project badges."
+msgstr ""
+
+msgid "ProjectSettings|Disable email notifications"
+msgstr ""
+
+msgid "ProjectSettings|Enable 'Delete source branch' option by default"
+msgstr ""
+
+msgid "ProjectSettings|Enable merge trains and pipelines for merged results"
+msgstr ""
+
+msgid "ProjectSettings|Every merge creates a merge commit"
+msgstr ""
+
+msgid "ProjectSettings|Every project can have its own space to store its Docker images"
+msgstr ""
+
+msgid "ProjectSettings|Every project can have its own space to store its packages"
+msgstr ""
+
+msgid "ProjectSettings|Everyone"
+msgstr ""
+
+msgid "ProjectSettings|Existing merge requests and protected branches are not affected"
+msgstr ""
+
+msgid "ProjectSettings|Failed to protect the tag"
+msgstr ""
+
+msgid "ProjectSettings|Failed to update tag!"
+msgstr ""
+
+msgid "ProjectSettings|Fast-forward merge"
+msgstr ""
+
+msgid "ProjectSettings|Fast-forward merges only"
+msgstr ""
+
+msgid "ProjectSettings|Forks"
+msgstr ""
+
+msgid "ProjectSettings|Git Large File Storage (LFS)"
+msgstr ""
+
+msgid "ProjectSettings|Internal"
+msgstr ""
+
+msgid "ProjectSettings|Issues"
+msgstr ""
+
+msgid "ProjectSettings|LFS objects from this repository are still available to forks. %{linkStart}How do I remove them?%{linkEnd}"
+msgstr ""
+
+msgid "ProjectSettings|Learn more about badges."
+msgstr ""
+
+msgid "ProjectSettings|Lightweight issue tracking system for this project"
+msgstr ""
+
+msgid "ProjectSettings|Manages large files such as audio, video, and graphics files"
+msgstr ""
+
+msgid "ProjectSettings|Merge checks"
+msgstr ""
+
+msgid "ProjectSettings|Merge commit"
+msgstr ""
+
+msgid "ProjectSettings|Merge commit with semi-linear history"
+msgstr ""
+
+msgid "ProjectSettings|Merge method"
+msgstr ""
+
+msgid "ProjectSettings|Merge options"
+msgstr ""
+
+msgid "ProjectSettings|Merge requests"
+msgstr ""
+
+msgid "ProjectSettings|Merge suggestions"
+msgstr ""
+
+msgid "ProjectSettings|No merge commits are created"
+msgstr ""
+
+msgid "ProjectSettings|Note: the container registry is always visible when a project is public"
+msgstr ""
+
+msgid "ProjectSettings|Only signed commits can be pushed to this repository."
+msgstr ""
+
+msgid "ProjectSettings|Packages"
+msgstr ""
+
+msgid "ProjectSettings|Pages"
+msgstr ""
+
+msgid "ProjectSettings|Pages for project documentation"
+msgstr ""
+
+msgid "ProjectSettings|Pipelines"
+msgstr ""
+
+msgid "ProjectSettings|Pipelines for merge requests must be enabled in the CI/CD configuration file, or pipelines could be unresolvable or dropped"
+msgstr ""
+
+msgid "ProjectSettings|Pipelines must succeed"
+msgstr ""
+
+msgid "ProjectSettings|Pipelines need to be configured to enable this feature."
+msgstr ""
+
+msgid "ProjectSettings|Private"
+msgstr ""
+
+msgid "ProjectSettings|Project visibility"
+msgstr ""
+
+msgid "ProjectSettings|Public"
+msgstr ""
+
+msgid "ProjectSettings|Repository"
+msgstr ""
+
+msgid "ProjectSettings|Share code pastes with others out of Git repository"
+msgstr ""
+
+msgid "ProjectSettings|Show default award emojis"
+msgstr ""
+
+msgid "ProjectSettings|Show link to create/view merge request when pushing from the command line"
+msgstr ""
+
+msgid "ProjectSettings|Skipped pipelines are considered successful"
+msgstr ""
+
+msgid "ProjectSettings|Snippets"
+msgstr ""
+
+msgid "ProjectSettings|Submit changes to be merged upstream"
+msgstr ""
+
+msgid "ProjectSettings|The commit message used to apply merge request suggestions"
+msgstr ""
+
+msgid "ProjectSettings|The variables GitLab supports:"
+msgstr ""
+
+msgid "ProjectSettings|These checks must pass before merge requests can be merged"
+msgstr ""
+
+msgid "ProjectSettings|This introduces the risk of merging changes that will not pass the pipeline."
+msgstr ""
+
+msgid "ProjectSettings|This setting is applied on the server level and can be overridden by an admin."
+msgstr ""
+
+msgid "ProjectSettings|This setting is applied on the server level but has been overridden for this project."
+msgstr ""
+
+msgid "ProjectSettings|This setting will be applied to all projects unless overridden by an admin."
+msgstr ""
+
+msgid "ProjectSettings|This setting will override user notification preferences for all project members."
+msgstr ""
+
+msgid "ProjectSettings|This will dictate the commit history when you merge a merge request"
+msgstr ""
+
+msgid "ProjectSettings|Users can only push commits to this repository that were committed with one of their own verified emails."
+msgstr ""
+
+msgid "ProjectSettings|View and edit files in this project"
+msgstr ""
+
+msgid "ProjectSettings|View and edit files in this project. Non-project members will only have read access"
+msgstr ""
+
+msgid "ProjectSettings|When conflicts arise the user is given the option to rebase"
+msgstr ""
+
+msgid "ProjectSettings|When enabled, issues, merge requests, and snippets will always show thumbs-up and thumbs-down award emoji buttons."
+msgstr ""
+
+msgid "ProjectSettings|Wiki"
+msgstr ""
+
+msgid "ProjectSettings|With GitLab Pages you can host your static websites on GitLab"
+msgstr ""
+
+msgid "ProjectSettings|With Metrics Dashboard you can visualize this project performance metrics"
+msgstr ""
+
+msgid "ProjectTemplates|.NET Core"
+msgstr ""
+
+msgid "ProjectTemplates|Android"
+msgstr ""
+
+msgid "ProjectTemplates|GitLab Cluster Management"
+msgstr ""
+
+msgid "ProjectTemplates|Go Micro"
+msgstr ""
+
+msgid "ProjectTemplates|HIPAA Audit Protocol"
+msgstr ""
+
+msgid "ProjectTemplates|Netlify/GitBook"
+msgstr ""
+
+msgid "ProjectTemplates|Netlify/Hexo"
+msgstr ""
+
+msgid "ProjectTemplates|Netlify/Hugo"
+msgstr ""
+
+msgid "ProjectTemplates|Netlify/Jekyll"
+msgstr ""
+
+msgid "ProjectTemplates|Netlify/Plain HTML"
+msgstr ""
+
+msgid "ProjectTemplates|NodeJS Express"
+msgstr ""
+
+msgid "ProjectTemplates|Pages/Gatsby"
+msgstr ""
+
+msgid "ProjectTemplates|Pages/GitBook"
+msgstr ""
+
+msgid "ProjectTemplates|Pages/Hexo"
+msgstr ""
+
+msgid "ProjectTemplates|Pages/Hugo"
+msgstr ""
+
+msgid "ProjectTemplates|Pages/Jekyll"
+msgstr ""
+
+msgid "ProjectTemplates|Pages/Plain HTML"
+msgstr ""
+
+msgid "ProjectTemplates|Ruby on Rails"
+msgstr ""
+
+msgid "ProjectTemplates|SalesforceDX"
+msgstr ""
+
+msgid "ProjectTemplates|Serverless Framework/JS"
+msgstr ""
+
+msgid "ProjectTemplates|Spring"
+msgstr ""
+
+msgid "ProjectTemplates|Static Site Editor/Middleman"
+msgstr ""
+
+msgid "ProjectTemplates|iOS (Swift)"
+msgstr ""
+
+msgid "Projects"
+msgstr ""
+
+msgid "Projects (%{count})"
+msgstr ""
+
+msgid "Projects Successfully Retrieved"
+msgstr ""
+
+msgid "Projects are graded based on the highest severity vulnerability present"
+msgstr ""
+
+msgid "Projects shared with %{group_name}"
+msgstr ""
+
+msgid "Projects that belong to a group are prefixed with the group namespace. Existing projects may be moved into a group."
+msgstr ""
+
+msgid "Projects to index"
+msgstr ""
+
+msgid "Projects with critical vulnerabilities"
+msgstr ""
+
+msgid "Projects with high or unknown vulnerabilities"
+msgstr ""
+
+msgid "Projects with low vulnerabilities"
+msgstr ""
+
+msgid "Projects with medium vulnerabilities"
+msgstr ""
+
+msgid "Projects with no vulnerabilities and security scanning enabled"
+msgstr ""
+
+msgid "Projects with write access"
+msgstr ""
+
+msgid "ProjectsDropdown|Frequently visited"
+msgstr ""
+
+msgid "ProjectsDropdown|Loading projects"
+msgstr ""
+
+msgid "ProjectsDropdown|Projects you visit often will appear here"
+msgstr ""
+
+msgid "ProjectsDropdown|Search your projects"
+msgstr ""
+
+msgid "ProjectsDropdown|Something went wrong on our end."
+msgstr ""
+
+msgid "ProjectsDropdown|Sorry, no projects matched your search"
+msgstr ""
+
+msgid "ProjectsDropdown|This feature requires browser localStorage support"
+msgstr ""
+
+msgid "ProjectsNew|Allows you to immediately clone this project’s repository. Skip this if you plan to push up an existing repository."
+msgstr ""
+
+msgid "ProjectsNew|Blank"
+msgstr ""
+
+msgid "ProjectsNew|Blank project"
+msgstr ""
+
+msgid "ProjectsNew|Connect your external repository to GitLab CI/CD."
+msgstr ""
+
+msgid "ProjectsNew|Contact an administrator to enable options for importing your project."
+msgstr ""
+
+msgid "ProjectsNew|Create"
+msgstr ""
+
+msgid "ProjectsNew|Create a blank project to house your files, plan your work, and collaborate on code, among other things."
+msgstr ""
+
+msgid "ProjectsNew|Create blank project"
+msgstr ""
+
+msgid "ProjectsNew|Create from template"
+msgstr ""
+
+msgid "ProjectsNew|Create new project"
+msgstr ""
+
+msgid "ProjectsNew|Creating project & repository."
+msgstr ""
+
+msgid "ProjectsNew|Description format"
+msgstr ""
+
+msgid "ProjectsNew|Import"
+msgstr ""
+
+msgid "ProjectsNew|Import project"
+msgstr ""
+
+msgid "ProjectsNew|Initialize repository with a README"
+msgstr ""
+
+msgid "ProjectsNew|No import options available"
+msgstr ""
+
+msgid "ProjectsNew|Please wait a moment, this page will automatically refresh when ready."
+msgstr ""
+
+msgid "ProjectsNew|Project description %{tag_start}(optional)%{tag_end}"
+msgstr ""
+
+msgid "ProjectsNew|Run CI/CD for external repository"
+msgstr ""
+
+msgid "ProjectsNew|Template"
+msgstr ""
+
+msgid "ProjectsNew|Visibility Level"
+msgstr ""
+
+msgid "ProjectsNew|Want to house several dependent projects under the same namespace? %{link_start}Create a group.%{link_end}"
+msgstr ""
+
+msgid "Prometheus"
+msgstr ""
+
+msgid "PrometheusAlerts|%{count} alerts applied"
+msgstr ""
+
+msgid "PrometheusAlerts|%{firingCount} firing"
+msgstr ""
+
+msgid "PrometheusAlerts|Add alert"
+msgstr ""
+
+msgid "PrometheusAlerts|Edit alert"
+msgstr ""
+
+msgid "PrometheusAlerts|Error creating alert"
+msgstr ""
+
+msgid "PrometheusAlerts|Error deleting alert"
+msgstr ""
+
+msgid "PrometheusAlerts|Error fetching alert"
+msgstr ""
+
+msgid "PrometheusAlerts|Error saving alert"
+msgstr ""
+
+msgid "PrometheusAlerts|Firing: %{alerts}"
+msgstr ""
+
+msgid "PrometheusAlerts|Firing: %{alert}"
+msgstr ""
+
+msgid "PrometheusAlerts|Operator"
+msgstr ""
+
+msgid "PrometheusAlerts|Select query"
+msgstr ""
+
+msgid "PrometheusAlerts|Threshold"
+msgstr ""
+
+msgid "PrometheusService|%{exporters} with %{metrics} were found"
+msgstr ""
+
+msgid "PrometheusService|<p class=\"text-tertiary\">No <a href=\"%{docsUrl}\">common metrics</a> were found</p>"
+msgstr ""
+
+msgid "PrometheusService|Active"
+msgstr ""
+
+msgid "PrometheusService|Auto configuration"
+msgstr ""
+
+msgid "PrometheusService|Automatically deploy and configure Prometheus on your clusters to monitor your project’s environments"
+msgstr ""
+
+msgid "PrometheusService|Client ID of the IAP secured resource (looks like IAP_CLIENT_ID.apps.googleusercontent.com)"
+msgstr ""
+
+msgid "PrometheusService|Common metrics"
+msgstr ""
+
+msgid "PrometheusService|Common metrics are automatically monitored based on a library of metrics from popular exporters."
+msgstr ""
+
+msgid "PrometheusService|Contents of the credentials.json file of your service account, like: { \"type\": \"service_account\", \"project_id\": ... }"
+msgstr ""
+
+msgid "PrometheusService|Custom metrics"
+msgstr ""
+
+msgid "PrometheusService|Custom metrics require Prometheus installed on a cluster with environment scope \"*\" OR a manually configured Prometheus to be available."
+msgstr ""
+
+msgid "PrometheusService|Enable Prometheus to define custom metrics, using either option above"
+msgstr ""
+
+msgid "PrometheusService|Finding and configuring metrics..."
+msgstr ""
+
+msgid "PrometheusService|Finding custom metrics..."
+msgstr ""
+
+msgid "PrometheusService|Install Prometheus on clusters"
+msgstr ""
+
+msgid "PrometheusService|Manage clusters"
+msgstr ""
+
+msgid "PrometheusService|Manual configuration"
+msgstr ""
+
+msgid "PrometheusService|Metrics"
+msgstr ""
+
+msgid "PrometheusService|Missing environment variable"
+msgstr ""
+
+msgid "PrometheusService|More information"
+msgstr ""
+
+msgid "PrometheusService|New metric"
+msgstr ""
+
+msgid "PrometheusService|No custom metrics have been created. Create one using the button above"
+msgstr ""
+
+msgid "PrometheusService|Prometheus API Base URL, like http://prometheus.example.com/"
+msgstr ""
+
+msgid "PrometheusService|Prometheus is being automatically managed on your clusters"
+msgstr ""
+
+msgid "PrometheusService|Select the Active checkbox to override the Auto Configuration with custom settings. If unchecked, Auto Configuration settings are used."
+msgstr ""
+
+msgid "PrometheusService|These metrics will only be monitored after your first deployment to an environment"
+msgstr ""
+
+msgid "PrometheusService|Time-series monitoring service"
+msgstr ""
+
+msgid "PrometheusService|To enable the installation of Prometheus on your clusters, deactivate the manual configuration below"
+msgstr ""
+
+msgid "PrometheusService|Waiting for your first deployment to an environment to find common metrics"
+msgstr ""
+
+msgid "PrometheusService|You can now manage your Prometheus settings on the %{operations_link_start}Operations%{operations_link_end} page. Fields on this page has been deprecated."
+msgstr ""
+
+msgid "Promote"
+msgstr ""
+
+msgid "Promote confidential issue to a non-confidential epic"
+msgstr ""
+
+msgid "Promote issue to an epic"
+msgstr ""
+
+msgid "Promote these project milestones into a group milestone."
+msgstr ""
+
+msgid "Promote to group label"
+msgstr ""
+
+msgid "PromoteMilestone|Only project milestones can be promoted."
+msgstr ""
+
+msgid "PromoteMilestone|Project does not belong to a group."
+msgstr ""
+
+msgid "PromoteMilestone|Promotion failed - %{message}"
+msgstr ""
+
+msgid "Promoted confidential issue to a non-confidential epic. Information in this issue is no longer confidential as epics are public to group members."
+msgstr ""
+
+msgid "Promoted issue to an epic."
+msgstr ""
+
+msgid "Promotions|Burndown Charts are visual representations of the progress of completing a milestone. At a glance, you see the current state for the completion a given milestone. Without them, you would have to organize the data from the milestone and plot it yourself to have the same sense of progress."
+msgstr ""
+
+msgid "Promotions|Buy EE"
+msgstr ""
+
+msgid "Promotions|Buy GitLab Enterprise Edition"
+msgstr ""
+
+msgid "Promotions|Contact an owner of group %{namespace_name} to upgrade the plan."
+msgstr ""
+
+msgid "Promotions|Contact owner %{link_start}%{owner_name}%{link_end} to upgrade the plan."
+msgstr ""
+
+msgid "Promotions|Contact your Administrator to upgrade your license."
+msgstr ""
+
+msgid "Promotions|Dismiss burndown charts promotion"
+msgstr ""
+
+msgid "Promotions|Don't show me this again"
+msgstr ""
+
+msgid "Promotions|Epics let you manage your portfolio of projects more efficiently and with less effort by tracking groups of issues that share a theme, across projects and milestones."
+msgstr ""
+
+msgid "Promotions|Improve issues management with Issue weight and GitLab Enterprise Edition."
+msgstr ""
+
+msgid "Promotions|Improve milestones with Burndown Charts."
+msgstr ""
+
+msgid "Promotions|Learn more"
+msgstr ""
+
+msgid "Promotions|Not now, thanks!"
+msgstr ""
+
+msgid "Promotions|See the other features in the %{subscription_link_start}bronze plan%{subscription_link_end}"
+msgstr ""
+
+msgid "Promotions|Start GitLab Ultimate trial"
+msgstr ""
+
+msgid "Promotions|This feature is locked."
+msgstr ""
+
+msgid "Promotions|Track activity with Contribution Analytics."
+msgstr ""
+
+msgid "Promotions|Try it for free"
+msgstr ""
+
+msgid "Promotions|Upgrade plan"
+msgstr ""
+
+msgid "Promotions|Upgrade your plan"
+msgstr ""
+
+msgid "Promotions|Upgrade your plan to activate Contribution Analytics."
+msgstr ""
+
+msgid "Promotions|Upgrade your plan to improve milestones with Burndown Charts."
+msgstr ""
+
+msgid "Promotions|Weight"
+msgstr ""
+
+msgid "Promotions|Weighting your issue"
+msgstr ""
+
+msgid "Promotions|When you have a lot of issues, it can be hard to get an overview. By adding a weight to your issues, you can get a better idea of the effort, cost, required time, or value of each, and so better manage them."
+msgstr ""
+
+msgid "Promotions|With Contribution Analytics you can have an overview for the activity of issues, merge requests, and push events of your organization and its members."
+msgstr ""
+
+msgid "Prompt users to upload SSH keys"
+msgstr ""
+
+msgid "Protect variable"
+msgstr ""
+
+msgid "Protected"
+msgstr ""
+
+msgid "Protected Branch"
+msgstr ""
+
+msgid "Protected Branches"
+msgstr ""
+
+msgid "Protected Environment"
+msgstr ""
+
+msgid "Protected Environments"
+msgstr ""
+
+msgid "Protected Paths"
+msgstr ""
+
+msgid "Protected Tag"
+msgstr ""
+
+msgid "Protected Tags"
+msgstr ""
+
+msgid "Protected branches"
+msgstr ""
+
+msgid "ProtectedBranch|%{wildcards_link_start}Wildcards%{wildcards_link_end} such as %{code_tag_start}*-stable%{code_tag_end} or %{code_tag_start}production/*%{code_tag_end} are supported"
+msgstr ""
+
+msgid "ProtectedBranch|Allowed to merge"
+msgstr ""
+
+msgid "ProtectedBranch|Allowed to merge:"
+msgstr ""
+
+msgid "ProtectedBranch|Allowed to push"
+msgstr ""
+
+msgid "ProtectedBranch|Allowed to push:"
+msgstr ""
+
+msgid "ProtectedBranch|Branch"
+msgstr ""
+
+msgid "ProtectedBranch|Code owner approval"
+msgstr ""
+
+msgid "ProtectedBranch|Protect"
+msgstr ""
+
+msgid "ProtectedBranch|Protect a branch"
+msgstr ""
+
+msgid "ProtectedBranch|Protected branch (%{protected_branches_count})"
+msgstr ""
+
+msgid "ProtectedBranch|Pushes that change filenames matched by the CODEOWNERS file will be rejected"
+msgstr ""
+
+msgid "ProtectedBranch|Require approval from code owners:"
+msgstr ""
+
+msgid "ProtectedBranch|There are currently no protected branches, protect a branch with the form above."
+msgstr ""
+
+msgid "ProtectedBranch|Toggle code owner approval"
+msgstr ""
+
+msgid "ProtectedEnvironment|%{environment_name} will be writable for developers. Are you sure?"
+msgstr ""
+
+msgid "ProtectedEnvironment|Allowed to deploy"
+msgstr ""
+
+msgid "ProtectedEnvironment|Environment"
+msgstr ""
+
+msgid "ProtectedEnvironment|Protect"
+msgstr ""
+
+msgid "ProtectedEnvironment|Protect an environment"
+msgstr ""
+
+msgid "ProtectedEnvironment|Protected Environment (%{protected_environments_count})"
+msgstr ""
+
+msgid "ProtectedEnvironment|Protecting an environment restricts the users who can execute deployments."
+msgstr ""
+
+msgid "ProtectedEnvironment|Select an environment"
+msgstr ""
+
+msgid "ProtectedEnvironment|Select users"
+msgstr ""
+
+msgid "ProtectedEnvironment|Select users to deploy and manage Feature Flag settings"
+msgstr ""
+
+msgid "ProtectedEnvironment|There are currently no protected environments, protect an environment with the form above."
+msgstr ""
+
+msgid "ProtectedEnvironment|Unprotect"
+msgstr ""
+
+msgid "ProtectedEnvironment|Your environment can't be unprotected"
+msgstr ""
+
+msgid "ProtectedEnvironment|Your environment has been protected."
+msgstr ""
+
+msgid "ProtectedEnvironment|Your environment has been unprotected"
+msgstr ""
+
+msgid "Protip:"
+msgstr ""
+
+msgid "Protocol"
+msgstr ""
+
+msgid "Provider"
+msgstr ""
+
+msgid "Proxy support for this API is not available currently"
+msgstr ""
+
+msgid "Pseudonymizer data collection"
+msgstr ""
+
+msgid "Public"
+msgstr ""
+
+msgid "Public - The group and any public projects can be viewed without any authentication."
+msgstr ""
+
+msgid "Public - The project can be accessed without any authentication."
+msgstr ""
+
+msgid "Public deploy keys (%{deploy_keys_count})"
+msgstr ""
+
+msgid "Public pipelines"
+msgstr ""
+
+msgid "Public projects Minutes cost factor"
+msgstr ""
+
+msgid "Publish to status page"
+msgstr ""
+
+msgid "Published on status page"
+msgstr ""
+
+msgid "Publishes this issue to the associated status page."
+msgstr ""
+
+msgid "Pull"
+msgstr ""
+
+msgid "Pull requests from fork are not supported"
+msgstr ""
+
+msgid "Puma is running with a thread count above 1 and the Rugged service is enabled. This may decrease performance in some environments. See our %{link_start}documentation%{link_end} for details of this issue."
+msgstr ""
+
+msgid "Purchase more minutes"
+msgstr ""
+
+msgid "Push"
+msgstr ""
+
+msgid "Push Rule updated successfully."
+msgstr ""
+
+msgid "Push Rules"
+msgstr ""
+
+msgid "Push Rules updated successfully."
+msgstr ""
+
+msgid "Push an existing Git repository"
+msgstr ""
+
+msgid "Push an existing folder"
+msgstr ""
+
+msgid "Push events"
+msgstr ""
+
+msgid "Push project from command line"
+msgstr ""
+
+msgid "Push to create a project"
+msgstr ""
+
+msgid "PushRule|Committer restriction"
+msgstr ""
+
+msgid "Pushed"
+msgstr ""
+
+msgid "Pushes"
+msgstr ""
+
+msgid "PushoverService|%{user_name} deleted branch \"%{ref}\"."
+msgstr ""
+
+msgid "PushoverService|%{user_name} push to branch \"%{ref}\"."
+msgstr ""
+
+msgid "PushoverService|%{user_name} pushed new branch \"%{ref}\"."
+msgstr ""
+
+msgid "PushoverService|High Priority"
+msgstr ""
+
+msgid "PushoverService|Leave blank for all active devices"
+msgstr ""
+
+msgid "PushoverService|Low Priority"
+msgstr ""
+
+msgid "PushoverService|Lowest Priority"
+msgstr ""
+
+msgid "PushoverService|Normal Priority"
+msgstr ""
+
+msgid "PushoverService|Pushover makes it easy to get real-time notifications on your Android device, iPhone, iPad, and Desktop."
+msgstr ""
+
+msgid "PushoverService|See project %{project_full_name}"
+msgstr ""
+
+msgid "PushoverService|Total commits count: %{total_commits_count}"
+msgstr ""
+
+msgid "PushoverService|Your application key"
+msgstr ""
+
+msgid "PushoverService|Your user key"
+msgstr ""
+
+msgid "Quarters"
+msgstr ""
+
+msgid "Query"
+msgstr ""
+
+msgid "Query cannot be processed"
+msgstr ""
+
+msgid "Query is valid"
+msgstr ""
+
+msgid "Queued"
+msgstr ""
+
+msgid "Quick actions"
+msgstr ""
+
+msgid "Quick actions can be used in the issues description and comment boxes."
+msgstr ""
+
+msgid "Quick range"
+msgstr ""
+
+msgid "README"
+msgstr ""
+
+msgid "Raw blob request rate limit per minute"
+msgstr ""
+
+msgid "Re-authentication period expired or never requested. Please try again"
+msgstr ""
+
+msgid "Re-authentication required"
+msgstr ""
+
+msgid "Re-verification interval"
+msgstr ""
+
+msgid "Read more"
+msgstr ""
+
+msgid "Read more about environments"
+msgstr ""
+
+msgid "Read more about project permissions <strong>%{link_to_help}</strong>"
+msgstr ""
+
+msgid "Read more about related issues"
+msgstr ""
+
+msgid "Real-time features"
+msgstr ""
+
+msgid "Rebase"
+msgstr ""
+
+msgid "Rebase in progress"
+msgstr ""
+
+msgid "Receive alerts from manually configured Prometheus servers."
+msgstr ""
+
+msgid "Receive notifications about your own activity"
+msgstr ""
+
+msgid "Recent"
+msgstr ""
+
+msgid "Recent Activity"
+msgstr ""
+
+msgid "Recent Project Activity"
+msgstr ""
+
+msgid "Recent Searches Service is unavailable"
+msgstr ""
+
+msgid "Recent searches"
+msgstr ""
+
+msgid "Recipe"
+msgstr ""
+
+msgid "Reconfigure"
+msgstr ""
+
+msgid "Recover hidden stage"
+msgstr ""
+
+msgid "Recovery Codes"
+msgstr ""
+
+msgid "Redirect to SAML provider to test configuration"
+msgstr ""
+
+msgid "Reduce project visibility"
+msgstr ""
+
+msgid "Reduce this project’s visibility?"
+msgstr ""
+
+msgid "Reference:"
+msgstr ""
+
+msgid "Refresh"
+msgstr ""
+
+msgid "Refreshing in a second to show the updated status..."
+msgid_plural "Refreshing in %d seconds to show the updated status..."
+msgstr[0] ""
+
+msgid "Regenerate export"
+msgstr ""
+
+msgid "Regenerate instance ID"
+msgstr ""
+
+msgid "Regenerate key"
+msgstr ""
+
+msgid "Regenerate recovery codes"
+msgstr ""
+
+msgid "Regenerating the instance ID can break integration depending on the client you are using."
+msgstr ""
+
+msgid "Regex pattern"
+msgstr ""
+
+msgid "Region that Elasticsearch is configured"
+msgstr ""
+
+msgid "Register"
+msgstr ""
+
+msgid "Register / Sign In"
+msgstr ""
+
+msgid "Register Two-Factor Authenticator"
+msgstr ""
+
+msgid "Register U2F device"
+msgstr ""
+
+msgid "Register Universal Two-Factor (U2F) Device"
+msgstr ""
+
+msgid "Register for GitLab"
+msgstr ""
+
+msgid "Register now"
+msgstr ""
+
+msgid "Register with two-factor app"
+msgstr ""
+
+msgid "Registration"
+msgstr ""
+
+msgid "Registration|Checkout"
+msgstr ""
+
+msgid "Registration|Your GitLab group"
+msgstr ""
+
+msgid "Registration|Your first project"
+msgstr ""
+
+msgid "Registration|Your profile"
+msgstr ""
+
+msgid "Regulate approvals by authors/committers, based on compliance frameworks. Can be changed only at the instance level."
+msgstr ""
+
+msgid "Rejected (closed)"
+msgstr ""
+
+msgid "Related Deployed Jobs"
+msgstr ""
+
+msgid "Related Issues"
+msgstr ""
+
+msgid "Related Jobs"
+msgstr ""
+
+msgid "Related Merge Requests"
+msgstr ""
+
+msgid "Related Merged Requests"
+msgstr ""
+
+msgid "Related merge requests"
+msgstr ""
+
+msgid "Relates to"
+msgstr ""
+
+msgid "Release"
+msgid_plural "Releases"
+msgstr[0] ""
+
+msgid "Release assets"
+msgstr ""
+
+msgid "Release assets documentation"
+msgstr ""
+
+msgid "Release does not have the same project as the milestone"
+msgstr ""
+
+msgid "Release notes"
+msgstr ""
+
+msgid "Release notes:"
+msgstr ""
+
+msgid "Release title"
+msgstr ""
+
+msgid "ReleaseAssetLinkType|Image"
+msgstr ""
+
+msgid "ReleaseAssetLinkType|Images"
+msgstr ""
+
+msgid "ReleaseAssetLinkType|Other"
+msgstr ""
+
+msgid "ReleaseAssetLinkType|Package"
+msgstr ""
+
+msgid "ReleaseAssetLinkType|Packages"
+msgstr ""
+
+msgid "ReleaseAssetLinkType|Runbook"
+msgstr ""
+
+msgid "ReleaseAssetLinkType|Runbooks"
+msgstr ""
+
+msgid "Releases"
+msgstr ""
+
+msgid "Releases are based on Git tags and mark specific points in a project's development history. They can contain information about the type of changes and can also deliver binaries, like compiled versions of your software."
+msgstr ""
+
+msgid "Releases are based on Git tags. We recommend tags that use semantic versioning, for example %{codeStart}v1.0%{codeEnd}, %{codeStart}v2.0-pre%{codeEnd}."
+msgstr ""
+
+msgid "Releases documentation"
+msgstr ""
+
+msgid "Releases|New Release"
+msgstr ""
+
+msgid "Release|Something went wrong while getting the release details"
+msgstr ""
+
+msgid "Release|Something went wrong while saving the release details"
+msgstr ""
+
+msgid "Remediated: needs review"
+msgstr ""
+
+msgid "Remediations"
+msgstr ""
+
+msgid "Remember me"
+msgstr ""
+
+msgid "Remind later"
+msgstr ""
+
+msgid "Remote object has no absolute path."
+msgstr ""
+
+msgid "Remove"
+msgstr ""
+
+msgid "Remove %{displayReference}"
+msgstr ""
+
+msgid "Remove Runner"
+msgstr ""
+
+msgid "Remove Zoom meeting"
+msgstr ""
+
+msgid "Remove all approvals in a merge request when new commits are pushed to its source branch"
+msgstr ""
+
+msgid "Remove all or specific assignee(s)"
+msgstr ""
+
+msgid "Remove all or specific label(s)"
+msgstr ""
+
+msgid "Remove approvers"
+msgstr ""
+
+msgid "Remove approvers?"
+msgstr ""
+
+msgid "Remove asset link"
+msgstr ""
+
+msgid "Remove assignee"
+msgstr ""
+
+msgid "Remove avatar"
+msgstr ""
+
+msgid "Remove card"
+msgstr ""
+
+msgid "Remove child epic from an epic"
+msgstr ""
+
+msgid "Remove description history"
+msgstr ""
+
+msgid "Remove due date"
+msgstr ""
+
+msgid "Remove fork relationship"
+msgstr ""
+
+msgid "Remove from batch"
+msgstr ""
+
+msgid "Remove from board"
+msgstr ""
+
+msgid "Remove from epic"
+msgstr ""
+
+msgid "Remove group"
+msgstr ""
+
+msgid "Remove iteration"
+msgstr ""
+
+msgid "Remove license"
+msgstr ""
+
+msgid "Remove limit"
+msgstr ""
+
+msgid "Remove milestone"
+msgstr ""
+
+msgid "Remove node"
+msgstr ""
+
+msgid "Remove parent epic from an epic"
+msgstr ""
+
+msgid "Remove primary node"
+msgstr ""
+
+msgid "Remove priority"
+msgstr ""
+
+msgid "Remove project"
+msgstr ""
+
+msgid "Remove secondary node"
+msgstr ""
+
+msgid "Remove spent time"
+msgstr ""
+
+msgid "Remove stage"
+msgstr ""
+
+msgid "Remove time estimate"
+msgstr ""
+
+msgid "Removed"
+msgstr ""
+
+msgid "Removed %{assignee_text} %{assignee_references}."
+msgstr ""
+
+msgid "Removed %{epic_ref} from child epics."
+msgstr ""
+
+msgid "Removed %{iteration_reference} iteration."
+msgstr ""
+
+msgid "Removed %{label_references} %{label_text}."
+msgstr ""
+
+msgid "Removed %{milestone_reference} milestone."
+msgstr ""
+
+msgid "Removed %{type} with id %{id}"
+msgstr ""
+
+msgid "Removed all labels."
+msgstr ""
+
+msgid "Removed an issue from an epic."
+msgstr ""
+
+msgid "Removed group can not be restored!"
+msgstr ""
+
+msgid "Removed parent epic %{epic_ref}."
+msgstr ""
+
+msgid "Removed projects cannot be restored!"
+msgstr ""
+
+msgid "Removed spent time."
+msgstr ""
+
+msgid "Removed the due date."
+msgstr ""
+
+msgid "Removed time estimate."
+msgstr ""
+
+msgid "Removes %{assignee_text} %{assignee_references}."
+msgstr ""
+
+msgid "Removes %{epic_ref} from child epics."
+msgstr ""
+
+msgid "Removes %{iteration_reference} iteration."
+msgstr ""
+
+msgid "Removes %{label_references} %{label_text}."
+msgstr ""
+
+msgid "Removes %{milestone_reference} milestone."
+msgstr ""
+
+msgid "Removes all labels."
+msgstr ""
+
+msgid "Removes an issue from an epic."
+msgstr ""
+
+msgid "Removes parent epic %{epic_ref}."
+msgstr ""
+
+msgid "Removes spent time."
+msgstr ""
+
+msgid "Removes the due date."
+msgstr ""
+
+msgid "Removes time estimate."
+msgstr ""
+
+msgid "Removing a project places it into a read-only state until %{date}, at which point the project will be permanantly removed. Are you ABSOLUTELY sure?"
+msgstr ""
+
+msgid "Removing a project places it into a read-only state until %{date}, at which point the project will be permanently removed."
+msgstr ""
+
+msgid "Removing license…"
+msgstr ""
+
+msgid "Removing the project will delete its repository and all related resources including issues, merge requests etc."
+msgstr ""
+
+msgid "Removing this group also removes all child projects, including archived projects, and their resources."
+msgstr ""
+
+msgid "Rename file"
+msgstr ""
+
+msgid "Rename folder"
+msgstr ""
+
+msgid "Rename/Move"
+msgstr ""
+
+msgid "Reopen"
+msgstr ""
+
+msgid "Reopen %{display_issuable_type}"
+msgstr ""
+
+msgid "Reopen epic"
+msgstr ""
+
+msgid "Reopen milestone"
+msgstr ""
+
+msgid "Reopen this %{quick_action_target}"
+msgstr ""
+
+msgid "Reopened this %{quick_action_target}."
+msgstr ""
+
+msgid "Reopens this %{quick_action_target}."
+msgstr ""
+
+msgid "Repair authentication"
+msgstr ""
+
+msgid "Replace"
+msgstr ""
+
+msgid "Replace all label(s)"
+msgstr ""
+
+msgid "Replaced all labels with %{label_references} %{label_text}."
+msgstr ""
+
+msgid "Replaces the clone URL root."
+msgstr ""
+
+msgid "Replication"
+msgstr ""
+
+msgid "Replication enabled"
+msgstr ""
+
+msgid "Replication paused"
+msgstr ""
+
+msgid "Reply by email"
+msgstr ""
+
+msgid "Reply to comment"
+msgstr ""
+
+msgid "Reply to this email directly or %{view_it_on_gitlab}."
+msgstr ""
+
+msgid "Reply..."
+msgstr ""
+
+msgid "Repo by URL"
+msgstr ""
+
+msgid "Report %{display_issuable_type} that are abusive, inappropriate or spam."
+msgstr ""
+
+msgid "Report abuse"
+msgstr ""
+
+msgid "Report abuse to admin"
+msgstr ""
+
+msgid "Reported %{timeAgo} by %{reportedBy}"
+msgstr ""
+
+msgid "Reporter"
+msgstr ""
+
+msgid "Reporting"
+msgstr ""
+
+msgid "Reports"
+msgstr ""
+
+msgid "Reports|%{combinedString} and %{resolvedString}"
+msgstr ""
+
+msgid "Reports|Accessibility scanning detected %d issue for the source branch only"
+msgid_plural "Reports|Accessibility scanning detected %d issues for the source branch only"
+msgstr[0] ""
+
+msgid "Reports|Accessibility scanning detected no issues for the source branch only"
+msgstr ""
+
+msgid "Reports|Accessibility scanning failed loading results"
+msgstr ""
+
+msgid "Reports|Accessibility scanning results are being parsed"
+msgstr ""
+
+msgid "Reports|Actions"
+msgstr ""
+
+msgid "Reports|An error occured while loading report"
+msgstr ""
+
+msgid "Reports|An error occurred while loading %{name} results"
+msgstr ""
+
+msgid "Reports|Class"
+msgstr ""
+
+msgid "Reports|Classname"
+msgstr ""
+
+msgid "Reports|Execution time"
+msgstr ""
+
+msgid "Reports|Failure"
+msgstr ""
+
+msgid "Reports|Identifier"
+msgstr ""
+
+msgid "Reports|Metrics reports are loading"
+msgstr ""
+
+msgid "Reports|Metrics reports changed on %{numberOfChanges} %{pointsString}"
+msgstr ""
+
+msgid "Reports|Metrics reports did not change"
+msgstr ""
+
+msgid "Reports|Metrics reports failed loading results"
+msgstr ""
+
+msgid "Reports|Scanner"
+msgstr ""
+
+msgid "Reports|Severity"
+msgstr ""
+
+msgid "Reports|System output"
+msgstr ""
+
+msgid "Reports|Test summary"
+msgstr ""
+
+msgid "Reports|Test summary failed loading results"
+msgstr ""
+
+msgid "Reports|Test summary results are being parsed"
+msgstr ""
+
+msgid "Reports|Vulnerability"
+msgstr ""
+
+msgid "Reports|no changed test results"
+msgstr ""
+
+msgid "Repository"
+msgstr ""
+
+msgid "Repository Analytics"
+msgstr ""
+
+msgid "Repository Graph"
+msgstr ""
+
+msgid "Repository Settings"
+msgstr ""
+
+msgid "Repository URL"
+msgstr ""
+
+msgid "Repository check"
+msgstr ""
+
+msgid "Repository check was triggered."
+msgstr ""
+
+msgid "Repository cleanup"
+msgstr ""
+
+msgid "Repository cleanup has started. You will receive an email once the cleanup operation is complete."
+msgstr ""
+
+msgid "Repository files count over the limit"
+msgstr ""
+
+msgid "Repository has an invalid default branch name."
+msgstr ""
+
+msgid "Repository has more than one branch."
+msgstr ""
+
+msgid "Repository has no locks."
+msgstr ""
+
+msgid "Repository has tags."
+msgstr ""
+
+msgid "Repository maintenance"
+msgstr ""
+
+msgid "Repository mirroring"
+msgstr ""
+
+msgid "Repository must contain at least 1 file."
+msgstr ""
+
+msgid "Repository size is above the limit."
+msgstr ""
+
+msgid "Repository static objects"
+msgstr ""
+
+msgid "Repository storage"
+msgstr ""
+
+msgid "Repository sync capacity"
+msgstr ""
+
+msgid "Repository: %{counter_repositories} / Wikis: %{counter_wikis} / Build Artifacts: %{counter_build_artifacts} / LFS: %{counter_lfs_objects} / Snippets: %{counter_snippets}"
+msgstr ""
+
+msgid "RepositorySettingsAccessLevel|Select"
+msgstr ""
+
+msgid "Request Access"
+msgstr ""
+
+msgid "Request Headers"
+msgstr ""
+
+msgid "Request details"
+msgstr ""
+
+msgid "Request parameter %{param} is missing."
+msgstr ""
+
+msgid "Request to link SAML account must be authorized"
+msgstr ""
+
+msgid "Requested %{time_ago}"
+msgstr ""
+
+msgid "Requested design version does not exist."
+msgstr ""
+
+msgid "Requested states are invalid"
+msgstr ""
+
+msgid "Requests"
+msgstr ""
+
+msgid "Requests Profiles"
+msgstr ""
+
+msgid "Requests to these domain(s)/address(es) on the local network will be allowed when local requests from hooks and services are not allowed. IP ranges such as 1:0:0:0:0:0:0:0/124 or 127.0.0.0/28 are supported. Domain wildcards are not supported currently. Use comma, semicolon, or newline to separate multiple entries. The allowlist can hold a maximum of 1000 entries. Domains should use IDNA encoding. Ex: example.com, 192.168.1.1, 127.0.0.0/28, xn--itlab-j1a.com."
+msgstr ""
+
+msgid "Require all users in this group to setup Two-factor authentication"
+msgstr ""
+
+msgid "Require all users to accept Terms of Service and Privacy Policy when they access GitLab."
+msgstr ""
+
+msgid "Require user password to approve"
+msgstr ""
+
+msgid "Require users to prove ownership of custom domains"
+msgstr ""
+
+msgid "Required approvals (%{approvals_given} given)"
+msgstr ""
+
+msgid "Required approvals (%{approvals_given} given, you've approved)"
+msgstr ""
+
+msgid "Requirement"
+msgstr ""
+
+msgid "Requirement %{reference} has been added"
+msgstr ""
+
+msgid "Requirement %{reference} has been archived"
+msgstr ""
+
+msgid "Requirement %{reference} has been reopened"
+msgstr ""
+
+msgid "Requirement %{reference} has been updated"
+msgstr ""
+
+msgid "Requirement title cannot have more than %{limit} characters."
+msgstr ""
+
+msgid "Requirements"
+msgstr ""
+
+msgid "Requirements can be based on users, stakeholders, system, software, or anything else you find important to capture."
+msgstr ""
+
+msgid "Requires approval from %{names}."
+msgid_plural "Requires %{count} more approvals from %{names}."
+msgstr[0] ""
+
+msgid "Requires approval."
+msgid_plural "Requires %d more approvals."
+msgstr[0] ""
+
+msgid "Requires values to meet regular expression requirements."
+msgstr ""
+
+msgid "Resend confirmation email"
+msgstr ""
+
+msgid "Resend invite"
+msgstr ""
+
+msgid "Resend it"
+msgstr ""
+
+msgid "Reset authorization key"
+msgstr ""
+
+msgid "Reset authorization key?"
+msgstr ""
+
+msgid "Reset health check access token"
+msgstr ""
+
+msgid "Reset key"
+msgstr ""
+
+msgid "Reset runners registration token"
+msgstr ""
+
+msgid "Reset template"
+msgstr ""
+
+msgid "Reset to project defaults"
+msgstr ""
+
+msgid "Resetting the authorization key for this project will require updating the authorization key in every alert source it is enabled in."
+msgstr ""
+
+msgid "Resetting the authorization key will invalidate the previous key. Existing alert configurations will need to be updated with the new key."
+msgstr ""
+
+msgid "Resolve"
+msgstr ""
+
+msgid "Resolve all threads in new issue"
+msgstr ""
+
+msgid "Resolve conflicts on source branch"
+msgstr ""
+
+msgid "Resolve thread"
+msgstr ""
+
+msgid "Resolved"
+msgstr ""
+
+msgid "Resolved 1 discussion."
+msgstr ""
+
+msgid "Resolved all discussions."
+msgstr ""
+
+msgid "Resolved by"
+msgstr ""
+
+msgid "Resolved by %{name}"
+msgstr ""
+
+msgid "Resolved by %{resolvedByName}"
+msgstr ""
+
+msgid "Resolves IP addresses once and uses them to submit requests"
+msgstr ""
+
+msgid "Response"
+msgstr ""
+
+msgid "Response Headers"
+msgstr ""
+
+msgid "Response Status"
+msgstr ""
+
+msgid "Response didn't include `service_desk_address`"
+msgstr ""
+
+msgid "Response metrics (AWS ELB)"
+msgstr ""
+
+msgid "Response metrics (Custom)"
+msgstr ""
+
+msgid "Response metrics (HA Proxy)"
+msgstr ""
+
+msgid "Response metrics (NGINX Ingress VTS)"
+msgstr ""
+
+msgid "Response metrics (NGINX Ingress)"
+msgstr ""
+
+msgid "Response metrics (NGINX)"
+msgstr ""
+
+msgid "Restart Terminal"
+msgstr ""
+
+msgid "Restore group"
+msgstr ""
+
+msgid "Restore project"
+msgstr ""
+
+msgid "Restoring the group will prevent the group, its subgroups and projects from being removed on this date."
+msgstr ""
+
+msgid "Restoring the project will prevent the project from being removed on this date and restore people's ability to make changes to it."
+msgstr ""
+
+msgid "Restrict access by IP address"
+msgstr ""
+
+msgid "Restrict membership by email"
+msgstr ""
+
+msgid "Restricts sign-ups for email addresses that match the given regex. See the %{supported_syntax_link_start}supported syntax%{supported_syntax_link_end} for more information."
+msgstr ""
+
+msgid "Resume"
+msgstr ""
+
+msgid "Resync"
+msgstr ""
+
+msgid "Resync all"
+msgstr ""
+
+msgid "Resync all %{replicableType}"
+msgstr ""
+
+msgid "Retry"
+msgstr ""
+
+msgid "Retry this job"
+msgstr ""
+
+msgid "Retry this job in order to create the necessary resources."
+msgstr ""
+
+msgid "Retry update"
+msgstr ""
+
+msgid "Retry verification"
+msgstr ""
+
+msgid "Reveal value"
+msgid_plural "Reveal values"
+msgstr[0] ""
+
+msgid "Reveal values"
+msgstr ""
+
+msgid "Revert this commit"
+msgstr ""
+
+msgid "Revert this merge request"
+msgstr ""
+
+msgid "Review"
+msgstr ""
+
+msgid "Review App|View app"
+msgstr ""
+
+msgid "Review App|View latest app"
+msgstr ""
+
+msgid "Review the process for configuring service providers in your identity provider — in this case, GitLab is the \"service provider\" or \"relying party\"."
+msgstr ""
+
+msgid "Review time"
+msgstr ""
+
+msgid "Review time is defined as the time it takes from first comment until merged."
+msgstr ""
+
+msgid "ReviewApp|Enable Review App"
+msgstr ""
+
+msgid "Reviewing"
+msgstr ""
+
+msgid "Reviewing (merge request !%{mergeRequestId})"
+msgstr ""
+
+msgid "Revoke"
+msgstr ""
+
+msgid "Revoked impersonation token %{token_name}!"
+msgstr ""
+
+msgid "Revoked personal access token %{personal_access_token_name}!"
+msgstr ""
+
+msgid "Revoked project access token %{project_access_token_name}!"
+msgstr ""
+
+msgid "RightSidebar|adding a"
+msgstr ""
+
+msgid "RightSidebar|deleting the"
+msgstr ""
+
+msgid "Roadmap"
+msgstr ""
+
+msgid "Role"
+msgstr ""
+
+msgid "Rollback"
+msgstr ""
+
+msgid "Rook"
+msgstr ""
+
+msgid "Rules that define what git pushes are accepted for a project in this group. All newly created projects in this group will use these settings."
+msgstr ""
+
+msgid "Rules that define what git pushes are accepted for a project. All newly created projects will use these settings."
+msgstr ""
+
+msgid "Run CI/CD pipelines for external repositories"
+msgstr ""
+
+msgid "Run housekeeping"
+msgstr ""
+
+msgid "Run tests against your code live using the Web Terminal"
+msgstr ""
+
+msgid "Run untagged jobs"
+msgstr ""
+
+msgid "Runner cannot be assigned to other projects"
+msgstr ""
+
+msgid "Runner runs jobs from all unassigned projects"
+msgstr ""
+
+msgid "Runner runs jobs from all unassigned projects in its group"
+msgstr ""
+
+msgid "Runner runs jobs from assigned projects"
+msgstr ""
+
+msgid "Runner token"
+msgstr ""
+
+msgid "Runner tokens"
+msgstr ""
+
+msgid "Runner was not updated."
+msgstr ""
+
+msgid "Runner was successfully updated."
+msgstr ""
+
+msgid "Runner will not receive any new jobs"
+msgstr ""
+
+msgid "Runners"
+msgstr ""
+
+msgid "Runners API"
+msgstr ""
+
+msgid "Runners activated for this project"
+msgstr ""
+
+msgid "Runners are processes that pick up and execute jobs for GitLab. Here you can register and see your Runners for this project."
+msgstr ""
+
+msgid "Runners can be placed on separate users, servers, and even on your local machine."
+msgstr ""
+
+msgid "Runners can be placed on separate users, servers, even on your local machine."
+msgstr ""
+
+msgid "Runners currently online: %{active_runners_count}"
+msgstr ""
+
+msgid "Runners page."
+msgstr ""
+
+msgid "Runners|You have used %{quotaUsed} out of %{quotaLimit} of your shared Runners pipeline minutes."
+msgstr ""
+
+msgid "Running"
+msgstr ""
+
+msgid "Running…"
+msgstr ""
+
+msgid "Runs a number of housekeeping tasks within the current repository, such as compressing file revisions and removing unreachable objects."
+msgstr ""
+
+msgid "SAML SSO"
+msgstr ""
+
+msgid "SAML SSO for %{group_name}"
+msgstr ""
+
+msgid "SAML discovery tokens"
+msgstr ""
+
+msgid "SAML for %{group_name}"
+msgstr ""
+
+msgid "SHA256"
+msgstr ""
+
+msgid "SSH Key"
+msgstr ""
+
+msgid "SSH Keys"
+msgstr ""
+
+msgid "SSH host key fingerprints"
+msgstr ""
+
+msgid "SSH host keys"
+msgstr ""
+
+msgid "SSH host keys are not available on this system. Please use <code>ssh-keyscan</code> command or contact your GitLab administrator for more information."
+msgstr ""
+
+msgid "SSH keys allow you to establish a secure connection between your computer and GitLab."
+msgstr ""
+
+msgid "SSH public key"
+msgstr ""
+
+msgid "SSL Verification:"
+msgstr ""
+
+msgid "Saturday"
+msgstr ""
+
+msgid "Save"
+msgstr ""
+
+msgid "Save Changes"
+msgstr ""
+
+msgid "Save anyway"
+msgstr ""
+
+msgid "Save application"
+msgstr ""
+
+msgid "Save changes"
+msgstr ""
+
+msgid "Save changes before testing"
+msgstr ""
+
+msgid "Save comment"
+msgstr ""
+
+msgid "Save expiration policy"
+msgstr ""
+
+msgid "Save password"
+msgstr ""
+
+msgid "Save pipeline schedule"
+msgstr ""
+
+msgid "Save template"
+msgstr ""
+
+msgid "Save variables"
+msgstr ""
+
+msgid "Saving"
+msgstr ""
+
+msgid "Saving project."
+msgstr ""
+
+msgid "Schedule a new pipeline"
+msgstr ""
+
+msgid "Scheduled"
+msgstr ""
+
+msgid "Scheduled to merge this merge request (%{strategy})."
+msgstr ""
+
+msgid "Scheduled to merge this merge request when the pipeline succeeds."
+msgstr ""
+
+msgid "Schedules"
+msgstr ""
+
+msgid "Schedules to merge this merge request (%{strategy})."
+msgstr ""
+
+msgid "Scheduling"
+msgstr ""
+
+msgid "Scheduling Pipelines"
+msgstr ""
+
+msgid "Scope"
+msgstr ""
+
+msgid "Scope not supported with disabled 'users_search' feature!"
+msgstr ""
+
+msgid "Scoped issue boards"
+msgstr ""
+
+msgid "Scopes"
+msgstr ""
+
+msgid "Scopes can't be blank"
+msgstr ""
+
+msgid "Scroll down"
+msgstr ""
+
+msgid "Scroll down to <strong>Google Code Project Hosting</strong> and enable the switch on the right."
+msgstr ""
+
+msgid "Scroll left"
+msgstr ""
+
+msgid "Scroll right"
+msgstr ""
+
+msgid "Scroll to bottom"
+msgstr ""
+
+msgid "Scroll to top"
+msgstr ""
+
+msgid "Scroll up"
+msgstr ""
+
+msgid "Search"
+msgstr ""
+
+msgid "Search Button"
+msgstr ""
+
+msgid "Search Milestones"
+msgstr ""
+
+msgid "Search an environment spec"
+msgstr ""
+
+msgid "Search authors"
+msgstr ""
+
+msgid "Search branches"
+msgstr ""
+
+msgid "Search branches and tags"
+msgstr ""
+
+msgid "Search by author"
+msgstr ""
+
+msgid "Search files"
+msgstr ""
+
+msgid "Search for a LDAP group"
+msgstr ""
+
+msgid "Search for a group"
+msgstr ""
+
+msgid "Search for a user"
+msgstr ""
+
+msgid "Search for projects, issues, etc."
+msgstr ""
+
+msgid "Search for this text"
+msgstr ""
+
+msgid "Search forks"
+msgstr ""
+
+msgid "Search groups"
+msgstr ""
+
+msgid "Search merge requests"
+msgstr ""
+
+msgid "Search milestones"
+msgstr ""
+
+msgid "Search or filter results..."
+msgstr ""
+
+msgid "Search or filter results…"
+msgstr ""
+
+msgid "Search or jump to…"
+msgstr ""
+
+msgid "Search project"
+msgstr ""
+
+msgid "Search projects"
+msgstr ""
+
+msgid "Search projects..."
+msgstr ""
+
+msgid "Search requirements"
+msgstr ""
+
+msgid "Search users"
+msgstr ""
+
+msgid "Search users or groups"
+msgstr ""
+
+msgid "Search your project dependencies for their licenses and apply policies."
+msgstr ""
+
+msgid "Search your projects"
+msgstr ""
+
+msgid "SearchAutocomplete|All GitLab"
+msgstr ""
+
+msgid "SearchAutocomplete|Issues I've created"
+msgstr ""
+
+msgid "SearchAutocomplete|Issues assigned to me"
+msgstr ""
+
+msgid "SearchAutocomplete|Merge requests I've created"
+msgstr ""
+
+msgid "SearchAutocomplete|Merge requests assigned to me"
+msgstr ""
+
+msgid "SearchAutocomplete|in all GitLab"
+msgstr ""
+
+msgid "SearchAutocomplete|in group %{groupName}"
+msgstr ""
+
+msgid "SearchAutocomplete|in project %{projectName}"
+msgstr ""
+
+msgid "SearchCodeResults|in"
+msgstr ""
+
+msgid "SearchCodeResults|of %{link_to_project}"
+msgstr ""
+
+msgid "SearchResults|Showing %{count} %{scope} for%{term_element}"
+msgstr ""
+
+msgid "SearchResults|Showing %{count} %{scope} for%{term_element} in your personal and project snippets"
+msgstr ""
+
+msgid "SearchResults|Showing %{from} - %{to} of %{count} %{scope} for%{term_element}"
+msgstr ""
+
+msgid "SearchResults|Showing %{from} - %{to} of %{count} %{scope} for%{term_element} in your personal and project snippets"
+msgstr ""
+
+msgid "SearchResults|We couldn't find any %{scope} matching %{term}"
+msgstr ""
+
+msgid "SearchResults|code result"
+msgid_plural "SearchResults|code results"
+msgstr[0] ""
+
+msgid "SearchResults|comment"
+msgid_plural "SearchResults|comments"
+msgstr[0] ""
+
+msgid "SearchResults|commit"
+msgid_plural "SearchResults|commits"
+msgstr[0] ""
+
+msgid "SearchResults|issue"
+msgid_plural "SearchResults|issues"
+msgstr[0] ""
+
+msgid "SearchResults|merge request"
+msgid_plural "SearchResults|merge requests"
+msgstr[0] ""
+
+msgid "SearchResults|milestone"
+msgid_plural "SearchResults|milestones"
+msgstr[0] ""
+
+msgid "SearchResults|project"
+msgid_plural "SearchResults|projects"
+msgstr[0] ""
+
+msgid "SearchResults|snippet"
+msgid_plural "SearchResults|snippets"
+msgstr[0] ""
+
+msgid "SearchResults|user"
+msgid_plural "SearchResults|users"
+msgstr[0] ""
+
+msgid "SearchResults|wiki result"
+msgid_plural "SearchResults|wiki results"
+msgstr[0] ""
+
+msgid "Searching by both author and message is currently not supported."
+msgstr ""
+
+msgid "Seat Link"
+msgstr ""
+
+msgid "Seat Link is disabled, and cannot be configured through this form."
+msgstr ""
+
+msgid "Seats currently in use"
+msgstr ""
+
+msgid "Seats in license"
+msgstr ""
+
+msgid "Secondary"
+msgstr ""
+
+msgid "Secret"
+msgstr ""
+
+msgid "Secret Detection"
+msgstr ""
+
+msgid "Security"
+msgstr ""
+
+msgid "Security & Compliance"
+msgstr ""
+
+msgid "Security Configuration"
+msgstr ""
+
+msgid "Security Dashboard"
+msgstr ""
+
+msgid "Security dashboard"
+msgstr ""
+
+msgid "Security report is out of date. Please update your branch with the latest changes from the target branch (%{targetBranchName})"
+msgstr ""
+
+msgid "Security report is out of date. Run %{newPipelineLinkStart}a new pipeline%{newPipelineLinkEnd} for the target branch (%{targetBranchName})"
+msgstr ""
+
+msgid "SecurityConfiguration|Enabled"
+msgstr ""
+
+msgid "SecurityConfiguration|Feature documentation for %{featureName}"
+msgstr ""
+
+msgid "SecurityConfiguration|Not yet enabled"
+msgstr ""
+
+msgid "SecurityConfiguration|Security Control"
+msgstr ""
+
+msgid "SecurityConfiguration|Status"
+msgstr ""
+
+msgid "SecurityConfiguration|Testing & Compliance"
+msgstr ""
+
+msgid "SecurityReports|%{firstProject} and %{secondProject}"
+msgstr ""
+
+msgid "SecurityReports|%{firstProject}, %{secondProject}, and %{rest}"
+msgstr ""
+
+msgid "SecurityReports|Add a project to your dashboard"
+msgstr ""
+
+msgid "SecurityReports|Add or remove projects from your dashboard"
+msgstr ""
+
+msgid "SecurityReports|Add projects"
+msgstr ""
+
+msgid "SecurityReports|Comment added to '%{vulnerabilityName}'"
+msgstr ""
+
+msgid "SecurityReports|Comment deleted on '%{vulnerabilityName}'"
+msgstr ""
+
+msgid "SecurityReports|Comment edited on '%{vulnerabilityName}'"
+msgstr ""
+
+msgid "SecurityReports|Create issue"
+msgstr ""
+
+msgid "SecurityReports|Dismiss Selected"
+msgstr ""
+
+msgid "SecurityReports|Dismiss vulnerability"
+msgstr ""
+
+msgid "SecurityReports|Dismissed '%{vulnerabilityName}'"
+msgstr ""
+
+msgid "SecurityReports|Dismissed '%{vulnerabilityName}'. Turn off the hide dismissed toggle to view."
+msgstr ""
+
+msgid "SecurityReports|Each vulnerability now has a unique page that can be directly linked to, shared, referenced, and tracked as the single source of truth. Vulnerability occurrences also persist across scanner runs, which improves tracking and visibility and reduces duplicates between scans."
+msgstr ""
+
+msgid "SecurityReports|Edit dashboard"
+msgstr ""
+
+msgid "SecurityReports|Either you don't have permission to view this dashboard or the dashboard has not been setup. Please check your permission settings with your administrator or check your dashboard configurations to proceed."
+msgstr ""
+
+msgid "SecurityReports|Error fetching the vulnerability counts. Please check your network connection and try again."
+msgstr ""
+
+msgid "SecurityReports|Error fetching the vulnerability list. Please check your network connection and try again."
+msgstr ""
+
+msgid "SecurityReports|False positive"
+msgstr ""
+
+msgid "SecurityReports|Group Security Dashboard"
+msgstr ""
+
+msgid "SecurityReports|Hide dismissed"
+msgstr ""
+
+msgid "SecurityReports|Introducing standalone vulnerabilities"
+msgstr ""
+
+msgid "SecurityReports|Issue Created"
+msgstr ""
+
+msgid "SecurityReports|Learn More"
+msgstr ""
+
+msgid "SecurityReports|Learn more about setting up your dashboard"
+msgstr ""
+
+msgid "SecurityReports|Load more vulnerabilities"
+msgstr ""
+
+msgid "SecurityReports|Monitor vulnerabilities in your code"
+msgstr ""
+
+msgid "SecurityReports|More info"
+msgstr ""
+
+msgid "SecurityReports|More information"
+msgstr ""
+
+msgid "SecurityReports|No vulnerabilities found for dashboard"
+msgstr ""
+
+msgid "SecurityReports|No vulnerabilities found for this group"
+msgstr ""
+
+msgid "SecurityReports|No vulnerabilities found for this pipeline"
+msgstr ""
+
+msgid "SecurityReports|No vulnerabilities found for this project"
+msgstr ""
+
+msgid "SecurityReports|Oops, something doesn't seem right."
+msgstr ""
+
+msgid "SecurityReports|Pipeline %{pipelineLink} triggered %{timeago} by %{user}"
+msgstr ""
+
+msgid "SecurityReports|Project"
+msgstr ""
+
+msgid "SecurityReports|Projects added"
+msgstr ""
+
+msgid "SecurityReports|Remove project from dashboard"
+msgstr ""
+
+msgid "SecurityReports|Return to dashboard"
+msgstr ""
+
+msgid "SecurityReports|Scan details"
+msgstr ""
+
+msgid "SecurityReports|Scanner type"
+msgstr ""
+
+msgid "SecurityReports|Security Dashboard"
+msgstr ""
+
+msgid "SecurityReports|Security reports can only be accessed by authorized users."
+msgstr ""
+
+msgid "SecurityReports|Select a project to add by using the project search field above."
+msgstr ""
+
+msgid "SecurityReports|Select a reason"
+msgstr ""
+
+msgid "SecurityReports|Severity"
+msgstr ""
+
+msgid "SecurityReports|Status"
+msgstr ""
+
+msgid "SecurityReports|The rating \"unknown\" indicates that the underlying scanner doesn’t contain or provide a severity rating."
+msgstr ""
+
+msgid "SecurityReports|The security dashboard displays the latest security findings for projects you wish to monitor. Select \"Edit dashboard\" to add and remove projects."
+msgstr ""
+
+msgid "SecurityReports|The security dashboard displays the latest security report. Use it to find and fix vulnerabilities."
+msgstr ""
+
+msgid "SecurityReports|There was an error adding the comment."
+msgstr ""
+
+msgid "SecurityReports|There was an error creating the issue."
+msgstr ""
+
+msgid "SecurityReports|There was an error creating the merge request."
+msgstr ""
+
+msgid "SecurityReports|There was an error deleting the comment."
+msgstr ""
+
+msgid "SecurityReports|There was an error dismissing the vulnerabilities."
+msgstr ""
+
+msgid "SecurityReports|There was an error dismissing the vulnerability."
+msgstr ""
+
+msgid "SecurityReports|There was an error reverting the dismissal."
+msgstr ""
+
+msgid "SecurityReports|There was an error reverting this dismissal."
+msgstr ""
+
+msgid "SecurityReports|There was an error while generating the report."
+msgstr ""
+
+msgid "SecurityReports|Unable to add %{invalidProjectsMessage}"
+msgstr ""
+
+msgid "SecurityReports|Unable to add %{invalidProjects}"
+msgstr ""
+
+msgid "SecurityReports|Undo dismiss"
+msgstr ""
+
+msgid "SecurityReports|While it's rare to have no vulnerabilities for your group, it can happen. In any event, we ask that you double check your settings to make sure you've set up your dashboard correctly."
+msgstr ""
+
+msgid "SecurityReports|While it's rare to have no vulnerabilities for your pipeline, it can happen. In any event, we ask that you double check your settings to make sure all security scanning jobs have passed successfully."
+msgstr ""
+
+msgid "SecurityReports|While it's rare to have no vulnerabilities for your project, it can happen. In any event, we ask that you double check your settings to make sure you've set up your dashboard correctly."
+msgstr ""
+
+msgid "SecurityReports|While it's rare to have no vulnerabilities, it can happen. In any event, we ask that you please double check your settings to make sure you've set up your dashboard correctly."
+msgstr ""
+
+msgid "SecurityReports|Won't fix / Accept risk"
+msgstr ""
+
+msgid "SecurityReports|You do not have sufficient permissions to access this report"
+msgstr ""
+
+msgid "SecurityReports|You must sign in as an authorized user to see this report"
+msgstr ""
+
+msgid "SecurityReports|[No reason]"
+msgstr ""
+
+msgid "See GitLab's %{password_policy_guidelines}"
+msgstr ""
+
+msgid "See metrics"
+msgstr ""
+
+msgid "See the affected projects in the GitLab admin panel"
+msgstr ""
+
+msgid "See what's new at GitLab"
+msgstr ""
+
+msgid "Select"
+msgstr ""
+
+msgid "Select Archive Format"
+msgstr ""
+
+msgid "Select Git revision"
+msgstr ""
+
+msgid "Select GitLab project to link with your Slack team"
+msgstr ""
+
+msgid "Select Page"
+msgstr ""
+
+msgid "Select Stack"
+msgstr ""
+
+msgid "Select a file from the left sidebar to begin editing. Afterwards, you'll be able to commit your changes."
+msgstr ""
+
+msgid "Select a group to invite"
+msgstr ""
+
+msgid "Select a label"
+msgstr ""
+
+msgid "Select a namespace to fork the project"
+msgstr ""
+
+msgid "Select a new namespace"
+msgstr ""
+
+msgid "Select a project"
+msgstr ""
+
+msgid "Select a project to read Insights configuration file"
+msgstr ""
+
+msgid "Select a reason"
+msgstr ""
+
+msgid "Select a repository"
+msgstr ""
+
+msgid "Select a template repository"
+msgstr ""
+
+msgid "Select a template type"
+msgstr ""
+
+msgid "Select a timezone"
+msgstr ""
+
+msgid "Select all"
+msgstr ""
+
+msgid "Select an existing Kubernetes cluster or create a new one"
+msgstr ""
+
+msgid "Select assignee"
+msgstr ""
+
+msgid "Select branch"
+msgstr ""
+
+msgid "Select branch/tag"
+msgstr ""
+
+msgid "Select due date"
+msgstr ""
+
+msgid "Select file"
+msgstr ""
+
+msgid "Select group or project"
+msgstr ""
+
+msgid "Select groups to replicate"
+msgstr ""
+
+msgid "Select health status"
+msgstr ""
+
+msgid "Select labels"
+msgstr ""
+
+msgid "Select merge moment"
+msgstr ""
+
+msgid "Select milestone"
+msgstr ""
+
+msgid "Select private project"
+msgstr ""
+
+msgid "Select project"
+msgstr ""
+
+msgid "Select project and zone to choose machine type"
+msgstr ""
+
+msgid "Select project to choose zone"
+msgstr ""
+
+msgid "Select projects"
+msgstr ""
+
+msgid "Select projects you want to import."
+msgstr ""
+
+msgid "Select required regulatory standard"
+msgstr ""
+
+msgid "Select shards to replicate"
+msgstr ""
+
+msgid "Select source branch"
+msgstr ""
+
+msgid "Select start date"
+msgstr ""
+
+msgid "Select status"
+msgstr ""
+
+msgid "Select strategy activation method"
+msgstr ""
+
+msgid "Select subscription"
+msgstr ""
+
+msgid "Select target branch"
+msgstr ""
+
+msgid "Select the branch you want to set as the default for this project. All merge requests and commits will automatically be made against this branch unless you specify a different one."
+msgstr ""
+
+msgid "Select the custom project template source group."
+msgstr ""
+
+msgid "Select timeframe"
+msgstr ""
+
+msgid "Select user"
+msgstr ""
+
+msgid "Selected levels cannot be used by non-admin users for groups, projects or snippets. If the public level is restricted, user profiles are only visible to logged in users."
+msgstr ""
+
+msgid "Selecting a GitLab user will add a link to the GitLab user in the descriptions of issues and comments (e.g. \"By <a href=\"#\">@johnsmith</a>\"). It will also associate and/or assign these issues and comments with the selected user."
+msgstr ""
+
+msgid "Selective synchronization"
+msgstr ""
+
+msgid "Self monitoring project does not exist"
+msgstr ""
+
+msgid "Self-monitoring project does not exist. Please check logs for any error messages"
+msgstr ""
+
+msgid "Self-monitoring project has been successfully deleted"
+msgstr ""
+
+msgid "Self-monitoring project was not deleted. Please check logs for any error messages"
+msgstr ""
+
+msgid "SelfMonitoring|Disable self monitoring?"
+msgstr ""
+
+msgid "SelfMonitoring|Disabling this feature will delete the self monitoring project. Are you sure you want to delete the project?"
+msgstr ""
+
+msgid "SelfMonitoring|Enable or disable instance self monitoring"
+msgstr ""
+
+msgid "SelfMonitoring|Enabling this feature creates a %{projectLinkStart}project%{projectLinkEnd} that can be used to monitor the health of your instance."
+msgstr ""
+
+msgid "SelfMonitoring|Enabling this feature creates a project that can be used to monitor the health of your instance."
+msgstr ""
+
+msgid "SelfMonitoring|Self monitoring"
+msgstr ""
+
+msgid "SelfMonitoring|Self monitoring project has been successfully created."
+msgstr ""
+
+msgid "SelfMonitoring|Self monitoring project has been successfully deleted."
+msgstr ""
+
+msgid "Send a separate email notification to Developers."
+msgstr ""
+
+msgid "Send confirmation email"
+msgstr ""
+
+msgid "Send email"
+msgstr ""
+
+msgid "Send email notification"
+msgstr ""
+
+msgid "Send message"
+msgstr ""
+
+msgid "Send report"
+msgstr ""
+
+msgid "Send usage data"
+msgstr ""
+
+msgid "Sentry API URL"
+msgstr ""
+
+msgid "Sentry event"
+msgstr ""
+
+msgid "Sep"
+msgstr ""
+
+msgid "Separate topics with commas."
+msgstr ""
+
+msgid "September"
+msgstr ""
+
+msgid "SeriesFinalConjunction|and"
+msgstr ""
+
+msgid "Serve repository static objects (e.g. archives, blobs, ...) from an external storage (e.g. a CDN)."
+msgstr ""
+
+msgid "Server supports batch API only, please update your Git LFS client to version 1.0.1 and up."
+msgstr ""
+
+msgid "Server version"
+msgstr ""
+
+msgid "Serverless"
+msgstr ""
+
+msgid "Serverless domain"
+msgstr ""
+
+msgid "ServerlessDetails|Function invocation metrics require Prometheus to be installed first."
+msgstr ""
+
+msgid "ServerlessDetails|Install Prometheus"
+msgstr ""
+
+msgid "ServerlessDetails|Invocation metrics loading or not available at this time."
+msgstr ""
+
+msgid "ServerlessDetails|Invocations"
+msgstr ""
+
+msgid "ServerlessDetails|Kubernetes Pods"
+msgstr ""
+
+msgid "ServerlessDetails|More information"
+msgstr ""
+
+msgid "ServerlessDetails|No pods loaded at this time."
+msgstr ""
+
+msgid "ServerlessDetails|Number of Kubernetes pods in use over time based on necessity."
+msgstr ""
+
+msgid "ServerlessDetails|pod in use"
+msgstr ""
+
+msgid "ServerlessDetails|pods in use"
+msgstr ""
+
+msgid "ServerlessURL|Copy URL"
+msgstr ""
+
+msgid "Serverless| In order to start using functions as a service, you must first install Knative on your Kubernetes cluster."
+msgstr ""
+
+msgid "Serverless|Getting started with serverless"
+msgstr ""
+
+msgid "Serverless|Help shape the future of Serverless at GitLab"
+msgstr ""
+
+msgid "Serverless|If you believe none of these apply, please check back later as the function data may be in the process of becoming available."
+msgstr ""
+
+msgid "Serverless|Install Knative"
+msgstr ""
+
+msgid "Serverless|Learn more about Serverless"
+msgstr ""
+
+msgid "Serverless|No functions available"
+msgstr ""
+
+msgid "Serverless|Sign up for First Look"
+msgstr ""
+
+msgid "Serverless|The deploy job has not finished."
+msgstr ""
+
+msgid "Serverless|The functions listed in the %{startTag}serverless.yml%{endTag} file don't match the namespace of your cluster."
+msgstr ""
+
+msgid "Serverless|There is currently no function data available from Knative. This could be for a variety of reasons including:"
+msgstr ""
+
+msgid "Serverless|We are continually striving to improve our Serverless functionality. As a Knative user, we would love to hear how we can make this experience better for you. Sign up for GitLab First Look today and we will be in touch shortly."
+msgstr ""
+
+msgid "Serverless|Your %{startTag}.gitlab-ci.yml%{endTag} file is not properly configured."
+msgstr ""
+
+msgid "Serverless|Your repository does not have a corresponding %{startTag}serverless.yml%{endTag} file."
+msgstr ""
+
+msgid "Service"
+msgstr ""
+
+msgid "Service Desk"
+msgstr ""
+
+msgid "Service Desk is enabled but not yet active"
+msgstr ""
+
+msgid "Service Templates"
+msgstr ""
+
+msgid "Service URL"
+msgstr ""
+
+msgid "Session duration (minutes)"
+msgstr ""
+
+msgid "Set %{epic_ref} as the parent epic."
+msgstr ""
+
+msgid "Set a default template for issue descriptions."
+msgstr ""
+
+msgid "Set a number of approvals required, the approvers and other approval settings."
+msgstr ""
+
+msgid "Set a password on your account to pull or push via %{protocol}."
+msgstr ""
+
+msgid "Set a template repository for projects in this group"
+msgstr ""
+
+msgid "Set an instance-wide domain that will be available to all clusters when installing Knative."
+msgstr ""
+
+msgid "Set default and restrict visibility levels. Configure import sources and git access protocol."
+msgstr ""
+
+msgid "Set due date"
+msgstr ""
+
+msgid "Set instance-wide template repository"
+msgstr ""
+
+msgid "Set iteration"
+msgstr ""
+
+msgid "Set max session time for web terminal."
+msgstr ""
+
+msgid "Set milestone"
+msgstr ""
+
+msgid "Set new password"
+msgstr ""
+
+msgid "Set notification email for abuse reports."
+msgstr ""
+
+msgid "Set parent epic to an epic"
+msgstr ""
+
+msgid "Set projects and maximum size limits, session duration, user options, and check feature availability for namespace plan."
+msgstr ""
+
+msgid "Set requirements for a user to sign-in. Enable mandatory two-factor authentication."
+msgstr ""
+
+msgid "Set target branch"
+msgstr ""
+
+msgid "Set target branch to %{branch_name}."
+msgstr ""
+
+msgid "Set the default expiration time for each job's artifacts. 0 for unlimited. The default unit is in seconds, but you can define an alternative. For example: <code>4 mins 2 sec</code>, <code>2h42min</code>."
+msgstr ""
+
+msgid "Set the due date to %{due_date}."
+msgstr ""
+
+msgid "Set the duration for which the jobs will be considered as old and expired. Once that time passes, the jobs will be archived and no longer able to be retried. Make it empty to never expire jobs. It has to be no less than 1 day, for example: <code>15 days</code>, <code>1 month</code>, <code>2 years</code>."
+msgstr ""
+
+msgid "Set the iteration to %{iteration_reference}."
+msgstr ""
+
+msgid "Set the maximum file size for each job's artifacts"
+msgstr ""
+
+msgid "Set the maximum number of pipeline minutes that a group can use on shared Runners per month. 0 for unlimited."
+msgstr ""
+
+msgid "Set the milestone to %{milestone_reference}."
+msgstr ""
+
+msgid "Set the number of concurrent requests this secondary node will make to the primary node while backfilling."
+msgstr ""
+
+msgid "Set the synchronization and verification capacity for the secondary node."
+msgstr ""
+
+msgid "Set the timeout in seconds to send a secondary node status to the primary and IPs allowed for the secondary nodes."
+msgstr ""
+
+msgid "Set time estimate"
+msgstr ""
+
+msgid "Set time estimate to %{time_estimate}."
+msgstr ""
+
+msgid "Set up CI/CD"
+msgstr ""
+
+msgid "Set up Jira Integration"
+msgstr ""
+
+msgid "Set up a %{type} Runner automatically"
+msgstr ""
+
+msgid "Set up a %{type} Runner manually"
+msgstr ""
+
+msgid "Set up assertions/attributes/claims (email, first_name, last_name) and NameID according to %{docsLinkStart}the documentation %{icon}%{docsLinkEnd}"
+msgstr ""
+
+msgid "Set up new U2F device"
+msgstr ""
+
+msgid "Set up new password"
+msgstr ""
+
+msgid "Set up pipeline subscriptions for this project."
+msgstr ""
+
+msgid "Set up your project to automatically push and/or pull changes to/from another repository. Branches, tags, and commits will be synced automatically."
+msgstr ""
+
+msgid "Set weight"
+msgstr ""
+
+msgid "Set weight to %{weight}."
+msgstr ""
+
+msgid "Set what should be replicated by choosing specific projects or groups by the secondary node."
+msgstr ""
+
+msgid "SetPasswordToCloneLink|set a password"
+msgstr ""
+
+msgid "SetStatusModal|Add status emoji"
+msgstr ""
+
+msgid "SetStatusModal|Clear status"
+msgstr ""
+
+msgid "SetStatusModal|Edit status"
+msgstr ""
+
+msgid "SetStatusModal|Remove status"
+msgstr ""
+
+msgid "SetStatusModal|Set a status"
+msgstr ""
+
+msgid "SetStatusModal|Set status"
+msgstr ""
+
+msgid "SetStatusModal|Sorry, we weren't able to set your status. Please try again later."
+msgstr ""
+
+msgid "SetStatusModal|What's your status?"
+msgstr ""
+
+msgid "Sets %{epic_ref} as parent epic."
+msgstr ""
+
+msgid "Sets target branch to %{branch_name}."
+msgstr ""
+
+msgid "Sets the due date to %{due_date}."
+msgstr ""
+
+msgid "Sets the iteration to %{iteration_reference}."
+msgstr ""
+
+msgid "Sets the milestone to %{milestone_reference}."
+msgstr ""
+
+msgid "Sets time estimate to %{time_estimate}."
+msgstr ""
+
+msgid "Sets weight to %{weight}."
+msgstr ""
+
+msgid "Settings"
+msgstr ""
+
+msgid "Settings related to the use and experience of using GitLab's Package Registry."
+msgstr ""
+
+msgid "Settings to prevent self-approval across all projects in the instance. Only an administrator can modify these settings."
+msgstr ""
+
+msgid "Severity"
+msgstr ""
+
+msgid "Shards (%{shards})"
+msgstr ""
+
+msgid "Shards to synchronize"
+msgstr ""
+
+msgid "Share"
+msgstr ""
+
+msgid "Share the <strong>%{sso_label}</strong> with members so they can sign in to your group through your identity provider"
+msgstr ""
+
+msgid "Shared Runners"
+msgstr ""
+
+msgid "Shared projects"
+msgstr ""
+
+msgid "Shared runners help link"
+msgstr ""
+
+msgid "SharedRunnersMinutesSettings|By resetting the pipeline minutes for this namespace, the currently used minutes will be set to zero."
+msgstr ""
+
+msgid "SharedRunnersMinutesSettings|Reset pipeline minutes"
+msgstr ""
+
+msgid "SharedRunnersMinutesSettings|Reset used pipeline minutes"
+msgstr ""
+
+msgid "Sherlock Transactions"
+msgstr ""
+
+msgid "Should you ever lose your phone or access to your one time password secret, each of these recovery codes can be used one time each to regain access to your account. Please save them in a safe place, or you %{b_start}will%{b_end} lose access to your account."
+msgstr ""
+
+msgid "Show all activity"
+msgstr ""
+
+msgid "Show all members"
+msgstr ""
+
+msgid "Show all requirements."
+msgstr ""
+
+msgid "Show archived projects"
+msgstr ""
+
+msgid "Show archived projects only"
+msgstr ""
+
+msgid "Show command"
+msgstr ""
+
+msgid "Show comments"
+msgstr ""
+
+msgid "Show comments only"
+msgstr ""
+
+msgid "Show commit description"
+msgstr ""
+
+msgid "Show complete raw log"
+msgstr ""
+
+msgid "Show details"
+msgstr ""
+
+msgid "Show file browser"
+msgstr ""
+
+msgid "Show file contents"
+msgstr ""
+
+msgid "Show latest version"
+msgstr ""
+
+msgid "Show list"
+msgstr ""
+
+msgid "Show me everything"
+msgstr ""
+
+msgid "Show me how to add a pipeline"
+msgstr ""
+
+msgid "Show me more advanced stuff"
+msgstr ""
+
+msgid "Show only direct members"
+msgstr ""
+
+msgid "Show only inherited members"
+msgstr ""
+
+msgid "Show parent pages"
+msgstr ""
+
+msgid "Show parent subgroups"
+msgstr ""
+
+msgid "Show whitespace changes"
+msgstr ""
+
+msgid "Showing %d event"
+msgid_plural "Showing %d events"
+msgstr[0] ""
+
+msgid "Showing %{count} of %{total} projects"
+msgstr ""
+
+msgid "Showing %{count} project"
+msgid_plural "Showing %{count} projects"
+msgstr[0] ""
+
+msgid "Showing %{limit} of %{total_count} issues. "
+msgstr ""
+
+msgid "Showing %{pageSize} of %{total} issues"
+msgstr ""
+
+msgid "Showing Latest Version"
+msgstr ""
+
+msgid "Showing Version #%{versionNumber}"
+msgstr ""
+
+msgid "Showing all issues"
+msgstr ""
+
+msgid "Showing last %{size} of log -"
+msgstr ""
+
+msgid "Side-by-side"
+msgstr ""
+
+msgid "Sidebar|Assign health status"
+msgstr ""
+
+msgid "Sidebar|Change weight"
+msgstr ""
+
+msgid "Sidebar|Health status"
+msgstr ""
+
+msgid "Sidebar|No status"
+msgstr ""
+
+msgid "Sidebar|None"
+msgstr ""
+
+msgid "Sidebar|Only numeral characters allowed"
+msgstr ""
+
+msgid "Sidebar|Weight"
+msgstr ""
+
+msgid "Sign in"
+msgstr ""
+
+msgid "Sign in / Register"
+msgstr ""
+
+msgid "Sign in to \"%{group_name}\""
+msgstr ""
+
+msgid "Sign in using smart card"
+msgstr ""
+
+msgid "Sign in via 2FA code"
+msgstr ""
+
+msgid "Sign in with Single Sign-On"
+msgstr ""
+
+msgid "Sign in with smart card"
+msgstr ""
+
+msgid "Sign out"
+msgstr ""
+
+msgid "Sign out & Register"
+msgstr ""
+
+msgid "Sign up"
+msgstr ""
+
+msgid "Sign up was successful! Please confirm your email to sign in."
+msgstr ""
+
+msgid "Sign-in restrictions"
+msgstr ""
+
+msgid "Sign-up restrictions"
+msgstr ""
+
+msgid "SignUp|First Name is too long (maximum is %{max_length} characters)."
+msgstr ""
+
+msgid "SignUp|Last Name is too long (maximum is %{max_length} characters)."
+msgstr ""
+
+msgid "SignUp|Name is too long (maximum is %{max_length} characters)."
+msgstr ""
+
+msgid "SignUp|Username is too long (maximum is %{max_length} characters)."
+msgstr ""
+
+msgid "Signed in"
+msgstr ""
+
+msgid "Signed in with %{authentication} authentication"
+msgstr ""
+
+msgid "Signing in using %{label} has been disabled"
+msgstr ""
+
+msgid "Signing in using your %{label} account without a pre-existing GitLab account is not allowed."
+msgstr ""
+
+msgid "Similar issues"
+msgstr ""
+
+msgid "Single or combined queries"
+msgstr ""
+
+msgid "Size"
+msgstr ""
+
+msgid "Size and domain settings for static websites"
+msgstr ""
+
+msgid "Size limit per repository (MB)"
+msgstr ""
+
+msgid "Size settings for static websites"
+msgstr ""
+
+msgid "Skip outdated deployment jobs"
+msgstr ""
+
+msgid "Skipped"
+msgstr ""
+
+msgid "Slack application"
+msgstr ""
+
+msgid "Slack channels (e.g. general, development)"
+msgstr ""
+
+msgid "Slack integration allows you to interact with GitLab via slash commands in a chat window."
+msgstr ""
+
+msgid "SlackIntegration|%{webhooks_link_start}Add an incoming webhook%{webhooks_link_end} in your Slack team. The default channel can be overridden for each event."
+msgstr ""
+
+msgid "SlackIntegration|<strong>Note:</strong> Usernames and private channels are not supported."
+msgstr ""
+
+msgid "SlackIntegration|Paste the <strong>Webhook URL</strong> into the field below."
+msgstr ""
+
+msgid "SlackIntegration|Select events below to enable notifications. The <strong>Slack channel names</strong> and <strong>Slack username</strong> fields are optional."
+msgstr ""
+
+msgid "SlackIntegration|This service send notifications about projects' events to Slack channels. To set up this service:"
+msgstr ""
+
+msgid "SlackService|2. Paste the <strong>Token</strong> into the field below"
+msgstr ""
+
+msgid "SlackService|3. Select the <strong>Active</strong> checkbox, press <strong>Save changes</strong> and start using GitLab inside Slack!"
+msgstr ""
+
+msgid "SlackService|Fill in the word that works best for your team."
+msgstr ""
+
+msgid "SlackService|See list of available commands in Slack after setting up this service, by entering"
+msgstr ""
+
+msgid "SlackService|This service allows users to perform common operations on this project by entering slash commands in Slack."
+msgstr ""
+
+msgid "Slower but makes sure the project workspace is pristine as it clones the repository from scratch for every job"
+msgstr ""
+
+msgid "Smartcard"
+msgstr ""
+
+msgid "Smartcard authentication failed: client certificate header is missing."
+msgstr ""
+
+msgid "Snippets"
+msgstr ""
+
+msgid "Snippets with non-text files can only be edited via Git."
+msgstr ""
+
+msgid "SnippetsEmptyState|Code snippets"
+msgstr ""
+
+msgid "SnippetsEmptyState|Documentation"
+msgstr ""
+
+msgid "SnippetsEmptyState|New snippet"
+msgstr ""
+
+msgid "SnippetsEmptyState|No snippets found"
+msgstr ""
+
+msgid "SnippetsEmptyState|Store, share, and embed small pieces of code and text."
+msgstr ""
+
+msgid "SnippetsEmptyState|There are no snippets to show."
+msgstr ""
+
+msgid "Snippets|Description (optional)"
+msgstr ""
+
+msgid "Snippets|File"
+msgstr ""
+
+msgid "Snippets|Give your file a name to add code highlighting, e.g. example.rb for Ruby"
+msgstr ""
+
+msgid "Snippets|Optionally add a description about what your snippet does or how to use it..."
+msgstr ""
+
+msgid "Snippets|Optionally add a description about what your snippet does or how to use it…"
+msgstr ""
+
+msgid "Snowplow"
+msgstr ""
+
+msgid "Solution"
+msgstr ""
+
+msgid "Some child epics may be hidden due to applied filters"
+msgstr ""
+
+msgid "Some common domains are not allowed. %{read_more_link}."
+msgstr ""
+
+msgid "Some email servers do not support overriding the email sender name. Enable this option to include the name of the author of the issue, merge request or comment in the email body instead."
+msgstr ""
+
+msgid "Some of the designs you tried uploading did not change:"
+msgstr ""
+
+msgid "Some of your epics may not be visible. A roadmap is limited to the first 1,000 epics, in your selected sort order."
+msgstr ""
+
+msgid "Someone edited the issue at the same time you did. Please check out %{linkStart}the issue%{linkEnd} and make sure your changes will not unintentionally remove theirs."
+msgstr ""
+
+msgid "Someone edited this %{issueType} at the same time you did. The description has been updated and you will need to make your changes again."
+msgstr ""
+
+msgid "Someone edited this merge request at the same time you did. Please refresh the page to see changes."
+msgstr ""
+
+msgid "Something went wrong on our end"
+msgstr ""
+
+msgid "Something went wrong on our end."
+msgstr ""
+
+msgid "Something went wrong on our end. Please try again!"
+msgstr ""
+
+msgid "Something went wrong on our end. Please try again."
+msgstr ""
+
+msgid "Something went wrong trying to change the confidentiality of this issue"
+msgstr ""
+
+msgid "Something went wrong trying to change the locked state of this %{issuableDisplayName}"
+msgstr ""
+
+msgid "Something went wrong when toggling the button"
+msgstr ""
+
+msgid "Something went wrong while adding your award. Please try again."
+msgstr ""
+
+msgid "Something went wrong while applying the batch of suggestions. Please try again."
+msgstr ""
+
+msgid "Something went wrong while applying the suggestion. Please try again."
+msgstr ""
+
+msgid "Something went wrong while archiving a requirement."
+msgstr ""
+
+msgid "Something went wrong while closing the %{issuable}. Please try again later"
+msgstr ""
+
+msgid "Something went wrong while creating a requirement."
+msgstr ""
+
+msgid "Something went wrong while deleting description changes. Please try again."
+msgstr ""
+
+msgid "Something went wrong while deleting the package."
+msgstr ""
+
+msgid "Something went wrong while deleting the source branch. Please try again."
+msgstr ""
+
+msgid "Something went wrong while deleting your note. Please try again."
+msgstr ""
+
+msgid "Something went wrong while deploying this environment. Please try again."
+msgstr ""
+
+msgid "Something went wrong while editing your comment. Please try again."
+msgstr ""
+
+msgid "Something went wrong while fetching %{listType} list"
+msgstr ""
+
+msgid "Something went wrong while fetching comments. Please try again."
+msgstr ""
+
+msgid "Something went wrong while fetching description changes. Please try again."
+msgstr ""
+
+msgid "Something went wrong while fetching group member contributions"
+msgstr ""
+
+msgid "Something went wrong while fetching latest comments."
+msgstr ""
+
+msgid "Something went wrong while fetching projects"
+msgstr ""
+
+msgid "Something went wrong while fetching projects."
+msgstr ""
+
+msgid "Something went wrong while fetching related merge requests."
+msgstr ""
+
+msgid "Something went wrong while fetching requirements count."
+msgstr ""
+
+msgid "Something went wrong while fetching requirements list."
+msgstr ""
+
+msgid "Something went wrong while fetching the environments for this merge request. Please try again."
+msgstr ""
+
+msgid "Something went wrong while fetching the package."
+msgstr ""
+
+msgid "Something went wrong while fetching the packages list."
+msgstr ""
+
+msgid "Something went wrong while initializing the OpenAPI viewer"
+msgstr ""
+
+msgid "Something went wrong while merging this merge request. Please try again."
+msgstr ""
+
+msgid "Something went wrong while moving issues."
+msgstr ""
+
+msgid "Something went wrong while obtaining the Let's Encrypt certificate."
+msgstr ""
+
+msgid "Something went wrong while performing the action."
+msgstr ""
+
+msgid "Something went wrong while reopening a requirement."
+msgstr ""
+
+msgid "Something went wrong while reopening the %{issuable}. Please try again later"
+msgstr ""
+
+msgid "Something went wrong while resolving this discussion. Please try again."
+msgstr ""
+
+msgid "Something went wrong while stopping this environment. Please try again."
+msgstr ""
+
+msgid "Something went wrong while toggling auto-fix settings, please try again later."
+msgstr ""
+
+msgid "Something went wrong while updating a requirement."
+msgstr ""
+
+msgid "Something went wrong while updating assignees"
+msgstr ""
+
+msgid "Something went wrong while updating your list settings"
+msgstr ""
+
+msgid "Something went wrong with your automatic subscription renewal."
+msgstr ""
+
+msgid "Something went wrong, unable to add %{project} to dashboard"
+msgstr ""
+
+msgid "Something went wrong, unable to add projects to dashboard"
+msgstr ""
+
+msgid "Something went wrong, unable to get projects"
+msgstr ""
+
+msgid "Something went wrong, unable to remove project"
+msgstr ""
+
+msgid "Something went wrong, unable to search projects"
+msgstr ""
+
+msgid "Something went wrong."
+msgstr ""
+
+msgid "Something went wrong. Please try again."
+msgstr ""
+
+msgid "Something went wrong. Try again later."
+msgstr ""
+
+msgid "Sorry, no epics matched your search"
+msgstr ""
+
+msgid "Sorry, no projects matched your search"
+msgstr ""
+
+msgid "Sorry, you have exceeded the maximum browsable page number. Please use the API to explore further."
+msgstr ""
+
+msgid "Sorry, your filter produced no results"
+msgstr ""
+
+msgid "Sort by"
+msgstr ""
+
+msgid "Sort direction"
+msgstr ""
+
+msgid "Sort direction: Ascending"
+msgstr ""
+
+msgid "Sort direction: Descending"
+msgstr ""
+
+msgid "SortOptions|Access level, ascending"
+msgstr ""
+
+msgid "SortOptions|Access level, descending"
+msgstr ""
+
+msgid "SortOptions|Created date"
+msgstr ""
+
+msgid "SortOptions|Due date"
+msgstr ""
+
+msgid "SortOptions|Due later"
+msgstr ""
+
+msgid "SortOptions|Due soon"
+msgstr ""
+
+msgid "SortOptions|Expired date"
+msgstr ""
+
+msgid "SortOptions|Label priority"
+msgstr ""
+
+msgid "SortOptions|Largest group"
+msgstr ""
+
+msgid "SortOptions|Largest repository"
+msgstr ""
+
+msgid "SortOptions|Last Contact"
+msgstr ""
+
+msgid "SortOptions|Last created"
+msgstr ""
+
+msgid "SortOptions|Last joined"
+msgstr ""
+
+msgid "SortOptions|Last updated"
+msgstr ""
+
+msgid "SortOptions|Least popular"
+msgstr ""
+
+msgid "SortOptions|Less weight"
+msgstr ""
+
+msgid "SortOptions|Manual"
+msgstr ""
+
+msgid "SortOptions|Milestone due date"
+msgstr ""
+
+msgid "SortOptions|Milestone due later"
+msgstr ""
+
+msgid "SortOptions|Milestone due soon"
+msgstr ""
+
+msgid "SortOptions|More weight"
+msgstr ""
+
+msgid "SortOptions|Most popular"
+msgstr ""
+
+msgid "SortOptions|Most stars"
+msgstr ""
+
+msgid "SortOptions|Name"
+msgstr ""
+
+msgid "SortOptions|Name, ascending"
+msgstr ""
+
+msgid "SortOptions|Name, descending"
+msgstr ""
+
+msgid "SortOptions|Oldest created"
+msgstr ""
+
+msgid "SortOptions|Oldest joined"
+msgstr ""
+
+msgid "SortOptions|Oldest last activity"
+msgstr ""
+
+msgid "SortOptions|Oldest sign in"
+msgstr ""
+
+msgid "SortOptions|Oldest starred"
+msgstr ""
+
+msgid "SortOptions|Oldest updated"
+msgstr ""
+
+msgid "SortOptions|Popularity"
+msgstr ""
+
+msgid "SortOptions|Priority"
+msgstr ""
+
+msgid "SortOptions|Project"
+msgstr ""
+
+msgid "SortOptions|Recent last activity"
+msgstr ""
+
+msgid "SortOptions|Recent sign in"
+msgstr ""
+
+msgid "SortOptions|Recently starred"
+msgstr ""
+
+msgid "SortOptions|Size"
+msgstr ""
+
+msgid "SortOptions|Sort by:"
+msgstr ""
+
+msgid "SortOptions|Sort direction"
+msgstr ""
+
+msgid "SortOptions|Stars"
+msgstr ""
+
+msgid "SortOptions|Start date"
+msgstr ""
+
+msgid "SortOptions|Start later"
+msgstr ""
+
+msgid "SortOptions|Start soon"
+msgstr ""
+
+msgid "SortOptions|Type"
+msgstr ""
+
+msgid "SortOptions|Version"
+msgstr ""
+
+msgid "SortOptions|Weight"
+msgstr ""
+
+msgid "Source"
+msgstr ""
+
+msgid "Source (branch or tag)"
+msgstr ""
+
+msgid "Source code"
+msgstr ""
+
+msgid "Source code (%{fileExtension})"
+msgstr ""
+
+msgid "Source is not available"
+msgstr ""
+
+msgid "Source project cannot be found."
+msgstr ""
+
+msgid "Sourcegraph"
+msgstr ""
+
+msgid "SourcegraphAdmin|Block on private and internal projects"
+msgstr ""
+
+msgid "SourcegraphAdmin|Configure the URL to a Sourcegraph instance which can read your GitLab projects."
+msgstr ""
+
+msgid "SourcegraphAdmin|Enable Sourcegraph"
+msgstr ""
+
+msgid "SourcegraphAdmin|Enable code intelligence powered by %{link_start}Sourcegraph%{link_end} on your GitLab instance's code views and merge requests."
+msgstr ""
+
+msgid "SourcegraphAdmin|If checked, only public projects will have code intelligence and communicate with Sourcegraph."
+msgstr ""
+
+msgid "SourcegraphAdmin|More information"
+msgstr ""
+
+msgid "SourcegraphAdmin|Save changes"
+msgstr ""
+
+msgid "SourcegraphAdmin|Sourcegraph URL"
+msgstr ""
+
+msgid "SourcegraphAdmin|e.g. https://sourcegraph.example.com"
+msgstr ""
+
+msgid "SourcegraphPreferences|This feature is experimental and currently limited to certain projects."
+msgstr ""
+
+msgid "SourcegraphPreferences|This feature is experimental and limited to public projects."
+msgstr ""
+
+msgid "SourcegraphPreferences|This feature is experimental."
+msgstr ""
+
+msgid "SourcegraphPreferences|Uses %{link_start}Sourcegraph.com%{link_end}."
+msgstr ""
+
+msgid "SourcegraphPreferences|Uses a custom %{link_start}Sourcegraph instance%{link_end}."
+msgstr ""
+
+msgid "Spam Logs"
+msgstr ""
+
+msgid "Spam and Anti-bot Protection"
+msgstr ""
+
+msgid "Spam log successfully submitted as ham."
+msgstr ""
+
+msgid "Specific Runners"
+msgstr ""
+
+msgid "Specified URL cannot be used: \"%{reason}\""
+msgstr ""
+
+msgid "Specify an e-mail address regex pattern to identify default internal users."
+msgstr ""
+
+msgid "Specify the following URL during the Runner setup:"
+msgstr ""
+
+msgid "Speed up your DevOps<br>with GitLab"
+msgstr ""
+
+msgid "Squash commit message"
+msgstr ""
+
+msgid "Squash commits"
+msgstr ""
+
+msgid "Stack trace"
+msgstr ""
+
+msgid "Stacktrace snippet"
+msgstr ""
+
+msgid "Stage"
+msgstr ""
+
+msgid "Stage & Commit"
+msgstr ""
+
+msgid "Stage data updated"
+msgstr ""
+
+msgid "Stage removed"
+msgstr ""
+
+msgid "Standard"
+msgstr ""
+
+msgid "Star a label to make it a priority label. Order the prioritized labels to change their relative priority, by dragging."
+msgstr ""
+
+msgid "Star labels to start sorting by priority"
+msgstr ""
+
+msgid "Star toggle failed. Try again later."
+msgstr ""
+
+msgid "StarProject|Star"
+msgstr ""
+
+msgid "Starred Projects"
+msgstr ""
+
+msgid "Starred Projects' Activity"
+msgstr ""
+
+msgid "Starred projects"
+msgstr ""
+
+msgid "StarredProjectsEmptyState|Visit a project page and press on a star icon. Then, you can find the project on this page."
+msgstr ""
+
+msgid "StarredProjectsEmptyState|You don't have starred projects yet."
+msgstr ""
+
+msgid "Starrers"
+msgstr ""
+
+msgid "Stars"
+msgstr ""
+
+msgid "Start Date"
+msgstr ""
+
+msgid "Start Web Terminal"
+msgstr ""
+
+msgid "Start a %{new_merge_request} with these changes"
+msgstr ""
+
+msgid "Start a Free Gold Trial"
+msgstr ""
+
+msgid "Start a new discussion..."
+msgstr ""
+
+msgid "Start a new merge request"
+msgstr ""
+
+msgid "Start a review"
+msgstr ""
+
+msgid "Start and due date"
+msgstr ""
+
+msgid "Start by choosing a group to see how your team is spending time. You can then drill down to the project level."
+msgstr ""
+
+msgid "Start by choosing a group to start exploring the merge requests in that group. You can then proceed to filter by projects, labels, milestones and authors."
+msgstr ""
+
+msgid "Start cleanup"
+msgstr ""
+
+msgid "Start date"
+msgstr ""
+
+msgid "Start merge train"
+msgstr ""
+
+msgid "Start merge train when pipeline succeeds"
+msgstr ""
+
+msgid "Start search"
+msgstr ""
+
+msgid "Start the Runner!"
+msgstr ""
+
+msgid "Start thread"
+msgstr ""
+
+msgid "Start thread & close %{noteable_name}"
+msgstr ""
+
+msgid "Start thread & reopen %{noteable_name}"
+msgstr ""
+
+msgid "Start using Directed Acyclic Graphs (DAG)"
+msgstr ""
+
+msgid "Start your Free Gold Trial"
+msgstr ""
+
+msgid "Start your free trial"
+msgstr ""
+
+msgid "Start your trial"
+msgstr ""
+
+msgid "Started"
+msgstr ""
+
+msgid "Started %{startsIn}"
+msgstr ""
+
+msgid "Started asynchronous removal of all repository check states."
+msgstr ""
+
+msgid "Started:"
+msgstr ""
+
+msgid "Starting..."
+msgstr ""
+
+msgid "Starts %{startsIn}"
+msgstr ""
+
+msgid "Starts at (UTC)"
+msgstr ""
+
+msgid "State your message to activate"
+msgstr ""
+
+msgid "Static Application Security Testing (SAST)"
+msgstr ""
+
+msgid "StaticSiteEditor|An error occurred while submitting your changes."
+msgstr ""
+
+msgid "StaticSiteEditor|Branch could not be created."
+msgstr ""
+
+msgid "StaticSiteEditor|Could not commit the content changes."
+msgstr ""
+
+msgid "StaticSiteEditor|Could not create merge request."
+msgstr ""
+
+msgid "StaticSiteEditor|Incompatible file content"
+msgstr ""
+
+msgid "StaticSiteEditor|Return to site"
+msgstr ""
+
+msgid "StaticSiteEditor|Static site editor"
+msgstr ""
+
+msgid "StaticSiteEditor|Success!"
+msgstr ""
+
+msgid "StaticSiteEditor|Summary of changes"
+msgstr ""
+
+msgid "StaticSiteEditor|The Static Site Editor is currently configured to only edit Markdown content on pages generated from Middleman. Visit the documentation to learn more about configuring your site to use the Static Site Editor."
+msgstr ""
+
+msgid "StaticSiteEditor|Update %{sourcePath} file"
+msgstr ""
+
+msgid "StaticSiteEditor|View documentation"
+msgstr ""
+
+msgid "StaticSiteEditor|View merge request"
+msgstr ""
+
+msgid "StaticSiteEditor|You added a commit:"
+msgstr ""
+
+msgid "StaticSiteEditor|You created a merge request:"
+msgstr ""
+
+msgid "StaticSiteEditor|You created a new branch:"
+msgstr ""
+
+msgid "StaticSiteEditor|Your changes have been submitted and a merge request has been created. The changes won’t be visible on the site until the merge request has been accepted."
+msgstr ""
+
+msgid "Statistics"
+msgstr ""
+
+msgid "Status"
+msgstr ""
+
+msgid "Status:"
+msgstr ""
+
+msgid "Status: %{title}"
+msgstr ""
+
+msgid "StatusPage|AWS Secret access key"
+msgstr ""
+
+msgid "StatusPage|AWS access key ID"
+msgstr ""
+
+msgid "StatusPage|AWS documentation"
+msgstr ""
+
+msgid "StatusPage|AWS region"
+msgstr ""
+
+msgid "StatusPage|Active"
+msgstr ""
+
+msgid "StatusPage|Bucket %{docsLink}"
+msgstr ""
+
+msgid "StatusPage|Configure file storage settings to link issues in this project to an external status page."
+msgstr ""
+
+msgid "StatusPage|For help with configuration, visit %{docsLink}"
+msgstr ""
+
+msgid "StatusPage|S3 Bucket name"
+msgstr ""
+
+msgid "StatusPage|Status page"
+msgstr ""
+
+msgid "StatusPage|Status page URL"
+msgstr ""
+
+msgid "StatusPage|Status page frontend documentation"
+msgstr ""
+
+msgid "StatusPage|To publish incidents to an external status page, GitLab will store a JSON file in your Amazon S3 account in a location accessible to your external status page service. Make sure to also set up %{docsLink}"
+msgstr ""
+
+msgid "StatusPage|configuration documentation"
+msgstr ""
+
+msgid "StatusPage|your status page frontend."
+msgstr ""
+
+msgid "Stay updated about the performance and health of your environment by configuring Prometheus to monitor your deployments."
+msgstr ""
+
+msgid "Still, we recommend keeping a backup saved somewhere. Otherwise, if you ever need it and have lost it, you will need to request GitLab Inc. to send it to you again."
+msgstr ""
+
+msgid "Stop Terminal"
+msgstr ""
+
+msgid "Stop impersonation"
+msgstr ""
+
+msgid "Stop this environment"
+msgstr ""
+
+msgid "Stopped"
+msgstr ""
+
+msgid "Stopping..."
+msgstr ""
+
+msgid "Storage"
+msgstr ""
+
+msgid "Storage nodes for new repositories"
+msgstr ""
+
+msgid "Storage:"
+msgstr ""
+
+msgid "StorageSize|Unknown"
+msgstr ""
+
+msgid "Subgroup milestone"
+msgstr ""
+
+msgid "Subgroup overview"
+msgstr ""
+
+msgid "SubgroupCreationLevel|Allowed to create subgroups"
+msgstr ""
+
+msgid "SubgroupCreationlevel|Allowed to create subgroups"
+msgstr ""
+
+msgid "SubgroupCreationlevel|Maintainers"
+msgstr ""
+
+msgid "SubgroupCreationlevel|Owners"
+msgstr ""
+
+msgid "Subgroups"
+msgstr ""
+
+msgid "Subgroups and projects"
+msgstr ""
+
+msgid "Subject Key Identifier:"
+msgstr ""
+
+msgid "Subkeys"
+msgstr ""
+
+msgid "Submit %{humanized_resource_name}"
+msgstr ""
+
+msgid "Submit Changes"
+msgstr ""
+
+msgid "Submit a review"
+msgstr ""
+
+msgid "Submit as spam"
+msgstr ""
+
+msgid "Submit feedback"
+msgstr ""
+
+msgid "Submit issue"
+msgstr ""
+
+msgid "Submit review"
+msgstr ""
+
+msgid "Submit search"
+msgstr ""
+
+msgid "Submit the current review."
+msgstr ""
+
+msgid "Submitted the current review."
+msgstr ""
+
+msgid "Subscribe"
+msgstr ""
+
+msgid "Subscribe at group level"
+msgstr ""
+
+msgid "Subscribe at project level"
+msgstr ""
+
+msgid "Subscribe to RSS feed"
+msgstr ""
+
+msgid "Subscribe to calendar"
+msgstr ""
+
+msgid "Subscribed"
+msgstr ""
+
+msgid "Subscribed to this %{quick_action_target}."
+msgstr ""
+
+msgid "Subscribes to this %{quick_action_target}."
+msgstr ""
+
+msgid "Subscription"
+msgstr ""
+
+msgid "Subscription deletion failed."
+msgstr ""
+
+msgid "Subscription successfully applied to \"%{group_name}\""
+msgstr ""
+
+msgid "Subscription successfully created."
+msgstr ""
+
+msgid "Subscription successfully deleted."
+msgstr ""
+
+msgid "SubscriptionTable|Billing"
+msgstr ""
+
+msgid "SubscriptionTable|Free"
+msgstr ""
+
+msgid "SubscriptionTable|GitLab allows you to continue using your subscription even if you exceed the number of seats you purchased. You will be required to pay for these seats upon renewal."
+msgstr ""
+
+msgid "SubscriptionTable|Last invoice"
+msgstr ""
+
+msgid "SubscriptionTable|Loading subscriptions"
+msgstr ""
+
+msgid "SubscriptionTable|Manage"
+msgstr ""
+
+msgid "SubscriptionTable|Max seats used"
+msgstr ""
+
+msgid "SubscriptionTable|Next invoice"
+msgstr ""
+
+msgid "SubscriptionTable|Seats currently in use"
+msgstr ""
+
+msgid "SubscriptionTable|Seats in subscription"
+msgstr ""
+
+msgid "SubscriptionTable|Seats owed"
+msgstr ""
+
+msgid "SubscriptionTable|Subscription end date"
+msgstr ""
+
+msgid "SubscriptionTable|Subscription start date"
+msgstr ""
+
+msgid "SubscriptionTable|This is the last time the GitLab.com team was in contact with you to settle any outstanding balances."
+msgstr ""
+
+msgid "SubscriptionTable|This is the maximum number of users that have existed at the same time since this subscription started."
+msgstr ""
+
+msgid "SubscriptionTable|This is the next date when the GitLab.com team is scheduled to get in contact with you to settle any outstanding balances."
+msgstr ""
+
+msgid "SubscriptionTable|This is the number of seats you will be required to purchase if you update to a paid plan."
+msgstr ""
+
+msgid "SubscriptionTable|Trial"
+msgstr ""
+
+msgid "SubscriptionTable|Trial end date"
+msgstr ""
+
+msgid "SubscriptionTable|Trial start date"
+msgstr ""
+
+msgid "SubscriptionTable|Upgrade"
+msgstr ""
+
+msgid "SubscriptionTable|Usage"
+msgstr ""
+
+msgid "SubscriptionTable|Usage count is performed once a day at 12:00 PM."
+msgstr ""
+
+msgid "Subscriptions"
+msgstr ""
+
+msgid "Subtracted"
+msgstr ""
+
+msgid "Subtracts"
+msgstr ""
+
+msgid "Succeeded"
+msgstr ""
+
+msgid "Successfully activated"
+msgstr ""
+
+msgid "Successfully blocked"
+msgstr ""
+
+msgid "Successfully confirmed"
+msgstr ""
+
+msgid "Successfully deactivated"
+msgstr ""
+
+msgid "Successfully deleted U2F device."
+msgstr ""
+
+msgid "Successfully removed email."
+msgstr ""
+
+msgid "Successfully scheduled a pipeline to run. Go to the %{pipelines_link_start}Pipelines page%{pipelines_link_end} for details."
+msgstr ""
+
+msgid "Successfully unblocked"
+msgstr ""
+
+msgid "Successfully unlocked"
+msgstr ""
+
+msgid "Successfully verified domain ownership"
+msgstr ""
+
+msgid "Suggest code changes which can be immediately applied in one click. Try it out!"
+msgstr ""
+
+msgid "Suggested Solutions"
+msgstr ""
+
+msgid "Suggested change"
+msgstr ""
+
+msgid "Suggested solutions help link"
+msgstr ""
+
+msgid "SuggestedColors|Bright green"
+msgstr ""
+
+msgid "SuggestedColors|Dark grayish cyan"
+msgstr ""
+
+msgid "SuggestedColors|Dark moderate blue"
+msgstr ""
+
+msgid "SuggestedColors|Dark moderate orange"
+msgstr ""
+
+msgid "SuggestedColors|Dark moderate pink"
+msgstr ""
+
+msgid "SuggestedColors|Dark moderate violet"
+msgstr ""
+
+msgid "SuggestedColors|Feijoa"
+msgstr ""
+
+msgid "SuggestedColors|Lime green"
+msgstr ""
+
+msgid "SuggestedColors|Moderate blue"
+msgstr ""
+
+msgid "SuggestedColors|Pure red"
+msgstr ""
+
+msgid "SuggestedColors|Slightly desaturated blue"
+msgstr ""
+
+msgid "SuggestedColors|Slightly desaturated green"
+msgstr ""
+
+msgid "SuggestedColors|Soft orange"
+msgstr ""
+
+msgid "SuggestedColors|Soft red"
+msgstr ""
+
+msgid "SuggestedColors|Strong pink"
+msgstr ""
+
+msgid "SuggestedColors|Strong red"
+msgstr ""
+
+msgid "SuggestedColors|Strong yellow"
+msgstr ""
+
+msgid "SuggestedColors|UA blue"
+msgstr ""
+
+msgid "SuggestedColors|Very dark desaturated blue"
+msgstr ""
+
+msgid "SuggestedColors|Very dark lime green"
+msgstr ""
+
+msgid "SuggestedColors|Very pale orange"
+msgstr ""
+
+msgid "Suggestion is not applicable as the suggestion was not found."
+msgstr ""
+
+msgid "Suggestions are not applicable as one or more suggestions were not found."
+msgstr ""
+
+msgid "Suggestions are not applicable as their lines cannot overlap."
+msgstr ""
+
+msgid "Suggestions must all be on the same branch."
+msgstr ""
+
+msgid "Suggestions that change line count can't be added to batches, yet."
+msgstr ""
+
+msgid "Suggestions:"
+msgstr ""
+
+msgid "Suite"
+msgstr ""
+
+msgid "Summary"
+msgstr ""
+
+msgid "Sunday"
+msgstr ""
+
+msgid "Support"
+msgstr ""
+
+msgid "Support for custom certificates is disabled. Ask your system's administrator to enable it."
+msgstr ""
+
+msgid "Support page URL"
+msgstr ""
+
+msgid "Survey Response"
+msgstr ""
+
+msgid "Switch branch/tag"
+msgstr ""
+
+msgid "Switch to GitLab Next"
+msgstr ""
+
+msgid "Switch to the source to copy the file contents"
+msgstr ""
+
+msgid "Sync information"
+msgstr ""
+
+msgid "Synced"
+msgstr ""
+
+msgid "Synchronization disabled"
+msgstr ""
+
+msgid "System"
+msgstr ""
+
+msgid "System Hooks"
+msgstr ""
+
+msgid "System Info"
+msgstr ""
+
+msgid "System default (%{default})"
+msgstr ""
+
+msgid "System header and footer"
+msgstr ""
+
+msgid "System hook was successfully updated."
+msgstr ""
+
+msgid "System metrics (Custom)"
+msgstr ""
+
+msgid "System metrics (Kubernetes)"
+msgstr ""
+
+msgid "Table of Contents"
+msgstr ""
+
+msgid "Tag"
+msgstr ""
+
+msgid "Tag list:"
+msgstr ""
+
+msgid "Tag name"
+msgstr ""
+
+msgid "Tag this commit."
+msgstr ""
+
+msgid "Tagged this commit to %{tag_name} with \"%{message}\"."
+msgstr ""
+
+msgid "Tagged this commit to %{tag_name}."
+msgstr ""
+
+msgid "Tags"
+msgstr ""
+
+msgid "Tags feed"
+msgstr ""
+
+msgid "Tags this commit to %{tag_name} with \"%{message}\"."
+msgstr ""
+
+msgid "Tags this commit to %{tag_name}."
+msgstr ""
+
+msgid "Tags:"
+msgstr ""
+
+msgid "TagsPage|Browse commits"
+msgstr ""
+
+msgid "TagsPage|Browse files"
+msgstr ""
+
+msgid "TagsPage|Can't find HEAD commit for this tag"
+msgstr ""
+
+msgid "TagsPage|Cancel"
+msgstr ""
+
+msgid "TagsPage|Create tag"
+msgstr ""
+
+msgid "TagsPage|Delete tag"
+msgstr ""
+
+msgid "TagsPage|Deleting the %{tag_name} tag cannot be undone. Are you sure?"
+msgstr ""
+
+msgid "TagsPage|Edit release notes"
+msgstr ""
+
+msgid "TagsPage|Existing branch name, tag, or commit SHA"
+msgstr ""
+
+msgid "TagsPage|Filter by tag name"
+msgstr ""
+
+msgid "TagsPage|New Tag"
+msgstr ""
+
+msgid "TagsPage|New tag"
+msgstr ""
+
+msgid "TagsPage|Optionally, add a message to the tag. Leaving this blank creates a %{link_start}lightweight tag.%{link_end}"
+msgstr ""
+
+msgid "TagsPage|Optionally, create a public Release of your project, based on this tag. Release notes are displayed on the %{releases_page_link_start}Releases%{link_end} page. %{docs_link_start}More information%{link_end}"
+msgstr ""
+
+msgid "TagsPage|Release notes"
+msgstr ""
+
+msgid "TagsPage|Repository has no tags yet."
+msgstr ""
+
+msgid "TagsPage|Sort by"
+msgstr ""
+
+msgid "TagsPage|Tags"
+msgstr ""
+
+msgid "TagsPage|Tags give the ability to mark specific points in history as being important"
+msgstr ""
+
+msgid "TagsPage|This tag has no release notes."
+msgstr ""
+
+msgid "TagsPage|Use git tag command to add a new one:"
+msgstr ""
+
+msgid "TagsPage|Write your release notes or drag files here…"
+msgstr ""
+
+msgid "TagsPage|protected"
+msgstr ""
+
+msgid "Target Branch"
+msgstr ""
+
+msgid "Target Path"
+msgstr ""
+
+msgid "Target branch"
+msgstr ""
+
+msgid "Target-Branch"
+msgstr ""
+
+msgid "Team"
+msgstr ""
+
+msgid "Team domain"
+msgstr ""
+
+msgid "Telephone number"
+msgstr ""
+
+msgid "Telephone number (Optional)"
+msgstr ""
+
+msgid "Template"
+msgstr ""
+
+msgid "Template to append to all Service Desk issues"
+msgstr ""
+
+msgid "Template was successfully saved."
+msgstr ""
+
+msgid "Templates"
+msgstr ""
+
+msgid "Terminal"
+msgstr ""
+
+msgid "Terminal for environment"
+msgstr ""
+
+msgid "Terminal sync service is running"
+msgstr ""
+
+msgid "Terms of Service Agreement and Privacy Policy"
+msgstr ""
+
+msgid "Terms of Service and Privacy Policy"
+msgstr ""
+
+msgid "Terraform|%{number} Terraform report failed to generate"
+msgid_plural "Terraform|%{number} Terraform reports failed to generate"
+msgstr[0] ""
+
+msgid "Terraform|%{number} Terraform report was generated in your pipelines"
+msgid_plural "Terraform|%{number} Terraform reports were generated in your pipelines"
+msgstr[0] ""
+
+msgid "Terraform|A Terraform report failed to generate."
+msgstr ""
+
+msgid "Terraform|A Terraform report was generated in your pipelines."
+msgstr ""
+
+msgid "Terraform|Generating the report caused an error."
+msgstr ""
+
+msgid "Terraform|Reported Resource Changes: %{addNum} to add, %{changeNum} to change, %{deleteNum} to delete"
+msgstr ""
+
+msgid "Terraform|The Terraform report %{name} failed to generate."
+msgstr ""
+
+msgid "Terraform|The Terraform report %{name} was generated in your pipelines."
+msgstr ""
+
+msgid "Test"
+msgstr ""
+
+msgid "Test coverage parsing"
+msgstr ""
+
+msgid "Test coverage: %d hit"
+msgid_plural "Test coverage: %d hits"
+msgstr[0] ""
+
+msgid "Test failed."
+msgstr ""
+
+msgid "Test settings and save changes"
+msgstr ""
+
+msgid "TestHooks|Ensure one of your projects has merge requests."
+msgstr ""
+
+msgid "TestHooks|Ensure the project has CI jobs."
+msgstr ""
+
+msgid "TestHooks|Ensure the project has CI pipelines."
+msgstr ""
+
+msgid "TestHooks|Ensure the project has deployments."
+msgstr ""
+
+msgid "TestHooks|Ensure the project has issues."
+msgstr ""
+
+msgid "TestHooks|Ensure the project has merge requests."
+msgstr ""
+
+msgid "TestHooks|Ensure the project has notes."
+msgstr ""
+
+msgid "TestHooks|Ensure the wiki is enabled and has pages."
+msgstr ""
+
+msgid "TestReports|%{count} errors"
+msgstr ""
+
+msgid "TestReports|%{count} failures"
+msgstr ""
+
+msgid "TestReports|%{count} jobs"
+msgstr ""
+
+msgid "TestReports|%{rate}%{sign} success rate"
+msgstr ""
+
+msgid "TestReports|Test suites"
+msgstr ""
+
+msgid "TestReports|Tests"
+msgstr ""
+
+msgid "TestReports|There are no test cases to display."
+msgstr ""
+
+msgid "TestReports|There are no test suites to show."
+msgstr ""
+
+msgid "TestReports|There are no tests to show."
+msgstr ""
+
+msgid "TestReports|There was an error fetching the test reports."
+msgstr ""
+
+msgid "Tests"
+msgstr ""
+
+msgid "Thank you for signing up for your free trial! You will get additional instructions in your inbox shortly."
+msgstr ""
+
+msgid "Thank you for your feedback!"
+msgstr ""
+
+msgid "Thank you for your report. A GitLab administrator will look into it shortly."
+msgstr ""
+
+msgid "Thanks for your purchase!"
+msgstr ""
+
+msgid "Thanks! Don't show me this again"
+msgstr ""
+
+msgid "That's it, well done!%{celebrate}"
+msgstr ""
+
+msgid "The \"%{group_path}\" group allows you to sign in with your Single Sign-On Account"
+msgstr ""
+
+msgid "The \"Require approval from CODEOWNERS\" setting was moved to %{banner_link_start}Protected Branches%{banner_link_end}"
+msgstr ""
+
+msgid "The %{link_start}true-up model%{link_end} allows having more users, and additional users will incur a retroactive charge on renewal."
+msgstr ""
+
+msgid "The %{true_up_link_start}true-up model%{link_end} has a retroactive charge for these users at the next renewal. If you want to update your license sooner to prevent this, %{support_link_start}please contact our Support team%{link_end}."
+msgstr ""
+
+msgid "The %{type} contains the following error:"
+msgid_plural "The %{type} contains the following errors:"
+msgstr[0] ""
+
+msgid "The Advanced Global Search in GitLab is a powerful search service that saves you time. Instead of creating duplicate code and wasting time, you can now search for code within other teams that can help your own project."
+msgstr ""
+
+msgid "The CSV export will be created in the background. Once finished, it will be sent to <strong>%{email}</strong> in an attachment."
+msgstr ""
+
+msgid "The Git LFS objects will <strong>not</strong> be synced."
+msgstr ""
+
+msgid "The Issue Tracker is the place to add things that need to be improved or solved in a project"
+msgstr ""
+
+msgid "The Issue Tracker is the place to add things that need to be improved or solved in a project. You can register or sign in to create issues for this project."
+msgstr ""
+
+msgid "The Prometheus server responded with \"bad request\". Please check your queries are correct and are supported in your Prometheus version. %{documentationLink}"
+msgstr ""
+
+msgid "The URL defined on the primary node that secondary nodes should use to contact it."
+msgstr ""
+
+msgid "The URL to use for connecting to Elasticsearch. Use a comma-separated list to support clustering (e.g., \"http://localhost:9200, http://localhost:9201\")."
+msgstr ""
+
+msgid "The X509 Certificate to use when mutual TLS is required to communicate with the external authorization service. If left blank, the server certificate is still validated when accessing over HTTPS."
+msgstr ""
+
+msgid "The above settings apply to all projects with the selected compliance framework(s)."
+msgstr ""
+
+msgid "The amount of seconds after which a request to get a secondary node status will time out."
+msgstr ""
+
+msgid "The application will be used where the client secret can be kept confidential. Native mobile apps and Single Page Apps are considered non-confidential."
+msgstr ""
+
+msgid "The associated issue #%{issueId} has been closed as the error is now resolved."
+msgstr ""
+
+msgid "The branch for this project has no active pipeline configuration."
+msgstr ""
+
+msgid "The branch or tag does not exist"
+msgstr ""
+
+msgid "The character highlighter helps you keep the subject line to %{titleLength} characters and wrap the body at %{bodyLength} so they are readable in git."
+msgstr ""
+
+msgid "The coding stage shows the time from the first commit to creating the merge request. The data will automatically be added here once you create your first merge request."
+msgstr ""
+
+msgid "The collection of events added to the data gathered for that stage."
+msgstr ""
+
+msgid "The commit does not exist"
+msgstr ""
+
+msgid "The connection will time out after %{timeout}. For repositories that take longer, use a clone/push combination."
+msgstr ""
+
+msgid "The content of this page is not encoded in UTF-8. Edits can only be made via the Git repository."
+msgstr ""
+
+msgid "The contents of this group, its subgroups and projects will be permanently removed after %{deletion_adjourned_period} days on %{date}. After this point, your data cannot be recovered."
+msgstr ""
+
+msgid "The current issue"
+msgstr ""
+
+msgid "The data source is connected, but there is no data to display. %{documentationLink}"
+msgstr ""
+
+msgid "The default CI configuration path for new projects."
+msgstr ""
+
+msgid "The dependency list details information about the components used within your project."
+msgstr ""
+
+msgid "The deployment of this job to %{environmentLink} did not succeed."
+msgstr ""
+
+msgid "The designs you tried uploading did not change."
+msgstr ""
+
+msgid "The directory has been successfully created."
+msgstr ""
+
+msgid "The domain you entered is misformatted."
+msgstr ""
+
+msgid "The domain you entered is not allowed."
+msgstr ""
+
+msgid "The download link will expire in 24 hours."
+msgstr ""
+
+msgid "The entered user map is not a valid JSON user map."
+msgstr ""
+
+msgid "The errors we encountered were:"
+msgstr ""
+
+msgid "The file has been successfully created."
+msgstr ""
+
+msgid "The file has been successfully deleted."
+msgstr ""
+
+msgid "The file name should have a .yml extension"
+msgstr ""
+
+msgid "The following items will NOT be exported:"
+msgstr ""
+
+msgid "The following items will be exported:"
+msgstr ""
+
+msgid "The following personal access token: %{token_names} was revoked, because a new policy to expire personal access tokens were set."
+msgid_plural "The following personal access tokens: %{token_names} were revoked, because a new policy to expire personal access tokens were set."
+msgstr[0] ""
+
+msgid "The fork relationship has been removed."
+msgstr ""
+
+msgid "The global settings require you to enable Two-Factor Authentication for your account."
+msgstr ""
+
+msgid "The group and any internal projects can be viewed by any logged in user."
+msgstr ""
+
+msgid "The group and any public projects can be viewed without any authentication."
+msgstr ""
+
+msgid "The group and its projects can only be viewed by members."
+msgstr ""
+
+msgid "The group can be fully restored"
+msgstr ""
+
+msgid "The group export can be downloaded from:"
+msgstr ""
+
+msgid "The group has already been shared with this group"
+msgstr ""
+
+msgid "The group settings for %{group_links} require you to enable Two-Factor Authentication for your account. You can %{leave_group_links}."
+msgstr ""
+
+msgid "The group will be placed in 'pending removal' state"
+msgstr ""
+
+msgid "The import will time out after %{timeout}. For repositories that take longer, use a clone/push combination."
+msgstr ""
+
+msgid "The invitation could not be accepted."
+msgstr ""
+
+msgid "The invitation could not be declined."
+msgstr ""
+
+msgid "The invitation has already been accepted."
+msgstr ""
+
+msgid "The invitation was successfully resent."
+msgstr ""
+
+msgid "The issue stage shows the time it takes from creating an issue to assigning the issue to a milestone, or add the issue to a list on your Issue Board. Begin creating issues to see data for this stage."
+msgstr ""
+
+msgid "The license key is invalid. Make sure it is exactly as you received it from GitLab Inc."
+msgstr ""
+
+msgid "The license was removed. GitLab has fallen back on the previous license."
+msgstr ""
+
+msgid "The license was removed. GitLab now no longer has a valid license."
+msgstr ""
+
+msgid "The license was successfully uploaded and is now active. You can see the details below."
+msgstr ""
+
+msgid "The license was successfully uploaded and will be active from %{starts_at}. You can see the details below."
+msgstr ""
+
+msgid "The maximum file size allowed is %{size}."
+msgstr ""
+
+msgid "The maximum file size allowed is 200KB."
+msgstr ""
+
+msgid "The merge conflicts for this merge request cannot be resolved through GitLab. Please try to resolve them locally."
+msgstr ""
+
+msgid "The merge conflicts for this merge request have already been resolved."
+msgstr ""
+
+msgid "The merge conflicts for this merge request have already been resolved. Please return to the merge request."
+msgstr ""
+
+msgid "The merge request can now be merged."
+msgstr ""
+
+msgid "The name \"%{name}\" is already taken in this directory."
+msgstr ""
+
+msgid "The number of changes to be fetched from GitLab when cloning a repository. This can speed up Pipelines execution. Keep empty or set to 0 to disable shallow clone by default and make GitLab CI fetch all branches and tags each time."
+msgstr ""
+
+msgid "The number of times an upload record could not find its file"
+msgstr ""
+
+msgid "The one place for your designs"
+msgstr ""
+
+msgid "The passphrase required to decrypt the private key. This is optional and the value is encrypted at rest."
+msgstr ""
+
+msgid "The path to the CI configuration file. Defaults to <code>.gitlab-ci.yml</code>"
+msgstr ""
+
+msgid "The phase of the development lifecycle."
+msgstr ""
+
+msgid "The pipeline has been deleted"
+msgstr ""
+
+msgid "The pipelines schedule runs pipelines in the future, repeatedly, for specific branches or tags. Those scheduled pipelines will inherit limited project access based on their associated user."
+msgstr ""
+
+msgid "The planning stage shows the time from the previous step to pushing your first commit. This time will be added automatically once you push your first commit."
+msgstr ""
+
+msgid "The private key to use when a client certificate is provided. This value is encrypted at rest."
+msgstr ""
+
+msgid "The production stage shows the total time it takes between creating an issue and deploying the code to production. The data will be automatically added once you have completed the full idea to production cycle."
+msgstr ""
+
+msgid "The project can be accessed by any logged in user."
+msgstr ""
+
+msgid "The project can be accessed by any user who is logged in."
+msgstr ""
+
+msgid "The project can be accessed by anyone, regardless of authentication."
+msgstr ""
+
+msgid "The project can be accessed without any authentication."
+msgstr ""
+
+msgid "The project is accessible only by members of the project. Access must be granted explicitly to each user."
+msgstr ""
+
+msgid "The project is still being deleted. Please try again later."
+msgstr ""
+
+msgid "The project was successfully forked."
+msgstr ""
+
+msgid "The project was successfully imported."
+msgstr ""
+
+msgid "The pseudonymizer data collection is disabled. When enabled, GitLab will run a background job that will produce pseudonymized CSVs of the GitLab database that will be uploaded to your configured object storage directory."
+msgstr ""
+
+msgid "The remote mirror took to long to complete."
+msgstr ""
+
+msgid "The remote repository is being updated..."
+msgstr ""
+
+msgid "The repository can be commited to, and issues, comments and other entities can be created."
+msgstr ""
+
+msgid "The repository for this project does not exist."
+msgstr ""
+
+msgid "The repository for this project is empty"
+msgstr ""
+
+msgid "The repository is being updated..."
+msgstr ""
+
+msgid "The repository must be accessible over <code>http://</code>, <code>https://</code> or <code>git://</code>."
+msgstr ""
+
+msgid "The repository must be accessible over <code>http://</code>, <code>https://</code>, <code>ssh://</code> or <code>git://</code>."
+msgstr ""
+
+msgid "The review stage shows the time from creating the merge request to merging it. The data will automatically be added after you merge your first merge request."
+msgstr ""
+
+msgid "The roadmap shows the progress of your epics along a timeline"
+msgstr ""
+
+msgid "The schedule time must be in the future!"
+msgstr ""
+
+msgid "The snippet can be accessed without any authentication."
+msgstr ""
+
+msgid "The snippet is visible only to me."
+msgstr ""
+
+msgid "The snippet is visible only to project members."
+msgstr ""
+
+msgid "The snippet is visible to any logged in user."
+msgstr ""
+
+msgid "The specified tab is invalid, please select another"
+msgstr ""
+
+msgid "The staging stage shows the time between merging the MR and deploying code to the production environment. The data will be automatically added once you deploy to production for the first time."
+msgstr ""
+
+msgid "The status of the table below only applies to the default branch and is based on the %{linkStart}latest pipeline%{linkEnd}. Once you've enabled a scan for the default branch, any subsequent feature branch you create will include the scan."
+msgstr ""
+
+msgid "The testing stage shows the time GitLab CI takes to run every pipeline for the related merge request. The data will automatically be added after your first pipeline finishes running."
+msgstr ""
+
+msgid "The time taken by each data entry gathered by that stage."
+msgstr ""
+
+msgid "The total stage shows the time it takes between creating an issue and deploying the code to production. The data will be automatically added once you have completed the full idea to production cycle."
+msgstr ""
+
+msgid "The update action will time out after %{number_of_minutes} minutes. For big repositories, use a clone/push combination."
+msgstr ""
+
+msgid "The uploaded file is not a valid Google Takeout archive."
+msgstr ""
+
+msgid "The usage ping is disabled, and cannot be configured through this form."
+msgstr ""
+
+msgid "The user is being deleted."
+msgstr ""
+
+msgid "The user map has been saved. Continue by selecting the projects you want to import."
+msgstr ""
+
+msgid "The user map is a JSON document mapping the Google Code users that participated on your projects to the way their email addresses and usernames will be imported into GitLab. You can change this by changing the value on the right hand side of <code>:</code>. Be sure to preserve the surrounding double quotes, other punctuation and the email address or username on the left hand side."
+msgstr ""
+
+msgid "The user map is a mapping of the FogBugz users that participated on your projects to the way their email address and usernames will be imported into GitLab. You can change this by populating the table below."
+msgstr ""
+
+msgid "The user you are trying to deactivate has been active in the past %{minimum_inactive_days} days and cannot be deactivated"
+msgstr ""
+
+msgid "The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6."
+msgstr ""
+
+msgid "The vulnerability is no longer detected. Verify the vulnerability has been fixed or removed before changing its status."
+msgstr ""
+
+msgid "The vulnerability is no longer detected. Verify the vulnerability has been remediated before changing its status."
+msgstr ""
+
+msgid "There are no %{replicableTypeName} to show"
+msgstr ""
+
+msgid "There are no GPG keys associated with this account."
+msgstr ""
+
+msgid "There are no GPG keys with access to your account."
+msgstr ""
+
+msgid "There are no SSH keys associated with this account."
+msgstr ""
+
+msgid "There are no SSH keys with access to your account."
+msgstr ""
+
+msgid "There are no archived projects yet"
+msgstr ""
+
+msgid "There are no archived requirements"
+msgstr ""
+
+msgid "There are no changes"
+msgstr ""
+
+msgid "There are no charts configured for this page"
+msgstr ""
+
+msgid "There are no closed issues"
+msgstr ""
+
+msgid "There are no closed merge requests"
+msgstr ""
+
+msgid "There are no custom project templates set up for this GitLab instance. They are enabled from GitLab's Admin Area. Contact your GitLab instance administrator to setup custom project templates."
+msgstr ""
+
+msgid "There are no issues to show"
+msgstr ""
+
+msgid "There are no issues to show."
+msgstr ""
+
+msgid "There are no labels yet"
+msgstr ""
+
+msgid "There are no matching files"
+msgstr ""
+
+msgid "There are no open issues"
+msgstr ""
+
+msgid "There are no open merge requests"
+msgstr ""
+
+msgid "There are no open requirements"
+msgstr ""
+
+msgid "There are no packages yet"
+msgstr ""
+
+msgid "There are no projects shared with this group yet"
+msgstr ""
+
+msgid "There are no variables yet."
+msgstr ""
+
+msgid "There is a limit of %{ci_project_subscriptions_limit} subscriptions from or to a project."
+msgstr ""
+
+msgid "There is already a repository with that name on disk"
+msgstr ""
+
+msgid "There is no data available. Please change your selection."
+msgstr ""
+
+msgid "There was a problem communicating with your device."
+msgstr ""
+
+msgid "There was a problem fetching project branches."
+msgstr ""
+
+msgid "There was a problem fetching project tags."
+msgstr ""
+
+msgid "There was a problem fetching project users."
+msgstr ""
+
+msgid "There was a problem fetching users."
+msgstr ""
+
+msgid "There was a problem refreshing the data, please try again"
+msgstr ""
+
+msgid "There was a problem saving your custom stage, please try again"
+msgstr ""
+
+msgid "There was a problem sending the confirmation email"
+msgstr ""
+
+msgid "There was an error %{message} todo."
+msgstr ""
+
+msgid "There was an error adding a To Do."
+msgstr ""
+
+msgid "There was an error creating the dashboard, branch name is invalid."
+msgstr ""
+
+msgid "There was an error creating the dashboard, branch named: %{branch} already exists."
+msgstr ""
+
+msgid "There was an error creating the issue"
+msgstr ""
+
+msgid "There was an error deleting the To Do."
+msgstr ""
+
+msgid "There was an error fetching configuration for charts"
+msgstr ""
+
+msgid "There was an error fetching data for the selected stage"
+msgstr ""
+
+msgid "There was an error fetching data for the tasks by type chart"
+msgstr ""
+
+msgid "There was an error fetching label data for the selected group"
+msgstr ""
+
+msgid "There was an error fetching median data for stages"
+msgstr ""
+
+msgid "There was an error fetching the %{replicableType}"
+msgstr ""
+
+msgid "There was an error fetching the Geo Settings"
+msgstr ""
+
+msgid "There was an error fetching the Node's Groups"
+msgstr ""
+
+msgid "There was an error fetching the environments information."
+msgstr ""
+
+msgid "There was an error fetching the top labels for the selected group"
+msgstr ""
+
+msgid "There was an error fetching the variables."
+msgstr ""
+
+msgid "There was an error fetching value stream analytics stages."
+msgstr ""
+
+msgid "There was an error gathering the chart data"
+msgstr ""
+
+msgid "There was an error getting the epic participants."
+msgstr ""
+
+msgid "There was an error importing the Jira project."
+msgstr ""
+
+msgid "There was an error loading users activity calendar."
+msgstr ""
+
+msgid "There was an error parsing the data for this graph."
+msgstr ""
+
+msgid "There was an error removing the e-mail."
+msgstr ""
+
+msgid "There was an error removing your custom stage, please try again"
+msgstr ""
+
+msgid "There was an error resetting group pipeline minutes."
+msgstr ""
+
+msgid "There was an error resetting user pipeline minutes."
+msgstr ""
+
+msgid "There was an error saving this Geo Node."
+msgstr ""
+
+msgid "There was an error saving your changes."
+msgstr ""
+
+msgid "There was an error saving your notification settings."
+msgstr ""
+
+msgid "There was an error subscribing to this label."
+msgstr ""
+
+msgid "There was an error syncing project %{name}"
+msgstr ""
+
+msgid "There was an error syncing the %{replicableType}"
+msgstr ""
+
+msgid "There was an error trying to validate your query"
+msgstr ""
+
+msgid "There was an error updating the dashboard, branch name is invalid."
+msgstr ""
+
+msgid "There was an error updating the dashboard, branch named: %{branch} already exists."
+msgstr ""
+
+msgid "There was an error updating the stage order. Please try reloading the page."
+msgstr ""
+
+msgid "There was an error when reseting email token."
+msgstr ""
+
+msgid "There was an error when subscribing to this label."
+msgstr ""
+
+msgid "There was an error when unsubscribing from this label."
+msgstr ""
+
+msgid "There was an error while fetching value stream analytics data."
+msgstr ""
+
+msgid "There was an error while fetching value stream analytics duration data."
+msgstr ""
+
+msgid "There was an error while fetching value stream analytics duration median data."
+msgstr ""
+
+msgid "There was an error while fetching value stream analytics recent activity data."
+msgstr ""
+
+msgid "There was an error while fetching value stream analytics time summary data."
+msgstr ""
+
+msgid "There was an error with the reCAPTCHA. Please solve the reCAPTCHA again."
+msgstr ""
+
+msgid "These existing issues have a similar title. It might be better to comment there instead of creating another similar issue."
+msgstr ""
+
+msgid "These variables are configured in the parent group settings, and will be active in the current project in addition to the project variables."
+msgstr ""
+
+msgid "They can be managed using the %{link}."
+msgstr ""
+
+msgid "Third Party Advisory Link"
+msgstr ""
+
+msgid "Third party offers"
+msgstr ""
+
+msgid "This %{issuableDisplayName} is locked. Only project members can comment."
+msgstr ""
+
+msgid "This %{issuable} is locked. Only <strong>project members</strong> can comment."
+msgstr ""
+
+msgid "This %{noteableTypeText} is %{confidentialLinkStart}confidential%{linkEnd} and %{lockedLinkStart}locked%{linkEnd}."
+msgstr ""
+
+msgid "This %{noteableTypeText} is locked."
+msgstr ""
+
+msgid "This %{viewer} could not be displayed because %{reason}. You can %{options} instead."
+msgstr ""
+
+msgid "This GitLab instance does not provide any shared Runners yet. Instance administrators can register shared Runners in the admin area."
+msgstr ""
+
+msgid "This GitLab instance is licensed at the %{insufficient_license} tier. Geo is only available for users who have at least a Premium license."
+msgstr ""
+
+msgid "This Project is currently archived and read-only. Please unarchive the project first if you want to resume Pull mirroring"
+msgstr ""
+
+msgid "This URL is already used for another link; duplicate URLs are not allowed"
+msgstr ""
+
+msgid "This action can lead to data loss. To prevent accidental actions we ask you to confirm your intention."
+msgstr ""
+
+msgid "This also resolves all related threads"
+msgstr ""
+
+msgid "This also resolves the discussion"
+msgstr ""
+
+msgid "This application was created by %{link_to_owner}."
+msgstr ""
+
+msgid "This application will be able to:"
+msgstr ""
+
+msgid "This attachment has been truncated to avoid exceeding the maximum allowed attachment size of 15MB. %{written_count} of %{issues_count} issues have been included. Consider re-exporting with a narrower selection of issues."
+msgstr ""
+
+msgid "This block is self-referential"
+msgstr ""
+
+msgid "This board's scope is reduced"
+msgstr ""
+
+msgid "This branch has changed since you started editing. Would you like to create a new branch?"
+msgstr ""
+
+msgid "This chart could not be displayed"
+msgstr ""
+
+msgid "This comment has changed since you started editing, please review the %{startTag}updated comment%{endTag} to ensure information is not lost."
+msgstr ""
+
+msgid "This commit is part of merge request %{link_to_merge_request}. Comments created here will be created in the context of that merge request."
+msgstr ""
+
+msgid "This commit was signed with a <strong>verified</strong> signature and the committer email is verified to belong to the same user."
+msgstr ""
+
+msgid "This commit was signed with a different user's verified signature."
+msgstr ""
+
+msgid "This commit was signed with a verified signature, but the committer email is <strong>not verified</strong> to belong to the same user."
+msgstr ""
+
+msgid "This commit was signed with an <strong>unverified</strong> signature."
+msgstr ""
+
+msgid "This content could not be displayed because %{reason}. You can %{options} instead."
+msgstr ""
+
+msgid "This date is after the due date, so this epic won't appear in the roadmap."
+msgstr ""
+
+msgid "This date is before the start date, so this epic won't appear in the roadmap."
+msgstr ""
+
+msgid "This device has already been registered with us."
+msgstr ""
+
+msgid "This device has not been registered with us."
+msgstr ""
+
+msgid "This diff is collapsed."
+msgstr ""
+
+msgid "This diff was suppressed by a .gitattributes entry."
+msgstr ""
+
+msgid "This directory"
+msgstr ""
+
+msgid "This domain is not verified. You will need to verify ownership before access is enabled."
+msgstr ""
+
+msgid "This endpoint has been requested too many times. Try again later."
+msgstr ""
+
+msgid "This environment has no deployments yet."
+msgstr ""
+
+msgid "This environment is being deployed"
+msgstr ""
+
+msgid "This environment is being re-deployed"
+msgstr ""
+
+msgid "This epic already has the maximum number of child epics."
+msgstr ""
+
+msgid "This epic and its child elements will only be visible to team members with at minimum Reporter access."
+msgstr ""
+
+msgid "This epic does not exist or you don't have sufficient permission."
+msgstr ""
+
+msgid "This feature is currently in beta. We invite you to %{linkStart}give feedback%{linkEnd}."
+msgstr ""
+
+msgid "This feature requires local storage to be enabled"
+msgstr ""
+
+msgid "This field is required."
+msgstr ""
+
+msgid "This group"
+msgstr ""
+
+msgid "This group cannot be invited to a project inside a group with enforced SSO"
+msgstr ""
+
+msgid "This group does not provide any group Runners yet."
+msgstr ""
+
+msgid "This group has been scheduled for permanent removal on %{date}"
+msgstr ""
+
+msgid "This group, including all subgroups, projects and git repositories, will only be reachable from the specified IP address range. Multiple addresses are supported with comma delimiters.<br>Example: <code>192.168.0.0/24,192.168.1.0/24</code>. %{read_more_link}."
+msgstr ""
+
+msgid "This group, its subgroups and projects has been scheduled for removal on %{date}."
+msgstr ""
+
+msgid "This group, its subgroups and projects will be removed on %{date} since its parent group '%{parent_group_name}'' has been scheduled for removal."
+msgstr ""
+
+msgid "This is a \"Ghost User\", created to hold all issues authored by users that have since been deleted. This user cannot be removed."
+msgstr ""
+
+msgid "This is a Work in Progress"
+msgstr ""
+
+msgid "This is a confidential %{noteableTypeText}."
+msgstr ""
+
+msgid "This is a delayed job to run in %{remainingTime}"
+msgstr ""
+
+msgid "This is a list of devices that have logged into your account. Revoke any sessions that you do not recognize."
+msgstr ""
+
+msgid "This is a security log of important events involving your account."
+msgstr ""
+
+msgid "This is the author's first Merge Request to this project."
+msgstr ""
+
+msgid "This is the highest peak of users on your installation since the license started."
+msgstr ""
+
+msgid "This is the maximum number of users that have existed at the same time since the license started. This is the minimum number of seats you will need to buy when you renew your license."
+msgstr ""
+
+msgid "This is the number of currently active users on your installation, and this is the minimum number you need to purchase when you renew your license."
+msgstr ""
+
+msgid "This is your current session"
+msgstr ""
+
+msgid "This issue is confidential"
+msgstr ""
+
+msgid "This issue is currently blocked by the following issues: %{issues}."
+msgstr ""
+
+msgid "This issue is in a child epic of the filtered epic"
+msgstr ""
+
+msgid "This job depends on other jobs with expired/erased artifacts: %{invalid_dependencies}"
+msgstr ""
+
+msgid "This job depends on upstream jobs that need to succeed in order for this job to be triggered"
+msgstr ""
+
+msgid "This job does not have a trace."
+msgstr ""
+
+msgid "This job has been canceled"
+msgstr ""
+
+msgid "This job has been skipped"
+msgstr ""
+
+msgid "This job has not been triggered yet"
+msgstr ""
+
+msgid "This job has not started yet"
+msgstr ""
+
+msgid "This job is an out-of-date deployment to %{environmentLink} using cluster %{clusterNameOrLink} and namespace %{kubernetesNamespace}."
+msgstr ""
+
+msgid "This job is an out-of-date deployment to %{environmentLink} using cluster %{clusterNameOrLink} and namespace %{kubernetesNamespace}. View the %{deploymentLink}."
+msgstr ""
+
+msgid "This job is an out-of-date deployment to %{environmentLink} using cluster %{clusterNameOrLink}."
+msgstr ""
+
+msgid "This job is an out-of-date deployment to %{environmentLink} using cluster %{clusterNameOrLink}. View the %{deploymentLink}."
+msgstr ""
+
+msgid "This job is an out-of-date deployment to %{environmentLink}."
+msgstr ""
+
+msgid "This job is an out-of-date deployment to %{environmentLink}. View the %{deploymentLink}."
+msgstr ""
+
+msgid "This job is archived. Only the complete pipeline can be retried."
+msgstr ""
+
+msgid "This job is creating a deployment to %{environmentLink} using cluster %{clusterNameOrLink} and namespace %{kubernetesNamespace}."
+msgstr ""
+
+msgid "This job is creating a deployment to %{environmentLink} using cluster %{clusterNameOrLink} and namespace %{kubernetesNamespace}. This will overwrite the %{deploymentLink}."
+msgstr ""
+
+msgid "This job is creating a deployment to %{environmentLink} using cluster %{clusterNameOrLink}."
+msgstr ""
+
+msgid "This job is creating a deployment to %{environmentLink} using cluster %{clusterNameOrLink}. This will overwrite the %{deploymentLink}."
+msgstr ""
+
+msgid "This job is creating a deployment to %{environmentLink}."
+msgstr ""
+
+msgid "This job is creating a deployment to %{environmentLink}. This will overwrite the %{deploymentLink}."
+msgstr ""
+
+msgid "This job is deployed to %{environmentLink} using cluster %{clusterNameOrLink} and namespace %{kubernetesNamespace}."
+msgstr ""
+
+msgid "This job is deployed to %{environmentLink} using cluster %{clusterNameOrLink}."
+msgstr ""
+
+msgid "This job is deployed to %{environmentLink}."
+msgstr ""
+
+msgid "This job is in pending state and is waiting to be picked by a runner"
+msgstr ""
+
+msgid "This job is performing tasks that must complete before it can start"
+msgstr ""
+
+msgid "This job is preparing to start"
+msgstr ""
+
+msgid "This job is stuck because you don't have any active runners online or available with any of these tags assigned to them:"
+msgstr ""
+
+msgid "This job is stuck because you don't have any active runners that can run this job."
+msgstr ""
+
+msgid "This job is waiting for resource: "
+msgstr ""
+
+msgid "This job requires a manual action"
+msgstr ""
+
+msgid "This job requires manual intervention to start. Before starting this job, you can add variables below for last-minute configuration changes."
+msgstr ""
+
+msgid "This job will automatically run after its timer finishes. Often they are used for incremental roll-out deploys to production environments. When unscheduled it converts into a manual action."
+msgstr ""
+
+msgid "This license has already expired."
+msgstr ""
+
+msgid "This link points to external content"
+msgstr ""
+
+msgid "This may expose confidential information as the selected fork is in another namespace that can have other members."
+msgstr ""
+
+msgid "This means you can not push code until you create an empty repository or import existing one."
+msgstr ""
+
+msgid "This merge request does not have accessibility reports"
+msgstr ""
+
+msgid "This merge request is locked."
+msgstr ""
+
+msgid "This namespace has already been taken! Please choose another one."
+msgstr ""
+
+msgid "This only applies to repository indexing operations."
+msgstr ""
+
+msgid "This option is only available on GitLab.com"
+msgstr ""
+
+msgid "This page is unavailable because you are not allowed to read information across multiple projects."
+msgstr ""
+
+msgid "This page will be removed in a future release."
+msgstr ""
+
+msgid "This pipeline does not use the %{codeStart}needs%{codeEnd} keyword and can't be represented as a directed acyclic graph."
+msgstr ""
+
+msgid "This pipeline makes use of a predefined CI/CD configuration enabled by %{strongStart}Auto DevOps.%{strongEnd}"
+msgstr ""
+
+msgid "This pipeline makes use of a predefined CI/CD configuration enabled by <b>Auto DevOps.</b>"
+msgstr ""
+
+msgid "This pipeline triggered a child pipeline"
+msgstr ""
+
+msgid "This pipeline was triggered by a parent pipeline"
+msgstr ""
+
+msgid "This project"
+msgstr ""
+
+msgid "This project does not belong to a group and can therefore not make use of group Runners."
+msgstr ""
+
+msgid "This project does not have %{service_desk_link_start}Service Desk%{service_desk_link_end} enabled, so the user who created the issue will no longer receive email notifications about new activity."
+msgstr ""
+
+msgid "This project does not have a wiki homepage yet"
+msgstr ""
+
+msgid "This project does not have billing enabled. To create a cluster, <a href=%{linkToBilling} target=\"_blank\" rel=\"noopener noreferrer\">enable billing <i class=\"fa fa-external-link\" aria-hidden=\"true\"></i></a> and try again."
+msgstr ""
+
+msgid "This project has no active access tokens."
+msgstr ""
+
+msgid "This project is archived and cannot be commented on."
+msgstr ""
+
+msgid "This project path either does not exist or you do not have access."
+msgstr ""
+
+msgid "This project will be removed on %{date}"
+msgstr ""
+
+msgid "This project will be removed on %{date} since its parent group '%{parent_group_name}' has been scheduled for removal."
+msgstr ""
+
+msgid "This project will live in your group <strong>%{namespace}</strong>. A project is where you house your files (repository), plan your work (issues), publish your documentation (wiki), and so much more."
+msgstr ""
+
+msgid "This repository"
+msgstr ""
+
+msgid "This repository has never been checked."
+msgstr ""
+
+msgid "This repository is currently empty. A new Auto DevOps pipeline will be created after a new file has been pushed to a branch."
+msgstr ""
+
+msgid "This repository was last checked %{last_check_timestamp}. The check %{strong_start}failed.%{strong_end} See the 'repocheck.log' file for error messages."
+msgstr ""
+
+msgid "This repository was last checked %{last_check_timestamp}. The check passed."
+msgstr ""
+
+msgid "This runner will only run on pipelines triggered on protected branches"
+msgstr ""
+
+msgid "This setting can be overridden in each project."
+msgstr ""
+
+msgid "This setting will update the hostname that is used to generate private commit emails. %{learn_more}"
+msgstr ""
+
+msgid "This subscription is for"
+msgstr ""
+
+msgid "This timeout will take precedence when lower than project-defined timeout and accepts a human readable time input language like \"1 hour\". Values without specification represent seconds."
+msgstr ""
+
+msgid "This user cannot be unlocked manually from GitLab"
+msgstr ""
+
+msgid "This user has no active %{type}."
+msgstr ""
+
+msgid "This user has no identities"
+msgstr ""
+
+msgid "This user will be the author of all events in the activity feed that are the result of an update, like new branches being created or new commits being pushed to existing branches."
+msgstr ""
+
+msgid "This variable can not be masked."
+msgstr ""
+
+msgid "This variable does not match the expected pattern."
+msgstr ""
+
+msgid "This will help us personalize your onboarding experience."
+msgstr ""
+
+msgid "This will redirect you to an external sign in page."
+msgstr ""
+
+msgid "This will remove the fork relationship between this project and %{fork_source}."
+msgstr ""
+
+msgid "This will remove the fork relationship between this project and other projects in the fork network."
+msgstr ""
+
+msgid "Those emails automatically become issues (with the comments becoming the email conversation) listed here."
+msgstr ""
+
+msgid "Thread to reply to cannot be found"
+msgstr ""
+
+msgid "Threat Monitoring"
+msgstr ""
+
+msgid "ThreatMonitoring|Anomalous Requests"
+msgstr ""
+
+msgid "ThreatMonitoring|Application firewall not detected"
+msgstr ""
+
+msgid "ThreatMonitoring|Container Network Policies are not installed or have been disabled. To view this data, ensure your Network Policies are installed and enabled for your cluster."
+msgstr ""
+
+msgid "ThreatMonitoring|Container Network Policy"
+msgstr ""
+
+msgid "ThreatMonitoring|Container NetworkPolicies not detected"
+msgstr ""
+
+msgid "ThreatMonitoring|Dropped Packets"
+msgstr ""
+
+msgid "ThreatMonitoring|Environment"
+msgstr ""
+
+msgid "ThreatMonitoring|No environments detected"
+msgstr ""
+
+msgid "ThreatMonitoring|Operations Per Second"
+msgstr ""
+
+msgid "ThreatMonitoring|Overview"
+msgstr ""
+
+msgid "ThreatMonitoring|Packet Activity"
+msgstr ""
+
+msgid "ThreatMonitoring|Policies"
+msgstr ""
+
+msgid "ThreatMonitoring|Requests"
+msgstr ""
+
+msgid "ThreatMonitoring|Show last"
+msgstr ""
+
+msgid "ThreatMonitoring|Something went wrong, unable to fetch environments"
+msgstr ""
+
+msgid "ThreatMonitoring|Something went wrong, unable to fetch statistics"
+msgstr ""
+
+msgid "ThreatMonitoring|The firewall is not installed or has been disabled. To view this data, ensure the web application firewall is installed and enabled for your cluster."
+msgstr ""
+
+msgid "ThreatMonitoring|The graph below is an overview of traffic coming to your application as tracked by the Web Application Firewall (WAF). View the docs for instructions on how to access the WAF logs to see what type of malicious traffic is trying to access your app. The docs link is also accessible by clicking the \"?\" icon next to the title below."
+msgstr ""
+
+msgid "ThreatMonitoring|Threat Monitoring"
+msgstr ""
+
+msgid "ThreatMonitoring|Threat Monitoring help page link"
+msgstr ""
+
+msgid "ThreatMonitoring|Time"
+msgstr ""
+
+msgid "ThreatMonitoring|To view this data, ensure you have configured an environment for this project and that at least one threat monitoring feature is enabled."
+msgstr ""
+
+msgid "ThreatMonitoring|Total Packets"
+msgstr ""
+
+msgid "ThreatMonitoring|Total Requests"
+msgstr ""
+
+msgid "ThreatMonitoring|View documentation"
+msgstr ""
+
+msgid "ThreatMonitoring|Web Application Firewall"
+msgstr ""
+
+msgid "ThreatMonitoring|While it's rare to have no traffic coming to your application, it can happen. In any event, we ask that you double check your settings to make sure you've set up the WAF correctly."
+msgstr ""
+
+msgid "Thursday"
+msgstr ""
+
+msgid "Time"
+msgstr ""
+
+msgid "Time based: Yes"
+msgstr ""
+
+msgid "Time before an issue gets scheduled"
+msgstr ""
+
+msgid "Time before an issue starts implementation"
+msgstr ""
+
+msgid "Time before enforced"
+msgstr ""
+
+msgid "Time between merge request creation and merge/close"
+msgstr ""
+
+msgid "Time estimate"
+msgstr ""
+
+msgid "Time from first comment to last commit"
+msgstr ""
+
+msgid "Time from first commit until first comment"
+msgstr ""
+
+msgid "Time from last commit to merge"
+msgstr ""
+
+msgid "Time in seconds"
+msgstr ""
+
+msgid "Time in seconds GitLab will wait for a response from the external service. When the service does not respond in time, access will be denied."
+msgstr ""
+
+msgid "Time of import: %{importTime}"
+msgstr ""
+
+msgid "Time remaining"
+msgstr ""
+
+msgid "Time spent"
+msgstr ""
+
+msgid "Time to merge"
+msgstr ""
+
+msgid "Time to subtract exceeds the total time spent"
+msgstr ""
+
+msgid "Time tracking"
+msgstr ""
+
+msgid "Time until first merge request"
+msgstr ""
+
+msgid "TimeTrackingEstimated|Est"
+msgstr ""
+
+msgid "TimeTracking|%{startTag}Spent: %{endTag}%{timeSpentHumanReadable}"
+msgstr ""
+
+msgid "TimeTracking|Estimated:"
+msgstr ""
+
+msgid "TimeTracking|Over by %{timeRemainingHumanReadable}"
+msgstr ""
+
+msgid "TimeTracking|Spent"
+msgstr ""
+
+msgid "TimeTracking|Time remaining: %{timeRemainingHumanReadable}"
+msgstr ""
+
+msgid "Timeago|%s days ago"
+msgstr ""
+
+msgid "Timeago|%s days remaining"
+msgstr ""
+
+msgid "Timeago|%s hours ago"
+msgstr ""
+
+msgid "Timeago|%s hours remaining"
+msgstr ""
+
+msgid "Timeago|%s minutes ago"
+msgstr ""
+
+msgid "Timeago|%s minutes remaining"
+msgstr ""
+
+msgid "Timeago|%s months ago"
+msgstr ""
+
+msgid "Timeago|%s months remaining"
+msgstr ""
+
+msgid "Timeago|%s seconds remaining"
+msgstr ""
+
+msgid "Timeago|%s weeks ago"
+msgstr ""
+
+msgid "Timeago|%s weeks remaining"
+msgstr ""
+
+msgid "Timeago|%s years ago"
+msgstr ""
+
+msgid "Timeago|%s years remaining"
+msgstr ""
+
+msgid "Timeago|1 day ago"
+msgstr ""
+
+msgid "Timeago|1 day remaining"
+msgstr ""
+
+msgid "Timeago|1 hour ago"
+msgstr ""
+
+msgid "Timeago|1 hour remaining"
+msgstr ""
+
+msgid "Timeago|1 minute ago"
+msgstr ""
+
+msgid "Timeago|1 minute remaining"
+msgstr ""
+
+msgid "Timeago|1 month ago"
+msgstr ""
+
+msgid "Timeago|1 month remaining"
+msgstr ""
+
+msgid "Timeago|1 week ago"
+msgstr ""
+
+msgid "Timeago|1 week remaining"
+msgstr ""
+
+msgid "Timeago|1 year ago"
+msgstr ""
+
+msgid "Timeago|1 year remaining"
+msgstr ""
+
+msgid "Timeago|Past due"
+msgstr ""
+
+msgid "Timeago|in %s days"
+msgstr ""
+
+msgid "Timeago|in %s hours"
+msgstr ""
+
+msgid "Timeago|in %s minutes"
+msgstr ""
+
+msgid "Timeago|in %s months"
+msgstr ""
+
+msgid "Timeago|in %s seconds"
+msgstr ""
+
+msgid "Timeago|in %s weeks"
+msgstr ""
+
+msgid "Timeago|in %s years"
+msgstr ""
+
+msgid "Timeago|in 1 day"
+msgstr ""
+
+msgid "Timeago|in 1 hour"
+msgstr ""
+
+msgid "Timeago|in 1 minute"
+msgstr ""
+
+msgid "Timeago|in 1 month"
+msgstr ""
+
+msgid "Timeago|in 1 week"
+msgstr ""
+
+msgid "Timeago|in 1 year"
+msgstr ""
+
+msgid "Timeago|just now"
+msgstr ""
+
+msgid "Timeago|right now"
+msgstr ""
+
+msgid "Timeout"
+msgstr ""
+
+msgid "Timeout connecting to the Google API. Please try again."
+msgstr ""
+
+msgid "Time|hr"
+msgid_plural "Time|hrs"
+msgstr[0] ""
+
+msgid "Time|min"
+msgid_plural "Time|mins"
+msgstr[0] ""
+
+msgid "Time|s"
+msgstr ""
+
+msgid "Tip:"
+msgstr ""
+
+msgid "Title"
+msgstr ""
+
+msgid "Title:"
+msgstr ""
+
+msgid "Titles and Descriptions"
+msgstr ""
+
+msgid "To"
+msgstr ""
+
+msgid "To %{link_to_help} of your domain, add the above key to a TXT record within to your DNS configuration."
+msgstr ""
+
+msgid "To Do"
+msgstr ""
+
+msgid "To GitLab"
+msgstr ""
+
+msgid "To access this domain create a new DNS record"
+msgstr ""
+
+msgid "To add an SSH key you need to %{generate_link_start}generate one%{link_end} or use an %{existing_link_start}existing key%{link_end}."
+msgstr ""
+
+msgid "To add the entry manually, provide the following details to the application on your phone."
+msgstr ""
+
+msgid "To connect GitHub repositories, you can use a %{personal_access_token_link}. When you create your Personal Access Token, you will need to select the <code>repo</code> scope, so we can display a list of your public and private repositories which are available to connect."
+msgstr ""
+
+msgid "To connect GitHub repositories, you first need to authorize GitLab to access the list of your GitHub repositories."
+msgstr ""
+
+msgid "To connect GitHub repositories, you first need to authorize GitLab to access the list of your GitHub repositories:"
+msgstr ""
+
+msgid "To connect an SVN repository, check out %{svn_link}."
+msgstr ""
+
+msgid "To define internal users, first enable new users set to external"
+msgstr ""
+
+msgid "To enable it and see User Cohorts, visit %{application_settings_link_start}application settings%{application_settings_link_end}."
+msgstr ""
+
+msgid "To further protect your account, consider configuring a %{mfa_link_start}two-factor authentication%{mfa_link_end} method."
+msgstr ""
+
+msgid "To further protect your account, consider configuring a two-factor authentication method: %{mfa_link}."
+msgstr ""
+
+msgid "To get started you enter your FogBugz URL and login information below. In the next steps, you'll be able to map users and select the projects you want to import."
+msgstr ""
+
+msgid "To get started, link this page to your Jaeger server, or find out how to %{link_start_tag}install Jaeger%{link_end_tag}"
+msgstr ""
+
+msgid "To get started, please enter your Gitea Host URL and a %{link_to_personal_token}."
+msgstr ""
+
+msgid "To help improve GitLab and its user experience, GitLab will periodically collect usage information."
+msgstr ""
+
+msgid "To help improve GitLab, we would like to periodically collect usage information. This can be changed at any time in %{settings_link_start}Settings%{link_end}. %{info_link_start}More Information%{link_end}"
+msgstr ""
+
+msgid "To import an SVN repository, check out %{svn_link}."
+msgstr ""
+
+msgid "To keep this project going, create a new issue"
+msgstr ""
+
+msgid "To keep this project going, create a new merge request"
+msgstr ""
+
+msgid "To link Sentry to GitLab, enter your Sentry URL and Auth Token."
+msgstr ""
+
+msgid "To move or copy an entire GitLab project from another GitLab installation to this one, navigate to the original project's settings page, generate an export file, and upload it here."
+msgstr ""
+
+msgid "To only use CI/CD features for an external repository, choose <strong>CI/CD for external repo</strong>."
+msgstr ""
+
+msgid "To open Jaeger and easily view tracing from GitLab, link the %{link} page to your server"
+msgstr ""
+
+msgid "To preserve performance only <strong>%{display_size} of %{real_size}</strong> files are displayed."
+msgstr ""
+
+msgid "To protect this issue's confidentiality, %{forkLink} and set the fork's visibility to private."
+msgstr ""
+
+msgid "To protect this issue's confidentiality, a private fork of this project was selected."
+msgstr ""
+
+msgid "To receive alerts from manually configured Prometheus services, add the following URL and Authorization key to your Prometheus webhook config file. Learn more about %{linkStart}configuring Prometheus%{linkEnd} to send alerts to GitLab."
+msgstr ""
+
+msgid "To see all the user's personal access tokens you must impersonate them first."
+msgstr ""
+
+msgid "To see this project's operational details, %{linkStart}upgrade its group plan to Silver%{linkEnd}. You can also remove the project from the dashboard."
+msgstr ""
+
+msgid "To see this project's operational details, contact an owner of group %{groupName} to upgrade the plan. You can also remove the project from the dashboard."
+msgstr ""
+
+msgid "To set up SAML authentication for your group through an identity provider like Azure, Okta, Onelogin, Ping Identity, or your custom SAML 2.0 provider:"
+msgstr ""
+
+msgid "To set up this service:"
+msgstr ""
+
+msgid "To simplify the billing process, GitLab will collect user counts in order to prorate charges for user growth throughout the year using a quarterly reconciliation process."
+msgstr ""
+
+msgid "To specify the notification level per project of a group you belong to, you need to visit project page and change notification level there."
+msgstr ""
+
+msgid "To start serving your jobs you can add Runners to your group"
+msgstr ""
+
+msgid "To start serving your jobs you can either add specific Runners to your project or use shared Runners"
+msgstr ""
+
+msgid "To this GitLab instance"
+msgstr ""
+
+msgid "To view the roadmap, add a start or due date to one of your epics in this group or its subgroups. In the months view, only epics in the past month, current month, and next 5 months are shown."
+msgstr ""
+
+msgid "To widen your search, change or remove filters above"
+msgstr ""
+
+msgid "To widen your search, change or remove filters."
+msgstr ""
+
+msgid "To-Do List"
+msgstr ""
+
+msgid "To-do item successfully marked as done."
+msgstr ""
+
+msgid "Today"
+msgstr ""
+
+msgid "Toggle Markdown preview"
+msgstr ""
+
+msgid "Toggle Sidebar"
+msgstr ""
+
+msgid "Toggle all threads"
+msgstr ""
+
+msgid "Toggle backtrace"
+msgstr ""
+
+msgid "Toggle collapse"
+msgstr ""
+
+msgid "Toggle comments for this file"
+msgstr ""
+
+msgid "Toggle commit description"
+msgstr ""
+
+msgid "Toggle commit list"
+msgstr ""
+
+msgid "Toggle dropdown"
+msgstr ""
+
+msgid "Toggle emoji award"
+msgstr ""
+
+msgid "Toggle navigation"
+msgstr ""
+
+msgid "Toggle project"
+msgstr ""
+
+msgid "Toggle sidebar"
+msgstr ""
+
+msgid "Toggle the Performance Bar"
+msgstr ""
+
+msgid "Toggle this dialog"
+msgstr ""
+
+msgid "Toggle thread"
+msgstr ""
+
+msgid "ToggleButton|Toggle Status: OFF"
+msgstr ""
+
+msgid "ToggleButton|Toggle Status: ON"
+msgstr ""
+
+msgid "Toggled :%{name}: emoji award."
+msgstr ""
+
+msgid "Toggles :%{name}: emoji award."
+msgstr ""
+
+msgid "Tomorrow"
+msgstr ""
+
+msgid "Too many changes to show."
+msgstr ""
+
+msgid "Too many namespaces enabled. You will need to manage them via the console or the API."
+msgstr ""
+
+msgid "Too many projects enabled. You will need to manage them via the console or the API."
+msgstr ""
+
+msgid "Topics (optional)"
+msgstr ""
+
+msgid "Total"
+msgstr ""
+
+msgid "Total Contributions"
+msgstr ""
+
+msgid "Total artifacts size: %{total_size}"
+msgstr ""
+
+msgid "Total cores (CPUs)"
+msgstr ""
+
+msgid "Total issues"
+msgstr ""
+
+msgid "Total memory (GB)"
+msgstr ""
+
+msgid "Total test time for all commits/merges"
+msgstr ""
+
+msgid "Total weight"
+msgstr ""
+
+msgid "Total: %{total}"
+msgstr ""
+
+msgid "Trace"
+msgstr ""
+
+msgid "Tracing"
+msgstr ""
+
+msgid "Track groups of issues that share a theme, across projects and milestones"
+msgstr ""
+
+msgid "Track time with quick actions"
+msgstr ""
+
+msgid "Track your GitLab projects with GitLab for Slack."
+msgstr ""
+
+msgid "Track your project with Audit Events."
+msgstr ""
+
+msgid "Transfer ownership"
+msgstr ""
+
+msgid "Transfer project"
+msgstr ""
+
+msgid "TransferGroup|Cannot transfer group to one of its subgroup."
+msgstr ""
+
+msgid "TransferGroup|Cannot update the path because there are projects under this group that contain Docker images in their Container Registry. Please remove the images from your projects first and try again."
+msgstr ""
+
+msgid "TransferGroup|Database is not supported."
+msgstr ""
+
+msgid "TransferGroup|Group contains projects with NPM packages."
+msgstr ""
+
+msgid "TransferGroup|Group is already a root group."
+msgstr ""
+
+msgid "TransferGroup|Group is already associated to the parent group."
+msgstr ""
+
+msgid "TransferGroup|The parent group already has a subgroup with the same path."
+msgstr ""
+
+msgid "TransferGroup|Transfer failed: %{error_message}"
+msgstr ""
+
+msgid "TransferGroup|You don't have enough permissions."
+msgstr ""
+
+msgid "TransferProject|Cannot move project"
+msgstr ""
+
+msgid "TransferProject|Please select a new namespace for your project."
+msgstr ""
+
+msgid "TransferProject|Project cannot be transferred, because tags are present in its container registry"
+msgstr ""
+
+msgid "TransferProject|Project with same name or path in target namespace already exists"
+msgstr ""
+
+msgid "TransferProject|Root namespace can't be updated if project has NPM packages"
+msgstr ""
+
+msgid "TransferProject|Transfer failed, please contact an admin."
+msgstr ""
+
+msgid "Tree view"
+msgstr ""
+
+msgid "Trending"
+msgstr ""
+
+msgid "Trials|Go back to GitLab"
+msgstr ""
+
+msgid "Trials|Skip Trial (Continue with Free Account)"
+msgstr ""
+
+msgid "Trials|You can always resume this process by selecting your avatar and choosing 'Start a Gold trial'"
+msgstr ""
+
+msgid "Trials|You won't get a free trial right now but you can always resume this process by clicking on your avatar and choosing 'Start a free trial'"
+msgstr ""
+
+msgid "Trigger"
+msgstr ""
+
+msgid "Trigger pipelines for mirror updates"
+msgstr ""
+
+msgid "Trigger pipelines when branches or tags are updated from the upstream repository. Depending on the activity of the upstream repository, this may greatly increase the load on your CI runners. Only enable this if you know they can handle the load."
+msgstr ""
+
+msgid "Trigger removed."
+msgstr ""
+
+msgid "Trigger this manual action"
+msgstr ""
+
+msgid "Trigger token:"
+msgstr ""
+
+msgid "Trigger variables:"
+msgstr ""
+
+msgid "Trigger was created successfully."
+msgstr ""
+
+msgid "Trigger was successfully updated."
+msgstr ""
+
+msgid "Triggerer"
+msgstr ""
+
+msgid "Triggers can force a specific branch or tag to get rebuilt with an API call. These tokens will impersonate their associated user including their access to projects and their project permissions."
+msgstr ""
+
+msgid "Troubleshoot and monitor your application with tracing"
+msgstr ""
+
+msgid "Try again"
+msgstr ""
+
+msgid "Try again?"
+msgstr ""
+
+msgid "Try all GitLab has to offer for 30 days."
+msgstr ""
+
+msgid "Try changing or removing filters."
+msgstr ""
+
+msgid "Try to fork again"
+msgstr ""
+
+msgid "Try to keep the first line under 52 characters and the others under 72."
+msgstr ""
+
+msgid "Try using a different search term to find the file you are looking for."
+msgstr ""
+
+msgid "Trying to communicate with your device. Plug it in (if you haven't already) and press the button on the device now."
+msgstr ""
+
+msgid "Tuesday"
+msgstr ""
+
+msgid "Turn Off"
+msgstr ""
+
+msgid "Turn On"
+msgstr ""
+
+msgid "Turn on Service Desk"
+msgstr ""
+
+msgid "Twitter"
+msgstr ""
+
+msgid "Two-Factor Authentication"
+msgstr ""
+
+msgid "Two-Factor Authentication code"
+msgstr ""
+
+msgid "Two-factor Authentication"
+msgstr ""
+
+msgid "Two-factor Authentication Recovery codes"
+msgstr ""
+
+msgid "Two-factor Authentication has been disabled for this user"
+msgstr ""
+
+msgid "Two-factor authentication"
+msgstr ""
+
+msgid "Type"
+msgstr ""
+
+msgid "Type/State"
+msgstr ""
+
+msgid "U2F Devices (%{length})"
+msgstr ""
+
+msgid "U2F only works with HTTPS-enabled websites. Contact your administrator for more details."
+msgstr ""
+
+msgid "UI Development Kit"
+msgstr ""
+
+msgid "URL"
+msgstr ""
+
+msgid "URL is required"
+msgstr ""
+
+msgid "URL must start with %{codeStart}http://%{codeEnd}, %{codeStart}https://%{codeEnd}, or %{codeStart}ftp://%{codeEnd}"
+msgstr ""
+
+msgid "URL of the external Spam Check endpoint"
+msgstr ""
+
+msgid "URL of the external storage that will serve the repository static objects (e.g. archives, blobs, ...)."
+msgstr ""
+
+msgid "URL or request ID"
+msgstr ""
+
+msgid "UTC"
+msgstr ""
+
+msgid "Unable to apply suggestions to a deleted line."
+msgstr ""
+
+msgid "Unable to build Slack link."
+msgstr ""
+
+msgid "Unable to collect CPU info"
+msgstr ""
+
+msgid "Unable to collect memory info"
+msgstr ""
+
+msgid "Unable to connect to Elasticsearch"
+msgstr ""
+
+msgid "Unable to connect to Prometheus server"
+msgstr ""
+
+msgid "Unable to connect to server: %{error}"
+msgstr ""
+
+msgid "Unable to connect to the Jira instance. Please check your Jira integration configuration."
+msgstr ""
+
+msgid "Unable to convert Kubernetes logs encoding to UTF-8"
+msgstr ""
+
+msgid "Unable to fetch unscanned projects"
+msgstr ""
+
+msgid "Unable to fetch vulnerable projects"
+msgstr ""
+
+msgid "Unable to find Jira project to import data from."
+msgstr ""
+
+msgid "Unable to generate new instance ID"
+msgstr ""
+
+msgid "Unable to load file contents. Try again later."
+msgstr ""
+
+msgid "Unable to load the diff"
+msgstr ""
+
+msgid "Unable to load the diff. %{button_try_again}"
+msgstr ""
+
+msgid "Unable to load the merge request widget. Try reloading the page."
+msgstr ""
+
+msgid "Unable to resolve"
+msgstr ""
+
+msgid "Unable to save iteration. Please try again"
+msgstr ""
+
+msgid "Unable to save your changes. Please try again."
+msgstr ""
+
+msgid "Unable to schedule a pipeline to run immediately"
+msgstr ""
+
+msgid "Unable to sign you in to the group with SAML due to \"%{reason}\""
+msgstr ""
+
+msgid "Unable to suggest a path. Please refresh and try again."
+msgstr ""
+
+msgid "Unable to update label prioritization at this time"
+msgstr ""
+
+msgid "Unable to update this epic at this time."
+msgstr ""
+
+msgid "Unable to update this issue at this time."
+msgstr ""
+
+msgid "Unarchive project"
+msgstr ""
+
+msgid "Unarchiving the project will restore people's ability to make changes to it. The repository can be committed to, and issues, comments, and other entities can be created. %{strong_start}Once active, this project shows up in the search and on the dashboard.%{strong_end}"
+msgstr ""
+
+msgid "Unassign from commenting user"
+msgstr ""
+
+msgid "Unblock"
+msgstr ""
+
+msgid "Undo"
+msgstr ""
+
+msgid "Undo Ignore"
+msgstr ""
+
+msgid "Undo ignore"
+msgstr ""
+
+msgid "Unfortunately, your email message to GitLab could not be processed."
+msgstr ""
+
+msgid "Uninstall"
+msgstr ""
+
+msgid "Uninstalling"
+msgstr ""
+
+msgid "Units|ms"
+msgstr ""
+
+msgid "Units|s"
+msgstr ""
+
+msgid "Unknown"
+msgstr ""
+
+msgid "Unknown Error"
+msgstr ""
+
+msgid "Unknown cache key"
+msgstr ""
+
+msgid "Unknown encryption strategy: %{encrypted_strategy}!"
+msgstr ""
+
+msgid "Unknown format"
+msgstr ""
+
+msgid "Unknown response text"
+msgstr ""
+
+msgid "Unlimited"
+msgstr ""
+
+msgid "Unlock"
+msgstr ""
+
+msgid "Unlock the discussion"
+msgstr ""
+
+msgid "Unlock this %{issuableDisplayName}? <strong>Everyone</strong> will be able to comment."
+msgstr ""
+
+msgid "Unlocked"
+msgstr ""
+
+msgid "Unlocked the discussion."
+msgstr ""
+
+msgid "Unlocks the discussion."
+msgstr ""
+
+msgid "Unmarked this %{noun} as Work In Progress."
+msgstr ""
+
+msgid "Unmarks this %{noun} as Work In Progress."
+msgstr ""
+
+msgid "Unreachable"
+msgstr ""
+
+msgid "Unrecognized cluster type"
+msgstr ""
+
+msgid "Unresolve"
+msgstr ""
+
+msgid "Unresolve thread"
+msgstr ""
+
+msgid "Unresolved"
+msgstr ""
+
+msgid "UnscannedProjects|15 or more days"
+msgstr ""
+
+msgid "UnscannedProjects|30 or more days"
+msgstr ""
+
+msgid "UnscannedProjects|5 or more days"
+msgstr ""
+
+msgid "UnscannedProjects|60 or more days"
+msgstr ""
+
+msgid "UnscannedProjects|Default branch scanning by project"
+msgstr ""
+
+msgid "UnscannedProjects|Out of date"
+msgstr ""
+
+msgid "UnscannedProjects|Project scanning"
+msgstr ""
+
+msgid "UnscannedProjects|Untested"
+msgstr ""
+
+msgid "UnscannedProjects|Your projects are up do date! Nice job!"
+msgstr ""
+
+msgid "Unschedule job"
+msgstr ""
+
+msgid "Unstar"
+msgstr ""
+
+msgid "Unsubscribe"
+msgstr ""
+
+msgid "Unsubscribe at group level"
+msgstr ""
+
+msgid "Unsubscribe at project level"
+msgstr ""
+
+msgid "Unsubscribe from %{type}"
+msgstr ""
+
+msgid "Unsubscribed from this %{quick_action_target}."
+msgstr ""
+
+msgid "Unsubscribes from this %{quick_action_target}."
+msgstr ""
+
+msgid "Unsupported todo type passed. Supported todo types are: %{todo_types}"
+msgstr ""
+
+msgid "Until"
+msgstr ""
+
+msgid "Until that time, the project can be restored."
+msgstr ""
+
+msgid "Unverified"
+msgstr ""
+
+msgid "Up to date"
+msgstr ""
+
+msgid "Upcoming"
+msgstr ""
+
+msgid "Upcoming Release"
+msgstr ""
+
+msgid "Update"
+msgstr ""
+
+msgid "Update all"
+msgstr ""
+
+msgid "Update approval rule"
+msgstr ""
+
+msgid "Update approvers"
+msgstr ""
+
+msgid "Update failed"
+msgstr ""
+
+msgid "Update failed. Please try again."
+msgstr ""
+
+msgid "Update it"
+msgstr ""
+
+msgid "Update iteration"
+msgstr ""
+
+msgid "Update now"
+msgstr ""
+
+msgid "Update variable"
+msgstr ""
+
+msgid "Update your bookmarked URLs as filtered/sorted branches URL has been changed."
+msgstr ""
+
+msgid "Update your group name, description, avatar, and visibility."
+msgstr ""
+
+msgid "Update your project name, topics, description and avatar."
+msgstr ""
+
+msgid "UpdateProject|Cannot rename project because it contains container registry tags!"
+msgstr ""
+
+msgid "UpdateProject|Could not set the default branch"
+msgstr ""
+
+msgid "UpdateProject|New visibility level not allowed!"
+msgstr ""
+
+msgid "UpdateProject|Project could not be updated!"
+msgstr ""
+
+msgid "UpdateRepositoryStorage|Error moving repository storage for %{project_full_path} - %{message}"
+msgstr ""
+
+msgid "UpdateRepositoryStorage|Failed to verify %{type} repository checksum from %{old} to %{new}"
+msgstr ""
+
+msgid "UpdateRepositoryStorage|Timeout waiting for %{type} repository pushes"
+msgstr ""
+
+msgid "Updated"
+msgstr ""
+
+msgid "Updated %{updated_at} by %{updated_by}"
+msgstr ""
+
+msgid "Updated at"
+msgstr ""
+
+msgid "Updated to %{linkStart}chart v%{linkEnd}"
+msgstr ""
+
+msgid "Updating"
+msgstr ""
+
+msgid "Upgrade plan to unlock Canary Deployments feature"
+msgstr ""
+
+msgid "Upgrade your plan to activate Advanced Global Search."
+msgstr ""
+
+msgid "Upgrade your plan to activate Audit Events."
+msgstr ""
+
+msgid "Upgrade your plan to activate Group Webhooks."
+msgstr ""
+
+msgid "Upgrade your plan to improve Issue boards."
+msgstr ""
+
+msgid "Upgrade your plan to improve Merge Requests."
+msgstr ""
+
+msgid "Upload <code>GoogleCodeProjectHosting.json</code> here:"
+msgstr ""
+
+msgid "Upload CSV file"
+msgstr ""
+
+msgid "Upload License"
+msgstr ""
+
+msgid "Upload New File"
+msgstr ""
+
+msgid "Upload New License"
+msgstr ""
+
+msgid "Upload a certificate for your domain with all intermediates"
+msgstr ""
+
+msgid "Upload a private key for your certificate"
+msgstr ""
+
+msgid "Upload file"
+msgstr ""
+
+msgid "Upload object map"
+msgstr ""
+
+msgid "UploadLink|click to upload"
+msgstr ""
+
+msgid "Uploaded on"
+msgstr ""
+
+msgid "Uploaded:"
+msgstr ""
+
+msgid "Uploading changes to terminal"
+msgstr ""
+
+msgid "Uploads"
+msgstr ""
+
+msgid "Upon performing this action, the contents of this group, its subgroup and projects will be permanently removed after %{deletion_adjourned_period} days on <strong>%{date}</strong>. Until that time:"
+msgstr ""
+
+msgid "Upstream"
+msgstr ""
+
+msgid "Uptime"
+msgstr ""
+
+msgid "Upvotes"
+msgstr ""
+
+msgid "Usage"
+msgstr ""
+
+msgid "Usage ping is not enabled"
+msgstr ""
+
+msgid "Usage statistics"
+msgstr ""
+
+msgid "UsageQuota|%{help_link_start}Shared runners%{help_link_end} are disabled, so there are no limits set on pipeline usage"
+msgstr ""
+
+msgid "UsageQuota|Artifacts"
+msgstr ""
+
+msgid "UsageQuota|Build Artifacts"
+msgstr ""
+
+msgid "UsageQuota|Buy additional minutes"
+msgstr ""
+
+msgid "UsageQuota|Current period usage"
+msgstr ""
+
+msgid "UsageQuota|LFS Objects"
+msgstr ""
+
+msgid "UsageQuota|LFS Storage"
+msgstr ""
+
+msgid "UsageQuota|Packages"
+msgstr ""
+
+msgid "UsageQuota|Pipelines"
+msgstr ""
+
+msgid "UsageQuota|Purchase more storage"
+msgstr ""
+
+msgid "UsageQuota|Repositories"
+msgstr ""
+
+msgid "UsageQuota|Repository"
+msgstr ""
+
+msgid "UsageQuota|Snippets"
+msgstr ""
+
+msgid "UsageQuota|Storage"
+msgstr ""
+
+msgid "UsageQuota|This namespace has no projects which use shared runners"
+msgstr ""
+
+msgid "UsageQuota|Unlimited"
+msgstr ""
+
+msgid "UsageQuota|Usage"
+msgstr ""
+
+msgid "UsageQuota|Usage Quotas"
+msgstr ""
+
+msgid "UsageQuota|Usage of group resources across the projects in the %{strong_start}%{group_name}%{strong_end} group"
+msgstr ""
+
+msgid "UsageQuota|Usage of resources across your projects"
+msgstr ""
+
+msgid "UsageQuota|Usage quotas help link"
+msgstr ""
+
+msgid "UsageQuota|Usage since"
+msgstr ""
+
+msgid "UsageQuota|Wiki"
+msgstr ""
+
+msgid "UsageQuota|Wikis"
+msgstr ""
+
+msgid "UsageQuota|You used: %{usage} %{limit}"
+msgstr ""
+
+msgid "UsageQuota|out of %{formattedLimit} of your namespace storage"
+msgstr ""
+
+msgid "Use %{code_start}::%{code_end} to create a %{link_start}scoped label set%{link_end} (eg. %{code_start}priority::1%{code_end})"
+msgstr ""
+
+msgid "Use <code>%{native_redirect_uri}</code> for local tests"
+msgstr ""
+
+msgid "Use Service Desk to connect with your users (e.g. to offer customer support) through email right inside GitLab"
+msgstr ""
+
+msgid "Use a hardware device to add the second factor of authentication."
+msgstr ""
+
+msgid "Use an one time password authenticator on your mobile device or computer to enable two-factor authentication (2FA)."
+msgstr ""
+
+msgid "Use custom color #FF0000"
+msgstr ""
+
+msgid "Use group milestones to manage issues from multiple projects in the same milestone."
+msgstr ""
+
+msgid "Use hashed storage"
+msgstr ""
+
+msgid "Use hashed storage paths for newly created and renamed repositories. Enable immutable, hash-based paths and repository names to store repositories on disk. This prevents repositories from having to be moved or renamed when the Repository URL changes and may improve disk I/O performance. (Always enabled since 13.0)"
+msgstr ""
+
+msgid "Use one line per URI"
+msgstr ""
+
+msgid "Use template"
+msgstr ""
+
+msgid "Use the following registration token during setup:"
+msgstr ""
+
+msgid "Use your global notification setting"
+msgstr ""
+
+msgid "Use your smart card to authenticate with the LDAP server."
+msgstr ""
+
+msgid "Used by members to sign in to your group in GitLab"
+msgstr ""
+
+msgid "Used programming language"
+msgstr ""
+
+msgid "Used to help configure your identity provider"
+msgstr ""
+
+msgid "User"
+msgstr ""
+
+msgid "User %{current_user_username} has started impersonating %{username}"
+msgstr ""
+
+msgid "User %{username} was successfully removed."
+msgstr ""
+
+msgid "User Cohorts are only shown when the %{usage_ping_link_start}usage ping%{usage_ping_link_end} is enabled."
+msgstr ""
+
+msgid "User IDs"
+msgstr ""
+
+msgid "User Lists can only be created and modified with %{linkStart}the API%{linkEnd}"
+msgstr ""
+
+msgid "User OAuth applications"
+msgstr ""
+
+msgid "User Settings"
+msgstr ""
+
+msgid "User and IP Rate Limits"
+msgstr ""
+
+msgid "User identity was successfully created."
+msgstr ""
+
+msgid "User identity was successfully removed."
+msgstr ""
+
+msgid "User identity was successfully updated."
+msgstr ""
+
+msgid "User is not allowed to resolve thread"
+msgstr ""
+
+msgid "User key was successfully removed."
+msgstr ""
+
+msgid "User list %{name} will be removed. Are you sure?"
+msgstr ""
+
+msgid "User map"
+msgstr ""
+
+msgid "User pipeline minutes were successfully reset."
+msgstr ""
+
+msgid "User restrictions"
+msgstr ""
+
+msgid "User was successfully created."
+msgstr ""
+
+msgid "User was successfully removed from group and any subresources."
+msgstr ""
+
+msgid "User was successfully removed from project."
+msgstr ""
+
+msgid "User was successfully updated."
+msgstr ""
+
+msgid "UserList|Delete %{name}?"
+msgstr ""
+
+msgid "UserList|created %{timeago}"
+msgstr ""
+
+msgid "UserProfile|Activity"
+msgstr ""
+
+msgid "UserProfile|Already reported for abuse"
+msgstr ""
+
+msgid "UserProfile|Blocked user"
+msgstr ""
+
+msgid "UserProfile|Contributed projects"
+msgstr ""
+
+msgid "UserProfile|Edit profile"
+msgstr ""
+
+msgid "UserProfile|Explore public groups to find projects to contribute to."
+msgstr ""
+
+msgid "UserProfile|Groups"
+msgstr ""
+
+msgid "UserProfile|Groups are the best way to manage projects and members."
+msgstr ""
+
+msgid "UserProfile|Join or create a group to start contributing by commenting on issues or submitting merge requests!"
+msgstr ""
+
+msgid "UserProfile|Most Recent Activity"
+msgstr ""
+
+msgid "UserProfile|No snippets found."
+msgstr ""
+
+msgid "UserProfile|Overview"
+msgstr ""
+
+msgid "UserProfile|Personal projects"
+msgstr ""
+
+msgid "UserProfile|Report abuse"
+msgstr ""
+
+msgid "UserProfile|Snippets"
+msgstr ""
+
+msgid "UserProfile|Snippets in GitLab can either be private, internal, or public."
+msgstr ""
+
+msgid "UserProfile|Star projects to track their progress and show your appreciation."
+msgstr ""
+
+msgid "UserProfile|Starred projects"
+msgstr ""
+
+msgid "UserProfile|Subscribe"
+msgstr ""
+
+msgid "UserProfile|This user doesn't have any personal projects"
+msgstr ""
+
+msgid "UserProfile|This user has a private profile"
+msgstr ""
+
+msgid "UserProfile|This user hasn't contributed to any projects"
+msgstr ""
+
+msgid "UserProfile|This user hasn't starred any projects"
+msgstr ""
+
+msgid "UserProfile|This user is blocked"
+msgstr ""
+
+msgid "UserProfile|View all"
+msgstr ""
+
+msgid "UserProfile|View user in admin area"
+msgstr ""
+
+msgid "UserProfile|You can create a group for several dependent projects."
+msgstr ""
+
+msgid "UserProfile|You haven't created any personal projects."
+msgstr ""
+
+msgid "UserProfile|You haven't created any snippets."
+msgstr ""
+
+msgid "UserProfile|Your projects can be available publicly, internally, or privately, at your choice."
+msgstr ""
+
+msgid "UserProfile|at"
+msgstr ""
+
+msgid "UserProfile|made a private contribution"
+msgstr ""
+
+msgid "Username (optional)"
+msgstr ""
+
+msgid "Username is already taken."
+msgstr ""
+
+msgid "Username is available."
+msgstr ""
+
+msgid "Username is too long (maximum is %{max_length} characters)."
+msgstr ""
+
+msgid "Username or email"
+msgstr ""
+
+msgid "Users"
+msgstr ""
+
+msgid "Users in License:"
+msgstr ""
+
+msgid "Users or groups set as approvers in the project's or merge request's settings."
+msgstr ""
+
+msgid "Users outside of license"
+msgstr ""
+
+msgid "Users over License:"
+msgstr ""
+
+msgid "Users requesting access to"
+msgstr ""
+
+msgid "Users were successfully added."
+msgstr ""
+
+msgid "Users with a Guest role or those who don't belong to any projects or groups don't count towards seats in use."
+msgstr ""
+
+msgid "UsersSelect|%{name} + %{length} more"
+msgstr ""
+
+msgid "UsersSelect|Any User"
+msgstr ""
+
+msgid "UsersSelect|Assignee"
+msgstr ""
+
+msgid "UsersSelect|No assignee - %{openingTag} assign yourself %{closingTag}"
+msgstr ""
+
+msgid "UsersSelect|Unassigned"
+msgstr ""
+
+msgid "Using %{codeStart}needs%{codeEnd} allows jobs to run before their stage is reached, as soon as their individual dependencies are met, which speeds up your pipelines."
+msgstr ""
+
+msgid "Using %{code_start}::%{code_end} denotes a %{link_start}scoped label set%{link_end}"
+msgstr ""
+
+msgid "Using required encryption strategy when encrypted field is missing!"
+msgstr ""
+
+msgid "Valid from"
+msgstr ""
+
+msgid "Validate"
+msgstr ""
+
+msgid "Validate your GitLab CI configuration file"
+msgstr ""
+
+msgid "Validations failed."
+msgstr ""
+
+msgid "Validity"
+msgstr ""
+
+msgid "Value"
+msgstr ""
+
+msgid "Value Stream"
+msgstr ""
+
+msgid "Value Stream Analytics"
+msgstr ""
+
+msgid "Value Stream Analytics can help you determine your team’s velocity"
+msgstr ""
+
+msgid "Value Stream Analytics gives an overview of how much time it takes to go from idea to production in your project."
+msgstr ""
+
+msgid "ValueStreamAnalytics|%{days}d"
+msgstr ""
+
+msgid "Variable"
+msgstr ""
+
+msgid "Variable will be masked in job logs."
+msgstr ""
+
+msgid "Variables"
+msgstr ""
+
+msgid "Various container registry settings."
+msgstr ""
+
+msgid "Various email settings."
+msgstr ""
+
+msgid "Various localization settings."
+msgstr ""
+
+msgid "Various settings that affect GitLab performance."
+msgstr ""
+
+msgid "Verification capacity"
+msgstr ""
+
+msgid "Verification information"
+msgstr ""
+
+msgid "Verification status"
+msgstr ""
+
+msgid "Verified"
+msgstr ""
+
+msgid "Verify SAML Configuration"
+msgstr ""
+
+msgid "Verify configuration"
+msgstr ""
+
+msgid "Version"
+msgstr ""
+
+msgid "Versions"
+msgstr ""
+
+msgid "View Documentation"
+msgstr ""
+
+msgid "View all issues"
+msgstr ""
+
+msgid "View blame prior to this change"
+msgstr ""
+
+msgid "View chart"
+msgid_plural "View charts"
+msgstr[0] ""
+
+msgid "View dependency details for your project"
+msgstr ""
+
+msgid "View deployment"
+msgstr ""
+
+msgid "View details"
+msgstr ""
+
+msgid "View details: %{details_url}"
+msgstr ""
+
+msgid "View documentation"
+msgstr ""
+
+msgid "View eligible approvers"
+msgstr ""
+
+msgid "View epics list"
+msgstr ""
+
+msgid "View exposed artifact"
+msgid_plural "View %d exposed artifacts"
+msgstr[0] ""
+
+msgid "View file @ "
+msgstr ""
+
+msgid "View file @ %{commitSha}"
+msgstr ""
+
+msgid "View full dashboard"
+msgstr ""
+
+msgid "View full log"
+msgstr ""
+
+msgid "View group labels"
+msgstr ""
+
+msgid "View incident issues."
+msgstr ""
+
+msgid "View issue"
+msgstr ""
+
+msgid "View issues"
+msgstr ""
+
+msgid "View it on GitLab"
+msgstr ""
+
+msgid "View job"
+msgstr ""
+
+msgid "View job log"
+msgstr ""
+
+msgid "View jobs"
+msgstr ""
+
+msgid "View labels"
+msgstr ""
+
+msgid "View log"
+msgstr ""
+
+msgid "View open merge request"
+msgstr ""
+
+msgid "View performance dashboard."
+msgstr ""
+
+msgid "View project"
+msgstr ""
+
+msgid "View project labels"
+msgstr ""
+
+msgid "View replaced file @ "
+msgstr ""
+
+msgid "View supported languages and frameworks"
+msgstr ""
+
+msgid "View the documentation"
+msgstr ""
+
+msgid "View the latest successful deployment to this environment"
+msgstr ""
+
+msgid "View the performance dashboard at"
+msgstr ""
+
+msgid "View users statistics"
+msgstr ""
+
+msgid "Viewing commit"
+msgstr ""
+
+msgid "Visibility"
+msgstr ""
+
+msgid "Visibility and access controls"
+msgstr ""
+
+msgid "Visibility level"
+msgstr ""
+
+msgid "Visibility level:"
+msgstr ""
+
+msgid "Visibility settings have been disabled by the administrator."
+msgstr ""
+
+msgid "Visibility, project features, permissions"
+msgstr ""
+
+msgid "Visibility:"
+msgstr ""
+
+msgid "VisibilityLevel|Internal"
+msgstr ""
+
+msgid "VisibilityLevel|Private"
+msgstr ""
+
+msgid "VisibilityLevel|Public"
+msgstr ""
+
+msgid "VisibilityLevel|Unknown"
+msgstr ""
+
+msgid "Visit settings page"
+msgstr ""
+
+msgid "VisualReviewApp|%{stepStart}Step 1%{stepEnd}. Copy the following script:"
+msgstr ""
+
+msgid "VisualReviewApp|%{stepStart}Step 2%{stepEnd}. Add it to the %{headTags} tags of every page of your application, ensuring the merge request ID is set or not set as required. "
+msgstr ""
+
+msgid "VisualReviewApp|%{stepStart}Step 3%{stepEnd}. If not previously %{linkStart}configured%{linkEnd} by a developer, enter the merge request ID for the review when prompted. The ID of this merge request is %{stepStart}%{mrId}%{stepStart}."
+msgstr ""
+
+msgid "VisualReviewApp|%{stepStart}Step 4%{stepEnd}. Leave feedback in the Review App."
+msgstr ""
+
+msgid "VisualReviewApp|Cancel"
+msgstr ""
+
+msgid "VisualReviewApp|Copy merge request ID"
+msgstr ""
+
+msgid "VisualReviewApp|Copy script"
+msgstr ""
+
+msgid "VisualReviewApp|Enable Visual Reviews"
+msgstr ""
+
+msgid "VisualReviewApp|Follow the steps below to enable Visual Reviews inside your application."
+msgstr ""
+
+msgid "VisualReviewApp|No review app found or available."
+msgstr ""
+
+msgid "VisualReviewApp|Open review app"
+msgstr ""
+
+msgid "VisualReviewApp|Review"
+msgstr ""
+
+msgid "VisualReviewApp|Steps 1 and 2 (and sometimes 3) are performed once by the developer before requesting feedback. Steps 3 (if necessary), 4 is performed by the reviewer each time they perform a review."
+msgstr ""
+
+msgid "Vulnerabilities"
+msgstr ""
+
+msgid "Vulnerabilities over time"
+msgstr ""
+
+msgid "Vulnerability remediated. Review before resolving."
+msgstr ""
+
+msgid "Vulnerability resolved in %{branch}"
+msgstr ""
+
+msgid "Vulnerability resolved in the default branch"
+msgstr ""
+
+msgid "Vulnerability-Check"
+msgstr ""
+
+msgid "VulnerabilityChart|%{formattedStartDate} to today"
+msgstr ""
+
+msgid "VulnerabilityChart|Severity"
+msgstr ""
+
+msgid "VulnerabilityManagement|A true-positive and will fix"
+msgstr ""
+
+msgid "VulnerabilityManagement|Change status"
+msgstr ""
+
+msgid "VulnerabilityManagement|Confirm"
+msgstr ""
+
+msgid "VulnerabilityManagement|Confirmed %{timeago} by %{user}"
+msgstr ""
+
+msgid "VulnerabilityManagement|Detected %{timeago} in pipeline %{pipelineLink}"
+msgstr ""
+
+msgid "VulnerabilityManagement|Dismiss"
+msgstr ""
+
+msgid "VulnerabilityManagement|Dismissed %{timeago} by %{user}"
+msgstr ""
+
+msgid "VulnerabilityManagement|Resolved"
+msgstr ""
+
+msgid "VulnerabilityManagement|Resolved %{timeago} by %{user}"
+msgstr ""
+
+msgid "VulnerabilityManagement|Something went wrong while trying to delete the comment. Please try again later."
+msgstr ""
+
+msgid "VulnerabilityManagement|Something went wrong while trying to refresh the vulnerability. Please try again later."
+msgstr ""
+
+msgid "VulnerabilityManagement|Something went wrong while trying to retrieve the vulnerability history. Please try again later."
+msgstr ""
+
+msgid "VulnerabilityManagement|Something went wrong while trying to save the comment. Please try again later."
+msgstr ""
+
+msgid "VulnerabilityManagement|Something went wrong, could not create an issue."
+msgstr ""
+
+msgid "VulnerabilityManagement|Something went wrong, could not get user."
+msgstr ""
+
+msgid "VulnerabilityManagement|Something went wrong, could not update vulnerability state."
+msgstr ""
+
+msgid "VulnerabilityManagement|Verified as fixed or mitigated"
+msgstr ""
+
+msgid "VulnerabilityManagement|Will not fix or a false-positive"
+msgstr ""
+
+msgid "VulnerabilityStatusTypes|All"
+msgstr ""
+
+msgid "VulnerabilityStatusTypes|Confirmed"
+msgstr ""
+
+msgid "VulnerabilityStatusTypes|Detected"
+msgstr ""
+
+msgid "VulnerabilityStatusTypes|Dismissed"
+msgstr ""
+
+msgid "VulnerabilityStatusTypes|Resolved"
+msgstr ""
+
+msgid "Vulnerability|%{scannerName} (version %{scannerVersion})"
+msgstr ""
+
+msgid "Vulnerability|Class"
+msgstr ""
+
+msgid "Vulnerability|Crash Address"
+msgstr ""
+
+msgid "Vulnerability|Description"
+msgstr ""
+
+msgid "Vulnerability|Evidence"
+msgstr ""
+
+msgid "Vulnerability|File"
+msgstr ""
+
+msgid "Vulnerability|Identifiers"
+msgstr ""
+
+msgid "Vulnerability|Image"
+msgstr ""
+
+msgid "Vulnerability|Instances"
+msgstr ""
+
+msgid "Vulnerability|Links"
+msgstr ""
+
+msgid "Vulnerability|Method"
+msgstr ""
+
+msgid "Vulnerability|Namespace"
+msgstr ""
+
+msgid "Vulnerability|Project"
+msgstr ""
+
+msgid "Vulnerability|Scanner Provider"
+msgstr ""
+
+msgid "Vulnerability|Scanner Type"
+msgstr ""
+
+msgid "Vulnerability|Severity"
+msgstr ""
+
+msgid "Vulnerability|Status"
+msgstr ""
+
+msgid "WIP"
+msgstr ""
+
+msgid "Wait for the file to load to copy its contents"
+msgstr ""
+
+msgid "Waiting for merge (open and assigned)"
+msgstr ""
+
+msgid "Waiting for performance data"
+msgstr ""
+
+msgid "Want to see the data? Please ask an administrator for access."
+msgstr ""
+
+msgid "Warning:"
+msgstr ""
+
+msgid "Warning: Displaying this diagram might cause performance issues on this page."
+msgstr ""
+
+msgid "We are currently unable to fetch data for this graph."
+msgstr ""
+
+msgid "We could not determine the path to remove the epic"
+msgstr ""
+
+msgid "We could not determine the path to remove the issue"
+msgstr ""
+
+msgid "We couldn't reach the Prometheus server. Either the server no longer exists or the configuration details need updating."
+msgstr ""
+
+msgid "We detected potential spam in the %{humanized_resource_name}. Please solve the reCAPTCHA to proceed."
+msgstr ""
+
+msgid "We don't have enough data to show this stage."
+msgstr ""
+
+msgid "We have found the following errors:"
+msgstr ""
+
+msgid "We heard back from your device. You have been authenticated."
+msgstr ""
+
+msgid "We recommend that you buy more Pipeline minutes to avoid any interruption of service."
+msgstr ""
+
+msgid "We recommend that you buy more Pipeline minutes to resume normal service."
+msgstr ""
+
+msgid "We sent you an email with reset password instructions"
+msgstr ""
+
+msgid "We tried to automatically renew your %{strong}%{plan_name}%{strong_close} subscription for %{strong}%{namespace_name}%{strong_close} on %{expires_on} but something went wrong so your subscription was downgraded to the free plan. Don't worry, your data is safe. We suggest you check your payment method and get in touch with our support team (%{support_link}). They'll gladly help with your subscription renewal."
+msgstr ""
+
+msgid "We want to be sure it is you, please confirm you are not a robot."
+msgstr ""
+
+msgid "We will automatically renew your %{strong}%{plan_name}%{strong_close} subscription for %{strong}%{namespace_name}%{strong_close} on %{strong}%{expires_on}%{strong_close}. There's nothing that you need to do, we'll let you know when the renewal is complete. Need more seats, a higher plan or just want to review your payment method?"
+msgstr ""
+
+msgid "We've found no vulnerabilities"
+msgstr ""
+
+msgid "Web Application Firewall"
+msgstr ""
+
+msgid "Web IDE"
+msgstr ""
+
+msgid "Web Terminal"
+msgstr ""
+
+msgid "Web terminal"
+msgstr ""
+
+msgid "WebIDE|Merge request"
+msgstr ""
+
+msgid "Webhook"
+msgstr ""
+
+msgid "Webhook Logs"
+msgstr ""
+
+msgid "Webhook Settings"
+msgstr ""
+
+msgid "Webhooks"
+msgstr ""
+
+msgid "Webhooks allow you to trigger a URL if, for example, new code is pushed or a new issue is created. You can configure webhooks to listen for specific events like pushes, issues or merge requests. Group webhooks will apply to all projects in a group, allowing you to standardize webhook functionality across your entire group."
+msgstr ""
+
+msgid "Webhooks have moved. They can now be found under the Settings menu."
+msgstr ""
+
+msgid "Wednesday"
+msgstr ""
+
+msgid "Weekday"
+msgstr ""
+
+msgid "Weeks"
+msgstr ""
+
+msgid "Weight"
+msgstr ""
+
+msgid "Weight %{weight}"
+msgstr ""
+
+msgid "Welcome back! Your account had been deactivated due to inactivity but is now reactivated."
+msgstr ""
+
+msgid "Welcome to GitLab"
+msgstr ""
+
+msgid "Welcome to GitLab %{name}!"
+msgstr ""
+
+msgid "Welcome to GitLab, %{first_name}!"
+msgstr ""
+
+msgid "Welcome to GitLab.com<br>@%{name}!"
+msgstr ""
+
+msgid "Welcome to the guided GitLab tour"
+msgstr ""
+
+msgid "Welcome to your Issue Board!"
+msgstr ""
+
+msgid "What are you searching for?"
+msgstr ""
+
+msgid "What describes you best?"
+msgstr ""
+
+msgid "What’s your experience level?"
+msgstr ""
+
+msgid "When a deployment job is successful, skip older deployment jobs that are still pending"
+msgstr ""
+
+msgid "When a runner is locked, it cannot be assigned to other projects"
+msgstr ""
+
+msgid "When enabled, any user visiting %{host} will be able to create an account."
+msgstr ""
+
+msgid "When enabled, if an NPM package isn't found in the GitLab Registry, we will attempt to pull from the global NPM registry."
+msgstr ""
+
+msgid "When enabled, users cannot use GitLab until the terms have been accepted."
+msgstr ""
+
+msgid "When leaving the URL blank, classification labels can still be specified without disabling cross project features or performing external authorization checks."
+msgstr ""
+
+msgid "When this merge request is accepted"
+msgid_plural "When these merge requests are accepted"
+msgstr[0] ""
+
+msgid "When using the <code>http://</code> or <code>https://</code> protocols, please provide the exact URL to the repository. HTTP redirects will not be followed."
+msgstr ""
+
+msgid "When:"
+msgstr ""
+
+msgid "While it's rare to have no vulnerabilities, it can happen. In any event, we ask that you please double check your settings to make sure you've set up your dashboard correctly."
+msgstr ""
+
+msgid "Who can be an approver?"
+msgstr ""
+
+msgid "Who can see this group?"
+msgstr ""
+
+msgid "Who will be able to see this group?"
+msgstr ""
+
+msgid "Who will be using GitLab?"
+msgstr ""
+
+msgid "Who will be using this GitLab subscription?"
+msgstr ""
+
+msgid "Who will be using this GitLab trial?"
+msgstr ""
+
+msgid "Wiki"
+msgstr ""
+
+msgid "Wiki was successfully updated."
+msgstr ""
+
+msgid "WikiClone|Clone your wiki"
+msgstr ""
+
+msgid "WikiClone|Git Access"
+msgstr ""
+
+msgid "WikiClone|Install Gollum"
+msgstr ""
+
+msgid "WikiClone|Start Gollum and edit locally"
+msgstr ""
+
+msgid "WikiEditPageTip|Tip: You can move this page by adding the path to the beginning of the title."
+msgstr ""
+
+msgid "WikiEdit|There is already a page with the same title in that path."
+msgstr ""
+
+msgid "WikiEmptyIssueMessage|Suggest wiki improvement"
+msgstr ""
+
+msgid "WikiEmptyIssueMessage|You must be a group member in order to add wiki pages. If you have suggestions for how to improve the wiki for this group, consider opening an issue in the %{issues_link}."
+msgstr ""
+
+msgid "WikiEmptyIssueMessage|You must be a project member in order to add wiki pages. If you have suggestions for how to improve the wiki for this project, consider opening an issue in the %{issues_link}."
+msgstr ""
+
+msgid "WikiEmptyIssueMessage|issue tracker"
+msgstr ""
+
+msgid "WikiEmpty|A wiki is where you can store all the details about your group. This can include why you've created it, its principles, how to use it, and so on."
+msgstr ""
+
+msgid "WikiEmpty|A wiki is where you can store all the details about your project. This can include why you've created it, its principles, how to use it, and so on."
+msgstr ""
+
+msgid "WikiEmpty|Create your first page"
+msgstr ""
+
+msgid "WikiEmpty|Suggest wiki improvement"
+msgstr ""
+
+msgid "WikiEmpty|The wiki lets you write documentation for your group"
+msgstr ""
+
+msgid "WikiEmpty|The wiki lets you write documentation for your project"
+msgstr ""
+
+msgid "WikiEmpty|This group has no wiki pages"
+msgstr ""
+
+msgid "WikiEmpty|This project has no wiki pages"
+msgstr ""
+
+msgid "WikiEmpty|You must be a group member in order to add wiki pages."
+msgstr ""
+
+msgid "WikiEmpty|You must be a project member in order to add wiki pages."
+msgstr ""
+
+msgid "WikiHistoricalPage|This is an old version of this page."
+msgstr ""
+
+msgid "WikiHistoricalPage|You can view the %{most_recent_link} or browse the %{history_link}."
+msgstr ""
+
+msgid "WikiHistoricalPage|history"
+msgstr ""
+
+msgid "WikiHistoricalPage|most recent version"
+msgstr ""
+
+msgid "WikiMarkdownDocs|More examples are in the %{docs_link}"
+msgstr ""
+
+msgid "WikiMarkdownDocs|documentation"
+msgstr ""
+
+msgid "WikiMarkdownTip|To link to a (new) page, simply type <code class=\"js-markup-link-example\">%{link_example}</code>"
+msgstr ""
+
+msgid "WikiNewPageTip|Tip: You can specify the full path for the new file. We will automatically create any missing directories."
+msgstr ""
+
+msgid "WikiPageConfirmDelete|Are you sure you want to delete this page?"
+msgstr ""
+
+msgid "WikiPageConfirmDelete|Delete page"
+msgstr ""
+
+msgid "WikiPageConfirmDelete|Delete page %{pageTitle}?"
+msgstr ""
+
+msgid "WikiPageConflictMessage|Someone edited the page the same time you did. Please check out %{page_link} and make sure your changes will not unintentionally remove theirs."
+msgstr ""
+
+msgid "WikiPageConflictMessage|the page"
+msgstr ""
+
+msgid "WikiPageCreate|Create %{pageTitle}"
+msgstr ""
+
+msgid "WikiPageEdit|Update %{pageTitle}"
+msgstr ""
+
+msgid "WikiPage|Write your content or drag files here…"
+msgstr ""
+
+msgid "Wiki|Create New Page"
+msgstr ""
+
+msgid "Wiki|Create page"
+msgstr ""
+
+msgid "Wiki|Created date"
+msgstr ""
+
+msgid "Wiki|Edit Page"
+msgstr ""
+
+msgid "Wiki|New page"
+msgstr ""
+
+msgid "Wiki|Page history"
+msgstr ""
+
+msgid "Wiki|Page title"
+msgstr ""
+
+msgid "Wiki|Page version"
+msgstr ""
+
+msgid "Wiki|Pages"
+msgstr ""
+
+msgid "Wiki|Title"
+msgstr ""
+
+msgid "Wiki|View All Pages"
+msgstr ""
+
+msgid "Wiki|Wiki Pages"
+msgstr ""
+
+msgid "Will be created"
+msgstr ""
+
+msgid "Will deploy to"
+msgstr ""
+
+msgid "With requirements, you can set criteria to check your products against."
+msgstr ""
+
+msgid "Withdraw Access Request"
+msgstr ""
+
+msgid "Won't fix / Accept risk"
+msgstr ""
+
+msgid "Work in progress (open and unassigned)"
+msgstr ""
+
+msgid "Work in progress Limit"
+msgstr ""
+
+msgid "Write"
+msgstr ""
+
+msgid "Write a comment or drag your files here…"
+msgstr ""
+
+msgid "Write a comment…"
+msgstr ""
+
+msgid "Write access allowed"
+msgstr ""
+
+msgid "Write milestone description..."
+msgstr ""
+
+msgid "Write your release notes or drag your files here…"
+msgstr ""
+
+msgid "Wrong extern UID provided. Make sure Auth0 is configured correctly."
+msgstr ""
+
+msgid "Yes"
+msgstr ""
+
+msgid "Yes or No"
+msgstr ""
+
+msgid "Yes, add it"
+msgstr ""
+
+msgid "Yes, close issue"
+msgstr ""
+
+msgid "Yes, let me map Google Code users to full names or GitLab users."
+msgstr ""
+
+msgid "Yesterday"
+msgstr ""
+
+msgid "You"
+msgstr ""
+
+msgid "You are about to delete %{domain} from your instance. This domain will no longer be available to any Knative application."
+msgstr ""
+
+msgid "You are about to transfer the control of your account to %{group_name} group. This action is NOT reversible, you won't be able to access any of your groups and projects outside of %{group_name} once this transfer is complete."
+msgstr ""
+
+msgid "You are an admin, which means granting access to <strong>%{client_name}</strong> will allow them to interact with GitLab as an admin as well. Proceed with caution."
+msgstr ""
+
+msgid "You are attempting to delete a file that has been previously updated."
+msgstr ""
+
+msgid "You are attempting to update a file that has changed since you started editing it."
+msgstr ""
+
+msgid "You are connected to the Prometheus server, but there is currently no data to display."
+msgstr ""
+
+msgid "You are going to remove %{group_name}, this will also remove all of its subgroups and projects. Removed groups CANNOT be restored! Are you ABSOLUTELY sure?"
+msgstr ""
+
+msgid "You are going to remove %{project_full_name}. Removed project CANNOT be restored! Are you ABSOLUTELY sure?"
+msgstr ""
+
+msgid "You are going to remove the fork relationship from %{project_full_name}. Are you ABSOLUTELY sure?"
+msgstr ""
+
+msgid "You are going to transfer %{project_full_name} to another owner. Are you ABSOLUTELY sure?"
+msgstr ""
+
+msgid "You are not allowed to push into this branch. Create another branch or open a merge request."
+msgstr ""
+
+msgid "You are not allowed to unlink your primary login account"
+msgstr ""
+
+msgid "You are not authorized to perform this action"
+msgstr ""
+
+msgid "You are now impersonating %{username}"
+msgstr ""
+
+msgid "You are on a read-only GitLab instance."
+msgstr ""
+
+msgid "You are receiving this message because you are a GitLab administrator for %{url}."
+msgstr ""
+
+msgid "You are trying to upload something other than an image. Please upload a .png, .jpg, .jpeg, .gif, .bmp, .tiff or .ico."
+msgstr ""
+
+msgid "You can %{linkStart}view the blob%{linkEnd} instead."
+msgstr ""
+
+msgid "You can also create a project from the command line."
+msgstr ""
+
+msgid "You can also press &#8984;-Enter"
+msgstr ""
+
+msgid "You can also press Ctrl-Enter"
+msgstr ""
+
+msgid "You can also star a label to make it a priority label."
+msgstr ""
+
+msgid "You can also test your %{gitlab_ci_yml} in %{lint_link_start}CI Lint%{lint_link_end}"
+msgstr ""
+
+msgid "You can also upload existing files from your computer using the instructions below."
+msgstr ""
+
+msgid "You can also use project access tokens to authenticate against Git over HTTP."
+msgstr ""
+
+msgid "You can always edit this later"
+msgstr ""
+
+msgid "You can apply your Trial to your Personal account or create a New Group."
+msgstr ""
+
+msgid "You can create a new one or check them in your %{pat_link_start}Personal Access Tokens%{pat_link_end} settings"
+msgstr ""
+
+msgid "You can create a new one or check them in your Personal Access Tokens settings %{pat_link}"
+msgstr ""
+
+msgid "You can create new ones at your %{pat_link_start}Personal Access Tokens%{pat_link_end} settings"
+msgstr ""
+
+msgid "You can create new ones at your Personal Access Tokens settings %{pat_link}"
+msgstr ""
+
+msgid "You can easily contribute to them by requesting to join these groups."
+msgstr ""
+
+msgid "You can easily install a Runner on a Kubernetes cluster. %{link_to_help_page}"
+msgstr ""
+
+msgid "You can filter by 'days to merge' by clicking on the columns in the chart."
+msgstr ""
+
+msgid "You can generate an access token scoped to this project for each application to use the GitLab API."
+msgstr ""
+
+msgid "You can get started by cloning the repository or start adding files to it with one of the following options."
+msgstr ""
+
+msgid "You can invite a new member to <strong>%{project_name}</strong> or invite another group."
+msgstr ""
+
+msgid "You can invite a new member to <strong>%{project_name}</strong>."
+msgstr ""
+
+msgid "You can invite another group to <strong>%{project_name}</strong>."
+msgstr ""
+
+msgid "You can move around the graph by using the arrow keys."
+msgstr ""
+
+msgid "You can notify the app / group or a project by sending them an email notification"
+msgstr ""
+
+msgid "You can now export your security dashboard to a CSV report."
+msgstr ""
+
+msgid "You can now manage alert endpoint configuration in the Alerts section on the Operations settings page. Fields on this page have been deprecated."
+msgstr ""
+
+msgid "You can now submit a merge request to get this change into the original branch."
+msgstr ""
+
+msgid "You can now submit a merge request to get this change into the original project."
+msgstr ""
+
+msgid "You can only edit files when you are on a branch"
+msgstr ""
+
+msgid "You can only merge once the items above are resolved."
+msgstr ""
+
+msgid "You can only merge once this merge request is approved."
+msgstr ""
+
+msgid "You can only transfer the project to namespaces you manage."
+msgstr ""
+
+msgid "You can only upload one design when dropping onto an existing design."
+msgstr ""
+
+msgid "You can resolve the merge conflict using either the Interactive mode, by choosing %{use_ours} or %{use_theirs} buttons, or by editing the files directly. Commit these changes into %{branch_name}"
+msgstr ""
+
+msgid "You can see your chat accounts."
+msgstr ""
+
+msgid "You can set up as many Runners as you need to run your jobs."
+msgstr ""
+
+msgid "You can set up jobs to only use Runners with specific tags. Separate tags with commas."
+msgstr ""
+
+msgid "You can specify notification level per group or per project."
+msgstr ""
+
+msgid "You can test your .gitlab-ci.yml in %{linkStart}CI Lint%{linkEnd}."
+msgstr ""
+
+msgid "You can try again using %{begin_link}basic search%{end_link}"
+msgstr ""
+
+msgid "You cannot access the raw file. Please wait a minute."
+msgstr ""
+
+msgid "You cannot impersonate a blocked user"
+msgstr ""
+
+msgid "You cannot impersonate a user who cannot log in"
+msgstr ""
+
+msgid "You cannot impersonate an internal user"
+msgstr ""
+
+msgid "You cannot play this scheduled pipeline at the moment. Please wait a minute."
+msgstr ""
+
+msgid "You cannot write to a read-only secondary GitLab Geo instance. Please use %{link_to_primary_node} instead."
+msgstr ""
+
+msgid "You cannot write to this read-only GitLab instance."
+msgstr ""
+
+msgid "You could not create a new trigger."
+msgstr ""
+
+msgid "You didn't renew your %{strong}%{plan_name}%{strong_close} subscription for %{strong}%{namespace_name}%{strong_close} so it was downgraded to the free plan."
+msgstr ""
+
+msgid "You didn't renew your %{strong}%{plan_name}%{strong_close} subscription so it was downgraded to the GitLab Core Plan."
+msgstr ""
+
+msgid "You do not have an active license"
+msgstr ""
+
+msgid "You do not have any subscriptions yet"
+msgstr ""
+
+msgid "You do not have permission to leave this %{namespaceType}."
+msgstr ""
+
+msgid "You do not have permission to run the Web Terminal. Please contact a project administrator."
+msgstr ""
+
+msgid "You do not have permissions to run the import."
+msgstr ""
+
+msgid "You do not have the correct permissions to override the settings from the LDAP group sync."
+msgstr ""
+
+msgid "You don't have any U2F devices registered yet."
+msgstr ""
+
+msgid "You don't have any active chat names."
+msgstr ""
+
+msgid "You don't have any applications"
+msgstr ""
+
+msgid "You don't have any authorized applications"
+msgstr ""
+
+msgid "You don't have any deployments right now."
+msgstr ""
+
+msgid "You don't have any open merge requests"
+msgstr ""
+
+msgid "You don't have any projects available."
+msgstr ""
+
+msgid "You don't have any recent searches"
+msgstr ""
+
+msgid "You don't have sufficient permission to perform this action."
+msgstr ""
+
+msgid "You don’t have access to Productivity Analytics in this group"
+msgstr ""
+
+msgid "You don’t have access to Value Stream Analytics for this group"
+msgstr ""
+
+msgid "You have a license that activates at a future date. Please see the License History table below."
+msgstr ""
+
+msgid "You have been granted %{access_level} access to the %{source_link} %{source_type}."
+msgstr ""
+
+msgid "You have been granted %{access_level} access to the %{source_name} %{source_type}."
+msgstr ""
+
+msgid "You have been granted %{member_human_access} access to %{label}."
+msgstr ""
+
+msgid "You have been unsubscribed from this thread."
+msgstr ""
+
+msgid "You have declined the invitation to join %{label}."
+msgstr ""
+
+msgid "You have imported from this project %{numberOfPreviousImports} times before. Each new import will create duplicate issues."
+msgstr ""
+
+msgid "You have no permissions"
+msgstr ""
+
+msgid "You have not added any approvers. Start by adding users or groups."
+msgstr ""
+
+msgid "You have reached your project limit"
+msgstr ""
+
+msgid "You have successfully purchased a %{plan} plan subscription for %{seats}. You’ll receive a receipt via email."
+msgstr ""
+
+msgid "You haven't added any issues to your project yet"
+msgstr ""
+
+msgid "You haven't selected any issues yet"
+msgstr ""
+
+msgid "You left the \"%{membershipable_human_name}\" %{source_type}."
+msgstr ""
+
+msgid "You may also add variables that are made available to the running application by prepending the variable key with <code>K8S_SECRET_</code>."
+msgstr ""
+
+msgid "You may close the milestone now."
+msgstr ""
+
+msgid "You must accept our Terms of Service and privacy policy in order to register an account"
+msgstr ""
+
+msgid "You must disassociate %{domain} from all clusters it is attached to before deletion."
+msgstr ""
+
+msgid "You must have maintainer access to force delete a lock"
+msgstr ""
+
+msgid "You must have permission to create a project in a namespace before forking."
+msgstr ""
+
+msgid "You must provide a valid current password"
+msgstr ""
+
+msgid "You must provide your current password in order to change it."
+msgstr ""
+
+msgid "You must select a stack for configuring your cloud provider. Learn more about"
+msgstr ""
+
+msgid "You must set up incoming email before it becomes active."
+msgstr ""
+
+msgid "You must upload a file with the same file name when dropping onto an existing design."
+msgstr ""
+
+msgid "You need a different license to enable FileLocks feature"
+msgstr ""
+
+msgid "You need git-lfs version %{min_git_lfs_version} (or greater) to continue. Please visit https://git-lfs.github.com"
+msgstr ""
+
+msgid "You need permission."
+msgstr ""
+
+msgid "You need to be logged in."
+msgstr ""
+
+msgid "You need to register a two-factor authentication app before you can set up a U2F device."
+msgstr ""
+
+msgid "You need to set terms to be enforced"
+msgstr ""
+
+msgid "You need to specify both an Access Token and a Host URL."
+msgstr ""
+
+msgid "You need to upload a GitLab project export archive (ending in .gz)."
+msgstr ""
+
+msgid "You need to upload a Google Takeout archive."
+msgstr ""
+
+msgid "You reached %{usage_in_percent} of %{namespace_name}'s storage capacity (%{used_storage} of %{storage_limit})"
+msgstr ""
+
+msgid "You tried to fork %{link_to_the_project} but it failed for the following reason:"
+msgstr ""
+
+msgid "You will be removed from existing projects/groups"
+msgstr ""
+
+msgid "You will be the author of all events in the activity feed that are the result of an update, like new branches being created or new commits being pushed to existing branches."
+msgstr ""
+
+msgid "You will first need to set up Jira Integration to use this feature."
+msgstr ""
+
+msgid "You will lose all changes you've made to this file. This action cannot be undone."
+msgstr ""
+
+msgid "You will lose all uncommitted changes you've made in this project. This action cannot be undone."
+msgstr ""
+
+msgid "You will need to update your local repositories to point to the new location."
+msgstr ""
+
+msgid "You will not get any notifications via email"
+msgstr ""
+
+msgid "You will only receive notifications for the events you choose"
+msgstr ""
+
+msgid "You will only receive notifications for threads you have participated in"
+msgstr ""
+
+msgid "You will receive notifications for any activity"
+msgstr ""
+
+msgid "You will receive notifications only for comments in which you were @mentioned"
+msgstr ""
+
+msgid "You won't be able to create new projects because you have reached your project limit."
+msgstr ""
+
+msgid "You won't be able to pull or push project code via %{protocol} until you %{set_password_link} on your account"
+msgstr ""
+
+msgid "You won't be able to pull or push project code via SSH until you add an SSH key to your profile"
+msgstr ""
+
+msgid "You'll be signed out from your current account automatically."
+msgstr ""
+
+msgid "You'll need to use different branch names to get a valid comparison."
+msgstr ""
+
+msgid "You're about to reduce the visibility of the project %{strong_start}%{project_name}%{strong_end} in %{strong_start}%{group_name}%{strong_end}."
+msgstr ""
+
+msgid "You're about to reduce the visibility of the project %{strong_start}%{project_name}%{strong_end}."
+msgstr ""
+
+msgid "You're at the first commit"
+msgstr ""
+
+msgid "You're at the last commit"
+msgstr ""
+
+msgid "You're not allowed to %{tag_start}edit%{tag_end} files in this project directly. Please fork this project, make your changes there, and submit a merge request."
+msgstr ""
+
+msgid "You're not allowed to make changes to this project directly. A fork of this project has been created that you can make changes in, so you can submit a merge request."
+msgstr ""
+
+msgid "You're not allowed to make changes to this project directly. A fork of this project is being created that you can make changes in, so you can submit a merge request."
+msgstr ""
+
+msgid "You're only seeing %{startTag}other activity%{endTag} in the feed. To add a comment, switch to one of the following options."
+msgstr ""
+
+msgid "You're receiving this email because of your account on %{host}."
+msgstr ""
+
+msgid "You're receiving this email because of your account on %{host}. %{manage_notifications_link} &middot; %{help_link}"
+msgstr ""
+
+msgid "You're receiving this email because of your activity on %{host}."
+msgstr ""
+
+msgid "You're receiving this email because you have been assigned an item on %{host}."
+msgstr ""
+
+msgid "You're receiving this email because you have been mentioned on %{host}."
+msgstr ""
+
+msgid "You've already enabled two-factor authentication using one time password authenticators. In order to register a different device, you must first disable two-factor authentication."
+msgstr ""
+
+msgid "YouTube"
+msgstr ""
+
+msgid "Your %{host} account was signed in to from a new location"
+msgstr ""
+
+msgid "Your %{strong}%{plan_name}%{strong_close} subscription for %{strong}%{namespace_name}%{strong_close} will expire on %{strong}%{expires_on}%{strong_close}."
+msgstr ""
+
+msgid "Your %{strong}%{plan_name}%{strong_close} subscription will expire on %{strong}%{expires_on}%{strong_close}. After that, you will not to be able to create issues or merge requests as well as many other features."
+msgstr ""
+
+msgid "Your CSV export has started. It will be emailed to %{email} when complete."
+msgstr ""
+
+msgid "Your CSV export of %{issues_count} from project %{project_link} has been added to this email as an attachment."
+msgstr ""
+
+msgid "Your CSV export of %{written_count} from project %{project_name} (%{project_url}) has been added to this email as an attachment."
+msgstr ""
+
+msgid "Your Commit Email will be used for web based operations, such as edits and merges."
+msgstr ""
+
+msgid "Your Default Notification Email will be used for account notifications if a %{openingTag}group-specific email address%{closingTag} is not set."
+msgstr ""
+
+msgid "Your DevOps Score gives an overview of how you are using GitLab from a feature perspective. View how you compare with other organizations, discover features you are not using, and learn best practices through blog posts and white papers."
+msgstr ""
+
+msgid "Your GPG keys (%{count})"
+msgstr ""
+
+msgid "Your GitLab group"
+msgstr ""
+
+msgid "Your Gitlab Gold trial will last 30 days after which point you can keep your free Gitlab account forever. We just need some additional information to activate your trial."
+msgstr ""
+
+msgid "Your Groups"
+msgstr ""
+
+msgid "Your License"
+msgstr ""
+
+msgid "Your Personal Access Tokens will expire in %{days_to_expire} days or less"
+msgstr ""
+
+msgid "Your Primary Email will be used for avatar detection."
+msgstr ""
+
+msgid "Your Projects (default)"
+msgstr ""
+
+msgid "Your Projects' Activity"
+msgstr ""
+
+msgid "Your Public Email will be displayed on your public profile."
+msgstr ""
+
+msgid "Your SSH keys (%{count})"
+msgstr ""
+
+msgid "Your To-Do List"
+msgstr ""
+
+msgid "Your U2F device did not send a valid JSON response."
+msgstr ""
+
+msgid "Your U2F device needs to be set up. Plug it in (if not already) and click the button on the left."
+msgstr ""
+
+msgid "Your U2F device was registered!"
+msgstr ""
+
+msgid "Your access request to the %{source_type} has been withdrawn."
+msgstr ""
+
+msgid "Your account has been deactivated by your administrator. Please log back in to reactivate your account."
+msgstr ""
+
+msgid "Your account is locked."
+msgstr ""
+
+msgid "Your account uses dedicated credentials for the \"%{group_name}\" group and can only be updated through SSO."
+msgstr ""
+
+msgid "Your applications (%{size})"
+msgstr ""
+
+msgid "Your authorized applications"
+msgstr ""
+
+msgid "Your browser doesn't support U2F. Please use Google Chrome desktop (version 41 or newer)."
+msgstr ""
+
+msgid "Your changes can be committed to %{branch_name} because a merge request is open."
+msgstr ""
+
+msgid "Your changes have been committed. Commit %{commitId} %{commitStats}"
+msgstr ""
+
+msgid "Your changes have been saved"
+msgstr ""
+
+msgid "Your changes have been successfully committed."
+msgstr ""
+
+msgid "Your comment could not be submitted because %{error}"
+msgstr ""
+
+msgid "Your comment could not be submitted! Please check your network connection and try again."
+msgstr ""
+
+msgid "Your comment could not be updated! Please check your network connection and try again."
+msgstr ""
+
+msgid "Your comment will be discarded."
+msgstr ""
+
+msgid "Your custom stage '%{title}' was created"
+msgstr ""
+
+msgid "Your dashboard has been copied. You can %{web_ide_link_start}edit it here%{web_ide_link_end}."
+msgstr ""
+
+msgid "Your dashboard has been updated. You can %{web_ide_link_start}edit it here%{web_ide_link_end}."
+msgstr ""
+
+msgid "Your deployment services will be broken, you will need to manually fix the services after renaming."
+msgstr ""
+
+msgid "Your device was successfully set up! Give it a name and register it with the GitLab server."
+msgstr ""
+
+msgid "Your first project"
+msgstr ""
+
+msgid "Your groups"
+msgstr ""
+
+msgid "Your instance has %{remaining_user_count} users remaining of the %{total_user_count} included in your subscription. You can add more users than the number included in your license, and we will include the overage in your next bill."
+msgstr ""
+
+msgid "Your instance is approaching its licensed user count"
+msgstr ""
+
+msgid "Your issues are being imported. Once finished, you'll get a confirmation email."
+msgstr ""
+
+msgid "Your issues will be imported in the background. Once finished, you'll get a confirmation email."
+msgstr ""
+
+msgid "Your license is valid from"
+msgstr ""
+
+msgid "Your license will be included in your GitLab backup and will survive upgrades, so in normal usage you should never need to re-upload your <code>.gitlab-license</code> file."
+msgstr ""
+
+msgid "Your message here"
+msgstr ""
+
+msgid "Your name"
+msgstr ""
+
+msgid "Your new %{type}"
+msgstr ""
+
+msgid "Your new SCIM token"
+msgstr ""
+
+msgid "Your new personal access token has been created."
+msgstr ""
+
+msgid "Your new project access token has been created."
+msgstr ""
+
+msgid "Your password isn't required to view this page. If a password or any other personal details are requested, please contact your administrator to report abuse."
+msgstr ""
+
+msgid "Your password reset token has expired."
+msgstr ""
+
+msgid "Your profile"
+msgstr ""
+
+msgid "Your project limit is %{limit} projects! Please contact your administrator to increase it"
+msgstr ""
+
+msgid "Your projects"
+msgstr ""
+
+msgid "Your request for access could not be processed: %{error_meesage}"
+msgstr ""
+
+msgid "Your request for access has been queued for review."
+msgstr ""
+
+msgid "Your response has been recorded."
+msgstr ""
+
+msgid "Your search didn't match any commits."
+msgstr ""
+
+msgid "Your subscription expired!"
+msgstr ""
+
+msgid "Your subscription has been downgraded."
+msgstr ""
+
+msgid "Your subscription will automatically renew in %{remaining_days}."
+msgstr ""
+
+msgid "Your subscription will expire in %{remaining_days}."
+msgstr ""
+
+msgid "Zoom meeting added"
+msgstr ""
+
+msgid "Zoom meeting removed"
+msgstr ""
+
+msgid "[No reason]"
+msgstr ""
+
+msgid "a deleted user"
+msgstr ""
+
+msgid "a design"
+msgstr ""
+
+msgid "about 1 hour"
+msgid_plural "about %d hours"
+msgstr[0] ""
+
+msgid "activated"
+msgstr ""
+
+msgid "added %{created_at_timeago}"
+msgstr ""
+
+msgid "added a Zoom call to this issue"
+msgstr ""
+
+msgid "ago"
+msgstr ""
+
+msgid "alert"
+msgstr ""
+
+msgid "allowed to fail"
+msgstr ""
+
+msgid "already being used for another group or project %{timebox_name}."
+msgstr ""
+
+msgid "already has a \"created\" issue link"
+msgstr ""
+
+msgid "already shared with this group"
+msgstr ""
+
+msgid "among other things"
+msgstr ""
+
+msgid "any-approver for the merge request already exists"
+msgstr ""
+
+msgid "any-approver for the project already exists"
+msgstr ""
+
+msgid "archived"
+msgstr ""
+
+msgid "assign yourself"
+msgstr ""
+
+msgid "at risk"
+msgstr ""
+
+msgid "attach a new file"
+msgstr ""
+
+msgid "authored"
+msgstr ""
+
+msgid "blocks"
+msgstr ""
+
+msgid "branch name"
+msgstr ""
+
+msgid "by"
+msgstr ""
+
+msgid "by %{user}"
+msgstr ""
+
+msgid "cannot be changed if a personal project has container registry tags."
+msgstr ""
+
+msgid "cannot be enabled unless all domains have TLS certificates"
+msgstr ""
+
+msgid "cannot be modified"
+msgstr ""
+
+msgid "cannot block others"
+msgstr ""
+
+msgid "cannot contain HTML/XML tags, including any word between angle brackets (<,>)."
+msgstr ""
+
+msgid "cannot include leading slash or directory traversal."
+msgstr ""
+
+msgid "cannot itself be blocked"
+msgstr ""
+
+msgid "cannot merge"
+msgstr ""
+
+msgid "ciReport|%{linkStartTag}Learn more about Container Scanning %{linkEndTag}"
+msgstr ""
+
+msgid "ciReport|%{linkStartTag}Learn more about DAST %{linkEndTag}"
+msgstr ""
+
+msgid "ciReport|%{linkStartTag}Learn more about Dependency Scanning %{linkEndTag}"
+msgstr ""
+
+msgid "ciReport|%{linkStartTag}Learn more about SAST %{linkEndTag}"
+msgstr ""
+
+msgid "ciReport|%{linkStartTag}Learn more about Secret Scanning %{linkEndTag}"
+msgstr ""
+
+msgid "ciReport|%{linkStartTag}Learn more about codequality reports %{linkEndTag}"
+msgstr ""
+
+msgid "ciReport|%{remainingPackagesCount} more"
+msgstr ""
+
+msgid "ciReport|%{reportType} is loading"
+msgstr ""
+
+msgid "ciReport|%{reportType}: Loading resulted in an error"
+msgstr ""
+
+msgid "ciReport|(errors when loading results)"
+msgstr ""
+
+msgid "ciReport|(is loading)"
+msgstr ""
+
+msgid "ciReport|(is loading, errors when loading results)"
+msgstr ""
+
+msgid "ciReport|All projects"
+msgstr ""
+
+msgid "ciReport|All scanner types"
+msgstr ""
+
+msgid "ciReport|All severities"
+msgstr ""
+
+msgid "ciReport|Automatically apply the patch in a new branch"
+msgstr ""
+
+msgid "ciReport|Base pipeline codequality artifact not found"
+msgstr ""
+
+msgid "ciReport|Code quality"
+msgstr ""
+
+msgid "ciReport|Container Scanning"
+msgstr ""
+
+msgid "ciReport|Container scanning"
+msgstr ""
+
+msgid "ciReport|Container scanning detects known vulnerabilities in your docker images."
+msgstr ""
+
+msgid "ciReport|Coverage Fuzzing"
+msgstr ""
+
+msgid "ciReport|Create a merge request to implement this solution, or download and apply the patch manually."
+msgstr ""
+
+msgid "ciReport|Create issue"
+msgstr ""
+
+msgid "ciReport|DAST"
+msgstr ""
+
+msgid "ciReport|Dependency Scanning"
+msgstr ""
+
+msgid "ciReport|Dependency Scanning detects known vulnerabilities in your source code's dependencies."
+msgstr ""
+
+msgid "ciReport|Dependency scanning"
+msgstr ""
+
+msgid "ciReport|Download patch to resolve"
+msgstr ""
+
+msgid "ciReport|Download the patch to apply it manually"
+msgstr ""
+
+msgid "ciReport|Dynamic Application Security Testing (DAST) detects known vulnerabilities in your web application."
+msgstr ""
+
+msgid "ciReport|Failed to load %{reportName} report"
+msgstr ""
+
+msgid "ciReport|Fixed"
+msgstr ""
+
+msgid "ciReport|Fixed:"
+msgstr ""
+
+msgid "ciReport|Found %{issuesWithCount}"
+msgstr ""
+
+msgid "ciReport|Investigate this vulnerability by creating an issue"
+msgstr ""
+
+msgid "ciReport|Learn more about interacting with security reports"
+msgstr ""
+
+msgid "ciReport|Loading %{reportName} report"
+msgstr ""
+
+msgid "ciReport|Manage licenses"
+msgstr ""
+
+msgid "ciReport|New"
+msgstr ""
+
+msgid "ciReport|No changes to code quality"
+msgstr ""
+
+msgid "ciReport|No changes to performance metrics"
+msgstr ""
+
+msgid "ciReport|No code quality issues found"
+msgstr ""
+
+msgid "ciReport|Performance metrics"
+msgstr ""
+
+msgid "ciReport|Resolve with merge request"
+msgstr ""
+
+msgid "ciReport|SAST"
+msgstr ""
+
+msgid "ciReport|Secret Detection"
+msgstr ""
+
+msgid "ciReport|Secret scanning"
+msgstr ""
+
+msgid "ciReport|Secret scanning detects secrets and credentials vulnerabilities in your source code."
+msgstr ""
+
+msgid "ciReport|Security scanning"
+msgstr ""
+
+msgid "ciReport|Security scanning failed loading any results"
+msgstr ""
+
+msgid "ciReport|Solution"
+msgstr ""
+
+msgid "ciReport|Static Application Security Testing (SAST) detects known vulnerabilities in your source code."
+msgstr ""
+
+msgid "ciReport|There was an error creating the issue. Please try again."
+msgstr ""
+
+msgid "ciReport|There was an error creating the merge request. Please try again."
+msgstr ""
+
+msgid "ciReport|There was an error dismissing the vulnerability. Please try again."
+msgstr ""
+
+msgid "ciReport|There was an error fetching the codequality report."
+msgstr ""
+
+msgid "ciReport|There was an error reverting the dismissal. Please try again."
+msgstr ""
+
+msgid "ciReport|Used by %{packagesString}"
+msgid_plural "ciReport|Used by %{packagesString}, and %{lastPackage}"
+msgstr[0] ""
+
+msgid "ciReport|View full report"
+msgstr ""
+
+msgid "closed issue"
+msgstr ""
+
+msgid "comment"
+msgstr ""
+
+msgid "commented on %{link_to_project}"
+msgstr ""
+
+msgid "commit %{commit_id}"
+msgstr ""
+
+msgid "committed"
+msgstr ""
+
+msgid "confidentiality|You are going to turn off the confidentiality. This means <strong>everyone</strong> will be able to see and leave a comment on this issue."
+msgstr ""
+
+msgid "confidentiality|You are going to turn on the confidentiality. This means that only team members with <strong>at least Reporter access</strong> are able to see and leave comments on the issue."
+msgstr ""
+
+msgid "connecting"
+msgstr ""
+
+msgid "container_name can contain only lowercase letters, digits, '-', and '.' and must start and end with an alphanumeric character"
+msgstr ""
+
+msgid "container_name cannot be larger than %{max_length} chars"
+msgstr ""
+
+msgid "could not read private key, is the passphrase correct?"
+msgstr ""
+
+msgid "created"
+msgstr ""
+
+msgid "created %{timeAgo}"
+msgstr ""
+
+msgid "customize"
+msgstr ""
+
+msgid "data"
+msgstr ""
+
+msgid "date must not be after 9999-12-31"
+msgstr ""
+
+msgid "day"
+msgid_plural "days"
+msgstr[0] ""
+
+msgid "default branch"
+msgstr ""
+
+msgid "deleted"
+msgstr ""
+
+msgid "deploy"
+msgstr ""
+
+msgid "design"
+msgstr ""
+
+msgid "designs"
+msgstr ""
+
+msgid "detached"
+msgstr ""
+
+msgid "disabled"
+msgstr ""
+
+msgid "does not have a supported extension. Only %{extension_list} are supported"
+msgstr ""
+
+msgid "done"
+msgstr ""
+
+msgid "download it"
+msgstr ""
+
+msgid "draft"
+msgid_plural "drafts"
+msgstr[0] ""
+
+msgid "e.g. %{token}"
+msgstr ""
+
+msgid "element is not a hierarchy"
+msgstr ""
+
+msgid "email '%{email}' does not match the allowed domains of %{email_domains}"
+msgstr ""
+
+msgid "email '%{email}' is not a verified email."
+msgstr ""
+
+msgid "enabled"
+msgstr ""
+
+msgid "encrypted: needs to be a :required, :optional or :migrating!"
+msgstr ""
+
+msgid "entries cannot be larger than 255 characters"
+msgstr ""
+
+msgid "entries cannot be nil"
+msgstr ""
+
+msgid "entries cannot contain HTML tags"
+msgstr ""
+
+msgid "epic"
+msgstr ""
+
+msgid "error"
+msgstr ""
+
+msgid "error code:"
+msgstr ""
+
+msgid "estimateCommand|%{slash_command} will update the estimated time with the latest command."
+msgstr ""
+
+msgid "exceeds the limit of %{bytes} bytes"
+msgstr ""
+
+msgid "exceeds the limit of %{bytes} bytes for directory name \"%{dirname}\""
+msgstr ""
+
+msgid "expired on %{timebox_due_date}"
+msgstr ""
+
+msgid "expires on %{timebox_due_date}"
+msgstr ""
+
+msgid "failed"
+msgstr ""
+
+msgid "failed to dismiss associated finding(id=%{finding_id}): %{message}"
+msgstr ""
+
+msgid "file"
+msgid_plural "files"
+msgstr[0] ""
+
+msgid "finding is not found or is already attached to a vulnerability"
+msgstr ""
+
+msgid "for %{link_to_merge_request} with %{link_to_merge_request_source_branch}"
+msgstr ""
+
+msgid "for %{link_to_merge_request} with %{link_to_merge_request_source_branch} into %{link_to_merge_request_target_branch}"
+msgstr ""
+
+msgid "for %{link_to_pipeline_ref}"
+msgstr ""
+
+msgid "for %{ref}"
+msgstr ""
+
+msgid "for this project"
+msgstr ""
+
+msgid "fork this project"
+msgstr ""
+
+msgid "from"
+msgstr ""
+
+msgid "group"
+msgstr ""
+
+msgid "groups"
+msgstr ""
+
+msgid "has already been linked to another vulnerability"
+msgstr ""
+
+msgid "has already been taken"
+msgstr ""
+
+msgid "help"
+msgstr ""
+
+msgid "here"
+msgstr ""
+
+msgid "https://your-bitbucket-server"
+msgstr ""
+
+msgid "image diff"
+msgstr ""
+
+msgid "impersonation token"
+msgstr ""
+
+msgid "impersonation tokens"
+msgstr ""
+
+msgid "import flow"
+msgstr ""
+
+msgid "importing"
+msgstr ""
+
+msgid "in group %{link_to_group}"
+msgstr ""
+
+msgid "in project %{link_to_project}"
+msgstr ""
+
+msgid "index"
+msgstr ""
+
+msgid "instance completed"
+msgid_plural "instances completed"
+msgstr[0] ""
+
+msgid "invalid milestone state `%{state}`"
+msgstr ""
+
+msgid "is"
+msgstr ""
+
+msgid "is already associated to a GitLab Issue. New issue will not be associated."
+msgstr ""
+
+msgid "is an invalid IP address range"
+msgstr ""
+
+msgid "is blocked by"
+msgstr ""
+
+msgid "is enabled."
+msgstr ""
+
+msgid "is invalid because there is downstream lock"
+msgstr ""
+
+msgid "is invalid because there is upstream lock"
+msgstr ""
+
+msgid "is not"
+msgstr ""
+
+msgid "is not a descendant of the Group owning the template"
+msgstr ""
+
+msgid "is not a valid X509 certificate."
+msgstr ""
+
+msgid "is not allowed. Try again with a different email address, or contact your GitLab admin."
+msgstr ""
+
+msgid "is not an email you own"
+msgstr ""
+
+msgid "is not in the group enforcing Group Managed Account"
+msgstr ""
+
+msgid "is read only"
+msgstr ""
+
+msgid "is too long (%{current_value}). The maximum size is %{max_size}."
+msgstr ""
+
+msgid "is too long (maximum is 100 entries)"
+msgstr ""
+
+msgid "is too long (maximum is 1000 entries)"
+msgstr ""
+
+msgid "issue"
+msgstr ""
+
+msgid "issues at risk"
+msgstr ""
+
+msgid "issues need attention"
+msgstr ""
+
+msgid "issues on track"
+msgstr ""
+
+msgid "it is larger than %{limit}"
+msgstr ""
+
+msgid "it is stored as a job artifact"
+msgstr ""
+
+msgid "it is stored externally"
+msgstr ""
+
+msgid "it is stored in LFS"
+msgstr ""
+
+msgid "it is too large"
+msgstr ""
+
+msgid "jigsaw is not defined"
+msgstr ""
+
+msgid "jira.issue.description.content"
+msgstr ""
+
+msgid "jira.issue.summary"
+msgstr ""
+
+msgid "latest"
+msgstr ""
+
+msgid "latest deployment"
+msgstr ""
+
+msgid "latest version"
+msgstr ""
+
+msgid "leave %{group_name}"
+msgstr ""
+
+msgid "less than a minute"
+msgstr ""
+
+msgid "level: %{level}"
+msgstr ""
+
+msgid "limit of %{project_limit} reached"
+msgstr ""
+
+msgid "load it anyway"
+msgstr ""
+
+msgid "loading"
+msgstr ""
+
+msgid "locked by %{path_lock_user_name} %{created_at}"
+msgstr ""
+
+msgid "log in"
+msgstr ""
+
+msgid "manual"
+msgstr ""
+
+msgid "math|The math in this entry is taking too long to render and may not be displayed as expected. For performance reasons, math blocks are also limited to %{maxChars} characters. Consider splitting up large formulae, splitting math blocks among multiple entries, or using an image instead."
+msgstr ""
+
+msgid "math|There was an error rendering this math block"
+msgstr ""
+
+msgid "merge request"
+msgid_plural "merge requests"
+msgstr[0] ""
+
+msgid "merged %{time_ago}"
+msgstr ""
+
+msgid "missing"
+msgstr ""
+
+msgid "most recent deployment"
+msgstr ""
+
+msgid "mrWidgetCommitsAdded|%{commitCount} and %{mergeCommitCount} will be added to %{targetBranch}."
+msgstr ""
+
+msgid "mrWidgetCommitsAdded|%{commitCount} will be added to %{targetBranch}."
+msgstr ""
+
+msgid "mrWidgetCommitsAdded|1 merge commit"
+msgstr ""
+
+msgid "mrWidgetNothingToMerge|Currently there are no changes in this merge request's source branch. Please push new commits or use a different branch."
+msgstr ""
+
+msgid "mrWidgetNothingToMerge|Interested parties can even contribute by pushing commits if they want to."
+msgstr ""
+
+msgid "mrWidgetNothingToMerge|Merge requests are a place to propose changes you have made to a project and discuss those changes with others."
+msgstr ""
+
+msgid "mrWidget| Please restore it or use a different %{missingBranchName} branch"
+msgstr ""
+
+msgid "mrWidget|%{link_start}Learn more about resolving conflicts%{link_end}"
+msgstr ""
+
+msgid "mrWidget|%{metricsLinkStart} Memory %{metricsLinkEnd} usage %{emphasisStart} decreased %{emphasisEnd} from %{memoryFrom}MB to %{memoryTo}MB"
+msgstr ""
+
+msgid "mrWidget|%{metricsLinkStart} Memory %{metricsLinkEnd} usage %{emphasisStart} increased %{emphasisEnd} from %{memoryFrom}MB to %{memoryTo}MB"
+msgstr ""
+
+msgid "mrWidget|%{metricsLinkStart} Memory %{metricsLinkEnd} usage is %{emphasisStart} unchanged %{emphasisEnd} at %{memoryFrom}MB"
+msgstr ""
+
+msgid "mrWidget|%{prefixToLinkStart}No pipeline%{prefixToLinkEnd} %{addPipelineLinkStart}Add the .gitlab-ci.yml file%{addPipelineLinkEnd} to create one."
+msgstr ""
+
+msgid "mrWidget|Added to the merge train by"
+msgstr ""
+
+msgid "mrWidget|Allows commits from members who can merge to the target branch"
+msgstr ""
+
+msgid "mrWidget|An error occurred while removing your approval."
+msgstr ""
+
+msgid "mrWidget|An error occurred while retrieving approval data for this merge request."
+msgstr ""
+
+msgid "mrWidget|An error occurred while submitting your approval."
+msgstr ""
+
+msgid "mrWidget|Approval password is invalid."
+msgstr ""
+
+msgid "mrWidget|Approve"
+msgstr ""
+
+msgid "mrWidget|Approve additionally"
+msgstr ""
+
+msgid "mrWidget|Approved by"
+msgstr ""
+
+msgid "mrWidget|Are you adding technical debt or code vulnerabilities?"
+msgstr ""
+
+msgid "mrWidget|Cancel automatic merge"
+msgstr ""
+
+msgid "mrWidget|Check out branch"
+msgstr ""
+
+msgid "mrWidget|Checking ability to merge automatically…"
+msgstr ""
+
+msgid "mrWidget|Cherry-pick"
+msgstr ""
+
+msgid "mrWidget|Cherry-pick this merge request in a new merge request"
+msgstr ""
+
+msgid "mrWidget|Closed"
+msgstr ""
+
+msgid "mrWidget|Closed by"
+msgstr ""
+
+msgid "mrWidget|Closes"
+msgstr ""
+
+msgid "mrWidget|Create an issue to resolve them later"
+msgstr ""
+
+msgid "mrWidget|Delete source branch"
+msgstr ""
+
+msgid "mrWidget|Deployment statistics are not available currently"
+msgstr ""
+
+msgid "mrWidget|Did not close"
+msgstr ""
+
+msgid "mrWidget|Email patches"
+msgstr ""
+
+msgid "mrWidget|Failed to load deployment statistics"
+msgstr ""
+
+msgid "mrWidget|Fast-forward merge is not possible. To merge this request, first rebase locally."
+msgstr ""
+
+msgid "mrWidget|Fork merge requests do not create merge request pipelines which validate a post merge result"
+msgstr ""
+
+msgid "mrWidget|If the %{branch} branch exists in your local repository, you can merge this merge request manually using the"
+msgstr ""
+
+msgid "mrWidget|If the %{missingBranchName} branch exists in your local repository, you can merge this merge request manually using the command line"
+msgstr ""
+
+msgid "mrWidget|In the merge train at position %{mergeTrainPosition}"
+msgstr ""
+
+msgid "mrWidget|Loading deployment statistics"
+msgstr ""
+
+msgid "mrWidget|Mentions"
+msgstr ""
+
+msgid "mrWidget|Merge"
+msgstr ""
+
+msgid "mrWidget|Merge failed."
+msgstr ""
+
+msgid "mrWidget|Merge failed: %{mergeError}. Please try again."
+msgstr ""
+
+msgid "mrWidget|Merge locally"
+msgstr ""
+
+msgid "mrWidget|Merge request approved."
+msgstr ""
+
+msgid "mrWidget|Merged by"
+msgstr ""
+
+msgid "mrWidget|More information"
+msgstr ""
+
+msgid "mrWidget|No approval required"
+msgstr ""
+
+msgid "mrWidget|No approval required; you can still approve"
+msgstr ""
+
+msgid "mrWidget|Open in Web IDE"
+msgstr ""
+
+msgid "mrWidget|Pipeline blocked. The pipeline for this merge request requires a manual action to proceed"
+msgstr ""
+
+msgid "mrWidget|Plain diff"
+msgstr ""
+
+msgid "mrWidget|Ready to be merged automatically. Ask someone with write access to this repository to merge this request"
+msgstr ""
+
+msgid "mrWidget|Refresh"
+msgstr ""
+
+msgid "mrWidget|Refresh now"
+msgstr ""
+
+msgid "mrWidget|Refreshing now"
+msgstr ""
+
+msgid "mrWidget|Remove from merge train"
+msgstr ""
+
+msgid "mrWidget|Request to merge"
+msgstr ""
+
+msgid "mrWidget|Resolve WIP status"
+msgstr ""
+
+msgid "mrWidget|Resolve conflicts"
+msgstr ""
+
+msgid "mrWidget|Resolve these conflicts or ask someone with write access to this repository to merge it locally"
+msgstr ""
+
+msgid "mrWidget|Revert"
+msgstr ""
+
+msgid "mrWidget|Revert this merge request in a new merge request"
+msgstr ""
+
+msgid "mrWidget|Revoke approval"
+msgstr ""
+
+msgid "mrWidget|Set by"
+msgstr ""
+
+msgid "mrWidget|The changes were merged into"
+msgstr ""
+
+msgid "mrWidget|The changes were not merged into"
+msgstr ""
+
+msgid "mrWidget|The changes will be merged into"
+msgstr ""
+
+msgid "mrWidget|The pipeline for this merge request failed. Please retry the job or push a new commit to fix the failure"
+msgstr ""
+
+msgid "mrWidget|The source branch HEAD has recently changed. Please reload the page and review the changes before merging"
+msgstr ""
+
+msgid "mrWidget|The source branch has been deleted"
+msgstr ""
+
+msgid "mrWidget|The source branch is %{commitsBehindLinkStart}%{commitsBehind}%{commitsBehindLinkEnd} the target branch"
+msgstr ""
+
+msgid "mrWidget|The source branch is being deleted"
+msgstr ""
+
+msgid "mrWidget|The source branch will be deleted"
+msgstr ""
+
+msgid "mrWidget|The source branch will not be deleted"
+msgstr ""
+
+msgid "mrWidget|There are merge conflicts"
+msgstr ""
+
+msgid "mrWidget|There are unresolved threads. Please resolve these threads"
+msgstr ""
+
+msgid "mrWidget|This feature merges changes from the target branch to the source branch. You cannot use this feature since the source branch is protected."
+msgstr ""
+
+msgid "mrWidget|This merge request failed to be merged automatically"
+msgstr ""
+
+msgid "mrWidget|This merge request is in the process of being merged"
+msgstr ""
+
+msgid "mrWidget|This merge request will be added to the merge train when pipeline %{linkStart}#%{pipelineId}%{linkEnd} succeeds."
+msgstr ""
+
+msgid "mrWidget|This merge request will start a merge train when pipeline %{linkStart}#%{pipelineId}%{linkEnd} succeeds."
+msgstr ""
+
+msgid "mrWidget|This project is archived, write access has been disabled"
+msgstr ""
+
+msgid "mrWidget|To approve this merge request, please enter your password. This project requires all approvals to be authenticated."
+msgstr ""
+
+msgid "mrWidget|Use %{linkStart}CI pipelines to test your code%{linkEnd}, simply add a GitLab CI configuration file to your project. It only takes a minute to make your code more secure and robust."
+msgstr ""
+
+msgid "mrWidget|When this merge request is ready, remove the WIP: prefix from the title to allow it to be merged"
+msgstr ""
+
+msgid "mrWidget|You are not allowed to edit this project directly. Please fork to make changes."
+msgstr ""
+
+msgid "mrWidget|You can delete the source branch now"
+msgstr ""
+
+msgid "mrWidget|You can merge this merge request manually using the"
+msgstr ""
+
+msgid "mrWidget|Your password"
+msgstr ""
+
+msgid "mrWidget|branch does not exist."
+msgstr ""
+
+msgid "mrWidget|command line"
+msgstr ""
+
+msgid "mrWidget|into"
+msgstr ""
+
+msgid "mrWidget|to be added to the merge train when the pipeline succeeds"
+msgstr ""
+
+msgid "mrWidget|to be merged automatically when the pipeline succeeds"
+msgstr ""
+
+msgid "mrWidget|to start a merge train when the pipeline succeeds"
+msgstr ""
+
+msgid "must be greater than start date"
+msgstr ""
+
+msgid "must contain only valid frameworks"
+msgstr ""
+
+msgid "my-awesome-group"
+msgstr ""
+
+msgid "n/a"
+msgstr ""
+
+msgid "need attention"
+msgstr ""
+
+msgid "needs to be between 10 minutes and 1 month"
+msgstr ""
+
+msgid "never"
+msgstr ""
+
+msgid "never expires"
+msgstr ""
+
+msgid "new merge request"
+msgstr ""
+
+msgid "no contributions"
+msgstr ""
+
+msgid "no expiration"
+msgstr ""
+
+msgid "no one can merge"
+msgstr ""
+
+msgid "none"
+msgstr ""
+
+msgid "not found"
+msgstr ""
+
+msgid "notification emails"
+msgstr ""
+
+msgid "nounSeries|%{firstItem} and %{lastItem}"
+msgstr ""
+
+msgid "nounSeries|%{item}"
+msgstr ""
+
+msgid "nounSeries|%{item}, %{nextItem}"
+msgstr ""
+
+msgid "nounSeries|%{item}, and %{lastItem}"
+msgstr ""
+
+msgid "on track"
+msgstr ""
+
+msgid "open issue"
+msgstr ""
+
+msgid "opened %{timeAgoString} by %{user}"
+msgstr ""
+
+msgid "opened %{timeAgo}"
+msgstr ""
+
+msgid "or"
+msgstr ""
+
+msgid "out of %d total test"
+msgid_plural "out of %d total tests"
+msgstr[0] ""
+
+msgid "parent"
+msgid_plural "parents"
+msgstr[0] ""
+
+msgid "password"
+msgstr ""
+
+msgid "pending comment"
+msgstr ""
+
+msgid "pending removal"
+msgstr ""
+
+msgid "per day"
+msgstr ""
+
+msgid "personal access token"
+msgstr ""
+
+msgid "personal access tokens"
+msgstr ""
+
+msgid "pipeline"
+msgstr ""
+
+msgid "pod_name can contain only lowercase letters, digits, '-', and '.' and must start and end with an alphanumeric character"
+msgstr ""
+
+msgid "pod_name cannot be larger than %{max_length} chars"
+msgstr ""
+
+msgid "point"
+msgid_plural "points"
+msgstr[0] ""
+
+msgid "private"
+msgstr ""
+
+msgid "private key does not match certificate."
+msgstr ""
+
+msgid "processing"
+msgstr ""
+
+msgid "project"
+msgid_plural "projects"
+msgstr[0] ""
+
+msgid "project access token"
+msgstr ""
+
+msgid "project access tokens"
+msgstr ""
+
+msgid "project avatar"
+msgstr ""
+
+msgid "projects"
+msgstr ""
+
+msgid "push to your repository, create pipelines, create issues or add comments. To reduce storage capacity, delete unused repositories, artifacts, wikis, issues, and pipelines."
+msgstr ""
+
+msgid "quick actions"
+msgstr ""
+
+msgid "register"
+msgstr ""
+
+msgid "relates to"
+msgstr ""
+
+msgid "released %{time}"
+msgstr ""
+
+msgid "remaining"
+msgstr ""
+
+msgid "remove"
+msgstr ""
+
+msgid "remove due date"
+msgstr ""
+
+msgid "remove weight"
+msgstr ""
+
+msgid "removed a Zoom call from this issue"
+msgstr ""
+
+msgid "rendered diff"
+msgstr ""
+
+msgid "reply"
+msgid_plural "replies"
+msgstr[0] ""
+
+msgid "reset it."
+msgstr ""
+
+msgid "resolved the corresponding error and closed the issue."
+msgstr ""
+
+msgid "revised"
+msgstr ""
+
+msgid "satisfied"
+msgstr ""
+
+msgid "score"
+msgstr ""
+
+msgid "security Reports|There was an error creating the merge request"
+msgstr ""
+
+msgid "settings saved, but not activated"
+msgstr ""
+
+msgid "severity|Critical"
+msgstr ""
+
+msgid "severity|High"
+msgstr ""
+
+msgid "severity|Info"
+msgstr ""
+
+msgid "severity|Low"
+msgstr ""
+
+msgid "severity|Medium"
+msgstr ""
+
+msgid "severity|None"
+msgstr ""
+
+msgid "severity|Unknown"
+msgstr ""
+
+msgid "should be an array of %{object_name} objects"
+msgstr ""
+
+msgid "should be greater than or equal to %{access} inherited membership from group %{group_name}"
+msgstr ""
+
+msgid "show %{count} more"
+msgstr ""
+
+msgid "show fewer"
+msgstr ""
+
+msgid "show less"
+msgstr ""
+
+msgid "sign in"
+msgstr ""
+
+msgid "sort:"
+msgstr ""
+
+msgid "source"
+msgstr ""
+
+msgid "source diff"
+msgstr ""
+
+msgid "specified top is not part of the tree"
+msgstr ""
+
+msgid "spendCommand|%{slash_command} will update the sum of the time spent."
+msgstr ""
+
+msgid "started"
+msgstr ""
+
+msgid "started a discussion on %{design_link}"
+msgstr ""
+
+msgid "started on %{timebox_start_date}"
+msgstr ""
+
+msgid "starts on %{timebox_start_date}"
+msgstr ""
+
+msgid "stuck"
+msgstr ""
+
+msgid "success"
+msgstr ""
+
+msgid "suggestPipeline|1/2: Choose a template"
+msgstr ""
+
+msgid "suggestPipeline|2/2: Commit your changes"
+msgstr ""
+
+msgid "suggestPipeline|Choose %{boldStart}Code Quality%{boldEnd} to add a pipeline that tests the quality of your code."
+msgstr ""
+
+msgid "suggestPipeline|The template is ready! You can now commit it to create your first pipeline."
+msgstr ""
+
+msgid "suggestPipeline|We’re adding a GitLab CI configuration file to add a pipeline to the project. You could create it manually, but we recommend that you start with a GitLab template that works out of the box."
+msgstr ""
+
+msgid "syntax is correct"
+msgstr ""
+
+msgid "syntax is incorrect"
+msgstr ""
+
+msgid "tag name"
+msgstr ""
+
+msgid "the following issue(s)"
+msgstr ""
+
+msgid "this document"
+msgstr ""
+
+msgid "to help your contributors communicate effectively!"
+msgstr ""
+
+msgid "to list"
+msgstr ""
+
+msgid "toggle collapse"
+msgstr ""
+
+msgid "toggle dropdown"
+msgstr ""
+
+msgid "triggered"
+msgstr ""
+
+msgid "unicode domains should use IDNA encoding"
+msgstr ""
+
+msgid "updated"
+msgstr ""
+
+msgid "updated %{timeAgo}"
+msgstr ""
+
+msgid "updated %{time_ago}"
+msgstr ""
+
+msgid "uploaded"
+msgstr ""
+
+msgid "uploads"
+msgstr ""
+
+msgid "user avatar"
+msgstr ""
+
+msgid "username"
+msgstr ""
+
+msgid "uses Kubernetes clusters to deploy your code!"
+msgstr ""
+
+msgid "v%{version} published %{timeAgo}"
+msgstr ""
+
+msgid "verify ownership"
+msgstr ""
+
+msgid "version %{versionIndex}"
+msgstr ""
+
+msgid "via %{closed_via}"
+msgstr ""
+
+msgid "via merge request %{link}"
+msgstr ""
+
+msgid "view it on GitLab"
+msgstr ""
+
+msgid "view the blob"
+msgstr ""
+
+msgid "view the source"
+msgstr ""
+
+msgid "vulnerability|Add a comment"
+msgstr ""
+
+msgid "vulnerability|Add a comment or reason for dismissal"
+msgstr ""
+
+msgid "vulnerability|Add comment"
+msgstr ""
+
+msgid "vulnerability|Add comment & dismiss"
+msgstr ""
+
+msgid "vulnerability|Dismiss vulnerability"
+msgstr ""
+
+msgid "vulnerability|Save comment"
+msgstr ""
+
+msgid "vulnerability|Undo dismiss"
+msgstr ""
+
+msgid "vulnerability|dismissed"
+msgstr ""
+
+msgid "wiki page"
+msgstr ""
+
+msgid "will be released %{time}"
+msgstr ""
+
+msgid "with %{additions} additions, %{deletions} deletions."
+msgstr ""
+
+msgid "with expiry changing from %{old_expiry} to %{new_expiry}"
+msgstr ""
+
+msgid "with expiry remaining unchanged at %{old_expiry}"
+msgstr ""
+
+msgid "yaml invalid"
+msgstr ""
+
diff --git a/locale/is_IS/gitlab.po b/locale/is_IS/gitlab.po
index 436367793f1..f5d7e49a39f 100644
--- a/locale/is_IS/gitlab.po
+++ b/locale/is_IS/gitlab.po
@@ -2,7 +2,7 @@ msgid ""
msgstr ""
"Project-Id-Version: gitlab-ee\n"
"Report-Msgid-Bugs-To: \n"
-"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Last-Translator: \n"
"Language-Team: Icelandic\n"
"Language: is_IS\n"
"MIME-Version: 1.0\n"
@@ -10,9 +10,11 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Crowdin-Project: gitlab-ee\n"
+"X-Crowdin-Project-ID: 288872\n"
"X-Crowdin-Language: is\n"
"X-Crowdin-File: /master/locale/gitlab.pot\n"
-"PO-Revision-Date: 2020-06-08 15:09\n"
+"X-Crowdin-File-ID: 6\n"
+"PO-Revision-Date: 2020-07-02 01:20\n"
msgid " %{start} to %{end}"
msgstr ""
@@ -197,6 +199,11 @@ msgid_plural "%d metrics"
msgstr[0] ""
msgstr[1] ""
+msgid "%d milestone"
+msgid_plural "%d milestones"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "%d minute"
msgid_plural "%d minutes"
msgstr[0] ""
@@ -242,11 +249,6 @@ msgid_plural "%d unresolved threads"
msgstr[0] ""
msgstr[1] ""
-msgid "%d url scanned"
-msgid_plural "%d urls scanned"
-msgstr[0] ""
-msgstr[1] ""
-
msgid "%d vulnerability"
msgid_plural "%d vulnerabilities"
msgstr[0] ""
@@ -324,9 +326,15 @@ msgstr[1] ""
msgid "%{count} related %{pluralized_subject}: %{links}"
msgstr ""
+msgid "%{dashboard_path} could not be found."
+msgstr ""
+
msgid "%{days} days until tags are automatically removed"
msgstr ""
+msgid "%{deployLinkStart}Use a template to deploy to ECS%{deployLinkEnd}, or use a docker image to %{commandsLinkStart}run AWS commands in GitLab CI/CD%{commandsLinkEnd}."
+msgstr ""
+
msgid "%{description}- Sentry event: %{errorUrl}- First seen: %{firstSeen}- Last seen: %{lastSeen} %{countLabel}: %{count}%{userCountLabel}: %{userCount}"
msgstr ""
@@ -369,12 +377,18 @@ msgstr ""
msgid "%{host} sign-in from new location"
msgstr ""
-msgid "%{icon}You are about to add %{usersTag} people to the discussion. Proceed with caution."
+msgid "%{icon}You are about to add %{usersTag} people to the discussion. They will all receive a notification."
+msgstr ""
+
+msgid "%{integrations_link_start}Integrations%{link_end} enable you to make third-party applications part of your GitLab workflow. If the available integrations don't meet your needs, consider using a %{webhooks_link_start}webhook%{link_end}."
msgstr ""
msgid "%{issuableType} will be removed! Are you sure?"
msgstr ""
+msgid "%{issuesCount} issues in this group"
+msgstr ""
+
msgid "%{issuesSize} issues"
msgstr ""
@@ -384,7 +398,7 @@ msgstr ""
msgid "%{labelStart}Class:%{labelEnd} %{class}"
msgstr ""
-msgid "%{labelStart}Confidence:%{labelEnd} %{confidence}"
+msgid "%{labelStart}Evidence:%{labelEnd} %{evidence}"
msgstr ""
msgid "%{labelStart}File:%{labelEnd} %{file}"
@@ -402,6 +416,9 @@ msgstr ""
msgid "%{labelStart}Report Type:%{labelEnd} %{reportType}"
msgstr ""
+msgid "%{labelStart}Scanner:%{labelEnd} %{scanner}"
+msgstr ""
+
msgid "%{labelStart}Severity:%{labelEnd} %{severity}"
msgstr ""
@@ -426,9 +443,6 @@ msgstr ""
msgid "%{link_start}Read more%{link_end} about role permissions"
msgstr ""
-msgid "%{link} can be used for binding events when something is happening within the project."
-msgstr ""
-
msgid "%{listToShow}, and %{awardsListLength} more."
msgstr ""
@@ -462,6 +476,9 @@ msgstr ""
msgid "%{name} found %{resultsString}"
msgstr ""
+msgid "%{name} is already being used for another emoji"
+msgstr ""
+
msgid "%{name} is scheduled for %{action}"
msgstr ""
@@ -509,6 +526,9 @@ msgstr ""
msgid "%{primary} (%{secondary})"
msgstr ""
+msgid "%{ref} cannot be added: %{error}"
+msgstr ""
+
msgid "%{releases} release"
msgid_plural "%{releases} releases"
msgstr[0] ""
@@ -517,13 +537,37 @@ msgstr[1] ""
msgid "%{remaining_approvals} left"
msgstr ""
-msgid "%{retryButtonStart}Try again%{retryButtonEnd} or %{newFileButtonStart}attach a new file%{newFileButtonEnd}"
+msgid "%{reportType} %{status} detected %{critical} critical and %{high} high severity vulnerabilities out of %{total}."
+msgstr ""
+
+msgid "%{reportType} %{status} detected %{critical} critical and %{high} high severity vulnerabilities."
+msgstr ""
+
+msgid "%{reportType} %{status} detected %{critical} critical severity vulnerabilities out of %{total}."
+msgstr ""
+
+msgid "%{reportType} %{status} detected %{critical} critical severity vulnerability."
+msgid_plural "%{reportType} %{status} detected %{critical} critical severity vulnerabilities."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{reportType} %{status} detected %{high} high severity vulnerabilities out of %{total}."
msgstr ""
-msgid "%{screenreaderOnlyStart}Keyboard shorcuts%{screenreaderOnlyEnd} Disabled"
+msgid "%{reportType} %{status} detected %{high} high severity vulnerability."
+msgid_plural "%{reportType} %{status} detected %{high} high severity vulnerabilities."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{reportType} %{status} detected %{other} vulnerability."
+msgid_plural "%{reportType} %{status} detected %{other} vulnerabilities."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{reportType} %{status} detected no new vulnerabilities."
msgstr ""
-msgid "%{screenreaderOnlyStart}Keyboard shorcuts%{screenreaderOnlyEnd} Enabled"
+msgid "%{retryButtonStart}Try again%{retryButtonEnd} or %{newFileButtonStart}attach a new file%{newFileButtonEnd}"
msgstr ""
msgid "%{service_title} %{message}."
@@ -620,6 +664,12 @@ msgstr ""
msgid "%{token}..."
msgstr ""
+msgid "%{totalCpu} (%{freeSpacePercentage}%{percentSymbol} free)"
+msgstr ""
+
+msgid "%{totalMemory} (%{freeSpacePercentage}%{percentSymbol} free)"
+msgstr ""
+
msgid "%{totalWeight} total weight"
msgstr ""
@@ -650,6 +700,12 @@ msgstr ""
msgid "%{verb} %{time_spent_value} spent time."
msgstr ""
+msgid "%{webhooks_link_start}%{webhook_type}%{link_end} enable you to send notifications to web applications in response to events in a group or project."
+msgstr ""
+
+msgid "%{webhooks_link_start}%{webhook_type}%{link_end} enable you to send notifications to web applications in response to events in a group or project. We recommend using an %{integrations_link_start}integration%{link_end} in preference to a webhook."
+msgstr ""
+
msgid "'%{level}' is not a valid visibility level"
msgstr ""
@@ -676,9 +732,6 @@ msgstr ""
msgid "(No changes)"
msgstr ""
-msgid "(Show all)"
-msgstr ""
-
msgid "(check progress)"
msgstr ""
@@ -745,6 +798,9 @@ msgstr ""
msgid "0 for unlimited"
msgstr ""
+msgid "0 for unlimited, only effective with remote storage enabled."
+msgstr ""
+
msgid "1 %{type} addition"
msgid_plural "%{count} %{type} additions"
msgstr[0] ""
@@ -892,6 +948,12 @@ msgstr ""
msgid "<code>\"johnsmith@example.com\": \"johnsmith@example.com\"</code> will add \"By <a href=\"#\">johnsmith@example.com</a>\" to all issues and comments originally created by johnsmith@example.com. By default, the email address or username is masked to ensure the user's privacy. Use this option if you want to show the full email address."
msgstr ""
+msgid "<code>Masked</code> to prevent the values from being displayed in job logs (must match certain regexp requirements)."
+msgstr ""
+
+msgid "<code>Protected</code> to expose them to protected branches or tags only."
+msgstr ""
+
msgid "<namespace / project>"
msgstr ""
@@ -919,9 +981,6 @@ msgstr ""
msgid "A .NET Core console application template, customizable for any .NET Core project"
msgstr ""
-msgid "A DAG must have two dependent jobs to be visualized on this tab."
-msgstr ""
-
msgid "A GitBook site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
msgstr ""
@@ -952,6 +1011,12 @@ msgstr ""
msgid "A deleted user"
msgstr ""
+msgid "A file has been changed."
+msgstr ""
+
+msgid "A file was not found."
+msgstr ""
+
msgid "A file with '%{file_name}' already exists in %{branch} branch"
msgstr ""
@@ -1018,7 +1083,7 @@ msgstr ""
msgid "A subscription will trigger a new pipeline on the default branch of this project when a pipeline successfully completes for a new tag on the %{default_branch_docs} of the subscribed project."
msgstr ""
-msgid "A terraform report was generated in your pipelines."
+msgid "A suggestion is not applicable."
msgstr ""
msgid "A user with write access to the source branch selected this option"
@@ -1189,6 +1254,9 @@ msgstr ""
msgid "Action to take when receiving an alert."
msgstr ""
+msgid "Actions"
+msgstr ""
+
msgid "Activate"
msgstr ""
@@ -1245,6 +1313,9 @@ msgstr ""
msgid "Add LICENSE"
msgstr ""
+msgid "Add New Node"
+msgstr ""
+
msgid "Add README"
msgstr ""
@@ -1320,6 +1391,9 @@ msgstr ""
msgid "Add approval rule"
msgstr ""
+msgid "Add approvers"
+msgstr ""
+
msgid "Add bold text"
msgstr ""
@@ -1377,6 +1451,9 @@ msgstr ""
msgid "Add strikethrough text"
msgstr ""
+msgid "Add suggestion to batch"
+msgstr ""
+
msgid "Add system hook"
msgstr ""
@@ -1476,6 +1553,9 @@ msgstr ""
msgid "Admin Note"
msgstr ""
+msgid "Admin Notifications"
+msgstr ""
+
msgid "Admin Overview"
msgstr ""
@@ -1587,6 +1667,9 @@ msgstr ""
msgid "AdminSettings|Integrations configured here will automatically apply to all projects on this instance."
msgstr ""
+msgid "AdminSettings|Moved to integrations"
+msgstr ""
+
msgid "AdminSettings|No required pipeline"
msgstr ""
@@ -1817,12 +1900,18 @@ msgid_plural "Alerts"
msgstr[0] ""
msgstr[1] ""
+msgid "AlertManagement| assign yourself"
+msgstr ""
+
msgid "AlertManagement|Acknowledged"
msgstr ""
msgid "AlertManagement|Alert"
msgstr ""
+msgid "AlertManagement|Alert assignee(s): %{assignees}"
+msgstr ""
+
msgid "AlertManagement|Alert detail"
msgstr ""
@@ -1838,9 +1927,15 @@ msgstr ""
msgid "AlertManagement|All alerts"
msgstr ""
+msgid "AlertManagement|Assign To"
+msgstr ""
+
msgid "AlertManagement|Assign status"
msgstr ""
+msgid "AlertManagement|Assignee"
+msgstr ""
+
msgid "AlertManagement|Assignees"
msgstr ""
@@ -1859,9 +1954,6 @@ msgstr ""
msgid "AlertManagement|Edit"
msgstr ""
-msgid "AlertManagement|End time"
-msgstr ""
-
msgid "AlertManagement|Events"
msgstr ""
@@ -1880,10 +1972,13 @@ msgstr ""
msgid "AlertManagement|More information"
msgstr ""
+msgid "AlertManagement|No Matching Results"
+msgstr ""
+
msgid "AlertManagement|No alert data to display."
msgstr ""
-msgid "AlertManagement|No alerts available to display. If you think you're seeing this message in error, refresh the page."
+msgid "AlertManagement|No alerts available to display. See %{linkStart}enabling alert management%{linkEnd} for more information on adding alerts to the list."
msgstr ""
msgid "AlertManagement|No alerts to display."
@@ -1892,6 +1987,9 @@ msgstr ""
msgid "AlertManagement|None"
msgstr ""
+msgid "AlertManagement|None -"
+msgstr ""
+
msgid "AlertManagement|Open"
msgstr ""
@@ -1928,9 +2026,18 @@ msgstr ""
msgid "AlertManagement|There was an error displaying the alerts. Confirm your endpoint's configuration details to ensure alerts appear."
msgstr ""
+msgid "AlertManagement|There was an error while updating the assignee(s) list. Please try again."
+msgstr ""
+
+msgid "AlertManagement|There was an error while updating the assignee(s) of the alert. Please try again."
+msgstr ""
+
msgid "AlertManagement|There was an error while updating the status of the alert. Please try again."
msgstr ""
+msgid "AlertManagement|This assignee cannot be assigned to this alert."
+msgstr ""
+
msgid "AlertManagement|Tool"
msgstr ""
@@ -2078,6 +2185,9 @@ msgstr ""
msgid "Allow users to request access (if visibility is public or internal)"
msgstr ""
+msgid "Allowed Geo IP"
+msgstr ""
+
msgid "Allowed email domain restriction only permitted for top-level groups"
msgstr ""
@@ -2111,6 +2221,9 @@ msgstr ""
msgid "Amazon Web Services"
msgstr ""
+msgid "Amazon Web Services Logo"
+msgstr ""
+
msgid "Amazon authentication is not %{link_start}correctly configured%{link_end}. Ask your GitLab administrator if you want to use this service."
msgstr ""
@@ -2168,10 +2281,16 @@ msgstr ""
msgid "An error occurred when updating the issue weight"
msgstr ""
+msgid "An error occurred while acknowledging the notification. Refresh the page and try again."
+msgstr ""
+
+msgid "An error occurred while adding approvers"
+msgstr ""
+
msgid "An error occurred while adding formatted title for epic"
msgstr ""
-msgid "An error occurred while checking group path"
+msgid "An error occurred while checking group path. Please refresh and try again."
msgstr ""
msgid "An error occurred while committing your changes."
@@ -2315,7 +2434,7 @@ msgstr ""
msgid "An error occurred while loading milestones"
msgstr ""
-msgid "An error occurred while loading terraform report"
+msgid "An error occurred while loading project creation UI"
msgstr ""
msgid "An error occurred while loading the data. Please try again."
@@ -2369,6 +2488,9 @@ msgstr ""
msgid "An error occurred while rendering preview broadcast message"
msgstr ""
+msgid "An error occurred while rendering the editor"
+msgstr ""
+
msgid "An error occurred while reordering issues."
msgstr ""
@@ -2465,7 +2587,7 @@ msgstr ""
msgid "Analyze your dependencies for known vulnerabilities."
msgstr ""
-msgid "Analyze your source code and git history for secrets"
+msgid "Analyze your source code and git history for secrets."
msgstr ""
msgid "Analyze your source code for known vulnerabilities."
@@ -2579,12 +2701,18 @@ msgstr ""
msgid "Apply suggestion"
msgstr ""
+msgid "Apply suggestions"
+msgstr ""
+
msgid "Apply template"
msgstr ""
msgid "Apply this approval rule to any branch or a specific protected branch."
msgstr ""
+msgid "Applying"
+msgstr ""
+
msgid "Applying a template will replace the existing issue description. Any changes you have made will be lost."
msgstr ""
@@ -2597,12 +2725,18 @@ msgstr ""
msgid "Applying multiple commands"
msgstr ""
-msgid "Applying suggestion"
+msgid "Applying suggestion..."
+msgstr ""
+
+msgid "Applying suggestions..."
msgstr ""
msgid "Approval rules"
msgstr ""
+msgid "Approval rules reset to project defaults"
+msgstr ""
+
msgid "ApprovalRuleRemove|%d member"
msgid_plural "ApprovalRuleRemove|%d members"
msgstr[0] ""
@@ -2656,6 +2790,9 @@ msgstr ""
msgid "Approved"
msgstr ""
+msgid "Approved MRs"
+msgstr ""
+
msgid "Approved by: "
msgstr ""
@@ -2869,6 +3006,9 @@ msgstr ""
msgid "Assign"
msgstr ""
+msgid "Assign Iteration"
+msgstr ""
+
msgid "Assign custom color like #FF0000"
msgstr ""
@@ -2887,6 +3027,9 @@ msgstr ""
msgid "Assign to"
msgstr ""
+msgid "Assign to commenting user"
+msgstr ""
+
msgid "Assign yourself to these issues"
msgstr ""
@@ -2913,6 +3056,9 @@ msgid_plural "%d Assignees"
msgstr[0] ""
msgstr[1] ""
+msgid "Assignee has no permissions"
+msgstr ""
+
msgid "Assignee lists not available with your current license"
msgstr ""
@@ -2922,6 +3068,9 @@ msgstr ""
msgid "Assignee(s)"
msgstr ""
+msgid "Assignees"
+msgstr ""
+
msgid "Assigns %{assignee_users_sentence}."
msgstr ""
@@ -2957,6 +3106,9 @@ msgstr ""
msgid "Audit Events is a way to keep track of important events that happened in GitLab."
msgstr ""
+msgid "Audit Log"
+msgstr ""
+
msgid "AuditEvents|(removed)"
msgstr ""
@@ -2993,6 +3145,9 @@ msgstr ""
msgid "AuditLogs|IP Address"
msgstr ""
+msgid "AuditLogs|Member Events"
+msgstr ""
+
msgid "AuditLogs|No matching %{type} found."
msgstr ""
@@ -3275,9 +3430,6 @@ msgstr ""
msgid "Badges|e.g. %{exampleUrl}"
msgstr ""
-msgid "Badge|New"
-msgstr ""
-
msgid "Balsamiq file could not be loaded."
msgstr ""
@@ -3326,6 +3478,12 @@ msgstr ""
msgid "Below you will find all the groups that are public."
msgstr ""
+msgid "Beta"
+msgstr ""
+
+msgid "Bi-weekly code coverage"
+msgstr ""
+
msgid "Billing"
msgstr ""
@@ -3386,9 +3544,15 @@ msgstr ""
msgid "Bitbucket Server Import"
msgstr ""
+msgid "Bitbucket Server import"
+msgstr ""
+
msgid "Bitbucket import"
msgstr ""
+msgid "Blame"
+msgstr ""
+
msgid "Blocked"
msgstr ""
@@ -3401,9 +3565,6 @@ msgstr ""
msgid "Blog"
msgstr ""
-msgid "Blue helpers indicate an action to be taken."
-msgstr ""
-
msgid "Board name"
msgstr ""
@@ -3602,6 +3763,9 @@ msgstr ""
msgid "Broadcast Message was successfully updated."
msgstr ""
+msgid "Broadcast Messages"
+msgstr ""
+
msgid "Browse Directory"
msgstr ""
@@ -3656,6 +3820,9 @@ msgstr ""
msgid "By %{user_name}"
msgstr ""
+msgid "By URL"
+msgstr ""
+
msgid "By default GitLab sends emails in HTML and plain text formats so mail clients can choose what format to use. Disable this option if you only want to send emails in plain text format."
msgstr ""
@@ -3761,6 +3928,9 @@ msgstr ""
msgid "Can override approvers and approvals required per merge request"
msgstr ""
+msgid "Can't apply as this line has changed or the suggestion already matches its content."
+msgstr ""
+
msgid "Can't create snippet: %{err}"
msgstr ""
@@ -3947,9 +4117,6 @@ msgstr ""
msgid "Changes are still tracked. Useful for cluster/index migrations."
msgstr ""
-msgid "Changes are unknown"
-msgstr ""
-
msgid "Changes suppressed. Click to show."
msgstr ""
@@ -4256,15 +4423,9 @@ msgstr ""
msgid "Choose what content you want to see on a group’s overview page"
msgstr ""
-msgid "Choose which groups you wish to synchronize to this secondary node"
-msgstr ""
-
msgid "Choose which repositories you want to connect and run CI/CD pipelines."
msgstr ""
-msgid "Choose which shards you wish to synchronize to this secondary node"
-msgstr ""
-
msgid "Choose your framework"
msgstr ""
@@ -4562,9 +4723,15 @@ msgstr ""
msgid "Cluster does not exist"
msgstr ""
+msgid "Cluster is required for Stages::ClusterEndpointInserter"
+msgstr ""
+
msgid "Cluster level"
msgstr ""
+msgid "Cluster type must be specificed for Stages::ClusterEndpointInserter"
+msgstr ""
+
msgid "ClusterIntegration| %{custom_domain_start}More information%{custom_domain_end}."
msgstr ""
@@ -4676,7 +4843,7 @@ msgstr ""
msgid "ClusterIntegration|Cert-Manager"
msgstr ""
-msgid "ClusterIntegration|Cert-Manager is a native Kubernetes certificate management controller that helps with issuing certificates. Installing Cert-Manager on your cluster will issue a certificate by %{letsEncrypt} and ensure that certificates are valid and up-to-date."
+msgid "ClusterIntegration|Cert-Manager is a native Kubernetes certificate management controller that helps with issuing certificates. Installing Cert-Manager on your cluster will issue a certificate by %{linkStart}Let's Encrypt%{linkEnd} and ensure that certificates are valid and up-to-date."
msgstr ""
msgid "ClusterIntegration|Certificate Authority bundle (PEM format)"
@@ -4703,9 +4870,6 @@ msgstr ""
msgid "ClusterIntegration|Clear the local cache of namespace and service accounts. This is necessary if your integration has become out of sync. The cache is repopulated during the next CI job that requires namespace and service accounts."
msgstr ""
-msgid "ClusterIntegration|Cloud Run"
-msgstr ""
-
msgid "ClusterIntegration|Cluster being created"
msgstr ""
@@ -4790,7 +4954,7 @@ msgstr ""
msgid "ClusterIntegration|Crossplane"
msgstr ""
-msgid "ClusterIntegration|Crossplane enables declarative provisioning of managed services from your cloud of choice using %{codeStart}kubectl%{codeEnd} or %{gitlabIntegrationLink}. Crossplane runs inside your Kubernetes cluster and supports secure connectivity and secrets management between app containers and the cloud services they depend on."
+msgid "ClusterIntegration|Crossplane enables declarative provisioning of managed services from your cloud of choice using %{codeStart}kubectl%{codeEnd} or %{linkStart}GitLab Integration%{linkEnd}. Crossplane runs inside your Kubernetes cluster and supports secure connectivity and secrets management between app containers and the cloud services they depend on."
msgstr ""
msgid "ClusterIntegration|Deletes all GitLab resources attached to this cluster during removal"
@@ -4874,9 +5038,6 @@ msgstr ""
msgid "ClusterIntegration|GitLab-managed cluster"
msgstr ""
-msgid "ClusterIntegration|Gitlab Integration"
-msgstr ""
-
msgid "ClusterIntegration|Global default"
msgstr ""
@@ -4922,7 +5083,7 @@ msgstr ""
msgid "ClusterIntegration|Ingress gives you a way to route requests to services based on the request host or path, centralizing a number of services into a single entrypoint."
msgstr ""
-msgid "ClusterIntegration|Installing Ingress may incur additional costs. Learn more about %{pricingLink}."
+msgid "ClusterIntegration|Installing Ingress may incur additional costs. Learn more about %{linkStart}pricing%{linkEnd}."
msgstr ""
msgid "ClusterIntegration|Instance cluster"
@@ -4937,7 +5098,7 @@ msgstr ""
msgid "ClusterIntegration|Issuer Email"
msgstr ""
-msgid "ClusterIntegration|Issuers represent a certificate authority. You must provide an email address for your Issuer. "
+msgid "ClusterIntegration|Issuers represent a certificate authority. You must provide an email address for your Issuer."
msgstr ""
msgid "ClusterIntegration|Jupyter Hostname"
@@ -5009,9 +5170,6 @@ msgstr ""
msgid "ClusterIntegration|Learn more about instance Kubernetes clusters"
msgstr ""
-msgid "ClusterIntegration|Let's Encrypt"
-msgstr ""
-
msgid "ClusterIntegration|Loading IAM Roles"
msgstr ""
@@ -5123,7 +5281,7 @@ msgstr ""
msgid "ClusterIntegration|Prometheus"
msgstr ""
-msgid "ClusterIntegration|Prometheus is an open-source monitoring system with %{gitlabIntegrationLink} to monitor deployed applications."
+msgid "ClusterIntegration|Prometheus is an open-source monitoring system with %{linkStart}GitLab Integration%{linkEnd} to monitor deployed applications."
msgstr ""
msgid "ClusterIntegration|Provider details"
@@ -5279,10 +5437,10 @@ msgstr ""
msgid "ClusterIntegration|Select zone to choose machine type"
msgstr ""
-msgid "ClusterIntegration|Send Cilium Logs"
+msgid "ClusterIntegration|Send Container Network Policies Logs"
msgstr ""
-msgid "ClusterIntegration|Send ModSecurity Logs"
+msgid "ClusterIntegration|Send Web Application Firewall Logs"
msgstr ""
msgid "ClusterIntegration|Service Token"
@@ -5450,15 +5608,12 @@ msgstr ""
msgid "ClusterIntegration|documentation"
msgstr ""
-msgid "ClusterIntegration|installed via %{installed_via}"
+msgid "ClusterIntegration|installed via %{linkStart}Cloud Run%{linkEnd}"
msgstr ""
msgid "ClusterIntegration|meets the requirements"
msgstr ""
-msgid "ClusterIntegration|pricing"
-msgstr ""
-
msgid "ClusterIntegration|sign up"
msgstr ""
@@ -5495,6 +5650,15 @@ msgstr ""
msgid "Code"
msgstr ""
+msgid "Code Coverage: %{coveragePercentage}%{percentSymbol}"
+msgstr ""
+
+msgid "Code Coverage| Empty code coverage data"
+msgstr ""
+
+msgid "Code Coverage|Couldn't fetch the code coverage data"
+msgstr ""
+
msgid "Code Owners"
msgstr ""
@@ -5549,7 +5713,7 @@ msgstr ""
msgid "Collapse approvers"
msgstr ""
-msgid "Collapse child epics"
+msgid "Collapse milestones"
msgstr ""
msgid "Collapse replies"
@@ -5567,6 +5731,9 @@ msgstr ""
msgid "Coming soon"
msgstr ""
+msgid "Comma-separated, e.g. '1.1.1.1, 2.2.2.0/24'"
+msgstr ""
+
msgid "Command"
msgstr ""
@@ -5737,6 +5904,12 @@ msgstr ""
msgid "Compliance framework (optional)"
msgstr ""
+msgid "Compliance frameworks"
+msgstr ""
+
+msgid "ComplianceDashboard|created by:"
+msgstr ""
+
msgid "ComplianceFramework|GDPR"
msgstr ""
@@ -5875,6 +6048,9 @@ msgstr ""
msgid "Connection timed out"
msgstr ""
+msgid "Connection timeout"
+msgstr ""
+
msgid "Contact sales to upgrade"
msgstr ""
@@ -5930,9 +6106,6 @@ msgstr ""
msgid "ContainerRegistry|CLI Commands"
msgstr ""
-msgid "ContainerRegistry|Compressed Size"
-msgstr ""
-
msgid "ContainerRegistry|Container Registry"
msgstr ""
@@ -5948,6 +6121,9 @@ msgstr ""
msgid "ContainerRegistry|Copy push command"
msgstr ""
+msgid "ContainerRegistry|Delete selected"
+msgstr ""
+
msgid "ContainerRegistry|Docker connection error"
msgstr ""
@@ -5981,16 +6157,16 @@ msgstr ""
msgid "ContainerRegistry|If you are not already logged in, you need to authenticate to the Container Registry by using your GitLab username and password. If you have %{twofaDocLinkStart}Two-Factor Authentication%{twofaDocLinkEnd} enabled, use a %{personalAccessTokensDocLinkStart}Personal Access Token%{personalAccessTokensDocLinkEnd} instead of a password."
msgstr ""
-msgid "ContainerRegistry|Image ID"
+msgid "ContainerRegistry|Image ID: %{imageId}"
msgstr ""
msgid "ContainerRegistry|Image Repositories"
msgstr ""
-msgid "ContainerRegistry|Keep and protect the images that matter most."
+msgid "ContainerRegistry|Image tags"
msgstr ""
-msgid "ContainerRegistry|Last Updated"
+msgid "ContainerRegistry|Keep and protect the images that matter most."
msgstr ""
msgid "ContainerRegistry|Login"
@@ -6005,6 +6181,9 @@ msgstr ""
msgid "ContainerRegistry|Please contact your administrator."
msgstr ""
+msgid "ContainerRegistry|Published %{timeInfo}"
+msgstr ""
+
msgid "ContainerRegistry|Push an image"
msgstr ""
@@ -6020,9 +6199,6 @@ msgstr ""
msgid "ContainerRegistry|Remove repository"
msgstr ""
-msgid "ContainerRegistry|Remove selected tags"
-msgstr ""
-
msgid "ContainerRegistry|Remove tag"
msgid_plural "ContainerRegistry|Remove tags"
msgstr[0] ""
@@ -6052,9 +6228,6 @@ msgstr ""
msgid "ContainerRegistry|Sorry, your filter produced no results."
msgstr ""
-msgid "ContainerRegistry|Tag"
-msgstr ""
-
msgid "ContainerRegistry|Tag expiration policy"
msgstr ""
@@ -6199,21 +6372,6 @@ msgstr ""
msgid "Control the display of third party offers."
msgstr ""
-msgid "Control the maximum concurrency of LFS/attachment backfill for this secondary node"
-msgstr ""
-
-msgid "Control the maximum concurrency of container repository operations for this Geo node"
-msgstr ""
-
-msgid "Control the maximum concurrency of repository backfill for this secondary node"
-msgstr ""
-
-msgid "Control the maximum concurrency of verification operations for this Geo node"
-msgstr ""
-
-msgid "Control the minimum interval in days that a repository should be reverified for this primary node"
-msgstr ""
-
msgid "Cookie domain"
msgstr ""
@@ -6355,6 +6513,9 @@ msgstr ""
msgid "Could not find design."
msgstr ""
+msgid "Could not find iteration"
+msgstr ""
+
msgid "Could not remove the trigger."
msgstr ""
@@ -6391,6 +6552,12 @@ msgstr ""
msgid "Coverage"
msgstr ""
+msgid "Coverage Fuzzing"
+msgstr ""
+
+msgid "Crash State"
+msgstr ""
+
msgid "Create"
msgstr ""
@@ -6439,6 +6606,9 @@ msgstr ""
msgid "Create a personal access token on your account to pull or push via %{protocol}."
msgstr ""
+msgid "Create a project pre-populated with the necessary files to get you started quickly."
+msgstr ""
+
msgid "Create an account using:"
msgstr ""
@@ -6607,6 +6777,9 @@ msgstr ""
msgid "Creates branch '%{branch_name}' and a merge request to resolve this issue."
msgstr ""
+msgid "Creating"
+msgstr ""
+
msgid "Creating epic"
msgstr ""
@@ -6658,7 +6831,10 @@ msgstr ""
msgid "Current vulnerabilities count"
msgstr ""
-msgid "CurrentUser|Buy CI minutes"
+msgid "CurrentUser|Buy Pipeline minutes"
+msgstr ""
+
+msgid "CurrentUser|One of your groups is running out"
msgstr ""
msgid "CurrentUser|Profile"
@@ -6673,6 +6849,9 @@ msgstr ""
msgid "CurrentUser|Upgrade"
msgstr ""
+msgid "Custom Attributes"
+msgstr ""
+
msgid "Custom CI configuration path"
msgstr ""
@@ -6936,12 +7115,18 @@ msgstr ""
msgid "DAG"
msgstr ""
+msgid "DAG visualization requires at least 3 dependent jobs."
+msgstr ""
+
msgid "DNS"
msgstr ""
msgid "Dashboard"
msgstr ""
+msgid "Dashboard uid not found"
+msgstr ""
+
msgid "DashboardProjects|All"
msgstr ""
@@ -6966,6 +7151,9 @@ msgstr ""
msgid "Data is still calculating..."
msgstr ""
+msgid "Datasource name not found"
+msgstr ""
+
msgid "Date"
msgstr ""
@@ -7146,6 +7334,9 @@ msgstr ""
msgid "Delete this attachment"
msgstr ""
+msgid "Delete user list"
+msgstr ""
+
msgid "Delete variable"
msgstr ""
@@ -7462,6 +7653,9 @@ msgstr ""
msgid "Deploying to"
msgstr ""
+msgid "Deploying to AWS is easy with GitLab"
+msgstr ""
+
msgid "Deployment Frequency"
msgstr ""
@@ -7824,6 +8018,9 @@ msgstr ""
msgid "Dismissed on pipeline %{pipelineLink} at %{projectLink}"
msgstr ""
+msgid "Display alerts from all your monitoring tools directly within GitLab."
+msgstr ""
+
msgid "Display name"
msgstr ""
@@ -7881,9 +8078,6 @@ msgstr ""
msgid "Don't show again"
msgstr ""
-msgid "Don't worry, you can access this tour by clicking on the help icon in the top right corner and choose <strong>Learn GitLab</strong>."
-msgstr ""
-
msgid "Done"
msgstr ""
@@ -8010,6 +8204,9 @@ msgstr ""
msgid "Edit Release"
msgstr ""
+msgid "Edit Slack integration"
+msgstr ""
+
msgid "Edit Snippet"
msgstr ""
@@ -8049,6 +8246,9 @@ msgstr ""
msgid "Edit issues"
msgstr ""
+msgid "Edit iteration"
+msgstr ""
+
msgid "Edit public deploy key"
msgstr ""
@@ -8097,6 +8297,9 @@ msgstr ""
msgid "Email"
msgstr ""
+msgid "Email Notification"
+msgstr ""
+
msgid "Email address"
msgstr ""
@@ -8109,6 +8312,9 @@ msgstr ""
msgid "Email not verified. Please verify your email in Salesforce."
msgstr ""
+msgid "Email notification for unknown sign-ins"
+msgstr ""
+
msgid "Email patch"
msgstr ""
@@ -8340,6 +8546,9 @@ msgstr ""
msgid "Enforce DNS rebinding attack protection"
msgstr ""
+msgid "Enforce personal access token expiration"
+msgstr ""
+
msgid "Ensure connectivity is available from the GitLab server to the Prometheus server"
msgstr ""
@@ -8409,6 +8618,9 @@ msgstr ""
msgid "Enter the name of your application, and we'll return a unique %{type}."
msgstr ""
+msgid "Enter weights for storages for new repositories."
+msgstr ""
+
msgid "Enter your password to approve"
msgstr ""
@@ -8418,15 +8630,27 @@ msgstr ""
msgid "Environment does not have deployments"
msgstr ""
+msgid "Environment is required for Stages::MetricEndpointInserter"
+msgstr ""
+
+msgid "Environment is required for Stages::VariableEndpointInserter"
+msgstr ""
+
msgid "Environment scope"
msgstr ""
+msgid "Environment variables are applied to all project environments in this instance via the Runner. You can use environment variables for passwords, secret keys, etc. Make variables available to the running application by prepending the variable key with <code>K8S_SECRET_</code>. You can set variables to be:"
+msgstr ""
+
msgid "Environment variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. Additionally, they can be masked so they are hidden in job logs, though they must match certain regexp requirements to do so. You can use environment variables for passwords, secret keys, or whatever you want."
msgstr ""
msgid "Environment variables are configured by your administrator to be %{link_start}protected%{link_end} by default"
msgstr ""
+msgid "Environment variables on this GitLab instance are configured to be %{link_start}protected%{link_end} by default"
+msgstr ""
+
msgid "Environment:"
msgstr ""
@@ -8958,6 +9182,9 @@ msgstr ""
msgid "EventFilterBy|Filter by comments"
msgstr ""
+msgid "EventFilterBy|Filter by designs"
+msgstr ""
+
msgid "EventFilterBy|Filter by epic events"
msgstr ""
@@ -9090,15 +9317,15 @@ msgstr ""
msgid "Expand approvers"
msgstr ""
-msgid "Expand child epics"
-msgstr ""
-
msgid "Expand down"
msgstr ""
msgid "Expand dropdown"
msgstr ""
+msgid "Expand milestones"
+msgstr ""
+
msgid "Expand sidebar"
msgstr ""
@@ -9114,6 +9341,9 @@ msgstr ""
msgid "Expiration date"
msgstr ""
+msgid "Expiration not enforced"
+msgstr ""
+
msgid "Expiration policy for the Container Registry is a perfect solution for keeping the Registry space down while still enjoying the full power of GitLab CI/CD."
msgstr ""
@@ -9234,6 +9464,9 @@ msgstr ""
msgid "Failed Jobs"
msgstr ""
+msgid "Failed on"
+msgstr ""
+
msgid "Failed to add a Zoom meeting"
msgstr ""
@@ -9294,13 +9527,19 @@ msgstr ""
msgid "Failed to install."
msgstr ""
+msgid "Failed to load assignees. Please try again."
+msgstr ""
+
+msgid "Failed to load authors. Please try again."
+msgstr ""
+
msgid "Failed to load emoji list."
msgstr ""
msgid "Failed to load error details from Sentry."
msgstr ""
-msgid "Failed to load errors from Sentry. Error message: %{errorMessage}"
+msgid "Failed to load errors from Sentry."
msgstr ""
msgid "Failed to load group activity metrics. Please try again."
@@ -9381,6 +9620,9 @@ msgstr ""
msgid "Failed to set due date because the date format is invalid."
msgstr ""
+msgid "Failed to set iteration on this issue. Please try again."
+msgstr ""
+
msgid "Failed to signing using smartcard authentication"
msgstr ""
@@ -9603,13 +9845,10 @@ msgstr ""
msgid "FeatureFlags|Target environments"
msgstr ""
-msgid "FeatureFlags|There are no active feature flags"
-msgstr ""
-
-msgid "FeatureFlags|There are no inactive feature flags"
+msgid "FeatureFlags|There was an error fetching the feature flags."
msgstr ""
-msgid "FeatureFlags|There was an error fetching the feature flags."
+msgid "FeatureFlags|There was an error retrieving user lists"
msgstr ""
msgid "FeatureFlags|Try again in a few moments or contact your support team."
@@ -9621,9 +9860,18 @@ msgstr ""
msgid "FeatureFlag|Delete strategy"
msgstr ""
+msgid "FeatureFlag|List"
+msgstr ""
+
msgid "FeatureFlag|Percentage"
msgstr ""
+msgid "FeatureFlag|Select a user list"
+msgstr ""
+
+msgid "FeatureFlag|There are no configured user lists"
+msgstr ""
+
msgid "FeatureFlag|Type"
msgstr ""
@@ -9780,6 +10028,12 @@ msgstr ""
msgid "Filter..."
msgstr ""
+msgid "Find File"
+msgstr ""
+
+msgid "Find bugs in your code with coverage-guided fuzzing"
+msgstr ""
+
msgid "Find by path"
msgstr ""
@@ -10044,6 +10298,9 @@ msgstr ""
msgid "Geo allows you to replicate your GitLab instance to other geographical locations."
msgstr ""
+msgid "Geo nodes are paused using a command run on the node"
+msgstr ""
+
msgid "GeoNodeStatusEvent|%{timeAgoStr} (%{pendingEvents} events)"
msgstr ""
@@ -10128,6 +10385,9 @@ msgstr ""
msgid "GeoNodes|Not checksummed"
msgstr ""
+msgid "GeoNodes|Package files"
+msgstr ""
+
msgid "GeoNodes|Pausing replication stops the sync process. Are you sure?"
msgstr ""
@@ -10146,6 +10406,9 @@ msgstr ""
msgid "GeoNodes|Replication slots"
msgstr ""
+msgid "GeoNodes|Replication status"
+msgstr ""
+
msgid "GeoNodes|Repositories"
msgstr ""
@@ -10356,6 +10619,9 @@ msgstr ""
msgid "Geo|Synchronization failed - %{error}"
msgstr ""
+msgid "Geo|Synchronization of %{itemTitle} is disabled."
+msgstr ""
+
msgid "Geo|The database is currently %{db_lag} behind the primary node."
msgstr ""
@@ -10668,6 +10934,9 @@ msgstr ""
msgid "Go to %{link_to_google_takeout}."
msgstr ""
+msgid "Go to <strong>Issues</strong> > <strong>Boards</strong> to access your personalized learning issue board."
+msgstr ""
+
msgid "Go to Pipelines"
msgstr ""
@@ -10767,9 +11036,6 @@ msgstr ""
msgid "Go to your snippets"
msgstr ""
-msgid "Golden Tanuki"
-msgstr ""
-
msgid "Google Cloud Platform"
msgstr ""
@@ -10791,6 +11057,9 @@ msgstr ""
msgid "Grafana URL"
msgstr ""
+msgid "Grafana response contains invalid json"
+msgstr ""
+
msgid "GrafanaIntegration|API Token"
msgstr ""
@@ -10839,9 +11108,6 @@ msgstr ""
msgid "Group %{group_name} was successfully created."
msgstr ""
-msgid "Group '%{group_name}' is being imported."
-msgstr ""
-
msgid "Group Audit Events"
msgstr ""
@@ -10878,9 +11144,6 @@ msgstr ""
msgid "Group by:"
msgstr ""
-msgid "Group could not be imported: %{errors}"
-msgstr ""
-
msgid "Group description"
msgstr ""
@@ -10908,9 +11171,15 @@ msgstr ""
msgid "Group has not been marked for deletion"
msgstr ""
+msgid "Group import could not be scheduled"
+msgstr ""
+
msgid "Group info:"
msgstr ""
+msgid "Group is required when cluster_type is :group"
+msgstr ""
+
msgid "Group maintainers can register group runners in the %{link}"
msgstr ""
@@ -10974,6 +11243,24 @@ msgstr ""
msgid "GroupActivyMetrics|Recent activity (last 90 days)"
msgstr ""
+msgid "GroupImport|Failed to import group."
+msgstr ""
+
+msgid "GroupImport|Group '%{group_name}' is being imported."
+msgstr ""
+
+msgid "GroupImport|Group could not be imported: %{errors}"
+msgstr ""
+
+msgid "GroupImport|Please wait while we import the group for you. Refresh at will."
+msgstr ""
+
+msgid "GroupImport|The group was successfully imported."
+msgstr ""
+
+msgid "GroupImport|Unable to process group import file"
+msgstr ""
+
msgid "GroupRoadmap|%{dateWord} – No end date"
msgstr ""
@@ -11241,6 +11528,9 @@ msgstr ""
msgid "Groups (%{groups})"
msgstr ""
+msgid "Groups and projects"
+msgstr ""
+
msgid "Groups can also be nested by creating %{subgroup_docs_link_start}subgroups%{subgroup_docs_link_end}."
msgstr ""
@@ -11286,6 +11576,33 @@ msgstr ""
msgid "GroupsEmptyState|You can manage your group member’s permissions and access to each project in the group."
msgstr ""
+msgid "GroupsNew|Contact an administrator to enable options for importing your group."
+msgstr ""
+
+msgid "GroupsNew|Create"
+msgstr ""
+
+msgid "GroupsNew|Create group"
+msgstr ""
+
+msgid "GroupsNew|GitLab group export"
+msgstr ""
+
+msgid "GroupsNew|Import"
+msgstr ""
+
+msgid "GroupsNew|Import group"
+msgstr ""
+
+msgid "GroupsNew|My Awesome Group"
+msgstr ""
+
+msgid "GroupsNew|No import options available"
+msgstr ""
+
+msgid "GroupsNew|To copy a GitLab group between installations, navigate to the group settings page for the original installation, generate an export file, and upload it here."
+msgstr ""
+
msgid "GroupsTree|Are you sure you want to leave the \"%{fullName}\" group?"
msgstr ""
@@ -11388,6 +11705,9 @@ msgstr ""
msgid "Helps reduce request volume for protected paths"
msgstr ""
+msgid "Here are all your projects in your group, including the one you just created. To start, let’s take a look at your personalized learning project which will help you learn about GitLab at your own pace."
+msgstr ""
+
msgid "Here you will find recent merge request activity"
msgstr ""
@@ -11402,6 +11722,9 @@ msgid_plural "Hide charts"
msgstr[0] ""
msgstr[1] ""
+msgid "Hide details"
+msgstr ""
+
msgid "Hide file browser"
msgstr ""
@@ -11411,6 +11734,9 @@ msgstr ""
msgid "Hide host keys manual input"
msgstr ""
+msgid "Hide list"
+msgstr ""
+
msgid "Hide marketing-related entries from help"
msgstr ""
@@ -11431,9 +11757,6 @@ msgstr[1] ""
msgid "Hide values"
msgstr ""
-msgid "Hiding all labels"
-msgstr ""
-
msgid "High or unknown vulnerabilities present"
msgstr ""
@@ -11518,6 +11841,9 @@ msgstr ""
msgid "ID:"
msgstr ""
+msgid "IDE"
+msgstr ""
+
msgid "IDE|Allow live previews of JavaScript projects in the Web IDE using CodeSandbox Live Preview."
msgstr ""
@@ -11608,10 +11934,10 @@ msgstr ""
msgid "If enabled"
msgstr ""
-msgid "If enabled, access to projects will be validated on an external service using their classification label."
+msgid "If enabled, GitLab will handle Object Storage replication using Geo. %{linkStart}More information%{linkEnd}"
msgstr ""
-msgid "If enabled, and if object storage is enabled, GitLab will handle Object Storage replication using Geo"
+msgid "If enabled, access to projects will be validated on an external service using their classification label."
msgstr ""
msgid "If there is no previous license or if the previous license has expired, some GitLab functionality will be blocked until a new, valid license is uploaded."
@@ -11973,6 +12299,9 @@ msgid_plural "Instances"
msgstr[0] ""
msgstr[1] ""
+msgid "Instance Configuration"
+msgstr ""
+
msgid "Instance Statistics visibility"
msgstr ""
@@ -11994,9 +12323,6 @@ msgstr ""
msgid "Integrations"
msgstr ""
-msgid "Integrations allow you to integrate GitLab with other applications"
-msgstr ""
-
msgid "Integrations|All details"
msgstr ""
@@ -12108,6 +12434,9 @@ msgstr ""
msgid "Invalid query"
msgstr ""
+msgid "Invalid repository bundle for snippet with id %{snippet_id}"
+msgstr ""
+
msgid "Invalid repository path"
msgstr ""
@@ -12126,6 +12455,9 @@ msgstr ""
msgid "Invalid two-factor code."
msgstr ""
+msgid "Invalid yaml"
+msgstr ""
+
msgid "Invitation"
msgstr ""
@@ -12345,15 +12677,24 @@ msgstr ""
msgid "It seems like the Dependency Scanning job ran successfully, but no dependencies have been detected in your project."
msgstr ""
+msgid "It seems that there is currently no available data for code coverage"
+msgstr ""
+
msgid "It's you"
msgstr ""
+msgid "Iteration"
+msgstr ""
+
msgid "Iteration changed to"
msgstr ""
msgid "Iteration removed"
msgstr ""
+msgid "Iteration updated"
+msgstr ""
+
msgid "Iterations"
msgstr ""
@@ -12384,12 +12725,18 @@ msgstr ""
msgid "January"
msgstr ""
+msgid "Jira Issues"
+msgstr ""
+
msgid "Jira import is already running."
msgstr ""
msgid "Jira integration not configured."
msgstr ""
+msgid "Jira project key is not configured"
+msgstr ""
+
msgid "Jira project: %{importProject}"
msgstr ""
@@ -12570,6 +12917,9 @@ msgstr ""
msgid "Keep divergent refs"
msgstr ""
+msgid "Kerberos access denied"
+msgstr ""
+
msgid "Key"
msgstr ""
@@ -12579,10 +12929,13 @@ msgstr ""
msgid "Key: %{key}"
msgstr ""
-msgid "Keyboard Shortcuts"
+msgid "Keyboard shortcuts"
msgstr ""
-msgid "Keyboard shortcuts"
+msgid "Keys"
+msgstr ""
+
+msgid "Ki"
msgstr ""
msgid "Kubernetes"
@@ -12624,6 +12977,9 @@ msgstr ""
msgid "LDAP"
msgstr ""
+msgid "LDAP Synchronization"
+msgstr ""
+
msgid "LDAP settings"
msgstr ""
@@ -12848,12 +13204,18 @@ msgstr ""
msgid "Learn more about custom project templates"
msgstr ""
+msgid "Learn more about deploying to AWS"
+msgstr ""
+
msgid "Learn more about deploying to a cluster"
msgstr ""
msgid "Learn more about group-level project templates"
msgstr ""
+msgid "Learn more about job dependencies"
+msgstr ""
+
msgid "Learn more about signing commits"
msgstr ""
@@ -12905,9 +13267,21 @@ msgstr ""
msgid "License History"
msgstr ""
+msgid "License ID:"
+msgstr ""
+
+msgid "License URL"
+msgstr ""
+
msgid "License-Check"
msgstr ""
+msgid "LicenseCompliance|%{docLinkStart}License Approvals%{docLinkEnd} are active"
+msgstr ""
+
+msgid "LicenseCompliance|%{docLinkStart}License Approvals%{docLinkEnd} are inactive"
+msgstr ""
+
msgid "LicenseCompliance|Add a license"
msgstr ""
@@ -12929,9 +13303,15 @@ msgstr ""
msgid "LicenseCompliance|Deny"
msgstr ""
+msgid "LicenseCompliance|Learn more about %{linkStart}License Approvals%{linkEnd}"
+msgstr ""
+
msgid "LicenseCompliance|License"
msgstr ""
+msgid "LicenseCompliance|License Approvals"
+msgstr ""
+
msgid "LicenseCompliance|License Compliance detected %d license and policy violation for the source branch only; approval required"
msgid_plural "LicenseCompliance|License Compliance detected %d licenses and policy violations for the source branch only; approval required"
msgstr[0] ""
@@ -13152,6 +13532,9 @@ msgstr ""
msgid "List your Bitbucket Server repositories"
msgstr ""
+msgid "Lists"
+msgstr ""
+
msgid "Live preview"
msgstr ""
@@ -13335,6 +13718,9 @@ msgstr ""
msgid "Manage project labels"
msgstr ""
+msgid "Manage storage usage"
+msgstr ""
+
msgid "Manage two-factor authentication"
msgstr ""
@@ -13350,6 +13736,9 @@ msgstr ""
msgid "Manifest file import"
msgstr ""
+msgid "Manifest import"
+msgstr ""
+
msgid "Manual job"
msgstr ""
@@ -13497,6 +13886,12 @@ msgstr ""
msgid "Maximum field length"
msgstr ""
+msgid "Maximum file size is 2MB. Please select a smaller file."
+msgstr ""
+
+msgid "Maximum import size (MB)"
+msgstr ""
+
msgid "Maximum job timeout"
msgstr ""
@@ -13536,6 +13931,9 @@ msgstr ""
msgid "Maximum size of Elasticsearch bulk indexing requests."
msgstr ""
+msgid "Maximum size of import files."
+msgstr ""
+
msgid "Maximum size of individual attachments in comments."
msgstr ""
@@ -13587,6 +13985,9 @@ msgstr ""
msgid "Merge (when the pipeline succeeds)"
msgstr ""
+msgid "Merge Conflicts"
+msgstr ""
+
msgid "Merge Request"
msgstr ""
@@ -13713,6 +14114,9 @@ msgstr ""
msgid "MergeRequests|Squash task canceled: another squash is already in progress."
msgstr ""
+msgid "MergeRequests|This project does not allow squashing commits when merge requests are accepted."
+msgstr ""
+
msgid "MergeRequests|Thread stays resolved"
msgstr ""
@@ -13866,6 +14270,12 @@ msgstr ""
msgid "MetricsSettings|Add a button to the metrics dashboard linking directly to your existing external dashboard."
msgstr ""
+msgid "MetricsSettings|Choose whether to display dashboard metrics in UTC or the user's local timezone."
+msgstr ""
+
+msgid "MetricsSettings|Dashboard timezone"
+msgstr ""
+
msgid "MetricsSettings|External dashboard URL"
msgstr ""
@@ -13875,6 +14285,12 @@ msgstr ""
msgid "MetricsSettings|Metrics Dashboard"
msgstr ""
+msgid "MetricsSettings|UTC (Coordinated Universal Time)"
+msgstr ""
+
+msgid "MetricsSettings|User's local timezone"
+msgstr ""
+
msgid "Metrics|Add metric"
msgstr ""
@@ -13890,6 +14306,9 @@ msgstr ""
msgid "Metrics|Create metric"
msgstr ""
+msgid "Metrics|Current"
+msgstr ""
+
msgid "Metrics|Delete metric"
msgstr ""
@@ -13940,6 +14359,9 @@ msgstr ""
msgid "Metrics|Max"
msgstr ""
+msgid "Metrics|Min"
+msgstr ""
+
msgid "Metrics|Must be a valid PromQL query."
msgstr ""
@@ -13955,6 +14377,9 @@ msgstr ""
msgid "Metrics|Refresh dashboard"
msgstr ""
+msgid "Metrics|Select a value"
+msgstr ""
+
msgid "Metrics|Star dashboard"
msgstr ""
@@ -13973,12 +14398,18 @@ msgstr ""
msgid "Metrics|There was an error getting annotations information."
msgstr ""
+msgid "Metrics|There was an error getting dashboard validation warnings information."
+msgstr ""
+
msgid "Metrics|There was an error getting deployment information."
msgstr ""
msgid "Metrics|There was an error getting environments information."
msgstr ""
+msgid "Metrics|There was an error getting options for variable \"%{name}\"."
+msgstr ""
+
msgid "Metrics|There was an error trying to validate your query"
msgstr ""
@@ -14021,6 +14452,9 @@ msgstr ""
msgid "Metrics|You're about to permanently delete this metric. This cannot be undone."
msgstr ""
+msgid "Metrics|Your dashboard schema is invalid. Edit the dashboard to correct the YAML schema."
+msgstr ""
+
msgid "Metrics|e.g. HTTP requests"
msgstr ""
@@ -14036,12 +14470,18 @@ msgstr ""
msgid "Metrics|e.g. req/sec"
msgstr ""
+msgid "Mi"
+msgstr ""
+
msgid "Microsoft Azure"
msgstr ""
msgid "Middleman project with Static Site Editor support"
msgstr ""
+msgid "Migrate your data from an external source like GitHub, Bitbucket, or another instance of GitLab."
+msgstr ""
+
msgid "Migrated %{success_count}/%{total_count} files."
msgstr ""
@@ -14176,6 +14616,9 @@ msgstr ""
msgid "Minimum capacity to be available before we schedule more mirrors preemptively."
msgstr ""
+msgid "Minimum interval in days"
+msgstr ""
+
msgid "Minimum length is %{minimum_password_length} characters"
msgstr ""
@@ -14362,6 +14805,12 @@ msgstr ""
msgid "Multiple uploaders found: %{uploader_types}"
msgstr ""
+msgid "Must match with the %{codeStart}external_url%{codeEnd} in %{codeStart}/etc/gitlab/gitlab.rb%{codeEnd}."
+msgstr ""
+
+msgid "Must match with the %{codeStart}geo_node_name%{codeEnd} in %{codeStart}/etc/gitlab/gitlab.rb%{codeEnd}. %{linkStart}More information%{linkEnd}"
+msgstr ""
+
msgid "My Awesome Group"
msgstr ""
@@ -14476,10 +14925,16 @@ msgstr ""
msgid "New Application"
msgstr ""
+msgid "New Branch"
+msgstr ""
+
+msgid "New Deploy Key"
+msgstr ""
+
msgid "New Environment"
msgstr ""
-msgid "New Geo Node"
+msgid "New File"
msgstr ""
msgid "New Group"
@@ -14496,15 +14951,15 @@ msgid_plural "New Issues"
msgstr[0] ""
msgstr[1] ""
-msgid "New Iteration"
-msgstr ""
-
msgid "New Jira import"
msgstr ""
msgid "New Label"
msgstr ""
+msgid "New Merge Request"
+msgstr ""
+
msgid "New Milestone"
msgstr ""
@@ -14523,6 +14978,9 @@ msgstr ""
msgid "New Snippet"
msgstr ""
+msgid "New User"
+msgstr ""
+
msgid "New branch"
msgstr ""
@@ -14565,6 +15023,9 @@ msgstr ""
msgid "New issue title"
msgstr ""
+msgid "New iteration"
+msgstr ""
+
msgid "New iteration created"
msgstr ""
@@ -14742,6 +15203,12 @@ msgstr ""
msgid "No grouping"
msgstr ""
+msgid "No iteration"
+msgstr ""
+
+msgid "No iterations to show"
+msgstr ""
+
msgid "No job log"
msgstr ""
@@ -14784,6 +15251,9 @@ msgstr ""
msgid "No other labels with such name or description"
msgstr ""
+msgid "No panels matching properties %{opts}"
+msgstr ""
+
msgid "No parent group"
msgstr ""
@@ -14817,6 +15287,9 @@ msgstr ""
msgid "No schedules"
msgstr ""
+msgid "No stack trace for this error"
+msgstr ""
+
msgid "No starrers matched your search"
msgstr ""
@@ -14829,7 +15302,7 @@ msgstr ""
msgid "No test coverage"
msgstr ""
-msgid "No thanks, don't show this again"
+msgid "No thanks"
msgstr ""
msgid "No vulnerabilities present"
@@ -14844,9 +15317,6 @@ msgstr ""
msgid "No, directly import the existing email addresses and usernames."
msgstr ""
-msgid "No, not interested right now"
-msgstr ""
-
msgid "No. of commits"
msgstr ""
@@ -14892,9 +15362,6 @@ msgstr ""
msgid "Not found."
msgstr ""
-msgid "Not helpful"
-msgstr ""
-
msgid "Not now"
msgstr ""
@@ -15051,6 +15518,9 @@ msgstr ""
msgid "Notifications on"
msgstr ""
+msgid "Notify users by email when sign-in location is not recognized"
+msgstr ""
+
msgid "Nov"
msgstr ""
@@ -15117,9 +15587,6 @@ msgstr ""
msgid "Oh no!"
msgstr ""
-msgid "Ok let's go"
-msgstr ""
-
msgid "Oldest first"
msgstr ""
@@ -15132,16 +15599,49 @@ msgstr ""
msgid "On track"
msgstr ""
+msgid "OnDemandScans|Attached branch"
+msgstr ""
+
+msgid "OnDemandScans|Attached branch is where the scan job runs."
+msgstr ""
+
+msgid "OnDemandScans|Could not run the scan. Please try again."
+msgstr ""
+
msgid "OnDemandScans|Create new DAST scan"
msgstr ""
+msgid "OnDemandScans|DAST will scan the target URL and any discovered sub URLs."
+msgstr ""
+
+msgid "OnDemandScans|New on-demand DAST scan"
+msgstr ""
+
msgid "OnDemandScans|On-demand Scans"
msgstr ""
+msgid "OnDemandScans|On-demand scans run outside the DevOps cycle and find vulnerabilities in your projects. %{learnMoreLinkStart}Learn more%{learnMoreLinkEnd}"
+msgstr ""
+
+msgid "OnDemandScans|Only a passive scan can be performed on demand."
+msgstr ""
+
+msgid "OnDemandScans|Passive DAST Scan"
+msgstr ""
+
+msgid "OnDemandScans|Please enter a valid URL format, ex: http://www.example.com/home"
+msgstr ""
+
+msgid "OnDemandScans|Run this scan"
+msgstr ""
+
+msgid "OnDemandScans|Scan mode"
+msgstr ""
+
msgid "OnDemandScans|Schedule or run scans immediately against target sites. Currently available on-demand scan type: DAST. %{helpLinkStart}More information%{helpLinkEnd}"
msgstr ""
-msgid "Onboarding"
+msgid "OnDemandScans|Target URL"
msgstr ""
msgid "Once imported, repositories can be mirrored over SSH. Read more %{link_start}here%{link_end}."
@@ -15167,6 +15667,9 @@ msgstr ""
msgid "One or more of you personal access tokens were revoked"
msgstr ""
+msgid "One or more of your %{provider} projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
+msgstr ""
+
msgid "One or more of your Bitbucket projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
msgstr ""
@@ -15668,6 +16171,9 @@ msgstr ""
msgid "Parent epic is not present."
msgstr ""
+msgid "Parsing error for param :embed_json. %{message}"
+msgstr ""
+
msgid "Part of merge request changes"
msgstr ""
@@ -15680,6 +16186,9 @@ msgstr ""
msgid "Passed"
msgstr ""
+msgid "Passed on"
+msgstr ""
+
msgid "Password"
msgstr ""
@@ -15764,6 +16273,9 @@ msgstr ""
msgid "Perform common operations on GitLab project"
msgstr ""
+msgid "Performance and resource management"
+msgstr ""
+
msgid "Performance optimization"
msgstr ""
@@ -15971,7 +16483,7 @@ msgstr ""
msgid "Pipelines|Something went wrong while cleaning runners cache."
msgstr ""
-msgid "Pipelines|There are currently no %{scope} pipelines."
+msgid "Pipelines|There are currently no finished pipelines."
msgstr ""
msgid "Pipelines|There are currently no pipelines."
@@ -16082,6 +16594,9 @@ msgstr ""
msgid "Pipeline|Stop pipeline #%{pipelineId}?"
msgstr ""
+msgid "Pipeline|Tag name"
+msgstr ""
+
msgid "Pipeline|Trigger author"
msgstr ""
@@ -16163,12 +16678,18 @@ msgstr ""
msgid "Please check your email (%{email}) to verify that you own this address and unlock the power of CI/CD. Didn't receive it? %{resend_link}. Wrong email address? %{update_link}."
msgstr ""
+msgid "Please choose a file"
+msgstr ""
+
msgid "Please choose a group URL with no special characters."
msgstr ""
msgid "Please complete your profile with email address"
msgstr ""
+msgid "Please convert %{linkStart}them to Git%{linkEnd}, and go through the %{linkToImportFlow} again."
+msgstr ""
+
msgid "Please convert them to %{link_to_git}, and go through the %{link_to_import_flow} again."
msgstr ""
@@ -16226,6 +16747,9 @@ msgstr ""
msgid "Please provide a valid email address."
msgstr ""
+msgid "Please provide attributes to update"
+msgstr ""
+
msgid "Please refer to <a href=\"%{docs_url}\">%{docs_url}</a>"
msgstr ""
@@ -16961,6 +17485,9 @@ msgstr ""
msgid "Project has too many %{label_for_message} to search"
msgstr ""
+msgid "Project is required when cluster_type is :project"
+msgstr ""
+
msgid "Project members"
msgstr ""
@@ -17078,7 +17605,40 @@ msgstr ""
msgid "ProjectService|Comment"
msgstr ""
-msgid "ProjectService|Comment will be posted on each event"
+msgid "ProjectService|Event will be triggered by a push to the repository"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a commit is created/updated"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a confidential issue is created/updated/closed"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a deployment finishes"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a merge request is created/updated/merged"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a new tag is pushed to the repository"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a new, unique alert is recorded"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a pipeline status changes"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a wiki page is created/updated"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when an issue is created/updated/closed"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when someone adds a comment"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when someone adds a comment on a confidential issue"
msgstr ""
msgid "ProjectService|Perform common operations on GitLab project: %{project_name}"
@@ -17129,6 +17689,9 @@ msgstr ""
msgid "ProjectSettings|Enable 'Delete source branch' option by default"
msgstr ""
+msgid "ProjectSettings|Enable merge trains and pipelines for merged results"
+msgstr ""
+
msgid "ProjectSettings|Every merge creates a merge commit"
msgstr ""
@@ -17159,7 +17722,7 @@ msgstr ""
msgid "ProjectSettings|Forks"
msgstr ""
-msgid "ProjectSettings|Git Large File Storage"
+msgid "ProjectSettings|Git Large File Storage (LFS)"
msgstr ""
msgid "ProjectSettings|Internal"
@@ -17195,9 +17758,6 @@ msgstr ""
msgid "ProjectSettings|Merge options"
msgstr ""
-msgid "ProjectSettings|Merge pipelines will try to validate the post-merge result prior to merging"
-msgstr ""
-
msgid "ProjectSettings|Merge requests"
msgstr ""
@@ -17225,6 +17785,9 @@ msgstr ""
msgid "ProjectSettings|Pipelines"
msgstr ""
+msgid "ProjectSettings|Pipelines for merge requests must be enabled in the CI/CD configuration file, or pipelines could be unresolvable or dropped"
+msgstr ""
+
msgid "ProjectSettings|Pipelines must succeed"
msgstr ""
@@ -17252,6 +17815,9 @@ msgstr ""
msgid "ProjectSettings|Show link to create/view merge request when pushing from the command line"
msgstr ""
+msgid "ProjectSettings|Skipped pipelines are considered successful"
+msgstr ""
+
msgid "ProjectSettings|Snippets"
msgstr ""
@@ -17267,6 +17833,9 @@ msgstr ""
msgid "ProjectSettings|These checks must pass before merge requests can be merged"
msgstr ""
+msgid "ProjectSettings|This introduces the risk of merging changes that will not pass the pipeline."
+msgstr ""
+
msgid "ProjectSettings|This setting is applied on the server level and can be overridden by an admin."
msgstr ""
@@ -17444,15 +18013,27 @@ msgstr ""
msgid "ProjectsNew|Blank project"
msgstr ""
+msgid "ProjectsNew|Connect your external repository to GitLab CI/CD."
+msgstr ""
+
msgid "ProjectsNew|Contact an administrator to enable options for importing your project."
msgstr ""
msgid "ProjectsNew|Create"
msgstr ""
+msgid "ProjectsNew|Create a blank project to house your files, plan your work, and collaborate on code, among other things."
+msgstr ""
+
+msgid "ProjectsNew|Create blank project"
+msgstr ""
+
msgid "ProjectsNew|Create from template"
msgstr ""
+msgid "ProjectsNew|Create new project"
+msgstr ""
+
msgid "ProjectsNew|Creating project & repository."
msgstr ""
@@ -17477,6 +18058,9 @@ msgstr ""
msgid "ProjectsNew|Project description %{tag_start}(optional)%{tag_end}"
msgstr ""
+msgid "ProjectsNew|Run CI/CD for external repository"
+msgstr ""
+
msgid "ProjectsNew|Template"
msgstr ""
@@ -17738,6 +18322,9 @@ msgstr ""
msgid "Protected Branch"
msgstr ""
+msgid "Protected Branches"
+msgstr ""
+
msgid "Protected Environment"
msgstr ""
@@ -17750,6 +18337,9 @@ msgstr ""
msgid "Protected Tag"
msgstr ""
+msgid "Protected Tags"
+msgstr ""
+
msgid "Protected branches"
msgstr ""
@@ -17849,6 +18439,9 @@ msgstr ""
msgid "Provider"
msgstr ""
+msgid "Proxy support for this API is not available currently"
+msgstr ""
+
msgid "Pseudonymizer data collection"
msgstr ""
@@ -17873,6 +18466,9 @@ msgstr ""
msgid "Publish to status page"
msgstr ""
+msgid "Published on status page"
+msgstr ""
+
msgid "Publishes this issue to the associated status page."
msgstr ""
@@ -18047,6 +18643,9 @@ msgstr ""
msgid "Recipe"
msgstr ""
+msgid "Reconfigure"
+msgstr ""
+
msgid "Recover hidden stage"
msgstr ""
@@ -18133,6 +18732,9 @@ msgstr ""
msgid "Registration|Your profile"
msgstr ""
+msgid "Regulate approvals by authors/committers, based on compliance frameworks. Can be changed only at the instance level."
+msgstr ""
+
msgid "Rejected (closed)"
msgstr ""
@@ -18183,15 +18785,24 @@ msgstr ""
msgid "ReleaseAssetLinkType|Image"
msgstr ""
+msgid "ReleaseAssetLinkType|Images"
+msgstr ""
+
msgid "ReleaseAssetLinkType|Other"
msgstr ""
msgid "ReleaseAssetLinkType|Package"
msgstr ""
+msgid "ReleaseAssetLinkType|Packages"
+msgstr ""
+
msgid "ReleaseAssetLinkType|Runbook"
msgstr ""
+msgid "ReleaseAssetLinkType|Runbooks"
+msgstr ""
+
msgid "Releases"
msgstr ""
@@ -18204,6 +18815,9 @@ msgstr ""
msgid "Releases documentation"
msgstr ""
+msgid "Releases|New Release"
+msgstr ""
+
msgid "Release|Something went wrong while getting the release details"
msgstr ""
@@ -18276,6 +18890,9 @@ msgstr ""
msgid "Remove fork relationship"
msgstr ""
+msgid "Remove from batch"
+msgstr ""
+
msgid "Remove from board"
msgstr ""
@@ -18465,6 +19082,12 @@ msgstr ""
msgid "Replication"
msgstr ""
+msgid "Replication enabled"
+msgstr ""
+
+msgid "Replication paused"
+msgstr ""
+
msgid "Reply by email"
msgstr ""
@@ -18492,15 +19115,15 @@ msgstr ""
msgid "Reported %{timeAgo} by %{reportedBy}"
msgstr ""
-msgid "Reported Resource Changes: %{addNum} to add, %{changeNum} to change, %{deleteNum} to delete"
-msgstr ""
-
msgid "Reporter"
msgstr ""
msgid "Reporting"
msgstr ""
+msgid "Reports"
+msgstr ""
+
msgid "Reports|%{combinedString} and %{resolvedString}"
msgstr ""
@@ -18539,6 +19162,9 @@ msgstr ""
msgid "Reports|Failure"
msgstr ""
+msgid "Reports|Identifier"
+msgstr ""
+
msgid "Reports|Metrics reports are loading"
msgstr ""
@@ -18551,6 +19177,9 @@ msgstr ""
msgid "Reports|Metrics reports failed loading results"
msgstr ""
+msgid "Reports|Scanner"
+msgstr ""
+
msgid "Reports|Severity"
msgstr ""
@@ -18599,15 +19228,33 @@ msgstr ""
msgid "Repository cleanup has started. You will receive an email once the cleanup operation is complete."
msgstr ""
+msgid "Repository files count over the limit"
+msgstr ""
+
+msgid "Repository has an invalid default branch name."
+msgstr ""
+
+msgid "Repository has more than one branch."
+msgstr ""
+
msgid "Repository has no locks."
msgstr ""
+msgid "Repository has tags."
+msgstr ""
+
msgid "Repository maintenance"
msgstr ""
msgid "Repository mirroring"
msgstr ""
+msgid "Repository must contain at least 1 file."
+msgstr ""
+
+msgid "Repository size is above the limit."
+msgstr ""
+
msgid "Repository static objects"
msgstr ""
@@ -18617,7 +19264,7 @@ msgstr ""
msgid "Repository sync capacity"
msgstr ""
-msgid "Repository: %{counter_repositories} / Wikis: %{counter_wikis} / Build Artifacts: %{counter_build_artifacts} / LFS: %{counter_lfs_objects}"
+msgid "Repository: %{counter_repositories} / Wikis: %{counter_wikis} / Build Artifacts: %{counter_build_artifacts} / LFS: %{counter_lfs_objects} / Snippets: %{counter_snippets}"
msgstr ""
msgid "RepositorySettingsAccessLevel|Select"
@@ -18629,6 +19276,9 @@ msgstr ""
msgid "Request Headers"
msgstr ""
+msgid "Request details"
+msgstr ""
+
msgid "Request parameter %{param} is missing."
msgstr ""
@@ -18735,6 +19385,9 @@ msgstr ""
msgid "Reset template"
msgstr ""
+msgid "Reset to project defaults"
+msgstr ""
+
msgid "Resetting the authorization key for this project will require updating the authorization key in every alert source it is enabled in."
msgstr ""
@@ -18831,9 +19484,6 @@ msgstr ""
msgid "Resume"
msgstr ""
-msgid "Resume replication"
-msgstr ""
-
msgid "Resync"
msgstr ""
@@ -19456,6 +20106,9 @@ msgstr ""
msgid "SecurityReports|False positive"
msgstr ""
+msgid "SecurityReports|Group Security Dashboard"
+msgstr ""
+
msgid "SecurityReports|Hide dismissed"
msgstr ""
@@ -19510,10 +20163,13 @@ msgstr ""
msgid "SecurityReports|Remove project from dashboard"
msgstr ""
-msgid "SecurityReports|Report type"
+msgid "SecurityReports|Return to dashboard"
msgstr ""
-msgid "SecurityReports|Return to dashboard"
+msgid "SecurityReports|Scan details"
+msgstr ""
+
+msgid "SecurityReports|Scanner type"
msgstr ""
msgid "SecurityReports|Security Dashboard"
@@ -19570,6 +20226,9 @@ msgstr ""
msgid "SecurityReports|There was an error while generating the report."
msgstr ""
+msgid "SecurityReports|Unable to add %{invalidProjectsMessage}"
+msgstr ""
+
msgid "SecurityReports|Unable to add %{invalidProjects}"
msgstr ""
@@ -19684,6 +20343,9 @@ msgstr ""
msgid "Select due date"
msgstr ""
+msgid "Select file"
+msgstr ""
+
msgid "Select group or project"
msgstr ""
@@ -19747,9 +20409,6 @@ msgstr ""
msgid "Select the branch you want to set as the default for this project. All merge requests and commits will automatically be made against this branch unless you specify a different one."
msgstr ""
-msgid "Select the configured storage available for new repositories to be placed on."
-msgstr ""
-
msgid "Select the custom project template source group."
msgstr ""
@@ -19942,12 +20601,6 @@ msgstr ""
msgid "Service Desk is enabled but not yet active"
msgstr ""
-msgid "Service Desk is off"
-msgstr ""
-
-msgid "Service Desk is on"
-msgstr ""
-
msgid "Service Templates"
msgstr ""
@@ -20035,6 +20688,15 @@ msgstr ""
msgid "Set the milestone to %{milestone_reference}."
msgstr ""
+msgid "Set the number of concurrent requests this secondary node will make to the primary node while backfilling."
+msgstr ""
+
+msgid "Set the synchronization and verification capacity for the secondary node."
+msgstr ""
+
+msgid "Set the timeout in seconds to send a secondary node status to the primary and IPs allowed for the secondary nodes."
+msgstr ""
+
msgid "Set time estimate"
msgstr ""
@@ -20074,6 +20736,9 @@ msgstr ""
msgid "Set weight to %{weight}."
msgstr ""
+msgid "Set what should be replicated by choosing specific projects or groups by the secondary node."
+msgstr ""
+
msgid "SetPasswordToCloneLink|set a password"
msgstr ""
@@ -20200,6 +20865,9 @@ msgstr ""
msgid "Show complete raw log"
msgstr ""
+msgid "Show details"
+msgstr ""
+
msgid "Show file browser"
msgstr ""
@@ -20209,10 +20877,13 @@ msgstr ""
msgid "Show latest version"
msgstr ""
+msgid "Show list"
+msgstr ""
+
msgid "Show me everything"
msgstr ""
-msgid "Show me how"
+msgid "Show me how to add a pipeline"
msgstr ""
msgid "Show me more advanced stuff"
@@ -20261,9 +20932,6 @@ msgstr ""
msgid "Showing all issues"
msgstr ""
-msgid "Showing all labels"
-msgstr ""
-
msgid "Showing last %{size} of log -"
msgstr ""
@@ -20375,9 +21043,6 @@ msgstr ""
msgid "Skip outdated deployment jobs"
msgstr ""
-msgid "Skip this for now"
-msgstr ""
-
msgid "Skipped"
msgstr ""
@@ -20522,6 +21187,9 @@ msgstr ""
msgid "Something went wrong while adding your award. Please try again."
msgstr ""
+msgid "Something went wrong while applying the batch of suggestions. Please try again."
+msgstr ""
+
msgid "Something went wrong while applying the suggestion. Please try again."
msgstr ""
@@ -20624,10 +21292,13 @@ msgstr ""
msgid "Something went wrong while updating a requirement."
msgstr ""
+msgid "Something went wrong while updating assignees"
+msgstr ""
+
msgid "Something went wrong while updating your list settings"
msgstr ""
-msgid "Something went wrong with your automatic subscription renewal"
+msgid "Something went wrong with your automatic subscription renewal."
msgstr ""
msgid "Something went wrong, unable to add %{project} to dashboard"
@@ -20645,6 +21316,9 @@ msgstr ""
msgid "Something went wrong, unable to search projects"
msgstr ""
+msgid "Something went wrong."
+msgstr ""
+
msgid "Something went wrong. Please try again."
msgstr ""
@@ -20828,6 +21502,9 @@ msgstr ""
msgid "Source code"
msgstr ""
+msgid "Source code (%{fileExtension})"
+msgstr ""
+
msgid "Source is not available"
msgstr ""
@@ -20912,6 +21589,9 @@ msgstr ""
msgid "Stack trace"
msgstr ""
+msgid "Stacktrace snippet"
+msgstr ""
+
msgid "Stage"
msgstr ""
@@ -21017,6 +21697,9 @@ msgstr ""
msgid "Start thread & reopen %{noteable_name}"
msgstr ""
+msgid "Start using Directed Acyclic Graphs (DAG)"
+msgstr ""
+
msgid "Start your Free Gold Trial"
msgstr ""
@@ -21479,6 +22162,21 @@ msgstr ""
msgid "SuggestedColors|Very pale orange"
msgstr ""
+msgid "Suggestion is not applicable as the suggestion was not found."
+msgstr ""
+
+msgid "Suggestions are not applicable as one or more suggestions were not found."
+msgstr ""
+
+msgid "Suggestions are not applicable as their lines cannot overlap."
+msgstr ""
+
+msgid "Suggestions must all be on the same branch."
+msgstr ""
+
+msgid "Suggestions that change line count can't be added to batches, yet."
+msgstr ""
+
msgid "Suggestions:"
msgstr ""
@@ -21521,9 +22219,6 @@ msgstr ""
msgid "Synchronization disabled"
msgstr ""
-msgid "Synchronization of container repositories is disabled."
-msgstr ""
-
msgid "System"
msgstr ""
@@ -21704,6 +22399,34 @@ msgstr ""
msgid "Terms of Service and Privacy Policy"
msgstr ""
+msgid "Terraform|%{number} Terraform report failed to generate"
+msgid_plural "Terraform|%{number} Terraform reports failed to generate"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Terraform|%{number} Terraform report was generated in your pipelines"
+msgid_plural "Terraform|%{number} Terraform reports were generated in your pipelines"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Terraform|A Terraform report failed to generate."
+msgstr ""
+
+msgid "Terraform|A Terraform report was generated in your pipelines."
+msgstr ""
+
+msgid "Terraform|Generating the report caused an error."
+msgstr ""
+
+msgid "Terraform|Reported Resource Changes: %{addNum} to add, %{changeNum} to change, %{deleteNum} to delete"
+msgstr ""
+
+msgid "Terraform|The Terraform report %{name} failed to generate."
+msgstr ""
+
+msgid "Terraform|The Terraform report %{name} was generated in your pipelines."
+msgstr ""
+
msgid "Test"
msgstr ""
@@ -21730,7 +22453,7 @@ msgstr ""
msgid "TestHooks|Ensure the project has CI pipelines."
msgstr ""
-msgid "TestHooks|Ensure the project has at least one commit."
+msgid "TestHooks|Ensure the project has deployments."
msgstr ""
msgid "TestHooks|Ensure the project has issues."
@@ -21831,7 +22554,7 @@ msgstr ""
msgid "The Prometheus server responded with \"bad request\". Please check your queries are correct and are supported in your Prometheus version. %{documentationLink}"
msgstr ""
-msgid "The URL defined on the primary node that secondary nodes should use to contact it. Defaults to URL"
+msgid "The URL defined on the primary node that secondary nodes should use to contact it."
msgstr ""
msgid "The URL to use for connecting to Elasticsearch. Use a comma-separated list to support clustering (e.g., \"http://localhost:9200, http://localhost:9201\")."
@@ -21840,6 +22563,9 @@ msgstr ""
msgid "The X509 Certificate to use when mutual TLS is required to communicate with the external authorization service. If left blank, the server certificate is still validated when accessing over HTTPS."
msgstr ""
+msgid "The above settings apply to all projects with the selected compliance framework(s)."
+msgstr ""
+
msgid "The amount of seconds after which a request to get a secondary node status will time out."
msgstr ""
@@ -22139,9 +22865,6 @@ msgstr ""
msgid "The total stage shows the time it takes between creating an issue and deploying the code to production. The data will be automatically added once you have completed the full idea to production cycle."
msgstr ""
-msgid "The unique identifier for the Geo node. Must match %{geoNodeName} if it is set in gitlab.rb, otherwise it must match %{externalUrl} with a trailing slash"
-msgstr ""
-
msgid "The update action will time out after %{number_of_minutes} minutes. For big repositories, use a clone/push combination."
msgstr ""
@@ -22166,9 +22889,6 @@ msgstr ""
msgid "The user you are trying to deactivate has been active in the past %{minimum_inactive_days} days and cannot be deactivated"
msgstr ""
-msgid "The user-facing URL of the Geo node"
-msgstr ""
-
msgid "The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6."
msgstr ""
@@ -22259,6 +22979,9 @@ msgstr ""
msgid "There was a problem fetching project branches."
msgstr ""
+msgid "There was a problem fetching project tags."
+msgstr ""
+
msgid "There was a problem fetching project users."
msgstr ""
@@ -22310,6 +23033,9 @@ msgstr ""
msgid "There was an error fetching the %{replicableType}"
msgstr ""
+msgid "There was an error fetching the Geo Settings"
+msgstr ""
+
msgid "There was an error fetching the Node's Groups"
msgstr ""
@@ -22430,6 +23156,12 @@ msgstr ""
msgid "This %{issuable} is locked. Only <strong>project members</strong> can comment."
msgstr ""
+msgid "This %{noteableTypeText} is %{confidentialLinkStart}confidential%{linkEnd} and %{lockedLinkStart}locked%{linkEnd}."
+msgstr ""
+
+msgid "This %{noteableTypeText} is locked."
+msgstr ""
+
msgid "This %{viewer} could not be displayed because %{reason}. You can %{options} instead."
msgstr ""
@@ -22448,6 +23180,9 @@ msgstr ""
msgid "This action can lead to data loss. To prevent accidental actions we ask you to confirm your intention."
msgstr ""
+msgid "This also resolves all related threads"
+msgstr ""
+
msgid "This also resolves the discussion"
msgstr ""
@@ -22538,6 +23273,9 @@ msgstr ""
msgid "This epic does not exist or you don't have sufficient permission."
msgstr ""
+msgid "This feature is currently in beta. We invite you to %{linkStart}give feedback%{linkEnd}."
+msgstr ""
+
msgid "This feature requires local storage to be enabled"
msgstr ""
@@ -22571,7 +23309,7 @@ msgstr ""
msgid "This is a Work in Progress"
msgstr ""
-msgid "This is a confidential issue."
+msgid "This is a confidential %{noteableTypeText}."
msgstr ""
msgid "This is a delayed job to run in %{remainingTime}"
@@ -22598,9 +23336,6 @@ msgstr ""
msgid "This is your current session"
msgstr ""
-msgid "This issue is %{confidentialLinkStart}confidential%{linkEnd} and %{lockedLinkStart}locked%{linkEnd}."
-msgstr ""
-
msgid "This issue is confidential"
msgstr ""
@@ -22610,9 +23345,6 @@ msgstr ""
msgid "This issue is in a child epic of the filtered epic"
msgstr ""
-msgid "This issue is locked."
-msgstr ""
-
msgid "This job depends on other jobs with expired/erased artifacts: %{invalid_dependencies}"
msgstr ""
@@ -22712,6 +23444,9 @@ msgstr ""
msgid "This license has already expired."
msgstr ""
+msgid "This link points to external content"
+msgstr ""
+
msgid "This may expose confidential information as the selected fork is in another namespace that can have other members."
msgstr ""
@@ -22739,6 +23474,9 @@ msgstr ""
msgid "This page will be removed in a future release."
msgstr ""
+msgid "This pipeline does not use the %{codeStart}needs%{codeEnd} keyword and can't be represented as a directed acyclic graph."
+msgstr ""
+
msgid "This pipeline makes use of a predefined CI/CD configuration enabled by %{strongStart}Auto DevOps.%{strongEnd}"
msgstr ""
@@ -22967,6 +23705,9 @@ msgstr ""
msgid "Time from last commit to merge"
msgstr ""
+msgid "Time in seconds"
+msgstr ""
+
msgid "Time in seconds GitLab will wait for a response from the external service. When the service does not respond in time, access will be denied."
msgstr ""
@@ -23388,9 +24129,15 @@ msgstr ""
msgid "Total artifacts size: %{total_size}"
msgstr ""
+msgid "Total cores (CPUs)"
+msgstr ""
+
msgid "Total issues"
msgstr ""
+msgid "Total memory (GB)"
+msgstr ""
+
msgid "Total test time for all commits/merges"
msgstr ""
@@ -23592,6 +24339,9 @@ msgstr ""
msgid "U2F only works with HTTPS-enabled websites. Contact your administrator for more details."
msgstr ""
+msgid "UI Development Kit"
+msgstr ""
+
msgid "URL"
msgstr ""
@@ -23664,9 +24414,6 @@ msgstr ""
msgid "Unable to load the merge request widget. Try reloading the page."
msgstr ""
-msgid "Unable to process group import file"
-msgstr ""
-
msgid "Unable to resolve"
msgstr ""
@@ -23682,6 +24429,9 @@ msgstr ""
msgid "Unable to sign you in to the group with SAML due to \"%{reason}\""
msgstr ""
+msgid "Unable to suggest a path. Please refresh and try again."
+msgstr ""
+
msgid "Unable to update label prioritization at this time"
msgstr ""
@@ -23697,6 +24447,9 @@ msgstr ""
msgid "Unarchiving the project will restore people's ability to make changes to it. The repository can be committed to, and issues, comments, and other entities can be created. %{strong_start}Once active, this project shows up in the search and on the dashboard.%{strong_end}"
msgstr ""
+msgid "Unassign from commenting user"
+msgstr ""
+
msgid "Unblock"
msgstr ""
@@ -23772,6 +24525,9 @@ msgstr ""
msgid "Unreachable"
msgstr ""
+msgid "Unrecognized cluster type"
+msgstr ""
+
msgid "Unresolve"
msgstr ""
@@ -23832,6 +24588,9 @@ msgstr ""
msgid "Unsubscribes from this %{quick_action_target}."
msgstr ""
+msgid "Unsupported todo type passed. Supported todo types are: %{todo_types}"
+msgstr ""
+
msgid "Until"
msgstr ""
@@ -23859,6 +24618,9 @@ msgstr ""
msgid "Update approval rule"
msgstr ""
+msgid "Update approvers"
+msgstr ""
+
msgid "Update failed"
msgstr ""
@@ -23868,6 +24630,9 @@ msgstr ""
msgid "Update it"
msgstr ""
+msgid "Update iteration"
+msgstr ""
+
msgid "Update now"
msgstr ""
@@ -23913,7 +24678,7 @@ msgstr ""
msgid "Updated at"
msgstr ""
-msgid "Updated to"
+msgid "Updated to %{linkStart}chart v%{linkEnd}"
msgstr ""
msgid "Updating"
@@ -23943,6 +24708,9 @@ msgstr ""
msgid "Upload CSV file"
msgstr ""
+msgid "Upload License"
+msgstr ""
+
msgid "Upload New File"
msgstr ""
@@ -24024,16 +24792,19 @@ msgstr ""
msgid "UsageQuota|Pipelines"
msgstr ""
+msgid "UsageQuota|Purchase more storage"
+msgstr ""
+
msgid "UsageQuota|Repositories"
msgstr ""
msgid "UsageQuota|Repository"
msgstr ""
-msgid "UsageQuota|Storage"
+msgid "UsageQuota|Snippets"
msgstr ""
-msgid "UsageQuota|Storage usage:"
+msgid "UsageQuota|Storage"
msgstr ""
msgid "UsageQuota|This namespace has no projects which use shared runners"
@@ -24066,6 +24837,12 @@ msgstr ""
msgid "UsageQuota|Wikis"
msgstr ""
+msgid "UsageQuota|You used: %{usage} %{limit}"
+msgstr ""
+
+msgid "UsageQuota|out of %{formattedLimit} of your namespace storage"
+msgstr ""
+
msgid "Use %{code_start}::%{code_end} to create a %{link_start}scoped label set%{link_end} (eg. %{code_start}priority::1%{code_end})"
msgstr ""
@@ -24132,6 +24909,9 @@ msgstr ""
msgid "User IDs"
msgstr ""
+msgid "User Lists can only be created and modified with %{linkStart}the API%{linkEnd}"
+msgstr ""
+
msgid "User OAuth applications"
msgstr ""
@@ -24156,6 +24936,9 @@ msgstr ""
msgid "User key was successfully removed."
msgstr ""
+msgid "User list %{name} will be removed. Are you sure?"
+msgstr ""
+
msgid "User map"
msgstr ""
@@ -24177,148 +24960,10 @@ msgstr ""
msgid "User was successfully updated."
msgstr ""
-msgid "UserOnboardingTour|%{activeTour}/%{totalTours}"
-msgstr ""
-
-msgid "UserOnboardingTour|%{completed}/%{total} steps completed"
-msgstr ""
-
-msgid "UserOnboardingTour|%{emphasisStart}Well done!%{emphasisEnd}%{lineBreak}%{lineBreak}That's it for our guided tour, congratulations for making it all the way to the end!%{lineBreak}%{lineBreak}We hope this gave you a good overview of GitLab and how it can help you. We'll now show you how to create your own project and invite your colleagues."
-msgstr ""
-
-msgid "UserOnboardingTour|Adding other members to a project is done through Project Settings. Click on %{emphasisStart}Settings%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Alright, that's it for Commits. Let's take a look at the %{emphasisStart}Branches%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Awesome! Now click on %{emphasisStart}Members%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Click on one of the %{emphasisStart}Compare%{emphasisEnd} buttons to compare a branch to master."
-msgstr ""
-
-msgid "UserOnboardingTour|Click on one of the %{emphasisStart}pipeline IDs%{emphasisEnd} to see the details of a pipeline."
-msgstr ""
-
-msgid "UserOnboardingTour|Click to open the latest commit to see its details."
-msgstr ""
-
-msgid "UserOnboardingTour|Close 'Learn GitLab'"
-msgstr ""
-
-msgid "UserOnboardingTour|Commits are shown in chronological order and can be filtered by the commit message or by the branch."
+msgid "UserList|Delete %{name}?"
msgstr ""
-msgid "UserOnboardingTour|Create a project"
-msgstr ""
-
-msgid "UserOnboardingTour|Exit 'Learn GitLab'"
-msgstr ""
-
-msgid "UserOnboardingTour|Got it"
-msgstr ""
-
-msgid "UserOnboardingTour|Great job! %{clapHands} We hope the tour was helpful and that you learned how to use GitLab.%{lineBreak}%{lineBreak}We'd love to get your feedback on this tour.%{lineBreak}%{lineBreak}%{emphasisStart}How helpful would you say this guided tour was?%{emphasisEnd}%{lineBreak}%{lineBreak}"
-msgstr ""
-
-msgid "UserOnboardingTour|Guided GitLab Tour"
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can compare the changes of this branch to another one. Changes are divided by files so that it's easier to see what was changed where."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can create a project from scratch, start with a template or import a repository from other platforms. Whatever you choose, we'll guide you through the process.%{lineBreak}%{lineBreak}Fill in your new project information and click on %{emphasisStart}Create Project%{emphasisEnd} to progress to the next step."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can see the breakdown of the pipelines: its stages and jobs in each of the stages and their status.%{lineBreak}%{lineBreak}Our CI/CD pipelines are quite complex, most of our users have fewer and simpler pipelines."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can see the current members of the project (just you at the moment) and invite new members.%{lineBreak}%{lineBreak}You can invite multiple members at once (existing GitLab users or invite by email) and you can also set their roles and permissions.%{lineBreak}%{lineBreak}Add a few members and click on %{emphasisStart}Add to project%{emphasisEnd} to complete this step."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can see what changes were made with this commit, on what branch and if there's a related merge request. The status of the pipeline will also show up if CI/CD is set up.%{lineBreak}%{lineBreak}You can also comment on the lines of code that were changed and start a discussion with your colleagues!"
-msgstr ""
-
-msgid "UserOnboardingTour|Here's an overview of branches in the %{emphasisStart}%{projectName}%{emphasisEnd} project. They're split into Active and Stale.%{lineBreak}%{lineBreak}From here, you can create a new merge request from a branch, or compare the branch to any other branch in the project. By default, it will compare it to the master branch."
-msgstr ""
-
-msgid "UserOnboardingTour|Invite colleagues"
-msgstr ""
-
-msgid "UserOnboardingTour|Issues are great for communicating and keeping track of progress in GitLab. These are all issues that are open in the %{emphasisStart}%{projectName}%{emphasisEnd}.%{lineBreak}%{lineBreak}You can help us improve GitLab by contributing work to issues that are labeled <span class=\"badge color-label accept-mr-label\">Accepting merge requests</span>.%{lineBreak}%{lineBreak}This list can be filtered by labels, milestones, assignees, authors... We'll show you how it looks when the list is filtered by a label."
-msgstr ""
-
-msgid "UserOnboardingTour|Learn GitLab"
-msgstr ""
-
-msgid "UserOnboardingTour|Let's take a closer look at a merge request. Click on the title of one."
-msgstr ""
-
-msgid "UserOnboardingTour|Let's take a closer look at all the commits. Click on %{emphasisStart}Commits%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Let's take a closer look at the repository of this project. Click on %{emphasisStart}Repository%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|No thanks"
-msgstr ""
-
-msgid "UserOnboardingTour|Ok, let's go"
-msgstr ""
-
-msgid "UserOnboardingTour|Ok, show me"
-msgstr ""
-
-msgid "UserOnboardingTour|Open one of the issues by clicking on its title."
-msgstr ""
-
-msgid "UserOnboardingTour|Restart this step"
-msgstr ""
-
-msgid "UserOnboardingTour|Skip this step"
-msgstr ""
-
-msgid "UserOnboardingTour|Sweet! Your project was created and is ready to be used.%{lineBreak}%{lineBreak}You can start adding files to the repository or clone it. One last thing we want to show you is how to invite your colleagues to your new project."
-msgstr ""
-
-msgid "UserOnboardingTour|Take a look. Here's a nifty menu for quickly creating issues, merge requests, snippets, projects and groups. Click on it and select \"New project\" from the \"GitLab\" section to get started."
-msgstr ""
-
-msgid "UserOnboardingTour|Thanks for taking the guided tour. Remember, if you want to go through it again, you can start %{emphasisStart}Learn GitLab%{emphasisEnd} in the help menu on the top right."
-msgstr ""
-
-msgid "UserOnboardingTour|Thanks for the feedback! %{thumbsUp}"
-msgstr ""
-
-msgid "UserOnboardingTour|That's it for issues. Let'st take a look at %{emphasisStart}Merge Requests%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|That's it for merge requests. Now for the final part of this guided tour - the %{emphasisStart}CI/CD%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|That's it for the Repository. Let's take a look at the %{emphasisStart}Issues%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|The structure of this page is very similar to issues. Status, description, discussion and the sidebar are all here.%{lineBreak}%{lineBreak}But take a look below the description and you'll notice that there's more information about the merge request, the CI/CD pipeline and the options for approving it.%{lineBreak}%{lineBreak}Alongside the discussion you can also see more information about commits in this merge request, the status of pipelines and review all changes that were made."
-msgstr ""
-
-msgid "UserOnboardingTour|There's a lot of information here but don't worry, we'll go through it.%{lineBreak}%{lineBreak}On the top you can see the status of the issue and when it was opened and by whom. Directly below it is the issue description and below that are other %{emphasisStart}related issues%{emphasisEnd} and %{emphasisStart}merge requests%{emphasisEnd} (if any). Then below that is the %{emphasisStart}discussion%{emphasisEnd}, that's where most of the communication happens.%{lineBreak}%{lineBreak}On the right, there's a sidebar where you can view/change the %{emphasisStart}assignee, milestone, due date, labels, weight%{emphasisEnd}, etc."
-msgstr ""
-
-msgid "UserOnboardingTour|These are all the CI/CD pipelines we have for our %{emphasisStart}%{projectName}%{emphasisEnd} project.%{lineBreak}%{lineBreak}Here you can see the status of each pipeline, for what commit it's running for, its stages and the status for them."
-msgstr ""
-
-msgid "UserOnboardingTour|These are all the issues that are available for community contributions. Let's take a closer look at one of them."
-msgstr ""
-
-msgid "UserOnboardingTour|This is an overview of all merge requests in this project. Similarly to the issues overview it can be filtered down by things like labels, milestones, authors, assignees, etc."
-msgstr ""
-
-msgid "UserOnboardingTour|This is the repository for the %{emphasisStart}%{projectName}%{emphasisEnd} project. All our code is stored here. Feel free to explore and take a closer look at folders and files.%{lineBreak}%{lineBreak}Above the file structure you can see the latest commit, who the author is and the status of the CI/CD pipeline.%{lineBreak}%{lineBreak}If you scroll down below the file structure, you'll find the Readme of this project. This is defined in the README.md file at the root of the repository."
-msgstr ""
-
-msgid "UserOnboardingTour|Welcome to the project overview of the %{emphasisStart}%{projectName}%{emphasisEnd} project. This is the project that we use to work on GitLab. At first, a project seems like a simple repository, but at GitLab, a project is so much more.%{lineBreak}%{lineBreak}You can create projects for hosting your codebase, use it as an issue tracker, collaborate on code, and continuously build, test, and deploy your app with built-in GitLab CI/CD."
+msgid "UserList|created %{timeago}"
msgstr ""
msgid "UserProfile|Activity"
@@ -24471,6 +25116,9 @@ msgstr ""
msgid "UsersSelect|Unassigned"
msgstr ""
+msgid "Using %{codeStart}needs%{codeEnd} allows jobs to run before their stage is reached, as soon as their individual dependencies are met, which speeds up your pipelines."
+msgstr ""
+
msgid "Using %{code_start}::%{code_end} denotes a %{link_start}scoped label set%{link_end}"
msgstr ""
@@ -24555,9 +25203,6 @@ msgstr ""
msgid "Versions"
msgstr ""
-msgid "Very helpful"
-msgstr ""
-
msgid "View Documentation"
msgstr ""
@@ -24706,6 +25351,9 @@ msgstr ""
msgid "VisibilityLevel|Unknown"
msgstr ""
+msgid "Visit settings page"
+msgstr ""
+
msgid "VisualReviewApp|%{stepStart}Step 1%{stepEnd}. Copy the following script:"
msgstr ""
@@ -24799,6 +25447,9 @@ msgstr ""
msgid "VulnerabilityManagement|Something went wrong while trying to delete the comment. Please try again later."
msgstr ""
+msgid "VulnerabilityManagement|Something went wrong while trying to refresh the vulnerability. Please try again later."
+msgstr ""
+
msgid "VulnerabilityManagement|Something went wrong while trying to retrieve the vulnerability history. Please try again later."
msgstr ""
@@ -24841,6 +25492,9 @@ msgstr ""
msgid "Vulnerability|Class"
msgstr ""
+msgid "Vulnerability|Crash Address"
+msgstr ""
+
msgid "Vulnerability|Description"
msgstr ""
@@ -24916,9 +25570,6 @@ msgstr ""
msgid "We couldn't reach the Prometheus server. Either the server no longer exists or the configuration details need updating."
msgstr ""
-msgid "We created a short guided tour that will help you learn the basics of GitLab and how it will help you be better at your job. It should only take a couple of minutes. You will be guided by two types of helpers, best recognized by their color."
-msgstr ""
-
msgid "We detected potential spam in the %{humanized_resource_name}. Please solve the reCAPTCHA to proceed."
msgstr ""
@@ -24952,6 +25603,9 @@ msgstr ""
msgid "We've found no vulnerabilities"
msgstr ""
+msgid "Web Application Firewall"
+msgstr ""
+
msgid "Web IDE"
msgstr ""
@@ -25012,9 +25666,6 @@ msgstr ""
msgid "Welcome to GitLab.com<br>@%{name}!"
msgstr ""
-msgid "Welcome to the Guided GitLab Tour"
-msgstr ""
-
msgid "Welcome to the guided GitLab tour"
msgstr ""
@@ -25062,9 +25713,6 @@ msgstr ""
msgid "While it's rare to have no vulnerabilities, it can happen. In any event, we ask that you please double check your settings to make sure you've set up your dashboard correctly."
msgstr ""
-msgid "White helpers give contextual information."
-msgstr ""
-
msgid "Who can be an approver?"
msgstr ""
@@ -25110,12 +25758,18 @@ msgstr ""
msgid "WikiEmptyIssueMessage|Suggest wiki improvement"
msgstr ""
+msgid "WikiEmptyIssueMessage|You must be a group member in order to add wiki pages. If you have suggestions for how to improve the wiki for this group, consider opening an issue in the %{issues_link}."
+msgstr ""
+
msgid "WikiEmptyIssueMessage|You must be a project member in order to add wiki pages. If you have suggestions for how to improve the wiki for this project, consider opening an issue in the %{issues_link}."
msgstr ""
msgid "WikiEmptyIssueMessage|issue tracker"
msgstr ""
+msgid "WikiEmpty|A wiki is where you can store all the details about your group. This can include why you've created it, its principles, how to use it, and so on."
+msgstr ""
+
msgid "WikiEmpty|A wiki is where you can store all the details about your project. This can include why you've created it, its principles, how to use it, and so on."
msgstr ""
@@ -25125,12 +25779,21 @@ msgstr ""
msgid "WikiEmpty|Suggest wiki improvement"
msgstr ""
+msgid "WikiEmpty|The wiki lets you write documentation for your group"
+msgstr ""
+
msgid "WikiEmpty|The wiki lets you write documentation for your project"
msgstr ""
+msgid "WikiEmpty|This group has no wiki pages"
+msgstr ""
+
msgid "WikiEmpty|This project has no wiki pages"
msgstr ""
+msgid "WikiEmpty|You must be a group member in order to add wiki pages."
+msgstr ""
+
msgid "WikiEmpty|You must be a project member in order to add wiki pages."
msgstr ""
@@ -25407,13 +26070,13 @@ msgstr ""
msgid "You can now export your security dashboard to a CSV report."
msgstr ""
-msgid "You can now submit a merge request to get this change into the original branch."
+msgid "You can now manage alert endpoint configuration in the Alerts section on the Operations settings page. Fields on this page have been deprecated."
msgstr ""
-msgid "You can now submit a merge request to get this change into the original project."
+msgid "You can now submit a merge request to get this change into the original branch."
msgstr ""
-msgid "You can only add files when you are on a branch"
+msgid "You can now submit a merge request to get this change into the original project."
msgstr ""
msgid "You can only edit files when you are on a branch"
@@ -25635,7 +26298,7 @@ msgstr ""
msgid "You need to upload a Google Takeout archive."
msgstr ""
-msgid "You reached %{usage_in_percent} of %{namespace_name}'s capacity (%{used_storage} of %{storage_limit})"
+msgid "You reached %{usage_in_percent} of %{namespace_name}'s storage capacity (%{used_storage} of %{storage_limit})"
msgstr ""
msgid "You tried to fork %{link_to_the_project} but it failed for the following reason:"
@@ -25938,13 +26601,13 @@ msgstr ""
msgid "Your subscription expired!"
msgstr ""
-msgid "Your subscription has been downgraded"
+msgid "Your subscription has been downgraded."
msgstr ""
-msgid "Your subscription will automatically renew in %{remaining_days}"
+msgid "Your subscription will automatically renew in %{remaining_days}."
msgstr ""
-msgid "Your subscription will expire in %{remaining_days}"
+msgid "Your subscription will expire in %{remaining_days}."
msgstr ""
msgid "Zoom meeting added"
@@ -26024,9 +26687,6 @@ msgstr ""
msgid "branch name"
msgstr ""
-msgid "build pipeline reference mismatch"
-msgstr ""
-
msgid "by"
msgstr ""
@@ -26045,6 +26705,9 @@ msgstr ""
msgid "cannot block others"
msgstr ""
+msgid "cannot contain HTML/XML tags, including any word between angle brackets (<,>)."
+msgstr ""
+
msgid "cannot include leading slash or directory traversal."
msgstr ""
@@ -26075,55 +26738,6 @@ msgstr ""
msgid "ciReport|%{remainingPackagesCount} more"
msgstr ""
-msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability"
-msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability for the source branch only"
-msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities for the source branch only"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerability"
-msgid_plural "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerabilities"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new vulnerability"
-msgid_plural "ciReport|%{reportType} %{status} detected %{newCount} new vulnerabilities"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities for the source branch only"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{fixedCount} fixed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} vulnerability for the source branch only"
-msgid_plural "ciReport|%{reportType} %{status} detected %{newCount} vulnerabilities for the source branch only"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "ciReport|%{reportType} %{status} detected no new vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected no vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected no vulnerabilities for the source branch only"
-msgstr ""
-
msgid "ciReport|%{reportType} is loading"
msgstr ""
@@ -26142,7 +26756,7 @@ msgstr ""
msgid "ciReport|All projects"
msgstr ""
-msgid "ciReport|All report types"
+msgid "ciReport|All scanner types"
msgstr ""
msgid "ciReport|All severities"
@@ -26166,6 +26780,9 @@ msgstr ""
msgid "ciReport|Container scanning detects known vulnerabilities in your docker images."
msgstr ""
+msgid "ciReport|Coverage Fuzzing"
+msgstr ""
+
msgid "ciReport|Create a merge request to implement this solution, or download and apply the patch manually."
msgstr ""
@@ -26324,6 +26941,9 @@ msgstr ""
msgid "customize"
msgstr ""
+msgid "data"
+msgstr ""
+
msgid "date must not be after 9999-12-31"
msgstr ""
@@ -26394,6 +27014,9 @@ msgstr ""
msgid "entries cannot contain HTML tags"
msgstr ""
+msgid "epic"
+msgstr ""
+
msgid "error"
msgstr ""
@@ -26415,9 +27038,6 @@ msgstr ""
msgid "expires on %{timebox_due_date}"
msgstr ""
-msgid "external_url"
-msgstr ""
-
msgid "failed"
msgstr ""
@@ -26453,9 +27073,6 @@ msgstr ""
msgid "from"
msgstr ""
-msgid "geo_node_name"
-msgstr ""
-
msgid "group"
msgstr ""
@@ -26477,9 +27094,6 @@ msgstr ""
msgid "https://your-bitbucket-server"
msgstr ""
-msgid "image"
-msgstr ""
-
msgid "image diff"
msgstr ""
@@ -26623,6 +27237,9 @@ msgstr ""
msgid "load it anyway"
msgstr ""
+msgid "loading"
+msgstr ""
+
msgid "locked by %{path_lock_user_name} %{created_at}"
msgstr ""
@@ -26751,9 +27368,6 @@ msgstr ""
msgid "mrWidget|Deployment statistics are not available currently"
msgstr ""
-msgid "mrWidget|Detect issues before deployment with a CI pipeline that continuously tests your code. We created a quick guide that will show you how to create one. Make your code more secure and more robust in just a minute."
-msgstr ""
-
msgid "mrWidget|Did not close"
msgstr ""
@@ -26916,6 +27530,9 @@ msgstr ""
msgid "mrWidget|To approve this merge request, please enter your password. This project requires all approvals to be authenticated."
msgstr ""
+msgid "mrWidget|Use %{linkStart}CI pipelines to test your code%{linkEnd}, simply add a GitLab CI configuration file to your project. It only takes a minute to make your code more secure and robust."
+msgstr ""
+
msgid "mrWidget|When this merge request is ready, remove the WIP: prefix from the title to allow it to be merged"
msgstr ""
@@ -26952,6 +27569,9 @@ msgstr ""
msgid "must be greater than start date"
msgstr ""
+msgid "must contain only valid frameworks"
+msgstr ""
+
msgid "my-awesome-group"
msgstr ""
@@ -26964,6 +27584,9 @@ msgstr ""
msgid "needs to be between 10 minutes and 1 month"
msgstr ""
+msgid "never"
+msgstr ""
+
msgid "never expires"
msgstr ""
@@ -26991,6 +27614,9 @@ msgstr ""
msgid "nounSeries|%{firstItem} and %{lastItem}"
msgstr ""
+msgid "nounSeries|%{item}"
+msgstr ""
+
msgid "nounSeries|%{item}, %{nextItem}"
msgstr ""
@@ -27127,6 +27753,9 @@ msgstr ""
msgid "revised"
msgstr ""
+msgid "satisfied"
+msgstr ""
+
msgid "score"
msgstr ""
@@ -27157,6 +27786,9 @@ msgstr ""
msgid "severity|Unknown"
msgstr ""
+msgid "should be an array of %{object_name} objects"
+msgstr ""
+
msgid "should be greater than or equal to %{access} inherited membership from group %{group_name}"
msgstr ""
@@ -27211,10 +27843,13 @@ msgstr ""
msgid "suggestPipeline|2/2: Commit your changes"
msgstr ""
-msgid "suggestPipeline|Commit the changes and your pipeline will automatically run for the first time."
+msgid "suggestPipeline|Choose %{boldStart}Code Quality%{boldEnd} to add a pipeline that tests the quality of your code."
msgstr ""
-msgid "suggestPipeline|We recommend the %{boldStart}Code Quality%{boldEnd} template, which will add a report widget to your Merge Requests. This way you’ll learn about code quality degradations much sooner. %{footerStart} Goodbye technical debt! %{footerEnd}"
+msgid "suggestPipeline|The template is ready! You can now commit it to create your first pipeline."
+msgstr ""
+
+msgid "suggestPipeline|We’re adding a GitLab CI configuration file to add a pipeline to the project. You could create it manually, but we recommend that you start with a GitLab template that works out of the box."
msgstr ""
msgid "syntax is correct"
@@ -27274,6 +27909,9 @@ msgstr ""
msgid "uses Kubernetes clusters to deploy your code!"
msgstr ""
+msgid "v%{version} published %{timeAgo}"
+msgstr ""
+
msgid "verify ownership"
msgstr ""
diff --git a/locale/it/gitlab.po b/locale/it/gitlab.po
index b20be7d591f..31c01e0347c 100644
--- a/locale/it/gitlab.po
+++ b/locale/it/gitlab.po
@@ -2,7 +2,7 @@ msgid ""
msgstr ""
"Project-Id-Version: gitlab-ee\n"
"Report-Msgid-Bugs-To: \n"
-"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Last-Translator: \n"
"Language-Team: Italian\n"
"Language: it_IT\n"
"MIME-Version: 1.0\n"
@@ -10,9 +10,11 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Crowdin-Project: gitlab-ee\n"
+"X-Crowdin-Project-ID: 288872\n"
"X-Crowdin-Language: it\n"
"X-Crowdin-File: /master/locale/gitlab.pot\n"
-"PO-Revision-Date: 2020-06-08 15:14\n"
+"X-Crowdin-File-ID: 6\n"
+"PO-Revision-Date: 2020-07-02 01:25\n"
msgid " %{start} to %{end}"
msgstr ""
@@ -197,6 +199,11 @@ msgid_plural "%d metrics"
msgstr[0] "%d metrica"
msgstr[1] "%d metriche"
+msgid "%d milestone"
+msgid_plural "%d milestones"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "%d minute"
msgid_plural "%d minutes"
msgstr[0] ""
@@ -242,11 +249,6 @@ msgid_plural "%d unresolved threads"
msgstr[0] ""
msgstr[1] ""
-msgid "%d url scanned"
-msgid_plural "%d urls scanned"
-msgstr[0] ""
-msgstr[1] ""
-
msgid "%d vulnerability"
msgid_plural "%d vulnerabilities"
msgstr[0] ""
@@ -324,9 +326,15 @@ msgstr[1] "%{count} commenti in attesa"
msgid "%{count} related %{pluralized_subject}: %{links}"
msgstr ""
+msgid "%{dashboard_path} could not be found."
+msgstr ""
+
msgid "%{days} days until tags are automatically removed"
msgstr ""
+msgid "%{deployLinkStart}Use a template to deploy to ECS%{deployLinkEnd}, or use a docker image to %{commandsLinkStart}run AWS commands in GitLab CI/CD%{commandsLinkEnd}."
+msgstr ""
+
msgid "%{description}- Sentry event: %{errorUrl}- First seen: %{firstSeen}- Last seen: %{lastSeen} %{countLabel}: %{count}%{userCountLabel}: %{userCount}"
msgstr ""
@@ -369,12 +377,18 @@ msgstr ""
msgid "%{host} sign-in from new location"
msgstr ""
-msgid "%{icon}You are about to add %{usersTag} people to the discussion. Proceed with caution."
+msgid "%{icon}You are about to add %{usersTag} people to the discussion. They will all receive a notification."
+msgstr ""
+
+msgid "%{integrations_link_start}Integrations%{link_end} enable you to make third-party applications part of your GitLab workflow. If the available integrations don't meet your needs, consider using a %{webhooks_link_start}webhook%{link_end}."
msgstr ""
msgid "%{issuableType} will be removed! Are you sure?"
msgstr "%{issuableType} sarà rimosso! Sei sicuro?"
+msgid "%{issuesCount} issues in this group"
+msgstr ""
+
msgid "%{issuesSize} issues"
msgstr ""
@@ -384,7 +398,7 @@ msgstr ""
msgid "%{labelStart}Class:%{labelEnd} %{class}"
msgstr ""
-msgid "%{labelStart}Confidence:%{labelEnd} %{confidence}"
+msgid "%{labelStart}Evidence:%{labelEnd} %{evidence}"
msgstr ""
msgid "%{labelStart}File:%{labelEnd} %{file}"
@@ -402,6 +416,9 @@ msgstr ""
msgid "%{labelStart}Report Type:%{labelEnd} %{reportType}"
msgstr ""
+msgid "%{labelStart}Scanner:%{labelEnd} %{scanner}"
+msgstr ""
+
msgid "%{labelStart}Severity:%{labelEnd} %{severity}"
msgstr ""
@@ -426,9 +443,6 @@ msgstr ""
msgid "%{link_start}Read more%{link_end} about role permissions"
msgstr "%{link_start}Leggi di più%{link_end} sulle autorizzazioni di ruolo"
-msgid "%{link} can be used for binding events when something is happening within the project."
-msgstr ""
-
msgid "%{listToShow}, and %{awardsListLength} more."
msgstr ""
@@ -462,6 +476,9 @@ msgstr ""
msgid "%{name} found %{resultsString}"
msgstr ""
+msgid "%{name} is already being used for another emoji"
+msgstr ""
+
msgid "%{name} is scheduled for %{action}"
msgstr ""
@@ -509,6 +526,9 @@ msgstr ""
msgid "%{primary} (%{secondary})"
msgstr ""
+msgid "%{ref} cannot be added: %{error}"
+msgstr ""
+
msgid "%{releases} release"
msgid_plural "%{releases} releases"
msgstr[0] ""
@@ -517,13 +537,37 @@ msgstr[1] ""
msgid "%{remaining_approvals} left"
msgstr ""
-msgid "%{retryButtonStart}Try again%{retryButtonEnd} or %{newFileButtonStart}attach a new file%{newFileButtonEnd}"
+msgid "%{reportType} %{status} detected %{critical} critical and %{high} high severity vulnerabilities out of %{total}."
+msgstr ""
+
+msgid "%{reportType} %{status} detected %{critical} critical and %{high} high severity vulnerabilities."
+msgstr ""
+
+msgid "%{reportType} %{status} detected %{critical} critical severity vulnerabilities out of %{total}."
+msgstr ""
+
+msgid "%{reportType} %{status} detected %{critical} critical severity vulnerability."
+msgid_plural "%{reportType} %{status} detected %{critical} critical severity vulnerabilities."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{reportType} %{status} detected %{high} high severity vulnerabilities out of %{total}."
msgstr ""
-msgid "%{screenreaderOnlyStart}Keyboard shorcuts%{screenreaderOnlyEnd} Disabled"
+msgid "%{reportType} %{status} detected %{high} high severity vulnerability."
+msgid_plural "%{reportType} %{status} detected %{high} high severity vulnerabilities."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{reportType} %{status} detected %{other} vulnerability."
+msgid_plural "%{reportType} %{status} detected %{other} vulnerabilities."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{reportType} %{status} detected no new vulnerabilities."
msgstr ""
-msgid "%{screenreaderOnlyStart}Keyboard shorcuts%{screenreaderOnlyEnd} Enabled"
+msgid "%{retryButtonStart}Try again%{retryButtonEnd} or %{newFileButtonStart}attach a new file%{newFileButtonEnd}"
msgstr ""
msgid "%{service_title} %{message}."
@@ -620,6 +664,12 @@ msgstr "%{title} cambiamenti"
msgid "%{token}..."
msgstr ""
+msgid "%{totalCpu} (%{freeSpacePercentage}%{percentSymbol} free)"
+msgstr ""
+
+msgid "%{totalMemory} (%{freeSpacePercentage}%{percentSymbol} free)"
+msgstr ""
+
msgid "%{totalWeight} total weight"
msgstr ""
@@ -650,6 +700,12 @@ msgstr ""
msgid "%{verb} %{time_spent_value} spent time."
msgstr ""
+msgid "%{webhooks_link_start}%{webhook_type}%{link_end} enable you to send notifications to web applications in response to events in a group or project."
+msgstr ""
+
+msgid "%{webhooks_link_start}%{webhook_type}%{link_end} enable you to send notifications to web applications in response to events in a group or project. We recommend using an %{integrations_link_start}integration%{link_end} in preference to a webhook."
+msgstr ""
+
msgid "'%{level}' is not a valid visibility level"
msgstr "'%{level}' non è un livello di visibilità valido"
@@ -676,9 +732,6 @@ msgstr "(%{mrCount} mergiati)"
msgid "(No changes)"
msgstr ""
-msgid "(Show all)"
-msgstr ""
-
msgid "(check progress)"
msgstr "(verifica progresso)"
@@ -745,6 +798,9 @@ msgstr "- riduci"
msgid "0 for unlimited"
msgstr "0 = illimitato"
+msgid "0 for unlimited, only effective with remote storage enabled."
+msgstr ""
+
msgid "1 %{type} addition"
msgid_plural "%{count} %{type} additions"
msgstr[0] "1 %{type} inserimento"
@@ -892,6 +948,12 @@ msgstr "<code>\"johnsmith@example.com\": \"johnsm...@example.com\"</code> Aggiun
msgid "<code>\"johnsmith@example.com\": \"johnsmith@example.com\"</code> will add \"By <a href=\"#\">johnsmith@example.com</a>\" to all issues and comments originally created by johnsmith@example.com. By default, the email address or username is masked to ensure the user's privacy. Use this option if you want to show the full email address."
msgstr ""
+msgid "<code>Masked</code> to prevent the values from being displayed in job logs (must match certain regexp requirements)."
+msgstr ""
+
+msgid "<code>Protected</code> to expose them to protected branches or tags only."
+msgstr ""
+
msgid "<namespace / project>"
msgstr ""
@@ -919,9 +981,6 @@ msgstr ""
msgid "A .NET Core console application template, customizable for any .NET Core project"
msgstr ""
-msgid "A DAG must have two dependent jobs to be visualized on this tab."
-msgstr ""
-
msgid "A GitBook site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
msgstr ""
@@ -952,6 +1011,12 @@ msgstr "Un ramo predefinito non può essere scelto per un progetto vuoto."
msgid "A deleted user"
msgstr ""
+msgid "A file has been changed."
+msgstr ""
+
+msgid "A file was not found."
+msgstr ""
+
msgid "A file with '%{file_name}' already exists in %{branch} branch"
msgstr ""
@@ -1018,7 +1083,7 @@ msgstr ""
msgid "A subscription will trigger a new pipeline on the default branch of this project when a pipeline successfully completes for a new tag on the %{default_branch_docs} of the subscribed project."
msgstr ""
-msgid "A terraform report was generated in your pipelines."
+msgid "A suggestion is not applicable."
msgstr ""
msgid "A user with write access to the source branch selected this option"
@@ -1189,6 +1254,9 @@ msgstr ""
msgid "Action to take when receiving an alert."
msgstr ""
+msgid "Actions"
+msgstr ""
+
msgid "Activate"
msgstr ""
@@ -1245,6 +1313,9 @@ msgstr ""
msgid "Add LICENSE"
msgstr ""
+msgid "Add New Node"
+msgstr ""
+
msgid "Add README"
msgstr ""
@@ -1320,6 +1391,9 @@ msgstr ""
msgid "Add approval rule"
msgstr ""
+msgid "Add approvers"
+msgstr ""
+
msgid "Add bold text"
msgstr ""
@@ -1377,6 +1451,9 @@ msgstr ""
msgid "Add strikethrough text"
msgstr ""
+msgid "Add suggestion to batch"
+msgstr ""
+
msgid "Add system hook"
msgstr ""
@@ -1476,6 +1553,9 @@ msgstr ""
msgid "Admin Note"
msgstr ""
+msgid "Admin Notifications"
+msgstr ""
+
msgid "Admin Overview"
msgstr ""
@@ -1587,6 +1667,9 @@ msgstr ""
msgid "AdminSettings|Integrations configured here will automatically apply to all projects on this instance."
msgstr ""
+msgid "AdminSettings|Moved to integrations"
+msgstr ""
+
msgid "AdminSettings|No required pipeline"
msgstr ""
@@ -1817,12 +1900,18 @@ msgid_plural "Alerts"
msgstr[0] ""
msgstr[1] ""
+msgid "AlertManagement| assign yourself"
+msgstr ""
+
msgid "AlertManagement|Acknowledged"
msgstr ""
msgid "AlertManagement|Alert"
msgstr ""
+msgid "AlertManagement|Alert assignee(s): %{assignees}"
+msgstr ""
+
msgid "AlertManagement|Alert detail"
msgstr ""
@@ -1838,9 +1927,15 @@ msgstr ""
msgid "AlertManagement|All alerts"
msgstr ""
+msgid "AlertManagement|Assign To"
+msgstr ""
+
msgid "AlertManagement|Assign status"
msgstr ""
+msgid "AlertManagement|Assignee"
+msgstr ""
+
msgid "AlertManagement|Assignees"
msgstr ""
@@ -1859,9 +1954,6 @@ msgstr ""
msgid "AlertManagement|Edit"
msgstr ""
-msgid "AlertManagement|End time"
-msgstr ""
-
msgid "AlertManagement|Events"
msgstr ""
@@ -1880,10 +1972,13 @@ msgstr ""
msgid "AlertManagement|More information"
msgstr ""
+msgid "AlertManagement|No Matching Results"
+msgstr ""
+
msgid "AlertManagement|No alert data to display."
msgstr ""
-msgid "AlertManagement|No alerts available to display. If you think you're seeing this message in error, refresh the page."
+msgid "AlertManagement|No alerts available to display. See %{linkStart}enabling alert management%{linkEnd} for more information on adding alerts to the list."
msgstr ""
msgid "AlertManagement|No alerts to display."
@@ -1892,6 +1987,9 @@ msgstr ""
msgid "AlertManagement|None"
msgstr ""
+msgid "AlertManagement|None -"
+msgstr ""
+
msgid "AlertManagement|Open"
msgstr ""
@@ -1928,9 +2026,18 @@ msgstr ""
msgid "AlertManagement|There was an error displaying the alerts. Confirm your endpoint's configuration details to ensure alerts appear."
msgstr ""
+msgid "AlertManagement|There was an error while updating the assignee(s) list. Please try again."
+msgstr ""
+
+msgid "AlertManagement|There was an error while updating the assignee(s) of the alert. Please try again."
+msgstr ""
+
msgid "AlertManagement|There was an error while updating the status of the alert. Please try again."
msgstr ""
+msgid "AlertManagement|This assignee cannot be assigned to this alert."
+msgstr ""
+
msgid "AlertManagement|Tool"
msgstr ""
@@ -2078,6 +2185,9 @@ msgstr ""
msgid "Allow users to request access (if visibility is public or internal)"
msgstr ""
+msgid "Allowed Geo IP"
+msgstr ""
+
msgid "Allowed email domain restriction only permitted for top-level groups"
msgstr ""
@@ -2111,6 +2221,9 @@ msgstr ""
msgid "Amazon Web Services"
msgstr ""
+msgid "Amazon Web Services Logo"
+msgstr ""
+
msgid "Amazon authentication is not %{link_start}correctly configured%{link_end}. Ask your GitLab administrator if you want to use this service."
msgstr ""
@@ -2168,10 +2281,16 @@ msgstr ""
msgid "An error occurred when updating the issue weight"
msgstr ""
+msgid "An error occurred while acknowledging the notification. Refresh the page and try again."
+msgstr ""
+
+msgid "An error occurred while adding approvers"
+msgstr ""
+
msgid "An error occurred while adding formatted title for epic"
msgstr ""
-msgid "An error occurred while checking group path"
+msgid "An error occurred while checking group path. Please refresh and try again."
msgstr ""
msgid "An error occurred while committing your changes."
@@ -2315,7 +2434,7 @@ msgstr ""
msgid "An error occurred while loading milestones"
msgstr ""
-msgid "An error occurred while loading terraform report"
+msgid "An error occurred while loading project creation UI"
msgstr ""
msgid "An error occurred while loading the data. Please try again."
@@ -2369,6 +2488,9 @@ msgstr ""
msgid "An error occurred while rendering preview broadcast message"
msgstr ""
+msgid "An error occurred while rendering the editor"
+msgstr ""
+
msgid "An error occurred while reordering issues."
msgstr ""
@@ -2465,7 +2587,7 @@ msgstr ""
msgid "Analyze your dependencies for known vulnerabilities."
msgstr ""
-msgid "Analyze your source code and git history for secrets"
+msgid "Analyze your source code and git history for secrets."
msgstr ""
msgid "Analyze your source code for known vulnerabilities."
@@ -2579,12 +2701,18 @@ msgstr ""
msgid "Apply suggestion"
msgstr ""
+msgid "Apply suggestions"
+msgstr ""
+
msgid "Apply template"
msgstr ""
msgid "Apply this approval rule to any branch or a specific protected branch."
msgstr ""
+msgid "Applying"
+msgstr ""
+
msgid "Applying a template will replace the existing issue description. Any changes you have made will be lost."
msgstr ""
@@ -2597,12 +2725,18 @@ msgstr ""
msgid "Applying multiple commands"
msgstr ""
-msgid "Applying suggestion"
+msgid "Applying suggestion..."
+msgstr ""
+
+msgid "Applying suggestions..."
msgstr ""
msgid "Approval rules"
msgstr ""
+msgid "Approval rules reset to project defaults"
+msgstr ""
+
msgid "ApprovalRuleRemove|%d member"
msgid_plural "ApprovalRuleRemove|%d members"
msgstr[0] ""
@@ -2656,6 +2790,9 @@ msgstr ""
msgid "Approved"
msgstr ""
+msgid "Approved MRs"
+msgstr ""
+
msgid "Approved by: "
msgstr ""
@@ -2869,6 +3006,9 @@ msgstr ""
msgid "Assign"
msgstr ""
+msgid "Assign Iteration"
+msgstr ""
+
msgid "Assign custom color like #FF0000"
msgstr ""
@@ -2887,6 +3027,9 @@ msgstr ""
msgid "Assign to"
msgstr ""
+msgid "Assign to commenting user"
+msgstr ""
+
msgid "Assign yourself to these issues"
msgstr ""
@@ -2913,6 +3056,9 @@ msgid_plural "%d Assignees"
msgstr[0] ""
msgstr[1] ""
+msgid "Assignee has no permissions"
+msgstr ""
+
msgid "Assignee lists not available with your current license"
msgstr ""
@@ -2922,6 +3068,9 @@ msgstr ""
msgid "Assignee(s)"
msgstr ""
+msgid "Assignees"
+msgstr ""
+
msgid "Assigns %{assignee_users_sentence}."
msgstr ""
@@ -2957,6 +3106,9 @@ msgstr ""
msgid "Audit Events is a way to keep track of important events that happened in GitLab."
msgstr ""
+msgid "Audit Log"
+msgstr ""
+
msgid "AuditEvents|(removed)"
msgstr ""
@@ -2993,6 +3145,9 @@ msgstr ""
msgid "AuditLogs|IP Address"
msgstr ""
+msgid "AuditLogs|Member Events"
+msgstr ""
+
msgid "AuditLogs|No matching %{type} found."
msgstr ""
@@ -3275,9 +3430,6 @@ msgstr ""
msgid "Badges|e.g. %{exampleUrl}"
msgstr ""
-msgid "Badge|New"
-msgstr ""
-
msgid "Balsamiq file could not be loaded."
msgstr ""
@@ -3326,6 +3478,12 @@ msgstr ""
msgid "Below you will find all the groups that are public."
msgstr ""
+msgid "Beta"
+msgstr ""
+
+msgid "Bi-weekly code coverage"
+msgstr ""
+
msgid "Billing"
msgstr ""
@@ -3386,9 +3544,15 @@ msgstr ""
msgid "Bitbucket Server Import"
msgstr ""
+msgid "Bitbucket Server import"
+msgstr ""
+
msgid "Bitbucket import"
msgstr ""
+msgid "Blame"
+msgstr ""
+
msgid "Blocked"
msgstr ""
@@ -3401,9 +3565,6 @@ msgstr ""
msgid "Blog"
msgstr ""
-msgid "Blue helpers indicate an action to be taken."
-msgstr ""
-
msgid "Board name"
msgstr ""
@@ -3602,6 +3763,9 @@ msgstr ""
msgid "Broadcast Message was successfully updated."
msgstr ""
+msgid "Broadcast Messages"
+msgstr ""
+
msgid "Browse Directory"
msgstr "Naviga direttori"
@@ -3656,6 +3820,9 @@ msgstr ""
msgid "By %{user_name}"
msgstr ""
+msgid "By URL"
+msgstr ""
+
msgid "By default GitLab sends emails in HTML and plain text formats so mail clients can choose what format to use. Disable this option if you only want to send emails in plain text format."
msgstr ""
@@ -3761,6 +3928,9 @@ msgstr ""
msgid "Can override approvers and approvals required per merge request"
msgstr ""
+msgid "Can't apply as this line has changed or the suggestion already matches its content."
+msgstr ""
+
msgid "Can't create snippet: %{err}"
msgstr ""
@@ -3947,9 +4117,6 @@ msgstr ""
msgid "Changes are still tracked. Useful for cluster/index migrations."
msgstr ""
-msgid "Changes are unknown"
-msgstr ""
-
msgid "Changes suppressed. Click to show."
msgstr ""
@@ -4256,15 +4423,9 @@ msgstr ""
msgid "Choose what content you want to see on a group’s overview page"
msgstr ""
-msgid "Choose which groups you wish to synchronize to this secondary node"
-msgstr ""
-
msgid "Choose which repositories you want to connect and run CI/CD pipelines."
msgstr ""
-msgid "Choose which shards you wish to synchronize to this secondary node"
-msgstr ""
-
msgid "Choose your framework"
msgstr ""
@@ -4562,9 +4723,15 @@ msgstr ""
msgid "Cluster does not exist"
msgstr ""
+msgid "Cluster is required for Stages::ClusterEndpointInserter"
+msgstr ""
+
msgid "Cluster level"
msgstr ""
+msgid "Cluster type must be specificed for Stages::ClusterEndpointInserter"
+msgstr ""
+
msgid "ClusterIntegration| %{custom_domain_start}More information%{custom_domain_end}."
msgstr ""
@@ -4676,7 +4843,7 @@ msgstr "Certificato CA"
msgid "ClusterIntegration|Cert-Manager"
msgstr ""
-msgid "ClusterIntegration|Cert-Manager is a native Kubernetes certificate management controller that helps with issuing certificates. Installing Cert-Manager on your cluster will issue a certificate by %{letsEncrypt} and ensure that certificates are valid and up-to-date."
+msgid "ClusterIntegration|Cert-Manager is a native Kubernetes certificate management controller that helps with issuing certificates. Installing Cert-Manager on your cluster will issue a certificate by %{linkStart}Let's Encrypt%{linkEnd} and ensure that certificates are valid and up-to-date."
msgstr ""
msgid "ClusterIntegration|Certificate Authority bundle (PEM format)"
@@ -4703,9 +4870,6 @@ msgstr ""
msgid "ClusterIntegration|Clear the local cache of namespace and service accounts. This is necessary if your integration has become out of sync. The cache is repopulated during the next CI job that requires namespace and service accounts."
msgstr ""
-msgid "ClusterIntegration|Cloud Run"
-msgstr ""
-
msgid "ClusterIntegration|Cluster being created"
msgstr ""
@@ -4790,7 +4954,7 @@ msgstr ""
msgid "ClusterIntegration|Crossplane"
msgstr ""
-msgid "ClusterIntegration|Crossplane enables declarative provisioning of managed services from your cloud of choice using %{codeStart}kubectl%{codeEnd} or %{gitlabIntegrationLink}. Crossplane runs inside your Kubernetes cluster and supports secure connectivity and secrets management between app containers and the cloud services they depend on."
+msgid "ClusterIntegration|Crossplane enables declarative provisioning of managed services from your cloud of choice using %{codeStart}kubectl%{codeEnd} or %{linkStart}GitLab Integration%{linkEnd}. Crossplane runs inside your Kubernetes cluster and supports secure connectivity and secrets management between app containers and the cloud services they depend on."
msgstr ""
msgid "ClusterIntegration|Deletes all GitLab resources attached to this cluster during removal"
@@ -4874,9 +5038,6 @@ msgstr ""
msgid "ClusterIntegration|GitLab-managed cluster"
msgstr ""
-msgid "ClusterIntegration|Gitlab Integration"
-msgstr ""
-
msgid "ClusterIntegration|Global default"
msgstr ""
@@ -4922,7 +5083,7 @@ msgstr ""
msgid "ClusterIntegration|Ingress gives you a way to route requests to services based on the request host or path, centralizing a number of services into a single entrypoint."
msgstr ""
-msgid "ClusterIntegration|Installing Ingress may incur additional costs. Learn more about %{pricingLink}."
+msgid "ClusterIntegration|Installing Ingress may incur additional costs. Learn more about %{linkStart}pricing%{linkEnd}."
msgstr ""
msgid "ClusterIntegration|Instance cluster"
@@ -4937,7 +5098,7 @@ msgstr ""
msgid "ClusterIntegration|Issuer Email"
msgstr ""
-msgid "ClusterIntegration|Issuers represent a certificate authority. You must provide an email address for your Issuer. "
+msgid "ClusterIntegration|Issuers represent a certificate authority. You must provide an email address for your Issuer."
msgstr ""
msgid "ClusterIntegration|Jupyter Hostname"
@@ -5009,9 +5170,6 @@ msgstr ""
msgid "ClusterIntegration|Learn more about instance Kubernetes clusters"
msgstr ""
-msgid "ClusterIntegration|Let's Encrypt"
-msgstr ""
-
msgid "ClusterIntegration|Loading IAM Roles"
msgstr ""
@@ -5123,7 +5281,7 @@ msgstr ""
msgid "ClusterIntegration|Prometheus"
msgstr ""
-msgid "ClusterIntegration|Prometheus is an open-source monitoring system with %{gitlabIntegrationLink} to monitor deployed applications."
+msgid "ClusterIntegration|Prometheus is an open-source monitoring system with %{linkStart}GitLab Integration%{linkEnd} to monitor deployed applications."
msgstr ""
msgid "ClusterIntegration|Provider details"
@@ -5279,10 +5437,10 @@ msgstr ""
msgid "ClusterIntegration|Select zone to choose machine type"
msgstr ""
-msgid "ClusterIntegration|Send Cilium Logs"
+msgid "ClusterIntegration|Send Container Network Policies Logs"
msgstr ""
-msgid "ClusterIntegration|Send ModSecurity Logs"
+msgid "ClusterIntegration|Send Web Application Firewall Logs"
msgstr ""
msgid "ClusterIntegration|Service Token"
@@ -5450,15 +5608,12 @@ msgstr ""
msgid "ClusterIntegration|documentation"
msgstr ""
-msgid "ClusterIntegration|installed via %{installed_via}"
+msgid "ClusterIntegration|installed via %{linkStart}Cloud Run%{linkEnd}"
msgstr ""
msgid "ClusterIntegration|meets the requirements"
msgstr ""
-msgid "ClusterIntegration|pricing"
-msgstr ""
-
msgid "ClusterIntegration|sign up"
msgstr ""
@@ -5495,6 +5650,15 @@ msgstr ""
msgid "Code"
msgstr ""
+msgid "Code Coverage: %{coveragePercentage}%{percentSymbol}"
+msgstr ""
+
+msgid "Code Coverage| Empty code coverage data"
+msgstr ""
+
+msgid "Code Coverage|Couldn't fetch the code coverage data"
+msgstr ""
+
msgid "Code Owners"
msgstr ""
@@ -5549,7 +5713,7 @@ msgstr ""
msgid "Collapse approvers"
msgstr ""
-msgid "Collapse child epics"
+msgid "Collapse milestones"
msgstr ""
msgid "Collapse replies"
@@ -5567,6 +5731,9 @@ msgstr ""
msgid "Coming soon"
msgstr ""
+msgid "Comma-separated, e.g. '1.1.1.1, 2.2.2.0/24'"
+msgstr ""
+
msgid "Command"
msgstr ""
@@ -5737,6 +5904,12 @@ msgstr ""
msgid "Compliance framework (optional)"
msgstr ""
+msgid "Compliance frameworks"
+msgstr ""
+
+msgid "ComplianceDashboard|created by:"
+msgstr ""
+
msgid "ComplianceFramework|GDPR"
msgstr ""
@@ -5875,6 +6048,9 @@ msgstr ""
msgid "Connection timed out"
msgstr ""
+msgid "Connection timeout"
+msgstr ""
+
msgid "Contact sales to upgrade"
msgstr ""
@@ -5930,9 +6106,6 @@ msgstr ""
msgid "ContainerRegistry|CLI Commands"
msgstr ""
-msgid "ContainerRegistry|Compressed Size"
-msgstr ""
-
msgid "ContainerRegistry|Container Registry"
msgstr ""
@@ -5948,6 +6121,9 @@ msgstr ""
msgid "ContainerRegistry|Copy push command"
msgstr ""
+msgid "ContainerRegistry|Delete selected"
+msgstr ""
+
msgid "ContainerRegistry|Docker connection error"
msgstr ""
@@ -5981,16 +6157,16 @@ msgstr ""
msgid "ContainerRegistry|If you are not already logged in, you need to authenticate to the Container Registry by using your GitLab username and password. If you have %{twofaDocLinkStart}Two-Factor Authentication%{twofaDocLinkEnd} enabled, use a %{personalAccessTokensDocLinkStart}Personal Access Token%{personalAccessTokensDocLinkEnd} instead of a password."
msgstr ""
-msgid "ContainerRegistry|Image ID"
+msgid "ContainerRegistry|Image ID: %{imageId}"
msgstr ""
msgid "ContainerRegistry|Image Repositories"
msgstr ""
-msgid "ContainerRegistry|Keep and protect the images that matter most."
+msgid "ContainerRegistry|Image tags"
msgstr ""
-msgid "ContainerRegistry|Last Updated"
+msgid "ContainerRegistry|Keep and protect the images that matter most."
msgstr ""
msgid "ContainerRegistry|Login"
@@ -6005,6 +6181,9 @@ msgstr ""
msgid "ContainerRegistry|Please contact your administrator."
msgstr ""
+msgid "ContainerRegistry|Published %{timeInfo}"
+msgstr ""
+
msgid "ContainerRegistry|Push an image"
msgstr ""
@@ -6020,9 +6199,6 @@ msgstr ""
msgid "ContainerRegistry|Remove repository"
msgstr ""
-msgid "ContainerRegistry|Remove selected tags"
-msgstr ""
-
msgid "ContainerRegistry|Remove tag"
msgid_plural "ContainerRegistry|Remove tags"
msgstr[0] ""
@@ -6052,9 +6228,6 @@ msgstr ""
msgid "ContainerRegistry|Sorry, your filter produced no results."
msgstr ""
-msgid "ContainerRegistry|Tag"
-msgstr "Tag"
-
msgid "ContainerRegistry|Tag expiration policy"
msgstr ""
@@ -6199,21 +6372,6 @@ msgstr ""
msgid "Control the display of third party offers."
msgstr ""
-msgid "Control the maximum concurrency of LFS/attachment backfill for this secondary node"
-msgstr ""
-
-msgid "Control the maximum concurrency of container repository operations for this Geo node"
-msgstr ""
-
-msgid "Control the maximum concurrency of repository backfill for this secondary node"
-msgstr ""
-
-msgid "Control the maximum concurrency of verification operations for this Geo node"
-msgstr ""
-
-msgid "Control the minimum interval in days that a repository should be reverified for this primary node"
-msgstr ""
-
msgid "Cookie domain"
msgstr ""
@@ -6355,6 +6513,9 @@ msgstr ""
msgid "Could not find design."
msgstr ""
+msgid "Could not find iteration"
+msgstr ""
+
msgid "Could not remove the trigger."
msgstr ""
@@ -6391,6 +6552,12 @@ msgstr ""
msgid "Coverage"
msgstr ""
+msgid "Coverage Fuzzing"
+msgstr ""
+
+msgid "Crash State"
+msgstr ""
+
msgid "Create"
msgstr ""
@@ -6439,6 +6606,9 @@ msgstr ""
msgid "Create a personal access token on your account to pull or push via %{protocol}."
msgstr "Creare un token di accesso sul tuo account per eseguire pull o push tramite %{protocol}"
+msgid "Create a project pre-populated with the necessary files to get you started quickly."
+msgstr ""
+
msgid "Create an account using:"
msgstr ""
@@ -6607,6 +6777,9 @@ msgstr ""
msgid "Creates branch '%{branch_name}' and a merge request to resolve this issue."
msgstr ""
+msgid "Creating"
+msgstr ""
+
msgid "Creating epic"
msgstr ""
@@ -6658,7 +6831,10 @@ msgstr ""
msgid "Current vulnerabilities count"
msgstr ""
-msgid "CurrentUser|Buy CI minutes"
+msgid "CurrentUser|Buy Pipeline minutes"
+msgstr ""
+
+msgid "CurrentUser|One of your groups is running out"
msgstr ""
msgid "CurrentUser|Profile"
@@ -6673,6 +6849,9 @@ msgstr ""
msgid "CurrentUser|Upgrade"
msgstr ""
+msgid "Custom Attributes"
+msgstr ""
+
msgid "Custom CI configuration path"
msgstr ""
@@ -6936,12 +7115,18 @@ msgstr ""
msgid "DAG"
msgstr ""
+msgid "DAG visualization requires at least 3 dependent jobs."
+msgstr ""
+
msgid "DNS"
msgstr ""
msgid "Dashboard"
msgstr ""
+msgid "Dashboard uid not found"
+msgstr ""
+
msgid "DashboardProjects|All"
msgstr "Tutti"
@@ -6966,6 +7151,9 @@ msgstr ""
msgid "Data is still calculating..."
msgstr ""
+msgid "Datasource name not found"
+msgstr ""
+
msgid "Date"
msgstr ""
@@ -7146,6 +7334,9 @@ msgstr ""
msgid "Delete this attachment"
msgstr ""
+msgid "Delete user list"
+msgstr ""
+
msgid "Delete variable"
msgstr ""
@@ -7462,6 +7653,9 @@ msgstr ""
msgid "Deploying to"
msgstr ""
+msgid "Deploying to AWS is easy with GitLab"
+msgstr ""
+
msgid "Deployment Frequency"
msgstr ""
@@ -7824,6 +8018,9 @@ msgstr ""
msgid "Dismissed on pipeline %{pipelineLink} at %{projectLink}"
msgstr ""
+msgid "Display alerts from all your monitoring tools directly within GitLab."
+msgstr ""
+
msgid "Display name"
msgstr ""
@@ -7881,9 +8078,6 @@ msgstr ""
msgid "Don't show again"
msgstr "Non mostrare più"
-msgid "Don't worry, you can access this tour by clicking on the help icon in the top right corner and choose <strong>Learn GitLab</strong>."
-msgstr ""
-
msgid "Done"
msgstr ""
@@ -8010,6 +8204,9 @@ msgstr "Cambia programmazione della pipeline %{id}"
msgid "Edit Release"
msgstr ""
+msgid "Edit Slack integration"
+msgstr ""
+
msgid "Edit Snippet"
msgstr ""
@@ -8049,6 +8246,9 @@ msgstr ""
msgid "Edit issues"
msgstr ""
+msgid "Edit iteration"
+msgstr ""
+
msgid "Edit public deploy key"
msgstr ""
@@ -8097,6 +8297,9 @@ msgstr ""
msgid "Email"
msgstr ""
+msgid "Email Notification"
+msgstr ""
+
msgid "Email address"
msgstr ""
@@ -8109,6 +8312,9 @@ msgstr ""
msgid "Email not verified. Please verify your email in Salesforce."
msgstr ""
+msgid "Email notification for unknown sign-ins"
+msgstr ""
+
msgid "Email patch"
msgstr ""
@@ -8340,6 +8546,9 @@ msgstr ""
msgid "Enforce DNS rebinding attack protection"
msgstr ""
+msgid "Enforce personal access token expiration"
+msgstr ""
+
msgid "Ensure connectivity is available from the GitLab server to the Prometheus server"
msgstr ""
@@ -8409,6 +8618,9 @@ msgstr ""
msgid "Enter the name of your application, and we'll return a unique %{type}."
msgstr ""
+msgid "Enter weights for storages for new repositories."
+msgstr ""
+
msgid "Enter your password to approve"
msgstr ""
@@ -8418,15 +8630,27 @@ msgstr ""
msgid "Environment does not have deployments"
msgstr ""
+msgid "Environment is required for Stages::MetricEndpointInserter"
+msgstr ""
+
+msgid "Environment is required for Stages::VariableEndpointInserter"
+msgstr ""
+
msgid "Environment scope"
msgstr ""
+msgid "Environment variables are applied to all project environments in this instance via the Runner. You can use environment variables for passwords, secret keys, etc. Make variables available to the running application by prepending the variable key with <code>K8S_SECRET_</code>. You can set variables to be:"
+msgstr ""
+
msgid "Environment variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. Additionally, they can be masked so they are hidden in job logs, though they must match certain regexp requirements to do so. You can use environment variables for passwords, secret keys, or whatever you want."
msgstr ""
msgid "Environment variables are configured by your administrator to be %{link_start}protected%{link_end} by default"
msgstr ""
+msgid "Environment variables on this GitLab instance are configured to be %{link_start}protected%{link_end} by default"
+msgstr ""
+
msgid "Environment:"
msgstr ""
@@ -8958,6 +9182,9 @@ msgstr "Filtra per tutti"
msgid "EventFilterBy|Filter by comments"
msgstr "Filtra per commenti"
+msgid "EventFilterBy|Filter by designs"
+msgstr ""
+
msgid "EventFilterBy|Filter by epic events"
msgstr ""
@@ -9090,15 +9317,15 @@ msgstr ""
msgid "Expand approvers"
msgstr ""
-msgid "Expand child epics"
-msgstr ""
-
msgid "Expand down"
msgstr ""
msgid "Expand dropdown"
msgstr ""
+msgid "Expand milestones"
+msgstr ""
+
msgid "Expand sidebar"
msgstr ""
@@ -9114,6 +9341,9 @@ msgstr ""
msgid "Expiration date"
msgstr ""
+msgid "Expiration not enforced"
+msgstr ""
+
msgid "Expiration policy for the Container Registry is a perfect solution for keeping the Registry space down while still enjoying the full power of GitLab CI/CD."
msgstr ""
@@ -9234,6 +9464,9 @@ msgstr ""
msgid "Failed Jobs"
msgstr ""
+msgid "Failed on"
+msgstr ""
+
msgid "Failed to add a Zoom meeting"
msgstr ""
@@ -9294,13 +9527,19 @@ msgstr ""
msgid "Failed to install."
msgstr ""
+msgid "Failed to load assignees. Please try again."
+msgstr ""
+
+msgid "Failed to load authors. Please try again."
+msgstr ""
+
msgid "Failed to load emoji list."
msgstr ""
msgid "Failed to load error details from Sentry."
msgstr ""
-msgid "Failed to load errors from Sentry. Error message: %{errorMessage}"
+msgid "Failed to load errors from Sentry."
msgstr ""
msgid "Failed to load group activity metrics. Please try again."
@@ -9381,6 +9620,9 @@ msgstr ""
msgid "Failed to set due date because the date format is invalid."
msgstr ""
+msgid "Failed to set iteration on this issue. Please try again."
+msgstr ""
+
msgid "Failed to signing using smartcard authentication"
msgstr ""
@@ -9603,13 +9845,10 @@ msgstr ""
msgid "FeatureFlags|Target environments"
msgstr ""
-msgid "FeatureFlags|There are no active feature flags"
-msgstr ""
-
-msgid "FeatureFlags|There are no inactive feature flags"
+msgid "FeatureFlags|There was an error fetching the feature flags."
msgstr ""
-msgid "FeatureFlags|There was an error fetching the feature flags."
+msgid "FeatureFlags|There was an error retrieving user lists"
msgstr ""
msgid "FeatureFlags|Try again in a few moments or contact your support team."
@@ -9621,9 +9860,18 @@ msgstr ""
msgid "FeatureFlag|Delete strategy"
msgstr ""
+msgid "FeatureFlag|List"
+msgstr ""
+
msgid "FeatureFlag|Percentage"
msgstr ""
+msgid "FeatureFlag|Select a user list"
+msgstr ""
+
+msgid "FeatureFlag|There are no configured user lists"
+msgstr ""
+
msgid "FeatureFlag|Type"
msgstr ""
@@ -9780,6 +10028,12 @@ msgstr ""
msgid "Filter..."
msgstr ""
+msgid "Find File"
+msgstr ""
+
+msgid "Find bugs in your code with coverage-guided fuzzing"
+msgstr ""
+
msgid "Find by path"
msgstr "Trova in percorso"
@@ -10044,6 +10298,9 @@ msgstr ""
msgid "Geo allows you to replicate your GitLab instance to other geographical locations."
msgstr ""
+msgid "Geo nodes are paused using a command run on the node"
+msgstr ""
+
msgid "GeoNodeStatusEvent|%{timeAgoStr} (%{pendingEvents} events)"
msgstr ""
@@ -10128,6 +10385,9 @@ msgstr ""
msgid "GeoNodes|Not checksummed"
msgstr ""
+msgid "GeoNodes|Package files"
+msgstr ""
+
msgid "GeoNodes|Pausing replication stops the sync process. Are you sure?"
msgstr ""
@@ -10146,6 +10406,9 @@ msgstr ""
msgid "GeoNodes|Replication slots"
msgstr ""
+msgid "GeoNodes|Replication status"
+msgstr ""
+
msgid "GeoNodes|Repositories"
msgstr ""
@@ -10356,6 +10619,9 @@ msgstr ""
msgid "Geo|Synchronization failed - %{error}"
msgstr ""
+msgid "Geo|Synchronization of %{itemTitle} is disabled."
+msgstr ""
+
msgid "Geo|The database is currently %{db_lag} behind the primary node."
msgstr ""
@@ -10668,6 +10934,9 @@ msgstr ""
msgid "Go to %{link_to_google_takeout}."
msgstr ""
+msgid "Go to <strong>Issues</strong> > <strong>Boards</strong> to access your personalized learning issue board."
+msgstr ""
+
msgid "Go to Pipelines"
msgstr ""
@@ -10767,9 +11036,6 @@ msgstr ""
msgid "Go to your snippets"
msgstr ""
-msgid "Golden Tanuki"
-msgstr ""
-
msgid "Google Cloud Platform"
msgstr ""
@@ -10791,6 +11057,9 @@ msgstr ""
msgid "Grafana URL"
msgstr ""
+msgid "Grafana response contains invalid json"
+msgstr ""
+
msgid "GrafanaIntegration|API Token"
msgstr ""
@@ -10839,9 +11108,6 @@ msgstr ""
msgid "Group %{group_name} was successfully created."
msgstr ""
-msgid "Group '%{group_name}' is being imported."
-msgstr ""
-
msgid "Group Audit Events"
msgstr ""
@@ -10878,9 +11144,6 @@ msgstr ""
msgid "Group by:"
msgstr ""
-msgid "Group could not be imported: %{errors}"
-msgstr ""
-
msgid "Group description"
msgstr ""
@@ -10908,9 +11171,15 @@ msgstr ""
msgid "Group has not been marked for deletion"
msgstr ""
+msgid "Group import could not be scheduled"
+msgstr ""
+
msgid "Group info:"
msgstr ""
+msgid "Group is required when cluster_type is :group"
+msgstr ""
+
msgid "Group maintainers can register group runners in the %{link}"
msgstr ""
@@ -10974,6 +11243,24 @@ msgstr ""
msgid "GroupActivyMetrics|Recent activity (last 90 days)"
msgstr ""
+msgid "GroupImport|Failed to import group."
+msgstr ""
+
+msgid "GroupImport|Group '%{group_name}' is being imported."
+msgstr ""
+
+msgid "GroupImport|Group could not be imported: %{errors}"
+msgstr ""
+
+msgid "GroupImport|Please wait while we import the group for you. Refresh at will."
+msgstr ""
+
+msgid "GroupImport|The group was successfully imported."
+msgstr ""
+
+msgid "GroupImport|Unable to process group import file"
+msgstr ""
+
msgid "GroupRoadmap|%{dateWord} – No end date"
msgstr ""
@@ -11241,6 +11528,9 @@ msgstr ""
msgid "Groups (%{groups})"
msgstr ""
+msgid "Groups and projects"
+msgstr ""
+
msgid "Groups can also be nested by creating %{subgroup_docs_link_start}subgroups%{subgroup_docs_link_end}."
msgstr ""
@@ -11286,6 +11576,33 @@ msgstr ""
msgid "GroupsEmptyState|You can manage your group member’s permissions and access to each project in the group."
msgstr ""
+msgid "GroupsNew|Contact an administrator to enable options for importing your group."
+msgstr ""
+
+msgid "GroupsNew|Create"
+msgstr ""
+
+msgid "GroupsNew|Create group"
+msgstr ""
+
+msgid "GroupsNew|GitLab group export"
+msgstr ""
+
+msgid "GroupsNew|Import"
+msgstr ""
+
+msgid "GroupsNew|Import group"
+msgstr ""
+
+msgid "GroupsNew|My Awesome Group"
+msgstr ""
+
+msgid "GroupsNew|No import options available"
+msgstr ""
+
+msgid "GroupsNew|To copy a GitLab group between installations, navigate to the group settings page for the original installation, generate an export file, and upload it here."
+msgstr ""
+
msgid "GroupsTree|Are you sure you want to leave the \"%{fullName}\" group?"
msgstr ""
@@ -11388,6 +11705,9 @@ msgstr ""
msgid "Helps reduce request volume for protected paths"
msgstr ""
+msgid "Here are all your projects in your group, including the one you just created. To start, let’s take a look at your personalized learning project which will help you learn about GitLab at your own pace."
+msgstr ""
+
msgid "Here you will find recent merge request activity"
msgstr ""
@@ -11402,6 +11722,9 @@ msgid_plural "Hide charts"
msgstr[0] ""
msgstr[1] ""
+msgid "Hide details"
+msgstr ""
+
msgid "Hide file browser"
msgstr ""
@@ -11411,6 +11734,9 @@ msgstr ""
msgid "Hide host keys manual input"
msgstr ""
+msgid "Hide list"
+msgstr ""
+
msgid "Hide marketing-related entries from help"
msgstr ""
@@ -11431,9 +11757,6 @@ msgstr[1] ""
msgid "Hide values"
msgstr ""
-msgid "Hiding all labels"
-msgstr ""
-
msgid "High or unknown vulnerabilities present"
msgstr ""
@@ -11518,6 +11841,9 @@ msgstr ""
msgid "ID:"
msgstr ""
+msgid "IDE"
+msgstr ""
+
msgid "IDE|Allow live previews of JavaScript projects in the Web IDE using CodeSandbox Live Preview."
msgstr ""
@@ -11608,10 +11934,10 @@ msgstr ""
msgid "If enabled"
msgstr ""
-msgid "If enabled, access to projects will be validated on an external service using their classification label."
+msgid "If enabled, GitLab will handle Object Storage replication using Geo. %{linkStart}More information%{linkEnd}"
msgstr ""
-msgid "If enabled, and if object storage is enabled, GitLab will handle Object Storage replication using Geo"
+msgid "If enabled, access to projects will be validated on an external service using their classification label."
msgstr ""
msgid "If there is no previous license or if the previous license has expired, some GitLab functionality will be blocked until a new, valid license is uploaded."
@@ -11973,6 +12299,9 @@ msgid_plural "Instances"
msgstr[0] ""
msgstr[1] ""
+msgid "Instance Configuration"
+msgstr ""
+
msgid "Instance Statistics visibility"
msgstr ""
@@ -11994,9 +12323,6 @@ msgstr ""
msgid "Integrations"
msgstr ""
-msgid "Integrations allow you to integrate GitLab with other applications"
-msgstr ""
-
msgid "Integrations|All details"
msgstr ""
@@ -12108,6 +12434,9 @@ msgstr ""
msgid "Invalid query"
msgstr ""
+msgid "Invalid repository bundle for snippet with id %{snippet_id}"
+msgstr ""
+
msgid "Invalid repository path"
msgstr ""
@@ -12126,6 +12455,9 @@ msgstr ""
msgid "Invalid two-factor code."
msgstr ""
+msgid "Invalid yaml"
+msgstr ""
+
msgid "Invitation"
msgstr ""
@@ -12345,15 +12677,24 @@ msgstr ""
msgid "It seems like the Dependency Scanning job ran successfully, but no dependencies have been detected in your project."
msgstr ""
+msgid "It seems that there is currently no available data for code coverage"
+msgstr ""
+
msgid "It's you"
msgstr ""
+msgid "Iteration"
+msgstr ""
+
msgid "Iteration changed to"
msgstr ""
msgid "Iteration removed"
msgstr ""
+msgid "Iteration updated"
+msgstr ""
+
msgid "Iterations"
msgstr ""
@@ -12384,12 +12725,18 @@ msgstr "Gen"
msgid "January"
msgstr "Gennaio"
+msgid "Jira Issues"
+msgstr ""
+
msgid "Jira import is already running."
msgstr ""
msgid "Jira integration not configured."
msgstr ""
+msgid "Jira project key is not configured"
+msgstr ""
+
msgid "Jira project: %{importProject}"
msgstr ""
@@ -12570,6 +12917,9 @@ msgstr ""
msgid "Keep divergent refs"
msgstr ""
+msgid "Kerberos access denied"
+msgstr ""
+
msgid "Key"
msgstr ""
@@ -12579,10 +12929,13 @@ msgstr ""
msgid "Key: %{key}"
msgstr ""
-msgid "Keyboard Shortcuts"
+msgid "Keyboard shortcuts"
msgstr ""
-msgid "Keyboard shortcuts"
+msgid "Keys"
+msgstr ""
+
+msgid "Ki"
msgstr ""
msgid "Kubernetes"
@@ -12624,6 +12977,9 @@ msgstr ""
msgid "LDAP"
msgstr ""
+msgid "LDAP Synchronization"
+msgstr ""
+
msgid "LDAP settings"
msgstr ""
@@ -12848,12 +13204,18 @@ msgstr ""
msgid "Learn more about custom project templates"
msgstr ""
+msgid "Learn more about deploying to AWS"
+msgstr ""
+
msgid "Learn more about deploying to a cluster"
msgstr ""
msgid "Learn more about group-level project templates"
msgstr ""
+msgid "Learn more about job dependencies"
+msgstr ""
+
msgid "Learn more about signing commits"
msgstr ""
@@ -12905,9 +13267,21 @@ msgstr ""
msgid "License History"
msgstr ""
+msgid "License ID:"
+msgstr ""
+
+msgid "License URL"
+msgstr ""
+
msgid "License-Check"
msgstr ""
+msgid "LicenseCompliance|%{docLinkStart}License Approvals%{docLinkEnd} are active"
+msgstr ""
+
+msgid "LicenseCompliance|%{docLinkStart}License Approvals%{docLinkEnd} are inactive"
+msgstr ""
+
msgid "LicenseCompliance|Add a license"
msgstr ""
@@ -12929,9 +13303,15 @@ msgstr ""
msgid "LicenseCompliance|Deny"
msgstr ""
+msgid "LicenseCompliance|Learn more about %{linkStart}License Approvals%{linkEnd}"
+msgstr ""
+
msgid "LicenseCompliance|License"
msgstr ""
+msgid "LicenseCompliance|License Approvals"
+msgstr ""
+
msgid "LicenseCompliance|License Compliance detected %d license and policy violation for the source branch only; approval required"
msgid_plural "LicenseCompliance|License Compliance detected %d licenses and policy violations for the source branch only; approval required"
msgstr[0] ""
@@ -13152,6 +13532,9 @@ msgstr ""
msgid "List your Bitbucket Server repositories"
msgstr ""
+msgid "Lists"
+msgstr ""
+
msgid "Live preview"
msgstr ""
@@ -13335,6 +13718,9 @@ msgstr ""
msgid "Manage project labels"
msgstr ""
+msgid "Manage storage usage"
+msgstr ""
+
msgid "Manage two-factor authentication"
msgstr ""
@@ -13350,6 +13736,9 @@ msgstr ""
msgid "Manifest file import"
msgstr ""
+msgid "Manifest import"
+msgstr ""
+
msgid "Manual job"
msgstr ""
@@ -13497,6 +13886,12 @@ msgstr ""
msgid "Maximum field length"
msgstr ""
+msgid "Maximum file size is 2MB. Please select a smaller file."
+msgstr ""
+
+msgid "Maximum import size (MB)"
+msgstr ""
+
msgid "Maximum job timeout"
msgstr ""
@@ -13536,6 +13931,9 @@ msgstr ""
msgid "Maximum size of Elasticsearch bulk indexing requests."
msgstr ""
+msgid "Maximum size of import files."
+msgstr ""
+
msgid "Maximum size of individual attachments in comments."
msgstr ""
@@ -13587,6 +13985,9 @@ msgstr ""
msgid "Merge (when the pipeline succeeds)"
msgstr ""
+msgid "Merge Conflicts"
+msgstr ""
+
msgid "Merge Request"
msgstr ""
@@ -13713,6 +14114,9 @@ msgstr ""
msgid "MergeRequests|Squash task canceled: another squash is already in progress."
msgstr ""
+msgid "MergeRequests|This project does not allow squashing commits when merge requests are accepted."
+msgstr ""
+
msgid "MergeRequests|Thread stays resolved"
msgstr ""
@@ -13866,6 +14270,12 @@ msgstr ""
msgid "MetricsSettings|Add a button to the metrics dashboard linking directly to your existing external dashboard."
msgstr ""
+msgid "MetricsSettings|Choose whether to display dashboard metrics in UTC or the user's local timezone."
+msgstr ""
+
+msgid "MetricsSettings|Dashboard timezone"
+msgstr ""
+
msgid "MetricsSettings|External dashboard URL"
msgstr ""
@@ -13875,6 +14285,12 @@ msgstr ""
msgid "MetricsSettings|Metrics Dashboard"
msgstr ""
+msgid "MetricsSettings|UTC (Coordinated Universal Time)"
+msgstr ""
+
+msgid "MetricsSettings|User's local timezone"
+msgstr ""
+
msgid "Metrics|Add metric"
msgstr ""
@@ -13890,6 +14306,9 @@ msgstr ""
msgid "Metrics|Create metric"
msgstr ""
+msgid "Metrics|Current"
+msgstr ""
+
msgid "Metrics|Delete metric"
msgstr ""
@@ -13940,6 +14359,9 @@ msgstr ""
msgid "Metrics|Max"
msgstr ""
+msgid "Metrics|Min"
+msgstr ""
+
msgid "Metrics|Must be a valid PromQL query."
msgstr ""
@@ -13955,6 +14377,9 @@ msgstr ""
msgid "Metrics|Refresh dashboard"
msgstr ""
+msgid "Metrics|Select a value"
+msgstr ""
+
msgid "Metrics|Star dashboard"
msgstr ""
@@ -13973,12 +14398,18 @@ msgstr ""
msgid "Metrics|There was an error getting annotations information."
msgstr ""
+msgid "Metrics|There was an error getting dashboard validation warnings information."
+msgstr ""
+
msgid "Metrics|There was an error getting deployment information."
msgstr ""
msgid "Metrics|There was an error getting environments information."
msgstr ""
+msgid "Metrics|There was an error getting options for variable \"%{name}\"."
+msgstr ""
+
msgid "Metrics|There was an error trying to validate your query"
msgstr ""
@@ -14021,6 +14452,9 @@ msgstr ""
msgid "Metrics|You're about to permanently delete this metric. This cannot be undone."
msgstr ""
+msgid "Metrics|Your dashboard schema is invalid. Edit the dashboard to correct the YAML schema."
+msgstr ""
+
msgid "Metrics|e.g. HTTP requests"
msgstr ""
@@ -14036,12 +14470,18 @@ msgstr ""
msgid "Metrics|e.g. req/sec"
msgstr ""
+msgid "Mi"
+msgstr ""
+
msgid "Microsoft Azure"
msgstr ""
msgid "Middleman project with Static Site Editor support"
msgstr ""
+msgid "Migrate your data from an external source like GitHub, Bitbucket, or another instance of GitLab."
+msgstr ""
+
msgid "Migrated %{success_count}/%{total_count} files."
msgstr ""
@@ -14176,6 +14616,9 @@ msgstr ""
msgid "Minimum capacity to be available before we schedule more mirrors preemptively."
msgstr ""
+msgid "Minimum interval in days"
+msgstr ""
+
msgid "Minimum length is %{minimum_password_length} characters"
msgstr ""
@@ -14362,6 +14805,12 @@ msgstr ""
msgid "Multiple uploaders found: %{uploader_types}"
msgstr ""
+msgid "Must match with the %{codeStart}external_url%{codeEnd} in %{codeStart}/etc/gitlab/gitlab.rb%{codeEnd}."
+msgstr ""
+
+msgid "Must match with the %{codeStart}geo_node_name%{codeEnd} in %{codeStart}/etc/gitlab/gitlab.rb%{codeEnd}. %{linkStart}More information%{linkEnd}"
+msgstr ""
+
msgid "My Awesome Group"
msgstr ""
@@ -14476,10 +14925,16 @@ msgstr ""
msgid "New Application"
msgstr ""
+msgid "New Branch"
+msgstr ""
+
+msgid "New Deploy Key"
+msgstr ""
+
msgid "New Environment"
msgstr ""
-msgid "New Geo Node"
+msgid "New File"
msgstr ""
msgid "New Group"
@@ -14496,15 +14951,15 @@ msgid_plural "New Issues"
msgstr[0] "Nuovo Issue"
msgstr[1] "Nuovi Issues"
-msgid "New Iteration"
-msgstr ""
-
msgid "New Jira import"
msgstr ""
msgid "New Label"
msgstr ""
+msgid "New Merge Request"
+msgstr ""
+
msgid "New Milestone"
msgstr ""
@@ -14523,6 +14978,9 @@ msgstr ""
msgid "New Snippet"
msgstr ""
+msgid "New User"
+msgstr ""
+
msgid "New branch"
msgstr "Nuova Branch"
@@ -14565,6 +15023,9 @@ msgstr "Nuovo Issue"
msgid "New issue title"
msgstr ""
+msgid "New iteration"
+msgstr ""
+
msgid "New iteration created"
msgstr ""
@@ -14742,6 +15203,12 @@ msgstr ""
msgid "No grouping"
msgstr ""
+msgid "No iteration"
+msgstr ""
+
+msgid "No iterations to show"
+msgstr ""
+
msgid "No job log"
msgstr ""
@@ -14784,6 +15251,9 @@ msgstr ""
msgid "No other labels with such name or description"
msgstr ""
+msgid "No panels matching properties %{opts}"
+msgstr ""
+
msgid "No parent group"
msgstr ""
@@ -14817,6 +15287,9 @@ msgstr ""
msgid "No schedules"
msgstr "Nessuna pianificazione"
+msgid "No stack trace for this error"
+msgstr ""
+
msgid "No starrers matched your search"
msgstr ""
@@ -14829,7 +15302,7 @@ msgstr ""
msgid "No test coverage"
msgstr ""
-msgid "No thanks, don't show this again"
+msgid "No thanks"
msgstr ""
msgid "No vulnerabilities present"
@@ -14844,9 +15317,6 @@ msgstr ""
msgid "No, directly import the existing email addresses and usernames."
msgstr ""
-msgid "No, not interested right now"
-msgstr ""
-
msgid "No. of commits"
msgstr ""
@@ -14892,9 +15362,6 @@ msgstr "Dati insufficienti "
msgid "Not found."
msgstr ""
-msgid "Not helpful"
-msgstr ""
-
msgid "Not now"
msgstr ""
@@ -15051,6 +15518,9 @@ msgstr ""
msgid "Notifications on"
msgstr ""
+msgid "Notify users by email when sign-in location is not recognized"
+msgstr ""
+
msgid "Nov"
msgstr "Nov"
@@ -15117,9 +15587,6 @@ msgstr "Filtra"
msgid "Oh no!"
msgstr ""
-msgid "Ok let's go"
-msgstr ""
-
msgid "Oldest first"
msgstr ""
@@ -15132,16 +15599,49 @@ msgstr ""
msgid "On track"
msgstr ""
+msgid "OnDemandScans|Attached branch"
+msgstr ""
+
+msgid "OnDemandScans|Attached branch is where the scan job runs."
+msgstr ""
+
+msgid "OnDemandScans|Could not run the scan. Please try again."
+msgstr ""
+
msgid "OnDemandScans|Create new DAST scan"
msgstr ""
+msgid "OnDemandScans|DAST will scan the target URL and any discovered sub URLs."
+msgstr ""
+
+msgid "OnDemandScans|New on-demand DAST scan"
+msgstr ""
+
msgid "OnDemandScans|On-demand Scans"
msgstr ""
+msgid "OnDemandScans|On-demand scans run outside the DevOps cycle and find vulnerabilities in your projects. %{learnMoreLinkStart}Learn more%{learnMoreLinkEnd}"
+msgstr ""
+
+msgid "OnDemandScans|Only a passive scan can be performed on demand."
+msgstr ""
+
+msgid "OnDemandScans|Passive DAST Scan"
+msgstr ""
+
+msgid "OnDemandScans|Please enter a valid URL format, ex: http://www.example.com/home"
+msgstr ""
+
+msgid "OnDemandScans|Run this scan"
+msgstr ""
+
+msgid "OnDemandScans|Scan mode"
+msgstr ""
+
msgid "OnDemandScans|Schedule or run scans immediately against target sites. Currently available on-demand scan type: DAST. %{helpLinkStart}More information%{helpLinkEnd}"
msgstr ""
-msgid "Onboarding"
+msgid "OnDemandScans|Target URL"
msgstr ""
msgid "Once imported, repositories can be mirrored over SSH. Read more %{link_start}here%{link_end}."
@@ -15167,6 +15667,9 @@ msgstr ""
msgid "One or more of you personal access tokens were revoked"
msgstr ""
+msgid "One or more of your %{provider} projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
+msgstr ""
+
msgid "One or more of your Bitbucket projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
msgstr ""
@@ -15668,6 +16171,9 @@ msgstr ""
msgid "Parent epic is not present."
msgstr ""
+msgid "Parsing error for param :embed_json. %{message}"
+msgstr ""
+
msgid "Part of merge request changes"
msgstr ""
@@ -15680,6 +16186,9 @@ msgstr ""
msgid "Passed"
msgstr ""
+msgid "Passed on"
+msgstr ""
+
msgid "Password"
msgstr "Password"
@@ -15764,6 +16273,9 @@ msgstr ""
msgid "Perform common operations on GitLab project"
msgstr ""
+msgid "Performance and resource management"
+msgstr ""
+
msgid "Performance optimization"
msgstr ""
@@ -15971,7 +16483,7 @@ msgstr ""
msgid "Pipelines|Something went wrong while cleaning runners cache."
msgstr ""
-msgid "Pipelines|There are currently no %{scope} pipelines."
+msgid "Pipelines|There are currently no finished pipelines."
msgstr ""
msgid "Pipelines|There are currently no pipelines."
@@ -16082,6 +16594,9 @@ msgstr ""
msgid "Pipeline|Stop pipeline #%{pipelineId}?"
msgstr ""
+msgid "Pipeline|Tag name"
+msgstr ""
+
msgid "Pipeline|Trigger author"
msgstr ""
@@ -16163,12 +16678,18 @@ msgstr ""
msgid "Please check your email (%{email}) to verify that you own this address and unlock the power of CI/CD. Didn't receive it? %{resend_link}. Wrong email address? %{update_link}."
msgstr ""
+msgid "Please choose a file"
+msgstr ""
+
msgid "Please choose a group URL with no special characters."
msgstr ""
msgid "Please complete your profile with email address"
msgstr ""
+msgid "Please convert %{linkStart}them to Git%{linkEnd}, and go through the %{linkToImportFlow} again."
+msgstr ""
+
msgid "Please convert them to %{link_to_git}, and go through the %{link_to_import_flow} again."
msgstr ""
@@ -16226,6 +16747,9 @@ msgstr ""
msgid "Please provide a valid email address."
msgstr ""
+msgid "Please provide attributes to update"
+msgstr ""
+
msgid "Please refer to <a href=\"%{docs_url}\">%{docs_url}</a>"
msgstr ""
@@ -16961,6 +17485,9 @@ msgstr ""
msgid "Project has too many %{label_for_message} to search"
msgstr ""
+msgid "Project is required when cluster_type is :project"
+msgstr ""
+
msgid "Project members"
msgstr ""
@@ -17078,7 +17605,40 @@ msgstr ""
msgid "ProjectService|Comment"
msgstr ""
-msgid "ProjectService|Comment will be posted on each event"
+msgid "ProjectService|Event will be triggered by a push to the repository"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a commit is created/updated"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a confidential issue is created/updated/closed"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a deployment finishes"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a merge request is created/updated/merged"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a new tag is pushed to the repository"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a new, unique alert is recorded"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a pipeline status changes"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a wiki page is created/updated"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when an issue is created/updated/closed"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when someone adds a comment"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when someone adds a comment on a confidential issue"
msgstr ""
msgid "ProjectService|Perform common operations on GitLab project: %{project_name}"
@@ -17129,6 +17689,9 @@ msgstr ""
msgid "ProjectSettings|Enable 'Delete source branch' option by default"
msgstr ""
+msgid "ProjectSettings|Enable merge trains and pipelines for merged results"
+msgstr ""
+
msgid "ProjectSettings|Every merge creates a merge commit"
msgstr ""
@@ -17159,7 +17722,7 @@ msgstr ""
msgid "ProjectSettings|Forks"
msgstr ""
-msgid "ProjectSettings|Git Large File Storage"
+msgid "ProjectSettings|Git Large File Storage (LFS)"
msgstr ""
msgid "ProjectSettings|Internal"
@@ -17195,9 +17758,6 @@ msgstr ""
msgid "ProjectSettings|Merge options"
msgstr ""
-msgid "ProjectSettings|Merge pipelines will try to validate the post-merge result prior to merging"
-msgstr ""
-
msgid "ProjectSettings|Merge requests"
msgstr ""
@@ -17225,6 +17785,9 @@ msgstr ""
msgid "ProjectSettings|Pipelines"
msgstr ""
+msgid "ProjectSettings|Pipelines for merge requests must be enabled in the CI/CD configuration file, or pipelines could be unresolvable or dropped"
+msgstr ""
+
msgid "ProjectSettings|Pipelines must succeed"
msgstr ""
@@ -17252,6 +17815,9 @@ msgstr ""
msgid "ProjectSettings|Show link to create/view merge request when pushing from the command line"
msgstr ""
+msgid "ProjectSettings|Skipped pipelines are considered successful"
+msgstr ""
+
msgid "ProjectSettings|Snippets"
msgstr ""
@@ -17267,6 +17833,9 @@ msgstr ""
msgid "ProjectSettings|These checks must pass before merge requests can be merged"
msgstr ""
+msgid "ProjectSettings|This introduces the risk of merging changes that will not pass the pipeline."
+msgstr ""
+
msgid "ProjectSettings|This setting is applied on the server level and can be overridden by an admin."
msgstr ""
@@ -17444,15 +18013,27 @@ msgstr ""
msgid "ProjectsNew|Blank project"
msgstr ""
+msgid "ProjectsNew|Connect your external repository to GitLab CI/CD."
+msgstr ""
+
msgid "ProjectsNew|Contact an administrator to enable options for importing your project."
msgstr ""
msgid "ProjectsNew|Create"
msgstr ""
+msgid "ProjectsNew|Create a blank project to house your files, plan your work, and collaborate on code, among other things."
+msgstr ""
+
+msgid "ProjectsNew|Create blank project"
+msgstr ""
+
msgid "ProjectsNew|Create from template"
msgstr ""
+msgid "ProjectsNew|Create new project"
+msgstr ""
+
msgid "ProjectsNew|Creating project & repository."
msgstr ""
@@ -17477,6 +18058,9 @@ msgstr ""
msgid "ProjectsNew|Project description %{tag_start}(optional)%{tag_end}"
msgstr ""
+msgid "ProjectsNew|Run CI/CD for external repository"
+msgstr ""
+
msgid "ProjectsNew|Template"
msgstr ""
@@ -17738,6 +18322,9 @@ msgstr ""
msgid "Protected Branch"
msgstr ""
+msgid "Protected Branches"
+msgstr ""
+
msgid "Protected Environment"
msgstr ""
@@ -17750,6 +18337,9 @@ msgstr ""
msgid "Protected Tag"
msgstr ""
+msgid "Protected Tags"
+msgstr ""
+
msgid "Protected branches"
msgstr ""
@@ -17849,6 +18439,9 @@ msgstr ""
msgid "Provider"
msgstr ""
+msgid "Proxy support for this API is not available currently"
+msgstr ""
+
msgid "Pseudonymizer data collection"
msgstr ""
@@ -17873,6 +18466,9 @@ msgstr ""
msgid "Publish to status page"
msgstr ""
+msgid "Published on status page"
+msgstr ""
+
msgid "Publishes this issue to the associated status page."
msgstr ""
@@ -18047,6 +18643,9 @@ msgstr ""
msgid "Recipe"
msgstr ""
+msgid "Reconfigure"
+msgstr ""
+
msgid "Recover hidden stage"
msgstr ""
@@ -18133,6 +18732,9 @@ msgstr ""
msgid "Registration|Your profile"
msgstr ""
+msgid "Regulate approvals by authors/committers, based on compliance frameworks. Can be changed only at the instance level."
+msgstr ""
+
msgid "Rejected (closed)"
msgstr ""
@@ -18183,15 +18785,24 @@ msgstr ""
msgid "ReleaseAssetLinkType|Image"
msgstr ""
+msgid "ReleaseAssetLinkType|Images"
+msgstr ""
+
msgid "ReleaseAssetLinkType|Other"
msgstr ""
msgid "ReleaseAssetLinkType|Package"
msgstr ""
+msgid "ReleaseAssetLinkType|Packages"
+msgstr ""
+
msgid "ReleaseAssetLinkType|Runbook"
msgstr ""
+msgid "ReleaseAssetLinkType|Runbooks"
+msgstr ""
+
msgid "Releases"
msgstr ""
@@ -18204,6 +18815,9 @@ msgstr ""
msgid "Releases documentation"
msgstr ""
+msgid "Releases|New Release"
+msgstr ""
+
msgid "Release|Something went wrong while getting the release details"
msgstr ""
@@ -18276,6 +18890,9 @@ msgstr ""
msgid "Remove fork relationship"
msgstr ""
+msgid "Remove from batch"
+msgstr ""
+
msgid "Remove from board"
msgstr ""
@@ -18465,6 +19082,12 @@ msgstr ""
msgid "Replication"
msgstr ""
+msgid "Replication enabled"
+msgstr ""
+
+msgid "Replication paused"
+msgstr ""
+
msgid "Reply by email"
msgstr ""
@@ -18492,15 +19115,15 @@ msgstr ""
msgid "Reported %{timeAgo} by %{reportedBy}"
msgstr ""
-msgid "Reported Resource Changes: %{addNum} to add, %{changeNum} to change, %{deleteNum} to delete"
-msgstr ""
-
msgid "Reporter"
msgstr ""
msgid "Reporting"
msgstr ""
+msgid "Reports"
+msgstr ""
+
msgid "Reports|%{combinedString} and %{resolvedString}"
msgstr ""
@@ -18539,6 +19162,9 @@ msgstr ""
msgid "Reports|Failure"
msgstr ""
+msgid "Reports|Identifier"
+msgstr ""
+
msgid "Reports|Metrics reports are loading"
msgstr ""
@@ -18551,6 +19177,9 @@ msgstr ""
msgid "Reports|Metrics reports failed loading results"
msgstr ""
+msgid "Reports|Scanner"
+msgstr ""
+
msgid "Reports|Severity"
msgstr ""
@@ -18599,15 +19228,33 @@ msgstr ""
msgid "Repository cleanup has started. You will receive an email once the cleanup operation is complete."
msgstr ""
+msgid "Repository files count over the limit"
+msgstr ""
+
+msgid "Repository has an invalid default branch name."
+msgstr ""
+
+msgid "Repository has more than one branch."
+msgstr ""
+
msgid "Repository has no locks."
msgstr ""
+msgid "Repository has tags."
+msgstr ""
+
msgid "Repository maintenance"
msgstr ""
msgid "Repository mirroring"
msgstr ""
+msgid "Repository must contain at least 1 file."
+msgstr ""
+
+msgid "Repository size is above the limit."
+msgstr ""
+
msgid "Repository static objects"
msgstr ""
@@ -18617,7 +19264,7 @@ msgstr ""
msgid "Repository sync capacity"
msgstr ""
-msgid "Repository: %{counter_repositories} / Wikis: %{counter_wikis} / Build Artifacts: %{counter_build_artifacts} / LFS: %{counter_lfs_objects}"
+msgid "Repository: %{counter_repositories} / Wikis: %{counter_wikis} / Build Artifacts: %{counter_build_artifacts} / LFS: %{counter_lfs_objects} / Snippets: %{counter_snippets}"
msgstr ""
msgid "RepositorySettingsAccessLevel|Select"
@@ -18629,6 +19276,9 @@ msgstr "Richiedi accesso"
msgid "Request Headers"
msgstr ""
+msgid "Request details"
+msgstr ""
+
msgid "Request parameter %{param} is missing."
msgstr ""
@@ -18735,6 +19385,9 @@ msgstr ""
msgid "Reset template"
msgstr ""
+msgid "Reset to project defaults"
+msgstr ""
+
msgid "Resetting the authorization key for this project will require updating the authorization key in every alert source it is enabled in."
msgstr ""
@@ -18831,9 +19484,6 @@ msgstr ""
msgid "Resume"
msgstr ""
-msgid "Resume replication"
-msgstr ""
-
msgid "Resync"
msgstr ""
@@ -19456,6 +20106,9 @@ msgstr ""
msgid "SecurityReports|False positive"
msgstr ""
+msgid "SecurityReports|Group Security Dashboard"
+msgstr ""
+
msgid "SecurityReports|Hide dismissed"
msgstr ""
@@ -19510,10 +20163,13 @@ msgstr ""
msgid "SecurityReports|Remove project from dashboard"
msgstr ""
-msgid "SecurityReports|Report type"
+msgid "SecurityReports|Return to dashboard"
msgstr ""
-msgid "SecurityReports|Return to dashboard"
+msgid "SecurityReports|Scan details"
+msgstr ""
+
+msgid "SecurityReports|Scanner type"
msgstr ""
msgid "SecurityReports|Security Dashboard"
@@ -19570,6 +20226,9 @@ msgstr ""
msgid "SecurityReports|There was an error while generating the report."
msgstr ""
+msgid "SecurityReports|Unable to add %{invalidProjectsMessage}"
+msgstr ""
+
msgid "SecurityReports|Unable to add %{invalidProjects}"
msgstr ""
@@ -19684,6 +20343,9 @@ msgstr ""
msgid "Select due date"
msgstr ""
+msgid "Select file"
+msgstr ""
+
msgid "Select group or project"
msgstr ""
@@ -19747,9 +20409,6 @@ msgstr "Seleziona una branch di destinazione"
msgid "Select the branch you want to set as the default for this project. All merge requests and commits will automatically be made against this branch unless you specify a different one."
msgstr "Seleziona il ramo che vuoi impostare come predefinito per questo progetto. Tutte le richieste di merge e i commit verranno automaticamente eseguiti su questo ramo, a meno che non ne sia specificato uno diverso."
-msgid "Select the configured storage available for new repositories to be placed on."
-msgstr ""
-
msgid "Select the custom project template source group."
msgstr ""
@@ -19942,12 +20601,6 @@ msgstr ""
msgid "Service Desk is enabled but not yet active"
msgstr ""
-msgid "Service Desk is off"
-msgstr ""
-
-msgid "Service Desk is on"
-msgstr ""
-
msgid "Service Templates"
msgstr ""
@@ -20035,6 +20688,15 @@ msgstr ""
msgid "Set the milestone to %{milestone_reference}."
msgstr ""
+msgid "Set the number of concurrent requests this secondary node will make to the primary node while backfilling."
+msgstr ""
+
+msgid "Set the synchronization and verification capacity for the secondary node."
+msgstr ""
+
+msgid "Set the timeout in seconds to send a secondary node status to the primary and IPs allowed for the secondary nodes."
+msgstr ""
+
msgid "Set time estimate"
msgstr ""
@@ -20074,6 +20736,9 @@ msgstr ""
msgid "Set weight to %{weight}."
msgstr ""
+msgid "Set what should be replicated by choosing specific projects or groups by the secondary node."
+msgstr ""
+
msgid "SetPasswordToCloneLink|set a password"
msgstr "imposta una password"
@@ -20200,6 +20865,9 @@ msgstr ""
msgid "Show complete raw log"
msgstr ""
+msgid "Show details"
+msgstr ""
+
msgid "Show file browser"
msgstr ""
@@ -20209,10 +20877,13 @@ msgstr ""
msgid "Show latest version"
msgstr ""
+msgid "Show list"
+msgstr ""
+
msgid "Show me everything"
msgstr ""
-msgid "Show me how"
+msgid "Show me how to add a pipeline"
msgstr ""
msgid "Show me more advanced stuff"
@@ -20261,9 +20932,6 @@ msgstr ""
msgid "Showing all issues"
msgstr ""
-msgid "Showing all labels"
-msgstr ""
-
msgid "Showing last %{size} of log -"
msgstr ""
@@ -20375,9 +21043,6 @@ msgstr ""
msgid "Skip outdated deployment jobs"
msgstr ""
-msgid "Skip this for now"
-msgstr ""
-
msgid "Skipped"
msgstr ""
@@ -20522,6 +21187,9 @@ msgstr ""
msgid "Something went wrong while adding your award. Please try again."
msgstr ""
+msgid "Something went wrong while applying the batch of suggestions. Please try again."
+msgstr ""
+
msgid "Something went wrong while applying the suggestion. Please try again."
msgstr ""
@@ -20624,10 +21292,13 @@ msgstr ""
msgid "Something went wrong while updating a requirement."
msgstr ""
+msgid "Something went wrong while updating assignees"
+msgstr ""
+
msgid "Something went wrong while updating your list settings"
msgstr ""
-msgid "Something went wrong with your automatic subscription renewal"
+msgid "Something went wrong with your automatic subscription renewal."
msgstr ""
msgid "Something went wrong, unable to add %{project} to dashboard"
@@ -20645,6 +21316,9 @@ msgstr ""
msgid "Something went wrong, unable to search projects"
msgstr ""
+msgid "Something went wrong."
+msgstr ""
+
msgid "Something went wrong. Please try again."
msgstr ""
@@ -20828,6 +21502,9 @@ msgstr ""
msgid "Source code"
msgstr "Codice Sorgente"
+msgid "Source code (%{fileExtension})"
+msgstr ""
+
msgid "Source is not available"
msgstr ""
@@ -20912,6 +21589,9 @@ msgstr ""
msgid "Stack trace"
msgstr ""
+msgid "Stacktrace snippet"
+msgstr ""
+
msgid "Stage"
msgstr ""
@@ -21017,6 +21697,9 @@ msgstr ""
msgid "Start thread & reopen %{noteable_name}"
msgstr ""
+msgid "Start using Directed Acyclic Graphs (DAG)"
+msgstr ""
+
msgid "Start your Free Gold Trial"
msgstr ""
@@ -21479,6 +22162,21 @@ msgstr ""
msgid "SuggestedColors|Very pale orange"
msgstr ""
+msgid "Suggestion is not applicable as the suggestion was not found."
+msgstr ""
+
+msgid "Suggestions are not applicable as one or more suggestions were not found."
+msgstr ""
+
+msgid "Suggestions are not applicable as their lines cannot overlap."
+msgstr ""
+
+msgid "Suggestions must all be on the same branch."
+msgstr ""
+
+msgid "Suggestions that change line count can't be added to batches, yet."
+msgstr ""
+
msgid "Suggestions:"
msgstr ""
@@ -21521,9 +22219,6 @@ msgstr ""
msgid "Synchronization disabled"
msgstr ""
-msgid "Synchronization of container repositories is disabled."
-msgstr ""
-
msgid "System"
msgstr ""
@@ -21704,6 +22399,34 @@ msgstr ""
msgid "Terms of Service and Privacy Policy"
msgstr ""
+msgid "Terraform|%{number} Terraform report failed to generate"
+msgid_plural "Terraform|%{number} Terraform reports failed to generate"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Terraform|%{number} Terraform report was generated in your pipelines"
+msgid_plural "Terraform|%{number} Terraform reports were generated in your pipelines"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Terraform|A Terraform report failed to generate."
+msgstr ""
+
+msgid "Terraform|A Terraform report was generated in your pipelines."
+msgstr ""
+
+msgid "Terraform|Generating the report caused an error."
+msgstr ""
+
+msgid "Terraform|Reported Resource Changes: %{addNum} to add, %{changeNum} to change, %{deleteNum} to delete"
+msgstr ""
+
+msgid "Terraform|The Terraform report %{name} failed to generate."
+msgstr ""
+
+msgid "Terraform|The Terraform report %{name} was generated in your pipelines."
+msgstr ""
+
msgid "Test"
msgstr ""
@@ -21730,7 +22453,7 @@ msgstr ""
msgid "TestHooks|Ensure the project has CI pipelines."
msgstr ""
-msgid "TestHooks|Ensure the project has at least one commit."
+msgid "TestHooks|Ensure the project has deployments."
msgstr ""
msgid "TestHooks|Ensure the project has issues."
@@ -21831,7 +22554,7 @@ msgstr ""
msgid "The Prometheus server responded with \"bad request\". Please check your queries are correct and are supported in your Prometheus version. %{documentationLink}"
msgstr ""
-msgid "The URL defined on the primary node that secondary nodes should use to contact it. Defaults to URL"
+msgid "The URL defined on the primary node that secondary nodes should use to contact it."
msgstr ""
msgid "The URL to use for connecting to Elasticsearch. Use a comma-separated list to support clustering (e.g., \"http://localhost:9200, http://localhost:9201\")."
@@ -21840,6 +22563,9 @@ msgstr ""
msgid "The X509 Certificate to use when mutual TLS is required to communicate with the external authorization service. If left blank, the server certificate is still validated when accessing over HTTPS."
msgstr ""
+msgid "The above settings apply to all projects with the selected compliance framework(s)."
+msgstr ""
+
msgid "The amount of seconds after which a request to get a secondary node status will time out."
msgstr ""
@@ -22139,9 +22865,6 @@ msgstr "Il tempo aggregato relativo eventi/data entry raccolto in quello stadio.
msgid "The total stage shows the time it takes between creating an issue and deploying the code to production. The data will be automatically added once you have completed the full idea to production cycle."
msgstr ""
-msgid "The unique identifier for the Geo node. Must match %{geoNodeName} if it is set in gitlab.rb, otherwise it must match %{externalUrl} with a trailing slash"
-msgstr ""
-
msgid "The update action will time out after %{number_of_minutes} minutes. For big repositories, use a clone/push combination."
msgstr ""
@@ -22166,9 +22889,6 @@ msgstr ""
msgid "The user you are trying to deactivate has been active in the past %{minimum_inactive_days} days and cannot be deactivated"
msgstr ""
-msgid "The user-facing URL of the Geo node"
-msgstr ""
-
msgid "The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6."
msgstr "Il valore falsato nel mezzo di una serie di dati osservati. ES: tra 3,5,9 il mediano è 5. Tra 3,5,7,8 il mediano è (5+7)/2 quindi 6."
@@ -22259,6 +22979,9 @@ msgstr ""
msgid "There was a problem fetching project branches."
msgstr ""
+msgid "There was a problem fetching project tags."
+msgstr ""
+
msgid "There was a problem fetching project users."
msgstr ""
@@ -22310,6 +23033,9 @@ msgstr ""
msgid "There was an error fetching the %{replicableType}"
msgstr ""
+msgid "There was an error fetching the Geo Settings"
+msgstr ""
+
msgid "There was an error fetching the Node's Groups"
msgstr ""
@@ -22430,6 +23156,12 @@ msgstr ""
msgid "This %{issuable} is locked. Only <strong>project members</strong> can comment."
msgstr ""
+msgid "This %{noteableTypeText} is %{confidentialLinkStart}confidential%{linkEnd} and %{lockedLinkStart}locked%{linkEnd}."
+msgstr ""
+
+msgid "This %{noteableTypeText} is locked."
+msgstr ""
+
msgid "This %{viewer} could not be displayed because %{reason}. You can %{options} instead."
msgstr ""
@@ -22448,6 +23180,9 @@ msgstr ""
msgid "This action can lead to data loss. To prevent accidental actions we ask you to confirm your intention."
msgstr ""
+msgid "This also resolves all related threads"
+msgstr ""
+
msgid "This also resolves the discussion"
msgstr ""
@@ -22538,6 +23273,9 @@ msgstr ""
msgid "This epic does not exist or you don't have sufficient permission."
msgstr ""
+msgid "This feature is currently in beta. We invite you to %{linkStart}give feedback%{linkEnd}."
+msgstr ""
+
msgid "This feature requires local storage to be enabled"
msgstr ""
@@ -22571,7 +23309,7 @@ msgstr ""
msgid "This is a Work in Progress"
msgstr ""
-msgid "This is a confidential issue."
+msgid "This is a confidential %{noteableTypeText}."
msgstr ""
msgid "This is a delayed job to run in %{remainingTime}"
@@ -22598,9 +23336,6 @@ msgstr ""
msgid "This is your current session"
msgstr ""
-msgid "This issue is %{confidentialLinkStart}confidential%{linkEnd} and %{lockedLinkStart}locked%{linkEnd}."
-msgstr ""
-
msgid "This issue is confidential"
msgstr ""
@@ -22610,9 +23345,6 @@ msgstr ""
msgid "This issue is in a child epic of the filtered epic"
msgstr ""
-msgid "This issue is locked."
-msgstr ""
-
msgid "This job depends on other jobs with expired/erased artifacts: %{invalid_dependencies}"
msgstr ""
@@ -22712,6 +23444,9 @@ msgstr ""
msgid "This license has already expired."
msgstr ""
+msgid "This link points to external content"
+msgstr ""
+
msgid "This may expose confidential information as the selected fork is in another namespace that can have other members."
msgstr ""
@@ -22739,6 +23474,9 @@ msgstr ""
msgid "This page will be removed in a future release."
msgstr ""
+msgid "This pipeline does not use the %{codeStart}needs%{codeEnd} keyword and can't be represented as a directed acyclic graph."
+msgstr ""
+
msgid "This pipeline makes use of a predefined CI/CD configuration enabled by %{strongStart}Auto DevOps.%{strongEnd}"
msgstr ""
@@ -22967,6 +23705,9 @@ msgstr ""
msgid "Time from last commit to merge"
msgstr ""
+msgid "Time in seconds"
+msgstr ""
+
msgid "Time in seconds GitLab will wait for a response from the external service. When the service does not respond in time, access will be denied."
msgstr ""
@@ -23388,9 +24129,15 @@ msgstr ""
msgid "Total artifacts size: %{total_size}"
msgstr ""
+msgid "Total cores (CPUs)"
+msgstr ""
+
msgid "Total issues"
msgstr ""
+msgid "Total memory (GB)"
+msgstr ""
+
msgid "Total test time for all commits/merges"
msgstr "Tempo totale di test per tutti i commits/merges"
@@ -23592,6 +24339,9 @@ msgstr ""
msgid "U2F only works with HTTPS-enabled websites. Contact your administrator for more details."
msgstr ""
+msgid "UI Development Kit"
+msgstr ""
+
msgid "URL"
msgstr ""
@@ -23664,9 +24414,6 @@ msgstr ""
msgid "Unable to load the merge request widget. Try reloading the page."
msgstr ""
-msgid "Unable to process group import file"
-msgstr ""
-
msgid "Unable to resolve"
msgstr ""
@@ -23682,6 +24429,9 @@ msgstr ""
msgid "Unable to sign you in to the group with SAML due to \"%{reason}\""
msgstr ""
+msgid "Unable to suggest a path. Please refresh and try again."
+msgstr ""
+
msgid "Unable to update label prioritization at this time"
msgstr ""
@@ -23697,6 +24447,9 @@ msgstr ""
msgid "Unarchiving the project will restore people's ability to make changes to it. The repository can be committed to, and issues, comments, and other entities can be created. %{strong_start}Once active, this project shows up in the search and on the dashboard.%{strong_end}"
msgstr ""
+msgid "Unassign from commenting user"
+msgstr ""
+
msgid "Unblock"
msgstr ""
@@ -23772,6 +24525,9 @@ msgstr ""
msgid "Unreachable"
msgstr ""
+msgid "Unrecognized cluster type"
+msgstr ""
+
msgid "Unresolve"
msgstr ""
@@ -23832,6 +24588,9 @@ msgstr ""
msgid "Unsubscribes from this %{quick_action_target}."
msgstr ""
+msgid "Unsupported todo type passed. Supported todo types are: %{todo_types}"
+msgstr ""
+
msgid "Until"
msgstr ""
@@ -23859,6 +24618,9 @@ msgstr ""
msgid "Update approval rule"
msgstr ""
+msgid "Update approvers"
+msgstr ""
+
msgid "Update failed"
msgstr ""
@@ -23868,6 +24630,9 @@ msgstr ""
msgid "Update it"
msgstr ""
+msgid "Update iteration"
+msgstr ""
+
msgid "Update now"
msgstr ""
@@ -23913,7 +24678,7 @@ msgstr ""
msgid "Updated at"
msgstr ""
-msgid "Updated to"
+msgid "Updated to %{linkStart}chart v%{linkEnd}"
msgstr ""
msgid "Updating"
@@ -23943,6 +24708,9 @@ msgstr ""
msgid "Upload CSV file"
msgstr ""
+msgid "Upload License"
+msgstr ""
+
msgid "Upload New File"
msgstr "Carica un nuovo file"
@@ -24024,16 +24792,19 @@ msgstr ""
msgid "UsageQuota|Pipelines"
msgstr ""
+msgid "UsageQuota|Purchase more storage"
+msgstr ""
+
msgid "UsageQuota|Repositories"
msgstr ""
msgid "UsageQuota|Repository"
msgstr ""
-msgid "UsageQuota|Storage"
+msgid "UsageQuota|Snippets"
msgstr ""
-msgid "UsageQuota|Storage usage:"
+msgid "UsageQuota|Storage"
msgstr ""
msgid "UsageQuota|This namespace has no projects which use shared runners"
@@ -24066,6 +24837,12 @@ msgstr ""
msgid "UsageQuota|Wikis"
msgstr ""
+msgid "UsageQuota|You used: %{usage} %{limit}"
+msgstr ""
+
+msgid "UsageQuota|out of %{formattedLimit} of your namespace storage"
+msgstr ""
+
msgid "Use %{code_start}::%{code_end} to create a %{link_start}scoped label set%{link_end} (eg. %{code_start}priority::1%{code_end})"
msgstr ""
@@ -24132,6 +24909,9 @@ msgstr ""
msgid "User IDs"
msgstr ""
+msgid "User Lists can only be created and modified with %{linkStart}the API%{linkEnd}"
+msgstr ""
+
msgid "User OAuth applications"
msgstr ""
@@ -24156,6 +24936,9 @@ msgstr ""
msgid "User key was successfully removed."
msgstr ""
+msgid "User list %{name} will be removed. Are you sure?"
+msgstr ""
+
msgid "User map"
msgstr ""
@@ -24177,148 +24960,10 @@ msgstr ""
msgid "User was successfully updated."
msgstr ""
-msgid "UserOnboardingTour|%{activeTour}/%{totalTours}"
-msgstr ""
-
-msgid "UserOnboardingTour|%{completed}/%{total} steps completed"
-msgstr ""
-
-msgid "UserOnboardingTour|%{emphasisStart}Well done!%{emphasisEnd}%{lineBreak}%{lineBreak}That's it for our guided tour, congratulations for making it all the way to the end!%{lineBreak}%{lineBreak}We hope this gave you a good overview of GitLab and how it can help you. We'll now show you how to create your own project and invite your colleagues."
-msgstr ""
-
-msgid "UserOnboardingTour|Adding other members to a project is done through Project Settings. Click on %{emphasisStart}Settings%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Alright, that's it for Commits. Let's take a look at the %{emphasisStart}Branches%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Awesome! Now click on %{emphasisStart}Members%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Click on one of the %{emphasisStart}Compare%{emphasisEnd} buttons to compare a branch to master."
-msgstr ""
-
-msgid "UserOnboardingTour|Click on one of the %{emphasisStart}pipeline IDs%{emphasisEnd} to see the details of a pipeline."
-msgstr ""
-
-msgid "UserOnboardingTour|Click to open the latest commit to see its details."
-msgstr ""
-
-msgid "UserOnboardingTour|Close 'Learn GitLab'"
-msgstr ""
-
-msgid "UserOnboardingTour|Commits are shown in chronological order and can be filtered by the commit message or by the branch."
-msgstr ""
-
-msgid "UserOnboardingTour|Create a project"
-msgstr ""
-
-msgid "UserOnboardingTour|Exit 'Learn GitLab'"
-msgstr ""
-
-msgid "UserOnboardingTour|Got it"
-msgstr ""
-
-msgid "UserOnboardingTour|Great job! %{clapHands} We hope the tour was helpful and that you learned how to use GitLab.%{lineBreak}%{lineBreak}We'd love to get your feedback on this tour.%{lineBreak}%{lineBreak}%{emphasisStart}How helpful would you say this guided tour was?%{emphasisEnd}%{lineBreak}%{lineBreak}"
-msgstr ""
-
-msgid "UserOnboardingTour|Guided GitLab Tour"
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can compare the changes of this branch to another one. Changes are divided by files so that it's easier to see what was changed where."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can create a project from scratch, start with a template or import a repository from other platforms. Whatever you choose, we'll guide you through the process.%{lineBreak}%{lineBreak}Fill in your new project information and click on %{emphasisStart}Create Project%{emphasisEnd} to progress to the next step."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can see the breakdown of the pipelines: its stages and jobs in each of the stages and their status.%{lineBreak}%{lineBreak}Our CI/CD pipelines are quite complex, most of our users have fewer and simpler pipelines."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can see the current members of the project (just you at the moment) and invite new members.%{lineBreak}%{lineBreak}You can invite multiple members at once (existing GitLab users or invite by email) and you can also set their roles and permissions.%{lineBreak}%{lineBreak}Add a few members and click on %{emphasisStart}Add to project%{emphasisEnd} to complete this step."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can see what changes were made with this commit, on what branch and if there's a related merge request. The status of the pipeline will also show up if CI/CD is set up.%{lineBreak}%{lineBreak}You can also comment on the lines of code that were changed and start a discussion with your colleagues!"
-msgstr ""
-
-msgid "UserOnboardingTour|Here's an overview of branches in the %{emphasisStart}%{projectName}%{emphasisEnd} project. They're split into Active and Stale.%{lineBreak}%{lineBreak}From here, you can create a new merge request from a branch, or compare the branch to any other branch in the project. By default, it will compare it to the master branch."
-msgstr ""
-
-msgid "UserOnboardingTour|Invite colleagues"
-msgstr ""
-
-msgid "UserOnboardingTour|Issues are great for communicating and keeping track of progress in GitLab. These are all issues that are open in the %{emphasisStart}%{projectName}%{emphasisEnd}.%{lineBreak}%{lineBreak}You can help us improve GitLab by contributing work to issues that are labeled <span class=\"badge color-label accept-mr-label\">Accepting merge requests</span>.%{lineBreak}%{lineBreak}This list can be filtered by labels, milestones, assignees, authors... We'll show you how it looks when the list is filtered by a label."
-msgstr ""
-
-msgid "UserOnboardingTour|Learn GitLab"
-msgstr ""
-
-msgid "UserOnboardingTour|Let's take a closer look at a merge request. Click on the title of one."
-msgstr ""
-
-msgid "UserOnboardingTour|Let's take a closer look at all the commits. Click on %{emphasisStart}Commits%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Let's take a closer look at the repository of this project. Click on %{emphasisStart}Repository%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|No thanks"
-msgstr ""
-
-msgid "UserOnboardingTour|Ok, let's go"
-msgstr ""
-
-msgid "UserOnboardingTour|Ok, show me"
-msgstr ""
-
-msgid "UserOnboardingTour|Open one of the issues by clicking on its title."
-msgstr ""
-
-msgid "UserOnboardingTour|Restart this step"
-msgstr ""
-
-msgid "UserOnboardingTour|Skip this step"
-msgstr ""
-
-msgid "UserOnboardingTour|Sweet! Your project was created and is ready to be used.%{lineBreak}%{lineBreak}You can start adding files to the repository or clone it. One last thing we want to show you is how to invite your colleagues to your new project."
-msgstr ""
-
-msgid "UserOnboardingTour|Take a look. Here's a nifty menu for quickly creating issues, merge requests, snippets, projects and groups. Click on it and select \"New project\" from the \"GitLab\" section to get started."
-msgstr ""
-
-msgid "UserOnboardingTour|Thanks for taking the guided tour. Remember, if you want to go through it again, you can start %{emphasisStart}Learn GitLab%{emphasisEnd} in the help menu on the top right."
-msgstr ""
-
-msgid "UserOnboardingTour|Thanks for the feedback! %{thumbsUp}"
-msgstr ""
-
-msgid "UserOnboardingTour|That's it for issues. Let'st take a look at %{emphasisStart}Merge Requests%{emphasisEnd}."
+msgid "UserList|Delete %{name}?"
msgstr ""
-msgid "UserOnboardingTour|That's it for merge requests. Now for the final part of this guided tour - the %{emphasisStart}CI/CD%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|That's it for the Repository. Let's take a look at the %{emphasisStart}Issues%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|The structure of this page is very similar to issues. Status, description, discussion and the sidebar are all here.%{lineBreak}%{lineBreak}But take a look below the description and you'll notice that there's more information about the merge request, the CI/CD pipeline and the options for approving it.%{lineBreak}%{lineBreak}Alongside the discussion you can also see more information about commits in this merge request, the status of pipelines and review all changes that were made."
-msgstr ""
-
-msgid "UserOnboardingTour|There's a lot of information here but don't worry, we'll go through it.%{lineBreak}%{lineBreak}On the top you can see the status of the issue and when it was opened and by whom. Directly below it is the issue description and below that are other %{emphasisStart}related issues%{emphasisEnd} and %{emphasisStart}merge requests%{emphasisEnd} (if any). Then below that is the %{emphasisStart}discussion%{emphasisEnd}, that's where most of the communication happens.%{lineBreak}%{lineBreak}On the right, there's a sidebar where you can view/change the %{emphasisStart}assignee, milestone, due date, labels, weight%{emphasisEnd}, etc."
-msgstr ""
-
-msgid "UserOnboardingTour|These are all the CI/CD pipelines we have for our %{emphasisStart}%{projectName}%{emphasisEnd} project.%{lineBreak}%{lineBreak}Here you can see the status of each pipeline, for what commit it's running for, its stages and the status for them."
-msgstr ""
-
-msgid "UserOnboardingTour|These are all the issues that are available for community contributions. Let's take a closer look at one of them."
-msgstr ""
-
-msgid "UserOnboardingTour|This is an overview of all merge requests in this project. Similarly to the issues overview it can be filtered down by things like labels, milestones, authors, assignees, etc."
-msgstr ""
-
-msgid "UserOnboardingTour|This is the repository for the %{emphasisStart}%{projectName}%{emphasisEnd} project. All our code is stored here. Feel free to explore and take a closer look at folders and files.%{lineBreak}%{lineBreak}Above the file structure you can see the latest commit, who the author is and the status of the CI/CD pipeline.%{lineBreak}%{lineBreak}If you scroll down below the file structure, you'll find the Readme of this project. This is defined in the README.md file at the root of the repository."
-msgstr ""
-
-msgid "UserOnboardingTour|Welcome to the project overview of the %{emphasisStart}%{projectName}%{emphasisEnd} project. This is the project that we use to work on GitLab. At first, a project seems like a simple repository, but at GitLab, a project is so much more.%{lineBreak}%{lineBreak}You can create projects for hosting your codebase, use it as an issue tracker, collaborate on code, and continuously build, test, and deploy your app with built-in GitLab CI/CD."
+msgid "UserList|created %{timeago}"
msgstr ""
msgid "UserProfile|Activity"
@@ -24471,6 +25116,9 @@ msgstr ""
msgid "UsersSelect|Unassigned"
msgstr ""
+msgid "Using %{codeStart}needs%{codeEnd} allows jobs to run before their stage is reached, as soon as their individual dependencies are met, which speeds up your pipelines."
+msgstr ""
+
msgid "Using %{code_start}::%{code_end} denotes a %{link_start}scoped label set%{link_end}"
msgstr ""
@@ -24555,9 +25203,6 @@ msgstr ""
msgid "Versions"
msgstr ""
-msgid "Very helpful"
-msgstr ""
-
msgid "View Documentation"
msgstr ""
@@ -24706,6 +25351,9 @@ msgstr "Pubblico"
msgid "VisibilityLevel|Unknown"
msgstr "Sconosciuto"
+msgid "Visit settings page"
+msgstr ""
+
msgid "VisualReviewApp|%{stepStart}Step 1%{stepEnd}. Copy the following script:"
msgstr ""
@@ -24799,6 +25447,9 @@ msgstr ""
msgid "VulnerabilityManagement|Something went wrong while trying to delete the comment. Please try again later."
msgstr ""
+msgid "VulnerabilityManagement|Something went wrong while trying to refresh the vulnerability. Please try again later."
+msgstr ""
+
msgid "VulnerabilityManagement|Something went wrong while trying to retrieve the vulnerability history. Please try again later."
msgstr ""
@@ -24841,6 +25492,9 @@ msgstr ""
msgid "Vulnerability|Class"
msgstr ""
+msgid "Vulnerability|Crash Address"
+msgstr ""
+
msgid "Vulnerability|Description"
msgstr ""
@@ -24916,9 +25570,6 @@ msgstr ""
msgid "We couldn't reach the Prometheus server. Either the server no longer exists or the configuration details need updating."
msgstr ""
-msgid "We created a short guided tour that will help you learn the basics of GitLab and how it will help you be better at your job. It should only take a couple of minutes. You will be guided by two types of helpers, best recognized by their color."
-msgstr ""
-
msgid "We detected potential spam in the %{humanized_resource_name}. Please solve the reCAPTCHA to proceed."
msgstr ""
@@ -24952,6 +25603,9 @@ msgstr ""
msgid "We've found no vulnerabilities"
msgstr ""
+msgid "Web Application Firewall"
+msgstr ""
+
msgid "Web IDE"
msgstr ""
@@ -25012,9 +25666,6 @@ msgstr ""
msgid "Welcome to GitLab.com<br>@%{name}!"
msgstr ""
-msgid "Welcome to the Guided GitLab Tour"
-msgstr ""
-
msgid "Welcome to the guided GitLab tour"
msgstr ""
@@ -25062,9 +25713,6 @@ msgstr ""
msgid "While it's rare to have no vulnerabilities, it can happen. In any event, we ask that you please double check your settings to make sure you've set up your dashboard correctly."
msgstr ""
-msgid "White helpers give contextual information."
-msgstr ""
-
msgid "Who can be an approver?"
msgstr ""
@@ -25110,12 +25758,18 @@ msgstr ""
msgid "WikiEmptyIssueMessage|Suggest wiki improvement"
msgstr ""
+msgid "WikiEmptyIssueMessage|You must be a group member in order to add wiki pages. If you have suggestions for how to improve the wiki for this group, consider opening an issue in the %{issues_link}."
+msgstr ""
+
msgid "WikiEmptyIssueMessage|You must be a project member in order to add wiki pages. If you have suggestions for how to improve the wiki for this project, consider opening an issue in the %{issues_link}."
msgstr ""
msgid "WikiEmptyIssueMessage|issue tracker"
msgstr ""
+msgid "WikiEmpty|A wiki is where you can store all the details about your group. This can include why you've created it, its principles, how to use it, and so on."
+msgstr ""
+
msgid "WikiEmpty|A wiki is where you can store all the details about your project. This can include why you've created it, its principles, how to use it, and so on."
msgstr ""
@@ -25125,12 +25779,21 @@ msgstr ""
msgid "WikiEmpty|Suggest wiki improvement"
msgstr ""
+msgid "WikiEmpty|The wiki lets you write documentation for your group"
+msgstr ""
+
msgid "WikiEmpty|The wiki lets you write documentation for your project"
msgstr ""
+msgid "WikiEmpty|This group has no wiki pages"
+msgstr ""
+
msgid "WikiEmpty|This project has no wiki pages"
msgstr ""
+msgid "WikiEmpty|You must be a group member in order to add wiki pages."
+msgstr ""
+
msgid "WikiEmpty|You must be a project member in order to add wiki pages."
msgstr ""
@@ -25407,15 +26070,15 @@ msgstr ""
msgid "You can now export your security dashboard to a CSV report."
msgstr ""
+msgid "You can now manage alert endpoint configuration in the Alerts section on the Operations settings page. Fields on this page have been deprecated."
+msgstr ""
+
msgid "You can now submit a merge request to get this change into the original branch."
msgstr ""
msgid "You can now submit a merge request to get this change into the original project."
msgstr ""
-msgid "You can only add files when you are on a branch"
-msgstr "Puoi aggiungere files solo quando sei in una branch"
-
msgid "You can only edit files when you are on a branch"
msgstr ""
@@ -25635,7 +26298,7 @@ msgstr ""
msgid "You need to upload a Google Takeout archive."
msgstr ""
-msgid "You reached %{usage_in_percent} of %{namespace_name}'s capacity (%{used_storage} of %{storage_limit})"
+msgid "You reached %{usage_in_percent} of %{namespace_name}'s storage capacity (%{used_storage} of %{storage_limit})"
msgstr ""
msgid "You tried to fork %{link_to_the_project} but it failed for the following reason:"
@@ -25938,13 +26601,13 @@ msgstr ""
msgid "Your subscription expired!"
msgstr ""
-msgid "Your subscription has been downgraded"
+msgid "Your subscription has been downgraded."
msgstr ""
-msgid "Your subscription will automatically renew in %{remaining_days}"
+msgid "Your subscription will automatically renew in %{remaining_days}."
msgstr ""
-msgid "Your subscription will expire in %{remaining_days}"
+msgid "Your subscription will expire in %{remaining_days}."
msgstr ""
msgid "Zoom meeting added"
@@ -26024,9 +26687,6 @@ msgstr ""
msgid "branch name"
msgstr ""
-msgid "build pipeline reference mismatch"
-msgstr ""
-
msgid "by"
msgstr ""
@@ -26045,6 +26705,9 @@ msgstr ""
msgid "cannot block others"
msgstr ""
+msgid "cannot contain HTML/XML tags, including any word between angle brackets (<,>)."
+msgstr ""
+
msgid "cannot include leading slash or directory traversal."
msgstr ""
@@ -26075,55 +26738,6 @@ msgstr ""
msgid "ciReport|%{remainingPackagesCount} more"
msgstr ""
-msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability"
-msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability for the source branch only"
-msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities for the source branch only"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerability"
-msgid_plural "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerabilities"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new vulnerability"
-msgid_plural "ciReport|%{reportType} %{status} detected %{newCount} new vulnerabilities"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities for the source branch only"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{fixedCount} fixed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} vulnerability for the source branch only"
-msgid_plural "ciReport|%{reportType} %{status} detected %{newCount} vulnerabilities for the source branch only"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "ciReport|%{reportType} %{status} detected no new vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected no vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected no vulnerabilities for the source branch only"
-msgstr ""
-
msgid "ciReport|%{reportType} is loading"
msgstr ""
@@ -26142,7 +26756,7 @@ msgstr ""
msgid "ciReport|All projects"
msgstr ""
-msgid "ciReport|All report types"
+msgid "ciReport|All scanner types"
msgstr ""
msgid "ciReport|All severities"
@@ -26166,6 +26780,9 @@ msgstr ""
msgid "ciReport|Container scanning detects known vulnerabilities in your docker images."
msgstr ""
+msgid "ciReport|Coverage Fuzzing"
+msgstr ""
+
msgid "ciReport|Create a merge request to implement this solution, or download and apply the patch manually."
msgstr ""
@@ -26324,6 +26941,9 @@ msgstr ""
msgid "customize"
msgstr ""
+msgid "data"
+msgstr ""
+
msgid "date must not be after 9999-12-31"
msgstr ""
@@ -26394,6 +27014,9 @@ msgstr ""
msgid "entries cannot contain HTML tags"
msgstr ""
+msgid "epic"
+msgstr ""
+
msgid "error"
msgstr ""
@@ -26415,9 +27038,6 @@ msgstr ""
msgid "expires on %{timebox_due_date}"
msgstr ""
-msgid "external_url"
-msgstr ""
-
msgid "failed"
msgstr ""
@@ -26453,9 +27073,6 @@ msgstr ""
msgid "from"
msgstr ""
-msgid "geo_node_name"
-msgstr ""
-
msgid "group"
msgstr ""
@@ -26477,9 +27094,6 @@ msgstr ""
msgid "https://your-bitbucket-server"
msgstr ""
-msgid "image"
-msgstr ""
-
msgid "image diff"
msgstr ""
@@ -26623,6 +27237,9 @@ msgstr ""
msgid "load it anyway"
msgstr ""
+msgid "loading"
+msgstr ""
+
msgid "locked by %{path_lock_user_name} %{created_at}"
msgstr ""
@@ -26751,9 +27368,6 @@ msgstr ""
msgid "mrWidget|Deployment statistics are not available currently"
msgstr ""
-msgid "mrWidget|Detect issues before deployment with a CI pipeline that continuously tests your code. We created a quick guide that will show you how to create one. Make your code more secure and more robust in just a minute."
-msgstr ""
-
msgid "mrWidget|Did not close"
msgstr ""
@@ -26916,6 +27530,9 @@ msgstr ""
msgid "mrWidget|To approve this merge request, please enter your password. This project requires all approvals to be authenticated."
msgstr ""
+msgid "mrWidget|Use %{linkStart}CI pipelines to test your code%{linkEnd}, simply add a GitLab CI configuration file to your project. It only takes a minute to make your code more secure and robust."
+msgstr ""
+
msgid "mrWidget|When this merge request is ready, remove the WIP: prefix from the title to allow it to be merged"
msgstr ""
@@ -26952,6 +27569,9 @@ msgstr ""
msgid "must be greater than start date"
msgstr ""
+msgid "must contain only valid frameworks"
+msgstr ""
+
msgid "my-awesome-group"
msgstr ""
@@ -26964,6 +27584,9 @@ msgstr ""
msgid "needs to be between 10 minutes and 1 month"
msgstr ""
+msgid "never"
+msgstr ""
+
msgid "never expires"
msgstr ""
@@ -26991,6 +27614,9 @@ msgstr "Notifiche via email"
msgid "nounSeries|%{firstItem} and %{lastItem}"
msgstr ""
+msgid "nounSeries|%{item}"
+msgstr ""
+
msgid "nounSeries|%{item}, %{nextItem}"
msgstr ""
@@ -27127,6 +27753,9 @@ msgstr ""
msgid "revised"
msgstr ""
+msgid "satisfied"
+msgstr ""
+
msgid "score"
msgstr ""
@@ -27157,6 +27786,9 @@ msgstr ""
msgid "severity|Unknown"
msgstr ""
+msgid "should be an array of %{object_name} objects"
+msgstr ""
+
msgid "should be greater than or equal to %{access} inherited membership from group %{group_name}"
msgstr ""
@@ -27211,10 +27843,13 @@ msgstr ""
msgid "suggestPipeline|2/2: Commit your changes"
msgstr ""
-msgid "suggestPipeline|Commit the changes and your pipeline will automatically run for the first time."
+msgid "suggestPipeline|Choose %{boldStart}Code Quality%{boldEnd} to add a pipeline that tests the quality of your code."
msgstr ""
-msgid "suggestPipeline|We recommend the %{boldStart}Code Quality%{boldEnd} template, which will add a report widget to your Merge Requests. This way you’ll learn about code quality degradations much sooner. %{footerStart} Goodbye technical debt! %{footerEnd}"
+msgid "suggestPipeline|The template is ready! You can now commit it to create your first pipeline."
+msgstr ""
+
+msgid "suggestPipeline|We’re adding a GitLab CI configuration file to add a pipeline to the project. You could create it manually, but we recommend that you start with a GitLab template that works out of the box."
msgstr ""
msgid "syntax is correct"
@@ -27274,6 +27909,9 @@ msgstr ""
msgid "uses Kubernetes clusters to deploy your code!"
msgstr ""
+msgid "v%{version} published %{timeAgo}"
+msgstr ""
+
msgid "verify ownership"
msgstr ""
diff --git a/locale/ja/gitlab.po b/locale/ja/gitlab.po
index 8bced41477f..853657e01cd 100644
--- a/locale/ja/gitlab.po
+++ b/locale/ja/gitlab.po
@@ -2,7 +2,7 @@ msgid ""
msgstr ""
"Project-Id-Version: gitlab-ee\n"
"Report-Msgid-Bugs-To: \n"
-"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Last-Translator: \n"
"Language-Team: Japanese\n"
"Language: ja_JP\n"
"MIME-Version: 1.0\n"
@@ -10,9 +10,11 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=1; plural=0;\n"
"X-Crowdin-Project: gitlab-ee\n"
+"X-Crowdin-Project-ID: 288872\n"
"X-Crowdin-Language: ja\n"
"X-Crowdin-File: /master/locale/gitlab.pot\n"
-"PO-Revision-Date: 2020-06-08 16:58\n"
+"X-Crowdin-File-ID: 6\n"
+"PO-Revision-Date: 2020-07-02 01:24\n"
msgid " %{start} to %{end}"
msgstr " %{start} ã‹ã‚‰ %{end} "
@@ -120,7 +122,7 @@ msgstr[0] "貢献 %d件"
msgid "%d day"
msgid_plural "%d days"
-msgstr[0] ""
+msgstr[0] "%d æ—¥"
msgid "%d error"
msgid_plural "%d errors"
@@ -170,6 +172,10 @@ msgid "%d metric"
msgid_plural "%d metrics"
msgstr[0] "%d メトリクス"
+msgid "%d milestone"
+msgid_plural "%d milestones"
+msgstr[0] ""
+
msgid "%d minute"
msgid_plural "%d minutes"
msgstr[0] "%d 分"
@@ -206,10 +212,6 @@ msgid "%d unresolved thread"
msgid_plural "%d unresolved threads"
msgstr[0] ""
-msgid "%d url scanned"
-msgid_plural "%d urls scanned"
-msgstr[0] ""
-
msgid "%d vulnerability"
msgid_plural "%d vulnerabilities"
msgstr[0] ""
@@ -280,9 +282,15 @@ msgstr[0] "ä¿ç•™ä¸­ã®ã‚³ãƒ¡ãƒ³ãƒˆ%{count} 件"
msgid "%{count} related %{pluralized_subject}: %{links}"
msgstr "%{count} 件ã®é–¢é€£ã—㟠%{pluralized_subject}: %{links}"
+msgid "%{dashboard_path} could not be found."
+msgstr ""
+
msgid "%{days} days until tags are automatically removed"
msgstr "ã‚¿ã‚°ãŒè‡ªå‹•çš„ã«å‰Šé™¤ã•ã‚Œã‚‹ã¾ã§%{days} æ—¥"
+msgid "%{deployLinkStart}Use a template to deploy to ECS%{deployLinkEnd}, or use a docker image to %{commandsLinkStart}run AWS commands in GitLab CI/CD%{commandsLinkEnd}."
+msgstr ""
+
msgid "%{description}- Sentry event: %{errorUrl}- First seen: %{firstSeen}- Last seen: %{lastSeen} %{countLabel}: %{count}%{userCountLabel}: %{userCount}"
msgstr ""
@@ -325,12 +333,18 @@ msgstr "%{group_name} ã¯ã‚°ãƒ«ãƒ¼ãƒ—管ç†ã‚¢ã‚«ã‚¦ãƒ³ãƒˆã‚’使用ã—ã¾ã™ã€‚
msgid "%{host} sign-in from new location"
msgstr ""
-msgid "%{icon}You are about to add %{usersTag} people to the discussion. Proceed with caution."
-msgstr "%{icon}ã“ã®è­°è«–ã« %{usersTag} 人を追加ã—よã†ã¨ã—ã¦ã„ã¾ã™ã€‚注æ„ã—ã¦é€²ã‚ã¦ãã ã•ã„。"
+msgid "%{icon}You are about to add %{usersTag} people to the discussion. They will all receive a notification."
+msgstr ""
+
+msgid "%{integrations_link_start}Integrations%{link_end} enable you to make third-party applications part of your GitLab workflow. If the available integrations don't meet your needs, consider using a %{webhooks_link_start}webhook%{link_end}."
+msgstr ""
msgid "%{issuableType} will be removed! Are you sure?"
msgstr "%{issuableType}を削除ã—ã¾ã™ï¼ã‚ˆã‚ã—ã„ã§ã™ã‹ï¼Ÿ"
+msgid "%{issuesCount} issues in this group"
+msgstr ""
+
msgid "%{issuesSize} issues"
msgstr "%{issuesSize} 件ã®èª²é¡Œ"
@@ -340,7 +354,7 @@ msgstr ""
msgid "%{labelStart}Class:%{labelEnd} %{class}"
msgstr ""
-msgid "%{labelStart}Confidence:%{labelEnd} %{confidence}"
+msgid "%{labelStart}Evidence:%{labelEnd} %{evidence}"
msgstr ""
msgid "%{labelStart}File:%{labelEnd} %{file}"
@@ -358,6 +372,9 @@ msgstr ""
msgid "%{labelStart}Report Type:%{labelEnd} %{reportType}"
msgstr ""
+msgid "%{labelStart}Scanner:%{labelEnd} %{scanner}"
+msgstr ""
+
msgid "%{labelStart}Severity:%{labelEnd} %{severity}"
msgstr ""
@@ -382,9 +399,6 @@ msgstr ""
msgid "%{link_start}Read more%{link_end} about role permissions"
msgstr "ロールã®æ¨©é™ã«ã¤ã„ã¦%{link_start}ã‚‚ã£ã¨èª­ã‚€%{link_end}"
-msgid "%{link} can be used for binding events when something is happening within the project."
-msgstr ""
-
msgid "%{listToShow}, and %{awardsListLength} more."
msgstr "%{listToShow}ã€ãã—ã¦ã•ã‚‰ã« %{awardsListLength} 個。"
@@ -418,6 +432,9 @@ msgstr "%{name} ã«ã¯ %{resultsString} ãŒå«ã¾ã‚Œã¦ã„ã¾ã™"
msgid "%{name} found %{resultsString}"
msgstr "%{name} ã« %{resultsString} ãŒè¦‹ã¤ã‹ã‚Šã¾ã—ãŸ"
+msgid "%{name} is already being used for another emoji"
+msgstr ""
+
msgid "%{name} is scheduled for %{action}"
msgstr ""
@@ -464,6 +481,9 @@ msgstr ""
msgid "%{primary} (%{secondary})"
msgstr "%{primary} (%{secondary})"
+msgid "%{ref} cannot be added: %{error}"
+msgstr ""
+
msgid "%{releases} release"
msgid_plural "%{releases} releases"
msgstr[0] "%{releases} リリース"
@@ -471,13 +491,34 @@ msgstr[0] "%{releases} リリース"
msgid "%{remaining_approvals} left"
msgstr ""
-msgid "%{retryButtonStart}Try again%{retryButtonEnd} or %{newFileButtonStart}attach a new file%{newFileButtonEnd}"
+msgid "%{reportType} %{status} detected %{critical} critical and %{high} high severity vulnerabilities out of %{total}."
+msgstr ""
+
+msgid "%{reportType} %{status} detected %{critical} critical and %{high} high severity vulnerabilities."
+msgstr ""
+
+msgid "%{reportType} %{status} detected %{critical} critical severity vulnerabilities out of %{total}."
msgstr ""
-msgid "%{screenreaderOnlyStart}Keyboard shorcuts%{screenreaderOnlyEnd} Disabled"
+msgid "%{reportType} %{status} detected %{critical} critical severity vulnerability."
+msgid_plural "%{reportType} %{status} detected %{critical} critical severity vulnerabilities."
+msgstr[0] ""
+
+msgid "%{reportType} %{status} detected %{high} high severity vulnerabilities out of %{total}."
+msgstr ""
+
+msgid "%{reportType} %{status} detected %{high} high severity vulnerability."
+msgid_plural "%{reportType} %{status} detected %{high} high severity vulnerabilities."
+msgstr[0] ""
+
+msgid "%{reportType} %{status} detected %{other} vulnerability."
+msgid_plural "%{reportType} %{status} detected %{other} vulnerabilities."
+msgstr[0] ""
+
+msgid "%{reportType} %{status} detected no new vulnerabilities."
msgstr ""
-msgid "%{screenreaderOnlyStart}Keyboard shorcuts%{screenreaderOnlyEnd} Enabled"
+msgid "%{retryButtonStart}Try again%{retryButtonEnd} or %{newFileButtonStart}attach a new file%{newFileButtonEnd}"
msgstr ""
msgid "%{service_title} %{message}."
@@ -569,6 +610,12 @@ msgstr "%{title} ã®å¤‰æ›´"
msgid "%{token}..."
msgstr "%{token}..."
+msgid "%{totalCpu} (%{freeSpacePercentage}%{percentSymbol} free)"
+msgstr ""
+
+msgid "%{totalMemory} (%{freeSpacePercentage}%{percentSymbol} free)"
+msgstr ""
+
msgid "%{totalWeight} total weight"
msgstr ""
@@ -599,6 +646,12 @@ msgstr ""
msgid "%{verb} %{time_spent_value} spent time."
msgstr "%{verb} ã—㦠%{time_spent_value} ãŒçµŒéŽã—ã¾ã—ãŸã€‚"
+msgid "%{webhooks_link_start}%{webhook_type}%{link_end} enable you to send notifications to web applications in response to events in a group or project."
+msgstr ""
+
+msgid "%{webhooks_link_start}%{webhook_type}%{link_end} enable you to send notifications to web applications in response to events in a group or project. We recommend using an %{integrations_link_start}integration%{link_end} in preference to a webhook."
+msgstr ""
+
msgid "'%{level}' is not a valid visibility level"
msgstr "'%{level}' ã¯æœ‰åŠ¹ãªå¯è¦–レベルã§ã¯ã‚ã‚Šã¾ã›ã‚“"
@@ -624,9 +677,6 @@ msgstr "(%{mrCount} 個ã®ãƒžãƒ¼ã‚¸æ¸ˆã¿)"
msgid "(No changes)"
msgstr "(変更ãªã—)"
-msgid "(Show all)"
-msgstr "(全ã¦ã‚’表示)"
-
msgid "(check progress)"
msgstr "(進行状æ³ã‚’確èªã™ã‚‹)"
@@ -690,6 +740,9 @@ msgstr "- 折りãŸãŸã‚€"
msgid "0 for unlimited"
msgstr "0ã¯ç„¡åˆ¶é™ã®æ„味"
+msgid "0 for unlimited, only effective with remote storage enabled."
+msgstr ""
+
msgid "1 %{type} addition"
msgid_plural "%{count} %{type} additions"
msgstr[0] "%{count} %{type} 件ã®è¿½åŠ æƒ…å ±"
@@ -822,6 +875,12 @@ msgstr "<code>\"johnsmith@example.com\": \"johnsm...@example.com\"</code> ã¯ã€
msgid "<code>\"johnsmith@example.com\": \"johnsmith@example.com\"</code> will add \"By <a href=\"#\">johnsmith@example.com</a>\" to all issues and comments originally created by johnsmith@example.com. By default, the email address or username is masked to ensure the user's privacy. Use this option if you want to show the full email address."
msgstr "<code>\"johnsmith@example.com\": \"johnsmith@example.com\"</code> ã¯ã€johnsmith@example.com ãŒä½œæˆã—ãŸå…¨ã¦ã®èª²é¡Œã¨ã‚³ãƒ¡ãƒ³ãƒˆã« \"By <a href=\"#\">johnsmith@example.com</a>\" を追加ã—ã¾ã™ã€‚デフォルトã§ã€ãƒ¡ãƒ¼ãƒ«ã‚¢ãƒ‰ãƒ¬ã‚¹ã‚„ユーザーåã‚’éš ã—ã¦ãƒ¦ãƒ¼ã‚¶ãƒ¼ã®ãƒ—ライãƒã‚·ãƒ¼ã‚’ä¿è­·ã•ã‚Œã¾ã™ã€‚メールアドレスを全ã¦è¡¨ç¤ºã—ãŸã„å ´åˆã€ã“ã®æ–¹æ³•ã‚’指定ã—ã¦ãã ã•ã„。"
+msgid "<code>Masked</code> to prevent the values from being displayed in job logs (must match certain regexp requirements)."
+msgstr ""
+
+msgid "<code>Protected</code> to expose them to protected branches or tags only."
+msgstr ""
+
msgid "<namespace / project>"
msgstr ""
@@ -849,9 +908,6 @@ msgstr "「Runnerã€ã¯ã‚¸ãƒ§ãƒ–を実行ã™ã‚‹ãƒ—ロセスã§ã™ã€‚å¿…è¦ãªæ•°
msgid "A .NET Core console application template, customizable for any .NET Core project"
msgstr "ä»»æ„ã®.NET Coreプロジェクト用ã«ã‚«ã‚¹ã‚¿ãƒžã‚¤ã‚ºå¯èƒ½ãªã€.NET Coreコンソールアプリケーションテンプレート"
-msgid "A DAG must have two dependent jobs to be visualized on this tab."
-msgstr ""
-
msgid "A GitBook site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
msgstr "GitLab ã®ä»£ã‚ã‚Šã« Netlify for CI/CD を使用ã—ã¦ã„ã‚‹ GitBook サイトã§ã™ãŒã€GitLab ã«ã¯ãªã„優れãŸæ©Ÿèƒ½ã‚‚å‚™ãˆã¦ã„ã¾ã™ã€‚"
@@ -882,6 +938,12 @@ msgstr "プロジェクトãŒç©ºã®å ´åˆã¯ãƒ‡ãƒ•ã‚©ãƒ«ãƒˆã®ãƒ–ランãƒã‚’é¸
msgid "A deleted user"
msgstr "削除ã•ã‚ŒãŸãƒ¦ãƒ¼ã‚¶ãƒ¼"
+msgid "A file has been changed."
+msgstr ""
+
+msgid "A file was not found."
+msgstr ""
+
msgid "A file with '%{file_name}' already exists in %{branch} branch"
msgstr "ファイル '%{file_name}' ã¯æ—¢ã« %{branch} ブランãƒã«å­˜åœ¨ã—ã¾ã™"
@@ -948,8 +1010,8 @@ msgstr ""
msgid "A subscription will trigger a new pipeline on the default branch of this project when a pipeline successfully completes for a new tag on the %{default_branch_docs} of the subscribed project."
msgstr "サブスクライブã—ãŸãƒ—ロジェクト㮠%{default_branch_docs} ã§æ–°ã—ã„ã‚¿ã‚°ã®ãƒ‘イプラインãŒæ­£å¸¸ã«å®Œäº†ã™ã‚‹ã¨ã€ã‚µãƒ–スクリプションã«ã‚ˆã£ã¦ã€ã“ã®ãƒ—ロジェクトã®ãƒ‡ãƒ•ã‚©ãƒ«ãƒˆãƒ–ランãƒã§æ–°ã—ã„パイプラインをトリガーã—ã¾ã™ã€‚"
-msgid "A terraform report was generated in your pipelines."
-msgstr "パイプラインã®ä¸­ã§terraformリãƒãƒ¼ãƒˆãŒç”Ÿæˆã•ã‚Œã¾ã—ãŸã€‚"
+msgid "A suggestion is not applicable."
+msgstr ""
msgid "A user with write access to the source branch selected this option"
msgstr "ã“ã®ã‚ªãƒ—ションをé¸æŠžã—ãŸã‚½ãƒ¼ã‚¹ãƒ–ランãƒã¸ã®æ›¸ãè¾¼ã¿ã‚’許å¯ã•ã‚ŒãŸãƒ¦ãƒ¼ã‚¶ãƒ¼"
@@ -1119,6 +1181,9 @@ msgstr "アカウント: %{account}"
msgid "Action to take when receiving an alert."
msgstr "アラートをå—ä¿¡ã—ãŸéš›ã®ã‚¢ã‚¯ã‚·ãƒ§ãƒ³"
+msgid "Actions"
+msgstr ""
+
msgid "Activate"
msgstr "有効化"
@@ -1174,11 +1239,14 @@ msgstr "Kubernetes クラスターを追加"
msgid "Add LICENSE"
msgstr "ライセンスã®è¿½åŠ "
+msgid "Add New Node"
+msgstr ""
+
msgid "Add README"
msgstr "README を追加"
msgid "Add Variable"
-msgstr ""
+msgstr "変数を追加"
msgid "Add Zoom meeting"
msgstr "Zoom ミーティングを追加"
@@ -1244,11 +1312,14 @@ msgid "Add an issue"
msgstr "課題追加"
msgid "Add another link"
-msgstr ""
+msgstr "ä»–ã®ãƒªãƒ³ã‚¯ã‚’追加"
msgid "Add approval rule"
msgstr "承èªãƒ«ãƒ¼ãƒ«ã®è¿½åŠ "
+msgid "Add approvers"
+msgstr ""
+
msgid "Add bold text"
msgstr "太字ã®ãƒ†ã‚­ã‚¹ãƒˆã‚’追加"
@@ -1306,6 +1377,9 @@ msgstr "リクエストを手動ã§è¿½åŠ ã—ã¾ã™"
msgid "Add strikethrough text"
msgstr ""
+msgid "Add suggestion to batch"
+msgstr ""
+
msgid "Add system hook"
msgstr "システムフックã®è¿½åŠ "
@@ -1405,6 +1479,9 @@ msgstr "管ç†è€…エリア"
msgid "Admin Note"
msgstr ""
+msgid "Admin Notifications"
+msgstr ""
+
msgid "Admin Overview"
msgstr "管ç†è€…用概è¦"
@@ -1516,6 +1593,9 @@ msgstr "一般設定ã«ç§»å‹•"
msgid "AdminSettings|Integrations configured here will automatically apply to all projects on this instance."
msgstr ""
+msgid "AdminSettings|Moved to integrations"
+msgstr ""
+
msgid "AdminSettings|No required pipeline"
msgstr "必須パイプラインãªã—"
@@ -1745,12 +1825,18 @@ msgid "Alert"
msgid_plural "Alerts"
msgstr[0] "アラート"
+msgid "AlertManagement| assign yourself"
+msgstr ""
+
msgid "AlertManagement|Acknowledged"
msgstr ""
msgid "AlertManagement|Alert"
msgstr ""
+msgid "AlertManagement|Alert assignee(s): %{assignees}"
+msgstr ""
+
msgid "AlertManagement|Alert detail"
msgstr ""
@@ -1766,9 +1852,15 @@ msgstr ""
msgid "AlertManagement|All alerts"
msgstr ""
+msgid "AlertManagement|Assign To"
+msgstr ""
+
msgid "AlertManagement|Assign status"
msgstr ""
+msgid "AlertManagement|Assignee"
+msgstr ""
+
msgid "AlertManagement|Assignees"
msgstr ""
@@ -1787,9 +1879,6 @@ msgstr ""
msgid "AlertManagement|Edit"
msgstr ""
-msgid "AlertManagement|End time"
-msgstr "終了時間"
-
msgid "AlertManagement|Events"
msgstr ""
@@ -1808,10 +1897,13 @@ msgstr ""
msgid "AlertManagement|More information"
msgstr ""
+msgid "AlertManagement|No Matching Results"
+msgstr ""
+
msgid "AlertManagement|No alert data to display."
msgstr ""
-msgid "AlertManagement|No alerts available to display. If you think you're seeing this message in error, refresh the page."
+msgid "AlertManagement|No alerts available to display. See %{linkStart}enabling alert management%{linkEnd} for more information on adding alerts to the list."
msgstr ""
msgid "AlertManagement|No alerts to display."
@@ -1820,6 +1912,9 @@ msgstr ""
msgid "AlertManagement|None"
msgstr ""
+msgid "AlertManagement|None -"
+msgstr ""
+
msgid "AlertManagement|Open"
msgstr ""
@@ -1856,9 +1951,18 @@ msgstr ""
msgid "AlertManagement|There was an error displaying the alerts. Confirm your endpoint's configuration details to ensure alerts appear."
msgstr ""
+msgid "AlertManagement|There was an error while updating the assignee(s) list. Please try again."
+msgstr ""
+
+msgid "AlertManagement|There was an error while updating the assignee(s) of the alert. Please try again."
+msgstr ""
+
msgid "AlertManagement|There was an error while updating the status of the alert. Please try again."
msgstr ""
+msgid "AlertManagement|This assignee cannot be assigned to this alert."
+msgstr ""
+
msgid "AlertManagement|Tool"
msgstr ""
@@ -2006,6 +2110,9 @@ msgstr "ユーザーãŒGitLabã‚’OAuthプロãƒã‚¤ãƒ€ãƒ¼ã¨ã—ã¦ä½¿ç”¨ã™ã‚‹ã‚ã
msgid "Allow users to request access (if visibility is public or internal)"
msgstr "ユーザーãŒã‚¢ã‚¯ã‚»ã‚¹ã‚’リクエストã§ãるよã†ã«ã™ã‚‹(å¯è¦–性ãŒãƒ‘ブリックã¾ãŸã¯å†…部ã®å ´åˆ)"
+msgid "Allowed Geo IP"
+msgstr ""
+
msgid "Allowed email domain restriction only permitted for top-level groups"
msgstr "メールドメイン制é™ã¯ã€æœ€ä¸Šä½ã‚°ãƒ«ãƒ¼ãƒ—ã«ã®ã¿è¨±å¯ã•ã‚Œã¾ã™"
@@ -2039,6 +2146,9 @@ msgstr "Amazon EKSçµ±åˆã«ã‚ˆã‚Šã€GitLabã‹ã‚‰EKSクラスターをプロビã‚
msgid "Amazon Web Services"
msgstr "アマゾンウェブサービス"
+msgid "Amazon Web Services Logo"
+msgstr ""
+
msgid "Amazon authentication is not %{link_start}correctly configured%{link_end}. Ask your GitLab administrator if you want to use this service."
msgstr "Amazon èªè¨¼ã¯ %{link_start} æ­£ã—ã設定 %{link_end} ã§ãã¦ã„ã¾ã›ã‚“。ã“ã®ã‚µãƒ¼ãƒ“スを使用ã—ãŸã„å ´åˆã€ GitLab ã®ç®¡ç†è€…ã«å•ã„åˆã‚ã›ã¦ãã ã•ã„。"
@@ -2096,11 +2206,17 @@ msgstr "検討ã®è§£æ±ºä¸­ã«ã‚¨ãƒ©ãƒ¼ãŒç™ºç”Ÿã—ã¾ã—ãŸã€‚ã‚‚ã†ä¸€åº¦ã‚„ã‚Š
msgid "An error occurred when updating the issue weight"
msgstr "課題ã®ã‚¦ã‚¨ã‚¤ãƒˆæ›´æ–°æ™‚ã«ã‚¨ãƒ©ãƒ¼ãŒç™ºç”Ÿã—ã¾ã—ãŸ"
+msgid "An error occurred while acknowledging the notification. Refresh the page and try again."
+msgstr ""
+
+msgid "An error occurred while adding approvers"
+msgstr ""
+
msgid "An error occurred while adding formatted title for epic"
msgstr ""
-msgid "An error occurred while checking group path"
-msgstr "グループã®ãƒ‘スã®ãƒã‚§ãƒƒã‚¯ä¸­ã«ã‚¨ãƒ©ãƒ¼ãŒç™ºç”Ÿã—ã¾ã—ãŸ"
+msgid "An error occurred while checking group path. Please refresh and try again."
+msgstr ""
msgid "An error occurred while committing your changes."
msgstr "変更ã®ã‚³ãƒŸãƒƒãƒˆä¸­ã«ã‚¨ãƒ©ãƒ¼ãŒç™ºç”Ÿã—ã¾ã—ãŸã€‚"
@@ -2243,7 +2359,7 @@ msgstr "マージリクエストã®ãƒ­ãƒ¼ãƒ‰ä¸­ã«ã‚¨ãƒ©ãƒ¼ãŒç™ºç”Ÿã—ã¾ã—ãŸ
msgid "An error occurred while loading milestones"
msgstr ""
-msgid "An error occurred while loading terraform report"
+msgid "An error occurred while loading project creation UI"
msgstr ""
msgid "An error occurred while loading the data. Please try again."
@@ -2297,6 +2413,9 @@ msgstr "課題ã®å‰Šé™¤ä¸­ã«ã‚¨ãƒ©ãƒ¼ãŒç™ºç”Ÿã—ã¾ã—ãŸã€‚"
msgid "An error occurred while rendering preview broadcast message"
msgstr "プレビュー時ã®ãƒ–ロードキャストメッセージをレンダリングã«ã‚¨ãƒ©ãƒ¼ãŒç™ºç”Ÿã—ã¾ã—ãŸ"
+msgid "An error occurred while rendering the editor"
+msgstr ""
+
msgid "An error occurred while reordering issues."
msgstr "課題ã®ä¸¦ã¹æ›¿ãˆä¸­ã«ã‚¨ãƒ©ãƒ¼ãŒç™ºç”Ÿã—ã¾ã—ãŸã€‚"
@@ -2393,7 +2512,7 @@ msgstr "Webアプリケーションã®ãƒ¬ãƒ“ューãƒãƒ¼ã‚¸ãƒ§ãƒ³ã‚’分æžã—ã¾
msgid "Analyze your dependencies for known vulnerabilities."
msgstr ""
-msgid "Analyze your source code and git history for secrets"
+msgid "Analyze your source code and git history for secrets."
msgstr ""
msgid "Analyze your source code for known vulnerabilities."
@@ -2507,12 +2626,18 @@ msgstr ""
msgid "Apply suggestion"
msgstr "æ案をé©ç”¨"
+msgid "Apply suggestions"
+msgstr ""
+
msgid "Apply template"
msgstr "テンプレートをé©ç”¨"
msgid "Apply this approval rule to any branch or a specific protected branch."
msgstr ""
+msgid "Applying"
+msgstr ""
+
msgid "Applying a template will replace the existing issue description. Any changes you have made will be lost."
msgstr "テンプレートをé©ç”¨ã™ã‚‹ã¨ã€æ—¢å­˜ã®èª²é¡Œã®èª¬æ˜ŽãŒç½®ãæ›ãˆã‚‰ã‚Œã¾ã™ã€‚ è¡Œã£ãŸå¤‰æ›´ã¯ã™ã¹ã¦å¤±ã‚ã‚Œã¾ã™ã€‚"
@@ -2525,12 +2650,18 @@ msgstr "コマンドを %{commandDescription} ã«é©ç”¨ã™ã‚‹"
msgid "Applying multiple commands"
msgstr "複数ã®ã‚³ãƒžãƒ³ãƒ‰ã‚’é©ç”¨"
-msgid "Applying suggestion"
-msgstr "æ案をé©ç”¨"
+msgid "Applying suggestion..."
+msgstr ""
+
+msgid "Applying suggestions..."
+msgstr ""
msgid "Approval rules"
msgstr "承èªãƒ«ãƒ¼ãƒ«"
+msgid "Approval rules reset to project defaults"
+msgstr ""
+
msgid "ApprovalRuleRemove|%d member"
msgid_plural "ApprovalRuleRemove|%d members"
msgstr[0] "%d 人ã®ãƒ¡ãƒ³ãƒãƒ¼"
@@ -2580,6 +2711,9 @@ msgstr "ç¾åœ¨ã®ãƒžãƒ¼ã‚¸ãƒªã‚¯ã‚¨ã‚¹ãƒˆã‚’承èªã™ã‚‹ã€‚"
msgid "Approved"
msgstr ""
+msgid "Approved MRs"
+msgstr ""
+
msgid "Approved by: "
msgstr ""
@@ -2793,6 +2927,9 @@ msgstr "アセット:"
msgid "Assign"
msgstr "割り当ã¦"
+msgid "Assign Iteration"
+msgstr ""
+
msgid "Assign custom color like #FF0000"
msgstr "#FF0000ã®ã‚ˆã†ãªã‚«ã‚¹ã‚¿ãƒ ã‚«ãƒ©ãƒ¼ã‚’割り当ã¦ã‚‹"
@@ -2811,6 +2948,9 @@ msgstr "ã“ã®ãƒžã‚¤ãƒ«ã‚¹ãƒˆãƒ¼ãƒ³ã«ã„ãã¤ã‹ã®èª²é¡Œã‚’割り当ã¦ã¾ã™
msgid "Assign to"
msgstr "割り当ã¦å…ˆ"
+msgid "Assign to commenting user"
+msgstr ""
+
msgid "Assign yourself to these issues"
msgstr "ã“れらã®èª²é¡Œã‚’自分ã«å‰²ã‚Šå½“ã¦ã¾ã™"
@@ -2836,6 +2976,9 @@ msgid "Assignee"
msgid_plural "%d Assignees"
msgstr[0] "%d 人ã®æ‹…当者"
+msgid "Assignee has no permissions"
+msgstr ""
+
msgid "Assignee lists not available with your current license"
msgstr "ç¾åœ¨ã®ãƒ©ã‚¤ã‚»ãƒ³ã‚¹ã§ã¯æ‹…当者リストを利用ã§ãã¾ã›ã‚“"
@@ -2845,6 +2988,9 @@ msgstr "担当者一覧ã«ã¯ã€é¸æŠžã—ãŸãƒ¦ãƒ¼ã‚¶ãƒ¼ã«å‰²ã‚Šå½“ã¦ã‚‰ã‚Œã¦
msgid "Assignee(s)"
msgstr "担当者"
+msgid "Assignees"
+msgstr ""
+
msgid "Assigns %{assignee_users_sentence}."
msgstr "%{assignee_users_sentence} を割り当ã¦ã¾ã™ã€‚"
@@ -2879,6 +3025,9 @@ msgstr "監査イベント"
msgid "Audit Events is a way to keep track of important events that happened in GitLab."
msgstr "監査イベントã¯ã€GitLab ã§ç™ºç”Ÿã—ãŸé‡è¦ãªã‚¤ãƒ™ãƒ³ãƒˆã‚’追跡ã—続ã‘る方法ã§ã™ã€‚"
+msgid "Audit Log"
+msgstr ""
+
msgid "AuditEvents|(removed)"
msgstr "(削除済ã¿)"
@@ -2895,7 +3044,7 @@ msgid "AuditLogs|(removed)"
msgstr ""
msgid "AuditLogs|Action"
-msgstr ""
+msgstr "アクション"
msgid "AuditLogs|Author"
msgstr ""
@@ -2913,6 +3062,9 @@ msgid "AuditLogs|Group Events"
msgstr ""
msgid "AuditLogs|IP Address"
+msgstr "IPアドレス"
+
+msgid "AuditLogs|Member Events"
msgstr ""
msgid "AuditLogs|No matching %{type} found."
@@ -3197,9 +3349,6 @@ msgstr "ãƒãƒƒã‚¸"
msgid "Badges|e.g. %{exampleUrl}"
msgstr "例 %{exampleUrl}"
-msgid "Badge|New"
-msgstr "æ–°"
-
msgid "Balsamiq file could not be loaded."
msgstr "Balsamiq ファイルを読ã¿è¾¼ã¿ã§ãã¾ã›ã‚“ã§ã—ãŸã€‚"
@@ -3248,6 +3397,12 @@ msgstr "ç¾åœ¨ã®ã‚¤ãƒ³ã‚¹ã‚¿ãƒ³ã‚¹ã®SSHホストキーã®ãƒ•ã‚£ãƒ³ã‚¬ãƒ¼ãƒ—リ
msgid "Below you will find all the groups that are public."
msgstr "以下ã«å…¬é–‹ã•ã‚Œã¦ã„る全グループを表示ã—ã¾ã™ã€‚"
+msgid "Beta"
+msgstr "ベータ"
+
+msgid "Bi-weekly code coverage"
+msgstr ""
+
msgid "Billing"
msgstr "請求"
@@ -3308,9 +3463,15 @@ msgstr "アップグレード"
msgid "Bitbucket Server Import"
msgstr "Bitbucket サーãƒãƒ¼ インãƒãƒ¼ãƒˆ"
+msgid "Bitbucket Server import"
+msgstr ""
+
msgid "Bitbucket import"
msgstr "Bitbucket インãƒãƒ¼ãƒˆ"
+msgid "Blame"
+msgstr ""
+
msgid "Blocked"
msgstr "ブロック中"
@@ -3323,9 +3484,6 @@ msgstr "ブロック"
msgid "Blog"
msgstr "ブログ"
-msgid "Blue helpers indicate an action to be taken."
-msgstr "é’ã„ヘルパーã¯å–ã‚‹ã¹ã行動を示ã—ã¾ã™ã€‚"
-
msgid "Board name"
msgstr "ボードå"
@@ -3524,6 +3682,9 @@ msgstr "ブロードキャストメッセージã¯æ­£å¸¸ã«ä½œæˆã•ã‚Œã¾ã—ãŸ
msgid "Broadcast Message was successfully updated."
msgstr "ブロードキャストメッセージã¯æ­£å¸¸ã«æ›´æ–°ã•ã‚Œã¾ã—ãŸã€‚"
+msgid "Broadcast Messages"
+msgstr ""
+
msgid "Browse Directory"
msgstr "ディレクトリを表示"
@@ -3578,6 +3739,9 @@ msgstr ""
msgid "By %{user_name}"
msgstr "%{user_name} ã«ã‚ˆã‚‹"
+msgid "By URL"
+msgstr ""
+
msgid "By default GitLab sends emails in HTML and plain text formats so mail clients can choose what format to use. Disable this option if you only want to send emails in plain text format."
msgstr "デフォルトã§ã¯ã€GitLabã¯HTMLå½¢å¼ã¨ãƒ—レーンテキスト形å¼ã®ä¸¡ãƒ•ã‚©ãƒ¼ãƒžãƒƒãƒˆã®ãƒ¡ãƒ¼ãƒ«ã‚’é€ä¿¡ã—ã¾ã™ã€‚ãã®ãŸã‚ã€ãƒ¡ãƒ¼ãƒ«ã‚¯ãƒ©ã‚¤ã‚¢ãƒ³ãƒˆã¯ã©ã¡ã‚‰ã®ãƒ•ã‚©ãƒ¼ãƒžãƒƒãƒˆã‚’表示ã™ã‚‹ã‹ã‚’é¸ã¹ã¾ã™ã€‚ã‚‚ã—プレーンテキスト形å¼ã ã‘ã§ãƒ¡ãƒ¼ãƒ«ã‚’é€ä¿¡ã—ãŸã„å ´åˆã¯ã€ã“ã®ã‚ªãƒ—ションを無効ã«ã—ã¾ã™"
@@ -3683,6 +3847,9 @@ msgstr ""
msgid "Can override approvers and approvals required per merge request"
msgstr "マージリクエストã”ã¨ã«å¿…è¦ãªæ‰¿èªè€…ã¨æ‰¿èªã‚’上書ãã§ãã¾ã™"
+msgid "Can't apply as this line has changed or the suggestion already matches its content."
+msgstr ""
+
msgid "Can't create snippet: %{err}"
msgstr ""
@@ -3869,9 +4036,6 @@ msgstr "<b>source</b>リビジョンãŒ<b>target</b>リビジョン内ã«å–ã‚Šè
msgid "Changes are still tracked. Useful for cluster/index migrations."
msgstr ""
-msgid "Changes are unknown"
-msgstr ""
-
msgid "Changes suppressed. Click to show."
msgstr "変更ã¯æŠ‘制ã•ã‚Œã¾ã—ãŸã€‚クリックã—ã¦è¡¨ç¤º"
@@ -3891,7 +4055,7 @@ msgid "Channel handle (e.g. town-square)"
msgstr ""
msgid "Charts"
-msgstr ""
+msgstr "ãƒãƒ£ãƒ¼ãƒˆ"
msgid "Charts can't be displayed as the request for data has timed out. %{documentationLink}"
msgstr "グラフを表示ã§ãã¾ã›ã‚“。データリクエストãŒã‚¿ã‚¤ãƒ ã‚¢ã‚¦ãƒˆã—ãŸãŸã‚ã§ã™ã€‚%{documentationLink}"
@@ -3954,7 +4118,7 @@ msgid "Check the %{docs_link_start}documentation%{docs_link_end}."
msgstr "%{docs_link_start}ドキュメント%{docs_link_end}を確èª"
msgid "Check your .gitlab-ci.yml"
-msgstr ".gitlab-ci.yml を確èªã—ã¦ãã ã•ã„"
+msgstr ".gitlab-ci.yml ã‚’ãƒã‚§ãƒƒã‚¯"
msgid "Check your Docker images for known vulnerabilities."
msgstr ""
@@ -4178,15 +4342,9 @@ msgstr "表示レベルã€ãƒ—ロジェクト機能(課題ã€ãƒªãƒã‚¸ãƒˆãƒªã€Wi
msgid "Choose what content you want to see on a group’s overview page"
msgstr "グループã®æ¦‚è¦ãƒšãƒ¼ã‚¸ã«è¡¨ç¤ºã—ãŸã„コンテンツをé¸æŠžã—ã¦ãã ã•ã„"
-msgid "Choose which groups you wish to synchronize to this secondary node"
-msgstr ""
-
msgid "Choose which repositories you want to connect and run CI/CD pipelines."
msgstr "CI/CD パイプラインを実行ã—ãŸã„リãƒã‚¸ãƒˆãƒªã‚’é¸æŠžã—ã¦ãã ã•ã„。"
-msgid "Choose which shards you wish to synchronize to this secondary node"
-msgstr ""
-
msgid "Choose your framework"
msgstr ""
@@ -4484,9 +4642,15 @@ msgstr "クラスターã®ã‚­ãƒ£ãƒƒã‚·ãƒ¥ã‚’削除ã—ã¾ã—ãŸ"
msgid "Cluster does not exist"
msgstr ""
+msgid "Cluster is required for Stages::ClusterEndpointInserter"
+msgstr ""
+
msgid "Cluster level"
msgstr ""
+msgid "Cluster type must be specificed for Stages::ClusterEndpointInserter"
+msgstr ""
+
msgid "ClusterIntegration| %{custom_domain_start}More information%{custom_domain_end}."
msgstr "%{custom_domain_start} 詳細情報 %{custom_domain_end}"
@@ -4598,8 +4762,8 @@ msgstr "CA 証明書"
msgid "ClusterIntegration|Cert-Manager"
msgstr "Cert-Manager"
-msgid "ClusterIntegration|Cert-Manager is a native Kubernetes certificate management controller that helps with issuing certificates. Installing Cert-Manager on your cluster will issue a certificate by %{letsEncrypt} and ensure that certificates are valid and up-to-date."
-msgstr "Cert-Managerã¯ã€è¨¼æ˜Žæ›¸ã®ç™ºè¡Œã‚’支æ´ã™ã‚‹Kubernetes用ã®è¨¼æ˜Žæ›¸ç®¡ç†ã‚³ãƒ³ãƒˆãƒ­ãƒ¼ãƒ©ã§ã™ã€‚クラスタã«Cert-Managerをインストールã™ã‚‹ã¨ã€%{letsEncrypt} ã‹ã‚‰è¨¼æ˜Žæ›¸ãŒç™ºè¡Œã•ã‚Œã€è¨¼æ˜Žæ›¸ãŒæœ‰åŠ¹ã§æœ€æ–°ã®çŠ¶æ…‹ã‚’維æŒã—ã¾ã™ã€‚"
+msgid "ClusterIntegration|Cert-Manager is a native Kubernetes certificate management controller that helps with issuing certificates. Installing Cert-Manager on your cluster will issue a certificate by %{linkStart}Let's Encrypt%{linkEnd} and ensure that certificates are valid and up-to-date."
+msgstr ""
msgid "ClusterIntegration|Certificate Authority bundle (PEM format)"
msgstr "èªè¨¼å±€ãƒãƒ³ãƒ‰ãƒ« (PEMå½¢å¼)"
@@ -4625,9 +4789,6 @@ msgstr "クラスターã®ã‚­ãƒ£ãƒƒã‚·ãƒ¥ã‚’削除"
msgid "ClusterIntegration|Clear the local cache of namespace and service accounts. This is necessary if your integration has become out of sync. The cache is repopulated during the next CI job that requires namespace and service accounts."
msgstr "åå‰ç©ºé–“ãŠã‚ˆã³ã‚µãƒ¼ãƒ“スアカウントã®ãƒ­ãƒ¼ã‚«ãƒ«ã‚­ãƒ£ãƒƒã‚·ãƒ¥ã‚’クリアã—ã¾ã™ã€‚ã“ã‚Œã¯ã‚¤ãƒ³ãƒ†ã‚°ãƒ¬ãƒ¼ã‚·ãƒ§ãƒ³ãŒåŒæœŸã•ã‚Œã¦ã„ãªã„å ´åˆã«å¿…è¦ã§ã™ã€‚キャッシュã¯ã€ãƒãƒ¼ãƒ ã‚¹ãƒšãƒ¼ã‚¹ãŠã‚ˆã³ã‚µãƒ¼ãƒ“スアカウントを必è¦ã¨ã™ã‚‹æ¬¡å›žã®CIジョブ中ã«å†ä½œæˆã•ã‚Œã¾ã™ã€‚"
-msgid "ClusterIntegration|Cloud Run"
-msgstr "Cloud Run"
-
msgid "ClusterIntegration|Cluster being created"
msgstr ""
@@ -4712,7 +4873,7 @@ msgstr "Kubernetesクラスターã®ä½œæˆ"
msgid "ClusterIntegration|Crossplane"
msgstr "Crossplane"
-msgid "ClusterIntegration|Crossplane enables declarative provisioning of managed services from your cloud of choice using %{codeStart}kubectl%{codeEnd} or %{gitlabIntegrationLink}. Crossplane runs inside your Kubernetes cluster and supports secure connectivity and secrets management between app containers and the cloud services they depend on."
+msgid "ClusterIntegration|Crossplane enables declarative provisioning of managed services from your cloud of choice using %{codeStart}kubectl%{codeEnd} or %{linkStart}GitLab Integration%{linkEnd}. Crossplane runs inside your Kubernetes cluster and supports secure connectivity and secrets management between app containers and the cloud services they depend on."
msgstr ""
msgid "ClusterIntegration|Deletes all GitLab resources attached to this cluster during removal"
@@ -4728,7 +4889,7 @@ msgid "ClusterIntegration|Elastic Stack"
msgstr "Elastic Stack"
msgid "ClusterIntegration|Enable Cloud Run for Anthos"
-msgstr ""
+msgstr "Cloud Run for Anthos 有効化"
msgid "ClusterIntegration|Enable or disable GitLab's connection to your Kubernetes cluster."
msgstr "Kubernetes クラスターã¸ã® GitLab 接続を有効ã¾ãŸã¯ç„¡åŠ¹ã«ã—ã¾ã™ã€‚"
@@ -4796,9 +4957,6 @@ msgstr "GitLab Runner ã¯ã“ã®ãƒªãƒã‚¸ãƒˆãƒªã«æŽ¥ç¶šã—ã€CI / CD ジョブã‚
msgid "ClusterIntegration|GitLab-managed cluster"
msgstr "GitLabマãƒãƒ¼ã‚¸ãƒ‰ã‚¯ãƒ©ã‚¹ã‚¿ãƒ¼"
-msgid "ClusterIntegration|Gitlab Integration"
-msgstr "GitLab çµ±åˆ"
-
msgid "ClusterIntegration|Global default"
msgstr ""
@@ -4844,8 +5002,8 @@ msgstr "Ingress エンドãƒã‚¤ãƒ³ãƒˆ"
msgid "ClusterIntegration|Ingress gives you a way to route requests to services based on the request host or path, centralizing a number of services into a single entrypoint."
msgstr "Ingressã«ã‚ˆã£ã¦ã€è¦æ±‚ホストã¾ãŸã¯ãƒ‘スã«åŸºã¥ã„ã¦è¦æ±‚をサービスã«ãƒ«ãƒ¼ãƒ†ã‚£ãƒ³ã‚°ã—ã€å¤šæ•°ã®ã‚µãƒ¼ãƒ“スをå˜ä¸€ã®ã‚¨ãƒ³ãƒˆãƒªãƒã‚¤ãƒ³ãƒˆã«é›†ä¸­ã•ã›ã‚‹ã“ã¨ãŒã§ãã¾ã™ã€‚"
-msgid "ClusterIntegration|Installing Ingress may incur additional costs. Learn more about %{pricingLink}."
-msgstr "Ingress をインストールã™ã‚‹ã¨è¿½åŠ ã®ã‚³ã‚¹ãƒˆãŒã‹ã‹ã‚‹å¯èƒ½æ€§ãŒã‚ã‚Šã¾ã™ã€‚ 詳細ã«ã¤ã„ã¦ã¯ %{pricingLink} ã‚’å‚ç…§"
+msgid "ClusterIntegration|Installing Ingress may incur additional costs. Learn more about %{linkStart}pricing%{linkEnd}."
+msgstr ""
msgid "ClusterIntegration|Instance cluster"
msgstr "インスタンスクラスター"
@@ -4859,8 +5017,8 @@ msgstr "Kubernetes クラスターを自動統åˆ"
msgid "ClusterIntegration|Issuer Email"
msgstr "発行者メール"
-msgid "ClusterIntegration|Issuers represent a certificate authority. You must provide an email address for your Issuer. "
-msgstr "発行者ã¯èªè¨¼å±€ã‚’表ã—ã¾ã™ã€‚ 発行者ã®Eメールアドレスを入力ã™ã‚‹å¿…è¦ãŒã‚ã‚Šã¾ã™ã€‚ "
+msgid "ClusterIntegration|Issuers represent a certificate authority. You must provide an email address for your Issuer."
+msgstr ""
msgid "ClusterIntegration|Jupyter Hostname"
msgstr "Jupyter ã®ãƒ›ã‚¹ãƒˆå"
@@ -4931,9 +5089,6 @@ msgstr "グループ Kubernetes クラスターã®è©³ç´°"
msgid "ClusterIntegration|Learn more about instance Kubernetes clusters"
msgstr "Kubernetes クラスタã®ã‚¤ãƒ³ã‚¹ã‚¿ãƒ³ã‚¹ã®è©³ç´°"
-msgid "ClusterIntegration|Let's Encrypt"
-msgstr "Let's Encrypt"
-
msgid "ClusterIntegration|Loading IAM Roles"
msgstr "IAMロールã®ãƒ­ãƒ¼ãƒ‰"
@@ -5045,8 +5200,8 @@ msgstr "プロジェクトã®åå‰ç©ºé–“ã®ãƒ—レフィックス (オプショã
msgid "ClusterIntegration|Prometheus"
msgstr "Prometheus"
-msgid "ClusterIntegration|Prometheus is an open-source monitoring system with %{gitlabIntegrationLink} to monitor deployed applications."
-msgstr "Prometheus ã¯ã€ãƒ‡ãƒ—ロイã—ãŸã‚¢ãƒ—リケーションを監視ã™ã‚‹ãŸã‚ã®ã‚ªãƒ¼ãƒ—ンソース監視システムã§ã™ã€‚ %{gitlabIntegrationLink}"
+msgid "ClusterIntegration|Prometheus is an open-source monitoring system with %{linkStart}GitLab Integration%{linkEnd} to monitor deployed applications."
+msgstr ""
msgid "ClusterIntegration|Provider details"
msgstr ""
@@ -5201,10 +5356,10 @@ msgstr "ゾーンをé¸æŠž"
msgid "ClusterIntegration|Select zone to choose machine type"
msgstr "ゾーンをé¸æŠžã—ã¦ãƒžã‚·ãƒ³ã‚¿ã‚¤ãƒ—ã‚’é¸æŠž"
-msgid "ClusterIntegration|Send Cilium Logs"
+msgid "ClusterIntegration|Send Container Network Policies Logs"
msgstr ""
-msgid "ClusterIntegration|Send ModSecurity Logs"
+msgid "ClusterIntegration|Send Web Application Firewall Logs"
msgstr ""
msgid "ClusterIntegration|Service Token"
@@ -5372,15 +5527,12 @@ msgstr "Google Kubernetes Engineã«ã‚¢ã‚¯ã‚»ã‚¹"
msgid "ClusterIntegration|documentation"
msgstr "ドキュメント"
-msgid "ClusterIntegration|installed via %{installed_via}"
-msgstr "%{installed_via} ã§ã‚¤ãƒ³ã‚¹ãƒˆãƒ¼ãƒ«"
+msgid "ClusterIntegration|installed via %{linkStart}Cloud Run%{linkEnd}"
+msgstr ""
msgid "ClusterIntegration|meets the requirements"
msgstr "å¿…è¦æ¡ä»¶"
-msgid "ClusterIntegration|pricing"
-msgstr "価格"
-
msgid "ClusterIntegration|sign up"
msgstr "æ–°è¦ç™»éŒ²"
@@ -5417,6 +5569,15 @@ msgstr ""
msgid "Code"
msgstr "コード"
+msgid "Code Coverage: %{coveragePercentage}%{percentSymbol}"
+msgstr ""
+
+msgid "Code Coverage| Empty code coverage data"
+msgstr ""
+
+msgid "Code Coverage|Couldn't fetch the code coverage data"
+msgstr ""
+
msgid "Code Owners"
msgstr "コードオーナー"
@@ -5471,7 +5632,7 @@ msgstr "折りãŸãŸã‚€"
msgid "Collapse approvers"
msgstr "承èªè€…ã‚’éžè¡¨ç¤ºã«ã™ã‚‹"
-msgid "Collapse child epics"
+msgid "Collapse milestones"
msgstr ""
msgid "Collapse replies"
@@ -5489,6 +5650,9 @@ msgstr "ComboSearch ãŒå®šç¾©ã•ã‚Œã¦ã„ã¾ã›ã‚“"
msgid "Coming soon"
msgstr ""
+msgid "Comma-separated, e.g. '1.1.1.1, 2.2.2.0/24'"
+msgstr ""
+
msgid "Command"
msgstr "コマンド"
@@ -5658,6 +5822,12 @@ msgstr ""
msgid "Compliance framework (optional)"
msgstr ""
+msgid "Compliance frameworks"
+msgstr ""
+
+msgid "ComplianceDashboard|created by:"
+msgstr ""
+
msgid "ComplianceFramework|GDPR"
msgstr ""
@@ -5796,6 +5966,9 @@ msgstr "接続ã«å¤±æ•—ã—ã¾ã—ãŸ"
msgid "Connection timed out"
msgstr "接続タイムアウト"
+msgid "Connection timeout"
+msgstr ""
+
msgid "Contact sales to upgrade"
msgstr "アップグレードã®å•ã„åˆã‚ã›"
@@ -5849,9 +6022,6 @@ msgstr ""
msgid "ContainerRegistry|CLI Commands"
msgstr ""
-msgid "ContainerRegistry|Compressed Size"
-msgstr ""
-
msgid "ContainerRegistry|Container Registry"
msgstr "コンテナレジストリ"
@@ -5867,6 +6037,9 @@ msgstr "ログインコマンドã®ã‚³ãƒ”ー"
msgid "ContainerRegistry|Copy push command"
msgstr "プッシュコマンドをコピー"
+msgid "ContainerRegistry|Delete selected"
+msgstr ""
+
msgid "ContainerRegistry|Docker connection error"
msgstr "Docker接続エラー"
@@ -5900,18 +6073,18 @@ msgstr ""
msgid "ContainerRegistry|If you are not already logged in, you need to authenticate to the Container Registry by using your GitLab username and password. If you have %{twofaDocLinkStart}Two-Factor Authentication%{twofaDocLinkEnd} enabled, use a %{personalAccessTokensDocLinkStart}Personal Access Token%{personalAccessTokensDocLinkEnd} instead of a password."
msgstr "ã¾ã ãƒ­ã‚°ã‚¤ãƒ³ã—ã¦ã„ãªã„å ´åˆã€GitLab ã®ãƒ¦ãƒ¼ã‚¶ãƒ¼åã¨ãƒ‘スワードを使用ã—ã¦ã‚³ãƒ³ãƒ†ãƒŠãƒ¬ã‚¸ã‚¹ãƒˆãƒªã‚’èªè¨¼ã™ã‚‹å¿…è¦ãŒã‚ã‚Šã¾ã™ã€‚ %{twofaDocLinkStart} 2è¦ç´ èªè¨¼ã‚’ %{twofaDocLinkEnd} 有効ã«ã—ã¦ã„ã‚‹å ´åˆã¯ã€ãƒ‘スワードã®ä»£ã‚ã‚Šã« %{personalAccessTokensDocLinkStart} パーソナルアクセストークン%{personalAccessTokensDocLinkEnd} 使用ã—ã¾ã™ã€‚"
-msgid "ContainerRegistry|Image ID"
-msgstr "イメージID"
+msgid "ContainerRegistry|Image ID: %{imageId}"
+msgstr ""
msgid "ContainerRegistry|Image Repositories"
msgstr ""
+msgid "ContainerRegistry|Image tags"
+msgstr ""
+
msgid "ContainerRegistry|Keep and protect the images that matter most."
msgstr "é‡è¦ãªç”»åƒã‚’ä¿æŒãŠã‚ˆã³ä¿è­·ã—ã¾ã™ã€‚"
-msgid "ContainerRegistry|Last Updated"
-msgstr "最終更新日"
-
msgid "ContainerRegistry|Login"
msgstr ""
@@ -5924,6 +6097,9 @@ msgstr "ä¿æŒã™ã‚‹ã‚¿ã‚°ã®æ•°:"
msgid "ContainerRegistry|Please contact your administrator."
msgstr ""
+msgid "ContainerRegistry|Published %{timeInfo}"
+msgstr ""
+
msgid "ContainerRegistry|Push an image"
msgstr ""
@@ -5939,9 +6115,6 @@ msgstr ""
msgid "ContainerRegistry|Remove repository"
msgstr "リãƒã‚¸ãƒˆãƒªã®å‰Šé™¤"
-msgid "ContainerRegistry|Remove selected tags"
-msgstr "é¸æŠžã—ãŸã‚¿ã‚°ã®å‰Šé™¤"
-
msgid "ContainerRegistry|Remove tag"
msgid_plural "ContainerRegistry|Remove tags"
msgstr[0] "ã‚¿ã‚°ã®å‰Šé™¤"
@@ -5970,9 +6143,6 @@ msgstr "有効期é™ãƒãƒªã‚·ãƒ¼ã®æ›´æ–°ä¸­ã«å•é¡ŒãŒç™ºç”Ÿã—ã¾ã—ãŸã€‚"
msgid "ContainerRegistry|Sorry, your filter produced no results."
msgstr ""
-msgid "ContainerRegistry|Tag"
-msgstr "ã‚¿ã‚°"
-
msgid "ContainerRegistry|Tag expiration policy"
msgstr "ã‚¿ã‚°ã®æœ‰åŠ¹æœŸé™ãƒãƒªã‚·ãƒ¼"
@@ -6117,21 +6287,6 @@ msgstr "アカウントã«ç´ã¥ãメールã®ç®¡ç†"
msgid "Control the display of third party offers."
msgstr "サードパーティã®ã‚ªãƒ•ã‚¡ãƒ¼ã®è¡¨ç¤ºã‚’管ç†ã—ã¾ã™ã€‚"
-msgid "Control the maximum concurrency of LFS/attachment backfill for this secondary node"
-msgstr ""
-
-msgid "Control the maximum concurrency of container repository operations for this Geo node"
-msgstr ""
-
-msgid "Control the maximum concurrency of repository backfill for this secondary node"
-msgstr "ã“ã®ã‚»ã‚«ãƒ³ãƒ€ãƒªãƒŽãƒ¼ãƒ‰ã®ãƒªãƒã‚¸ãƒˆãƒªãƒãƒƒã‚¯ãƒ•ã‚£ãƒ«ã®æœ€å¤§åŒæ™‚実行性を制御ã™ã‚‹"
-
-msgid "Control the maximum concurrency of verification operations for this Geo node"
-msgstr ""
-
-msgid "Control the minimum interval in days that a repository should be reverified for this primary node"
-msgstr ""
-
msgid "Cookie domain"
msgstr "クッキーã®ãƒ‰ãƒ¡ã‚¤ãƒ³"
@@ -6273,6 +6428,9 @@ msgstr "ãƒãƒ£ãƒƒãƒˆã®ãƒ‹ãƒƒã‚¯ãƒãƒ¼ãƒ  %{chat_name} を削除ã§ãã¾ã›ã‚“ã
msgid "Could not find design."
msgstr ""
+msgid "Could not find iteration"
+msgstr ""
+
msgid "Could not remove the trigger."
msgstr "トリガーを除去ã§ãã¾ã›ã‚“ã§ã—ãŸã€‚"
@@ -6309,6 +6467,12 @@ msgstr "国"
msgid "Coverage"
msgstr "ã‚«ãƒãƒ¬ãƒƒã‚¸"
+msgid "Coverage Fuzzing"
+msgstr ""
+
+msgid "Crash State"
+msgstr ""
+
msgid "Create"
msgstr "作æˆ"
@@ -6357,6 +6521,9 @@ msgstr "æ–°ã—ã„リãƒã‚¸ãƒˆãƒªã‚’作æˆ"
msgid "Create a personal access token on your account to pull or push via %{protocol}."
msgstr "%{protocol} ã§ãƒ—ッシュやプルã™ã‚‹ãŸã‚ã®ã‚ãªãŸå€‹äººç”¨ã‚¢ã‚¯ã‚»ã‚¹ãƒˆãƒ¼ã‚¯ãƒ³ã‚’作æˆ"
+msgid "Create a project pre-populated with the necessary files to get you started quickly."
+msgstr ""
+
msgid "Create an account using:"
msgstr "アカウントを作æˆã™ã‚‹ã€‚ãã®æ–¹æ³•:"
@@ -6460,7 +6627,7 @@ msgid "Create your first page"
msgstr "最åˆã®ãƒšãƒ¼ã‚¸ã‚’作æˆã™ã‚‹"
msgid "Create your group"
-msgstr ""
+msgstr "グループを作æˆ"
msgid "Create/import your first project"
msgstr ""
@@ -6525,6 +6692,9 @@ msgstr "ブランãƒã‚’作æˆã—マージリクエストを作æˆã—ã¦ã€ã“ã®
msgid "Creates branch '%{branch_name}' and a merge request to resolve this issue."
msgstr "'%{branch_name}' ブランãƒã‚’作æˆã—マージリクエストを作æˆã—ã€ã“ã®èª²é¡Œã‚’解決ã—ã¾ã™ã€‚"
+msgid "Creating"
+msgstr ""
+
msgid "Creating epic"
msgstr "エピックを作æˆã—ã¦ã„ã¾ã™"
@@ -6576,7 +6746,10 @@ msgstr "ç¾åœ¨ã®ãƒ‘スワード"
msgid "Current vulnerabilities count"
msgstr "ç¾åœ¨ã®è„†å¼±æ€§ã‚«ã‚¦ãƒ³ãƒˆ"
-msgid "CurrentUser|Buy CI minutes"
+msgid "CurrentUser|Buy Pipeline minutes"
+msgstr ""
+
+msgid "CurrentUser|One of your groups is running out"
msgstr ""
msgid "CurrentUser|Profile"
@@ -6591,6 +6764,9 @@ msgstr "Gold試用版を開始"
msgid "CurrentUser|Upgrade"
msgstr ""
+msgid "Custom Attributes"
+msgstr ""
+
msgid "Custom CI configuration path"
msgstr "カスタムCIã®è¨­å®šãƒ‘ス"
@@ -6853,12 +7029,18 @@ msgstr "ステージドロップダウン"
msgid "DAG"
msgstr ""
+msgid "DAG visualization requires at least 3 dependent jobs."
+msgstr ""
+
msgid "DNS"
msgstr "DNS"
msgid "Dashboard"
msgstr "ダッシュボード"
+msgid "Dashboard uid not found"
+msgstr ""
+
msgid "DashboardProjects|All"
msgstr "ã™ã¹ã¦"
@@ -6883,6 +7065,9 @@ msgstr "%{invalidProjects} を追加ã§ãã¾ã›ã‚“。ダッシュボードãŒåˆ
msgid "Data is still calculating..."
msgstr "データã¯ã¾ã è¨ˆç®—中ã§ã™..."
+msgid "Datasource name not found"
+msgstr ""
+
msgid "Date"
msgstr "日付"
@@ -7063,6 +7248,9 @@ msgstr "ソースブランãƒã‚’削除"
msgid "Delete this attachment"
msgstr "ã“ã®æ·»ä»˜ãƒ•ã‚¡ã‚¤ãƒ«ã‚’削除"
+msgid "Delete user list"
+msgstr ""
+
msgid "Delete variable"
msgstr ""
@@ -7374,6 +7562,9 @@ msgstr "デプロイ先"
msgid "Deploying to"
msgstr "デプロイ先"
+msgid "Deploying to AWS is easy with GitLab"
+msgstr ""
+
msgid "Deployment Frequency"
msgstr ""
@@ -7735,6 +7926,9 @@ msgstr ""
msgid "Dismissed on pipeline %{pipelineLink} at %{projectLink}"
msgstr ""
+msgid "Display alerts from all your monitoring tools directly within GitLab."
+msgstr ""
+
msgid "Display name"
msgstr "表示å"
@@ -7792,9 +7986,6 @@ msgstr "-----BEGIN PGP PUBLIC KEY BLOCK----- ã§å§‹ã¾ã‚‹å…¬é–‹éƒ¨åˆ†ã‚’貼りä»
msgid "Don't show again"
msgstr "次回ã‹ã‚‰è¡¨ç¤ºã—ãªã„"
-msgid "Don't worry, you can access this tour by clicking on the help icon in the top right corner and choose <strong>Learn GitLab</strong>."
-msgstr "心é…ã—ãªã„ã§ãã ã•ã„。å³ä¸Šã«ã‚るヘルプアイコンをクリックã—ã¦ã€ã“ã®ãƒ„アーã«ã‚¢ã‚¯ã‚»ã‚¹ã—〠<strong> GitLabを学㶠</strong>ã‚’é¸æŠžã—ã¦ãã ã•ã„。"
-
msgid "Done"
msgstr "完了"
@@ -7921,6 +8112,9 @@ msgstr "パイプラインスケジュール %{id} を編集"
msgid "Edit Release"
msgstr "リリースを編集"
+msgid "Edit Slack integration"
+msgstr ""
+
msgid "Edit Snippet"
msgstr "スニペットを編集"
@@ -7960,6 +8154,9 @@ msgstr "%{user_name} ã® ID を編集ã™ã‚‹"
msgid "Edit issues"
msgstr "課題を編集"
+msgid "Edit iteration"
+msgstr ""
+
msgid "Edit public deploy key"
msgstr "公開デプロイキーã®ç·¨é›†"
@@ -8008,6 +8205,9 @@ msgstr "ãªã—。インデックスã™ã‚‹ãƒ—ロジェクトをé¸æŠžã—ã¦ãã 
msgid "Email"
msgstr "メール"
+msgid "Email Notification"
+msgstr ""
+
msgid "Email address"
msgstr "メールアドレス"
@@ -8020,6 +8220,9 @@ msgstr ""
msgid "Email not verified. Please verify your email in Salesforce."
msgstr "メールãŒç¢ºèªã§ãã¦ã„ã¾ã›ã‚“。 Salesforce ã§ãƒ¡ãƒ¼ãƒ«ã‚’確èªã—ã¦ãã ã•ã„。"
+msgid "Email notification for unknown sign-ins"
+msgstr ""
+
msgid "Email patch"
msgstr "パッãƒã‚’メールã™ã‚‹"
@@ -8030,7 +8233,7 @@ msgid "Email restrictions for sign-ups"
msgstr ""
msgid "Email sent"
-msgstr ""
+msgstr "メールé€ä¿¡å®Œäº†"
msgid "Email the pipelines status to a list of recipients."
msgstr "パイプラインã®ã‚¹ãƒ†ãƒ¼ã‚¿ã‚¹ã‚’å—信者ã®ãƒªã‚¹ãƒˆã«Eメールã§é€šçŸ¥ã—ã¾ã™ã€‚"
@@ -8251,6 +8454,9 @@ msgstr "終了時刻 (UTC)"
msgid "Enforce DNS rebinding attack protection"
msgstr "DNSå†ãƒã‚¤ãƒ³ãƒ‰æ”»æ’ƒã«å¯¾ã™ã‚‹ä¿è­·ã‚’強化ã™ã‚‹"
+msgid "Enforce personal access token expiration"
+msgstr ""
+
msgid "Ensure connectivity is available from the GitLab server to the Prometheus server"
msgstr "GitLab サーãƒãƒ¼ã‹ã‚‰ Prometheus サーãƒãƒ¼ã¸ã®æŽ¥ç¶šãŒåˆ©ç”¨ã§ãã‚‹ã“ã¨ã‚’確èªã—ã¾ã™ã€‚"
@@ -8320,6 +8526,9 @@ msgstr "マージリクエストã®ã‚¿ã‚¤ãƒˆãƒ«ã‚’入力ã—ã¦ãã ã•ã„"
msgid "Enter the name of your application, and we'll return a unique %{type}."
msgstr ""
+msgid "Enter weights for storages for new repositories."
+msgstr ""
+
msgid "Enter your password to approve"
msgstr "承èªã™ã‚‹ã«ã¯ãƒ‘スワードを入力ã—ã¦ãã ã•ã„"
@@ -8329,15 +8538,27 @@ msgstr "環境"
msgid "Environment does not have deployments"
msgstr ""
+msgid "Environment is required for Stages::MetricEndpointInserter"
+msgstr ""
+
+msgid "Environment is required for Stages::VariableEndpointInserter"
+msgstr ""
+
msgid "Environment scope"
msgstr ""
+msgid "Environment variables are applied to all project environments in this instance via the Runner. You can use environment variables for passwords, secret keys, etc. Make variables available to the running application by prepending the variable key with <code>K8S_SECRET_</code>. You can set variables to be:"
+msgstr ""
+
msgid "Environment variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. Additionally, they can be masked so they are hidden in job logs, though they must match certain regexp requirements to do so. You can use environment variables for passwords, secret keys, or whatever you want."
msgstr "環境変数㯠Runner を介ã—ã¦ç’°å¢ƒã«é©ç”¨ã•ã‚Œã¾ã™ã€‚ä¿è­·ã•ã‚ŒãŸãƒ–ランãƒã‚„ã‚¿ã‚°ã«ãれらを公開ã™ã‚‹ã ã‘ã§ä¿è­·ã™ã‚‹ã“ã¨ãŒã§ãã¾ã™ã€‚ã•ã‚‰ã«ã€ãƒžã‚¹ã‚¯ã•ã‚Œã¦ã„ã‚‹ãŸã‚ã€ã‚¸ãƒ§ãƒ–ログã«ã¯è¡¨ç¤ºã•ã‚Œã¾ã›ã‚“ãŒã€æ­£è¦è¡¨ç¾ã®è¦ä»¶ã‚’満ãŸã™å¿…è¦ãŒã‚ã‚Šã¾ã™ã€‚ã‚ãªãŸã¯ãƒ‘スワードã€ç§˜å¯†éµã€ã¾ãŸã¯ã‚ãªãŸãŒæœ›ã‚€ã‚‚ã®ãªã‚‰ä½•ã§ã‚‚環境変数を使ã†ã“ã¨ãŒã§ãã¾ã™ã€‚"
msgid "Environment variables are configured by your administrator to be %{link_start}protected%{link_end} by default"
msgstr "環境変数ã¯ã€ç®¡ç†è€…ã«ã‚ˆã£ã¦ãƒ‡ãƒ•ã‚©ãƒ«ãƒˆãŒ %{link_start}ä¿è­·%{link_end} ã«è¨­å®šã•ã‚Œã¦ã„ã¾ã™ã€‚"
+msgid "Environment variables on this GitLab instance are configured to be %{link_start}protected%{link_end} by default"
+msgstr ""
+
msgid "Environment:"
msgstr "環境:"
@@ -8378,7 +8599,7 @@ msgid "EnvironmentsDashboard|More actions"
msgstr "追加ã®ã‚¢ã‚¯ã‚·ãƒ§ãƒ³"
msgid "EnvironmentsDashboard|More information"
-msgstr ""
+msgstr "詳細情報"
msgid "EnvironmentsDashboard|Remove"
msgstr "削除"
@@ -8869,6 +9090,9 @@ msgstr "ã™ã¹ã¦"
msgid "EventFilterBy|Filter by comments"
msgstr "コメントã§ãƒ•ã‚£ãƒ«ã‚¿ãƒ¼"
+msgid "EventFilterBy|Filter by designs"
+msgstr ""
+
msgid "EventFilterBy|Filter by epic events"
msgstr "エピックイベントã§ãƒ•ã‚£ãƒ«ã‚¿ãƒ¼"
@@ -9001,15 +9225,15 @@ msgstr "ã™ã¹ã¦å±•é–‹"
msgid "Expand approvers"
msgstr "承èªè€…を展開"
-msgid "Expand child epics"
-msgstr ""
-
msgid "Expand down"
msgstr "ドロップダウンã®å±•é–‹"
msgid "Expand dropdown"
msgstr "ドロップダウンã®å±•é–‹"
+msgid "Expand milestones"
+msgstr ""
+
msgid "Expand sidebar"
msgstr "サイドãƒãƒ¼ã‚’é–‹ã"
@@ -9025,6 +9249,9 @@ msgstr "有効期é™"
msgid "Expiration date"
msgstr "有効期é™"
+msgid "Expiration not enforced"
+msgstr ""
+
msgid "Expiration policy for the Container Registry is a perfect solution for keeping the Registry space down while still enjoying the full power of GitLab CI/CD."
msgstr ""
@@ -9145,6 +9372,9 @@ msgstr "失敗"
msgid "Failed Jobs"
msgstr "失敗ã—ãŸã‚¸ãƒ§ãƒ–"
+msgid "Failed on"
+msgstr ""
+
msgid "Failed to add a Zoom meeting"
msgstr "Zoom ミーティングã®è¿½åŠ ã«å¤±æ•—ã—ã¾ã—ãŸ"
@@ -9205,14 +9435,20 @@ msgstr "ref ã‚’å–å¾—ã§ãã¾ã›ã‚“ã§ã—ãŸ"
msgid "Failed to install."
msgstr "インストールã«å¤±æ•—ã—ã¾ã—ãŸã€‚"
+msgid "Failed to load assignees. Please try again."
+msgstr ""
+
+msgid "Failed to load authors. Please try again."
+msgstr ""
+
msgid "Failed to load emoji list."
msgstr "絵文字リストã®ãƒ­ãƒ¼ãƒ‰ã«å¤±æ•—ã—ã¾ã—ãŸã€‚"
msgid "Failed to load error details from Sentry."
msgstr "Sentry ã‹ã‚‰ã®ã‚¨ãƒ©ãƒ¼è©³ç´°ã®èª­ã¿è¾¼ã¿ã«å¤±æ•—ã—ã¾ã—ãŸ."
-msgid "Failed to load errors from Sentry. Error message: %{errorMessage}"
-msgstr "Sentryã‹ã‚‰ã®ã‚¨ãƒ©ãƒ¼èª­ã¿è¾¼ã¿ã«å¤±æ•—ã—ã¾ã—ãŸã€‚エラーメッセージ: %{errorMessage}"
+msgid "Failed to load errors from Sentry."
+msgstr ""
msgid "Failed to load group activity metrics. Please try again."
msgstr ""
@@ -9292,6 +9528,9 @@ msgstr "設定をä¿å­˜ã§ãã¾ã›ã‚“ã§ã—ãŸã€‚"
msgid "Failed to set due date because the date format is invalid."
msgstr "日付ã®ãƒ•ã‚©ãƒ¼ãƒžãƒƒãƒˆãŒç„¡åŠ¹ãªãŸã‚ã€æœŸæ—¥ã®è¨­å®šã«å¤±æ•—ã—ã¾ã—ãŸã€‚"
+msgid "Failed to set iteration on this issue. Please try again."
+msgstr ""
+
msgid "Failed to signing using smartcard authentication"
msgstr "スマートカードèªè¨¼ã‚’使用ã—ã¦ã®ç½²åã«å¤±æ•—ã—ã¾ã—ãŸã€‚"
@@ -9514,15 +9753,12 @@ msgstr "戦略"
msgid "FeatureFlags|Target environments"
msgstr "ターゲット環境"
-msgid "FeatureFlags|There are no active feature flags"
-msgstr "有効ãªæ©Ÿèƒ½ãƒ•ãƒ©ã‚°ã¯ã‚ã‚Šã¾ã›ã‚“"
-
-msgid "FeatureFlags|There are no inactive feature flags"
-msgstr "無効ãªæ©Ÿèƒ½ãƒ•ãƒ©ã‚°ã¯ã‚ã‚Šã¾ã›ã‚“"
-
msgid "FeatureFlags|There was an error fetching the feature flags."
msgstr "機能フラグã®å–得中ã«ã‚¨ãƒ©ãƒ¼ãŒç™ºç”Ÿã—ã¾ã—ãŸã€‚"
+msgid "FeatureFlags|There was an error retrieving user lists"
+msgstr ""
+
msgid "FeatureFlags|Try again in a few moments or contact your support team."
msgstr "ã—ã°ã‚‰ãã—ã¦ã‹ã‚‰ã‚‚ã†ä¸€åº¦è©¦ã™ã‹ã€ã‚µãƒãƒ¼ãƒˆãƒãƒ¼ãƒ ã«é€£çµ¡ã—ã¦ãã ã•ã„。"
@@ -9532,9 +9768,18 @@ msgstr "ユーザーID"
msgid "FeatureFlag|Delete strategy"
msgstr ""
+msgid "FeatureFlag|List"
+msgstr ""
+
msgid "FeatureFlag|Percentage"
msgstr ""
+msgid "FeatureFlag|Select a user list"
+msgstr ""
+
+msgid "FeatureFlag|There are no configured user lists"
+msgstr ""
+
msgid "FeatureFlag|Type"
msgstr ""
@@ -9691,6 +9936,12 @@ msgstr "åå‰ã§ã‚ãªãŸã®ãƒ—ロジェクトをフィルタ"
msgid "Filter..."
msgstr "フィルター..."
+msgid "Find File"
+msgstr ""
+
+msgid "Find bugs in your code with coverage-guided fuzzing"
+msgstr ""
+
msgid "Find by path"
msgstr "パスã§æ¤œç´¢"
@@ -9955,6 +10206,9 @@ msgstr "Geo設定"
msgid "Geo allows you to replicate your GitLab instance to other geographical locations."
msgstr "Geo ã¯ã€GitLab インスタンスを他ã®åœ°ç†çš„ãªå ´æ‰€ã«è¤‡è£½ã™ã‚‹ã“ã¨ãŒã§ãã¾ã™ã€‚"
+msgid "Geo nodes are paused using a command run on the node"
+msgstr ""
+
msgid "GeoNodeStatusEvent|%{timeAgoStr} (%{pendingEvents} events)"
msgstr "%{timeAgoStr}(%{pendingEvents} イベント)"
@@ -10039,6 +10293,9 @@ msgstr ""
msgid "GeoNodes|Not checksummed"
msgstr "ãƒã‚§ãƒƒã‚¯ã‚µãƒ ãªã—"
+msgid "GeoNodes|Package files"
+msgstr ""
+
msgid "GeoNodes|Pausing replication stops the sync process. Are you sure?"
msgstr "レプリケーションを一時åœæ­¢ã™ã‚‹ã¨ã€åŒæœŸãƒ—ロセスãŒåœæ­¢ã—ã¾ã™ã€‚本当ã«å®Ÿè¡Œã—ã¾ã™ã‹?"
@@ -10057,6 +10314,9 @@ msgstr "WAL レプリケーションスロット"
msgid "GeoNodes|Replication slots"
msgstr "レプリケーションスロット"
+msgid "GeoNodes|Replication status"
+msgstr ""
+
msgid "GeoNodes|Repositories"
msgstr "リãƒã‚¸ãƒˆãƒª"
@@ -10267,6 +10527,9 @@ msgstr "åŒæœŸå…ˆ"
msgid "Geo|Synchronization failed - %{error}"
msgstr "åŒæœŸã«å¤±æ•—ã—ã¾ã—㟠- %{error}"
+msgid "Geo|Synchronization of %{itemTitle} is disabled."
+msgstr ""
+
msgid "Geo|The database is currently %{db_lag} behind the primary node."
msgstr "データベースã¯ç¾åœ¨ãƒ—ライマリーノードã®å¾Œã‚ã« %{db_lag} ã§ã™ã€‚"
@@ -10579,6 +10842,9 @@ msgstr "全画é¢è¡¨ç¤º"
msgid "Go to %{link_to_google_takeout}."
msgstr "%{link_to_google_takeout} ã«ç§»å‹•ã—ã¾ã™ã€‚"
+msgid "Go to <strong>Issues</strong> > <strong>Boards</strong> to access your personalized learning issue board."
+msgstr ""
+
msgid "Go to Pipelines"
msgstr ""
@@ -10678,9 +10944,6 @@ msgstr "ã‚ãªãŸã®ãƒ—ロジェクトã¸ç§»å‹•"
msgid "Go to your snippets"
msgstr "ã‚ãªãŸã®ã‚¹ãƒ‹ãƒšãƒƒãƒˆã¸ç§»å‹•"
-msgid "Golden Tanuki"
-msgstr "ゴールデンタヌキ"
-
msgid "Google Cloud Platform"
msgstr ""
@@ -10702,6 +10965,9 @@ msgstr "入手ã—ã¾ã—ょã†ï¼"
msgid "Grafana URL"
msgstr "Grafana ã® URL"
+msgid "Grafana response contains invalid json"
+msgstr ""
+
msgid "GrafanaIntegration|API Token"
msgstr "APIトークン"
@@ -10750,9 +11016,6 @@ msgstr "グループ %{group_name} ã¯å‰Šé™¤äºˆå®šã—ã¾ã—ãŸã€‚"
msgid "Group %{group_name} was successfully created."
msgstr "'%{group_name}' グループã¯æ­£å¸¸ã«ä½œæˆã•ã‚Œã¾ã—ãŸã€‚"
-msgid "Group '%{group_name}' is being imported."
-msgstr ""
-
msgid "Group Audit Events"
msgstr ""
@@ -10789,9 +11052,6 @@ msgstr "グループアãƒã‚¿ãƒ¼"
msgid "Group by:"
msgstr ""
-msgid "Group could not be imported: %{errors}"
-msgstr ""
-
msgid "Group description"
msgstr "グループã®èª¬æ˜Ž"
@@ -10819,9 +11079,15 @@ msgstr "グループã¯å‰Šé™¤å¯¾è±¡ã¨ã—ã¦ãƒžãƒ¼ã‚¯ãŒã¤ã„ã¦ã„ã¾ã™"
msgid "Group has not been marked for deletion"
msgstr "グループã¯å‰Šé™¤å¯¾è±¡ã¨ã—ã¦ãƒžãƒ¼ã‚¯ãŒã¤ã„ã¦ã„ã¾ã›ã‚“"
+msgid "Group import could not be scheduled"
+msgstr ""
+
msgid "Group info:"
msgstr "グループ情報:"
+msgid "Group is required when cluster_type is :group"
+msgstr ""
+
msgid "Group maintainers can register group runners in the %{link}"
msgstr "グループ Maintainer 㯠%{link} ã§ã‚°ãƒ«ãƒ¼ãƒ— Runner を登録ã§ãã¾ã™ã€‚"
@@ -10885,6 +11151,24 @@ msgstr ""
msgid "GroupActivyMetrics|Recent activity (last 90 days)"
msgstr ""
+msgid "GroupImport|Failed to import group."
+msgstr ""
+
+msgid "GroupImport|Group '%{group_name}' is being imported."
+msgstr ""
+
+msgid "GroupImport|Group could not be imported: %{errors}"
+msgstr ""
+
+msgid "GroupImport|Please wait while we import the group for you. Refresh at will."
+msgstr ""
+
+msgid "GroupImport|The group was successfully imported."
+msgstr ""
+
+msgid "GroupImport|Unable to process group import file"
+msgstr ""
+
msgid "GroupRoadmap|%{dateWord} – No end date"
msgstr ""
@@ -11152,6 +11436,9 @@ msgstr "グループ (%{count})"
msgid "Groups (%{groups})"
msgstr ""
+msgid "Groups and projects"
+msgstr ""
+
msgid "Groups can also be nested by creating %{subgroup_docs_link_start}subgroups%{subgroup_docs_link_end}."
msgstr "グループã¯ã€%{subgroup_docs_link_start}サブグループ%{subgroup_docs_link_end}を作æˆã™ã‚‹ã“ã¨ã§ãƒã‚¹ãƒˆã™ã‚‹ã“ã¨ã‚‚ã§ãã¾ã™ã€‚"
@@ -11197,6 +11484,33 @@ msgstr "グループã¯ã‚ã‚Šã¾ã›ã‚“"
msgid "GroupsEmptyState|You can manage your group member’s permissions and access to each project in the group."
msgstr "グループメンãƒãƒ¼ã®æ¨©é™ç®¡ç†ã€ãŠã‚ˆã³ã‚°ãƒ«ãƒ¼ãƒ—内ã®å„プロジェクトã®ã‚¢ã‚¯ã‚»ã‚¹æ¨©é™ã‚’管ç†ã§ãã¾ã™ã€‚"
+msgid "GroupsNew|Contact an administrator to enable options for importing your group."
+msgstr ""
+
+msgid "GroupsNew|Create"
+msgstr ""
+
+msgid "GroupsNew|Create group"
+msgstr ""
+
+msgid "GroupsNew|GitLab group export"
+msgstr ""
+
+msgid "GroupsNew|Import"
+msgstr ""
+
+msgid "GroupsNew|Import group"
+msgstr ""
+
+msgid "GroupsNew|My Awesome Group"
+msgstr ""
+
+msgid "GroupsNew|No import options available"
+msgstr ""
+
+msgid "GroupsNew|To copy a GitLab group between installations, navigate to the group settings page for the original installation, generate an export file, and upload it here."
+msgstr ""
+
msgid "GroupsTree|Are you sure you want to leave the \"%{fullName}\" group?"
msgstr "\"%{fullName}\" グループã‹ã‚‰é›¢è„±ã—よã†ã¨ã—ã¦ã„ã¾ã™ã€‚よã‚ã—ã„ã§ã™ã‹ï¼Ÿ"
@@ -11299,6 +11613,9 @@ msgstr "アラートã®é‡ã‚’減らã™ã®ã«å½¹ç«‹ã¡ã¾ã™(例:作æˆã™ã‚‹èª
msgid "Helps reduce request volume for protected paths"
msgstr "ä¿è­·ã•ã‚ŒãŸãƒ‘スã®ãƒªã‚¯ã‚¨ã‚¹ãƒˆé‡ã‚’減らã™ã®ã«å½¹ç«‹ã¡ã¾ã™"
+msgid "Here are all your projects in your group, including the one you just created. To start, let’s take a look at your personalized learning project which will help you learn about GitLab at your own pace."
+msgstr ""
+
msgid "Here you will find recent merge request activity"
msgstr ""
@@ -11312,6 +11629,9 @@ msgid "Hide chart"
msgid_plural "Hide charts"
msgstr[0] ""
+msgid "Hide details"
+msgstr ""
+
msgid "Hide file browser"
msgstr "ファイルブラウザをéžè¡¨ç¤º"
@@ -11321,6 +11641,9 @@ msgstr "グループプロジェクトをéžè¡¨ç¤º"
msgid "Hide host keys manual input"
msgstr "ホストキーã®ãƒžãƒ‹ãƒ¥ã‚¢ãƒ«å…¥åŠ›ã‚’éš ã™"
+msgid "Hide list"
+msgstr ""
+
msgid "Hide marketing-related entries from help"
msgstr ""
@@ -11340,9 +11663,6 @@ msgstr[0] "éžè¡¨ç¤º"
msgid "Hide values"
msgstr "éžè¡¨ç¤º"
-msgid "Hiding all labels"
-msgstr "ã™ã¹ã¦ã®ãƒ©ãƒ™ãƒ«ã‚’éš ã™"
-
msgid "High or unknown vulnerabilities present"
msgstr ""
@@ -11427,6 +11747,9 @@ msgstr "ID"
msgid "ID:"
msgstr "ID:"
+msgid "IDE"
+msgstr ""
+
msgid "IDE|Allow live previews of JavaScript projects in the Web IDE using CodeSandbox Live Preview."
msgstr ""
@@ -11517,12 +11840,12 @@ msgstr "無効ã«ã™ã‚‹ã¨ã€ã‚¢ã‚¯ã‚»ã‚¹ãƒ¬ãƒ™ãƒ«ã¯ãã®ãƒ—ロジェクトã®
msgid "If enabled"
msgstr "有効ã«ã—ãŸå ´åˆ"
+msgid "If enabled, GitLab will handle Object Storage replication using Geo. %{linkStart}More information%{linkEnd}"
+msgstr ""
+
msgid "If enabled, access to projects will be validated on an external service using their classification label."
msgstr "有効ã«è¨­å®šã—ãŸå ´åˆã€å¤–部サービスã‹ã‚‰ãƒ—ロジェクトã¸ã®ã‚¢ã‚¯ã‚»ã‚¹ãŒåˆ†é¡žãƒ©ãƒ™ãƒ«ã‚’使用ã—ã¦åˆ¶å¾¡ã•ã‚Œã¾ã™ã€‚"
-msgid "If enabled, and if object storage is enabled, GitLab will handle Object Storage replication using Geo"
-msgstr ""
-
msgid "If there is no previous license or if the previous license has expired, some GitLab functionality will be blocked until a new, valid license is uploaded."
msgstr ""
@@ -11569,7 +11892,7 @@ msgid "Image Details"
msgstr ""
msgid "Image URL"
-msgstr ""
+msgstr "ç”»åƒã®URL"
msgid "ImageDiffViewer|2-up"
msgstr "2-up"
@@ -11881,6 +12204,9 @@ msgid "Instance"
msgid_plural "Instances"
msgstr[0] "インスタンス"
+msgid "Instance Configuration"
+msgstr ""
+
msgid "Instance Statistics visibility"
msgstr "インスタンス統計ã®å¯è¦–性"
@@ -11902,9 +12228,6 @@ msgstr ""
msgid "Integrations"
msgstr "インテグレーション"
-msgid "Integrations allow you to integrate GitLab with other applications"
-msgstr ""
-
msgid "Integrations|All details"
msgstr ""
@@ -12016,6 +12339,9 @@ msgstr ""
msgid "Invalid query"
msgstr "無効ãªã‚¯ã‚¨ãƒªãƒ¼"
+msgid "Invalid repository bundle for snippet with id %{snippet_id}"
+msgstr ""
+
msgid "Invalid repository path"
msgstr "リãƒã‚¸ãƒˆãƒªãƒ‘スãŒç„¡åŠ¹ã§ã™"
@@ -12034,6 +12360,9 @@ msgstr ""
msgid "Invalid two-factor code."
msgstr "無効ãªäºŒæ®µéšŽèªè¨¼ã‚³ãƒ¼ãƒ‰ã§ã™ã€‚"
+msgid "Invalid yaml"
+msgstr ""
+
msgid "Invitation"
msgstr "招待状"
@@ -12253,15 +12582,24 @@ msgstr "ヘッダー行ã¨å°‘ãªãã¨ã‚‚2ã¤ã®åˆ—ãŒå¿…è¦ã§ã™ã€‚最åˆã®åˆ
msgid "It seems like the Dependency Scanning job ran successfully, but no dependencies have been detected in your project."
msgstr ""
+msgid "It seems that there is currently no available data for code coverage"
+msgstr ""
+
msgid "It's you"
msgstr "ã‚ãªãŸã§ã™"
+msgid "Iteration"
+msgstr ""
+
msgid "Iteration changed to"
msgstr ""
msgid "Iteration removed"
msgstr ""
+msgid "Iteration updated"
+msgstr ""
+
msgid "Iterations"
msgstr ""
@@ -12292,12 +12630,18 @@ msgstr "1月"
msgid "January"
msgstr "1月"
+msgid "Jira Issues"
+msgstr ""
+
msgid "Jira import is already running."
msgstr ""
msgid "Jira integration not configured."
msgstr ""
+msgid "Jira project key is not configured"
+msgstr ""
+
msgid "Jira project: %{importProject}"
msgstr ""
@@ -12478,6 +12822,9 @@ msgstr ""
msgid "Keep divergent refs"
msgstr ""
+msgid "Kerberos access denied"
+msgstr ""
+
msgid "Key"
msgstr "キー"
@@ -12487,12 +12834,15 @@ msgstr "キー (PEM)"
msgid "Key: %{key}"
msgstr "キー: %{key}"
-msgid "Keyboard Shortcuts"
-msgstr "キーボード ショートカット"
-
msgid "Keyboard shortcuts"
msgstr ""
+msgid "Keys"
+msgstr ""
+
+msgid "Ki"
+msgstr ""
+
msgid "Kubernetes"
msgstr "Kubernetes"
@@ -12532,6 +12882,9 @@ msgstr "Kubernetes ãƒãƒƒãƒ—オーãƒãƒ¼"
msgid "LDAP"
msgstr "LDAP"
+msgid "LDAP Synchronization"
+msgstr ""
+
msgid "LDAP settings"
msgstr "LDAP 設定"
@@ -12755,12 +13108,18 @@ msgstr "承èªã®è©³ç´°"
msgid "Learn more about custom project templates"
msgstr "カスタムプロジェクトテンプレートã®è©³ç´°"
+msgid "Learn more about deploying to AWS"
+msgstr ""
+
msgid "Learn more about deploying to a cluster"
msgstr "クラスターã¸ã®ãƒ‡ãƒ—ロイã®è©³ç´°ã«ã¤ã„ã¦"
msgid "Learn more about group-level project templates"
msgstr "グループレベルプロジェクトテンプレートã®è©³ç´°"
+msgid "Learn more about job dependencies"
+msgstr ""
+
msgid "Learn more about signing commits"
msgstr "コミットã¸ã®ç½²åã®è©³ç´°"
@@ -12812,9 +13171,21 @@ msgstr "ライセンスコンプライアンス"
msgid "License History"
msgstr ""
+msgid "License ID:"
+msgstr ""
+
+msgid "License URL"
+msgstr ""
+
msgid "License-Check"
msgstr "ライセンス ãƒã‚§ãƒƒã‚¯"
+msgid "LicenseCompliance|%{docLinkStart}License Approvals%{docLinkEnd} are active"
+msgstr ""
+
+msgid "LicenseCompliance|%{docLinkStart}License Approvals%{docLinkEnd} are inactive"
+msgstr ""
+
msgid "LicenseCompliance|Add a license"
msgstr "ライセンスを追加"
@@ -12836,9 +13207,15 @@ msgstr ""
msgid "LicenseCompliance|Deny"
msgstr ""
+msgid "LicenseCompliance|Learn more about %{linkStart}License Approvals%{linkEnd}"
+msgstr ""
+
msgid "LicenseCompliance|License"
msgstr "ライセンス"
+msgid "LicenseCompliance|License Approvals"
+msgstr ""
+
msgid "LicenseCompliance|License Compliance detected %d license and policy violation for the source branch only; approval required"
msgid_plural "LicenseCompliance|License Compliance detected %d licenses and policy violations for the source branch only; approval required"
msgstr[0] ""
@@ -13054,6 +13431,9 @@ msgstr "一覧表示"
msgid "List your Bitbucket Server repositories"
msgstr "ã‚ãªãŸã®ã€Bitbucket Server ã®ãƒªãƒã‚¸ãƒˆãƒªã‚’一覧表示ã™ã‚‹"
+msgid "Lists"
+msgstr ""
+
msgid "Live preview"
msgstr "ライブプレビュー"
@@ -13237,6 +13617,9 @@ msgstr ""
msgid "Manage project labels"
msgstr "プロジェクトラベルã®ç®¡ç†"
+msgid "Manage storage usage"
+msgstr ""
+
msgid "Manage two-factor authentication"
msgstr "2è¦ç´ èªè¨¼ã®ç®¡ç†"
@@ -13252,6 +13635,9 @@ msgstr "マニフェスト"
msgid "Manifest file import"
msgstr "マニフェストファイルã®ã‚¤ãƒ³ãƒãƒ¼ãƒˆ"
+msgid "Manifest import"
+msgstr ""
+
msgid "Manual job"
msgstr "手動ジョブ"
@@ -13399,6 +13785,12 @@ msgstr ""
msgid "Maximum field length"
msgstr ""
+msgid "Maximum file size is 2MB. Please select a smaller file."
+msgstr ""
+
+msgid "Maximum import size (MB)"
+msgstr ""
+
msgid "Maximum job timeout"
msgstr "ジョブタイムアウトã®æœ€å¤§å€¤"
@@ -13438,6 +13830,9 @@ msgstr ""
msgid "Maximum size of Elasticsearch bulk indexing requests."
msgstr ""
+msgid "Maximum size of import files."
+msgstr ""
+
msgid "Maximum size of individual attachments in comments."
msgstr ""
@@ -13489,6 +13884,9 @@ msgstr "マージ"
msgid "Merge (when the pipeline succeeds)"
msgstr "パイプラインãŒæˆåŠŸã—ãŸã¨ãã«ãƒžãƒ¼ã‚¸"
+msgid "Merge Conflicts"
+msgstr ""
+
msgid "Merge Request"
msgstr "マージリクエスト"
@@ -13615,6 +14013,9 @@ msgstr "コメントã®ä¿å­˜ã«å¤±æ•—ã—ã¾ã—ãŸ"
msgid "MergeRequests|Squash task canceled: another squash is already in progress."
msgstr "スカッシュタスクãŒå–り消ã•ã‚Œã¾ã—ãŸã€‚別ã®ã‚¹ã‚«ãƒƒã‚·ãƒ¥ãŒæ—¢ã«é€²è¡Œä¸­ã§ã™ã€‚"
+msgid "MergeRequests|This project does not allow squashing commits when merge requests are accepted."
+msgstr ""
+
msgid "MergeRequests|Thread stays resolved"
msgstr "スレッドã¯è§£æ±ºæ¸ˆã¿ã®ã¾ã¾ã§ã™"
@@ -13736,25 +14137,25 @@ msgid "Metrics and profiling"
msgstr "メトリクスã¨ãƒ—ロファイリング"
msgid "Metrics::Dashboard::Annotation|Annotation can't belong to both a cluster and an environment at the same time"
-msgstr ""
+msgstr "アノテーションã¯ã€ã‚¯ãƒ©ã‚¹ã‚¿ãƒ¼ã¨ç’°å¢ƒã®ä¸¡æ–¹ã§åŒæ™‚ã«æ‰€å±žã§ãã¾ã›ã‚“"
msgid "Metrics::Dashboard::Annotation|Annotation has not been deleted"
-msgstr ""
+msgstr "アノテーションãŒå‰Šé™¤ã•ã‚Œã¦ã„ã¾ã›ã‚“。"
msgid "Metrics::Dashboard::Annotation|Annotation must belong to a cluster or an environment"
-msgstr ""
+msgstr "アノテーションã¯ã€ã‚¯ãƒ©ã‚¹ã‚¿ãƒ¼ã¨ç’°å¢ƒã®ã©ã¡ã‚‰ã‹ã«æ‰€å±žã—ãªã‘ã‚Œã°ãªã‚Šã¾ã›ã‚“。"
msgid "Metrics::Dashboard::Annotation|Dashboard with requested path can not be found"
-msgstr ""
+msgstr "è¦æ±‚ã•ã‚ŒãŸãƒ‘スをæŒã¤ãƒ€ãƒƒã‚·ãƒ¥ãƒœãƒ¼ãƒ‰ãŒè¦‹ã¤ã‹ã‚Šã¾ã›ã‚“"
msgid "Metrics::Dashboard::Annotation|You are not authorized to create annotation for selected cluster"
-msgstr ""
+msgstr "é¸æŠžã—ãŸã‚¯ãƒ©ã‚¹ã‚¿ãƒ¼ã®ãŸã‚ã®ã‚¢ãƒŽãƒ†ãƒ¼ã‚·ãƒ§ãƒ³ã‚’作æˆã™ã‚‹æ¨©é™ãŒã‚ã‚Šã¾ã›ã‚“"
msgid "Metrics::Dashboard::Annotation|You are not authorized to create annotation for selected environment"
-msgstr ""
+msgstr "é¸æŠžã—ãŸç’°å¢ƒã®ã‚¢ãƒŽãƒ†ãƒ¼ã‚·ãƒ§ãƒ³ã‚’作æˆã™ã‚‹æ¨©é™ãŒã‚ã‚Šã¾ã›ã‚“"
msgid "Metrics::Dashboard::Annotation|You are not authorized to delete this annotation"
-msgstr ""
+msgstr "ã“ã®ã‚¢ãƒŽãƒ†ãƒ¼ã‚·ãƒ§ãƒ³ã‚’削除ã™ã‚‹æ¨©é™ãŒã‚ã‚Šã¾ã›ã‚“"
msgid "Metrics::Dashboard::Annotation|can't be before starting_at time"
msgstr ""
@@ -13768,6 +14169,12 @@ msgstr ""
msgid "MetricsSettings|Add a button to the metrics dashboard linking directly to your existing external dashboard."
msgstr ""
+msgid "MetricsSettings|Choose whether to display dashboard metrics in UTC or the user's local timezone."
+msgstr ""
+
+msgid "MetricsSettings|Dashboard timezone"
+msgstr ""
+
msgid "MetricsSettings|External dashboard URL"
msgstr ""
@@ -13777,6 +14184,12 @@ msgstr ""
msgid "MetricsSettings|Metrics Dashboard"
msgstr ""
+msgid "MetricsSettings|UTC (Coordinated Universal Time)"
+msgstr ""
+
+msgid "MetricsSettings|User's local timezone"
+msgstr ""
+
msgid "Metrics|Add metric"
msgstr "メトリクスを追加"
@@ -13792,6 +14205,9 @@ msgstr "カスタムダッシュボード %{fileName} を作æˆ"
msgid "Metrics|Create metric"
msgstr "メトリクスを作æˆ"
+msgid "Metrics|Current"
+msgstr ""
+
msgid "Metrics|Delete metric"
msgstr "メトリクスを削除"
@@ -13841,6 +14257,9 @@ msgstr ""
msgid "Metrics|Max"
msgstr "最大"
+msgid "Metrics|Min"
+msgstr ""
+
msgid "Metrics|Must be a valid PromQL query."
msgstr "有効㪠PromQL クエリã§ã‚ã‚‹å¿…è¦ãŒã‚ã‚Šã¾ã™ã€‚"
@@ -13856,6 +14275,9 @@ msgstr "Prometheus クエリã®ãƒ‰ã‚­ãƒ¥ãƒ¡ãƒ³ãƒˆ"
msgid "Metrics|Refresh dashboard"
msgstr ""
+msgid "Metrics|Select a value"
+msgstr ""
+
msgid "Metrics|Star dashboard"
msgstr ""
@@ -13874,12 +14296,18 @@ msgstr "環境データã®å–得中ã«ã‚¨ãƒ©ãƒ¼ãŒç™ºç”Ÿã—ã¾ã—ãŸã€‚ã‚‚ã†ä¸€
msgid "Metrics|There was an error getting annotations information."
msgstr ""
+msgid "Metrics|There was an error getting dashboard validation warnings information."
+msgstr ""
+
msgid "Metrics|There was an error getting deployment information."
msgstr "デプロイ情報ã®å–得中ã«ã‚¨ãƒ©ãƒ¼ãŒç™ºç”Ÿã—ã¾ã—ãŸã€‚"
msgid "Metrics|There was an error getting environments information."
msgstr "環境情報ã®å–得中ã«ã‚¨ãƒ©ãƒ¼ãŒç™ºç”Ÿã—ã¾ã—ãŸã€‚"
+msgid "Metrics|There was an error getting options for variable \"%{name}\"."
+msgstr ""
+
msgid "Metrics|There was an error trying to validate your query"
msgstr "クエリã®æ¤œè¨¼ä¸­ã«ã‚¨ãƒ©ãƒ¼ãŒç™ºç”Ÿã—ã¾ã—ãŸ"
@@ -13922,6 +14350,9 @@ msgstr "ã“ã®ãƒ€ãƒƒã‚·ãƒ¥ãƒœãƒ¼ãƒ‰ã‚’ã‚ãªãŸã®ãƒªãƒã‚¸ãƒˆãƒªã«ã‚³ãƒ”ーã—
msgid "Metrics|You're about to permanently delete this metric. This cannot be undone."
msgstr "ã“ã®ãƒ¡ãƒˆãƒªã‚¯ã‚¹ã‚’完全ã«å‰Šé™¤ã—よã†ã¨ã—ã¦ã„ã¾ã™ã€‚ã“ã®æ“作ã¯ã€å…ƒã«æˆ»ã›ã¾ã›ã‚“。"
+msgid "Metrics|Your dashboard schema is invalid. Edit the dashboard to correct the YAML schema."
+msgstr ""
+
msgid "Metrics|e.g. HTTP requests"
msgstr "例: HTTP リクエスト"
@@ -13937,12 +14368,18 @@ msgstr "例:レート (http_requests_total[5m])"
msgid "Metrics|e.g. req/sec"
msgstr "例:リクエスト毎秒"
+msgid "Mi"
+msgstr ""
+
msgid "Microsoft Azure"
msgstr "Microsoft Azure"
msgid "Middleman project with Static Site Editor support"
msgstr ""
+msgid "Migrate your data from an external source like GitHub, Bitbucket, or another instance of GitLab."
+msgstr ""
+
msgid "Migrated %{success_count}/%{total_count} files."
msgstr "%{success_count}/%{total_count} 個ã®ãƒ•ã‚¡ã‚¤ãƒ«ãŒç§»è¡Œã•ã‚Œã¾ã—ãŸã€‚"
@@ -14076,6 +14513,9 @@ msgstr ""
msgid "Minimum capacity to be available before we schedule more mirrors preemptively."
msgstr "より多ãã®ãƒŸãƒ©ãƒ¼ã‚’優先的ã«ã‚¹ã‚±ã‚¸ãƒ¥ãƒ¼ãƒ«ã™ã‚‹å‰ã«ä½¿ç”¨å¯èƒ½ã«ãªã‚‹ãŸã‚ã®æœ€å°å®¹é‡ã€‚"
+msgid "Minimum interval in days"
+msgstr ""
+
msgid "Minimum length is %{minimum_password_length} characters"
msgstr "パスワードã¯æœ€ä½Ž %{minimum_password_length} 文字ã§ã™ã€‚"
@@ -14262,6 +14702,12 @@ msgstr "複数ã®ãƒ¢ãƒ‡ãƒ«ã‚¿ã‚¤ãƒ—ãŒè¦‹ã¤ã‹ã‚Šã¾ã—ãŸ: %{model_types}"
msgid "Multiple uploaders found: %{uploader_types}"
msgstr "複数ã®ã‚¢ãƒƒãƒ—ローダーãŒè¦‹ã¤ã‹ã‚Šã¾ã—ãŸ: %{uploader_types}"
+msgid "Must match with the %{codeStart}external_url%{codeEnd} in %{codeStart}/etc/gitlab/gitlab.rb%{codeEnd}."
+msgstr ""
+
+msgid "Must match with the %{codeStart}geo_node_name%{codeEnd} in %{codeStart}/etc/gitlab/gitlab.rb%{codeEnd}. %{linkStart}More information%{linkEnd}"
+msgstr ""
+
msgid "My Awesome Group"
msgstr ""
@@ -14376,11 +14822,17 @@ msgstr "æ–°ã—ã„"
msgid "New Application"
msgstr "æ–°ã—ã„アプリケーション"
+msgid "New Branch"
+msgstr ""
+
+msgid "New Deploy Key"
+msgstr ""
+
msgid "New Environment"
msgstr "æ–°ã—ã„環境"
-msgid "New Geo Node"
-msgstr "新ジオノード"
+msgid "New File"
+msgstr ""
msgid "New Group"
msgstr "æ–°ã—ã„グループ"
@@ -14395,15 +14847,15 @@ msgid "New Issue"
msgid_plural "New Issues"
msgstr[0] "æ–°è¦èª²é¡Œ"
-msgid "New Iteration"
-msgstr ""
-
msgid "New Jira import"
msgstr "æ–°ã—ã„ Jira インãƒãƒ¼ãƒˆ"
msgid "New Label"
msgstr "æ–°ã—ã„ラベル"
+msgid "New Merge Request"
+msgstr ""
+
msgid "New Milestone"
msgstr "æ–°ã—ã„マイルストーン"
@@ -14422,6 +14874,9 @@ msgstr "æ–°è¦ãƒ—ロジェクト"
msgid "New Snippet"
msgstr "æ–°è¦ã‚¹ãƒ‹ãƒšãƒƒãƒˆ"
+msgid "New User"
+msgstr ""
+
msgid "New branch"
msgstr "æ–°è¦ãƒ–ランãƒ"
@@ -14464,6 +14919,9 @@ msgstr "æ–°è¦èª²é¡Œ"
msgid "New issue title"
msgstr "æ–°ã—ã„課題ã®ã‚¿ã‚¤ãƒˆãƒ«"
+msgid "New iteration"
+msgstr ""
+
msgid "New iteration created"
msgstr ""
@@ -14641,6 +15099,12 @@ msgstr "ã‚ãªãŸãŒåˆ©ç”¨å¯èƒ½ãªãƒ•ã‚©ãƒ¼ã‚¯ã¯ã‚ã‚Šã¾ã›ã‚“。"
msgid "No grouping"
msgstr ""
+msgid "No iteration"
+msgstr ""
+
+msgid "No iterations to show"
+msgstr ""
+
msgid "No job log"
msgstr "ジョブログãŒã‚ã‚Šã¾ã›ã‚“"
@@ -14683,6 +15147,9 @@ msgstr "表示ã™ã‚‹ãƒžã‚¤ãƒ«ã‚¹ãƒˆãƒ¼ãƒ³ãŒã‚ã‚Šã¾ã›ã‚“"
msgid "No other labels with such name or description"
msgstr "ãã®ä»–ã«ã€ãã®åå‰ã¾ãŸã¯èª¬æ˜Žã®ãƒ©ãƒ™ãƒ«ãŒã‚ã‚Šã¾ã›ã‚“"
+msgid "No panels matching properties %{opts}"
+msgstr ""
+
msgid "No parent group"
msgstr "親グループã¯ã‚ã‚Šã¾ã›ã‚“"
@@ -14716,6 +15183,9 @@ msgstr "Runner ãŒè¦‹ã¤ã‹ã‚Šã¾ã›ã‚“"
msgid "No schedules"
msgstr "スケジュールãªã—"
+msgid "No stack trace for this error"
+msgstr ""
+
msgid "No starrers matched your search"
msgstr "ã‚ãªãŸã®æ¤œç´¢ã«ãƒžãƒƒãƒã—ãŸãŠæ°—ã«å…¥ã‚Šã¯ã‚ã‚Šã¾ã›ã‚“"
@@ -14728,7 +15198,7 @@ msgstr "テンプレートãŒã‚ã‚Šã¾ã›ã‚“"
msgid "No test coverage"
msgstr ""
-msgid "No thanks, don't show this again"
+msgid "No thanks"
msgstr ""
msgid "No vulnerabilities present"
@@ -14743,9 +15213,6 @@ msgstr ""
msgid "No, directly import the existing email addresses and usernames."
msgstr "ã„ã„ãˆã€æ—¢å­˜ã®ãƒ¡ãƒ¼ãƒ«ã‚¢ãƒ‰ãƒ¬ã‚¹ã¨ãƒ¦ãƒ¼ã‚¶ãƒ¼åを直接インãƒãƒ¼ãƒˆã—ã¾ã™ã€‚"
-msgid "No, not interested right now"
-msgstr "ã„ã„ãˆã€ä»Šã¯èˆˆå‘³ãŒã‚ã‚Šã¾ã›ã‚“"
-
msgid "No. of commits"
msgstr ""
@@ -14791,9 +15258,6 @@ msgstr "データä¸è¶³"
msgid "Not found."
msgstr "見ã¤ã‹ã‚Šã¾ã›ã‚“。"
-msgid "Not helpful"
-msgstr "å½¹ã«ç«‹ã¡ã¾ã›ã‚“"
-
msgid "Not now"
msgstr "後ã§"
@@ -14950,6 +15414,9 @@ msgstr "通知オフ"
msgid "Notifications on"
msgstr "通知オン"
+msgid "Notify users by email when sign-in location is not recognized"
+msgstr ""
+
msgid "Nov"
msgstr "11月"
@@ -15016,9 +15483,6 @@ msgstr "フィルター"
msgid "Oh no!"
msgstr "ãŠã£ã¨!"
-msgid "Ok let's go"
-msgstr "ã•ã‚ã€å§‹ã‚よã†"
-
msgid "Oldest first"
msgstr "å¤ã„é †"
@@ -15031,17 +15495,50 @@ msgstr ""
msgid "On track"
msgstr ""
+msgid "OnDemandScans|Attached branch"
+msgstr ""
+
+msgid "OnDemandScans|Attached branch is where the scan job runs."
+msgstr ""
+
+msgid "OnDemandScans|Could not run the scan. Please try again."
+msgstr ""
+
msgid "OnDemandScans|Create new DAST scan"
msgstr ""
+msgid "OnDemandScans|DAST will scan the target URL and any discovered sub URLs."
+msgstr ""
+
+msgid "OnDemandScans|New on-demand DAST scan"
+msgstr ""
+
msgid "OnDemandScans|On-demand Scans"
msgstr ""
+msgid "OnDemandScans|On-demand scans run outside the DevOps cycle and find vulnerabilities in your projects. %{learnMoreLinkStart}Learn more%{learnMoreLinkEnd}"
+msgstr ""
+
+msgid "OnDemandScans|Only a passive scan can be performed on demand."
+msgstr ""
+
+msgid "OnDemandScans|Passive DAST Scan"
+msgstr ""
+
+msgid "OnDemandScans|Please enter a valid URL format, ex: http://www.example.com/home"
+msgstr ""
+
+msgid "OnDemandScans|Run this scan"
+msgstr ""
+
+msgid "OnDemandScans|Scan mode"
+msgstr ""
+
msgid "OnDemandScans|Schedule or run scans immediately against target sites. Currently available on-demand scan type: DAST. %{helpLinkStart}More information%{helpLinkEnd}"
msgstr ""
-msgid "Onboarding"
-msgstr "オンボーディング"
+msgid "OnDemandScans|Target URL"
+msgstr ""
msgid "Once imported, repositories can be mirrored over SSH. Read more %{link_start}here%{link_end}."
msgstr "インãƒãƒ¼ãƒˆã™ã‚‹ã¨ã€ãƒªãƒã‚¸ãƒˆãƒªã¯SSHã«ã‚ˆã‚ŠãƒŸãƒ©ãƒ¼ãƒªãƒ³ã‚°ã§ãã¾ã™ã€‚詳細㯠%{link_start}ã“ã¡ã‚‰%{link_end} ã‚’ã”覧ãã ã•ã„。"
@@ -15065,6 +15562,9 @@ msgstr "アクセスã§ããªã„1ã¤ã¾ãŸã¯è¤‡æ•°ã®ã‚°ãƒ«ãƒ¼ãƒ—"
msgid "One or more of you personal access tokens were revoked"
msgstr "一ã¤ä»¥ä¸Šã®ãƒ‘ーソナルアクセストークンを失効ã•ã›ã¾ã—ãŸ"
+msgid "One or more of your %{provider} projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
+msgstr ""
+
msgid "One or more of your Bitbucket projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
msgstr "1ã¤ä»¥ä¸Šã® Bitbucket プロジェクトを GitLab ã«ç›´æŽ¥ã‚¤ãƒ³ãƒãƒ¼ãƒˆã§ãã¾ã›ã‚“。ãƒãƒ¼ã‚¸ãƒ§ãƒ³ç®¡ç†ã«ã€Git ã§ã¯ãªãã€Subversion ã‚„ Mercurial を使用ã—ã¦ã„ã‚‹ã‹ã‚‰ã§ã™ã€‚"
@@ -15566,6 +16066,9 @@ msgstr "親 epic ãŒã‚ã‚Šã¾ã›ã‚“。"
msgid "Parent epic is not present."
msgstr "親エピックãŒã‚ã‚Šã¾ã›ã‚“。"
+msgid "Parsing error for param :embed_json. %{message}"
+msgstr ""
+
msgid "Part of merge request changes"
msgstr "マージリクエストã§ã®å¤‰æ›´ç®‡æ‰€"
@@ -15578,6 +16081,9 @@ msgstr "å‚加者"
msgid "Passed"
msgstr ""
+msgid "Passed on"
+msgstr ""
+
msgid "Password"
msgstr "パスワード"
@@ -15662,6 +16168,9 @@ msgstr ""
msgid "Perform common operations on GitLab project"
msgstr "GitLabプロジェクトã§ä¸€èˆ¬çš„ãªæ“作を実施"
+msgid "Performance and resource management"
+msgstr ""
+
msgid "Performance optimization"
msgstr "パフォーマンスã®æœ€é©åŒ–"
@@ -15869,8 +16378,8 @@ msgstr "パイプライン実行"
msgid "Pipelines|Something went wrong while cleaning runners cache."
msgstr "Runner キャッシュã®ã‚¯ãƒªãƒ¼ãƒ‹ãƒ³ã‚°ä¸­ã«å•é¡ŒãŒç™ºç”Ÿã—ã¾ã—ãŸã€‚"
-msgid "Pipelines|There are currently no %{scope} pipelines."
-msgstr "%{scope} パイプラインã¯ç¾åœ¨ã‚ã‚Šã¾ã›ã‚“。"
+msgid "Pipelines|There are currently no finished pipelines."
+msgstr ""
msgid "Pipelines|There are currently no pipelines."
msgstr "パイプラインã¯ç¾åœ¨ã‚ã‚Šã¾ã›ã‚“。"
@@ -15980,6 +16489,9 @@ msgstr "パイプラインã®åœæ­¢"
msgid "Pipeline|Stop pipeline #%{pipelineId}?"
msgstr "#%{pipelineId} パイプラインをåœæ­¢ã—ã¾ã™ã‹ï¼Ÿ"
+msgid "Pipeline|Tag name"
+msgstr ""
+
msgid "Pipeline|Trigger author"
msgstr ""
@@ -16061,12 +16573,18 @@ msgstr "設定ファイルをãƒã‚§ãƒƒã‚¯ã—ã¦ã€ãã‚ŒãŒä½¿ç”¨å¯èƒ½ã§ã‹ã¤
msgid "Please check your email (%{email}) to verify that you own this address and unlock the power of CI/CD. Didn't receive it? %{resend_link}. Wrong email address? %{update_link}."
msgstr "é›»å­ãƒ¡ãƒ¼ãƒ«ï¼ˆ%{email})をãƒã‚§ãƒƒã‚¯ã—ã¦ã€ã“ã®ã‚¢ãƒ‰ãƒ¬ã‚¹ã‚’所有ã—ã¦ã„ã‚‹ã“ã¨ã‚’確èªã—ã€CI/CDã®ãƒ­ãƒƒã‚¯ã‚’解除ã—ã¦ãã ã•ã„。電å­ãƒ¡ãƒ¼ãƒ«ã‚’å—ã‘å–ã£ã¦ã„ãªã„? %{resend_link} メールアドレスを間é•ã£ã¦ã„ã¾ã›ã‚“ã‹ï¼Ÿ %{update_link}"
+msgid "Please choose a file"
+msgstr ""
+
msgid "Please choose a group URL with no special characters."
msgstr ""
msgid "Please complete your profile with email address"
msgstr "ã‚ãªãŸã®ãƒ—ロフィールã«ãƒ¡ãƒ¼ãƒ«ã‚¢ãƒ‰ãƒ¬ã‚¹ã‚’記入ã—ã¦ã€ãƒ—ロファイルを完了ã—ã¦ãã ã•ã„"
+msgid "Please convert %{linkStart}them to Git%{linkEnd}, and go through the %{linkToImportFlow} again."
+msgstr ""
+
msgid "Please convert them to %{link_to_git}, and go through the %{link_to_import_flow} again."
msgstr "ãれらを %{link_to_git} ã«å¤‰æ›ã—ã¦ã€å†ã³ %{link_to_import_flow} ã‚’è¡Œã£ã¦ãã ã•ã„。"
@@ -16124,6 +16642,9 @@ msgstr ""
msgid "Please provide a valid email address."
msgstr "有効ãªãƒ¡ãƒ¼ãƒ«ã‚¢ãƒ‰ãƒ¬ã‚¹ã‚’入力ã—ã¦ãã ã•ã„"
+msgid "Please provide attributes to update"
+msgstr ""
+
msgid "Please refer to <a href=\"%{docs_url}\">%{docs_url}</a>"
msgstr "<a href=\"%{docs_url}\">%{docs_url}</a>ã‚’å‚ç…§ã—ã¦ãã ã•ã„"
@@ -16859,6 +17380,9 @@ msgstr ""
msgid "Project has too many %{label_for_message} to search"
msgstr "プロジェクトã¯%{label_for_message} ãŒå¤šã™ãŽã¦æ¤œç´¢ã§ãã¾ã›ã‚“"
+msgid "Project is required when cluster_type is :project"
+msgstr ""
+
msgid "Project members"
msgstr "プロジェクトメンãƒãƒ¼"
@@ -16976,8 +17500,41 @@ msgstr ""
msgid "ProjectService|Comment"
msgstr "コメント"
-msgid "ProjectService|Comment will be posted on each event"
-msgstr "コメントã¯å„イベントã”ã¨ã«æŠ•ç¨¿ã•ã‚Œã¾ã™"
+msgid "ProjectService|Event will be triggered by a push to the repository"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a commit is created/updated"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a confidential issue is created/updated/closed"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a deployment finishes"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a merge request is created/updated/merged"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a new tag is pushed to the repository"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a new, unique alert is recorded"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a pipeline status changes"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a wiki page is created/updated"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when an issue is created/updated/closed"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when someone adds a comment"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when someone adds a comment on a confidential issue"
+msgstr ""
msgid "ProjectService|Perform common operations on GitLab project: %{project_name}"
msgstr "GitLabプロジェクト㮠%{project_name} ã§ä¸€èˆ¬çš„ãªæ“作を実行"
@@ -17027,6 +17584,9 @@ msgstr "メール通知を無効ã«ã™ã‚‹"
msgid "ProjectSettings|Enable 'Delete source branch' option by default"
msgstr "ソースブランãƒã®å‰Šé™¤ã®ã‚ªãƒ—ションをデフォルトã§æœ‰åŠ¹ã«ã™ã‚‹"
+msgid "ProjectSettings|Enable merge trains and pipelines for merged results"
+msgstr ""
+
msgid "ProjectSettings|Every merge creates a merge commit"
msgstr "マージã”ã¨ã«ãƒžãƒ¼ã‚¸ã‚³ãƒŸãƒƒãƒˆãŒä½œæˆã•ã‚Œã¾ã™"
@@ -17057,8 +17617,8 @@ msgstr "æ—©é€ã‚Šãƒžãƒ¼ã‚¸ã®ã¿"
msgid "ProjectSettings|Forks"
msgstr "フォーク"
-msgid "ProjectSettings|Git Large File Storage"
-msgstr "Git Large File Storage"
+msgid "ProjectSettings|Git Large File Storage (LFS)"
+msgstr ""
msgid "ProjectSettings|Internal"
msgstr "内部"
@@ -17093,9 +17653,6 @@ msgstr "マージ方法"
msgid "ProjectSettings|Merge options"
msgstr "マージオプション"
-msgid "ProjectSettings|Merge pipelines will try to validate the post-merge result prior to merging"
-msgstr "マージパイプラインã¯ãƒžãƒ¼ã‚¸ã‚’実行ã™ã‚‹å‰ã«ã€ãƒžãƒ¼ã‚¸ã®çµæžœã®æ¤œè¨¼ã‚’試ã¿ã¾ã™"
-
msgid "ProjectSettings|Merge requests"
msgstr "マージリクエスト"
@@ -17123,6 +17680,9 @@ msgstr "プロジェクト文書ã®ãƒšãƒ¼ã‚¸"
msgid "ProjectSettings|Pipelines"
msgstr "パイプライン"
+msgid "ProjectSettings|Pipelines for merge requests must be enabled in the CI/CD configuration file, or pipelines could be unresolvable or dropped"
+msgstr ""
+
msgid "ProjectSettings|Pipelines must succeed"
msgstr "パイプラインã¯æˆåŠŸã—ãªã‘ã‚Œã°ãªã‚‰ãªã„"
@@ -17150,6 +17710,9 @@ msgstr ""
msgid "ProjectSettings|Show link to create/view merge request when pushing from the command line"
msgstr "コマンドラインã‹ã‚‰ãƒ—ッシュã™ã‚‹ã¨ãã€ãƒžãƒ¼ã‚¸ãƒªã‚¯ã‚¨ã‚¹ãƒˆã‚’作æˆã¾ãŸã¯è¡¨ç¤ºã™ã‚‹ãŸã‚ã®ãƒªãƒ³ã‚¯ã‚’表示"
+msgid "ProjectSettings|Skipped pipelines are considered successful"
+msgstr ""
+
msgid "ProjectSettings|Snippets"
msgstr "スニペット"
@@ -17165,6 +17728,9 @@ msgstr "GitLab ã¯æ¬¡ã®å¤‰æ•°ã‚’サãƒãƒ¼ãƒˆã—ã¦ã„ã¾ã™:"
msgid "ProjectSettings|These checks must pass before merge requests can be merged"
msgstr "マージリクエストをマージã™ã‚‹å‰ã«ã€ã“れらã®ãƒã‚§ãƒƒã‚¯ã«åˆæ ¼ã—ãªã‘ã‚Œã°ãªã‚Šã¾ã›ã‚“。"
+msgid "ProjectSettings|This introduces the risk of merging changes that will not pass the pipeline."
+msgstr ""
+
msgid "ProjectSettings|This setting is applied on the server level and can be overridden by an admin."
msgstr "ã“ã®è¨­å®šã¯ã‚µãƒ¼ãƒãƒ¼ãƒ¬ãƒ™ãƒ«ã§é©ç”¨ã•ã‚Œã¦ãŠã‚Šã€ç®¡ç†è€…ãŒä¸Šæ›¸ãã§ãã¾ã™ã€‚"
@@ -17342,15 +17908,27 @@ msgstr "空"
msgid "ProjectsNew|Blank project"
msgstr "空ã®ãƒ—ロジェクト"
+msgid "ProjectsNew|Connect your external repository to GitLab CI/CD."
+msgstr ""
+
msgid "ProjectsNew|Contact an administrator to enable options for importing your project."
msgstr "管ç†è€…ã«é€£çµ¡ã—ã¦ã€ãƒ—ロジェクトをインãƒãƒ¼ãƒˆã™ã‚‹ãŸã‚ã®ã‚ªãƒ—ションを有効ã«ã—ã¦ãã ã•ã„。"
msgid "ProjectsNew|Create"
msgstr ""
+msgid "ProjectsNew|Create a blank project to house your files, plan your work, and collaborate on code, among other things."
+msgstr ""
+
+msgid "ProjectsNew|Create blank project"
+msgstr ""
+
msgid "ProjectsNew|Create from template"
msgstr "テンプレートã‹ã‚‰ä½œæˆ"
+msgid "ProjectsNew|Create new project"
+msgstr ""
+
msgid "ProjectsNew|Creating project & repository."
msgstr "プロジェクトã¨ãƒªãƒã‚¸ãƒˆãƒªã®ä½œæˆã€‚"
@@ -17375,6 +17953,9 @@ msgstr "少々ãŠå¾…ã¡ãã ã•ã„。準備ãŒæ•´ã„次第ã“ã®ãƒšãƒ¼ã‚¸ã¯è‡ª
msgid "ProjectsNew|Project description %{tag_start}(optional)%{tag_end}"
msgstr "プロジェクトã®èª¬æ˜Ž%{tag_start}(オプション)%{tag_end}"
+msgid "ProjectsNew|Run CI/CD for external repository"
+msgstr ""
+
msgid "ProjectsNew|Template"
msgstr "テンプレート"
@@ -17636,6 +18217,9 @@ msgstr "ä¿è­·ã•ã‚Œã¦ã„ã¾ã™"
msgid "Protected Branch"
msgstr "ä¿è­·ã•ã‚ŒãŸãƒ–ランãƒ"
+msgid "Protected Branches"
+msgstr ""
+
msgid "Protected Environment"
msgstr "ä¿è­·ã•ã‚ŒãŸç’°å¢ƒ"
@@ -17648,6 +18232,9 @@ msgstr "ä¿è­·æ¸ˆã¿ã®ãƒ‘ス"
msgid "Protected Tag"
msgstr "ä¿è­·æ¸ˆã¿ã®tag"
+msgid "Protected Tags"
+msgstr ""
+
msgid "Protected branches"
msgstr "ä¿è­·ã•ã‚ŒãŸãƒ–ランãƒ"
@@ -17742,11 +18329,14 @@ msgid "Protip:"
msgstr "Protip:"
msgid "Protocol"
-msgstr ""
+msgstr "プロトコル"
msgid "Provider"
msgstr "プロãƒã‚¤ãƒ€ãƒ¼"
+msgid "Proxy support for this API is not available currently"
+msgstr ""
+
msgid "Pseudonymizer data collection"
msgstr "匿å化データã®åŽé›†"
@@ -17771,6 +18361,9 @@ msgstr ""
msgid "Publish to status page"
msgstr ""
+msgid "Published on status page"
+msgstr ""
+
msgid "Publishes this issue to the associated status page."
msgstr ""
@@ -17945,6 +18538,9 @@ msgstr "最近ã®æ¤œç´¢"
msgid "Recipe"
msgstr "レシピ"
+msgid "Reconfigure"
+msgstr ""
+
msgid "Recover hidden stage"
msgstr ""
@@ -17971,7 +18567,7 @@ msgid_plural "Refreshing in %d seconds to show the updated status..."
msgstr[0] "æ›´æ–°ã•ã‚ŒãŸã‚¹ãƒ†ãƒ¼ã‚¿ã‚¹ã‚’表示ã™ã‚‹ãŸã‚ã«ã€ %d 秒間リフレッシュã—ã¦ã„ã¾ã™..."
msgid "Regenerate export"
-msgstr ""
+msgstr "エクスãƒãƒ¼ãƒˆã‚’å†ç”Ÿæˆ"
msgid "Regenerate instance ID"
msgstr "インスタンスIDã‚’å†ç”Ÿæˆ"
@@ -18030,6 +18626,9 @@ msgstr ""
msgid "Registration|Your profile"
msgstr ""
+msgid "Regulate approvals by authors/committers, based on compliance frameworks. Can be changed only at the instance level."
+msgstr ""
+
msgid "Rejected (closed)"
msgstr ""
@@ -18079,15 +18678,24 @@ msgstr "リリース タイトル"
msgid "ReleaseAssetLinkType|Image"
msgstr ""
+msgid "ReleaseAssetLinkType|Images"
+msgstr ""
+
msgid "ReleaseAssetLinkType|Other"
msgstr ""
msgid "ReleaseAssetLinkType|Package"
msgstr ""
+msgid "ReleaseAssetLinkType|Packages"
+msgstr ""
+
msgid "ReleaseAssetLinkType|Runbook"
msgstr ""
+msgid "ReleaseAssetLinkType|Runbooks"
+msgstr ""
+
msgid "Releases"
msgstr "リリース"
@@ -18100,6 +18708,9 @@ msgstr ""
msgid "Releases documentation"
msgstr ""
+msgid "Releases|New Release"
+msgstr ""
+
msgid "Release|Something went wrong while getting the release details"
msgstr "リリースã®è©³ç´°ã‚’å–得中ã«å•é¡ŒãŒç™ºç”Ÿã—ã¾ã—ãŸ"
@@ -18172,6 +18783,9 @@ msgstr "期日を削除"
msgid "Remove fork relationship"
msgstr "フォークã®é–¢ä¿‚を削除"
+msgid "Remove from batch"
+msgstr ""
+
msgid "Remove from board"
msgstr "ボードã‹ã‚‰å‰Šé™¤"
@@ -18361,6 +18975,12 @@ msgstr "クローンURLã®ãƒ«ãƒ¼ãƒˆã‚’ç½®ãæ›ãˆã‚‹ã€‚"
msgid "Replication"
msgstr ""
+msgid "Replication enabled"
+msgstr ""
+
+msgid "Replication paused"
+msgstr ""
+
msgid "Reply by email"
msgstr "メールã¸ã®è¿”ä¿¡"
@@ -18388,15 +19008,15 @@ msgstr ""
msgid "Reported %{timeAgo} by %{reportedBy}"
msgstr ""
-msgid "Reported Resource Changes: %{addNum} to add, %{changeNum} to change, %{deleteNum} to delete"
-msgstr ""
-
msgid "Reporter"
-msgstr ""
+msgstr "レãƒãƒ¼ã‚¿ãƒ¼"
msgid "Reporting"
msgstr "レãƒãƒ¼ãƒˆ"
+msgid "Reports"
+msgstr ""
+
msgid "Reports|%{combinedString} and %{resolvedString}"
msgstr ""
@@ -18434,6 +19054,9 @@ msgstr "実行時間"
msgid "Reports|Failure"
msgstr "失敗"
+msgid "Reports|Identifier"
+msgstr ""
+
msgid "Reports|Metrics reports are loading"
msgstr "メトリクスレãƒãƒ¼ãƒˆã®èª­ã¿è¾¼ã¿ä¸­"
@@ -18446,6 +19069,9 @@ msgstr "メトリクスレãƒãƒ¼ãƒˆã¯å¤‰æ›´ã•ã‚Œã¾ã›ã‚“ã§ã—ãŸã€‚"
msgid "Reports|Metrics reports failed loading results"
msgstr "メトリクスレãƒãƒ¼ãƒˆã§çµæžœã®èª­ã¿è¾¼ã¿ã«å¤±æ•—ã—ã¾ã—ãŸ"
+msgid "Reports|Scanner"
+msgstr ""
+
msgid "Reports|Severity"
msgstr ""
@@ -18494,15 +19120,33 @@ msgstr "リãƒã‚¸ãƒˆãƒªã®ã‚¯ãƒªãƒ¼ãƒ³ã‚¢ãƒƒãƒ—"
msgid "Repository cleanup has started. You will receive an email once the cleanup operation is complete."
msgstr "リãƒã‚¸ãƒˆãƒªã®ã‚¯ãƒªãƒ¼ãƒ³ã‚¢ãƒƒãƒ—ãŒé–‹å§‹ã•ã‚Œã¾ã—ãŸã€‚クリーンアップãŒå®Œäº†ã™ã‚‹ã¨ã€ãƒ¡ãƒ¼ãƒ«ãŒå±Šãã¾ã™ã€‚"
+msgid "Repository files count over the limit"
+msgstr ""
+
+msgid "Repository has an invalid default branch name."
+msgstr ""
+
+msgid "Repository has more than one branch."
+msgstr ""
+
msgid "Repository has no locks."
msgstr "リãƒã‚¸ãƒˆãƒªã¯ãƒ­ãƒƒã‚¯ã•ã‚Œã¦ã„ã¾ã›ã‚“。"
+msgid "Repository has tags."
+msgstr ""
+
msgid "Repository maintenance"
msgstr "リãƒã‚¸ãƒˆãƒªã®ä¿å®ˆ"
msgid "Repository mirroring"
msgstr "リãƒã‚¸ãƒˆãƒªã®ãƒŸãƒ©ãƒ¼ãƒªãƒ³ã‚°"
+msgid "Repository must contain at least 1 file."
+msgstr ""
+
+msgid "Repository size is above the limit."
+msgstr ""
+
msgid "Repository static objects"
msgstr "リãƒã‚¸ãƒˆãƒªé™çš„オブジェクト"
@@ -18512,7 +19156,7 @@ msgstr "リãƒã‚¸ãƒˆãƒªã®ã‚¹ãƒˆãƒ¬ãƒ¼ã‚¸"
msgid "Repository sync capacity"
msgstr "リãƒã‚¸ãƒˆãƒªåŒæœŸå®¹é‡"
-msgid "Repository: %{counter_repositories} / Wikis: %{counter_wikis} / Build Artifacts: %{counter_build_artifacts} / LFS: %{counter_lfs_objects}"
+msgid "Repository: %{counter_repositories} / Wikis: %{counter_wikis} / Build Artifacts: %{counter_build_artifacts} / LFS: %{counter_lfs_objects} / Snippets: %{counter_snippets}"
msgstr ""
msgid "RepositorySettingsAccessLevel|Select"
@@ -18524,6 +19168,9 @@ msgstr "アクセス権é™ã‚’リクエストã™ã‚‹"
msgid "Request Headers"
msgstr ""
+msgid "Request details"
+msgstr ""
+
msgid "Request parameter %{param} is missing."
msgstr "必須パラメーター %{param} ãŒã‚ã‚Šã¾ã›ã‚“。"
@@ -18628,6 +19275,9 @@ msgstr "Runner 登録トークンをリセット"
msgid "Reset template"
msgstr "テンプレートã®ãƒªã‚»ãƒƒãƒˆ"
+msgid "Reset to project defaults"
+msgstr ""
+
msgid "Resetting the authorization key for this project will require updating the authorization key in every alert source it is enabled in."
msgstr "ã“ã®ãƒ—ロジェクトã®èªè¨¼ã‚­ãƒ¼ã‚’リセットã™ã‚‹ã«ã¯ã€æœ‰åŠ¹ã«ãªã£ã¦ã„ã‚‹ã™ã¹ã¦ã®ã‚¢ãƒ©ãƒ¼ãƒˆã‚½ãƒ¼ã‚¹ã§èªè¨¼ã‚­ãƒ¼ã‚’æ›´æ–°ã™ã‚‹å¿…è¦ãŒã‚ã‚Šã¾ã™ã€‚"
@@ -18724,9 +19374,6 @@ msgstr ""
msgid "Resume"
msgstr "å†é–‹"
-msgid "Resume replication"
-msgstr "レプリケーションã®å†é–‹"
-
msgid "Resync"
msgstr "å†åŒæœŸ"
@@ -19338,6 +19985,9 @@ msgstr ""
msgid "SecurityReports|False positive"
msgstr ""
+msgid "SecurityReports|Group Security Dashboard"
+msgstr ""
+
msgid "SecurityReports|Hide dismissed"
msgstr ""
@@ -19392,10 +20042,13 @@ msgstr ""
msgid "SecurityReports|Remove project from dashboard"
msgstr ""
-msgid "SecurityReports|Report type"
+msgid "SecurityReports|Return to dashboard"
msgstr ""
-msgid "SecurityReports|Return to dashboard"
+msgid "SecurityReports|Scan details"
+msgstr ""
+
+msgid "SecurityReports|Scanner type"
msgstr ""
msgid "SecurityReports|Security Dashboard"
@@ -19452,6 +20105,9 @@ msgstr ""
msgid "SecurityReports|There was an error while generating the report."
msgstr ""
+msgid "SecurityReports|Unable to add %{invalidProjectsMessage}"
+msgstr ""
+
msgid "SecurityReports|Unable to add %{invalidProjects}"
msgstr ""
@@ -19474,10 +20130,10 @@ msgid "SecurityReports|Won't fix / Accept risk"
msgstr ""
msgid "SecurityReports|You do not have sufficient permissions to access this report"
-msgstr ""
+msgstr "ã“ã®ãƒ¬ãƒãƒ¼ãƒˆã«ã‚¢ã‚¯ã‚»ã‚¹ã™ã‚‹æ¨©é™ãŒã‚ã‚Šã¾ã›ã‚“"
msgid "SecurityReports|You must sign in as an authorized user to see this report"
-msgstr ""
+msgstr "ã“ã®ãƒ¬ãƒãƒ¼ãƒˆã‚’表示ã™ã‚‹ã«ã¯ã€æ‰¿èªã•ã‚ŒãŸãƒ¦ãƒ¼ã‚¶ãƒ¼ã¨ã—ã¦ã‚µã‚¤ãƒ³ã‚¤ãƒ³ã—ãªã‘ã‚Œã°ãªã‚Šã¾ã›ã‚“。"
msgid "SecurityReports|[No reason]"
msgstr ""
@@ -19492,7 +20148,7 @@ msgid "See the affected projects in the GitLab admin panel"
msgstr "GitLab管ç†ç”»é¢ã§å½±éŸ¿ã‚’å—ã‘るプロジェクトをå‚ç…§"
msgid "See what's new at GitLab"
-msgstr ""
+msgstr "GitLab ã®æœ€æ–°æƒ…報を見る"
msgid "Select"
msgstr "é¸æŠž"
@@ -19566,6 +20222,9 @@ msgstr "ブランãƒãƒ»ã‚¿ã‚°é¸æŠž"
msgid "Select due date"
msgstr ""
+msgid "Select file"
+msgstr ""
+
msgid "Select group or project"
msgstr "グループã¾ãŸã¯ãƒ—ロジェクトをé¸æŠž"
@@ -19629,9 +20288,6 @@ msgstr "ターゲットブランãƒã‚’é¸æŠž"
msgid "Select the branch you want to set as the default for this project. All merge requests and commits will automatically be made against this branch unless you specify a different one."
msgstr "ã“ã®ãƒ—ロジェクトã®ãƒ‡ãƒ•ã‚©ãƒ«ãƒˆã¨ã—ã¦è¨­å®šã™ã‚‹ãƒ–ランãƒã‚’é¸æŠžã—ã¾ã™ã€‚ã‚ãªãŸãŒä»–ã®ãƒ–ランãƒã‚’指定ã—ãªã„é™ã‚Šã€ã™ã¹ã¦ã®ãƒžãƒ¼ã‚¸ãƒªã‚¯ã‚¨ã‚¹ãƒˆã¨ã‚³ãƒŸãƒƒãƒˆãŒã“ã®ãƒ–ランãƒã«å¯¾ã—ã¦è‡ªå‹•çš„ã«è¡Œã‚ã‚Œã¾ã™ã€‚"
-msgid "Select the configured storage available for new repositories to be placed on."
-msgstr ""
-
msgid "Select the custom project template source group."
msgstr "カスタムã®ãƒ—ロジェクトテンプレートã®ã‚°ãƒ«ãƒ¼ãƒ—ã‚’é¸æŠžã—ã¾ã™ã€‚"
@@ -19824,12 +20480,6 @@ msgstr "サービスデスク"
msgid "Service Desk is enabled but not yet active"
msgstr "サービスデスクã¯æœ‰åŠ¹ã§ã™ã€‚ã—ã‹ã—アクティブã§ã¯ã‚ã‚Šã¾ã›ã‚“"
-msgid "Service Desk is off"
-msgstr "サービスデスクã¯ã‚ªãƒ•ã§ã™"
-
-msgid "Service Desk is on"
-msgstr "サービスデスクã¯ã‚ªãƒ³ã§ã™"
-
msgid "Service Templates"
msgstr "サービス テンプレート"
@@ -19917,6 +20567,15 @@ msgstr "グループãŒ1ヶ月間ã«å…±æœ‰ãƒ©ãƒ³ãƒŠãƒ¼ã§ä½¿ç”¨ã§ãã‚‹ã®ãƒ‘ã‚
msgid "Set the milestone to %{milestone_reference}."
msgstr "マイルストーンを %{milestone_reference} ã«è¨­å®šã€‚"
+msgid "Set the number of concurrent requests this secondary node will make to the primary node while backfilling."
+msgstr ""
+
+msgid "Set the synchronization and verification capacity for the secondary node."
+msgstr ""
+
+msgid "Set the timeout in seconds to send a secondary node status to the primary and IPs allowed for the secondary nodes."
+msgstr ""
+
msgid "Set time estimate"
msgstr "見ç©æ™‚é–“ã®è¨­å®š"
@@ -19956,6 +20615,9 @@ msgstr "ウェイトを設定"
msgid "Set weight to %{weight}."
msgstr "ウェイトを %{weight} ã«è¨­å®š"
+msgid "Set what should be replicated by choosing specific projects or groups by the secondary node."
+msgstr ""
+
msgid "SetPasswordToCloneLink|set a password"
msgstr "パスワードを設定"
@@ -20082,6 +20744,9 @@ msgstr "コミットã®èª¬æ˜Žã‚’表示"
msgid "Show complete raw log"
msgstr "完全ãªç”Ÿãƒ­ã‚°ã‚’表示ã™ã‚‹"
+msgid "Show details"
+msgstr ""
+
msgid "Show file browser"
msgstr "ファイルブラウザを表示"
@@ -20091,10 +20756,13 @@ msgstr ""
msgid "Show latest version"
msgstr "最新ãƒãƒ¼ã‚¸ãƒ§ãƒ³ã‚’表示"
+msgid "Show list"
+msgstr ""
+
msgid "Show me everything"
msgstr ""
-msgid "Show me how"
+msgid "Show me how to add a pipeline"
msgstr ""
msgid "Show me more advanced stuff"
@@ -20141,9 +20809,6 @@ msgstr "ãƒãƒ¼ã‚¸ãƒ§ãƒ³ç•ªå·#%{versionNumber} を表示"
msgid "Showing all issues"
msgstr "ã™ã¹ã¦ã®èª²é¡Œã‚’表示"
-msgid "Showing all labels"
-msgstr "ã™ã¹ã¦ã®ãƒ©ãƒ™ãƒ«ã‚’表示"
-
msgid "Showing last %{size} of log -"
msgstr "ログã®ç›´è¿‘ã®éƒ¨åˆ† (%{size} サイズ) を表示"
@@ -20255,9 +20920,6 @@ msgstr "é™çš„ãªã‚¦ã‚§ãƒ–サイトã®ã‚µã‚¤ã‚ºè¨­å®š"
msgid "Skip outdated deployment jobs"
msgstr ""
-msgid "Skip this for now"
-msgstr "今ã¯ã‚¹ã‚­ãƒƒãƒ—ã™ã‚‹"
-
msgid "Skipped"
msgstr "スキップã—ã¾ã—ãŸ"
@@ -20402,6 +21064,9 @@ msgstr "ボタンã®åˆ‡ã‚Šæ›¿ãˆä¸­ã«å•é¡ŒãŒç™ºç”Ÿã—ã¾ã—ãŸ"
msgid "Something went wrong while adding your award. Please try again."
msgstr "å—賞中ã«å•é¡ŒãŒç™ºç”Ÿã—ã¾ã—ãŸã€‚ã‚‚ã†ä¸€åº¦ã‚„ã‚Šç›´ã—ã¦ãã ã•ã„。"
+msgid "Something went wrong while applying the batch of suggestions. Please try again."
+msgstr ""
+
msgid "Something went wrong while applying the suggestion. Please try again."
msgstr "æ案ã—ã¦ã„ã‚‹é–“ã«å•é¡ŒãŒç™ºç”Ÿã—ã¾ã—ãŸã€‚ã‚‚ã†ä¸€åº¦ã‚„ã‚Šç›´ã—ã¦ãã ã•ã„。"
@@ -20504,10 +21169,13 @@ msgstr ""
msgid "Something went wrong while updating a requirement."
msgstr ""
+msgid "Something went wrong while updating assignees"
+msgstr ""
+
msgid "Something went wrong while updating your list settings"
msgstr "リスト設定を更新ã™ã‚‹é–“ã«å•é¡ŒãŒç™ºç”Ÿã—ã¾ã—ãŸ"
-msgid "Something went wrong with your automatic subscription renewal"
+msgid "Something went wrong with your automatic subscription renewal."
msgstr ""
msgid "Something went wrong, unable to add %{project} to dashboard"
@@ -20525,6 +21193,9 @@ msgstr "å•é¡ŒãŒç™ºç”Ÿã—ã¦ã€ãƒ—ロジェクトを削除ã§ãã¾ã›ã‚“。"
msgid "Something went wrong, unable to search projects"
msgstr "å•é¡ŒãŒç™ºç”Ÿã—ã¦ã€ãƒ—ロジェクトを検索ã§ãã¾ã›ã‚“。"
+msgid "Something went wrong."
+msgstr ""
+
msgid "Something went wrong. Please try again."
msgstr "å•é¡ŒãŒç™ºç”Ÿã—ã¾ã—ãŸã€‚å†è©¦è¡Œã—ã¦ãã ã•ã„。"
@@ -20708,6 +21379,9 @@ msgstr "ソース (ブランãƒã‹ã‚¿ã‚°)"
msgid "Source code"
msgstr "ソースコード"
+msgid "Source code (%{fileExtension})"
+msgstr ""
+
msgid "Source is not available"
msgstr "ソースã¯åˆ©ç”¨ã§ãã¾ã›ã‚“"
@@ -20792,6 +21466,9 @@ msgstr "コミットを1ã¤ã«ã¾ã¨ã‚ã‚‹"
msgid "Stack trace"
msgstr "スタックトレース"
+msgid "Stacktrace snippet"
+msgstr ""
+
msgid "Stage"
msgstr "ステージ"
@@ -20805,7 +21482,7 @@ msgid "Stage removed"
msgstr "削除ãšã¿ã‚¹ãƒ†ãƒ¼ã‚¸"
msgid "Standard"
-msgstr ""
+msgstr "標準"
msgid "Star a label to make it a priority label. Order the prioritized labels to change their relative priority, by dragging."
msgstr "優先ラベルã«ã™ã‚‹ã«ã¯ã€ãƒ©ãƒ™ãƒ«ã«ã‚¹ã‚¿ãƒ¼ã‚’付ã‘ã¾ã™ã€‚ドラッグã—ã¦ã€å„ªå…ˆé †ä½ã‚’並ã¹æ›¿ãˆã‚‹ã“ã¨ãŒã§ãã¾ã™ã€‚"
@@ -20897,6 +21574,9 @@ msgstr "スレッドã®é–‹å§‹ã¨ã‚¯ãƒ­ãƒ¼ã‚º %{noteable_name}"
msgid "Start thread & reopen %{noteable_name}"
msgstr "スレッドã®é–‹å§‹ã¨ %{noteable_name} ã®å†é–‹"
+msgid "Start using Directed Acyclic Graphs (DAG)"
+msgstr ""
+
msgid "Start your Free Gold Trial"
msgstr "ç„¡æ–™ã®ã‚´ãƒ¼ãƒ«ãƒ‰è©¦ç”¨ç‰ˆã‚’開始ã™ã‚‹"
@@ -21359,6 +22039,21 @@ msgstr "暗黄緑色"
msgid "SuggestedColors|Very pale orange"
msgstr "æ·¡æ©™"
+msgid "Suggestion is not applicable as the suggestion was not found."
+msgstr ""
+
+msgid "Suggestions are not applicable as one or more suggestions were not found."
+msgstr ""
+
+msgid "Suggestions are not applicable as their lines cannot overlap."
+msgstr ""
+
+msgid "Suggestions must all be on the same branch."
+msgstr ""
+
+msgid "Suggestions that change line count can't be added to batches, yet."
+msgstr ""
+
msgid "Suggestions:"
msgstr "æ案:"
@@ -21399,10 +22094,7 @@ msgid "Synced"
msgstr ""
msgid "Synchronization disabled"
-msgstr ""
-
-msgid "Synchronization of container repositories is disabled."
-msgstr ""
+msgstr "åŒæœŸã¯ç„¡åŠ¹ã§ã™"
msgid "System"
msgstr "システム"
@@ -21584,6 +22276,32 @@ msgstr "利用è¦ç´„ã¨ãƒ—ライãƒã‚·ãƒ¼ãƒãƒªã‚·ãƒ¼"
msgid "Terms of Service and Privacy Policy"
msgstr "利用è¦ç´„ã¨ãƒ—ライãƒã‚·ãƒ¼ãƒãƒªã‚·ãƒ¼"
+msgid "Terraform|%{number} Terraform report failed to generate"
+msgid_plural "Terraform|%{number} Terraform reports failed to generate"
+msgstr[0] ""
+
+msgid "Terraform|%{number} Terraform report was generated in your pipelines"
+msgid_plural "Terraform|%{number} Terraform reports were generated in your pipelines"
+msgstr[0] ""
+
+msgid "Terraform|A Terraform report failed to generate."
+msgstr ""
+
+msgid "Terraform|A Terraform report was generated in your pipelines."
+msgstr ""
+
+msgid "Terraform|Generating the report caused an error."
+msgstr ""
+
+msgid "Terraform|Reported Resource Changes: %{addNum} to add, %{changeNum} to change, %{deleteNum} to delete"
+msgstr ""
+
+msgid "Terraform|The Terraform report %{name} failed to generate."
+msgstr ""
+
+msgid "Terraform|The Terraform report %{name} was generated in your pipelines."
+msgstr ""
+
msgid "Test"
msgstr "テスト"
@@ -21609,8 +22327,8 @@ msgstr "プロジェクトã«CIジョブãŒã‚ã‚‹ã“ã¨ã‚’確èªã—ã¦ãã ã•ã
msgid "TestHooks|Ensure the project has CI pipelines."
msgstr "プロジェクトã«CIパイプラインãŒã‚ã‚‹ã“ã¨ã‚’確èªã—ã¦ãã ã•ã„。"
-msgid "TestHooks|Ensure the project has at least one commit."
-msgstr "プロジェクトã«å°‘ãªãã¨ã‚‚1ã¤ã®ã‚³ãƒŸãƒƒãƒˆãŒã‚ã‚‹ã“ã¨ã‚’確èªã—ã¦ãã ã•ã„。"
+msgid "TestHooks|Ensure the project has deployments."
+msgstr ""
msgid "TestHooks|Ensure the project has issues."
msgstr "プロジェクトã«èª²é¡ŒãŒã‚ã‚‹ã“ã¨ã‚’確èªã—ã¦ãã ã•ã„。"
@@ -21709,7 +22427,7 @@ msgstr "課題トラッカーã¯ã€ãƒ—ロジェクトを改善ã—ãŸã‚Šè§£æ±ºã—
msgid "The Prometheus server responded with \"bad request\". Please check your queries are correct and are supported in your Prometheus version. %{documentationLink}"
msgstr "Prometheusサーãƒãƒ¼ã¯ã€Œæ‚ªã„リクエストã€ã¨å¿œç­”ã—ã¾ã—ãŸã€‚クエリãŒæ­£ã—ãã‚ãªãŸã®Prometheusã®ãƒãƒ¼ã‚¸ãƒ§ãƒ³ã§ã‚µãƒãƒ¼ãƒˆã•ã‚Œã¦ã„ã‚‹ã“ã¨ã‚’確èªã—ã¦ãã ã•ã„。 %{documentationLink}"
-msgid "The URL defined on the primary node that secondary nodes should use to contact it. Defaults to URL"
+msgid "The URL defined on the primary node that secondary nodes should use to contact it."
msgstr ""
msgid "The URL to use for connecting to Elasticsearch. Use a comma-separated list to support clustering (e.g., \"http://localhost:9200, http://localhost:9201\")."
@@ -21718,6 +22436,9 @@ msgstr "Elasticsearchã¸ã®æŽ¥ç¶šã«ä½¿ç”¨ã™ã‚‹URL。クラスタリングをã‚
msgid "The X509 Certificate to use when mutual TLS is required to communicate with the external authorization service. If left blank, the server certificate is still validated when accessing over HTTPS."
msgstr "外部èªè¨¼ã‚µãƒ¼ãƒ“スã¨ã®é€šä¿¡ã«ç›¸äº’ TLS ãŒå¿…è¦ãªå ´åˆã«ä½¿ç”¨ã™ã‚‹ X509 証明書。空白ã®ã¾ã¾ã«ã™ã‚‹ã¨ã€HTTPS 経由ã§ã‚¢ã‚¯ã‚»ã‚¹ã™ã‚‹ã¨ãã«ã‚µãƒ¼ãƒè¨¼æ˜Žæ›¸ã®æ¤œè¨¼ãŒè¡Œã‚ã‚Œã¾ã™ã€‚"
+msgid "The above settings apply to all projects with the selected compliance framework(s)."
+msgstr ""
+
msgid "The amount of seconds after which a request to get a secondary node status will time out."
msgstr "セカンダリノードã®ã‚¹ãƒ†ãƒ¼ã‚¿ã‚¹ã‚’å–å¾—ã™ã‚‹è¦æ±‚ãŒã‚¿ã‚¤ãƒ ã‚¢ã‚¦ãƒˆã™ã‚‹ã¾ã§ã®ç§’数。"
@@ -22016,9 +22737,6 @@ msgstr "ã“ã®ã‚¹ãƒ†ãƒ¼ã‚¸ã«åŽé›†ã•ã‚ŒãŸãƒ‡ãƒ¼ã‚¿æ¯Žã®æ™‚é–“"
msgid "The total stage shows the time it takes between creating an issue and deploying the code to production. The data will be automatically added once you have completed the full idea to production cycle."
msgstr ""
-msgid "The unique identifier for the Geo node. Must match %{geoNodeName} if it is set in gitlab.rb, otherwise it must match %{externalUrl} with a trailing slash"
-msgstr ""
-
msgid "The update action will time out after %{number_of_minutes} minutes. For big repositories, use a clone/push combination."
msgstr "アップデート㯠%{number_of_minutes} 分後ã«ã‚¿ã‚¤ãƒ ã‚¢ã‚¦ãƒˆã—ã¾ã™ã€‚大ããªãƒªãƒã‚¸ãƒˆãƒªã«ã¯ã€clone 㨠push を組ã¿åˆã‚ã›ã¦ä½¿ã£ã¦ãã ã•ã„。"
@@ -22043,9 +22761,6 @@ msgstr "ユーザーマップã¯ã€ã‚ãªãŸã®ãƒ—ロジェクトã«å‚加ã—ãŸ
msgid "The user you are trying to deactivate has been active in the past %{minimum_inactive_days} days and cannot be deactivated"
msgstr ""
-msgid "The user-facing URL of the Geo node"
-msgstr "ジオノードã®ãƒ¦ãƒ¼ã‚¶ãƒ¼å‘ã‘ã®URL。"
-
msgid "The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6."
msgstr "得られãŸä¸€é€£ã®ãƒ‡ãƒ¼ã‚¿ã‚’å°ã•ã„é †ã«ä¸¦ã¹ãŸã¨ãã«ä¸­å¤®ã«ä½ç½®ã™ã‚‹å€¤ã€‚例ãˆã°ã€3, 5, 9 ã®ä¸­å¤®å€¤ã¯ 5。3, 5, 7, 8 ã®ä¸­å¤®å€¤ã¯ (5+7)/2 = 6。"
@@ -22136,6 +22851,9 @@ msgstr "ã‚ãªãŸã®ãƒ‡ãƒã‚¤ã‚¹ã¨ã®é–“ã«é€šä¿¡éšœå®³ãŒç™ºç”Ÿã—ã¦ã„ã¾ã™
msgid "There was a problem fetching project branches."
msgstr ""
+msgid "There was a problem fetching project tags."
+msgstr ""
+
msgid "There was a problem fetching project users."
msgstr ""
@@ -22187,6 +22905,9 @@ msgstr ""
msgid "There was an error fetching the %{replicableType}"
msgstr ""
+msgid "There was an error fetching the Geo Settings"
+msgstr ""
+
msgid "There was an error fetching the Node's Groups"
msgstr ""
@@ -22307,6 +23028,12 @@ msgstr "ã“ã® %{issuableDisplayName} ã¯ãƒ­ãƒƒã‚¯ã•ã‚Œã¦ã„ã¾ã™ã€‚プロジ
msgid "This %{issuable} is locked. Only <strong>project members</strong> can comment."
msgstr "ã“ã® %{issuable} ã¯ãƒ­ãƒƒã‚¯ã•ã‚Œã¦ã„ã¾ã™ã€‚<strong>プロジェクトメンãƒãƒ¼</strong> ã ã‘ãŒã‚³ãƒ¡ãƒ³ãƒˆã§ãã¾ã™ã€‚"
+msgid "This %{noteableTypeText} is %{confidentialLinkStart}confidential%{linkEnd} and %{lockedLinkStart}locked%{linkEnd}."
+msgstr ""
+
+msgid "This %{noteableTypeText} is locked."
+msgstr ""
+
msgid "This %{viewer} could not be displayed because %{reason}. You can %{options} instead."
msgstr "%{reason} ã®ãŸã‚ã€ã“ã® %{viewer} ã¯è¡¨ç¤ºã§ãã¾ã›ã‚“ã§ã—ãŸã€‚代ã‚ã‚Šã« %{options} ãŒä½¿ç”¨ã§ãã¾ã™ã€‚"
@@ -22325,8 +23052,11 @@ msgstr ""
msgid "This action can lead to data loss. To prevent accidental actions we ask you to confirm your intention."
msgstr "ã“ã®å‹•ä½œã«ã‚ˆã£ã¦ãƒ‡ãƒ¼ã‚¿ãŒå¤±ã‚れるå¯èƒ½æ€§ãŒã‚ã‚Šã¾ã™ã€‚çªç™ºçš„ãªäº‹æ…‹ã‚’防ããŸã‚ã«ã€ä¸€åº¦æ“作ã®æ„図を確èªã—ã¦ãã ã•ã„。ãŠé¡˜ã„ã—ã¾ã™ã€‚"
+msgid "This also resolves all related threads"
+msgstr ""
+
msgid "This also resolves the discussion"
-msgstr "ã“れもディスカッションを解決ã—ã¾ã™"
+msgstr ""
msgid "This application was created by %{link_to_owner}."
msgstr "ã“ã®ã‚¢ãƒ—リケーション㯠%{link_to_owner} ã«ã‚ˆã£ã¦ä½œæˆã•ã‚Œã¾ã—ãŸã€‚"
@@ -22415,6 +23145,9 @@ msgstr ""
msgid "This epic does not exist or you don't have sufficient permission."
msgstr "ã“ã®ã‚¨ãƒ”ックã¯å­˜åœ¨ã—ãªã„ã‹ã€ã‚ãªãŸã«å分ãªæ¨©é™ãŒã‚ã‚Šã¾ã›ã‚“。"
+msgid "This feature is currently in beta. We invite you to %{linkStart}give feedback%{linkEnd}."
+msgstr ""
+
msgid "This feature requires local storage to be enabled"
msgstr ""
@@ -22448,8 +23181,8 @@ msgstr "ã“ã‚Œã¯ã€ä»¥å‰ã«å‰Šé™¤ã•ã‚ŒãŸãƒ¦ãƒ¼ã‚¶ãƒ¼ã«ã‚ˆã£ã¦ä½œæˆã•ã‚Œ
msgid "This is a Work in Progress"
msgstr "ã“ã‚Œã¯ä½œæ¥­ä¸­(WIP) ã§ã™ã€‚"
-msgid "This is a confidential issue."
-msgstr "ã“ã‚Œã¯éžå…¬é–‹ã®èª²é¡Œã§ã™ã€‚"
+msgid "This is a confidential %{noteableTypeText}."
+msgstr ""
msgid "This is a delayed job to run in %{remainingTime}"
msgstr "ã“れ㯠%{remainingTime} ã«å®Ÿè¡Œã•ã‚Œã‚‹é…延ジョブã§ã™"
@@ -22475,9 +23208,6 @@ msgstr ""
msgid "This is your current session"
msgstr "ã“ã‚Œã¯ã‚ãªãŸã®ç¾åœ¨ã®ã‚»ãƒƒã‚·ãƒ§ãƒ³ã§ã™"
-msgid "This issue is %{confidentialLinkStart}confidential%{linkEnd} and %{lockedLinkStart}locked%{linkEnd}."
-msgstr "ã“ã®èª²é¡Œã¯ %{confidentialLinkStart} 機密 %{linkEnd} ã§ã‚り〠%{lockedLinkStart}ロック%{linkEnd}ã•ã‚Œã¦ã„ã¾ã™ã€‚"
-
msgid "This issue is confidential"
msgstr "ã“ã®èª²é¡Œã¯éžå…¬é–‹è¨­å®šã§ã™"
@@ -22487,9 +23217,6 @@ msgstr "ã“ã®èª²é¡Œã¯ç¾åœ¨ã€æ¬¡ã®èª²é¡Œã«ã‚ˆã£ã¦ãƒ–ロックã•ã‚Œã¦ã„
msgid "This issue is in a child epic of the filtered epic"
msgstr ""
-msgid "This issue is locked."
-msgstr "ã“ã®èª²é¡Œã¯ãƒ­ãƒƒã‚¯ã•ã‚Œã¦ã„ã¾ã™ã€‚"
-
msgid "This job depends on other jobs with expired/erased artifacts: %{invalid_dependencies}"
msgstr "ã“ã®ã‚¸ãƒ§ãƒ–ã¯ã€æœŸé™åˆ‡ã‚Œ/消去済ã¿ã®ã‚¢ãƒ¼ãƒ†ã‚£ãƒ•ã‚¡ã‚¯ãƒˆã‚’æŒã¤ä»–ã®ã‚¸ãƒ§ãƒ–ã«ä¾å­˜ã—ã¦ã„ã¾ã™ï¼š%{invalid_dependencies}"
@@ -22589,6 +23316,9 @@ msgstr "ã“ã®ã‚¸ãƒ§ãƒ–ã¯ã‚¿ã‚¤ãƒžãƒ¼çµ‚了後ã«è‡ªå‹•çš„ã«å®Ÿè¡Œã•ã‚Œã¾ã™
msgid "This license has already expired."
msgstr ""
+msgid "This link points to external content"
+msgstr ""
+
msgid "This may expose confidential information as the selected fork is in another namespace that can have other members."
msgstr "é¸æŠžã—ãŸãƒ•ã‚©ãƒ¼ã‚¯ã¯ä»–ã®ãƒ¡ãƒ³ãƒãƒ¼ã‚’æŒã¤ã“ã¨ãŒã§ãる別ã®åå‰ç©ºé–“ã«ã‚ã‚‹ãŸã‚ã€ã“ã‚Œã«ã‚ˆã‚Šæ©Ÿå¯†æƒ…å ±ãŒå…¬é–‹ã•ã‚Œã‚‹å¯èƒ½æ€§ãŒã‚ã‚Šã¾ã™ã€‚"
@@ -22616,6 +23346,9 @@ msgstr "複数ã®ãƒ—ロジェクト間ã§èª­ã¿è¾¼ã¿ãŒè¨±å¯ã•ã‚Œã¦ã„ãªã„
msgid "This page will be removed in a future release."
msgstr "ã“ã®ãƒšãƒ¼ã‚¸ã¯ã€å°†æ¥ã®ãƒªãƒªãƒ¼ã‚¹ã§å‰Šé™¤ã•ã‚Œã‚‹äºˆå®šã§ã™ã€‚"
+msgid "This pipeline does not use the %{codeStart}needs%{codeEnd} keyword and can't be represented as a directed acyclic graph."
+msgstr ""
+
msgid "This pipeline makes use of a predefined CI/CD configuration enabled by %{strongStart}Auto DevOps.%{strongEnd}"
msgstr "ã“ã®ãƒ‘イプラインã¯ã€ %{strongStart}Auto DevOps%{strongEnd}ã«ã‚ˆã£ã¦æœ‰åŠ¹åŒ–ã•ã‚ŒãŸå®šç¾©æ¸ˆã¿ã®CI/CD構æˆã‚’利用ã—ã¾ã™ã€‚"
@@ -22704,7 +23437,7 @@ msgid "This user will be the author of all events in the activity feed that are
msgstr "ã“ã®ãƒ¦ãƒ¼ã‚¶ãƒ¼ã¯ã€æ–°ã—ã作æˆã•ã‚ŒãŸãƒ–ランãƒã‚„既存ã®ãƒ–ランãƒã¸ã®æ–°è¦ã‚³ãƒŸãƒƒãƒˆãªã©ã®ã‚ˆã†ã«ã€æ›´æ–°ã®çµæžœã§ã‚るアクティビティーフィード内ã®ã™ã¹ã¦ã®ã‚¤ãƒ™ãƒ³ãƒˆã®ä½œæˆè€…ã«ãªã‚Šã¾ã™ã€‚"
msgid "This variable can not be masked."
-msgstr ""
+msgstr "ã“ã®å¤‰æ•°ã¯ãƒžã‚¹ã‚¯ã§ãã¾ã›ã‚“."
msgid "This variable does not match the expected pattern."
msgstr ""
@@ -22844,6 +23577,9 @@ msgstr "最åˆã®ã‚³ãƒŸãƒƒãƒˆã‹ã‚‰æœ€åˆã®ã‚³ãƒ¡ãƒ³ãƒˆã¾ã§ã®æ™‚é–“"
msgid "Time from last commit to merge"
msgstr "ç›´å‰ã®ã‚³ãƒŸãƒƒãƒˆã‹ã‚‰ãƒžãƒ¼ã‚¸ã¾ã§ã®æ™‚é–“"
+msgid "Time in seconds"
+msgstr ""
+
msgid "Time in seconds GitLab will wait for a response from the external service. When the service does not respond in time, access will be denied."
msgstr "外部サービスã‹ã‚‰ã®å¿œç­”時間(秒å˜ä½ï¼‰ã‚’設定ã—ã¾ã™ã€‚設定時間内ã«å¿œç­”ãŒç„¡ã„å ´åˆã€ã‚¢ã‚¯ã‚»ã‚¹ãŒæ‹’å¦ã•ã‚Œã¾ã™ã€‚"
@@ -23036,7 +23772,7 @@ msgid "Title:"
msgstr "タイトル:"
msgid "Titles and Descriptions"
-msgstr ""
+msgstr "タイトルãŠã‚ˆã³èª¬æ˜Ž"
msgid "To"
msgstr ""
@@ -23263,9 +23999,15 @@ msgstr "ç·è²¢çŒ®åº¦"
msgid "Total artifacts size: %{total_size}"
msgstr "アーティファクトã®ã‚µã‚¤ã‚º(åˆè¨ˆ): %{total_size}"
+msgid "Total cores (CPUs)"
+msgstr ""
+
msgid "Total issues"
msgstr "全課題"
+msgid "Total memory (GB)"
+msgstr ""
+
msgid "Total test time for all commits/merges"
msgstr "ã™ã¹ã¦ã®ã‚³ãƒŸãƒƒãƒˆ/マージã®åˆè¨ˆãƒ†ã‚¹ãƒˆæ™‚é–“"
@@ -23408,7 +24150,7 @@ msgid "Try all GitLab has to offer for 30 days."
msgstr "30日間ã§GitLabãŒæä¾›ã™ã‚‹ã™ã¹ã¦ã®æ©Ÿèƒ½ã‚’試ã—ã¦ã¿ã¦ãã ã•ã„。"
msgid "Try changing or removing filters."
-msgstr ""
+msgstr "フィルターを変更ã—ã¦ã¿ã¦ãã ã•ã„。ã¾ãŸã¯å‰Šé™¤ã—ã¦ã¿ã¦ãã ã•ã„。"
msgid "Try to fork again"
msgstr "ã‚‚ã†ä¸€åº¦ãƒ•ã‚©ãƒ¼ã‚¯ã™ã‚‹"
@@ -23467,6 +24209,9 @@ msgstr "2è¦ç´ èªè¨¼ãƒ‡ãƒã‚¤ã‚¹ (%{length})"
msgid "U2F only works with HTTPS-enabled websites. Contact your administrator for more details."
msgstr "2è¦ç´ èªè¨¼ã¯HTTPSãŒæœ‰åŠ¹ãªWebサイトã§ã®ã¿å‹•ä½œã—ã¾ã™ã€‚詳細ã«ã¤ã„ã¦ã¯ç®¡ç†è€…ã«é€£çµ¡ã—ã¦ãã ã•ã„。"
+msgid "UI Development Kit"
+msgstr ""
+
msgid "URL"
msgstr "URL"
@@ -23510,7 +24255,7 @@ msgid "Unable to connect to server: %{error}"
msgstr "サーãƒãƒ¼ã«æŽ¥ç¶šã§ãã¾ã›ã‚“ã§ã—ãŸ: %{error}"
msgid "Unable to connect to the Jira instance. Please check your Jira integration configuration."
-msgstr ""
+msgstr "Jira インスタンスã«æŽ¥ç¶šã§ãã¾ã›ã‚“。 Jira çµ±åˆè¨­å®šã‚’確èªã—ã¦ãã ã•ã„。"
msgid "Unable to convert Kubernetes logs encoding to UTF-8"
msgstr ""
@@ -23539,9 +24284,6 @@ msgstr "差分を読ã¿è¾¼ã‚€ã“ã¨ãŒã§ãã¾ã›ã‚“。%{button_try_again}"
msgid "Unable to load the merge request widget. Try reloading the page."
msgstr "マージリクエストウィジェットをロードã§ãã¾ã›ã‚“。ページをå†èª­ã¿è¾¼ã¿ã—ã¦ãã ã•ã„。"
-msgid "Unable to process group import file"
-msgstr ""
-
msgid "Unable to resolve"
msgstr "解決ä¸å¯"
@@ -23557,6 +24299,9 @@ msgstr "パイプラインを今ã™ã実行ã™ã‚‹ã‚ˆã†ã«ã‚¹ã‚±ã‚¸ãƒ¥ãƒ¼ãƒ«ã§
msgid "Unable to sign you in to the group with SAML due to \"%{reason}\""
msgstr "\"%{reason}\"ã®ãŸã‚ SAML ã§ã‚°ãƒ«ãƒ¼ãƒ—ã«ã‚µã‚¤ãƒ³ã‚¤ãƒ³ã§ãã¾ã›ã‚“"
+msgid "Unable to suggest a path. Please refresh and try again."
+msgstr ""
+
msgid "Unable to update label prioritization at this time"
msgstr "ç¾æ™‚点ã§ã¯ãƒ©ãƒ™ãƒ«ã®å„ªå…ˆé †ä½ä»˜ã‘ã‚’æ›´æ–°ã§ãã¾ã›ã‚“"
@@ -23572,6 +24317,9 @@ msgstr "プロジェクトã®ã‚¢ãƒ¼ã‚«ã‚¤ãƒ–解除"
msgid "Unarchiving the project will restore people's ability to make changes to it. The repository can be committed to, and issues, comments, and other entities can be created. %{strong_start}Once active, this project shows up in the search and on the dashboard.%{strong_end}"
msgstr "ã“ã®ã‚¢ãƒ¼ã‚«ã‚¤ãƒ–化ã—ãŸãƒ—ロジェクトを解除ã™ã‚‹ã¨ã€ã“ã®ãƒ—ロジェクトã«å¤‰æ›´ã‚’加ãˆã‚‰ã‚Œã‚‹ã‚ˆã†ã«ãªã‚Šã¾ã™ã€‚リãƒã‚¸ãƒˆãƒªã¯ã‚³ãƒŸãƒƒãƒˆã§ãるよã†ã«ãªã‚Šã€èª²é¡Œã€ã‚³ãƒ¡ãƒ³ãƒˆã€ãã®ä»–ã®ã‚¨ãƒ³ãƒ†ã‚£ãƒ†ã‚£ã‚’作æˆã§ãã¾ã™ã€‚ %{strong_start}ã“ã®ãƒ—ロジェクトをアクティブ化ãŒçµ‚ã‚‹ã¨ã€ã“ã®ãƒ—ロジェクトã¯æ¤œç´¢ã¨ãƒ€ãƒƒã‚·ãƒ¥ãƒœãƒ¼ãƒ‰ã«è¡¨ç¤ºã•ã‚Œã‚‹ã‚ˆã†ã«ãªã‚Šã¾ã™ã€‚%{strong_end}"
+msgid "Unassign from commenting user"
+msgstr ""
+
msgid "Unblock"
msgstr "ブロック解除"
@@ -23647,6 +24395,9 @@ msgstr "ã“ã® %{noun} を作業中マークを解除ã—ã¾ã—ãŸã€‚"
msgid "Unreachable"
msgstr ""
+msgid "Unrecognized cluster type"
+msgstr ""
+
msgid "Unresolve"
msgstr ""
@@ -23707,6 +24458,9 @@ msgstr ""
msgid "Unsubscribes from this %{quick_action_target}."
msgstr ""
+msgid "Unsupported todo type passed. Supported todo types are: %{todo_types}"
+msgstr ""
+
msgid "Until"
msgstr ""
@@ -23734,6 +24488,9 @@ msgstr "å…¨ã¦æ›´æ–°"
msgid "Update approval rule"
msgstr "承èªãƒ«ãƒ¼ãƒ«ã®æ›´æ–°"
+msgid "Update approvers"
+msgstr ""
+
msgid "Update failed"
msgstr "更新失敗"
@@ -23743,6 +24500,9 @@ msgstr "æ›´æ–°ã«å¤±æ•—ã—ã¾ã—ãŸã€‚後ã§ã‚‚ã†ä¸€åº¦ãŠè©¦ã—ãã ã•ã„"
msgid "Update it"
msgstr "æ›´æ–°!"
+msgid "Update iteration"
+msgstr ""
+
msgid "Update now"
msgstr "今ã™ãæ›´æ–°"
@@ -23788,7 +24548,7 @@ msgstr "%{updated_by} ã«ã‚ˆã£ã¦%{updated_at} ã«æ›´æ–°"
msgid "Updated at"
msgstr ""
-msgid "Updated to"
+msgid "Updated to %{linkStart}chart v%{linkEnd}"
msgstr ""
msgid "Updating"
@@ -23818,6 +24578,9 @@ msgstr "<code>GoogleCodeProjectHosting.json</code> をアップロードã—ã¾ã
msgid "Upload CSV file"
msgstr "CSVファイルã®ã‚¢ãƒƒãƒ—ロード"
+msgid "Upload License"
+msgstr ""
+
msgid "Upload New File"
msgstr "æ–°è¦ãƒ•ã‚¡ã‚¤ãƒ«ã‚’アップロード"
@@ -23899,18 +24662,21 @@ msgstr "パッケージ"
msgid "UsageQuota|Pipelines"
msgstr "パイプライン"
+msgid "UsageQuota|Purchase more storage"
+msgstr ""
+
msgid "UsageQuota|Repositories"
msgstr ""
msgid "UsageQuota|Repository"
msgstr "リãƒã‚¸ãƒˆãƒª"
+msgid "UsageQuota|Snippets"
+msgstr ""
+
msgid "UsageQuota|Storage"
msgstr "ストレージ"
-msgid "UsageQuota|Storage usage:"
-msgstr ""
-
msgid "UsageQuota|This namespace has no projects which use shared runners"
msgstr "ã“ã®ãƒãƒ¼ãƒ ã‚¹ãƒšãƒ¼ã‚¹ã«ã¯ã€å…±æœ‰ãƒ©ãƒ³ãƒŠãƒ¼ã‚’使用ã™ã‚‹ãƒ—ロジェクトã¯ã‚ã‚Šã¾ã›ã‚“。"
@@ -23941,6 +24707,12 @@ msgstr "Wiki"
msgid "UsageQuota|Wikis"
msgstr ""
+msgid "UsageQuota|You used: %{usage} %{limit}"
+msgstr ""
+
+msgid "UsageQuota|out of %{formattedLimit} of your namespace storage"
+msgstr ""
+
msgid "Use %{code_start}::%{code_end} to create a %{link_start}scoped label set%{link_end} (eg. %{code_start}priority::1%{code_end})"
msgstr "%{link_start} スコープã®ãƒ©ãƒ™ãƒ«ã‚»ãƒƒãƒˆ%{link_end} を作æˆã™ã‚‹ã«ã¯ã€ %{code_start}::%{code_end} を使用ã—ã¾ã™ï¼ˆä¾‹ï¼š %{code_start}priority::1%{code_end})。"
@@ -24007,6 +24779,9 @@ msgstr "ユーザー世代㯠%{usage_ping_link_start} ping ã®ä½¿ç”¨ %{usage_pi
msgid "User IDs"
msgstr ""
+msgid "User Lists can only be created and modified with %{linkStart}the API%{linkEnd}"
+msgstr ""
+
msgid "User OAuth applications"
msgstr "ユーザー OAuth アプリケーション"
@@ -24031,6 +24806,9 @@ msgstr ""
msgid "User key was successfully removed."
msgstr "ユーザーキーを正常ã«å‰Šé™¤ã—ã¾ã—ãŸã€‚"
+msgid "User list %{name} will be removed. Are you sure?"
+msgstr ""
+
msgid "User map"
msgstr "ユーザーマップ"
@@ -24052,149 +24830,11 @@ msgstr "ユーザーã¯æ­£å¸¸ã«ãƒ—ロジェクトã‹ã‚‰å‰Šé™¤ã•ã‚Œã¾ã—ãŸã€‚
msgid "User was successfully updated."
msgstr "ユーザーを正常ã«æ›´æ–°ã—ã¾ã—ãŸã€‚"
-msgid "UserOnboardingTour|%{activeTour}/%{totalTours}"
-msgstr "%{activeTour}/%{totalTours}"
-
-msgid "UserOnboardingTour|%{completed}/%{total} steps completed"
-msgstr "%{total} ステップ中 %{completed} ステップ完了"
-
-msgid "UserOnboardingTour|%{emphasisStart}Well done!%{emphasisEnd}%{lineBreak}%{lineBreak}That's it for our guided tour, congratulations for making it all the way to the end!%{lineBreak}%{lineBreak}We hope this gave you a good overview of GitLab and how it can help you. We'll now show you how to create your own project and invite your colleagues."
-msgstr "%{emphasisStart}よãã§ãã¾ã—ãŸï¼%{emphasisEnd}%{lineBreak}%{lineBreak}ã“ã‚Œã§ã‚¬ã‚¤ãƒ‰ãƒ„アーã¯çµ‚了ã§ã™ã€‚最後ã¾ã§å®Œäº†ã—ã¾ã—ãŸã€‚ãŠã‚ã§ã¨ã†ã”ã–ã„ã¾ã™ï¼%{lineBreak}%{lineBreak}ã“れ㧠GitLab ã®æ¦‚è¦ã¨ã©ã†æ´»ç”¨ã™ã‚‹ã‹ç†è§£ã§ããŸã¨æ€ã„ã¾ã™ã€‚ã“ã‚Œã‹ã‚‰ã‚ãªãŸè‡ªèº«ã®ãƒ—ロジェクトを作æˆã—ã€åŒåƒšã‚’招待ã™ã‚‹æ–¹æ³•ã‚’紹介ã—ã¾ã™ã€‚"
-
-msgid "UserOnboardingTour|Adding other members to a project is done through Project Settings. Click on %{emphasisStart}Settings%{emphasisEnd}."
-msgstr "プロジェクト設定ã‹ã‚‰ä»–ã®ãƒ¡ãƒ³ãƒãƒ¼ã‚’プロジェクトã«è¿½åŠ ã§ãã¾ã™ã€‚%{emphasisStart}設定%{emphasisEnd}をクリックã—ã¦ãã ã•ã„。"
-
-msgid "UserOnboardingTour|Alright, that's it for Commits. Let's take a look at the %{emphasisStart}Branches%{emphasisEnd}."
-msgstr "ãã†ã€ã“ã‚ŒãŒã‚³ãƒŸãƒƒãƒˆã§ã™ã€‚ %{emphasisStart}ブランãƒ%{emphasisEnd}を見ã¦ã¿ã¾ã—ょã†ã€‚"
-
-msgid "UserOnboardingTour|Awesome! Now click on %{emphasisStart}Members%{emphasisEnd}."
-msgstr "ã™ã°ã‚‰ã—ã„ï¼ç¶šã„ã¦%{emphasisStart}メンãƒãƒ¼%{emphasisEnd}をクリック。"
-
-msgid "UserOnboardingTour|Click on one of the %{emphasisStart}Compare%{emphasisEnd} buttons to compare a branch to master."
-msgstr "ä»»æ„ã®ãƒ–ランãƒã‚’ master ブランãƒã¨æ¯”較ã™ã‚‹ã«ã¯ã€ã„ãšã‚Œã‹ã® %{emphasisStart} 比較%{emphasisEnd} ボタンをクリックã—ã¾ã™ã€‚"
-
-msgid "UserOnboardingTour|Click on one of the %{emphasisStart}pipeline IDs%{emphasisEnd} to see the details of a pipeline."
-msgstr "パイプラインã®è©³ç´°ã‚’表示ã™ã‚‹ã«ã¯ã€ %{emphasisStart}パイプラインID%{emphasisEnd} ã„ãšã‚Œã‹ã‚’クリックã—ã¾ã™ã€‚"
-
-msgid "UserOnboardingTour|Click to open the latest commit to see its details."
-msgstr "最新ã®ã‚³ãƒŸãƒƒãƒˆã‚’é–‹ã„ã¦è©³ç´°ã‚’確èªã™ã‚‹ã«ã¯ã‚¯ãƒªãƒƒã‚¯ã—ã¾ã™ã€‚"
-
-msgid "UserOnboardingTour|Close 'Learn GitLab'"
-msgstr "'GitLabã«ã¤ã„ã¦å­¦ã¶' を終了"
-
-msgid "UserOnboardingTour|Commits are shown in chronological order and can be filtered by the commit message or by the branch."
-msgstr "コミットã¯æ™‚系列ã§è¡¨ç¤ºã•ã‚Œã€ã‚³ãƒŸãƒƒãƒˆãƒ¡ãƒƒã‚»ãƒ¼ã‚¸ã¾ãŸã¯ãƒ–ランãƒã§ãƒ•ã‚£ãƒ«ã‚¿ãƒªãƒ³ã‚°ã§ãã¾ã™ã€‚"
-
-msgid "UserOnboardingTour|Create a project"
-msgstr "プロジェクトを作æˆã™ã‚‹"
-
-msgid "UserOnboardingTour|Exit 'Learn GitLab'"
-msgstr "'GitLabã«ã¤ã„ã¦å­¦ã¶' を終了"
-
-msgid "UserOnboardingTour|Got it"
-msgstr "了解"
-
-msgid "UserOnboardingTour|Great job! %{clapHands} We hope the tour was helpful and that you learned how to use GitLab.%{lineBreak}%{lineBreak}We'd love to get your feedback on this tour.%{lineBreak}%{lineBreak}%{emphasisStart}How helpful would you say this guided tour was?%{emphasisEnd}%{lineBreak}%{lineBreak}"
-msgstr "よãã§ãã¾ã—ãŸï¼ %{clapHands} ã“ã®ãƒ„アーãŒå‚考ã«ãªã‚Šã€GitLab ã®ä½¿ã„方を学ã¹ã‚Œã°å¹¸ã„ã§ã™ã€‚%{lineBreak}%{lineBreak}ã“ã®ãƒ„アーã«é–¢ã™ã‚‹ãƒ•ã‚£ãƒ¼ãƒ‰ãƒãƒƒã‚¯ã‚’ãŠå¾…ã¡ã—ã¦ãŠã‚Šã¾ã™ã€‚%{lineBreak}%{lineBreak}%{emphasisStart}ã“ã®ã‚¬ã‚¤ãƒ‰ä»˜ãツアーã¯ã©ã®ç¨‹åº¦å½¹ã«ç«‹ã¡ã¾ã—ãŸã‹ï¼Ÿ%{emphasisEnd}%{lineBreak}%{lineBreak}"
-
-msgid "UserOnboardingTour|Guided GitLab Tour"
-msgstr "ガイド付ãGitLabツアー"
-
-msgid "UserOnboardingTour|Here you can compare the changes of this branch to another one. Changes are divided by files so that it's easier to see what was changed where."
-msgstr "ã“ã®ãƒ–ランãƒã®å¤‰æ›´ã‚’ä»–ã®ãƒ–ランãƒã¨æ¯”較ã™ã‚‹ã“ã¨ãŒã§ãã¾ã™ã€‚変更ã¯ãƒ•ã‚¡ã‚¤ãƒ«ã«ã‚ˆã£ã¦åˆ†å‰²ã•ã‚Œã‚‹ãŸã‚ã€å¤‰æ›´å†…容ãŒã©ã“ã§å¤‰æ›´ã•ã‚ŒãŸã‹ãŒåˆ†ã‹ã‚Šã‚„ã™ããªã‚Šã¾ã™ã€‚"
-
-msgid "UserOnboardingTour|Here you can create a project from scratch, start with a template or import a repository from other platforms. Whatever you choose, we'll guide you through the process.%{lineBreak}%{lineBreak}Fill in your new project information and click on %{emphasisStart}Create Project%{emphasisEnd} to progress to the next step."
-msgstr "ã“ã“ã§ã¯ã€ãƒ—ロジェクトを最åˆã‹ã‚‰ä½œæˆã™ã‚‹ã€ãƒ†ãƒ³ãƒ—レートã‹ã‚‰å§‹ã‚ã‚‹ã€ã¾ãŸã¯ä»–ã®ãƒ—ラットフォームã‹ã‚‰ãƒªãƒã‚¸ãƒˆãƒªã‚’インãƒãƒ¼ãƒˆã™ã‚‹ã“ã¨ãŒã§ãã¾ã™ã€‚ã©ã®æ–¹æ³•ã§å§‹ã‚ã¦ã‚‚ã€é€²è¡ŒçŠ¶æ³ã‚’ãŠçŸ¥ã‚‰ã›ã—ã¾ã™ã€‚%{lineBreak}%{lineBreak} æ–°ã—ã„プロジェクトã®æƒ…報を入力ã—ã¦ã€ %{emphasisStart}プロジェクトを作æˆ%{emphasisEnd} をクリックã—ã¦æ¬¡ã®ã‚¹ãƒ†ãƒƒãƒ—ã«é€²ã¿ã¾ã™ã€‚"
-
-msgid "UserOnboardingTour|Here you can see the breakdown of the pipelines: its stages and jobs in each of the stages and their status.%{lineBreak}%{lineBreak}Our CI/CD pipelines are quite complex, most of our users have fewer and simpler pipelines."
-msgstr "ã“ã“ã§ãƒ‘イプラインã®ä¸­èº«ã‚’見るã“ã¨ãŒã§ãã¾ã™ã€‚ステージã¨ã€å„ステージã®ã‚¸ãƒ§ãƒ–ã€ãã—ã¦ãれらã®ã‚¹ãƒ†ãƒ¼ã‚¿ã‚¹ã§ã™ã€‚%{lineBreak}%{lineBreak}ç§ãŸã¡ã®CI/CDパイプラインã¯éžå¸¸ã«è¤‡é›‘ã§ã™ã€‚ユーザーã®ã»ã¨ã‚“ã©ã¯ã‚ˆã‚Šå°‘ãªãã€ã‚ˆã‚Šå˜ç´”ãªãƒ‘イプラインを使ã£ã¦ã„ã¾ã™ã€‚"
-
-msgid "UserOnboardingTour|Here you can see the current members of the project (just you at the moment) and invite new members.%{lineBreak}%{lineBreak}You can invite multiple members at once (existing GitLab users or invite by email) and you can also set their roles and permissions.%{lineBreak}%{lineBreak}Add a few members and click on %{emphasisStart}Add to project%{emphasisEnd} to complete this step."
-msgstr "ã“ã“ã§ã¯ã€ãƒ—ロジェクトã®ç¾åœ¨ã®ãƒ¡ãƒ³ãƒãƒ¼ã‚’確èªã—ã¦(ç¾æ™‚点ã§ã¯ã‚ãªãŸã ã‘)ã€æ–°ã—ã„メンãƒãƒ¼ã‚’招待ã§ãã¾ã™ã€‚%{lineBreak}%{lineBreak}一度ã«è¤‡æ•°ã®ãƒ¡ãƒ³ãƒãƒ¼ã‚’招待ã§ãã¾ã™(既存㮠GitLab ユーザーã®æ‹›å¾…ã¾ãŸã¯ãƒ¡ãƒ¼ãƒ«ã§ã®æ‹›å¾…)ã—ã€ã¾ãŸãƒ¡ãƒ³ãƒãƒ¼ã®å½¹å‰²ã¨æ¨©é™ã‚’設定ã™ã‚‹ã“ã¨ã‚‚ã§ãã¾ã™ã€‚%{lineBreak}%{lineBreak}数人ã®ãƒ¡ãƒ³ãƒãƒ¼ã‚’追加ã—〠%{emphasisStart}プロジェクトã«è¿½åŠ %{emphasisEnd} をクリックã—ã¦ã“ã®ã‚¹ãƒ†ãƒƒãƒ—を完了ã—ã¾ã™ã€‚"
-
-msgid "UserOnboardingTour|Here you can see what changes were made with this commit, on what branch and if there's a related merge request. The status of the pipeline will also show up if CI/CD is set up.%{lineBreak}%{lineBreak}You can also comment on the lines of code that were changed and start a discussion with your colleagues!"
-msgstr "ã“ã“ã§ã¯ã€ã“ã®ã‚³ãƒŸãƒƒãƒˆã§ã©ã®ã‚ˆã†ãªå¤‰æ›´ãŒè¡Œã‚ã‚ŒãŸã®ã‹ã€ã©ã®ãƒ–ランãƒã§ã€ãã—ã¦é–¢é€£ã™ã‚‹ãƒžãƒ¼ã‚¸ãƒªã‚¯ã‚¨ã‚¹ãƒˆãŒã‚ã‚‹ã®ã‹ã‚’確èªã§ãã¾ã™ã€‚CI/CDãŒè¨­å®šã•ã‚Œã¦ã„ã‚‹å ´åˆã¯ã€ãƒ‘イプラインã®ã‚¹ãƒ†ãƒ¼ã‚¿ã‚¹ã‚‚表示ã•ã‚Œã¾ã™ã€‚%{lineBreak}%{lineBreak}変更ã•ã‚ŒãŸã‚³ãƒ¼ãƒ‰ã«ã‚³ãƒ¡ãƒ³ãƒˆã—ã¦ã€åŒåƒšã¨ãƒ‡ã‚£ã‚¹ã‚«ãƒƒã‚·ãƒ§ãƒ³ã‚’始ã‚ã‚‹ã“ã¨ã‚‚ã§ãã¾ã™ã€‚"
-
-msgid "UserOnboardingTour|Here's an overview of branches in the %{emphasisStart}%{projectName}%{emphasisEnd} project. They're split into Active and Stale.%{lineBreak}%{lineBreak}From here, you can create a new merge request from a branch, or compare the branch to any other branch in the project. By default, it will compare it to the master branch."
-msgstr "ã“れ㯠%{emphasisStart}%{projectName}%{emphasisEnd} プロジェクトã®ãƒ–ランãƒã®æ¦‚è¦ã§ã™ã€‚アクティブブランãƒã¨å¤ã„ブランãƒã«åˆ†ã‘られã¾ã™ã€‚%{lineBreak}%{lineBreak}ã“ã“ã‹ã‚‰ã€ãƒ–ランãƒã‹ã‚‰æ–°ã—ã„マージリクエストを作æˆã§ãã¾ã™ã€‚ã¾ãŸã€ãã®ãƒ–ランãƒã‚’プロジェクト内ã®ä»–ã®ãƒ–ランãƒã¨æ¯”較ã§ãã¾ã™ã€‚デフォルトã§ã¯ã€ãれをマスターブランãƒã¨æ¯”較ã—ã¾ã™ã€‚"
-
-msgid "UserOnboardingTour|Invite colleagues"
-msgstr "招待ã™ã‚‹"
-
-msgid "UserOnboardingTour|Issues are great for communicating and keeping track of progress in GitLab. These are all issues that are open in the %{emphasisStart}%{projectName}%{emphasisEnd}.%{lineBreak}%{lineBreak}You can help us improve GitLab by contributing work to issues that are labeled <span class=\"badge color-label accept-mr-label\">Accepting merge requests</span>.%{lineBreak}%{lineBreak}This list can be filtered by labels, milestones, assignees, authors... We'll show you how it looks when the list is filtered by a label."
-msgstr "課題ã¯ã€GitLab 上ã§ã‚³ãƒŸãƒ¥ãƒ‹ã‚±ãƒ¼ã‚·ãƒ§ãƒ³ã‚’å–ã‚Šã€é€²è¡ŒçŠ¶æ³ã‚’追跡ã™ã‚‹ã®ã«æœ€é©ãªæ©Ÿèƒ½ã§ã™ã€‚ã“れらã¯ã™ã¹ã¦ %{emphasisStart}%{projectName}%{emphasisEnd} ã®æœªè§£æ±ºã®èª²é¡Œã§ã™ã€‚%{lineBreak}%{lineBreak} <span class=\"badge color-label accept-mr-label\">マージリクエストã®æ‰¿èª</span> ã¨ã„ã†ãƒ©ãƒ™ãƒ«ã®ä»˜ã„ãŸèª²é¡Œã«ä½œæ¥­ã‚’貢献ã™ã‚‹ã“ã¨ã§ã€GitLab 本体ã®æ”¹å–„ã«ã”å”力ã„ãŸã ã‘ã¾ã™ã€‚%{lineBreak}%{lineBreak}ã“ã®ãƒªã‚¹ãƒˆã¯ãƒ©ãƒ™ãƒ«ã€ãƒžã‚¤ãƒ«ã‚¹ãƒˆãƒ¼ãƒ³ã€æ‹…当者ã€ä½œæˆè€…ãªã©ã§çµžã‚Šè¾¼ã‚€ã“ã¨ãŒã§ãã¾ã™ã€‚リストãŒãƒ©ãƒ™ãƒ«ã§çµžã‚Šè¾¼ã¾ã‚ŒãŸã¨ãã®æ§˜å­ã‚’紹介ã—ã¾ã™ã€‚"
-
-msgid "UserOnboardingTour|Learn GitLab"
-msgstr "GitLabã«ã¤ã„ã¦å­¦ã¶"
-
-msgid "UserOnboardingTour|Let's take a closer look at a merge request. Click on the title of one."
-msgstr "マージリクエストを詳ã—ã見ã¦ã¿ã¾ã—ょã†ã€‚マージリクエストã®ã‚¿ã‚¤ãƒˆãƒ«ã‚’クリックã—ã¦ãã ã•ã„。"
-
-msgid "UserOnboardingTour|Let's take a closer look at all the commits. Click on %{emphasisStart}Commits%{emphasisEnd}."
-msgstr "ã™ã¹ã¦ã®ã‚³ãƒŸãƒƒãƒˆã‚’詳ã—ã見ã¦ã¿ã¾ã—ょã†ã€‚ %{emphasisStart}コミット%{emphasisEnd}をクリックã—ã¦ãã ã•ã„。"
-
-msgid "UserOnboardingTour|Let's take a closer look at the repository of this project. Click on %{emphasisStart}Repository%{emphasisEnd}."
-msgstr "ã“ã®ãƒ—ロジェクトã®ãƒªãƒã‚¸ãƒˆãƒªã‚’詳ã—ã見ã¦ã¿ã¾ã—ょã†ã€‚ %{emphasisStart}リãƒã‚¸ãƒˆãƒª%{emphasisEnd}をクリックã—ã¦ãã ã•ã„。"
-
-msgid "UserOnboardingTour|No thanks"
-msgstr "ã„ã„ãˆã€ä¸è¦ã§ã™"
-
-msgid "UserOnboardingTour|Ok, let's go"
-msgstr "OKã€ã‚„ã‚Šã¾ã—ょã†"
-
-msgid "UserOnboardingTour|Ok, show me"
-msgstr "ã¯ã„ã€è¦‹ã›ã¦ãã ã•ã„"
-
-msgid "UserOnboardingTour|Open one of the issues by clicking on its title."
-msgstr "タイトルをクリックã—ã¦èª²é¡Œã‚’é–‹ãã¾ã™ã€‚"
-
-msgid "UserOnboardingTour|Restart this step"
-msgstr "ã“ã®ã‚¹ãƒ†ãƒƒãƒ—ã‚’å†é–‹"
-
-msgid "UserOnboardingTour|Skip this step"
-msgstr "ã“ã®ã‚¹ãƒ†ãƒƒãƒ—をスキップ"
-
-msgid "UserOnboardingTour|Sweet! Your project was created and is ready to be used.%{lineBreak}%{lineBreak}You can start adding files to the repository or clone it. One last thing we want to show you is how to invite your colleagues to your new project."
-msgstr "ã„ã„ã§ã™ã­ï¼ãƒ—ロジェクトãŒä½œæˆã•ã‚Œã€åˆ©ç”¨ã§ãるよã†ã«ãªã‚Šã¾ã—ãŸã€‚%{lineBreak}%{lineBreak}リãƒã‚¸ãƒˆãƒªã«ãƒ•ã‚¡ã‚¤ãƒ«ã‚’追加ã§ãã¾ã™ã€‚ã¾ãŸã€ã‚¯ãƒ­ãƒ¼ãƒ³ã§ãã¾ã™ã€‚最後ã«ãŠè¦‹ã›ã—ãŸã„ã®ã¯ã€åŒåƒšã‚’æ–°ã—ã„プロジェクトã«æ‹›å¾…ã™ã‚‹æ–¹æ³•ã§ã™ã€‚"
-
-msgid "UserOnboardingTour|Take a look. Here's a nifty menu for quickly creating issues, merge requests, snippets, projects and groups. Click on it and select \"New project\" from the \"GitLab\" section to get started."
-msgstr "ã“れを見ã¦ãã ã•ã„。課題ã€ãƒžãƒ¼ã‚¸ãƒªã‚¯ã‚¨ã‚¹ãƒˆã€ã‚¹ãƒ‹ãƒšãƒƒãƒˆã€ãƒ—ロジェクトã€ãŠã‚ˆã³ã‚°ãƒ«ãƒ¼ãƒ—ã‚’ã™ã°ã‚„ã作æˆã™ã‚‹ãŸã‚ã®ä¾¿åˆ©ãªãƒ¡ãƒ‹ãƒ¥ãƒ¼ãŒã‚ã‚Šã¾ã™ã€‚ãれをクリックã—ã¦ã€ã€ŒGitLabã€ã‚»ã‚¯ã‚·ãƒ§ãƒ³ã‹ã‚‰ã€ŒNew projectã€ã‚’é¸æŠžã—ã¦é–‹å§‹ã—ã¾ã™ã€‚"
-
-msgid "UserOnboardingTour|Thanks for taking the guided tour. Remember, if you want to go through it again, you can start %{emphasisStart}Learn GitLab%{emphasisEnd} in the help menu on the top right."
-msgstr "ガイド付ãツアーをã”利用ã„ãŸã ãã‚ã‚ŠãŒã¨ã†ã”ã–ã„ã¾ã™ã€‚ã‚ãªãŸãŒå†ã³ãƒ„アーã«å‚加ã—ãŸã„ãªã‚‰ã€å³ä¸Šã®ãƒ˜ãƒ«ãƒ—メニュー㮠%{emphasisStart} GitLab を学㶠%{emphasisEnd} ã‹ã‚‰å§‹ã‚られã¾ã™ã€‚"
-
-msgid "UserOnboardingTour|Thanks for the feedback! %{thumbsUp}"
-msgstr "フィードãƒãƒƒã‚¯ã‚’ã‚ã‚ŠãŒã¨ã†ã”ã–ã„ã¾ã™ï¼ %{thumbsUp}"
-
-msgid "UserOnboardingTour|That's it for issues. Let'st take a look at %{emphasisStart}Merge Requests%{emphasisEnd}."
-msgstr "ã“ã‚ŒãŒèª²é¡Œã§ã™ã€‚%{emphasisStart} マージリクエスト %{emphasisEnd}を見ã¦ã¿ã¾ã—ょã†ã€‚"
-
-msgid "UserOnboardingTour|That's it for merge requests. Now for the final part of this guided tour - the %{emphasisStart}CI/CD%{emphasisEnd}."
-msgstr "ã“ã‚ŒãŒãƒžãƒ¼ã‚¸ãƒªã‚¯ã‚¨ã‚¹ãƒˆã§ã™ã€‚ãã—ã¦ã“ã‚ŒãŒã‚¬ã‚¤ãƒ‰ä»˜ãツアーã®æœ€å¾Œã®éƒ¨åˆ†ã§ã™ã€‚ %{emphasisStart}CI/CD%{emphasisEnd}"
-
-msgid "UserOnboardingTour|That's it for the Repository. Let's take a look at the %{emphasisStart}Issues%{emphasisEnd}."
-msgstr "ã“ã‚ŒãŒãƒªãƒã‚¸ãƒˆãƒªã§ã™ã€‚%{emphasisStart} 課題 %{emphasisEnd} を見ã¦ã¿ã¾ã—ょã†ã€‚"
-
-msgid "UserOnboardingTour|The structure of this page is very similar to issues. Status, description, discussion and the sidebar are all here.%{lineBreak}%{lineBreak}But take a look below the description and you'll notice that there's more information about the merge request, the CI/CD pipeline and the options for approving it.%{lineBreak}%{lineBreak}Alongside the discussion you can also see more information about commits in this merge request, the status of pipelines and review all changes that were made."
-msgstr "ã“ã®ãƒšãƒ¼ã‚¸ã®æ§‹é€ ã¯ã€èª²é¡Œã¨éžå¸¸ã«ã‚ˆãä¼¼ã¦ã„ã¾ã™ã€‚ステータスã€èª¬æ˜Žã€ãƒ‡ã‚£ã‚¹ã‚«ãƒƒã‚·ãƒ§ãƒ³ã€ã‚µã‚¤ãƒ‰ãƒãƒ¼ãŒã™ã¹ã¦ã‚ã‚Šã¾ã™ã€‚%{lineBreak}%{lineBreak}ã—ã‹ã—ã€ä¸‹ã®èª¬æ˜Žã‚’読むã¨ã€ãƒžãƒ¼ã‚¸ãƒªã‚¯ã‚¨ã‚¹ãƒˆã€CI/CD パイプラインã€ãŠã‚ˆã³ãã‚Œã®æ‰¿èªã‚ªãƒ—ションã«ã¤ã„ã¦ã®è©³ã—ã„情報ãŒã‚ã‚‹ã“ã¨ãŒã‚ã‹ã‚Šã¾ã™ã€‚%{lineBreak}%{lineBreak}以下ã§ã¯ã€ãƒ‡ã‚£ã‚¹ã‚«ãƒƒã‚·ãƒ§ãƒ³ã¨ä¸¦è¡Œã—ã¦ã€ã“ã®ãƒžãƒ¼ã‚¸ãƒªã‚¯ã‚¨ã‚¹ãƒˆã®ã‚³ãƒŸãƒƒãƒˆã€ãƒ‘イプラインã®ã‚¹ãƒ†ãƒ¼ã‚¿ã‚¹ã€ãŠã‚ˆã³è¡Œã‚ã‚ŒãŸã™ã¹ã¦ã®å¤‰æ›´ã®ãƒ¬ãƒ“ューã«ã¤ã„ã¦ã‚‚確èªã§ãã¾ã™ã€‚"
-
-msgid "UserOnboardingTour|There's a lot of information here but don't worry, we'll go through it.%{lineBreak}%{lineBreak}On the top you can see the status of the issue and when it was opened and by whom. Directly below it is the issue description and below that are other %{emphasisStart}related issues%{emphasisEnd} and %{emphasisStart}merge requests%{emphasisEnd} (if any). Then below that is the %{emphasisStart}discussion%{emphasisEnd}, that's where most of the communication happens.%{lineBreak}%{lineBreak}On the right, there's a sidebar where you can view/change the %{emphasisStart}assignee, milestone, due date, labels, weight%{emphasisEnd}, etc."
-msgstr "ã“ã“ã«ã¯ãŸãã•ã‚“ã®æƒ…å ±ãŒã‚ã‚Šã¾ã™ãŒã€å¿ƒé…ã—ãªã„ã§ãã ã•ã„。%{lineBreak}%{lineBreak}一番上ã«ã¯ã€èª²é¡Œã®çŠ¶æ…‹ã‚„ã„ã¤èª°ãŒã‚ªãƒ¼ãƒ—ンã—ãŸã®ã‹ãŒç¢ºèªã§ãã¾ã™ã€‚ãã®ã™ã下ã«ã¯èª²é¡Œã®èª¬æ˜ŽãŒã‚ã‚Šã€ãã®ä¸‹ã«ã¯ä»–ã® %{emphasisStart}関連ã™ã‚‹èª²é¡Œ%{emphasisEnd} 㨠%{emphasisStart}マージリクエスト%{emphasisEnd} (ã‚ã‚‹å ´åˆ)ãŒã‚ã‚Šã¾ã™ã€‚ãã‚Œã‹ã‚‰ãã®ä¸‹ã« %{emphasisStart}ディスカッション%{emphasisEnd}ãŒã‚ã‚Šã€ã“ã‚Œã¯ã‚³ãƒŸãƒ¥ãƒ‹ã‚±ãƒ¼ã‚·ãƒ§ãƒ³ã®å¤§éƒ¨åˆ†ãŒèµ·ã“ã‚‹ã¨ã“ã‚ã§ã™ã€‚%{lineBreak}%{lineBreak}å³å´ã«ã¯ã€ %{emphasisStart}ã€æ‹…当者ã€ãƒžã‚¤ãƒ«ã‚¹ãƒˆãƒ¼ãƒ³ã€æœŸæ—¥ã€ãƒ©ãƒ™ãƒ«ã€ã‚¦ã‚§ã‚¤ãƒˆ%{emphasisEnd}ãªã©ã‚’表示/変更ã™ã‚‹ã‚µã‚¤ãƒ‰ãƒãƒ¼ãŒã‚ã‚Šã¾ã™ã€‚"
-
-msgid "UserOnboardingTour|These are all the CI/CD pipelines we have for our %{emphasisStart}%{projectName}%{emphasisEnd} project.%{lineBreak}%{lineBreak}Here you can see the status of each pipeline, for what commit it's running for, its stages and the status for them."
-msgstr "ã“ã‚Œã¯ã€ %{emphasisStart}%{projectName}%{emphasisEnd} プロジェクト用ã®ã™ã¹ã¦ã®CI/CDパイプラインã§ã™ã€‚%{lineBreak}%{lineBreak}ã“ã“ã§ã¯ã€å„パイプラインã®ã‚¹ãƒ†ãƒ¼ã‚¿ã‚¹ã€å®Ÿè¡Œä¸­ã®ã‚³ãƒŸãƒƒãƒˆã€ã‚¹ãƒ†ãƒ¼ã‚¸ã€ãŠã‚ˆã³ãれらã®ã‚¹ãƒ†ãƒ¼ã‚¿ã‚¹ã‚’確èªã§ãã¾ã™ã€‚"
-
-msgid "UserOnboardingTour|These are all the issues that are available for community contributions. Let's take a closer look at one of them."
-msgstr "コミュニティã®è²¢çŒ®ã«åˆ©ç”¨ã§ãる課題ã§ã™ã€‚ãã®ã†ã¡ã®1ã¤ã‚’見ã¦ã¿ã¾ã—ょã†ã€‚"
-
-msgid "UserOnboardingTour|This is an overview of all merge requests in this project. Similarly to the issues overview it can be filtered down by things like labels, milestones, authors, assignees, etc."
-msgstr "ã“ã‚Œã¯ã€ã“ã®ãƒ—ロジェクトã®ã™ã¹ã¦ã®ãƒžãƒ¼ã‚¸ãƒªã‚¯ã‚¨ã‚¹ãƒˆã®ä¸€è¦§ã§ã™ã€‚課題ã®ä¸€è¦§ã¨åŒã˜æ§˜ã«ã€ãƒ©ãƒ™ãƒ«ã€ãƒžã‚¤ãƒ«ã‚¹ãƒˆãƒ¼ãƒ³ã€ä½œæˆè€…ã€æ‹…当者ãªã©ã§çµžã‚Šè¾¼ã‚€ã“ã¨ãŒã§ãã¾ã™ã€‚"
-
-msgid "UserOnboardingTour|This is the repository for the %{emphasisStart}%{projectName}%{emphasisEnd} project. All our code is stored here. Feel free to explore and take a closer look at folders and files.%{lineBreak}%{lineBreak}Above the file structure you can see the latest commit, who the author is and the status of the CI/CD pipeline.%{lineBreak}%{lineBreak}If you scroll down below the file structure, you'll find the Readme of this project. This is defined in the README.md file at the root of the repository."
-msgstr "ã“れ㯠%{emphasisStart}%{projectName}%{emphasisEnd} プロジェクトã®ãƒªãƒã‚¸ãƒˆãƒªã§ã™ã€‚ã™ã¹ã¦ã®ã‚³ãƒ¼ãƒ‰ã¯ã“ã“ã«æ ¼ç´ã•ã‚Œã¦ã„ã¾ã™ã€‚気軽ã«æŽ¢ç´¢ã—ã¦ã€ãƒ•ã‚©ãƒ«ãƒ€ãƒ¼ã¨ãƒ•ã‚¡ã‚¤ãƒ«ã‚’確èªã§ãã¾ã™ã€‚%{lineBreak}%{lineBreak}ファイル構造ã®ä¸Šã«ã€æœ€æ–°ã®ã‚³ãƒŸãƒƒãƒˆã€ä½œæˆè€…ãŒèª°ã§ã‚ã‚‹ã‹ã€ãŠã‚ˆã³CI/CD パイプラインã®ã‚¹ãƒ†ãƒ¼ã‚¿ã‚¹ã‚’確èªã§ãã¾ã™ã€‚%{lineBreak}%{lineBreak}ファイル構造ã®ã‚ˆã‚Šä¸‹ã«ã‚¹ã‚¯ãƒ­ãƒ¼ãƒ«ã™ã‚‹ã¨ã€ã“ã®ãƒ—ロジェクトã®Readmeを確èªã§ãã¾ã™ã€‚ã“ã‚Œã¯ãƒªãƒã‚¸ãƒˆãƒªã®ãƒ«ãƒ¼ãƒˆã«ã‚ã‚‹README.md ファイルã§ã™ã€‚"
+msgid "UserList|Delete %{name}?"
+msgstr ""
-msgid "UserOnboardingTour|Welcome to the project overview of the %{emphasisStart}%{projectName}%{emphasisEnd} project. This is the project that we use to work on GitLab. At first, a project seems like a simple repository, but at GitLab, a project is so much more.%{lineBreak}%{lineBreak}You can create projects for hosting your codebase, use it as an issue tracker, collaborate on code, and continuously build, test, and deploy your app with built-in GitLab CI/CD."
-msgstr "%{emphasisStart}%{projectName}%{emphasisEnd} プロジェクトã®ãƒ—ロジェクト概è¦ã¸ã‚ˆã†ã“ã。ã“ã‚Œã¯ç§ãŸã¡ãŒGitLabã«å–り組むãŸã‚ã®ãƒ—ロジェクトã§ã™ã€‚最åˆã«ã€ãƒ—ロジェクトã¯å˜ç´”ãªãƒªãƒã‚¸ãƒˆãƒªã®ã‚ˆã†ã«è¦‹ãˆã¾ã™ãŒã€GitLab ã«ã¨ã£ã¦ãƒ—ロジェクトã¯ãã‚Œã ã‘ã§ã¯ã‚ã‚Šã¾ã›ã‚“。%{lineBreak}%{lineBreak}コードベースをホスティングã™ã‚‹ãŸã‚ã®ãƒ—ロジェクトを作æˆã—ã€ãれを課題トラッカーã¨ã—ã¦ä½¿ç”¨ã—ã€ã‚³ãƒ¼ãƒ‰ã‚’コラボレートã—ã€ã•ã‚‰ã«GitLab CI/CD を組ã¿è¾¼ã‚“ã§ã‚¢ãƒ—リを継続的ã«æ§‹ç¯‰ã€ãƒ†ã‚¹ãƒˆã€ãŠã‚ˆã³å±•é–‹ã§ãã¾ã™ã€‚"
+msgid "UserList|created %{timeago}"
+msgstr ""
msgid "UserProfile|Activity"
msgstr "アクティビティ"
@@ -24346,6 +24986,9 @@ msgstr "è­²å—人ãªã— - %{openingTag} 自分自身を割り当ã¦ã‚‹ %{closing
msgid "UsersSelect|Unassigned"
msgstr "未割り当ã¦"
+msgid "Using %{codeStart}needs%{codeEnd} allows jobs to run before their stage is reached, as soon as their individual dependencies are met, which speeds up your pipelines."
+msgstr ""
+
msgid "Using %{code_start}::%{code_end} denotes a %{link_start}scoped label set%{link_end}"
msgstr "%{code_start}::%{code_end} を使ã£ã¦ã€ %{link_start} ラベルセットã®ç¯„囲%{link_end} を示ã™"
@@ -24430,9 +25073,6 @@ msgstr "ãƒãƒ¼ã‚¸ãƒ§ãƒ³"
msgid "Versions"
msgstr ""
-msgid "Very helpful"
-msgstr "ã¨ã¦ã‚‚å½¹ã«ç«‹ã¡ã¾ã™"
-
msgid "View Documentation"
msgstr "文書ã®è¡¨ç¤º"
@@ -24493,7 +25133,7 @@ msgid "View issue"
msgstr "課題を表示"
msgid "View issues"
-msgstr ""
+msgstr "課題を表示"
msgid "View it on GitLab"
msgstr "GitLab ã§è¡¨ç¤º"
@@ -24579,6 +25219,9 @@ msgstr "パブリック"
msgid "VisibilityLevel|Unknown"
msgstr "ä¸æ˜Ž"
+msgid "Visit settings page"
+msgstr ""
+
msgid "VisualReviewApp|%{stepStart}Step 1%{stepEnd}. Copy the following script:"
msgstr "%{stepStart}ステップ1%{stepEnd} 次ã®ã‚¹ã‚¯ãƒªãƒ—トをコピーã—ã¾ã™:"
@@ -24672,6 +25315,9 @@ msgstr ""
msgid "VulnerabilityManagement|Something went wrong while trying to delete the comment. Please try again later."
msgstr ""
+msgid "VulnerabilityManagement|Something went wrong while trying to refresh the vulnerability. Please try again later."
+msgstr ""
+
msgid "VulnerabilityManagement|Something went wrong while trying to retrieve the vulnerability history. Please try again later."
msgstr ""
@@ -24714,6 +25360,9 @@ msgstr "%{scannerName} (ãƒãƒ¼ã‚¸ãƒ§ãƒ³ %{scannerVersion})"
msgid "Vulnerability|Class"
msgstr "クラス"
+msgid "Vulnerability|Crash Address"
+msgstr ""
+
msgid "Vulnerability|Description"
msgstr "説明"
@@ -24789,9 +25438,6 @@ msgstr "課題を削除ã™ã‚‹ãŸã‚ã®ãƒ‘スを特定ã§ãã¾ã›ã‚“ã§ã—ãŸ"
msgid "We couldn't reach the Prometheus server. Either the server no longer exists or the configuration details need updating."
msgstr "Prometheusサーãƒãƒ¼ã«åˆ°é”ã§ãã¾ã›ã‚“ã§ã—ãŸã€‚サーãƒãƒ¼ãŒå­˜åœ¨ã—ãªã„ã‹ã€è¨­å®šã®è©³ç´°ã‚’æ›´æ–°ã™ã‚‹å¿…è¦ãŒã‚ã‚Šã¾ã™ã€‚"
-msgid "We created a short guided tour that will help you learn the basics of GitLab and how it will help you be better at your job. It should only take a couple of minutes. You will be guided by two types of helpers, best recognized by their color."
-msgstr "GitLabã®åŸºæœ¬ã‚’å­¦ã¶ã®ã«å½¹ç«‹ã¤çŸ­ã„ツアーを作æˆã—ã¾ã—ãŸã€‚ã“ã‚ŒãŒã©ã®ã‚ˆã†ã«ã‚ãªãŸã®ä»•äº‹ã‚’改善ã§ãã‚‹ã‹ã‚’説明ã—ã¾ã™ã€‚ã“ã®ãƒ„アーã¯æ•°åˆ†ã—ã‹ã‹ã‹ã‚Šã¾ã›ã‚“。ã‚ãªãŸ2種類ã®ãƒ˜ãƒ«ãƒ‘ーã«ã‚ˆã£ã¦å°Žã‹ã‚Œã‚‹ã§ã—ょã†ã€‚ヘルパーã¯è‰²ã§å‘¼ã°ã‚Œã¾ã™ã€‚"
-
msgid "We detected potential spam in the %{humanized_resource_name}. Please solve the reCAPTCHA to proceed."
msgstr "%{humanized_resource_name} ã«ã‚¹ãƒ‘ムãŒã‚ã‚‹å¯èƒ½æ€§ã‚’検出ã—ã¾ã—ãŸã€‚続行ã™ã‚‹ã«ã¯ reCAPTCHA を実行ã—ã¦ãã ã•ã„。"
@@ -24825,6 +25471,9 @@ msgstr ""
msgid "We've found no vulnerabilities"
msgstr "脆弱性ã¯ç™ºè¦‹ã•ã‚Œã¾ã›ã‚“ã§ã—ãŸ"
+msgid "Web Application Firewall"
+msgstr ""
+
msgid "Web IDE"
msgstr "Web IDE"
@@ -24885,9 +25534,6 @@ msgstr ""
msgid "Welcome to GitLab.com<br>@%{name}!"
msgstr ""
-msgid "Welcome to the Guided GitLab Tour"
-msgstr "GitLab ツアーã¸ã‚ˆã†ã“ã"
-
msgid "Welcome to the guided GitLab tour"
msgstr ""
@@ -24934,9 +25580,6 @@ msgstr ""
msgid "While it's rare to have no vulnerabilities, it can happen. In any event, we ask that you please double check your settings to make sure you've set up your dashboard correctly."
msgstr "脆弱性ãŒãªã„事ã¯ç¨€ã§ã™ãŒã€èµ·ã“ã‚Šå¾—ã¾ã™ã€‚ã„ãšã‚Œã«ã—ã¦ã‚‚ã€ãƒ€ãƒƒã‚·ãƒ¥ãƒœãƒ¼ãƒ‰ãŒæ­£ã—ã設定ã•ã‚Œã¦ã„ã‚‹ã“ã¨ã‚’確èªã™ã‚‹ãŸã‚ã«ã€è¨­å®šã‚’å†ç¢ºèªã—ã¦ãã ã•ã„。"
-msgid "White helpers give contextual information."
-msgstr "ホワイトヘルパーã¯æ–‡è„ˆæƒ…報を与ãˆã¾ã™ã€‚"
-
msgid "Who can be an approver?"
msgstr "誰ãŒæ‰¿èªè€…ã«ãªã‚‹ã“ã¨ãŒã§ãã¾ã™ã‹ï¼Ÿ"
@@ -24982,12 +25625,18 @@ msgstr "åŒã˜ãƒ‘スã«åŒã˜ã‚¿ã‚¤ãƒˆãƒ«ã®ãƒšãƒ¼ã‚¸ãŒæ—¢ã«ã‚ã‚Šã¾ã™ã€‚"
msgid "WikiEmptyIssueMessage|Suggest wiki improvement"
msgstr "Wiki ã®æ”¹å–„ã‚’æ案ã™ã‚‹"
+msgid "WikiEmptyIssueMessage|You must be a group member in order to add wiki pages. If you have suggestions for how to improve the wiki for this group, consider opening an issue in the %{issues_link}."
+msgstr ""
+
msgid "WikiEmptyIssueMessage|You must be a project member in order to add wiki pages. If you have suggestions for how to improve the wiki for this project, consider opening an issue in the %{issues_link}."
msgstr "Wiki ページを追加ã™ã‚‹ã«ã¯ã€ãƒ—ロジェクトメンãƒãƒ¼ã§ãªã‘ã‚Œã°ãªã‚Šã¾ã›ã‚“。ã“ã®ãƒ—ロジェクト㮠Wiki を改善ã™ã‚‹æ–¹æ³•ã®æ案ãŒã‚ã‚‹å ´åˆã¯ã€ %{issues_link} ã§å•é¡Œã‚’é–‹ãã“ã¨ã‚’検討ã—ã¦ãã ã•ã„。"
msgid "WikiEmptyIssueMessage|issue tracker"
msgstr "課題トラッカー"
+msgid "WikiEmpty|A wiki is where you can store all the details about your group. This can include why you've created it, its principles, how to use it, and so on."
+msgstr ""
+
msgid "WikiEmpty|A wiki is where you can store all the details about your project. This can include why you've created it, its principles, how to use it, and so on."
msgstr "Wiki ã¯ãƒ—ロジェクトã«é–¢ã™ã‚‹ã™ã¹ã¦ã®è©³ç´°ã‚’ä¿å­˜ã™ã‚‹å ´æ‰€ã§ã™ã€‚ã“ã‚Œã«ã¯ã€ãƒ—ロジェクトを作æˆã—ãŸç†ç”±ã€ãƒ—ロジェクトã®åŽŸå‰‡ã€ãƒ—ロジェクトã®ä½¿ç”¨æ–¹æ³•ãªã©ãŒå«ã¾ã‚Œã¾ã™ã€‚"
@@ -24997,12 +25646,21 @@ msgstr "最åˆã®ãƒšãƒ¼ã‚¸ã‚’作æˆ"
msgid "WikiEmpty|Suggest wiki improvement"
msgstr "Wiki ã®æ”¹å–„ã‚’æ案ã™ã‚‹"
+msgid "WikiEmpty|The wiki lets you write documentation for your group"
+msgstr ""
+
msgid "WikiEmpty|The wiki lets you write documentation for your project"
msgstr "Wiki ã§ã¯ã€ãƒ—ロジェクトã®ãƒ‰ã‚­ãƒ¥ãƒ¡ãƒ³ãƒˆã‚’書ãã“ã¨ãŒã§ãã¾ã™"
+msgid "WikiEmpty|This group has no wiki pages"
+msgstr ""
+
msgid "WikiEmpty|This project has no wiki pages"
msgstr "ã“ã®ãƒ—ロジェクト㫠Wiki ページã¯ã‚ã‚Šã¾ã›ã‚“"
+msgid "WikiEmpty|You must be a group member in order to add wiki pages."
+msgstr ""
+
msgid "WikiEmpty|You must be a project member in order to add wiki pages."
msgstr "Wiki ページを追加ã™ã‚‹ã«ã¯ã€ãƒ—ロジェクトメンãƒãƒ¼ã§ã‚ã‚‹å¿…è¦ãŒã‚ã‚Šã¾ã™ã€‚"
@@ -25279,15 +25937,15 @@ msgstr ""
msgid "You can now export your security dashboard to a CSV report."
msgstr ""
+msgid "You can now manage alert endpoint configuration in the Alerts section on the Operations settings page. Fields on this page have been deprecated."
+msgstr ""
+
msgid "You can now submit a merge request to get this change into the original branch."
msgstr "ã“ã®å¤‰æ›´ã‚’å…ƒã®ãƒ–ランãƒã«å映ã•ã›ã‚‹ãŸã‚ã«ãƒžãƒ¼ã‚¸ãƒªã‚¯ã‚¨ã‚¹ãƒˆã‚’é€ä¿¡ã§ãるよã†ã«ãªã‚Šã¾ã—ãŸã€‚"
msgid "You can now submit a merge request to get this change into the original project."
msgstr "ã“ã®å¤‰æ›´ã‚’å…ƒã®ãƒ—ロジェクトã«å映ã•ã›ã‚‹ãŸã‚ã«ãƒžãƒ¼ã‚¸ãƒªã‚¯ã‚¨ã‚¹ãƒˆã‚’é€ä¿¡ã§ãるよã†ã«ãªã‚Šã¾ã—ãŸã€‚"
-msgid "You can only add files when you are on a branch"
-msgstr "ファイルを追加ã™ã‚‹ã«ã¯ã€ã©ã“ã‹ã®ãƒ–ランãƒã«ã„ãªã‘ã‚Œã°ã„ã‘ã¾ã›ã‚“"
-
msgid "You can only edit files when you are on a branch"
msgstr "ファイルを編集ã™ã‚‹ã«ã¯ã€ã©ã“ã‹ã®ãƒ–ランãƒã«ã„ãªã‘ã‚Œã°ã„ã‘ã¾ã›ã‚“"
@@ -25507,7 +26165,7 @@ msgstr "GitLab プロジェクトã®ã‚¨ã‚¯ã‚¹ãƒãƒ¼ãƒˆã‚¢ãƒ¼ã‚«ã‚¤ãƒ–(.gz ã§ç
msgid "You need to upload a Google Takeout archive."
msgstr "Google テイクアウトアーカイブをアップロードã™ã‚‹å¿…è¦ãŒã‚ã‚Šã¾ã™ã€‚"
-msgid "You reached %{usage_in_percent} of %{namespace_name}'s capacity (%{used_storage} of %{storage_limit})"
+msgid "You reached %{usage_in_percent} of %{namespace_name}'s storage capacity (%{used_storage} of %{storage_limit})"
msgstr ""
msgid "You tried to fork %{link_to_the_project} but it failed for the following reason:"
@@ -25810,13 +26468,13 @@ msgstr "ã©ã®ã‚³ãƒŸãƒƒãƒˆã«ã‚‚一致ã—ã¾ã›ã‚“ã§ã—ãŸã€‚"
msgid "Your subscription expired!"
msgstr "サブスクリプションã®æœ‰åŠ¹æœŸé™ãŒåˆ‡ã‚Œã¾ã—ãŸï¼"
-msgid "Your subscription has been downgraded"
-msgstr "サブスクリプションã®ãƒ€ã‚¦ãƒ³ãƒ­ãƒ¼ãƒ‰ã‚’完了ã—ã¾ã—ãŸã€‚"
+msgid "Your subscription has been downgraded."
+msgstr ""
-msgid "Your subscription will automatically renew in %{remaining_days}"
-msgstr "ã‚ãªãŸã®ã‚µãƒ–スクリプションã¯å¾Œ%{remaining_days}ã§è‡ªå‹•çš„ã«æ›´æ–°ã•ã‚Œã¾ã™ã€‚"
+msgid "Your subscription will automatically renew in %{remaining_days}."
+msgstr ""
-msgid "Your subscription will expire in %{remaining_days}"
+msgid "Your subscription will expire in %{remaining_days}."
msgstr ""
msgid "Zoom meeting added"
@@ -25895,9 +26553,6 @@ msgstr ""
msgid "branch name"
msgstr "ブランãƒå"
-msgid "build pipeline reference mismatch"
-msgstr ""
-
msgid "by"
msgstr "by"
@@ -25916,6 +26571,9 @@ msgstr "変更ã§ãã¾ã›ã‚“"
msgid "cannot block others"
msgstr "他をブロックã§ãã¾ã›ã‚“"
+msgid "cannot contain HTML/XML tags, including any word between angle brackets (<,>)."
+msgstr ""
+
msgid "cannot include leading slash or directory traversal."
msgstr "先頭ã®ã‚¹ãƒ©ãƒƒã‚·ãƒ¥ã‚’å«ã‚られã¾ã›ã‚“。ã¾ãŸãƒ‡ã‚£ãƒ¬ã‚¯ãƒˆãƒªãƒˆãƒ©ãƒãƒ¼ã‚µãƒ«ã¯ã§ãã¾ã›ã‚“。"
@@ -25946,50 +26604,6 @@ msgstr ""
msgid "ciReport|%{remainingPackagesCount} more"
msgstr "他 %{remainingPackagesCount} 件"
-msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability"
-msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities"
-msgstr[0] "%{reportType} %{status} 㯠%{dismissedCount} 件ã®ç„¡è¦–ã•ã‚ŒãŸè„†å¼±æ€§ã‚’検出ã—ã¾ã—ãŸ"
-
-msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability for the source branch only"
-msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities for the source branch only"
-msgstr[0] "%{reportType}%{status} ã¯ã‚½ãƒ¼ã‚¹ãƒ–ランãƒã«ã®ã¿%{dismissedCount} 件ã®ç„¡è¦–ã•ã‚ŒãŸè„†å¼±æ€§ã‚’検出ã—ã¾ã—ãŸ"
-
-msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerability"
-msgid_plural "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerabilities"
-msgstr[0] "%{reportType} %{status} ã¯%{fixedCount} 件ã®ä¿®æ­£ã•ã‚ŒãŸè„†å¼±æ€§ã‚’検出ã—ã¾ã—ãŸ"
-
-msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
-msgstr "%{reportType}%{status} ã¯%{fixedCount} 件ã®ä¿®æ­£ã¨%{dismissedCount} 件ã®ç„¡è¦–ã•ã‚ŒãŸè„†å¼±æ€§ã‚’検出ã—ã¾ã—ãŸ"
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new vulnerability"
-msgid_plural "ciReport|%{reportType} %{status} detected %{newCount} new vulnerabilities"
-msgstr[0] "%{reportType} %{status} ã¯æ–°ãŸã« %{newCount} 件ã®è„†å¼±æ€§ã‚’検出ã—ã¾ã—ãŸ"
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
-msgstr "%{reportType}%{status} ã¯æ–°ãŸã«%{newCount} 件ã®è„†å¼±æ€§ã€ãŠã‚ˆã³%{fixedCount} 件ã®ä¿®æ­£ã€%{dismissedCount} 件ã®ç„¡è¦–ã•ã‚ŒãŸè„†å¼±æ€§ã‚’検出ã—ã¾ã—ãŸ"
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities"
-msgstr "%{reportType}%{status} ã¯æ–°ãŸã«%{newCount} 件ã®è„†å¼±æ€§ãŒã€%{dismissedCount} 件ã®è„†å¼±æ€§ãŒå´ä¸‹ã•ã‚Œã¾ã—ãŸã€‚"
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities for the source branch only"
-msgstr "%{reportType} %{status} ã¯ã‚½ãƒ¼ã‚¹ãƒ–ランãƒã«ã®ã¿ã€æ–°ãŸã«%{newCount} 件ã®è„†å¼±æ€§ã‚’検出ã—ã€%{dismissedCount} 件ã®ç„¡è¦–ã•ã‚ŒãŸè„†å¼±æ€§ãŒæ¤œå‡ºã•ã‚Œã¾ã—ãŸ"
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{fixedCount} fixed vulnerabilities"
-msgstr "%{reportType} %{status} ã§æ–°ãŸã«%{newCount} 件ã®è„†å¼±æ€§ã¨ã€ %{fixedCount} 件ã®ä¿®æ­£ã•ã‚ŒãŸè„†å¼±æ€§ã‚’検出ã•ã‚Œã¾ã—ãŸ"
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} vulnerability for the source branch only"
-msgid_plural "ciReport|%{reportType} %{status} detected %{newCount} vulnerabilities for the source branch only"
-msgstr[0] "%{reportType} %{status} ã¯ã‚½ãƒ¼ã‚¹ãƒ–ランãƒã«ã®ã¿ã€ %{newCount} 件ã®è„†å¼±æ€§ã‚’検出ã—ã¾ã—ãŸ"
-
-msgid "ciReport|%{reportType} %{status} detected no new vulnerabilities"
-msgstr "%{reportType} %{status} ã¯æ–°ã—ã„脆弱性を検出ã—ã¾ã›ã‚“ã§ã—ãŸ"
-
-msgid "ciReport|%{reportType} %{status} detected no vulnerabilities"
-msgstr "%{reportType} %{status} ã¯è„†å¼±æ€§ã‚’検出ã—ã¾ã›ã‚“ã§ã—ãŸ"
-
-msgid "ciReport|%{reportType} %{status} detected no vulnerabilities for the source branch only"
-msgstr "%{reportType} %{status} ã¯ã€ã‚½ãƒ¼ã‚¹ãƒ–ランãƒã«ã®ã¿è„†å¼±æ€§ã¯è¦‹ã¤ã‹ã‚Šã¾ã›ã‚“ã§ã—ãŸ"
-
msgid "ciReport|%{reportType} is loading"
msgstr "%{reportType} を読ã¿è¾¼ã¿ä¸­"
@@ -26008,8 +26622,8 @@ msgstr "(ロード中ã€çµæžœãƒ­ãƒ¼ãƒ‰æ™‚ã®ã‚¨ãƒ©ãƒ¼)"
msgid "ciReport|All projects"
msgstr "ã™ã¹ã¦ã®ãƒ—ロジェクト"
-msgid "ciReport|All report types"
-msgstr "ã™ã¹ã¦ã®ãƒ¬ãƒãƒ¼ãƒˆã‚¿ã‚¤ãƒ—"
+msgid "ciReport|All scanner types"
+msgstr ""
msgid "ciReport|All severities"
msgstr "ã™ã¹ã¦ã®é‡è¦åº¦"
@@ -26032,6 +26646,9 @@ msgstr "コンテナースキャン"
msgid "ciReport|Container scanning detects known vulnerabilities in your docker images."
msgstr "コンテナスキャンã¯ã€Docker イメージã«å­˜åœ¨ã™ã‚‹æ—¢çŸ¥ã®è„†å¼±æ€§ã‚’検出ã—ã¾ã™ã€‚"
+msgid "ciReport|Coverage Fuzzing"
+msgstr ""
+
msgid "ciReport|Create a merge request to implement this solution, or download and apply the patch manually."
msgstr "マージリクエストを作æˆã—ã¦ã“ã®ã‚½ãƒªãƒ¥ãƒ¼ã‚·ãƒ§ãƒ³ã‚’実装ã™ã‚‹ã€ã¾ãŸã¯æ‰‹å‹•ã§ãƒ‘ッãƒã‚’ダウンロードã—ã¦é©ç”¨ã—ã¾ã™ã€‚"
@@ -26166,7 +26783,7 @@ msgid "confidentiality|You are going to turn off the confidentiality. This means
msgstr "ã‚ãªãŸã¯å…¬é–‹è¨­å®šã«å¤‰æ›´ã—よã†ã¨ã—ã¦ã„ã¾ã™ã€‚ã“ã‚Œã¯<strong>ã™ã¹ã¦ã®äºº</strong> ãŒé–²è¦§å¯èƒ½ã«ãªã‚Šã€èª²é¡Œã«å¯¾ã—ã¦ã‚³ãƒ¡ãƒ³ãƒˆã‚’残ã™ã“ã¨ãŒã§ãるよã†ã«ãªã‚‹ã“ã¨ã‚’æ„味ã—ã¾ã™ã€‚"
msgid "confidentiality|You are going to turn on the confidentiality. This means that only team members with <strong>at least Reporter access</strong> are able to see and leave comments on the issue."
-msgstr "ã‚ãªãŸã¯å…¬é–‹è¨­å®šã«å¤‰æ›´ã—よã†ã¨ã—ã¦ã„ã¾ã™ã€‚ã“ã‚Œã¯ãƒãƒ¼ãƒ ã«é™å®šã—ã¦ã„ãŸ<strong>最å°é™ã®å ±å‘Šæ¨©é™</strong>ã‚’ãªãã—ã€èª²é¡Œã«å¯¾ã—ã¦ã‚³ãƒ¡ãƒ³ãƒˆã‚’残ã™ã“ã¨ãŒã§ãるよã†ã«ãªã‚‹ã“ã¨ã‚’æ„味ã—ã¾ã™ã€‚"
+msgstr "ã‚ãªãŸã¯éžå…¬é–‹è¨­å®šã‚’オンã«ã—よã†ã¨ã—ã¦ã„ã¾ã™ã€‚ã“ã‚Œã¯ã€æœ€ä½Žã§ã‚‚<strong>報告権é™</strong>ã‚’æŒã£ãŸãƒãƒ¼ãƒ ãƒ¡ãƒ³ãƒãƒ¼ã®ã¿ãŒèª²é¡Œã‚’表示ã—ãŸã‚Šã‚³ãƒ¡ãƒ³ãƒˆã‚’残ã—ãŸã‚Šã™ã‚‹ã“ã¨ãŒã§ãるよã†ã«ãªã‚‹ã¨ã„ã†ã“ã¨ã§ã™ã€‚"
msgid "connecting"
msgstr "接続中"
@@ -26189,6 +26806,9 @@ msgstr ""
msgid "customize"
msgstr "カスタマイズ"
+msgid "data"
+msgstr ""
+
msgid "date must not be after 9999-12-31"
msgstr "日付㯠9999-12-31 よりå‰ã‚’指定ã—ã¦ä¸‹ã•ã„"
@@ -26257,6 +26877,9 @@ msgstr "エントリをnilã«ã™ã‚‹ã“ã¨ã¯ã§ãã¾ã›ã‚“"
msgid "entries cannot contain HTML tags"
msgstr "エントリã«HTMLã‚¿ã‚°ã‚’å«ã‚られã¾ã›ã‚“。"
+msgid "epic"
+msgstr ""
+
msgid "error"
msgstr "エラー"
@@ -26278,9 +26901,6 @@ msgstr ""
msgid "expires on %{timebox_due_date}"
msgstr ""
-msgid "external_url"
-msgstr ""
-
msgid "failed"
msgstr "失敗ã—ã¾ã—ãŸ"
@@ -26315,9 +26935,6 @@ msgstr "ã“ã®ãƒ—ロジェクトをフォーク"
msgid "from"
msgstr ""
-msgid "geo_node_name"
-msgstr "geo_node_name"
-
msgid "group"
msgstr "グループ"
@@ -26339,9 +26956,6 @@ msgstr "ã“ã“"
msgid "https://your-bitbucket-server"
msgstr "https://your-bitbucket-server"
-msgid "image"
-msgstr ""
-
msgid "image diff"
msgstr "ç”»åƒã®å·®åˆ†"
@@ -26484,6 +27098,9 @@ msgstr "%{project_limit} ã®åˆ¶é™ã«é”ã—ã¾ã—ãŸ"
msgid "load it anyway"
msgstr ""
+msgid "loading"
+msgstr ""
+
msgid "locked by %{path_lock_user_name} %{created_at}"
msgstr "%{path_lock_user_name} ã«ã‚ˆã£ã¦ %{created_at} ã«ãƒ­ãƒƒã‚¯ã•ã‚Œã¦ã„ã¾ã™ã€‚"
@@ -26611,9 +27228,6 @@ msgstr "ソースブランãƒã‚’削除"
msgid "mrWidget|Deployment statistics are not available currently"
msgstr "デプロイ統計ã¯ç¾åœ¨åˆ©ç”¨ã§ãã¾ã›ã‚“"
-msgid "mrWidget|Detect issues before deployment with a CI pipeline that continuously tests your code. We created a quick guide that will show you how to create one. Make your code more secure and more robust in just a minute."
-msgstr ""
-
msgid "mrWidget|Did not close"
msgstr "クローズã—ãªã‹ã£ãŸ"
@@ -26776,6 +27390,9 @@ msgstr "ã“ã®ãƒ—ロジェクトã¯ã‚¢ãƒ¼ã‚«ã‚¤ãƒ–ã•ã‚Œã¦ã„ã‚‹ãŸã‚ã€æ›¸ã
msgid "mrWidget|To approve this merge request, please enter your password. This project requires all approvals to be authenticated."
msgstr "ã“ã®ãƒžãƒ¼ã‚¸ãƒªã‚¯ã‚¨ã‚¹ãƒˆã‚’承èªã™ã‚‹ã«ã¯ã€ãƒ‘スワードを入力ã—ã¦ãã ã•ã„。ã“ã®ãƒ—ロジェクトã§ã¯ã€ã™ã¹ã¦ã®æ‰¿èªãŒèªè¨¼ã•ã‚Œã¦ã„ãªã‘ã‚Œã°ãªã‚Šã¾ã›ã‚“。"
+msgid "mrWidget|Use %{linkStart}CI pipelines to test your code%{linkEnd}, simply add a GitLab CI configuration file to your project. It only takes a minute to make your code more secure and robust."
+msgstr ""
+
msgid "mrWidget|When this merge request is ready, remove the WIP: prefix from the title to allow it to be merged"
msgstr "ã“ã®ãƒžãƒ¼ã‚¸ãƒªã‚¯ã‚¨ã‚¹ãƒˆã«æº–å‚™ãŒã§ããŸã‚‰ã€ã‚¿ã‚¤ãƒˆãƒ«ã®å…ˆé ­ã‹ã‚‰ WIP: を削除ã—ã¦ãƒžãƒ¼ã‚¸ã§ãるよã†ã«ã—ã¾ã™"
@@ -26812,6 +27429,9 @@ msgstr "パイプラインãŒæˆåŠŸã—ãŸã¨ãã«ãƒžãƒ¼ã‚¸ãƒˆãƒ¬ã‚¤ãƒ³ã‚’開始
msgid "must be greater than start date"
msgstr "開始日より後ã«ã—ã¦ãã ã•ã„。"
+msgid "must contain only valid frameworks"
+msgstr ""
+
msgid "my-awesome-group"
msgstr ""
@@ -26824,6 +27444,9 @@ msgstr ""
msgid "needs to be between 10 minutes and 1 month"
msgstr "10分ã‹ã‚‰1ヶ月ã¾ã§ã®é–“ã«ã™ã‚‹å¿…è¦ãŒã‚ã‚Šã¾ã™"
+msgid "never"
+msgstr ""
+
msgid "never expires"
msgstr "無期é™"
@@ -26851,6 +27474,9 @@ msgstr "メール通知"
msgid "nounSeries|%{firstItem} and %{lastItem}"
msgstr "%{firstItem} 㨠%{lastItem}"
+msgid "nounSeries|%{item}"
+msgstr ""
+
msgid "nounSeries|%{item}, %{nextItem}"
msgstr "%{item} 〠%{nextItem}"
@@ -26982,6 +27608,9 @@ msgstr "対応ã™ã‚‹ã‚¨ãƒ©ãƒ¼ã‚’解決ã—ã€èª²é¡Œã‚’完了ã—ã¾ã—ãŸã€‚"
msgid "revised"
msgstr ""
+msgid "satisfied"
+msgstr ""
+
msgid "score"
msgstr "スコア"
@@ -27012,6 +27641,9 @@ msgstr "ãªã—"
msgid "severity|Unknown"
msgstr "ä¸æ˜Ž"
+msgid "should be an array of %{object_name} objects"
+msgstr ""
+
msgid "should be greater than or equal to %{access} inherited membership from group %{group_name}"
msgstr "%{group_name} グループã‹ã‚‰ç¶™æ‰¿ã•ã‚ŒãŸãƒ¡ãƒ³ãƒãƒ¼ã‚·ãƒƒãƒ—㯠%{access} ã¨åŒç­‰ã¾ãŸã¯ãれ以上ã§ã‚ã‚‹å¿…è¦ãŒã‚ã‚Šã¾ã™"
@@ -27066,10 +27698,13 @@ msgstr ""
msgid "suggestPipeline|2/2: Commit your changes"
msgstr ""
-msgid "suggestPipeline|Commit the changes and your pipeline will automatically run for the first time."
+msgid "suggestPipeline|Choose %{boldStart}Code Quality%{boldEnd} to add a pipeline that tests the quality of your code."
+msgstr ""
+
+msgid "suggestPipeline|The template is ready! You can now commit it to create your first pipeline."
msgstr ""
-msgid "suggestPipeline|We recommend the %{boldStart}Code Quality%{boldEnd} template, which will add a report widget to your Merge Requests. This way you’ll learn about code quality degradations much sooner. %{footerStart} Goodbye technical debt! %{footerEnd}"
+msgid "suggestPipeline|We’re adding a GitLab CI configuration file to add a pipeline to the project. You could create it manually, but we recommend that you start with a GitLab template that works out of the box."
msgstr ""
msgid "syntax is correct"
@@ -27129,6 +27764,9 @@ msgstr "ユーザーå"
msgid "uses Kubernetes clusters to deploy your code!"
msgstr "コードをデプロイã™ã‚‹ãŸã‚ã« Kubernetes クラスターを使用ã™ã‚‹ã€‚"
+msgid "v%{version} published %{timeAgo}"
+msgstr ""
+
msgid "verify ownership"
msgstr "所有者を確èªã™ã‚‹"
diff --git a/locale/ka_GE/gitlab.po b/locale/ka_GE/gitlab.po
index 915d43f81f8..25788491ef8 100644
--- a/locale/ka_GE/gitlab.po
+++ b/locale/ka_GE/gitlab.po
@@ -2,7 +2,7 @@ msgid ""
msgstr ""
"Project-Id-Version: gitlab-ee\n"
"Report-Msgid-Bugs-To: \n"
-"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Last-Translator: \n"
"Language-Team: Georgian\n"
"Language: ka_GE\n"
"MIME-Version: 1.0\n"
@@ -10,9 +10,11 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Crowdin-Project: gitlab-ee\n"
+"X-Crowdin-Project-ID: 288872\n"
"X-Crowdin-Language: ka\n"
"X-Crowdin-File: /master/locale/gitlab.pot\n"
-"PO-Revision-Date: 2020-06-08 15:17\n"
+"X-Crowdin-File-ID: 6\n"
+"PO-Revision-Date: 2020-07-02 01:26\n"
msgid " %{start} to %{end}"
msgstr ""
@@ -197,6 +199,11 @@ msgid_plural "%d metrics"
msgstr[0] ""
msgstr[1] ""
+msgid "%d milestone"
+msgid_plural "%d milestones"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "%d minute"
msgid_plural "%d minutes"
msgstr[0] ""
@@ -242,11 +249,6 @@ msgid_plural "%d unresolved threads"
msgstr[0] ""
msgstr[1] ""
-msgid "%d url scanned"
-msgid_plural "%d urls scanned"
-msgstr[0] ""
-msgstr[1] ""
-
msgid "%d vulnerability"
msgid_plural "%d vulnerabilities"
msgstr[0] ""
@@ -324,9 +326,15 @@ msgstr[1] ""
msgid "%{count} related %{pluralized_subject}: %{links}"
msgstr ""
+msgid "%{dashboard_path} could not be found."
+msgstr ""
+
msgid "%{days} days until tags are automatically removed"
msgstr ""
+msgid "%{deployLinkStart}Use a template to deploy to ECS%{deployLinkEnd}, or use a docker image to %{commandsLinkStart}run AWS commands in GitLab CI/CD%{commandsLinkEnd}."
+msgstr ""
+
msgid "%{description}- Sentry event: %{errorUrl}- First seen: %{firstSeen}- Last seen: %{lastSeen} %{countLabel}: %{count}%{userCountLabel}: %{userCount}"
msgstr ""
@@ -369,12 +377,18 @@ msgstr ""
msgid "%{host} sign-in from new location"
msgstr ""
-msgid "%{icon}You are about to add %{usersTag} people to the discussion. Proceed with caution."
+msgid "%{icon}You are about to add %{usersTag} people to the discussion. They will all receive a notification."
+msgstr ""
+
+msgid "%{integrations_link_start}Integrations%{link_end} enable you to make third-party applications part of your GitLab workflow. If the available integrations don't meet your needs, consider using a %{webhooks_link_start}webhook%{link_end}."
msgstr ""
msgid "%{issuableType} will be removed! Are you sure?"
msgstr ""
+msgid "%{issuesCount} issues in this group"
+msgstr ""
+
msgid "%{issuesSize} issues"
msgstr ""
@@ -384,7 +398,7 @@ msgstr ""
msgid "%{labelStart}Class:%{labelEnd} %{class}"
msgstr ""
-msgid "%{labelStart}Confidence:%{labelEnd} %{confidence}"
+msgid "%{labelStart}Evidence:%{labelEnd} %{evidence}"
msgstr ""
msgid "%{labelStart}File:%{labelEnd} %{file}"
@@ -402,6 +416,9 @@ msgstr ""
msgid "%{labelStart}Report Type:%{labelEnd} %{reportType}"
msgstr ""
+msgid "%{labelStart}Scanner:%{labelEnd} %{scanner}"
+msgstr ""
+
msgid "%{labelStart}Severity:%{labelEnd} %{severity}"
msgstr ""
@@ -426,9 +443,6 @@ msgstr ""
msgid "%{link_start}Read more%{link_end} about role permissions"
msgstr ""
-msgid "%{link} can be used for binding events when something is happening within the project."
-msgstr ""
-
msgid "%{listToShow}, and %{awardsListLength} more."
msgstr ""
@@ -462,6 +476,9 @@ msgstr ""
msgid "%{name} found %{resultsString}"
msgstr ""
+msgid "%{name} is already being used for another emoji"
+msgstr ""
+
msgid "%{name} is scheduled for %{action}"
msgstr ""
@@ -509,6 +526,9 @@ msgstr ""
msgid "%{primary} (%{secondary})"
msgstr ""
+msgid "%{ref} cannot be added: %{error}"
+msgstr ""
+
msgid "%{releases} release"
msgid_plural "%{releases} releases"
msgstr[0] ""
@@ -517,13 +537,37 @@ msgstr[1] ""
msgid "%{remaining_approvals} left"
msgstr ""
-msgid "%{retryButtonStart}Try again%{retryButtonEnd} or %{newFileButtonStart}attach a new file%{newFileButtonEnd}"
+msgid "%{reportType} %{status} detected %{critical} critical and %{high} high severity vulnerabilities out of %{total}."
+msgstr ""
+
+msgid "%{reportType} %{status} detected %{critical} critical and %{high} high severity vulnerabilities."
+msgstr ""
+
+msgid "%{reportType} %{status} detected %{critical} critical severity vulnerabilities out of %{total}."
+msgstr ""
+
+msgid "%{reportType} %{status} detected %{critical} critical severity vulnerability."
+msgid_plural "%{reportType} %{status} detected %{critical} critical severity vulnerabilities."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{reportType} %{status} detected %{high} high severity vulnerabilities out of %{total}."
msgstr ""
-msgid "%{screenreaderOnlyStart}Keyboard shorcuts%{screenreaderOnlyEnd} Disabled"
+msgid "%{reportType} %{status} detected %{high} high severity vulnerability."
+msgid_plural "%{reportType} %{status} detected %{high} high severity vulnerabilities."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{reportType} %{status} detected %{other} vulnerability."
+msgid_plural "%{reportType} %{status} detected %{other} vulnerabilities."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{reportType} %{status} detected no new vulnerabilities."
msgstr ""
-msgid "%{screenreaderOnlyStart}Keyboard shorcuts%{screenreaderOnlyEnd} Enabled"
+msgid "%{retryButtonStart}Try again%{retryButtonEnd} or %{newFileButtonStart}attach a new file%{newFileButtonEnd}"
msgstr ""
msgid "%{service_title} %{message}."
@@ -620,6 +664,12 @@ msgstr ""
msgid "%{token}..."
msgstr ""
+msgid "%{totalCpu} (%{freeSpacePercentage}%{percentSymbol} free)"
+msgstr ""
+
+msgid "%{totalMemory} (%{freeSpacePercentage}%{percentSymbol} free)"
+msgstr ""
+
msgid "%{totalWeight} total weight"
msgstr ""
@@ -650,6 +700,12 @@ msgstr ""
msgid "%{verb} %{time_spent_value} spent time."
msgstr ""
+msgid "%{webhooks_link_start}%{webhook_type}%{link_end} enable you to send notifications to web applications in response to events in a group or project."
+msgstr ""
+
+msgid "%{webhooks_link_start}%{webhook_type}%{link_end} enable you to send notifications to web applications in response to events in a group or project. We recommend using an %{integrations_link_start}integration%{link_end} in preference to a webhook."
+msgstr ""
+
msgid "'%{level}' is not a valid visibility level"
msgstr ""
@@ -676,9 +732,6 @@ msgstr ""
msgid "(No changes)"
msgstr ""
-msgid "(Show all)"
-msgstr ""
-
msgid "(check progress)"
msgstr ""
@@ -745,6 +798,9 @@ msgstr ""
msgid "0 for unlimited"
msgstr ""
+msgid "0 for unlimited, only effective with remote storage enabled."
+msgstr ""
+
msgid "1 %{type} addition"
msgid_plural "%{count} %{type} additions"
msgstr[0] ""
@@ -892,6 +948,12 @@ msgstr ""
msgid "<code>\"johnsmith@example.com\": \"johnsmith@example.com\"</code> will add \"By <a href=\"#\">johnsmith@example.com</a>\" to all issues and comments originally created by johnsmith@example.com. By default, the email address or username is masked to ensure the user's privacy. Use this option if you want to show the full email address."
msgstr ""
+msgid "<code>Masked</code> to prevent the values from being displayed in job logs (must match certain regexp requirements)."
+msgstr ""
+
+msgid "<code>Protected</code> to expose them to protected branches or tags only."
+msgstr ""
+
msgid "<namespace / project>"
msgstr ""
@@ -919,9 +981,6 @@ msgstr ""
msgid "A .NET Core console application template, customizable for any .NET Core project"
msgstr ""
-msgid "A DAG must have two dependent jobs to be visualized on this tab."
-msgstr ""
-
msgid "A GitBook site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
msgstr ""
@@ -952,6 +1011,12 @@ msgstr ""
msgid "A deleted user"
msgstr ""
+msgid "A file has been changed."
+msgstr ""
+
+msgid "A file was not found."
+msgstr ""
+
msgid "A file with '%{file_name}' already exists in %{branch} branch"
msgstr ""
@@ -1018,7 +1083,7 @@ msgstr ""
msgid "A subscription will trigger a new pipeline on the default branch of this project when a pipeline successfully completes for a new tag on the %{default_branch_docs} of the subscribed project."
msgstr ""
-msgid "A terraform report was generated in your pipelines."
+msgid "A suggestion is not applicable."
msgstr ""
msgid "A user with write access to the source branch selected this option"
@@ -1189,6 +1254,9 @@ msgstr ""
msgid "Action to take when receiving an alert."
msgstr ""
+msgid "Actions"
+msgstr ""
+
msgid "Activate"
msgstr ""
@@ -1245,6 +1313,9 @@ msgstr ""
msgid "Add LICENSE"
msgstr ""
+msgid "Add New Node"
+msgstr ""
+
msgid "Add README"
msgstr ""
@@ -1320,6 +1391,9 @@ msgstr ""
msgid "Add approval rule"
msgstr ""
+msgid "Add approvers"
+msgstr ""
+
msgid "Add bold text"
msgstr ""
@@ -1377,6 +1451,9 @@ msgstr ""
msgid "Add strikethrough text"
msgstr ""
+msgid "Add suggestion to batch"
+msgstr ""
+
msgid "Add system hook"
msgstr ""
@@ -1476,6 +1553,9 @@ msgstr ""
msgid "Admin Note"
msgstr ""
+msgid "Admin Notifications"
+msgstr ""
+
msgid "Admin Overview"
msgstr ""
@@ -1587,6 +1667,9 @@ msgstr ""
msgid "AdminSettings|Integrations configured here will automatically apply to all projects on this instance."
msgstr ""
+msgid "AdminSettings|Moved to integrations"
+msgstr ""
+
msgid "AdminSettings|No required pipeline"
msgstr ""
@@ -1817,12 +1900,18 @@ msgid_plural "Alerts"
msgstr[0] ""
msgstr[1] ""
+msgid "AlertManagement| assign yourself"
+msgstr ""
+
msgid "AlertManagement|Acknowledged"
msgstr ""
msgid "AlertManagement|Alert"
msgstr ""
+msgid "AlertManagement|Alert assignee(s): %{assignees}"
+msgstr ""
+
msgid "AlertManagement|Alert detail"
msgstr ""
@@ -1838,9 +1927,15 @@ msgstr ""
msgid "AlertManagement|All alerts"
msgstr ""
+msgid "AlertManagement|Assign To"
+msgstr ""
+
msgid "AlertManagement|Assign status"
msgstr ""
+msgid "AlertManagement|Assignee"
+msgstr ""
+
msgid "AlertManagement|Assignees"
msgstr ""
@@ -1859,9 +1954,6 @@ msgstr ""
msgid "AlertManagement|Edit"
msgstr ""
-msgid "AlertManagement|End time"
-msgstr ""
-
msgid "AlertManagement|Events"
msgstr ""
@@ -1880,10 +1972,13 @@ msgstr ""
msgid "AlertManagement|More information"
msgstr ""
+msgid "AlertManagement|No Matching Results"
+msgstr ""
+
msgid "AlertManagement|No alert data to display."
msgstr ""
-msgid "AlertManagement|No alerts available to display. If you think you're seeing this message in error, refresh the page."
+msgid "AlertManagement|No alerts available to display. See %{linkStart}enabling alert management%{linkEnd} for more information on adding alerts to the list."
msgstr ""
msgid "AlertManagement|No alerts to display."
@@ -1892,6 +1987,9 @@ msgstr ""
msgid "AlertManagement|None"
msgstr ""
+msgid "AlertManagement|None -"
+msgstr ""
+
msgid "AlertManagement|Open"
msgstr ""
@@ -1928,9 +2026,18 @@ msgstr ""
msgid "AlertManagement|There was an error displaying the alerts. Confirm your endpoint's configuration details to ensure alerts appear."
msgstr ""
+msgid "AlertManagement|There was an error while updating the assignee(s) list. Please try again."
+msgstr ""
+
+msgid "AlertManagement|There was an error while updating the assignee(s) of the alert. Please try again."
+msgstr ""
+
msgid "AlertManagement|There was an error while updating the status of the alert. Please try again."
msgstr ""
+msgid "AlertManagement|This assignee cannot be assigned to this alert."
+msgstr ""
+
msgid "AlertManagement|Tool"
msgstr ""
@@ -2078,6 +2185,9 @@ msgstr ""
msgid "Allow users to request access (if visibility is public or internal)"
msgstr ""
+msgid "Allowed Geo IP"
+msgstr ""
+
msgid "Allowed email domain restriction only permitted for top-level groups"
msgstr ""
@@ -2111,6 +2221,9 @@ msgstr ""
msgid "Amazon Web Services"
msgstr ""
+msgid "Amazon Web Services Logo"
+msgstr ""
+
msgid "Amazon authentication is not %{link_start}correctly configured%{link_end}. Ask your GitLab administrator if you want to use this service."
msgstr ""
@@ -2168,10 +2281,16 @@ msgstr ""
msgid "An error occurred when updating the issue weight"
msgstr ""
+msgid "An error occurred while acknowledging the notification. Refresh the page and try again."
+msgstr ""
+
+msgid "An error occurred while adding approvers"
+msgstr ""
+
msgid "An error occurred while adding formatted title for epic"
msgstr ""
-msgid "An error occurred while checking group path"
+msgid "An error occurred while checking group path. Please refresh and try again."
msgstr ""
msgid "An error occurred while committing your changes."
@@ -2315,7 +2434,7 @@ msgstr ""
msgid "An error occurred while loading milestones"
msgstr ""
-msgid "An error occurred while loading terraform report"
+msgid "An error occurred while loading project creation UI"
msgstr ""
msgid "An error occurred while loading the data. Please try again."
@@ -2369,6 +2488,9 @@ msgstr ""
msgid "An error occurred while rendering preview broadcast message"
msgstr ""
+msgid "An error occurred while rendering the editor"
+msgstr ""
+
msgid "An error occurred while reordering issues."
msgstr ""
@@ -2465,7 +2587,7 @@ msgstr ""
msgid "Analyze your dependencies for known vulnerabilities."
msgstr ""
-msgid "Analyze your source code and git history for secrets"
+msgid "Analyze your source code and git history for secrets."
msgstr ""
msgid "Analyze your source code for known vulnerabilities."
@@ -2579,12 +2701,18 @@ msgstr ""
msgid "Apply suggestion"
msgstr ""
+msgid "Apply suggestions"
+msgstr ""
+
msgid "Apply template"
msgstr ""
msgid "Apply this approval rule to any branch or a specific protected branch."
msgstr ""
+msgid "Applying"
+msgstr ""
+
msgid "Applying a template will replace the existing issue description. Any changes you have made will be lost."
msgstr ""
@@ -2597,12 +2725,18 @@ msgstr ""
msgid "Applying multiple commands"
msgstr ""
-msgid "Applying suggestion"
+msgid "Applying suggestion..."
+msgstr ""
+
+msgid "Applying suggestions..."
msgstr ""
msgid "Approval rules"
msgstr ""
+msgid "Approval rules reset to project defaults"
+msgstr ""
+
msgid "ApprovalRuleRemove|%d member"
msgid_plural "ApprovalRuleRemove|%d members"
msgstr[0] ""
@@ -2656,6 +2790,9 @@ msgstr ""
msgid "Approved"
msgstr ""
+msgid "Approved MRs"
+msgstr ""
+
msgid "Approved by: "
msgstr ""
@@ -2869,6 +3006,9 @@ msgstr ""
msgid "Assign"
msgstr ""
+msgid "Assign Iteration"
+msgstr ""
+
msgid "Assign custom color like #FF0000"
msgstr ""
@@ -2887,6 +3027,9 @@ msgstr ""
msgid "Assign to"
msgstr ""
+msgid "Assign to commenting user"
+msgstr ""
+
msgid "Assign yourself to these issues"
msgstr ""
@@ -2913,6 +3056,9 @@ msgid_plural "%d Assignees"
msgstr[0] ""
msgstr[1] ""
+msgid "Assignee has no permissions"
+msgstr ""
+
msgid "Assignee lists not available with your current license"
msgstr ""
@@ -2922,6 +3068,9 @@ msgstr ""
msgid "Assignee(s)"
msgstr ""
+msgid "Assignees"
+msgstr ""
+
msgid "Assigns %{assignee_users_sentence}."
msgstr ""
@@ -2957,6 +3106,9 @@ msgstr ""
msgid "Audit Events is a way to keep track of important events that happened in GitLab."
msgstr ""
+msgid "Audit Log"
+msgstr ""
+
msgid "AuditEvents|(removed)"
msgstr ""
@@ -2993,6 +3145,9 @@ msgstr ""
msgid "AuditLogs|IP Address"
msgstr ""
+msgid "AuditLogs|Member Events"
+msgstr ""
+
msgid "AuditLogs|No matching %{type} found."
msgstr ""
@@ -3275,9 +3430,6 @@ msgstr ""
msgid "Badges|e.g. %{exampleUrl}"
msgstr ""
-msgid "Badge|New"
-msgstr ""
-
msgid "Balsamiq file could not be loaded."
msgstr ""
@@ -3326,6 +3478,12 @@ msgstr ""
msgid "Below you will find all the groups that are public."
msgstr ""
+msgid "Beta"
+msgstr ""
+
+msgid "Bi-weekly code coverage"
+msgstr ""
+
msgid "Billing"
msgstr ""
@@ -3386,9 +3544,15 @@ msgstr ""
msgid "Bitbucket Server Import"
msgstr ""
+msgid "Bitbucket Server import"
+msgstr ""
+
msgid "Bitbucket import"
msgstr ""
+msgid "Blame"
+msgstr ""
+
msgid "Blocked"
msgstr ""
@@ -3401,9 +3565,6 @@ msgstr ""
msgid "Blog"
msgstr ""
-msgid "Blue helpers indicate an action to be taken."
-msgstr ""
-
msgid "Board name"
msgstr ""
@@ -3602,6 +3763,9 @@ msgstr ""
msgid "Broadcast Message was successfully updated."
msgstr ""
+msgid "Broadcast Messages"
+msgstr ""
+
msgid "Browse Directory"
msgstr ""
@@ -3656,6 +3820,9 @@ msgstr ""
msgid "By %{user_name}"
msgstr ""
+msgid "By URL"
+msgstr ""
+
msgid "By default GitLab sends emails in HTML and plain text formats so mail clients can choose what format to use. Disable this option if you only want to send emails in plain text format."
msgstr ""
@@ -3761,6 +3928,9 @@ msgstr ""
msgid "Can override approvers and approvals required per merge request"
msgstr ""
+msgid "Can't apply as this line has changed or the suggestion already matches its content."
+msgstr ""
+
msgid "Can't create snippet: %{err}"
msgstr ""
@@ -3947,9 +4117,6 @@ msgstr ""
msgid "Changes are still tracked. Useful for cluster/index migrations."
msgstr ""
-msgid "Changes are unknown"
-msgstr ""
-
msgid "Changes suppressed. Click to show."
msgstr ""
@@ -4256,15 +4423,9 @@ msgstr ""
msgid "Choose what content you want to see on a group’s overview page"
msgstr ""
-msgid "Choose which groups you wish to synchronize to this secondary node"
-msgstr ""
-
msgid "Choose which repositories you want to connect and run CI/CD pipelines."
msgstr ""
-msgid "Choose which shards you wish to synchronize to this secondary node"
-msgstr ""
-
msgid "Choose your framework"
msgstr ""
@@ -4562,9 +4723,15 @@ msgstr ""
msgid "Cluster does not exist"
msgstr ""
+msgid "Cluster is required for Stages::ClusterEndpointInserter"
+msgstr ""
+
msgid "Cluster level"
msgstr ""
+msgid "Cluster type must be specificed for Stages::ClusterEndpointInserter"
+msgstr ""
+
msgid "ClusterIntegration| %{custom_domain_start}More information%{custom_domain_end}."
msgstr ""
@@ -4676,7 +4843,7 @@ msgstr ""
msgid "ClusterIntegration|Cert-Manager"
msgstr ""
-msgid "ClusterIntegration|Cert-Manager is a native Kubernetes certificate management controller that helps with issuing certificates. Installing Cert-Manager on your cluster will issue a certificate by %{letsEncrypt} and ensure that certificates are valid and up-to-date."
+msgid "ClusterIntegration|Cert-Manager is a native Kubernetes certificate management controller that helps with issuing certificates. Installing Cert-Manager on your cluster will issue a certificate by %{linkStart}Let's Encrypt%{linkEnd} and ensure that certificates are valid and up-to-date."
msgstr ""
msgid "ClusterIntegration|Certificate Authority bundle (PEM format)"
@@ -4703,9 +4870,6 @@ msgstr ""
msgid "ClusterIntegration|Clear the local cache of namespace and service accounts. This is necessary if your integration has become out of sync. The cache is repopulated during the next CI job that requires namespace and service accounts."
msgstr ""
-msgid "ClusterIntegration|Cloud Run"
-msgstr ""
-
msgid "ClusterIntegration|Cluster being created"
msgstr ""
@@ -4790,7 +4954,7 @@ msgstr ""
msgid "ClusterIntegration|Crossplane"
msgstr ""
-msgid "ClusterIntegration|Crossplane enables declarative provisioning of managed services from your cloud of choice using %{codeStart}kubectl%{codeEnd} or %{gitlabIntegrationLink}. Crossplane runs inside your Kubernetes cluster and supports secure connectivity and secrets management between app containers and the cloud services they depend on."
+msgid "ClusterIntegration|Crossplane enables declarative provisioning of managed services from your cloud of choice using %{codeStart}kubectl%{codeEnd} or %{linkStart}GitLab Integration%{linkEnd}. Crossplane runs inside your Kubernetes cluster and supports secure connectivity and secrets management between app containers and the cloud services they depend on."
msgstr ""
msgid "ClusterIntegration|Deletes all GitLab resources attached to this cluster during removal"
@@ -4874,9 +5038,6 @@ msgstr ""
msgid "ClusterIntegration|GitLab-managed cluster"
msgstr ""
-msgid "ClusterIntegration|Gitlab Integration"
-msgstr ""
-
msgid "ClusterIntegration|Global default"
msgstr ""
@@ -4922,7 +5083,7 @@ msgstr ""
msgid "ClusterIntegration|Ingress gives you a way to route requests to services based on the request host or path, centralizing a number of services into a single entrypoint."
msgstr ""
-msgid "ClusterIntegration|Installing Ingress may incur additional costs. Learn more about %{pricingLink}."
+msgid "ClusterIntegration|Installing Ingress may incur additional costs. Learn more about %{linkStart}pricing%{linkEnd}."
msgstr ""
msgid "ClusterIntegration|Instance cluster"
@@ -4937,7 +5098,7 @@ msgstr ""
msgid "ClusterIntegration|Issuer Email"
msgstr ""
-msgid "ClusterIntegration|Issuers represent a certificate authority. You must provide an email address for your Issuer. "
+msgid "ClusterIntegration|Issuers represent a certificate authority. You must provide an email address for your Issuer."
msgstr ""
msgid "ClusterIntegration|Jupyter Hostname"
@@ -5009,9 +5170,6 @@ msgstr ""
msgid "ClusterIntegration|Learn more about instance Kubernetes clusters"
msgstr ""
-msgid "ClusterIntegration|Let's Encrypt"
-msgstr ""
-
msgid "ClusterIntegration|Loading IAM Roles"
msgstr ""
@@ -5123,7 +5281,7 @@ msgstr ""
msgid "ClusterIntegration|Prometheus"
msgstr ""
-msgid "ClusterIntegration|Prometheus is an open-source monitoring system with %{gitlabIntegrationLink} to monitor deployed applications."
+msgid "ClusterIntegration|Prometheus is an open-source monitoring system with %{linkStart}GitLab Integration%{linkEnd} to monitor deployed applications."
msgstr ""
msgid "ClusterIntegration|Provider details"
@@ -5279,10 +5437,10 @@ msgstr ""
msgid "ClusterIntegration|Select zone to choose machine type"
msgstr ""
-msgid "ClusterIntegration|Send Cilium Logs"
+msgid "ClusterIntegration|Send Container Network Policies Logs"
msgstr ""
-msgid "ClusterIntegration|Send ModSecurity Logs"
+msgid "ClusterIntegration|Send Web Application Firewall Logs"
msgstr ""
msgid "ClusterIntegration|Service Token"
@@ -5450,15 +5608,12 @@ msgstr ""
msgid "ClusterIntegration|documentation"
msgstr ""
-msgid "ClusterIntegration|installed via %{installed_via}"
+msgid "ClusterIntegration|installed via %{linkStart}Cloud Run%{linkEnd}"
msgstr ""
msgid "ClusterIntegration|meets the requirements"
msgstr ""
-msgid "ClusterIntegration|pricing"
-msgstr ""
-
msgid "ClusterIntegration|sign up"
msgstr ""
@@ -5495,6 +5650,15 @@ msgstr ""
msgid "Code"
msgstr ""
+msgid "Code Coverage: %{coveragePercentage}%{percentSymbol}"
+msgstr ""
+
+msgid "Code Coverage| Empty code coverage data"
+msgstr ""
+
+msgid "Code Coverage|Couldn't fetch the code coverage data"
+msgstr ""
+
msgid "Code Owners"
msgstr ""
@@ -5549,7 +5713,7 @@ msgstr ""
msgid "Collapse approvers"
msgstr ""
-msgid "Collapse child epics"
+msgid "Collapse milestones"
msgstr ""
msgid "Collapse replies"
@@ -5567,6 +5731,9 @@ msgstr ""
msgid "Coming soon"
msgstr ""
+msgid "Comma-separated, e.g. '1.1.1.1, 2.2.2.0/24'"
+msgstr ""
+
msgid "Command"
msgstr ""
@@ -5737,6 +5904,12 @@ msgstr ""
msgid "Compliance framework (optional)"
msgstr ""
+msgid "Compliance frameworks"
+msgstr ""
+
+msgid "ComplianceDashboard|created by:"
+msgstr ""
+
msgid "ComplianceFramework|GDPR"
msgstr ""
@@ -5875,6 +6048,9 @@ msgstr ""
msgid "Connection timed out"
msgstr ""
+msgid "Connection timeout"
+msgstr ""
+
msgid "Contact sales to upgrade"
msgstr ""
@@ -5930,9 +6106,6 @@ msgstr ""
msgid "ContainerRegistry|CLI Commands"
msgstr ""
-msgid "ContainerRegistry|Compressed Size"
-msgstr ""
-
msgid "ContainerRegistry|Container Registry"
msgstr ""
@@ -5948,6 +6121,9 @@ msgstr ""
msgid "ContainerRegistry|Copy push command"
msgstr ""
+msgid "ContainerRegistry|Delete selected"
+msgstr ""
+
msgid "ContainerRegistry|Docker connection error"
msgstr ""
@@ -5981,16 +6157,16 @@ msgstr ""
msgid "ContainerRegistry|If you are not already logged in, you need to authenticate to the Container Registry by using your GitLab username and password. If you have %{twofaDocLinkStart}Two-Factor Authentication%{twofaDocLinkEnd} enabled, use a %{personalAccessTokensDocLinkStart}Personal Access Token%{personalAccessTokensDocLinkEnd} instead of a password."
msgstr ""
-msgid "ContainerRegistry|Image ID"
+msgid "ContainerRegistry|Image ID: %{imageId}"
msgstr ""
msgid "ContainerRegistry|Image Repositories"
msgstr ""
-msgid "ContainerRegistry|Keep and protect the images that matter most."
+msgid "ContainerRegistry|Image tags"
msgstr ""
-msgid "ContainerRegistry|Last Updated"
+msgid "ContainerRegistry|Keep and protect the images that matter most."
msgstr ""
msgid "ContainerRegistry|Login"
@@ -6005,6 +6181,9 @@ msgstr ""
msgid "ContainerRegistry|Please contact your administrator."
msgstr ""
+msgid "ContainerRegistry|Published %{timeInfo}"
+msgstr ""
+
msgid "ContainerRegistry|Push an image"
msgstr ""
@@ -6020,9 +6199,6 @@ msgstr ""
msgid "ContainerRegistry|Remove repository"
msgstr ""
-msgid "ContainerRegistry|Remove selected tags"
-msgstr ""
-
msgid "ContainerRegistry|Remove tag"
msgid_plural "ContainerRegistry|Remove tags"
msgstr[0] ""
@@ -6052,9 +6228,6 @@ msgstr ""
msgid "ContainerRegistry|Sorry, your filter produced no results."
msgstr ""
-msgid "ContainerRegistry|Tag"
-msgstr ""
-
msgid "ContainerRegistry|Tag expiration policy"
msgstr ""
@@ -6199,21 +6372,6 @@ msgstr ""
msgid "Control the display of third party offers."
msgstr ""
-msgid "Control the maximum concurrency of LFS/attachment backfill for this secondary node"
-msgstr ""
-
-msgid "Control the maximum concurrency of container repository operations for this Geo node"
-msgstr ""
-
-msgid "Control the maximum concurrency of repository backfill for this secondary node"
-msgstr ""
-
-msgid "Control the maximum concurrency of verification operations for this Geo node"
-msgstr ""
-
-msgid "Control the minimum interval in days that a repository should be reverified for this primary node"
-msgstr ""
-
msgid "Cookie domain"
msgstr ""
@@ -6355,6 +6513,9 @@ msgstr ""
msgid "Could not find design."
msgstr ""
+msgid "Could not find iteration"
+msgstr ""
+
msgid "Could not remove the trigger."
msgstr ""
@@ -6391,6 +6552,12 @@ msgstr ""
msgid "Coverage"
msgstr ""
+msgid "Coverage Fuzzing"
+msgstr ""
+
+msgid "Crash State"
+msgstr ""
+
msgid "Create"
msgstr ""
@@ -6439,6 +6606,9 @@ msgstr ""
msgid "Create a personal access token on your account to pull or push via %{protocol}."
msgstr ""
+msgid "Create a project pre-populated with the necessary files to get you started quickly."
+msgstr ""
+
msgid "Create an account using:"
msgstr ""
@@ -6607,6 +6777,9 @@ msgstr ""
msgid "Creates branch '%{branch_name}' and a merge request to resolve this issue."
msgstr ""
+msgid "Creating"
+msgstr ""
+
msgid "Creating epic"
msgstr ""
@@ -6658,7 +6831,10 @@ msgstr ""
msgid "Current vulnerabilities count"
msgstr ""
-msgid "CurrentUser|Buy CI minutes"
+msgid "CurrentUser|Buy Pipeline minutes"
+msgstr ""
+
+msgid "CurrentUser|One of your groups is running out"
msgstr ""
msgid "CurrentUser|Profile"
@@ -6673,6 +6849,9 @@ msgstr ""
msgid "CurrentUser|Upgrade"
msgstr ""
+msgid "Custom Attributes"
+msgstr ""
+
msgid "Custom CI configuration path"
msgstr ""
@@ -6936,12 +7115,18 @@ msgstr ""
msgid "DAG"
msgstr ""
+msgid "DAG visualization requires at least 3 dependent jobs."
+msgstr ""
+
msgid "DNS"
msgstr ""
msgid "Dashboard"
msgstr ""
+msgid "Dashboard uid not found"
+msgstr ""
+
msgid "DashboardProjects|All"
msgstr ""
@@ -6966,6 +7151,9 @@ msgstr ""
msgid "Data is still calculating..."
msgstr ""
+msgid "Datasource name not found"
+msgstr ""
+
msgid "Date"
msgstr ""
@@ -7146,6 +7334,9 @@ msgstr ""
msgid "Delete this attachment"
msgstr ""
+msgid "Delete user list"
+msgstr ""
+
msgid "Delete variable"
msgstr ""
@@ -7462,6 +7653,9 @@ msgstr ""
msgid "Deploying to"
msgstr ""
+msgid "Deploying to AWS is easy with GitLab"
+msgstr ""
+
msgid "Deployment Frequency"
msgstr ""
@@ -7824,6 +8018,9 @@ msgstr ""
msgid "Dismissed on pipeline %{pipelineLink} at %{projectLink}"
msgstr ""
+msgid "Display alerts from all your monitoring tools directly within GitLab."
+msgstr ""
+
msgid "Display name"
msgstr ""
@@ -7881,9 +8078,6 @@ msgstr ""
msgid "Don't show again"
msgstr ""
-msgid "Don't worry, you can access this tour by clicking on the help icon in the top right corner and choose <strong>Learn GitLab</strong>."
-msgstr ""
-
msgid "Done"
msgstr ""
@@ -8010,6 +8204,9 @@ msgstr ""
msgid "Edit Release"
msgstr ""
+msgid "Edit Slack integration"
+msgstr ""
+
msgid "Edit Snippet"
msgstr ""
@@ -8049,6 +8246,9 @@ msgstr ""
msgid "Edit issues"
msgstr ""
+msgid "Edit iteration"
+msgstr ""
+
msgid "Edit public deploy key"
msgstr ""
@@ -8097,6 +8297,9 @@ msgstr ""
msgid "Email"
msgstr ""
+msgid "Email Notification"
+msgstr ""
+
msgid "Email address"
msgstr ""
@@ -8109,6 +8312,9 @@ msgstr ""
msgid "Email not verified. Please verify your email in Salesforce."
msgstr ""
+msgid "Email notification for unknown sign-ins"
+msgstr ""
+
msgid "Email patch"
msgstr ""
@@ -8340,6 +8546,9 @@ msgstr ""
msgid "Enforce DNS rebinding attack protection"
msgstr ""
+msgid "Enforce personal access token expiration"
+msgstr ""
+
msgid "Ensure connectivity is available from the GitLab server to the Prometheus server"
msgstr ""
@@ -8409,6 +8618,9 @@ msgstr ""
msgid "Enter the name of your application, and we'll return a unique %{type}."
msgstr ""
+msgid "Enter weights for storages for new repositories."
+msgstr ""
+
msgid "Enter your password to approve"
msgstr ""
@@ -8418,15 +8630,27 @@ msgstr ""
msgid "Environment does not have deployments"
msgstr ""
+msgid "Environment is required for Stages::MetricEndpointInserter"
+msgstr ""
+
+msgid "Environment is required for Stages::VariableEndpointInserter"
+msgstr ""
+
msgid "Environment scope"
msgstr ""
+msgid "Environment variables are applied to all project environments in this instance via the Runner. You can use environment variables for passwords, secret keys, etc. Make variables available to the running application by prepending the variable key with <code>K8S_SECRET_</code>. You can set variables to be:"
+msgstr ""
+
msgid "Environment variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. Additionally, they can be masked so they are hidden in job logs, though they must match certain regexp requirements to do so. You can use environment variables for passwords, secret keys, or whatever you want."
msgstr ""
msgid "Environment variables are configured by your administrator to be %{link_start}protected%{link_end} by default"
msgstr ""
+msgid "Environment variables on this GitLab instance are configured to be %{link_start}protected%{link_end} by default"
+msgstr ""
+
msgid "Environment:"
msgstr ""
@@ -8958,6 +9182,9 @@ msgstr ""
msgid "EventFilterBy|Filter by comments"
msgstr ""
+msgid "EventFilterBy|Filter by designs"
+msgstr ""
+
msgid "EventFilterBy|Filter by epic events"
msgstr ""
@@ -9090,15 +9317,15 @@ msgstr ""
msgid "Expand approvers"
msgstr ""
-msgid "Expand child epics"
-msgstr ""
-
msgid "Expand down"
msgstr ""
msgid "Expand dropdown"
msgstr ""
+msgid "Expand milestones"
+msgstr ""
+
msgid "Expand sidebar"
msgstr ""
@@ -9114,6 +9341,9 @@ msgstr ""
msgid "Expiration date"
msgstr ""
+msgid "Expiration not enforced"
+msgstr ""
+
msgid "Expiration policy for the Container Registry is a perfect solution for keeping the Registry space down while still enjoying the full power of GitLab CI/CD."
msgstr ""
@@ -9234,6 +9464,9 @@ msgstr ""
msgid "Failed Jobs"
msgstr ""
+msgid "Failed on"
+msgstr ""
+
msgid "Failed to add a Zoom meeting"
msgstr ""
@@ -9294,13 +9527,19 @@ msgstr ""
msgid "Failed to install."
msgstr ""
+msgid "Failed to load assignees. Please try again."
+msgstr ""
+
+msgid "Failed to load authors. Please try again."
+msgstr ""
+
msgid "Failed to load emoji list."
msgstr ""
msgid "Failed to load error details from Sentry."
msgstr ""
-msgid "Failed to load errors from Sentry. Error message: %{errorMessage}"
+msgid "Failed to load errors from Sentry."
msgstr ""
msgid "Failed to load group activity metrics. Please try again."
@@ -9381,6 +9620,9 @@ msgstr ""
msgid "Failed to set due date because the date format is invalid."
msgstr ""
+msgid "Failed to set iteration on this issue. Please try again."
+msgstr ""
+
msgid "Failed to signing using smartcard authentication"
msgstr ""
@@ -9603,13 +9845,10 @@ msgstr ""
msgid "FeatureFlags|Target environments"
msgstr ""
-msgid "FeatureFlags|There are no active feature flags"
-msgstr ""
-
-msgid "FeatureFlags|There are no inactive feature flags"
+msgid "FeatureFlags|There was an error fetching the feature flags."
msgstr ""
-msgid "FeatureFlags|There was an error fetching the feature flags."
+msgid "FeatureFlags|There was an error retrieving user lists"
msgstr ""
msgid "FeatureFlags|Try again in a few moments or contact your support team."
@@ -9621,9 +9860,18 @@ msgstr ""
msgid "FeatureFlag|Delete strategy"
msgstr ""
+msgid "FeatureFlag|List"
+msgstr ""
+
msgid "FeatureFlag|Percentage"
msgstr ""
+msgid "FeatureFlag|Select a user list"
+msgstr ""
+
+msgid "FeatureFlag|There are no configured user lists"
+msgstr ""
+
msgid "FeatureFlag|Type"
msgstr ""
@@ -9780,6 +10028,12 @@ msgstr ""
msgid "Filter..."
msgstr ""
+msgid "Find File"
+msgstr ""
+
+msgid "Find bugs in your code with coverage-guided fuzzing"
+msgstr ""
+
msgid "Find by path"
msgstr ""
@@ -10044,6 +10298,9 @@ msgstr ""
msgid "Geo allows you to replicate your GitLab instance to other geographical locations."
msgstr ""
+msgid "Geo nodes are paused using a command run on the node"
+msgstr ""
+
msgid "GeoNodeStatusEvent|%{timeAgoStr} (%{pendingEvents} events)"
msgstr ""
@@ -10128,6 +10385,9 @@ msgstr ""
msgid "GeoNodes|Not checksummed"
msgstr ""
+msgid "GeoNodes|Package files"
+msgstr ""
+
msgid "GeoNodes|Pausing replication stops the sync process. Are you sure?"
msgstr ""
@@ -10146,6 +10406,9 @@ msgstr ""
msgid "GeoNodes|Replication slots"
msgstr ""
+msgid "GeoNodes|Replication status"
+msgstr ""
+
msgid "GeoNodes|Repositories"
msgstr ""
@@ -10356,6 +10619,9 @@ msgstr ""
msgid "Geo|Synchronization failed - %{error}"
msgstr ""
+msgid "Geo|Synchronization of %{itemTitle} is disabled."
+msgstr ""
+
msgid "Geo|The database is currently %{db_lag} behind the primary node."
msgstr ""
@@ -10668,6 +10934,9 @@ msgstr ""
msgid "Go to %{link_to_google_takeout}."
msgstr ""
+msgid "Go to <strong>Issues</strong> > <strong>Boards</strong> to access your personalized learning issue board."
+msgstr ""
+
msgid "Go to Pipelines"
msgstr ""
@@ -10767,9 +11036,6 @@ msgstr ""
msgid "Go to your snippets"
msgstr ""
-msgid "Golden Tanuki"
-msgstr ""
-
msgid "Google Cloud Platform"
msgstr ""
@@ -10791,6 +11057,9 @@ msgstr ""
msgid "Grafana URL"
msgstr ""
+msgid "Grafana response contains invalid json"
+msgstr ""
+
msgid "GrafanaIntegration|API Token"
msgstr ""
@@ -10839,9 +11108,6 @@ msgstr ""
msgid "Group %{group_name} was successfully created."
msgstr ""
-msgid "Group '%{group_name}' is being imported."
-msgstr ""
-
msgid "Group Audit Events"
msgstr ""
@@ -10878,9 +11144,6 @@ msgstr ""
msgid "Group by:"
msgstr ""
-msgid "Group could not be imported: %{errors}"
-msgstr ""
-
msgid "Group description"
msgstr ""
@@ -10908,9 +11171,15 @@ msgstr ""
msgid "Group has not been marked for deletion"
msgstr ""
+msgid "Group import could not be scheduled"
+msgstr ""
+
msgid "Group info:"
msgstr ""
+msgid "Group is required when cluster_type is :group"
+msgstr ""
+
msgid "Group maintainers can register group runners in the %{link}"
msgstr ""
@@ -10974,6 +11243,24 @@ msgstr ""
msgid "GroupActivyMetrics|Recent activity (last 90 days)"
msgstr ""
+msgid "GroupImport|Failed to import group."
+msgstr ""
+
+msgid "GroupImport|Group '%{group_name}' is being imported."
+msgstr ""
+
+msgid "GroupImport|Group could not be imported: %{errors}"
+msgstr ""
+
+msgid "GroupImport|Please wait while we import the group for you. Refresh at will."
+msgstr ""
+
+msgid "GroupImport|The group was successfully imported."
+msgstr ""
+
+msgid "GroupImport|Unable to process group import file"
+msgstr ""
+
msgid "GroupRoadmap|%{dateWord} – No end date"
msgstr ""
@@ -11241,6 +11528,9 @@ msgstr ""
msgid "Groups (%{groups})"
msgstr ""
+msgid "Groups and projects"
+msgstr ""
+
msgid "Groups can also be nested by creating %{subgroup_docs_link_start}subgroups%{subgroup_docs_link_end}."
msgstr ""
@@ -11286,6 +11576,33 @@ msgstr ""
msgid "GroupsEmptyState|You can manage your group member’s permissions and access to each project in the group."
msgstr ""
+msgid "GroupsNew|Contact an administrator to enable options for importing your group."
+msgstr ""
+
+msgid "GroupsNew|Create"
+msgstr ""
+
+msgid "GroupsNew|Create group"
+msgstr ""
+
+msgid "GroupsNew|GitLab group export"
+msgstr ""
+
+msgid "GroupsNew|Import"
+msgstr ""
+
+msgid "GroupsNew|Import group"
+msgstr ""
+
+msgid "GroupsNew|My Awesome Group"
+msgstr ""
+
+msgid "GroupsNew|No import options available"
+msgstr ""
+
+msgid "GroupsNew|To copy a GitLab group between installations, navigate to the group settings page for the original installation, generate an export file, and upload it here."
+msgstr ""
+
msgid "GroupsTree|Are you sure you want to leave the \"%{fullName}\" group?"
msgstr ""
@@ -11388,6 +11705,9 @@ msgstr ""
msgid "Helps reduce request volume for protected paths"
msgstr ""
+msgid "Here are all your projects in your group, including the one you just created. To start, let’s take a look at your personalized learning project which will help you learn about GitLab at your own pace."
+msgstr ""
+
msgid "Here you will find recent merge request activity"
msgstr ""
@@ -11402,6 +11722,9 @@ msgid_plural "Hide charts"
msgstr[0] ""
msgstr[1] ""
+msgid "Hide details"
+msgstr ""
+
msgid "Hide file browser"
msgstr ""
@@ -11411,6 +11734,9 @@ msgstr ""
msgid "Hide host keys manual input"
msgstr ""
+msgid "Hide list"
+msgstr ""
+
msgid "Hide marketing-related entries from help"
msgstr ""
@@ -11431,9 +11757,6 @@ msgstr[1] ""
msgid "Hide values"
msgstr ""
-msgid "Hiding all labels"
-msgstr ""
-
msgid "High or unknown vulnerabilities present"
msgstr ""
@@ -11518,6 +11841,9 @@ msgstr ""
msgid "ID:"
msgstr ""
+msgid "IDE"
+msgstr ""
+
msgid "IDE|Allow live previews of JavaScript projects in the Web IDE using CodeSandbox Live Preview."
msgstr ""
@@ -11608,10 +11934,10 @@ msgstr ""
msgid "If enabled"
msgstr ""
-msgid "If enabled, access to projects will be validated on an external service using their classification label."
+msgid "If enabled, GitLab will handle Object Storage replication using Geo. %{linkStart}More information%{linkEnd}"
msgstr ""
-msgid "If enabled, and if object storage is enabled, GitLab will handle Object Storage replication using Geo"
+msgid "If enabled, access to projects will be validated on an external service using their classification label."
msgstr ""
msgid "If there is no previous license or if the previous license has expired, some GitLab functionality will be blocked until a new, valid license is uploaded."
@@ -11973,6 +12299,9 @@ msgid_plural "Instances"
msgstr[0] ""
msgstr[1] ""
+msgid "Instance Configuration"
+msgstr ""
+
msgid "Instance Statistics visibility"
msgstr ""
@@ -11994,9 +12323,6 @@ msgstr ""
msgid "Integrations"
msgstr ""
-msgid "Integrations allow you to integrate GitLab with other applications"
-msgstr ""
-
msgid "Integrations|All details"
msgstr ""
@@ -12108,6 +12434,9 @@ msgstr ""
msgid "Invalid query"
msgstr ""
+msgid "Invalid repository bundle for snippet with id %{snippet_id}"
+msgstr ""
+
msgid "Invalid repository path"
msgstr ""
@@ -12126,6 +12455,9 @@ msgstr ""
msgid "Invalid two-factor code."
msgstr ""
+msgid "Invalid yaml"
+msgstr ""
+
msgid "Invitation"
msgstr ""
@@ -12345,15 +12677,24 @@ msgstr ""
msgid "It seems like the Dependency Scanning job ran successfully, but no dependencies have been detected in your project."
msgstr ""
+msgid "It seems that there is currently no available data for code coverage"
+msgstr ""
+
msgid "It's you"
msgstr ""
+msgid "Iteration"
+msgstr ""
+
msgid "Iteration changed to"
msgstr ""
msgid "Iteration removed"
msgstr ""
+msgid "Iteration updated"
+msgstr ""
+
msgid "Iterations"
msgstr ""
@@ -12384,12 +12725,18 @@ msgstr ""
msgid "January"
msgstr ""
+msgid "Jira Issues"
+msgstr ""
+
msgid "Jira import is already running."
msgstr ""
msgid "Jira integration not configured."
msgstr ""
+msgid "Jira project key is not configured"
+msgstr ""
+
msgid "Jira project: %{importProject}"
msgstr ""
@@ -12570,6 +12917,9 @@ msgstr ""
msgid "Keep divergent refs"
msgstr ""
+msgid "Kerberos access denied"
+msgstr ""
+
msgid "Key"
msgstr ""
@@ -12579,10 +12929,13 @@ msgstr ""
msgid "Key: %{key}"
msgstr ""
-msgid "Keyboard Shortcuts"
+msgid "Keyboard shortcuts"
msgstr ""
-msgid "Keyboard shortcuts"
+msgid "Keys"
+msgstr ""
+
+msgid "Ki"
msgstr ""
msgid "Kubernetes"
@@ -12624,6 +12977,9 @@ msgstr ""
msgid "LDAP"
msgstr ""
+msgid "LDAP Synchronization"
+msgstr ""
+
msgid "LDAP settings"
msgstr ""
@@ -12848,12 +13204,18 @@ msgstr ""
msgid "Learn more about custom project templates"
msgstr ""
+msgid "Learn more about deploying to AWS"
+msgstr ""
+
msgid "Learn more about deploying to a cluster"
msgstr ""
msgid "Learn more about group-level project templates"
msgstr ""
+msgid "Learn more about job dependencies"
+msgstr ""
+
msgid "Learn more about signing commits"
msgstr ""
@@ -12905,9 +13267,21 @@ msgstr ""
msgid "License History"
msgstr ""
+msgid "License ID:"
+msgstr ""
+
+msgid "License URL"
+msgstr ""
+
msgid "License-Check"
msgstr ""
+msgid "LicenseCompliance|%{docLinkStart}License Approvals%{docLinkEnd} are active"
+msgstr ""
+
+msgid "LicenseCompliance|%{docLinkStart}License Approvals%{docLinkEnd} are inactive"
+msgstr ""
+
msgid "LicenseCompliance|Add a license"
msgstr ""
@@ -12929,9 +13303,15 @@ msgstr ""
msgid "LicenseCompliance|Deny"
msgstr ""
+msgid "LicenseCompliance|Learn more about %{linkStart}License Approvals%{linkEnd}"
+msgstr ""
+
msgid "LicenseCompliance|License"
msgstr ""
+msgid "LicenseCompliance|License Approvals"
+msgstr ""
+
msgid "LicenseCompliance|License Compliance detected %d license and policy violation for the source branch only; approval required"
msgid_plural "LicenseCompliance|License Compliance detected %d licenses and policy violations for the source branch only; approval required"
msgstr[0] ""
@@ -13152,6 +13532,9 @@ msgstr ""
msgid "List your Bitbucket Server repositories"
msgstr ""
+msgid "Lists"
+msgstr ""
+
msgid "Live preview"
msgstr ""
@@ -13335,6 +13718,9 @@ msgstr ""
msgid "Manage project labels"
msgstr ""
+msgid "Manage storage usage"
+msgstr ""
+
msgid "Manage two-factor authentication"
msgstr ""
@@ -13350,6 +13736,9 @@ msgstr ""
msgid "Manifest file import"
msgstr ""
+msgid "Manifest import"
+msgstr ""
+
msgid "Manual job"
msgstr ""
@@ -13497,6 +13886,12 @@ msgstr ""
msgid "Maximum field length"
msgstr ""
+msgid "Maximum file size is 2MB. Please select a smaller file."
+msgstr ""
+
+msgid "Maximum import size (MB)"
+msgstr ""
+
msgid "Maximum job timeout"
msgstr ""
@@ -13536,6 +13931,9 @@ msgstr ""
msgid "Maximum size of Elasticsearch bulk indexing requests."
msgstr ""
+msgid "Maximum size of import files."
+msgstr ""
+
msgid "Maximum size of individual attachments in comments."
msgstr ""
@@ -13587,6 +13985,9 @@ msgstr ""
msgid "Merge (when the pipeline succeeds)"
msgstr ""
+msgid "Merge Conflicts"
+msgstr ""
+
msgid "Merge Request"
msgstr ""
@@ -13713,6 +14114,9 @@ msgstr ""
msgid "MergeRequests|Squash task canceled: another squash is already in progress."
msgstr ""
+msgid "MergeRequests|This project does not allow squashing commits when merge requests are accepted."
+msgstr ""
+
msgid "MergeRequests|Thread stays resolved"
msgstr ""
@@ -13866,6 +14270,12 @@ msgstr ""
msgid "MetricsSettings|Add a button to the metrics dashboard linking directly to your existing external dashboard."
msgstr ""
+msgid "MetricsSettings|Choose whether to display dashboard metrics in UTC or the user's local timezone."
+msgstr ""
+
+msgid "MetricsSettings|Dashboard timezone"
+msgstr ""
+
msgid "MetricsSettings|External dashboard URL"
msgstr ""
@@ -13875,6 +14285,12 @@ msgstr ""
msgid "MetricsSettings|Metrics Dashboard"
msgstr ""
+msgid "MetricsSettings|UTC (Coordinated Universal Time)"
+msgstr ""
+
+msgid "MetricsSettings|User's local timezone"
+msgstr ""
+
msgid "Metrics|Add metric"
msgstr ""
@@ -13890,6 +14306,9 @@ msgstr ""
msgid "Metrics|Create metric"
msgstr ""
+msgid "Metrics|Current"
+msgstr ""
+
msgid "Metrics|Delete metric"
msgstr ""
@@ -13940,6 +14359,9 @@ msgstr ""
msgid "Metrics|Max"
msgstr ""
+msgid "Metrics|Min"
+msgstr ""
+
msgid "Metrics|Must be a valid PromQL query."
msgstr ""
@@ -13955,6 +14377,9 @@ msgstr ""
msgid "Metrics|Refresh dashboard"
msgstr ""
+msgid "Metrics|Select a value"
+msgstr ""
+
msgid "Metrics|Star dashboard"
msgstr ""
@@ -13973,12 +14398,18 @@ msgstr ""
msgid "Metrics|There was an error getting annotations information."
msgstr ""
+msgid "Metrics|There was an error getting dashboard validation warnings information."
+msgstr ""
+
msgid "Metrics|There was an error getting deployment information."
msgstr ""
msgid "Metrics|There was an error getting environments information."
msgstr ""
+msgid "Metrics|There was an error getting options for variable \"%{name}\"."
+msgstr ""
+
msgid "Metrics|There was an error trying to validate your query"
msgstr ""
@@ -14021,6 +14452,9 @@ msgstr ""
msgid "Metrics|You're about to permanently delete this metric. This cannot be undone."
msgstr ""
+msgid "Metrics|Your dashboard schema is invalid. Edit the dashboard to correct the YAML schema."
+msgstr ""
+
msgid "Metrics|e.g. HTTP requests"
msgstr ""
@@ -14036,12 +14470,18 @@ msgstr ""
msgid "Metrics|e.g. req/sec"
msgstr ""
+msgid "Mi"
+msgstr ""
+
msgid "Microsoft Azure"
msgstr ""
msgid "Middleman project with Static Site Editor support"
msgstr ""
+msgid "Migrate your data from an external source like GitHub, Bitbucket, or another instance of GitLab."
+msgstr ""
+
msgid "Migrated %{success_count}/%{total_count} files."
msgstr ""
@@ -14176,6 +14616,9 @@ msgstr ""
msgid "Minimum capacity to be available before we schedule more mirrors preemptively."
msgstr ""
+msgid "Minimum interval in days"
+msgstr ""
+
msgid "Minimum length is %{minimum_password_length} characters"
msgstr ""
@@ -14362,6 +14805,12 @@ msgstr ""
msgid "Multiple uploaders found: %{uploader_types}"
msgstr ""
+msgid "Must match with the %{codeStart}external_url%{codeEnd} in %{codeStart}/etc/gitlab/gitlab.rb%{codeEnd}."
+msgstr ""
+
+msgid "Must match with the %{codeStart}geo_node_name%{codeEnd} in %{codeStart}/etc/gitlab/gitlab.rb%{codeEnd}. %{linkStart}More information%{linkEnd}"
+msgstr ""
+
msgid "My Awesome Group"
msgstr ""
@@ -14476,10 +14925,16 @@ msgstr ""
msgid "New Application"
msgstr ""
+msgid "New Branch"
+msgstr ""
+
+msgid "New Deploy Key"
+msgstr ""
+
msgid "New Environment"
msgstr ""
-msgid "New Geo Node"
+msgid "New File"
msgstr ""
msgid "New Group"
@@ -14496,15 +14951,15 @@ msgid_plural "New Issues"
msgstr[0] ""
msgstr[1] ""
-msgid "New Iteration"
-msgstr ""
-
msgid "New Jira import"
msgstr ""
msgid "New Label"
msgstr ""
+msgid "New Merge Request"
+msgstr ""
+
msgid "New Milestone"
msgstr ""
@@ -14523,6 +14978,9 @@ msgstr ""
msgid "New Snippet"
msgstr ""
+msgid "New User"
+msgstr ""
+
msgid "New branch"
msgstr ""
@@ -14565,6 +15023,9 @@ msgstr ""
msgid "New issue title"
msgstr ""
+msgid "New iteration"
+msgstr ""
+
msgid "New iteration created"
msgstr ""
@@ -14742,6 +15203,12 @@ msgstr ""
msgid "No grouping"
msgstr ""
+msgid "No iteration"
+msgstr ""
+
+msgid "No iterations to show"
+msgstr ""
+
msgid "No job log"
msgstr ""
@@ -14784,6 +15251,9 @@ msgstr ""
msgid "No other labels with such name or description"
msgstr ""
+msgid "No panels matching properties %{opts}"
+msgstr ""
+
msgid "No parent group"
msgstr ""
@@ -14817,6 +15287,9 @@ msgstr ""
msgid "No schedules"
msgstr ""
+msgid "No stack trace for this error"
+msgstr ""
+
msgid "No starrers matched your search"
msgstr ""
@@ -14829,7 +15302,7 @@ msgstr ""
msgid "No test coverage"
msgstr ""
-msgid "No thanks, don't show this again"
+msgid "No thanks"
msgstr ""
msgid "No vulnerabilities present"
@@ -14844,9 +15317,6 @@ msgstr ""
msgid "No, directly import the existing email addresses and usernames."
msgstr ""
-msgid "No, not interested right now"
-msgstr ""
-
msgid "No. of commits"
msgstr ""
@@ -14892,9 +15362,6 @@ msgstr ""
msgid "Not found."
msgstr ""
-msgid "Not helpful"
-msgstr ""
-
msgid "Not now"
msgstr ""
@@ -15051,6 +15518,9 @@ msgstr ""
msgid "Notifications on"
msgstr ""
+msgid "Notify users by email when sign-in location is not recognized"
+msgstr ""
+
msgid "Nov"
msgstr ""
@@ -15117,9 +15587,6 @@ msgstr ""
msgid "Oh no!"
msgstr ""
-msgid "Ok let's go"
-msgstr ""
-
msgid "Oldest first"
msgstr ""
@@ -15132,16 +15599,49 @@ msgstr ""
msgid "On track"
msgstr ""
+msgid "OnDemandScans|Attached branch"
+msgstr ""
+
+msgid "OnDemandScans|Attached branch is where the scan job runs."
+msgstr ""
+
+msgid "OnDemandScans|Could not run the scan. Please try again."
+msgstr ""
+
msgid "OnDemandScans|Create new DAST scan"
msgstr ""
+msgid "OnDemandScans|DAST will scan the target URL and any discovered sub URLs."
+msgstr ""
+
+msgid "OnDemandScans|New on-demand DAST scan"
+msgstr ""
+
msgid "OnDemandScans|On-demand Scans"
msgstr ""
+msgid "OnDemandScans|On-demand scans run outside the DevOps cycle and find vulnerabilities in your projects. %{learnMoreLinkStart}Learn more%{learnMoreLinkEnd}"
+msgstr ""
+
+msgid "OnDemandScans|Only a passive scan can be performed on demand."
+msgstr ""
+
+msgid "OnDemandScans|Passive DAST Scan"
+msgstr ""
+
+msgid "OnDemandScans|Please enter a valid URL format, ex: http://www.example.com/home"
+msgstr ""
+
+msgid "OnDemandScans|Run this scan"
+msgstr ""
+
+msgid "OnDemandScans|Scan mode"
+msgstr ""
+
msgid "OnDemandScans|Schedule or run scans immediately against target sites. Currently available on-demand scan type: DAST. %{helpLinkStart}More information%{helpLinkEnd}"
msgstr ""
-msgid "Onboarding"
+msgid "OnDemandScans|Target URL"
msgstr ""
msgid "Once imported, repositories can be mirrored over SSH. Read more %{link_start}here%{link_end}."
@@ -15167,6 +15667,9 @@ msgstr ""
msgid "One or more of you personal access tokens were revoked"
msgstr ""
+msgid "One or more of your %{provider} projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
+msgstr ""
+
msgid "One or more of your Bitbucket projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
msgstr ""
@@ -15668,6 +16171,9 @@ msgstr ""
msgid "Parent epic is not present."
msgstr ""
+msgid "Parsing error for param :embed_json. %{message}"
+msgstr ""
+
msgid "Part of merge request changes"
msgstr ""
@@ -15680,6 +16186,9 @@ msgstr ""
msgid "Passed"
msgstr ""
+msgid "Passed on"
+msgstr ""
+
msgid "Password"
msgstr ""
@@ -15764,6 +16273,9 @@ msgstr ""
msgid "Perform common operations on GitLab project"
msgstr ""
+msgid "Performance and resource management"
+msgstr ""
+
msgid "Performance optimization"
msgstr ""
@@ -15971,7 +16483,7 @@ msgstr ""
msgid "Pipelines|Something went wrong while cleaning runners cache."
msgstr ""
-msgid "Pipelines|There are currently no %{scope} pipelines."
+msgid "Pipelines|There are currently no finished pipelines."
msgstr ""
msgid "Pipelines|There are currently no pipelines."
@@ -16082,6 +16594,9 @@ msgstr ""
msgid "Pipeline|Stop pipeline #%{pipelineId}?"
msgstr ""
+msgid "Pipeline|Tag name"
+msgstr ""
+
msgid "Pipeline|Trigger author"
msgstr ""
@@ -16163,12 +16678,18 @@ msgstr ""
msgid "Please check your email (%{email}) to verify that you own this address and unlock the power of CI/CD. Didn't receive it? %{resend_link}. Wrong email address? %{update_link}."
msgstr ""
+msgid "Please choose a file"
+msgstr ""
+
msgid "Please choose a group URL with no special characters."
msgstr ""
msgid "Please complete your profile with email address"
msgstr ""
+msgid "Please convert %{linkStart}them to Git%{linkEnd}, and go through the %{linkToImportFlow} again."
+msgstr ""
+
msgid "Please convert them to %{link_to_git}, and go through the %{link_to_import_flow} again."
msgstr ""
@@ -16226,6 +16747,9 @@ msgstr ""
msgid "Please provide a valid email address."
msgstr ""
+msgid "Please provide attributes to update"
+msgstr ""
+
msgid "Please refer to <a href=\"%{docs_url}\">%{docs_url}</a>"
msgstr ""
@@ -16961,6 +17485,9 @@ msgstr ""
msgid "Project has too many %{label_for_message} to search"
msgstr ""
+msgid "Project is required when cluster_type is :project"
+msgstr ""
+
msgid "Project members"
msgstr ""
@@ -17078,7 +17605,40 @@ msgstr ""
msgid "ProjectService|Comment"
msgstr ""
-msgid "ProjectService|Comment will be posted on each event"
+msgid "ProjectService|Event will be triggered by a push to the repository"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a commit is created/updated"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a confidential issue is created/updated/closed"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a deployment finishes"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a merge request is created/updated/merged"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a new tag is pushed to the repository"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a new, unique alert is recorded"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a pipeline status changes"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a wiki page is created/updated"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when an issue is created/updated/closed"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when someone adds a comment"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when someone adds a comment on a confidential issue"
msgstr ""
msgid "ProjectService|Perform common operations on GitLab project: %{project_name}"
@@ -17129,6 +17689,9 @@ msgstr ""
msgid "ProjectSettings|Enable 'Delete source branch' option by default"
msgstr ""
+msgid "ProjectSettings|Enable merge trains and pipelines for merged results"
+msgstr ""
+
msgid "ProjectSettings|Every merge creates a merge commit"
msgstr ""
@@ -17159,7 +17722,7 @@ msgstr ""
msgid "ProjectSettings|Forks"
msgstr ""
-msgid "ProjectSettings|Git Large File Storage"
+msgid "ProjectSettings|Git Large File Storage (LFS)"
msgstr ""
msgid "ProjectSettings|Internal"
@@ -17195,9 +17758,6 @@ msgstr ""
msgid "ProjectSettings|Merge options"
msgstr ""
-msgid "ProjectSettings|Merge pipelines will try to validate the post-merge result prior to merging"
-msgstr ""
-
msgid "ProjectSettings|Merge requests"
msgstr ""
@@ -17225,6 +17785,9 @@ msgstr ""
msgid "ProjectSettings|Pipelines"
msgstr ""
+msgid "ProjectSettings|Pipelines for merge requests must be enabled in the CI/CD configuration file, or pipelines could be unresolvable or dropped"
+msgstr ""
+
msgid "ProjectSettings|Pipelines must succeed"
msgstr ""
@@ -17252,6 +17815,9 @@ msgstr ""
msgid "ProjectSettings|Show link to create/view merge request when pushing from the command line"
msgstr ""
+msgid "ProjectSettings|Skipped pipelines are considered successful"
+msgstr ""
+
msgid "ProjectSettings|Snippets"
msgstr ""
@@ -17267,6 +17833,9 @@ msgstr ""
msgid "ProjectSettings|These checks must pass before merge requests can be merged"
msgstr ""
+msgid "ProjectSettings|This introduces the risk of merging changes that will not pass the pipeline."
+msgstr ""
+
msgid "ProjectSettings|This setting is applied on the server level and can be overridden by an admin."
msgstr ""
@@ -17444,15 +18013,27 @@ msgstr ""
msgid "ProjectsNew|Blank project"
msgstr ""
+msgid "ProjectsNew|Connect your external repository to GitLab CI/CD."
+msgstr ""
+
msgid "ProjectsNew|Contact an administrator to enable options for importing your project."
msgstr ""
msgid "ProjectsNew|Create"
msgstr ""
+msgid "ProjectsNew|Create a blank project to house your files, plan your work, and collaborate on code, among other things."
+msgstr ""
+
+msgid "ProjectsNew|Create blank project"
+msgstr ""
+
msgid "ProjectsNew|Create from template"
msgstr ""
+msgid "ProjectsNew|Create new project"
+msgstr ""
+
msgid "ProjectsNew|Creating project & repository."
msgstr ""
@@ -17477,6 +18058,9 @@ msgstr ""
msgid "ProjectsNew|Project description %{tag_start}(optional)%{tag_end}"
msgstr ""
+msgid "ProjectsNew|Run CI/CD for external repository"
+msgstr ""
+
msgid "ProjectsNew|Template"
msgstr ""
@@ -17738,6 +18322,9 @@ msgstr ""
msgid "Protected Branch"
msgstr ""
+msgid "Protected Branches"
+msgstr ""
+
msgid "Protected Environment"
msgstr ""
@@ -17750,6 +18337,9 @@ msgstr ""
msgid "Protected Tag"
msgstr ""
+msgid "Protected Tags"
+msgstr ""
+
msgid "Protected branches"
msgstr ""
@@ -17849,6 +18439,9 @@ msgstr ""
msgid "Provider"
msgstr ""
+msgid "Proxy support for this API is not available currently"
+msgstr ""
+
msgid "Pseudonymizer data collection"
msgstr ""
@@ -17873,6 +18466,9 @@ msgstr ""
msgid "Publish to status page"
msgstr ""
+msgid "Published on status page"
+msgstr ""
+
msgid "Publishes this issue to the associated status page."
msgstr ""
@@ -18047,6 +18643,9 @@ msgstr ""
msgid "Recipe"
msgstr ""
+msgid "Reconfigure"
+msgstr ""
+
msgid "Recover hidden stage"
msgstr ""
@@ -18133,6 +18732,9 @@ msgstr ""
msgid "Registration|Your profile"
msgstr ""
+msgid "Regulate approvals by authors/committers, based on compliance frameworks. Can be changed only at the instance level."
+msgstr ""
+
msgid "Rejected (closed)"
msgstr ""
@@ -18183,15 +18785,24 @@ msgstr ""
msgid "ReleaseAssetLinkType|Image"
msgstr ""
+msgid "ReleaseAssetLinkType|Images"
+msgstr ""
+
msgid "ReleaseAssetLinkType|Other"
msgstr ""
msgid "ReleaseAssetLinkType|Package"
msgstr ""
+msgid "ReleaseAssetLinkType|Packages"
+msgstr ""
+
msgid "ReleaseAssetLinkType|Runbook"
msgstr ""
+msgid "ReleaseAssetLinkType|Runbooks"
+msgstr ""
+
msgid "Releases"
msgstr ""
@@ -18204,6 +18815,9 @@ msgstr ""
msgid "Releases documentation"
msgstr ""
+msgid "Releases|New Release"
+msgstr ""
+
msgid "Release|Something went wrong while getting the release details"
msgstr ""
@@ -18276,6 +18890,9 @@ msgstr ""
msgid "Remove fork relationship"
msgstr ""
+msgid "Remove from batch"
+msgstr ""
+
msgid "Remove from board"
msgstr ""
@@ -18465,6 +19082,12 @@ msgstr ""
msgid "Replication"
msgstr ""
+msgid "Replication enabled"
+msgstr ""
+
+msgid "Replication paused"
+msgstr ""
+
msgid "Reply by email"
msgstr ""
@@ -18492,15 +19115,15 @@ msgstr ""
msgid "Reported %{timeAgo} by %{reportedBy}"
msgstr ""
-msgid "Reported Resource Changes: %{addNum} to add, %{changeNum} to change, %{deleteNum} to delete"
-msgstr ""
-
msgid "Reporter"
msgstr ""
msgid "Reporting"
msgstr ""
+msgid "Reports"
+msgstr ""
+
msgid "Reports|%{combinedString} and %{resolvedString}"
msgstr ""
@@ -18539,6 +19162,9 @@ msgstr ""
msgid "Reports|Failure"
msgstr ""
+msgid "Reports|Identifier"
+msgstr ""
+
msgid "Reports|Metrics reports are loading"
msgstr ""
@@ -18551,6 +19177,9 @@ msgstr ""
msgid "Reports|Metrics reports failed loading results"
msgstr ""
+msgid "Reports|Scanner"
+msgstr ""
+
msgid "Reports|Severity"
msgstr ""
@@ -18599,15 +19228,33 @@ msgstr ""
msgid "Repository cleanup has started. You will receive an email once the cleanup operation is complete."
msgstr ""
+msgid "Repository files count over the limit"
+msgstr ""
+
+msgid "Repository has an invalid default branch name."
+msgstr ""
+
+msgid "Repository has more than one branch."
+msgstr ""
+
msgid "Repository has no locks."
msgstr ""
+msgid "Repository has tags."
+msgstr ""
+
msgid "Repository maintenance"
msgstr ""
msgid "Repository mirroring"
msgstr ""
+msgid "Repository must contain at least 1 file."
+msgstr ""
+
+msgid "Repository size is above the limit."
+msgstr ""
+
msgid "Repository static objects"
msgstr ""
@@ -18617,7 +19264,7 @@ msgstr ""
msgid "Repository sync capacity"
msgstr ""
-msgid "Repository: %{counter_repositories} / Wikis: %{counter_wikis} / Build Artifacts: %{counter_build_artifacts} / LFS: %{counter_lfs_objects}"
+msgid "Repository: %{counter_repositories} / Wikis: %{counter_wikis} / Build Artifacts: %{counter_build_artifacts} / LFS: %{counter_lfs_objects} / Snippets: %{counter_snippets}"
msgstr ""
msgid "RepositorySettingsAccessLevel|Select"
@@ -18629,6 +19276,9 @@ msgstr ""
msgid "Request Headers"
msgstr ""
+msgid "Request details"
+msgstr ""
+
msgid "Request parameter %{param} is missing."
msgstr ""
@@ -18735,6 +19385,9 @@ msgstr ""
msgid "Reset template"
msgstr ""
+msgid "Reset to project defaults"
+msgstr ""
+
msgid "Resetting the authorization key for this project will require updating the authorization key in every alert source it is enabled in."
msgstr ""
@@ -18831,9 +19484,6 @@ msgstr ""
msgid "Resume"
msgstr ""
-msgid "Resume replication"
-msgstr ""
-
msgid "Resync"
msgstr ""
@@ -19456,6 +20106,9 @@ msgstr ""
msgid "SecurityReports|False positive"
msgstr ""
+msgid "SecurityReports|Group Security Dashboard"
+msgstr ""
+
msgid "SecurityReports|Hide dismissed"
msgstr ""
@@ -19510,10 +20163,13 @@ msgstr ""
msgid "SecurityReports|Remove project from dashboard"
msgstr ""
-msgid "SecurityReports|Report type"
+msgid "SecurityReports|Return to dashboard"
msgstr ""
-msgid "SecurityReports|Return to dashboard"
+msgid "SecurityReports|Scan details"
+msgstr ""
+
+msgid "SecurityReports|Scanner type"
msgstr ""
msgid "SecurityReports|Security Dashboard"
@@ -19570,6 +20226,9 @@ msgstr ""
msgid "SecurityReports|There was an error while generating the report."
msgstr ""
+msgid "SecurityReports|Unable to add %{invalidProjectsMessage}"
+msgstr ""
+
msgid "SecurityReports|Unable to add %{invalidProjects}"
msgstr ""
@@ -19684,6 +20343,9 @@ msgstr ""
msgid "Select due date"
msgstr ""
+msgid "Select file"
+msgstr ""
+
msgid "Select group or project"
msgstr ""
@@ -19747,9 +20409,6 @@ msgstr ""
msgid "Select the branch you want to set as the default for this project. All merge requests and commits will automatically be made against this branch unless you specify a different one."
msgstr ""
-msgid "Select the configured storage available for new repositories to be placed on."
-msgstr ""
-
msgid "Select the custom project template source group."
msgstr ""
@@ -19942,12 +20601,6 @@ msgstr ""
msgid "Service Desk is enabled but not yet active"
msgstr ""
-msgid "Service Desk is off"
-msgstr ""
-
-msgid "Service Desk is on"
-msgstr ""
-
msgid "Service Templates"
msgstr ""
@@ -20035,6 +20688,15 @@ msgstr ""
msgid "Set the milestone to %{milestone_reference}."
msgstr ""
+msgid "Set the number of concurrent requests this secondary node will make to the primary node while backfilling."
+msgstr ""
+
+msgid "Set the synchronization and verification capacity for the secondary node."
+msgstr ""
+
+msgid "Set the timeout in seconds to send a secondary node status to the primary and IPs allowed for the secondary nodes."
+msgstr ""
+
msgid "Set time estimate"
msgstr ""
@@ -20074,6 +20736,9 @@ msgstr ""
msgid "Set weight to %{weight}."
msgstr ""
+msgid "Set what should be replicated by choosing specific projects or groups by the secondary node."
+msgstr ""
+
msgid "SetPasswordToCloneLink|set a password"
msgstr ""
@@ -20200,6 +20865,9 @@ msgstr ""
msgid "Show complete raw log"
msgstr ""
+msgid "Show details"
+msgstr ""
+
msgid "Show file browser"
msgstr ""
@@ -20209,10 +20877,13 @@ msgstr ""
msgid "Show latest version"
msgstr ""
+msgid "Show list"
+msgstr ""
+
msgid "Show me everything"
msgstr ""
-msgid "Show me how"
+msgid "Show me how to add a pipeline"
msgstr ""
msgid "Show me more advanced stuff"
@@ -20261,9 +20932,6 @@ msgstr ""
msgid "Showing all issues"
msgstr ""
-msgid "Showing all labels"
-msgstr ""
-
msgid "Showing last %{size} of log -"
msgstr ""
@@ -20375,9 +21043,6 @@ msgstr ""
msgid "Skip outdated deployment jobs"
msgstr ""
-msgid "Skip this for now"
-msgstr ""
-
msgid "Skipped"
msgstr ""
@@ -20522,6 +21187,9 @@ msgstr ""
msgid "Something went wrong while adding your award. Please try again."
msgstr ""
+msgid "Something went wrong while applying the batch of suggestions. Please try again."
+msgstr ""
+
msgid "Something went wrong while applying the suggestion. Please try again."
msgstr ""
@@ -20624,10 +21292,13 @@ msgstr ""
msgid "Something went wrong while updating a requirement."
msgstr ""
+msgid "Something went wrong while updating assignees"
+msgstr ""
+
msgid "Something went wrong while updating your list settings"
msgstr ""
-msgid "Something went wrong with your automatic subscription renewal"
+msgid "Something went wrong with your automatic subscription renewal."
msgstr ""
msgid "Something went wrong, unable to add %{project} to dashboard"
@@ -20645,6 +21316,9 @@ msgstr ""
msgid "Something went wrong, unable to search projects"
msgstr ""
+msgid "Something went wrong."
+msgstr ""
+
msgid "Something went wrong. Please try again."
msgstr ""
@@ -20828,6 +21502,9 @@ msgstr ""
msgid "Source code"
msgstr ""
+msgid "Source code (%{fileExtension})"
+msgstr ""
+
msgid "Source is not available"
msgstr ""
@@ -20912,6 +21589,9 @@ msgstr ""
msgid "Stack trace"
msgstr ""
+msgid "Stacktrace snippet"
+msgstr ""
+
msgid "Stage"
msgstr ""
@@ -21017,6 +21697,9 @@ msgstr ""
msgid "Start thread & reopen %{noteable_name}"
msgstr ""
+msgid "Start using Directed Acyclic Graphs (DAG)"
+msgstr ""
+
msgid "Start your Free Gold Trial"
msgstr ""
@@ -21479,6 +22162,21 @@ msgstr ""
msgid "SuggestedColors|Very pale orange"
msgstr ""
+msgid "Suggestion is not applicable as the suggestion was not found."
+msgstr ""
+
+msgid "Suggestions are not applicable as one or more suggestions were not found."
+msgstr ""
+
+msgid "Suggestions are not applicable as their lines cannot overlap."
+msgstr ""
+
+msgid "Suggestions must all be on the same branch."
+msgstr ""
+
+msgid "Suggestions that change line count can't be added to batches, yet."
+msgstr ""
+
msgid "Suggestions:"
msgstr ""
@@ -21521,9 +22219,6 @@ msgstr ""
msgid "Synchronization disabled"
msgstr ""
-msgid "Synchronization of container repositories is disabled."
-msgstr ""
-
msgid "System"
msgstr ""
@@ -21704,6 +22399,34 @@ msgstr ""
msgid "Terms of Service and Privacy Policy"
msgstr ""
+msgid "Terraform|%{number} Terraform report failed to generate"
+msgid_plural "Terraform|%{number} Terraform reports failed to generate"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Terraform|%{number} Terraform report was generated in your pipelines"
+msgid_plural "Terraform|%{number} Terraform reports were generated in your pipelines"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Terraform|A Terraform report failed to generate."
+msgstr ""
+
+msgid "Terraform|A Terraform report was generated in your pipelines."
+msgstr ""
+
+msgid "Terraform|Generating the report caused an error."
+msgstr ""
+
+msgid "Terraform|Reported Resource Changes: %{addNum} to add, %{changeNum} to change, %{deleteNum} to delete"
+msgstr ""
+
+msgid "Terraform|The Terraform report %{name} failed to generate."
+msgstr ""
+
+msgid "Terraform|The Terraform report %{name} was generated in your pipelines."
+msgstr ""
+
msgid "Test"
msgstr ""
@@ -21730,7 +22453,7 @@ msgstr ""
msgid "TestHooks|Ensure the project has CI pipelines."
msgstr ""
-msgid "TestHooks|Ensure the project has at least one commit."
+msgid "TestHooks|Ensure the project has deployments."
msgstr ""
msgid "TestHooks|Ensure the project has issues."
@@ -21831,7 +22554,7 @@ msgstr ""
msgid "The Prometheus server responded with \"bad request\". Please check your queries are correct and are supported in your Prometheus version. %{documentationLink}"
msgstr ""
-msgid "The URL defined on the primary node that secondary nodes should use to contact it. Defaults to URL"
+msgid "The URL defined on the primary node that secondary nodes should use to contact it."
msgstr ""
msgid "The URL to use for connecting to Elasticsearch. Use a comma-separated list to support clustering (e.g., \"http://localhost:9200, http://localhost:9201\")."
@@ -21840,6 +22563,9 @@ msgstr ""
msgid "The X509 Certificate to use when mutual TLS is required to communicate with the external authorization service. If left blank, the server certificate is still validated when accessing over HTTPS."
msgstr ""
+msgid "The above settings apply to all projects with the selected compliance framework(s)."
+msgstr ""
+
msgid "The amount of seconds after which a request to get a secondary node status will time out."
msgstr ""
@@ -22139,9 +22865,6 @@ msgstr ""
msgid "The total stage shows the time it takes between creating an issue and deploying the code to production. The data will be automatically added once you have completed the full idea to production cycle."
msgstr ""
-msgid "The unique identifier for the Geo node. Must match %{geoNodeName} if it is set in gitlab.rb, otherwise it must match %{externalUrl} with a trailing slash"
-msgstr ""
-
msgid "The update action will time out after %{number_of_minutes} minutes. For big repositories, use a clone/push combination."
msgstr ""
@@ -22166,9 +22889,6 @@ msgstr ""
msgid "The user you are trying to deactivate has been active in the past %{minimum_inactive_days} days and cannot be deactivated"
msgstr ""
-msgid "The user-facing URL of the Geo node"
-msgstr ""
-
msgid "The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6."
msgstr ""
@@ -22259,6 +22979,9 @@ msgstr ""
msgid "There was a problem fetching project branches."
msgstr ""
+msgid "There was a problem fetching project tags."
+msgstr ""
+
msgid "There was a problem fetching project users."
msgstr ""
@@ -22310,6 +23033,9 @@ msgstr ""
msgid "There was an error fetching the %{replicableType}"
msgstr ""
+msgid "There was an error fetching the Geo Settings"
+msgstr ""
+
msgid "There was an error fetching the Node's Groups"
msgstr ""
@@ -22430,6 +23156,12 @@ msgstr ""
msgid "This %{issuable} is locked. Only <strong>project members</strong> can comment."
msgstr ""
+msgid "This %{noteableTypeText} is %{confidentialLinkStart}confidential%{linkEnd} and %{lockedLinkStart}locked%{linkEnd}."
+msgstr ""
+
+msgid "This %{noteableTypeText} is locked."
+msgstr ""
+
msgid "This %{viewer} could not be displayed because %{reason}. You can %{options} instead."
msgstr ""
@@ -22448,6 +23180,9 @@ msgstr ""
msgid "This action can lead to data loss. To prevent accidental actions we ask you to confirm your intention."
msgstr ""
+msgid "This also resolves all related threads"
+msgstr ""
+
msgid "This also resolves the discussion"
msgstr ""
@@ -22538,6 +23273,9 @@ msgstr ""
msgid "This epic does not exist or you don't have sufficient permission."
msgstr ""
+msgid "This feature is currently in beta. We invite you to %{linkStart}give feedback%{linkEnd}."
+msgstr ""
+
msgid "This feature requires local storage to be enabled"
msgstr ""
@@ -22571,7 +23309,7 @@ msgstr ""
msgid "This is a Work in Progress"
msgstr ""
-msgid "This is a confidential issue."
+msgid "This is a confidential %{noteableTypeText}."
msgstr ""
msgid "This is a delayed job to run in %{remainingTime}"
@@ -22598,9 +23336,6 @@ msgstr ""
msgid "This is your current session"
msgstr ""
-msgid "This issue is %{confidentialLinkStart}confidential%{linkEnd} and %{lockedLinkStart}locked%{linkEnd}."
-msgstr ""
-
msgid "This issue is confidential"
msgstr ""
@@ -22610,9 +23345,6 @@ msgstr ""
msgid "This issue is in a child epic of the filtered epic"
msgstr ""
-msgid "This issue is locked."
-msgstr ""
-
msgid "This job depends on other jobs with expired/erased artifacts: %{invalid_dependencies}"
msgstr ""
@@ -22712,6 +23444,9 @@ msgstr ""
msgid "This license has already expired."
msgstr ""
+msgid "This link points to external content"
+msgstr ""
+
msgid "This may expose confidential information as the selected fork is in another namespace that can have other members."
msgstr ""
@@ -22739,6 +23474,9 @@ msgstr ""
msgid "This page will be removed in a future release."
msgstr ""
+msgid "This pipeline does not use the %{codeStart}needs%{codeEnd} keyword and can't be represented as a directed acyclic graph."
+msgstr ""
+
msgid "This pipeline makes use of a predefined CI/CD configuration enabled by %{strongStart}Auto DevOps.%{strongEnd}"
msgstr ""
@@ -22967,6 +23705,9 @@ msgstr ""
msgid "Time from last commit to merge"
msgstr ""
+msgid "Time in seconds"
+msgstr ""
+
msgid "Time in seconds GitLab will wait for a response from the external service. When the service does not respond in time, access will be denied."
msgstr ""
@@ -23388,9 +24129,15 @@ msgstr ""
msgid "Total artifacts size: %{total_size}"
msgstr ""
+msgid "Total cores (CPUs)"
+msgstr ""
+
msgid "Total issues"
msgstr ""
+msgid "Total memory (GB)"
+msgstr ""
+
msgid "Total test time for all commits/merges"
msgstr ""
@@ -23592,6 +24339,9 @@ msgstr ""
msgid "U2F only works with HTTPS-enabled websites. Contact your administrator for more details."
msgstr ""
+msgid "UI Development Kit"
+msgstr ""
+
msgid "URL"
msgstr ""
@@ -23664,9 +24414,6 @@ msgstr ""
msgid "Unable to load the merge request widget. Try reloading the page."
msgstr ""
-msgid "Unable to process group import file"
-msgstr ""
-
msgid "Unable to resolve"
msgstr ""
@@ -23682,6 +24429,9 @@ msgstr ""
msgid "Unable to sign you in to the group with SAML due to \"%{reason}\""
msgstr ""
+msgid "Unable to suggest a path. Please refresh and try again."
+msgstr ""
+
msgid "Unable to update label prioritization at this time"
msgstr ""
@@ -23697,6 +24447,9 @@ msgstr ""
msgid "Unarchiving the project will restore people's ability to make changes to it. The repository can be committed to, and issues, comments, and other entities can be created. %{strong_start}Once active, this project shows up in the search and on the dashboard.%{strong_end}"
msgstr ""
+msgid "Unassign from commenting user"
+msgstr ""
+
msgid "Unblock"
msgstr ""
@@ -23772,6 +24525,9 @@ msgstr ""
msgid "Unreachable"
msgstr ""
+msgid "Unrecognized cluster type"
+msgstr ""
+
msgid "Unresolve"
msgstr ""
@@ -23832,6 +24588,9 @@ msgstr ""
msgid "Unsubscribes from this %{quick_action_target}."
msgstr ""
+msgid "Unsupported todo type passed. Supported todo types are: %{todo_types}"
+msgstr ""
+
msgid "Until"
msgstr ""
@@ -23859,6 +24618,9 @@ msgstr ""
msgid "Update approval rule"
msgstr ""
+msgid "Update approvers"
+msgstr ""
+
msgid "Update failed"
msgstr ""
@@ -23868,6 +24630,9 @@ msgstr ""
msgid "Update it"
msgstr ""
+msgid "Update iteration"
+msgstr ""
+
msgid "Update now"
msgstr ""
@@ -23913,7 +24678,7 @@ msgstr ""
msgid "Updated at"
msgstr ""
-msgid "Updated to"
+msgid "Updated to %{linkStart}chart v%{linkEnd}"
msgstr ""
msgid "Updating"
@@ -23943,6 +24708,9 @@ msgstr ""
msgid "Upload CSV file"
msgstr ""
+msgid "Upload License"
+msgstr ""
+
msgid "Upload New File"
msgstr ""
@@ -24024,16 +24792,19 @@ msgstr ""
msgid "UsageQuota|Pipelines"
msgstr ""
+msgid "UsageQuota|Purchase more storage"
+msgstr ""
+
msgid "UsageQuota|Repositories"
msgstr ""
msgid "UsageQuota|Repository"
msgstr ""
-msgid "UsageQuota|Storage"
+msgid "UsageQuota|Snippets"
msgstr ""
-msgid "UsageQuota|Storage usage:"
+msgid "UsageQuota|Storage"
msgstr ""
msgid "UsageQuota|This namespace has no projects which use shared runners"
@@ -24066,6 +24837,12 @@ msgstr ""
msgid "UsageQuota|Wikis"
msgstr ""
+msgid "UsageQuota|You used: %{usage} %{limit}"
+msgstr ""
+
+msgid "UsageQuota|out of %{formattedLimit} of your namespace storage"
+msgstr ""
+
msgid "Use %{code_start}::%{code_end} to create a %{link_start}scoped label set%{link_end} (eg. %{code_start}priority::1%{code_end})"
msgstr ""
@@ -24132,6 +24909,9 @@ msgstr ""
msgid "User IDs"
msgstr ""
+msgid "User Lists can only be created and modified with %{linkStart}the API%{linkEnd}"
+msgstr ""
+
msgid "User OAuth applications"
msgstr ""
@@ -24156,6 +24936,9 @@ msgstr ""
msgid "User key was successfully removed."
msgstr ""
+msgid "User list %{name} will be removed. Are you sure?"
+msgstr ""
+
msgid "User map"
msgstr ""
@@ -24177,148 +24960,10 @@ msgstr ""
msgid "User was successfully updated."
msgstr ""
-msgid "UserOnboardingTour|%{activeTour}/%{totalTours}"
-msgstr ""
-
-msgid "UserOnboardingTour|%{completed}/%{total} steps completed"
-msgstr ""
-
-msgid "UserOnboardingTour|%{emphasisStart}Well done!%{emphasisEnd}%{lineBreak}%{lineBreak}That's it for our guided tour, congratulations for making it all the way to the end!%{lineBreak}%{lineBreak}We hope this gave you a good overview of GitLab and how it can help you. We'll now show you how to create your own project and invite your colleagues."
-msgstr ""
-
-msgid "UserOnboardingTour|Adding other members to a project is done through Project Settings. Click on %{emphasisStart}Settings%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Alright, that's it for Commits. Let's take a look at the %{emphasisStart}Branches%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Awesome! Now click on %{emphasisStart}Members%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Click on one of the %{emphasisStart}Compare%{emphasisEnd} buttons to compare a branch to master."
-msgstr ""
-
-msgid "UserOnboardingTour|Click on one of the %{emphasisStart}pipeline IDs%{emphasisEnd} to see the details of a pipeline."
-msgstr ""
-
-msgid "UserOnboardingTour|Click to open the latest commit to see its details."
-msgstr ""
-
-msgid "UserOnboardingTour|Close 'Learn GitLab'"
-msgstr ""
-
-msgid "UserOnboardingTour|Commits are shown in chronological order and can be filtered by the commit message or by the branch."
+msgid "UserList|Delete %{name}?"
msgstr ""
-msgid "UserOnboardingTour|Create a project"
-msgstr ""
-
-msgid "UserOnboardingTour|Exit 'Learn GitLab'"
-msgstr ""
-
-msgid "UserOnboardingTour|Got it"
-msgstr ""
-
-msgid "UserOnboardingTour|Great job! %{clapHands} We hope the tour was helpful and that you learned how to use GitLab.%{lineBreak}%{lineBreak}We'd love to get your feedback on this tour.%{lineBreak}%{lineBreak}%{emphasisStart}How helpful would you say this guided tour was?%{emphasisEnd}%{lineBreak}%{lineBreak}"
-msgstr ""
-
-msgid "UserOnboardingTour|Guided GitLab Tour"
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can compare the changes of this branch to another one. Changes are divided by files so that it's easier to see what was changed where."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can create a project from scratch, start with a template or import a repository from other platforms. Whatever you choose, we'll guide you through the process.%{lineBreak}%{lineBreak}Fill in your new project information and click on %{emphasisStart}Create Project%{emphasisEnd} to progress to the next step."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can see the breakdown of the pipelines: its stages and jobs in each of the stages and their status.%{lineBreak}%{lineBreak}Our CI/CD pipelines are quite complex, most of our users have fewer and simpler pipelines."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can see the current members of the project (just you at the moment) and invite new members.%{lineBreak}%{lineBreak}You can invite multiple members at once (existing GitLab users or invite by email) and you can also set their roles and permissions.%{lineBreak}%{lineBreak}Add a few members and click on %{emphasisStart}Add to project%{emphasisEnd} to complete this step."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can see what changes were made with this commit, on what branch and if there's a related merge request. The status of the pipeline will also show up if CI/CD is set up.%{lineBreak}%{lineBreak}You can also comment on the lines of code that were changed and start a discussion with your colleagues!"
-msgstr ""
-
-msgid "UserOnboardingTour|Here's an overview of branches in the %{emphasisStart}%{projectName}%{emphasisEnd} project. They're split into Active and Stale.%{lineBreak}%{lineBreak}From here, you can create a new merge request from a branch, or compare the branch to any other branch in the project. By default, it will compare it to the master branch."
-msgstr ""
-
-msgid "UserOnboardingTour|Invite colleagues"
-msgstr ""
-
-msgid "UserOnboardingTour|Issues are great for communicating and keeping track of progress in GitLab. These are all issues that are open in the %{emphasisStart}%{projectName}%{emphasisEnd}.%{lineBreak}%{lineBreak}You can help us improve GitLab by contributing work to issues that are labeled <span class=\"badge color-label accept-mr-label\">Accepting merge requests</span>.%{lineBreak}%{lineBreak}This list can be filtered by labels, milestones, assignees, authors... We'll show you how it looks when the list is filtered by a label."
-msgstr ""
-
-msgid "UserOnboardingTour|Learn GitLab"
-msgstr ""
-
-msgid "UserOnboardingTour|Let's take a closer look at a merge request. Click on the title of one."
-msgstr ""
-
-msgid "UserOnboardingTour|Let's take a closer look at all the commits. Click on %{emphasisStart}Commits%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Let's take a closer look at the repository of this project. Click on %{emphasisStart}Repository%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|No thanks"
-msgstr ""
-
-msgid "UserOnboardingTour|Ok, let's go"
-msgstr ""
-
-msgid "UserOnboardingTour|Ok, show me"
-msgstr ""
-
-msgid "UserOnboardingTour|Open one of the issues by clicking on its title."
-msgstr ""
-
-msgid "UserOnboardingTour|Restart this step"
-msgstr ""
-
-msgid "UserOnboardingTour|Skip this step"
-msgstr ""
-
-msgid "UserOnboardingTour|Sweet! Your project was created and is ready to be used.%{lineBreak}%{lineBreak}You can start adding files to the repository or clone it. One last thing we want to show you is how to invite your colleagues to your new project."
-msgstr ""
-
-msgid "UserOnboardingTour|Take a look. Here's a nifty menu for quickly creating issues, merge requests, snippets, projects and groups. Click on it and select \"New project\" from the \"GitLab\" section to get started."
-msgstr ""
-
-msgid "UserOnboardingTour|Thanks for taking the guided tour. Remember, if you want to go through it again, you can start %{emphasisStart}Learn GitLab%{emphasisEnd} in the help menu on the top right."
-msgstr ""
-
-msgid "UserOnboardingTour|Thanks for the feedback! %{thumbsUp}"
-msgstr ""
-
-msgid "UserOnboardingTour|That's it for issues. Let'st take a look at %{emphasisStart}Merge Requests%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|That's it for merge requests. Now for the final part of this guided tour - the %{emphasisStart}CI/CD%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|That's it for the Repository. Let's take a look at the %{emphasisStart}Issues%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|The structure of this page is very similar to issues. Status, description, discussion and the sidebar are all here.%{lineBreak}%{lineBreak}But take a look below the description and you'll notice that there's more information about the merge request, the CI/CD pipeline and the options for approving it.%{lineBreak}%{lineBreak}Alongside the discussion you can also see more information about commits in this merge request, the status of pipelines and review all changes that were made."
-msgstr ""
-
-msgid "UserOnboardingTour|There's a lot of information here but don't worry, we'll go through it.%{lineBreak}%{lineBreak}On the top you can see the status of the issue and when it was opened and by whom. Directly below it is the issue description and below that are other %{emphasisStart}related issues%{emphasisEnd} and %{emphasisStart}merge requests%{emphasisEnd} (if any). Then below that is the %{emphasisStart}discussion%{emphasisEnd}, that's where most of the communication happens.%{lineBreak}%{lineBreak}On the right, there's a sidebar where you can view/change the %{emphasisStart}assignee, milestone, due date, labels, weight%{emphasisEnd}, etc."
-msgstr ""
-
-msgid "UserOnboardingTour|These are all the CI/CD pipelines we have for our %{emphasisStart}%{projectName}%{emphasisEnd} project.%{lineBreak}%{lineBreak}Here you can see the status of each pipeline, for what commit it's running for, its stages and the status for them."
-msgstr ""
-
-msgid "UserOnboardingTour|These are all the issues that are available for community contributions. Let's take a closer look at one of them."
-msgstr ""
-
-msgid "UserOnboardingTour|This is an overview of all merge requests in this project. Similarly to the issues overview it can be filtered down by things like labels, milestones, authors, assignees, etc."
-msgstr ""
-
-msgid "UserOnboardingTour|This is the repository for the %{emphasisStart}%{projectName}%{emphasisEnd} project. All our code is stored here. Feel free to explore and take a closer look at folders and files.%{lineBreak}%{lineBreak}Above the file structure you can see the latest commit, who the author is and the status of the CI/CD pipeline.%{lineBreak}%{lineBreak}If you scroll down below the file structure, you'll find the Readme of this project. This is defined in the README.md file at the root of the repository."
-msgstr ""
-
-msgid "UserOnboardingTour|Welcome to the project overview of the %{emphasisStart}%{projectName}%{emphasisEnd} project. This is the project that we use to work on GitLab. At first, a project seems like a simple repository, but at GitLab, a project is so much more.%{lineBreak}%{lineBreak}You can create projects for hosting your codebase, use it as an issue tracker, collaborate on code, and continuously build, test, and deploy your app with built-in GitLab CI/CD."
+msgid "UserList|created %{timeago}"
msgstr ""
msgid "UserProfile|Activity"
@@ -24471,6 +25116,9 @@ msgstr ""
msgid "UsersSelect|Unassigned"
msgstr ""
+msgid "Using %{codeStart}needs%{codeEnd} allows jobs to run before their stage is reached, as soon as their individual dependencies are met, which speeds up your pipelines."
+msgstr ""
+
msgid "Using %{code_start}::%{code_end} denotes a %{link_start}scoped label set%{link_end}"
msgstr ""
@@ -24555,9 +25203,6 @@ msgstr ""
msgid "Versions"
msgstr ""
-msgid "Very helpful"
-msgstr ""
-
msgid "View Documentation"
msgstr ""
@@ -24706,6 +25351,9 @@ msgstr ""
msgid "VisibilityLevel|Unknown"
msgstr ""
+msgid "Visit settings page"
+msgstr ""
+
msgid "VisualReviewApp|%{stepStart}Step 1%{stepEnd}. Copy the following script:"
msgstr ""
@@ -24799,6 +25447,9 @@ msgstr ""
msgid "VulnerabilityManagement|Something went wrong while trying to delete the comment. Please try again later."
msgstr ""
+msgid "VulnerabilityManagement|Something went wrong while trying to refresh the vulnerability. Please try again later."
+msgstr ""
+
msgid "VulnerabilityManagement|Something went wrong while trying to retrieve the vulnerability history. Please try again later."
msgstr ""
@@ -24841,6 +25492,9 @@ msgstr ""
msgid "Vulnerability|Class"
msgstr ""
+msgid "Vulnerability|Crash Address"
+msgstr ""
+
msgid "Vulnerability|Description"
msgstr ""
@@ -24916,9 +25570,6 @@ msgstr ""
msgid "We couldn't reach the Prometheus server. Either the server no longer exists or the configuration details need updating."
msgstr ""
-msgid "We created a short guided tour that will help you learn the basics of GitLab and how it will help you be better at your job. It should only take a couple of minutes. You will be guided by two types of helpers, best recognized by their color."
-msgstr ""
-
msgid "We detected potential spam in the %{humanized_resource_name}. Please solve the reCAPTCHA to proceed."
msgstr ""
@@ -24952,6 +25603,9 @@ msgstr ""
msgid "We've found no vulnerabilities"
msgstr ""
+msgid "Web Application Firewall"
+msgstr ""
+
msgid "Web IDE"
msgstr ""
@@ -25012,9 +25666,6 @@ msgstr ""
msgid "Welcome to GitLab.com<br>@%{name}!"
msgstr ""
-msgid "Welcome to the Guided GitLab Tour"
-msgstr ""
-
msgid "Welcome to the guided GitLab tour"
msgstr ""
@@ -25062,9 +25713,6 @@ msgstr ""
msgid "While it's rare to have no vulnerabilities, it can happen. In any event, we ask that you please double check your settings to make sure you've set up your dashboard correctly."
msgstr ""
-msgid "White helpers give contextual information."
-msgstr ""
-
msgid "Who can be an approver?"
msgstr ""
@@ -25110,12 +25758,18 @@ msgstr ""
msgid "WikiEmptyIssueMessage|Suggest wiki improvement"
msgstr ""
+msgid "WikiEmptyIssueMessage|You must be a group member in order to add wiki pages. If you have suggestions for how to improve the wiki for this group, consider opening an issue in the %{issues_link}."
+msgstr ""
+
msgid "WikiEmptyIssueMessage|You must be a project member in order to add wiki pages. If you have suggestions for how to improve the wiki for this project, consider opening an issue in the %{issues_link}."
msgstr ""
msgid "WikiEmptyIssueMessage|issue tracker"
msgstr ""
+msgid "WikiEmpty|A wiki is where you can store all the details about your group. This can include why you've created it, its principles, how to use it, and so on."
+msgstr ""
+
msgid "WikiEmpty|A wiki is where you can store all the details about your project. This can include why you've created it, its principles, how to use it, and so on."
msgstr ""
@@ -25125,12 +25779,21 @@ msgstr ""
msgid "WikiEmpty|Suggest wiki improvement"
msgstr ""
+msgid "WikiEmpty|The wiki lets you write documentation for your group"
+msgstr ""
+
msgid "WikiEmpty|The wiki lets you write documentation for your project"
msgstr ""
+msgid "WikiEmpty|This group has no wiki pages"
+msgstr ""
+
msgid "WikiEmpty|This project has no wiki pages"
msgstr ""
+msgid "WikiEmpty|You must be a group member in order to add wiki pages."
+msgstr ""
+
msgid "WikiEmpty|You must be a project member in order to add wiki pages."
msgstr ""
@@ -25407,13 +26070,13 @@ msgstr ""
msgid "You can now export your security dashboard to a CSV report."
msgstr ""
-msgid "You can now submit a merge request to get this change into the original branch."
+msgid "You can now manage alert endpoint configuration in the Alerts section on the Operations settings page. Fields on this page have been deprecated."
msgstr ""
-msgid "You can now submit a merge request to get this change into the original project."
+msgid "You can now submit a merge request to get this change into the original branch."
msgstr ""
-msgid "You can only add files when you are on a branch"
+msgid "You can now submit a merge request to get this change into the original project."
msgstr ""
msgid "You can only edit files when you are on a branch"
@@ -25635,7 +26298,7 @@ msgstr ""
msgid "You need to upload a Google Takeout archive."
msgstr ""
-msgid "You reached %{usage_in_percent} of %{namespace_name}'s capacity (%{used_storage} of %{storage_limit})"
+msgid "You reached %{usage_in_percent} of %{namespace_name}'s storage capacity (%{used_storage} of %{storage_limit})"
msgstr ""
msgid "You tried to fork %{link_to_the_project} but it failed for the following reason:"
@@ -25938,13 +26601,13 @@ msgstr ""
msgid "Your subscription expired!"
msgstr ""
-msgid "Your subscription has been downgraded"
+msgid "Your subscription has been downgraded."
msgstr ""
-msgid "Your subscription will automatically renew in %{remaining_days}"
+msgid "Your subscription will automatically renew in %{remaining_days}."
msgstr ""
-msgid "Your subscription will expire in %{remaining_days}"
+msgid "Your subscription will expire in %{remaining_days}."
msgstr ""
msgid "Zoom meeting added"
@@ -26024,9 +26687,6 @@ msgstr ""
msgid "branch name"
msgstr ""
-msgid "build pipeline reference mismatch"
-msgstr ""
-
msgid "by"
msgstr ""
@@ -26045,6 +26705,9 @@ msgstr ""
msgid "cannot block others"
msgstr ""
+msgid "cannot contain HTML/XML tags, including any word between angle brackets (<,>)."
+msgstr ""
+
msgid "cannot include leading slash or directory traversal."
msgstr ""
@@ -26075,55 +26738,6 @@ msgstr ""
msgid "ciReport|%{remainingPackagesCount} more"
msgstr ""
-msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability"
-msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability for the source branch only"
-msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities for the source branch only"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerability"
-msgid_plural "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerabilities"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new vulnerability"
-msgid_plural "ciReport|%{reportType} %{status} detected %{newCount} new vulnerabilities"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities for the source branch only"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{fixedCount} fixed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} vulnerability for the source branch only"
-msgid_plural "ciReport|%{reportType} %{status} detected %{newCount} vulnerabilities for the source branch only"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "ciReport|%{reportType} %{status} detected no new vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected no vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected no vulnerabilities for the source branch only"
-msgstr ""
-
msgid "ciReport|%{reportType} is loading"
msgstr ""
@@ -26142,7 +26756,7 @@ msgstr ""
msgid "ciReport|All projects"
msgstr ""
-msgid "ciReport|All report types"
+msgid "ciReport|All scanner types"
msgstr ""
msgid "ciReport|All severities"
@@ -26166,6 +26780,9 @@ msgstr ""
msgid "ciReport|Container scanning detects known vulnerabilities in your docker images."
msgstr ""
+msgid "ciReport|Coverage Fuzzing"
+msgstr ""
+
msgid "ciReport|Create a merge request to implement this solution, or download and apply the patch manually."
msgstr ""
@@ -26324,6 +26941,9 @@ msgstr ""
msgid "customize"
msgstr ""
+msgid "data"
+msgstr ""
+
msgid "date must not be after 9999-12-31"
msgstr ""
@@ -26394,6 +27014,9 @@ msgstr ""
msgid "entries cannot contain HTML tags"
msgstr ""
+msgid "epic"
+msgstr ""
+
msgid "error"
msgstr ""
@@ -26415,9 +27038,6 @@ msgstr ""
msgid "expires on %{timebox_due_date}"
msgstr ""
-msgid "external_url"
-msgstr ""
-
msgid "failed"
msgstr ""
@@ -26453,9 +27073,6 @@ msgstr ""
msgid "from"
msgstr ""
-msgid "geo_node_name"
-msgstr ""
-
msgid "group"
msgstr ""
@@ -26477,9 +27094,6 @@ msgstr ""
msgid "https://your-bitbucket-server"
msgstr ""
-msgid "image"
-msgstr ""
-
msgid "image diff"
msgstr ""
@@ -26623,6 +27237,9 @@ msgstr ""
msgid "load it anyway"
msgstr ""
+msgid "loading"
+msgstr ""
+
msgid "locked by %{path_lock_user_name} %{created_at}"
msgstr ""
@@ -26751,9 +27368,6 @@ msgstr ""
msgid "mrWidget|Deployment statistics are not available currently"
msgstr ""
-msgid "mrWidget|Detect issues before deployment with a CI pipeline that continuously tests your code. We created a quick guide that will show you how to create one. Make your code more secure and more robust in just a minute."
-msgstr ""
-
msgid "mrWidget|Did not close"
msgstr ""
@@ -26916,6 +27530,9 @@ msgstr ""
msgid "mrWidget|To approve this merge request, please enter your password. This project requires all approvals to be authenticated."
msgstr ""
+msgid "mrWidget|Use %{linkStart}CI pipelines to test your code%{linkEnd}, simply add a GitLab CI configuration file to your project. It only takes a minute to make your code more secure and robust."
+msgstr ""
+
msgid "mrWidget|When this merge request is ready, remove the WIP: prefix from the title to allow it to be merged"
msgstr ""
@@ -26952,6 +27569,9 @@ msgstr ""
msgid "must be greater than start date"
msgstr ""
+msgid "must contain only valid frameworks"
+msgstr ""
+
msgid "my-awesome-group"
msgstr ""
@@ -26964,6 +27584,9 @@ msgstr ""
msgid "needs to be between 10 minutes and 1 month"
msgstr ""
+msgid "never"
+msgstr ""
+
msgid "never expires"
msgstr ""
@@ -26991,6 +27614,9 @@ msgstr ""
msgid "nounSeries|%{firstItem} and %{lastItem}"
msgstr ""
+msgid "nounSeries|%{item}"
+msgstr ""
+
msgid "nounSeries|%{item}, %{nextItem}"
msgstr ""
@@ -27127,6 +27753,9 @@ msgstr ""
msgid "revised"
msgstr ""
+msgid "satisfied"
+msgstr ""
+
msgid "score"
msgstr ""
@@ -27157,6 +27786,9 @@ msgstr ""
msgid "severity|Unknown"
msgstr ""
+msgid "should be an array of %{object_name} objects"
+msgstr ""
+
msgid "should be greater than or equal to %{access} inherited membership from group %{group_name}"
msgstr ""
@@ -27211,10 +27843,13 @@ msgstr ""
msgid "suggestPipeline|2/2: Commit your changes"
msgstr ""
-msgid "suggestPipeline|Commit the changes and your pipeline will automatically run for the first time."
+msgid "suggestPipeline|Choose %{boldStart}Code Quality%{boldEnd} to add a pipeline that tests the quality of your code."
msgstr ""
-msgid "suggestPipeline|We recommend the %{boldStart}Code Quality%{boldEnd} template, which will add a report widget to your Merge Requests. This way you’ll learn about code quality degradations much sooner. %{footerStart} Goodbye technical debt! %{footerEnd}"
+msgid "suggestPipeline|The template is ready! You can now commit it to create your first pipeline."
+msgstr ""
+
+msgid "suggestPipeline|We’re adding a GitLab CI configuration file to add a pipeline to the project. You could create it manually, but we recommend that you start with a GitLab template that works out of the box."
msgstr ""
msgid "syntax is correct"
@@ -27274,6 +27909,9 @@ msgstr ""
msgid "uses Kubernetes clusters to deploy your code!"
msgstr ""
+msgid "v%{version} published %{timeAgo}"
+msgstr ""
+
msgid "verify ownership"
msgstr ""
diff --git a/locale/ko/gitlab.po b/locale/ko/gitlab.po
index 3190c685718..406b3a60c60 100644
--- a/locale/ko/gitlab.po
+++ b/locale/ko/gitlab.po
@@ -2,7 +2,7 @@ msgid ""
msgstr ""
"Project-Id-Version: gitlab-ee\n"
"Report-Msgid-Bugs-To: \n"
-"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Last-Translator: \n"
"Language-Team: Korean\n"
"Language: ko_KR\n"
"MIME-Version: 1.0\n"
@@ -10,9 +10,11 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=1; plural=0;\n"
"X-Crowdin-Project: gitlab-ee\n"
+"X-Crowdin-Project-ID: 288872\n"
"X-Crowdin-Language: ko\n"
"X-Crowdin-File: /master/locale/gitlab.pot\n"
-"PO-Revision-Date: 2020-06-08 15:17\n"
+"X-Crowdin-File-ID: 6\n"
+"PO-Revision-Date: 2020-07-02 01:26\n"
msgid " %{start} to %{end}"
msgstr ""
@@ -170,6 +172,10 @@ msgid "%d metric"
msgid_plural "%d metrics"
msgstr[0] "%d 측정치"
+msgid "%d milestone"
+msgid_plural "%d milestones"
+msgstr[0] ""
+
msgid "%d minute"
msgid_plural "%d minutes"
msgstr[0] "%d 분"
@@ -206,10 +212,6 @@ msgid "%d unresolved thread"
msgid_plural "%d unresolved threads"
msgstr[0] ""
-msgid "%d url scanned"
-msgid_plural "%d urls scanned"
-msgstr[0] ""
-
msgid "%d vulnerability"
msgid_plural "%d vulnerabilities"
msgstr[0] ""
@@ -280,9 +282,15 @@ msgstr[0] "%{count}ê°œì˜ ëŒ€ê¸°ì¤‘ì¸ ëŒ“ê¸€ "
msgid "%{count} related %{pluralized_subject}: %{links}"
msgstr "%{count} ê±´ê³¼ ê´€ë ¨ëœ %{pluralized_subject}: %{links}"
+msgid "%{dashboard_path} could not be found."
+msgstr ""
+
msgid "%{days} days until tags are automatically removed"
msgstr ""
+msgid "%{deployLinkStart}Use a template to deploy to ECS%{deployLinkEnd}, or use a docker image to %{commandsLinkStart}run AWS commands in GitLab CI/CD%{commandsLinkEnd}."
+msgstr ""
+
msgid "%{description}- Sentry event: %{errorUrl}- First seen: %{firstSeen}- Last seen: %{lastSeen} %{countLabel}: %{count}%{userCountLabel}: %{userCount}"
msgstr ""
@@ -325,12 +333,18 @@ msgstr "%{group_name}ì€ ê·¸ë£¹ 관리 ê³„ì •ì„ ì‚¬ìš© 합니다. %{group_name}
msgid "%{host} sign-in from new location"
msgstr ""
-msgid "%{icon}You are about to add %{usersTag} people to the discussion. Proceed with caution."
+msgid "%{icon}You are about to add %{usersTag} people to the discussion. They will all receive a notification."
+msgstr ""
+
+msgid "%{integrations_link_start}Integrations%{link_end} enable you to make third-party applications part of your GitLab workflow. If the available integrations don't meet your needs, consider using a %{webhooks_link_start}webhook%{link_end}."
msgstr ""
msgid "%{issuableType} will be removed! Are you sure?"
msgstr "%{issuableType}ì´ ì‚­ì œë©ë‹ˆë‹¤! 확실합니까?"
+msgid "%{issuesCount} issues in this group"
+msgstr ""
+
msgid "%{issuesSize} issues"
msgstr ""
@@ -340,7 +354,7 @@ msgstr ""
msgid "%{labelStart}Class:%{labelEnd} %{class}"
msgstr ""
-msgid "%{labelStart}Confidence:%{labelEnd} %{confidence}"
+msgid "%{labelStart}Evidence:%{labelEnd} %{evidence}"
msgstr ""
msgid "%{labelStart}File:%{labelEnd} %{file}"
@@ -358,6 +372,9 @@ msgstr ""
msgid "%{labelStart}Report Type:%{labelEnd} %{reportType}"
msgstr ""
+msgid "%{labelStart}Scanner:%{labelEnd} %{scanner}"
+msgstr ""
+
msgid "%{labelStart}Severity:%{labelEnd} %{severity}"
msgstr ""
@@ -382,9 +399,6 @@ msgstr ""
msgid "%{link_start}Read more%{link_end} about role permissions"
msgstr "%{link_start}ì—­í•  ê¶Œí•œì— ëŒ€í•´ ìžì„¸ížˆ 알아보기%{link_end}"
-msgid "%{link} can be used for binding events when something is happening within the project."
-msgstr ""
-
msgid "%{listToShow}, and %{awardsListLength} more."
msgstr ""
@@ -418,6 +432,9 @@ msgstr "%{name} í¬í•¨ë¨ %{resultsString}"
msgid "%{name} found %{resultsString}"
msgstr "%{name} 찾았습니다 %{resultsString}"
+msgid "%{name} is already being used for another emoji"
+msgstr ""
+
msgid "%{name} is scheduled for %{action}"
msgstr ""
@@ -464,6 +481,9 @@ msgstr ""
msgid "%{primary} (%{secondary})"
msgstr "%{primary}(%{secondary})"
+msgid "%{ref} cannot be added: %{error}"
+msgstr ""
+
msgid "%{releases} release"
msgid_plural "%{releases} releases"
msgstr[0] "%{releases} 릴리즈"
@@ -471,13 +491,34 @@ msgstr[0] "%{releases} 릴리즈"
msgid "%{remaining_approvals} left"
msgstr ""
-msgid "%{retryButtonStart}Try again%{retryButtonEnd} or %{newFileButtonStart}attach a new file%{newFileButtonEnd}"
+msgid "%{reportType} %{status} detected %{critical} critical and %{high} high severity vulnerabilities out of %{total}."
+msgstr ""
+
+msgid "%{reportType} %{status} detected %{critical} critical and %{high} high severity vulnerabilities."
msgstr ""
-msgid "%{screenreaderOnlyStart}Keyboard shorcuts%{screenreaderOnlyEnd} Disabled"
+msgid "%{reportType} %{status} detected %{critical} critical severity vulnerabilities out of %{total}."
msgstr ""
-msgid "%{screenreaderOnlyStart}Keyboard shorcuts%{screenreaderOnlyEnd} Enabled"
+msgid "%{reportType} %{status} detected %{critical} critical severity vulnerability."
+msgid_plural "%{reportType} %{status} detected %{critical} critical severity vulnerabilities."
+msgstr[0] ""
+
+msgid "%{reportType} %{status} detected %{high} high severity vulnerabilities out of %{total}."
+msgstr ""
+
+msgid "%{reportType} %{status} detected %{high} high severity vulnerability."
+msgid_plural "%{reportType} %{status} detected %{high} high severity vulnerabilities."
+msgstr[0] ""
+
+msgid "%{reportType} %{status} detected %{other} vulnerability."
+msgid_plural "%{reportType} %{status} detected %{other} vulnerabilities."
+msgstr[0] ""
+
+msgid "%{reportType} %{status} detected no new vulnerabilities."
+msgstr ""
+
+msgid "%{retryButtonStart}Try again%{retryButtonEnd} or %{newFileButtonStart}attach a new file%{newFileButtonEnd}"
msgstr ""
msgid "%{service_title} %{message}."
@@ -569,6 +610,12 @@ msgstr "%{title} 변경"
msgid "%{token}..."
msgstr ""
+msgid "%{totalCpu} (%{freeSpacePercentage}%{percentSymbol} free)"
+msgstr ""
+
+msgid "%{totalMemory} (%{freeSpacePercentage}%{percentSymbol} free)"
+msgstr ""
+
msgid "%{totalWeight} total weight"
msgstr ""
@@ -599,6 +646,12 @@ msgstr ""
msgid "%{verb} %{time_spent_value} spent time."
msgstr "%{verb} %{time_spent_value} ì‹œê°„ì´ ì§€ë‚¬ìŠµë‹ˆë‹¤."
+msgid "%{webhooks_link_start}%{webhook_type}%{link_end} enable you to send notifications to web applications in response to events in a group or project."
+msgstr ""
+
+msgid "%{webhooks_link_start}%{webhook_type}%{link_end} enable you to send notifications to web applications in response to events in a group or project. We recommend using an %{integrations_link_start}integration%{link_end} in preference to a webhook."
+msgstr ""
+
msgid "'%{level}' is not a valid visibility level"
msgstr "'%{level}' ìˆ˜ì¤€ì€ ì˜¬ë°”ë¥¸ 공개 ìˆ˜ì¤€ì´ ì•„ë‹™ë‹ˆë‹¤"
@@ -624,9 +677,6 @@ msgstr "(%{mrCount} 병합)"
msgid "(No changes)"
msgstr "(변경사항 ì—†ìŒ)"
-msgid "(Show all)"
-msgstr ""
-
msgid "(check progress)"
msgstr "(진행 ìƒí™© 확ì¸)"
@@ -690,6 +740,9 @@ msgstr "-ëœ ë³´ê¸°"
msgid "0 for unlimited"
msgstr "ë¬´ì œí•œì˜ ê²½ìš° 0"
+msgid "0 for unlimited, only effective with remote storage enabled."
+msgstr ""
+
msgid "1 %{type} addition"
msgid_plural "%{count} %{type} additions"
msgstr[0] "%{count} %{type} ê°œì˜ ì¶”ê°€ ì •ë³´"
@@ -822,6 +875,12 @@ msgstr "<code>\"johnsmith@example.com\": \"johnsm... @ example.com\"</code> ì€
msgid "<code>\"johnsmith@example.com\": \"johnsmith@example.com\"</code> will add \"By <a href=\"#\">johnsmith@example.com</a>\" to all issues and comments originally created by johnsmith@example.com. By default, the email address or username is masked to ensure the user's privacy. Use this option if you want to show the full email address."
msgstr "<code>\"johnsmith@example.com\": \"johnsmith@example.com\"</code> ì€ ì›ëž˜ johnsmith@example.comì´ ìƒì„±í•œ 모든 ì´ìŠˆì™€ 주ì„ì— \"By <a href=\"#\">johnsmith@example.com</a>\"ì„ ì¶”ê°€í•©ë‹ˆë‹¤. 기본ì ìœ¼ë¡œ ì´ë©”ì¼ ì£¼ì†Œ ë˜ëŠ” ì‚¬ìš©ìž ì´ë¦„ì€ ê°€ë ¤ì ¸ 있어서 사용ìžì˜ ê°œì¸ì •ë³´ë¥¼ 보호합니다. ì „ì²´ ì „ìž ë©”ì¼ ì£¼ì†Œë¥¼ 표시하려면 ì´ ì˜µì…˜ì„ ì‚¬ìš©í•˜ì‹­ì‹œì˜¤."
+msgid "<code>Masked</code> to prevent the values from being displayed in job logs (must match certain regexp requirements)."
+msgstr ""
+
+msgid "<code>Protected</code> to expose them to protected branches or tags only."
+msgstr ""
+
msgid "<namespace / project>"
msgstr ""
@@ -849,9 +908,6 @@ msgstr "'러너(Runner)'는 ìž‘ì—…ì„ ì‹¤í–‰í•˜ëŠ” 프로세스입니다. í•„ìš”
msgid "A .NET Core console application template, customizable for any .NET Core project"
msgstr ".NET 코어 콘솔 ì‘ìš© 프로그램 템플릿, 모든 .NET 코어 프로ì íŠ¸ì— 맞게 ì‚¬ìš©ìž ì •ì˜ ê°€ëŠ¥"
-msgid "A DAG must have two dependent jobs to be visualized on this tab."
-msgstr ""
-
msgid "A GitBook site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
msgstr ""
@@ -882,6 +938,12 @@ msgstr "빈 프로ì íŠ¸ì—서는 기본 브랜치를 ì„ íƒí•  수 없습니다
msgid "A deleted user"
msgstr "ì‚­ì œëœ ì‚¬ìš©ìž"
+msgid "A file has been changed."
+msgstr ""
+
+msgid "A file was not found."
+msgstr ""
+
msgid "A file with '%{file_name}' already exists in %{branch} branch"
msgstr ""
@@ -948,7 +1010,7 @@ msgstr ""
msgid "A subscription will trigger a new pipeline on the default branch of this project when a pipeline successfully completes for a new tag on the %{default_branch_docs} of the subscribed project."
msgstr ""
-msgid "A terraform report was generated in your pipelines."
+msgid "A suggestion is not applicable."
msgstr ""
msgid "A user with write access to the source branch selected this option"
@@ -1119,6 +1181,9 @@ msgstr "계정: %{account}"
msgid "Action to take when receiving an alert."
msgstr "ê²½ê³ ë¥¼ë°›ì„ ë•Œ 취할 조치."
+msgid "Actions"
+msgstr ""
+
msgid "Activate"
msgstr ""
@@ -1174,6 +1239,9 @@ msgstr "Kubernetes í´ëŸ¬ìŠ¤í„° 추가"
msgid "Add LICENSE"
msgstr ""
+msgid "Add New Node"
+msgstr ""
+
msgid "Add README"
msgstr "README 추가"
@@ -1249,6 +1317,9 @@ msgstr ""
msgid "Add approval rule"
msgstr ""
+msgid "Add approvers"
+msgstr ""
+
msgid "Add bold text"
msgstr "êµµì€ ë¬¸ìžì—´ 추가"
@@ -1306,6 +1377,9 @@ msgstr ""
msgid "Add strikethrough text"
msgstr ""
+msgid "Add suggestion to batch"
+msgstr ""
+
msgid "Add system hook"
msgstr ""
@@ -1405,6 +1479,9 @@ msgstr "ê´€ë¦¬ìž ì˜ì—­"
msgid "Admin Note"
msgstr ""
+msgid "Admin Notifications"
+msgstr ""
+
msgid "Admin Overview"
msgstr "ê´€ë¦¬ìž ê°œìš”"
@@ -1516,6 +1593,9 @@ msgstr ""
msgid "AdminSettings|Integrations configured here will automatically apply to all projects on this instance."
msgstr ""
+msgid "AdminSettings|Moved to integrations"
+msgstr ""
+
msgid "AdminSettings|No required pipeline"
msgstr ""
@@ -1745,12 +1825,18 @@ msgid "Alert"
msgid_plural "Alerts"
msgstr[0] "알림"
+msgid "AlertManagement| assign yourself"
+msgstr ""
+
msgid "AlertManagement|Acknowledged"
msgstr ""
msgid "AlertManagement|Alert"
msgstr ""
+msgid "AlertManagement|Alert assignee(s): %{assignees}"
+msgstr ""
+
msgid "AlertManagement|Alert detail"
msgstr ""
@@ -1766,9 +1852,15 @@ msgstr ""
msgid "AlertManagement|All alerts"
msgstr ""
+msgid "AlertManagement|Assign To"
+msgstr ""
+
msgid "AlertManagement|Assign status"
msgstr ""
+msgid "AlertManagement|Assignee"
+msgstr ""
+
msgid "AlertManagement|Assignees"
msgstr ""
@@ -1787,9 +1879,6 @@ msgstr ""
msgid "AlertManagement|Edit"
msgstr ""
-msgid "AlertManagement|End time"
-msgstr ""
-
msgid "AlertManagement|Events"
msgstr ""
@@ -1808,10 +1897,13 @@ msgstr ""
msgid "AlertManagement|More information"
msgstr ""
+msgid "AlertManagement|No Matching Results"
+msgstr ""
+
msgid "AlertManagement|No alert data to display."
msgstr ""
-msgid "AlertManagement|No alerts available to display. If you think you're seeing this message in error, refresh the page."
+msgid "AlertManagement|No alerts available to display. See %{linkStart}enabling alert management%{linkEnd} for more information on adding alerts to the list."
msgstr ""
msgid "AlertManagement|No alerts to display."
@@ -1820,6 +1912,9 @@ msgstr ""
msgid "AlertManagement|None"
msgstr ""
+msgid "AlertManagement|None -"
+msgstr ""
+
msgid "AlertManagement|Open"
msgstr ""
@@ -1856,9 +1951,18 @@ msgstr ""
msgid "AlertManagement|There was an error displaying the alerts. Confirm your endpoint's configuration details to ensure alerts appear."
msgstr ""
+msgid "AlertManagement|There was an error while updating the assignee(s) list. Please try again."
+msgstr ""
+
+msgid "AlertManagement|There was an error while updating the assignee(s) of the alert. Please try again."
+msgstr ""
+
msgid "AlertManagement|There was an error while updating the status of the alert. Please try again."
msgstr ""
+msgid "AlertManagement|This assignee cannot be assigned to this alert."
+msgstr ""
+
msgid "AlertManagement|Tool"
msgstr ""
@@ -2006,6 +2110,9 @@ msgstr ""
msgid "Allow users to request access (if visibility is public or internal)"
msgstr ""
+msgid "Allowed Geo IP"
+msgstr ""
+
msgid "Allowed email domain restriction only permitted for top-level groups"
msgstr ""
@@ -2039,6 +2146,9 @@ msgstr ""
msgid "Amazon Web Services"
msgstr ""
+msgid "Amazon Web Services Logo"
+msgstr ""
+
msgid "Amazon authentication is not %{link_start}correctly configured%{link_end}. Ask your GitLab administrator if you want to use this service."
msgstr ""
@@ -2096,10 +2206,16 @@ msgstr ""
msgid "An error occurred when updating the issue weight"
msgstr "ì´ìŠˆ ì¤‘ìš”ë„ ì—…ë°ì´íŠ¸ 중 문제가 ë°œìƒí–ˆìŠµë‹ˆë‹¤."
+msgid "An error occurred while acknowledging the notification. Refresh the page and try again."
+msgstr ""
+
+msgid "An error occurred while adding approvers"
+msgstr ""
+
msgid "An error occurred while adding formatted title for epic"
msgstr ""
-msgid "An error occurred while checking group path"
+msgid "An error occurred while checking group path. Please refresh and try again."
msgstr ""
msgid "An error occurred while committing your changes."
@@ -2243,7 +2359,7 @@ msgstr ""
msgid "An error occurred while loading milestones"
msgstr ""
-msgid "An error occurred while loading terraform report"
+msgid "An error occurred while loading project creation UI"
msgstr ""
msgid "An error occurred while loading the data. Please try again."
@@ -2297,6 +2413,9 @@ msgstr "ì´ìŠˆë¥¼ 삭제하는 ë„중 오류가 ë°œìƒí–ˆìŠµë‹ˆë‹¤."
msgid "An error occurred while rendering preview broadcast message"
msgstr "방송 메시지 미리보기를 ë Œë”ë§í•˜ëŠ” 중 오류가 ë°œìƒí–ˆìŠµë‹ˆë‹¤."
+msgid "An error occurred while rendering the editor"
+msgstr ""
+
msgid "An error occurred while reordering issues."
msgstr ""
@@ -2393,7 +2512,7 @@ msgstr ""
msgid "Analyze your dependencies for known vulnerabilities."
msgstr ""
-msgid "Analyze your source code and git history for secrets"
+msgid "Analyze your source code and git history for secrets."
msgstr ""
msgid "Analyze your source code for known vulnerabilities."
@@ -2507,12 +2626,18 @@ msgstr ""
msgid "Apply suggestion"
msgstr "제안 ì ìš©"
+msgid "Apply suggestions"
+msgstr ""
+
msgid "Apply template"
msgstr ""
msgid "Apply this approval rule to any branch or a specific protected branch."
msgstr ""
+msgid "Applying"
+msgstr ""
+
msgid "Applying a template will replace the existing issue description. Any changes you have made will be lost."
msgstr ""
@@ -2525,12 +2650,18 @@ msgstr ""
msgid "Applying multiple commands"
msgstr ""
-msgid "Applying suggestion"
+msgid "Applying suggestion..."
+msgstr ""
+
+msgid "Applying suggestions..."
msgstr ""
msgid "Approval rules"
msgstr ""
+msgid "Approval rules reset to project defaults"
+msgstr ""
+
msgid "ApprovalRuleRemove|%d member"
msgid_plural "ApprovalRuleRemove|%d members"
msgstr[0] ""
@@ -2580,6 +2711,9 @@ msgstr ""
msgid "Approved"
msgstr ""
+msgid "Approved MRs"
+msgstr ""
+
msgid "Approved by: "
msgstr ""
@@ -2793,6 +2927,9 @@ msgstr ""
msgid "Assign"
msgstr ""
+msgid "Assign Iteration"
+msgstr ""
+
msgid "Assign custom color like #FF0000"
msgstr "#FF0000ê³¼ ê°™ì´ ë§žì¶¤ ìƒ‰ìƒ ì§€ì •"
@@ -2811,6 +2948,9 @@ msgstr "ì´ ë§ˆì¼ë“œìŠ¤í†¤ì— ì´ìŠˆë¥¼ 할당합니다."
msgid "Assign to"
msgstr "담당ìž"
+msgid "Assign to commenting user"
+msgstr ""
+
msgid "Assign yourself to these issues"
msgstr ""
@@ -2836,6 +2976,9 @@ msgid "Assignee"
msgid_plural "%d Assignees"
msgstr[0] ""
+msgid "Assignee has no permissions"
+msgstr ""
+
msgid "Assignee lists not available with your current license"
msgstr "ë‹´ë‹¹ìž ëª©ë¡ì€ 현재 ë¼ì´ì„¼ìŠ¤ë¡œëŠ” 사용할 수 없습니다."
@@ -2845,6 +2988,9 @@ msgstr "ë‹´ë‹¹ìž ëª©ë¡ì€ ì„ íƒëœ 사용ìžì—게 할당 ëœ ëª¨ë“  ì´ìŠˆê°
msgid "Assignee(s)"
msgstr "담당ìž"
+msgid "Assignees"
+msgstr ""
+
msgid "Assigns %{assignee_users_sentence}."
msgstr ""
@@ -2879,6 +3025,9 @@ msgstr "ê°ì‚¬ ì´ë²¤íŠ¸"
msgid "Audit Events is a way to keep track of important events that happened in GitLab."
msgstr ""
+msgid "Audit Log"
+msgstr ""
+
msgid "AuditEvents|(removed)"
msgstr ""
@@ -2915,6 +3064,9 @@ msgstr ""
msgid "AuditLogs|IP Address"
msgstr ""
+msgid "AuditLogs|Member Events"
+msgstr ""
+
msgid "AuditLogs|No matching %{type} found."
msgstr ""
@@ -3197,9 +3349,6 @@ msgstr "내 배지"
msgid "Badges|e.g. %{exampleUrl}"
msgstr "e.g. %{exampleUrl}"
-msgid "Badge|New"
-msgstr "배지 | 신규"
-
msgid "Balsamiq file could not be loaded."
msgstr "Balsamiq 파ì¼ì„ 로드하지 못했습니다."
@@ -3248,6 +3397,12 @@ msgstr ""
msgid "Below you will find all the groups that are public."
msgstr "공개 ëœ ëª¨ë“  ê·¸ë£¹ì„ ì•„ëž˜ì—ì„œ ì°¾ì„ ìˆ˜ 있습니다."
+msgid "Beta"
+msgstr ""
+
+msgid "Bi-weekly code coverage"
+msgstr ""
+
msgid "Billing"
msgstr "결제"
@@ -3308,9 +3463,15 @@ msgstr ""
msgid "Bitbucket Server Import"
msgstr "Bitbucket 서버ì—ì„œ 가져 오기"
+msgid "Bitbucket Server import"
+msgstr ""
+
msgid "Bitbucket import"
msgstr "Bitbucketì—ì„œ 가져오기"
+msgid "Blame"
+msgstr ""
+
msgid "Blocked"
msgstr "차단ë¨"
@@ -3323,9 +3484,6 @@ msgstr ""
msgid "Blog"
msgstr "블로그"
-msgid "Blue helpers indicate an action to be taken."
-msgstr ""
-
msgid "Board name"
msgstr "ë³´ë“œ ì´ë¦„"
@@ -3524,6 +3682,9 @@ msgstr ""
msgid "Broadcast Message was successfully updated."
msgstr ""
+msgid "Broadcast Messages"
+msgstr ""
+
msgid "Browse Directory"
msgstr "디렉토리 찾아보기"
@@ -3578,6 +3739,9 @@ msgstr ""
msgid "By %{user_name}"
msgstr "%{user_name} ì˜í•´ì„œ"
+msgid "By URL"
+msgstr ""
+
msgid "By default GitLab sends emails in HTML and plain text formats so mail clients can choose what format to use. Disable this option if you only want to send emails in plain text format."
msgstr ""
@@ -3683,6 +3847,9 @@ msgstr ""
msgid "Can override approvers and approvals required per merge request"
msgstr "승ì¸ìžë¥¼ ë®ì–´ì“¸ 수 있으며, 매 머지 리퀘스트 마다 승ì¸ì´ 필요합니다."
+msgid "Can't apply as this line has changed or the suggestion already matches its content."
+msgstr ""
+
msgid "Can't create snippet: %{err}"
msgstr ""
@@ -3869,9 +4036,6 @@ msgstr "변경 ì‚¬í•­ì€ <b>source</b> ë¦¬ë¹„ì „ì´ <b>target</b> ë¦¬ë¹„ì „ì— ë
msgid "Changes are still tracked. Useful for cluster/index migrations."
msgstr ""
-msgid "Changes are unknown"
-msgstr ""
-
msgid "Changes suppressed. Click to show."
msgstr "ì—…ë°ì´íŠ¸ê°€ 반려ë˜ì—ˆìŠµë‹ˆë‹¤. í´ë¦­í•˜ì—¬ 확ì¸í•©ë‹ˆë‹¤."
@@ -4178,15 +4342,9 @@ msgstr "공개 ìˆ˜ì¤€ì„ ì •í•˜ê³ , 프로ì íŠ¸ ê¸°ëŠ¥ì„ (ì´ìŠˆ, 저장소, ì
msgid "Choose what content you want to see on a group’s overview page"
msgstr "해당 ê·¸ë£¹ì˜ ê°œìš” 페ì´ì§€ì—ì„œ ë³´ê³  ì‹¶ì€ ë¶€ë¶„ì„ ê³ ë¥´ì„¸ìš”."
-msgid "Choose which groups you wish to synchronize to this secondary node"
-msgstr ""
-
msgid "Choose which repositories you want to connect and run CI/CD pipelines."
msgstr "Ci/CD 파ì´í”„ë¼ì¸ì´ ì—°ê²°ë˜ê³  실행할 저장소를 ì„ íƒí•´ 주세요."
-msgid "Choose which shards you wish to synchronize to this secondary node"
-msgstr ""
-
msgid "Choose your framework"
msgstr ""
@@ -4484,9 +4642,15 @@ msgstr ""
msgid "Cluster does not exist"
msgstr ""
+msgid "Cluster is required for Stages::ClusterEndpointInserter"
+msgstr ""
+
msgid "Cluster level"
msgstr ""
+msgid "Cluster type must be specificed for Stages::ClusterEndpointInserter"
+msgstr ""
+
msgid "ClusterIntegration| %{custom_domain_start}More information%{custom_domain_end}."
msgstr "%{custom_domain_start}추가 정보%{custom_domain_end}"
@@ -4598,7 +4762,7 @@ msgstr "CA ì¸ì¦ì„œ"
msgid "ClusterIntegration|Cert-Manager"
msgstr ""
-msgid "ClusterIntegration|Cert-Manager is a native Kubernetes certificate management controller that helps with issuing certificates. Installing Cert-Manager on your cluster will issue a certificate by %{letsEncrypt} and ensure that certificates are valid and up-to-date."
+msgid "ClusterIntegration|Cert-Manager is a native Kubernetes certificate management controller that helps with issuing certificates. Installing Cert-Manager on your cluster will issue a certificate by %{linkStart}Let's Encrypt%{linkEnd} and ensure that certificates are valid and up-to-date."
msgstr ""
msgid "ClusterIntegration|Certificate Authority bundle (PEM format)"
@@ -4625,9 +4789,6 @@ msgstr ""
msgid "ClusterIntegration|Clear the local cache of namespace and service accounts. This is necessary if your integration has become out of sync. The cache is repopulated during the next CI job that requires namespace and service accounts."
msgstr ""
-msgid "ClusterIntegration|Cloud Run"
-msgstr ""
-
msgid "ClusterIntegration|Cluster being created"
msgstr ""
@@ -4712,7 +4873,7 @@ msgstr ""
msgid "ClusterIntegration|Crossplane"
msgstr ""
-msgid "ClusterIntegration|Crossplane enables declarative provisioning of managed services from your cloud of choice using %{codeStart}kubectl%{codeEnd} or %{gitlabIntegrationLink}. Crossplane runs inside your Kubernetes cluster and supports secure connectivity and secrets management between app containers and the cloud services they depend on."
+msgid "ClusterIntegration|Crossplane enables declarative provisioning of managed services from your cloud of choice using %{codeStart}kubectl%{codeEnd} or %{linkStart}GitLab Integration%{linkEnd}. Crossplane runs inside your Kubernetes cluster and supports secure connectivity and secrets management between app containers and the cloud services they depend on."
msgstr ""
msgid "ClusterIntegration|Deletes all GitLab resources attached to this cluster during removal"
@@ -4796,9 +4957,6 @@ msgstr ""
msgid "ClusterIntegration|GitLab-managed cluster"
msgstr ""
-msgid "ClusterIntegration|Gitlab Integration"
-msgstr ""
-
msgid "ClusterIntegration|Global default"
msgstr ""
@@ -4844,7 +5002,7 @@ msgstr ""
msgid "ClusterIntegration|Ingress gives you a way to route requests to services based on the request host or path, centralizing a number of services into a single entrypoint."
msgstr ""
-msgid "ClusterIntegration|Installing Ingress may incur additional costs. Learn more about %{pricingLink}."
+msgid "ClusterIntegration|Installing Ingress may incur additional costs. Learn more about %{linkStart}pricing%{linkEnd}."
msgstr ""
msgid "ClusterIntegration|Instance cluster"
@@ -4859,7 +5017,7 @@ msgstr "Kubernetes í´ëŸ¬ìŠ¤í„° ìžë™í™” 통합"
msgid "ClusterIntegration|Issuer Email"
msgstr ""
-msgid "ClusterIntegration|Issuers represent a certificate authority. You must provide an email address for your Issuer. "
+msgid "ClusterIntegration|Issuers represent a certificate authority. You must provide an email address for your Issuer."
msgstr ""
msgid "ClusterIntegration|Jupyter Hostname"
@@ -4931,9 +5089,6 @@ msgstr "그룹 Kubernetes í´ëŸ¬ìŠ¤í„°ì— 대해 ìžì„¸ížˆ 알아보기."
msgid "ClusterIntegration|Learn more about instance Kubernetes clusters"
msgstr ""
-msgid "ClusterIntegration|Let's Encrypt"
-msgstr "Let's Encrypt"
-
msgid "ClusterIntegration|Loading IAM Roles"
msgstr ""
@@ -5045,7 +5200,7 @@ msgstr ""
msgid "ClusterIntegration|Prometheus"
msgstr "Prometheus"
-msgid "ClusterIntegration|Prometheus is an open-source monitoring system with %{gitlabIntegrationLink} to monitor deployed applications."
+msgid "ClusterIntegration|Prometheus is an open-source monitoring system with %{linkStart}GitLab Integration%{linkEnd} to monitor deployed applications."
msgstr ""
msgid "ClusterIntegration|Provider details"
@@ -5201,10 +5356,10 @@ msgstr "ClusterIntegration|지역 ì„ íƒ"
msgid "ClusterIntegration|Select zone to choose machine type"
msgstr ""
-msgid "ClusterIntegration|Send Cilium Logs"
+msgid "ClusterIntegration|Send Container Network Policies Logs"
msgstr ""
-msgid "ClusterIntegration|Send ModSecurity Logs"
+msgid "ClusterIntegration|Send Web Application Firewall Logs"
msgstr ""
msgid "ClusterIntegration|Service Token"
@@ -5372,15 +5527,12 @@ msgstr "구글 Kubernetes 컨테ì´ë„ˆ ì—”ì§„ì— ì—‘ì„¸ìŠ¤"
msgid "ClusterIntegration|documentation"
msgstr "문서"
-msgid "ClusterIntegration|installed via %{installed_via}"
+msgid "ClusterIntegration|installed via %{linkStart}Cloud Run%{linkEnd}"
msgstr ""
msgid "ClusterIntegration|meets the requirements"
msgstr "요구 ì‚¬í•­ì„ ì¶©ì¡±"
-msgid "ClusterIntegration|pricing"
-msgstr ""
-
msgid "ClusterIntegration|sign up"
msgstr "ClusterIntegration|가입"
@@ -5417,6 +5569,15 @@ msgstr ""
msgid "Code"
msgstr "코드"
+msgid "Code Coverage: %{coveragePercentage}%{percentSymbol}"
+msgstr ""
+
+msgid "Code Coverage| Empty code coverage data"
+msgstr ""
+
+msgid "Code Coverage|Couldn't fetch the code coverage data"
+msgstr ""
+
msgid "Code Owners"
msgstr "코드 소유ìž"
@@ -5471,7 +5632,7 @@ msgstr "ê°ì¶”기"
msgid "Collapse approvers"
msgstr ""
-msgid "Collapse child epics"
+msgid "Collapse milestones"
msgstr ""
msgid "Collapse replies"
@@ -5489,6 +5650,9 @@ msgstr ""
msgid "Coming soon"
msgstr ""
+msgid "Comma-separated, e.g. '1.1.1.1, 2.2.2.0/24'"
+msgstr ""
+
msgid "Command"
msgstr ""
@@ -5658,6 +5822,12 @@ msgstr ""
msgid "Compliance framework (optional)"
msgstr ""
+msgid "Compliance frameworks"
+msgstr ""
+
+msgid "ComplianceDashboard|created by:"
+msgstr ""
+
msgid "ComplianceFramework|GDPR"
msgstr ""
@@ -5796,6 +5966,9 @@ msgstr "연결 실패"
msgid "Connection timed out"
msgstr ""
+msgid "Connection timeout"
+msgstr ""
+
msgid "Contact sales to upgrade"
msgstr "업그레ì´ë“œ 위해 ì˜ì—…íŒ€ì— ë¬¸ì˜"
@@ -5849,9 +6022,6 @@ msgstr ""
msgid "ContainerRegistry|CLI Commands"
msgstr ""
-msgid "ContainerRegistry|Compressed Size"
-msgstr ""
-
msgid "ContainerRegistry|Container Registry"
msgstr ""
@@ -5867,6 +6037,9 @@ msgstr ""
msgid "ContainerRegistry|Copy push command"
msgstr ""
+msgid "ContainerRegistry|Delete selected"
+msgstr ""
+
msgid "ContainerRegistry|Docker connection error"
msgstr ""
@@ -5900,16 +6073,16 @@ msgstr ""
msgid "ContainerRegistry|If you are not already logged in, you need to authenticate to the Container Registry by using your GitLab username and password. If you have %{twofaDocLinkStart}Two-Factor Authentication%{twofaDocLinkEnd} enabled, use a %{personalAccessTokensDocLinkStart}Personal Access Token%{personalAccessTokensDocLinkEnd} instead of a password."
msgstr ""
-msgid "ContainerRegistry|Image ID"
+msgid "ContainerRegistry|Image ID: %{imageId}"
msgstr ""
msgid "ContainerRegistry|Image Repositories"
msgstr ""
-msgid "ContainerRegistry|Keep and protect the images that matter most."
+msgid "ContainerRegistry|Image tags"
msgstr ""
-msgid "ContainerRegistry|Last Updated"
+msgid "ContainerRegistry|Keep and protect the images that matter most."
msgstr ""
msgid "ContainerRegistry|Login"
@@ -5924,6 +6097,9 @@ msgstr ""
msgid "ContainerRegistry|Please contact your administrator."
msgstr ""
+msgid "ContainerRegistry|Published %{timeInfo}"
+msgstr ""
+
msgid "ContainerRegistry|Push an image"
msgstr ""
@@ -5939,9 +6115,6 @@ msgstr ""
msgid "ContainerRegistry|Remove repository"
msgstr "저장소 제거"
-msgid "ContainerRegistry|Remove selected tags"
-msgstr ""
-
msgid "ContainerRegistry|Remove tag"
msgid_plural "ContainerRegistry|Remove tags"
msgstr[0] ""
@@ -5970,9 +6143,6 @@ msgstr ""
msgid "ContainerRegistry|Sorry, your filter produced no results."
msgstr ""
-msgid "ContainerRegistry|Tag"
-msgstr "태그"
-
msgid "ContainerRegistry|Tag expiration policy"
msgstr ""
@@ -6117,21 +6287,6 @@ msgstr ""
msgid "Control the display of third party offers."
msgstr ""
-msgid "Control the maximum concurrency of LFS/attachment backfill for this secondary node"
-msgstr ""
-
-msgid "Control the maximum concurrency of container repository operations for this Geo node"
-msgstr ""
-
-msgid "Control the maximum concurrency of repository backfill for this secondary node"
-msgstr ""
-
-msgid "Control the maximum concurrency of verification operations for this Geo node"
-msgstr ""
-
-msgid "Control the minimum interval in days that a repository should be reverified for this primary node"
-msgstr ""
-
msgid "Cookie domain"
msgstr ""
@@ -6273,6 +6428,9 @@ msgstr "채팅 닉네임 %{chat_name}(ì„)를 삭제할 수 없습니다."
msgid "Could not find design."
msgstr ""
+msgid "Could not find iteration"
+msgstr ""
+
msgid "Could not remove the trigger."
msgstr ""
@@ -6309,6 +6467,12 @@ msgstr ""
msgid "Coverage"
msgstr ""
+msgid "Coverage Fuzzing"
+msgstr ""
+
+msgid "Crash State"
+msgstr ""
+
msgid "Create"
msgstr "만들기"
@@ -6357,6 +6521,9 @@ msgstr "새 저장소 만들기"
msgid "Create a personal access token on your account to pull or push via %{protocol}."
msgstr "%{protocol}ì„ (를) 통해 Pull 하거나 Push í•  ê°œì¸ ì•¡ì„¸ìŠ¤ 토í°ì„ 만드십시오."
+msgid "Create a project pre-populated with the necessary files to get you started quickly."
+msgstr ""
+
msgid "Create an account using:"
msgstr ""
@@ -6525,6 +6692,9 @@ msgstr ""
msgid "Creates branch '%{branch_name}' and a merge request to resolve this issue."
msgstr ""
+msgid "Creating"
+msgstr ""
+
msgid "Creating epic"
msgstr "ì—픽 ìƒì„± 중"
@@ -6576,7 +6746,10 @@ msgstr "현재 비밀번호"
msgid "Current vulnerabilities count"
msgstr ""
-msgid "CurrentUser|Buy CI minutes"
+msgid "CurrentUser|Buy Pipeline minutes"
+msgstr ""
+
+msgid "CurrentUser|One of your groups is running out"
msgstr ""
msgid "CurrentUser|Profile"
@@ -6591,6 +6764,9 @@ msgstr ""
msgid "CurrentUser|Upgrade"
msgstr ""
+msgid "Custom Attributes"
+msgstr ""
+
msgid "Custom CI configuration path"
msgstr ""
@@ -6853,12 +7029,18 @@ msgstr ""
msgid "DAG"
msgstr ""
+msgid "DAG visualization requires at least 3 dependent jobs."
+msgstr ""
+
msgid "DNS"
msgstr "DNS"
msgid "Dashboard"
msgstr "대시보드"
+msgid "Dashboard uid not found"
+msgstr ""
+
msgid "DashboardProjects|All"
msgstr "모든"
@@ -6883,6 +7065,9 @@ msgstr ""
msgid "Data is still calculating..."
msgstr "ë°ì´í„°ê°€ ì•„ì§ ê³„ì‚°ì¤‘ìž…ë‹ˆë‹¤..."
+msgid "Datasource name not found"
+msgstr ""
+
msgid "Date"
msgstr ""
@@ -7063,6 +7248,9 @@ msgstr "소스 브랜치 삭제"
msgid "Delete this attachment"
msgstr ""
+msgid "Delete user list"
+msgstr ""
+
msgid "Delete variable"
msgstr ""
@@ -7374,6 +7562,9 @@ msgstr "ë°°í¬ë¨"
msgid "Deploying to"
msgstr "다ìŒì— ë°°í¬ì¤‘: "
+msgid "Deploying to AWS is easy with GitLab"
+msgstr ""
+
msgid "Deployment Frequency"
msgstr ""
@@ -7735,6 +7926,9 @@ msgstr ""
msgid "Dismissed on pipeline %{pipelineLink} at %{projectLink}"
msgstr ""
+msgid "Display alerts from all your monitoring tools directly within GitLab."
+msgstr ""
+
msgid "Display name"
msgstr ""
@@ -7792,9 +7986,6 @@ msgstr ""
msgid "Don't show again"
msgstr "다시 표시하지 ì•ŠìŒ"
-msgid "Don't worry, you can access this tour by clicking on the help icon in the top right corner and choose <strong>Learn GitLab</strong>."
-msgstr ""
-
msgid "Done"
msgstr "완료"
@@ -7921,6 +8112,9 @@ msgstr "파ì´í”„ë¼ì¸ 스케줄 편집 %{id}"
msgid "Edit Release"
msgstr ""
+msgid "Edit Slack integration"
+msgstr ""
+
msgid "Edit Snippet"
msgstr "스니펫 편집"
@@ -7960,6 +8154,9 @@ msgstr "%{user_name}ì˜ ì‹ ì› íŽ¸ì§‘"
msgid "Edit issues"
msgstr "ì´ìŠˆ 편집"
+msgid "Edit iteration"
+msgstr ""
+
msgid "Edit public deploy key"
msgstr "공개 ë°°í¬ í‚¤ 편집"
@@ -8008,6 +8205,9 @@ msgstr ""
msgid "Email"
msgstr "ì´ë©”ì¼"
+msgid "Email Notification"
+msgstr ""
+
msgid "Email address"
msgstr "ì´ë©”ì¼ ì£¼ì†Œ"
@@ -8020,6 +8220,9 @@ msgstr ""
msgid "Email not verified. Please verify your email in Salesforce."
msgstr ""
+msgid "Email notification for unknown sign-ins"
+msgstr ""
+
msgid "Email patch"
msgstr "패치를 ì´ë©”ì¼ë¡œ 보냄"
@@ -8251,6 +8454,9 @@ msgstr "(UTC)ì— ì¢…ë£Œë©ë‹ˆë‹¤"
msgid "Enforce DNS rebinding attack protection"
msgstr ""
+msgid "Enforce personal access token expiration"
+msgstr ""
+
msgid "Ensure connectivity is available from the GitLab server to the Prometheus server"
msgstr ""
@@ -8320,6 +8526,9 @@ msgstr "머지 리퀘스트(MR) ì œëª©ì„ ìž…ë ¥ 해주세요"
msgid "Enter the name of your application, and we'll return a unique %{type}."
msgstr ""
+msgid "Enter weights for storages for new repositories."
+msgstr ""
+
msgid "Enter your password to approve"
msgstr ""
@@ -8329,15 +8538,27 @@ msgstr ""
msgid "Environment does not have deployments"
msgstr ""
+msgid "Environment is required for Stages::MetricEndpointInserter"
+msgstr ""
+
+msgid "Environment is required for Stages::VariableEndpointInserter"
+msgstr ""
+
msgid "Environment scope"
msgstr ""
+msgid "Environment variables are applied to all project environments in this instance via the Runner. You can use environment variables for passwords, secret keys, etc. Make variables available to the running application by prepending the variable key with <code>K8S_SECRET_</code>. You can set variables to be:"
+msgstr ""
+
msgid "Environment variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. Additionally, they can be masked so they are hidden in job logs, though they must match certain regexp requirements to do so. You can use environment variables for passwords, secret keys, or whatever you want."
msgstr ""
msgid "Environment variables are configured by your administrator to be %{link_start}protected%{link_end} by default"
msgstr ""
+msgid "Environment variables on this GitLab instance are configured to be %{link_start}protected%{link_end} by default"
+msgstr ""
+
msgid "Environment:"
msgstr "환경:"
@@ -8869,6 +9090,9 @@ msgstr "모든 ê°’ì„ ê¸°ì¤€ìœ¼ë¡œ í•„í„°"
msgid "EventFilterBy|Filter by comments"
msgstr "댓글 기준으로 필터"
+msgid "EventFilterBy|Filter by designs"
+msgstr ""
+
msgid "EventFilterBy|Filter by epic events"
msgstr ""
@@ -9001,15 +9225,15 @@ msgstr "ëª¨ë‘ í™•ìž¥"
msgid "Expand approvers"
msgstr "승ì¸ìž 확장"
-msgid "Expand child epics"
-msgstr ""
-
msgid "Expand down"
msgstr ""
msgid "Expand dropdown"
msgstr ""
+msgid "Expand milestones"
+msgstr ""
+
msgid "Expand sidebar"
msgstr "사ì´ë“œë°” 확장"
@@ -9025,6 +9249,9 @@ msgstr ""
msgid "Expiration date"
msgstr "만료ì¼"
+msgid "Expiration not enforced"
+msgstr ""
+
msgid "Expiration policy for the Container Registry is a perfect solution for keeping the Registry space down while still enjoying the full power of GitLab CI/CD."
msgstr ""
@@ -9145,6 +9372,9 @@ msgstr "실패"
msgid "Failed Jobs"
msgstr "작업 실패"
+msgid "Failed on"
+msgstr ""
+
msgid "Failed to add a Zoom meeting"
msgstr ""
@@ -9205,13 +9435,19 @@ msgstr ""
msgid "Failed to install."
msgstr ""
+msgid "Failed to load assignees. Please try again."
+msgstr ""
+
+msgid "Failed to load authors. Please try again."
+msgstr ""
+
msgid "Failed to load emoji list."
msgstr "ì´ëª¨ì§€ 목ë¡ì„ 불러올 수 없습니다"
msgid "Failed to load error details from Sentry."
msgstr ""
-msgid "Failed to load errors from Sentry. Error message: %{errorMessage}"
+msgid "Failed to load errors from Sentry."
msgstr ""
msgid "Failed to load group activity metrics. Please try again."
@@ -9292,6 +9528,9 @@ msgstr ""
msgid "Failed to set due date because the date format is invalid."
msgstr ""
+msgid "Failed to set iteration on this issue. Please try again."
+msgstr ""
+
msgid "Failed to signing using smartcard authentication"
msgstr ""
@@ -9514,13 +9753,10 @@ msgstr ""
msgid "FeatureFlags|Target environments"
msgstr ""
-msgid "FeatureFlags|There are no active feature flags"
-msgstr ""
-
-msgid "FeatureFlags|There are no inactive feature flags"
+msgid "FeatureFlags|There was an error fetching the feature flags."
msgstr ""
-msgid "FeatureFlags|There was an error fetching the feature flags."
+msgid "FeatureFlags|There was an error retrieving user lists"
msgstr ""
msgid "FeatureFlags|Try again in a few moments or contact your support team."
@@ -9532,9 +9768,18 @@ msgstr ""
msgid "FeatureFlag|Delete strategy"
msgstr ""
+msgid "FeatureFlag|List"
+msgstr ""
+
msgid "FeatureFlag|Percentage"
msgstr ""
+msgid "FeatureFlag|Select a user list"
+msgstr ""
+
+msgid "FeatureFlag|There are no configured user lists"
+msgstr ""
+
msgid "FeatureFlag|Type"
msgstr ""
@@ -9691,6 +9936,12 @@ msgstr ""
msgid "Filter..."
msgstr "í•„í„°..."
+msgid "Find File"
+msgstr ""
+
+msgid "Find bugs in your code with coverage-guided fuzzing"
+msgstr ""
+
msgid "Find by path"
msgstr "경로로 찾기"
@@ -9955,6 +10206,9 @@ msgstr ""
msgid "Geo allows you to replicate your GitLab instance to other geographical locations."
msgstr ""
+msgid "Geo nodes are paused using a command run on the node"
+msgstr ""
+
msgid "GeoNodeStatusEvent|%{timeAgoStr} (%{pendingEvents} events)"
msgstr ""
@@ -10039,6 +10293,9 @@ msgstr ""
msgid "GeoNodes|Not checksummed"
msgstr "ì²´í¬ì„¬ ë˜ì§€ ì•ŠìŒ"
+msgid "GeoNodes|Package files"
+msgstr ""
+
msgid "GeoNodes|Pausing replication stops the sync process. Are you sure?"
msgstr ""
@@ -10057,6 +10314,9 @@ msgstr ""
msgid "GeoNodes|Replication slots"
msgstr ""
+msgid "GeoNodes|Replication status"
+msgstr ""
+
msgid "GeoNodes|Repositories"
msgstr "저장소"
@@ -10267,6 +10527,9 @@ msgstr ""
msgid "Geo|Synchronization failed - %{error}"
msgstr "Geo|ë™ê¸°í™” 실패 - %{error}"
+msgid "Geo|Synchronization of %{itemTitle} is disabled."
+msgstr ""
+
msgid "Geo|The database is currently %{db_lag} behind the primary node."
msgstr ""
@@ -10579,6 +10842,9 @@ msgstr ""
msgid "Go to %{link_to_google_takeout}."
msgstr "%{link_to_google_takeout}ë¡œ ì´ë™í•˜ì‹­ì‹œì˜¤."
+msgid "Go to <strong>Issues</strong> > <strong>Boards</strong> to access your personalized learning issue board."
+msgstr ""
+
msgid "Go to Pipelines"
msgstr ""
@@ -10678,9 +10944,6 @@ msgstr ""
msgid "Go to your snippets"
msgstr ""
-msgid "Golden Tanuki"
-msgstr ""
-
msgid "Google Cloud Platform"
msgstr ""
@@ -10702,6 +10965,9 @@ msgstr "확ì¸!"
msgid "Grafana URL"
msgstr ""
+msgid "Grafana response contains invalid json"
+msgstr ""
+
msgid "GrafanaIntegration|API Token"
msgstr ""
@@ -10750,9 +11016,6 @@ msgstr ""
msgid "Group %{group_name} was successfully created."
msgstr ""
-msgid "Group '%{group_name}' is being imported."
-msgstr ""
-
msgid "Group Audit Events"
msgstr ""
@@ -10789,9 +11052,6 @@ msgstr "그룹 아바타"
msgid "Group by:"
msgstr ""
-msgid "Group could not be imported: %{errors}"
-msgstr ""
-
msgid "Group description"
msgstr "그룹 설명"
@@ -10819,9 +11079,15 @@ msgstr ""
msgid "Group has not been marked for deletion"
msgstr ""
+msgid "Group import could not be scheduled"
+msgstr ""
+
msgid "Group info:"
msgstr "그룹 정보:"
+msgid "Group is required when cluster_type is :group"
+msgstr ""
+
msgid "Group maintainers can register group runners in the %{link}"
msgstr "그룹 관리ìžëŠ” grouo runners를 여기서 ë“±ë¡ í•  수 있습니다: %{link}"
@@ -10885,6 +11151,24 @@ msgstr ""
msgid "GroupActivyMetrics|Recent activity (last 90 days)"
msgstr ""
+msgid "GroupImport|Failed to import group."
+msgstr ""
+
+msgid "GroupImport|Group '%{group_name}' is being imported."
+msgstr ""
+
+msgid "GroupImport|Group could not be imported: %{errors}"
+msgstr ""
+
+msgid "GroupImport|Please wait while we import the group for you. Refresh at will."
+msgstr ""
+
+msgid "GroupImport|The group was successfully imported."
+msgstr ""
+
+msgid "GroupImport|Unable to process group import file"
+msgstr ""
+
msgid "GroupRoadmap|%{dateWord} – No end date"
msgstr ""
@@ -11152,6 +11436,9 @@ msgstr ""
msgid "Groups (%{groups})"
msgstr ""
+msgid "Groups and projects"
+msgstr ""
+
msgid "Groups can also be nested by creating %{subgroup_docs_link_start}subgroups%{subgroup_docs_link_end}."
msgstr ""
@@ -11197,6 +11484,33 @@ msgstr "ê·¸ë£¹ì´ ì—†ìŠµë‹ˆë‹¤."
msgid "GroupsEmptyState|You can manage your group member’s permissions and access to each project in the group."
msgstr "그룹 구성ì›ì˜ 사용 ê¶Œí•œì„ ê´€ë¦¬í•˜ê³  ê·¸ë£¹ì˜ ê° í”„ë¡œì íŠ¸ì— ì ‘ê·¼ í•  수 있습니다."
+msgid "GroupsNew|Contact an administrator to enable options for importing your group."
+msgstr ""
+
+msgid "GroupsNew|Create"
+msgstr ""
+
+msgid "GroupsNew|Create group"
+msgstr ""
+
+msgid "GroupsNew|GitLab group export"
+msgstr ""
+
+msgid "GroupsNew|Import"
+msgstr ""
+
+msgid "GroupsNew|Import group"
+msgstr ""
+
+msgid "GroupsNew|My Awesome Group"
+msgstr ""
+
+msgid "GroupsNew|No import options available"
+msgstr ""
+
+msgid "GroupsNew|To copy a GitLab group between installations, navigate to the group settings page for the original installation, generate an export file, and upload it here."
+msgstr ""
+
msgid "GroupsTree|Are you sure you want to leave the \"%{fullName}\" group?"
msgstr ""
@@ -11299,6 +11613,9 @@ msgstr ""
msgid "Helps reduce request volume for protected paths"
msgstr ""
+msgid "Here are all your projects in your group, including the one you just created. To start, let’s take a look at your personalized learning project which will help you learn about GitLab at your own pace."
+msgstr ""
+
msgid "Here you will find recent merge request activity"
msgstr ""
@@ -11312,6 +11629,9 @@ msgid "Hide chart"
msgid_plural "Hide charts"
msgstr[0] ""
+msgid "Hide details"
+msgstr ""
+
msgid "Hide file browser"
msgstr ""
@@ -11321,6 +11641,9 @@ msgstr ""
msgid "Hide host keys manual input"
msgstr "호스트 키 ìˆ˜ë™ ìž…ë ¥ 숨기기"
+msgid "Hide list"
+msgstr ""
+
msgid "Hide marketing-related entries from help"
msgstr ""
@@ -11340,9 +11663,6 @@ msgstr[0] "값 숨기기"
msgid "Hide values"
msgstr "값 숨기기"
-msgid "Hiding all labels"
-msgstr ""
-
msgid "High or unknown vulnerabilities present"
msgstr ""
@@ -11427,6 +11747,9 @@ msgstr "ID"
msgid "ID:"
msgstr ""
+msgid "IDE"
+msgstr ""
+
msgid "IDE|Allow live previews of JavaScript projects in the Web IDE using CodeSandbox Live Preview."
msgstr ""
@@ -11517,10 +11840,10 @@ msgstr ""
msgid "If enabled"
msgstr "만약 활성화 ëœ ê²½ìš°"
-msgid "If enabled, access to projects will be validated on an external service using their classification label."
+msgid "If enabled, GitLab will handle Object Storage replication using Geo. %{linkStart}More information%{linkEnd}"
msgstr ""
-msgid "If enabled, and if object storage is enabled, GitLab will handle Object Storage replication using Geo"
+msgid "If enabled, access to projects will be validated on an external service using their classification label."
msgstr ""
msgid "If there is no previous license or if the previous license has expired, some GitLab functionality will be blocked until a new, valid license is uploaded."
@@ -11881,6 +12204,9 @@ msgid "Instance"
msgid_plural "Instances"
msgstr[0] "ì¸ìŠ¤í„´ìŠ¤"
+msgid "Instance Configuration"
+msgstr ""
+
msgid "Instance Statistics visibility"
msgstr "ì¸ìŠ¤í„´ìŠ¤ 통계 가시성"
@@ -11902,9 +12228,6 @@ msgstr ""
msgid "Integrations"
msgstr "ì—°ë™"
-msgid "Integrations allow you to integrate GitLab with other applications"
-msgstr ""
-
msgid "Integrations|All details"
msgstr ""
@@ -12016,6 +12339,9 @@ msgstr ""
msgid "Invalid query"
msgstr ""
+msgid "Invalid repository bundle for snippet with id %{snippet_id}"
+msgstr ""
+
msgid "Invalid repository path"
msgstr ""
@@ -12034,6 +12360,9 @@ msgstr ""
msgid "Invalid two-factor code."
msgstr ""
+msgid "Invalid yaml"
+msgstr ""
+
msgid "Invitation"
msgstr "초대"
@@ -12253,15 +12582,24 @@ msgstr ""
msgid "It seems like the Dependency Scanning job ran successfully, but no dependencies have been detected in your project."
msgstr ""
+msgid "It seems that there is currently no available data for code coverage"
+msgstr ""
+
msgid "It's you"
msgstr "바로 너"
+msgid "Iteration"
+msgstr ""
+
msgid "Iteration changed to"
msgstr ""
msgid "Iteration removed"
msgstr ""
+msgid "Iteration updated"
+msgstr ""
+
msgid "Iterations"
msgstr ""
@@ -12292,12 +12630,18 @@ msgstr "1ì›”"
msgid "January"
msgstr "1ì›”"
+msgid "Jira Issues"
+msgstr ""
+
msgid "Jira import is already running."
msgstr ""
msgid "Jira integration not configured."
msgstr ""
+msgid "Jira project key is not configured"
+msgstr ""
+
msgid "Jira project: %{importProject}"
msgstr ""
@@ -12478,6 +12822,9 @@ msgstr ""
msgid "Keep divergent refs"
msgstr ""
+msgid "Kerberos access denied"
+msgstr ""
+
msgid "Key"
msgstr ""
@@ -12487,10 +12834,13 @@ msgstr "키 (PEM)"
msgid "Key: %{key}"
msgstr ""
-msgid "Keyboard Shortcuts"
+msgid "Keyboard shortcuts"
msgstr ""
-msgid "Keyboard shortcuts"
+msgid "Keys"
+msgstr ""
+
+msgid "Ki"
msgstr ""
msgid "Kubernetes"
@@ -12532,6 +12882,9 @@ msgstr ""
msgid "LDAP"
msgstr ""
+msgid "LDAP Synchronization"
+msgstr ""
+
msgid "LDAP settings"
msgstr ""
@@ -12755,12 +13108,18 @@ msgstr ""
msgid "Learn more about custom project templates"
msgstr ""
+msgid "Learn more about deploying to AWS"
+msgstr ""
+
msgid "Learn more about deploying to a cluster"
msgstr ""
msgid "Learn more about group-level project templates"
msgstr ""
+msgid "Learn more about job dependencies"
+msgstr ""
+
msgid "Learn more about signing commits"
msgstr ""
@@ -12812,9 +13171,21 @@ msgstr ""
msgid "License History"
msgstr ""
+msgid "License ID:"
+msgstr ""
+
+msgid "License URL"
+msgstr ""
+
msgid "License-Check"
msgstr ""
+msgid "LicenseCompliance|%{docLinkStart}License Approvals%{docLinkEnd} are active"
+msgstr ""
+
+msgid "LicenseCompliance|%{docLinkStart}License Approvals%{docLinkEnd} are inactive"
+msgstr ""
+
msgid "LicenseCompliance|Add a license"
msgstr ""
@@ -12836,9 +13207,15 @@ msgstr ""
msgid "LicenseCompliance|Deny"
msgstr ""
+msgid "LicenseCompliance|Learn more about %{linkStart}License Approvals%{linkEnd}"
+msgstr ""
+
msgid "LicenseCompliance|License"
msgstr ""
+msgid "LicenseCompliance|License Approvals"
+msgstr ""
+
msgid "LicenseCompliance|License Compliance detected %d license and policy violation for the source branch only; approval required"
msgid_plural "LicenseCompliance|License Compliance detected %d licenses and policy violations for the source branch only; approval required"
msgstr[0] ""
@@ -13054,6 +13431,9 @@ msgstr "ëª©ë¡ ë³´ê¸°"
msgid "List your Bitbucket Server repositories"
msgstr "Bitbucket Server 저장소 목ë¡"
+msgid "Lists"
+msgstr ""
+
msgid "Live preview"
msgstr "실시간 미리보기"
@@ -13237,6 +13617,9 @@ msgstr ""
msgid "Manage project labels"
msgstr "프로ì íŠ¸ ë¼ë²¨ 관리"
+msgid "Manage storage usage"
+msgstr ""
+
msgid "Manage two-factor authentication"
msgstr ""
@@ -13252,6 +13635,9 @@ msgstr "Manifest"
msgid "Manifest file import"
msgstr "Manifest íŒŒì¼ ê°€ì ¸ì˜¤ê¸°"
+msgid "Manifest import"
+msgstr ""
+
msgid "Manual job"
msgstr ""
@@ -13399,6 +13785,12 @@ msgstr ""
msgid "Maximum field length"
msgstr ""
+msgid "Maximum file size is 2MB. Please select a smaller file."
+msgstr ""
+
+msgid "Maximum import size (MB)"
+msgstr ""
+
msgid "Maximum job timeout"
msgstr "최대 작업 시간 초과"
@@ -13438,6 +13830,9 @@ msgstr ""
msgid "Maximum size of Elasticsearch bulk indexing requests."
msgstr ""
+msgid "Maximum size of import files."
+msgstr ""
+
msgid "Maximum size of individual attachments in comments."
msgstr ""
@@ -13489,6 +13884,9 @@ msgstr ""
msgid "Merge (when the pipeline succeeds)"
msgstr ""
+msgid "Merge Conflicts"
+msgstr ""
+
msgid "Merge Request"
msgstr "머지 리퀘스트(MR)"
@@ -13615,6 +14013,9 @@ msgstr "코멘트 저장 실패"
msgid "MergeRequests|Squash task canceled: another squash is already in progress."
msgstr ""
+msgid "MergeRequests|This project does not allow squashing commits when merge requests are accepted."
+msgstr ""
+
msgid "MergeRequests|Thread stays resolved"
msgstr ""
@@ -13768,6 +14169,12 @@ msgstr ""
msgid "MetricsSettings|Add a button to the metrics dashboard linking directly to your existing external dashboard."
msgstr ""
+msgid "MetricsSettings|Choose whether to display dashboard metrics in UTC or the user's local timezone."
+msgstr ""
+
+msgid "MetricsSettings|Dashboard timezone"
+msgstr ""
+
msgid "MetricsSettings|External dashboard URL"
msgstr ""
@@ -13777,6 +14184,12 @@ msgstr ""
msgid "MetricsSettings|Metrics Dashboard"
msgstr ""
+msgid "MetricsSettings|UTC (Coordinated Universal Time)"
+msgstr ""
+
+msgid "MetricsSettings|User's local timezone"
+msgstr ""
+
msgid "Metrics|Add metric"
msgstr ""
@@ -13792,6 +14205,9 @@ msgstr ""
msgid "Metrics|Create metric"
msgstr ""
+msgid "Metrics|Current"
+msgstr ""
+
msgid "Metrics|Delete metric"
msgstr ""
@@ -13841,6 +14257,9 @@ msgstr ""
msgid "Metrics|Max"
msgstr ""
+msgid "Metrics|Min"
+msgstr ""
+
msgid "Metrics|Must be a valid PromQL query."
msgstr ""
@@ -13856,6 +14275,9 @@ msgstr ""
msgid "Metrics|Refresh dashboard"
msgstr ""
+msgid "Metrics|Select a value"
+msgstr ""
+
msgid "Metrics|Star dashboard"
msgstr ""
@@ -13874,12 +14296,18 @@ msgstr ""
msgid "Metrics|There was an error getting annotations information."
msgstr ""
+msgid "Metrics|There was an error getting dashboard validation warnings information."
+msgstr ""
+
msgid "Metrics|There was an error getting deployment information."
msgstr ""
msgid "Metrics|There was an error getting environments information."
msgstr ""
+msgid "Metrics|There was an error getting options for variable \"%{name}\"."
+msgstr ""
+
msgid "Metrics|There was an error trying to validate your query"
msgstr ""
@@ -13922,6 +14350,9 @@ msgstr ""
msgid "Metrics|You're about to permanently delete this metric. This cannot be undone."
msgstr ""
+msgid "Metrics|Your dashboard schema is invalid. Edit the dashboard to correct the YAML schema."
+msgstr ""
+
msgid "Metrics|e.g. HTTP requests"
msgstr ""
@@ -13937,12 +14368,18 @@ msgstr ""
msgid "Metrics|e.g. req/sec"
msgstr ""
+msgid "Mi"
+msgstr ""
+
msgid "Microsoft Azure"
msgstr ""
msgid "Middleman project with Static Site Editor support"
msgstr ""
+msgid "Migrate your data from an external source like GitHub, Bitbucket, or another instance of GitLab."
+msgstr ""
+
msgid "Migrated %{success_count}/%{total_count} files."
msgstr ""
@@ -14076,6 +14513,9 @@ msgstr ""
msgid "Minimum capacity to be available before we schedule more mirrors preemptively."
msgstr ""
+msgid "Minimum interval in days"
+msgstr ""
+
msgid "Minimum length is %{minimum_password_length} characters"
msgstr "최소 길ì´ëŠ” %{minimum_password_length}ìž ìž…ë‹ˆë‹¤."
@@ -14262,6 +14702,12 @@ msgstr ""
msgid "Multiple uploaders found: %{uploader_types}"
msgstr ""
+msgid "Must match with the %{codeStart}external_url%{codeEnd} in %{codeStart}/etc/gitlab/gitlab.rb%{codeEnd}."
+msgstr ""
+
+msgid "Must match with the %{codeStart}geo_node_name%{codeEnd} in %{codeStart}/etc/gitlab/gitlab.rb%{codeEnd}. %{linkStart}More information%{linkEnd}"
+msgstr ""
+
msgid "My Awesome Group"
msgstr ""
@@ -14376,10 +14822,16 @@ msgstr "신규"
msgid "New Application"
msgstr "새로운 애플리케ì´ì…˜"
+msgid "New Branch"
+msgstr ""
+
+msgid "New Deploy Key"
+msgstr ""
+
msgid "New Environment"
msgstr ""
-msgid "New Geo Node"
+msgid "New File"
msgstr ""
msgid "New Group"
@@ -14395,15 +14847,15 @@ msgid "New Issue"
msgid_plural "New Issues"
msgstr[0] "새 ì´ìŠˆ"
-msgid "New Iteration"
-msgstr ""
-
msgid "New Jira import"
msgstr ""
msgid "New Label"
msgstr "새 ë¼ë²¨"
+msgid "New Merge Request"
+msgstr ""
+
msgid "New Milestone"
msgstr ""
@@ -14422,6 +14874,9 @@ msgstr "새 프로ì íŠ¸"
msgid "New Snippet"
msgstr "새 스니펫"
+msgid "New User"
+msgstr ""
+
msgid "New branch"
msgstr "새 브랜치"
@@ -14464,6 +14919,9 @@ msgstr "새 ì´ìŠˆ"
msgid "New issue title"
msgstr ""
+msgid "New iteration"
+msgstr ""
+
msgid "New iteration created"
msgstr ""
@@ -14641,6 +15099,12 @@ msgstr ""
msgid "No grouping"
msgstr ""
+msgid "No iteration"
+msgstr ""
+
+msgid "No iterations to show"
+msgstr ""
+
msgid "No job log"
msgstr ""
@@ -14683,6 +15147,9 @@ msgstr ""
msgid "No other labels with such name or description"
msgstr "그런 ì´ë¦„ ë˜ëŠ” 설명ì´ìžˆëŠ” 다른 ë ˆì´ë¸”ì´ ì—†ìŠµë‹ˆë‹¤."
+msgid "No panels matching properties %{opts}"
+msgstr ""
+
msgid "No parent group"
msgstr ""
@@ -14716,6 +15183,9 @@ msgstr "Runners ì—†ìŒ"
msgid "No schedules"
msgstr "ì¼ì • ì—†ìŒ"
+msgid "No stack trace for this error"
+msgstr ""
+
msgid "No starrers matched your search"
msgstr ""
@@ -14728,7 +15198,7 @@ msgstr "템플릿 ì—†ìŒ"
msgid "No test coverage"
msgstr ""
-msgid "No thanks, don't show this again"
+msgid "No thanks"
msgstr ""
msgid "No vulnerabilities present"
@@ -14743,9 +15213,6 @@ msgstr ""
msgid "No, directly import the existing email addresses and usernames."
msgstr ""
-msgid "No, not interested right now"
-msgstr ""
-
msgid "No. of commits"
msgstr ""
@@ -14791,9 +15258,6 @@ msgstr "ë°ì´í„°ê°€ 충분하지 않습니다."
msgid "Not found."
msgstr ""
-msgid "Not helpful"
-msgstr ""
-
msgid "Not now"
msgstr "나중ì—"
@@ -14950,6 +15414,9 @@ msgstr "알림 ë„기"
msgid "Notifications on"
msgstr "알림 켬"
+msgid "Notify users by email when sign-in location is not recognized"
+msgstr ""
+
msgid "Nov"
msgstr "11ì›”"
@@ -15016,9 +15483,6 @@ msgstr "í•„í„°"
msgid "Oh no!"
msgstr ""
-msgid "Ok let's go"
-msgstr "ìž, 시작해봅시다"
-
msgid "Oldest first"
msgstr ""
@@ -15031,16 +15495,49 @@ msgstr ""
msgid "On track"
msgstr ""
+msgid "OnDemandScans|Attached branch"
+msgstr ""
+
+msgid "OnDemandScans|Attached branch is where the scan job runs."
+msgstr ""
+
+msgid "OnDemandScans|Could not run the scan. Please try again."
+msgstr ""
+
msgid "OnDemandScans|Create new DAST scan"
msgstr ""
+msgid "OnDemandScans|DAST will scan the target URL and any discovered sub URLs."
+msgstr ""
+
+msgid "OnDemandScans|New on-demand DAST scan"
+msgstr ""
+
msgid "OnDemandScans|On-demand Scans"
msgstr ""
+msgid "OnDemandScans|On-demand scans run outside the DevOps cycle and find vulnerabilities in your projects. %{learnMoreLinkStart}Learn more%{learnMoreLinkEnd}"
+msgstr ""
+
+msgid "OnDemandScans|Only a passive scan can be performed on demand."
+msgstr ""
+
+msgid "OnDemandScans|Passive DAST Scan"
+msgstr ""
+
+msgid "OnDemandScans|Please enter a valid URL format, ex: http://www.example.com/home"
+msgstr ""
+
+msgid "OnDemandScans|Run this scan"
+msgstr ""
+
+msgid "OnDemandScans|Scan mode"
+msgstr ""
+
msgid "OnDemandScans|Schedule or run scans immediately against target sites. Currently available on-demand scan type: DAST. %{helpLinkStart}More information%{helpLinkEnd}"
msgstr ""
-msgid "Onboarding"
+msgid "OnDemandScans|Target URL"
msgstr ""
msgid "Once imported, repositories can be mirrored over SSH. Read more %{link_start}here%{link_end}."
@@ -15065,6 +15562,9 @@ msgstr ""
msgid "One or more of you personal access tokens were revoked"
msgstr ""
+msgid "One or more of your %{provider} projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
+msgstr ""
+
msgid "One or more of your Bitbucket projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
msgstr ""
@@ -15566,6 +16066,9 @@ msgstr ""
msgid "Parent epic is not present."
msgstr ""
+msgid "Parsing error for param :embed_json. %{message}"
+msgstr ""
+
msgid "Part of merge request changes"
msgstr "머지 리퀘스트(MR) 변경 사항 중 ì¼ë¶€"
@@ -15578,6 +16081,9 @@ msgstr ""
msgid "Passed"
msgstr ""
+msgid "Passed on"
+msgstr ""
+
msgid "Password"
msgstr "패스워드"
@@ -15662,6 +16168,9 @@ msgstr ""
msgid "Perform common operations on GitLab project"
msgstr ""
+msgid "Performance and resource management"
+msgstr ""
+
msgid "Performance optimization"
msgstr "성능 최ì í™”"
@@ -15869,8 +16378,8 @@ msgstr "파ì´í”„ë¼ì¸ 실행"
msgid "Pipelines|Something went wrong while cleaning runners cache."
msgstr "Runnerì˜ ìºì‹œë¥¼ ë¹„ìš°ë˜ ì¤‘ 오류가 ë°œìƒí•˜ì˜€ìŠµë‹ˆë‹¤."
-msgid "Pipelines|There are currently no %{scope} pipelines."
-msgstr "%{scope} 파ì´í”„ë¼ì¸ 중 현재 실행중 ì¸ ê²ƒì€ ì—†ìŠµë‹ˆë‹¤."
+msgid "Pipelines|There are currently no finished pipelines."
+msgstr ""
msgid "Pipelines|There are currently no pipelines."
msgstr "현재 ì‹¤í–‰ì¤‘ì¸ íŒŒì´í”„ë¼ì¸ì´ 없습니다."
@@ -15980,6 +16489,9 @@ msgstr "파ì´í”„ë¼ì¸ 중지"
msgid "Pipeline|Stop pipeline #%{pipelineId}?"
msgstr "#%{pipelineId} 파ì´í”„ë¼ì¸ì„ 중지하시겠습니까?"
+msgid "Pipeline|Tag name"
+msgstr ""
+
msgid "Pipeline|Trigger author"
msgstr ""
@@ -16061,12 +16573,18 @@ msgstr ""
msgid "Please check your email (%{email}) to verify that you own this address and unlock the power of CI/CD. Didn't receive it? %{resend_link}. Wrong email address? %{update_link}."
msgstr ""
+msgid "Please choose a file"
+msgstr ""
+
msgid "Please choose a group URL with no special characters."
msgstr ""
msgid "Please complete your profile with email address"
msgstr ""
+msgid "Please convert %{linkStart}them to Git%{linkEnd}, and go through the %{linkToImportFlow} again."
+msgstr ""
+
msgid "Please convert them to %{link_to_git}, and go through the %{link_to_import_flow} again."
msgstr ""
@@ -16124,6 +16642,9 @@ msgstr ""
msgid "Please provide a valid email address."
msgstr ""
+msgid "Please provide attributes to update"
+msgstr ""
+
msgid "Please refer to <a href=\"%{docs_url}\">%{docs_url}</a>"
msgstr ""
@@ -16859,6 +17380,9 @@ msgstr ""
msgid "Project has too many %{label_for_message} to search"
msgstr ""
+msgid "Project is required when cluster_type is :project"
+msgstr ""
+
msgid "Project members"
msgstr ""
@@ -16976,7 +17500,40 @@ msgstr ""
msgid "ProjectService|Comment"
msgstr ""
-msgid "ProjectService|Comment will be posted on each event"
+msgid "ProjectService|Event will be triggered by a push to the repository"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a commit is created/updated"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a confidential issue is created/updated/closed"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a deployment finishes"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a merge request is created/updated/merged"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a new tag is pushed to the repository"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a new, unique alert is recorded"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a pipeline status changes"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a wiki page is created/updated"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when an issue is created/updated/closed"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when someone adds a comment"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when someone adds a comment on a confidential issue"
msgstr ""
msgid "ProjectService|Perform common operations on GitLab project: %{project_name}"
@@ -17027,6 +17584,9 @@ msgstr ""
msgid "ProjectSettings|Enable 'Delete source branch' option by default"
msgstr ""
+msgid "ProjectSettings|Enable merge trains and pipelines for merged results"
+msgstr ""
+
msgid "ProjectSettings|Every merge creates a merge commit"
msgstr ""
@@ -17057,7 +17617,7 @@ msgstr ""
msgid "ProjectSettings|Forks"
msgstr ""
-msgid "ProjectSettings|Git Large File Storage"
+msgid "ProjectSettings|Git Large File Storage (LFS)"
msgstr ""
msgid "ProjectSettings|Internal"
@@ -17093,9 +17653,6 @@ msgstr ""
msgid "ProjectSettings|Merge options"
msgstr ""
-msgid "ProjectSettings|Merge pipelines will try to validate the post-merge result prior to merging"
-msgstr ""
-
msgid "ProjectSettings|Merge requests"
msgstr ""
@@ -17123,6 +17680,9 @@ msgstr ""
msgid "ProjectSettings|Pipelines"
msgstr ""
+msgid "ProjectSettings|Pipelines for merge requests must be enabled in the CI/CD configuration file, or pipelines could be unresolvable or dropped"
+msgstr ""
+
msgid "ProjectSettings|Pipelines must succeed"
msgstr ""
@@ -17150,6 +17710,9 @@ msgstr ""
msgid "ProjectSettings|Show link to create/view merge request when pushing from the command line"
msgstr ""
+msgid "ProjectSettings|Skipped pipelines are considered successful"
+msgstr ""
+
msgid "ProjectSettings|Snippets"
msgstr ""
@@ -17165,6 +17728,9 @@ msgstr ""
msgid "ProjectSettings|These checks must pass before merge requests can be merged"
msgstr ""
+msgid "ProjectSettings|This introduces the risk of merging changes that will not pass the pipeline."
+msgstr ""
+
msgid "ProjectSettings|This setting is applied on the server level and can be overridden by an admin."
msgstr "ì´ ì„¤ì •ì€ ì„œë²„ 레벨ì—ì„œ ì ìš©ë˜ì—ˆìœ¼ë©°, 관리ìžì— ì˜í•´ ë³€ê²½ë  ìˆ˜ 있습니다."
@@ -17342,15 +17908,27 @@ msgstr ""
msgid "ProjectsNew|Blank project"
msgstr ""
+msgid "ProjectsNew|Connect your external repository to GitLab CI/CD."
+msgstr ""
+
msgid "ProjectsNew|Contact an administrator to enable options for importing your project."
msgstr ""
msgid "ProjectsNew|Create"
msgstr ""
+msgid "ProjectsNew|Create a blank project to house your files, plan your work, and collaborate on code, among other things."
+msgstr ""
+
+msgid "ProjectsNew|Create blank project"
+msgstr ""
+
msgid "ProjectsNew|Create from template"
msgstr ""
+msgid "ProjectsNew|Create new project"
+msgstr ""
+
msgid "ProjectsNew|Creating project & repository."
msgstr ""
@@ -17375,6 +17953,9 @@ msgstr ""
msgid "ProjectsNew|Project description %{tag_start}(optional)%{tag_end}"
msgstr ""
+msgid "ProjectsNew|Run CI/CD for external repository"
+msgstr ""
+
msgid "ProjectsNew|Template"
msgstr ""
@@ -17636,6 +18217,9 @@ msgstr "보호ë¨"
msgid "Protected Branch"
msgstr ""
+msgid "Protected Branches"
+msgstr ""
+
msgid "Protected Environment"
msgstr ""
@@ -17648,6 +18232,9 @@ msgstr ""
msgid "Protected Tag"
msgstr ""
+msgid "Protected Tags"
+msgstr ""
+
msgid "Protected branches"
msgstr ""
@@ -17747,6 +18334,9 @@ msgstr ""
msgid "Provider"
msgstr "공급ìž"
+msgid "Proxy support for this API is not available currently"
+msgstr ""
+
msgid "Pseudonymizer data collection"
msgstr ""
@@ -17771,6 +18361,9 @@ msgstr ""
msgid "Publish to status page"
msgstr ""
+msgid "Published on status page"
+msgstr ""
+
msgid "Publishes this issue to the associated status page."
msgstr ""
@@ -17945,6 +18538,9 @@ msgstr "최근 검색"
msgid "Recipe"
msgstr ""
+msgid "Reconfigure"
+msgstr ""
+
msgid "Recover hidden stage"
msgstr ""
@@ -18030,6 +18626,9 @@ msgstr ""
msgid "Registration|Your profile"
msgstr ""
+msgid "Regulate approvals by authors/committers, based on compliance frameworks. Can be changed only at the instance level."
+msgstr ""
+
msgid "Rejected (closed)"
msgstr ""
@@ -18079,15 +18678,24 @@ msgstr ""
msgid "ReleaseAssetLinkType|Image"
msgstr ""
+msgid "ReleaseAssetLinkType|Images"
+msgstr ""
+
msgid "ReleaseAssetLinkType|Other"
msgstr ""
msgid "ReleaseAssetLinkType|Package"
msgstr ""
+msgid "ReleaseAssetLinkType|Packages"
+msgstr ""
+
msgid "ReleaseAssetLinkType|Runbook"
msgstr ""
+msgid "ReleaseAssetLinkType|Runbooks"
+msgstr ""
+
msgid "Releases"
msgstr ""
@@ -18100,6 +18708,9 @@ msgstr ""
msgid "Releases documentation"
msgstr ""
+msgid "Releases|New Release"
+msgstr ""
+
msgid "Release|Something went wrong while getting the release details"
msgstr ""
@@ -18172,6 +18783,9 @@ msgstr ""
msgid "Remove fork relationship"
msgstr ""
+msgid "Remove from batch"
+msgstr ""
+
msgid "Remove from board"
msgstr ""
@@ -18361,6 +18975,12 @@ msgstr ""
msgid "Replication"
msgstr ""
+msgid "Replication enabled"
+msgstr ""
+
+msgid "Replication paused"
+msgstr ""
+
msgid "Reply by email"
msgstr ""
@@ -18388,15 +19008,15 @@ msgstr ""
msgid "Reported %{timeAgo} by %{reportedBy}"
msgstr ""
-msgid "Reported Resource Changes: %{addNum} to add, %{changeNum} to change, %{deleteNum} to delete"
-msgstr ""
-
msgid "Reporter"
msgstr ""
msgid "Reporting"
msgstr "ë³´ê³ "
+msgid "Reports"
+msgstr ""
+
msgid "Reports|%{combinedString} and %{resolvedString}"
msgstr ""
@@ -18434,6 +19054,9 @@ msgstr "실행 시간"
msgid "Reports|Failure"
msgstr "실패"
+msgid "Reports|Identifier"
+msgstr ""
+
msgid "Reports|Metrics reports are loading"
msgstr ""
@@ -18446,6 +19069,9 @@ msgstr ""
msgid "Reports|Metrics reports failed loading results"
msgstr ""
+msgid "Reports|Scanner"
+msgstr ""
+
msgid "Reports|Severity"
msgstr ""
@@ -18494,15 +19120,33 @@ msgstr ""
msgid "Repository cleanup has started. You will receive an email once the cleanup operation is complete."
msgstr ""
+msgid "Repository files count over the limit"
+msgstr ""
+
+msgid "Repository has an invalid default branch name."
+msgstr ""
+
+msgid "Repository has more than one branch."
+msgstr ""
+
msgid "Repository has no locks."
msgstr "ì €ìž¥ì†Œì— ìž ê¸ˆì´ ì—†ìŠµë‹ˆë‹¤."
+msgid "Repository has tags."
+msgstr ""
+
msgid "Repository maintenance"
msgstr "저장소 유지 보수"
msgid "Repository mirroring"
msgstr ""
+msgid "Repository must contain at least 1 file."
+msgstr ""
+
+msgid "Repository size is above the limit."
+msgstr ""
+
msgid "Repository static objects"
msgstr ""
@@ -18512,7 +19156,7 @@ msgstr "ì €ìž¥ì†Œì˜ ì €ìž¥ê³µê°„"
msgid "Repository sync capacity"
msgstr ""
-msgid "Repository: %{counter_repositories} / Wikis: %{counter_wikis} / Build Artifacts: %{counter_build_artifacts} / LFS: %{counter_lfs_objects}"
+msgid "Repository: %{counter_repositories} / Wikis: %{counter_wikis} / Build Artifacts: %{counter_build_artifacts} / LFS: %{counter_lfs_objects} / Snippets: %{counter_snippets}"
msgstr ""
msgid "RepositorySettingsAccessLevel|Select"
@@ -18524,6 +19168,9 @@ msgstr "액세스 요청"
msgid "Request Headers"
msgstr ""
+msgid "Request details"
+msgstr ""
+
msgid "Request parameter %{param} is missing."
msgstr ""
@@ -18628,6 +19275,9 @@ msgstr "runner ë“±ë¡ í† í° ì´ˆê¸°í™”"
msgid "Reset template"
msgstr ""
+msgid "Reset to project defaults"
+msgstr ""
+
msgid "Resetting the authorization key for this project will require updating the authorization key in every alert source it is enabled in."
msgstr ""
@@ -18724,9 +19374,6 @@ msgstr ""
msgid "Resume"
msgstr "재개"
-msgid "Resume replication"
-msgstr ""
-
msgid "Resync"
msgstr ""
@@ -19338,6 +19985,9 @@ msgstr ""
msgid "SecurityReports|False positive"
msgstr ""
+msgid "SecurityReports|Group Security Dashboard"
+msgstr ""
+
msgid "SecurityReports|Hide dismissed"
msgstr ""
@@ -19392,10 +20042,13 @@ msgstr ""
msgid "SecurityReports|Remove project from dashboard"
msgstr ""
-msgid "SecurityReports|Report type"
+msgid "SecurityReports|Return to dashboard"
msgstr ""
-msgid "SecurityReports|Return to dashboard"
+msgid "SecurityReports|Scan details"
+msgstr ""
+
+msgid "SecurityReports|Scanner type"
msgstr ""
msgid "SecurityReports|Security Dashboard"
@@ -19452,6 +20105,9 @@ msgstr ""
msgid "SecurityReports|There was an error while generating the report."
msgstr ""
+msgid "SecurityReports|Unable to add %{invalidProjectsMessage}"
+msgstr ""
+
msgid "SecurityReports|Unable to add %{invalidProjects}"
msgstr ""
@@ -19566,6 +20222,9 @@ msgstr "브랜치/태그 ì„ íƒ"
msgid "Select due date"
msgstr ""
+msgid "Select file"
+msgstr ""
+
msgid "Select group or project"
msgstr ""
@@ -19629,9 +20288,6 @@ msgstr "ëŒ€ìƒ ë¸Œëžœì¹˜ ì„ íƒ"
msgid "Select the branch you want to set as the default for this project. All merge requests and commits will automatically be made against this branch unless you specify a different one."
msgstr ""
-msgid "Select the configured storage available for new repositories to be placed on."
-msgstr ""
-
msgid "Select the custom project template source group."
msgstr ""
@@ -19824,12 +20480,6 @@ msgstr "서비스 ë°ìŠ¤í¬"
msgid "Service Desk is enabled but not yet active"
msgstr ""
-msgid "Service Desk is off"
-msgstr ""
-
-msgid "Service Desk is on"
-msgstr ""
-
msgid "Service Templates"
msgstr "서비스 템플릿"
@@ -19917,6 +20567,15 @@ msgstr ""
msgid "Set the milestone to %{milestone_reference}."
msgstr ""
+msgid "Set the number of concurrent requests this secondary node will make to the primary node while backfilling."
+msgstr ""
+
+msgid "Set the synchronization and verification capacity for the secondary node."
+msgstr ""
+
+msgid "Set the timeout in seconds to send a secondary node status to the primary and IPs allowed for the secondary nodes."
+msgstr ""
+
msgid "Set time estimate"
msgstr ""
@@ -19956,6 +20615,9 @@ msgstr ""
msgid "Set weight to %{weight}."
msgstr ""
+msgid "Set what should be replicated by choosing specific projects or groups by the secondary node."
+msgstr ""
+
msgid "SetPasswordToCloneLink|set a password"
msgstr "패스워드 설정"
@@ -20082,6 +20744,9 @@ msgstr ""
msgid "Show complete raw log"
msgstr "완료 로그 표시"
+msgid "Show details"
+msgstr ""
+
msgid "Show file browser"
msgstr ""
@@ -20091,10 +20756,13 @@ msgstr ""
msgid "Show latest version"
msgstr "최신 버전 보기"
+msgid "Show list"
+msgstr ""
+
msgid "Show me everything"
msgstr ""
-msgid "Show me how"
+msgid "Show me how to add a pipeline"
msgstr ""
msgid "Show me more advanced stuff"
@@ -20141,9 +20809,6 @@ msgstr ""
msgid "Showing all issues"
msgstr ""
-msgid "Showing all labels"
-msgstr ""
-
msgid "Showing last %{size} of log -"
msgstr ""
@@ -20255,9 +20920,6 @@ msgstr ""
msgid "Skip outdated deployment jobs"
msgstr ""
-msgid "Skip this for now"
-msgstr "ì¼ë‹¨ 건너뛰기"
-
msgid "Skipped"
msgstr ""
@@ -20402,6 +21064,9 @@ msgstr "ë²„íŠ¼ì„ í† ê¸€í•˜ë˜ ì¤‘ 문제가 ë°œìƒí–ˆìŠµë‹ˆë‹¤."
msgid "Something went wrong while adding your award. Please try again."
msgstr ""
+msgid "Something went wrong while applying the batch of suggestions. Please try again."
+msgstr ""
+
msgid "Something went wrong while applying the suggestion. Please try again."
msgstr ""
@@ -20504,10 +21169,13 @@ msgstr ""
msgid "Something went wrong while updating a requirement."
msgstr ""
+msgid "Something went wrong while updating assignees"
+msgstr ""
+
msgid "Something went wrong while updating your list settings"
msgstr ""
-msgid "Something went wrong with your automatic subscription renewal"
+msgid "Something went wrong with your automatic subscription renewal."
msgstr ""
msgid "Something went wrong, unable to add %{project} to dashboard"
@@ -20525,6 +21193,9 @@ msgstr ""
msgid "Something went wrong, unable to search projects"
msgstr ""
+msgid "Something went wrong."
+msgstr ""
+
msgid "Something went wrong. Please try again."
msgstr "문제가 ë°œìƒí–ˆìŠµë‹ˆë‹¤. 다시 ì‹œë„해주세요."
@@ -20708,6 +21379,9 @@ msgstr "소스 (브랜치 ë˜ëŠ” 태그)"
msgid "Source code"
msgstr "소스 코드"
+msgid "Source code (%{fileExtension})"
+msgstr ""
+
msgid "Source is not available"
msgstr "소스를 사용할 수 없습니다."
@@ -20792,6 +21466,9 @@ msgstr ""
msgid "Stack trace"
msgstr ""
+msgid "Stacktrace snippet"
+msgstr ""
+
msgid "Stage"
msgstr "스테ì´ì§€"
@@ -20897,6 +21574,9 @@ msgstr ""
msgid "Start thread & reopen %{noteable_name}"
msgstr ""
+msgid "Start using Directed Acyclic Graphs (DAG)"
+msgstr ""
+
msgid "Start your Free Gold Trial"
msgstr ""
@@ -21359,6 +22039,21 @@ msgstr ""
msgid "SuggestedColors|Very pale orange"
msgstr ""
+msgid "Suggestion is not applicable as the suggestion was not found."
+msgstr ""
+
+msgid "Suggestions are not applicable as one or more suggestions were not found."
+msgstr ""
+
+msgid "Suggestions are not applicable as their lines cannot overlap."
+msgstr ""
+
+msgid "Suggestions must all be on the same branch."
+msgstr ""
+
+msgid "Suggestions that change line count can't be added to batches, yet."
+msgstr ""
+
msgid "Suggestions:"
msgstr ""
@@ -21401,9 +22096,6 @@ msgstr ""
msgid "Synchronization disabled"
msgstr ""
-msgid "Synchronization of container repositories is disabled."
-msgstr ""
-
msgid "System"
msgstr ""
@@ -21584,6 +22276,32 @@ msgstr "서비스 약관 계약 ë° ê°œì¸ ì •ë³´ 보호 ì •ì±…"
msgid "Terms of Service and Privacy Policy"
msgstr "서비스 약관 ë° ê°œì¸ ì •ë³´ 보호 ì •ì±…"
+msgid "Terraform|%{number} Terraform report failed to generate"
+msgid_plural "Terraform|%{number} Terraform reports failed to generate"
+msgstr[0] ""
+
+msgid "Terraform|%{number} Terraform report was generated in your pipelines"
+msgid_plural "Terraform|%{number} Terraform reports were generated in your pipelines"
+msgstr[0] ""
+
+msgid "Terraform|A Terraform report failed to generate."
+msgstr ""
+
+msgid "Terraform|A Terraform report was generated in your pipelines."
+msgstr ""
+
+msgid "Terraform|Generating the report caused an error."
+msgstr ""
+
+msgid "Terraform|Reported Resource Changes: %{addNum} to add, %{changeNum} to change, %{deleteNum} to delete"
+msgstr ""
+
+msgid "Terraform|The Terraform report %{name} failed to generate."
+msgstr ""
+
+msgid "Terraform|The Terraform report %{name} was generated in your pipelines."
+msgstr ""
+
msgid "Test"
msgstr ""
@@ -21609,7 +22327,7 @@ msgstr ""
msgid "TestHooks|Ensure the project has CI pipelines."
msgstr ""
-msgid "TestHooks|Ensure the project has at least one commit."
+msgid "TestHooks|Ensure the project has deployments."
msgstr ""
msgid "TestHooks|Ensure the project has issues."
@@ -21709,7 +22427,7 @@ msgstr "ì´ìŠˆ 트래커는 프로ì íŠ¸ì—ì„œ 개선해야하거나 í•´ê²°í•´ì•
msgid "The Prometheus server responded with \"bad request\". Please check your queries are correct and are supported in your Prometheus version. %{documentationLink}"
msgstr ""
-msgid "The URL defined on the primary node that secondary nodes should use to contact it. Defaults to URL"
+msgid "The URL defined on the primary node that secondary nodes should use to contact it."
msgstr ""
msgid "The URL to use for connecting to Elasticsearch. Use a comma-separated list to support clustering (e.g., \"http://localhost:9200, http://localhost:9201\")."
@@ -21718,6 +22436,9 @@ msgstr ""
msgid "The X509 Certificate to use when mutual TLS is required to communicate with the external authorization service. If left blank, the server certificate is still validated when accessing over HTTPS."
msgstr ""
+msgid "The above settings apply to all projects with the selected compliance framework(s)."
+msgstr ""
+
msgid "The amount of seconds after which a request to get a secondary node status will time out."
msgstr ""
@@ -22016,9 +22737,6 @@ msgstr "해당 단계ì—ì„œ 수집 í•œ ê° ë°ì´í„° ìž…ë ¥ì— ì†Œìš” ëœ ì‹œê°„
msgid "The total stage shows the time it takes between creating an issue and deploying the code to production. The data will be automatically added once you have completed the full idea to production cycle."
msgstr ""
-msgid "The unique identifier for the Geo node. Must match %{geoNodeName} if it is set in gitlab.rb, otherwise it must match %{externalUrl} with a trailing slash"
-msgstr ""
-
msgid "The update action will time out after %{number_of_minutes} minutes. For big repositories, use a clone/push combination."
msgstr ""
@@ -22043,9 +22761,6 @@ msgstr ""
msgid "The user you are trying to deactivate has been active in the past %{minimum_inactive_days} days and cannot be deactivated"
msgstr ""
-msgid "The user-facing URL of the Geo node"
-msgstr ""
-
msgid "The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6."
msgstr "ê°’ì€ ì¼ë ¨ì˜ 관측 ê°’ 중ì ì— 있습니다. 예를 들어, 3, 5, 9 사ì´ì˜ 중간 ê°’ì€ 5입니다. 3, 5, 7, 8 사ì´ì˜ 중간 ê°’ì€ (5 + 7) / 2 = 6입니다."
@@ -22136,6 +22851,9 @@ msgstr ""
msgid "There was a problem fetching project branches."
msgstr ""
+msgid "There was a problem fetching project tags."
+msgstr ""
+
msgid "There was a problem fetching project users."
msgstr ""
@@ -22187,6 +22905,9 @@ msgstr ""
msgid "There was an error fetching the %{replicableType}"
msgstr ""
+msgid "There was an error fetching the Geo Settings"
+msgstr ""
+
msgid "There was an error fetching the Node's Groups"
msgstr ""
@@ -22307,6 +23028,12 @@ msgstr ""
msgid "This %{issuable} is locked. Only <strong>project members</strong> can comment."
msgstr ""
+msgid "This %{noteableTypeText} is %{confidentialLinkStart}confidential%{linkEnd} and %{lockedLinkStart}locked%{linkEnd}."
+msgstr ""
+
+msgid "This %{noteableTypeText} is locked."
+msgstr ""
+
msgid "This %{viewer} could not be displayed because %{reason}. You can %{options} instead."
msgstr ""
@@ -22325,6 +23052,9 @@ msgstr ""
msgid "This action can lead to data loss. To prevent accidental actions we ask you to confirm your intention."
msgstr ""
+msgid "This also resolves all related threads"
+msgstr ""
+
msgid "This also resolves the discussion"
msgstr ""
@@ -22415,6 +23145,9 @@ msgstr ""
msgid "This epic does not exist or you don't have sufficient permission."
msgstr ""
+msgid "This feature is currently in beta. We invite you to %{linkStart}give feedback%{linkEnd}."
+msgstr ""
+
msgid "This feature requires local storage to be enabled"
msgstr ""
@@ -22448,8 +23181,8 @@ msgstr ""
msgid "This is a Work in Progress"
msgstr ""
-msgid "This is a confidential issue."
-msgstr "비밀 ì´ìŠˆìž…니다."
+msgid "This is a confidential %{noteableTypeText}."
+msgstr ""
msgid "This is a delayed job to run in %{remainingTime}"
msgstr ""
@@ -22475,9 +23208,6 @@ msgstr ""
msgid "This is your current session"
msgstr ""
-msgid "This issue is %{confidentialLinkStart}confidential%{linkEnd} and %{lockedLinkStart}locked%{linkEnd}."
-msgstr ""
-
msgid "This issue is confidential"
msgstr "ì´ ì´ìŠˆëŠ” 비밀입니다."
@@ -22487,9 +23217,6 @@ msgstr ""
msgid "This issue is in a child epic of the filtered epic"
msgstr ""
-msgid "This issue is locked."
-msgstr "ì´ ì´ìŠˆëŠ” lock ë˜ì—ˆìŠµë‹ˆë‹¤."
-
msgid "This job depends on other jobs with expired/erased artifacts: %{invalid_dependencies}"
msgstr ""
@@ -22589,6 +23316,9 @@ msgstr ""
msgid "This license has already expired."
msgstr ""
+msgid "This link points to external content"
+msgstr ""
+
msgid "This may expose confidential information as the selected fork is in another namespace that can have other members."
msgstr ""
@@ -22616,6 +23346,9 @@ msgstr "여러 프로ì íŠ¸ì—ì„œ 정보를 ì½ì„ 수 없으므로 ì´ íŽ˜ì´ì§
msgid "This page will be removed in a future release."
msgstr "ì´ íŽ˜ì´ì§€ëŠ” 앞으로 릴리스ì—ì„œ ì œê±°ë  ì˜ˆì •ìž…ë‹ˆë‹¤."
+msgid "This pipeline does not use the %{codeStart}needs%{codeEnd} keyword and can't be represented as a directed acyclic graph."
+msgstr ""
+
msgid "This pipeline makes use of a predefined CI/CD configuration enabled by %{strongStart}Auto DevOps.%{strongEnd}"
msgstr ""
@@ -22844,6 +23577,9 @@ msgstr ""
msgid "Time from last commit to merge"
msgstr ""
+msgid "Time in seconds"
+msgstr ""
+
msgid "Time in seconds GitLab will wait for a response from the external service. When the service does not respond in time, access will be denied."
msgstr ""
@@ -23263,9 +23999,15 @@ msgstr "ì´ ê¸°ì—¬ë„"
msgid "Total artifacts size: %{total_size}"
msgstr ""
+msgid "Total cores (CPUs)"
+msgstr ""
+
msgid "Total issues"
msgstr ""
+msgid "Total memory (GB)"
+msgstr ""
+
msgid "Total test time for all commits/merges"
msgstr "모든 커밋 / ë¨¸ì§€ì˜ ì´ í…ŒìŠ¤íŠ¸ 시간"
@@ -23467,6 +24209,9 @@ msgstr ""
msgid "U2F only works with HTTPS-enabled websites. Contact your administrator for more details."
msgstr ""
+msgid "UI Development Kit"
+msgstr ""
+
msgid "URL"
msgstr "URL"
@@ -23539,9 +24284,6 @@ msgstr "ì°¨ì´ì ì„ ì½ì–´ë“¤ì¼ 수 없습니다. %{button_try_again}"
msgid "Unable to load the merge request widget. Try reloading the page."
msgstr ""
-msgid "Unable to process group import file"
-msgstr ""
-
msgid "Unable to resolve"
msgstr ""
@@ -23557,6 +24299,9 @@ msgstr ""
msgid "Unable to sign you in to the group with SAML due to \"%{reason}\""
msgstr ""
+msgid "Unable to suggest a path. Please refresh and try again."
+msgstr ""
+
msgid "Unable to update label prioritization at this time"
msgstr ""
@@ -23572,6 +24317,9 @@ msgstr ""
msgid "Unarchiving the project will restore people's ability to make changes to it. The repository can be committed to, and issues, comments, and other entities can be created. %{strong_start}Once active, this project shows up in the search and on the dashboard.%{strong_end}"
msgstr ""
+msgid "Unassign from commenting user"
+msgstr ""
+
msgid "Unblock"
msgstr ""
@@ -23647,6 +24395,9 @@ msgstr ""
msgid "Unreachable"
msgstr ""
+msgid "Unrecognized cluster type"
+msgstr ""
+
msgid "Unresolve"
msgstr ""
@@ -23707,6 +24458,9 @@ msgstr ""
msgid "Unsubscribes from this %{quick_action_target}."
msgstr ""
+msgid "Unsupported todo type passed. Supported todo types are: %{todo_types}"
+msgstr ""
+
msgid "Until"
msgstr ""
@@ -23734,6 +24488,9 @@ msgstr ""
msgid "Update approval rule"
msgstr ""
+msgid "Update approvers"
+msgstr ""
+
msgid "Update failed"
msgstr ""
@@ -23743,6 +24500,9 @@ msgstr ""
msgid "Update it"
msgstr ""
+msgid "Update iteration"
+msgstr ""
+
msgid "Update now"
msgstr "지금 ì—…ë°ì´íŠ¸"
@@ -23788,7 +24548,7 @@ msgstr ""
msgid "Updated at"
msgstr ""
-msgid "Updated to"
+msgid "Updated to %{linkStart}chart v%{linkEnd}"
msgstr ""
msgid "Updating"
@@ -23818,6 +24578,9 @@ msgstr ""
msgid "Upload CSV file"
msgstr "CSV íŒŒì¼ ì—…ë¡œë“œ"
+msgid "Upload License"
+msgstr ""
+
msgid "Upload New File"
msgstr "새 íŒŒì¼ ì—…ë¡œë“œ"
@@ -23899,16 +24662,19 @@ msgstr ""
msgid "UsageQuota|Pipelines"
msgstr ""
+msgid "UsageQuota|Purchase more storage"
+msgstr ""
+
msgid "UsageQuota|Repositories"
msgstr ""
msgid "UsageQuota|Repository"
msgstr ""
-msgid "UsageQuota|Storage"
+msgid "UsageQuota|Snippets"
msgstr ""
-msgid "UsageQuota|Storage usage:"
+msgid "UsageQuota|Storage"
msgstr ""
msgid "UsageQuota|This namespace has no projects which use shared runners"
@@ -23941,6 +24707,12 @@ msgstr ""
msgid "UsageQuota|Wikis"
msgstr ""
+msgid "UsageQuota|You used: %{usage} %{limit}"
+msgstr ""
+
+msgid "UsageQuota|out of %{formattedLimit} of your namespace storage"
+msgstr ""
+
msgid "Use %{code_start}::%{code_end} to create a %{link_start}scoped label set%{link_end} (eg. %{code_start}priority::1%{code_end})"
msgstr ""
@@ -24007,6 +24779,9 @@ msgstr ""
msgid "User IDs"
msgstr ""
+msgid "User Lists can only be created and modified with %{linkStart}the API%{linkEnd}"
+msgstr ""
+
msgid "User OAuth applications"
msgstr ""
@@ -24031,6 +24806,9 @@ msgstr ""
msgid "User key was successfully removed."
msgstr ""
+msgid "User list %{name} will be removed. Are you sure?"
+msgstr ""
+
msgid "User map"
msgstr ""
@@ -24052,148 +24830,10 @@ msgstr ""
msgid "User was successfully updated."
msgstr ""
-msgid "UserOnboardingTour|%{activeTour}/%{totalTours}"
-msgstr ""
-
-msgid "UserOnboardingTour|%{completed}/%{total} steps completed"
-msgstr ""
-
-msgid "UserOnboardingTour|%{emphasisStart}Well done!%{emphasisEnd}%{lineBreak}%{lineBreak}That's it for our guided tour, congratulations for making it all the way to the end!%{lineBreak}%{lineBreak}We hope this gave you a good overview of GitLab and how it can help you. We'll now show you how to create your own project and invite your colleagues."
-msgstr ""
-
-msgid "UserOnboardingTour|Adding other members to a project is done through Project Settings. Click on %{emphasisStart}Settings%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Alright, that's it for Commits. Let's take a look at the %{emphasisStart}Branches%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Awesome! Now click on %{emphasisStart}Members%{emphasisEnd}."
+msgid "UserList|Delete %{name}?"
msgstr ""
-msgid "UserOnboardingTour|Click on one of the %{emphasisStart}Compare%{emphasisEnd} buttons to compare a branch to master."
-msgstr ""
-
-msgid "UserOnboardingTour|Click on one of the %{emphasisStart}pipeline IDs%{emphasisEnd} to see the details of a pipeline."
-msgstr ""
-
-msgid "UserOnboardingTour|Click to open the latest commit to see its details."
-msgstr ""
-
-msgid "UserOnboardingTour|Close 'Learn GitLab'"
-msgstr ""
-
-msgid "UserOnboardingTour|Commits are shown in chronological order and can be filtered by the commit message or by the branch."
-msgstr ""
-
-msgid "UserOnboardingTour|Create a project"
-msgstr ""
-
-msgid "UserOnboardingTour|Exit 'Learn GitLab'"
-msgstr ""
-
-msgid "UserOnboardingTour|Got it"
-msgstr ""
-
-msgid "UserOnboardingTour|Great job! %{clapHands} We hope the tour was helpful and that you learned how to use GitLab.%{lineBreak}%{lineBreak}We'd love to get your feedback on this tour.%{lineBreak}%{lineBreak}%{emphasisStart}How helpful would you say this guided tour was?%{emphasisEnd}%{lineBreak}%{lineBreak}"
-msgstr ""
-
-msgid "UserOnboardingTour|Guided GitLab Tour"
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can compare the changes of this branch to another one. Changes are divided by files so that it's easier to see what was changed where."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can create a project from scratch, start with a template or import a repository from other platforms. Whatever you choose, we'll guide you through the process.%{lineBreak}%{lineBreak}Fill in your new project information and click on %{emphasisStart}Create Project%{emphasisEnd} to progress to the next step."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can see the breakdown of the pipelines: its stages and jobs in each of the stages and their status.%{lineBreak}%{lineBreak}Our CI/CD pipelines are quite complex, most of our users have fewer and simpler pipelines."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can see the current members of the project (just you at the moment) and invite new members.%{lineBreak}%{lineBreak}You can invite multiple members at once (existing GitLab users or invite by email) and you can also set their roles and permissions.%{lineBreak}%{lineBreak}Add a few members and click on %{emphasisStart}Add to project%{emphasisEnd} to complete this step."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can see what changes were made with this commit, on what branch and if there's a related merge request. The status of the pipeline will also show up if CI/CD is set up.%{lineBreak}%{lineBreak}You can also comment on the lines of code that were changed and start a discussion with your colleagues!"
-msgstr ""
-
-msgid "UserOnboardingTour|Here's an overview of branches in the %{emphasisStart}%{projectName}%{emphasisEnd} project. They're split into Active and Stale.%{lineBreak}%{lineBreak}From here, you can create a new merge request from a branch, or compare the branch to any other branch in the project. By default, it will compare it to the master branch."
-msgstr ""
-
-msgid "UserOnboardingTour|Invite colleagues"
-msgstr ""
-
-msgid "UserOnboardingTour|Issues are great for communicating and keeping track of progress in GitLab. These are all issues that are open in the %{emphasisStart}%{projectName}%{emphasisEnd}.%{lineBreak}%{lineBreak}You can help us improve GitLab by contributing work to issues that are labeled <span class=\"badge color-label accept-mr-label\">Accepting merge requests</span>.%{lineBreak}%{lineBreak}This list can be filtered by labels, milestones, assignees, authors... We'll show you how it looks when the list is filtered by a label."
-msgstr ""
-
-msgid "UserOnboardingTour|Learn GitLab"
-msgstr ""
-
-msgid "UserOnboardingTour|Let's take a closer look at a merge request. Click on the title of one."
-msgstr ""
-
-msgid "UserOnboardingTour|Let's take a closer look at all the commits. Click on %{emphasisStart}Commits%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Let's take a closer look at the repository of this project. Click on %{emphasisStart}Repository%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|No thanks"
-msgstr ""
-
-msgid "UserOnboardingTour|Ok, let's go"
-msgstr ""
-
-msgid "UserOnboardingTour|Ok, show me"
-msgstr ""
-
-msgid "UserOnboardingTour|Open one of the issues by clicking on its title."
-msgstr ""
-
-msgid "UserOnboardingTour|Restart this step"
-msgstr ""
-
-msgid "UserOnboardingTour|Skip this step"
-msgstr ""
-
-msgid "UserOnboardingTour|Sweet! Your project was created and is ready to be used.%{lineBreak}%{lineBreak}You can start adding files to the repository or clone it. One last thing we want to show you is how to invite your colleagues to your new project."
-msgstr ""
-
-msgid "UserOnboardingTour|Take a look. Here's a nifty menu for quickly creating issues, merge requests, snippets, projects and groups. Click on it and select \"New project\" from the \"GitLab\" section to get started."
-msgstr ""
-
-msgid "UserOnboardingTour|Thanks for taking the guided tour. Remember, if you want to go through it again, you can start %{emphasisStart}Learn GitLab%{emphasisEnd} in the help menu on the top right."
-msgstr ""
-
-msgid "UserOnboardingTour|Thanks for the feedback! %{thumbsUp}"
-msgstr ""
-
-msgid "UserOnboardingTour|That's it for issues. Let'st take a look at %{emphasisStart}Merge Requests%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|That's it for merge requests. Now for the final part of this guided tour - the %{emphasisStart}CI/CD%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|That's it for the Repository. Let's take a look at the %{emphasisStart}Issues%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|The structure of this page is very similar to issues. Status, description, discussion and the sidebar are all here.%{lineBreak}%{lineBreak}But take a look below the description and you'll notice that there's more information about the merge request, the CI/CD pipeline and the options for approving it.%{lineBreak}%{lineBreak}Alongside the discussion you can also see more information about commits in this merge request, the status of pipelines and review all changes that were made."
-msgstr ""
-
-msgid "UserOnboardingTour|There's a lot of information here but don't worry, we'll go through it.%{lineBreak}%{lineBreak}On the top you can see the status of the issue and when it was opened and by whom. Directly below it is the issue description and below that are other %{emphasisStart}related issues%{emphasisEnd} and %{emphasisStart}merge requests%{emphasisEnd} (if any). Then below that is the %{emphasisStart}discussion%{emphasisEnd}, that's where most of the communication happens.%{lineBreak}%{lineBreak}On the right, there's a sidebar where you can view/change the %{emphasisStart}assignee, milestone, due date, labels, weight%{emphasisEnd}, etc."
-msgstr ""
-
-msgid "UserOnboardingTour|These are all the CI/CD pipelines we have for our %{emphasisStart}%{projectName}%{emphasisEnd} project.%{lineBreak}%{lineBreak}Here you can see the status of each pipeline, for what commit it's running for, its stages and the status for them."
-msgstr ""
-
-msgid "UserOnboardingTour|These are all the issues that are available for community contributions. Let's take a closer look at one of them."
-msgstr ""
-
-msgid "UserOnboardingTour|This is an overview of all merge requests in this project. Similarly to the issues overview it can be filtered down by things like labels, milestones, authors, assignees, etc."
-msgstr ""
-
-msgid "UserOnboardingTour|This is the repository for the %{emphasisStart}%{projectName}%{emphasisEnd} project. All our code is stored here. Feel free to explore and take a closer look at folders and files.%{lineBreak}%{lineBreak}Above the file structure you can see the latest commit, who the author is and the status of the CI/CD pipeline.%{lineBreak}%{lineBreak}If you scroll down below the file structure, you'll find the Readme of this project. This is defined in the README.md file at the root of the repository."
-msgstr ""
-
-msgid "UserOnboardingTour|Welcome to the project overview of the %{emphasisStart}%{projectName}%{emphasisEnd} project. This is the project that we use to work on GitLab. At first, a project seems like a simple repository, but at GitLab, a project is so much more.%{lineBreak}%{lineBreak}You can create projects for hosting your codebase, use it as an issue tracker, collaborate on code, and continuously build, test, and deploy your app with built-in GitLab CI/CD."
+msgid "UserList|created %{timeago}"
msgstr ""
msgid "UserProfile|Activity"
@@ -24346,6 +24986,9 @@ msgstr ""
msgid "UsersSelect|Unassigned"
msgstr ""
+msgid "Using %{codeStart}needs%{codeEnd} allows jobs to run before their stage is reached, as soon as their individual dependencies are met, which speeds up your pipelines."
+msgstr ""
+
msgid "Using %{code_start}::%{code_end} denotes a %{link_start}scoped label set%{link_end}"
msgstr ""
@@ -24430,9 +25073,6 @@ msgstr "버전"
msgid "Versions"
msgstr ""
-msgid "Very helpful"
-msgstr ""
-
msgid "View Documentation"
msgstr ""
@@ -24579,6 +25219,9 @@ msgstr "Public"
msgid "VisibilityLevel|Unknown"
msgstr "ì•Œ 수 ì—†ìŒ"
+msgid "Visit settings page"
+msgstr ""
+
msgid "VisualReviewApp|%{stepStart}Step 1%{stepEnd}. Copy the following script:"
msgstr ""
@@ -24672,6 +25315,9 @@ msgstr ""
msgid "VulnerabilityManagement|Something went wrong while trying to delete the comment. Please try again later."
msgstr ""
+msgid "VulnerabilityManagement|Something went wrong while trying to refresh the vulnerability. Please try again later."
+msgstr ""
+
msgid "VulnerabilityManagement|Something went wrong while trying to retrieve the vulnerability history. Please try again later."
msgstr ""
@@ -24714,6 +25360,9 @@ msgstr ""
msgid "Vulnerability|Class"
msgstr ""
+msgid "Vulnerability|Crash Address"
+msgstr ""
+
msgid "Vulnerability|Description"
msgstr "설명"
@@ -24789,9 +25438,6 @@ msgstr ""
msgid "We couldn't reach the Prometheus server. Either the server no longer exists or the configuration details need updating."
msgstr ""
-msgid "We created a short guided tour that will help you learn the basics of GitLab and how it will help you be better at your job. It should only take a couple of minutes. You will be guided by two types of helpers, best recognized by their color."
-msgstr ""
-
msgid "We detected potential spam in the %{humanized_resource_name}. Please solve the reCAPTCHA to proceed."
msgstr "%{humanized_resource_name}ì—ì„œ 잠재ì ì¸ ìŠ¤íŒ¸ì„ íƒì§€í–ˆìŠµë‹ˆë‹¤. 계ì†í•˜ë ¤ë©´ reCAPTCHA를 진행하세요."
@@ -24825,6 +25471,9 @@ msgstr ""
msgid "We've found no vulnerabilities"
msgstr ""
+msgid "Web Application Firewall"
+msgstr ""
+
msgid "Web IDE"
msgstr "웹 IDE"
@@ -24885,9 +25534,6 @@ msgstr ""
msgid "Welcome to GitLab.com<br>@%{name}!"
msgstr ""
-msgid "Welcome to the Guided GitLab Tour"
-msgstr ""
-
msgid "Welcome to the guided GitLab tour"
msgstr ""
@@ -24934,9 +25580,6 @@ msgstr ""
msgid "While it's rare to have no vulnerabilities, it can happen. In any event, we ask that you please double check your settings to make sure you've set up your dashboard correctly."
msgstr ""
-msgid "White helpers give contextual information."
-msgstr ""
-
msgid "Who can be an approver?"
msgstr ""
@@ -24982,12 +25625,18 @@ msgstr "ì œëª©ì˜ ê²½ë¡œì™€ ê°™ì€ ê³³ì— íŽ˜ì´ì§€ê°€ ì´ë¯¸ 존재합니다."
msgid "WikiEmptyIssueMessage|Suggest wiki improvement"
msgstr ""
+msgid "WikiEmptyIssueMessage|You must be a group member in order to add wiki pages. If you have suggestions for how to improve the wiki for this group, consider opening an issue in the %{issues_link}."
+msgstr ""
+
msgid "WikiEmptyIssueMessage|You must be a project member in order to add wiki pages. If you have suggestions for how to improve the wiki for this project, consider opening an issue in the %{issues_link}."
msgstr ""
msgid "WikiEmptyIssueMessage|issue tracker"
msgstr ""
+msgid "WikiEmpty|A wiki is where you can store all the details about your group. This can include why you've created it, its principles, how to use it, and so on."
+msgstr ""
+
msgid "WikiEmpty|A wiki is where you can store all the details about your project. This can include why you've created it, its principles, how to use it, and so on."
msgstr ""
@@ -24997,12 +25646,21 @@ msgstr "첫 번째 페ì´ì§€ ìƒì„±í•˜ê¸°"
msgid "WikiEmpty|Suggest wiki improvement"
msgstr ""
+msgid "WikiEmpty|The wiki lets you write documentation for your group"
+msgstr ""
+
msgid "WikiEmpty|The wiki lets you write documentation for your project"
msgstr ""
+msgid "WikiEmpty|This group has no wiki pages"
+msgstr ""
+
msgid "WikiEmpty|This project has no wiki pages"
msgstr ""
+msgid "WikiEmpty|You must be a group member in order to add wiki pages."
+msgstr ""
+
msgid "WikiEmpty|You must be a project member in order to add wiki pages."
msgstr ""
@@ -25279,15 +25937,15 @@ msgstr ""
msgid "You can now export your security dashboard to a CSV report."
msgstr ""
+msgid "You can now manage alert endpoint configuration in the Alerts section on the Operations settings page. Fields on this page have been deprecated."
+msgstr ""
+
msgid "You can now submit a merge request to get this change into the original branch."
msgstr ""
msgid "You can now submit a merge request to get this change into the original project."
msgstr ""
-msgid "You can only add files when you are on a branch"
-msgstr "ë¸Œëžœì¹˜ì— ìžˆì„ ë•Œì—만 파ì¼ì„ 추가 í•  수 있습니다."
-
msgid "You can only edit files when you are on a branch"
msgstr "ë¸Œëžœì¹˜ì— ìžˆì„ ë•Œë§Œ 파ì¼ì„ 편집할 수 있습니다."
@@ -25507,7 +26165,7 @@ msgstr ""
msgid "You need to upload a Google Takeout archive."
msgstr ""
-msgid "You reached %{usage_in_percent} of %{namespace_name}'s capacity (%{used_storage} of %{storage_limit})"
+msgid "You reached %{usage_in_percent} of %{namespace_name}'s storage capacity (%{used_storage} of %{storage_limit})"
msgstr ""
msgid "You tried to fork %{link_to_the_project} but it failed for the following reason:"
@@ -25810,13 +26468,13 @@ msgstr ""
msgid "Your subscription expired!"
msgstr ""
-msgid "Your subscription has been downgraded"
+msgid "Your subscription has been downgraded."
msgstr ""
-msgid "Your subscription will automatically renew in %{remaining_days}"
+msgid "Your subscription will automatically renew in %{remaining_days}."
msgstr ""
-msgid "Your subscription will expire in %{remaining_days}"
+msgid "Your subscription will expire in %{remaining_days}."
msgstr ""
msgid "Zoom meeting added"
@@ -25895,9 +26553,6 @@ msgstr ""
msgid "branch name"
msgstr "브랜치 ì´ë¦„"
-msgid "build pipeline reference mismatch"
-msgstr ""
-
msgid "by"
msgstr "by"
@@ -25916,6 +26571,9 @@ msgstr ""
msgid "cannot block others"
msgstr ""
+msgid "cannot contain HTML/XML tags, including any word between angle brackets (<,>)."
+msgstr ""
+
msgid "cannot include leading slash or directory traversal."
msgstr ""
@@ -25946,50 +26604,6 @@ msgstr ""
msgid "ciReport|%{remainingPackagesCount} more"
msgstr ""
-msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability"
-msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities"
-msgstr[0] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability for the source branch only"
-msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities for the source branch only"
-msgstr[0] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerability"
-msgid_plural "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerabilities"
-msgstr[0] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new vulnerability"
-msgid_plural "ciReport|%{reportType} %{status} detected %{newCount} new vulnerabilities"
-msgstr[0] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities for the source branch only"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{fixedCount} fixed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} vulnerability for the source branch only"
-msgid_plural "ciReport|%{reportType} %{status} detected %{newCount} vulnerabilities for the source branch only"
-msgstr[0] ""
-
-msgid "ciReport|%{reportType} %{status} detected no new vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected no vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected no vulnerabilities for the source branch only"
-msgstr ""
-
msgid "ciReport|%{reportType} is loading"
msgstr ""
@@ -26008,7 +26622,7 @@ msgstr ""
msgid "ciReport|All projects"
msgstr ""
-msgid "ciReport|All report types"
+msgid "ciReport|All scanner types"
msgstr ""
msgid "ciReport|All severities"
@@ -26032,6 +26646,9 @@ msgstr ""
msgid "ciReport|Container scanning detects known vulnerabilities in your docker images."
msgstr ""
+msgid "ciReport|Coverage Fuzzing"
+msgstr ""
+
msgid "ciReport|Create a merge request to implement this solution, or download and apply the patch manually."
msgstr ""
@@ -26189,6 +26806,9 @@ msgstr ""
msgid "customize"
msgstr ""
+msgid "data"
+msgstr ""
+
msgid "date must not be after 9999-12-31"
msgstr ""
@@ -26257,6 +26877,9 @@ msgstr ""
msgid "entries cannot contain HTML tags"
msgstr ""
+msgid "epic"
+msgstr ""
+
msgid "error"
msgstr ""
@@ -26278,9 +26901,6 @@ msgstr ""
msgid "expires on %{timebox_due_date}"
msgstr ""
-msgid "external_url"
-msgstr ""
-
msgid "failed"
msgstr ""
@@ -26315,9 +26935,6 @@ msgstr ""
msgid "from"
msgstr ""
-msgid "geo_node_name"
-msgstr ""
-
msgid "group"
msgstr ""
@@ -26339,9 +26956,6 @@ msgstr "여기"
msgid "https://your-bitbucket-server"
msgstr "https://나ì˜-bitbucket-server"
-msgid "image"
-msgstr ""
-
msgid "image diff"
msgstr ""
@@ -26484,6 +27098,9 @@ msgstr ""
msgid "load it anyway"
msgstr ""
+msgid "loading"
+msgstr ""
+
msgid "locked by %{path_lock_user_name} %{created_at}"
msgstr ""
@@ -26611,9 +27228,6 @@ msgstr ""
msgid "mrWidget|Deployment statistics are not available currently"
msgstr "ë°°í¬ í†µê³„ëŠ” ì•„ì§ ì‚¬ìš©í•  수 없습니다."
-msgid "mrWidget|Detect issues before deployment with a CI pipeline that continuously tests your code. We created a quick guide that will show you how to create one. Make your code more secure and more robust in just a minute."
-msgstr ""
-
msgid "mrWidget|Did not close"
msgstr "닫히지 ì•ŠìŒ"
@@ -26776,6 +27390,9 @@ msgstr "ì´ í”„ë¡œì íŠ¸ëŠ” ë³´ê´€ë˜ì—ˆê³ , 쓰기 ì ‘ê·¼ì´ ë¹„í™œì„±í™”ë˜ì—ˆ
msgid "mrWidget|To approve this merge request, please enter your password. This project requires all approvals to be authenticated."
msgstr ""
+msgid "mrWidget|Use %{linkStart}CI pipelines to test your code%{linkEnd}, simply add a GitLab CI configuration file to your project. It only takes a minute to make your code more secure and robust."
+msgstr ""
+
msgid "mrWidget|When this merge request is ready, remove the WIP: prefix from the title to allow it to be merged"
msgstr ""
@@ -26812,6 +27429,9 @@ msgstr ""
msgid "must be greater than start date"
msgstr ""
+msgid "must contain only valid frameworks"
+msgstr ""
+
msgid "my-awesome-group"
msgstr ""
@@ -26824,6 +27444,9 @@ msgstr ""
msgid "needs to be between 10 minutes and 1 month"
msgstr ""
+msgid "never"
+msgstr ""
+
msgid "never expires"
msgstr ""
@@ -26851,6 +27474,9 @@ msgstr "알림 ì´ë©”ì¼"
msgid "nounSeries|%{firstItem} and %{lastItem}"
msgstr ""
+msgid "nounSeries|%{item}"
+msgstr ""
+
msgid "nounSeries|%{item}, %{nextItem}"
msgstr ""
@@ -26982,6 +27608,9 @@ msgstr ""
msgid "revised"
msgstr ""
+msgid "satisfied"
+msgstr ""
+
msgid "score"
msgstr ""
@@ -27012,6 +27641,9 @@ msgstr ""
msgid "severity|Unknown"
msgstr ""
+msgid "should be an array of %{object_name} objects"
+msgstr ""
+
msgid "should be greater than or equal to %{access} inherited membership from group %{group_name}"
msgstr ""
@@ -27066,10 +27698,13 @@ msgstr ""
msgid "suggestPipeline|2/2: Commit your changes"
msgstr ""
-msgid "suggestPipeline|Commit the changes and your pipeline will automatically run for the first time."
+msgid "suggestPipeline|Choose %{boldStart}Code Quality%{boldEnd} to add a pipeline that tests the quality of your code."
msgstr ""
-msgid "suggestPipeline|We recommend the %{boldStart}Code Quality%{boldEnd} template, which will add a report widget to your Merge Requests. This way you’ll learn about code quality degradations much sooner. %{footerStart} Goodbye technical debt! %{footerEnd}"
+msgid "suggestPipeline|The template is ready! You can now commit it to create your first pipeline."
+msgstr ""
+
+msgid "suggestPipeline|We’re adding a GitLab CI configuration file to add a pipeline to the project. You could create it manually, but we recommend that you start with a GitLab template that works out of the box."
msgstr ""
msgid "syntax is correct"
@@ -27129,6 +27764,9 @@ msgstr "사용ìžëª…"
msgid "uses Kubernetes clusters to deploy your code!"
msgstr "kubernetes í´ëŸ¬ìŠ¤í„°ë¥¼ 사용하여 코드를 ë°°í¬í•©ë‹ˆë‹¤!"
+msgid "v%{version} published %{timeAgo}"
+msgstr ""
+
msgid "verify ownership"
msgstr ""
diff --git a/locale/ku_TR/gitlab.po b/locale/ku_TR/gitlab.po
index 382100972bc..404ce52eb84 100644
--- a/locale/ku_TR/gitlab.po
+++ b/locale/ku_TR/gitlab.po
@@ -2,7 +2,7 @@ msgid ""
msgstr ""
"Project-Id-Version: gitlab-ee\n"
"Report-Msgid-Bugs-To: \n"
-"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Last-Translator: \n"
"Language-Team: Kurdish\n"
"Language: ku_TR\n"
"MIME-Version: 1.0\n"
@@ -10,9 +10,11 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Crowdin-Project: gitlab-ee\n"
+"X-Crowdin-Project-ID: 288872\n"
"X-Crowdin-Language: ku\n"
"X-Crowdin-File: /master/locale/gitlab.pot\n"
-"PO-Revision-Date: 2020-06-08 15:17\n"
+"X-Crowdin-File-ID: 6\n"
+"PO-Revision-Date: 2020-07-02 01:26\n"
msgid " %{start} to %{end}"
msgstr ""
@@ -197,6 +199,11 @@ msgid_plural "%d metrics"
msgstr[0] ""
msgstr[1] ""
+msgid "%d milestone"
+msgid_plural "%d milestones"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "%d minute"
msgid_plural "%d minutes"
msgstr[0] ""
@@ -242,11 +249,6 @@ msgid_plural "%d unresolved threads"
msgstr[0] ""
msgstr[1] ""
-msgid "%d url scanned"
-msgid_plural "%d urls scanned"
-msgstr[0] ""
-msgstr[1] ""
-
msgid "%d vulnerability"
msgid_plural "%d vulnerabilities"
msgstr[0] ""
@@ -324,9 +326,15 @@ msgstr[1] ""
msgid "%{count} related %{pluralized_subject}: %{links}"
msgstr ""
+msgid "%{dashboard_path} could not be found."
+msgstr ""
+
msgid "%{days} days until tags are automatically removed"
msgstr ""
+msgid "%{deployLinkStart}Use a template to deploy to ECS%{deployLinkEnd}, or use a docker image to %{commandsLinkStart}run AWS commands in GitLab CI/CD%{commandsLinkEnd}."
+msgstr ""
+
msgid "%{description}- Sentry event: %{errorUrl}- First seen: %{firstSeen}- Last seen: %{lastSeen} %{countLabel}: %{count}%{userCountLabel}: %{userCount}"
msgstr ""
@@ -369,12 +377,18 @@ msgstr ""
msgid "%{host} sign-in from new location"
msgstr ""
-msgid "%{icon}You are about to add %{usersTag} people to the discussion. Proceed with caution."
+msgid "%{icon}You are about to add %{usersTag} people to the discussion. They will all receive a notification."
+msgstr ""
+
+msgid "%{integrations_link_start}Integrations%{link_end} enable you to make third-party applications part of your GitLab workflow. If the available integrations don't meet your needs, consider using a %{webhooks_link_start}webhook%{link_end}."
msgstr ""
msgid "%{issuableType} will be removed! Are you sure?"
msgstr ""
+msgid "%{issuesCount} issues in this group"
+msgstr ""
+
msgid "%{issuesSize} issues"
msgstr ""
@@ -384,7 +398,7 @@ msgstr ""
msgid "%{labelStart}Class:%{labelEnd} %{class}"
msgstr ""
-msgid "%{labelStart}Confidence:%{labelEnd} %{confidence}"
+msgid "%{labelStart}Evidence:%{labelEnd} %{evidence}"
msgstr ""
msgid "%{labelStart}File:%{labelEnd} %{file}"
@@ -402,6 +416,9 @@ msgstr ""
msgid "%{labelStart}Report Type:%{labelEnd} %{reportType}"
msgstr ""
+msgid "%{labelStart}Scanner:%{labelEnd} %{scanner}"
+msgstr ""
+
msgid "%{labelStart}Severity:%{labelEnd} %{severity}"
msgstr ""
@@ -426,9 +443,6 @@ msgstr ""
msgid "%{link_start}Read more%{link_end} about role permissions"
msgstr ""
-msgid "%{link} can be used for binding events when something is happening within the project."
-msgstr ""
-
msgid "%{listToShow}, and %{awardsListLength} more."
msgstr ""
@@ -462,6 +476,9 @@ msgstr ""
msgid "%{name} found %{resultsString}"
msgstr ""
+msgid "%{name} is already being used for another emoji"
+msgstr ""
+
msgid "%{name} is scheduled for %{action}"
msgstr ""
@@ -509,6 +526,9 @@ msgstr ""
msgid "%{primary} (%{secondary})"
msgstr ""
+msgid "%{ref} cannot be added: %{error}"
+msgstr ""
+
msgid "%{releases} release"
msgid_plural "%{releases} releases"
msgstr[0] ""
@@ -517,13 +537,37 @@ msgstr[1] ""
msgid "%{remaining_approvals} left"
msgstr ""
-msgid "%{retryButtonStart}Try again%{retryButtonEnd} or %{newFileButtonStart}attach a new file%{newFileButtonEnd}"
+msgid "%{reportType} %{status} detected %{critical} critical and %{high} high severity vulnerabilities out of %{total}."
+msgstr ""
+
+msgid "%{reportType} %{status} detected %{critical} critical and %{high} high severity vulnerabilities."
+msgstr ""
+
+msgid "%{reportType} %{status} detected %{critical} critical severity vulnerabilities out of %{total}."
+msgstr ""
+
+msgid "%{reportType} %{status} detected %{critical} critical severity vulnerability."
+msgid_plural "%{reportType} %{status} detected %{critical} critical severity vulnerabilities."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{reportType} %{status} detected %{high} high severity vulnerabilities out of %{total}."
msgstr ""
-msgid "%{screenreaderOnlyStart}Keyboard shorcuts%{screenreaderOnlyEnd} Disabled"
+msgid "%{reportType} %{status} detected %{high} high severity vulnerability."
+msgid_plural "%{reportType} %{status} detected %{high} high severity vulnerabilities."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{reportType} %{status} detected %{other} vulnerability."
+msgid_plural "%{reportType} %{status} detected %{other} vulnerabilities."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{reportType} %{status} detected no new vulnerabilities."
msgstr ""
-msgid "%{screenreaderOnlyStart}Keyboard shorcuts%{screenreaderOnlyEnd} Enabled"
+msgid "%{retryButtonStart}Try again%{retryButtonEnd} or %{newFileButtonStart}attach a new file%{newFileButtonEnd}"
msgstr ""
msgid "%{service_title} %{message}."
@@ -620,6 +664,12 @@ msgstr ""
msgid "%{token}..."
msgstr ""
+msgid "%{totalCpu} (%{freeSpacePercentage}%{percentSymbol} free)"
+msgstr ""
+
+msgid "%{totalMemory} (%{freeSpacePercentage}%{percentSymbol} free)"
+msgstr ""
+
msgid "%{totalWeight} total weight"
msgstr ""
@@ -650,6 +700,12 @@ msgstr ""
msgid "%{verb} %{time_spent_value} spent time."
msgstr ""
+msgid "%{webhooks_link_start}%{webhook_type}%{link_end} enable you to send notifications to web applications in response to events in a group or project."
+msgstr ""
+
+msgid "%{webhooks_link_start}%{webhook_type}%{link_end} enable you to send notifications to web applications in response to events in a group or project. We recommend using an %{integrations_link_start}integration%{link_end} in preference to a webhook."
+msgstr ""
+
msgid "'%{level}' is not a valid visibility level"
msgstr ""
@@ -676,9 +732,6 @@ msgstr ""
msgid "(No changes)"
msgstr ""
-msgid "(Show all)"
-msgstr ""
-
msgid "(check progress)"
msgstr ""
@@ -745,6 +798,9 @@ msgstr ""
msgid "0 for unlimited"
msgstr ""
+msgid "0 for unlimited, only effective with remote storage enabled."
+msgstr ""
+
msgid "1 %{type} addition"
msgid_plural "%{count} %{type} additions"
msgstr[0] ""
@@ -892,6 +948,12 @@ msgstr ""
msgid "<code>\"johnsmith@example.com\": \"johnsmith@example.com\"</code> will add \"By <a href=\"#\">johnsmith@example.com</a>\" to all issues and comments originally created by johnsmith@example.com. By default, the email address or username is masked to ensure the user's privacy. Use this option if you want to show the full email address."
msgstr ""
+msgid "<code>Masked</code> to prevent the values from being displayed in job logs (must match certain regexp requirements)."
+msgstr ""
+
+msgid "<code>Protected</code> to expose them to protected branches or tags only."
+msgstr ""
+
msgid "<namespace / project>"
msgstr ""
@@ -919,9 +981,6 @@ msgstr ""
msgid "A .NET Core console application template, customizable for any .NET Core project"
msgstr ""
-msgid "A DAG must have two dependent jobs to be visualized on this tab."
-msgstr ""
-
msgid "A GitBook site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
msgstr ""
@@ -952,6 +1011,12 @@ msgstr ""
msgid "A deleted user"
msgstr ""
+msgid "A file has been changed."
+msgstr ""
+
+msgid "A file was not found."
+msgstr ""
+
msgid "A file with '%{file_name}' already exists in %{branch} branch"
msgstr ""
@@ -1018,7 +1083,7 @@ msgstr ""
msgid "A subscription will trigger a new pipeline on the default branch of this project when a pipeline successfully completes for a new tag on the %{default_branch_docs} of the subscribed project."
msgstr ""
-msgid "A terraform report was generated in your pipelines."
+msgid "A suggestion is not applicable."
msgstr ""
msgid "A user with write access to the source branch selected this option"
@@ -1189,6 +1254,9 @@ msgstr ""
msgid "Action to take when receiving an alert."
msgstr ""
+msgid "Actions"
+msgstr ""
+
msgid "Activate"
msgstr ""
@@ -1245,6 +1313,9 @@ msgstr ""
msgid "Add LICENSE"
msgstr ""
+msgid "Add New Node"
+msgstr ""
+
msgid "Add README"
msgstr ""
@@ -1320,6 +1391,9 @@ msgstr ""
msgid "Add approval rule"
msgstr ""
+msgid "Add approvers"
+msgstr ""
+
msgid "Add bold text"
msgstr ""
@@ -1377,6 +1451,9 @@ msgstr ""
msgid "Add strikethrough text"
msgstr ""
+msgid "Add suggestion to batch"
+msgstr ""
+
msgid "Add system hook"
msgstr ""
@@ -1476,6 +1553,9 @@ msgstr ""
msgid "Admin Note"
msgstr ""
+msgid "Admin Notifications"
+msgstr ""
+
msgid "Admin Overview"
msgstr ""
@@ -1587,6 +1667,9 @@ msgstr ""
msgid "AdminSettings|Integrations configured here will automatically apply to all projects on this instance."
msgstr ""
+msgid "AdminSettings|Moved to integrations"
+msgstr ""
+
msgid "AdminSettings|No required pipeline"
msgstr ""
@@ -1817,12 +1900,18 @@ msgid_plural "Alerts"
msgstr[0] ""
msgstr[1] ""
+msgid "AlertManagement| assign yourself"
+msgstr ""
+
msgid "AlertManagement|Acknowledged"
msgstr ""
msgid "AlertManagement|Alert"
msgstr ""
+msgid "AlertManagement|Alert assignee(s): %{assignees}"
+msgstr ""
+
msgid "AlertManagement|Alert detail"
msgstr ""
@@ -1838,9 +1927,15 @@ msgstr ""
msgid "AlertManagement|All alerts"
msgstr ""
+msgid "AlertManagement|Assign To"
+msgstr ""
+
msgid "AlertManagement|Assign status"
msgstr ""
+msgid "AlertManagement|Assignee"
+msgstr ""
+
msgid "AlertManagement|Assignees"
msgstr ""
@@ -1859,9 +1954,6 @@ msgstr ""
msgid "AlertManagement|Edit"
msgstr ""
-msgid "AlertManagement|End time"
-msgstr ""
-
msgid "AlertManagement|Events"
msgstr ""
@@ -1880,10 +1972,13 @@ msgstr ""
msgid "AlertManagement|More information"
msgstr ""
+msgid "AlertManagement|No Matching Results"
+msgstr ""
+
msgid "AlertManagement|No alert data to display."
msgstr ""
-msgid "AlertManagement|No alerts available to display. If you think you're seeing this message in error, refresh the page."
+msgid "AlertManagement|No alerts available to display. See %{linkStart}enabling alert management%{linkEnd} for more information on adding alerts to the list."
msgstr ""
msgid "AlertManagement|No alerts to display."
@@ -1892,6 +1987,9 @@ msgstr ""
msgid "AlertManagement|None"
msgstr ""
+msgid "AlertManagement|None -"
+msgstr ""
+
msgid "AlertManagement|Open"
msgstr ""
@@ -1928,9 +2026,18 @@ msgstr ""
msgid "AlertManagement|There was an error displaying the alerts. Confirm your endpoint's configuration details to ensure alerts appear."
msgstr ""
+msgid "AlertManagement|There was an error while updating the assignee(s) list. Please try again."
+msgstr ""
+
+msgid "AlertManagement|There was an error while updating the assignee(s) of the alert. Please try again."
+msgstr ""
+
msgid "AlertManagement|There was an error while updating the status of the alert. Please try again."
msgstr ""
+msgid "AlertManagement|This assignee cannot be assigned to this alert."
+msgstr ""
+
msgid "AlertManagement|Tool"
msgstr ""
@@ -2078,6 +2185,9 @@ msgstr ""
msgid "Allow users to request access (if visibility is public or internal)"
msgstr ""
+msgid "Allowed Geo IP"
+msgstr ""
+
msgid "Allowed email domain restriction only permitted for top-level groups"
msgstr ""
@@ -2111,6 +2221,9 @@ msgstr ""
msgid "Amazon Web Services"
msgstr ""
+msgid "Amazon Web Services Logo"
+msgstr ""
+
msgid "Amazon authentication is not %{link_start}correctly configured%{link_end}. Ask your GitLab administrator if you want to use this service."
msgstr ""
@@ -2168,10 +2281,16 @@ msgstr ""
msgid "An error occurred when updating the issue weight"
msgstr ""
+msgid "An error occurred while acknowledging the notification. Refresh the page and try again."
+msgstr ""
+
+msgid "An error occurred while adding approvers"
+msgstr ""
+
msgid "An error occurred while adding formatted title for epic"
msgstr ""
-msgid "An error occurred while checking group path"
+msgid "An error occurred while checking group path. Please refresh and try again."
msgstr ""
msgid "An error occurred while committing your changes."
@@ -2315,7 +2434,7 @@ msgstr ""
msgid "An error occurred while loading milestones"
msgstr ""
-msgid "An error occurred while loading terraform report"
+msgid "An error occurred while loading project creation UI"
msgstr ""
msgid "An error occurred while loading the data. Please try again."
@@ -2369,6 +2488,9 @@ msgstr ""
msgid "An error occurred while rendering preview broadcast message"
msgstr ""
+msgid "An error occurred while rendering the editor"
+msgstr ""
+
msgid "An error occurred while reordering issues."
msgstr ""
@@ -2465,7 +2587,7 @@ msgstr ""
msgid "Analyze your dependencies for known vulnerabilities."
msgstr ""
-msgid "Analyze your source code and git history for secrets"
+msgid "Analyze your source code and git history for secrets."
msgstr ""
msgid "Analyze your source code for known vulnerabilities."
@@ -2579,12 +2701,18 @@ msgstr ""
msgid "Apply suggestion"
msgstr ""
+msgid "Apply suggestions"
+msgstr ""
+
msgid "Apply template"
msgstr ""
msgid "Apply this approval rule to any branch or a specific protected branch."
msgstr ""
+msgid "Applying"
+msgstr ""
+
msgid "Applying a template will replace the existing issue description. Any changes you have made will be lost."
msgstr ""
@@ -2597,12 +2725,18 @@ msgstr ""
msgid "Applying multiple commands"
msgstr ""
-msgid "Applying suggestion"
+msgid "Applying suggestion..."
+msgstr ""
+
+msgid "Applying suggestions..."
msgstr ""
msgid "Approval rules"
msgstr ""
+msgid "Approval rules reset to project defaults"
+msgstr ""
+
msgid "ApprovalRuleRemove|%d member"
msgid_plural "ApprovalRuleRemove|%d members"
msgstr[0] ""
@@ -2656,6 +2790,9 @@ msgstr ""
msgid "Approved"
msgstr ""
+msgid "Approved MRs"
+msgstr ""
+
msgid "Approved by: "
msgstr ""
@@ -2869,6 +3006,9 @@ msgstr ""
msgid "Assign"
msgstr ""
+msgid "Assign Iteration"
+msgstr ""
+
msgid "Assign custom color like #FF0000"
msgstr ""
@@ -2887,6 +3027,9 @@ msgstr ""
msgid "Assign to"
msgstr ""
+msgid "Assign to commenting user"
+msgstr ""
+
msgid "Assign yourself to these issues"
msgstr ""
@@ -2913,6 +3056,9 @@ msgid_plural "%d Assignees"
msgstr[0] ""
msgstr[1] ""
+msgid "Assignee has no permissions"
+msgstr ""
+
msgid "Assignee lists not available with your current license"
msgstr ""
@@ -2922,6 +3068,9 @@ msgstr ""
msgid "Assignee(s)"
msgstr ""
+msgid "Assignees"
+msgstr ""
+
msgid "Assigns %{assignee_users_sentence}."
msgstr ""
@@ -2957,6 +3106,9 @@ msgstr ""
msgid "Audit Events is a way to keep track of important events that happened in GitLab."
msgstr ""
+msgid "Audit Log"
+msgstr ""
+
msgid "AuditEvents|(removed)"
msgstr ""
@@ -2993,6 +3145,9 @@ msgstr ""
msgid "AuditLogs|IP Address"
msgstr ""
+msgid "AuditLogs|Member Events"
+msgstr ""
+
msgid "AuditLogs|No matching %{type} found."
msgstr ""
@@ -3275,9 +3430,6 @@ msgstr ""
msgid "Badges|e.g. %{exampleUrl}"
msgstr ""
-msgid "Badge|New"
-msgstr ""
-
msgid "Balsamiq file could not be loaded."
msgstr ""
@@ -3326,6 +3478,12 @@ msgstr ""
msgid "Below you will find all the groups that are public."
msgstr ""
+msgid "Beta"
+msgstr ""
+
+msgid "Bi-weekly code coverage"
+msgstr ""
+
msgid "Billing"
msgstr ""
@@ -3386,9 +3544,15 @@ msgstr ""
msgid "Bitbucket Server Import"
msgstr ""
+msgid "Bitbucket Server import"
+msgstr ""
+
msgid "Bitbucket import"
msgstr ""
+msgid "Blame"
+msgstr ""
+
msgid "Blocked"
msgstr ""
@@ -3401,9 +3565,6 @@ msgstr ""
msgid "Blog"
msgstr ""
-msgid "Blue helpers indicate an action to be taken."
-msgstr ""
-
msgid "Board name"
msgstr ""
@@ -3602,6 +3763,9 @@ msgstr ""
msgid "Broadcast Message was successfully updated."
msgstr ""
+msgid "Broadcast Messages"
+msgstr ""
+
msgid "Browse Directory"
msgstr ""
@@ -3656,6 +3820,9 @@ msgstr ""
msgid "By %{user_name}"
msgstr ""
+msgid "By URL"
+msgstr ""
+
msgid "By default GitLab sends emails in HTML and plain text formats so mail clients can choose what format to use. Disable this option if you only want to send emails in plain text format."
msgstr ""
@@ -3761,6 +3928,9 @@ msgstr ""
msgid "Can override approvers and approvals required per merge request"
msgstr ""
+msgid "Can't apply as this line has changed or the suggestion already matches its content."
+msgstr ""
+
msgid "Can't create snippet: %{err}"
msgstr ""
@@ -3947,9 +4117,6 @@ msgstr ""
msgid "Changes are still tracked. Useful for cluster/index migrations."
msgstr ""
-msgid "Changes are unknown"
-msgstr ""
-
msgid "Changes suppressed. Click to show."
msgstr ""
@@ -4256,15 +4423,9 @@ msgstr ""
msgid "Choose what content you want to see on a group’s overview page"
msgstr ""
-msgid "Choose which groups you wish to synchronize to this secondary node"
-msgstr ""
-
msgid "Choose which repositories you want to connect and run CI/CD pipelines."
msgstr ""
-msgid "Choose which shards you wish to synchronize to this secondary node"
-msgstr ""
-
msgid "Choose your framework"
msgstr ""
@@ -4562,9 +4723,15 @@ msgstr ""
msgid "Cluster does not exist"
msgstr ""
+msgid "Cluster is required for Stages::ClusterEndpointInserter"
+msgstr ""
+
msgid "Cluster level"
msgstr ""
+msgid "Cluster type must be specificed for Stages::ClusterEndpointInserter"
+msgstr ""
+
msgid "ClusterIntegration| %{custom_domain_start}More information%{custom_domain_end}."
msgstr ""
@@ -4676,7 +4843,7 @@ msgstr ""
msgid "ClusterIntegration|Cert-Manager"
msgstr ""
-msgid "ClusterIntegration|Cert-Manager is a native Kubernetes certificate management controller that helps with issuing certificates. Installing Cert-Manager on your cluster will issue a certificate by %{letsEncrypt} and ensure that certificates are valid and up-to-date."
+msgid "ClusterIntegration|Cert-Manager is a native Kubernetes certificate management controller that helps with issuing certificates. Installing Cert-Manager on your cluster will issue a certificate by %{linkStart}Let's Encrypt%{linkEnd} and ensure that certificates are valid and up-to-date."
msgstr ""
msgid "ClusterIntegration|Certificate Authority bundle (PEM format)"
@@ -4703,9 +4870,6 @@ msgstr ""
msgid "ClusterIntegration|Clear the local cache of namespace and service accounts. This is necessary if your integration has become out of sync. The cache is repopulated during the next CI job that requires namespace and service accounts."
msgstr ""
-msgid "ClusterIntegration|Cloud Run"
-msgstr ""
-
msgid "ClusterIntegration|Cluster being created"
msgstr ""
@@ -4790,7 +4954,7 @@ msgstr ""
msgid "ClusterIntegration|Crossplane"
msgstr ""
-msgid "ClusterIntegration|Crossplane enables declarative provisioning of managed services from your cloud of choice using %{codeStart}kubectl%{codeEnd} or %{gitlabIntegrationLink}. Crossplane runs inside your Kubernetes cluster and supports secure connectivity and secrets management between app containers and the cloud services they depend on."
+msgid "ClusterIntegration|Crossplane enables declarative provisioning of managed services from your cloud of choice using %{codeStart}kubectl%{codeEnd} or %{linkStart}GitLab Integration%{linkEnd}. Crossplane runs inside your Kubernetes cluster and supports secure connectivity and secrets management between app containers and the cloud services they depend on."
msgstr ""
msgid "ClusterIntegration|Deletes all GitLab resources attached to this cluster during removal"
@@ -4874,9 +5038,6 @@ msgstr ""
msgid "ClusterIntegration|GitLab-managed cluster"
msgstr ""
-msgid "ClusterIntegration|Gitlab Integration"
-msgstr ""
-
msgid "ClusterIntegration|Global default"
msgstr ""
@@ -4922,7 +5083,7 @@ msgstr ""
msgid "ClusterIntegration|Ingress gives you a way to route requests to services based on the request host or path, centralizing a number of services into a single entrypoint."
msgstr ""
-msgid "ClusterIntegration|Installing Ingress may incur additional costs. Learn more about %{pricingLink}."
+msgid "ClusterIntegration|Installing Ingress may incur additional costs. Learn more about %{linkStart}pricing%{linkEnd}."
msgstr ""
msgid "ClusterIntegration|Instance cluster"
@@ -4937,7 +5098,7 @@ msgstr ""
msgid "ClusterIntegration|Issuer Email"
msgstr ""
-msgid "ClusterIntegration|Issuers represent a certificate authority. You must provide an email address for your Issuer. "
+msgid "ClusterIntegration|Issuers represent a certificate authority. You must provide an email address for your Issuer."
msgstr ""
msgid "ClusterIntegration|Jupyter Hostname"
@@ -5009,9 +5170,6 @@ msgstr ""
msgid "ClusterIntegration|Learn more about instance Kubernetes clusters"
msgstr ""
-msgid "ClusterIntegration|Let's Encrypt"
-msgstr ""
-
msgid "ClusterIntegration|Loading IAM Roles"
msgstr ""
@@ -5123,7 +5281,7 @@ msgstr ""
msgid "ClusterIntegration|Prometheus"
msgstr ""
-msgid "ClusterIntegration|Prometheus is an open-source monitoring system with %{gitlabIntegrationLink} to monitor deployed applications."
+msgid "ClusterIntegration|Prometheus is an open-source monitoring system with %{linkStart}GitLab Integration%{linkEnd} to monitor deployed applications."
msgstr ""
msgid "ClusterIntegration|Provider details"
@@ -5279,10 +5437,10 @@ msgstr ""
msgid "ClusterIntegration|Select zone to choose machine type"
msgstr ""
-msgid "ClusterIntegration|Send Cilium Logs"
+msgid "ClusterIntegration|Send Container Network Policies Logs"
msgstr ""
-msgid "ClusterIntegration|Send ModSecurity Logs"
+msgid "ClusterIntegration|Send Web Application Firewall Logs"
msgstr ""
msgid "ClusterIntegration|Service Token"
@@ -5450,15 +5608,12 @@ msgstr ""
msgid "ClusterIntegration|documentation"
msgstr ""
-msgid "ClusterIntegration|installed via %{installed_via}"
+msgid "ClusterIntegration|installed via %{linkStart}Cloud Run%{linkEnd}"
msgstr ""
msgid "ClusterIntegration|meets the requirements"
msgstr ""
-msgid "ClusterIntegration|pricing"
-msgstr ""
-
msgid "ClusterIntegration|sign up"
msgstr ""
@@ -5495,6 +5650,15 @@ msgstr ""
msgid "Code"
msgstr ""
+msgid "Code Coverage: %{coveragePercentage}%{percentSymbol}"
+msgstr ""
+
+msgid "Code Coverage| Empty code coverage data"
+msgstr ""
+
+msgid "Code Coverage|Couldn't fetch the code coverage data"
+msgstr ""
+
msgid "Code Owners"
msgstr ""
@@ -5549,7 +5713,7 @@ msgstr ""
msgid "Collapse approvers"
msgstr ""
-msgid "Collapse child epics"
+msgid "Collapse milestones"
msgstr ""
msgid "Collapse replies"
@@ -5567,6 +5731,9 @@ msgstr ""
msgid "Coming soon"
msgstr ""
+msgid "Comma-separated, e.g. '1.1.1.1, 2.2.2.0/24'"
+msgstr ""
+
msgid "Command"
msgstr ""
@@ -5737,6 +5904,12 @@ msgstr ""
msgid "Compliance framework (optional)"
msgstr ""
+msgid "Compliance frameworks"
+msgstr ""
+
+msgid "ComplianceDashboard|created by:"
+msgstr ""
+
msgid "ComplianceFramework|GDPR"
msgstr ""
@@ -5875,6 +6048,9 @@ msgstr ""
msgid "Connection timed out"
msgstr ""
+msgid "Connection timeout"
+msgstr ""
+
msgid "Contact sales to upgrade"
msgstr ""
@@ -5930,9 +6106,6 @@ msgstr ""
msgid "ContainerRegistry|CLI Commands"
msgstr ""
-msgid "ContainerRegistry|Compressed Size"
-msgstr ""
-
msgid "ContainerRegistry|Container Registry"
msgstr ""
@@ -5948,6 +6121,9 @@ msgstr ""
msgid "ContainerRegistry|Copy push command"
msgstr ""
+msgid "ContainerRegistry|Delete selected"
+msgstr ""
+
msgid "ContainerRegistry|Docker connection error"
msgstr ""
@@ -5981,16 +6157,16 @@ msgstr ""
msgid "ContainerRegistry|If you are not already logged in, you need to authenticate to the Container Registry by using your GitLab username and password. If you have %{twofaDocLinkStart}Two-Factor Authentication%{twofaDocLinkEnd} enabled, use a %{personalAccessTokensDocLinkStart}Personal Access Token%{personalAccessTokensDocLinkEnd} instead of a password."
msgstr ""
-msgid "ContainerRegistry|Image ID"
+msgid "ContainerRegistry|Image ID: %{imageId}"
msgstr ""
msgid "ContainerRegistry|Image Repositories"
msgstr ""
-msgid "ContainerRegistry|Keep and protect the images that matter most."
+msgid "ContainerRegistry|Image tags"
msgstr ""
-msgid "ContainerRegistry|Last Updated"
+msgid "ContainerRegistry|Keep and protect the images that matter most."
msgstr ""
msgid "ContainerRegistry|Login"
@@ -6005,6 +6181,9 @@ msgstr ""
msgid "ContainerRegistry|Please contact your administrator."
msgstr ""
+msgid "ContainerRegistry|Published %{timeInfo}"
+msgstr ""
+
msgid "ContainerRegistry|Push an image"
msgstr ""
@@ -6020,9 +6199,6 @@ msgstr ""
msgid "ContainerRegistry|Remove repository"
msgstr ""
-msgid "ContainerRegistry|Remove selected tags"
-msgstr ""
-
msgid "ContainerRegistry|Remove tag"
msgid_plural "ContainerRegistry|Remove tags"
msgstr[0] ""
@@ -6052,9 +6228,6 @@ msgstr ""
msgid "ContainerRegistry|Sorry, your filter produced no results."
msgstr ""
-msgid "ContainerRegistry|Tag"
-msgstr ""
-
msgid "ContainerRegistry|Tag expiration policy"
msgstr ""
@@ -6199,21 +6372,6 @@ msgstr ""
msgid "Control the display of third party offers."
msgstr ""
-msgid "Control the maximum concurrency of LFS/attachment backfill for this secondary node"
-msgstr ""
-
-msgid "Control the maximum concurrency of container repository operations for this Geo node"
-msgstr ""
-
-msgid "Control the maximum concurrency of repository backfill for this secondary node"
-msgstr ""
-
-msgid "Control the maximum concurrency of verification operations for this Geo node"
-msgstr ""
-
-msgid "Control the minimum interval in days that a repository should be reverified for this primary node"
-msgstr ""
-
msgid "Cookie domain"
msgstr ""
@@ -6355,6 +6513,9 @@ msgstr ""
msgid "Could not find design."
msgstr ""
+msgid "Could not find iteration"
+msgstr ""
+
msgid "Could not remove the trigger."
msgstr ""
@@ -6391,6 +6552,12 @@ msgstr ""
msgid "Coverage"
msgstr ""
+msgid "Coverage Fuzzing"
+msgstr ""
+
+msgid "Crash State"
+msgstr ""
+
msgid "Create"
msgstr ""
@@ -6439,6 +6606,9 @@ msgstr ""
msgid "Create a personal access token on your account to pull or push via %{protocol}."
msgstr ""
+msgid "Create a project pre-populated with the necessary files to get you started quickly."
+msgstr ""
+
msgid "Create an account using:"
msgstr ""
@@ -6607,6 +6777,9 @@ msgstr ""
msgid "Creates branch '%{branch_name}' and a merge request to resolve this issue."
msgstr ""
+msgid "Creating"
+msgstr ""
+
msgid "Creating epic"
msgstr ""
@@ -6658,7 +6831,10 @@ msgstr ""
msgid "Current vulnerabilities count"
msgstr ""
-msgid "CurrentUser|Buy CI minutes"
+msgid "CurrentUser|Buy Pipeline minutes"
+msgstr ""
+
+msgid "CurrentUser|One of your groups is running out"
msgstr ""
msgid "CurrentUser|Profile"
@@ -6673,6 +6849,9 @@ msgstr ""
msgid "CurrentUser|Upgrade"
msgstr ""
+msgid "Custom Attributes"
+msgstr ""
+
msgid "Custom CI configuration path"
msgstr ""
@@ -6936,12 +7115,18 @@ msgstr ""
msgid "DAG"
msgstr ""
+msgid "DAG visualization requires at least 3 dependent jobs."
+msgstr ""
+
msgid "DNS"
msgstr ""
msgid "Dashboard"
msgstr ""
+msgid "Dashboard uid not found"
+msgstr ""
+
msgid "DashboardProjects|All"
msgstr ""
@@ -6966,6 +7151,9 @@ msgstr ""
msgid "Data is still calculating..."
msgstr ""
+msgid "Datasource name not found"
+msgstr ""
+
msgid "Date"
msgstr ""
@@ -7146,6 +7334,9 @@ msgstr ""
msgid "Delete this attachment"
msgstr ""
+msgid "Delete user list"
+msgstr ""
+
msgid "Delete variable"
msgstr ""
@@ -7462,6 +7653,9 @@ msgstr ""
msgid "Deploying to"
msgstr ""
+msgid "Deploying to AWS is easy with GitLab"
+msgstr ""
+
msgid "Deployment Frequency"
msgstr ""
@@ -7824,6 +8018,9 @@ msgstr ""
msgid "Dismissed on pipeline %{pipelineLink} at %{projectLink}"
msgstr ""
+msgid "Display alerts from all your monitoring tools directly within GitLab."
+msgstr ""
+
msgid "Display name"
msgstr ""
@@ -7881,9 +8078,6 @@ msgstr ""
msgid "Don't show again"
msgstr ""
-msgid "Don't worry, you can access this tour by clicking on the help icon in the top right corner and choose <strong>Learn GitLab</strong>."
-msgstr ""
-
msgid "Done"
msgstr ""
@@ -8010,6 +8204,9 @@ msgstr ""
msgid "Edit Release"
msgstr ""
+msgid "Edit Slack integration"
+msgstr ""
+
msgid "Edit Snippet"
msgstr ""
@@ -8049,6 +8246,9 @@ msgstr ""
msgid "Edit issues"
msgstr ""
+msgid "Edit iteration"
+msgstr ""
+
msgid "Edit public deploy key"
msgstr ""
@@ -8097,6 +8297,9 @@ msgstr ""
msgid "Email"
msgstr ""
+msgid "Email Notification"
+msgstr ""
+
msgid "Email address"
msgstr ""
@@ -8109,6 +8312,9 @@ msgstr ""
msgid "Email not verified. Please verify your email in Salesforce."
msgstr ""
+msgid "Email notification for unknown sign-ins"
+msgstr ""
+
msgid "Email patch"
msgstr ""
@@ -8340,6 +8546,9 @@ msgstr ""
msgid "Enforce DNS rebinding attack protection"
msgstr ""
+msgid "Enforce personal access token expiration"
+msgstr ""
+
msgid "Ensure connectivity is available from the GitLab server to the Prometheus server"
msgstr ""
@@ -8409,6 +8618,9 @@ msgstr ""
msgid "Enter the name of your application, and we'll return a unique %{type}."
msgstr ""
+msgid "Enter weights for storages for new repositories."
+msgstr ""
+
msgid "Enter your password to approve"
msgstr ""
@@ -8418,15 +8630,27 @@ msgstr ""
msgid "Environment does not have deployments"
msgstr ""
+msgid "Environment is required for Stages::MetricEndpointInserter"
+msgstr ""
+
+msgid "Environment is required for Stages::VariableEndpointInserter"
+msgstr ""
+
msgid "Environment scope"
msgstr ""
+msgid "Environment variables are applied to all project environments in this instance via the Runner. You can use environment variables for passwords, secret keys, etc. Make variables available to the running application by prepending the variable key with <code>K8S_SECRET_</code>. You can set variables to be:"
+msgstr ""
+
msgid "Environment variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. Additionally, they can be masked so they are hidden in job logs, though they must match certain regexp requirements to do so. You can use environment variables for passwords, secret keys, or whatever you want."
msgstr ""
msgid "Environment variables are configured by your administrator to be %{link_start}protected%{link_end} by default"
msgstr ""
+msgid "Environment variables on this GitLab instance are configured to be %{link_start}protected%{link_end} by default"
+msgstr ""
+
msgid "Environment:"
msgstr ""
@@ -8958,6 +9182,9 @@ msgstr ""
msgid "EventFilterBy|Filter by comments"
msgstr ""
+msgid "EventFilterBy|Filter by designs"
+msgstr ""
+
msgid "EventFilterBy|Filter by epic events"
msgstr ""
@@ -9090,15 +9317,15 @@ msgstr ""
msgid "Expand approvers"
msgstr ""
-msgid "Expand child epics"
-msgstr ""
-
msgid "Expand down"
msgstr ""
msgid "Expand dropdown"
msgstr ""
+msgid "Expand milestones"
+msgstr ""
+
msgid "Expand sidebar"
msgstr ""
@@ -9114,6 +9341,9 @@ msgstr ""
msgid "Expiration date"
msgstr ""
+msgid "Expiration not enforced"
+msgstr ""
+
msgid "Expiration policy for the Container Registry is a perfect solution for keeping the Registry space down while still enjoying the full power of GitLab CI/CD."
msgstr ""
@@ -9234,6 +9464,9 @@ msgstr ""
msgid "Failed Jobs"
msgstr ""
+msgid "Failed on"
+msgstr ""
+
msgid "Failed to add a Zoom meeting"
msgstr ""
@@ -9294,13 +9527,19 @@ msgstr ""
msgid "Failed to install."
msgstr ""
+msgid "Failed to load assignees. Please try again."
+msgstr ""
+
+msgid "Failed to load authors. Please try again."
+msgstr ""
+
msgid "Failed to load emoji list."
msgstr ""
msgid "Failed to load error details from Sentry."
msgstr ""
-msgid "Failed to load errors from Sentry. Error message: %{errorMessage}"
+msgid "Failed to load errors from Sentry."
msgstr ""
msgid "Failed to load group activity metrics. Please try again."
@@ -9381,6 +9620,9 @@ msgstr ""
msgid "Failed to set due date because the date format is invalid."
msgstr ""
+msgid "Failed to set iteration on this issue. Please try again."
+msgstr ""
+
msgid "Failed to signing using smartcard authentication"
msgstr ""
@@ -9603,13 +9845,10 @@ msgstr ""
msgid "FeatureFlags|Target environments"
msgstr ""
-msgid "FeatureFlags|There are no active feature flags"
-msgstr ""
-
-msgid "FeatureFlags|There are no inactive feature flags"
+msgid "FeatureFlags|There was an error fetching the feature flags."
msgstr ""
-msgid "FeatureFlags|There was an error fetching the feature flags."
+msgid "FeatureFlags|There was an error retrieving user lists"
msgstr ""
msgid "FeatureFlags|Try again in a few moments or contact your support team."
@@ -9621,9 +9860,18 @@ msgstr ""
msgid "FeatureFlag|Delete strategy"
msgstr ""
+msgid "FeatureFlag|List"
+msgstr ""
+
msgid "FeatureFlag|Percentage"
msgstr ""
+msgid "FeatureFlag|Select a user list"
+msgstr ""
+
+msgid "FeatureFlag|There are no configured user lists"
+msgstr ""
+
msgid "FeatureFlag|Type"
msgstr ""
@@ -9780,6 +10028,12 @@ msgstr ""
msgid "Filter..."
msgstr ""
+msgid "Find File"
+msgstr ""
+
+msgid "Find bugs in your code with coverage-guided fuzzing"
+msgstr ""
+
msgid "Find by path"
msgstr ""
@@ -10044,6 +10298,9 @@ msgstr ""
msgid "Geo allows you to replicate your GitLab instance to other geographical locations."
msgstr ""
+msgid "Geo nodes are paused using a command run on the node"
+msgstr ""
+
msgid "GeoNodeStatusEvent|%{timeAgoStr} (%{pendingEvents} events)"
msgstr ""
@@ -10128,6 +10385,9 @@ msgstr ""
msgid "GeoNodes|Not checksummed"
msgstr ""
+msgid "GeoNodes|Package files"
+msgstr ""
+
msgid "GeoNodes|Pausing replication stops the sync process. Are you sure?"
msgstr ""
@@ -10146,6 +10406,9 @@ msgstr ""
msgid "GeoNodes|Replication slots"
msgstr ""
+msgid "GeoNodes|Replication status"
+msgstr ""
+
msgid "GeoNodes|Repositories"
msgstr ""
@@ -10356,6 +10619,9 @@ msgstr ""
msgid "Geo|Synchronization failed - %{error}"
msgstr ""
+msgid "Geo|Synchronization of %{itemTitle} is disabled."
+msgstr ""
+
msgid "Geo|The database is currently %{db_lag} behind the primary node."
msgstr ""
@@ -10668,6 +10934,9 @@ msgstr ""
msgid "Go to %{link_to_google_takeout}."
msgstr ""
+msgid "Go to <strong>Issues</strong> > <strong>Boards</strong> to access your personalized learning issue board."
+msgstr ""
+
msgid "Go to Pipelines"
msgstr ""
@@ -10767,9 +11036,6 @@ msgstr ""
msgid "Go to your snippets"
msgstr ""
-msgid "Golden Tanuki"
-msgstr ""
-
msgid "Google Cloud Platform"
msgstr ""
@@ -10791,6 +11057,9 @@ msgstr ""
msgid "Grafana URL"
msgstr ""
+msgid "Grafana response contains invalid json"
+msgstr ""
+
msgid "GrafanaIntegration|API Token"
msgstr ""
@@ -10839,9 +11108,6 @@ msgstr ""
msgid "Group %{group_name} was successfully created."
msgstr ""
-msgid "Group '%{group_name}' is being imported."
-msgstr ""
-
msgid "Group Audit Events"
msgstr ""
@@ -10878,9 +11144,6 @@ msgstr ""
msgid "Group by:"
msgstr ""
-msgid "Group could not be imported: %{errors}"
-msgstr ""
-
msgid "Group description"
msgstr ""
@@ -10908,9 +11171,15 @@ msgstr ""
msgid "Group has not been marked for deletion"
msgstr ""
+msgid "Group import could not be scheduled"
+msgstr ""
+
msgid "Group info:"
msgstr ""
+msgid "Group is required when cluster_type is :group"
+msgstr ""
+
msgid "Group maintainers can register group runners in the %{link}"
msgstr ""
@@ -10974,6 +11243,24 @@ msgstr ""
msgid "GroupActivyMetrics|Recent activity (last 90 days)"
msgstr ""
+msgid "GroupImport|Failed to import group."
+msgstr ""
+
+msgid "GroupImport|Group '%{group_name}' is being imported."
+msgstr ""
+
+msgid "GroupImport|Group could not be imported: %{errors}"
+msgstr ""
+
+msgid "GroupImport|Please wait while we import the group for you. Refresh at will."
+msgstr ""
+
+msgid "GroupImport|The group was successfully imported."
+msgstr ""
+
+msgid "GroupImport|Unable to process group import file"
+msgstr ""
+
msgid "GroupRoadmap|%{dateWord} – No end date"
msgstr ""
@@ -11241,6 +11528,9 @@ msgstr ""
msgid "Groups (%{groups})"
msgstr ""
+msgid "Groups and projects"
+msgstr ""
+
msgid "Groups can also be nested by creating %{subgroup_docs_link_start}subgroups%{subgroup_docs_link_end}."
msgstr ""
@@ -11286,6 +11576,33 @@ msgstr ""
msgid "GroupsEmptyState|You can manage your group member’s permissions and access to each project in the group."
msgstr ""
+msgid "GroupsNew|Contact an administrator to enable options for importing your group."
+msgstr ""
+
+msgid "GroupsNew|Create"
+msgstr ""
+
+msgid "GroupsNew|Create group"
+msgstr ""
+
+msgid "GroupsNew|GitLab group export"
+msgstr ""
+
+msgid "GroupsNew|Import"
+msgstr ""
+
+msgid "GroupsNew|Import group"
+msgstr ""
+
+msgid "GroupsNew|My Awesome Group"
+msgstr ""
+
+msgid "GroupsNew|No import options available"
+msgstr ""
+
+msgid "GroupsNew|To copy a GitLab group between installations, navigate to the group settings page for the original installation, generate an export file, and upload it here."
+msgstr ""
+
msgid "GroupsTree|Are you sure you want to leave the \"%{fullName}\" group?"
msgstr ""
@@ -11388,6 +11705,9 @@ msgstr ""
msgid "Helps reduce request volume for protected paths"
msgstr ""
+msgid "Here are all your projects in your group, including the one you just created. To start, let’s take a look at your personalized learning project which will help you learn about GitLab at your own pace."
+msgstr ""
+
msgid "Here you will find recent merge request activity"
msgstr ""
@@ -11402,6 +11722,9 @@ msgid_plural "Hide charts"
msgstr[0] ""
msgstr[1] ""
+msgid "Hide details"
+msgstr ""
+
msgid "Hide file browser"
msgstr ""
@@ -11411,6 +11734,9 @@ msgstr ""
msgid "Hide host keys manual input"
msgstr ""
+msgid "Hide list"
+msgstr ""
+
msgid "Hide marketing-related entries from help"
msgstr ""
@@ -11431,9 +11757,6 @@ msgstr[1] ""
msgid "Hide values"
msgstr ""
-msgid "Hiding all labels"
-msgstr ""
-
msgid "High or unknown vulnerabilities present"
msgstr ""
@@ -11518,6 +11841,9 @@ msgstr ""
msgid "ID:"
msgstr ""
+msgid "IDE"
+msgstr ""
+
msgid "IDE|Allow live previews of JavaScript projects in the Web IDE using CodeSandbox Live Preview."
msgstr ""
@@ -11608,10 +11934,10 @@ msgstr ""
msgid "If enabled"
msgstr ""
-msgid "If enabled, access to projects will be validated on an external service using their classification label."
+msgid "If enabled, GitLab will handle Object Storage replication using Geo. %{linkStart}More information%{linkEnd}"
msgstr ""
-msgid "If enabled, and if object storage is enabled, GitLab will handle Object Storage replication using Geo"
+msgid "If enabled, access to projects will be validated on an external service using their classification label."
msgstr ""
msgid "If there is no previous license or if the previous license has expired, some GitLab functionality will be blocked until a new, valid license is uploaded."
@@ -11973,6 +12299,9 @@ msgid_plural "Instances"
msgstr[0] ""
msgstr[1] ""
+msgid "Instance Configuration"
+msgstr ""
+
msgid "Instance Statistics visibility"
msgstr ""
@@ -11994,9 +12323,6 @@ msgstr ""
msgid "Integrations"
msgstr ""
-msgid "Integrations allow you to integrate GitLab with other applications"
-msgstr ""
-
msgid "Integrations|All details"
msgstr ""
@@ -12108,6 +12434,9 @@ msgstr ""
msgid "Invalid query"
msgstr ""
+msgid "Invalid repository bundle for snippet with id %{snippet_id}"
+msgstr ""
+
msgid "Invalid repository path"
msgstr ""
@@ -12126,6 +12455,9 @@ msgstr ""
msgid "Invalid two-factor code."
msgstr ""
+msgid "Invalid yaml"
+msgstr ""
+
msgid "Invitation"
msgstr ""
@@ -12345,15 +12677,24 @@ msgstr ""
msgid "It seems like the Dependency Scanning job ran successfully, but no dependencies have been detected in your project."
msgstr ""
+msgid "It seems that there is currently no available data for code coverage"
+msgstr ""
+
msgid "It's you"
msgstr ""
+msgid "Iteration"
+msgstr ""
+
msgid "Iteration changed to"
msgstr ""
msgid "Iteration removed"
msgstr ""
+msgid "Iteration updated"
+msgstr ""
+
msgid "Iterations"
msgstr ""
@@ -12384,12 +12725,18 @@ msgstr ""
msgid "January"
msgstr ""
+msgid "Jira Issues"
+msgstr ""
+
msgid "Jira import is already running."
msgstr ""
msgid "Jira integration not configured."
msgstr ""
+msgid "Jira project key is not configured"
+msgstr ""
+
msgid "Jira project: %{importProject}"
msgstr ""
@@ -12570,6 +12917,9 @@ msgstr ""
msgid "Keep divergent refs"
msgstr ""
+msgid "Kerberos access denied"
+msgstr ""
+
msgid "Key"
msgstr ""
@@ -12579,10 +12929,13 @@ msgstr ""
msgid "Key: %{key}"
msgstr ""
-msgid "Keyboard Shortcuts"
+msgid "Keyboard shortcuts"
msgstr ""
-msgid "Keyboard shortcuts"
+msgid "Keys"
+msgstr ""
+
+msgid "Ki"
msgstr ""
msgid "Kubernetes"
@@ -12624,6 +12977,9 @@ msgstr ""
msgid "LDAP"
msgstr ""
+msgid "LDAP Synchronization"
+msgstr ""
+
msgid "LDAP settings"
msgstr ""
@@ -12848,12 +13204,18 @@ msgstr ""
msgid "Learn more about custom project templates"
msgstr ""
+msgid "Learn more about deploying to AWS"
+msgstr ""
+
msgid "Learn more about deploying to a cluster"
msgstr ""
msgid "Learn more about group-level project templates"
msgstr ""
+msgid "Learn more about job dependencies"
+msgstr ""
+
msgid "Learn more about signing commits"
msgstr ""
@@ -12905,9 +13267,21 @@ msgstr ""
msgid "License History"
msgstr ""
+msgid "License ID:"
+msgstr ""
+
+msgid "License URL"
+msgstr ""
+
msgid "License-Check"
msgstr ""
+msgid "LicenseCompliance|%{docLinkStart}License Approvals%{docLinkEnd} are active"
+msgstr ""
+
+msgid "LicenseCompliance|%{docLinkStart}License Approvals%{docLinkEnd} are inactive"
+msgstr ""
+
msgid "LicenseCompliance|Add a license"
msgstr ""
@@ -12929,9 +13303,15 @@ msgstr ""
msgid "LicenseCompliance|Deny"
msgstr ""
+msgid "LicenseCompliance|Learn more about %{linkStart}License Approvals%{linkEnd}"
+msgstr ""
+
msgid "LicenseCompliance|License"
msgstr ""
+msgid "LicenseCompliance|License Approvals"
+msgstr ""
+
msgid "LicenseCompliance|License Compliance detected %d license and policy violation for the source branch only; approval required"
msgid_plural "LicenseCompliance|License Compliance detected %d licenses and policy violations for the source branch only; approval required"
msgstr[0] ""
@@ -13152,6 +13532,9 @@ msgstr ""
msgid "List your Bitbucket Server repositories"
msgstr ""
+msgid "Lists"
+msgstr ""
+
msgid "Live preview"
msgstr ""
@@ -13335,6 +13718,9 @@ msgstr ""
msgid "Manage project labels"
msgstr ""
+msgid "Manage storage usage"
+msgstr ""
+
msgid "Manage two-factor authentication"
msgstr ""
@@ -13350,6 +13736,9 @@ msgstr ""
msgid "Manifest file import"
msgstr ""
+msgid "Manifest import"
+msgstr ""
+
msgid "Manual job"
msgstr ""
@@ -13497,6 +13886,12 @@ msgstr ""
msgid "Maximum field length"
msgstr ""
+msgid "Maximum file size is 2MB. Please select a smaller file."
+msgstr ""
+
+msgid "Maximum import size (MB)"
+msgstr ""
+
msgid "Maximum job timeout"
msgstr ""
@@ -13536,6 +13931,9 @@ msgstr ""
msgid "Maximum size of Elasticsearch bulk indexing requests."
msgstr ""
+msgid "Maximum size of import files."
+msgstr ""
+
msgid "Maximum size of individual attachments in comments."
msgstr ""
@@ -13587,6 +13985,9 @@ msgstr ""
msgid "Merge (when the pipeline succeeds)"
msgstr ""
+msgid "Merge Conflicts"
+msgstr ""
+
msgid "Merge Request"
msgstr ""
@@ -13713,6 +14114,9 @@ msgstr ""
msgid "MergeRequests|Squash task canceled: another squash is already in progress."
msgstr ""
+msgid "MergeRequests|This project does not allow squashing commits when merge requests are accepted."
+msgstr ""
+
msgid "MergeRequests|Thread stays resolved"
msgstr ""
@@ -13866,6 +14270,12 @@ msgstr ""
msgid "MetricsSettings|Add a button to the metrics dashboard linking directly to your existing external dashboard."
msgstr ""
+msgid "MetricsSettings|Choose whether to display dashboard metrics in UTC or the user's local timezone."
+msgstr ""
+
+msgid "MetricsSettings|Dashboard timezone"
+msgstr ""
+
msgid "MetricsSettings|External dashboard URL"
msgstr ""
@@ -13875,6 +14285,12 @@ msgstr ""
msgid "MetricsSettings|Metrics Dashboard"
msgstr ""
+msgid "MetricsSettings|UTC (Coordinated Universal Time)"
+msgstr ""
+
+msgid "MetricsSettings|User's local timezone"
+msgstr ""
+
msgid "Metrics|Add metric"
msgstr ""
@@ -13890,6 +14306,9 @@ msgstr ""
msgid "Metrics|Create metric"
msgstr ""
+msgid "Metrics|Current"
+msgstr ""
+
msgid "Metrics|Delete metric"
msgstr ""
@@ -13940,6 +14359,9 @@ msgstr ""
msgid "Metrics|Max"
msgstr ""
+msgid "Metrics|Min"
+msgstr ""
+
msgid "Metrics|Must be a valid PromQL query."
msgstr ""
@@ -13955,6 +14377,9 @@ msgstr ""
msgid "Metrics|Refresh dashboard"
msgstr ""
+msgid "Metrics|Select a value"
+msgstr ""
+
msgid "Metrics|Star dashboard"
msgstr ""
@@ -13973,12 +14398,18 @@ msgstr ""
msgid "Metrics|There was an error getting annotations information."
msgstr ""
+msgid "Metrics|There was an error getting dashboard validation warnings information."
+msgstr ""
+
msgid "Metrics|There was an error getting deployment information."
msgstr ""
msgid "Metrics|There was an error getting environments information."
msgstr ""
+msgid "Metrics|There was an error getting options for variable \"%{name}\"."
+msgstr ""
+
msgid "Metrics|There was an error trying to validate your query"
msgstr ""
@@ -14021,6 +14452,9 @@ msgstr ""
msgid "Metrics|You're about to permanently delete this metric. This cannot be undone."
msgstr ""
+msgid "Metrics|Your dashboard schema is invalid. Edit the dashboard to correct the YAML schema."
+msgstr ""
+
msgid "Metrics|e.g. HTTP requests"
msgstr ""
@@ -14036,12 +14470,18 @@ msgstr ""
msgid "Metrics|e.g. req/sec"
msgstr ""
+msgid "Mi"
+msgstr ""
+
msgid "Microsoft Azure"
msgstr ""
msgid "Middleman project with Static Site Editor support"
msgstr ""
+msgid "Migrate your data from an external source like GitHub, Bitbucket, or another instance of GitLab."
+msgstr ""
+
msgid "Migrated %{success_count}/%{total_count} files."
msgstr ""
@@ -14176,6 +14616,9 @@ msgstr ""
msgid "Minimum capacity to be available before we schedule more mirrors preemptively."
msgstr ""
+msgid "Minimum interval in days"
+msgstr ""
+
msgid "Minimum length is %{minimum_password_length} characters"
msgstr ""
@@ -14362,6 +14805,12 @@ msgstr ""
msgid "Multiple uploaders found: %{uploader_types}"
msgstr ""
+msgid "Must match with the %{codeStart}external_url%{codeEnd} in %{codeStart}/etc/gitlab/gitlab.rb%{codeEnd}."
+msgstr ""
+
+msgid "Must match with the %{codeStart}geo_node_name%{codeEnd} in %{codeStart}/etc/gitlab/gitlab.rb%{codeEnd}. %{linkStart}More information%{linkEnd}"
+msgstr ""
+
msgid "My Awesome Group"
msgstr ""
@@ -14476,10 +14925,16 @@ msgstr ""
msgid "New Application"
msgstr ""
+msgid "New Branch"
+msgstr ""
+
+msgid "New Deploy Key"
+msgstr ""
+
msgid "New Environment"
msgstr ""
-msgid "New Geo Node"
+msgid "New File"
msgstr ""
msgid "New Group"
@@ -14496,15 +14951,15 @@ msgid_plural "New Issues"
msgstr[0] ""
msgstr[1] ""
-msgid "New Iteration"
-msgstr ""
-
msgid "New Jira import"
msgstr ""
msgid "New Label"
msgstr ""
+msgid "New Merge Request"
+msgstr ""
+
msgid "New Milestone"
msgstr ""
@@ -14523,6 +14978,9 @@ msgstr ""
msgid "New Snippet"
msgstr ""
+msgid "New User"
+msgstr ""
+
msgid "New branch"
msgstr ""
@@ -14565,6 +15023,9 @@ msgstr ""
msgid "New issue title"
msgstr ""
+msgid "New iteration"
+msgstr ""
+
msgid "New iteration created"
msgstr ""
@@ -14742,6 +15203,12 @@ msgstr ""
msgid "No grouping"
msgstr ""
+msgid "No iteration"
+msgstr ""
+
+msgid "No iterations to show"
+msgstr ""
+
msgid "No job log"
msgstr ""
@@ -14784,6 +15251,9 @@ msgstr ""
msgid "No other labels with such name or description"
msgstr ""
+msgid "No panels matching properties %{opts}"
+msgstr ""
+
msgid "No parent group"
msgstr ""
@@ -14817,6 +15287,9 @@ msgstr ""
msgid "No schedules"
msgstr ""
+msgid "No stack trace for this error"
+msgstr ""
+
msgid "No starrers matched your search"
msgstr ""
@@ -14829,7 +15302,7 @@ msgstr ""
msgid "No test coverage"
msgstr ""
-msgid "No thanks, don't show this again"
+msgid "No thanks"
msgstr ""
msgid "No vulnerabilities present"
@@ -14844,9 +15317,6 @@ msgstr ""
msgid "No, directly import the existing email addresses and usernames."
msgstr ""
-msgid "No, not interested right now"
-msgstr ""
-
msgid "No. of commits"
msgstr ""
@@ -14892,9 +15362,6 @@ msgstr ""
msgid "Not found."
msgstr ""
-msgid "Not helpful"
-msgstr ""
-
msgid "Not now"
msgstr ""
@@ -15051,6 +15518,9 @@ msgstr ""
msgid "Notifications on"
msgstr ""
+msgid "Notify users by email when sign-in location is not recognized"
+msgstr ""
+
msgid "Nov"
msgstr ""
@@ -15117,9 +15587,6 @@ msgstr ""
msgid "Oh no!"
msgstr ""
-msgid "Ok let's go"
-msgstr ""
-
msgid "Oldest first"
msgstr ""
@@ -15132,16 +15599,49 @@ msgstr ""
msgid "On track"
msgstr ""
+msgid "OnDemandScans|Attached branch"
+msgstr ""
+
+msgid "OnDemandScans|Attached branch is where the scan job runs."
+msgstr ""
+
+msgid "OnDemandScans|Could not run the scan. Please try again."
+msgstr ""
+
msgid "OnDemandScans|Create new DAST scan"
msgstr ""
+msgid "OnDemandScans|DAST will scan the target URL and any discovered sub URLs."
+msgstr ""
+
+msgid "OnDemandScans|New on-demand DAST scan"
+msgstr ""
+
msgid "OnDemandScans|On-demand Scans"
msgstr ""
+msgid "OnDemandScans|On-demand scans run outside the DevOps cycle and find vulnerabilities in your projects. %{learnMoreLinkStart}Learn more%{learnMoreLinkEnd}"
+msgstr ""
+
+msgid "OnDemandScans|Only a passive scan can be performed on demand."
+msgstr ""
+
+msgid "OnDemandScans|Passive DAST Scan"
+msgstr ""
+
+msgid "OnDemandScans|Please enter a valid URL format, ex: http://www.example.com/home"
+msgstr ""
+
+msgid "OnDemandScans|Run this scan"
+msgstr ""
+
+msgid "OnDemandScans|Scan mode"
+msgstr ""
+
msgid "OnDemandScans|Schedule or run scans immediately against target sites. Currently available on-demand scan type: DAST. %{helpLinkStart}More information%{helpLinkEnd}"
msgstr ""
-msgid "Onboarding"
+msgid "OnDemandScans|Target URL"
msgstr ""
msgid "Once imported, repositories can be mirrored over SSH. Read more %{link_start}here%{link_end}."
@@ -15167,6 +15667,9 @@ msgstr ""
msgid "One or more of you personal access tokens were revoked"
msgstr ""
+msgid "One or more of your %{provider} projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
+msgstr ""
+
msgid "One or more of your Bitbucket projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
msgstr ""
@@ -15668,6 +16171,9 @@ msgstr ""
msgid "Parent epic is not present."
msgstr ""
+msgid "Parsing error for param :embed_json. %{message}"
+msgstr ""
+
msgid "Part of merge request changes"
msgstr ""
@@ -15680,6 +16186,9 @@ msgstr ""
msgid "Passed"
msgstr ""
+msgid "Passed on"
+msgstr ""
+
msgid "Password"
msgstr ""
@@ -15764,6 +16273,9 @@ msgstr ""
msgid "Perform common operations on GitLab project"
msgstr ""
+msgid "Performance and resource management"
+msgstr ""
+
msgid "Performance optimization"
msgstr ""
@@ -15971,7 +16483,7 @@ msgstr ""
msgid "Pipelines|Something went wrong while cleaning runners cache."
msgstr ""
-msgid "Pipelines|There are currently no %{scope} pipelines."
+msgid "Pipelines|There are currently no finished pipelines."
msgstr ""
msgid "Pipelines|There are currently no pipelines."
@@ -16082,6 +16594,9 @@ msgstr ""
msgid "Pipeline|Stop pipeline #%{pipelineId}?"
msgstr ""
+msgid "Pipeline|Tag name"
+msgstr ""
+
msgid "Pipeline|Trigger author"
msgstr ""
@@ -16163,12 +16678,18 @@ msgstr ""
msgid "Please check your email (%{email}) to verify that you own this address and unlock the power of CI/CD. Didn't receive it? %{resend_link}. Wrong email address? %{update_link}."
msgstr ""
+msgid "Please choose a file"
+msgstr ""
+
msgid "Please choose a group URL with no special characters."
msgstr ""
msgid "Please complete your profile with email address"
msgstr ""
+msgid "Please convert %{linkStart}them to Git%{linkEnd}, and go through the %{linkToImportFlow} again."
+msgstr ""
+
msgid "Please convert them to %{link_to_git}, and go through the %{link_to_import_flow} again."
msgstr ""
@@ -16226,6 +16747,9 @@ msgstr ""
msgid "Please provide a valid email address."
msgstr ""
+msgid "Please provide attributes to update"
+msgstr ""
+
msgid "Please refer to <a href=\"%{docs_url}\">%{docs_url}</a>"
msgstr ""
@@ -16961,6 +17485,9 @@ msgstr ""
msgid "Project has too many %{label_for_message} to search"
msgstr ""
+msgid "Project is required when cluster_type is :project"
+msgstr ""
+
msgid "Project members"
msgstr ""
@@ -17078,7 +17605,40 @@ msgstr ""
msgid "ProjectService|Comment"
msgstr ""
-msgid "ProjectService|Comment will be posted on each event"
+msgid "ProjectService|Event will be triggered by a push to the repository"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a commit is created/updated"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a confidential issue is created/updated/closed"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a deployment finishes"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a merge request is created/updated/merged"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a new tag is pushed to the repository"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a new, unique alert is recorded"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a pipeline status changes"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a wiki page is created/updated"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when an issue is created/updated/closed"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when someone adds a comment"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when someone adds a comment on a confidential issue"
msgstr ""
msgid "ProjectService|Perform common operations on GitLab project: %{project_name}"
@@ -17129,6 +17689,9 @@ msgstr ""
msgid "ProjectSettings|Enable 'Delete source branch' option by default"
msgstr ""
+msgid "ProjectSettings|Enable merge trains and pipelines for merged results"
+msgstr ""
+
msgid "ProjectSettings|Every merge creates a merge commit"
msgstr ""
@@ -17159,7 +17722,7 @@ msgstr ""
msgid "ProjectSettings|Forks"
msgstr ""
-msgid "ProjectSettings|Git Large File Storage"
+msgid "ProjectSettings|Git Large File Storage (LFS)"
msgstr ""
msgid "ProjectSettings|Internal"
@@ -17195,9 +17758,6 @@ msgstr ""
msgid "ProjectSettings|Merge options"
msgstr ""
-msgid "ProjectSettings|Merge pipelines will try to validate the post-merge result prior to merging"
-msgstr ""
-
msgid "ProjectSettings|Merge requests"
msgstr ""
@@ -17225,6 +17785,9 @@ msgstr ""
msgid "ProjectSettings|Pipelines"
msgstr ""
+msgid "ProjectSettings|Pipelines for merge requests must be enabled in the CI/CD configuration file, or pipelines could be unresolvable or dropped"
+msgstr ""
+
msgid "ProjectSettings|Pipelines must succeed"
msgstr ""
@@ -17252,6 +17815,9 @@ msgstr ""
msgid "ProjectSettings|Show link to create/view merge request when pushing from the command line"
msgstr ""
+msgid "ProjectSettings|Skipped pipelines are considered successful"
+msgstr ""
+
msgid "ProjectSettings|Snippets"
msgstr ""
@@ -17267,6 +17833,9 @@ msgstr ""
msgid "ProjectSettings|These checks must pass before merge requests can be merged"
msgstr ""
+msgid "ProjectSettings|This introduces the risk of merging changes that will not pass the pipeline."
+msgstr ""
+
msgid "ProjectSettings|This setting is applied on the server level and can be overridden by an admin."
msgstr ""
@@ -17444,15 +18013,27 @@ msgstr ""
msgid "ProjectsNew|Blank project"
msgstr ""
+msgid "ProjectsNew|Connect your external repository to GitLab CI/CD."
+msgstr ""
+
msgid "ProjectsNew|Contact an administrator to enable options for importing your project."
msgstr ""
msgid "ProjectsNew|Create"
msgstr ""
+msgid "ProjectsNew|Create a blank project to house your files, plan your work, and collaborate on code, among other things."
+msgstr ""
+
+msgid "ProjectsNew|Create blank project"
+msgstr ""
+
msgid "ProjectsNew|Create from template"
msgstr ""
+msgid "ProjectsNew|Create new project"
+msgstr ""
+
msgid "ProjectsNew|Creating project & repository."
msgstr ""
@@ -17477,6 +18058,9 @@ msgstr ""
msgid "ProjectsNew|Project description %{tag_start}(optional)%{tag_end}"
msgstr ""
+msgid "ProjectsNew|Run CI/CD for external repository"
+msgstr ""
+
msgid "ProjectsNew|Template"
msgstr ""
@@ -17738,6 +18322,9 @@ msgstr ""
msgid "Protected Branch"
msgstr ""
+msgid "Protected Branches"
+msgstr ""
+
msgid "Protected Environment"
msgstr ""
@@ -17750,6 +18337,9 @@ msgstr ""
msgid "Protected Tag"
msgstr ""
+msgid "Protected Tags"
+msgstr ""
+
msgid "Protected branches"
msgstr ""
@@ -17849,6 +18439,9 @@ msgstr ""
msgid "Provider"
msgstr ""
+msgid "Proxy support for this API is not available currently"
+msgstr ""
+
msgid "Pseudonymizer data collection"
msgstr ""
@@ -17873,6 +18466,9 @@ msgstr ""
msgid "Publish to status page"
msgstr ""
+msgid "Published on status page"
+msgstr ""
+
msgid "Publishes this issue to the associated status page."
msgstr ""
@@ -18047,6 +18643,9 @@ msgstr ""
msgid "Recipe"
msgstr ""
+msgid "Reconfigure"
+msgstr ""
+
msgid "Recover hidden stage"
msgstr ""
@@ -18133,6 +18732,9 @@ msgstr ""
msgid "Registration|Your profile"
msgstr ""
+msgid "Regulate approvals by authors/committers, based on compliance frameworks. Can be changed only at the instance level."
+msgstr ""
+
msgid "Rejected (closed)"
msgstr ""
@@ -18183,15 +18785,24 @@ msgstr ""
msgid "ReleaseAssetLinkType|Image"
msgstr ""
+msgid "ReleaseAssetLinkType|Images"
+msgstr ""
+
msgid "ReleaseAssetLinkType|Other"
msgstr ""
msgid "ReleaseAssetLinkType|Package"
msgstr ""
+msgid "ReleaseAssetLinkType|Packages"
+msgstr ""
+
msgid "ReleaseAssetLinkType|Runbook"
msgstr ""
+msgid "ReleaseAssetLinkType|Runbooks"
+msgstr ""
+
msgid "Releases"
msgstr ""
@@ -18204,6 +18815,9 @@ msgstr ""
msgid "Releases documentation"
msgstr ""
+msgid "Releases|New Release"
+msgstr ""
+
msgid "Release|Something went wrong while getting the release details"
msgstr ""
@@ -18276,6 +18890,9 @@ msgstr ""
msgid "Remove fork relationship"
msgstr ""
+msgid "Remove from batch"
+msgstr ""
+
msgid "Remove from board"
msgstr ""
@@ -18465,6 +19082,12 @@ msgstr ""
msgid "Replication"
msgstr ""
+msgid "Replication enabled"
+msgstr ""
+
+msgid "Replication paused"
+msgstr ""
+
msgid "Reply by email"
msgstr ""
@@ -18492,15 +19115,15 @@ msgstr ""
msgid "Reported %{timeAgo} by %{reportedBy}"
msgstr ""
-msgid "Reported Resource Changes: %{addNum} to add, %{changeNum} to change, %{deleteNum} to delete"
-msgstr ""
-
msgid "Reporter"
msgstr ""
msgid "Reporting"
msgstr ""
+msgid "Reports"
+msgstr ""
+
msgid "Reports|%{combinedString} and %{resolvedString}"
msgstr ""
@@ -18539,6 +19162,9 @@ msgstr ""
msgid "Reports|Failure"
msgstr ""
+msgid "Reports|Identifier"
+msgstr ""
+
msgid "Reports|Metrics reports are loading"
msgstr ""
@@ -18551,6 +19177,9 @@ msgstr ""
msgid "Reports|Metrics reports failed loading results"
msgstr ""
+msgid "Reports|Scanner"
+msgstr ""
+
msgid "Reports|Severity"
msgstr ""
@@ -18599,15 +19228,33 @@ msgstr ""
msgid "Repository cleanup has started. You will receive an email once the cleanup operation is complete."
msgstr ""
+msgid "Repository files count over the limit"
+msgstr ""
+
+msgid "Repository has an invalid default branch name."
+msgstr ""
+
+msgid "Repository has more than one branch."
+msgstr ""
+
msgid "Repository has no locks."
msgstr ""
+msgid "Repository has tags."
+msgstr ""
+
msgid "Repository maintenance"
msgstr ""
msgid "Repository mirroring"
msgstr ""
+msgid "Repository must contain at least 1 file."
+msgstr ""
+
+msgid "Repository size is above the limit."
+msgstr ""
+
msgid "Repository static objects"
msgstr ""
@@ -18617,7 +19264,7 @@ msgstr ""
msgid "Repository sync capacity"
msgstr ""
-msgid "Repository: %{counter_repositories} / Wikis: %{counter_wikis} / Build Artifacts: %{counter_build_artifacts} / LFS: %{counter_lfs_objects}"
+msgid "Repository: %{counter_repositories} / Wikis: %{counter_wikis} / Build Artifacts: %{counter_build_artifacts} / LFS: %{counter_lfs_objects} / Snippets: %{counter_snippets}"
msgstr ""
msgid "RepositorySettingsAccessLevel|Select"
@@ -18629,6 +19276,9 @@ msgstr ""
msgid "Request Headers"
msgstr ""
+msgid "Request details"
+msgstr ""
+
msgid "Request parameter %{param} is missing."
msgstr ""
@@ -18735,6 +19385,9 @@ msgstr ""
msgid "Reset template"
msgstr ""
+msgid "Reset to project defaults"
+msgstr ""
+
msgid "Resetting the authorization key for this project will require updating the authorization key in every alert source it is enabled in."
msgstr ""
@@ -18831,9 +19484,6 @@ msgstr ""
msgid "Resume"
msgstr ""
-msgid "Resume replication"
-msgstr ""
-
msgid "Resync"
msgstr ""
@@ -19456,6 +20106,9 @@ msgstr ""
msgid "SecurityReports|False positive"
msgstr ""
+msgid "SecurityReports|Group Security Dashboard"
+msgstr ""
+
msgid "SecurityReports|Hide dismissed"
msgstr ""
@@ -19510,10 +20163,13 @@ msgstr ""
msgid "SecurityReports|Remove project from dashboard"
msgstr ""
-msgid "SecurityReports|Report type"
+msgid "SecurityReports|Return to dashboard"
msgstr ""
-msgid "SecurityReports|Return to dashboard"
+msgid "SecurityReports|Scan details"
+msgstr ""
+
+msgid "SecurityReports|Scanner type"
msgstr ""
msgid "SecurityReports|Security Dashboard"
@@ -19570,6 +20226,9 @@ msgstr ""
msgid "SecurityReports|There was an error while generating the report."
msgstr ""
+msgid "SecurityReports|Unable to add %{invalidProjectsMessage}"
+msgstr ""
+
msgid "SecurityReports|Unable to add %{invalidProjects}"
msgstr ""
@@ -19684,6 +20343,9 @@ msgstr ""
msgid "Select due date"
msgstr ""
+msgid "Select file"
+msgstr ""
+
msgid "Select group or project"
msgstr ""
@@ -19747,9 +20409,6 @@ msgstr ""
msgid "Select the branch you want to set as the default for this project. All merge requests and commits will automatically be made against this branch unless you specify a different one."
msgstr ""
-msgid "Select the configured storage available for new repositories to be placed on."
-msgstr ""
-
msgid "Select the custom project template source group."
msgstr ""
@@ -19942,12 +20601,6 @@ msgstr ""
msgid "Service Desk is enabled but not yet active"
msgstr ""
-msgid "Service Desk is off"
-msgstr ""
-
-msgid "Service Desk is on"
-msgstr ""
-
msgid "Service Templates"
msgstr ""
@@ -20035,6 +20688,15 @@ msgstr ""
msgid "Set the milestone to %{milestone_reference}."
msgstr ""
+msgid "Set the number of concurrent requests this secondary node will make to the primary node while backfilling."
+msgstr ""
+
+msgid "Set the synchronization and verification capacity for the secondary node."
+msgstr ""
+
+msgid "Set the timeout in seconds to send a secondary node status to the primary and IPs allowed for the secondary nodes."
+msgstr ""
+
msgid "Set time estimate"
msgstr ""
@@ -20074,6 +20736,9 @@ msgstr ""
msgid "Set weight to %{weight}."
msgstr ""
+msgid "Set what should be replicated by choosing specific projects or groups by the secondary node."
+msgstr ""
+
msgid "SetPasswordToCloneLink|set a password"
msgstr ""
@@ -20200,6 +20865,9 @@ msgstr ""
msgid "Show complete raw log"
msgstr ""
+msgid "Show details"
+msgstr ""
+
msgid "Show file browser"
msgstr ""
@@ -20209,10 +20877,13 @@ msgstr ""
msgid "Show latest version"
msgstr ""
+msgid "Show list"
+msgstr ""
+
msgid "Show me everything"
msgstr ""
-msgid "Show me how"
+msgid "Show me how to add a pipeline"
msgstr ""
msgid "Show me more advanced stuff"
@@ -20261,9 +20932,6 @@ msgstr ""
msgid "Showing all issues"
msgstr ""
-msgid "Showing all labels"
-msgstr ""
-
msgid "Showing last %{size} of log -"
msgstr ""
@@ -20375,9 +21043,6 @@ msgstr ""
msgid "Skip outdated deployment jobs"
msgstr ""
-msgid "Skip this for now"
-msgstr ""
-
msgid "Skipped"
msgstr ""
@@ -20522,6 +21187,9 @@ msgstr ""
msgid "Something went wrong while adding your award. Please try again."
msgstr ""
+msgid "Something went wrong while applying the batch of suggestions. Please try again."
+msgstr ""
+
msgid "Something went wrong while applying the suggestion. Please try again."
msgstr ""
@@ -20624,10 +21292,13 @@ msgstr ""
msgid "Something went wrong while updating a requirement."
msgstr ""
+msgid "Something went wrong while updating assignees"
+msgstr ""
+
msgid "Something went wrong while updating your list settings"
msgstr ""
-msgid "Something went wrong with your automatic subscription renewal"
+msgid "Something went wrong with your automatic subscription renewal."
msgstr ""
msgid "Something went wrong, unable to add %{project} to dashboard"
@@ -20645,6 +21316,9 @@ msgstr ""
msgid "Something went wrong, unable to search projects"
msgstr ""
+msgid "Something went wrong."
+msgstr ""
+
msgid "Something went wrong. Please try again."
msgstr ""
@@ -20828,6 +21502,9 @@ msgstr ""
msgid "Source code"
msgstr ""
+msgid "Source code (%{fileExtension})"
+msgstr ""
+
msgid "Source is not available"
msgstr ""
@@ -20912,6 +21589,9 @@ msgstr ""
msgid "Stack trace"
msgstr ""
+msgid "Stacktrace snippet"
+msgstr ""
+
msgid "Stage"
msgstr ""
@@ -21017,6 +21697,9 @@ msgstr ""
msgid "Start thread & reopen %{noteable_name}"
msgstr ""
+msgid "Start using Directed Acyclic Graphs (DAG)"
+msgstr ""
+
msgid "Start your Free Gold Trial"
msgstr ""
@@ -21479,6 +22162,21 @@ msgstr ""
msgid "SuggestedColors|Very pale orange"
msgstr ""
+msgid "Suggestion is not applicable as the suggestion was not found."
+msgstr ""
+
+msgid "Suggestions are not applicable as one or more suggestions were not found."
+msgstr ""
+
+msgid "Suggestions are not applicable as their lines cannot overlap."
+msgstr ""
+
+msgid "Suggestions must all be on the same branch."
+msgstr ""
+
+msgid "Suggestions that change line count can't be added to batches, yet."
+msgstr ""
+
msgid "Suggestions:"
msgstr ""
@@ -21521,9 +22219,6 @@ msgstr ""
msgid "Synchronization disabled"
msgstr ""
-msgid "Synchronization of container repositories is disabled."
-msgstr ""
-
msgid "System"
msgstr ""
@@ -21704,6 +22399,34 @@ msgstr ""
msgid "Terms of Service and Privacy Policy"
msgstr ""
+msgid "Terraform|%{number} Terraform report failed to generate"
+msgid_plural "Terraform|%{number} Terraform reports failed to generate"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Terraform|%{number} Terraform report was generated in your pipelines"
+msgid_plural "Terraform|%{number} Terraform reports were generated in your pipelines"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Terraform|A Terraform report failed to generate."
+msgstr ""
+
+msgid "Terraform|A Terraform report was generated in your pipelines."
+msgstr ""
+
+msgid "Terraform|Generating the report caused an error."
+msgstr ""
+
+msgid "Terraform|Reported Resource Changes: %{addNum} to add, %{changeNum} to change, %{deleteNum} to delete"
+msgstr ""
+
+msgid "Terraform|The Terraform report %{name} failed to generate."
+msgstr ""
+
+msgid "Terraform|The Terraform report %{name} was generated in your pipelines."
+msgstr ""
+
msgid "Test"
msgstr ""
@@ -21730,7 +22453,7 @@ msgstr ""
msgid "TestHooks|Ensure the project has CI pipelines."
msgstr ""
-msgid "TestHooks|Ensure the project has at least one commit."
+msgid "TestHooks|Ensure the project has deployments."
msgstr ""
msgid "TestHooks|Ensure the project has issues."
@@ -21831,7 +22554,7 @@ msgstr ""
msgid "The Prometheus server responded with \"bad request\". Please check your queries are correct and are supported in your Prometheus version. %{documentationLink}"
msgstr ""
-msgid "The URL defined on the primary node that secondary nodes should use to contact it. Defaults to URL"
+msgid "The URL defined on the primary node that secondary nodes should use to contact it."
msgstr ""
msgid "The URL to use for connecting to Elasticsearch. Use a comma-separated list to support clustering (e.g., \"http://localhost:9200, http://localhost:9201\")."
@@ -21840,6 +22563,9 @@ msgstr ""
msgid "The X509 Certificate to use when mutual TLS is required to communicate with the external authorization service. If left blank, the server certificate is still validated when accessing over HTTPS."
msgstr ""
+msgid "The above settings apply to all projects with the selected compliance framework(s)."
+msgstr ""
+
msgid "The amount of seconds after which a request to get a secondary node status will time out."
msgstr ""
@@ -22139,9 +22865,6 @@ msgstr ""
msgid "The total stage shows the time it takes between creating an issue and deploying the code to production. The data will be automatically added once you have completed the full idea to production cycle."
msgstr ""
-msgid "The unique identifier for the Geo node. Must match %{geoNodeName} if it is set in gitlab.rb, otherwise it must match %{externalUrl} with a trailing slash"
-msgstr ""
-
msgid "The update action will time out after %{number_of_minutes} minutes. For big repositories, use a clone/push combination."
msgstr ""
@@ -22166,9 +22889,6 @@ msgstr ""
msgid "The user you are trying to deactivate has been active in the past %{minimum_inactive_days} days and cannot be deactivated"
msgstr ""
-msgid "The user-facing URL of the Geo node"
-msgstr ""
-
msgid "The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6."
msgstr ""
@@ -22259,6 +22979,9 @@ msgstr ""
msgid "There was a problem fetching project branches."
msgstr ""
+msgid "There was a problem fetching project tags."
+msgstr ""
+
msgid "There was a problem fetching project users."
msgstr ""
@@ -22310,6 +23033,9 @@ msgstr ""
msgid "There was an error fetching the %{replicableType}"
msgstr ""
+msgid "There was an error fetching the Geo Settings"
+msgstr ""
+
msgid "There was an error fetching the Node's Groups"
msgstr ""
@@ -22430,6 +23156,12 @@ msgstr ""
msgid "This %{issuable} is locked. Only <strong>project members</strong> can comment."
msgstr ""
+msgid "This %{noteableTypeText} is %{confidentialLinkStart}confidential%{linkEnd} and %{lockedLinkStart}locked%{linkEnd}."
+msgstr ""
+
+msgid "This %{noteableTypeText} is locked."
+msgstr ""
+
msgid "This %{viewer} could not be displayed because %{reason}. You can %{options} instead."
msgstr ""
@@ -22448,6 +23180,9 @@ msgstr ""
msgid "This action can lead to data loss. To prevent accidental actions we ask you to confirm your intention."
msgstr ""
+msgid "This also resolves all related threads"
+msgstr ""
+
msgid "This also resolves the discussion"
msgstr ""
@@ -22538,6 +23273,9 @@ msgstr ""
msgid "This epic does not exist or you don't have sufficient permission."
msgstr ""
+msgid "This feature is currently in beta. We invite you to %{linkStart}give feedback%{linkEnd}."
+msgstr ""
+
msgid "This feature requires local storage to be enabled"
msgstr ""
@@ -22571,7 +23309,7 @@ msgstr ""
msgid "This is a Work in Progress"
msgstr ""
-msgid "This is a confidential issue."
+msgid "This is a confidential %{noteableTypeText}."
msgstr ""
msgid "This is a delayed job to run in %{remainingTime}"
@@ -22598,9 +23336,6 @@ msgstr ""
msgid "This is your current session"
msgstr ""
-msgid "This issue is %{confidentialLinkStart}confidential%{linkEnd} and %{lockedLinkStart}locked%{linkEnd}."
-msgstr ""
-
msgid "This issue is confidential"
msgstr ""
@@ -22610,9 +23345,6 @@ msgstr ""
msgid "This issue is in a child epic of the filtered epic"
msgstr ""
-msgid "This issue is locked."
-msgstr ""
-
msgid "This job depends on other jobs with expired/erased artifacts: %{invalid_dependencies}"
msgstr ""
@@ -22712,6 +23444,9 @@ msgstr ""
msgid "This license has already expired."
msgstr ""
+msgid "This link points to external content"
+msgstr ""
+
msgid "This may expose confidential information as the selected fork is in another namespace that can have other members."
msgstr ""
@@ -22739,6 +23474,9 @@ msgstr ""
msgid "This page will be removed in a future release."
msgstr ""
+msgid "This pipeline does not use the %{codeStart}needs%{codeEnd} keyword and can't be represented as a directed acyclic graph."
+msgstr ""
+
msgid "This pipeline makes use of a predefined CI/CD configuration enabled by %{strongStart}Auto DevOps.%{strongEnd}"
msgstr ""
@@ -22967,6 +23705,9 @@ msgstr ""
msgid "Time from last commit to merge"
msgstr ""
+msgid "Time in seconds"
+msgstr ""
+
msgid "Time in seconds GitLab will wait for a response from the external service. When the service does not respond in time, access will be denied."
msgstr ""
@@ -23388,9 +24129,15 @@ msgstr ""
msgid "Total artifacts size: %{total_size}"
msgstr ""
+msgid "Total cores (CPUs)"
+msgstr ""
+
msgid "Total issues"
msgstr ""
+msgid "Total memory (GB)"
+msgstr ""
+
msgid "Total test time for all commits/merges"
msgstr ""
@@ -23592,6 +24339,9 @@ msgstr ""
msgid "U2F only works with HTTPS-enabled websites. Contact your administrator for more details."
msgstr ""
+msgid "UI Development Kit"
+msgstr ""
+
msgid "URL"
msgstr ""
@@ -23664,9 +24414,6 @@ msgstr ""
msgid "Unable to load the merge request widget. Try reloading the page."
msgstr ""
-msgid "Unable to process group import file"
-msgstr ""
-
msgid "Unable to resolve"
msgstr ""
@@ -23682,6 +24429,9 @@ msgstr ""
msgid "Unable to sign you in to the group with SAML due to \"%{reason}\""
msgstr ""
+msgid "Unable to suggest a path. Please refresh and try again."
+msgstr ""
+
msgid "Unable to update label prioritization at this time"
msgstr ""
@@ -23697,6 +24447,9 @@ msgstr ""
msgid "Unarchiving the project will restore people's ability to make changes to it. The repository can be committed to, and issues, comments, and other entities can be created. %{strong_start}Once active, this project shows up in the search and on the dashboard.%{strong_end}"
msgstr ""
+msgid "Unassign from commenting user"
+msgstr ""
+
msgid "Unblock"
msgstr ""
@@ -23772,6 +24525,9 @@ msgstr ""
msgid "Unreachable"
msgstr ""
+msgid "Unrecognized cluster type"
+msgstr ""
+
msgid "Unresolve"
msgstr ""
@@ -23832,6 +24588,9 @@ msgstr ""
msgid "Unsubscribes from this %{quick_action_target}."
msgstr ""
+msgid "Unsupported todo type passed. Supported todo types are: %{todo_types}"
+msgstr ""
+
msgid "Until"
msgstr ""
@@ -23859,6 +24618,9 @@ msgstr ""
msgid "Update approval rule"
msgstr ""
+msgid "Update approvers"
+msgstr ""
+
msgid "Update failed"
msgstr ""
@@ -23868,6 +24630,9 @@ msgstr ""
msgid "Update it"
msgstr ""
+msgid "Update iteration"
+msgstr ""
+
msgid "Update now"
msgstr ""
@@ -23913,7 +24678,7 @@ msgstr ""
msgid "Updated at"
msgstr ""
-msgid "Updated to"
+msgid "Updated to %{linkStart}chart v%{linkEnd}"
msgstr ""
msgid "Updating"
@@ -23943,6 +24708,9 @@ msgstr ""
msgid "Upload CSV file"
msgstr ""
+msgid "Upload License"
+msgstr ""
+
msgid "Upload New File"
msgstr ""
@@ -24024,16 +24792,19 @@ msgstr ""
msgid "UsageQuota|Pipelines"
msgstr ""
+msgid "UsageQuota|Purchase more storage"
+msgstr ""
+
msgid "UsageQuota|Repositories"
msgstr ""
msgid "UsageQuota|Repository"
msgstr ""
-msgid "UsageQuota|Storage"
+msgid "UsageQuota|Snippets"
msgstr ""
-msgid "UsageQuota|Storage usage:"
+msgid "UsageQuota|Storage"
msgstr ""
msgid "UsageQuota|This namespace has no projects which use shared runners"
@@ -24066,6 +24837,12 @@ msgstr ""
msgid "UsageQuota|Wikis"
msgstr ""
+msgid "UsageQuota|You used: %{usage} %{limit}"
+msgstr ""
+
+msgid "UsageQuota|out of %{formattedLimit} of your namespace storage"
+msgstr ""
+
msgid "Use %{code_start}::%{code_end} to create a %{link_start}scoped label set%{link_end} (eg. %{code_start}priority::1%{code_end})"
msgstr ""
@@ -24132,6 +24909,9 @@ msgstr ""
msgid "User IDs"
msgstr ""
+msgid "User Lists can only be created and modified with %{linkStart}the API%{linkEnd}"
+msgstr ""
+
msgid "User OAuth applications"
msgstr ""
@@ -24156,6 +24936,9 @@ msgstr ""
msgid "User key was successfully removed."
msgstr ""
+msgid "User list %{name} will be removed. Are you sure?"
+msgstr ""
+
msgid "User map"
msgstr ""
@@ -24177,148 +24960,10 @@ msgstr ""
msgid "User was successfully updated."
msgstr ""
-msgid "UserOnboardingTour|%{activeTour}/%{totalTours}"
-msgstr ""
-
-msgid "UserOnboardingTour|%{completed}/%{total} steps completed"
-msgstr ""
-
-msgid "UserOnboardingTour|%{emphasisStart}Well done!%{emphasisEnd}%{lineBreak}%{lineBreak}That's it for our guided tour, congratulations for making it all the way to the end!%{lineBreak}%{lineBreak}We hope this gave you a good overview of GitLab and how it can help you. We'll now show you how to create your own project and invite your colleagues."
-msgstr ""
-
-msgid "UserOnboardingTour|Adding other members to a project is done through Project Settings. Click on %{emphasisStart}Settings%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Alright, that's it for Commits. Let's take a look at the %{emphasisStart}Branches%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Awesome! Now click on %{emphasisStart}Members%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Click on one of the %{emphasisStart}Compare%{emphasisEnd} buttons to compare a branch to master."
-msgstr ""
-
-msgid "UserOnboardingTour|Click on one of the %{emphasisStart}pipeline IDs%{emphasisEnd} to see the details of a pipeline."
-msgstr ""
-
-msgid "UserOnboardingTour|Click to open the latest commit to see its details."
-msgstr ""
-
-msgid "UserOnboardingTour|Close 'Learn GitLab'"
-msgstr ""
-
-msgid "UserOnboardingTour|Commits are shown in chronological order and can be filtered by the commit message or by the branch."
+msgid "UserList|Delete %{name}?"
msgstr ""
-msgid "UserOnboardingTour|Create a project"
-msgstr ""
-
-msgid "UserOnboardingTour|Exit 'Learn GitLab'"
-msgstr ""
-
-msgid "UserOnboardingTour|Got it"
-msgstr ""
-
-msgid "UserOnboardingTour|Great job! %{clapHands} We hope the tour was helpful and that you learned how to use GitLab.%{lineBreak}%{lineBreak}We'd love to get your feedback on this tour.%{lineBreak}%{lineBreak}%{emphasisStart}How helpful would you say this guided tour was?%{emphasisEnd}%{lineBreak}%{lineBreak}"
-msgstr ""
-
-msgid "UserOnboardingTour|Guided GitLab Tour"
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can compare the changes of this branch to another one. Changes are divided by files so that it's easier to see what was changed where."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can create a project from scratch, start with a template or import a repository from other platforms. Whatever you choose, we'll guide you through the process.%{lineBreak}%{lineBreak}Fill in your new project information and click on %{emphasisStart}Create Project%{emphasisEnd} to progress to the next step."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can see the breakdown of the pipelines: its stages and jobs in each of the stages and their status.%{lineBreak}%{lineBreak}Our CI/CD pipelines are quite complex, most of our users have fewer and simpler pipelines."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can see the current members of the project (just you at the moment) and invite new members.%{lineBreak}%{lineBreak}You can invite multiple members at once (existing GitLab users or invite by email) and you can also set their roles and permissions.%{lineBreak}%{lineBreak}Add a few members and click on %{emphasisStart}Add to project%{emphasisEnd} to complete this step."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can see what changes were made with this commit, on what branch and if there's a related merge request. The status of the pipeline will also show up if CI/CD is set up.%{lineBreak}%{lineBreak}You can also comment on the lines of code that were changed and start a discussion with your colleagues!"
-msgstr ""
-
-msgid "UserOnboardingTour|Here's an overview of branches in the %{emphasisStart}%{projectName}%{emphasisEnd} project. They're split into Active and Stale.%{lineBreak}%{lineBreak}From here, you can create a new merge request from a branch, or compare the branch to any other branch in the project. By default, it will compare it to the master branch."
-msgstr ""
-
-msgid "UserOnboardingTour|Invite colleagues"
-msgstr ""
-
-msgid "UserOnboardingTour|Issues are great for communicating and keeping track of progress in GitLab. These are all issues that are open in the %{emphasisStart}%{projectName}%{emphasisEnd}.%{lineBreak}%{lineBreak}You can help us improve GitLab by contributing work to issues that are labeled <span class=\"badge color-label accept-mr-label\">Accepting merge requests</span>.%{lineBreak}%{lineBreak}This list can be filtered by labels, milestones, assignees, authors... We'll show you how it looks when the list is filtered by a label."
-msgstr ""
-
-msgid "UserOnboardingTour|Learn GitLab"
-msgstr ""
-
-msgid "UserOnboardingTour|Let's take a closer look at a merge request. Click on the title of one."
-msgstr ""
-
-msgid "UserOnboardingTour|Let's take a closer look at all the commits. Click on %{emphasisStart}Commits%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Let's take a closer look at the repository of this project. Click on %{emphasisStart}Repository%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|No thanks"
-msgstr ""
-
-msgid "UserOnboardingTour|Ok, let's go"
-msgstr ""
-
-msgid "UserOnboardingTour|Ok, show me"
-msgstr ""
-
-msgid "UserOnboardingTour|Open one of the issues by clicking on its title."
-msgstr ""
-
-msgid "UserOnboardingTour|Restart this step"
-msgstr ""
-
-msgid "UserOnboardingTour|Skip this step"
-msgstr ""
-
-msgid "UserOnboardingTour|Sweet! Your project was created and is ready to be used.%{lineBreak}%{lineBreak}You can start adding files to the repository or clone it. One last thing we want to show you is how to invite your colleagues to your new project."
-msgstr ""
-
-msgid "UserOnboardingTour|Take a look. Here's a nifty menu for quickly creating issues, merge requests, snippets, projects and groups. Click on it and select \"New project\" from the \"GitLab\" section to get started."
-msgstr ""
-
-msgid "UserOnboardingTour|Thanks for taking the guided tour. Remember, if you want to go through it again, you can start %{emphasisStart}Learn GitLab%{emphasisEnd} in the help menu on the top right."
-msgstr ""
-
-msgid "UserOnboardingTour|Thanks for the feedback! %{thumbsUp}"
-msgstr ""
-
-msgid "UserOnboardingTour|That's it for issues. Let'st take a look at %{emphasisStart}Merge Requests%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|That's it for merge requests. Now for the final part of this guided tour - the %{emphasisStart}CI/CD%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|That's it for the Repository. Let's take a look at the %{emphasisStart}Issues%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|The structure of this page is very similar to issues. Status, description, discussion and the sidebar are all here.%{lineBreak}%{lineBreak}But take a look below the description and you'll notice that there's more information about the merge request, the CI/CD pipeline and the options for approving it.%{lineBreak}%{lineBreak}Alongside the discussion you can also see more information about commits in this merge request, the status of pipelines and review all changes that were made."
-msgstr ""
-
-msgid "UserOnboardingTour|There's a lot of information here but don't worry, we'll go through it.%{lineBreak}%{lineBreak}On the top you can see the status of the issue and when it was opened and by whom. Directly below it is the issue description and below that are other %{emphasisStart}related issues%{emphasisEnd} and %{emphasisStart}merge requests%{emphasisEnd} (if any). Then below that is the %{emphasisStart}discussion%{emphasisEnd}, that's where most of the communication happens.%{lineBreak}%{lineBreak}On the right, there's a sidebar where you can view/change the %{emphasisStart}assignee, milestone, due date, labels, weight%{emphasisEnd}, etc."
-msgstr ""
-
-msgid "UserOnboardingTour|These are all the CI/CD pipelines we have for our %{emphasisStart}%{projectName}%{emphasisEnd} project.%{lineBreak}%{lineBreak}Here you can see the status of each pipeline, for what commit it's running for, its stages and the status for them."
-msgstr ""
-
-msgid "UserOnboardingTour|These are all the issues that are available for community contributions. Let's take a closer look at one of them."
-msgstr ""
-
-msgid "UserOnboardingTour|This is an overview of all merge requests in this project. Similarly to the issues overview it can be filtered down by things like labels, milestones, authors, assignees, etc."
-msgstr ""
-
-msgid "UserOnboardingTour|This is the repository for the %{emphasisStart}%{projectName}%{emphasisEnd} project. All our code is stored here. Feel free to explore and take a closer look at folders and files.%{lineBreak}%{lineBreak}Above the file structure you can see the latest commit, who the author is and the status of the CI/CD pipeline.%{lineBreak}%{lineBreak}If you scroll down below the file structure, you'll find the Readme of this project. This is defined in the README.md file at the root of the repository."
-msgstr ""
-
-msgid "UserOnboardingTour|Welcome to the project overview of the %{emphasisStart}%{projectName}%{emphasisEnd} project. This is the project that we use to work on GitLab. At first, a project seems like a simple repository, but at GitLab, a project is so much more.%{lineBreak}%{lineBreak}You can create projects for hosting your codebase, use it as an issue tracker, collaborate on code, and continuously build, test, and deploy your app with built-in GitLab CI/CD."
+msgid "UserList|created %{timeago}"
msgstr ""
msgid "UserProfile|Activity"
@@ -24471,6 +25116,9 @@ msgstr ""
msgid "UsersSelect|Unassigned"
msgstr ""
+msgid "Using %{codeStart}needs%{codeEnd} allows jobs to run before their stage is reached, as soon as their individual dependencies are met, which speeds up your pipelines."
+msgstr ""
+
msgid "Using %{code_start}::%{code_end} denotes a %{link_start}scoped label set%{link_end}"
msgstr ""
@@ -24555,9 +25203,6 @@ msgstr ""
msgid "Versions"
msgstr ""
-msgid "Very helpful"
-msgstr ""
-
msgid "View Documentation"
msgstr ""
@@ -24706,6 +25351,9 @@ msgstr ""
msgid "VisibilityLevel|Unknown"
msgstr ""
+msgid "Visit settings page"
+msgstr ""
+
msgid "VisualReviewApp|%{stepStart}Step 1%{stepEnd}. Copy the following script:"
msgstr ""
@@ -24799,6 +25447,9 @@ msgstr ""
msgid "VulnerabilityManagement|Something went wrong while trying to delete the comment. Please try again later."
msgstr ""
+msgid "VulnerabilityManagement|Something went wrong while trying to refresh the vulnerability. Please try again later."
+msgstr ""
+
msgid "VulnerabilityManagement|Something went wrong while trying to retrieve the vulnerability history. Please try again later."
msgstr ""
@@ -24841,6 +25492,9 @@ msgstr ""
msgid "Vulnerability|Class"
msgstr ""
+msgid "Vulnerability|Crash Address"
+msgstr ""
+
msgid "Vulnerability|Description"
msgstr ""
@@ -24916,9 +25570,6 @@ msgstr ""
msgid "We couldn't reach the Prometheus server. Either the server no longer exists or the configuration details need updating."
msgstr ""
-msgid "We created a short guided tour that will help you learn the basics of GitLab and how it will help you be better at your job. It should only take a couple of minutes. You will be guided by two types of helpers, best recognized by their color."
-msgstr ""
-
msgid "We detected potential spam in the %{humanized_resource_name}. Please solve the reCAPTCHA to proceed."
msgstr ""
@@ -24952,6 +25603,9 @@ msgstr ""
msgid "We've found no vulnerabilities"
msgstr ""
+msgid "Web Application Firewall"
+msgstr ""
+
msgid "Web IDE"
msgstr ""
@@ -25012,9 +25666,6 @@ msgstr ""
msgid "Welcome to GitLab.com<br>@%{name}!"
msgstr ""
-msgid "Welcome to the Guided GitLab Tour"
-msgstr ""
-
msgid "Welcome to the guided GitLab tour"
msgstr ""
@@ -25062,9 +25713,6 @@ msgstr ""
msgid "While it's rare to have no vulnerabilities, it can happen. In any event, we ask that you please double check your settings to make sure you've set up your dashboard correctly."
msgstr ""
-msgid "White helpers give contextual information."
-msgstr ""
-
msgid "Who can be an approver?"
msgstr ""
@@ -25110,12 +25758,18 @@ msgstr ""
msgid "WikiEmptyIssueMessage|Suggest wiki improvement"
msgstr ""
+msgid "WikiEmptyIssueMessage|You must be a group member in order to add wiki pages. If you have suggestions for how to improve the wiki for this group, consider opening an issue in the %{issues_link}."
+msgstr ""
+
msgid "WikiEmptyIssueMessage|You must be a project member in order to add wiki pages. If you have suggestions for how to improve the wiki for this project, consider opening an issue in the %{issues_link}."
msgstr ""
msgid "WikiEmptyIssueMessage|issue tracker"
msgstr ""
+msgid "WikiEmpty|A wiki is where you can store all the details about your group. This can include why you've created it, its principles, how to use it, and so on."
+msgstr ""
+
msgid "WikiEmpty|A wiki is where you can store all the details about your project. This can include why you've created it, its principles, how to use it, and so on."
msgstr ""
@@ -25125,12 +25779,21 @@ msgstr ""
msgid "WikiEmpty|Suggest wiki improvement"
msgstr ""
+msgid "WikiEmpty|The wiki lets you write documentation for your group"
+msgstr ""
+
msgid "WikiEmpty|The wiki lets you write documentation for your project"
msgstr ""
+msgid "WikiEmpty|This group has no wiki pages"
+msgstr ""
+
msgid "WikiEmpty|This project has no wiki pages"
msgstr ""
+msgid "WikiEmpty|You must be a group member in order to add wiki pages."
+msgstr ""
+
msgid "WikiEmpty|You must be a project member in order to add wiki pages."
msgstr ""
@@ -25407,13 +26070,13 @@ msgstr ""
msgid "You can now export your security dashboard to a CSV report."
msgstr ""
-msgid "You can now submit a merge request to get this change into the original branch."
+msgid "You can now manage alert endpoint configuration in the Alerts section on the Operations settings page. Fields on this page have been deprecated."
msgstr ""
-msgid "You can now submit a merge request to get this change into the original project."
+msgid "You can now submit a merge request to get this change into the original branch."
msgstr ""
-msgid "You can only add files when you are on a branch"
+msgid "You can now submit a merge request to get this change into the original project."
msgstr ""
msgid "You can only edit files when you are on a branch"
@@ -25635,7 +26298,7 @@ msgstr ""
msgid "You need to upload a Google Takeout archive."
msgstr ""
-msgid "You reached %{usage_in_percent} of %{namespace_name}'s capacity (%{used_storage} of %{storage_limit})"
+msgid "You reached %{usage_in_percent} of %{namespace_name}'s storage capacity (%{used_storage} of %{storage_limit})"
msgstr ""
msgid "You tried to fork %{link_to_the_project} but it failed for the following reason:"
@@ -25938,13 +26601,13 @@ msgstr ""
msgid "Your subscription expired!"
msgstr ""
-msgid "Your subscription has been downgraded"
+msgid "Your subscription has been downgraded."
msgstr ""
-msgid "Your subscription will automatically renew in %{remaining_days}"
+msgid "Your subscription will automatically renew in %{remaining_days}."
msgstr ""
-msgid "Your subscription will expire in %{remaining_days}"
+msgid "Your subscription will expire in %{remaining_days}."
msgstr ""
msgid "Zoom meeting added"
@@ -26024,9 +26687,6 @@ msgstr ""
msgid "branch name"
msgstr ""
-msgid "build pipeline reference mismatch"
-msgstr ""
-
msgid "by"
msgstr ""
@@ -26045,6 +26705,9 @@ msgstr ""
msgid "cannot block others"
msgstr ""
+msgid "cannot contain HTML/XML tags, including any word between angle brackets (<,>)."
+msgstr ""
+
msgid "cannot include leading slash or directory traversal."
msgstr ""
@@ -26075,55 +26738,6 @@ msgstr ""
msgid "ciReport|%{remainingPackagesCount} more"
msgstr ""
-msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability"
-msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability for the source branch only"
-msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities for the source branch only"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerability"
-msgid_plural "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerabilities"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new vulnerability"
-msgid_plural "ciReport|%{reportType} %{status} detected %{newCount} new vulnerabilities"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities for the source branch only"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{fixedCount} fixed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} vulnerability for the source branch only"
-msgid_plural "ciReport|%{reportType} %{status} detected %{newCount} vulnerabilities for the source branch only"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "ciReport|%{reportType} %{status} detected no new vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected no vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected no vulnerabilities for the source branch only"
-msgstr ""
-
msgid "ciReport|%{reportType} is loading"
msgstr ""
@@ -26142,7 +26756,7 @@ msgstr ""
msgid "ciReport|All projects"
msgstr ""
-msgid "ciReport|All report types"
+msgid "ciReport|All scanner types"
msgstr ""
msgid "ciReport|All severities"
@@ -26166,6 +26780,9 @@ msgstr ""
msgid "ciReport|Container scanning detects known vulnerabilities in your docker images."
msgstr ""
+msgid "ciReport|Coverage Fuzzing"
+msgstr ""
+
msgid "ciReport|Create a merge request to implement this solution, or download and apply the patch manually."
msgstr ""
@@ -26324,6 +26941,9 @@ msgstr ""
msgid "customize"
msgstr ""
+msgid "data"
+msgstr ""
+
msgid "date must not be after 9999-12-31"
msgstr ""
@@ -26394,6 +27014,9 @@ msgstr ""
msgid "entries cannot contain HTML tags"
msgstr ""
+msgid "epic"
+msgstr ""
+
msgid "error"
msgstr ""
@@ -26415,9 +27038,6 @@ msgstr ""
msgid "expires on %{timebox_due_date}"
msgstr ""
-msgid "external_url"
-msgstr ""
-
msgid "failed"
msgstr ""
@@ -26453,9 +27073,6 @@ msgstr ""
msgid "from"
msgstr ""
-msgid "geo_node_name"
-msgstr ""
-
msgid "group"
msgstr ""
@@ -26477,9 +27094,6 @@ msgstr ""
msgid "https://your-bitbucket-server"
msgstr ""
-msgid "image"
-msgstr ""
-
msgid "image diff"
msgstr ""
@@ -26623,6 +27237,9 @@ msgstr ""
msgid "load it anyway"
msgstr ""
+msgid "loading"
+msgstr ""
+
msgid "locked by %{path_lock_user_name} %{created_at}"
msgstr ""
@@ -26751,9 +27368,6 @@ msgstr ""
msgid "mrWidget|Deployment statistics are not available currently"
msgstr ""
-msgid "mrWidget|Detect issues before deployment with a CI pipeline that continuously tests your code. We created a quick guide that will show you how to create one. Make your code more secure and more robust in just a minute."
-msgstr ""
-
msgid "mrWidget|Did not close"
msgstr ""
@@ -26916,6 +27530,9 @@ msgstr ""
msgid "mrWidget|To approve this merge request, please enter your password. This project requires all approvals to be authenticated."
msgstr ""
+msgid "mrWidget|Use %{linkStart}CI pipelines to test your code%{linkEnd}, simply add a GitLab CI configuration file to your project. It only takes a minute to make your code more secure and robust."
+msgstr ""
+
msgid "mrWidget|When this merge request is ready, remove the WIP: prefix from the title to allow it to be merged"
msgstr ""
@@ -26952,6 +27569,9 @@ msgstr ""
msgid "must be greater than start date"
msgstr ""
+msgid "must contain only valid frameworks"
+msgstr ""
+
msgid "my-awesome-group"
msgstr ""
@@ -26964,6 +27584,9 @@ msgstr ""
msgid "needs to be between 10 minutes and 1 month"
msgstr ""
+msgid "never"
+msgstr ""
+
msgid "never expires"
msgstr ""
@@ -26991,6 +27614,9 @@ msgstr ""
msgid "nounSeries|%{firstItem} and %{lastItem}"
msgstr ""
+msgid "nounSeries|%{item}"
+msgstr ""
+
msgid "nounSeries|%{item}, %{nextItem}"
msgstr ""
@@ -27127,6 +27753,9 @@ msgstr ""
msgid "revised"
msgstr ""
+msgid "satisfied"
+msgstr ""
+
msgid "score"
msgstr ""
@@ -27157,6 +27786,9 @@ msgstr ""
msgid "severity|Unknown"
msgstr ""
+msgid "should be an array of %{object_name} objects"
+msgstr ""
+
msgid "should be greater than or equal to %{access} inherited membership from group %{group_name}"
msgstr ""
@@ -27211,10 +27843,13 @@ msgstr ""
msgid "suggestPipeline|2/2: Commit your changes"
msgstr ""
-msgid "suggestPipeline|Commit the changes and your pipeline will automatically run for the first time."
+msgid "suggestPipeline|Choose %{boldStart}Code Quality%{boldEnd} to add a pipeline that tests the quality of your code."
msgstr ""
-msgid "suggestPipeline|We recommend the %{boldStart}Code Quality%{boldEnd} template, which will add a report widget to your Merge Requests. This way you’ll learn about code quality degradations much sooner. %{footerStart} Goodbye technical debt! %{footerEnd}"
+msgid "suggestPipeline|The template is ready! You can now commit it to create your first pipeline."
+msgstr ""
+
+msgid "suggestPipeline|We’re adding a GitLab CI configuration file to add a pipeline to the project. You could create it manually, but we recommend that you start with a GitLab template that works out of the box."
msgstr ""
msgid "syntax is correct"
@@ -27274,6 +27909,9 @@ msgstr ""
msgid "uses Kubernetes clusters to deploy your code!"
msgstr ""
+msgid "v%{version} published %{timeAgo}"
+msgstr ""
+
msgid "verify ownership"
msgstr ""
diff --git a/locale/lt_LT/gitlab.po b/locale/lt_LT/gitlab.po
new file mode 100644
index 00000000000..7d12790b62c
--- /dev/null
+++ b/locale/lt_LT/gitlab.po
@@ -0,0 +1,28267 @@
+msgid ""
+msgstr ""
+"Project-Id-Version: gitlab-ee\n"
+"Report-Msgid-Bugs-To: \n"
+"Last-Translator: \n"
+"Language-Team: Lithuanian\n"
+"Language: lt_LT\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=4; plural=(n%10==1 && (n%100>19 || n%100<11) ? 0 : (n%10>=2 && n%10<=9) && (n%100>19 || n%100<11) ? 1 : n%1!=0 ? 2: 3);\n"
+"X-Crowdin-Project: gitlab-ee\n"
+"X-Crowdin-Project-ID: 288872\n"
+"X-Crowdin-Language: lt\n"
+"X-Crowdin-File: /master/locale/gitlab.pot\n"
+"X-Crowdin-File-ID: 6\n"
+"PO-Revision-Date: 2020-07-02 01:26\n"
+
+msgid " %{start} to %{end}"
+msgstr ""
+
+msgid " (from %{timeoutSource})"
+msgstr ""
+
+msgid " Collected %{time}"
+msgstr ""
+
+msgid " Please sign in."
+msgstr ""
+
+msgid " Try to %{action} this file again."
+msgstr ""
+
+msgid " You need to do this before %{grace_period_deadline}."
+msgstr ""
+
+msgid " and"
+msgstr ""
+
+msgid " and "
+msgstr ""
+
+msgid " and %{sliced}"
+msgstr ""
+
+msgid " degraded on %d point"
+msgid_plural " degraded on %d points"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid " improved on %d point"
+msgid_plural " improved on %d points"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid " or "
+msgstr ""
+
+msgid " or <!merge request id>"
+msgstr ""
+
+msgid " or <#issue id>"
+msgstr ""
+
+msgid " or <&epic id>"
+msgstr ""
+
+msgid " or references (e.g. path/to/project!merge_request_id)"
+msgstr ""
+
+msgid "\"%{path}\" did not exist on \"%{ref}\""
+msgstr ""
+
+msgid "%d URL scanned"
+msgid_plural "%d URLs scanned"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "%d approver"
+msgid_plural "%d approvers"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "%d approver (you've approved)"
+msgid_plural "%d approvers (you've approved)"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "%d changed file"
+msgid_plural "%d changed files"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "%d child epic"
+msgid_plural "%d child epics"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "%d code quality issue"
+msgid_plural "%d code quality issues"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "%d comment"
+msgid_plural "%d comments"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "%d comment on this commit"
+msgid_plural "%d comments on this commit"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "%d commit"
+msgid_plural "%d commits"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "%d commit behind"
+msgid_plural "%d commits behind"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "%d commit,"
+msgid_plural "%d commits,"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "%d commits"
+msgstr ""
+
+msgid "%d contribution"
+msgid_plural "%d contributions"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "%d day"
+msgid_plural "%d days"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "%d error"
+msgid_plural "%d errors"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "%d exporter"
+msgid_plural "%d exporters"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "%d failed"
+msgid_plural "%d failed"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "%d fixed test result"
+msgid_plural "%d fixed test results"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "%d group selected"
+msgid_plural "%d groups selected"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "%d inaccessible merge request"
+msgid_plural "%d inaccessible merge requests"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "%d issue"
+msgid_plural "%d issues"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "%d issue selected"
+msgid_plural "%d issues selected"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "%d layer"
+msgid_plural "%d layers"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "%d merge request"
+msgid_plural "%d merge requests"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "%d merge request that you don't have access to."
+msgid_plural "%d merge requests that you don't have access to."
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "%d metric"
+msgid_plural "%d metrics"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "%d milestone"
+msgid_plural "%d milestones"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "%d minute"
+msgid_plural "%d minutes"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "%d more comment"
+msgid_plural "%d more comments"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "%d personal project will be removed and cannot be restored."
+msgid_plural "%d personal projects will be removed and cannot be restored."
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "%d project"
+msgid_plural "%d projects"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "%d request with warnings"
+msgid_plural "%d requests with warnings"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "%d second"
+msgid_plural "%d seconds"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "%d shard selected"
+msgid_plural "%d shards selected"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "%d tag"
+msgid_plural "%d tags"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "%d unresolved thread"
+msgid_plural "%d unresolved threads"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "%d vulnerability"
+msgid_plural "%d vulnerabilities"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "%d vulnerability dismissed"
+msgid_plural "%d vulnerabilities dismissed"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "%s additional commit has been omitted to prevent performance issues."
+msgid_plural "%s additional commits have been omitted to prevent performance issues."
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "%{actionText} & %{openOrClose} %{noteable}"
+msgstr ""
+
+msgid "%{authorsName}'s thread"
+msgstr ""
+
+msgid "%{commit_author_link} authored %{commit_timeago}"
+msgstr ""
+
+msgid "%{completedWeight} of %{totalWeight} weight completed"
+msgstr ""
+
+msgid "%{containerScanningLinkStart}Container Scanning%{containerScanningLinkEnd} and/or %{dependencyScanningLinkStart}Dependency Scanning%{dependencyScanningLinkEnd} must be enabled. %{securityBotLinkStart}GitLab-Security-Bot%{securityBotLinkEnd} will be the author of the auto-created merge request. %{moreInfoLinkStart}More information%{moreInfoLinkEnd}."
+msgstr ""
+
+msgid "%{cores} cores"
+msgstr ""
+
+msgid "%{count} LOC/commit"
+msgstr ""
+
+msgid "%{count} approval required from %{name}"
+msgid_plural "%{count} approvals required from %{name}"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "%{count} approvals from %{name}"
+msgstr ""
+
+msgid "%{count} files touched"
+msgstr ""
+
+msgid "%{count} more"
+msgstr ""
+
+msgid "%{count} more assignees"
+msgstr ""
+
+msgid "%{count} more release"
+msgid_plural "%{count} more releases"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "%{count} of %{required} approvals from %{name}"
+msgstr ""
+
+msgid "%{count} of %{total}"
+msgstr ""
+
+msgid "%{count} participant"
+msgid_plural "%{count} participants"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "%{count} pending comment"
+msgid_plural "%{count} pending comments"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "%{count} related %{pluralized_subject}: %{links}"
+msgstr ""
+
+msgid "%{dashboard_path} could not be found."
+msgstr ""
+
+msgid "%{days} days until tags are automatically removed"
+msgstr ""
+
+msgid "%{deployLinkStart}Use a template to deploy to ECS%{deployLinkEnd}, or use a docker image to %{commandsLinkStart}run AWS commands in GitLab CI/CD%{commandsLinkEnd}."
+msgstr ""
+
+msgid "%{description}- Sentry event: %{errorUrl}- First seen: %{firstSeen}- Last seen: %{lastSeen} %{countLabel}: %{count}%{userCountLabel}: %{userCount}"
+msgstr ""
+
+msgid "%{duration}ms"
+msgstr ""
+
+msgid "%{edit_in_new_fork_notice} Try to cherry-pick this commit again."
+msgstr ""
+
+msgid "%{edit_in_new_fork_notice} Try to create a new directory again."
+msgstr ""
+
+msgid "%{edit_in_new_fork_notice} Try to revert this commit again."
+msgstr ""
+
+msgid "%{edit_in_new_fork_notice} Try to upload a file again."
+msgstr ""
+
+msgid "%{extra} more downstream pipelines"
+msgstr ""
+
+msgid "%{filePath} deleted"
+msgstr ""
+
+msgid "%{firstLabel} +%{labelCount} more"
+msgstr ""
+
+msgid "%{firstMilestoneName} + %{numberOfOtherMilestones} more"
+msgstr ""
+
+msgid "%{global_id} is not a valid id for %{expected_type}."
+msgstr ""
+
+msgid "%{group_docs_link_start}Groups%{group_docs_link_end} allow you to manage and collaborate across multiple projects. Members of a group have access to all of its projects."
+msgstr ""
+
+msgid "%{group_name} uses group managed accounts. You need to create a new GitLab account which will be managed by %{group_name}."
+msgstr ""
+
+msgid "%{host} sign-in from new location"
+msgstr ""
+
+msgid "%{icon}You are about to add %{usersTag} people to the discussion. They will all receive a notification."
+msgstr ""
+
+msgid "%{integrations_link_start}Integrations%{link_end} enable you to make third-party applications part of your GitLab workflow. If the available integrations don't meet your needs, consider using a %{webhooks_link_start}webhook%{link_end}."
+msgstr ""
+
+msgid "%{issuableType} will be removed! Are you sure?"
+msgstr ""
+
+msgid "%{issuesCount} issues in this group"
+msgstr ""
+
+msgid "%{issuesSize} issues"
+msgstr ""
+
+msgid "%{issuesSize} issues with a limit of %{maxIssueCount}"
+msgstr ""
+
+msgid "%{labelStart}Class:%{labelEnd} %{class}"
+msgstr ""
+
+msgid "%{labelStart}Evidence:%{labelEnd} %{evidence}"
+msgstr ""
+
+msgid "%{labelStart}File:%{labelEnd} %{file}"
+msgstr ""
+
+msgid "%{labelStart}Image:%{labelEnd} %{image}"
+msgstr ""
+
+msgid "%{labelStart}Method:%{labelEnd} %{method}"
+msgstr ""
+
+msgid "%{labelStart}Namespace:%{labelEnd} %{namespace}"
+msgstr ""
+
+msgid "%{labelStart}Report Type:%{labelEnd} %{reportType}"
+msgstr ""
+
+msgid "%{labelStart}Scanner:%{labelEnd} %{scanner}"
+msgstr ""
+
+msgid "%{labelStart}Severity:%{labelEnd} %{severity}"
+msgstr ""
+
+msgid "%{label_for_message} unavailable"
+msgstr ""
+
+msgid "%{lets_encrypt_link_start}Let's Encrypt%{lets_encrypt_link_end} is a free, automated, and open certificate authority (CA), that give digital certificates in order to enable HTTPS (SSL/TLS) for websites."
+msgstr ""
+
+msgid "%{level_name} is not allowed in a %{group_level_name} group."
+msgstr ""
+
+msgid "%{level_name} is not allowed since the fork source project has lower visibility."
+msgstr ""
+
+msgid "%{lineOneStart}Drag and drop to upload your designs%{lineOneEnd} or %{linkStart}click to upload%{linkEnd}."
+msgstr ""
+
+msgid "%{link_start}Learn more%{link_end} about what information is shared with GitLab Inc."
+msgstr ""
+
+msgid "%{link_start}Read more%{link_end} about role permissions"
+msgstr ""
+
+msgid "%{listToShow}, and %{awardsListLength} more."
+msgstr ""
+
+msgid "%{loadingIcon} Started"
+msgstr ""
+
+msgid "%{lock_path} is locked by GitLab User %{lock_user_id}"
+msgstr ""
+
+msgid "%{markdownDocsLinkStart}Markdown%{markdownDocsLinkEnd} and %{quickActionsDocsLinkStart}quick actions%{quickActionsDocsLinkEnd} are supported"
+msgstr ""
+
+msgid "%{mergeLength}/%{usersLength} can merge"
+msgstr ""
+
+msgid "%{mrText}, this issue will be closed automatically."
+msgstr ""
+
+msgid "%{name_with_link} has %{percent} or less Shared Runner Pipeline minutes remaining. Once it runs out, no new jobs or pipelines in its projects will run."
+msgstr ""
+
+msgid "%{name_with_link} has run out of Shared Runner Pipeline minutes so no new jobs or pipelines in its projects will run."
+msgstr ""
+
+msgid "%{namespace_name} is now read-only. You cannot: %{base_message}"
+msgstr ""
+
+msgid "%{name} contained %{resultsString}"
+msgstr ""
+
+msgid "%{name} found %{resultsString}"
+msgstr ""
+
+msgid "%{name} is already being used for another emoji"
+msgstr ""
+
+msgid "%{name} is scheduled for %{action}"
+msgstr ""
+
+msgid "%{name}'s avatar"
+msgstr ""
+
+msgid "%{name}(%{url}) has %{percent} or less Shared Runner Pipeline minutes remaining. Once it runs out, no new jobs or pipelines in its projects will run."
+msgstr ""
+
+msgid "%{name}(%{url}) has run out of Shared Runner Pipeline minutes so no new jobs or pipelines in its projects will run."
+msgstr ""
+
+msgid "%{no_of_days} day"
+msgid_plural "%{no_of_days} days"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "%{number_commits_behind} commits behind %{default_branch}, %{number_commits_ahead} commits ahead"
+msgstr ""
+
+msgid "%{openOrClose} %{noteable}"
+msgstr ""
+
+msgid "%{openedEpics} open, %{closedEpics} closed"
+msgstr ""
+
+msgid "%{openedIssues} open, %{closedIssues} closed"
+msgstr ""
+
+msgid "%{percentage}%% weight completed"
+msgstr ""
+
+msgid "%{percent}%% complete"
+msgstr ""
+
+msgid "%{percent}%{percentSymbol} complete"
+msgstr ""
+
+msgid "%{placeholder} is not a valid color scheme"
+msgstr ""
+
+msgid "%{placeholder} is not a valid theme"
+msgstr ""
+
+msgid "%{primary} (%{secondary})"
+msgstr ""
+
+msgid "%{ref} cannot be added: %{error}"
+msgstr ""
+
+msgid "%{releases} release"
+msgid_plural "%{releases} releases"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "%{remaining_approvals} left"
+msgstr ""
+
+msgid "%{reportType} %{status} detected %{critical} critical and %{high} high severity vulnerabilities out of %{total}."
+msgstr ""
+
+msgid "%{reportType} %{status} detected %{critical} critical and %{high} high severity vulnerabilities."
+msgstr ""
+
+msgid "%{reportType} %{status} detected %{critical} critical severity vulnerabilities out of %{total}."
+msgstr ""
+
+msgid "%{reportType} %{status} detected %{critical} critical severity vulnerability."
+msgid_plural "%{reportType} %{status} detected %{critical} critical severity vulnerabilities."
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "%{reportType} %{status} detected %{high} high severity vulnerabilities out of %{total}."
+msgstr ""
+
+msgid "%{reportType} %{status} detected %{high} high severity vulnerability."
+msgid_plural "%{reportType} %{status} detected %{high} high severity vulnerabilities."
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "%{reportType} %{status} detected %{other} vulnerability."
+msgid_plural "%{reportType} %{status} detected %{other} vulnerabilities."
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "%{reportType} %{status} detected no new vulnerabilities."
+msgstr ""
+
+msgid "%{retryButtonStart}Try again%{retryButtonEnd} or %{newFileButtonStart}attach a new file%{newFileButtonEnd}"
+msgstr ""
+
+msgid "%{service_title} %{message}."
+msgstr ""
+
+msgid "%{size} GiB"
+msgstr ""
+
+msgid "%{size} KiB"
+msgstr ""
+
+msgid "%{size} MiB"
+msgstr ""
+
+msgid "%{size} bytes"
+msgstr ""
+
+msgid "%{spammable_titlecase} was submitted to Akismet successfully."
+msgstr ""
+
+msgid "%{spanStart}at line%{spanEnd} %{errorLine}%{errorColumn}"
+msgstr ""
+
+msgid "%{spanStart}in%{spanEnd} %{errorFn}"
+msgstr ""
+
+msgid "%{start} to %{end}"
+msgstr ""
+
+msgid "%{state} epics"
+msgstr ""
+
+msgid "%{strongStart}Note:%{strongEnd} Once a custom stage has been added you can re-order stages by dragging them into the desired position."
+msgstr ""
+
+msgid "%{strong_start}%{branch_count}%{strong_end} Branch"
+msgid_plural "%{strong_start}%{branch_count}%{strong_end} Branches"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "%{strong_start}%{commit_count}%{strong_end} Commit"
+msgid_plural "%{strong_start}%{commit_count}%{strong_end} Commits"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "%{strong_start}%{human_size}%{strong_end} Files"
+msgstr ""
+
+msgid "%{strong_start}%{human_size}%{strong_end} Storage"
+msgstr ""
+
+msgid "%{strong_start}%{release_count}%{strong_end} Release"
+msgid_plural "%{strong_start}%{release_count}%{strong_end} Releases"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "%{strong_start}%{tag_count}%{strong_end} Tag"
+msgid_plural "%{strong_start}%{tag_count}%{strong_end} Tags"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "%{tabname} changed"
+msgstr ""
+
+msgid "%{tags} tag per image name"
+msgstr ""
+
+msgid "%{tags} tags per image name"
+msgstr ""
+
+msgid "%{tag}-%{evidence}-%{filename}"
+msgstr ""
+
+msgid "%{template_project_id} is unknown or invalid"
+msgstr ""
+
+msgid "%{text} %{files}"
+msgid_plural "%{text} %{files} files"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "%{text} is available"
+msgstr ""
+
+msgid "%{timebox_name} should belong either to a project or a group."
+msgstr ""
+
+msgid "%{title} %{operator} %{threshold}"
+msgstr ""
+
+msgid "%{title} changes"
+msgstr ""
+
+msgid "%{token}..."
+msgstr ""
+
+msgid "%{totalCpu} (%{freeSpacePercentage}%{percentSymbol} free)"
+msgstr ""
+
+msgid "%{totalMemory} (%{freeSpacePercentage}%{percentSymbol} free)"
+msgstr ""
+
+msgid "%{totalWeight} total weight"
+msgstr ""
+
+msgid "%{total} open issue weight"
+msgstr ""
+
+msgid "%{total} open issues"
+msgstr ""
+
+msgid "%{usage_ping_link_start}Learn more%{usage_ping_link_end} about what information is shared with GitLab Inc."
+msgstr ""
+
+msgid "%{userName} (cannot merge)"
+msgstr ""
+
+msgid "%{userName}'s avatar"
+msgstr ""
+
+msgid "%{user_name} profile page"
+msgstr ""
+
+msgid "%{username}'s avatar"
+msgstr ""
+
+msgid "%{value} s"
+msgstr ""
+
+msgid "%{verb} %{time_spent_value} spent time."
+msgstr ""
+
+msgid "%{webhooks_link_start}%{webhook_type}%{link_end} enable you to send notifications to web applications in response to events in a group or project."
+msgstr ""
+
+msgid "%{webhooks_link_start}%{webhook_type}%{link_end} enable you to send notifications to web applications in response to events in a group or project. We recommend using an %{integrations_link_start}integration%{link_end} in preference to a webhook."
+msgstr ""
+
+msgid "'%{level}' is not a valid visibility level"
+msgstr ""
+
+msgid "'%{name}' stage already exists"
+msgstr ""
+
+msgid "'%{source}' is not a import source"
+msgstr ""
+
+msgid "'%{template_name}' is unknown or invalid"
+msgstr ""
+
+msgid "(%d closed)"
+msgid_plural "(%d closed)"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "(%{linkStart}Cron syntax%{linkEnd})"
+msgstr ""
+
+msgid "(%{mrCount} merged)"
+msgstr ""
+
+msgid "(No changes)"
+msgstr ""
+
+msgid "(check progress)"
+msgstr ""
+
+msgid "(external source)"
+msgstr ""
+
+msgid "(removed)"
+msgstr ""
+
+msgid "(revoked)"
+msgstr ""
+
+msgid "*"
+msgstr ""
+
+msgid "+ %{amount} more"
+msgstr ""
+
+msgid "+ %{count} more"
+msgstr ""
+
+msgid "+ %{moreCount} more"
+msgstr ""
+
+msgid "+ %{numberOfHiddenAssignees} more"
+msgstr ""
+
+msgid "+%d more"
+msgid_plural "+%d more"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "+%{approvers} more approvers"
+msgstr ""
+
+msgid "+%{tags} more"
+msgstr ""
+
+msgid ", or "
+msgstr ""
+
+msgid "- Event"
+msgid_plural "- Events"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "- Runner is active and can process any new jobs"
+msgstr ""
+
+msgid "- Runner is paused and will not receive any new jobs"
+msgstr ""
+
+msgid "- User"
+msgid_plural "- Users"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "- of - weight completed"
+msgstr ""
+
+msgid "- show less"
+msgstr ""
+
+msgid "0 for unlimited"
+msgstr ""
+
+msgid "0 for unlimited, only effective with remote storage enabled."
+msgstr ""
+
+msgid "1 %{type} addition"
+msgid_plural "%{count} %{type} additions"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "1 %{type} modification"
+msgid_plural "%{count} %{type} modifications"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "1 Day"
+msgid_plural "%d Days"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "1 closed issue"
+msgid_plural "%{issues} closed issues"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "1 closed merge request"
+msgid_plural "%{merge_requests} closed merge requests"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "1 day"
+msgid_plural "%d days"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "1 group"
+msgid_plural "%d groups"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "1 hour"
+msgid_plural "%d hours"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "1 merged merge request"
+msgid_plural "%{merge_requests} merged merge requests"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "1 minute"
+msgid_plural "%d minutes"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "1 month"
+msgstr ""
+
+msgid "1 open issue"
+msgid_plural "%{issues} open issues"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "1 open merge request"
+msgid_plural "%{merge_requests} open merge requests"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "1 pipeline"
+msgid_plural "%d pipelines"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "1 role"
+msgid_plural "%d roles"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "1 user"
+msgid_plural "%{num} users"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "1 week"
+msgstr ""
+
+msgid "1-9 contributions"
+msgstr ""
+
+msgid "10-19 contributions"
+msgstr ""
+
+msgid "1st contribution!"
+msgstr ""
+
+msgid "20-29 contributions"
+msgstr ""
+
+msgid "2FA"
+msgstr ""
+
+msgid "2FADevice|Registered On"
+msgstr ""
+
+msgid "3 days"
+msgstr ""
+
+msgid "3 hours"
+msgstr ""
+
+msgid "30 minutes"
+msgstr ""
+
+msgid "30+ contributions"
+msgstr ""
+
+msgid "403|Please contact your GitLab administrator to get permission."
+msgstr ""
+
+msgid "403|You don't have the permission to access this page."
+msgstr ""
+
+msgid "404|Make sure the address is correct and the page hasn't moved."
+msgstr ""
+
+msgid "404|Page Not Found"
+msgstr ""
+
+msgid "404|Please contact your GitLab administrator if you think this is a mistake."
+msgstr ""
+
+msgid "8 hours"
+msgstr ""
+
+msgid ":%{startLine} to %{endLine}"
+msgstr ""
+
+msgid "< 1 hour"
+msgstr ""
+
+msgid "<code>\"johnsmith@example.com\": \"@johnsmith\"</code> will add \"By <a href=\"#\">@johnsmith</a>\" to all issues and comments originally created by johnsmith@example.com, and will set <a href=\"#\">@johnsmith</a> as the assignee on all issues originally assigned to johnsmith@example.com."
+msgstr ""
+
+msgid "<code>\"johnsmith@example.com\": \"John Smith\"</code> will add \"By John Smith\" to all issues and comments originally created by johnsmith@example.com."
+msgstr ""
+
+msgid "<code>\"johnsmith@example.com\": \"johnsm...@example.com\"</code> will add \"By johnsm...@example.com\" to all issues and comments originally created by johnsmith@example.com. The email address or username is masked to ensure the user's privacy."
+msgstr ""
+
+msgid "<code>\"johnsmith@example.com\": \"johnsmith@example.com\"</code> will add \"By <a href=\"#\">johnsmith@example.com</a>\" to all issues and comments originally created by johnsmith@example.com. By default, the email address or username is masked to ensure the user's privacy. Use this option if you want to show the full email address."
+msgstr ""
+
+msgid "<code>Masked</code> to prevent the values from being displayed in job logs (must match certain regexp requirements)."
+msgstr ""
+
+msgid "<code>Protected</code> to expose them to protected branches or tags only."
+msgstr ""
+
+msgid "<namespace / project>"
+msgstr ""
+
+msgid "<no name set>"
+msgstr ""
+
+msgid "<no scopes selected>"
+msgstr ""
+
+msgid "<project name>"
+msgstr ""
+
+msgid "<strong>%{group_name}</strong> group members"
+msgstr ""
+
+msgid "<strong>%{label_name}</strong> <span>will be permanently deleted from %{subject_name}. This cannot be undone.</span>"
+msgstr ""
+
+msgid "<strong>Deletes</strong> source branch"
+msgstr ""
+
+msgid "A 'Runner' is a process which runs a job. You can set up as many Runners as you need."
+msgstr ""
+
+msgid "A .NET Core console application template, customizable for any .NET Core project"
+msgstr ""
+
+msgid "A GitBook site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
+msgid "A Hexo site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
+msgid "A Hugo site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
+msgid "A Jekyll site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
+msgid "A Let's Encrypt SSL certificate can not be obtained until your domain is verified."
+msgstr ""
+
+msgid "A Let's Encrypt account will be configured for this GitLab installation using your email address. You will receive emails to warn of expiring certificates."
+msgstr ""
+
+msgid "A basic page and serverless function that uses AWS Lambda, AWS API Gateway, and GitLab Pages"
+msgstr ""
+
+msgid "A complete DevOps platform"
+msgstr ""
+
+msgid "A default branch cannot be chosen for an empty project."
+msgstr ""
+
+msgid "A deleted user"
+msgstr ""
+
+msgid "A file has been changed."
+msgstr ""
+
+msgid "A file was not found."
+msgstr ""
+
+msgid "A file with '%{file_name}' already exists in %{branch} branch"
+msgstr ""
+
+msgid "A fork is a copy of a project.<br />Forking a repository allows you to make changes without affecting the original project."
+msgstr ""
+
+msgid "A group is a collection of several projects"
+msgstr ""
+
+msgid "A group represents your organization in GitLab. Groups allow you to manage users and collaborate across multiple projects."
+msgstr ""
+
+msgid "A member of the abuse team will review your report as soon as possible."
+msgstr ""
+
+msgid "A merge request approval is required when a security report contains a new vulnerability of high, critical, or unknown severity."
+msgstr ""
+
+msgid "A merge request approval is required when the license compliance report contains a blacklisted license."
+msgstr ""
+
+msgid "A new Auto DevOps pipeline has been created, go to %{pipelines_link_start}Pipelines page%{pipelines_link_end} for details"
+msgstr ""
+
+msgid "A new Release %{tag} for %{name} was published. Visit the %{release_link_start}Releases page%{release_link_end} to read more about it."
+msgstr ""
+
+msgid "A new Release %{tag} for %{name} was published. Visit the Releases page to read more about it:"
+msgstr ""
+
+msgid "A new branch will be created in your fork and a new merge request will be started."
+msgstr ""
+
+msgid "A new impersonation token has been created."
+msgstr ""
+
+msgid "A plain HTML site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
+msgid "A project boilerplate for Salesforce App development with Salesforce Developer tools."
+msgstr ""
+
+msgid "A project containing issues for each audit inquiry in the HIPAA Audit Protocol published by the U.S. Department of Health & Human Services"
+msgstr ""
+
+msgid "A project is where you house your files (repository), plan your work (issues), and publish your documentation (wiki), %{among_other_things_link}."
+msgstr ""
+
+msgid "A ready-to-go template for use with Android apps."
+msgstr ""
+
+msgid "A ready-to-go template for use with iOS Swift apps."
+msgstr ""
+
+msgid "A regular expression that will be used to find the test coverage output in the job log. Leave blank to disable"
+msgstr ""
+
+msgid "A secure token that identifies an external storage request."
+msgstr ""
+
+msgid "A sign-in to your account has been made from the following IP address: %{ip}"
+msgstr ""
+
+msgid "A subscription will trigger a new pipeline on the default branch of this project when a pipeline successfully completes for a new tag on the %{default_branch_docs} of the subscribed project."
+msgstr ""
+
+msgid "A suggestion is not applicable."
+msgstr ""
+
+msgid "A user with write access to the source branch selected this option"
+msgstr ""
+
+msgid "ACTION REQUIRED: Something went wrong while obtaining the Let's Encrypt certificate for GitLab Pages domain '%{domain}'"
+msgstr ""
+
+msgid "API Token"
+msgstr ""
+
+msgid "AWS Access Key"
+msgstr ""
+
+msgid "AWS Access Key. Only required if not using role instance credentials"
+msgstr ""
+
+msgid "AWS Secret Access Key"
+msgstr ""
+
+msgid "AWS Secret Access Key. Only required if not using role instance credentials"
+msgstr ""
+
+msgid "Abort"
+msgstr ""
+
+msgid "About GitLab"
+msgstr ""
+
+msgid "About GitLab CE"
+msgstr ""
+
+msgid "About auto deploy"
+msgstr ""
+
+msgid "About this feature"
+msgstr ""
+
+msgid "Abuse Reports"
+msgstr ""
+
+msgid "Abuse reports"
+msgstr ""
+
+msgid "Accept invitation"
+msgstr ""
+
+msgid "Accept terms"
+msgstr ""
+
+msgid "Acceptable for use in this project"
+msgstr ""
+
+msgid "Access Tokens"
+msgstr ""
+
+msgid "Access denied for your LDAP account."
+msgstr ""
+
+msgid "Access denied! Please verify you can add deploy keys to this repository."
+msgstr ""
+
+msgid "Access expiration date"
+msgstr ""
+
+msgid "Access forbidden. Check your access level."
+msgstr ""
+
+msgid "Access to '%{classification_label}' not allowed"
+msgstr ""
+
+msgid "Access to Pages websites are controlled based on the user's membership to a given project. By checking this box, users will be required to be logged in to have access to all Pages websites in your instance."
+msgstr ""
+
+msgid "AccessDropdown|Groups"
+msgstr ""
+
+msgid "AccessDropdown|Roles"
+msgstr ""
+
+msgid "AccessDropdown|Users"
+msgstr ""
+
+msgid "AccessTokens|Access Tokens"
+msgstr ""
+
+msgid "AccessTokens|Are you sure?"
+msgstr ""
+
+msgid "AccessTokens|Are you sure? Any RSS or calendar URLs currently in use will stop working."
+msgstr ""
+
+msgid "AccessTokens|Are you sure? Any issue email addresses currently in use will stop working."
+msgstr ""
+
+msgid "AccessTokens|Created"
+msgstr ""
+
+msgid "AccessTokens|Feed token"
+msgstr ""
+
+msgid "AccessTokens|Incoming email token"
+msgstr ""
+
+msgid "AccessTokens|It cannot be used to access any other data."
+msgstr ""
+
+msgid "AccessTokens|Keep this token secret. Anyone who gets ahold of it can access repository static objects as if they were you. You should %{reset_link_start}reset it%{reset_link_end} if that ever happens."
+msgstr ""
+
+msgid "AccessTokens|Keep this token secret. Anyone who gets ahold of it can create issues as if they were you. You should %{link_reset_it} if that ever happens."
+msgstr ""
+
+msgid "AccessTokens|Keep this token secret. Anyone who gets ahold of it can read activity and issue RSS feeds or your calendar feed as if they were you. You should %{link_reset_it} if that ever happens."
+msgstr ""
+
+msgid "AccessTokens|Personal Access Tokens"
+msgstr ""
+
+msgid "AccessTokens|Static object token"
+msgstr ""
+
+msgid "AccessTokens|They are the only accepted password when you have Two-Factor Authentication (2FA) enabled."
+msgstr ""
+
+msgid "AccessTokens|You can also use personal access tokens to authenticate against Git over HTTP."
+msgstr ""
+
+msgid "AccessTokens|You can generate a personal access token for each application you use that needs access to the GitLab API."
+msgstr ""
+
+msgid "AccessTokens|Your feed token is used to authenticate you when your RSS reader loads a personalized RSS feed or when your calendar application loads a personalized calendar, and is included in those feed URLs."
+msgstr ""
+
+msgid "AccessTokens|Your incoming email token is used to authenticate you when you create a new issue by email, and is included in your personal project-specific email addresses."
+msgstr ""
+
+msgid "AccessTokens|Your static object token is used to authenticate you when repository static objects (e.g. archives, blobs, ...) are being served from an external storage."
+msgstr ""
+
+msgid "AccessTokens|reset it"
+msgstr ""
+
+msgid "AccessibilityReport|Learn More"
+msgstr ""
+
+msgid "AccessibilityReport|Message: %{message}"
+msgstr ""
+
+msgid "AccessibilityReport|New"
+msgstr ""
+
+msgid "AccessibilityReport|The accessibility scanning found an error of the following type: %{code}"
+msgstr ""
+
+msgid "Account"
+msgstr ""
+
+msgid "Account ID"
+msgstr ""
+
+msgid "Account and limit"
+msgstr ""
+
+msgid "Account: %{account}"
+msgstr ""
+
+msgid "Action to take when receiving an alert."
+msgstr ""
+
+msgid "Actions"
+msgstr ""
+
+msgid "Activate"
+msgstr ""
+
+msgid "Activate Service Desk"
+msgstr ""
+
+msgid "Active"
+msgstr ""
+
+msgid "Active %{type} (%{token_length})"
+msgstr ""
+
+msgid "Active Sessions"
+msgstr ""
+
+msgid "Active Users:"
+msgstr ""
+
+msgid "Active users"
+msgstr ""
+
+msgid "Activity"
+msgstr ""
+
+msgid "Add"
+msgstr ""
+
+msgid "Add %d issue"
+msgid_plural "Add %d issues"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "Add %{linkStart}assets%{linkEnd} to your Release. GitLab automatically includes read-only assets, like source code and release evidence."
+msgstr ""
+
+msgid "Add CHANGELOG"
+msgstr ""
+
+msgid "Add CONTRIBUTING"
+msgstr ""
+
+msgid "Add GitLab to Slack"
+msgstr ""
+
+msgid "Add Group Webhooks and GitLab Enterprise Edition."
+msgstr ""
+
+msgid "Add Jaeger URL"
+msgstr ""
+
+msgid "Add Kubernetes cluster"
+msgstr ""
+
+msgid "Add LICENSE"
+msgstr ""
+
+msgid "Add New Node"
+msgstr ""
+
+msgid "Add README"
+msgstr ""
+
+msgid "Add Variable"
+msgstr ""
+
+msgid "Add Zoom meeting"
+msgstr ""
+
+msgid "Add a %{type}"
+msgstr ""
+
+msgid "Add a GPG key"
+msgstr ""
+
+msgid "Add a Grafana button in the admin sidebar, monitoring section, to access a variety of statistics on the health and performance of GitLab."
+msgstr ""
+
+msgid "Add a To Do"
+msgstr ""
+
+msgid "Add a bullet list"
+msgstr ""
+
+msgid "Add a comment to this line"
+msgstr ""
+
+msgid "Add a general comment to this %{noteableDisplayName}."
+msgstr ""
+
+msgid "Add a general comment to this %{noteable_name}."
+msgstr ""
+
+msgid "Add a homepage to your wiki that contains information about your project and GitLab will display it here instead of this message."
+msgstr ""
+
+msgid "Add a line"
+msgstr ""
+
+msgid "Add a link"
+msgstr ""
+
+msgid "Add a new issue"
+msgstr ""
+
+msgid "Add a numbered list"
+msgstr ""
+
+msgid "Add a table"
+msgstr ""
+
+msgid "Add a task list"
+msgstr ""
+
+msgid "Add additional text to appear in all email communications. %{character_limit} character limit"
+msgstr ""
+
+msgid "Add an SSH key"
+msgstr ""
+
+msgid "Add an existing issue"
+msgstr ""
+
+msgid "Add an impersonation token"
+msgstr ""
+
+msgid "Add an issue"
+msgstr ""
+
+msgid "Add another link"
+msgstr ""
+
+msgid "Add approval rule"
+msgstr ""
+
+msgid "Add approvers"
+msgstr ""
+
+msgid "Add bold text"
+msgstr ""
+
+msgid "Add child epic to an epic"
+msgstr ""
+
+msgid "Add comment now"
+msgstr ""
+
+msgid "Add domain"
+msgstr ""
+
+msgid "Add email address"
+msgstr ""
+
+msgid "Add environment"
+msgstr ""
+
+msgid "Add header and footer to emails. Please note that color settings will only be applied within the application interface"
+msgstr ""
+
+msgid "Add image comment"
+msgstr ""
+
+msgid "Add issues"
+msgstr ""
+
+msgid "Add italic text"
+msgstr ""
+
+msgid "Add label(s)"
+msgstr ""
+
+msgid "Add license"
+msgstr ""
+
+msgid "Add list"
+msgstr ""
+
+msgid "Add new application"
+msgstr ""
+
+msgid "Add new directory"
+msgstr ""
+
+msgid "Add or subtract spent time"
+msgstr ""
+
+msgid "Add reaction"
+msgstr ""
+
+msgid "Add request manually"
+msgstr ""
+
+msgid "Add strikethrough text"
+msgstr ""
+
+msgid "Add suggestion to batch"
+msgstr ""
+
+msgid "Add system hook"
+msgstr ""
+
+msgid "Add to Slack"
+msgstr ""
+
+msgid "Add to epic"
+msgstr ""
+
+msgid "Add to merge train"
+msgstr ""
+
+msgid "Add to merge train when pipeline succeeds"
+msgstr ""
+
+msgid "Add to review"
+msgstr ""
+
+msgid "Add to tree"
+msgstr ""
+
+msgid "Add user(s) to the group:"
+msgstr ""
+
+msgid "Add users to group"
+msgstr ""
+
+msgid "Add variable"
+msgstr ""
+
+msgid "Add webhook"
+msgstr ""
+
+msgid "AddMember|No users specified."
+msgstr ""
+
+msgid "AddMember|Too many users specified (limit is %{user_limit})"
+msgstr ""
+
+msgid "Added"
+msgstr ""
+
+msgid "Added %{epic_ref} as a child epic."
+msgstr ""
+
+msgid "Added %{label_references} %{label_text}."
+msgstr ""
+
+msgid "Added a To Do."
+msgstr ""
+
+msgid "Added an issue to an epic."
+msgstr ""
+
+msgid "Added at"
+msgstr ""
+
+msgid "Added for this merge request"
+msgstr ""
+
+msgid "Added in this version"
+msgstr ""
+
+msgid "Adding new applications is disabled in your GitLab instance. Please contact your GitLab administrator to get the permission"
+msgstr ""
+
+msgid "Additional minutes"
+msgstr ""
+
+msgid "Additional text"
+msgstr ""
+
+msgid "Adds"
+msgstr ""
+
+msgid "Adds %{epic_ref} as child epic."
+msgstr ""
+
+msgid "Adds %{labels} %{label_text}."
+msgstr ""
+
+msgid "Adds a To Do."
+msgstr ""
+
+msgid "Adds a Zoom meeting"
+msgstr ""
+
+msgid "Adds an issue to an epic."
+msgstr ""
+
+msgid "Adjust your filters/search criteria above. If you believe this may be an error, please refer to the %{linkStart}Geo Troubleshooting%{linkEnd} documentation for more information."
+msgstr ""
+
+msgid "Admin Area"
+msgstr ""
+
+msgid "Admin Note"
+msgstr ""
+
+msgid "Admin Notifications"
+msgstr ""
+
+msgid "Admin Overview"
+msgstr ""
+
+msgid "Admin mode already enabled"
+msgstr ""
+
+msgid "Admin mode disabled"
+msgstr ""
+
+msgid "Admin mode enabled"
+msgstr ""
+
+msgid "Admin notes"
+msgstr ""
+
+msgid "AdminArea|Active users"
+msgstr ""
+
+msgid "AdminArea|Billable users"
+msgstr ""
+
+msgid "AdminArea|Blocked users"
+msgstr ""
+
+msgid "AdminArea|Bots"
+msgstr ""
+
+msgid "AdminArea|Developer"
+msgstr ""
+
+msgid "AdminArea|Guest"
+msgstr ""
+
+msgid "AdminArea|Included Free in license"
+msgstr ""
+
+msgid "AdminArea|Maintainer"
+msgstr ""
+
+msgid "AdminArea|Owner"
+msgstr ""
+
+msgid "AdminArea|Reporter"
+msgstr ""
+
+msgid "AdminArea|Stop all jobs"
+msgstr ""
+
+msgid "AdminArea|Stop all jobs?"
+msgstr ""
+
+msgid "AdminArea|Stop jobs"
+msgstr ""
+
+msgid "AdminArea|Stopping jobs failed"
+msgstr ""
+
+msgid "AdminArea|Total users"
+msgstr ""
+
+msgid "AdminArea|Users statistics"
+msgstr ""
+
+msgid "AdminArea|Users with highest role"
+msgstr ""
+
+msgid "AdminArea|Users without a Group and Project"
+msgstr ""
+
+msgid "AdminArea|You’re about to stop all jobs.This will halt all current jobs that are running."
+msgstr ""
+
+msgid "AdminDashboard|Error loading the statistics. Please try again"
+msgstr ""
+
+msgid "AdminNote|Note"
+msgstr ""
+
+msgid "AdminProjects| You’re about to permanently delete the project %{projectName}, its repository, and all related resources including issues, merge requests, etc.. Once you confirm and press %{strong_start}Delete project%{strong_end}, it cannot be undone or recovered."
+msgstr ""
+
+msgid "AdminProjects|Delete"
+msgstr ""
+
+msgid "AdminProjects|Delete Project %{projectName}?"
+msgstr ""
+
+msgid "AdminProjects|Delete project"
+msgstr ""
+
+msgid "AdminSettings|Apply integration settings to all Projects"
+msgstr ""
+
+msgid "AdminSettings|Auto DevOps domain"
+msgstr ""
+
+msgid "AdminSettings|Elasticsearch, PlantUML, Slack application, Third party offers, Snowplow, Amazon EKS have moved to Settings > General."
+msgstr ""
+
+msgid "AdminSettings|Enable shared runners for new projects"
+msgstr ""
+
+msgid "AdminSettings|Environment variables are protected by default"
+msgstr ""
+
+msgid "AdminSettings|Go to General Settings"
+msgstr ""
+
+msgid "AdminSettings|Integrations configured here will automatically apply to all projects on this instance."
+msgstr ""
+
+msgid "AdminSettings|Moved to integrations"
+msgstr ""
+
+msgid "AdminSettings|No required pipeline"
+msgstr ""
+
+msgid "AdminSettings|Required pipeline configuration"
+msgstr ""
+
+msgid "AdminSettings|Select a pipeline configuration file"
+msgstr ""
+
+msgid "AdminSettings|Select a template"
+msgstr ""
+
+msgid "AdminSettings|Service template allows you to set default values for integrations"
+msgstr ""
+
+msgid "AdminSettings|Set an instance-wide auto included %{link_start}pipeline configuration%{link_end}. This pipeline configuration will be run after the project's own configuration."
+msgstr ""
+
+msgid "AdminSettings|Some settings have moved"
+msgstr ""
+
+msgid "AdminSettings|Specify a domain to use by default for every project's Auto Review Apps and Auto Deploy stages."
+msgstr ""
+
+msgid "AdminSettings|The required pipeline configuration can be selected from the %{code_start}gitlab-ci%{code_end} directory inside of the configured %{link_start}instance template repository%{link_end} or from GitLab provided configurations."
+msgstr ""
+
+msgid "AdminSettings|When creating a new environment variable it will be protected by default."
+msgstr ""
+
+msgid "AdminStatistics|Active Users"
+msgstr ""
+
+msgid "AdminStatistics|Forks"
+msgstr ""
+
+msgid "AdminStatistics|Issues"
+msgstr ""
+
+msgid "AdminStatistics|Merge Requests"
+msgstr ""
+
+msgid "AdminStatistics|Milestones"
+msgstr ""
+
+msgid "AdminStatistics|Notes"
+msgstr ""
+
+msgid "AdminStatistics|SSH Keys"
+msgstr ""
+
+msgid "AdminStatistics|Snippets"
+msgstr ""
+
+msgid "AdminUsers|2FA Disabled"
+msgstr ""
+
+msgid "AdminUsers|2FA Enabled"
+msgstr ""
+
+msgid "AdminUsers|Active"
+msgstr ""
+
+msgid "AdminUsers|Admin"
+msgstr ""
+
+msgid "AdminUsers|Admins"
+msgstr ""
+
+msgid "AdminUsers|Block"
+msgstr ""
+
+msgid "AdminUsers|Block user"
+msgstr ""
+
+msgid "AdminUsers|Block user %{username}?"
+msgstr ""
+
+msgid "AdminUsers|Blocked"
+msgstr ""
+
+msgid "AdminUsers|Blocking user has the following effects:"
+msgstr ""
+
+msgid "AdminUsers|Cannot unblock LDAP blocked users"
+msgstr ""
+
+msgid "AdminUsers|Deactivate"
+msgstr ""
+
+msgid "AdminUsers|Deactivate User %{username}?"
+msgstr ""
+
+msgid "AdminUsers|Deactivate user"
+msgstr ""
+
+msgid "AdminUsers|Deactivated"
+msgstr ""
+
+msgid "AdminUsers|Deactivating a user has the following effects:"
+msgstr ""
+
+msgid "AdminUsers|Delete User %{username} and contributions?"
+msgstr ""
+
+msgid "AdminUsers|Delete User %{username}?"
+msgstr ""
+
+msgid "AdminUsers|Delete user"
+msgstr ""
+
+msgid "AdminUsers|Delete user and contributions"
+msgstr ""
+
+msgid "AdminUsers|External"
+msgstr ""
+
+msgid "AdminUsers|Is using seat"
+msgstr ""
+
+msgid "AdminUsers|It's you!"
+msgstr ""
+
+msgid "AdminUsers|New user"
+msgstr ""
+
+msgid "AdminUsers|No users found"
+msgstr ""
+
+msgid "AdminUsers|Owned groups will be left"
+msgstr ""
+
+msgid "AdminUsers|Personal projects will be left"
+msgstr ""
+
+msgid "AdminUsers|Personal projects, group and user history will be left intact"
+msgstr ""
+
+msgid "AdminUsers|Reactivating a user will:"
+msgstr ""
+
+msgid "AdminUsers|Restore user access to the account, including web, Git and API."
+msgstr ""
+
+msgid "AdminUsers|Search by name, email or username"
+msgstr ""
+
+msgid "AdminUsers|Search users"
+msgstr ""
+
+msgid "AdminUsers|Send email to users"
+msgstr ""
+
+msgid "AdminUsers|Sort by"
+msgstr ""
+
+msgid "AdminUsers|The user will be logged out"
+msgstr ""
+
+msgid "AdminUsers|The user will not be able to access git repositories"
+msgstr ""
+
+msgid "AdminUsers|The user will not be able to access the API"
+msgstr ""
+
+msgid "AdminUsers|The user will not be able to use slash commands"
+msgstr ""
+
+msgid "AdminUsers|The user will not receive any notifications"
+msgstr ""
+
+msgid "AdminUsers|To confirm, type %{projectName}"
+msgstr ""
+
+msgid "AdminUsers|To confirm, type %{username}"
+msgstr ""
+
+msgid "AdminUsers|User will not be able to access git repositories"
+msgstr ""
+
+msgid "AdminUsers|User will not be able to login"
+msgstr ""
+
+msgid "AdminUsers|When the user logs back in, their account will reactivate as a fully active account"
+msgstr ""
+
+msgid "AdminUsers|Without projects"
+msgstr ""
+
+msgid "AdminUsers|You are about to permanently delete the user %{username}. Issues, merge requests, and groups linked to them will be transferred to a system-wide \"Ghost-user\". To avoid data loss, consider using the %{strong_start}block user%{strong_end} feature instead. Once you %{strong_start}Delete user%{strong_end}, it cannot be undone or recovered."
+msgstr ""
+
+msgid "AdminUsers|You are about to permanently delete the user %{username}. This will delete all of the issues, merge requests, and groups linked to them. To avoid data loss, consider using the %{strong_start}block user%{strong_end} feature instead. Once you %{strong_start}Delete user%{strong_end}, it cannot be undone or recovered."
+msgstr ""
+
+msgid "Administration"
+msgstr ""
+
+msgid "Advanced"
+msgstr ""
+
+msgid "Advanced Settings"
+msgstr ""
+
+msgid "Advanced permissions, Large File Storage and Two-Factor authentication settings."
+msgstr ""
+
+msgid "Advanced search functionality"
+msgstr ""
+
+msgid "After a successful password update you will be redirected to login screen."
+msgstr ""
+
+msgid "After a successful password update, you will be redirected to the login page where you can log in with your new password."
+msgstr ""
+
+msgid "After that, you will not to be able to use merge approvals or code quality as well as many other features."
+msgstr ""
+
+msgid "After that, you will not to be able to use merge approvals or epics as well as many other features."
+msgstr ""
+
+msgid "After that, you will not to be able to use merge approvals or epics as well as many security features."
+msgstr ""
+
+msgid "Alert"
+msgid_plural "Alerts"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "AlertManagement| assign yourself"
+msgstr ""
+
+msgid "AlertManagement|Acknowledged"
+msgstr ""
+
+msgid "AlertManagement|Alert"
+msgstr ""
+
+msgid "AlertManagement|Alert assignee(s): %{assignees}"
+msgstr ""
+
+msgid "AlertManagement|Alert detail"
+msgstr ""
+
+msgid "AlertManagement|Alert details"
+msgstr ""
+
+msgid "AlertManagement|Alert status: %{status}"
+msgstr ""
+
+msgid "AlertManagement|Alerts"
+msgstr ""
+
+msgid "AlertManagement|All alerts"
+msgstr ""
+
+msgid "AlertManagement|Assign To"
+msgstr ""
+
+msgid "AlertManagement|Assign status"
+msgstr ""
+
+msgid "AlertManagement|Assignee"
+msgstr ""
+
+msgid "AlertManagement|Assignees"
+msgstr ""
+
+msgid "AlertManagement|Authorize external service"
+msgstr ""
+
+msgid "AlertManagement|Create issue"
+msgstr ""
+
+msgid "AlertManagement|Critical"
+msgstr ""
+
+msgid "AlertManagement|Display alerts from all your monitoring tools directly within GitLab. Streamline the investigation of your alerts and the escalation of alerts to incidents."
+msgstr ""
+
+msgid "AlertManagement|Edit"
+msgstr ""
+
+msgid "AlertManagement|Events"
+msgstr ""
+
+msgid "AlertManagement|High"
+msgstr ""
+
+msgid "AlertManagement|Info"
+msgstr ""
+
+msgid "AlertManagement|Low"
+msgstr ""
+
+msgid "AlertManagement|Medium"
+msgstr ""
+
+msgid "AlertManagement|More information"
+msgstr ""
+
+msgid "AlertManagement|No Matching Results"
+msgstr ""
+
+msgid "AlertManagement|No alert data to display."
+msgstr ""
+
+msgid "AlertManagement|No alerts available to display. See %{linkStart}enabling alert management%{linkEnd} for more information on adding alerts to the list."
+msgstr ""
+
+msgid "AlertManagement|No alerts to display."
+msgstr ""
+
+msgid "AlertManagement|None"
+msgstr ""
+
+msgid "AlertManagement|None -"
+msgstr ""
+
+msgid "AlertManagement|Open"
+msgstr ""
+
+msgid "AlertManagement|Overview"
+msgstr ""
+
+msgid "AlertManagement|Reported %{when}"
+msgstr ""
+
+msgid "AlertManagement|Reported %{when} by %{tool}"
+msgstr ""
+
+msgid "AlertManagement|Resolved"
+msgstr ""
+
+msgid "AlertManagement|Service"
+msgstr ""
+
+msgid "AlertManagement|Severity"
+msgstr ""
+
+msgid "AlertManagement|Start time"
+msgstr ""
+
+msgid "AlertManagement|Status"
+msgstr ""
+
+msgid "AlertManagement|Surface alerts in GitLab"
+msgstr ""
+
+msgid "AlertManagement|There was an error displaying the alert. Please refresh the page to try again."
+msgstr ""
+
+msgid "AlertManagement|There was an error displaying the alerts. Confirm your endpoint's configuration details to ensure alerts appear."
+msgstr ""
+
+msgid "AlertManagement|There was an error while updating the assignee(s) list. Please try again."
+msgstr ""
+
+msgid "AlertManagement|There was an error while updating the assignee(s) of the alert. Please try again."
+msgstr ""
+
+msgid "AlertManagement|There was an error while updating the status of the alert. Please try again."
+msgstr ""
+
+msgid "AlertManagement|This assignee cannot be assigned to this alert."
+msgstr ""
+
+msgid "AlertManagement|Tool"
+msgstr ""
+
+msgid "AlertManagement|Triggered"
+msgstr ""
+
+msgid "AlertManagement|Unassigned"
+msgstr ""
+
+msgid "AlertManagement|Unknown"
+msgstr ""
+
+msgid "AlertManagement|View issue"
+msgstr ""
+
+msgid "AlertService|Review your external service's documentation to learn where to provide this information to your external service, and the %{linkStart}GitLab documentation%{linkEnd} to learn more about configuring your endpoint."
+msgstr ""
+
+msgid "AlertService|You must provide this URL and authorization key to authorize an external service to send alerts to GitLab. You can provide this URL and key to multiple services. After configuring an external service, alerts from your service will display on the GitLab %{linkStart}Alerts%{linkEnd} page."
+msgstr ""
+
+msgid "Alerts"
+msgstr ""
+
+msgid "Alerts endpoint"
+msgstr ""
+
+msgid "Algorithm"
+msgstr ""
+
+msgid "All"
+msgstr ""
+
+msgid "All %{replicableType} are being scheduled for %{action}"
+msgstr ""
+
+msgid "All (default)"
+msgstr ""
+
+msgid "All Members"
+msgstr ""
+
+msgid "All branches"
+msgstr ""
+
+msgid "All changes are committed"
+msgstr ""
+
+msgid "All default stages are currently visible"
+msgstr ""
+
+msgid "All email addresses will be used to identify your commits."
+msgstr ""
+
+msgid "All environments"
+msgstr ""
+
+msgid "All features are enabled for blank projects, from templates, or when importing, but you can disable them afterward in the project settings."
+msgstr ""
+
+msgid "All groups and projects"
+msgstr ""
+
+msgid "All issues for this milestone are closed."
+msgstr ""
+
+msgid "All issues for this milestone are closed. You may close this milestone now."
+msgstr ""
+
+msgid "All merge conflicts were resolved. The merge request can now be merged."
+msgstr ""
+
+msgid "All merge request dependencies have been merged"
+msgstr ""
+
+msgid "All paths are relative to the GitLab URL. Do not include %{relative_url_link_start}relative URL%{relative_url_link_end}."
+msgstr ""
+
+msgid "All projects"
+msgstr ""
+
+msgid "All security scans are enabled because %{linkStart}Auto DevOps%{linkEnd} is enabled on this project"
+msgstr ""
+
+msgid "All threads resolved"
+msgstr ""
+
+msgid "All users"
+msgstr ""
+
+msgid "All users must have a name."
+msgstr ""
+
+msgid "Allow \"%{group_name}\" to sign you in"
+msgstr ""
+
+msgid "Allow commits from members who can merge to the target branch."
+msgstr ""
+
+msgid "Allow group owners to manage LDAP-related settings"
+msgstr ""
+
+msgid "Allow only the selected protocols to be used for Git access."
+msgstr ""
+
+msgid "Allow owners to manage default branch protection per group"
+msgstr ""
+
+msgid "Allow owners to manually add users outside of LDAP"
+msgstr ""
+
+msgid "Allow projects within this group to use Git LFS"
+msgstr ""
+
+msgid "Allow public access to pipelines and job details, including output logs and artifacts"
+msgstr ""
+
+msgid "Allow rendering of PlantUML diagrams in Asciidoc documents."
+msgstr ""
+
+msgid "Allow repository mirroring to be configured by project maintainers"
+msgstr ""
+
+msgid "Allow requests to the local network from hooks and services."
+msgstr ""
+
+msgid "Allow requests to the local network from system hooks"
+msgstr ""
+
+msgid "Allow requests to the local network from web hooks and services"
+msgstr ""
+
+msgid "Allow this key to push to repository as well? (Default only allows pull access.)"
+msgstr ""
+
+msgid "Allow this secondary node to replicate content on Object Storage"
+msgstr ""
+
+msgid "Allow users to dismiss the broadcast message"
+msgstr ""
+
+msgid "Allow users to register any application to use GitLab as an OAuth provider"
+msgstr ""
+
+msgid "Allow users to request access (if visibility is public or internal)"
+msgstr ""
+
+msgid "Allowed Geo IP"
+msgstr ""
+
+msgid "Allowed email domain restriction only permitted for top-level groups"
+msgstr ""
+
+msgid "Allowed to fail"
+msgstr ""
+
+msgid "Allows you to add and manage Kubernetes clusters."
+msgstr ""
+
+msgid "Almost there"
+msgstr ""
+
+msgid "Also called \"Issuer\" or \"Relying party trust identifier\""
+msgstr ""
+
+msgid "Also called \"Relying party service URL\" or \"Reply URL\""
+msgstr ""
+
+msgid "Alternate support URL for help page and help dropdown"
+msgstr ""
+
+msgid "Alternatively, you can convert your account to a managed account by the %{group_name} group."
+msgstr ""
+
+msgid "Amazon EKS"
+msgstr ""
+
+msgid "Amazon EKS integration allows you to provision EKS clusters from GitLab."
+msgstr ""
+
+msgid "Amazon Web Services"
+msgstr ""
+
+msgid "Amazon Web Services Logo"
+msgstr ""
+
+msgid "Amazon authentication is not %{link_start}correctly configured%{link_end}. Ask your GitLab administrator if you want to use this service."
+msgstr ""
+
+msgid "Amount of time (in hours) that users are allowed to skip forced configuration of two-factor authentication"
+msgstr ""
+
+msgid "An alert has been triggered in %{project_path}."
+msgstr ""
+
+msgid "An application called %{link_to_client} is requesting access to your GitLab account."
+msgstr ""
+
+msgid "An email notification was recently sent from the admin panel. Please wait %{wait_time_in_words} before attempting to send another message."
+msgstr ""
+
+msgid "An empty GitLab User field will add the FogBugz user's full name (e.g. \"By John Smith\") in the description of all issues and comments. It will also associate and/or assign these issues and comments with the project creator."
+msgstr ""
+
+msgid "An error has occurred"
+msgstr ""
+
+msgid "An error occurred adding a draft to the thread."
+msgstr ""
+
+msgid "An error occurred adding a new draft."
+msgstr ""
+
+msgid "An error occurred creating the new branch."
+msgstr ""
+
+msgid "An error occurred fetching the approval rules."
+msgstr ""
+
+msgid "An error occurred fetching the approvers for the new rule."
+msgstr ""
+
+msgid "An error occurred fetching the dropdown data."
+msgstr ""
+
+msgid "An error occurred fetching the project authors."
+msgstr ""
+
+msgid "An error occurred previewing the blob"
+msgstr ""
+
+msgid "An error occurred when toggling the notification subscription"
+msgstr ""
+
+msgid "An error occurred when trying to resolve a comment. Please try again."
+msgstr ""
+
+msgid "An error occurred when trying to resolve a discussion. Please try again."
+msgstr ""
+
+msgid "An error occurred when updating the issue weight"
+msgstr ""
+
+msgid "An error occurred while acknowledging the notification. Refresh the page and try again."
+msgstr ""
+
+msgid "An error occurred while adding approvers"
+msgstr ""
+
+msgid "An error occurred while adding formatted title for epic"
+msgstr ""
+
+msgid "An error occurred while checking group path. Please refresh and try again."
+msgstr ""
+
+msgid "An error occurred while committing your changes."
+msgstr ""
+
+msgid "An error occurred while decoding the file."
+msgstr ""
+
+msgid "An error occurred while deleting the approvers group"
+msgstr ""
+
+msgid "An error occurred while deleting the comment"
+msgstr ""
+
+msgid "An error occurred while deleting the pipeline."
+msgstr ""
+
+msgid "An error occurred while detecting host keys"
+msgstr ""
+
+msgid "An error occurred while disabling Service Desk."
+msgstr ""
+
+msgid "An error occurred while dismissing the alert. Refresh the page and try again."
+msgstr ""
+
+msgid "An error occurred while dismissing the feature highlight. Refresh the page and try dismissing again."
+msgstr ""
+
+msgid "An error occurred while enabling Service Desk."
+msgstr ""
+
+msgid "An error occurred while fetching coverage reports."
+msgstr ""
+
+msgid "An error occurred while fetching environments."
+msgstr ""
+
+msgid "An error occurred while fetching exposed artifacts."
+msgstr ""
+
+msgid "An error occurred while fetching folder content."
+msgstr ""
+
+msgid "An error occurred while fetching issues."
+msgstr ""
+
+msgid "An error occurred while fetching label colors."
+msgstr ""
+
+msgid "An error occurred while fetching markdown preview"
+msgstr ""
+
+msgid "An error occurred while fetching pending comments"
+msgstr ""
+
+msgid "An error occurred while fetching projects autocomplete."
+msgstr ""
+
+msgid "An error occurred while fetching sidebar data"
+msgstr ""
+
+msgid "An error occurred while fetching terraform reports."
+msgstr ""
+
+msgid "An error occurred while fetching the Service Desk address."
+msgstr ""
+
+msgid "An error occurred while fetching the board lists. Please try again."
+msgstr ""
+
+msgid "An error occurred while fetching the builds."
+msgstr ""
+
+msgid "An error occurred while fetching the job log."
+msgstr ""
+
+msgid "An error occurred while fetching the job trace."
+msgstr ""
+
+msgid "An error occurred while fetching the job."
+msgstr ""
+
+msgid "An error occurred while fetching the jobs."
+msgstr ""
+
+msgid "An error occurred while fetching the latest pipeline."
+msgstr ""
+
+msgid "An error occurred while fetching the pipeline."
+msgstr ""
+
+msgid "An error occurred while fetching the releases. Please try again."
+msgstr ""
+
+msgid "An error occurred while fetching this tab."
+msgstr ""
+
+msgid "An error occurred while generating a username. Please try again."
+msgstr ""
+
+msgid "An error occurred while getting files for - %{branchId}"
+msgstr ""
+
+msgid "An error occurred while getting projects"
+msgstr ""
+
+msgid "An error occurred while importing project: %{details}"
+msgstr ""
+
+msgid "An error occurred while initializing path locks"
+msgstr ""
+
+msgid "An error occurred while loading all the files."
+msgstr ""
+
+msgid "An error occurred while loading chart data"
+msgstr ""
+
+msgid "An error occurred while loading commit signatures"
+msgstr ""
+
+msgid "An error occurred while loading designs. Please try again."
+msgstr ""
+
+msgid "An error occurred while loading diff"
+msgstr ""
+
+msgid "An error occurred while loading filenames"
+msgstr ""
+
+msgid "An error occurred while loading group members."
+msgstr ""
+
+msgid "An error occurred while loading issues"
+msgstr ""
+
+msgid "An error occurred while loading merge requests."
+msgstr ""
+
+msgid "An error occurred while loading milestones"
+msgstr ""
+
+msgid "An error occurred while loading project creation UI"
+msgstr ""
+
+msgid "An error occurred while loading the data. Please try again."
+msgstr ""
+
+msgid "An error occurred while loading the file"
+msgstr ""
+
+msgid "An error occurred while loading the file content."
+msgstr ""
+
+msgid "An error occurred while loading the file."
+msgstr ""
+
+msgid "An error occurred while loading the file. Please try again later."
+msgstr ""
+
+msgid "An error occurred while loading the merge request changes."
+msgstr ""
+
+msgid "An error occurred while loading the merge request version data."
+msgstr ""
+
+msgid "An error occurred while loading the merge request."
+msgstr ""
+
+msgid "An error occurred while loading the pipelines jobs."
+msgstr ""
+
+msgid "An error occurred while loading the subscription details."
+msgstr ""
+
+msgid "An error occurred while making the request."
+msgstr ""
+
+msgid "An error occurred while moving the issue."
+msgstr ""
+
+msgid "An error occurred while parsing recent searches"
+msgstr ""
+
+msgid "An error occurred while parsing the file."
+msgstr ""
+
+msgid "An error occurred while removing epics."
+msgstr ""
+
+msgid "An error occurred while removing issues."
+msgstr ""
+
+msgid "An error occurred while rendering preview broadcast message"
+msgstr ""
+
+msgid "An error occurred while rendering the editor"
+msgstr ""
+
+msgid "An error occurred while reordering issues."
+msgstr ""
+
+msgid "An error occurred while retrieving calendar activity"
+msgstr ""
+
+msgid "An error occurred while retrieving diff"
+msgstr ""
+
+msgid "An error occurred while saving LDAP override status. Please try again."
+msgstr ""
+
+msgid "An error occurred while saving assignees"
+msgstr ""
+
+msgid "An error occurred while saving the approval settings"
+msgstr ""
+
+msgid "An error occurred while saving the template. Please check if the template exists."
+msgstr ""
+
+msgid "An error occurred while searching for milestones"
+msgstr ""
+
+msgid "An error occurred while subscribing to notifications."
+msgstr ""
+
+msgid "An error occurred while triggering the job."
+msgstr ""
+
+msgid "An error occurred while trying to run a new pipeline for this Merge Request."
+msgstr ""
+
+msgid "An error occurred while unsubscribing to notifications."
+msgstr ""
+
+msgid "An error occurred while updating approvers"
+msgstr ""
+
+msgid "An error occurred while updating the comment"
+msgstr ""
+
+msgid "An error occurred while validating group path"
+msgstr ""
+
+msgid "An error occurred while validating username"
+msgstr ""
+
+msgid "An error occurred. Please try again."
+msgstr ""
+
+msgid "An error ocurred while loading your content. Please try again."
+msgstr ""
+
+msgid "An example project for managing Kubernetes clusters integrated with GitLab."
+msgstr ""
+
+msgid "An instance-level serverless domain already exists."
+msgstr ""
+
+msgid "An issue already exists"
+msgstr ""
+
+msgid "An issue can be a bug, a todo or a feature request that needs to be discussed in a project. Besides, issues are searchable and filterable."
+msgstr ""
+
+msgid "An unauthenticated user"
+msgstr ""
+
+msgid "An unexpected error occurred while checking the project environment."
+msgstr ""
+
+msgid "An unexpected error occurred while checking the project runners."
+msgstr ""
+
+msgid "An unexpected error occurred while communicating with the Web Terminal."
+msgstr ""
+
+msgid "An unexpected error occurred while starting the Web Terminal."
+msgstr ""
+
+msgid "An unexpected error occurred while stopping the Web Terminal."
+msgstr ""
+
+msgid "An unknown error occurred while loading this graph."
+msgstr ""
+
+msgid "Analytics"
+msgstr ""
+
+msgid "Analyze a review version of your web application."
+msgstr ""
+
+msgid "Analyze your dependencies for known vulnerabilities."
+msgstr ""
+
+msgid "Analyze your source code and git history for secrets."
+msgstr ""
+
+msgid "Analyze your source code for known vulnerabilities."
+msgstr ""
+
+msgid "Ancestors"
+msgstr ""
+
+msgid "Anonymous"
+msgstr ""
+
+msgid "Another action is currently in progress"
+msgstr ""
+
+msgid "Another issue tracker is already in use. Only one issue tracker service can be active at a time"
+msgstr ""
+
+msgid "Anti-spam verification"
+msgstr ""
+
+msgid "Any"
+msgstr ""
+
+msgid "Any Author"
+msgstr ""
+
+msgid "Any branch"
+msgstr ""
+
+msgid "Any eligible user"
+msgstr ""
+
+msgid "Any encrypted tokens"
+msgstr ""
+
+msgid "Any label"
+msgstr ""
+
+msgid "Any member with Developer or higher permissions to the project."
+msgstr ""
+
+msgid "Any milestone"
+msgstr ""
+
+msgid "Any namespace"
+msgstr ""
+
+msgid "Any user"
+msgstr ""
+
+msgid "App ID"
+msgstr ""
+
+msgid "Appearance"
+msgstr ""
+
+msgid "Appearance was successfully created."
+msgstr ""
+
+msgid "Appearance was successfully updated."
+msgstr ""
+
+msgid "Append the comment with %{shrug}"
+msgstr ""
+
+msgid "Append the comment with %{tableflip}"
+msgstr ""
+
+msgid "Application"
+msgstr ""
+
+msgid "Application ID"
+msgstr ""
+
+msgid "Application settings saved successfully"
+msgstr ""
+
+msgid "Application settings update failed"
+msgstr ""
+
+msgid "Application uninstalled but failed to destroy: %{error_message}"
+msgstr ""
+
+msgid "Application was successfully destroyed."
+msgstr ""
+
+msgid "Application was successfully updated."
+msgstr ""
+
+msgid "Application: %{name}"
+msgstr ""
+
+msgid "Applications"
+msgstr ""
+
+msgid "Applied"
+msgstr ""
+
+msgid "Apply"
+msgstr ""
+
+msgid "Apply a label"
+msgstr ""
+
+msgid "Apply a template"
+msgstr ""
+
+msgid "Apply changes"
+msgstr ""
+
+msgid "Apply suggestion"
+msgstr ""
+
+msgid "Apply suggestions"
+msgstr ""
+
+msgid "Apply template"
+msgstr ""
+
+msgid "Apply this approval rule to any branch or a specific protected branch."
+msgstr ""
+
+msgid "Applying"
+msgstr ""
+
+msgid "Applying a template will replace the existing issue description. Any changes you have made will be lost."
+msgstr ""
+
+msgid "Applying command"
+msgstr ""
+
+msgid "Applying command to %{commandDescription}"
+msgstr ""
+
+msgid "Applying multiple commands"
+msgstr ""
+
+msgid "Applying suggestion..."
+msgstr ""
+
+msgid "Applying suggestions..."
+msgstr ""
+
+msgid "Approval rules"
+msgstr ""
+
+msgid "Approval rules reset to project defaults"
+msgstr ""
+
+msgid "ApprovalRuleRemove|%d member"
+msgid_plural "ApprovalRuleRemove|%d members"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "ApprovalRuleRemove|Approvals from this member are not revoked."
+msgid_plural "ApprovalRuleRemove|Approvals from these members are not revoked."
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "ApprovalRuleRemove|You are about to remove the %{name} approver group which has %{nMembers}."
+msgstr ""
+
+msgid "ApprovalRuleSummary|%d member"
+msgid_plural "ApprovalRuleSummary|%d members"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "ApprovalRuleSummary|%{count} approval required from %{membersCount}"
+msgid_plural "ApprovalRuleSummary|%{count} approvals required from %{membersCount}"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "ApprovalRule|Approvers"
+msgstr ""
+
+msgid "ApprovalRule|Name"
+msgstr ""
+
+msgid "ApprovalRule|No. approvals required"
+msgstr ""
+
+msgid "ApprovalRule|Rule name"
+msgstr ""
+
+msgid "ApprovalRule|Target branch"
+msgstr ""
+
+msgid "ApprovalRule|e.g. QA, Security, etc."
+msgstr ""
+
+msgid "Approve"
+msgstr ""
+
+msgid "Approve a merge request"
+msgstr ""
+
+msgid "Approve the current merge request."
+msgstr ""
+
+msgid "Approved"
+msgstr ""
+
+msgid "Approved MRs"
+msgstr ""
+
+msgid "Approved by: "
+msgstr ""
+
+msgid "Approved the current merge request."
+msgstr ""
+
+msgid "Approved-By"
+msgstr ""
+
+msgid "Approver"
+msgstr ""
+
+msgid "Approvers"
+msgstr ""
+
+msgid "Apr"
+msgstr ""
+
+msgid "April"
+msgstr ""
+
+msgid "Archive"
+msgstr ""
+
+msgid "Archive jobs"
+msgstr ""
+
+msgid "Archive project"
+msgstr ""
+
+msgid "Archived"
+msgstr ""
+
+msgid "Archived project! Repository and other project resources are read only"
+msgstr ""
+
+msgid "Archived project! Repository and other project resources are read-only"
+msgstr ""
+
+msgid "Archived projects"
+msgstr ""
+
+msgid "Archiving the project will make it entirely read only. It is hidden from the dashboard and doesn't show up in searches. %{strong_start}The repository cannot be committed to, and no issues, comments, or other entities can be created.%{strong_end}"
+msgstr ""
+
+msgid "Are you setting up GitLab for a company?"
+msgstr ""
+
+msgid "Are you sure that you want to archive this project?"
+msgstr ""
+
+msgid "Are you sure that you want to unarchive this project?"
+msgstr ""
+
+msgid "Are you sure you want to cancel editing this comment?"
+msgstr ""
+
+msgid "Are you sure you want to close this blocked issue?"
+msgstr ""
+
+msgid "Are you sure you want to delete %{name}?"
+msgstr ""
+
+msgid "Are you sure you want to delete these artifacts?"
+msgstr ""
+
+msgid "Are you sure you want to delete this %{typeOfComment}?"
+msgstr ""
+
+msgid "Are you sure you want to delete this board?"
+msgstr ""
+
+msgid "Are you sure you want to delete this device? This action cannot be undone."
+msgstr ""
+
+msgid "Are you sure you want to delete this list?"
+msgstr ""
+
+msgid "Are you sure you want to delete this pipeline schedule?"
+msgstr ""
+
+msgid "Are you sure you want to delete this pipeline? Doing so will expire all pipeline caches and delete all related objects, such as builds, logs, artifacts, and triggers. This action cannot be undone."
+msgstr ""
+
+msgid "Are you sure you want to deploy this environment?"
+msgstr ""
+
+msgid "Are you sure you want to discard this comment?"
+msgstr ""
+
+msgid "Are you sure you want to erase this build?"
+msgstr ""
+
+msgid "Are you sure you want to lose unsaved changes?"
+msgstr ""
+
+msgid "Are you sure you want to lose your issue information?"
+msgstr ""
+
+msgid "Are you sure you want to merge immediately?"
+msgstr ""
+
+msgid "Are you sure you want to permanently delete this license?"
+msgstr ""
+
+msgid "Are you sure you want to re-deploy this environment?"
+msgstr ""
+
+msgid "Are you sure you want to regenerate the public key? You will have to update the public key on the remote server before mirroring will work again."
+msgstr ""
+
+msgid "Are you sure you want to remove %{group_name}?"
+msgstr ""
+
+msgid "Are you sure you want to remove the attachment?"
+msgstr ""
+
+msgid "Are you sure you want to remove the license?"
+msgstr ""
+
+msgid "Are you sure you want to remove this identity?"
+msgstr ""
+
+msgid "Are you sure you want to reset registration token?"
+msgstr ""
+
+msgid "Are you sure you want to reset the SCIM token? SCIM provisioning will stop working until the new token is updated."
+msgstr ""
+
+msgid "Are you sure you want to reset the health check token?"
+msgstr ""
+
+msgid "Are you sure you want to revoke this %{type}? This action cannot be undone."
+msgstr ""
+
+msgid "Are you sure you want to revoke this nickname?"
+msgstr ""
+
+msgid "Are you sure you want to stop this environment?"
+msgstr ""
+
+msgid "Are you sure you want to unlock %{path_lock_path}?"
+msgstr ""
+
+msgid "Are you sure you want to unsubscribe from the %{type}: %{link_to_noteable_text}?"
+msgstr ""
+
+msgid "Are you sure?"
+msgstr ""
+
+msgid "Are you sure? All commits that were signed with this GPG key will be unverified."
+msgstr ""
+
+msgid "Are you sure? Removing this GPG key does not affect already signed commits."
+msgstr ""
+
+msgid "Are you sure? The device will be signed out of GitLab."
+msgstr ""
+
+msgid "Are you sure? This will invalidate your registered applications and U2F devices."
+msgstr ""
+
+msgid "Arrange charts"
+msgstr ""
+
+msgid "Artifact"
+msgstr ""
+
+msgid "Artifact ID"
+msgstr ""
+
+msgid "Artifact could not be deleted."
+msgstr ""
+
+msgid "Artifact was successfully deleted."
+msgstr ""
+
+msgid "Artifacts"
+msgstr ""
+
+msgid "As U2F devices are only supported by a few browsers, we require that you set up a two-factor authentication app before a U2F device. That way you'll always be able to log in - even when you're using an unsupported browser."
+msgstr ""
+
+msgid "AsanaService|%{user} pushed to branch %{branch} of %{project_name} ( %{commit_url} ):"
+msgstr ""
+
+msgid "AsanaService|Asana - Teamwork without email"
+msgstr ""
+
+msgid "AsanaService|Comma-separated list of branches which will be automatically inspected. Leave blank to include all branches."
+msgstr ""
+
+msgid "AsanaService|User Personal Access Token. User must have access to task, all comments will be attributed to this user."
+msgstr ""
+
+msgid "Ascending"
+msgstr ""
+
+msgid "Ask your group maintainer to set up a group Runner."
+msgstr ""
+
+msgid "Assertion consumer service URL"
+msgstr ""
+
+msgid "Assets"
+msgstr ""
+
+msgid "Assets:"
+msgstr ""
+
+msgid "Assign"
+msgstr ""
+
+msgid "Assign Iteration"
+msgstr ""
+
+msgid "Assign custom color like #FF0000"
+msgstr ""
+
+msgid "Assign epic"
+msgstr ""
+
+msgid "Assign labels"
+msgstr ""
+
+msgid "Assign milestone"
+msgstr ""
+
+msgid "Assign some issues to this milestone."
+msgstr ""
+
+msgid "Assign to"
+msgstr ""
+
+msgid "Assign to commenting user"
+msgstr ""
+
+msgid "Assign yourself to these issues"
+msgstr ""
+
+msgid "Assign yourself to this issue"
+msgstr ""
+
+msgid "Assigned %{assignee_users_sentence}."
+msgstr ""
+
+msgid "Assigned Issues"
+msgstr ""
+
+msgid "Assigned Merge Requests"
+msgstr ""
+
+msgid "Assigned to %{assignee_name}"
+msgstr ""
+
+msgid "Assigned to me"
+msgstr ""
+
+msgid "Assignee"
+msgid_plural "%d Assignees"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "Assignee has no permissions"
+msgstr ""
+
+msgid "Assignee lists not available with your current license"
+msgstr ""
+
+msgid "Assignee lists show all issues assigned to the selected user."
+msgstr ""
+
+msgid "Assignee(s)"
+msgstr ""
+
+msgid "Assignees"
+msgstr ""
+
+msgid "Assigns %{assignee_users_sentence}."
+msgstr ""
+
+msgid "At least one approval from a code owner is required to change files matching the respective CODEOWNER rules."
+msgstr ""
+
+msgid "At least one logging option is required to be enabled"
+msgstr ""
+
+msgid "At least one of group_id or project_id must be specified"
+msgstr ""
+
+msgid "At risk"
+msgstr ""
+
+msgid "Attach a file"
+msgstr ""
+
+msgid "Attach a file by drag &amp; drop or %{upload_link}"
+msgstr ""
+
+msgid "Attaching a file"
+msgid_plural "Attaching %d files"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "Attaching the file failed."
+msgstr ""
+
+msgid "Audit Events"
+msgstr ""
+
+msgid "Audit Events is a way to keep track of important events that happened in GitLab."
+msgstr ""
+
+msgid "Audit Log"
+msgstr ""
+
+msgid "AuditEvents|(removed)"
+msgstr ""
+
+msgid "AuditEvents|Action"
+msgstr ""
+
+msgid "AuditEvents|At"
+msgstr ""
+
+msgid "AuditEvents|Target"
+msgstr ""
+
+msgid "AuditLogs|(removed)"
+msgstr ""
+
+msgid "AuditLogs|Action"
+msgstr ""
+
+msgid "AuditLogs|Author"
+msgstr ""
+
+msgid "AuditLogs|Date"
+msgstr ""
+
+msgid "AuditLogs|Failed to find %{type}. Please search for another %{type}."
+msgstr ""
+
+msgid "AuditLogs|Failed to find %{type}. Please try again."
+msgstr ""
+
+msgid "AuditLogs|Group Events"
+msgstr ""
+
+msgid "AuditLogs|IP Address"
+msgstr ""
+
+msgid "AuditLogs|Member Events"
+msgstr ""
+
+msgid "AuditLogs|No matching %{type} found."
+msgstr ""
+
+msgid "AuditLogs|Object"
+msgstr ""
+
+msgid "AuditLogs|Project Events"
+msgstr ""
+
+msgid "AuditLogs|Target"
+msgstr ""
+
+msgid "AuditLogs|User Events"
+msgstr ""
+
+msgid "Aug"
+msgstr ""
+
+msgid "August"
+msgstr ""
+
+msgid "Authenticate"
+msgstr ""
+
+msgid "Authenticate with GitHub"
+msgstr ""
+
+msgid "Authenticating"
+msgstr ""
+
+msgid "Authentication Failure"
+msgstr ""
+
+msgid "Authentication Log"
+msgstr ""
+
+msgid "Authentication failed: %{error_message}"
+msgstr ""
+
+msgid "Authentication log"
+msgstr ""
+
+msgid "Authentication method"
+msgstr ""
+
+msgid "Authentication method updated"
+msgstr ""
+
+msgid "Authentication via U2F device failed."
+msgstr ""
+
+msgid "Author"
+msgstr ""
+
+msgid "Authored %{timeago} by %{author}"
+msgstr ""
+
+msgid "Authorization code:"
+msgstr ""
+
+msgid "Authorization key"
+msgstr ""
+
+msgid "Authorization was granted by entering your username and password in the application."
+msgstr ""
+
+msgid "Authorize"
+msgstr ""
+
+msgid "Authorize %{link_to_client} to use your account?"
+msgstr ""
+
+msgid "Authorize external services to send alerts to GitLab"
+msgstr ""
+
+msgid "Authorized %{new_chat_name}"
+msgstr ""
+
+msgid "Authorized At"
+msgstr ""
+
+msgid "Authorized applications (%{size})"
+msgstr ""
+
+msgid "Authors: %{authors}"
+msgstr ""
+
+msgid "Auto DevOps"
+msgstr ""
+
+msgid "Auto DevOps enabled"
+msgstr ""
+
+msgid "Auto DevOps, runners and job artifacts"
+msgstr ""
+
+msgid "Auto stop successfully canceled."
+msgstr ""
+
+msgid "Auto-cancel redundant, pending pipelines"
+msgstr ""
+
+msgid "Auto-close referenced issues on default branch"
+msgstr ""
+
+msgid "AutoDevOps|Auto DevOps"
+msgstr ""
+
+msgid "AutoDevOps|Auto DevOps can automatically build, test, and deploy applications based on predefined continuous integration and delivery configuration. %{auto_devops_start}Learn more about Auto DevOps%{auto_devops_end} or use our %{quickstart_start}quick start guide%{quickstart_end} to get started right away."
+msgstr ""
+
+msgid "AutoDevOps|Auto DevOps documentation"
+msgstr ""
+
+msgid "AutoDevOps|Dismiss Auto DevOps box"
+msgstr ""
+
+msgid "AutoDevOps|Enable in settings"
+msgstr ""
+
+msgid "AutoDevOps|It will automatically build, test, and deploy your application based on a predefined CI/CD configuration."
+msgstr ""
+
+msgid "AutoDevOps|Learn more in the %{link_to_documentation}"
+msgstr ""
+
+msgid "AutoDevOps|The Auto DevOps pipeline has been enabled and will be used if no alternative CI configuration file is found. %{more_information_link}"
+msgstr ""
+
+msgid "Autocomplete"
+msgstr ""
+
+msgid "Autocomplete description"
+msgstr ""
+
+msgid "Autocomplete hint"
+msgstr ""
+
+msgid "Autocomplete usage hint"
+msgstr ""
+
+msgid "Automatic certificate management using %{lets_encrypt_link_start}Let's Encrypt%{lets_encrypt_link_end}"
+msgstr ""
+
+msgid "Automatic certificate management using Let's Encrypt"
+msgstr ""
+
+msgid "Automatically create merge requests for vulnerabilities that have fixes available."
+msgstr ""
+
+msgid "Automatically marked as default internal user"
+msgstr ""
+
+msgid "Automatically resolved"
+msgstr ""
+
+msgid "Automatically update this project's branches and tags from the upstream repository every hour."
+msgstr ""
+
+msgid "Autosave|Note"
+msgstr ""
+
+msgid "Available"
+msgstr ""
+
+msgid "Available for dependency and container scanning"
+msgstr ""
+
+msgid "Available group Runners: %{runners}"
+msgstr ""
+
+msgid "Available shared Runners:"
+msgstr ""
+
+msgid "Available specific runners"
+msgstr ""
+
+msgid "Avatar for %{assigneeName}"
+msgstr ""
+
+msgid "Avatar for %{name}"
+msgstr ""
+
+msgid "Avatar will be removed. Are you sure?"
+msgstr ""
+
+msgid "Average per day: %{average}"
+msgstr ""
+
+msgid "Back to page %{number}"
+msgstr ""
+
+msgid "Background Color"
+msgstr ""
+
+msgid "Background Jobs"
+msgstr ""
+
+msgid "Background color"
+msgstr ""
+
+msgid "Badges"
+msgstr ""
+
+msgid "Badges|A new badge was added."
+msgstr ""
+
+msgid "Badges|Add badge"
+msgstr ""
+
+msgid "Badges|Adding the badge failed, please check the entered URLs and try again."
+msgstr ""
+
+msgid "Badges|Badge image URL"
+msgstr ""
+
+msgid "Badges|Badge image preview"
+msgstr ""
+
+msgid "Badges|Delete badge"
+msgstr ""
+
+msgid "Badges|Delete badge?"
+msgstr ""
+
+msgid "Badges|Deleting the badge failed, please try again."
+msgstr ""
+
+msgid "Badges|Group Badge"
+msgstr ""
+
+msgid "Badges|Link"
+msgstr ""
+
+msgid "Badges|Name"
+msgstr ""
+
+msgid "Badges|No badge image"
+msgstr ""
+
+msgid "Badges|No image to preview"
+msgstr ""
+
+msgid "Badges|Please fill in a valid URL"
+msgstr ""
+
+msgid "Badges|Project Badge"
+msgstr ""
+
+msgid "Badges|Reload badge image"
+msgstr ""
+
+msgid "Badges|Save changes"
+msgstr ""
+
+msgid "Badges|Saving the badge failed, please check the entered URLs and try again."
+msgstr ""
+
+msgid "Badges|The %{docsLinkStart}variables%{docsLinkEnd} GitLab supports: %{placeholders}"
+msgstr ""
+
+msgid "Badges|The badge was deleted."
+msgstr ""
+
+msgid "Badges|The badge was saved."
+msgstr ""
+
+msgid "Badges|This group has no badges"
+msgstr ""
+
+msgid "Badges|This project has no badges"
+msgstr ""
+
+msgid "Badges|You are going to delete this badge. Deleted badges <strong>cannot</strong> be restored."
+msgstr ""
+
+msgid "Badges|Your badges"
+msgstr ""
+
+msgid "Badges|e.g. %{exampleUrl}"
+msgstr ""
+
+msgid "Balsamiq file could not be loaded."
+msgstr ""
+
+msgid "BambooService|A continuous integration and build server"
+msgstr ""
+
+msgid "BambooService|A user with API access, if applicable"
+msgstr ""
+
+msgid "BambooService|Atlassian Bamboo CI"
+msgstr ""
+
+msgid "BambooService|Bamboo build plan key like KEY"
+msgstr ""
+
+msgid "BambooService|Bamboo root URL like https://bamboo.example.com"
+msgstr ""
+
+msgid "BambooService|You must set up automatic revision labeling and a repository trigger in Bamboo."
+msgstr ""
+
+msgid "BatchComments|Delete all pending comments"
+msgstr ""
+
+msgid "BatchComments|Discard review?"
+msgstr ""
+
+msgid "BatchComments|You're about to discard your review which will delete all of your pending comments. The deleted comments %{strong_start}cannot%{strong_end} be restored."
+msgstr ""
+
+msgid "Be careful. Changing the project's namespace can have unintended side effects."
+msgstr ""
+
+msgid "Be careful. Renaming a project's repository can have unintended side effects."
+msgstr ""
+
+msgid "Begin with the selected commit"
+msgstr ""
+
+msgid "Below are examples of regex for existing tools:"
+msgstr ""
+
+msgid "Below are the fingerprints for the current instance SSH host keys."
+msgstr ""
+
+msgid "Below you will find all the groups that are public."
+msgstr ""
+
+msgid "Beta"
+msgstr ""
+
+msgid "Bi-weekly code coverage"
+msgstr ""
+
+msgid "Billing"
+msgstr ""
+
+msgid "BillingPlans|%{group_name} is currently using the %{plan_name} plan."
+msgstr ""
+
+msgid "BillingPlans|@%{user_name} you are currently using the %{plan_name} plan."
+msgstr ""
+
+msgid "BillingPlans|Congratulations, your new trial is activated"
+msgstr ""
+
+msgid "BillingPlans|If you would like to downgrade your plan please contact %{support_link_start}Customer Support%{support_link_end}."
+msgstr ""
+
+msgid "BillingPlans|Learn more about each plan by reading our %{faq_link}, or start a free 30-day trial of GitLab.com Gold."
+msgstr ""
+
+msgid "BillingPlans|Learn more about each plan by visiting our %{pricing_page_link}."
+msgstr ""
+
+msgid "BillingPlans|Manage plan"
+msgstr ""
+
+msgid "BillingPlans|Pricing page"
+msgstr ""
+
+msgid "BillingPlans|See all %{plan_name} features"
+msgstr ""
+
+msgid "BillingPlans|This group uses the plan associated with its parent group."
+msgstr ""
+
+msgid "BillingPlans|To manage the plan for this group, visit the billing section of %{parent_billing_page_link}."
+msgstr ""
+
+msgid "BillingPlans|Your GitLab.com %{plan} trial will <strong>expire after %{expiration_date}</strong>. You can retain access to the %{plan} features by upgrading below."
+msgstr ""
+
+msgid "BillingPlans|Your GitLab.com trial expired on %{expiration_date}. You can restore access to the features at any time by upgrading below."
+msgstr ""
+
+msgid "BillingPlans|billed annually at %{price_per_year}"
+msgstr ""
+
+msgid "BillingPlans|frequently asked questions"
+msgstr ""
+
+msgid "BillingPlans|monthly"
+msgstr ""
+
+msgid "BillingPlans|per user"
+msgstr ""
+
+msgid "BillingPlan|Upgrade"
+msgstr ""
+
+msgid "Bitbucket Server Import"
+msgstr ""
+
+msgid "Bitbucket Server import"
+msgstr ""
+
+msgid "Bitbucket import"
+msgstr ""
+
+msgid "Blame"
+msgstr ""
+
+msgid "Blocked"
+msgstr ""
+
+msgid "Blocked issue"
+msgstr ""
+
+msgid "Blocks"
+msgstr ""
+
+msgid "Blog"
+msgstr ""
+
+msgid "Board name"
+msgstr ""
+
+msgid "Board scope"
+msgstr ""
+
+msgid "Board scope affects which issues are displayed for anyone who visits this board"
+msgstr ""
+
+msgid "BoardBlankState|Add default lists"
+msgstr ""
+
+msgid "BoardBlankState|Add the following default lists to your Issue Board with one click:"
+msgstr ""
+
+msgid "BoardBlankState|Nevermind, I'll use my own"
+msgstr ""
+
+msgid "BoardBlankState|Starting out with the default set of lists will get you right on the way to making the most of your board."
+msgstr ""
+
+msgid "Boards"
+msgstr ""
+
+msgid "Boards and Board Lists"
+msgstr ""
+
+msgid "Boards|Collapse"
+msgstr ""
+
+msgid "Boards|Edit board"
+msgstr ""
+
+msgid "Boards|Expand"
+msgstr ""
+
+msgid "Boards|View scope"
+msgstr ""
+
+msgid "Branch"
+msgstr ""
+
+msgid "Branch %{branchName} was not found in this project's repository."
+msgstr ""
+
+msgid "Branch <strong>%{branch_name}</strong> was created. To set up auto deploy, choose a GitLab CI Yaml template and commit your changes. %{link_to_autodeploy_doc}"
+msgstr ""
+
+msgid "Branch has changed"
+msgstr ""
+
+msgid "Branch is already taken"
+msgstr ""
+
+msgid "Branch name"
+msgstr ""
+
+msgid "Branch not loaded - %{branchId}"
+msgstr ""
+
+msgid "Branch prefix"
+msgstr ""
+
+msgid "BranchSwitcherPlaceholder|Search branches"
+msgstr ""
+
+msgid "BranchSwitcherTitle|Switch branch"
+msgstr ""
+
+msgid "Branches"
+msgstr ""
+
+msgid "Branches|Active"
+msgstr ""
+
+msgid "Branches|Active branches"
+msgstr ""
+
+msgid "Branches|All"
+msgstr ""
+
+msgid "Branches|Cant find HEAD commit for this branch"
+msgstr ""
+
+msgid "Branches|Compare"
+msgstr ""
+
+msgid "Branches|Delete all branches that are merged into '%{default_branch}'"
+msgstr ""
+
+msgid "Branches|Delete branch"
+msgstr ""
+
+msgid "Branches|Delete merged branches"
+msgstr ""
+
+msgid "Branches|Delete protected branch"
+msgstr ""
+
+msgid "Branches|Delete protected branch '%{branch_name}'?"
+msgstr ""
+
+msgid "Branches|Deleting the '%{branch_name}' branch cannot be undone. Are you sure?"
+msgstr ""
+
+msgid "Branches|Deleting the merged branches cannot be undone. Are you sure?"
+msgstr ""
+
+msgid "Branches|Filter by branch name"
+msgstr ""
+
+msgid "Branches|Merged into %{default_branch}"
+msgstr ""
+
+msgid "Branches|New branch"
+msgstr ""
+
+msgid "Branches|No branches to show"
+msgstr ""
+
+msgid "Branches|Once you confirm and press %{delete_protected_branch}, it cannot be undone or recovered."
+msgstr ""
+
+msgid "Branches|Only a project maintainer or owner can delete a protected branch"
+msgstr ""
+
+msgid "Branches|Overview"
+msgstr ""
+
+msgid "Branches|Protected branches can be managed in %{project_settings_link}."
+msgstr ""
+
+msgid "Branches|Show active branches"
+msgstr ""
+
+msgid "Branches|Show all branches"
+msgstr ""
+
+msgid "Branches|Show more active branches"
+msgstr ""
+
+msgid "Branches|Show more stale branches"
+msgstr ""
+
+msgid "Branches|Show overview of the branches"
+msgstr ""
+
+msgid "Branches|Show stale branches"
+msgstr ""
+
+msgid "Branches|Sort by"
+msgstr ""
+
+msgid "Branches|Stale"
+msgstr ""
+
+msgid "Branches|Stale branches"
+msgstr ""
+
+msgid "Branches|The branch could not be updated automatically because it has diverged from its upstream counterpart."
+msgstr ""
+
+msgid "Branches|The default branch cannot be deleted"
+msgstr ""
+
+msgid "Branches|This branch hasn’t been merged into %{default_branch}."
+msgstr ""
+
+msgid "Branches|To avoid data loss, consider merging this branch before deleting it."
+msgstr ""
+
+msgid "Branches|To confirm, type %{branch_name_confirmation}:"
+msgstr ""
+
+msgid "Branches|To discard the local changes and overwrite the branch with the upstream version, delete it here and choose 'Update Now' above."
+msgstr ""
+
+msgid "Branches|You’re about to permanently delete the protected branch %{branch_name}."
+msgstr ""
+
+msgid "Branches|diverged from upstream"
+msgstr ""
+
+msgid "Branches|merged"
+msgstr ""
+
+msgid "Branches|project settings"
+msgstr ""
+
+msgid "Branches|protected"
+msgstr ""
+
+msgid "Broadcast Message was successfully created."
+msgstr ""
+
+msgid "Broadcast Message was successfully updated."
+msgstr ""
+
+msgid "Broadcast Messages"
+msgstr ""
+
+msgid "Browse Directory"
+msgstr ""
+
+msgid "Browse File"
+msgstr ""
+
+msgid "Browse Files"
+msgstr ""
+
+msgid "Browse artifacts"
+msgstr ""
+
+msgid "Browse files"
+msgstr ""
+
+msgid "BuildArtifacts|An error occurred while fetching the artifacts"
+msgstr ""
+
+msgid "BuildArtifacts|Loading artifacts"
+msgstr ""
+
+msgid "Built-in"
+msgstr ""
+
+msgid "Bulk request concurrency"
+msgstr ""
+
+msgid "Burndown chart"
+msgstr ""
+
+msgid "BurndownChartLabel|Open issue weight"
+msgstr ""
+
+msgid "BurndownChartLabel|Open issues"
+msgstr ""
+
+msgid "Burnup chart"
+msgstr ""
+
+msgid "Business"
+msgstr ""
+
+msgid "Business metrics (Custom)"
+msgstr ""
+
+msgid "Buy License"
+msgstr ""
+
+msgid "Buy more Pipeline minutes"
+msgstr ""
+
+msgid "By %{user_name}"
+msgstr ""
+
+msgid "By URL"
+msgstr ""
+
+msgid "By default GitLab sends emails in HTML and plain text formats so mail clients can choose what format to use. Disable this option if you only want to send emails in plain text format."
+msgstr ""
+
+msgid "By default, all projects and groups will use the global notifications setting."
+msgstr ""
+
+msgid "ByAuthor|by"
+msgstr ""
+
+msgid "CHANGELOG"
+msgstr ""
+
+msgid "CI / CD"
+msgstr ""
+
+msgid "CI / CD Analytics"
+msgstr ""
+
+msgid "CI / CD Settings"
+msgstr ""
+
+msgid "CI Lint"
+msgstr ""
+
+msgid "CI settings"
+msgstr ""
+
+msgid "CI variables"
+msgstr ""
+
+msgid "CI will run using the credentials assigned above."
+msgstr ""
+
+msgid "CI/CD"
+msgstr ""
+
+msgid "CI/CD configuration"
+msgstr ""
+
+msgid "CI/CD for external repo"
+msgstr ""
+
+msgid "CI/CD settings"
+msgstr ""
+
+msgid "CICD|Add a %{kubernetes_cluster_link_start}Kubernetes cluster integration%{link_end} with a domain or create an AUTO_DEVOPS_PLATFORM_TARGET CI variable."
+msgstr ""
+
+msgid "CICD|Auto DevOps"
+msgstr ""
+
+msgid "CICD|Automatic deployment to staging, manual deployment to production"
+msgstr ""
+
+msgid "CICD|Continuous deployment to production"
+msgstr ""
+
+msgid "CICD|Continuous deployment to production using timed incremental rollout"
+msgstr ""
+
+msgid "CICD|Default to Auto DevOps pipeline"
+msgstr ""
+
+msgid "CICD|Default to Auto DevOps pipeline for all projects"
+msgstr ""
+
+msgid "CICD|Deployment strategy"
+msgstr ""
+
+msgid "CICD|Jobs"
+msgstr ""
+
+msgid "CICD|The Auto DevOps pipeline will run if no alternative CI configuration file is found."
+msgstr ""
+
+msgid "CICD|You must add a %{base_domain_link_start}base domain%{link_end} to your %{kubernetes_cluster_link_start}Kubernetes cluster%{link_end} in order for your deployment strategy to work."
+msgstr ""
+
+msgid "CICD|group enabled"
+msgstr ""
+
+msgid "CICD|instance enabled"
+msgstr ""
+
+msgid "CLOSED"
+msgstr ""
+
+msgid "CLOSED (MOVED)"
+msgstr ""
+
+msgid "CONTRIBUTING"
+msgstr ""
+
+msgid "CPU"
+msgstr ""
+
+msgid "Callback URL"
+msgstr ""
+
+msgid "Can be manually deployed to"
+msgstr ""
+
+msgid "Can override approvers and approvals required per merge request"
+msgstr ""
+
+msgid "Can't apply as this line has changed or the suggestion already matches its content."
+msgstr ""
+
+msgid "Can't create snippet: %{err}"
+msgstr ""
+
+msgid "Can't edit as source branch was deleted"
+msgstr ""
+
+msgid "Can't find HEAD commit for this branch"
+msgstr ""
+
+msgid "Can't find variable: ZiteReader"
+msgstr ""
+
+msgid "Can't load mermaid module: %{err}"
+msgstr ""
+
+msgid "Can't remove group members without group managed account"
+msgstr ""
+
+msgid "Can't scan the code?"
+msgstr ""
+
+msgid "Can't update snippet: %{err}"
+msgstr ""
+
+msgid "Canary"
+msgstr ""
+
+msgid "Canary Deployments is a popular CI strategy, where a small portion of the fleet is updated to the new version of your application."
+msgstr ""
+
+msgid "Cancel"
+msgstr ""
+
+msgid "Cancel running"
+msgstr ""
+
+msgid "Cancel this job"
+msgstr ""
+
+msgid "Canceled deployment to"
+msgstr ""
+
+msgid "Cancelling Preview"
+msgstr ""
+
+msgid "Cannot be merged automatically"
+msgstr ""
+
+msgid "Cannot create the abuse report. The user has been deleted."
+msgstr ""
+
+msgid "Cannot create the abuse report. This user has been blocked."
+msgstr ""
+
+msgid "Cannot have multiple Jira imports running at the same time"
+msgstr ""
+
+msgid "Cannot import because issues are not available in this project."
+msgstr ""
+
+msgid "Cannot make epic confidential if it contains not-confidential issues"
+msgstr ""
+
+msgid "Cannot make epic confidential if it contains not-confidential sub-epics"
+msgstr ""
+
+msgid "Cannot merge"
+msgstr ""
+
+msgid "Cannot modify managed Kubernetes cluster"
+msgstr ""
+
+msgid "Cannot modify provider during creation"
+msgstr ""
+
+msgid "Cannot promote issue because it does not belong to a group."
+msgstr ""
+
+msgid "Cannot promote issue due to insufficient permissions."
+msgstr ""
+
+msgid "Cannot refer to a group %{timebox_type} by an internal id!"
+msgstr ""
+
+msgid "Cannot set confidential epic for not-confidential issue"
+msgstr ""
+
+msgid "Cannot show preview. For previews on sketch files, they must have the file format introduced by Sketch version 43 and above."
+msgstr ""
+
+msgid "Cannot skip two factor authentication setup"
+msgstr ""
+
+msgid "Capacity threshold"
+msgstr ""
+
+msgid "Certain user content will be moved to a system-wide \"Ghost User\" in order to maintain content for posterity. For further information, please refer to the %{link_start}user account deletion documentation.%{link_end}"
+msgstr ""
+
+msgid "Certificate"
+msgstr ""
+
+msgid "Certificate (PEM)"
+msgstr ""
+
+msgid "Certificate Issuer"
+msgstr ""
+
+msgid "Certificate Subject"
+msgstr ""
+
+msgid "Change assignee"
+msgstr ""
+
+msgid "Change assignee(s)"
+msgstr ""
+
+msgid "Change assignee(s)."
+msgstr ""
+
+msgid "Change branches"
+msgstr ""
+
+msgid "Change label"
+msgstr ""
+
+msgid "Change milestone"
+msgstr ""
+
+msgid "Change path"
+msgstr ""
+
+msgid "Change permissions"
+msgstr ""
+
+msgid "Change status"
+msgstr ""
+
+msgid "Change subscription"
+msgstr ""
+
+msgid "Change template"
+msgstr ""
+
+msgid "Change this value to influence how frequently the GitLab UI polls for updates."
+msgstr ""
+
+msgid "Change title"
+msgstr ""
+
+msgid "Change your password"
+msgstr ""
+
+msgid "Change your password or recover your current one"
+msgstr ""
+
+msgid "ChangeTypeActionLabel|Pick into branch"
+msgstr ""
+
+msgid "ChangeTypeActionLabel|Revert in branch"
+msgstr ""
+
+msgid "ChangeTypeAction|Cherry-pick"
+msgstr ""
+
+msgid "ChangeTypeAction|Revert"
+msgstr ""
+
+msgid "ChangeTypeAction|This will create a new commit in order to revert the existing changes."
+msgstr ""
+
+msgid "Changed assignee(s)."
+msgstr ""
+
+msgid "Changed the title to \"%{title_param}\"."
+msgstr ""
+
+msgid "Changes"
+msgstr ""
+
+msgid "Changes are shown as if the <b>source</b> revision was being merged into the <b>target</b> revision."
+msgstr ""
+
+msgid "Changes are still tracked. Useful for cluster/index migrations."
+msgstr ""
+
+msgid "Changes suppressed. Click to show."
+msgstr ""
+
+msgid "Changes the title to \"%{title_param}\"."
+msgstr ""
+
+msgid "Changes won't take place until the index is %{link_start}recreated%{link_end}."
+msgstr ""
+
+msgid "Changing a Release tag is only supported via Releases API. %{linkStart}More information%{linkEnd}"
+msgstr ""
+
+msgid "Changing group path can have unintended side effects."
+msgstr ""
+
+msgid "Channel handle (e.g. town-square)"
+msgstr ""
+
+msgid "Charts"
+msgstr ""
+
+msgid "Charts can't be displayed as the request for data has timed out. %{documentationLink}"
+msgstr ""
+
+msgid "Chat"
+msgstr ""
+
+msgid "ChatMessage|%{project_link}: Pipeline %{pipeline_link} of %{ref_type} %{ref_link} by %{user_combined_name} %{humanized_status} in %{duration}"
+msgstr ""
+
+msgid "ChatMessage|Branch"
+msgstr ""
+
+msgid "ChatMessage|Commit"
+msgstr ""
+
+msgid "ChatMessage|Failed job"
+msgstr ""
+
+msgid "ChatMessage|Failed stage"
+msgstr ""
+
+msgid "ChatMessage|Invalid CI config YAML file"
+msgstr ""
+
+msgid "ChatMessage|Pipeline #%{pipeline_id} %{humanized_status} in %{duration}"
+msgstr ""
+
+msgid "ChatMessage|Pipeline %{pipeline_link} of %{ref_type} %{ref_link} by %{user_combined_name} %{humanized_status}"
+msgstr ""
+
+msgid "ChatMessage|Tag"
+msgstr ""
+
+msgid "ChatMessage|and [%{count} more](%{pipeline_failed_jobs_url})"
+msgstr ""
+
+msgid "ChatMessage|has failed"
+msgstr ""
+
+msgid "ChatMessage|has passed"
+msgstr ""
+
+msgid "ChatMessage|has passed with warnings"
+msgstr ""
+
+msgid "ChatMessage|in %{duration}"
+msgstr ""
+
+msgid "ChatMessage|in %{project_link}"
+msgstr ""
+
+msgid "Check again"
+msgstr ""
+
+msgid "Check feature availability on namespace plan"
+msgstr ""
+
+msgid "Check the %{docs_link_start}documentation%{docs_link_end}."
+msgstr ""
+
+msgid "Check your .gitlab-ci.yml"
+msgstr ""
+
+msgid "Check your Docker images for known vulnerabilities."
+msgstr ""
+
+msgid "Checking %{text} availability…"
+msgstr ""
+
+msgid "Checking approval status"
+msgstr ""
+
+msgid "Checking branch availability..."
+msgstr ""
+
+msgid "Checking group path availability..."
+msgstr ""
+
+msgid "Checking username availability..."
+msgstr ""
+
+msgid "Checkout"
+msgstr ""
+
+msgid "Checkout|$%{selectedPlanPrice} per user per year"
+msgstr ""
+
+msgid "Checkout|%{cardType} ending in %{lastFourDigits}"
+msgstr ""
+
+msgid "Checkout|%{name}'s GitLab subscription"
+msgstr ""
+
+msgid "Checkout|%{selectedPlanText} plan"
+msgstr ""
+
+msgid "Checkout|%{startDate} - %{endDate}"
+msgstr ""
+
+msgid "Checkout|(x%{numberOfUsers})"
+msgstr ""
+
+msgid "Checkout|Billing address"
+msgstr ""
+
+msgid "Checkout|Checkout"
+msgstr ""
+
+msgid "Checkout|City"
+msgstr ""
+
+msgid "Checkout|Confirm purchase"
+msgstr ""
+
+msgid "Checkout|Confirming..."
+msgstr ""
+
+msgid "Checkout|Continue to billing"
+msgstr ""
+
+msgid "Checkout|Continue to payment"
+msgstr ""
+
+msgid "Checkout|Country"
+msgstr ""
+
+msgid "Checkout|Create a new group"
+msgstr ""
+
+msgid "Checkout|Credit card form failed to load. Please try again."
+msgstr ""
+
+msgid "Checkout|Credit card form failed to load: %{message}"
+msgstr ""
+
+msgid "Checkout|Edit"
+msgstr ""
+
+msgid "Checkout|Exp %{expirationMonth}/%{expirationYear}"
+msgstr ""
+
+msgid "Checkout|Failed to confirm your order! Please try again."
+msgstr ""
+
+msgid "Checkout|Failed to confirm your order: %{message}. Please try again."
+msgstr ""
+
+msgid "Checkout|Failed to load countries. Please try again."
+msgstr ""
+
+msgid "Checkout|Failed to load states. Please try again."
+msgstr ""
+
+msgid "Checkout|Failed to register credit card. Please try again."
+msgstr ""
+
+msgid "Checkout|GitLab group"
+msgstr ""
+
+msgid "Checkout|GitLab plan"
+msgstr ""
+
+msgid "Checkout|Group"
+msgstr ""
+
+msgid "Checkout|Name of company or organization using GitLab"
+msgstr ""
+
+msgid "Checkout|Need more users? Purchase GitLab for your %{company}."
+msgstr ""
+
+msgid "Checkout|Number of users"
+msgstr ""
+
+msgid "Checkout|Payment method"
+msgstr ""
+
+msgid "Checkout|Please select a country"
+msgstr ""
+
+msgid "Checkout|Please select a state"
+msgstr ""
+
+msgid "Checkout|Select"
+msgstr ""
+
+msgid "Checkout|State"
+msgstr ""
+
+msgid "Checkout|Street address"
+msgstr ""
+
+msgid "Checkout|Submitting the credit card form failed with code %{errorCode}: %{errorMessage}"
+msgstr ""
+
+msgid "Checkout|Subscription details"
+msgstr ""
+
+msgid "Checkout|Subtotal"
+msgstr ""
+
+msgid "Checkout|Tax"
+msgstr ""
+
+msgid "Checkout|Total"
+msgstr ""
+
+msgid "Checkout|Users"
+msgstr ""
+
+msgid "Checkout|You'll create your new group after checkout"
+msgstr ""
+
+msgid "Checkout|Your organization"
+msgstr ""
+
+msgid "Checkout|Your subscription will be applied to this group"
+msgstr ""
+
+msgid "Checkout|Zip code"
+msgstr ""
+
+msgid "Checkout|company or team"
+msgstr ""
+
+msgid "Cherry-pick this commit"
+msgstr ""
+
+msgid "Cherry-pick this merge request"
+msgstr ""
+
+msgid "Child"
+msgstr ""
+
+msgid "Child epic does not exist."
+msgstr ""
+
+msgid "Child epic doesn't exist."
+msgstr ""
+
+msgid "Choose <strong>Create archive</strong> and wait for archiving to complete."
+msgstr ""
+
+msgid "Choose <strong>Next</strong> at the bottom of the page."
+msgstr ""
+
+msgid "Choose a branch/tag (e.g. %{master}) or enter a commit (e.g. %{sha}) to see what's changed or to create a merge request."
+msgstr ""
+
+msgid "Choose a file"
+msgstr ""
+
+msgid "Choose a group"
+msgstr ""
+
+msgid "Choose a role permission"
+msgstr ""
+
+msgid "Choose a template"
+msgstr ""
+
+msgid "Choose a template..."
+msgstr ""
+
+msgid "Choose a type..."
+msgstr ""
+
+msgid "Choose any color."
+msgstr ""
+
+msgid "Choose between <code>clone</code> or <code>fetch</code> to get the recent application code"
+msgstr ""
+
+msgid "Choose file…"
+msgstr ""
+
+msgid "Choose the top-level group for your repository imports."
+msgstr ""
+
+msgid "Choose visibility level, enable/disable project features (issues, repository, wiki, snippets) and set permissions."
+msgstr ""
+
+msgid "Choose what content you want to see on a group’s overview page"
+msgstr ""
+
+msgid "Choose which repositories you want to connect and run CI/CD pipelines."
+msgstr ""
+
+msgid "Choose your framework"
+msgstr ""
+
+msgid "CiStatusLabel|canceled"
+msgstr ""
+
+msgid "CiStatusLabel|created"
+msgstr ""
+
+msgid "CiStatusLabel|delayed"
+msgstr ""
+
+msgid "CiStatusLabel|failed"
+msgstr ""
+
+msgid "CiStatusLabel|manual action"
+msgstr ""
+
+msgid "CiStatusLabel|passed"
+msgstr ""
+
+msgid "CiStatusLabel|passed with warnings"
+msgstr ""
+
+msgid "CiStatusLabel|pending"
+msgstr ""
+
+msgid "CiStatusLabel|preparing"
+msgstr ""
+
+msgid "CiStatusLabel|skipped"
+msgstr ""
+
+msgid "CiStatusLabel|waiting for delayed job"
+msgstr ""
+
+msgid "CiStatusLabel|waiting for manual action"
+msgstr ""
+
+msgid "CiStatusLabel|waiting for resource"
+msgstr ""
+
+msgid "CiStatusText|blocked"
+msgstr ""
+
+msgid "CiStatusText|canceled"
+msgstr ""
+
+msgid "CiStatusText|created"
+msgstr ""
+
+msgid "CiStatusText|delayed"
+msgstr ""
+
+msgid "CiStatusText|failed"
+msgstr ""
+
+msgid "CiStatusText|manual"
+msgstr ""
+
+msgid "CiStatusText|passed"
+msgstr ""
+
+msgid "CiStatusText|pending"
+msgstr ""
+
+msgid "CiStatusText|preparing"
+msgstr ""
+
+msgid "CiStatusText|skipped"
+msgstr ""
+
+msgid "CiStatusText|waiting"
+msgstr ""
+
+msgid "CiStatus|running"
+msgstr ""
+
+msgid "CiVariables|Cannot use Masked Variable with current value"
+msgstr ""
+
+msgid "CiVariables|Environments"
+msgstr ""
+
+msgid "CiVariables|Input variable key"
+msgstr ""
+
+msgid "CiVariables|Input variable value"
+msgstr ""
+
+msgid "CiVariables|Key"
+msgstr ""
+
+msgid "CiVariables|Masked"
+msgstr ""
+
+msgid "CiVariables|Protected"
+msgstr ""
+
+msgid "CiVariables|Remove variable row"
+msgstr ""
+
+msgid "CiVariables|Scope"
+msgstr ""
+
+msgid "CiVariables|Specify variable values to be used in this run. The values specified in %{linkStart}CI/CD settings%{linkEnd} will be used as default"
+msgstr ""
+
+msgid "CiVariables|State"
+msgstr ""
+
+msgid "CiVariables|Type"
+msgstr ""
+
+msgid "CiVariables|Value"
+msgstr ""
+
+msgid "CiVariables|Variables"
+msgstr ""
+
+msgid "CiVariable|* (All environments)"
+msgstr ""
+
+msgid "CiVariable|All environments"
+msgstr ""
+
+msgid "CiVariable|Create wildcard"
+msgstr ""
+
+msgid "CiVariable|Error occurred while saving variables"
+msgstr ""
+
+msgid "CiVariable|Masked"
+msgstr ""
+
+msgid "CiVariable|New environment"
+msgstr ""
+
+msgid "CiVariable|Protected"
+msgstr ""
+
+msgid "CiVariable|Search environments"
+msgstr ""
+
+msgid "CiVariable|Toggle masked"
+msgstr ""
+
+msgid "CiVariable|Toggle protected"
+msgstr ""
+
+msgid "CiVariable|Validation failed"
+msgstr ""
+
+msgid "Class"
+msgstr ""
+
+msgid "Classification Label (optional)"
+msgstr ""
+
+msgid "ClassificationLabelUnavailable|is unavailable: %{reason}"
+msgstr ""
+
+msgid "Clear"
+msgstr ""
+
+msgid "Clear chart filters"
+msgstr ""
+
+msgid "Clear due date"
+msgstr ""
+
+msgid "Clear input"
+msgstr ""
+
+msgid "Clear recent searches"
+msgstr ""
+
+msgid "Clear search"
+msgstr ""
+
+msgid "Clear search input"
+msgstr ""
+
+msgid "Clear start date"
+msgstr ""
+
+msgid "Clear templates search input"
+msgstr ""
+
+msgid "Clear weight"
+msgstr ""
+
+msgid "Cleared weight."
+msgstr ""
+
+msgid "Clears weight."
+msgstr ""
+
+msgid "Click any <strong>project name</strong> in the project list below to navigate to the project milestone."
+msgstr ""
+
+msgid "Click the <strong>Download</strong> button and wait for downloading to complete."
+msgstr ""
+
+msgid "Click the <strong>Promote</strong> button in the top right corner to promote it to a group milestone."
+msgstr ""
+
+msgid "Click the <strong>Select none</strong> button on the right, since we only need \"Google Code Project Hosting\"."
+msgstr ""
+
+msgid "Click the button below to begin the install process by navigating to the Kubernetes page"
+msgstr ""
+
+msgid "Click to expand it."
+msgstr ""
+
+msgid "Click to expand text"
+msgstr ""
+
+msgid "Client authentication certificate"
+msgstr ""
+
+msgid "Client authentication key"
+msgstr ""
+
+msgid "Client authentication key password"
+msgstr ""
+
+msgid "Clients"
+msgstr ""
+
+msgid "Clone"
+msgstr ""
+
+msgid "Clone repository"
+msgstr ""
+
+msgid "Clone with %{http_label}"
+msgstr ""
+
+msgid "Clone with %{protocol}"
+msgstr ""
+
+msgid "Clone with KRB5"
+msgstr ""
+
+msgid "Clone with SSH"
+msgstr ""
+
+msgid "Close"
+msgstr ""
+
+msgid "Close %{display_issuable_type}"
+msgstr ""
+
+msgid "Close %{tabname}"
+msgstr ""
+
+msgid "Close epic"
+msgstr ""
+
+msgid "Close milestone"
+msgstr ""
+
+msgid "Close sidebar"
+msgstr ""
+
+msgid "Close this %{quick_action_target}"
+msgstr ""
+
+msgid "Closed"
+msgstr ""
+
+msgid "Closed issues"
+msgstr ""
+
+msgid "Closed this %{quick_action_target}."
+msgstr ""
+
+msgid "Closed: %{closedIssuesCount}"
+msgstr ""
+
+msgid "Closes this %{quick_action_target}."
+msgstr ""
+
+msgid "Cluster"
+msgstr ""
+
+msgid "Cluster Health"
+msgstr ""
+
+msgid "Cluster cache cleared."
+msgstr ""
+
+msgid "Cluster does not exist"
+msgstr ""
+
+msgid "Cluster is required for Stages::ClusterEndpointInserter"
+msgstr ""
+
+msgid "Cluster level"
+msgstr ""
+
+msgid "Cluster type must be specificed for Stages::ClusterEndpointInserter"
+msgstr ""
+
+msgid "ClusterIntegration| %{custom_domain_start}More information%{custom_domain_end}."
+msgstr ""
+
+msgid "ClusterIntegration| This will permanently delete the following resources: <ul> <li>All installed applications and related resources</li> <li>The <code>gitlab-managed-apps</code> namespace</li> <li>Any project namespaces</li> <li><code>clusterroles</code></li> <li><code>clusterrolebindings</code></li> </ul>"
+msgstr ""
+
+msgid "ClusterIntegration| can be used instead of a custom domain."
+msgstr ""
+
+msgid "ClusterIntegration| is the default environment scope for this cluster. This means that all jobs, regardless of their environment, will use this cluster. %{environment_scope_start}More information%{environment_scope_end}"
+msgstr ""
+
+msgid "ClusterIntegration|%{appList} was successfully installed on your Kubernetes cluster"
+msgstr ""
+
+msgid "ClusterIntegration|%{external_ip}.nip.io"
+msgstr ""
+
+msgid "ClusterIntegration|%{title} uninstalled successfully."
+msgstr ""
+
+msgid "ClusterIntegration|%{title} updated successfully."
+msgstr ""
+
+msgid "ClusterIntegration|A cluster management project can be used to run deployment jobs with Kubernetes <code>cluster-admin</code> privileges."
+msgstr ""
+
+msgid "ClusterIntegration|A service token scoped to %{code}kube-system%{end_code} with %{code}cluster-admin%{end_code} privileges."
+msgstr ""
+
+msgid "ClusterIntegration|API URL"
+msgstr ""
+
+msgid "ClusterIntegration|API URL should be a valid http/https url."
+msgstr ""
+
+msgid "ClusterIntegration|Add Kubernetes cluster"
+msgstr ""
+
+msgid "ClusterIntegration|Add a Kubernetes cluster integration"
+msgstr ""
+
+msgid "ClusterIntegration|Adding a Kubernetes cluster to your group will automatically share the cluster across all your projects. Use review apps, deploy your applications, and easily run your pipelines for all projects using the same cluster."
+msgstr ""
+
+msgid "ClusterIntegration|Adding a Kubernetes cluster will automatically share the cluster across all projects. Use review apps, deploy your applications, and easily run your pipelines for all projects using the same cluster."
+msgstr ""
+
+msgid "ClusterIntegration|Adding an integration to your group will share the cluster across all your projects."
+msgstr ""
+
+msgid "ClusterIntegration|Adding an integration will share the cluster across all projects."
+msgstr ""
+
+msgid "ClusterIntegration|Advanced options on this Kubernetes cluster’s integration"
+msgstr ""
+
+msgid "ClusterIntegration|All data not committed to GitLab will be deleted and cannot be restored."
+msgstr ""
+
+msgid "ClusterIntegration|All data will be deleted and cannot be restored."
+msgstr ""
+
+msgid "ClusterIntegration|Allow GitLab to manage namespace and service accounts for this cluster."
+msgstr ""
+
+msgid "ClusterIntegration|Allow GitLab to manage namespace and service accounts for this cluster. %{startLink}More information%{endLink}"
+msgstr ""
+
+msgid "ClusterIntegration|Alternatively"
+msgstr ""
+
+msgid "ClusterIntegration|Amazon EKS"
+msgstr ""
+
+msgid "ClusterIntegration|An error occurred when trying to contact the Google Cloud API. Please try again later."
+msgstr ""
+
+msgid "ClusterIntegration|An error occurred while trying to fetch project zones: %{error}"
+msgstr ""
+
+msgid "ClusterIntegration|An error occurred while trying to fetch your projects: %{error}"
+msgstr ""
+
+msgid "ClusterIntegration|An error occurred while trying to fetch zone machine types: %{error}"
+msgstr ""
+
+msgid "ClusterIntegration|Any running pipelines will be canceled."
+msgstr ""
+
+msgid "ClusterIntegration|Apply for credit"
+msgstr ""
+
+msgid "ClusterIntegration|Authenticate with AWS"
+msgstr ""
+
+msgid "ClusterIntegration|Authenticate with Amazon Web Services"
+msgstr ""
+
+msgid "ClusterIntegration|Base domain"
+msgstr ""
+
+msgid "ClusterIntegration|Blocking mode"
+msgstr ""
+
+msgid "ClusterIntegration|CA Certificate"
+msgstr ""
+
+msgid "ClusterIntegration|Cert-Manager"
+msgstr ""
+
+msgid "ClusterIntegration|Cert-Manager is a native Kubernetes certificate management controller that helps with issuing certificates. Installing Cert-Manager on your cluster will issue a certificate by %{linkStart}Let's Encrypt%{linkEnd} and ensure that certificates are valid and up-to-date."
+msgstr ""
+
+msgid "ClusterIntegration|Certificate Authority bundle (PEM format)"
+msgstr ""
+
+msgid "ClusterIntegration|Choose the %{startLink}security group %{externalLinkIcon} %{endLink} to apply to the EKS-managed Elastic Network Interfaces that are created in your worker node subnets."
+msgstr ""
+
+msgid "ClusterIntegration|Choose the %{startLink}subnets %{externalLinkIcon} %{endLink} in your VPC where your worker nodes will run."
+msgstr ""
+
+msgid "ClusterIntegration|Choose the worker node %{startLink}instance type %{externalLinkIcon} %{endLink}."
+msgstr ""
+
+msgid "ClusterIntegration|Choose which applications to install on your Kubernetes cluster. Helm Tiller is required to install any of the following applications."
+msgstr ""
+
+msgid "ClusterIntegration|Choose which of your environments will use this cluster."
+msgstr ""
+
+msgid "ClusterIntegration|Clear cluster cache"
+msgstr ""
+
+msgid "ClusterIntegration|Clear the local cache of namespace and service accounts. This is necessary if your integration has become out of sync. The cache is repopulated during the next CI job that requires namespace and service accounts."
+msgstr ""
+
+msgid "ClusterIntegration|Cluster being created"
+msgstr ""
+
+msgid "ClusterIntegration|Cluster management project (alpha)"
+msgstr ""
+
+msgid "ClusterIntegration|Cluster name is required."
+msgstr ""
+
+msgid "ClusterIntegration|Cluster_applications artifact too big. Maximum allowable size: %{human_size}"
+msgstr ""
+
+msgid "ClusterIntegration|Clusters are utilized by selecting the nearest ancestor with a matching environment scope. For example, project clusters will override group clusters."
+msgstr ""
+
+msgid "ClusterIntegration|Copy API URL"
+msgstr ""
+
+msgid "ClusterIntegration|Copy CA Certificate"
+msgstr ""
+
+msgid "ClusterIntegration|Copy Ingress Endpoint"
+msgstr ""
+
+msgid "ClusterIntegration|Copy Jupyter Hostname"
+msgstr ""
+
+msgid "ClusterIntegration|Copy Knative Endpoint"
+msgstr ""
+
+msgid "ClusterIntegration|Copy Kubernetes cluster name"
+msgstr ""
+
+msgid "ClusterIntegration|Could not load IAM roles"
+msgstr ""
+
+msgid "ClusterIntegration|Could not load Key Pairs"
+msgstr ""
+
+msgid "ClusterIntegration|Could not load VPCs for the selected region"
+msgstr ""
+
+msgid "ClusterIntegration|Could not load instance types"
+msgstr ""
+
+msgid "ClusterIntegration|Could not load networks"
+msgstr ""
+
+msgid "ClusterIntegration|Could not load regions from your AWS account"
+msgstr ""
+
+msgid "ClusterIntegration|Could not load security groups for the selected VPC"
+msgstr ""
+
+msgid "ClusterIntegration|Could not load subnets for the selected VPC"
+msgstr ""
+
+msgid "ClusterIntegration|Could not load subnetworks"
+msgstr ""
+
+msgid "ClusterIntegration|Create Kubernetes cluster"
+msgstr ""
+
+msgid "ClusterIntegration|Create a provision role on %{startAwsLink}Amazon Web Services %{externalLinkIcon}%{endLink} using the account and external ID above. %{startMoreInfoLink}More information%{endLink}"
+msgstr ""
+
+msgid "ClusterIntegration|Create cluster on"
+msgstr ""
+
+msgid "ClusterIntegration|Create new cluster"
+msgstr ""
+
+msgid "ClusterIntegration|Create new cluster on EKS"
+msgstr ""
+
+msgid "ClusterIntegration|Create new cluster on GKE"
+msgstr ""
+
+msgid "ClusterIntegration|Creating Kubernetes cluster"
+msgstr ""
+
+msgid "ClusterIntegration|Crossplane"
+msgstr ""
+
+msgid "ClusterIntegration|Crossplane enables declarative provisioning of managed services from your cloud of choice using %{codeStart}kubectl%{codeEnd} or %{linkStart}GitLab Integration%{linkEnd}. Crossplane runs inside your Kubernetes cluster and supports secure connectivity and secrets management between app containers and the cloud services they depend on."
+msgstr ""
+
+msgid "ClusterIntegration|Deletes all GitLab resources attached to this cluster during removal"
+msgstr ""
+
+msgid "ClusterIntegration|Did you know?"
+msgstr ""
+
+msgid "ClusterIntegration|Elastic Kubernetes Service"
+msgstr ""
+
+msgid "ClusterIntegration|Elastic Stack"
+msgstr ""
+
+msgid "ClusterIntegration|Enable Cloud Run for Anthos"
+msgstr ""
+
+msgid "ClusterIntegration|Enable or disable GitLab's connection to your Kubernetes cluster."
+msgstr ""
+
+msgid "ClusterIntegration|Enable this setting if using role-based access control (RBAC)."
+msgstr ""
+
+msgid "ClusterIntegration|Enabled stack"
+msgstr ""
+
+msgid "ClusterIntegration|Enter new Service Token"
+msgstr ""
+
+msgid "ClusterIntegration|Enter the details for your Amazon EKS Kubernetes cluster"
+msgstr ""
+
+msgid "ClusterIntegration|Enter the details for your Kubernetes cluster"
+msgstr ""
+
+msgid "ClusterIntegration|Environment scope"
+msgstr ""
+
+msgid "ClusterIntegration|Every new Google Cloud Platform (GCP) account receives $300 in credit upon %{sign_up_link}. In partnership with Google, GitLab is able to offer an additional $200 for both new and existing GCP accounts to get started with GitLab's Google Kubernetes Engine Integration."
+msgstr ""
+
+msgid "ClusterIntegration|Failed to configure EKS provider: %{message}"
+msgstr ""
+
+msgid "ClusterIntegration|Failed to configure Google Kubernetes Engine Cluster: %{message}"
+msgstr ""
+
+msgid "ClusterIntegration|Failed to fetch CloudFormation stack: %{message}"
+msgstr ""
+
+msgid "ClusterIntegration|Failed to request to Google Cloud Platform: %{message}"
+msgstr ""
+
+msgid "ClusterIntegration|Failed to run Kubeclient: %{message}"
+msgstr ""
+
+msgid "ClusterIntegration|Fetching machine types"
+msgstr ""
+
+msgid "ClusterIntegration|Fetching projects"
+msgstr ""
+
+msgid "ClusterIntegration|Fetching zones"
+msgstr ""
+
+msgid "ClusterIntegration|Fluentd"
+msgstr ""
+
+msgid "ClusterIntegration|Fluentd is an open source data collector, which lets you unify the data collection and consumption for a better use and understanding of data. It requires at least one of the following logs to be successfully installed."
+msgstr ""
+
+msgid "ClusterIntegration|GitLab Integration"
+msgstr ""
+
+msgid "ClusterIntegration|GitLab Runner"
+msgstr ""
+
+msgid "ClusterIntegration|GitLab Runner connects to the repository and executes CI/CD jobs, pushing results back and deploying applications to production."
+msgstr ""
+
+msgid "ClusterIntegration|GitLab-managed cluster"
+msgstr ""
+
+msgid "ClusterIntegration|Global default"
+msgstr ""
+
+msgid "ClusterIntegration|Google Cloud Platform project"
+msgstr ""
+
+msgid "ClusterIntegration|Google GKE"
+msgstr ""
+
+msgid "ClusterIntegration|Google Kubernetes Engine"
+msgstr ""
+
+msgid "ClusterIntegration|Google Kubernetes Engine project"
+msgstr ""
+
+msgid "ClusterIntegration|Group cluster"
+msgstr ""
+
+msgid "ClusterIntegration|Helm Tiller"
+msgstr ""
+
+msgid "ClusterIntegration|Helm release failed to install"
+msgstr ""
+
+msgid "ClusterIntegration|Helm streamlines installing and managing Kubernetes applications. Tiller runs inside of your Kubernetes Cluster, and manages releases of your charts."
+msgstr ""
+
+msgid "ClusterIntegration|If you are setting up multiple clusters and are using Auto DevOps, %{help_link_start}read this first%{help_link_end}."
+msgstr ""
+
+msgid "ClusterIntegration|If you do not wish to delete all associated GitLab resources, you can simply remove the integration."
+msgstr ""
+
+msgid "ClusterIntegration|In order to view the health of your cluster, you must first install Prometheus in the Applications tab."
+msgstr ""
+
+msgid "ClusterIntegration|Ingress"
+msgstr ""
+
+msgid "ClusterIntegration|Ingress Endpoint"
+msgstr ""
+
+msgid "ClusterIntegration|Ingress gives you a way to route requests to services based on the request host or path, centralizing a number of services into a single entrypoint."
+msgstr ""
+
+msgid "ClusterIntegration|Installing Ingress may incur additional costs. Learn more about %{linkStart}pricing%{linkEnd}."
+msgstr ""
+
+msgid "ClusterIntegration|Instance cluster"
+msgstr ""
+
+msgid "ClusterIntegration|Instance type"
+msgstr ""
+
+msgid "ClusterIntegration|Integrate Kubernetes cluster automation"
+msgstr ""
+
+msgid "ClusterIntegration|Issuer Email"
+msgstr ""
+
+msgid "ClusterIntegration|Issuers represent a certificate authority. You must provide an email address for your Issuer."
+msgstr ""
+
+msgid "ClusterIntegration|Jupyter Hostname"
+msgstr ""
+
+msgid "ClusterIntegration|JupyterHub"
+msgstr ""
+
+msgid "ClusterIntegration|JupyterHub, a multi-user Hub, spawns, manages, and proxies multiple instances of the single-user Jupyter notebook server. JupyterHub can be used to serve notebooks to a class of students, a corporate data science group, or a scientific research group."
+msgstr ""
+
+msgid "ClusterIntegration|Key pair name"
+msgstr ""
+
+msgid "ClusterIntegration|Knative"
+msgstr ""
+
+msgid "ClusterIntegration|Knative Domain Name:"
+msgstr ""
+
+msgid "ClusterIntegration|Knative Endpoint:"
+msgstr ""
+
+msgid "ClusterIntegration|Knative domain name was updated successfully."
+msgstr ""
+
+msgid "ClusterIntegration|Knative extends Kubernetes to provide a set of middleware components that are essential to build modern, source-centric, and container-based applications that can run anywhere: on premises, in the cloud, or even in a third-party data center."
+msgstr ""
+
+msgid "ClusterIntegration|Kubernetes cluster"
+msgstr ""
+
+msgid "ClusterIntegration|Kubernetes cluster is being created..."
+msgstr ""
+
+msgid "ClusterIntegration|Kubernetes cluster name"
+msgstr ""
+
+msgid "ClusterIntegration|Kubernetes cluster was successfully created."
+msgstr ""
+
+msgid "ClusterIntegration|Kubernetes clusters allow you to use review apps, deploy your applications, run your pipelines, and much more in an easy way."
+msgstr ""
+
+msgid "ClusterIntegration|Kubernetes clusters can be used to deploy applications and to provide Review Apps for this project"
+msgstr ""
+
+msgid "ClusterIntegration|Kubernetes version"
+msgstr ""
+
+msgid "ClusterIntegration|Kubernetes version not found"
+msgstr ""
+
+msgid "ClusterIntegration|Learn more about %{help_link_start_machine_type}machine types%{help_link_end} and %{help_link_start_pricing}pricing%{help_link_end}."
+msgstr ""
+
+msgid "ClusterIntegration|Learn more about %{help_link_start}zones%{help_link_end}."
+msgstr ""
+
+msgid "ClusterIntegration|Learn more about %{startLink}Regions %{externalLinkIcon}%{endLink}."
+msgstr ""
+
+msgid "ClusterIntegration|Learn more about Kubernetes"
+msgstr ""
+
+msgid "ClusterIntegration|Learn more about group Kubernetes clusters"
+msgstr ""
+
+msgid "ClusterIntegration|Learn more about instance Kubernetes clusters"
+msgstr ""
+
+msgid "ClusterIntegration|Loading IAM Roles"
+msgstr ""
+
+msgid "ClusterIntegration|Loading Key Pairs"
+msgstr ""
+
+msgid "ClusterIntegration|Loading Regions"
+msgstr ""
+
+msgid "ClusterIntegration|Loading VPCs"
+msgstr ""
+
+msgid "ClusterIntegration|Loading instance types"
+msgstr ""
+
+msgid "ClusterIntegration|Loading networks"
+msgstr ""
+
+msgid "ClusterIntegration|Loading security groups"
+msgstr ""
+
+msgid "ClusterIntegration|Loading subnets"
+msgstr ""
+
+msgid "ClusterIntegration|Loading subnetworks"
+msgstr ""
+
+msgid "ClusterIntegration|Logging mode"
+msgstr ""
+
+msgid "ClusterIntegration|Machine type"
+msgstr ""
+
+msgid "ClusterIntegration|Make sure your account %{link_to_requirements} to create Kubernetes clusters"
+msgstr ""
+
+msgid "ClusterIntegration|Manage your Kubernetes cluster by visiting %{provider_link}"
+msgstr ""
+
+msgid "ClusterIntegration|No IAM Roles found"
+msgstr ""
+
+msgid "ClusterIntegration|No Key Pairs found"
+msgstr ""
+
+msgid "ClusterIntegration|No VPCs found"
+msgstr ""
+
+msgid "ClusterIntegration|No deployment cluster found for this job"
+msgstr ""
+
+msgid "ClusterIntegration|No deployment found for this job"
+msgstr ""
+
+msgid "ClusterIntegration|No instance type found"
+msgstr ""
+
+msgid "ClusterIntegration|No machine types matched your search"
+msgstr ""
+
+msgid "ClusterIntegration|No networks found"
+msgstr ""
+
+msgid "ClusterIntegration|No projects found"
+msgstr ""
+
+msgid "ClusterIntegration|No projects matched your search"
+msgstr ""
+
+msgid "ClusterIntegration|No region found"
+msgstr ""
+
+msgid "ClusterIntegration|No security group found"
+msgstr ""
+
+msgid "ClusterIntegration|No subnet found"
+msgstr ""
+
+msgid "ClusterIntegration|No subnetworks found"
+msgstr ""
+
+msgid "ClusterIntegration|No zones matched your search"
+msgstr ""
+
+msgid "ClusterIntegration|Number of nodes"
+msgstr ""
+
+msgid "ClusterIntegration|Number of nodes must be a numerical value."
+msgstr ""
+
+msgid "ClusterIntegration|Please enter access information for your Kubernetes cluster. If you need help, you can read our %{link_to_help_page} on Kubernetes"
+msgstr ""
+
+msgid "ClusterIntegration|Please make sure that your Google account meets the following requirements:"
+msgstr ""
+
+msgid "ClusterIntegration|Point a wildcard DNS to this generated endpoint in order to access your application after it has been deployed."
+msgstr ""
+
+msgid "ClusterIntegration|Project cluster"
+msgstr ""
+
+msgid "ClusterIntegration|Project namespace (optional, unique)"
+msgstr ""
+
+msgid "ClusterIntegration|Project namespace prefix (optional, unique)"
+msgstr ""
+
+msgid "ClusterIntegration|Prometheus"
+msgstr ""
+
+msgid "ClusterIntegration|Prometheus is an open-source monitoring system with %{linkStart}GitLab Integration%{linkEnd} to monitor deployed applications."
+msgstr ""
+
+msgid "ClusterIntegration|Provider details"
+msgstr ""
+
+msgid "ClusterIntegration|Provision Role ARN"
+msgstr ""
+
+msgid "ClusterIntegration|RBAC-enabled cluster"
+msgstr ""
+
+msgid "ClusterIntegration|Read our %{link_start}help page%{link_end} on Kubernetes cluster integration."
+msgstr ""
+
+msgid "ClusterIntegration|Real-time web application monitoring, logging and access control. %{linkStart}More information%{linkEnd}"
+msgstr ""
+
+msgid "ClusterIntegration|Region"
+msgstr ""
+
+msgid "ClusterIntegration|Remove Kubernetes cluster integration"
+msgstr ""
+
+msgid "ClusterIntegration|Remove integration"
+msgstr ""
+
+msgid "ClusterIntegration|Remove integration and resources"
+msgstr ""
+
+msgid "ClusterIntegration|Remove integration and resources?"
+msgstr ""
+
+msgid "ClusterIntegration|Remove integration?"
+msgstr ""
+
+msgid "ClusterIntegration|Remove this Kubernetes cluster's configuration from this project. This will not delete your actual Kubernetes cluster."
+msgstr ""
+
+msgid "ClusterIntegration|Removes cluster from project but keeps associated resources"
+msgstr ""
+
+msgid "ClusterIntegration|Replace this with your own hostname if you want. If you do so, point hostname to Ingress IP Address from above."
+msgstr ""
+
+msgid "ClusterIntegration|Request to begin installing failed"
+msgstr ""
+
+msgid "ClusterIntegration|Request to begin uninstalling failed"
+msgstr ""
+
+msgid "ClusterIntegration|SIEM Hostname"
+msgstr ""
+
+msgid "ClusterIntegration|SIEM Port"
+msgstr ""
+
+msgid "ClusterIntegration|SIEM Protocol"
+msgstr ""
+
+msgid "ClusterIntegration|Save changes"
+msgstr ""
+
+msgid "ClusterIntegration|Search IAM Roles"
+msgstr ""
+
+msgid "ClusterIntegration|Search Key Pairs"
+msgstr ""
+
+msgid "ClusterIntegration|Search VPCs"
+msgstr ""
+
+msgid "ClusterIntegration|Search domains"
+msgstr ""
+
+msgid "ClusterIntegration|Search instance types"
+msgstr ""
+
+msgid "ClusterIntegration|Search machine types"
+msgstr ""
+
+msgid "ClusterIntegration|Search networks"
+msgstr ""
+
+msgid "ClusterIntegration|Search projects"
+msgstr ""
+
+msgid "ClusterIntegration|Search regions"
+msgstr ""
+
+msgid "ClusterIntegration|Search security groups"
+msgstr ""
+
+msgid "ClusterIntegration|Search subnets"
+msgstr ""
+
+msgid "ClusterIntegration|Search subnetworks"
+msgstr ""
+
+msgid "ClusterIntegration|Search zones"
+msgstr ""
+
+msgid "ClusterIntegration|Security group"
+msgstr ""
+
+msgid "ClusterIntegration|See and edit the details for your Kubernetes cluster"
+msgstr ""
+
+msgid "ClusterIntegration|Select a VPC to choose a security group"
+msgstr ""
+
+msgid "ClusterIntegration|Select a VPC to choose a subnet"
+msgstr ""
+
+msgid "ClusterIntegration|Select a VPC to use for your EKS Cluster resources. To use a new VPC, first create one on %{startLink}Amazon Web Services %{externalLinkIcon} %{endLink}."
+msgstr ""
+
+msgid "ClusterIntegration|Select a network to choose a subnetwork"
+msgstr ""
+
+msgid "ClusterIntegration|Select a region to choose a Key Pair"
+msgstr ""
+
+msgid "ClusterIntegration|Select a region to choose a VPC"
+msgstr ""
+
+msgid "ClusterIntegration|Select a stack to install Crossplane."
+msgstr ""
+
+msgid "ClusterIntegration|Select a zone to choose a network"
+msgstr ""
+
+msgid "ClusterIntegration|Select existing domain or use new"
+msgstr ""
+
+msgid "ClusterIntegration|Select machine type"
+msgstr ""
+
+msgid "ClusterIntegration|Select project"
+msgstr ""
+
+msgid "ClusterIntegration|Select project and zone to choose machine type"
+msgstr ""
+
+msgid "ClusterIntegration|Select project to choose zone"
+msgstr ""
+
+msgid "ClusterIntegration|Select the key pair name that will be used to create EC2 nodes. To use a new key pair name, first create one on %{startLink}Amazon Web Services %{externalLinkIcon} %{endLink}."
+msgstr ""
+
+msgid "ClusterIntegration|Select zone"
+msgstr ""
+
+msgid "ClusterIntegration|Select zone to choose machine type"
+msgstr ""
+
+msgid "ClusterIntegration|Send Container Network Policies Logs"
+msgstr ""
+
+msgid "ClusterIntegration|Send Web Application Firewall Logs"
+msgstr ""
+
+msgid "ClusterIntegration|Service Token"
+msgstr ""
+
+msgid "ClusterIntegration|Service role"
+msgstr ""
+
+msgid "ClusterIntegration|Service token is required."
+msgstr ""
+
+msgid "ClusterIntegration|Set a prefix for your namespaces. If not set, defaults to your project path. If modified, existing environments will use their current namespaces until the cluster cache is cleared."
+msgstr ""
+
+msgid "ClusterIntegration|Set the global mode for the WAF in this cluster. This can be overridden at the environmental level."
+msgstr ""
+
+msgid "ClusterIntegration|Something went wrong on our end."
+msgstr ""
+
+msgid "ClusterIntegration|Something went wrong while creating your Kubernetes cluster"
+msgstr ""
+
+msgid "ClusterIntegration|Something went wrong while installing %{title}"
+msgstr ""
+
+msgid "ClusterIntegration|Something went wrong while trying to save your settings. Please try again."
+msgstr ""
+
+msgid "ClusterIntegration|Something went wrong while uninstalling %{title}"
+msgstr ""
+
+msgid "ClusterIntegration|Something went wrong while updating Knative domain name."
+msgstr ""
+
+msgid "ClusterIntegration|Specifying a domain will allow you to use Auto Review Apps and Auto Deploy stages for %{auto_devops_start}Auto DevOps%{auto_devops_end}. The domain should have a wildcard DNS configured matching the domain."
+msgstr ""
+
+msgid "ClusterIntegration|Subnets"
+msgstr ""
+
+msgid "ClusterIntegration|The Amazon Resource Name (ARN) associated with your role. If you do not have a provision role, first create one on %{startAwsLink}Amazon Web Services %{externalLinkIcon}%{endLink} using the above account and external IDs. %{startMoreInfoLink}More information%{endLink}"
+msgstr ""
+
+msgid "ClusterIntegration|The Kubernetes certificate used to authenticate to the cluster."
+msgstr ""
+
+msgid "ClusterIntegration|The URL used to access the Kubernetes API."
+msgstr ""
+
+msgid "ClusterIntegration|The associated IP and all deployed services will be deleted and cannot be restored. Uninstalling Knative will also remove Istio from your cluster. This will not effect any other applications."
+msgstr ""
+
+msgid "ClusterIntegration|The associated Tiller pod, the %{gitlabManagedAppsNamespace} namespace, and all of its resources will be deleted and cannot be restored."
+msgstr ""
+
+msgid "ClusterIntegration|The associated load balancer and IP will be deleted and cannot be restored."
+msgstr ""
+
+msgid "ClusterIntegration|The associated private key will be deleted and cannot be restored."
+msgstr ""
+
+msgid "ClusterIntegration|The elastic stack collects logs from all pods in your cluster"
+msgstr ""
+
+msgid "ClusterIntegration|The endpoint is in the process of being assigned. Please check your Kubernetes cluster or Quotas on Google Kubernetes Engine if it takes a long time."
+msgstr ""
+
+msgid "ClusterIntegration|The namespace associated with your project. This will be used for deploy boards, logs, and Web terminals."
+msgstr ""
+
+msgid "ClusterIntegration|There was a problem authenticating with your cluster. Please ensure your CA Certificate and Token are valid."
+msgstr ""
+
+msgid "ClusterIntegration|This account must have permissions to create a Kubernetes cluster in the %{link_to_container_project} specified below"
+msgstr ""
+
+msgid "ClusterIntegration|This option will allow you to install applications on RBAC clusters."
+msgstr ""
+
+msgid "ClusterIntegration|To access your application after deployment, point a wildcard DNS to the Knative Endpoint."
+msgstr ""
+
+msgid "ClusterIntegration|To remove your integration and resources, type %{clusterName} to confirm:"
+msgstr ""
+
+msgid "ClusterIntegration|To remove your integration, type %{clusterName} to confirm:"
+msgstr ""
+
+msgid "ClusterIntegration|Toggle Kubernetes cluster"
+msgstr ""
+
+msgid "ClusterIntegration|Uninstall %{appTitle}"
+msgstr ""
+
+msgid "ClusterIntegration|Update %{appTitle}"
+msgstr ""
+
+msgid "ClusterIntegration|Update failed. Please check the logs and try again."
+msgstr ""
+
+msgid "ClusterIntegration|Use %{query}"
+msgstr ""
+
+msgid "ClusterIntegration|Uses the Cloud Run, Istio, and HTTP Load Balancing addons for this cluster."
+msgstr ""
+
+msgid "ClusterIntegration|VPC"
+msgstr ""
+
+msgid "ClusterIntegration|Validating project billing status"
+msgstr ""
+
+msgid "ClusterIntegration|We could not verify that one of your projects on GCP has billing enabled. Please try again."
+msgstr ""
+
+msgid "ClusterIntegration|With a Kubernetes cluster associated to this project, you can use review apps, deploy your applications, run your pipelines, and much more in an easy way."
+msgstr ""
+
+msgid "ClusterIntegration|You are about to remove your cluster integration and all GitLab-created resources associated with this cluster."
+msgstr ""
+
+msgid "ClusterIntegration|You are about to remove your cluster integration."
+msgstr ""
+
+msgid "ClusterIntegration|You are about to uninstall %{appTitle} from your cluster."
+msgstr ""
+
+msgid "ClusterIntegration|You are about to update %{appTitle} on your cluster."
+msgstr ""
+
+msgid "ClusterIntegration|You must first install Helm Tiller before installing the applications below"
+msgstr ""
+
+msgid "ClusterIntegration|You must grant access to your organization’s AWS resources in order to create a new EKS cluster. To grant access, create a provision role using the account and external ID below and provide us the ARN."
+msgstr ""
+
+msgid "ClusterIntegration|You must have an RBAC-enabled cluster to install Knative."
+msgstr ""
+
+msgid "ClusterIntegration|You must specify a domain before you can install Knative."
+msgstr ""
+
+msgid "ClusterIntegration|You should select at least two subnets"
+msgstr ""
+
+msgid "ClusterIntegration|Your Elasticsearch cluster will be re-created during this upgrade. Your logs will be re-indexed, and you will lose historical logs from hosts terminated in the last 30 days."
+msgstr ""
+
+msgid "ClusterIntegration|Your account must have %{link_to_kubernetes_engine}"
+msgstr ""
+
+msgid "ClusterIntegration|Your cluster API is unreachable. Please ensure your API URL is correct."
+msgstr ""
+
+msgid "ClusterIntegration|Your service role is distinct from the provision role used when authenticating. It will allow Amazon EKS and the Kubernetes control plane to manage AWS resources on your behalf. To use a new role, first create one on %{startLink}Amazon Web Services %{externalLinkIcon} %{endLink}."
+msgstr ""
+
+msgid "ClusterIntegration|Zone"
+msgstr ""
+
+msgid "ClusterIntegration|access to Google Kubernetes Engine"
+msgstr ""
+
+msgid "ClusterIntegration|documentation"
+msgstr ""
+
+msgid "ClusterIntegration|installed via %{linkStart}Cloud Run%{linkEnd}"
+msgstr ""
+
+msgid "ClusterIntegration|meets the requirements"
+msgstr ""
+
+msgid "ClusterIntegration|sign up"
+msgstr ""
+
+msgid "ClusterIntergation|Select a VPC"
+msgstr ""
+
+msgid "ClusterIntergation|Select a network"
+msgstr ""
+
+msgid "ClusterIntergation|Select a region"
+msgstr ""
+
+msgid "ClusterIntergation|Select a security group"
+msgstr ""
+
+msgid "ClusterIntergation|Select a subnet"
+msgstr ""
+
+msgid "ClusterIntergation|Select a subnetwork"
+msgstr ""
+
+msgid "ClusterIntergation|Select an instance type"
+msgstr ""
+
+msgid "ClusterIntergation|Select key pair"
+msgstr ""
+
+msgid "ClusterIntergation|Select service role"
+msgstr ""
+
+msgid "Clusters|An error occurred while loading clusters"
+msgstr ""
+
+msgid "Code"
+msgstr ""
+
+msgid "Code Coverage: %{coveragePercentage}%{percentSymbol}"
+msgstr ""
+
+msgid "Code Coverage| Empty code coverage data"
+msgstr ""
+
+msgid "Code Coverage|Couldn't fetch the code coverage data"
+msgstr ""
+
+msgid "Code Owners"
+msgstr ""
+
+msgid "Code Owners to the merge request changes."
+msgstr ""
+
+msgid "Code Quality"
+msgstr ""
+
+msgid "Code Review"
+msgstr ""
+
+msgid "Code Review Analytics displays a table of open merge requests considered to be in code review. There are currently no merge requests in review for this project and/or filters."
+msgstr ""
+
+msgid "Code coverage statistics for master %{start_date} - %{end_date}"
+msgstr ""
+
+msgid "Code owner approval is required"
+msgstr ""
+
+msgid "Code owners"
+msgstr ""
+
+msgid "CodeOwner|Pattern"
+msgstr ""
+
+msgid "Cohorts"
+msgstr ""
+
+msgid "Cohorts|Inactive users"
+msgstr ""
+
+msgid "Cohorts|Month %{month_index}"
+msgstr ""
+
+msgid "Cohorts|New users"
+msgstr ""
+
+msgid "Cohorts|Registration month"
+msgstr ""
+
+msgid "Cohorts|Returning users"
+msgstr ""
+
+msgid "Cohorts|User cohorts are shown for the last %{months_included} months. Only users with activity are counted in the 'New users' column; inactive users are counted separately."
+msgstr ""
+
+msgid "Collapse"
+msgstr ""
+
+msgid "Collapse approvers"
+msgstr ""
+
+msgid "Collapse milestones"
+msgstr ""
+
+msgid "Collapse replies"
+msgstr ""
+
+msgid "Collapse sidebar"
+msgstr ""
+
+msgid "Collector hostname"
+msgstr ""
+
+msgid "ComboSearch is not defined"
+msgstr ""
+
+msgid "Coming soon"
+msgstr ""
+
+msgid "Comma-separated, e.g. '1.1.1.1, 2.2.2.0/24'"
+msgstr ""
+
+msgid "Command"
+msgstr ""
+
+msgid "Command line instructions"
+msgstr ""
+
+msgid "Commands applied"
+msgstr ""
+
+msgid "Commands did not apply"
+msgstr ""
+
+msgid "Comment"
+msgstr ""
+
+msgid "Comment & close %{noteable_name}"
+msgstr ""
+
+msgid "Comment & reopen %{noteable_name}"
+msgstr ""
+
+msgid "Comment & resolve thread"
+msgstr ""
+
+msgid "Comment & unresolve thread"
+msgstr ""
+
+msgid "Comment '%{label}' position"
+msgstr ""
+
+msgid "Comment form position"
+msgstr ""
+
+msgid "Comment is being updated"
+msgstr ""
+
+msgid "Comment on lines %{startLine} to %{endLine}"
+msgstr ""
+
+msgid "Comment/Reply (quoting selected text)"
+msgstr ""
+
+msgid "Comments"
+msgstr ""
+
+msgid "Commit"
+msgid_plural "Commits"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "Commit %{commit_id}"
+msgstr ""
+
+msgid "Commit (when editing commit message)"
+msgstr ""
+
+msgid "Commit Message"
+msgstr ""
+
+msgid "Commit deleted"
+msgstr ""
+
+msgid "Commit message"
+msgstr ""
+
+msgid "Commit message (optional)"
+msgstr ""
+
+msgid "Commit statistics for %{ref} %{start_time} - %{end_time}"
+msgstr ""
+
+msgid "Commit to %{branchName} branch"
+msgstr ""
+
+msgid "CommitBoxTitle|Commit"
+msgstr ""
+
+msgid "CommitMessage|Add %{file_name}"
+msgstr ""
+
+msgid "CommitWidget|authored"
+msgstr ""
+
+msgid "Commits"
+msgstr ""
+
+msgid "Commits feed"
+msgstr ""
+
+msgid "Commits per day hour (UTC)"
+msgstr ""
+
+msgid "Commits per day of month"
+msgstr ""
+
+msgid "Commits per weekday"
+msgstr ""
+
+msgid "Commits to"
+msgstr ""
+
+msgid "Commits|An error occurred while fetching merge requests data."
+msgstr ""
+
+msgid "Commits|History"
+msgstr ""
+
+msgid "Commits|No related merge requests found"
+msgstr ""
+
+msgid "Committed by"
+msgstr ""
+
+msgid "Commit…"
+msgstr ""
+
+msgid "Company"
+msgstr ""
+
+msgid "Company name"
+msgstr ""
+
+msgid "Compare"
+msgstr ""
+
+msgid "Compare Git revisions"
+msgstr ""
+
+msgid "Compare Revisions"
+msgstr ""
+
+msgid "Compare changes"
+msgstr ""
+
+msgid "Compare changes with the last commit"
+msgstr ""
+
+msgid "Compare changes with the merge request target branch"
+msgstr ""
+
+msgid "Compare with previous version"
+msgstr ""
+
+msgid "CompareBranches|%{source_branch} and %{target_branch} are the same."
+msgstr ""
+
+msgid "CompareBranches|Compare"
+msgstr ""
+
+msgid "CompareBranches|Source"
+msgstr ""
+
+msgid "CompareBranches|Target"
+msgstr ""
+
+msgid "CompareBranches|There isn't anything to compare."
+msgstr ""
+
+msgid "Complete"
+msgstr ""
+
+msgid "Compliance"
+msgstr ""
+
+msgid "Compliance Dashboard"
+msgstr ""
+
+msgid "Compliance framework (optional)"
+msgstr ""
+
+msgid "Compliance frameworks"
+msgstr ""
+
+msgid "ComplianceDashboard|created by:"
+msgstr ""
+
+msgid "ComplianceFramework|GDPR"
+msgstr ""
+
+msgid "ComplianceFramework|GDPR - General Data Protection Regulation"
+msgstr ""
+
+msgid "ComplianceFramework|HIPAA"
+msgstr ""
+
+msgid "ComplianceFramework|HIPAA - Health Insurance Portability and Accountability Act"
+msgstr ""
+
+msgid "ComplianceFramework|PCI-DSS"
+msgstr ""
+
+msgid "ComplianceFramework|PCI-DSS - Payment Card Industry-Data Security Standard"
+msgstr ""
+
+msgid "ComplianceFramework|SOC 2"
+msgstr ""
+
+msgid "ComplianceFramework|SOC 2 - Service Organization Control 2"
+msgstr ""
+
+msgid "ComplianceFramework|SOX"
+msgstr ""
+
+msgid "ComplianceFramework|SOX - Sarbanes-Oxley"
+msgstr ""
+
+msgid "ComplianceFramework|This project is regulated by %{framework}."
+msgstr ""
+
+msgid "Confidence"
+msgstr ""
+
+msgid "Confidential"
+msgstr ""
+
+msgid "Confidentiality"
+msgstr ""
+
+msgid "Configuration"
+msgstr ""
+
+msgid "Configure GitLab runners to start using the Web Terminal. %{helpStart}Learn more.%{helpEnd}"
+msgstr ""
+
+msgid "Configure Gitaly timeouts."
+msgstr ""
+
+msgid "Configure Let's Encrypt"
+msgstr ""
+
+msgid "Configure Prometheus"
+msgstr ""
+
+msgid "Configure Tracing"
+msgstr ""
+
+msgid "Configure a <code>.gitlab-webide.yml</code> file in the <code>.gitlab</code> directory to start using the Web Terminal. %{helpStart}Learn more.%{helpEnd}"
+msgstr ""
+
+msgid "Configure automatic git checks and housekeeping on repositories."
+msgstr ""
+
+msgid "Configure existing installation"
+msgstr ""
+
+msgid "Configure limit for issues created per minute by web and API requests."
+msgstr ""
+
+msgid "Configure limits for web and API requests."
+msgstr ""
+
+msgid "Configure limits on the number of inbound alerts able to be sent to a project."
+msgstr ""
+
+msgid "Configure paths to be protected by Rack Attack."
+msgstr ""
+
+msgid "Configure repository mirroring."
+msgstr ""
+
+msgid "Configure storage path settings."
+msgstr ""
+
+msgid "Configure the %{link} integration."
+msgstr ""
+
+msgid "Configure the way a user creates a new account."
+msgstr ""
+
+msgid "Confirm"
+msgstr ""
+
+msgid "Confirmation email sent to %{email}"
+msgstr ""
+
+msgid "Confirmation required"
+msgstr ""
+
+msgid "Congratulations! You have enabled Two-factor Authentication!"
+msgstr ""
+
+msgid "Connect"
+msgstr ""
+
+msgid "Connect all repositories"
+msgstr ""
+
+msgid "Connect repositories from GitHub"
+msgstr ""
+
+msgid "Connect your external repositories, and CI/CD pipelines will run for new commits. A GitLab project will be created with only CI/CD features enabled."
+msgstr ""
+
+msgid "Connected"
+msgstr ""
+
+msgid "Connecting"
+msgstr ""
+
+msgid "Connecting to terminal sync service"
+msgstr ""
+
+msgid "Connecting..."
+msgstr ""
+
+msgid "Connection failed"
+msgstr ""
+
+msgid "Connection failure"
+msgstr ""
+
+msgid "Connection timed out"
+msgstr ""
+
+msgid "Connection timeout"
+msgstr ""
+
+msgid "Contact sales to upgrade"
+msgstr ""
+
+msgid "Contact support"
+msgstr ""
+
+msgid "Container Registry"
+msgstr ""
+
+msgid "Container Registry tag expiration policy"
+msgstr ""
+
+msgid "Container Scanning"
+msgstr ""
+
+msgid "Container does not exist"
+msgstr ""
+
+msgid "Container registry images"
+msgstr ""
+
+msgid "Container registry is not enabled on this GitLab instance. Ask an administrator to enable it in order for Auto DevOps to work."
+msgstr ""
+
+msgid "Container repositories sync capacity"
+msgstr ""
+
+msgid "ContainerRegistry| Please visit the %{linkStart}administration settings%{linkEnd} to enable this feature."
+msgstr ""
+
+msgid "ContainerRegistry|%{count} Image repository"
+msgid_plural "ContainerRegistry|%{count} Image repositories"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "ContainerRegistry|%{count} Tag"
+msgid_plural "ContainerRegistry|%{count} Tags"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "ContainerRegistry|%{imageName} tags"
+msgstr ""
+
+msgid "ContainerRegistry|%{title} was successfully scheduled for deletion"
+msgstr ""
+
+msgid "ContainerRegistry|Automatically remove extra images that aren't designed to be kept."
+msgstr ""
+
+msgid "ContainerRegistry|Build an image"
+msgstr ""
+
+msgid "ContainerRegistry|CLI Commands"
+msgstr ""
+
+msgid "ContainerRegistry|Container Registry"
+msgstr ""
+
+msgid "ContainerRegistry|Container Registry tag expiration and retention policy is disabled"
+msgstr ""
+
+msgid "ContainerRegistry|Copy build command"
+msgstr ""
+
+msgid "ContainerRegistry|Copy login command"
+msgstr ""
+
+msgid "ContainerRegistry|Copy push command"
+msgstr ""
+
+msgid "ContainerRegistry|Delete selected"
+msgstr ""
+
+msgid "ContainerRegistry|Docker connection error"
+msgstr ""
+
+msgid "ContainerRegistry|Docker tag expiration policy is %{toggleStatus}"
+msgstr ""
+
+msgid "ContainerRegistry|Expiration interval:"
+msgstr ""
+
+msgid "ContainerRegistry|Expiration policies help manage the storage space used by the Container Registry, but the expiration policies for this registry are disabled. Contact your administrator to enable. %{docLinkStart}More information%{docLinkEnd}"
+msgstr ""
+
+msgid "ContainerRegistry|Expiration policy is disabled"
+msgstr ""
+
+msgid "ContainerRegistry|Expiration policy successfully saved."
+msgstr ""
+
+msgid "ContainerRegistry|Expiration policy will run in %{time}"
+msgstr ""
+
+msgid "ContainerRegistry|Expiration policy:"
+msgstr ""
+
+msgid "ContainerRegistry|Expiration schedule:"
+msgstr ""
+
+msgid "ContainerRegistry|Filter by name"
+msgstr ""
+
+msgid "ContainerRegistry|If you are not already logged in, you need to authenticate to the Container Registry by using your GitLab username and password. If you have %{twofaDocLinkStart}Two-Factor Authentication%{twofaDocLinkEnd} enabled, use a %{personalAccessTokensDocLinkStart}Personal Access Token%{personalAccessTokensDocLinkEnd} instead of a password."
+msgstr ""
+
+msgid "ContainerRegistry|Image ID: %{imageId}"
+msgstr ""
+
+msgid "ContainerRegistry|Image Repositories"
+msgstr ""
+
+msgid "ContainerRegistry|Image tags"
+msgstr ""
+
+msgid "ContainerRegistry|Keep and protect the images that matter most."
+msgstr ""
+
+msgid "ContainerRegistry|Login"
+msgstr ""
+
+msgid "ContainerRegistry|Missing or insufficient permission, delete button disabled"
+msgstr ""
+
+msgid "ContainerRegistry|Number of tags to retain:"
+msgstr ""
+
+msgid "ContainerRegistry|Please contact your administrator."
+msgstr ""
+
+msgid "ContainerRegistry|Published %{timeInfo}"
+msgstr ""
+
+msgid "ContainerRegistry|Push an image"
+msgstr ""
+
+msgid "ContainerRegistry|Regular expressions such as %{codeStart}.*-master%{codeEnd} or %{codeStart}release-.*%{codeEnd} are supported"
+msgstr ""
+
+msgid "ContainerRegistry|Regular expressions such as %{codeStart}.*-test%{codeEnd} or %{codeStart}dev-.*%{codeEnd} are supported. To select all tags, use %{codeStart}.*%{codeEnd}"
+msgstr ""
+
+msgid "ContainerRegistry|Remember to run %{docLinkStart}garbage collection%{docLinkEnd} to remove the stale data from storage."
+msgstr ""
+
+msgid "ContainerRegistry|Remove repository"
+msgstr ""
+
+msgid "ContainerRegistry|Remove tag"
+msgid_plural "ContainerRegistry|Remove tags"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "ContainerRegistry|Something went wrong while fetching the expiration policy."
+msgstr ""
+
+msgid "ContainerRegistry|Something went wrong while fetching the repository list."
+msgstr ""
+
+msgid "ContainerRegistry|Something went wrong while fetching the tags list."
+msgstr ""
+
+msgid "ContainerRegistry|Something went wrong while marking the tag for deletion."
+msgstr ""
+
+msgid "ContainerRegistry|Something went wrong while marking the tags for deletion."
+msgstr ""
+
+msgid "ContainerRegistry|Something went wrong while scheduling %{title} for deletion. Please try again."
+msgstr ""
+
+msgid "ContainerRegistry|Something went wrong while updating the expiration policy."
+msgstr ""
+
+msgid "ContainerRegistry|Sorry, your filter produced no results."
+msgstr ""
+
+msgid "ContainerRegistry|Tag expiration policy"
+msgstr ""
+
+msgid "ContainerRegistry|Tag expiration policy is designed to:"
+msgstr ""
+
+msgid "ContainerRegistry|Tag successfully marked for deletion."
+msgstr ""
+
+msgid "ContainerRegistry|Tags successfully marked for deletion."
+msgstr ""
+
+msgid "ContainerRegistry|Tags with names matching this regex pattern will %{italicStart}be preserved:%{italicEnd}"
+msgstr ""
+
+msgid "ContainerRegistry|Tags with names matching this regex pattern will %{italicStart}expire:%{italicEnd}"
+msgstr ""
+
+msgid "ContainerRegistry|The Container Registry tag expiration and retention policies for this project have not been enabled."
+msgstr ""
+
+msgid "ContainerRegistry|The last tag related to this image was recently removed. This empty image and any associated data will be automatically removed as part of the regular Garbage Collection process. If you have any questions, contact your administrator."
+msgstr ""
+
+msgid "ContainerRegistry|The value of this input should be less than 255 characters"
+msgstr ""
+
+msgid "ContainerRegistry|There are no container images available in this group"
+msgstr ""
+
+msgid "ContainerRegistry|There are no container images stored for this project"
+msgstr ""
+
+msgid "ContainerRegistry|There was an error during the deletion of this image repository, please try again."
+msgstr ""
+
+msgid "ContainerRegistry|This image has no active tags"
+msgstr ""
+
+msgid "ContainerRegistry|This image repository is scheduled for deletion"
+msgstr ""
+
+msgid "ContainerRegistry|To widen your search, change or remove the filters above."
+msgstr ""
+
+msgid "ContainerRegistry|We are having trouble connecting to the Registry, which could be due to an issue with your project name or path. %{docLinkStart}More information%{docLinkEnd}"
+msgstr ""
+
+msgid "ContainerRegistry|With the Container Registry, every project can have its own space to store its Docker images. %{docLinkStart}More Information%{docLinkEnd}"
+msgstr ""
+
+msgid "ContainerRegistry|With the Container Registry, every project can have its own space to store its Docker images. Push at least one Docker image in one of this group's projects in order to show up here. %{docLinkStart}More Information%{docLinkEnd}"
+msgstr ""
+
+msgid "ContainerRegistry|With the GitLab Container Registry, every project can have its own space to store images. %{docLinkStart}More information%{docLinkEnd}"
+msgstr ""
+
+msgid "ContainerRegistry|You are about to remove %{item} tags. Are you sure?"
+msgstr ""
+
+msgid "ContainerRegistry|You are about to remove %{item}. Are you sure?"
+msgstr ""
+
+msgid "ContainerRegistry|You are about to remove repository %{title}. Once you confirm, this repository will be permanently deleted."
+msgstr ""
+
+msgid "ContainerRegistry|You can add an image to this registry with the following commands:"
+msgstr ""
+
+msgid "Contains %{count} blobs of images (%{size})"
+msgstr ""
+
+msgid "Contents of .gitlab-ci.yml"
+msgstr ""
+
+msgid "Continue"
+msgstr ""
+
+msgid "Continue to the next step"
+msgstr ""
+
+msgid "Continuous Integration and Deployment"
+msgstr ""
+
+msgid "Contribute to GitLab"
+msgstr ""
+
+msgid "Contribution"
+msgstr ""
+
+msgid "Contribution Analytics"
+msgstr ""
+
+msgid "ContributionAnalytics|<strong>%{created_count}</strong> created, <strong>%{closed_count}</strong> closed."
+msgstr ""
+
+msgid "ContributionAnalytics|<strong>%{created_count}</strong> created, <strong>%{merged_count}</strong> merged."
+msgstr ""
+
+msgid "ContributionAnalytics|<strong>%{pushes}</strong> pushes, more than <strong>%{commits}</strong> commits by <strong>%{people}</strong> contributors."
+msgstr ""
+
+msgid "ContributionAnalytics|Contribution analytics for issues, merge requests and push events since %{start_date}"
+msgstr ""
+
+msgid "ContributionAnalytics|Issues"
+msgstr ""
+
+msgid "ContributionAnalytics|Last 3 months"
+msgstr ""
+
+msgid "ContributionAnalytics|Last month"
+msgstr ""
+
+msgid "ContributionAnalytics|Last week"
+msgstr ""
+
+msgid "ContributionAnalytics|Merge Requests"
+msgstr ""
+
+msgid "ContributionAnalytics|No issues for the selected time period."
+msgstr ""
+
+msgid "ContributionAnalytics|No merge requests for the selected time period."
+msgstr ""
+
+msgid "ContributionAnalytics|No pushes for the selected time period."
+msgstr ""
+
+msgid "Contributions for <strong>%{calendar_date}</strong>"
+msgstr ""
+
+msgid "Contributions per group member"
+msgstr ""
+
+msgid "Contributors"
+msgstr ""
+
+msgid "Control emails linked to your account"
+msgstr ""
+
+msgid "Control the display of third party offers."
+msgstr ""
+
+msgid "Cookie domain"
+msgstr ""
+
+msgid "Copied"
+msgstr ""
+
+msgid "Copied labels and milestone from %{source_issuable_reference}."
+msgstr ""
+
+msgid "Copy"
+msgstr ""
+
+msgid "Copy %{field}"
+msgstr ""
+
+msgid "Copy %{http_label} clone URL"
+msgstr ""
+
+msgid "Copy %{protocol} clone URL"
+msgstr ""
+
+msgid "Copy %{proxy_url}"
+msgstr ""
+
+msgid "Copy %{type}"
+msgstr ""
+
+msgid "Copy Account ID to clipboard"
+msgstr ""
+
+msgid "Copy External ID to clipboard"
+msgstr ""
+
+msgid "Copy ID"
+msgstr ""
+
+msgid "Copy KRB5 clone URL"
+msgstr ""
+
+msgid "Copy SSH clone URL"
+msgstr ""
+
+msgid "Copy SSH public key"
+msgstr ""
+
+msgid "Copy URL"
+msgstr ""
+
+msgid "Copy branch name"
+msgstr ""
+
+msgid "Copy command"
+msgstr ""
+
+msgid "Copy commands"
+msgstr ""
+
+msgid "Copy commit SHA"
+msgstr ""
+
+msgid "Copy environment"
+msgstr ""
+
+msgid "Copy evidence SHA"
+msgstr ""
+
+msgid "Copy file contents"
+msgstr ""
+
+msgid "Copy file path"
+msgstr ""
+
+msgid "Copy key"
+msgstr ""
+
+msgid "Copy labels and milestone from %{source_issuable_reference}."
+msgstr ""
+
+msgid "Copy labels and milestone from other issue or merge request in this project"
+msgstr ""
+
+msgid "Copy link"
+msgstr ""
+
+msgid "Copy link to chart"
+msgstr ""
+
+msgid "Copy reference"
+msgstr ""
+
+msgid "Copy secret"
+msgstr ""
+
+msgid "Copy token"
+msgstr ""
+
+msgid "Copy trigger token"
+msgstr ""
+
+msgid "Copy value"
+msgstr ""
+
+msgid "Could not add admins as members"
+msgstr ""
+
+msgid "Could not authorize chat nickname. Try again!"
+msgstr ""
+
+msgid "Could not change HEAD: branch '%{branch}' does not exist"
+msgstr ""
+
+msgid "Could not connect to FogBugz, check your URL"
+msgstr ""
+
+msgid "Could not connect to Sentry. Refresh the page to try again."
+msgstr ""
+
+msgid "Could not connect to Web IDE file mirror service."
+msgstr ""
+
+msgid "Could not create Wiki Repository at this time. Please try again later."
+msgstr ""
+
+msgid "Could not create environment"
+msgstr ""
+
+msgid "Could not create group"
+msgstr ""
+
+msgid "Could not create project"
+msgstr ""
+
+msgid "Could not delete %{design}. Please try again."
+msgstr ""
+
+msgid "Could not delete chat nickname %{chat_name}."
+msgstr ""
+
+msgid "Could not find design."
+msgstr ""
+
+msgid "Could not find iteration"
+msgstr ""
+
+msgid "Could not remove the trigger."
+msgstr ""
+
+msgid "Could not restore the group"
+msgstr ""
+
+msgid "Could not revoke impersonation token %{token_name}."
+msgstr ""
+
+msgid "Could not revoke personal access token %{personal_access_token_name}."
+msgstr ""
+
+msgid "Could not revoke project access token %{project_access_token_name}."
+msgstr ""
+
+msgid "Could not save group ID"
+msgstr ""
+
+msgid "Could not save project ID"
+msgstr ""
+
+msgid "Could not save prometheus manual configuration"
+msgstr ""
+
+msgid "Could not update the LDAP settings"
+msgstr ""
+
+msgid "Could not upload your designs as one or more files uploaded are not supported."
+msgstr ""
+
+msgid "Country"
+msgstr ""
+
+msgid "Coverage"
+msgstr ""
+
+msgid "Coverage Fuzzing"
+msgstr ""
+
+msgid "Crash State"
+msgstr ""
+
+msgid "Create"
+msgstr ""
+
+msgid "Create %{environment}"
+msgstr ""
+
+msgid "Create %{type}"
+msgstr ""
+
+msgid "Create New Directory"
+msgstr ""
+
+msgid "Create New Domain"
+msgstr ""
+
+msgid "Create Project"
+msgstr ""
+
+msgid "Create a GitLab account first, and then connect it to your %{label} account."
+msgstr ""
+
+msgid "Create a Mattermost team for this group"
+msgstr ""
+
+msgid "Create a local proxy for storing frequently used upstream images. %{link_start}Learn more%{link_end} about dependency proxies."
+msgstr ""
+
+msgid "Create a merge request"
+msgstr ""
+
+msgid "Create a new branch"
+msgstr ""
+
+msgid "Create a new deploy key for this project"
+msgstr ""
+
+msgid "Create a new file as there are no files yet. Afterwards, you'll be able to commit your changes."
+msgstr ""
+
+msgid "Create a new issue"
+msgstr ""
+
+msgid "Create a new repository"
+msgstr ""
+
+msgid "Create a personal access token on your account to pull or push via %{protocol}."
+msgstr ""
+
+msgid "Create a project pre-populated with the necessary files to get you started quickly."
+msgstr ""
+
+msgid "Create an account using:"
+msgstr ""
+
+msgid "Create an issue. Issues are created for each alert triggered."
+msgstr ""
+
+msgid "Create and provide your GitHub %{link_start}Personal Access Token%{link_end}. You will need to select the <code>repo</code> scope, so we can display a list of your public and private repositories which are available to import."
+msgstr ""
+
+msgid "Create board"
+msgstr ""
+
+msgid "Create branch"
+msgstr ""
+
+msgid "Create commit"
+msgstr ""
+
+msgid "Create confidential merge request"
+msgstr ""
+
+msgid "Create confidential merge request and branch"
+msgstr ""
+
+msgid "Create directory"
+msgstr ""
+
+msgid "Create empty repository"
+msgstr ""
+
+msgid "Create epic"
+msgstr ""
+
+msgid "Create file"
+msgstr ""
+
+msgid "Create group"
+msgstr ""
+
+msgid "Create group label"
+msgstr ""
+
+msgid "Create issue"
+msgstr ""
+
+msgid "Create iteration"
+msgstr ""
+
+msgid "Create lists from labels. Issues with that label appear in that list."
+msgstr ""
+
+msgid "Create merge request"
+msgstr ""
+
+msgid "Create merge request and branch"
+msgstr ""
+
+msgid "Create milestone"
+msgstr ""
+
+msgid "Create new"
+msgstr ""
+
+msgid "Create new board"
+msgstr ""
+
+msgid "Create new branch"
+msgstr ""
+
+msgid "Create new directory"
+msgstr ""
+
+msgid "Create new file"
+msgstr ""
+
+msgid "Create new file or directory"
+msgstr ""
+
+msgid "Create new label"
+msgstr ""
+
+msgid "Create new..."
+msgstr ""
+
+msgid "Create project"
+msgstr ""
+
+msgid "Create project label"
+msgstr ""
+
+msgid "Create requirement"
+msgstr ""
+
+msgid "Create snippet"
+msgstr ""
+
+msgid "Create wildcard: %{searchTerm}"
+msgstr ""
+
+msgid "Create your first page"
+msgstr ""
+
+msgid "Create your group"
+msgstr ""
+
+msgid "Create/import your first project"
+msgstr ""
+
+msgid "CreateGroup|You don’t have permission to create a subgroup in this group."
+msgstr ""
+
+msgid "CreateGroup|You don’t have permission to create groups."
+msgstr ""
+
+msgid "CreateTag|Tag"
+msgstr ""
+
+msgid "CreateTokenToCloneLink|create a personal access token"
+msgstr ""
+
+msgid "Created"
+msgstr ""
+
+msgid "Created %{timestamp}"
+msgstr ""
+
+msgid "Created At"
+msgstr ""
+
+msgid "Created On"
+msgstr ""
+
+msgid "Created a branch and a merge request to resolve this issue."
+msgstr ""
+
+msgid "Created branch '%{branch_name}' and a merge request to resolve this issue."
+msgstr ""
+
+msgid "Created by me"
+msgstr ""
+
+msgid "Created date"
+msgstr ""
+
+msgid "Created issue %{issueLink}"
+msgstr ""
+
+msgid "Created issue %{issueLink} at %{projectLink}"
+msgstr ""
+
+msgid "Created merge request %{mergeRequestLink}"
+msgstr ""
+
+msgid "Created merge request %{mergeRequestLink} at %{projectLink}"
+msgstr ""
+
+msgid "Created on"
+msgstr ""
+
+msgid "Created on:"
+msgstr ""
+
+msgid "Creates a branch and a merge request to resolve this issue."
+msgstr ""
+
+msgid "Creates branch '%{branch_name}' and a merge request to resolve this issue."
+msgstr ""
+
+msgid "Creating"
+msgstr ""
+
+msgid "Creating epic"
+msgstr ""
+
+msgid "Creating graphs uses the data from the Prometheus server. If this takes a long time, ensure that data is available."
+msgstr ""
+
+msgid "Creation date"
+msgstr ""
+
+msgid "Credentials"
+msgstr ""
+
+msgid "CredentialsInventory|No credentials found"
+msgstr ""
+
+msgid "CredentialsInventory|Personal Access Tokens"
+msgstr ""
+
+msgid "CredentialsInventory|SSH Keys"
+msgstr ""
+
+msgid "Critical vulnerabilities present"
+msgstr ""
+
+msgid "Cron Timezone"
+msgstr ""
+
+msgid "Crossplane"
+msgstr ""
+
+msgid "Current Branch"
+msgstr ""
+
+msgid "Current Plan"
+msgstr ""
+
+msgid "Current Project"
+msgstr ""
+
+msgid "Current node"
+msgstr ""
+
+msgid "Current node must be the primary node or you will be locking yourself out"
+msgstr ""
+
+msgid "Current password"
+msgstr ""
+
+msgid "Current vulnerabilities count"
+msgstr ""
+
+msgid "CurrentUser|Buy Pipeline minutes"
+msgstr ""
+
+msgid "CurrentUser|One of your groups is running out"
+msgstr ""
+
+msgid "CurrentUser|Profile"
+msgstr ""
+
+msgid "CurrentUser|Settings"
+msgstr ""
+
+msgid "CurrentUser|Start a Gold trial"
+msgstr ""
+
+msgid "CurrentUser|Upgrade"
+msgstr ""
+
+msgid "Custom Attributes"
+msgstr ""
+
+msgid "Custom CI configuration path"
+msgstr ""
+
+msgid "Custom Git clone URL for HTTP(S)"
+msgstr ""
+
+msgid "Custom hostname (for private commit emails)"
+msgstr ""
+
+msgid "Custom notification events"
+msgstr ""
+
+msgid "Custom notification levels are the same as participating levels. With custom notification levels you will also receive notifications for select events. To find out more, check out %{notification_link}."
+msgstr ""
+
+msgid "Custom project templates"
+msgstr ""
+
+msgid "Custom project templates have not been set up for groups that you are a member of. They are enabled from a group’s settings page. Contact your group’s Owner or Maintainer to setup custom project templates."
+msgstr ""
+
+msgid "Custom range"
+msgstr ""
+
+msgid "Custom range (UTC)"
+msgstr ""
+
+msgid "CustomCycleAnalytics|Add a stage"
+msgstr ""
+
+msgid "CustomCycleAnalytics|Add stage"
+msgstr ""
+
+msgid "CustomCycleAnalytics|Editing stage"
+msgstr ""
+
+msgid "CustomCycleAnalytics|Enter a name for the stage"
+msgstr ""
+
+msgid "CustomCycleAnalytics|Name"
+msgstr ""
+
+msgid "CustomCycleAnalytics|New stage"
+msgstr ""
+
+msgid "CustomCycleAnalytics|Please select a start event first"
+msgstr ""
+
+msgid "CustomCycleAnalytics|Select start event"
+msgstr ""
+
+msgid "CustomCycleAnalytics|Select stop event"
+msgstr ""
+
+msgid "CustomCycleAnalytics|Stage name already exists"
+msgstr ""
+
+msgid "CustomCycleAnalytics|Start event"
+msgstr ""
+
+msgid "CustomCycleAnalytics|Start event changed, please select a valid stop event"
+msgstr ""
+
+msgid "CustomCycleAnalytics|Start event label"
+msgstr ""
+
+msgid "CustomCycleAnalytics|Stop event"
+msgstr ""
+
+msgid "CustomCycleAnalytics|Stop event label"
+msgstr ""
+
+msgid "CustomCycleAnalytics|Update stage"
+msgstr ""
+
+msgid "Customer Portal"
+msgstr ""
+
+msgid "Customize colors"
+msgstr ""
+
+msgid "Customize how FogBugz email addresses and usernames are imported into GitLab. In the next step, you'll be able to select the projects you want to import."
+msgstr ""
+
+msgid "Customize how Google Code email addresses and usernames are imported into GitLab. In the next step, you'll be able to select the projects you want to import."
+msgstr ""
+
+msgid "Customize icon"
+msgstr ""
+
+msgid "Customize language and region related settings."
+msgstr ""
+
+msgid "Customize name"
+msgstr ""
+
+msgid "Customize your pipeline configuration, view your pipeline status and coverage report."
+msgstr ""
+
+msgid "Customize your pipeline configuration."
+msgstr ""
+
+msgid "Cycle Time"
+msgstr ""
+
+msgid "CycleAnalyticsEvent|Issue closed"
+msgstr ""
+
+msgid "CycleAnalyticsEvent|Issue created"
+msgstr ""
+
+msgid "CycleAnalyticsEvent|Issue first added to a board"
+msgstr ""
+
+msgid "CycleAnalyticsEvent|Issue first associated with a milestone"
+msgstr ""
+
+msgid "CycleAnalyticsEvent|Issue first associated with a milestone or issue first added to a board"
+msgstr ""
+
+msgid "CycleAnalyticsEvent|Issue first mentioned in a commit"
+msgstr ""
+
+msgid "CycleAnalyticsEvent|Issue label was added"
+msgstr ""
+
+msgid "CycleAnalyticsEvent|Issue label was removed"
+msgstr ""
+
+msgid "CycleAnalyticsEvent|Issue last edited"
+msgstr ""
+
+msgid "CycleAnalyticsEvent|Merge request closed"
+msgstr ""
+
+msgid "CycleAnalyticsEvent|Merge request created"
+msgstr ""
+
+msgid "CycleAnalyticsEvent|Merge request first deployed to production"
+msgstr ""
+
+msgid "CycleAnalyticsEvent|Merge request label was added"
+msgstr ""
+
+msgid "CycleAnalyticsEvent|Merge request label was removed"
+msgstr ""
+
+msgid "CycleAnalyticsEvent|Merge request last build finish time"
+msgstr ""
+
+msgid "CycleAnalyticsEvent|Merge request last build start time"
+msgstr ""
+
+msgid "CycleAnalyticsEvent|Merge request last edited"
+msgstr ""
+
+msgid "CycleAnalyticsEvent|Merge request merged"
+msgstr ""
+
+msgid "CycleAnalyticsStage|Code"
+msgstr ""
+
+msgid "CycleAnalyticsStage|Issue"
+msgstr ""
+
+msgid "CycleAnalyticsStage|Plan"
+msgstr ""
+
+msgid "CycleAnalyticsStage|Review"
+msgstr ""
+
+msgid "CycleAnalyticsStage|Staging"
+msgstr ""
+
+msgid "CycleAnalyticsStage|Test"
+msgstr ""
+
+msgid "CycleAnalyticsStage|Total"
+msgstr ""
+
+msgid "CycleAnalyticsStage|is not available for the selected group"
+msgstr ""
+
+msgid "CycleAnalyticsStage|should be under a group"
+msgstr ""
+
+msgid "CycleAnalytics|%{selectedLabelsCount} selected (%{maxLabels} max)"
+msgstr ""
+
+msgid "CycleAnalytics|%{stageCount} stages selected"
+msgstr ""
+
+msgid "CycleAnalytics|All stages"
+msgstr ""
+
+msgid "CycleAnalytics|Date"
+msgstr ""
+
+msgid "CycleAnalytics|Days to completion"
+msgstr ""
+
+msgid "CycleAnalytics|Display chart filters"
+msgstr ""
+
+msgid "CycleAnalytics|No stages selected"
+msgstr ""
+
+msgid "CycleAnalytics|Number of tasks"
+msgstr ""
+
+msgid "CycleAnalytics|Only %{maxLabels} labels can be selected at this time"
+msgstr ""
+
+msgid "CycleAnalytics|Project selected"
+msgid_plural "CycleAnalytics|%d projects selected"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "CycleAnalytics|Select labels"
+msgstr ""
+
+msgid "CycleAnalytics|Show"
+msgstr ""
+
+msgid "CycleAnalytics|Showing %{subjectFilterText} and %{selectedLabelsCount} labels"
+msgstr ""
+
+msgid "CycleAnalytics|Showing data for group '%{groupName}' and %{selectedProjectCount} projects from %{startDate} to %{endDate}"
+msgstr ""
+
+msgid "CycleAnalytics|Showing data for group '%{groupName}' from %{startDate} to %{endDate}"
+msgstr ""
+
+msgid "CycleAnalytics|Stages"
+msgstr ""
+
+msgid "CycleAnalytics|Tasks by type"
+msgstr ""
+
+msgid "CycleAnalytics|The given date range is larger than 180 days"
+msgstr ""
+
+msgid "CycleAnalytics|Total days to completion"
+msgstr ""
+
+msgid "CycleAnalytics|Type of work"
+msgstr ""
+
+msgid "CycleAnalytics|group dropdown filter"
+msgstr ""
+
+msgid "CycleAnalytics|not allowed for the given start event"
+msgstr ""
+
+msgid "CycleAnalytics|project dropdown filter"
+msgstr ""
+
+msgid "CycleAnalytics|stage dropdown"
+msgstr ""
+
+msgid "DAG"
+msgstr ""
+
+msgid "DAG visualization requires at least 3 dependent jobs."
+msgstr ""
+
+msgid "DNS"
+msgstr ""
+
+msgid "Dashboard"
+msgstr ""
+
+msgid "Dashboard uid not found"
+msgstr ""
+
+msgid "DashboardProjects|All"
+msgstr ""
+
+msgid "DashboardProjects|Personal"
+msgstr ""
+
+msgid "DashboardProjects|Trending"
+msgstr ""
+
+msgid "Dashboards"
+msgstr ""
+
+msgid "Dashboard|%{firstProject} and %{secondProject}"
+msgstr ""
+
+msgid "Dashboard|%{firstProject}, %{rest}, and %{secondProject}"
+msgstr ""
+
+msgid "Dashboard|Unable to add %{invalidProjects}. This dashboard is available for public projects, and private projects in groups with a Silver plan."
+msgstr ""
+
+msgid "Data is still calculating..."
+msgstr ""
+
+msgid "Datasource name not found"
+msgstr ""
+
+msgid "Date"
+msgstr ""
+
+msgid "Date picker"
+msgstr ""
+
+msgid "Date range cannot exceed %{maxDateRange} days."
+msgstr ""
+
+msgid "Day of month"
+msgstr ""
+
+msgid "DayTitle|F"
+msgstr ""
+
+msgid "DayTitle|M"
+msgstr ""
+
+msgid "DayTitle|S"
+msgstr ""
+
+msgid "DayTitle|W"
+msgstr ""
+
+msgid "Days"
+msgstr ""
+
+msgid "Days to merge"
+msgstr ""
+
+msgid "Debug"
+msgstr ""
+
+msgid "Dec"
+msgstr ""
+
+msgid "December"
+msgstr ""
+
+msgid "Decline"
+msgstr ""
+
+msgid "Decline and sign out"
+msgstr ""
+
+msgid "Default Branch"
+msgstr ""
+
+msgid "Default CI configuration path"
+msgstr ""
+
+msgid "Default artifacts expiration"
+msgstr ""
+
+msgid "Default branch"
+msgstr ""
+
+msgid "Default branch and protected branches"
+msgstr ""
+
+msgid "Default classification label"
+msgstr ""
+
+msgid "Default dashboard"
+msgstr ""
+
+msgid "Default deletion adjourned period"
+msgstr ""
+
+msgid "Default description template for issues"
+msgstr ""
+
+msgid "Default description template for merge requests"
+msgstr ""
+
+msgid "Default first day of the week"
+msgstr ""
+
+msgid "Default first day of the week in calendars and date pickers."
+msgstr ""
+
+msgid "Default issue template"
+msgstr ""
+
+msgid "Default project deletion protection"
+msgstr ""
+
+msgid "Default projects limit"
+msgstr ""
+
+msgid "Default stages"
+msgstr ""
+
+msgid "Default: Directly import the Google Code email address or username"
+msgstr ""
+
+msgid "Default: Map a FogBugz account ID to a full name"
+msgstr ""
+
+msgid "Define a custom pattern with cron syntax"
+msgstr ""
+
+msgid "Define custom rules for what constitutes spam, independent of Akismet"
+msgstr ""
+
+msgid "Define environments in the deploy stage(s) in <code>.gitlab-ci.yml</code> to track deployments here."
+msgstr ""
+
+msgid "DelayedJobs|Are you sure you want to run %{jobName} immediately? Otherwise this job will run automatically after it's timer finishes."
+msgstr ""
+
+msgid "DelayedJobs|Are you sure you want to run %{job_name} immediately? This job will run automatically after it's timer finishes."
+msgstr ""
+
+msgid "DelayedJobs|Start now"
+msgstr ""
+
+msgid "DelayedJobs|Unschedule"
+msgstr ""
+
+msgid "DelayedJobs|delayed"
+msgstr ""
+
+msgid "Delete"
+msgstr ""
+
+msgid "Delete Comment"
+msgstr ""
+
+msgid "Delete Package"
+msgstr ""
+
+msgid "Delete Snippet"
+msgstr ""
+
+msgid "Delete artifacts"
+msgstr ""
+
+msgid "Delete board"
+msgstr ""
+
+msgid "Delete comment"
+msgstr ""
+
+msgid "Delete domain"
+msgstr ""
+
+msgid "Delete label"
+msgstr ""
+
+msgid "Delete label: %{label_name} ?"
+msgstr ""
+
+msgid "Delete license"
+msgstr ""
+
+msgid "Delete list"
+msgstr ""
+
+msgid "Delete pipeline"
+msgstr ""
+
+msgid "Delete project"
+msgstr ""
+
+msgid "Delete serverless domain?"
+msgstr ""
+
+msgid "Delete snippet"
+msgstr ""
+
+msgid "Delete snippet?"
+msgstr ""
+
+msgid "Delete source branch"
+msgstr ""
+
+msgid "Delete this attachment"
+msgstr ""
+
+msgid "Delete user list"
+msgstr ""
+
+msgid "Delete variable"
+msgstr ""
+
+msgid "DeleteProject|Failed to remove project repository. Please try again or contact administrator."
+msgstr ""
+
+msgid "DeleteProject|Failed to remove project snippets. Please try again or contact administrator."
+msgstr ""
+
+msgid "DeleteProject|Failed to remove some tags in project container registry. Please try again or contact administrator."
+msgstr ""
+
+msgid "DeleteProject|Failed to remove wiki repository. Please try again or contact administrator."
+msgstr ""
+
+msgid "DeleteProject|Failed to restore project repository. Please contact the administrator."
+msgstr ""
+
+msgid "DeleteProject|Failed to restore wiki repository. Please contact the administrator."
+msgstr ""
+
+msgid "Deleted"
+msgstr ""
+
+msgid "Deleted chat nickname: %{chat_name}!"
+msgstr ""
+
+msgid "Deleted in this version"
+msgstr ""
+
+msgid "Deleting"
+msgstr ""
+
+msgid "Deleting the license failed."
+msgstr ""
+
+msgid "Deleting the license failed. The license was not found."
+msgstr ""
+
+msgid "Deleting the license failed. You are not permitted to perform this action."
+msgstr ""
+
+msgid "Deletion pending. This project will be removed on %{date}. Repository and other project resources are read-only."
+msgstr ""
+
+msgid "Denied authorization of chat nickname %{user_name}."
+msgstr ""
+
+msgid "Deny"
+msgstr ""
+
+msgid "Dependencies"
+msgstr ""
+
+msgid "Dependencies help page link"
+msgstr ""
+
+msgid "Dependencies|%d additional vulnerability not shown"
+msgid_plural "Dependencies|%d additional vulnerabilities not shown"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "Dependencies|%d vulnerability detected"
+msgid_plural "Dependencies|%d vulnerabilities detected"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "Dependencies|%{remainingLicensesCount} more"
+msgstr ""
+
+msgid "Dependencies|All"
+msgstr ""
+
+msgid "Dependencies|Based on the %{linkStart}latest successful%{linkEnd} scan"
+msgstr ""
+
+msgid "Dependencies|Component"
+msgstr ""
+
+msgid "Dependencies|Component name"
+msgstr ""
+
+msgid "Dependencies|Export as JSON"
+msgstr ""
+
+msgid "Dependencies|Job failed to generate the dependency list"
+msgstr ""
+
+msgid "Dependencies|License"
+msgstr ""
+
+msgid "Dependencies|Location"
+msgstr ""
+
+msgid "Dependencies|Packager"
+msgstr ""
+
+msgid "Dependencies|The %{codeStartTag}dependency_scanning%{codeEndTag} job has failed and cannot generate the list. Please ensure the job is running properly and run the pipeline again."
+msgstr ""
+
+msgid "Dependencies|Toggle vulnerability list"
+msgstr ""
+
+msgid "Dependencies|Unsupported file(s) detected"
+msgstr ""
+
+msgid "Dependencies|Vulnerable components"
+msgstr ""
+
+msgid "Dependency List"
+msgstr ""
+
+msgid "Dependency List has no entries"
+msgstr ""
+
+msgid "Dependency Proxy"
+msgstr ""
+
+msgid "Dependency Scanning"
+msgstr ""
+
+msgid "Dependency proxy"
+msgstr ""
+
+msgid "Dependency proxy URL"
+msgstr ""
+
+msgid "Dependency proxy feature is limited to public groups for now."
+msgstr ""
+
+msgid "DependencyProxy|Toggle Dependency Proxy"
+msgstr ""
+
+msgid "Depends on %d merge request being merged"
+msgid_plural "Depends on %d merge requests being merged"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "Depends on <strong>%d closed</strong> merge request."
+msgid_plural "Depends on <strong>%d closed</strong> merge requests."
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "Deploy"
+msgid_plural "Deploys"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "Deploy Keys"
+msgstr ""
+
+msgid "Deploy key was successfully updated."
+msgstr ""
+
+msgid "Deploy keys allow read-only or read-write (if enabled) access to your repository. Deploy keys can be used for CI, staging or production servers. You can create a deploy key or add an existing one."
+msgstr ""
+
+msgid "Deploy progress not found. To see pods, ensure your environment matches %{linkStart}deploy board criteria%{linkEnd}."
+msgstr ""
+
+msgid "Deploy to..."
+msgstr ""
+
+msgid "DeployBoard|Matching on the %{appLabel} label has been removed for deploy boards. To see all instances on your board, you must update your chart and redeploy."
+msgstr ""
+
+msgid "DeployKeys|+%{count} others"
+msgstr ""
+
+msgid "DeployKeys|Current project"
+msgstr ""
+
+msgid "DeployKeys|Deploy key"
+msgstr ""
+
+msgid "DeployKeys|Enabled deploy keys"
+msgstr ""
+
+msgid "DeployKeys|Error enabling deploy key"
+msgstr ""
+
+msgid "DeployKeys|Error getting deploy keys"
+msgstr ""
+
+msgid "DeployKeys|Error removing deploy key"
+msgstr ""
+
+msgid "DeployKeys|Expand %{count} other projects"
+msgstr ""
+
+msgid "DeployKeys|Loading deploy keys"
+msgstr ""
+
+msgid "DeployKeys|No deploy keys found. Create one with the form above."
+msgstr ""
+
+msgid "DeployKeys|Privately accessible deploy keys"
+msgstr ""
+
+msgid "DeployKeys|Project usage"
+msgstr ""
+
+msgid "DeployKeys|Publicly accessible deploy keys"
+msgstr ""
+
+msgid "DeployKeys|Read access only"
+msgstr ""
+
+msgid "DeployKeys|Write access allowed"
+msgstr ""
+
+msgid "DeployKeys|You are going to remove this deploy key. Are you sure?"
+msgstr ""
+
+msgid "DeployTokens|Active Deploy Tokens (%{active_tokens})"
+msgstr ""
+
+msgid "DeployTokens|Add a deploy token"
+msgstr ""
+
+msgid "DeployTokens|Allows read access to the package registry"
+msgstr ""
+
+msgid "DeployTokens|Allows read-only access to the registry images"
+msgstr ""
+
+msgid "DeployTokens|Allows read-only access to the repository"
+msgstr ""
+
+msgid "DeployTokens|Allows write access to the package registry"
+msgstr ""
+
+msgid "DeployTokens|Allows write access to the registry images"
+msgstr ""
+
+msgid "DeployTokens|Copy deploy token"
+msgstr ""
+
+msgid "DeployTokens|Copy username"
+msgstr ""
+
+msgid "DeployTokens|Create deploy token"
+msgstr ""
+
+msgid "DeployTokens|Created"
+msgstr ""
+
+msgid "DeployTokens|Default format is \"gitlab+deploy-token-{n}\". Enter custom username if you want to change it."
+msgstr ""
+
+msgid "DeployTokens|Deploy Tokens"
+msgstr ""
+
+msgid "DeployTokens|Deploy tokens allow access to packages, your repository, and registry images."
+msgstr ""
+
+msgid "DeployTokens|Expires"
+msgstr ""
+
+msgid "DeployTokens|Group deploy tokens allow access to the packages, repositories, and registry images within the group."
+msgstr ""
+
+msgid "DeployTokens|Name"
+msgstr ""
+
+msgid "DeployTokens|Pick a name for the application, and we'll give you a unique deploy token."
+msgstr ""
+
+msgid "DeployTokens|Revoke"
+msgstr ""
+
+msgid "DeployTokens|Revoke %{b_start}%{name}%{b_end}?"
+msgstr ""
+
+msgid "DeployTokens|Revoke %{name}"
+msgstr ""
+
+msgid "DeployTokens|Scopes"
+msgstr ""
+
+msgid "DeployTokens|This %{entity_type} has no active Deploy Tokens."
+msgstr ""
+
+msgid "DeployTokens|This action cannot be undone."
+msgstr ""
+
+msgid "DeployTokens|Use this token as a password. Make sure you save it - you won't be able to access it again."
+msgstr ""
+
+msgid "DeployTokens|Use this username as a login."
+msgstr ""
+
+msgid "DeployTokens|Username"
+msgstr ""
+
+msgid "DeployTokens|You are about to revoke %{b_start}%{name}%{b_end}."
+msgstr ""
+
+msgid "DeployTokens|Your New Deploy Token"
+msgstr ""
+
+msgid "DeployTokens|Your new group deploy token has been created."
+msgstr ""
+
+msgid "DeployTokens|Your new project deploy token has been created."
+msgstr ""
+
+msgid "Deployed"
+msgstr ""
+
+msgid "Deployed to"
+msgstr ""
+
+msgid "Deploying to"
+msgstr ""
+
+msgid "Deploying to AWS is easy with GitLab"
+msgstr ""
+
+msgid "Deployment Frequency"
+msgstr ""
+
+msgid "Deployment|API"
+msgstr ""
+
+msgid "Deployment|This deployment was created using the API"
+msgstr ""
+
+msgid "Deployment|canceled"
+msgstr ""
+
+msgid "Deployment|created"
+msgstr ""
+
+msgid "Deployment|failed"
+msgstr ""
+
+msgid "Deployment|running"
+msgstr ""
+
+msgid "Deployment|success"
+msgstr ""
+
+msgid "Deprioritize label"
+msgstr ""
+
+msgid "Descending"
+msgstr ""
+
+msgid "Describe the goal of the changes and what reviewers should be aware of."
+msgstr ""
+
+msgid "Describe the requirement here"
+msgstr ""
+
+msgid "Description"
+msgstr ""
+
+msgid "Description parsed with %{link_start}GitLab Flavored Markdown%{link_end}"
+msgstr ""
+
+msgid "Description templates allow you to define context-specific templates for issue and merge request description fields for your project."
+msgstr ""
+
+msgid "Description:"
+msgstr ""
+
+msgid "Descriptive label"
+msgstr ""
+
+msgid "Deselect all"
+msgstr ""
+
+msgid "Design Management files and data"
+msgstr ""
+
+msgid "DesignManagement|%{current_design} of %{designs_count}"
+msgstr ""
+
+msgid "DesignManagement|%{filename} did not change."
+msgstr ""
+
+msgid "DesignManagement|Adding a design with the same filename replaces the file in a new version."
+msgstr ""
+
+msgid "DesignManagement|Are you sure you want to cancel changes to this comment?"
+msgstr ""
+
+msgid "DesignManagement|Are you sure you want to cancel creating this comment?"
+msgstr ""
+
+msgid "DesignManagement|Are you sure you want to delete the selected designs?"
+msgstr ""
+
+msgid "DesignManagement|Cancel changes"
+msgstr ""
+
+msgid "DesignManagement|Cancel comment confirmation"
+msgstr ""
+
+msgid "DesignManagement|Cancel comment update confirmation"
+msgstr ""
+
+msgid "DesignManagement|Click the image where you'd like to start a new discussion"
+msgstr ""
+
+msgid "DesignManagement|Comment"
+msgstr ""
+
+msgid "DesignManagement|Comments you resolve can be viewed and unresolved by going to the \"Resolved Comments\" section below"
+msgstr ""
+
+msgid "DesignManagement|Could not add a new comment. Please try again."
+msgstr ""
+
+msgid "DesignManagement|Could not create new discussion. Please try again."
+msgstr ""
+
+msgid "DesignManagement|Could not update discussion. Please try again."
+msgstr ""
+
+msgid "DesignManagement|Could not update note. Please try again."
+msgstr ""
+
+msgid "DesignManagement|Delete"
+msgstr ""
+
+msgid "DesignManagement|Delete designs confirmation"
+msgstr ""
+
+msgid "DesignManagement|Delete selected"
+msgstr ""
+
+msgid "DesignManagement|Deselect all"
+msgstr ""
+
+msgid "DesignManagement|Discard comment"
+msgstr ""
+
+msgid "DesignManagement|Error uploading a new design. Please try again."
+msgstr ""
+
+msgid "DesignManagement|Go back to designs"
+msgstr ""
+
+msgid "DesignManagement|Go to next design"
+msgstr ""
+
+msgid "DesignManagement|Go to previous design"
+msgstr ""
+
+msgid "DesignManagement|Keep changes"
+msgstr ""
+
+msgid "DesignManagement|Keep comment"
+msgstr ""
+
+msgid "DesignManagement|Learn more about resolving comments"
+msgstr ""
+
+msgid "DesignManagement|Requested design version does not exist. Showing latest version instead"
+msgstr ""
+
+msgid "DesignManagement|Resolve thread"
+msgstr ""
+
+msgid "DesignManagement|Resolved Comments"
+msgstr ""
+
+msgid "DesignManagement|Save comment"
+msgstr ""
+
+msgid "DesignManagement|Select all"
+msgstr ""
+
+msgid "DesignManagement|The maximum number of designs allowed to be uploaded is %{upload_limit}. Please try again."
+msgstr ""
+
+msgid "DesignManagement|To enable design management, you'll need to %{requirements_link_start}meet the requirements%{requirements_link_end}. If you need help, reach out to our %{support_link_start}support team%{support_link_end} for assistance."
+msgstr ""
+
+msgid "DesignManagement|Unresolve thread"
+msgstr ""
+
+msgid "DesignManagement|Upload designs"
+msgstr ""
+
+msgid "DesignManagement|Upload skipped."
+msgstr ""
+
+msgid "DesignManagement|and %{moreCount} more."
+msgstr ""
+
+msgid "Designs"
+msgstr ""
+
+msgid "Destroy"
+msgstr ""
+
+msgid "Detail"
+msgstr ""
+
+msgid "Details"
+msgstr ""
+
+msgid "Details (default)"
+msgstr ""
+
+msgid "Detect host keys"
+msgstr ""
+
+msgid "DevOps Score"
+msgstr ""
+
+msgid "Diff content limits"
+msgstr ""
+
+msgid "Diff limits"
+msgstr ""
+
+msgid "Difference between start date and now"
+msgstr ""
+
+msgid "DiffsCompareBaseBranch|(HEAD)"
+msgstr ""
+
+msgid "DiffsCompareBaseBranch|(base)"
+msgstr ""
+
+msgid "Diffs|No file name available"
+msgstr ""
+
+msgid "Diffs|Show unchanged lines"
+msgstr ""
+
+msgid "Diffs|Something went wrong while fetching diff lines."
+msgstr ""
+
+msgid "Direction"
+msgstr ""
+
+msgid "Directory name"
+msgstr ""
+
+msgid "Disable"
+msgstr ""
+
+msgid "Disable for this project"
+msgstr ""
+
+msgid "Disable group Runners"
+msgstr ""
+
+msgid "Disable public access to Pages sites"
+msgstr ""
+
+msgid "Disable shared Runners"
+msgstr ""
+
+msgid "Disable two-factor authentication"
+msgstr ""
+
+msgid "Disabled"
+msgstr ""
+
+msgid "Disabled mirrors can only be enabled by instance owners. It is recommended that you delete them."
+msgstr ""
+
+msgid "Discard all changes"
+msgstr ""
+
+msgid "Discard all changes?"
+msgstr ""
+
+msgid "Discard changes"
+msgstr ""
+
+msgid "Discard changes to %{path}?"
+msgstr ""
+
+msgid "Discard draft"
+msgstr ""
+
+msgid "Discard review"
+msgstr ""
+
+msgid "DiscordService|Discord Notifications"
+msgstr ""
+
+msgid "DiscordService|Receive event notifications in Discord"
+msgstr ""
+
+msgid "Discover GitLab Geo"
+msgstr ""
+
+msgid "Discover projects, groups and snippets. Share your projects with others"
+msgstr ""
+
+msgid "Discover|Check your application for security vulnerabilities that may lead to unauthorized access, data leaks, and denial of services."
+msgstr ""
+
+msgid "Discover|For code that's already live in production, our dashboards give you an easy way to prioritize any issues that are found, empowering your team to ship quickly and securely."
+msgstr ""
+
+msgid "Discover|GitLab will perform static and dynamic tests on the code of your application, looking for known flaws and report them in the merge request so you can fix them before merging."
+msgstr ""
+
+msgid "Discover|Give feedback for this page"
+msgstr ""
+
+msgid "Discover|Security capabilities, integrated into your development lifecycle"
+msgstr ""
+
+msgid "Discover|See the other features of the %{linkStart}gold plan%{linkEnd}"
+msgstr ""
+
+msgid "Discover|Start a free trial"
+msgstr ""
+
+msgid "Discover|Upgrade now"
+msgstr ""
+
+msgid "Discuss a specific suggestion or question"
+msgstr ""
+
+msgid "Discuss a specific suggestion or question that needs to be resolved"
+msgstr ""
+
+msgid "Discuss a specific suggestion or question that needs to be resolved."
+msgstr ""
+
+msgid "Discuss a specific suggestion or question."
+msgstr ""
+
+msgid "Discussion"
+msgstr ""
+
+msgid "Discussion to reply to cannot be found"
+msgstr ""
+
+msgid "Disk Usage"
+msgstr ""
+
+msgid "Dismiss"
+msgstr ""
+
+msgid "Dismiss %d selected vulnerability as"
+msgid_plural "Dismiss %d selected vulnerabilities as"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "Dismiss DevOps Score introduction"
+msgstr ""
+
+msgid "Dismiss Merge Request promotion"
+msgstr ""
+
+msgid "Dismiss Selected"
+msgstr ""
+
+msgid "Dismiss Value Stream Analytics introduction box"
+msgstr ""
+
+msgid "Dismiss trial promotion"
+msgstr ""
+
+msgid "Dismissable"
+msgstr ""
+
+msgid "Dismissed"
+msgstr ""
+
+msgid "Dismissed at %{projectLink}"
+msgstr ""
+
+msgid "Dismissed on pipeline %{pipelineLink}"
+msgstr ""
+
+msgid "Dismissed on pipeline %{pipelineLink} at %{projectLink}"
+msgstr ""
+
+msgid "Display alerts from all your monitoring tools directly within GitLab."
+msgstr ""
+
+msgid "Display name"
+msgstr ""
+
+msgid "Display rendered file"
+msgstr ""
+
+msgid "Display source"
+msgstr ""
+
+msgid "Do not display offers from third parties within GitLab"
+msgstr ""
+
+msgid "Do you want to customize how Google Code email addresses and usernames are imported into GitLab?"
+msgstr ""
+
+msgid "Dockerfile"
+msgstr ""
+
+msgid "Documentation"
+msgstr ""
+
+msgid "Documentation for popular identity providers"
+msgstr ""
+
+msgid "Doing"
+msgstr ""
+
+msgid "Domain"
+msgstr ""
+
+msgid "Domain cannot be deleted while associated to one or more clusters."
+msgstr ""
+
+msgid "Domain verification is an essential security measure for public GitLab sites. Users are required to demonstrate they control a domain before it is enabled"
+msgstr ""
+
+msgid "Domain was successfully created."
+msgstr ""
+
+msgid "Domain was successfully deleted."
+msgstr ""
+
+msgid "Domain was successfully updated."
+msgstr ""
+
+msgid "Don't have an account yet?"
+msgstr ""
+
+msgid "Don't include description in commit message"
+msgstr ""
+
+msgid "Don't paste the private part of the GPG key. Paste the public part which begins with '-----BEGIN PGP PUBLIC KEY BLOCK-----'."
+msgstr ""
+
+msgid "Don't show again"
+msgstr ""
+
+msgid "Done"
+msgstr ""
+
+msgid "Download"
+msgstr ""
+
+msgid "Download %{format}"
+msgstr ""
+
+msgid "Download %{format}:"
+msgstr ""
+
+msgid "Download CSV"
+msgstr ""
+
+msgid "Download artifacts"
+msgstr ""
+
+msgid "Download as"
+msgstr ""
+
+msgid "Download asset"
+msgstr ""
+
+msgid "Download codes"
+msgstr ""
+
+msgid "Download evidence JSON"
+msgstr ""
+
+msgid "Download export"
+msgstr ""
+
+msgid "Download image"
+msgstr ""
+
+msgid "Download license"
+msgstr ""
+
+msgid "Download raw data (.csv)"
+msgstr ""
+
+msgid "Download source code"
+msgstr ""
+
+msgid "Download this directory"
+msgstr ""
+
+msgid "DownloadCommit|Email Patches"
+msgstr ""
+
+msgid "DownloadCommit|Plain Diff"
+msgstr ""
+
+msgid "DownloadSource|Download"
+msgstr ""
+
+msgid "Downstream"
+msgstr ""
+
+msgid "Downvotes"
+msgstr ""
+
+msgid "Drop your designs to start your upload."
+msgstr ""
+
+msgid "Due Date"
+msgstr ""
+
+msgid "Due date"
+msgstr ""
+
+msgid "Duration"
+msgstr ""
+
+msgid "Duration for the last 30 commits"
+msgstr ""
+
+msgid "During this process, you’ll be asked for URLs from GitLab’s side. Use the URLs shown below."
+msgstr ""
+
+msgid "Dynamic Application Security Testing (DAST)"
+msgstr ""
+
+msgid "Each Runner can be in one of the following states and/or belong to one of the following types:"
+msgstr ""
+
+msgid "Each Runner can be in one of the following states:"
+msgstr ""
+
+msgid "Edit"
+msgstr ""
+
+msgid "Edit %{issuable}"
+msgstr ""
+
+msgid "Edit %{name}"
+msgstr ""
+
+msgid "Edit Comment"
+msgstr ""
+
+msgid "Edit Deploy Key"
+msgstr ""
+
+msgid "Edit Geo Node"
+msgstr ""
+
+msgid "Edit Group Hook"
+msgstr ""
+
+msgid "Edit Label"
+msgstr ""
+
+msgid "Edit Milestone"
+msgstr ""
+
+msgid "Edit Password"
+msgstr ""
+
+msgid "Edit Pipeline Schedule %{id}"
+msgstr ""
+
+msgid "Edit Release"
+msgstr ""
+
+msgid "Edit Slack integration"
+msgstr ""
+
+msgid "Edit Snippet"
+msgstr ""
+
+msgid "Edit System Hook"
+msgstr ""
+
+msgid "Edit application"
+msgstr ""
+
+msgid "Edit board"
+msgstr ""
+
+msgid "Edit comment"
+msgstr ""
+
+msgid "Edit dashboard"
+msgstr ""
+
+msgid "Edit description"
+msgstr ""
+
+msgid "Edit environment"
+msgstr ""
+
+msgid "Edit file"
+msgstr ""
+
+msgid "Edit files in the editor and commit changes here"
+msgstr ""
+
+msgid "Edit group: %{group_name}"
+msgstr ""
+
+msgid "Edit identity for %{user_name}"
+msgstr ""
+
+msgid "Edit issues"
+msgstr ""
+
+msgid "Edit iteration"
+msgstr ""
+
+msgid "Edit public deploy key"
+msgstr ""
+
+msgid "Edit stage"
+msgstr ""
+
+msgid "Edit this release"
+msgstr ""
+
+msgid "Edit wiki page"
+msgstr ""
+
+msgid "Edit your most recent comment in a thread (from an empty textarea)"
+msgstr ""
+
+msgid "Edited %{timeago}"
+msgstr ""
+
+msgid "Editing"
+msgstr ""
+
+msgid "Elasticsearch"
+msgstr ""
+
+msgid "Elasticsearch AWS IAM credentials"
+msgstr ""
+
+msgid "Elasticsearch indexing restrictions"
+msgstr ""
+
+msgid "Elasticsearch indexing started"
+msgstr ""
+
+msgid "Elasticsearch integration. Elasticsearch AWS IAM."
+msgstr ""
+
+msgid "Elasticsearch returned status code: %{status_code}"
+msgstr ""
+
+msgid "Elastic|None. Select namespaces to index."
+msgstr ""
+
+msgid "Elastic|None. Select projects to index."
+msgstr ""
+
+msgid "Email"
+msgstr ""
+
+msgid "Email Notification"
+msgstr ""
+
+msgid "Email address"
+msgstr ""
+
+msgid "Email could not be sent"
+msgstr ""
+
+msgid "Email display name"
+msgstr ""
+
+msgid "Email not verified. Please verify your email in Salesforce."
+msgstr ""
+
+msgid "Email notification for unknown sign-ins"
+msgstr ""
+
+msgid "Email patch"
+msgstr ""
+
+msgid "Email restrictions"
+msgstr ""
+
+msgid "Email restrictions for sign-ups"
+msgstr ""
+
+msgid "Email sent"
+msgstr ""
+
+msgid "Email the pipelines status to a list of recipients."
+msgstr ""
+
+msgid "EmailError|It appears that the email is blank. Make sure your reply is at the top of the email, we can't process inline replies."
+msgstr ""
+
+msgid "EmailError|The thread you are replying to no longer exists, perhaps it was deleted? If you believe this is in error, contact a staff member."
+msgstr ""
+
+msgid "EmailError|We couldn't figure out what the email is for. Please create your issue or comment through the web interface."
+msgstr ""
+
+msgid "EmailError|We couldn't figure out what the email is in reply to. Please create your comment through the web interface."
+msgstr ""
+
+msgid "EmailError|We couldn't figure out what user corresponds to the email. Please create your comment through the web interface."
+msgstr ""
+
+msgid "EmailError|We couldn't find the project. Please check if there's any typo."
+msgstr ""
+
+msgid "EmailError|You are not allowed to perform this action. If you believe this is in error, contact a staff member."
+msgstr ""
+
+msgid "EmailError|Your account has been blocked. If you believe this is in error, contact a staff member."
+msgstr ""
+
+msgid "EmailToken|reset it"
+msgstr ""
+
+msgid "EmailToken|resetting..."
+msgstr ""
+
+msgid "Emails"
+msgstr ""
+
+msgid "Emails sent from Service Desk will have this name"
+msgstr ""
+
+msgid "Emails separated by comma"
+msgstr ""
+
+msgid "EmailsOnPushService|Disable code diffs"
+msgstr ""
+
+msgid "EmailsOnPushService|Don't include possibly sensitive code diffs in notification body."
+msgstr ""
+
+msgid "EmailsOnPushService|Email the commits and diff of each push to a list of recipients."
+msgstr ""
+
+msgid "EmailsOnPushService|Emails on push"
+msgstr ""
+
+msgid "EmailsOnPushService|Emails separated by whitespace"
+msgstr ""
+
+msgid "EmailsOnPushService|Send from committer"
+msgstr ""
+
+msgid "EmailsOnPushService|Send notifications from the committer's email address if the domain is part of the domain GitLab is running on (e.g. %{domains})."
+msgstr ""
+
+msgid "Embed"
+msgstr ""
+
+msgid "Empty file"
+msgstr ""
+
+msgid "Enable"
+msgstr ""
+
+msgid "Enable Auto DevOps"
+msgstr ""
+
+msgid "Enable HTML emails"
+msgstr ""
+
+msgid "Enable Incident Management inbound alert limit"
+msgstr ""
+
+msgid "Enable PlantUML"
+msgstr ""
+
+msgid "Enable Pseudonymizer data collection"
+msgstr ""
+
+msgid "Enable SAML authentication for this group"
+msgstr ""
+
+msgid "Enable Seat Link"
+msgstr ""
+
+msgid "Enable Spam Check via external API endpoint"
+msgstr ""
+
+msgid "Enable access to Grafana"
+msgstr ""
+
+msgid "Enable access to the Performance Bar for a given group."
+msgstr ""
+
+msgid "Enable and configure Grafana."
+msgstr ""
+
+msgid "Enable and configure Prometheus metrics."
+msgstr ""
+
+msgid "Enable classification control using an external service"
+msgstr ""
+
+msgid "Enable container expiration and retention policies for projects created earlier than GitLab 12.7."
+msgstr ""
+
+msgid "Enable email restrictions for sign ups"
+msgstr ""
+
+msgid "Enable error tracking"
+msgstr ""
+
+msgid "Enable feature to choose access level"
+msgstr ""
+
+msgid "Enable for this project"
+msgstr ""
+
+msgid "Enable group Runners"
+msgstr ""
+
+msgid "Enable header and footer in emails"
+msgstr ""
+
+msgid "Enable integration"
+msgstr ""
+
+msgid "Enable maintenance mode"
+msgstr ""
+
+msgid "Enable mirror configuration"
+msgstr ""
+
+msgid "Enable or disable Seat Link."
+msgstr ""
+
+msgid "Enable or disable keyboard shortcuts"
+msgstr ""
+
+msgid "Enable or disable the Pseudonymizer data collection."
+msgstr ""
+
+msgid "Enable or disable version check and usage ping."
+msgstr ""
+
+msgid "Enable protected paths rate limit"
+msgstr ""
+
+msgid "Enable proxy"
+msgstr ""
+
+msgid "Enable reCAPTCHA or Akismet and set IP limits. For reCAPTCHA, we currently only support %{recaptcha_v2_link_start}v2%{recaptcha_v2_link_end}"
+msgstr ""
+
+msgid "Enable shared Runners"
+msgstr ""
+
+msgid "Enable snowplow tracking"
+msgstr ""
+
+msgid "Enable two-factor authentication"
+msgstr ""
+
+msgid "Enable usage ping"
+msgstr ""
+
+msgid "Enable usage ping to get an overview of how you are using GitLab from a feature perspective."
+msgstr ""
+
+msgid "Enable/disable your service desk. %{link_start}Learn more about service desk%{link_end}."
+msgstr ""
+
+msgid "EnableReviewApp|%{stepStart}Step 1%{stepEnd}. Ensure you have Kubernetes set up and have a base domain for your %{linkStart}cluster%{linkEnd}."
+msgstr ""
+
+msgid "EnableReviewApp|%{stepStart}Step 2%{stepEnd}. Copy the following snippet:"
+msgstr ""
+
+msgid "EnableReviewApp|%{stepStart}Step 3%{stepEnd}. Add it to the project %{linkStart}gitlab-ci.yml%{linkEnd} file."
+msgstr ""
+
+msgid "EnableReviewApp|Close"
+msgstr ""
+
+msgid "EnableReviewApp|Copy snippet text"
+msgstr ""
+
+msgid "Enabled"
+msgstr ""
+
+msgid "Enabled Git access protocols"
+msgstr ""
+
+msgid "Enabled sources for code import during project creation. OmniAuth must be configured for GitHub"
+msgstr ""
+
+msgid "Enabling this will only make licensed EE features available to projects if the project namespace's plan includes the feature or if the project is public."
+msgstr ""
+
+msgid "Encountered an error while rendering: %{err}"
+msgstr ""
+
+msgid "End date"
+msgstr ""
+
+msgid "Ends at (UTC)"
+msgstr ""
+
+msgid "Enforce DNS rebinding attack protection"
+msgstr ""
+
+msgid "Enforce personal access token expiration"
+msgstr ""
+
+msgid "Ensure connectivity is available from the GitLab server to the Prometheus server"
+msgstr ""
+
+msgid "Ensure your %{linkStart}environment is part of the deploy stage%{linkEnd} of your CI pipeline to track deployments to your cluster."
+msgstr ""
+
+msgid "Enter 2FA for Admin Mode"
+msgstr ""
+
+msgid "Enter Admin Mode"
+msgstr ""
+
+msgid "Enter IP address range"
+msgstr ""
+
+msgid "Enter a number"
+msgstr ""
+
+msgid "Enter a whole number between 0 and 100"
+msgstr ""
+
+msgid "Enter at least three characters to search"
+msgstr ""
+
+msgid "Enter board name"
+msgstr ""
+
+msgid "Enter domain"
+msgstr ""
+
+msgid "Enter in your Bitbucket Server URL and personal access token below"
+msgstr ""
+
+msgid "Enter in your Phabricator Server URL and personal access token below"
+msgstr ""
+
+msgid "Enter merge request URLs"
+msgstr ""
+
+msgid "Enter new %{field_title}"
+msgstr ""
+
+msgid "Enter new AWS Secret Access Key"
+msgstr ""
+
+msgid "Enter number of issues"
+msgstr ""
+
+msgid "Enter one or more user ID separated by commas"
+msgstr ""
+
+msgid "Enter the code from the two-factor app on your mobile device. If you've lost your device, you may enter one of your recovery codes."
+msgstr ""
+
+msgid "Enter the issue description"
+msgstr ""
+
+msgid "Enter the issue title"
+msgstr ""
+
+msgid "Enter the merge request description"
+msgstr ""
+
+msgid "Enter the merge request title"
+msgstr ""
+
+msgid "Enter the name of your application, and we'll return a unique %{type}."
+msgstr ""
+
+msgid "Enter weights for storages for new repositories."
+msgstr ""
+
+msgid "Enter your password to approve"
+msgstr ""
+
+msgid "Environment"
+msgstr ""
+
+msgid "Environment does not have deployments"
+msgstr ""
+
+msgid "Environment is required for Stages::MetricEndpointInserter"
+msgstr ""
+
+msgid "Environment is required for Stages::VariableEndpointInserter"
+msgstr ""
+
+msgid "Environment scope"
+msgstr ""
+
+msgid "Environment variables are applied to all project environments in this instance via the Runner. You can use environment variables for passwords, secret keys, etc. Make variables available to the running application by prepending the variable key with <code>K8S_SECRET_</code>. You can set variables to be:"
+msgstr ""
+
+msgid "Environment variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. Additionally, they can be masked so they are hidden in job logs, though they must match certain regexp requirements to do so. You can use environment variables for passwords, secret keys, or whatever you want."
+msgstr ""
+
+msgid "Environment variables are configured by your administrator to be %{link_start}protected%{link_end} by default"
+msgstr ""
+
+msgid "Environment variables on this GitLab instance are configured to be %{link_start}protected%{link_end} by default"
+msgstr ""
+
+msgid "Environment:"
+msgstr ""
+
+msgid "EnvironmentDashboard|API"
+msgstr ""
+
+msgid "EnvironmentDashboard|Created through the Deployment API"
+msgstr ""
+
+msgid "EnvironmentDashboard|You are looking at the last updated environment"
+msgstr ""
+
+msgid "Environments"
+msgstr ""
+
+msgid "Environments Dashboard"
+msgstr ""
+
+msgid "Environments allow you to track deployments of your application %{link_to_read_more}."
+msgstr ""
+
+msgid "Environments in %{name}"
+msgstr ""
+
+msgid "EnvironmentsDashboard|Add a project to the dashboard"
+msgstr ""
+
+msgid "EnvironmentsDashboard|Add projects"
+msgstr ""
+
+msgid "EnvironmentsDashboard|Environments Dashboard"
+msgstr ""
+
+msgid "EnvironmentsDashboard|Job: %{job}"
+msgstr ""
+
+msgid "EnvironmentsDashboard|More actions"
+msgstr ""
+
+msgid "EnvironmentsDashboard|More information"
+msgstr ""
+
+msgid "EnvironmentsDashboard|Remove"
+msgstr ""
+
+msgid "EnvironmentsDashboard|The environments dashboard provides a summary of each project's environments' status, including pipeline and alert statuses."
+msgstr ""
+
+msgid "EnvironmentsDashboard|This dashboard displays a maximum of 7 projects and 3 environments per project. %{readMoreLink}"
+msgstr ""
+
+msgid "Environments|An error occurred while canceling the auto stop, please try again"
+msgstr ""
+
+msgid "Environments|An error occurred while deleting the environment. Check if the environment stopped; if not, stop it and try again."
+msgstr ""
+
+msgid "Environments|An error occurred while fetching the environments."
+msgstr ""
+
+msgid "Environments|An error occurred while making the request."
+msgstr ""
+
+msgid "Environments|An error occurred while re-deploying the environment, please try again"
+msgstr ""
+
+msgid "Environments|An error occurred while rolling back the environment, please try again"
+msgstr ""
+
+msgid "Environments|An error occurred while stopping the environment, please try again"
+msgstr ""
+
+msgid "Environments|Are you sure you want to stop this environment?"
+msgstr ""
+
+msgid "Environments|Auto stop in"
+msgstr ""
+
+msgid "Environments|Auto stops %{auto_stop_time}"
+msgstr ""
+
+msgid "Environments|Commit"
+msgstr ""
+
+msgid "Environments|Currently showing %{fetched} results."
+msgstr ""
+
+msgid "Environments|Currently showing all results."
+msgstr ""
+
+msgid "Environments|Delete"
+msgstr ""
+
+msgid "Environments|Delete environment"
+msgstr ""
+
+msgid "Environments|Deleting the '%{environmentName}' environment cannot be undone. Do you want to delete it anyway?"
+msgstr ""
+
+msgid "Environments|Deploy to..."
+msgstr ""
+
+msgid "Environments|Deployment"
+msgstr ""
+
+msgid "Environments|Enable review app"
+msgstr ""
+
+msgid "Environments|Environment"
+msgstr ""
+
+msgid "Environments|Environments"
+msgstr ""
+
+msgid "Environments|Environments are places where code gets deployed, such as staging or production."
+msgstr ""
+
+msgid "Environments|Install Elastic Stack on your cluster to enable advanced querying capabilities such as full text search."
+msgstr ""
+
+msgid "Environments|Job"
+msgstr ""
+
+msgid "Environments|Learn about environments"
+msgstr ""
+
+msgid "Environments|Learn more about stopping environments"
+msgstr ""
+
+msgid "Environments|Logs from %{start} to %{end}."
+msgstr ""
+
+msgid "Environments|New environment"
+msgstr ""
+
+msgid "Environments|No deployed environments"
+msgstr ""
+
+msgid "Environments|No deployments yet"
+msgstr ""
+
+msgid "Environments|No pod selected"
+msgstr ""
+
+msgid "Environments|No pods to display"
+msgstr ""
+
+msgid "Environments|Note that this action will stop the environment, but it will %{emphasisStart}not%{emphasisEnd} have an effect on any existing deployment due to no “stop environment action†being defined in the %{ciConfigLinkStart}.gitlab-ci.yml%{ciConfigLinkEnd} file."
+msgstr ""
+
+msgid "Environments|Note that this action will stop the environment, but it will %{emphasis_start}not%{emphasis_end} have an effect on any existing deployment due to no “stop environment action†being defined in the %{ci_config_link_start}.gitlab-ci.yml%{ci_config_link_end} file."
+msgstr ""
+
+msgid "Environments|Open live environment"
+msgstr ""
+
+msgid "Environments|Pod name"
+msgstr ""
+
+msgid "Environments|Re-deploy"
+msgstr ""
+
+msgid "Environments|Re-deploy environment %{environment_name}?"
+msgstr ""
+
+msgid "Environments|Re-deploy environment %{name}?"
+msgstr ""
+
+msgid "Environments|Re-deploy to environment"
+msgstr ""
+
+msgid "Environments|Read more about environments"
+msgstr ""
+
+msgid "Environments|Rollback"
+msgstr ""
+
+msgid "Environments|Rollback environment"
+msgstr ""
+
+msgid "Environments|Rollback environment %{environment_name}?"
+msgstr ""
+
+msgid "Environments|Rollback environment %{name}?"
+msgstr ""
+
+msgid "Environments|Select environment"
+msgstr ""
+
+msgid "Environments|Select pod"
+msgstr ""
+
+msgid "Environments|Show all"
+msgstr ""
+
+msgid "Environments|Stop"
+msgstr ""
+
+msgid "Environments|Stop environment"
+msgstr ""
+
+msgid "Environments|Stopping"
+msgstr ""
+
+msgid "Environments|There was an error fetching the logs. Please try again."
+msgstr ""
+
+msgid "Environments|This action will relaunch the job for commit %{commit_id}, putting the environment in a previous version. Are you sure you want to continue?"
+msgstr ""
+
+msgid "Environments|This action will relaunch the job for commit %{linkStart}%{commitId}%{linkEnd}, putting the environment in a previous version. Are you sure you want to continue?"
+msgstr ""
+
+msgid "Environments|This action will run the job defined by %{environment_name} for commit %{commit_id}, putting the environment in a previous version. You can revert it by re-deploying the latest version of your application. Are you sure you want to continue?"
+msgstr ""
+
+msgid "Environments|This action will run the job defined by %{name} for commit %{linkStart}%{commitId}%{linkEnd} putting the environment in a previous version. You can revert it by re-deploying the latest version of your application. Are you sure you want to continue?"
+msgstr ""
+
+msgid "Environments|Updated"
+msgstr ""
+
+msgid "Environments|You don't have any environments right now"
+msgstr ""
+
+msgid "Environments|protected"
+msgstr ""
+
+msgid "Epic"
+msgstr ""
+
+msgid "Epic cannot be found."
+msgstr ""
+
+msgid "Epic events"
+msgstr ""
+
+msgid "Epics"
+msgstr ""
+
+msgid "Epics Roadmap"
+msgstr ""
+
+msgid "Epics and Issues"
+msgstr ""
+
+msgid "Epics let you manage your portfolio of projects more efficiently and with less effort"
+msgstr ""
+
+msgid "Epics, Issues, and Merge Requests"
+msgstr ""
+
+msgid "Epics|Add a new epic"
+msgstr ""
+
+msgid "Epics|Add an existing epic"
+msgstr ""
+
+msgid "Epics|An error occurred while saving the %{epicDateType} date"
+msgstr ""
+
+msgid "Epics|An error occurred while updating labels."
+msgstr ""
+
+msgid "Epics|Are you sure you want to remove %{bStart}%{targetIssueTitle}%{bEnd} from %{bStart}%{parentEpicTitle}%{bEnd}?"
+msgstr ""
+
+msgid "Epics|How can I solve this?"
+msgstr ""
+
+msgid "Epics|More information"
+msgstr ""
+
+msgid "Epics|Remove epic"
+msgstr ""
+
+msgid "Epics|Remove issue"
+msgstr ""
+
+msgid "Epics|Show more"
+msgstr ""
+
+msgid "Epics|Something went wrong while assigning issue to epic."
+msgstr ""
+
+msgid "Epics|Something went wrong while creating child epics."
+msgstr ""
+
+msgid "Epics|Something went wrong while creating issue."
+msgstr ""
+
+msgid "Epics|Something went wrong while fetching child epics."
+msgstr ""
+
+msgid "Epics|Something went wrong while fetching group epics."
+msgstr ""
+
+msgid "Epics|Something went wrong while moving item."
+msgstr ""
+
+msgid "Epics|Something went wrong while ordering item."
+msgstr ""
+
+msgid "Epics|Something went wrong while removing issue from epic."
+msgstr ""
+
+msgid "Epics|These dates affect how your epics appear in the roadmap. Dates from milestones come from the milestones assigned to issues in the epic. You can also set fixed dates or remove them entirely."
+msgstr ""
+
+msgid "Epics|This will also remove any descendents of %{bStart}%{targetEpicTitle}%{bEnd} from %{bStart}%{parentEpicTitle}%{bEnd}. Are you sure?"
+msgstr ""
+
+msgid "Epics|To schedule your epic's %{epicDateType} date based on milestones, assign a milestone with a %{epicDateType} date to any issue in the epic."
+msgstr ""
+
+msgid "Epics|due"
+msgstr ""
+
+msgid "Epics|start"
+msgstr ""
+
+msgid "Error"
+msgstr ""
+
+msgid "Error Details"
+msgstr ""
+
+msgid "Error Tracking"
+msgstr ""
+
+msgid "Error creating epic"
+msgstr ""
+
+msgid "Error creating label."
+msgstr ""
+
+msgid "Error creating new iteration"
+msgstr ""
+
+msgid "Error creating repository for snippet with id %{snippet_id}"
+msgstr ""
+
+msgid "Error creating the snippet"
+msgstr ""
+
+msgid "Error deleting %{issuableType}"
+msgstr ""
+
+msgid "Error deleting project. Check logs for error details."
+msgstr ""
+
+msgid "Error fetching diverging counts for branches. Please try again."
+msgstr ""
+
+msgid "Error fetching forked projects. Please try again."
+msgstr ""
+
+msgid "Error fetching labels."
+msgstr ""
+
+msgid "Error fetching network graph."
+msgstr ""
+
+msgid "Error fetching payload data."
+msgstr ""
+
+msgid "Error fetching projects"
+msgstr ""
+
+msgid "Error fetching refs"
+msgstr ""
+
+msgid "Error fetching the dependency list. Please check your network connection and try again."
+msgstr ""
+
+msgid "Error loading branch data. Please try again."
+msgstr ""
+
+msgid "Error loading branches."
+msgstr ""
+
+msgid "Error loading burndown chart data"
+msgstr ""
+
+msgid "Error loading countries data."
+msgstr ""
+
+msgid "Error loading file viewer."
+msgstr ""
+
+msgid "Error loading last commit."
+msgstr ""
+
+msgid "Error loading markdown preview"
+msgstr ""
+
+msgid "Error loading merge requests."
+msgstr ""
+
+msgid "Error loading milestone tab"
+msgstr ""
+
+msgid "Error loading project data. Please try again."
+msgstr ""
+
+msgid "Error loading template types."
+msgstr ""
+
+msgid "Error loading template."
+msgstr ""
+
+msgid "Error loading viewer"
+msgstr ""
+
+msgid "Error occurred when fetching sidebar data"
+msgstr ""
+
+msgid "Error occurred when saving assignees"
+msgstr ""
+
+msgid "Error occurred when toggling the notification subscription"
+msgstr ""
+
+msgid "Error occurred while updating the issue status"
+msgstr ""
+
+msgid "Error occurred while updating the issue weight"
+msgstr ""
+
+msgid "Error occurred. A blocked user cannot be deactivated"
+msgstr ""
+
+msgid "Error occurred. A blocked user must be unblocked to be activated"
+msgstr ""
+
+msgid "Error occurred. User was not blocked"
+msgstr ""
+
+msgid "Error occurred. User was not confirmed"
+msgstr ""
+
+msgid "Error occurred. User was not unblocked"
+msgstr ""
+
+msgid "Error occurred. User was not unlocked"
+msgstr ""
+
+msgid "Error rendering markdown preview"
+msgstr ""
+
+msgid "Error saving label update."
+msgstr ""
+
+msgid "Error setting up editor. Please try again."
+msgstr ""
+
+msgid "Error updating %{issuableType}"
+msgstr ""
+
+msgid "Error updating status for all to-do items."
+msgstr ""
+
+msgid "Error updating status of to-do item."
+msgstr ""
+
+msgid "Error updating the snippet"
+msgstr ""
+
+msgid "Error uploading file"
+msgstr ""
+
+msgid "Error uploading file: %{stripped}"
+msgstr ""
+
+msgid "Error while loading the merge request. Please try again."
+msgstr ""
+
+msgid "Error while loading the project data. Please try again."
+msgstr ""
+
+msgid "Error while migrating %{upload_id}: %{error_message}"
+msgstr ""
+
+msgid "Error with Akismet. Please check the logs for more info."
+msgstr ""
+
+msgid "ErrorTracking|Active"
+msgstr ""
+
+msgid "ErrorTracking|After adding your Auth Token, use the 'Connect' button to load projects"
+msgstr ""
+
+msgid "ErrorTracking|Auth Token"
+msgstr ""
+
+msgid "ErrorTracking|Click 'Connect' to re-establish the connection to Sentry and activate the dropdown."
+msgstr ""
+
+msgid "ErrorTracking|Connection has failed. Re-check Auth Token and try again."
+msgstr ""
+
+msgid "ErrorTracking|If you self-host Sentry, enter the full URL of your Sentry instance. If you're using Sentry's hosted solution, enter https://sentry.io"
+msgstr ""
+
+msgid "ErrorTracking|No projects available"
+msgstr ""
+
+msgid "ErrorTracking|Select project"
+msgstr ""
+
+msgid "ErrorTracking|To enable project selection, enter a valid Auth Token"
+msgstr ""
+
+msgid "Errors"
+msgstr ""
+
+msgid "Errors:"
+msgstr ""
+
+msgid "Estimate"
+msgstr ""
+
+msgid "Estimated"
+msgstr ""
+
+msgid "EventFilterBy|Filter by all"
+msgstr ""
+
+msgid "EventFilterBy|Filter by comments"
+msgstr ""
+
+msgid "EventFilterBy|Filter by designs"
+msgstr ""
+
+msgid "EventFilterBy|Filter by epic events"
+msgstr ""
+
+msgid "EventFilterBy|Filter by issue events"
+msgstr ""
+
+msgid "EventFilterBy|Filter by merge events"
+msgstr ""
+
+msgid "EventFilterBy|Filter by push events"
+msgstr ""
+
+msgid "EventFilterBy|Filter by team"
+msgstr ""
+
+msgid "EventFilterBy|Filter by wiki"
+msgstr ""
+
+msgid "Events"
+msgstr ""
+
+msgid "Events in %{group_name}"
+msgstr ""
+
+msgid "Events in %{project_path}"
+msgstr ""
+
+msgid "Every %{action} attempt has failed: %{job_error_message}. Please try again."
+msgstr ""
+
+msgid "Every day"
+msgstr ""
+
+msgid "Every day (at %{time})"
+msgstr ""
+
+msgid "Every month"
+msgstr ""
+
+msgid "Every month (Day %{day} at %{time})"
+msgstr ""
+
+msgid "Every three months"
+msgstr ""
+
+msgid "Every two weeks"
+msgstr ""
+
+msgid "Every week"
+msgstr ""
+
+msgid "Every week (%{weekday} at %{time})"
+msgstr ""
+
+msgid "Everyone"
+msgstr ""
+
+msgid "Everyone With Access"
+msgstr ""
+
+msgid "Everyone can contribute"
+msgstr ""
+
+msgid "Everything on your to-do list is marked as done."
+msgstr ""
+
+msgid "Everything you need to create a GitLab Pages site using Gatsby."
+msgstr ""
+
+msgid "Everything you need to create a GitLab Pages site using GitBook."
+msgstr ""
+
+msgid "Everything you need to create a GitLab Pages site using Hexo."
+msgstr ""
+
+msgid "Everything you need to create a GitLab Pages site using Hugo."
+msgstr ""
+
+msgid "Everything you need to create a GitLab Pages site using Jekyll."
+msgstr ""
+
+msgid "Everything you need to create a GitLab Pages site using plain HTML."
+msgstr ""
+
+msgid "Evidence collection"
+msgstr ""
+
+msgid "Exactly one of %{attributes} is required"
+msgstr ""
+
+msgid "Example: <code>acme.com,acme.co.in,acme.uk</code>."
+msgstr ""
+
+msgid "Example: @sub\\.company\\.com$"
+msgstr ""
+
+msgid "Example: Usage = single query. (Requested) / (Capacity) = multiple queries combined into a formula."
+msgstr ""
+
+msgid "Except policy:"
+msgstr ""
+
+msgid "Excluding merge commits. Limited to %{limit} commits."
+msgstr ""
+
+msgid "Excluding merge commits. Limited to 6,000 commits."
+msgstr ""
+
+msgid "Existing members and groups"
+msgstr ""
+
+msgid "Existing projects may be moved into a group"
+msgstr ""
+
+msgid "Existing projects will be able to use expiration policies. Avoid enabling this if an external Container Registry is being used, as there is a performance risk if many images exist on one project."
+msgstr ""
+
+msgid "Existing shares"
+msgstr ""
+
+msgid "Existing sign in methods may be removed"
+msgstr ""
+
+msgid "Expand"
+msgstr ""
+
+msgid "Expand all"
+msgstr ""
+
+msgid "Expand approvers"
+msgstr ""
+
+msgid "Expand down"
+msgstr ""
+
+msgid "Expand dropdown"
+msgstr ""
+
+msgid "Expand milestones"
+msgstr ""
+
+msgid "Expand sidebar"
+msgstr ""
+
+msgid "Expand up"
+msgstr ""
+
+msgid "Experienced"
+msgstr ""
+
+msgid "Expiration"
+msgstr ""
+
+msgid "Expiration date"
+msgstr ""
+
+msgid "Expiration not enforced"
+msgstr ""
+
+msgid "Expiration policy for the Container Registry is a perfect solution for keeping the Registry space down while still enjoying the full power of GitLab CI/CD."
+msgstr ""
+
+msgid "Expired"
+msgstr ""
+
+msgid "Expired %{expiredOn}"
+msgstr ""
+
+msgid "Expired:"
+msgstr ""
+
+msgid "Expires"
+msgstr ""
+
+msgid "Expires at"
+msgstr ""
+
+msgid "Expires in %{expires_at}"
+msgstr ""
+
+msgid "Expires on"
+msgstr ""
+
+msgid "Expires:"
+msgstr ""
+
+msgid "Explain the problem. If appropriate, provide a link to the relevant issue or comment."
+msgstr ""
+
+msgid "Explore"
+msgstr ""
+
+msgid "Explore GitLab"
+msgstr ""
+
+msgid "Explore Groups"
+msgstr ""
+
+msgid "Explore groups"
+msgstr ""
+
+msgid "Explore projects"
+msgstr ""
+
+msgid "Explore public groups"
+msgstr ""
+
+msgid "Export as CSV"
+msgstr ""
+
+msgid "Export group"
+msgstr ""
+
+msgid "Export issues"
+msgstr ""
+
+msgid "Export project"
+msgstr ""
+
+msgid "Export this group with all related data to a new GitLab instance. Once complete, you can import the data file from the \"New Group\" page."
+msgstr ""
+
+msgid "Export this project with all its related data in order to move your project to a new GitLab instance. Once the export is finished, you can import the file from the \"New Project\" page."
+msgstr ""
+
+msgid "Export variable to pipelines running on protected branches and tags only."
+msgstr ""
+
+msgid "External Classification Policy Authorization"
+msgstr ""
+
+msgid "External ID"
+msgstr ""
+
+msgid "External URL"
+msgstr ""
+
+msgid "External Wiki"
+msgstr ""
+
+msgid "External authentication"
+msgstr ""
+
+msgid "External authorization denied access to this project"
+msgstr ""
+
+msgid "External authorization request timeout"
+msgstr ""
+
+msgid "External storage URL"
+msgstr ""
+
+msgid "External storage authentication token"
+msgstr ""
+
+msgid "ExternalAuthorizationService|Classification label"
+msgstr ""
+
+msgid "ExternalAuthorizationService|When no classification label is set the default label `%{default_label}` will be used."
+msgstr ""
+
+msgid "ExternalWikiService|External Wiki"
+msgstr ""
+
+msgid "ExternalWikiService|Replaces the link to the internal wiki with a link to an external wiki."
+msgstr ""
+
+msgid "ExternalWikiService|The URL of the external Wiki"
+msgstr ""
+
+msgid "Facebook"
+msgstr ""
+
+msgid "Failed"
+msgstr ""
+
+msgid "Failed Jobs"
+msgstr ""
+
+msgid "Failed on"
+msgstr ""
+
+msgid "Failed to add a Zoom meeting"
+msgstr ""
+
+msgid "Failed to apply commands."
+msgstr ""
+
+msgid "Failed to assign a user because no user was found."
+msgstr ""
+
+msgid "Failed to cancel auto stop because failed to update the environment."
+msgstr ""
+
+msgid "Failed to cancel auto stop because the environment is not set as auto stop."
+msgstr ""
+
+msgid "Failed to cancel auto stop because you do not have permission to update the environment."
+msgstr ""
+
+msgid "Failed to change the owner"
+msgstr ""
+
+msgid "Failed to check related branches."
+msgstr ""
+
+msgid "Failed to create Merge Request. Please try again."
+msgstr ""
+
+msgid "Failed to create a branch for this issue. Please try again."
+msgstr ""
+
+msgid "Failed to create import label for jira import."
+msgstr ""
+
+msgid "Failed to create repository"
+msgstr ""
+
+msgid "Failed to create resources"
+msgstr ""
+
+msgid "Failed to create wiki"
+msgstr ""
+
+msgid "Failed to delete board. Please try again."
+msgstr ""
+
+msgid "Failed to deploy to"
+msgstr ""
+
+msgid "Failed to enqueue the rebase operation, possibly due to a long-lived transaction. Try again later."
+msgstr ""
+
+msgid "Failed to find import label for Jira import."
+msgstr ""
+
+msgid "Failed to get ref."
+msgstr ""
+
+msgid "Failed to install."
+msgstr ""
+
+msgid "Failed to load assignees. Please try again."
+msgstr ""
+
+msgid "Failed to load authors. Please try again."
+msgstr ""
+
+msgid "Failed to load emoji list."
+msgstr ""
+
+msgid "Failed to load error details from Sentry."
+msgstr ""
+
+msgid "Failed to load errors from Sentry."
+msgstr ""
+
+msgid "Failed to load group activity metrics. Please try again."
+msgstr ""
+
+msgid "Failed to load groups & users."
+msgstr ""
+
+msgid "Failed to load labels. Please try again."
+msgstr ""
+
+msgid "Failed to load milestones. Please try again."
+msgstr ""
+
+msgid "Failed to load related branches"
+msgstr ""
+
+msgid "Failed to load stacktrace."
+msgstr ""
+
+msgid "Failed to mark this issue as a duplicate because referenced issue was not found."
+msgstr ""
+
+msgid "Failed to move this issue because label was not found."
+msgstr ""
+
+msgid "Failed to move this issue because only a single label can be provided."
+msgstr ""
+
+msgid "Failed to move this issue because target project doesn't exist."
+msgstr ""
+
+msgid "Failed to promote label due to internal error. Please contact administrators."
+msgstr ""
+
+msgid "Failed to protect the branch"
+msgstr ""
+
+msgid "Failed to protect the environment"
+msgstr ""
+
+msgid "Failed to publish issue on status page."
+msgstr ""
+
+msgid "Failed to remove a Zoom meeting"
+msgstr ""
+
+msgid "Failed to remove issue from board, please try again."
+msgstr ""
+
+msgid "Failed to remove mirror."
+msgstr ""
+
+msgid "Failed to remove the pipeline schedule"
+msgstr ""
+
+msgid "Failed to remove user identity."
+msgstr ""
+
+msgid "Failed to remove user key."
+msgstr ""
+
+msgid "Failed to reset key. Please try again."
+msgstr ""
+
+msgid "Failed to save merge conflicts resolutions. Please try again!"
+msgstr ""
+
+msgid "Failed to save new settings"
+msgstr ""
+
+msgid "Failed to save preferences (%{error_message})."
+msgstr ""
+
+msgid "Failed to save preferences."
+msgstr ""
+
+msgid "Failed to set due date because the date format is invalid."
+msgstr ""
+
+msgid "Failed to set iteration on this issue. Please try again."
+msgstr ""
+
+msgid "Failed to signing using smartcard authentication"
+msgstr ""
+
+msgid "Failed to update branch!"
+msgstr ""
+
+msgid "Failed to update environment!"
+msgstr ""
+
+msgid "Failed to update issue status"
+msgstr ""
+
+msgid "Failed to update issues, please try again."
+msgstr ""
+
+msgid "Failed to update tag!"
+msgstr ""
+
+msgid "Failed to update."
+msgstr ""
+
+msgid "Failed to upgrade."
+msgstr ""
+
+msgid "Failed to upload object map file"
+msgstr ""
+
+msgid "Failed to verify domain ownership"
+msgstr ""
+
+msgid "Failure"
+msgstr ""
+
+msgid "False positive"
+msgstr ""
+
+msgid "Fast-forward merge is not possible. Rebase the source branch onto %{targetBranch} to allow this merge request to be merged."
+msgstr ""
+
+msgid "Fast-forward merge is not possible. Rebase the source branch onto the target branch."
+msgstr ""
+
+msgid "Fast-forward merge without a merge commit"
+msgstr ""
+
+msgid "Faster as it re-uses the project workspace (falling back to clone if it doesn't exist)"
+msgstr ""
+
+msgid "Faster releases. Better code. Less pain."
+msgstr ""
+
+msgid "Favicon was successfully removed."
+msgstr ""
+
+msgid "Feature Flags"
+msgstr ""
+
+msgid "Feature flag was not removed."
+msgstr ""
+
+msgid "Feature flag was successfully removed."
+msgstr ""
+
+msgid "FeatureFlags|* (All Environments)"
+msgstr ""
+
+msgid "FeatureFlags|* (All environments)"
+msgstr ""
+
+msgid "FeatureFlags|API URL"
+msgstr ""
+
+msgid "FeatureFlags|Active"
+msgstr ""
+
+msgid "FeatureFlags|Add strategy"
+msgstr ""
+
+msgid "FeatureFlags|All users"
+msgstr ""
+
+msgid "FeatureFlags|Configure"
+msgstr ""
+
+msgid "FeatureFlags|Configure feature flags"
+msgstr ""
+
+msgid "FeatureFlags|Create feature flag"
+msgstr ""
+
+msgid "FeatureFlags|Delete %{name}?"
+msgstr ""
+
+msgid "FeatureFlags|Delete feature flag"
+msgstr ""
+
+msgid "FeatureFlags|Description"
+msgstr ""
+
+msgid "FeatureFlags|Edit Feature Flag"
+msgstr ""
+
+msgid "FeatureFlags|Edit Feature Flag User List"
+msgstr ""
+
+msgid "FeatureFlags|Edit list"
+msgstr ""
+
+msgid "FeatureFlags|Enable features for specific users and specific environments by defining feature flag strategies. By default, features are available to all users in all environments."
+msgstr ""
+
+msgid "FeatureFlags|Environment Spec"
+msgstr ""
+
+msgid "FeatureFlags|Environment Specs"
+msgstr ""
+
+msgid "FeatureFlags|Feature Flag"
+msgstr ""
+
+msgid "FeatureFlags|Feature Flag User List Details"
+msgstr ""
+
+msgid "FeatureFlags|Feature Flag behavior is built up by creating a set of rules to define the status of target environments. A default wildcard rule %{codeStart}*%{codeEnd} for %{boldStart}All Environments%{boldEnd} is set, and you are able to add as many rules as you need by choosing environment specs below. You can toggle the behavior for each of your rules to set them %{boldStart}Active%{boldEnd} or %{boldStart}Inactive%{boldEnd}."
+msgstr ""
+
+msgid "FeatureFlags|Feature Flag has no strategies"
+msgstr ""
+
+msgid "FeatureFlags|Feature Flags"
+msgstr ""
+
+msgid "FeatureFlags|Feature Flags will look different in the next milestone. No action is needed, but you may notice the functionality was changed to improve the workflow."
+msgstr ""
+
+msgid "FeatureFlags|Feature flag %{name} will be removed. Are you sure?"
+msgstr ""
+
+msgid "FeatureFlags|Feature flags allow you to configure your code into different flavors by dynamically toggling certain functionality."
+msgstr ""
+
+msgid "FeatureFlags|Flag becomes read only soon"
+msgstr ""
+
+msgid "FeatureFlags|Get started with feature flags"
+msgstr ""
+
+msgid "FeatureFlags|GitLab is moving to a new way of managing feature flags, and in 13.4, this feature flag will become read-only. Please create a new feature flag."
+msgstr ""
+
+msgid "FeatureFlags|ID"
+msgstr ""
+
+msgid "FeatureFlags|Inactive"
+msgstr ""
+
+msgid "FeatureFlags|Inactive flag for %{scope}"
+msgstr ""
+
+msgid "FeatureFlags|Include additional user IDs"
+msgstr ""
+
+msgid "FeatureFlags|Install a %{docs_link_anchored_start}compatible client library%{docs_link_anchored_end} and specify the API URL, application name, and instance ID during the configuration setup. %{docs_link_start}More Information%{docs_link_end}"
+msgstr ""
+
+msgid "FeatureFlags|Instance ID"
+msgstr ""
+
+msgid "FeatureFlags|List details"
+msgstr ""
+
+msgid "FeatureFlags|Loading feature flags"
+msgstr ""
+
+msgid "FeatureFlags|More information"
+msgstr ""
+
+msgid "FeatureFlags|Name"
+msgstr ""
+
+msgid "FeatureFlags|New"
+msgstr ""
+
+msgid "FeatureFlags|New Feature Flag"
+msgstr ""
+
+msgid "FeatureFlags|New Feature Flag User List"
+msgstr ""
+
+msgid "FeatureFlags|New feature flag"
+msgstr ""
+
+msgid "FeatureFlags|New list"
+msgstr ""
+
+msgid "FeatureFlags|Percent rollout (logged in users)"
+msgstr ""
+
+msgid "FeatureFlags|Percent rollout must be a whole number between 0 and 100"
+msgstr ""
+
+msgid "FeatureFlags|Protected"
+msgstr ""
+
+msgid "FeatureFlags|Remove"
+msgstr ""
+
+msgid "FeatureFlags|Rollout Percentage"
+msgstr ""
+
+msgid "FeatureFlags|Rollout Strategy"
+msgstr ""
+
+msgid "FeatureFlags|Status"
+msgstr ""
+
+msgid "FeatureFlags|Strategies"
+msgstr ""
+
+msgid "FeatureFlags|Target environments"
+msgstr ""
+
+msgid "FeatureFlags|There was an error fetching the feature flags."
+msgstr ""
+
+msgid "FeatureFlags|There was an error retrieving user lists"
+msgstr ""
+
+msgid "FeatureFlags|Try again in a few moments or contact your support team."
+msgstr ""
+
+msgid "FeatureFlags|User IDs"
+msgstr ""
+
+msgid "FeatureFlag|Delete strategy"
+msgstr ""
+
+msgid "FeatureFlag|List"
+msgstr ""
+
+msgid "FeatureFlag|Percentage"
+msgstr ""
+
+msgid "FeatureFlag|Select a user list"
+msgstr ""
+
+msgid "FeatureFlag|There are no configured user lists"
+msgstr ""
+
+msgid "FeatureFlag|Type"
+msgstr ""
+
+msgid "FeatureFlag|User IDs"
+msgstr ""
+
+msgid "Feb"
+msgstr ""
+
+msgid "February"
+msgstr ""
+
+msgid "Fetching incoming email"
+msgstr ""
+
+msgid "Fetching licenses failed."
+msgstr ""
+
+msgid "Fetching licenses failed. The request endpoint was not found."
+msgstr ""
+
+msgid "Fetching licenses failed. You are not permitted to perform this action."
+msgstr ""
+
+msgid "File"
+msgstr ""
+
+msgid "File Hooks"
+msgstr ""
+
+msgid "File Hooks (%{count})"
+msgstr ""
+
+msgid "File added"
+msgstr ""
+
+msgid "File browser"
+msgstr ""
+
+msgid "File deleted"
+msgstr ""
+
+msgid "File format is no longer supported"
+msgstr ""
+
+msgid "File hooks are similar to system hooks but are executed as files instead of sending data to a URL."
+msgstr ""
+
+msgid "File mode changed from %{a_mode} to %{b_mode}"
+msgstr ""
+
+msgid "File moved"
+msgstr ""
+
+msgid "File name"
+msgstr ""
+
+msgid "File renamed with no changes."
+msgstr ""
+
+msgid "File sync capacity"
+msgstr ""
+
+msgid "File templates"
+msgstr ""
+
+msgid "File upload error."
+msgstr ""
+
+msgid "Files"
+msgstr ""
+
+msgid "Files breadcrumb"
+msgstr ""
+
+msgid "Files, directories, and submodules in the path %{path} for commit reference %{ref}"
+msgstr ""
+
+msgid "Fill in the fields below, turn on <strong>%{enable_label}</strong>, and press <strong>%{save_changes}</strong>"
+msgstr ""
+
+msgid "Filter"
+msgstr ""
+
+msgid "Filter by %{issuable_type} that are currently closed."
+msgstr ""
+
+msgid "Filter by %{issuable_type} that are currently opened."
+msgstr ""
+
+msgid "Filter by %{page_context_word} that are currently opened."
+msgstr ""
+
+msgid "Filter by Git revision"
+msgstr ""
+
+msgid "Filter by commit message"
+msgstr ""
+
+msgid "Filter by issues that are currently closed."
+msgstr ""
+
+msgid "Filter by label"
+msgstr ""
+
+msgid "Filter by merge requests that are currently closed and unmerged."
+msgstr ""
+
+msgid "Filter by merge requests that are currently merged."
+msgstr ""
+
+msgid "Filter by milestone name"
+msgstr ""
+
+msgid "Filter by name"
+msgstr ""
+
+msgid "Filter by requirements that are currently archived."
+msgstr ""
+
+msgid "Filter by requirements that are currently opened."
+msgstr ""
+
+msgid "Filter by status"
+msgstr ""
+
+msgid "Filter by two-factor authentication"
+msgstr ""
+
+msgid "Filter by user"
+msgstr ""
+
+msgid "Filter pipelines"
+msgstr ""
+
+msgid "Filter projects"
+msgstr ""
+
+msgid "Filter results"
+msgstr ""
+
+msgid "Filter results by group"
+msgstr ""
+
+msgid "Filter results by project"
+msgstr ""
+
+msgid "Filter results..."
+msgstr ""
+
+msgid "Filter your projects by name"
+msgstr ""
+
+msgid "Filter..."
+msgstr ""
+
+msgid "Find File"
+msgstr ""
+
+msgid "Find bugs in your code with coverage-guided fuzzing"
+msgstr ""
+
+msgid "Find by path"
+msgstr ""
+
+msgid "Find existing members by name"
+msgstr ""
+
+msgid "Find file"
+msgstr ""
+
+msgid "Find the downloaded ZIP file and decompress it."
+msgstr ""
+
+msgid "Find the newly extracted <code>Takeout/Google Code Project Hosting/GoogleCodeProjectHosting.json</code> file."
+msgstr ""
+
+msgid "Fingerprint"
+msgstr ""
+
+msgid "Fingerprints"
+msgstr ""
+
+msgid "Finish editing this message first!"
+msgstr ""
+
+msgid "Finish review"
+msgstr ""
+
+msgid "Finish setting up your dedicated account for <strong>%{group_name}</strong>."
+msgstr ""
+
+msgid "Finished"
+msgstr ""
+
+msgid "First Name is too long (maximum is %{max_length} characters)."
+msgstr ""
+
+msgid "First Seen"
+msgstr ""
+
+msgid "First day of the week"
+msgstr ""
+
+msgid "First name"
+msgstr ""
+
+msgid "First seen"
+msgstr ""
+
+msgid "Fixed date"
+msgstr ""
+
+msgid "Fixed due date"
+msgstr ""
+
+msgid "Fixed start date"
+msgstr ""
+
+msgid "Fixed:"
+msgstr ""
+
+msgid "Flags"
+msgstr ""
+
+msgid "FlowdockService|Flowdock Git source token"
+msgstr ""
+
+msgid "FlowdockService|Flowdock is a collaboration web app for technical teams."
+msgstr ""
+
+msgid "FogBugz Email"
+msgstr ""
+
+msgid "FogBugz Import"
+msgstr ""
+
+msgid "FogBugz Password"
+msgstr ""
+
+msgid "FogBugz URL"
+msgstr ""
+
+msgid "FogBugz import"
+msgstr ""
+
+msgid "Folder/%{name}"
+msgstr ""
+
+msgid "Follow the steps below to export your Google Code project data."
+msgstr ""
+
+msgid "Font Color"
+msgstr ""
+
+msgid "Footer message"
+msgstr ""
+
+msgid "For each Jira issue successfully imported, we'll create a new GitLab issue with the following data:"
+msgstr ""
+
+msgid "For internal projects, any logged in user can view pipelines and access job details (output logs and artifacts)"
+msgstr ""
+
+msgid "For more info, read the documentation."
+msgstr ""
+
+msgid "For more information, go to the "
+msgstr ""
+
+msgid "For more information, please review %{link_start_tag}Jaeger's configuration doc%{link_end_tag}"
+msgstr ""
+
+msgid "For more information, see the File Hooks documentation."
+msgstr ""
+
+msgid "For more information, see the documentation on %{deactivating_usage_ping_link_start}deactivating the usage ping%{deactivating_usage_ping_link_end}."
+msgstr ""
+
+msgid "For more information, see the documentation on %{link_start}disabling Seat Link%{link_end}."
+msgstr ""
+
+msgid "For private projects, any member (guest or higher) can view pipelines and access job details (output logs and artifacts)"
+msgstr ""
+
+msgid "For public projects, anyone can view pipelines and access job details (output logs and artifacts)"
+msgstr ""
+
+msgid "Forgot your password?"
+msgstr ""
+
+msgid "Fork"
+msgstr ""
+
+msgid "Fork Error!"
+msgstr ""
+
+msgid "Fork project"
+msgstr ""
+
+msgid "Fork project?"
+msgstr ""
+
+msgid "ForkedFromProjectPath|Forked from"
+msgstr ""
+
+msgid "ForkedFromProjectPath|Forked from an inaccessible project"
+msgstr ""
+
+msgid "Forking in progress"
+msgstr ""
+
+msgid "Forks"
+msgstr ""
+
+msgid "Format"
+msgstr ""
+
+msgid "Format: %{dateFormat}"
+msgstr ""
+
+msgid "Forward external support email address to"
+msgstr ""
+
+msgid "Found errors in your %{gitlab_ci_yml}:"
+msgstr ""
+
+msgid "Found errors in your .gitlab-ci.yml:"
+msgstr ""
+
+msgid "Free Trial"
+msgstr ""
+
+msgid "Free Trial of GitLab.com Gold"
+msgstr ""
+
+msgid "Frequency"
+msgstr ""
+
+msgid "Friday"
+msgstr ""
+
+msgid "From"
+msgstr ""
+
+msgid "From %{providerTitle}"
+msgstr ""
+
+msgid "From <code>%{source_title}</code> into"
+msgstr ""
+
+msgid "From Bitbucket"
+msgstr ""
+
+msgid "From Bitbucket Server"
+msgstr ""
+
+msgid "From FogBugz"
+msgstr ""
+
+msgid "From GitLab.com"
+msgstr ""
+
+msgid "From Google Code"
+msgstr ""
+
+msgid "From issue creation until deploy to production"
+msgstr ""
+
+msgid "From merge request merge until deploy to production"
+msgstr ""
+
+msgid "From the Kubernetes cluster details view, install Runner from the applications list"
+msgstr ""
+
+msgid "Full name"
+msgstr ""
+
+msgid "GPG Key ID:"
+msgstr ""
+
+msgid "GPG Keys"
+msgstr ""
+
+msgid "GPG keys allow you to verify signed commits."
+msgstr ""
+
+msgid "GPG signature (loading...)"
+msgstr ""
+
+msgid "General"
+msgstr ""
+
+msgid "General Settings"
+msgstr ""
+
+msgid "General pipelines"
+msgstr ""
+
+msgid "Generate a default set of labels"
+msgstr ""
+
+msgid "Generate key"
+msgstr ""
+
+msgid "Generate new export"
+msgstr ""
+
+msgid "Geo"
+msgstr ""
+
+msgid "Geo Nodes"
+msgstr ""
+
+msgid "Geo Nodes|Cannot remove a primary node if there is a secondary node"
+msgstr ""
+
+msgid "Geo Replication"
+msgstr ""
+
+msgid "Geo Settings"
+msgstr ""
+
+msgid "Geo allows you to replicate your GitLab instance to other geographical locations."
+msgstr ""
+
+msgid "Geo nodes are paused using a command run on the node"
+msgstr ""
+
+msgid "GeoNodeStatusEvent|%{timeAgoStr} (%{pendingEvents} events)"
+msgstr ""
+
+msgid "GeoNodeSyncStatus|Node is failing or broken."
+msgstr ""
+
+msgid "GeoNodeSyncStatus|Node is slow, overloaded, or it just recovered after an outage."
+msgstr ""
+
+msgid "GeoNodes|Attachments"
+msgstr ""
+
+msgid "GeoNodes|Checksummed"
+msgstr ""
+
+msgid "GeoNodes|Consult Geo troubleshooting information"
+msgstr ""
+
+msgid "GeoNodes|Container repositories"
+msgstr ""
+
+msgid "GeoNodes|Data replication lag"
+msgstr ""
+
+msgid "GeoNodes|Design repositories"
+msgstr ""
+
+msgid "GeoNodes|Does not match the primary storage configuration"
+msgstr ""
+
+msgid "GeoNodes|Failed"
+msgstr ""
+
+msgid "GeoNodes|Full"
+msgstr ""
+
+msgid "GeoNodes|GitLab version"
+msgstr ""
+
+msgid "GeoNodes|GitLab version does not match the primary node version"
+msgstr ""
+
+msgid "GeoNodes|Health status"
+msgstr ""
+
+msgid "GeoNodes|Internal URL"
+msgstr ""
+
+msgid "GeoNodes|Job artifacts"
+msgstr ""
+
+msgid "GeoNodes|LFS objects"
+msgstr ""
+
+msgid "GeoNodes|Last event ID processed by cursor"
+msgstr ""
+
+msgid "GeoNodes|Last event ID seen from primary"
+msgstr ""
+
+msgid "GeoNodes|Learn more about Geo node statuses"
+msgstr ""
+
+msgid "GeoNodes|Loading nodes"
+msgstr ""
+
+msgid "GeoNodes|New node"
+msgstr ""
+
+msgid "GeoNodes|Node Authentication was successfully repaired."
+msgstr ""
+
+msgid "GeoNodes|Node URL"
+msgstr ""
+
+msgid "GeoNodes|Node was successfully removed."
+msgstr ""
+
+msgid "GeoNodes|Node's status was updated %{timeAgo}."
+msgstr ""
+
+msgid "GeoNodes|Not checksummed"
+msgstr ""
+
+msgid "GeoNodes|Package files"
+msgstr ""
+
+msgid "GeoNodes|Pausing replication stops the sync process. Are you sure?"
+msgstr ""
+
+msgid "GeoNodes|Removing a Geo primary node stops the synchronization to all nodes. Are you sure?"
+msgstr ""
+
+msgid "GeoNodes|Removing a Geo secondary node stops the synchronization to that node. Are you sure?"
+msgstr ""
+
+msgid "GeoNodes|Replicated data is verified with the %{nodeText} using checksums"
+msgstr ""
+
+msgid "GeoNodes|Replication slot WAL"
+msgstr ""
+
+msgid "GeoNodes|Replication slots"
+msgstr ""
+
+msgid "GeoNodes|Replication status"
+msgstr ""
+
+msgid "GeoNodes|Repositories"
+msgstr ""
+
+msgid "GeoNodes|Repository checksum progress"
+msgstr ""
+
+msgid "GeoNodes|Repository verification progress"
+msgstr ""
+
+msgid "GeoNodes|Selective (%{syncLabel})"
+msgstr ""
+
+msgid "GeoNodes|Selective synchronization"
+msgstr ""
+
+msgid "GeoNodes|Something went wrong while changing node status"
+msgstr ""
+
+msgid "GeoNodes|Something went wrong while fetching nodes"
+msgstr ""
+
+msgid "GeoNodes|Something went wrong while removing node"
+msgstr ""
+
+msgid "GeoNodes|Something went wrong while repairing node"
+msgstr ""
+
+msgid "GeoNodes|Storage config"
+msgstr ""
+
+msgid "GeoNodes|Sync settings"
+msgstr ""
+
+msgid "GeoNodes|Unused slots"
+msgstr ""
+
+msgid "GeoNodes|Unverified"
+msgstr ""
+
+msgid "GeoNodes|Updated %{timeAgo}"
+msgstr ""
+
+msgid "GeoNodes|Used slots"
+msgstr ""
+
+msgid "GeoNodes|Verified"
+msgstr ""
+
+msgid "GeoNodes|Wiki checksum progress"
+msgstr ""
+
+msgid "GeoNodes|Wiki verification progress"
+msgstr ""
+
+msgid "GeoNodes|Wikis"
+msgstr ""
+
+msgid "GeoNodes|With %{geo} you can install a special read-only and replicated instance anywhere. Before you add nodes, follow the %{instructions} in the exact order they appear."
+msgstr ""
+
+msgid "GeoNodes|You have configured Geo nodes using an insecure HTTP connection. We recommend the use of HTTPS."
+msgstr ""
+
+msgid "GeoNodes|primary node"
+msgstr ""
+
+msgid "GeoNodes|secondary nodes"
+msgstr ""
+
+msgid "Geo|%{label} can't be blank"
+msgstr ""
+
+msgid "Geo|%{label} should be between 1-999"
+msgstr ""
+
+msgid "Geo|%{name} is scheduled for forced re-download"
+msgstr ""
+
+msgid "Geo|%{name} is scheduled for re-sync"
+msgstr ""
+
+msgid "Geo|%{name} is scheduled for re-verify"
+msgstr ""
+
+msgid "Geo|Adjust your filters/search criteria above. If you believe this may be an error, please refer to the %{linkStart}Geo Troubleshooting%{linkEnd} documentation for more information."
+msgstr ""
+
+msgid "Geo|All %{replicable_name}"
+msgstr ""
+
+msgid "Geo|All projects"
+msgstr ""
+
+msgid "Geo|All projects are being scheduled for resync"
+msgstr ""
+
+msgid "Geo|All projects are being scheduled for reverify"
+msgstr ""
+
+msgid "Geo|Could not remove tracking entry for an existing project."
+msgstr ""
+
+msgid "Geo|Could not remove tracking entry for an existing upload."
+msgstr ""
+
+msgid "Geo|Failed"
+msgstr ""
+
+msgid "Geo|Filter by status"
+msgstr ""
+
+msgid "Geo|Geo Status"
+msgstr ""
+
+msgid "Geo|In progress"
+msgstr ""
+
+msgid "Geo|In sync"
+msgstr ""
+
+msgid "Geo|Last repository check run"
+msgstr ""
+
+msgid "Geo|Last successful sync"
+msgstr ""
+
+msgid "Geo|Last sync attempt"
+msgstr ""
+
+msgid "Geo|Last time verified"
+msgstr ""
+
+msgid "Geo|Never"
+msgstr ""
+
+msgid "Geo|Next sync scheduled at"
+msgstr ""
+
+msgid "Geo|Node name can't be blank"
+msgstr ""
+
+msgid "Geo|Node name should be between 1 and 255 characters"
+msgstr ""
+
+msgid "Geo|Not synced yet"
+msgstr ""
+
+msgid "Geo|Pending synchronization"
+msgstr ""
+
+msgid "Geo|Pending verification"
+msgstr ""
+
+msgid "Geo|Please refer to Geo Troubleshooting."
+msgstr ""
+
+msgid "Geo|Project"
+msgstr ""
+
+msgid "Geo|Project (ID: %{project_id}) no longer exists on the primary. It is safe to remove this entry, as this will not remove any data on disk."
+msgstr ""
+
+msgid "Geo|Projects in certain groups"
+msgstr ""
+
+msgid "Geo|Projects in certain storage shards"
+msgstr ""
+
+msgid "Geo|Redownload"
+msgstr ""
+
+msgid "Geo|Remove"
+msgstr ""
+
+msgid "Geo|Remove entry"
+msgstr ""
+
+msgid "Geo|Remove tracking database entry"
+msgstr ""
+
+msgid "Geo|Resync"
+msgstr ""
+
+msgid "Geo|Resync all"
+msgstr ""
+
+msgid "Geo|Retry count"
+msgstr ""
+
+msgid "Geo|Reverify"
+msgstr ""
+
+msgid "Geo|Reverify all"
+msgstr ""
+
+msgid "Geo|Review replication status, and resynchronize and reverify items with the primary node."
+msgstr ""
+
+msgid "Geo|Status"
+msgstr ""
+
+msgid "Geo|Synced"
+msgstr ""
+
+msgid "Geo|Synced at"
+msgstr ""
+
+msgid "Geo|Synchronization failed - %{error}"
+msgstr ""
+
+msgid "Geo|Synchronization of %{itemTitle} is disabled."
+msgstr ""
+
+msgid "Geo|The database is currently %{db_lag} behind the primary node."
+msgstr ""
+
+msgid "Geo|The node is currently %{minutes_behind} behind the primary node."
+msgstr ""
+
+msgid "Geo|There are no %{replicable_type} to show"
+msgstr ""
+
+msgid "Geo|Tracking database entry will be removed. Are you sure?"
+msgstr ""
+
+msgid "Geo|Tracking entry for project (%{project_id}) was successfully removed."
+msgstr ""
+
+msgid "Geo|Tracking entry for upload (%{type}/%{id}) was successfully removed."
+msgstr ""
+
+msgid "Geo|URL can't be blank"
+msgstr ""
+
+msgid "Geo|URL must be a valid url (ex: https://gitlab.com)"
+msgstr ""
+
+msgid "Geo|Unknown state"
+msgstr ""
+
+msgid "Geo|Verification failed - %{error}"
+msgstr ""
+
+msgid "Geo|Waiting for scheduler"
+msgstr ""
+
+msgid "Geo|You are on a secondary, <b>read-only</b> Geo node. If you want to make changes, you must visit this page on the %{primary_node}."
+msgstr ""
+
+msgid "Geo|You are on a secondary, <b>read-only</b> Geo node. You may be able to make a limited amount of changes or perform a limited amount of actions on this page."
+msgstr ""
+
+msgid "Geo|misconfigured"
+msgstr ""
+
+msgid "Geo|primary"
+msgstr ""
+
+msgid "Geo|secondary"
+msgstr ""
+
+msgid "Get a free instance review"
+msgstr ""
+
+msgid "Get started"
+msgstr ""
+
+msgid "Get started with error tracking"
+msgstr ""
+
+msgid "Get started with performance monitoring"
+msgstr ""
+
+msgid "Get started!"
+msgstr ""
+
+msgid "Getting started with releases"
+msgstr ""
+
+msgid "Git"
+msgstr ""
+
+msgid "Git LFS is not enabled on this GitLab server, contact your admin."
+msgstr ""
+
+msgid "Git LFS objects will be synced in pull mirrors if LFS is %{docs_link_start}enabled for the project%{docs_link_end}. They will <strong>not</strong> be synced in push mirrors."
+msgstr ""
+
+msgid "Git global setup"
+msgstr ""
+
+msgid "Git repository URL"
+msgstr ""
+
+msgid "Git revision"
+msgstr ""
+
+msgid "Git shallow clone"
+msgstr ""
+
+msgid "Git strategy for pipelines"
+msgstr ""
+
+msgid "Git version"
+msgstr ""
+
+msgid "GitHub API rate limit exceeded. Try again after %{reset_time}"
+msgstr ""
+
+msgid "GitHub import"
+msgstr ""
+
+msgid "GitLab / Unsubscribe"
+msgstr ""
+
+msgid "GitLab Enterprise Edition %{plan}"
+msgstr ""
+
+msgid "GitLab Group Runners can execute code for all the projects in this group."
+msgstr ""
+
+msgid "GitLab Import"
+msgstr ""
+
+msgid "GitLab Issue"
+msgstr ""
+
+msgid "GitLab Shared Runners execute code of different projects on the same Runner unless you configure GitLab Runner Autoscale with MaxBuilds 1 (which it is on GitLab.com)."
+msgstr ""
+
+msgid "GitLab Support Bot"
+msgstr ""
+
+msgid "GitLab Team Member"
+msgstr ""
+
+msgid "GitLab User"
+msgstr ""
+
+msgid "GitLab allows you to continue using your license even if you exceed the number of seats you purchased. You will be required to pay for these seats when you renew your license."
+msgstr ""
+
+msgid "GitLab commit"
+msgstr ""
+
+msgid "GitLab for Slack"
+msgstr ""
+
+msgid "GitLab is a single application for the entire software development lifecycle. From project planning and source code management to CI/CD, monitoring, and security."
+msgstr ""
+
+msgid "GitLab is obtaining a Let's Encrypt SSL certificate for this domain. This process can take some time. Please try again later."
+msgstr ""
+
+msgid "GitLab is undergoing maintenance and is operating in a read-only mode."
+msgstr ""
+
+msgid "GitLab member or Email address"
+msgstr ""
+
+msgid "GitLab metadata URL"
+msgstr ""
+
+msgid "GitLab project export"
+msgstr ""
+
+msgid "GitLab restart is required to apply changes."
+msgstr ""
+
+msgid "GitLab single sign on URL"
+msgstr ""
+
+msgid "GitLab uses %{jaeger_link} to monitor distributed systems."
+msgstr ""
+
+msgid "GitLab will run a background job that will produce pseudonymized CSVs of the GitLab database that will be uploaded to your configured object storage directory."
+msgstr ""
+
+msgid "GitLab.com import"
+msgstr ""
+
+msgid "GitLabPagesDomains|Retry"
+msgstr ""
+
+msgid "GitLabPages|%{domain} is not verified. To learn how to verify ownership, visit your %{link_start}domain details%{link_end}."
+msgstr ""
+
+msgid "GitLabPages|Access Control is enabled for this Pages website; only authorized users will be able to access it. To make your website publicly available, navigate to your project's %{strong_start}Settings > General > Visibility%{strong_end} and select %{strong_start}Everyone%{strong_end} in pages section. Read the %{link_start}documentation%{link_end} for more information."
+msgstr ""
+
+msgid "GitLabPages|Access pages"
+msgstr ""
+
+msgid "GitLabPages|Are you sure?"
+msgstr ""
+
+msgid "GitLabPages|Certificate: %{subject}"
+msgstr ""
+
+msgid "GitLabPages|Configure pages"
+msgstr ""
+
+msgid "GitLabPages|Domains"
+msgstr ""
+
+msgid "GitLabPages|Edit"
+msgstr ""
+
+msgid "GitLabPages|Expired"
+msgstr ""
+
+msgid "GitLabPages|Force HTTPS (requires valid certificates)"
+msgstr ""
+
+msgid "GitLabPages|GitLab Pages are disabled for this project. You can enable them on your project's %{strong_start}Settings > General > Visibility%{strong_end} page."
+msgstr ""
+
+msgid "GitLabPages|It may take up to 30 minutes before the site is available after the first deployment."
+msgstr ""
+
+msgid "GitLabPages|Learn how to upload your static site and have it served by GitLab by following the %{link_start}documentation on GitLab Pages%{link_end}."
+msgstr ""
+
+msgid "GitLabPages|Learn more."
+msgstr ""
+
+msgid "GitLabPages|Maximum size of pages (MB)"
+msgstr ""
+
+msgid "GitLabPages|New Domain"
+msgstr ""
+
+msgid "GitLabPages|Only project maintainers can remove pages"
+msgstr ""
+
+msgid "GitLabPages|Pages"
+msgstr ""
+
+msgid "GitLabPages|Remove"
+msgstr ""
+
+msgid "GitLabPages|Remove pages"
+msgstr ""
+
+msgid "GitLabPages|Removing pages will prevent them from being exposed to the outside world."
+msgstr ""
+
+msgid "GitLabPages|Save"
+msgstr ""
+
+msgid "GitLabPages|Something went wrong while obtaining the Let's Encrypt certificate for %{domain}. To retry visit your %{link_start}domain details%{link_end}."
+msgstr ""
+
+msgid "GitLabPages|Support for domains and certificates is disabled. Ask your system's administrator to enable it."
+msgstr ""
+
+msgid "GitLabPages|The total size of deployed static content will be limited to this size. 0 for unlimited. Leave empty to inherit the global value."
+msgstr ""
+
+msgid "GitLabPages|Unverified"
+msgstr ""
+
+msgid "GitLabPages|Verified"
+msgstr ""
+
+msgid "GitLabPages|When using Pages under the general domain of a GitLab instance (%{pages_host}), you cannot use HTTPS with sub-subdomains. This means that if your username/groupname contains a dot it will not work. This is a limitation of the HTTP Over TLS protocol. HTTP pages will continue to work provided you don't redirect HTTP to HTTPS."
+msgstr ""
+
+msgid "GitLabPages|With GitLab Pages you can host your static websites on GitLab. Combined with the power of GitLab CI and the help of GitLab Runner you can deploy static pages for your individual projects, your user or your group."
+msgstr ""
+
+msgid "GitLabPages|Your pages are served under:"
+msgstr ""
+
+msgid "Gitaly"
+msgstr ""
+
+msgid "Gitaly Servers"
+msgstr ""
+
+msgid "Gitaly|Address"
+msgstr ""
+
+msgid "Gitea Host URL"
+msgstr ""
+
+msgid "Gitea Import"
+msgstr ""
+
+msgid "Gitlab Pages"
+msgstr ""
+
+msgid "Given access %{time_ago}"
+msgstr ""
+
+msgid "Given epic is already related to this epic."
+msgstr ""
+
+msgid "Global Shortcuts"
+msgstr ""
+
+msgid "Global notification settings"
+msgstr ""
+
+msgid "Go Back"
+msgstr ""
+
+msgid "Go Micro is a framework for micro service development."
+msgstr ""
+
+msgid "Go back"
+msgstr ""
+
+msgid "Go back (while searching for files)"
+msgstr ""
+
+msgid "Go back to %{startTag}Open issues%{endTag} and select some issues to add to your board."
+msgstr ""
+
+msgid "Go full screen"
+msgstr ""
+
+msgid "Go to %{link_to_google_takeout}."
+msgstr ""
+
+msgid "Go to <strong>Issues</strong> > <strong>Boards</strong> to access your personalized learning issue board."
+msgstr ""
+
+msgid "Go to Pipelines"
+msgstr ""
+
+msgid "Go to Webhooks"
+msgstr ""
+
+msgid "Go to commits"
+msgstr ""
+
+msgid "Go to definition"
+msgstr ""
+
+msgid "Go to environments"
+msgstr ""
+
+msgid "Go to file"
+msgstr ""
+
+msgid "Go to file permalink (while viewing a file)"
+msgstr ""
+
+msgid "Go to files"
+msgstr ""
+
+msgid "Go to find file"
+msgstr ""
+
+msgid "Go to issue boards"
+msgstr ""
+
+msgid "Go to issues"
+msgstr ""
+
+msgid "Go to jobs"
+msgstr ""
+
+msgid "Go to kubernetes"
+msgstr ""
+
+msgid "Go to merge requests"
+msgstr ""
+
+msgid "Go to metrics"
+msgstr ""
+
+msgid "Go to parent"
+msgstr ""
+
+msgid "Go to project"
+msgstr ""
+
+msgid "Go to releases"
+msgstr ""
+
+msgid "Go to repository charts"
+msgstr ""
+
+msgid "Go to repository graph"
+msgstr ""
+
+msgid "Go to snippets"
+msgstr ""
+
+msgid "Go to the activity feed"
+msgstr ""
+
+msgid "Go to the milestone list"
+msgstr ""
+
+msgid "Go to the project's activity feed"
+msgstr ""
+
+msgid "Go to the project's overview page"
+msgstr ""
+
+msgid "Go to wiki"
+msgstr ""
+
+msgid "Go to your To-Do list"
+msgstr ""
+
+msgid "Go to your fork"
+msgstr ""
+
+msgid "Go to your groups"
+msgstr ""
+
+msgid "Go to your issues"
+msgstr ""
+
+msgid "Go to your merge requests"
+msgstr ""
+
+msgid "Go to your projects"
+msgstr ""
+
+msgid "Go to your snippets"
+msgstr ""
+
+msgid "Google Cloud Platform"
+msgstr ""
+
+msgid "Google Code import"
+msgstr ""
+
+msgid "Google Takeout"
+msgstr ""
+
+msgid "Google authentication is not %{link_start}properly configured%{link_end}. Ask your GitLab administrator if you want to use this service."
+msgstr ""
+
+msgid "Got it"
+msgstr ""
+
+msgid "Got it!"
+msgstr ""
+
+msgid "Grafana URL"
+msgstr ""
+
+msgid "Grafana response contains invalid json"
+msgstr ""
+
+msgid "GrafanaIntegration|API Token"
+msgstr ""
+
+msgid "GrafanaIntegration|Active"
+msgstr ""
+
+msgid "GrafanaIntegration|Embed Grafana charts in GitLab issues."
+msgstr ""
+
+msgid "GrafanaIntegration|Enter the Grafana API Token."
+msgstr ""
+
+msgid "GrafanaIntegration|Enter the base URL of the Grafana instance."
+msgstr ""
+
+msgid "GrafanaIntegration|Grafana Authentication"
+msgstr ""
+
+msgid "GrafanaIntegration|Grafana URL"
+msgstr ""
+
+msgid "Grant access"
+msgstr ""
+
+msgid "Graph"
+msgstr ""
+
+msgid "Gravatar"
+msgstr ""
+
+msgid "Gravatar enabled"
+msgstr ""
+
+msgid "Group"
+msgstr ""
+
+msgid "Group %{group_name} couldn't be exported."
+msgstr ""
+
+msgid "Group %{group_name} was exported successfully."
+msgstr ""
+
+msgid "Group %{group_name} was scheduled for deletion."
+msgstr ""
+
+msgid "Group %{group_name} was successfully created."
+msgstr ""
+
+msgid "Group Audit Events"
+msgstr ""
+
+msgid "Group CI/CD settings"
+msgstr ""
+
+msgid "Group Git LFS status:"
+msgstr ""
+
+msgid "Group Hooks"
+msgstr ""
+
+msgid "Group ID"
+msgstr ""
+
+msgid "Group ID: %{group_id}"
+msgstr ""
+
+msgid "Group Owner must have signed in with SAML before enabling Group Managed Accounts"
+msgstr ""
+
+msgid "Group Runners"
+msgstr ""
+
+msgid "Group SAML must be enabled to test"
+msgstr ""
+
+msgid "Group URL"
+msgstr ""
+
+msgid "Group avatar"
+msgstr ""
+
+msgid "Group by:"
+msgstr ""
+
+msgid "Group description"
+msgstr ""
+
+msgid "Group description (optional)"
+msgstr ""
+
+msgid "Group details"
+msgstr ""
+
+msgid "Group export could not be started."
+msgstr ""
+
+msgid "Group export error"
+msgstr ""
+
+msgid "Group export link has expired. Please generate a new export from your group settings."
+msgstr ""
+
+msgid "Group export started. A download link will be sent by email and made available on this page."
+msgstr ""
+
+msgid "Group has been already marked for deletion"
+msgstr ""
+
+msgid "Group has not been marked for deletion"
+msgstr ""
+
+msgid "Group import could not be scheduled"
+msgstr ""
+
+msgid "Group info:"
+msgstr ""
+
+msgid "Group is required when cluster_type is :group"
+msgstr ""
+
+msgid "Group maintainers can register group runners in the %{link}"
+msgstr ""
+
+msgid "Group members"
+msgstr ""
+
+msgid "Group milestone"
+msgstr ""
+
+msgid "Group name"
+msgstr ""
+
+msgid "Group name (your organization)"
+msgstr ""
+
+msgid "Group overview"
+msgstr ""
+
+msgid "Group overview content"
+msgstr ""
+
+msgid "Group path is already taken. Suggestions: "
+msgstr ""
+
+msgid "Group path is available."
+msgstr ""
+
+msgid "Group pipeline minutes were successfully reset."
+msgstr ""
+
+msgid "Group project URLs are prefixed with the group namespace"
+msgstr ""
+
+msgid "Group requires separate account"
+msgstr ""
+
+msgid "Group variables (inherited)"
+msgstr ""
+
+msgid "Group was exported"
+msgstr ""
+
+msgid "Group was successfully updated."
+msgstr ""
+
+msgid "Group: %{group_name}"
+msgstr ""
+
+msgid "Group: %{name}"
+msgstr ""
+
+msgid "GroupActivityMetrics|New Members created"
+msgstr ""
+
+msgid "GroupActivyMetrics|Issues created"
+msgstr ""
+
+msgid "GroupActivyMetrics|Merge Requests created"
+msgstr ""
+
+msgid "GroupActivyMetrics|Recent activity (last 90 days)"
+msgstr ""
+
+msgid "GroupImport|Failed to import group."
+msgstr ""
+
+msgid "GroupImport|Group '%{group_name}' is being imported."
+msgstr ""
+
+msgid "GroupImport|Group could not be imported: %{errors}"
+msgstr ""
+
+msgid "GroupImport|Please wait while we import the group for you. Refresh at will."
+msgstr ""
+
+msgid "GroupImport|The group was successfully imported."
+msgstr ""
+
+msgid "GroupImport|Unable to process group import file"
+msgstr ""
+
+msgid "GroupRoadmap|%{dateWord} – No end date"
+msgstr ""
+
+msgid "GroupRoadmap|%{startDateInWords} – %{endDateInWords}"
+msgstr ""
+
+msgid "GroupRoadmap|No start date – %{dateWord}"
+msgstr ""
+
+msgid "GroupRoadmap|Something went wrong while fetching epics"
+msgstr ""
+
+msgid "GroupRoadmap|Something went wrong while fetching milestones"
+msgstr ""
+
+msgid "GroupRoadmap|Sorry, no epics matched your search"
+msgstr ""
+
+msgid "GroupRoadmap|The roadmap shows the progress of your epics along a timeline"
+msgstr ""
+
+msgid "GroupRoadmap|To view the roadmap, add a start or due date to one of the %{linkStart}child epics%{linkEnd}."
+msgstr ""
+
+msgid "GroupRoadmap|To view the roadmap, add a start or due date to one of your epics in this group or its subgroups; from %{startDate} to %{endDate}."
+msgstr ""
+
+msgid "GroupRoadmap|To widen your search, change or remove filters; from %{startDate} to %{endDate}."
+msgstr ""
+
+msgid "GroupSAML|Certificate fingerprint"
+msgstr ""
+
+msgid "GroupSAML|Configuration"
+msgstr ""
+
+msgid "GroupSAML|Copy SAML Response XML"
+msgstr ""
+
+msgid "GroupSAML|Enable SAML authentication for this group."
+msgstr ""
+
+msgid "GroupSAML|Enforce SSO-only authentication for this group."
+msgstr ""
+
+msgid "GroupSAML|Enforce users to have dedicated group managed accounts for this group."
+msgstr ""
+
+msgid "GroupSAML|Enforced SSO"
+msgstr ""
+
+msgid "GroupSAML|Generate a SCIM token"
+msgstr ""
+
+msgid "GroupSAML|Generate a SCIM token to set up your System for Cross-Domain Identity Management."
+msgstr ""
+
+msgid "GroupSAML|Identity"
+msgstr ""
+
+msgid "GroupSAML|Identity provider single sign on URL"
+msgstr ""
+
+msgid "GroupSAML|Make sure you save this token — you won't be able to access it again."
+msgstr ""
+
+msgid "GroupSAML|Manage your group’s membership while adding another level of security with SAML."
+msgstr ""
+
+msgid "GroupSAML|Members"
+msgstr ""
+
+msgid "GroupSAML|Members will be forwarded here when signing in to your group. Get this from your identity provider, where it can also be called \"SSO Service Location\", \"SAML Token Issuance Endpoint\", or \"SAML 2.0/W-Federation URL\"."
+msgstr ""
+
+msgid "GroupSAML|NameID"
+msgstr ""
+
+msgid "GroupSAML|NameID Format"
+msgstr ""
+
+msgid "GroupSAML|Prohibit outer forks"
+msgstr ""
+
+msgid "GroupSAML|Prohibit outer forks for this group."
+msgstr ""
+
+msgid "GroupSAML|SAML Response Output"
+msgstr ""
+
+msgid "GroupSAML|SAML Response XML"
+msgstr ""
+
+msgid "GroupSAML|SAML Single Sign On"
+msgstr ""
+
+msgid "GroupSAML|SAML Single Sign On Settings"
+msgstr ""
+
+msgid "GroupSAML|SCIM API endpoint URL"
+msgstr ""
+
+msgid "GroupSAML|SCIM Token"
+msgstr ""
+
+msgid "GroupSAML|SHA1 fingerprint of the SAML token signing certificate. Get this from your identity provider, where it can also be called \"Thumbprint\"."
+msgstr ""
+
+msgid "GroupSAML|The SCIM token is now hidden. To see the value of the token again, you need to "
+msgstr ""
+
+msgid "GroupSAML|To be able to enable enforced SSO, you first need to enable SAML authentication."
+msgstr ""
+
+msgid "GroupSAML|To be able to enable group managed accounts, you first need to enable enforced SSO."
+msgstr ""
+
+msgid "GroupSAML|To be able to prohibit outer forks, you first need to enforce dedicate group managed accounts."
+msgstr ""
+
+msgid "GroupSAML|Toggle SAML authentication"
+msgstr ""
+
+msgid "GroupSAML|Valid SAML Response"
+msgstr ""
+
+msgid "GroupSAML|With group managed accounts enabled, all the users without a group managed account will be excluded from the group."
+msgstr ""
+
+msgid "GroupSAML|With prohibit outer forks flag enabled group members will be able to fork project only inside your group."
+msgstr ""
+
+msgid "GroupSAML|Your SCIM token"
+msgstr ""
+
+msgid "GroupSAML|must match stored NameID of \"%{extern_uid}\" as we use this to identify users. If the NameID changes users will be unable to sign in."
+msgstr ""
+
+msgid "GroupSAML|should be \"persistent\""
+msgstr ""
+
+msgid "GroupSAML|should be a random persistent ID, emails are discouraged"
+msgstr ""
+
+msgid "GroupSettings|Apply integration settings to all Projects"
+msgstr ""
+
+msgid "GroupSettings|Auto DevOps pipeline was updated for the group"
+msgstr ""
+
+msgid "GroupSettings|Badges"
+msgstr ""
+
+msgid "GroupSettings|Be careful. Changing a group's parent can have unintended %{side_effects_link_start}side effects%{side_effects_link_end}."
+msgstr ""
+
+msgid "GroupSettings|Cannot update the path because there are projects under this group that contain Docker images in their Container Registry. Please remove the images from your projects first and try again."
+msgstr ""
+
+msgid "GroupSettings|Change group path"
+msgstr ""
+
+msgid "GroupSettings|Changing group path can have unintended side effects."
+msgstr ""
+
+msgid "GroupSettings|Custom project templates"
+msgstr ""
+
+msgid "GroupSettings|Customize your group badges."
+msgstr ""
+
+msgid "GroupSettings|Default to Auto DevOps pipeline for all projects within this group"
+msgstr ""
+
+msgid "GroupSettings|Disable email notifications"
+msgstr ""
+
+msgid "GroupSettings|Disable group mentions"
+msgstr ""
+
+msgid "GroupSettings|Export group"
+msgstr ""
+
+msgid "GroupSettings|If the parent group's visibility is lower than the group current visibility, visibility levels for subgroups and projects will be changed to match the new parent group's visibility."
+msgstr ""
+
+msgid "GroupSettings|Integrations configured here will automatically apply to all projects in this group."
+msgstr ""
+
+msgid "GroupSettings|Learn more about badges."
+msgstr ""
+
+msgid "GroupSettings|Learn more about group-level project templates."
+msgstr ""
+
+msgid "GroupSettings|New runners registration token has been generated!"
+msgstr ""
+
+msgid "GroupSettings|Pipeline settings was updated for the group"
+msgstr ""
+
+msgid "GroupSettings|Please choose a group path with no special characters."
+msgstr ""
+
+msgid "GroupSettings|Prevent sharing a project within %{group} with other groups"
+msgstr ""
+
+msgid "GroupSettings|Select a sub-group as the custom project template source for this group."
+msgstr ""
+
+msgid "GroupSettings|The Auto DevOps pipeline will run if no alternative CI configuration file is found."
+msgstr ""
+
+msgid "GroupSettings|There was a problem updating Auto DevOps pipeline: %{error_messages}."
+msgstr ""
+
+msgid "GroupSettings|There was a problem updating the pipeline settings: %{error_messages}."
+msgstr ""
+
+msgid "GroupSettings|This setting is applied on %{ancestor_group} and has been overridden on this subgroup."
+msgstr ""
+
+msgid "GroupSettings|This setting is applied on %{ancestor_group}. To share projects in this group with another group, ask the owner to override the setting or %{remove_ancestor_share_with_group_lock}."
+msgstr ""
+
+msgid "GroupSettings|This setting is applied on %{ancestor_group}. You can override the setting or %{remove_ancestor_share_with_group_lock}."
+msgstr ""
+
+msgid "GroupSettings|This setting will be applied to all subgroups unless overridden by a group owner. Groups that already have access to the project will continue to have access unless removed manually."
+msgstr ""
+
+msgid "GroupSettings|This setting will override user notification preferences for all members of the group, subgroups, and projects."
+msgstr ""
+
+msgid "GroupSettings|This setting will prevent group members from being notified if the group is mentioned."
+msgstr ""
+
+msgid "GroupSettings|Transfer group"
+msgstr ""
+
+msgid "GroupSettings|You can only transfer the group to a group you manage."
+msgstr ""
+
+msgid "GroupSettings|You will need to update your local repositories to point to the new location."
+msgstr ""
+
+msgid "GroupSettings|cannot be changed by you"
+msgstr ""
+
+msgid "GroupSettings|cannot be disabled when the parent group \"Share with group lock\" is enabled, except by the owner of the parent group"
+msgstr ""
+
+msgid "GroupSettings|cannot change when group contains projects with NPM packages"
+msgstr ""
+
+msgid "GroupSettings|remove the share with group lock from %{ancestor_group_name}"
+msgstr ""
+
+msgid "Groups"
+msgstr ""
+
+msgid "Groups (%{count})"
+msgstr ""
+
+msgid "Groups (%{groups})"
+msgstr ""
+
+msgid "Groups and projects"
+msgstr ""
+
+msgid "Groups can also be nested by creating %{subgroup_docs_link_start}subgroups%{subgroup_docs_link_end}."
+msgstr ""
+
+msgid "Groups to synchronize"
+msgstr ""
+
+msgid "Groups with access to %{strong_start}%{group_name}%{strong_end}"
+msgstr ""
+
+msgid "Groups with access to <strong>%{project_name}</strong>"
+msgstr ""
+
+msgid "GroupsDropdown|Frequently visited"
+msgstr ""
+
+msgid "GroupsDropdown|Groups you visit often will appear here"
+msgstr ""
+
+msgid "GroupsDropdown|Loading groups"
+msgstr ""
+
+msgid "GroupsDropdown|Search your groups"
+msgstr ""
+
+msgid "GroupsDropdown|Something went wrong on our end."
+msgstr ""
+
+msgid "GroupsDropdown|Sorry, no groups matched your search"
+msgstr ""
+
+msgid "GroupsDropdown|This feature requires browser localStorage support"
+msgstr ""
+
+msgid "GroupsEmptyState|A group is a collection of several projects."
+msgstr ""
+
+msgid "GroupsEmptyState|If you organize your projects under a group, it works like a folder."
+msgstr ""
+
+msgid "GroupsEmptyState|No groups found"
+msgstr ""
+
+msgid "GroupsEmptyState|You can manage your group member’s permissions and access to each project in the group."
+msgstr ""
+
+msgid "GroupsNew|Contact an administrator to enable options for importing your group."
+msgstr ""
+
+msgid "GroupsNew|Create"
+msgstr ""
+
+msgid "GroupsNew|Create group"
+msgstr ""
+
+msgid "GroupsNew|GitLab group export"
+msgstr ""
+
+msgid "GroupsNew|Import"
+msgstr ""
+
+msgid "GroupsNew|Import group"
+msgstr ""
+
+msgid "GroupsNew|My Awesome Group"
+msgstr ""
+
+msgid "GroupsNew|No import options available"
+msgstr ""
+
+msgid "GroupsNew|To copy a GitLab group between installations, navigate to the group settings page for the original installation, generate an export file, and upload it here."
+msgstr ""
+
+msgid "GroupsTree|Are you sure you want to leave the \"%{fullName}\" group?"
+msgstr ""
+
+msgid "GroupsTree|Create a project in this group."
+msgstr ""
+
+msgid "GroupsTree|Create a subgroup in this group."
+msgstr ""
+
+msgid "GroupsTree|Edit group"
+msgstr ""
+
+msgid "GroupsTree|Failed to leave the group. Please make sure you are not the only owner."
+msgstr ""
+
+msgid "GroupsTree|Leave this group"
+msgstr ""
+
+msgid "GroupsTree|Loading groups"
+msgstr ""
+
+msgid "GroupsTree|No groups matched your search"
+msgstr ""
+
+msgid "GroupsTree|No groups or projects matched your search"
+msgstr ""
+
+msgid "GroupsTree|Search by name"
+msgstr ""
+
+msgid "Guideline"
+msgstr ""
+
+msgid "HTTP Basic: Access denied\\nYou must use a personal access token with 'api' scope for Git over HTTP.\\nYou can generate one at %{profile_personal_access_tokens_url}"
+msgstr ""
+
+msgid "Hashed Storage must be enabled to use Geo"
+msgstr ""
+
+msgid "Hashed repository storage paths"
+msgstr ""
+
+msgid "Hashed storage can't be disabled anymore for new projects"
+msgstr ""
+
+msgid "Have your users email"
+msgstr ""
+
+msgid "Header logo was successfully removed."
+msgstr ""
+
+msgid "Header message"
+msgstr ""
+
+msgid "Headings"
+msgstr ""
+
+msgid "Health"
+msgstr ""
+
+msgid "Health Check"
+msgstr ""
+
+msgid "Health information can be retrieved from the following endpoints. More information is available"
+msgstr ""
+
+msgid "HealthCheck|Access token is"
+msgstr ""
+
+msgid "HealthCheck|Healthy"
+msgstr ""
+
+msgid "HealthCheck|No Health Problems Detected"
+msgstr ""
+
+msgid "HealthCheck|Unhealthy"
+msgstr ""
+
+msgid "Hello there"
+msgstr ""
+
+msgid "Help"
+msgstr ""
+
+msgid "Help page"
+msgstr ""
+
+msgid "Help page text and support page url."
+msgstr ""
+
+msgid "Helps prevent bots from brute-force attacks."
+msgstr ""
+
+msgid "Helps prevent bots from creating accounts."
+msgstr ""
+
+msgid "Helps reduce alert volume (e.g. if creating too many issues)"
+msgstr ""
+
+msgid "Helps reduce request volume for protected paths"
+msgstr ""
+
+msgid "Here are all your projects in your group, including the one you just created. To start, let’s take a look at your personalized learning project which will help you learn about GitLab at your own pace."
+msgstr ""
+
+msgid "Here you will find recent merge request activity"
+msgstr ""
+
+msgid "Hi %{username}!"
+msgstr ""
+
+msgid "Hide archived projects"
+msgstr ""
+
+msgid "Hide chart"
+msgid_plural "Hide charts"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "Hide details"
+msgstr ""
+
+msgid "Hide file browser"
+msgstr ""
+
+msgid "Hide group projects"
+msgstr ""
+
+msgid "Hide host keys manual input"
+msgstr ""
+
+msgid "Hide list"
+msgstr ""
+
+msgid "Hide marketing-related entries from help"
+msgstr ""
+
+msgid "Hide payload"
+msgstr ""
+
+msgid "Hide shared projects"
+msgstr ""
+
+msgid "Hide stage"
+msgstr ""
+
+msgid "Hide value"
+msgid_plural "Hide values"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "Hide values"
+msgstr ""
+
+msgid "High or unknown vulnerabilities present"
+msgstr ""
+
+msgid "Highest number of requests per minute for each raw path, default to 300. To disable throttling set to 0."
+msgstr ""
+
+msgid "Highest role:"
+msgstr ""
+
+msgid "History"
+msgstr ""
+
+msgid "History of authentications"
+msgstr ""
+
+msgid "Homepage"
+msgstr ""
+
+msgid "Hook execution failed. Ensure the group has a project with commits."
+msgstr ""
+
+msgid "Hook was successfully created."
+msgstr ""
+
+msgid "Hook was successfully updated."
+msgstr ""
+
+msgid "Hostname"
+msgstr ""
+
+msgid "Hour (UTC)"
+msgstr ""
+
+msgid "Housekeeping"
+msgstr ""
+
+msgid "Housekeeping successfully started"
+msgstr ""
+
+msgid "Housekeeping, export, path, transfer, remove, archive."
+msgstr ""
+
+msgid "How it works"
+msgstr ""
+
+msgid "How many days need to pass between marking entity for deletion and actual removing it."
+msgstr ""
+
+msgid "How many replicas each Elasticsearch shard has."
+msgstr ""
+
+msgid "How many shards to split the Elasticsearch index over."
+msgstr ""
+
+msgid "How many users will be evaluating the trial?"
+msgstr ""
+
+msgid "However, you are already a member of this %{member_source}. Sign in using a different account to accept the invitation."
+msgstr ""
+
+msgid "I accept the %{terms_link_start}Terms of Service and Privacy Policy%{terms_link_end}"
+msgstr ""
+
+msgid "I accept the %{terms_link}"
+msgstr ""
+
+msgid "I accept the|Terms of Service and Privacy Policy"
+msgstr ""
+
+msgid "I forgot my password"
+msgstr ""
+
+msgid "I have read and agree to the Let's Encrypt %{link_start}Terms of Service%{link_end} (PDF)"
+msgstr ""
+
+msgid "I'd like to receive updates via email about GitLab"
+msgstr ""
+
+msgid "ID"
+msgstr ""
+
+msgid "ID:"
+msgstr ""
+
+msgid "IDE"
+msgstr ""
+
+msgid "IDE|Allow live previews of JavaScript projects in the Web IDE using CodeSandbox Live Preview."
+msgstr ""
+
+msgid "IDE|Back"
+msgstr ""
+
+msgid "IDE|Commit"
+msgstr ""
+
+msgid "IDE|Commit to %{branchName} branch"
+msgstr ""
+
+msgid "IDE|Edit"
+msgstr ""
+
+msgid "IDE|Get started with Live Preview"
+msgstr ""
+
+msgid "IDE|Go to project"
+msgstr ""
+
+msgid "IDE|Live Preview"
+msgstr ""
+
+msgid "IDE|Preview your web application using Web IDE client-side evaluation."
+msgstr ""
+
+msgid "IDE|Refresh preview"
+msgstr ""
+
+msgid "IDE|Review"
+msgstr ""
+
+msgid "IDE|Successful commit"
+msgstr ""
+
+msgid "IDE|This option is disabled because you are not allowed to create merge requests in this project."
+msgstr ""
+
+msgid "IDE|This option is disabled because you don't have write permissions for the current branch."
+msgstr ""
+
+msgid "INFO: Your SSH key has expired. Please generate a new key."
+msgstr ""
+
+msgid "INFO: Your SSH key is expiring soon. Please generate a new key."
+msgstr ""
+
+msgid "IP Address"
+msgstr ""
+
+msgid "IP subnet restriction only allowed for top-level groups"
+msgstr ""
+
+msgid "Identifier"
+msgstr ""
+
+msgid "Identifiers"
+msgstr ""
+
+msgid "Identities"
+msgstr ""
+
+msgid "If any indexed field exceeds this limit it will be truncated to this number of characters and the rest will not be indexed or searchable. This does not apply to repository and wiki indexing. Setting this to 0 means it is unlimited."
+msgstr ""
+
+msgid "If any job surpasses this timeout threshold, it will be marked as failed. Human readable time input language is accepted like \"1 hour\". Values without specification represent seconds."
+msgstr ""
+
+msgid "If blank, set allowable lifetime to %{instance_level_policy_in_words}, as defined by the instance admin. Once set, existing tokens for users in this group may be revoked."
+msgstr ""
+
+msgid "If checked, group owners can manage LDAP group links and LDAP member overrides"
+msgstr ""
+
+msgid "If checked, new group memberships and permissions can only be added via LDAP synchronization"
+msgstr ""
+
+msgid "If disabled, a diverged local branch will not be automatically updated with commits from its remote counterpart, to prevent local data loss. If the default branch (%{default_branch}) has diverged and cannot be updated, mirroring will fail. Other diverged branches are silently ignored."
+msgstr ""
+
+msgid "If disabled, only admins will be able to configure repository mirroring."
+msgstr ""
+
+msgid "If disabled, the access level will depend on the user's permissions in the project."
+msgstr ""
+
+msgid "If enabled"
+msgstr ""
+
+msgid "If enabled, GitLab will handle Object Storage replication using Geo. %{linkStart}More information%{linkEnd}"
+msgstr ""
+
+msgid "If enabled, access to projects will be validated on an external service using their classification label."
+msgstr ""
+
+msgid "If there is no previous license or if the previous license has expired, some GitLab functionality will be blocked until a new, valid license is uploaded."
+msgstr ""
+
+msgid "If this was a mistake you can %{leave_link_start}leave the %{source_type}%{link_end}."
+msgstr ""
+
+msgid "If this was a mistake you can leave the %{source_type}."
+msgstr ""
+
+msgid "If using GitHub, you’ll see pipeline statuses on GitHub for your commits and pull requests. %{more_info_link}"
+msgstr ""
+
+msgid "If you did not recently sign in, you should immediately %{password_link_start}change your password%{password_link_end}."
+msgstr ""
+
+msgid "If you did not recently sign in, you should immediately change your password: %{password_link}."
+msgstr ""
+
+msgid "If you lose your recovery codes you can generate new ones, invalidating all previous codes."
+msgstr ""
+
+msgid "If you reach 100%% storage capacity, you will not be able to: %{base_message}"
+msgstr ""
+
+msgid "If you recently signed in and recognize the IP address, you may disregard this email."
+msgstr ""
+
+msgid "If you remove this license, GitLab will fall back on the previous license, if any."
+msgstr ""
+
+msgid "If your HTTP repository is not publicly accessible, add your credentials."
+msgstr ""
+
+msgid "Iglu registry URL (optional)"
+msgstr ""
+
+msgid "Ignore"
+msgstr ""
+
+msgid "Ignored"
+msgstr ""
+
+msgid "Image Details"
+msgstr ""
+
+msgid "Image URL"
+msgstr ""
+
+msgid "ImageDiffViewer|2-up"
+msgstr ""
+
+msgid "ImageDiffViewer|Onion skin"
+msgstr ""
+
+msgid "ImageDiffViewer|Swipe"
+msgstr ""
+
+msgid "ImageViewerDimensions|H"
+msgstr ""
+
+msgid "ImageViewerDimensions|W"
+msgstr ""
+
+msgid "Impersonation Tokens"
+msgstr ""
+
+msgid "Impersonation has been disabled"
+msgstr ""
+
+msgid "Import"
+msgstr ""
+
+msgid "Import CSV"
+msgstr ""
+
+msgid "Import Projects from Gitea"
+msgstr ""
+
+msgid "Import all compatible projects"
+msgstr ""
+
+msgid "Import all compatible repositories"
+msgstr ""
+
+msgid "Import all projects"
+msgstr ""
+
+msgid "Import all repositories"
+msgstr ""
+
+msgid "Import an exported GitLab project"
+msgstr ""
+
+msgid "Import failed due to a GitHub error: %{original}"
+msgstr ""
+
+msgid "Import from"
+msgstr ""
+
+msgid "Import from Jira"
+msgstr ""
+
+msgid "Import in progress"
+msgstr ""
+
+msgid "Import in progress. Refresh page to see newly added issues."
+msgstr ""
+
+msgid "Import issues"
+msgstr ""
+
+msgid "Import members"
+msgstr ""
+
+msgid "Import members from another project"
+msgstr ""
+
+msgid "Import multiple repositories by uploading a manifest file."
+msgstr ""
+
+msgid "Import project"
+msgstr ""
+
+msgid "Import project members"
+msgstr ""
+
+msgid "Import projects from Bitbucket"
+msgstr ""
+
+msgid "Import projects from Bitbucket Server"
+msgstr ""
+
+msgid "Import projects from FogBugz"
+msgstr ""
+
+msgid "Import projects from GitLab.com"
+msgstr ""
+
+msgid "Import projects from Google Code"
+msgstr ""
+
+msgid "Import repositories from Bitbucket Server"
+msgstr ""
+
+msgid "Import repositories from GitHub"
+msgstr ""
+
+msgid "Import repository"
+msgstr ""
+
+msgid "Import started by: %{importInitiator}"
+msgstr ""
+
+msgid "Import tasks"
+msgstr ""
+
+msgid "Import tasks from Phabricator into issues"
+msgstr ""
+
+msgid "Import timed out. Import took longer than %{import_jobs_expiration} seconds"
+msgstr ""
+
+msgid "Import/Export illustration"
+msgstr ""
+
+msgid "ImportButtons|Connect repositories from"
+msgstr ""
+
+msgid "ImportProjects|Blocked import URL: %{message}"
+msgstr ""
+
+msgid "ImportProjects|Error importing repository %{project_safe_import_url} into %{project_full_path} - %{message}"
+msgstr ""
+
+msgid "ImportProjects|Importing the project failed"
+msgstr ""
+
+msgid "ImportProjects|Requesting your %{provider} repositories failed"
+msgstr ""
+
+msgid "ImportProjects|Select the projects you want to import"
+msgstr ""
+
+msgid "ImportProjects|The remote data could not be imported."
+msgstr ""
+
+msgid "ImportProjects|The repository could not be created."
+msgstr ""
+
+msgid "ImportProjects|Update of imported projects with realtime changes failed"
+msgstr ""
+
+msgid "Improve Issue boards"
+msgstr ""
+
+msgid "Improve Issue boards with GitLab Enterprise Edition."
+msgstr ""
+
+msgid "Improve Merge Requests and customer support with GitLab Enterprise Edition."
+msgstr ""
+
+msgid "Improve search with Advanced Global Search and GitLab Enterprise Edition."
+msgstr ""
+
+msgid "In %{time_to_now}"
+msgstr ""
+
+msgid "In order to enable instance-level analytics, please ask an admin to enable %{usage_ping_link_start}usage ping%{usage_ping_link_end}."
+msgstr ""
+
+msgid "In order to gather accurate feature usage data, it can take 1 to 2 weeks to see your index."
+msgstr ""
+
+msgid "In order to personalize your experience with GitLab<br>we would like to know a bit more about you."
+msgstr ""
+
+msgid "In order to tailor your experience with GitLab we<br>would like to know a bit more about you."
+msgstr ""
+
+msgid "In progress"
+msgstr ""
+
+msgid "In the next step, you'll be able to select the projects you want to import."
+msgstr ""
+
+msgid "Incident Management Limits"
+msgstr ""
+
+msgid "Incidents"
+msgstr ""
+
+msgid "Include a Terms of Service agreement and Privacy Policy that all users must accept."
+msgstr ""
+
+msgid "Include author name in notification email body"
+msgstr ""
+
+msgid "Include description in commit message"
+msgstr ""
+
+msgid "Include merge request description"
+msgstr ""
+
+msgid "Include the username in the URL if required: <code>https://username@gitlab.company.com/group/project.git</code>."
+msgstr ""
+
+msgid "Includes LFS objects. It can be overridden per group, or per project. 0 for unlimited."
+msgstr ""
+
+msgid "Includes an MVC structure to help you get started."
+msgstr ""
+
+msgid "Includes an MVC structure, Gemfile, Rakefile, along with many others, to help you get started."
+msgstr ""
+
+msgid "Includes an MVC structure, mvnw and pom.xml to help you get started."
+msgstr ""
+
+msgid "Includes repository storage, wiki storage, LFS objects, build artifacts and packages. 0 for unlimited."
+msgstr ""
+
+msgid "Incoming email"
+msgstr ""
+
+msgid "Incoming!"
+msgstr ""
+
+msgid "Incompatible Project"
+msgstr ""
+
+msgid "Incompatible options set!"
+msgstr ""
+
+msgid "Incompatible project"
+msgstr ""
+
+msgid "Indent"
+msgstr ""
+
+msgid "Index all projects"
+msgstr ""
+
+msgid "Indicates whether this runner can pick jobs without tags"
+msgstr ""
+
+msgid "Inform users without uploaded SSH keys that they can't push over SSH until one is added"
+msgstr ""
+
+msgid "Information about additional Pages templates and how to install them can be found in our %{pages_getting_started_guide}."
+msgstr ""
+
+msgid "Inherited:"
+msgstr ""
+
+msgid "Inline"
+msgstr ""
+
+msgid "Input host keys manually"
+msgstr ""
+
+msgid "Input your repository URL"
+msgstr ""
+
+msgid "Insert"
+msgstr ""
+
+msgid "Insert a code block"
+msgstr ""
+
+msgid "Insert a quote"
+msgstr ""
+
+msgid "Insert an image"
+msgstr ""
+
+msgid "Insert code"
+msgstr ""
+
+msgid "Insert inline code"
+msgstr ""
+
+msgid "Insert suggestion"
+msgstr ""
+
+msgid "Insights"
+msgstr ""
+
+msgid "Insights|Some items are not visible beacuse the project was filtered out in the insights.yml file (see the projects.only config for more information)."
+msgstr ""
+
+msgid "Insights|This project is filtered out in the insights.yml file (see the projects.only config for more information)."
+msgstr ""
+
+msgid "Install"
+msgstr ""
+
+msgid "Install GitLab Runner"
+msgstr ""
+
+msgid "Install Runner on Kubernetes"
+msgstr ""
+
+msgid "Install a soft token authenticator like %{free_otp_link} or Google Authenticator from your application repository and use that app to scan this QR code. More information is available in the %{help_link_start}documentation%{help_link_end}."
+msgstr ""
+
+msgid "Install on clusters"
+msgstr ""
+
+msgid "Installed"
+msgstr ""
+
+msgid "Installing"
+msgstr ""
+
+msgid "Instance"
+msgid_plural "Instances"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "Instance Configuration"
+msgstr ""
+
+msgid "Instance Statistics visibility"
+msgstr ""
+
+msgid "Instance administrators group already exists"
+msgstr ""
+
+msgid "Instance does not support multiple Kubernetes clusters"
+msgstr ""
+
+msgid "Instance license"
+msgstr ""
+
+msgid "Integration"
+msgstr ""
+
+msgid "Integration Settings"
+msgstr ""
+
+msgid "Integrations"
+msgstr ""
+
+msgid "Integrations|All details"
+msgstr ""
+
+msgid "Integrations|Comment detail:"
+msgstr ""
+
+msgid "Integrations|Comment settings:"
+msgstr ""
+
+msgid "Integrations|Enable comments"
+msgstr ""
+
+msgid "Integrations|Includes Standard plus entire commit message, commit hash, and issue IDs"
+msgstr ""
+
+msgid "Integrations|Includes commit title and branch"
+msgstr ""
+
+msgid "Integrations|Standard"
+msgstr ""
+
+msgid "Integrations|When a Jira issue is mentioned in a commit or merge request a remote link and comment (if enabled) will be created."
+msgstr ""
+
+msgid "Interested parties can even contribute by pushing commits if they want to."
+msgstr ""
+
+msgid "Internal"
+msgstr ""
+
+msgid "Internal - The group and any internal projects can be viewed by any logged in user."
+msgstr ""
+
+msgid "Internal - The project can be accessed by any logged in user."
+msgstr ""
+
+msgid "Internal URL (optional)"
+msgstr ""
+
+msgid "Internal users"
+msgstr ""
+
+msgid "Interval Pattern"
+msgstr ""
+
+msgid "Introducing Value Stream Analytics"
+msgstr ""
+
+msgid "Introducing Your DevOps Score"
+msgstr ""
+
+msgid "Invalid Git ref"
+msgstr ""
+
+msgid "Invalid Insights config file detected"
+msgstr ""
+
+msgid "Invalid Login or password"
+msgstr ""
+
+msgid "Invalid URL"
+msgstr ""
+
+msgid "Invalid container_name"
+msgstr ""
+
+msgid "Invalid cursor parameter"
+msgstr ""
+
+msgid "Invalid cursor value provided"
+msgstr ""
+
+msgid "Invalid date"
+msgstr ""
+
+msgid "Invalid date format. Please use UTC format as YYYY-MM-DD"
+msgstr ""
+
+msgid "Invalid date range"
+msgstr ""
+
+msgid "Invalid feature"
+msgstr ""
+
+msgid "Invalid field"
+msgstr ""
+
+msgid "Invalid file format with specified file type"
+msgstr ""
+
+msgid "Invalid file."
+msgstr ""
+
+msgid "Invalid import params"
+msgstr ""
+
+msgid "Invalid input, please avoid emojis"
+msgstr ""
+
+msgid "Invalid login or password"
+msgstr ""
+
+msgid "Invalid pin code"
+msgstr ""
+
+msgid "Invalid pod_name"
+msgstr ""
+
+msgid "Invalid query"
+msgstr ""
+
+msgid "Invalid repository bundle for snippet with id %{snippet_id}"
+msgstr ""
+
+msgid "Invalid repository path"
+msgstr ""
+
+msgid "Invalid search parameter"
+msgstr ""
+
+msgid "Invalid server response"
+msgstr ""
+
+msgid "Invalid start or end time format"
+msgstr ""
+
+msgid "Invalid status"
+msgstr ""
+
+msgid "Invalid two-factor code."
+msgstr ""
+
+msgid "Invalid yaml"
+msgstr ""
+
+msgid "Invitation"
+msgstr ""
+
+msgid "Invite"
+msgstr ""
+
+msgid "Invite \"%{trimmed}\" by email"
+msgstr ""
+
+msgid "Invite Members"
+msgstr ""
+
+msgid "Invite group"
+msgstr ""
+
+msgid "Invite member"
+msgstr ""
+
+msgid "Invocations"
+msgstr ""
+
+msgid "Is blocked by"
+msgstr ""
+
+msgid "Is this GitLab trial for your company?"
+msgstr ""
+
+msgid "Is using license seat:"
+msgstr ""
+
+msgid "Is using seat"
+msgstr ""
+
+msgid "IssuableStatus|Closed"
+msgstr ""
+
+msgid "IssuableStatus|Closed (%{link})"
+msgstr ""
+
+msgid "IssuableStatus|duplicated"
+msgstr ""
+
+msgid "IssuableStatus|moved"
+msgstr ""
+
+msgid "IssuableStatus|promoted"
+msgstr ""
+
+msgid "Issue"
+msgstr ""
+
+msgid "Issue %{issue_reference} has already been added to epic %{epic_reference}."
+msgstr ""
+
+msgid "Issue Boards"
+msgstr ""
+
+msgid "Issue already promoted to epic."
+msgstr ""
+
+msgid "Issue cannot be found."
+msgstr ""
+
+msgid "Issue events"
+msgstr ""
+
+msgid "Issue first depoloyed to production"
+msgstr ""
+
+msgid "Issue label"
+msgstr ""
+
+msgid "Issue or Merge Request ID is required"
+msgstr ""
+
+msgid "Issue published on status page."
+msgstr ""
+
+msgid "Issue template (optional)"
+msgstr ""
+
+msgid "Issue update failed"
+msgstr ""
+
+msgid "Issue was closed by %{name} %{reason}"
+msgstr ""
+
+msgid "Issue weight"
+msgstr ""
+
+msgid "IssueAnalytics|Age"
+msgstr ""
+
+msgid "IssueAnalytics|Assignees"
+msgstr ""
+
+msgid "IssueAnalytics|Due date"
+msgstr ""
+
+msgid "IssueAnalytics|Failed to load issues. Please try again."
+msgstr ""
+
+msgid "IssueAnalytics|Issue"
+msgstr ""
+
+msgid "IssueAnalytics|Milestone"
+msgstr ""
+
+msgid "IssueAnalytics|Opened by"
+msgstr ""
+
+msgid "IssueAnalytics|Status"
+msgstr ""
+
+msgid "IssueAnalytics|Weight"
+msgstr ""
+
+msgid "IssueBoards|Board"
+msgstr ""
+
+msgid "IssueBoards|Boards"
+msgstr ""
+
+msgid "IssueBoards|Create new board"
+msgstr ""
+
+msgid "IssueBoards|Delete board"
+msgstr ""
+
+msgid "IssueBoards|No matching boards found"
+msgstr ""
+
+msgid "IssueBoards|Some of your boards are hidden, activate a license to see them again."
+msgstr ""
+
+msgid "IssueBoards|Switch board"
+msgstr ""
+
+msgid "IssueTracker|Bugzilla issue tracker"
+msgstr ""
+
+msgid "IssueTracker|Custom issue tracker"
+msgstr ""
+
+msgid "IssueTracker|GitLab issue tracker"
+msgstr ""
+
+msgid "IssueTracker|Redmine issue tracker"
+msgstr ""
+
+msgid "IssueTracker|YouTrack issue tracker"
+msgstr ""
+
+msgid "Issues"
+msgstr ""
+
+msgid "Issues Analytics"
+msgstr ""
+
+msgid "Issues Rate Limits"
+msgstr ""
+
+msgid "Issues and Merge Requests"
+msgstr ""
+
+msgid "Issues can be bugs, tasks or ideas to be discussed. Also, issues are searchable and filterable."
+msgstr ""
+
+msgid "Issues closed"
+msgstr ""
+
+msgid "Issues referenced by merge requests and commits within the default branch will be closed automatically"
+msgstr ""
+
+msgid "Issues successfully imported with the label"
+msgstr ""
+
+msgid "Issues with comments, merge requests with diffs and comments, labels, milestones, snippets, and other project entities"
+msgstr ""
+
+msgid "Issues, merge requests, pushes, and comments."
+msgstr ""
+
+msgid "IssuesAnalytics|After you begin creating issues for your projects, we can start tracking and displaying metrics for them"
+msgstr ""
+
+msgid "IssuesAnalytics|Avg/Month:"
+msgstr ""
+
+msgid "IssuesAnalytics|Issues opened"
+msgstr ""
+
+msgid "IssuesAnalytics|Issues opened per month"
+msgstr ""
+
+msgid "IssuesAnalytics|Last 12 months"
+msgstr ""
+
+msgid "IssuesAnalytics|Sorry, your filter produced no results"
+msgstr ""
+
+msgid "IssuesAnalytics|There are no issues for the projects in your group"
+msgstr ""
+
+msgid "IssuesAnalytics|To widen your search, change or remove filters in the filter bar above"
+msgstr ""
+
+msgid "IssuesAnalytics|Total:"
+msgstr ""
+
+msgid "Issue|Title"
+msgstr ""
+
+msgid "It must have a header row and at least two columns: the first column is the issue title and the second column is the issue description. The separator is automatically detected."
+msgstr ""
+
+msgid "It seems like the Dependency Scanning job ran successfully, but no dependencies have been detected in your project."
+msgstr ""
+
+msgid "It seems that there is currently no available data for code coverage"
+msgstr ""
+
+msgid "It's you"
+msgstr ""
+
+msgid "Iteration"
+msgstr ""
+
+msgid "Iteration changed to"
+msgstr ""
+
+msgid "Iteration removed"
+msgstr ""
+
+msgid "Iteration updated"
+msgstr ""
+
+msgid "Iterations"
+msgstr ""
+
+msgid "Iteration|Dates cannot overlap with other existing Iterations"
+msgstr ""
+
+msgid "Iteration|cannot be in the past"
+msgstr ""
+
+msgid "Iteration|cannot be more than 500 years in the future"
+msgstr ""
+
+msgid "I’m familiar with the basics of project management and DevOps."
+msgstr ""
+
+msgid "I’m not very familiar with the basics of project management and DevOps."
+msgstr ""
+
+msgid "Jaeger URL"
+msgstr ""
+
+msgid "Jaeger tracing"
+msgstr ""
+
+msgid "Jan"
+msgstr ""
+
+msgid "January"
+msgstr ""
+
+msgid "Jira Issues"
+msgstr ""
+
+msgid "Jira import is already running."
+msgstr ""
+
+msgid "Jira integration not configured."
+msgstr ""
+
+msgid "Jira project key is not configured"
+msgstr ""
+
+msgid "Jira project: %{importProject}"
+msgstr ""
+
+msgid "Jira service not configured."
+msgstr ""
+
+msgid "JiraService| on branch %{branch_link}"
+msgstr ""
+
+msgid "JiraService|%{user_link} mentioned this issue in %{entity_link} of %{project_link}%{branch}:{quote}%{entity_message}{quote}"
+msgstr ""
+
+msgid "JiraService|Events for %{noteable_model_name} are disabled."
+msgstr ""
+
+msgid "JiraService|If different from Web URL"
+msgstr ""
+
+msgid "JiraService|Jira API URL"
+msgstr ""
+
+msgid "JiraService|Jira comments will be created when an issue gets referenced in a commit."
+msgstr ""
+
+msgid "JiraService|Jira comments will be created when an issue gets referenced in a merge request."
+msgstr ""
+
+msgid "JiraService|Jira issue tracker"
+msgstr ""
+
+msgid "JiraService|Password or API token"
+msgstr ""
+
+msgid "JiraService|Transition ID(s)"
+msgstr ""
+
+msgid "JiraService|Use , or ; to separate multiple transition IDs"
+msgstr ""
+
+msgid "JiraService|Use a password for server version and an API token for cloud version"
+msgstr ""
+
+msgid "JiraService|Use a username for server version and an email for cloud version"
+msgstr ""
+
+msgid "JiraService|Username or Email"
+msgstr ""
+
+msgid "JiraService|Web URL"
+msgstr ""
+
+msgid "JiraService|transition ids can have only numbers which can be split with , or ;"
+msgstr ""
+
+msgid "Job"
+msgstr ""
+
+msgid "Job Failed #%{build_id}"
+msgstr ""
+
+msgid "Job ID"
+msgstr ""
+
+msgid "Job has been erased"
+msgstr ""
+
+msgid "Job has been successfully erased!"
+msgstr ""
+
+msgid "Job has wrong arguments format."
+msgstr ""
+
+msgid "Job is missing the `model_type` argument."
+msgstr ""
+
+msgid "Job is stuck. Check runners."
+msgstr ""
+
+msgid "Job logs and artifacts"
+msgstr ""
+
+msgid "Job to create self-monitoring project is in progress"
+msgstr ""
+
+msgid "Job to delete self-monitoring project is in progress"
+msgstr ""
+
+msgid "Job was retried"
+msgstr ""
+
+msgid "Jobs"
+msgstr ""
+
+msgid "Job|Browse"
+msgstr ""
+
+msgid "Job|Complete Raw"
+msgstr ""
+
+msgid "Job|Download"
+msgstr ""
+
+msgid "Job|Erase job log"
+msgstr ""
+
+msgid "Job|Job artifacts"
+msgstr ""
+
+msgid "Job|Job has been erased"
+msgstr ""
+
+msgid "Job|Job has been erased by"
+msgstr ""
+
+msgid "Job|Keep"
+msgstr ""
+
+msgid "Job|Pipeline"
+msgstr ""
+
+msgid "Job|Scroll to bottom"
+msgstr ""
+
+msgid "Job|Scroll to top"
+msgstr ""
+
+msgid "Job|Show complete raw"
+msgstr ""
+
+msgid "Job|The artifacts were removed"
+msgstr ""
+
+msgid "Job|The artifacts will be removed"
+msgstr ""
+
+msgid "Job|These artifacts are the latest. They will not be deleted (even if expired) until newer artifacts are available."
+msgstr ""
+
+msgid "Job|This job failed because the necessary resources were not successfully created."
+msgstr ""
+
+msgid "Job|This job is stuck because the project doesn't have any runners online assigned to it."
+msgstr ""
+
+msgid "Job|for"
+msgstr ""
+
+msgid "Job|into"
+msgstr ""
+
+msgid "Job|with"
+msgstr ""
+
+msgid "Join Zoom meeting"
+msgstr ""
+
+msgid "Jul"
+msgstr ""
+
+msgid "July"
+msgstr ""
+
+msgid "Jump to first unresolved thread"
+msgstr ""
+
+msgid "Jump to next unresolved thread"
+msgstr ""
+
+msgid "Jun"
+msgstr ""
+
+msgid "June"
+msgstr ""
+
+msgid "Just me"
+msgstr ""
+
+msgid "Keep divergent refs"
+msgstr ""
+
+msgid "Kerberos access denied"
+msgstr ""
+
+msgid "Key"
+msgstr ""
+
+msgid "Key (PEM)"
+msgstr ""
+
+msgid "Key: %{key}"
+msgstr ""
+
+msgid "Keyboard shortcuts"
+msgstr ""
+
+msgid "Keys"
+msgstr ""
+
+msgid "Ki"
+msgstr ""
+
+msgid "Kubernetes"
+msgstr ""
+
+msgid "Kubernetes API returned status code: %{error_code}"
+msgstr ""
+
+msgid "Kubernetes Cluster"
+msgstr ""
+
+msgid "Kubernetes Clusters"
+msgstr ""
+
+msgid "Kubernetes cluster"
+msgstr ""
+
+msgid "Kubernetes cluster creation time exceeds timeout; %{timeout}"
+msgstr ""
+
+msgid "Kubernetes cluster integration and resources are being removed."
+msgstr ""
+
+msgid "Kubernetes cluster integration was successfully removed."
+msgstr ""
+
+msgid "Kubernetes cluster was successfully updated."
+msgstr ""
+
+msgid "Kubernetes deployment not found"
+msgstr ""
+
+msgid "Kubernetes error: %{error_code}"
+msgstr ""
+
+msgid "Kubernetes popover"
+msgstr ""
+
+msgid "LDAP"
+msgstr ""
+
+msgid "LDAP Synchronization"
+msgstr ""
+
+msgid "LDAP settings"
+msgstr ""
+
+msgid "LDAP settings updated"
+msgstr ""
+
+msgid "LDAP sync in progress. This could take a few minutes. Refresh the page to see the changes."
+msgstr ""
+
+msgid "LFS"
+msgstr ""
+
+msgid "LFS objects"
+msgstr ""
+
+msgid "LFSStatus|Disabled"
+msgstr ""
+
+msgid "LFSStatus|Enabled"
+msgstr ""
+
+msgid "LICENSE"
+msgstr ""
+
+msgid "Label"
+msgstr ""
+
+msgid "Label actions dropdown"
+msgstr ""
+
+msgid "Label lists show all issues with the selected label."
+msgstr ""
+
+msgid "Label was created"
+msgstr ""
+
+msgid "Label was removed"
+msgstr ""
+
+msgid "Label was successfully updated."
+msgstr ""
+
+msgid "LabelSelect|%{firstLabelName} +%{remainingLabelCount} more"
+msgstr ""
+
+msgid "LabelSelect|%{labelsString}, and %{remainingLabelCount} more"
+msgstr ""
+
+msgid "LabelSelect|Labels"
+msgstr ""
+
+msgid "Labels"
+msgstr ""
+
+msgid "Labels can be applied to %{features}. Group labels are available for any project within the group."
+msgstr ""
+
+msgid "Labels can be applied to issues and merge requests to categorize them."
+msgstr ""
+
+msgid "Labels can be applied to issues and merge requests."
+msgstr ""
+
+msgid "Labels|<span>Promote label</span> %{labelTitle} <span>to Group Label?</span>"
+msgstr ""
+
+msgid "Labels|Promote Label"
+msgstr ""
+
+msgid "Labels|Promoting %{labelTitle} will make it available for all projects inside %{groupName}. Existing project labels with the same title will be merged. If a group label with the same title exists, it will also be merged. This action cannot be reversed."
+msgstr ""
+
+msgid "Labels|and %{count} more"
+msgstr ""
+
+msgid "Language"
+msgstr ""
+
+msgid "Large File Storage"
+msgstr ""
+
+msgid "Last %d day"
+msgid_plural "Last %d days"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "Last %{days} days"
+msgstr ""
+
+msgid "Last Accessed On"
+msgstr ""
+
+msgid "Last Name is too long (maximum is %{max_length} characters)."
+msgstr ""
+
+msgid "Last Pipeline"
+msgstr ""
+
+msgid "Last Seen"
+msgstr ""
+
+msgid "Last accessed on"
+msgstr ""
+
+msgid "Last activity"
+msgstr ""
+
+msgid "Last commit"
+msgstr ""
+
+msgid "Last contact"
+msgstr ""
+
+msgid "Last edited %{date}"
+msgstr ""
+
+msgid "Last edited by %{name}"
+msgstr ""
+
+msgid "Last name"
+msgstr ""
+
+msgid "Last reply by"
+msgstr ""
+
+msgid "Last repository check (%{last_check_timestamp}) failed. See the 'repocheck.log' file for error messages."
+msgstr ""
+
+msgid "Last repository check run"
+msgstr ""
+
+msgid "Last seen"
+msgstr ""
+
+msgid "Last successful sync"
+msgstr ""
+
+msgid "Last successful update"
+msgstr ""
+
+msgid "Last time verified"
+msgstr ""
+
+msgid "Last update"
+msgstr ""
+
+msgid "Last update attempt"
+msgstr ""
+
+msgid "Last updated"
+msgstr ""
+
+msgid "Last used"
+msgstr ""
+
+msgid "Last used on:"
+msgstr ""
+
+msgid "LastCommit|authored"
+msgstr ""
+
+msgid "LastPushEvent|You pushed to"
+msgstr ""
+
+msgid "LastPushEvent|at"
+msgstr ""
+
+msgid "Latest changes"
+msgstr ""
+
+msgid "Latest pipeline for the most recent commit on this branch"
+msgstr ""
+
+msgid "Lead"
+msgstr ""
+
+msgid "Lead Time"
+msgstr ""
+
+msgid "Learn GitLab"
+msgstr ""
+
+msgid "Learn More"
+msgstr ""
+
+msgid "Learn how to %{link_start}contribute to the built-in templates%{link_end}"
+msgstr ""
+
+msgid "Learn how to %{no_packages_link_start}publish and share your packages%{no_packages_link_end} with GitLab."
+msgstr ""
+
+msgid "Learn how to enable synchronization"
+msgstr ""
+
+msgid "Learn more"
+msgstr ""
+
+msgid "Learn more about Auto DevOps"
+msgstr ""
+
+msgid "Learn more about Kubernetes"
+msgstr ""
+
+msgid "Learn more about License-Check"
+msgstr ""
+
+msgid "Learn more about Vulnerability-Check"
+msgstr ""
+
+msgid "Learn more about Web Terminal"
+msgstr ""
+
+msgid "Learn more about X.509 signed commits"
+msgstr ""
+
+msgid "Learn more about adding certificates to your project by following the %{docs_link_start}documentation on GitLab Pages%{docs_link_end}."
+msgstr ""
+
+msgid "Learn more about approvals."
+msgstr ""
+
+msgid "Learn more about custom project templates"
+msgstr ""
+
+msgid "Learn more about deploying to AWS"
+msgstr ""
+
+msgid "Learn more about deploying to a cluster"
+msgstr ""
+
+msgid "Learn more about group-level project templates"
+msgstr ""
+
+msgid "Learn more about job dependencies"
+msgstr ""
+
+msgid "Learn more about signing commits"
+msgstr ""
+
+msgid "Learn more about the dependency list"
+msgstr ""
+
+msgid "Learn more in the"
+msgstr ""
+
+msgid "Learn more in the|pipeline schedules documentation"
+msgstr ""
+
+msgid "Leave"
+msgstr ""
+
+msgid "Leave Admin Mode"
+msgstr ""
+
+msgid "Leave blank for no limit. Once set, existing personal access tokens may be revoked."
+msgstr ""
+
+msgid "Leave edit mode? All unsaved changes will be lost."
+msgstr ""
+
+msgid "Leave group"
+msgstr ""
+
+msgid "Leave project"
+msgstr ""
+
+msgid "Leave the \"File type\" and \"Delivery method\" options on their default values."
+msgstr ""
+
+msgid "Leave zen mode"
+msgstr ""
+
+msgid "Let's Encrypt does not accept emails on example.com"
+msgstr ""
+
+msgid "Let's Encrypt is a free, automated, and open certificate authority (CA) that gives digital certificates in order to enable HTTPS (SSL/TLS) for websites. Learn more about Let's Encrypt configuration by following the %{docs_link_start}documentation on GitLab Pages%{docs_link_end}."
+msgstr ""
+
+msgid "License"
+msgstr ""
+
+msgid "License Compliance"
+msgstr ""
+
+msgid "License History"
+msgstr ""
+
+msgid "License ID:"
+msgstr ""
+
+msgid "License URL"
+msgstr ""
+
+msgid "License-Check"
+msgstr ""
+
+msgid "LicenseCompliance|%{docLinkStart}License Approvals%{docLinkEnd} are active"
+msgstr ""
+
+msgid "LicenseCompliance|%{docLinkStart}License Approvals%{docLinkEnd} are inactive"
+msgstr ""
+
+msgid "LicenseCompliance|Add a license"
+msgstr ""
+
+msgid "LicenseCompliance|Add license and related policy"
+msgstr ""
+
+msgid "LicenseCompliance|Allow"
+msgstr ""
+
+msgid "LicenseCompliance|Allowed"
+msgstr ""
+
+msgid "LicenseCompliance|Cancel"
+msgstr ""
+
+msgid "LicenseCompliance|Denied"
+msgstr ""
+
+msgid "LicenseCompliance|Deny"
+msgstr ""
+
+msgid "LicenseCompliance|Learn more about %{linkStart}License Approvals%{linkEnd}"
+msgstr ""
+
+msgid "LicenseCompliance|License"
+msgstr ""
+
+msgid "LicenseCompliance|License Approvals"
+msgstr ""
+
+msgid "LicenseCompliance|License Compliance detected %d license and policy violation for the source branch only; approval required"
+msgid_plural "LicenseCompliance|License Compliance detected %d licenses and policy violations for the source branch only; approval required"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "LicenseCompliance|License Compliance detected %d license for the source branch only"
+msgid_plural "LicenseCompliance|License Compliance detected %d licenses for the source branch only"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "LicenseCompliance|License Compliance detected %d new license"
+msgid_plural "LicenseCompliance|License Compliance detected %d new licenses"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "LicenseCompliance|License Compliance detected %d new license and policy violation; approval required"
+msgid_plural "LicenseCompliance|License Compliance detected %d new licenses and policy violations; approval required"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "LicenseCompliance|License Compliance detected no licenses for the source branch only"
+msgstr ""
+
+msgid "LicenseCompliance|License Compliance detected no new licenses"
+msgstr ""
+
+msgid "LicenseCompliance|License details"
+msgstr ""
+
+msgid "LicenseCompliance|License name"
+msgstr ""
+
+msgid "LicenseCompliance|License review"
+msgstr ""
+
+msgid "LicenseCompliance|Packages"
+msgstr ""
+
+msgid "LicenseCompliance|Remove license"
+msgstr ""
+
+msgid "LicenseCompliance|Remove license?"
+msgstr ""
+
+msgid "LicenseCompliance|Submit"
+msgstr ""
+
+msgid "LicenseCompliance|There are currently no approved or blacklisted licenses in this project."
+msgstr ""
+
+msgid "LicenseCompliance|There are currently no approved or blacklisted licenses that match in this project."
+msgstr ""
+
+msgid "LicenseCompliance|This license already exists in this project."
+msgstr ""
+
+msgid "LicenseCompliance|URL"
+msgstr ""
+
+msgid "LicenseCompliance|You are about to remove the license, %{name}, from this project."
+msgstr ""
+
+msgid "LicenseManagement|Allowed"
+msgstr ""
+
+msgid "LicenseManagement|Denied"
+msgstr ""
+
+msgid "LicenseManagement|Uncategorized"
+msgstr ""
+
+msgid "Licensed Features"
+msgstr ""
+
+msgid "Licensed to"
+msgstr ""
+
+msgid "Licenses"
+msgstr ""
+
+msgid "Licenses|%{remainingComponentsCount} more"
+msgstr ""
+
+msgid "Licenses|Component"
+msgstr ""
+
+msgid "Licenses|Components"
+msgstr ""
+
+msgid "Licenses|Detected in Project"
+msgstr ""
+
+msgid "Licenses|Detected licenses that are out-of-compliance with the project's assigned policies"
+msgstr ""
+
+msgid "Licenses|Displays licenses detected in the project, based on the %{linkStart}latest successful%{linkEnd} scan"
+msgstr ""
+
+msgid "Licenses|Error fetching the license list. Please check your network connection and try again."
+msgstr ""
+
+msgid "Licenses|Learn more about license compliance"
+msgstr ""
+
+msgid "Licenses|License Compliance"
+msgstr ""
+
+msgid "Licenses|Name"
+msgstr ""
+
+msgid "Licenses|Policies"
+msgstr ""
+
+msgid "Licenses|Policy"
+msgstr ""
+
+msgid "Licenses|Policy violation: denied"
+msgstr ""
+
+msgid "Licenses|Specified policies in this project"
+msgstr ""
+
+msgid "Licenses|The license list details information about the licenses used within your project."
+msgstr ""
+
+msgid "Licenses|View license details for your project"
+msgstr ""
+
+msgid "License|Buy license"
+msgstr ""
+
+msgid "License|License"
+msgstr ""
+
+msgid "License|Licensed user count exceeded"
+msgstr ""
+
+msgid "License|You can restore access to the Gold features at any time by upgrading."
+msgstr ""
+
+msgid "License|You can start a free trial of GitLab Ultimate without any obligation or payment details."
+msgstr ""
+
+msgid "License|You do not have a license."
+msgstr ""
+
+msgid "License|Your License"
+msgstr ""
+
+msgid "License|Your free trial of GitLab Ultimate expired on %{trial_ends_on}."
+msgstr ""
+
+msgid "License|Your instance has exceeded your subscription's number of licensed users by %{extra_users_count}. You can continue to add more users and we'll include the overage in your next bill."
+msgstr ""
+
+msgid "Limit display of time tracking units to hours."
+msgstr ""
+
+msgid "Limit namespaces and projects that can be indexed"
+msgstr ""
+
+msgid "Limited to showing %d event at most"
+msgid_plural "Limited to showing %d events at most"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "Line changes"
+msgstr ""
+
+msgid "Link Prometheus monitoring to GitLab."
+msgstr ""
+
+msgid "Link copied"
+msgstr ""
+
+msgid "Link title"
+msgstr ""
+
+msgid "Link title is required"
+msgstr ""
+
+msgid "Linked emails (%{email_count})"
+msgstr ""
+
+msgid "Linked issues"
+msgstr ""
+
+msgid "LinkedIn"
+msgstr ""
+
+msgid "LinkedPipelines|%{counterLabel} more downstream pipelines"
+msgstr ""
+
+msgid "Links"
+msgstr ""
+
+msgid "List"
+msgstr ""
+
+msgid "List Your Gitea Repositories"
+msgstr ""
+
+msgid "List available repositories"
+msgstr ""
+
+msgid "List of IPs and CIDRs of allowed secondary nodes. Comma-separated, e.g. \"1.1.1.1, 2.2.2.0/24\""
+msgstr ""
+
+msgid "List settings"
+msgstr ""
+
+msgid "List the merge requests that must be merged before this one."
+msgstr ""
+
+msgid "List view"
+msgstr ""
+
+msgid "List your Bitbucket Server repositories"
+msgstr ""
+
+msgid "Lists"
+msgstr ""
+
+msgid "Live preview"
+msgstr ""
+
+msgid "Loading"
+msgstr ""
+
+msgid "Loading contribution stats for group members"
+msgstr ""
+
+msgid "Loading files, directories, and submodules in the path %{path} for commit reference %{ref}"
+msgstr ""
+
+msgid "Loading functions timed out. Please reload the page to try again."
+msgstr ""
+
+msgid "Loading issues"
+msgstr ""
+
+msgid "Loading snippet"
+msgstr ""
+
+msgid "Loading the GitLab IDE..."
+msgstr ""
+
+msgid "Loading..."
+msgstr ""
+
+msgid "Loading…"
+msgstr ""
+
+msgid "Local IP addresses and domain names that hooks and services may access."
+msgstr ""
+
+msgid "Localization"
+msgstr ""
+
+msgid "Location"
+msgstr ""
+
+msgid "Lock"
+msgstr ""
+
+msgid "Lock %{issuableDisplayName}"
+msgstr ""
+
+msgid "Lock memberships to LDAP synchronization"
+msgstr ""
+
+msgid "Lock not found"
+msgstr ""
+
+msgid "Lock the discussion"
+msgstr ""
+
+msgid "Lock this %{issuableDisplayName}? Only <strong>project members</strong> will be able to comment."
+msgstr ""
+
+msgid "Lock to current projects"
+msgstr ""
+
+msgid "Locked"
+msgstr ""
+
+msgid "Locked Files"
+msgstr ""
+
+msgid "Locked by %{fileLockUserName}"
+msgstr ""
+
+msgid "Locked the discussion."
+msgstr ""
+
+msgid "Locked to current projects"
+msgstr ""
+
+msgid "Locks give the ability to lock specific file or folder."
+msgstr ""
+
+msgid "Locks the discussion."
+msgstr ""
+
+msgid "Login with smartcard"
+msgstr ""
+
+msgid "Logo was successfully removed."
+msgstr ""
+
+msgid "Logs"
+msgstr ""
+
+msgid "Logs|To see the logs, deploy your code to an environment."
+msgstr ""
+
+msgid "Low vulnerabilities present"
+msgstr ""
+
+msgid "MB"
+msgstr ""
+
+msgid "MD5"
+msgstr ""
+
+msgid "MERGED"
+msgstr ""
+
+msgid "MR widget|Take a look at our %{beginnerLinkStart}Beginner's Guide to Continuous Integration%{beginnerLinkEnd} and our %{exampleLinkStart}examples of GitLab CI/CD%{exampleLinkEnd} to see all the cool stuff you can do with it."
+msgstr ""
+
+msgid "MR widget|The pipeline will now run automatically every time you commit code. Pipelines are useful for deploying static web pages, detecting vulnerabilities in dependencies, static or dynamic application security testing (SAST and DAST), and so much more!"
+msgstr ""
+
+msgid "MRApprovals|Approvals"
+msgstr ""
+
+msgid "MRApprovals|Approved by"
+msgstr ""
+
+msgid "MRApprovals|Approvers"
+msgstr ""
+
+msgid "MRDiff|Show changes only"
+msgstr ""
+
+msgid "MRDiff|Show full file"
+msgstr ""
+
+msgid "Made this issue confidential."
+msgstr ""
+
+msgid "Maintenance mode"
+msgstr ""
+
+msgid "Make and review changes in the browser with the Web IDE"
+msgstr ""
+
+msgid "Make everyone on your team more productive regardless of their location. GitLab Geo creates read-only mirrors of your GitLab instance so you can reduce the time it takes to clone and fetch large repos."
+msgstr ""
+
+msgid "Make issue confidential"
+msgstr ""
+
+msgid "Make sure you save it - you won't be able to access it again."
+msgstr ""
+
+msgid "Make sure you're logged into the account that owns the projects you'd like to import."
+msgstr ""
+
+msgid "Make this epic confidential"
+msgstr ""
+
+msgid "Makes this issue confidential."
+msgstr ""
+
+msgid "Manage"
+msgstr ""
+
+msgid "Manage Web IDE features"
+msgstr ""
+
+msgid "Manage access"
+msgstr ""
+
+msgid "Manage all notifications"
+msgstr ""
+
+msgid "Manage applications that can use GitLab as an OAuth provider, and applications that you've authorized to use your account."
+msgstr ""
+
+msgid "Manage applications that you've authorized to use your account."
+msgstr ""
+
+msgid "Manage group labels"
+msgstr ""
+
+msgid "Manage labels"
+msgstr ""
+
+msgid "Manage milestones"
+msgstr ""
+
+msgid "Manage project labels"
+msgstr ""
+
+msgid "Manage storage usage"
+msgstr ""
+
+msgid "Manage two-factor authentication"
+msgstr ""
+
+msgid "Manage your license"
+msgstr ""
+
+msgid "Managed Account"
+msgstr ""
+
+msgid "Manifest"
+msgstr ""
+
+msgid "Manifest file import"
+msgstr ""
+
+msgid "Manifest import"
+msgstr ""
+
+msgid "Manual job"
+msgstr ""
+
+msgid "ManualOrdering|Couldn't save the order of the issues"
+msgstr ""
+
+msgid "Map a FogBugz account ID to a GitLab user"
+msgstr ""
+
+msgid "Map a Google Code user to a GitLab user"
+msgstr ""
+
+msgid "Map a Google Code user to a full email address"
+msgstr ""
+
+msgid "Map a Google Code user to a full name"
+msgstr ""
+
+msgid "Mar"
+msgstr ""
+
+msgid "March"
+msgstr ""
+
+msgid "Mark To Do as done"
+msgstr ""
+
+msgid "Mark as done"
+msgstr ""
+
+msgid "Mark as resolved"
+msgstr ""
+
+msgid "Mark this issue as a duplicate of another issue"
+msgstr ""
+
+msgid "Mark this issue as related to another issue"
+msgstr ""
+
+msgid "Markdown"
+msgstr ""
+
+msgid "Markdown enabled"
+msgstr ""
+
+msgid "Markdown is supported"
+msgstr ""
+
+msgid "Marked To Do as done."
+msgstr ""
+
+msgid "Marked this %{noun} as Work In Progress."
+msgstr ""
+
+msgid "Marked this issue as a duplicate of %{duplicate_param}."
+msgstr ""
+
+msgid "Marked this issue as related to %{issue_ref}."
+msgstr ""
+
+msgid "Marks To Do as done."
+msgstr ""
+
+msgid "Marks this %{noun} as Work In Progress."
+msgstr ""
+
+msgid "Marks this issue as a duplicate of %{duplicate_reference}."
+msgstr ""
+
+msgid "Marks this issue as related to %{issue_ref}."
+msgstr ""
+
+msgid "Mask variable"
+msgstr ""
+
+msgid "Match not found; try refining your search query."
+msgstr ""
+
+msgid "MattermostService|Add to Mattermost"
+msgstr ""
+
+msgid "MattermostService|Command trigger word"
+msgstr ""
+
+msgid "MattermostService|Fill in the word that works best for your team."
+msgstr ""
+
+msgid "MattermostService|Request URL"
+msgstr ""
+
+msgid "MattermostService|Request method"
+msgstr ""
+
+msgid "MattermostService|Response icon"
+msgstr ""
+
+msgid "MattermostService|Response username"
+msgstr ""
+
+msgid "MattermostService|See list of available commands in Mattermost after setting up this service, by entering"
+msgstr ""
+
+msgid "MattermostService|Suggestions:"
+msgstr ""
+
+msgid "MattermostService|This service allows users to perform common operations on this project by entering slash commands in Mattermost."
+msgstr ""
+
+msgid "Maven Metadata"
+msgstr ""
+
+msgid "Max access level"
+msgstr ""
+
+msgid "Max seats used"
+msgstr ""
+
+msgid "Maximum Users:"
+msgstr ""
+
+msgid "Maximum allowable lifetime for personal access token (days)"
+msgstr ""
+
+msgid "Maximum artifacts size (MB)"
+msgstr ""
+
+msgid "Maximum attachment size (MB)"
+msgstr ""
+
+msgid "Maximum bulk request size (MiB)"
+msgstr ""
+
+msgid "Maximum capacity"
+msgstr ""
+
+msgid "Maximum concurrency of Elasticsearch bulk requests per indexing operation."
+msgstr ""
+
+msgid "Maximum delay (Minutes)"
+msgstr ""
+
+msgid "Maximum duration of a session."
+msgstr ""
+
+msgid "Maximum field length"
+msgstr ""
+
+msgid "Maximum file size is 2MB. Please select a smaller file."
+msgstr ""
+
+msgid "Maximum import size (MB)"
+msgstr ""
+
+msgid "Maximum job timeout"
+msgstr ""
+
+msgid "Maximum job timeout has a value which could not be accepted"
+msgstr ""
+
+msgid "Maximum lifetime allowable for Personal Access Tokens is active, your expire date must be set before %{maximum_allowable_date}."
+msgstr ""
+
+msgid "Maximum namespace storage (MB)"
+msgstr ""
+
+msgid "Maximum number of %{name} (%{count}) exceeded"
+msgstr ""
+
+msgid "Maximum number of comments exceeded"
+msgstr ""
+
+msgid "Maximum number of mirrors that can be synchronizing at the same time."
+msgstr ""
+
+msgid "Maximum number of projects."
+msgstr ""
+
+msgid "Maximum page reached"
+msgstr ""
+
+msgid "Maximum push size (MB)"
+msgstr ""
+
+msgid "Maximum size limit for a single commit."
+msgstr ""
+
+msgid "Maximum size limit for each repository."
+msgstr ""
+
+msgid "Maximum size of Elasticsearch bulk indexing requests."
+msgstr ""
+
+msgid "Maximum size of import files."
+msgstr ""
+
+msgid "Maximum size of individual attachments in comments."
+msgstr ""
+
+msgid "Maximum time between updates that a mirror can have when scheduled to synchronize."
+msgstr ""
+
+msgid "May"
+msgstr ""
+
+msgid "Measured in bytes of code. Excludes generated and vendored code."
+msgstr ""
+
+msgid "Median"
+msgstr ""
+
+msgid "Medium vulnerabilities present"
+msgstr ""
+
+msgid "Member lock"
+msgstr ""
+
+msgid "Member since %{date}"
+msgstr ""
+
+msgid "Members"
+msgstr ""
+
+msgid "Members can be added by project <i>Maintainers</i> or <i>Owners</i>"
+msgstr ""
+
+msgid "Members of <strong>%{project_name}</strong>"
+msgstr ""
+
+msgid "Members of a group may only view projects they have permission to access"
+msgstr ""
+
+msgid "Members with access to %{strong_start}%{group_name}%{strong_end}"
+msgstr ""
+
+msgid "Members with pending access to %{strong_start}%{group_name}%{strong_end}"
+msgstr ""
+
+msgid "Memory Usage"
+msgstr ""
+
+msgid "Merge"
+msgstr ""
+
+msgid "Merge (when the pipeline succeeds)"
+msgstr ""
+
+msgid "Merge Conflicts"
+msgstr ""
+
+msgid "Merge Request"
+msgstr ""
+
+msgid "Merge Request Approvals"
+msgstr ""
+
+msgid "Merge Request Commits"
+msgstr ""
+
+msgid "Merge Requests"
+msgstr ""
+
+msgid "Merge Requests created"
+msgstr ""
+
+msgid "Merge Requests in Review"
+msgstr ""
+
+msgid "Merge automatically (%{strategy})"
+msgstr ""
+
+msgid "Merge commit message"
+msgstr ""
+
+msgid "Merge events"
+msgstr ""
+
+msgid "Merge immediately"
+msgstr ""
+
+msgid "Merge in progress"
+msgstr ""
+
+msgid "Merge options"
+msgstr ""
+
+msgid "Merge request"
+msgstr ""
+
+msgid "Merge request %{iid} authored by %{authorName}"
+msgstr ""
+
+msgid "Merge request %{mr_link} was reviewed by %{mr_author}"
+msgstr ""
+
+msgid "Merge request approvals"
+msgstr ""
+
+msgid "Merge request approvals allow you to set the number of necessary approvals and predefine a list of approvers that will need to approve every merge request in a project."
+msgstr ""
+
+msgid "Merge request dependencies"
+msgstr ""
+
+msgid "Merge requests"
+msgstr ""
+
+msgid "Merge requests approvals"
+msgstr ""
+
+msgid "Merge requests are a place to propose changes you've made to a project and discuss those changes with others"
+msgstr ""
+
+msgid "Merge requests are read-only in a secondary Geo node"
+msgstr ""
+
+msgid "Merge when pipeline succeeds"
+msgstr ""
+
+msgid "MergeConflict|Commit to source branch"
+msgstr ""
+
+msgid "MergeConflict|Committing..."
+msgstr ""
+
+msgid "MergeConflict|HEAD//our changes"
+msgstr ""
+
+msgid "MergeConflict|Use ours"
+msgstr ""
+
+msgid "MergeConflict|Use theirs"
+msgstr ""
+
+msgid "MergeConflict|conflict"
+msgstr ""
+
+msgid "MergeConflict|conflicts"
+msgstr ""
+
+msgid "MergeConflict|origin//their changes"
+msgstr ""
+
+msgid "MergeRequestDiffs|Commenting on lines %{selectStart}start%{selectEnd} to %{end}"
+msgstr ""
+
+msgid "MergeRequestDiffs|Select comment starting line"
+msgstr ""
+
+msgid "MergeRequests|Add a reply"
+msgstr ""
+
+msgid "MergeRequests|An error occurred while checking whether another squash is in progress."
+msgstr ""
+
+msgid "MergeRequests|An error occurred while saving the draft comment."
+msgstr ""
+
+msgid "MergeRequests|Failed to squash. Should be done manually."
+msgstr ""
+
+msgid "MergeRequests|Jump to next unresolved thread"
+msgstr ""
+
+msgid "MergeRequests|Reply..."
+msgstr ""
+
+msgid "MergeRequests|Resolve this thread in a new issue"
+msgstr ""
+
+msgid "MergeRequests|Saving the comment failed"
+msgstr ""
+
+msgid "MergeRequests|Squash task canceled: another squash is already in progress."
+msgstr ""
+
+msgid "MergeRequests|This project does not allow squashing commits when merge requests are accepted."
+msgstr ""
+
+msgid "MergeRequests|Thread stays resolved"
+msgstr ""
+
+msgid "MergeRequests|Thread stays unresolved"
+msgstr ""
+
+msgid "MergeRequests|Thread will be resolved"
+msgstr ""
+
+msgid "MergeRequests|Thread will be unresolved"
+msgstr ""
+
+msgid "MergeRequests|Toggle comments for this file"
+msgstr ""
+
+msgid "MergeRequests|View file @ %{commitId}"
+msgstr ""
+
+msgid "MergeRequests|View replaced file @ %{commitId}"
+msgstr ""
+
+msgid "MergeRequests|commented on commit %{commitLink}"
+msgstr ""
+
+msgid "MergeRequests|started a thread"
+msgstr ""
+
+msgid "MergeRequests|started a thread on %{linkStart}an old version of the diff%{linkEnd}"
+msgstr ""
+
+msgid "MergeRequests|started a thread on %{linkStart}the diff%{linkEnd}"
+msgstr ""
+
+msgid "MergeRequests|started a thread on an outdated change in commit %{linkStart}%{commitDisplay}%{linkEnd}"
+msgstr ""
+
+msgid "MergeRequests|started a thread on commit %{linkStart}%{commitDisplay}%{linkEnd}"
+msgstr ""
+
+msgid "MergeRequest|Compare %{target} and %{source}"
+msgstr ""
+
+msgid "MergeRequest|Error dismissing suggestion popover. Please try again."
+msgstr ""
+
+msgid "MergeRequest|Error loading full diff. Please try again."
+msgstr ""
+
+msgid "MergeRequest|No files found"
+msgstr ""
+
+msgid "MergeRequest|Search files (%{modifier_key}P)"
+msgstr ""
+
+msgid "Merged"
+msgstr ""
+
+msgid "Merged MRs"
+msgstr ""
+
+msgid "Merged branches are being deleted. This can take some time depending on the number of branches. Please refresh the page to see changes."
+msgstr ""
+
+msgid "Merged this merge request."
+msgstr ""
+
+msgid "Merges this merge request immediately."
+msgstr ""
+
+msgid "Merges this merge request when the pipeline succeeds."
+msgstr ""
+
+msgid "Merging immediately isn't recommended as it may negatively impact the existing merge train. Read the %{docsLinkStart}documentation%{docsLinkEnd} for more information."
+msgstr ""
+
+msgid "Messages"
+msgstr ""
+
+msgid "Method"
+msgstr ""
+
+msgid "Metric was successfully added."
+msgstr ""
+
+msgid "Metric was successfully updated."
+msgstr ""
+
+msgid "Metric:"
+msgstr ""
+
+msgid "MetricChart|Please select a metric"
+msgstr ""
+
+msgid "MetricChart|Selected"
+msgstr ""
+
+msgid "Metrics"
+msgstr ""
+
+msgid "Metrics - Grafana"
+msgstr ""
+
+msgid "Metrics - Prometheus"
+msgstr ""
+
+msgid "Metrics Dashboard"
+msgstr ""
+
+msgid "Metrics Dashboard YAML definition"
+msgstr ""
+
+msgid "Metrics Dashboard YAML definition is invalid:"
+msgstr ""
+
+msgid "Metrics Dashboard YAML definition is valid."
+msgstr ""
+
+msgid "Metrics and profiling"
+msgstr ""
+
+msgid "Metrics::Dashboard::Annotation|Annotation can't belong to both a cluster and an environment at the same time"
+msgstr ""
+
+msgid "Metrics::Dashboard::Annotation|Annotation has not been deleted"
+msgstr ""
+
+msgid "Metrics::Dashboard::Annotation|Annotation must belong to a cluster or an environment"
+msgstr ""
+
+msgid "Metrics::Dashboard::Annotation|Dashboard with requested path can not be found"
+msgstr ""
+
+msgid "Metrics::Dashboard::Annotation|You are not authorized to create annotation for selected cluster"
+msgstr ""
+
+msgid "Metrics::Dashboard::Annotation|You are not authorized to create annotation for selected environment"
+msgstr ""
+
+msgid "Metrics::Dashboard::Annotation|You are not authorized to delete this annotation"
+msgstr ""
+
+msgid "Metrics::Dashboard::Annotation|can't be before starting_at time"
+msgstr ""
+
+msgid "Metrics::UsersStarredDashboards|Dashboard with requested path can not be found"
+msgstr ""
+
+msgid "Metrics::UsersStarredDashboards|You are not authorized to add star to this dashboard"
+msgstr ""
+
+msgid "MetricsSettings|Add a button to the metrics dashboard linking directly to your existing external dashboard."
+msgstr ""
+
+msgid "MetricsSettings|Choose whether to display dashboard metrics in UTC or the user's local timezone."
+msgstr ""
+
+msgid "MetricsSettings|Dashboard timezone"
+msgstr ""
+
+msgid "MetricsSettings|External dashboard URL"
+msgstr ""
+
+msgid "MetricsSettings|Manage Metrics Dashboard settings."
+msgstr ""
+
+msgid "MetricsSettings|Metrics Dashboard"
+msgstr ""
+
+msgid "MetricsSettings|UTC (Coordinated Universal Time)"
+msgstr ""
+
+msgid "MetricsSettings|User's local timezone"
+msgstr ""
+
+msgid "Metrics|Add metric"
+msgstr ""
+
+msgid "Metrics|Avg"
+msgstr ""
+
+msgid "Metrics|Check out the CI/CD documentation on deploying to an environment"
+msgstr ""
+
+msgid "Metrics|Create custom dashboard %{fileName}"
+msgstr ""
+
+msgid "Metrics|Create metric"
+msgstr ""
+
+msgid "Metrics|Current"
+msgstr ""
+
+msgid "Metrics|Delete metric"
+msgstr ""
+
+msgid "Metrics|Delete metric?"
+msgstr ""
+
+msgid "Metrics|Duplicate"
+msgstr ""
+
+msgid "Metrics|Duplicate dashboard"
+msgstr ""
+
+msgid "Metrics|Duplicating..."
+msgstr ""
+
+msgid "Metrics|Edit metric"
+msgid_plural "Metrics|Edit metrics"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "Metrics|Expand panel"
+msgstr ""
+
+msgid "Metrics|For grouping similar metrics"
+msgstr ""
+
+msgid "Metrics|Go back (Esc)"
+msgstr ""
+
+msgid "Metrics|Invalid time range, please verify."
+msgstr ""
+
+msgid "Metrics|Label of the y-axis (usually the unit). The x-axis always represents time."
+msgstr ""
+
+msgid "Metrics|Legend label (optional)"
+msgstr ""
+
+msgid "Metrics|Link contains an invalid time window, please verify the link to see the requested time range."
+msgstr ""
+
+msgid "Metrics|Link contains invalid chart information, please verify the link to see the expanded panel."
+msgstr ""
+
+msgid "Metrics|Manage chart links"
+msgstr ""
+
+msgid "Metrics|Max"
+msgstr ""
+
+msgid "Metrics|Min"
+msgstr ""
+
+msgid "Metrics|Must be a valid PromQL query."
+msgstr ""
+
+msgid "Metrics|New metric"
+msgstr ""
+
+msgid "Metrics|PromQL query is valid"
+msgstr ""
+
+msgid "Metrics|Prometheus Query Documentation"
+msgstr ""
+
+msgid "Metrics|Refresh dashboard"
+msgstr ""
+
+msgid "Metrics|Select a value"
+msgstr ""
+
+msgid "Metrics|Star dashboard"
+msgstr ""
+
+msgid "Metrics|There was an error creating the dashboard."
+msgstr ""
+
+msgid "Metrics|There was an error creating the dashboard. %{error}"
+msgstr ""
+
+msgid "Metrics|There was an error fetching annotations. Please try again."
+msgstr ""
+
+msgid "Metrics|There was an error fetching the environments data, please try again"
+msgstr ""
+
+msgid "Metrics|There was an error getting annotations information."
+msgstr ""
+
+msgid "Metrics|There was an error getting dashboard validation warnings information."
+msgstr ""
+
+msgid "Metrics|There was an error getting deployment information."
+msgstr ""
+
+msgid "Metrics|There was an error getting environments information."
+msgstr ""
+
+msgid "Metrics|There was an error getting options for variable \"%{name}\"."
+msgstr ""
+
+msgid "Metrics|There was an error trying to validate your query"
+msgstr ""
+
+msgid "Metrics|There was an error while retrieving metrics"
+msgstr ""
+
+msgid "Metrics|There was an error while retrieving metrics. %{message}"
+msgstr ""
+
+msgid "Metrics|Unexpected deployment data response from prometheus endpoint"
+msgstr ""
+
+msgid "Metrics|Unit label"
+msgstr ""
+
+msgid "Metrics|Unstar dashboard"
+msgstr ""
+
+msgid "Metrics|Used as a title for the chart"
+msgstr ""
+
+msgid "Metrics|Used if the query returns a single series. If it returns multiple series, their legend labels will be picked up from the response."
+msgstr ""
+
+msgid "Metrics|Validating query"
+msgstr ""
+
+msgid "Metrics|Values"
+msgstr ""
+
+msgid "Metrics|View logs"
+msgstr ""
+
+msgid "Metrics|Y-axis label"
+msgstr ""
+
+msgid "Metrics|You can save a copy of this dashboard to your repository so it can be customized. Select a file name and branch to save it."
+msgstr ""
+
+msgid "Metrics|You're about to permanently delete this metric. This cannot be undone."
+msgstr ""
+
+msgid "Metrics|Your dashboard schema is invalid. Edit the dashboard to correct the YAML schema."
+msgstr ""
+
+msgid "Metrics|e.g. HTTP requests"
+msgstr ""
+
+msgid "Metrics|e.g. Requests/second"
+msgstr ""
+
+msgid "Metrics|e.g. Throughput"
+msgstr ""
+
+msgid "Metrics|e.g. rate(http_requests_total[5m])"
+msgstr ""
+
+msgid "Metrics|e.g. req/sec"
+msgstr ""
+
+msgid "Mi"
+msgstr ""
+
+msgid "Microsoft Azure"
+msgstr ""
+
+msgid "Middleman project with Static Site Editor support"
+msgstr ""
+
+msgid "Migrate your data from an external source like GitHub, Bitbucket, or another instance of GitLab."
+msgstr ""
+
+msgid "Migrated %{success_count}/%{total_count} files."
+msgstr ""
+
+msgid "Migration successful."
+msgstr ""
+
+msgid "Milestone"
+msgid_plural "Milestones"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "Milestone lists not available with your current license"
+msgstr ""
+
+msgid "Milestone lists show all issues from the selected milestone."
+msgstr ""
+
+msgid "MilestoneSidebar|Closed:"
+msgstr ""
+
+msgid "MilestoneSidebar|Copy reference"
+msgstr ""
+
+msgid "MilestoneSidebar|Due date"
+msgstr ""
+
+msgid "MilestoneSidebar|Edit"
+msgstr ""
+
+msgid "MilestoneSidebar|From"
+msgstr ""
+
+msgid "MilestoneSidebar|Issues"
+msgstr ""
+
+msgid "MilestoneSidebar|Merge requests"
+msgstr ""
+
+msgid "MilestoneSidebar|Merged:"
+msgstr ""
+
+msgid "MilestoneSidebar|New Issue"
+msgstr ""
+
+msgid "MilestoneSidebar|New issue"
+msgstr ""
+
+msgid "MilestoneSidebar|No due date"
+msgstr ""
+
+msgid "MilestoneSidebar|No start date"
+msgstr ""
+
+msgid "MilestoneSidebar|None"
+msgstr ""
+
+msgid "MilestoneSidebar|Open:"
+msgstr ""
+
+msgid "MilestoneSidebar|Reference:"
+msgstr ""
+
+msgid "MilestoneSidebar|Start date"
+msgstr ""
+
+msgid "MilestoneSidebar|Toggle sidebar"
+msgstr ""
+
+msgid "MilestoneSidebar|Until"
+msgstr ""
+
+msgid "MilestoneSidebar|complete"
+msgstr ""
+
+msgid "Milestones"
+msgstr ""
+
+msgid "Milestones| You’re about to permanently delete the milestone %{milestoneTitle} and remove it from %{issuesWithCount} and %{mergeRequestsWithCount}. Once deleted, it cannot be undone or recovered."
+msgstr ""
+
+msgid "Milestones| You’re about to permanently delete the milestone %{milestoneTitle}. This milestone is not currently used in any issues or merge requests."
+msgstr ""
+
+msgid "Milestones|Close Milestone"
+msgstr ""
+
+msgid "Milestones|Completed Issues (closed)"
+msgstr ""
+
+msgid "Milestones|Delete milestone"
+msgstr ""
+
+msgid "Milestones|Delete milestone %{milestoneTitle}?"
+msgstr ""
+
+msgid "Milestones|Failed to delete milestone %{milestoneTitle}"
+msgstr ""
+
+msgid "Milestones|Group Milestone"
+msgstr ""
+
+msgid "Milestones|Milestone %{milestoneTitle} was not found"
+msgstr ""
+
+msgid "Milestones|Ongoing Issues (open and assigned)"
+msgstr ""
+
+msgid "Milestones|Project Milestone"
+msgstr ""
+
+msgid "Milestones|Promote %{milestoneTitle} to group milestone?"
+msgstr ""
+
+msgid "Milestones|Promote Milestone"
+msgstr ""
+
+msgid "Milestones|Promote to Group Milestone"
+msgstr ""
+
+msgid "Milestones|Promoting %{milestoneTitle} will make it available for all projects inside %{groupName}. Existing project milestones with the same title will be merged."
+msgstr ""
+
+msgid "Milestones|Reopen Milestone"
+msgstr ""
+
+msgid "Milestones|This action cannot be reversed."
+msgstr ""
+
+msgid "Milestones|Unstarted Issues (open and unassigned)"
+msgstr ""
+
+msgid "Minimum capacity to be available before we schedule more mirrors preemptively."
+msgstr ""
+
+msgid "Minimum interval in days"
+msgstr ""
+
+msgid "Minimum length is %{minimum_password_length} characters"
+msgstr ""
+
+msgid "Minimum length is %{minimum_password_length} characters."
+msgstr ""
+
+msgid "Minimum password length (number of characters)"
+msgstr ""
+
+msgid "Minutes"
+msgstr ""
+
+msgid "Mirror direction"
+msgstr ""
+
+msgid "Mirror repository"
+msgstr ""
+
+msgid "Mirror settings are only available to GitLab administrators."
+msgstr ""
+
+msgid "Mirror user"
+msgstr ""
+
+msgid "Mirrored branches will have this prefix. If you enabled 'Only mirror protected branches' you need to include this prefix on protected branches in this project or nothing will be mirrored."
+msgstr ""
+
+msgid "Mirrored repositories"
+msgstr ""
+
+msgid "Mirroring repositories"
+msgstr ""
+
+msgid "Mirroring settings were successfully updated."
+msgstr ""
+
+msgid "Mirroring settings were successfully updated. The project is being updated."
+msgstr ""
+
+msgid "Mirroring was successfully disabled."
+msgstr ""
+
+msgid "Mirroring will only be available if the feature is included in the plan of the selected group or user."
+msgstr ""
+
+msgid "Missing commit signatures endpoint!"
+msgstr ""
+
+msgid "MissingSSHKeyWarningLink|Add SSH key"
+msgstr ""
+
+msgid "MissingSSHKeyWarningLink|Don't show again"
+msgstr ""
+
+msgid "MissingSSHKeyWarningLink|You won't be able to pull or push project code via SSH until you add an SSH key to your profile"
+msgstr ""
+
+msgid "ModalButton|Add projects"
+msgstr ""
+
+msgid "Modal|Cancel"
+msgstr ""
+
+msgid "Modal|Close"
+msgstr ""
+
+msgid "Modified"
+msgstr ""
+
+msgid "Modified in this version"
+msgstr ""
+
+msgid "Modify commit message"
+msgstr ""
+
+msgid "Modify commit messages"
+msgstr ""
+
+msgid "Modify merge commit"
+msgstr ""
+
+msgid "Monday"
+msgstr ""
+
+msgid "Monitor your errors by integrating with Sentry."
+msgstr ""
+
+msgid "Monitoring"
+msgstr ""
+
+msgid "Months"
+msgstr ""
+
+msgid "More"
+msgstr ""
+
+msgid "More Information"
+msgstr ""
+
+msgid "More Slack commands"
+msgstr ""
+
+msgid "More actions"
+msgstr ""
+
+msgid "More details"
+msgstr ""
+
+msgid "More info"
+msgstr ""
+
+msgid "More information"
+msgstr ""
+
+msgid "More information and share feedback"
+msgstr ""
+
+msgid "More information is available|here"
+msgstr ""
+
+msgid "More than %{number_commits_distance} commits different with %{default_branch}"
+msgstr ""
+
+msgid "Most stars"
+msgstr ""
+
+msgid "Mount point %{mounted_as} not found in %{model_class}."
+msgstr ""
+
+msgid "Move"
+msgstr ""
+
+msgid "Move issue"
+msgstr ""
+
+msgid "Move issue from one column of the board to another"
+msgstr ""
+
+msgid "Move selection down"
+msgstr ""
+
+msgid "Move selection up"
+msgstr ""
+
+msgid "Move this issue to another project."
+msgstr ""
+
+msgid "MoveIssue|Cannot move issue due to insufficient permissions!"
+msgstr ""
+
+msgid "MoveIssue|Cannot move issue to project it originates from!"
+msgstr ""
+
+msgid "Moved issue to %{label} column in the board."
+msgstr ""
+
+msgid "Moved this issue to %{path_to_project}."
+msgstr ""
+
+msgid "Moves issue to %{label} column in the board."
+msgstr ""
+
+msgid "Moves this issue to %{path_to_project}."
+msgstr ""
+
+msgid "MrDeploymentActions|Deploy"
+msgstr ""
+
+msgid "MrDeploymentActions|Re-deploy"
+msgstr ""
+
+msgid "MrDeploymentActions|Stop environment"
+msgstr ""
+
+msgid "Multiple domains are supported with comma delimiters."
+msgstr ""
+
+msgid "Multiple issue boards"
+msgstr ""
+
+msgid "Multiple model types found: %{model_types}"
+msgstr ""
+
+msgid "Multiple uploaders found: %{uploader_types}"
+msgstr ""
+
+msgid "Must match with the %{codeStart}external_url%{codeEnd} in %{codeStart}/etc/gitlab/gitlab.rb%{codeEnd}."
+msgstr ""
+
+msgid "Must match with the %{codeStart}geo_node_name%{codeEnd} in %{codeStart}/etc/gitlab/gitlab.rb%{codeEnd}. %{linkStart}More information%{linkEnd}"
+msgstr ""
+
+msgid "My Awesome Group"
+msgstr ""
+
+msgid "My company or team"
+msgstr ""
+
+msgid "My-Reaction"
+msgstr ""
+
+msgid "Name"
+msgstr ""
+
+msgid "Name has already been taken"
+msgstr ""
+
+msgid "Name new label"
+msgstr ""
+
+msgid "Name:"
+msgstr ""
+
+msgid "Namespace is empty"
+msgstr ""
+
+msgid "Namespaces to index"
+msgstr ""
+
+msgid "Naming, topics, avatar"
+msgstr ""
+
+msgid "Naming, visibility"
+msgstr ""
+
+msgid "Navigate to the project to close the milestone."
+msgstr ""
+
+msgid "Nav|Help"
+msgstr ""
+
+msgid "Nav|Home"
+msgstr ""
+
+msgid "Nav|Sign In / Register"
+msgstr ""
+
+msgid "Nav|Sign out and sign in with a different account"
+msgstr ""
+
+msgid "Need help?"
+msgstr ""
+
+msgid "Needs attention"
+msgstr ""
+
+msgid "Network"
+msgstr ""
+
+msgid "NetworkPolicies|Choose whether to enforce this policy."
+msgstr ""
+
+msgid "NetworkPolicies|Define this policy's location, conditions and actions."
+msgstr ""
+
+msgid "NetworkPolicies|Enforcement status"
+msgstr ""
+
+msgid "NetworkPolicies|Environment does not have deployment platform"
+msgstr ""
+
+msgid "NetworkPolicies|If you are using Auto DevOps, your %{monospacedStart}auto-deploy-values.yaml%{monospacedEnd} file will not be updated if you change a policy in this section. Auto DevOps users should make changes by following the %{linkStart}Container Network Policy documentation%{linkEnd}."
+msgstr ""
+
+msgid "NetworkPolicies|Invalid or empty policy"
+msgstr ""
+
+msgid "NetworkPolicies|Kubernetes error: %{error}"
+msgstr ""
+
+msgid "NetworkPolicies|Last modified"
+msgstr ""
+
+msgid "NetworkPolicies|Name"
+msgstr ""
+
+msgid "NetworkPolicies|No policies detected"
+msgstr ""
+
+msgid "NetworkPolicies|Policies are a specification of how groups of pods are allowed to communicate with each other's network endpoints."
+msgstr ""
+
+msgid "NetworkPolicies|Policy %{policyName} was successfully changed"
+msgstr ""
+
+msgid "NetworkPolicies|Policy definition"
+msgstr ""
+
+msgid "NetworkPolicies|Something went wrong, failed to update policy"
+msgstr ""
+
+msgid "NetworkPolicies|Something went wrong, unable to fetch policies"
+msgstr ""
+
+msgid "NetworkPolicies|Status"
+msgstr ""
+
+msgid "Never"
+msgstr ""
+
+msgid "New"
+msgstr ""
+
+msgid "New Application"
+msgstr ""
+
+msgid "New Branch"
+msgstr ""
+
+msgid "New Deploy Key"
+msgstr ""
+
+msgid "New Environment"
+msgstr ""
+
+msgid "New File"
+msgstr ""
+
+msgid "New Group"
+msgstr ""
+
+msgid "New Group Name"
+msgstr ""
+
+msgid "New Identity"
+msgstr ""
+
+msgid "New Issue"
+msgid_plural "New Issues"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "New Jira import"
+msgstr ""
+
+msgid "New Label"
+msgstr ""
+
+msgid "New Merge Request"
+msgstr ""
+
+msgid "New Milestone"
+msgstr ""
+
+msgid "New Pages Domain"
+msgstr ""
+
+msgid "New Password"
+msgstr ""
+
+msgid "New Pipeline Schedule"
+msgstr ""
+
+msgid "New Project"
+msgstr ""
+
+msgid "New Snippet"
+msgstr ""
+
+msgid "New User"
+msgstr ""
+
+msgid "New branch"
+msgstr ""
+
+msgid "New branch unavailable"
+msgstr ""
+
+msgid "New changes were added. %{linkStart}Reload the page to review them%{linkEnd}"
+msgstr ""
+
+msgid "New deploy key"
+msgstr ""
+
+msgid "New directory"
+msgstr ""
+
+msgid "New environment"
+msgstr ""
+
+msgid "New epic"
+msgstr ""
+
+msgid "New epic title"
+msgstr ""
+
+msgid "New file"
+msgstr ""
+
+msgid "New group"
+msgstr ""
+
+msgid "New health check access token has been generated!"
+msgstr ""
+
+msgid "New identity"
+msgstr ""
+
+msgid "New issue"
+msgstr ""
+
+msgid "New issue title"
+msgstr ""
+
+msgid "New iteration"
+msgstr ""
+
+msgid "New iteration created"
+msgstr ""
+
+msgid "New label"
+msgstr ""
+
+msgid "New merge request"
+msgstr ""
+
+msgid "New milestone"
+msgstr ""
+
+msgid "New password"
+msgstr ""
+
+msgid "New pipelines will cancel older, pending pipelines on the same branch"
+msgstr ""
+
+msgid "New project"
+msgstr ""
+
+msgid "New release"
+msgstr ""
+
+msgid "New requirement"
+msgstr ""
+
+msgid "New runners registration token has been generated!"
+msgstr ""
+
+msgid "New schedule"
+msgstr ""
+
+msgid "New snippet"
+msgstr ""
+
+msgid "New subgroup"
+msgstr ""
+
+msgid "New tag"
+msgstr ""
+
+msgid "New users set to external"
+msgstr ""
+
+msgid "New! Suggest changes directly"
+msgstr ""
+
+msgid "New..."
+msgstr ""
+
+msgid "Newest first"
+msgstr ""
+
+msgid "Newly registered users will by default be external"
+msgstr ""
+
+msgid "Next"
+msgstr ""
+
+msgid "Next commit"
+msgstr ""
+
+msgid "Next file in diff"
+msgstr ""
+
+msgid "Next unresolved discussion"
+msgstr ""
+
+msgid "Nickname"
+msgstr ""
+
+msgid "No"
+msgstr ""
+
+msgid "No %{header} for this request."
+msgstr ""
+
+msgid "No %{providerTitle} repositories found"
+msgstr ""
+
+msgid "No Epic"
+msgstr ""
+
+msgid "No Scopes"
+msgstr ""
+
+msgid "No Tag"
+msgstr ""
+
+msgid "No active admin user found"
+msgstr ""
+
+msgid "No activities found"
+msgstr ""
+
+msgid "No application_settings found"
+msgstr ""
+
+msgid "No approvers"
+msgstr ""
+
+msgid "No authentication methods configured."
+msgstr ""
+
+msgid "No available namespaces to fork the project."
+msgstr ""
+
+msgid "No branches found"
+msgstr ""
+
+msgid "No changes"
+msgstr ""
+
+msgid "No changes between %{ref_start}%{source_branch}%{ref_end} and %{ref_start}%{target_branch}%{ref_end}"
+msgstr ""
+
+msgid "No child epics match applied filters"
+msgstr ""
+
+msgid "No connection could be made to a Gitaly Server, please check your logs!"
+msgstr ""
+
+msgid "No containers available"
+msgstr ""
+
+msgid "No contributions"
+msgstr ""
+
+msgid "No contributions were found"
+msgstr ""
+
+msgid "No credit card required."
+msgstr ""
+
+msgid "No data found"
+msgstr ""
+
+msgid "No data to display"
+msgstr ""
+
+msgid "No deployments found"
+msgstr ""
+
+msgid "No due date"
+msgstr ""
+
+msgid "No endpoint provided"
+msgstr ""
+
+msgid "No errors to display."
+msgstr ""
+
+msgid "No estimate or time spent"
+msgstr ""
+
+msgid "No file chosen"
+msgstr ""
+
+msgid "No file hooks found."
+msgstr ""
+
+msgid "No file selected"
+msgstr ""
+
+msgid "No files"
+msgstr ""
+
+msgid "No files found."
+msgstr ""
+
+msgid "No forks are available to you."
+msgstr ""
+
+msgid "No grouping"
+msgstr ""
+
+msgid "No iteration"
+msgstr ""
+
+msgid "No iterations to show"
+msgstr ""
+
+msgid "No job log"
+msgstr ""
+
+msgid "No jobs to show"
+msgstr ""
+
+msgid "No label"
+msgstr ""
+
+msgid "No labels with such name or description"
+msgstr ""
+
+msgid "No license. All rights reserved"
+msgstr ""
+
+msgid "No licenses found."
+msgstr ""
+
+msgid "No matches found"
+msgstr ""
+
+msgid "No matching labels"
+msgstr ""
+
+msgid "No matching results"
+msgstr ""
+
+msgid "No merge requests found"
+msgstr ""
+
+msgid "No messages were logged"
+msgstr ""
+
+msgid "No milestone"
+msgstr ""
+
+msgid "No milestones to show"
+msgstr ""
+
+msgid "No other labels with such name or description"
+msgstr ""
+
+msgid "No panels matching properties %{opts}"
+msgstr ""
+
+msgid "No parent group"
+msgstr ""
+
+msgid "No pods available"
+msgstr ""
+
+msgid "No policy matches this license"
+msgstr ""
+
+msgid "No preview for this file type"
+msgstr ""
+
+msgid "No prioritized labels with such name or description"
+msgstr ""
+
+msgid "No public groups"
+msgstr ""
+
+msgid "No related merge requests found."
+msgstr ""
+
+msgid "No repository"
+msgstr ""
+
+msgid "No required pipeline"
+msgstr ""
+
+msgid "No runners found"
+msgstr ""
+
+msgid "No schedules"
+msgstr ""
+
+msgid "No stack trace for this error"
+msgstr ""
+
+msgid "No starrers matched your search"
+msgstr ""
+
+msgid "No start date"
+msgstr ""
+
+msgid "No template"
+msgstr ""
+
+msgid "No test coverage"
+msgstr ""
+
+msgid "No thanks"
+msgstr ""
+
+msgid "No vulnerabilities present"
+msgstr ""
+
+msgid "No webhooks found, add one in the form above."
+msgstr ""
+
+msgid "No worries, you can still use all the %{strong}%{plan_name}%{strong_close} features for now. You have %{remaining_days} to renew your subscription."
+msgstr ""
+
+msgid "No, directly import the existing email addresses and usernames."
+msgstr ""
+
+msgid "No. of commits"
+msgstr ""
+
+msgid "Nobody has starred this repository yet"
+msgstr ""
+
+msgid "Node was successfully created."
+msgstr ""
+
+msgid "Node was successfully updated."
+msgstr ""
+
+msgid "Nodes"
+msgstr ""
+
+msgid "Non-admin users can sign in with read-only access and make read-only API requests."
+msgstr ""
+
+msgid "None"
+msgstr ""
+
+msgid "Not Implemented"
+msgstr ""
+
+msgid "Not all data has been processed yet, the accuracy of the chart for the selected timeframe is limited."
+msgstr ""
+
+msgid "Not available"
+msgstr ""
+
+msgid "Not available for private projects"
+msgstr ""
+
+msgid "Not available for protected branches"
+msgstr ""
+
+msgid "Not confidential"
+msgstr ""
+
+msgid "Not enough data"
+msgstr ""
+
+msgid "Not found."
+msgstr ""
+
+msgid "Not now"
+msgstr ""
+
+msgid "Not ready yet. Try again later."
+msgstr ""
+
+msgid "Not started"
+msgstr ""
+
+msgid "Not-confidential epic cannot be assigned to a confidential parent epic"
+msgstr ""
+
+msgid "Note"
+msgstr ""
+
+msgid "Note parameters are invalid: %{errors}"
+msgstr ""
+
+msgid "Note that PostgreSQL 11 will become the minimum required PostgreSQL version in GitLab 13.0 (May 2020). PostgreSQL 9.6 and PostgreSQL 10 will no longer be supported in GitLab 13.0. Please consider upgrading your PostgreSQL version (%{db_version}) soon."
+msgstr ""
+
+msgid "Note that this invitation was sent to %{mail_to_invite_email}, but you are signed in as %{link_to_current_user} with email %{mail_to_current_user}."
+msgstr ""
+
+msgid "Note: As an administrator you may like to configure %{github_integration_link}, which will allow login via GitHub and allow connecting repositories without generating a Personal Access Token."
+msgstr ""
+
+msgid "Note: As an administrator you may like to configure %{github_integration_link}, which will allow login via GitHub and allow importing repositories without generating a Personal Access Token."
+msgstr ""
+
+msgid "Note: Consider asking your GitLab administrator to configure %{github_integration_link}, which will allow login via GitHub and allow connecting repositories without generating a Personal Access Token."
+msgstr ""
+
+msgid "Note: Consider asking your GitLab administrator to configure %{github_integration_link}, which will allow login via GitHub and allow importing repositories without generating a Personal Access Token."
+msgstr ""
+
+msgid "NoteForm|Note"
+msgstr ""
+
+msgid "Notes|Are you sure you want to cancel creating this comment?"
+msgstr ""
+
+msgid "Notes|Collapse replies"
+msgstr ""
+
+msgid "Notes|Private comments are accessible by internal staff only"
+msgstr ""
+
+msgid "Notes|Show all activity"
+msgstr ""
+
+msgid "Notes|Show comments only"
+msgstr ""
+
+msgid "Notes|Show history only"
+msgstr ""
+
+msgid "Notes|This comment has changed since you started editing, please review the %{open_link}updated comment%{close_link} to ensure information is not lost"
+msgstr ""
+
+msgid "Nothing found…"
+msgstr ""
+
+msgid "Nothing to preview."
+msgstr ""
+
+msgid "Nothing to synchronize"
+msgstr ""
+
+msgid "Notification events"
+msgstr ""
+
+msgid "Notification setting"
+msgstr ""
+
+msgid "Notification setting - %{notification_title}"
+msgstr ""
+
+msgid "Notification settings saved"
+msgstr ""
+
+msgid "NotificationEvent|Close issue"
+msgstr ""
+
+msgid "NotificationEvent|Close merge request"
+msgstr ""
+
+msgid "NotificationEvent|Failed pipeline"
+msgstr ""
+
+msgid "NotificationEvent|Fixed pipeline"
+msgstr ""
+
+msgid "NotificationEvent|Merge merge request"
+msgstr ""
+
+msgid "NotificationEvent|New epic"
+msgstr ""
+
+msgid "NotificationEvent|New issue"
+msgstr ""
+
+msgid "NotificationEvent|New merge request"
+msgstr ""
+
+msgid "NotificationEvent|New note"
+msgstr ""
+
+msgid "NotificationEvent|New release"
+msgstr ""
+
+msgid "NotificationEvent|Reassign issue"
+msgstr ""
+
+msgid "NotificationEvent|Reassign merge request"
+msgstr ""
+
+msgid "NotificationEvent|Reopen issue"
+msgstr ""
+
+msgid "NotificationEvent|Successful pipeline"
+msgstr ""
+
+msgid "NotificationLevel|Custom"
+msgstr ""
+
+msgid "NotificationLevel|Disabled"
+msgstr ""
+
+msgid "NotificationLevel|Global"
+msgstr ""
+
+msgid "NotificationLevel|On mention"
+msgstr ""
+
+msgid "NotificationLevel|Participate"
+msgstr ""
+
+msgid "NotificationLevel|Watch"
+msgstr ""
+
+msgid "NotificationSetting|Custom"
+msgstr ""
+
+msgid "Notifications"
+msgstr ""
+
+msgid "Notifications have been disabled by the project or group owner"
+msgstr ""
+
+msgid "Notifications off"
+msgstr ""
+
+msgid "Notifications on"
+msgstr ""
+
+msgid "Notify users by email when sign-in location is not recognized"
+msgstr ""
+
+msgid "Nov"
+msgstr ""
+
+msgid "November"
+msgstr ""
+
+msgid "Novice"
+msgstr ""
+
+msgid "Now you can access the merge request navigation tabs at the top, where they’re easier to find."
+msgstr ""
+
+msgid "Nuget metadatum must have at least license_url, project_url or icon_url set"
+msgstr ""
+
+msgid "Number of %{itemTitle}"
+msgstr ""
+
+msgid "Number of Elasticsearch replicas"
+msgstr ""
+
+msgid "Number of Elasticsearch shards"
+msgstr ""
+
+msgid "Number of LOCs per commit"
+msgstr ""
+
+msgid "Number of changes (branches or tags) in a single push to determine whether individual push events or bulk push event will be created. Bulk push event will be created if it surpasses that value."
+msgstr ""
+
+msgid "Number of changes (branches or tags) in a single push to determine whether webhooks and services will be fired or not. Webhooks and services won't be submitted if it surpasses that value."
+msgstr ""
+
+msgid "Number of commits"
+msgstr ""
+
+msgid "Number of commits per MR"
+msgstr ""
+
+msgid "Number of employees"
+msgstr ""
+
+msgid "Number of files touched"
+msgstr ""
+
+msgid "OK"
+msgstr ""
+
+msgid "Object Storage replication"
+msgstr ""
+
+msgid "Object does not exist on the server or you don't have permissions to access it"
+msgstr ""
+
+msgid "Oct"
+msgstr ""
+
+msgid "October"
+msgstr ""
+
+msgid "OfSearchInADropdown|Filter"
+msgstr ""
+
+msgid "Oh no!"
+msgstr ""
+
+msgid "Oldest first"
+msgstr ""
+
+msgid "OmniAuth"
+msgstr ""
+
+msgid "Omnibus Protected Paths throttle is active, and takes priority over these settings. From 12.4, Omnibus throttle is deprecated and will be removed in a future release. Please read the %{relative_url_link_start}Migrating Protected Paths documentation%{relative_url_link_end}."
+msgstr ""
+
+msgid "On track"
+msgstr ""
+
+msgid "OnDemandScans|Attached branch"
+msgstr ""
+
+msgid "OnDemandScans|Attached branch is where the scan job runs."
+msgstr ""
+
+msgid "OnDemandScans|Could not run the scan. Please try again."
+msgstr ""
+
+msgid "OnDemandScans|Create new DAST scan"
+msgstr ""
+
+msgid "OnDemandScans|DAST will scan the target URL and any discovered sub URLs."
+msgstr ""
+
+msgid "OnDemandScans|New on-demand DAST scan"
+msgstr ""
+
+msgid "OnDemandScans|On-demand Scans"
+msgstr ""
+
+msgid "OnDemandScans|On-demand scans run outside the DevOps cycle and find vulnerabilities in your projects. %{learnMoreLinkStart}Learn more%{learnMoreLinkEnd}"
+msgstr ""
+
+msgid "OnDemandScans|Only a passive scan can be performed on demand."
+msgstr ""
+
+msgid "OnDemandScans|Passive DAST Scan"
+msgstr ""
+
+msgid "OnDemandScans|Please enter a valid URL format, ex: http://www.example.com/home"
+msgstr ""
+
+msgid "OnDemandScans|Run this scan"
+msgstr ""
+
+msgid "OnDemandScans|Scan mode"
+msgstr ""
+
+msgid "OnDemandScans|Schedule or run scans immediately against target sites. Currently available on-demand scan type: DAST. %{helpLinkStart}More information%{helpLinkEnd}"
+msgstr ""
+
+msgid "OnDemandScans|Target URL"
+msgstr ""
+
+msgid "Once imported, repositories can be mirrored over SSH. Read more %{link_start}here%{link_end}."
+msgstr ""
+
+msgid "Once removed, the fork relationship cannot be restored and you will no longer be able to send merge requests to the source."
+msgstr ""
+
+msgid "Once the exported file is ready, you will receive a notification email with a download link, or you can download it from this page."
+msgstr ""
+
+msgid "Once you confirm and press \"Reduce project visibility\":"
+msgstr ""
+
+msgid "One more item"
+msgid_plural "%d more items"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "One or more groups that you don't have access to."
+msgstr ""
+
+msgid "One or more of you personal access tokens were revoked"
+msgstr ""
+
+msgid "One or more of your %{provider} projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
+msgstr ""
+
+msgid "One or more of your Bitbucket projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
+msgstr ""
+
+msgid "One or more of your Google Code projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
+msgstr ""
+
+msgid "One or more of your dependency files are not supported, and the dependency list may be incomplete. Below is a list of supported file types."
+msgstr ""
+
+msgid "One or more of your personal access tokens will expire in %{days_to_expire} days or less."
+msgstr ""
+
+msgid "Only 'Reporter' roles and above on tiers Premium / Silver and above can see Value Stream Analytics."
+msgstr ""
+
+msgid "Only 1 appearances row can exist"
+msgstr ""
+
+msgid "Only Issue ID or Merge Request ID is required"
+msgstr ""
+
+msgid "Only Project Members"
+msgstr ""
+
+msgid "Only active this projects shows up in the search and on the dashboard."
+msgstr ""
+
+msgid "Only admins"
+msgstr ""
+
+msgid "Only admins can delete project"
+msgstr ""
+
+msgid "Only mirror protected branches"
+msgstr ""
+
+msgid "Only policy:"
+msgstr ""
+
+msgid "Only proceed if you trust %{idp_url} to control your GitLab account sign in."
+msgstr ""
+
+msgid "Only project members can comment."
+msgstr ""
+
+msgid "Only project members will be imported. Group members will be skipped."
+msgstr ""
+
+msgid "Only verified users with an email address in any of these domains can be added to the group."
+msgstr ""
+
+msgid "Only ‘Reporter’ roles and above on tiers Premium / Silver and above can see Productivity Analytics."
+msgstr ""
+
+msgid "Oops, are you sure?"
+msgstr ""
+
+msgid "Open"
+msgstr ""
+
+msgid "Open Selection"
+msgstr ""
+
+msgid "Open comment type dropdown"
+msgstr ""
+
+msgid "Open errors"
+msgstr ""
+
+msgid "Open in Xcode"
+msgstr ""
+
+msgid "Open issues"
+msgstr ""
+
+msgid "Open projects"
+msgstr ""
+
+msgid "Open raw"
+msgstr ""
+
+msgid "Open sidebar"
+msgstr ""
+
+msgid "Open: %{openIssuesCount}"
+msgstr ""
+
+msgid "Open: %{open} • Closed: %{closed}"
+msgstr ""
+
+msgid "Opened"
+msgstr ""
+
+msgid "Opened MRs"
+msgstr ""
+
+msgid "Opened issues"
+msgstr ""
+
+msgid "OpenedNDaysAgo|Opened"
+msgstr ""
+
+msgid "Opens in a new window"
+msgstr ""
+
+msgid "Operation failed. Check pod logs for %{pod_name} for more details."
+msgstr ""
+
+msgid "Operation not allowed"
+msgstr ""
+
+msgid "Operation timed out. Check pod logs for %{pod_name} for more details."
+msgstr ""
+
+msgid "Operations"
+msgstr ""
+
+msgid "Operations Dashboard"
+msgstr ""
+
+msgid "Operations Settings"
+msgstr ""
+
+msgid "OperationsDashboard|Add a project to the dashboard"
+msgstr ""
+
+msgid "OperationsDashboard|Add projects"
+msgstr ""
+
+msgid "OperationsDashboard|More information"
+msgstr ""
+
+msgid "OperationsDashboard|Operations Dashboard"
+msgstr ""
+
+msgid "OperationsDashboard|The operations dashboard provides a summary of each project's operational health, including pipeline and alert statuses."
+msgstr ""
+
+msgid "Optional"
+msgstr ""
+
+msgid "Optional parameter \"variables\" must be a Hash. Ex: variables[key1]=value1"
+msgstr ""
+
+msgid "Optionally, you can %{link_to_customize} how FogBugz email addresses and usernames are imported into GitLab."
+msgstr ""
+
+msgid "Optionally, you can %{link_to_customize} how Google Code email addresses and usernames are imported into GitLab."
+msgstr ""
+
+msgid "Options"
+msgstr ""
+
+msgid "Or you can choose one of the suggested colors below"
+msgstr ""
+
+msgid "Origin"
+msgstr ""
+
+msgid "Other Labels"
+msgstr ""
+
+msgid "Other information"
+msgstr ""
+
+msgid "Other merge requests block this MR"
+msgstr ""
+
+msgid "Other visibility settings have been disabled by the administrator."
+msgstr ""
+
+msgid "Out-of-compliance with this project's policies and should be removed"
+msgstr ""
+
+msgid "Outbound requests"
+msgstr ""
+
+msgid "OutdatedBrowser|From May 2020 GitLab no longer supports Internet Explorer 11."
+msgstr ""
+
+msgid "OutdatedBrowser|GitLab may not work properly, because you are using an outdated web browser."
+msgstr ""
+
+msgid "OutdatedBrowser|Please install a %{browser_link_start}supported web browser%{browser_link_end} for a better experience."
+msgstr ""
+
+msgid "OutdatedBrowser|You can provide feedback %{feedback_link_start}on this issue%{feedback_link_end} or via your usual support channels."
+msgstr ""
+
+msgid "Outdent"
+msgstr ""
+
+msgid "Overridden"
+msgstr ""
+
+msgid "Overview"
+msgstr ""
+
+msgid "Overwrite diverged branches"
+msgstr ""
+
+msgid "Owned by anyone"
+msgstr ""
+
+msgid "Owned by me"
+msgstr ""
+
+msgid "Owner"
+msgstr ""
+
+msgid "Package Files"
+msgstr ""
+
+msgid "Package Registry"
+msgstr ""
+
+msgid "Package already exists"
+msgstr ""
+
+msgid "Package deleted successfully"
+msgstr ""
+
+msgid "Package information"
+msgstr ""
+
+msgid "Package recipe already exists"
+msgstr ""
+
+msgid "Package type must be Conan"
+msgstr ""
+
+msgid "Package type must be Maven"
+msgstr ""
+
+msgid "Package type must be NuGet"
+msgstr ""
+
+msgid "Package type must be PyPi"
+msgstr ""
+
+msgid "Package was removed"
+msgstr ""
+
+msgid "PackageRegistry|Add Conan Remote"
+msgstr ""
+
+msgid "PackageRegistry|Add NuGet Source"
+msgstr ""
+
+msgid "PackageRegistry|Conan"
+msgstr ""
+
+msgid "PackageRegistry|Conan Command"
+msgstr ""
+
+msgid "PackageRegistry|Copy .pypirc content"
+msgstr ""
+
+msgid "PackageRegistry|Copy Conan Command"
+msgstr ""
+
+msgid "PackageRegistry|Copy Conan Setup Command"
+msgstr ""
+
+msgid "PackageRegistry|Copy Maven XML"
+msgstr ""
+
+msgid "PackageRegistry|Copy Maven command"
+msgstr ""
+
+msgid "PackageRegistry|Copy Maven registry XML"
+msgstr ""
+
+msgid "PackageRegistry|Copy NuGet Command"
+msgstr ""
+
+msgid "PackageRegistry|Copy NuGet Setup Command"
+msgstr ""
+
+msgid "PackageRegistry|Copy Pip command"
+msgstr ""
+
+msgid "PackageRegistry|Copy and paste this inside your %{codeStart}pom.xml%{codeEnd} %{codeStart}dependencies%{codeEnd} block."
+msgstr ""
+
+msgid "PackageRegistry|Copy npm command"
+msgstr ""
+
+msgid "PackageRegistry|Copy npm setup command"
+msgstr ""
+
+msgid "PackageRegistry|Copy yarn command"
+msgstr ""
+
+msgid "PackageRegistry|Copy yarn setup command"
+msgstr ""
+
+msgid "PackageRegistry|Delete Package Version"
+msgstr ""
+
+msgid "PackageRegistry|Delete package"
+msgstr ""
+
+msgid "PackageRegistry|Filter by name"
+msgstr ""
+
+msgid "PackageRegistry|For more information on the Conan registry, %{linkStart}see the documentation%{linkEnd}."
+msgstr ""
+
+msgid "PackageRegistry|For more information on the Maven registry, %{linkStart}see the documentation%{linkEnd}."
+msgstr ""
+
+msgid "PackageRegistry|For more information on the NuGet registry, %{linkStart}see the documentation%{linkEnd}."
+msgstr ""
+
+msgid "PackageRegistry|For more information on the PyPi registry, %{linkStart}see the documentation%{linkEnd}."
+msgstr ""
+
+msgid "PackageRegistry|If you haven't already done so, you will need to add the below to your %{codeStart}.pypirc%{codeEnd} file."
+msgstr ""
+
+msgid "PackageRegistry|If you haven't already done so, you will need to add the below to your %{codeStart}pom.xml%{codeEnd} file."
+msgstr ""
+
+msgid "PackageRegistry|Installation"
+msgstr ""
+
+msgid "PackageRegistry|Is your favorite package manager missing? We'd love your help in building first-class support for it into GitLab! %{contributionLinkStart}Visit the contribution documentation%{contributionLinkEnd} to learn more about how to build support for new package managers into GitLab. Below is a list of package managers that are on our radar."
+msgstr ""
+
+msgid "PackageRegistry|Learn how to %{noPackagesLinkStart}publish and share your packages%{noPackagesLinkEnd} with GitLab."
+msgstr ""
+
+msgid "PackageRegistry|Manually Published"
+msgstr ""
+
+msgid "PackageRegistry|Maven"
+msgstr ""
+
+msgid "PackageRegistry|Maven Command"
+msgstr ""
+
+msgid "PackageRegistry|Maven XML"
+msgstr ""
+
+msgid "PackageRegistry|NPM"
+msgstr ""
+
+msgid "PackageRegistry|No upcoming issues"
+msgstr ""
+
+msgid "PackageRegistry|NuGet"
+msgstr ""
+
+msgid "PackageRegistry|NuGet Command"
+msgstr ""
+
+msgid "PackageRegistry|Pip Command"
+msgstr ""
+
+msgid "PackageRegistry|Pipeline %{linkStart}%{linkEnd} triggered %{timestamp} by %{author}"
+msgstr ""
+
+msgid "PackageRegistry|Published to the repository at %{timestamp}"
+msgstr ""
+
+msgid "PackageRegistry|PyPi"
+msgstr ""
+
+msgid "PackageRegistry|Registry Setup"
+msgstr ""
+
+msgid "PackageRegistry|Remove package"
+msgstr ""
+
+msgid "PackageRegistry|Sorry, your filter produced no results"
+msgstr ""
+
+msgid "PackageRegistry|There are no %{packageType} packages yet"
+msgstr ""
+
+msgid "PackageRegistry|There are no other versions of this package."
+msgstr ""
+
+msgid "PackageRegistry|There are no packages yet"
+msgstr ""
+
+msgid "PackageRegistry|There are no upcoming issues to display."
+msgstr ""
+
+msgid "PackageRegistry|There was a problem fetching the details for this package."
+msgstr ""
+
+msgid "PackageRegistry|This NuGet package has no dependencies."
+msgstr ""
+
+msgid "PackageRegistry|To widen your search, change or remove the filters above."
+msgstr ""
+
+msgid "PackageRegistry|Unable to fetch package version information."
+msgstr ""
+
+msgid "PackageRegistry|Unable to load package"
+msgstr ""
+
+msgid "PackageRegistry|Upcoming package managers"
+msgstr ""
+
+msgid "PackageRegistry|You are about to delete %{name}, this operation is irreversible, are you sure?"
+msgstr ""
+
+msgid "PackageRegistry|You are about to delete version %{version} of %{name}. Are you sure?"
+msgstr ""
+
+msgid "PackageRegistry|You may also need to setup authentication using an auth token. %{linkStart}See the documentation%{linkEnd} to find out more."
+msgstr ""
+
+msgid "PackageRegistry|npm"
+msgstr ""
+
+msgid "PackageRegistry|published by %{author}"
+msgstr ""
+
+msgid "PackageRegistry|yarn"
+msgstr ""
+
+msgid "PackageType|Conan"
+msgstr ""
+
+msgid "PackageType|Maven"
+msgstr ""
+
+msgid "PackageType|NPM"
+msgstr ""
+
+msgid "PackageType|NuGet"
+msgstr ""
+
+msgid "PackageType|PyPi"
+msgstr ""
+
+msgid "Packages"
+msgstr ""
+
+msgid "Packages & Registries"
+msgstr ""
+
+msgid "Page not found"
+msgstr ""
+
+msgid "Page was successfully deleted"
+msgstr ""
+
+msgid "Pages"
+msgstr ""
+
+msgid "Pages Domain"
+msgstr ""
+
+msgid "Pages getting started guide"
+msgstr ""
+
+msgid "Pagination|Go to first page"
+msgstr ""
+
+msgid "Pagination|Go to last page"
+msgstr ""
+
+msgid "Pagination|Go to next page"
+msgstr ""
+
+msgid "Pagination|Go to previous page"
+msgstr ""
+
+msgid "Pagination|Last »"
+msgstr ""
+
+msgid "Pagination|Next"
+msgstr ""
+
+msgid "Pagination|Prev"
+msgstr ""
+
+msgid "Pagination|« First"
+msgstr ""
+
+msgid "Parameter"
+msgstr ""
+
+msgid "Parameter \"job_id\" cannot exceed length of %{job_id_max_size}"
+msgstr ""
+
+msgid "Parent"
+msgstr ""
+
+msgid "Parent epic doesn't exist."
+msgstr ""
+
+msgid "Parent epic is not present."
+msgstr ""
+
+msgid "Parsing error for param :embed_json. %{message}"
+msgstr ""
+
+msgid "Part of merge request changes"
+msgstr ""
+
+msgid "Partial token for reference only"
+msgstr ""
+
+msgid "Participants"
+msgstr ""
+
+msgid "Passed"
+msgstr ""
+
+msgid "Passed on"
+msgstr ""
+
+msgid "Password"
+msgstr ""
+
+msgid "Password (optional)"
+msgstr ""
+
+msgid "Password Policy Guidelines"
+msgstr ""
+
+msgid "Password authentication is unavailable."
+msgstr ""
+
+msgid "Password confirmation"
+msgstr ""
+
+msgid "Password successfully changed"
+msgstr ""
+
+msgid "Password was successfully updated. Please login with it"
+msgstr ""
+
+msgid "Passwords should be unique and not used for any other sites or services."
+msgstr ""
+
+msgid "Past due"
+msgstr ""
+
+msgid "Paste a machine public key here. Read more about how to generate it"
+msgstr ""
+
+msgid "Paste a machine public key here. Read more about how to generate it %{link_start}here%{link_end}"
+msgstr ""
+
+msgid "Paste epic link"
+msgstr ""
+
+msgid "Paste issue link"
+msgstr ""
+
+msgid "Paste your public SSH key, which is usually contained in the file '~/.ssh/id_ed25519.pub' or '~/.ssh/id_rsa.pub' and begins with 'ssh-ed25519' or 'ssh-rsa'. Don't use your private SSH key."
+msgstr ""
+
+msgid "Patch to apply"
+msgstr ""
+
+msgid "Path"
+msgstr ""
+
+msgid "Path:"
+msgstr ""
+
+msgid "Paths can contain wildcards, like */welcome"
+msgstr ""
+
+msgid "Pause"
+msgstr ""
+
+msgid "Pause replication"
+msgstr ""
+
+msgid "Paused Runners don't accept new jobs"
+msgstr ""
+
+msgid "Pending"
+msgstr ""
+
+msgid "People without permission will never get a notification and won't be able to comment."
+msgstr ""
+
+msgid "People without permission will never get a notification."
+msgstr ""
+
+msgid "Percent rollout (logged in users)"
+msgstr ""
+
+msgid "Percentage"
+msgstr ""
+
+msgid "Perform advanced options such as changing path, transferring, exporting, or removing the group."
+msgstr ""
+
+msgid "Perform common operations on GitLab project"
+msgstr ""
+
+msgid "Performance and resource management"
+msgstr ""
+
+msgid "Performance optimization"
+msgstr ""
+
+msgid "PerformanceBar|Bullet notifications"
+msgstr ""
+
+msgid "PerformanceBar|Download"
+msgstr ""
+
+msgid "PerformanceBar|Elasticsearch calls"
+msgstr ""
+
+msgid "PerformanceBar|Frontend resources"
+msgstr ""
+
+msgid "PerformanceBar|Gitaly calls"
+msgstr ""
+
+msgid "PerformanceBar|Redis calls"
+msgstr ""
+
+msgid "PerformanceBar|Rugged calls"
+msgstr ""
+
+msgid "PerformanceBar|SQL queries"
+msgstr ""
+
+msgid "PerformanceBar|trace"
+msgstr ""
+
+msgid "Permissions"
+msgstr ""
+
+msgid "Permissions, LFS, 2FA"
+msgstr ""
+
+msgid "Personal Access Token"
+msgstr ""
+
+msgid "Personal project creation is not allowed. Please contact your administrator with questions"
+msgstr ""
+
+msgid "Phabricator Server Import"
+msgstr ""
+
+msgid "Phabricator Server URL"
+msgstr ""
+
+msgid "Phabricator Tasks"
+msgstr ""
+
+msgid "Pick a name"
+msgstr ""
+
+msgid "Pin code"
+msgstr ""
+
+msgid "Pipeline"
+msgstr ""
+
+msgid "Pipeline %{label}"
+msgstr ""
+
+msgid "Pipeline %{label} for \"%{dataTitle}\""
+msgstr ""
+
+msgid "Pipeline Schedule"
+msgstr ""
+
+msgid "Pipeline Schedules"
+msgstr ""
+
+msgid "Pipeline minutes quota"
+msgstr ""
+
+msgid "Pipeline subscriptions"
+msgstr ""
+
+msgid "Pipeline triggers"
+msgstr ""
+
+msgid "Pipeline: %{status}"
+msgstr ""
+
+msgid "PipelineCharts|CI / CD Analytics"
+msgstr ""
+
+msgid "PipelineCharts|Failed:"
+msgstr ""
+
+msgid "PipelineCharts|Overall statistics"
+msgstr ""
+
+msgid "PipelineCharts|Success ratio:"
+msgstr ""
+
+msgid "PipelineCharts|Successful:"
+msgstr ""
+
+msgid "PipelineCharts|Total:"
+msgstr ""
+
+msgid "PipelineSchedules|Activated"
+msgstr ""
+
+msgid "PipelineSchedules|Active"
+msgstr ""
+
+msgid "PipelineSchedules|All"
+msgstr ""
+
+msgid "PipelineSchedules|Inactive"
+msgstr ""
+
+msgid "PipelineSchedules|Next Run"
+msgstr ""
+
+msgid "PipelineSchedules|None"
+msgstr ""
+
+msgid "PipelineSchedules|Provide a short description for this pipeline"
+msgstr ""
+
+msgid "PipelineSchedules|Take ownership"
+msgstr ""
+
+msgid "PipelineSchedules|Target"
+msgstr ""
+
+msgid "PipelineSchedules|Variables"
+msgstr ""
+
+msgid "PipelineSheduleIntervalPattern|Custom"
+msgstr ""
+
+msgid "PipelineStatusTooltip|Pipeline: %{ciStatus}"
+msgstr ""
+
+msgid "PipelineStatusTooltip|Pipeline: %{ci_status}"
+msgstr ""
+
+msgid "Pipelines"
+msgstr ""
+
+msgid "Pipelines charts"
+msgstr ""
+
+msgid "Pipelines emails"
+msgstr ""
+
+msgid "Pipelines for last month (%{oneMonthAgo} - %{today})"
+msgstr ""
+
+msgid "Pipelines for last week (%{oneWeekAgo} - %{today})"
+msgstr ""
+
+msgid "Pipelines for last year"
+msgstr ""
+
+msgid "Pipelines for merge requests are configured. A detached pipeline runs in the context of the merge request, and not against the merged result. Learn more in the documentation for Pipelines for Merged Results."
+msgstr ""
+
+msgid "Pipelines must succeed for merge requests to be eligible to merge. Please enable pipelines for this project to continue. For more information, see the %{linkStart}documentation.%{linkEnd}"
+msgstr ""
+
+msgid "Pipelines settings for '%{project_name}' were successfully updated."
+msgstr ""
+
+msgid "Pipelines|API"
+msgstr ""
+
+msgid "Pipelines|Build with confidence"
+msgstr ""
+
+msgid "Pipelines|CI Lint"
+msgstr ""
+
+msgid "Pipelines|Child pipeline"
+msgstr ""
+
+msgid "Pipelines|Clear Runner Caches"
+msgstr ""
+
+msgid "Pipelines|Continuous Integration can help catch bugs by running your tests automatically, while Continuous Deployment can help you deliver code to your product environment."
+msgstr ""
+
+msgid "Pipelines|Get started with Pipelines"
+msgstr ""
+
+msgid "Pipelines|Group %{namespace_name} has %{percentage}%% or less Shared Runner Pipeline minutes remaining. Once it runs out, no new jobs or pipelines in its projects will run."
+msgstr ""
+
+msgid "Pipelines|Group %{namespace_name} has exceeded its pipeline minutes quota. Unless you buy additional pipeline minutes, no new jobs or pipelines in its projects will run."
+msgstr ""
+
+msgid "Pipelines|Loading Pipelines"
+msgstr ""
+
+msgid "Pipelines|Project cache successfully reset."
+msgstr ""
+
+msgid "Pipelines|Run Pipeline"
+msgstr ""
+
+msgid "Pipelines|Something went wrong while cleaning runners cache."
+msgstr ""
+
+msgid "Pipelines|There are currently no finished pipelines."
+msgstr ""
+
+msgid "Pipelines|There are currently no pipelines."
+msgstr ""
+
+msgid "Pipelines|There was an error fetching the pipelines. Try again in a few moments or contact your support team."
+msgstr ""
+
+msgid "Pipelines|This is a child pipeline within the parent pipeline"
+msgstr ""
+
+msgid "Pipelines|This project is not currently set up to run pipelines."
+msgstr ""
+
+msgid "Pipelines|parent"
+msgstr ""
+
+msgid "Pipeline|Branch name"
+msgstr ""
+
+msgid "Pipeline|Canceled"
+msgstr ""
+
+msgid "Pipeline|Commit"
+msgstr ""
+
+msgid "Pipeline|Could not retrieve the pipeline status. For troubleshooting steps, read the %{linkStart}documentation%{linkEnd}."
+msgstr ""
+
+msgid "Pipeline|Coverage"
+msgstr ""
+
+msgid "Pipeline|Created"
+msgstr ""
+
+msgid "Pipeline|Date"
+msgstr ""
+
+msgid "Pipeline|Detached merge request pipeline"
+msgstr ""
+
+msgid "Pipeline|Duration"
+msgstr ""
+
+msgid "Pipeline|Existing branch name or tag"
+msgstr ""
+
+msgid "Pipeline|Failed"
+msgstr ""
+
+msgid "Pipeline|Key"
+msgstr ""
+
+msgid "Pipeline|Manual"
+msgstr ""
+
+msgid "Pipeline|Merge train pipeline"
+msgstr ""
+
+msgid "Pipeline|Merged result pipeline"
+msgstr ""
+
+msgid "Pipeline|No pipeline has been run for this commit."
+msgstr ""
+
+msgid "Pipeline|Passed"
+msgstr ""
+
+msgid "Pipeline|Pending"
+msgstr ""
+
+msgid "Pipeline|Pipeline"
+msgstr ""
+
+msgid "Pipeline|Pipelines"
+msgstr ""
+
+msgid "Pipeline|Raw text search is not currently supported. Please use the available search tokens."
+msgstr ""
+
+msgid "Pipeline|Run Pipeline"
+msgstr ""
+
+msgid "Pipeline|Run for"
+msgstr ""
+
+msgid "Pipeline|Running"
+msgstr ""
+
+msgid "Pipeline|Search branches"
+msgstr ""
+
+msgid "Pipeline|Skipped"
+msgstr ""
+
+msgid "Pipeline|Specify variable values to be used in this run. The values specified in %{settings_link} will be used by default."
+msgstr ""
+
+msgid "Pipeline|Stages"
+msgstr ""
+
+msgid "Pipeline|Status"
+msgstr ""
+
+msgid "Pipeline|Stop pipeline"
+msgstr ""
+
+msgid "Pipeline|Stop pipeline #%{pipelineId}?"
+msgstr ""
+
+msgid "Pipeline|Tag name"
+msgstr ""
+
+msgid "Pipeline|Trigger author"
+msgstr ""
+
+msgid "Pipeline|Triggerer"
+msgstr ""
+
+msgid "Pipeline|Value"
+msgstr ""
+
+msgid "Pipeline|Variables"
+msgstr ""
+
+msgid "Pipeline|You’re about to stop pipeline %{pipelineId}."
+msgstr ""
+
+msgid "Pipeline|for"
+msgstr ""
+
+msgid "Pipeline|on"
+msgstr ""
+
+msgid "Pipeline|with stage"
+msgstr ""
+
+msgid "Pipeline|with stages"
+msgstr ""
+
+msgid "PivotalTrackerService|Comma-separated list of branches which will be automatically inspected. Leave blank to include all branches."
+msgstr ""
+
+msgid "PivotalTrackerService|Pivotal Tracker API token."
+msgstr ""
+
+msgid "PivotalTrackerService|Project Management Software (Source Commits Endpoint)"
+msgstr ""
+
+msgid "Plain diff"
+msgstr ""
+
+msgid "Plan"
+msgstr ""
+
+msgid "Plan:"
+msgstr ""
+
+msgid "PlantUML"
+msgstr ""
+
+msgid "Play"
+msgstr ""
+
+msgid "Play all manual"
+msgstr ""
+
+msgid "Please %{link_to_register} or %{link_to_sign_in} to comment"
+msgstr ""
+
+msgid "Please %{startTagRegister}register%{endRegisterTag} or %{startTagSignIn}sign in%{endSignInTag} to reply"
+msgstr ""
+
+msgid "Please accept the Terms of Service before continuing."
+msgstr ""
+
+msgid "Please add a comment in the text area above"
+msgstr ""
+
+msgid "Please add a list to your board first"
+msgstr ""
+
+msgid "Please check the configuration file for this chart"
+msgstr ""
+
+msgid "Please check the configuration file to ensure that a collection of charts has been declared."
+msgstr ""
+
+msgid "Please check the configuration file to ensure that it is available and the YAML is valid"
+msgstr ""
+
+msgid "Please check your email (%{email}) to verify that you own this address and unlock the power of CI/CD. Didn't receive it? %{resend_link}. Wrong email address? %{update_link}."
+msgstr ""
+
+msgid "Please choose a file"
+msgstr ""
+
+msgid "Please choose a group URL with no special characters."
+msgstr ""
+
+msgid "Please complete your profile with email address"
+msgstr ""
+
+msgid "Please convert %{linkStart}them to Git%{linkEnd}, and go through the %{linkToImportFlow} again."
+msgstr ""
+
+msgid "Please convert them to %{link_to_git}, and go through the %{link_to_import_flow} again."
+msgstr ""
+
+msgid "Please convert them to Git on Google Code, and go through the %{link_to_import_flow} again."
+msgstr ""
+
+msgid "Please create a password for your new account."
+msgstr ""
+
+msgid "Please create a username with only alphanumeric characters."
+msgstr ""
+
+msgid "Please create an index before enabling indexing"
+msgstr ""
+
+msgid "Please enable and migrate to hashed storage to avoid security issues and ensure data integrity. %{migrate_link}"
+msgstr ""
+
+msgid "Please ensure your account's %{account_link_start}recovery settings%{account_link_end} are up to date."
+msgstr ""
+
+msgid "Please enter a non-negative number"
+msgstr ""
+
+msgid "Please enter a number greater than %{number} (from the project settings)"
+msgstr ""
+
+msgid "Please enter a valid number"
+msgstr ""
+
+msgid "Please enter or upload a license."
+msgstr ""
+
+msgid "Please fill in a descriptive name for your group."
+msgstr ""
+
+msgid "Please follow the %{link_start}Let's Encrypt troubleshooting instructions%{link_end} to re-obtain your Let's Encrypt certificate."
+msgstr ""
+
+msgid "Please follow the Let's Encrypt troubleshooting instructions to re-obtain your Let's Encrypt certificate: %{docs_url}."
+msgstr ""
+
+msgid "Please migrate all existing projects to hashed storage to avoid security issues and ensure data integrity. %{migrate_link}"
+msgstr ""
+
+msgid "Please note that this application is not provided by GitLab and you should verify its authenticity before allowing access."
+msgstr ""
+
+msgid "Please provide a name"
+msgstr ""
+
+msgid "Please provide a valid URL"
+msgstr ""
+
+msgid "Please provide a valid email address."
+msgstr ""
+
+msgid "Please provide attributes to update"
+msgstr ""
+
+msgid "Please refer to <a href=\"%{docs_url}\">%{docs_url}</a>"
+msgstr ""
+
+msgid "Please retype the email address."
+msgstr ""
+
+msgid "Please select"
+msgstr ""
+
+msgid "Please select a Jira project"
+msgstr ""
+
+msgid "Please select a country"
+msgstr ""
+
+msgid "Please select a file"
+msgstr ""
+
+msgid "Please select a group."
+msgstr ""
+
+msgid "Please select a valid target branch"
+msgstr ""
+
+msgid "Please select and add a member"
+msgstr ""
+
+msgid "Please select at least one filter to see results"
+msgstr ""
+
+msgid "Please set a new password before proceeding."
+msgstr ""
+
+msgid "Please solve the reCAPTCHA"
+msgstr ""
+
+msgid "Please try again"
+msgstr ""
+
+msgid "Please type %{phrase_code} to proceed or close this modal to cancel."
+msgstr ""
+
+msgid "Please upgrade PostgreSQL to version 9.6 or greater. The status of the replication cannot be determined reliably with the current version."
+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 moment, this page will automatically refresh when ready."
+msgstr ""
+
+msgid "Please wait while we connect to your repository. Refresh at will."
+msgstr ""
+
+msgid "Please wait while we import the repository for you. Refresh at will."
+msgstr ""
+
+msgid "Plugins directory is deprecated and will be removed in 14.0. Please move this file into /file_hooks directory."
+msgstr ""
+
+msgid "Pod does not exist"
+msgstr ""
+
+msgid "Pod not found"
+msgstr ""
+
+msgid "Pods in use"
+msgstr ""
+
+msgid "Point to any links you like: documentation, built binaries, or other related materials. These can be internal or external links from your GitLab instance. Duplicate URLs are not allowed."
+msgstr ""
+
+msgid "Pre-defined push rules."
+msgstr ""
+
+msgid "Preferences"
+msgstr ""
+
+msgid "Preferences saved."
+msgstr ""
+
+msgid "Preferences|Behavior"
+msgstr ""
+
+msgid "Preferences|Choose between fixed (max. 1280px) and fluid (%{percentage}) application layout."
+msgstr ""
+
+msgid "Preferences|Choose what content you want to see on a project’s overview page."
+msgstr ""
+
+msgid "Preferences|Customize integrations with third party services."
+msgstr ""
+
+msgid "Preferences|Customize the appearance of the application header and navigation sidebar."
+msgstr ""
+
+msgid "Preferences|Default dashboard"
+msgstr ""
+
+msgid "Preferences|Display time in 24-hour format"
+msgstr ""
+
+msgid "Preferences|Enable integrated code intelligence on code views"
+msgstr ""
+
+msgid "Preferences|For example: 30 mins ago."
+msgstr ""
+
+msgid "Preferences|Integrations"
+msgstr ""
+
+msgid "Preferences|Layout width"
+msgstr ""
+
+msgid "Preferences|Must be a number between %{min} and %{max}"
+msgstr ""
+
+msgid "Preferences|Navigation theme"
+msgstr ""
+
+msgid "Preferences|Project overview content"
+msgstr ""
+
+msgid "Preferences|Render whitespace characters in the Web IDE"
+msgstr ""
+
+msgid "Preferences|Show whitespace changes in diffs"
+msgstr ""
+
+msgid "Preferences|Sourcegraph"
+msgstr ""
+
+msgid "Preferences|Syntax highlighting theme"
+msgstr ""
+
+msgid "Preferences|Tab width"
+msgstr ""
+
+msgid "Preferences|These settings will update how dates and times are displayed for you."
+msgstr ""
+
+msgid "Preferences|This feature is experimental and translations are not complete yet"
+msgstr ""
+
+msgid "Preferences|This setting allows you to customize the appearance of the syntax."
+msgstr ""
+
+msgid "Preferences|This setting allows you to customize the behavior of the system layout and default views."
+msgstr ""
+
+msgid "Preferences|Time display"
+msgstr ""
+
+msgid "Preferences|Time format"
+msgstr ""
+
+msgid "Preferences|Time preferences"
+msgstr ""
+
+msgid "Preferences|Use relative times"
+msgstr ""
+
+msgid "Press %{key}-C to copy"
+msgstr ""
+
+msgid "Prev"
+msgstr ""
+
+msgid "Prevent adding new members to project membership within this group"
+msgstr ""
+
+msgid "Prevent approval of merge requests by merge request author"
+msgstr ""
+
+msgid "Prevent approval of merge requests by merge request committers"
+msgstr ""
+
+msgid "Prevent environment from auto-stopping"
+msgstr ""
+
+msgid "Prevent users from changing their profile name"
+msgstr ""
+
+msgid "Prevent users from modifying merge request approvers list"
+msgstr ""
+
+msgid "Prevent users from performing write operations on GitLab while performing maintenance."
+msgstr ""
+
+msgid "Preview"
+msgstr ""
+
+msgid "Preview Markdown"
+msgstr ""
+
+msgid "Preview changes"
+msgstr ""
+
+msgid "Preview payload"
+msgstr ""
+
+msgid "Previous Artifacts"
+msgstr ""
+
+msgid "Previous commit"
+msgstr ""
+
+msgid "Previous file in diff"
+msgstr ""
+
+msgid "Previous unresolved discussion"
+msgstr ""
+
+msgid "Primary"
+msgstr ""
+
+msgid "Prioritize"
+msgstr ""
+
+msgid "Prioritize label"
+msgstr ""
+
+msgid "Prioritized Labels"
+msgstr ""
+
+msgid "Prioritized label"
+msgstr ""
+
+msgid "Private"
+msgstr ""
+
+msgid "Private - Project access must be granted explicitly to each user. If this project is part of a group, access will be granted to members of the group."
+msgstr ""
+
+msgid "Private - The group and its projects can only be viewed by members."
+msgstr ""
+
+msgid "Private group(s)"
+msgstr ""
+
+msgid "Private profile"
+msgstr ""
+
+msgid "Private projects Minutes cost factor"
+msgstr ""
+
+msgid "Private projects can be created in your personal namespace with:"
+msgstr ""
+
+msgid "Proceed"
+msgstr ""
+
+msgid "Productivity"
+msgstr ""
+
+msgid "Productivity Analytics"
+msgstr ""
+
+msgid "Productivity analytics can help identify the problems that are delaying your team"
+msgstr ""
+
+msgid "ProductivityAanalytics|Merge requests"
+msgstr ""
+
+msgid "ProductivityAanalytics|is earlier than the allowed minimum date"
+msgstr ""
+
+msgid "ProductivityAnalytics|Ascending"
+msgstr ""
+
+msgid "ProductivityAnalytics|Days"
+msgstr ""
+
+msgid "ProductivityAnalytics|Days to merge"
+msgstr ""
+
+msgid "ProductivityAnalytics|Descending"
+msgstr ""
+
+msgid "ProductivityAnalytics|Hours"
+msgstr ""
+
+msgid "ProductivityAnalytics|List"
+msgstr ""
+
+msgid "ProductivityAnalytics|Merge Requests"
+msgstr ""
+
+msgid "ProductivityAnalytics|Merge date"
+msgstr ""
+
+msgid "ProductivityAnalytics|Merge requests"
+msgstr ""
+
+msgid "ProductivityAnalytics|Time to merge"
+msgstr ""
+
+msgid "ProductivityAnalytics|Trendline"
+msgstr ""
+
+msgid "ProductivityAnalytics|is earlier than the given merged at after date"
+msgstr ""
+
+msgid "Profile"
+msgstr ""
+
+msgid "Profile Settings"
+msgstr ""
+
+msgid "ProfileSession|on"
+msgstr ""
+
+msgid "Profiles| You are about to permanently delete %{yourAccount}, and all of the issues, merge requests, and groups linked to your account. Once you confirm %{deleteAccount}, it cannot be undone or recovered."
+msgstr ""
+
+msgid "Profiles| You are going to change the username %{currentUsernameBold} to %{newUsernameBold}. Profile and projects will be redirected to the %{newUsername} namespace but this redirect will expire once the %{currentUsername} namespace is registered by another user or group. Please update your Git repository remotes as soon as possible."
+msgstr ""
+
+msgid "Profiles|@username"
+msgstr ""
+
+msgid "Profiles|Account scheduled for removal."
+msgstr ""
+
+msgid "Profiles|Activate signin with one of the following services"
+msgstr ""
+
+msgid "Profiles|Active"
+msgstr ""
+
+msgid "Profiles|Add key"
+msgstr ""
+
+msgid "Profiles|Add status emoji"
+msgstr ""
+
+msgid "Profiles|Avatar cropper"
+msgstr ""
+
+msgid "Profiles|Avatar will be removed. Are you sure?"
+msgstr ""
+
+msgid "Profiles|Bio"
+msgstr ""
+
+msgid "Profiles|Change username"
+msgstr ""
+
+msgid "Profiles|Changing your username can have unintended side effects."
+msgstr ""
+
+msgid "Profiles|Choose file..."
+msgstr ""
+
+msgid "Profiles|Choose to show contributions of private projects on your public profile without any project, repository or organization information"
+msgstr ""
+
+msgid "Profiles|City, country"
+msgstr ""
+
+msgid "Profiles|Clear status"
+msgstr ""
+
+msgid "Profiles|Click on icon to activate signin with one of the following services"
+msgstr ""
+
+msgid "Profiles|Commit email"
+msgstr ""
+
+msgid "Profiles|Connect"
+msgstr ""
+
+msgid "Profiles|Connected Accounts"
+msgstr ""
+
+msgid "Profiles|Current path: %{path}"
+msgstr ""
+
+msgid "Profiles|Current status"
+msgstr ""
+
+msgid "Profiles|Default notification email"
+msgstr ""
+
+msgid "Profiles|Delete Account"
+msgstr ""
+
+msgid "Profiles|Delete account"
+msgstr ""
+
+msgid "Profiles|Delete your account?"
+msgstr ""
+
+msgid "Profiles|Deleting an account has the following effects:"
+msgstr ""
+
+msgid "Profiles|Disconnect"
+msgstr ""
+
+msgid "Profiles|Do not show on profile"
+msgstr ""
+
+msgid "Profiles|Don't display activity-related personal information on your profiles"
+msgstr ""
+
+msgid "Profiles|Edit Profile"
+msgstr ""
+
+msgid "Profiles|Enter your name, so people you know can recognize you"
+msgstr ""
+
+msgid "Profiles|Expires at"
+msgstr ""
+
+msgid "Profiles|Expires:"
+msgstr ""
+
+msgid "Profiles|Feed token was successfully reset"
+msgstr ""
+
+msgid "Profiles|Full name"
+msgstr ""
+
+msgid "Profiles|Give your individual key a title"
+msgstr ""
+
+msgid "Profiles|Include private contributions on my profile"
+msgstr ""
+
+msgid "Profiles|Incoming email token was successfully reset"
+msgstr ""
+
+msgid "Profiles|Increase your account's security by enabling Two-Factor Authentication (2FA)"
+msgstr ""
+
+msgid "Profiles|Invalid password"
+msgstr ""
+
+msgid "Profiles|Invalid username"
+msgstr ""
+
+msgid "Profiles|Key"
+msgstr ""
+
+msgid "Profiles|Last used:"
+msgstr ""
+
+msgid "Profiles|Learn more"
+msgstr ""
+
+msgid "Profiles|Location"
+msgstr ""
+
+msgid "Profiles|Made a private contribution"
+msgstr ""
+
+msgid "Profiles|Main settings"
+msgstr ""
+
+msgid "Profiles|No file chosen"
+msgstr ""
+
+msgid "Profiles|Notification email"
+msgstr ""
+
+msgid "Profiles|Organization"
+msgstr ""
+
+msgid "Profiles|Path"
+msgstr ""
+
+msgid "Profiles|Position and size your new avatar"
+msgstr ""
+
+msgid "Profiles|Primary email"
+msgstr ""
+
+msgid "Profiles|Private contributions"
+msgstr ""
+
+msgid "Profiles|Profile was successfully updated"
+msgstr ""
+
+msgid "Profiles|Public Avatar"
+msgstr ""
+
+msgid "Profiles|Public email"
+msgstr ""
+
+msgid "Profiles|Remove avatar"
+msgstr ""
+
+msgid "Profiles|Set new profile picture"
+msgstr ""
+
+msgid "Profiles|Social sign-in"
+msgstr ""
+
+msgid "Profiles|Some options are unavailable for LDAP accounts"
+msgstr ""
+
+msgid "Profiles|Static object token was successfully reset"
+msgstr ""
+
+msgid "Profiles|Tell us about yourself in fewer than 250 characters"
+msgstr ""
+
+msgid "Profiles|The ability to update your name has been disabled by your administrator."
+msgstr ""
+
+msgid "Profiles|The maximum file size allowed is 200KB."
+msgstr ""
+
+msgid "Profiles|This doesn't look like a public SSH key, are you sure you want to add it?"
+msgstr ""
+
+msgid "Profiles|This email will be displayed on your public profile"
+msgstr ""
+
+msgid "Profiles|This email will be used for web based operations, such as edits and merges. %{commit_email_link_start}Learn more%{commit_email_link_end}"
+msgstr ""
+
+msgid "Profiles|This emoji and message will appear on your profile and throughout the interface."
+msgstr ""
+
+msgid "Profiles|This information will appear on your profile"
+msgstr ""
+
+msgid "Profiles|Time settings"
+msgstr ""
+
+msgid "Profiles|Two-Factor Authentication"
+msgstr ""
+
+msgid "Profiles|Type your %{confirmationValue} to confirm:"
+msgstr ""
+
+msgid "Profiles|Typically starts with \"ssh-ed25519 …\" or \"ssh-rsa …\""
+msgstr ""
+
+msgid "Profiles|Update profile settings"
+msgstr ""
+
+msgid "Profiles|Update username"
+msgstr ""
+
+msgid "Profiles|Upload new avatar"
+msgstr ""
+
+msgid "Profiles|Use a private email - %{email}"
+msgstr ""
+
+msgid "Profiles|User ID"
+msgstr ""
+
+msgid "Profiles|Username change failed - %{message}"
+msgstr ""
+
+msgid "Profiles|Username successfully changed"
+msgstr ""
+
+msgid "Profiles|Using emojis in names seems fun, but please try to set a status message instead"
+msgstr ""
+
+msgid "Profiles|What's your status?"
+msgstr ""
+
+msgid "Profiles|Who you represent or work for"
+msgstr ""
+
+msgid "Profiles|You can change your avatar here"
+msgstr ""
+
+msgid "Profiles|You can change your avatar here or remove the current avatar to revert to %{gravatar_link}"
+msgstr ""
+
+msgid "Profiles|You can set your current timezone here"
+msgstr ""
+
+msgid "Profiles|You can upload your avatar here"
+msgstr ""
+
+msgid "Profiles|You can upload your avatar here or change it at %{gravatar_link}"
+msgstr ""
+
+msgid "Profiles|You don't have access to delete this user."
+msgstr ""
+
+msgid "Profiles|You must transfer ownership or delete these groups before you can delete your account."
+msgstr ""
+
+msgid "Profiles|Your LinkedIn profile name from linkedin.com/in/profilename"
+msgstr ""
+
+msgid "Profiles|Your account is currently an owner in these groups:"
+msgstr ""
+
+msgid "Profiles|Your email address was automatically set based on your %{provider_label} account"
+msgstr ""
+
+msgid "Profiles|Your key has expired"
+msgstr ""
+
+msgid "Profiles|Your location was automatically set based on your %{provider_label} account"
+msgstr ""
+
+msgid "Profiles|Your name was automatically set based on your %{provider_label} account, so people you know can recognize you"
+msgstr ""
+
+msgid "Profiles|Your status"
+msgstr ""
+
+msgid "Profiles|e.g. My MacBook key"
+msgstr ""
+
+msgid "Profiles|username"
+msgstr ""
+
+msgid "Profiles|website.com"
+msgstr ""
+
+msgid "Profiles|your account"
+msgstr ""
+
+msgid "Profile|%{job_title} at %{organization}"
+msgstr ""
+
+msgid "Profiling - Performance bar"
+msgstr ""
+
+msgid "Programming languages used in this repository"
+msgstr ""
+
+msgid "Progress"
+msgstr ""
+
+msgid "Project"
+msgstr ""
+
+msgid "Project \"%{name}\" is no longer available. Select another project to continue."
+msgstr ""
+
+msgid "Project %{project_repo} could not be found"
+msgstr ""
+
+msgid "Project & Group can not be assigned at the same time"
+msgstr ""
+
+msgid "Project '%{project_name}' is being imported."
+msgstr ""
+
+msgid "Project '%{project_name}' is in the process of being deleted."
+msgstr ""
+
+msgid "Project '%{project_name}' is restored."
+msgstr ""
+
+msgid "Project '%{project_name}' queued for deletion."
+msgstr ""
+
+msgid "Project '%{project_name}' was successfully created."
+msgstr ""
+
+msgid "Project '%{project_name}' was successfully updated."
+msgstr ""
+
+msgid "Project '%{project_name}' will be deleted on %{date}"
+msgstr ""
+
+msgid "Project Access Tokens"
+msgstr ""
+
+msgid "Project Audit Events"
+msgstr ""
+
+msgid "Project Badges"
+msgstr ""
+
+msgid "Project Files"
+msgstr ""
+
+msgid "Project ID"
+msgstr ""
+
+msgid "Project URL"
+msgstr ""
+
+msgid "Project access must be granted explicitly to each user. If this project is part of a group, access will be granted to members of the group."
+msgstr ""
+
+msgid "Project already deleted"
+msgstr ""
+
+msgid "Project and wiki repositories"
+msgstr ""
+
+msgid "Project avatar"
+msgstr ""
+
+msgid "Project cannot be shared with the group it is in or one of its ancestors."
+msgstr ""
+
+msgid "Project clone URL"
+msgstr ""
+
+msgid "Project configuration, including services"
+msgstr ""
+
+msgid "Project description (optional)"
+msgstr ""
+
+msgid "Project details"
+msgstr ""
+
+msgid "Project does not exist or you don't have permission to perform this action"
+msgstr ""
+
+msgid "Project export could not be deleted."
+msgstr ""
+
+msgid "Project export enabled"
+msgstr ""
+
+msgid "Project export has been deleted."
+msgstr ""
+
+msgid "Project export link has expired. Please generate a new export from your project settings."
+msgstr ""
+
+msgid "Project export started. A download link will be sent by email and made available on this page."
+msgstr ""
+
+msgid "Project has too many %{label_for_message} to search"
+msgstr ""
+
+msgid "Project is required when cluster_type is :project"
+msgstr ""
+
+msgid "Project members"
+msgstr ""
+
+msgid "Project milestone"
+msgstr ""
+
+msgid "Project name"
+msgstr ""
+
+msgid "Project name suffix"
+msgstr ""
+
+msgid "Project name suffix is a user-defined string which will be appended to the project path, and will form the Service Desk email address."
+msgstr ""
+
+msgid "Project order will not be saved as local storage is not available."
+msgstr ""
+
+msgid "Project overview"
+msgstr ""
+
+msgid "Project path"
+msgstr ""
+
+msgid "Project scanning help page"
+msgstr ""
+
+msgid "Project security status"
+msgstr ""
+
+msgid "Project security status help page"
+msgstr ""
+
+msgid "Project slug"
+msgstr ""
+
+msgid "Project uploads"
+msgstr ""
+
+msgid "Project visibility level will be changed to match namespace rules when transferring to a group."
+msgstr ""
+
+msgid "Project: %{name}"
+msgstr ""
+
+msgid "ProjectActivityRSS|Subscribe"
+msgstr ""
+
+msgid "ProjectCreationLevel|Allowed to create projects"
+msgstr ""
+
+msgid "ProjectCreationLevel|Default project creation protection"
+msgstr ""
+
+msgid "ProjectCreationLevel|Developers + Maintainers"
+msgstr ""
+
+msgid "ProjectCreationLevel|Maintainers"
+msgstr ""
+
+msgid "ProjectCreationLevel|No one"
+msgstr ""
+
+msgid "ProjectFileTree|Name"
+msgstr ""
+
+msgid "ProjectLastActivity|Never"
+msgstr ""
+
+msgid "ProjectLifecycle|Stage"
+msgstr ""
+
+msgid "ProjectOverview|Fork"
+msgstr ""
+
+msgid "ProjectOverview|Forks"
+msgstr ""
+
+msgid "ProjectOverview|Go to your fork"
+msgstr ""
+
+msgid "ProjectOverview|Star"
+msgstr ""
+
+msgid "ProjectOverview|Starrer"
+msgstr ""
+
+msgid "ProjectOverview|Starrers"
+msgstr ""
+
+msgid "ProjectOverview|Unstar"
+msgstr ""
+
+msgid "ProjectOverview|You have reached your project limit"
+msgstr ""
+
+msgid "ProjectOverview|You must sign in to star a project"
+msgstr ""
+
+msgid "ProjectPage|Project ID: %{project_id}"
+msgstr ""
+
+msgid "ProjectSelect| or group"
+msgstr ""
+
+msgid "ProjectSelect|Search for project"
+msgstr ""
+
+msgid "ProjectService|%{service_title}: status off"
+msgstr ""
+
+msgid "ProjectService|%{service_title}: status on"
+msgstr ""
+
+msgid "ProjectService|Comment"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered by a push to the repository"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a commit is created/updated"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a confidential issue is created/updated/closed"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a deployment finishes"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a merge request is created/updated/merged"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a new tag is pushed to the repository"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a new, unique alert is recorded"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a pipeline status changes"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a wiki page is created/updated"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when an issue is created/updated/closed"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when someone adds a comment"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when someone adds a comment on a confidential issue"
+msgstr ""
+
+msgid "ProjectService|Perform common operations on GitLab project: %{project_name}"
+msgstr ""
+
+msgid "ProjectService|To set up this service:"
+msgstr ""
+
+msgid "ProjectSettings|Additional merge request capabilities that influence how and when merges will be performed"
+msgstr ""
+
+msgid "ProjectSettings|All discussions must be resolved"
+msgstr ""
+
+msgid "ProjectSettings|Allow users to make copies of your repository to a new project"
+msgstr ""
+
+msgid "ProjectSettings|Allow users to request access"
+msgstr ""
+
+msgid "ProjectSettings|Automatically resolve merge request diff discussions when they become outdated"
+msgstr ""
+
+msgid "ProjectSettings|Badges"
+msgstr ""
+
+msgid "ProjectSettings|Build, test, and deploy your changes"
+msgstr ""
+
+msgid "ProjectSettings|Choose your merge method, merge options, merge checks, and merge suggestions."
+msgstr ""
+
+msgid "ProjectSettings|Choose your merge method, merge options, merge checks, merge suggestions, and set up a default description template for merge requests."
+msgstr ""
+
+msgid "ProjectSettings|Contact an admin to change this setting."
+msgstr ""
+
+msgid "ProjectSettings|Container registry"
+msgstr ""
+
+msgid "ProjectSettings|Customize your project badges."
+msgstr ""
+
+msgid "ProjectSettings|Disable email notifications"
+msgstr ""
+
+msgid "ProjectSettings|Enable 'Delete source branch' option by default"
+msgstr ""
+
+msgid "ProjectSettings|Enable merge trains and pipelines for merged results"
+msgstr ""
+
+msgid "ProjectSettings|Every merge creates a merge commit"
+msgstr ""
+
+msgid "ProjectSettings|Every project can have its own space to store its Docker images"
+msgstr ""
+
+msgid "ProjectSettings|Every project can have its own space to store its packages"
+msgstr ""
+
+msgid "ProjectSettings|Everyone"
+msgstr ""
+
+msgid "ProjectSettings|Existing merge requests and protected branches are not affected"
+msgstr ""
+
+msgid "ProjectSettings|Failed to protect the tag"
+msgstr ""
+
+msgid "ProjectSettings|Failed to update tag!"
+msgstr ""
+
+msgid "ProjectSettings|Fast-forward merge"
+msgstr ""
+
+msgid "ProjectSettings|Fast-forward merges only"
+msgstr ""
+
+msgid "ProjectSettings|Forks"
+msgstr ""
+
+msgid "ProjectSettings|Git Large File Storage (LFS)"
+msgstr ""
+
+msgid "ProjectSettings|Internal"
+msgstr ""
+
+msgid "ProjectSettings|Issues"
+msgstr ""
+
+msgid "ProjectSettings|LFS objects from this repository are still available to forks. %{linkStart}How do I remove them?%{linkEnd}"
+msgstr ""
+
+msgid "ProjectSettings|Learn more about badges."
+msgstr ""
+
+msgid "ProjectSettings|Lightweight issue tracking system for this project"
+msgstr ""
+
+msgid "ProjectSettings|Manages large files such as audio, video, and graphics files"
+msgstr ""
+
+msgid "ProjectSettings|Merge checks"
+msgstr ""
+
+msgid "ProjectSettings|Merge commit"
+msgstr ""
+
+msgid "ProjectSettings|Merge commit with semi-linear history"
+msgstr ""
+
+msgid "ProjectSettings|Merge method"
+msgstr ""
+
+msgid "ProjectSettings|Merge options"
+msgstr ""
+
+msgid "ProjectSettings|Merge requests"
+msgstr ""
+
+msgid "ProjectSettings|Merge suggestions"
+msgstr ""
+
+msgid "ProjectSettings|No merge commits are created"
+msgstr ""
+
+msgid "ProjectSettings|Note: the container registry is always visible when a project is public"
+msgstr ""
+
+msgid "ProjectSettings|Only signed commits can be pushed to this repository."
+msgstr ""
+
+msgid "ProjectSettings|Packages"
+msgstr ""
+
+msgid "ProjectSettings|Pages"
+msgstr ""
+
+msgid "ProjectSettings|Pages for project documentation"
+msgstr ""
+
+msgid "ProjectSettings|Pipelines"
+msgstr ""
+
+msgid "ProjectSettings|Pipelines for merge requests must be enabled in the CI/CD configuration file, or pipelines could be unresolvable or dropped"
+msgstr ""
+
+msgid "ProjectSettings|Pipelines must succeed"
+msgstr ""
+
+msgid "ProjectSettings|Pipelines need to be configured to enable this feature."
+msgstr ""
+
+msgid "ProjectSettings|Private"
+msgstr ""
+
+msgid "ProjectSettings|Project visibility"
+msgstr ""
+
+msgid "ProjectSettings|Public"
+msgstr ""
+
+msgid "ProjectSettings|Repository"
+msgstr ""
+
+msgid "ProjectSettings|Share code pastes with others out of Git repository"
+msgstr ""
+
+msgid "ProjectSettings|Show default award emojis"
+msgstr ""
+
+msgid "ProjectSettings|Show link to create/view merge request when pushing from the command line"
+msgstr ""
+
+msgid "ProjectSettings|Skipped pipelines are considered successful"
+msgstr ""
+
+msgid "ProjectSettings|Snippets"
+msgstr ""
+
+msgid "ProjectSettings|Submit changes to be merged upstream"
+msgstr ""
+
+msgid "ProjectSettings|The commit message used to apply merge request suggestions"
+msgstr ""
+
+msgid "ProjectSettings|The variables GitLab supports:"
+msgstr ""
+
+msgid "ProjectSettings|These checks must pass before merge requests can be merged"
+msgstr ""
+
+msgid "ProjectSettings|This introduces the risk of merging changes that will not pass the pipeline."
+msgstr ""
+
+msgid "ProjectSettings|This setting is applied on the server level and can be overridden by an admin."
+msgstr ""
+
+msgid "ProjectSettings|This setting is applied on the server level but has been overridden for this project."
+msgstr ""
+
+msgid "ProjectSettings|This setting will be applied to all projects unless overridden by an admin."
+msgstr ""
+
+msgid "ProjectSettings|This setting will override user notification preferences for all project members."
+msgstr ""
+
+msgid "ProjectSettings|This will dictate the commit history when you merge a merge request"
+msgstr ""
+
+msgid "ProjectSettings|Users can only push commits to this repository that were committed with one of their own verified emails."
+msgstr ""
+
+msgid "ProjectSettings|View and edit files in this project"
+msgstr ""
+
+msgid "ProjectSettings|View and edit files in this project. Non-project members will only have read access"
+msgstr ""
+
+msgid "ProjectSettings|When conflicts arise the user is given the option to rebase"
+msgstr ""
+
+msgid "ProjectSettings|When enabled, issues, merge requests, and snippets will always show thumbs-up and thumbs-down award emoji buttons."
+msgstr ""
+
+msgid "ProjectSettings|Wiki"
+msgstr ""
+
+msgid "ProjectSettings|With GitLab Pages you can host your static websites on GitLab"
+msgstr ""
+
+msgid "ProjectSettings|With Metrics Dashboard you can visualize this project performance metrics"
+msgstr ""
+
+msgid "ProjectTemplates|.NET Core"
+msgstr ""
+
+msgid "ProjectTemplates|Android"
+msgstr ""
+
+msgid "ProjectTemplates|GitLab Cluster Management"
+msgstr ""
+
+msgid "ProjectTemplates|Go Micro"
+msgstr ""
+
+msgid "ProjectTemplates|HIPAA Audit Protocol"
+msgstr ""
+
+msgid "ProjectTemplates|Netlify/GitBook"
+msgstr ""
+
+msgid "ProjectTemplates|Netlify/Hexo"
+msgstr ""
+
+msgid "ProjectTemplates|Netlify/Hugo"
+msgstr ""
+
+msgid "ProjectTemplates|Netlify/Jekyll"
+msgstr ""
+
+msgid "ProjectTemplates|Netlify/Plain HTML"
+msgstr ""
+
+msgid "ProjectTemplates|NodeJS Express"
+msgstr ""
+
+msgid "ProjectTemplates|Pages/Gatsby"
+msgstr ""
+
+msgid "ProjectTemplates|Pages/GitBook"
+msgstr ""
+
+msgid "ProjectTemplates|Pages/Hexo"
+msgstr ""
+
+msgid "ProjectTemplates|Pages/Hugo"
+msgstr ""
+
+msgid "ProjectTemplates|Pages/Jekyll"
+msgstr ""
+
+msgid "ProjectTemplates|Pages/Plain HTML"
+msgstr ""
+
+msgid "ProjectTemplates|Ruby on Rails"
+msgstr ""
+
+msgid "ProjectTemplates|SalesforceDX"
+msgstr ""
+
+msgid "ProjectTemplates|Serverless Framework/JS"
+msgstr ""
+
+msgid "ProjectTemplates|Spring"
+msgstr ""
+
+msgid "ProjectTemplates|Static Site Editor/Middleman"
+msgstr ""
+
+msgid "ProjectTemplates|iOS (Swift)"
+msgstr ""
+
+msgid "Projects"
+msgstr ""
+
+msgid "Projects (%{count})"
+msgstr ""
+
+msgid "Projects Successfully Retrieved"
+msgstr ""
+
+msgid "Projects are graded based on the highest severity vulnerability present"
+msgstr ""
+
+msgid "Projects shared with %{group_name}"
+msgstr ""
+
+msgid "Projects that belong to a group are prefixed with the group namespace. Existing projects may be moved into a group."
+msgstr ""
+
+msgid "Projects to index"
+msgstr ""
+
+msgid "Projects with critical vulnerabilities"
+msgstr ""
+
+msgid "Projects with high or unknown vulnerabilities"
+msgstr ""
+
+msgid "Projects with low vulnerabilities"
+msgstr ""
+
+msgid "Projects with medium vulnerabilities"
+msgstr ""
+
+msgid "Projects with no vulnerabilities and security scanning enabled"
+msgstr ""
+
+msgid "Projects with write access"
+msgstr ""
+
+msgid "ProjectsDropdown|Frequently visited"
+msgstr ""
+
+msgid "ProjectsDropdown|Loading projects"
+msgstr ""
+
+msgid "ProjectsDropdown|Projects you visit often will appear here"
+msgstr ""
+
+msgid "ProjectsDropdown|Search your projects"
+msgstr ""
+
+msgid "ProjectsDropdown|Something went wrong on our end."
+msgstr ""
+
+msgid "ProjectsDropdown|Sorry, no projects matched your search"
+msgstr ""
+
+msgid "ProjectsDropdown|This feature requires browser localStorage support"
+msgstr ""
+
+msgid "ProjectsNew|Allows you to immediately clone this project’s repository. Skip this if you plan to push up an existing repository."
+msgstr ""
+
+msgid "ProjectsNew|Blank"
+msgstr ""
+
+msgid "ProjectsNew|Blank project"
+msgstr ""
+
+msgid "ProjectsNew|Connect your external repository to GitLab CI/CD."
+msgstr ""
+
+msgid "ProjectsNew|Contact an administrator to enable options for importing your project."
+msgstr ""
+
+msgid "ProjectsNew|Create"
+msgstr ""
+
+msgid "ProjectsNew|Create a blank project to house your files, plan your work, and collaborate on code, among other things."
+msgstr ""
+
+msgid "ProjectsNew|Create blank project"
+msgstr ""
+
+msgid "ProjectsNew|Create from template"
+msgstr ""
+
+msgid "ProjectsNew|Create new project"
+msgstr ""
+
+msgid "ProjectsNew|Creating project & repository."
+msgstr ""
+
+msgid "ProjectsNew|Description format"
+msgstr ""
+
+msgid "ProjectsNew|Import"
+msgstr ""
+
+msgid "ProjectsNew|Import project"
+msgstr ""
+
+msgid "ProjectsNew|Initialize repository with a README"
+msgstr ""
+
+msgid "ProjectsNew|No import options available"
+msgstr ""
+
+msgid "ProjectsNew|Please wait a moment, this page will automatically refresh when ready."
+msgstr ""
+
+msgid "ProjectsNew|Project description %{tag_start}(optional)%{tag_end}"
+msgstr ""
+
+msgid "ProjectsNew|Run CI/CD for external repository"
+msgstr ""
+
+msgid "ProjectsNew|Template"
+msgstr ""
+
+msgid "ProjectsNew|Visibility Level"
+msgstr ""
+
+msgid "ProjectsNew|Want to house several dependent projects under the same namespace? %{link_start}Create a group.%{link_end}"
+msgstr ""
+
+msgid "Prometheus"
+msgstr ""
+
+msgid "PrometheusAlerts|%{count} alerts applied"
+msgstr ""
+
+msgid "PrometheusAlerts|%{firingCount} firing"
+msgstr ""
+
+msgid "PrometheusAlerts|Add alert"
+msgstr ""
+
+msgid "PrometheusAlerts|Edit alert"
+msgstr ""
+
+msgid "PrometheusAlerts|Error creating alert"
+msgstr ""
+
+msgid "PrometheusAlerts|Error deleting alert"
+msgstr ""
+
+msgid "PrometheusAlerts|Error fetching alert"
+msgstr ""
+
+msgid "PrometheusAlerts|Error saving alert"
+msgstr ""
+
+msgid "PrometheusAlerts|Firing: %{alerts}"
+msgstr ""
+
+msgid "PrometheusAlerts|Firing: %{alert}"
+msgstr ""
+
+msgid "PrometheusAlerts|Operator"
+msgstr ""
+
+msgid "PrometheusAlerts|Select query"
+msgstr ""
+
+msgid "PrometheusAlerts|Threshold"
+msgstr ""
+
+msgid "PrometheusService|%{exporters} with %{metrics} were found"
+msgstr ""
+
+msgid "PrometheusService|<p class=\"text-tertiary\">No <a href=\"%{docsUrl}\">common metrics</a> were found</p>"
+msgstr ""
+
+msgid "PrometheusService|Active"
+msgstr ""
+
+msgid "PrometheusService|Auto configuration"
+msgstr ""
+
+msgid "PrometheusService|Automatically deploy and configure Prometheus on your clusters to monitor your project’s environments"
+msgstr ""
+
+msgid "PrometheusService|Client ID of the IAP secured resource (looks like IAP_CLIENT_ID.apps.googleusercontent.com)"
+msgstr ""
+
+msgid "PrometheusService|Common metrics"
+msgstr ""
+
+msgid "PrometheusService|Common metrics are automatically monitored based on a library of metrics from popular exporters."
+msgstr ""
+
+msgid "PrometheusService|Contents of the credentials.json file of your service account, like: { \"type\": \"service_account\", \"project_id\": ... }"
+msgstr ""
+
+msgid "PrometheusService|Custom metrics"
+msgstr ""
+
+msgid "PrometheusService|Custom metrics require Prometheus installed on a cluster with environment scope \"*\" OR a manually configured Prometheus to be available."
+msgstr ""
+
+msgid "PrometheusService|Enable Prometheus to define custom metrics, using either option above"
+msgstr ""
+
+msgid "PrometheusService|Finding and configuring metrics..."
+msgstr ""
+
+msgid "PrometheusService|Finding custom metrics..."
+msgstr ""
+
+msgid "PrometheusService|Install Prometheus on clusters"
+msgstr ""
+
+msgid "PrometheusService|Manage clusters"
+msgstr ""
+
+msgid "PrometheusService|Manual configuration"
+msgstr ""
+
+msgid "PrometheusService|Metrics"
+msgstr ""
+
+msgid "PrometheusService|Missing environment variable"
+msgstr ""
+
+msgid "PrometheusService|More information"
+msgstr ""
+
+msgid "PrometheusService|New metric"
+msgstr ""
+
+msgid "PrometheusService|No custom metrics have been created. Create one using the button above"
+msgstr ""
+
+msgid "PrometheusService|Prometheus API Base URL, like http://prometheus.example.com/"
+msgstr ""
+
+msgid "PrometheusService|Prometheus is being automatically managed on your clusters"
+msgstr ""
+
+msgid "PrometheusService|Select the Active checkbox to override the Auto Configuration with custom settings. If unchecked, Auto Configuration settings are used."
+msgstr ""
+
+msgid "PrometheusService|These metrics will only be monitored after your first deployment to an environment"
+msgstr ""
+
+msgid "PrometheusService|Time-series monitoring service"
+msgstr ""
+
+msgid "PrometheusService|To enable the installation of Prometheus on your clusters, deactivate the manual configuration below"
+msgstr ""
+
+msgid "PrometheusService|Waiting for your first deployment to an environment to find common metrics"
+msgstr ""
+
+msgid "PrometheusService|You can now manage your Prometheus settings on the %{operations_link_start}Operations%{operations_link_end} page. Fields on this page has been deprecated."
+msgstr ""
+
+msgid "Promote"
+msgstr ""
+
+msgid "Promote confidential issue to a non-confidential epic"
+msgstr ""
+
+msgid "Promote issue to an epic"
+msgstr ""
+
+msgid "Promote these project milestones into a group milestone."
+msgstr ""
+
+msgid "Promote to group label"
+msgstr ""
+
+msgid "PromoteMilestone|Only project milestones can be promoted."
+msgstr ""
+
+msgid "PromoteMilestone|Project does not belong to a group."
+msgstr ""
+
+msgid "PromoteMilestone|Promotion failed - %{message}"
+msgstr ""
+
+msgid "Promoted confidential issue to a non-confidential epic. Information in this issue is no longer confidential as epics are public to group members."
+msgstr ""
+
+msgid "Promoted issue to an epic."
+msgstr ""
+
+msgid "Promotions|Burndown Charts are visual representations of the progress of completing a milestone. At a glance, you see the current state for the completion a given milestone. Without them, you would have to organize the data from the milestone and plot it yourself to have the same sense of progress."
+msgstr ""
+
+msgid "Promotions|Buy EE"
+msgstr ""
+
+msgid "Promotions|Buy GitLab Enterprise Edition"
+msgstr ""
+
+msgid "Promotions|Contact an owner of group %{namespace_name} to upgrade the plan."
+msgstr ""
+
+msgid "Promotions|Contact owner %{link_start}%{owner_name}%{link_end} to upgrade the plan."
+msgstr ""
+
+msgid "Promotions|Contact your Administrator to upgrade your license."
+msgstr ""
+
+msgid "Promotions|Dismiss burndown charts promotion"
+msgstr ""
+
+msgid "Promotions|Don't show me this again"
+msgstr ""
+
+msgid "Promotions|Epics let you manage your portfolio of projects more efficiently and with less effort by tracking groups of issues that share a theme, across projects and milestones."
+msgstr ""
+
+msgid "Promotions|Improve issues management with Issue weight and GitLab Enterprise Edition."
+msgstr ""
+
+msgid "Promotions|Improve milestones with Burndown Charts."
+msgstr ""
+
+msgid "Promotions|Learn more"
+msgstr ""
+
+msgid "Promotions|Not now, thanks!"
+msgstr ""
+
+msgid "Promotions|See the other features in the %{subscription_link_start}bronze plan%{subscription_link_end}"
+msgstr ""
+
+msgid "Promotions|Start GitLab Ultimate trial"
+msgstr ""
+
+msgid "Promotions|This feature is locked."
+msgstr ""
+
+msgid "Promotions|Track activity with Contribution Analytics."
+msgstr ""
+
+msgid "Promotions|Try it for free"
+msgstr ""
+
+msgid "Promotions|Upgrade plan"
+msgstr ""
+
+msgid "Promotions|Upgrade your plan"
+msgstr ""
+
+msgid "Promotions|Upgrade your plan to activate Contribution Analytics."
+msgstr ""
+
+msgid "Promotions|Upgrade your plan to improve milestones with Burndown Charts."
+msgstr ""
+
+msgid "Promotions|Weight"
+msgstr ""
+
+msgid "Promotions|Weighting your issue"
+msgstr ""
+
+msgid "Promotions|When you have a lot of issues, it can be hard to get an overview. By adding a weight to your issues, you can get a better idea of the effort, cost, required time, or value of each, and so better manage them."
+msgstr ""
+
+msgid "Promotions|With Contribution Analytics you can have an overview for the activity of issues, merge requests, and push events of your organization and its members."
+msgstr ""
+
+msgid "Prompt users to upload SSH keys"
+msgstr ""
+
+msgid "Protect variable"
+msgstr ""
+
+msgid "Protected"
+msgstr ""
+
+msgid "Protected Branch"
+msgstr ""
+
+msgid "Protected Branches"
+msgstr ""
+
+msgid "Protected Environment"
+msgstr ""
+
+msgid "Protected Environments"
+msgstr ""
+
+msgid "Protected Paths"
+msgstr ""
+
+msgid "Protected Tag"
+msgstr ""
+
+msgid "Protected Tags"
+msgstr ""
+
+msgid "Protected branches"
+msgstr ""
+
+msgid "ProtectedBranch|%{wildcards_link_start}Wildcards%{wildcards_link_end} such as %{code_tag_start}*-stable%{code_tag_end} or %{code_tag_start}production/*%{code_tag_end} are supported"
+msgstr ""
+
+msgid "ProtectedBranch|Allowed to merge"
+msgstr ""
+
+msgid "ProtectedBranch|Allowed to merge:"
+msgstr ""
+
+msgid "ProtectedBranch|Allowed to push"
+msgstr ""
+
+msgid "ProtectedBranch|Allowed to push:"
+msgstr ""
+
+msgid "ProtectedBranch|Branch"
+msgstr ""
+
+msgid "ProtectedBranch|Code owner approval"
+msgstr ""
+
+msgid "ProtectedBranch|Protect"
+msgstr ""
+
+msgid "ProtectedBranch|Protect a branch"
+msgstr ""
+
+msgid "ProtectedBranch|Protected branch (%{protected_branches_count})"
+msgstr ""
+
+msgid "ProtectedBranch|Pushes that change filenames matched by the CODEOWNERS file will be rejected"
+msgstr ""
+
+msgid "ProtectedBranch|Require approval from code owners:"
+msgstr ""
+
+msgid "ProtectedBranch|There are currently no protected branches, protect a branch with the form above."
+msgstr ""
+
+msgid "ProtectedBranch|Toggle code owner approval"
+msgstr ""
+
+msgid "ProtectedEnvironment|%{environment_name} will be writable for developers. Are you sure?"
+msgstr ""
+
+msgid "ProtectedEnvironment|Allowed to deploy"
+msgstr ""
+
+msgid "ProtectedEnvironment|Environment"
+msgstr ""
+
+msgid "ProtectedEnvironment|Protect"
+msgstr ""
+
+msgid "ProtectedEnvironment|Protect an environment"
+msgstr ""
+
+msgid "ProtectedEnvironment|Protected Environment (%{protected_environments_count})"
+msgstr ""
+
+msgid "ProtectedEnvironment|Protecting an environment restricts the users who can execute deployments."
+msgstr ""
+
+msgid "ProtectedEnvironment|Select an environment"
+msgstr ""
+
+msgid "ProtectedEnvironment|Select users"
+msgstr ""
+
+msgid "ProtectedEnvironment|Select users to deploy and manage Feature Flag settings"
+msgstr ""
+
+msgid "ProtectedEnvironment|There are currently no protected environments, protect an environment with the form above."
+msgstr ""
+
+msgid "ProtectedEnvironment|Unprotect"
+msgstr ""
+
+msgid "ProtectedEnvironment|Your environment can't be unprotected"
+msgstr ""
+
+msgid "ProtectedEnvironment|Your environment has been protected."
+msgstr ""
+
+msgid "ProtectedEnvironment|Your environment has been unprotected"
+msgstr ""
+
+msgid "Protip:"
+msgstr ""
+
+msgid "Protocol"
+msgstr ""
+
+msgid "Provider"
+msgstr ""
+
+msgid "Proxy support for this API is not available currently"
+msgstr ""
+
+msgid "Pseudonymizer data collection"
+msgstr ""
+
+msgid "Public"
+msgstr ""
+
+msgid "Public - The group and any public projects can be viewed without any authentication."
+msgstr ""
+
+msgid "Public - The project can be accessed without any authentication."
+msgstr ""
+
+msgid "Public deploy keys (%{deploy_keys_count})"
+msgstr ""
+
+msgid "Public pipelines"
+msgstr ""
+
+msgid "Public projects Minutes cost factor"
+msgstr ""
+
+msgid "Publish to status page"
+msgstr ""
+
+msgid "Published on status page"
+msgstr ""
+
+msgid "Publishes this issue to the associated status page."
+msgstr ""
+
+msgid "Pull"
+msgstr ""
+
+msgid "Pull requests from fork are not supported"
+msgstr ""
+
+msgid "Puma is running with a thread count above 1 and the Rugged service is enabled. This may decrease performance in some environments. See our %{link_start}documentation%{link_end} for details of this issue."
+msgstr ""
+
+msgid "Purchase more minutes"
+msgstr ""
+
+msgid "Push"
+msgstr ""
+
+msgid "Push Rule updated successfully."
+msgstr ""
+
+msgid "Push Rules"
+msgstr ""
+
+msgid "Push Rules updated successfully."
+msgstr ""
+
+msgid "Push an existing Git repository"
+msgstr ""
+
+msgid "Push an existing folder"
+msgstr ""
+
+msgid "Push events"
+msgstr ""
+
+msgid "Push project from command line"
+msgstr ""
+
+msgid "Push to create a project"
+msgstr ""
+
+msgid "PushRule|Committer restriction"
+msgstr ""
+
+msgid "Pushed"
+msgstr ""
+
+msgid "Pushes"
+msgstr ""
+
+msgid "PushoverService|%{user_name} deleted branch \"%{ref}\"."
+msgstr ""
+
+msgid "PushoverService|%{user_name} push to branch \"%{ref}\"."
+msgstr ""
+
+msgid "PushoverService|%{user_name} pushed new branch \"%{ref}\"."
+msgstr ""
+
+msgid "PushoverService|High Priority"
+msgstr ""
+
+msgid "PushoverService|Leave blank for all active devices"
+msgstr ""
+
+msgid "PushoverService|Low Priority"
+msgstr ""
+
+msgid "PushoverService|Lowest Priority"
+msgstr ""
+
+msgid "PushoverService|Normal Priority"
+msgstr ""
+
+msgid "PushoverService|Pushover makes it easy to get real-time notifications on your Android device, iPhone, iPad, and Desktop."
+msgstr ""
+
+msgid "PushoverService|See project %{project_full_name}"
+msgstr ""
+
+msgid "PushoverService|Total commits count: %{total_commits_count}"
+msgstr ""
+
+msgid "PushoverService|Your application key"
+msgstr ""
+
+msgid "PushoverService|Your user key"
+msgstr ""
+
+msgid "Quarters"
+msgstr ""
+
+msgid "Query"
+msgstr ""
+
+msgid "Query cannot be processed"
+msgstr ""
+
+msgid "Query is valid"
+msgstr ""
+
+msgid "Queued"
+msgstr ""
+
+msgid "Quick actions"
+msgstr ""
+
+msgid "Quick actions can be used in the issues description and comment boxes."
+msgstr ""
+
+msgid "Quick range"
+msgstr ""
+
+msgid "README"
+msgstr ""
+
+msgid "Raw blob request rate limit per minute"
+msgstr ""
+
+msgid "Re-authentication period expired or never requested. Please try again"
+msgstr ""
+
+msgid "Re-authentication required"
+msgstr ""
+
+msgid "Re-verification interval"
+msgstr ""
+
+msgid "Read more"
+msgstr ""
+
+msgid "Read more about environments"
+msgstr ""
+
+msgid "Read more about project permissions <strong>%{link_to_help}</strong>"
+msgstr ""
+
+msgid "Read more about related issues"
+msgstr ""
+
+msgid "Real-time features"
+msgstr ""
+
+msgid "Rebase"
+msgstr ""
+
+msgid "Rebase in progress"
+msgstr ""
+
+msgid "Receive alerts from manually configured Prometheus servers."
+msgstr ""
+
+msgid "Receive notifications about your own activity"
+msgstr ""
+
+msgid "Recent"
+msgstr ""
+
+msgid "Recent Activity"
+msgstr ""
+
+msgid "Recent Project Activity"
+msgstr ""
+
+msgid "Recent Searches Service is unavailable"
+msgstr ""
+
+msgid "Recent searches"
+msgstr ""
+
+msgid "Recipe"
+msgstr ""
+
+msgid "Reconfigure"
+msgstr ""
+
+msgid "Recover hidden stage"
+msgstr ""
+
+msgid "Recovery Codes"
+msgstr ""
+
+msgid "Redirect to SAML provider to test configuration"
+msgstr ""
+
+msgid "Reduce project visibility"
+msgstr ""
+
+msgid "Reduce this project’s visibility?"
+msgstr ""
+
+msgid "Reference:"
+msgstr ""
+
+msgid "Refresh"
+msgstr ""
+
+msgid "Refreshing in a second to show the updated status..."
+msgid_plural "Refreshing in %d seconds to show the updated status..."
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "Regenerate export"
+msgstr ""
+
+msgid "Regenerate instance ID"
+msgstr ""
+
+msgid "Regenerate key"
+msgstr ""
+
+msgid "Regenerate recovery codes"
+msgstr ""
+
+msgid "Regenerating the instance ID can break integration depending on the client you are using."
+msgstr ""
+
+msgid "Regex pattern"
+msgstr ""
+
+msgid "Region that Elasticsearch is configured"
+msgstr ""
+
+msgid "Register"
+msgstr ""
+
+msgid "Register / Sign In"
+msgstr ""
+
+msgid "Register Two-Factor Authenticator"
+msgstr ""
+
+msgid "Register U2F device"
+msgstr ""
+
+msgid "Register Universal Two-Factor (U2F) Device"
+msgstr ""
+
+msgid "Register for GitLab"
+msgstr ""
+
+msgid "Register now"
+msgstr ""
+
+msgid "Register with two-factor app"
+msgstr ""
+
+msgid "Registration"
+msgstr ""
+
+msgid "Registration|Checkout"
+msgstr ""
+
+msgid "Registration|Your GitLab group"
+msgstr ""
+
+msgid "Registration|Your first project"
+msgstr ""
+
+msgid "Registration|Your profile"
+msgstr ""
+
+msgid "Regulate approvals by authors/committers, based on compliance frameworks. Can be changed only at the instance level."
+msgstr ""
+
+msgid "Rejected (closed)"
+msgstr ""
+
+msgid "Related Deployed Jobs"
+msgstr ""
+
+msgid "Related Issues"
+msgstr ""
+
+msgid "Related Jobs"
+msgstr ""
+
+msgid "Related Merge Requests"
+msgstr ""
+
+msgid "Related Merged Requests"
+msgstr ""
+
+msgid "Related merge requests"
+msgstr ""
+
+msgid "Relates to"
+msgstr ""
+
+msgid "Release"
+msgid_plural "Releases"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "Release assets"
+msgstr ""
+
+msgid "Release assets documentation"
+msgstr ""
+
+msgid "Release does not have the same project as the milestone"
+msgstr ""
+
+msgid "Release notes"
+msgstr ""
+
+msgid "Release notes:"
+msgstr ""
+
+msgid "Release title"
+msgstr ""
+
+msgid "ReleaseAssetLinkType|Image"
+msgstr ""
+
+msgid "ReleaseAssetLinkType|Images"
+msgstr ""
+
+msgid "ReleaseAssetLinkType|Other"
+msgstr ""
+
+msgid "ReleaseAssetLinkType|Package"
+msgstr ""
+
+msgid "ReleaseAssetLinkType|Packages"
+msgstr ""
+
+msgid "ReleaseAssetLinkType|Runbook"
+msgstr ""
+
+msgid "ReleaseAssetLinkType|Runbooks"
+msgstr ""
+
+msgid "Releases"
+msgstr ""
+
+msgid "Releases are based on Git tags and mark specific points in a project's development history. They can contain information about the type of changes and can also deliver binaries, like compiled versions of your software."
+msgstr ""
+
+msgid "Releases are based on Git tags. We recommend tags that use semantic versioning, for example %{codeStart}v1.0%{codeEnd}, %{codeStart}v2.0-pre%{codeEnd}."
+msgstr ""
+
+msgid "Releases documentation"
+msgstr ""
+
+msgid "Releases|New Release"
+msgstr ""
+
+msgid "Release|Something went wrong while getting the release details"
+msgstr ""
+
+msgid "Release|Something went wrong while saving the release details"
+msgstr ""
+
+msgid "Remediated: needs review"
+msgstr ""
+
+msgid "Remediations"
+msgstr ""
+
+msgid "Remember me"
+msgstr ""
+
+msgid "Remind later"
+msgstr ""
+
+msgid "Remote object has no absolute path."
+msgstr ""
+
+msgid "Remove"
+msgstr ""
+
+msgid "Remove %{displayReference}"
+msgstr ""
+
+msgid "Remove Runner"
+msgstr ""
+
+msgid "Remove Zoom meeting"
+msgstr ""
+
+msgid "Remove all approvals in a merge request when new commits are pushed to its source branch"
+msgstr ""
+
+msgid "Remove all or specific assignee(s)"
+msgstr ""
+
+msgid "Remove all or specific label(s)"
+msgstr ""
+
+msgid "Remove approvers"
+msgstr ""
+
+msgid "Remove approvers?"
+msgstr ""
+
+msgid "Remove asset link"
+msgstr ""
+
+msgid "Remove assignee"
+msgstr ""
+
+msgid "Remove avatar"
+msgstr ""
+
+msgid "Remove card"
+msgstr ""
+
+msgid "Remove child epic from an epic"
+msgstr ""
+
+msgid "Remove description history"
+msgstr ""
+
+msgid "Remove due date"
+msgstr ""
+
+msgid "Remove fork relationship"
+msgstr ""
+
+msgid "Remove from batch"
+msgstr ""
+
+msgid "Remove from board"
+msgstr ""
+
+msgid "Remove from epic"
+msgstr ""
+
+msgid "Remove group"
+msgstr ""
+
+msgid "Remove iteration"
+msgstr ""
+
+msgid "Remove license"
+msgstr ""
+
+msgid "Remove limit"
+msgstr ""
+
+msgid "Remove milestone"
+msgstr ""
+
+msgid "Remove node"
+msgstr ""
+
+msgid "Remove parent epic from an epic"
+msgstr ""
+
+msgid "Remove primary node"
+msgstr ""
+
+msgid "Remove priority"
+msgstr ""
+
+msgid "Remove project"
+msgstr ""
+
+msgid "Remove secondary node"
+msgstr ""
+
+msgid "Remove spent time"
+msgstr ""
+
+msgid "Remove stage"
+msgstr ""
+
+msgid "Remove time estimate"
+msgstr ""
+
+msgid "Removed"
+msgstr ""
+
+msgid "Removed %{assignee_text} %{assignee_references}."
+msgstr ""
+
+msgid "Removed %{epic_ref} from child epics."
+msgstr ""
+
+msgid "Removed %{iteration_reference} iteration."
+msgstr ""
+
+msgid "Removed %{label_references} %{label_text}."
+msgstr ""
+
+msgid "Removed %{milestone_reference} milestone."
+msgstr ""
+
+msgid "Removed %{type} with id %{id}"
+msgstr ""
+
+msgid "Removed all labels."
+msgstr ""
+
+msgid "Removed an issue from an epic."
+msgstr ""
+
+msgid "Removed group can not be restored!"
+msgstr ""
+
+msgid "Removed parent epic %{epic_ref}."
+msgstr ""
+
+msgid "Removed projects cannot be restored!"
+msgstr ""
+
+msgid "Removed spent time."
+msgstr ""
+
+msgid "Removed the due date."
+msgstr ""
+
+msgid "Removed time estimate."
+msgstr ""
+
+msgid "Removes %{assignee_text} %{assignee_references}."
+msgstr ""
+
+msgid "Removes %{epic_ref} from child epics."
+msgstr ""
+
+msgid "Removes %{iteration_reference} iteration."
+msgstr ""
+
+msgid "Removes %{label_references} %{label_text}."
+msgstr ""
+
+msgid "Removes %{milestone_reference} milestone."
+msgstr ""
+
+msgid "Removes all labels."
+msgstr ""
+
+msgid "Removes an issue from an epic."
+msgstr ""
+
+msgid "Removes parent epic %{epic_ref}."
+msgstr ""
+
+msgid "Removes spent time."
+msgstr ""
+
+msgid "Removes the due date."
+msgstr ""
+
+msgid "Removes time estimate."
+msgstr ""
+
+msgid "Removing a project places it into a read-only state until %{date}, at which point the project will be permanantly removed. Are you ABSOLUTELY sure?"
+msgstr ""
+
+msgid "Removing a project places it into a read-only state until %{date}, at which point the project will be permanently removed."
+msgstr ""
+
+msgid "Removing license…"
+msgstr ""
+
+msgid "Removing the project will delete its repository and all related resources including issues, merge requests etc."
+msgstr ""
+
+msgid "Removing this group also removes all child projects, including archived projects, and their resources."
+msgstr ""
+
+msgid "Rename file"
+msgstr ""
+
+msgid "Rename folder"
+msgstr ""
+
+msgid "Rename/Move"
+msgstr ""
+
+msgid "Reopen"
+msgstr ""
+
+msgid "Reopen %{display_issuable_type}"
+msgstr ""
+
+msgid "Reopen epic"
+msgstr ""
+
+msgid "Reopen milestone"
+msgstr ""
+
+msgid "Reopen this %{quick_action_target}"
+msgstr ""
+
+msgid "Reopened this %{quick_action_target}."
+msgstr ""
+
+msgid "Reopens this %{quick_action_target}."
+msgstr ""
+
+msgid "Repair authentication"
+msgstr ""
+
+msgid "Replace"
+msgstr ""
+
+msgid "Replace all label(s)"
+msgstr ""
+
+msgid "Replaced all labels with %{label_references} %{label_text}."
+msgstr ""
+
+msgid "Replaces the clone URL root."
+msgstr ""
+
+msgid "Replication"
+msgstr ""
+
+msgid "Replication enabled"
+msgstr ""
+
+msgid "Replication paused"
+msgstr ""
+
+msgid "Reply by email"
+msgstr ""
+
+msgid "Reply to comment"
+msgstr ""
+
+msgid "Reply to this email directly or %{view_it_on_gitlab}."
+msgstr ""
+
+msgid "Reply..."
+msgstr ""
+
+msgid "Repo by URL"
+msgstr ""
+
+msgid "Report %{display_issuable_type} that are abusive, inappropriate or spam."
+msgstr ""
+
+msgid "Report abuse"
+msgstr ""
+
+msgid "Report abuse to admin"
+msgstr ""
+
+msgid "Reported %{timeAgo} by %{reportedBy}"
+msgstr ""
+
+msgid "Reporter"
+msgstr ""
+
+msgid "Reporting"
+msgstr ""
+
+msgid "Reports"
+msgstr ""
+
+msgid "Reports|%{combinedString} and %{resolvedString}"
+msgstr ""
+
+msgid "Reports|Accessibility scanning detected %d issue for the source branch only"
+msgid_plural "Reports|Accessibility scanning detected %d issues for the source branch only"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "Reports|Accessibility scanning detected no issues for the source branch only"
+msgstr ""
+
+msgid "Reports|Accessibility scanning failed loading results"
+msgstr ""
+
+msgid "Reports|Accessibility scanning results are being parsed"
+msgstr ""
+
+msgid "Reports|Actions"
+msgstr ""
+
+msgid "Reports|An error occured while loading report"
+msgstr ""
+
+msgid "Reports|An error occurred while loading %{name} results"
+msgstr ""
+
+msgid "Reports|Class"
+msgstr ""
+
+msgid "Reports|Classname"
+msgstr ""
+
+msgid "Reports|Execution time"
+msgstr ""
+
+msgid "Reports|Failure"
+msgstr ""
+
+msgid "Reports|Identifier"
+msgstr ""
+
+msgid "Reports|Metrics reports are loading"
+msgstr ""
+
+msgid "Reports|Metrics reports changed on %{numberOfChanges} %{pointsString}"
+msgstr ""
+
+msgid "Reports|Metrics reports did not change"
+msgstr ""
+
+msgid "Reports|Metrics reports failed loading results"
+msgstr ""
+
+msgid "Reports|Scanner"
+msgstr ""
+
+msgid "Reports|Severity"
+msgstr ""
+
+msgid "Reports|System output"
+msgstr ""
+
+msgid "Reports|Test summary"
+msgstr ""
+
+msgid "Reports|Test summary failed loading results"
+msgstr ""
+
+msgid "Reports|Test summary results are being parsed"
+msgstr ""
+
+msgid "Reports|Vulnerability"
+msgstr ""
+
+msgid "Reports|no changed test results"
+msgstr ""
+
+msgid "Repository"
+msgstr ""
+
+msgid "Repository Analytics"
+msgstr ""
+
+msgid "Repository Graph"
+msgstr ""
+
+msgid "Repository Settings"
+msgstr ""
+
+msgid "Repository URL"
+msgstr ""
+
+msgid "Repository check"
+msgstr ""
+
+msgid "Repository check was triggered."
+msgstr ""
+
+msgid "Repository cleanup"
+msgstr ""
+
+msgid "Repository cleanup has started. You will receive an email once the cleanup operation is complete."
+msgstr ""
+
+msgid "Repository files count over the limit"
+msgstr ""
+
+msgid "Repository has an invalid default branch name."
+msgstr ""
+
+msgid "Repository has more than one branch."
+msgstr ""
+
+msgid "Repository has no locks."
+msgstr ""
+
+msgid "Repository has tags."
+msgstr ""
+
+msgid "Repository maintenance"
+msgstr ""
+
+msgid "Repository mirroring"
+msgstr ""
+
+msgid "Repository must contain at least 1 file."
+msgstr ""
+
+msgid "Repository size is above the limit."
+msgstr ""
+
+msgid "Repository static objects"
+msgstr ""
+
+msgid "Repository storage"
+msgstr ""
+
+msgid "Repository sync capacity"
+msgstr ""
+
+msgid "Repository: %{counter_repositories} / Wikis: %{counter_wikis} / Build Artifacts: %{counter_build_artifacts} / LFS: %{counter_lfs_objects} / Snippets: %{counter_snippets}"
+msgstr ""
+
+msgid "RepositorySettingsAccessLevel|Select"
+msgstr ""
+
+msgid "Request Access"
+msgstr ""
+
+msgid "Request Headers"
+msgstr ""
+
+msgid "Request details"
+msgstr ""
+
+msgid "Request parameter %{param} is missing."
+msgstr ""
+
+msgid "Request to link SAML account must be authorized"
+msgstr ""
+
+msgid "Requested %{time_ago}"
+msgstr ""
+
+msgid "Requested design version does not exist."
+msgstr ""
+
+msgid "Requested states are invalid"
+msgstr ""
+
+msgid "Requests"
+msgstr ""
+
+msgid "Requests Profiles"
+msgstr ""
+
+msgid "Requests to these domain(s)/address(es) on the local network will be allowed when local requests from hooks and services are not allowed. IP ranges such as 1:0:0:0:0:0:0:0/124 or 127.0.0.0/28 are supported. Domain wildcards are not supported currently. Use comma, semicolon, or newline to separate multiple entries. The allowlist can hold a maximum of 1000 entries. Domains should use IDNA encoding. Ex: example.com, 192.168.1.1, 127.0.0.0/28, xn--itlab-j1a.com."
+msgstr ""
+
+msgid "Require all users in this group to setup Two-factor authentication"
+msgstr ""
+
+msgid "Require all users to accept Terms of Service and Privacy Policy when they access GitLab."
+msgstr ""
+
+msgid "Require user password to approve"
+msgstr ""
+
+msgid "Require users to prove ownership of custom domains"
+msgstr ""
+
+msgid "Required approvals (%{approvals_given} given)"
+msgstr ""
+
+msgid "Required approvals (%{approvals_given} given, you've approved)"
+msgstr ""
+
+msgid "Requirement"
+msgstr ""
+
+msgid "Requirement %{reference} has been added"
+msgstr ""
+
+msgid "Requirement %{reference} has been archived"
+msgstr ""
+
+msgid "Requirement %{reference} has been reopened"
+msgstr ""
+
+msgid "Requirement %{reference} has been updated"
+msgstr ""
+
+msgid "Requirement title cannot have more than %{limit} characters."
+msgstr ""
+
+msgid "Requirements"
+msgstr ""
+
+msgid "Requirements can be based on users, stakeholders, system, software, or anything else you find important to capture."
+msgstr ""
+
+msgid "Requires approval from %{names}."
+msgid_plural "Requires %{count} more approvals from %{names}."
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "Requires approval."
+msgid_plural "Requires %d more approvals."
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "Requires values to meet regular expression requirements."
+msgstr ""
+
+msgid "Resend confirmation email"
+msgstr ""
+
+msgid "Resend invite"
+msgstr ""
+
+msgid "Resend it"
+msgstr ""
+
+msgid "Reset authorization key"
+msgstr ""
+
+msgid "Reset authorization key?"
+msgstr ""
+
+msgid "Reset health check access token"
+msgstr ""
+
+msgid "Reset key"
+msgstr ""
+
+msgid "Reset runners registration token"
+msgstr ""
+
+msgid "Reset template"
+msgstr ""
+
+msgid "Reset to project defaults"
+msgstr ""
+
+msgid "Resetting the authorization key for this project will require updating the authorization key in every alert source it is enabled in."
+msgstr ""
+
+msgid "Resetting the authorization key will invalidate the previous key. Existing alert configurations will need to be updated with the new key."
+msgstr ""
+
+msgid "Resolve"
+msgstr ""
+
+msgid "Resolve all threads in new issue"
+msgstr ""
+
+msgid "Resolve conflicts on source branch"
+msgstr ""
+
+msgid "Resolve thread"
+msgstr ""
+
+msgid "Resolved"
+msgstr ""
+
+msgid "Resolved 1 discussion."
+msgstr ""
+
+msgid "Resolved all discussions."
+msgstr ""
+
+msgid "Resolved by"
+msgstr ""
+
+msgid "Resolved by %{name}"
+msgstr ""
+
+msgid "Resolved by %{resolvedByName}"
+msgstr ""
+
+msgid "Resolves IP addresses once and uses them to submit requests"
+msgstr ""
+
+msgid "Response"
+msgstr ""
+
+msgid "Response Headers"
+msgstr ""
+
+msgid "Response Status"
+msgstr ""
+
+msgid "Response didn't include `service_desk_address`"
+msgstr ""
+
+msgid "Response metrics (AWS ELB)"
+msgstr ""
+
+msgid "Response metrics (Custom)"
+msgstr ""
+
+msgid "Response metrics (HA Proxy)"
+msgstr ""
+
+msgid "Response metrics (NGINX Ingress VTS)"
+msgstr ""
+
+msgid "Response metrics (NGINX Ingress)"
+msgstr ""
+
+msgid "Response metrics (NGINX)"
+msgstr ""
+
+msgid "Restart Terminal"
+msgstr ""
+
+msgid "Restore group"
+msgstr ""
+
+msgid "Restore project"
+msgstr ""
+
+msgid "Restoring the group will prevent the group, its subgroups and projects from being removed on this date."
+msgstr ""
+
+msgid "Restoring the project will prevent the project from being removed on this date and restore people's ability to make changes to it."
+msgstr ""
+
+msgid "Restrict access by IP address"
+msgstr ""
+
+msgid "Restrict membership by email"
+msgstr ""
+
+msgid "Restricts sign-ups for email addresses that match the given regex. See the %{supported_syntax_link_start}supported syntax%{supported_syntax_link_end} for more information."
+msgstr ""
+
+msgid "Resume"
+msgstr ""
+
+msgid "Resync"
+msgstr ""
+
+msgid "Resync all"
+msgstr ""
+
+msgid "Resync all %{replicableType}"
+msgstr ""
+
+msgid "Retry"
+msgstr ""
+
+msgid "Retry this job"
+msgstr ""
+
+msgid "Retry this job in order to create the necessary resources."
+msgstr ""
+
+msgid "Retry update"
+msgstr ""
+
+msgid "Retry verification"
+msgstr ""
+
+msgid "Reveal value"
+msgid_plural "Reveal values"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "Reveal values"
+msgstr ""
+
+msgid "Revert this commit"
+msgstr ""
+
+msgid "Revert this merge request"
+msgstr ""
+
+msgid "Review"
+msgstr ""
+
+msgid "Review App|View app"
+msgstr ""
+
+msgid "Review App|View latest app"
+msgstr ""
+
+msgid "Review the process for configuring service providers in your identity provider — in this case, GitLab is the \"service provider\" or \"relying party\"."
+msgstr ""
+
+msgid "Review time"
+msgstr ""
+
+msgid "Review time is defined as the time it takes from first comment until merged."
+msgstr ""
+
+msgid "ReviewApp|Enable Review App"
+msgstr ""
+
+msgid "Reviewing"
+msgstr ""
+
+msgid "Reviewing (merge request !%{mergeRequestId})"
+msgstr ""
+
+msgid "Revoke"
+msgstr ""
+
+msgid "Revoked impersonation token %{token_name}!"
+msgstr ""
+
+msgid "Revoked personal access token %{personal_access_token_name}!"
+msgstr ""
+
+msgid "Revoked project access token %{project_access_token_name}!"
+msgstr ""
+
+msgid "RightSidebar|adding a"
+msgstr ""
+
+msgid "RightSidebar|deleting the"
+msgstr ""
+
+msgid "Roadmap"
+msgstr ""
+
+msgid "Role"
+msgstr ""
+
+msgid "Rollback"
+msgstr ""
+
+msgid "Rook"
+msgstr ""
+
+msgid "Rules that define what git pushes are accepted for a project in this group. All newly created projects in this group will use these settings."
+msgstr ""
+
+msgid "Rules that define what git pushes are accepted for a project. All newly created projects will use these settings."
+msgstr ""
+
+msgid "Run CI/CD pipelines for external repositories"
+msgstr ""
+
+msgid "Run housekeeping"
+msgstr ""
+
+msgid "Run tests against your code live using the Web Terminal"
+msgstr ""
+
+msgid "Run untagged jobs"
+msgstr ""
+
+msgid "Runner cannot be assigned to other projects"
+msgstr ""
+
+msgid "Runner runs jobs from all unassigned projects"
+msgstr ""
+
+msgid "Runner runs jobs from all unassigned projects in its group"
+msgstr ""
+
+msgid "Runner runs jobs from assigned projects"
+msgstr ""
+
+msgid "Runner token"
+msgstr ""
+
+msgid "Runner tokens"
+msgstr ""
+
+msgid "Runner was not updated."
+msgstr ""
+
+msgid "Runner was successfully updated."
+msgstr ""
+
+msgid "Runner will not receive any new jobs"
+msgstr ""
+
+msgid "Runners"
+msgstr ""
+
+msgid "Runners API"
+msgstr ""
+
+msgid "Runners activated for this project"
+msgstr ""
+
+msgid "Runners are processes that pick up and execute jobs for GitLab. Here you can register and see your Runners for this project."
+msgstr ""
+
+msgid "Runners can be placed on separate users, servers, and even on your local machine."
+msgstr ""
+
+msgid "Runners can be placed on separate users, servers, even on your local machine."
+msgstr ""
+
+msgid "Runners currently online: %{active_runners_count}"
+msgstr ""
+
+msgid "Runners page."
+msgstr ""
+
+msgid "Runners|You have used %{quotaUsed} out of %{quotaLimit} of your shared Runners pipeline minutes."
+msgstr ""
+
+msgid "Running"
+msgstr ""
+
+msgid "Running…"
+msgstr ""
+
+msgid "Runs a number of housekeeping tasks within the current repository, such as compressing file revisions and removing unreachable objects."
+msgstr ""
+
+msgid "SAML SSO"
+msgstr ""
+
+msgid "SAML SSO for %{group_name}"
+msgstr ""
+
+msgid "SAML discovery tokens"
+msgstr ""
+
+msgid "SAML for %{group_name}"
+msgstr ""
+
+msgid "SHA256"
+msgstr ""
+
+msgid "SSH Key"
+msgstr ""
+
+msgid "SSH Keys"
+msgstr ""
+
+msgid "SSH host key fingerprints"
+msgstr ""
+
+msgid "SSH host keys"
+msgstr ""
+
+msgid "SSH host keys are not available on this system. Please use <code>ssh-keyscan</code> command or contact your GitLab administrator for more information."
+msgstr ""
+
+msgid "SSH keys allow you to establish a secure connection between your computer and GitLab."
+msgstr ""
+
+msgid "SSH public key"
+msgstr ""
+
+msgid "SSL Verification:"
+msgstr ""
+
+msgid "Saturday"
+msgstr ""
+
+msgid "Save"
+msgstr ""
+
+msgid "Save Changes"
+msgstr ""
+
+msgid "Save anyway"
+msgstr ""
+
+msgid "Save application"
+msgstr ""
+
+msgid "Save changes"
+msgstr ""
+
+msgid "Save changes before testing"
+msgstr ""
+
+msgid "Save comment"
+msgstr ""
+
+msgid "Save expiration policy"
+msgstr ""
+
+msgid "Save password"
+msgstr ""
+
+msgid "Save pipeline schedule"
+msgstr ""
+
+msgid "Save template"
+msgstr ""
+
+msgid "Save variables"
+msgstr ""
+
+msgid "Saving"
+msgstr ""
+
+msgid "Saving project."
+msgstr ""
+
+msgid "Schedule a new pipeline"
+msgstr ""
+
+msgid "Scheduled"
+msgstr ""
+
+msgid "Scheduled to merge this merge request (%{strategy})."
+msgstr ""
+
+msgid "Scheduled to merge this merge request when the pipeline succeeds."
+msgstr ""
+
+msgid "Schedules"
+msgstr ""
+
+msgid "Schedules to merge this merge request (%{strategy})."
+msgstr ""
+
+msgid "Scheduling"
+msgstr ""
+
+msgid "Scheduling Pipelines"
+msgstr ""
+
+msgid "Scope"
+msgstr ""
+
+msgid "Scope not supported with disabled 'users_search' feature!"
+msgstr ""
+
+msgid "Scoped issue boards"
+msgstr ""
+
+msgid "Scopes"
+msgstr ""
+
+msgid "Scopes can't be blank"
+msgstr ""
+
+msgid "Scroll down"
+msgstr ""
+
+msgid "Scroll down to <strong>Google Code Project Hosting</strong> and enable the switch on the right."
+msgstr ""
+
+msgid "Scroll left"
+msgstr ""
+
+msgid "Scroll right"
+msgstr ""
+
+msgid "Scroll to bottom"
+msgstr ""
+
+msgid "Scroll to top"
+msgstr ""
+
+msgid "Scroll up"
+msgstr ""
+
+msgid "Search"
+msgstr ""
+
+msgid "Search Button"
+msgstr ""
+
+msgid "Search Milestones"
+msgstr ""
+
+msgid "Search an environment spec"
+msgstr ""
+
+msgid "Search authors"
+msgstr ""
+
+msgid "Search branches"
+msgstr ""
+
+msgid "Search branches and tags"
+msgstr ""
+
+msgid "Search by author"
+msgstr ""
+
+msgid "Search files"
+msgstr ""
+
+msgid "Search for a LDAP group"
+msgstr ""
+
+msgid "Search for a group"
+msgstr ""
+
+msgid "Search for a user"
+msgstr ""
+
+msgid "Search for projects, issues, etc."
+msgstr ""
+
+msgid "Search for this text"
+msgstr ""
+
+msgid "Search forks"
+msgstr ""
+
+msgid "Search groups"
+msgstr ""
+
+msgid "Search merge requests"
+msgstr ""
+
+msgid "Search milestones"
+msgstr ""
+
+msgid "Search or filter results..."
+msgstr ""
+
+msgid "Search or filter results…"
+msgstr ""
+
+msgid "Search or jump to…"
+msgstr ""
+
+msgid "Search project"
+msgstr ""
+
+msgid "Search projects"
+msgstr ""
+
+msgid "Search projects..."
+msgstr ""
+
+msgid "Search requirements"
+msgstr ""
+
+msgid "Search users"
+msgstr ""
+
+msgid "Search users or groups"
+msgstr ""
+
+msgid "Search your project dependencies for their licenses and apply policies."
+msgstr ""
+
+msgid "Search your projects"
+msgstr ""
+
+msgid "SearchAutocomplete|All GitLab"
+msgstr ""
+
+msgid "SearchAutocomplete|Issues I've created"
+msgstr ""
+
+msgid "SearchAutocomplete|Issues assigned to me"
+msgstr ""
+
+msgid "SearchAutocomplete|Merge requests I've created"
+msgstr ""
+
+msgid "SearchAutocomplete|Merge requests assigned to me"
+msgstr ""
+
+msgid "SearchAutocomplete|in all GitLab"
+msgstr ""
+
+msgid "SearchAutocomplete|in group %{groupName}"
+msgstr ""
+
+msgid "SearchAutocomplete|in project %{projectName}"
+msgstr ""
+
+msgid "SearchCodeResults|in"
+msgstr ""
+
+msgid "SearchCodeResults|of %{link_to_project}"
+msgstr ""
+
+msgid "SearchResults|Showing %{count} %{scope} for%{term_element}"
+msgstr ""
+
+msgid "SearchResults|Showing %{count} %{scope} for%{term_element} in your personal and project snippets"
+msgstr ""
+
+msgid "SearchResults|Showing %{from} - %{to} of %{count} %{scope} for%{term_element}"
+msgstr ""
+
+msgid "SearchResults|Showing %{from} - %{to} of %{count} %{scope} for%{term_element} in your personal and project snippets"
+msgstr ""
+
+msgid "SearchResults|We couldn't find any %{scope} matching %{term}"
+msgstr ""
+
+msgid "SearchResults|code result"
+msgid_plural "SearchResults|code results"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "SearchResults|comment"
+msgid_plural "SearchResults|comments"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "SearchResults|commit"
+msgid_plural "SearchResults|commits"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "SearchResults|issue"
+msgid_plural "SearchResults|issues"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "SearchResults|merge request"
+msgid_plural "SearchResults|merge requests"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "SearchResults|milestone"
+msgid_plural "SearchResults|milestones"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "SearchResults|project"
+msgid_plural "SearchResults|projects"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "SearchResults|snippet"
+msgid_plural "SearchResults|snippets"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "SearchResults|user"
+msgid_plural "SearchResults|users"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "SearchResults|wiki result"
+msgid_plural "SearchResults|wiki results"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "Searching by both author and message is currently not supported."
+msgstr ""
+
+msgid "Seat Link"
+msgstr ""
+
+msgid "Seat Link is disabled, and cannot be configured through this form."
+msgstr ""
+
+msgid "Seats currently in use"
+msgstr ""
+
+msgid "Seats in license"
+msgstr ""
+
+msgid "Secondary"
+msgstr ""
+
+msgid "Secret"
+msgstr ""
+
+msgid "Secret Detection"
+msgstr ""
+
+msgid "Security"
+msgstr ""
+
+msgid "Security & Compliance"
+msgstr ""
+
+msgid "Security Configuration"
+msgstr ""
+
+msgid "Security Dashboard"
+msgstr ""
+
+msgid "Security dashboard"
+msgstr ""
+
+msgid "Security report is out of date. Please update your branch with the latest changes from the target branch (%{targetBranchName})"
+msgstr ""
+
+msgid "Security report is out of date. Run %{newPipelineLinkStart}a new pipeline%{newPipelineLinkEnd} for the target branch (%{targetBranchName})"
+msgstr ""
+
+msgid "SecurityConfiguration|Enabled"
+msgstr ""
+
+msgid "SecurityConfiguration|Feature documentation for %{featureName}"
+msgstr ""
+
+msgid "SecurityConfiguration|Not yet enabled"
+msgstr ""
+
+msgid "SecurityConfiguration|Security Control"
+msgstr ""
+
+msgid "SecurityConfiguration|Status"
+msgstr ""
+
+msgid "SecurityConfiguration|Testing & Compliance"
+msgstr ""
+
+msgid "SecurityReports|%{firstProject} and %{secondProject}"
+msgstr ""
+
+msgid "SecurityReports|%{firstProject}, %{secondProject}, and %{rest}"
+msgstr ""
+
+msgid "SecurityReports|Add a project to your dashboard"
+msgstr ""
+
+msgid "SecurityReports|Add or remove projects from your dashboard"
+msgstr ""
+
+msgid "SecurityReports|Add projects"
+msgstr ""
+
+msgid "SecurityReports|Comment added to '%{vulnerabilityName}'"
+msgstr ""
+
+msgid "SecurityReports|Comment deleted on '%{vulnerabilityName}'"
+msgstr ""
+
+msgid "SecurityReports|Comment edited on '%{vulnerabilityName}'"
+msgstr ""
+
+msgid "SecurityReports|Create issue"
+msgstr ""
+
+msgid "SecurityReports|Dismiss Selected"
+msgstr ""
+
+msgid "SecurityReports|Dismiss vulnerability"
+msgstr ""
+
+msgid "SecurityReports|Dismissed '%{vulnerabilityName}'"
+msgstr ""
+
+msgid "SecurityReports|Dismissed '%{vulnerabilityName}'. Turn off the hide dismissed toggle to view."
+msgstr ""
+
+msgid "SecurityReports|Each vulnerability now has a unique page that can be directly linked to, shared, referenced, and tracked as the single source of truth. Vulnerability occurrences also persist across scanner runs, which improves tracking and visibility and reduces duplicates between scans."
+msgstr ""
+
+msgid "SecurityReports|Edit dashboard"
+msgstr ""
+
+msgid "SecurityReports|Either you don't have permission to view this dashboard or the dashboard has not been setup. Please check your permission settings with your administrator or check your dashboard configurations to proceed."
+msgstr ""
+
+msgid "SecurityReports|Error fetching the vulnerability counts. Please check your network connection and try again."
+msgstr ""
+
+msgid "SecurityReports|Error fetching the vulnerability list. Please check your network connection and try again."
+msgstr ""
+
+msgid "SecurityReports|False positive"
+msgstr ""
+
+msgid "SecurityReports|Group Security Dashboard"
+msgstr ""
+
+msgid "SecurityReports|Hide dismissed"
+msgstr ""
+
+msgid "SecurityReports|Introducing standalone vulnerabilities"
+msgstr ""
+
+msgid "SecurityReports|Issue Created"
+msgstr ""
+
+msgid "SecurityReports|Learn More"
+msgstr ""
+
+msgid "SecurityReports|Learn more about setting up your dashboard"
+msgstr ""
+
+msgid "SecurityReports|Load more vulnerabilities"
+msgstr ""
+
+msgid "SecurityReports|Monitor vulnerabilities in your code"
+msgstr ""
+
+msgid "SecurityReports|More info"
+msgstr ""
+
+msgid "SecurityReports|More information"
+msgstr ""
+
+msgid "SecurityReports|No vulnerabilities found for dashboard"
+msgstr ""
+
+msgid "SecurityReports|No vulnerabilities found for this group"
+msgstr ""
+
+msgid "SecurityReports|No vulnerabilities found for this pipeline"
+msgstr ""
+
+msgid "SecurityReports|No vulnerabilities found for this project"
+msgstr ""
+
+msgid "SecurityReports|Oops, something doesn't seem right."
+msgstr ""
+
+msgid "SecurityReports|Pipeline %{pipelineLink} triggered %{timeago} by %{user}"
+msgstr ""
+
+msgid "SecurityReports|Project"
+msgstr ""
+
+msgid "SecurityReports|Projects added"
+msgstr ""
+
+msgid "SecurityReports|Remove project from dashboard"
+msgstr ""
+
+msgid "SecurityReports|Return to dashboard"
+msgstr ""
+
+msgid "SecurityReports|Scan details"
+msgstr ""
+
+msgid "SecurityReports|Scanner type"
+msgstr ""
+
+msgid "SecurityReports|Security Dashboard"
+msgstr ""
+
+msgid "SecurityReports|Security reports can only be accessed by authorized users."
+msgstr ""
+
+msgid "SecurityReports|Select a project to add by using the project search field above."
+msgstr ""
+
+msgid "SecurityReports|Select a reason"
+msgstr ""
+
+msgid "SecurityReports|Severity"
+msgstr ""
+
+msgid "SecurityReports|Status"
+msgstr ""
+
+msgid "SecurityReports|The rating \"unknown\" indicates that the underlying scanner doesn’t contain or provide a severity rating."
+msgstr ""
+
+msgid "SecurityReports|The security dashboard displays the latest security findings for projects you wish to monitor. Select \"Edit dashboard\" to add and remove projects."
+msgstr ""
+
+msgid "SecurityReports|The security dashboard displays the latest security report. Use it to find and fix vulnerabilities."
+msgstr ""
+
+msgid "SecurityReports|There was an error adding the comment."
+msgstr ""
+
+msgid "SecurityReports|There was an error creating the issue."
+msgstr ""
+
+msgid "SecurityReports|There was an error creating the merge request."
+msgstr ""
+
+msgid "SecurityReports|There was an error deleting the comment."
+msgstr ""
+
+msgid "SecurityReports|There was an error dismissing the vulnerabilities."
+msgstr ""
+
+msgid "SecurityReports|There was an error dismissing the vulnerability."
+msgstr ""
+
+msgid "SecurityReports|There was an error reverting the dismissal."
+msgstr ""
+
+msgid "SecurityReports|There was an error reverting this dismissal."
+msgstr ""
+
+msgid "SecurityReports|There was an error while generating the report."
+msgstr ""
+
+msgid "SecurityReports|Unable to add %{invalidProjectsMessage}"
+msgstr ""
+
+msgid "SecurityReports|Unable to add %{invalidProjects}"
+msgstr ""
+
+msgid "SecurityReports|Undo dismiss"
+msgstr ""
+
+msgid "SecurityReports|While it's rare to have no vulnerabilities for your group, it can happen. In any event, we ask that you double check your settings to make sure you've set up your dashboard correctly."
+msgstr ""
+
+msgid "SecurityReports|While it's rare to have no vulnerabilities for your pipeline, it can happen. In any event, we ask that you double check your settings to make sure all security scanning jobs have passed successfully."
+msgstr ""
+
+msgid "SecurityReports|While it's rare to have no vulnerabilities for your project, it can happen. In any event, we ask that you double check your settings to make sure you've set up your dashboard correctly."
+msgstr ""
+
+msgid "SecurityReports|While it's rare to have no vulnerabilities, it can happen. In any event, we ask that you please double check your settings to make sure you've set up your dashboard correctly."
+msgstr ""
+
+msgid "SecurityReports|Won't fix / Accept risk"
+msgstr ""
+
+msgid "SecurityReports|You do not have sufficient permissions to access this report"
+msgstr ""
+
+msgid "SecurityReports|You must sign in as an authorized user to see this report"
+msgstr ""
+
+msgid "SecurityReports|[No reason]"
+msgstr ""
+
+msgid "See GitLab's %{password_policy_guidelines}"
+msgstr ""
+
+msgid "See metrics"
+msgstr ""
+
+msgid "See the affected projects in the GitLab admin panel"
+msgstr ""
+
+msgid "See what's new at GitLab"
+msgstr ""
+
+msgid "Select"
+msgstr ""
+
+msgid "Select Archive Format"
+msgstr ""
+
+msgid "Select Git revision"
+msgstr ""
+
+msgid "Select GitLab project to link with your Slack team"
+msgstr ""
+
+msgid "Select Page"
+msgstr ""
+
+msgid "Select Stack"
+msgstr ""
+
+msgid "Select a file from the left sidebar to begin editing. Afterwards, you'll be able to commit your changes."
+msgstr ""
+
+msgid "Select a group to invite"
+msgstr ""
+
+msgid "Select a label"
+msgstr ""
+
+msgid "Select a namespace to fork the project"
+msgstr ""
+
+msgid "Select a new namespace"
+msgstr ""
+
+msgid "Select a project"
+msgstr ""
+
+msgid "Select a project to read Insights configuration file"
+msgstr ""
+
+msgid "Select a reason"
+msgstr ""
+
+msgid "Select a repository"
+msgstr ""
+
+msgid "Select a template repository"
+msgstr ""
+
+msgid "Select a template type"
+msgstr ""
+
+msgid "Select a timezone"
+msgstr ""
+
+msgid "Select all"
+msgstr ""
+
+msgid "Select an existing Kubernetes cluster or create a new one"
+msgstr ""
+
+msgid "Select assignee"
+msgstr ""
+
+msgid "Select branch"
+msgstr ""
+
+msgid "Select branch/tag"
+msgstr ""
+
+msgid "Select due date"
+msgstr ""
+
+msgid "Select file"
+msgstr ""
+
+msgid "Select group or project"
+msgstr ""
+
+msgid "Select groups to replicate"
+msgstr ""
+
+msgid "Select health status"
+msgstr ""
+
+msgid "Select labels"
+msgstr ""
+
+msgid "Select merge moment"
+msgstr ""
+
+msgid "Select milestone"
+msgstr ""
+
+msgid "Select private project"
+msgstr ""
+
+msgid "Select project"
+msgstr ""
+
+msgid "Select project and zone to choose machine type"
+msgstr ""
+
+msgid "Select project to choose zone"
+msgstr ""
+
+msgid "Select projects"
+msgstr ""
+
+msgid "Select projects you want to import."
+msgstr ""
+
+msgid "Select required regulatory standard"
+msgstr ""
+
+msgid "Select shards to replicate"
+msgstr ""
+
+msgid "Select source branch"
+msgstr ""
+
+msgid "Select start date"
+msgstr ""
+
+msgid "Select status"
+msgstr ""
+
+msgid "Select strategy activation method"
+msgstr ""
+
+msgid "Select subscription"
+msgstr ""
+
+msgid "Select target branch"
+msgstr ""
+
+msgid "Select the branch you want to set as the default for this project. All merge requests and commits will automatically be made against this branch unless you specify a different one."
+msgstr ""
+
+msgid "Select the custom project template source group."
+msgstr ""
+
+msgid "Select timeframe"
+msgstr ""
+
+msgid "Select user"
+msgstr ""
+
+msgid "Selected levels cannot be used by non-admin users for groups, projects or snippets. If the public level is restricted, user profiles are only visible to logged in users."
+msgstr ""
+
+msgid "Selecting a GitLab user will add a link to the GitLab user in the descriptions of issues and comments (e.g. \"By <a href=\"#\">@johnsmith</a>\"). It will also associate and/or assign these issues and comments with the selected user."
+msgstr ""
+
+msgid "Selective synchronization"
+msgstr ""
+
+msgid "Self monitoring project does not exist"
+msgstr ""
+
+msgid "Self-monitoring project does not exist. Please check logs for any error messages"
+msgstr ""
+
+msgid "Self-monitoring project has been successfully deleted"
+msgstr ""
+
+msgid "Self-monitoring project was not deleted. Please check logs for any error messages"
+msgstr ""
+
+msgid "SelfMonitoring|Disable self monitoring?"
+msgstr ""
+
+msgid "SelfMonitoring|Disabling this feature will delete the self monitoring project. Are you sure you want to delete the project?"
+msgstr ""
+
+msgid "SelfMonitoring|Enable or disable instance self monitoring"
+msgstr ""
+
+msgid "SelfMonitoring|Enabling this feature creates a %{projectLinkStart}project%{projectLinkEnd} that can be used to monitor the health of your instance."
+msgstr ""
+
+msgid "SelfMonitoring|Enabling this feature creates a project that can be used to monitor the health of your instance."
+msgstr ""
+
+msgid "SelfMonitoring|Self monitoring"
+msgstr ""
+
+msgid "SelfMonitoring|Self monitoring project has been successfully created."
+msgstr ""
+
+msgid "SelfMonitoring|Self monitoring project has been successfully deleted."
+msgstr ""
+
+msgid "Send a separate email notification to Developers."
+msgstr ""
+
+msgid "Send confirmation email"
+msgstr ""
+
+msgid "Send email"
+msgstr ""
+
+msgid "Send email notification"
+msgstr ""
+
+msgid "Send message"
+msgstr ""
+
+msgid "Send report"
+msgstr ""
+
+msgid "Send usage data"
+msgstr ""
+
+msgid "Sentry API URL"
+msgstr ""
+
+msgid "Sentry event"
+msgstr ""
+
+msgid "Sep"
+msgstr ""
+
+msgid "Separate topics with commas."
+msgstr ""
+
+msgid "September"
+msgstr ""
+
+msgid "SeriesFinalConjunction|and"
+msgstr ""
+
+msgid "Serve repository static objects (e.g. archives, blobs, ...) from an external storage (e.g. a CDN)."
+msgstr ""
+
+msgid "Server supports batch API only, please update your Git LFS client to version 1.0.1 and up."
+msgstr ""
+
+msgid "Server version"
+msgstr ""
+
+msgid "Serverless"
+msgstr ""
+
+msgid "Serverless domain"
+msgstr ""
+
+msgid "ServerlessDetails|Function invocation metrics require Prometheus to be installed first."
+msgstr ""
+
+msgid "ServerlessDetails|Install Prometheus"
+msgstr ""
+
+msgid "ServerlessDetails|Invocation metrics loading or not available at this time."
+msgstr ""
+
+msgid "ServerlessDetails|Invocations"
+msgstr ""
+
+msgid "ServerlessDetails|Kubernetes Pods"
+msgstr ""
+
+msgid "ServerlessDetails|More information"
+msgstr ""
+
+msgid "ServerlessDetails|No pods loaded at this time."
+msgstr ""
+
+msgid "ServerlessDetails|Number of Kubernetes pods in use over time based on necessity."
+msgstr ""
+
+msgid "ServerlessDetails|pod in use"
+msgstr ""
+
+msgid "ServerlessDetails|pods in use"
+msgstr ""
+
+msgid "ServerlessURL|Copy URL"
+msgstr ""
+
+msgid "Serverless| In order to start using functions as a service, you must first install Knative on your Kubernetes cluster."
+msgstr ""
+
+msgid "Serverless|Getting started with serverless"
+msgstr ""
+
+msgid "Serverless|Help shape the future of Serverless at GitLab"
+msgstr ""
+
+msgid "Serverless|If you believe none of these apply, please check back later as the function data may be in the process of becoming available."
+msgstr ""
+
+msgid "Serverless|Install Knative"
+msgstr ""
+
+msgid "Serverless|Learn more about Serverless"
+msgstr ""
+
+msgid "Serverless|No functions available"
+msgstr ""
+
+msgid "Serverless|Sign up for First Look"
+msgstr ""
+
+msgid "Serverless|The deploy job has not finished."
+msgstr ""
+
+msgid "Serverless|The functions listed in the %{startTag}serverless.yml%{endTag} file don't match the namespace of your cluster."
+msgstr ""
+
+msgid "Serverless|There is currently no function data available from Knative. This could be for a variety of reasons including:"
+msgstr ""
+
+msgid "Serverless|We are continually striving to improve our Serverless functionality. As a Knative user, we would love to hear how we can make this experience better for you. Sign up for GitLab First Look today and we will be in touch shortly."
+msgstr ""
+
+msgid "Serverless|Your %{startTag}.gitlab-ci.yml%{endTag} file is not properly configured."
+msgstr ""
+
+msgid "Serverless|Your repository does not have a corresponding %{startTag}serverless.yml%{endTag} file."
+msgstr ""
+
+msgid "Service"
+msgstr ""
+
+msgid "Service Desk"
+msgstr ""
+
+msgid "Service Desk is enabled but not yet active"
+msgstr ""
+
+msgid "Service Templates"
+msgstr ""
+
+msgid "Service URL"
+msgstr ""
+
+msgid "Session duration (minutes)"
+msgstr ""
+
+msgid "Set %{epic_ref} as the parent epic."
+msgstr ""
+
+msgid "Set a default template for issue descriptions."
+msgstr ""
+
+msgid "Set a number of approvals required, the approvers and other approval settings."
+msgstr ""
+
+msgid "Set a password on your account to pull or push via %{protocol}."
+msgstr ""
+
+msgid "Set a template repository for projects in this group"
+msgstr ""
+
+msgid "Set an instance-wide domain that will be available to all clusters when installing Knative."
+msgstr ""
+
+msgid "Set default and restrict visibility levels. Configure import sources and git access protocol."
+msgstr ""
+
+msgid "Set due date"
+msgstr ""
+
+msgid "Set instance-wide template repository"
+msgstr ""
+
+msgid "Set iteration"
+msgstr ""
+
+msgid "Set max session time for web terminal."
+msgstr ""
+
+msgid "Set milestone"
+msgstr ""
+
+msgid "Set new password"
+msgstr ""
+
+msgid "Set notification email for abuse reports."
+msgstr ""
+
+msgid "Set parent epic to an epic"
+msgstr ""
+
+msgid "Set projects and maximum size limits, session duration, user options, and check feature availability for namespace plan."
+msgstr ""
+
+msgid "Set requirements for a user to sign-in. Enable mandatory two-factor authentication."
+msgstr ""
+
+msgid "Set target branch"
+msgstr ""
+
+msgid "Set target branch to %{branch_name}."
+msgstr ""
+
+msgid "Set the default expiration time for each job's artifacts. 0 for unlimited. The default unit is in seconds, but you can define an alternative. For example: <code>4 mins 2 sec</code>, <code>2h42min</code>."
+msgstr ""
+
+msgid "Set the due date to %{due_date}."
+msgstr ""
+
+msgid "Set the duration for which the jobs will be considered as old and expired. Once that time passes, the jobs will be archived and no longer able to be retried. Make it empty to never expire jobs. It has to be no less than 1 day, for example: <code>15 days</code>, <code>1 month</code>, <code>2 years</code>."
+msgstr ""
+
+msgid "Set the iteration to %{iteration_reference}."
+msgstr ""
+
+msgid "Set the maximum file size for each job's artifacts"
+msgstr ""
+
+msgid "Set the maximum number of pipeline minutes that a group can use on shared Runners per month. 0 for unlimited."
+msgstr ""
+
+msgid "Set the milestone to %{milestone_reference}."
+msgstr ""
+
+msgid "Set the number of concurrent requests this secondary node will make to the primary node while backfilling."
+msgstr ""
+
+msgid "Set the synchronization and verification capacity for the secondary node."
+msgstr ""
+
+msgid "Set the timeout in seconds to send a secondary node status to the primary and IPs allowed for the secondary nodes."
+msgstr ""
+
+msgid "Set time estimate"
+msgstr ""
+
+msgid "Set time estimate to %{time_estimate}."
+msgstr ""
+
+msgid "Set up CI/CD"
+msgstr ""
+
+msgid "Set up Jira Integration"
+msgstr ""
+
+msgid "Set up a %{type} Runner automatically"
+msgstr ""
+
+msgid "Set up a %{type} Runner manually"
+msgstr ""
+
+msgid "Set up assertions/attributes/claims (email, first_name, last_name) and NameID according to %{docsLinkStart}the documentation %{icon}%{docsLinkEnd}"
+msgstr ""
+
+msgid "Set up new U2F device"
+msgstr ""
+
+msgid "Set up new password"
+msgstr ""
+
+msgid "Set up pipeline subscriptions for this project."
+msgstr ""
+
+msgid "Set up your project to automatically push and/or pull changes to/from another repository. Branches, tags, and commits will be synced automatically."
+msgstr ""
+
+msgid "Set weight"
+msgstr ""
+
+msgid "Set weight to %{weight}."
+msgstr ""
+
+msgid "Set what should be replicated by choosing specific projects or groups by the secondary node."
+msgstr ""
+
+msgid "SetPasswordToCloneLink|set a password"
+msgstr ""
+
+msgid "SetStatusModal|Add status emoji"
+msgstr ""
+
+msgid "SetStatusModal|Clear status"
+msgstr ""
+
+msgid "SetStatusModal|Edit status"
+msgstr ""
+
+msgid "SetStatusModal|Remove status"
+msgstr ""
+
+msgid "SetStatusModal|Set a status"
+msgstr ""
+
+msgid "SetStatusModal|Set status"
+msgstr ""
+
+msgid "SetStatusModal|Sorry, we weren't able to set your status. Please try again later."
+msgstr ""
+
+msgid "SetStatusModal|What's your status?"
+msgstr ""
+
+msgid "Sets %{epic_ref} as parent epic."
+msgstr ""
+
+msgid "Sets target branch to %{branch_name}."
+msgstr ""
+
+msgid "Sets the due date to %{due_date}."
+msgstr ""
+
+msgid "Sets the iteration to %{iteration_reference}."
+msgstr ""
+
+msgid "Sets the milestone to %{milestone_reference}."
+msgstr ""
+
+msgid "Sets time estimate to %{time_estimate}."
+msgstr ""
+
+msgid "Sets weight to %{weight}."
+msgstr ""
+
+msgid "Settings"
+msgstr ""
+
+msgid "Settings related to the use and experience of using GitLab's Package Registry."
+msgstr ""
+
+msgid "Settings to prevent self-approval across all projects in the instance. Only an administrator can modify these settings."
+msgstr ""
+
+msgid "Severity"
+msgstr ""
+
+msgid "Shards (%{shards})"
+msgstr ""
+
+msgid "Shards to synchronize"
+msgstr ""
+
+msgid "Share"
+msgstr ""
+
+msgid "Share the <strong>%{sso_label}</strong> with members so they can sign in to your group through your identity provider"
+msgstr ""
+
+msgid "Shared Runners"
+msgstr ""
+
+msgid "Shared projects"
+msgstr ""
+
+msgid "Shared runners help link"
+msgstr ""
+
+msgid "SharedRunnersMinutesSettings|By resetting the pipeline minutes for this namespace, the currently used minutes will be set to zero."
+msgstr ""
+
+msgid "SharedRunnersMinutesSettings|Reset pipeline minutes"
+msgstr ""
+
+msgid "SharedRunnersMinutesSettings|Reset used pipeline minutes"
+msgstr ""
+
+msgid "Sherlock Transactions"
+msgstr ""
+
+msgid "Should you ever lose your phone or access to your one time password secret, each of these recovery codes can be used one time each to regain access to your account. Please save them in a safe place, or you %{b_start}will%{b_end} lose access to your account."
+msgstr ""
+
+msgid "Show all activity"
+msgstr ""
+
+msgid "Show all members"
+msgstr ""
+
+msgid "Show all requirements."
+msgstr ""
+
+msgid "Show archived projects"
+msgstr ""
+
+msgid "Show archived projects only"
+msgstr ""
+
+msgid "Show command"
+msgstr ""
+
+msgid "Show comments"
+msgstr ""
+
+msgid "Show comments only"
+msgstr ""
+
+msgid "Show commit description"
+msgstr ""
+
+msgid "Show complete raw log"
+msgstr ""
+
+msgid "Show details"
+msgstr ""
+
+msgid "Show file browser"
+msgstr ""
+
+msgid "Show file contents"
+msgstr ""
+
+msgid "Show latest version"
+msgstr ""
+
+msgid "Show list"
+msgstr ""
+
+msgid "Show me everything"
+msgstr ""
+
+msgid "Show me how to add a pipeline"
+msgstr ""
+
+msgid "Show me more advanced stuff"
+msgstr ""
+
+msgid "Show only direct members"
+msgstr ""
+
+msgid "Show only inherited members"
+msgstr ""
+
+msgid "Show parent pages"
+msgstr ""
+
+msgid "Show parent subgroups"
+msgstr ""
+
+msgid "Show whitespace changes"
+msgstr ""
+
+msgid "Showing %d event"
+msgid_plural "Showing %d events"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "Showing %{count} of %{total} projects"
+msgstr ""
+
+msgid "Showing %{count} project"
+msgid_plural "Showing %{count} projects"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "Showing %{limit} of %{total_count} issues. "
+msgstr ""
+
+msgid "Showing %{pageSize} of %{total} issues"
+msgstr ""
+
+msgid "Showing Latest Version"
+msgstr ""
+
+msgid "Showing Version #%{versionNumber}"
+msgstr ""
+
+msgid "Showing all issues"
+msgstr ""
+
+msgid "Showing last %{size} of log -"
+msgstr ""
+
+msgid "Side-by-side"
+msgstr ""
+
+msgid "Sidebar|Assign health status"
+msgstr ""
+
+msgid "Sidebar|Change weight"
+msgstr ""
+
+msgid "Sidebar|Health status"
+msgstr ""
+
+msgid "Sidebar|No status"
+msgstr ""
+
+msgid "Sidebar|None"
+msgstr ""
+
+msgid "Sidebar|Only numeral characters allowed"
+msgstr ""
+
+msgid "Sidebar|Weight"
+msgstr ""
+
+msgid "Sign in"
+msgstr ""
+
+msgid "Sign in / Register"
+msgstr ""
+
+msgid "Sign in to \"%{group_name}\""
+msgstr ""
+
+msgid "Sign in using smart card"
+msgstr ""
+
+msgid "Sign in via 2FA code"
+msgstr ""
+
+msgid "Sign in with Single Sign-On"
+msgstr ""
+
+msgid "Sign in with smart card"
+msgstr ""
+
+msgid "Sign out"
+msgstr ""
+
+msgid "Sign out & Register"
+msgstr ""
+
+msgid "Sign up"
+msgstr ""
+
+msgid "Sign up was successful! Please confirm your email to sign in."
+msgstr ""
+
+msgid "Sign-in restrictions"
+msgstr ""
+
+msgid "Sign-up restrictions"
+msgstr ""
+
+msgid "SignUp|First Name is too long (maximum is %{max_length} characters)."
+msgstr ""
+
+msgid "SignUp|Last Name is too long (maximum is %{max_length} characters)."
+msgstr ""
+
+msgid "SignUp|Name is too long (maximum is %{max_length} characters)."
+msgstr ""
+
+msgid "SignUp|Username is too long (maximum is %{max_length} characters)."
+msgstr ""
+
+msgid "Signed in"
+msgstr ""
+
+msgid "Signed in with %{authentication} authentication"
+msgstr ""
+
+msgid "Signing in using %{label} has been disabled"
+msgstr ""
+
+msgid "Signing in using your %{label} account without a pre-existing GitLab account is not allowed."
+msgstr ""
+
+msgid "Similar issues"
+msgstr ""
+
+msgid "Single or combined queries"
+msgstr ""
+
+msgid "Size"
+msgstr ""
+
+msgid "Size and domain settings for static websites"
+msgstr ""
+
+msgid "Size limit per repository (MB)"
+msgstr ""
+
+msgid "Size settings for static websites"
+msgstr ""
+
+msgid "Skip outdated deployment jobs"
+msgstr ""
+
+msgid "Skipped"
+msgstr ""
+
+msgid "Slack application"
+msgstr ""
+
+msgid "Slack channels (e.g. general, development)"
+msgstr ""
+
+msgid "Slack integration allows you to interact with GitLab via slash commands in a chat window."
+msgstr ""
+
+msgid "SlackIntegration|%{webhooks_link_start}Add an incoming webhook%{webhooks_link_end} in your Slack team. The default channel can be overridden for each event."
+msgstr ""
+
+msgid "SlackIntegration|<strong>Note:</strong> Usernames and private channels are not supported."
+msgstr ""
+
+msgid "SlackIntegration|Paste the <strong>Webhook URL</strong> into the field below."
+msgstr ""
+
+msgid "SlackIntegration|Select events below to enable notifications. The <strong>Slack channel names</strong> and <strong>Slack username</strong> fields are optional."
+msgstr ""
+
+msgid "SlackIntegration|This service send notifications about projects' events to Slack channels. To set up this service:"
+msgstr ""
+
+msgid "SlackService|2. Paste the <strong>Token</strong> into the field below"
+msgstr ""
+
+msgid "SlackService|3. Select the <strong>Active</strong> checkbox, press <strong>Save changes</strong> and start using GitLab inside Slack!"
+msgstr ""
+
+msgid "SlackService|Fill in the word that works best for your team."
+msgstr ""
+
+msgid "SlackService|See list of available commands in Slack after setting up this service, by entering"
+msgstr ""
+
+msgid "SlackService|This service allows users to perform common operations on this project by entering slash commands in Slack."
+msgstr ""
+
+msgid "Slower but makes sure the project workspace is pristine as it clones the repository from scratch for every job"
+msgstr ""
+
+msgid "Smartcard"
+msgstr ""
+
+msgid "Smartcard authentication failed: client certificate header is missing."
+msgstr ""
+
+msgid "Snippets"
+msgstr ""
+
+msgid "Snippets with non-text files can only be edited via Git."
+msgstr ""
+
+msgid "SnippetsEmptyState|Code snippets"
+msgstr ""
+
+msgid "SnippetsEmptyState|Documentation"
+msgstr ""
+
+msgid "SnippetsEmptyState|New snippet"
+msgstr ""
+
+msgid "SnippetsEmptyState|No snippets found"
+msgstr ""
+
+msgid "SnippetsEmptyState|Store, share, and embed small pieces of code and text."
+msgstr ""
+
+msgid "SnippetsEmptyState|There are no snippets to show."
+msgstr ""
+
+msgid "Snippets|Description (optional)"
+msgstr ""
+
+msgid "Snippets|File"
+msgstr ""
+
+msgid "Snippets|Give your file a name to add code highlighting, e.g. example.rb for Ruby"
+msgstr ""
+
+msgid "Snippets|Optionally add a description about what your snippet does or how to use it..."
+msgstr ""
+
+msgid "Snippets|Optionally add a description about what your snippet does or how to use it…"
+msgstr ""
+
+msgid "Snowplow"
+msgstr ""
+
+msgid "Solution"
+msgstr ""
+
+msgid "Some child epics may be hidden due to applied filters"
+msgstr ""
+
+msgid "Some common domains are not allowed. %{read_more_link}."
+msgstr ""
+
+msgid "Some email servers do not support overriding the email sender name. Enable this option to include the name of the author of the issue, merge request or comment in the email body instead."
+msgstr ""
+
+msgid "Some of the designs you tried uploading did not change:"
+msgstr ""
+
+msgid "Some of your epics may not be visible. A roadmap is limited to the first 1,000 epics, in your selected sort order."
+msgstr ""
+
+msgid "Someone edited the issue at the same time you did. Please check out %{linkStart}the issue%{linkEnd} and make sure your changes will not unintentionally remove theirs."
+msgstr ""
+
+msgid "Someone edited this %{issueType} at the same time you did. The description has been updated and you will need to make your changes again."
+msgstr ""
+
+msgid "Someone edited this merge request at the same time you did. Please refresh the page to see changes."
+msgstr ""
+
+msgid "Something went wrong on our end"
+msgstr ""
+
+msgid "Something went wrong on our end."
+msgstr ""
+
+msgid "Something went wrong on our end. Please try again!"
+msgstr ""
+
+msgid "Something went wrong on our end. Please try again."
+msgstr ""
+
+msgid "Something went wrong trying to change the confidentiality of this issue"
+msgstr ""
+
+msgid "Something went wrong trying to change the locked state of this %{issuableDisplayName}"
+msgstr ""
+
+msgid "Something went wrong when toggling the button"
+msgstr ""
+
+msgid "Something went wrong while adding your award. Please try again."
+msgstr ""
+
+msgid "Something went wrong while applying the batch of suggestions. Please try again."
+msgstr ""
+
+msgid "Something went wrong while applying the suggestion. Please try again."
+msgstr ""
+
+msgid "Something went wrong while archiving a requirement."
+msgstr ""
+
+msgid "Something went wrong while closing the %{issuable}. Please try again later"
+msgstr ""
+
+msgid "Something went wrong while creating a requirement."
+msgstr ""
+
+msgid "Something went wrong while deleting description changes. Please try again."
+msgstr ""
+
+msgid "Something went wrong while deleting the package."
+msgstr ""
+
+msgid "Something went wrong while deleting the source branch. Please try again."
+msgstr ""
+
+msgid "Something went wrong while deleting your note. Please try again."
+msgstr ""
+
+msgid "Something went wrong while deploying this environment. Please try again."
+msgstr ""
+
+msgid "Something went wrong while editing your comment. Please try again."
+msgstr ""
+
+msgid "Something went wrong while fetching %{listType} list"
+msgstr ""
+
+msgid "Something went wrong while fetching comments. Please try again."
+msgstr ""
+
+msgid "Something went wrong while fetching description changes. Please try again."
+msgstr ""
+
+msgid "Something went wrong while fetching group member contributions"
+msgstr ""
+
+msgid "Something went wrong while fetching latest comments."
+msgstr ""
+
+msgid "Something went wrong while fetching projects"
+msgstr ""
+
+msgid "Something went wrong while fetching projects."
+msgstr ""
+
+msgid "Something went wrong while fetching related merge requests."
+msgstr ""
+
+msgid "Something went wrong while fetching requirements count."
+msgstr ""
+
+msgid "Something went wrong while fetching requirements list."
+msgstr ""
+
+msgid "Something went wrong while fetching the environments for this merge request. Please try again."
+msgstr ""
+
+msgid "Something went wrong while fetching the package."
+msgstr ""
+
+msgid "Something went wrong while fetching the packages list."
+msgstr ""
+
+msgid "Something went wrong while initializing the OpenAPI viewer"
+msgstr ""
+
+msgid "Something went wrong while merging this merge request. Please try again."
+msgstr ""
+
+msgid "Something went wrong while moving issues."
+msgstr ""
+
+msgid "Something went wrong while obtaining the Let's Encrypt certificate."
+msgstr ""
+
+msgid "Something went wrong while performing the action."
+msgstr ""
+
+msgid "Something went wrong while reopening a requirement."
+msgstr ""
+
+msgid "Something went wrong while reopening the %{issuable}. Please try again later"
+msgstr ""
+
+msgid "Something went wrong while resolving this discussion. Please try again."
+msgstr ""
+
+msgid "Something went wrong while stopping this environment. Please try again."
+msgstr ""
+
+msgid "Something went wrong while toggling auto-fix settings, please try again later."
+msgstr ""
+
+msgid "Something went wrong while updating a requirement."
+msgstr ""
+
+msgid "Something went wrong while updating assignees"
+msgstr ""
+
+msgid "Something went wrong while updating your list settings"
+msgstr ""
+
+msgid "Something went wrong with your automatic subscription renewal."
+msgstr ""
+
+msgid "Something went wrong, unable to add %{project} to dashboard"
+msgstr ""
+
+msgid "Something went wrong, unable to add projects to dashboard"
+msgstr ""
+
+msgid "Something went wrong, unable to get projects"
+msgstr ""
+
+msgid "Something went wrong, unable to remove project"
+msgstr ""
+
+msgid "Something went wrong, unable to search projects"
+msgstr ""
+
+msgid "Something went wrong."
+msgstr ""
+
+msgid "Something went wrong. Please try again."
+msgstr ""
+
+msgid "Something went wrong. Try again later."
+msgstr ""
+
+msgid "Sorry, no epics matched your search"
+msgstr ""
+
+msgid "Sorry, no projects matched your search"
+msgstr ""
+
+msgid "Sorry, you have exceeded the maximum browsable page number. Please use the API to explore further."
+msgstr ""
+
+msgid "Sorry, your filter produced no results"
+msgstr ""
+
+msgid "Sort by"
+msgstr ""
+
+msgid "Sort direction"
+msgstr ""
+
+msgid "Sort direction: Ascending"
+msgstr ""
+
+msgid "Sort direction: Descending"
+msgstr ""
+
+msgid "SortOptions|Access level, ascending"
+msgstr ""
+
+msgid "SortOptions|Access level, descending"
+msgstr ""
+
+msgid "SortOptions|Created date"
+msgstr ""
+
+msgid "SortOptions|Due date"
+msgstr ""
+
+msgid "SortOptions|Due later"
+msgstr ""
+
+msgid "SortOptions|Due soon"
+msgstr ""
+
+msgid "SortOptions|Expired date"
+msgstr ""
+
+msgid "SortOptions|Label priority"
+msgstr ""
+
+msgid "SortOptions|Largest group"
+msgstr ""
+
+msgid "SortOptions|Largest repository"
+msgstr ""
+
+msgid "SortOptions|Last Contact"
+msgstr ""
+
+msgid "SortOptions|Last created"
+msgstr ""
+
+msgid "SortOptions|Last joined"
+msgstr ""
+
+msgid "SortOptions|Last updated"
+msgstr ""
+
+msgid "SortOptions|Least popular"
+msgstr ""
+
+msgid "SortOptions|Less weight"
+msgstr ""
+
+msgid "SortOptions|Manual"
+msgstr ""
+
+msgid "SortOptions|Milestone due date"
+msgstr ""
+
+msgid "SortOptions|Milestone due later"
+msgstr ""
+
+msgid "SortOptions|Milestone due soon"
+msgstr ""
+
+msgid "SortOptions|More weight"
+msgstr ""
+
+msgid "SortOptions|Most popular"
+msgstr ""
+
+msgid "SortOptions|Most stars"
+msgstr ""
+
+msgid "SortOptions|Name"
+msgstr ""
+
+msgid "SortOptions|Name, ascending"
+msgstr ""
+
+msgid "SortOptions|Name, descending"
+msgstr ""
+
+msgid "SortOptions|Oldest created"
+msgstr ""
+
+msgid "SortOptions|Oldest joined"
+msgstr ""
+
+msgid "SortOptions|Oldest last activity"
+msgstr ""
+
+msgid "SortOptions|Oldest sign in"
+msgstr ""
+
+msgid "SortOptions|Oldest starred"
+msgstr ""
+
+msgid "SortOptions|Oldest updated"
+msgstr ""
+
+msgid "SortOptions|Popularity"
+msgstr ""
+
+msgid "SortOptions|Priority"
+msgstr ""
+
+msgid "SortOptions|Project"
+msgstr ""
+
+msgid "SortOptions|Recent last activity"
+msgstr ""
+
+msgid "SortOptions|Recent sign in"
+msgstr ""
+
+msgid "SortOptions|Recently starred"
+msgstr ""
+
+msgid "SortOptions|Size"
+msgstr ""
+
+msgid "SortOptions|Sort by:"
+msgstr ""
+
+msgid "SortOptions|Sort direction"
+msgstr ""
+
+msgid "SortOptions|Stars"
+msgstr ""
+
+msgid "SortOptions|Start date"
+msgstr ""
+
+msgid "SortOptions|Start later"
+msgstr ""
+
+msgid "SortOptions|Start soon"
+msgstr ""
+
+msgid "SortOptions|Type"
+msgstr ""
+
+msgid "SortOptions|Version"
+msgstr ""
+
+msgid "SortOptions|Weight"
+msgstr ""
+
+msgid "Source"
+msgstr ""
+
+msgid "Source (branch or tag)"
+msgstr ""
+
+msgid "Source code"
+msgstr ""
+
+msgid "Source code (%{fileExtension})"
+msgstr ""
+
+msgid "Source is not available"
+msgstr ""
+
+msgid "Source project cannot be found."
+msgstr ""
+
+msgid "Sourcegraph"
+msgstr ""
+
+msgid "SourcegraphAdmin|Block on private and internal projects"
+msgstr ""
+
+msgid "SourcegraphAdmin|Configure the URL to a Sourcegraph instance which can read your GitLab projects."
+msgstr ""
+
+msgid "SourcegraphAdmin|Enable Sourcegraph"
+msgstr ""
+
+msgid "SourcegraphAdmin|Enable code intelligence powered by %{link_start}Sourcegraph%{link_end} on your GitLab instance's code views and merge requests."
+msgstr ""
+
+msgid "SourcegraphAdmin|If checked, only public projects will have code intelligence and communicate with Sourcegraph."
+msgstr ""
+
+msgid "SourcegraphAdmin|More information"
+msgstr ""
+
+msgid "SourcegraphAdmin|Save changes"
+msgstr ""
+
+msgid "SourcegraphAdmin|Sourcegraph URL"
+msgstr ""
+
+msgid "SourcegraphAdmin|e.g. https://sourcegraph.example.com"
+msgstr ""
+
+msgid "SourcegraphPreferences|This feature is experimental and currently limited to certain projects."
+msgstr ""
+
+msgid "SourcegraphPreferences|This feature is experimental and limited to public projects."
+msgstr ""
+
+msgid "SourcegraphPreferences|This feature is experimental."
+msgstr ""
+
+msgid "SourcegraphPreferences|Uses %{link_start}Sourcegraph.com%{link_end}."
+msgstr ""
+
+msgid "SourcegraphPreferences|Uses a custom %{link_start}Sourcegraph instance%{link_end}."
+msgstr ""
+
+msgid "Spam Logs"
+msgstr ""
+
+msgid "Spam and Anti-bot Protection"
+msgstr ""
+
+msgid "Spam log successfully submitted as ham."
+msgstr ""
+
+msgid "Specific Runners"
+msgstr ""
+
+msgid "Specified URL cannot be used: \"%{reason}\""
+msgstr ""
+
+msgid "Specify an e-mail address regex pattern to identify default internal users."
+msgstr ""
+
+msgid "Specify the following URL during the Runner setup:"
+msgstr ""
+
+msgid "Speed up your DevOps<br>with GitLab"
+msgstr ""
+
+msgid "Squash commit message"
+msgstr ""
+
+msgid "Squash commits"
+msgstr ""
+
+msgid "Stack trace"
+msgstr ""
+
+msgid "Stacktrace snippet"
+msgstr ""
+
+msgid "Stage"
+msgstr ""
+
+msgid "Stage & Commit"
+msgstr ""
+
+msgid "Stage data updated"
+msgstr ""
+
+msgid "Stage removed"
+msgstr ""
+
+msgid "Standard"
+msgstr ""
+
+msgid "Star a label to make it a priority label. Order the prioritized labels to change their relative priority, by dragging."
+msgstr ""
+
+msgid "Star labels to start sorting by priority"
+msgstr ""
+
+msgid "Star toggle failed. Try again later."
+msgstr ""
+
+msgid "StarProject|Star"
+msgstr ""
+
+msgid "Starred Projects"
+msgstr ""
+
+msgid "Starred Projects' Activity"
+msgstr ""
+
+msgid "Starred projects"
+msgstr ""
+
+msgid "StarredProjectsEmptyState|Visit a project page and press on a star icon. Then, you can find the project on this page."
+msgstr ""
+
+msgid "StarredProjectsEmptyState|You don't have starred projects yet."
+msgstr ""
+
+msgid "Starrers"
+msgstr ""
+
+msgid "Stars"
+msgstr ""
+
+msgid "Start Date"
+msgstr ""
+
+msgid "Start Web Terminal"
+msgstr ""
+
+msgid "Start a %{new_merge_request} with these changes"
+msgstr ""
+
+msgid "Start a Free Gold Trial"
+msgstr ""
+
+msgid "Start a new discussion..."
+msgstr ""
+
+msgid "Start a new merge request"
+msgstr ""
+
+msgid "Start a review"
+msgstr ""
+
+msgid "Start and due date"
+msgstr ""
+
+msgid "Start by choosing a group to see how your team is spending time. You can then drill down to the project level."
+msgstr ""
+
+msgid "Start by choosing a group to start exploring the merge requests in that group. You can then proceed to filter by projects, labels, milestones and authors."
+msgstr ""
+
+msgid "Start cleanup"
+msgstr ""
+
+msgid "Start date"
+msgstr ""
+
+msgid "Start merge train"
+msgstr ""
+
+msgid "Start merge train when pipeline succeeds"
+msgstr ""
+
+msgid "Start search"
+msgstr ""
+
+msgid "Start the Runner!"
+msgstr ""
+
+msgid "Start thread"
+msgstr ""
+
+msgid "Start thread & close %{noteable_name}"
+msgstr ""
+
+msgid "Start thread & reopen %{noteable_name}"
+msgstr ""
+
+msgid "Start using Directed Acyclic Graphs (DAG)"
+msgstr ""
+
+msgid "Start your Free Gold Trial"
+msgstr ""
+
+msgid "Start your free trial"
+msgstr ""
+
+msgid "Start your trial"
+msgstr ""
+
+msgid "Started"
+msgstr ""
+
+msgid "Started %{startsIn}"
+msgstr ""
+
+msgid "Started asynchronous removal of all repository check states."
+msgstr ""
+
+msgid "Started:"
+msgstr ""
+
+msgid "Starting..."
+msgstr ""
+
+msgid "Starts %{startsIn}"
+msgstr ""
+
+msgid "Starts at (UTC)"
+msgstr ""
+
+msgid "State your message to activate"
+msgstr ""
+
+msgid "Static Application Security Testing (SAST)"
+msgstr ""
+
+msgid "StaticSiteEditor|An error occurred while submitting your changes."
+msgstr ""
+
+msgid "StaticSiteEditor|Branch could not be created."
+msgstr ""
+
+msgid "StaticSiteEditor|Could not commit the content changes."
+msgstr ""
+
+msgid "StaticSiteEditor|Could not create merge request."
+msgstr ""
+
+msgid "StaticSiteEditor|Incompatible file content"
+msgstr ""
+
+msgid "StaticSiteEditor|Return to site"
+msgstr ""
+
+msgid "StaticSiteEditor|Static site editor"
+msgstr ""
+
+msgid "StaticSiteEditor|Success!"
+msgstr ""
+
+msgid "StaticSiteEditor|Summary of changes"
+msgstr ""
+
+msgid "StaticSiteEditor|The Static Site Editor is currently configured to only edit Markdown content on pages generated from Middleman. Visit the documentation to learn more about configuring your site to use the Static Site Editor."
+msgstr ""
+
+msgid "StaticSiteEditor|Update %{sourcePath} file"
+msgstr ""
+
+msgid "StaticSiteEditor|View documentation"
+msgstr ""
+
+msgid "StaticSiteEditor|View merge request"
+msgstr ""
+
+msgid "StaticSiteEditor|You added a commit:"
+msgstr ""
+
+msgid "StaticSiteEditor|You created a merge request:"
+msgstr ""
+
+msgid "StaticSiteEditor|You created a new branch:"
+msgstr ""
+
+msgid "StaticSiteEditor|Your changes have been submitted and a merge request has been created. The changes won’t be visible on the site until the merge request has been accepted."
+msgstr ""
+
+msgid "Statistics"
+msgstr ""
+
+msgid "Status"
+msgstr ""
+
+msgid "Status:"
+msgstr ""
+
+msgid "Status: %{title}"
+msgstr ""
+
+msgid "StatusPage|AWS Secret access key"
+msgstr ""
+
+msgid "StatusPage|AWS access key ID"
+msgstr ""
+
+msgid "StatusPage|AWS documentation"
+msgstr ""
+
+msgid "StatusPage|AWS region"
+msgstr ""
+
+msgid "StatusPage|Active"
+msgstr ""
+
+msgid "StatusPage|Bucket %{docsLink}"
+msgstr ""
+
+msgid "StatusPage|Configure file storage settings to link issues in this project to an external status page."
+msgstr ""
+
+msgid "StatusPage|For help with configuration, visit %{docsLink}"
+msgstr ""
+
+msgid "StatusPage|S3 Bucket name"
+msgstr ""
+
+msgid "StatusPage|Status page"
+msgstr ""
+
+msgid "StatusPage|Status page URL"
+msgstr ""
+
+msgid "StatusPage|Status page frontend documentation"
+msgstr ""
+
+msgid "StatusPage|To publish incidents to an external status page, GitLab will store a JSON file in your Amazon S3 account in a location accessible to your external status page service. Make sure to also set up %{docsLink}"
+msgstr ""
+
+msgid "StatusPage|configuration documentation"
+msgstr ""
+
+msgid "StatusPage|your status page frontend."
+msgstr ""
+
+msgid "Stay updated about the performance and health of your environment by configuring Prometheus to monitor your deployments."
+msgstr ""
+
+msgid "Still, we recommend keeping a backup saved somewhere. Otherwise, if you ever need it and have lost it, you will need to request GitLab Inc. to send it to you again."
+msgstr ""
+
+msgid "Stop Terminal"
+msgstr ""
+
+msgid "Stop impersonation"
+msgstr ""
+
+msgid "Stop this environment"
+msgstr ""
+
+msgid "Stopped"
+msgstr ""
+
+msgid "Stopping..."
+msgstr ""
+
+msgid "Storage"
+msgstr ""
+
+msgid "Storage nodes for new repositories"
+msgstr ""
+
+msgid "Storage:"
+msgstr ""
+
+msgid "StorageSize|Unknown"
+msgstr ""
+
+msgid "Subgroup milestone"
+msgstr ""
+
+msgid "Subgroup overview"
+msgstr ""
+
+msgid "SubgroupCreationLevel|Allowed to create subgroups"
+msgstr ""
+
+msgid "SubgroupCreationlevel|Allowed to create subgroups"
+msgstr ""
+
+msgid "SubgroupCreationlevel|Maintainers"
+msgstr ""
+
+msgid "SubgroupCreationlevel|Owners"
+msgstr ""
+
+msgid "Subgroups"
+msgstr ""
+
+msgid "Subgroups and projects"
+msgstr ""
+
+msgid "Subject Key Identifier:"
+msgstr ""
+
+msgid "Subkeys"
+msgstr ""
+
+msgid "Submit %{humanized_resource_name}"
+msgstr ""
+
+msgid "Submit Changes"
+msgstr ""
+
+msgid "Submit a review"
+msgstr ""
+
+msgid "Submit as spam"
+msgstr ""
+
+msgid "Submit feedback"
+msgstr ""
+
+msgid "Submit issue"
+msgstr ""
+
+msgid "Submit review"
+msgstr ""
+
+msgid "Submit search"
+msgstr ""
+
+msgid "Submit the current review."
+msgstr ""
+
+msgid "Submitted the current review."
+msgstr ""
+
+msgid "Subscribe"
+msgstr ""
+
+msgid "Subscribe at group level"
+msgstr ""
+
+msgid "Subscribe at project level"
+msgstr ""
+
+msgid "Subscribe to RSS feed"
+msgstr ""
+
+msgid "Subscribe to calendar"
+msgstr ""
+
+msgid "Subscribed"
+msgstr ""
+
+msgid "Subscribed to this %{quick_action_target}."
+msgstr ""
+
+msgid "Subscribes to this %{quick_action_target}."
+msgstr ""
+
+msgid "Subscription"
+msgstr ""
+
+msgid "Subscription deletion failed."
+msgstr ""
+
+msgid "Subscription successfully applied to \"%{group_name}\""
+msgstr ""
+
+msgid "Subscription successfully created."
+msgstr ""
+
+msgid "Subscription successfully deleted."
+msgstr ""
+
+msgid "SubscriptionTable|Billing"
+msgstr ""
+
+msgid "SubscriptionTable|Free"
+msgstr ""
+
+msgid "SubscriptionTable|GitLab allows you to continue using your subscription even if you exceed the number of seats you purchased. You will be required to pay for these seats upon renewal."
+msgstr ""
+
+msgid "SubscriptionTable|Last invoice"
+msgstr ""
+
+msgid "SubscriptionTable|Loading subscriptions"
+msgstr ""
+
+msgid "SubscriptionTable|Manage"
+msgstr ""
+
+msgid "SubscriptionTable|Max seats used"
+msgstr ""
+
+msgid "SubscriptionTable|Next invoice"
+msgstr ""
+
+msgid "SubscriptionTable|Seats currently in use"
+msgstr ""
+
+msgid "SubscriptionTable|Seats in subscription"
+msgstr ""
+
+msgid "SubscriptionTable|Seats owed"
+msgstr ""
+
+msgid "SubscriptionTable|Subscription end date"
+msgstr ""
+
+msgid "SubscriptionTable|Subscription start date"
+msgstr ""
+
+msgid "SubscriptionTable|This is the last time the GitLab.com team was in contact with you to settle any outstanding balances."
+msgstr ""
+
+msgid "SubscriptionTable|This is the maximum number of users that have existed at the same time since this subscription started."
+msgstr ""
+
+msgid "SubscriptionTable|This is the next date when the GitLab.com team is scheduled to get in contact with you to settle any outstanding balances."
+msgstr ""
+
+msgid "SubscriptionTable|This is the number of seats you will be required to purchase if you update to a paid plan."
+msgstr ""
+
+msgid "SubscriptionTable|Trial"
+msgstr ""
+
+msgid "SubscriptionTable|Trial end date"
+msgstr ""
+
+msgid "SubscriptionTable|Trial start date"
+msgstr ""
+
+msgid "SubscriptionTable|Upgrade"
+msgstr ""
+
+msgid "SubscriptionTable|Usage"
+msgstr ""
+
+msgid "SubscriptionTable|Usage count is performed once a day at 12:00 PM."
+msgstr ""
+
+msgid "Subscriptions"
+msgstr ""
+
+msgid "Subtracted"
+msgstr ""
+
+msgid "Subtracts"
+msgstr ""
+
+msgid "Succeeded"
+msgstr ""
+
+msgid "Successfully activated"
+msgstr ""
+
+msgid "Successfully blocked"
+msgstr ""
+
+msgid "Successfully confirmed"
+msgstr ""
+
+msgid "Successfully deactivated"
+msgstr ""
+
+msgid "Successfully deleted U2F device."
+msgstr ""
+
+msgid "Successfully removed email."
+msgstr ""
+
+msgid "Successfully scheduled a pipeline to run. Go to the %{pipelines_link_start}Pipelines page%{pipelines_link_end} for details."
+msgstr ""
+
+msgid "Successfully unblocked"
+msgstr ""
+
+msgid "Successfully unlocked"
+msgstr ""
+
+msgid "Successfully verified domain ownership"
+msgstr ""
+
+msgid "Suggest code changes which can be immediately applied in one click. Try it out!"
+msgstr ""
+
+msgid "Suggested Solutions"
+msgstr ""
+
+msgid "Suggested change"
+msgstr ""
+
+msgid "Suggested solutions help link"
+msgstr ""
+
+msgid "SuggestedColors|Bright green"
+msgstr ""
+
+msgid "SuggestedColors|Dark grayish cyan"
+msgstr ""
+
+msgid "SuggestedColors|Dark moderate blue"
+msgstr ""
+
+msgid "SuggestedColors|Dark moderate orange"
+msgstr ""
+
+msgid "SuggestedColors|Dark moderate pink"
+msgstr ""
+
+msgid "SuggestedColors|Dark moderate violet"
+msgstr ""
+
+msgid "SuggestedColors|Feijoa"
+msgstr ""
+
+msgid "SuggestedColors|Lime green"
+msgstr ""
+
+msgid "SuggestedColors|Moderate blue"
+msgstr ""
+
+msgid "SuggestedColors|Pure red"
+msgstr ""
+
+msgid "SuggestedColors|Slightly desaturated blue"
+msgstr ""
+
+msgid "SuggestedColors|Slightly desaturated green"
+msgstr ""
+
+msgid "SuggestedColors|Soft orange"
+msgstr ""
+
+msgid "SuggestedColors|Soft red"
+msgstr ""
+
+msgid "SuggestedColors|Strong pink"
+msgstr ""
+
+msgid "SuggestedColors|Strong red"
+msgstr ""
+
+msgid "SuggestedColors|Strong yellow"
+msgstr ""
+
+msgid "SuggestedColors|UA blue"
+msgstr ""
+
+msgid "SuggestedColors|Very dark desaturated blue"
+msgstr ""
+
+msgid "SuggestedColors|Very dark lime green"
+msgstr ""
+
+msgid "SuggestedColors|Very pale orange"
+msgstr ""
+
+msgid "Suggestion is not applicable as the suggestion was not found."
+msgstr ""
+
+msgid "Suggestions are not applicable as one or more suggestions were not found."
+msgstr ""
+
+msgid "Suggestions are not applicable as their lines cannot overlap."
+msgstr ""
+
+msgid "Suggestions must all be on the same branch."
+msgstr ""
+
+msgid "Suggestions that change line count can't be added to batches, yet."
+msgstr ""
+
+msgid "Suggestions:"
+msgstr ""
+
+msgid "Suite"
+msgstr ""
+
+msgid "Summary"
+msgstr ""
+
+msgid "Sunday"
+msgstr ""
+
+msgid "Support"
+msgstr ""
+
+msgid "Support for custom certificates is disabled. Ask your system's administrator to enable it."
+msgstr ""
+
+msgid "Support page URL"
+msgstr ""
+
+msgid "Survey Response"
+msgstr ""
+
+msgid "Switch branch/tag"
+msgstr ""
+
+msgid "Switch to GitLab Next"
+msgstr ""
+
+msgid "Switch to the source to copy the file contents"
+msgstr ""
+
+msgid "Sync information"
+msgstr ""
+
+msgid "Synced"
+msgstr ""
+
+msgid "Synchronization disabled"
+msgstr ""
+
+msgid "System"
+msgstr ""
+
+msgid "System Hooks"
+msgstr ""
+
+msgid "System Info"
+msgstr ""
+
+msgid "System default (%{default})"
+msgstr ""
+
+msgid "System header and footer"
+msgstr ""
+
+msgid "System hook was successfully updated."
+msgstr ""
+
+msgid "System metrics (Custom)"
+msgstr ""
+
+msgid "System metrics (Kubernetes)"
+msgstr ""
+
+msgid "Table of Contents"
+msgstr ""
+
+msgid "Tag"
+msgstr ""
+
+msgid "Tag list:"
+msgstr ""
+
+msgid "Tag name"
+msgstr ""
+
+msgid "Tag this commit."
+msgstr ""
+
+msgid "Tagged this commit to %{tag_name} with \"%{message}\"."
+msgstr ""
+
+msgid "Tagged this commit to %{tag_name}."
+msgstr ""
+
+msgid "Tags"
+msgstr ""
+
+msgid "Tags feed"
+msgstr ""
+
+msgid "Tags this commit to %{tag_name} with \"%{message}\"."
+msgstr ""
+
+msgid "Tags this commit to %{tag_name}."
+msgstr ""
+
+msgid "Tags:"
+msgstr ""
+
+msgid "TagsPage|Browse commits"
+msgstr ""
+
+msgid "TagsPage|Browse files"
+msgstr ""
+
+msgid "TagsPage|Can't find HEAD commit for this tag"
+msgstr ""
+
+msgid "TagsPage|Cancel"
+msgstr ""
+
+msgid "TagsPage|Create tag"
+msgstr ""
+
+msgid "TagsPage|Delete tag"
+msgstr ""
+
+msgid "TagsPage|Deleting the %{tag_name} tag cannot be undone. Are you sure?"
+msgstr ""
+
+msgid "TagsPage|Edit release notes"
+msgstr ""
+
+msgid "TagsPage|Existing branch name, tag, or commit SHA"
+msgstr ""
+
+msgid "TagsPage|Filter by tag name"
+msgstr ""
+
+msgid "TagsPage|New Tag"
+msgstr ""
+
+msgid "TagsPage|New tag"
+msgstr ""
+
+msgid "TagsPage|Optionally, add a message to the tag. Leaving this blank creates a %{link_start}lightweight tag.%{link_end}"
+msgstr ""
+
+msgid "TagsPage|Optionally, create a public Release of your project, based on this tag. Release notes are displayed on the %{releases_page_link_start}Releases%{link_end} page. %{docs_link_start}More information%{link_end}"
+msgstr ""
+
+msgid "TagsPage|Release notes"
+msgstr ""
+
+msgid "TagsPage|Repository has no tags yet."
+msgstr ""
+
+msgid "TagsPage|Sort by"
+msgstr ""
+
+msgid "TagsPage|Tags"
+msgstr ""
+
+msgid "TagsPage|Tags give the ability to mark specific points in history as being important"
+msgstr ""
+
+msgid "TagsPage|This tag has no release notes."
+msgstr ""
+
+msgid "TagsPage|Use git tag command to add a new one:"
+msgstr ""
+
+msgid "TagsPage|Write your release notes or drag files here…"
+msgstr ""
+
+msgid "TagsPage|protected"
+msgstr ""
+
+msgid "Target Branch"
+msgstr ""
+
+msgid "Target Path"
+msgstr ""
+
+msgid "Target branch"
+msgstr ""
+
+msgid "Target-Branch"
+msgstr ""
+
+msgid "Team"
+msgstr ""
+
+msgid "Team domain"
+msgstr ""
+
+msgid "Telephone number"
+msgstr ""
+
+msgid "Telephone number (Optional)"
+msgstr ""
+
+msgid "Template"
+msgstr ""
+
+msgid "Template to append to all Service Desk issues"
+msgstr ""
+
+msgid "Template was successfully saved."
+msgstr ""
+
+msgid "Templates"
+msgstr ""
+
+msgid "Terminal"
+msgstr ""
+
+msgid "Terminal for environment"
+msgstr ""
+
+msgid "Terminal sync service is running"
+msgstr ""
+
+msgid "Terms of Service Agreement and Privacy Policy"
+msgstr ""
+
+msgid "Terms of Service and Privacy Policy"
+msgstr ""
+
+msgid "Terraform|%{number} Terraform report failed to generate"
+msgid_plural "Terraform|%{number} Terraform reports failed to generate"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "Terraform|%{number} Terraform report was generated in your pipelines"
+msgid_plural "Terraform|%{number} Terraform reports were generated in your pipelines"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "Terraform|A Terraform report failed to generate."
+msgstr ""
+
+msgid "Terraform|A Terraform report was generated in your pipelines."
+msgstr ""
+
+msgid "Terraform|Generating the report caused an error."
+msgstr ""
+
+msgid "Terraform|Reported Resource Changes: %{addNum} to add, %{changeNum} to change, %{deleteNum} to delete"
+msgstr ""
+
+msgid "Terraform|The Terraform report %{name} failed to generate."
+msgstr ""
+
+msgid "Terraform|The Terraform report %{name} was generated in your pipelines."
+msgstr ""
+
+msgid "Test"
+msgstr ""
+
+msgid "Test coverage parsing"
+msgstr ""
+
+msgid "Test coverage: %d hit"
+msgid_plural "Test coverage: %d hits"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "Test failed."
+msgstr ""
+
+msgid "Test settings and save changes"
+msgstr ""
+
+msgid "TestHooks|Ensure one of your projects has merge requests."
+msgstr ""
+
+msgid "TestHooks|Ensure the project has CI jobs."
+msgstr ""
+
+msgid "TestHooks|Ensure the project has CI pipelines."
+msgstr ""
+
+msgid "TestHooks|Ensure the project has deployments."
+msgstr ""
+
+msgid "TestHooks|Ensure the project has issues."
+msgstr ""
+
+msgid "TestHooks|Ensure the project has merge requests."
+msgstr ""
+
+msgid "TestHooks|Ensure the project has notes."
+msgstr ""
+
+msgid "TestHooks|Ensure the wiki is enabled and has pages."
+msgstr ""
+
+msgid "TestReports|%{count} errors"
+msgstr ""
+
+msgid "TestReports|%{count} failures"
+msgstr ""
+
+msgid "TestReports|%{count} jobs"
+msgstr ""
+
+msgid "TestReports|%{rate}%{sign} success rate"
+msgstr ""
+
+msgid "TestReports|Test suites"
+msgstr ""
+
+msgid "TestReports|Tests"
+msgstr ""
+
+msgid "TestReports|There are no test cases to display."
+msgstr ""
+
+msgid "TestReports|There are no test suites to show."
+msgstr ""
+
+msgid "TestReports|There are no tests to show."
+msgstr ""
+
+msgid "TestReports|There was an error fetching the test reports."
+msgstr ""
+
+msgid "Tests"
+msgstr ""
+
+msgid "Thank you for signing up for your free trial! You will get additional instructions in your inbox shortly."
+msgstr ""
+
+msgid "Thank you for your feedback!"
+msgstr ""
+
+msgid "Thank you for your report. A GitLab administrator will look into it shortly."
+msgstr ""
+
+msgid "Thanks for your purchase!"
+msgstr ""
+
+msgid "Thanks! Don't show me this again"
+msgstr ""
+
+msgid "That's it, well done!%{celebrate}"
+msgstr ""
+
+msgid "The \"%{group_path}\" group allows you to sign in with your Single Sign-On Account"
+msgstr ""
+
+msgid "The \"Require approval from CODEOWNERS\" setting was moved to %{banner_link_start}Protected Branches%{banner_link_end}"
+msgstr ""
+
+msgid "The %{link_start}true-up model%{link_end} allows having more users, and additional users will incur a retroactive charge on renewal."
+msgstr ""
+
+msgid "The %{true_up_link_start}true-up model%{link_end} has a retroactive charge for these users at the next renewal. If you want to update your license sooner to prevent this, %{support_link_start}please contact our Support team%{link_end}."
+msgstr ""
+
+msgid "The %{type} contains the following error:"
+msgid_plural "The %{type} contains the following errors:"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "The Advanced Global Search in GitLab is a powerful search service that saves you time. Instead of creating duplicate code and wasting time, you can now search for code within other teams that can help your own project."
+msgstr ""
+
+msgid "The CSV export will be created in the background. Once finished, it will be sent to <strong>%{email}</strong> in an attachment."
+msgstr ""
+
+msgid "The Git LFS objects will <strong>not</strong> be synced."
+msgstr ""
+
+msgid "The Issue Tracker is the place to add things that need to be improved or solved in a project"
+msgstr ""
+
+msgid "The Issue Tracker is the place to add things that need to be improved or solved in a project. You can register or sign in to create issues for this project."
+msgstr ""
+
+msgid "The Prometheus server responded with \"bad request\". Please check your queries are correct and are supported in your Prometheus version. %{documentationLink}"
+msgstr ""
+
+msgid "The URL defined on the primary node that secondary nodes should use to contact it."
+msgstr ""
+
+msgid "The URL to use for connecting to Elasticsearch. Use a comma-separated list to support clustering (e.g., \"http://localhost:9200, http://localhost:9201\")."
+msgstr ""
+
+msgid "The X509 Certificate to use when mutual TLS is required to communicate with the external authorization service. If left blank, the server certificate is still validated when accessing over HTTPS."
+msgstr ""
+
+msgid "The above settings apply to all projects with the selected compliance framework(s)."
+msgstr ""
+
+msgid "The amount of seconds after which a request to get a secondary node status will time out."
+msgstr ""
+
+msgid "The application will be used where the client secret can be kept confidential. Native mobile apps and Single Page Apps are considered non-confidential."
+msgstr ""
+
+msgid "The associated issue #%{issueId} has been closed as the error is now resolved."
+msgstr ""
+
+msgid "The branch for this project has no active pipeline configuration."
+msgstr ""
+
+msgid "The branch or tag does not exist"
+msgstr ""
+
+msgid "The character highlighter helps you keep the subject line to %{titleLength} characters and wrap the body at %{bodyLength} so they are readable in git."
+msgstr ""
+
+msgid "The coding stage shows the time from the first commit to creating the merge request. The data will automatically be added here once you create your first merge request."
+msgstr ""
+
+msgid "The collection of events added to the data gathered for that stage."
+msgstr ""
+
+msgid "The commit does not exist"
+msgstr ""
+
+msgid "The connection will time out after %{timeout}. For repositories that take longer, use a clone/push combination."
+msgstr ""
+
+msgid "The content of this page is not encoded in UTF-8. Edits can only be made via the Git repository."
+msgstr ""
+
+msgid "The contents of this group, its subgroups and projects will be permanently removed after %{deletion_adjourned_period} days on %{date}. After this point, your data cannot be recovered."
+msgstr ""
+
+msgid "The current issue"
+msgstr ""
+
+msgid "The data source is connected, but there is no data to display. %{documentationLink}"
+msgstr ""
+
+msgid "The default CI configuration path for new projects."
+msgstr ""
+
+msgid "The dependency list details information about the components used within your project."
+msgstr ""
+
+msgid "The deployment of this job to %{environmentLink} did not succeed."
+msgstr ""
+
+msgid "The designs you tried uploading did not change."
+msgstr ""
+
+msgid "The directory has been successfully created."
+msgstr ""
+
+msgid "The domain you entered is misformatted."
+msgstr ""
+
+msgid "The domain you entered is not allowed."
+msgstr ""
+
+msgid "The download link will expire in 24 hours."
+msgstr ""
+
+msgid "The entered user map is not a valid JSON user map."
+msgstr ""
+
+msgid "The errors we encountered were:"
+msgstr ""
+
+msgid "The file has been successfully created."
+msgstr ""
+
+msgid "The file has been successfully deleted."
+msgstr ""
+
+msgid "The file name should have a .yml extension"
+msgstr ""
+
+msgid "The following items will NOT be exported:"
+msgstr ""
+
+msgid "The following items will be exported:"
+msgstr ""
+
+msgid "The following personal access token: %{token_names} was revoked, because a new policy to expire personal access tokens were set."
+msgid_plural "The following personal access tokens: %{token_names} were revoked, because a new policy to expire personal access tokens were set."
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "The fork relationship has been removed."
+msgstr ""
+
+msgid "The global settings require you to enable Two-Factor Authentication for your account."
+msgstr ""
+
+msgid "The group and any internal projects can be viewed by any logged in user."
+msgstr ""
+
+msgid "The group and any public projects can be viewed without any authentication."
+msgstr ""
+
+msgid "The group and its projects can only be viewed by members."
+msgstr ""
+
+msgid "The group can be fully restored"
+msgstr ""
+
+msgid "The group export can be downloaded from:"
+msgstr ""
+
+msgid "The group has already been shared with this group"
+msgstr ""
+
+msgid "The group settings for %{group_links} require you to enable Two-Factor Authentication for your account. You can %{leave_group_links}."
+msgstr ""
+
+msgid "The group will be placed in 'pending removal' state"
+msgstr ""
+
+msgid "The import will time out after %{timeout}. For repositories that take longer, use a clone/push combination."
+msgstr ""
+
+msgid "The invitation could not be accepted."
+msgstr ""
+
+msgid "The invitation could not be declined."
+msgstr ""
+
+msgid "The invitation has already been accepted."
+msgstr ""
+
+msgid "The invitation was successfully resent."
+msgstr ""
+
+msgid "The issue stage shows the time it takes from creating an issue to assigning the issue to a milestone, or add the issue to a list on your Issue Board. Begin creating issues to see data for this stage."
+msgstr ""
+
+msgid "The license key is invalid. Make sure it is exactly as you received it from GitLab Inc."
+msgstr ""
+
+msgid "The license was removed. GitLab has fallen back on the previous license."
+msgstr ""
+
+msgid "The license was removed. GitLab now no longer has a valid license."
+msgstr ""
+
+msgid "The license was successfully uploaded and is now active. You can see the details below."
+msgstr ""
+
+msgid "The license was successfully uploaded and will be active from %{starts_at}. You can see the details below."
+msgstr ""
+
+msgid "The maximum file size allowed is %{size}."
+msgstr ""
+
+msgid "The maximum file size allowed is 200KB."
+msgstr ""
+
+msgid "The merge conflicts for this merge request cannot be resolved through GitLab. Please try to resolve them locally."
+msgstr ""
+
+msgid "The merge conflicts for this merge request have already been resolved."
+msgstr ""
+
+msgid "The merge conflicts for this merge request have already been resolved. Please return to the merge request."
+msgstr ""
+
+msgid "The merge request can now be merged."
+msgstr ""
+
+msgid "The name \"%{name}\" is already taken in this directory."
+msgstr ""
+
+msgid "The number of changes to be fetched from GitLab when cloning a repository. This can speed up Pipelines execution. Keep empty or set to 0 to disable shallow clone by default and make GitLab CI fetch all branches and tags each time."
+msgstr ""
+
+msgid "The number of times an upload record could not find its file"
+msgstr ""
+
+msgid "The one place for your designs"
+msgstr ""
+
+msgid "The passphrase required to decrypt the private key. This is optional and the value is encrypted at rest."
+msgstr ""
+
+msgid "The path to the CI configuration file. Defaults to <code>.gitlab-ci.yml</code>"
+msgstr ""
+
+msgid "The phase of the development lifecycle."
+msgstr ""
+
+msgid "The pipeline has been deleted"
+msgstr ""
+
+msgid "The pipelines schedule runs pipelines in the future, repeatedly, for specific branches or tags. Those scheduled pipelines will inherit limited project access based on their associated user."
+msgstr ""
+
+msgid "The planning stage shows the time from the previous step to pushing your first commit. This time will be added automatically once you push your first commit."
+msgstr ""
+
+msgid "The private key to use when a client certificate is provided. This value is encrypted at rest."
+msgstr ""
+
+msgid "The production stage shows the total time it takes between creating an issue and deploying the code to production. The data will be automatically added once you have completed the full idea to production cycle."
+msgstr ""
+
+msgid "The project can be accessed by any logged in user."
+msgstr ""
+
+msgid "The project can be accessed by any user who is logged in."
+msgstr ""
+
+msgid "The project can be accessed by anyone, regardless of authentication."
+msgstr ""
+
+msgid "The project can be accessed without any authentication."
+msgstr ""
+
+msgid "The project is accessible only by members of the project. Access must be granted explicitly to each user."
+msgstr ""
+
+msgid "The project is still being deleted. Please try again later."
+msgstr ""
+
+msgid "The project was successfully forked."
+msgstr ""
+
+msgid "The project was successfully imported."
+msgstr ""
+
+msgid "The pseudonymizer data collection is disabled. When enabled, GitLab will run a background job that will produce pseudonymized CSVs of the GitLab database that will be uploaded to your configured object storage directory."
+msgstr ""
+
+msgid "The remote mirror took to long to complete."
+msgstr ""
+
+msgid "The remote repository is being updated..."
+msgstr ""
+
+msgid "The repository can be commited to, and issues, comments and other entities can be created."
+msgstr ""
+
+msgid "The repository for this project does not exist."
+msgstr ""
+
+msgid "The repository for this project is empty"
+msgstr ""
+
+msgid "The repository is being updated..."
+msgstr ""
+
+msgid "The repository must be accessible over <code>http://</code>, <code>https://</code> or <code>git://</code>."
+msgstr ""
+
+msgid "The repository must be accessible over <code>http://</code>, <code>https://</code>, <code>ssh://</code> or <code>git://</code>."
+msgstr ""
+
+msgid "The review stage shows the time from creating the merge request to merging it. The data will automatically be added after you merge your first merge request."
+msgstr ""
+
+msgid "The roadmap shows the progress of your epics along a timeline"
+msgstr ""
+
+msgid "The schedule time must be in the future!"
+msgstr ""
+
+msgid "The snippet can be accessed without any authentication."
+msgstr ""
+
+msgid "The snippet is visible only to me."
+msgstr ""
+
+msgid "The snippet is visible only to project members."
+msgstr ""
+
+msgid "The snippet is visible to any logged in user."
+msgstr ""
+
+msgid "The specified tab is invalid, please select another"
+msgstr ""
+
+msgid "The staging stage shows the time between merging the MR and deploying code to the production environment. The data will be automatically added once you deploy to production for the first time."
+msgstr ""
+
+msgid "The status of the table below only applies to the default branch and is based on the %{linkStart}latest pipeline%{linkEnd}. Once you've enabled a scan for the default branch, any subsequent feature branch you create will include the scan."
+msgstr ""
+
+msgid "The testing stage shows the time GitLab CI takes to run every pipeline for the related merge request. The data will automatically be added after your first pipeline finishes running."
+msgstr ""
+
+msgid "The time taken by each data entry gathered by that stage."
+msgstr ""
+
+msgid "The total stage shows the time it takes between creating an issue and deploying the code to production. The data will be automatically added once you have completed the full idea to production cycle."
+msgstr ""
+
+msgid "The update action will time out after %{number_of_minutes} minutes. For big repositories, use a clone/push combination."
+msgstr ""
+
+msgid "The uploaded file is not a valid Google Takeout archive."
+msgstr ""
+
+msgid "The usage ping is disabled, and cannot be configured through this form."
+msgstr ""
+
+msgid "The user is being deleted."
+msgstr ""
+
+msgid "The user map has been saved. Continue by selecting the projects you want to import."
+msgstr ""
+
+msgid "The user map is a JSON document mapping the Google Code users that participated on your projects to the way their email addresses and usernames will be imported into GitLab. You can change this by changing the value on the right hand side of <code>:</code>. Be sure to preserve the surrounding double quotes, other punctuation and the email address or username on the left hand side."
+msgstr ""
+
+msgid "The user map is a mapping of the FogBugz users that participated on your projects to the way their email address and usernames will be imported into GitLab. You can change this by populating the table below."
+msgstr ""
+
+msgid "The user you are trying to deactivate has been active in the past %{minimum_inactive_days} days and cannot be deactivated"
+msgstr ""
+
+msgid "The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6."
+msgstr ""
+
+msgid "The vulnerability is no longer detected. Verify the vulnerability has been fixed or removed before changing its status."
+msgstr ""
+
+msgid "The vulnerability is no longer detected. Verify the vulnerability has been remediated before changing its status."
+msgstr ""
+
+msgid "There are no %{replicableTypeName} to show"
+msgstr ""
+
+msgid "There are no GPG keys associated with this account."
+msgstr ""
+
+msgid "There are no GPG keys with access to your account."
+msgstr ""
+
+msgid "There are no SSH keys associated with this account."
+msgstr ""
+
+msgid "There are no SSH keys with access to your account."
+msgstr ""
+
+msgid "There are no archived projects yet"
+msgstr ""
+
+msgid "There are no archived requirements"
+msgstr ""
+
+msgid "There are no changes"
+msgstr ""
+
+msgid "There are no charts configured for this page"
+msgstr ""
+
+msgid "There are no closed issues"
+msgstr ""
+
+msgid "There are no closed merge requests"
+msgstr ""
+
+msgid "There are no custom project templates set up for this GitLab instance. They are enabled from GitLab's Admin Area. Contact your GitLab instance administrator to setup custom project templates."
+msgstr ""
+
+msgid "There are no issues to show"
+msgstr ""
+
+msgid "There are no issues to show."
+msgstr ""
+
+msgid "There are no labels yet"
+msgstr ""
+
+msgid "There are no matching files"
+msgstr ""
+
+msgid "There are no open issues"
+msgstr ""
+
+msgid "There are no open merge requests"
+msgstr ""
+
+msgid "There are no open requirements"
+msgstr ""
+
+msgid "There are no packages yet"
+msgstr ""
+
+msgid "There are no projects shared with this group yet"
+msgstr ""
+
+msgid "There are no variables yet."
+msgstr ""
+
+msgid "There is a limit of %{ci_project_subscriptions_limit} subscriptions from or to a project."
+msgstr ""
+
+msgid "There is already a repository with that name on disk"
+msgstr ""
+
+msgid "There is no data available. Please change your selection."
+msgstr ""
+
+msgid "There was a problem communicating with your device."
+msgstr ""
+
+msgid "There was a problem fetching project branches."
+msgstr ""
+
+msgid "There was a problem fetching project tags."
+msgstr ""
+
+msgid "There was a problem fetching project users."
+msgstr ""
+
+msgid "There was a problem fetching users."
+msgstr ""
+
+msgid "There was a problem refreshing the data, please try again"
+msgstr ""
+
+msgid "There was a problem saving your custom stage, please try again"
+msgstr ""
+
+msgid "There was a problem sending the confirmation email"
+msgstr ""
+
+msgid "There was an error %{message} todo."
+msgstr ""
+
+msgid "There was an error adding a To Do."
+msgstr ""
+
+msgid "There was an error creating the dashboard, branch name is invalid."
+msgstr ""
+
+msgid "There was an error creating the dashboard, branch named: %{branch} already exists."
+msgstr ""
+
+msgid "There was an error creating the issue"
+msgstr ""
+
+msgid "There was an error deleting the To Do."
+msgstr ""
+
+msgid "There was an error fetching configuration for charts"
+msgstr ""
+
+msgid "There was an error fetching data for the selected stage"
+msgstr ""
+
+msgid "There was an error fetching data for the tasks by type chart"
+msgstr ""
+
+msgid "There was an error fetching label data for the selected group"
+msgstr ""
+
+msgid "There was an error fetching median data for stages"
+msgstr ""
+
+msgid "There was an error fetching the %{replicableType}"
+msgstr ""
+
+msgid "There was an error fetching the Geo Settings"
+msgstr ""
+
+msgid "There was an error fetching the Node's Groups"
+msgstr ""
+
+msgid "There was an error fetching the environments information."
+msgstr ""
+
+msgid "There was an error fetching the top labels for the selected group"
+msgstr ""
+
+msgid "There was an error fetching the variables."
+msgstr ""
+
+msgid "There was an error fetching value stream analytics stages."
+msgstr ""
+
+msgid "There was an error gathering the chart data"
+msgstr ""
+
+msgid "There was an error getting the epic participants."
+msgstr ""
+
+msgid "There was an error importing the Jira project."
+msgstr ""
+
+msgid "There was an error loading users activity calendar."
+msgstr ""
+
+msgid "There was an error parsing the data for this graph."
+msgstr ""
+
+msgid "There was an error removing the e-mail."
+msgstr ""
+
+msgid "There was an error removing your custom stage, please try again"
+msgstr ""
+
+msgid "There was an error resetting group pipeline minutes."
+msgstr ""
+
+msgid "There was an error resetting user pipeline minutes."
+msgstr ""
+
+msgid "There was an error saving this Geo Node."
+msgstr ""
+
+msgid "There was an error saving your changes."
+msgstr ""
+
+msgid "There was an error saving your notification settings."
+msgstr ""
+
+msgid "There was an error subscribing to this label."
+msgstr ""
+
+msgid "There was an error syncing project %{name}"
+msgstr ""
+
+msgid "There was an error syncing the %{replicableType}"
+msgstr ""
+
+msgid "There was an error trying to validate your query"
+msgstr ""
+
+msgid "There was an error updating the dashboard, branch name is invalid."
+msgstr ""
+
+msgid "There was an error updating the dashboard, branch named: %{branch} already exists."
+msgstr ""
+
+msgid "There was an error updating the stage order. Please try reloading the page."
+msgstr ""
+
+msgid "There was an error when reseting email token."
+msgstr ""
+
+msgid "There was an error when subscribing to this label."
+msgstr ""
+
+msgid "There was an error when unsubscribing from this label."
+msgstr ""
+
+msgid "There was an error while fetching value stream analytics data."
+msgstr ""
+
+msgid "There was an error while fetching value stream analytics duration data."
+msgstr ""
+
+msgid "There was an error while fetching value stream analytics duration median data."
+msgstr ""
+
+msgid "There was an error while fetching value stream analytics recent activity data."
+msgstr ""
+
+msgid "There was an error while fetching value stream analytics time summary data."
+msgstr ""
+
+msgid "There was an error with the reCAPTCHA. Please solve the reCAPTCHA again."
+msgstr ""
+
+msgid "These existing issues have a similar title. It might be better to comment there instead of creating another similar issue."
+msgstr ""
+
+msgid "These variables are configured in the parent group settings, and will be active in the current project in addition to the project variables."
+msgstr ""
+
+msgid "They can be managed using the %{link}."
+msgstr ""
+
+msgid "Third Party Advisory Link"
+msgstr ""
+
+msgid "Third party offers"
+msgstr ""
+
+msgid "This %{issuableDisplayName} is locked. Only project members can comment."
+msgstr ""
+
+msgid "This %{issuable} is locked. Only <strong>project members</strong> can comment."
+msgstr ""
+
+msgid "This %{noteableTypeText} is %{confidentialLinkStart}confidential%{linkEnd} and %{lockedLinkStart}locked%{linkEnd}."
+msgstr ""
+
+msgid "This %{noteableTypeText} is locked."
+msgstr ""
+
+msgid "This %{viewer} could not be displayed because %{reason}. You can %{options} instead."
+msgstr ""
+
+msgid "This GitLab instance does not provide any shared Runners yet. Instance administrators can register shared Runners in the admin area."
+msgstr ""
+
+msgid "This GitLab instance is licensed at the %{insufficient_license} tier. Geo is only available for users who have at least a Premium license."
+msgstr ""
+
+msgid "This Project is currently archived and read-only. Please unarchive the project first if you want to resume Pull mirroring"
+msgstr ""
+
+msgid "This URL is already used for another link; duplicate URLs are not allowed"
+msgstr ""
+
+msgid "This action can lead to data loss. To prevent accidental actions we ask you to confirm your intention."
+msgstr ""
+
+msgid "This also resolves all related threads"
+msgstr ""
+
+msgid "This also resolves the discussion"
+msgstr ""
+
+msgid "This application was created by %{link_to_owner}."
+msgstr ""
+
+msgid "This application will be able to:"
+msgstr ""
+
+msgid "This attachment has been truncated to avoid exceeding the maximum allowed attachment size of 15MB. %{written_count} of %{issues_count} issues have been included. Consider re-exporting with a narrower selection of issues."
+msgstr ""
+
+msgid "This block is self-referential"
+msgstr ""
+
+msgid "This board's scope is reduced"
+msgstr ""
+
+msgid "This branch has changed since you started editing. Would you like to create a new branch?"
+msgstr ""
+
+msgid "This chart could not be displayed"
+msgstr ""
+
+msgid "This comment has changed since you started editing, please review the %{startTag}updated comment%{endTag} to ensure information is not lost."
+msgstr ""
+
+msgid "This commit is part of merge request %{link_to_merge_request}. Comments created here will be created in the context of that merge request."
+msgstr ""
+
+msgid "This commit was signed with a <strong>verified</strong> signature and the committer email is verified to belong to the same user."
+msgstr ""
+
+msgid "This commit was signed with a different user's verified signature."
+msgstr ""
+
+msgid "This commit was signed with a verified signature, but the committer email is <strong>not verified</strong> to belong to the same user."
+msgstr ""
+
+msgid "This commit was signed with an <strong>unverified</strong> signature."
+msgstr ""
+
+msgid "This content could not be displayed because %{reason}. You can %{options} instead."
+msgstr ""
+
+msgid "This date is after the due date, so this epic won't appear in the roadmap."
+msgstr ""
+
+msgid "This date is before the start date, so this epic won't appear in the roadmap."
+msgstr ""
+
+msgid "This device has already been registered with us."
+msgstr ""
+
+msgid "This device has not been registered with us."
+msgstr ""
+
+msgid "This diff is collapsed."
+msgstr ""
+
+msgid "This diff was suppressed by a .gitattributes entry."
+msgstr ""
+
+msgid "This directory"
+msgstr ""
+
+msgid "This domain is not verified. You will need to verify ownership before access is enabled."
+msgstr ""
+
+msgid "This endpoint has been requested too many times. Try again later."
+msgstr ""
+
+msgid "This environment has no deployments yet."
+msgstr ""
+
+msgid "This environment is being deployed"
+msgstr ""
+
+msgid "This environment is being re-deployed"
+msgstr ""
+
+msgid "This epic already has the maximum number of child epics."
+msgstr ""
+
+msgid "This epic and its child elements will only be visible to team members with at minimum Reporter access."
+msgstr ""
+
+msgid "This epic does not exist or you don't have sufficient permission."
+msgstr ""
+
+msgid "This feature is currently in beta. We invite you to %{linkStart}give feedback%{linkEnd}."
+msgstr ""
+
+msgid "This feature requires local storage to be enabled"
+msgstr ""
+
+msgid "This field is required."
+msgstr ""
+
+msgid "This group"
+msgstr ""
+
+msgid "This group cannot be invited to a project inside a group with enforced SSO"
+msgstr ""
+
+msgid "This group does not provide any group Runners yet."
+msgstr ""
+
+msgid "This group has been scheduled for permanent removal on %{date}"
+msgstr ""
+
+msgid "This group, including all subgroups, projects and git repositories, will only be reachable from the specified IP address range. Multiple addresses are supported with comma delimiters.<br>Example: <code>192.168.0.0/24,192.168.1.0/24</code>. %{read_more_link}."
+msgstr ""
+
+msgid "This group, its subgroups and projects has been scheduled for removal on %{date}."
+msgstr ""
+
+msgid "This group, its subgroups and projects will be removed on %{date} since its parent group '%{parent_group_name}'' has been scheduled for removal."
+msgstr ""
+
+msgid "This is a \"Ghost User\", created to hold all issues authored by users that have since been deleted. This user cannot be removed."
+msgstr ""
+
+msgid "This is a Work in Progress"
+msgstr ""
+
+msgid "This is a confidential %{noteableTypeText}."
+msgstr ""
+
+msgid "This is a delayed job to run in %{remainingTime}"
+msgstr ""
+
+msgid "This is a list of devices that have logged into your account. Revoke any sessions that you do not recognize."
+msgstr ""
+
+msgid "This is a security log of important events involving your account."
+msgstr ""
+
+msgid "This is the author's first Merge Request to this project."
+msgstr ""
+
+msgid "This is the highest peak of users on your installation since the license started."
+msgstr ""
+
+msgid "This is the maximum number of users that have existed at the same time since the license started. This is the minimum number of seats you will need to buy when you renew your license."
+msgstr ""
+
+msgid "This is the number of currently active users on your installation, and this is the minimum number you need to purchase when you renew your license."
+msgstr ""
+
+msgid "This is your current session"
+msgstr ""
+
+msgid "This issue is confidential"
+msgstr ""
+
+msgid "This issue is currently blocked by the following issues: %{issues}."
+msgstr ""
+
+msgid "This issue is in a child epic of the filtered epic"
+msgstr ""
+
+msgid "This job depends on other jobs with expired/erased artifacts: %{invalid_dependencies}"
+msgstr ""
+
+msgid "This job depends on upstream jobs that need to succeed in order for this job to be triggered"
+msgstr ""
+
+msgid "This job does not have a trace."
+msgstr ""
+
+msgid "This job has been canceled"
+msgstr ""
+
+msgid "This job has been skipped"
+msgstr ""
+
+msgid "This job has not been triggered yet"
+msgstr ""
+
+msgid "This job has not started yet"
+msgstr ""
+
+msgid "This job is an out-of-date deployment to %{environmentLink} using cluster %{clusterNameOrLink} and namespace %{kubernetesNamespace}."
+msgstr ""
+
+msgid "This job is an out-of-date deployment to %{environmentLink} using cluster %{clusterNameOrLink} and namespace %{kubernetesNamespace}. View the %{deploymentLink}."
+msgstr ""
+
+msgid "This job is an out-of-date deployment to %{environmentLink} using cluster %{clusterNameOrLink}."
+msgstr ""
+
+msgid "This job is an out-of-date deployment to %{environmentLink} using cluster %{clusterNameOrLink}. View the %{deploymentLink}."
+msgstr ""
+
+msgid "This job is an out-of-date deployment to %{environmentLink}."
+msgstr ""
+
+msgid "This job is an out-of-date deployment to %{environmentLink}. View the %{deploymentLink}."
+msgstr ""
+
+msgid "This job is archived. Only the complete pipeline can be retried."
+msgstr ""
+
+msgid "This job is creating a deployment to %{environmentLink} using cluster %{clusterNameOrLink} and namespace %{kubernetesNamespace}."
+msgstr ""
+
+msgid "This job is creating a deployment to %{environmentLink} using cluster %{clusterNameOrLink} and namespace %{kubernetesNamespace}. This will overwrite the %{deploymentLink}."
+msgstr ""
+
+msgid "This job is creating a deployment to %{environmentLink} using cluster %{clusterNameOrLink}."
+msgstr ""
+
+msgid "This job is creating a deployment to %{environmentLink} using cluster %{clusterNameOrLink}. This will overwrite the %{deploymentLink}."
+msgstr ""
+
+msgid "This job is creating a deployment to %{environmentLink}."
+msgstr ""
+
+msgid "This job is creating a deployment to %{environmentLink}. This will overwrite the %{deploymentLink}."
+msgstr ""
+
+msgid "This job is deployed to %{environmentLink} using cluster %{clusterNameOrLink} and namespace %{kubernetesNamespace}."
+msgstr ""
+
+msgid "This job is deployed to %{environmentLink} using cluster %{clusterNameOrLink}."
+msgstr ""
+
+msgid "This job is deployed to %{environmentLink}."
+msgstr ""
+
+msgid "This job is in pending state and is waiting to be picked by a runner"
+msgstr ""
+
+msgid "This job is performing tasks that must complete before it can start"
+msgstr ""
+
+msgid "This job is preparing to start"
+msgstr ""
+
+msgid "This job is stuck because you don't have any active runners online or available with any of these tags assigned to them:"
+msgstr ""
+
+msgid "This job is stuck because you don't have any active runners that can run this job."
+msgstr ""
+
+msgid "This job is waiting for resource: "
+msgstr ""
+
+msgid "This job requires a manual action"
+msgstr ""
+
+msgid "This job requires manual intervention to start. Before starting this job, you can add variables below for last-minute configuration changes."
+msgstr ""
+
+msgid "This job will automatically run after its timer finishes. Often they are used for incremental roll-out deploys to production environments. When unscheduled it converts into a manual action."
+msgstr ""
+
+msgid "This license has already expired."
+msgstr ""
+
+msgid "This link points to external content"
+msgstr ""
+
+msgid "This may expose confidential information as the selected fork is in another namespace that can have other members."
+msgstr ""
+
+msgid "This means you can not push code until you create an empty repository or import existing one."
+msgstr ""
+
+msgid "This merge request does not have accessibility reports"
+msgstr ""
+
+msgid "This merge request is locked."
+msgstr ""
+
+msgid "This namespace has already been taken! Please choose another one."
+msgstr ""
+
+msgid "This only applies to repository indexing operations."
+msgstr ""
+
+msgid "This option is only available on GitLab.com"
+msgstr ""
+
+msgid "This page is unavailable because you are not allowed to read information across multiple projects."
+msgstr ""
+
+msgid "This page will be removed in a future release."
+msgstr ""
+
+msgid "This pipeline does not use the %{codeStart}needs%{codeEnd} keyword and can't be represented as a directed acyclic graph."
+msgstr ""
+
+msgid "This pipeline makes use of a predefined CI/CD configuration enabled by %{strongStart}Auto DevOps.%{strongEnd}"
+msgstr ""
+
+msgid "This pipeline makes use of a predefined CI/CD configuration enabled by <b>Auto DevOps.</b>"
+msgstr ""
+
+msgid "This pipeline triggered a child pipeline"
+msgstr ""
+
+msgid "This pipeline was triggered by a parent pipeline"
+msgstr ""
+
+msgid "This project"
+msgstr ""
+
+msgid "This project does not belong to a group and can therefore not make use of group Runners."
+msgstr ""
+
+msgid "This project does not have %{service_desk_link_start}Service Desk%{service_desk_link_end} enabled, so the user who created the issue will no longer receive email notifications about new activity."
+msgstr ""
+
+msgid "This project does not have a wiki homepage yet"
+msgstr ""
+
+msgid "This project does not have billing enabled. To create a cluster, <a href=%{linkToBilling} target=\"_blank\" rel=\"noopener noreferrer\">enable billing <i class=\"fa fa-external-link\" aria-hidden=\"true\"></i></a> and try again."
+msgstr ""
+
+msgid "This project has no active access tokens."
+msgstr ""
+
+msgid "This project is archived and cannot be commented on."
+msgstr ""
+
+msgid "This project path either does not exist or you do not have access."
+msgstr ""
+
+msgid "This project will be removed on %{date}"
+msgstr ""
+
+msgid "This project will be removed on %{date} since its parent group '%{parent_group_name}' has been scheduled for removal."
+msgstr ""
+
+msgid "This project will live in your group <strong>%{namespace}</strong>. A project is where you house your files (repository), plan your work (issues), publish your documentation (wiki), and so much more."
+msgstr ""
+
+msgid "This repository"
+msgstr ""
+
+msgid "This repository has never been checked."
+msgstr ""
+
+msgid "This repository is currently empty. A new Auto DevOps pipeline will be created after a new file has been pushed to a branch."
+msgstr ""
+
+msgid "This repository was last checked %{last_check_timestamp}. The check %{strong_start}failed.%{strong_end} See the 'repocheck.log' file for error messages."
+msgstr ""
+
+msgid "This repository was last checked %{last_check_timestamp}. The check passed."
+msgstr ""
+
+msgid "This runner will only run on pipelines triggered on protected branches"
+msgstr ""
+
+msgid "This setting can be overridden in each project."
+msgstr ""
+
+msgid "This setting will update the hostname that is used to generate private commit emails. %{learn_more}"
+msgstr ""
+
+msgid "This subscription is for"
+msgstr ""
+
+msgid "This timeout will take precedence when lower than project-defined timeout and accepts a human readable time input language like \"1 hour\". Values without specification represent seconds."
+msgstr ""
+
+msgid "This user cannot be unlocked manually from GitLab"
+msgstr ""
+
+msgid "This user has no active %{type}."
+msgstr ""
+
+msgid "This user has no identities"
+msgstr ""
+
+msgid "This user will be the author of all events in the activity feed that are the result of an update, like new branches being created or new commits being pushed to existing branches."
+msgstr ""
+
+msgid "This variable can not be masked."
+msgstr ""
+
+msgid "This variable does not match the expected pattern."
+msgstr ""
+
+msgid "This will help us personalize your onboarding experience."
+msgstr ""
+
+msgid "This will redirect you to an external sign in page."
+msgstr ""
+
+msgid "This will remove the fork relationship between this project and %{fork_source}."
+msgstr ""
+
+msgid "This will remove the fork relationship between this project and other projects in the fork network."
+msgstr ""
+
+msgid "Those emails automatically become issues (with the comments becoming the email conversation) listed here."
+msgstr ""
+
+msgid "Thread to reply to cannot be found"
+msgstr ""
+
+msgid "Threat Monitoring"
+msgstr ""
+
+msgid "ThreatMonitoring|Anomalous Requests"
+msgstr ""
+
+msgid "ThreatMonitoring|Application firewall not detected"
+msgstr ""
+
+msgid "ThreatMonitoring|Container Network Policies are not installed or have been disabled. To view this data, ensure your Network Policies are installed and enabled for your cluster."
+msgstr ""
+
+msgid "ThreatMonitoring|Container Network Policy"
+msgstr ""
+
+msgid "ThreatMonitoring|Container NetworkPolicies not detected"
+msgstr ""
+
+msgid "ThreatMonitoring|Dropped Packets"
+msgstr ""
+
+msgid "ThreatMonitoring|Environment"
+msgstr ""
+
+msgid "ThreatMonitoring|No environments detected"
+msgstr ""
+
+msgid "ThreatMonitoring|Operations Per Second"
+msgstr ""
+
+msgid "ThreatMonitoring|Overview"
+msgstr ""
+
+msgid "ThreatMonitoring|Packet Activity"
+msgstr ""
+
+msgid "ThreatMonitoring|Policies"
+msgstr ""
+
+msgid "ThreatMonitoring|Requests"
+msgstr ""
+
+msgid "ThreatMonitoring|Show last"
+msgstr ""
+
+msgid "ThreatMonitoring|Something went wrong, unable to fetch environments"
+msgstr ""
+
+msgid "ThreatMonitoring|Something went wrong, unable to fetch statistics"
+msgstr ""
+
+msgid "ThreatMonitoring|The firewall is not installed or has been disabled. To view this data, ensure the web application firewall is installed and enabled for your cluster."
+msgstr ""
+
+msgid "ThreatMonitoring|The graph below is an overview of traffic coming to your application as tracked by the Web Application Firewall (WAF). View the docs for instructions on how to access the WAF logs to see what type of malicious traffic is trying to access your app. The docs link is also accessible by clicking the \"?\" icon next to the title below."
+msgstr ""
+
+msgid "ThreatMonitoring|Threat Monitoring"
+msgstr ""
+
+msgid "ThreatMonitoring|Threat Monitoring help page link"
+msgstr ""
+
+msgid "ThreatMonitoring|Time"
+msgstr ""
+
+msgid "ThreatMonitoring|To view this data, ensure you have configured an environment for this project and that at least one threat monitoring feature is enabled."
+msgstr ""
+
+msgid "ThreatMonitoring|Total Packets"
+msgstr ""
+
+msgid "ThreatMonitoring|Total Requests"
+msgstr ""
+
+msgid "ThreatMonitoring|View documentation"
+msgstr ""
+
+msgid "ThreatMonitoring|Web Application Firewall"
+msgstr ""
+
+msgid "ThreatMonitoring|While it's rare to have no traffic coming to your application, it can happen. In any event, we ask that you double check your settings to make sure you've set up the WAF correctly."
+msgstr ""
+
+msgid "Thursday"
+msgstr ""
+
+msgid "Time"
+msgstr ""
+
+msgid "Time based: Yes"
+msgstr ""
+
+msgid "Time before an issue gets scheduled"
+msgstr ""
+
+msgid "Time before an issue starts implementation"
+msgstr ""
+
+msgid "Time before enforced"
+msgstr ""
+
+msgid "Time between merge request creation and merge/close"
+msgstr ""
+
+msgid "Time estimate"
+msgstr ""
+
+msgid "Time from first comment to last commit"
+msgstr ""
+
+msgid "Time from first commit until first comment"
+msgstr ""
+
+msgid "Time from last commit to merge"
+msgstr ""
+
+msgid "Time in seconds"
+msgstr ""
+
+msgid "Time in seconds GitLab will wait for a response from the external service. When the service does not respond in time, access will be denied."
+msgstr ""
+
+msgid "Time of import: %{importTime}"
+msgstr ""
+
+msgid "Time remaining"
+msgstr ""
+
+msgid "Time spent"
+msgstr ""
+
+msgid "Time to merge"
+msgstr ""
+
+msgid "Time to subtract exceeds the total time spent"
+msgstr ""
+
+msgid "Time tracking"
+msgstr ""
+
+msgid "Time until first merge request"
+msgstr ""
+
+msgid "TimeTrackingEstimated|Est"
+msgstr ""
+
+msgid "TimeTracking|%{startTag}Spent: %{endTag}%{timeSpentHumanReadable}"
+msgstr ""
+
+msgid "TimeTracking|Estimated:"
+msgstr ""
+
+msgid "TimeTracking|Over by %{timeRemainingHumanReadable}"
+msgstr ""
+
+msgid "TimeTracking|Spent"
+msgstr ""
+
+msgid "TimeTracking|Time remaining: %{timeRemainingHumanReadable}"
+msgstr ""
+
+msgid "Timeago|%s days ago"
+msgstr ""
+
+msgid "Timeago|%s days remaining"
+msgstr ""
+
+msgid "Timeago|%s hours ago"
+msgstr ""
+
+msgid "Timeago|%s hours remaining"
+msgstr ""
+
+msgid "Timeago|%s minutes ago"
+msgstr ""
+
+msgid "Timeago|%s minutes remaining"
+msgstr ""
+
+msgid "Timeago|%s months ago"
+msgstr ""
+
+msgid "Timeago|%s months remaining"
+msgstr ""
+
+msgid "Timeago|%s seconds remaining"
+msgstr ""
+
+msgid "Timeago|%s weeks ago"
+msgstr ""
+
+msgid "Timeago|%s weeks remaining"
+msgstr ""
+
+msgid "Timeago|%s years ago"
+msgstr ""
+
+msgid "Timeago|%s years remaining"
+msgstr ""
+
+msgid "Timeago|1 day ago"
+msgstr ""
+
+msgid "Timeago|1 day remaining"
+msgstr ""
+
+msgid "Timeago|1 hour ago"
+msgstr ""
+
+msgid "Timeago|1 hour remaining"
+msgstr ""
+
+msgid "Timeago|1 minute ago"
+msgstr ""
+
+msgid "Timeago|1 minute remaining"
+msgstr ""
+
+msgid "Timeago|1 month ago"
+msgstr ""
+
+msgid "Timeago|1 month remaining"
+msgstr ""
+
+msgid "Timeago|1 week ago"
+msgstr ""
+
+msgid "Timeago|1 week remaining"
+msgstr ""
+
+msgid "Timeago|1 year ago"
+msgstr ""
+
+msgid "Timeago|1 year remaining"
+msgstr ""
+
+msgid "Timeago|Past due"
+msgstr ""
+
+msgid "Timeago|in %s days"
+msgstr ""
+
+msgid "Timeago|in %s hours"
+msgstr ""
+
+msgid "Timeago|in %s minutes"
+msgstr ""
+
+msgid "Timeago|in %s months"
+msgstr ""
+
+msgid "Timeago|in %s seconds"
+msgstr ""
+
+msgid "Timeago|in %s weeks"
+msgstr ""
+
+msgid "Timeago|in %s years"
+msgstr ""
+
+msgid "Timeago|in 1 day"
+msgstr ""
+
+msgid "Timeago|in 1 hour"
+msgstr ""
+
+msgid "Timeago|in 1 minute"
+msgstr ""
+
+msgid "Timeago|in 1 month"
+msgstr ""
+
+msgid "Timeago|in 1 week"
+msgstr ""
+
+msgid "Timeago|in 1 year"
+msgstr ""
+
+msgid "Timeago|just now"
+msgstr ""
+
+msgid "Timeago|right now"
+msgstr ""
+
+msgid "Timeout"
+msgstr ""
+
+msgid "Timeout connecting to the Google API. Please try again."
+msgstr ""
+
+msgid "Time|hr"
+msgid_plural "Time|hrs"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "Time|min"
+msgid_plural "Time|mins"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "Time|s"
+msgstr ""
+
+msgid "Tip:"
+msgstr ""
+
+msgid "Title"
+msgstr ""
+
+msgid "Title:"
+msgstr ""
+
+msgid "Titles and Descriptions"
+msgstr ""
+
+msgid "To"
+msgstr ""
+
+msgid "To %{link_to_help} of your domain, add the above key to a TXT record within to your DNS configuration."
+msgstr ""
+
+msgid "To Do"
+msgstr ""
+
+msgid "To GitLab"
+msgstr ""
+
+msgid "To access this domain create a new DNS record"
+msgstr ""
+
+msgid "To add an SSH key you need to %{generate_link_start}generate one%{link_end} or use an %{existing_link_start}existing key%{link_end}."
+msgstr ""
+
+msgid "To add the entry manually, provide the following details to the application on your phone."
+msgstr ""
+
+msgid "To connect GitHub repositories, you can use a %{personal_access_token_link}. When you create your Personal Access Token, you will need to select the <code>repo</code> scope, so we can display a list of your public and private repositories which are available to connect."
+msgstr ""
+
+msgid "To connect GitHub repositories, you first need to authorize GitLab to access the list of your GitHub repositories."
+msgstr ""
+
+msgid "To connect GitHub repositories, you first need to authorize GitLab to access the list of your GitHub repositories:"
+msgstr ""
+
+msgid "To connect an SVN repository, check out %{svn_link}."
+msgstr ""
+
+msgid "To define internal users, first enable new users set to external"
+msgstr ""
+
+msgid "To enable it and see User Cohorts, visit %{application_settings_link_start}application settings%{application_settings_link_end}."
+msgstr ""
+
+msgid "To further protect your account, consider configuring a %{mfa_link_start}two-factor authentication%{mfa_link_end} method."
+msgstr ""
+
+msgid "To further protect your account, consider configuring a two-factor authentication method: %{mfa_link}."
+msgstr ""
+
+msgid "To get started you enter your FogBugz URL and login information below. In the next steps, you'll be able to map users and select the projects you want to import."
+msgstr ""
+
+msgid "To get started, link this page to your Jaeger server, or find out how to %{link_start_tag}install Jaeger%{link_end_tag}"
+msgstr ""
+
+msgid "To get started, please enter your Gitea Host URL and a %{link_to_personal_token}."
+msgstr ""
+
+msgid "To help improve GitLab and its user experience, GitLab will periodically collect usage information."
+msgstr ""
+
+msgid "To help improve GitLab, we would like to periodically collect usage information. This can be changed at any time in %{settings_link_start}Settings%{link_end}. %{info_link_start}More Information%{link_end}"
+msgstr ""
+
+msgid "To import an SVN repository, check out %{svn_link}."
+msgstr ""
+
+msgid "To keep this project going, create a new issue"
+msgstr ""
+
+msgid "To keep this project going, create a new merge request"
+msgstr ""
+
+msgid "To link Sentry to GitLab, enter your Sentry URL and Auth Token."
+msgstr ""
+
+msgid "To move or copy an entire GitLab project from another GitLab installation to this one, navigate to the original project's settings page, generate an export file, and upload it here."
+msgstr ""
+
+msgid "To only use CI/CD features for an external repository, choose <strong>CI/CD for external repo</strong>."
+msgstr ""
+
+msgid "To open Jaeger and easily view tracing from GitLab, link the %{link} page to your server"
+msgstr ""
+
+msgid "To preserve performance only <strong>%{display_size} of %{real_size}</strong> files are displayed."
+msgstr ""
+
+msgid "To protect this issue's confidentiality, %{forkLink} and set the fork's visibility to private."
+msgstr ""
+
+msgid "To protect this issue's confidentiality, a private fork of this project was selected."
+msgstr ""
+
+msgid "To receive alerts from manually configured Prometheus services, add the following URL and Authorization key to your Prometheus webhook config file. Learn more about %{linkStart}configuring Prometheus%{linkEnd} to send alerts to GitLab."
+msgstr ""
+
+msgid "To see all the user's personal access tokens you must impersonate them first."
+msgstr ""
+
+msgid "To see this project's operational details, %{linkStart}upgrade its group plan to Silver%{linkEnd}. You can also remove the project from the dashboard."
+msgstr ""
+
+msgid "To see this project's operational details, contact an owner of group %{groupName} to upgrade the plan. You can also remove the project from the dashboard."
+msgstr ""
+
+msgid "To set up SAML authentication for your group through an identity provider like Azure, Okta, Onelogin, Ping Identity, or your custom SAML 2.0 provider:"
+msgstr ""
+
+msgid "To set up this service:"
+msgstr ""
+
+msgid "To simplify the billing process, GitLab will collect user counts in order to prorate charges for user growth throughout the year using a quarterly reconciliation process."
+msgstr ""
+
+msgid "To specify the notification level per project of a group you belong to, you need to visit project page and change notification level there."
+msgstr ""
+
+msgid "To start serving your jobs you can add Runners to your group"
+msgstr ""
+
+msgid "To start serving your jobs you can either add specific Runners to your project or use shared Runners"
+msgstr ""
+
+msgid "To this GitLab instance"
+msgstr ""
+
+msgid "To view the roadmap, add a start or due date to one of your epics in this group or its subgroups. In the months view, only epics in the past month, current month, and next 5 months are shown."
+msgstr ""
+
+msgid "To widen your search, change or remove filters above"
+msgstr ""
+
+msgid "To widen your search, change or remove filters."
+msgstr ""
+
+msgid "To-Do List"
+msgstr ""
+
+msgid "To-do item successfully marked as done."
+msgstr ""
+
+msgid "Today"
+msgstr ""
+
+msgid "Toggle Markdown preview"
+msgstr ""
+
+msgid "Toggle Sidebar"
+msgstr ""
+
+msgid "Toggle all threads"
+msgstr ""
+
+msgid "Toggle backtrace"
+msgstr ""
+
+msgid "Toggle collapse"
+msgstr ""
+
+msgid "Toggle comments for this file"
+msgstr ""
+
+msgid "Toggle commit description"
+msgstr ""
+
+msgid "Toggle commit list"
+msgstr ""
+
+msgid "Toggle dropdown"
+msgstr ""
+
+msgid "Toggle emoji award"
+msgstr ""
+
+msgid "Toggle navigation"
+msgstr ""
+
+msgid "Toggle project"
+msgstr ""
+
+msgid "Toggle sidebar"
+msgstr ""
+
+msgid "Toggle the Performance Bar"
+msgstr ""
+
+msgid "Toggle this dialog"
+msgstr ""
+
+msgid "Toggle thread"
+msgstr ""
+
+msgid "ToggleButton|Toggle Status: OFF"
+msgstr ""
+
+msgid "ToggleButton|Toggle Status: ON"
+msgstr ""
+
+msgid "Toggled :%{name}: emoji award."
+msgstr ""
+
+msgid "Toggles :%{name}: emoji award."
+msgstr ""
+
+msgid "Tomorrow"
+msgstr ""
+
+msgid "Too many changes to show."
+msgstr ""
+
+msgid "Too many namespaces enabled. You will need to manage them via the console or the API."
+msgstr ""
+
+msgid "Too many projects enabled. You will need to manage them via the console or the API."
+msgstr ""
+
+msgid "Topics (optional)"
+msgstr ""
+
+msgid "Total"
+msgstr ""
+
+msgid "Total Contributions"
+msgstr ""
+
+msgid "Total artifacts size: %{total_size}"
+msgstr ""
+
+msgid "Total cores (CPUs)"
+msgstr ""
+
+msgid "Total issues"
+msgstr ""
+
+msgid "Total memory (GB)"
+msgstr ""
+
+msgid "Total test time for all commits/merges"
+msgstr ""
+
+msgid "Total weight"
+msgstr ""
+
+msgid "Total: %{total}"
+msgstr ""
+
+msgid "Trace"
+msgstr ""
+
+msgid "Tracing"
+msgstr ""
+
+msgid "Track groups of issues that share a theme, across projects and milestones"
+msgstr ""
+
+msgid "Track time with quick actions"
+msgstr ""
+
+msgid "Track your GitLab projects with GitLab for Slack."
+msgstr ""
+
+msgid "Track your project with Audit Events."
+msgstr ""
+
+msgid "Transfer ownership"
+msgstr ""
+
+msgid "Transfer project"
+msgstr ""
+
+msgid "TransferGroup|Cannot transfer group to one of its subgroup."
+msgstr ""
+
+msgid "TransferGroup|Cannot update the path because there are projects under this group that contain Docker images in their Container Registry. Please remove the images from your projects first and try again."
+msgstr ""
+
+msgid "TransferGroup|Database is not supported."
+msgstr ""
+
+msgid "TransferGroup|Group contains projects with NPM packages."
+msgstr ""
+
+msgid "TransferGroup|Group is already a root group."
+msgstr ""
+
+msgid "TransferGroup|Group is already associated to the parent group."
+msgstr ""
+
+msgid "TransferGroup|The parent group already has a subgroup with the same path."
+msgstr ""
+
+msgid "TransferGroup|Transfer failed: %{error_message}"
+msgstr ""
+
+msgid "TransferGroup|You don't have enough permissions."
+msgstr ""
+
+msgid "TransferProject|Cannot move project"
+msgstr ""
+
+msgid "TransferProject|Please select a new namespace for your project."
+msgstr ""
+
+msgid "TransferProject|Project cannot be transferred, because tags are present in its container registry"
+msgstr ""
+
+msgid "TransferProject|Project with same name or path in target namespace already exists"
+msgstr ""
+
+msgid "TransferProject|Root namespace can't be updated if project has NPM packages"
+msgstr ""
+
+msgid "TransferProject|Transfer failed, please contact an admin."
+msgstr ""
+
+msgid "Tree view"
+msgstr ""
+
+msgid "Trending"
+msgstr ""
+
+msgid "Trials|Go back to GitLab"
+msgstr ""
+
+msgid "Trials|Skip Trial (Continue with Free Account)"
+msgstr ""
+
+msgid "Trials|You can always resume this process by selecting your avatar and choosing 'Start a Gold trial'"
+msgstr ""
+
+msgid "Trials|You won't get a free trial right now but you can always resume this process by clicking on your avatar and choosing 'Start a free trial'"
+msgstr ""
+
+msgid "Trigger"
+msgstr ""
+
+msgid "Trigger pipelines for mirror updates"
+msgstr ""
+
+msgid "Trigger pipelines when branches or tags are updated from the upstream repository. Depending on the activity of the upstream repository, this may greatly increase the load on your CI runners. Only enable this if you know they can handle the load."
+msgstr ""
+
+msgid "Trigger removed."
+msgstr ""
+
+msgid "Trigger this manual action"
+msgstr ""
+
+msgid "Trigger token:"
+msgstr ""
+
+msgid "Trigger variables:"
+msgstr ""
+
+msgid "Trigger was created successfully."
+msgstr ""
+
+msgid "Trigger was successfully updated."
+msgstr ""
+
+msgid "Triggerer"
+msgstr ""
+
+msgid "Triggers can force a specific branch or tag to get rebuilt with an API call. These tokens will impersonate their associated user including their access to projects and their project permissions."
+msgstr ""
+
+msgid "Troubleshoot and monitor your application with tracing"
+msgstr ""
+
+msgid "Try again"
+msgstr ""
+
+msgid "Try again?"
+msgstr ""
+
+msgid "Try all GitLab has to offer for 30 days."
+msgstr ""
+
+msgid "Try changing or removing filters."
+msgstr ""
+
+msgid "Try to fork again"
+msgstr ""
+
+msgid "Try to keep the first line under 52 characters and the others under 72."
+msgstr ""
+
+msgid "Try using a different search term to find the file you are looking for."
+msgstr ""
+
+msgid "Trying to communicate with your device. Plug it in (if you haven't already) and press the button on the device now."
+msgstr ""
+
+msgid "Tuesday"
+msgstr ""
+
+msgid "Turn Off"
+msgstr ""
+
+msgid "Turn On"
+msgstr ""
+
+msgid "Turn on Service Desk"
+msgstr ""
+
+msgid "Twitter"
+msgstr ""
+
+msgid "Two-Factor Authentication"
+msgstr ""
+
+msgid "Two-Factor Authentication code"
+msgstr ""
+
+msgid "Two-factor Authentication"
+msgstr ""
+
+msgid "Two-factor Authentication Recovery codes"
+msgstr ""
+
+msgid "Two-factor Authentication has been disabled for this user"
+msgstr ""
+
+msgid "Two-factor authentication"
+msgstr ""
+
+msgid "Type"
+msgstr ""
+
+msgid "Type/State"
+msgstr ""
+
+msgid "U2F Devices (%{length})"
+msgstr ""
+
+msgid "U2F only works with HTTPS-enabled websites. Contact your administrator for more details."
+msgstr ""
+
+msgid "UI Development Kit"
+msgstr ""
+
+msgid "URL"
+msgstr ""
+
+msgid "URL is required"
+msgstr ""
+
+msgid "URL must start with %{codeStart}http://%{codeEnd}, %{codeStart}https://%{codeEnd}, or %{codeStart}ftp://%{codeEnd}"
+msgstr ""
+
+msgid "URL of the external Spam Check endpoint"
+msgstr ""
+
+msgid "URL of the external storage that will serve the repository static objects (e.g. archives, blobs, ...)."
+msgstr ""
+
+msgid "URL or request ID"
+msgstr ""
+
+msgid "UTC"
+msgstr ""
+
+msgid "Unable to apply suggestions to a deleted line."
+msgstr ""
+
+msgid "Unable to build Slack link."
+msgstr ""
+
+msgid "Unable to collect CPU info"
+msgstr ""
+
+msgid "Unable to collect memory info"
+msgstr ""
+
+msgid "Unable to connect to Elasticsearch"
+msgstr ""
+
+msgid "Unable to connect to Prometheus server"
+msgstr ""
+
+msgid "Unable to connect to server: %{error}"
+msgstr ""
+
+msgid "Unable to connect to the Jira instance. Please check your Jira integration configuration."
+msgstr ""
+
+msgid "Unable to convert Kubernetes logs encoding to UTF-8"
+msgstr ""
+
+msgid "Unable to fetch unscanned projects"
+msgstr ""
+
+msgid "Unable to fetch vulnerable projects"
+msgstr ""
+
+msgid "Unable to find Jira project to import data from."
+msgstr ""
+
+msgid "Unable to generate new instance ID"
+msgstr ""
+
+msgid "Unable to load file contents. Try again later."
+msgstr ""
+
+msgid "Unable to load the diff"
+msgstr ""
+
+msgid "Unable to load the diff. %{button_try_again}"
+msgstr ""
+
+msgid "Unable to load the merge request widget. Try reloading the page."
+msgstr ""
+
+msgid "Unable to resolve"
+msgstr ""
+
+msgid "Unable to save iteration. Please try again"
+msgstr ""
+
+msgid "Unable to save your changes. Please try again."
+msgstr ""
+
+msgid "Unable to schedule a pipeline to run immediately"
+msgstr ""
+
+msgid "Unable to sign you in to the group with SAML due to \"%{reason}\""
+msgstr ""
+
+msgid "Unable to suggest a path. Please refresh and try again."
+msgstr ""
+
+msgid "Unable to update label prioritization at this time"
+msgstr ""
+
+msgid "Unable to update this epic at this time."
+msgstr ""
+
+msgid "Unable to update this issue at this time."
+msgstr ""
+
+msgid "Unarchive project"
+msgstr ""
+
+msgid "Unarchiving the project will restore people's ability to make changes to it. The repository can be committed to, and issues, comments, and other entities can be created. %{strong_start}Once active, this project shows up in the search and on the dashboard.%{strong_end}"
+msgstr ""
+
+msgid "Unassign from commenting user"
+msgstr ""
+
+msgid "Unblock"
+msgstr ""
+
+msgid "Undo"
+msgstr ""
+
+msgid "Undo Ignore"
+msgstr ""
+
+msgid "Undo ignore"
+msgstr ""
+
+msgid "Unfortunately, your email message to GitLab could not be processed."
+msgstr ""
+
+msgid "Uninstall"
+msgstr ""
+
+msgid "Uninstalling"
+msgstr ""
+
+msgid "Units|ms"
+msgstr ""
+
+msgid "Units|s"
+msgstr ""
+
+msgid "Unknown"
+msgstr ""
+
+msgid "Unknown Error"
+msgstr ""
+
+msgid "Unknown cache key"
+msgstr ""
+
+msgid "Unknown encryption strategy: %{encrypted_strategy}!"
+msgstr ""
+
+msgid "Unknown format"
+msgstr ""
+
+msgid "Unknown response text"
+msgstr ""
+
+msgid "Unlimited"
+msgstr ""
+
+msgid "Unlock"
+msgstr ""
+
+msgid "Unlock the discussion"
+msgstr ""
+
+msgid "Unlock this %{issuableDisplayName}? <strong>Everyone</strong> will be able to comment."
+msgstr ""
+
+msgid "Unlocked"
+msgstr ""
+
+msgid "Unlocked the discussion."
+msgstr ""
+
+msgid "Unlocks the discussion."
+msgstr ""
+
+msgid "Unmarked this %{noun} as Work In Progress."
+msgstr ""
+
+msgid "Unmarks this %{noun} as Work In Progress."
+msgstr ""
+
+msgid "Unreachable"
+msgstr ""
+
+msgid "Unrecognized cluster type"
+msgstr ""
+
+msgid "Unresolve"
+msgstr ""
+
+msgid "Unresolve thread"
+msgstr ""
+
+msgid "Unresolved"
+msgstr ""
+
+msgid "UnscannedProjects|15 or more days"
+msgstr ""
+
+msgid "UnscannedProjects|30 or more days"
+msgstr ""
+
+msgid "UnscannedProjects|5 or more days"
+msgstr ""
+
+msgid "UnscannedProjects|60 or more days"
+msgstr ""
+
+msgid "UnscannedProjects|Default branch scanning by project"
+msgstr ""
+
+msgid "UnscannedProjects|Out of date"
+msgstr ""
+
+msgid "UnscannedProjects|Project scanning"
+msgstr ""
+
+msgid "UnscannedProjects|Untested"
+msgstr ""
+
+msgid "UnscannedProjects|Your projects are up do date! Nice job!"
+msgstr ""
+
+msgid "Unschedule job"
+msgstr ""
+
+msgid "Unstar"
+msgstr ""
+
+msgid "Unsubscribe"
+msgstr ""
+
+msgid "Unsubscribe at group level"
+msgstr ""
+
+msgid "Unsubscribe at project level"
+msgstr ""
+
+msgid "Unsubscribe from %{type}"
+msgstr ""
+
+msgid "Unsubscribed from this %{quick_action_target}."
+msgstr ""
+
+msgid "Unsubscribes from this %{quick_action_target}."
+msgstr ""
+
+msgid "Unsupported todo type passed. Supported todo types are: %{todo_types}"
+msgstr ""
+
+msgid "Until"
+msgstr ""
+
+msgid "Until that time, the project can be restored."
+msgstr ""
+
+msgid "Unverified"
+msgstr ""
+
+msgid "Up to date"
+msgstr ""
+
+msgid "Upcoming"
+msgstr ""
+
+msgid "Upcoming Release"
+msgstr ""
+
+msgid "Update"
+msgstr ""
+
+msgid "Update all"
+msgstr ""
+
+msgid "Update approval rule"
+msgstr ""
+
+msgid "Update approvers"
+msgstr ""
+
+msgid "Update failed"
+msgstr ""
+
+msgid "Update failed. Please try again."
+msgstr ""
+
+msgid "Update it"
+msgstr ""
+
+msgid "Update iteration"
+msgstr ""
+
+msgid "Update now"
+msgstr ""
+
+msgid "Update variable"
+msgstr ""
+
+msgid "Update your bookmarked URLs as filtered/sorted branches URL has been changed."
+msgstr ""
+
+msgid "Update your group name, description, avatar, and visibility."
+msgstr ""
+
+msgid "Update your project name, topics, description and avatar."
+msgstr ""
+
+msgid "UpdateProject|Cannot rename project because it contains container registry tags!"
+msgstr ""
+
+msgid "UpdateProject|Could not set the default branch"
+msgstr ""
+
+msgid "UpdateProject|New visibility level not allowed!"
+msgstr ""
+
+msgid "UpdateProject|Project could not be updated!"
+msgstr ""
+
+msgid "UpdateRepositoryStorage|Error moving repository storage for %{project_full_path} - %{message}"
+msgstr ""
+
+msgid "UpdateRepositoryStorage|Failed to verify %{type} repository checksum from %{old} to %{new}"
+msgstr ""
+
+msgid "UpdateRepositoryStorage|Timeout waiting for %{type} repository pushes"
+msgstr ""
+
+msgid "Updated"
+msgstr ""
+
+msgid "Updated %{updated_at} by %{updated_by}"
+msgstr ""
+
+msgid "Updated at"
+msgstr ""
+
+msgid "Updated to %{linkStart}chart v%{linkEnd}"
+msgstr ""
+
+msgid "Updating"
+msgstr ""
+
+msgid "Upgrade plan to unlock Canary Deployments feature"
+msgstr ""
+
+msgid "Upgrade your plan to activate Advanced Global Search."
+msgstr ""
+
+msgid "Upgrade your plan to activate Audit Events."
+msgstr ""
+
+msgid "Upgrade your plan to activate Group Webhooks."
+msgstr ""
+
+msgid "Upgrade your plan to improve Issue boards."
+msgstr ""
+
+msgid "Upgrade your plan to improve Merge Requests."
+msgstr ""
+
+msgid "Upload <code>GoogleCodeProjectHosting.json</code> here:"
+msgstr ""
+
+msgid "Upload CSV file"
+msgstr ""
+
+msgid "Upload License"
+msgstr ""
+
+msgid "Upload New File"
+msgstr ""
+
+msgid "Upload New License"
+msgstr ""
+
+msgid "Upload a certificate for your domain with all intermediates"
+msgstr ""
+
+msgid "Upload a private key for your certificate"
+msgstr ""
+
+msgid "Upload file"
+msgstr ""
+
+msgid "Upload object map"
+msgstr ""
+
+msgid "UploadLink|click to upload"
+msgstr ""
+
+msgid "Uploaded on"
+msgstr ""
+
+msgid "Uploaded:"
+msgstr ""
+
+msgid "Uploading changes to terminal"
+msgstr ""
+
+msgid "Uploads"
+msgstr ""
+
+msgid "Upon performing this action, the contents of this group, its subgroup and projects will be permanently removed after %{deletion_adjourned_period} days on <strong>%{date}</strong>. Until that time:"
+msgstr ""
+
+msgid "Upstream"
+msgstr ""
+
+msgid "Uptime"
+msgstr ""
+
+msgid "Upvotes"
+msgstr ""
+
+msgid "Usage"
+msgstr ""
+
+msgid "Usage ping is not enabled"
+msgstr ""
+
+msgid "Usage statistics"
+msgstr ""
+
+msgid "UsageQuota|%{help_link_start}Shared runners%{help_link_end} are disabled, so there are no limits set on pipeline usage"
+msgstr ""
+
+msgid "UsageQuota|Artifacts"
+msgstr ""
+
+msgid "UsageQuota|Build Artifacts"
+msgstr ""
+
+msgid "UsageQuota|Buy additional minutes"
+msgstr ""
+
+msgid "UsageQuota|Current period usage"
+msgstr ""
+
+msgid "UsageQuota|LFS Objects"
+msgstr ""
+
+msgid "UsageQuota|LFS Storage"
+msgstr ""
+
+msgid "UsageQuota|Packages"
+msgstr ""
+
+msgid "UsageQuota|Pipelines"
+msgstr ""
+
+msgid "UsageQuota|Purchase more storage"
+msgstr ""
+
+msgid "UsageQuota|Repositories"
+msgstr ""
+
+msgid "UsageQuota|Repository"
+msgstr ""
+
+msgid "UsageQuota|Snippets"
+msgstr ""
+
+msgid "UsageQuota|Storage"
+msgstr ""
+
+msgid "UsageQuota|This namespace has no projects which use shared runners"
+msgstr ""
+
+msgid "UsageQuota|Unlimited"
+msgstr ""
+
+msgid "UsageQuota|Usage"
+msgstr ""
+
+msgid "UsageQuota|Usage Quotas"
+msgstr ""
+
+msgid "UsageQuota|Usage of group resources across the projects in the %{strong_start}%{group_name}%{strong_end} group"
+msgstr ""
+
+msgid "UsageQuota|Usage of resources across your projects"
+msgstr ""
+
+msgid "UsageQuota|Usage quotas help link"
+msgstr ""
+
+msgid "UsageQuota|Usage since"
+msgstr ""
+
+msgid "UsageQuota|Wiki"
+msgstr ""
+
+msgid "UsageQuota|Wikis"
+msgstr ""
+
+msgid "UsageQuota|You used: %{usage} %{limit}"
+msgstr ""
+
+msgid "UsageQuota|out of %{formattedLimit} of your namespace storage"
+msgstr ""
+
+msgid "Use %{code_start}::%{code_end} to create a %{link_start}scoped label set%{link_end} (eg. %{code_start}priority::1%{code_end})"
+msgstr ""
+
+msgid "Use <code>%{native_redirect_uri}</code> for local tests"
+msgstr ""
+
+msgid "Use Service Desk to connect with your users (e.g. to offer customer support) through email right inside GitLab"
+msgstr ""
+
+msgid "Use a hardware device to add the second factor of authentication."
+msgstr ""
+
+msgid "Use an one time password authenticator on your mobile device or computer to enable two-factor authentication (2FA)."
+msgstr ""
+
+msgid "Use custom color #FF0000"
+msgstr ""
+
+msgid "Use group milestones to manage issues from multiple projects in the same milestone."
+msgstr ""
+
+msgid "Use hashed storage"
+msgstr ""
+
+msgid "Use hashed storage paths for newly created and renamed repositories. Enable immutable, hash-based paths and repository names to store repositories on disk. This prevents repositories from having to be moved or renamed when the Repository URL changes and may improve disk I/O performance. (Always enabled since 13.0)"
+msgstr ""
+
+msgid "Use one line per URI"
+msgstr ""
+
+msgid "Use template"
+msgstr ""
+
+msgid "Use the following registration token during setup:"
+msgstr ""
+
+msgid "Use your global notification setting"
+msgstr ""
+
+msgid "Use your smart card to authenticate with the LDAP server."
+msgstr ""
+
+msgid "Used by members to sign in to your group in GitLab"
+msgstr ""
+
+msgid "Used programming language"
+msgstr ""
+
+msgid "Used to help configure your identity provider"
+msgstr ""
+
+msgid "User"
+msgstr ""
+
+msgid "User %{current_user_username} has started impersonating %{username}"
+msgstr ""
+
+msgid "User %{username} was successfully removed."
+msgstr ""
+
+msgid "User Cohorts are only shown when the %{usage_ping_link_start}usage ping%{usage_ping_link_end} is enabled."
+msgstr ""
+
+msgid "User IDs"
+msgstr ""
+
+msgid "User Lists can only be created and modified with %{linkStart}the API%{linkEnd}"
+msgstr ""
+
+msgid "User OAuth applications"
+msgstr ""
+
+msgid "User Settings"
+msgstr ""
+
+msgid "User and IP Rate Limits"
+msgstr ""
+
+msgid "User identity was successfully created."
+msgstr ""
+
+msgid "User identity was successfully removed."
+msgstr ""
+
+msgid "User identity was successfully updated."
+msgstr ""
+
+msgid "User is not allowed to resolve thread"
+msgstr ""
+
+msgid "User key was successfully removed."
+msgstr ""
+
+msgid "User list %{name} will be removed. Are you sure?"
+msgstr ""
+
+msgid "User map"
+msgstr ""
+
+msgid "User pipeline minutes were successfully reset."
+msgstr ""
+
+msgid "User restrictions"
+msgstr ""
+
+msgid "User was successfully created."
+msgstr ""
+
+msgid "User was successfully removed from group and any subresources."
+msgstr ""
+
+msgid "User was successfully removed from project."
+msgstr ""
+
+msgid "User was successfully updated."
+msgstr ""
+
+msgid "UserList|Delete %{name}?"
+msgstr ""
+
+msgid "UserList|created %{timeago}"
+msgstr ""
+
+msgid "UserProfile|Activity"
+msgstr ""
+
+msgid "UserProfile|Already reported for abuse"
+msgstr ""
+
+msgid "UserProfile|Blocked user"
+msgstr ""
+
+msgid "UserProfile|Contributed projects"
+msgstr ""
+
+msgid "UserProfile|Edit profile"
+msgstr ""
+
+msgid "UserProfile|Explore public groups to find projects to contribute to."
+msgstr ""
+
+msgid "UserProfile|Groups"
+msgstr ""
+
+msgid "UserProfile|Groups are the best way to manage projects and members."
+msgstr ""
+
+msgid "UserProfile|Join or create a group to start contributing by commenting on issues or submitting merge requests!"
+msgstr ""
+
+msgid "UserProfile|Most Recent Activity"
+msgstr ""
+
+msgid "UserProfile|No snippets found."
+msgstr ""
+
+msgid "UserProfile|Overview"
+msgstr ""
+
+msgid "UserProfile|Personal projects"
+msgstr ""
+
+msgid "UserProfile|Report abuse"
+msgstr ""
+
+msgid "UserProfile|Snippets"
+msgstr ""
+
+msgid "UserProfile|Snippets in GitLab can either be private, internal, or public."
+msgstr ""
+
+msgid "UserProfile|Star projects to track their progress and show your appreciation."
+msgstr ""
+
+msgid "UserProfile|Starred projects"
+msgstr ""
+
+msgid "UserProfile|Subscribe"
+msgstr ""
+
+msgid "UserProfile|This user doesn't have any personal projects"
+msgstr ""
+
+msgid "UserProfile|This user has a private profile"
+msgstr ""
+
+msgid "UserProfile|This user hasn't contributed to any projects"
+msgstr ""
+
+msgid "UserProfile|This user hasn't starred any projects"
+msgstr ""
+
+msgid "UserProfile|This user is blocked"
+msgstr ""
+
+msgid "UserProfile|View all"
+msgstr ""
+
+msgid "UserProfile|View user in admin area"
+msgstr ""
+
+msgid "UserProfile|You can create a group for several dependent projects."
+msgstr ""
+
+msgid "UserProfile|You haven't created any personal projects."
+msgstr ""
+
+msgid "UserProfile|You haven't created any snippets."
+msgstr ""
+
+msgid "UserProfile|Your projects can be available publicly, internally, or privately, at your choice."
+msgstr ""
+
+msgid "UserProfile|at"
+msgstr ""
+
+msgid "UserProfile|made a private contribution"
+msgstr ""
+
+msgid "Username (optional)"
+msgstr ""
+
+msgid "Username is already taken."
+msgstr ""
+
+msgid "Username is available."
+msgstr ""
+
+msgid "Username is too long (maximum is %{max_length} characters)."
+msgstr ""
+
+msgid "Username or email"
+msgstr ""
+
+msgid "Users"
+msgstr ""
+
+msgid "Users in License:"
+msgstr ""
+
+msgid "Users or groups set as approvers in the project's or merge request's settings."
+msgstr ""
+
+msgid "Users outside of license"
+msgstr ""
+
+msgid "Users over License:"
+msgstr ""
+
+msgid "Users requesting access to"
+msgstr ""
+
+msgid "Users were successfully added."
+msgstr ""
+
+msgid "Users with a Guest role or those who don't belong to any projects or groups don't count towards seats in use."
+msgstr ""
+
+msgid "UsersSelect|%{name} + %{length} more"
+msgstr ""
+
+msgid "UsersSelect|Any User"
+msgstr ""
+
+msgid "UsersSelect|Assignee"
+msgstr ""
+
+msgid "UsersSelect|No assignee - %{openingTag} assign yourself %{closingTag}"
+msgstr ""
+
+msgid "UsersSelect|Unassigned"
+msgstr ""
+
+msgid "Using %{codeStart}needs%{codeEnd} allows jobs to run before their stage is reached, as soon as their individual dependencies are met, which speeds up your pipelines."
+msgstr ""
+
+msgid "Using %{code_start}::%{code_end} denotes a %{link_start}scoped label set%{link_end}"
+msgstr ""
+
+msgid "Using required encryption strategy when encrypted field is missing!"
+msgstr ""
+
+msgid "Valid from"
+msgstr ""
+
+msgid "Validate"
+msgstr ""
+
+msgid "Validate your GitLab CI configuration file"
+msgstr ""
+
+msgid "Validations failed."
+msgstr ""
+
+msgid "Validity"
+msgstr ""
+
+msgid "Value"
+msgstr ""
+
+msgid "Value Stream"
+msgstr ""
+
+msgid "Value Stream Analytics"
+msgstr ""
+
+msgid "Value Stream Analytics can help you determine your team’s velocity"
+msgstr ""
+
+msgid "Value Stream Analytics gives an overview of how much time it takes to go from idea to production in your project."
+msgstr ""
+
+msgid "ValueStreamAnalytics|%{days}d"
+msgstr ""
+
+msgid "Variable"
+msgstr ""
+
+msgid "Variable will be masked in job logs."
+msgstr ""
+
+msgid "Variables"
+msgstr ""
+
+msgid "Various container registry settings."
+msgstr ""
+
+msgid "Various email settings."
+msgstr ""
+
+msgid "Various localization settings."
+msgstr ""
+
+msgid "Various settings that affect GitLab performance."
+msgstr ""
+
+msgid "Verification capacity"
+msgstr ""
+
+msgid "Verification information"
+msgstr ""
+
+msgid "Verification status"
+msgstr ""
+
+msgid "Verified"
+msgstr ""
+
+msgid "Verify SAML Configuration"
+msgstr ""
+
+msgid "Verify configuration"
+msgstr ""
+
+msgid "Version"
+msgstr ""
+
+msgid "Versions"
+msgstr ""
+
+msgid "View Documentation"
+msgstr ""
+
+msgid "View all issues"
+msgstr ""
+
+msgid "View blame prior to this change"
+msgstr ""
+
+msgid "View chart"
+msgid_plural "View charts"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "View dependency details for your project"
+msgstr ""
+
+msgid "View deployment"
+msgstr ""
+
+msgid "View details"
+msgstr ""
+
+msgid "View details: %{details_url}"
+msgstr ""
+
+msgid "View documentation"
+msgstr ""
+
+msgid "View eligible approvers"
+msgstr ""
+
+msgid "View epics list"
+msgstr ""
+
+msgid "View exposed artifact"
+msgid_plural "View %d exposed artifacts"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "View file @ "
+msgstr ""
+
+msgid "View file @ %{commitSha}"
+msgstr ""
+
+msgid "View full dashboard"
+msgstr ""
+
+msgid "View full log"
+msgstr ""
+
+msgid "View group labels"
+msgstr ""
+
+msgid "View incident issues."
+msgstr ""
+
+msgid "View issue"
+msgstr ""
+
+msgid "View issues"
+msgstr ""
+
+msgid "View it on GitLab"
+msgstr ""
+
+msgid "View job"
+msgstr ""
+
+msgid "View job log"
+msgstr ""
+
+msgid "View jobs"
+msgstr ""
+
+msgid "View labels"
+msgstr ""
+
+msgid "View log"
+msgstr ""
+
+msgid "View open merge request"
+msgstr ""
+
+msgid "View performance dashboard."
+msgstr ""
+
+msgid "View project"
+msgstr ""
+
+msgid "View project labels"
+msgstr ""
+
+msgid "View replaced file @ "
+msgstr ""
+
+msgid "View supported languages and frameworks"
+msgstr ""
+
+msgid "View the documentation"
+msgstr ""
+
+msgid "View the latest successful deployment to this environment"
+msgstr ""
+
+msgid "View the performance dashboard at"
+msgstr ""
+
+msgid "View users statistics"
+msgstr ""
+
+msgid "Viewing commit"
+msgstr ""
+
+msgid "Visibility"
+msgstr ""
+
+msgid "Visibility and access controls"
+msgstr ""
+
+msgid "Visibility level"
+msgstr ""
+
+msgid "Visibility level:"
+msgstr ""
+
+msgid "Visibility settings have been disabled by the administrator."
+msgstr ""
+
+msgid "Visibility, project features, permissions"
+msgstr ""
+
+msgid "Visibility:"
+msgstr ""
+
+msgid "VisibilityLevel|Internal"
+msgstr ""
+
+msgid "VisibilityLevel|Private"
+msgstr ""
+
+msgid "VisibilityLevel|Public"
+msgstr ""
+
+msgid "VisibilityLevel|Unknown"
+msgstr ""
+
+msgid "Visit settings page"
+msgstr ""
+
+msgid "VisualReviewApp|%{stepStart}Step 1%{stepEnd}. Copy the following script:"
+msgstr ""
+
+msgid "VisualReviewApp|%{stepStart}Step 2%{stepEnd}. Add it to the %{headTags} tags of every page of your application, ensuring the merge request ID is set or not set as required. "
+msgstr ""
+
+msgid "VisualReviewApp|%{stepStart}Step 3%{stepEnd}. If not previously %{linkStart}configured%{linkEnd} by a developer, enter the merge request ID for the review when prompted. The ID of this merge request is %{stepStart}%{mrId}%{stepStart}."
+msgstr ""
+
+msgid "VisualReviewApp|%{stepStart}Step 4%{stepEnd}. Leave feedback in the Review App."
+msgstr ""
+
+msgid "VisualReviewApp|Cancel"
+msgstr ""
+
+msgid "VisualReviewApp|Copy merge request ID"
+msgstr ""
+
+msgid "VisualReviewApp|Copy script"
+msgstr ""
+
+msgid "VisualReviewApp|Enable Visual Reviews"
+msgstr ""
+
+msgid "VisualReviewApp|Follow the steps below to enable Visual Reviews inside your application."
+msgstr ""
+
+msgid "VisualReviewApp|No review app found or available."
+msgstr ""
+
+msgid "VisualReviewApp|Open review app"
+msgstr ""
+
+msgid "VisualReviewApp|Review"
+msgstr ""
+
+msgid "VisualReviewApp|Steps 1 and 2 (and sometimes 3) are performed once by the developer before requesting feedback. Steps 3 (if necessary), 4 is performed by the reviewer each time they perform a review."
+msgstr ""
+
+msgid "Vulnerabilities"
+msgstr ""
+
+msgid "Vulnerabilities over time"
+msgstr ""
+
+msgid "Vulnerability remediated. Review before resolving."
+msgstr ""
+
+msgid "Vulnerability resolved in %{branch}"
+msgstr ""
+
+msgid "Vulnerability resolved in the default branch"
+msgstr ""
+
+msgid "Vulnerability-Check"
+msgstr ""
+
+msgid "VulnerabilityChart|%{formattedStartDate} to today"
+msgstr ""
+
+msgid "VulnerabilityChart|Severity"
+msgstr ""
+
+msgid "VulnerabilityManagement|A true-positive and will fix"
+msgstr ""
+
+msgid "VulnerabilityManagement|Change status"
+msgstr ""
+
+msgid "VulnerabilityManagement|Confirm"
+msgstr ""
+
+msgid "VulnerabilityManagement|Confirmed %{timeago} by %{user}"
+msgstr ""
+
+msgid "VulnerabilityManagement|Detected %{timeago} in pipeline %{pipelineLink}"
+msgstr ""
+
+msgid "VulnerabilityManagement|Dismiss"
+msgstr ""
+
+msgid "VulnerabilityManagement|Dismissed %{timeago} by %{user}"
+msgstr ""
+
+msgid "VulnerabilityManagement|Resolved"
+msgstr ""
+
+msgid "VulnerabilityManagement|Resolved %{timeago} by %{user}"
+msgstr ""
+
+msgid "VulnerabilityManagement|Something went wrong while trying to delete the comment. Please try again later."
+msgstr ""
+
+msgid "VulnerabilityManagement|Something went wrong while trying to refresh the vulnerability. Please try again later."
+msgstr ""
+
+msgid "VulnerabilityManagement|Something went wrong while trying to retrieve the vulnerability history. Please try again later."
+msgstr ""
+
+msgid "VulnerabilityManagement|Something went wrong while trying to save the comment. Please try again later."
+msgstr ""
+
+msgid "VulnerabilityManagement|Something went wrong, could not create an issue."
+msgstr ""
+
+msgid "VulnerabilityManagement|Something went wrong, could not get user."
+msgstr ""
+
+msgid "VulnerabilityManagement|Something went wrong, could not update vulnerability state."
+msgstr ""
+
+msgid "VulnerabilityManagement|Verified as fixed or mitigated"
+msgstr ""
+
+msgid "VulnerabilityManagement|Will not fix or a false-positive"
+msgstr ""
+
+msgid "VulnerabilityStatusTypes|All"
+msgstr ""
+
+msgid "VulnerabilityStatusTypes|Confirmed"
+msgstr ""
+
+msgid "VulnerabilityStatusTypes|Detected"
+msgstr ""
+
+msgid "VulnerabilityStatusTypes|Dismissed"
+msgstr ""
+
+msgid "VulnerabilityStatusTypes|Resolved"
+msgstr ""
+
+msgid "Vulnerability|%{scannerName} (version %{scannerVersion})"
+msgstr ""
+
+msgid "Vulnerability|Class"
+msgstr ""
+
+msgid "Vulnerability|Crash Address"
+msgstr ""
+
+msgid "Vulnerability|Description"
+msgstr ""
+
+msgid "Vulnerability|Evidence"
+msgstr ""
+
+msgid "Vulnerability|File"
+msgstr ""
+
+msgid "Vulnerability|Identifiers"
+msgstr ""
+
+msgid "Vulnerability|Image"
+msgstr ""
+
+msgid "Vulnerability|Instances"
+msgstr ""
+
+msgid "Vulnerability|Links"
+msgstr ""
+
+msgid "Vulnerability|Method"
+msgstr ""
+
+msgid "Vulnerability|Namespace"
+msgstr ""
+
+msgid "Vulnerability|Project"
+msgstr ""
+
+msgid "Vulnerability|Scanner Provider"
+msgstr ""
+
+msgid "Vulnerability|Scanner Type"
+msgstr ""
+
+msgid "Vulnerability|Severity"
+msgstr ""
+
+msgid "Vulnerability|Status"
+msgstr ""
+
+msgid "WIP"
+msgstr ""
+
+msgid "Wait for the file to load to copy its contents"
+msgstr ""
+
+msgid "Waiting for merge (open and assigned)"
+msgstr ""
+
+msgid "Waiting for performance data"
+msgstr ""
+
+msgid "Want to see the data? Please ask an administrator for access."
+msgstr ""
+
+msgid "Warning:"
+msgstr ""
+
+msgid "Warning: Displaying this diagram might cause performance issues on this page."
+msgstr ""
+
+msgid "We are currently unable to fetch data for this graph."
+msgstr ""
+
+msgid "We could not determine the path to remove the epic"
+msgstr ""
+
+msgid "We could not determine the path to remove the issue"
+msgstr ""
+
+msgid "We couldn't reach the Prometheus server. Either the server no longer exists or the configuration details need updating."
+msgstr ""
+
+msgid "We detected potential spam in the %{humanized_resource_name}. Please solve the reCAPTCHA to proceed."
+msgstr ""
+
+msgid "We don't have enough data to show this stage."
+msgstr ""
+
+msgid "We have found the following errors:"
+msgstr ""
+
+msgid "We heard back from your device. You have been authenticated."
+msgstr ""
+
+msgid "We recommend that you buy more Pipeline minutes to avoid any interruption of service."
+msgstr ""
+
+msgid "We recommend that you buy more Pipeline minutes to resume normal service."
+msgstr ""
+
+msgid "We sent you an email with reset password instructions"
+msgstr ""
+
+msgid "We tried to automatically renew your %{strong}%{plan_name}%{strong_close} subscription for %{strong}%{namespace_name}%{strong_close} on %{expires_on} but something went wrong so your subscription was downgraded to the free plan. Don't worry, your data is safe. We suggest you check your payment method and get in touch with our support team (%{support_link}). They'll gladly help with your subscription renewal."
+msgstr ""
+
+msgid "We want to be sure it is you, please confirm you are not a robot."
+msgstr ""
+
+msgid "We will automatically renew your %{strong}%{plan_name}%{strong_close} subscription for %{strong}%{namespace_name}%{strong_close} on %{strong}%{expires_on}%{strong_close}. There's nothing that you need to do, we'll let you know when the renewal is complete. Need more seats, a higher plan or just want to review your payment method?"
+msgstr ""
+
+msgid "We've found no vulnerabilities"
+msgstr ""
+
+msgid "Web Application Firewall"
+msgstr ""
+
+msgid "Web IDE"
+msgstr ""
+
+msgid "Web Terminal"
+msgstr ""
+
+msgid "Web terminal"
+msgstr ""
+
+msgid "WebIDE|Merge request"
+msgstr ""
+
+msgid "Webhook"
+msgstr ""
+
+msgid "Webhook Logs"
+msgstr ""
+
+msgid "Webhook Settings"
+msgstr ""
+
+msgid "Webhooks"
+msgstr ""
+
+msgid "Webhooks allow you to trigger a URL if, for example, new code is pushed or a new issue is created. You can configure webhooks to listen for specific events like pushes, issues or merge requests. Group webhooks will apply to all projects in a group, allowing you to standardize webhook functionality across your entire group."
+msgstr ""
+
+msgid "Webhooks have moved. They can now be found under the Settings menu."
+msgstr ""
+
+msgid "Wednesday"
+msgstr ""
+
+msgid "Weekday"
+msgstr ""
+
+msgid "Weeks"
+msgstr ""
+
+msgid "Weight"
+msgstr ""
+
+msgid "Weight %{weight}"
+msgstr ""
+
+msgid "Welcome back! Your account had been deactivated due to inactivity but is now reactivated."
+msgstr ""
+
+msgid "Welcome to GitLab"
+msgstr ""
+
+msgid "Welcome to GitLab %{name}!"
+msgstr ""
+
+msgid "Welcome to GitLab, %{first_name}!"
+msgstr ""
+
+msgid "Welcome to GitLab.com<br>@%{name}!"
+msgstr ""
+
+msgid "Welcome to the guided GitLab tour"
+msgstr ""
+
+msgid "Welcome to your Issue Board!"
+msgstr ""
+
+msgid "What are you searching for?"
+msgstr ""
+
+msgid "What describes you best?"
+msgstr ""
+
+msgid "What’s your experience level?"
+msgstr ""
+
+msgid "When a deployment job is successful, skip older deployment jobs that are still pending"
+msgstr ""
+
+msgid "When a runner is locked, it cannot be assigned to other projects"
+msgstr ""
+
+msgid "When enabled, any user visiting %{host} will be able to create an account."
+msgstr ""
+
+msgid "When enabled, if an NPM package isn't found in the GitLab Registry, we will attempt to pull from the global NPM registry."
+msgstr ""
+
+msgid "When enabled, users cannot use GitLab until the terms have been accepted."
+msgstr ""
+
+msgid "When leaving the URL blank, classification labels can still be specified without disabling cross project features or performing external authorization checks."
+msgstr ""
+
+msgid "When this merge request is accepted"
+msgid_plural "When these merge requests are accepted"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "When using the <code>http://</code> or <code>https://</code> protocols, please provide the exact URL to the repository. HTTP redirects will not be followed."
+msgstr ""
+
+msgid "When:"
+msgstr ""
+
+msgid "While it's rare to have no vulnerabilities, it can happen. In any event, we ask that you please double check your settings to make sure you've set up your dashboard correctly."
+msgstr ""
+
+msgid "Who can be an approver?"
+msgstr ""
+
+msgid "Who can see this group?"
+msgstr ""
+
+msgid "Who will be able to see this group?"
+msgstr ""
+
+msgid "Who will be using GitLab?"
+msgstr ""
+
+msgid "Who will be using this GitLab subscription?"
+msgstr ""
+
+msgid "Who will be using this GitLab trial?"
+msgstr ""
+
+msgid "Wiki"
+msgstr ""
+
+msgid "Wiki was successfully updated."
+msgstr ""
+
+msgid "WikiClone|Clone your wiki"
+msgstr ""
+
+msgid "WikiClone|Git Access"
+msgstr ""
+
+msgid "WikiClone|Install Gollum"
+msgstr ""
+
+msgid "WikiClone|Start Gollum and edit locally"
+msgstr ""
+
+msgid "WikiEditPageTip|Tip: You can move this page by adding the path to the beginning of the title."
+msgstr ""
+
+msgid "WikiEdit|There is already a page with the same title in that path."
+msgstr ""
+
+msgid "WikiEmptyIssueMessage|Suggest wiki improvement"
+msgstr ""
+
+msgid "WikiEmptyIssueMessage|You must be a group member in order to add wiki pages. If you have suggestions for how to improve the wiki for this group, consider opening an issue in the %{issues_link}."
+msgstr ""
+
+msgid "WikiEmptyIssueMessage|You must be a project member in order to add wiki pages. If you have suggestions for how to improve the wiki for this project, consider opening an issue in the %{issues_link}."
+msgstr ""
+
+msgid "WikiEmptyIssueMessage|issue tracker"
+msgstr ""
+
+msgid "WikiEmpty|A wiki is where you can store all the details about your group. This can include why you've created it, its principles, how to use it, and so on."
+msgstr ""
+
+msgid "WikiEmpty|A wiki is where you can store all the details about your project. This can include why you've created it, its principles, how to use it, and so on."
+msgstr ""
+
+msgid "WikiEmpty|Create your first page"
+msgstr ""
+
+msgid "WikiEmpty|Suggest wiki improvement"
+msgstr ""
+
+msgid "WikiEmpty|The wiki lets you write documentation for your group"
+msgstr ""
+
+msgid "WikiEmpty|The wiki lets you write documentation for your project"
+msgstr ""
+
+msgid "WikiEmpty|This group has no wiki pages"
+msgstr ""
+
+msgid "WikiEmpty|This project has no wiki pages"
+msgstr ""
+
+msgid "WikiEmpty|You must be a group member in order to add wiki pages."
+msgstr ""
+
+msgid "WikiEmpty|You must be a project member in order to add wiki pages."
+msgstr ""
+
+msgid "WikiHistoricalPage|This is an old version of this page."
+msgstr ""
+
+msgid "WikiHistoricalPage|You can view the %{most_recent_link} or browse the %{history_link}."
+msgstr ""
+
+msgid "WikiHistoricalPage|history"
+msgstr ""
+
+msgid "WikiHistoricalPage|most recent version"
+msgstr ""
+
+msgid "WikiMarkdownDocs|More examples are in the %{docs_link}"
+msgstr ""
+
+msgid "WikiMarkdownDocs|documentation"
+msgstr ""
+
+msgid "WikiMarkdownTip|To link to a (new) page, simply type <code class=\"js-markup-link-example\">%{link_example}</code>"
+msgstr ""
+
+msgid "WikiNewPageTip|Tip: You can specify the full path for the new file. We will automatically create any missing directories."
+msgstr ""
+
+msgid "WikiPageConfirmDelete|Are you sure you want to delete this page?"
+msgstr ""
+
+msgid "WikiPageConfirmDelete|Delete page"
+msgstr ""
+
+msgid "WikiPageConfirmDelete|Delete page %{pageTitle}?"
+msgstr ""
+
+msgid "WikiPageConflictMessage|Someone edited the page the same time you did. Please check out %{page_link} and make sure your changes will not unintentionally remove theirs."
+msgstr ""
+
+msgid "WikiPageConflictMessage|the page"
+msgstr ""
+
+msgid "WikiPageCreate|Create %{pageTitle}"
+msgstr ""
+
+msgid "WikiPageEdit|Update %{pageTitle}"
+msgstr ""
+
+msgid "WikiPage|Write your content or drag files here…"
+msgstr ""
+
+msgid "Wiki|Create New Page"
+msgstr ""
+
+msgid "Wiki|Create page"
+msgstr ""
+
+msgid "Wiki|Created date"
+msgstr ""
+
+msgid "Wiki|Edit Page"
+msgstr ""
+
+msgid "Wiki|New page"
+msgstr ""
+
+msgid "Wiki|Page history"
+msgstr ""
+
+msgid "Wiki|Page title"
+msgstr ""
+
+msgid "Wiki|Page version"
+msgstr ""
+
+msgid "Wiki|Pages"
+msgstr ""
+
+msgid "Wiki|Title"
+msgstr ""
+
+msgid "Wiki|View All Pages"
+msgstr ""
+
+msgid "Wiki|Wiki Pages"
+msgstr ""
+
+msgid "Will be created"
+msgstr ""
+
+msgid "Will deploy to"
+msgstr ""
+
+msgid "With requirements, you can set criteria to check your products against."
+msgstr ""
+
+msgid "Withdraw Access Request"
+msgstr ""
+
+msgid "Won't fix / Accept risk"
+msgstr ""
+
+msgid "Work in progress (open and unassigned)"
+msgstr ""
+
+msgid "Work in progress Limit"
+msgstr ""
+
+msgid "Write"
+msgstr ""
+
+msgid "Write a comment or drag your files here…"
+msgstr ""
+
+msgid "Write a comment…"
+msgstr ""
+
+msgid "Write access allowed"
+msgstr ""
+
+msgid "Write milestone description..."
+msgstr ""
+
+msgid "Write your release notes or drag your files here…"
+msgstr ""
+
+msgid "Wrong extern UID provided. Make sure Auth0 is configured correctly."
+msgstr ""
+
+msgid "Yes"
+msgstr ""
+
+msgid "Yes or No"
+msgstr ""
+
+msgid "Yes, add it"
+msgstr ""
+
+msgid "Yes, close issue"
+msgstr ""
+
+msgid "Yes, let me map Google Code users to full names or GitLab users."
+msgstr ""
+
+msgid "Yesterday"
+msgstr ""
+
+msgid "You"
+msgstr ""
+
+msgid "You are about to delete %{domain} from your instance. This domain will no longer be available to any Knative application."
+msgstr ""
+
+msgid "You are about to transfer the control of your account to %{group_name} group. This action is NOT reversible, you won't be able to access any of your groups and projects outside of %{group_name} once this transfer is complete."
+msgstr ""
+
+msgid "You are an admin, which means granting access to <strong>%{client_name}</strong> will allow them to interact with GitLab as an admin as well. Proceed with caution."
+msgstr ""
+
+msgid "You are attempting to delete a file that has been previously updated."
+msgstr ""
+
+msgid "You are attempting to update a file that has changed since you started editing it."
+msgstr ""
+
+msgid "You are connected to the Prometheus server, but there is currently no data to display."
+msgstr ""
+
+msgid "You are going to remove %{group_name}, this will also remove all of its subgroups and projects. Removed groups CANNOT be restored! Are you ABSOLUTELY sure?"
+msgstr ""
+
+msgid "You are going to remove %{project_full_name}. Removed project CANNOT be restored! Are you ABSOLUTELY sure?"
+msgstr ""
+
+msgid "You are going to remove the fork relationship from %{project_full_name}. Are you ABSOLUTELY sure?"
+msgstr ""
+
+msgid "You are going to transfer %{project_full_name} to another owner. Are you ABSOLUTELY sure?"
+msgstr ""
+
+msgid "You are not allowed to push into this branch. Create another branch or open a merge request."
+msgstr ""
+
+msgid "You are not allowed to unlink your primary login account"
+msgstr ""
+
+msgid "You are not authorized to perform this action"
+msgstr ""
+
+msgid "You are now impersonating %{username}"
+msgstr ""
+
+msgid "You are on a read-only GitLab instance."
+msgstr ""
+
+msgid "You are receiving this message because you are a GitLab administrator for %{url}."
+msgstr ""
+
+msgid "You are trying to upload something other than an image. Please upload a .png, .jpg, .jpeg, .gif, .bmp, .tiff or .ico."
+msgstr ""
+
+msgid "You can %{linkStart}view the blob%{linkEnd} instead."
+msgstr ""
+
+msgid "You can also create a project from the command line."
+msgstr ""
+
+msgid "You can also press &#8984;-Enter"
+msgstr ""
+
+msgid "You can also press Ctrl-Enter"
+msgstr ""
+
+msgid "You can also star a label to make it a priority label."
+msgstr ""
+
+msgid "You can also test your %{gitlab_ci_yml} in %{lint_link_start}CI Lint%{lint_link_end}"
+msgstr ""
+
+msgid "You can also upload existing files from your computer using the instructions below."
+msgstr ""
+
+msgid "You can also use project access tokens to authenticate against Git over HTTP."
+msgstr ""
+
+msgid "You can always edit this later"
+msgstr ""
+
+msgid "You can apply your Trial to your Personal account or create a New Group."
+msgstr ""
+
+msgid "You can create a new one or check them in your %{pat_link_start}Personal Access Tokens%{pat_link_end} settings"
+msgstr ""
+
+msgid "You can create a new one or check them in your Personal Access Tokens settings %{pat_link}"
+msgstr ""
+
+msgid "You can create new ones at your %{pat_link_start}Personal Access Tokens%{pat_link_end} settings"
+msgstr ""
+
+msgid "You can create new ones at your Personal Access Tokens settings %{pat_link}"
+msgstr ""
+
+msgid "You can easily contribute to them by requesting to join these groups."
+msgstr ""
+
+msgid "You can easily install a Runner on a Kubernetes cluster. %{link_to_help_page}"
+msgstr ""
+
+msgid "You can filter by 'days to merge' by clicking on the columns in the chart."
+msgstr ""
+
+msgid "You can generate an access token scoped to this project for each application to use the GitLab API."
+msgstr ""
+
+msgid "You can get started by cloning the repository or start adding files to it with one of the following options."
+msgstr ""
+
+msgid "You can invite a new member to <strong>%{project_name}</strong> or invite another group."
+msgstr ""
+
+msgid "You can invite a new member to <strong>%{project_name}</strong>."
+msgstr ""
+
+msgid "You can invite another group to <strong>%{project_name}</strong>."
+msgstr ""
+
+msgid "You can move around the graph by using the arrow keys."
+msgstr ""
+
+msgid "You can notify the app / group or a project by sending them an email notification"
+msgstr ""
+
+msgid "You can now export your security dashboard to a CSV report."
+msgstr ""
+
+msgid "You can now manage alert endpoint configuration in the Alerts section on the Operations settings page. Fields on this page have been deprecated."
+msgstr ""
+
+msgid "You can now submit a merge request to get this change into the original branch."
+msgstr ""
+
+msgid "You can now submit a merge request to get this change into the original project."
+msgstr ""
+
+msgid "You can only edit files when you are on a branch"
+msgstr ""
+
+msgid "You can only merge once the items above are resolved."
+msgstr ""
+
+msgid "You can only merge once this merge request is approved."
+msgstr ""
+
+msgid "You can only transfer the project to namespaces you manage."
+msgstr ""
+
+msgid "You can only upload one design when dropping onto an existing design."
+msgstr ""
+
+msgid "You can resolve the merge conflict using either the Interactive mode, by choosing %{use_ours} or %{use_theirs} buttons, or by editing the files directly. Commit these changes into %{branch_name}"
+msgstr ""
+
+msgid "You can see your chat accounts."
+msgstr ""
+
+msgid "You can set up as many Runners as you need to run your jobs."
+msgstr ""
+
+msgid "You can set up jobs to only use Runners with specific tags. Separate tags with commas."
+msgstr ""
+
+msgid "You can specify notification level per group or per project."
+msgstr ""
+
+msgid "You can test your .gitlab-ci.yml in %{linkStart}CI Lint%{linkEnd}."
+msgstr ""
+
+msgid "You can try again using %{begin_link}basic search%{end_link}"
+msgstr ""
+
+msgid "You cannot access the raw file. Please wait a minute."
+msgstr ""
+
+msgid "You cannot impersonate a blocked user"
+msgstr ""
+
+msgid "You cannot impersonate a user who cannot log in"
+msgstr ""
+
+msgid "You cannot impersonate an internal user"
+msgstr ""
+
+msgid "You cannot play this scheduled pipeline at the moment. Please wait a minute."
+msgstr ""
+
+msgid "You cannot write to a read-only secondary GitLab Geo instance. Please use %{link_to_primary_node} instead."
+msgstr ""
+
+msgid "You cannot write to this read-only GitLab instance."
+msgstr ""
+
+msgid "You could not create a new trigger."
+msgstr ""
+
+msgid "You didn't renew your %{strong}%{plan_name}%{strong_close} subscription for %{strong}%{namespace_name}%{strong_close} so it was downgraded to the free plan."
+msgstr ""
+
+msgid "You didn't renew your %{strong}%{plan_name}%{strong_close} subscription so it was downgraded to the GitLab Core Plan."
+msgstr ""
+
+msgid "You do not have an active license"
+msgstr ""
+
+msgid "You do not have any subscriptions yet"
+msgstr ""
+
+msgid "You do not have permission to leave this %{namespaceType}."
+msgstr ""
+
+msgid "You do not have permission to run the Web Terminal. Please contact a project administrator."
+msgstr ""
+
+msgid "You do not have permissions to run the import."
+msgstr ""
+
+msgid "You do not have the correct permissions to override the settings from the LDAP group sync."
+msgstr ""
+
+msgid "You don't have any U2F devices registered yet."
+msgstr ""
+
+msgid "You don't have any active chat names."
+msgstr ""
+
+msgid "You don't have any applications"
+msgstr ""
+
+msgid "You don't have any authorized applications"
+msgstr ""
+
+msgid "You don't have any deployments right now."
+msgstr ""
+
+msgid "You don't have any open merge requests"
+msgstr ""
+
+msgid "You don't have any projects available."
+msgstr ""
+
+msgid "You don't have any recent searches"
+msgstr ""
+
+msgid "You don't have sufficient permission to perform this action."
+msgstr ""
+
+msgid "You don’t have access to Productivity Analytics in this group"
+msgstr ""
+
+msgid "You don’t have access to Value Stream Analytics for this group"
+msgstr ""
+
+msgid "You have a license that activates at a future date. Please see the License History table below."
+msgstr ""
+
+msgid "You have been granted %{access_level} access to the %{source_link} %{source_type}."
+msgstr ""
+
+msgid "You have been granted %{access_level} access to the %{source_name} %{source_type}."
+msgstr ""
+
+msgid "You have been granted %{member_human_access} access to %{label}."
+msgstr ""
+
+msgid "You have been unsubscribed from this thread."
+msgstr ""
+
+msgid "You have declined the invitation to join %{label}."
+msgstr ""
+
+msgid "You have imported from this project %{numberOfPreviousImports} times before. Each new import will create duplicate issues."
+msgstr ""
+
+msgid "You have no permissions"
+msgstr ""
+
+msgid "You have not added any approvers. Start by adding users or groups."
+msgstr ""
+
+msgid "You have reached your project limit"
+msgstr ""
+
+msgid "You have successfully purchased a %{plan} plan subscription for %{seats}. You’ll receive a receipt via email."
+msgstr ""
+
+msgid "You haven't added any issues to your project yet"
+msgstr ""
+
+msgid "You haven't selected any issues yet"
+msgstr ""
+
+msgid "You left the \"%{membershipable_human_name}\" %{source_type}."
+msgstr ""
+
+msgid "You may also add variables that are made available to the running application by prepending the variable key with <code>K8S_SECRET_</code>."
+msgstr ""
+
+msgid "You may close the milestone now."
+msgstr ""
+
+msgid "You must accept our Terms of Service and privacy policy in order to register an account"
+msgstr ""
+
+msgid "You must disassociate %{domain} from all clusters it is attached to before deletion."
+msgstr ""
+
+msgid "You must have maintainer access to force delete a lock"
+msgstr ""
+
+msgid "You must have permission to create a project in a namespace before forking."
+msgstr ""
+
+msgid "You must provide a valid current password"
+msgstr ""
+
+msgid "You must provide your current password in order to change it."
+msgstr ""
+
+msgid "You must select a stack for configuring your cloud provider. Learn more about"
+msgstr ""
+
+msgid "You must set up incoming email before it becomes active."
+msgstr ""
+
+msgid "You must upload a file with the same file name when dropping onto an existing design."
+msgstr ""
+
+msgid "You need a different license to enable FileLocks feature"
+msgstr ""
+
+msgid "You need git-lfs version %{min_git_lfs_version} (or greater) to continue. Please visit https://git-lfs.github.com"
+msgstr ""
+
+msgid "You need permission."
+msgstr ""
+
+msgid "You need to be logged in."
+msgstr ""
+
+msgid "You need to register a two-factor authentication app before you can set up a U2F device."
+msgstr ""
+
+msgid "You need to set terms to be enforced"
+msgstr ""
+
+msgid "You need to specify both an Access Token and a Host URL."
+msgstr ""
+
+msgid "You need to upload a GitLab project export archive (ending in .gz)."
+msgstr ""
+
+msgid "You need to upload a Google Takeout archive."
+msgstr ""
+
+msgid "You reached %{usage_in_percent} of %{namespace_name}'s storage capacity (%{used_storage} of %{storage_limit})"
+msgstr ""
+
+msgid "You tried to fork %{link_to_the_project} but it failed for the following reason:"
+msgstr ""
+
+msgid "You will be removed from existing projects/groups"
+msgstr ""
+
+msgid "You will be the author of all events in the activity feed that are the result of an update, like new branches being created or new commits being pushed to existing branches."
+msgstr ""
+
+msgid "You will first need to set up Jira Integration to use this feature."
+msgstr ""
+
+msgid "You will lose all changes you've made to this file. This action cannot be undone."
+msgstr ""
+
+msgid "You will lose all uncommitted changes you've made in this project. This action cannot be undone."
+msgstr ""
+
+msgid "You will need to update your local repositories to point to the new location."
+msgstr ""
+
+msgid "You will not get any notifications via email"
+msgstr ""
+
+msgid "You will only receive notifications for the events you choose"
+msgstr ""
+
+msgid "You will only receive notifications for threads you have participated in"
+msgstr ""
+
+msgid "You will receive notifications for any activity"
+msgstr ""
+
+msgid "You will receive notifications only for comments in which you were @mentioned"
+msgstr ""
+
+msgid "You won't be able to create new projects because you have reached your project limit."
+msgstr ""
+
+msgid "You won't be able to pull or push project code via %{protocol} until you %{set_password_link} on your account"
+msgstr ""
+
+msgid "You won't be able to pull or push project code via SSH until you add an SSH key to your profile"
+msgstr ""
+
+msgid "You'll be signed out from your current account automatically."
+msgstr ""
+
+msgid "You'll need to use different branch names to get a valid comparison."
+msgstr ""
+
+msgid "You're about to reduce the visibility of the project %{strong_start}%{project_name}%{strong_end} in %{strong_start}%{group_name}%{strong_end}."
+msgstr ""
+
+msgid "You're about to reduce the visibility of the project %{strong_start}%{project_name}%{strong_end}."
+msgstr ""
+
+msgid "You're at the first commit"
+msgstr ""
+
+msgid "You're at the last commit"
+msgstr ""
+
+msgid "You're not allowed to %{tag_start}edit%{tag_end} files in this project directly. Please fork this project, make your changes there, and submit a merge request."
+msgstr ""
+
+msgid "You're not allowed to make changes to this project directly. A fork of this project has been created that you can make changes in, so you can submit a merge request."
+msgstr ""
+
+msgid "You're not allowed to make changes to this project directly. A fork of this project is being created that you can make changes in, so you can submit a merge request."
+msgstr ""
+
+msgid "You're only seeing %{startTag}other activity%{endTag} in the feed. To add a comment, switch to one of the following options."
+msgstr ""
+
+msgid "You're receiving this email because of your account on %{host}."
+msgstr ""
+
+msgid "You're receiving this email because of your account on %{host}. %{manage_notifications_link} &middot; %{help_link}"
+msgstr ""
+
+msgid "You're receiving this email because of your activity on %{host}."
+msgstr ""
+
+msgid "You're receiving this email because you have been assigned an item on %{host}."
+msgstr ""
+
+msgid "You're receiving this email because you have been mentioned on %{host}."
+msgstr ""
+
+msgid "You've already enabled two-factor authentication using one time password authenticators. In order to register a different device, you must first disable two-factor authentication."
+msgstr ""
+
+msgid "YouTube"
+msgstr ""
+
+msgid "Your %{host} account was signed in to from a new location"
+msgstr ""
+
+msgid "Your %{strong}%{plan_name}%{strong_close} subscription for %{strong}%{namespace_name}%{strong_close} will expire on %{strong}%{expires_on}%{strong_close}."
+msgstr ""
+
+msgid "Your %{strong}%{plan_name}%{strong_close} subscription will expire on %{strong}%{expires_on}%{strong_close}. After that, you will not to be able to create issues or merge requests as well as many other features."
+msgstr ""
+
+msgid "Your CSV export has started. It will be emailed to %{email} when complete."
+msgstr ""
+
+msgid "Your CSV export of %{issues_count} from project %{project_link} has been added to this email as an attachment."
+msgstr ""
+
+msgid "Your CSV export of %{written_count} from project %{project_name} (%{project_url}) has been added to this email as an attachment."
+msgstr ""
+
+msgid "Your Commit Email will be used for web based operations, such as edits and merges."
+msgstr ""
+
+msgid "Your Default Notification Email will be used for account notifications if a %{openingTag}group-specific email address%{closingTag} is not set."
+msgstr ""
+
+msgid "Your DevOps Score gives an overview of how you are using GitLab from a feature perspective. View how you compare with other organizations, discover features you are not using, and learn best practices through blog posts and white papers."
+msgstr ""
+
+msgid "Your GPG keys (%{count})"
+msgstr ""
+
+msgid "Your GitLab group"
+msgstr ""
+
+msgid "Your Gitlab Gold trial will last 30 days after which point you can keep your free Gitlab account forever. We just need some additional information to activate your trial."
+msgstr ""
+
+msgid "Your Groups"
+msgstr ""
+
+msgid "Your License"
+msgstr ""
+
+msgid "Your Personal Access Tokens will expire in %{days_to_expire} days or less"
+msgstr ""
+
+msgid "Your Primary Email will be used for avatar detection."
+msgstr ""
+
+msgid "Your Projects (default)"
+msgstr ""
+
+msgid "Your Projects' Activity"
+msgstr ""
+
+msgid "Your Public Email will be displayed on your public profile."
+msgstr ""
+
+msgid "Your SSH keys (%{count})"
+msgstr ""
+
+msgid "Your To-Do List"
+msgstr ""
+
+msgid "Your U2F device did not send a valid JSON response."
+msgstr ""
+
+msgid "Your U2F device needs to be set up. Plug it in (if not already) and click the button on the left."
+msgstr ""
+
+msgid "Your U2F device was registered!"
+msgstr ""
+
+msgid "Your access request to the %{source_type} has been withdrawn."
+msgstr ""
+
+msgid "Your account has been deactivated by your administrator. Please log back in to reactivate your account."
+msgstr ""
+
+msgid "Your account is locked."
+msgstr ""
+
+msgid "Your account uses dedicated credentials for the \"%{group_name}\" group and can only be updated through SSO."
+msgstr ""
+
+msgid "Your applications (%{size})"
+msgstr ""
+
+msgid "Your authorized applications"
+msgstr ""
+
+msgid "Your browser doesn't support U2F. Please use Google Chrome desktop (version 41 or newer)."
+msgstr ""
+
+msgid "Your changes can be committed to %{branch_name} because a merge request is open."
+msgstr ""
+
+msgid "Your changes have been committed. Commit %{commitId} %{commitStats}"
+msgstr ""
+
+msgid "Your changes have been saved"
+msgstr ""
+
+msgid "Your changes have been successfully committed."
+msgstr ""
+
+msgid "Your comment could not be submitted because %{error}"
+msgstr ""
+
+msgid "Your comment could not be submitted! Please check your network connection and try again."
+msgstr ""
+
+msgid "Your comment could not be updated! Please check your network connection and try again."
+msgstr ""
+
+msgid "Your comment will be discarded."
+msgstr ""
+
+msgid "Your custom stage '%{title}' was created"
+msgstr ""
+
+msgid "Your dashboard has been copied. You can %{web_ide_link_start}edit it here%{web_ide_link_end}."
+msgstr ""
+
+msgid "Your dashboard has been updated. You can %{web_ide_link_start}edit it here%{web_ide_link_end}."
+msgstr ""
+
+msgid "Your deployment services will be broken, you will need to manually fix the services after renaming."
+msgstr ""
+
+msgid "Your device was successfully set up! Give it a name and register it with the GitLab server."
+msgstr ""
+
+msgid "Your first project"
+msgstr ""
+
+msgid "Your groups"
+msgstr ""
+
+msgid "Your instance has %{remaining_user_count} users remaining of the %{total_user_count} included in your subscription. You can add more users than the number included in your license, and we will include the overage in your next bill."
+msgstr ""
+
+msgid "Your instance is approaching its licensed user count"
+msgstr ""
+
+msgid "Your issues are being imported. Once finished, you'll get a confirmation email."
+msgstr ""
+
+msgid "Your issues will be imported in the background. Once finished, you'll get a confirmation email."
+msgstr ""
+
+msgid "Your license is valid from"
+msgstr ""
+
+msgid "Your license will be included in your GitLab backup and will survive upgrades, so in normal usage you should never need to re-upload your <code>.gitlab-license</code> file."
+msgstr ""
+
+msgid "Your message here"
+msgstr ""
+
+msgid "Your name"
+msgstr ""
+
+msgid "Your new %{type}"
+msgstr ""
+
+msgid "Your new SCIM token"
+msgstr ""
+
+msgid "Your new personal access token has been created."
+msgstr ""
+
+msgid "Your new project access token has been created."
+msgstr ""
+
+msgid "Your password isn't required to view this page. If a password or any other personal details are requested, please contact your administrator to report abuse."
+msgstr ""
+
+msgid "Your password reset token has expired."
+msgstr ""
+
+msgid "Your profile"
+msgstr ""
+
+msgid "Your project limit is %{limit} projects! Please contact your administrator to increase it"
+msgstr ""
+
+msgid "Your projects"
+msgstr ""
+
+msgid "Your request for access could not be processed: %{error_meesage}"
+msgstr ""
+
+msgid "Your request for access has been queued for review."
+msgstr ""
+
+msgid "Your response has been recorded."
+msgstr ""
+
+msgid "Your search didn't match any commits."
+msgstr ""
+
+msgid "Your subscription expired!"
+msgstr ""
+
+msgid "Your subscription has been downgraded."
+msgstr ""
+
+msgid "Your subscription will automatically renew in %{remaining_days}."
+msgstr ""
+
+msgid "Your subscription will expire in %{remaining_days}."
+msgstr ""
+
+msgid "Zoom meeting added"
+msgstr ""
+
+msgid "Zoom meeting removed"
+msgstr ""
+
+msgid "[No reason]"
+msgstr ""
+
+msgid "a deleted user"
+msgstr ""
+
+msgid "a design"
+msgstr ""
+
+msgid "about 1 hour"
+msgid_plural "about %d hours"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "activated"
+msgstr ""
+
+msgid "added %{created_at_timeago}"
+msgstr ""
+
+msgid "added a Zoom call to this issue"
+msgstr ""
+
+msgid "ago"
+msgstr ""
+
+msgid "alert"
+msgstr ""
+
+msgid "allowed to fail"
+msgstr ""
+
+msgid "already being used for another group or project %{timebox_name}."
+msgstr ""
+
+msgid "already has a \"created\" issue link"
+msgstr ""
+
+msgid "already shared with this group"
+msgstr ""
+
+msgid "among other things"
+msgstr ""
+
+msgid "any-approver for the merge request already exists"
+msgstr ""
+
+msgid "any-approver for the project already exists"
+msgstr ""
+
+msgid "archived"
+msgstr ""
+
+msgid "assign yourself"
+msgstr ""
+
+msgid "at risk"
+msgstr ""
+
+msgid "attach a new file"
+msgstr ""
+
+msgid "authored"
+msgstr ""
+
+msgid "blocks"
+msgstr ""
+
+msgid "branch name"
+msgstr ""
+
+msgid "by"
+msgstr ""
+
+msgid "by %{user}"
+msgstr ""
+
+msgid "cannot be changed if a personal project has container registry tags."
+msgstr ""
+
+msgid "cannot be enabled unless all domains have TLS certificates"
+msgstr ""
+
+msgid "cannot be modified"
+msgstr ""
+
+msgid "cannot block others"
+msgstr ""
+
+msgid "cannot contain HTML/XML tags, including any word between angle brackets (<,>)."
+msgstr ""
+
+msgid "cannot include leading slash or directory traversal."
+msgstr ""
+
+msgid "cannot itself be blocked"
+msgstr ""
+
+msgid "cannot merge"
+msgstr ""
+
+msgid "ciReport|%{linkStartTag}Learn more about Container Scanning %{linkEndTag}"
+msgstr ""
+
+msgid "ciReport|%{linkStartTag}Learn more about DAST %{linkEndTag}"
+msgstr ""
+
+msgid "ciReport|%{linkStartTag}Learn more about Dependency Scanning %{linkEndTag}"
+msgstr ""
+
+msgid "ciReport|%{linkStartTag}Learn more about SAST %{linkEndTag}"
+msgstr ""
+
+msgid "ciReport|%{linkStartTag}Learn more about Secret Scanning %{linkEndTag}"
+msgstr ""
+
+msgid "ciReport|%{linkStartTag}Learn more about codequality reports %{linkEndTag}"
+msgstr ""
+
+msgid "ciReport|%{remainingPackagesCount} more"
+msgstr ""
+
+msgid "ciReport|%{reportType} is loading"
+msgstr ""
+
+msgid "ciReport|%{reportType}: Loading resulted in an error"
+msgstr ""
+
+msgid "ciReport|(errors when loading results)"
+msgstr ""
+
+msgid "ciReport|(is loading)"
+msgstr ""
+
+msgid "ciReport|(is loading, errors when loading results)"
+msgstr ""
+
+msgid "ciReport|All projects"
+msgstr ""
+
+msgid "ciReport|All scanner types"
+msgstr ""
+
+msgid "ciReport|All severities"
+msgstr ""
+
+msgid "ciReport|Automatically apply the patch in a new branch"
+msgstr ""
+
+msgid "ciReport|Base pipeline codequality artifact not found"
+msgstr ""
+
+msgid "ciReport|Code quality"
+msgstr ""
+
+msgid "ciReport|Container Scanning"
+msgstr ""
+
+msgid "ciReport|Container scanning"
+msgstr ""
+
+msgid "ciReport|Container scanning detects known vulnerabilities in your docker images."
+msgstr ""
+
+msgid "ciReport|Coverage Fuzzing"
+msgstr ""
+
+msgid "ciReport|Create a merge request to implement this solution, or download and apply the patch manually."
+msgstr ""
+
+msgid "ciReport|Create issue"
+msgstr ""
+
+msgid "ciReport|DAST"
+msgstr ""
+
+msgid "ciReport|Dependency Scanning"
+msgstr ""
+
+msgid "ciReport|Dependency Scanning detects known vulnerabilities in your source code's dependencies."
+msgstr ""
+
+msgid "ciReport|Dependency scanning"
+msgstr ""
+
+msgid "ciReport|Download patch to resolve"
+msgstr ""
+
+msgid "ciReport|Download the patch to apply it manually"
+msgstr ""
+
+msgid "ciReport|Dynamic Application Security Testing (DAST) detects known vulnerabilities in your web application."
+msgstr ""
+
+msgid "ciReport|Failed to load %{reportName} report"
+msgstr ""
+
+msgid "ciReport|Fixed"
+msgstr ""
+
+msgid "ciReport|Fixed:"
+msgstr ""
+
+msgid "ciReport|Found %{issuesWithCount}"
+msgstr ""
+
+msgid "ciReport|Investigate this vulnerability by creating an issue"
+msgstr ""
+
+msgid "ciReport|Learn more about interacting with security reports"
+msgstr ""
+
+msgid "ciReport|Loading %{reportName} report"
+msgstr ""
+
+msgid "ciReport|Manage licenses"
+msgstr ""
+
+msgid "ciReport|New"
+msgstr ""
+
+msgid "ciReport|No changes to code quality"
+msgstr ""
+
+msgid "ciReport|No changes to performance metrics"
+msgstr ""
+
+msgid "ciReport|No code quality issues found"
+msgstr ""
+
+msgid "ciReport|Performance metrics"
+msgstr ""
+
+msgid "ciReport|Resolve with merge request"
+msgstr ""
+
+msgid "ciReport|SAST"
+msgstr ""
+
+msgid "ciReport|Secret Detection"
+msgstr ""
+
+msgid "ciReport|Secret scanning"
+msgstr ""
+
+msgid "ciReport|Secret scanning detects secrets and credentials vulnerabilities in your source code."
+msgstr ""
+
+msgid "ciReport|Security scanning"
+msgstr ""
+
+msgid "ciReport|Security scanning failed loading any results"
+msgstr ""
+
+msgid "ciReport|Solution"
+msgstr ""
+
+msgid "ciReport|Static Application Security Testing (SAST) detects known vulnerabilities in your source code."
+msgstr ""
+
+msgid "ciReport|There was an error creating the issue. Please try again."
+msgstr ""
+
+msgid "ciReport|There was an error creating the merge request. Please try again."
+msgstr ""
+
+msgid "ciReport|There was an error dismissing the vulnerability. Please try again."
+msgstr ""
+
+msgid "ciReport|There was an error fetching the codequality report."
+msgstr ""
+
+msgid "ciReport|There was an error reverting the dismissal. Please try again."
+msgstr ""
+
+msgid "ciReport|Used by %{packagesString}"
+msgid_plural "ciReport|Used by %{packagesString}, and %{lastPackage}"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "ciReport|View full report"
+msgstr ""
+
+msgid "closed issue"
+msgstr ""
+
+msgid "comment"
+msgstr ""
+
+msgid "commented on %{link_to_project}"
+msgstr ""
+
+msgid "commit %{commit_id}"
+msgstr ""
+
+msgid "committed"
+msgstr ""
+
+msgid "confidentiality|You are going to turn off the confidentiality. This means <strong>everyone</strong> will be able to see and leave a comment on this issue."
+msgstr ""
+
+msgid "confidentiality|You are going to turn on the confidentiality. This means that only team members with <strong>at least Reporter access</strong> are able to see and leave comments on the issue."
+msgstr ""
+
+msgid "connecting"
+msgstr ""
+
+msgid "container_name can contain only lowercase letters, digits, '-', and '.' and must start and end with an alphanumeric character"
+msgstr ""
+
+msgid "container_name cannot be larger than %{max_length} chars"
+msgstr ""
+
+msgid "could not read private key, is the passphrase correct?"
+msgstr ""
+
+msgid "created"
+msgstr ""
+
+msgid "created %{timeAgo}"
+msgstr ""
+
+msgid "customize"
+msgstr ""
+
+msgid "data"
+msgstr ""
+
+msgid "date must not be after 9999-12-31"
+msgstr ""
+
+msgid "day"
+msgid_plural "days"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "default branch"
+msgstr ""
+
+msgid "deleted"
+msgstr ""
+
+msgid "deploy"
+msgstr ""
+
+msgid "design"
+msgstr ""
+
+msgid "designs"
+msgstr ""
+
+msgid "detached"
+msgstr ""
+
+msgid "disabled"
+msgstr ""
+
+msgid "does not have a supported extension. Only %{extension_list} are supported"
+msgstr ""
+
+msgid "done"
+msgstr ""
+
+msgid "download it"
+msgstr ""
+
+msgid "draft"
+msgid_plural "drafts"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "e.g. %{token}"
+msgstr ""
+
+msgid "element is not a hierarchy"
+msgstr ""
+
+msgid "email '%{email}' does not match the allowed domains of %{email_domains}"
+msgstr ""
+
+msgid "email '%{email}' is not a verified email."
+msgstr ""
+
+msgid "enabled"
+msgstr ""
+
+msgid "encrypted: needs to be a :required, :optional or :migrating!"
+msgstr ""
+
+msgid "entries cannot be larger than 255 characters"
+msgstr ""
+
+msgid "entries cannot be nil"
+msgstr ""
+
+msgid "entries cannot contain HTML tags"
+msgstr ""
+
+msgid "epic"
+msgstr ""
+
+msgid "error"
+msgstr ""
+
+msgid "error code:"
+msgstr ""
+
+msgid "estimateCommand|%{slash_command} will update the estimated time with the latest command."
+msgstr ""
+
+msgid "exceeds the limit of %{bytes} bytes"
+msgstr ""
+
+msgid "exceeds the limit of %{bytes} bytes for directory name \"%{dirname}\""
+msgstr ""
+
+msgid "expired on %{timebox_due_date}"
+msgstr ""
+
+msgid "expires on %{timebox_due_date}"
+msgstr ""
+
+msgid "failed"
+msgstr ""
+
+msgid "failed to dismiss associated finding(id=%{finding_id}): %{message}"
+msgstr ""
+
+msgid "file"
+msgid_plural "files"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "finding is not found or is already attached to a vulnerability"
+msgstr ""
+
+msgid "for %{link_to_merge_request} with %{link_to_merge_request_source_branch}"
+msgstr ""
+
+msgid "for %{link_to_merge_request} with %{link_to_merge_request_source_branch} into %{link_to_merge_request_target_branch}"
+msgstr ""
+
+msgid "for %{link_to_pipeline_ref}"
+msgstr ""
+
+msgid "for %{ref}"
+msgstr ""
+
+msgid "for this project"
+msgstr ""
+
+msgid "fork this project"
+msgstr ""
+
+msgid "from"
+msgstr ""
+
+msgid "group"
+msgstr ""
+
+msgid "groups"
+msgstr ""
+
+msgid "has already been linked to another vulnerability"
+msgstr ""
+
+msgid "has already been taken"
+msgstr ""
+
+msgid "help"
+msgstr ""
+
+msgid "here"
+msgstr ""
+
+msgid "https://your-bitbucket-server"
+msgstr ""
+
+msgid "image diff"
+msgstr ""
+
+msgid "impersonation token"
+msgstr ""
+
+msgid "impersonation tokens"
+msgstr ""
+
+msgid "import flow"
+msgstr ""
+
+msgid "importing"
+msgstr ""
+
+msgid "in group %{link_to_group}"
+msgstr ""
+
+msgid "in project %{link_to_project}"
+msgstr ""
+
+msgid "index"
+msgstr ""
+
+msgid "instance completed"
+msgid_plural "instances completed"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "invalid milestone state `%{state}`"
+msgstr ""
+
+msgid "is"
+msgstr ""
+
+msgid "is already associated to a GitLab Issue. New issue will not be associated."
+msgstr ""
+
+msgid "is an invalid IP address range"
+msgstr ""
+
+msgid "is blocked by"
+msgstr ""
+
+msgid "is enabled."
+msgstr ""
+
+msgid "is invalid because there is downstream lock"
+msgstr ""
+
+msgid "is invalid because there is upstream lock"
+msgstr ""
+
+msgid "is not"
+msgstr ""
+
+msgid "is not a descendant of the Group owning the template"
+msgstr ""
+
+msgid "is not a valid X509 certificate."
+msgstr ""
+
+msgid "is not allowed. Try again with a different email address, or contact your GitLab admin."
+msgstr ""
+
+msgid "is not an email you own"
+msgstr ""
+
+msgid "is not in the group enforcing Group Managed Account"
+msgstr ""
+
+msgid "is read only"
+msgstr ""
+
+msgid "is too long (%{current_value}). The maximum size is %{max_size}."
+msgstr ""
+
+msgid "is too long (maximum is 100 entries)"
+msgstr ""
+
+msgid "is too long (maximum is 1000 entries)"
+msgstr ""
+
+msgid "issue"
+msgstr ""
+
+msgid "issues at risk"
+msgstr ""
+
+msgid "issues need attention"
+msgstr ""
+
+msgid "issues on track"
+msgstr ""
+
+msgid "it is larger than %{limit}"
+msgstr ""
+
+msgid "it is stored as a job artifact"
+msgstr ""
+
+msgid "it is stored externally"
+msgstr ""
+
+msgid "it is stored in LFS"
+msgstr ""
+
+msgid "it is too large"
+msgstr ""
+
+msgid "jigsaw is not defined"
+msgstr ""
+
+msgid "jira.issue.description.content"
+msgstr ""
+
+msgid "jira.issue.summary"
+msgstr ""
+
+msgid "latest"
+msgstr ""
+
+msgid "latest deployment"
+msgstr ""
+
+msgid "latest version"
+msgstr ""
+
+msgid "leave %{group_name}"
+msgstr ""
+
+msgid "less than a minute"
+msgstr ""
+
+msgid "level: %{level}"
+msgstr ""
+
+msgid "limit of %{project_limit} reached"
+msgstr ""
+
+msgid "load it anyway"
+msgstr ""
+
+msgid "loading"
+msgstr ""
+
+msgid "locked by %{path_lock_user_name} %{created_at}"
+msgstr ""
+
+msgid "log in"
+msgstr ""
+
+msgid "manual"
+msgstr ""
+
+msgid "math|The math in this entry is taking too long to render and may not be displayed as expected. For performance reasons, math blocks are also limited to %{maxChars} characters. Consider splitting up large formulae, splitting math blocks among multiple entries, or using an image instead."
+msgstr ""
+
+msgid "math|There was an error rendering this math block"
+msgstr ""
+
+msgid "merge request"
+msgid_plural "merge requests"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "merged %{time_ago}"
+msgstr ""
+
+msgid "missing"
+msgstr ""
+
+msgid "most recent deployment"
+msgstr ""
+
+msgid "mrWidgetCommitsAdded|%{commitCount} and %{mergeCommitCount} will be added to %{targetBranch}."
+msgstr ""
+
+msgid "mrWidgetCommitsAdded|%{commitCount} will be added to %{targetBranch}."
+msgstr ""
+
+msgid "mrWidgetCommitsAdded|1 merge commit"
+msgstr ""
+
+msgid "mrWidgetNothingToMerge|Currently there are no changes in this merge request's source branch. Please push new commits or use a different branch."
+msgstr ""
+
+msgid "mrWidgetNothingToMerge|Interested parties can even contribute by pushing commits if they want to."
+msgstr ""
+
+msgid "mrWidgetNothingToMerge|Merge requests are a place to propose changes you have made to a project and discuss those changes with others."
+msgstr ""
+
+msgid "mrWidget| Please restore it or use a different %{missingBranchName} branch"
+msgstr ""
+
+msgid "mrWidget|%{link_start}Learn more about resolving conflicts%{link_end}"
+msgstr ""
+
+msgid "mrWidget|%{metricsLinkStart} Memory %{metricsLinkEnd} usage %{emphasisStart} decreased %{emphasisEnd} from %{memoryFrom}MB to %{memoryTo}MB"
+msgstr ""
+
+msgid "mrWidget|%{metricsLinkStart} Memory %{metricsLinkEnd} usage %{emphasisStart} increased %{emphasisEnd} from %{memoryFrom}MB to %{memoryTo}MB"
+msgstr ""
+
+msgid "mrWidget|%{metricsLinkStart} Memory %{metricsLinkEnd} usage is %{emphasisStart} unchanged %{emphasisEnd} at %{memoryFrom}MB"
+msgstr ""
+
+msgid "mrWidget|%{prefixToLinkStart}No pipeline%{prefixToLinkEnd} %{addPipelineLinkStart}Add the .gitlab-ci.yml file%{addPipelineLinkEnd} to create one."
+msgstr ""
+
+msgid "mrWidget|Added to the merge train by"
+msgstr ""
+
+msgid "mrWidget|Allows commits from members who can merge to the target branch"
+msgstr ""
+
+msgid "mrWidget|An error occurred while removing your approval."
+msgstr ""
+
+msgid "mrWidget|An error occurred while retrieving approval data for this merge request."
+msgstr ""
+
+msgid "mrWidget|An error occurred while submitting your approval."
+msgstr ""
+
+msgid "mrWidget|Approval password is invalid."
+msgstr ""
+
+msgid "mrWidget|Approve"
+msgstr ""
+
+msgid "mrWidget|Approve additionally"
+msgstr ""
+
+msgid "mrWidget|Approved by"
+msgstr ""
+
+msgid "mrWidget|Are you adding technical debt or code vulnerabilities?"
+msgstr ""
+
+msgid "mrWidget|Cancel automatic merge"
+msgstr ""
+
+msgid "mrWidget|Check out branch"
+msgstr ""
+
+msgid "mrWidget|Checking ability to merge automatically…"
+msgstr ""
+
+msgid "mrWidget|Cherry-pick"
+msgstr ""
+
+msgid "mrWidget|Cherry-pick this merge request in a new merge request"
+msgstr ""
+
+msgid "mrWidget|Closed"
+msgstr ""
+
+msgid "mrWidget|Closed by"
+msgstr ""
+
+msgid "mrWidget|Closes"
+msgstr ""
+
+msgid "mrWidget|Create an issue to resolve them later"
+msgstr ""
+
+msgid "mrWidget|Delete source branch"
+msgstr ""
+
+msgid "mrWidget|Deployment statistics are not available currently"
+msgstr ""
+
+msgid "mrWidget|Did not close"
+msgstr ""
+
+msgid "mrWidget|Email patches"
+msgstr ""
+
+msgid "mrWidget|Failed to load deployment statistics"
+msgstr ""
+
+msgid "mrWidget|Fast-forward merge is not possible. To merge this request, first rebase locally."
+msgstr ""
+
+msgid "mrWidget|Fork merge requests do not create merge request pipelines which validate a post merge result"
+msgstr ""
+
+msgid "mrWidget|If the %{branch} branch exists in your local repository, you can merge this merge request manually using the"
+msgstr ""
+
+msgid "mrWidget|If the %{missingBranchName} branch exists in your local repository, you can merge this merge request manually using the command line"
+msgstr ""
+
+msgid "mrWidget|In the merge train at position %{mergeTrainPosition}"
+msgstr ""
+
+msgid "mrWidget|Loading deployment statistics"
+msgstr ""
+
+msgid "mrWidget|Mentions"
+msgstr ""
+
+msgid "mrWidget|Merge"
+msgstr ""
+
+msgid "mrWidget|Merge failed."
+msgstr ""
+
+msgid "mrWidget|Merge failed: %{mergeError}. Please try again."
+msgstr ""
+
+msgid "mrWidget|Merge locally"
+msgstr ""
+
+msgid "mrWidget|Merge request approved."
+msgstr ""
+
+msgid "mrWidget|Merged by"
+msgstr ""
+
+msgid "mrWidget|More information"
+msgstr ""
+
+msgid "mrWidget|No approval required"
+msgstr ""
+
+msgid "mrWidget|No approval required; you can still approve"
+msgstr ""
+
+msgid "mrWidget|Open in Web IDE"
+msgstr ""
+
+msgid "mrWidget|Pipeline blocked. The pipeline for this merge request requires a manual action to proceed"
+msgstr ""
+
+msgid "mrWidget|Plain diff"
+msgstr ""
+
+msgid "mrWidget|Ready to be merged automatically. Ask someone with write access to this repository to merge this request"
+msgstr ""
+
+msgid "mrWidget|Refresh"
+msgstr ""
+
+msgid "mrWidget|Refresh now"
+msgstr ""
+
+msgid "mrWidget|Refreshing now"
+msgstr ""
+
+msgid "mrWidget|Remove from merge train"
+msgstr ""
+
+msgid "mrWidget|Request to merge"
+msgstr ""
+
+msgid "mrWidget|Resolve WIP status"
+msgstr ""
+
+msgid "mrWidget|Resolve conflicts"
+msgstr ""
+
+msgid "mrWidget|Resolve these conflicts or ask someone with write access to this repository to merge it locally"
+msgstr ""
+
+msgid "mrWidget|Revert"
+msgstr ""
+
+msgid "mrWidget|Revert this merge request in a new merge request"
+msgstr ""
+
+msgid "mrWidget|Revoke approval"
+msgstr ""
+
+msgid "mrWidget|Set by"
+msgstr ""
+
+msgid "mrWidget|The changes were merged into"
+msgstr ""
+
+msgid "mrWidget|The changes were not merged into"
+msgstr ""
+
+msgid "mrWidget|The changes will be merged into"
+msgstr ""
+
+msgid "mrWidget|The pipeline for this merge request failed. Please retry the job or push a new commit to fix the failure"
+msgstr ""
+
+msgid "mrWidget|The source branch HEAD has recently changed. Please reload the page and review the changes before merging"
+msgstr ""
+
+msgid "mrWidget|The source branch has been deleted"
+msgstr ""
+
+msgid "mrWidget|The source branch is %{commitsBehindLinkStart}%{commitsBehind}%{commitsBehindLinkEnd} the target branch"
+msgstr ""
+
+msgid "mrWidget|The source branch is being deleted"
+msgstr ""
+
+msgid "mrWidget|The source branch will be deleted"
+msgstr ""
+
+msgid "mrWidget|The source branch will not be deleted"
+msgstr ""
+
+msgid "mrWidget|There are merge conflicts"
+msgstr ""
+
+msgid "mrWidget|There are unresolved threads. Please resolve these threads"
+msgstr ""
+
+msgid "mrWidget|This feature merges changes from the target branch to the source branch. You cannot use this feature since the source branch is protected."
+msgstr ""
+
+msgid "mrWidget|This merge request failed to be merged automatically"
+msgstr ""
+
+msgid "mrWidget|This merge request is in the process of being merged"
+msgstr ""
+
+msgid "mrWidget|This merge request will be added to the merge train when pipeline %{linkStart}#%{pipelineId}%{linkEnd} succeeds."
+msgstr ""
+
+msgid "mrWidget|This merge request will start a merge train when pipeline %{linkStart}#%{pipelineId}%{linkEnd} succeeds."
+msgstr ""
+
+msgid "mrWidget|This project is archived, write access has been disabled"
+msgstr ""
+
+msgid "mrWidget|To approve this merge request, please enter your password. This project requires all approvals to be authenticated."
+msgstr ""
+
+msgid "mrWidget|Use %{linkStart}CI pipelines to test your code%{linkEnd}, simply add a GitLab CI configuration file to your project. It only takes a minute to make your code more secure and robust."
+msgstr ""
+
+msgid "mrWidget|When this merge request is ready, remove the WIP: prefix from the title to allow it to be merged"
+msgstr ""
+
+msgid "mrWidget|You are not allowed to edit this project directly. Please fork to make changes."
+msgstr ""
+
+msgid "mrWidget|You can delete the source branch now"
+msgstr ""
+
+msgid "mrWidget|You can merge this merge request manually using the"
+msgstr ""
+
+msgid "mrWidget|Your password"
+msgstr ""
+
+msgid "mrWidget|branch does not exist."
+msgstr ""
+
+msgid "mrWidget|command line"
+msgstr ""
+
+msgid "mrWidget|into"
+msgstr ""
+
+msgid "mrWidget|to be added to the merge train when the pipeline succeeds"
+msgstr ""
+
+msgid "mrWidget|to be merged automatically when the pipeline succeeds"
+msgstr ""
+
+msgid "mrWidget|to start a merge train when the pipeline succeeds"
+msgstr ""
+
+msgid "must be greater than start date"
+msgstr ""
+
+msgid "must contain only valid frameworks"
+msgstr ""
+
+msgid "my-awesome-group"
+msgstr ""
+
+msgid "n/a"
+msgstr ""
+
+msgid "need attention"
+msgstr ""
+
+msgid "needs to be between 10 minutes and 1 month"
+msgstr ""
+
+msgid "never"
+msgstr ""
+
+msgid "never expires"
+msgstr ""
+
+msgid "new merge request"
+msgstr ""
+
+msgid "no contributions"
+msgstr ""
+
+msgid "no expiration"
+msgstr ""
+
+msgid "no one can merge"
+msgstr ""
+
+msgid "none"
+msgstr ""
+
+msgid "not found"
+msgstr ""
+
+msgid "notification emails"
+msgstr ""
+
+msgid "nounSeries|%{firstItem} and %{lastItem}"
+msgstr ""
+
+msgid "nounSeries|%{item}"
+msgstr ""
+
+msgid "nounSeries|%{item}, %{nextItem}"
+msgstr ""
+
+msgid "nounSeries|%{item}, and %{lastItem}"
+msgstr ""
+
+msgid "on track"
+msgstr ""
+
+msgid "open issue"
+msgstr ""
+
+msgid "opened %{timeAgoString} by %{user}"
+msgstr ""
+
+msgid "opened %{timeAgo}"
+msgstr ""
+
+msgid "or"
+msgstr ""
+
+msgid "out of %d total test"
+msgid_plural "out of %d total tests"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "parent"
+msgid_plural "parents"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "password"
+msgstr ""
+
+msgid "pending comment"
+msgstr ""
+
+msgid "pending removal"
+msgstr ""
+
+msgid "per day"
+msgstr ""
+
+msgid "personal access token"
+msgstr ""
+
+msgid "personal access tokens"
+msgstr ""
+
+msgid "pipeline"
+msgstr ""
+
+msgid "pod_name can contain only lowercase letters, digits, '-', and '.' and must start and end with an alphanumeric character"
+msgstr ""
+
+msgid "pod_name cannot be larger than %{max_length} chars"
+msgstr ""
+
+msgid "point"
+msgid_plural "points"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "private"
+msgstr ""
+
+msgid "private key does not match certificate."
+msgstr ""
+
+msgid "processing"
+msgstr ""
+
+msgid "project"
+msgid_plural "projects"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "project access token"
+msgstr ""
+
+msgid "project access tokens"
+msgstr ""
+
+msgid "project avatar"
+msgstr ""
+
+msgid "projects"
+msgstr ""
+
+msgid "push to your repository, create pipelines, create issues or add comments. To reduce storage capacity, delete unused repositories, artifacts, wikis, issues, and pipelines."
+msgstr ""
+
+msgid "quick actions"
+msgstr ""
+
+msgid "register"
+msgstr ""
+
+msgid "relates to"
+msgstr ""
+
+msgid "released %{time}"
+msgstr ""
+
+msgid "remaining"
+msgstr ""
+
+msgid "remove"
+msgstr ""
+
+msgid "remove due date"
+msgstr ""
+
+msgid "remove weight"
+msgstr ""
+
+msgid "removed a Zoom call from this issue"
+msgstr ""
+
+msgid "rendered diff"
+msgstr ""
+
+msgid "reply"
+msgid_plural "replies"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "reset it."
+msgstr ""
+
+msgid "resolved the corresponding error and closed the issue."
+msgstr ""
+
+msgid "revised"
+msgstr ""
+
+msgid "satisfied"
+msgstr ""
+
+msgid "score"
+msgstr ""
+
+msgid "security Reports|There was an error creating the merge request"
+msgstr ""
+
+msgid "settings saved, but not activated"
+msgstr ""
+
+msgid "severity|Critical"
+msgstr ""
+
+msgid "severity|High"
+msgstr ""
+
+msgid "severity|Info"
+msgstr ""
+
+msgid "severity|Low"
+msgstr ""
+
+msgid "severity|Medium"
+msgstr ""
+
+msgid "severity|None"
+msgstr ""
+
+msgid "severity|Unknown"
+msgstr ""
+
+msgid "should be an array of %{object_name} objects"
+msgstr ""
+
+msgid "should be greater than or equal to %{access} inherited membership from group %{group_name}"
+msgstr ""
+
+msgid "show %{count} more"
+msgstr ""
+
+msgid "show fewer"
+msgstr ""
+
+msgid "show less"
+msgstr ""
+
+msgid "sign in"
+msgstr ""
+
+msgid "sort:"
+msgstr ""
+
+msgid "source"
+msgstr ""
+
+msgid "source diff"
+msgstr ""
+
+msgid "specified top is not part of the tree"
+msgstr ""
+
+msgid "spendCommand|%{slash_command} will update the sum of the time spent."
+msgstr ""
+
+msgid "started"
+msgstr ""
+
+msgid "started a discussion on %{design_link}"
+msgstr ""
+
+msgid "started on %{timebox_start_date}"
+msgstr ""
+
+msgid "starts on %{timebox_start_date}"
+msgstr ""
+
+msgid "stuck"
+msgstr ""
+
+msgid "success"
+msgstr ""
+
+msgid "suggestPipeline|1/2: Choose a template"
+msgstr ""
+
+msgid "suggestPipeline|2/2: Commit your changes"
+msgstr ""
+
+msgid "suggestPipeline|Choose %{boldStart}Code Quality%{boldEnd} to add a pipeline that tests the quality of your code."
+msgstr ""
+
+msgid "suggestPipeline|The template is ready! You can now commit it to create your first pipeline."
+msgstr ""
+
+msgid "suggestPipeline|We’re adding a GitLab CI configuration file to add a pipeline to the project. You could create it manually, but we recommend that you start with a GitLab template that works out of the box."
+msgstr ""
+
+msgid "syntax is correct"
+msgstr ""
+
+msgid "syntax is incorrect"
+msgstr ""
+
+msgid "tag name"
+msgstr ""
+
+msgid "the following issue(s)"
+msgstr ""
+
+msgid "this document"
+msgstr ""
+
+msgid "to help your contributors communicate effectively!"
+msgstr ""
+
+msgid "to list"
+msgstr ""
+
+msgid "toggle collapse"
+msgstr ""
+
+msgid "toggle dropdown"
+msgstr ""
+
+msgid "triggered"
+msgstr ""
+
+msgid "unicode domains should use IDNA encoding"
+msgstr ""
+
+msgid "updated"
+msgstr ""
+
+msgid "updated %{timeAgo}"
+msgstr ""
+
+msgid "updated %{time_ago}"
+msgstr ""
+
+msgid "uploaded"
+msgstr ""
+
+msgid "uploads"
+msgstr ""
+
+msgid "user avatar"
+msgstr ""
+
+msgid "username"
+msgstr ""
+
+msgid "uses Kubernetes clusters to deploy your code!"
+msgstr ""
+
+msgid "v%{version} published %{timeAgo}"
+msgstr ""
+
+msgid "verify ownership"
+msgstr ""
+
+msgid "version %{versionIndex}"
+msgstr ""
+
+msgid "via %{closed_via}"
+msgstr ""
+
+msgid "via merge request %{link}"
+msgstr ""
+
+msgid "view it on GitLab"
+msgstr ""
+
+msgid "view the blob"
+msgstr ""
+
+msgid "view the source"
+msgstr ""
+
+msgid "vulnerability|Add a comment"
+msgstr ""
+
+msgid "vulnerability|Add a comment or reason for dismissal"
+msgstr ""
+
+msgid "vulnerability|Add comment"
+msgstr ""
+
+msgid "vulnerability|Add comment & dismiss"
+msgstr ""
+
+msgid "vulnerability|Dismiss vulnerability"
+msgstr ""
+
+msgid "vulnerability|Save comment"
+msgstr ""
+
+msgid "vulnerability|Undo dismiss"
+msgstr ""
+
+msgid "vulnerability|dismissed"
+msgstr ""
+
+msgid "wiki page"
+msgstr ""
+
+msgid "will be released %{time}"
+msgstr ""
+
+msgid "with %{additions} additions, %{deletions} deletions."
+msgstr ""
+
+msgid "with expiry changing from %{old_expiry} to %{new_expiry}"
+msgstr ""
+
+msgid "with expiry remaining unchanged at %{old_expiry}"
+msgstr ""
+
+msgid "yaml invalid"
+msgstr ""
+
diff --git a/locale/mn_MN/gitlab.po b/locale/mn_MN/gitlab.po
index fff5b0d9572..cb4c5a007ff 100644
--- a/locale/mn_MN/gitlab.po
+++ b/locale/mn_MN/gitlab.po
@@ -2,7 +2,7 @@ msgid ""
msgstr ""
"Project-Id-Version: gitlab-ee\n"
"Report-Msgid-Bugs-To: \n"
-"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Last-Translator: \n"
"Language-Team: Mongolian\n"
"Language: mn_MN\n"
"MIME-Version: 1.0\n"
@@ -10,9 +10,11 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Crowdin-Project: gitlab-ee\n"
+"X-Crowdin-Project-ID: 288872\n"
"X-Crowdin-Language: mn\n"
"X-Crowdin-File: /master/locale/gitlab.pot\n"
-"PO-Revision-Date: 2020-06-08 15:18\n"
+"X-Crowdin-File-ID: 6\n"
+"PO-Revision-Date: 2020-07-02 01:26\n"
msgid " %{start} to %{end}"
msgstr ""
@@ -197,6 +199,11 @@ msgid_plural "%d metrics"
msgstr[0] ""
msgstr[1] ""
+msgid "%d milestone"
+msgid_plural "%d milestones"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "%d minute"
msgid_plural "%d minutes"
msgstr[0] ""
@@ -242,11 +249,6 @@ msgid_plural "%d unresolved threads"
msgstr[0] ""
msgstr[1] ""
-msgid "%d url scanned"
-msgid_plural "%d urls scanned"
-msgstr[0] ""
-msgstr[1] ""
-
msgid "%d vulnerability"
msgid_plural "%d vulnerabilities"
msgstr[0] ""
@@ -324,9 +326,15 @@ msgstr[1] ""
msgid "%{count} related %{pluralized_subject}: %{links}"
msgstr ""
+msgid "%{dashboard_path} could not be found."
+msgstr ""
+
msgid "%{days} days until tags are automatically removed"
msgstr ""
+msgid "%{deployLinkStart}Use a template to deploy to ECS%{deployLinkEnd}, or use a docker image to %{commandsLinkStart}run AWS commands in GitLab CI/CD%{commandsLinkEnd}."
+msgstr ""
+
msgid "%{description}- Sentry event: %{errorUrl}- First seen: %{firstSeen}- Last seen: %{lastSeen} %{countLabel}: %{count}%{userCountLabel}: %{userCount}"
msgstr ""
@@ -369,12 +377,18 @@ msgstr ""
msgid "%{host} sign-in from new location"
msgstr ""
-msgid "%{icon}You are about to add %{usersTag} people to the discussion. Proceed with caution."
+msgid "%{icon}You are about to add %{usersTag} people to the discussion. They will all receive a notification."
+msgstr ""
+
+msgid "%{integrations_link_start}Integrations%{link_end} enable you to make third-party applications part of your GitLab workflow. If the available integrations don't meet your needs, consider using a %{webhooks_link_start}webhook%{link_end}."
msgstr ""
msgid "%{issuableType} will be removed! Are you sure?"
msgstr ""
+msgid "%{issuesCount} issues in this group"
+msgstr ""
+
msgid "%{issuesSize} issues"
msgstr ""
@@ -384,7 +398,7 @@ msgstr ""
msgid "%{labelStart}Class:%{labelEnd} %{class}"
msgstr ""
-msgid "%{labelStart}Confidence:%{labelEnd} %{confidence}"
+msgid "%{labelStart}Evidence:%{labelEnd} %{evidence}"
msgstr ""
msgid "%{labelStart}File:%{labelEnd} %{file}"
@@ -402,6 +416,9 @@ msgstr ""
msgid "%{labelStart}Report Type:%{labelEnd} %{reportType}"
msgstr ""
+msgid "%{labelStart}Scanner:%{labelEnd} %{scanner}"
+msgstr ""
+
msgid "%{labelStart}Severity:%{labelEnd} %{severity}"
msgstr ""
@@ -426,9 +443,6 @@ msgstr ""
msgid "%{link_start}Read more%{link_end} about role permissions"
msgstr ""
-msgid "%{link} can be used for binding events when something is happening within the project."
-msgstr ""
-
msgid "%{listToShow}, and %{awardsListLength} more."
msgstr ""
@@ -462,6 +476,9 @@ msgstr ""
msgid "%{name} found %{resultsString}"
msgstr ""
+msgid "%{name} is already being used for another emoji"
+msgstr ""
+
msgid "%{name} is scheduled for %{action}"
msgstr ""
@@ -509,6 +526,9 @@ msgstr ""
msgid "%{primary} (%{secondary})"
msgstr ""
+msgid "%{ref} cannot be added: %{error}"
+msgstr ""
+
msgid "%{releases} release"
msgid_plural "%{releases} releases"
msgstr[0] ""
@@ -517,13 +537,37 @@ msgstr[1] ""
msgid "%{remaining_approvals} left"
msgstr ""
-msgid "%{retryButtonStart}Try again%{retryButtonEnd} or %{newFileButtonStart}attach a new file%{newFileButtonEnd}"
+msgid "%{reportType} %{status} detected %{critical} critical and %{high} high severity vulnerabilities out of %{total}."
+msgstr ""
+
+msgid "%{reportType} %{status} detected %{critical} critical and %{high} high severity vulnerabilities."
+msgstr ""
+
+msgid "%{reportType} %{status} detected %{critical} critical severity vulnerabilities out of %{total}."
+msgstr ""
+
+msgid "%{reportType} %{status} detected %{critical} critical severity vulnerability."
+msgid_plural "%{reportType} %{status} detected %{critical} critical severity vulnerabilities."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{reportType} %{status} detected %{high} high severity vulnerabilities out of %{total}."
msgstr ""
-msgid "%{screenreaderOnlyStart}Keyboard shorcuts%{screenreaderOnlyEnd} Disabled"
+msgid "%{reportType} %{status} detected %{high} high severity vulnerability."
+msgid_plural "%{reportType} %{status} detected %{high} high severity vulnerabilities."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{reportType} %{status} detected %{other} vulnerability."
+msgid_plural "%{reportType} %{status} detected %{other} vulnerabilities."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{reportType} %{status} detected no new vulnerabilities."
msgstr ""
-msgid "%{screenreaderOnlyStart}Keyboard shorcuts%{screenreaderOnlyEnd} Enabled"
+msgid "%{retryButtonStart}Try again%{retryButtonEnd} or %{newFileButtonStart}attach a new file%{newFileButtonEnd}"
msgstr ""
msgid "%{service_title} %{message}."
@@ -620,6 +664,12 @@ msgstr ""
msgid "%{token}..."
msgstr ""
+msgid "%{totalCpu} (%{freeSpacePercentage}%{percentSymbol} free)"
+msgstr ""
+
+msgid "%{totalMemory} (%{freeSpacePercentage}%{percentSymbol} free)"
+msgstr ""
+
msgid "%{totalWeight} total weight"
msgstr ""
@@ -650,6 +700,12 @@ msgstr ""
msgid "%{verb} %{time_spent_value} spent time."
msgstr ""
+msgid "%{webhooks_link_start}%{webhook_type}%{link_end} enable you to send notifications to web applications in response to events in a group or project."
+msgstr ""
+
+msgid "%{webhooks_link_start}%{webhook_type}%{link_end} enable you to send notifications to web applications in response to events in a group or project. We recommend using an %{integrations_link_start}integration%{link_end} in preference to a webhook."
+msgstr ""
+
msgid "'%{level}' is not a valid visibility level"
msgstr ""
@@ -676,9 +732,6 @@ msgstr ""
msgid "(No changes)"
msgstr ""
-msgid "(Show all)"
-msgstr ""
-
msgid "(check progress)"
msgstr ""
@@ -745,6 +798,9 @@ msgstr ""
msgid "0 for unlimited"
msgstr ""
+msgid "0 for unlimited, only effective with remote storage enabled."
+msgstr ""
+
msgid "1 %{type} addition"
msgid_plural "%{count} %{type} additions"
msgstr[0] ""
@@ -892,6 +948,12 @@ msgstr ""
msgid "<code>\"johnsmith@example.com\": \"johnsmith@example.com\"</code> will add \"By <a href=\"#\">johnsmith@example.com</a>\" to all issues and comments originally created by johnsmith@example.com. By default, the email address or username is masked to ensure the user's privacy. Use this option if you want to show the full email address."
msgstr ""
+msgid "<code>Masked</code> to prevent the values from being displayed in job logs (must match certain regexp requirements)."
+msgstr ""
+
+msgid "<code>Protected</code> to expose them to protected branches or tags only."
+msgstr ""
+
msgid "<namespace / project>"
msgstr ""
@@ -919,9 +981,6 @@ msgstr ""
msgid "A .NET Core console application template, customizable for any .NET Core project"
msgstr ""
-msgid "A DAG must have two dependent jobs to be visualized on this tab."
-msgstr ""
-
msgid "A GitBook site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
msgstr ""
@@ -952,6 +1011,12 @@ msgstr ""
msgid "A deleted user"
msgstr ""
+msgid "A file has been changed."
+msgstr ""
+
+msgid "A file was not found."
+msgstr ""
+
msgid "A file with '%{file_name}' already exists in %{branch} branch"
msgstr ""
@@ -1018,7 +1083,7 @@ msgstr ""
msgid "A subscription will trigger a new pipeline on the default branch of this project when a pipeline successfully completes for a new tag on the %{default_branch_docs} of the subscribed project."
msgstr ""
-msgid "A terraform report was generated in your pipelines."
+msgid "A suggestion is not applicable."
msgstr ""
msgid "A user with write access to the source branch selected this option"
@@ -1189,6 +1254,9 @@ msgstr ""
msgid "Action to take when receiving an alert."
msgstr ""
+msgid "Actions"
+msgstr ""
+
msgid "Activate"
msgstr ""
@@ -1245,6 +1313,9 @@ msgstr ""
msgid "Add LICENSE"
msgstr ""
+msgid "Add New Node"
+msgstr ""
+
msgid "Add README"
msgstr ""
@@ -1320,6 +1391,9 @@ msgstr ""
msgid "Add approval rule"
msgstr ""
+msgid "Add approvers"
+msgstr ""
+
msgid "Add bold text"
msgstr ""
@@ -1377,6 +1451,9 @@ msgstr ""
msgid "Add strikethrough text"
msgstr ""
+msgid "Add suggestion to batch"
+msgstr ""
+
msgid "Add system hook"
msgstr ""
@@ -1476,6 +1553,9 @@ msgstr ""
msgid "Admin Note"
msgstr ""
+msgid "Admin Notifications"
+msgstr ""
+
msgid "Admin Overview"
msgstr ""
@@ -1587,6 +1667,9 @@ msgstr ""
msgid "AdminSettings|Integrations configured here will automatically apply to all projects on this instance."
msgstr ""
+msgid "AdminSettings|Moved to integrations"
+msgstr ""
+
msgid "AdminSettings|No required pipeline"
msgstr ""
@@ -1817,12 +1900,18 @@ msgid_plural "Alerts"
msgstr[0] ""
msgstr[1] ""
+msgid "AlertManagement| assign yourself"
+msgstr ""
+
msgid "AlertManagement|Acknowledged"
msgstr ""
msgid "AlertManagement|Alert"
msgstr ""
+msgid "AlertManagement|Alert assignee(s): %{assignees}"
+msgstr ""
+
msgid "AlertManagement|Alert detail"
msgstr ""
@@ -1838,9 +1927,15 @@ msgstr ""
msgid "AlertManagement|All alerts"
msgstr ""
+msgid "AlertManagement|Assign To"
+msgstr ""
+
msgid "AlertManagement|Assign status"
msgstr ""
+msgid "AlertManagement|Assignee"
+msgstr ""
+
msgid "AlertManagement|Assignees"
msgstr ""
@@ -1859,9 +1954,6 @@ msgstr ""
msgid "AlertManagement|Edit"
msgstr ""
-msgid "AlertManagement|End time"
-msgstr ""
-
msgid "AlertManagement|Events"
msgstr ""
@@ -1880,10 +1972,13 @@ msgstr ""
msgid "AlertManagement|More information"
msgstr ""
+msgid "AlertManagement|No Matching Results"
+msgstr ""
+
msgid "AlertManagement|No alert data to display."
msgstr ""
-msgid "AlertManagement|No alerts available to display. If you think you're seeing this message in error, refresh the page."
+msgid "AlertManagement|No alerts available to display. See %{linkStart}enabling alert management%{linkEnd} for more information on adding alerts to the list."
msgstr ""
msgid "AlertManagement|No alerts to display."
@@ -1892,6 +1987,9 @@ msgstr ""
msgid "AlertManagement|None"
msgstr ""
+msgid "AlertManagement|None -"
+msgstr ""
+
msgid "AlertManagement|Open"
msgstr ""
@@ -1928,9 +2026,18 @@ msgstr ""
msgid "AlertManagement|There was an error displaying the alerts. Confirm your endpoint's configuration details to ensure alerts appear."
msgstr ""
+msgid "AlertManagement|There was an error while updating the assignee(s) list. Please try again."
+msgstr ""
+
+msgid "AlertManagement|There was an error while updating the assignee(s) of the alert. Please try again."
+msgstr ""
+
msgid "AlertManagement|There was an error while updating the status of the alert. Please try again."
msgstr ""
+msgid "AlertManagement|This assignee cannot be assigned to this alert."
+msgstr ""
+
msgid "AlertManagement|Tool"
msgstr ""
@@ -2078,6 +2185,9 @@ msgstr ""
msgid "Allow users to request access (if visibility is public or internal)"
msgstr ""
+msgid "Allowed Geo IP"
+msgstr ""
+
msgid "Allowed email domain restriction only permitted for top-level groups"
msgstr ""
@@ -2111,6 +2221,9 @@ msgstr ""
msgid "Amazon Web Services"
msgstr ""
+msgid "Amazon Web Services Logo"
+msgstr ""
+
msgid "Amazon authentication is not %{link_start}correctly configured%{link_end}. Ask your GitLab administrator if you want to use this service."
msgstr ""
@@ -2168,10 +2281,16 @@ msgstr ""
msgid "An error occurred when updating the issue weight"
msgstr ""
+msgid "An error occurred while acknowledging the notification. Refresh the page and try again."
+msgstr ""
+
+msgid "An error occurred while adding approvers"
+msgstr ""
+
msgid "An error occurred while adding formatted title for epic"
msgstr ""
-msgid "An error occurred while checking group path"
+msgid "An error occurred while checking group path. Please refresh and try again."
msgstr ""
msgid "An error occurred while committing your changes."
@@ -2315,7 +2434,7 @@ msgstr ""
msgid "An error occurred while loading milestones"
msgstr ""
-msgid "An error occurred while loading terraform report"
+msgid "An error occurred while loading project creation UI"
msgstr ""
msgid "An error occurred while loading the data. Please try again."
@@ -2369,6 +2488,9 @@ msgstr ""
msgid "An error occurred while rendering preview broadcast message"
msgstr ""
+msgid "An error occurred while rendering the editor"
+msgstr ""
+
msgid "An error occurred while reordering issues."
msgstr ""
@@ -2465,7 +2587,7 @@ msgstr ""
msgid "Analyze your dependencies for known vulnerabilities."
msgstr ""
-msgid "Analyze your source code and git history for secrets"
+msgid "Analyze your source code and git history for secrets."
msgstr ""
msgid "Analyze your source code for known vulnerabilities."
@@ -2579,12 +2701,18 @@ msgstr ""
msgid "Apply suggestion"
msgstr ""
+msgid "Apply suggestions"
+msgstr ""
+
msgid "Apply template"
msgstr ""
msgid "Apply this approval rule to any branch or a specific protected branch."
msgstr ""
+msgid "Applying"
+msgstr ""
+
msgid "Applying a template will replace the existing issue description. Any changes you have made will be lost."
msgstr ""
@@ -2597,12 +2725,18 @@ msgstr ""
msgid "Applying multiple commands"
msgstr ""
-msgid "Applying suggestion"
+msgid "Applying suggestion..."
+msgstr ""
+
+msgid "Applying suggestions..."
msgstr ""
msgid "Approval rules"
msgstr ""
+msgid "Approval rules reset to project defaults"
+msgstr ""
+
msgid "ApprovalRuleRemove|%d member"
msgid_plural "ApprovalRuleRemove|%d members"
msgstr[0] ""
@@ -2656,6 +2790,9 @@ msgstr ""
msgid "Approved"
msgstr ""
+msgid "Approved MRs"
+msgstr ""
+
msgid "Approved by: "
msgstr ""
@@ -2869,6 +3006,9 @@ msgstr ""
msgid "Assign"
msgstr ""
+msgid "Assign Iteration"
+msgstr ""
+
msgid "Assign custom color like #FF0000"
msgstr ""
@@ -2887,6 +3027,9 @@ msgstr ""
msgid "Assign to"
msgstr ""
+msgid "Assign to commenting user"
+msgstr ""
+
msgid "Assign yourself to these issues"
msgstr ""
@@ -2913,6 +3056,9 @@ msgid_plural "%d Assignees"
msgstr[0] ""
msgstr[1] ""
+msgid "Assignee has no permissions"
+msgstr ""
+
msgid "Assignee lists not available with your current license"
msgstr ""
@@ -2922,6 +3068,9 @@ msgstr ""
msgid "Assignee(s)"
msgstr ""
+msgid "Assignees"
+msgstr ""
+
msgid "Assigns %{assignee_users_sentence}."
msgstr ""
@@ -2957,6 +3106,9 @@ msgstr ""
msgid "Audit Events is a way to keep track of important events that happened in GitLab."
msgstr ""
+msgid "Audit Log"
+msgstr ""
+
msgid "AuditEvents|(removed)"
msgstr ""
@@ -2993,6 +3145,9 @@ msgstr ""
msgid "AuditLogs|IP Address"
msgstr ""
+msgid "AuditLogs|Member Events"
+msgstr ""
+
msgid "AuditLogs|No matching %{type} found."
msgstr ""
@@ -3275,9 +3430,6 @@ msgstr ""
msgid "Badges|e.g. %{exampleUrl}"
msgstr ""
-msgid "Badge|New"
-msgstr ""
-
msgid "Balsamiq file could not be loaded."
msgstr ""
@@ -3326,6 +3478,12 @@ msgstr ""
msgid "Below you will find all the groups that are public."
msgstr ""
+msgid "Beta"
+msgstr ""
+
+msgid "Bi-weekly code coverage"
+msgstr ""
+
msgid "Billing"
msgstr ""
@@ -3386,9 +3544,15 @@ msgstr ""
msgid "Bitbucket Server Import"
msgstr ""
+msgid "Bitbucket Server import"
+msgstr ""
+
msgid "Bitbucket import"
msgstr ""
+msgid "Blame"
+msgstr ""
+
msgid "Blocked"
msgstr ""
@@ -3401,9 +3565,6 @@ msgstr ""
msgid "Blog"
msgstr ""
-msgid "Blue helpers indicate an action to be taken."
-msgstr ""
-
msgid "Board name"
msgstr ""
@@ -3602,6 +3763,9 @@ msgstr ""
msgid "Broadcast Message was successfully updated."
msgstr ""
+msgid "Broadcast Messages"
+msgstr ""
+
msgid "Browse Directory"
msgstr ""
@@ -3656,6 +3820,9 @@ msgstr ""
msgid "By %{user_name}"
msgstr ""
+msgid "By URL"
+msgstr ""
+
msgid "By default GitLab sends emails in HTML and plain text formats so mail clients can choose what format to use. Disable this option if you only want to send emails in plain text format."
msgstr ""
@@ -3761,6 +3928,9 @@ msgstr ""
msgid "Can override approvers and approvals required per merge request"
msgstr ""
+msgid "Can't apply as this line has changed or the suggestion already matches its content."
+msgstr ""
+
msgid "Can't create snippet: %{err}"
msgstr ""
@@ -3947,9 +4117,6 @@ msgstr ""
msgid "Changes are still tracked. Useful for cluster/index migrations."
msgstr ""
-msgid "Changes are unknown"
-msgstr ""
-
msgid "Changes suppressed. Click to show."
msgstr ""
@@ -4256,15 +4423,9 @@ msgstr ""
msgid "Choose what content you want to see on a group’s overview page"
msgstr ""
-msgid "Choose which groups you wish to synchronize to this secondary node"
-msgstr ""
-
msgid "Choose which repositories you want to connect and run CI/CD pipelines."
msgstr ""
-msgid "Choose which shards you wish to synchronize to this secondary node"
-msgstr ""
-
msgid "Choose your framework"
msgstr ""
@@ -4562,9 +4723,15 @@ msgstr ""
msgid "Cluster does not exist"
msgstr ""
+msgid "Cluster is required for Stages::ClusterEndpointInserter"
+msgstr ""
+
msgid "Cluster level"
msgstr ""
+msgid "Cluster type must be specificed for Stages::ClusterEndpointInserter"
+msgstr ""
+
msgid "ClusterIntegration| %{custom_domain_start}More information%{custom_domain_end}."
msgstr ""
@@ -4676,7 +4843,7 @@ msgstr ""
msgid "ClusterIntegration|Cert-Manager"
msgstr ""
-msgid "ClusterIntegration|Cert-Manager is a native Kubernetes certificate management controller that helps with issuing certificates. Installing Cert-Manager on your cluster will issue a certificate by %{letsEncrypt} and ensure that certificates are valid and up-to-date."
+msgid "ClusterIntegration|Cert-Manager is a native Kubernetes certificate management controller that helps with issuing certificates. Installing Cert-Manager on your cluster will issue a certificate by %{linkStart}Let's Encrypt%{linkEnd} and ensure that certificates are valid and up-to-date."
msgstr ""
msgid "ClusterIntegration|Certificate Authority bundle (PEM format)"
@@ -4703,9 +4870,6 @@ msgstr ""
msgid "ClusterIntegration|Clear the local cache of namespace and service accounts. This is necessary if your integration has become out of sync. The cache is repopulated during the next CI job that requires namespace and service accounts."
msgstr ""
-msgid "ClusterIntegration|Cloud Run"
-msgstr ""
-
msgid "ClusterIntegration|Cluster being created"
msgstr ""
@@ -4790,7 +4954,7 @@ msgstr ""
msgid "ClusterIntegration|Crossplane"
msgstr ""
-msgid "ClusterIntegration|Crossplane enables declarative provisioning of managed services from your cloud of choice using %{codeStart}kubectl%{codeEnd} or %{gitlabIntegrationLink}. Crossplane runs inside your Kubernetes cluster and supports secure connectivity and secrets management between app containers and the cloud services they depend on."
+msgid "ClusterIntegration|Crossplane enables declarative provisioning of managed services from your cloud of choice using %{codeStart}kubectl%{codeEnd} or %{linkStart}GitLab Integration%{linkEnd}. Crossplane runs inside your Kubernetes cluster and supports secure connectivity and secrets management between app containers and the cloud services they depend on."
msgstr ""
msgid "ClusterIntegration|Deletes all GitLab resources attached to this cluster during removal"
@@ -4874,9 +5038,6 @@ msgstr ""
msgid "ClusterIntegration|GitLab-managed cluster"
msgstr ""
-msgid "ClusterIntegration|Gitlab Integration"
-msgstr ""
-
msgid "ClusterIntegration|Global default"
msgstr ""
@@ -4922,7 +5083,7 @@ msgstr ""
msgid "ClusterIntegration|Ingress gives you a way to route requests to services based on the request host or path, centralizing a number of services into a single entrypoint."
msgstr ""
-msgid "ClusterIntegration|Installing Ingress may incur additional costs. Learn more about %{pricingLink}."
+msgid "ClusterIntegration|Installing Ingress may incur additional costs. Learn more about %{linkStart}pricing%{linkEnd}."
msgstr ""
msgid "ClusterIntegration|Instance cluster"
@@ -4937,7 +5098,7 @@ msgstr ""
msgid "ClusterIntegration|Issuer Email"
msgstr ""
-msgid "ClusterIntegration|Issuers represent a certificate authority. You must provide an email address for your Issuer. "
+msgid "ClusterIntegration|Issuers represent a certificate authority. You must provide an email address for your Issuer."
msgstr ""
msgid "ClusterIntegration|Jupyter Hostname"
@@ -5009,9 +5170,6 @@ msgstr ""
msgid "ClusterIntegration|Learn more about instance Kubernetes clusters"
msgstr ""
-msgid "ClusterIntegration|Let's Encrypt"
-msgstr ""
-
msgid "ClusterIntegration|Loading IAM Roles"
msgstr ""
@@ -5123,7 +5281,7 @@ msgstr ""
msgid "ClusterIntegration|Prometheus"
msgstr ""
-msgid "ClusterIntegration|Prometheus is an open-source monitoring system with %{gitlabIntegrationLink} to monitor deployed applications."
+msgid "ClusterIntegration|Prometheus is an open-source monitoring system with %{linkStart}GitLab Integration%{linkEnd} to monitor deployed applications."
msgstr ""
msgid "ClusterIntegration|Provider details"
@@ -5279,10 +5437,10 @@ msgstr ""
msgid "ClusterIntegration|Select zone to choose machine type"
msgstr ""
-msgid "ClusterIntegration|Send Cilium Logs"
+msgid "ClusterIntegration|Send Container Network Policies Logs"
msgstr ""
-msgid "ClusterIntegration|Send ModSecurity Logs"
+msgid "ClusterIntegration|Send Web Application Firewall Logs"
msgstr ""
msgid "ClusterIntegration|Service Token"
@@ -5450,15 +5608,12 @@ msgstr ""
msgid "ClusterIntegration|documentation"
msgstr ""
-msgid "ClusterIntegration|installed via %{installed_via}"
+msgid "ClusterIntegration|installed via %{linkStart}Cloud Run%{linkEnd}"
msgstr ""
msgid "ClusterIntegration|meets the requirements"
msgstr ""
-msgid "ClusterIntegration|pricing"
-msgstr ""
-
msgid "ClusterIntegration|sign up"
msgstr ""
@@ -5495,6 +5650,15 @@ msgstr ""
msgid "Code"
msgstr ""
+msgid "Code Coverage: %{coveragePercentage}%{percentSymbol}"
+msgstr ""
+
+msgid "Code Coverage| Empty code coverage data"
+msgstr ""
+
+msgid "Code Coverage|Couldn't fetch the code coverage data"
+msgstr ""
+
msgid "Code Owners"
msgstr ""
@@ -5549,7 +5713,7 @@ msgstr ""
msgid "Collapse approvers"
msgstr ""
-msgid "Collapse child epics"
+msgid "Collapse milestones"
msgstr ""
msgid "Collapse replies"
@@ -5567,6 +5731,9 @@ msgstr ""
msgid "Coming soon"
msgstr ""
+msgid "Comma-separated, e.g. '1.1.1.1, 2.2.2.0/24'"
+msgstr ""
+
msgid "Command"
msgstr ""
@@ -5737,6 +5904,12 @@ msgstr ""
msgid "Compliance framework (optional)"
msgstr ""
+msgid "Compliance frameworks"
+msgstr ""
+
+msgid "ComplianceDashboard|created by:"
+msgstr ""
+
msgid "ComplianceFramework|GDPR"
msgstr ""
@@ -5875,6 +6048,9 @@ msgstr ""
msgid "Connection timed out"
msgstr ""
+msgid "Connection timeout"
+msgstr ""
+
msgid "Contact sales to upgrade"
msgstr ""
@@ -5930,9 +6106,6 @@ msgstr ""
msgid "ContainerRegistry|CLI Commands"
msgstr ""
-msgid "ContainerRegistry|Compressed Size"
-msgstr ""
-
msgid "ContainerRegistry|Container Registry"
msgstr ""
@@ -5948,6 +6121,9 @@ msgstr ""
msgid "ContainerRegistry|Copy push command"
msgstr ""
+msgid "ContainerRegistry|Delete selected"
+msgstr ""
+
msgid "ContainerRegistry|Docker connection error"
msgstr ""
@@ -5981,16 +6157,16 @@ msgstr ""
msgid "ContainerRegistry|If you are not already logged in, you need to authenticate to the Container Registry by using your GitLab username and password. If you have %{twofaDocLinkStart}Two-Factor Authentication%{twofaDocLinkEnd} enabled, use a %{personalAccessTokensDocLinkStart}Personal Access Token%{personalAccessTokensDocLinkEnd} instead of a password."
msgstr ""
-msgid "ContainerRegistry|Image ID"
+msgid "ContainerRegistry|Image ID: %{imageId}"
msgstr ""
msgid "ContainerRegistry|Image Repositories"
msgstr ""
-msgid "ContainerRegistry|Keep and protect the images that matter most."
+msgid "ContainerRegistry|Image tags"
msgstr ""
-msgid "ContainerRegistry|Last Updated"
+msgid "ContainerRegistry|Keep and protect the images that matter most."
msgstr ""
msgid "ContainerRegistry|Login"
@@ -6005,6 +6181,9 @@ msgstr ""
msgid "ContainerRegistry|Please contact your administrator."
msgstr ""
+msgid "ContainerRegistry|Published %{timeInfo}"
+msgstr ""
+
msgid "ContainerRegistry|Push an image"
msgstr ""
@@ -6020,9 +6199,6 @@ msgstr ""
msgid "ContainerRegistry|Remove repository"
msgstr ""
-msgid "ContainerRegistry|Remove selected tags"
-msgstr ""
-
msgid "ContainerRegistry|Remove tag"
msgid_plural "ContainerRegistry|Remove tags"
msgstr[0] ""
@@ -6052,9 +6228,6 @@ msgstr ""
msgid "ContainerRegistry|Sorry, your filter produced no results."
msgstr ""
-msgid "ContainerRegistry|Tag"
-msgstr ""
-
msgid "ContainerRegistry|Tag expiration policy"
msgstr ""
@@ -6199,21 +6372,6 @@ msgstr ""
msgid "Control the display of third party offers."
msgstr ""
-msgid "Control the maximum concurrency of LFS/attachment backfill for this secondary node"
-msgstr ""
-
-msgid "Control the maximum concurrency of container repository operations for this Geo node"
-msgstr ""
-
-msgid "Control the maximum concurrency of repository backfill for this secondary node"
-msgstr ""
-
-msgid "Control the maximum concurrency of verification operations for this Geo node"
-msgstr ""
-
-msgid "Control the minimum interval in days that a repository should be reverified for this primary node"
-msgstr ""
-
msgid "Cookie domain"
msgstr ""
@@ -6355,6 +6513,9 @@ msgstr ""
msgid "Could not find design."
msgstr ""
+msgid "Could not find iteration"
+msgstr ""
+
msgid "Could not remove the trigger."
msgstr ""
@@ -6391,6 +6552,12 @@ msgstr ""
msgid "Coverage"
msgstr ""
+msgid "Coverage Fuzzing"
+msgstr ""
+
+msgid "Crash State"
+msgstr ""
+
msgid "Create"
msgstr ""
@@ -6439,6 +6606,9 @@ msgstr ""
msgid "Create a personal access token on your account to pull or push via %{protocol}."
msgstr ""
+msgid "Create a project pre-populated with the necessary files to get you started quickly."
+msgstr ""
+
msgid "Create an account using:"
msgstr ""
@@ -6607,6 +6777,9 @@ msgstr ""
msgid "Creates branch '%{branch_name}' and a merge request to resolve this issue."
msgstr ""
+msgid "Creating"
+msgstr ""
+
msgid "Creating epic"
msgstr ""
@@ -6658,7 +6831,10 @@ msgstr ""
msgid "Current vulnerabilities count"
msgstr ""
-msgid "CurrentUser|Buy CI minutes"
+msgid "CurrentUser|Buy Pipeline minutes"
+msgstr ""
+
+msgid "CurrentUser|One of your groups is running out"
msgstr ""
msgid "CurrentUser|Profile"
@@ -6673,6 +6849,9 @@ msgstr ""
msgid "CurrentUser|Upgrade"
msgstr ""
+msgid "Custom Attributes"
+msgstr ""
+
msgid "Custom CI configuration path"
msgstr ""
@@ -6936,12 +7115,18 @@ msgstr ""
msgid "DAG"
msgstr ""
+msgid "DAG visualization requires at least 3 dependent jobs."
+msgstr ""
+
msgid "DNS"
msgstr ""
msgid "Dashboard"
msgstr ""
+msgid "Dashboard uid not found"
+msgstr ""
+
msgid "DashboardProjects|All"
msgstr ""
@@ -6966,6 +7151,9 @@ msgstr ""
msgid "Data is still calculating..."
msgstr ""
+msgid "Datasource name not found"
+msgstr ""
+
msgid "Date"
msgstr ""
@@ -7146,6 +7334,9 @@ msgstr ""
msgid "Delete this attachment"
msgstr ""
+msgid "Delete user list"
+msgstr ""
+
msgid "Delete variable"
msgstr ""
@@ -7462,6 +7653,9 @@ msgstr ""
msgid "Deploying to"
msgstr ""
+msgid "Deploying to AWS is easy with GitLab"
+msgstr ""
+
msgid "Deployment Frequency"
msgstr ""
@@ -7824,6 +8018,9 @@ msgstr ""
msgid "Dismissed on pipeline %{pipelineLink} at %{projectLink}"
msgstr ""
+msgid "Display alerts from all your monitoring tools directly within GitLab."
+msgstr ""
+
msgid "Display name"
msgstr ""
@@ -7881,9 +8078,6 @@ msgstr ""
msgid "Don't show again"
msgstr ""
-msgid "Don't worry, you can access this tour by clicking on the help icon in the top right corner and choose <strong>Learn GitLab</strong>."
-msgstr ""
-
msgid "Done"
msgstr ""
@@ -8010,6 +8204,9 @@ msgstr ""
msgid "Edit Release"
msgstr ""
+msgid "Edit Slack integration"
+msgstr ""
+
msgid "Edit Snippet"
msgstr ""
@@ -8049,6 +8246,9 @@ msgstr ""
msgid "Edit issues"
msgstr ""
+msgid "Edit iteration"
+msgstr ""
+
msgid "Edit public deploy key"
msgstr ""
@@ -8097,6 +8297,9 @@ msgstr ""
msgid "Email"
msgstr ""
+msgid "Email Notification"
+msgstr ""
+
msgid "Email address"
msgstr ""
@@ -8109,6 +8312,9 @@ msgstr ""
msgid "Email not verified. Please verify your email in Salesforce."
msgstr ""
+msgid "Email notification for unknown sign-ins"
+msgstr ""
+
msgid "Email patch"
msgstr ""
@@ -8340,6 +8546,9 @@ msgstr ""
msgid "Enforce DNS rebinding attack protection"
msgstr ""
+msgid "Enforce personal access token expiration"
+msgstr ""
+
msgid "Ensure connectivity is available from the GitLab server to the Prometheus server"
msgstr ""
@@ -8409,6 +8618,9 @@ msgstr ""
msgid "Enter the name of your application, and we'll return a unique %{type}."
msgstr ""
+msgid "Enter weights for storages for new repositories."
+msgstr ""
+
msgid "Enter your password to approve"
msgstr ""
@@ -8418,15 +8630,27 @@ msgstr ""
msgid "Environment does not have deployments"
msgstr ""
+msgid "Environment is required for Stages::MetricEndpointInserter"
+msgstr ""
+
+msgid "Environment is required for Stages::VariableEndpointInserter"
+msgstr ""
+
msgid "Environment scope"
msgstr ""
+msgid "Environment variables are applied to all project environments in this instance via the Runner. You can use environment variables for passwords, secret keys, etc. Make variables available to the running application by prepending the variable key with <code>K8S_SECRET_</code>. You can set variables to be:"
+msgstr ""
+
msgid "Environment variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. Additionally, they can be masked so they are hidden in job logs, though they must match certain regexp requirements to do so. You can use environment variables for passwords, secret keys, or whatever you want."
msgstr ""
msgid "Environment variables are configured by your administrator to be %{link_start}protected%{link_end} by default"
msgstr ""
+msgid "Environment variables on this GitLab instance are configured to be %{link_start}protected%{link_end} by default"
+msgstr ""
+
msgid "Environment:"
msgstr ""
@@ -8958,6 +9182,9 @@ msgstr ""
msgid "EventFilterBy|Filter by comments"
msgstr ""
+msgid "EventFilterBy|Filter by designs"
+msgstr ""
+
msgid "EventFilterBy|Filter by epic events"
msgstr ""
@@ -9090,15 +9317,15 @@ msgstr ""
msgid "Expand approvers"
msgstr ""
-msgid "Expand child epics"
-msgstr ""
-
msgid "Expand down"
msgstr ""
msgid "Expand dropdown"
msgstr ""
+msgid "Expand milestones"
+msgstr ""
+
msgid "Expand sidebar"
msgstr ""
@@ -9114,6 +9341,9 @@ msgstr ""
msgid "Expiration date"
msgstr ""
+msgid "Expiration not enforced"
+msgstr ""
+
msgid "Expiration policy for the Container Registry is a perfect solution for keeping the Registry space down while still enjoying the full power of GitLab CI/CD."
msgstr ""
@@ -9234,6 +9464,9 @@ msgstr ""
msgid "Failed Jobs"
msgstr ""
+msgid "Failed on"
+msgstr ""
+
msgid "Failed to add a Zoom meeting"
msgstr ""
@@ -9294,13 +9527,19 @@ msgstr ""
msgid "Failed to install."
msgstr ""
+msgid "Failed to load assignees. Please try again."
+msgstr ""
+
+msgid "Failed to load authors. Please try again."
+msgstr ""
+
msgid "Failed to load emoji list."
msgstr ""
msgid "Failed to load error details from Sentry."
msgstr ""
-msgid "Failed to load errors from Sentry. Error message: %{errorMessage}"
+msgid "Failed to load errors from Sentry."
msgstr ""
msgid "Failed to load group activity metrics. Please try again."
@@ -9381,6 +9620,9 @@ msgstr ""
msgid "Failed to set due date because the date format is invalid."
msgstr ""
+msgid "Failed to set iteration on this issue. Please try again."
+msgstr ""
+
msgid "Failed to signing using smartcard authentication"
msgstr ""
@@ -9603,13 +9845,10 @@ msgstr ""
msgid "FeatureFlags|Target environments"
msgstr ""
-msgid "FeatureFlags|There are no active feature flags"
-msgstr ""
-
-msgid "FeatureFlags|There are no inactive feature flags"
+msgid "FeatureFlags|There was an error fetching the feature flags."
msgstr ""
-msgid "FeatureFlags|There was an error fetching the feature flags."
+msgid "FeatureFlags|There was an error retrieving user lists"
msgstr ""
msgid "FeatureFlags|Try again in a few moments or contact your support team."
@@ -9621,9 +9860,18 @@ msgstr ""
msgid "FeatureFlag|Delete strategy"
msgstr ""
+msgid "FeatureFlag|List"
+msgstr ""
+
msgid "FeatureFlag|Percentage"
msgstr ""
+msgid "FeatureFlag|Select a user list"
+msgstr ""
+
+msgid "FeatureFlag|There are no configured user lists"
+msgstr ""
+
msgid "FeatureFlag|Type"
msgstr ""
@@ -9780,6 +10028,12 @@ msgstr ""
msgid "Filter..."
msgstr ""
+msgid "Find File"
+msgstr ""
+
+msgid "Find bugs in your code with coverage-guided fuzzing"
+msgstr ""
+
msgid "Find by path"
msgstr ""
@@ -10044,6 +10298,9 @@ msgstr ""
msgid "Geo allows you to replicate your GitLab instance to other geographical locations."
msgstr ""
+msgid "Geo nodes are paused using a command run on the node"
+msgstr ""
+
msgid "GeoNodeStatusEvent|%{timeAgoStr} (%{pendingEvents} events)"
msgstr ""
@@ -10128,6 +10385,9 @@ msgstr ""
msgid "GeoNodes|Not checksummed"
msgstr ""
+msgid "GeoNodes|Package files"
+msgstr ""
+
msgid "GeoNodes|Pausing replication stops the sync process. Are you sure?"
msgstr ""
@@ -10146,6 +10406,9 @@ msgstr ""
msgid "GeoNodes|Replication slots"
msgstr ""
+msgid "GeoNodes|Replication status"
+msgstr ""
+
msgid "GeoNodes|Repositories"
msgstr ""
@@ -10356,6 +10619,9 @@ msgstr ""
msgid "Geo|Synchronization failed - %{error}"
msgstr ""
+msgid "Geo|Synchronization of %{itemTitle} is disabled."
+msgstr ""
+
msgid "Geo|The database is currently %{db_lag} behind the primary node."
msgstr ""
@@ -10668,6 +10934,9 @@ msgstr ""
msgid "Go to %{link_to_google_takeout}."
msgstr ""
+msgid "Go to <strong>Issues</strong> > <strong>Boards</strong> to access your personalized learning issue board."
+msgstr ""
+
msgid "Go to Pipelines"
msgstr ""
@@ -10767,9 +11036,6 @@ msgstr ""
msgid "Go to your snippets"
msgstr ""
-msgid "Golden Tanuki"
-msgstr ""
-
msgid "Google Cloud Platform"
msgstr ""
@@ -10791,6 +11057,9 @@ msgstr ""
msgid "Grafana URL"
msgstr ""
+msgid "Grafana response contains invalid json"
+msgstr ""
+
msgid "GrafanaIntegration|API Token"
msgstr ""
@@ -10839,9 +11108,6 @@ msgstr ""
msgid "Group %{group_name} was successfully created."
msgstr ""
-msgid "Group '%{group_name}' is being imported."
-msgstr ""
-
msgid "Group Audit Events"
msgstr ""
@@ -10878,9 +11144,6 @@ msgstr ""
msgid "Group by:"
msgstr ""
-msgid "Group could not be imported: %{errors}"
-msgstr ""
-
msgid "Group description"
msgstr ""
@@ -10908,9 +11171,15 @@ msgstr ""
msgid "Group has not been marked for deletion"
msgstr ""
+msgid "Group import could not be scheduled"
+msgstr ""
+
msgid "Group info:"
msgstr ""
+msgid "Group is required when cluster_type is :group"
+msgstr ""
+
msgid "Group maintainers can register group runners in the %{link}"
msgstr ""
@@ -10974,6 +11243,24 @@ msgstr ""
msgid "GroupActivyMetrics|Recent activity (last 90 days)"
msgstr ""
+msgid "GroupImport|Failed to import group."
+msgstr ""
+
+msgid "GroupImport|Group '%{group_name}' is being imported."
+msgstr ""
+
+msgid "GroupImport|Group could not be imported: %{errors}"
+msgstr ""
+
+msgid "GroupImport|Please wait while we import the group for you. Refresh at will."
+msgstr ""
+
+msgid "GroupImport|The group was successfully imported."
+msgstr ""
+
+msgid "GroupImport|Unable to process group import file"
+msgstr ""
+
msgid "GroupRoadmap|%{dateWord} – No end date"
msgstr ""
@@ -11241,6 +11528,9 @@ msgstr ""
msgid "Groups (%{groups})"
msgstr ""
+msgid "Groups and projects"
+msgstr ""
+
msgid "Groups can also be nested by creating %{subgroup_docs_link_start}subgroups%{subgroup_docs_link_end}."
msgstr ""
@@ -11286,6 +11576,33 @@ msgstr ""
msgid "GroupsEmptyState|You can manage your group member’s permissions and access to each project in the group."
msgstr ""
+msgid "GroupsNew|Contact an administrator to enable options for importing your group."
+msgstr ""
+
+msgid "GroupsNew|Create"
+msgstr ""
+
+msgid "GroupsNew|Create group"
+msgstr ""
+
+msgid "GroupsNew|GitLab group export"
+msgstr ""
+
+msgid "GroupsNew|Import"
+msgstr ""
+
+msgid "GroupsNew|Import group"
+msgstr ""
+
+msgid "GroupsNew|My Awesome Group"
+msgstr ""
+
+msgid "GroupsNew|No import options available"
+msgstr ""
+
+msgid "GroupsNew|To copy a GitLab group between installations, navigate to the group settings page for the original installation, generate an export file, and upload it here."
+msgstr ""
+
msgid "GroupsTree|Are you sure you want to leave the \"%{fullName}\" group?"
msgstr ""
@@ -11388,6 +11705,9 @@ msgstr ""
msgid "Helps reduce request volume for protected paths"
msgstr ""
+msgid "Here are all your projects in your group, including the one you just created. To start, let’s take a look at your personalized learning project which will help you learn about GitLab at your own pace."
+msgstr ""
+
msgid "Here you will find recent merge request activity"
msgstr ""
@@ -11402,6 +11722,9 @@ msgid_plural "Hide charts"
msgstr[0] ""
msgstr[1] ""
+msgid "Hide details"
+msgstr ""
+
msgid "Hide file browser"
msgstr ""
@@ -11411,6 +11734,9 @@ msgstr ""
msgid "Hide host keys manual input"
msgstr ""
+msgid "Hide list"
+msgstr ""
+
msgid "Hide marketing-related entries from help"
msgstr ""
@@ -11431,9 +11757,6 @@ msgstr[1] ""
msgid "Hide values"
msgstr ""
-msgid "Hiding all labels"
-msgstr ""
-
msgid "High or unknown vulnerabilities present"
msgstr ""
@@ -11518,6 +11841,9 @@ msgstr ""
msgid "ID:"
msgstr ""
+msgid "IDE"
+msgstr ""
+
msgid "IDE|Allow live previews of JavaScript projects in the Web IDE using CodeSandbox Live Preview."
msgstr ""
@@ -11608,10 +11934,10 @@ msgstr ""
msgid "If enabled"
msgstr ""
-msgid "If enabled, access to projects will be validated on an external service using their classification label."
+msgid "If enabled, GitLab will handle Object Storage replication using Geo. %{linkStart}More information%{linkEnd}"
msgstr ""
-msgid "If enabled, and if object storage is enabled, GitLab will handle Object Storage replication using Geo"
+msgid "If enabled, access to projects will be validated on an external service using their classification label."
msgstr ""
msgid "If there is no previous license or if the previous license has expired, some GitLab functionality will be blocked until a new, valid license is uploaded."
@@ -11973,6 +12299,9 @@ msgid_plural "Instances"
msgstr[0] ""
msgstr[1] ""
+msgid "Instance Configuration"
+msgstr ""
+
msgid "Instance Statistics visibility"
msgstr ""
@@ -11994,9 +12323,6 @@ msgstr ""
msgid "Integrations"
msgstr ""
-msgid "Integrations allow you to integrate GitLab with other applications"
-msgstr ""
-
msgid "Integrations|All details"
msgstr ""
@@ -12108,6 +12434,9 @@ msgstr ""
msgid "Invalid query"
msgstr ""
+msgid "Invalid repository bundle for snippet with id %{snippet_id}"
+msgstr ""
+
msgid "Invalid repository path"
msgstr ""
@@ -12126,6 +12455,9 @@ msgstr ""
msgid "Invalid two-factor code."
msgstr ""
+msgid "Invalid yaml"
+msgstr ""
+
msgid "Invitation"
msgstr ""
@@ -12345,15 +12677,24 @@ msgstr ""
msgid "It seems like the Dependency Scanning job ran successfully, but no dependencies have been detected in your project."
msgstr ""
+msgid "It seems that there is currently no available data for code coverage"
+msgstr ""
+
msgid "It's you"
msgstr ""
+msgid "Iteration"
+msgstr ""
+
msgid "Iteration changed to"
msgstr ""
msgid "Iteration removed"
msgstr ""
+msgid "Iteration updated"
+msgstr ""
+
msgid "Iterations"
msgstr ""
@@ -12384,12 +12725,18 @@ msgstr ""
msgid "January"
msgstr ""
+msgid "Jira Issues"
+msgstr ""
+
msgid "Jira import is already running."
msgstr ""
msgid "Jira integration not configured."
msgstr ""
+msgid "Jira project key is not configured"
+msgstr ""
+
msgid "Jira project: %{importProject}"
msgstr ""
@@ -12570,6 +12917,9 @@ msgstr ""
msgid "Keep divergent refs"
msgstr ""
+msgid "Kerberos access denied"
+msgstr ""
+
msgid "Key"
msgstr ""
@@ -12579,10 +12929,13 @@ msgstr ""
msgid "Key: %{key}"
msgstr ""
-msgid "Keyboard Shortcuts"
+msgid "Keyboard shortcuts"
msgstr ""
-msgid "Keyboard shortcuts"
+msgid "Keys"
+msgstr ""
+
+msgid "Ki"
msgstr ""
msgid "Kubernetes"
@@ -12624,6 +12977,9 @@ msgstr ""
msgid "LDAP"
msgstr ""
+msgid "LDAP Synchronization"
+msgstr ""
+
msgid "LDAP settings"
msgstr ""
@@ -12848,12 +13204,18 @@ msgstr ""
msgid "Learn more about custom project templates"
msgstr ""
+msgid "Learn more about deploying to AWS"
+msgstr ""
+
msgid "Learn more about deploying to a cluster"
msgstr ""
msgid "Learn more about group-level project templates"
msgstr ""
+msgid "Learn more about job dependencies"
+msgstr ""
+
msgid "Learn more about signing commits"
msgstr ""
@@ -12905,9 +13267,21 @@ msgstr ""
msgid "License History"
msgstr ""
+msgid "License ID:"
+msgstr ""
+
+msgid "License URL"
+msgstr ""
+
msgid "License-Check"
msgstr ""
+msgid "LicenseCompliance|%{docLinkStart}License Approvals%{docLinkEnd} are active"
+msgstr ""
+
+msgid "LicenseCompliance|%{docLinkStart}License Approvals%{docLinkEnd} are inactive"
+msgstr ""
+
msgid "LicenseCompliance|Add a license"
msgstr ""
@@ -12929,9 +13303,15 @@ msgstr ""
msgid "LicenseCompliance|Deny"
msgstr ""
+msgid "LicenseCompliance|Learn more about %{linkStart}License Approvals%{linkEnd}"
+msgstr ""
+
msgid "LicenseCompliance|License"
msgstr ""
+msgid "LicenseCompliance|License Approvals"
+msgstr ""
+
msgid "LicenseCompliance|License Compliance detected %d license and policy violation for the source branch only; approval required"
msgid_plural "LicenseCompliance|License Compliance detected %d licenses and policy violations for the source branch only; approval required"
msgstr[0] ""
@@ -13152,6 +13532,9 @@ msgstr ""
msgid "List your Bitbucket Server repositories"
msgstr ""
+msgid "Lists"
+msgstr ""
+
msgid "Live preview"
msgstr ""
@@ -13335,6 +13718,9 @@ msgstr ""
msgid "Manage project labels"
msgstr ""
+msgid "Manage storage usage"
+msgstr ""
+
msgid "Manage two-factor authentication"
msgstr ""
@@ -13350,6 +13736,9 @@ msgstr ""
msgid "Manifest file import"
msgstr ""
+msgid "Manifest import"
+msgstr ""
+
msgid "Manual job"
msgstr ""
@@ -13497,6 +13886,12 @@ msgstr ""
msgid "Maximum field length"
msgstr ""
+msgid "Maximum file size is 2MB. Please select a smaller file."
+msgstr ""
+
+msgid "Maximum import size (MB)"
+msgstr ""
+
msgid "Maximum job timeout"
msgstr ""
@@ -13536,6 +13931,9 @@ msgstr ""
msgid "Maximum size of Elasticsearch bulk indexing requests."
msgstr ""
+msgid "Maximum size of import files."
+msgstr ""
+
msgid "Maximum size of individual attachments in comments."
msgstr ""
@@ -13587,6 +13985,9 @@ msgstr ""
msgid "Merge (when the pipeline succeeds)"
msgstr ""
+msgid "Merge Conflicts"
+msgstr ""
+
msgid "Merge Request"
msgstr ""
@@ -13713,6 +14114,9 @@ msgstr ""
msgid "MergeRequests|Squash task canceled: another squash is already in progress."
msgstr ""
+msgid "MergeRequests|This project does not allow squashing commits when merge requests are accepted."
+msgstr ""
+
msgid "MergeRequests|Thread stays resolved"
msgstr ""
@@ -13866,6 +14270,12 @@ msgstr ""
msgid "MetricsSettings|Add a button to the metrics dashboard linking directly to your existing external dashboard."
msgstr ""
+msgid "MetricsSettings|Choose whether to display dashboard metrics in UTC or the user's local timezone."
+msgstr ""
+
+msgid "MetricsSettings|Dashboard timezone"
+msgstr ""
+
msgid "MetricsSettings|External dashboard URL"
msgstr ""
@@ -13875,6 +14285,12 @@ msgstr ""
msgid "MetricsSettings|Metrics Dashboard"
msgstr ""
+msgid "MetricsSettings|UTC (Coordinated Universal Time)"
+msgstr ""
+
+msgid "MetricsSettings|User's local timezone"
+msgstr ""
+
msgid "Metrics|Add metric"
msgstr ""
@@ -13890,6 +14306,9 @@ msgstr ""
msgid "Metrics|Create metric"
msgstr ""
+msgid "Metrics|Current"
+msgstr ""
+
msgid "Metrics|Delete metric"
msgstr ""
@@ -13940,6 +14359,9 @@ msgstr ""
msgid "Metrics|Max"
msgstr ""
+msgid "Metrics|Min"
+msgstr ""
+
msgid "Metrics|Must be a valid PromQL query."
msgstr ""
@@ -13955,6 +14377,9 @@ msgstr ""
msgid "Metrics|Refresh dashboard"
msgstr ""
+msgid "Metrics|Select a value"
+msgstr ""
+
msgid "Metrics|Star dashboard"
msgstr ""
@@ -13973,12 +14398,18 @@ msgstr ""
msgid "Metrics|There was an error getting annotations information."
msgstr ""
+msgid "Metrics|There was an error getting dashboard validation warnings information."
+msgstr ""
+
msgid "Metrics|There was an error getting deployment information."
msgstr ""
msgid "Metrics|There was an error getting environments information."
msgstr ""
+msgid "Metrics|There was an error getting options for variable \"%{name}\"."
+msgstr ""
+
msgid "Metrics|There was an error trying to validate your query"
msgstr ""
@@ -14021,6 +14452,9 @@ msgstr ""
msgid "Metrics|You're about to permanently delete this metric. This cannot be undone."
msgstr ""
+msgid "Metrics|Your dashboard schema is invalid. Edit the dashboard to correct the YAML schema."
+msgstr ""
+
msgid "Metrics|e.g. HTTP requests"
msgstr ""
@@ -14036,12 +14470,18 @@ msgstr ""
msgid "Metrics|e.g. req/sec"
msgstr ""
+msgid "Mi"
+msgstr ""
+
msgid "Microsoft Azure"
msgstr ""
msgid "Middleman project with Static Site Editor support"
msgstr ""
+msgid "Migrate your data from an external source like GitHub, Bitbucket, or another instance of GitLab."
+msgstr ""
+
msgid "Migrated %{success_count}/%{total_count} files."
msgstr ""
@@ -14176,6 +14616,9 @@ msgstr ""
msgid "Minimum capacity to be available before we schedule more mirrors preemptively."
msgstr ""
+msgid "Minimum interval in days"
+msgstr ""
+
msgid "Minimum length is %{minimum_password_length} characters"
msgstr ""
@@ -14362,6 +14805,12 @@ msgstr ""
msgid "Multiple uploaders found: %{uploader_types}"
msgstr ""
+msgid "Must match with the %{codeStart}external_url%{codeEnd} in %{codeStart}/etc/gitlab/gitlab.rb%{codeEnd}."
+msgstr ""
+
+msgid "Must match with the %{codeStart}geo_node_name%{codeEnd} in %{codeStart}/etc/gitlab/gitlab.rb%{codeEnd}. %{linkStart}More information%{linkEnd}"
+msgstr ""
+
msgid "My Awesome Group"
msgstr ""
@@ -14476,10 +14925,16 @@ msgstr ""
msgid "New Application"
msgstr ""
+msgid "New Branch"
+msgstr ""
+
+msgid "New Deploy Key"
+msgstr ""
+
msgid "New Environment"
msgstr ""
-msgid "New Geo Node"
+msgid "New File"
msgstr ""
msgid "New Group"
@@ -14496,15 +14951,15 @@ msgid_plural "New Issues"
msgstr[0] ""
msgstr[1] ""
-msgid "New Iteration"
-msgstr ""
-
msgid "New Jira import"
msgstr ""
msgid "New Label"
msgstr ""
+msgid "New Merge Request"
+msgstr ""
+
msgid "New Milestone"
msgstr ""
@@ -14523,6 +14978,9 @@ msgstr ""
msgid "New Snippet"
msgstr ""
+msgid "New User"
+msgstr ""
+
msgid "New branch"
msgstr ""
@@ -14565,6 +15023,9 @@ msgstr ""
msgid "New issue title"
msgstr ""
+msgid "New iteration"
+msgstr ""
+
msgid "New iteration created"
msgstr ""
@@ -14742,6 +15203,12 @@ msgstr ""
msgid "No grouping"
msgstr ""
+msgid "No iteration"
+msgstr ""
+
+msgid "No iterations to show"
+msgstr ""
+
msgid "No job log"
msgstr ""
@@ -14784,6 +15251,9 @@ msgstr ""
msgid "No other labels with such name or description"
msgstr ""
+msgid "No panels matching properties %{opts}"
+msgstr ""
+
msgid "No parent group"
msgstr ""
@@ -14817,6 +15287,9 @@ msgstr ""
msgid "No schedules"
msgstr ""
+msgid "No stack trace for this error"
+msgstr ""
+
msgid "No starrers matched your search"
msgstr ""
@@ -14829,7 +15302,7 @@ msgstr ""
msgid "No test coverage"
msgstr ""
-msgid "No thanks, don't show this again"
+msgid "No thanks"
msgstr ""
msgid "No vulnerabilities present"
@@ -14844,9 +15317,6 @@ msgstr ""
msgid "No, directly import the existing email addresses and usernames."
msgstr ""
-msgid "No, not interested right now"
-msgstr ""
-
msgid "No. of commits"
msgstr ""
@@ -14892,9 +15362,6 @@ msgstr ""
msgid "Not found."
msgstr ""
-msgid "Not helpful"
-msgstr ""
-
msgid "Not now"
msgstr ""
@@ -15051,6 +15518,9 @@ msgstr ""
msgid "Notifications on"
msgstr ""
+msgid "Notify users by email when sign-in location is not recognized"
+msgstr ""
+
msgid "Nov"
msgstr ""
@@ -15117,9 +15587,6 @@ msgstr ""
msgid "Oh no!"
msgstr ""
-msgid "Ok let's go"
-msgstr ""
-
msgid "Oldest first"
msgstr ""
@@ -15132,16 +15599,49 @@ msgstr ""
msgid "On track"
msgstr ""
+msgid "OnDemandScans|Attached branch"
+msgstr ""
+
+msgid "OnDemandScans|Attached branch is where the scan job runs."
+msgstr ""
+
+msgid "OnDemandScans|Could not run the scan. Please try again."
+msgstr ""
+
msgid "OnDemandScans|Create new DAST scan"
msgstr ""
+msgid "OnDemandScans|DAST will scan the target URL and any discovered sub URLs."
+msgstr ""
+
+msgid "OnDemandScans|New on-demand DAST scan"
+msgstr ""
+
msgid "OnDemandScans|On-demand Scans"
msgstr ""
+msgid "OnDemandScans|On-demand scans run outside the DevOps cycle and find vulnerabilities in your projects. %{learnMoreLinkStart}Learn more%{learnMoreLinkEnd}"
+msgstr ""
+
+msgid "OnDemandScans|Only a passive scan can be performed on demand."
+msgstr ""
+
+msgid "OnDemandScans|Passive DAST Scan"
+msgstr ""
+
+msgid "OnDemandScans|Please enter a valid URL format, ex: http://www.example.com/home"
+msgstr ""
+
+msgid "OnDemandScans|Run this scan"
+msgstr ""
+
+msgid "OnDemandScans|Scan mode"
+msgstr ""
+
msgid "OnDemandScans|Schedule or run scans immediately against target sites. Currently available on-demand scan type: DAST. %{helpLinkStart}More information%{helpLinkEnd}"
msgstr ""
-msgid "Onboarding"
+msgid "OnDemandScans|Target URL"
msgstr ""
msgid "Once imported, repositories can be mirrored over SSH. Read more %{link_start}here%{link_end}."
@@ -15167,6 +15667,9 @@ msgstr ""
msgid "One or more of you personal access tokens were revoked"
msgstr ""
+msgid "One or more of your %{provider} projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
+msgstr ""
+
msgid "One or more of your Bitbucket projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
msgstr ""
@@ -15668,6 +16171,9 @@ msgstr ""
msgid "Parent epic is not present."
msgstr ""
+msgid "Parsing error for param :embed_json. %{message}"
+msgstr ""
+
msgid "Part of merge request changes"
msgstr ""
@@ -15680,6 +16186,9 @@ msgstr ""
msgid "Passed"
msgstr ""
+msgid "Passed on"
+msgstr ""
+
msgid "Password"
msgstr ""
@@ -15764,6 +16273,9 @@ msgstr ""
msgid "Perform common operations on GitLab project"
msgstr ""
+msgid "Performance and resource management"
+msgstr ""
+
msgid "Performance optimization"
msgstr ""
@@ -15971,7 +16483,7 @@ msgstr ""
msgid "Pipelines|Something went wrong while cleaning runners cache."
msgstr ""
-msgid "Pipelines|There are currently no %{scope} pipelines."
+msgid "Pipelines|There are currently no finished pipelines."
msgstr ""
msgid "Pipelines|There are currently no pipelines."
@@ -16082,6 +16594,9 @@ msgstr ""
msgid "Pipeline|Stop pipeline #%{pipelineId}?"
msgstr ""
+msgid "Pipeline|Tag name"
+msgstr ""
+
msgid "Pipeline|Trigger author"
msgstr ""
@@ -16163,12 +16678,18 @@ msgstr ""
msgid "Please check your email (%{email}) to verify that you own this address and unlock the power of CI/CD. Didn't receive it? %{resend_link}. Wrong email address? %{update_link}."
msgstr ""
+msgid "Please choose a file"
+msgstr ""
+
msgid "Please choose a group URL with no special characters."
msgstr ""
msgid "Please complete your profile with email address"
msgstr ""
+msgid "Please convert %{linkStart}them to Git%{linkEnd}, and go through the %{linkToImportFlow} again."
+msgstr ""
+
msgid "Please convert them to %{link_to_git}, and go through the %{link_to_import_flow} again."
msgstr ""
@@ -16226,6 +16747,9 @@ msgstr ""
msgid "Please provide a valid email address."
msgstr ""
+msgid "Please provide attributes to update"
+msgstr ""
+
msgid "Please refer to <a href=\"%{docs_url}\">%{docs_url}</a>"
msgstr ""
@@ -16961,6 +17485,9 @@ msgstr ""
msgid "Project has too many %{label_for_message} to search"
msgstr ""
+msgid "Project is required when cluster_type is :project"
+msgstr ""
+
msgid "Project members"
msgstr ""
@@ -17078,7 +17605,40 @@ msgstr ""
msgid "ProjectService|Comment"
msgstr ""
-msgid "ProjectService|Comment will be posted on each event"
+msgid "ProjectService|Event will be triggered by a push to the repository"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a commit is created/updated"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a confidential issue is created/updated/closed"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a deployment finishes"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a merge request is created/updated/merged"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a new tag is pushed to the repository"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a new, unique alert is recorded"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a pipeline status changes"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a wiki page is created/updated"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when an issue is created/updated/closed"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when someone adds a comment"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when someone adds a comment on a confidential issue"
msgstr ""
msgid "ProjectService|Perform common operations on GitLab project: %{project_name}"
@@ -17129,6 +17689,9 @@ msgstr ""
msgid "ProjectSettings|Enable 'Delete source branch' option by default"
msgstr ""
+msgid "ProjectSettings|Enable merge trains and pipelines for merged results"
+msgstr ""
+
msgid "ProjectSettings|Every merge creates a merge commit"
msgstr ""
@@ -17159,7 +17722,7 @@ msgstr ""
msgid "ProjectSettings|Forks"
msgstr ""
-msgid "ProjectSettings|Git Large File Storage"
+msgid "ProjectSettings|Git Large File Storage (LFS)"
msgstr ""
msgid "ProjectSettings|Internal"
@@ -17195,9 +17758,6 @@ msgstr ""
msgid "ProjectSettings|Merge options"
msgstr ""
-msgid "ProjectSettings|Merge pipelines will try to validate the post-merge result prior to merging"
-msgstr ""
-
msgid "ProjectSettings|Merge requests"
msgstr ""
@@ -17225,6 +17785,9 @@ msgstr ""
msgid "ProjectSettings|Pipelines"
msgstr ""
+msgid "ProjectSettings|Pipelines for merge requests must be enabled in the CI/CD configuration file, or pipelines could be unresolvable or dropped"
+msgstr ""
+
msgid "ProjectSettings|Pipelines must succeed"
msgstr ""
@@ -17252,6 +17815,9 @@ msgstr ""
msgid "ProjectSettings|Show link to create/view merge request when pushing from the command line"
msgstr ""
+msgid "ProjectSettings|Skipped pipelines are considered successful"
+msgstr ""
+
msgid "ProjectSettings|Snippets"
msgstr ""
@@ -17267,6 +17833,9 @@ msgstr ""
msgid "ProjectSettings|These checks must pass before merge requests can be merged"
msgstr ""
+msgid "ProjectSettings|This introduces the risk of merging changes that will not pass the pipeline."
+msgstr ""
+
msgid "ProjectSettings|This setting is applied on the server level and can be overridden by an admin."
msgstr ""
@@ -17444,15 +18013,27 @@ msgstr ""
msgid "ProjectsNew|Blank project"
msgstr ""
+msgid "ProjectsNew|Connect your external repository to GitLab CI/CD."
+msgstr ""
+
msgid "ProjectsNew|Contact an administrator to enable options for importing your project."
msgstr ""
msgid "ProjectsNew|Create"
msgstr ""
+msgid "ProjectsNew|Create a blank project to house your files, plan your work, and collaborate on code, among other things."
+msgstr ""
+
+msgid "ProjectsNew|Create blank project"
+msgstr ""
+
msgid "ProjectsNew|Create from template"
msgstr ""
+msgid "ProjectsNew|Create new project"
+msgstr ""
+
msgid "ProjectsNew|Creating project & repository."
msgstr ""
@@ -17477,6 +18058,9 @@ msgstr ""
msgid "ProjectsNew|Project description %{tag_start}(optional)%{tag_end}"
msgstr ""
+msgid "ProjectsNew|Run CI/CD for external repository"
+msgstr ""
+
msgid "ProjectsNew|Template"
msgstr ""
@@ -17738,6 +18322,9 @@ msgstr ""
msgid "Protected Branch"
msgstr ""
+msgid "Protected Branches"
+msgstr ""
+
msgid "Protected Environment"
msgstr ""
@@ -17750,6 +18337,9 @@ msgstr ""
msgid "Protected Tag"
msgstr ""
+msgid "Protected Tags"
+msgstr ""
+
msgid "Protected branches"
msgstr ""
@@ -17849,6 +18439,9 @@ msgstr ""
msgid "Provider"
msgstr ""
+msgid "Proxy support for this API is not available currently"
+msgstr ""
+
msgid "Pseudonymizer data collection"
msgstr ""
@@ -17873,6 +18466,9 @@ msgstr ""
msgid "Publish to status page"
msgstr ""
+msgid "Published on status page"
+msgstr ""
+
msgid "Publishes this issue to the associated status page."
msgstr ""
@@ -18047,6 +18643,9 @@ msgstr ""
msgid "Recipe"
msgstr ""
+msgid "Reconfigure"
+msgstr ""
+
msgid "Recover hidden stage"
msgstr ""
@@ -18133,6 +18732,9 @@ msgstr ""
msgid "Registration|Your profile"
msgstr ""
+msgid "Regulate approvals by authors/committers, based on compliance frameworks. Can be changed only at the instance level."
+msgstr ""
+
msgid "Rejected (closed)"
msgstr ""
@@ -18183,15 +18785,24 @@ msgstr ""
msgid "ReleaseAssetLinkType|Image"
msgstr ""
+msgid "ReleaseAssetLinkType|Images"
+msgstr ""
+
msgid "ReleaseAssetLinkType|Other"
msgstr ""
msgid "ReleaseAssetLinkType|Package"
msgstr ""
+msgid "ReleaseAssetLinkType|Packages"
+msgstr ""
+
msgid "ReleaseAssetLinkType|Runbook"
msgstr ""
+msgid "ReleaseAssetLinkType|Runbooks"
+msgstr ""
+
msgid "Releases"
msgstr ""
@@ -18204,6 +18815,9 @@ msgstr ""
msgid "Releases documentation"
msgstr ""
+msgid "Releases|New Release"
+msgstr ""
+
msgid "Release|Something went wrong while getting the release details"
msgstr ""
@@ -18276,6 +18890,9 @@ msgstr ""
msgid "Remove fork relationship"
msgstr ""
+msgid "Remove from batch"
+msgstr ""
+
msgid "Remove from board"
msgstr ""
@@ -18465,6 +19082,12 @@ msgstr ""
msgid "Replication"
msgstr ""
+msgid "Replication enabled"
+msgstr ""
+
+msgid "Replication paused"
+msgstr ""
+
msgid "Reply by email"
msgstr ""
@@ -18492,15 +19115,15 @@ msgstr ""
msgid "Reported %{timeAgo} by %{reportedBy}"
msgstr ""
-msgid "Reported Resource Changes: %{addNum} to add, %{changeNum} to change, %{deleteNum} to delete"
-msgstr ""
-
msgid "Reporter"
msgstr ""
msgid "Reporting"
msgstr ""
+msgid "Reports"
+msgstr ""
+
msgid "Reports|%{combinedString} and %{resolvedString}"
msgstr ""
@@ -18539,6 +19162,9 @@ msgstr ""
msgid "Reports|Failure"
msgstr ""
+msgid "Reports|Identifier"
+msgstr ""
+
msgid "Reports|Metrics reports are loading"
msgstr ""
@@ -18551,6 +19177,9 @@ msgstr ""
msgid "Reports|Metrics reports failed loading results"
msgstr ""
+msgid "Reports|Scanner"
+msgstr ""
+
msgid "Reports|Severity"
msgstr ""
@@ -18599,15 +19228,33 @@ msgstr ""
msgid "Repository cleanup has started. You will receive an email once the cleanup operation is complete."
msgstr ""
+msgid "Repository files count over the limit"
+msgstr ""
+
+msgid "Repository has an invalid default branch name."
+msgstr ""
+
+msgid "Repository has more than one branch."
+msgstr ""
+
msgid "Repository has no locks."
msgstr ""
+msgid "Repository has tags."
+msgstr ""
+
msgid "Repository maintenance"
msgstr ""
msgid "Repository mirroring"
msgstr ""
+msgid "Repository must contain at least 1 file."
+msgstr ""
+
+msgid "Repository size is above the limit."
+msgstr ""
+
msgid "Repository static objects"
msgstr ""
@@ -18617,7 +19264,7 @@ msgstr ""
msgid "Repository sync capacity"
msgstr ""
-msgid "Repository: %{counter_repositories} / Wikis: %{counter_wikis} / Build Artifacts: %{counter_build_artifacts} / LFS: %{counter_lfs_objects}"
+msgid "Repository: %{counter_repositories} / Wikis: %{counter_wikis} / Build Artifacts: %{counter_build_artifacts} / LFS: %{counter_lfs_objects} / Snippets: %{counter_snippets}"
msgstr ""
msgid "RepositorySettingsAccessLevel|Select"
@@ -18629,6 +19276,9 @@ msgstr ""
msgid "Request Headers"
msgstr ""
+msgid "Request details"
+msgstr ""
+
msgid "Request parameter %{param} is missing."
msgstr ""
@@ -18735,6 +19385,9 @@ msgstr ""
msgid "Reset template"
msgstr ""
+msgid "Reset to project defaults"
+msgstr ""
+
msgid "Resetting the authorization key for this project will require updating the authorization key in every alert source it is enabled in."
msgstr ""
@@ -18831,9 +19484,6 @@ msgstr ""
msgid "Resume"
msgstr ""
-msgid "Resume replication"
-msgstr ""
-
msgid "Resync"
msgstr ""
@@ -19456,6 +20106,9 @@ msgstr ""
msgid "SecurityReports|False positive"
msgstr ""
+msgid "SecurityReports|Group Security Dashboard"
+msgstr ""
+
msgid "SecurityReports|Hide dismissed"
msgstr ""
@@ -19510,10 +20163,13 @@ msgstr ""
msgid "SecurityReports|Remove project from dashboard"
msgstr ""
-msgid "SecurityReports|Report type"
+msgid "SecurityReports|Return to dashboard"
msgstr ""
-msgid "SecurityReports|Return to dashboard"
+msgid "SecurityReports|Scan details"
+msgstr ""
+
+msgid "SecurityReports|Scanner type"
msgstr ""
msgid "SecurityReports|Security Dashboard"
@@ -19570,6 +20226,9 @@ msgstr ""
msgid "SecurityReports|There was an error while generating the report."
msgstr ""
+msgid "SecurityReports|Unable to add %{invalidProjectsMessage}"
+msgstr ""
+
msgid "SecurityReports|Unable to add %{invalidProjects}"
msgstr ""
@@ -19684,6 +20343,9 @@ msgstr ""
msgid "Select due date"
msgstr ""
+msgid "Select file"
+msgstr ""
+
msgid "Select group or project"
msgstr ""
@@ -19747,9 +20409,6 @@ msgstr ""
msgid "Select the branch you want to set as the default for this project. All merge requests and commits will automatically be made against this branch unless you specify a different one."
msgstr ""
-msgid "Select the configured storage available for new repositories to be placed on."
-msgstr ""
-
msgid "Select the custom project template source group."
msgstr ""
@@ -19942,12 +20601,6 @@ msgstr ""
msgid "Service Desk is enabled but not yet active"
msgstr ""
-msgid "Service Desk is off"
-msgstr ""
-
-msgid "Service Desk is on"
-msgstr ""
-
msgid "Service Templates"
msgstr ""
@@ -20035,6 +20688,15 @@ msgstr ""
msgid "Set the milestone to %{milestone_reference}."
msgstr ""
+msgid "Set the number of concurrent requests this secondary node will make to the primary node while backfilling."
+msgstr ""
+
+msgid "Set the synchronization and verification capacity for the secondary node."
+msgstr ""
+
+msgid "Set the timeout in seconds to send a secondary node status to the primary and IPs allowed for the secondary nodes."
+msgstr ""
+
msgid "Set time estimate"
msgstr ""
@@ -20074,6 +20736,9 @@ msgstr ""
msgid "Set weight to %{weight}."
msgstr ""
+msgid "Set what should be replicated by choosing specific projects or groups by the secondary node."
+msgstr ""
+
msgid "SetPasswordToCloneLink|set a password"
msgstr ""
@@ -20200,6 +20865,9 @@ msgstr ""
msgid "Show complete raw log"
msgstr ""
+msgid "Show details"
+msgstr ""
+
msgid "Show file browser"
msgstr ""
@@ -20209,10 +20877,13 @@ msgstr ""
msgid "Show latest version"
msgstr ""
+msgid "Show list"
+msgstr ""
+
msgid "Show me everything"
msgstr ""
-msgid "Show me how"
+msgid "Show me how to add a pipeline"
msgstr ""
msgid "Show me more advanced stuff"
@@ -20261,9 +20932,6 @@ msgstr ""
msgid "Showing all issues"
msgstr ""
-msgid "Showing all labels"
-msgstr ""
-
msgid "Showing last %{size} of log -"
msgstr ""
@@ -20375,9 +21043,6 @@ msgstr ""
msgid "Skip outdated deployment jobs"
msgstr ""
-msgid "Skip this for now"
-msgstr ""
-
msgid "Skipped"
msgstr ""
@@ -20522,6 +21187,9 @@ msgstr ""
msgid "Something went wrong while adding your award. Please try again."
msgstr ""
+msgid "Something went wrong while applying the batch of suggestions. Please try again."
+msgstr ""
+
msgid "Something went wrong while applying the suggestion. Please try again."
msgstr ""
@@ -20624,10 +21292,13 @@ msgstr ""
msgid "Something went wrong while updating a requirement."
msgstr ""
+msgid "Something went wrong while updating assignees"
+msgstr ""
+
msgid "Something went wrong while updating your list settings"
msgstr ""
-msgid "Something went wrong with your automatic subscription renewal"
+msgid "Something went wrong with your automatic subscription renewal."
msgstr ""
msgid "Something went wrong, unable to add %{project} to dashboard"
@@ -20645,6 +21316,9 @@ msgstr ""
msgid "Something went wrong, unable to search projects"
msgstr ""
+msgid "Something went wrong."
+msgstr ""
+
msgid "Something went wrong. Please try again."
msgstr ""
@@ -20828,6 +21502,9 @@ msgstr ""
msgid "Source code"
msgstr ""
+msgid "Source code (%{fileExtension})"
+msgstr ""
+
msgid "Source is not available"
msgstr ""
@@ -20912,6 +21589,9 @@ msgstr ""
msgid "Stack trace"
msgstr ""
+msgid "Stacktrace snippet"
+msgstr ""
+
msgid "Stage"
msgstr ""
@@ -21017,6 +21697,9 @@ msgstr ""
msgid "Start thread & reopen %{noteable_name}"
msgstr ""
+msgid "Start using Directed Acyclic Graphs (DAG)"
+msgstr ""
+
msgid "Start your Free Gold Trial"
msgstr ""
@@ -21479,6 +22162,21 @@ msgstr ""
msgid "SuggestedColors|Very pale orange"
msgstr ""
+msgid "Suggestion is not applicable as the suggestion was not found."
+msgstr ""
+
+msgid "Suggestions are not applicable as one or more suggestions were not found."
+msgstr ""
+
+msgid "Suggestions are not applicable as their lines cannot overlap."
+msgstr ""
+
+msgid "Suggestions must all be on the same branch."
+msgstr ""
+
+msgid "Suggestions that change line count can't be added to batches, yet."
+msgstr ""
+
msgid "Suggestions:"
msgstr ""
@@ -21521,9 +22219,6 @@ msgstr ""
msgid "Synchronization disabled"
msgstr ""
-msgid "Synchronization of container repositories is disabled."
-msgstr ""
-
msgid "System"
msgstr ""
@@ -21704,6 +22399,34 @@ msgstr ""
msgid "Terms of Service and Privacy Policy"
msgstr ""
+msgid "Terraform|%{number} Terraform report failed to generate"
+msgid_plural "Terraform|%{number} Terraform reports failed to generate"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Terraform|%{number} Terraform report was generated in your pipelines"
+msgid_plural "Terraform|%{number} Terraform reports were generated in your pipelines"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Terraform|A Terraform report failed to generate."
+msgstr ""
+
+msgid "Terraform|A Terraform report was generated in your pipelines."
+msgstr ""
+
+msgid "Terraform|Generating the report caused an error."
+msgstr ""
+
+msgid "Terraform|Reported Resource Changes: %{addNum} to add, %{changeNum} to change, %{deleteNum} to delete"
+msgstr ""
+
+msgid "Terraform|The Terraform report %{name} failed to generate."
+msgstr ""
+
+msgid "Terraform|The Terraform report %{name} was generated in your pipelines."
+msgstr ""
+
msgid "Test"
msgstr ""
@@ -21730,7 +22453,7 @@ msgstr ""
msgid "TestHooks|Ensure the project has CI pipelines."
msgstr ""
-msgid "TestHooks|Ensure the project has at least one commit."
+msgid "TestHooks|Ensure the project has deployments."
msgstr ""
msgid "TestHooks|Ensure the project has issues."
@@ -21831,7 +22554,7 @@ msgstr ""
msgid "The Prometheus server responded with \"bad request\". Please check your queries are correct and are supported in your Prometheus version. %{documentationLink}"
msgstr ""
-msgid "The URL defined on the primary node that secondary nodes should use to contact it. Defaults to URL"
+msgid "The URL defined on the primary node that secondary nodes should use to contact it."
msgstr ""
msgid "The URL to use for connecting to Elasticsearch. Use a comma-separated list to support clustering (e.g., \"http://localhost:9200, http://localhost:9201\")."
@@ -21840,6 +22563,9 @@ msgstr ""
msgid "The X509 Certificate to use when mutual TLS is required to communicate with the external authorization service. If left blank, the server certificate is still validated when accessing over HTTPS."
msgstr ""
+msgid "The above settings apply to all projects with the selected compliance framework(s)."
+msgstr ""
+
msgid "The amount of seconds after which a request to get a secondary node status will time out."
msgstr ""
@@ -22139,9 +22865,6 @@ msgstr ""
msgid "The total stage shows the time it takes between creating an issue and deploying the code to production. The data will be automatically added once you have completed the full idea to production cycle."
msgstr ""
-msgid "The unique identifier for the Geo node. Must match %{geoNodeName} if it is set in gitlab.rb, otherwise it must match %{externalUrl} with a trailing slash"
-msgstr ""
-
msgid "The update action will time out after %{number_of_minutes} minutes. For big repositories, use a clone/push combination."
msgstr ""
@@ -22166,9 +22889,6 @@ msgstr ""
msgid "The user you are trying to deactivate has been active in the past %{minimum_inactive_days} days and cannot be deactivated"
msgstr ""
-msgid "The user-facing URL of the Geo node"
-msgstr ""
-
msgid "The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6."
msgstr ""
@@ -22259,6 +22979,9 @@ msgstr ""
msgid "There was a problem fetching project branches."
msgstr ""
+msgid "There was a problem fetching project tags."
+msgstr ""
+
msgid "There was a problem fetching project users."
msgstr ""
@@ -22310,6 +23033,9 @@ msgstr ""
msgid "There was an error fetching the %{replicableType}"
msgstr ""
+msgid "There was an error fetching the Geo Settings"
+msgstr ""
+
msgid "There was an error fetching the Node's Groups"
msgstr ""
@@ -22430,6 +23156,12 @@ msgstr ""
msgid "This %{issuable} is locked. Only <strong>project members</strong> can comment."
msgstr ""
+msgid "This %{noteableTypeText} is %{confidentialLinkStart}confidential%{linkEnd} and %{lockedLinkStart}locked%{linkEnd}."
+msgstr ""
+
+msgid "This %{noteableTypeText} is locked."
+msgstr ""
+
msgid "This %{viewer} could not be displayed because %{reason}. You can %{options} instead."
msgstr ""
@@ -22448,6 +23180,9 @@ msgstr ""
msgid "This action can lead to data loss. To prevent accidental actions we ask you to confirm your intention."
msgstr ""
+msgid "This also resolves all related threads"
+msgstr ""
+
msgid "This also resolves the discussion"
msgstr ""
@@ -22538,6 +23273,9 @@ msgstr ""
msgid "This epic does not exist or you don't have sufficient permission."
msgstr ""
+msgid "This feature is currently in beta. We invite you to %{linkStart}give feedback%{linkEnd}."
+msgstr ""
+
msgid "This feature requires local storage to be enabled"
msgstr ""
@@ -22571,7 +23309,7 @@ msgstr ""
msgid "This is a Work in Progress"
msgstr ""
-msgid "This is a confidential issue."
+msgid "This is a confidential %{noteableTypeText}."
msgstr ""
msgid "This is a delayed job to run in %{remainingTime}"
@@ -22598,9 +23336,6 @@ msgstr ""
msgid "This is your current session"
msgstr ""
-msgid "This issue is %{confidentialLinkStart}confidential%{linkEnd} and %{lockedLinkStart}locked%{linkEnd}."
-msgstr ""
-
msgid "This issue is confidential"
msgstr ""
@@ -22610,9 +23345,6 @@ msgstr ""
msgid "This issue is in a child epic of the filtered epic"
msgstr ""
-msgid "This issue is locked."
-msgstr ""
-
msgid "This job depends on other jobs with expired/erased artifacts: %{invalid_dependencies}"
msgstr ""
@@ -22712,6 +23444,9 @@ msgstr ""
msgid "This license has already expired."
msgstr ""
+msgid "This link points to external content"
+msgstr ""
+
msgid "This may expose confidential information as the selected fork is in another namespace that can have other members."
msgstr ""
@@ -22739,6 +23474,9 @@ msgstr ""
msgid "This page will be removed in a future release."
msgstr ""
+msgid "This pipeline does not use the %{codeStart}needs%{codeEnd} keyword and can't be represented as a directed acyclic graph."
+msgstr ""
+
msgid "This pipeline makes use of a predefined CI/CD configuration enabled by %{strongStart}Auto DevOps.%{strongEnd}"
msgstr ""
@@ -22967,6 +23705,9 @@ msgstr ""
msgid "Time from last commit to merge"
msgstr ""
+msgid "Time in seconds"
+msgstr ""
+
msgid "Time in seconds GitLab will wait for a response from the external service. When the service does not respond in time, access will be denied."
msgstr ""
@@ -23388,9 +24129,15 @@ msgstr ""
msgid "Total artifacts size: %{total_size}"
msgstr ""
+msgid "Total cores (CPUs)"
+msgstr ""
+
msgid "Total issues"
msgstr ""
+msgid "Total memory (GB)"
+msgstr ""
+
msgid "Total test time for all commits/merges"
msgstr ""
@@ -23592,6 +24339,9 @@ msgstr ""
msgid "U2F only works with HTTPS-enabled websites. Contact your administrator for more details."
msgstr ""
+msgid "UI Development Kit"
+msgstr ""
+
msgid "URL"
msgstr ""
@@ -23664,9 +24414,6 @@ msgstr ""
msgid "Unable to load the merge request widget. Try reloading the page."
msgstr ""
-msgid "Unable to process group import file"
-msgstr ""
-
msgid "Unable to resolve"
msgstr ""
@@ -23682,6 +24429,9 @@ msgstr ""
msgid "Unable to sign you in to the group with SAML due to \"%{reason}\""
msgstr ""
+msgid "Unable to suggest a path. Please refresh and try again."
+msgstr ""
+
msgid "Unable to update label prioritization at this time"
msgstr ""
@@ -23697,6 +24447,9 @@ msgstr ""
msgid "Unarchiving the project will restore people's ability to make changes to it. The repository can be committed to, and issues, comments, and other entities can be created. %{strong_start}Once active, this project shows up in the search and on the dashboard.%{strong_end}"
msgstr ""
+msgid "Unassign from commenting user"
+msgstr ""
+
msgid "Unblock"
msgstr ""
@@ -23772,6 +24525,9 @@ msgstr ""
msgid "Unreachable"
msgstr ""
+msgid "Unrecognized cluster type"
+msgstr ""
+
msgid "Unresolve"
msgstr ""
@@ -23832,6 +24588,9 @@ msgstr ""
msgid "Unsubscribes from this %{quick_action_target}."
msgstr ""
+msgid "Unsupported todo type passed. Supported todo types are: %{todo_types}"
+msgstr ""
+
msgid "Until"
msgstr ""
@@ -23859,6 +24618,9 @@ msgstr ""
msgid "Update approval rule"
msgstr ""
+msgid "Update approvers"
+msgstr ""
+
msgid "Update failed"
msgstr ""
@@ -23868,6 +24630,9 @@ msgstr ""
msgid "Update it"
msgstr ""
+msgid "Update iteration"
+msgstr ""
+
msgid "Update now"
msgstr ""
@@ -23913,7 +24678,7 @@ msgstr ""
msgid "Updated at"
msgstr ""
-msgid "Updated to"
+msgid "Updated to %{linkStart}chart v%{linkEnd}"
msgstr ""
msgid "Updating"
@@ -23943,6 +24708,9 @@ msgstr ""
msgid "Upload CSV file"
msgstr ""
+msgid "Upload License"
+msgstr ""
+
msgid "Upload New File"
msgstr ""
@@ -24024,16 +24792,19 @@ msgstr ""
msgid "UsageQuota|Pipelines"
msgstr ""
+msgid "UsageQuota|Purchase more storage"
+msgstr ""
+
msgid "UsageQuota|Repositories"
msgstr ""
msgid "UsageQuota|Repository"
msgstr ""
-msgid "UsageQuota|Storage"
+msgid "UsageQuota|Snippets"
msgstr ""
-msgid "UsageQuota|Storage usage:"
+msgid "UsageQuota|Storage"
msgstr ""
msgid "UsageQuota|This namespace has no projects which use shared runners"
@@ -24066,6 +24837,12 @@ msgstr ""
msgid "UsageQuota|Wikis"
msgstr ""
+msgid "UsageQuota|You used: %{usage} %{limit}"
+msgstr ""
+
+msgid "UsageQuota|out of %{formattedLimit} of your namespace storage"
+msgstr ""
+
msgid "Use %{code_start}::%{code_end} to create a %{link_start}scoped label set%{link_end} (eg. %{code_start}priority::1%{code_end})"
msgstr ""
@@ -24132,6 +24909,9 @@ msgstr ""
msgid "User IDs"
msgstr ""
+msgid "User Lists can only be created and modified with %{linkStart}the API%{linkEnd}"
+msgstr ""
+
msgid "User OAuth applications"
msgstr ""
@@ -24156,6 +24936,9 @@ msgstr ""
msgid "User key was successfully removed."
msgstr ""
+msgid "User list %{name} will be removed. Are you sure?"
+msgstr ""
+
msgid "User map"
msgstr ""
@@ -24177,148 +24960,10 @@ msgstr ""
msgid "User was successfully updated."
msgstr ""
-msgid "UserOnboardingTour|%{activeTour}/%{totalTours}"
-msgstr ""
-
-msgid "UserOnboardingTour|%{completed}/%{total} steps completed"
-msgstr ""
-
-msgid "UserOnboardingTour|%{emphasisStart}Well done!%{emphasisEnd}%{lineBreak}%{lineBreak}That's it for our guided tour, congratulations for making it all the way to the end!%{lineBreak}%{lineBreak}We hope this gave you a good overview of GitLab and how it can help you. We'll now show you how to create your own project and invite your colleagues."
-msgstr ""
-
-msgid "UserOnboardingTour|Adding other members to a project is done through Project Settings. Click on %{emphasisStart}Settings%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Alright, that's it for Commits. Let's take a look at the %{emphasisStart}Branches%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Awesome! Now click on %{emphasisStart}Members%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Click on one of the %{emphasisStart}Compare%{emphasisEnd} buttons to compare a branch to master."
-msgstr ""
-
-msgid "UserOnboardingTour|Click on one of the %{emphasisStart}pipeline IDs%{emphasisEnd} to see the details of a pipeline."
-msgstr ""
-
-msgid "UserOnboardingTour|Click to open the latest commit to see its details."
-msgstr ""
-
-msgid "UserOnboardingTour|Close 'Learn GitLab'"
-msgstr ""
-
-msgid "UserOnboardingTour|Commits are shown in chronological order and can be filtered by the commit message or by the branch."
+msgid "UserList|Delete %{name}?"
msgstr ""
-msgid "UserOnboardingTour|Create a project"
-msgstr ""
-
-msgid "UserOnboardingTour|Exit 'Learn GitLab'"
-msgstr ""
-
-msgid "UserOnboardingTour|Got it"
-msgstr ""
-
-msgid "UserOnboardingTour|Great job! %{clapHands} We hope the tour was helpful and that you learned how to use GitLab.%{lineBreak}%{lineBreak}We'd love to get your feedback on this tour.%{lineBreak}%{lineBreak}%{emphasisStart}How helpful would you say this guided tour was?%{emphasisEnd}%{lineBreak}%{lineBreak}"
-msgstr ""
-
-msgid "UserOnboardingTour|Guided GitLab Tour"
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can compare the changes of this branch to another one. Changes are divided by files so that it's easier to see what was changed where."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can create a project from scratch, start with a template or import a repository from other platforms. Whatever you choose, we'll guide you through the process.%{lineBreak}%{lineBreak}Fill in your new project information and click on %{emphasisStart}Create Project%{emphasisEnd} to progress to the next step."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can see the breakdown of the pipelines: its stages and jobs in each of the stages and their status.%{lineBreak}%{lineBreak}Our CI/CD pipelines are quite complex, most of our users have fewer and simpler pipelines."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can see the current members of the project (just you at the moment) and invite new members.%{lineBreak}%{lineBreak}You can invite multiple members at once (existing GitLab users or invite by email) and you can also set their roles and permissions.%{lineBreak}%{lineBreak}Add a few members and click on %{emphasisStart}Add to project%{emphasisEnd} to complete this step."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can see what changes were made with this commit, on what branch and if there's a related merge request. The status of the pipeline will also show up if CI/CD is set up.%{lineBreak}%{lineBreak}You can also comment on the lines of code that were changed and start a discussion with your colleagues!"
-msgstr ""
-
-msgid "UserOnboardingTour|Here's an overview of branches in the %{emphasisStart}%{projectName}%{emphasisEnd} project. They're split into Active and Stale.%{lineBreak}%{lineBreak}From here, you can create a new merge request from a branch, or compare the branch to any other branch in the project. By default, it will compare it to the master branch."
-msgstr ""
-
-msgid "UserOnboardingTour|Invite colleagues"
-msgstr ""
-
-msgid "UserOnboardingTour|Issues are great for communicating and keeping track of progress in GitLab. These are all issues that are open in the %{emphasisStart}%{projectName}%{emphasisEnd}.%{lineBreak}%{lineBreak}You can help us improve GitLab by contributing work to issues that are labeled <span class=\"badge color-label accept-mr-label\">Accepting merge requests</span>.%{lineBreak}%{lineBreak}This list can be filtered by labels, milestones, assignees, authors... We'll show you how it looks when the list is filtered by a label."
-msgstr ""
-
-msgid "UserOnboardingTour|Learn GitLab"
-msgstr ""
-
-msgid "UserOnboardingTour|Let's take a closer look at a merge request. Click on the title of one."
-msgstr ""
-
-msgid "UserOnboardingTour|Let's take a closer look at all the commits. Click on %{emphasisStart}Commits%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Let's take a closer look at the repository of this project. Click on %{emphasisStart}Repository%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|No thanks"
-msgstr ""
-
-msgid "UserOnboardingTour|Ok, let's go"
-msgstr ""
-
-msgid "UserOnboardingTour|Ok, show me"
-msgstr ""
-
-msgid "UserOnboardingTour|Open one of the issues by clicking on its title."
-msgstr ""
-
-msgid "UserOnboardingTour|Restart this step"
-msgstr ""
-
-msgid "UserOnboardingTour|Skip this step"
-msgstr ""
-
-msgid "UserOnboardingTour|Sweet! Your project was created and is ready to be used.%{lineBreak}%{lineBreak}You can start adding files to the repository or clone it. One last thing we want to show you is how to invite your colleagues to your new project."
-msgstr ""
-
-msgid "UserOnboardingTour|Take a look. Here's a nifty menu for quickly creating issues, merge requests, snippets, projects and groups. Click on it and select \"New project\" from the \"GitLab\" section to get started."
-msgstr ""
-
-msgid "UserOnboardingTour|Thanks for taking the guided tour. Remember, if you want to go through it again, you can start %{emphasisStart}Learn GitLab%{emphasisEnd} in the help menu on the top right."
-msgstr ""
-
-msgid "UserOnboardingTour|Thanks for the feedback! %{thumbsUp}"
-msgstr ""
-
-msgid "UserOnboardingTour|That's it for issues. Let'st take a look at %{emphasisStart}Merge Requests%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|That's it for merge requests. Now for the final part of this guided tour - the %{emphasisStart}CI/CD%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|That's it for the Repository. Let's take a look at the %{emphasisStart}Issues%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|The structure of this page is very similar to issues. Status, description, discussion and the sidebar are all here.%{lineBreak}%{lineBreak}But take a look below the description and you'll notice that there's more information about the merge request, the CI/CD pipeline and the options for approving it.%{lineBreak}%{lineBreak}Alongside the discussion you can also see more information about commits in this merge request, the status of pipelines and review all changes that were made."
-msgstr ""
-
-msgid "UserOnboardingTour|There's a lot of information here but don't worry, we'll go through it.%{lineBreak}%{lineBreak}On the top you can see the status of the issue and when it was opened and by whom. Directly below it is the issue description and below that are other %{emphasisStart}related issues%{emphasisEnd} and %{emphasisStart}merge requests%{emphasisEnd} (if any). Then below that is the %{emphasisStart}discussion%{emphasisEnd}, that's where most of the communication happens.%{lineBreak}%{lineBreak}On the right, there's a sidebar where you can view/change the %{emphasisStart}assignee, milestone, due date, labels, weight%{emphasisEnd}, etc."
-msgstr ""
-
-msgid "UserOnboardingTour|These are all the CI/CD pipelines we have for our %{emphasisStart}%{projectName}%{emphasisEnd} project.%{lineBreak}%{lineBreak}Here you can see the status of each pipeline, for what commit it's running for, its stages and the status for them."
-msgstr ""
-
-msgid "UserOnboardingTour|These are all the issues that are available for community contributions. Let's take a closer look at one of them."
-msgstr ""
-
-msgid "UserOnboardingTour|This is an overview of all merge requests in this project. Similarly to the issues overview it can be filtered down by things like labels, milestones, authors, assignees, etc."
-msgstr ""
-
-msgid "UserOnboardingTour|This is the repository for the %{emphasisStart}%{projectName}%{emphasisEnd} project. All our code is stored here. Feel free to explore and take a closer look at folders and files.%{lineBreak}%{lineBreak}Above the file structure you can see the latest commit, who the author is and the status of the CI/CD pipeline.%{lineBreak}%{lineBreak}If you scroll down below the file structure, you'll find the Readme of this project. This is defined in the README.md file at the root of the repository."
-msgstr ""
-
-msgid "UserOnboardingTour|Welcome to the project overview of the %{emphasisStart}%{projectName}%{emphasisEnd} project. This is the project that we use to work on GitLab. At first, a project seems like a simple repository, but at GitLab, a project is so much more.%{lineBreak}%{lineBreak}You can create projects for hosting your codebase, use it as an issue tracker, collaborate on code, and continuously build, test, and deploy your app with built-in GitLab CI/CD."
+msgid "UserList|created %{timeago}"
msgstr ""
msgid "UserProfile|Activity"
@@ -24471,6 +25116,9 @@ msgstr ""
msgid "UsersSelect|Unassigned"
msgstr ""
+msgid "Using %{codeStart}needs%{codeEnd} allows jobs to run before their stage is reached, as soon as their individual dependencies are met, which speeds up your pipelines."
+msgstr ""
+
msgid "Using %{code_start}::%{code_end} denotes a %{link_start}scoped label set%{link_end}"
msgstr ""
@@ -24555,9 +25203,6 @@ msgstr ""
msgid "Versions"
msgstr ""
-msgid "Very helpful"
-msgstr ""
-
msgid "View Documentation"
msgstr ""
@@ -24706,6 +25351,9 @@ msgstr ""
msgid "VisibilityLevel|Unknown"
msgstr ""
+msgid "Visit settings page"
+msgstr ""
+
msgid "VisualReviewApp|%{stepStart}Step 1%{stepEnd}. Copy the following script:"
msgstr ""
@@ -24799,6 +25447,9 @@ msgstr ""
msgid "VulnerabilityManagement|Something went wrong while trying to delete the comment. Please try again later."
msgstr ""
+msgid "VulnerabilityManagement|Something went wrong while trying to refresh the vulnerability. Please try again later."
+msgstr ""
+
msgid "VulnerabilityManagement|Something went wrong while trying to retrieve the vulnerability history. Please try again later."
msgstr ""
@@ -24841,6 +25492,9 @@ msgstr ""
msgid "Vulnerability|Class"
msgstr ""
+msgid "Vulnerability|Crash Address"
+msgstr ""
+
msgid "Vulnerability|Description"
msgstr ""
@@ -24916,9 +25570,6 @@ msgstr ""
msgid "We couldn't reach the Prometheus server. Either the server no longer exists or the configuration details need updating."
msgstr ""
-msgid "We created a short guided tour that will help you learn the basics of GitLab and how it will help you be better at your job. It should only take a couple of minutes. You will be guided by two types of helpers, best recognized by their color."
-msgstr ""
-
msgid "We detected potential spam in the %{humanized_resource_name}. Please solve the reCAPTCHA to proceed."
msgstr ""
@@ -24952,6 +25603,9 @@ msgstr ""
msgid "We've found no vulnerabilities"
msgstr ""
+msgid "Web Application Firewall"
+msgstr ""
+
msgid "Web IDE"
msgstr ""
@@ -25012,9 +25666,6 @@ msgstr ""
msgid "Welcome to GitLab.com<br>@%{name}!"
msgstr ""
-msgid "Welcome to the Guided GitLab Tour"
-msgstr ""
-
msgid "Welcome to the guided GitLab tour"
msgstr ""
@@ -25062,9 +25713,6 @@ msgstr ""
msgid "While it's rare to have no vulnerabilities, it can happen. In any event, we ask that you please double check your settings to make sure you've set up your dashboard correctly."
msgstr ""
-msgid "White helpers give contextual information."
-msgstr ""
-
msgid "Who can be an approver?"
msgstr ""
@@ -25110,12 +25758,18 @@ msgstr ""
msgid "WikiEmptyIssueMessage|Suggest wiki improvement"
msgstr ""
+msgid "WikiEmptyIssueMessage|You must be a group member in order to add wiki pages. If you have suggestions for how to improve the wiki for this group, consider opening an issue in the %{issues_link}."
+msgstr ""
+
msgid "WikiEmptyIssueMessage|You must be a project member in order to add wiki pages. If you have suggestions for how to improve the wiki for this project, consider opening an issue in the %{issues_link}."
msgstr ""
msgid "WikiEmptyIssueMessage|issue tracker"
msgstr ""
+msgid "WikiEmpty|A wiki is where you can store all the details about your group. This can include why you've created it, its principles, how to use it, and so on."
+msgstr ""
+
msgid "WikiEmpty|A wiki is where you can store all the details about your project. This can include why you've created it, its principles, how to use it, and so on."
msgstr ""
@@ -25125,12 +25779,21 @@ msgstr ""
msgid "WikiEmpty|Suggest wiki improvement"
msgstr ""
+msgid "WikiEmpty|The wiki lets you write documentation for your group"
+msgstr ""
+
msgid "WikiEmpty|The wiki lets you write documentation for your project"
msgstr ""
+msgid "WikiEmpty|This group has no wiki pages"
+msgstr ""
+
msgid "WikiEmpty|This project has no wiki pages"
msgstr ""
+msgid "WikiEmpty|You must be a group member in order to add wiki pages."
+msgstr ""
+
msgid "WikiEmpty|You must be a project member in order to add wiki pages."
msgstr ""
@@ -25407,13 +26070,13 @@ msgstr ""
msgid "You can now export your security dashboard to a CSV report."
msgstr ""
-msgid "You can now submit a merge request to get this change into the original branch."
+msgid "You can now manage alert endpoint configuration in the Alerts section on the Operations settings page. Fields on this page have been deprecated."
msgstr ""
-msgid "You can now submit a merge request to get this change into the original project."
+msgid "You can now submit a merge request to get this change into the original branch."
msgstr ""
-msgid "You can only add files when you are on a branch"
+msgid "You can now submit a merge request to get this change into the original project."
msgstr ""
msgid "You can only edit files when you are on a branch"
@@ -25635,7 +26298,7 @@ msgstr ""
msgid "You need to upload a Google Takeout archive."
msgstr ""
-msgid "You reached %{usage_in_percent} of %{namespace_name}'s capacity (%{used_storage} of %{storage_limit})"
+msgid "You reached %{usage_in_percent} of %{namespace_name}'s storage capacity (%{used_storage} of %{storage_limit})"
msgstr ""
msgid "You tried to fork %{link_to_the_project} but it failed for the following reason:"
@@ -25938,13 +26601,13 @@ msgstr ""
msgid "Your subscription expired!"
msgstr ""
-msgid "Your subscription has been downgraded"
+msgid "Your subscription has been downgraded."
msgstr ""
-msgid "Your subscription will automatically renew in %{remaining_days}"
+msgid "Your subscription will automatically renew in %{remaining_days}."
msgstr ""
-msgid "Your subscription will expire in %{remaining_days}"
+msgid "Your subscription will expire in %{remaining_days}."
msgstr ""
msgid "Zoom meeting added"
@@ -26024,9 +26687,6 @@ msgstr ""
msgid "branch name"
msgstr ""
-msgid "build pipeline reference mismatch"
-msgstr ""
-
msgid "by"
msgstr ""
@@ -26045,6 +26705,9 @@ msgstr ""
msgid "cannot block others"
msgstr ""
+msgid "cannot contain HTML/XML tags, including any word between angle brackets (<,>)."
+msgstr ""
+
msgid "cannot include leading slash or directory traversal."
msgstr ""
@@ -26075,55 +26738,6 @@ msgstr ""
msgid "ciReport|%{remainingPackagesCount} more"
msgstr ""
-msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability"
-msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability for the source branch only"
-msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities for the source branch only"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerability"
-msgid_plural "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerabilities"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new vulnerability"
-msgid_plural "ciReport|%{reportType} %{status} detected %{newCount} new vulnerabilities"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities for the source branch only"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{fixedCount} fixed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} vulnerability for the source branch only"
-msgid_plural "ciReport|%{reportType} %{status} detected %{newCount} vulnerabilities for the source branch only"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "ciReport|%{reportType} %{status} detected no new vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected no vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected no vulnerabilities for the source branch only"
-msgstr ""
-
msgid "ciReport|%{reportType} is loading"
msgstr ""
@@ -26142,7 +26756,7 @@ msgstr ""
msgid "ciReport|All projects"
msgstr ""
-msgid "ciReport|All report types"
+msgid "ciReport|All scanner types"
msgstr ""
msgid "ciReport|All severities"
@@ -26166,6 +26780,9 @@ msgstr ""
msgid "ciReport|Container scanning detects known vulnerabilities in your docker images."
msgstr ""
+msgid "ciReport|Coverage Fuzzing"
+msgstr ""
+
msgid "ciReport|Create a merge request to implement this solution, or download and apply the patch manually."
msgstr ""
@@ -26324,6 +26941,9 @@ msgstr ""
msgid "customize"
msgstr ""
+msgid "data"
+msgstr ""
+
msgid "date must not be after 9999-12-31"
msgstr ""
@@ -26394,6 +27014,9 @@ msgstr ""
msgid "entries cannot contain HTML tags"
msgstr ""
+msgid "epic"
+msgstr ""
+
msgid "error"
msgstr ""
@@ -26415,9 +27038,6 @@ msgstr ""
msgid "expires on %{timebox_due_date}"
msgstr ""
-msgid "external_url"
-msgstr ""
-
msgid "failed"
msgstr ""
@@ -26453,9 +27073,6 @@ msgstr ""
msgid "from"
msgstr ""
-msgid "geo_node_name"
-msgstr ""
-
msgid "group"
msgstr ""
@@ -26477,9 +27094,6 @@ msgstr ""
msgid "https://your-bitbucket-server"
msgstr ""
-msgid "image"
-msgstr ""
-
msgid "image diff"
msgstr ""
@@ -26623,6 +27237,9 @@ msgstr ""
msgid "load it anyway"
msgstr ""
+msgid "loading"
+msgstr ""
+
msgid "locked by %{path_lock_user_name} %{created_at}"
msgstr ""
@@ -26751,9 +27368,6 @@ msgstr ""
msgid "mrWidget|Deployment statistics are not available currently"
msgstr ""
-msgid "mrWidget|Detect issues before deployment with a CI pipeline that continuously tests your code. We created a quick guide that will show you how to create one. Make your code more secure and more robust in just a minute."
-msgstr ""
-
msgid "mrWidget|Did not close"
msgstr ""
@@ -26916,6 +27530,9 @@ msgstr ""
msgid "mrWidget|To approve this merge request, please enter your password. This project requires all approvals to be authenticated."
msgstr ""
+msgid "mrWidget|Use %{linkStart}CI pipelines to test your code%{linkEnd}, simply add a GitLab CI configuration file to your project. It only takes a minute to make your code more secure and robust."
+msgstr ""
+
msgid "mrWidget|When this merge request is ready, remove the WIP: prefix from the title to allow it to be merged"
msgstr ""
@@ -26952,6 +27569,9 @@ msgstr ""
msgid "must be greater than start date"
msgstr ""
+msgid "must contain only valid frameworks"
+msgstr ""
+
msgid "my-awesome-group"
msgstr ""
@@ -26964,6 +27584,9 @@ msgstr ""
msgid "needs to be between 10 minutes and 1 month"
msgstr ""
+msgid "never"
+msgstr ""
+
msgid "never expires"
msgstr ""
@@ -26991,6 +27614,9 @@ msgstr ""
msgid "nounSeries|%{firstItem} and %{lastItem}"
msgstr ""
+msgid "nounSeries|%{item}"
+msgstr ""
+
msgid "nounSeries|%{item}, %{nextItem}"
msgstr ""
@@ -27127,6 +27753,9 @@ msgstr ""
msgid "revised"
msgstr ""
+msgid "satisfied"
+msgstr ""
+
msgid "score"
msgstr ""
@@ -27157,6 +27786,9 @@ msgstr ""
msgid "severity|Unknown"
msgstr ""
+msgid "should be an array of %{object_name} objects"
+msgstr ""
+
msgid "should be greater than or equal to %{access} inherited membership from group %{group_name}"
msgstr ""
@@ -27211,10 +27843,13 @@ msgstr ""
msgid "suggestPipeline|2/2: Commit your changes"
msgstr ""
-msgid "suggestPipeline|Commit the changes and your pipeline will automatically run for the first time."
+msgid "suggestPipeline|Choose %{boldStart}Code Quality%{boldEnd} to add a pipeline that tests the quality of your code."
msgstr ""
-msgid "suggestPipeline|We recommend the %{boldStart}Code Quality%{boldEnd} template, which will add a report widget to your Merge Requests. This way you’ll learn about code quality degradations much sooner. %{footerStart} Goodbye technical debt! %{footerEnd}"
+msgid "suggestPipeline|The template is ready! You can now commit it to create your first pipeline."
+msgstr ""
+
+msgid "suggestPipeline|We’re adding a GitLab CI configuration file to add a pipeline to the project. You could create it manually, but we recommend that you start with a GitLab template that works out of the box."
msgstr ""
msgid "syntax is correct"
@@ -27274,6 +27909,9 @@ msgstr ""
msgid "uses Kubernetes clusters to deploy your code!"
msgstr ""
+msgid "v%{version} published %{timeAgo}"
+msgstr ""
+
msgid "verify ownership"
msgstr ""
diff --git a/locale/nb_NO/gitlab.po b/locale/nb_NO/gitlab.po
index 8088638cc5d..46d1fc81c85 100644
--- a/locale/nb_NO/gitlab.po
+++ b/locale/nb_NO/gitlab.po
@@ -2,7 +2,7 @@ msgid ""
msgstr ""
"Project-Id-Version: gitlab-ee\n"
"Report-Msgid-Bugs-To: \n"
-"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Last-Translator: \n"
"Language-Team: Norwegian Bokmal\n"
"Language: nb_NO\n"
"MIME-Version: 1.0\n"
@@ -10,9 +10,11 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Crowdin-Project: gitlab-ee\n"
+"X-Crowdin-Project-ID: 288872\n"
"X-Crowdin-Language: nb\n"
"X-Crowdin-File: /master/locale/gitlab.pot\n"
-"PO-Revision-Date: 2020-06-08 15:13\n"
+"X-Crowdin-File-ID: 6\n"
+"PO-Revision-Date: 2020-07-02 01:22\n"
msgid " %{start} to %{end}"
msgstr ""
@@ -197,6 +199,11 @@ msgid_plural "%d metrics"
msgstr[0] ""
msgstr[1] ""
+msgid "%d milestone"
+msgid_plural "%d milestones"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "%d minute"
msgid_plural "%d minutes"
msgstr[0] ""
@@ -242,11 +249,6 @@ msgid_plural "%d unresolved threads"
msgstr[0] ""
msgstr[1] ""
-msgid "%d url scanned"
-msgid_plural "%d urls scanned"
-msgstr[0] ""
-msgstr[1] ""
-
msgid "%d vulnerability"
msgid_plural "%d vulnerabilities"
msgstr[0] ""
@@ -324,9 +326,15 @@ msgstr[1] ""
msgid "%{count} related %{pluralized_subject}: %{links}"
msgstr ""
+msgid "%{dashboard_path} could not be found."
+msgstr ""
+
msgid "%{days} days until tags are automatically removed"
msgstr ""
+msgid "%{deployLinkStart}Use a template to deploy to ECS%{deployLinkEnd}, or use a docker image to %{commandsLinkStart}run AWS commands in GitLab CI/CD%{commandsLinkEnd}."
+msgstr ""
+
msgid "%{description}- Sentry event: %{errorUrl}- First seen: %{firstSeen}- Last seen: %{lastSeen} %{countLabel}: %{count}%{userCountLabel}: %{userCount}"
msgstr ""
@@ -369,12 +377,18 @@ msgstr ""
msgid "%{host} sign-in from new location"
msgstr ""
-msgid "%{icon}You are about to add %{usersTag} people to the discussion. Proceed with caution."
+msgid "%{icon}You are about to add %{usersTag} people to the discussion. They will all receive a notification."
+msgstr ""
+
+msgid "%{integrations_link_start}Integrations%{link_end} enable you to make third-party applications part of your GitLab workflow. If the available integrations don't meet your needs, consider using a %{webhooks_link_start}webhook%{link_end}."
msgstr ""
msgid "%{issuableType} will be removed! Are you sure?"
msgstr ""
+msgid "%{issuesCount} issues in this group"
+msgstr ""
+
msgid "%{issuesSize} issues"
msgstr ""
@@ -384,7 +398,7 @@ msgstr ""
msgid "%{labelStart}Class:%{labelEnd} %{class}"
msgstr ""
-msgid "%{labelStart}Confidence:%{labelEnd} %{confidence}"
+msgid "%{labelStart}Evidence:%{labelEnd} %{evidence}"
msgstr ""
msgid "%{labelStart}File:%{labelEnd} %{file}"
@@ -402,6 +416,9 @@ msgstr ""
msgid "%{labelStart}Report Type:%{labelEnd} %{reportType}"
msgstr ""
+msgid "%{labelStart}Scanner:%{labelEnd} %{scanner}"
+msgstr ""
+
msgid "%{labelStart}Severity:%{labelEnd} %{severity}"
msgstr ""
@@ -426,9 +443,6 @@ msgstr ""
msgid "%{link_start}Read more%{link_end} about role permissions"
msgstr ""
-msgid "%{link} can be used for binding events when something is happening within the project."
-msgstr ""
-
msgid "%{listToShow}, and %{awardsListLength} more."
msgstr ""
@@ -462,6 +476,9 @@ msgstr ""
msgid "%{name} found %{resultsString}"
msgstr ""
+msgid "%{name} is already being used for another emoji"
+msgstr ""
+
msgid "%{name} is scheduled for %{action}"
msgstr ""
@@ -509,6 +526,9 @@ msgstr ""
msgid "%{primary} (%{secondary})"
msgstr ""
+msgid "%{ref} cannot be added: %{error}"
+msgstr ""
+
msgid "%{releases} release"
msgid_plural "%{releases} releases"
msgstr[0] ""
@@ -517,13 +537,37 @@ msgstr[1] ""
msgid "%{remaining_approvals} left"
msgstr ""
-msgid "%{retryButtonStart}Try again%{retryButtonEnd} or %{newFileButtonStart}attach a new file%{newFileButtonEnd}"
+msgid "%{reportType} %{status} detected %{critical} critical and %{high} high severity vulnerabilities out of %{total}."
+msgstr ""
+
+msgid "%{reportType} %{status} detected %{critical} critical and %{high} high severity vulnerabilities."
+msgstr ""
+
+msgid "%{reportType} %{status} detected %{critical} critical severity vulnerabilities out of %{total}."
+msgstr ""
+
+msgid "%{reportType} %{status} detected %{critical} critical severity vulnerability."
+msgid_plural "%{reportType} %{status} detected %{critical} critical severity vulnerabilities."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{reportType} %{status} detected %{high} high severity vulnerabilities out of %{total}."
msgstr ""
-msgid "%{screenreaderOnlyStart}Keyboard shorcuts%{screenreaderOnlyEnd} Disabled"
+msgid "%{reportType} %{status} detected %{high} high severity vulnerability."
+msgid_plural "%{reportType} %{status} detected %{high} high severity vulnerabilities."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{reportType} %{status} detected %{other} vulnerability."
+msgid_plural "%{reportType} %{status} detected %{other} vulnerabilities."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{reportType} %{status} detected no new vulnerabilities."
msgstr ""
-msgid "%{screenreaderOnlyStart}Keyboard shorcuts%{screenreaderOnlyEnd} Enabled"
+msgid "%{retryButtonStart}Try again%{retryButtonEnd} or %{newFileButtonStart}attach a new file%{newFileButtonEnd}"
msgstr ""
msgid "%{service_title} %{message}."
@@ -620,6 +664,12 @@ msgstr ""
msgid "%{token}..."
msgstr ""
+msgid "%{totalCpu} (%{freeSpacePercentage}%{percentSymbol} free)"
+msgstr ""
+
+msgid "%{totalMemory} (%{freeSpacePercentage}%{percentSymbol} free)"
+msgstr ""
+
msgid "%{totalWeight} total weight"
msgstr ""
@@ -650,6 +700,12 @@ msgstr ""
msgid "%{verb} %{time_spent_value} spent time."
msgstr ""
+msgid "%{webhooks_link_start}%{webhook_type}%{link_end} enable you to send notifications to web applications in response to events in a group or project."
+msgstr ""
+
+msgid "%{webhooks_link_start}%{webhook_type}%{link_end} enable you to send notifications to web applications in response to events in a group or project. We recommend using an %{integrations_link_start}integration%{link_end} in preference to a webhook."
+msgstr ""
+
msgid "'%{level}' is not a valid visibility level"
msgstr ""
@@ -676,9 +732,6 @@ msgstr ""
msgid "(No changes)"
msgstr ""
-msgid "(Show all)"
-msgstr ""
-
msgid "(check progress)"
msgstr ""
@@ -745,6 +798,9 @@ msgstr ""
msgid "0 for unlimited"
msgstr ""
+msgid "0 for unlimited, only effective with remote storage enabled."
+msgstr ""
+
msgid "1 %{type} addition"
msgid_plural "%{count} %{type} additions"
msgstr[0] ""
@@ -892,6 +948,12 @@ msgstr ""
msgid "<code>\"johnsmith@example.com\": \"johnsmith@example.com\"</code> will add \"By <a href=\"#\">johnsmith@example.com</a>\" to all issues and comments originally created by johnsmith@example.com. By default, the email address or username is masked to ensure the user's privacy. Use this option if you want to show the full email address."
msgstr ""
+msgid "<code>Masked</code> to prevent the values from being displayed in job logs (must match certain regexp requirements)."
+msgstr ""
+
+msgid "<code>Protected</code> to expose them to protected branches or tags only."
+msgstr ""
+
msgid "<namespace / project>"
msgstr ""
@@ -919,9 +981,6 @@ msgstr ""
msgid "A .NET Core console application template, customizable for any .NET Core project"
msgstr ""
-msgid "A DAG must have two dependent jobs to be visualized on this tab."
-msgstr ""
-
msgid "A GitBook site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
msgstr ""
@@ -952,6 +1011,12 @@ msgstr ""
msgid "A deleted user"
msgstr ""
+msgid "A file has been changed."
+msgstr ""
+
+msgid "A file was not found."
+msgstr ""
+
msgid "A file with '%{file_name}' already exists in %{branch} branch"
msgstr ""
@@ -1018,7 +1083,7 @@ msgstr ""
msgid "A subscription will trigger a new pipeline on the default branch of this project when a pipeline successfully completes for a new tag on the %{default_branch_docs} of the subscribed project."
msgstr ""
-msgid "A terraform report was generated in your pipelines."
+msgid "A suggestion is not applicable."
msgstr ""
msgid "A user with write access to the source branch selected this option"
@@ -1189,6 +1254,9 @@ msgstr ""
msgid "Action to take when receiving an alert."
msgstr ""
+msgid "Actions"
+msgstr ""
+
msgid "Activate"
msgstr ""
@@ -1245,6 +1313,9 @@ msgstr ""
msgid "Add LICENSE"
msgstr ""
+msgid "Add New Node"
+msgstr ""
+
msgid "Add README"
msgstr ""
@@ -1320,6 +1391,9 @@ msgstr ""
msgid "Add approval rule"
msgstr ""
+msgid "Add approvers"
+msgstr ""
+
msgid "Add bold text"
msgstr ""
@@ -1377,6 +1451,9 @@ msgstr ""
msgid "Add strikethrough text"
msgstr ""
+msgid "Add suggestion to batch"
+msgstr ""
+
msgid "Add system hook"
msgstr ""
@@ -1476,6 +1553,9 @@ msgstr ""
msgid "Admin Note"
msgstr ""
+msgid "Admin Notifications"
+msgstr ""
+
msgid "Admin Overview"
msgstr ""
@@ -1587,6 +1667,9 @@ msgstr ""
msgid "AdminSettings|Integrations configured here will automatically apply to all projects on this instance."
msgstr ""
+msgid "AdminSettings|Moved to integrations"
+msgstr ""
+
msgid "AdminSettings|No required pipeline"
msgstr ""
@@ -1817,12 +1900,18 @@ msgid_plural "Alerts"
msgstr[0] ""
msgstr[1] ""
+msgid "AlertManagement| assign yourself"
+msgstr ""
+
msgid "AlertManagement|Acknowledged"
msgstr ""
msgid "AlertManagement|Alert"
msgstr ""
+msgid "AlertManagement|Alert assignee(s): %{assignees}"
+msgstr ""
+
msgid "AlertManagement|Alert detail"
msgstr ""
@@ -1838,9 +1927,15 @@ msgstr ""
msgid "AlertManagement|All alerts"
msgstr ""
+msgid "AlertManagement|Assign To"
+msgstr ""
+
msgid "AlertManagement|Assign status"
msgstr ""
+msgid "AlertManagement|Assignee"
+msgstr ""
+
msgid "AlertManagement|Assignees"
msgstr ""
@@ -1859,9 +1954,6 @@ msgstr ""
msgid "AlertManagement|Edit"
msgstr ""
-msgid "AlertManagement|End time"
-msgstr ""
-
msgid "AlertManagement|Events"
msgstr ""
@@ -1880,10 +1972,13 @@ msgstr ""
msgid "AlertManagement|More information"
msgstr ""
+msgid "AlertManagement|No Matching Results"
+msgstr ""
+
msgid "AlertManagement|No alert data to display."
msgstr ""
-msgid "AlertManagement|No alerts available to display. If you think you're seeing this message in error, refresh the page."
+msgid "AlertManagement|No alerts available to display. See %{linkStart}enabling alert management%{linkEnd} for more information on adding alerts to the list."
msgstr ""
msgid "AlertManagement|No alerts to display."
@@ -1892,6 +1987,9 @@ msgstr ""
msgid "AlertManagement|None"
msgstr ""
+msgid "AlertManagement|None -"
+msgstr ""
+
msgid "AlertManagement|Open"
msgstr ""
@@ -1928,9 +2026,18 @@ msgstr ""
msgid "AlertManagement|There was an error displaying the alerts. Confirm your endpoint's configuration details to ensure alerts appear."
msgstr ""
+msgid "AlertManagement|There was an error while updating the assignee(s) list. Please try again."
+msgstr ""
+
+msgid "AlertManagement|There was an error while updating the assignee(s) of the alert. Please try again."
+msgstr ""
+
msgid "AlertManagement|There was an error while updating the status of the alert. Please try again."
msgstr ""
+msgid "AlertManagement|This assignee cannot be assigned to this alert."
+msgstr ""
+
msgid "AlertManagement|Tool"
msgstr ""
@@ -2078,6 +2185,9 @@ msgstr ""
msgid "Allow users to request access (if visibility is public or internal)"
msgstr ""
+msgid "Allowed Geo IP"
+msgstr ""
+
msgid "Allowed email domain restriction only permitted for top-level groups"
msgstr ""
@@ -2111,6 +2221,9 @@ msgstr ""
msgid "Amazon Web Services"
msgstr ""
+msgid "Amazon Web Services Logo"
+msgstr ""
+
msgid "Amazon authentication is not %{link_start}correctly configured%{link_end}. Ask your GitLab administrator if you want to use this service."
msgstr ""
@@ -2168,10 +2281,16 @@ msgstr ""
msgid "An error occurred when updating the issue weight"
msgstr ""
+msgid "An error occurred while acknowledging the notification. Refresh the page and try again."
+msgstr ""
+
+msgid "An error occurred while adding approvers"
+msgstr ""
+
msgid "An error occurred while adding formatted title for epic"
msgstr ""
-msgid "An error occurred while checking group path"
+msgid "An error occurred while checking group path. Please refresh and try again."
msgstr ""
msgid "An error occurred while committing your changes."
@@ -2315,7 +2434,7 @@ msgstr ""
msgid "An error occurred while loading milestones"
msgstr ""
-msgid "An error occurred while loading terraform report"
+msgid "An error occurred while loading project creation UI"
msgstr ""
msgid "An error occurred while loading the data. Please try again."
@@ -2369,6 +2488,9 @@ msgstr ""
msgid "An error occurred while rendering preview broadcast message"
msgstr ""
+msgid "An error occurred while rendering the editor"
+msgstr ""
+
msgid "An error occurred while reordering issues."
msgstr ""
@@ -2465,7 +2587,7 @@ msgstr ""
msgid "Analyze your dependencies for known vulnerabilities."
msgstr ""
-msgid "Analyze your source code and git history for secrets"
+msgid "Analyze your source code and git history for secrets."
msgstr ""
msgid "Analyze your source code for known vulnerabilities."
@@ -2579,12 +2701,18 @@ msgstr ""
msgid "Apply suggestion"
msgstr ""
+msgid "Apply suggestions"
+msgstr ""
+
msgid "Apply template"
msgstr ""
msgid "Apply this approval rule to any branch or a specific protected branch."
msgstr ""
+msgid "Applying"
+msgstr ""
+
msgid "Applying a template will replace the existing issue description. Any changes you have made will be lost."
msgstr ""
@@ -2597,12 +2725,18 @@ msgstr ""
msgid "Applying multiple commands"
msgstr ""
-msgid "Applying suggestion"
+msgid "Applying suggestion..."
+msgstr ""
+
+msgid "Applying suggestions..."
msgstr ""
msgid "Approval rules"
msgstr ""
+msgid "Approval rules reset to project defaults"
+msgstr ""
+
msgid "ApprovalRuleRemove|%d member"
msgid_plural "ApprovalRuleRemove|%d members"
msgstr[0] ""
@@ -2656,6 +2790,9 @@ msgstr ""
msgid "Approved"
msgstr ""
+msgid "Approved MRs"
+msgstr ""
+
msgid "Approved by: "
msgstr ""
@@ -2869,6 +3006,9 @@ msgstr ""
msgid "Assign"
msgstr ""
+msgid "Assign Iteration"
+msgstr ""
+
msgid "Assign custom color like #FF0000"
msgstr ""
@@ -2887,6 +3027,9 @@ msgstr ""
msgid "Assign to"
msgstr ""
+msgid "Assign to commenting user"
+msgstr ""
+
msgid "Assign yourself to these issues"
msgstr ""
@@ -2913,6 +3056,9 @@ msgid_plural "%d Assignees"
msgstr[0] ""
msgstr[1] ""
+msgid "Assignee has no permissions"
+msgstr ""
+
msgid "Assignee lists not available with your current license"
msgstr ""
@@ -2922,6 +3068,9 @@ msgstr ""
msgid "Assignee(s)"
msgstr ""
+msgid "Assignees"
+msgstr ""
+
msgid "Assigns %{assignee_users_sentence}."
msgstr ""
@@ -2957,6 +3106,9 @@ msgstr ""
msgid "Audit Events is a way to keep track of important events that happened in GitLab."
msgstr ""
+msgid "Audit Log"
+msgstr ""
+
msgid "AuditEvents|(removed)"
msgstr ""
@@ -2993,6 +3145,9 @@ msgstr ""
msgid "AuditLogs|IP Address"
msgstr ""
+msgid "AuditLogs|Member Events"
+msgstr ""
+
msgid "AuditLogs|No matching %{type} found."
msgstr ""
@@ -3275,9 +3430,6 @@ msgstr ""
msgid "Badges|e.g. %{exampleUrl}"
msgstr ""
-msgid "Badge|New"
-msgstr ""
-
msgid "Balsamiq file could not be loaded."
msgstr ""
@@ -3326,6 +3478,12 @@ msgstr ""
msgid "Below you will find all the groups that are public."
msgstr ""
+msgid "Beta"
+msgstr ""
+
+msgid "Bi-weekly code coverage"
+msgstr ""
+
msgid "Billing"
msgstr ""
@@ -3386,9 +3544,15 @@ msgstr ""
msgid "Bitbucket Server Import"
msgstr ""
+msgid "Bitbucket Server import"
+msgstr ""
+
msgid "Bitbucket import"
msgstr ""
+msgid "Blame"
+msgstr ""
+
msgid "Blocked"
msgstr ""
@@ -3401,9 +3565,6 @@ msgstr ""
msgid "Blog"
msgstr ""
-msgid "Blue helpers indicate an action to be taken."
-msgstr ""
-
msgid "Board name"
msgstr ""
@@ -3602,6 +3763,9 @@ msgstr ""
msgid "Broadcast Message was successfully updated."
msgstr ""
+msgid "Broadcast Messages"
+msgstr ""
+
msgid "Browse Directory"
msgstr ""
@@ -3656,6 +3820,9 @@ msgstr ""
msgid "By %{user_name}"
msgstr ""
+msgid "By URL"
+msgstr ""
+
msgid "By default GitLab sends emails in HTML and plain text formats so mail clients can choose what format to use. Disable this option if you only want to send emails in plain text format."
msgstr ""
@@ -3761,6 +3928,9 @@ msgstr ""
msgid "Can override approvers and approvals required per merge request"
msgstr ""
+msgid "Can't apply as this line has changed or the suggestion already matches its content."
+msgstr ""
+
msgid "Can't create snippet: %{err}"
msgstr ""
@@ -3947,9 +4117,6 @@ msgstr ""
msgid "Changes are still tracked. Useful for cluster/index migrations."
msgstr ""
-msgid "Changes are unknown"
-msgstr ""
-
msgid "Changes suppressed. Click to show."
msgstr ""
@@ -4256,15 +4423,9 @@ msgstr ""
msgid "Choose what content you want to see on a group’s overview page"
msgstr ""
-msgid "Choose which groups you wish to synchronize to this secondary node"
-msgstr ""
-
msgid "Choose which repositories you want to connect and run CI/CD pipelines."
msgstr ""
-msgid "Choose which shards you wish to synchronize to this secondary node"
-msgstr ""
-
msgid "Choose your framework"
msgstr ""
@@ -4562,9 +4723,15 @@ msgstr ""
msgid "Cluster does not exist"
msgstr ""
+msgid "Cluster is required for Stages::ClusterEndpointInserter"
+msgstr ""
+
msgid "Cluster level"
msgstr ""
+msgid "Cluster type must be specificed for Stages::ClusterEndpointInserter"
+msgstr ""
+
msgid "ClusterIntegration| %{custom_domain_start}More information%{custom_domain_end}."
msgstr ""
@@ -4676,7 +4843,7 @@ msgstr ""
msgid "ClusterIntegration|Cert-Manager"
msgstr ""
-msgid "ClusterIntegration|Cert-Manager is a native Kubernetes certificate management controller that helps with issuing certificates. Installing Cert-Manager on your cluster will issue a certificate by %{letsEncrypt} and ensure that certificates are valid and up-to-date."
+msgid "ClusterIntegration|Cert-Manager is a native Kubernetes certificate management controller that helps with issuing certificates. Installing Cert-Manager on your cluster will issue a certificate by %{linkStart}Let's Encrypt%{linkEnd} and ensure that certificates are valid and up-to-date."
msgstr ""
msgid "ClusterIntegration|Certificate Authority bundle (PEM format)"
@@ -4703,9 +4870,6 @@ msgstr ""
msgid "ClusterIntegration|Clear the local cache of namespace and service accounts. This is necessary if your integration has become out of sync. The cache is repopulated during the next CI job that requires namespace and service accounts."
msgstr ""
-msgid "ClusterIntegration|Cloud Run"
-msgstr ""
-
msgid "ClusterIntegration|Cluster being created"
msgstr ""
@@ -4790,7 +4954,7 @@ msgstr ""
msgid "ClusterIntegration|Crossplane"
msgstr ""
-msgid "ClusterIntegration|Crossplane enables declarative provisioning of managed services from your cloud of choice using %{codeStart}kubectl%{codeEnd} or %{gitlabIntegrationLink}. Crossplane runs inside your Kubernetes cluster and supports secure connectivity and secrets management between app containers and the cloud services they depend on."
+msgid "ClusterIntegration|Crossplane enables declarative provisioning of managed services from your cloud of choice using %{codeStart}kubectl%{codeEnd} or %{linkStart}GitLab Integration%{linkEnd}. Crossplane runs inside your Kubernetes cluster and supports secure connectivity and secrets management between app containers and the cloud services they depend on."
msgstr ""
msgid "ClusterIntegration|Deletes all GitLab resources attached to this cluster during removal"
@@ -4874,9 +5038,6 @@ msgstr ""
msgid "ClusterIntegration|GitLab-managed cluster"
msgstr ""
-msgid "ClusterIntegration|Gitlab Integration"
-msgstr ""
-
msgid "ClusterIntegration|Global default"
msgstr ""
@@ -4922,7 +5083,7 @@ msgstr ""
msgid "ClusterIntegration|Ingress gives you a way to route requests to services based on the request host or path, centralizing a number of services into a single entrypoint."
msgstr ""
-msgid "ClusterIntegration|Installing Ingress may incur additional costs. Learn more about %{pricingLink}."
+msgid "ClusterIntegration|Installing Ingress may incur additional costs. Learn more about %{linkStart}pricing%{linkEnd}."
msgstr ""
msgid "ClusterIntegration|Instance cluster"
@@ -4937,7 +5098,7 @@ msgstr ""
msgid "ClusterIntegration|Issuer Email"
msgstr ""
-msgid "ClusterIntegration|Issuers represent a certificate authority. You must provide an email address for your Issuer. "
+msgid "ClusterIntegration|Issuers represent a certificate authority. You must provide an email address for your Issuer."
msgstr ""
msgid "ClusterIntegration|Jupyter Hostname"
@@ -5009,9 +5170,6 @@ msgstr ""
msgid "ClusterIntegration|Learn more about instance Kubernetes clusters"
msgstr ""
-msgid "ClusterIntegration|Let's Encrypt"
-msgstr ""
-
msgid "ClusterIntegration|Loading IAM Roles"
msgstr ""
@@ -5123,7 +5281,7 @@ msgstr ""
msgid "ClusterIntegration|Prometheus"
msgstr ""
-msgid "ClusterIntegration|Prometheus is an open-source monitoring system with %{gitlabIntegrationLink} to monitor deployed applications."
+msgid "ClusterIntegration|Prometheus is an open-source monitoring system with %{linkStart}GitLab Integration%{linkEnd} to monitor deployed applications."
msgstr ""
msgid "ClusterIntegration|Provider details"
@@ -5279,10 +5437,10 @@ msgstr ""
msgid "ClusterIntegration|Select zone to choose machine type"
msgstr ""
-msgid "ClusterIntegration|Send Cilium Logs"
+msgid "ClusterIntegration|Send Container Network Policies Logs"
msgstr ""
-msgid "ClusterIntegration|Send ModSecurity Logs"
+msgid "ClusterIntegration|Send Web Application Firewall Logs"
msgstr ""
msgid "ClusterIntegration|Service Token"
@@ -5450,15 +5608,12 @@ msgstr ""
msgid "ClusterIntegration|documentation"
msgstr ""
-msgid "ClusterIntegration|installed via %{installed_via}"
+msgid "ClusterIntegration|installed via %{linkStart}Cloud Run%{linkEnd}"
msgstr ""
msgid "ClusterIntegration|meets the requirements"
msgstr ""
-msgid "ClusterIntegration|pricing"
-msgstr ""
-
msgid "ClusterIntegration|sign up"
msgstr ""
@@ -5495,6 +5650,15 @@ msgstr ""
msgid "Code"
msgstr ""
+msgid "Code Coverage: %{coveragePercentage}%{percentSymbol}"
+msgstr ""
+
+msgid "Code Coverage| Empty code coverage data"
+msgstr ""
+
+msgid "Code Coverage|Couldn't fetch the code coverage data"
+msgstr ""
+
msgid "Code Owners"
msgstr ""
@@ -5549,7 +5713,7 @@ msgstr ""
msgid "Collapse approvers"
msgstr ""
-msgid "Collapse child epics"
+msgid "Collapse milestones"
msgstr ""
msgid "Collapse replies"
@@ -5567,6 +5731,9 @@ msgstr ""
msgid "Coming soon"
msgstr ""
+msgid "Comma-separated, e.g. '1.1.1.1, 2.2.2.0/24'"
+msgstr ""
+
msgid "Command"
msgstr ""
@@ -5737,6 +5904,12 @@ msgstr ""
msgid "Compliance framework (optional)"
msgstr ""
+msgid "Compliance frameworks"
+msgstr ""
+
+msgid "ComplianceDashboard|created by:"
+msgstr ""
+
msgid "ComplianceFramework|GDPR"
msgstr ""
@@ -5875,6 +6048,9 @@ msgstr ""
msgid "Connection timed out"
msgstr ""
+msgid "Connection timeout"
+msgstr ""
+
msgid "Contact sales to upgrade"
msgstr ""
@@ -5930,9 +6106,6 @@ msgstr ""
msgid "ContainerRegistry|CLI Commands"
msgstr ""
-msgid "ContainerRegistry|Compressed Size"
-msgstr ""
-
msgid "ContainerRegistry|Container Registry"
msgstr ""
@@ -5948,6 +6121,9 @@ msgstr ""
msgid "ContainerRegistry|Copy push command"
msgstr ""
+msgid "ContainerRegistry|Delete selected"
+msgstr ""
+
msgid "ContainerRegistry|Docker connection error"
msgstr ""
@@ -5981,16 +6157,16 @@ msgstr ""
msgid "ContainerRegistry|If you are not already logged in, you need to authenticate to the Container Registry by using your GitLab username and password. If you have %{twofaDocLinkStart}Two-Factor Authentication%{twofaDocLinkEnd} enabled, use a %{personalAccessTokensDocLinkStart}Personal Access Token%{personalAccessTokensDocLinkEnd} instead of a password."
msgstr ""
-msgid "ContainerRegistry|Image ID"
+msgid "ContainerRegistry|Image ID: %{imageId}"
msgstr ""
msgid "ContainerRegistry|Image Repositories"
msgstr ""
-msgid "ContainerRegistry|Keep and protect the images that matter most."
+msgid "ContainerRegistry|Image tags"
msgstr ""
-msgid "ContainerRegistry|Last Updated"
+msgid "ContainerRegistry|Keep and protect the images that matter most."
msgstr ""
msgid "ContainerRegistry|Login"
@@ -6005,6 +6181,9 @@ msgstr ""
msgid "ContainerRegistry|Please contact your administrator."
msgstr ""
+msgid "ContainerRegistry|Published %{timeInfo}"
+msgstr ""
+
msgid "ContainerRegistry|Push an image"
msgstr ""
@@ -6020,9 +6199,6 @@ msgstr ""
msgid "ContainerRegistry|Remove repository"
msgstr ""
-msgid "ContainerRegistry|Remove selected tags"
-msgstr ""
-
msgid "ContainerRegistry|Remove tag"
msgid_plural "ContainerRegistry|Remove tags"
msgstr[0] ""
@@ -6052,9 +6228,6 @@ msgstr ""
msgid "ContainerRegistry|Sorry, your filter produced no results."
msgstr ""
-msgid "ContainerRegistry|Tag"
-msgstr ""
-
msgid "ContainerRegistry|Tag expiration policy"
msgstr ""
@@ -6199,21 +6372,6 @@ msgstr ""
msgid "Control the display of third party offers."
msgstr ""
-msgid "Control the maximum concurrency of LFS/attachment backfill for this secondary node"
-msgstr ""
-
-msgid "Control the maximum concurrency of container repository operations for this Geo node"
-msgstr ""
-
-msgid "Control the maximum concurrency of repository backfill for this secondary node"
-msgstr ""
-
-msgid "Control the maximum concurrency of verification operations for this Geo node"
-msgstr ""
-
-msgid "Control the minimum interval in days that a repository should be reverified for this primary node"
-msgstr ""
-
msgid "Cookie domain"
msgstr ""
@@ -6355,6 +6513,9 @@ msgstr ""
msgid "Could not find design."
msgstr ""
+msgid "Could not find iteration"
+msgstr ""
+
msgid "Could not remove the trigger."
msgstr ""
@@ -6391,6 +6552,12 @@ msgstr ""
msgid "Coverage"
msgstr ""
+msgid "Coverage Fuzzing"
+msgstr ""
+
+msgid "Crash State"
+msgstr ""
+
msgid "Create"
msgstr ""
@@ -6439,6 +6606,9 @@ msgstr ""
msgid "Create a personal access token on your account to pull or push via %{protocol}."
msgstr ""
+msgid "Create a project pre-populated with the necessary files to get you started quickly."
+msgstr ""
+
msgid "Create an account using:"
msgstr ""
@@ -6607,6 +6777,9 @@ msgstr ""
msgid "Creates branch '%{branch_name}' and a merge request to resolve this issue."
msgstr ""
+msgid "Creating"
+msgstr ""
+
msgid "Creating epic"
msgstr ""
@@ -6658,7 +6831,10 @@ msgstr ""
msgid "Current vulnerabilities count"
msgstr ""
-msgid "CurrentUser|Buy CI minutes"
+msgid "CurrentUser|Buy Pipeline minutes"
+msgstr ""
+
+msgid "CurrentUser|One of your groups is running out"
msgstr ""
msgid "CurrentUser|Profile"
@@ -6673,6 +6849,9 @@ msgstr ""
msgid "CurrentUser|Upgrade"
msgstr ""
+msgid "Custom Attributes"
+msgstr ""
+
msgid "Custom CI configuration path"
msgstr ""
@@ -6936,12 +7115,18 @@ msgstr ""
msgid "DAG"
msgstr ""
+msgid "DAG visualization requires at least 3 dependent jobs."
+msgstr ""
+
msgid "DNS"
msgstr ""
msgid "Dashboard"
msgstr ""
+msgid "Dashboard uid not found"
+msgstr ""
+
msgid "DashboardProjects|All"
msgstr ""
@@ -6966,6 +7151,9 @@ msgstr ""
msgid "Data is still calculating..."
msgstr ""
+msgid "Datasource name not found"
+msgstr ""
+
msgid "Date"
msgstr ""
@@ -7146,6 +7334,9 @@ msgstr ""
msgid "Delete this attachment"
msgstr ""
+msgid "Delete user list"
+msgstr ""
+
msgid "Delete variable"
msgstr ""
@@ -7462,6 +7653,9 @@ msgstr ""
msgid "Deploying to"
msgstr ""
+msgid "Deploying to AWS is easy with GitLab"
+msgstr ""
+
msgid "Deployment Frequency"
msgstr ""
@@ -7824,6 +8018,9 @@ msgstr ""
msgid "Dismissed on pipeline %{pipelineLink} at %{projectLink}"
msgstr ""
+msgid "Display alerts from all your monitoring tools directly within GitLab."
+msgstr ""
+
msgid "Display name"
msgstr ""
@@ -7881,9 +8078,6 @@ msgstr ""
msgid "Don't show again"
msgstr ""
-msgid "Don't worry, you can access this tour by clicking on the help icon in the top right corner and choose <strong>Learn GitLab</strong>."
-msgstr ""
-
msgid "Done"
msgstr ""
@@ -8010,6 +8204,9 @@ msgstr ""
msgid "Edit Release"
msgstr ""
+msgid "Edit Slack integration"
+msgstr ""
+
msgid "Edit Snippet"
msgstr ""
@@ -8049,6 +8246,9 @@ msgstr ""
msgid "Edit issues"
msgstr ""
+msgid "Edit iteration"
+msgstr ""
+
msgid "Edit public deploy key"
msgstr ""
@@ -8097,6 +8297,9 @@ msgstr ""
msgid "Email"
msgstr ""
+msgid "Email Notification"
+msgstr ""
+
msgid "Email address"
msgstr ""
@@ -8109,6 +8312,9 @@ msgstr ""
msgid "Email not verified. Please verify your email in Salesforce."
msgstr ""
+msgid "Email notification for unknown sign-ins"
+msgstr ""
+
msgid "Email patch"
msgstr ""
@@ -8340,6 +8546,9 @@ msgstr ""
msgid "Enforce DNS rebinding attack protection"
msgstr ""
+msgid "Enforce personal access token expiration"
+msgstr ""
+
msgid "Ensure connectivity is available from the GitLab server to the Prometheus server"
msgstr ""
@@ -8409,6 +8618,9 @@ msgstr ""
msgid "Enter the name of your application, and we'll return a unique %{type}."
msgstr ""
+msgid "Enter weights for storages for new repositories."
+msgstr ""
+
msgid "Enter your password to approve"
msgstr ""
@@ -8418,15 +8630,27 @@ msgstr ""
msgid "Environment does not have deployments"
msgstr ""
+msgid "Environment is required for Stages::MetricEndpointInserter"
+msgstr ""
+
+msgid "Environment is required for Stages::VariableEndpointInserter"
+msgstr ""
+
msgid "Environment scope"
msgstr ""
+msgid "Environment variables are applied to all project environments in this instance via the Runner. You can use environment variables for passwords, secret keys, etc. Make variables available to the running application by prepending the variable key with <code>K8S_SECRET_</code>. You can set variables to be:"
+msgstr ""
+
msgid "Environment variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. Additionally, they can be masked so they are hidden in job logs, though they must match certain regexp requirements to do so. You can use environment variables for passwords, secret keys, or whatever you want."
msgstr ""
msgid "Environment variables are configured by your administrator to be %{link_start}protected%{link_end} by default"
msgstr ""
+msgid "Environment variables on this GitLab instance are configured to be %{link_start}protected%{link_end} by default"
+msgstr ""
+
msgid "Environment:"
msgstr ""
@@ -8958,6 +9182,9 @@ msgstr ""
msgid "EventFilterBy|Filter by comments"
msgstr ""
+msgid "EventFilterBy|Filter by designs"
+msgstr ""
+
msgid "EventFilterBy|Filter by epic events"
msgstr ""
@@ -9090,15 +9317,15 @@ msgstr ""
msgid "Expand approvers"
msgstr ""
-msgid "Expand child epics"
-msgstr ""
-
msgid "Expand down"
msgstr ""
msgid "Expand dropdown"
msgstr ""
+msgid "Expand milestones"
+msgstr ""
+
msgid "Expand sidebar"
msgstr ""
@@ -9114,6 +9341,9 @@ msgstr ""
msgid "Expiration date"
msgstr ""
+msgid "Expiration not enforced"
+msgstr ""
+
msgid "Expiration policy for the Container Registry is a perfect solution for keeping the Registry space down while still enjoying the full power of GitLab CI/CD."
msgstr ""
@@ -9234,6 +9464,9 @@ msgstr ""
msgid "Failed Jobs"
msgstr ""
+msgid "Failed on"
+msgstr ""
+
msgid "Failed to add a Zoom meeting"
msgstr ""
@@ -9294,13 +9527,19 @@ msgstr ""
msgid "Failed to install."
msgstr ""
+msgid "Failed to load assignees. Please try again."
+msgstr ""
+
+msgid "Failed to load authors. Please try again."
+msgstr ""
+
msgid "Failed to load emoji list."
msgstr ""
msgid "Failed to load error details from Sentry."
msgstr ""
-msgid "Failed to load errors from Sentry. Error message: %{errorMessage}"
+msgid "Failed to load errors from Sentry."
msgstr ""
msgid "Failed to load group activity metrics. Please try again."
@@ -9381,6 +9620,9 @@ msgstr ""
msgid "Failed to set due date because the date format is invalid."
msgstr ""
+msgid "Failed to set iteration on this issue. Please try again."
+msgstr ""
+
msgid "Failed to signing using smartcard authentication"
msgstr ""
@@ -9603,13 +9845,10 @@ msgstr ""
msgid "FeatureFlags|Target environments"
msgstr ""
-msgid "FeatureFlags|There are no active feature flags"
-msgstr ""
-
-msgid "FeatureFlags|There are no inactive feature flags"
+msgid "FeatureFlags|There was an error fetching the feature flags."
msgstr ""
-msgid "FeatureFlags|There was an error fetching the feature flags."
+msgid "FeatureFlags|There was an error retrieving user lists"
msgstr ""
msgid "FeatureFlags|Try again in a few moments or contact your support team."
@@ -9621,9 +9860,18 @@ msgstr ""
msgid "FeatureFlag|Delete strategy"
msgstr ""
+msgid "FeatureFlag|List"
+msgstr ""
+
msgid "FeatureFlag|Percentage"
msgstr ""
+msgid "FeatureFlag|Select a user list"
+msgstr ""
+
+msgid "FeatureFlag|There are no configured user lists"
+msgstr ""
+
msgid "FeatureFlag|Type"
msgstr ""
@@ -9780,6 +10028,12 @@ msgstr ""
msgid "Filter..."
msgstr ""
+msgid "Find File"
+msgstr ""
+
+msgid "Find bugs in your code with coverage-guided fuzzing"
+msgstr ""
+
msgid "Find by path"
msgstr ""
@@ -10044,6 +10298,9 @@ msgstr ""
msgid "Geo allows you to replicate your GitLab instance to other geographical locations."
msgstr ""
+msgid "Geo nodes are paused using a command run on the node"
+msgstr ""
+
msgid "GeoNodeStatusEvent|%{timeAgoStr} (%{pendingEvents} events)"
msgstr ""
@@ -10128,6 +10385,9 @@ msgstr ""
msgid "GeoNodes|Not checksummed"
msgstr ""
+msgid "GeoNodes|Package files"
+msgstr ""
+
msgid "GeoNodes|Pausing replication stops the sync process. Are you sure?"
msgstr ""
@@ -10146,6 +10406,9 @@ msgstr ""
msgid "GeoNodes|Replication slots"
msgstr ""
+msgid "GeoNodes|Replication status"
+msgstr ""
+
msgid "GeoNodes|Repositories"
msgstr ""
@@ -10356,6 +10619,9 @@ msgstr ""
msgid "Geo|Synchronization failed - %{error}"
msgstr ""
+msgid "Geo|Synchronization of %{itemTitle} is disabled."
+msgstr ""
+
msgid "Geo|The database is currently %{db_lag} behind the primary node."
msgstr ""
@@ -10668,6 +10934,9 @@ msgstr ""
msgid "Go to %{link_to_google_takeout}."
msgstr ""
+msgid "Go to <strong>Issues</strong> > <strong>Boards</strong> to access your personalized learning issue board."
+msgstr ""
+
msgid "Go to Pipelines"
msgstr ""
@@ -10767,9 +11036,6 @@ msgstr ""
msgid "Go to your snippets"
msgstr ""
-msgid "Golden Tanuki"
-msgstr ""
-
msgid "Google Cloud Platform"
msgstr ""
@@ -10791,6 +11057,9 @@ msgstr ""
msgid "Grafana URL"
msgstr ""
+msgid "Grafana response contains invalid json"
+msgstr ""
+
msgid "GrafanaIntegration|API Token"
msgstr ""
@@ -10839,9 +11108,6 @@ msgstr ""
msgid "Group %{group_name} was successfully created."
msgstr ""
-msgid "Group '%{group_name}' is being imported."
-msgstr ""
-
msgid "Group Audit Events"
msgstr ""
@@ -10878,9 +11144,6 @@ msgstr ""
msgid "Group by:"
msgstr ""
-msgid "Group could not be imported: %{errors}"
-msgstr ""
-
msgid "Group description"
msgstr ""
@@ -10908,9 +11171,15 @@ msgstr ""
msgid "Group has not been marked for deletion"
msgstr ""
+msgid "Group import could not be scheduled"
+msgstr ""
+
msgid "Group info:"
msgstr ""
+msgid "Group is required when cluster_type is :group"
+msgstr ""
+
msgid "Group maintainers can register group runners in the %{link}"
msgstr ""
@@ -10974,6 +11243,24 @@ msgstr ""
msgid "GroupActivyMetrics|Recent activity (last 90 days)"
msgstr ""
+msgid "GroupImport|Failed to import group."
+msgstr ""
+
+msgid "GroupImport|Group '%{group_name}' is being imported."
+msgstr ""
+
+msgid "GroupImport|Group could not be imported: %{errors}"
+msgstr ""
+
+msgid "GroupImport|Please wait while we import the group for you. Refresh at will."
+msgstr ""
+
+msgid "GroupImport|The group was successfully imported."
+msgstr ""
+
+msgid "GroupImport|Unable to process group import file"
+msgstr ""
+
msgid "GroupRoadmap|%{dateWord} – No end date"
msgstr ""
@@ -11241,6 +11528,9 @@ msgstr ""
msgid "Groups (%{groups})"
msgstr ""
+msgid "Groups and projects"
+msgstr ""
+
msgid "Groups can also be nested by creating %{subgroup_docs_link_start}subgroups%{subgroup_docs_link_end}."
msgstr ""
@@ -11286,6 +11576,33 @@ msgstr ""
msgid "GroupsEmptyState|You can manage your group member’s permissions and access to each project in the group."
msgstr ""
+msgid "GroupsNew|Contact an administrator to enable options for importing your group."
+msgstr ""
+
+msgid "GroupsNew|Create"
+msgstr ""
+
+msgid "GroupsNew|Create group"
+msgstr ""
+
+msgid "GroupsNew|GitLab group export"
+msgstr ""
+
+msgid "GroupsNew|Import"
+msgstr ""
+
+msgid "GroupsNew|Import group"
+msgstr ""
+
+msgid "GroupsNew|My Awesome Group"
+msgstr ""
+
+msgid "GroupsNew|No import options available"
+msgstr ""
+
+msgid "GroupsNew|To copy a GitLab group between installations, navigate to the group settings page for the original installation, generate an export file, and upload it here."
+msgstr ""
+
msgid "GroupsTree|Are you sure you want to leave the \"%{fullName}\" group?"
msgstr ""
@@ -11388,6 +11705,9 @@ msgstr ""
msgid "Helps reduce request volume for protected paths"
msgstr ""
+msgid "Here are all your projects in your group, including the one you just created. To start, let’s take a look at your personalized learning project which will help you learn about GitLab at your own pace."
+msgstr ""
+
msgid "Here you will find recent merge request activity"
msgstr ""
@@ -11402,6 +11722,9 @@ msgid_plural "Hide charts"
msgstr[0] ""
msgstr[1] ""
+msgid "Hide details"
+msgstr ""
+
msgid "Hide file browser"
msgstr ""
@@ -11411,6 +11734,9 @@ msgstr ""
msgid "Hide host keys manual input"
msgstr ""
+msgid "Hide list"
+msgstr ""
+
msgid "Hide marketing-related entries from help"
msgstr ""
@@ -11431,9 +11757,6 @@ msgstr[1] ""
msgid "Hide values"
msgstr ""
-msgid "Hiding all labels"
-msgstr ""
-
msgid "High or unknown vulnerabilities present"
msgstr ""
@@ -11518,6 +11841,9 @@ msgstr ""
msgid "ID:"
msgstr ""
+msgid "IDE"
+msgstr ""
+
msgid "IDE|Allow live previews of JavaScript projects in the Web IDE using CodeSandbox Live Preview."
msgstr ""
@@ -11608,10 +11934,10 @@ msgstr ""
msgid "If enabled"
msgstr ""
-msgid "If enabled, access to projects will be validated on an external service using their classification label."
+msgid "If enabled, GitLab will handle Object Storage replication using Geo. %{linkStart}More information%{linkEnd}"
msgstr ""
-msgid "If enabled, and if object storage is enabled, GitLab will handle Object Storage replication using Geo"
+msgid "If enabled, access to projects will be validated on an external service using their classification label."
msgstr ""
msgid "If there is no previous license or if the previous license has expired, some GitLab functionality will be blocked until a new, valid license is uploaded."
@@ -11973,6 +12299,9 @@ msgid_plural "Instances"
msgstr[0] ""
msgstr[1] ""
+msgid "Instance Configuration"
+msgstr ""
+
msgid "Instance Statistics visibility"
msgstr ""
@@ -11994,9 +12323,6 @@ msgstr ""
msgid "Integrations"
msgstr ""
-msgid "Integrations allow you to integrate GitLab with other applications"
-msgstr ""
-
msgid "Integrations|All details"
msgstr ""
@@ -12108,6 +12434,9 @@ msgstr ""
msgid "Invalid query"
msgstr ""
+msgid "Invalid repository bundle for snippet with id %{snippet_id}"
+msgstr ""
+
msgid "Invalid repository path"
msgstr ""
@@ -12126,6 +12455,9 @@ msgstr ""
msgid "Invalid two-factor code."
msgstr ""
+msgid "Invalid yaml"
+msgstr ""
+
msgid "Invitation"
msgstr ""
@@ -12345,15 +12677,24 @@ msgstr ""
msgid "It seems like the Dependency Scanning job ran successfully, but no dependencies have been detected in your project."
msgstr ""
+msgid "It seems that there is currently no available data for code coverage"
+msgstr ""
+
msgid "It's you"
msgstr ""
+msgid "Iteration"
+msgstr ""
+
msgid "Iteration changed to"
msgstr ""
msgid "Iteration removed"
msgstr ""
+msgid "Iteration updated"
+msgstr ""
+
msgid "Iterations"
msgstr ""
@@ -12384,12 +12725,18 @@ msgstr ""
msgid "January"
msgstr ""
+msgid "Jira Issues"
+msgstr ""
+
msgid "Jira import is already running."
msgstr ""
msgid "Jira integration not configured."
msgstr ""
+msgid "Jira project key is not configured"
+msgstr ""
+
msgid "Jira project: %{importProject}"
msgstr ""
@@ -12570,6 +12917,9 @@ msgstr ""
msgid "Keep divergent refs"
msgstr ""
+msgid "Kerberos access denied"
+msgstr ""
+
msgid "Key"
msgstr ""
@@ -12579,10 +12929,13 @@ msgstr ""
msgid "Key: %{key}"
msgstr ""
-msgid "Keyboard Shortcuts"
+msgid "Keyboard shortcuts"
msgstr ""
-msgid "Keyboard shortcuts"
+msgid "Keys"
+msgstr ""
+
+msgid "Ki"
msgstr ""
msgid "Kubernetes"
@@ -12624,6 +12977,9 @@ msgstr ""
msgid "LDAP"
msgstr ""
+msgid "LDAP Synchronization"
+msgstr ""
+
msgid "LDAP settings"
msgstr ""
@@ -12848,12 +13204,18 @@ msgstr ""
msgid "Learn more about custom project templates"
msgstr ""
+msgid "Learn more about deploying to AWS"
+msgstr ""
+
msgid "Learn more about deploying to a cluster"
msgstr ""
msgid "Learn more about group-level project templates"
msgstr ""
+msgid "Learn more about job dependencies"
+msgstr ""
+
msgid "Learn more about signing commits"
msgstr ""
@@ -12905,9 +13267,21 @@ msgstr ""
msgid "License History"
msgstr ""
+msgid "License ID:"
+msgstr ""
+
+msgid "License URL"
+msgstr ""
+
msgid "License-Check"
msgstr ""
+msgid "LicenseCompliance|%{docLinkStart}License Approvals%{docLinkEnd} are active"
+msgstr ""
+
+msgid "LicenseCompliance|%{docLinkStart}License Approvals%{docLinkEnd} are inactive"
+msgstr ""
+
msgid "LicenseCompliance|Add a license"
msgstr ""
@@ -12929,9 +13303,15 @@ msgstr ""
msgid "LicenseCompliance|Deny"
msgstr ""
+msgid "LicenseCompliance|Learn more about %{linkStart}License Approvals%{linkEnd}"
+msgstr ""
+
msgid "LicenseCompliance|License"
msgstr ""
+msgid "LicenseCompliance|License Approvals"
+msgstr ""
+
msgid "LicenseCompliance|License Compliance detected %d license and policy violation for the source branch only; approval required"
msgid_plural "LicenseCompliance|License Compliance detected %d licenses and policy violations for the source branch only; approval required"
msgstr[0] ""
@@ -13152,6 +13532,9 @@ msgstr ""
msgid "List your Bitbucket Server repositories"
msgstr ""
+msgid "Lists"
+msgstr ""
+
msgid "Live preview"
msgstr ""
@@ -13335,6 +13718,9 @@ msgstr ""
msgid "Manage project labels"
msgstr ""
+msgid "Manage storage usage"
+msgstr ""
+
msgid "Manage two-factor authentication"
msgstr ""
@@ -13350,6 +13736,9 @@ msgstr ""
msgid "Manifest file import"
msgstr ""
+msgid "Manifest import"
+msgstr ""
+
msgid "Manual job"
msgstr ""
@@ -13497,6 +13886,12 @@ msgstr ""
msgid "Maximum field length"
msgstr ""
+msgid "Maximum file size is 2MB. Please select a smaller file."
+msgstr ""
+
+msgid "Maximum import size (MB)"
+msgstr ""
+
msgid "Maximum job timeout"
msgstr ""
@@ -13536,6 +13931,9 @@ msgstr ""
msgid "Maximum size of Elasticsearch bulk indexing requests."
msgstr ""
+msgid "Maximum size of import files."
+msgstr ""
+
msgid "Maximum size of individual attachments in comments."
msgstr ""
@@ -13587,6 +13985,9 @@ msgstr ""
msgid "Merge (when the pipeline succeeds)"
msgstr ""
+msgid "Merge Conflicts"
+msgstr ""
+
msgid "Merge Request"
msgstr ""
@@ -13713,6 +14114,9 @@ msgstr ""
msgid "MergeRequests|Squash task canceled: another squash is already in progress."
msgstr ""
+msgid "MergeRequests|This project does not allow squashing commits when merge requests are accepted."
+msgstr ""
+
msgid "MergeRequests|Thread stays resolved"
msgstr ""
@@ -13866,6 +14270,12 @@ msgstr ""
msgid "MetricsSettings|Add a button to the metrics dashboard linking directly to your existing external dashboard."
msgstr ""
+msgid "MetricsSettings|Choose whether to display dashboard metrics in UTC or the user's local timezone."
+msgstr ""
+
+msgid "MetricsSettings|Dashboard timezone"
+msgstr ""
+
msgid "MetricsSettings|External dashboard URL"
msgstr ""
@@ -13875,6 +14285,12 @@ msgstr ""
msgid "MetricsSettings|Metrics Dashboard"
msgstr ""
+msgid "MetricsSettings|UTC (Coordinated Universal Time)"
+msgstr ""
+
+msgid "MetricsSettings|User's local timezone"
+msgstr ""
+
msgid "Metrics|Add metric"
msgstr ""
@@ -13890,6 +14306,9 @@ msgstr ""
msgid "Metrics|Create metric"
msgstr ""
+msgid "Metrics|Current"
+msgstr ""
+
msgid "Metrics|Delete metric"
msgstr ""
@@ -13940,6 +14359,9 @@ msgstr ""
msgid "Metrics|Max"
msgstr ""
+msgid "Metrics|Min"
+msgstr ""
+
msgid "Metrics|Must be a valid PromQL query."
msgstr ""
@@ -13955,6 +14377,9 @@ msgstr ""
msgid "Metrics|Refresh dashboard"
msgstr ""
+msgid "Metrics|Select a value"
+msgstr ""
+
msgid "Metrics|Star dashboard"
msgstr ""
@@ -13973,12 +14398,18 @@ msgstr ""
msgid "Metrics|There was an error getting annotations information."
msgstr ""
+msgid "Metrics|There was an error getting dashboard validation warnings information."
+msgstr ""
+
msgid "Metrics|There was an error getting deployment information."
msgstr ""
msgid "Metrics|There was an error getting environments information."
msgstr ""
+msgid "Metrics|There was an error getting options for variable \"%{name}\"."
+msgstr ""
+
msgid "Metrics|There was an error trying to validate your query"
msgstr ""
@@ -14021,6 +14452,9 @@ msgstr ""
msgid "Metrics|You're about to permanently delete this metric. This cannot be undone."
msgstr ""
+msgid "Metrics|Your dashboard schema is invalid. Edit the dashboard to correct the YAML schema."
+msgstr ""
+
msgid "Metrics|e.g. HTTP requests"
msgstr ""
@@ -14036,12 +14470,18 @@ msgstr ""
msgid "Metrics|e.g. req/sec"
msgstr ""
+msgid "Mi"
+msgstr ""
+
msgid "Microsoft Azure"
msgstr ""
msgid "Middleman project with Static Site Editor support"
msgstr ""
+msgid "Migrate your data from an external source like GitHub, Bitbucket, or another instance of GitLab."
+msgstr ""
+
msgid "Migrated %{success_count}/%{total_count} files."
msgstr ""
@@ -14176,6 +14616,9 @@ msgstr ""
msgid "Minimum capacity to be available before we schedule more mirrors preemptively."
msgstr ""
+msgid "Minimum interval in days"
+msgstr ""
+
msgid "Minimum length is %{minimum_password_length} characters"
msgstr ""
@@ -14362,6 +14805,12 @@ msgstr ""
msgid "Multiple uploaders found: %{uploader_types}"
msgstr ""
+msgid "Must match with the %{codeStart}external_url%{codeEnd} in %{codeStart}/etc/gitlab/gitlab.rb%{codeEnd}."
+msgstr ""
+
+msgid "Must match with the %{codeStart}geo_node_name%{codeEnd} in %{codeStart}/etc/gitlab/gitlab.rb%{codeEnd}. %{linkStart}More information%{linkEnd}"
+msgstr ""
+
msgid "My Awesome Group"
msgstr ""
@@ -14476,10 +14925,16 @@ msgstr ""
msgid "New Application"
msgstr ""
+msgid "New Branch"
+msgstr ""
+
+msgid "New Deploy Key"
+msgstr ""
+
msgid "New Environment"
msgstr ""
-msgid "New Geo Node"
+msgid "New File"
msgstr ""
msgid "New Group"
@@ -14496,15 +14951,15 @@ msgid_plural "New Issues"
msgstr[0] ""
msgstr[1] ""
-msgid "New Iteration"
-msgstr ""
-
msgid "New Jira import"
msgstr ""
msgid "New Label"
msgstr ""
+msgid "New Merge Request"
+msgstr ""
+
msgid "New Milestone"
msgstr ""
@@ -14523,6 +14978,9 @@ msgstr ""
msgid "New Snippet"
msgstr ""
+msgid "New User"
+msgstr ""
+
msgid "New branch"
msgstr ""
@@ -14565,6 +15023,9 @@ msgstr ""
msgid "New issue title"
msgstr ""
+msgid "New iteration"
+msgstr ""
+
msgid "New iteration created"
msgstr ""
@@ -14742,6 +15203,12 @@ msgstr ""
msgid "No grouping"
msgstr ""
+msgid "No iteration"
+msgstr ""
+
+msgid "No iterations to show"
+msgstr ""
+
msgid "No job log"
msgstr ""
@@ -14784,6 +15251,9 @@ msgstr ""
msgid "No other labels with such name or description"
msgstr ""
+msgid "No panels matching properties %{opts}"
+msgstr ""
+
msgid "No parent group"
msgstr ""
@@ -14817,6 +15287,9 @@ msgstr ""
msgid "No schedules"
msgstr ""
+msgid "No stack trace for this error"
+msgstr ""
+
msgid "No starrers matched your search"
msgstr ""
@@ -14829,7 +15302,7 @@ msgstr ""
msgid "No test coverage"
msgstr ""
-msgid "No thanks, don't show this again"
+msgid "No thanks"
msgstr ""
msgid "No vulnerabilities present"
@@ -14844,9 +15317,6 @@ msgstr ""
msgid "No, directly import the existing email addresses and usernames."
msgstr ""
-msgid "No, not interested right now"
-msgstr ""
-
msgid "No. of commits"
msgstr ""
@@ -14892,9 +15362,6 @@ msgstr ""
msgid "Not found."
msgstr ""
-msgid "Not helpful"
-msgstr ""
-
msgid "Not now"
msgstr ""
@@ -15051,6 +15518,9 @@ msgstr ""
msgid "Notifications on"
msgstr ""
+msgid "Notify users by email when sign-in location is not recognized"
+msgstr ""
+
msgid "Nov"
msgstr ""
@@ -15117,9 +15587,6 @@ msgstr ""
msgid "Oh no!"
msgstr ""
-msgid "Ok let's go"
-msgstr ""
-
msgid "Oldest first"
msgstr ""
@@ -15132,16 +15599,49 @@ msgstr ""
msgid "On track"
msgstr ""
+msgid "OnDemandScans|Attached branch"
+msgstr ""
+
+msgid "OnDemandScans|Attached branch is where the scan job runs."
+msgstr ""
+
+msgid "OnDemandScans|Could not run the scan. Please try again."
+msgstr ""
+
msgid "OnDemandScans|Create new DAST scan"
msgstr ""
+msgid "OnDemandScans|DAST will scan the target URL and any discovered sub URLs."
+msgstr ""
+
+msgid "OnDemandScans|New on-demand DAST scan"
+msgstr ""
+
msgid "OnDemandScans|On-demand Scans"
msgstr ""
+msgid "OnDemandScans|On-demand scans run outside the DevOps cycle and find vulnerabilities in your projects. %{learnMoreLinkStart}Learn more%{learnMoreLinkEnd}"
+msgstr ""
+
+msgid "OnDemandScans|Only a passive scan can be performed on demand."
+msgstr ""
+
+msgid "OnDemandScans|Passive DAST Scan"
+msgstr ""
+
+msgid "OnDemandScans|Please enter a valid URL format, ex: http://www.example.com/home"
+msgstr ""
+
+msgid "OnDemandScans|Run this scan"
+msgstr ""
+
+msgid "OnDemandScans|Scan mode"
+msgstr ""
+
msgid "OnDemandScans|Schedule or run scans immediately against target sites. Currently available on-demand scan type: DAST. %{helpLinkStart}More information%{helpLinkEnd}"
msgstr ""
-msgid "Onboarding"
+msgid "OnDemandScans|Target URL"
msgstr ""
msgid "Once imported, repositories can be mirrored over SSH. Read more %{link_start}here%{link_end}."
@@ -15167,6 +15667,9 @@ msgstr ""
msgid "One or more of you personal access tokens were revoked"
msgstr ""
+msgid "One or more of your %{provider} projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
+msgstr ""
+
msgid "One or more of your Bitbucket projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
msgstr ""
@@ -15668,6 +16171,9 @@ msgstr ""
msgid "Parent epic is not present."
msgstr ""
+msgid "Parsing error for param :embed_json. %{message}"
+msgstr ""
+
msgid "Part of merge request changes"
msgstr ""
@@ -15680,6 +16186,9 @@ msgstr ""
msgid "Passed"
msgstr ""
+msgid "Passed on"
+msgstr ""
+
msgid "Password"
msgstr ""
@@ -15764,6 +16273,9 @@ msgstr ""
msgid "Perform common operations on GitLab project"
msgstr ""
+msgid "Performance and resource management"
+msgstr ""
+
msgid "Performance optimization"
msgstr ""
@@ -15971,7 +16483,7 @@ msgstr ""
msgid "Pipelines|Something went wrong while cleaning runners cache."
msgstr ""
-msgid "Pipelines|There are currently no %{scope} pipelines."
+msgid "Pipelines|There are currently no finished pipelines."
msgstr ""
msgid "Pipelines|There are currently no pipelines."
@@ -16082,6 +16594,9 @@ msgstr ""
msgid "Pipeline|Stop pipeline #%{pipelineId}?"
msgstr ""
+msgid "Pipeline|Tag name"
+msgstr ""
+
msgid "Pipeline|Trigger author"
msgstr ""
@@ -16163,12 +16678,18 @@ msgstr ""
msgid "Please check your email (%{email}) to verify that you own this address and unlock the power of CI/CD. Didn't receive it? %{resend_link}. Wrong email address? %{update_link}."
msgstr ""
+msgid "Please choose a file"
+msgstr ""
+
msgid "Please choose a group URL with no special characters."
msgstr ""
msgid "Please complete your profile with email address"
msgstr ""
+msgid "Please convert %{linkStart}them to Git%{linkEnd}, and go through the %{linkToImportFlow} again."
+msgstr ""
+
msgid "Please convert them to %{link_to_git}, and go through the %{link_to_import_flow} again."
msgstr ""
@@ -16226,6 +16747,9 @@ msgstr ""
msgid "Please provide a valid email address."
msgstr ""
+msgid "Please provide attributes to update"
+msgstr ""
+
msgid "Please refer to <a href=\"%{docs_url}\">%{docs_url}</a>"
msgstr ""
@@ -16961,6 +17485,9 @@ msgstr ""
msgid "Project has too many %{label_for_message} to search"
msgstr ""
+msgid "Project is required when cluster_type is :project"
+msgstr ""
+
msgid "Project members"
msgstr ""
@@ -17078,7 +17605,40 @@ msgstr ""
msgid "ProjectService|Comment"
msgstr ""
-msgid "ProjectService|Comment will be posted on each event"
+msgid "ProjectService|Event will be triggered by a push to the repository"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a commit is created/updated"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a confidential issue is created/updated/closed"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a deployment finishes"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a merge request is created/updated/merged"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a new tag is pushed to the repository"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a new, unique alert is recorded"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a pipeline status changes"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a wiki page is created/updated"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when an issue is created/updated/closed"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when someone adds a comment"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when someone adds a comment on a confidential issue"
msgstr ""
msgid "ProjectService|Perform common operations on GitLab project: %{project_name}"
@@ -17129,6 +17689,9 @@ msgstr ""
msgid "ProjectSettings|Enable 'Delete source branch' option by default"
msgstr ""
+msgid "ProjectSettings|Enable merge trains and pipelines for merged results"
+msgstr ""
+
msgid "ProjectSettings|Every merge creates a merge commit"
msgstr ""
@@ -17159,7 +17722,7 @@ msgstr ""
msgid "ProjectSettings|Forks"
msgstr ""
-msgid "ProjectSettings|Git Large File Storage"
+msgid "ProjectSettings|Git Large File Storage (LFS)"
msgstr ""
msgid "ProjectSettings|Internal"
@@ -17195,9 +17758,6 @@ msgstr ""
msgid "ProjectSettings|Merge options"
msgstr ""
-msgid "ProjectSettings|Merge pipelines will try to validate the post-merge result prior to merging"
-msgstr ""
-
msgid "ProjectSettings|Merge requests"
msgstr ""
@@ -17225,6 +17785,9 @@ msgstr ""
msgid "ProjectSettings|Pipelines"
msgstr ""
+msgid "ProjectSettings|Pipelines for merge requests must be enabled in the CI/CD configuration file, or pipelines could be unresolvable or dropped"
+msgstr ""
+
msgid "ProjectSettings|Pipelines must succeed"
msgstr ""
@@ -17252,6 +17815,9 @@ msgstr ""
msgid "ProjectSettings|Show link to create/view merge request when pushing from the command line"
msgstr ""
+msgid "ProjectSettings|Skipped pipelines are considered successful"
+msgstr ""
+
msgid "ProjectSettings|Snippets"
msgstr ""
@@ -17267,6 +17833,9 @@ msgstr ""
msgid "ProjectSettings|These checks must pass before merge requests can be merged"
msgstr ""
+msgid "ProjectSettings|This introduces the risk of merging changes that will not pass the pipeline."
+msgstr ""
+
msgid "ProjectSettings|This setting is applied on the server level and can be overridden by an admin."
msgstr ""
@@ -17444,15 +18013,27 @@ msgstr ""
msgid "ProjectsNew|Blank project"
msgstr ""
+msgid "ProjectsNew|Connect your external repository to GitLab CI/CD."
+msgstr ""
+
msgid "ProjectsNew|Contact an administrator to enable options for importing your project."
msgstr ""
msgid "ProjectsNew|Create"
msgstr ""
+msgid "ProjectsNew|Create a blank project to house your files, plan your work, and collaborate on code, among other things."
+msgstr ""
+
+msgid "ProjectsNew|Create blank project"
+msgstr ""
+
msgid "ProjectsNew|Create from template"
msgstr ""
+msgid "ProjectsNew|Create new project"
+msgstr ""
+
msgid "ProjectsNew|Creating project & repository."
msgstr ""
@@ -17477,6 +18058,9 @@ msgstr ""
msgid "ProjectsNew|Project description %{tag_start}(optional)%{tag_end}"
msgstr ""
+msgid "ProjectsNew|Run CI/CD for external repository"
+msgstr ""
+
msgid "ProjectsNew|Template"
msgstr ""
@@ -17738,6 +18322,9 @@ msgstr ""
msgid "Protected Branch"
msgstr ""
+msgid "Protected Branches"
+msgstr ""
+
msgid "Protected Environment"
msgstr ""
@@ -17750,6 +18337,9 @@ msgstr ""
msgid "Protected Tag"
msgstr ""
+msgid "Protected Tags"
+msgstr ""
+
msgid "Protected branches"
msgstr ""
@@ -17849,6 +18439,9 @@ msgstr ""
msgid "Provider"
msgstr ""
+msgid "Proxy support for this API is not available currently"
+msgstr ""
+
msgid "Pseudonymizer data collection"
msgstr ""
@@ -17873,6 +18466,9 @@ msgstr ""
msgid "Publish to status page"
msgstr ""
+msgid "Published on status page"
+msgstr ""
+
msgid "Publishes this issue to the associated status page."
msgstr ""
@@ -18047,6 +18643,9 @@ msgstr ""
msgid "Recipe"
msgstr ""
+msgid "Reconfigure"
+msgstr ""
+
msgid "Recover hidden stage"
msgstr ""
@@ -18133,6 +18732,9 @@ msgstr ""
msgid "Registration|Your profile"
msgstr ""
+msgid "Regulate approvals by authors/committers, based on compliance frameworks. Can be changed only at the instance level."
+msgstr ""
+
msgid "Rejected (closed)"
msgstr ""
@@ -18183,15 +18785,24 @@ msgstr ""
msgid "ReleaseAssetLinkType|Image"
msgstr ""
+msgid "ReleaseAssetLinkType|Images"
+msgstr ""
+
msgid "ReleaseAssetLinkType|Other"
msgstr ""
msgid "ReleaseAssetLinkType|Package"
msgstr ""
+msgid "ReleaseAssetLinkType|Packages"
+msgstr ""
+
msgid "ReleaseAssetLinkType|Runbook"
msgstr ""
+msgid "ReleaseAssetLinkType|Runbooks"
+msgstr ""
+
msgid "Releases"
msgstr ""
@@ -18204,6 +18815,9 @@ msgstr ""
msgid "Releases documentation"
msgstr ""
+msgid "Releases|New Release"
+msgstr ""
+
msgid "Release|Something went wrong while getting the release details"
msgstr ""
@@ -18276,6 +18890,9 @@ msgstr ""
msgid "Remove fork relationship"
msgstr ""
+msgid "Remove from batch"
+msgstr ""
+
msgid "Remove from board"
msgstr ""
@@ -18465,6 +19082,12 @@ msgstr ""
msgid "Replication"
msgstr ""
+msgid "Replication enabled"
+msgstr ""
+
+msgid "Replication paused"
+msgstr ""
+
msgid "Reply by email"
msgstr ""
@@ -18492,15 +19115,15 @@ msgstr ""
msgid "Reported %{timeAgo} by %{reportedBy}"
msgstr ""
-msgid "Reported Resource Changes: %{addNum} to add, %{changeNum} to change, %{deleteNum} to delete"
-msgstr ""
-
msgid "Reporter"
msgstr ""
msgid "Reporting"
msgstr ""
+msgid "Reports"
+msgstr ""
+
msgid "Reports|%{combinedString} and %{resolvedString}"
msgstr ""
@@ -18539,6 +19162,9 @@ msgstr ""
msgid "Reports|Failure"
msgstr ""
+msgid "Reports|Identifier"
+msgstr ""
+
msgid "Reports|Metrics reports are loading"
msgstr ""
@@ -18551,6 +19177,9 @@ msgstr ""
msgid "Reports|Metrics reports failed loading results"
msgstr ""
+msgid "Reports|Scanner"
+msgstr ""
+
msgid "Reports|Severity"
msgstr ""
@@ -18599,15 +19228,33 @@ msgstr ""
msgid "Repository cleanup has started. You will receive an email once the cleanup operation is complete."
msgstr ""
+msgid "Repository files count over the limit"
+msgstr ""
+
+msgid "Repository has an invalid default branch name."
+msgstr ""
+
+msgid "Repository has more than one branch."
+msgstr ""
+
msgid "Repository has no locks."
msgstr ""
+msgid "Repository has tags."
+msgstr ""
+
msgid "Repository maintenance"
msgstr ""
msgid "Repository mirroring"
msgstr ""
+msgid "Repository must contain at least 1 file."
+msgstr ""
+
+msgid "Repository size is above the limit."
+msgstr ""
+
msgid "Repository static objects"
msgstr ""
@@ -18617,7 +19264,7 @@ msgstr ""
msgid "Repository sync capacity"
msgstr ""
-msgid "Repository: %{counter_repositories} / Wikis: %{counter_wikis} / Build Artifacts: %{counter_build_artifacts} / LFS: %{counter_lfs_objects}"
+msgid "Repository: %{counter_repositories} / Wikis: %{counter_wikis} / Build Artifacts: %{counter_build_artifacts} / LFS: %{counter_lfs_objects} / Snippets: %{counter_snippets}"
msgstr ""
msgid "RepositorySettingsAccessLevel|Select"
@@ -18629,6 +19276,9 @@ msgstr ""
msgid "Request Headers"
msgstr ""
+msgid "Request details"
+msgstr ""
+
msgid "Request parameter %{param} is missing."
msgstr ""
@@ -18735,6 +19385,9 @@ msgstr ""
msgid "Reset template"
msgstr ""
+msgid "Reset to project defaults"
+msgstr ""
+
msgid "Resetting the authorization key for this project will require updating the authorization key in every alert source it is enabled in."
msgstr ""
@@ -18831,9 +19484,6 @@ msgstr ""
msgid "Resume"
msgstr ""
-msgid "Resume replication"
-msgstr ""
-
msgid "Resync"
msgstr ""
@@ -19456,6 +20106,9 @@ msgstr ""
msgid "SecurityReports|False positive"
msgstr ""
+msgid "SecurityReports|Group Security Dashboard"
+msgstr ""
+
msgid "SecurityReports|Hide dismissed"
msgstr ""
@@ -19510,10 +20163,13 @@ msgstr ""
msgid "SecurityReports|Remove project from dashboard"
msgstr ""
-msgid "SecurityReports|Report type"
+msgid "SecurityReports|Return to dashboard"
msgstr ""
-msgid "SecurityReports|Return to dashboard"
+msgid "SecurityReports|Scan details"
+msgstr ""
+
+msgid "SecurityReports|Scanner type"
msgstr ""
msgid "SecurityReports|Security Dashboard"
@@ -19570,6 +20226,9 @@ msgstr ""
msgid "SecurityReports|There was an error while generating the report."
msgstr ""
+msgid "SecurityReports|Unable to add %{invalidProjectsMessage}"
+msgstr ""
+
msgid "SecurityReports|Unable to add %{invalidProjects}"
msgstr ""
@@ -19684,6 +20343,9 @@ msgstr ""
msgid "Select due date"
msgstr ""
+msgid "Select file"
+msgstr ""
+
msgid "Select group or project"
msgstr ""
@@ -19747,9 +20409,6 @@ msgstr ""
msgid "Select the branch you want to set as the default for this project. All merge requests and commits will automatically be made against this branch unless you specify a different one."
msgstr ""
-msgid "Select the configured storage available for new repositories to be placed on."
-msgstr ""
-
msgid "Select the custom project template source group."
msgstr ""
@@ -19942,12 +20601,6 @@ msgstr ""
msgid "Service Desk is enabled but not yet active"
msgstr ""
-msgid "Service Desk is off"
-msgstr ""
-
-msgid "Service Desk is on"
-msgstr ""
-
msgid "Service Templates"
msgstr ""
@@ -20035,6 +20688,15 @@ msgstr ""
msgid "Set the milestone to %{milestone_reference}."
msgstr ""
+msgid "Set the number of concurrent requests this secondary node will make to the primary node while backfilling."
+msgstr ""
+
+msgid "Set the synchronization and verification capacity for the secondary node."
+msgstr ""
+
+msgid "Set the timeout in seconds to send a secondary node status to the primary and IPs allowed for the secondary nodes."
+msgstr ""
+
msgid "Set time estimate"
msgstr ""
@@ -20074,6 +20736,9 @@ msgstr ""
msgid "Set weight to %{weight}."
msgstr ""
+msgid "Set what should be replicated by choosing specific projects or groups by the secondary node."
+msgstr ""
+
msgid "SetPasswordToCloneLink|set a password"
msgstr ""
@@ -20200,6 +20865,9 @@ msgstr ""
msgid "Show complete raw log"
msgstr ""
+msgid "Show details"
+msgstr ""
+
msgid "Show file browser"
msgstr ""
@@ -20209,10 +20877,13 @@ msgstr ""
msgid "Show latest version"
msgstr ""
+msgid "Show list"
+msgstr ""
+
msgid "Show me everything"
msgstr ""
-msgid "Show me how"
+msgid "Show me how to add a pipeline"
msgstr ""
msgid "Show me more advanced stuff"
@@ -20261,9 +20932,6 @@ msgstr ""
msgid "Showing all issues"
msgstr ""
-msgid "Showing all labels"
-msgstr ""
-
msgid "Showing last %{size} of log -"
msgstr ""
@@ -20375,9 +21043,6 @@ msgstr ""
msgid "Skip outdated deployment jobs"
msgstr ""
-msgid "Skip this for now"
-msgstr ""
-
msgid "Skipped"
msgstr ""
@@ -20522,6 +21187,9 @@ msgstr ""
msgid "Something went wrong while adding your award. Please try again."
msgstr ""
+msgid "Something went wrong while applying the batch of suggestions. Please try again."
+msgstr ""
+
msgid "Something went wrong while applying the suggestion. Please try again."
msgstr ""
@@ -20624,10 +21292,13 @@ msgstr ""
msgid "Something went wrong while updating a requirement."
msgstr ""
+msgid "Something went wrong while updating assignees"
+msgstr ""
+
msgid "Something went wrong while updating your list settings"
msgstr ""
-msgid "Something went wrong with your automatic subscription renewal"
+msgid "Something went wrong with your automatic subscription renewal."
msgstr ""
msgid "Something went wrong, unable to add %{project} to dashboard"
@@ -20645,6 +21316,9 @@ msgstr ""
msgid "Something went wrong, unable to search projects"
msgstr ""
+msgid "Something went wrong."
+msgstr ""
+
msgid "Something went wrong. Please try again."
msgstr ""
@@ -20828,6 +21502,9 @@ msgstr ""
msgid "Source code"
msgstr ""
+msgid "Source code (%{fileExtension})"
+msgstr ""
+
msgid "Source is not available"
msgstr ""
@@ -20912,6 +21589,9 @@ msgstr ""
msgid "Stack trace"
msgstr ""
+msgid "Stacktrace snippet"
+msgstr ""
+
msgid "Stage"
msgstr ""
@@ -21017,6 +21697,9 @@ msgstr ""
msgid "Start thread & reopen %{noteable_name}"
msgstr ""
+msgid "Start using Directed Acyclic Graphs (DAG)"
+msgstr ""
+
msgid "Start your Free Gold Trial"
msgstr ""
@@ -21479,6 +22162,21 @@ msgstr ""
msgid "SuggestedColors|Very pale orange"
msgstr ""
+msgid "Suggestion is not applicable as the suggestion was not found."
+msgstr ""
+
+msgid "Suggestions are not applicable as one or more suggestions were not found."
+msgstr ""
+
+msgid "Suggestions are not applicable as their lines cannot overlap."
+msgstr ""
+
+msgid "Suggestions must all be on the same branch."
+msgstr ""
+
+msgid "Suggestions that change line count can't be added to batches, yet."
+msgstr ""
+
msgid "Suggestions:"
msgstr ""
@@ -21521,9 +22219,6 @@ msgstr ""
msgid "Synchronization disabled"
msgstr ""
-msgid "Synchronization of container repositories is disabled."
-msgstr ""
-
msgid "System"
msgstr ""
@@ -21704,6 +22399,34 @@ msgstr ""
msgid "Terms of Service and Privacy Policy"
msgstr ""
+msgid "Terraform|%{number} Terraform report failed to generate"
+msgid_plural "Terraform|%{number} Terraform reports failed to generate"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Terraform|%{number} Terraform report was generated in your pipelines"
+msgid_plural "Terraform|%{number} Terraform reports were generated in your pipelines"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Terraform|A Terraform report failed to generate."
+msgstr ""
+
+msgid "Terraform|A Terraform report was generated in your pipelines."
+msgstr ""
+
+msgid "Terraform|Generating the report caused an error."
+msgstr ""
+
+msgid "Terraform|Reported Resource Changes: %{addNum} to add, %{changeNum} to change, %{deleteNum} to delete"
+msgstr ""
+
+msgid "Terraform|The Terraform report %{name} failed to generate."
+msgstr ""
+
+msgid "Terraform|The Terraform report %{name} was generated in your pipelines."
+msgstr ""
+
msgid "Test"
msgstr ""
@@ -21730,7 +22453,7 @@ msgstr ""
msgid "TestHooks|Ensure the project has CI pipelines."
msgstr ""
-msgid "TestHooks|Ensure the project has at least one commit."
+msgid "TestHooks|Ensure the project has deployments."
msgstr ""
msgid "TestHooks|Ensure the project has issues."
@@ -21831,7 +22554,7 @@ msgstr ""
msgid "The Prometheus server responded with \"bad request\". Please check your queries are correct and are supported in your Prometheus version. %{documentationLink}"
msgstr ""
-msgid "The URL defined on the primary node that secondary nodes should use to contact it. Defaults to URL"
+msgid "The URL defined on the primary node that secondary nodes should use to contact it."
msgstr ""
msgid "The URL to use for connecting to Elasticsearch. Use a comma-separated list to support clustering (e.g., \"http://localhost:9200, http://localhost:9201\")."
@@ -21840,6 +22563,9 @@ msgstr ""
msgid "The X509 Certificate to use when mutual TLS is required to communicate with the external authorization service. If left blank, the server certificate is still validated when accessing over HTTPS."
msgstr ""
+msgid "The above settings apply to all projects with the selected compliance framework(s)."
+msgstr ""
+
msgid "The amount of seconds after which a request to get a secondary node status will time out."
msgstr ""
@@ -22139,9 +22865,6 @@ msgstr ""
msgid "The total stage shows the time it takes between creating an issue and deploying the code to production. The data will be automatically added once you have completed the full idea to production cycle."
msgstr ""
-msgid "The unique identifier for the Geo node. Must match %{geoNodeName} if it is set in gitlab.rb, otherwise it must match %{externalUrl} with a trailing slash"
-msgstr ""
-
msgid "The update action will time out after %{number_of_minutes} minutes. For big repositories, use a clone/push combination."
msgstr ""
@@ -22166,9 +22889,6 @@ msgstr ""
msgid "The user you are trying to deactivate has been active in the past %{minimum_inactive_days} days and cannot be deactivated"
msgstr ""
-msgid "The user-facing URL of the Geo node"
-msgstr ""
-
msgid "The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6."
msgstr ""
@@ -22259,6 +22979,9 @@ msgstr ""
msgid "There was a problem fetching project branches."
msgstr ""
+msgid "There was a problem fetching project tags."
+msgstr ""
+
msgid "There was a problem fetching project users."
msgstr ""
@@ -22310,6 +23033,9 @@ msgstr ""
msgid "There was an error fetching the %{replicableType}"
msgstr ""
+msgid "There was an error fetching the Geo Settings"
+msgstr ""
+
msgid "There was an error fetching the Node's Groups"
msgstr ""
@@ -22430,6 +23156,12 @@ msgstr ""
msgid "This %{issuable} is locked. Only <strong>project members</strong> can comment."
msgstr ""
+msgid "This %{noteableTypeText} is %{confidentialLinkStart}confidential%{linkEnd} and %{lockedLinkStart}locked%{linkEnd}."
+msgstr ""
+
+msgid "This %{noteableTypeText} is locked."
+msgstr ""
+
msgid "This %{viewer} could not be displayed because %{reason}. You can %{options} instead."
msgstr ""
@@ -22448,6 +23180,9 @@ msgstr ""
msgid "This action can lead to data loss. To prevent accidental actions we ask you to confirm your intention."
msgstr ""
+msgid "This also resolves all related threads"
+msgstr ""
+
msgid "This also resolves the discussion"
msgstr ""
@@ -22538,6 +23273,9 @@ msgstr ""
msgid "This epic does not exist or you don't have sufficient permission."
msgstr ""
+msgid "This feature is currently in beta. We invite you to %{linkStart}give feedback%{linkEnd}."
+msgstr ""
+
msgid "This feature requires local storage to be enabled"
msgstr ""
@@ -22571,7 +23309,7 @@ msgstr ""
msgid "This is a Work in Progress"
msgstr ""
-msgid "This is a confidential issue."
+msgid "This is a confidential %{noteableTypeText}."
msgstr ""
msgid "This is a delayed job to run in %{remainingTime}"
@@ -22598,9 +23336,6 @@ msgstr ""
msgid "This is your current session"
msgstr ""
-msgid "This issue is %{confidentialLinkStart}confidential%{linkEnd} and %{lockedLinkStart}locked%{linkEnd}."
-msgstr ""
-
msgid "This issue is confidential"
msgstr ""
@@ -22610,9 +23345,6 @@ msgstr ""
msgid "This issue is in a child epic of the filtered epic"
msgstr ""
-msgid "This issue is locked."
-msgstr ""
-
msgid "This job depends on other jobs with expired/erased artifacts: %{invalid_dependencies}"
msgstr ""
@@ -22712,6 +23444,9 @@ msgstr ""
msgid "This license has already expired."
msgstr ""
+msgid "This link points to external content"
+msgstr ""
+
msgid "This may expose confidential information as the selected fork is in another namespace that can have other members."
msgstr ""
@@ -22739,6 +23474,9 @@ msgstr ""
msgid "This page will be removed in a future release."
msgstr ""
+msgid "This pipeline does not use the %{codeStart}needs%{codeEnd} keyword and can't be represented as a directed acyclic graph."
+msgstr ""
+
msgid "This pipeline makes use of a predefined CI/CD configuration enabled by %{strongStart}Auto DevOps.%{strongEnd}"
msgstr ""
@@ -22967,6 +23705,9 @@ msgstr ""
msgid "Time from last commit to merge"
msgstr ""
+msgid "Time in seconds"
+msgstr ""
+
msgid "Time in seconds GitLab will wait for a response from the external service. When the service does not respond in time, access will be denied."
msgstr ""
@@ -23388,9 +24129,15 @@ msgstr ""
msgid "Total artifacts size: %{total_size}"
msgstr ""
+msgid "Total cores (CPUs)"
+msgstr ""
+
msgid "Total issues"
msgstr ""
+msgid "Total memory (GB)"
+msgstr ""
+
msgid "Total test time for all commits/merges"
msgstr ""
@@ -23592,6 +24339,9 @@ msgstr ""
msgid "U2F only works with HTTPS-enabled websites. Contact your administrator for more details."
msgstr ""
+msgid "UI Development Kit"
+msgstr ""
+
msgid "URL"
msgstr ""
@@ -23664,9 +24414,6 @@ msgstr ""
msgid "Unable to load the merge request widget. Try reloading the page."
msgstr ""
-msgid "Unable to process group import file"
-msgstr ""
-
msgid "Unable to resolve"
msgstr ""
@@ -23682,6 +24429,9 @@ msgstr ""
msgid "Unable to sign you in to the group with SAML due to \"%{reason}\""
msgstr ""
+msgid "Unable to suggest a path. Please refresh and try again."
+msgstr ""
+
msgid "Unable to update label prioritization at this time"
msgstr ""
@@ -23697,6 +24447,9 @@ msgstr ""
msgid "Unarchiving the project will restore people's ability to make changes to it. The repository can be committed to, and issues, comments, and other entities can be created. %{strong_start}Once active, this project shows up in the search and on the dashboard.%{strong_end}"
msgstr ""
+msgid "Unassign from commenting user"
+msgstr ""
+
msgid "Unblock"
msgstr ""
@@ -23772,6 +24525,9 @@ msgstr ""
msgid "Unreachable"
msgstr ""
+msgid "Unrecognized cluster type"
+msgstr ""
+
msgid "Unresolve"
msgstr ""
@@ -23832,6 +24588,9 @@ msgstr ""
msgid "Unsubscribes from this %{quick_action_target}."
msgstr ""
+msgid "Unsupported todo type passed. Supported todo types are: %{todo_types}"
+msgstr ""
+
msgid "Until"
msgstr ""
@@ -23859,6 +24618,9 @@ msgstr ""
msgid "Update approval rule"
msgstr ""
+msgid "Update approvers"
+msgstr ""
+
msgid "Update failed"
msgstr ""
@@ -23868,6 +24630,9 @@ msgstr ""
msgid "Update it"
msgstr ""
+msgid "Update iteration"
+msgstr ""
+
msgid "Update now"
msgstr ""
@@ -23913,7 +24678,7 @@ msgstr ""
msgid "Updated at"
msgstr ""
-msgid "Updated to"
+msgid "Updated to %{linkStart}chart v%{linkEnd}"
msgstr ""
msgid "Updating"
@@ -23943,6 +24708,9 @@ msgstr ""
msgid "Upload CSV file"
msgstr ""
+msgid "Upload License"
+msgstr ""
+
msgid "Upload New File"
msgstr ""
@@ -24024,16 +24792,19 @@ msgstr ""
msgid "UsageQuota|Pipelines"
msgstr ""
+msgid "UsageQuota|Purchase more storage"
+msgstr ""
+
msgid "UsageQuota|Repositories"
msgstr ""
msgid "UsageQuota|Repository"
msgstr ""
-msgid "UsageQuota|Storage"
+msgid "UsageQuota|Snippets"
msgstr ""
-msgid "UsageQuota|Storage usage:"
+msgid "UsageQuota|Storage"
msgstr ""
msgid "UsageQuota|This namespace has no projects which use shared runners"
@@ -24066,6 +24837,12 @@ msgstr ""
msgid "UsageQuota|Wikis"
msgstr ""
+msgid "UsageQuota|You used: %{usage} %{limit}"
+msgstr ""
+
+msgid "UsageQuota|out of %{formattedLimit} of your namespace storage"
+msgstr ""
+
msgid "Use %{code_start}::%{code_end} to create a %{link_start}scoped label set%{link_end} (eg. %{code_start}priority::1%{code_end})"
msgstr ""
@@ -24132,6 +24909,9 @@ msgstr ""
msgid "User IDs"
msgstr ""
+msgid "User Lists can only be created and modified with %{linkStart}the API%{linkEnd}"
+msgstr ""
+
msgid "User OAuth applications"
msgstr ""
@@ -24156,6 +24936,9 @@ msgstr ""
msgid "User key was successfully removed."
msgstr ""
+msgid "User list %{name} will be removed. Are you sure?"
+msgstr ""
+
msgid "User map"
msgstr ""
@@ -24177,148 +24960,10 @@ msgstr ""
msgid "User was successfully updated."
msgstr ""
-msgid "UserOnboardingTour|%{activeTour}/%{totalTours}"
-msgstr ""
-
-msgid "UserOnboardingTour|%{completed}/%{total} steps completed"
-msgstr ""
-
-msgid "UserOnboardingTour|%{emphasisStart}Well done!%{emphasisEnd}%{lineBreak}%{lineBreak}That's it for our guided tour, congratulations for making it all the way to the end!%{lineBreak}%{lineBreak}We hope this gave you a good overview of GitLab and how it can help you. We'll now show you how to create your own project and invite your colleagues."
-msgstr ""
-
-msgid "UserOnboardingTour|Adding other members to a project is done through Project Settings. Click on %{emphasisStart}Settings%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Alright, that's it for Commits. Let's take a look at the %{emphasisStart}Branches%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Awesome! Now click on %{emphasisStart}Members%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Click on one of the %{emphasisStart}Compare%{emphasisEnd} buttons to compare a branch to master."
-msgstr ""
-
-msgid "UserOnboardingTour|Click on one of the %{emphasisStart}pipeline IDs%{emphasisEnd} to see the details of a pipeline."
-msgstr ""
-
-msgid "UserOnboardingTour|Click to open the latest commit to see its details."
-msgstr ""
-
-msgid "UserOnboardingTour|Close 'Learn GitLab'"
-msgstr ""
-
-msgid "UserOnboardingTour|Commits are shown in chronological order and can be filtered by the commit message or by the branch."
+msgid "UserList|Delete %{name}?"
msgstr ""
-msgid "UserOnboardingTour|Create a project"
-msgstr ""
-
-msgid "UserOnboardingTour|Exit 'Learn GitLab'"
-msgstr ""
-
-msgid "UserOnboardingTour|Got it"
-msgstr ""
-
-msgid "UserOnboardingTour|Great job! %{clapHands} We hope the tour was helpful and that you learned how to use GitLab.%{lineBreak}%{lineBreak}We'd love to get your feedback on this tour.%{lineBreak}%{lineBreak}%{emphasisStart}How helpful would you say this guided tour was?%{emphasisEnd}%{lineBreak}%{lineBreak}"
-msgstr ""
-
-msgid "UserOnboardingTour|Guided GitLab Tour"
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can compare the changes of this branch to another one. Changes are divided by files so that it's easier to see what was changed where."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can create a project from scratch, start with a template or import a repository from other platforms. Whatever you choose, we'll guide you through the process.%{lineBreak}%{lineBreak}Fill in your new project information and click on %{emphasisStart}Create Project%{emphasisEnd} to progress to the next step."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can see the breakdown of the pipelines: its stages and jobs in each of the stages and their status.%{lineBreak}%{lineBreak}Our CI/CD pipelines are quite complex, most of our users have fewer and simpler pipelines."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can see the current members of the project (just you at the moment) and invite new members.%{lineBreak}%{lineBreak}You can invite multiple members at once (existing GitLab users or invite by email) and you can also set their roles and permissions.%{lineBreak}%{lineBreak}Add a few members and click on %{emphasisStart}Add to project%{emphasisEnd} to complete this step."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can see what changes were made with this commit, on what branch and if there's a related merge request. The status of the pipeline will also show up if CI/CD is set up.%{lineBreak}%{lineBreak}You can also comment on the lines of code that were changed and start a discussion with your colleagues!"
-msgstr ""
-
-msgid "UserOnboardingTour|Here's an overview of branches in the %{emphasisStart}%{projectName}%{emphasisEnd} project. They're split into Active and Stale.%{lineBreak}%{lineBreak}From here, you can create a new merge request from a branch, or compare the branch to any other branch in the project. By default, it will compare it to the master branch."
-msgstr ""
-
-msgid "UserOnboardingTour|Invite colleagues"
-msgstr ""
-
-msgid "UserOnboardingTour|Issues are great for communicating and keeping track of progress in GitLab. These are all issues that are open in the %{emphasisStart}%{projectName}%{emphasisEnd}.%{lineBreak}%{lineBreak}You can help us improve GitLab by contributing work to issues that are labeled <span class=\"badge color-label accept-mr-label\">Accepting merge requests</span>.%{lineBreak}%{lineBreak}This list can be filtered by labels, milestones, assignees, authors... We'll show you how it looks when the list is filtered by a label."
-msgstr ""
-
-msgid "UserOnboardingTour|Learn GitLab"
-msgstr ""
-
-msgid "UserOnboardingTour|Let's take a closer look at a merge request. Click on the title of one."
-msgstr ""
-
-msgid "UserOnboardingTour|Let's take a closer look at all the commits. Click on %{emphasisStart}Commits%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Let's take a closer look at the repository of this project. Click on %{emphasisStart}Repository%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|No thanks"
-msgstr ""
-
-msgid "UserOnboardingTour|Ok, let's go"
-msgstr ""
-
-msgid "UserOnboardingTour|Ok, show me"
-msgstr ""
-
-msgid "UserOnboardingTour|Open one of the issues by clicking on its title."
-msgstr ""
-
-msgid "UserOnboardingTour|Restart this step"
-msgstr ""
-
-msgid "UserOnboardingTour|Skip this step"
-msgstr ""
-
-msgid "UserOnboardingTour|Sweet! Your project was created and is ready to be used.%{lineBreak}%{lineBreak}You can start adding files to the repository or clone it. One last thing we want to show you is how to invite your colleagues to your new project."
-msgstr ""
-
-msgid "UserOnboardingTour|Take a look. Here's a nifty menu for quickly creating issues, merge requests, snippets, projects and groups. Click on it and select \"New project\" from the \"GitLab\" section to get started."
-msgstr ""
-
-msgid "UserOnboardingTour|Thanks for taking the guided tour. Remember, if you want to go through it again, you can start %{emphasisStart}Learn GitLab%{emphasisEnd} in the help menu on the top right."
-msgstr ""
-
-msgid "UserOnboardingTour|Thanks for the feedback! %{thumbsUp}"
-msgstr ""
-
-msgid "UserOnboardingTour|That's it for issues. Let'st take a look at %{emphasisStart}Merge Requests%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|That's it for merge requests. Now for the final part of this guided tour - the %{emphasisStart}CI/CD%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|That's it for the Repository. Let's take a look at the %{emphasisStart}Issues%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|The structure of this page is very similar to issues. Status, description, discussion and the sidebar are all here.%{lineBreak}%{lineBreak}But take a look below the description and you'll notice that there's more information about the merge request, the CI/CD pipeline and the options for approving it.%{lineBreak}%{lineBreak}Alongside the discussion you can also see more information about commits in this merge request, the status of pipelines and review all changes that were made."
-msgstr ""
-
-msgid "UserOnboardingTour|There's a lot of information here but don't worry, we'll go through it.%{lineBreak}%{lineBreak}On the top you can see the status of the issue and when it was opened and by whom. Directly below it is the issue description and below that are other %{emphasisStart}related issues%{emphasisEnd} and %{emphasisStart}merge requests%{emphasisEnd} (if any). Then below that is the %{emphasisStart}discussion%{emphasisEnd}, that's where most of the communication happens.%{lineBreak}%{lineBreak}On the right, there's a sidebar where you can view/change the %{emphasisStart}assignee, milestone, due date, labels, weight%{emphasisEnd}, etc."
-msgstr ""
-
-msgid "UserOnboardingTour|These are all the CI/CD pipelines we have for our %{emphasisStart}%{projectName}%{emphasisEnd} project.%{lineBreak}%{lineBreak}Here you can see the status of each pipeline, for what commit it's running for, its stages and the status for them."
-msgstr ""
-
-msgid "UserOnboardingTour|These are all the issues that are available for community contributions. Let's take a closer look at one of them."
-msgstr ""
-
-msgid "UserOnboardingTour|This is an overview of all merge requests in this project. Similarly to the issues overview it can be filtered down by things like labels, milestones, authors, assignees, etc."
-msgstr ""
-
-msgid "UserOnboardingTour|This is the repository for the %{emphasisStart}%{projectName}%{emphasisEnd} project. All our code is stored here. Feel free to explore and take a closer look at folders and files.%{lineBreak}%{lineBreak}Above the file structure you can see the latest commit, who the author is and the status of the CI/CD pipeline.%{lineBreak}%{lineBreak}If you scroll down below the file structure, you'll find the Readme of this project. This is defined in the README.md file at the root of the repository."
-msgstr ""
-
-msgid "UserOnboardingTour|Welcome to the project overview of the %{emphasisStart}%{projectName}%{emphasisEnd} project. This is the project that we use to work on GitLab. At first, a project seems like a simple repository, but at GitLab, a project is so much more.%{lineBreak}%{lineBreak}You can create projects for hosting your codebase, use it as an issue tracker, collaborate on code, and continuously build, test, and deploy your app with built-in GitLab CI/CD."
+msgid "UserList|created %{timeago}"
msgstr ""
msgid "UserProfile|Activity"
@@ -24471,6 +25116,9 @@ msgstr ""
msgid "UsersSelect|Unassigned"
msgstr ""
+msgid "Using %{codeStart}needs%{codeEnd} allows jobs to run before their stage is reached, as soon as their individual dependencies are met, which speeds up your pipelines."
+msgstr ""
+
msgid "Using %{code_start}::%{code_end} denotes a %{link_start}scoped label set%{link_end}"
msgstr ""
@@ -24555,9 +25203,6 @@ msgstr ""
msgid "Versions"
msgstr ""
-msgid "Very helpful"
-msgstr ""
-
msgid "View Documentation"
msgstr ""
@@ -24706,6 +25351,9 @@ msgstr ""
msgid "VisibilityLevel|Unknown"
msgstr ""
+msgid "Visit settings page"
+msgstr ""
+
msgid "VisualReviewApp|%{stepStart}Step 1%{stepEnd}. Copy the following script:"
msgstr ""
@@ -24799,6 +25447,9 @@ msgstr ""
msgid "VulnerabilityManagement|Something went wrong while trying to delete the comment. Please try again later."
msgstr ""
+msgid "VulnerabilityManagement|Something went wrong while trying to refresh the vulnerability. Please try again later."
+msgstr ""
+
msgid "VulnerabilityManagement|Something went wrong while trying to retrieve the vulnerability history. Please try again later."
msgstr ""
@@ -24841,6 +25492,9 @@ msgstr ""
msgid "Vulnerability|Class"
msgstr ""
+msgid "Vulnerability|Crash Address"
+msgstr ""
+
msgid "Vulnerability|Description"
msgstr ""
@@ -24916,9 +25570,6 @@ msgstr ""
msgid "We couldn't reach the Prometheus server. Either the server no longer exists or the configuration details need updating."
msgstr ""
-msgid "We created a short guided tour that will help you learn the basics of GitLab and how it will help you be better at your job. It should only take a couple of minutes. You will be guided by two types of helpers, best recognized by their color."
-msgstr ""
-
msgid "We detected potential spam in the %{humanized_resource_name}. Please solve the reCAPTCHA to proceed."
msgstr ""
@@ -24952,6 +25603,9 @@ msgstr ""
msgid "We've found no vulnerabilities"
msgstr ""
+msgid "Web Application Firewall"
+msgstr ""
+
msgid "Web IDE"
msgstr ""
@@ -25012,9 +25666,6 @@ msgstr ""
msgid "Welcome to GitLab.com<br>@%{name}!"
msgstr ""
-msgid "Welcome to the Guided GitLab Tour"
-msgstr ""
-
msgid "Welcome to the guided GitLab tour"
msgstr ""
@@ -25062,9 +25713,6 @@ msgstr ""
msgid "While it's rare to have no vulnerabilities, it can happen. In any event, we ask that you please double check your settings to make sure you've set up your dashboard correctly."
msgstr ""
-msgid "White helpers give contextual information."
-msgstr ""
-
msgid "Who can be an approver?"
msgstr ""
@@ -25110,12 +25758,18 @@ msgstr ""
msgid "WikiEmptyIssueMessage|Suggest wiki improvement"
msgstr ""
+msgid "WikiEmptyIssueMessage|You must be a group member in order to add wiki pages. If you have suggestions for how to improve the wiki for this group, consider opening an issue in the %{issues_link}."
+msgstr ""
+
msgid "WikiEmptyIssueMessage|You must be a project member in order to add wiki pages. If you have suggestions for how to improve the wiki for this project, consider opening an issue in the %{issues_link}."
msgstr ""
msgid "WikiEmptyIssueMessage|issue tracker"
msgstr ""
+msgid "WikiEmpty|A wiki is where you can store all the details about your group. This can include why you've created it, its principles, how to use it, and so on."
+msgstr ""
+
msgid "WikiEmpty|A wiki is where you can store all the details about your project. This can include why you've created it, its principles, how to use it, and so on."
msgstr ""
@@ -25125,12 +25779,21 @@ msgstr ""
msgid "WikiEmpty|Suggest wiki improvement"
msgstr ""
+msgid "WikiEmpty|The wiki lets you write documentation for your group"
+msgstr ""
+
msgid "WikiEmpty|The wiki lets you write documentation for your project"
msgstr ""
+msgid "WikiEmpty|This group has no wiki pages"
+msgstr ""
+
msgid "WikiEmpty|This project has no wiki pages"
msgstr ""
+msgid "WikiEmpty|You must be a group member in order to add wiki pages."
+msgstr ""
+
msgid "WikiEmpty|You must be a project member in order to add wiki pages."
msgstr ""
@@ -25407,13 +26070,13 @@ msgstr ""
msgid "You can now export your security dashboard to a CSV report."
msgstr ""
-msgid "You can now submit a merge request to get this change into the original branch."
+msgid "You can now manage alert endpoint configuration in the Alerts section on the Operations settings page. Fields on this page have been deprecated."
msgstr ""
-msgid "You can now submit a merge request to get this change into the original project."
+msgid "You can now submit a merge request to get this change into the original branch."
msgstr ""
-msgid "You can only add files when you are on a branch"
+msgid "You can now submit a merge request to get this change into the original project."
msgstr ""
msgid "You can only edit files when you are on a branch"
@@ -25635,7 +26298,7 @@ msgstr ""
msgid "You need to upload a Google Takeout archive."
msgstr ""
-msgid "You reached %{usage_in_percent} of %{namespace_name}'s capacity (%{used_storage} of %{storage_limit})"
+msgid "You reached %{usage_in_percent} of %{namespace_name}'s storage capacity (%{used_storage} of %{storage_limit})"
msgstr ""
msgid "You tried to fork %{link_to_the_project} but it failed for the following reason:"
@@ -25938,13 +26601,13 @@ msgstr ""
msgid "Your subscription expired!"
msgstr ""
-msgid "Your subscription has been downgraded"
+msgid "Your subscription has been downgraded."
msgstr ""
-msgid "Your subscription will automatically renew in %{remaining_days}"
+msgid "Your subscription will automatically renew in %{remaining_days}."
msgstr ""
-msgid "Your subscription will expire in %{remaining_days}"
+msgid "Your subscription will expire in %{remaining_days}."
msgstr ""
msgid "Zoom meeting added"
@@ -26024,9 +26687,6 @@ msgstr ""
msgid "branch name"
msgstr ""
-msgid "build pipeline reference mismatch"
-msgstr ""
-
msgid "by"
msgstr ""
@@ -26045,6 +26705,9 @@ msgstr ""
msgid "cannot block others"
msgstr ""
+msgid "cannot contain HTML/XML tags, including any word between angle brackets (<,>)."
+msgstr ""
+
msgid "cannot include leading slash or directory traversal."
msgstr ""
@@ -26075,55 +26738,6 @@ msgstr ""
msgid "ciReport|%{remainingPackagesCount} more"
msgstr ""
-msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability"
-msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability for the source branch only"
-msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities for the source branch only"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerability"
-msgid_plural "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerabilities"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new vulnerability"
-msgid_plural "ciReport|%{reportType} %{status} detected %{newCount} new vulnerabilities"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities for the source branch only"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{fixedCount} fixed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} vulnerability for the source branch only"
-msgid_plural "ciReport|%{reportType} %{status} detected %{newCount} vulnerabilities for the source branch only"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "ciReport|%{reportType} %{status} detected no new vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected no vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected no vulnerabilities for the source branch only"
-msgstr ""
-
msgid "ciReport|%{reportType} is loading"
msgstr ""
@@ -26142,7 +26756,7 @@ msgstr ""
msgid "ciReport|All projects"
msgstr ""
-msgid "ciReport|All report types"
+msgid "ciReport|All scanner types"
msgstr ""
msgid "ciReport|All severities"
@@ -26166,6 +26780,9 @@ msgstr ""
msgid "ciReport|Container scanning detects known vulnerabilities in your docker images."
msgstr ""
+msgid "ciReport|Coverage Fuzzing"
+msgstr ""
+
msgid "ciReport|Create a merge request to implement this solution, or download and apply the patch manually."
msgstr ""
@@ -26324,6 +26941,9 @@ msgstr ""
msgid "customize"
msgstr ""
+msgid "data"
+msgstr ""
+
msgid "date must not be after 9999-12-31"
msgstr ""
@@ -26394,6 +27014,9 @@ msgstr ""
msgid "entries cannot contain HTML tags"
msgstr ""
+msgid "epic"
+msgstr ""
+
msgid "error"
msgstr ""
@@ -26415,9 +27038,6 @@ msgstr ""
msgid "expires on %{timebox_due_date}"
msgstr ""
-msgid "external_url"
-msgstr ""
-
msgid "failed"
msgstr ""
@@ -26453,9 +27073,6 @@ msgstr ""
msgid "from"
msgstr ""
-msgid "geo_node_name"
-msgstr ""
-
msgid "group"
msgstr ""
@@ -26477,9 +27094,6 @@ msgstr ""
msgid "https://your-bitbucket-server"
msgstr ""
-msgid "image"
-msgstr ""
-
msgid "image diff"
msgstr ""
@@ -26623,6 +27237,9 @@ msgstr ""
msgid "load it anyway"
msgstr ""
+msgid "loading"
+msgstr ""
+
msgid "locked by %{path_lock_user_name} %{created_at}"
msgstr ""
@@ -26751,9 +27368,6 @@ msgstr ""
msgid "mrWidget|Deployment statistics are not available currently"
msgstr ""
-msgid "mrWidget|Detect issues before deployment with a CI pipeline that continuously tests your code. We created a quick guide that will show you how to create one. Make your code more secure and more robust in just a minute."
-msgstr ""
-
msgid "mrWidget|Did not close"
msgstr ""
@@ -26916,6 +27530,9 @@ msgstr ""
msgid "mrWidget|To approve this merge request, please enter your password. This project requires all approvals to be authenticated."
msgstr ""
+msgid "mrWidget|Use %{linkStart}CI pipelines to test your code%{linkEnd}, simply add a GitLab CI configuration file to your project. It only takes a minute to make your code more secure and robust."
+msgstr ""
+
msgid "mrWidget|When this merge request is ready, remove the WIP: prefix from the title to allow it to be merged"
msgstr ""
@@ -26952,6 +27569,9 @@ msgstr ""
msgid "must be greater than start date"
msgstr ""
+msgid "must contain only valid frameworks"
+msgstr ""
+
msgid "my-awesome-group"
msgstr ""
@@ -26964,6 +27584,9 @@ msgstr ""
msgid "needs to be between 10 minutes and 1 month"
msgstr ""
+msgid "never"
+msgstr ""
+
msgid "never expires"
msgstr ""
@@ -26991,6 +27614,9 @@ msgstr ""
msgid "nounSeries|%{firstItem} and %{lastItem}"
msgstr ""
+msgid "nounSeries|%{item}"
+msgstr ""
+
msgid "nounSeries|%{item}, %{nextItem}"
msgstr ""
@@ -27127,6 +27753,9 @@ msgstr ""
msgid "revised"
msgstr ""
+msgid "satisfied"
+msgstr ""
+
msgid "score"
msgstr ""
@@ -27157,6 +27786,9 @@ msgstr ""
msgid "severity|Unknown"
msgstr ""
+msgid "should be an array of %{object_name} objects"
+msgstr ""
+
msgid "should be greater than or equal to %{access} inherited membership from group %{group_name}"
msgstr ""
@@ -27211,10 +27843,13 @@ msgstr ""
msgid "suggestPipeline|2/2: Commit your changes"
msgstr ""
-msgid "suggestPipeline|Commit the changes and your pipeline will automatically run for the first time."
+msgid "suggestPipeline|Choose %{boldStart}Code Quality%{boldEnd} to add a pipeline that tests the quality of your code."
msgstr ""
-msgid "suggestPipeline|We recommend the %{boldStart}Code Quality%{boldEnd} template, which will add a report widget to your Merge Requests. This way you’ll learn about code quality degradations much sooner. %{footerStart} Goodbye technical debt! %{footerEnd}"
+msgid "suggestPipeline|The template is ready! You can now commit it to create your first pipeline."
+msgstr ""
+
+msgid "suggestPipeline|We’re adding a GitLab CI configuration file to add a pipeline to the project. You could create it manually, but we recommend that you start with a GitLab template that works out of the box."
msgstr ""
msgid "syntax is correct"
@@ -27274,6 +27909,9 @@ msgstr ""
msgid "uses Kubernetes clusters to deploy your code!"
msgstr ""
+msgid "v%{version} published %{timeAgo}"
+msgstr ""
+
msgid "verify ownership"
msgstr ""
diff --git a/locale/nl_NL/gitlab.po b/locale/nl_NL/gitlab.po
index f9b88574940..6a05667cc8a 100644
--- a/locale/nl_NL/gitlab.po
+++ b/locale/nl_NL/gitlab.po
@@ -2,7 +2,7 @@ msgid ""
msgstr ""
"Project-Id-Version: gitlab-ee\n"
"Report-Msgid-Bugs-To: \n"
-"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Last-Translator: \n"
"Language-Team: Dutch\n"
"Language: nl_NL\n"
"MIME-Version: 1.0\n"
@@ -10,9 +10,11 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Crowdin-Project: gitlab-ee\n"
+"X-Crowdin-Project-ID: 288872\n"
"X-Crowdin-Language: nl\n"
"X-Crowdin-File: /master/locale/gitlab.pot\n"
-"PO-Revision-Date: 2020-06-08 15:18\n"
+"X-Crowdin-File-ID: 6\n"
+"PO-Revision-Date: 2020-07-02 01:27\n"
msgid " %{start} to %{end}"
msgstr ""
@@ -197,6 +199,11 @@ msgid_plural "%d metrics"
msgstr[0] "%d metriek"
msgstr[1] "%d statistieken"
+msgid "%d milestone"
+msgid_plural "%d milestones"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "%d minute"
msgid_plural "%d minutes"
msgstr[0] ""
@@ -242,11 +249,6 @@ msgid_plural "%d unresolved threads"
msgstr[0] ""
msgstr[1] ""
-msgid "%d url scanned"
-msgid_plural "%d urls scanned"
-msgstr[0] ""
-msgstr[1] ""
-
msgid "%d vulnerability"
msgid_plural "%d vulnerabilities"
msgstr[0] ""
@@ -324,9 +326,15 @@ msgstr[1] "%{count} openstaande reacties"
msgid "%{count} related %{pluralized_subject}: %{links}"
msgstr ""
+msgid "%{dashboard_path} could not be found."
+msgstr ""
+
msgid "%{days} days until tags are automatically removed"
msgstr ""
+msgid "%{deployLinkStart}Use a template to deploy to ECS%{deployLinkEnd}, or use a docker image to %{commandsLinkStart}run AWS commands in GitLab CI/CD%{commandsLinkEnd}."
+msgstr ""
+
msgid "%{description}- Sentry event: %{errorUrl}- First seen: %{firstSeen}- Last seen: %{lastSeen} %{countLabel}: %{count}%{userCountLabel}: %{userCount}"
msgstr ""
@@ -369,12 +377,18 @@ msgstr ""
msgid "%{host} sign-in from new location"
msgstr ""
-msgid "%{icon}You are about to add %{usersTag} people to the discussion. Proceed with caution."
+msgid "%{icon}You are about to add %{usersTag} people to the discussion. They will all receive a notification."
+msgstr ""
+
+msgid "%{integrations_link_start}Integrations%{link_end} enable you to make third-party applications part of your GitLab workflow. If the available integrations don't meet your needs, consider using a %{webhooks_link_start}webhook%{link_end}."
msgstr ""
msgid "%{issuableType} will be removed! Are you sure?"
msgstr "%{issuableType} wordt verwijderd! Weet je het zeker?"
+msgid "%{issuesCount} issues in this group"
+msgstr ""
+
msgid "%{issuesSize} issues"
msgstr ""
@@ -384,7 +398,7 @@ msgstr ""
msgid "%{labelStart}Class:%{labelEnd} %{class}"
msgstr ""
-msgid "%{labelStart}Confidence:%{labelEnd} %{confidence}"
+msgid "%{labelStart}Evidence:%{labelEnd} %{evidence}"
msgstr ""
msgid "%{labelStart}File:%{labelEnd} %{file}"
@@ -402,6 +416,9 @@ msgstr ""
msgid "%{labelStart}Report Type:%{labelEnd} %{reportType}"
msgstr ""
+msgid "%{labelStart}Scanner:%{labelEnd} %{scanner}"
+msgstr ""
+
msgid "%{labelStart}Severity:%{labelEnd} %{severity}"
msgstr ""
@@ -426,9 +443,6 @@ msgstr ""
msgid "%{link_start}Read more%{link_end} about role permissions"
msgstr ""
-msgid "%{link} can be used for binding events when something is happening within the project."
-msgstr ""
-
msgid "%{listToShow}, and %{awardsListLength} more."
msgstr ""
@@ -462,6 +476,9 @@ msgstr ""
msgid "%{name} found %{resultsString}"
msgstr ""
+msgid "%{name} is already being used for another emoji"
+msgstr ""
+
msgid "%{name} is scheduled for %{action}"
msgstr ""
@@ -509,6 +526,9 @@ msgstr ""
msgid "%{primary} (%{secondary})"
msgstr ""
+msgid "%{ref} cannot be added: %{error}"
+msgstr ""
+
msgid "%{releases} release"
msgid_plural "%{releases} releases"
msgstr[0] ""
@@ -517,13 +537,37 @@ msgstr[1] ""
msgid "%{remaining_approvals} left"
msgstr ""
-msgid "%{retryButtonStart}Try again%{retryButtonEnd} or %{newFileButtonStart}attach a new file%{newFileButtonEnd}"
+msgid "%{reportType} %{status} detected %{critical} critical and %{high} high severity vulnerabilities out of %{total}."
+msgstr ""
+
+msgid "%{reportType} %{status} detected %{critical} critical and %{high} high severity vulnerabilities."
+msgstr ""
+
+msgid "%{reportType} %{status} detected %{critical} critical severity vulnerabilities out of %{total}."
+msgstr ""
+
+msgid "%{reportType} %{status} detected %{critical} critical severity vulnerability."
+msgid_plural "%{reportType} %{status} detected %{critical} critical severity vulnerabilities."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{reportType} %{status} detected %{high} high severity vulnerabilities out of %{total}."
msgstr ""
-msgid "%{screenreaderOnlyStart}Keyboard shorcuts%{screenreaderOnlyEnd} Disabled"
+msgid "%{reportType} %{status} detected %{high} high severity vulnerability."
+msgid_plural "%{reportType} %{status} detected %{high} high severity vulnerabilities."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{reportType} %{status} detected %{other} vulnerability."
+msgid_plural "%{reportType} %{status} detected %{other} vulnerabilities."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{reportType} %{status} detected no new vulnerabilities."
msgstr ""
-msgid "%{screenreaderOnlyStart}Keyboard shorcuts%{screenreaderOnlyEnd} Enabled"
+msgid "%{retryButtonStart}Try again%{retryButtonEnd} or %{newFileButtonStart}attach a new file%{newFileButtonEnd}"
msgstr ""
msgid "%{service_title} %{message}."
@@ -620,6 +664,12 @@ msgstr "%{title} wijzigingen"
msgid "%{token}..."
msgstr ""
+msgid "%{totalCpu} (%{freeSpacePercentage}%{percentSymbol} free)"
+msgstr ""
+
+msgid "%{totalMemory} (%{freeSpacePercentage}%{percentSymbol} free)"
+msgstr ""
+
msgid "%{totalWeight} total weight"
msgstr ""
@@ -650,6 +700,12 @@ msgstr ""
msgid "%{verb} %{time_spent_value} spent time."
msgstr ""
+msgid "%{webhooks_link_start}%{webhook_type}%{link_end} enable you to send notifications to web applications in response to events in a group or project."
+msgstr ""
+
+msgid "%{webhooks_link_start}%{webhook_type}%{link_end} enable you to send notifications to web applications in response to events in a group or project. We recommend using an %{integrations_link_start}integration%{link_end} in preference to a webhook."
+msgstr ""
+
msgid "'%{level}' is not a valid visibility level"
msgstr ""
@@ -676,9 +732,6 @@ msgstr ""
msgid "(No changes)"
msgstr ""
-msgid "(Show all)"
-msgstr ""
-
msgid "(check progress)"
msgstr ""
@@ -745,6 +798,9 @@ msgstr "- toon minder"
msgid "0 for unlimited"
msgstr ""
+msgid "0 for unlimited, only effective with remote storage enabled."
+msgstr ""
+
msgid "1 %{type} addition"
msgid_plural "%{count} %{type} additions"
msgstr[0] "1 %{type} toevoeging"
@@ -892,6 +948,12 @@ msgstr ""
msgid "<code>\"johnsmith@example.com\": \"johnsmith@example.com\"</code> will add \"By <a href=\"#\">johnsmith@example.com</a>\" to all issues and comments originally created by johnsmith@example.com. By default, the email address or username is masked to ensure the user's privacy. Use this option if you want to show the full email address."
msgstr ""
+msgid "<code>Masked</code> to prevent the values from being displayed in job logs (must match certain regexp requirements)."
+msgstr ""
+
+msgid "<code>Protected</code> to expose them to protected branches or tags only."
+msgstr ""
+
msgid "<namespace / project>"
msgstr ""
@@ -919,9 +981,6 @@ msgstr ""
msgid "A .NET Core console application template, customizable for any .NET Core project"
msgstr ""
-msgid "A DAG must have two dependent jobs to be visualized on this tab."
-msgstr ""
-
msgid "A GitBook site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
msgstr ""
@@ -952,6 +1011,12 @@ msgstr ""
msgid "A deleted user"
msgstr ""
+msgid "A file has been changed."
+msgstr ""
+
+msgid "A file was not found."
+msgstr ""
+
msgid "A file with '%{file_name}' already exists in %{branch} branch"
msgstr ""
@@ -1018,7 +1083,7 @@ msgstr ""
msgid "A subscription will trigger a new pipeline on the default branch of this project when a pipeline successfully completes for a new tag on the %{default_branch_docs} of the subscribed project."
msgstr ""
-msgid "A terraform report was generated in your pipelines."
+msgid "A suggestion is not applicable."
msgstr ""
msgid "A user with write access to the source branch selected this option"
@@ -1189,6 +1254,9 @@ msgstr ""
msgid "Action to take when receiving an alert."
msgstr ""
+msgid "Actions"
+msgstr ""
+
msgid "Activate"
msgstr ""
@@ -1245,6 +1313,9 @@ msgstr ""
msgid "Add LICENSE"
msgstr ""
+msgid "Add New Node"
+msgstr ""
+
msgid "Add README"
msgstr ""
@@ -1320,6 +1391,9 @@ msgstr ""
msgid "Add approval rule"
msgstr ""
+msgid "Add approvers"
+msgstr ""
+
msgid "Add bold text"
msgstr ""
@@ -1377,6 +1451,9 @@ msgstr ""
msgid "Add strikethrough text"
msgstr ""
+msgid "Add suggestion to batch"
+msgstr ""
+
msgid "Add system hook"
msgstr ""
@@ -1476,6 +1553,9 @@ msgstr ""
msgid "Admin Note"
msgstr ""
+msgid "Admin Notifications"
+msgstr ""
+
msgid "Admin Overview"
msgstr ""
@@ -1587,6 +1667,9 @@ msgstr ""
msgid "AdminSettings|Integrations configured here will automatically apply to all projects on this instance."
msgstr ""
+msgid "AdminSettings|Moved to integrations"
+msgstr ""
+
msgid "AdminSettings|No required pipeline"
msgstr ""
@@ -1817,12 +1900,18 @@ msgid_plural "Alerts"
msgstr[0] ""
msgstr[1] ""
+msgid "AlertManagement| assign yourself"
+msgstr ""
+
msgid "AlertManagement|Acknowledged"
msgstr ""
msgid "AlertManagement|Alert"
msgstr ""
+msgid "AlertManagement|Alert assignee(s): %{assignees}"
+msgstr ""
+
msgid "AlertManagement|Alert detail"
msgstr ""
@@ -1838,9 +1927,15 @@ msgstr ""
msgid "AlertManagement|All alerts"
msgstr ""
+msgid "AlertManagement|Assign To"
+msgstr ""
+
msgid "AlertManagement|Assign status"
msgstr ""
+msgid "AlertManagement|Assignee"
+msgstr ""
+
msgid "AlertManagement|Assignees"
msgstr ""
@@ -1859,9 +1954,6 @@ msgstr ""
msgid "AlertManagement|Edit"
msgstr ""
-msgid "AlertManagement|End time"
-msgstr ""
-
msgid "AlertManagement|Events"
msgstr ""
@@ -1880,10 +1972,13 @@ msgstr ""
msgid "AlertManagement|More information"
msgstr ""
+msgid "AlertManagement|No Matching Results"
+msgstr ""
+
msgid "AlertManagement|No alert data to display."
msgstr ""
-msgid "AlertManagement|No alerts available to display. If you think you're seeing this message in error, refresh the page."
+msgid "AlertManagement|No alerts available to display. See %{linkStart}enabling alert management%{linkEnd} for more information on adding alerts to the list."
msgstr ""
msgid "AlertManagement|No alerts to display."
@@ -1892,6 +1987,9 @@ msgstr ""
msgid "AlertManagement|None"
msgstr ""
+msgid "AlertManagement|None -"
+msgstr ""
+
msgid "AlertManagement|Open"
msgstr ""
@@ -1928,9 +2026,18 @@ msgstr ""
msgid "AlertManagement|There was an error displaying the alerts. Confirm your endpoint's configuration details to ensure alerts appear."
msgstr ""
+msgid "AlertManagement|There was an error while updating the assignee(s) list. Please try again."
+msgstr ""
+
+msgid "AlertManagement|There was an error while updating the assignee(s) of the alert. Please try again."
+msgstr ""
+
msgid "AlertManagement|There was an error while updating the status of the alert. Please try again."
msgstr ""
+msgid "AlertManagement|This assignee cannot be assigned to this alert."
+msgstr ""
+
msgid "AlertManagement|Tool"
msgstr ""
@@ -2078,6 +2185,9 @@ msgstr ""
msgid "Allow users to request access (if visibility is public or internal)"
msgstr ""
+msgid "Allowed Geo IP"
+msgstr ""
+
msgid "Allowed email domain restriction only permitted for top-level groups"
msgstr ""
@@ -2111,6 +2221,9 @@ msgstr ""
msgid "Amazon Web Services"
msgstr ""
+msgid "Amazon Web Services Logo"
+msgstr ""
+
msgid "Amazon authentication is not %{link_start}correctly configured%{link_end}. Ask your GitLab administrator if you want to use this service."
msgstr ""
@@ -2168,10 +2281,16 @@ msgstr ""
msgid "An error occurred when updating the issue weight"
msgstr ""
+msgid "An error occurred while acknowledging the notification. Refresh the page and try again."
+msgstr ""
+
+msgid "An error occurred while adding approvers"
+msgstr ""
+
msgid "An error occurred while adding formatted title for epic"
msgstr ""
-msgid "An error occurred while checking group path"
+msgid "An error occurred while checking group path. Please refresh and try again."
msgstr ""
msgid "An error occurred while committing your changes."
@@ -2315,7 +2434,7 @@ msgstr ""
msgid "An error occurred while loading milestones"
msgstr ""
-msgid "An error occurred while loading terraform report"
+msgid "An error occurred while loading project creation UI"
msgstr ""
msgid "An error occurred while loading the data. Please try again."
@@ -2369,6 +2488,9 @@ msgstr ""
msgid "An error occurred while rendering preview broadcast message"
msgstr ""
+msgid "An error occurred while rendering the editor"
+msgstr ""
+
msgid "An error occurred while reordering issues."
msgstr ""
@@ -2465,7 +2587,7 @@ msgstr ""
msgid "Analyze your dependencies for known vulnerabilities."
msgstr ""
-msgid "Analyze your source code and git history for secrets"
+msgid "Analyze your source code and git history for secrets."
msgstr ""
msgid "Analyze your source code for known vulnerabilities."
@@ -2579,12 +2701,18 @@ msgstr ""
msgid "Apply suggestion"
msgstr ""
+msgid "Apply suggestions"
+msgstr ""
+
msgid "Apply template"
msgstr ""
msgid "Apply this approval rule to any branch or a specific protected branch."
msgstr ""
+msgid "Applying"
+msgstr ""
+
msgid "Applying a template will replace the existing issue description. Any changes you have made will be lost."
msgstr ""
@@ -2597,12 +2725,18 @@ msgstr ""
msgid "Applying multiple commands"
msgstr ""
-msgid "Applying suggestion"
+msgid "Applying suggestion..."
+msgstr ""
+
+msgid "Applying suggestions..."
msgstr ""
msgid "Approval rules"
msgstr ""
+msgid "Approval rules reset to project defaults"
+msgstr ""
+
msgid "ApprovalRuleRemove|%d member"
msgid_plural "ApprovalRuleRemove|%d members"
msgstr[0] ""
@@ -2656,6 +2790,9 @@ msgstr ""
msgid "Approved"
msgstr ""
+msgid "Approved MRs"
+msgstr ""
+
msgid "Approved by: "
msgstr ""
@@ -2869,6 +3006,9 @@ msgstr ""
msgid "Assign"
msgstr ""
+msgid "Assign Iteration"
+msgstr ""
+
msgid "Assign custom color like #FF0000"
msgstr ""
@@ -2887,6 +3027,9 @@ msgstr ""
msgid "Assign to"
msgstr ""
+msgid "Assign to commenting user"
+msgstr ""
+
msgid "Assign yourself to these issues"
msgstr ""
@@ -2913,6 +3056,9 @@ msgid_plural "%d Assignees"
msgstr[0] ""
msgstr[1] ""
+msgid "Assignee has no permissions"
+msgstr ""
+
msgid "Assignee lists not available with your current license"
msgstr ""
@@ -2922,6 +3068,9 @@ msgstr ""
msgid "Assignee(s)"
msgstr ""
+msgid "Assignees"
+msgstr ""
+
msgid "Assigns %{assignee_users_sentence}."
msgstr ""
@@ -2957,6 +3106,9 @@ msgstr ""
msgid "Audit Events is a way to keep track of important events that happened in GitLab."
msgstr ""
+msgid "Audit Log"
+msgstr ""
+
msgid "AuditEvents|(removed)"
msgstr ""
@@ -2993,6 +3145,9 @@ msgstr ""
msgid "AuditLogs|IP Address"
msgstr ""
+msgid "AuditLogs|Member Events"
+msgstr ""
+
msgid "AuditLogs|No matching %{type} found."
msgstr ""
@@ -3275,9 +3430,6 @@ msgstr ""
msgid "Badges|e.g. %{exampleUrl}"
msgstr ""
-msgid "Badge|New"
-msgstr ""
-
msgid "Balsamiq file could not be loaded."
msgstr ""
@@ -3326,6 +3478,12 @@ msgstr ""
msgid "Below you will find all the groups that are public."
msgstr ""
+msgid "Beta"
+msgstr ""
+
+msgid "Bi-weekly code coverage"
+msgstr ""
+
msgid "Billing"
msgstr ""
@@ -3386,9 +3544,15 @@ msgstr ""
msgid "Bitbucket Server Import"
msgstr ""
+msgid "Bitbucket Server import"
+msgstr ""
+
msgid "Bitbucket import"
msgstr ""
+msgid "Blame"
+msgstr ""
+
msgid "Blocked"
msgstr ""
@@ -3401,9 +3565,6 @@ msgstr ""
msgid "Blog"
msgstr ""
-msgid "Blue helpers indicate an action to be taken."
-msgstr ""
-
msgid "Board name"
msgstr ""
@@ -3602,6 +3763,9 @@ msgstr ""
msgid "Broadcast Message was successfully updated."
msgstr ""
+msgid "Broadcast Messages"
+msgstr ""
+
msgid "Browse Directory"
msgstr "Bladeren in map"
@@ -3656,6 +3820,9 @@ msgstr ""
msgid "By %{user_name}"
msgstr ""
+msgid "By URL"
+msgstr ""
+
msgid "By default GitLab sends emails in HTML and plain text formats so mail clients can choose what format to use. Disable this option if you only want to send emails in plain text format."
msgstr ""
@@ -3761,6 +3928,9 @@ msgstr ""
msgid "Can override approvers and approvals required per merge request"
msgstr ""
+msgid "Can't apply as this line has changed or the suggestion already matches its content."
+msgstr ""
+
msgid "Can't create snippet: %{err}"
msgstr ""
@@ -3947,9 +4117,6 @@ msgstr ""
msgid "Changes are still tracked. Useful for cluster/index migrations."
msgstr ""
-msgid "Changes are unknown"
-msgstr ""
-
msgid "Changes suppressed. Click to show."
msgstr ""
@@ -4256,15 +4423,9 @@ msgstr ""
msgid "Choose what content you want to see on a group’s overview page"
msgstr ""
-msgid "Choose which groups you wish to synchronize to this secondary node"
-msgstr ""
-
msgid "Choose which repositories you want to connect and run CI/CD pipelines."
msgstr ""
-msgid "Choose which shards you wish to synchronize to this secondary node"
-msgstr ""
-
msgid "Choose your framework"
msgstr ""
@@ -4562,9 +4723,15 @@ msgstr ""
msgid "Cluster does not exist"
msgstr ""
+msgid "Cluster is required for Stages::ClusterEndpointInserter"
+msgstr ""
+
msgid "Cluster level"
msgstr ""
+msgid "Cluster type must be specificed for Stages::ClusterEndpointInserter"
+msgstr ""
+
msgid "ClusterIntegration| %{custom_domain_start}More information%{custom_domain_end}."
msgstr ""
@@ -4676,7 +4843,7 @@ msgstr ""
msgid "ClusterIntegration|Cert-Manager"
msgstr ""
-msgid "ClusterIntegration|Cert-Manager is a native Kubernetes certificate management controller that helps with issuing certificates. Installing Cert-Manager on your cluster will issue a certificate by %{letsEncrypt} and ensure that certificates are valid and up-to-date."
+msgid "ClusterIntegration|Cert-Manager is a native Kubernetes certificate management controller that helps with issuing certificates. Installing Cert-Manager on your cluster will issue a certificate by %{linkStart}Let's Encrypt%{linkEnd} and ensure that certificates are valid and up-to-date."
msgstr ""
msgid "ClusterIntegration|Certificate Authority bundle (PEM format)"
@@ -4703,9 +4870,6 @@ msgstr ""
msgid "ClusterIntegration|Clear the local cache of namespace and service accounts. This is necessary if your integration has become out of sync. The cache is repopulated during the next CI job that requires namespace and service accounts."
msgstr ""
-msgid "ClusterIntegration|Cloud Run"
-msgstr ""
-
msgid "ClusterIntegration|Cluster being created"
msgstr ""
@@ -4790,7 +4954,7 @@ msgstr ""
msgid "ClusterIntegration|Crossplane"
msgstr ""
-msgid "ClusterIntegration|Crossplane enables declarative provisioning of managed services from your cloud of choice using %{codeStart}kubectl%{codeEnd} or %{gitlabIntegrationLink}. Crossplane runs inside your Kubernetes cluster and supports secure connectivity and secrets management between app containers and the cloud services they depend on."
+msgid "ClusterIntegration|Crossplane enables declarative provisioning of managed services from your cloud of choice using %{codeStart}kubectl%{codeEnd} or %{linkStart}GitLab Integration%{linkEnd}. Crossplane runs inside your Kubernetes cluster and supports secure connectivity and secrets management between app containers and the cloud services they depend on."
msgstr ""
msgid "ClusterIntegration|Deletes all GitLab resources attached to this cluster during removal"
@@ -4874,9 +5038,6 @@ msgstr ""
msgid "ClusterIntegration|GitLab-managed cluster"
msgstr ""
-msgid "ClusterIntegration|Gitlab Integration"
-msgstr ""
-
msgid "ClusterIntegration|Global default"
msgstr ""
@@ -4922,7 +5083,7 @@ msgstr ""
msgid "ClusterIntegration|Ingress gives you a way to route requests to services based on the request host or path, centralizing a number of services into a single entrypoint."
msgstr ""
-msgid "ClusterIntegration|Installing Ingress may incur additional costs. Learn more about %{pricingLink}."
+msgid "ClusterIntegration|Installing Ingress may incur additional costs. Learn more about %{linkStart}pricing%{linkEnd}."
msgstr ""
msgid "ClusterIntegration|Instance cluster"
@@ -4937,7 +5098,7 @@ msgstr ""
msgid "ClusterIntegration|Issuer Email"
msgstr ""
-msgid "ClusterIntegration|Issuers represent a certificate authority. You must provide an email address for your Issuer. "
+msgid "ClusterIntegration|Issuers represent a certificate authority. You must provide an email address for your Issuer."
msgstr ""
msgid "ClusterIntegration|Jupyter Hostname"
@@ -5009,9 +5170,6 @@ msgstr ""
msgid "ClusterIntegration|Learn more about instance Kubernetes clusters"
msgstr ""
-msgid "ClusterIntegration|Let's Encrypt"
-msgstr ""
-
msgid "ClusterIntegration|Loading IAM Roles"
msgstr ""
@@ -5123,7 +5281,7 @@ msgstr ""
msgid "ClusterIntegration|Prometheus"
msgstr ""
-msgid "ClusterIntegration|Prometheus is an open-source monitoring system with %{gitlabIntegrationLink} to monitor deployed applications."
+msgid "ClusterIntegration|Prometheus is an open-source monitoring system with %{linkStart}GitLab Integration%{linkEnd} to monitor deployed applications."
msgstr ""
msgid "ClusterIntegration|Provider details"
@@ -5279,10 +5437,10 @@ msgstr ""
msgid "ClusterIntegration|Select zone to choose machine type"
msgstr ""
-msgid "ClusterIntegration|Send Cilium Logs"
+msgid "ClusterIntegration|Send Container Network Policies Logs"
msgstr ""
-msgid "ClusterIntegration|Send ModSecurity Logs"
+msgid "ClusterIntegration|Send Web Application Firewall Logs"
msgstr ""
msgid "ClusterIntegration|Service Token"
@@ -5450,15 +5608,12 @@ msgstr ""
msgid "ClusterIntegration|documentation"
msgstr ""
-msgid "ClusterIntegration|installed via %{installed_via}"
+msgid "ClusterIntegration|installed via %{linkStart}Cloud Run%{linkEnd}"
msgstr ""
msgid "ClusterIntegration|meets the requirements"
msgstr ""
-msgid "ClusterIntegration|pricing"
-msgstr ""
-
msgid "ClusterIntegration|sign up"
msgstr ""
@@ -5495,6 +5650,15 @@ msgstr ""
msgid "Code"
msgstr ""
+msgid "Code Coverage: %{coveragePercentage}%{percentSymbol}"
+msgstr ""
+
+msgid "Code Coverage| Empty code coverage data"
+msgstr ""
+
+msgid "Code Coverage|Couldn't fetch the code coverage data"
+msgstr ""
+
msgid "Code Owners"
msgstr ""
@@ -5549,7 +5713,7 @@ msgstr ""
msgid "Collapse approvers"
msgstr ""
-msgid "Collapse child epics"
+msgid "Collapse milestones"
msgstr ""
msgid "Collapse replies"
@@ -5567,6 +5731,9 @@ msgstr ""
msgid "Coming soon"
msgstr ""
+msgid "Comma-separated, e.g. '1.1.1.1, 2.2.2.0/24'"
+msgstr ""
+
msgid "Command"
msgstr ""
@@ -5737,6 +5904,12 @@ msgstr ""
msgid "Compliance framework (optional)"
msgstr ""
+msgid "Compliance frameworks"
+msgstr ""
+
+msgid "ComplianceDashboard|created by:"
+msgstr ""
+
msgid "ComplianceFramework|GDPR"
msgstr ""
@@ -5875,6 +6048,9 @@ msgstr ""
msgid "Connection timed out"
msgstr ""
+msgid "Connection timeout"
+msgstr ""
+
msgid "Contact sales to upgrade"
msgstr ""
@@ -5930,9 +6106,6 @@ msgstr ""
msgid "ContainerRegistry|CLI Commands"
msgstr ""
-msgid "ContainerRegistry|Compressed Size"
-msgstr ""
-
msgid "ContainerRegistry|Container Registry"
msgstr ""
@@ -5948,6 +6121,9 @@ msgstr ""
msgid "ContainerRegistry|Copy push command"
msgstr ""
+msgid "ContainerRegistry|Delete selected"
+msgstr ""
+
msgid "ContainerRegistry|Docker connection error"
msgstr ""
@@ -5981,16 +6157,16 @@ msgstr ""
msgid "ContainerRegistry|If you are not already logged in, you need to authenticate to the Container Registry by using your GitLab username and password. If you have %{twofaDocLinkStart}Two-Factor Authentication%{twofaDocLinkEnd} enabled, use a %{personalAccessTokensDocLinkStart}Personal Access Token%{personalAccessTokensDocLinkEnd} instead of a password."
msgstr ""
-msgid "ContainerRegistry|Image ID"
+msgid "ContainerRegistry|Image ID: %{imageId}"
msgstr ""
msgid "ContainerRegistry|Image Repositories"
msgstr ""
-msgid "ContainerRegistry|Keep and protect the images that matter most."
+msgid "ContainerRegistry|Image tags"
msgstr ""
-msgid "ContainerRegistry|Last Updated"
+msgid "ContainerRegistry|Keep and protect the images that matter most."
msgstr ""
msgid "ContainerRegistry|Login"
@@ -6005,6 +6181,9 @@ msgstr ""
msgid "ContainerRegistry|Please contact your administrator."
msgstr ""
+msgid "ContainerRegistry|Published %{timeInfo}"
+msgstr ""
+
msgid "ContainerRegistry|Push an image"
msgstr ""
@@ -6020,9 +6199,6 @@ msgstr ""
msgid "ContainerRegistry|Remove repository"
msgstr ""
-msgid "ContainerRegistry|Remove selected tags"
-msgstr ""
-
msgid "ContainerRegistry|Remove tag"
msgid_plural "ContainerRegistry|Remove tags"
msgstr[0] ""
@@ -6052,9 +6228,6 @@ msgstr ""
msgid "ContainerRegistry|Sorry, your filter produced no results."
msgstr ""
-msgid "ContainerRegistry|Tag"
-msgstr ""
-
msgid "ContainerRegistry|Tag expiration policy"
msgstr ""
@@ -6199,21 +6372,6 @@ msgstr ""
msgid "Control the display of third party offers."
msgstr ""
-msgid "Control the maximum concurrency of LFS/attachment backfill for this secondary node"
-msgstr ""
-
-msgid "Control the maximum concurrency of container repository operations for this Geo node"
-msgstr ""
-
-msgid "Control the maximum concurrency of repository backfill for this secondary node"
-msgstr ""
-
-msgid "Control the maximum concurrency of verification operations for this Geo node"
-msgstr ""
-
-msgid "Control the minimum interval in days that a repository should be reverified for this primary node"
-msgstr ""
-
msgid "Cookie domain"
msgstr ""
@@ -6355,6 +6513,9 @@ msgstr ""
msgid "Could not find design."
msgstr ""
+msgid "Could not find iteration"
+msgstr ""
+
msgid "Could not remove the trigger."
msgstr ""
@@ -6391,6 +6552,12 @@ msgstr ""
msgid "Coverage"
msgstr ""
+msgid "Coverage Fuzzing"
+msgstr ""
+
+msgid "Crash State"
+msgstr ""
+
msgid "Create"
msgstr ""
@@ -6439,6 +6606,9 @@ msgstr ""
msgid "Create a personal access token on your account to pull or push via %{protocol}."
msgstr ""
+msgid "Create a project pre-populated with the necessary files to get you started quickly."
+msgstr ""
+
msgid "Create an account using:"
msgstr ""
@@ -6607,6 +6777,9 @@ msgstr ""
msgid "Creates branch '%{branch_name}' and a merge request to resolve this issue."
msgstr ""
+msgid "Creating"
+msgstr ""
+
msgid "Creating epic"
msgstr ""
@@ -6658,7 +6831,10 @@ msgstr ""
msgid "Current vulnerabilities count"
msgstr ""
-msgid "CurrentUser|Buy CI minutes"
+msgid "CurrentUser|Buy Pipeline minutes"
+msgstr ""
+
+msgid "CurrentUser|One of your groups is running out"
msgstr ""
msgid "CurrentUser|Profile"
@@ -6673,6 +6849,9 @@ msgstr ""
msgid "CurrentUser|Upgrade"
msgstr ""
+msgid "Custom Attributes"
+msgstr ""
+
msgid "Custom CI configuration path"
msgstr ""
@@ -6936,12 +7115,18 @@ msgstr ""
msgid "DAG"
msgstr ""
+msgid "DAG visualization requires at least 3 dependent jobs."
+msgstr ""
+
msgid "DNS"
msgstr ""
msgid "Dashboard"
msgstr ""
+msgid "Dashboard uid not found"
+msgstr ""
+
msgid "DashboardProjects|All"
msgstr ""
@@ -6966,6 +7151,9 @@ msgstr ""
msgid "Data is still calculating..."
msgstr ""
+msgid "Datasource name not found"
+msgstr ""
+
msgid "Date"
msgstr ""
@@ -7146,6 +7334,9 @@ msgstr ""
msgid "Delete this attachment"
msgstr ""
+msgid "Delete user list"
+msgstr ""
+
msgid "Delete variable"
msgstr ""
@@ -7462,6 +7653,9 @@ msgstr ""
msgid "Deploying to"
msgstr ""
+msgid "Deploying to AWS is easy with GitLab"
+msgstr ""
+
msgid "Deployment Frequency"
msgstr ""
@@ -7824,6 +8018,9 @@ msgstr ""
msgid "Dismissed on pipeline %{pipelineLink} at %{projectLink}"
msgstr ""
+msgid "Display alerts from all your monitoring tools directly within GitLab."
+msgstr ""
+
msgid "Display name"
msgstr ""
@@ -7881,9 +8078,6 @@ msgstr ""
msgid "Don't show again"
msgstr ""
-msgid "Don't worry, you can access this tour by clicking on the help icon in the top right corner and choose <strong>Learn GitLab</strong>."
-msgstr ""
-
msgid "Done"
msgstr ""
@@ -8010,6 +8204,9 @@ msgstr ""
msgid "Edit Release"
msgstr ""
+msgid "Edit Slack integration"
+msgstr ""
+
msgid "Edit Snippet"
msgstr ""
@@ -8049,6 +8246,9 @@ msgstr ""
msgid "Edit issues"
msgstr ""
+msgid "Edit iteration"
+msgstr ""
+
msgid "Edit public deploy key"
msgstr ""
@@ -8097,6 +8297,9 @@ msgstr ""
msgid "Email"
msgstr ""
+msgid "Email Notification"
+msgstr ""
+
msgid "Email address"
msgstr ""
@@ -8109,6 +8312,9 @@ msgstr ""
msgid "Email not verified. Please verify your email in Salesforce."
msgstr ""
+msgid "Email notification for unknown sign-ins"
+msgstr ""
+
msgid "Email patch"
msgstr ""
@@ -8340,6 +8546,9 @@ msgstr ""
msgid "Enforce DNS rebinding attack protection"
msgstr ""
+msgid "Enforce personal access token expiration"
+msgstr ""
+
msgid "Ensure connectivity is available from the GitLab server to the Prometheus server"
msgstr ""
@@ -8409,6 +8618,9 @@ msgstr ""
msgid "Enter the name of your application, and we'll return a unique %{type}."
msgstr ""
+msgid "Enter weights for storages for new repositories."
+msgstr ""
+
msgid "Enter your password to approve"
msgstr ""
@@ -8418,15 +8630,27 @@ msgstr ""
msgid "Environment does not have deployments"
msgstr ""
+msgid "Environment is required for Stages::MetricEndpointInserter"
+msgstr ""
+
+msgid "Environment is required for Stages::VariableEndpointInserter"
+msgstr ""
+
msgid "Environment scope"
msgstr ""
+msgid "Environment variables are applied to all project environments in this instance via the Runner. You can use environment variables for passwords, secret keys, etc. Make variables available to the running application by prepending the variable key with <code>K8S_SECRET_</code>. You can set variables to be:"
+msgstr ""
+
msgid "Environment variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. Additionally, they can be masked so they are hidden in job logs, though they must match certain regexp requirements to do so. You can use environment variables for passwords, secret keys, or whatever you want."
msgstr ""
msgid "Environment variables are configured by your administrator to be %{link_start}protected%{link_end} by default"
msgstr ""
+msgid "Environment variables on this GitLab instance are configured to be %{link_start}protected%{link_end} by default"
+msgstr ""
+
msgid "Environment:"
msgstr ""
@@ -8958,6 +9182,9 @@ msgstr ""
msgid "EventFilterBy|Filter by comments"
msgstr ""
+msgid "EventFilterBy|Filter by designs"
+msgstr ""
+
msgid "EventFilterBy|Filter by epic events"
msgstr ""
@@ -9090,15 +9317,15 @@ msgstr ""
msgid "Expand approvers"
msgstr ""
-msgid "Expand child epics"
-msgstr ""
-
msgid "Expand down"
msgstr ""
msgid "Expand dropdown"
msgstr ""
+msgid "Expand milestones"
+msgstr ""
+
msgid "Expand sidebar"
msgstr ""
@@ -9114,6 +9341,9 @@ msgstr ""
msgid "Expiration date"
msgstr ""
+msgid "Expiration not enforced"
+msgstr ""
+
msgid "Expiration policy for the Container Registry is a perfect solution for keeping the Registry space down while still enjoying the full power of GitLab CI/CD."
msgstr ""
@@ -9234,6 +9464,9 @@ msgstr ""
msgid "Failed Jobs"
msgstr ""
+msgid "Failed on"
+msgstr ""
+
msgid "Failed to add a Zoom meeting"
msgstr ""
@@ -9294,13 +9527,19 @@ msgstr ""
msgid "Failed to install."
msgstr ""
+msgid "Failed to load assignees. Please try again."
+msgstr ""
+
+msgid "Failed to load authors. Please try again."
+msgstr ""
+
msgid "Failed to load emoji list."
msgstr ""
msgid "Failed to load error details from Sentry."
msgstr ""
-msgid "Failed to load errors from Sentry. Error message: %{errorMessage}"
+msgid "Failed to load errors from Sentry."
msgstr ""
msgid "Failed to load group activity metrics. Please try again."
@@ -9381,6 +9620,9 @@ msgstr ""
msgid "Failed to set due date because the date format is invalid."
msgstr ""
+msgid "Failed to set iteration on this issue. Please try again."
+msgstr ""
+
msgid "Failed to signing using smartcard authentication"
msgstr ""
@@ -9603,13 +9845,10 @@ msgstr ""
msgid "FeatureFlags|Target environments"
msgstr ""
-msgid "FeatureFlags|There are no active feature flags"
-msgstr ""
-
-msgid "FeatureFlags|There are no inactive feature flags"
+msgid "FeatureFlags|There was an error fetching the feature flags."
msgstr ""
-msgid "FeatureFlags|There was an error fetching the feature flags."
+msgid "FeatureFlags|There was an error retrieving user lists"
msgstr ""
msgid "FeatureFlags|Try again in a few moments or contact your support team."
@@ -9621,9 +9860,18 @@ msgstr ""
msgid "FeatureFlag|Delete strategy"
msgstr ""
+msgid "FeatureFlag|List"
+msgstr ""
+
msgid "FeatureFlag|Percentage"
msgstr ""
+msgid "FeatureFlag|Select a user list"
+msgstr ""
+
+msgid "FeatureFlag|There are no configured user lists"
+msgstr ""
+
msgid "FeatureFlag|Type"
msgstr ""
@@ -9780,6 +10028,12 @@ msgstr ""
msgid "Filter..."
msgstr ""
+msgid "Find File"
+msgstr ""
+
+msgid "Find bugs in your code with coverage-guided fuzzing"
+msgstr ""
+
msgid "Find by path"
msgstr ""
@@ -10044,6 +10298,9 @@ msgstr ""
msgid "Geo allows you to replicate your GitLab instance to other geographical locations."
msgstr ""
+msgid "Geo nodes are paused using a command run on the node"
+msgstr ""
+
msgid "GeoNodeStatusEvent|%{timeAgoStr} (%{pendingEvents} events)"
msgstr ""
@@ -10128,6 +10385,9 @@ msgstr ""
msgid "GeoNodes|Not checksummed"
msgstr ""
+msgid "GeoNodes|Package files"
+msgstr ""
+
msgid "GeoNodes|Pausing replication stops the sync process. Are you sure?"
msgstr ""
@@ -10146,6 +10406,9 @@ msgstr ""
msgid "GeoNodes|Replication slots"
msgstr ""
+msgid "GeoNodes|Replication status"
+msgstr ""
+
msgid "GeoNodes|Repositories"
msgstr ""
@@ -10356,6 +10619,9 @@ msgstr ""
msgid "Geo|Synchronization failed - %{error}"
msgstr ""
+msgid "Geo|Synchronization of %{itemTitle} is disabled."
+msgstr ""
+
msgid "Geo|The database is currently %{db_lag} behind the primary node."
msgstr ""
@@ -10668,6 +10934,9 @@ msgstr ""
msgid "Go to %{link_to_google_takeout}."
msgstr ""
+msgid "Go to <strong>Issues</strong> > <strong>Boards</strong> to access your personalized learning issue board."
+msgstr ""
+
msgid "Go to Pipelines"
msgstr ""
@@ -10767,9 +11036,6 @@ msgstr ""
msgid "Go to your snippets"
msgstr ""
-msgid "Golden Tanuki"
-msgstr ""
-
msgid "Google Cloud Platform"
msgstr ""
@@ -10791,6 +11057,9 @@ msgstr ""
msgid "Grafana URL"
msgstr ""
+msgid "Grafana response contains invalid json"
+msgstr ""
+
msgid "GrafanaIntegration|API Token"
msgstr ""
@@ -10839,9 +11108,6 @@ msgstr ""
msgid "Group %{group_name} was successfully created."
msgstr ""
-msgid "Group '%{group_name}' is being imported."
-msgstr ""
-
msgid "Group Audit Events"
msgstr ""
@@ -10878,9 +11144,6 @@ msgstr ""
msgid "Group by:"
msgstr ""
-msgid "Group could not be imported: %{errors}"
-msgstr ""
-
msgid "Group description"
msgstr ""
@@ -10908,9 +11171,15 @@ msgstr ""
msgid "Group has not been marked for deletion"
msgstr ""
+msgid "Group import could not be scheduled"
+msgstr ""
+
msgid "Group info:"
msgstr ""
+msgid "Group is required when cluster_type is :group"
+msgstr ""
+
msgid "Group maintainers can register group runners in the %{link}"
msgstr ""
@@ -10974,6 +11243,24 @@ msgstr ""
msgid "GroupActivyMetrics|Recent activity (last 90 days)"
msgstr ""
+msgid "GroupImport|Failed to import group."
+msgstr ""
+
+msgid "GroupImport|Group '%{group_name}' is being imported."
+msgstr ""
+
+msgid "GroupImport|Group could not be imported: %{errors}"
+msgstr ""
+
+msgid "GroupImport|Please wait while we import the group for you. Refresh at will."
+msgstr ""
+
+msgid "GroupImport|The group was successfully imported."
+msgstr ""
+
+msgid "GroupImport|Unable to process group import file"
+msgstr ""
+
msgid "GroupRoadmap|%{dateWord} – No end date"
msgstr ""
@@ -11241,6 +11528,9 @@ msgstr ""
msgid "Groups (%{groups})"
msgstr ""
+msgid "Groups and projects"
+msgstr ""
+
msgid "Groups can also be nested by creating %{subgroup_docs_link_start}subgroups%{subgroup_docs_link_end}."
msgstr ""
@@ -11286,6 +11576,33 @@ msgstr ""
msgid "GroupsEmptyState|You can manage your group member’s permissions and access to each project in the group."
msgstr ""
+msgid "GroupsNew|Contact an administrator to enable options for importing your group."
+msgstr ""
+
+msgid "GroupsNew|Create"
+msgstr ""
+
+msgid "GroupsNew|Create group"
+msgstr ""
+
+msgid "GroupsNew|GitLab group export"
+msgstr ""
+
+msgid "GroupsNew|Import"
+msgstr ""
+
+msgid "GroupsNew|Import group"
+msgstr ""
+
+msgid "GroupsNew|My Awesome Group"
+msgstr ""
+
+msgid "GroupsNew|No import options available"
+msgstr ""
+
+msgid "GroupsNew|To copy a GitLab group between installations, navigate to the group settings page for the original installation, generate an export file, and upload it here."
+msgstr ""
+
msgid "GroupsTree|Are you sure you want to leave the \"%{fullName}\" group?"
msgstr ""
@@ -11388,6 +11705,9 @@ msgstr ""
msgid "Helps reduce request volume for protected paths"
msgstr ""
+msgid "Here are all your projects in your group, including the one you just created. To start, let’s take a look at your personalized learning project which will help you learn about GitLab at your own pace."
+msgstr ""
+
msgid "Here you will find recent merge request activity"
msgstr ""
@@ -11402,6 +11722,9 @@ msgid_plural "Hide charts"
msgstr[0] ""
msgstr[1] ""
+msgid "Hide details"
+msgstr ""
+
msgid "Hide file browser"
msgstr ""
@@ -11411,6 +11734,9 @@ msgstr ""
msgid "Hide host keys manual input"
msgstr ""
+msgid "Hide list"
+msgstr ""
+
msgid "Hide marketing-related entries from help"
msgstr ""
@@ -11431,9 +11757,6 @@ msgstr[1] ""
msgid "Hide values"
msgstr ""
-msgid "Hiding all labels"
-msgstr ""
-
msgid "High or unknown vulnerabilities present"
msgstr ""
@@ -11518,6 +11841,9 @@ msgstr ""
msgid "ID:"
msgstr ""
+msgid "IDE"
+msgstr ""
+
msgid "IDE|Allow live previews of JavaScript projects in the Web IDE using CodeSandbox Live Preview."
msgstr ""
@@ -11608,10 +11934,10 @@ msgstr ""
msgid "If enabled"
msgstr ""
-msgid "If enabled, access to projects will be validated on an external service using their classification label."
+msgid "If enabled, GitLab will handle Object Storage replication using Geo. %{linkStart}More information%{linkEnd}"
msgstr ""
-msgid "If enabled, and if object storage is enabled, GitLab will handle Object Storage replication using Geo"
+msgid "If enabled, access to projects will be validated on an external service using their classification label."
msgstr ""
msgid "If there is no previous license or if the previous license has expired, some GitLab functionality will be blocked until a new, valid license is uploaded."
@@ -11973,6 +12299,9 @@ msgid_plural "Instances"
msgstr[0] ""
msgstr[1] ""
+msgid "Instance Configuration"
+msgstr ""
+
msgid "Instance Statistics visibility"
msgstr ""
@@ -11994,9 +12323,6 @@ msgstr ""
msgid "Integrations"
msgstr ""
-msgid "Integrations allow you to integrate GitLab with other applications"
-msgstr ""
-
msgid "Integrations|All details"
msgstr ""
@@ -12108,6 +12434,9 @@ msgstr ""
msgid "Invalid query"
msgstr ""
+msgid "Invalid repository bundle for snippet with id %{snippet_id}"
+msgstr ""
+
msgid "Invalid repository path"
msgstr ""
@@ -12126,6 +12455,9 @@ msgstr ""
msgid "Invalid two-factor code."
msgstr ""
+msgid "Invalid yaml"
+msgstr ""
+
msgid "Invitation"
msgstr ""
@@ -12345,15 +12677,24 @@ msgstr ""
msgid "It seems like the Dependency Scanning job ran successfully, but no dependencies have been detected in your project."
msgstr ""
+msgid "It seems that there is currently no available data for code coverage"
+msgstr ""
+
msgid "It's you"
msgstr ""
+msgid "Iteration"
+msgstr ""
+
msgid "Iteration changed to"
msgstr ""
msgid "Iteration removed"
msgstr ""
+msgid "Iteration updated"
+msgstr ""
+
msgid "Iterations"
msgstr ""
@@ -12384,12 +12725,18 @@ msgstr ""
msgid "January"
msgstr ""
+msgid "Jira Issues"
+msgstr ""
+
msgid "Jira import is already running."
msgstr ""
msgid "Jira integration not configured."
msgstr ""
+msgid "Jira project key is not configured"
+msgstr ""
+
msgid "Jira project: %{importProject}"
msgstr ""
@@ -12570,6 +12917,9 @@ msgstr ""
msgid "Keep divergent refs"
msgstr ""
+msgid "Kerberos access denied"
+msgstr ""
+
msgid "Key"
msgstr ""
@@ -12579,10 +12929,13 @@ msgstr ""
msgid "Key: %{key}"
msgstr ""
-msgid "Keyboard Shortcuts"
+msgid "Keyboard shortcuts"
msgstr ""
-msgid "Keyboard shortcuts"
+msgid "Keys"
+msgstr ""
+
+msgid "Ki"
msgstr ""
msgid "Kubernetes"
@@ -12624,6 +12977,9 @@ msgstr ""
msgid "LDAP"
msgstr ""
+msgid "LDAP Synchronization"
+msgstr ""
+
msgid "LDAP settings"
msgstr ""
@@ -12848,12 +13204,18 @@ msgstr ""
msgid "Learn more about custom project templates"
msgstr ""
+msgid "Learn more about deploying to AWS"
+msgstr ""
+
msgid "Learn more about deploying to a cluster"
msgstr ""
msgid "Learn more about group-level project templates"
msgstr ""
+msgid "Learn more about job dependencies"
+msgstr ""
+
msgid "Learn more about signing commits"
msgstr ""
@@ -12905,9 +13267,21 @@ msgstr ""
msgid "License History"
msgstr ""
+msgid "License ID:"
+msgstr ""
+
+msgid "License URL"
+msgstr ""
+
msgid "License-Check"
msgstr ""
+msgid "LicenseCompliance|%{docLinkStart}License Approvals%{docLinkEnd} are active"
+msgstr ""
+
+msgid "LicenseCompliance|%{docLinkStart}License Approvals%{docLinkEnd} are inactive"
+msgstr ""
+
msgid "LicenseCompliance|Add a license"
msgstr ""
@@ -12929,9 +13303,15 @@ msgstr ""
msgid "LicenseCompliance|Deny"
msgstr ""
+msgid "LicenseCompliance|Learn more about %{linkStart}License Approvals%{linkEnd}"
+msgstr ""
+
msgid "LicenseCompliance|License"
msgstr ""
+msgid "LicenseCompliance|License Approvals"
+msgstr ""
+
msgid "LicenseCompliance|License Compliance detected %d license and policy violation for the source branch only; approval required"
msgid_plural "LicenseCompliance|License Compliance detected %d licenses and policy violations for the source branch only; approval required"
msgstr[0] ""
@@ -13152,6 +13532,9 @@ msgstr ""
msgid "List your Bitbucket Server repositories"
msgstr ""
+msgid "Lists"
+msgstr ""
+
msgid "Live preview"
msgstr ""
@@ -13335,6 +13718,9 @@ msgstr ""
msgid "Manage project labels"
msgstr ""
+msgid "Manage storage usage"
+msgstr ""
+
msgid "Manage two-factor authentication"
msgstr ""
@@ -13350,6 +13736,9 @@ msgstr ""
msgid "Manifest file import"
msgstr ""
+msgid "Manifest import"
+msgstr ""
+
msgid "Manual job"
msgstr ""
@@ -13497,6 +13886,12 @@ msgstr ""
msgid "Maximum field length"
msgstr ""
+msgid "Maximum file size is 2MB. Please select a smaller file."
+msgstr ""
+
+msgid "Maximum import size (MB)"
+msgstr ""
+
msgid "Maximum job timeout"
msgstr ""
@@ -13536,6 +13931,9 @@ msgstr ""
msgid "Maximum size of Elasticsearch bulk indexing requests."
msgstr ""
+msgid "Maximum size of import files."
+msgstr ""
+
msgid "Maximum size of individual attachments in comments."
msgstr ""
@@ -13587,6 +13985,9 @@ msgstr ""
msgid "Merge (when the pipeline succeeds)"
msgstr ""
+msgid "Merge Conflicts"
+msgstr ""
+
msgid "Merge Request"
msgstr ""
@@ -13713,6 +14114,9 @@ msgstr ""
msgid "MergeRequests|Squash task canceled: another squash is already in progress."
msgstr ""
+msgid "MergeRequests|This project does not allow squashing commits when merge requests are accepted."
+msgstr ""
+
msgid "MergeRequests|Thread stays resolved"
msgstr ""
@@ -13866,6 +14270,12 @@ msgstr ""
msgid "MetricsSettings|Add a button to the metrics dashboard linking directly to your existing external dashboard."
msgstr ""
+msgid "MetricsSettings|Choose whether to display dashboard metrics in UTC or the user's local timezone."
+msgstr ""
+
+msgid "MetricsSettings|Dashboard timezone"
+msgstr ""
+
msgid "MetricsSettings|External dashboard URL"
msgstr ""
@@ -13875,6 +14285,12 @@ msgstr ""
msgid "MetricsSettings|Metrics Dashboard"
msgstr ""
+msgid "MetricsSettings|UTC (Coordinated Universal Time)"
+msgstr ""
+
+msgid "MetricsSettings|User's local timezone"
+msgstr ""
+
msgid "Metrics|Add metric"
msgstr ""
@@ -13890,6 +14306,9 @@ msgstr ""
msgid "Metrics|Create metric"
msgstr ""
+msgid "Metrics|Current"
+msgstr ""
+
msgid "Metrics|Delete metric"
msgstr ""
@@ -13940,6 +14359,9 @@ msgstr ""
msgid "Metrics|Max"
msgstr ""
+msgid "Metrics|Min"
+msgstr ""
+
msgid "Metrics|Must be a valid PromQL query."
msgstr ""
@@ -13955,6 +14377,9 @@ msgstr ""
msgid "Metrics|Refresh dashboard"
msgstr ""
+msgid "Metrics|Select a value"
+msgstr ""
+
msgid "Metrics|Star dashboard"
msgstr ""
@@ -13973,12 +14398,18 @@ msgstr ""
msgid "Metrics|There was an error getting annotations information."
msgstr ""
+msgid "Metrics|There was an error getting dashboard validation warnings information."
+msgstr ""
+
msgid "Metrics|There was an error getting deployment information."
msgstr ""
msgid "Metrics|There was an error getting environments information."
msgstr ""
+msgid "Metrics|There was an error getting options for variable \"%{name}\"."
+msgstr ""
+
msgid "Metrics|There was an error trying to validate your query"
msgstr ""
@@ -14021,6 +14452,9 @@ msgstr ""
msgid "Metrics|You're about to permanently delete this metric. This cannot be undone."
msgstr ""
+msgid "Metrics|Your dashboard schema is invalid. Edit the dashboard to correct the YAML schema."
+msgstr ""
+
msgid "Metrics|e.g. HTTP requests"
msgstr ""
@@ -14036,12 +14470,18 @@ msgstr ""
msgid "Metrics|e.g. req/sec"
msgstr ""
+msgid "Mi"
+msgstr ""
+
msgid "Microsoft Azure"
msgstr ""
msgid "Middleman project with Static Site Editor support"
msgstr ""
+msgid "Migrate your data from an external source like GitHub, Bitbucket, or another instance of GitLab."
+msgstr ""
+
msgid "Migrated %{success_count}/%{total_count} files."
msgstr ""
@@ -14176,6 +14616,9 @@ msgstr ""
msgid "Minimum capacity to be available before we schedule more mirrors preemptively."
msgstr ""
+msgid "Minimum interval in days"
+msgstr ""
+
msgid "Minimum length is %{minimum_password_length} characters"
msgstr ""
@@ -14362,6 +14805,12 @@ msgstr ""
msgid "Multiple uploaders found: %{uploader_types}"
msgstr ""
+msgid "Must match with the %{codeStart}external_url%{codeEnd} in %{codeStart}/etc/gitlab/gitlab.rb%{codeEnd}."
+msgstr ""
+
+msgid "Must match with the %{codeStart}geo_node_name%{codeEnd} in %{codeStart}/etc/gitlab/gitlab.rb%{codeEnd}. %{linkStart}More information%{linkEnd}"
+msgstr ""
+
msgid "My Awesome Group"
msgstr ""
@@ -14476,10 +14925,16 @@ msgstr ""
msgid "New Application"
msgstr ""
+msgid "New Branch"
+msgstr ""
+
+msgid "New Deploy Key"
+msgstr ""
+
msgid "New Environment"
msgstr ""
-msgid "New Geo Node"
+msgid "New File"
msgstr ""
msgid "New Group"
@@ -14496,15 +14951,15 @@ msgid_plural "New Issues"
msgstr[0] "Nieuwe issue"
msgstr[1] "Nieuwe issues"
-msgid "New Iteration"
-msgstr ""
-
msgid "New Jira import"
msgstr ""
msgid "New Label"
msgstr ""
+msgid "New Merge Request"
+msgstr ""
+
msgid "New Milestone"
msgstr ""
@@ -14523,6 +14978,9 @@ msgstr ""
msgid "New Snippet"
msgstr ""
+msgid "New User"
+msgstr ""
+
msgid "New branch"
msgstr ""
@@ -14565,6 +15023,9 @@ msgstr ""
msgid "New issue title"
msgstr ""
+msgid "New iteration"
+msgstr ""
+
msgid "New iteration created"
msgstr ""
@@ -14742,6 +15203,12 @@ msgstr ""
msgid "No grouping"
msgstr ""
+msgid "No iteration"
+msgstr ""
+
+msgid "No iterations to show"
+msgstr ""
+
msgid "No job log"
msgstr ""
@@ -14784,6 +15251,9 @@ msgstr ""
msgid "No other labels with such name or description"
msgstr ""
+msgid "No panels matching properties %{opts}"
+msgstr ""
+
msgid "No parent group"
msgstr ""
@@ -14817,6 +15287,9 @@ msgstr ""
msgid "No schedules"
msgstr ""
+msgid "No stack trace for this error"
+msgstr ""
+
msgid "No starrers matched your search"
msgstr ""
@@ -14829,7 +15302,7 @@ msgstr ""
msgid "No test coverage"
msgstr ""
-msgid "No thanks, don't show this again"
+msgid "No thanks"
msgstr ""
msgid "No vulnerabilities present"
@@ -14844,9 +15317,6 @@ msgstr ""
msgid "No, directly import the existing email addresses and usernames."
msgstr ""
-msgid "No, not interested right now"
-msgstr ""
-
msgid "No. of commits"
msgstr ""
@@ -14892,9 +15362,6 @@ msgstr ""
msgid "Not found."
msgstr ""
-msgid "Not helpful"
-msgstr ""
-
msgid "Not now"
msgstr ""
@@ -15051,6 +15518,9 @@ msgstr ""
msgid "Notifications on"
msgstr ""
+msgid "Notify users by email when sign-in location is not recognized"
+msgstr ""
+
msgid "Nov"
msgstr ""
@@ -15117,9 +15587,6 @@ msgstr ""
msgid "Oh no!"
msgstr ""
-msgid "Ok let's go"
-msgstr ""
-
msgid "Oldest first"
msgstr ""
@@ -15132,16 +15599,49 @@ msgstr ""
msgid "On track"
msgstr ""
+msgid "OnDemandScans|Attached branch"
+msgstr ""
+
+msgid "OnDemandScans|Attached branch is where the scan job runs."
+msgstr ""
+
+msgid "OnDemandScans|Could not run the scan. Please try again."
+msgstr ""
+
msgid "OnDemandScans|Create new DAST scan"
msgstr ""
+msgid "OnDemandScans|DAST will scan the target URL and any discovered sub URLs."
+msgstr ""
+
+msgid "OnDemandScans|New on-demand DAST scan"
+msgstr ""
+
msgid "OnDemandScans|On-demand Scans"
msgstr ""
+msgid "OnDemandScans|On-demand scans run outside the DevOps cycle and find vulnerabilities in your projects. %{learnMoreLinkStart}Learn more%{learnMoreLinkEnd}"
+msgstr ""
+
+msgid "OnDemandScans|Only a passive scan can be performed on demand."
+msgstr ""
+
+msgid "OnDemandScans|Passive DAST Scan"
+msgstr ""
+
+msgid "OnDemandScans|Please enter a valid URL format, ex: http://www.example.com/home"
+msgstr ""
+
+msgid "OnDemandScans|Run this scan"
+msgstr ""
+
+msgid "OnDemandScans|Scan mode"
+msgstr ""
+
msgid "OnDemandScans|Schedule or run scans immediately against target sites. Currently available on-demand scan type: DAST. %{helpLinkStart}More information%{helpLinkEnd}"
msgstr ""
-msgid "Onboarding"
+msgid "OnDemandScans|Target URL"
msgstr ""
msgid "Once imported, repositories can be mirrored over SSH. Read more %{link_start}here%{link_end}."
@@ -15167,6 +15667,9 @@ msgstr ""
msgid "One or more of you personal access tokens were revoked"
msgstr ""
+msgid "One or more of your %{provider} projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
+msgstr ""
+
msgid "One or more of your Bitbucket projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
msgstr ""
@@ -15668,6 +16171,9 @@ msgstr ""
msgid "Parent epic is not present."
msgstr ""
+msgid "Parsing error for param :embed_json. %{message}"
+msgstr ""
+
msgid "Part of merge request changes"
msgstr ""
@@ -15680,6 +16186,9 @@ msgstr ""
msgid "Passed"
msgstr ""
+msgid "Passed on"
+msgstr ""
+
msgid "Password"
msgstr ""
@@ -15764,6 +16273,9 @@ msgstr ""
msgid "Perform common operations on GitLab project"
msgstr ""
+msgid "Performance and resource management"
+msgstr ""
+
msgid "Performance optimization"
msgstr ""
@@ -15971,7 +16483,7 @@ msgstr ""
msgid "Pipelines|Something went wrong while cleaning runners cache."
msgstr ""
-msgid "Pipelines|There are currently no %{scope} pipelines."
+msgid "Pipelines|There are currently no finished pipelines."
msgstr ""
msgid "Pipelines|There are currently no pipelines."
@@ -16082,6 +16594,9 @@ msgstr ""
msgid "Pipeline|Stop pipeline #%{pipelineId}?"
msgstr ""
+msgid "Pipeline|Tag name"
+msgstr ""
+
msgid "Pipeline|Trigger author"
msgstr ""
@@ -16163,12 +16678,18 @@ msgstr ""
msgid "Please check your email (%{email}) to verify that you own this address and unlock the power of CI/CD. Didn't receive it? %{resend_link}. Wrong email address? %{update_link}."
msgstr ""
+msgid "Please choose a file"
+msgstr ""
+
msgid "Please choose a group URL with no special characters."
msgstr ""
msgid "Please complete your profile with email address"
msgstr ""
+msgid "Please convert %{linkStart}them to Git%{linkEnd}, and go through the %{linkToImportFlow} again."
+msgstr ""
+
msgid "Please convert them to %{link_to_git}, and go through the %{link_to_import_flow} again."
msgstr ""
@@ -16226,6 +16747,9 @@ msgstr ""
msgid "Please provide a valid email address."
msgstr ""
+msgid "Please provide attributes to update"
+msgstr ""
+
msgid "Please refer to <a href=\"%{docs_url}\">%{docs_url}</a>"
msgstr ""
@@ -16961,6 +17485,9 @@ msgstr ""
msgid "Project has too many %{label_for_message} to search"
msgstr ""
+msgid "Project is required when cluster_type is :project"
+msgstr ""
+
msgid "Project members"
msgstr ""
@@ -17078,7 +17605,40 @@ msgstr ""
msgid "ProjectService|Comment"
msgstr ""
-msgid "ProjectService|Comment will be posted on each event"
+msgid "ProjectService|Event will be triggered by a push to the repository"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a commit is created/updated"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a confidential issue is created/updated/closed"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a deployment finishes"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a merge request is created/updated/merged"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a new tag is pushed to the repository"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a new, unique alert is recorded"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a pipeline status changes"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a wiki page is created/updated"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when an issue is created/updated/closed"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when someone adds a comment"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when someone adds a comment on a confidential issue"
msgstr ""
msgid "ProjectService|Perform common operations on GitLab project: %{project_name}"
@@ -17129,6 +17689,9 @@ msgstr ""
msgid "ProjectSettings|Enable 'Delete source branch' option by default"
msgstr ""
+msgid "ProjectSettings|Enable merge trains and pipelines for merged results"
+msgstr ""
+
msgid "ProjectSettings|Every merge creates a merge commit"
msgstr ""
@@ -17159,7 +17722,7 @@ msgstr ""
msgid "ProjectSettings|Forks"
msgstr ""
-msgid "ProjectSettings|Git Large File Storage"
+msgid "ProjectSettings|Git Large File Storage (LFS)"
msgstr ""
msgid "ProjectSettings|Internal"
@@ -17195,9 +17758,6 @@ msgstr ""
msgid "ProjectSettings|Merge options"
msgstr ""
-msgid "ProjectSettings|Merge pipelines will try to validate the post-merge result prior to merging"
-msgstr ""
-
msgid "ProjectSettings|Merge requests"
msgstr ""
@@ -17225,6 +17785,9 @@ msgstr ""
msgid "ProjectSettings|Pipelines"
msgstr ""
+msgid "ProjectSettings|Pipelines for merge requests must be enabled in the CI/CD configuration file, or pipelines could be unresolvable or dropped"
+msgstr ""
+
msgid "ProjectSettings|Pipelines must succeed"
msgstr ""
@@ -17252,6 +17815,9 @@ msgstr ""
msgid "ProjectSettings|Show link to create/view merge request when pushing from the command line"
msgstr ""
+msgid "ProjectSettings|Skipped pipelines are considered successful"
+msgstr ""
+
msgid "ProjectSettings|Snippets"
msgstr ""
@@ -17267,6 +17833,9 @@ msgstr ""
msgid "ProjectSettings|These checks must pass before merge requests can be merged"
msgstr ""
+msgid "ProjectSettings|This introduces the risk of merging changes that will not pass the pipeline."
+msgstr ""
+
msgid "ProjectSettings|This setting is applied on the server level and can be overridden by an admin."
msgstr ""
@@ -17444,15 +18013,27 @@ msgstr ""
msgid "ProjectsNew|Blank project"
msgstr ""
+msgid "ProjectsNew|Connect your external repository to GitLab CI/CD."
+msgstr ""
+
msgid "ProjectsNew|Contact an administrator to enable options for importing your project."
msgstr ""
msgid "ProjectsNew|Create"
msgstr ""
+msgid "ProjectsNew|Create a blank project to house your files, plan your work, and collaborate on code, among other things."
+msgstr ""
+
+msgid "ProjectsNew|Create blank project"
+msgstr ""
+
msgid "ProjectsNew|Create from template"
msgstr ""
+msgid "ProjectsNew|Create new project"
+msgstr ""
+
msgid "ProjectsNew|Creating project & repository."
msgstr ""
@@ -17477,6 +18058,9 @@ msgstr ""
msgid "ProjectsNew|Project description %{tag_start}(optional)%{tag_end}"
msgstr ""
+msgid "ProjectsNew|Run CI/CD for external repository"
+msgstr ""
+
msgid "ProjectsNew|Template"
msgstr ""
@@ -17738,6 +18322,9 @@ msgstr ""
msgid "Protected Branch"
msgstr ""
+msgid "Protected Branches"
+msgstr ""
+
msgid "Protected Environment"
msgstr ""
@@ -17750,6 +18337,9 @@ msgstr ""
msgid "Protected Tag"
msgstr ""
+msgid "Protected Tags"
+msgstr ""
+
msgid "Protected branches"
msgstr ""
@@ -17849,6 +18439,9 @@ msgstr ""
msgid "Provider"
msgstr ""
+msgid "Proxy support for this API is not available currently"
+msgstr ""
+
msgid "Pseudonymizer data collection"
msgstr ""
@@ -17873,6 +18466,9 @@ msgstr ""
msgid "Publish to status page"
msgstr ""
+msgid "Published on status page"
+msgstr ""
+
msgid "Publishes this issue to the associated status page."
msgstr ""
@@ -18047,6 +18643,9 @@ msgstr ""
msgid "Recipe"
msgstr ""
+msgid "Reconfigure"
+msgstr ""
+
msgid "Recover hidden stage"
msgstr ""
@@ -18133,6 +18732,9 @@ msgstr ""
msgid "Registration|Your profile"
msgstr ""
+msgid "Regulate approvals by authors/committers, based on compliance frameworks. Can be changed only at the instance level."
+msgstr ""
+
msgid "Rejected (closed)"
msgstr ""
@@ -18183,15 +18785,24 @@ msgstr ""
msgid "ReleaseAssetLinkType|Image"
msgstr ""
+msgid "ReleaseAssetLinkType|Images"
+msgstr ""
+
msgid "ReleaseAssetLinkType|Other"
msgstr ""
msgid "ReleaseAssetLinkType|Package"
msgstr ""
+msgid "ReleaseAssetLinkType|Packages"
+msgstr ""
+
msgid "ReleaseAssetLinkType|Runbook"
msgstr ""
+msgid "ReleaseAssetLinkType|Runbooks"
+msgstr ""
+
msgid "Releases"
msgstr ""
@@ -18204,6 +18815,9 @@ msgstr ""
msgid "Releases documentation"
msgstr ""
+msgid "Releases|New Release"
+msgstr ""
+
msgid "Release|Something went wrong while getting the release details"
msgstr ""
@@ -18276,6 +18890,9 @@ msgstr ""
msgid "Remove fork relationship"
msgstr ""
+msgid "Remove from batch"
+msgstr ""
+
msgid "Remove from board"
msgstr ""
@@ -18465,6 +19082,12 @@ msgstr ""
msgid "Replication"
msgstr ""
+msgid "Replication enabled"
+msgstr ""
+
+msgid "Replication paused"
+msgstr ""
+
msgid "Reply by email"
msgstr ""
@@ -18492,15 +19115,15 @@ msgstr ""
msgid "Reported %{timeAgo} by %{reportedBy}"
msgstr ""
-msgid "Reported Resource Changes: %{addNum} to add, %{changeNum} to change, %{deleteNum} to delete"
-msgstr ""
-
msgid "Reporter"
msgstr ""
msgid "Reporting"
msgstr ""
+msgid "Reports"
+msgstr ""
+
msgid "Reports|%{combinedString} and %{resolvedString}"
msgstr ""
@@ -18539,6 +19162,9 @@ msgstr ""
msgid "Reports|Failure"
msgstr ""
+msgid "Reports|Identifier"
+msgstr ""
+
msgid "Reports|Metrics reports are loading"
msgstr ""
@@ -18551,6 +19177,9 @@ msgstr ""
msgid "Reports|Metrics reports failed loading results"
msgstr ""
+msgid "Reports|Scanner"
+msgstr ""
+
msgid "Reports|Severity"
msgstr ""
@@ -18599,15 +19228,33 @@ msgstr ""
msgid "Repository cleanup has started. You will receive an email once the cleanup operation is complete."
msgstr ""
+msgid "Repository files count over the limit"
+msgstr ""
+
+msgid "Repository has an invalid default branch name."
+msgstr ""
+
+msgid "Repository has more than one branch."
+msgstr ""
+
msgid "Repository has no locks."
msgstr ""
+msgid "Repository has tags."
+msgstr ""
+
msgid "Repository maintenance"
msgstr ""
msgid "Repository mirroring"
msgstr ""
+msgid "Repository must contain at least 1 file."
+msgstr ""
+
+msgid "Repository size is above the limit."
+msgstr ""
+
msgid "Repository static objects"
msgstr ""
@@ -18617,7 +19264,7 @@ msgstr ""
msgid "Repository sync capacity"
msgstr ""
-msgid "Repository: %{counter_repositories} / Wikis: %{counter_wikis} / Build Artifacts: %{counter_build_artifacts} / LFS: %{counter_lfs_objects}"
+msgid "Repository: %{counter_repositories} / Wikis: %{counter_wikis} / Build Artifacts: %{counter_build_artifacts} / LFS: %{counter_lfs_objects} / Snippets: %{counter_snippets}"
msgstr ""
msgid "RepositorySettingsAccessLevel|Select"
@@ -18629,6 +19276,9 @@ msgstr ""
msgid "Request Headers"
msgstr ""
+msgid "Request details"
+msgstr ""
+
msgid "Request parameter %{param} is missing."
msgstr ""
@@ -18735,6 +19385,9 @@ msgstr ""
msgid "Reset template"
msgstr ""
+msgid "Reset to project defaults"
+msgstr ""
+
msgid "Resetting the authorization key for this project will require updating the authorization key in every alert source it is enabled in."
msgstr ""
@@ -18831,9 +19484,6 @@ msgstr ""
msgid "Resume"
msgstr ""
-msgid "Resume replication"
-msgstr ""
-
msgid "Resync"
msgstr ""
@@ -19456,6 +20106,9 @@ msgstr ""
msgid "SecurityReports|False positive"
msgstr ""
+msgid "SecurityReports|Group Security Dashboard"
+msgstr ""
+
msgid "SecurityReports|Hide dismissed"
msgstr ""
@@ -19510,10 +20163,13 @@ msgstr ""
msgid "SecurityReports|Remove project from dashboard"
msgstr ""
-msgid "SecurityReports|Report type"
+msgid "SecurityReports|Return to dashboard"
msgstr ""
-msgid "SecurityReports|Return to dashboard"
+msgid "SecurityReports|Scan details"
+msgstr ""
+
+msgid "SecurityReports|Scanner type"
msgstr ""
msgid "SecurityReports|Security Dashboard"
@@ -19570,6 +20226,9 @@ msgstr ""
msgid "SecurityReports|There was an error while generating the report."
msgstr ""
+msgid "SecurityReports|Unable to add %{invalidProjectsMessage}"
+msgstr ""
+
msgid "SecurityReports|Unable to add %{invalidProjects}"
msgstr ""
@@ -19684,6 +20343,9 @@ msgstr ""
msgid "Select due date"
msgstr ""
+msgid "Select file"
+msgstr ""
+
msgid "Select group or project"
msgstr ""
@@ -19747,9 +20409,6 @@ msgstr ""
msgid "Select the branch you want to set as the default for this project. All merge requests and commits will automatically be made against this branch unless you specify a different one."
msgstr ""
-msgid "Select the configured storage available for new repositories to be placed on."
-msgstr ""
-
msgid "Select the custom project template source group."
msgstr ""
@@ -19942,12 +20601,6 @@ msgstr ""
msgid "Service Desk is enabled but not yet active"
msgstr ""
-msgid "Service Desk is off"
-msgstr ""
-
-msgid "Service Desk is on"
-msgstr ""
-
msgid "Service Templates"
msgstr ""
@@ -20035,6 +20688,15 @@ msgstr ""
msgid "Set the milestone to %{milestone_reference}."
msgstr ""
+msgid "Set the number of concurrent requests this secondary node will make to the primary node while backfilling."
+msgstr ""
+
+msgid "Set the synchronization and verification capacity for the secondary node."
+msgstr ""
+
+msgid "Set the timeout in seconds to send a secondary node status to the primary and IPs allowed for the secondary nodes."
+msgstr ""
+
msgid "Set time estimate"
msgstr ""
@@ -20074,6 +20736,9 @@ msgstr ""
msgid "Set weight to %{weight}."
msgstr ""
+msgid "Set what should be replicated by choosing specific projects or groups by the secondary node."
+msgstr ""
+
msgid "SetPasswordToCloneLink|set a password"
msgstr ""
@@ -20200,6 +20865,9 @@ msgstr ""
msgid "Show complete raw log"
msgstr ""
+msgid "Show details"
+msgstr ""
+
msgid "Show file browser"
msgstr ""
@@ -20209,10 +20877,13 @@ msgstr ""
msgid "Show latest version"
msgstr ""
+msgid "Show list"
+msgstr ""
+
msgid "Show me everything"
msgstr ""
-msgid "Show me how"
+msgid "Show me how to add a pipeline"
msgstr ""
msgid "Show me more advanced stuff"
@@ -20261,9 +20932,6 @@ msgstr ""
msgid "Showing all issues"
msgstr ""
-msgid "Showing all labels"
-msgstr ""
-
msgid "Showing last %{size} of log -"
msgstr ""
@@ -20375,9 +21043,6 @@ msgstr ""
msgid "Skip outdated deployment jobs"
msgstr ""
-msgid "Skip this for now"
-msgstr ""
-
msgid "Skipped"
msgstr ""
@@ -20522,6 +21187,9 @@ msgstr ""
msgid "Something went wrong while adding your award. Please try again."
msgstr ""
+msgid "Something went wrong while applying the batch of suggestions. Please try again."
+msgstr ""
+
msgid "Something went wrong while applying the suggestion. Please try again."
msgstr ""
@@ -20624,10 +21292,13 @@ msgstr ""
msgid "Something went wrong while updating a requirement."
msgstr ""
+msgid "Something went wrong while updating assignees"
+msgstr ""
+
msgid "Something went wrong while updating your list settings"
msgstr ""
-msgid "Something went wrong with your automatic subscription renewal"
+msgid "Something went wrong with your automatic subscription renewal."
msgstr ""
msgid "Something went wrong, unable to add %{project} to dashboard"
@@ -20645,6 +21316,9 @@ msgstr ""
msgid "Something went wrong, unable to search projects"
msgstr ""
+msgid "Something went wrong."
+msgstr ""
+
msgid "Something went wrong. Please try again."
msgstr ""
@@ -20828,6 +21502,9 @@ msgstr ""
msgid "Source code"
msgstr ""
+msgid "Source code (%{fileExtension})"
+msgstr ""
+
msgid "Source is not available"
msgstr ""
@@ -20912,6 +21589,9 @@ msgstr ""
msgid "Stack trace"
msgstr ""
+msgid "Stacktrace snippet"
+msgstr ""
+
msgid "Stage"
msgstr ""
@@ -21017,6 +21697,9 @@ msgstr ""
msgid "Start thread & reopen %{noteable_name}"
msgstr ""
+msgid "Start using Directed Acyclic Graphs (DAG)"
+msgstr ""
+
msgid "Start your Free Gold Trial"
msgstr ""
@@ -21479,6 +22162,21 @@ msgstr ""
msgid "SuggestedColors|Very pale orange"
msgstr ""
+msgid "Suggestion is not applicable as the suggestion was not found."
+msgstr ""
+
+msgid "Suggestions are not applicable as one or more suggestions were not found."
+msgstr ""
+
+msgid "Suggestions are not applicable as their lines cannot overlap."
+msgstr ""
+
+msgid "Suggestions must all be on the same branch."
+msgstr ""
+
+msgid "Suggestions that change line count can't be added to batches, yet."
+msgstr ""
+
msgid "Suggestions:"
msgstr ""
@@ -21521,9 +22219,6 @@ msgstr ""
msgid "Synchronization disabled"
msgstr ""
-msgid "Synchronization of container repositories is disabled."
-msgstr ""
-
msgid "System"
msgstr ""
@@ -21704,6 +22399,34 @@ msgstr ""
msgid "Terms of Service and Privacy Policy"
msgstr ""
+msgid "Terraform|%{number} Terraform report failed to generate"
+msgid_plural "Terraform|%{number} Terraform reports failed to generate"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Terraform|%{number} Terraform report was generated in your pipelines"
+msgid_plural "Terraform|%{number} Terraform reports were generated in your pipelines"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Terraform|A Terraform report failed to generate."
+msgstr ""
+
+msgid "Terraform|A Terraform report was generated in your pipelines."
+msgstr ""
+
+msgid "Terraform|Generating the report caused an error."
+msgstr ""
+
+msgid "Terraform|Reported Resource Changes: %{addNum} to add, %{changeNum} to change, %{deleteNum} to delete"
+msgstr ""
+
+msgid "Terraform|The Terraform report %{name} failed to generate."
+msgstr ""
+
+msgid "Terraform|The Terraform report %{name} was generated in your pipelines."
+msgstr ""
+
msgid "Test"
msgstr ""
@@ -21730,7 +22453,7 @@ msgstr ""
msgid "TestHooks|Ensure the project has CI pipelines."
msgstr ""
-msgid "TestHooks|Ensure the project has at least one commit."
+msgid "TestHooks|Ensure the project has deployments."
msgstr ""
msgid "TestHooks|Ensure the project has issues."
@@ -21831,7 +22554,7 @@ msgstr ""
msgid "The Prometheus server responded with \"bad request\". Please check your queries are correct and are supported in your Prometheus version. %{documentationLink}"
msgstr ""
-msgid "The URL defined on the primary node that secondary nodes should use to contact it. Defaults to URL"
+msgid "The URL defined on the primary node that secondary nodes should use to contact it."
msgstr ""
msgid "The URL to use for connecting to Elasticsearch. Use a comma-separated list to support clustering (e.g., \"http://localhost:9200, http://localhost:9201\")."
@@ -21840,6 +22563,9 @@ msgstr ""
msgid "The X509 Certificate to use when mutual TLS is required to communicate with the external authorization service. If left blank, the server certificate is still validated when accessing over HTTPS."
msgstr ""
+msgid "The above settings apply to all projects with the selected compliance framework(s)."
+msgstr ""
+
msgid "The amount of seconds after which a request to get a secondary node status will time out."
msgstr ""
@@ -22139,9 +22865,6 @@ msgstr ""
msgid "The total stage shows the time it takes between creating an issue and deploying the code to production. The data will be automatically added once you have completed the full idea to production cycle."
msgstr ""
-msgid "The unique identifier for the Geo node. Must match %{geoNodeName} if it is set in gitlab.rb, otherwise it must match %{externalUrl} with a trailing slash"
-msgstr ""
-
msgid "The update action will time out after %{number_of_minutes} minutes. For big repositories, use a clone/push combination."
msgstr ""
@@ -22166,9 +22889,6 @@ msgstr ""
msgid "The user you are trying to deactivate has been active in the past %{minimum_inactive_days} days and cannot be deactivated"
msgstr ""
-msgid "The user-facing URL of the Geo node"
-msgstr ""
-
msgid "The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6."
msgstr ""
@@ -22259,6 +22979,9 @@ msgstr ""
msgid "There was a problem fetching project branches."
msgstr ""
+msgid "There was a problem fetching project tags."
+msgstr ""
+
msgid "There was a problem fetching project users."
msgstr ""
@@ -22310,6 +23033,9 @@ msgstr ""
msgid "There was an error fetching the %{replicableType}"
msgstr ""
+msgid "There was an error fetching the Geo Settings"
+msgstr ""
+
msgid "There was an error fetching the Node's Groups"
msgstr ""
@@ -22430,6 +23156,12 @@ msgstr ""
msgid "This %{issuable} is locked. Only <strong>project members</strong> can comment."
msgstr ""
+msgid "This %{noteableTypeText} is %{confidentialLinkStart}confidential%{linkEnd} and %{lockedLinkStart}locked%{linkEnd}."
+msgstr ""
+
+msgid "This %{noteableTypeText} is locked."
+msgstr ""
+
msgid "This %{viewer} could not be displayed because %{reason}. You can %{options} instead."
msgstr ""
@@ -22448,6 +23180,9 @@ msgstr ""
msgid "This action can lead to data loss. To prevent accidental actions we ask you to confirm your intention."
msgstr ""
+msgid "This also resolves all related threads"
+msgstr ""
+
msgid "This also resolves the discussion"
msgstr ""
@@ -22538,6 +23273,9 @@ msgstr ""
msgid "This epic does not exist or you don't have sufficient permission."
msgstr ""
+msgid "This feature is currently in beta. We invite you to %{linkStart}give feedback%{linkEnd}."
+msgstr ""
+
msgid "This feature requires local storage to be enabled"
msgstr ""
@@ -22571,7 +23309,7 @@ msgstr ""
msgid "This is a Work in Progress"
msgstr ""
-msgid "This is a confidential issue."
+msgid "This is a confidential %{noteableTypeText}."
msgstr ""
msgid "This is a delayed job to run in %{remainingTime}"
@@ -22598,9 +23336,6 @@ msgstr ""
msgid "This is your current session"
msgstr ""
-msgid "This issue is %{confidentialLinkStart}confidential%{linkEnd} and %{lockedLinkStart}locked%{linkEnd}."
-msgstr ""
-
msgid "This issue is confidential"
msgstr ""
@@ -22610,9 +23345,6 @@ msgstr ""
msgid "This issue is in a child epic of the filtered epic"
msgstr ""
-msgid "This issue is locked."
-msgstr ""
-
msgid "This job depends on other jobs with expired/erased artifacts: %{invalid_dependencies}"
msgstr ""
@@ -22712,6 +23444,9 @@ msgstr ""
msgid "This license has already expired."
msgstr ""
+msgid "This link points to external content"
+msgstr ""
+
msgid "This may expose confidential information as the selected fork is in another namespace that can have other members."
msgstr ""
@@ -22739,6 +23474,9 @@ msgstr ""
msgid "This page will be removed in a future release."
msgstr ""
+msgid "This pipeline does not use the %{codeStart}needs%{codeEnd} keyword and can't be represented as a directed acyclic graph."
+msgstr ""
+
msgid "This pipeline makes use of a predefined CI/CD configuration enabled by %{strongStart}Auto DevOps.%{strongEnd}"
msgstr ""
@@ -22967,6 +23705,9 @@ msgstr ""
msgid "Time from last commit to merge"
msgstr ""
+msgid "Time in seconds"
+msgstr ""
+
msgid "Time in seconds GitLab will wait for a response from the external service. When the service does not respond in time, access will be denied."
msgstr ""
@@ -23388,9 +24129,15 @@ msgstr ""
msgid "Total artifacts size: %{total_size}"
msgstr ""
+msgid "Total cores (CPUs)"
+msgstr ""
+
msgid "Total issues"
msgstr ""
+msgid "Total memory (GB)"
+msgstr ""
+
msgid "Total test time for all commits/merges"
msgstr ""
@@ -23592,6 +24339,9 @@ msgstr ""
msgid "U2F only works with HTTPS-enabled websites. Contact your administrator for more details."
msgstr ""
+msgid "UI Development Kit"
+msgstr ""
+
msgid "URL"
msgstr ""
@@ -23664,9 +24414,6 @@ msgstr ""
msgid "Unable to load the merge request widget. Try reloading the page."
msgstr ""
-msgid "Unable to process group import file"
-msgstr ""
-
msgid "Unable to resolve"
msgstr ""
@@ -23682,6 +24429,9 @@ msgstr ""
msgid "Unable to sign you in to the group with SAML due to \"%{reason}\""
msgstr ""
+msgid "Unable to suggest a path. Please refresh and try again."
+msgstr ""
+
msgid "Unable to update label prioritization at this time"
msgstr ""
@@ -23697,6 +24447,9 @@ msgstr ""
msgid "Unarchiving the project will restore people's ability to make changes to it. The repository can be committed to, and issues, comments, and other entities can be created. %{strong_start}Once active, this project shows up in the search and on the dashboard.%{strong_end}"
msgstr ""
+msgid "Unassign from commenting user"
+msgstr ""
+
msgid "Unblock"
msgstr ""
@@ -23772,6 +24525,9 @@ msgstr ""
msgid "Unreachable"
msgstr ""
+msgid "Unrecognized cluster type"
+msgstr ""
+
msgid "Unresolve"
msgstr ""
@@ -23832,6 +24588,9 @@ msgstr ""
msgid "Unsubscribes from this %{quick_action_target}."
msgstr ""
+msgid "Unsupported todo type passed. Supported todo types are: %{todo_types}"
+msgstr ""
+
msgid "Until"
msgstr ""
@@ -23859,6 +24618,9 @@ msgstr ""
msgid "Update approval rule"
msgstr ""
+msgid "Update approvers"
+msgstr ""
+
msgid "Update failed"
msgstr ""
@@ -23868,6 +24630,9 @@ msgstr ""
msgid "Update it"
msgstr ""
+msgid "Update iteration"
+msgstr ""
+
msgid "Update now"
msgstr ""
@@ -23913,7 +24678,7 @@ msgstr ""
msgid "Updated at"
msgstr ""
-msgid "Updated to"
+msgid "Updated to %{linkStart}chart v%{linkEnd}"
msgstr ""
msgid "Updating"
@@ -23943,6 +24708,9 @@ msgstr ""
msgid "Upload CSV file"
msgstr ""
+msgid "Upload License"
+msgstr ""
+
msgid "Upload New File"
msgstr ""
@@ -24024,16 +24792,19 @@ msgstr ""
msgid "UsageQuota|Pipelines"
msgstr ""
+msgid "UsageQuota|Purchase more storage"
+msgstr ""
+
msgid "UsageQuota|Repositories"
msgstr ""
msgid "UsageQuota|Repository"
msgstr ""
-msgid "UsageQuota|Storage"
+msgid "UsageQuota|Snippets"
msgstr ""
-msgid "UsageQuota|Storage usage:"
+msgid "UsageQuota|Storage"
msgstr ""
msgid "UsageQuota|This namespace has no projects which use shared runners"
@@ -24066,6 +24837,12 @@ msgstr ""
msgid "UsageQuota|Wikis"
msgstr ""
+msgid "UsageQuota|You used: %{usage} %{limit}"
+msgstr ""
+
+msgid "UsageQuota|out of %{formattedLimit} of your namespace storage"
+msgstr ""
+
msgid "Use %{code_start}::%{code_end} to create a %{link_start}scoped label set%{link_end} (eg. %{code_start}priority::1%{code_end})"
msgstr ""
@@ -24132,6 +24909,9 @@ msgstr ""
msgid "User IDs"
msgstr ""
+msgid "User Lists can only be created and modified with %{linkStart}the API%{linkEnd}"
+msgstr ""
+
msgid "User OAuth applications"
msgstr ""
@@ -24156,6 +24936,9 @@ msgstr ""
msgid "User key was successfully removed."
msgstr ""
+msgid "User list %{name} will be removed. Are you sure?"
+msgstr ""
+
msgid "User map"
msgstr ""
@@ -24177,148 +24960,10 @@ msgstr ""
msgid "User was successfully updated."
msgstr ""
-msgid "UserOnboardingTour|%{activeTour}/%{totalTours}"
-msgstr ""
-
-msgid "UserOnboardingTour|%{completed}/%{total} steps completed"
-msgstr ""
-
-msgid "UserOnboardingTour|%{emphasisStart}Well done!%{emphasisEnd}%{lineBreak}%{lineBreak}That's it for our guided tour, congratulations for making it all the way to the end!%{lineBreak}%{lineBreak}We hope this gave you a good overview of GitLab and how it can help you. We'll now show you how to create your own project and invite your colleagues."
-msgstr ""
-
-msgid "UserOnboardingTour|Adding other members to a project is done through Project Settings. Click on %{emphasisStart}Settings%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Alright, that's it for Commits. Let's take a look at the %{emphasisStart}Branches%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Awesome! Now click on %{emphasisStart}Members%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Click on one of the %{emphasisStart}Compare%{emphasisEnd} buttons to compare a branch to master."
-msgstr ""
-
-msgid "UserOnboardingTour|Click on one of the %{emphasisStart}pipeline IDs%{emphasisEnd} to see the details of a pipeline."
-msgstr ""
-
-msgid "UserOnboardingTour|Click to open the latest commit to see its details."
-msgstr ""
-
-msgid "UserOnboardingTour|Close 'Learn GitLab'"
-msgstr ""
-
-msgid "UserOnboardingTour|Commits are shown in chronological order and can be filtered by the commit message or by the branch."
+msgid "UserList|Delete %{name}?"
msgstr ""
-msgid "UserOnboardingTour|Create a project"
-msgstr ""
-
-msgid "UserOnboardingTour|Exit 'Learn GitLab'"
-msgstr ""
-
-msgid "UserOnboardingTour|Got it"
-msgstr ""
-
-msgid "UserOnboardingTour|Great job! %{clapHands} We hope the tour was helpful and that you learned how to use GitLab.%{lineBreak}%{lineBreak}We'd love to get your feedback on this tour.%{lineBreak}%{lineBreak}%{emphasisStart}How helpful would you say this guided tour was?%{emphasisEnd}%{lineBreak}%{lineBreak}"
-msgstr ""
-
-msgid "UserOnboardingTour|Guided GitLab Tour"
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can compare the changes of this branch to another one. Changes are divided by files so that it's easier to see what was changed where."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can create a project from scratch, start with a template or import a repository from other platforms. Whatever you choose, we'll guide you through the process.%{lineBreak}%{lineBreak}Fill in your new project information and click on %{emphasisStart}Create Project%{emphasisEnd} to progress to the next step."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can see the breakdown of the pipelines: its stages and jobs in each of the stages and their status.%{lineBreak}%{lineBreak}Our CI/CD pipelines are quite complex, most of our users have fewer and simpler pipelines."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can see the current members of the project (just you at the moment) and invite new members.%{lineBreak}%{lineBreak}You can invite multiple members at once (existing GitLab users or invite by email) and you can also set their roles and permissions.%{lineBreak}%{lineBreak}Add a few members and click on %{emphasisStart}Add to project%{emphasisEnd} to complete this step."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can see what changes were made with this commit, on what branch and if there's a related merge request. The status of the pipeline will also show up if CI/CD is set up.%{lineBreak}%{lineBreak}You can also comment on the lines of code that were changed and start a discussion with your colleagues!"
-msgstr ""
-
-msgid "UserOnboardingTour|Here's an overview of branches in the %{emphasisStart}%{projectName}%{emphasisEnd} project. They're split into Active and Stale.%{lineBreak}%{lineBreak}From here, you can create a new merge request from a branch, or compare the branch to any other branch in the project. By default, it will compare it to the master branch."
-msgstr ""
-
-msgid "UserOnboardingTour|Invite colleagues"
-msgstr ""
-
-msgid "UserOnboardingTour|Issues are great for communicating and keeping track of progress in GitLab. These are all issues that are open in the %{emphasisStart}%{projectName}%{emphasisEnd}.%{lineBreak}%{lineBreak}You can help us improve GitLab by contributing work to issues that are labeled <span class=\"badge color-label accept-mr-label\">Accepting merge requests</span>.%{lineBreak}%{lineBreak}This list can be filtered by labels, milestones, assignees, authors... We'll show you how it looks when the list is filtered by a label."
-msgstr ""
-
-msgid "UserOnboardingTour|Learn GitLab"
-msgstr ""
-
-msgid "UserOnboardingTour|Let's take a closer look at a merge request. Click on the title of one."
-msgstr ""
-
-msgid "UserOnboardingTour|Let's take a closer look at all the commits. Click on %{emphasisStart}Commits%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Let's take a closer look at the repository of this project. Click on %{emphasisStart}Repository%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|No thanks"
-msgstr ""
-
-msgid "UserOnboardingTour|Ok, let's go"
-msgstr ""
-
-msgid "UserOnboardingTour|Ok, show me"
-msgstr ""
-
-msgid "UserOnboardingTour|Open one of the issues by clicking on its title."
-msgstr ""
-
-msgid "UserOnboardingTour|Restart this step"
-msgstr ""
-
-msgid "UserOnboardingTour|Skip this step"
-msgstr ""
-
-msgid "UserOnboardingTour|Sweet! Your project was created and is ready to be used.%{lineBreak}%{lineBreak}You can start adding files to the repository or clone it. One last thing we want to show you is how to invite your colleagues to your new project."
-msgstr ""
-
-msgid "UserOnboardingTour|Take a look. Here's a nifty menu for quickly creating issues, merge requests, snippets, projects and groups. Click on it and select \"New project\" from the \"GitLab\" section to get started."
-msgstr ""
-
-msgid "UserOnboardingTour|Thanks for taking the guided tour. Remember, if you want to go through it again, you can start %{emphasisStart}Learn GitLab%{emphasisEnd} in the help menu on the top right."
-msgstr ""
-
-msgid "UserOnboardingTour|Thanks for the feedback! %{thumbsUp}"
-msgstr ""
-
-msgid "UserOnboardingTour|That's it for issues. Let'st take a look at %{emphasisStart}Merge Requests%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|That's it for merge requests. Now for the final part of this guided tour - the %{emphasisStart}CI/CD%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|That's it for the Repository. Let's take a look at the %{emphasisStart}Issues%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|The structure of this page is very similar to issues. Status, description, discussion and the sidebar are all here.%{lineBreak}%{lineBreak}But take a look below the description and you'll notice that there's more information about the merge request, the CI/CD pipeline and the options for approving it.%{lineBreak}%{lineBreak}Alongside the discussion you can also see more information about commits in this merge request, the status of pipelines and review all changes that were made."
-msgstr ""
-
-msgid "UserOnboardingTour|There's a lot of information here but don't worry, we'll go through it.%{lineBreak}%{lineBreak}On the top you can see the status of the issue and when it was opened and by whom. Directly below it is the issue description and below that are other %{emphasisStart}related issues%{emphasisEnd} and %{emphasisStart}merge requests%{emphasisEnd} (if any). Then below that is the %{emphasisStart}discussion%{emphasisEnd}, that's where most of the communication happens.%{lineBreak}%{lineBreak}On the right, there's a sidebar where you can view/change the %{emphasisStart}assignee, milestone, due date, labels, weight%{emphasisEnd}, etc."
-msgstr ""
-
-msgid "UserOnboardingTour|These are all the CI/CD pipelines we have for our %{emphasisStart}%{projectName}%{emphasisEnd} project.%{lineBreak}%{lineBreak}Here you can see the status of each pipeline, for what commit it's running for, its stages and the status for them."
-msgstr ""
-
-msgid "UserOnboardingTour|These are all the issues that are available for community contributions. Let's take a closer look at one of them."
-msgstr ""
-
-msgid "UserOnboardingTour|This is an overview of all merge requests in this project. Similarly to the issues overview it can be filtered down by things like labels, milestones, authors, assignees, etc."
-msgstr ""
-
-msgid "UserOnboardingTour|This is the repository for the %{emphasisStart}%{projectName}%{emphasisEnd} project. All our code is stored here. Feel free to explore and take a closer look at folders and files.%{lineBreak}%{lineBreak}Above the file structure you can see the latest commit, who the author is and the status of the CI/CD pipeline.%{lineBreak}%{lineBreak}If you scroll down below the file structure, you'll find the Readme of this project. This is defined in the README.md file at the root of the repository."
-msgstr ""
-
-msgid "UserOnboardingTour|Welcome to the project overview of the %{emphasisStart}%{projectName}%{emphasisEnd} project. This is the project that we use to work on GitLab. At first, a project seems like a simple repository, but at GitLab, a project is so much more.%{lineBreak}%{lineBreak}You can create projects for hosting your codebase, use it as an issue tracker, collaborate on code, and continuously build, test, and deploy your app with built-in GitLab CI/CD."
+msgid "UserList|created %{timeago}"
msgstr ""
msgid "UserProfile|Activity"
@@ -24471,6 +25116,9 @@ msgstr ""
msgid "UsersSelect|Unassigned"
msgstr ""
+msgid "Using %{codeStart}needs%{codeEnd} allows jobs to run before their stage is reached, as soon as their individual dependencies are met, which speeds up your pipelines."
+msgstr ""
+
msgid "Using %{code_start}::%{code_end} denotes a %{link_start}scoped label set%{link_end}"
msgstr ""
@@ -24555,9 +25203,6 @@ msgstr ""
msgid "Versions"
msgstr ""
-msgid "Very helpful"
-msgstr ""
-
msgid "View Documentation"
msgstr ""
@@ -24706,6 +25351,9 @@ msgstr ""
msgid "VisibilityLevel|Unknown"
msgstr ""
+msgid "Visit settings page"
+msgstr ""
+
msgid "VisualReviewApp|%{stepStart}Step 1%{stepEnd}. Copy the following script:"
msgstr ""
@@ -24799,6 +25447,9 @@ msgstr ""
msgid "VulnerabilityManagement|Something went wrong while trying to delete the comment. Please try again later."
msgstr ""
+msgid "VulnerabilityManagement|Something went wrong while trying to refresh the vulnerability. Please try again later."
+msgstr ""
+
msgid "VulnerabilityManagement|Something went wrong while trying to retrieve the vulnerability history. Please try again later."
msgstr ""
@@ -24841,6 +25492,9 @@ msgstr ""
msgid "Vulnerability|Class"
msgstr ""
+msgid "Vulnerability|Crash Address"
+msgstr ""
+
msgid "Vulnerability|Description"
msgstr ""
@@ -24916,9 +25570,6 @@ msgstr ""
msgid "We couldn't reach the Prometheus server. Either the server no longer exists or the configuration details need updating."
msgstr ""
-msgid "We created a short guided tour that will help you learn the basics of GitLab and how it will help you be better at your job. It should only take a couple of minutes. You will be guided by two types of helpers, best recognized by their color."
-msgstr ""
-
msgid "We detected potential spam in the %{humanized_resource_name}. Please solve the reCAPTCHA to proceed."
msgstr ""
@@ -24952,6 +25603,9 @@ msgstr ""
msgid "We've found no vulnerabilities"
msgstr ""
+msgid "Web Application Firewall"
+msgstr ""
+
msgid "Web IDE"
msgstr ""
@@ -25012,9 +25666,6 @@ msgstr ""
msgid "Welcome to GitLab.com<br>@%{name}!"
msgstr ""
-msgid "Welcome to the Guided GitLab Tour"
-msgstr ""
-
msgid "Welcome to the guided GitLab tour"
msgstr ""
@@ -25062,9 +25713,6 @@ msgstr ""
msgid "While it's rare to have no vulnerabilities, it can happen. In any event, we ask that you please double check your settings to make sure you've set up your dashboard correctly."
msgstr ""
-msgid "White helpers give contextual information."
-msgstr ""
-
msgid "Who can be an approver?"
msgstr ""
@@ -25110,12 +25758,18 @@ msgstr ""
msgid "WikiEmptyIssueMessage|Suggest wiki improvement"
msgstr ""
+msgid "WikiEmptyIssueMessage|You must be a group member in order to add wiki pages. If you have suggestions for how to improve the wiki for this group, consider opening an issue in the %{issues_link}."
+msgstr ""
+
msgid "WikiEmptyIssueMessage|You must be a project member in order to add wiki pages. If you have suggestions for how to improve the wiki for this project, consider opening an issue in the %{issues_link}."
msgstr ""
msgid "WikiEmptyIssueMessage|issue tracker"
msgstr ""
+msgid "WikiEmpty|A wiki is where you can store all the details about your group. This can include why you've created it, its principles, how to use it, and so on."
+msgstr ""
+
msgid "WikiEmpty|A wiki is where you can store all the details about your project. This can include why you've created it, its principles, how to use it, and so on."
msgstr ""
@@ -25125,12 +25779,21 @@ msgstr ""
msgid "WikiEmpty|Suggest wiki improvement"
msgstr ""
+msgid "WikiEmpty|The wiki lets you write documentation for your group"
+msgstr ""
+
msgid "WikiEmpty|The wiki lets you write documentation for your project"
msgstr ""
+msgid "WikiEmpty|This group has no wiki pages"
+msgstr ""
+
msgid "WikiEmpty|This project has no wiki pages"
msgstr ""
+msgid "WikiEmpty|You must be a group member in order to add wiki pages."
+msgstr ""
+
msgid "WikiEmpty|You must be a project member in order to add wiki pages."
msgstr ""
@@ -25407,13 +26070,13 @@ msgstr ""
msgid "You can now export your security dashboard to a CSV report."
msgstr ""
-msgid "You can now submit a merge request to get this change into the original branch."
+msgid "You can now manage alert endpoint configuration in the Alerts section on the Operations settings page. Fields on this page have been deprecated."
msgstr ""
-msgid "You can now submit a merge request to get this change into the original project."
+msgid "You can now submit a merge request to get this change into the original branch."
msgstr ""
-msgid "You can only add files when you are on a branch"
+msgid "You can now submit a merge request to get this change into the original project."
msgstr ""
msgid "You can only edit files when you are on a branch"
@@ -25635,7 +26298,7 @@ msgstr ""
msgid "You need to upload a Google Takeout archive."
msgstr ""
-msgid "You reached %{usage_in_percent} of %{namespace_name}'s capacity (%{used_storage} of %{storage_limit})"
+msgid "You reached %{usage_in_percent} of %{namespace_name}'s storage capacity (%{used_storage} of %{storage_limit})"
msgstr ""
msgid "You tried to fork %{link_to_the_project} but it failed for the following reason:"
@@ -25938,13 +26601,13 @@ msgstr ""
msgid "Your subscription expired!"
msgstr ""
-msgid "Your subscription has been downgraded"
+msgid "Your subscription has been downgraded."
msgstr ""
-msgid "Your subscription will automatically renew in %{remaining_days}"
+msgid "Your subscription will automatically renew in %{remaining_days}."
msgstr ""
-msgid "Your subscription will expire in %{remaining_days}"
+msgid "Your subscription will expire in %{remaining_days}."
msgstr ""
msgid "Zoom meeting added"
@@ -26024,9 +26687,6 @@ msgstr ""
msgid "branch name"
msgstr ""
-msgid "build pipeline reference mismatch"
-msgstr ""
-
msgid "by"
msgstr ""
@@ -26045,6 +26705,9 @@ msgstr ""
msgid "cannot block others"
msgstr ""
+msgid "cannot contain HTML/XML tags, including any word between angle brackets (<,>)."
+msgstr ""
+
msgid "cannot include leading slash or directory traversal."
msgstr ""
@@ -26075,55 +26738,6 @@ msgstr ""
msgid "ciReport|%{remainingPackagesCount} more"
msgstr ""
-msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability"
-msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability for the source branch only"
-msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities for the source branch only"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerability"
-msgid_plural "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerabilities"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new vulnerability"
-msgid_plural "ciReport|%{reportType} %{status} detected %{newCount} new vulnerabilities"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities for the source branch only"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{fixedCount} fixed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} vulnerability for the source branch only"
-msgid_plural "ciReport|%{reportType} %{status} detected %{newCount} vulnerabilities for the source branch only"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "ciReport|%{reportType} %{status} detected no new vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected no vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected no vulnerabilities for the source branch only"
-msgstr ""
-
msgid "ciReport|%{reportType} is loading"
msgstr ""
@@ -26142,7 +26756,7 @@ msgstr ""
msgid "ciReport|All projects"
msgstr ""
-msgid "ciReport|All report types"
+msgid "ciReport|All scanner types"
msgstr ""
msgid "ciReport|All severities"
@@ -26166,6 +26780,9 @@ msgstr ""
msgid "ciReport|Container scanning detects known vulnerabilities in your docker images."
msgstr ""
+msgid "ciReport|Coverage Fuzzing"
+msgstr ""
+
msgid "ciReport|Create a merge request to implement this solution, or download and apply the patch manually."
msgstr ""
@@ -26324,6 +26941,9 @@ msgstr ""
msgid "customize"
msgstr ""
+msgid "data"
+msgstr ""
+
msgid "date must not be after 9999-12-31"
msgstr ""
@@ -26394,6 +27014,9 @@ msgstr ""
msgid "entries cannot contain HTML tags"
msgstr ""
+msgid "epic"
+msgstr ""
+
msgid "error"
msgstr ""
@@ -26415,9 +27038,6 @@ msgstr ""
msgid "expires on %{timebox_due_date}"
msgstr ""
-msgid "external_url"
-msgstr ""
-
msgid "failed"
msgstr ""
@@ -26453,9 +27073,6 @@ msgstr ""
msgid "from"
msgstr ""
-msgid "geo_node_name"
-msgstr ""
-
msgid "group"
msgstr ""
@@ -26477,9 +27094,6 @@ msgstr ""
msgid "https://your-bitbucket-server"
msgstr ""
-msgid "image"
-msgstr ""
-
msgid "image diff"
msgstr ""
@@ -26623,6 +27237,9 @@ msgstr ""
msgid "load it anyway"
msgstr ""
+msgid "loading"
+msgstr ""
+
msgid "locked by %{path_lock_user_name} %{created_at}"
msgstr ""
@@ -26751,9 +27368,6 @@ msgstr ""
msgid "mrWidget|Deployment statistics are not available currently"
msgstr ""
-msgid "mrWidget|Detect issues before deployment with a CI pipeline that continuously tests your code. We created a quick guide that will show you how to create one. Make your code more secure and more robust in just a minute."
-msgstr ""
-
msgid "mrWidget|Did not close"
msgstr ""
@@ -26916,6 +27530,9 @@ msgstr ""
msgid "mrWidget|To approve this merge request, please enter your password. This project requires all approvals to be authenticated."
msgstr ""
+msgid "mrWidget|Use %{linkStart}CI pipelines to test your code%{linkEnd}, simply add a GitLab CI configuration file to your project. It only takes a minute to make your code more secure and robust."
+msgstr ""
+
msgid "mrWidget|When this merge request is ready, remove the WIP: prefix from the title to allow it to be merged"
msgstr ""
@@ -26952,6 +27569,9 @@ msgstr ""
msgid "must be greater than start date"
msgstr ""
+msgid "must contain only valid frameworks"
+msgstr ""
+
msgid "my-awesome-group"
msgstr ""
@@ -26964,6 +27584,9 @@ msgstr ""
msgid "needs to be between 10 minutes and 1 month"
msgstr ""
+msgid "never"
+msgstr ""
+
msgid "never expires"
msgstr ""
@@ -26991,6 +27614,9 @@ msgstr ""
msgid "nounSeries|%{firstItem} and %{lastItem}"
msgstr ""
+msgid "nounSeries|%{item}"
+msgstr ""
+
msgid "nounSeries|%{item}, %{nextItem}"
msgstr ""
@@ -27127,6 +27753,9 @@ msgstr ""
msgid "revised"
msgstr ""
+msgid "satisfied"
+msgstr ""
+
msgid "score"
msgstr ""
@@ -27157,6 +27786,9 @@ msgstr ""
msgid "severity|Unknown"
msgstr ""
+msgid "should be an array of %{object_name} objects"
+msgstr ""
+
msgid "should be greater than or equal to %{access} inherited membership from group %{group_name}"
msgstr ""
@@ -27211,10 +27843,13 @@ msgstr ""
msgid "suggestPipeline|2/2: Commit your changes"
msgstr ""
-msgid "suggestPipeline|Commit the changes and your pipeline will automatically run for the first time."
+msgid "suggestPipeline|Choose %{boldStart}Code Quality%{boldEnd} to add a pipeline that tests the quality of your code."
msgstr ""
-msgid "suggestPipeline|We recommend the %{boldStart}Code Quality%{boldEnd} template, which will add a report widget to your Merge Requests. This way you’ll learn about code quality degradations much sooner. %{footerStart} Goodbye technical debt! %{footerEnd}"
+msgid "suggestPipeline|The template is ready! You can now commit it to create your first pipeline."
+msgstr ""
+
+msgid "suggestPipeline|We’re adding a GitLab CI configuration file to add a pipeline to the project. You could create it manually, but we recommend that you start with a GitLab template that works out of the box."
msgstr ""
msgid "syntax is correct"
@@ -27274,6 +27909,9 @@ msgstr ""
msgid "uses Kubernetes clusters to deploy your code!"
msgstr ""
+msgid "v%{version} published %{timeAgo}"
+msgstr ""
+
msgid "verify ownership"
msgstr ""
diff --git a/locale/pa_IN/gitlab.po b/locale/pa_IN/gitlab.po
index eaca07a86a9..0dfbcfa8291 100644
--- a/locale/pa_IN/gitlab.po
+++ b/locale/pa_IN/gitlab.po
@@ -2,7 +2,7 @@ msgid ""
msgstr ""
"Project-Id-Version: gitlab-ee\n"
"Report-Msgid-Bugs-To: \n"
-"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Last-Translator: \n"
"Language-Team: Punjabi\n"
"Language: pa_IN\n"
"MIME-Version: 1.0\n"
@@ -10,9 +10,11 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Crowdin-Project: gitlab-ee\n"
+"X-Crowdin-Project-ID: 288872\n"
"X-Crowdin-Language: pa-IN\n"
"X-Crowdin-File: /master/locale/gitlab.pot\n"
-"PO-Revision-Date: 2020-06-08 15:18\n"
+"X-Crowdin-File-ID: 6\n"
+"PO-Revision-Date: 2020-07-02 01:27\n"
msgid " %{start} to %{end}"
msgstr ""
@@ -197,6 +199,11 @@ msgid_plural "%d metrics"
msgstr[0] ""
msgstr[1] ""
+msgid "%d milestone"
+msgid_plural "%d milestones"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "%d minute"
msgid_plural "%d minutes"
msgstr[0] ""
@@ -242,11 +249,6 @@ msgid_plural "%d unresolved threads"
msgstr[0] ""
msgstr[1] ""
-msgid "%d url scanned"
-msgid_plural "%d urls scanned"
-msgstr[0] ""
-msgstr[1] ""
-
msgid "%d vulnerability"
msgid_plural "%d vulnerabilities"
msgstr[0] ""
@@ -324,9 +326,15 @@ msgstr[1] ""
msgid "%{count} related %{pluralized_subject}: %{links}"
msgstr ""
+msgid "%{dashboard_path} could not be found."
+msgstr ""
+
msgid "%{days} days until tags are automatically removed"
msgstr ""
+msgid "%{deployLinkStart}Use a template to deploy to ECS%{deployLinkEnd}, or use a docker image to %{commandsLinkStart}run AWS commands in GitLab CI/CD%{commandsLinkEnd}."
+msgstr ""
+
msgid "%{description}- Sentry event: %{errorUrl}- First seen: %{firstSeen}- Last seen: %{lastSeen} %{countLabel}: %{count}%{userCountLabel}: %{userCount}"
msgstr ""
@@ -369,12 +377,18 @@ msgstr ""
msgid "%{host} sign-in from new location"
msgstr ""
-msgid "%{icon}You are about to add %{usersTag} people to the discussion. Proceed with caution."
+msgid "%{icon}You are about to add %{usersTag} people to the discussion. They will all receive a notification."
+msgstr ""
+
+msgid "%{integrations_link_start}Integrations%{link_end} enable you to make third-party applications part of your GitLab workflow. If the available integrations don't meet your needs, consider using a %{webhooks_link_start}webhook%{link_end}."
msgstr ""
msgid "%{issuableType} will be removed! Are you sure?"
msgstr ""
+msgid "%{issuesCount} issues in this group"
+msgstr ""
+
msgid "%{issuesSize} issues"
msgstr ""
@@ -384,7 +398,7 @@ msgstr ""
msgid "%{labelStart}Class:%{labelEnd} %{class}"
msgstr ""
-msgid "%{labelStart}Confidence:%{labelEnd} %{confidence}"
+msgid "%{labelStart}Evidence:%{labelEnd} %{evidence}"
msgstr ""
msgid "%{labelStart}File:%{labelEnd} %{file}"
@@ -402,6 +416,9 @@ msgstr ""
msgid "%{labelStart}Report Type:%{labelEnd} %{reportType}"
msgstr ""
+msgid "%{labelStart}Scanner:%{labelEnd} %{scanner}"
+msgstr ""
+
msgid "%{labelStart}Severity:%{labelEnd} %{severity}"
msgstr ""
@@ -426,9 +443,6 @@ msgstr ""
msgid "%{link_start}Read more%{link_end} about role permissions"
msgstr ""
-msgid "%{link} can be used for binding events when something is happening within the project."
-msgstr ""
-
msgid "%{listToShow}, and %{awardsListLength} more."
msgstr ""
@@ -462,6 +476,9 @@ msgstr ""
msgid "%{name} found %{resultsString}"
msgstr ""
+msgid "%{name} is already being used for another emoji"
+msgstr ""
+
msgid "%{name} is scheduled for %{action}"
msgstr ""
@@ -509,6 +526,9 @@ msgstr ""
msgid "%{primary} (%{secondary})"
msgstr ""
+msgid "%{ref} cannot be added: %{error}"
+msgstr ""
+
msgid "%{releases} release"
msgid_plural "%{releases} releases"
msgstr[0] ""
@@ -517,13 +537,37 @@ msgstr[1] ""
msgid "%{remaining_approvals} left"
msgstr ""
-msgid "%{retryButtonStart}Try again%{retryButtonEnd} or %{newFileButtonStart}attach a new file%{newFileButtonEnd}"
+msgid "%{reportType} %{status} detected %{critical} critical and %{high} high severity vulnerabilities out of %{total}."
+msgstr ""
+
+msgid "%{reportType} %{status} detected %{critical} critical and %{high} high severity vulnerabilities."
+msgstr ""
+
+msgid "%{reportType} %{status} detected %{critical} critical severity vulnerabilities out of %{total}."
+msgstr ""
+
+msgid "%{reportType} %{status} detected %{critical} critical severity vulnerability."
+msgid_plural "%{reportType} %{status} detected %{critical} critical severity vulnerabilities."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{reportType} %{status} detected %{high} high severity vulnerabilities out of %{total}."
msgstr ""
-msgid "%{screenreaderOnlyStart}Keyboard shorcuts%{screenreaderOnlyEnd} Disabled"
+msgid "%{reportType} %{status} detected %{high} high severity vulnerability."
+msgid_plural "%{reportType} %{status} detected %{high} high severity vulnerabilities."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{reportType} %{status} detected %{other} vulnerability."
+msgid_plural "%{reportType} %{status} detected %{other} vulnerabilities."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{reportType} %{status} detected no new vulnerabilities."
msgstr ""
-msgid "%{screenreaderOnlyStart}Keyboard shorcuts%{screenreaderOnlyEnd} Enabled"
+msgid "%{retryButtonStart}Try again%{retryButtonEnd} or %{newFileButtonStart}attach a new file%{newFileButtonEnd}"
msgstr ""
msgid "%{service_title} %{message}."
@@ -620,6 +664,12 @@ msgstr ""
msgid "%{token}..."
msgstr ""
+msgid "%{totalCpu} (%{freeSpacePercentage}%{percentSymbol} free)"
+msgstr ""
+
+msgid "%{totalMemory} (%{freeSpacePercentage}%{percentSymbol} free)"
+msgstr ""
+
msgid "%{totalWeight} total weight"
msgstr ""
@@ -650,6 +700,12 @@ msgstr ""
msgid "%{verb} %{time_spent_value} spent time."
msgstr ""
+msgid "%{webhooks_link_start}%{webhook_type}%{link_end} enable you to send notifications to web applications in response to events in a group or project."
+msgstr ""
+
+msgid "%{webhooks_link_start}%{webhook_type}%{link_end} enable you to send notifications to web applications in response to events in a group or project. We recommend using an %{integrations_link_start}integration%{link_end} in preference to a webhook."
+msgstr ""
+
msgid "'%{level}' is not a valid visibility level"
msgstr ""
@@ -676,9 +732,6 @@ msgstr ""
msgid "(No changes)"
msgstr ""
-msgid "(Show all)"
-msgstr ""
-
msgid "(check progress)"
msgstr ""
@@ -745,6 +798,9 @@ msgstr ""
msgid "0 for unlimited"
msgstr ""
+msgid "0 for unlimited, only effective with remote storage enabled."
+msgstr ""
+
msgid "1 %{type} addition"
msgid_plural "%{count} %{type} additions"
msgstr[0] ""
@@ -892,6 +948,12 @@ msgstr ""
msgid "<code>\"johnsmith@example.com\": \"johnsmith@example.com\"</code> will add \"By <a href=\"#\">johnsmith@example.com</a>\" to all issues and comments originally created by johnsmith@example.com. By default, the email address or username is masked to ensure the user's privacy. Use this option if you want to show the full email address."
msgstr ""
+msgid "<code>Masked</code> to prevent the values from being displayed in job logs (must match certain regexp requirements)."
+msgstr ""
+
+msgid "<code>Protected</code> to expose them to protected branches or tags only."
+msgstr ""
+
msgid "<namespace / project>"
msgstr ""
@@ -919,9 +981,6 @@ msgstr ""
msgid "A .NET Core console application template, customizable for any .NET Core project"
msgstr ""
-msgid "A DAG must have two dependent jobs to be visualized on this tab."
-msgstr ""
-
msgid "A GitBook site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
msgstr ""
@@ -952,6 +1011,12 @@ msgstr ""
msgid "A deleted user"
msgstr ""
+msgid "A file has been changed."
+msgstr ""
+
+msgid "A file was not found."
+msgstr ""
+
msgid "A file with '%{file_name}' already exists in %{branch} branch"
msgstr ""
@@ -1018,7 +1083,7 @@ msgstr ""
msgid "A subscription will trigger a new pipeline on the default branch of this project when a pipeline successfully completes for a new tag on the %{default_branch_docs} of the subscribed project."
msgstr ""
-msgid "A terraform report was generated in your pipelines."
+msgid "A suggestion is not applicable."
msgstr ""
msgid "A user with write access to the source branch selected this option"
@@ -1189,6 +1254,9 @@ msgstr ""
msgid "Action to take when receiving an alert."
msgstr ""
+msgid "Actions"
+msgstr ""
+
msgid "Activate"
msgstr ""
@@ -1245,6 +1313,9 @@ msgstr ""
msgid "Add LICENSE"
msgstr ""
+msgid "Add New Node"
+msgstr ""
+
msgid "Add README"
msgstr ""
@@ -1320,6 +1391,9 @@ msgstr ""
msgid "Add approval rule"
msgstr ""
+msgid "Add approvers"
+msgstr ""
+
msgid "Add bold text"
msgstr ""
@@ -1377,6 +1451,9 @@ msgstr ""
msgid "Add strikethrough text"
msgstr ""
+msgid "Add suggestion to batch"
+msgstr ""
+
msgid "Add system hook"
msgstr ""
@@ -1476,6 +1553,9 @@ msgstr ""
msgid "Admin Note"
msgstr ""
+msgid "Admin Notifications"
+msgstr ""
+
msgid "Admin Overview"
msgstr ""
@@ -1587,6 +1667,9 @@ msgstr ""
msgid "AdminSettings|Integrations configured here will automatically apply to all projects on this instance."
msgstr ""
+msgid "AdminSettings|Moved to integrations"
+msgstr ""
+
msgid "AdminSettings|No required pipeline"
msgstr ""
@@ -1817,12 +1900,18 @@ msgid_plural "Alerts"
msgstr[0] ""
msgstr[1] ""
+msgid "AlertManagement| assign yourself"
+msgstr ""
+
msgid "AlertManagement|Acknowledged"
msgstr ""
msgid "AlertManagement|Alert"
msgstr ""
+msgid "AlertManagement|Alert assignee(s): %{assignees}"
+msgstr ""
+
msgid "AlertManagement|Alert detail"
msgstr ""
@@ -1838,9 +1927,15 @@ msgstr ""
msgid "AlertManagement|All alerts"
msgstr ""
+msgid "AlertManagement|Assign To"
+msgstr ""
+
msgid "AlertManagement|Assign status"
msgstr ""
+msgid "AlertManagement|Assignee"
+msgstr ""
+
msgid "AlertManagement|Assignees"
msgstr ""
@@ -1859,9 +1954,6 @@ msgstr ""
msgid "AlertManagement|Edit"
msgstr ""
-msgid "AlertManagement|End time"
-msgstr ""
-
msgid "AlertManagement|Events"
msgstr ""
@@ -1880,10 +1972,13 @@ msgstr ""
msgid "AlertManagement|More information"
msgstr ""
+msgid "AlertManagement|No Matching Results"
+msgstr ""
+
msgid "AlertManagement|No alert data to display."
msgstr ""
-msgid "AlertManagement|No alerts available to display. If you think you're seeing this message in error, refresh the page."
+msgid "AlertManagement|No alerts available to display. See %{linkStart}enabling alert management%{linkEnd} for more information on adding alerts to the list."
msgstr ""
msgid "AlertManagement|No alerts to display."
@@ -1892,6 +1987,9 @@ msgstr ""
msgid "AlertManagement|None"
msgstr ""
+msgid "AlertManagement|None -"
+msgstr ""
+
msgid "AlertManagement|Open"
msgstr ""
@@ -1928,9 +2026,18 @@ msgstr ""
msgid "AlertManagement|There was an error displaying the alerts. Confirm your endpoint's configuration details to ensure alerts appear."
msgstr ""
+msgid "AlertManagement|There was an error while updating the assignee(s) list. Please try again."
+msgstr ""
+
+msgid "AlertManagement|There was an error while updating the assignee(s) of the alert. Please try again."
+msgstr ""
+
msgid "AlertManagement|There was an error while updating the status of the alert. Please try again."
msgstr ""
+msgid "AlertManagement|This assignee cannot be assigned to this alert."
+msgstr ""
+
msgid "AlertManagement|Tool"
msgstr ""
@@ -2078,6 +2185,9 @@ msgstr ""
msgid "Allow users to request access (if visibility is public or internal)"
msgstr ""
+msgid "Allowed Geo IP"
+msgstr ""
+
msgid "Allowed email domain restriction only permitted for top-level groups"
msgstr ""
@@ -2111,6 +2221,9 @@ msgstr ""
msgid "Amazon Web Services"
msgstr ""
+msgid "Amazon Web Services Logo"
+msgstr ""
+
msgid "Amazon authentication is not %{link_start}correctly configured%{link_end}. Ask your GitLab administrator if you want to use this service."
msgstr ""
@@ -2168,10 +2281,16 @@ msgstr ""
msgid "An error occurred when updating the issue weight"
msgstr ""
+msgid "An error occurred while acknowledging the notification. Refresh the page and try again."
+msgstr ""
+
+msgid "An error occurred while adding approvers"
+msgstr ""
+
msgid "An error occurred while adding formatted title for epic"
msgstr ""
-msgid "An error occurred while checking group path"
+msgid "An error occurred while checking group path. Please refresh and try again."
msgstr ""
msgid "An error occurred while committing your changes."
@@ -2315,7 +2434,7 @@ msgstr ""
msgid "An error occurred while loading milestones"
msgstr ""
-msgid "An error occurred while loading terraform report"
+msgid "An error occurred while loading project creation UI"
msgstr ""
msgid "An error occurred while loading the data. Please try again."
@@ -2369,6 +2488,9 @@ msgstr ""
msgid "An error occurred while rendering preview broadcast message"
msgstr ""
+msgid "An error occurred while rendering the editor"
+msgstr ""
+
msgid "An error occurred while reordering issues."
msgstr ""
@@ -2465,7 +2587,7 @@ msgstr ""
msgid "Analyze your dependencies for known vulnerabilities."
msgstr ""
-msgid "Analyze your source code and git history for secrets"
+msgid "Analyze your source code and git history for secrets."
msgstr ""
msgid "Analyze your source code for known vulnerabilities."
@@ -2579,12 +2701,18 @@ msgstr ""
msgid "Apply suggestion"
msgstr ""
+msgid "Apply suggestions"
+msgstr ""
+
msgid "Apply template"
msgstr ""
msgid "Apply this approval rule to any branch or a specific protected branch."
msgstr ""
+msgid "Applying"
+msgstr ""
+
msgid "Applying a template will replace the existing issue description. Any changes you have made will be lost."
msgstr ""
@@ -2597,12 +2725,18 @@ msgstr ""
msgid "Applying multiple commands"
msgstr ""
-msgid "Applying suggestion"
+msgid "Applying suggestion..."
+msgstr ""
+
+msgid "Applying suggestions..."
msgstr ""
msgid "Approval rules"
msgstr ""
+msgid "Approval rules reset to project defaults"
+msgstr ""
+
msgid "ApprovalRuleRemove|%d member"
msgid_plural "ApprovalRuleRemove|%d members"
msgstr[0] ""
@@ -2656,6 +2790,9 @@ msgstr ""
msgid "Approved"
msgstr ""
+msgid "Approved MRs"
+msgstr ""
+
msgid "Approved by: "
msgstr ""
@@ -2869,6 +3006,9 @@ msgstr ""
msgid "Assign"
msgstr ""
+msgid "Assign Iteration"
+msgstr ""
+
msgid "Assign custom color like #FF0000"
msgstr ""
@@ -2887,6 +3027,9 @@ msgstr ""
msgid "Assign to"
msgstr ""
+msgid "Assign to commenting user"
+msgstr ""
+
msgid "Assign yourself to these issues"
msgstr ""
@@ -2913,6 +3056,9 @@ msgid_plural "%d Assignees"
msgstr[0] ""
msgstr[1] ""
+msgid "Assignee has no permissions"
+msgstr ""
+
msgid "Assignee lists not available with your current license"
msgstr ""
@@ -2922,6 +3068,9 @@ msgstr ""
msgid "Assignee(s)"
msgstr ""
+msgid "Assignees"
+msgstr ""
+
msgid "Assigns %{assignee_users_sentence}."
msgstr ""
@@ -2957,6 +3106,9 @@ msgstr ""
msgid "Audit Events is a way to keep track of important events that happened in GitLab."
msgstr ""
+msgid "Audit Log"
+msgstr ""
+
msgid "AuditEvents|(removed)"
msgstr ""
@@ -2993,6 +3145,9 @@ msgstr ""
msgid "AuditLogs|IP Address"
msgstr ""
+msgid "AuditLogs|Member Events"
+msgstr ""
+
msgid "AuditLogs|No matching %{type} found."
msgstr ""
@@ -3275,9 +3430,6 @@ msgstr ""
msgid "Badges|e.g. %{exampleUrl}"
msgstr ""
-msgid "Badge|New"
-msgstr ""
-
msgid "Balsamiq file could not be loaded."
msgstr ""
@@ -3326,6 +3478,12 @@ msgstr ""
msgid "Below you will find all the groups that are public."
msgstr ""
+msgid "Beta"
+msgstr ""
+
+msgid "Bi-weekly code coverage"
+msgstr ""
+
msgid "Billing"
msgstr ""
@@ -3386,9 +3544,15 @@ msgstr ""
msgid "Bitbucket Server Import"
msgstr ""
+msgid "Bitbucket Server import"
+msgstr ""
+
msgid "Bitbucket import"
msgstr ""
+msgid "Blame"
+msgstr ""
+
msgid "Blocked"
msgstr ""
@@ -3401,9 +3565,6 @@ msgstr ""
msgid "Blog"
msgstr ""
-msgid "Blue helpers indicate an action to be taken."
-msgstr ""
-
msgid "Board name"
msgstr ""
@@ -3602,6 +3763,9 @@ msgstr ""
msgid "Broadcast Message was successfully updated."
msgstr ""
+msgid "Broadcast Messages"
+msgstr ""
+
msgid "Browse Directory"
msgstr ""
@@ -3656,6 +3820,9 @@ msgstr ""
msgid "By %{user_name}"
msgstr ""
+msgid "By URL"
+msgstr ""
+
msgid "By default GitLab sends emails in HTML and plain text formats so mail clients can choose what format to use. Disable this option if you only want to send emails in plain text format."
msgstr ""
@@ -3761,6 +3928,9 @@ msgstr ""
msgid "Can override approvers and approvals required per merge request"
msgstr ""
+msgid "Can't apply as this line has changed or the suggestion already matches its content."
+msgstr ""
+
msgid "Can't create snippet: %{err}"
msgstr ""
@@ -3947,9 +4117,6 @@ msgstr ""
msgid "Changes are still tracked. Useful for cluster/index migrations."
msgstr ""
-msgid "Changes are unknown"
-msgstr ""
-
msgid "Changes suppressed. Click to show."
msgstr ""
@@ -4256,15 +4423,9 @@ msgstr ""
msgid "Choose what content you want to see on a group’s overview page"
msgstr ""
-msgid "Choose which groups you wish to synchronize to this secondary node"
-msgstr ""
-
msgid "Choose which repositories you want to connect and run CI/CD pipelines."
msgstr ""
-msgid "Choose which shards you wish to synchronize to this secondary node"
-msgstr ""
-
msgid "Choose your framework"
msgstr ""
@@ -4562,9 +4723,15 @@ msgstr ""
msgid "Cluster does not exist"
msgstr ""
+msgid "Cluster is required for Stages::ClusterEndpointInserter"
+msgstr ""
+
msgid "Cluster level"
msgstr ""
+msgid "Cluster type must be specificed for Stages::ClusterEndpointInserter"
+msgstr ""
+
msgid "ClusterIntegration| %{custom_domain_start}More information%{custom_domain_end}."
msgstr ""
@@ -4676,7 +4843,7 @@ msgstr ""
msgid "ClusterIntegration|Cert-Manager"
msgstr ""
-msgid "ClusterIntegration|Cert-Manager is a native Kubernetes certificate management controller that helps with issuing certificates. Installing Cert-Manager on your cluster will issue a certificate by %{letsEncrypt} and ensure that certificates are valid and up-to-date."
+msgid "ClusterIntegration|Cert-Manager is a native Kubernetes certificate management controller that helps with issuing certificates. Installing Cert-Manager on your cluster will issue a certificate by %{linkStart}Let's Encrypt%{linkEnd} and ensure that certificates are valid and up-to-date."
msgstr ""
msgid "ClusterIntegration|Certificate Authority bundle (PEM format)"
@@ -4703,9 +4870,6 @@ msgstr ""
msgid "ClusterIntegration|Clear the local cache of namespace and service accounts. This is necessary if your integration has become out of sync. The cache is repopulated during the next CI job that requires namespace and service accounts."
msgstr ""
-msgid "ClusterIntegration|Cloud Run"
-msgstr ""
-
msgid "ClusterIntegration|Cluster being created"
msgstr ""
@@ -4790,7 +4954,7 @@ msgstr ""
msgid "ClusterIntegration|Crossplane"
msgstr ""
-msgid "ClusterIntegration|Crossplane enables declarative provisioning of managed services from your cloud of choice using %{codeStart}kubectl%{codeEnd} or %{gitlabIntegrationLink}. Crossplane runs inside your Kubernetes cluster and supports secure connectivity and secrets management between app containers and the cloud services they depend on."
+msgid "ClusterIntegration|Crossplane enables declarative provisioning of managed services from your cloud of choice using %{codeStart}kubectl%{codeEnd} or %{linkStart}GitLab Integration%{linkEnd}. Crossplane runs inside your Kubernetes cluster and supports secure connectivity and secrets management between app containers and the cloud services they depend on."
msgstr ""
msgid "ClusterIntegration|Deletes all GitLab resources attached to this cluster during removal"
@@ -4874,9 +5038,6 @@ msgstr ""
msgid "ClusterIntegration|GitLab-managed cluster"
msgstr ""
-msgid "ClusterIntegration|Gitlab Integration"
-msgstr ""
-
msgid "ClusterIntegration|Global default"
msgstr ""
@@ -4922,7 +5083,7 @@ msgstr ""
msgid "ClusterIntegration|Ingress gives you a way to route requests to services based on the request host or path, centralizing a number of services into a single entrypoint."
msgstr ""
-msgid "ClusterIntegration|Installing Ingress may incur additional costs. Learn more about %{pricingLink}."
+msgid "ClusterIntegration|Installing Ingress may incur additional costs. Learn more about %{linkStart}pricing%{linkEnd}."
msgstr ""
msgid "ClusterIntegration|Instance cluster"
@@ -4937,7 +5098,7 @@ msgstr ""
msgid "ClusterIntegration|Issuer Email"
msgstr ""
-msgid "ClusterIntegration|Issuers represent a certificate authority. You must provide an email address for your Issuer. "
+msgid "ClusterIntegration|Issuers represent a certificate authority. You must provide an email address for your Issuer."
msgstr ""
msgid "ClusterIntegration|Jupyter Hostname"
@@ -5009,9 +5170,6 @@ msgstr ""
msgid "ClusterIntegration|Learn more about instance Kubernetes clusters"
msgstr ""
-msgid "ClusterIntegration|Let's Encrypt"
-msgstr ""
-
msgid "ClusterIntegration|Loading IAM Roles"
msgstr ""
@@ -5123,7 +5281,7 @@ msgstr ""
msgid "ClusterIntegration|Prometheus"
msgstr ""
-msgid "ClusterIntegration|Prometheus is an open-source monitoring system with %{gitlabIntegrationLink} to monitor deployed applications."
+msgid "ClusterIntegration|Prometheus is an open-source monitoring system with %{linkStart}GitLab Integration%{linkEnd} to monitor deployed applications."
msgstr ""
msgid "ClusterIntegration|Provider details"
@@ -5279,10 +5437,10 @@ msgstr ""
msgid "ClusterIntegration|Select zone to choose machine type"
msgstr ""
-msgid "ClusterIntegration|Send Cilium Logs"
+msgid "ClusterIntegration|Send Container Network Policies Logs"
msgstr ""
-msgid "ClusterIntegration|Send ModSecurity Logs"
+msgid "ClusterIntegration|Send Web Application Firewall Logs"
msgstr ""
msgid "ClusterIntegration|Service Token"
@@ -5450,15 +5608,12 @@ msgstr ""
msgid "ClusterIntegration|documentation"
msgstr ""
-msgid "ClusterIntegration|installed via %{installed_via}"
+msgid "ClusterIntegration|installed via %{linkStart}Cloud Run%{linkEnd}"
msgstr ""
msgid "ClusterIntegration|meets the requirements"
msgstr ""
-msgid "ClusterIntegration|pricing"
-msgstr ""
-
msgid "ClusterIntegration|sign up"
msgstr ""
@@ -5495,6 +5650,15 @@ msgstr ""
msgid "Code"
msgstr ""
+msgid "Code Coverage: %{coveragePercentage}%{percentSymbol}"
+msgstr ""
+
+msgid "Code Coverage| Empty code coverage data"
+msgstr ""
+
+msgid "Code Coverage|Couldn't fetch the code coverage data"
+msgstr ""
+
msgid "Code Owners"
msgstr ""
@@ -5549,7 +5713,7 @@ msgstr ""
msgid "Collapse approvers"
msgstr ""
-msgid "Collapse child epics"
+msgid "Collapse milestones"
msgstr ""
msgid "Collapse replies"
@@ -5567,6 +5731,9 @@ msgstr ""
msgid "Coming soon"
msgstr ""
+msgid "Comma-separated, e.g. '1.1.1.1, 2.2.2.0/24'"
+msgstr ""
+
msgid "Command"
msgstr ""
@@ -5737,6 +5904,12 @@ msgstr ""
msgid "Compliance framework (optional)"
msgstr ""
+msgid "Compliance frameworks"
+msgstr ""
+
+msgid "ComplianceDashboard|created by:"
+msgstr ""
+
msgid "ComplianceFramework|GDPR"
msgstr ""
@@ -5875,6 +6048,9 @@ msgstr ""
msgid "Connection timed out"
msgstr ""
+msgid "Connection timeout"
+msgstr ""
+
msgid "Contact sales to upgrade"
msgstr ""
@@ -5930,9 +6106,6 @@ msgstr ""
msgid "ContainerRegistry|CLI Commands"
msgstr ""
-msgid "ContainerRegistry|Compressed Size"
-msgstr ""
-
msgid "ContainerRegistry|Container Registry"
msgstr ""
@@ -5948,6 +6121,9 @@ msgstr ""
msgid "ContainerRegistry|Copy push command"
msgstr ""
+msgid "ContainerRegistry|Delete selected"
+msgstr ""
+
msgid "ContainerRegistry|Docker connection error"
msgstr ""
@@ -5981,16 +6157,16 @@ msgstr ""
msgid "ContainerRegistry|If you are not already logged in, you need to authenticate to the Container Registry by using your GitLab username and password. If you have %{twofaDocLinkStart}Two-Factor Authentication%{twofaDocLinkEnd} enabled, use a %{personalAccessTokensDocLinkStart}Personal Access Token%{personalAccessTokensDocLinkEnd} instead of a password."
msgstr ""
-msgid "ContainerRegistry|Image ID"
+msgid "ContainerRegistry|Image ID: %{imageId}"
msgstr ""
msgid "ContainerRegistry|Image Repositories"
msgstr ""
-msgid "ContainerRegistry|Keep and protect the images that matter most."
+msgid "ContainerRegistry|Image tags"
msgstr ""
-msgid "ContainerRegistry|Last Updated"
+msgid "ContainerRegistry|Keep and protect the images that matter most."
msgstr ""
msgid "ContainerRegistry|Login"
@@ -6005,6 +6181,9 @@ msgstr ""
msgid "ContainerRegistry|Please contact your administrator."
msgstr ""
+msgid "ContainerRegistry|Published %{timeInfo}"
+msgstr ""
+
msgid "ContainerRegistry|Push an image"
msgstr ""
@@ -6020,9 +6199,6 @@ msgstr ""
msgid "ContainerRegistry|Remove repository"
msgstr ""
-msgid "ContainerRegistry|Remove selected tags"
-msgstr ""
-
msgid "ContainerRegistry|Remove tag"
msgid_plural "ContainerRegistry|Remove tags"
msgstr[0] ""
@@ -6052,9 +6228,6 @@ msgstr ""
msgid "ContainerRegistry|Sorry, your filter produced no results."
msgstr ""
-msgid "ContainerRegistry|Tag"
-msgstr ""
-
msgid "ContainerRegistry|Tag expiration policy"
msgstr ""
@@ -6199,21 +6372,6 @@ msgstr ""
msgid "Control the display of third party offers."
msgstr ""
-msgid "Control the maximum concurrency of LFS/attachment backfill for this secondary node"
-msgstr ""
-
-msgid "Control the maximum concurrency of container repository operations for this Geo node"
-msgstr ""
-
-msgid "Control the maximum concurrency of repository backfill for this secondary node"
-msgstr ""
-
-msgid "Control the maximum concurrency of verification operations for this Geo node"
-msgstr ""
-
-msgid "Control the minimum interval in days that a repository should be reverified for this primary node"
-msgstr ""
-
msgid "Cookie domain"
msgstr ""
@@ -6355,6 +6513,9 @@ msgstr ""
msgid "Could not find design."
msgstr ""
+msgid "Could not find iteration"
+msgstr ""
+
msgid "Could not remove the trigger."
msgstr ""
@@ -6391,6 +6552,12 @@ msgstr ""
msgid "Coverage"
msgstr ""
+msgid "Coverage Fuzzing"
+msgstr ""
+
+msgid "Crash State"
+msgstr ""
+
msgid "Create"
msgstr ""
@@ -6439,6 +6606,9 @@ msgstr ""
msgid "Create a personal access token on your account to pull or push via %{protocol}."
msgstr ""
+msgid "Create a project pre-populated with the necessary files to get you started quickly."
+msgstr ""
+
msgid "Create an account using:"
msgstr ""
@@ -6607,6 +6777,9 @@ msgstr ""
msgid "Creates branch '%{branch_name}' and a merge request to resolve this issue."
msgstr ""
+msgid "Creating"
+msgstr ""
+
msgid "Creating epic"
msgstr ""
@@ -6658,7 +6831,10 @@ msgstr ""
msgid "Current vulnerabilities count"
msgstr ""
-msgid "CurrentUser|Buy CI minutes"
+msgid "CurrentUser|Buy Pipeline minutes"
+msgstr ""
+
+msgid "CurrentUser|One of your groups is running out"
msgstr ""
msgid "CurrentUser|Profile"
@@ -6673,6 +6849,9 @@ msgstr ""
msgid "CurrentUser|Upgrade"
msgstr ""
+msgid "Custom Attributes"
+msgstr ""
+
msgid "Custom CI configuration path"
msgstr ""
@@ -6936,12 +7115,18 @@ msgstr ""
msgid "DAG"
msgstr ""
+msgid "DAG visualization requires at least 3 dependent jobs."
+msgstr ""
+
msgid "DNS"
msgstr ""
msgid "Dashboard"
msgstr ""
+msgid "Dashboard uid not found"
+msgstr ""
+
msgid "DashboardProjects|All"
msgstr ""
@@ -6966,6 +7151,9 @@ msgstr ""
msgid "Data is still calculating..."
msgstr ""
+msgid "Datasource name not found"
+msgstr ""
+
msgid "Date"
msgstr ""
@@ -7146,6 +7334,9 @@ msgstr ""
msgid "Delete this attachment"
msgstr ""
+msgid "Delete user list"
+msgstr ""
+
msgid "Delete variable"
msgstr ""
@@ -7462,6 +7653,9 @@ msgstr ""
msgid "Deploying to"
msgstr ""
+msgid "Deploying to AWS is easy with GitLab"
+msgstr ""
+
msgid "Deployment Frequency"
msgstr ""
@@ -7824,6 +8018,9 @@ msgstr ""
msgid "Dismissed on pipeline %{pipelineLink} at %{projectLink}"
msgstr ""
+msgid "Display alerts from all your monitoring tools directly within GitLab."
+msgstr ""
+
msgid "Display name"
msgstr ""
@@ -7881,9 +8078,6 @@ msgstr ""
msgid "Don't show again"
msgstr ""
-msgid "Don't worry, you can access this tour by clicking on the help icon in the top right corner and choose <strong>Learn GitLab</strong>."
-msgstr ""
-
msgid "Done"
msgstr ""
@@ -8010,6 +8204,9 @@ msgstr ""
msgid "Edit Release"
msgstr ""
+msgid "Edit Slack integration"
+msgstr ""
+
msgid "Edit Snippet"
msgstr ""
@@ -8049,6 +8246,9 @@ msgstr ""
msgid "Edit issues"
msgstr ""
+msgid "Edit iteration"
+msgstr ""
+
msgid "Edit public deploy key"
msgstr ""
@@ -8097,6 +8297,9 @@ msgstr ""
msgid "Email"
msgstr ""
+msgid "Email Notification"
+msgstr ""
+
msgid "Email address"
msgstr ""
@@ -8109,6 +8312,9 @@ msgstr ""
msgid "Email not verified. Please verify your email in Salesforce."
msgstr ""
+msgid "Email notification for unknown sign-ins"
+msgstr ""
+
msgid "Email patch"
msgstr ""
@@ -8340,6 +8546,9 @@ msgstr ""
msgid "Enforce DNS rebinding attack protection"
msgstr ""
+msgid "Enforce personal access token expiration"
+msgstr ""
+
msgid "Ensure connectivity is available from the GitLab server to the Prometheus server"
msgstr ""
@@ -8409,6 +8618,9 @@ msgstr ""
msgid "Enter the name of your application, and we'll return a unique %{type}."
msgstr ""
+msgid "Enter weights for storages for new repositories."
+msgstr ""
+
msgid "Enter your password to approve"
msgstr ""
@@ -8418,15 +8630,27 @@ msgstr ""
msgid "Environment does not have deployments"
msgstr ""
+msgid "Environment is required for Stages::MetricEndpointInserter"
+msgstr ""
+
+msgid "Environment is required for Stages::VariableEndpointInserter"
+msgstr ""
+
msgid "Environment scope"
msgstr ""
+msgid "Environment variables are applied to all project environments in this instance via the Runner. You can use environment variables for passwords, secret keys, etc. Make variables available to the running application by prepending the variable key with <code>K8S_SECRET_</code>. You can set variables to be:"
+msgstr ""
+
msgid "Environment variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. Additionally, they can be masked so they are hidden in job logs, though they must match certain regexp requirements to do so. You can use environment variables for passwords, secret keys, or whatever you want."
msgstr ""
msgid "Environment variables are configured by your administrator to be %{link_start}protected%{link_end} by default"
msgstr ""
+msgid "Environment variables on this GitLab instance are configured to be %{link_start}protected%{link_end} by default"
+msgstr ""
+
msgid "Environment:"
msgstr ""
@@ -8958,6 +9182,9 @@ msgstr ""
msgid "EventFilterBy|Filter by comments"
msgstr ""
+msgid "EventFilterBy|Filter by designs"
+msgstr ""
+
msgid "EventFilterBy|Filter by epic events"
msgstr ""
@@ -9090,15 +9317,15 @@ msgstr ""
msgid "Expand approvers"
msgstr ""
-msgid "Expand child epics"
-msgstr ""
-
msgid "Expand down"
msgstr ""
msgid "Expand dropdown"
msgstr ""
+msgid "Expand milestones"
+msgstr ""
+
msgid "Expand sidebar"
msgstr ""
@@ -9114,6 +9341,9 @@ msgstr ""
msgid "Expiration date"
msgstr ""
+msgid "Expiration not enforced"
+msgstr ""
+
msgid "Expiration policy for the Container Registry is a perfect solution for keeping the Registry space down while still enjoying the full power of GitLab CI/CD."
msgstr ""
@@ -9234,6 +9464,9 @@ msgstr ""
msgid "Failed Jobs"
msgstr ""
+msgid "Failed on"
+msgstr ""
+
msgid "Failed to add a Zoom meeting"
msgstr ""
@@ -9294,13 +9527,19 @@ msgstr ""
msgid "Failed to install."
msgstr ""
+msgid "Failed to load assignees. Please try again."
+msgstr ""
+
+msgid "Failed to load authors. Please try again."
+msgstr ""
+
msgid "Failed to load emoji list."
msgstr ""
msgid "Failed to load error details from Sentry."
msgstr ""
-msgid "Failed to load errors from Sentry. Error message: %{errorMessage}"
+msgid "Failed to load errors from Sentry."
msgstr ""
msgid "Failed to load group activity metrics. Please try again."
@@ -9381,6 +9620,9 @@ msgstr ""
msgid "Failed to set due date because the date format is invalid."
msgstr ""
+msgid "Failed to set iteration on this issue. Please try again."
+msgstr ""
+
msgid "Failed to signing using smartcard authentication"
msgstr ""
@@ -9603,13 +9845,10 @@ msgstr ""
msgid "FeatureFlags|Target environments"
msgstr ""
-msgid "FeatureFlags|There are no active feature flags"
-msgstr ""
-
-msgid "FeatureFlags|There are no inactive feature flags"
+msgid "FeatureFlags|There was an error fetching the feature flags."
msgstr ""
-msgid "FeatureFlags|There was an error fetching the feature flags."
+msgid "FeatureFlags|There was an error retrieving user lists"
msgstr ""
msgid "FeatureFlags|Try again in a few moments or contact your support team."
@@ -9621,9 +9860,18 @@ msgstr ""
msgid "FeatureFlag|Delete strategy"
msgstr ""
+msgid "FeatureFlag|List"
+msgstr ""
+
msgid "FeatureFlag|Percentage"
msgstr ""
+msgid "FeatureFlag|Select a user list"
+msgstr ""
+
+msgid "FeatureFlag|There are no configured user lists"
+msgstr ""
+
msgid "FeatureFlag|Type"
msgstr ""
@@ -9780,6 +10028,12 @@ msgstr ""
msgid "Filter..."
msgstr ""
+msgid "Find File"
+msgstr ""
+
+msgid "Find bugs in your code with coverage-guided fuzzing"
+msgstr ""
+
msgid "Find by path"
msgstr ""
@@ -10044,6 +10298,9 @@ msgstr ""
msgid "Geo allows you to replicate your GitLab instance to other geographical locations."
msgstr ""
+msgid "Geo nodes are paused using a command run on the node"
+msgstr ""
+
msgid "GeoNodeStatusEvent|%{timeAgoStr} (%{pendingEvents} events)"
msgstr ""
@@ -10128,6 +10385,9 @@ msgstr ""
msgid "GeoNodes|Not checksummed"
msgstr ""
+msgid "GeoNodes|Package files"
+msgstr ""
+
msgid "GeoNodes|Pausing replication stops the sync process. Are you sure?"
msgstr ""
@@ -10146,6 +10406,9 @@ msgstr ""
msgid "GeoNodes|Replication slots"
msgstr ""
+msgid "GeoNodes|Replication status"
+msgstr ""
+
msgid "GeoNodes|Repositories"
msgstr ""
@@ -10356,6 +10619,9 @@ msgstr ""
msgid "Geo|Synchronization failed - %{error}"
msgstr ""
+msgid "Geo|Synchronization of %{itemTitle} is disabled."
+msgstr ""
+
msgid "Geo|The database is currently %{db_lag} behind the primary node."
msgstr ""
@@ -10668,6 +10934,9 @@ msgstr ""
msgid "Go to %{link_to_google_takeout}."
msgstr ""
+msgid "Go to <strong>Issues</strong> > <strong>Boards</strong> to access your personalized learning issue board."
+msgstr ""
+
msgid "Go to Pipelines"
msgstr ""
@@ -10767,9 +11036,6 @@ msgstr ""
msgid "Go to your snippets"
msgstr ""
-msgid "Golden Tanuki"
-msgstr ""
-
msgid "Google Cloud Platform"
msgstr ""
@@ -10791,6 +11057,9 @@ msgstr ""
msgid "Grafana URL"
msgstr ""
+msgid "Grafana response contains invalid json"
+msgstr ""
+
msgid "GrafanaIntegration|API Token"
msgstr ""
@@ -10839,9 +11108,6 @@ msgstr ""
msgid "Group %{group_name} was successfully created."
msgstr ""
-msgid "Group '%{group_name}' is being imported."
-msgstr ""
-
msgid "Group Audit Events"
msgstr ""
@@ -10878,9 +11144,6 @@ msgstr ""
msgid "Group by:"
msgstr ""
-msgid "Group could not be imported: %{errors}"
-msgstr ""
-
msgid "Group description"
msgstr ""
@@ -10908,9 +11171,15 @@ msgstr ""
msgid "Group has not been marked for deletion"
msgstr ""
+msgid "Group import could not be scheduled"
+msgstr ""
+
msgid "Group info:"
msgstr ""
+msgid "Group is required when cluster_type is :group"
+msgstr ""
+
msgid "Group maintainers can register group runners in the %{link}"
msgstr ""
@@ -10974,6 +11243,24 @@ msgstr ""
msgid "GroupActivyMetrics|Recent activity (last 90 days)"
msgstr ""
+msgid "GroupImport|Failed to import group."
+msgstr ""
+
+msgid "GroupImport|Group '%{group_name}' is being imported."
+msgstr ""
+
+msgid "GroupImport|Group could not be imported: %{errors}"
+msgstr ""
+
+msgid "GroupImport|Please wait while we import the group for you. Refresh at will."
+msgstr ""
+
+msgid "GroupImport|The group was successfully imported."
+msgstr ""
+
+msgid "GroupImport|Unable to process group import file"
+msgstr ""
+
msgid "GroupRoadmap|%{dateWord} – No end date"
msgstr ""
@@ -11241,6 +11528,9 @@ msgstr ""
msgid "Groups (%{groups})"
msgstr ""
+msgid "Groups and projects"
+msgstr ""
+
msgid "Groups can also be nested by creating %{subgroup_docs_link_start}subgroups%{subgroup_docs_link_end}."
msgstr ""
@@ -11286,6 +11576,33 @@ msgstr ""
msgid "GroupsEmptyState|You can manage your group member’s permissions and access to each project in the group."
msgstr ""
+msgid "GroupsNew|Contact an administrator to enable options for importing your group."
+msgstr ""
+
+msgid "GroupsNew|Create"
+msgstr ""
+
+msgid "GroupsNew|Create group"
+msgstr ""
+
+msgid "GroupsNew|GitLab group export"
+msgstr ""
+
+msgid "GroupsNew|Import"
+msgstr ""
+
+msgid "GroupsNew|Import group"
+msgstr ""
+
+msgid "GroupsNew|My Awesome Group"
+msgstr ""
+
+msgid "GroupsNew|No import options available"
+msgstr ""
+
+msgid "GroupsNew|To copy a GitLab group between installations, navigate to the group settings page for the original installation, generate an export file, and upload it here."
+msgstr ""
+
msgid "GroupsTree|Are you sure you want to leave the \"%{fullName}\" group?"
msgstr ""
@@ -11388,6 +11705,9 @@ msgstr ""
msgid "Helps reduce request volume for protected paths"
msgstr ""
+msgid "Here are all your projects in your group, including the one you just created. To start, let’s take a look at your personalized learning project which will help you learn about GitLab at your own pace."
+msgstr ""
+
msgid "Here you will find recent merge request activity"
msgstr ""
@@ -11402,6 +11722,9 @@ msgid_plural "Hide charts"
msgstr[0] ""
msgstr[1] ""
+msgid "Hide details"
+msgstr ""
+
msgid "Hide file browser"
msgstr ""
@@ -11411,6 +11734,9 @@ msgstr ""
msgid "Hide host keys manual input"
msgstr ""
+msgid "Hide list"
+msgstr ""
+
msgid "Hide marketing-related entries from help"
msgstr ""
@@ -11431,9 +11757,6 @@ msgstr[1] ""
msgid "Hide values"
msgstr ""
-msgid "Hiding all labels"
-msgstr ""
-
msgid "High or unknown vulnerabilities present"
msgstr ""
@@ -11518,6 +11841,9 @@ msgstr ""
msgid "ID:"
msgstr ""
+msgid "IDE"
+msgstr ""
+
msgid "IDE|Allow live previews of JavaScript projects in the Web IDE using CodeSandbox Live Preview."
msgstr ""
@@ -11608,10 +11934,10 @@ msgstr ""
msgid "If enabled"
msgstr ""
-msgid "If enabled, access to projects will be validated on an external service using their classification label."
+msgid "If enabled, GitLab will handle Object Storage replication using Geo. %{linkStart}More information%{linkEnd}"
msgstr ""
-msgid "If enabled, and if object storage is enabled, GitLab will handle Object Storage replication using Geo"
+msgid "If enabled, access to projects will be validated on an external service using their classification label."
msgstr ""
msgid "If there is no previous license or if the previous license has expired, some GitLab functionality will be blocked until a new, valid license is uploaded."
@@ -11973,6 +12299,9 @@ msgid_plural "Instances"
msgstr[0] ""
msgstr[1] ""
+msgid "Instance Configuration"
+msgstr ""
+
msgid "Instance Statistics visibility"
msgstr ""
@@ -11994,9 +12323,6 @@ msgstr ""
msgid "Integrations"
msgstr ""
-msgid "Integrations allow you to integrate GitLab with other applications"
-msgstr ""
-
msgid "Integrations|All details"
msgstr ""
@@ -12108,6 +12434,9 @@ msgstr ""
msgid "Invalid query"
msgstr ""
+msgid "Invalid repository bundle for snippet with id %{snippet_id}"
+msgstr ""
+
msgid "Invalid repository path"
msgstr ""
@@ -12126,6 +12455,9 @@ msgstr ""
msgid "Invalid two-factor code."
msgstr ""
+msgid "Invalid yaml"
+msgstr ""
+
msgid "Invitation"
msgstr ""
@@ -12345,15 +12677,24 @@ msgstr ""
msgid "It seems like the Dependency Scanning job ran successfully, but no dependencies have been detected in your project."
msgstr ""
+msgid "It seems that there is currently no available data for code coverage"
+msgstr ""
+
msgid "It's you"
msgstr ""
+msgid "Iteration"
+msgstr ""
+
msgid "Iteration changed to"
msgstr ""
msgid "Iteration removed"
msgstr ""
+msgid "Iteration updated"
+msgstr ""
+
msgid "Iterations"
msgstr ""
@@ -12384,12 +12725,18 @@ msgstr ""
msgid "January"
msgstr ""
+msgid "Jira Issues"
+msgstr ""
+
msgid "Jira import is already running."
msgstr ""
msgid "Jira integration not configured."
msgstr ""
+msgid "Jira project key is not configured"
+msgstr ""
+
msgid "Jira project: %{importProject}"
msgstr ""
@@ -12570,6 +12917,9 @@ msgstr ""
msgid "Keep divergent refs"
msgstr ""
+msgid "Kerberos access denied"
+msgstr ""
+
msgid "Key"
msgstr ""
@@ -12579,10 +12929,13 @@ msgstr ""
msgid "Key: %{key}"
msgstr ""
-msgid "Keyboard Shortcuts"
+msgid "Keyboard shortcuts"
msgstr ""
-msgid "Keyboard shortcuts"
+msgid "Keys"
+msgstr ""
+
+msgid "Ki"
msgstr ""
msgid "Kubernetes"
@@ -12624,6 +12977,9 @@ msgstr ""
msgid "LDAP"
msgstr ""
+msgid "LDAP Synchronization"
+msgstr ""
+
msgid "LDAP settings"
msgstr ""
@@ -12848,12 +13204,18 @@ msgstr ""
msgid "Learn more about custom project templates"
msgstr ""
+msgid "Learn more about deploying to AWS"
+msgstr ""
+
msgid "Learn more about deploying to a cluster"
msgstr ""
msgid "Learn more about group-level project templates"
msgstr ""
+msgid "Learn more about job dependencies"
+msgstr ""
+
msgid "Learn more about signing commits"
msgstr ""
@@ -12905,9 +13267,21 @@ msgstr ""
msgid "License History"
msgstr ""
+msgid "License ID:"
+msgstr ""
+
+msgid "License URL"
+msgstr ""
+
msgid "License-Check"
msgstr ""
+msgid "LicenseCompliance|%{docLinkStart}License Approvals%{docLinkEnd} are active"
+msgstr ""
+
+msgid "LicenseCompliance|%{docLinkStart}License Approvals%{docLinkEnd} are inactive"
+msgstr ""
+
msgid "LicenseCompliance|Add a license"
msgstr ""
@@ -12929,9 +13303,15 @@ msgstr ""
msgid "LicenseCompliance|Deny"
msgstr ""
+msgid "LicenseCompliance|Learn more about %{linkStart}License Approvals%{linkEnd}"
+msgstr ""
+
msgid "LicenseCompliance|License"
msgstr ""
+msgid "LicenseCompliance|License Approvals"
+msgstr ""
+
msgid "LicenseCompliance|License Compliance detected %d license and policy violation for the source branch only; approval required"
msgid_plural "LicenseCompliance|License Compliance detected %d licenses and policy violations for the source branch only; approval required"
msgstr[0] ""
@@ -13152,6 +13532,9 @@ msgstr ""
msgid "List your Bitbucket Server repositories"
msgstr ""
+msgid "Lists"
+msgstr ""
+
msgid "Live preview"
msgstr ""
@@ -13335,6 +13718,9 @@ msgstr ""
msgid "Manage project labels"
msgstr ""
+msgid "Manage storage usage"
+msgstr ""
+
msgid "Manage two-factor authentication"
msgstr ""
@@ -13350,6 +13736,9 @@ msgstr ""
msgid "Manifest file import"
msgstr ""
+msgid "Manifest import"
+msgstr ""
+
msgid "Manual job"
msgstr ""
@@ -13497,6 +13886,12 @@ msgstr ""
msgid "Maximum field length"
msgstr ""
+msgid "Maximum file size is 2MB. Please select a smaller file."
+msgstr ""
+
+msgid "Maximum import size (MB)"
+msgstr ""
+
msgid "Maximum job timeout"
msgstr ""
@@ -13536,6 +13931,9 @@ msgstr ""
msgid "Maximum size of Elasticsearch bulk indexing requests."
msgstr ""
+msgid "Maximum size of import files."
+msgstr ""
+
msgid "Maximum size of individual attachments in comments."
msgstr ""
@@ -13587,6 +13985,9 @@ msgstr ""
msgid "Merge (when the pipeline succeeds)"
msgstr ""
+msgid "Merge Conflicts"
+msgstr ""
+
msgid "Merge Request"
msgstr ""
@@ -13713,6 +14114,9 @@ msgstr ""
msgid "MergeRequests|Squash task canceled: another squash is already in progress."
msgstr ""
+msgid "MergeRequests|This project does not allow squashing commits when merge requests are accepted."
+msgstr ""
+
msgid "MergeRequests|Thread stays resolved"
msgstr ""
@@ -13866,6 +14270,12 @@ msgstr ""
msgid "MetricsSettings|Add a button to the metrics dashboard linking directly to your existing external dashboard."
msgstr ""
+msgid "MetricsSettings|Choose whether to display dashboard metrics in UTC or the user's local timezone."
+msgstr ""
+
+msgid "MetricsSettings|Dashboard timezone"
+msgstr ""
+
msgid "MetricsSettings|External dashboard URL"
msgstr ""
@@ -13875,6 +14285,12 @@ msgstr ""
msgid "MetricsSettings|Metrics Dashboard"
msgstr ""
+msgid "MetricsSettings|UTC (Coordinated Universal Time)"
+msgstr ""
+
+msgid "MetricsSettings|User's local timezone"
+msgstr ""
+
msgid "Metrics|Add metric"
msgstr ""
@@ -13890,6 +14306,9 @@ msgstr ""
msgid "Metrics|Create metric"
msgstr ""
+msgid "Metrics|Current"
+msgstr ""
+
msgid "Metrics|Delete metric"
msgstr ""
@@ -13940,6 +14359,9 @@ msgstr ""
msgid "Metrics|Max"
msgstr ""
+msgid "Metrics|Min"
+msgstr ""
+
msgid "Metrics|Must be a valid PromQL query."
msgstr ""
@@ -13955,6 +14377,9 @@ msgstr ""
msgid "Metrics|Refresh dashboard"
msgstr ""
+msgid "Metrics|Select a value"
+msgstr ""
+
msgid "Metrics|Star dashboard"
msgstr ""
@@ -13973,12 +14398,18 @@ msgstr ""
msgid "Metrics|There was an error getting annotations information."
msgstr ""
+msgid "Metrics|There was an error getting dashboard validation warnings information."
+msgstr ""
+
msgid "Metrics|There was an error getting deployment information."
msgstr ""
msgid "Metrics|There was an error getting environments information."
msgstr ""
+msgid "Metrics|There was an error getting options for variable \"%{name}\"."
+msgstr ""
+
msgid "Metrics|There was an error trying to validate your query"
msgstr ""
@@ -14021,6 +14452,9 @@ msgstr ""
msgid "Metrics|You're about to permanently delete this metric. This cannot be undone."
msgstr ""
+msgid "Metrics|Your dashboard schema is invalid. Edit the dashboard to correct the YAML schema."
+msgstr ""
+
msgid "Metrics|e.g. HTTP requests"
msgstr ""
@@ -14036,12 +14470,18 @@ msgstr ""
msgid "Metrics|e.g. req/sec"
msgstr ""
+msgid "Mi"
+msgstr ""
+
msgid "Microsoft Azure"
msgstr ""
msgid "Middleman project with Static Site Editor support"
msgstr ""
+msgid "Migrate your data from an external source like GitHub, Bitbucket, or another instance of GitLab."
+msgstr ""
+
msgid "Migrated %{success_count}/%{total_count} files."
msgstr ""
@@ -14176,6 +14616,9 @@ msgstr ""
msgid "Minimum capacity to be available before we schedule more mirrors preemptively."
msgstr ""
+msgid "Minimum interval in days"
+msgstr ""
+
msgid "Minimum length is %{minimum_password_length} characters"
msgstr ""
@@ -14362,6 +14805,12 @@ msgstr ""
msgid "Multiple uploaders found: %{uploader_types}"
msgstr ""
+msgid "Must match with the %{codeStart}external_url%{codeEnd} in %{codeStart}/etc/gitlab/gitlab.rb%{codeEnd}."
+msgstr ""
+
+msgid "Must match with the %{codeStart}geo_node_name%{codeEnd} in %{codeStart}/etc/gitlab/gitlab.rb%{codeEnd}. %{linkStart}More information%{linkEnd}"
+msgstr ""
+
msgid "My Awesome Group"
msgstr ""
@@ -14476,10 +14925,16 @@ msgstr ""
msgid "New Application"
msgstr ""
+msgid "New Branch"
+msgstr ""
+
+msgid "New Deploy Key"
+msgstr ""
+
msgid "New Environment"
msgstr ""
-msgid "New Geo Node"
+msgid "New File"
msgstr ""
msgid "New Group"
@@ -14496,15 +14951,15 @@ msgid_plural "New Issues"
msgstr[0] ""
msgstr[1] ""
-msgid "New Iteration"
-msgstr ""
-
msgid "New Jira import"
msgstr ""
msgid "New Label"
msgstr ""
+msgid "New Merge Request"
+msgstr ""
+
msgid "New Milestone"
msgstr ""
@@ -14523,6 +14978,9 @@ msgstr ""
msgid "New Snippet"
msgstr ""
+msgid "New User"
+msgstr ""
+
msgid "New branch"
msgstr ""
@@ -14565,6 +15023,9 @@ msgstr ""
msgid "New issue title"
msgstr ""
+msgid "New iteration"
+msgstr ""
+
msgid "New iteration created"
msgstr ""
@@ -14742,6 +15203,12 @@ msgstr ""
msgid "No grouping"
msgstr ""
+msgid "No iteration"
+msgstr ""
+
+msgid "No iterations to show"
+msgstr ""
+
msgid "No job log"
msgstr ""
@@ -14784,6 +15251,9 @@ msgstr ""
msgid "No other labels with such name or description"
msgstr ""
+msgid "No panels matching properties %{opts}"
+msgstr ""
+
msgid "No parent group"
msgstr ""
@@ -14817,6 +15287,9 @@ msgstr ""
msgid "No schedules"
msgstr ""
+msgid "No stack trace for this error"
+msgstr ""
+
msgid "No starrers matched your search"
msgstr ""
@@ -14829,7 +15302,7 @@ msgstr ""
msgid "No test coverage"
msgstr ""
-msgid "No thanks, don't show this again"
+msgid "No thanks"
msgstr ""
msgid "No vulnerabilities present"
@@ -14844,9 +15317,6 @@ msgstr ""
msgid "No, directly import the existing email addresses and usernames."
msgstr ""
-msgid "No, not interested right now"
-msgstr ""
-
msgid "No. of commits"
msgstr ""
@@ -14892,9 +15362,6 @@ msgstr ""
msgid "Not found."
msgstr ""
-msgid "Not helpful"
-msgstr ""
-
msgid "Not now"
msgstr ""
@@ -15051,6 +15518,9 @@ msgstr ""
msgid "Notifications on"
msgstr ""
+msgid "Notify users by email when sign-in location is not recognized"
+msgstr ""
+
msgid "Nov"
msgstr ""
@@ -15117,9 +15587,6 @@ msgstr ""
msgid "Oh no!"
msgstr ""
-msgid "Ok let's go"
-msgstr ""
-
msgid "Oldest first"
msgstr ""
@@ -15132,16 +15599,49 @@ msgstr ""
msgid "On track"
msgstr ""
+msgid "OnDemandScans|Attached branch"
+msgstr ""
+
+msgid "OnDemandScans|Attached branch is where the scan job runs."
+msgstr ""
+
+msgid "OnDemandScans|Could not run the scan. Please try again."
+msgstr ""
+
msgid "OnDemandScans|Create new DAST scan"
msgstr ""
+msgid "OnDemandScans|DAST will scan the target URL and any discovered sub URLs."
+msgstr ""
+
+msgid "OnDemandScans|New on-demand DAST scan"
+msgstr ""
+
msgid "OnDemandScans|On-demand Scans"
msgstr ""
+msgid "OnDemandScans|On-demand scans run outside the DevOps cycle and find vulnerabilities in your projects. %{learnMoreLinkStart}Learn more%{learnMoreLinkEnd}"
+msgstr ""
+
+msgid "OnDemandScans|Only a passive scan can be performed on demand."
+msgstr ""
+
+msgid "OnDemandScans|Passive DAST Scan"
+msgstr ""
+
+msgid "OnDemandScans|Please enter a valid URL format, ex: http://www.example.com/home"
+msgstr ""
+
+msgid "OnDemandScans|Run this scan"
+msgstr ""
+
+msgid "OnDemandScans|Scan mode"
+msgstr ""
+
msgid "OnDemandScans|Schedule or run scans immediately against target sites. Currently available on-demand scan type: DAST. %{helpLinkStart}More information%{helpLinkEnd}"
msgstr ""
-msgid "Onboarding"
+msgid "OnDemandScans|Target URL"
msgstr ""
msgid "Once imported, repositories can be mirrored over SSH. Read more %{link_start}here%{link_end}."
@@ -15167,6 +15667,9 @@ msgstr ""
msgid "One or more of you personal access tokens were revoked"
msgstr ""
+msgid "One or more of your %{provider} projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
+msgstr ""
+
msgid "One or more of your Bitbucket projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
msgstr ""
@@ -15668,6 +16171,9 @@ msgstr ""
msgid "Parent epic is not present."
msgstr ""
+msgid "Parsing error for param :embed_json. %{message}"
+msgstr ""
+
msgid "Part of merge request changes"
msgstr ""
@@ -15680,6 +16186,9 @@ msgstr ""
msgid "Passed"
msgstr ""
+msgid "Passed on"
+msgstr ""
+
msgid "Password"
msgstr ""
@@ -15764,6 +16273,9 @@ msgstr ""
msgid "Perform common operations on GitLab project"
msgstr ""
+msgid "Performance and resource management"
+msgstr ""
+
msgid "Performance optimization"
msgstr ""
@@ -15971,7 +16483,7 @@ msgstr ""
msgid "Pipelines|Something went wrong while cleaning runners cache."
msgstr ""
-msgid "Pipelines|There are currently no %{scope} pipelines."
+msgid "Pipelines|There are currently no finished pipelines."
msgstr ""
msgid "Pipelines|There are currently no pipelines."
@@ -16082,6 +16594,9 @@ msgstr ""
msgid "Pipeline|Stop pipeline #%{pipelineId}?"
msgstr ""
+msgid "Pipeline|Tag name"
+msgstr ""
+
msgid "Pipeline|Trigger author"
msgstr ""
@@ -16163,12 +16678,18 @@ msgstr ""
msgid "Please check your email (%{email}) to verify that you own this address and unlock the power of CI/CD. Didn't receive it? %{resend_link}. Wrong email address? %{update_link}."
msgstr ""
+msgid "Please choose a file"
+msgstr ""
+
msgid "Please choose a group URL with no special characters."
msgstr ""
msgid "Please complete your profile with email address"
msgstr ""
+msgid "Please convert %{linkStart}them to Git%{linkEnd}, and go through the %{linkToImportFlow} again."
+msgstr ""
+
msgid "Please convert them to %{link_to_git}, and go through the %{link_to_import_flow} again."
msgstr ""
@@ -16226,6 +16747,9 @@ msgstr ""
msgid "Please provide a valid email address."
msgstr ""
+msgid "Please provide attributes to update"
+msgstr ""
+
msgid "Please refer to <a href=\"%{docs_url}\">%{docs_url}</a>"
msgstr ""
@@ -16961,6 +17485,9 @@ msgstr ""
msgid "Project has too many %{label_for_message} to search"
msgstr ""
+msgid "Project is required when cluster_type is :project"
+msgstr ""
+
msgid "Project members"
msgstr ""
@@ -17078,7 +17605,40 @@ msgstr ""
msgid "ProjectService|Comment"
msgstr ""
-msgid "ProjectService|Comment will be posted on each event"
+msgid "ProjectService|Event will be triggered by a push to the repository"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a commit is created/updated"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a confidential issue is created/updated/closed"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a deployment finishes"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a merge request is created/updated/merged"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a new tag is pushed to the repository"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a new, unique alert is recorded"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a pipeline status changes"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a wiki page is created/updated"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when an issue is created/updated/closed"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when someone adds a comment"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when someone adds a comment on a confidential issue"
msgstr ""
msgid "ProjectService|Perform common operations on GitLab project: %{project_name}"
@@ -17129,6 +17689,9 @@ msgstr ""
msgid "ProjectSettings|Enable 'Delete source branch' option by default"
msgstr ""
+msgid "ProjectSettings|Enable merge trains and pipelines for merged results"
+msgstr ""
+
msgid "ProjectSettings|Every merge creates a merge commit"
msgstr ""
@@ -17159,7 +17722,7 @@ msgstr ""
msgid "ProjectSettings|Forks"
msgstr ""
-msgid "ProjectSettings|Git Large File Storage"
+msgid "ProjectSettings|Git Large File Storage (LFS)"
msgstr ""
msgid "ProjectSettings|Internal"
@@ -17195,9 +17758,6 @@ msgstr ""
msgid "ProjectSettings|Merge options"
msgstr ""
-msgid "ProjectSettings|Merge pipelines will try to validate the post-merge result prior to merging"
-msgstr ""
-
msgid "ProjectSettings|Merge requests"
msgstr ""
@@ -17225,6 +17785,9 @@ msgstr ""
msgid "ProjectSettings|Pipelines"
msgstr ""
+msgid "ProjectSettings|Pipelines for merge requests must be enabled in the CI/CD configuration file, or pipelines could be unresolvable or dropped"
+msgstr ""
+
msgid "ProjectSettings|Pipelines must succeed"
msgstr ""
@@ -17252,6 +17815,9 @@ msgstr ""
msgid "ProjectSettings|Show link to create/view merge request when pushing from the command line"
msgstr ""
+msgid "ProjectSettings|Skipped pipelines are considered successful"
+msgstr ""
+
msgid "ProjectSettings|Snippets"
msgstr ""
@@ -17267,6 +17833,9 @@ msgstr ""
msgid "ProjectSettings|These checks must pass before merge requests can be merged"
msgstr ""
+msgid "ProjectSettings|This introduces the risk of merging changes that will not pass the pipeline."
+msgstr ""
+
msgid "ProjectSettings|This setting is applied on the server level and can be overridden by an admin."
msgstr ""
@@ -17444,15 +18013,27 @@ msgstr ""
msgid "ProjectsNew|Blank project"
msgstr ""
+msgid "ProjectsNew|Connect your external repository to GitLab CI/CD."
+msgstr ""
+
msgid "ProjectsNew|Contact an administrator to enable options for importing your project."
msgstr ""
msgid "ProjectsNew|Create"
msgstr ""
+msgid "ProjectsNew|Create a blank project to house your files, plan your work, and collaborate on code, among other things."
+msgstr ""
+
+msgid "ProjectsNew|Create blank project"
+msgstr ""
+
msgid "ProjectsNew|Create from template"
msgstr ""
+msgid "ProjectsNew|Create new project"
+msgstr ""
+
msgid "ProjectsNew|Creating project & repository."
msgstr ""
@@ -17477,6 +18058,9 @@ msgstr ""
msgid "ProjectsNew|Project description %{tag_start}(optional)%{tag_end}"
msgstr ""
+msgid "ProjectsNew|Run CI/CD for external repository"
+msgstr ""
+
msgid "ProjectsNew|Template"
msgstr ""
@@ -17738,6 +18322,9 @@ msgstr ""
msgid "Protected Branch"
msgstr ""
+msgid "Protected Branches"
+msgstr ""
+
msgid "Protected Environment"
msgstr ""
@@ -17750,6 +18337,9 @@ msgstr ""
msgid "Protected Tag"
msgstr ""
+msgid "Protected Tags"
+msgstr ""
+
msgid "Protected branches"
msgstr ""
@@ -17849,6 +18439,9 @@ msgstr ""
msgid "Provider"
msgstr ""
+msgid "Proxy support for this API is not available currently"
+msgstr ""
+
msgid "Pseudonymizer data collection"
msgstr ""
@@ -17873,6 +18466,9 @@ msgstr ""
msgid "Publish to status page"
msgstr ""
+msgid "Published on status page"
+msgstr ""
+
msgid "Publishes this issue to the associated status page."
msgstr ""
@@ -18047,6 +18643,9 @@ msgstr ""
msgid "Recipe"
msgstr ""
+msgid "Reconfigure"
+msgstr ""
+
msgid "Recover hidden stage"
msgstr ""
@@ -18133,6 +18732,9 @@ msgstr ""
msgid "Registration|Your profile"
msgstr ""
+msgid "Regulate approvals by authors/committers, based on compliance frameworks. Can be changed only at the instance level."
+msgstr ""
+
msgid "Rejected (closed)"
msgstr ""
@@ -18183,15 +18785,24 @@ msgstr ""
msgid "ReleaseAssetLinkType|Image"
msgstr ""
+msgid "ReleaseAssetLinkType|Images"
+msgstr ""
+
msgid "ReleaseAssetLinkType|Other"
msgstr ""
msgid "ReleaseAssetLinkType|Package"
msgstr ""
+msgid "ReleaseAssetLinkType|Packages"
+msgstr ""
+
msgid "ReleaseAssetLinkType|Runbook"
msgstr ""
+msgid "ReleaseAssetLinkType|Runbooks"
+msgstr ""
+
msgid "Releases"
msgstr ""
@@ -18204,6 +18815,9 @@ msgstr ""
msgid "Releases documentation"
msgstr ""
+msgid "Releases|New Release"
+msgstr ""
+
msgid "Release|Something went wrong while getting the release details"
msgstr ""
@@ -18276,6 +18890,9 @@ msgstr ""
msgid "Remove fork relationship"
msgstr ""
+msgid "Remove from batch"
+msgstr ""
+
msgid "Remove from board"
msgstr ""
@@ -18465,6 +19082,12 @@ msgstr ""
msgid "Replication"
msgstr ""
+msgid "Replication enabled"
+msgstr ""
+
+msgid "Replication paused"
+msgstr ""
+
msgid "Reply by email"
msgstr ""
@@ -18492,15 +19115,15 @@ msgstr ""
msgid "Reported %{timeAgo} by %{reportedBy}"
msgstr ""
-msgid "Reported Resource Changes: %{addNum} to add, %{changeNum} to change, %{deleteNum} to delete"
-msgstr ""
-
msgid "Reporter"
msgstr ""
msgid "Reporting"
msgstr ""
+msgid "Reports"
+msgstr ""
+
msgid "Reports|%{combinedString} and %{resolvedString}"
msgstr ""
@@ -18539,6 +19162,9 @@ msgstr ""
msgid "Reports|Failure"
msgstr ""
+msgid "Reports|Identifier"
+msgstr ""
+
msgid "Reports|Metrics reports are loading"
msgstr ""
@@ -18551,6 +19177,9 @@ msgstr ""
msgid "Reports|Metrics reports failed loading results"
msgstr ""
+msgid "Reports|Scanner"
+msgstr ""
+
msgid "Reports|Severity"
msgstr ""
@@ -18599,15 +19228,33 @@ msgstr ""
msgid "Repository cleanup has started. You will receive an email once the cleanup operation is complete."
msgstr ""
+msgid "Repository files count over the limit"
+msgstr ""
+
+msgid "Repository has an invalid default branch name."
+msgstr ""
+
+msgid "Repository has more than one branch."
+msgstr ""
+
msgid "Repository has no locks."
msgstr ""
+msgid "Repository has tags."
+msgstr ""
+
msgid "Repository maintenance"
msgstr ""
msgid "Repository mirroring"
msgstr ""
+msgid "Repository must contain at least 1 file."
+msgstr ""
+
+msgid "Repository size is above the limit."
+msgstr ""
+
msgid "Repository static objects"
msgstr ""
@@ -18617,7 +19264,7 @@ msgstr ""
msgid "Repository sync capacity"
msgstr ""
-msgid "Repository: %{counter_repositories} / Wikis: %{counter_wikis} / Build Artifacts: %{counter_build_artifacts} / LFS: %{counter_lfs_objects}"
+msgid "Repository: %{counter_repositories} / Wikis: %{counter_wikis} / Build Artifacts: %{counter_build_artifacts} / LFS: %{counter_lfs_objects} / Snippets: %{counter_snippets}"
msgstr ""
msgid "RepositorySettingsAccessLevel|Select"
@@ -18629,6 +19276,9 @@ msgstr ""
msgid "Request Headers"
msgstr ""
+msgid "Request details"
+msgstr ""
+
msgid "Request parameter %{param} is missing."
msgstr ""
@@ -18735,6 +19385,9 @@ msgstr ""
msgid "Reset template"
msgstr ""
+msgid "Reset to project defaults"
+msgstr ""
+
msgid "Resetting the authorization key for this project will require updating the authorization key in every alert source it is enabled in."
msgstr ""
@@ -18831,9 +19484,6 @@ msgstr ""
msgid "Resume"
msgstr ""
-msgid "Resume replication"
-msgstr ""
-
msgid "Resync"
msgstr ""
@@ -19456,6 +20106,9 @@ msgstr ""
msgid "SecurityReports|False positive"
msgstr ""
+msgid "SecurityReports|Group Security Dashboard"
+msgstr ""
+
msgid "SecurityReports|Hide dismissed"
msgstr ""
@@ -19510,10 +20163,13 @@ msgstr ""
msgid "SecurityReports|Remove project from dashboard"
msgstr ""
-msgid "SecurityReports|Report type"
+msgid "SecurityReports|Return to dashboard"
msgstr ""
-msgid "SecurityReports|Return to dashboard"
+msgid "SecurityReports|Scan details"
+msgstr ""
+
+msgid "SecurityReports|Scanner type"
msgstr ""
msgid "SecurityReports|Security Dashboard"
@@ -19570,6 +20226,9 @@ msgstr ""
msgid "SecurityReports|There was an error while generating the report."
msgstr ""
+msgid "SecurityReports|Unable to add %{invalidProjectsMessage}"
+msgstr ""
+
msgid "SecurityReports|Unable to add %{invalidProjects}"
msgstr ""
@@ -19684,6 +20343,9 @@ msgstr ""
msgid "Select due date"
msgstr ""
+msgid "Select file"
+msgstr ""
+
msgid "Select group or project"
msgstr ""
@@ -19747,9 +20409,6 @@ msgstr ""
msgid "Select the branch you want to set as the default for this project. All merge requests and commits will automatically be made against this branch unless you specify a different one."
msgstr ""
-msgid "Select the configured storage available for new repositories to be placed on."
-msgstr ""
-
msgid "Select the custom project template source group."
msgstr ""
@@ -19942,12 +20601,6 @@ msgstr ""
msgid "Service Desk is enabled but not yet active"
msgstr ""
-msgid "Service Desk is off"
-msgstr ""
-
-msgid "Service Desk is on"
-msgstr ""
-
msgid "Service Templates"
msgstr ""
@@ -20035,6 +20688,15 @@ msgstr ""
msgid "Set the milestone to %{milestone_reference}."
msgstr ""
+msgid "Set the number of concurrent requests this secondary node will make to the primary node while backfilling."
+msgstr ""
+
+msgid "Set the synchronization and verification capacity for the secondary node."
+msgstr ""
+
+msgid "Set the timeout in seconds to send a secondary node status to the primary and IPs allowed for the secondary nodes."
+msgstr ""
+
msgid "Set time estimate"
msgstr ""
@@ -20074,6 +20736,9 @@ msgstr ""
msgid "Set weight to %{weight}."
msgstr ""
+msgid "Set what should be replicated by choosing specific projects or groups by the secondary node."
+msgstr ""
+
msgid "SetPasswordToCloneLink|set a password"
msgstr ""
@@ -20200,6 +20865,9 @@ msgstr ""
msgid "Show complete raw log"
msgstr ""
+msgid "Show details"
+msgstr ""
+
msgid "Show file browser"
msgstr ""
@@ -20209,10 +20877,13 @@ msgstr ""
msgid "Show latest version"
msgstr ""
+msgid "Show list"
+msgstr ""
+
msgid "Show me everything"
msgstr ""
-msgid "Show me how"
+msgid "Show me how to add a pipeline"
msgstr ""
msgid "Show me more advanced stuff"
@@ -20261,9 +20932,6 @@ msgstr ""
msgid "Showing all issues"
msgstr ""
-msgid "Showing all labels"
-msgstr ""
-
msgid "Showing last %{size} of log -"
msgstr ""
@@ -20375,9 +21043,6 @@ msgstr ""
msgid "Skip outdated deployment jobs"
msgstr ""
-msgid "Skip this for now"
-msgstr ""
-
msgid "Skipped"
msgstr ""
@@ -20522,6 +21187,9 @@ msgstr ""
msgid "Something went wrong while adding your award. Please try again."
msgstr ""
+msgid "Something went wrong while applying the batch of suggestions. Please try again."
+msgstr ""
+
msgid "Something went wrong while applying the suggestion. Please try again."
msgstr ""
@@ -20624,10 +21292,13 @@ msgstr ""
msgid "Something went wrong while updating a requirement."
msgstr ""
+msgid "Something went wrong while updating assignees"
+msgstr ""
+
msgid "Something went wrong while updating your list settings"
msgstr ""
-msgid "Something went wrong with your automatic subscription renewal"
+msgid "Something went wrong with your automatic subscription renewal."
msgstr ""
msgid "Something went wrong, unable to add %{project} to dashboard"
@@ -20645,6 +21316,9 @@ msgstr ""
msgid "Something went wrong, unable to search projects"
msgstr ""
+msgid "Something went wrong."
+msgstr ""
+
msgid "Something went wrong. Please try again."
msgstr ""
@@ -20828,6 +21502,9 @@ msgstr ""
msgid "Source code"
msgstr ""
+msgid "Source code (%{fileExtension})"
+msgstr ""
+
msgid "Source is not available"
msgstr ""
@@ -20912,6 +21589,9 @@ msgstr ""
msgid "Stack trace"
msgstr ""
+msgid "Stacktrace snippet"
+msgstr ""
+
msgid "Stage"
msgstr ""
@@ -21017,6 +21697,9 @@ msgstr ""
msgid "Start thread & reopen %{noteable_name}"
msgstr ""
+msgid "Start using Directed Acyclic Graphs (DAG)"
+msgstr ""
+
msgid "Start your Free Gold Trial"
msgstr ""
@@ -21479,6 +22162,21 @@ msgstr ""
msgid "SuggestedColors|Very pale orange"
msgstr ""
+msgid "Suggestion is not applicable as the suggestion was not found."
+msgstr ""
+
+msgid "Suggestions are not applicable as one or more suggestions were not found."
+msgstr ""
+
+msgid "Suggestions are not applicable as their lines cannot overlap."
+msgstr ""
+
+msgid "Suggestions must all be on the same branch."
+msgstr ""
+
+msgid "Suggestions that change line count can't be added to batches, yet."
+msgstr ""
+
msgid "Suggestions:"
msgstr ""
@@ -21521,9 +22219,6 @@ msgstr ""
msgid "Synchronization disabled"
msgstr ""
-msgid "Synchronization of container repositories is disabled."
-msgstr ""
-
msgid "System"
msgstr ""
@@ -21704,6 +22399,34 @@ msgstr ""
msgid "Terms of Service and Privacy Policy"
msgstr ""
+msgid "Terraform|%{number} Terraform report failed to generate"
+msgid_plural "Terraform|%{number} Terraform reports failed to generate"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Terraform|%{number} Terraform report was generated in your pipelines"
+msgid_plural "Terraform|%{number} Terraform reports were generated in your pipelines"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Terraform|A Terraform report failed to generate."
+msgstr ""
+
+msgid "Terraform|A Terraform report was generated in your pipelines."
+msgstr ""
+
+msgid "Terraform|Generating the report caused an error."
+msgstr ""
+
+msgid "Terraform|Reported Resource Changes: %{addNum} to add, %{changeNum} to change, %{deleteNum} to delete"
+msgstr ""
+
+msgid "Terraform|The Terraform report %{name} failed to generate."
+msgstr ""
+
+msgid "Terraform|The Terraform report %{name} was generated in your pipelines."
+msgstr ""
+
msgid "Test"
msgstr ""
@@ -21730,7 +22453,7 @@ msgstr ""
msgid "TestHooks|Ensure the project has CI pipelines."
msgstr ""
-msgid "TestHooks|Ensure the project has at least one commit."
+msgid "TestHooks|Ensure the project has deployments."
msgstr ""
msgid "TestHooks|Ensure the project has issues."
@@ -21831,7 +22554,7 @@ msgstr ""
msgid "The Prometheus server responded with \"bad request\". Please check your queries are correct and are supported in your Prometheus version. %{documentationLink}"
msgstr ""
-msgid "The URL defined on the primary node that secondary nodes should use to contact it. Defaults to URL"
+msgid "The URL defined on the primary node that secondary nodes should use to contact it."
msgstr ""
msgid "The URL to use for connecting to Elasticsearch. Use a comma-separated list to support clustering (e.g., \"http://localhost:9200, http://localhost:9201\")."
@@ -21840,6 +22563,9 @@ msgstr ""
msgid "The X509 Certificate to use when mutual TLS is required to communicate with the external authorization service. If left blank, the server certificate is still validated when accessing over HTTPS."
msgstr ""
+msgid "The above settings apply to all projects with the selected compliance framework(s)."
+msgstr ""
+
msgid "The amount of seconds after which a request to get a secondary node status will time out."
msgstr ""
@@ -22139,9 +22865,6 @@ msgstr ""
msgid "The total stage shows the time it takes between creating an issue and deploying the code to production. The data will be automatically added once you have completed the full idea to production cycle."
msgstr ""
-msgid "The unique identifier for the Geo node. Must match %{geoNodeName} if it is set in gitlab.rb, otherwise it must match %{externalUrl} with a trailing slash"
-msgstr ""
-
msgid "The update action will time out after %{number_of_minutes} minutes. For big repositories, use a clone/push combination."
msgstr ""
@@ -22166,9 +22889,6 @@ msgstr ""
msgid "The user you are trying to deactivate has been active in the past %{minimum_inactive_days} days and cannot be deactivated"
msgstr ""
-msgid "The user-facing URL of the Geo node"
-msgstr ""
-
msgid "The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6."
msgstr ""
@@ -22259,6 +22979,9 @@ msgstr ""
msgid "There was a problem fetching project branches."
msgstr ""
+msgid "There was a problem fetching project tags."
+msgstr ""
+
msgid "There was a problem fetching project users."
msgstr ""
@@ -22310,6 +23033,9 @@ msgstr ""
msgid "There was an error fetching the %{replicableType}"
msgstr ""
+msgid "There was an error fetching the Geo Settings"
+msgstr ""
+
msgid "There was an error fetching the Node's Groups"
msgstr ""
@@ -22430,6 +23156,12 @@ msgstr ""
msgid "This %{issuable} is locked. Only <strong>project members</strong> can comment."
msgstr ""
+msgid "This %{noteableTypeText} is %{confidentialLinkStart}confidential%{linkEnd} and %{lockedLinkStart}locked%{linkEnd}."
+msgstr ""
+
+msgid "This %{noteableTypeText} is locked."
+msgstr ""
+
msgid "This %{viewer} could not be displayed because %{reason}. You can %{options} instead."
msgstr ""
@@ -22448,6 +23180,9 @@ msgstr ""
msgid "This action can lead to data loss. To prevent accidental actions we ask you to confirm your intention."
msgstr ""
+msgid "This also resolves all related threads"
+msgstr ""
+
msgid "This also resolves the discussion"
msgstr ""
@@ -22538,6 +23273,9 @@ msgstr ""
msgid "This epic does not exist or you don't have sufficient permission."
msgstr ""
+msgid "This feature is currently in beta. We invite you to %{linkStart}give feedback%{linkEnd}."
+msgstr ""
+
msgid "This feature requires local storage to be enabled"
msgstr ""
@@ -22571,7 +23309,7 @@ msgstr ""
msgid "This is a Work in Progress"
msgstr ""
-msgid "This is a confidential issue."
+msgid "This is a confidential %{noteableTypeText}."
msgstr ""
msgid "This is a delayed job to run in %{remainingTime}"
@@ -22598,9 +23336,6 @@ msgstr ""
msgid "This is your current session"
msgstr ""
-msgid "This issue is %{confidentialLinkStart}confidential%{linkEnd} and %{lockedLinkStart}locked%{linkEnd}."
-msgstr ""
-
msgid "This issue is confidential"
msgstr ""
@@ -22610,9 +23345,6 @@ msgstr ""
msgid "This issue is in a child epic of the filtered epic"
msgstr ""
-msgid "This issue is locked."
-msgstr ""
-
msgid "This job depends on other jobs with expired/erased artifacts: %{invalid_dependencies}"
msgstr ""
@@ -22712,6 +23444,9 @@ msgstr ""
msgid "This license has already expired."
msgstr ""
+msgid "This link points to external content"
+msgstr ""
+
msgid "This may expose confidential information as the selected fork is in another namespace that can have other members."
msgstr ""
@@ -22739,6 +23474,9 @@ msgstr ""
msgid "This page will be removed in a future release."
msgstr ""
+msgid "This pipeline does not use the %{codeStart}needs%{codeEnd} keyword and can't be represented as a directed acyclic graph."
+msgstr ""
+
msgid "This pipeline makes use of a predefined CI/CD configuration enabled by %{strongStart}Auto DevOps.%{strongEnd}"
msgstr ""
@@ -22967,6 +23705,9 @@ msgstr ""
msgid "Time from last commit to merge"
msgstr ""
+msgid "Time in seconds"
+msgstr ""
+
msgid "Time in seconds GitLab will wait for a response from the external service. When the service does not respond in time, access will be denied."
msgstr ""
@@ -23388,9 +24129,15 @@ msgstr ""
msgid "Total artifacts size: %{total_size}"
msgstr ""
+msgid "Total cores (CPUs)"
+msgstr ""
+
msgid "Total issues"
msgstr ""
+msgid "Total memory (GB)"
+msgstr ""
+
msgid "Total test time for all commits/merges"
msgstr ""
@@ -23592,6 +24339,9 @@ msgstr ""
msgid "U2F only works with HTTPS-enabled websites. Contact your administrator for more details."
msgstr ""
+msgid "UI Development Kit"
+msgstr ""
+
msgid "URL"
msgstr ""
@@ -23664,9 +24414,6 @@ msgstr ""
msgid "Unable to load the merge request widget. Try reloading the page."
msgstr ""
-msgid "Unable to process group import file"
-msgstr ""
-
msgid "Unable to resolve"
msgstr ""
@@ -23682,6 +24429,9 @@ msgstr ""
msgid "Unable to sign you in to the group with SAML due to \"%{reason}\""
msgstr ""
+msgid "Unable to suggest a path. Please refresh and try again."
+msgstr ""
+
msgid "Unable to update label prioritization at this time"
msgstr ""
@@ -23697,6 +24447,9 @@ msgstr ""
msgid "Unarchiving the project will restore people's ability to make changes to it. The repository can be committed to, and issues, comments, and other entities can be created. %{strong_start}Once active, this project shows up in the search and on the dashboard.%{strong_end}"
msgstr ""
+msgid "Unassign from commenting user"
+msgstr ""
+
msgid "Unblock"
msgstr ""
@@ -23772,6 +24525,9 @@ msgstr ""
msgid "Unreachable"
msgstr ""
+msgid "Unrecognized cluster type"
+msgstr ""
+
msgid "Unresolve"
msgstr ""
@@ -23832,6 +24588,9 @@ msgstr ""
msgid "Unsubscribes from this %{quick_action_target}."
msgstr ""
+msgid "Unsupported todo type passed. Supported todo types are: %{todo_types}"
+msgstr ""
+
msgid "Until"
msgstr ""
@@ -23859,6 +24618,9 @@ msgstr ""
msgid "Update approval rule"
msgstr ""
+msgid "Update approvers"
+msgstr ""
+
msgid "Update failed"
msgstr ""
@@ -23868,6 +24630,9 @@ msgstr ""
msgid "Update it"
msgstr ""
+msgid "Update iteration"
+msgstr ""
+
msgid "Update now"
msgstr ""
@@ -23913,7 +24678,7 @@ msgstr ""
msgid "Updated at"
msgstr ""
-msgid "Updated to"
+msgid "Updated to %{linkStart}chart v%{linkEnd}"
msgstr ""
msgid "Updating"
@@ -23943,6 +24708,9 @@ msgstr ""
msgid "Upload CSV file"
msgstr ""
+msgid "Upload License"
+msgstr ""
+
msgid "Upload New File"
msgstr ""
@@ -24024,16 +24792,19 @@ msgstr ""
msgid "UsageQuota|Pipelines"
msgstr ""
+msgid "UsageQuota|Purchase more storage"
+msgstr ""
+
msgid "UsageQuota|Repositories"
msgstr ""
msgid "UsageQuota|Repository"
msgstr ""
-msgid "UsageQuota|Storage"
+msgid "UsageQuota|Snippets"
msgstr ""
-msgid "UsageQuota|Storage usage:"
+msgid "UsageQuota|Storage"
msgstr ""
msgid "UsageQuota|This namespace has no projects which use shared runners"
@@ -24066,6 +24837,12 @@ msgstr ""
msgid "UsageQuota|Wikis"
msgstr ""
+msgid "UsageQuota|You used: %{usage} %{limit}"
+msgstr ""
+
+msgid "UsageQuota|out of %{formattedLimit} of your namespace storage"
+msgstr ""
+
msgid "Use %{code_start}::%{code_end} to create a %{link_start}scoped label set%{link_end} (eg. %{code_start}priority::1%{code_end})"
msgstr ""
@@ -24132,6 +24909,9 @@ msgstr ""
msgid "User IDs"
msgstr ""
+msgid "User Lists can only be created and modified with %{linkStart}the API%{linkEnd}"
+msgstr ""
+
msgid "User OAuth applications"
msgstr ""
@@ -24156,6 +24936,9 @@ msgstr ""
msgid "User key was successfully removed."
msgstr ""
+msgid "User list %{name} will be removed. Are you sure?"
+msgstr ""
+
msgid "User map"
msgstr ""
@@ -24177,148 +24960,10 @@ msgstr ""
msgid "User was successfully updated."
msgstr ""
-msgid "UserOnboardingTour|%{activeTour}/%{totalTours}"
-msgstr ""
-
-msgid "UserOnboardingTour|%{completed}/%{total} steps completed"
-msgstr ""
-
-msgid "UserOnboardingTour|%{emphasisStart}Well done!%{emphasisEnd}%{lineBreak}%{lineBreak}That's it for our guided tour, congratulations for making it all the way to the end!%{lineBreak}%{lineBreak}We hope this gave you a good overview of GitLab and how it can help you. We'll now show you how to create your own project and invite your colleagues."
-msgstr ""
-
-msgid "UserOnboardingTour|Adding other members to a project is done through Project Settings. Click on %{emphasisStart}Settings%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Alright, that's it for Commits. Let's take a look at the %{emphasisStart}Branches%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Awesome! Now click on %{emphasisStart}Members%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Click on one of the %{emphasisStart}Compare%{emphasisEnd} buttons to compare a branch to master."
-msgstr ""
-
-msgid "UserOnboardingTour|Click on one of the %{emphasisStart}pipeline IDs%{emphasisEnd} to see the details of a pipeline."
-msgstr ""
-
-msgid "UserOnboardingTour|Click to open the latest commit to see its details."
-msgstr ""
-
-msgid "UserOnboardingTour|Close 'Learn GitLab'"
-msgstr ""
-
-msgid "UserOnboardingTour|Commits are shown in chronological order and can be filtered by the commit message or by the branch."
+msgid "UserList|Delete %{name}?"
msgstr ""
-msgid "UserOnboardingTour|Create a project"
-msgstr ""
-
-msgid "UserOnboardingTour|Exit 'Learn GitLab'"
-msgstr ""
-
-msgid "UserOnboardingTour|Got it"
-msgstr ""
-
-msgid "UserOnboardingTour|Great job! %{clapHands} We hope the tour was helpful and that you learned how to use GitLab.%{lineBreak}%{lineBreak}We'd love to get your feedback on this tour.%{lineBreak}%{lineBreak}%{emphasisStart}How helpful would you say this guided tour was?%{emphasisEnd}%{lineBreak}%{lineBreak}"
-msgstr ""
-
-msgid "UserOnboardingTour|Guided GitLab Tour"
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can compare the changes of this branch to another one. Changes are divided by files so that it's easier to see what was changed where."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can create a project from scratch, start with a template or import a repository from other platforms. Whatever you choose, we'll guide you through the process.%{lineBreak}%{lineBreak}Fill in your new project information and click on %{emphasisStart}Create Project%{emphasisEnd} to progress to the next step."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can see the breakdown of the pipelines: its stages and jobs in each of the stages and their status.%{lineBreak}%{lineBreak}Our CI/CD pipelines are quite complex, most of our users have fewer and simpler pipelines."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can see the current members of the project (just you at the moment) and invite new members.%{lineBreak}%{lineBreak}You can invite multiple members at once (existing GitLab users or invite by email) and you can also set their roles and permissions.%{lineBreak}%{lineBreak}Add a few members and click on %{emphasisStart}Add to project%{emphasisEnd} to complete this step."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can see what changes were made with this commit, on what branch and if there's a related merge request. The status of the pipeline will also show up if CI/CD is set up.%{lineBreak}%{lineBreak}You can also comment on the lines of code that were changed and start a discussion with your colleagues!"
-msgstr ""
-
-msgid "UserOnboardingTour|Here's an overview of branches in the %{emphasisStart}%{projectName}%{emphasisEnd} project. They're split into Active and Stale.%{lineBreak}%{lineBreak}From here, you can create a new merge request from a branch, or compare the branch to any other branch in the project. By default, it will compare it to the master branch."
-msgstr ""
-
-msgid "UserOnboardingTour|Invite colleagues"
-msgstr ""
-
-msgid "UserOnboardingTour|Issues are great for communicating and keeping track of progress in GitLab. These are all issues that are open in the %{emphasisStart}%{projectName}%{emphasisEnd}.%{lineBreak}%{lineBreak}You can help us improve GitLab by contributing work to issues that are labeled <span class=\"badge color-label accept-mr-label\">Accepting merge requests</span>.%{lineBreak}%{lineBreak}This list can be filtered by labels, milestones, assignees, authors... We'll show you how it looks when the list is filtered by a label."
-msgstr ""
-
-msgid "UserOnboardingTour|Learn GitLab"
-msgstr ""
-
-msgid "UserOnboardingTour|Let's take a closer look at a merge request. Click on the title of one."
-msgstr ""
-
-msgid "UserOnboardingTour|Let's take a closer look at all the commits. Click on %{emphasisStart}Commits%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Let's take a closer look at the repository of this project. Click on %{emphasisStart}Repository%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|No thanks"
-msgstr ""
-
-msgid "UserOnboardingTour|Ok, let's go"
-msgstr ""
-
-msgid "UserOnboardingTour|Ok, show me"
-msgstr ""
-
-msgid "UserOnboardingTour|Open one of the issues by clicking on its title."
-msgstr ""
-
-msgid "UserOnboardingTour|Restart this step"
-msgstr ""
-
-msgid "UserOnboardingTour|Skip this step"
-msgstr ""
-
-msgid "UserOnboardingTour|Sweet! Your project was created and is ready to be used.%{lineBreak}%{lineBreak}You can start adding files to the repository or clone it. One last thing we want to show you is how to invite your colleagues to your new project."
-msgstr ""
-
-msgid "UserOnboardingTour|Take a look. Here's a nifty menu for quickly creating issues, merge requests, snippets, projects and groups. Click on it and select \"New project\" from the \"GitLab\" section to get started."
-msgstr ""
-
-msgid "UserOnboardingTour|Thanks for taking the guided tour. Remember, if you want to go through it again, you can start %{emphasisStart}Learn GitLab%{emphasisEnd} in the help menu on the top right."
-msgstr ""
-
-msgid "UserOnboardingTour|Thanks for the feedback! %{thumbsUp}"
-msgstr ""
-
-msgid "UserOnboardingTour|That's it for issues. Let'st take a look at %{emphasisStart}Merge Requests%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|That's it for merge requests. Now for the final part of this guided tour - the %{emphasisStart}CI/CD%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|That's it for the Repository. Let's take a look at the %{emphasisStart}Issues%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|The structure of this page is very similar to issues. Status, description, discussion and the sidebar are all here.%{lineBreak}%{lineBreak}But take a look below the description and you'll notice that there's more information about the merge request, the CI/CD pipeline and the options for approving it.%{lineBreak}%{lineBreak}Alongside the discussion you can also see more information about commits in this merge request, the status of pipelines and review all changes that were made."
-msgstr ""
-
-msgid "UserOnboardingTour|There's a lot of information here but don't worry, we'll go through it.%{lineBreak}%{lineBreak}On the top you can see the status of the issue and when it was opened and by whom. Directly below it is the issue description and below that are other %{emphasisStart}related issues%{emphasisEnd} and %{emphasisStart}merge requests%{emphasisEnd} (if any). Then below that is the %{emphasisStart}discussion%{emphasisEnd}, that's where most of the communication happens.%{lineBreak}%{lineBreak}On the right, there's a sidebar where you can view/change the %{emphasisStart}assignee, milestone, due date, labels, weight%{emphasisEnd}, etc."
-msgstr ""
-
-msgid "UserOnboardingTour|These are all the CI/CD pipelines we have for our %{emphasisStart}%{projectName}%{emphasisEnd} project.%{lineBreak}%{lineBreak}Here you can see the status of each pipeline, for what commit it's running for, its stages and the status for them."
-msgstr ""
-
-msgid "UserOnboardingTour|These are all the issues that are available for community contributions. Let's take a closer look at one of them."
-msgstr ""
-
-msgid "UserOnboardingTour|This is an overview of all merge requests in this project. Similarly to the issues overview it can be filtered down by things like labels, milestones, authors, assignees, etc."
-msgstr ""
-
-msgid "UserOnboardingTour|This is the repository for the %{emphasisStart}%{projectName}%{emphasisEnd} project. All our code is stored here. Feel free to explore and take a closer look at folders and files.%{lineBreak}%{lineBreak}Above the file structure you can see the latest commit, who the author is and the status of the CI/CD pipeline.%{lineBreak}%{lineBreak}If you scroll down below the file structure, you'll find the Readme of this project. This is defined in the README.md file at the root of the repository."
-msgstr ""
-
-msgid "UserOnboardingTour|Welcome to the project overview of the %{emphasisStart}%{projectName}%{emphasisEnd} project. This is the project that we use to work on GitLab. At first, a project seems like a simple repository, but at GitLab, a project is so much more.%{lineBreak}%{lineBreak}You can create projects for hosting your codebase, use it as an issue tracker, collaborate on code, and continuously build, test, and deploy your app with built-in GitLab CI/CD."
+msgid "UserList|created %{timeago}"
msgstr ""
msgid "UserProfile|Activity"
@@ -24471,6 +25116,9 @@ msgstr ""
msgid "UsersSelect|Unassigned"
msgstr ""
+msgid "Using %{codeStart}needs%{codeEnd} allows jobs to run before their stage is reached, as soon as their individual dependencies are met, which speeds up your pipelines."
+msgstr ""
+
msgid "Using %{code_start}::%{code_end} denotes a %{link_start}scoped label set%{link_end}"
msgstr ""
@@ -24555,9 +25203,6 @@ msgstr ""
msgid "Versions"
msgstr ""
-msgid "Very helpful"
-msgstr ""
-
msgid "View Documentation"
msgstr ""
@@ -24706,6 +25351,9 @@ msgstr ""
msgid "VisibilityLevel|Unknown"
msgstr ""
+msgid "Visit settings page"
+msgstr ""
+
msgid "VisualReviewApp|%{stepStart}Step 1%{stepEnd}. Copy the following script:"
msgstr ""
@@ -24799,6 +25447,9 @@ msgstr ""
msgid "VulnerabilityManagement|Something went wrong while trying to delete the comment. Please try again later."
msgstr ""
+msgid "VulnerabilityManagement|Something went wrong while trying to refresh the vulnerability. Please try again later."
+msgstr ""
+
msgid "VulnerabilityManagement|Something went wrong while trying to retrieve the vulnerability history. Please try again later."
msgstr ""
@@ -24841,6 +25492,9 @@ msgstr ""
msgid "Vulnerability|Class"
msgstr ""
+msgid "Vulnerability|Crash Address"
+msgstr ""
+
msgid "Vulnerability|Description"
msgstr ""
@@ -24916,9 +25570,6 @@ msgstr ""
msgid "We couldn't reach the Prometheus server. Either the server no longer exists or the configuration details need updating."
msgstr ""
-msgid "We created a short guided tour that will help you learn the basics of GitLab and how it will help you be better at your job. It should only take a couple of minutes. You will be guided by two types of helpers, best recognized by their color."
-msgstr ""
-
msgid "We detected potential spam in the %{humanized_resource_name}. Please solve the reCAPTCHA to proceed."
msgstr ""
@@ -24952,6 +25603,9 @@ msgstr ""
msgid "We've found no vulnerabilities"
msgstr ""
+msgid "Web Application Firewall"
+msgstr ""
+
msgid "Web IDE"
msgstr ""
@@ -25012,9 +25666,6 @@ msgstr ""
msgid "Welcome to GitLab.com<br>@%{name}!"
msgstr ""
-msgid "Welcome to the Guided GitLab Tour"
-msgstr ""
-
msgid "Welcome to the guided GitLab tour"
msgstr ""
@@ -25062,9 +25713,6 @@ msgstr ""
msgid "While it's rare to have no vulnerabilities, it can happen. In any event, we ask that you please double check your settings to make sure you've set up your dashboard correctly."
msgstr ""
-msgid "White helpers give contextual information."
-msgstr ""
-
msgid "Who can be an approver?"
msgstr ""
@@ -25110,12 +25758,18 @@ msgstr ""
msgid "WikiEmptyIssueMessage|Suggest wiki improvement"
msgstr ""
+msgid "WikiEmptyIssueMessage|You must be a group member in order to add wiki pages. If you have suggestions for how to improve the wiki for this group, consider opening an issue in the %{issues_link}."
+msgstr ""
+
msgid "WikiEmptyIssueMessage|You must be a project member in order to add wiki pages. If you have suggestions for how to improve the wiki for this project, consider opening an issue in the %{issues_link}."
msgstr ""
msgid "WikiEmptyIssueMessage|issue tracker"
msgstr ""
+msgid "WikiEmpty|A wiki is where you can store all the details about your group. This can include why you've created it, its principles, how to use it, and so on."
+msgstr ""
+
msgid "WikiEmpty|A wiki is where you can store all the details about your project. This can include why you've created it, its principles, how to use it, and so on."
msgstr ""
@@ -25125,12 +25779,21 @@ msgstr ""
msgid "WikiEmpty|Suggest wiki improvement"
msgstr ""
+msgid "WikiEmpty|The wiki lets you write documentation for your group"
+msgstr ""
+
msgid "WikiEmpty|The wiki lets you write documentation for your project"
msgstr ""
+msgid "WikiEmpty|This group has no wiki pages"
+msgstr ""
+
msgid "WikiEmpty|This project has no wiki pages"
msgstr ""
+msgid "WikiEmpty|You must be a group member in order to add wiki pages."
+msgstr ""
+
msgid "WikiEmpty|You must be a project member in order to add wiki pages."
msgstr ""
@@ -25407,13 +26070,13 @@ msgstr ""
msgid "You can now export your security dashboard to a CSV report."
msgstr ""
-msgid "You can now submit a merge request to get this change into the original branch."
+msgid "You can now manage alert endpoint configuration in the Alerts section on the Operations settings page. Fields on this page have been deprecated."
msgstr ""
-msgid "You can now submit a merge request to get this change into the original project."
+msgid "You can now submit a merge request to get this change into the original branch."
msgstr ""
-msgid "You can only add files when you are on a branch"
+msgid "You can now submit a merge request to get this change into the original project."
msgstr ""
msgid "You can only edit files when you are on a branch"
@@ -25635,7 +26298,7 @@ msgstr ""
msgid "You need to upload a Google Takeout archive."
msgstr ""
-msgid "You reached %{usage_in_percent} of %{namespace_name}'s capacity (%{used_storage} of %{storage_limit})"
+msgid "You reached %{usage_in_percent} of %{namespace_name}'s storage capacity (%{used_storage} of %{storage_limit})"
msgstr ""
msgid "You tried to fork %{link_to_the_project} but it failed for the following reason:"
@@ -25938,13 +26601,13 @@ msgstr ""
msgid "Your subscription expired!"
msgstr ""
-msgid "Your subscription has been downgraded"
+msgid "Your subscription has been downgraded."
msgstr ""
-msgid "Your subscription will automatically renew in %{remaining_days}"
+msgid "Your subscription will automatically renew in %{remaining_days}."
msgstr ""
-msgid "Your subscription will expire in %{remaining_days}"
+msgid "Your subscription will expire in %{remaining_days}."
msgstr ""
msgid "Zoom meeting added"
@@ -26024,9 +26687,6 @@ msgstr ""
msgid "branch name"
msgstr ""
-msgid "build pipeline reference mismatch"
-msgstr ""
-
msgid "by"
msgstr ""
@@ -26045,6 +26705,9 @@ msgstr ""
msgid "cannot block others"
msgstr ""
+msgid "cannot contain HTML/XML tags, including any word between angle brackets (<,>)."
+msgstr ""
+
msgid "cannot include leading slash or directory traversal."
msgstr ""
@@ -26075,55 +26738,6 @@ msgstr ""
msgid "ciReport|%{remainingPackagesCount} more"
msgstr ""
-msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability"
-msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability for the source branch only"
-msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities for the source branch only"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerability"
-msgid_plural "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerabilities"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new vulnerability"
-msgid_plural "ciReport|%{reportType} %{status} detected %{newCount} new vulnerabilities"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities for the source branch only"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{fixedCount} fixed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} vulnerability for the source branch only"
-msgid_plural "ciReport|%{reportType} %{status} detected %{newCount} vulnerabilities for the source branch only"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "ciReport|%{reportType} %{status} detected no new vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected no vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected no vulnerabilities for the source branch only"
-msgstr ""
-
msgid "ciReport|%{reportType} is loading"
msgstr ""
@@ -26142,7 +26756,7 @@ msgstr ""
msgid "ciReport|All projects"
msgstr ""
-msgid "ciReport|All report types"
+msgid "ciReport|All scanner types"
msgstr ""
msgid "ciReport|All severities"
@@ -26166,6 +26780,9 @@ msgstr ""
msgid "ciReport|Container scanning detects known vulnerabilities in your docker images."
msgstr ""
+msgid "ciReport|Coverage Fuzzing"
+msgstr ""
+
msgid "ciReport|Create a merge request to implement this solution, or download and apply the patch manually."
msgstr ""
@@ -26324,6 +26941,9 @@ msgstr ""
msgid "customize"
msgstr ""
+msgid "data"
+msgstr ""
+
msgid "date must not be after 9999-12-31"
msgstr ""
@@ -26394,6 +27014,9 @@ msgstr ""
msgid "entries cannot contain HTML tags"
msgstr ""
+msgid "epic"
+msgstr ""
+
msgid "error"
msgstr ""
@@ -26415,9 +27038,6 @@ msgstr ""
msgid "expires on %{timebox_due_date}"
msgstr ""
-msgid "external_url"
-msgstr ""
-
msgid "failed"
msgstr ""
@@ -26453,9 +27073,6 @@ msgstr ""
msgid "from"
msgstr ""
-msgid "geo_node_name"
-msgstr ""
-
msgid "group"
msgstr ""
@@ -26477,9 +27094,6 @@ msgstr ""
msgid "https://your-bitbucket-server"
msgstr ""
-msgid "image"
-msgstr ""
-
msgid "image diff"
msgstr ""
@@ -26623,6 +27237,9 @@ msgstr ""
msgid "load it anyway"
msgstr ""
+msgid "loading"
+msgstr ""
+
msgid "locked by %{path_lock_user_name} %{created_at}"
msgstr ""
@@ -26751,9 +27368,6 @@ msgstr ""
msgid "mrWidget|Deployment statistics are not available currently"
msgstr ""
-msgid "mrWidget|Detect issues before deployment with a CI pipeline that continuously tests your code. We created a quick guide that will show you how to create one. Make your code more secure and more robust in just a minute."
-msgstr ""
-
msgid "mrWidget|Did not close"
msgstr ""
@@ -26916,6 +27530,9 @@ msgstr ""
msgid "mrWidget|To approve this merge request, please enter your password. This project requires all approvals to be authenticated."
msgstr ""
+msgid "mrWidget|Use %{linkStart}CI pipelines to test your code%{linkEnd}, simply add a GitLab CI configuration file to your project. It only takes a minute to make your code more secure and robust."
+msgstr ""
+
msgid "mrWidget|When this merge request is ready, remove the WIP: prefix from the title to allow it to be merged"
msgstr ""
@@ -26952,6 +27569,9 @@ msgstr ""
msgid "must be greater than start date"
msgstr ""
+msgid "must contain only valid frameworks"
+msgstr ""
+
msgid "my-awesome-group"
msgstr ""
@@ -26964,6 +27584,9 @@ msgstr ""
msgid "needs to be between 10 minutes and 1 month"
msgstr ""
+msgid "never"
+msgstr ""
+
msgid "never expires"
msgstr ""
@@ -26991,6 +27614,9 @@ msgstr ""
msgid "nounSeries|%{firstItem} and %{lastItem}"
msgstr ""
+msgid "nounSeries|%{item}"
+msgstr ""
+
msgid "nounSeries|%{item}, %{nextItem}"
msgstr ""
@@ -27127,6 +27753,9 @@ msgstr ""
msgid "revised"
msgstr ""
+msgid "satisfied"
+msgstr ""
+
msgid "score"
msgstr ""
@@ -27157,6 +27786,9 @@ msgstr ""
msgid "severity|Unknown"
msgstr ""
+msgid "should be an array of %{object_name} objects"
+msgstr ""
+
msgid "should be greater than or equal to %{access} inherited membership from group %{group_name}"
msgstr ""
@@ -27211,10 +27843,13 @@ msgstr ""
msgid "suggestPipeline|2/2: Commit your changes"
msgstr ""
-msgid "suggestPipeline|Commit the changes and your pipeline will automatically run for the first time."
+msgid "suggestPipeline|Choose %{boldStart}Code Quality%{boldEnd} to add a pipeline that tests the quality of your code."
msgstr ""
-msgid "suggestPipeline|We recommend the %{boldStart}Code Quality%{boldEnd} template, which will add a report widget to your Merge Requests. This way you’ll learn about code quality degradations much sooner. %{footerStart} Goodbye technical debt! %{footerEnd}"
+msgid "suggestPipeline|The template is ready! You can now commit it to create your first pipeline."
+msgstr ""
+
+msgid "suggestPipeline|We’re adding a GitLab CI configuration file to add a pipeline to the project. You could create it manually, but we recommend that you start with a GitLab template that works out of the box."
msgstr ""
msgid "syntax is correct"
@@ -27274,6 +27909,9 @@ msgstr ""
msgid "uses Kubernetes clusters to deploy your code!"
msgstr ""
+msgid "v%{version} published %{timeAgo}"
+msgstr ""
+
msgid "verify ownership"
msgstr ""
diff --git a/locale/pl_PL/gitlab.po b/locale/pl_PL/gitlab.po
index 4287b3ba770..aa5846dbc04 100644
--- a/locale/pl_PL/gitlab.po
+++ b/locale/pl_PL/gitlab.po
@@ -2,7 +2,7 @@ msgid ""
msgstr ""
"Project-Id-Version: gitlab-ee\n"
"Report-Msgid-Bugs-To: \n"
-"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Last-Translator: \n"
"Language-Team: Polish\n"
"Language: pl_PL\n"
"MIME-Version: 1.0\n"
@@ -10,9 +10,11 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=4; plural=(n==1 ? 0 : (n%10>=2 && n%10<=4) && (n%100<12 || n%100>14) ? 1 : n!=1 && (n%10>=0 && n%10<=1) || (n%10>=5 && n%10<=9) || (n%100>=12 && n%100<=14) ? 2 : 3);\n"
"X-Crowdin-Project: gitlab-ee\n"
+"X-Crowdin-Project-ID: 288872\n"
"X-Crowdin-Language: pl\n"
"X-Crowdin-File: /master/locale/gitlab.pot\n"
-"PO-Revision-Date: 2020-06-08 15:19\n"
+"X-Crowdin-File-ID: 6\n"
+"PO-Revision-Date: 2020-07-02 01:27\n"
msgid " %{start} to %{end}"
msgstr ""
@@ -251,6 +253,13 @@ msgstr[1] ""
msgstr[2] ""
msgstr[3] ""
+msgid "%d milestone"
+msgid_plural "%d milestones"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
msgid "%d minute"
msgid_plural "%d minutes"
msgstr[0] ""
@@ -314,13 +323,6 @@ msgstr[1] ""
msgstr[2] ""
msgstr[3] ""
-msgid "%d url scanned"
-msgid_plural "%d urls scanned"
-msgstr[0] ""
-msgstr[1] ""
-msgstr[2] ""
-msgstr[3] ""
-
msgid "%d vulnerability"
msgid_plural "%d vulnerabilities"
msgstr[0] ""
@@ -412,9 +414,15 @@ msgstr[3] ""
msgid "%{count} related %{pluralized_subject}: %{links}"
msgstr ""
+msgid "%{dashboard_path} could not be found."
+msgstr ""
+
msgid "%{days} days until tags are automatically removed"
msgstr ""
+msgid "%{deployLinkStart}Use a template to deploy to ECS%{deployLinkEnd}, or use a docker image to %{commandsLinkStart}run AWS commands in GitLab CI/CD%{commandsLinkEnd}."
+msgstr ""
+
msgid "%{description}- Sentry event: %{errorUrl}- First seen: %{firstSeen}- Last seen: %{lastSeen} %{countLabel}: %{count}%{userCountLabel}: %{userCount}"
msgstr ""
@@ -457,12 +465,18 @@ msgstr ""
msgid "%{host} sign-in from new location"
msgstr ""
-msgid "%{icon}You are about to add %{usersTag} people to the discussion. Proceed with caution."
+msgid "%{icon}You are about to add %{usersTag} people to the discussion. They will all receive a notification."
+msgstr ""
+
+msgid "%{integrations_link_start}Integrations%{link_end} enable you to make third-party applications part of your GitLab workflow. If the available integrations don't meet your needs, consider using a %{webhooks_link_start}webhook%{link_end}."
msgstr ""
msgid "%{issuableType} will be removed! Are you sure?"
msgstr ""
+msgid "%{issuesCount} issues in this group"
+msgstr ""
+
msgid "%{issuesSize} issues"
msgstr ""
@@ -472,7 +486,7 @@ msgstr ""
msgid "%{labelStart}Class:%{labelEnd} %{class}"
msgstr ""
-msgid "%{labelStart}Confidence:%{labelEnd} %{confidence}"
+msgid "%{labelStart}Evidence:%{labelEnd} %{evidence}"
msgstr ""
msgid "%{labelStart}File:%{labelEnd} %{file}"
@@ -490,6 +504,9 @@ msgstr ""
msgid "%{labelStart}Report Type:%{labelEnd} %{reportType}"
msgstr ""
+msgid "%{labelStart}Scanner:%{labelEnd} %{scanner}"
+msgstr ""
+
msgid "%{labelStart}Severity:%{labelEnd} %{severity}"
msgstr ""
@@ -514,9 +531,6 @@ msgstr ""
msgid "%{link_start}Read more%{link_end} about role permissions"
msgstr ""
-msgid "%{link} can be used for binding events when something is happening within the project."
-msgstr ""
-
msgid "%{listToShow}, and %{awardsListLength} more."
msgstr ""
@@ -550,6 +564,9 @@ msgstr ""
msgid "%{name} found %{resultsString}"
msgstr ""
+msgid "%{name} is already being used for another emoji"
+msgstr ""
+
msgid "%{name} is scheduled for %{action}"
msgstr ""
@@ -599,6 +616,9 @@ msgstr ""
msgid "%{primary} (%{secondary})"
msgstr ""
+msgid "%{ref} cannot be added: %{error}"
+msgstr ""
+
msgid "%{releases} release"
msgid_plural "%{releases} releases"
msgstr[0] ""
@@ -609,13 +629,43 @@ msgstr[3] ""
msgid "%{remaining_approvals} left"
msgstr ""
-msgid "%{retryButtonStart}Try again%{retryButtonEnd} or %{newFileButtonStart}attach a new file%{newFileButtonEnd}"
+msgid "%{reportType} %{status} detected %{critical} critical and %{high} high severity vulnerabilities out of %{total}."
msgstr ""
-msgid "%{screenreaderOnlyStart}Keyboard shorcuts%{screenreaderOnlyEnd} Disabled"
+msgid "%{reportType} %{status} detected %{critical} critical and %{high} high severity vulnerabilities."
msgstr ""
-msgid "%{screenreaderOnlyStart}Keyboard shorcuts%{screenreaderOnlyEnd} Enabled"
+msgid "%{reportType} %{status} detected %{critical} critical severity vulnerabilities out of %{total}."
+msgstr ""
+
+msgid "%{reportType} %{status} detected %{critical} critical severity vulnerability."
+msgid_plural "%{reportType} %{status} detected %{critical} critical severity vulnerabilities."
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "%{reportType} %{status} detected %{high} high severity vulnerabilities out of %{total}."
+msgstr ""
+
+msgid "%{reportType} %{status} detected %{high} high severity vulnerability."
+msgid_plural "%{reportType} %{status} detected %{high} high severity vulnerabilities."
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "%{reportType} %{status} detected %{other} vulnerability."
+msgid_plural "%{reportType} %{status} detected %{other} vulnerabilities."
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "%{reportType} %{status} detected no new vulnerabilities."
+msgstr ""
+
+msgid "%{retryButtonStart}Try again%{retryButtonEnd} or %{newFileButtonStart}attach a new file%{newFileButtonEnd}"
msgstr ""
msgid "%{service_title} %{message}."
@@ -722,6 +772,12 @@ msgstr ""
msgid "%{token}..."
msgstr ""
+msgid "%{totalCpu} (%{freeSpacePercentage}%{percentSymbol} free)"
+msgstr ""
+
+msgid "%{totalMemory} (%{freeSpacePercentage}%{percentSymbol} free)"
+msgstr ""
+
msgid "%{totalWeight} total weight"
msgstr ""
@@ -752,6 +808,12 @@ msgstr ""
msgid "%{verb} %{time_spent_value} spent time."
msgstr ""
+msgid "%{webhooks_link_start}%{webhook_type}%{link_end} enable you to send notifications to web applications in response to events in a group or project."
+msgstr ""
+
+msgid "%{webhooks_link_start}%{webhook_type}%{link_end} enable you to send notifications to web applications in response to events in a group or project. We recommend using an %{integrations_link_start}integration%{link_end} in preference to a webhook."
+msgstr ""
+
msgid "'%{level}' is not a valid visibility level"
msgstr ""
@@ -780,9 +842,6 @@ msgstr ""
msgid "(No changes)"
msgstr ""
-msgid "(Show all)"
-msgstr ""
-
msgid "(check progress)"
msgstr ""
@@ -855,6 +914,9 @@ msgstr ""
msgid "0 for unlimited"
msgstr ""
+msgid "0 for unlimited, only effective with remote storage enabled."
+msgstr ""
+
msgid "1 %{type} addition"
msgid_plural "%{count} %{type} additions"
msgstr[0] ""
@@ -1032,6 +1094,12 @@ msgstr ""
msgid "<code>\"johnsmith@example.com\": \"johnsmith@example.com\"</code> will add \"By <a href=\"#\">johnsmith@example.com</a>\" to all issues and comments originally created by johnsmith@example.com. By default, the email address or username is masked to ensure the user's privacy. Use this option if you want to show the full email address."
msgstr ""
+msgid "<code>Masked</code> to prevent the values from being displayed in job logs (must match certain regexp requirements)."
+msgstr ""
+
+msgid "<code>Protected</code> to expose them to protected branches or tags only."
+msgstr ""
+
msgid "<namespace / project>"
msgstr ""
@@ -1059,9 +1127,6 @@ msgstr ""
msgid "A .NET Core console application template, customizable for any .NET Core project"
msgstr ""
-msgid "A DAG must have two dependent jobs to be visualized on this tab."
-msgstr ""
-
msgid "A GitBook site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
msgstr ""
@@ -1092,6 +1157,12 @@ msgstr ""
msgid "A deleted user"
msgstr ""
+msgid "A file has been changed."
+msgstr ""
+
+msgid "A file was not found."
+msgstr ""
+
msgid "A file with '%{file_name}' already exists in %{branch} branch"
msgstr ""
@@ -1158,7 +1229,7 @@ msgstr ""
msgid "A subscription will trigger a new pipeline on the default branch of this project when a pipeline successfully completes for a new tag on the %{default_branch_docs} of the subscribed project."
msgstr ""
-msgid "A terraform report was generated in your pipelines."
+msgid "A suggestion is not applicable."
msgstr ""
msgid "A user with write access to the source branch selected this option"
@@ -1329,6 +1400,9 @@ msgstr ""
msgid "Action to take when receiving an alert."
msgstr ""
+msgid "Actions"
+msgstr ""
+
msgid "Activate"
msgstr ""
@@ -1387,6 +1461,9 @@ msgstr ""
msgid "Add LICENSE"
msgstr ""
+msgid "Add New Node"
+msgstr ""
+
msgid "Add README"
msgstr ""
@@ -1462,6 +1539,9 @@ msgstr ""
msgid "Add approval rule"
msgstr ""
+msgid "Add approvers"
+msgstr ""
+
msgid "Add bold text"
msgstr ""
@@ -1519,6 +1599,9 @@ msgstr ""
msgid "Add strikethrough text"
msgstr ""
+msgid "Add suggestion to batch"
+msgstr ""
+
msgid "Add system hook"
msgstr ""
@@ -1618,6 +1701,9 @@ msgstr ""
msgid "Admin Note"
msgstr ""
+msgid "Admin Notifications"
+msgstr ""
+
msgid "Admin Overview"
msgstr ""
@@ -1729,6 +1815,9 @@ msgstr ""
msgid "AdminSettings|Integrations configured here will automatically apply to all projects on this instance."
msgstr ""
+msgid "AdminSettings|Moved to integrations"
+msgstr ""
+
msgid "AdminSettings|No required pipeline"
msgstr ""
@@ -1961,12 +2050,18 @@ msgstr[1] ""
msgstr[2] ""
msgstr[3] ""
+msgid "AlertManagement| assign yourself"
+msgstr ""
+
msgid "AlertManagement|Acknowledged"
msgstr ""
msgid "AlertManagement|Alert"
msgstr ""
+msgid "AlertManagement|Alert assignee(s): %{assignees}"
+msgstr ""
+
msgid "AlertManagement|Alert detail"
msgstr ""
@@ -1982,9 +2077,15 @@ msgstr ""
msgid "AlertManagement|All alerts"
msgstr ""
+msgid "AlertManagement|Assign To"
+msgstr ""
+
msgid "AlertManagement|Assign status"
msgstr ""
+msgid "AlertManagement|Assignee"
+msgstr ""
+
msgid "AlertManagement|Assignees"
msgstr ""
@@ -2003,9 +2104,6 @@ msgstr ""
msgid "AlertManagement|Edit"
msgstr ""
-msgid "AlertManagement|End time"
-msgstr ""
-
msgid "AlertManagement|Events"
msgstr ""
@@ -2024,10 +2122,13 @@ msgstr ""
msgid "AlertManagement|More information"
msgstr ""
+msgid "AlertManagement|No Matching Results"
+msgstr ""
+
msgid "AlertManagement|No alert data to display."
msgstr ""
-msgid "AlertManagement|No alerts available to display. If you think you're seeing this message in error, refresh the page."
+msgid "AlertManagement|No alerts available to display. See %{linkStart}enabling alert management%{linkEnd} for more information on adding alerts to the list."
msgstr ""
msgid "AlertManagement|No alerts to display."
@@ -2036,6 +2137,9 @@ msgstr ""
msgid "AlertManagement|None"
msgstr ""
+msgid "AlertManagement|None -"
+msgstr ""
+
msgid "AlertManagement|Open"
msgstr ""
@@ -2072,9 +2176,18 @@ msgstr ""
msgid "AlertManagement|There was an error displaying the alerts. Confirm your endpoint's configuration details to ensure alerts appear."
msgstr ""
+msgid "AlertManagement|There was an error while updating the assignee(s) list. Please try again."
+msgstr ""
+
+msgid "AlertManagement|There was an error while updating the assignee(s) of the alert. Please try again."
+msgstr ""
+
msgid "AlertManagement|There was an error while updating the status of the alert. Please try again."
msgstr ""
+msgid "AlertManagement|This assignee cannot be assigned to this alert."
+msgstr ""
+
msgid "AlertManagement|Tool"
msgstr ""
@@ -2222,6 +2335,9 @@ msgstr ""
msgid "Allow users to request access (if visibility is public or internal)"
msgstr ""
+msgid "Allowed Geo IP"
+msgstr ""
+
msgid "Allowed email domain restriction only permitted for top-level groups"
msgstr ""
@@ -2255,6 +2371,9 @@ msgstr ""
msgid "Amazon Web Services"
msgstr ""
+msgid "Amazon Web Services Logo"
+msgstr ""
+
msgid "Amazon authentication is not %{link_start}correctly configured%{link_end}. Ask your GitLab administrator if you want to use this service."
msgstr ""
@@ -2312,10 +2431,16 @@ msgstr ""
msgid "An error occurred when updating the issue weight"
msgstr ""
+msgid "An error occurred while acknowledging the notification. Refresh the page and try again."
+msgstr ""
+
+msgid "An error occurred while adding approvers"
+msgstr ""
+
msgid "An error occurred while adding formatted title for epic"
msgstr ""
-msgid "An error occurred while checking group path"
+msgid "An error occurred while checking group path. Please refresh and try again."
msgstr ""
msgid "An error occurred while committing your changes."
@@ -2459,7 +2584,7 @@ msgstr ""
msgid "An error occurred while loading milestones"
msgstr ""
-msgid "An error occurred while loading terraform report"
+msgid "An error occurred while loading project creation UI"
msgstr ""
msgid "An error occurred while loading the data. Please try again."
@@ -2513,6 +2638,9 @@ msgstr ""
msgid "An error occurred while rendering preview broadcast message"
msgstr ""
+msgid "An error occurred while rendering the editor"
+msgstr ""
+
msgid "An error occurred while reordering issues."
msgstr ""
@@ -2609,7 +2737,7 @@ msgstr ""
msgid "Analyze your dependencies for known vulnerabilities."
msgstr ""
-msgid "Analyze your source code and git history for secrets"
+msgid "Analyze your source code and git history for secrets."
msgstr ""
msgid "Analyze your source code for known vulnerabilities."
@@ -2723,12 +2851,18 @@ msgstr ""
msgid "Apply suggestion"
msgstr ""
+msgid "Apply suggestions"
+msgstr ""
+
msgid "Apply template"
msgstr ""
msgid "Apply this approval rule to any branch or a specific protected branch."
msgstr ""
+msgid "Applying"
+msgstr ""
+
msgid "Applying a template will replace the existing issue description. Any changes you have made will be lost."
msgstr ""
@@ -2741,12 +2875,18 @@ msgstr ""
msgid "Applying multiple commands"
msgstr ""
-msgid "Applying suggestion"
+msgid "Applying suggestion..."
+msgstr ""
+
+msgid "Applying suggestions..."
msgstr ""
msgid "Approval rules"
msgstr ""
+msgid "Approval rules reset to project defaults"
+msgstr ""
+
msgid "ApprovalRuleRemove|%d member"
msgid_plural "ApprovalRuleRemove|%d members"
msgstr[0] ""
@@ -2808,6 +2948,9 @@ msgstr ""
msgid "Approved"
msgstr ""
+msgid "Approved MRs"
+msgstr ""
+
msgid "Approved by: "
msgstr ""
@@ -3021,6 +3164,9 @@ msgstr ""
msgid "Assign"
msgstr ""
+msgid "Assign Iteration"
+msgstr ""
+
msgid "Assign custom color like #FF0000"
msgstr ""
@@ -3039,6 +3185,9 @@ msgstr ""
msgid "Assign to"
msgstr ""
+msgid "Assign to commenting user"
+msgstr ""
+
msgid "Assign yourself to these issues"
msgstr ""
@@ -3067,6 +3216,9 @@ msgstr[1] ""
msgstr[2] ""
msgstr[3] ""
+msgid "Assignee has no permissions"
+msgstr ""
+
msgid "Assignee lists not available with your current license"
msgstr ""
@@ -3076,6 +3228,9 @@ msgstr ""
msgid "Assignee(s)"
msgstr ""
+msgid "Assignees"
+msgstr ""
+
msgid "Assigns %{assignee_users_sentence}."
msgstr ""
@@ -3113,6 +3268,9 @@ msgstr ""
msgid "Audit Events is a way to keep track of important events that happened in GitLab."
msgstr ""
+msgid "Audit Log"
+msgstr ""
+
msgid "AuditEvents|(removed)"
msgstr ""
@@ -3149,6 +3307,9 @@ msgstr ""
msgid "AuditLogs|IP Address"
msgstr ""
+msgid "AuditLogs|Member Events"
+msgstr ""
+
msgid "AuditLogs|No matching %{type} found."
msgstr ""
@@ -3431,9 +3592,6 @@ msgstr ""
msgid "Badges|e.g. %{exampleUrl}"
msgstr ""
-msgid "Badge|New"
-msgstr ""
-
msgid "Balsamiq file could not be loaded."
msgstr ""
@@ -3482,6 +3640,12 @@ msgstr ""
msgid "Below you will find all the groups that are public."
msgstr ""
+msgid "Beta"
+msgstr ""
+
+msgid "Bi-weekly code coverage"
+msgstr ""
+
msgid "Billing"
msgstr ""
@@ -3542,9 +3706,15 @@ msgstr ""
msgid "Bitbucket Server Import"
msgstr ""
+msgid "Bitbucket Server import"
+msgstr ""
+
msgid "Bitbucket import"
msgstr ""
+msgid "Blame"
+msgstr ""
+
msgid "Blocked"
msgstr ""
@@ -3557,9 +3727,6 @@ msgstr ""
msgid "Blog"
msgstr ""
-msgid "Blue helpers indicate an action to be taken."
-msgstr ""
-
msgid "Board name"
msgstr ""
@@ -3758,6 +3925,9 @@ msgstr ""
msgid "Broadcast Message was successfully updated."
msgstr ""
+msgid "Broadcast Messages"
+msgstr ""
+
msgid "Browse Directory"
msgstr ""
@@ -3812,6 +3982,9 @@ msgstr ""
msgid "By %{user_name}"
msgstr ""
+msgid "By URL"
+msgstr ""
+
msgid "By default GitLab sends emails in HTML and plain text formats so mail clients can choose what format to use. Disable this option if you only want to send emails in plain text format."
msgstr ""
@@ -3917,6 +4090,9 @@ msgstr ""
msgid "Can override approvers and approvals required per merge request"
msgstr ""
+msgid "Can't apply as this line has changed or the suggestion already matches its content."
+msgstr ""
+
msgid "Can't create snippet: %{err}"
msgstr ""
@@ -4103,9 +4279,6 @@ msgstr ""
msgid "Changes are still tracked. Useful for cluster/index migrations."
msgstr ""
-msgid "Changes are unknown"
-msgstr ""
-
msgid "Changes suppressed. Click to show."
msgstr ""
@@ -4412,15 +4585,9 @@ msgstr ""
msgid "Choose what content you want to see on a group’s overview page"
msgstr ""
-msgid "Choose which groups you wish to synchronize to this secondary node"
-msgstr ""
-
msgid "Choose which repositories you want to connect and run CI/CD pipelines."
msgstr ""
-msgid "Choose which shards you wish to synchronize to this secondary node"
-msgstr ""
-
msgid "Choose your framework"
msgstr ""
@@ -4718,9 +4885,15 @@ msgstr ""
msgid "Cluster does not exist"
msgstr ""
+msgid "Cluster is required for Stages::ClusterEndpointInserter"
+msgstr ""
+
msgid "Cluster level"
msgstr ""
+msgid "Cluster type must be specificed for Stages::ClusterEndpointInserter"
+msgstr ""
+
msgid "ClusterIntegration| %{custom_domain_start}More information%{custom_domain_end}."
msgstr ""
@@ -4832,7 +5005,7 @@ msgstr ""
msgid "ClusterIntegration|Cert-Manager"
msgstr ""
-msgid "ClusterIntegration|Cert-Manager is a native Kubernetes certificate management controller that helps with issuing certificates. Installing Cert-Manager on your cluster will issue a certificate by %{letsEncrypt} and ensure that certificates are valid and up-to-date."
+msgid "ClusterIntegration|Cert-Manager is a native Kubernetes certificate management controller that helps with issuing certificates. Installing Cert-Manager on your cluster will issue a certificate by %{linkStart}Let's Encrypt%{linkEnd} and ensure that certificates are valid and up-to-date."
msgstr ""
msgid "ClusterIntegration|Certificate Authority bundle (PEM format)"
@@ -4859,9 +5032,6 @@ msgstr ""
msgid "ClusterIntegration|Clear the local cache of namespace and service accounts. This is necessary if your integration has become out of sync. The cache is repopulated during the next CI job that requires namespace and service accounts."
msgstr ""
-msgid "ClusterIntegration|Cloud Run"
-msgstr ""
-
msgid "ClusterIntegration|Cluster being created"
msgstr ""
@@ -4946,7 +5116,7 @@ msgstr ""
msgid "ClusterIntegration|Crossplane"
msgstr ""
-msgid "ClusterIntegration|Crossplane enables declarative provisioning of managed services from your cloud of choice using %{codeStart}kubectl%{codeEnd} or %{gitlabIntegrationLink}. Crossplane runs inside your Kubernetes cluster and supports secure connectivity and secrets management between app containers and the cloud services they depend on."
+msgid "ClusterIntegration|Crossplane enables declarative provisioning of managed services from your cloud of choice using %{codeStart}kubectl%{codeEnd} or %{linkStart}GitLab Integration%{linkEnd}. Crossplane runs inside your Kubernetes cluster and supports secure connectivity and secrets management between app containers and the cloud services they depend on."
msgstr ""
msgid "ClusterIntegration|Deletes all GitLab resources attached to this cluster during removal"
@@ -5030,9 +5200,6 @@ msgstr ""
msgid "ClusterIntegration|GitLab-managed cluster"
msgstr ""
-msgid "ClusterIntegration|Gitlab Integration"
-msgstr ""
-
msgid "ClusterIntegration|Global default"
msgstr ""
@@ -5078,7 +5245,7 @@ msgstr ""
msgid "ClusterIntegration|Ingress gives you a way to route requests to services based on the request host or path, centralizing a number of services into a single entrypoint."
msgstr "Wprowadzanie umożliwia kierowanie żądań do usług na podstawie hosta lub ścieżki żądania, centralizując szereg usług w jeden punkt początkowy."
-msgid "ClusterIntegration|Installing Ingress may incur additional costs. Learn more about %{pricingLink}."
+msgid "ClusterIntegration|Installing Ingress may incur additional costs. Learn more about %{linkStart}pricing%{linkEnd}."
msgstr ""
msgid "ClusterIntegration|Instance cluster"
@@ -5093,7 +5260,7 @@ msgstr ""
msgid "ClusterIntegration|Issuer Email"
msgstr ""
-msgid "ClusterIntegration|Issuers represent a certificate authority. You must provide an email address for your Issuer. "
+msgid "ClusterIntegration|Issuers represent a certificate authority. You must provide an email address for your Issuer."
msgstr ""
msgid "ClusterIntegration|Jupyter Hostname"
@@ -5165,9 +5332,6 @@ msgstr ""
msgid "ClusterIntegration|Learn more about instance Kubernetes clusters"
msgstr ""
-msgid "ClusterIntegration|Let's Encrypt"
-msgstr ""
-
msgid "ClusterIntegration|Loading IAM Roles"
msgstr ""
@@ -5279,7 +5443,7 @@ msgstr ""
msgid "ClusterIntegration|Prometheus"
msgstr ""
-msgid "ClusterIntegration|Prometheus is an open-source monitoring system with %{gitlabIntegrationLink} to monitor deployed applications."
+msgid "ClusterIntegration|Prometheus is an open-source monitoring system with %{linkStart}GitLab Integration%{linkEnd} to monitor deployed applications."
msgstr ""
msgid "ClusterIntegration|Provider details"
@@ -5435,10 +5599,10 @@ msgstr ""
msgid "ClusterIntegration|Select zone to choose machine type"
msgstr ""
-msgid "ClusterIntegration|Send Cilium Logs"
+msgid "ClusterIntegration|Send Container Network Policies Logs"
msgstr ""
-msgid "ClusterIntegration|Send ModSecurity Logs"
+msgid "ClusterIntegration|Send Web Application Firewall Logs"
msgstr ""
msgid "ClusterIntegration|Service Token"
@@ -5606,15 +5770,12 @@ msgstr ""
msgid "ClusterIntegration|documentation"
msgstr ""
-msgid "ClusterIntegration|installed via %{installed_via}"
+msgid "ClusterIntegration|installed via %{linkStart}Cloud Run%{linkEnd}"
msgstr ""
msgid "ClusterIntegration|meets the requirements"
msgstr ""
-msgid "ClusterIntegration|pricing"
-msgstr ""
-
msgid "ClusterIntegration|sign up"
msgstr ""
@@ -5651,6 +5812,15 @@ msgstr ""
msgid "Code"
msgstr ""
+msgid "Code Coverage: %{coveragePercentage}%{percentSymbol}"
+msgstr ""
+
+msgid "Code Coverage| Empty code coverage data"
+msgstr ""
+
+msgid "Code Coverage|Couldn't fetch the code coverage data"
+msgstr ""
+
msgid "Code Owners"
msgstr ""
@@ -5705,7 +5875,7 @@ msgstr ""
msgid "Collapse approvers"
msgstr ""
-msgid "Collapse child epics"
+msgid "Collapse milestones"
msgstr ""
msgid "Collapse replies"
@@ -5723,6 +5893,9 @@ msgstr ""
msgid "Coming soon"
msgstr ""
+msgid "Comma-separated, e.g. '1.1.1.1, 2.2.2.0/24'"
+msgstr ""
+
msgid "Command"
msgstr ""
@@ -5895,6 +6068,12 @@ msgstr ""
msgid "Compliance framework (optional)"
msgstr ""
+msgid "Compliance frameworks"
+msgstr ""
+
+msgid "ComplianceDashboard|created by:"
+msgstr ""
+
msgid "ComplianceFramework|GDPR"
msgstr ""
@@ -6033,6 +6212,9 @@ msgstr ""
msgid "Connection timed out"
msgstr ""
+msgid "Connection timeout"
+msgstr ""
+
msgid "Contact sales to upgrade"
msgstr ""
@@ -6092,9 +6274,6 @@ msgstr ""
msgid "ContainerRegistry|CLI Commands"
msgstr ""
-msgid "ContainerRegistry|Compressed Size"
-msgstr ""
-
msgid "ContainerRegistry|Container Registry"
msgstr ""
@@ -6110,6 +6289,9 @@ msgstr ""
msgid "ContainerRegistry|Copy push command"
msgstr ""
+msgid "ContainerRegistry|Delete selected"
+msgstr ""
+
msgid "ContainerRegistry|Docker connection error"
msgstr ""
@@ -6143,16 +6325,16 @@ msgstr ""
msgid "ContainerRegistry|If you are not already logged in, you need to authenticate to the Container Registry by using your GitLab username and password. If you have %{twofaDocLinkStart}Two-Factor Authentication%{twofaDocLinkEnd} enabled, use a %{personalAccessTokensDocLinkStart}Personal Access Token%{personalAccessTokensDocLinkEnd} instead of a password."
msgstr ""
-msgid "ContainerRegistry|Image ID"
+msgid "ContainerRegistry|Image ID: %{imageId}"
msgstr ""
msgid "ContainerRegistry|Image Repositories"
msgstr ""
-msgid "ContainerRegistry|Keep and protect the images that matter most."
+msgid "ContainerRegistry|Image tags"
msgstr ""
-msgid "ContainerRegistry|Last Updated"
+msgid "ContainerRegistry|Keep and protect the images that matter most."
msgstr ""
msgid "ContainerRegistry|Login"
@@ -6167,6 +6349,9 @@ msgstr ""
msgid "ContainerRegistry|Please contact your administrator."
msgstr ""
+msgid "ContainerRegistry|Published %{timeInfo}"
+msgstr ""
+
msgid "ContainerRegistry|Push an image"
msgstr ""
@@ -6182,9 +6367,6 @@ msgstr ""
msgid "ContainerRegistry|Remove repository"
msgstr ""
-msgid "ContainerRegistry|Remove selected tags"
-msgstr ""
-
msgid "ContainerRegistry|Remove tag"
msgid_plural "ContainerRegistry|Remove tags"
msgstr[0] ""
@@ -6216,9 +6398,6 @@ msgstr ""
msgid "ContainerRegistry|Sorry, your filter produced no results."
msgstr ""
-msgid "ContainerRegistry|Tag"
-msgstr ""
-
msgid "ContainerRegistry|Tag expiration policy"
msgstr ""
@@ -6363,21 +6542,6 @@ msgstr ""
msgid "Control the display of third party offers."
msgstr ""
-msgid "Control the maximum concurrency of LFS/attachment backfill for this secondary node"
-msgstr ""
-
-msgid "Control the maximum concurrency of container repository operations for this Geo node"
-msgstr ""
-
-msgid "Control the maximum concurrency of repository backfill for this secondary node"
-msgstr ""
-
-msgid "Control the maximum concurrency of verification operations for this Geo node"
-msgstr ""
-
-msgid "Control the minimum interval in days that a repository should be reverified for this primary node"
-msgstr ""
-
msgid "Cookie domain"
msgstr ""
@@ -6519,6 +6683,9 @@ msgstr ""
msgid "Could not find design."
msgstr ""
+msgid "Could not find iteration"
+msgstr ""
+
msgid "Could not remove the trigger."
msgstr ""
@@ -6555,6 +6722,12 @@ msgstr ""
msgid "Coverage"
msgstr ""
+msgid "Coverage Fuzzing"
+msgstr ""
+
+msgid "Crash State"
+msgstr ""
+
msgid "Create"
msgstr ""
@@ -6603,6 +6776,9 @@ msgstr ""
msgid "Create a personal access token on your account to pull or push via %{protocol}."
msgstr ""
+msgid "Create a project pre-populated with the necessary files to get you started quickly."
+msgstr ""
+
msgid "Create an account using:"
msgstr ""
@@ -6771,6 +6947,9 @@ msgstr ""
msgid "Creates branch '%{branch_name}' and a merge request to resolve this issue."
msgstr ""
+msgid "Creating"
+msgstr ""
+
msgid "Creating epic"
msgstr ""
@@ -6822,7 +7001,10 @@ msgstr ""
msgid "Current vulnerabilities count"
msgstr ""
-msgid "CurrentUser|Buy CI minutes"
+msgid "CurrentUser|Buy Pipeline minutes"
+msgstr ""
+
+msgid "CurrentUser|One of your groups is running out"
msgstr ""
msgid "CurrentUser|Profile"
@@ -6837,6 +7019,9 @@ msgstr ""
msgid "CurrentUser|Upgrade"
msgstr ""
+msgid "Custom Attributes"
+msgstr ""
+
msgid "Custom CI configuration path"
msgstr ""
@@ -7102,12 +7287,18 @@ msgstr ""
msgid "DAG"
msgstr ""
+msgid "DAG visualization requires at least 3 dependent jobs."
+msgstr ""
+
msgid "DNS"
msgstr ""
msgid "Dashboard"
msgstr ""
+msgid "Dashboard uid not found"
+msgstr ""
+
msgid "DashboardProjects|All"
msgstr ""
@@ -7132,6 +7323,9 @@ msgstr ""
msgid "Data is still calculating..."
msgstr ""
+msgid "Datasource name not found"
+msgstr ""
+
msgid "Date"
msgstr ""
@@ -7312,6 +7506,9 @@ msgstr ""
msgid "Delete this attachment"
msgstr ""
+msgid "Delete user list"
+msgstr ""
+
msgid "Delete variable"
msgstr ""
@@ -7638,6 +7835,9 @@ msgstr ""
msgid "Deploying to"
msgstr ""
+msgid "Deploying to AWS is easy with GitLab"
+msgstr ""
+
msgid "Deployment Frequency"
msgstr ""
@@ -8002,6 +8202,9 @@ msgstr ""
msgid "Dismissed on pipeline %{pipelineLink} at %{projectLink}"
msgstr ""
+msgid "Display alerts from all your monitoring tools directly within GitLab."
+msgstr ""
+
msgid "Display name"
msgstr ""
@@ -8059,9 +8262,6 @@ msgstr ""
msgid "Don't show again"
msgstr ""
-msgid "Don't worry, you can access this tour by clicking on the help icon in the top right corner and choose <strong>Learn GitLab</strong>."
-msgstr ""
-
msgid "Done"
msgstr ""
@@ -8188,6 +8388,9 @@ msgstr ""
msgid "Edit Release"
msgstr ""
+msgid "Edit Slack integration"
+msgstr ""
+
msgid "Edit Snippet"
msgstr ""
@@ -8227,6 +8430,9 @@ msgstr ""
msgid "Edit issues"
msgstr ""
+msgid "Edit iteration"
+msgstr ""
+
msgid "Edit public deploy key"
msgstr ""
@@ -8275,6 +8481,9 @@ msgstr ""
msgid "Email"
msgstr ""
+msgid "Email Notification"
+msgstr ""
+
msgid "Email address"
msgstr ""
@@ -8287,6 +8496,9 @@ msgstr ""
msgid "Email not verified. Please verify your email in Salesforce."
msgstr ""
+msgid "Email notification for unknown sign-ins"
+msgstr ""
+
msgid "Email patch"
msgstr ""
@@ -8518,6 +8730,9 @@ msgstr ""
msgid "Enforce DNS rebinding attack protection"
msgstr ""
+msgid "Enforce personal access token expiration"
+msgstr ""
+
msgid "Ensure connectivity is available from the GitLab server to the Prometheus server"
msgstr ""
@@ -8587,6 +8802,9 @@ msgstr ""
msgid "Enter the name of your application, and we'll return a unique %{type}."
msgstr ""
+msgid "Enter weights for storages for new repositories."
+msgstr ""
+
msgid "Enter your password to approve"
msgstr ""
@@ -8596,15 +8814,27 @@ msgstr ""
msgid "Environment does not have deployments"
msgstr ""
+msgid "Environment is required for Stages::MetricEndpointInserter"
+msgstr ""
+
+msgid "Environment is required for Stages::VariableEndpointInserter"
+msgstr ""
+
msgid "Environment scope"
msgstr ""
+msgid "Environment variables are applied to all project environments in this instance via the Runner. You can use environment variables for passwords, secret keys, etc. Make variables available to the running application by prepending the variable key with <code>K8S_SECRET_</code>. You can set variables to be:"
+msgstr ""
+
msgid "Environment variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. Additionally, they can be masked so they are hidden in job logs, though they must match certain regexp requirements to do so. You can use environment variables for passwords, secret keys, or whatever you want."
msgstr ""
msgid "Environment variables are configured by your administrator to be %{link_start}protected%{link_end} by default"
msgstr ""
+msgid "Environment variables on this GitLab instance are configured to be %{link_start}protected%{link_end} by default"
+msgstr ""
+
msgid "Environment:"
msgstr ""
@@ -9136,6 +9366,9 @@ msgstr ""
msgid "EventFilterBy|Filter by comments"
msgstr ""
+msgid "EventFilterBy|Filter by designs"
+msgstr ""
+
msgid "EventFilterBy|Filter by epic events"
msgstr ""
@@ -9268,15 +9501,15 @@ msgstr ""
msgid "Expand approvers"
msgstr ""
-msgid "Expand child epics"
-msgstr ""
-
msgid "Expand down"
msgstr ""
msgid "Expand dropdown"
msgstr ""
+msgid "Expand milestones"
+msgstr ""
+
msgid "Expand sidebar"
msgstr ""
@@ -9292,6 +9525,9 @@ msgstr ""
msgid "Expiration date"
msgstr ""
+msgid "Expiration not enforced"
+msgstr ""
+
msgid "Expiration policy for the Container Registry is a perfect solution for keeping the Registry space down while still enjoying the full power of GitLab CI/CD."
msgstr ""
@@ -9412,6 +9648,9 @@ msgstr ""
msgid "Failed Jobs"
msgstr ""
+msgid "Failed on"
+msgstr ""
+
msgid "Failed to add a Zoom meeting"
msgstr ""
@@ -9472,13 +9711,19 @@ msgstr ""
msgid "Failed to install."
msgstr ""
+msgid "Failed to load assignees. Please try again."
+msgstr ""
+
+msgid "Failed to load authors. Please try again."
+msgstr ""
+
msgid "Failed to load emoji list."
msgstr ""
msgid "Failed to load error details from Sentry."
msgstr ""
-msgid "Failed to load errors from Sentry. Error message: %{errorMessage}"
+msgid "Failed to load errors from Sentry."
msgstr ""
msgid "Failed to load group activity metrics. Please try again."
@@ -9559,6 +9804,9 @@ msgstr ""
msgid "Failed to set due date because the date format is invalid."
msgstr ""
+msgid "Failed to set iteration on this issue. Please try again."
+msgstr ""
+
msgid "Failed to signing using smartcard authentication"
msgstr ""
@@ -9781,13 +10029,10 @@ msgstr ""
msgid "FeatureFlags|Target environments"
msgstr ""
-msgid "FeatureFlags|There are no active feature flags"
+msgid "FeatureFlags|There was an error fetching the feature flags."
msgstr ""
-msgid "FeatureFlags|There are no inactive feature flags"
-msgstr ""
-
-msgid "FeatureFlags|There was an error fetching the feature flags."
+msgid "FeatureFlags|There was an error retrieving user lists"
msgstr ""
msgid "FeatureFlags|Try again in a few moments or contact your support team."
@@ -9799,9 +10044,18 @@ msgstr ""
msgid "FeatureFlag|Delete strategy"
msgstr ""
+msgid "FeatureFlag|List"
+msgstr ""
+
msgid "FeatureFlag|Percentage"
msgstr ""
+msgid "FeatureFlag|Select a user list"
+msgstr ""
+
+msgid "FeatureFlag|There are no configured user lists"
+msgstr ""
+
msgid "FeatureFlag|Type"
msgstr ""
@@ -9958,6 +10212,12 @@ msgstr ""
msgid "Filter..."
msgstr ""
+msgid "Find File"
+msgstr ""
+
+msgid "Find bugs in your code with coverage-guided fuzzing"
+msgstr ""
+
msgid "Find by path"
msgstr ""
@@ -10222,6 +10482,9 @@ msgstr ""
msgid "Geo allows you to replicate your GitLab instance to other geographical locations."
msgstr ""
+msgid "Geo nodes are paused using a command run on the node"
+msgstr ""
+
msgid "GeoNodeStatusEvent|%{timeAgoStr} (%{pendingEvents} events)"
msgstr ""
@@ -10306,6 +10569,9 @@ msgstr ""
msgid "GeoNodes|Not checksummed"
msgstr ""
+msgid "GeoNodes|Package files"
+msgstr ""
+
msgid "GeoNodes|Pausing replication stops the sync process. Are you sure?"
msgstr ""
@@ -10324,6 +10590,9 @@ msgstr ""
msgid "GeoNodes|Replication slots"
msgstr ""
+msgid "GeoNodes|Replication status"
+msgstr ""
+
msgid "GeoNodes|Repositories"
msgstr ""
@@ -10534,6 +10803,9 @@ msgstr ""
msgid "Geo|Synchronization failed - %{error}"
msgstr ""
+msgid "Geo|Synchronization of %{itemTitle} is disabled."
+msgstr ""
+
msgid "Geo|The database is currently %{db_lag} behind the primary node."
msgstr ""
@@ -10846,6 +11118,9 @@ msgstr ""
msgid "Go to %{link_to_google_takeout}."
msgstr ""
+msgid "Go to <strong>Issues</strong> > <strong>Boards</strong> to access your personalized learning issue board."
+msgstr ""
+
msgid "Go to Pipelines"
msgstr ""
@@ -10945,9 +11220,6 @@ msgstr ""
msgid "Go to your snippets"
msgstr ""
-msgid "Golden Tanuki"
-msgstr ""
-
msgid "Google Cloud Platform"
msgstr ""
@@ -10969,6 +11241,9 @@ msgstr ""
msgid "Grafana URL"
msgstr ""
+msgid "Grafana response contains invalid json"
+msgstr ""
+
msgid "GrafanaIntegration|API Token"
msgstr ""
@@ -11017,9 +11292,6 @@ msgstr ""
msgid "Group %{group_name} was successfully created."
msgstr ""
-msgid "Group '%{group_name}' is being imported."
-msgstr ""
-
msgid "Group Audit Events"
msgstr ""
@@ -11056,9 +11328,6 @@ msgstr ""
msgid "Group by:"
msgstr ""
-msgid "Group could not be imported: %{errors}"
-msgstr ""
-
msgid "Group description"
msgstr ""
@@ -11086,9 +11355,15 @@ msgstr ""
msgid "Group has not been marked for deletion"
msgstr ""
+msgid "Group import could not be scheduled"
+msgstr ""
+
msgid "Group info:"
msgstr ""
+msgid "Group is required when cluster_type is :group"
+msgstr ""
+
msgid "Group maintainers can register group runners in the %{link}"
msgstr ""
@@ -11152,6 +11427,24 @@ msgstr ""
msgid "GroupActivyMetrics|Recent activity (last 90 days)"
msgstr ""
+msgid "GroupImport|Failed to import group."
+msgstr ""
+
+msgid "GroupImport|Group '%{group_name}' is being imported."
+msgstr ""
+
+msgid "GroupImport|Group could not be imported: %{errors}"
+msgstr ""
+
+msgid "GroupImport|Please wait while we import the group for you. Refresh at will."
+msgstr ""
+
+msgid "GroupImport|The group was successfully imported."
+msgstr ""
+
+msgid "GroupImport|Unable to process group import file"
+msgstr ""
+
msgid "GroupRoadmap|%{dateWord} – No end date"
msgstr ""
@@ -11419,6 +11712,9 @@ msgstr ""
msgid "Groups (%{groups})"
msgstr ""
+msgid "Groups and projects"
+msgstr ""
+
msgid "Groups can also be nested by creating %{subgroup_docs_link_start}subgroups%{subgroup_docs_link_end}."
msgstr ""
@@ -11464,6 +11760,33 @@ msgstr ""
msgid "GroupsEmptyState|You can manage your group member’s permissions and access to each project in the group."
msgstr ""
+msgid "GroupsNew|Contact an administrator to enable options for importing your group."
+msgstr ""
+
+msgid "GroupsNew|Create"
+msgstr ""
+
+msgid "GroupsNew|Create group"
+msgstr ""
+
+msgid "GroupsNew|GitLab group export"
+msgstr ""
+
+msgid "GroupsNew|Import"
+msgstr ""
+
+msgid "GroupsNew|Import group"
+msgstr ""
+
+msgid "GroupsNew|My Awesome Group"
+msgstr ""
+
+msgid "GroupsNew|No import options available"
+msgstr ""
+
+msgid "GroupsNew|To copy a GitLab group between installations, navigate to the group settings page for the original installation, generate an export file, and upload it here."
+msgstr ""
+
msgid "GroupsTree|Are you sure you want to leave the \"%{fullName}\" group?"
msgstr ""
@@ -11566,6 +11889,9 @@ msgstr ""
msgid "Helps reduce request volume for protected paths"
msgstr ""
+msgid "Here are all your projects in your group, including the one you just created. To start, let’s take a look at your personalized learning project which will help you learn about GitLab at your own pace."
+msgstr ""
+
msgid "Here you will find recent merge request activity"
msgstr ""
@@ -11582,6 +11908,9 @@ msgstr[1] ""
msgstr[2] ""
msgstr[3] ""
+msgid "Hide details"
+msgstr ""
+
msgid "Hide file browser"
msgstr ""
@@ -11591,6 +11920,9 @@ msgstr ""
msgid "Hide host keys manual input"
msgstr ""
+msgid "Hide list"
+msgstr ""
+
msgid "Hide marketing-related entries from help"
msgstr ""
@@ -11613,9 +11945,6 @@ msgstr[3] ""
msgid "Hide values"
msgstr ""
-msgid "Hiding all labels"
-msgstr ""
-
msgid "High or unknown vulnerabilities present"
msgstr ""
@@ -11700,6 +12029,9 @@ msgstr ""
msgid "ID:"
msgstr ""
+msgid "IDE"
+msgstr ""
+
msgid "IDE|Allow live previews of JavaScript projects in the Web IDE using CodeSandbox Live Preview."
msgstr ""
@@ -11790,10 +12122,10 @@ msgstr ""
msgid "If enabled"
msgstr ""
-msgid "If enabled, access to projects will be validated on an external service using their classification label."
+msgid "If enabled, GitLab will handle Object Storage replication using Geo. %{linkStart}More information%{linkEnd}"
msgstr ""
-msgid "If enabled, and if object storage is enabled, GitLab will handle Object Storage replication using Geo"
+msgid "If enabled, access to projects will be validated on an external service using their classification label."
msgstr ""
msgid "If there is no previous license or if the previous license has expired, some GitLab functionality will be blocked until a new, valid license is uploaded."
@@ -12157,6 +12489,9 @@ msgstr[1] ""
msgstr[2] ""
msgstr[3] ""
+msgid "Instance Configuration"
+msgstr ""
+
msgid "Instance Statistics visibility"
msgstr ""
@@ -12178,9 +12513,6 @@ msgstr ""
msgid "Integrations"
msgstr ""
-msgid "Integrations allow you to integrate GitLab with other applications"
-msgstr ""
-
msgid "Integrations|All details"
msgstr ""
@@ -12292,6 +12624,9 @@ msgstr ""
msgid "Invalid query"
msgstr ""
+msgid "Invalid repository bundle for snippet with id %{snippet_id}"
+msgstr ""
+
msgid "Invalid repository path"
msgstr ""
@@ -12310,6 +12645,9 @@ msgstr ""
msgid "Invalid two-factor code."
msgstr ""
+msgid "Invalid yaml"
+msgstr ""
+
msgid "Invitation"
msgstr ""
@@ -12529,15 +12867,24 @@ msgstr ""
msgid "It seems like the Dependency Scanning job ran successfully, but no dependencies have been detected in your project."
msgstr ""
+msgid "It seems that there is currently no available data for code coverage"
+msgstr ""
+
msgid "It's you"
msgstr ""
+msgid "Iteration"
+msgstr ""
+
msgid "Iteration changed to"
msgstr ""
msgid "Iteration removed"
msgstr ""
+msgid "Iteration updated"
+msgstr ""
+
msgid "Iterations"
msgstr ""
@@ -12568,12 +12915,18 @@ msgstr ""
msgid "January"
msgstr ""
+msgid "Jira Issues"
+msgstr ""
+
msgid "Jira import is already running."
msgstr ""
msgid "Jira integration not configured."
msgstr ""
+msgid "Jira project key is not configured"
+msgstr ""
+
msgid "Jira project: %{importProject}"
msgstr ""
@@ -12754,6 +13107,9 @@ msgstr ""
msgid "Keep divergent refs"
msgstr ""
+msgid "Kerberos access denied"
+msgstr ""
+
msgid "Key"
msgstr ""
@@ -12763,10 +13119,13 @@ msgstr ""
msgid "Key: %{key}"
msgstr ""
-msgid "Keyboard Shortcuts"
+msgid "Keyboard shortcuts"
msgstr ""
-msgid "Keyboard shortcuts"
+msgid "Keys"
+msgstr ""
+
+msgid "Ki"
msgstr ""
msgid "Kubernetes"
@@ -12808,6 +13167,9 @@ msgstr ""
msgid "LDAP"
msgstr ""
+msgid "LDAP Synchronization"
+msgstr ""
+
msgid "LDAP settings"
msgstr ""
@@ -13034,12 +13396,18 @@ msgstr ""
msgid "Learn more about custom project templates"
msgstr ""
+msgid "Learn more about deploying to AWS"
+msgstr ""
+
msgid "Learn more about deploying to a cluster"
msgstr ""
msgid "Learn more about group-level project templates"
msgstr ""
+msgid "Learn more about job dependencies"
+msgstr ""
+
msgid "Learn more about signing commits"
msgstr ""
@@ -13091,9 +13459,21 @@ msgstr ""
msgid "License History"
msgstr ""
+msgid "License ID:"
+msgstr ""
+
+msgid "License URL"
+msgstr ""
+
msgid "License-Check"
msgstr ""
+msgid "LicenseCompliance|%{docLinkStart}License Approvals%{docLinkEnd} are active"
+msgstr ""
+
+msgid "LicenseCompliance|%{docLinkStart}License Approvals%{docLinkEnd} are inactive"
+msgstr ""
+
msgid "LicenseCompliance|Add a license"
msgstr ""
@@ -13115,9 +13495,15 @@ msgstr ""
msgid "LicenseCompliance|Deny"
msgstr ""
+msgid "LicenseCompliance|Learn more about %{linkStart}License Approvals%{linkEnd}"
+msgstr ""
+
msgid "LicenseCompliance|License"
msgstr ""
+msgid "LicenseCompliance|License Approvals"
+msgstr ""
+
msgid "LicenseCompliance|License Compliance detected %d license and policy violation for the source branch only; approval required"
msgid_plural "LicenseCompliance|License Compliance detected %d licenses and policy violations for the source branch only; approval required"
msgstr[0] ""
@@ -13348,6 +13734,9 @@ msgstr ""
msgid "List your Bitbucket Server repositories"
msgstr ""
+msgid "Lists"
+msgstr ""
+
msgid "Live preview"
msgstr ""
@@ -13531,6 +13920,9 @@ msgstr ""
msgid "Manage project labels"
msgstr ""
+msgid "Manage storage usage"
+msgstr ""
+
msgid "Manage two-factor authentication"
msgstr ""
@@ -13546,6 +13938,9 @@ msgstr ""
msgid "Manifest file import"
msgstr ""
+msgid "Manifest import"
+msgstr ""
+
msgid "Manual job"
msgstr ""
@@ -13693,6 +14088,12 @@ msgstr ""
msgid "Maximum field length"
msgstr ""
+msgid "Maximum file size is 2MB. Please select a smaller file."
+msgstr ""
+
+msgid "Maximum import size (MB)"
+msgstr ""
+
msgid "Maximum job timeout"
msgstr ""
@@ -13732,6 +14133,9 @@ msgstr ""
msgid "Maximum size of Elasticsearch bulk indexing requests."
msgstr ""
+msgid "Maximum size of import files."
+msgstr ""
+
msgid "Maximum size of individual attachments in comments."
msgstr ""
@@ -13783,6 +14187,9 @@ msgstr ""
msgid "Merge (when the pipeline succeeds)"
msgstr ""
+msgid "Merge Conflicts"
+msgstr ""
+
msgid "Merge Request"
msgstr ""
@@ -13909,6 +14316,9 @@ msgstr ""
msgid "MergeRequests|Squash task canceled: another squash is already in progress."
msgstr ""
+msgid "MergeRequests|This project does not allow squashing commits when merge requests are accepted."
+msgstr ""
+
msgid "MergeRequests|Thread stays resolved"
msgstr ""
@@ -14062,6 +14472,12 @@ msgstr ""
msgid "MetricsSettings|Add a button to the metrics dashboard linking directly to your existing external dashboard."
msgstr ""
+msgid "MetricsSettings|Choose whether to display dashboard metrics in UTC or the user's local timezone."
+msgstr ""
+
+msgid "MetricsSettings|Dashboard timezone"
+msgstr ""
+
msgid "MetricsSettings|External dashboard URL"
msgstr ""
@@ -14071,6 +14487,12 @@ msgstr ""
msgid "MetricsSettings|Metrics Dashboard"
msgstr ""
+msgid "MetricsSettings|UTC (Coordinated Universal Time)"
+msgstr ""
+
+msgid "MetricsSettings|User's local timezone"
+msgstr ""
+
msgid "Metrics|Add metric"
msgstr ""
@@ -14086,6 +14508,9 @@ msgstr ""
msgid "Metrics|Create metric"
msgstr ""
+msgid "Metrics|Current"
+msgstr ""
+
msgid "Metrics|Delete metric"
msgstr ""
@@ -14138,6 +14563,9 @@ msgstr ""
msgid "Metrics|Max"
msgstr ""
+msgid "Metrics|Min"
+msgstr ""
+
msgid "Metrics|Must be a valid PromQL query."
msgstr ""
@@ -14153,6 +14581,9 @@ msgstr ""
msgid "Metrics|Refresh dashboard"
msgstr ""
+msgid "Metrics|Select a value"
+msgstr ""
+
msgid "Metrics|Star dashboard"
msgstr ""
@@ -14171,12 +14602,18 @@ msgstr ""
msgid "Metrics|There was an error getting annotations information."
msgstr ""
+msgid "Metrics|There was an error getting dashboard validation warnings information."
+msgstr ""
+
msgid "Metrics|There was an error getting deployment information."
msgstr ""
msgid "Metrics|There was an error getting environments information."
msgstr ""
+msgid "Metrics|There was an error getting options for variable \"%{name}\"."
+msgstr ""
+
msgid "Metrics|There was an error trying to validate your query"
msgstr ""
@@ -14219,6 +14656,9 @@ msgstr ""
msgid "Metrics|You're about to permanently delete this metric. This cannot be undone."
msgstr ""
+msgid "Metrics|Your dashboard schema is invalid. Edit the dashboard to correct the YAML schema."
+msgstr ""
+
msgid "Metrics|e.g. HTTP requests"
msgstr ""
@@ -14234,12 +14674,18 @@ msgstr ""
msgid "Metrics|e.g. req/sec"
msgstr ""
+msgid "Mi"
+msgstr ""
+
msgid "Microsoft Azure"
msgstr ""
msgid "Middleman project with Static Site Editor support"
msgstr ""
+msgid "Migrate your data from an external source like GitHub, Bitbucket, or another instance of GitLab."
+msgstr ""
+
msgid "Migrated %{success_count}/%{total_count} files."
msgstr ""
@@ -14376,6 +14822,9 @@ msgstr ""
msgid "Minimum capacity to be available before we schedule more mirrors preemptively."
msgstr ""
+msgid "Minimum interval in days"
+msgstr ""
+
msgid "Minimum length is %{minimum_password_length} characters"
msgstr ""
@@ -14562,6 +15011,12 @@ msgstr ""
msgid "Multiple uploaders found: %{uploader_types}"
msgstr ""
+msgid "Must match with the %{codeStart}external_url%{codeEnd} in %{codeStart}/etc/gitlab/gitlab.rb%{codeEnd}."
+msgstr ""
+
+msgid "Must match with the %{codeStart}geo_node_name%{codeEnd} in %{codeStart}/etc/gitlab/gitlab.rb%{codeEnd}. %{linkStart}More information%{linkEnd}"
+msgstr ""
+
msgid "My Awesome Group"
msgstr ""
@@ -14676,10 +15131,16 @@ msgstr ""
msgid "New Application"
msgstr ""
+msgid "New Branch"
+msgstr ""
+
+msgid "New Deploy Key"
+msgstr ""
+
msgid "New Environment"
msgstr ""
-msgid "New Geo Node"
+msgid "New File"
msgstr ""
msgid "New Group"
@@ -14698,15 +15159,15 @@ msgstr[1] ""
msgstr[2] ""
msgstr[3] ""
-msgid "New Iteration"
-msgstr ""
-
msgid "New Jira import"
msgstr ""
msgid "New Label"
msgstr ""
+msgid "New Merge Request"
+msgstr ""
+
msgid "New Milestone"
msgstr ""
@@ -14725,6 +15186,9 @@ msgstr ""
msgid "New Snippet"
msgstr ""
+msgid "New User"
+msgstr ""
+
msgid "New branch"
msgstr ""
@@ -14767,6 +15231,9 @@ msgstr ""
msgid "New issue title"
msgstr ""
+msgid "New iteration"
+msgstr ""
+
msgid "New iteration created"
msgstr ""
@@ -14944,6 +15411,12 @@ msgstr ""
msgid "No grouping"
msgstr ""
+msgid "No iteration"
+msgstr ""
+
+msgid "No iterations to show"
+msgstr ""
+
msgid "No job log"
msgstr ""
@@ -14986,6 +15459,9 @@ msgstr ""
msgid "No other labels with such name or description"
msgstr ""
+msgid "No panels matching properties %{opts}"
+msgstr ""
+
msgid "No parent group"
msgstr ""
@@ -15019,6 +15495,9 @@ msgstr ""
msgid "No schedules"
msgstr ""
+msgid "No stack trace for this error"
+msgstr ""
+
msgid "No starrers matched your search"
msgstr ""
@@ -15031,7 +15510,7 @@ msgstr ""
msgid "No test coverage"
msgstr ""
-msgid "No thanks, don't show this again"
+msgid "No thanks"
msgstr ""
msgid "No vulnerabilities present"
@@ -15046,9 +15525,6 @@ msgstr ""
msgid "No, directly import the existing email addresses and usernames."
msgstr ""
-msgid "No, not interested right now"
-msgstr ""
-
msgid "No. of commits"
msgstr ""
@@ -15094,9 +15570,6 @@ msgstr ""
msgid "Not found."
msgstr ""
-msgid "Not helpful"
-msgstr ""
-
msgid "Not now"
msgstr ""
@@ -15253,6 +15726,9 @@ msgstr ""
msgid "Notifications on"
msgstr ""
+msgid "Notify users by email when sign-in location is not recognized"
+msgstr ""
+
msgid "Nov"
msgstr ""
@@ -15319,9 +15795,6 @@ msgstr ""
msgid "Oh no!"
msgstr ""
-msgid "Ok let's go"
-msgstr ""
-
msgid "Oldest first"
msgstr ""
@@ -15334,16 +15807,49 @@ msgstr ""
msgid "On track"
msgstr ""
+msgid "OnDemandScans|Attached branch"
+msgstr ""
+
+msgid "OnDemandScans|Attached branch is where the scan job runs."
+msgstr ""
+
+msgid "OnDemandScans|Could not run the scan. Please try again."
+msgstr ""
+
msgid "OnDemandScans|Create new DAST scan"
msgstr ""
+msgid "OnDemandScans|DAST will scan the target URL and any discovered sub URLs."
+msgstr ""
+
+msgid "OnDemandScans|New on-demand DAST scan"
+msgstr ""
+
msgid "OnDemandScans|On-demand Scans"
msgstr ""
+msgid "OnDemandScans|On-demand scans run outside the DevOps cycle and find vulnerabilities in your projects. %{learnMoreLinkStart}Learn more%{learnMoreLinkEnd}"
+msgstr ""
+
+msgid "OnDemandScans|Only a passive scan can be performed on demand."
+msgstr ""
+
+msgid "OnDemandScans|Passive DAST Scan"
+msgstr ""
+
+msgid "OnDemandScans|Please enter a valid URL format, ex: http://www.example.com/home"
+msgstr ""
+
+msgid "OnDemandScans|Run this scan"
+msgstr ""
+
+msgid "OnDemandScans|Scan mode"
+msgstr ""
+
msgid "OnDemandScans|Schedule or run scans immediately against target sites. Currently available on-demand scan type: DAST. %{helpLinkStart}More information%{helpLinkEnd}"
msgstr ""
-msgid "Onboarding"
+msgid "OnDemandScans|Target URL"
msgstr ""
msgid "Once imported, repositories can be mirrored over SSH. Read more %{link_start}here%{link_end}."
@@ -15371,6 +15877,9 @@ msgstr ""
msgid "One or more of you personal access tokens were revoked"
msgstr ""
+msgid "One or more of your %{provider} projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
+msgstr ""
+
msgid "One or more of your Bitbucket projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
msgstr ""
@@ -15872,6 +16381,9 @@ msgstr ""
msgid "Parent epic is not present."
msgstr ""
+msgid "Parsing error for param :embed_json. %{message}"
+msgstr ""
+
msgid "Part of merge request changes"
msgstr ""
@@ -15884,6 +16396,9 @@ msgstr ""
msgid "Passed"
msgstr ""
+msgid "Passed on"
+msgstr ""
+
msgid "Password"
msgstr ""
@@ -15968,6 +16483,9 @@ msgstr ""
msgid "Perform common operations on GitLab project"
msgstr ""
+msgid "Performance and resource management"
+msgstr ""
+
msgid "Performance optimization"
msgstr ""
@@ -16175,7 +16693,7 @@ msgstr ""
msgid "Pipelines|Something went wrong while cleaning runners cache."
msgstr ""
-msgid "Pipelines|There are currently no %{scope} pipelines."
+msgid "Pipelines|There are currently no finished pipelines."
msgstr ""
msgid "Pipelines|There are currently no pipelines."
@@ -16286,6 +16804,9 @@ msgstr ""
msgid "Pipeline|Stop pipeline #%{pipelineId}?"
msgstr ""
+msgid "Pipeline|Tag name"
+msgstr ""
+
msgid "Pipeline|Trigger author"
msgstr ""
@@ -16367,12 +16888,18 @@ msgstr ""
msgid "Please check your email (%{email}) to verify that you own this address and unlock the power of CI/CD. Didn't receive it? %{resend_link}. Wrong email address? %{update_link}."
msgstr ""
+msgid "Please choose a file"
+msgstr ""
+
msgid "Please choose a group URL with no special characters."
msgstr ""
msgid "Please complete your profile with email address"
msgstr ""
+msgid "Please convert %{linkStart}them to Git%{linkEnd}, and go through the %{linkToImportFlow} again."
+msgstr ""
+
msgid "Please convert them to %{link_to_git}, and go through the %{link_to_import_flow} again."
msgstr ""
@@ -16430,6 +16957,9 @@ msgstr ""
msgid "Please provide a valid email address."
msgstr ""
+msgid "Please provide attributes to update"
+msgstr ""
+
msgid "Please refer to <a href=\"%{docs_url}\">%{docs_url}</a>"
msgstr ""
@@ -17165,6 +17695,9 @@ msgstr ""
msgid "Project has too many %{label_for_message} to search"
msgstr ""
+msgid "Project is required when cluster_type is :project"
+msgstr ""
+
msgid "Project members"
msgstr ""
@@ -17282,7 +17815,40 @@ msgstr ""
msgid "ProjectService|Comment"
msgstr ""
-msgid "ProjectService|Comment will be posted on each event"
+msgid "ProjectService|Event will be triggered by a push to the repository"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a commit is created/updated"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a confidential issue is created/updated/closed"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a deployment finishes"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a merge request is created/updated/merged"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a new tag is pushed to the repository"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a new, unique alert is recorded"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a pipeline status changes"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a wiki page is created/updated"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when an issue is created/updated/closed"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when someone adds a comment"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when someone adds a comment on a confidential issue"
msgstr ""
msgid "ProjectService|Perform common operations on GitLab project: %{project_name}"
@@ -17333,6 +17899,9 @@ msgstr ""
msgid "ProjectSettings|Enable 'Delete source branch' option by default"
msgstr ""
+msgid "ProjectSettings|Enable merge trains and pipelines for merged results"
+msgstr ""
+
msgid "ProjectSettings|Every merge creates a merge commit"
msgstr ""
@@ -17363,7 +17932,7 @@ msgstr ""
msgid "ProjectSettings|Forks"
msgstr ""
-msgid "ProjectSettings|Git Large File Storage"
+msgid "ProjectSettings|Git Large File Storage (LFS)"
msgstr ""
msgid "ProjectSettings|Internal"
@@ -17399,9 +17968,6 @@ msgstr ""
msgid "ProjectSettings|Merge options"
msgstr ""
-msgid "ProjectSettings|Merge pipelines will try to validate the post-merge result prior to merging"
-msgstr ""
-
msgid "ProjectSettings|Merge requests"
msgstr ""
@@ -17429,6 +17995,9 @@ msgstr ""
msgid "ProjectSettings|Pipelines"
msgstr ""
+msgid "ProjectSettings|Pipelines for merge requests must be enabled in the CI/CD configuration file, or pipelines could be unresolvable or dropped"
+msgstr ""
+
msgid "ProjectSettings|Pipelines must succeed"
msgstr ""
@@ -17456,6 +18025,9 @@ msgstr ""
msgid "ProjectSettings|Show link to create/view merge request when pushing from the command line"
msgstr ""
+msgid "ProjectSettings|Skipped pipelines are considered successful"
+msgstr ""
+
msgid "ProjectSettings|Snippets"
msgstr ""
@@ -17471,6 +18043,9 @@ msgstr ""
msgid "ProjectSettings|These checks must pass before merge requests can be merged"
msgstr ""
+msgid "ProjectSettings|This introduces the risk of merging changes that will not pass the pipeline."
+msgstr ""
+
msgid "ProjectSettings|This setting is applied on the server level and can be overridden by an admin."
msgstr ""
@@ -17648,15 +18223,27 @@ msgstr ""
msgid "ProjectsNew|Blank project"
msgstr ""
+msgid "ProjectsNew|Connect your external repository to GitLab CI/CD."
+msgstr ""
+
msgid "ProjectsNew|Contact an administrator to enable options for importing your project."
msgstr ""
msgid "ProjectsNew|Create"
msgstr ""
+msgid "ProjectsNew|Create a blank project to house your files, plan your work, and collaborate on code, among other things."
+msgstr ""
+
+msgid "ProjectsNew|Create blank project"
+msgstr ""
+
msgid "ProjectsNew|Create from template"
msgstr ""
+msgid "ProjectsNew|Create new project"
+msgstr ""
+
msgid "ProjectsNew|Creating project & repository."
msgstr ""
@@ -17681,6 +18268,9 @@ msgstr ""
msgid "ProjectsNew|Project description %{tag_start}(optional)%{tag_end}"
msgstr ""
+msgid "ProjectsNew|Run CI/CD for external repository"
+msgstr ""
+
msgid "ProjectsNew|Template"
msgstr ""
@@ -17942,6 +18532,9 @@ msgstr ""
msgid "Protected Branch"
msgstr ""
+msgid "Protected Branches"
+msgstr ""
+
msgid "Protected Environment"
msgstr ""
@@ -17954,6 +18547,9 @@ msgstr ""
msgid "Protected Tag"
msgstr ""
+msgid "Protected Tags"
+msgstr ""
+
msgid "Protected branches"
msgstr ""
@@ -18053,6 +18649,9 @@ msgstr ""
msgid "Provider"
msgstr ""
+msgid "Proxy support for this API is not available currently"
+msgstr ""
+
msgid "Pseudonymizer data collection"
msgstr ""
@@ -18077,6 +18676,9 @@ msgstr ""
msgid "Publish to status page"
msgstr ""
+msgid "Published on status page"
+msgstr ""
+
msgid "Publishes this issue to the associated status page."
msgstr ""
@@ -18251,6 +18853,9 @@ msgstr ""
msgid "Recipe"
msgstr ""
+msgid "Reconfigure"
+msgstr ""
+
msgid "Recover hidden stage"
msgstr ""
@@ -18339,6 +18944,9 @@ msgstr ""
msgid "Registration|Your profile"
msgstr ""
+msgid "Regulate approvals by authors/committers, based on compliance frameworks. Can be changed only at the instance level."
+msgstr ""
+
msgid "Rejected (closed)"
msgstr ""
@@ -18391,15 +18999,24 @@ msgstr ""
msgid "ReleaseAssetLinkType|Image"
msgstr ""
+msgid "ReleaseAssetLinkType|Images"
+msgstr ""
+
msgid "ReleaseAssetLinkType|Other"
msgstr ""
msgid "ReleaseAssetLinkType|Package"
msgstr ""
+msgid "ReleaseAssetLinkType|Packages"
+msgstr ""
+
msgid "ReleaseAssetLinkType|Runbook"
msgstr ""
+msgid "ReleaseAssetLinkType|Runbooks"
+msgstr ""
+
msgid "Releases"
msgstr ""
@@ -18412,6 +19029,9 @@ msgstr ""
msgid "Releases documentation"
msgstr ""
+msgid "Releases|New Release"
+msgstr ""
+
msgid "Release|Something went wrong while getting the release details"
msgstr ""
@@ -18484,6 +19104,9 @@ msgstr ""
msgid "Remove fork relationship"
msgstr ""
+msgid "Remove from batch"
+msgstr ""
+
msgid "Remove from board"
msgstr ""
@@ -18673,6 +19296,12 @@ msgstr ""
msgid "Replication"
msgstr ""
+msgid "Replication enabled"
+msgstr ""
+
+msgid "Replication paused"
+msgstr ""
+
msgid "Reply by email"
msgstr ""
@@ -18700,15 +19329,15 @@ msgstr ""
msgid "Reported %{timeAgo} by %{reportedBy}"
msgstr ""
-msgid "Reported Resource Changes: %{addNum} to add, %{changeNum} to change, %{deleteNum} to delete"
-msgstr ""
-
msgid "Reporter"
msgstr ""
msgid "Reporting"
msgstr ""
+msgid "Reports"
+msgstr ""
+
msgid "Reports|%{combinedString} and %{resolvedString}"
msgstr ""
@@ -18749,6 +19378,9 @@ msgstr ""
msgid "Reports|Failure"
msgstr ""
+msgid "Reports|Identifier"
+msgstr ""
+
msgid "Reports|Metrics reports are loading"
msgstr ""
@@ -18761,6 +19393,9 @@ msgstr ""
msgid "Reports|Metrics reports failed loading results"
msgstr ""
+msgid "Reports|Scanner"
+msgstr ""
+
msgid "Reports|Severity"
msgstr ""
@@ -18809,15 +19444,33 @@ msgstr ""
msgid "Repository cleanup has started. You will receive an email once the cleanup operation is complete."
msgstr ""
+msgid "Repository files count over the limit"
+msgstr ""
+
+msgid "Repository has an invalid default branch name."
+msgstr ""
+
+msgid "Repository has more than one branch."
+msgstr ""
+
msgid "Repository has no locks."
msgstr "Repozytorium nie ma blokad."
+msgid "Repository has tags."
+msgstr ""
+
msgid "Repository maintenance"
msgstr ""
msgid "Repository mirroring"
msgstr ""
+msgid "Repository must contain at least 1 file."
+msgstr ""
+
+msgid "Repository size is above the limit."
+msgstr ""
+
msgid "Repository static objects"
msgstr ""
@@ -18827,7 +19480,7 @@ msgstr ""
msgid "Repository sync capacity"
msgstr ""
-msgid "Repository: %{counter_repositories} / Wikis: %{counter_wikis} / Build Artifacts: %{counter_build_artifacts} / LFS: %{counter_lfs_objects}"
+msgid "Repository: %{counter_repositories} / Wikis: %{counter_wikis} / Build Artifacts: %{counter_build_artifacts} / LFS: %{counter_lfs_objects} / Snippets: %{counter_snippets}"
msgstr ""
msgid "RepositorySettingsAccessLevel|Select"
@@ -18839,6 +19492,9 @@ msgstr ""
msgid "Request Headers"
msgstr ""
+msgid "Request details"
+msgstr ""
+
msgid "Request parameter %{param} is missing."
msgstr ""
@@ -18949,6 +19605,9 @@ msgstr ""
msgid "Reset template"
msgstr ""
+msgid "Reset to project defaults"
+msgstr ""
+
msgid "Resetting the authorization key for this project will require updating the authorization key in every alert source it is enabled in."
msgstr ""
@@ -19045,9 +19704,6 @@ msgstr ""
msgid "Resume"
msgstr ""
-msgid "Resume replication"
-msgstr ""
-
msgid "Resync"
msgstr ""
@@ -19692,6 +20348,9 @@ msgstr ""
msgid "SecurityReports|False positive"
msgstr ""
+msgid "SecurityReports|Group Security Dashboard"
+msgstr ""
+
msgid "SecurityReports|Hide dismissed"
msgstr ""
@@ -19746,10 +20405,13 @@ msgstr ""
msgid "SecurityReports|Remove project from dashboard"
msgstr ""
-msgid "SecurityReports|Report type"
+msgid "SecurityReports|Return to dashboard"
+msgstr ""
+
+msgid "SecurityReports|Scan details"
msgstr ""
-msgid "SecurityReports|Return to dashboard"
+msgid "SecurityReports|Scanner type"
msgstr ""
msgid "SecurityReports|Security Dashboard"
@@ -19806,6 +20468,9 @@ msgstr ""
msgid "SecurityReports|There was an error while generating the report."
msgstr ""
+msgid "SecurityReports|Unable to add %{invalidProjectsMessage}"
+msgstr ""
+
msgid "SecurityReports|Unable to add %{invalidProjects}"
msgstr ""
@@ -19920,6 +20585,9 @@ msgstr ""
msgid "Select due date"
msgstr ""
+msgid "Select file"
+msgstr ""
+
msgid "Select group or project"
msgstr ""
@@ -19983,9 +20651,6 @@ msgstr ""
msgid "Select the branch you want to set as the default for this project. All merge requests and commits will automatically be made against this branch unless you specify a different one."
msgstr ""
-msgid "Select the configured storage available for new repositories to be placed on."
-msgstr ""
-
msgid "Select the custom project template source group."
msgstr ""
@@ -20178,12 +20843,6 @@ msgstr ""
msgid "Service Desk is enabled but not yet active"
msgstr ""
-msgid "Service Desk is off"
-msgstr ""
-
-msgid "Service Desk is on"
-msgstr ""
-
msgid "Service Templates"
msgstr ""
@@ -20271,6 +20930,15 @@ msgstr ""
msgid "Set the milestone to %{milestone_reference}."
msgstr ""
+msgid "Set the number of concurrent requests this secondary node will make to the primary node while backfilling."
+msgstr ""
+
+msgid "Set the synchronization and verification capacity for the secondary node."
+msgstr ""
+
+msgid "Set the timeout in seconds to send a secondary node status to the primary and IPs allowed for the secondary nodes."
+msgstr ""
+
msgid "Set time estimate"
msgstr ""
@@ -20310,6 +20978,9 @@ msgstr ""
msgid "Set weight to %{weight}."
msgstr ""
+msgid "Set what should be replicated by choosing specific projects or groups by the secondary node."
+msgstr ""
+
msgid "SetPasswordToCloneLink|set a password"
msgstr ""
@@ -20436,6 +21107,9 @@ msgstr ""
msgid "Show complete raw log"
msgstr ""
+msgid "Show details"
+msgstr ""
+
msgid "Show file browser"
msgstr ""
@@ -20445,10 +21119,13 @@ msgstr ""
msgid "Show latest version"
msgstr ""
+msgid "Show list"
+msgstr ""
+
msgid "Show me everything"
msgstr ""
-msgid "Show me how"
+msgid "Show me how to add a pipeline"
msgstr ""
msgid "Show me more advanced stuff"
@@ -20501,9 +21178,6 @@ msgstr ""
msgid "Showing all issues"
msgstr ""
-msgid "Showing all labels"
-msgstr ""
-
msgid "Showing last %{size} of log -"
msgstr ""
@@ -20615,9 +21289,6 @@ msgstr ""
msgid "Skip outdated deployment jobs"
msgstr ""
-msgid "Skip this for now"
-msgstr ""
-
msgid "Skipped"
msgstr ""
@@ -20762,6 +21433,9 @@ msgstr ""
msgid "Something went wrong while adding your award. Please try again."
msgstr ""
+msgid "Something went wrong while applying the batch of suggestions. Please try again."
+msgstr ""
+
msgid "Something went wrong while applying the suggestion. Please try again."
msgstr ""
@@ -20864,10 +21538,13 @@ msgstr ""
msgid "Something went wrong while updating a requirement."
msgstr ""
+msgid "Something went wrong while updating assignees"
+msgstr ""
+
msgid "Something went wrong while updating your list settings"
msgstr ""
-msgid "Something went wrong with your automatic subscription renewal"
+msgid "Something went wrong with your automatic subscription renewal."
msgstr ""
msgid "Something went wrong, unable to add %{project} to dashboard"
@@ -20885,6 +21562,9 @@ msgstr "Coś poszło nie tak, nie można usunąć projektu"
msgid "Something went wrong, unable to search projects"
msgstr ""
+msgid "Something went wrong."
+msgstr ""
+
msgid "Something went wrong. Please try again."
msgstr ""
@@ -21068,6 +21748,9 @@ msgstr ""
msgid "Source code"
msgstr ""
+msgid "Source code (%{fileExtension})"
+msgstr ""
+
msgid "Source is not available"
msgstr ""
@@ -21152,6 +21835,9 @@ msgstr "Angaże Squash"
msgid "Stack trace"
msgstr ""
+msgid "Stacktrace snippet"
+msgstr ""
+
msgid "Stage"
msgstr ""
@@ -21257,6 +21943,9 @@ msgstr ""
msgid "Start thread & reopen %{noteable_name}"
msgstr ""
+msgid "Start using Directed Acyclic Graphs (DAG)"
+msgstr ""
+
msgid "Start your Free Gold Trial"
msgstr ""
@@ -21719,6 +22408,21 @@ msgstr ""
msgid "SuggestedColors|Very pale orange"
msgstr ""
+msgid "Suggestion is not applicable as the suggestion was not found."
+msgstr ""
+
+msgid "Suggestions are not applicable as one or more suggestions were not found."
+msgstr ""
+
+msgid "Suggestions are not applicable as their lines cannot overlap."
+msgstr ""
+
+msgid "Suggestions must all be on the same branch."
+msgstr ""
+
+msgid "Suggestions that change line count can't be added to batches, yet."
+msgstr ""
+
msgid "Suggestions:"
msgstr ""
@@ -21761,9 +22465,6 @@ msgstr ""
msgid "Synchronization disabled"
msgstr ""
-msgid "Synchronization of container repositories is disabled."
-msgstr ""
-
msgid "System"
msgstr ""
@@ -21944,6 +22645,38 @@ msgstr ""
msgid "Terms of Service and Privacy Policy"
msgstr "Warunki Użytkowania oraz Polityka Prywatności"
+msgid "Terraform|%{number} Terraform report failed to generate"
+msgid_plural "Terraform|%{number} Terraform reports failed to generate"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "Terraform|%{number} Terraform report was generated in your pipelines"
+msgid_plural "Terraform|%{number} Terraform reports were generated in your pipelines"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "Terraform|A Terraform report failed to generate."
+msgstr ""
+
+msgid "Terraform|A Terraform report was generated in your pipelines."
+msgstr ""
+
+msgid "Terraform|Generating the report caused an error."
+msgstr ""
+
+msgid "Terraform|Reported Resource Changes: %{addNum} to add, %{changeNum} to change, %{deleteNum} to delete"
+msgstr ""
+
+msgid "Terraform|The Terraform report %{name} failed to generate."
+msgstr ""
+
+msgid "Terraform|The Terraform report %{name} was generated in your pipelines."
+msgstr ""
+
msgid "Test"
msgstr ""
@@ -21972,7 +22705,7 @@ msgstr ""
msgid "TestHooks|Ensure the project has CI pipelines."
msgstr ""
-msgid "TestHooks|Ensure the project has at least one commit."
+msgid "TestHooks|Ensure the project has deployments."
msgstr ""
msgid "TestHooks|Ensure the project has issues."
@@ -22075,7 +22808,7 @@ msgstr ""
msgid "The Prometheus server responded with \"bad request\". Please check your queries are correct and are supported in your Prometheus version. %{documentationLink}"
msgstr ""
-msgid "The URL defined on the primary node that secondary nodes should use to contact it. Defaults to URL"
+msgid "The URL defined on the primary node that secondary nodes should use to contact it."
msgstr ""
msgid "The URL to use for connecting to Elasticsearch. Use a comma-separated list to support clustering (e.g., \"http://localhost:9200, http://localhost:9201\")."
@@ -22084,6 +22817,9 @@ msgstr ""
msgid "The X509 Certificate to use when mutual TLS is required to communicate with the external authorization service. If left blank, the server certificate is still validated when accessing over HTTPS."
msgstr "Certyfikat X509 służy do użycia w przypadku, gdy do komunikacji z zewnętrzną usługą autoryzacji jest wymagany wspólny TLS. Jeśli pozostanie puste, certyfikat serwera będzie nadal sprawdzany podczas uzyskiwania dostępu za pośrednictwem protokołu HTTPS."
+msgid "The above settings apply to all projects with the selected compliance framework(s)."
+msgstr ""
+
msgid "The amount of seconds after which a request to get a secondary node status will time out."
msgstr ""
@@ -22385,9 +23121,6 @@ msgstr ""
msgid "The total stage shows the time it takes between creating an issue and deploying the code to production. The data will be automatically added once you have completed the full idea to production cycle."
msgstr ""
-msgid "The unique identifier for the Geo node. Must match %{geoNodeName} if it is set in gitlab.rb, otherwise it must match %{externalUrl} with a trailing slash"
-msgstr ""
-
msgid "The update action will time out after %{number_of_minutes} minutes. For big repositories, use a clone/push combination."
msgstr "Akcja aktualizacji zakończy się po upływie %{number_of_minutes} minut. W przypadku dużych repozytoriów użyj kombinacji klonuj/pchnij."
@@ -22412,9 +23145,6 @@ msgstr "Mapa użytkownika to mapowanie użytkowników FogBugz, którzy uczestnic
msgid "The user you are trying to deactivate has been active in the past %{minimum_inactive_days} days and cannot be deactivated"
msgstr ""
-msgid "The user-facing URL of the Geo node"
-msgstr ""
-
msgid "The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6."
msgstr ""
@@ -22505,6 +23235,9 @@ msgstr ""
msgid "There was a problem fetching project branches."
msgstr ""
+msgid "There was a problem fetching project tags."
+msgstr ""
+
msgid "There was a problem fetching project users."
msgstr ""
@@ -22556,6 +23289,9 @@ msgstr ""
msgid "There was an error fetching the %{replicableType}"
msgstr ""
+msgid "There was an error fetching the Geo Settings"
+msgstr ""
+
msgid "There was an error fetching the Node's Groups"
msgstr ""
@@ -22676,6 +23412,12 @@ msgstr ""
msgid "This %{issuable} is locked. Only <strong>project members</strong> can comment."
msgstr ""
+msgid "This %{noteableTypeText} is %{confidentialLinkStart}confidential%{linkEnd} and %{lockedLinkStart}locked%{linkEnd}."
+msgstr ""
+
+msgid "This %{noteableTypeText} is locked."
+msgstr ""
+
msgid "This %{viewer} could not be displayed because %{reason}. You can %{options} instead."
msgstr ""
@@ -22694,6 +23436,9 @@ msgstr ""
msgid "This action can lead to data loss. To prevent accidental actions we ask you to confirm your intention."
msgstr ""
+msgid "This also resolves all related threads"
+msgstr ""
+
msgid "This also resolves the discussion"
msgstr ""
@@ -22784,6 +23529,9 @@ msgstr ""
msgid "This epic does not exist or you don't have sufficient permission."
msgstr ""
+msgid "This feature is currently in beta. We invite you to %{linkStart}give feedback%{linkEnd}."
+msgstr ""
+
msgid "This feature requires local storage to be enabled"
msgstr ""
@@ -22817,7 +23565,7 @@ msgstr ""
msgid "This is a Work in Progress"
msgstr ""
-msgid "This is a confidential issue."
+msgid "This is a confidential %{noteableTypeText}."
msgstr ""
msgid "This is a delayed job to run in %{remainingTime}"
@@ -22844,9 +23592,6 @@ msgstr ""
msgid "This is your current session"
msgstr ""
-msgid "This issue is %{confidentialLinkStart}confidential%{linkEnd} and %{lockedLinkStart}locked%{linkEnd}."
-msgstr ""
-
msgid "This issue is confidential"
msgstr ""
@@ -22856,9 +23601,6 @@ msgstr ""
msgid "This issue is in a child epic of the filtered epic"
msgstr ""
-msgid "This issue is locked."
-msgstr ""
-
msgid "This job depends on other jobs with expired/erased artifacts: %{invalid_dependencies}"
msgstr ""
@@ -22958,6 +23700,9 @@ msgstr ""
msgid "This license has already expired."
msgstr ""
+msgid "This link points to external content"
+msgstr ""
+
msgid "This may expose confidential information as the selected fork is in another namespace that can have other members."
msgstr ""
@@ -22985,6 +23730,9 @@ msgstr ""
msgid "This page will be removed in a future release."
msgstr ""
+msgid "This pipeline does not use the %{codeStart}needs%{codeEnd} keyword and can't be represented as a directed acyclic graph."
+msgstr ""
+
msgid "This pipeline makes use of a predefined CI/CD configuration enabled by %{strongStart}Auto DevOps.%{strongEnd}"
msgstr ""
@@ -23213,6 +23961,9 @@ msgstr ""
msgid "Time from last commit to merge"
msgstr ""
+msgid "Time in seconds"
+msgstr ""
+
msgid "Time in seconds GitLab will wait for a response from the external service. When the service does not respond in time, access will be denied."
msgstr ""
@@ -23638,9 +24389,15 @@ msgstr ""
msgid "Total artifacts size: %{total_size}"
msgstr ""
+msgid "Total cores (CPUs)"
+msgstr ""
+
msgid "Total issues"
msgstr ""
+msgid "Total memory (GB)"
+msgstr ""
+
msgid "Total test time for all commits/merges"
msgstr ""
@@ -23842,6 +24599,9 @@ msgstr ""
msgid "U2F only works with HTTPS-enabled websites. Contact your administrator for more details."
msgstr ""
+msgid "UI Development Kit"
+msgstr ""
+
msgid "URL"
msgstr ""
@@ -23914,9 +24674,6 @@ msgstr ""
msgid "Unable to load the merge request widget. Try reloading the page."
msgstr ""
-msgid "Unable to process group import file"
-msgstr ""
-
msgid "Unable to resolve"
msgstr ""
@@ -23932,6 +24689,9 @@ msgstr ""
msgid "Unable to sign you in to the group with SAML due to \"%{reason}\""
msgstr ""
+msgid "Unable to suggest a path. Please refresh and try again."
+msgstr ""
+
msgid "Unable to update label prioritization at this time"
msgstr ""
@@ -23947,6 +24707,9 @@ msgstr ""
msgid "Unarchiving the project will restore people's ability to make changes to it. The repository can be committed to, and issues, comments, and other entities can be created. %{strong_start}Once active, this project shows up in the search and on the dashboard.%{strong_end}"
msgstr ""
+msgid "Unassign from commenting user"
+msgstr ""
+
msgid "Unblock"
msgstr ""
@@ -24022,6 +24785,9 @@ msgstr ""
msgid "Unreachable"
msgstr ""
+msgid "Unrecognized cluster type"
+msgstr ""
+
msgid "Unresolve"
msgstr ""
@@ -24082,6 +24848,9 @@ msgstr ""
msgid "Unsubscribes from this %{quick_action_target}."
msgstr ""
+msgid "Unsupported todo type passed. Supported todo types are: %{todo_types}"
+msgstr ""
+
msgid "Until"
msgstr ""
@@ -24109,6 +24878,9 @@ msgstr ""
msgid "Update approval rule"
msgstr ""
+msgid "Update approvers"
+msgstr ""
+
msgid "Update failed"
msgstr ""
@@ -24118,6 +24890,9 @@ msgstr ""
msgid "Update it"
msgstr ""
+msgid "Update iteration"
+msgstr ""
+
msgid "Update now"
msgstr ""
@@ -24163,7 +24938,7 @@ msgstr ""
msgid "Updated at"
msgstr ""
-msgid "Updated to"
+msgid "Updated to %{linkStart}chart v%{linkEnd}"
msgstr ""
msgid "Updating"
@@ -24193,6 +24968,9 @@ msgstr ""
msgid "Upload CSV file"
msgstr ""
+msgid "Upload License"
+msgstr ""
+
msgid "Upload New File"
msgstr ""
@@ -24274,16 +25052,19 @@ msgstr ""
msgid "UsageQuota|Pipelines"
msgstr ""
+msgid "UsageQuota|Purchase more storage"
+msgstr ""
+
msgid "UsageQuota|Repositories"
msgstr ""
msgid "UsageQuota|Repository"
msgstr ""
-msgid "UsageQuota|Storage"
+msgid "UsageQuota|Snippets"
msgstr ""
-msgid "UsageQuota|Storage usage:"
+msgid "UsageQuota|Storage"
msgstr ""
msgid "UsageQuota|This namespace has no projects which use shared runners"
@@ -24316,6 +25097,12 @@ msgstr ""
msgid "UsageQuota|Wikis"
msgstr ""
+msgid "UsageQuota|You used: %{usage} %{limit}"
+msgstr ""
+
+msgid "UsageQuota|out of %{formattedLimit} of your namespace storage"
+msgstr ""
+
msgid "Use %{code_start}::%{code_end} to create a %{link_start}scoped label set%{link_end} (eg. %{code_start}priority::1%{code_end})"
msgstr ""
@@ -24382,6 +25169,9 @@ msgstr ""
msgid "User IDs"
msgstr ""
+msgid "User Lists can only be created and modified with %{linkStart}the API%{linkEnd}"
+msgstr ""
+
msgid "User OAuth applications"
msgstr ""
@@ -24406,6 +25196,9 @@ msgstr ""
msgid "User key was successfully removed."
msgstr ""
+msgid "User list %{name} will be removed. Are you sure?"
+msgstr ""
+
msgid "User map"
msgstr ""
@@ -24427,148 +25220,10 @@ msgstr ""
msgid "User was successfully updated."
msgstr ""
-msgid "UserOnboardingTour|%{activeTour}/%{totalTours}"
-msgstr ""
-
-msgid "UserOnboardingTour|%{completed}/%{total} steps completed"
-msgstr ""
-
-msgid "UserOnboardingTour|%{emphasisStart}Well done!%{emphasisEnd}%{lineBreak}%{lineBreak}That's it for our guided tour, congratulations for making it all the way to the end!%{lineBreak}%{lineBreak}We hope this gave you a good overview of GitLab and how it can help you. We'll now show you how to create your own project and invite your colleagues."
-msgstr ""
-
-msgid "UserOnboardingTour|Adding other members to a project is done through Project Settings. Click on %{emphasisStart}Settings%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Alright, that's it for Commits. Let's take a look at the %{emphasisStart}Branches%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Awesome! Now click on %{emphasisStart}Members%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Click on one of the %{emphasisStart}Compare%{emphasisEnd} buttons to compare a branch to master."
-msgstr ""
-
-msgid "UserOnboardingTour|Click on one of the %{emphasisStart}pipeline IDs%{emphasisEnd} to see the details of a pipeline."
-msgstr ""
-
-msgid "UserOnboardingTour|Click to open the latest commit to see its details."
-msgstr ""
-
-msgid "UserOnboardingTour|Close 'Learn GitLab'"
-msgstr ""
-
-msgid "UserOnboardingTour|Commits are shown in chronological order and can be filtered by the commit message or by the branch."
-msgstr ""
-
-msgid "UserOnboardingTour|Create a project"
-msgstr ""
-
-msgid "UserOnboardingTour|Exit 'Learn GitLab'"
-msgstr ""
-
-msgid "UserOnboardingTour|Got it"
-msgstr ""
-
-msgid "UserOnboardingTour|Great job! %{clapHands} We hope the tour was helpful and that you learned how to use GitLab.%{lineBreak}%{lineBreak}We'd love to get your feedback on this tour.%{lineBreak}%{lineBreak}%{emphasisStart}How helpful would you say this guided tour was?%{emphasisEnd}%{lineBreak}%{lineBreak}"
-msgstr ""
-
-msgid "UserOnboardingTour|Guided GitLab Tour"
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can compare the changes of this branch to another one. Changes are divided by files so that it's easier to see what was changed where."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can create a project from scratch, start with a template or import a repository from other platforms. Whatever you choose, we'll guide you through the process.%{lineBreak}%{lineBreak}Fill in your new project information and click on %{emphasisStart}Create Project%{emphasisEnd} to progress to the next step."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can see the breakdown of the pipelines: its stages and jobs in each of the stages and their status.%{lineBreak}%{lineBreak}Our CI/CD pipelines are quite complex, most of our users have fewer and simpler pipelines."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can see the current members of the project (just you at the moment) and invite new members.%{lineBreak}%{lineBreak}You can invite multiple members at once (existing GitLab users or invite by email) and you can also set their roles and permissions.%{lineBreak}%{lineBreak}Add a few members and click on %{emphasisStart}Add to project%{emphasisEnd} to complete this step."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can see what changes were made with this commit, on what branch and if there's a related merge request. The status of the pipeline will also show up if CI/CD is set up.%{lineBreak}%{lineBreak}You can also comment on the lines of code that were changed and start a discussion with your colleagues!"
-msgstr ""
-
-msgid "UserOnboardingTour|Here's an overview of branches in the %{emphasisStart}%{projectName}%{emphasisEnd} project. They're split into Active and Stale.%{lineBreak}%{lineBreak}From here, you can create a new merge request from a branch, or compare the branch to any other branch in the project. By default, it will compare it to the master branch."
-msgstr ""
-
-msgid "UserOnboardingTour|Invite colleagues"
-msgstr ""
-
-msgid "UserOnboardingTour|Issues are great for communicating and keeping track of progress in GitLab. These are all issues that are open in the %{emphasisStart}%{projectName}%{emphasisEnd}.%{lineBreak}%{lineBreak}You can help us improve GitLab by contributing work to issues that are labeled <span class=\"badge color-label accept-mr-label\">Accepting merge requests</span>.%{lineBreak}%{lineBreak}This list can be filtered by labels, milestones, assignees, authors... We'll show you how it looks when the list is filtered by a label."
-msgstr ""
-
-msgid "UserOnboardingTour|Learn GitLab"
-msgstr ""
-
-msgid "UserOnboardingTour|Let's take a closer look at a merge request. Click on the title of one."
-msgstr ""
-
-msgid "UserOnboardingTour|Let's take a closer look at all the commits. Click on %{emphasisStart}Commits%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Let's take a closer look at the repository of this project. Click on %{emphasisStart}Repository%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|No thanks"
-msgstr ""
-
-msgid "UserOnboardingTour|Ok, let's go"
-msgstr ""
-
-msgid "UserOnboardingTour|Ok, show me"
-msgstr ""
-
-msgid "UserOnboardingTour|Open one of the issues by clicking on its title."
-msgstr ""
-
-msgid "UserOnboardingTour|Restart this step"
-msgstr ""
-
-msgid "UserOnboardingTour|Skip this step"
-msgstr ""
-
-msgid "UserOnboardingTour|Sweet! Your project was created and is ready to be used.%{lineBreak}%{lineBreak}You can start adding files to the repository or clone it. One last thing we want to show you is how to invite your colleagues to your new project."
-msgstr ""
-
-msgid "UserOnboardingTour|Take a look. Here's a nifty menu for quickly creating issues, merge requests, snippets, projects and groups. Click on it and select \"New project\" from the \"GitLab\" section to get started."
-msgstr ""
-
-msgid "UserOnboardingTour|Thanks for taking the guided tour. Remember, if you want to go through it again, you can start %{emphasisStart}Learn GitLab%{emphasisEnd} in the help menu on the top right."
-msgstr ""
-
-msgid "UserOnboardingTour|Thanks for the feedback! %{thumbsUp}"
-msgstr ""
-
-msgid "UserOnboardingTour|That's it for issues. Let'st take a look at %{emphasisStart}Merge Requests%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|That's it for merge requests. Now for the final part of this guided tour - the %{emphasisStart}CI/CD%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|That's it for the Repository. Let's take a look at the %{emphasisStart}Issues%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|The structure of this page is very similar to issues. Status, description, discussion and the sidebar are all here.%{lineBreak}%{lineBreak}But take a look below the description and you'll notice that there's more information about the merge request, the CI/CD pipeline and the options for approving it.%{lineBreak}%{lineBreak}Alongside the discussion you can also see more information about commits in this merge request, the status of pipelines and review all changes that were made."
-msgstr ""
-
-msgid "UserOnboardingTour|There's a lot of information here but don't worry, we'll go through it.%{lineBreak}%{lineBreak}On the top you can see the status of the issue and when it was opened and by whom. Directly below it is the issue description and below that are other %{emphasisStart}related issues%{emphasisEnd} and %{emphasisStart}merge requests%{emphasisEnd} (if any). Then below that is the %{emphasisStart}discussion%{emphasisEnd}, that's where most of the communication happens.%{lineBreak}%{lineBreak}On the right, there's a sidebar where you can view/change the %{emphasisStart}assignee, milestone, due date, labels, weight%{emphasisEnd}, etc."
+msgid "UserList|Delete %{name}?"
msgstr ""
-msgid "UserOnboardingTour|These are all the CI/CD pipelines we have for our %{emphasisStart}%{projectName}%{emphasisEnd} project.%{lineBreak}%{lineBreak}Here you can see the status of each pipeline, for what commit it's running for, its stages and the status for them."
-msgstr ""
-
-msgid "UserOnboardingTour|These are all the issues that are available for community contributions. Let's take a closer look at one of them."
-msgstr ""
-
-msgid "UserOnboardingTour|This is an overview of all merge requests in this project. Similarly to the issues overview it can be filtered down by things like labels, milestones, authors, assignees, etc."
-msgstr ""
-
-msgid "UserOnboardingTour|This is the repository for the %{emphasisStart}%{projectName}%{emphasisEnd} project. All our code is stored here. Feel free to explore and take a closer look at folders and files.%{lineBreak}%{lineBreak}Above the file structure you can see the latest commit, who the author is and the status of the CI/CD pipeline.%{lineBreak}%{lineBreak}If you scroll down below the file structure, you'll find the Readme of this project. This is defined in the README.md file at the root of the repository."
-msgstr ""
-
-msgid "UserOnboardingTour|Welcome to the project overview of the %{emphasisStart}%{projectName}%{emphasisEnd} project. This is the project that we use to work on GitLab. At first, a project seems like a simple repository, but at GitLab, a project is so much more.%{lineBreak}%{lineBreak}You can create projects for hosting your codebase, use it as an issue tracker, collaborate on code, and continuously build, test, and deploy your app with built-in GitLab CI/CD."
+msgid "UserList|created %{timeago}"
msgstr ""
msgid "UserProfile|Activity"
@@ -24721,6 +25376,9 @@ msgstr ""
msgid "UsersSelect|Unassigned"
msgstr ""
+msgid "Using %{codeStart}needs%{codeEnd} allows jobs to run before their stage is reached, as soon as their individual dependencies are met, which speeds up your pipelines."
+msgstr ""
+
msgid "Using %{code_start}::%{code_end} denotes a %{link_start}scoped label set%{link_end}"
msgstr ""
@@ -24805,9 +25463,6 @@ msgstr ""
msgid "Versions"
msgstr ""
-msgid "Very helpful"
-msgstr ""
-
msgid "View Documentation"
msgstr ""
@@ -24960,6 +25615,9 @@ msgstr ""
msgid "VisibilityLevel|Unknown"
msgstr ""
+msgid "Visit settings page"
+msgstr ""
+
msgid "VisualReviewApp|%{stepStart}Step 1%{stepEnd}. Copy the following script:"
msgstr ""
@@ -25053,6 +25711,9 @@ msgstr ""
msgid "VulnerabilityManagement|Something went wrong while trying to delete the comment. Please try again later."
msgstr ""
+msgid "VulnerabilityManagement|Something went wrong while trying to refresh the vulnerability. Please try again later."
+msgstr ""
+
msgid "VulnerabilityManagement|Something went wrong while trying to retrieve the vulnerability history. Please try again later."
msgstr ""
@@ -25095,6 +25756,9 @@ msgstr ""
msgid "Vulnerability|Class"
msgstr ""
+msgid "Vulnerability|Crash Address"
+msgstr ""
+
msgid "Vulnerability|Description"
msgstr ""
@@ -25170,9 +25834,6 @@ msgstr ""
msgid "We couldn't reach the Prometheus server. Either the server no longer exists or the configuration details need updating."
msgstr ""
-msgid "We created a short guided tour that will help you learn the basics of GitLab and how it will help you be better at your job. It should only take a couple of minutes. You will be guided by two types of helpers, best recognized by their color."
-msgstr ""
-
msgid "We detected potential spam in the %{humanized_resource_name}. Please solve the reCAPTCHA to proceed."
msgstr ""
@@ -25206,6 +25867,9 @@ msgstr ""
msgid "We've found no vulnerabilities"
msgstr ""
+msgid "Web Application Firewall"
+msgstr ""
+
msgid "Web IDE"
msgstr ""
@@ -25266,9 +25930,6 @@ msgstr ""
msgid "Welcome to GitLab.com<br>@%{name}!"
msgstr ""
-msgid "Welcome to the Guided GitLab Tour"
-msgstr ""
-
msgid "Welcome to the guided GitLab tour"
msgstr ""
@@ -25318,9 +25979,6 @@ msgstr ""
msgid "While it's rare to have no vulnerabilities, it can happen. In any event, we ask that you please double check your settings to make sure you've set up your dashboard correctly."
msgstr ""
-msgid "White helpers give contextual information."
-msgstr ""
-
msgid "Who can be an approver?"
msgstr ""
@@ -25366,12 +26024,18 @@ msgstr ""
msgid "WikiEmptyIssueMessage|Suggest wiki improvement"
msgstr ""
+msgid "WikiEmptyIssueMessage|You must be a group member in order to add wiki pages. If you have suggestions for how to improve the wiki for this group, consider opening an issue in the %{issues_link}."
+msgstr ""
+
msgid "WikiEmptyIssueMessage|You must be a project member in order to add wiki pages. If you have suggestions for how to improve the wiki for this project, consider opening an issue in the %{issues_link}."
msgstr ""
msgid "WikiEmptyIssueMessage|issue tracker"
msgstr ""
+msgid "WikiEmpty|A wiki is where you can store all the details about your group. This can include why you've created it, its principles, how to use it, and so on."
+msgstr ""
+
msgid "WikiEmpty|A wiki is where you can store all the details about your project. This can include why you've created it, its principles, how to use it, and so on."
msgstr ""
@@ -25381,12 +26045,21 @@ msgstr ""
msgid "WikiEmpty|Suggest wiki improvement"
msgstr ""
+msgid "WikiEmpty|The wiki lets you write documentation for your group"
+msgstr ""
+
msgid "WikiEmpty|The wiki lets you write documentation for your project"
msgstr ""
+msgid "WikiEmpty|This group has no wiki pages"
+msgstr ""
+
msgid "WikiEmpty|This project has no wiki pages"
msgstr ""
+msgid "WikiEmpty|You must be a group member in order to add wiki pages."
+msgstr ""
+
msgid "WikiEmpty|You must be a project member in order to add wiki pages."
msgstr ""
@@ -25663,13 +26336,13 @@ msgstr ""
msgid "You can now export your security dashboard to a CSV report."
msgstr ""
-msgid "You can now submit a merge request to get this change into the original branch."
+msgid "You can now manage alert endpoint configuration in the Alerts section on the Operations settings page. Fields on this page have been deprecated."
msgstr ""
-msgid "You can now submit a merge request to get this change into the original project."
+msgid "You can now submit a merge request to get this change into the original branch."
msgstr ""
-msgid "You can only add files when you are on a branch"
+msgid "You can now submit a merge request to get this change into the original project."
msgstr ""
msgid "You can only edit files when you are on a branch"
@@ -25891,7 +26564,7 @@ msgstr ""
msgid "You need to upload a Google Takeout archive."
msgstr ""
-msgid "You reached %{usage_in_percent} of %{namespace_name}'s capacity (%{used_storage} of %{storage_limit})"
+msgid "You reached %{usage_in_percent} of %{namespace_name}'s storage capacity (%{used_storage} of %{storage_limit})"
msgstr ""
msgid "You tried to fork %{link_to_the_project} but it failed for the following reason:"
@@ -26194,13 +26867,13 @@ msgstr ""
msgid "Your subscription expired!"
msgstr ""
-msgid "Your subscription has been downgraded"
+msgid "Your subscription has been downgraded."
msgstr ""
-msgid "Your subscription will automatically renew in %{remaining_days}"
+msgid "Your subscription will automatically renew in %{remaining_days}."
msgstr ""
-msgid "Your subscription will expire in %{remaining_days}"
+msgid "Your subscription will expire in %{remaining_days}."
msgstr ""
msgid "Zoom meeting added"
@@ -26282,9 +26955,6 @@ msgstr ""
msgid "branch name"
msgstr ""
-msgid "build pipeline reference mismatch"
-msgstr ""
-
msgid "by"
msgstr ""
@@ -26303,6 +26973,9 @@ msgstr ""
msgid "cannot block others"
msgstr ""
+msgid "cannot contain HTML/XML tags, including any word between angle brackets (<,>)."
+msgstr ""
+
msgid "cannot include leading slash or directory traversal."
msgstr ""
@@ -26333,65 +27006,6 @@ msgstr ""
msgid "ciReport|%{remainingPackagesCount} more"
msgstr ""
-msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability"
-msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities"
-msgstr[0] ""
-msgstr[1] ""
-msgstr[2] ""
-msgstr[3] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability for the source branch only"
-msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities for the source branch only"
-msgstr[0] ""
-msgstr[1] ""
-msgstr[2] ""
-msgstr[3] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerability"
-msgid_plural "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerabilities"
-msgstr[0] ""
-msgstr[1] ""
-msgstr[2] ""
-msgstr[3] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new vulnerability"
-msgid_plural "ciReport|%{reportType} %{status} detected %{newCount} new vulnerabilities"
-msgstr[0] ""
-msgstr[1] ""
-msgstr[2] ""
-msgstr[3] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities for the source branch only"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{fixedCount} fixed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} vulnerability for the source branch only"
-msgid_plural "ciReport|%{reportType} %{status} detected %{newCount} vulnerabilities for the source branch only"
-msgstr[0] ""
-msgstr[1] ""
-msgstr[2] ""
-msgstr[3] ""
-
-msgid "ciReport|%{reportType} %{status} detected no new vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected no vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected no vulnerabilities for the source branch only"
-msgstr ""
-
msgid "ciReport|%{reportType} is loading"
msgstr ""
@@ -26410,7 +27024,7 @@ msgstr ""
msgid "ciReport|All projects"
msgstr ""
-msgid "ciReport|All report types"
+msgid "ciReport|All scanner types"
msgstr ""
msgid "ciReport|All severities"
@@ -26434,6 +27048,9 @@ msgstr ""
msgid "ciReport|Container scanning detects known vulnerabilities in your docker images."
msgstr ""
+msgid "ciReport|Coverage Fuzzing"
+msgstr ""
+
msgid "ciReport|Create a merge request to implement this solution, or download and apply the patch manually."
msgstr ""
@@ -26594,6 +27211,9 @@ msgstr ""
msgid "customize"
msgstr ""
+msgid "data"
+msgstr ""
+
msgid "date must not be after 9999-12-31"
msgstr ""
@@ -26668,6 +27288,9 @@ msgstr ""
msgid "entries cannot contain HTML tags"
msgstr ""
+msgid "epic"
+msgstr ""
+
msgid "error"
msgstr ""
@@ -26689,9 +27312,6 @@ msgstr ""
msgid "expires on %{timebox_due_date}"
msgstr ""
-msgid "external_url"
-msgstr ""
-
msgid "failed"
msgstr ""
@@ -26729,9 +27349,6 @@ msgstr ""
msgid "from"
msgstr ""
-msgid "geo_node_name"
-msgstr ""
-
msgid "group"
msgstr ""
@@ -26753,9 +27370,6 @@ msgstr ""
msgid "https://your-bitbucket-server"
msgstr ""
-msgid "image"
-msgstr ""
-
msgid "image diff"
msgstr ""
@@ -26901,6 +27515,9 @@ msgstr ""
msgid "load it anyway"
msgstr ""
+msgid "loading"
+msgstr ""
+
msgid "locked by %{path_lock_user_name} %{created_at}"
msgstr ""
@@ -27031,9 +27648,6 @@ msgstr ""
msgid "mrWidget|Deployment statistics are not available currently"
msgstr ""
-msgid "mrWidget|Detect issues before deployment with a CI pipeline that continuously tests your code. We created a quick guide that will show you how to create one. Make your code more secure and more robust in just a minute."
-msgstr ""
-
msgid "mrWidget|Did not close"
msgstr ""
@@ -27196,6 +27810,9 @@ msgstr ""
msgid "mrWidget|To approve this merge request, please enter your password. This project requires all approvals to be authenticated."
msgstr ""
+msgid "mrWidget|Use %{linkStart}CI pipelines to test your code%{linkEnd}, simply add a GitLab CI configuration file to your project. It only takes a minute to make your code more secure and robust."
+msgstr ""
+
msgid "mrWidget|When this merge request is ready, remove the WIP: prefix from the title to allow it to be merged"
msgstr ""
@@ -27232,6 +27849,9 @@ msgstr ""
msgid "must be greater than start date"
msgstr ""
+msgid "must contain only valid frameworks"
+msgstr ""
+
msgid "my-awesome-group"
msgstr ""
@@ -27244,6 +27864,9 @@ msgstr ""
msgid "needs to be between 10 minutes and 1 month"
msgstr ""
+msgid "never"
+msgstr ""
+
msgid "never expires"
msgstr ""
@@ -27271,6 +27894,9 @@ msgstr ""
msgid "nounSeries|%{firstItem} and %{lastItem}"
msgstr ""
+msgid "nounSeries|%{item}"
+msgstr ""
+
msgid "nounSeries|%{item}, %{nextItem}"
msgstr ""
@@ -27417,6 +28043,9 @@ msgstr ""
msgid "revised"
msgstr ""
+msgid "satisfied"
+msgstr ""
+
msgid "score"
msgstr ""
@@ -27447,6 +28076,9 @@ msgstr ""
msgid "severity|Unknown"
msgstr ""
+msgid "should be an array of %{object_name} objects"
+msgstr ""
+
msgid "should be greater than or equal to %{access} inherited membership from group %{group_name}"
msgstr ""
@@ -27501,10 +28133,13 @@ msgstr ""
msgid "suggestPipeline|2/2: Commit your changes"
msgstr ""
-msgid "suggestPipeline|Commit the changes and your pipeline will automatically run for the first time."
+msgid "suggestPipeline|Choose %{boldStart}Code Quality%{boldEnd} to add a pipeline that tests the quality of your code."
msgstr ""
-msgid "suggestPipeline|We recommend the %{boldStart}Code Quality%{boldEnd} template, which will add a report widget to your Merge Requests. This way you’ll learn about code quality degradations much sooner. %{footerStart} Goodbye technical debt! %{footerEnd}"
+msgid "suggestPipeline|The template is ready! You can now commit it to create your first pipeline."
+msgstr ""
+
+msgid "suggestPipeline|We’re adding a GitLab CI configuration file to add a pipeline to the project. You could create it manually, but we recommend that you start with a GitLab template that works out of the box."
msgstr ""
msgid "syntax is correct"
@@ -27564,6 +28199,9 @@ msgstr ""
msgid "uses Kubernetes clusters to deploy your code!"
msgstr ""
+msgid "v%{version} published %{timeAgo}"
+msgstr ""
+
msgid "verify ownership"
msgstr ""
diff --git a/locale/pt_BR/gitlab.po b/locale/pt_BR/gitlab.po
index 6ed3f217797..32d0f3395e0 100644
--- a/locale/pt_BR/gitlab.po
+++ b/locale/pt_BR/gitlab.po
@@ -2,7 +2,7 @@ msgid ""
msgstr ""
"Project-Id-Version: gitlab-ee\n"
"Report-Msgid-Bugs-To: \n"
-"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Last-Translator: \n"
"Language-Team: Portuguese, Brazilian\n"
"Language: pt_BR\n"
"MIME-Version: 1.0\n"
@@ -10,9 +10,11 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Crowdin-Project: gitlab-ee\n"
+"X-Crowdin-Project-ID: 288872\n"
"X-Crowdin-Language: pt-BR\n"
"X-Crowdin-File: /master/locale/gitlab.pot\n"
-"PO-Revision-Date: 2020-06-08 15:09\n"
+"X-Crowdin-File-ID: 6\n"
+"PO-Revision-Date: 2020-07-02 01:20\n"
msgid " %{start} to %{end}"
msgstr ""
@@ -197,6 +199,11 @@ msgid_plural "%d metrics"
msgstr[0] "%d métrica"
msgstr[1] "%d métricas"
+msgid "%d milestone"
+msgid_plural "%d milestones"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "%d minute"
msgid_plural "%d minutes"
msgstr[0] "%d minuto"
@@ -242,11 +249,6 @@ msgid_plural "%d unresolved threads"
msgstr[0] ""
msgstr[1] ""
-msgid "%d url scanned"
-msgid_plural "%d urls scanned"
-msgstr[0] ""
-msgstr[1] ""
-
msgid "%d vulnerability"
msgid_plural "%d vulnerabilities"
msgstr[0] ""
@@ -324,9 +326,15 @@ msgstr[1] "%{count} comentários pendentes"
msgid "%{count} related %{pluralized_subject}: %{links}"
msgstr ""
+msgid "%{dashboard_path} could not be found."
+msgstr ""
+
msgid "%{days} days until tags are automatically removed"
msgstr ""
+msgid "%{deployLinkStart}Use a template to deploy to ECS%{deployLinkEnd}, or use a docker image to %{commandsLinkStart}run AWS commands in GitLab CI/CD%{commandsLinkEnd}."
+msgstr ""
+
msgid "%{description}- Sentry event: %{errorUrl}- First seen: %{firstSeen}- Last seen: %{lastSeen} %{countLabel}: %{count}%{userCountLabel}: %{userCount}"
msgstr ""
@@ -369,12 +377,18 @@ msgstr "%{group_name} usa contas gerenciadas por grupo. Você precisa criar uma
msgid "%{host} sign-in from new location"
msgstr ""
-msgid "%{icon}You are about to add %{usersTag} people to the discussion. Proceed with caution."
-msgstr "%{icon}Você está prestes a adicionar %{usersTag} pessoas à discussão. Prossiga com cuidado."
+msgid "%{icon}You are about to add %{usersTag} people to the discussion. They will all receive a notification."
+msgstr ""
+
+msgid "%{integrations_link_start}Integrations%{link_end} enable you to make third-party applications part of your GitLab workflow. If the available integrations don't meet your needs, consider using a %{webhooks_link_start}webhook%{link_end}."
+msgstr ""
msgid "%{issuableType} will be removed! Are you sure?"
msgstr "%{issuableType} será removido! Você tem certeza?"
+msgid "%{issuesCount} issues in this group"
+msgstr ""
+
msgid "%{issuesSize} issues"
msgstr ""
@@ -384,7 +398,7 @@ msgstr ""
msgid "%{labelStart}Class:%{labelEnd} %{class}"
msgstr ""
-msgid "%{labelStart}Confidence:%{labelEnd} %{confidence}"
+msgid "%{labelStart}Evidence:%{labelEnd} %{evidence}"
msgstr ""
msgid "%{labelStart}File:%{labelEnd} %{file}"
@@ -402,6 +416,9 @@ msgstr ""
msgid "%{labelStart}Report Type:%{labelEnd} %{reportType}"
msgstr ""
+msgid "%{labelStart}Scanner:%{labelEnd} %{scanner}"
+msgstr ""
+
msgid "%{labelStart}Severity:%{labelEnd} %{severity}"
msgstr ""
@@ -426,9 +443,6 @@ msgstr ""
msgid "%{link_start}Read more%{link_end} about role permissions"
msgstr "%{link_start}Saiba mais%{link_end} sobre permissões"
-msgid "%{link} can be used for binding events when something is happening within the project."
-msgstr ""
-
msgid "%{listToShow}, and %{awardsListLength} more."
msgstr ""
@@ -462,6 +476,9 @@ msgstr "%{name} continha %{resultsString}"
msgid "%{name} found %{resultsString}"
msgstr "%{name} encontrou %{resultsString}"
+msgid "%{name} is already being used for another emoji"
+msgstr ""
+
msgid "%{name} is scheduled for %{action}"
msgstr ""
@@ -509,6 +526,9 @@ msgstr ""
msgid "%{primary} (%{secondary})"
msgstr ""
+msgid "%{ref} cannot be added: %{error}"
+msgstr ""
+
msgid "%{releases} release"
msgid_plural "%{releases} releases"
msgstr[0] ""
@@ -517,13 +537,37 @@ msgstr[1] ""
msgid "%{remaining_approvals} left"
msgstr ""
-msgid "%{retryButtonStart}Try again%{retryButtonEnd} or %{newFileButtonStart}attach a new file%{newFileButtonEnd}"
+msgid "%{reportType} %{status} detected %{critical} critical and %{high} high severity vulnerabilities out of %{total}."
+msgstr ""
+
+msgid "%{reportType} %{status} detected %{critical} critical and %{high} high severity vulnerabilities."
+msgstr ""
+
+msgid "%{reportType} %{status} detected %{critical} critical severity vulnerabilities out of %{total}."
msgstr ""
-msgid "%{screenreaderOnlyStart}Keyboard shorcuts%{screenreaderOnlyEnd} Disabled"
+msgid "%{reportType} %{status} detected %{critical} critical severity vulnerability."
+msgid_plural "%{reportType} %{status} detected %{critical} critical severity vulnerabilities."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{reportType} %{status} detected %{high} high severity vulnerabilities out of %{total}."
+msgstr ""
+
+msgid "%{reportType} %{status} detected %{high} high severity vulnerability."
+msgid_plural "%{reportType} %{status} detected %{high} high severity vulnerabilities."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{reportType} %{status} detected %{other} vulnerability."
+msgid_plural "%{reportType} %{status} detected %{other} vulnerabilities."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{reportType} %{status} detected no new vulnerabilities."
msgstr ""
-msgid "%{screenreaderOnlyStart}Keyboard shorcuts%{screenreaderOnlyEnd} Enabled"
+msgid "%{retryButtonStart}Try again%{retryButtonEnd} or %{newFileButtonStart}attach a new file%{newFileButtonEnd}"
msgstr ""
msgid "%{service_title} %{message}."
@@ -620,6 +664,12 @@ msgstr "Alterações de %{title}"
msgid "%{token}..."
msgstr ""
+msgid "%{totalCpu} (%{freeSpacePercentage}%{percentSymbol} free)"
+msgstr ""
+
+msgid "%{totalMemory} (%{freeSpacePercentage}%{percentSymbol} free)"
+msgstr ""
+
msgid "%{totalWeight} total weight"
msgstr ""
@@ -650,6 +700,12 @@ msgstr ""
msgid "%{verb} %{time_spent_value} spent time."
msgstr "%{verb} %{time_spent_value} spent time."
+msgid "%{webhooks_link_start}%{webhook_type}%{link_end} enable you to send notifications to web applications in response to events in a group or project."
+msgstr ""
+
+msgid "%{webhooks_link_start}%{webhook_type}%{link_end} enable you to send notifications to web applications in response to events in a group or project. We recommend using an %{integrations_link_start}integration%{link_end} in preference to a webhook."
+msgstr ""
+
msgid "'%{level}' is not a valid visibility level"
msgstr "'%{level}' não é um nível de visibilidade válido"
@@ -676,9 +732,6 @@ msgstr "(%{mrCount} merges realizados)"
msgid "(No changes)"
msgstr "(Nenhuma mudança)"
-msgid "(Show all)"
-msgstr "(Mostrar tudo)"
-
msgid "(check progress)"
msgstr "(verificar progresso)"
@@ -745,6 +798,9 @@ msgstr "- exibir menos"
msgid "0 for unlimited"
msgstr "0 para ilimitado"
+msgid "0 for unlimited, only effective with remote storage enabled."
+msgstr ""
+
msgid "1 %{type} addition"
msgid_plural "%{count} %{type} additions"
msgstr[0] "1 adição de %{type}"
@@ -892,6 +948,12 @@ msgstr "<code>\"johnsmith@example.com\": \"johnsm...@example.com\"</code> adicio
msgid "<code>\"johnsmith@example.com\": \"johnsmith@example.com\"</code> will add \"By <a href=\"#\">johnsmith@example.com</a>\" to all issues and comments originally created by johnsmith@example.com. By default, the email address or username is masked to ensure the user's privacy. Use this option if you want to show the full email address."
msgstr "<code>\"johnsmith@example.com\": \"johnsmith@example.com\"</code> adicionará \"Por <a href=\"#\">johnsmith@example.com</a>\" a todas as issues e comentários originalmente criados por johnsmith@example.com. Por padrão, o endereço de e-mail ou nome de usuário é mascarado para garantir a privacidade do usuário. Use esta opção se você quiser mostrar o endereço de e-mail completo."
+msgid "<code>Masked</code> to prevent the values from being displayed in job logs (must match certain regexp requirements)."
+msgstr ""
+
+msgid "<code>Protected</code> to expose them to protected branches or tags only."
+msgstr ""
+
msgid "<namespace / project>"
msgstr ""
@@ -919,9 +981,6 @@ msgstr "'Runner' é um processo que executa uma tarefa. Você pode configurar qu
msgid "A .NET Core console application template, customizable for any .NET Core project"
msgstr "Um template de aplicação console .NET Core customizável para qualquer projeto .NET Code"
-msgid "A DAG must have two dependent jobs to be visualized on this tab."
-msgstr ""
-
msgid "A GitBook site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
msgstr "Um site Gitbook que usa Netlify para CI/CD em vez do Gitlab, mas ainda com todas as outras ótimas funcionalidades do Gitlab."
@@ -952,6 +1011,12 @@ msgstr "Um branch padrão não pode ser escolhido para um projeto vazio."
msgid "A deleted user"
msgstr "Um usuário excluído"
+msgid "A file has been changed."
+msgstr ""
+
+msgid "A file was not found."
+msgstr ""
+
msgid "A file with '%{file_name}' already exists in %{branch} branch"
msgstr ""
@@ -1018,7 +1083,7 @@ msgstr ""
msgid "A subscription will trigger a new pipeline on the default branch of this project when a pipeline successfully completes for a new tag on the %{default_branch_docs} of the subscribed project."
msgstr ""
-msgid "A terraform report was generated in your pipelines."
+msgid "A suggestion is not applicable."
msgstr ""
msgid "A user with write access to the source branch selected this option"
@@ -1189,6 +1254,9 @@ msgstr "Conta: %{account}"
msgid "Action to take when receiving an alert."
msgstr "Ação a ser tomada ao receber um alerta."
+msgid "Actions"
+msgstr ""
+
msgid "Activate"
msgstr "Ativar"
@@ -1245,6 +1313,9 @@ msgstr "Adicionar cluster Kubernetes"
msgid "Add LICENSE"
msgstr ""
+msgid "Add New Node"
+msgstr ""
+
msgid "Add README"
msgstr "Adicionar README"
@@ -1320,6 +1391,9 @@ msgstr ""
msgid "Add approval rule"
msgstr "Adicionar regra de aprovação"
+msgid "Add approvers"
+msgstr ""
+
msgid "Add bold text"
msgstr "Adicionar texto em negrito"
@@ -1377,6 +1451,9 @@ msgstr ""
msgid "Add strikethrough text"
msgstr ""
+msgid "Add suggestion to batch"
+msgstr ""
+
msgid "Add system hook"
msgstr ""
@@ -1476,6 +1553,9 @@ msgstr "Ãrea do Administrador"
msgid "Admin Note"
msgstr ""
+msgid "Admin Notifications"
+msgstr ""
+
msgid "Admin Overview"
msgstr "Visão Geral do Administrador"
@@ -1587,6 +1667,9 @@ msgstr ""
msgid "AdminSettings|Integrations configured here will automatically apply to all projects on this instance."
msgstr ""
+msgid "AdminSettings|Moved to integrations"
+msgstr ""
+
msgid "AdminSettings|No required pipeline"
msgstr "Nenhum pipeline necessário"
@@ -1817,12 +1900,18 @@ msgid_plural "Alerts"
msgstr[0] "Alerta"
msgstr[1] "Alertas"
+msgid "AlertManagement| assign yourself"
+msgstr ""
+
msgid "AlertManagement|Acknowledged"
msgstr ""
msgid "AlertManagement|Alert"
msgstr ""
+msgid "AlertManagement|Alert assignee(s): %{assignees}"
+msgstr ""
+
msgid "AlertManagement|Alert detail"
msgstr ""
@@ -1838,9 +1927,15 @@ msgstr ""
msgid "AlertManagement|All alerts"
msgstr ""
+msgid "AlertManagement|Assign To"
+msgstr ""
+
msgid "AlertManagement|Assign status"
msgstr ""
+msgid "AlertManagement|Assignee"
+msgstr ""
+
msgid "AlertManagement|Assignees"
msgstr ""
@@ -1859,9 +1954,6 @@ msgstr ""
msgid "AlertManagement|Edit"
msgstr ""
-msgid "AlertManagement|End time"
-msgstr ""
-
msgid "AlertManagement|Events"
msgstr ""
@@ -1880,10 +1972,13 @@ msgstr ""
msgid "AlertManagement|More information"
msgstr ""
+msgid "AlertManagement|No Matching Results"
+msgstr ""
+
msgid "AlertManagement|No alert data to display."
msgstr ""
-msgid "AlertManagement|No alerts available to display. If you think you're seeing this message in error, refresh the page."
+msgid "AlertManagement|No alerts available to display. See %{linkStart}enabling alert management%{linkEnd} for more information on adding alerts to the list."
msgstr ""
msgid "AlertManagement|No alerts to display."
@@ -1892,6 +1987,9 @@ msgstr ""
msgid "AlertManagement|None"
msgstr ""
+msgid "AlertManagement|None -"
+msgstr ""
+
msgid "AlertManagement|Open"
msgstr ""
@@ -1928,9 +2026,18 @@ msgstr ""
msgid "AlertManagement|There was an error displaying the alerts. Confirm your endpoint's configuration details to ensure alerts appear."
msgstr ""
+msgid "AlertManagement|There was an error while updating the assignee(s) list. Please try again."
+msgstr ""
+
+msgid "AlertManagement|There was an error while updating the assignee(s) of the alert. Please try again."
+msgstr ""
+
msgid "AlertManagement|There was an error while updating the status of the alert. Please try again."
msgstr ""
+msgid "AlertManagement|This assignee cannot be assigned to this alert."
+msgstr ""
+
msgid "AlertManagement|Tool"
msgstr ""
@@ -2078,6 +2185,9 @@ msgstr "Permitir que os usuários registrem qualquer aplicativo para usar o GitL
msgid "Allow users to request access (if visibility is public or internal)"
msgstr ""
+msgid "Allowed Geo IP"
+msgstr ""
+
msgid "Allowed email domain restriction only permitted for top-level groups"
msgstr ""
@@ -2111,6 +2221,9 @@ msgstr ""
msgid "Amazon Web Services"
msgstr ""
+msgid "Amazon Web Services Logo"
+msgstr ""
+
msgid "Amazon authentication is not %{link_start}correctly configured%{link_end}. Ask your GitLab administrator if you want to use this service."
msgstr ""
@@ -2168,10 +2281,16 @@ msgstr "Ocorreu um erro ao tentar resolver uma discussão. Por favor, tente nova
msgid "An error occurred when updating the issue weight"
msgstr "Ocorreu um erro ao atualizar o peso do issue"
+msgid "An error occurred while acknowledging the notification. Refresh the page and try again."
+msgstr ""
+
+msgid "An error occurred while adding approvers"
+msgstr ""
+
msgid "An error occurred while adding formatted title for epic"
msgstr ""
-msgid "An error occurred while checking group path"
+msgid "An error occurred while checking group path. Please refresh and try again."
msgstr ""
msgid "An error occurred while committing your changes."
@@ -2315,7 +2434,7 @@ msgstr ""
msgid "An error occurred while loading milestones"
msgstr ""
-msgid "An error occurred while loading terraform report"
+msgid "An error occurred while loading project creation UI"
msgstr ""
msgid "An error occurred while loading the data. Please try again."
@@ -2369,6 +2488,9 @@ msgstr "Um erro ocorreu ao remover issues."
msgid "An error occurred while rendering preview broadcast message"
msgstr "Erro ao renderizar pré-visualização da mensagem de transmissão"
+msgid "An error occurred while rendering the editor"
+msgstr ""
+
msgid "An error occurred while reordering issues."
msgstr ""
@@ -2465,7 +2587,7 @@ msgstr ""
msgid "Analyze your dependencies for known vulnerabilities."
msgstr ""
-msgid "Analyze your source code and git history for secrets"
+msgid "Analyze your source code and git history for secrets."
msgstr ""
msgid "Analyze your source code for known vulnerabilities."
@@ -2579,12 +2701,18 @@ msgstr ""
msgid "Apply suggestion"
msgstr "Aplicar sugestão"
+msgid "Apply suggestions"
+msgstr ""
+
msgid "Apply template"
msgstr ""
msgid "Apply this approval rule to any branch or a specific protected branch."
msgstr ""
+msgid "Applying"
+msgstr ""
+
msgid "Applying a template will replace the existing issue description. Any changes you have made will be lost."
msgstr ""
@@ -2597,12 +2725,18 @@ msgstr "Aplicando comando a %{commandDescription}"
msgid "Applying multiple commands"
msgstr "Aplicando vários comandos"
-msgid "Applying suggestion"
-msgstr "Aplicando sugestão"
+msgid "Applying suggestion..."
+msgstr ""
+
+msgid "Applying suggestions..."
+msgstr ""
msgid "Approval rules"
msgstr ""
+msgid "Approval rules reset to project defaults"
+msgstr ""
+
msgid "ApprovalRuleRemove|%d member"
msgid_plural "ApprovalRuleRemove|%d members"
msgstr[0] "%d membro"
@@ -2656,6 +2790,9 @@ msgstr ""
msgid "Approved"
msgstr ""
+msgid "Approved MRs"
+msgstr ""
+
msgid "Approved by: "
msgstr ""
@@ -2869,6 +3006,9 @@ msgstr ""
msgid "Assign"
msgstr "Atribuir"
+msgid "Assign Iteration"
+msgstr ""
+
msgid "Assign custom color like #FF0000"
msgstr "Coloque uma cor personalizada, como #FF0000"
@@ -2887,6 +3027,9 @@ msgstr "Atribua alguns issues a este marco."
msgid "Assign to"
msgstr "Atribuir à"
+msgid "Assign to commenting user"
+msgstr ""
+
msgid "Assign yourself to these issues"
msgstr "Atribuir-se a essas issues"
@@ -2913,6 +3056,9 @@ msgid_plural "%d Assignees"
msgstr[0] "Responsável"
msgstr[1] "%d Responsáveis"
+msgid "Assignee has no permissions"
+msgstr ""
+
msgid "Assignee lists not available with your current license"
msgstr "Quadro de responsáveis não disponível com sua licença atual"
@@ -2922,6 +3068,9 @@ msgstr "Listas de responsáveis mostram todas as issues atribuídas ao usuário
msgid "Assignee(s)"
msgstr "Responsável(is)"
+msgid "Assignees"
+msgstr ""
+
msgid "Assigns %{assignee_users_sentence}."
msgstr ""
@@ -2957,6 +3106,9 @@ msgstr "Eventos de Auditoria"
msgid "Audit Events is a way to keep track of important events that happened in GitLab."
msgstr "Eventos de auditoria é uma maneira de acompanhar eventos importantes que aconteceram no GitLab."
+msgid "Audit Log"
+msgstr ""
+
msgid "AuditEvents|(removed)"
msgstr ""
@@ -2993,6 +3145,9 @@ msgstr ""
msgid "AuditLogs|IP Address"
msgstr ""
+msgid "AuditLogs|Member Events"
+msgstr ""
+
msgid "AuditLogs|No matching %{type} found."
msgstr ""
@@ -3275,9 +3430,6 @@ msgstr "Seus selos"
msgid "Badges|e.g. %{exampleUrl}"
msgstr "por exemplo, %{exampleUrl}"
-msgid "Badge|New"
-msgstr "Novo"
-
msgid "Balsamiq file could not be loaded."
msgstr "O arquivo Balsamiq não pôde ser carregado."
@@ -3326,6 +3478,12 @@ msgstr "Abaixo estão as impressões digitais das chaves do host SSH da instânc
msgid "Below you will find all the groups that are public."
msgstr "Abaixo você encontrará todos os grupos que são públicos."
+msgid "Beta"
+msgstr ""
+
+msgid "Bi-weekly code coverage"
+msgstr ""
+
msgid "Billing"
msgstr "Cobrança"
@@ -3386,9 +3544,15 @@ msgstr ""
msgid "Bitbucket Server Import"
msgstr "Importação de Servidores Bitbucket"
+msgid "Bitbucket Server import"
+msgstr ""
+
msgid "Bitbucket import"
msgstr "Importar do Bitbucket"
+msgid "Blame"
+msgstr ""
+
msgid "Blocked"
msgstr "Bloqueado"
@@ -3401,9 +3565,6 @@ msgstr ""
msgid "Blog"
msgstr "Blog"
-msgid "Blue helpers indicate an action to be taken."
-msgstr "Auxiliares azuis indicam uma ação a ser tomada."
-
msgid "Board name"
msgstr "Nome do painel"
@@ -3602,6 +3763,9 @@ msgstr "Transmissão de messagem foi criada com sucesso."
msgid "Broadcast Message was successfully updated."
msgstr "Transmissão de messagem foi atualizada com sucesso."
+msgid "Broadcast Messages"
+msgstr ""
+
msgid "Browse Directory"
msgstr "Navegar no Diretório"
@@ -3656,6 +3820,9 @@ msgstr ""
msgid "By %{user_name}"
msgstr "Por %{user_name}"
+msgid "By URL"
+msgstr ""
+
msgid "By default GitLab sends emails in HTML and plain text formats so mail clients can choose what format to use. Disable this option if you only want to send emails in plain text format."
msgstr "Por padrão, o GitLab envia e-mails em formatos HTML e texto plano para que os clientes possam escolher o formato a ser utilizado. Desative esta opção se você quiser apenas enviar e-mails em formato de texto plano."
@@ -3761,6 +3928,9 @@ msgstr ""
msgid "Can override approvers and approvals required per merge request"
msgstr "Pode substituir aprovadores e aprovações necessárias por merge request"
+msgid "Can't apply as this line has changed or the suggestion already matches its content."
+msgstr ""
+
msgid "Can't create snippet: %{err}"
msgstr ""
@@ -3947,9 +4117,6 @@ msgstr "Mudanças serão mostradas se revisão de <b>origem</b> tiver sofrido me
msgid "Changes are still tracked. Useful for cluster/index migrations."
msgstr ""
-msgid "Changes are unknown"
-msgstr ""
-
msgid "Changes suppressed. Click to show."
msgstr "Alterações suprimidas. Clique para mostrar."
@@ -4256,15 +4423,9 @@ msgstr "Escolha o nível de visibilidade, ative/desative os recursos do projeto
msgid "Choose what content you want to see on a group’s overview page"
msgstr "Escolha o conteúdo que você deseja ver na página de visão geral de um grupo"
-msgid "Choose which groups you wish to synchronize to this secondary node"
-msgstr ""
-
msgid "Choose which repositories you want to connect and run CI/CD pipelines."
msgstr "Escolha quais repositórios você deseja se conectar e executar pipelines de CI/CD."
-msgid "Choose which shards you wish to synchronize to this secondary node"
-msgstr ""
-
msgid "Choose your framework"
msgstr ""
@@ -4562,9 +4723,15 @@ msgstr ""
msgid "Cluster does not exist"
msgstr ""
+msgid "Cluster is required for Stages::ClusterEndpointInserter"
+msgstr ""
+
msgid "Cluster level"
msgstr ""
+msgid "Cluster type must be specificed for Stages::ClusterEndpointInserter"
+msgstr ""
+
msgid "ClusterIntegration| %{custom_domain_start}More information%{custom_domain_end}."
msgstr " %{custom_domain_start}Mais informações%{custom_domain_end}."
@@ -4676,8 +4843,8 @@ msgstr "Certificado CA"
msgid "ClusterIntegration|Cert-Manager"
msgstr "Cert-Manager"
-msgid "ClusterIntegration|Cert-Manager is a native Kubernetes certificate management controller that helps with issuing certificates. Installing Cert-Manager on your cluster will issue a certificate by %{letsEncrypt} and ensure that certificates are valid and up-to-date."
-msgstr "Cert-Manager é um controlador de gerenciamento de certificados nativo do Kubernetes que ajuda na emissão de certificados. A instalação do Cert-Manager em seu cluster emitirá um certificado por %{letsEncrypt} e garantirá que os certificados sejam válidos e atualizados."
+msgid "ClusterIntegration|Cert-Manager is a native Kubernetes certificate management controller that helps with issuing certificates. Installing Cert-Manager on your cluster will issue a certificate by %{linkStart}Let's Encrypt%{linkEnd} and ensure that certificates are valid and up-to-date."
+msgstr ""
msgid "ClusterIntegration|Certificate Authority bundle (PEM format)"
msgstr "Pacote de autoridade certificadora (Formato PEM)"
@@ -4703,9 +4870,6 @@ msgstr ""
msgid "ClusterIntegration|Clear the local cache of namespace and service accounts. This is necessary if your integration has become out of sync. The cache is repopulated during the next CI job that requires namespace and service accounts."
msgstr ""
-msgid "ClusterIntegration|Cloud Run"
-msgstr ""
-
msgid "ClusterIntegration|Cluster being created"
msgstr ""
@@ -4790,7 +4954,7 @@ msgstr ""
msgid "ClusterIntegration|Crossplane"
msgstr ""
-msgid "ClusterIntegration|Crossplane enables declarative provisioning of managed services from your cloud of choice using %{codeStart}kubectl%{codeEnd} or %{gitlabIntegrationLink}. Crossplane runs inside your Kubernetes cluster and supports secure connectivity and secrets management between app containers and the cloud services they depend on."
+msgid "ClusterIntegration|Crossplane enables declarative provisioning of managed services from your cloud of choice using %{codeStart}kubectl%{codeEnd} or %{linkStart}GitLab Integration%{linkEnd}. Crossplane runs inside your Kubernetes cluster and supports secure connectivity and secrets management between app containers and the cloud services they depend on."
msgstr ""
msgid "ClusterIntegration|Deletes all GitLab resources attached to this cluster during removal"
@@ -4874,9 +5038,6 @@ msgstr ""
msgid "ClusterIntegration|GitLab-managed cluster"
msgstr "Cluster gerenciado pelo GitLab"
-msgid "ClusterIntegration|Gitlab Integration"
-msgstr ""
-
msgid "ClusterIntegration|Global default"
msgstr ""
@@ -4922,8 +5083,8 @@ msgstr "Ingress Endpoint"
msgid "ClusterIntegration|Ingress gives you a way to route requests to services based on the request host or path, centralizing a number of services into a single entrypoint."
msgstr "Ingress oferece uma maneira de rotear solicitações para serviços com base no host ou caminho da solicitação, centralizando diversos serviços em um único ponto de entrada."
-msgid "ClusterIntegration|Installing Ingress may incur additional costs. Learn more about %{pricingLink}."
-msgstr "Instalação do Ingress poderá incorrer em custos adicionais. Saiba mais %{pricingLink}."
+msgid "ClusterIntegration|Installing Ingress may incur additional costs. Learn more about %{linkStart}pricing%{linkEnd}."
+msgstr ""
msgid "ClusterIntegration|Instance cluster"
msgstr "Cluster de instância"
@@ -4937,8 +5098,8 @@ msgstr "Integrar automação de cluster Kubernetes"
msgid "ClusterIntegration|Issuer Email"
msgstr ""
-msgid "ClusterIntegration|Issuers represent a certificate authority. You must provide an email address for your Issuer. "
-msgstr "Emissores representam uma autoridade certificadora. Você deve fornecer um endereço de e-mail do seu Emissor."
+msgid "ClusterIntegration|Issuers represent a certificate authority. You must provide an email address for your Issuer."
+msgstr ""
msgid "ClusterIntegration|Jupyter Hostname"
msgstr "Nome do host Jupyter"
@@ -5009,9 +5170,6 @@ msgstr "Saiba mais sobre grupo de cluster Kubernetes"
msgid "ClusterIntegration|Learn more about instance Kubernetes clusters"
msgstr "Saiba mais sobre instância de cluster Kubernetes"
-msgid "ClusterIntegration|Let's Encrypt"
-msgstr "Let's Encrypt"
-
msgid "ClusterIntegration|Loading IAM Roles"
msgstr ""
@@ -5123,8 +5281,8 @@ msgstr "Prefixo do namespace do projeto (opcional, único)"
msgid "ClusterIntegration|Prometheus"
msgstr "Prometheus"
-msgid "ClusterIntegration|Prometheus is an open-source monitoring system with %{gitlabIntegrationLink} to monitor deployed applications."
-msgstr "Prometheus é um sistema de monitoramento de código aberto com %{gitlabIntegrationLink} para monitorar aplicações lançadas."
+msgid "ClusterIntegration|Prometheus is an open-source monitoring system with %{linkStart}GitLab Integration%{linkEnd} to monitor deployed applications."
+msgstr ""
msgid "ClusterIntegration|Provider details"
msgstr ""
@@ -5279,10 +5437,10 @@ msgstr "Selecione a zona"
msgid "ClusterIntegration|Select zone to choose machine type"
msgstr "Selecione a zone para escolher o tipo de máquina"
-msgid "ClusterIntegration|Send Cilium Logs"
+msgid "ClusterIntegration|Send Container Network Policies Logs"
msgstr ""
-msgid "ClusterIntegration|Send ModSecurity Logs"
+msgid "ClusterIntegration|Send Web Application Firewall Logs"
msgstr ""
msgid "ClusterIntegration|Service Token"
@@ -5450,15 +5608,12 @@ msgstr "acesso ao Google Container Engine"
msgid "ClusterIntegration|documentation"
msgstr "documentação"
-msgid "ClusterIntegration|installed via %{installed_via}"
+msgid "ClusterIntegration|installed via %{linkStart}Cloud Run%{linkEnd}"
msgstr ""
msgid "ClusterIntegration|meets the requirements"
msgstr "atende aos requisitos"
-msgid "ClusterIntegration|pricing"
-msgstr "preços"
-
msgid "ClusterIntegration|sign up"
msgstr "cadastrar"
@@ -5495,6 +5650,15 @@ msgstr ""
msgid "Code"
msgstr "Código"
+msgid "Code Coverage: %{coveragePercentage}%{percentSymbol}"
+msgstr ""
+
+msgid "Code Coverage| Empty code coverage data"
+msgstr ""
+
+msgid "Code Coverage|Couldn't fetch the code coverage data"
+msgstr ""
+
msgid "Code Owners"
msgstr "Proprietários de código"
@@ -5549,7 +5713,7 @@ msgstr "Recolher"
msgid "Collapse approvers"
msgstr "Recolher aprovadores"
-msgid "Collapse child epics"
+msgid "Collapse milestones"
msgstr ""
msgid "Collapse replies"
@@ -5567,6 +5731,9 @@ msgstr "ComboSearch não está definido"
msgid "Coming soon"
msgstr ""
+msgid "Comma-separated, e.g. '1.1.1.1, 2.2.2.0/24'"
+msgstr ""
+
msgid "Command"
msgstr "Comando"
@@ -5737,6 +5904,12 @@ msgstr ""
msgid "Compliance framework (optional)"
msgstr ""
+msgid "Compliance frameworks"
+msgstr ""
+
+msgid "ComplianceDashboard|created by:"
+msgstr ""
+
msgid "ComplianceFramework|GDPR"
msgstr ""
@@ -5875,6 +6048,9 @@ msgstr "Falha na conexão"
msgid "Connection timed out"
msgstr ""
+msgid "Connection timeout"
+msgstr ""
+
msgid "Contact sales to upgrade"
msgstr "Entre em contato com o departamento de vendas para atualizar"
@@ -5930,9 +6106,6 @@ msgstr ""
msgid "ContainerRegistry|CLI Commands"
msgstr ""
-msgid "ContainerRegistry|Compressed Size"
-msgstr ""
-
msgid "ContainerRegistry|Container Registry"
msgstr ""
@@ -5948,6 +6121,9 @@ msgstr ""
msgid "ContainerRegistry|Copy push command"
msgstr ""
+msgid "ContainerRegistry|Delete selected"
+msgstr ""
+
msgid "ContainerRegistry|Docker connection error"
msgstr ""
@@ -5981,17 +6157,17 @@ msgstr ""
msgid "ContainerRegistry|If you are not already logged in, you need to authenticate to the Container Registry by using your GitLab username and password. If you have %{twofaDocLinkStart}Two-Factor Authentication%{twofaDocLinkEnd} enabled, use a %{personalAccessTokensDocLinkStart}Personal Access Token%{personalAccessTokensDocLinkEnd} instead of a password."
msgstr ""
-msgid "ContainerRegistry|Image ID"
+msgid "ContainerRegistry|Image ID: %{imageId}"
msgstr ""
msgid "ContainerRegistry|Image Repositories"
msgstr ""
-msgid "ContainerRegistry|Keep and protect the images that matter most."
+msgid "ContainerRegistry|Image tags"
msgstr ""
-msgid "ContainerRegistry|Last Updated"
-msgstr "Última atualização"
+msgid "ContainerRegistry|Keep and protect the images that matter most."
+msgstr ""
msgid "ContainerRegistry|Login"
msgstr ""
@@ -6005,6 +6181,9 @@ msgstr ""
msgid "ContainerRegistry|Please contact your administrator."
msgstr ""
+msgid "ContainerRegistry|Published %{timeInfo}"
+msgstr ""
+
msgid "ContainerRegistry|Push an image"
msgstr ""
@@ -6020,9 +6199,6 @@ msgstr ""
msgid "ContainerRegistry|Remove repository"
msgstr "Apagar repositório"
-msgid "ContainerRegistry|Remove selected tags"
-msgstr ""
-
msgid "ContainerRegistry|Remove tag"
msgid_plural "ContainerRegistry|Remove tags"
msgstr[0] ""
@@ -6052,9 +6228,6 @@ msgstr ""
msgid "ContainerRegistry|Sorry, your filter produced no results."
msgstr ""
-msgid "ContainerRegistry|Tag"
-msgstr "Tag"
-
msgid "ContainerRegistry|Tag expiration policy"
msgstr ""
@@ -6199,21 +6372,6 @@ msgstr "Controle e-mails vinculados à sua conta"
msgid "Control the display of third party offers."
msgstr "Controle a exibição de ofertas de terceiros."
-msgid "Control the maximum concurrency of LFS/attachment backfill for this secondary node"
-msgstr ""
-
-msgid "Control the maximum concurrency of container repository operations for this Geo node"
-msgstr ""
-
-msgid "Control the maximum concurrency of repository backfill for this secondary node"
-msgstr "Controlar a concorrência máxima do enchimento do repositório para esse nó secundário"
-
-msgid "Control the maximum concurrency of verification operations for this Geo node"
-msgstr ""
-
-msgid "Control the minimum interval in days that a repository should be reverified for this primary node"
-msgstr ""
-
msgid "Cookie domain"
msgstr ""
@@ -6355,6 +6513,9 @@ msgstr "Não foi possível excluir o apelido de bate-papo %{chat_name}."
msgid "Could not find design."
msgstr ""
+msgid "Could not find iteration"
+msgstr ""
+
msgid "Could not remove the trigger."
msgstr "Não foi possível remover o gatilho."
@@ -6391,6 +6552,12 @@ msgstr ""
msgid "Coverage"
msgstr "Cobertura"
+msgid "Coverage Fuzzing"
+msgstr ""
+
+msgid "Crash State"
+msgstr ""
+
msgid "Create"
msgstr "Criar"
@@ -6439,6 +6606,9 @@ msgstr "Criar um novo repositório"
msgid "Create a personal access token on your account to pull or push via %{protocol}."
msgstr "Crie um token de acesso pessoal na sua conta para dar pull ou push via %{protocol}."
+msgid "Create a project pre-populated with the necessary files to get you started quickly."
+msgstr ""
+
msgid "Create an account using:"
msgstr ""
@@ -6607,6 +6777,9 @@ msgstr ""
msgid "Creates branch '%{branch_name}' and a merge request to resolve this issue."
msgstr ""
+msgid "Creating"
+msgstr ""
+
msgid "Creating epic"
msgstr "Criando epic"
@@ -6658,7 +6831,10 @@ msgstr "Senha atual"
msgid "Current vulnerabilities count"
msgstr ""
-msgid "CurrentUser|Buy CI minutes"
+msgid "CurrentUser|Buy Pipeline minutes"
+msgstr ""
+
+msgid "CurrentUser|One of your groups is running out"
msgstr ""
msgid "CurrentUser|Profile"
@@ -6673,6 +6849,9 @@ msgstr ""
msgid "CurrentUser|Upgrade"
msgstr ""
+msgid "Custom Attributes"
+msgstr ""
+
msgid "Custom CI configuration path"
msgstr ""
@@ -6936,12 +7115,18 @@ msgstr ""
msgid "DAG"
msgstr ""
+msgid "DAG visualization requires at least 3 dependent jobs."
+msgstr ""
+
msgid "DNS"
msgstr "DNS"
msgid "Dashboard"
msgstr "Dashboard"
+msgid "Dashboard uid not found"
+msgstr ""
+
msgid "DashboardProjects|All"
msgstr "Todos"
@@ -6966,6 +7151,9 @@ msgstr ""
msgid "Data is still calculating..."
msgstr "Os dados ainda estão a ser calculados..."
+msgid "Datasource name not found"
+msgstr ""
+
msgid "Date"
msgstr ""
@@ -7146,6 +7334,9 @@ msgstr "Excluir branch de origem"
msgid "Delete this attachment"
msgstr "Excluir este anexo"
+msgid "Delete user list"
+msgstr ""
+
msgid "Delete variable"
msgstr ""
@@ -7462,6 +7653,9 @@ msgstr "Deploy para"
msgid "Deploying to"
msgstr "Fazer deploy para"
+msgid "Deploying to AWS is easy with GitLab"
+msgstr ""
+
msgid "Deployment Frequency"
msgstr ""
@@ -7824,6 +8018,9 @@ msgstr "Descartado no pipeline %{pipelineLink}"
msgid "Dismissed on pipeline %{pipelineLink} at %{projectLink}"
msgstr "Descartado no pipeline %{pipelineLink} em %{projectLink}"
+msgid "Display alerts from all your monitoring tools directly within GitLab."
+msgstr ""
+
msgid "Display name"
msgstr "Nome de exibição"
@@ -7881,9 +8078,6 @@ msgstr "Não cole a parte privada da chave GPG. Cole a parte pública que começ
msgid "Don't show again"
msgstr "Não exibir novamente"
-msgid "Don't worry, you can access this tour by clicking on the help icon in the top right corner and choose <strong>Learn GitLab</strong>."
-msgstr ""
-
msgid "Done"
msgstr "Pronto"
@@ -8010,6 +8204,9 @@ msgstr "Alterar Agendamento do Pipeline %{id}"
msgid "Edit Release"
msgstr ""
+msgid "Edit Slack integration"
+msgstr ""
+
msgid "Edit Snippet"
msgstr "Editar Snippet"
@@ -8049,6 +8246,9 @@ msgstr "Editar identidade para %{user_name}"
msgid "Edit issues"
msgstr "Editar issues"
+msgid "Edit iteration"
+msgstr ""
+
msgid "Edit public deploy key"
msgstr "Editar chave de deploy pública"
@@ -8097,6 +8297,9 @@ msgstr ""
msgid "Email"
msgstr "E-mail"
+msgid "Email Notification"
+msgstr ""
+
msgid "Email address"
msgstr "Endereço de e-mail"
@@ -8109,6 +8312,9 @@ msgstr ""
msgid "Email not verified. Please verify your email in Salesforce."
msgstr ""
+msgid "Email notification for unknown sign-ins"
+msgstr ""
+
msgid "Email patch"
msgstr "Patch de email"
@@ -8340,6 +8546,9 @@ msgstr "Termina em (UTC)"
msgid "Enforce DNS rebinding attack protection"
msgstr ""
+msgid "Enforce personal access token expiration"
+msgstr ""
+
msgid "Ensure connectivity is available from the GitLab server to the Prometheus server"
msgstr ""
@@ -8409,6 +8618,9 @@ msgstr "Digite o título do merge request"
msgid "Enter the name of your application, and we'll return a unique %{type}."
msgstr ""
+msgid "Enter weights for storages for new repositories."
+msgstr ""
+
msgid "Enter your password to approve"
msgstr ""
@@ -8418,15 +8630,27 @@ msgstr "Ambiente"
msgid "Environment does not have deployments"
msgstr ""
+msgid "Environment is required for Stages::MetricEndpointInserter"
+msgstr ""
+
+msgid "Environment is required for Stages::VariableEndpointInserter"
+msgstr ""
+
msgid "Environment scope"
msgstr ""
+msgid "Environment variables are applied to all project environments in this instance via the Runner. You can use environment variables for passwords, secret keys, etc. Make variables available to the running application by prepending the variable key with <code>K8S_SECRET_</code>. You can set variables to be:"
+msgstr ""
+
msgid "Environment variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. Additionally, they can be masked so they are hidden in job logs, though they must match certain regexp requirements to do so. You can use environment variables for passwords, secret keys, or whatever you want."
msgstr "As variáveis de ambiente são aplicadas aos ambientes por meio do runner. Elas podem ser protegidas apenas expondo-as a branches ou tags protegidas. Além disso, elas podem ser mascaradas de forma a estarem ocultas nos registros de tarefas, embora precisem corresponder a certos requisitos de expressão regular para fazer isso. Você pode usar variáveis de ambiente para senhas, chaves secretas ou o que você quiser."
msgid "Environment variables are configured by your administrator to be %{link_start}protected%{link_end} by default"
msgstr "As variáveis de ambiente são configuradas pelo seu administrador para serem %{link_start}protegidas%{link_end} por padrão"
+msgid "Environment variables on this GitLab instance are configured to be %{link_start}protected%{link_end} by default"
+msgstr ""
+
msgid "Environment:"
msgstr "Ambiente:"
@@ -8958,6 +9182,9 @@ msgstr "Filtrar por tudo"
msgid "EventFilterBy|Filter by comments"
msgstr "Filtrar por comentários"
+msgid "EventFilterBy|Filter by designs"
+msgstr ""
+
msgid "EventFilterBy|Filter by epic events"
msgstr ""
@@ -9090,15 +9317,15 @@ msgstr "Expandir tudo"
msgid "Expand approvers"
msgstr "Expandir aprovadores"
-msgid "Expand child epics"
-msgstr ""
-
msgid "Expand down"
msgstr ""
msgid "Expand dropdown"
msgstr ""
+msgid "Expand milestones"
+msgstr ""
+
msgid "Expand sidebar"
msgstr "Expandir barra lateral"
@@ -9114,6 +9341,9 @@ msgstr ""
msgid "Expiration date"
msgstr "Data de validade"
+msgid "Expiration not enforced"
+msgstr ""
+
msgid "Expiration policy for the Container Registry is a perfect solution for keeping the Registry space down while still enjoying the full power of GitLab CI/CD."
msgstr ""
@@ -9234,6 +9464,9 @@ msgstr "Falha"
msgid "Failed Jobs"
msgstr "Tarefas com falha"
+msgid "Failed on"
+msgstr ""
+
msgid "Failed to add a Zoom meeting"
msgstr ""
@@ -9294,14 +9527,20 @@ msgstr ""
msgid "Failed to install."
msgstr "Falha ao instalar."
+msgid "Failed to load assignees. Please try again."
+msgstr ""
+
+msgid "Failed to load authors. Please try again."
+msgstr ""
+
msgid "Failed to load emoji list."
msgstr "Falha ao carregar a lista de emojis."
msgid "Failed to load error details from Sentry."
msgstr ""
-msgid "Failed to load errors from Sentry. Error message: %{errorMessage}"
-msgstr "Falha ao carregar erros do Sentry. Mensagem de erro: %{errorMessage}"
+msgid "Failed to load errors from Sentry."
+msgstr ""
msgid "Failed to load group activity metrics. Please try again."
msgstr ""
@@ -9381,6 +9620,9 @@ msgstr "Falha ao salvar preferências."
msgid "Failed to set due date because the date format is invalid."
msgstr ""
+msgid "Failed to set iteration on this issue. Please try again."
+msgstr ""
+
msgid "Failed to signing using smartcard authentication"
msgstr "Falha ao entrar usando a autenticação de cartão inteligente"
@@ -9603,15 +9845,12 @@ msgstr ""
msgid "FeatureFlags|Target environments"
msgstr "Ambientes alvo"
-msgid "FeatureFlags|There are no active feature flags"
-msgstr "Não há feature flags ativas"
-
-msgid "FeatureFlags|There are no inactive feature flags"
-msgstr "Não há feature flags inativas"
-
msgid "FeatureFlags|There was an error fetching the feature flags."
msgstr "Ocorreu um erro ao buscar as feature flag."
+msgid "FeatureFlags|There was an error retrieving user lists"
+msgstr ""
+
msgid "FeatureFlags|Try again in a few moments or contact your support team."
msgstr "Tente novamente daqui a pouco ou entre em contato com a sua equipe de suporte."
@@ -9621,9 +9860,18 @@ msgstr ""
msgid "FeatureFlag|Delete strategy"
msgstr ""
+msgid "FeatureFlag|List"
+msgstr ""
+
msgid "FeatureFlag|Percentage"
msgstr ""
+msgid "FeatureFlag|Select a user list"
+msgstr ""
+
+msgid "FeatureFlag|There are no configured user lists"
+msgstr ""
+
msgid "FeatureFlag|Type"
msgstr ""
@@ -9780,6 +10028,12 @@ msgstr "Filtre seus projetos por nome"
msgid "Filter..."
msgstr "Filtro..."
+msgid "Find File"
+msgstr ""
+
+msgid "Find bugs in your code with coverage-guided fuzzing"
+msgstr ""
+
msgid "Find by path"
msgstr "Localizar por caminho"
@@ -10044,6 +10298,9 @@ msgstr ""
msgid "Geo allows you to replicate your GitLab instance to other geographical locations."
msgstr "Geo permite que você replique sua instância do GitLab para outras localizações geográficas."
+msgid "Geo nodes are paused using a command run on the node"
+msgstr ""
+
msgid "GeoNodeStatusEvent|%{timeAgoStr} (%{pendingEvents} events)"
msgstr ""
@@ -10128,6 +10385,9 @@ msgstr ""
msgid "GeoNodes|Not checksummed"
msgstr "Soma de verificação não conferida"
+msgid "GeoNodes|Package files"
+msgstr ""
+
msgid "GeoNodes|Pausing replication stops the sync process. Are you sure?"
msgstr ""
@@ -10146,6 +10406,9 @@ msgstr "WAL de slots de replicação"
msgid "GeoNodes|Replication slots"
msgstr "Slots de replicação"
+msgid "GeoNodes|Replication status"
+msgstr ""
+
msgid "GeoNodes|Repositories"
msgstr "Repositórios"
@@ -10356,6 +10619,9 @@ msgstr "Sincronizado em"
msgid "Geo|Synchronization failed - %{error}"
msgstr "Falha na sincronização - %{error}"
+msgid "Geo|Synchronization of %{itemTitle} is disabled."
+msgstr ""
+
msgid "Geo|The database is currently %{db_lag} behind the primary node."
msgstr ""
@@ -10668,6 +10934,9 @@ msgstr "Tela cheia"
msgid "Go to %{link_to_google_takeout}."
msgstr "Ir para %{link_to_google_takeout}."
+msgid "Go to <strong>Issues</strong> > <strong>Boards</strong> to access your personalized learning issue board."
+msgstr ""
+
msgid "Go to Pipelines"
msgstr ""
@@ -10767,9 +11036,6 @@ msgstr "Ir para seus projetos"
msgid "Go to your snippets"
msgstr "Ir para seus snippets"
-msgid "Golden Tanuki"
-msgstr "Tanuki Dourado"
-
msgid "Google Cloud Platform"
msgstr ""
@@ -10791,6 +11057,9 @@ msgstr "Entendi!"
msgid "Grafana URL"
msgstr "URL do Grafana"
+msgid "Grafana response contains invalid json"
+msgstr ""
+
msgid "GrafanaIntegration|API Token"
msgstr ""
@@ -10839,9 +11108,6 @@ msgstr "O grupo %{group_name} foi agendado para exclusão."
msgid "Group %{group_name} was successfully created."
msgstr "O grupo %{group_name} foi criado com sucesso."
-msgid "Group '%{group_name}' is being imported."
-msgstr ""
-
msgid "Group Audit Events"
msgstr ""
@@ -10878,9 +11144,6 @@ msgstr "Avatar do grupo"
msgid "Group by:"
msgstr ""
-msgid "Group could not be imported: %{errors}"
-msgstr ""
-
msgid "Group description"
msgstr "Descrição do grupo"
@@ -10908,9 +11171,15 @@ msgstr ""
msgid "Group has not been marked for deletion"
msgstr ""
+msgid "Group import could not be scheduled"
+msgstr ""
+
msgid "Group info:"
msgstr "Info. do grupo:"
+msgid "Group is required when cluster_type is :group"
+msgstr ""
+
msgid "Group maintainers can register group runners in the %{link}"
msgstr "Os mantenedores podem registrar grupos de runners em %{link}"
@@ -10974,6 +11243,24 @@ msgstr ""
msgid "GroupActivyMetrics|Recent activity (last 90 days)"
msgstr ""
+msgid "GroupImport|Failed to import group."
+msgstr ""
+
+msgid "GroupImport|Group '%{group_name}' is being imported."
+msgstr ""
+
+msgid "GroupImport|Group could not be imported: %{errors}"
+msgstr ""
+
+msgid "GroupImport|Please wait while we import the group for you. Refresh at will."
+msgstr ""
+
+msgid "GroupImport|The group was successfully imported."
+msgstr ""
+
+msgid "GroupImport|Unable to process group import file"
+msgstr ""
+
msgid "GroupRoadmap|%{dateWord} – No end date"
msgstr ""
@@ -11241,6 +11528,9 @@ msgstr ""
msgid "Groups (%{groups})"
msgstr ""
+msgid "Groups and projects"
+msgstr ""
+
msgid "Groups can also be nested by creating %{subgroup_docs_link_start}subgroups%{subgroup_docs_link_end}."
msgstr "Grupos também podem ser aninhados criando %{subgroup_docs_link_start}subgrupos%{subgroup_docs_link_end}."
@@ -11286,6 +11576,33 @@ msgstr "Nenhum grupo encontrado"
msgid "GroupsEmptyState|You can manage your group member’s permissions and access to each project in the group."
msgstr "Você pode gerenciar permissões de membros e acesso do seu grupo para cada projeto no grupo."
+msgid "GroupsNew|Contact an administrator to enable options for importing your group."
+msgstr ""
+
+msgid "GroupsNew|Create"
+msgstr ""
+
+msgid "GroupsNew|Create group"
+msgstr ""
+
+msgid "GroupsNew|GitLab group export"
+msgstr ""
+
+msgid "GroupsNew|Import"
+msgstr ""
+
+msgid "GroupsNew|Import group"
+msgstr ""
+
+msgid "GroupsNew|My Awesome Group"
+msgstr ""
+
+msgid "GroupsNew|No import options available"
+msgstr ""
+
+msgid "GroupsNew|To copy a GitLab group between installations, navigate to the group settings page for the original installation, generate an export file, and upload it here."
+msgstr ""
+
msgid "GroupsTree|Are you sure you want to leave the \"%{fullName}\" group?"
msgstr "Tem certeza que deseja sair do grupo \"%{fullName}\"?"
@@ -11388,6 +11705,9 @@ msgstr ""
msgid "Helps reduce request volume for protected paths"
msgstr ""
+msgid "Here are all your projects in your group, including the one you just created. To start, let’s take a look at your personalized learning project which will help you learn about GitLab at your own pace."
+msgstr ""
+
msgid "Here you will find recent merge request activity"
msgstr ""
@@ -11402,6 +11722,9 @@ msgid_plural "Hide charts"
msgstr[0] ""
msgstr[1] ""
+msgid "Hide details"
+msgstr ""
+
msgid "Hide file browser"
msgstr "Ocultar navegador de arquivos"
@@ -11411,6 +11734,9 @@ msgstr "Ocultar projetos de grupo"
msgid "Hide host keys manual input"
msgstr "Ocultar entrada manual das teclas do host"
+msgid "Hide list"
+msgstr ""
+
msgid "Hide marketing-related entries from help"
msgstr "Ocultar as entradas relacionadas ao marketing da ajuda"
@@ -11431,9 +11757,6 @@ msgstr[1] "Ocultar valores"
msgid "Hide values"
msgstr "Ocultar valores"
-msgid "Hiding all labels"
-msgstr ""
-
msgid "High or unknown vulnerabilities present"
msgstr ""
@@ -11518,6 +11841,9 @@ msgstr "ID"
msgid "ID:"
msgstr "ID:"
+msgid "IDE"
+msgstr ""
+
msgid "IDE|Allow live previews of JavaScript projects in the Web IDE using CodeSandbox Live Preview."
msgstr ""
@@ -11608,12 +11934,12 @@ msgstr "Se desativado, o nível de acesso irá depender das permissões do usuá
msgid "If enabled"
msgstr "Se ativado"
+msgid "If enabled, GitLab will handle Object Storage replication using Geo. %{linkStart}More information%{linkEnd}"
+msgstr ""
+
msgid "If enabled, access to projects will be validated on an external service using their classification label."
msgstr "Se ativado, o acesso aos projetos será validado em um serviço externo usando sua etiqueta de classificação."
-msgid "If enabled, and if object storage is enabled, GitLab will handle Object Storage replication using Geo"
-msgstr ""
-
msgid "If there is no previous license or if the previous license has expired, some GitLab functionality will be blocked until a new, valid license is uploaded."
msgstr ""
@@ -11973,6 +12299,9 @@ msgid_plural "Instances"
msgstr[0] "Instância"
msgstr[1] "Instâncias"
+msgid "Instance Configuration"
+msgstr ""
+
msgid "Instance Statistics visibility"
msgstr "Visibilidade de Estatísticas de Instância"
@@ -11994,9 +12323,6 @@ msgstr ""
msgid "Integrations"
msgstr "Integrações"
-msgid "Integrations allow you to integrate GitLab with other applications"
-msgstr ""
-
msgid "Integrations|All details"
msgstr ""
@@ -12108,6 +12434,9 @@ msgstr ""
msgid "Invalid query"
msgstr ""
+msgid "Invalid repository bundle for snippet with id %{snippet_id}"
+msgstr ""
+
msgid "Invalid repository path"
msgstr "Caminho de repositório inválido"
@@ -12126,6 +12455,9 @@ msgstr ""
msgid "Invalid two-factor code."
msgstr "Código de dois fatores inválido."
+msgid "Invalid yaml"
+msgstr ""
+
msgid "Invitation"
msgstr "Convite"
@@ -12345,15 +12677,24 @@ msgstr "Deve ter uma linha de cabeçalho e pelo menos duas colunas: a primeira c
msgid "It seems like the Dependency Scanning job ran successfully, but no dependencies have been detected in your project."
msgstr ""
+msgid "It seems that there is currently no available data for code coverage"
+msgstr ""
+
msgid "It's you"
msgstr "É você"
+msgid "Iteration"
+msgstr ""
+
msgid "Iteration changed to"
msgstr ""
msgid "Iteration removed"
msgstr ""
+msgid "Iteration updated"
+msgstr ""
+
msgid "Iterations"
msgstr ""
@@ -12384,12 +12725,18 @@ msgstr "Jan"
msgid "January"
msgstr "Janeiro"
+msgid "Jira Issues"
+msgstr ""
+
msgid "Jira import is already running."
msgstr ""
msgid "Jira integration not configured."
msgstr ""
+msgid "Jira project key is not configured"
+msgstr ""
+
msgid "Jira project: %{importProject}"
msgstr ""
@@ -12570,6 +12917,9 @@ msgstr ""
msgid "Keep divergent refs"
msgstr ""
+msgid "Kerberos access denied"
+msgstr ""
+
msgid "Key"
msgstr ""
@@ -12579,12 +12929,15 @@ msgstr "Chave (PEM)"
msgid "Key: %{key}"
msgstr "Chave: %{key}"
-msgid "Keyboard Shortcuts"
-msgstr "Atalhos de Teclado"
-
msgid "Keyboard shortcuts"
msgstr ""
+msgid "Keys"
+msgstr ""
+
+msgid "Ki"
+msgstr ""
+
msgid "Kubernetes"
msgstr "Kubernetes"
@@ -12624,6 +12977,9 @@ msgstr ""
msgid "LDAP"
msgstr ""
+msgid "LDAP Synchronization"
+msgstr ""
+
msgid "LDAP settings"
msgstr "Configurações LDAP"
@@ -12848,12 +13204,18 @@ msgstr "Saiba mas sobre aprovações."
msgid "Learn more about custom project templates"
msgstr "Saiba mais sobre os modelos de projetos personalizados"
+msgid "Learn more about deploying to AWS"
+msgstr ""
+
msgid "Learn more about deploying to a cluster"
msgstr ""
msgid "Learn more about group-level project templates"
msgstr "Saiba mais sobre os modelos de projeto de nível de grupo"
+msgid "Learn more about job dependencies"
+msgstr ""
+
msgid "Learn more about signing commits"
msgstr "Saiba mais sobre assinar commits"
@@ -12905,9 +13267,21 @@ msgstr ""
msgid "License History"
msgstr ""
+msgid "License ID:"
+msgstr ""
+
+msgid "License URL"
+msgstr ""
+
msgid "License-Check"
msgstr ""
+msgid "LicenseCompliance|%{docLinkStart}License Approvals%{docLinkEnd} are active"
+msgstr ""
+
+msgid "LicenseCompliance|%{docLinkStart}License Approvals%{docLinkEnd} are inactive"
+msgstr ""
+
msgid "LicenseCompliance|Add a license"
msgstr ""
@@ -12929,9 +13303,15 @@ msgstr ""
msgid "LicenseCompliance|Deny"
msgstr ""
+msgid "LicenseCompliance|Learn more about %{linkStart}License Approvals%{linkEnd}"
+msgstr ""
+
msgid "LicenseCompliance|License"
msgstr ""
+msgid "LicenseCompliance|License Approvals"
+msgstr ""
+
msgid "LicenseCompliance|License Compliance detected %d license and policy violation for the source branch only; approval required"
msgid_plural "LicenseCompliance|License Compliance detected %d licenses and policy violations for the source branch only; approval required"
msgstr[0] ""
@@ -13152,6 +13532,9 @@ msgstr "Exibir em lista"
msgid "List your Bitbucket Server repositories"
msgstr "Liste seus repositórios do servidor Bitbucket"
+msgid "Lists"
+msgstr ""
+
msgid "Live preview"
msgstr "Pré-visualização ao vivo"
@@ -13335,6 +13718,9 @@ msgstr ""
msgid "Manage project labels"
msgstr "Gerenciar etiquetas de projetos"
+msgid "Manage storage usage"
+msgstr ""
+
msgid "Manage two-factor authentication"
msgstr "Gerenciar a autenticação de dois fatores"
@@ -13350,6 +13736,9 @@ msgstr "Manifesto"
msgid "Manifest file import"
msgstr "Importação de arquivo de manifesto"
+msgid "Manifest import"
+msgstr ""
+
msgid "Manual job"
msgstr ""
@@ -13497,6 +13886,12 @@ msgstr ""
msgid "Maximum field length"
msgstr ""
+msgid "Maximum file size is 2MB. Please select a smaller file."
+msgstr ""
+
+msgid "Maximum import size (MB)"
+msgstr ""
+
msgid "Maximum job timeout"
msgstr "Tempo limite máximo da tarefa"
@@ -13536,6 +13931,9 @@ msgstr ""
msgid "Maximum size of Elasticsearch bulk indexing requests."
msgstr ""
+msgid "Maximum size of import files."
+msgstr ""
+
msgid "Maximum size of individual attachments in comments."
msgstr ""
@@ -13587,6 +13985,9 @@ msgstr ""
msgid "Merge (when the pipeline succeeds)"
msgstr ""
+msgid "Merge Conflicts"
+msgstr ""
+
msgid "Merge Request"
msgstr "Merge Request"
@@ -13713,6 +14114,9 @@ msgstr "Falha ao salvar comentário"
msgid "MergeRequests|Squash task canceled: another squash is already in progress."
msgstr ""
+msgid "MergeRequests|This project does not allow squashing commits when merge requests are accepted."
+msgstr ""
+
msgid "MergeRequests|Thread stays resolved"
msgstr ""
@@ -13866,6 +14270,12 @@ msgstr ""
msgid "MetricsSettings|Add a button to the metrics dashboard linking directly to your existing external dashboard."
msgstr ""
+msgid "MetricsSettings|Choose whether to display dashboard metrics in UTC or the user's local timezone."
+msgstr ""
+
+msgid "MetricsSettings|Dashboard timezone"
+msgstr ""
+
msgid "MetricsSettings|External dashboard URL"
msgstr ""
@@ -13875,6 +14285,12 @@ msgstr ""
msgid "MetricsSettings|Metrics Dashboard"
msgstr ""
+msgid "MetricsSettings|UTC (Coordinated Universal Time)"
+msgstr ""
+
+msgid "MetricsSettings|User's local timezone"
+msgstr ""
+
msgid "Metrics|Add metric"
msgstr "Adicionar métrica"
@@ -13890,6 +14306,9 @@ msgstr ""
msgid "Metrics|Create metric"
msgstr "Criar métricas"
+msgid "Metrics|Current"
+msgstr ""
+
msgid "Metrics|Delete metric"
msgstr ""
@@ -13940,6 +14359,9 @@ msgstr ""
msgid "Metrics|Max"
msgstr ""
+msgid "Metrics|Min"
+msgstr ""
+
msgid "Metrics|Must be a valid PromQL query."
msgstr "Deve ser uma consulta PromQL válida."
@@ -13955,6 +14377,9 @@ msgstr "Documentação de consulta do Prometheus"
msgid "Metrics|Refresh dashboard"
msgstr ""
+msgid "Metrics|Select a value"
+msgstr ""
+
msgid "Metrics|Star dashboard"
msgstr ""
@@ -13973,12 +14398,18 @@ msgstr "Houve um erro ao obter os dados do ambiente; por favor, tente novamente"
msgid "Metrics|There was an error getting annotations information."
msgstr ""
+msgid "Metrics|There was an error getting dashboard validation warnings information."
+msgstr ""
+
msgid "Metrics|There was an error getting deployment information."
msgstr "Houve um erro ao obter informações de deploy."
msgid "Metrics|There was an error getting environments information."
msgstr "Houve um erro ao obter informações dos ambientes."
+msgid "Metrics|There was an error getting options for variable \"%{name}\"."
+msgstr ""
+
msgid "Metrics|There was an error trying to validate your query"
msgstr ""
@@ -14021,6 +14452,9 @@ msgstr ""
msgid "Metrics|You're about to permanently delete this metric. This cannot be undone."
msgstr ""
+msgid "Metrics|Your dashboard schema is invalid. Edit the dashboard to correct the YAML schema."
+msgstr ""
+
msgid "Metrics|e.g. HTTP requests"
msgstr ""
@@ -14036,12 +14470,18 @@ msgstr ""
msgid "Metrics|e.g. req/sec"
msgstr ""
+msgid "Mi"
+msgstr ""
+
msgid "Microsoft Azure"
msgstr ""
msgid "Middleman project with Static Site Editor support"
msgstr ""
+msgid "Migrate your data from an external source like GitHub, Bitbucket, or another instance of GitLab."
+msgstr ""
+
msgid "Migrated %{success_count}/%{total_count} files."
msgstr ""
@@ -14176,6 +14616,9 @@ msgstr ""
msgid "Minimum capacity to be available before we schedule more mirrors preemptively."
msgstr "Capacidade mínima para estar disponível antes de agendar mais espelhos preventivamente."
+msgid "Minimum interval in days"
+msgstr ""
+
msgid "Minimum length is %{minimum_password_length} characters"
msgstr ""
@@ -14362,6 +14805,12 @@ msgstr ""
msgid "Multiple uploaders found: %{uploader_types}"
msgstr ""
+msgid "Must match with the %{codeStart}external_url%{codeEnd} in %{codeStart}/etc/gitlab/gitlab.rb%{codeEnd}."
+msgstr ""
+
+msgid "Must match with the %{codeStart}geo_node_name%{codeEnd} in %{codeStart}/etc/gitlab/gitlab.rb%{codeEnd}. %{linkStart}More information%{linkEnd}"
+msgstr ""
+
msgid "My Awesome Group"
msgstr ""
@@ -14476,10 +14925,16 @@ msgstr "Novo"
msgid "New Application"
msgstr "Novo aplicativo"
+msgid "New Branch"
+msgstr ""
+
+msgid "New Deploy Key"
+msgstr ""
+
msgid "New Environment"
msgstr ""
-msgid "New Geo Node"
+msgid "New File"
msgstr ""
msgid "New Group"
@@ -14496,15 +14951,15 @@ msgid_plural "New Issues"
msgstr[0] "Nova Issue"
msgstr[1] "Novas Issues"
-msgid "New Iteration"
-msgstr ""
-
msgid "New Jira import"
msgstr ""
msgid "New Label"
msgstr "Nova etiqueta"
+msgid "New Merge Request"
+msgstr ""
+
msgid "New Milestone"
msgstr "Novo Marco"
@@ -14523,6 +14978,9 @@ msgstr ""
msgid "New Snippet"
msgstr "Novo Snippet"
+msgid "New User"
+msgstr ""
+
msgid "New branch"
msgstr "Novo branch"
@@ -14565,6 +15023,9 @@ msgstr "Nova issue"
msgid "New issue title"
msgstr ""
+msgid "New iteration"
+msgstr ""
+
msgid "New iteration created"
msgstr ""
@@ -14742,6 +15203,12 @@ msgstr ""
msgid "No grouping"
msgstr ""
+msgid "No iteration"
+msgstr ""
+
+msgid "No iterations to show"
+msgstr ""
+
msgid "No job log"
msgstr ""
@@ -14784,6 +15251,9 @@ msgstr "Sem marcos para mostrar"
msgid "No other labels with such name or description"
msgstr "Sem outras etiquetas com esse nome ou descrição"
+msgid "No panels matching properties %{opts}"
+msgstr ""
+
msgid "No parent group"
msgstr ""
@@ -14817,6 +15287,9 @@ msgstr "Nenhum runner encontrado"
msgid "No schedules"
msgstr "Nenhum agendamento"
+msgid "No stack trace for this error"
+msgstr ""
+
msgid "No starrers matched your search"
msgstr ""
@@ -14829,7 +15302,7 @@ msgstr "Nenhum modelo"
msgid "No test coverage"
msgstr ""
-msgid "No thanks, don't show this again"
+msgid "No thanks"
msgstr ""
msgid "No vulnerabilities present"
@@ -14844,9 +15317,6 @@ msgstr ""
msgid "No, directly import the existing email addresses and usernames."
msgstr "Não, importe diretamente os endereços de e-mail e nomes de usuários existentes."
-msgid "No, not interested right now"
-msgstr ""
-
msgid "No. of commits"
msgstr ""
@@ -14892,9 +15362,6 @@ msgstr "Dados insuficientes"
msgid "Not found."
msgstr "Não encontrado."
-msgid "Not helpful"
-msgstr ""
-
msgid "Not now"
msgstr "Agora não"
@@ -15051,6 +15518,9 @@ msgstr "Notificações deligadas"
msgid "Notifications on"
msgstr "Notificações ligadas"
+msgid "Notify users by email when sign-in location is not recognized"
+msgstr ""
+
msgid "Nov"
msgstr "Nov"
@@ -15117,9 +15587,6 @@ msgstr "Filtrar"
msgid "Oh no!"
msgstr ""
-msgid "Ok let's go"
-msgstr "Ok, vamos lá"
-
msgid "Oldest first"
msgstr ""
@@ -15132,17 +15599,50 @@ msgstr ""
msgid "On track"
msgstr ""
+msgid "OnDemandScans|Attached branch"
+msgstr ""
+
+msgid "OnDemandScans|Attached branch is where the scan job runs."
+msgstr ""
+
+msgid "OnDemandScans|Could not run the scan. Please try again."
+msgstr ""
+
msgid "OnDemandScans|Create new DAST scan"
msgstr ""
+msgid "OnDemandScans|DAST will scan the target URL and any discovered sub URLs."
+msgstr ""
+
+msgid "OnDemandScans|New on-demand DAST scan"
+msgstr ""
+
msgid "OnDemandScans|On-demand Scans"
msgstr ""
+msgid "OnDemandScans|On-demand scans run outside the DevOps cycle and find vulnerabilities in your projects. %{learnMoreLinkStart}Learn more%{learnMoreLinkEnd}"
+msgstr ""
+
+msgid "OnDemandScans|Only a passive scan can be performed on demand."
+msgstr ""
+
+msgid "OnDemandScans|Passive DAST Scan"
+msgstr ""
+
+msgid "OnDemandScans|Please enter a valid URL format, ex: http://www.example.com/home"
+msgstr ""
+
+msgid "OnDemandScans|Run this scan"
+msgstr ""
+
+msgid "OnDemandScans|Scan mode"
+msgstr ""
+
msgid "OnDemandScans|Schedule or run scans immediately against target sites. Currently available on-demand scan type: DAST. %{helpLinkStart}More information%{helpLinkEnd}"
msgstr ""
-msgid "Onboarding"
-msgstr "Começando"
+msgid "OnDemandScans|Target URL"
+msgstr ""
msgid "Once imported, repositories can be mirrored over SSH. Read more %{link_start}here%{link_end}."
msgstr "Uma vez importados, os repositórios podem ser espelhados por SSH. Leia mais %{link_start}aqui%{link_end}."
@@ -15167,6 +15667,9 @@ msgstr ""
msgid "One or more of you personal access tokens were revoked"
msgstr ""
+msgid "One or more of your %{provider} projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
+msgstr ""
+
msgid "One or more of your Bitbucket projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
msgstr "Um ou mais dos seus projetos do Bitbucket não podem ser importados diretamente no GitLab porque eles usam Subversion ou Mercurial para o controle de versão, ao invés de Git."
@@ -15668,6 +16171,9 @@ msgstr ""
msgid "Parent epic is not present."
msgstr ""
+msgid "Parsing error for param :embed_json. %{message}"
+msgstr ""
+
msgid "Part of merge request changes"
msgstr "Parte das mudanças do merge request"
@@ -15680,6 +16186,9 @@ msgstr ""
msgid "Passed"
msgstr ""
+msgid "Passed on"
+msgstr ""
+
msgid "Password"
msgstr "Senha"
@@ -15764,6 +16273,9 @@ msgstr ""
msgid "Perform common operations on GitLab project"
msgstr ""
+msgid "Performance and resource management"
+msgstr ""
+
msgid "Performance optimization"
msgstr "Otimização de performance"
@@ -15971,8 +16483,8 @@ msgstr "Executar Pipeline"
msgid "Pipelines|Something went wrong while cleaning runners cache."
msgstr "Algo deu errado ao limpar o cache dos runners."
-msgid "Pipelines|There are currently no %{scope} pipelines."
-msgstr "Atualmente, não há pipelines de %{scope}."
+msgid "Pipelines|There are currently no finished pipelines."
+msgstr ""
msgid "Pipelines|There are currently no pipelines."
msgstr "Atualmente não há pipelines."
@@ -16082,6 +16594,9 @@ msgstr "Parar pipeline"
msgid "Pipeline|Stop pipeline #%{pipelineId}?"
msgstr "Parar pipeline #%{pipelineId}?"
+msgid "Pipeline|Tag name"
+msgstr ""
+
msgid "Pipeline|Trigger author"
msgstr ""
@@ -16163,12 +16678,18 @@ msgstr ""
msgid "Please check your email (%{email}) to verify that you own this address and unlock the power of CI/CD. Didn't receive it? %{resend_link}. Wrong email address? %{update_link}."
msgstr ""
+msgid "Please choose a file"
+msgstr ""
+
msgid "Please choose a group URL with no special characters."
msgstr "Por favor, escolha um URL de grupo sem caracteres especiais."
msgid "Please complete your profile with email address"
msgstr "Por favor, complete seu perfil com endereço de e-mail"
+msgid "Please convert %{linkStart}them to Git%{linkEnd}, and go through the %{linkToImportFlow} again."
+msgstr ""
+
msgid "Please convert them to %{link_to_git}, and go through the %{link_to_import_flow} again."
msgstr "Por favor, converta-os para %{link_to_git} e passe pelo %{link_to_import_flow} novamente."
@@ -16226,6 +16747,9 @@ msgstr ""
msgid "Please provide a valid email address."
msgstr ""
+msgid "Please provide attributes to update"
+msgstr ""
+
msgid "Please refer to <a href=\"%{docs_url}\">%{docs_url}</a>"
msgstr ""
@@ -16961,6 +17485,9 @@ msgstr ""
msgid "Project has too many %{label_for_message} to search"
msgstr ""
+msgid "Project is required when cluster_type is :project"
+msgstr ""
+
msgid "Project members"
msgstr "Membros do projeto"
@@ -17078,7 +17605,40 @@ msgstr "%{service_title}: status ligado"
msgid "ProjectService|Comment"
msgstr ""
-msgid "ProjectService|Comment will be posted on each event"
+msgid "ProjectService|Event will be triggered by a push to the repository"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a commit is created/updated"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a confidential issue is created/updated/closed"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a deployment finishes"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a merge request is created/updated/merged"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a new tag is pushed to the repository"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a new, unique alert is recorded"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a pipeline status changes"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a wiki page is created/updated"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when an issue is created/updated/closed"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when someone adds a comment"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when someone adds a comment on a confidential issue"
msgstr ""
msgid "ProjectService|Perform common operations on GitLab project: %{project_name}"
@@ -17129,6 +17689,9 @@ msgstr ""
msgid "ProjectSettings|Enable 'Delete source branch' option by default"
msgstr ""
+msgid "ProjectSettings|Enable merge trains and pipelines for merged results"
+msgstr ""
+
msgid "ProjectSettings|Every merge creates a merge commit"
msgstr "Cada merge cria um commit de merge"
@@ -17159,7 +17722,7 @@ msgstr "Merges com fast-forward apenas"
msgid "ProjectSettings|Forks"
msgstr ""
-msgid "ProjectSettings|Git Large File Storage"
+msgid "ProjectSettings|Git Large File Storage (LFS)"
msgstr ""
msgid "ProjectSettings|Internal"
@@ -17195,9 +17758,6 @@ msgstr "Método de merge"
msgid "ProjectSettings|Merge options"
msgstr "Opções de merge"
-msgid "ProjectSettings|Merge pipelines will try to validate the post-merge result prior to merging"
-msgstr "Pipelines de merge vão tentar validar o resultado pós-merge antes de realizar o merge"
-
msgid "ProjectSettings|Merge requests"
msgstr ""
@@ -17225,6 +17785,9 @@ msgstr ""
msgid "ProjectSettings|Pipelines"
msgstr ""
+msgid "ProjectSettings|Pipelines for merge requests must be enabled in the CI/CD configuration file, or pipelines could be unresolvable or dropped"
+msgstr ""
+
msgid "ProjectSettings|Pipelines must succeed"
msgstr "Pipelines devem ter êxito"
@@ -17252,6 +17815,9 @@ msgstr ""
msgid "ProjectSettings|Show link to create/view merge request when pushing from the command line"
msgstr "Mostrar link para criar/ver merge request ao fazer push da linha de comando"
+msgid "ProjectSettings|Skipped pipelines are considered successful"
+msgstr ""
+
msgid "ProjectSettings|Snippets"
msgstr ""
@@ -17267,6 +17833,9 @@ msgstr ""
msgid "ProjectSettings|These checks must pass before merge requests can be merged"
msgstr ""
+msgid "ProjectSettings|This introduces the risk of merging changes that will not pass the pipeline."
+msgstr ""
+
msgid "ProjectSettings|This setting is applied on the server level and can be overridden by an admin."
msgstr "Essa configuração está aplicada no nível do servidor e pode ser sobrescrita por um administrador."
@@ -17444,15 +18013,27 @@ msgstr "Em branco"
msgid "ProjectsNew|Blank project"
msgstr "Projeto em branco"
+msgid "ProjectsNew|Connect your external repository to GitLab CI/CD."
+msgstr ""
+
msgid "ProjectsNew|Contact an administrator to enable options for importing your project."
msgstr "Contate um administrador para habilitar opções para importar seu projeto."
msgid "ProjectsNew|Create"
msgstr ""
+msgid "ProjectsNew|Create a blank project to house your files, plan your work, and collaborate on code, among other things."
+msgstr ""
+
+msgid "ProjectsNew|Create blank project"
+msgstr ""
+
msgid "ProjectsNew|Create from template"
msgstr ""
+msgid "ProjectsNew|Create new project"
+msgstr ""
+
msgid "ProjectsNew|Creating project & repository."
msgstr ""
@@ -17477,6 +18058,9 @@ msgstr ""
msgid "ProjectsNew|Project description %{tag_start}(optional)%{tag_end}"
msgstr ""
+msgid "ProjectsNew|Run CI/CD for external repository"
+msgstr ""
+
msgid "ProjectsNew|Template"
msgstr ""
@@ -17738,6 +18322,9 @@ msgstr "Protegido"
msgid "Protected Branch"
msgstr ""
+msgid "Protected Branches"
+msgstr ""
+
msgid "Protected Environment"
msgstr ""
@@ -17750,6 +18337,9 @@ msgstr ""
msgid "Protected Tag"
msgstr ""
+msgid "Protected Tags"
+msgstr ""
+
msgid "Protected branches"
msgstr ""
@@ -17849,6 +18439,9 @@ msgstr ""
msgid "Provider"
msgstr "Provedor"
+msgid "Proxy support for this API is not available currently"
+msgstr ""
+
msgid "Pseudonymizer data collection"
msgstr "Coleção de dados Pseudonymizer"
@@ -17873,6 +18466,9 @@ msgstr ""
msgid "Publish to status page"
msgstr ""
+msgid "Published on status page"
+msgstr ""
+
msgid "Publishes this issue to the associated status page."
msgstr ""
@@ -18047,6 +18643,9 @@ msgstr "Pesquisas recentes"
msgid "Recipe"
msgstr ""
+msgid "Reconfigure"
+msgstr ""
+
msgid "Recover hidden stage"
msgstr ""
@@ -18133,6 +18732,9 @@ msgstr ""
msgid "Registration|Your profile"
msgstr ""
+msgid "Regulate approvals by authors/committers, based on compliance frameworks. Can be changed only at the instance level."
+msgstr ""
+
msgid "Rejected (closed)"
msgstr ""
@@ -18183,15 +18785,24 @@ msgstr ""
msgid "ReleaseAssetLinkType|Image"
msgstr ""
+msgid "ReleaseAssetLinkType|Images"
+msgstr ""
+
msgid "ReleaseAssetLinkType|Other"
msgstr ""
msgid "ReleaseAssetLinkType|Package"
msgstr ""
+msgid "ReleaseAssetLinkType|Packages"
+msgstr ""
+
msgid "ReleaseAssetLinkType|Runbook"
msgstr ""
+msgid "ReleaseAssetLinkType|Runbooks"
+msgstr ""
+
msgid "Releases"
msgstr "Versões"
@@ -18204,6 +18815,9 @@ msgstr ""
msgid "Releases documentation"
msgstr ""
+msgid "Releases|New Release"
+msgstr ""
+
msgid "Release|Something went wrong while getting the release details"
msgstr ""
@@ -18276,6 +18890,9 @@ msgstr ""
msgid "Remove fork relationship"
msgstr "Remover relacionamento de fork"
+msgid "Remove from batch"
+msgstr ""
+
msgid "Remove from board"
msgstr ""
@@ -18465,6 +19082,12 @@ msgstr ""
msgid "Replication"
msgstr ""
+msgid "Replication enabled"
+msgstr ""
+
+msgid "Replication paused"
+msgstr ""
+
msgid "Reply by email"
msgstr ""
@@ -18492,15 +19115,15 @@ msgstr ""
msgid "Reported %{timeAgo} by %{reportedBy}"
msgstr ""
-msgid "Reported Resource Changes: %{addNum} to add, %{changeNum} to change, %{deleteNum} to delete"
-msgstr ""
-
msgid "Reporter"
msgstr ""
msgid "Reporting"
msgstr "Reportando"
+msgid "Reports"
+msgstr ""
+
msgid "Reports|%{combinedString} and %{resolvedString}"
msgstr ""
@@ -18539,6 +19162,9 @@ msgstr "Tempo de execução"
msgid "Reports|Failure"
msgstr "Falha"
+msgid "Reports|Identifier"
+msgstr ""
+
msgid "Reports|Metrics reports are loading"
msgstr ""
@@ -18551,6 +19177,9 @@ msgstr ""
msgid "Reports|Metrics reports failed loading results"
msgstr ""
+msgid "Reports|Scanner"
+msgstr ""
+
msgid "Reports|Severity"
msgstr "Gravidade"
@@ -18599,15 +19228,33 @@ msgstr "Limpeza do repositório"
msgid "Repository cleanup has started. You will receive an email once the cleanup operation is complete."
msgstr "A limpeza do repositório já começou. Você receberá um e-mail assim que a operação de limpeza for concluída."
+msgid "Repository files count over the limit"
+msgstr ""
+
+msgid "Repository has an invalid default branch name."
+msgstr ""
+
+msgid "Repository has more than one branch."
+msgstr ""
+
msgid "Repository has no locks."
msgstr "O repositório não possui bloqueios."
+msgid "Repository has tags."
+msgstr ""
+
msgid "Repository maintenance"
msgstr "Manutenção do repositório"
msgid "Repository mirroring"
msgstr ""
+msgid "Repository must contain at least 1 file."
+msgstr ""
+
+msgid "Repository size is above the limit."
+msgstr ""
+
msgid "Repository static objects"
msgstr ""
@@ -18617,8 +19264,8 @@ msgstr "Armazenamento do Repositório"
msgid "Repository sync capacity"
msgstr ""
-msgid "Repository: %{counter_repositories} / Wikis: %{counter_wikis} / Build Artifacts: %{counter_build_artifacts} / LFS: %{counter_lfs_objects}"
-msgstr "Repositório: %{counter_repositories} / Wikis: %{counter_wikis} / Artefatos de compilação: %{counter_build_artifacts} / LFS: %{counter_lfs_objects}"
+msgid "Repository: %{counter_repositories} / Wikis: %{counter_wikis} / Build Artifacts: %{counter_build_artifacts} / LFS: %{counter_lfs_objects} / Snippets: %{counter_snippets}"
+msgstr ""
msgid "RepositorySettingsAccessLevel|Select"
msgstr "Selecionar"
@@ -18629,6 +19276,9 @@ msgstr "Solicitar acesso"
msgid "Request Headers"
msgstr ""
+msgid "Request details"
+msgstr ""
+
msgid "Request parameter %{param} is missing."
msgstr ""
@@ -18735,6 +19385,9 @@ msgstr "Recriar o token de registro de runners"
msgid "Reset template"
msgstr "Redefinir modelo"
+msgid "Reset to project defaults"
+msgstr ""
+
msgid "Resetting the authorization key for this project will require updating the authorization key in every alert source it is enabled in."
msgstr ""
@@ -18831,9 +19484,6 @@ msgstr ""
msgid "Resume"
msgstr "Continuar"
-msgid "Resume replication"
-msgstr ""
-
msgid "Resync"
msgstr ""
@@ -19456,6 +20106,9 @@ msgstr ""
msgid "SecurityReports|False positive"
msgstr ""
+msgid "SecurityReports|Group Security Dashboard"
+msgstr ""
+
msgid "SecurityReports|Hide dismissed"
msgstr ""
@@ -19510,10 +20163,13 @@ msgstr ""
msgid "SecurityReports|Remove project from dashboard"
msgstr ""
-msgid "SecurityReports|Report type"
+msgid "SecurityReports|Return to dashboard"
msgstr ""
-msgid "SecurityReports|Return to dashboard"
+msgid "SecurityReports|Scan details"
+msgstr ""
+
+msgid "SecurityReports|Scanner type"
msgstr ""
msgid "SecurityReports|Security Dashboard"
@@ -19570,6 +20226,9 @@ msgstr ""
msgid "SecurityReports|There was an error while generating the report."
msgstr ""
+msgid "SecurityReports|Unable to add %{invalidProjectsMessage}"
+msgstr ""
+
msgid "SecurityReports|Unable to add %{invalidProjects}"
msgstr ""
@@ -19684,6 +20343,9 @@ msgstr "Selecionar o branch/tag"
msgid "Select due date"
msgstr ""
+msgid "Select file"
+msgstr ""
+
msgid "Select group or project"
msgstr "Selecionar grupo ou projeto"
@@ -19747,9 +20409,6 @@ msgstr "Selecionar branch de destino"
msgid "Select the branch you want to set as the default for this project. All merge requests and commits will automatically be made against this branch unless you specify a different one."
msgstr "Selecione o branch que você deseja definir como o padrão para este projeto. Todas os merges requests e commits serão feitos automaticamente nesse branch, a menos que você especifique um diferente."
-msgid "Select the configured storage available for new repositories to be placed on."
-msgstr ""
-
msgid "Select the custom project template source group."
msgstr "Selecione o grupo de origem dos modelos customizados de projeto."
@@ -19942,12 +20601,6 @@ msgstr "Balcão de Atendimento"
msgid "Service Desk is enabled but not yet active"
msgstr ""
-msgid "Service Desk is off"
-msgstr ""
-
-msgid "Service Desk is on"
-msgstr ""
-
msgid "Service Templates"
msgstr "Modelos de serviço"
@@ -20035,6 +20688,15 @@ msgstr ""
msgid "Set the milestone to %{milestone_reference}."
msgstr ""
+msgid "Set the number of concurrent requests this secondary node will make to the primary node while backfilling."
+msgstr ""
+
+msgid "Set the synchronization and verification capacity for the secondary node."
+msgstr ""
+
+msgid "Set the timeout in seconds to send a secondary node status to the primary and IPs allowed for the secondary nodes."
+msgstr ""
+
msgid "Set time estimate"
msgstr ""
@@ -20074,6 +20736,9 @@ msgstr ""
msgid "Set weight to %{weight}."
msgstr ""
+msgid "Set what should be replicated by choosing specific projects or groups by the secondary node."
+msgstr ""
+
msgid "SetPasswordToCloneLink|set a password"
msgstr "defina uma senha"
@@ -20200,6 +20865,9 @@ msgstr "Mostrar descrição do commit"
msgid "Show complete raw log"
msgstr "Visualizar raw log completo"
+msgid "Show details"
+msgstr ""
+
msgid "Show file browser"
msgstr ""
@@ -20209,10 +20877,13 @@ msgstr ""
msgid "Show latest version"
msgstr "Mostrar a versão mais recente"
+msgid "Show list"
+msgstr ""
+
msgid "Show me everything"
msgstr ""
-msgid "Show me how"
+msgid "Show me how to add a pipeline"
msgstr ""
msgid "Show me more advanced stuff"
@@ -20261,9 +20932,6 @@ msgstr ""
msgid "Showing all issues"
msgstr ""
-msgid "Showing all labels"
-msgstr ""
-
msgid "Showing last %{size} of log -"
msgstr "Mostrando os últimos %{size} de log -"
@@ -20375,9 +21043,6 @@ msgstr ""
msgid "Skip outdated deployment jobs"
msgstr ""
-msgid "Skip this for now"
-msgstr "Ignorar isso por hora"
-
msgid "Skipped"
msgstr ""
@@ -20522,6 +21187,9 @@ msgstr "Algo deu errado ao alternar o botão"
msgid "Something went wrong while adding your award. Please try again."
msgstr "Algo deu errado ao adicionar seu prêmio. Por favor, tente novamente."
+msgid "Something went wrong while applying the batch of suggestions. Please try again."
+msgstr ""
+
msgid "Something went wrong while applying the suggestion. Please try again."
msgstr ""
@@ -20624,10 +21292,13 @@ msgstr ""
msgid "Something went wrong while updating a requirement."
msgstr ""
+msgid "Something went wrong while updating assignees"
+msgstr ""
+
msgid "Something went wrong while updating your list settings"
msgstr ""
-msgid "Something went wrong with your automatic subscription renewal"
+msgid "Something went wrong with your automatic subscription renewal."
msgstr ""
msgid "Something went wrong, unable to add %{project} to dashboard"
@@ -20645,6 +21316,9 @@ msgstr "Algo deu errado, não é possível remover o projeto"
msgid "Something went wrong, unable to search projects"
msgstr ""
+msgid "Something went wrong."
+msgstr ""
+
msgid "Something went wrong. Please try again."
msgstr "Algo deu errado. Por favor, tente novamente."
@@ -20828,6 +21502,9 @@ msgstr "Fonte (branch or tag)"
msgid "Source code"
msgstr "Código-fonte"
+msgid "Source code (%{fileExtension})"
+msgstr ""
+
msgid "Source is not available"
msgstr "Origem não está disponível"
@@ -20912,6 +21589,9 @@ msgstr "Squash commits"
msgid "Stack trace"
msgstr "Stack trace"
+msgid "Stacktrace snippet"
+msgstr ""
+
msgid "Stage"
msgstr "Colocar na lista para commit"
@@ -21017,6 +21697,9 @@ msgstr ""
msgid "Start thread & reopen %{noteable_name}"
msgstr ""
+msgid "Start using Directed Acyclic Graphs (DAG)"
+msgstr ""
+
msgid "Start your Free Gold Trial"
msgstr ""
@@ -21479,6 +22162,21 @@ msgstr "Verde limão muito escuro"
msgid "SuggestedColors|Very pale orange"
msgstr "Laranja muito pálido"
+msgid "Suggestion is not applicable as the suggestion was not found."
+msgstr ""
+
+msgid "Suggestions are not applicable as one or more suggestions were not found."
+msgstr ""
+
+msgid "Suggestions are not applicable as their lines cannot overlap."
+msgstr ""
+
+msgid "Suggestions must all be on the same branch."
+msgstr ""
+
+msgid "Suggestions that change line count can't be added to batches, yet."
+msgstr ""
+
msgid "Suggestions:"
msgstr "Sugestões:"
@@ -21521,9 +22219,6 @@ msgstr ""
msgid "Synchronization disabled"
msgstr ""
-msgid "Synchronization of container repositories is disabled."
-msgstr ""
-
msgid "System"
msgstr "Sistema"
@@ -21704,6 +22399,34 @@ msgstr "Contrato de Termos de Serviço e Política de Privacidade"
msgid "Terms of Service and Privacy Policy"
msgstr "Termos de Serviço e Política de Privacidade"
+msgid "Terraform|%{number} Terraform report failed to generate"
+msgid_plural "Terraform|%{number} Terraform reports failed to generate"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Terraform|%{number} Terraform report was generated in your pipelines"
+msgid_plural "Terraform|%{number} Terraform reports were generated in your pipelines"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Terraform|A Terraform report failed to generate."
+msgstr ""
+
+msgid "Terraform|A Terraform report was generated in your pipelines."
+msgstr ""
+
+msgid "Terraform|Generating the report caused an error."
+msgstr ""
+
+msgid "Terraform|Reported Resource Changes: %{addNum} to add, %{changeNum} to change, %{deleteNum} to delete"
+msgstr ""
+
+msgid "Terraform|The Terraform report %{name} failed to generate."
+msgstr ""
+
+msgid "Terraform|The Terraform report %{name} was generated in your pipelines."
+msgstr ""
+
msgid "Test"
msgstr ""
@@ -21730,7 +22453,7 @@ msgstr ""
msgid "TestHooks|Ensure the project has CI pipelines."
msgstr ""
-msgid "TestHooks|Ensure the project has at least one commit."
+msgid "TestHooks|Ensure the project has deployments."
msgstr ""
msgid "TestHooks|Ensure the project has issues."
@@ -21831,7 +22554,7 @@ msgstr "O Rastreador de Issue é o lugar para adicionar coisas que precisam ser
msgid "The Prometheus server responded with \"bad request\". Please check your queries are correct and are supported in your Prometheus version. %{documentationLink}"
msgstr ""
-msgid "The URL defined on the primary node that secondary nodes should use to contact it. Defaults to URL"
+msgid "The URL defined on the primary node that secondary nodes should use to contact it."
msgstr ""
msgid "The URL to use for connecting to Elasticsearch. Use a comma-separated list to support clustering (e.g., \"http://localhost:9200, http://localhost:9201\")."
@@ -21840,6 +22563,9 @@ msgstr ""
msgid "The X509 Certificate to use when mutual TLS is required to communicate with the external authorization service. If left blank, the server certificate is still validated when accessing over HTTPS."
msgstr "O Certificado X509 a ser usado quando o TLS mútuo é necessário para se comunicar com o serviço de autorização externa. Se deixado em branco, o certificado do servidor ainda será validado ao acessar por HTTPS."
+msgid "The above settings apply to all projects with the selected compliance framework(s)."
+msgstr ""
+
msgid "The amount of seconds after which a request to get a secondary node status will time out."
msgstr ""
@@ -22139,9 +22865,6 @@ msgstr "O tempo necessário por cada entrada de dados reunida por essa etapa."
msgid "The total stage shows the time it takes between creating an issue and deploying the code to production. The data will be automatically added once you have completed the full idea to production cycle."
msgstr ""
-msgid "The unique identifier for the Geo node. Must match %{geoNodeName} if it is set in gitlab.rb, otherwise it must match %{externalUrl} with a trailing slash"
-msgstr ""
-
msgid "The update action will time out after %{number_of_minutes} minutes. For big repositories, use a clone/push combination."
msgstr "A ação de atualização irá expirar depois de %{number_of_minutes} minutos. Para grandes repositórios, use uma combinação de clone/push."
@@ -22166,9 +22889,6 @@ msgstr "O mapa do usuário é um mapeamento dos usuários do FogBugz que partici
msgid "The user you are trying to deactivate has been active in the past %{minimum_inactive_days} days and cannot be deactivated"
msgstr ""
-msgid "The user-facing URL of the Geo node"
-msgstr ""
-
msgid "The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6."
msgstr "O valor situado no ponto médio de uma série de valores observados. Ex., entre 3, 5, 9, a mediana é 5. Entre 3, 5, 7, 8, a mediana é (5+7)/2 = 6."
@@ -22259,6 +22979,9 @@ msgstr "Houve um problema de comunicação com o seu dispositivo."
msgid "There was a problem fetching project branches."
msgstr ""
+msgid "There was a problem fetching project tags."
+msgstr ""
+
msgid "There was a problem fetching project users."
msgstr ""
@@ -22310,6 +23033,9 @@ msgstr ""
msgid "There was an error fetching the %{replicableType}"
msgstr ""
+msgid "There was an error fetching the Geo Settings"
+msgstr ""
+
msgid "There was an error fetching the Node's Groups"
msgstr ""
@@ -22430,6 +23156,12 @@ msgstr ""
msgid "This %{issuable} is locked. Only <strong>project members</strong> can comment."
msgstr ""
+msgid "This %{noteableTypeText} is %{confidentialLinkStart}confidential%{linkEnd} and %{lockedLinkStart}locked%{linkEnd}."
+msgstr ""
+
+msgid "This %{noteableTypeText} is locked."
+msgstr ""
+
msgid "This %{viewer} could not be displayed because %{reason}. You can %{options} instead."
msgstr ""
@@ -22448,8 +23180,11 @@ msgstr ""
msgid "This action can lead to data loss. To prevent accidental actions we ask you to confirm your intention."
msgstr ""
+msgid "This also resolves all related threads"
+msgstr ""
+
msgid "This also resolves the discussion"
-msgstr "Isso também resolve a discussão"
+msgstr ""
msgid "This application was created by %{link_to_owner}."
msgstr "Esse aplicativo foi criado por %{link_to_owner}."
@@ -22538,6 +23273,9 @@ msgstr ""
msgid "This epic does not exist or you don't have sufficient permission."
msgstr ""
+msgid "This feature is currently in beta. We invite you to %{linkStart}give feedback%{linkEnd}."
+msgstr ""
+
msgid "This feature requires local storage to be enabled"
msgstr ""
@@ -22571,8 +23309,8 @@ msgstr ""
msgid "This is a Work in Progress"
msgstr ""
-msgid "This is a confidential issue."
-msgstr "Essa issue é confidencial."
+msgid "This is a confidential %{noteableTypeText}."
+msgstr ""
msgid "This is a delayed job to run in %{remainingTime}"
msgstr "Esta tarefa foi atrasada para executar em %{remainingTime}"
@@ -22598,9 +23336,6 @@ msgstr ""
msgid "This is your current session"
msgstr "Esta é a sua sessão atual"
-msgid "This issue is %{confidentialLinkStart}confidential%{linkEnd} and %{lockedLinkStart}locked%{linkEnd}."
-msgstr ""
-
msgid "This issue is confidential"
msgstr "Essa issue é confidencial"
@@ -22610,9 +23345,6 @@ msgstr ""
msgid "This issue is in a child epic of the filtered epic"
msgstr ""
-msgid "This issue is locked."
-msgstr "Essa issue está bloqueada."
-
msgid "This job depends on other jobs with expired/erased artifacts: %{invalid_dependencies}"
msgstr ""
@@ -22712,6 +23444,9 @@ msgstr ""
msgid "This license has already expired."
msgstr ""
+msgid "This link points to external content"
+msgstr ""
+
msgid "This may expose confidential information as the selected fork is in another namespace that can have other members."
msgstr ""
@@ -22739,6 +23474,9 @@ msgstr "Esta página não está disponível porque você não tem permissão par
msgid "This page will be removed in a future release."
msgstr "Esta página será removida em uma versão futura."
+msgid "This pipeline does not use the %{codeStart}needs%{codeEnd} keyword and can't be represented as a directed acyclic graph."
+msgstr ""
+
msgid "This pipeline makes use of a predefined CI/CD configuration enabled by %{strongStart}Auto DevOps.%{strongEnd}"
msgstr ""
@@ -22967,6 +23705,9 @@ msgstr ""
msgid "Time from last commit to merge"
msgstr ""
+msgid "Time in seconds"
+msgstr ""
+
msgid "Time in seconds GitLab will wait for a response from the external service. When the service does not respond in time, access will be denied."
msgstr "Tempo em segundos o GitLab aguardará uma resposta do serviço externo. Quando o serviço não responder a tempo, o acesso será negado."
@@ -23388,9 +24129,15 @@ msgstr "Contribuições totais"
msgid "Total artifacts size: %{total_size}"
msgstr ""
+msgid "Total cores (CPUs)"
+msgstr ""
+
msgid "Total issues"
msgstr ""
+msgid "Total memory (GB)"
+msgstr ""
+
msgid "Total test time for all commits/merges"
msgstr "Tempo de teste total para todos os commits/merges"
@@ -23592,6 +24339,9 @@ msgstr "Dispositivos U2F (%{length})"
msgid "U2F only works with HTTPS-enabled websites. Contact your administrator for more details."
msgstr ""
+msgid "UI Development Kit"
+msgstr ""
+
msgid "URL"
msgstr "URL"
@@ -23664,9 +24414,6 @@ msgstr "Não é possível carregar o diff. %{button_try_again}"
msgid "Unable to load the merge request widget. Try reloading the page."
msgstr ""
-msgid "Unable to process group import file"
-msgstr ""
-
msgid "Unable to resolve"
msgstr ""
@@ -23682,6 +24429,9 @@ msgstr ""
msgid "Unable to sign you in to the group with SAML due to \"%{reason}\""
msgstr "Não é possível inscrever você no grupo com SAML devido a \"%{reason}\""
+msgid "Unable to suggest a path. Please refresh and try again."
+msgstr ""
+
msgid "Unable to update label prioritization at this time"
msgstr ""
@@ -23697,6 +24447,9 @@ msgstr "Desarquivar projeto"
msgid "Unarchiving the project will restore people's ability to make changes to it. The repository can be committed to, and issues, comments, and other entities can be created. %{strong_start}Once active, this project shows up in the search and on the dashboard.%{strong_end}"
msgstr ""
+msgid "Unassign from commenting user"
+msgstr ""
+
msgid "Unblock"
msgstr ""
@@ -23772,6 +24525,9 @@ msgstr ""
msgid "Unreachable"
msgstr ""
+msgid "Unrecognized cluster type"
+msgstr ""
+
msgid "Unresolve"
msgstr ""
@@ -23832,6 +24588,9 @@ msgstr ""
msgid "Unsubscribes from this %{quick_action_target}."
msgstr ""
+msgid "Unsupported todo type passed. Supported todo types are: %{todo_types}"
+msgstr ""
+
msgid "Until"
msgstr ""
@@ -23859,6 +24618,9 @@ msgstr ""
msgid "Update approval rule"
msgstr ""
+msgid "Update approvers"
+msgstr ""
+
msgid "Update failed"
msgstr ""
@@ -23868,6 +24630,9 @@ msgstr ""
msgid "Update it"
msgstr "Atualizar"
+msgid "Update iteration"
+msgstr ""
+
msgid "Update now"
msgstr "Atualizar agora"
@@ -23913,7 +24678,7 @@ msgstr "Atualizado em %{updated_at} por %{updated_by}"
msgid "Updated at"
msgstr ""
-msgid "Updated to"
+msgid "Updated to %{linkStart}chart v%{linkEnd}"
msgstr ""
msgid "Updating"
@@ -23943,6 +24708,9 @@ msgstr "Envie o <code>GoogleCodeProjectHosting.json</code> aqui:"
msgid "Upload CSV file"
msgstr ""
+msgid "Upload License"
+msgstr ""
+
msgid "Upload New File"
msgstr "Enviar Novo Arquivo"
@@ -24024,16 +24792,19 @@ msgstr ""
msgid "UsageQuota|Pipelines"
msgstr "Pipelines"
+msgid "UsageQuota|Purchase more storage"
+msgstr ""
+
msgid "UsageQuota|Repositories"
msgstr ""
msgid "UsageQuota|Repository"
msgstr ""
-msgid "UsageQuota|Storage"
+msgid "UsageQuota|Snippets"
msgstr ""
-msgid "UsageQuota|Storage usage:"
+msgid "UsageQuota|Storage"
msgstr ""
msgid "UsageQuota|This namespace has no projects which use shared runners"
@@ -24066,6 +24837,12 @@ msgstr ""
msgid "UsageQuota|Wikis"
msgstr ""
+msgid "UsageQuota|You used: %{usage} %{limit}"
+msgstr ""
+
+msgid "UsageQuota|out of %{formattedLimit} of your namespace storage"
+msgstr ""
+
msgid "Use %{code_start}::%{code_end} to create a %{link_start}scoped label set%{link_end} (eg. %{code_start}priority::1%{code_end})"
msgstr ""
@@ -24132,6 +24909,9 @@ msgstr "GrupoCoortes de usuário são mostradas somente quando os %{usage_ping_l
msgid "User IDs"
msgstr ""
+msgid "User Lists can only be created and modified with %{linkStart}the API%{linkEnd}"
+msgstr ""
+
msgid "User OAuth applications"
msgstr "Aplicativos OAuth do usuário"
@@ -24156,6 +24936,9 @@ msgstr ""
msgid "User key was successfully removed."
msgstr "A chave do usuário removida com sucesso."
+msgid "User list %{name} will be removed. Are you sure?"
+msgstr ""
+
msgid "User map"
msgstr "Mapa do usuário"
@@ -24177,148 +24960,10 @@ msgstr ""
msgid "User was successfully updated."
msgstr ""
-msgid "UserOnboardingTour|%{activeTour}/%{totalTours}"
-msgstr "%{activeTour}/%{totalTours}"
-
-msgid "UserOnboardingTour|%{completed}/%{total} steps completed"
-msgstr "%{completed}/%{total} passos concluídos"
-
-msgid "UserOnboardingTour|%{emphasisStart}Well done!%{emphasisEnd}%{lineBreak}%{lineBreak}That's it for our guided tour, congratulations for making it all the way to the end!%{lineBreak}%{lineBreak}We hope this gave you a good overview of GitLab and how it can help you. We'll now show you how to create your own project and invite your colleagues."
-msgstr "%{emphasisStart}Muito bem!%{emphasisEnd}%{lineBreak}%{lineBreak}Isso é tudo para a nossa visita guiada, parabéns por chegar até o fim!%{lineBreak}%{lineBreak}Esperamos que isso tenha lhe dado uma boa visão geral do GitLab e como ele pode ajudá-lo. Agora mostraremos como criar seu próprio projeto e convidar seus colegas."
-
-msgid "UserOnboardingTour|Adding other members to a project is done through Project Settings. Click on %{emphasisStart}Settings%{emphasisEnd}."
-msgstr "A adição de outros membros a um projeto é feita através das Configurações do Projeto. Clique em %{emphasisStart}Configurações%{emphasisEnd}."
-
-msgid "UserOnboardingTour|Alright, that's it for Commits. Let's take a look at the %{emphasisStart}Branches%{emphasisEnd}."
-msgstr "Certo, finalizamos os Commits. Vamos dar uma olhada nos %{emphasisStart}Branches%{emphasisEnd}."
-
-msgid "UserOnboardingTour|Awesome! Now click on %{emphasisStart}Members%{emphasisEnd}."
-msgstr "Incrível! Agora clique em %{emphasisStart}Membros%{emphasisEnd}."
-
-msgid "UserOnboardingTour|Click on one of the %{emphasisStart}Compare%{emphasisEnd} buttons to compare a branch to master."
-msgstr ""
-
-msgid "UserOnboardingTour|Click on one of the %{emphasisStart}pipeline IDs%{emphasisEnd} to see the details of a pipeline."
-msgstr ""
-
-msgid "UserOnboardingTour|Click to open the latest commit to see its details."
-msgstr ""
-
-msgid "UserOnboardingTour|Close 'Learn GitLab'"
-msgstr "Fechar 'Aprender GitLab'"
-
-msgid "UserOnboardingTour|Commits are shown in chronological order and can be filtered by the commit message or by the branch."
-msgstr ""
-
-msgid "UserOnboardingTour|Create a project"
-msgstr "Crie um projeto"
-
-msgid "UserOnboardingTour|Exit 'Learn GitLab'"
-msgstr ""
-
-msgid "UserOnboardingTour|Got it"
-msgstr "Entendi"
-
-msgid "UserOnboardingTour|Great job! %{clapHands} We hope the tour was helpful and that you learned how to use GitLab.%{lineBreak}%{lineBreak}We'd love to get your feedback on this tour.%{lineBreak}%{lineBreak}%{emphasisStart}How helpful would you say this guided tour was?%{emphasisEnd}%{lineBreak}%{lineBreak}"
-msgstr "Ótimo trabalho! %{clapHands} Esperamos que o tour tenha sido útil e que você tenha aprendido a usar o GitLab.%{lineBreak}%{lineBreak}Adoraríamos ter sua opinião sobre este tour.%{lineBreak}%{lineBreak}%{emphasisStart}Quão útil você diria que este tour foi?%{emphasisEnd}%{lineBreak}%{lineBreak}"
-
-msgid "UserOnboardingTour|Guided GitLab Tour"
-msgstr "Tour guiado do GitLab"
-
-msgid "UserOnboardingTour|Here you can compare the changes of this branch to another one. Changes are divided by files so that it's easier to see what was changed where."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can create a project from scratch, start with a template or import a repository from other platforms. Whatever you choose, we'll guide you through the process.%{lineBreak}%{lineBreak}Fill in your new project information and click on %{emphasisStart}Create Project%{emphasisEnd} to progress to the next step."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can see the breakdown of the pipelines: its stages and jobs in each of the stages and their status.%{lineBreak}%{lineBreak}Our CI/CD pipelines are quite complex, most of our users have fewer and simpler pipelines."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can see the current members of the project (just you at the moment) and invite new members.%{lineBreak}%{lineBreak}You can invite multiple members at once (existing GitLab users or invite by email) and you can also set their roles and permissions.%{lineBreak}%{lineBreak}Add a few members and click on %{emphasisStart}Add to project%{emphasisEnd} to complete this step."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can see what changes were made with this commit, on what branch and if there's a related merge request. The status of the pipeline will also show up if CI/CD is set up.%{lineBreak}%{lineBreak}You can also comment on the lines of code that were changed and start a discussion with your colleagues!"
-msgstr ""
-
-msgid "UserOnboardingTour|Here's an overview of branches in the %{emphasisStart}%{projectName}%{emphasisEnd} project. They're split into Active and Stale.%{lineBreak}%{lineBreak}From here, you can create a new merge request from a branch, or compare the branch to any other branch in the project. By default, it will compare it to the master branch."
-msgstr ""
-
-msgid "UserOnboardingTour|Invite colleagues"
-msgstr "Convide colegas"
-
-msgid "UserOnboardingTour|Issues are great for communicating and keeping track of progress in GitLab. These are all issues that are open in the %{emphasisStart}%{projectName}%{emphasisEnd}.%{lineBreak}%{lineBreak}You can help us improve GitLab by contributing work to issues that are labeled <span class=\"badge color-label accept-mr-label\">Accepting merge requests</span>.%{lineBreak}%{lineBreak}This list can be filtered by labels, milestones, assignees, authors... We'll show you how it looks when the list is filtered by a label."
-msgstr ""
-
-msgid "UserOnboardingTour|Learn GitLab"
-msgstr ""
-
-msgid "UserOnboardingTour|Let's take a closer look at a merge request. Click on the title of one."
-msgstr ""
-
-msgid "UserOnboardingTour|Let's take a closer look at all the commits. Click on %{emphasisStart}Commits%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Let's take a closer look at the repository of this project. Click on %{emphasisStart}Repository%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|No thanks"
-msgstr "Não obrigado"
-
-msgid "UserOnboardingTour|Ok, let's go"
-msgstr "Ok, vamos lá"
-
-msgid "UserOnboardingTour|Ok, show me"
-msgstr "Ok, mostre-me"
-
-msgid "UserOnboardingTour|Open one of the issues by clicking on its title."
-msgstr ""
-
-msgid "UserOnboardingTour|Restart this step"
-msgstr "Reiniciar esta etapa"
-
-msgid "UserOnboardingTour|Skip this step"
-msgstr ""
-
-msgid "UserOnboardingTour|Sweet! Your project was created and is ready to be used.%{lineBreak}%{lineBreak}You can start adding files to the repository or clone it. One last thing we want to show you is how to invite your colleagues to your new project."
-msgstr ""
-
-msgid "UserOnboardingTour|Take a look. Here's a nifty menu for quickly creating issues, merge requests, snippets, projects and groups. Click on it and select \"New project\" from the \"GitLab\" section to get started."
-msgstr ""
-
-msgid "UserOnboardingTour|Thanks for taking the guided tour. Remember, if you want to go through it again, you can start %{emphasisStart}Learn GitLab%{emphasisEnd} in the help menu on the top right."
-msgstr ""
-
-msgid "UserOnboardingTour|Thanks for the feedback! %{thumbsUp}"
-msgstr ""
-
-msgid "UserOnboardingTour|That's it for issues. Let'st take a look at %{emphasisStart}Merge Requests%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|That's it for merge requests. Now for the final part of this guided tour - the %{emphasisStart}CI/CD%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|That's it for the Repository. Let's take a look at the %{emphasisStart}Issues%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|The structure of this page is very similar to issues. Status, description, discussion and the sidebar are all here.%{lineBreak}%{lineBreak}But take a look below the description and you'll notice that there's more information about the merge request, the CI/CD pipeline and the options for approving it.%{lineBreak}%{lineBreak}Alongside the discussion you can also see more information about commits in this merge request, the status of pipelines and review all changes that were made."
-msgstr ""
-
-msgid "UserOnboardingTour|There's a lot of information here but don't worry, we'll go through it.%{lineBreak}%{lineBreak}On the top you can see the status of the issue and when it was opened and by whom. Directly below it is the issue description and below that are other %{emphasisStart}related issues%{emphasisEnd} and %{emphasisStart}merge requests%{emphasisEnd} (if any). Then below that is the %{emphasisStart}discussion%{emphasisEnd}, that's where most of the communication happens.%{lineBreak}%{lineBreak}On the right, there's a sidebar where you can view/change the %{emphasisStart}assignee, milestone, due date, labels, weight%{emphasisEnd}, etc."
-msgstr ""
-
-msgid "UserOnboardingTour|These are all the CI/CD pipelines we have for our %{emphasisStart}%{projectName}%{emphasisEnd} project.%{lineBreak}%{lineBreak}Here you can see the status of each pipeline, for what commit it's running for, its stages and the status for them."
+msgid "UserList|Delete %{name}?"
msgstr ""
-msgid "UserOnboardingTour|These are all the issues that are available for community contributions. Let's take a closer look at one of them."
-msgstr ""
-
-msgid "UserOnboardingTour|This is an overview of all merge requests in this project. Similarly to the issues overview it can be filtered down by things like labels, milestones, authors, assignees, etc."
-msgstr ""
-
-msgid "UserOnboardingTour|This is the repository for the %{emphasisStart}%{projectName}%{emphasisEnd} project. All our code is stored here. Feel free to explore and take a closer look at folders and files.%{lineBreak}%{lineBreak}Above the file structure you can see the latest commit, who the author is and the status of the CI/CD pipeline.%{lineBreak}%{lineBreak}If you scroll down below the file structure, you'll find the Readme of this project. This is defined in the README.md file at the root of the repository."
-msgstr ""
-
-msgid "UserOnboardingTour|Welcome to the project overview of the %{emphasisStart}%{projectName}%{emphasisEnd} project. This is the project that we use to work on GitLab. At first, a project seems like a simple repository, but at GitLab, a project is so much more.%{lineBreak}%{lineBreak}You can create projects for hosting your codebase, use it as an issue tracker, collaborate on code, and continuously build, test, and deploy your app with built-in GitLab CI/CD."
+msgid "UserList|created %{timeago}"
msgstr ""
msgid "UserProfile|Activity"
@@ -24471,6 +25116,9 @@ msgstr "Nenhum responsável - %{openingTag} atribua a você mesmo %{closingTag}"
msgid "UsersSelect|Unassigned"
msgstr ""
+msgid "Using %{codeStart}needs%{codeEnd} allows jobs to run before their stage is reached, as soon as their individual dependencies are met, which speeds up your pipelines."
+msgstr ""
+
msgid "Using %{code_start}::%{code_end} denotes a %{link_start}scoped label set%{link_end}"
msgstr ""
@@ -24555,9 +25203,6 @@ msgstr "Versão"
msgid "Versions"
msgstr ""
-msgid "Very helpful"
-msgstr "Muito útil"
-
msgid "View Documentation"
msgstr ""
@@ -24706,6 +25351,9 @@ msgstr "Público"
msgid "VisibilityLevel|Unknown"
msgstr "Desconhecido"
+msgid "Visit settings page"
+msgstr ""
+
msgid "VisualReviewApp|%{stepStart}Step 1%{stepEnd}. Copy the following script:"
msgstr "%{stepStart}Etapa 1%{stepEnd}. Copie o seguinte script:"
@@ -24799,6 +25447,9 @@ msgstr ""
msgid "VulnerabilityManagement|Something went wrong while trying to delete the comment. Please try again later."
msgstr ""
+msgid "VulnerabilityManagement|Something went wrong while trying to refresh the vulnerability. Please try again later."
+msgstr ""
+
msgid "VulnerabilityManagement|Something went wrong while trying to retrieve the vulnerability history. Please try again later."
msgstr ""
@@ -24841,6 +25492,9 @@ msgstr ""
msgid "Vulnerability|Class"
msgstr "Classe"
+msgid "Vulnerability|Crash Address"
+msgstr ""
+
msgid "Vulnerability|Description"
msgstr "Descrição"
@@ -24916,9 +25570,6 @@ msgstr ""
msgid "We couldn't reach the Prometheus server. Either the server no longer exists or the configuration details need updating."
msgstr ""
-msgid "We created a short guided tour that will help you learn the basics of GitLab and how it will help you be better at your job. It should only take a couple of minutes. You will be guided by two types of helpers, best recognized by their color."
-msgstr ""
-
msgid "We detected potential spam in the %{humanized_resource_name}. Please solve the reCAPTCHA to proceed."
msgstr "Detectamos spam potencial no %{humanized_resource_name}. Por favor, resolva o reCAPTCHA para continuar."
@@ -24952,6 +25603,9 @@ msgstr ""
msgid "We've found no vulnerabilities"
msgstr ""
+msgid "Web Application Firewall"
+msgstr ""
+
msgid "Web IDE"
msgstr "IDE Web"
@@ -25012,9 +25666,6 @@ msgstr ""
msgid "Welcome to GitLab.com<br>@%{name}!"
msgstr ""
-msgid "Welcome to the Guided GitLab Tour"
-msgstr "Bem-vindo ao Visita Guiada ao GitLab"
-
msgid "Welcome to the guided GitLab tour"
msgstr ""
@@ -25062,9 +25713,6 @@ msgstr "Quando:"
msgid "While it's rare to have no vulnerabilities, it can happen. In any event, we ask that you please double check your settings to make sure you've set up your dashboard correctly."
msgstr ""
-msgid "White helpers give contextual information."
-msgstr "Auxiliares brancos fornecem informações contextuais."
-
msgid "Who can be an approver?"
msgstr ""
@@ -25110,12 +25758,18 @@ msgstr "Já existe uma página com o mesmo título nesse caminho."
msgid "WikiEmptyIssueMessage|Suggest wiki improvement"
msgstr "Sugira uma melhoria na wiki"
+msgid "WikiEmptyIssueMessage|You must be a group member in order to add wiki pages. If you have suggestions for how to improve the wiki for this group, consider opening an issue in the %{issues_link}."
+msgstr ""
+
msgid "WikiEmptyIssueMessage|You must be a project member in order to add wiki pages. If you have suggestions for how to improve the wiki for this project, consider opening an issue in the %{issues_link}."
msgstr "Você deve ser um membro do projeto para adicionar páginas na wiki. Se você tiver sugestões de como melhorar o wiki para este projeto, considere a possibilidade de abrir uma issue no %{issues_link}."
msgid "WikiEmptyIssueMessage|issue tracker"
msgstr "issue tracker"
+msgid "WikiEmpty|A wiki is where you can store all the details about your group. This can include why you've created it, its principles, how to use it, and so on."
+msgstr ""
+
msgid "WikiEmpty|A wiki is where you can store all the details about your project. This can include why you've created it, its principles, how to use it, and so on."
msgstr "Um wiki é onde você pode armazenar todos os detalhes sobre o seu projeto. Isso pode incluir por que você criou, seus princípios, como usá-lo e assim por diante."
@@ -25125,12 +25779,21 @@ msgstr "Crie sua primeira página"
msgid "WikiEmpty|Suggest wiki improvement"
msgstr "Sugira a melhoria do wiki"
+msgid "WikiEmpty|The wiki lets you write documentation for your group"
+msgstr ""
+
msgid "WikiEmpty|The wiki lets you write documentation for your project"
msgstr "O wiki permite que você escreva documentação para seu projeto"
+msgid "WikiEmpty|This group has no wiki pages"
+msgstr ""
+
msgid "WikiEmpty|This project has no wiki pages"
msgstr "Este projeto não tem páginas wiki"
+msgid "WikiEmpty|You must be a group member in order to add wiki pages."
+msgstr ""
+
msgid "WikiEmpty|You must be a project member in order to add wiki pages."
msgstr "Você deve ser um membro do projeto para adicionar páginas wiki."
@@ -25407,15 +26070,15 @@ msgstr ""
msgid "You can now export your security dashboard to a CSV report."
msgstr ""
+msgid "You can now manage alert endpoint configuration in the Alerts section on the Operations settings page. Fields on this page have been deprecated."
+msgstr ""
+
msgid "You can now submit a merge request to get this change into the original branch."
msgstr ""
msgid "You can now submit a merge request to get this change into the original project."
msgstr "Agora você pode enviar um merge request para fazer esta mudança no projeto original."
-msgid "You can only add files when you are on a branch"
-msgstr "Você somente pode adicionar arquivos quando estiver em um branch"
-
msgid "You can only edit files when you are on a branch"
msgstr "Você só pode editar arquivos quando estiver em um branch"
@@ -25635,7 +26298,7 @@ msgstr ""
msgid "You need to upload a Google Takeout archive."
msgstr ""
-msgid "You reached %{usage_in_percent} of %{namespace_name}'s capacity (%{used_storage} of %{storage_limit})"
+msgid "You reached %{usage_in_percent} of %{namespace_name}'s storage capacity (%{used_storage} of %{storage_limit})"
msgstr ""
msgid "You tried to fork %{link_to_the_project} but it failed for the following reason:"
@@ -25938,13 +26601,13 @@ msgstr ""
msgid "Your subscription expired!"
msgstr ""
-msgid "Your subscription has been downgraded"
+msgid "Your subscription has been downgraded."
msgstr ""
-msgid "Your subscription will automatically renew in %{remaining_days}"
+msgid "Your subscription will automatically renew in %{remaining_days}."
msgstr ""
-msgid "Your subscription will expire in %{remaining_days}"
+msgid "Your subscription will expire in %{remaining_days}."
msgstr ""
msgid "Zoom meeting added"
@@ -26024,9 +26687,6 @@ msgstr ""
msgid "branch name"
msgstr "nome da branch"
-msgid "build pipeline reference mismatch"
-msgstr ""
-
msgid "by"
msgstr "por"
@@ -26045,6 +26705,9 @@ msgstr ""
msgid "cannot block others"
msgstr "não foi possível bloquear outros"
+msgid "cannot contain HTML/XML tags, including any word between angle brackets (<,>)."
+msgstr ""
+
msgid "cannot include leading slash or directory traversal."
msgstr ""
@@ -26075,55 +26738,6 @@ msgstr ""
msgid "ciReport|%{remainingPackagesCount} more"
msgstr "mais %{remainingPackagesCount}"
-msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability"
-msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability for the source branch only"
-msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities for the source branch only"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerability"
-msgid_plural "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerabilities"
-msgstr[0] "%{reportType} %{status} detectado de %{fixedCount} vulnerabilidade corrigida"
-msgstr[1] "%{reportType} %{status} detectado de %{fixedCount} vulnerabilidades corrigidas"
-
-msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new vulnerability"
-msgid_plural "ciReport|%{reportType} %{status} detected %{newCount} new vulnerabilities"
-msgstr[0] "%{reportType} %{status} detectado de %{newCount} nova vulnerabilidade"
-msgstr[1] "%{reportType} %{status} detectado de %{newCount} novas vulnerabilidades"
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities for the source branch only"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{fixedCount} fixed vulnerabilities"
-msgstr "%{reportType} %{status} detectado de %{newCount} nova, e %{fixedCount} vulnerabilidades concertadas"
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} vulnerability for the source branch only"
-msgid_plural "ciReport|%{reportType} %{status} detected %{newCount} vulnerabilities for the source branch only"
-msgstr[0] "%{reportType} %{status} detectou apenas %{newCount} vulnerabilidade para o branch de origem"
-msgstr[1] "%{reportType} %{status} detectou apenas %{newCount} vulnerabilidades para o branch de origem"
-
-msgid "ciReport|%{reportType} %{status} detected no new vulnerabilities"
-msgstr "%{reportType} %{status} não detectou novas vulnerabilidades"
-
-msgid "ciReport|%{reportType} %{status} detected no vulnerabilities"
-msgstr "%{reportType} %{status} não detectou vulnerabilidades"
-
-msgid "ciReport|%{reportType} %{status} detected no vulnerabilities for the source branch only"
-msgstr "%{reportType} %{status} não detectou vulnerabilidades para somente o branch origem"
-
msgid "ciReport|%{reportType} is loading"
msgstr "%{reportType} está carregando"
@@ -26142,7 +26756,7 @@ msgstr "(está carregando, erros ao carregar resultados)"
msgid "ciReport|All projects"
msgstr "Todos os projetos"
-msgid "ciReport|All report types"
+msgid "ciReport|All scanner types"
msgstr ""
msgid "ciReport|All severities"
@@ -26166,6 +26780,9 @@ msgstr "Fazer scan no container"
msgid "ciReport|Container scanning detects known vulnerabilities in your docker images."
msgstr "A varredura de contêiner detectou vulnerabilidades conhecidas em suas imagens docker."
+msgid "ciReport|Coverage Fuzzing"
+msgstr ""
+
msgid "ciReport|Create a merge request to implement this solution, or download and apply the patch manually."
msgstr ""
@@ -26324,6 +26941,9 @@ msgstr ""
msgid "customize"
msgstr "personalizar"
+msgid "data"
+msgstr ""
+
msgid "date must not be after 9999-12-31"
msgstr ""
@@ -26394,6 +27014,9 @@ msgstr ""
msgid "entries cannot contain HTML tags"
msgstr ""
+msgid "epic"
+msgstr ""
+
msgid "error"
msgstr ""
@@ -26415,9 +27038,6 @@ msgstr ""
msgid "expires on %{timebox_due_date}"
msgstr ""
-msgid "external_url"
-msgstr ""
-
msgid "failed"
msgstr ""
@@ -26453,9 +27073,6 @@ msgstr ""
msgid "from"
msgstr "de"
-msgid "geo_node_name"
-msgstr ""
-
msgid "group"
msgstr ""
@@ -26477,9 +27094,6 @@ msgstr "aqui"
msgid "https://your-bitbucket-server"
msgstr "https://seu-servidor-do-bitbucket"
-msgid "image"
-msgstr ""
-
msgid "image diff"
msgstr ""
@@ -26623,6 +27237,9 @@ msgstr ""
msgid "load it anyway"
msgstr ""
+msgid "loading"
+msgstr ""
+
msgid "locked by %{path_lock_user_name} %{created_at}"
msgstr "bloqueador por %{path_lock_user_name} %{created_at}"
@@ -26751,9 +27368,6 @@ msgstr ""
msgid "mrWidget|Deployment statistics are not available currently"
msgstr "Estatísticas de deploy não estão disponíveis atualmente"
-msgid "mrWidget|Detect issues before deployment with a CI pipeline that continuously tests your code. We created a quick guide that will show you how to create one. Make your code more secure and more robust in just a minute."
-msgstr ""
-
msgid "mrWidget|Did not close"
msgstr "Não foi possível fechar"
@@ -26916,6 +27530,9 @@ msgstr "Este projeto está arquivado, a escrita foi desativada"
msgid "mrWidget|To approve this merge request, please enter your password. This project requires all approvals to be authenticated."
msgstr ""
+msgid "mrWidget|Use %{linkStart}CI pipelines to test your code%{linkEnd}, simply add a GitLab CI configuration file to your project. It only takes a minute to make your code more secure and robust."
+msgstr ""
+
msgid "mrWidget|When this merge request is ready, remove the WIP: prefix from the title to allow it to be merged"
msgstr ""
@@ -26952,6 +27569,9 @@ msgstr ""
msgid "must be greater than start date"
msgstr ""
+msgid "must contain only valid frameworks"
+msgstr ""
+
msgid "my-awesome-group"
msgstr ""
@@ -26964,6 +27584,9 @@ msgstr ""
msgid "needs to be between 10 minutes and 1 month"
msgstr ""
+msgid "never"
+msgstr ""
+
msgid "never expires"
msgstr ""
@@ -26991,6 +27614,9 @@ msgstr "e-mails de notificação"
msgid "nounSeries|%{firstItem} and %{lastItem}"
msgstr ""
+msgid "nounSeries|%{item}"
+msgstr ""
+
msgid "nounSeries|%{item}, %{nextItem}"
msgstr ""
@@ -27127,6 +27753,9 @@ msgstr ""
msgid "revised"
msgstr ""
+msgid "satisfied"
+msgstr ""
+
msgid "score"
msgstr ""
@@ -27157,6 +27786,9 @@ msgstr ""
msgid "severity|Unknown"
msgstr ""
+msgid "should be an array of %{object_name} objects"
+msgstr ""
+
msgid "should be greater than or equal to %{access} inherited membership from group %{group_name}"
msgstr ""
@@ -27211,10 +27843,13 @@ msgstr ""
msgid "suggestPipeline|2/2: Commit your changes"
msgstr ""
-msgid "suggestPipeline|Commit the changes and your pipeline will automatically run for the first time."
+msgid "suggestPipeline|Choose %{boldStart}Code Quality%{boldEnd} to add a pipeline that tests the quality of your code."
msgstr ""
-msgid "suggestPipeline|We recommend the %{boldStart}Code Quality%{boldEnd} template, which will add a report widget to your Merge Requests. This way you’ll learn about code quality degradations much sooner. %{footerStart} Goodbye technical debt! %{footerEnd}"
+msgid "suggestPipeline|The template is ready! You can now commit it to create your first pipeline."
+msgstr ""
+
+msgid "suggestPipeline|We’re adding a GitLab CI configuration file to add a pipeline to the project. You could create it manually, but we recommend that you start with a GitLab template that works out of the box."
msgstr ""
msgid "syntax is correct"
@@ -27274,6 +27909,9 @@ msgstr "nome do usuário"
msgid "uses Kubernetes clusters to deploy your code!"
msgstr "use clusters Kubernetes para deploy do seu código!"
+msgid "v%{version} published %{timeAgo}"
+msgstr ""
+
msgid "verify ownership"
msgstr "verificar propriedade"
diff --git a/locale/pt_PT/gitlab.po b/locale/pt_PT/gitlab.po
index caf20f4ea9e..dec2892d0dc 100644
--- a/locale/pt_PT/gitlab.po
+++ b/locale/pt_PT/gitlab.po
@@ -2,7 +2,7 @@ msgid ""
msgstr ""
"Project-Id-Version: gitlab-ee\n"
"Report-Msgid-Bugs-To: \n"
-"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Last-Translator: \n"
"Language-Team: Portuguese\n"
"Language: pt_PT\n"
"MIME-Version: 1.0\n"
@@ -10,9 +10,11 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Crowdin-Project: gitlab-ee\n"
+"X-Crowdin-Project-ID: 288872\n"
"X-Crowdin-Language: pt-PT\n"
"X-Crowdin-File: /master/locale/gitlab.pot\n"
-"PO-Revision-Date: 2020-06-08 15:19\n"
+"X-Crowdin-File-ID: 6\n"
+"PO-Revision-Date: 2020-07-02 01:27\n"
msgid " %{start} to %{end}"
msgstr ""
@@ -197,6 +199,11 @@ msgid_plural "%d metrics"
msgstr[0] "%d métrica"
msgstr[1] "%d métricas"
+msgid "%d milestone"
+msgid_plural "%d milestones"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "%d minute"
msgid_plural "%d minutes"
msgstr[0] ""
@@ -242,11 +249,6 @@ msgid_plural "%d unresolved threads"
msgstr[0] ""
msgstr[1] ""
-msgid "%d url scanned"
-msgid_plural "%d urls scanned"
-msgstr[0] ""
-msgstr[1] ""
-
msgid "%d vulnerability"
msgid_plural "%d vulnerabilities"
msgstr[0] ""
@@ -324,9 +326,15 @@ msgstr[1] "%{count} comentários pendentes"
msgid "%{count} related %{pluralized_subject}: %{links}"
msgstr ""
+msgid "%{dashboard_path} could not be found."
+msgstr ""
+
msgid "%{days} days until tags are automatically removed"
msgstr ""
+msgid "%{deployLinkStart}Use a template to deploy to ECS%{deployLinkEnd}, or use a docker image to %{commandsLinkStart}run AWS commands in GitLab CI/CD%{commandsLinkEnd}."
+msgstr ""
+
msgid "%{description}- Sentry event: %{errorUrl}- First seen: %{firstSeen}- Last seen: %{lastSeen} %{countLabel}: %{count}%{userCountLabel}: %{userCount}"
msgstr ""
@@ -369,12 +377,18 @@ msgstr "%{group_name} usa contas de gestão de grupo. Precisas de criar uma nova
msgid "%{host} sign-in from new location"
msgstr ""
-msgid "%{icon}You are about to add %{usersTag} people to the discussion. Proceed with caution."
+msgid "%{icon}You are about to add %{usersTag} people to the discussion. They will all receive a notification."
+msgstr ""
+
+msgid "%{integrations_link_start}Integrations%{link_end} enable you to make third-party applications part of your GitLab workflow. If the available integrations don't meet your needs, consider using a %{webhooks_link_start}webhook%{link_end}."
msgstr ""
msgid "%{issuableType} will be removed! Are you sure?"
msgstr "%{issuableType} será removido! Tens a certeza?"
+msgid "%{issuesCount} issues in this group"
+msgstr ""
+
msgid "%{issuesSize} issues"
msgstr ""
@@ -384,7 +398,7 @@ msgstr ""
msgid "%{labelStart}Class:%{labelEnd} %{class}"
msgstr ""
-msgid "%{labelStart}Confidence:%{labelEnd} %{confidence}"
+msgid "%{labelStart}Evidence:%{labelEnd} %{evidence}"
msgstr ""
msgid "%{labelStart}File:%{labelEnd} %{file}"
@@ -402,6 +416,9 @@ msgstr ""
msgid "%{labelStart}Report Type:%{labelEnd} %{reportType}"
msgstr ""
+msgid "%{labelStart}Scanner:%{labelEnd} %{scanner}"
+msgstr ""
+
msgid "%{labelStart}Severity:%{labelEnd} %{severity}"
msgstr ""
@@ -426,9 +443,6 @@ msgstr ""
msgid "%{link_start}Read more%{link_end} about role permissions"
msgstr "%{link_start}Lê mais%{link_end} sobre as permissões de função"
-msgid "%{link} can be used for binding events when something is happening within the project."
-msgstr ""
-
msgid "%{listToShow}, and %{awardsListLength} more."
msgstr ""
@@ -462,6 +476,9 @@ msgstr "%{name} continha %{resultsString}"
msgid "%{name} found %{resultsString}"
msgstr "%{name} encontrou %{resultsString}"
+msgid "%{name} is already being used for another emoji"
+msgstr ""
+
msgid "%{name} is scheduled for %{action}"
msgstr ""
@@ -509,6 +526,9 @@ msgstr ""
msgid "%{primary} (%{secondary})"
msgstr ""
+msgid "%{ref} cannot be added: %{error}"
+msgstr ""
+
msgid "%{releases} release"
msgid_plural "%{releases} releases"
msgstr[0] ""
@@ -517,13 +537,37 @@ msgstr[1] ""
msgid "%{remaining_approvals} left"
msgstr ""
-msgid "%{retryButtonStart}Try again%{retryButtonEnd} or %{newFileButtonStart}attach a new file%{newFileButtonEnd}"
+msgid "%{reportType} %{status} detected %{critical} critical and %{high} high severity vulnerabilities out of %{total}."
+msgstr ""
+
+msgid "%{reportType} %{status} detected %{critical} critical and %{high} high severity vulnerabilities."
+msgstr ""
+
+msgid "%{reportType} %{status} detected %{critical} critical severity vulnerabilities out of %{total}."
+msgstr ""
+
+msgid "%{reportType} %{status} detected %{critical} critical severity vulnerability."
+msgid_plural "%{reportType} %{status} detected %{critical} critical severity vulnerabilities."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{reportType} %{status} detected %{high} high severity vulnerabilities out of %{total}."
msgstr ""
-msgid "%{screenreaderOnlyStart}Keyboard shorcuts%{screenreaderOnlyEnd} Disabled"
+msgid "%{reportType} %{status} detected %{high} high severity vulnerability."
+msgid_plural "%{reportType} %{status} detected %{high} high severity vulnerabilities."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{reportType} %{status} detected %{other} vulnerability."
+msgid_plural "%{reportType} %{status} detected %{other} vulnerabilities."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{reportType} %{status} detected no new vulnerabilities."
msgstr ""
-msgid "%{screenreaderOnlyStart}Keyboard shorcuts%{screenreaderOnlyEnd} Enabled"
+msgid "%{retryButtonStart}Try again%{retryButtonEnd} or %{newFileButtonStart}attach a new file%{newFileButtonEnd}"
msgstr ""
msgid "%{service_title} %{message}."
@@ -620,6 +664,12 @@ msgstr "Alterações de %{title}"
msgid "%{token}..."
msgstr ""
+msgid "%{totalCpu} (%{freeSpacePercentage}%{percentSymbol} free)"
+msgstr ""
+
+msgid "%{totalMemory} (%{freeSpacePercentage}%{percentSymbol} free)"
+msgstr ""
+
msgid "%{totalWeight} total weight"
msgstr ""
@@ -650,6 +700,12 @@ msgstr ""
msgid "%{verb} %{time_spent_value} spent time."
msgstr "%{verb} %{time_spent_value} tempo passado."
+msgid "%{webhooks_link_start}%{webhook_type}%{link_end} enable you to send notifications to web applications in response to events in a group or project."
+msgstr ""
+
+msgid "%{webhooks_link_start}%{webhook_type}%{link_end} enable you to send notifications to web applications in response to events in a group or project. We recommend using an %{integrations_link_start}integration%{link_end} in preference to a webhook."
+msgstr ""
+
msgid "'%{level}' is not a valid visibility level"
msgstr "'%{level}' não é um nível de visibilidade válido"
@@ -676,9 +732,6 @@ msgstr "(%{mrCount} mesclado)"
msgid "(No changes)"
msgstr "(Sem alterações)"
-msgid "(Show all)"
-msgstr ""
-
msgid "(check progress)"
msgstr "(verificar progresso)"
@@ -745,6 +798,9 @@ msgstr "- mostrar menos"
msgid "0 for unlimited"
msgstr "0 para ilimitado"
+msgid "0 for unlimited, only effective with remote storage enabled."
+msgstr ""
+
msgid "1 %{type} addition"
msgid_plural "%{count} %{type} additions"
msgstr[0] "1 adição de %{type}"
@@ -892,6 +948,12 @@ msgstr "<code>\"johnsmith@example.com\": \"johnsm...@example.com\"</code> adicio
msgid "<code>\"johnsmith@example.com\": \"johnsmith@example.com\"</code> will add \"By <a href=\"#\">johnsmith@example.com</a>\" to all issues and comments originally created by johnsmith@example.com. By default, the email address or username is masked to ensure the user's privacy. Use this option if you want to show the full email address."
msgstr "<code>\"johnsmith@example.com\": \"johnsmith@example.com\"</code> adicionará \"Por <a href=\"#\">johnsmith@example.com</a>\" a todos os problemas e comentários, originalmente, criados por johnsmith@example.com. Por padrão, o endereço de email ou nome de utilizador é ocultado para garantir a privacidade do utilizador. Use esta opção se quiser mostrar o endereço de email completo."
+msgid "<code>Masked</code> to prevent the values from being displayed in job logs (must match certain regexp requirements)."
+msgstr ""
+
+msgid "<code>Protected</code> to expose them to protected branches or tags only."
+msgstr ""
+
msgid "<namespace / project>"
msgstr ""
@@ -919,9 +981,6 @@ msgstr "Um 'Executador' é um processo que executa um trabalho. Podes configurar
msgid "A .NET Core console application template, customizable for any .NET Core project"
msgstr "Um .NET Core de um modelo da aplicação de consola, personalizável para qualquer projeto .NET Core"
-msgid "A DAG must have two dependent jobs to be visualized on this tab."
-msgstr ""
-
msgid "A GitBook site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
msgstr "Um site GitBook que usa o Netlify para CI/CD, em vez do GitLab, mas ainda com todos os outros ótimos recursos do GitLab."
@@ -952,6 +1011,12 @@ msgstr "Um ramo padrão não pode ser escolhido para um projeto vazio."
msgid "A deleted user"
msgstr "Um utilizador apagado"
+msgid "A file has been changed."
+msgstr ""
+
+msgid "A file was not found."
+msgstr ""
+
msgid "A file with '%{file_name}' already exists in %{branch} branch"
msgstr ""
@@ -1018,7 +1083,7 @@ msgstr ""
msgid "A subscription will trigger a new pipeline on the default branch of this project when a pipeline successfully completes for a new tag on the %{default_branch_docs} of the subscribed project."
msgstr ""
-msgid "A terraform report was generated in your pipelines."
+msgid "A suggestion is not applicable."
msgstr ""
msgid "A user with write access to the source branch selected this option"
@@ -1189,6 +1254,9 @@ msgstr "Conta: %{account}"
msgid "Action to take when receiving an alert."
msgstr "Ação a tomar ao receber um alerta."
+msgid "Actions"
+msgstr ""
+
msgid "Activate"
msgstr ""
@@ -1245,6 +1313,9 @@ msgstr "Adicionar cluster Kubernetes"
msgid "Add LICENSE"
msgstr ""
+msgid "Add New Node"
+msgstr ""
+
msgid "Add README"
msgstr "Adicionar README"
@@ -1320,6 +1391,9 @@ msgstr ""
msgid "Add approval rule"
msgstr ""
+msgid "Add approvers"
+msgstr ""
+
msgid "Add bold text"
msgstr "Adicionar texto em negrito"
@@ -1377,6 +1451,9 @@ msgstr ""
msgid "Add strikethrough text"
msgstr ""
+msgid "Add suggestion to batch"
+msgstr ""
+
msgid "Add system hook"
msgstr ""
@@ -1476,6 +1553,9 @@ msgstr "Ãrea de Administração"
msgid "Admin Note"
msgstr ""
+msgid "Admin Notifications"
+msgstr ""
+
msgid "Admin Overview"
msgstr "Visão Geral do Administrador"
@@ -1587,6 +1667,9 @@ msgstr ""
msgid "AdminSettings|Integrations configured here will automatically apply to all projects on this instance."
msgstr ""
+msgid "AdminSettings|Moved to integrations"
+msgstr ""
+
msgid "AdminSettings|No required pipeline"
msgstr "Nenhum pipeline necessário"
@@ -1817,12 +1900,18 @@ msgid_plural "Alerts"
msgstr[0] "Alerta"
msgstr[1] "Alertas"
+msgid "AlertManagement| assign yourself"
+msgstr ""
+
msgid "AlertManagement|Acknowledged"
msgstr ""
msgid "AlertManagement|Alert"
msgstr ""
+msgid "AlertManagement|Alert assignee(s): %{assignees}"
+msgstr ""
+
msgid "AlertManagement|Alert detail"
msgstr ""
@@ -1838,9 +1927,15 @@ msgstr ""
msgid "AlertManagement|All alerts"
msgstr ""
+msgid "AlertManagement|Assign To"
+msgstr ""
+
msgid "AlertManagement|Assign status"
msgstr ""
+msgid "AlertManagement|Assignee"
+msgstr ""
+
msgid "AlertManagement|Assignees"
msgstr ""
@@ -1859,9 +1954,6 @@ msgstr ""
msgid "AlertManagement|Edit"
msgstr ""
-msgid "AlertManagement|End time"
-msgstr ""
-
msgid "AlertManagement|Events"
msgstr ""
@@ -1880,10 +1972,13 @@ msgstr ""
msgid "AlertManagement|More information"
msgstr ""
+msgid "AlertManagement|No Matching Results"
+msgstr ""
+
msgid "AlertManagement|No alert data to display."
msgstr ""
-msgid "AlertManagement|No alerts available to display. If you think you're seeing this message in error, refresh the page."
+msgid "AlertManagement|No alerts available to display. See %{linkStart}enabling alert management%{linkEnd} for more information on adding alerts to the list."
msgstr ""
msgid "AlertManagement|No alerts to display."
@@ -1892,6 +1987,9 @@ msgstr ""
msgid "AlertManagement|None"
msgstr ""
+msgid "AlertManagement|None -"
+msgstr ""
+
msgid "AlertManagement|Open"
msgstr ""
@@ -1928,9 +2026,18 @@ msgstr ""
msgid "AlertManagement|There was an error displaying the alerts. Confirm your endpoint's configuration details to ensure alerts appear."
msgstr ""
+msgid "AlertManagement|There was an error while updating the assignee(s) list. Please try again."
+msgstr ""
+
+msgid "AlertManagement|There was an error while updating the assignee(s) of the alert. Please try again."
+msgstr ""
+
msgid "AlertManagement|There was an error while updating the status of the alert. Please try again."
msgstr ""
+msgid "AlertManagement|This assignee cannot be assigned to this alert."
+msgstr ""
+
msgid "AlertManagement|Tool"
msgstr ""
@@ -2078,6 +2185,9 @@ msgstr "Permitir que os utilizadores se registem a qualquer aplicação para usa
msgid "Allow users to request access (if visibility is public or internal)"
msgstr ""
+msgid "Allowed Geo IP"
+msgstr ""
+
msgid "Allowed email domain restriction only permitted for top-level groups"
msgstr ""
@@ -2111,6 +2221,9 @@ msgstr ""
msgid "Amazon Web Services"
msgstr ""
+msgid "Amazon Web Services Logo"
+msgstr ""
+
msgid "Amazon authentication is not %{link_start}correctly configured%{link_end}. Ask your GitLab administrator if you want to use this service."
msgstr ""
@@ -2168,10 +2281,16 @@ msgstr "Ocorreu um erro ao tentar resolver uma discussão. Por favor, tenta nova
msgid "An error occurred when updating the issue weight"
msgstr "Ocorreu um erro ao atualizar o peso do problema"
+msgid "An error occurred while acknowledging the notification. Refresh the page and try again."
+msgstr ""
+
+msgid "An error occurred while adding approvers"
+msgstr ""
+
msgid "An error occurred while adding formatted title for epic"
msgstr ""
-msgid "An error occurred while checking group path"
+msgid "An error occurred while checking group path. Please refresh and try again."
msgstr ""
msgid "An error occurred while committing your changes."
@@ -2315,7 +2434,7 @@ msgstr ""
msgid "An error occurred while loading milestones"
msgstr ""
-msgid "An error occurred while loading terraform report"
+msgid "An error occurred while loading project creation UI"
msgstr ""
msgid "An error occurred while loading the data. Please try again."
@@ -2369,6 +2488,9 @@ msgstr "Ocorreu um erro ao remover problemas."
msgid "An error occurred while rendering preview broadcast message"
msgstr "Ocorreu um erro ao renderizar a mensagem de pré-visualização da transmissão"
+msgid "An error occurred while rendering the editor"
+msgstr ""
+
msgid "An error occurred while reordering issues."
msgstr ""
@@ -2465,7 +2587,7 @@ msgstr ""
msgid "Analyze your dependencies for known vulnerabilities."
msgstr ""
-msgid "Analyze your source code and git history for secrets"
+msgid "Analyze your source code and git history for secrets."
msgstr ""
msgid "Analyze your source code for known vulnerabilities."
@@ -2579,12 +2701,18 @@ msgstr ""
msgid "Apply suggestion"
msgstr "Aplicar sugestão"
+msgid "Apply suggestions"
+msgstr ""
+
msgid "Apply template"
msgstr ""
msgid "Apply this approval rule to any branch or a specific protected branch."
msgstr ""
+msgid "Applying"
+msgstr ""
+
msgid "Applying a template will replace the existing issue description. Any changes you have made will be lost."
msgstr ""
@@ -2597,12 +2725,18 @@ msgstr "A aplicar comando a %{commandDescription}"
msgid "Applying multiple commands"
msgstr "A aplicar vários comandos"
-msgid "Applying suggestion"
-msgstr "A aplicar a sugestão"
+msgid "Applying suggestion..."
+msgstr ""
+
+msgid "Applying suggestions..."
+msgstr ""
msgid "Approval rules"
msgstr ""
+msgid "Approval rules reset to project defaults"
+msgstr ""
+
msgid "ApprovalRuleRemove|%d member"
msgid_plural "ApprovalRuleRemove|%d members"
msgstr[0] "%d membro"
@@ -2656,6 +2790,9 @@ msgstr ""
msgid "Approved"
msgstr ""
+msgid "Approved MRs"
+msgstr ""
+
msgid "Approved by: "
msgstr ""
@@ -2869,6 +3006,9 @@ msgstr ""
msgid "Assign"
msgstr "Atribuir"
+msgid "Assign Iteration"
+msgstr ""
+
msgid "Assign custom color like #FF0000"
msgstr "Atribui uma cor personalizada, como #FF0000"
@@ -2887,6 +3027,9 @@ msgstr "Atribuir alguns problemas para este objetivo."
msgid "Assign to"
msgstr "Atribuir a"
+msgid "Assign to commenting user"
+msgstr ""
+
msgid "Assign yourself to these issues"
msgstr "Atribuir-te a estes problemas"
@@ -2913,6 +3056,9 @@ msgid_plural "%d Assignees"
msgstr[0] ""
msgstr[1] ""
+msgid "Assignee has no permissions"
+msgstr ""
+
msgid "Assignee lists not available with your current license"
msgstr "Listas de responsáveis não disponíveis com a tua licença atual"
@@ -2922,6 +3068,9 @@ msgstr "As listas de responsáveis mostram todos os problemas atribuídos ao uti
msgid "Assignee(s)"
msgstr "Responsável(eis)"
+msgid "Assignees"
+msgstr ""
+
msgid "Assigns %{assignee_users_sentence}."
msgstr ""
@@ -2957,6 +3106,9 @@ msgstr "Eventos de Auditoria"
msgid "Audit Events is a way to keep track of important events that happened in GitLab."
msgstr ""
+msgid "Audit Log"
+msgstr ""
+
msgid "AuditEvents|(removed)"
msgstr ""
@@ -2993,6 +3145,9 @@ msgstr ""
msgid "AuditLogs|IP Address"
msgstr ""
+msgid "AuditLogs|Member Events"
+msgstr ""
+
msgid "AuditLogs|No matching %{type} found."
msgstr ""
@@ -3275,9 +3430,6 @@ msgstr "Os teus emblemas"
msgid "Badges|e.g. %{exampleUrl}"
msgstr "por exemplo, %{exampleUrl}"
-msgid "Badge|New"
-msgstr "Novo"
-
msgid "Balsamiq file could not be loaded."
msgstr "O ficheiro Balsamiq não pôde ser carregado."
@@ -3326,6 +3478,12 @@ msgstr ""
msgid "Below you will find all the groups that are public."
msgstr "Abaixo vais encontrar todos os grupos que são públicos."
+msgid "Beta"
+msgstr ""
+
+msgid "Bi-weekly code coverage"
+msgstr ""
+
msgid "Billing"
msgstr "Faturação"
@@ -3386,9 +3544,15 @@ msgstr ""
msgid "Bitbucket Server Import"
msgstr "Importação de Servidores Bitbucket"
+msgid "Bitbucket Server import"
+msgstr ""
+
msgid "Bitbucket import"
msgstr "Importar do Bitbucket"
+msgid "Blame"
+msgstr ""
+
msgid "Blocked"
msgstr "Bloqueado"
@@ -3401,9 +3565,6 @@ msgstr ""
msgid "Blog"
msgstr "Blogue"
-msgid "Blue helpers indicate an action to be taken."
-msgstr "Os auxiliares azuis indicam que é necessário tomar medidas."
-
msgid "Board name"
msgstr ""
@@ -3602,6 +3763,9 @@ msgstr "Mensagem de transmissão criada com sucesso."
msgid "Broadcast Message was successfully updated."
msgstr "Mensagem de transmissão atualizada com sucesso."
+msgid "Broadcast Messages"
+msgstr ""
+
msgid "Browse Directory"
msgstr "Procurar Diretório"
@@ -3656,6 +3820,9 @@ msgstr ""
msgid "By %{user_name}"
msgstr "Por %{user_name}"
+msgid "By URL"
+msgstr ""
+
msgid "By default GitLab sends emails in HTML and plain text formats so mail clients can choose what format to use. Disable this option if you only want to send emails in plain text format."
msgstr "Por padrão, o GitLab envia emails em formatos HTML e texto simples para que os clientes de email possam escolher o formato a ser usado. Desativa esta opção se quiseres apenas enviar emails em formato de texto simples."
@@ -3761,6 +3928,9 @@ msgstr ""
msgid "Can override approvers and approvals required per merge request"
msgstr "Pode substituir aprovadores e aprovações requeridas por pedido de mesclagem"
+msgid "Can't apply as this line has changed or the suggestion already matches its content."
+msgstr ""
+
msgid "Can't create snippet: %{err}"
msgstr ""
@@ -3947,9 +4117,6 @@ msgstr "As alterações são mostradas como se a revisão de <b>origem</b> estiv
msgid "Changes are still tracked. Useful for cluster/index migrations."
msgstr ""
-msgid "Changes are unknown"
-msgstr ""
-
msgid "Changes suppressed. Click to show."
msgstr "Alterações suprimidas Clica para mostrar."
@@ -4256,15 +4423,9 @@ msgstr "Escolhe o nível de visibilidade, ativa/desativa os recursos do projeto
msgid "Choose what content you want to see on a group’s overview page"
msgstr "Escolhe o conteúdo que desejas ver na página de visão geral de um grupo"
-msgid "Choose which groups you wish to synchronize to this secondary node"
-msgstr ""
-
msgid "Choose which repositories you want to connect and run CI/CD pipelines."
msgstr "Escolhe quais repositórios desejas conectar e executar pipelines de CI/CD."
-msgid "Choose which shards you wish to synchronize to this secondary node"
-msgstr ""
-
msgid "Choose your framework"
msgstr ""
@@ -4562,9 +4723,15 @@ msgstr ""
msgid "Cluster does not exist"
msgstr ""
+msgid "Cluster is required for Stages::ClusterEndpointInserter"
+msgstr ""
+
msgid "Cluster level"
msgstr ""
+msgid "Cluster type must be specificed for Stages::ClusterEndpointInserter"
+msgstr ""
+
msgid "ClusterIntegration| %{custom_domain_start}More information%{custom_domain_end}."
msgstr "%{custom_domain_start}Mais informações%{custom_domain_end}."
@@ -4676,8 +4843,8 @@ msgstr "Certificado CA"
msgid "ClusterIntegration|Cert-Manager"
msgstr "Gestor de Certificados"
-msgid "ClusterIntegration|Cert-Manager is a native Kubernetes certificate management controller that helps with issuing certificates. Installing Cert-Manager on your cluster will issue a certificate by %{letsEncrypt} and ensure that certificates are valid and up-to-date."
-msgstr "O Gestor de Certificados é um controlador de gestão de certificados nativo do Kubernetes, que ajuda na emissão de certificados. A instalação no teu cluster, emitirá um certificado por %{letsEncrypt} e garantirá, que os certificados sejam válidos e atualizados."
+msgid "ClusterIntegration|Cert-Manager is a native Kubernetes certificate management controller that helps with issuing certificates. Installing Cert-Manager on your cluster will issue a certificate by %{linkStart}Let's Encrypt%{linkEnd} and ensure that certificates are valid and up-to-date."
+msgstr ""
msgid "ClusterIntegration|Certificate Authority bundle (PEM format)"
msgstr "Pacote de Autoridade de Certificados (formato PEM)"
@@ -4703,9 +4870,6 @@ msgstr ""
msgid "ClusterIntegration|Clear the local cache of namespace and service accounts. This is necessary if your integration has become out of sync. The cache is repopulated during the next CI job that requires namespace and service accounts."
msgstr ""
-msgid "ClusterIntegration|Cloud Run"
-msgstr ""
-
msgid "ClusterIntegration|Cluster being created"
msgstr ""
@@ -4790,7 +4954,7 @@ msgstr ""
msgid "ClusterIntegration|Crossplane"
msgstr ""
-msgid "ClusterIntegration|Crossplane enables declarative provisioning of managed services from your cloud of choice using %{codeStart}kubectl%{codeEnd} or %{gitlabIntegrationLink}. Crossplane runs inside your Kubernetes cluster and supports secure connectivity and secrets management between app containers and the cloud services they depend on."
+msgid "ClusterIntegration|Crossplane enables declarative provisioning of managed services from your cloud of choice using %{codeStart}kubectl%{codeEnd} or %{linkStart}GitLab Integration%{linkEnd}. Crossplane runs inside your Kubernetes cluster and supports secure connectivity and secrets management between app containers and the cloud services they depend on."
msgstr ""
msgid "ClusterIntegration|Deletes all GitLab resources attached to this cluster during removal"
@@ -4874,9 +5038,6 @@ msgstr "O Executador do GitLab conecta-se ao repositório e executa os trabalhos
msgid "ClusterIntegration|GitLab-managed cluster"
msgstr "Cluster gerido por GitLab"
-msgid "ClusterIntegration|Gitlab Integration"
-msgstr ""
-
msgid "ClusterIntegration|Global default"
msgstr ""
@@ -4922,8 +5083,8 @@ msgstr "Ponto Final do Ingress"
msgid "ClusterIntegration|Ingress gives you a way to route requests to services based on the request host or path, centralizing a number of services into a single entrypoint."
msgstr "Ingress dá-te uma maneira de encaminhar pedidos de serviços com base no pedido do administrador ou o caminho, a centralização de diversos serviços num único ponto de entrada."
-msgid "ClusterIntegration|Installing Ingress may incur additional costs. Learn more about %{pricingLink}."
-msgstr "Instalação do Ingress poderão incorrer em custos adicionais. Aprende mais sobre %{pricingLink}."
+msgid "ClusterIntegration|Installing Ingress may incur additional costs. Learn more about %{linkStart}pricing%{linkEnd}."
+msgstr ""
msgid "ClusterIntegration|Instance cluster"
msgstr "Cluster de instância"
@@ -4937,8 +5098,8 @@ msgstr "Integrar automação de cluster Kubernetes"
msgid "ClusterIntegration|Issuer Email"
msgstr "Email do emissor"
-msgid "ClusterIntegration|Issuers represent a certificate authority. You must provide an email address for your Issuer. "
-msgstr "Emissores representam uma Autoridade Certificadora. Deves fornecer um endereço de email para o teu Emissor. "
+msgid "ClusterIntegration|Issuers represent a certificate authority. You must provide an email address for your Issuer."
+msgstr ""
msgid "ClusterIntegration|Jupyter Hostname"
msgstr "Nome de administrador do Jupyter"
@@ -5009,9 +5170,6 @@ msgstr "Aprende mais sobre os clusters do Kubernetes de grupo"
msgid "ClusterIntegration|Learn more about instance Kubernetes clusters"
msgstr "Aprende mais sobre a instância de clusters do Kubernetes"
-msgid "ClusterIntegration|Let's Encrypt"
-msgstr "Vamos Criptografar"
-
msgid "ClusterIntegration|Loading IAM Roles"
msgstr ""
@@ -5123,8 +5281,8 @@ msgstr ""
msgid "ClusterIntegration|Prometheus"
msgstr "Prometheus"
-msgid "ClusterIntegration|Prometheus is an open-source monitoring system with %{gitlabIntegrationLink} to monitor deployed applications."
-msgstr "Prometheus é um sistema de monitoramento de código aberto com %{gitlabIntegrationLink} para monitorar aplicações implantadas."
+msgid "ClusterIntegration|Prometheus is an open-source monitoring system with %{linkStart}GitLab Integration%{linkEnd} to monitor deployed applications."
+msgstr ""
msgid "ClusterIntegration|Provider details"
msgstr ""
@@ -5279,10 +5437,10 @@ msgstr "Selecionar zona"
msgid "ClusterIntegration|Select zone to choose machine type"
msgstr "Seleciona a zona para escolher o tipo de máquina"
-msgid "ClusterIntegration|Send Cilium Logs"
+msgid "ClusterIntegration|Send Container Network Policies Logs"
msgstr ""
-msgid "ClusterIntegration|Send ModSecurity Logs"
+msgid "ClusterIntegration|Send Web Application Firewall Logs"
msgstr ""
msgid "ClusterIntegration|Service Token"
@@ -5450,15 +5608,12 @@ msgstr "acesso ao Google Kubernetes Engine"
msgid "ClusterIntegration|documentation"
msgstr ""
-msgid "ClusterIntegration|installed via %{installed_via}"
+msgid "ClusterIntegration|installed via %{linkStart}Cloud Run%{linkEnd}"
msgstr ""
msgid "ClusterIntegration|meets the requirements"
msgstr ""
-msgid "ClusterIntegration|pricing"
-msgstr ""
-
msgid "ClusterIntegration|sign up"
msgstr ""
@@ -5495,6 +5650,15 @@ msgstr ""
msgid "Code"
msgstr ""
+msgid "Code Coverage: %{coveragePercentage}%{percentSymbol}"
+msgstr ""
+
+msgid "Code Coverage| Empty code coverage data"
+msgstr ""
+
+msgid "Code Coverage|Couldn't fetch the code coverage data"
+msgstr ""
+
msgid "Code Owners"
msgstr ""
@@ -5549,7 +5713,7 @@ msgstr ""
msgid "Collapse approvers"
msgstr ""
-msgid "Collapse child epics"
+msgid "Collapse milestones"
msgstr ""
msgid "Collapse replies"
@@ -5567,6 +5731,9 @@ msgstr ""
msgid "Coming soon"
msgstr ""
+msgid "Comma-separated, e.g. '1.1.1.1, 2.2.2.0/24'"
+msgstr ""
+
msgid "Command"
msgstr ""
@@ -5737,6 +5904,12 @@ msgstr ""
msgid "Compliance framework (optional)"
msgstr ""
+msgid "Compliance frameworks"
+msgstr ""
+
+msgid "ComplianceDashboard|created by:"
+msgstr ""
+
msgid "ComplianceFramework|GDPR"
msgstr ""
@@ -5875,6 +6048,9 @@ msgstr ""
msgid "Connection timed out"
msgstr ""
+msgid "Connection timeout"
+msgstr ""
+
msgid "Contact sales to upgrade"
msgstr ""
@@ -5930,9 +6106,6 @@ msgstr ""
msgid "ContainerRegistry|CLI Commands"
msgstr ""
-msgid "ContainerRegistry|Compressed Size"
-msgstr ""
-
msgid "ContainerRegistry|Container Registry"
msgstr ""
@@ -5948,6 +6121,9 @@ msgstr ""
msgid "ContainerRegistry|Copy push command"
msgstr ""
+msgid "ContainerRegistry|Delete selected"
+msgstr ""
+
msgid "ContainerRegistry|Docker connection error"
msgstr ""
@@ -5981,16 +6157,16 @@ msgstr ""
msgid "ContainerRegistry|If you are not already logged in, you need to authenticate to the Container Registry by using your GitLab username and password. If you have %{twofaDocLinkStart}Two-Factor Authentication%{twofaDocLinkEnd} enabled, use a %{personalAccessTokensDocLinkStart}Personal Access Token%{personalAccessTokensDocLinkEnd} instead of a password."
msgstr ""
-msgid "ContainerRegistry|Image ID"
+msgid "ContainerRegistry|Image ID: %{imageId}"
msgstr ""
msgid "ContainerRegistry|Image Repositories"
msgstr ""
-msgid "ContainerRegistry|Keep and protect the images that matter most."
+msgid "ContainerRegistry|Image tags"
msgstr ""
-msgid "ContainerRegistry|Last Updated"
+msgid "ContainerRegistry|Keep and protect the images that matter most."
msgstr ""
msgid "ContainerRegistry|Login"
@@ -6005,6 +6181,9 @@ msgstr ""
msgid "ContainerRegistry|Please contact your administrator."
msgstr ""
+msgid "ContainerRegistry|Published %{timeInfo}"
+msgstr ""
+
msgid "ContainerRegistry|Push an image"
msgstr ""
@@ -6020,9 +6199,6 @@ msgstr ""
msgid "ContainerRegistry|Remove repository"
msgstr ""
-msgid "ContainerRegistry|Remove selected tags"
-msgstr ""
-
msgid "ContainerRegistry|Remove tag"
msgid_plural "ContainerRegistry|Remove tags"
msgstr[0] ""
@@ -6052,9 +6228,6 @@ msgstr ""
msgid "ContainerRegistry|Sorry, your filter produced no results."
msgstr ""
-msgid "ContainerRegistry|Tag"
-msgstr ""
-
msgid "ContainerRegistry|Tag expiration policy"
msgstr ""
@@ -6199,21 +6372,6 @@ msgstr ""
msgid "Control the display of third party offers."
msgstr ""
-msgid "Control the maximum concurrency of LFS/attachment backfill for this secondary node"
-msgstr ""
-
-msgid "Control the maximum concurrency of container repository operations for this Geo node"
-msgstr ""
-
-msgid "Control the maximum concurrency of repository backfill for this secondary node"
-msgstr ""
-
-msgid "Control the maximum concurrency of verification operations for this Geo node"
-msgstr ""
-
-msgid "Control the minimum interval in days that a repository should be reverified for this primary node"
-msgstr ""
-
msgid "Cookie domain"
msgstr ""
@@ -6355,6 +6513,9 @@ msgstr ""
msgid "Could not find design."
msgstr ""
+msgid "Could not find iteration"
+msgstr ""
+
msgid "Could not remove the trigger."
msgstr ""
@@ -6391,6 +6552,12 @@ msgstr ""
msgid "Coverage"
msgstr ""
+msgid "Coverage Fuzzing"
+msgstr ""
+
+msgid "Crash State"
+msgstr ""
+
msgid "Create"
msgstr ""
@@ -6439,6 +6606,9 @@ msgstr ""
msgid "Create a personal access token on your account to pull or push via %{protocol}."
msgstr ""
+msgid "Create a project pre-populated with the necessary files to get you started quickly."
+msgstr ""
+
msgid "Create an account using:"
msgstr ""
@@ -6607,6 +6777,9 @@ msgstr ""
msgid "Creates branch '%{branch_name}' and a merge request to resolve this issue."
msgstr ""
+msgid "Creating"
+msgstr ""
+
msgid "Creating epic"
msgstr ""
@@ -6658,7 +6831,10 @@ msgstr ""
msgid "Current vulnerabilities count"
msgstr ""
-msgid "CurrentUser|Buy CI minutes"
+msgid "CurrentUser|Buy Pipeline minutes"
+msgstr ""
+
+msgid "CurrentUser|One of your groups is running out"
msgstr ""
msgid "CurrentUser|Profile"
@@ -6673,6 +6849,9 @@ msgstr ""
msgid "CurrentUser|Upgrade"
msgstr ""
+msgid "Custom Attributes"
+msgstr ""
+
msgid "Custom CI configuration path"
msgstr ""
@@ -6936,12 +7115,18 @@ msgstr ""
msgid "DAG"
msgstr ""
+msgid "DAG visualization requires at least 3 dependent jobs."
+msgstr ""
+
msgid "DNS"
msgstr ""
msgid "Dashboard"
msgstr ""
+msgid "Dashboard uid not found"
+msgstr ""
+
msgid "DashboardProjects|All"
msgstr ""
@@ -6966,6 +7151,9 @@ msgstr ""
msgid "Data is still calculating..."
msgstr ""
+msgid "Datasource name not found"
+msgstr ""
+
msgid "Date"
msgstr ""
@@ -7146,6 +7334,9 @@ msgstr ""
msgid "Delete this attachment"
msgstr ""
+msgid "Delete user list"
+msgstr ""
+
msgid "Delete variable"
msgstr ""
@@ -7462,6 +7653,9 @@ msgstr ""
msgid "Deploying to"
msgstr ""
+msgid "Deploying to AWS is easy with GitLab"
+msgstr ""
+
msgid "Deployment Frequency"
msgstr ""
@@ -7824,6 +8018,9 @@ msgstr ""
msgid "Dismissed on pipeline %{pipelineLink} at %{projectLink}"
msgstr ""
+msgid "Display alerts from all your monitoring tools directly within GitLab."
+msgstr ""
+
msgid "Display name"
msgstr ""
@@ -7881,9 +8078,6 @@ msgstr ""
msgid "Don't show again"
msgstr ""
-msgid "Don't worry, you can access this tour by clicking on the help icon in the top right corner and choose <strong>Learn GitLab</strong>."
-msgstr ""
-
msgid "Done"
msgstr ""
@@ -8010,6 +8204,9 @@ msgstr ""
msgid "Edit Release"
msgstr ""
+msgid "Edit Slack integration"
+msgstr ""
+
msgid "Edit Snippet"
msgstr ""
@@ -8049,6 +8246,9 @@ msgstr ""
msgid "Edit issues"
msgstr ""
+msgid "Edit iteration"
+msgstr ""
+
msgid "Edit public deploy key"
msgstr ""
@@ -8097,6 +8297,9 @@ msgstr ""
msgid "Email"
msgstr ""
+msgid "Email Notification"
+msgstr ""
+
msgid "Email address"
msgstr "Endereço de email"
@@ -8109,6 +8312,9 @@ msgstr ""
msgid "Email not verified. Please verify your email in Salesforce."
msgstr ""
+msgid "Email notification for unknown sign-ins"
+msgstr ""
+
msgid "Email patch"
msgstr ""
@@ -8340,6 +8546,9 @@ msgstr ""
msgid "Enforce DNS rebinding attack protection"
msgstr ""
+msgid "Enforce personal access token expiration"
+msgstr ""
+
msgid "Ensure connectivity is available from the GitLab server to the Prometheus server"
msgstr ""
@@ -8409,6 +8618,9 @@ msgstr ""
msgid "Enter the name of your application, and we'll return a unique %{type}."
msgstr ""
+msgid "Enter weights for storages for new repositories."
+msgstr ""
+
msgid "Enter your password to approve"
msgstr ""
@@ -8418,15 +8630,27 @@ msgstr ""
msgid "Environment does not have deployments"
msgstr ""
+msgid "Environment is required for Stages::MetricEndpointInserter"
+msgstr ""
+
+msgid "Environment is required for Stages::VariableEndpointInserter"
+msgstr ""
+
msgid "Environment scope"
msgstr ""
+msgid "Environment variables are applied to all project environments in this instance via the Runner. You can use environment variables for passwords, secret keys, etc. Make variables available to the running application by prepending the variable key with <code>K8S_SECRET_</code>. You can set variables to be:"
+msgstr ""
+
msgid "Environment variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. Additionally, they can be masked so they are hidden in job logs, though they must match certain regexp requirements to do so. You can use environment variables for passwords, secret keys, or whatever you want."
msgstr ""
msgid "Environment variables are configured by your administrator to be %{link_start}protected%{link_end} by default"
msgstr ""
+msgid "Environment variables on this GitLab instance are configured to be %{link_start}protected%{link_end} by default"
+msgstr ""
+
msgid "Environment:"
msgstr ""
@@ -8958,6 +9182,9 @@ msgstr ""
msgid "EventFilterBy|Filter by comments"
msgstr ""
+msgid "EventFilterBy|Filter by designs"
+msgstr ""
+
msgid "EventFilterBy|Filter by epic events"
msgstr ""
@@ -9090,15 +9317,15 @@ msgstr ""
msgid "Expand approvers"
msgstr ""
-msgid "Expand child epics"
-msgstr ""
-
msgid "Expand down"
msgstr ""
msgid "Expand dropdown"
msgstr ""
+msgid "Expand milestones"
+msgstr ""
+
msgid "Expand sidebar"
msgstr ""
@@ -9114,6 +9341,9 @@ msgstr ""
msgid "Expiration date"
msgstr ""
+msgid "Expiration not enforced"
+msgstr ""
+
msgid "Expiration policy for the Container Registry is a perfect solution for keeping the Registry space down while still enjoying the full power of GitLab CI/CD."
msgstr ""
@@ -9234,6 +9464,9 @@ msgstr ""
msgid "Failed Jobs"
msgstr ""
+msgid "Failed on"
+msgstr ""
+
msgid "Failed to add a Zoom meeting"
msgstr ""
@@ -9294,13 +9527,19 @@ msgstr ""
msgid "Failed to install."
msgstr ""
+msgid "Failed to load assignees. Please try again."
+msgstr ""
+
+msgid "Failed to load authors. Please try again."
+msgstr ""
+
msgid "Failed to load emoji list."
msgstr ""
msgid "Failed to load error details from Sentry."
msgstr ""
-msgid "Failed to load errors from Sentry. Error message: %{errorMessage}"
+msgid "Failed to load errors from Sentry."
msgstr ""
msgid "Failed to load group activity metrics. Please try again."
@@ -9381,6 +9620,9 @@ msgstr ""
msgid "Failed to set due date because the date format is invalid."
msgstr ""
+msgid "Failed to set iteration on this issue. Please try again."
+msgstr ""
+
msgid "Failed to signing using smartcard authentication"
msgstr ""
@@ -9603,13 +9845,10 @@ msgstr ""
msgid "FeatureFlags|Target environments"
msgstr ""
-msgid "FeatureFlags|There are no active feature flags"
-msgstr ""
-
-msgid "FeatureFlags|There are no inactive feature flags"
+msgid "FeatureFlags|There was an error fetching the feature flags."
msgstr ""
-msgid "FeatureFlags|There was an error fetching the feature flags."
+msgid "FeatureFlags|There was an error retrieving user lists"
msgstr ""
msgid "FeatureFlags|Try again in a few moments or contact your support team."
@@ -9621,9 +9860,18 @@ msgstr ""
msgid "FeatureFlag|Delete strategy"
msgstr ""
+msgid "FeatureFlag|List"
+msgstr ""
+
msgid "FeatureFlag|Percentage"
msgstr ""
+msgid "FeatureFlag|Select a user list"
+msgstr ""
+
+msgid "FeatureFlag|There are no configured user lists"
+msgstr ""
+
msgid "FeatureFlag|Type"
msgstr ""
@@ -9780,6 +10028,12 @@ msgstr ""
msgid "Filter..."
msgstr ""
+msgid "Find File"
+msgstr ""
+
+msgid "Find bugs in your code with coverage-guided fuzzing"
+msgstr ""
+
msgid "Find by path"
msgstr ""
@@ -10044,6 +10298,9 @@ msgstr ""
msgid "Geo allows you to replicate your GitLab instance to other geographical locations."
msgstr ""
+msgid "Geo nodes are paused using a command run on the node"
+msgstr ""
+
msgid "GeoNodeStatusEvent|%{timeAgoStr} (%{pendingEvents} events)"
msgstr ""
@@ -10128,6 +10385,9 @@ msgstr ""
msgid "GeoNodes|Not checksummed"
msgstr ""
+msgid "GeoNodes|Package files"
+msgstr ""
+
msgid "GeoNodes|Pausing replication stops the sync process. Are you sure?"
msgstr ""
@@ -10146,6 +10406,9 @@ msgstr ""
msgid "GeoNodes|Replication slots"
msgstr ""
+msgid "GeoNodes|Replication status"
+msgstr ""
+
msgid "GeoNodes|Repositories"
msgstr ""
@@ -10356,6 +10619,9 @@ msgstr ""
msgid "Geo|Synchronization failed - %{error}"
msgstr ""
+msgid "Geo|Synchronization of %{itemTitle} is disabled."
+msgstr ""
+
msgid "Geo|The database is currently %{db_lag} behind the primary node."
msgstr ""
@@ -10668,6 +10934,9 @@ msgstr ""
msgid "Go to %{link_to_google_takeout}."
msgstr ""
+msgid "Go to <strong>Issues</strong> > <strong>Boards</strong> to access your personalized learning issue board."
+msgstr ""
+
msgid "Go to Pipelines"
msgstr ""
@@ -10767,9 +11036,6 @@ msgstr ""
msgid "Go to your snippets"
msgstr ""
-msgid "Golden Tanuki"
-msgstr ""
-
msgid "Google Cloud Platform"
msgstr ""
@@ -10791,6 +11057,9 @@ msgstr ""
msgid "Grafana URL"
msgstr ""
+msgid "Grafana response contains invalid json"
+msgstr ""
+
msgid "GrafanaIntegration|API Token"
msgstr ""
@@ -10839,9 +11108,6 @@ msgstr ""
msgid "Group %{group_name} was successfully created."
msgstr ""
-msgid "Group '%{group_name}' is being imported."
-msgstr ""
-
msgid "Group Audit Events"
msgstr ""
@@ -10878,9 +11144,6 @@ msgstr ""
msgid "Group by:"
msgstr ""
-msgid "Group could not be imported: %{errors}"
-msgstr ""
-
msgid "Group description"
msgstr ""
@@ -10908,9 +11171,15 @@ msgstr ""
msgid "Group has not been marked for deletion"
msgstr ""
+msgid "Group import could not be scheduled"
+msgstr ""
+
msgid "Group info:"
msgstr ""
+msgid "Group is required when cluster_type is :group"
+msgstr ""
+
msgid "Group maintainers can register group runners in the %{link}"
msgstr "Os responsáveis do grupo podem registar executadores de grupo no %{link}"
@@ -10974,6 +11243,24 @@ msgstr ""
msgid "GroupActivyMetrics|Recent activity (last 90 days)"
msgstr ""
+msgid "GroupImport|Failed to import group."
+msgstr ""
+
+msgid "GroupImport|Group '%{group_name}' is being imported."
+msgstr ""
+
+msgid "GroupImport|Group could not be imported: %{errors}"
+msgstr ""
+
+msgid "GroupImport|Please wait while we import the group for you. Refresh at will."
+msgstr ""
+
+msgid "GroupImport|The group was successfully imported."
+msgstr ""
+
+msgid "GroupImport|Unable to process group import file"
+msgstr ""
+
msgid "GroupRoadmap|%{dateWord} – No end date"
msgstr ""
@@ -11241,6 +11528,9 @@ msgstr "Grupos (%{count})"
msgid "Groups (%{groups})"
msgstr ""
+msgid "Groups and projects"
+msgstr ""
+
msgid "Groups can also be nested by creating %{subgroup_docs_link_start}subgroups%{subgroup_docs_link_end}."
msgstr ""
@@ -11286,6 +11576,33 @@ msgstr ""
msgid "GroupsEmptyState|You can manage your group member’s permissions and access to each project in the group."
msgstr ""
+msgid "GroupsNew|Contact an administrator to enable options for importing your group."
+msgstr ""
+
+msgid "GroupsNew|Create"
+msgstr ""
+
+msgid "GroupsNew|Create group"
+msgstr ""
+
+msgid "GroupsNew|GitLab group export"
+msgstr ""
+
+msgid "GroupsNew|Import"
+msgstr ""
+
+msgid "GroupsNew|Import group"
+msgstr ""
+
+msgid "GroupsNew|My Awesome Group"
+msgstr ""
+
+msgid "GroupsNew|No import options available"
+msgstr ""
+
+msgid "GroupsNew|To copy a GitLab group between installations, navigate to the group settings page for the original installation, generate an export file, and upload it here."
+msgstr ""
+
msgid "GroupsTree|Are you sure you want to leave the \"%{fullName}\" group?"
msgstr ""
@@ -11388,6 +11705,9 @@ msgstr ""
msgid "Helps reduce request volume for protected paths"
msgstr ""
+msgid "Here are all your projects in your group, including the one you just created. To start, let’s take a look at your personalized learning project which will help you learn about GitLab at your own pace."
+msgstr ""
+
msgid "Here you will find recent merge request activity"
msgstr ""
@@ -11402,6 +11722,9 @@ msgid_plural "Hide charts"
msgstr[0] ""
msgstr[1] ""
+msgid "Hide details"
+msgstr ""
+
msgid "Hide file browser"
msgstr ""
@@ -11411,6 +11734,9 @@ msgstr ""
msgid "Hide host keys manual input"
msgstr ""
+msgid "Hide list"
+msgstr ""
+
msgid "Hide marketing-related entries from help"
msgstr ""
@@ -11431,9 +11757,6 @@ msgstr[1] ""
msgid "Hide values"
msgstr ""
-msgid "Hiding all labels"
-msgstr ""
-
msgid "High or unknown vulnerabilities present"
msgstr ""
@@ -11518,6 +11841,9 @@ msgstr ""
msgid "ID:"
msgstr ""
+msgid "IDE"
+msgstr ""
+
msgid "IDE|Allow live previews of JavaScript projects in the Web IDE using CodeSandbox Live Preview."
msgstr ""
@@ -11608,10 +11934,10 @@ msgstr ""
msgid "If enabled"
msgstr ""
-msgid "If enabled, access to projects will be validated on an external service using their classification label."
+msgid "If enabled, GitLab will handle Object Storage replication using Geo. %{linkStart}More information%{linkEnd}"
msgstr ""
-msgid "If enabled, and if object storage is enabled, GitLab will handle Object Storage replication using Geo"
+msgid "If enabled, access to projects will be validated on an external service using their classification label."
msgstr ""
msgid "If there is no previous license or if the previous license has expired, some GitLab functionality will be blocked until a new, valid license is uploaded."
@@ -11973,6 +12299,9 @@ msgid_plural "Instances"
msgstr[0] ""
msgstr[1] ""
+msgid "Instance Configuration"
+msgstr ""
+
msgid "Instance Statistics visibility"
msgstr ""
@@ -11994,9 +12323,6 @@ msgstr ""
msgid "Integrations"
msgstr ""
-msgid "Integrations allow you to integrate GitLab with other applications"
-msgstr ""
-
msgid "Integrations|All details"
msgstr ""
@@ -12108,6 +12434,9 @@ msgstr ""
msgid "Invalid query"
msgstr ""
+msgid "Invalid repository bundle for snippet with id %{snippet_id}"
+msgstr ""
+
msgid "Invalid repository path"
msgstr ""
@@ -12126,6 +12455,9 @@ msgstr ""
msgid "Invalid two-factor code."
msgstr ""
+msgid "Invalid yaml"
+msgstr ""
+
msgid "Invitation"
msgstr ""
@@ -12345,15 +12677,24 @@ msgstr ""
msgid "It seems like the Dependency Scanning job ran successfully, but no dependencies have been detected in your project."
msgstr ""
+msgid "It seems that there is currently no available data for code coverage"
+msgstr ""
+
msgid "It's you"
msgstr ""
+msgid "Iteration"
+msgstr ""
+
msgid "Iteration changed to"
msgstr ""
msgid "Iteration removed"
msgstr ""
+msgid "Iteration updated"
+msgstr ""
+
msgid "Iterations"
msgstr ""
@@ -12384,12 +12725,18 @@ msgstr ""
msgid "January"
msgstr ""
+msgid "Jira Issues"
+msgstr ""
+
msgid "Jira import is already running."
msgstr ""
msgid "Jira integration not configured."
msgstr ""
+msgid "Jira project key is not configured"
+msgstr ""
+
msgid "Jira project: %{importProject}"
msgstr ""
@@ -12570,6 +12917,9 @@ msgstr ""
msgid "Keep divergent refs"
msgstr ""
+msgid "Kerberos access denied"
+msgstr ""
+
msgid "Key"
msgstr ""
@@ -12579,10 +12929,13 @@ msgstr ""
msgid "Key: %{key}"
msgstr ""
-msgid "Keyboard Shortcuts"
+msgid "Keyboard shortcuts"
msgstr ""
-msgid "Keyboard shortcuts"
+msgid "Keys"
+msgstr ""
+
+msgid "Ki"
msgstr ""
msgid "Kubernetes"
@@ -12624,6 +12977,9 @@ msgstr ""
msgid "LDAP"
msgstr ""
+msgid "LDAP Synchronization"
+msgstr ""
+
msgid "LDAP settings"
msgstr ""
@@ -12848,12 +13204,18 @@ msgstr ""
msgid "Learn more about custom project templates"
msgstr ""
+msgid "Learn more about deploying to AWS"
+msgstr ""
+
msgid "Learn more about deploying to a cluster"
msgstr ""
msgid "Learn more about group-level project templates"
msgstr ""
+msgid "Learn more about job dependencies"
+msgstr ""
+
msgid "Learn more about signing commits"
msgstr ""
@@ -12905,9 +13267,21 @@ msgstr ""
msgid "License History"
msgstr ""
+msgid "License ID:"
+msgstr ""
+
+msgid "License URL"
+msgstr ""
+
msgid "License-Check"
msgstr ""
+msgid "LicenseCompliance|%{docLinkStart}License Approvals%{docLinkEnd} are active"
+msgstr ""
+
+msgid "LicenseCompliance|%{docLinkStart}License Approvals%{docLinkEnd} are inactive"
+msgstr ""
+
msgid "LicenseCompliance|Add a license"
msgstr ""
@@ -12929,9 +13303,15 @@ msgstr ""
msgid "LicenseCompliance|Deny"
msgstr ""
+msgid "LicenseCompliance|Learn more about %{linkStart}License Approvals%{linkEnd}"
+msgstr ""
+
msgid "LicenseCompliance|License"
msgstr ""
+msgid "LicenseCompliance|License Approvals"
+msgstr ""
+
msgid "LicenseCompliance|License Compliance detected %d license and policy violation for the source branch only; approval required"
msgid_plural "LicenseCompliance|License Compliance detected %d licenses and policy violations for the source branch only; approval required"
msgstr[0] ""
@@ -13152,6 +13532,9 @@ msgstr ""
msgid "List your Bitbucket Server repositories"
msgstr ""
+msgid "Lists"
+msgstr ""
+
msgid "Live preview"
msgstr ""
@@ -13335,6 +13718,9 @@ msgstr ""
msgid "Manage project labels"
msgstr ""
+msgid "Manage storage usage"
+msgstr ""
+
msgid "Manage two-factor authentication"
msgstr ""
@@ -13350,6 +13736,9 @@ msgstr ""
msgid "Manifest file import"
msgstr ""
+msgid "Manifest import"
+msgstr ""
+
msgid "Manual job"
msgstr ""
@@ -13497,6 +13886,12 @@ msgstr ""
msgid "Maximum field length"
msgstr ""
+msgid "Maximum file size is 2MB. Please select a smaller file."
+msgstr ""
+
+msgid "Maximum import size (MB)"
+msgstr ""
+
msgid "Maximum job timeout"
msgstr ""
@@ -13536,6 +13931,9 @@ msgstr ""
msgid "Maximum size of Elasticsearch bulk indexing requests."
msgstr ""
+msgid "Maximum size of import files."
+msgstr ""
+
msgid "Maximum size of individual attachments in comments."
msgstr ""
@@ -13587,6 +13985,9 @@ msgstr ""
msgid "Merge (when the pipeline succeeds)"
msgstr ""
+msgid "Merge Conflicts"
+msgstr ""
+
msgid "Merge Request"
msgstr ""
@@ -13713,6 +14114,9 @@ msgstr ""
msgid "MergeRequests|Squash task canceled: another squash is already in progress."
msgstr ""
+msgid "MergeRequests|This project does not allow squashing commits when merge requests are accepted."
+msgstr ""
+
msgid "MergeRequests|Thread stays resolved"
msgstr ""
@@ -13866,6 +14270,12 @@ msgstr ""
msgid "MetricsSettings|Add a button to the metrics dashboard linking directly to your existing external dashboard."
msgstr ""
+msgid "MetricsSettings|Choose whether to display dashboard metrics in UTC or the user's local timezone."
+msgstr ""
+
+msgid "MetricsSettings|Dashboard timezone"
+msgstr ""
+
msgid "MetricsSettings|External dashboard URL"
msgstr ""
@@ -13875,6 +14285,12 @@ msgstr ""
msgid "MetricsSettings|Metrics Dashboard"
msgstr ""
+msgid "MetricsSettings|UTC (Coordinated Universal Time)"
+msgstr ""
+
+msgid "MetricsSettings|User's local timezone"
+msgstr ""
+
msgid "Metrics|Add metric"
msgstr ""
@@ -13890,6 +14306,9 @@ msgstr ""
msgid "Metrics|Create metric"
msgstr ""
+msgid "Metrics|Current"
+msgstr ""
+
msgid "Metrics|Delete metric"
msgstr ""
@@ -13940,6 +14359,9 @@ msgstr ""
msgid "Metrics|Max"
msgstr ""
+msgid "Metrics|Min"
+msgstr ""
+
msgid "Metrics|Must be a valid PromQL query."
msgstr ""
@@ -13955,6 +14377,9 @@ msgstr ""
msgid "Metrics|Refresh dashboard"
msgstr ""
+msgid "Metrics|Select a value"
+msgstr ""
+
msgid "Metrics|Star dashboard"
msgstr ""
@@ -13973,12 +14398,18 @@ msgstr ""
msgid "Metrics|There was an error getting annotations information."
msgstr ""
+msgid "Metrics|There was an error getting dashboard validation warnings information."
+msgstr ""
+
msgid "Metrics|There was an error getting deployment information."
msgstr ""
msgid "Metrics|There was an error getting environments information."
msgstr ""
+msgid "Metrics|There was an error getting options for variable \"%{name}\"."
+msgstr ""
+
msgid "Metrics|There was an error trying to validate your query"
msgstr "Houve um erro ao tentar validar a tua consulta"
@@ -14021,6 +14452,9 @@ msgstr ""
msgid "Metrics|You're about to permanently delete this metric. This cannot be undone."
msgstr ""
+msgid "Metrics|Your dashboard schema is invalid. Edit the dashboard to correct the YAML schema."
+msgstr ""
+
msgid "Metrics|e.g. HTTP requests"
msgstr ""
@@ -14036,12 +14470,18 @@ msgstr ""
msgid "Metrics|e.g. req/sec"
msgstr ""
+msgid "Mi"
+msgstr ""
+
msgid "Microsoft Azure"
msgstr ""
msgid "Middleman project with Static Site Editor support"
msgstr ""
+msgid "Migrate your data from an external source like GitHub, Bitbucket, or another instance of GitLab."
+msgstr ""
+
msgid "Migrated %{success_count}/%{total_count} files."
msgstr ""
@@ -14176,6 +14616,9 @@ msgstr ""
msgid "Minimum capacity to be available before we schedule more mirrors preemptively."
msgstr ""
+msgid "Minimum interval in days"
+msgstr ""
+
msgid "Minimum length is %{minimum_password_length} characters"
msgstr ""
@@ -14362,6 +14805,12 @@ msgstr ""
msgid "Multiple uploaders found: %{uploader_types}"
msgstr ""
+msgid "Must match with the %{codeStart}external_url%{codeEnd} in %{codeStart}/etc/gitlab/gitlab.rb%{codeEnd}."
+msgstr ""
+
+msgid "Must match with the %{codeStart}geo_node_name%{codeEnd} in %{codeStart}/etc/gitlab/gitlab.rb%{codeEnd}. %{linkStart}More information%{linkEnd}"
+msgstr ""
+
msgid "My Awesome Group"
msgstr ""
@@ -14476,10 +14925,16 @@ msgstr ""
msgid "New Application"
msgstr ""
+msgid "New Branch"
+msgstr ""
+
+msgid "New Deploy Key"
+msgstr ""
+
msgid "New Environment"
msgstr ""
-msgid "New Geo Node"
+msgid "New File"
msgstr ""
msgid "New Group"
@@ -14496,15 +14951,15 @@ msgid_plural "New Issues"
msgstr[0] ""
msgstr[1] ""
-msgid "New Iteration"
-msgstr ""
-
msgid "New Jira import"
msgstr ""
msgid "New Label"
msgstr ""
+msgid "New Merge Request"
+msgstr ""
+
msgid "New Milestone"
msgstr "Novo Objetivo"
@@ -14523,6 +14978,9 @@ msgstr ""
msgid "New Snippet"
msgstr ""
+msgid "New User"
+msgstr ""
+
msgid "New branch"
msgstr ""
@@ -14565,6 +15023,9 @@ msgstr ""
msgid "New issue title"
msgstr ""
+msgid "New iteration"
+msgstr ""
+
msgid "New iteration created"
msgstr ""
@@ -14742,6 +15203,12 @@ msgstr ""
msgid "No grouping"
msgstr ""
+msgid "No iteration"
+msgstr ""
+
+msgid "No iterations to show"
+msgstr ""
+
msgid "No job log"
msgstr ""
@@ -14784,6 +15251,9 @@ msgstr "Nenhum objetivo para mostrar"
msgid "No other labels with such name or description"
msgstr "Nenhumas outras etiquetas com tal nome ou descrição"
+msgid "No panels matching properties %{opts}"
+msgstr ""
+
msgid "No parent group"
msgstr ""
@@ -14817,6 +15287,9 @@ msgstr ""
msgid "No schedules"
msgstr ""
+msgid "No stack trace for this error"
+msgstr ""
+
msgid "No starrers matched your search"
msgstr ""
@@ -14829,7 +15302,7 @@ msgstr "Nenhum modelo"
msgid "No test coverage"
msgstr ""
-msgid "No thanks, don't show this again"
+msgid "No thanks"
msgstr ""
msgid "No vulnerabilities present"
@@ -14844,9 +15317,6 @@ msgstr ""
msgid "No, directly import the existing email addresses and usernames."
msgstr ""
-msgid "No, not interested right now"
-msgstr ""
-
msgid "No. of commits"
msgstr ""
@@ -14892,9 +15362,6 @@ msgstr ""
msgid "Not found."
msgstr ""
-msgid "Not helpful"
-msgstr ""
-
msgid "Not now"
msgstr ""
@@ -15051,6 +15518,9 @@ msgstr ""
msgid "Notifications on"
msgstr ""
+msgid "Notify users by email when sign-in location is not recognized"
+msgstr ""
+
msgid "Nov"
msgstr ""
@@ -15117,9 +15587,6 @@ msgstr ""
msgid "Oh no!"
msgstr ""
-msgid "Ok let's go"
-msgstr ""
-
msgid "Oldest first"
msgstr ""
@@ -15132,16 +15599,49 @@ msgstr ""
msgid "On track"
msgstr ""
+msgid "OnDemandScans|Attached branch"
+msgstr ""
+
+msgid "OnDemandScans|Attached branch is where the scan job runs."
+msgstr ""
+
+msgid "OnDemandScans|Could not run the scan. Please try again."
+msgstr ""
+
msgid "OnDemandScans|Create new DAST scan"
msgstr ""
+msgid "OnDemandScans|DAST will scan the target URL and any discovered sub URLs."
+msgstr ""
+
+msgid "OnDemandScans|New on-demand DAST scan"
+msgstr ""
+
msgid "OnDemandScans|On-demand Scans"
msgstr ""
+msgid "OnDemandScans|On-demand scans run outside the DevOps cycle and find vulnerabilities in your projects. %{learnMoreLinkStart}Learn more%{learnMoreLinkEnd}"
+msgstr ""
+
+msgid "OnDemandScans|Only a passive scan can be performed on demand."
+msgstr ""
+
+msgid "OnDemandScans|Passive DAST Scan"
+msgstr ""
+
+msgid "OnDemandScans|Please enter a valid URL format, ex: http://www.example.com/home"
+msgstr ""
+
+msgid "OnDemandScans|Run this scan"
+msgstr ""
+
+msgid "OnDemandScans|Scan mode"
+msgstr ""
+
msgid "OnDemandScans|Schedule or run scans immediately against target sites. Currently available on-demand scan type: DAST. %{helpLinkStart}More information%{helpLinkEnd}"
msgstr ""
-msgid "Onboarding"
+msgid "OnDemandScans|Target URL"
msgstr ""
msgid "Once imported, repositories can be mirrored over SSH. Read more %{link_start}here%{link_end}."
@@ -15167,6 +15667,9 @@ msgstr ""
msgid "One or more of you personal access tokens were revoked"
msgstr ""
+msgid "One or more of your %{provider} projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
+msgstr ""
+
msgid "One or more of your Bitbucket projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
msgstr ""
@@ -15668,6 +16171,9 @@ msgstr ""
msgid "Parent epic is not present."
msgstr ""
+msgid "Parsing error for param :embed_json. %{message}"
+msgstr ""
+
msgid "Part of merge request changes"
msgstr ""
@@ -15680,6 +16186,9 @@ msgstr ""
msgid "Passed"
msgstr ""
+msgid "Passed on"
+msgstr ""
+
msgid "Password"
msgstr ""
@@ -15764,6 +16273,9 @@ msgstr ""
msgid "Perform common operations on GitLab project"
msgstr ""
+msgid "Performance and resource management"
+msgstr ""
+
msgid "Performance optimization"
msgstr ""
@@ -15971,7 +16483,7 @@ msgstr ""
msgid "Pipelines|Something went wrong while cleaning runners cache."
msgstr ""
-msgid "Pipelines|There are currently no %{scope} pipelines."
+msgid "Pipelines|There are currently no finished pipelines."
msgstr ""
msgid "Pipelines|There are currently no pipelines."
@@ -16082,6 +16594,9 @@ msgstr ""
msgid "Pipeline|Stop pipeline #%{pipelineId}?"
msgstr ""
+msgid "Pipeline|Tag name"
+msgstr ""
+
msgid "Pipeline|Trigger author"
msgstr ""
@@ -16163,12 +16678,18 @@ msgstr ""
msgid "Please check your email (%{email}) to verify that you own this address and unlock the power of CI/CD. Didn't receive it? %{resend_link}. Wrong email address? %{update_link}."
msgstr ""
+msgid "Please choose a file"
+msgstr ""
+
msgid "Please choose a group URL with no special characters."
msgstr ""
msgid "Please complete your profile with email address"
msgstr ""
+msgid "Please convert %{linkStart}them to Git%{linkEnd}, and go through the %{linkToImportFlow} again."
+msgstr ""
+
msgid "Please convert them to %{link_to_git}, and go through the %{link_to_import_flow} again."
msgstr ""
@@ -16226,6 +16747,9 @@ msgstr ""
msgid "Please provide a valid email address."
msgstr ""
+msgid "Please provide attributes to update"
+msgstr ""
+
msgid "Please refer to <a href=\"%{docs_url}\">%{docs_url}</a>"
msgstr ""
@@ -16961,6 +17485,9 @@ msgstr ""
msgid "Project has too many %{label_for_message} to search"
msgstr ""
+msgid "Project is required when cluster_type is :project"
+msgstr ""
+
msgid "Project members"
msgstr ""
@@ -17078,7 +17605,40 @@ msgstr ""
msgid "ProjectService|Comment"
msgstr ""
-msgid "ProjectService|Comment will be posted on each event"
+msgid "ProjectService|Event will be triggered by a push to the repository"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a commit is created/updated"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a confidential issue is created/updated/closed"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a deployment finishes"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a merge request is created/updated/merged"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a new tag is pushed to the repository"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a new, unique alert is recorded"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a pipeline status changes"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a wiki page is created/updated"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when an issue is created/updated/closed"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when someone adds a comment"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when someone adds a comment on a confidential issue"
msgstr ""
msgid "ProjectService|Perform common operations on GitLab project: %{project_name}"
@@ -17129,6 +17689,9 @@ msgstr ""
msgid "ProjectSettings|Enable 'Delete source branch' option by default"
msgstr ""
+msgid "ProjectSettings|Enable merge trains and pipelines for merged results"
+msgstr ""
+
msgid "ProjectSettings|Every merge creates a merge commit"
msgstr ""
@@ -17159,7 +17722,7 @@ msgstr ""
msgid "ProjectSettings|Forks"
msgstr ""
-msgid "ProjectSettings|Git Large File Storage"
+msgid "ProjectSettings|Git Large File Storage (LFS)"
msgstr ""
msgid "ProjectSettings|Internal"
@@ -17195,9 +17758,6 @@ msgstr ""
msgid "ProjectSettings|Merge options"
msgstr ""
-msgid "ProjectSettings|Merge pipelines will try to validate the post-merge result prior to merging"
-msgstr ""
-
msgid "ProjectSettings|Merge requests"
msgstr ""
@@ -17225,6 +17785,9 @@ msgstr ""
msgid "ProjectSettings|Pipelines"
msgstr ""
+msgid "ProjectSettings|Pipelines for merge requests must be enabled in the CI/CD configuration file, or pipelines could be unresolvable or dropped"
+msgstr ""
+
msgid "ProjectSettings|Pipelines must succeed"
msgstr ""
@@ -17252,6 +17815,9 @@ msgstr ""
msgid "ProjectSettings|Show link to create/view merge request when pushing from the command line"
msgstr ""
+msgid "ProjectSettings|Skipped pipelines are considered successful"
+msgstr ""
+
msgid "ProjectSettings|Snippets"
msgstr ""
@@ -17267,6 +17833,9 @@ msgstr ""
msgid "ProjectSettings|These checks must pass before merge requests can be merged"
msgstr ""
+msgid "ProjectSettings|This introduces the risk of merging changes that will not pass the pipeline."
+msgstr ""
+
msgid "ProjectSettings|This setting is applied on the server level and can be overridden by an admin."
msgstr ""
@@ -17444,15 +18013,27 @@ msgstr ""
msgid "ProjectsNew|Blank project"
msgstr ""
+msgid "ProjectsNew|Connect your external repository to GitLab CI/CD."
+msgstr ""
+
msgid "ProjectsNew|Contact an administrator to enable options for importing your project."
msgstr ""
msgid "ProjectsNew|Create"
msgstr ""
+msgid "ProjectsNew|Create a blank project to house your files, plan your work, and collaborate on code, among other things."
+msgstr ""
+
+msgid "ProjectsNew|Create blank project"
+msgstr ""
+
msgid "ProjectsNew|Create from template"
msgstr ""
+msgid "ProjectsNew|Create new project"
+msgstr ""
+
msgid "ProjectsNew|Creating project & repository."
msgstr ""
@@ -17477,6 +18058,9 @@ msgstr ""
msgid "ProjectsNew|Project description %{tag_start}(optional)%{tag_end}"
msgstr ""
+msgid "ProjectsNew|Run CI/CD for external repository"
+msgstr ""
+
msgid "ProjectsNew|Template"
msgstr ""
@@ -17738,6 +18322,9 @@ msgstr ""
msgid "Protected Branch"
msgstr ""
+msgid "Protected Branches"
+msgstr ""
+
msgid "Protected Environment"
msgstr ""
@@ -17750,6 +18337,9 @@ msgstr ""
msgid "Protected Tag"
msgstr ""
+msgid "Protected Tags"
+msgstr ""
+
msgid "Protected branches"
msgstr ""
@@ -17849,6 +18439,9 @@ msgstr ""
msgid "Provider"
msgstr ""
+msgid "Proxy support for this API is not available currently"
+msgstr ""
+
msgid "Pseudonymizer data collection"
msgstr ""
@@ -17873,6 +18466,9 @@ msgstr ""
msgid "Publish to status page"
msgstr ""
+msgid "Published on status page"
+msgstr ""
+
msgid "Publishes this issue to the associated status page."
msgstr ""
@@ -18047,6 +18643,9 @@ msgstr ""
msgid "Recipe"
msgstr ""
+msgid "Reconfigure"
+msgstr ""
+
msgid "Recover hidden stage"
msgstr ""
@@ -18133,6 +18732,9 @@ msgstr ""
msgid "Registration|Your profile"
msgstr ""
+msgid "Regulate approvals by authors/committers, based on compliance frameworks. Can be changed only at the instance level."
+msgstr ""
+
msgid "Rejected (closed)"
msgstr ""
@@ -18183,15 +18785,24 @@ msgstr ""
msgid "ReleaseAssetLinkType|Image"
msgstr ""
+msgid "ReleaseAssetLinkType|Images"
+msgstr ""
+
msgid "ReleaseAssetLinkType|Other"
msgstr ""
msgid "ReleaseAssetLinkType|Package"
msgstr ""
+msgid "ReleaseAssetLinkType|Packages"
+msgstr ""
+
msgid "ReleaseAssetLinkType|Runbook"
msgstr ""
+msgid "ReleaseAssetLinkType|Runbooks"
+msgstr ""
+
msgid "Releases"
msgstr ""
@@ -18204,6 +18815,9 @@ msgstr ""
msgid "Releases documentation"
msgstr ""
+msgid "Releases|New Release"
+msgstr ""
+
msgid "Release|Something went wrong while getting the release details"
msgstr ""
@@ -18276,6 +18890,9 @@ msgstr ""
msgid "Remove fork relationship"
msgstr ""
+msgid "Remove from batch"
+msgstr ""
+
msgid "Remove from board"
msgstr ""
@@ -18465,6 +19082,12 @@ msgstr ""
msgid "Replication"
msgstr ""
+msgid "Replication enabled"
+msgstr ""
+
+msgid "Replication paused"
+msgstr ""
+
msgid "Reply by email"
msgstr ""
@@ -18492,15 +19115,15 @@ msgstr ""
msgid "Reported %{timeAgo} by %{reportedBy}"
msgstr ""
-msgid "Reported Resource Changes: %{addNum} to add, %{changeNum} to change, %{deleteNum} to delete"
-msgstr ""
-
msgid "Reporter"
msgstr ""
msgid "Reporting"
msgstr ""
+msgid "Reports"
+msgstr ""
+
msgid "Reports|%{combinedString} and %{resolvedString}"
msgstr ""
@@ -18539,6 +19162,9 @@ msgstr ""
msgid "Reports|Failure"
msgstr ""
+msgid "Reports|Identifier"
+msgstr ""
+
msgid "Reports|Metrics reports are loading"
msgstr ""
@@ -18551,6 +19177,9 @@ msgstr ""
msgid "Reports|Metrics reports failed loading results"
msgstr ""
+msgid "Reports|Scanner"
+msgstr ""
+
msgid "Reports|Severity"
msgstr ""
@@ -18599,15 +19228,33 @@ msgstr ""
msgid "Repository cleanup has started. You will receive an email once the cleanup operation is complete."
msgstr ""
+msgid "Repository files count over the limit"
+msgstr ""
+
+msgid "Repository has an invalid default branch name."
+msgstr ""
+
+msgid "Repository has more than one branch."
+msgstr ""
+
msgid "Repository has no locks."
msgstr ""
+msgid "Repository has tags."
+msgstr ""
+
msgid "Repository maintenance"
msgstr ""
msgid "Repository mirroring"
msgstr ""
+msgid "Repository must contain at least 1 file."
+msgstr ""
+
+msgid "Repository size is above the limit."
+msgstr ""
+
msgid "Repository static objects"
msgstr ""
@@ -18617,7 +19264,7 @@ msgstr ""
msgid "Repository sync capacity"
msgstr ""
-msgid "Repository: %{counter_repositories} / Wikis: %{counter_wikis} / Build Artifacts: %{counter_build_artifacts} / LFS: %{counter_lfs_objects}"
+msgid "Repository: %{counter_repositories} / Wikis: %{counter_wikis} / Build Artifacts: %{counter_build_artifacts} / LFS: %{counter_lfs_objects} / Snippets: %{counter_snippets}"
msgstr ""
msgid "RepositorySettingsAccessLevel|Select"
@@ -18629,6 +19276,9 @@ msgstr ""
msgid "Request Headers"
msgstr ""
+msgid "Request details"
+msgstr ""
+
msgid "Request parameter %{param} is missing."
msgstr ""
@@ -18735,6 +19385,9 @@ msgstr ""
msgid "Reset template"
msgstr "Redefinir modelo"
+msgid "Reset to project defaults"
+msgstr ""
+
msgid "Resetting the authorization key for this project will require updating the authorization key in every alert source it is enabled in."
msgstr ""
@@ -18831,9 +19484,6 @@ msgstr ""
msgid "Resume"
msgstr ""
-msgid "Resume replication"
-msgstr ""
-
msgid "Resync"
msgstr ""
@@ -19456,6 +20106,9 @@ msgstr ""
msgid "SecurityReports|False positive"
msgstr ""
+msgid "SecurityReports|Group Security Dashboard"
+msgstr ""
+
msgid "SecurityReports|Hide dismissed"
msgstr ""
@@ -19510,10 +20163,13 @@ msgstr ""
msgid "SecurityReports|Remove project from dashboard"
msgstr ""
-msgid "SecurityReports|Report type"
+msgid "SecurityReports|Return to dashboard"
msgstr ""
-msgid "SecurityReports|Return to dashboard"
+msgid "SecurityReports|Scan details"
+msgstr ""
+
+msgid "SecurityReports|Scanner type"
msgstr ""
msgid "SecurityReports|Security Dashboard"
@@ -19570,6 +20226,9 @@ msgstr ""
msgid "SecurityReports|There was an error while generating the report."
msgstr ""
+msgid "SecurityReports|Unable to add %{invalidProjectsMessage}"
+msgstr ""
+
msgid "SecurityReports|Unable to add %{invalidProjects}"
msgstr ""
@@ -19684,6 +20343,9 @@ msgstr ""
msgid "Select due date"
msgstr ""
+msgid "Select file"
+msgstr ""
+
msgid "Select group or project"
msgstr ""
@@ -19747,9 +20409,6 @@ msgstr ""
msgid "Select the branch you want to set as the default for this project. All merge requests and commits will automatically be made against this branch unless you specify a different one."
msgstr ""
-msgid "Select the configured storage available for new repositories to be placed on."
-msgstr ""
-
msgid "Select the custom project template source group."
msgstr ""
@@ -19942,12 +20601,6 @@ msgstr ""
msgid "Service Desk is enabled but not yet active"
msgstr ""
-msgid "Service Desk is off"
-msgstr ""
-
-msgid "Service Desk is on"
-msgstr ""
-
msgid "Service Templates"
msgstr ""
@@ -20035,6 +20688,15 @@ msgstr ""
msgid "Set the milestone to %{milestone_reference}."
msgstr ""
+msgid "Set the number of concurrent requests this secondary node will make to the primary node while backfilling."
+msgstr ""
+
+msgid "Set the synchronization and verification capacity for the secondary node."
+msgstr ""
+
+msgid "Set the timeout in seconds to send a secondary node status to the primary and IPs allowed for the secondary nodes."
+msgstr ""
+
msgid "Set time estimate"
msgstr ""
@@ -20074,6 +20736,9 @@ msgstr ""
msgid "Set weight to %{weight}."
msgstr ""
+msgid "Set what should be replicated by choosing specific projects or groups by the secondary node."
+msgstr ""
+
msgid "SetPasswordToCloneLink|set a password"
msgstr ""
@@ -20200,6 +20865,9 @@ msgstr "Mostrar descrição do envio"
msgid "Show complete raw log"
msgstr ""
+msgid "Show details"
+msgstr ""
+
msgid "Show file browser"
msgstr ""
@@ -20209,10 +20877,13 @@ msgstr ""
msgid "Show latest version"
msgstr ""
+msgid "Show list"
+msgstr ""
+
msgid "Show me everything"
msgstr ""
-msgid "Show me how"
+msgid "Show me how to add a pipeline"
msgstr ""
msgid "Show me more advanced stuff"
@@ -20261,9 +20932,6 @@ msgstr ""
msgid "Showing all issues"
msgstr ""
-msgid "Showing all labels"
-msgstr ""
-
msgid "Showing last %{size} of log -"
msgstr "Mostrar último %{size} do registo -"
@@ -20375,9 +21043,6 @@ msgstr ""
msgid "Skip outdated deployment jobs"
msgstr ""
-msgid "Skip this for now"
-msgstr ""
-
msgid "Skipped"
msgstr ""
@@ -20522,6 +21187,9 @@ msgstr ""
msgid "Something went wrong while adding your award. Please try again."
msgstr ""
+msgid "Something went wrong while applying the batch of suggestions. Please try again."
+msgstr ""
+
msgid "Something went wrong while applying the suggestion. Please try again."
msgstr ""
@@ -20624,10 +21292,13 @@ msgstr ""
msgid "Something went wrong while updating a requirement."
msgstr ""
+msgid "Something went wrong while updating assignees"
+msgstr ""
+
msgid "Something went wrong while updating your list settings"
msgstr ""
-msgid "Something went wrong with your automatic subscription renewal"
+msgid "Something went wrong with your automatic subscription renewal."
msgstr ""
msgid "Something went wrong, unable to add %{project} to dashboard"
@@ -20645,6 +21316,9 @@ msgstr ""
msgid "Something went wrong, unable to search projects"
msgstr ""
+msgid "Something went wrong."
+msgstr ""
+
msgid "Something went wrong. Please try again."
msgstr ""
@@ -20828,6 +21502,9 @@ msgstr ""
msgid "Source code"
msgstr ""
+msgid "Source code (%{fileExtension})"
+msgstr ""
+
msgid "Source is not available"
msgstr ""
@@ -20912,6 +21589,9 @@ msgstr ""
msgid "Stack trace"
msgstr ""
+msgid "Stacktrace snippet"
+msgstr ""
+
msgid "Stage"
msgstr ""
@@ -21017,6 +21697,9 @@ msgstr ""
msgid "Start thread & reopen %{noteable_name}"
msgstr ""
+msgid "Start using Directed Acyclic Graphs (DAG)"
+msgstr ""
+
msgid "Start your Free Gold Trial"
msgstr ""
@@ -21479,6 +22162,21 @@ msgstr ""
msgid "SuggestedColors|Very pale orange"
msgstr ""
+msgid "Suggestion is not applicable as the suggestion was not found."
+msgstr ""
+
+msgid "Suggestions are not applicable as one or more suggestions were not found."
+msgstr ""
+
+msgid "Suggestions are not applicable as their lines cannot overlap."
+msgstr ""
+
+msgid "Suggestions must all be on the same branch."
+msgstr ""
+
+msgid "Suggestions that change line count can't be added to batches, yet."
+msgstr ""
+
msgid "Suggestions:"
msgstr ""
@@ -21521,9 +22219,6 @@ msgstr ""
msgid "Synchronization disabled"
msgstr ""
-msgid "Synchronization of container repositories is disabled."
-msgstr ""
-
msgid "System"
msgstr ""
@@ -21704,6 +22399,34 @@ msgstr ""
msgid "Terms of Service and Privacy Policy"
msgstr ""
+msgid "Terraform|%{number} Terraform report failed to generate"
+msgid_plural "Terraform|%{number} Terraform reports failed to generate"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Terraform|%{number} Terraform report was generated in your pipelines"
+msgid_plural "Terraform|%{number} Terraform reports were generated in your pipelines"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Terraform|A Terraform report failed to generate."
+msgstr ""
+
+msgid "Terraform|A Terraform report was generated in your pipelines."
+msgstr ""
+
+msgid "Terraform|Generating the report caused an error."
+msgstr ""
+
+msgid "Terraform|Reported Resource Changes: %{addNum} to add, %{changeNum} to change, %{deleteNum} to delete"
+msgstr ""
+
+msgid "Terraform|The Terraform report %{name} failed to generate."
+msgstr ""
+
+msgid "Terraform|The Terraform report %{name} was generated in your pipelines."
+msgstr ""
+
msgid "Test"
msgstr ""
@@ -21730,7 +22453,7 @@ msgstr ""
msgid "TestHooks|Ensure the project has CI pipelines."
msgstr ""
-msgid "TestHooks|Ensure the project has at least one commit."
+msgid "TestHooks|Ensure the project has deployments."
msgstr ""
msgid "TestHooks|Ensure the project has issues."
@@ -21831,7 +22554,7 @@ msgstr ""
msgid "The Prometheus server responded with \"bad request\". Please check your queries are correct and are supported in your Prometheus version. %{documentationLink}"
msgstr ""
-msgid "The URL defined on the primary node that secondary nodes should use to contact it. Defaults to URL"
+msgid "The URL defined on the primary node that secondary nodes should use to contact it."
msgstr ""
msgid "The URL to use for connecting to Elasticsearch. Use a comma-separated list to support clustering (e.g., \"http://localhost:9200, http://localhost:9201\")."
@@ -21840,6 +22563,9 @@ msgstr ""
msgid "The X509 Certificate to use when mutual TLS is required to communicate with the external authorization service. If left blank, the server certificate is still validated when accessing over HTTPS."
msgstr ""
+msgid "The above settings apply to all projects with the selected compliance framework(s)."
+msgstr ""
+
msgid "The amount of seconds after which a request to get a secondary node status will time out."
msgstr ""
@@ -22139,9 +22865,6 @@ msgstr ""
msgid "The total stage shows the time it takes between creating an issue and deploying the code to production. The data will be automatically added once you have completed the full idea to production cycle."
msgstr ""
-msgid "The unique identifier for the Geo node. Must match %{geoNodeName} if it is set in gitlab.rb, otherwise it must match %{externalUrl} with a trailing slash"
-msgstr ""
-
msgid "The update action will time out after %{number_of_minutes} minutes. For big repositories, use a clone/push combination."
msgstr ""
@@ -22166,9 +22889,6 @@ msgstr ""
msgid "The user you are trying to deactivate has been active in the past %{minimum_inactive_days} days and cannot be deactivated"
msgstr ""
-msgid "The user-facing URL of the Geo node"
-msgstr ""
-
msgid "The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6."
msgstr ""
@@ -22259,6 +22979,9 @@ msgstr ""
msgid "There was a problem fetching project branches."
msgstr ""
+msgid "There was a problem fetching project tags."
+msgstr ""
+
msgid "There was a problem fetching project users."
msgstr ""
@@ -22310,6 +23033,9 @@ msgstr ""
msgid "There was an error fetching the %{replicableType}"
msgstr ""
+msgid "There was an error fetching the Geo Settings"
+msgstr ""
+
msgid "There was an error fetching the Node's Groups"
msgstr ""
@@ -22430,6 +23156,12 @@ msgstr ""
msgid "This %{issuable} is locked. Only <strong>project members</strong> can comment."
msgstr ""
+msgid "This %{noteableTypeText} is %{confidentialLinkStart}confidential%{linkEnd} and %{lockedLinkStart}locked%{linkEnd}."
+msgstr ""
+
+msgid "This %{noteableTypeText} is locked."
+msgstr ""
+
msgid "This %{viewer} could not be displayed because %{reason}. You can %{options} instead."
msgstr ""
@@ -22448,6 +23180,9 @@ msgstr ""
msgid "This action can lead to data loss. To prevent accidental actions we ask you to confirm your intention."
msgstr ""
+msgid "This also resolves all related threads"
+msgstr ""
+
msgid "This also resolves the discussion"
msgstr ""
@@ -22538,6 +23273,9 @@ msgstr ""
msgid "This epic does not exist or you don't have sufficient permission."
msgstr ""
+msgid "This feature is currently in beta. We invite you to %{linkStart}give feedback%{linkEnd}."
+msgstr ""
+
msgid "This feature requires local storage to be enabled"
msgstr ""
@@ -22571,7 +23309,7 @@ msgstr ""
msgid "This is a Work in Progress"
msgstr ""
-msgid "This is a confidential issue."
+msgid "This is a confidential %{noteableTypeText}."
msgstr ""
msgid "This is a delayed job to run in %{remainingTime}"
@@ -22598,9 +23336,6 @@ msgstr ""
msgid "This is your current session"
msgstr ""
-msgid "This issue is %{confidentialLinkStart}confidential%{linkEnd} and %{lockedLinkStart}locked%{linkEnd}."
-msgstr ""
-
msgid "This issue is confidential"
msgstr ""
@@ -22610,9 +23345,6 @@ msgstr ""
msgid "This issue is in a child epic of the filtered epic"
msgstr ""
-msgid "This issue is locked."
-msgstr ""
-
msgid "This job depends on other jobs with expired/erased artifacts: %{invalid_dependencies}"
msgstr ""
@@ -22712,6 +23444,9 @@ msgstr ""
msgid "This license has already expired."
msgstr ""
+msgid "This link points to external content"
+msgstr ""
+
msgid "This may expose confidential information as the selected fork is in another namespace that can have other members."
msgstr ""
@@ -22739,6 +23474,9 @@ msgstr ""
msgid "This page will be removed in a future release."
msgstr ""
+msgid "This pipeline does not use the %{codeStart}needs%{codeEnd} keyword and can't be represented as a directed acyclic graph."
+msgstr ""
+
msgid "This pipeline makes use of a predefined CI/CD configuration enabled by %{strongStart}Auto DevOps.%{strongEnd}"
msgstr ""
@@ -22967,6 +23705,9 @@ msgstr ""
msgid "Time from last commit to merge"
msgstr ""
+msgid "Time in seconds"
+msgstr ""
+
msgid "Time in seconds GitLab will wait for a response from the external service. When the service does not respond in time, access will be denied."
msgstr ""
@@ -23388,9 +24129,15 @@ msgstr ""
msgid "Total artifacts size: %{total_size}"
msgstr ""
+msgid "Total cores (CPUs)"
+msgstr ""
+
msgid "Total issues"
msgstr ""
+msgid "Total memory (GB)"
+msgstr ""
+
msgid "Total test time for all commits/merges"
msgstr ""
@@ -23592,6 +24339,9 @@ msgstr ""
msgid "U2F only works with HTTPS-enabled websites. Contact your administrator for more details."
msgstr ""
+msgid "UI Development Kit"
+msgstr ""
+
msgid "URL"
msgstr ""
@@ -23664,9 +24414,6 @@ msgstr ""
msgid "Unable to load the merge request widget. Try reloading the page."
msgstr ""
-msgid "Unable to process group import file"
-msgstr ""
-
msgid "Unable to resolve"
msgstr ""
@@ -23682,6 +24429,9 @@ msgstr ""
msgid "Unable to sign you in to the group with SAML due to \"%{reason}\""
msgstr ""
+msgid "Unable to suggest a path. Please refresh and try again."
+msgstr ""
+
msgid "Unable to update label prioritization at this time"
msgstr ""
@@ -23697,6 +24447,9 @@ msgstr ""
msgid "Unarchiving the project will restore people's ability to make changes to it. The repository can be committed to, and issues, comments, and other entities can be created. %{strong_start}Once active, this project shows up in the search and on the dashboard.%{strong_end}"
msgstr ""
+msgid "Unassign from commenting user"
+msgstr ""
+
msgid "Unblock"
msgstr ""
@@ -23772,6 +24525,9 @@ msgstr ""
msgid "Unreachable"
msgstr ""
+msgid "Unrecognized cluster type"
+msgstr ""
+
msgid "Unresolve"
msgstr ""
@@ -23832,6 +24588,9 @@ msgstr ""
msgid "Unsubscribes from this %{quick_action_target}."
msgstr ""
+msgid "Unsupported todo type passed. Supported todo types are: %{todo_types}"
+msgstr ""
+
msgid "Until"
msgstr ""
@@ -23859,6 +24618,9 @@ msgstr ""
msgid "Update approval rule"
msgstr ""
+msgid "Update approvers"
+msgstr ""
+
msgid "Update failed"
msgstr ""
@@ -23868,6 +24630,9 @@ msgstr ""
msgid "Update it"
msgstr ""
+msgid "Update iteration"
+msgstr ""
+
msgid "Update now"
msgstr ""
@@ -23913,7 +24678,7 @@ msgstr ""
msgid "Updated at"
msgstr ""
-msgid "Updated to"
+msgid "Updated to %{linkStart}chart v%{linkEnd}"
msgstr ""
msgid "Updating"
@@ -23943,6 +24708,9 @@ msgstr "Enviar <code>GoogleCodeProjectHosting.json</code> aqui:"
msgid "Upload CSV file"
msgstr "Enviar ficheiro CSV"
+msgid "Upload License"
+msgstr ""
+
msgid "Upload New File"
msgstr "Enviar Novo Ficheiro"
@@ -24024,16 +24792,19 @@ msgstr ""
msgid "UsageQuota|Pipelines"
msgstr ""
+msgid "UsageQuota|Purchase more storage"
+msgstr ""
+
msgid "UsageQuota|Repositories"
msgstr ""
msgid "UsageQuota|Repository"
msgstr ""
-msgid "UsageQuota|Storage"
+msgid "UsageQuota|Snippets"
msgstr ""
-msgid "UsageQuota|Storage usage:"
+msgid "UsageQuota|Storage"
msgstr ""
msgid "UsageQuota|This namespace has no projects which use shared runners"
@@ -24066,6 +24837,12 @@ msgstr ""
msgid "UsageQuota|Wikis"
msgstr ""
+msgid "UsageQuota|You used: %{usage} %{limit}"
+msgstr ""
+
+msgid "UsageQuota|out of %{formattedLimit} of your namespace storage"
+msgstr ""
+
msgid "Use %{code_start}::%{code_end} to create a %{link_start}scoped label set%{link_end} (eg. %{code_start}priority::1%{code_end})"
msgstr ""
@@ -24132,6 +24909,9 @@ msgstr ""
msgid "User IDs"
msgstr ""
+msgid "User Lists can only be created and modified with %{linkStart}the API%{linkEnd}"
+msgstr ""
+
msgid "User OAuth applications"
msgstr ""
@@ -24156,6 +24936,9 @@ msgstr ""
msgid "User key was successfully removed."
msgstr ""
+msgid "User list %{name} will be removed. Are you sure?"
+msgstr ""
+
msgid "User map"
msgstr ""
@@ -24177,148 +24960,10 @@ msgstr ""
msgid "User was successfully updated."
msgstr ""
-msgid "UserOnboardingTour|%{activeTour}/%{totalTours}"
-msgstr ""
-
-msgid "UserOnboardingTour|%{completed}/%{total} steps completed"
-msgstr ""
-
-msgid "UserOnboardingTour|%{emphasisStart}Well done!%{emphasisEnd}%{lineBreak}%{lineBreak}That's it for our guided tour, congratulations for making it all the way to the end!%{lineBreak}%{lineBreak}We hope this gave you a good overview of GitLab and how it can help you. We'll now show you how to create your own project and invite your colleagues."
-msgstr ""
-
-msgid "UserOnboardingTour|Adding other members to a project is done through Project Settings. Click on %{emphasisStart}Settings%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Alright, that's it for Commits. Let's take a look at the %{emphasisStart}Branches%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Awesome! Now click on %{emphasisStart}Members%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Click on one of the %{emphasisStart}Compare%{emphasisEnd} buttons to compare a branch to master."
-msgstr ""
-
-msgid "UserOnboardingTour|Click on one of the %{emphasisStart}pipeline IDs%{emphasisEnd} to see the details of a pipeline."
-msgstr ""
-
-msgid "UserOnboardingTour|Click to open the latest commit to see its details."
-msgstr ""
-
-msgid "UserOnboardingTour|Close 'Learn GitLab'"
-msgstr ""
-
-msgid "UserOnboardingTour|Commits are shown in chronological order and can be filtered by the commit message or by the branch."
-msgstr ""
-
-msgid "UserOnboardingTour|Create a project"
-msgstr ""
-
-msgid "UserOnboardingTour|Exit 'Learn GitLab'"
-msgstr ""
-
-msgid "UserOnboardingTour|Got it"
-msgstr ""
-
-msgid "UserOnboardingTour|Great job! %{clapHands} We hope the tour was helpful and that you learned how to use GitLab.%{lineBreak}%{lineBreak}We'd love to get your feedback on this tour.%{lineBreak}%{lineBreak}%{emphasisStart}How helpful would you say this guided tour was?%{emphasisEnd}%{lineBreak}%{lineBreak}"
-msgstr ""
-
-msgid "UserOnboardingTour|Guided GitLab Tour"
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can compare the changes of this branch to another one. Changes are divided by files so that it's easier to see what was changed where."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can create a project from scratch, start with a template or import a repository from other platforms. Whatever you choose, we'll guide you through the process.%{lineBreak}%{lineBreak}Fill in your new project information and click on %{emphasisStart}Create Project%{emphasisEnd} to progress to the next step."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can see the breakdown of the pipelines: its stages and jobs in each of the stages and their status.%{lineBreak}%{lineBreak}Our CI/CD pipelines are quite complex, most of our users have fewer and simpler pipelines."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can see the current members of the project (just you at the moment) and invite new members.%{lineBreak}%{lineBreak}You can invite multiple members at once (existing GitLab users or invite by email) and you can also set their roles and permissions.%{lineBreak}%{lineBreak}Add a few members and click on %{emphasisStart}Add to project%{emphasisEnd} to complete this step."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can see what changes were made with this commit, on what branch and if there's a related merge request. The status of the pipeline will also show up if CI/CD is set up.%{lineBreak}%{lineBreak}You can also comment on the lines of code that were changed and start a discussion with your colleagues!"
-msgstr ""
-
-msgid "UserOnboardingTour|Here's an overview of branches in the %{emphasisStart}%{projectName}%{emphasisEnd} project. They're split into Active and Stale.%{lineBreak}%{lineBreak}From here, you can create a new merge request from a branch, or compare the branch to any other branch in the project. By default, it will compare it to the master branch."
-msgstr ""
-
-msgid "UserOnboardingTour|Invite colleagues"
-msgstr ""
-
-msgid "UserOnboardingTour|Issues are great for communicating and keeping track of progress in GitLab. These are all issues that are open in the %{emphasisStart}%{projectName}%{emphasisEnd}.%{lineBreak}%{lineBreak}You can help us improve GitLab by contributing work to issues that are labeled <span class=\"badge color-label accept-mr-label\">Accepting merge requests</span>.%{lineBreak}%{lineBreak}This list can be filtered by labels, milestones, assignees, authors... We'll show you how it looks when the list is filtered by a label."
-msgstr ""
-
-msgid "UserOnboardingTour|Learn GitLab"
-msgstr ""
-
-msgid "UserOnboardingTour|Let's take a closer look at a merge request. Click on the title of one."
-msgstr ""
-
-msgid "UserOnboardingTour|Let's take a closer look at all the commits. Click on %{emphasisStart}Commits%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Let's take a closer look at the repository of this project. Click on %{emphasisStart}Repository%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|No thanks"
-msgstr ""
-
-msgid "UserOnboardingTour|Ok, let's go"
-msgstr ""
-
-msgid "UserOnboardingTour|Ok, show me"
-msgstr ""
-
-msgid "UserOnboardingTour|Open one of the issues by clicking on its title."
-msgstr ""
-
-msgid "UserOnboardingTour|Restart this step"
-msgstr ""
-
-msgid "UserOnboardingTour|Skip this step"
-msgstr ""
-
-msgid "UserOnboardingTour|Sweet! Your project was created and is ready to be used.%{lineBreak}%{lineBreak}You can start adding files to the repository or clone it. One last thing we want to show you is how to invite your colleagues to your new project."
-msgstr ""
-
-msgid "UserOnboardingTour|Take a look. Here's a nifty menu for quickly creating issues, merge requests, snippets, projects and groups. Click on it and select \"New project\" from the \"GitLab\" section to get started."
-msgstr ""
-
-msgid "UserOnboardingTour|Thanks for taking the guided tour. Remember, if you want to go through it again, you can start %{emphasisStart}Learn GitLab%{emphasisEnd} in the help menu on the top right."
-msgstr ""
-
-msgid "UserOnboardingTour|Thanks for the feedback! %{thumbsUp}"
-msgstr ""
-
-msgid "UserOnboardingTour|That's it for issues. Let'st take a look at %{emphasisStart}Merge Requests%{emphasisEnd}."
+msgid "UserList|Delete %{name}?"
msgstr ""
-msgid "UserOnboardingTour|That's it for merge requests. Now for the final part of this guided tour - the %{emphasisStart}CI/CD%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|That's it for the Repository. Let's take a look at the %{emphasisStart}Issues%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|The structure of this page is very similar to issues. Status, description, discussion and the sidebar are all here.%{lineBreak}%{lineBreak}But take a look below the description and you'll notice that there's more information about the merge request, the CI/CD pipeline and the options for approving it.%{lineBreak}%{lineBreak}Alongside the discussion you can also see more information about commits in this merge request, the status of pipelines and review all changes that were made."
-msgstr ""
-
-msgid "UserOnboardingTour|There's a lot of information here but don't worry, we'll go through it.%{lineBreak}%{lineBreak}On the top you can see the status of the issue and when it was opened and by whom. Directly below it is the issue description and below that are other %{emphasisStart}related issues%{emphasisEnd} and %{emphasisStart}merge requests%{emphasisEnd} (if any). Then below that is the %{emphasisStart}discussion%{emphasisEnd}, that's where most of the communication happens.%{lineBreak}%{lineBreak}On the right, there's a sidebar where you can view/change the %{emphasisStart}assignee, milestone, due date, labels, weight%{emphasisEnd}, etc."
-msgstr ""
-
-msgid "UserOnboardingTour|These are all the CI/CD pipelines we have for our %{emphasisStart}%{projectName}%{emphasisEnd} project.%{lineBreak}%{lineBreak}Here you can see the status of each pipeline, for what commit it's running for, its stages and the status for them."
-msgstr ""
-
-msgid "UserOnboardingTour|These are all the issues that are available for community contributions. Let's take a closer look at one of them."
-msgstr ""
-
-msgid "UserOnboardingTour|This is an overview of all merge requests in this project. Similarly to the issues overview it can be filtered down by things like labels, milestones, authors, assignees, etc."
-msgstr ""
-
-msgid "UserOnboardingTour|This is the repository for the %{emphasisStart}%{projectName}%{emphasisEnd} project. All our code is stored here. Feel free to explore and take a closer look at folders and files.%{lineBreak}%{lineBreak}Above the file structure you can see the latest commit, who the author is and the status of the CI/CD pipeline.%{lineBreak}%{lineBreak}If you scroll down below the file structure, you'll find the Readme of this project. This is defined in the README.md file at the root of the repository."
-msgstr ""
-
-msgid "UserOnboardingTour|Welcome to the project overview of the %{emphasisStart}%{projectName}%{emphasisEnd} project. This is the project that we use to work on GitLab. At first, a project seems like a simple repository, but at GitLab, a project is so much more.%{lineBreak}%{lineBreak}You can create projects for hosting your codebase, use it as an issue tracker, collaborate on code, and continuously build, test, and deploy your app with built-in GitLab CI/CD."
+msgid "UserList|created %{timeago}"
msgstr ""
msgid "UserProfile|Activity"
@@ -24471,6 +25116,9 @@ msgstr ""
msgid "UsersSelect|Unassigned"
msgstr ""
+msgid "Using %{codeStart}needs%{codeEnd} allows jobs to run before their stage is reached, as soon as their individual dependencies are met, which speeds up your pipelines."
+msgstr ""
+
msgid "Using %{code_start}::%{code_end} denotes a %{link_start}scoped label set%{link_end}"
msgstr ""
@@ -24555,9 +25203,6 @@ msgstr ""
msgid "Versions"
msgstr ""
-msgid "Very helpful"
-msgstr ""
-
msgid "View Documentation"
msgstr ""
@@ -24706,6 +25351,9 @@ msgstr ""
msgid "VisibilityLevel|Unknown"
msgstr ""
+msgid "Visit settings page"
+msgstr ""
+
msgid "VisualReviewApp|%{stepStart}Step 1%{stepEnd}. Copy the following script:"
msgstr ""
@@ -24799,6 +25447,9 @@ msgstr ""
msgid "VulnerabilityManagement|Something went wrong while trying to delete the comment. Please try again later."
msgstr ""
+msgid "VulnerabilityManagement|Something went wrong while trying to refresh the vulnerability. Please try again later."
+msgstr ""
+
msgid "VulnerabilityManagement|Something went wrong while trying to retrieve the vulnerability history. Please try again later."
msgstr ""
@@ -24841,6 +25492,9 @@ msgstr ""
msgid "Vulnerability|Class"
msgstr ""
+msgid "Vulnerability|Crash Address"
+msgstr ""
+
msgid "Vulnerability|Description"
msgstr ""
@@ -24916,9 +25570,6 @@ msgstr ""
msgid "We couldn't reach the Prometheus server. Either the server no longer exists or the configuration details need updating."
msgstr ""
-msgid "We created a short guided tour that will help you learn the basics of GitLab and how it will help you be better at your job. It should only take a couple of minutes. You will be guided by two types of helpers, best recognized by their color."
-msgstr ""
-
msgid "We detected potential spam in the %{humanized_resource_name}. Please solve the reCAPTCHA to proceed."
msgstr ""
@@ -24952,6 +25603,9 @@ msgstr ""
msgid "We've found no vulnerabilities"
msgstr ""
+msgid "Web Application Firewall"
+msgstr ""
+
msgid "Web IDE"
msgstr ""
@@ -25012,9 +25666,6 @@ msgstr ""
msgid "Welcome to GitLab.com<br>@%{name}!"
msgstr ""
-msgid "Welcome to the Guided GitLab Tour"
-msgstr ""
-
msgid "Welcome to the guided GitLab tour"
msgstr ""
@@ -25062,9 +25713,6 @@ msgstr ""
msgid "While it's rare to have no vulnerabilities, it can happen. In any event, we ask that you please double check your settings to make sure you've set up your dashboard correctly."
msgstr ""
-msgid "White helpers give contextual information."
-msgstr ""
-
msgid "Who can be an approver?"
msgstr ""
@@ -25110,12 +25758,18 @@ msgstr ""
msgid "WikiEmptyIssueMessage|Suggest wiki improvement"
msgstr ""
+msgid "WikiEmptyIssueMessage|You must be a group member in order to add wiki pages. If you have suggestions for how to improve the wiki for this group, consider opening an issue in the %{issues_link}."
+msgstr ""
+
msgid "WikiEmptyIssueMessage|You must be a project member in order to add wiki pages. If you have suggestions for how to improve the wiki for this project, consider opening an issue in the %{issues_link}."
msgstr ""
msgid "WikiEmptyIssueMessage|issue tracker"
msgstr ""
+msgid "WikiEmpty|A wiki is where you can store all the details about your group. This can include why you've created it, its principles, how to use it, and so on."
+msgstr ""
+
msgid "WikiEmpty|A wiki is where you can store all the details about your project. This can include why you've created it, its principles, how to use it, and so on."
msgstr ""
@@ -25125,12 +25779,21 @@ msgstr ""
msgid "WikiEmpty|Suggest wiki improvement"
msgstr ""
+msgid "WikiEmpty|The wiki lets you write documentation for your group"
+msgstr ""
+
msgid "WikiEmpty|The wiki lets you write documentation for your project"
msgstr ""
+msgid "WikiEmpty|This group has no wiki pages"
+msgstr ""
+
msgid "WikiEmpty|This project has no wiki pages"
msgstr ""
+msgid "WikiEmpty|You must be a group member in order to add wiki pages."
+msgstr ""
+
msgid "WikiEmpty|You must be a project member in order to add wiki pages."
msgstr ""
@@ -25407,13 +26070,13 @@ msgstr ""
msgid "You can now export your security dashboard to a CSV report."
msgstr ""
-msgid "You can now submit a merge request to get this change into the original branch."
+msgid "You can now manage alert endpoint configuration in the Alerts section on the Operations settings page. Fields on this page have been deprecated."
msgstr ""
-msgid "You can now submit a merge request to get this change into the original project."
+msgid "You can now submit a merge request to get this change into the original branch."
msgstr ""
-msgid "You can only add files when you are on a branch"
+msgid "You can now submit a merge request to get this change into the original project."
msgstr ""
msgid "You can only edit files when you are on a branch"
@@ -25635,7 +26298,7 @@ msgstr ""
msgid "You need to upload a Google Takeout archive."
msgstr ""
-msgid "You reached %{usage_in_percent} of %{namespace_name}'s capacity (%{used_storage} of %{storage_limit})"
+msgid "You reached %{usage_in_percent} of %{namespace_name}'s storage capacity (%{used_storage} of %{storage_limit})"
msgstr ""
msgid "You tried to fork %{link_to_the_project} but it failed for the following reason:"
@@ -25938,13 +26601,13 @@ msgstr ""
msgid "Your subscription expired!"
msgstr ""
-msgid "Your subscription has been downgraded"
+msgid "Your subscription has been downgraded."
msgstr ""
-msgid "Your subscription will automatically renew in %{remaining_days}"
+msgid "Your subscription will automatically renew in %{remaining_days}."
msgstr ""
-msgid "Your subscription will expire in %{remaining_days}"
+msgid "Your subscription will expire in %{remaining_days}."
msgstr ""
msgid "Zoom meeting added"
@@ -26024,9 +26687,6 @@ msgstr ""
msgid "branch name"
msgstr "nome do ramo"
-msgid "build pipeline reference mismatch"
-msgstr ""
-
msgid "by"
msgstr ""
@@ -26045,6 +26705,9 @@ msgstr ""
msgid "cannot block others"
msgstr ""
+msgid "cannot contain HTML/XML tags, including any word between angle brackets (<,>)."
+msgstr ""
+
msgid "cannot include leading slash or directory traversal."
msgstr ""
@@ -26075,55 +26738,6 @@ msgstr ""
msgid "ciReport|%{remainingPackagesCount} more"
msgstr ""
-msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability"
-msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability for the source branch only"
-msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities for the source branch only"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerability"
-msgid_plural "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerabilities"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new vulnerability"
-msgid_plural "ciReport|%{reportType} %{status} detected %{newCount} new vulnerabilities"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities for the source branch only"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{fixedCount} fixed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} vulnerability for the source branch only"
-msgid_plural "ciReport|%{reportType} %{status} detected %{newCount} vulnerabilities for the source branch only"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "ciReport|%{reportType} %{status} detected no new vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected no vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected no vulnerabilities for the source branch only"
-msgstr ""
-
msgid "ciReport|%{reportType} is loading"
msgstr ""
@@ -26142,7 +26756,7 @@ msgstr ""
msgid "ciReport|All projects"
msgstr ""
-msgid "ciReport|All report types"
+msgid "ciReport|All scanner types"
msgstr ""
msgid "ciReport|All severities"
@@ -26166,6 +26780,9 @@ msgstr ""
msgid "ciReport|Container scanning detects known vulnerabilities in your docker images."
msgstr ""
+msgid "ciReport|Coverage Fuzzing"
+msgstr ""
+
msgid "ciReport|Create a merge request to implement this solution, or download and apply the patch manually."
msgstr ""
@@ -26324,6 +26941,9 @@ msgstr ""
msgid "customize"
msgstr ""
+msgid "data"
+msgstr ""
+
msgid "date must not be after 9999-12-31"
msgstr ""
@@ -26394,6 +27014,9 @@ msgstr ""
msgid "entries cannot contain HTML tags"
msgstr ""
+msgid "epic"
+msgstr ""
+
msgid "error"
msgstr ""
@@ -26415,9 +27038,6 @@ msgstr ""
msgid "expires on %{timebox_due_date}"
msgstr ""
-msgid "external_url"
-msgstr ""
-
msgid "failed"
msgstr ""
@@ -26453,9 +27073,6 @@ msgstr ""
msgid "from"
msgstr ""
-msgid "geo_node_name"
-msgstr ""
-
msgid "group"
msgstr ""
@@ -26477,9 +27094,6 @@ msgstr ""
msgid "https://your-bitbucket-server"
msgstr ""
-msgid "image"
-msgstr ""
-
msgid "image diff"
msgstr ""
@@ -26623,6 +27237,9 @@ msgstr ""
msgid "load it anyway"
msgstr ""
+msgid "loading"
+msgstr ""
+
msgid "locked by %{path_lock_user_name} %{created_at}"
msgstr ""
@@ -26751,9 +27368,6 @@ msgstr ""
msgid "mrWidget|Deployment statistics are not available currently"
msgstr ""
-msgid "mrWidget|Detect issues before deployment with a CI pipeline that continuously tests your code. We created a quick guide that will show you how to create one. Make your code more secure and more robust in just a minute."
-msgstr ""
-
msgid "mrWidget|Did not close"
msgstr ""
@@ -26916,6 +27530,9 @@ msgstr ""
msgid "mrWidget|To approve this merge request, please enter your password. This project requires all approvals to be authenticated."
msgstr ""
+msgid "mrWidget|Use %{linkStart}CI pipelines to test your code%{linkEnd}, simply add a GitLab CI configuration file to your project. It only takes a minute to make your code more secure and robust."
+msgstr ""
+
msgid "mrWidget|When this merge request is ready, remove the WIP: prefix from the title to allow it to be merged"
msgstr ""
@@ -26952,6 +27569,9 @@ msgstr ""
msgid "must be greater than start date"
msgstr ""
+msgid "must contain only valid frameworks"
+msgstr ""
+
msgid "my-awesome-group"
msgstr ""
@@ -26964,6 +27584,9 @@ msgstr ""
msgid "needs to be between 10 minutes and 1 month"
msgstr ""
+msgid "never"
+msgstr ""
+
msgid "never expires"
msgstr ""
@@ -26991,6 +27614,9 @@ msgstr ""
msgid "nounSeries|%{firstItem} and %{lastItem}"
msgstr ""
+msgid "nounSeries|%{item}"
+msgstr ""
+
msgid "nounSeries|%{item}, %{nextItem}"
msgstr ""
@@ -27127,6 +27753,9 @@ msgstr ""
msgid "revised"
msgstr ""
+msgid "satisfied"
+msgstr ""
+
msgid "score"
msgstr ""
@@ -27157,6 +27786,9 @@ msgstr ""
msgid "severity|Unknown"
msgstr ""
+msgid "should be an array of %{object_name} objects"
+msgstr ""
+
msgid "should be greater than or equal to %{access} inherited membership from group %{group_name}"
msgstr ""
@@ -27211,10 +27843,13 @@ msgstr ""
msgid "suggestPipeline|2/2: Commit your changes"
msgstr ""
-msgid "suggestPipeline|Commit the changes and your pipeline will automatically run for the first time."
+msgid "suggestPipeline|Choose %{boldStart}Code Quality%{boldEnd} to add a pipeline that tests the quality of your code."
msgstr ""
-msgid "suggestPipeline|We recommend the %{boldStart}Code Quality%{boldEnd} template, which will add a report widget to your Merge Requests. This way you’ll learn about code quality degradations much sooner. %{footerStart} Goodbye technical debt! %{footerEnd}"
+msgid "suggestPipeline|The template is ready! You can now commit it to create your first pipeline."
+msgstr ""
+
+msgid "suggestPipeline|We’re adding a GitLab CI configuration file to add a pipeline to the project. You could create it manually, but we recommend that you start with a GitLab template that works out of the box."
msgstr ""
msgid "syntax is correct"
@@ -27274,6 +27909,9 @@ msgstr ""
msgid "uses Kubernetes clusters to deploy your code!"
msgstr ""
+msgid "v%{version} published %{timeAgo}"
+msgstr ""
+
msgid "verify ownership"
msgstr ""
diff --git a/locale/ro_RO/gitlab.po b/locale/ro_RO/gitlab.po
index 9dbd275e389..92194753af1 100644
--- a/locale/ro_RO/gitlab.po
+++ b/locale/ro_RO/gitlab.po
@@ -2,7 +2,7 @@ msgid ""
msgstr ""
"Project-Id-Version: gitlab-ee\n"
"Report-Msgid-Bugs-To: \n"
-"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Last-Translator: \n"
"Language-Team: Romanian\n"
"Language: ro_RO\n"
"MIME-Version: 1.0\n"
@@ -10,9 +10,11 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : (n==0 || (n%100>0 && n%100<20)) ? 1 : 2);\n"
"X-Crowdin-Project: gitlab-ee\n"
+"X-Crowdin-Project-ID: 288872\n"
"X-Crowdin-Language: ro\n"
"X-Crowdin-File: /master/locale/gitlab.pot\n"
-"PO-Revision-Date: 2020-06-08 15:07\n"
+"X-Crowdin-File-ID: 6\n"
+"PO-Revision-Date: 2020-07-02 01:19\n"
msgid " %{start} to %{end}"
msgstr ""
@@ -224,6 +226,12 @@ msgstr[0] ""
msgstr[1] ""
msgstr[2] ""
+msgid "%d milestone"
+msgid_plural "%d milestones"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+
msgid "%d minute"
msgid_plural "%d minutes"
msgstr[0] ""
@@ -278,12 +286,6 @@ msgstr[0] ""
msgstr[1] ""
msgstr[2] ""
-msgid "%d url scanned"
-msgid_plural "%d urls scanned"
-msgstr[0] ""
-msgstr[1] ""
-msgstr[2] ""
-
msgid "%d vulnerability"
msgid_plural "%d vulnerabilities"
msgstr[0] ""
@@ -368,9 +370,15 @@ msgstr[2] ""
msgid "%{count} related %{pluralized_subject}: %{links}"
msgstr ""
+msgid "%{dashboard_path} could not be found."
+msgstr ""
+
msgid "%{days} days until tags are automatically removed"
msgstr ""
+msgid "%{deployLinkStart}Use a template to deploy to ECS%{deployLinkEnd}, or use a docker image to %{commandsLinkStart}run AWS commands in GitLab CI/CD%{commandsLinkEnd}."
+msgstr ""
+
msgid "%{description}- Sentry event: %{errorUrl}- First seen: %{firstSeen}- Last seen: %{lastSeen} %{countLabel}: %{count}%{userCountLabel}: %{userCount}"
msgstr ""
@@ -413,10 +421,16 @@ msgstr ""
msgid "%{host} sign-in from new location"
msgstr ""
-msgid "%{icon}You are about to add %{usersTag} people to the discussion. Proceed with caution."
+msgid "%{icon}You are about to add %{usersTag} people to the discussion. They will all receive a notification."
+msgstr ""
+
+msgid "%{integrations_link_start}Integrations%{link_end} enable you to make third-party applications part of your GitLab workflow. If the available integrations don't meet your needs, consider using a %{webhooks_link_start}webhook%{link_end}."
msgstr ""
msgid "%{issuableType} will be removed! Are you sure?"
+msgstr "%{issuableType} va fi eliminat! Esti sigur?"
+
+msgid "%{issuesCount} issues in this group"
msgstr ""
msgid "%{issuesSize} issues"
@@ -428,7 +442,7 @@ msgstr ""
msgid "%{labelStart}Class:%{labelEnd} %{class}"
msgstr ""
-msgid "%{labelStart}Confidence:%{labelEnd} %{confidence}"
+msgid "%{labelStart}Evidence:%{labelEnd} %{evidence}"
msgstr ""
msgid "%{labelStart}File:%{labelEnd} %{file}"
@@ -446,11 +460,14 @@ msgstr ""
msgid "%{labelStart}Report Type:%{labelEnd} %{reportType}"
msgstr ""
+msgid "%{labelStart}Scanner:%{labelEnd} %{scanner}"
+msgstr ""
+
msgid "%{labelStart}Severity:%{labelEnd} %{severity}"
msgstr ""
msgid "%{label_for_message} unavailable"
-msgstr ""
+msgstr "%{label_for_message} indisponibil"
msgid "%{lets_encrypt_link_start}Let's Encrypt%{lets_encrypt_link_end} is a free, automated, and open certificate authority (CA), that give digital certificates in order to enable HTTPS (SSL/TLS) for websites."
msgstr ""
@@ -470,17 +487,14 @@ msgstr ""
msgid "%{link_start}Read more%{link_end} about role permissions"
msgstr ""
-msgid "%{link} can be used for binding events when something is happening within the project."
-msgstr ""
-
msgid "%{listToShow}, and %{awardsListLength} more."
msgstr ""
msgid "%{loadingIcon} Started"
-msgstr ""
+msgstr "%{loadingIcon} ÃŽnceput"
msgid "%{lock_path} is locked by GitLab User %{lock_user_id}"
-msgstr ""
+msgstr "%{lock_path} este blocat de utilizatorul GitLab %{lock_user_id}"
msgid "%{markdownDocsLinkStart}Markdown%{markdownDocsLinkEnd} and %{quickActionsDocsLinkStart}quick actions%{quickActionsDocsLinkEnd} are supported"
msgstr ""
@@ -506,6 +520,9 @@ msgstr ""
msgid "%{name} found %{resultsString}"
msgstr ""
+msgid "%{name} is already being used for another emoji"
+msgstr ""
+
msgid "%{name} is scheduled for %{action}"
msgstr ""
@@ -528,7 +545,7 @@ msgid "%{number_commits_behind} commits behind %{default_branch}, %{number_commi
msgstr ""
msgid "%{openOrClose} %{noteable}"
-msgstr ""
+msgstr "%{openOrClose} %{noteable}"
msgid "%{openedEpics} open, %{closedEpics} closed"
msgstr ""
@@ -554,6 +571,9 @@ msgstr ""
msgid "%{primary} (%{secondary})"
msgstr ""
+msgid "%{ref} cannot be added: %{error}"
+msgstr ""
+
msgid "%{releases} release"
msgid_plural "%{releases} releases"
msgstr[0] ""
@@ -563,13 +583,40 @@ msgstr[2] ""
msgid "%{remaining_approvals} left"
msgstr ""
-msgid "%{retryButtonStart}Try again%{retryButtonEnd} or %{newFileButtonStart}attach a new file%{newFileButtonEnd}"
+msgid "%{reportType} %{status} detected %{critical} critical and %{high} high severity vulnerabilities out of %{total}."
msgstr ""
-msgid "%{screenreaderOnlyStart}Keyboard shorcuts%{screenreaderOnlyEnd} Disabled"
+msgid "%{reportType} %{status} detected %{critical} critical and %{high} high severity vulnerabilities."
msgstr ""
-msgid "%{screenreaderOnlyStart}Keyboard shorcuts%{screenreaderOnlyEnd} Enabled"
+msgid "%{reportType} %{status} detected %{critical} critical severity vulnerabilities out of %{total}."
+msgstr ""
+
+msgid "%{reportType} %{status} detected %{critical} critical severity vulnerability."
+msgid_plural "%{reportType} %{status} detected %{critical} critical severity vulnerabilities."
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+
+msgid "%{reportType} %{status} detected %{high} high severity vulnerabilities out of %{total}."
+msgstr ""
+
+msgid "%{reportType} %{status} detected %{high} high severity vulnerability."
+msgid_plural "%{reportType} %{status} detected %{high} high severity vulnerabilities."
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+
+msgid "%{reportType} %{status} detected %{other} vulnerability."
+msgid_plural "%{reportType} %{status} detected %{other} vulnerabilities."
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+
+msgid "%{reportType} %{status} detected no new vulnerabilities."
+msgstr ""
+
+msgid "%{retryButtonStart}Try again%{retryButtonEnd} or %{newFileButtonStart}attach a new file%{newFileButtonEnd}"
msgstr ""
msgid "%{service_title} %{message}."
@@ -657,7 +704,7 @@ msgstr[1] ""
msgstr[2] ""
msgid "%{text} is available"
-msgstr ""
+msgstr "%{text} este disponibil"
msgid "%{timebox_name} should belong either to a project or a group."
msgstr ""
@@ -671,6 +718,12 @@ msgstr ""
msgid "%{token}..."
msgstr ""
+msgid "%{totalCpu} (%{freeSpacePercentage}%{percentSymbol} free)"
+msgstr ""
+
+msgid "%{totalMemory} (%{freeSpacePercentage}%{percentSymbol} free)"
+msgstr ""
+
msgid "%{totalWeight} total weight"
msgstr ""
@@ -701,6 +754,12 @@ msgstr ""
msgid "%{verb} %{time_spent_value} spent time."
msgstr ""
+msgid "%{webhooks_link_start}%{webhook_type}%{link_end} enable you to send notifications to web applications in response to events in a group or project."
+msgstr ""
+
+msgid "%{webhooks_link_start}%{webhook_type}%{link_end} enable you to send notifications to web applications in response to events in a group or project. We recommend using an %{integrations_link_start}integration%{link_end} in preference to a webhook."
+msgstr ""
+
msgid "'%{level}' is not a valid visibility level"
msgstr ""
@@ -728,9 +787,6 @@ msgstr ""
msgid "(No changes)"
msgstr ""
-msgid "(Show all)"
-msgstr ""
-
msgid "(check progress)"
msgstr ""
@@ -800,6 +856,9 @@ msgstr ""
msgid "0 for unlimited"
msgstr ""
+msgid "0 for unlimited, only effective with remote storage enabled."
+msgstr ""
+
msgid "1 %{type} addition"
msgid_plural "%{count} %{type} additions"
msgstr[0] ""
@@ -962,6 +1021,12 @@ msgstr ""
msgid "<code>\"johnsmith@example.com\": \"johnsmith@example.com\"</code> will add \"By <a href=\"#\">johnsmith@example.com</a>\" to all issues and comments originally created by johnsmith@example.com. By default, the email address or username is masked to ensure the user's privacy. Use this option if you want to show the full email address."
msgstr ""
+msgid "<code>Masked</code> to prevent the values from being displayed in job logs (must match certain regexp requirements)."
+msgstr ""
+
+msgid "<code>Protected</code> to expose them to protected branches or tags only."
+msgstr ""
+
msgid "<namespace / project>"
msgstr ""
@@ -989,9 +1054,6 @@ msgstr ""
msgid "A .NET Core console application template, customizable for any .NET Core project"
msgstr ""
-msgid "A DAG must have two dependent jobs to be visualized on this tab."
-msgstr ""
-
msgid "A GitBook site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
msgstr ""
@@ -1022,6 +1084,12 @@ msgstr ""
msgid "A deleted user"
msgstr ""
+msgid "A file has been changed."
+msgstr ""
+
+msgid "A file was not found."
+msgstr ""
+
msgid "A file with '%{file_name}' already exists in %{branch} branch"
msgstr ""
@@ -1088,7 +1156,7 @@ msgstr ""
msgid "A subscription will trigger a new pipeline on the default branch of this project when a pipeline successfully completes for a new tag on the %{default_branch_docs} of the subscribed project."
msgstr ""
-msgid "A terraform report was generated in your pipelines."
+msgid "A suggestion is not applicable."
msgstr ""
msgid "A user with write access to the source branch selected this option"
@@ -1259,6 +1327,9 @@ msgstr ""
msgid "Action to take when receiving an alert."
msgstr ""
+msgid "Actions"
+msgstr ""
+
msgid "Activate"
msgstr ""
@@ -1316,6 +1387,9 @@ msgstr ""
msgid "Add LICENSE"
msgstr ""
+msgid "Add New Node"
+msgstr ""
+
msgid "Add README"
msgstr ""
@@ -1391,6 +1465,9 @@ msgstr ""
msgid "Add approval rule"
msgstr ""
+msgid "Add approvers"
+msgstr ""
+
msgid "Add bold text"
msgstr ""
@@ -1448,6 +1525,9 @@ msgstr ""
msgid "Add strikethrough text"
msgstr ""
+msgid "Add suggestion to batch"
+msgstr ""
+
msgid "Add system hook"
msgstr ""
@@ -1547,6 +1627,9 @@ msgstr ""
msgid "Admin Note"
msgstr ""
+msgid "Admin Notifications"
+msgstr ""
+
msgid "Admin Overview"
msgstr ""
@@ -1658,6 +1741,9 @@ msgstr ""
msgid "AdminSettings|Integrations configured here will automatically apply to all projects on this instance."
msgstr ""
+msgid "AdminSettings|Moved to integrations"
+msgstr ""
+
msgid "AdminSettings|No required pipeline"
msgstr ""
@@ -1889,12 +1975,18 @@ msgstr[0] ""
msgstr[1] ""
msgstr[2] ""
+msgid "AlertManagement| assign yourself"
+msgstr ""
+
msgid "AlertManagement|Acknowledged"
msgstr ""
msgid "AlertManagement|Alert"
msgstr ""
+msgid "AlertManagement|Alert assignee(s): %{assignees}"
+msgstr ""
+
msgid "AlertManagement|Alert detail"
msgstr ""
@@ -1910,9 +2002,15 @@ msgstr ""
msgid "AlertManagement|All alerts"
msgstr ""
+msgid "AlertManagement|Assign To"
+msgstr ""
+
msgid "AlertManagement|Assign status"
msgstr ""
+msgid "AlertManagement|Assignee"
+msgstr ""
+
msgid "AlertManagement|Assignees"
msgstr ""
@@ -1931,9 +2029,6 @@ msgstr ""
msgid "AlertManagement|Edit"
msgstr ""
-msgid "AlertManagement|End time"
-msgstr ""
-
msgid "AlertManagement|Events"
msgstr ""
@@ -1952,10 +2047,13 @@ msgstr ""
msgid "AlertManagement|More information"
msgstr ""
+msgid "AlertManagement|No Matching Results"
+msgstr ""
+
msgid "AlertManagement|No alert data to display."
msgstr ""
-msgid "AlertManagement|No alerts available to display. If you think you're seeing this message in error, refresh the page."
+msgid "AlertManagement|No alerts available to display. See %{linkStart}enabling alert management%{linkEnd} for more information on adding alerts to the list."
msgstr ""
msgid "AlertManagement|No alerts to display."
@@ -1964,6 +2062,9 @@ msgstr ""
msgid "AlertManagement|None"
msgstr ""
+msgid "AlertManagement|None -"
+msgstr ""
+
msgid "AlertManagement|Open"
msgstr ""
@@ -2000,9 +2101,18 @@ msgstr ""
msgid "AlertManagement|There was an error displaying the alerts. Confirm your endpoint's configuration details to ensure alerts appear."
msgstr ""
+msgid "AlertManagement|There was an error while updating the assignee(s) list. Please try again."
+msgstr ""
+
+msgid "AlertManagement|There was an error while updating the assignee(s) of the alert. Please try again."
+msgstr ""
+
msgid "AlertManagement|There was an error while updating the status of the alert. Please try again."
msgstr ""
+msgid "AlertManagement|This assignee cannot be assigned to this alert."
+msgstr ""
+
msgid "AlertManagement|Tool"
msgstr ""
@@ -2150,6 +2260,9 @@ msgstr ""
msgid "Allow users to request access (if visibility is public or internal)"
msgstr ""
+msgid "Allowed Geo IP"
+msgstr ""
+
msgid "Allowed email domain restriction only permitted for top-level groups"
msgstr ""
@@ -2183,6 +2296,9 @@ msgstr ""
msgid "Amazon Web Services"
msgstr ""
+msgid "Amazon Web Services Logo"
+msgstr ""
+
msgid "Amazon authentication is not %{link_start}correctly configured%{link_end}. Ask your GitLab administrator if you want to use this service."
msgstr ""
@@ -2240,10 +2356,16 @@ msgstr ""
msgid "An error occurred when updating the issue weight"
msgstr ""
+msgid "An error occurred while acknowledging the notification. Refresh the page and try again."
+msgstr ""
+
+msgid "An error occurred while adding approvers"
+msgstr ""
+
msgid "An error occurred while adding formatted title for epic"
msgstr ""
-msgid "An error occurred while checking group path"
+msgid "An error occurred while checking group path. Please refresh and try again."
msgstr ""
msgid "An error occurred while committing your changes."
@@ -2387,7 +2509,7 @@ msgstr ""
msgid "An error occurred while loading milestones"
msgstr ""
-msgid "An error occurred while loading terraform report"
+msgid "An error occurred while loading project creation UI"
msgstr ""
msgid "An error occurred while loading the data. Please try again."
@@ -2441,6 +2563,9 @@ msgstr ""
msgid "An error occurred while rendering preview broadcast message"
msgstr ""
+msgid "An error occurred while rendering the editor"
+msgstr ""
+
msgid "An error occurred while reordering issues."
msgstr ""
@@ -2537,7 +2662,7 @@ msgstr ""
msgid "Analyze your dependencies for known vulnerabilities."
msgstr ""
-msgid "Analyze your source code and git history for secrets"
+msgid "Analyze your source code and git history for secrets."
msgstr ""
msgid "Analyze your source code for known vulnerabilities."
@@ -2651,12 +2776,18 @@ msgstr ""
msgid "Apply suggestion"
msgstr ""
+msgid "Apply suggestions"
+msgstr ""
+
msgid "Apply template"
msgstr ""
msgid "Apply this approval rule to any branch or a specific protected branch."
msgstr ""
+msgid "Applying"
+msgstr ""
+
msgid "Applying a template will replace the existing issue description. Any changes you have made will be lost."
msgstr ""
@@ -2669,12 +2800,18 @@ msgstr ""
msgid "Applying multiple commands"
msgstr ""
-msgid "Applying suggestion"
+msgid "Applying suggestion..."
+msgstr ""
+
+msgid "Applying suggestions..."
msgstr ""
msgid "Approval rules"
msgstr ""
+msgid "Approval rules reset to project defaults"
+msgstr ""
+
msgid "ApprovalRuleRemove|%d member"
msgid_plural "ApprovalRuleRemove|%d members"
msgstr[0] ""
@@ -2732,6 +2869,9 @@ msgstr ""
msgid "Approved"
msgstr ""
+msgid "Approved MRs"
+msgstr ""
+
msgid "Approved by: "
msgstr ""
@@ -2945,6 +3085,9 @@ msgstr ""
msgid "Assign"
msgstr ""
+msgid "Assign Iteration"
+msgstr ""
+
msgid "Assign custom color like #FF0000"
msgstr ""
@@ -2963,6 +3106,9 @@ msgstr ""
msgid "Assign to"
msgstr ""
+msgid "Assign to commenting user"
+msgstr ""
+
msgid "Assign yourself to these issues"
msgstr ""
@@ -2990,6 +3136,9 @@ msgstr[0] ""
msgstr[1] ""
msgstr[2] ""
+msgid "Assignee has no permissions"
+msgstr ""
+
msgid "Assignee lists not available with your current license"
msgstr ""
@@ -2999,6 +3148,9 @@ msgstr ""
msgid "Assignee(s)"
msgstr ""
+msgid "Assignees"
+msgstr ""
+
msgid "Assigns %{assignee_users_sentence}."
msgstr ""
@@ -3035,6 +3187,9 @@ msgstr ""
msgid "Audit Events is a way to keep track of important events that happened in GitLab."
msgstr ""
+msgid "Audit Log"
+msgstr ""
+
msgid "AuditEvents|(removed)"
msgstr ""
@@ -3071,6 +3226,9 @@ msgstr ""
msgid "AuditLogs|IP Address"
msgstr ""
+msgid "AuditLogs|Member Events"
+msgstr ""
+
msgid "AuditLogs|No matching %{type} found."
msgstr ""
@@ -3353,9 +3511,6 @@ msgstr ""
msgid "Badges|e.g. %{exampleUrl}"
msgstr ""
-msgid "Badge|New"
-msgstr ""
-
msgid "Balsamiq file could not be loaded."
msgstr ""
@@ -3404,6 +3559,12 @@ msgstr ""
msgid "Below you will find all the groups that are public."
msgstr ""
+msgid "Beta"
+msgstr ""
+
+msgid "Bi-weekly code coverage"
+msgstr ""
+
msgid "Billing"
msgstr ""
@@ -3464,9 +3625,15 @@ msgstr ""
msgid "Bitbucket Server Import"
msgstr ""
+msgid "Bitbucket Server import"
+msgstr ""
+
msgid "Bitbucket import"
msgstr ""
+msgid "Blame"
+msgstr ""
+
msgid "Blocked"
msgstr ""
@@ -3479,9 +3646,6 @@ msgstr ""
msgid "Blog"
msgstr ""
-msgid "Blue helpers indicate an action to be taken."
-msgstr ""
-
msgid "Board name"
msgstr ""
@@ -3680,6 +3844,9 @@ msgstr ""
msgid "Broadcast Message was successfully updated."
msgstr ""
+msgid "Broadcast Messages"
+msgstr ""
+
msgid "Browse Directory"
msgstr ""
@@ -3734,6 +3901,9 @@ msgstr ""
msgid "By %{user_name}"
msgstr ""
+msgid "By URL"
+msgstr ""
+
msgid "By default GitLab sends emails in HTML and plain text formats so mail clients can choose what format to use. Disable this option if you only want to send emails in plain text format."
msgstr ""
@@ -3839,6 +4009,9 @@ msgstr ""
msgid "Can override approvers and approvals required per merge request"
msgstr ""
+msgid "Can't apply as this line has changed or the suggestion already matches its content."
+msgstr ""
+
msgid "Can't create snippet: %{err}"
msgstr ""
@@ -4025,9 +4198,6 @@ msgstr ""
msgid "Changes are still tracked. Useful for cluster/index migrations."
msgstr ""
-msgid "Changes are unknown"
-msgstr ""
-
msgid "Changes suppressed. Click to show."
msgstr ""
@@ -4334,15 +4504,9 @@ msgstr ""
msgid "Choose what content you want to see on a group’s overview page"
msgstr ""
-msgid "Choose which groups you wish to synchronize to this secondary node"
-msgstr ""
-
msgid "Choose which repositories you want to connect and run CI/CD pipelines."
msgstr ""
-msgid "Choose which shards you wish to synchronize to this secondary node"
-msgstr ""
-
msgid "Choose your framework"
msgstr ""
@@ -4640,9 +4804,15 @@ msgstr ""
msgid "Cluster does not exist"
msgstr ""
+msgid "Cluster is required for Stages::ClusterEndpointInserter"
+msgstr ""
+
msgid "Cluster level"
msgstr ""
+msgid "Cluster type must be specificed for Stages::ClusterEndpointInserter"
+msgstr ""
+
msgid "ClusterIntegration| %{custom_domain_start}More information%{custom_domain_end}."
msgstr ""
@@ -4754,7 +4924,7 @@ msgstr ""
msgid "ClusterIntegration|Cert-Manager"
msgstr ""
-msgid "ClusterIntegration|Cert-Manager is a native Kubernetes certificate management controller that helps with issuing certificates. Installing Cert-Manager on your cluster will issue a certificate by %{letsEncrypt} and ensure that certificates are valid and up-to-date."
+msgid "ClusterIntegration|Cert-Manager is a native Kubernetes certificate management controller that helps with issuing certificates. Installing Cert-Manager on your cluster will issue a certificate by %{linkStart}Let's Encrypt%{linkEnd} and ensure that certificates are valid and up-to-date."
msgstr ""
msgid "ClusterIntegration|Certificate Authority bundle (PEM format)"
@@ -4781,9 +4951,6 @@ msgstr ""
msgid "ClusterIntegration|Clear the local cache of namespace and service accounts. This is necessary if your integration has become out of sync. The cache is repopulated during the next CI job that requires namespace and service accounts."
msgstr ""
-msgid "ClusterIntegration|Cloud Run"
-msgstr ""
-
msgid "ClusterIntegration|Cluster being created"
msgstr ""
@@ -4868,7 +5035,7 @@ msgstr ""
msgid "ClusterIntegration|Crossplane"
msgstr ""
-msgid "ClusterIntegration|Crossplane enables declarative provisioning of managed services from your cloud of choice using %{codeStart}kubectl%{codeEnd} or %{gitlabIntegrationLink}. Crossplane runs inside your Kubernetes cluster and supports secure connectivity and secrets management between app containers and the cloud services they depend on."
+msgid "ClusterIntegration|Crossplane enables declarative provisioning of managed services from your cloud of choice using %{codeStart}kubectl%{codeEnd} or %{linkStart}GitLab Integration%{linkEnd}. Crossplane runs inside your Kubernetes cluster and supports secure connectivity and secrets management between app containers and the cloud services they depend on."
msgstr ""
msgid "ClusterIntegration|Deletes all GitLab resources attached to this cluster during removal"
@@ -4952,9 +5119,6 @@ msgstr ""
msgid "ClusterIntegration|GitLab-managed cluster"
msgstr ""
-msgid "ClusterIntegration|Gitlab Integration"
-msgstr ""
-
msgid "ClusterIntegration|Global default"
msgstr ""
@@ -5000,7 +5164,7 @@ msgstr ""
msgid "ClusterIntegration|Ingress gives you a way to route requests to services based on the request host or path, centralizing a number of services into a single entrypoint."
msgstr ""
-msgid "ClusterIntegration|Installing Ingress may incur additional costs. Learn more about %{pricingLink}."
+msgid "ClusterIntegration|Installing Ingress may incur additional costs. Learn more about %{linkStart}pricing%{linkEnd}."
msgstr ""
msgid "ClusterIntegration|Instance cluster"
@@ -5015,7 +5179,7 @@ msgstr ""
msgid "ClusterIntegration|Issuer Email"
msgstr ""
-msgid "ClusterIntegration|Issuers represent a certificate authority. You must provide an email address for your Issuer. "
+msgid "ClusterIntegration|Issuers represent a certificate authority. You must provide an email address for your Issuer."
msgstr ""
msgid "ClusterIntegration|Jupyter Hostname"
@@ -5087,9 +5251,6 @@ msgstr ""
msgid "ClusterIntegration|Learn more about instance Kubernetes clusters"
msgstr ""
-msgid "ClusterIntegration|Let's Encrypt"
-msgstr ""
-
msgid "ClusterIntegration|Loading IAM Roles"
msgstr ""
@@ -5201,7 +5362,7 @@ msgstr ""
msgid "ClusterIntegration|Prometheus"
msgstr ""
-msgid "ClusterIntegration|Prometheus is an open-source monitoring system with %{gitlabIntegrationLink} to monitor deployed applications."
+msgid "ClusterIntegration|Prometheus is an open-source monitoring system with %{linkStart}GitLab Integration%{linkEnd} to monitor deployed applications."
msgstr ""
msgid "ClusterIntegration|Provider details"
@@ -5357,10 +5518,10 @@ msgstr ""
msgid "ClusterIntegration|Select zone to choose machine type"
msgstr ""
-msgid "ClusterIntegration|Send Cilium Logs"
+msgid "ClusterIntegration|Send Container Network Policies Logs"
msgstr ""
-msgid "ClusterIntegration|Send ModSecurity Logs"
+msgid "ClusterIntegration|Send Web Application Firewall Logs"
msgstr ""
msgid "ClusterIntegration|Service Token"
@@ -5528,15 +5689,12 @@ msgstr ""
msgid "ClusterIntegration|documentation"
msgstr ""
-msgid "ClusterIntegration|installed via %{installed_via}"
+msgid "ClusterIntegration|installed via %{linkStart}Cloud Run%{linkEnd}"
msgstr ""
msgid "ClusterIntegration|meets the requirements"
msgstr ""
-msgid "ClusterIntegration|pricing"
-msgstr ""
-
msgid "ClusterIntegration|sign up"
msgstr ""
@@ -5573,6 +5731,15 @@ msgstr ""
msgid "Code"
msgstr ""
+msgid "Code Coverage: %{coveragePercentage}%{percentSymbol}"
+msgstr ""
+
+msgid "Code Coverage| Empty code coverage data"
+msgstr ""
+
+msgid "Code Coverage|Couldn't fetch the code coverage data"
+msgstr ""
+
msgid "Code Owners"
msgstr ""
@@ -5627,7 +5794,7 @@ msgstr ""
msgid "Collapse approvers"
msgstr ""
-msgid "Collapse child epics"
+msgid "Collapse milestones"
msgstr ""
msgid "Collapse replies"
@@ -5645,6 +5812,9 @@ msgstr ""
msgid "Coming soon"
msgstr ""
+msgid "Comma-separated, e.g. '1.1.1.1, 2.2.2.0/24'"
+msgstr ""
+
msgid "Command"
msgstr ""
@@ -5816,6 +5986,12 @@ msgstr ""
msgid "Compliance framework (optional)"
msgstr ""
+msgid "Compliance frameworks"
+msgstr ""
+
+msgid "ComplianceDashboard|created by:"
+msgstr ""
+
msgid "ComplianceFramework|GDPR"
msgstr ""
@@ -5954,6 +6130,9 @@ msgstr ""
msgid "Connection timed out"
msgstr ""
+msgid "Connection timeout"
+msgstr ""
+
msgid "Contact sales to upgrade"
msgstr ""
@@ -6011,9 +6190,6 @@ msgstr ""
msgid "ContainerRegistry|CLI Commands"
msgstr ""
-msgid "ContainerRegistry|Compressed Size"
-msgstr ""
-
msgid "ContainerRegistry|Container Registry"
msgstr ""
@@ -6029,6 +6205,9 @@ msgstr ""
msgid "ContainerRegistry|Copy push command"
msgstr ""
+msgid "ContainerRegistry|Delete selected"
+msgstr ""
+
msgid "ContainerRegistry|Docker connection error"
msgstr ""
@@ -6062,16 +6241,16 @@ msgstr ""
msgid "ContainerRegistry|If you are not already logged in, you need to authenticate to the Container Registry by using your GitLab username and password. If you have %{twofaDocLinkStart}Two-Factor Authentication%{twofaDocLinkEnd} enabled, use a %{personalAccessTokensDocLinkStart}Personal Access Token%{personalAccessTokensDocLinkEnd} instead of a password."
msgstr ""
-msgid "ContainerRegistry|Image ID"
+msgid "ContainerRegistry|Image ID: %{imageId}"
msgstr ""
msgid "ContainerRegistry|Image Repositories"
msgstr ""
-msgid "ContainerRegistry|Keep and protect the images that matter most."
+msgid "ContainerRegistry|Image tags"
msgstr ""
-msgid "ContainerRegistry|Last Updated"
+msgid "ContainerRegistry|Keep and protect the images that matter most."
msgstr ""
msgid "ContainerRegistry|Login"
@@ -6086,6 +6265,9 @@ msgstr ""
msgid "ContainerRegistry|Please contact your administrator."
msgstr ""
+msgid "ContainerRegistry|Published %{timeInfo}"
+msgstr ""
+
msgid "ContainerRegistry|Push an image"
msgstr ""
@@ -6101,9 +6283,6 @@ msgstr ""
msgid "ContainerRegistry|Remove repository"
msgstr ""
-msgid "ContainerRegistry|Remove selected tags"
-msgstr ""
-
msgid "ContainerRegistry|Remove tag"
msgid_plural "ContainerRegistry|Remove tags"
msgstr[0] ""
@@ -6134,9 +6313,6 @@ msgstr ""
msgid "ContainerRegistry|Sorry, your filter produced no results."
msgstr ""
-msgid "ContainerRegistry|Tag"
-msgstr ""
-
msgid "ContainerRegistry|Tag expiration policy"
msgstr ""
@@ -6281,21 +6457,6 @@ msgstr ""
msgid "Control the display of third party offers."
msgstr ""
-msgid "Control the maximum concurrency of LFS/attachment backfill for this secondary node"
-msgstr ""
-
-msgid "Control the maximum concurrency of container repository operations for this Geo node"
-msgstr ""
-
-msgid "Control the maximum concurrency of repository backfill for this secondary node"
-msgstr ""
-
-msgid "Control the maximum concurrency of verification operations for this Geo node"
-msgstr ""
-
-msgid "Control the minimum interval in days that a repository should be reverified for this primary node"
-msgstr ""
-
msgid "Cookie domain"
msgstr ""
@@ -6437,6 +6598,9 @@ msgstr ""
msgid "Could not find design."
msgstr ""
+msgid "Could not find iteration"
+msgstr ""
+
msgid "Could not remove the trigger."
msgstr ""
@@ -6473,6 +6637,12 @@ msgstr ""
msgid "Coverage"
msgstr ""
+msgid "Coverage Fuzzing"
+msgstr ""
+
+msgid "Crash State"
+msgstr ""
+
msgid "Create"
msgstr ""
@@ -6521,6 +6691,9 @@ msgstr ""
msgid "Create a personal access token on your account to pull or push via %{protocol}."
msgstr ""
+msgid "Create a project pre-populated with the necessary files to get you started quickly."
+msgstr ""
+
msgid "Create an account using:"
msgstr ""
@@ -6689,6 +6862,9 @@ msgstr ""
msgid "Creates branch '%{branch_name}' and a merge request to resolve this issue."
msgstr ""
+msgid "Creating"
+msgstr ""
+
msgid "Creating epic"
msgstr ""
@@ -6740,7 +6916,10 @@ msgstr ""
msgid "Current vulnerabilities count"
msgstr ""
-msgid "CurrentUser|Buy CI minutes"
+msgid "CurrentUser|Buy Pipeline minutes"
+msgstr ""
+
+msgid "CurrentUser|One of your groups is running out"
msgstr ""
msgid "CurrentUser|Profile"
@@ -6755,6 +6934,9 @@ msgstr ""
msgid "CurrentUser|Upgrade"
msgstr ""
+msgid "Custom Attributes"
+msgstr ""
+
msgid "Custom CI configuration path"
msgstr ""
@@ -7019,12 +7201,18 @@ msgstr ""
msgid "DAG"
msgstr ""
+msgid "DAG visualization requires at least 3 dependent jobs."
+msgstr ""
+
msgid "DNS"
msgstr ""
msgid "Dashboard"
msgstr ""
+msgid "Dashboard uid not found"
+msgstr ""
+
msgid "DashboardProjects|All"
msgstr ""
@@ -7049,6 +7237,9 @@ msgstr ""
msgid "Data is still calculating..."
msgstr ""
+msgid "Datasource name not found"
+msgstr ""
+
msgid "Date"
msgstr ""
@@ -7229,6 +7420,9 @@ msgstr ""
msgid "Delete this attachment"
msgstr ""
+msgid "Delete user list"
+msgstr ""
+
msgid "Delete variable"
msgstr ""
@@ -7550,6 +7744,9 @@ msgstr ""
msgid "Deploying to"
msgstr ""
+msgid "Deploying to AWS is easy with GitLab"
+msgstr ""
+
msgid "Deployment Frequency"
msgstr ""
@@ -7913,6 +8110,9 @@ msgstr ""
msgid "Dismissed on pipeline %{pipelineLink} at %{projectLink}"
msgstr ""
+msgid "Display alerts from all your monitoring tools directly within GitLab."
+msgstr ""
+
msgid "Display name"
msgstr ""
@@ -7970,9 +8170,6 @@ msgstr ""
msgid "Don't show again"
msgstr ""
-msgid "Don't worry, you can access this tour by clicking on the help icon in the top right corner and choose <strong>Learn GitLab</strong>."
-msgstr ""
-
msgid "Done"
msgstr ""
@@ -8099,6 +8296,9 @@ msgstr ""
msgid "Edit Release"
msgstr ""
+msgid "Edit Slack integration"
+msgstr ""
+
msgid "Edit Snippet"
msgstr ""
@@ -8138,6 +8338,9 @@ msgstr ""
msgid "Edit issues"
msgstr ""
+msgid "Edit iteration"
+msgstr ""
+
msgid "Edit public deploy key"
msgstr ""
@@ -8186,6 +8389,9 @@ msgstr ""
msgid "Email"
msgstr ""
+msgid "Email Notification"
+msgstr ""
+
msgid "Email address"
msgstr ""
@@ -8198,6 +8404,9 @@ msgstr ""
msgid "Email not verified. Please verify your email in Salesforce."
msgstr ""
+msgid "Email notification for unknown sign-ins"
+msgstr ""
+
msgid "Email patch"
msgstr ""
@@ -8429,6 +8638,9 @@ msgstr ""
msgid "Enforce DNS rebinding attack protection"
msgstr ""
+msgid "Enforce personal access token expiration"
+msgstr ""
+
msgid "Ensure connectivity is available from the GitLab server to the Prometheus server"
msgstr ""
@@ -8498,6 +8710,9 @@ msgstr ""
msgid "Enter the name of your application, and we'll return a unique %{type}."
msgstr ""
+msgid "Enter weights for storages for new repositories."
+msgstr ""
+
msgid "Enter your password to approve"
msgstr ""
@@ -8507,15 +8722,27 @@ msgstr ""
msgid "Environment does not have deployments"
msgstr ""
+msgid "Environment is required for Stages::MetricEndpointInserter"
+msgstr ""
+
+msgid "Environment is required for Stages::VariableEndpointInserter"
+msgstr ""
+
msgid "Environment scope"
msgstr ""
+msgid "Environment variables are applied to all project environments in this instance via the Runner. You can use environment variables for passwords, secret keys, etc. Make variables available to the running application by prepending the variable key with <code>K8S_SECRET_</code>. You can set variables to be:"
+msgstr ""
+
msgid "Environment variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. Additionally, they can be masked so they are hidden in job logs, though they must match certain regexp requirements to do so. You can use environment variables for passwords, secret keys, or whatever you want."
msgstr ""
msgid "Environment variables are configured by your administrator to be %{link_start}protected%{link_end} by default"
msgstr ""
+msgid "Environment variables on this GitLab instance are configured to be %{link_start}protected%{link_end} by default"
+msgstr ""
+
msgid "Environment:"
msgstr ""
@@ -9047,6 +9274,9 @@ msgstr ""
msgid "EventFilterBy|Filter by comments"
msgstr ""
+msgid "EventFilterBy|Filter by designs"
+msgstr ""
+
msgid "EventFilterBy|Filter by epic events"
msgstr ""
@@ -9179,15 +9409,15 @@ msgstr ""
msgid "Expand approvers"
msgstr ""
-msgid "Expand child epics"
-msgstr ""
-
msgid "Expand down"
msgstr ""
msgid "Expand dropdown"
msgstr ""
+msgid "Expand milestones"
+msgstr ""
+
msgid "Expand sidebar"
msgstr ""
@@ -9203,6 +9433,9 @@ msgstr ""
msgid "Expiration date"
msgstr ""
+msgid "Expiration not enforced"
+msgstr ""
+
msgid "Expiration policy for the Container Registry is a perfect solution for keeping the Registry space down while still enjoying the full power of GitLab CI/CD."
msgstr ""
@@ -9323,6 +9556,9 @@ msgstr ""
msgid "Failed Jobs"
msgstr ""
+msgid "Failed on"
+msgstr ""
+
msgid "Failed to add a Zoom meeting"
msgstr ""
@@ -9383,13 +9619,19 @@ msgstr ""
msgid "Failed to install."
msgstr ""
+msgid "Failed to load assignees. Please try again."
+msgstr ""
+
+msgid "Failed to load authors. Please try again."
+msgstr ""
+
msgid "Failed to load emoji list."
msgstr ""
msgid "Failed to load error details from Sentry."
msgstr ""
-msgid "Failed to load errors from Sentry. Error message: %{errorMessage}"
+msgid "Failed to load errors from Sentry."
msgstr ""
msgid "Failed to load group activity metrics. Please try again."
@@ -9470,6 +9712,9 @@ msgstr ""
msgid "Failed to set due date because the date format is invalid."
msgstr ""
+msgid "Failed to set iteration on this issue. Please try again."
+msgstr ""
+
msgid "Failed to signing using smartcard authentication"
msgstr ""
@@ -9692,13 +9937,10 @@ msgstr ""
msgid "FeatureFlags|Target environments"
msgstr ""
-msgid "FeatureFlags|There are no active feature flags"
-msgstr ""
-
-msgid "FeatureFlags|There are no inactive feature flags"
+msgid "FeatureFlags|There was an error fetching the feature flags."
msgstr ""
-msgid "FeatureFlags|There was an error fetching the feature flags."
+msgid "FeatureFlags|There was an error retrieving user lists"
msgstr ""
msgid "FeatureFlags|Try again in a few moments or contact your support team."
@@ -9710,9 +9952,18 @@ msgstr ""
msgid "FeatureFlag|Delete strategy"
msgstr ""
+msgid "FeatureFlag|List"
+msgstr ""
+
msgid "FeatureFlag|Percentage"
msgstr ""
+msgid "FeatureFlag|Select a user list"
+msgstr ""
+
+msgid "FeatureFlag|There are no configured user lists"
+msgstr ""
+
msgid "FeatureFlag|Type"
msgstr ""
@@ -9869,6 +10120,12 @@ msgstr ""
msgid "Filter..."
msgstr ""
+msgid "Find File"
+msgstr ""
+
+msgid "Find bugs in your code with coverage-guided fuzzing"
+msgstr ""
+
msgid "Find by path"
msgstr ""
@@ -10133,6 +10390,9 @@ msgstr ""
msgid "Geo allows you to replicate your GitLab instance to other geographical locations."
msgstr ""
+msgid "Geo nodes are paused using a command run on the node"
+msgstr ""
+
msgid "GeoNodeStatusEvent|%{timeAgoStr} (%{pendingEvents} events)"
msgstr ""
@@ -10217,6 +10477,9 @@ msgstr ""
msgid "GeoNodes|Not checksummed"
msgstr ""
+msgid "GeoNodes|Package files"
+msgstr ""
+
msgid "GeoNodes|Pausing replication stops the sync process. Are you sure?"
msgstr ""
@@ -10235,6 +10498,9 @@ msgstr ""
msgid "GeoNodes|Replication slots"
msgstr ""
+msgid "GeoNodes|Replication status"
+msgstr ""
+
msgid "GeoNodes|Repositories"
msgstr ""
@@ -10445,6 +10711,9 @@ msgstr ""
msgid "Geo|Synchronization failed - %{error}"
msgstr ""
+msgid "Geo|Synchronization of %{itemTitle} is disabled."
+msgstr ""
+
msgid "Geo|The database is currently %{db_lag} behind the primary node."
msgstr ""
@@ -10757,6 +11026,9 @@ msgstr ""
msgid "Go to %{link_to_google_takeout}."
msgstr ""
+msgid "Go to <strong>Issues</strong> > <strong>Boards</strong> to access your personalized learning issue board."
+msgstr ""
+
msgid "Go to Pipelines"
msgstr ""
@@ -10856,9 +11128,6 @@ msgstr ""
msgid "Go to your snippets"
msgstr ""
-msgid "Golden Tanuki"
-msgstr ""
-
msgid "Google Cloud Platform"
msgstr ""
@@ -10880,6 +11149,9 @@ msgstr ""
msgid "Grafana URL"
msgstr ""
+msgid "Grafana response contains invalid json"
+msgstr ""
+
msgid "GrafanaIntegration|API Token"
msgstr ""
@@ -10928,9 +11200,6 @@ msgstr ""
msgid "Group %{group_name} was successfully created."
msgstr ""
-msgid "Group '%{group_name}' is being imported."
-msgstr ""
-
msgid "Group Audit Events"
msgstr ""
@@ -10967,9 +11236,6 @@ msgstr ""
msgid "Group by:"
msgstr ""
-msgid "Group could not be imported: %{errors}"
-msgstr ""
-
msgid "Group description"
msgstr ""
@@ -10997,9 +11263,15 @@ msgstr ""
msgid "Group has not been marked for deletion"
msgstr ""
+msgid "Group import could not be scheduled"
+msgstr ""
+
msgid "Group info:"
msgstr ""
+msgid "Group is required when cluster_type is :group"
+msgstr ""
+
msgid "Group maintainers can register group runners in the %{link}"
msgstr ""
@@ -11063,6 +11335,24 @@ msgstr ""
msgid "GroupActivyMetrics|Recent activity (last 90 days)"
msgstr ""
+msgid "GroupImport|Failed to import group."
+msgstr ""
+
+msgid "GroupImport|Group '%{group_name}' is being imported."
+msgstr ""
+
+msgid "GroupImport|Group could not be imported: %{errors}"
+msgstr ""
+
+msgid "GroupImport|Please wait while we import the group for you. Refresh at will."
+msgstr ""
+
+msgid "GroupImport|The group was successfully imported."
+msgstr ""
+
+msgid "GroupImport|Unable to process group import file"
+msgstr ""
+
msgid "GroupRoadmap|%{dateWord} – No end date"
msgstr ""
@@ -11330,6 +11620,9 @@ msgstr ""
msgid "Groups (%{groups})"
msgstr ""
+msgid "Groups and projects"
+msgstr ""
+
msgid "Groups can also be nested by creating %{subgroup_docs_link_start}subgroups%{subgroup_docs_link_end}."
msgstr ""
@@ -11375,6 +11668,33 @@ msgstr ""
msgid "GroupsEmptyState|You can manage your group member’s permissions and access to each project in the group."
msgstr ""
+msgid "GroupsNew|Contact an administrator to enable options for importing your group."
+msgstr ""
+
+msgid "GroupsNew|Create"
+msgstr ""
+
+msgid "GroupsNew|Create group"
+msgstr ""
+
+msgid "GroupsNew|GitLab group export"
+msgstr ""
+
+msgid "GroupsNew|Import"
+msgstr ""
+
+msgid "GroupsNew|Import group"
+msgstr ""
+
+msgid "GroupsNew|My Awesome Group"
+msgstr ""
+
+msgid "GroupsNew|No import options available"
+msgstr ""
+
+msgid "GroupsNew|To copy a GitLab group between installations, navigate to the group settings page for the original installation, generate an export file, and upload it here."
+msgstr ""
+
msgid "GroupsTree|Are you sure you want to leave the \"%{fullName}\" group?"
msgstr ""
@@ -11477,6 +11797,9 @@ msgstr ""
msgid "Helps reduce request volume for protected paths"
msgstr ""
+msgid "Here are all your projects in your group, including the one you just created. To start, let’s take a look at your personalized learning project which will help you learn about GitLab at your own pace."
+msgstr ""
+
msgid "Here you will find recent merge request activity"
msgstr ""
@@ -11492,6 +11815,9 @@ msgstr[0] ""
msgstr[1] ""
msgstr[2] ""
+msgid "Hide details"
+msgstr ""
+
msgid "Hide file browser"
msgstr ""
@@ -11501,6 +11827,9 @@ msgstr ""
msgid "Hide host keys manual input"
msgstr ""
+msgid "Hide list"
+msgstr ""
+
msgid "Hide marketing-related entries from help"
msgstr ""
@@ -11522,9 +11851,6 @@ msgstr[2] ""
msgid "Hide values"
msgstr ""
-msgid "Hiding all labels"
-msgstr ""
-
msgid "High or unknown vulnerabilities present"
msgstr ""
@@ -11609,6 +11935,9 @@ msgstr ""
msgid "ID:"
msgstr ""
+msgid "IDE"
+msgstr ""
+
msgid "IDE|Allow live previews of JavaScript projects in the Web IDE using CodeSandbox Live Preview."
msgstr ""
@@ -11699,10 +12028,10 @@ msgstr ""
msgid "If enabled"
msgstr ""
-msgid "If enabled, access to projects will be validated on an external service using their classification label."
+msgid "If enabled, GitLab will handle Object Storage replication using Geo. %{linkStart}More information%{linkEnd}"
msgstr ""
-msgid "If enabled, and if object storage is enabled, GitLab will handle Object Storage replication using Geo"
+msgid "If enabled, access to projects will be validated on an external service using their classification label."
msgstr ""
msgid "If there is no previous license or if the previous license has expired, some GitLab functionality will be blocked until a new, valid license is uploaded."
@@ -12065,6 +12394,9 @@ msgstr[0] ""
msgstr[1] ""
msgstr[2] ""
+msgid "Instance Configuration"
+msgstr ""
+
msgid "Instance Statistics visibility"
msgstr ""
@@ -12086,9 +12418,6 @@ msgstr ""
msgid "Integrations"
msgstr ""
-msgid "Integrations allow you to integrate GitLab with other applications"
-msgstr ""
-
msgid "Integrations|All details"
msgstr ""
@@ -12200,6 +12529,9 @@ msgstr ""
msgid "Invalid query"
msgstr ""
+msgid "Invalid repository bundle for snippet with id %{snippet_id}"
+msgstr ""
+
msgid "Invalid repository path"
msgstr ""
@@ -12218,6 +12550,9 @@ msgstr ""
msgid "Invalid two-factor code."
msgstr ""
+msgid "Invalid yaml"
+msgstr ""
+
msgid "Invitation"
msgstr ""
@@ -12437,15 +12772,24 @@ msgstr ""
msgid "It seems like the Dependency Scanning job ran successfully, but no dependencies have been detected in your project."
msgstr ""
+msgid "It seems that there is currently no available data for code coverage"
+msgstr ""
+
msgid "It's you"
msgstr ""
+msgid "Iteration"
+msgstr ""
+
msgid "Iteration changed to"
msgstr ""
msgid "Iteration removed"
msgstr ""
+msgid "Iteration updated"
+msgstr ""
+
msgid "Iterations"
msgstr ""
@@ -12476,12 +12820,18 @@ msgstr ""
msgid "January"
msgstr ""
+msgid "Jira Issues"
+msgstr ""
+
msgid "Jira import is already running."
msgstr ""
msgid "Jira integration not configured."
msgstr ""
+msgid "Jira project key is not configured"
+msgstr ""
+
msgid "Jira project: %{importProject}"
msgstr ""
@@ -12662,6 +13012,9 @@ msgstr ""
msgid "Keep divergent refs"
msgstr ""
+msgid "Kerberos access denied"
+msgstr ""
+
msgid "Key"
msgstr ""
@@ -12671,10 +13024,13 @@ msgstr ""
msgid "Key: %{key}"
msgstr ""
-msgid "Keyboard Shortcuts"
+msgid "Keyboard shortcuts"
msgstr ""
-msgid "Keyboard shortcuts"
+msgid "Keys"
+msgstr ""
+
+msgid "Ki"
msgstr ""
msgid "Kubernetes"
@@ -12716,6 +13072,9 @@ msgstr ""
msgid "LDAP"
msgstr ""
+msgid "LDAP Synchronization"
+msgstr ""
+
msgid "LDAP settings"
msgstr ""
@@ -12941,12 +13300,18 @@ msgstr ""
msgid "Learn more about custom project templates"
msgstr ""
+msgid "Learn more about deploying to AWS"
+msgstr ""
+
msgid "Learn more about deploying to a cluster"
msgstr ""
msgid "Learn more about group-level project templates"
msgstr ""
+msgid "Learn more about job dependencies"
+msgstr ""
+
msgid "Learn more about signing commits"
msgstr ""
@@ -12998,9 +13363,21 @@ msgstr ""
msgid "License History"
msgstr ""
+msgid "License ID:"
+msgstr ""
+
+msgid "License URL"
+msgstr ""
+
msgid "License-Check"
msgstr ""
+msgid "LicenseCompliance|%{docLinkStart}License Approvals%{docLinkEnd} are active"
+msgstr ""
+
+msgid "LicenseCompliance|%{docLinkStart}License Approvals%{docLinkEnd} are inactive"
+msgstr ""
+
msgid "LicenseCompliance|Add a license"
msgstr ""
@@ -13022,9 +13399,15 @@ msgstr ""
msgid "LicenseCompliance|Deny"
msgstr ""
+msgid "LicenseCompliance|Learn more about %{linkStart}License Approvals%{linkEnd}"
+msgstr ""
+
msgid "LicenseCompliance|License"
msgstr ""
+msgid "LicenseCompliance|License Approvals"
+msgstr ""
+
msgid "LicenseCompliance|License Compliance detected %d license and policy violation for the source branch only; approval required"
msgid_plural "LicenseCompliance|License Compliance detected %d licenses and policy violations for the source branch only; approval required"
msgstr[0] ""
@@ -13250,6 +13633,9 @@ msgstr ""
msgid "List your Bitbucket Server repositories"
msgstr ""
+msgid "Lists"
+msgstr ""
+
msgid "Live preview"
msgstr ""
@@ -13433,6 +13819,9 @@ msgstr ""
msgid "Manage project labels"
msgstr ""
+msgid "Manage storage usage"
+msgstr ""
+
msgid "Manage two-factor authentication"
msgstr ""
@@ -13448,6 +13837,9 @@ msgstr ""
msgid "Manifest file import"
msgstr ""
+msgid "Manifest import"
+msgstr ""
+
msgid "Manual job"
msgstr ""
@@ -13595,6 +13987,12 @@ msgstr ""
msgid "Maximum field length"
msgstr ""
+msgid "Maximum file size is 2MB. Please select a smaller file."
+msgstr ""
+
+msgid "Maximum import size (MB)"
+msgstr ""
+
msgid "Maximum job timeout"
msgstr ""
@@ -13634,6 +14032,9 @@ msgstr ""
msgid "Maximum size of Elasticsearch bulk indexing requests."
msgstr ""
+msgid "Maximum size of import files."
+msgstr ""
+
msgid "Maximum size of individual attachments in comments."
msgstr ""
@@ -13685,6 +14086,9 @@ msgstr ""
msgid "Merge (when the pipeline succeeds)"
msgstr ""
+msgid "Merge Conflicts"
+msgstr ""
+
msgid "Merge Request"
msgstr ""
@@ -13811,6 +14215,9 @@ msgstr ""
msgid "MergeRequests|Squash task canceled: another squash is already in progress."
msgstr ""
+msgid "MergeRequests|This project does not allow squashing commits when merge requests are accepted."
+msgstr ""
+
msgid "MergeRequests|Thread stays resolved"
msgstr ""
@@ -13964,6 +14371,12 @@ msgstr ""
msgid "MetricsSettings|Add a button to the metrics dashboard linking directly to your existing external dashboard."
msgstr ""
+msgid "MetricsSettings|Choose whether to display dashboard metrics in UTC or the user's local timezone."
+msgstr ""
+
+msgid "MetricsSettings|Dashboard timezone"
+msgstr ""
+
msgid "MetricsSettings|External dashboard URL"
msgstr ""
@@ -13973,6 +14386,12 @@ msgstr ""
msgid "MetricsSettings|Metrics Dashboard"
msgstr ""
+msgid "MetricsSettings|UTC (Coordinated Universal Time)"
+msgstr ""
+
+msgid "MetricsSettings|User's local timezone"
+msgstr ""
+
msgid "Metrics|Add metric"
msgstr ""
@@ -13988,6 +14407,9 @@ msgstr ""
msgid "Metrics|Create metric"
msgstr ""
+msgid "Metrics|Current"
+msgstr ""
+
msgid "Metrics|Delete metric"
msgstr ""
@@ -14039,6 +14461,9 @@ msgstr ""
msgid "Metrics|Max"
msgstr ""
+msgid "Metrics|Min"
+msgstr ""
+
msgid "Metrics|Must be a valid PromQL query."
msgstr ""
@@ -14054,6 +14479,9 @@ msgstr ""
msgid "Metrics|Refresh dashboard"
msgstr ""
+msgid "Metrics|Select a value"
+msgstr ""
+
msgid "Metrics|Star dashboard"
msgstr ""
@@ -14072,12 +14500,18 @@ msgstr ""
msgid "Metrics|There was an error getting annotations information."
msgstr ""
+msgid "Metrics|There was an error getting dashboard validation warnings information."
+msgstr ""
+
msgid "Metrics|There was an error getting deployment information."
msgstr ""
msgid "Metrics|There was an error getting environments information."
msgstr ""
+msgid "Metrics|There was an error getting options for variable \"%{name}\"."
+msgstr ""
+
msgid "Metrics|There was an error trying to validate your query"
msgstr ""
@@ -14120,6 +14554,9 @@ msgstr ""
msgid "Metrics|You're about to permanently delete this metric. This cannot be undone."
msgstr ""
+msgid "Metrics|Your dashboard schema is invalid. Edit the dashboard to correct the YAML schema."
+msgstr ""
+
msgid "Metrics|e.g. HTTP requests"
msgstr ""
@@ -14135,12 +14572,18 @@ msgstr ""
msgid "Metrics|e.g. req/sec"
msgstr ""
+msgid "Mi"
+msgstr ""
+
msgid "Microsoft Azure"
msgstr ""
msgid "Middleman project with Static Site Editor support"
msgstr ""
+msgid "Migrate your data from an external source like GitHub, Bitbucket, or another instance of GitLab."
+msgstr ""
+
msgid "Migrated %{success_count}/%{total_count} files."
msgstr ""
@@ -14276,6 +14719,9 @@ msgstr ""
msgid "Minimum capacity to be available before we schedule more mirrors preemptively."
msgstr ""
+msgid "Minimum interval in days"
+msgstr ""
+
msgid "Minimum length is %{minimum_password_length} characters"
msgstr ""
@@ -14462,6 +14908,12 @@ msgstr ""
msgid "Multiple uploaders found: %{uploader_types}"
msgstr ""
+msgid "Must match with the %{codeStart}external_url%{codeEnd} in %{codeStart}/etc/gitlab/gitlab.rb%{codeEnd}."
+msgstr ""
+
+msgid "Must match with the %{codeStart}geo_node_name%{codeEnd} in %{codeStart}/etc/gitlab/gitlab.rb%{codeEnd}. %{linkStart}More information%{linkEnd}"
+msgstr ""
+
msgid "My Awesome Group"
msgstr ""
@@ -14576,10 +15028,16 @@ msgstr ""
msgid "New Application"
msgstr ""
+msgid "New Branch"
+msgstr ""
+
+msgid "New Deploy Key"
+msgstr ""
+
msgid "New Environment"
msgstr ""
-msgid "New Geo Node"
+msgid "New File"
msgstr ""
msgid "New Group"
@@ -14597,15 +15055,15 @@ msgstr[0] ""
msgstr[1] ""
msgstr[2] ""
-msgid "New Iteration"
-msgstr ""
-
msgid "New Jira import"
msgstr ""
msgid "New Label"
msgstr ""
+msgid "New Merge Request"
+msgstr ""
+
msgid "New Milestone"
msgstr ""
@@ -14624,6 +15082,9 @@ msgstr ""
msgid "New Snippet"
msgstr ""
+msgid "New User"
+msgstr ""
+
msgid "New branch"
msgstr ""
@@ -14666,6 +15127,9 @@ msgstr ""
msgid "New issue title"
msgstr ""
+msgid "New iteration"
+msgstr ""
+
msgid "New iteration created"
msgstr ""
@@ -14843,6 +15307,12 @@ msgstr ""
msgid "No grouping"
msgstr ""
+msgid "No iteration"
+msgstr ""
+
+msgid "No iterations to show"
+msgstr ""
+
msgid "No job log"
msgstr ""
@@ -14885,6 +15355,9 @@ msgstr ""
msgid "No other labels with such name or description"
msgstr ""
+msgid "No panels matching properties %{opts}"
+msgstr ""
+
msgid "No parent group"
msgstr ""
@@ -14918,6 +15391,9 @@ msgstr ""
msgid "No schedules"
msgstr ""
+msgid "No stack trace for this error"
+msgstr ""
+
msgid "No starrers matched your search"
msgstr ""
@@ -14930,7 +15406,7 @@ msgstr ""
msgid "No test coverage"
msgstr ""
-msgid "No thanks, don't show this again"
+msgid "No thanks"
msgstr ""
msgid "No vulnerabilities present"
@@ -14945,9 +15421,6 @@ msgstr ""
msgid "No, directly import the existing email addresses and usernames."
msgstr ""
-msgid "No, not interested right now"
-msgstr ""
-
msgid "No. of commits"
msgstr ""
@@ -14993,9 +15466,6 @@ msgstr ""
msgid "Not found."
msgstr ""
-msgid "Not helpful"
-msgstr ""
-
msgid "Not now"
msgstr ""
@@ -15152,6 +15622,9 @@ msgstr ""
msgid "Notifications on"
msgstr ""
+msgid "Notify users by email when sign-in location is not recognized"
+msgstr ""
+
msgid "Nov"
msgstr ""
@@ -15218,9 +15691,6 @@ msgstr ""
msgid "Oh no!"
msgstr ""
-msgid "Ok let's go"
-msgstr ""
-
msgid "Oldest first"
msgstr ""
@@ -15233,16 +15703,49 @@ msgstr ""
msgid "On track"
msgstr ""
+msgid "OnDemandScans|Attached branch"
+msgstr ""
+
+msgid "OnDemandScans|Attached branch is where the scan job runs."
+msgstr ""
+
+msgid "OnDemandScans|Could not run the scan. Please try again."
+msgstr ""
+
msgid "OnDemandScans|Create new DAST scan"
msgstr ""
+msgid "OnDemandScans|DAST will scan the target URL and any discovered sub URLs."
+msgstr ""
+
+msgid "OnDemandScans|New on-demand DAST scan"
+msgstr ""
+
msgid "OnDemandScans|On-demand Scans"
msgstr ""
+msgid "OnDemandScans|On-demand scans run outside the DevOps cycle and find vulnerabilities in your projects. %{learnMoreLinkStart}Learn more%{learnMoreLinkEnd}"
+msgstr ""
+
+msgid "OnDemandScans|Only a passive scan can be performed on demand."
+msgstr ""
+
+msgid "OnDemandScans|Passive DAST Scan"
+msgstr ""
+
+msgid "OnDemandScans|Please enter a valid URL format, ex: http://www.example.com/home"
+msgstr ""
+
+msgid "OnDemandScans|Run this scan"
+msgstr ""
+
+msgid "OnDemandScans|Scan mode"
+msgstr ""
+
msgid "OnDemandScans|Schedule or run scans immediately against target sites. Currently available on-demand scan type: DAST. %{helpLinkStart}More information%{helpLinkEnd}"
msgstr ""
-msgid "Onboarding"
+msgid "OnDemandScans|Target URL"
msgstr ""
msgid "Once imported, repositories can be mirrored over SSH. Read more %{link_start}here%{link_end}."
@@ -15269,6 +15772,9 @@ msgstr ""
msgid "One or more of you personal access tokens were revoked"
msgstr ""
+msgid "One or more of your %{provider} projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
+msgstr ""
+
msgid "One or more of your Bitbucket projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
msgstr ""
@@ -15770,6 +16276,9 @@ msgstr ""
msgid "Parent epic is not present."
msgstr ""
+msgid "Parsing error for param :embed_json. %{message}"
+msgstr ""
+
msgid "Part of merge request changes"
msgstr ""
@@ -15782,6 +16291,9 @@ msgstr ""
msgid "Passed"
msgstr ""
+msgid "Passed on"
+msgstr ""
+
msgid "Password"
msgstr ""
@@ -15866,6 +16378,9 @@ msgstr ""
msgid "Perform common operations on GitLab project"
msgstr ""
+msgid "Performance and resource management"
+msgstr ""
+
msgid "Performance optimization"
msgstr ""
@@ -16073,7 +16588,7 @@ msgstr ""
msgid "Pipelines|Something went wrong while cleaning runners cache."
msgstr ""
-msgid "Pipelines|There are currently no %{scope} pipelines."
+msgid "Pipelines|There are currently no finished pipelines."
msgstr ""
msgid "Pipelines|There are currently no pipelines."
@@ -16184,6 +16699,9 @@ msgstr ""
msgid "Pipeline|Stop pipeline #%{pipelineId}?"
msgstr ""
+msgid "Pipeline|Tag name"
+msgstr ""
+
msgid "Pipeline|Trigger author"
msgstr ""
@@ -16265,12 +16783,18 @@ msgstr ""
msgid "Please check your email (%{email}) to verify that you own this address and unlock the power of CI/CD. Didn't receive it? %{resend_link}. Wrong email address? %{update_link}."
msgstr ""
+msgid "Please choose a file"
+msgstr ""
+
msgid "Please choose a group URL with no special characters."
msgstr ""
msgid "Please complete your profile with email address"
msgstr ""
+msgid "Please convert %{linkStart}them to Git%{linkEnd}, and go through the %{linkToImportFlow} again."
+msgstr ""
+
msgid "Please convert them to %{link_to_git}, and go through the %{link_to_import_flow} again."
msgstr ""
@@ -16328,6 +16852,9 @@ msgstr ""
msgid "Please provide a valid email address."
msgstr ""
+msgid "Please provide attributes to update"
+msgstr ""
+
msgid "Please refer to <a href=\"%{docs_url}\">%{docs_url}</a>"
msgstr ""
@@ -17063,6 +17590,9 @@ msgstr ""
msgid "Project has too many %{label_for_message} to search"
msgstr ""
+msgid "Project is required when cluster_type is :project"
+msgstr ""
+
msgid "Project members"
msgstr ""
@@ -17180,7 +17710,40 @@ msgstr ""
msgid "ProjectService|Comment"
msgstr ""
-msgid "ProjectService|Comment will be posted on each event"
+msgid "ProjectService|Event will be triggered by a push to the repository"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a commit is created/updated"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a confidential issue is created/updated/closed"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a deployment finishes"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a merge request is created/updated/merged"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a new tag is pushed to the repository"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a new, unique alert is recorded"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a pipeline status changes"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a wiki page is created/updated"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when an issue is created/updated/closed"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when someone adds a comment"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when someone adds a comment on a confidential issue"
msgstr ""
msgid "ProjectService|Perform common operations on GitLab project: %{project_name}"
@@ -17231,6 +17794,9 @@ msgstr ""
msgid "ProjectSettings|Enable 'Delete source branch' option by default"
msgstr ""
+msgid "ProjectSettings|Enable merge trains and pipelines for merged results"
+msgstr ""
+
msgid "ProjectSettings|Every merge creates a merge commit"
msgstr ""
@@ -17261,7 +17827,7 @@ msgstr ""
msgid "ProjectSettings|Forks"
msgstr ""
-msgid "ProjectSettings|Git Large File Storage"
+msgid "ProjectSettings|Git Large File Storage (LFS)"
msgstr ""
msgid "ProjectSettings|Internal"
@@ -17297,9 +17863,6 @@ msgstr ""
msgid "ProjectSettings|Merge options"
msgstr ""
-msgid "ProjectSettings|Merge pipelines will try to validate the post-merge result prior to merging"
-msgstr ""
-
msgid "ProjectSettings|Merge requests"
msgstr ""
@@ -17327,6 +17890,9 @@ msgstr ""
msgid "ProjectSettings|Pipelines"
msgstr ""
+msgid "ProjectSettings|Pipelines for merge requests must be enabled in the CI/CD configuration file, or pipelines could be unresolvable or dropped"
+msgstr ""
+
msgid "ProjectSettings|Pipelines must succeed"
msgstr ""
@@ -17354,6 +17920,9 @@ msgstr ""
msgid "ProjectSettings|Show link to create/view merge request when pushing from the command line"
msgstr ""
+msgid "ProjectSettings|Skipped pipelines are considered successful"
+msgstr ""
+
msgid "ProjectSettings|Snippets"
msgstr ""
@@ -17369,6 +17938,9 @@ msgstr ""
msgid "ProjectSettings|These checks must pass before merge requests can be merged"
msgstr ""
+msgid "ProjectSettings|This introduces the risk of merging changes that will not pass the pipeline."
+msgstr ""
+
msgid "ProjectSettings|This setting is applied on the server level and can be overridden by an admin."
msgstr ""
@@ -17546,15 +18118,27 @@ msgstr ""
msgid "ProjectsNew|Blank project"
msgstr ""
+msgid "ProjectsNew|Connect your external repository to GitLab CI/CD."
+msgstr ""
+
msgid "ProjectsNew|Contact an administrator to enable options for importing your project."
msgstr ""
msgid "ProjectsNew|Create"
msgstr ""
+msgid "ProjectsNew|Create a blank project to house your files, plan your work, and collaborate on code, among other things."
+msgstr ""
+
+msgid "ProjectsNew|Create blank project"
+msgstr ""
+
msgid "ProjectsNew|Create from template"
msgstr ""
+msgid "ProjectsNew|Create new project"
+msgstr ""
+
msgid "ProjectsNew|Creating project & repository."
msgstr ""
@@ -17579,6 +18163,9 @@ msgstr ""
msgid "ProjectsNew|Project description %{tag_start}(optional)%{tag_end}"
msgstr ""
+msgid "ProjectsNew|Run CI/CD for external repository"
+msgstr ""
+
msgid "ProjectsNew|Template"
msgstr ""
@@ -17840,6 +18427,9 @@ msgstr ""
msgid "Protected Branch"
msgstr ""
+msgid "Protected Branches"
+msgstr ""
+
msgid "Protected Environment"
msgstr ""
@@ -17852,6 +18442,9 @@ msgstr ""
msgid "Protected Tag"
msgstr ""
+msgid "Protected Tags"
+msgstr ""
+
msgid "Protected branches"
msgstr ""
@@ -17951,6 +18544,9 @@ msgstr ""
msgid "Provider"
msgstr ""
+msgid "Proxy support for this API is not available currently"
+msgstr ""
+
msgid "Pseudonymizer data collection"
msgstr ""
@@ -17975,6 +18571,9 @@ msgstr ""
msgid "Publish to status page"
msgstr ""
+msgid "Published on status page"
+msgstr ""
+
msgid "Publishes this issue to the associated status page."
msgstr ""
@@ -18149,6 +18748,9 @@ msgstr ""
msgid "Recipe"
msgstr ""
+msgid "Reconfigure"
+msgstr ""
+
msgid "Recover hidden stage"
msgstr ""
@@ -18236,6 +18838,9 @@ msgstr ""
msgid "Registration|Your profile"
msgstr ""
+msgid "Regulate approvals by authors/committers, based on compliance frameworks. Can be changed only at the instance level."
+msgstr ""
+
msgid "Rejected (closed)"
msgstr ""
@@ -18287,15 +18892,24 @@ msgstr ""
msgid "ReleaseAssetLinkType|Image"
msgstr ""
+msgid "ReleaseAssetLinkType|Images"
+msgstr ""
+
msgid "ReleaseAssetLinkType|Other"
msgstr ""
msgid "ReleaseAssetLinkType|Package"
msgstr ""
+msgid "ReleaseAssetLinkType|Packages"
+msgstr ""
+
msgid "ReleaseAssetLinkType|Runbook"
msgstr ""
+msgid "ReleaseAssetLinkType|Runbooks"
+msgstr ""
+
msgid "Releases"
msgstr ""
@@ -18308,6 +18922,9 @@ msgstr ""
msgid "Releases documentation"
msgstr ""
+msgid "Releases|New Release"
+msgstr ""
+
msgid "Release|Something went wrong while getting the release details"
msgstr ""
@@ -18380,6 +18997,9 @@ msgstr ""
msgid "Remove fork relationship"
msgstr ""
+msgid "Remove from batch"
+msgstr ""
+
msgid "Remove from board"
msgstr ""
@@ -18569,6 +19189,12 @@ msgstr ""
msgid "Replication"
msgstr ""
+msgid "Replication enabled"
+msgstr ""
+
+msgid "Replication paused"
+msgstr ""
+
msgid "Reply by email"
msgstr ""
@@ -18596,15 +19222,15 @@ msgstr ""
msgid "Reported %{timeAgo} by %{reportedBy}"
msgstr ""
-msgid "Reported Resource Changes: %{addNum} to add, %{changeNum} to change, %{deleteNum} to delete"
-msgstr ""
-
msgid "Reporter"
msgstr ""
msgid "Reporting"
msgstr ""
+msgid "Reports"
+msgstr ""
+
msgid "Reports|%{combinedString} and %{resolvedString}"
msgstr ""
@@ -18644,6 +19270,9 @@ msgstr ""
msgid "Reports|Failure"
msgstr ""
+msgid "Reports|Identifier"
+msgstr ""
+
msgid "Reports|Metrics reports are loading"
msgstr ""
@@ -18656,6 +19285,9 @@ msgstr ""
msgid "Reports|Metrics reports failed loading results"
msgstr ""
+msgid "Reports|Scanner"
+msgstr ""
+
msgid "Reports|Severity"
msgstr ""
@@ -18704,15 +19336,33 @@ msgstr ""
msgid "Repository cleanup has started. You will receive an email once the cleanup operation is complete."
msgstr ""
+msgid "Repository files count over the limit"
+msgstr ""
+
+msgid "Repository has an invalid default branch name."
+msgstr ""
+
+msgid "Repository has more than one branch."
+msgstr ""
+
msgid "Repository has no locks."
msgstr ""
+msgid "Repository has tags."
+msgstr ""
+
msgid "Repository maintenance"
msgstr ""
msgid "Repository mirroring"
msgstr ""
+msgid "Repository must contain at least 1 file."
+msgstr ""
+
+msgid "Repository size is above the limit."
+msgstr ""
+
msgid "Repository static objects"
msgstr ""
@@ -18722,7 +19372,7 @@ msgstr ""
msgid "Repository sync capacity"
msgstr ""
-msgid "Repository: %{counter_repositories} / Wikis: %{counter_wikis} / Build Artifacts: %{counter_build_artifacts} / LFS: %{counter_lfs_objects}"
+msgid "Repository: %{counter_repositories} / Wikis: %{counter_wikis} / Build Artifacts: %{counter_build_artifacts} / LFS: %{counter_lfs_objects} / Snippets: %{counter_snippets}"
msgstr ""
msgid "RepositorySettingsAccessLevel|Select"
@@ -18734,6 +19384,9 @@ msgstr ""
msgid "Request Headers"
msgstr ""
+msgid "Request details"
+msgstr ""
+
msgid "Request parameter %{param} is missing."
msgstr ""
@@ -18842,6 +19495,9 @@ msgstr ""
msgid "Reset template"
msgstr ""
+msgid "Reset to project defaults"
+msgstr ""
+
msgid "Resetting the authorization key for this project will require updating the authorization key in every alert source it is enabled in."
msgstr ""
@@ -18938,9 +19594,6 @@ msgstr ""
msgid "Resume"
msgstr ""
-msgid "Resume replication"
-msgstr ""
-
msgid "Resync"
msgstr ""
@@ -19574,6 +20227,9 @@ msgstr ""
msgid "SecurityReports|False positive"
msgstr ""
+msgid "SecurityReports|Group Security Dashboard"
+msgstr ""
+
msgid "SecurityReports|Hide dismissed"
msgstr ""
@@ -19628,10 +20284,13 @@ msgstr ""
msgid "SecurityReports|Remove project from dashboard"
msgstr ""
-msgid "SecurityReports|Report type"
+msgid "SecurityReports|Return to dashboard"
+msgstr ""
+
+msgid "SecurityReports|Scan details"
msgstr ""
-msgid "SecurityReports|Return to dashboard"
+msgid "SecurityReports|Scanner type"
msgstr ""
msgid "SecurityReports|Security Dashboard"
@@ -19688,6 +20347,9 @@ msgstr ""
msgid "SecurityReports|There was an error while generating the report."
msgstr ""
+msgid "SecurityReports|Unable to add %{invalidProjectsMessage}"
+msgstr ""
+
msgid "SecurityReports|Unable to add %{invalidProjects}"
msgstr ""
@@ -19802,6 +20464,9 @@ msgstr ""
msgid "Select due date"
msgstr ""
+msgid "Select file"
+msgstr ""
+
msgid "Select group or project"
msgstr ""
@@ -19865,9 +20530,6 @@ msgstr ""
msgid "Select the branch you want to set as the default for this project. All merge requests and commits will automatically be made against this branch unless you specify a different one."
msgstr ""
-msgid "Select the configured storage available for new repositories to be placed on."
-msgstr ""
-
msgid "Select the custom project template source group."
msgstr ""
@@ -20060,12 +20722,6 @@ msgstr ""
msgid "Service Desk is enabled but not yet active"
msgstr ""
-msgid "Service Desk is off"
-msgstr ""
-
-msgid "Service Desk is on"
-msgstr ""
-
msgid "Service Templates"
msgstr ""
@@ -20153,6 +20809,15 @@ msgstr ""
msgid "Set the milestone to %{milestone_reference}."
msgstr ""
+msgid "Set the number of concurrent requests this secondary node will make to the primary node while backfilling."
+msgstr ""
+
+msgid "Set the synchronization and verification capacity for the secondary node."
+msgstr ""
+
+msgid "Set the timeout in seconds to send a secondary node status to the primary and IPs allowed for the secondary nodes."
+msgstr ""
+
msgid "Set time estimate"
msgstr ""
@@ -20192,6 +20857,9 @@ msgstr ""
msgid "Set weight to %{weight}."
msgstr ""
+msgid "Set what should be replicated by choosing specific projects or groups by the secondary node."
+msgstr ""
+
msgid "SetPasswordToCloneLink|set a password"
msgstr ""
@@ -20318,6 +20986,9 @@ msgstr ""
msgid "Show complete raw log"
msgstr ""
+msgid "Show details"
+msgstr ""
+
msgid "Show file browser"
msgstr ""
@@ -20327,10 +20998,13 @@ msgstr ""
msgid "Show latest version"
msgstr ""
+msgid "Show list"
+msgstr ""
+
msgid "Show me everything"
msgstr ""
-msgid "Show me how"
+msgid "Show me how to add a pipeline"
msgstr ""
msgid "Show me more advanced stuff"
@@ -20381,9 +21055,6 @@ msgstr ""
msgid "Showing all issues"
msgstr ""
-msgid "Showing all labels"
-msgstr ""
-
msgid "Showing last %{size} of log -"
msgstr ""
@@ -20495,9 +21166,6 @@ msgstr ""
msgid "Skip outdated deployment jobs"
msgstr ""
-msgid "Skip this for now"
-msgstr ""
-
msgid "Skipped"
msgstr ""
@@ -20642,6 +21310,9 @@ msgstr ""
msgid "Something went wrong while adding your award. Please try again."
msgstr ""
+msgid "Something went wrong while applying the batch of suggestions. Please try again."
+msgstr ""
+
msgid "Something went wrong while applying the suggestion. Please try again."
msgstr ""
@@ -20744,10 +21415,13 @@ msgstr ""
msgid "Something went wrong while updating a requirement."
msgstr ""
+msgid "Something went wrong while updating assignees"
+msgstr ""
+
msgid "Something went wrong while updating your list settings"
msgstr ""
-msgid "Something went wrong with your automatic subscription renewal"
+msgid "Something went wrong with your automatic subscription renewal."
msgstr ""
msgid "Something went wrong, unable to add %{project} to dashboard"
@@ -20765,6 +21439,9 @@ msgstr ""
msgid "Something went wrong, unable to search projects"
msgstr ""
+msgid "Something went wrong."
+msgstr ""
+
msgid "Something went wrong. Please try again."
msgstr ""
@@ -20948,6 +21625,9 @@ msgstr ""
msgid "Source code"
msgstr ""
+msgid "Source code (%{fileExtension})"
+msgstr ""
+
msgid "Source is not available"
msgstr ""
@@ -21032,6 +21712,9 @@ msgstr ""
msgid "Stack trace"
msgstr ""
+msgid "Stacktrace snippet"
+msgstr ""
+
msgid "Stage"
msgstr ""
@@ -21137,6 +21820,9 @@ msgstr ""
msgid "Start thread & reopen %{noteable_name}"
msgstr ""
+msgid "Start using Directed Acyclic Graphs (DAG)"
+msgstr ""
+
msgid "Start your Free Gold Trial"
msgstr ""
@@ -21599,6 +22285,21 @@ msgstr ""
msgid "SuggestedColors|Very pale orange"
msgstr ""
+msgid "Suggestion is not applicable as the suggestion was not found."
+msgstr ""
+
+msgid "Suggestions are not applicable as one or more suggestions were not found."
+msgstr ""
+
+msgid "Suggestions are not applicable as their lines cannot overlap."
+msgstr ""
+
+msgid "Suggestions must all be on the same branch."
+msgstr ""
+
+msgid "Suggestions that change line count can't be added to batches, yet."
+msgstr ""
+
msgid "Suggestions:"
msgstr ""
@@ -21641,9 +22342,6 @@ msgstr ""
msgid "Synchronization disabled"
msgstr ""
-msgid "Synchronization of container repositories is disabled."
-msgstr ""
-
msgid "System"
msgstr ""
@@ -21824,6 +22522,36 @@ msgstr ""
msgid "Terms of Service and Privacy Policy"
msgstr ""
+msgid "Terraform|%{number} Terraform report failed to generate"
+msgid_plural "Terraform|%{number} Terraform reports failed to generate"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+
+msgid "Terraform|%{number} Terraform report was generated in your pipelines"
+msgid_plural "Terraform|%{number} Terraform reports were generated in your pipelines"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+
+msgid "Terraform|A Terraform report failed to generate."
+msgstr ""
+
+msgid "Terraform|A Terraform report was generated in your pipelines."
+msgstr ""
+
+msgid "Terraform|Generating the report caused an error."
+msgstr ""
+
+msgid "Terraform|Reported Resource Changes: %{addNum} to add, %{changeNum} to change, %{deleteNum} to delete"
+msgstr ""
+
+msgid "Terraform|The Terraform report %{name} failed to generate."
+msgstr ""
+
+msgid "Terraform|The Terraform report %{name} was generated in your pipelines."
+msgstr ""
+
msgid "Test"
msgstr ""
@@ -21851,7 +22579,7 @@ msgstr ""
msgid "TestHooks|Ensure the project has CI pipelines."
msgstr ""
-msgid "TestHooks|Ensure the project has at least one commit."
+msgid "TestHooks|Ensure the project has deployments."
msgstr ""
msgid "TestHooks|Ensure the project has issues."
@@ -21953,7 +22681,7 @@ msgstr ""
msgid "The Prometheus server responded with \"bad request\". Please check your queries are correct and are supported in your Prometheus version. %{documentationLink}"
msgstr ""
-msgid "The URL defined on the primary node that secondary nodes should use to contact it. Defaults to URL"
+msgid "The URL defined on the primary node that secondary nodes should use to contact it."
msgstr ""
msgid "The URL to use for connecting to Elasticsearch. Use a comma-separated list to support clustering (e.g., \"http://localhost:9200, http://localhost:9201\")."
@@ -21962,6 +22690,9 @@ msgstr ""
msgid "The X509 Certificate to use when mutual TLS is required to communicate with the external authorization service. If left blank, the server certificate is still validated when accessing over HTTPS."
msgstr ""
+msgid "The above settings apply to all projects with the selected compliance framework(s)."
+msgstr ""
+
msgid "The amount of seconds after which a request to get a secondary node status will time out."
msgstr ""
@@ -22262,9 +22993,6 @@ msgstr ""
msgid "The total stage shows the time it takes between creating an issue and deploying the code to production. The data will be automatically added once you have completed the full idea to production cycle."
msgstr ""
-msgid "The unique identifier for the Geo node. Must match %{geoNodeName} if it is set in gitlab.rb, otherwise it must match %{externalUrl} with a trailing slash"
-msgstr ""
-
msgid "The update action will time out after %{number_of_minutes} minutes. For big repositories, use a clone/push combination."
msgstr ""
@@ -22289,9 +23017,6 @@ msgstr ""
msgid "The user you are trying to deactivate has been active in the past %{minimum_inactive_days} days and cannot be deactivated"
msgstr ""
-msgid "The user-facing URL of the Geo node"
-msgstr ""
-
msgid "The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6."
msgstr ""
@@ -22382,6 +23107,9 @@ msgstr ""
msgid "There was a problem fetching project branches."
msgstr ""
+msgid "There was a problem fetching project tags."
+msgstr ""
+
msgid "There was a problem fetching project users."
msgstr ""
@@ -22433,6 +23161,9 @@ msgstr ""
msgid "There was an error fetching the %{replicableType}"
msgstr ""
+msgid "There was an error fetching the Geo Settings"
+msgstr ""
+
msgid "There was an error fetching the Node's Groups"
msgstr ""
@@ -22553,6 +23284,12 @@ msgstr ""
msgid "This %{issuable} is locked. Only <strong>project members</strong> can comment."
msgstr ""
+msgid "This %{noteableTypeText} is %{confidentialLinkStart}confidential%{linkEnd} and %{lockedLinkStart}locked%{linkEnd}."
+msgstr ""
+
+msgid "This %{noteableTypeText} is locked."
+msgstr ""
+
msgid "This %{viewer} could not be displayed because %{reason}. You can %{options} instead."
msgstr ""
@@ -22571,6 +23308,9 @@ msgstr ""
msgid "This action can lead to data loss. To prevent accidental actions we ask you to confirm your intention."
msgstr ""
+msgid "This also resolves all related threads"
+msgstr ""
+
msgid "This also resolves the discussion"
msgstr ""
@@ -22661,6 +23401,9 @@ msgstr ""
msgid "This epic does not exist or you don't have sufficient permission."
msgstr ""
+msgid "This feature is currently in beta. We invite you to %{linkStart}give feedback%{linkEnd}."
+msgstr ""
+
msgid "This feature requires local storage to be enabled"
msgstr ""
@@ -22694,7 +23437,7 @@ msgstr ""
msgid "This is a Work in Progress"
msgstr ""
-msgid "This is a confidential issue."
+msgid "This is a confidential %{noteableTypeText}."
msgstr ""
msgid "This is a delayed job to run in %{remainingTime}"
@@ -22721,9 +23464,6 @@ msgstr ""
msgid "This is your current session"
msgstr ""
-msgid "This issue is %{confidentialLinkStart}confidential%{linkEnd} and %{lockedLinkStart}locked%{linkEnd}."
-msgstr ""
-
msgid "This issue is confidential"
msgstr ""
@@ -22733,9 +23473,6 @@ msgstr ""
msgid "This issue is in a child epic of the filtered epic"
msgstr ""
-msgid "This issue is locked."
-msgstr ""
-
msgid "This job depends on other jobs with expired/erased artifacts: %{invalid_dependencies}"
msgstr ""
@@ -22835,6 +23572,9 @@ msgstr ""
msgid "This license has already expired."
msgstr ""
+msgid "This link points to external content"
+msgstr ""
+
msgid "This may expose confidential information as the selected fork is in another namespace that can have other members."
msgstr ""
@@ -22862,6 +23602,9 @@ msgstr ""
msgid "This page will be removed in a future release."
msgstr ""
+msgid "This pipeline does not use the %{codeStart}needs%{codeEnd} keyword and can't be represented as a directed acyclic graph."
+msgstr ""
+
msgid "This pipeline makes use of a predefined CI/CD configuration enabled by %{strongStart}Auto DevOps.%{strongEnd}"
msgstr ""
@@ -23090,6 +23833,9 @@ msgstr ""
msgid "Time from last commit to merge"
msgstr ""
+msgid "Time in seconds"
+msgstr ""
+
msgid "Time in seconds GitLab will wait for a response from the external service. When the service does not respond in time, access will be denied."
msgstr ""
@@ -23513,9 +24259,15 @@ msgstr ""
msgid "Total artifacts size: %{total_size}"
msgstr ""
+msgid "Total cores (CPUs)"
+msgstr ""
+
msgid "Total issues"
msgstr ""
+msgid "Total memory (GB)"
+msgstr ""
+
msgid "Total test time for all commits/merges"
msgstr ""
@@ -23717,6 +24469,9 @@ msgstr ""
msgid "U2F only works with HTTPS-enabled websites. Contact your administrator for more details."
msgstr ""
+msgid "UI Development Kit"
+msgstr ""
+
msgid "URL"
msgstr ""
@@ -23789,9 +24544,6 @@ msgstr ""
msgid "Unable to load the merge request widget. Try reloading the page."
msgstr ""
-msgid "Unable to process group import file"
-msgstr ""
-
msgid "Unable to resolve"
msgstr ""
@@ -23807,6 +24559,9 @@ msgstr ""
msgid "Unable to sign you in to the group with SAML due to \"%{reason}\""
msgstr ""
+msgid "Unable to suggest a path. Please refresh and try again."
+msgstr ""
+
msgid "Unable to update label prioritization at this time"
msgstr ""
@@ -23822,6 +24577,9 @@ msgstr ""
msgid "Unarchiving the project will restore people's ability to make changes to it. The repository can be committed to, and issues, comments, and other entities can be created. %{strong_start}Once active, this project shows up in the search and on the dashboard.%{strong_end}"
msgstr ""
+msgid "Unassign from commenting user"
+msgstr ""
+
msgid "Unblock"
msgstr ""
@@ -23897,6 +24655,9 @@ msgstr ""
msgid "Unreachable"
msgstr ""
+msgid "Unrecognized cluster type"
+msgstr ""
+
msgid "Unresolve"
msgstr ""
@@ -23957,6 +24718,9 @@ msgstr ""
msgid "Unsubscribes from this %{quick_action_target}."
msgstr ""
+msgid "Unsupported todo type passed. Supported todo types are: %{todo_types}"
+msgstr ""
+
msgid "Until"
msgstr ""
@@ -23984,6 +24748,9 @@ msgstr ""
msgid "Update approval rule"
msgstr ""
+msgid "Update approvers"
+msgstr ""
+
msgid "Update failed"
msgstr ""
@@ -23993,6 +24760,9 @@ msgstr ""
msgid "Update it"
msgstr ""
+msgid "Update iteration"
+msgstr ""
+
msgid "Update now"
msgstr ""
@@ -24038,7 +24808,7 @@ msgstr ""
msgid "Updated at"
msgstr ""
-msgid "Updated to"
+msgid "Updated to %{linkStart}chart v%{linkEnd}"
msgstr ""
msgid "Updating"
@@ -24068,6 +24838,9 @@ msgstr ""
msgid "Upload CSV file"
msgstr ""
+msgid "Upload License"
+msgstr ""
+
msgid "Upload New File"
msgstr ""
@@ -24149,16 +24922,19 @@ msgstr ""
msgid "UsageQuota|Pipelines"
msgstr ""
+msgid "UsageQuota|Purchase more storage"
+msgstr ""
+
msgid "UsageQuota|Repositories"
msgstr ""
msgid "UsageQuota|Repository"
msgstr ""
-msgid "UsageQuota|Storage"
+msgid "UsageQuota|Snippets"
msgstr ""
-msgid "UsageQuota|Storage usage:"
+msgid "UsageQuota|Storage"
msgstr ""
msgid "UsageQuota|This namespace has no projects which use shared runners"
@@ -24191,6 +24967,12 @@ msgstr ""
msgid "UsageQuota|Wikis"
msgstr ""
+msgid "UsageQuota|You used: %{usage} %{limit}"
+msgstr ""
+
+msgid "UsageQuota|out of %{formattedLimit} of your namespace storage"
+msgstr ""
+
msgid "Use %{code_start}::%{code_end} to create a %{link_start}scoped label set%{link_end} (eg. %{code_start}priority::1%{code_end})"
msgstr ""
@@ -24257,6 +25039,9 @@ msgstr ""
msgid "User IDs"
msgstr ""
+msgid "User Lists can only be created and modified with %{linkStart}the API%{linkEnd}"
+msgstr ""
+
msgid "User OAuth applications"
msgstr ""
@@ -24281,6 +25066,9 @@ msgstr ""
msgid "User key was successfully removed."
msgstr ""
+msgid "User list %{name} will be removed. Are you sure?"
+msgstr ""
+
msgid "User map"
msgstr ""
@@ -24302,148 +25090,10 @@ msgstr ""
msgid "User was successfully updated."
msgstr ""
-msgid "UserOnboardingTour|%{activeTour}/%{totalTours}"
-msgstr ""
-
-msgid "UserOnboardingTour|%{completed}/%{total} steps completed"
-msgstr ""
-
-msgid "UserOnboardingTour|%{emphasisStart}Well done!%{emphasisEnd}%{lineBreak}%{lineBreak}That's it for our guided tour, congratulations for making it all the way to the end!%{lineBreak}%{lineBreak}We hope this gave you a good overview of GitLab and how it can help you. We'll now show you how to create your own project and invite your colleagues."
-msgstr ""
-
-msgid "UserOnboardingTour|Adding other members to a project is done through Project Settings. Click on %{emphasisStart}Settings%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Alright, that's it for Commits. Let's take a look at the %{emphasisStart}Branches%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Awesome! Now click on %{emphasisStart}Members%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Click on one of the %{emphasisStart}Compare%{emphasisEnd} buttons to compare a branch to master."
-msgstr ""
-
-msgid "UserOnboardingTour|Click on one of the %{emphasisStart}pipeline IDs%{emphasisEnd} to see the details of a pipeline."
-msgstr ""
-
-msgid "UserOnboardingTour|Click to open the latest commit to see its details."
-msgstr ""
-
-msgid "UserOnboardingTour|Close 'Learn GitLab'"
-msgstr ""
-
-msgid "UserOnboardingTour|Commits are shown in chronological order and can be filtered by the commit message or by the branch."
-msgstr ""
-
-msgid "UserOnboardingTour|Create a project"
-msgstr ""
-
-msgid "UserOnboardingTour|Exit 'Learn GitLab'"
-msgstr ""
-
-msgid "UserOnboardingTour|Got it"
-msgstr ""
-
-msgid "UserOnboardingTour|Great job! %{clapHands} We hope the tour was helpful and that you learned how to use GitLab.%{lineBreak}%{lineBreak}We'd love to get your feedback on this tour.%{lineBreak}%{lineBreak}%{emphasisStart}How helpful would you say this guided tour was?%{emphasisEnd}%{lineBreak}%{lineBreak}"
-msgstr ""
-
-msgid "UserOnboardingTour|Guided GitLab Tour"
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can compare the changes of this branch to another one. Changes are divided by files so that it's easier to see what was changed where."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can create a project from scratch, start with a template or import a repository from other platforms. Whatever you choose, we'll guide you through the process.%{lineBreak}%{lineBreak}Fill in your new project information and click on %{emphasisStart}Create Project%{emphasisEnd} to progress to the next step."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can see the breakdown of the pipelines: its stages and jobs in each of the stages and their status.%{lineBreak}%{lineBreak}Our CI/CD pipelines are quite complex, most of our users have fewer and simpler pipelines."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can see the current members of the project (just you at the moment) and invite new members.%{lineBreak}%{lineBreak}You can invite multiple members at once (existing GitLab users or invite by email) and you can also set their roles and permissions.%{lineBreak}%{lineBreak}Add a few members and click on %{emphasisStart}Add to project%{emphasisEnd} to complete this step."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can see what changes were made with this commit, on what branch and if there's a related merge request. The status of the pipeline will also show up if CI/CD is set up.%{lineBreak}%{lineBreak}You can also comment on the lines of code that were changed and start a discussion with your colleagues!"
-msgstr ""
-
-msgid "UserOnboardingTour|Here's an overview of branches in the %{emphasisStart}%{projectName}%{emphasisEnd} project. They're split into Active and Stale.%{lineBreak}%{lineBreak}From here, you can create a new merge request from a branch, or compare the branch to any other branch in the project. By default, it will compare it to the master branch."
-msgstr ""
-
-msgid "UserOnboardingTour|Invite colleagues"
-msgstr ""
-
-msgid "UserOnboardingTour|Issues are great for communicating and keeping track of progress in GitLab. These are all issues that are open in the %{emphasisStart}%{projectName}%{emphasisEnd}.%{lineBreak}%{lineBreak}You can help us improve GitLab by contributing work to issues that are labeled <span class=\"badge color-label accept-mr-label\">Accepting merge requests</span>.%{lineBreak}%{lineBreak}This list can be filtered by labels, milestones, assignees, authors... We'll show you how it looks when the list is filtered by a label."
-msgstr ""
-
-msgid "UserOnboardingTour|Learn GitLab"
-msgstr ""
-
-msgid "UserOnboardingTour|Let's take a closer look at a merge request. Click on the title of one."
-msgstr ""
-
-msgid "UserOnboardingTour|Let's take a closer look at all the commits. Click on %{emphasisStart}Commits%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Let's take a closer look at the repository of this project. Click on %{emphasisStart}Repository%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|No thanks"
-msgstr ""
-
-msgid "UserOnboardingTour|Ok, let's go"
-msgstr ""
-
-msgid "UserOnboardingTour|Ok, show me"
-msgstr ""
-
-msgid "UserOnboardingTour|Open one of the issues by clicking on its title."
-msgstr ""
-
-msgid "UserOnboardingTour|Restart this step"
-msgstr ""
-
-msgid "UserOnboardingTour|Skip this step"
-msgstr ""
-
-msgid "UserOnboardingTour|Sweet! Your project was created and is ready to be used.%{lineBreak}%{lineBreak}You can start adding files to the repository or clone it. One last thing we want to show you is how to invite your colleagues to your new project."
-msgstr ""
-
-msgid "UserOnboardingTour|Take a look. Here's a nifty menu for quickly creating issues, merge requests, snippets, projects and groups. Click on it and select \"New project\" from the \"GitLab\" section to get started."
-msgstr ""
-
-msgid "UserOnboardingTour|Thanks for taking the guided tour. Remember, if you want to go through it again, you can start %{emphasisStart}Learn GitLab%{emphasisEnd} in the help menu on the top right."
-msgstr ""
-
-msgid "UserOnboardingTour|Thanks for the feedback! %{thumbsUp}"
-msgstr ""
-
-msgid "UserOnboardingTour|That's it for issues. Let'st take a look at %{emphasisStart}Merge Requests%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|That's it for merge requests. Now for the final part of this guided tour - the %{emphasisStart}CI/CD%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|That's it for the Repository. Let's take a look at the %{emphasisStart}Issues%{emphasisEnd}."
+msgid "UserList|Delete %{name}?"
msgstr ""
-msgid "UserOnboardingTour|The structure of this page is very similar to issues. Status, description, discussion and the sidebar are all here.%{lineBreak}%{lineBreak}But take a look below the description and you'll notice that there's more information about the merge request, the CI/CD pipeline and the options for approving it.%{lineBreak}%{lineBreak}Alongside the discussion you can also see more information about commits in this merge request, the status of pipelines and review all changes that were made."
-msgstr ""
-
-msgid "UserOnboardingTour|There's a lot of information here but don't worry, we'll go through it.%{lineBreak}%{lineBreak}On the top you can see the status of the issue and when it was opened and by whom. Directly below it is the issue description and below that are other %{emphasisStart}related issues%{emphasisEnd} and %{emphasisStart}merge requests%{emphasisEnd} (if any). Then below that is the %{emphasisStart}discussion%{emphasisEnd}, that's where most of the communication happens.%{lineBreak}%{lineBreak}On the right, there's a sidebar where you can view/change the %{emphasisStart}assignee, milestone, due date, labels, weight%{emphasisEnd}, etc."
-msgstr ""
-
-msgid "UserOnboardingTour|These are all the CI/CD pipelines we have for our %{emphasisStart}%{projectName}%{emphasisEnd} project.%{lineBreak}%{lineBreak}Here you can see the status of each pipeline, for what commit it's running for, its stages and the status for them."
-msgstr ""
-
-msgid "UserOnboardingTour|These are all the issues that are available for community contributions. Let's take a closer look at one of them."
-msgstr ""
-
-msgid "UserOnboardingTour|This is an overview of all merge requests in this project. Similarly to the issues overview it can be filtered down by things like labels, milestones, authors, assignees, etc."
-msgstr ""
-
-msgid "UserOnboardingTour|This is the repository for the %{emphasisStart}%{projectName}%{emphasisEnd} project. All our code is stored here. Feel free to explore and take a closer look at folders and files.%{lineBreak}%{lineBreak}Above the file structure you can see the latest commit, who the author is and the status of the CI/CD pipeline.%{lineBreak}%{lineBreak}If you scroll down below the file structure, you'll find the Readme of this project. This is defined in the README.md file at the root of the repository."
-msgstr ""
-
-msgid "UserOnboardingTour|Welcome to the project overview of the %{emphasisStart}%{projectName}%{emphasisEnd} project. This is the project that we use to work on GitLab. At first, a project seems like a simple repository, but at GitLab, a project is so much more.%{lineBreak}%{lineBreak}You can create projects for hosting your codebase, use it as an issue tracker, collaborate on code, and continuously build, test, and deploy your app with built-in GitLab CI/CD."
+msgid "UserList|created %{timeago}"
msgstr ""
msgid "UserProfile|Activity"
@@ -24596,6 +25246,9 @@ msgstr ""
msgid "UsersSelect|Unassigned"
msgstr ""
+msgid "Using %{codeStart}needs%{codeEnd} allows jobs to run before their stage is reached, as soon as their individual dependencies are met, which speeds up your pipelines."
+msgstr ""
+
msgid "Using %{code_start}::%{code_end} denotes a %{link_start}scoped label set%{link_end}"
msgstr ""
@@ -24680,9 +25333,6 @@ msgstr ""
msgid "Versions"
msgstr ""
-msgid "Very helpful"
-msgstr ""
-
msgid "View Documentation"
msgstr ""
@@ -24833,6 +25483,9 @@ msgstr ""
msgid "VisibilityLevel|Unknown"
msgstr ""
+msgid "Visit settings page"
+msgstr ""
+
msgid "VisualReviewApp|%{stepStart}Step 1%{stepEnd}. Copy the following script:"
msgstr ""
@@ -24926,6 +25579,9 @@ msgstr ""
msgid "VulnerabilityManagement|Something went wrong while trying to delete the comment. Please try again later."
msgstr ""
+msgid "VulnerabilityManagement|Something went wrong while trying to refresh the vulnerability. Please try again later."
+msgstr ""
+
msgid "VulnerabilityManagement|Something went wrong while trying to retrieve the vulnerability history. Please try again later."
msgstr ""
@@ -24968,6 +25624,9 @@ msgstr ""
msgid "Vulnerability|Class"
msgstr ""
+msgid "Vulnerability|Crash Address"
+msgstr ""
+
msgid "Vulnerability|Description"
msgstr ""
@@ -25043,9 +25702,6 @@ msgstr ""
msgid "We couldn't reach the Prometheus server. Either the server no longer exists or the configuration details need updating."
msgstr ""
-msgid "We created a short guided tour that will help you learn the basics of GitLab and how it will help you be better at your job. It should only take a couple of minutes. You will be guided by two types of helpers, best recognized by their color."
-msgstr ""
-
msgid "We detected potential spam in the %{humanized_resource_name}. Please solve the reCAPTCHA to proceed."
msgstr ""
@@ -25079,6 +25735,9 @@ msgstr ""
msgid "We've found no vulnerabilities"
msgstr ""
+msgid "Web Application Firewall"
+msgstr ""
+
msgid "Web IDE"
msgstr ""
@@ -25139,9 +25798,6 @@ msgstr ""
msgid "Welcome to GitLab.com<br>@%{name}!"
msgstr ""
-msgid "Welcome to the Guided GitLab Tour"
-msgstr ""
-
msgid "Welcome to the guided GitLab tour"
msgstr ""
@@ -25190,9 +25846,6 @@ msgstr ""
msgid "While it's rare to have no vulnerabilities, it can happen. In any event, we ask that you please double check your settings to make sure you've set up your dashboard correctly."
msgstr ""
-msgid "White helpers give contextual information."
-msgstr ""
-
msgid "Who can be an approver?"
msgstr ""
@@ -25238,12 +25891,18 @@ msgstr ""
msgid "WikiEmptyIssueMessage|Suggest wiki improvement"
msgstr ""
+msgid "WikiEmptyIssueMessage|You must be a group member in order to add wiki pages. If you have suggestions for how to improve the wiki for this group, consider opening an issue in the %{issues_link}."
+msgstr ""
+
msgid "WikiEmptyIssueMessage|You must be a project member in order to add wiki pages. If you have suggestions for how to improve the wiki for this project, consider opening an issue in the %{issues_link}."
msgstr ""
msgid "WikiEmptyIssueMessage|issue tracker"
msgstr ""
+msgid "WikiEmpty|A wiki is where you can store all the details about your group. This can include why you've created it, its principles, how to use it, and so on."
+msgstr ""
+
msgid "WikiEmpty|A wiki is where you can store all the details about your project. This can include why you've created it, its principles, how to use it, and so on."
msgstr ""
@@ -25253,12 +25912,21 @@ msgstr ""
msgid "WikiEmpty|Suggest wiki improvement"
msgstr ""
+msgid "WikiEmpty|The wiki lets you write documentation for your group"
+msgstr ""
+
msgid "WikiEmpty|The wiki lets you write documentation for your project"
msgstr ""
+msgid "WikiEmpty|This group has no wiki pages"
+msgstr ""
+
msgid "WikiEmpty|This project has no wiki pages"
msgstr ""
+msgid "WikiEmpty|You must be a group member in order to add wiki pages."
+msgstr ""
+
msgid "WikiEmpty|You must be a project member in order to add wiki pages."
msgstr ""
@@ -25535,13 +26203,13 @@ msgstr ""
msgid "You can now export your security dashboard to a CSV report."
msgstr ""
-msgid "You can now submit a merge request to get this change into the original branch."
+msgid "You can now manage alert endpoint configuration in the Alerts section on the Operations settings page. Fields on this page have been deprecated."
msgstr ""
-msgid "You can now submit a merge request to get this change into the original project."
+msgid "You can now submit a merge request to get this change into the original branch."
msgstr ""
-msgid "You can only add files when you are on a branch"
+msgid "You can now submit a merge request to get this change into the original project."
msgstr ""
msgid "You can only edit files when you are on a branch"
@@ -25763,7 +26431,7 @@ msgstr ""
msgid "You need to upload a Google Takeout archive."
msgstr ""
-msgid "You reached %{usage_in_percent} of %{namespace_name}'s capacity (%{used_storage} of %{storage_limit})"
+msgid "You reached %{usage_in_percent} of %{namespace_name}'s storage capacity (%{used_storage} of %{storage_limit})"
msgstr ""
msgid "You tried to fork %{link_to_the_project} but it failed for the following reason:"
@@ -26066,13 +26734,13 @@ msgstr ""
msgid "Your subscription expired!"
msgstr ""
-msgid "Your subscription has been downgraded"
+msgid "Your subscription has been downgraded."
msgstr ""
-msgid "Your subscription will automatically renew in %{remaining_days}"
+msgid "Your subscription will automatically renew in %{remaining_days}."
msgstr ""
-msgid "Your subscription will expire in %{remaining_days}"
+msgid "Your subscription will expire in %{remaining_days}."
msgstr ""
msgid "Zoom meeting added"
@@ -26153,9 +26821,6 @@ msgstr ""
msgid "branch name"
msgstr ""
-msgid "build pipeline reference mismatch"
-msgstr ""
-
msgid "by"
msgstr ""
@@ -26174,6 +26839,9 @@ msgstr ""
msgid "cannot block others"
msgstr ""
+msgid "cannot contain HTML/XML tags, including any word between angle brackets (<,>)."
+msgstr ""
+
msgid "cannot include leading slash or directory traversal."
msgstr ""
@@ -26204,60 +26872,6 @@ msgstr ""
msgid "ciReport|%{remainingPackagesCount} more"
msgstr ""
-msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability"
-msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities"
-msgstr[0] ""
-msgstr[1] ""
-msgstr[2] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability for the source branch only"
-msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities for the source branch only"
-msgstr[0] ""
-msgstr[1] ""
-msgstr[2] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerability"
-msgid_plural "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerabilities"
-msgstr[0] ""
-msgstr[1] ""
-msgstr[2] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new vulnerability"
-msgid_plural "ciReport|%{reportType} %{status} detected %{newCount} new vulnerabilities"
-msgstr[0] ""
-msgstr[1] ""
-msgstr[2] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities for the source branch only"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{fixedCount} fixed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} vulnerability for the source branch only"
-msgid_plural "ciReport|%{reportType} %{status} detected %{newCount} vulnerabilities for the source branch only"
-msgstr[0] ""
-msgstr[1] ""
-msgstr[2] ""
-
-msgid "ciReport|%{reportType} %{status} detected no new vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected no vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected no vulnerabilities for the source branch only"
-msgstr ""
-
msgid "ciReport|%{reportType} is loading"
msgstr ""
@@ -26276,7 +26890,7 @@ msgstr ""
msgid "ciReport|All projects"
msgstr ""
-msgid "ciReport|All report types"
+msgid "ciReport|All scanner types"
msgstr ""
msgid "ciReport|All severities"
@@ -26300,6 +26914,9 @@ msgstr ""
msgid "ciReport|Container scanning detects known vulnerabilities in your docker images."
msgstr ""
+msgid "ciReport|Coverage Fuzzing"
+msgstr ""
+
msgid "ciReport|Create a merge request to implement this solution, or download and apply the patch manually."
msgstr ""
@@ -26459,6 +27076,9 @@ msgstr ""
msgid "customize"
msgstr ""
+msgid "data"
+msgstr ""
+
msgid "date must not be after 9999-12-31"
msgstr ""
@@ -26531,6 +27151,9 @@ msgstr ""
msgid "entries cannot contain HTML tags"
msgstr ""
+msgid "epic"
+msgstr ""
+
msgid "error"
msgstr ""
@@ -26552,9 +27175,6 @@ msgstr ""
msgid "expires on %{timebox_due_date}"
msgstr ""
-msgid "external_url"
-msgstr ""
-
msgid "failed"
msgstr ""
@@ -26591,9 +27211,6 @@ msgstr ""
msgid "from"
msgstr ""
-msgid "geo_node_name"
-msgstr ""
-
msgid "group"
msgstr ""
@@ -26615,9 +27232,6 @@ msgstr ""
msgid "https://your-bitbucket-server"
msgstr ""
-msgid "image"
-msgstr ""
-
msgid "image diff"
msgstr ""
@@ -26762,6 +27376,9 @@ msgstr ""
msgid "load it anyway"
msgstr ""
+msgid "loading"
+msgstr ""
+
msgid "locked by %{path_lock_user_name} %{created_at}"
msgstr ""
@@ -26891,9 +27508,6 @@ msgstr ""
msgid "mrWidget|Deployment statistics are not available currently"
msgstr ""
-msgid "mrWidget|Detect issues before deployment with a CI pipeline that continuously tests your code. We created a quick guide that will show you how to create one. Make your code more secure and more robust in just a minute."
-msgstr ""
-
msgid "mrWidget|Did not close"
msgstr ""
@@ -27056,6 +27670,9 @@ msgstr ""
msgid "mrWidget|To approve this merge request, please enter your password. This project requires all approvals to be authenticated."
msgstr ""
+msgid "mrWidget|Use %{linkStart}CI pipelines to test your code%{linkEnd}, simply add a GitLab CI configuration file to your project. It only takes a minute to make your code more secure and robust."
+msgstr ""
+
msgid "mrWidget|When this merge request is ready, remove the WIP: prefix from the title to allow it to be merged"
msgstr ""
@@ -27092,6 +27709,9 @@ msgstr ""
msgid "must be greater than start date"
msgstr ""
+msgid "must contain only valid frameworks"
+msgstr ""
+
msgid "my-awesome-group"
msgstr ""
@@ -27104,6 +27724,9 @@ msgstr ""
msgid "needs to be between 10 minutes and 1 month"
msgstr ""
+msgid "never"
+msgstr ""
+
msgid "never expires"
msgstr ""
@@ -27131,6 +27754,9 @@ msgstr ""
msgid "nounSeries|%{firstItem} and %{lastItem}"
msgstr ""
+msgid "nounSeries|%{item}"
+msgstr ""
+
msgid "nounSeries|%{item}, %{nextItem}"
msgstr ""
@@ -27272,6 +27898,9 @@ msgstr ""
msgid "revised"
msgstr ""
+msgid "satisfied"
+msgstr ""
+
msgid "score"
msgstr ""
@@ -27302,6 +27931,9 @@ msgstr ""
msgid "severity|Unknown"
msgstr ""
+msgid "should be an array of %{object_name} objects"
+msgstr ""
+
msgid "should be greater than or equal to %{access} inherited membership from group %{group_name}"
msgstr ""
@@ -27356,10 +27988,13 @@ msgstr ""
msgid "suggestPipeline|2/2: Commit your changes"
msgstr ""
-msgid "suggestPipeline|Commit the changes and your pipeline will automatically run for the first time."
+msgid "suggestPipeline|Choose %{boldStart}Code Quality%{boldEnd} to add a pipeline that tests the quality of your code."
msgstr ""
-msgid "suggestPipeline|We recommend the %{boldStart}Code Quality%{boldEnd} template, which will add a report widget to your Merge Requests. This way you’ll learn about code quality degradations much sooner. %{footerStart} Goodbye technical debt! %{footerEnd}"
+msgid "suggestPipeline|The template is ready! You can now commit it to create your first pipeline."
+msgstr ""
+
+msgid "suggestPipeline|We’re adding a GitLab CI configuration file to add a pipeline to the project. You could create it manually, but we recommend that you start with a GitLab template that works out of the box."
msgstr ""
msgid "syntax is correct"
@@ -27419,6 +28054,9 @@ msgstr ""
msgid "uses Kubernetes clusters to deploy your code!"
msgstr ""
+msgid "v%{version} published %{timeAgo}"
+msgstr ""
+
msgid "verify ownership"
msgstr ""
diff --git a/locale/ru/gitlab.po b/locale/ru/gitlab.po
index af2077a89d0..976f9f46f50 100644
--- a/locale/ru/gitlab.po
+++ b/locale/ru/gitlab.po
@@ -2,7 +2,7 @@ msgid ""
msgstr ""
"Project-Id-Version: gitlab-ee\n"
"Report-Msgid-Bugs-To: \n"
-"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Last-Translator: \n"
"Language-Team: Russian\n"
"Language: ru_RU\n"
"MIME-Version: 1.0\n"
@@ -10,9 +10,11 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=4; plural=((n%10==1 && n%100!=11) ? 0 : ((n%10 >= 2 && n%10 <=4 && (n%100 < 12 || n%100 > 14)) ? 1 : ((n%10 == 0 || (n%10 >= 5 && n%10 <=9)) || (n%100 >= 11 && n%100 <= 14)) ? 2 : 3));\n"
"X-Crowdin-Project: gitlab-ee\n"
+"X-Crowdin-Project-ID: 288872\n"
"X-Crowdin-Language: ru\n"
"X-Crowdin-File: /master/locale/gitlab.pot\n"
-"PO-Revision-Date: 2020-06-08 15:19\n"
+"X-Crowdin-File-ID: 6\n"
+"PO-Revision-Date: 2020-07-02 01:27\n"
msgid " %{start} to %{end}"
msgstr " %{start} по %{end}"
@@ -82,17 +84,17 @@ msgstr[3] "%d URL проÑканировано"
msgid "%d approver"
msgid_plural "%d approvers"
-msgstr[0] ""
-msgstr[1] ""
-msgstr[2] ""
-msgstr[3] ""
+msgstr[0] "%d утверждающий"
+msgstr[1] "%d утверждающих"
+msgstr[2] "%d утверждающих"
+msgstr[3] "%d утверждающих"
msgid "%d approver (you've approved)"
msgid_plural "%d approvers (you've approved)"
-msgstr[0] ""
-msgstr[1] ""
-msgstr[2] ""
-msgstr[3] ""
+msgstr[0] "%d утверждающий (вы утвердили)"
+msgstr[1] "%d утверждающих (вы утвердили)"
+msgstr[2] "%d утверждающих (вы утвердили)"
+msgstr[3] "%d утверждающих (вы утвердили)"
msgid "%d changed file"
msgid_plural "%d changed files"
@@ -124,10 +126,10 @@ msgstr[3] "%d комментариев"
msgid "%d comment on this commit"
msgid_plural "%d comments on this commit"
-msgstr[0] ""
-msgstr[1] ""
-msgstr[2] ""
-msgstr[3] ""
+msgstr[0] "%d комментарий к Ñтому коммиту"
+msgstr[1] "%d ÐºÐ¾Ð¼Ð¼ÐµÐ½Ñ‚Ð°Ñ€Ð¸Ñ Ðº Ñтому коммиту"
+msgstr[2] "%d комментариев к Ñтому коммиту"
+msgstr[3] "%d комментариев к Ñтому коммиту"
msgid "%d commit"
msgid_plural "%d commits"
@@ -251,6 +253,13 @@ msgstr[1] "%d метрики"
msgstr[2] "%d метрик"
msgstr[3] "%d метрик"
+msgid "%d milestone"
+msgid_plural "%d milestones"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
msgid "%d minute"
msgid_plural "%d minutes"
msgstr[0] "%d минута"
@@ -314,13 +323,6 @@ msgstr[1] "%d нерешённые темы"
msgstr[2] "%d нерешённых тем"
msgstr[3] "%d нерешённых тем"
-msgid "%d url scanned"
-msgid_plural "%d urls scanned"
-msgstr[0] ""
-msgstr[1] ""
-msgstr[2] ""
-msgstr[3] ""
-
msgid "%d vulnerability"
msgid_plural "%d vulnerabilities"
msgstr[0] ""
@@ -380,7 +382,7 @@ msgid "%{count} more"
msgstr "ещё %{count}"
msgid "%{count} more assignees"
-msgstr "Ещё %{count} иÑполнителей"
+msgstr "Ещё %{count} ответÑтвенных"
msgid "%{count} more release"
msgid_plural "%{count} more releases"
@@ -412,9 +414,15 @@ msgstr[3] "%{count} ожидающих комментариев"
msgid "%{count} related %{pluralized_subject}: %{links}"
msgstr "%{count} ÑвÑзанный %{pluralized_subject}: %{links}"
+msgid "%{dashboard_path} could not be found."
+msgstr ""
+
msgid "%{days} days until tags are automatically removed"
msgstr "%{days} дней до автоматичеÑкого ÑƒÐ´Ð°Ð»ÐµÐ½Ð¸Ñ Ñ‚ÐµÐ³Ð¾Ð²"
+msgid "%{deployLinkStart}Use a template to deploy to ECS%{deployLinkEnd}, or use a docker image to %{commandsLinkStart}run AWS commands in GitLab CI/CD%{commandsLinkEnd}."
+msgstr ""
+
msgid "%{description}- Sentry event: %{errorUrl}- First seen: %{firstSeen}- Last seen: %{lastSeen} %{countLabel}: %{count}%{userCountLabel}: %{userCount}"
msgstr "%{description}- Событие Sentry: %{errorUrl}- Первый проÑмотр: %{firstSeen}- ПоÑледний проÑмотр: %{lastSeen} %{countLabel}: %{count}%{userCountLabel}: %{userCount}"
@@ -457,12 +465,18 @@ msgstr "%{group_name} иÑпользует управлÑемые группов
msgid "%{host} sign-in from new location"
msgstr ""
-msgid "%{icon}You are about to add %{usersTag} people to the discussion. Proceed with caution."
-msgstr "%{icon} Ð’Ñ‹ ÑобираетеÑÑŒ добавить %{usersTag} людей к обÑуждению. Будьте оÑторожны."
+msgid "%{icon}You are about to add %{usersTag} people to the discussion. They will all receive a notification."
+msgstr ""
+
+msgid "%{integrations_link_start}Integrations%{link_end} enable you to make third-party applications part of your GitLab workflow. If the available integrations don't meet your needs, consider using a %{webhooks_link_start}webhook%{link_end}."
+msgstr ""
msgid "%{issuableType} will be removed! Are you sure?"
msgstr "%{issuableType} будет удален! Вы уверены?"
+msgid "%{issuesCount} issues in this group"
+msgstr ""
+
msgid "%{issuesSize} issues"
msgstr "%{issuesSize} обÑуждений"
@@ -472,7 +486,7 @@ msgstr "%{issuesSize} обÑуждение Ñ Ð»Ð¸Ð¼Ð¸Ñ‚Ð¾Ð¼ в %{maxIssueCount}
msgid "%{labelStart}Class:%{labelEnd} %{class}"
msgstr ""
-msgid "%{labelStart}Confidence:%{labelEnd} %{confidence}"
+msgid "%{labelStart}Evidence:%{labelEnd} %{evidence}"
msgstr ""
msgid "%{labelStart}File:%{labelEnd} %{file}"
@@ -490,6 +504,9 @@ msgstr ""
msgid "%{labelStart}Report Type:%{labelEnd} %{reportType}"
msgstr ""
+msgid "%{labelStart}Scanner:%{labelEnd} %{scanner}"
+msgstr ""
+
msgid "%{labelStart}Severity:%{labelEnd} %{severity}"
msgstr ""
@@ -514,9 +531,6 @@ msgstr "%{link_start}Узнайте больше%{link_end} о том, какаÑ
msgid "%{link_start}Read more%{link_end} about role permissions"
msgstr "%{link_start}Подробнее%{link_end} о ролевом доÑтупе"
-msgid "%{link} can be used for binding events when something is happening within the project."
-msgstr "%{link} могут быть иÑпользованы Ð´Ð»Ñ Ð¿Ñ€Ð¸Ð²Ñзки Ñобытий, когда что-то проиÑходит в проекте."
-
msgid "%{listToShow}, and %{awardsListLength} more."
msgstr "%{listToShow}, и еще %{awardsListLength}."
@@ -550,6 +564,9 @@ msgstr "%{name} Ñодержал %{resultsString}"
msgid "%{name} found %{resultsString}"
msgstr "%{name} нашел %{resultsString}"
+msgid "%{name} is already being used for another emoji"
+msgstr ""
+
msgid "%{name} is scheduled for %{action}"
msgstr "%{name} запланирован на %{action}"
@@ -599,6 +616,9 @@ msgstr "%{placeholder} не ÑвлÑетÑÑ Ð´Ð¾Ð¿ÑƒÑтимой темой"
msgid "%{primary} (%{secondary})"
msgstr "%{primary} (%{secondary})"
+msgid "%{ref} cannot be added: %{error}"
+msgstr ""
+
msgid "%{releases} release"
msgid_plural "%{releases} releases"
msgstr[0] "%{releases} релиз"
@@ -609,14 +629,44 @@ msgstr[3] "%{releases} релизов"
msgid "%{remaining_approvals} left"
msgstr ""
-msgid "%{retryButtonStart}Try again%{retryButtonEnd} or %{newFileButtonStart}attach a new file%{newFileButtonEnd}"
+msgid "%{reportType} %{status} detected %{critical} critical and %{high} high severity vulnerabilities out of %{total}."
msgstr ""
-msgid "%{screenreaderOnlyStart}Keyboard shorcuts%{screenreaderOnlyEnd} Disabled"
-msgstr "%{screenreaderOnlyStart}Ð¡Ð¾Ñ‡ÐµÑ‚Ð°Ð½Ð¸Ñ ÐºÐ»Ð°Ð²Ð¸Ñˆ%{screenreaderOnlyEnd} отключены"
+msgid "%{reportType} %{status} detected %{critical} critical and %{high} high severity vulnerabilities."
+msgstr ""
+
+msgid "%{reportType} %{status} detected %{critical} critical severity vulnerabilities out of %{total}."
+msgstr ""
-msgid "%{screenreaderOnlyStart}Keyboard shorcuts%{screenreaderOnlyEnd} Enabled"
-msgstr "%{screenreaderOnlyStart}Ð¡Ð¾Ñ‡ÐµÑ‚Ð°Ð½Ð¸Ñ ÐºÐ»Ð°Ð²Ð¸Ñˆ%{screenreaderOnlyEnd} включены"
+msgid "%{reportType} %{status} detected %{critical} critical severity vulnerability."
+msgid_plural "%{reportType} %{status} detected %{critical} critical severity vulnerabilities."
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "%{reportType} %{status} detected %{high} high severity vulnerabilities out of %{total}."
+msgstr ""
+
+msgid "%{reportType} %{status} detected %{high} high severity vulnerability."
+msgid_plural "%{reportType} %{status} detected %{high} high severity vulnerabilities."
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "%{reportType} %{status} detected %{other} vulnerability."
+msgid_plural "%{reportType} %{status} detected %{other} vulnerabilities."
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "%{reportType} %{status} detected no new vulnerabilities."
+msgstr ""
+
+msgid "%{retryButtonStart}Try again%{retryButtonEnd} or %{newFileButtonStart}attach a new file%{newFileButtonEnd}"
+msgstr ""
msgid "%{service_title} %{message}."
msgstr "%{service_title} %{message}."
@@ -722,6 +772,12 @@ msgstr "Ð˜Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ð² %{title}"
msgid "%{token}..."
msgstr "%{token}..."
+msgid "%{totalCpu} (%{freeSpacePercentage}%{percentSymbol} free)"
+msgstr ""
+
+msgid "%{totalMemory} (%{freeSpacePercentage}%{percentSymbol} free)"
+msgstr ""
+
msgid "%{totalWeight} total weight"
msgstr "общий приоритет %{totalWeight}"
@@ -752,6 +808,12 @@ msgstr "%{value} Ñ"
msgid "%{verb} %{time_spent_value} spent time."
msgstr "%{verb} %{time_spent_value} потраченного времени."
+msgid "%{webhooks_link_start}%{webhook_type}%{link_end} enable you to send notifications to web applications in response to events in a group or project."
+msgstr ""
+
+msgid "%{webhooks_link_start}%{webhook_type}%{link_end} enable you to send notifications to web applications in response to events in a group or project. We recommend using an %{integrations_link_start}integration%{link_end} in preference to a webhook."
+msgstr ""
+
msgid "'%{level}' is not a valid visibility level"
msgstr ""
@@ -780,9 +842,6 @@ msgstr "(%{mrCount} объединено)"
msgid "(No changes)"
msgstr "(Без изменений)"
-msgid "(Show all)"
-msgstr "(Показать вÑе)"
-
msgid "(check progress)"
msgstr "(прогреÑÑ Ð¿Ñ€Ð¾Ð²ÐµÑ€ÐºÐ¸)"
@@ -808,7 +867,7 @@ msgid "+ %{moreCount} more"
msgstr "+ ещё %{moreCount}"
msgid "+ %{numberOfHiddenAssignees} more"
-msgstr "+ %{numberOfHiddenAssignees} больше"
+msgstr "+ещё %{numberOfHiddenAssignees}"
msgid "+%d more"
msgid_plural "+%d more"
@@ -855,6 +914,9 @@ msgstr "- Ñвернуть"
msgid "0 for unlimited"
msgstr "0 — без ограничений"
+msgid "0 for unlimited, only effective with remote storage enabled."
+msgstr ""
+
msgid "1 %{type} addition"
msgid_plural "%{count} %{type} additions"
msgstr[0] "1 дополнение типа %{type}"
@@ -1021,7 +1083,7 @@ msgid "< 1 hour"
msgstr "< 1 чаÑа"
msgid "<code>\"johnsmith@example.com\": \"@johnsmith\"</code> will add \"By <a href=\"#\">@johnsmith</a>\" to all issues and comments originally created by johnsmith@example.com, and will set <a href=\"#\">@johnsmith</a> as the assignee on all issues originally assigned to johnsmith@example.com."
-msgstr "<code>\"johnsmith@example.com\": \"@johnsmith\"</code> добавил \"<a href=\"#\">@johnsmith</a>\" По вÑем вопроÑам и комментариÑм, изначально Ñозданным johnsmith@example.com, и уÑтановил <a href=\"#\">@johnsmith</a> в качеÑтве правопреемника по вÑем задачам первоначально назначен johnsmith@example.com."
+msgstr "<code>\"johnsmith@example.com\": \"@johnsmith\"</code> добавил \"<a href=\"#\">@johnsmith</a>\" ко вÑем обÑуждениÑм и комментариÑм, изначально Ñозданным johnsmith@example.com, и уÑтановил <a href=\"#\">@johnsmith</a> в качеÑтве ответÑтвенного по вÑем обÑуждениÑм, на которые изначально был назначен johnsmith@example.com."
msgid "<code>\"johnsmith@example.com\": \"John Smith\"</code> will add \"By John Smith\" to all issues and comments originally created by johnsmith@example.com."
msgstr "<code>\"johnsmith@example.com\": \"John Smith\"</code> добавит \"От John Smith\" ко вÑем обÑуждениÑм и комментариÑм, Ñозданным johnsmith@example.com."
@@ -1032,6 +1094,12 @@ msgstr "<code>\"johnsmith@example.com\": \"johnsm...@example.com\"</code> доб
msgid "<code>\"johnsmith@example.com\": \"johnsmith@example.com\"</code> will add \"By <a href=\"#\">johnsmith@example.com</a>\" to all issues and comments originally created by johnsmith@example.com. By default, the email address or username is masked to ensure the user's privacy. Use this option if you want to show the full email address."
msgstr "<code>\"johnsmith@example.com\": \"johnsmith@example.com\"</code> добавит \"От <a href=\"#\">johnsmith@example.com</a>\" ко вÑем обÑуждениÑм и комментариÑм, Ñозданным johnsmith@example.com. По умолчанию Ð°Ð´Ñ€ÐµÑ Ð¿Ð¾Ñ‡Ñ‚Ñ‹ и Ð¸Ð¼Ñ Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ ÑкрываютÑÑ Ð´Ð»Ñ Ð¾Ð±ÐµÑÐ¿ÐµÑ‡ÐµÐ½Ð¸Ñ ÐºÐ¾Ð½Ñ„Ð¸Ð´ÐµÐ½Ñ†Ð¸Ð°Ð»ÑŒÐ½Ð¾Ñти. ИÑпользуйте Ñту опцию, еÑли хотите показывать полный Ð°Ð´Ñ€ÐµÑ Ñлектронной почты."
+msgid "<code>Masked</code> to prevent the values from being displayed in job logs (must match certain regexp requirements)."
+msgstr ""
+
+msgid "<code>Protected</code> to expose them to protected branches or tags only."
+msgstr ""
+
msgid "<namespace / project>"
msgstr ""
@@ -1059,9 +1127,6 @@ msgstr "«Runner» - Ñто процеÑÑ, который выполнÑет Ñ
msgid "A .NET Core console application template, customizable for any .NET Core project"
msgstr "Шаблон конÑольного Ð¿Ñ€Ð¸Ð»Ð¾Ð¶ÐµÐ½Ð¸Ñ .NET Core, наÑтраиваемый Ð´Ð»Ñ Ð»ÑŽÐ±Ð¾Ð³Ð¾ проекта .NET Core"
-msgid "A DAG must have two dependent jobs to be visualized on this tab."
-msgstr ""
-
msgid "A GitBook site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
msgstr "Сайт GitBook, который иÑпользует Netlify Ð´Ð»Ñ CI/CD вмеÑто GitLab, но вÑÑ‘ ещё Ñ Ð´Ñ€ÑƒÐ³Ð¸Ð¼Ð¸ замечательными возможноÑÑ‚Ñми GitLab."
@@ -1092,6 +1157,12 @@ msgstr "Ð”Ð»Ñ Ð¿ÑƒÑтого проекта Ð½ÐµÐ»ÑŒÐ·Ñ Ð²Ñ‹Ð±Ñ€Ð°Ñ‚ÑŒ ветÐ
msgid "A deleted user"
msgstr "Удаленный пользователь"
+msgid "A file has been changed."
+msgstr ""
+
+msgid "A file was not found."
+msgstr ""
+
msgid "A file with '%{file_name}' already exists in %{branch} branch"
msgstr "Файл '%{file_name}' уже ÑущеÑтвует в ветке %{branch}"
@@ -1158,8 +1229,8 @@ msgstr ""
msgid "A subscription will trigger a new pipeline on the default branch of this project when a pipeline successfully completes for a new tag on the %{default_branch_docs} of the subscribed project."
msgstr ""
-msgid "A terraform report was generated in your pipelines."
-msgstr "Отчёт Terraform был Ñгенерирован в ваших Ñборочных линиÑÑ…."
+msgid "A suggestion is not applicable."
+msgstr ""
msgid "A user with write access to the source branch selected this option"
msgstr "Пользователь Ñ Ñ€Ð°Ð·Ñ€ÐµÑˆÐµÐ½Ð¸ÐµÐ¼ на запиÑÑŒ в ветку иÑточника выбрал Ñтот вариант"
@@ -1329,6 +1400,9 @@ msgstr "Ðккаунт: %{account}"
msgid "Action to take when receiving an alert."
msgstr "ДейÑтвиÑ, предпринимаемые при получении оповещениÑ."
+msgid "Actions"
+msgstr ""
+
msgid "Activate"
msgstr "Ðктивировать"
@@ -1387,6 +1461,9 @@ msgstr "Добавить клаÑтер Kubernetes"
msgid "Add LICENSE"
msgstr "Добавить LICENSE"
+msgid "Add New Node"
+msgstr ""
+
msgid "Add README"
msgstr "Добавить README"
@@ -1430,7 +1507,7 @@ msgid "Add a link"
msgstr "Добавить ÑÑылку"
msgid "Add a new issue"
-msgstr ""
+msgstr "Добавить новое обÑуждение"
msgid "Add a numbered list"
msgstr "Добавить нумерованный ÑпиÑок"
@@ -1462,6 +1539,9 @@ msgstr "Добавить ещё ÑÑылку"
msgid "Add approval rule"
msgstr "Добавить правило утверждениÑ"
+msgid "Add approvers"
+msgstr ""
+
msgid "Add bold text"
msgstr "Добавить жирный текÑÑ‚"
@@ -1519,6 +1599,9 @@ msgstr "Добавить Ð·Ð°Ð¿Ñ€Ð¾Ñ Ð²Ñ€ÑƒÑ‡Ð½ÑƒÑŽ"
msgid "Add strikethrough text"
msgstr ""
+msgid "Add suggestion to batch"
+msgstr ""
+
msgid "Add system hook"
msgstr "Добавить ÑиÑтемный обработчик"
@@ -1613,11 +1696,14 @@ msgid "Adjust your filters/search criteria above. If you believe this may be an
msgstr ""
msgid "Admin Area"
-msgstr "Ðдмин панель"
+msgstr "Панель управлениÑ"
msgid "Admin Note"
msgstr ""
+msgid "Admin Notifications"
+msgstr ""
+
msgid "Admin Overview"
msgstr "Обзор ÐдминиÑтратора"
@@ -1729,6 +1815,9 @@ msgstr "Перейти к общим наÑтройкам"
msgid "AdminSettings|Integrations configured here will automatically apply to all projects on this instance."
msgstr "Интеграции, наÑтроенные здеÑÑŒ, будут автоматичеÑки применÑÑ‚ÑŒÑÑ ÐºÐ¾ вÑем проектам на Ñтом ÑкземплÑре."
+msgid "AdminSettings|Moved to integrations"
+msgstr ""
+
msgid "AdminSettings|No required pipeline"
msgstr "Ðе требуетÑÑ ÑÐ±Ð¾Ñ€Ð¾Ñ‡Ð½Ð°Ñ Ð»Ð¸Ð½Ð¸Ñ"
@@ -1961,12 +2050,18 @@ msgstr[1] "ОповещениÑ"
msgstr[2] "Оповещений"
msgstr[3] "Оповещений"
+msgid "AlertManagement| assign yourself"
+msgstr ""
+
msgid "AlertManagement|Acknowledged"
msgstr "Прочитано"
msgid "AlertManagement|Alert"
msgstr "Оповещение"
+msgid "AlertManagement|Alert assignee(s): %{assignees}"
+msgstr ""
+
msgid "AlertManagement|Alert detail"
msgstr ""
@@ -1982,17 +2077,23 @@ msgstr ""
msgid "AlertManagement|All alerts"
msgstr ""
+msgid "AlertManagement|Assign To"
+msgstr ""
+
msgid "AlertManagement|Assign status"
msgstr ""
-msgid "AlertManagement|Assignees"
+msgid "AlertManagement|Assignee"
msgstr ""
+msgid "AlertManagement|Assignees"
+msgstr "ОтветÑтвенные"
+
msgid "AlertManagement|Authorize external service"
msgstr "Ðвторизовать внешнюю Ñлужбу"
msgid "AlertManagement|Create issue"
-msgstr ""
+msgstr "Создать обÑуждение"
msgid "AlertManagement|Critical"
msgstr ""
@@ -2003,9 +2104,6 @@ msgstr "Выводите ÑƒÐ²ÐµÐ´Ð¾Ð¼Ð»ÐµÐ½Ð¸Ñ Ð¾Ñ‚ вÑех Ñвоих инÑÑ
msgid "AlertManagement|Edit"
msgstr ""
-msgid "AlertManagement|End time"
-msgstr "Ð’Ñ€ÐµÐ¼Ñ Ð¾ÐºÐ¾Ð½Ñ‡Ð°Ð½Ð¸Ñ"
-
msgid "AlertManagement|Events"
msgstr "СобытиÑ"
@@ -2024,11 +2122,14 @@ msgstr ""
msgid "AlertManagement|More information"
msgstr "Ð”Ð¾Ð¿Ð¾Ð»Ð½Ð¸Ñ‚ÐµÐ»ÑŒÐ½Ð°Ñ Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ"
+msgid "AlertManagement|No Matching Results"
+msgstr ""
+
msgid "AlertManagement|No alert data to display."
msgstr ""
-msgid "AlertManagement|No alerts available to display. If you think you're seeing this message in error, refresh the page."
-msgstr "Ðет оповещений Ð´Ð»Ñ Ð¾Ñ‚Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð¸Ñ. ЕÑли, по-вашему, вы видите Ñто Ñообщение по ошибке, обновите Ñтраницу."
+msgid "AlertManagement|No alerts available to display. See %{linkStart}enabling alert management%{linkEnd} for more information on adding alerts to the list."
+msgstr ""
msgid "AlertManagement|No alerts to display."
msgstr "Ðет оповещений Ð´Ð»Ñ Ð¾Ñ‚Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð¸Ñ."
@@ -2036,6 +2137,9 @@ msgstr "Ðет оповещений Ð´Ð»Ñ Ð¾Ñ‚Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð¸Ñ."
msgid "AlertManagement|None"
msgstr ""
+msgid "AlertManagement|None -"
+msgstr ""
+
msgid "AlertManagement|Open"
msgstr ""
@@ -2072,9 +2176,18 @@ msgstr ""
msgid "AlertManagement|There was an error displaying the alerts. Confirm your endpoint's configuration details to ensure alerts appear."
msgstr "Произошла ошибка при отображении оповещений. Проверьте наÑтройки конечной точки, чтобы убедитьÑÑ, что Ð¾Ð¿Ð¾Ð²ÐµÑ‰ÐµÐ½Ð¸Ñ Ð¿Ð¾ÐºÐ°Ð·Ñ‹Ð²Ð°ÑŽÑ‚ÑÑ."
+msgid "AlertManagement|There was an error while updating the assignee(s) list. Please try again."
+msgstr ""
+
+msgid "AlertManagement|There was an error while updating the assignee(s) of the alert. Please try again."
+msgstr ""
+
msgid "AlertManagement|There was an error while updating the status of the alert. Please try again."
msgstr ""
+msgid "AlertManagement|This assignee cannot be assigned to this alert."
+msgstr ""
+
msgid "AlertManagement|Tool"
msgstr ""
@@ -2088,7 +2201,7 @@ msgid "AlertManagement|Unknown"
msgstr ""
msgid "AlertManagement|View issue"
-msgstr ""
+msgstr "Открыть обÑуждение"
msgid "AlertService|Review your external service's documentation to learn where to provide this information to your external service, and the %{linkStart}GitLab documentation%{linkEnd} to learn more about configuring your endpoint."
msgstr ""
@@ -2214,7 +2327,7 @@ msgid "Allow this secondary node to replicate content on Object Storage"
msgstr ""
msgid "Allow users to dismiss the broadcast message"
-msgstr "ПозволÑет пользователÑм отклонÑÑ‚ÑŒ раÑÑылаемое Ñообщение"
+msgstr "Разрешить пользователÑм закрыть Ñообщение"
msgid "Allow users to register any application to use GitLab as an OAuth provider"
msgstr "Разрешить пользователÑм региÑтрировать любое приложение Ð´Ð»Ñ Ð¸ÑÐ¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ð½Ð¸Ñ GitLab как провайдера OAuth"
@@ -2222,6 +2335,9 @@ msgstr "Разрешить пользователÑм региÑтрироват
msgid "Allow users to request access (if visibility is public or internal)"
msgstr "Разрешить пользователÑм запрашивать доÑтуп (еÑли видимоÑÑ‚ÑŒ ÑвлÑетÑÑ Ð¾Ð±Ñ‰ÐµÐ´Ð¾Ñтупной или внутренней)"
+msgid "Allowed Geo IP"
+msgstr ""
+
msgid "Allowed email domain restriction only permitted for top-level groups"
msgstr "Разрешено ограничение доменов Ñлектронной почты только Ð´Ð»Ñ Ð³Ñ€ÑƒÐ¿Ð¿ верхнего уровнÑ"
@@ -2255,6 +2371,9 @@ msgstr "Ð˜Ð½Ñ‚ÐµÐ³Ñ€Ð°Ñ†Ð¸Ñ Ñ Amazon EKS позволÑет вам предоÑ
msgid "Amazon Web Services"
msgstr "Amazon Web Services"
+msgid "Amazon Web Services Logo"
+msgstr ""
+
msgid "Amazon authentication is not %{link_start}correctly configured%{link_end}. Ask your GitLab administrator if you want to use this service."
msgstr "ÐÑƒÑ‚ÐµÐ½Ñ‚Ð¸Ñ„Ð¸ÐºÐ°Ñ†Ð¸Ñ Amazon не %{link_start}наÑтроена%{link_end}. ОбратитеÑÑŒ к Ñвоему админиÑтратору GitLab, еÑли хотите иÑпользовать Ñту Ñлужбу."
@@ -2289,7 +2408,7 @@ msgid "An error occurred fetching the approval rules."
msgstr "Произошла ошибка при выборке правил утверждениÑ."
msgid "An error occurred fetching the approvers for the new rule."
-msgstr ""
+msgstr "Произошла ошибка при извлечении утверждающий Ð´Ð»Ñ Ð½Ð¾Ð²Ð¾Ð³Ð¾ правила."
msgid "An error occurred fetching the dropdown data."
msgstr "Произошла ошибка при извлечении данных выпадающего ÑпиÑка."
@@ -2312,11 +2431,17 @@ msgstr "Произошла ошибка при попытке разрешить
msgid "An error occurred when updating the issue weight"
msgstr "Произошла ошибка при обновлении приоритета обÑуждениÑ"
+msgid "An error occurred while acknowledging the notification. Refresh the page and try again."
+msgstr ""
+
+msgid "An error occurred while adding approvers"
+msgstr ""
+
msgid "An error occurred while adding formatted title for epic"
msgstr "Произошла ошибка при Ñоздании форматированного заголовка Ð´Ð»Ñ Ñ†ÐµÐ»Ð¸."
-msgid "An error occurred while checking group path"
-msgstr "Произошла ошибка при проверке пути группы"
+msgid "An error occurred while checking group path. Please refresh and try again."
+msgstr ""
msgid "An error occurred while committing your changes."
msgstr "Произошла ошибка во Ð²Ñ€ÐµÐ¼Ñ Ñ„Ð¸ÐºÑации изменений."
@@ -2459,8 +2584,8 @@ msgstr "Произошла ошибка при загрузке запроÑов
msgid "An error occurred while loading milestones"
msgstr ""
-msgid "An error occurred while loading terraform report"
-msgstr "Произошла ошибка при загрузке отчёта Terraform"
+msgid "An error occurred while loading project creation UI"
+msgstr ""
msgid "An error occurred while loading the data. Please try again."
msgstr "Произошла ошибка при загрузке данных. ПожалуйÑта, повторите попытку."
@@ -2513,6 +2638,9 @@ msgstr "Произошла ошибка при удалении обÑужден
msgid "An error occurred while rendering preview broadcast message"
msgstr "Произошла ошибка при визуализации проÑмотра широковещательного ÑообщениÑ"
+msgid "An error occurred while rendering the editor"
+msgstr ""
+
msgid "An error occurred while reordering issues."
msgstr "Произошла ошибка при переназначении задач."
@@ -2550,7 +2678,7 @@ msgid "An error occurred while unsubscribing to notifications."
msgstr "При отпиÑке от уведомлений произошла ошибка."
msgid "An error occurred while updating approvers"
-msgstr ""
+msgstr "Произошла ошибка при обновлении утверждающих"
msgid "An error occurred while updating the comment"
msgstr "Произошла ошибка при обновлении комментариÑ"
@@ -2574,7 +2702,7 @@ msgid "An instance-level serverless domain already exists."
msgstr "Домен Serverless ÑƒÑ€Ð¾Ð²Ð½Ñ ÑкземплÑра уже ÑущеÑтвует."
msgid "An issue already exists"
-msgstr ""
+msgstr "ОбÑуждение уже ÑущеÑтвует"
msgid "An issue can be a bug, a todo or a feature request that needs to be discussed in a project. Besides, issues are searchable and filterable."
msgstr "Ðа обÑуждение может выноÑитÑÑ Ð±Ð°Ð³, задача или Ð·Ð°Ð¿Ñ€Ð¾Ñ Ñ„ÑƒÐ½ÐºÑ†Ð¸Ð¸, ÐºÐ¾Ñ‚Ð¾Ñ€Ð°Ñ Ð´Ð¾Ð»Ð¶Ð½Ð° быть раÑÑмотрена в проекте. Кроме того, обÑÑƒÐ¶Ð´ÐµÐ½Ð¸Ñ Ð´Ð¾Ñтупны Ð´Ð»Ñ Ð¿Ð¾Ð¸Ñка и фильтрации."
@@ -2609,7 +2737,7 @@ msgstr "Проанализировать рецензируемую верÑию
msgid "Analyze your dependencies for known vulnerabilities."
msgstr "Ðнализируйте ваши завиÑимоÑти на предмет извеÑтных уÑзвимоÑтей."
-msgid "Analyze your source code and git history for secrets"
+msgid "Analyze your source code and git history for secrets."
msgstr ""
msgid "Analyze your source code for known vulnerabilities."
@@ -2649,7 +2777,7 @@ msgid "Any label"
msgstr ""
msgid "Any member with Developer or higher permissions to the project."
-msgstr "Любой учаÑтник проекта Ñ Ð¿Ñ€Ð°Ð²Ð°Ð¼Ð¸ Разработчика или более выÑокими."
+msgstr "Любой учаÑтник проекта Ñ Ð¿Ñ€Ð°Ð²Ð°Ð¼Ð¸ Developer или выше."
msgid "Any milestone"
msgstr ""
@@ -2723,12 +2851,18 @@ msgstr ""
msgid "Apply suggestion"
msgstr "Применить предложение"
+msgid "Apply suggestions"
+msgstr ""
+
msgid "Apply template"
msgstr "Применить шаблон"
msgid "Apply this approval rule to any branch or a specific protected branch."
msgstr ""
+msgid "Applying"
+msgstr ""
+
msgid "Applying a template will replace the existing issue description. Any changes you have made will be lost."
msgstr "Применение шаблона заменит ÑущеÑтвующее опиÑание обÑуждениÑ. Любые Ñделанные вами Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ð±ÑƒÐ´ÑƒÑ‚ потерÑны."
@@ -2741,12 +2875,18 @@ msgstr "Применение команды к %{commandDescription}"
msgid "Applying multiple commands"
msgstr "Применение неÑкольких команд"
-msgid "Applying suggestion"
-msgstr "Применение предложениÑ"
+msgid "Applying suggestion..."
+msgstr ""
+
+msgid "Applying suggestions..."
+msgstr ""
msgid "Approval rules"
msgstr "Правила утверждениÑ"
+msgid "Approval rules reset to project defaults"
+msgstr ""
+
msgid "ApprovalRuleRemove|%d member"
msgid_plural "ApprovalRuleRemove|%d members"
msgstr[0] "%d учаÑтник"
@@ -2808,6 +2948,9 @@ msgstr "Утвердить текущий Ð·Ð°Ð¿Ñ€Ð¾Ñ Ð½Ð° ÑлиÑние."
msgid "Approved"
msgstr ""
+msgid "Approved MRs"
+msgstr ""
+
msgid "Approved by: "
msgstr "Утверждено: "
@@ -3021,6 +3164,9 @@ msgstr "РеÑурÑÑ‹:"
msgid "Assign"
msgstr "Ðазначить"
+msgid "Assign Iteration"
+msgstr ""
+
msgid "Assign custom color like #FF0000"
msgstr "Ðазначьте пользовательÑкий цвет, например #FF0000"
@@ -3039,6 +3185,9 @@ msgstr "Ðазначить какие-либо задачи Ð´Ð»Ñ Ð´Ð°Ð½Ð½Ð¾Ð³Ð
msgid "Assign to"
msgstr "Ðазначить"
+msgid "Assign to commenting user"
+msgstr ""
+
msgid "Assign yourself to these issues"
msgstr "ВзÑÑ‚ÑŒ на ÑÐµÐ±Ñ Ñти обÑуждениÑ"
@@ -3055,26 +3204,32 @@ msgid "Assigned Merge Requests"
msgstr "Ðазначенные запроÑÑ‹ на ÑлиÑние"
msgid "Assigned to %{assignee_name}"
-msgstr ""
+msgstr "Ðазначен %{assignee_name}"
msgid "Assigned to me"
msgstr "Ðазначить мне"
msgid "Assignee"
msgid_plural "%d Assignees"
-msgstr[0] "Ðазначенный"
-msgstr[1] "%d Ðазначенных"
-msgstr[2] "%d Ðазначенных"
-msgstr[3] "%d Ðазначенных"
+msgstr[0] "ОтветÑтвенный"
+msgstr[1] "%d ответÑтвенных"
+msgstr[2] "%d ответÑтвенных"
+msgstr[3] "%d ответÑтвенных"
+
+msgid "Assignee has no permissions"
+msgstr ""
msgid "Assignee lists not available with your current license"
-msgstr "СпиÑки назначенных не доÑтупны Ñ Ð²Ð°ÑˆÐµÐ¹ текущей лицензией"
+msgstr "СпиÑки ответÑтвенных не доÑтупны Ñ Ð²Ð°ÑˆÐµÐ¹ текущей лицензией"
msgid "Assignee lists show all issues assigned to the selected user."
-msgstr "СпиÑки иÑполнителей показывают вÑе обÑуждениÑ, назначенные выбранному пользователю."
+msgstr "СпиÑки ответÑтвенных показывают вÑе обÑуждениÑ, назначенные выбранному пользователю."
msgid "Assignee(s)"
-msgstr "ОтветÑтвенный(ные)"
+msgstr "ОтветÑтвенный(ые)"
+
+msgid "Assignees"
+msgstr ""
msgid "Assigns %{assignee_users_sentence}."
msgstr "Ðазначение %{assignee_users_sentence}."
@@ -3113,6 +3268,9 @@ msgstr "Ðудит Ñобытий"
msgid "Audit Events is a way to keep track of important events that happened in GitLab."
msgstr ""
+msgid "Audit Log"
+msgstr ""
+
msgid "AuditEvents|(removed)"
msgstr "(удалено)"
@@ -3149,6 +3307,9 @@ msgstr ""
msgid "AuditLogs|IP Address"
msgstr ""
+msgid "AuditLogs|Member Events"
+msgstr ""
+
msgid "AuditLogs|No matching %{type} found."
msgstr ""
@@ -3237,13 +3398,13 @@ msgid "Authors: %{authors}"
msgstr "Ðвторы: %{authors}"
msgid "Auto DevOps"
-msgstr "ÐвтоматичеÑкий DevOps"
+msgstr "Auto DevOps"
msgid "Auto DevOps enabled"
msgstr "Auto DevOps включен"
msgid "Auto DevOps, runners and job artifacts"
-msgstr "ÐвтоматичеÑкий DevOps, обработчики и артефакты заданий"
+msgstr "Auto DevOps, обработчики и артефакты заданий"
msgid "Auto stop successfully canceled."
msgstr "ÐвтоматичеÑÐºÐ°Ñ Ð¾Ñтановка уÑпешно отменена."
@@ -3255,10 +3416,10 @@ msgid "Auto-close referenced issues on default branch"
msgstr ""
msgid "AutoDevOps|Auto DevOps"
-msgstr "ÐвтоматичеÑкий DevOps"
+msgstr "Auto DevOps"
msgid "AutoDevOps|Auto DevOps can automatically build, test, and deploy applications based on predefined continuous integration and delivery configuration. %{auto_devops_start}Learn more about Auto DevOps%{auto_devops_end} or use our %{quickstart_start}quick start guide%{quickstart_end} to get started right away."
-msgstr "ÐвтоматичеÑкий DevOps может автоматичеÑки Ñобирать, теÑтировать и развертывать приложениÑ, оÑновываÑÑÑŒ на предпопределенных наÑтройках непрерывной интеграции и развертываниÑ. %{auto_devops_start}Узнайте больше про автоматичеÑкий ÐвтоматичеÑкий DevOps%{auto_devops_end} или иÑпользуйте наше %{quickstart_start}рукодводÑтво по быÑтрому Ñтарту%{quickstart_end}, чтобы приÑтупить Ñразу же."
+msgstr "Auto DevOps может автоматичеÑки Ñобирать, теÑтировать и развертывать приложениÑ, оÑновываÑÑÑŒ на предопределенных наÑтройках непрерывной интеграции и развертываниÑ. %{auto_devops_start}Узнайте больше про Auto DevOps%{auto_devops_end} или иÑпользуйте наше %{quickstart_start} краткое руководÑтво пользователÑ%{quickstart_end}, чтобы приÑтупить Ñразу же."
msgid "AutoDevOps|Auto DevOps documentation"
msgstr "Ð”Ð¾ÐºÑƒÐ¼ÐµÐ½Ñ‚Ð°Ñ†Ð¸Ñ Ð¿Ð¾ Auto DevOps"
@@ -3270,7 +3431,7 @@ msgid "AutoDevOps|Enable in settings"
msgstr "Включить в наÑтройках"
msgid "AutoDevOps|It will automatically build, test, and deploy your application based on a predefined CI/CD configuration."
-msgstr "Ð”Ð»Ñ Ñтого проекта может быть активирован автоматичеÑкий DevOps. Он будет автоматичеÑки Ñобирать, теÑтировать и разворачивать ваши Ð¿Ñ€Ð¸Ð»Ð¾Ð¶ÐµÐ½Ð¸Ñ Ð½Ð° оÑнове предопределенной конфигурации CI/CD."
+msgstr "Ð”Ð»Ñ Ñтого проекта может быть активирован Auto DevOps. Он будет автоматичеÑки Ñобирать, теÑтировать и разворачивать ваши Ð¿Ñ€Ð¸Ð»Ð¾Ð¶ÐµÐ½Ð¸Ñ Ð½Ð° оÑнове предопределенной конфигурации CI/CD."
msgid "AutoDevOps|Learn more in the %{link_to_documentation}"
msgstr "Подробнее по ÑÑылке %{link_to_documentation}"
@@ -3431,9 +3592,6 @@ msgstr "Ваши значки"
msgid "Badges|e.g. %{exampleUrl}"
msgstr "например %{exampleUrl}"
-msgid "Badge|New"
-msgstr "Ðовый"
-
msgid "Balsamiq file could not be loaded."
msgstr "Файл Balsamiq не может быть загружен."
@@ -3482,6 +3640,12 @@ msgstr "Ðиже приведены отпечатки ключей SSH теку
msgid "Below you will find all the groups that are public."
msgstr "Ðиже показаны вÑе открытые группы."
+msgid "Beta"
+msgstr ""
+
+msgid "Bi-weekly code coverage"
+msgstr ""
+
msgid "Billing"
msgstr "Тарифы"
@@ -3542,9 +3706,15 @@ msgstr "Улучшить"
msgid "Bitbucket Server Import"
msgstr "Импорт из Bitbucket Server"
+msgid "Bitbucket Server import"
+msgstr ""
+
msgid "Bitbucket import"
msgstr "Импорт из BitBucket"
+msgid "Blame"
+msgstr ""
+
msgid "Blocked"
msgstr ""
@@ -3557,9 +3727,6 @@ msgstr "Блокирует"
msgid "Blog"
msgstr "Блог"
-msgid "Blue helpers indicate an action to be taken."
-msgstr "Синие подÑказки указывают на дейÑтвие, которое необходимо предпринÑÑ‚ÑŒ."
-
msgid "Board name"
msgstr "Ðазвание доÑки"
@@ -3758,6 +3925,9 @@ msgstr ""
msgid "Broadcast Message was successfully updated."
msgstr ""
+msgid "Broadcast Messages"
+msgstr ""
+
msgid "Browse Directory"
msgstr "ПроÑмотр каталога"
@@ -3812,6 +3982,9 @@ msgstr "Купить минут Ð´Ð»Ñ CI"
msgid "By %{user_name}"
msgstr "От %{user_name}"
+msgid "By URL"
+msgstr ""
+
msgid "By default GitLab sends emails in HTML and plain text formats so mail clients can choose what format to use. Disable this option if you only want to send emails in plain text format."
msgstr "По умолчанию GitLab отправлÑет пиÑьма в формате HTML и обычного текÑта, так чтобы почтовые клиенты могли выбрать, какой формат иÑпользовать. Отключите Ñту опцию, еÑли вы хотите отправлÑÑ‚ÑŒ пиÑьма только в текÑтовом формате."
@@ -3861,7 +4034,7 @@ msgid "CICD|Add a %{kubernetes_cluster_link_start}Kubernetes cluster integration
msgstr ""
msgid "CICD|Auto DevOps"
-msgstr "ÐвтоматичеÑкий DevOps"
+msgstr "Auto DevOps"
msgid "CICD|Automatic deployment to staging, manual deployment to production"
msgstr "ÐвтоматичеÑкое развёртывание в теÑтовую Ñреду, ручное - в продакшн"
@@ -3915,6 +4088,9 @@ msgid "Can be manually deployed to"
msgstr ""
msgid "Can override approvers and approvals required per merge request"
+msgstr "Может переопределÑÑ‚ÑŒ утверждающих и их ÑƒÑ‚Ð²ÐµÑ€Ð¶Ð´ÐµÐ½Ð¸Ñ Ð·Ð°Ð¿Ñ€Ð¾Ñа на ÑлиÑние"
+
+msgid "Can't apply as this line has changed or the suggestion already matches its content."
msgstr ""
msgid "Can't create snippet: %{err}"
@@ -4029,13 +4205,13 @@ msgid "Certificate Subject"
msgstr ""
msgid "Change assignee"
-msgstr "Изменить иÑполнителÑ"
+msgstr "Изменить ответÑтвенного"
msgid "Change assignee(s)"
-msgstr "Изменить иÑполнителÑ(ей)"
+msgstr "Изменить ответÑтвенного(Ñ‹Ñ…)"
msgid "Change assignee(s)."
-msgstr "Изменить иÑполнителÑ(ей)."
+msgstr "Изменить ответÑтвенного(Ñ‹Ñ…)."
msgid "Change branches"
msgstr "Сменить ветки"
@@ -4089,7 +4265,7 @@ msgid "ChangeTypeAction|This will create a new commit in order to revert the exi
msgstr "Это ÑоздаÑÑ‚ новый коммит Ð´Ð»Ñ Ñ‚Ð¾Ð³Ð¾, чтобы откатить ÑущеÑтвующие изменениÑ."
msgid "Changed assignee(s)."
-msgstr "Изменены иÑполнителÑ(ей)."
+msgstr "Изменён ответÑтвенный(ые)."
msgid "Changed the title to \"%{title_param}\"."
msgstr ""
@@ -4103,9 +4279,6 @@ msgstr "Показаны Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ ÐºÐ°Ðº будто произошло
msgid "Changes are still tracked. Useful for cluster/index migrations."
msgstr ""
-msgid "Changes are unknown"
-msgstr ""
-
msgid "Changes suppressed. Click to show."
msgstr "Ð˜Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ñкрыты. Ðажмите, чтобы показать."
@@ -4412,15 +4585,9 @@ msgstr "Выберите уровень доÑтупа, включите / отÐ
msgid "Choose what content you want to see on a group’s overview page"
msgstr ""
-msgid "Choose which groups you wish to synchronize to this secondary node"
-msgstr "Выберите, какие группы вы хотите Ñинхронизировать Ñо вторичным узлом"
-
msgid "Choose which repositories you want to connect and run CI/CD pipelines."
msgstr ""
-msgid "Choose which shards you wish to synchronize to this secondary node"
-msgstr ""
-
msgid "Choose your framework"
msgstr "Выберите фреймворк"
@@ -4718,9 +4885,15 @@ msgstr ""
msgid "Cluster does not exist"
msgstr ""
+msgid "Cluster is required for Stages::ClusterEndpointInserter"
+msgstr ""
+
msgid "Cluster level"
msgstr ""
+msgid "Cluster type must be specificed for Stages::ClusterEndpointInserter"
+msgstr ""
+
msgid "ClusterIntegration| %{custom_domain_start}More information%{custom_domain_end}."
msgstr ""
@@ -4832,7 +5005,7 @@ msgstr "Сертификат удоÑтоверÑющего центра"
msgid "ClusterIntegration|Cert-Manager"
msgstr ""
-msgid "ClusterIntegration|Cert-Manager is a native Kubernetes certificate management controller that helps with issuing certificates. Installing Cert-Manager on your cluster will issue a certificate by %{letsEncrypt} and ensure that certificates are valid and up-to-date."
+msgid "ClusterIntegration|Cert-Manager is a native Kubernetes certificate management controller that helps with issuing certificates. Installing Cert-Manager on your cluster will issue a certificate by %{linkStart}Let's Encrypt%{linkEnd} and ensure that certificates are valid and up-to-date."
msgstr ""
msgid "ClusterIntegration|Certificate Authority bundle (PEM format)"
@@ -4859,9 +5032,6 @@ msgstr "ОчиÑтить кÑш клаÑтера"
msgid "ClusterIntegration|Clear the local cache of namespace and service accounts. This is necessary if your integration has become out of sync. The cache is repopulated during the next CI job that requires namespace and service accounts."
msgstr ""
-msgid "ClusterIntegration|Cloud Run"
-msgstr "Cloud Run"
-
msgid "ClusterIntegration|Cluster being created"
msgstr "КлаÑтер ÑоздаетÑÑ"
@@ -4946,7 +5116,7 @@ msgstr "СоздаетÑÑ ÐºÐ»Ð°Ñтер Kubernetes"
msgid "ClusterIntegration|Crossplane"
msgstr "Crossplane"
-msgid "ClusterIntegration|Crossplane enables declarative provisioning of managed services from your cloud of choice using %{codeStart}kubectl%{codeEnd} or %{gitlabIntegrationLink}. Crossplane runs inside your Kubernetes cluster and supports secure connectivity and secrets management between app containers and the cloud services they depend on."
+msgid "ClusterIntegration|Crossplane enables declarative provisioning of managed services from your cloud of choice using %{codeStart}kubectl%{codeEnd} or %{linkStart}GitLab Integration%{linkEnd}. Crossplane runs inside your Kubernetes cluster and supports secure connectivity and secrets management between app containers and the cloud services they depend on."
msgstr ""
msgid "ClusterIntegration|Deletes all GitLab resources attached to this cluster during removal"
@@ -5030,9 +5200,6 @@ msgstr "GitLab Runner подключаетÑÑ Ðº репозиторию и вы
msgid "ClusterIntegration|GitLab-managed cluster"
msgstr "КлаÑтер управлÑемый GitLab"
-msgid "ClusterIntegration|Gitlab Integration"
-msgstr "Ð˜Ð½Ñ‚ÐµÐ³Ñ€Ð°Ñ†Ð¸Ñ Ñ Gitlab"
-
msgid "ClusterIntegration|Global default"
msgstr "Везде по умолчанию"
@@ -5078,8 +5245,8 @@ msgstr "Точка Ð¿Ð¾Ð´ÐºÐ»ÑŽÑ‡ÐµÐ½Ð¸Ñ Ingress"
msgid "ClusterIntegration|Ingress gives you a way to route requests to services based on the request host or path, centralizing a number of services into a single entrypoint."
msgstr "Ingress позволÑет маршрутизировать запроÑÑ‹ к Ñлужбам на оÑнове запрошенного хоÑта или пути, объединÑÑ Ñ€Ñд ÑервиÑов в одну точку входа."
-msgid "ClusterIntegration|Installing Ingress may incur additional costs. Learn more about %{pricingLink}."
-msgstr "УÑтановка Ingress может повлечь дополнительные затраты. Узнайте больше по ÑÑылке %{pricingLink}."
+msgid "ClusterIntegration|Installing Ingress may incur additional costs. Learn more about %{linkStart}pricing%{linkEnd}."
+msgstr ""
msgid "ClusterIntegration|Instance cluster"
msgstr "ЭкземплÑÑ€ клаÑтера"
@@ -5093,8 +5260,8 @@ msgstr "Ð˜Ð½Ñ‚ÐµÐ³Ñ€Ð°Ñ†Ð¸Ñ Ð°Ð²Ñ‚Ð¾Ð¼Ð°Ñ‚Ð¸Ð·Ð°Ñ†Ð¸Ð¸ клаÑтеров Kuber
msgid "ClusterIntegration|Issuer Email"
msgstr "Ð­Ð»ÐµÐºÑ‚Ñ€Ð¾Ð½Ð½Ð°Ñ Ð¿Ð¾Ñ‡Ñ‚Ð° Ñмитента"
-msgid "ClusterIntegration|Issuers represent a certificate authority. You must provide an email address for your Issuer. "
-msgstr "Эмитенты предÑтавлÑÑŽÑ‚ центры Ñертификации. Ð’Ñ‹ должны указать Ñлектронную почту вашего Ñмитента. "
+msgid "ClusterIntegration|Issuers represent a certificate authority. You must provide an email address for your Issuer."
+msgstr ""
msgid "ClusterIntegration|Jupyter Hostname"
msgstr "Ð˜Ð¼Ñ Ñ…Ð¾Ñта ÑервиÑа Jupyter"
@@ -5165,9 +5332,6 @@ msgstr ""
msgid "ClusterIntegration|Learn more about instance Kubernetes clusters"
msgstr "Узнайте больше о ÑкземплÑрах клаÑтеров Kubernetes"
-msgid "ClusterIntegration|Let's Encrypt"
-msgstr "Let's Encrypt"
-
msgid "ClusterIntegration|Loading IAM Roles"
msgstr "Загрузка ролей IAM"
@@ -5279,8 +5443,8 @@ msgstr ""
msgid "ClusterIntegration|Prometheus"
msgstr "Prometheus"
-msgid "ClusterIntegration|Prometheus is an open-source monitoring system with %{gitlabIntegrationLink} to monitor deployed applications."
-msgstr "Prometheus - Ñто ÑиÑтема мониторинга Ñ Ð¾Ñ‚ÐºÑ€Ñ‹Ñ‚Ñ‹Ð¼ иÑходным кодом (%{gitlabIntegrationLink}) Ð´Ð»Ñ Ð¼Ð¾Ð½Ð¸Ñ‚Ð¾Ñ€Ð¸Ð½Ð³Ð° развернутых приложений."
+msgid "ClusterIntegration|Prometheus is an open-source monitoring system with %{linkStart}GitLab Integration%{linkEnd} to monitor deployed applications."
+msgstr ""
msgid "ClusterIntegration|Provider details"
msgstr ""
@@ -5435,10 +5599,10 @@ msgstr "Выберете зону"
msgid "ClusterIntegration|Select zone to choose machine type"
msgstr "Выберите зону, чтобы выбрать тип машины"
-msgid "ClusterIntegration|Send Cilium Logs"
+msgid "ClusterIntegration|Send Container Network Policies Logs"
msgstr ""
-msgid "ClusterIntegration|Send ModSecurity Logs"
+msgid "ClusterIntegration|Send Web Application Firewall Logs"
msgstr ""
msgid "ClusterIntegration|Service Token"
@@ -5606,15 +5770,12 @@ msgstr "доÑтуп к Google Kubernetes Engine"
msgid "ClusterIntegration|documentation"
msgstr "документациÑ"
-msgid "ClusterIntegration|installed via %{installed_via}"
+msgid "ClusterIntegration|installed via %{linkStart}Cloud Run%{linkEnd}"
msgstr ""
msgid "ClusterIntegration|meets the requirements"
msgstr "отвечает требованиÑм"
-msgid "ClusterIntegration|pricing"
-msgstr "СтоимоÑÑ‚ÑŒ"
-
msgid "ClusterIntegration|sign up"
msgstr "зарегиÑтрироватьÑÑ"
@@ -5651,6 +5812,15 @@ msgstr ""
msgid "Code"
msgstr "Код"
+msgid "Code Coverage: %{coveragePercentage}%{percentSymbol}"
+msgstr ""
+
+msgid "Code Coverage| Empty code coverage data"
+msgstr ""
+
+msgid "Code Coverage|Couldn't fetch the code coverage data"
+msgstr ""
+
msgid "Code Owners"
msgstr ""
@@ -5703,10 +5873,10 @@ msgid "Collapse"
msgstr "Свернуть"
msgid "Collapse approvers"
-msgstr ""
+msgstr "Свернуть утверждающих"
-msgid "Collapse child epics"
-msgstr "Свернуть дочерние цели"
+msgid "Collapse milestones"
+msgstr ""
msgid "Collapse replies"
msgstr ""
@@ -5723,6 +5893,9 @@ msgstr "ComboSearch не определен"
msgid "Coming soon"
msgstr ""
+msgid "Comma-separated, e.g. '1.1.1.1, 2.2.2.0/24'"
+msgstr ""
+
msgid "Command"
msgstr ""
@@ -5824,7 +5997,7 @@ msgid "Commits per weekday"
msgstr "Коммиты по днÑм недели"
msgid "Commits to"
-msgstr ""
+msgstr "Коммитов в"
msgid "Commits|An error occurred while fetching merge requests data."
msgstr "Произошла ошибка при получении данных запроÑа на ÑлиÑниÑ."
@@ -5895,6 +6068,12 @@ msgstr "Панель ÑƒÐ¿Ñ€Ð°Ð²Ð»ÐµÐ½Ð¸Ñ Ñлужбы комплаенÑа"
msgid "Compliance framework (optional)"
msgstr "Фреймворк комплаенÑа (необÑзательно)"
+msgid "Compliance frameworks"
+msgstr ""
+
+msgid "ComplianceDashboard|created by:"
+msgstr ""
+
msgid "ComplianceFramework|GDPR"
msgstr "GDPR"
@@ -6033,6 +6212,9 @@ msgstr "Ошибка подключениÑ"
msgid "Connection timed out"
msgstr ""
+msgid "Connection timeout"
+msgstr ""
+
msgid "Contact sales to upgrade"
msgstr "СвÑзатьÑÑ Ñ Ð¾Ñ‚Ð´ÐµÐ»Ð¾Ð¼ продаж Ð´Ð»Ñ Ð¾Ð±Ð½Ð¾Ð²Ð»ÐµÐ½Ð¸Ñ"
@@ -6055,7 +6237,7 @@ msgid "Container registry images"
msgstr "Образы рееÑтра контейнеров"
msgid "Container registry is not enabled on this GitLab instance. Ask an administrator to enable it in order for Auto DevOps to work."
-msgstr ""
+msgstr "РееÑÑ‚Ñ€ контейнеров не включен в Ñтом ÑкземплÑре GitLab. ПопроÑите админиÑтратора включить его, чтобы Auto DevOps работал."
msgid "Container repositories sync capacity"
msgstr "Объем Ñинхронизации рееÑтра контейнеров"
@@ -6092,9 +6274,6 @@ msgstr "Собрать образ"
msgid "ContainerRegistry|CLI Commands"
msgstr ""
-msgid "ContainerRegistry|Compressed Size"
-msgstr "Сжатый размер"
-
msgid "ContainerRegistry|Container Registry"
msgstr "РееÑÑ‚Ñ€ Контейнеров"
@@ -6110,6 +6289,9 @@ msgstr "Скопировать команду входа"
msgid "ContainerRegistry|Copy push command"
msgstr "Скопировать команду Ð´Ð»Ñ Ð¾Ñ‚Ð¿Ñ€Ð°Ð²ÐºÐ¸"
+msgid "ContainerRegistry|Delete selected"
+msgstr ""
+
msgid "ContainerRegistry|Docker connection error"
msgstr "Ошибка Ð¿Ð¾Ð´ÐºÐ»ÑŽÑ‡ÐµÐ½Ð¸Ñ Docker"
@@ -6143,18 +6325,18 @@ msgstr ""
msgid "ContainerRegistry|If you are not already logged in, you need to authenticate to the Container Registry by using your GitLab username and password. If you have %{twofaDocLinkStart}Two-Factor Authentication%{twofaDocLinkEnd} enabled, use a %{personalAccessTokensDocLinkStart}Personal Access Token%{personalAccessTokensDocLinkEnd} instead of a password."
msgstr ""
-msgid "ContainerRegistry|Image ID"
-msgstr "ID образа"
+msgid "ContainerRegistry|Image ID: %{imageId}"
+msgstr ""
msgid "ContainerRegistry|Image Repositories"
msgstr ""
+msgid "ContainerRegistry|Image tags"
+msgstr ""
+
msgid "ContainerRegistry|Keep and protect the images that matter most."
msgstr "СохранÑÑ‚ÑŒ и защищать образы, которые имеют наибольшее значение."
-msgid "ContainerRegistry|Last Updated"
-msgstr "ПоÑледнее обновление"
-
msgid "ContainerRegistry|Login"
msgstr "Вход"
@@ -6167,6 +6349,9 @@ msgstr "КоличеÑтво тегов Ð´Ð»Ñ ÑохранениÑ:"
msgid "ContainerRegistry|Please contact your administrator."
msgstr "ПожалуйÑта, ÑвÑжитеÑÑŒ Ñ Ð°Ð´Ð¼Ð¸Ð½Ð¸Ñтратором."
+msgid "ContainerRegistry|Published %{timeInfo}"
+msgstr ""
+
msgid "ContainerRegistry|Push an image"
msgstr "Отправить образ"
@@ -6182,9 +6367,6 @@ msgstr "Ðе забудьте запуÑтить %{docLinkStart}Ñборку мÑ
msgid "ContainerRegistry|Remove repository"
msgstr "Удалить репозиторий"
-msgid "ContainerRegistry|Remove selected tags"
-msgstr "Удалить выбранные теги"
-
msgid "ContainerRegistry|Remove tag"
msgid_plural "ContainerRegistry|Remove tags"
msgstr[0] "Удалить тег"
@@ -6216,9 +6398,6 @@ msgstr "Что-то пошло не так во Ð²Ñ€ÐµÐ¼Ñ Ð¾Ð±Ð½Ð¾Ð²Ð»ÐµÐ½Ð¸Ñ
msgid "ContainerRegistry|Sorry, your filter produced no results."
msgstr ""
-msgid "ContainerRegistry|Tag"
-msgstr "Тег"
-
msgid "ContainerRegistry|Tag expiration policy"
msgstr "Политика иÑÑ‚ÐµÑ‡ÐµÐ½Ð¸Ñ Ñ‚ÐµÐ³Ð¾Ð²"
@@ -6319,7 +6498,7 @@ msgid "ContributionAnalytics|<strong>%{created_count}</strong> created, <strong>
msgstr ""
msgid "ContributionAnalytics|<strong>%{pushes}</strong> pushes, more than <strong>%{commits}</strong> commits by <strong>%{people}</strong> contributors."
-msgstr ""
+msgstr "<strong>%{pushes}</strong> отправок — больше чем <strong>%{commits}</strong> коммитов от <strong>%{people}</strong> учаÑтников."
msgid "ContributionAnalytics|Contribution analytics for issues, merge requests and push events since %{start_date}"
msgstr ""
@@ -6363,21 +6542,6 @@ msgstr "Управление адреÑами Ñлектронной почты,
msgid "Control the display of third party offers."
msgstr ""
-msgid "Control the maximum concurrency of LFS/attachment backfill for this secondary node"
-msgstr ""
-
-msgid "Control the maximum concurrency of container repository operations for this Geo node"
-msgstr ""
-
-msgid "Control the maximum concurrency of repository backfill for this secondary node"
-msgstr ""
-
-msgid "Control the maximum concurrency of verification operations for this Geo node"
-msgstr ""
-
-msgid "Control the minimum interval in days that a repository should be reverified for this primary node"
-msgstr "Задайте минимальный интервал в днÑÑ…, поÑле которого репозиторий должен быть перепроверен Ð´Ð»Ñ Ñтого оÑновного узла"
-
msgid "Cookie domain"
msgstr ""
@@ -6519,6 +6683,9 @@ msgstr "Ðе удалоÑÑŒ удалить никнейм чата %{chat_name}.
msgid "Could not find design."
msgstr ""
+msgid "Could not find iteration"
+msgstr ""
+
msgid "Could not remove the trigger."
msgstr ""
@@ -6555,6 +6722,12 @@ msgstr ""
msgid "Coverage"
msgstr "Покрытие"
+msgid "Coverage Fuzzing"
+msgstr ""
+
+msgid "Crash State"
+msgstr ""
+
msgid "Create"
msgstr "Создать"
@@ -6603,6 +6776,9 @@ msgstr "Создать новый репозиторий"
msgid "Create a personal access token on your account to pull or push via %{protocol}."
msgstr "Создать личный токен на аккаунте Ð´Ð»Ñ Ð¿Ð¾Ð»ÑƒÑ‡ÐµÐ½Ð¸Ñ Ð¸Ð»Ð¸ отправки через %{protocol}."
+msgid "Create a project pre-populated with the necessary files to get you started quickly."
+msgstr ""
+
msgid "Create an account using:"
msgstr ""
@@ -6771,6 +6947,9 @@ msgstr ""
msgid "Creates branch '%{branch_name}' and a merge request to resolve this issue."
msgstr "Создает ветку %{branch_name} и Ð·Ð°Ð¿Ñ€Ð¾Ñ Ð½Ð° ÑлиÑние Ð´Ð»Ñ Ñ€ÐµÑˆÐµÐ½Ð¸Ñ Ñтого вопроÑа."
+msgid "Creating"
+msgstr ""
+
msgid "Creating epic"
msgstr "Цель ÑоздаетÑÑ"
@@ -6822,8 +7001,11 @@ msgstr "Текущий пароль"
msgid "Current vulnerabilities count"
msgstr ""
-msgid "CurrentUser|Buy CI minutes"
-msgstr "ПриобреÑти минуты Ð´Ð»Ñ CI"
+msgid "CurrentUser|Buy Pipeline minutes"
+msgstr ""
+
+msgid "CurrentUser|One of your groups is running out"
+msgstr ""
msgid "CurrentUser|Profile"
msgstr "Профиль"
@@ -6837,6 +7019,9 @@ msgstr "Ðачать пробный период Gold"
msgid "CurrentUser|Upgrade"
msgstr ""
+msgid "Custom Attributes"
+msgstr ""
+
msgid "Custom CI configuration path"
msgstr "ПользовательÑкий путь конфигурации CI"
@@ -7102,12 +7287,18 @@ msgstr "Выпадающий ÑпиÑок Ñтапов"
msgid "DAG"
msgstr ""
+msgid "DAG visualization requires at least 3 dependent jobs."
+msgstr ""
+
msgid "DNS"
msgstr "DNS"
msgid "Dashboard"
msgstr "Панель управлениÑ"
+msgid "Dashboard uid not found"
+msgstr ""
+
msgid "DashboardProjects|All"
msgstr "Ð’Ñе"
@@ -7132,6 +7323,9 @@ msgstr ""
msgid "Data is still calculating..."
msgstr "Данные вÑе еще вычиÑлÑÑŽÑ‚ÑÑ..."
+msgid "Datasource name not found"
+msgstr ""
+
msgid "Date"
msgstr "Дата"
@@ -7312,6 +7506,9 @@ msgstr "Удалить иÑходную ветку"
msgid "Delete this attachment"
msgstr "Удалить вложение"
+msgid "Delete user list"
+msgstr ""
+
msgid "Delete variable"
msgstr ""
@@ -7638,6 +7835,9 @@ msgstr ""
msgid "Deploying to"
msgstr ""
+msgid "Deploying to AWS is easy with GitLab"
+msgstr ""
+
msgid "Deployment Frequency"
msgstr ""
@@ -7988,7 +8188,7 @@ msgid "Dismiss trial promotion"
msgstr ""
msgid "Dismissable"
-msgstr ""
+msgstr "ОтклонÑемое"
msgid "Dismissed"
msgstr ""
@@ -8002,6 +8202,9 @@ msgstr "Отклонено в Ñборке %{pipelineLink}"
msgid "Dismissed on pipeline %{pipelineLink} at %{projectLink}"
msgstr "Отклонено в Ñборке %{pipelineLink} в %{projectLink}"
+msgid "Display alerts from all your monitoring tools directly within GitLab."
+msgstr ""
+
msgid "Display name"
msgstr "Отображаемое имÑ"
@@ -8051,7 +8254,7 @@ msgid "Don't have an account yet?"
msgstr ""
msgid "Don't include description in commit message"
-msgstr ""
+msgstr "Ðе включать опиÑание в опиÑание коммита"
msgid "Don't paste the private part of the GPG key. Paste the public part which begins with '-----BEGIN PGP PUBLIC KEY BLOCK-----'."
msgstr "Ðе вÑтавлÑйте закрытую чаÑÑ‚ÑŒ ключа GPG. Ð’Ñтавьте открытую чаÑÑ‚ÑŒ, начинающуюÑÑ Ð½Ð° «----- BEGIN PGP PUBLIC KEY BLOCK -----»."
@@ -8059,9 +8262,6 @@ msgstr "Ðе вÑтавлÑйте закрытую чаÑÑ‚ÑŒ ключа GPG. Ð’
msgid "Don't show again"
msgstr "Ðе показывать Ñнова"
-msgid "Don't worry, you can access this tour by clicking on the help icon in the top right corner and choose <strong>Learn GitLab</strong>."
-msgstr "Ðе беÑпокойтеÑÑŒ, вы можете получить доÑтуп к Ñтому туру, нажав на значок Ñправа в верхнем углу и выберите <strong>Узнать GitLab</strong>."
-
msgid "Done"
msgstr "Выполнено"
@@ -8138,7 +8338,7 @@ msgid "Duration"
msgstr ""
msgid "Duration for the last 30 commits"
-msgstr ""
+msgstr "ДлительноÑÑ‚ÑŒ за поÑледние 30 коммитов"
msgid "During this process, you’ll be asked for URLs from GitLab’s side. Use the URLs shown below."
msgstr ""
@@ -8188,6 +8388,9 @@ msgstr "Изменить раÑпиÑание Ñборочной линии %{id
msgid "Edit Release"
msgstr ""
+msgid "Edit Slack integration"
+msgstr ""
+
msgid "Edit Snippet"
msgstr "Редактировать Ñниппет"
@@ -8227,6 +8430,9 @@ msgstr "Изменить идентификацию Ð´Ð»Ñ %{user_name}"
msgid "Edit issues"
msgstr ""
+msgid "Edit iteration"
+msgstr ""
+
msgid "Edit public deploy key"
msgstr ""
@@ -8275,6 +8481,9 @@ msgstr "ПуÑто. Выберите проекты Ð´Ð»Ñ Ð¸Ð½Ð´ÐµÐºÑации.
msgid "Email"
msgstr "Ð­Ð»ÐµÐºÑ‚Ñ€Ð¾Ð½Ð½Ð°Ñ Ð¿Ð¾Ñ‡Ñ‚Ð°"
+msgid "Email Notification"
+msgstr ""
+
msgid "Email address"
msgstr ""
@@ -8287,6 +8496,9 @@ msgstr "Отображаемое Ð¸Ð¼Ñ Ð´Ð»Ñ Ñл. почты"
msgid "Email not verified. Please verify your email in Salesforce."
msgstr "Ð­Ð»ÐµÐºÑ‚Ñ€Ð¾Ð½Ð½Ð°Ñ Ð¿Ð¾Ñ‡Ñ‚Ð° не подтверждена. ПожалуйÑта, подтвердите ваш Ð°Ð´Ñ€ÐµÑ Ñлектронной почты в Salesforce."
+msgid "Email notification for unknown sign-ins"
+msgstr ""
+
msgid "Email patch"
msgstr ""
@@ -8518,6 +8730,9 @@ msgstr "ЗаканчиваетÑÑ Ð² (UTC)"
msgid "Enforce DNS rebinding attack protection"
msgstr "ÐŸÑ€Ð¸Ð½ÑƒÐ´Ð¸Ñ‚ÐµÐ»ÑŒÐ½Ð°Ñ Ð·Ð°Ñ‰Ð¸Ñ‚Ð° от перепривÑÐ·Ñ‹Ð²Ð°Ð½Ð¸Ñ DNS"
+msgid "Enforce personal access token expiration"
+msgstr ""
+
msgid "Ensure connectivity is available from the GitLab server to the Prometheus server"
msgstr ""
@@ -8587,6 +8802,9 @@ msgstr "Введите заголовок запроÑа на ÑлиÑние"
msgid "Enter the name of your application, and we'll return a unique %{type}."
msgstr ""
+msgid "Enter weights for storages for new repositories."
+msgstr ""
+
msgid "Enter your password to approve"
msgstr "Ð”Ð»Ñ Ð¿Ñ€Ð¾Ð´Ð¾Ð»Ð¶ÐµÐ½Ð¸Ñ Ð²Ð²ÐµÐ´Ð¸Ñ‚Ðµ Ñвой пароль"
@@ -8596,15 +8814,27 @@ msgstr "Окружение"
msgid "Environment does not have deployments"
msgstr "Окружение не имеет развёртываний"
+msgid "Environment is required for Stages::MetricEndpointInserter"
+msgstr ""
+
+msgid "Environment is required for Stages::VariableEndpointInserter"
+msgstr ""
+
msgid "Environment scope"
msgstr ""
+msgid "Environment variables are applied to all project environments in this instance via the Runner. You can use environment variables for passwords, secret keys, etc. Make variables available to the running application by prepending the variable key with <code>K8S_SECRET_</code>. You can set variables to be:"
+msgstr ""
+
msgid "Environment variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. Additionally, they can be masked so they are hidden in job logs, though they must match certain regexp requirements to do so. You can use environment variables for passwords, secret keys, or whatever you want."
msgstr "Переменные Ñреды Ð¾ÐºÑ€ÑƒÐ¶ÐµÐ½Ð¸Ñ Ð¿Ñ€Ð¸Ð¼ÐµÐ½ÑÑŽÑ‚ÑÑ Ðº Ñредам через Runner. Они могут быть защищены только при нахождении в защищенных ветках и тегах. Кроме того, они могут быть замаÑкированы, чтобы Ñкрыть их в журналах заданий, Ñ…Ð¾Ñ‚Ñ Ð´Ð»Ñ Ñтого они должны ÑоответÑтвовать требованиÑм правильного регулÑрного выражениÑ. Ð’Ñ‹ можете иÑпользовать переменные Ñреды Ð¾ÐºÑ€ÑƒÐ¶ÐµÐ½Ð¸Ñ Ð´Ð»Ñ Ð¿Ð°Ñ€Ð¾Ð»ÐµÐ¹, Ñекретных ключей или Ð´Ð»Ñ Ñ‡ÐµÐ³Ð¾ угодного, чего бы вы хотели."
msgid "Environment variables are configured by your administrator to be %{link_start}protected%{link_end} by default"
msgstr "Переменные Ð¾ÐºÑ€ÑƒÐ¶ÐµÐ½Ð¸Ñ Ð½Ð°Ñтроены админиÑтратором, чтобы быть %{link_start}защищенными%{link_end} по умолчанию"
+msgid "Environment variables on this GitLab instance are configured to be %{link_start}protected%{link_end} by default"
+msgstr ""
+
msgid "Environment:"
msgstr "Окружение:"
@@ -9023,7 +9253,7 @@ msgid "Error occurred when fetching sidebar data"
msgstr "Произошла ошибка при получении данных боковой панели"
msgid "Error occurred when saving assignees"
-msgstr "Произошла ошибка при Ñохранении назначенных"
+msgstr "Произошла ошибка при Ñохранении ответÑтвенных"
msgid "Error occurred when toggling the notification subscription"
msgstr "Произошла ошибка при переключении подпиÑки на оповещение"
@@ -9035,19 +9265,19 @@ msgid "Error occurred while updating the issue weight"
msgstr "Произошла ошибка при изменении приоритета задачи"
msgid "Error occurred. A blocked user cannot be deactivated"
-msgstr ""
+msgstr "Произошла ошибка. Заблокированный пользователь не может быть деактивирован"
msgid "Error occurred. A blocked user must be unblocked to be activated"
-msgstr ""
+msgstr "Произошла ошибка. Заблокированный пользователь должен быть разблокирован Ð´Ð»Ñ Ð°ÐºÑ‚Ð¸Ð²Ð°Ñ†Ð¸Ð¸"
msgid "Error occurred. User was not blocked"
-msgstr ""
+msgstr "Произошла ошибка. Пользователь не был заблокирован"
msgid "Error occurred. User was not confirmed"
msgstr ""
msgid "Error occurred. User was not unblocked"
-msgstr ""
+msgstr "Произошла ошибка. Пользователь не был разблокирован"
msgid "Error occurred. User was not unlocked"
msgstr "Произошла ошибка. Пользователь не был разблокирован"
@@ -9136,6 +9366,9 @@ msgstr "Фильтр по вÑему"
msgid "EventFilterBy|Filter by comments"
msgstr "Фильтр по комментарию"
+msgid "EventFilterBy|Filter by designs"
+msgstr ""
+
msgid "EventFilterBy|Filter by epic events"
msgstr "Фильтровать по ÑобытиÑм целей"
@@ -9268,15 +9501,15 @@ msgstr "Развернуть вÑе"
msgid "Expand approvers"
msgstr "Развернуть утверждающих"
-msgid "Expand child epics"
-msgstr "Развернуть дочерние цели"
-
msgid "Expand down"
msgstr "Развернуть вниз"
msgid "Expand dropdown"
msgstr "Развернуть раÑкрывающийÑÑ ÑпиÑок"
+msgid "Expand milestones"
+msgstr ""
+
msgid "Expand sidebar"
msgstr "Развернуть боковую панель"
@@ -9292,6 +9525,9 @@ msgstr ""
msgid "Expiration date"
msgstr "Срок дейÑтвиÑ"
+msgid "Expiration not enforced"
+msgstr ""
+
msgid "Expiration policy for the Container Registry is a perfect solution for keeping the Registry space down while still enjoying the full power of GitLab CI/CD."
msgstr ""
@@ -9412,6 +9648,9 @@ msgstr "Ðеудачно"
msgid "Failed Jobs"
msgstr "Ðевыполненные ЗаданиÑ"
+msgid "Failed on"
+msgstr ""
+
msgid "Failed to add a Zoom meeting"
msgstr ""
@@ -9472,13 +9711,19 @@ msgstr "Ðе удаетÑÑ Ð¿Ð¾Ð»ÑƒÑ‡Ð¸Ñ‚ÑŒ ÑÑылку"
msgid "Failed to install."
msgstr ""
+msgid "Failed to load assignees. Please try again."
+msgstr ""
+
+msgid "Failed to load authors. Please try again."
+msgstr ""
+
msgid "Failed to load emoji list."
msgstr ""
msgid "Failed to load error details from Sentry."
msgstr ""
-msgid "Failed to load errors from Sentry. Error message: %{errorMessage}"
+msgid "Failed to load errors from Sentry."
msgstr ""
msgid "Failed to load group activity metrics. Please try again."
@@ -9559,6 +9804,9 @@ msgstr ""
msgid "Failed to set due date because the date format is invalid."
msgstr ""
+msgid "Failed to set iteration on this issue. Please try again."
+msgstr ""
+
msgid "Failed to signing using smartcard authentication"
msgstr ""
@@ -9781,15 +10029,12 @@ msgstr ""
msgid "FeatureFlags|Target environments"
msgstr "Целевые окружениÑ"
-msgid "FeatureFlags|There are no active feature flags"
-msgstr "Ðет активных функциональных опций"
-
-msgid "FeatureFlags|There are no inactive feature flags"
-msgstr "Ðет неактивных функциональных опций"
-
msgid "FeatureFlags|There was an error fetching the feature flags."
msgstr ""
+msgid "FeatureFlags|There was an error retrieving user lists"
+msgstr ""
+
msgid "FeatureFlags|Try again in a few moments or contact your support team."
msgstr "Попробуйте еще раз через неÑколько минут или ÑвÑжитеÑÑŒ Ñ Ð²Ð°ÑˆÐµÐ¹ Ñлужбой поддержки."
@@ -9799,9 +10044,18 @@ msgstr "Идентификаторы пользователÑ"
msgid "FeatureFlag|Delete strategy"
msgstr ""
+msgid "FeatureFlag|List"
+msgstr ""
+
msgid "FeatureFlag|Percentage"
msgstr ""
+msgid "FeatureFlag|Select a user list"
+msgstr ""
+
+msgid "FeatureFlag|There are no configured user lists"
+msgstr ""
+
msgid "FeatureFlag|Type"
msgstr ""
@@ -9932,7 +10186,7 @@ msgid "Filter by two-factor authentication"
msgstr "Фильтр по двухфакторной аутентификации"
msgid "Filter by user"
-msgstr ""
+msgstr "Фильтр по пользователю"
msgid "Filter pipelines"
msgstr ""
@@ -9958,11 +10212,17 @@ msgstr "Отфильтровать проекты по названию"
msgid "Filter..."
msgstr "Фильтр..."
+msgid "Find File"
+msgstr ""
+
+msgid "Find bugs in your code with coverage-guided fuzzing"
+msgstr ""
+
msgid "Find by path"
msgstr "ПоиÑк по пути"
msgid "Find existing members by name"
-msgstr "ПоиÑк текущих учаÑтников"
+msgstr "ПоиÑк учаÑтников"
msgid "Find file"
msgstr "Ðайти файл"
@@ -10222,6 +10482,9 @@ msgstr "ÐаÑтройки Geo"
msgid "Geo allows you to replicate your GitLab instance to other geographical locations."
msgstr "Geo позволÑет вам реплицировать ÑкземплÑÑ€ GitLab в различных географичеÑких положениÑÑ…."
+msgid "Geo nodes are paused using a command run on the node"
+msgstr ""
+
msgid "GeoNodeStatusEvent|%{timeAgoStr} (%{pendingEvents} events)"
msgstr "%{timeAgoStr}(%{pendingEvents} ÑобытиÑ)"
@@ -10306,6 +10569,9 @@ msgstr "СоÑтоÑние узла было обновлено %{timeAgo}."
msgid "GeoNodes|Not checksummed"
msgstr ""
+msgid "GeoNodes|Package files"
+msgstr ""
+
msgid "GeoNodes|Pausing replication stops the sync process. Are you sure?"
msgstr "ПриоÑтановка репликации прекращает процеÑÑ Ñинхронизации. Ð’Ñ‹ уверены?"
@@ -10324,6 +10590,9 @@ msgstr ""
msgid "GeoNodes|Replication slots"
msgstr "Слоты репликации"
+msgid "GeoNodes|Replication status"
+msgstr ""
+
msgid "GeoNodes|Repositories"
msgstr "Репозитории"
@@ -10534,6 +10803,9 @@ msgstr "Синхронизировано в"
msgid "Geo|Synchronization failed - %{error}"
msgstr "Ошибка Ñинхронизации - %{error}"
+msgid "Geo|Synchronization of %{itemTitle} is disabled."
+msgstr ""
+
msgid "Geo|The database is currently %{db_lag} behind the primary node."
msgstr "Ð’ наÑтоÑщее Ð²Ñ€ÐµÐ¼Ñ Ð±Ð°Ð·Ð° данных отÑтает от оÑновного узла на %{db_lag}."
@@ -10846,6 +11118,9 @@ msgstr "Ðа веÑÑŒ Ñкран"
msgid "Go to %{link_to_google_takeout}."
msgstr ""
+msgid "Go to <strong>Issues</strong> > <strong>Boards</strong> to access your personalized learning issue board."
+msgstr ""
+
msgid "Go to Pipelines"
msgstr ""
@@ -10945,9 +11220,6 @@ msgstr "Перейдите к вашим проектам"
msgid "Go to your snippets"
msgstr "Перейти к вашим Ñниппетам"
-msgid "Golden Tanuki"
-msgstr ""
-
msgid "Google Cloud Platform"
msgstr ""
@@ -10969,6 +11241,9 @@ msgstr "ПонÑтно!"
msgid "Grafana URL"
msgstr "URL Grafana"
+msgid "Grafana response contains invalid json"
+msgstr ""
+
msgid "GrafanaIntegration|API Token"
msgstr ""
@@ -11017,9 +11292,6 @@ msgstr "Группа %{group_name} была запланирована Ð´Ð»Ñ Ñƒ
msgid "Group %{group_name} was successfully created."
msgstr "Группа %{group_name} уÑпешно Ñоздана."
-msgid "Group '%{group_name}' is being imported."
-msgstr ""
-
msgid "Group Audit Events"
msgstr ""
@@ -11056,9 +11328,6 @@ msgstr "Ðватар группы"
msgid "Group by:"
msgstr ""
-msgid "Group could not be imported: %{errors}"
-msgstr ""
-
msgid "Group description"
msgstr "ОпиÑание группы"
@@ -11086,9 +11355,15 @@ msgstr ""
msgid "Group has not been marked for deletion"
msgstr ""
+msgid "Group import could not be scheduled"
+msgstr ""
+
msgid "Group info:"
msgstr "Ð˜Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð¾ группе:"
+msgid "Group is required when cluster_type is :group"
+msgstr ""
+
msgid "Group maintainers can register group runners in the %{link}"
msgstr ""
@@ -11152,6 +11427,24 @@ msgstr ""
msgid "GroupActivyMetrics|Recent activity (last 90 days)"
msgstr "ÐедавнÑÑ Ð°ÐºÑ‚Ð¸Ð²Ð½Ð¾ÑÑ‚ÑŒ (за поÑледние 90 дней)"
+msgid "GroupImport|Failed to import group."
+msgstr ""
+
+msgid "GroupImport|Group '%{group_name}' is being imported."
+msgstr ""
+
+msgid "GroupImport|Group could not be imported: %{errors}"
+msgstr ""
+
+msgid "GroupImport|Please wait while we import the group for you. Refresh at will."
+msgstr ""
+
+msgid "GroupImport|The group was successfully imported."
+msgstr ""
+
+msgid "GroupImport|Unable to process group import file"
+msgstr ""
+
msgid "GroupRoadmap|%{dateWord} – No end date"
msgstr "%{dateWord} – Без конечной даты"
@@ -11419,6 +11712,9 @@ msgstr "Группы (%{count})"
msgid "Groups (%{groups})"
msgstr ""
+msgid "Groups and projects"
+msgstr ""
+
msgid "Groups can also be nested by creating %{subgroup_docs_link_start}subgroups%{subgroup_docs_link_end}."
msgstr ""
@@ -11464,6 +11760,33 @@ msgstr "Группы не найдены"
msgid "GroupsEmptyState|You can manage your group member’s permissions and access to each project in the group."
msgstr "Ð’Ñ‹ можете управлÑÑ‚ÑŒ правами и доÑтупом учаÑтников вашей группы к каждому проекту в группе."
+msgid "GroupsNew|Contact an administrator to enable options for importing your group."
+msgstr ""
+
+msgid "GroupsNew|Create"
+msgstr ""
+
+msgid "GroupsNew|Create group"
+msgstr ""
+
+msgid "GroupsNew|GitLab group export"
+msgstr ""
+
+msgid "GroupsNew|Import"
+msgstr ""
+
+msgid "GroupsNew|Import group"
+msgstr ""
+
+msgid "GroupsNew|My Awesome Group"
+msgstr ""
+
+msgid "GroupsNew|No import options available"
+msgstr ""
+
+msgid "GroupsNew|To copy a GitLab group between installations, navigate to the group settings page for the original installation, generate an export file, and upload it here."
+msgstr ""
+
msgid "GroupsTree|Are you sure you want to leave the \"%{fullName}\" group?"
msgstr "Вы уверены, что вы хотите покинуть группу \"%{fullName}\"?"
@@ -11566,11 +11889,14 @@ msgstr "Помогает уменьшить объем оповещений (нÐ
msgid "Helps reduce request volume for protected paths"
msgstr ""
+msgid "Here are all your projects in your group, including the one you just created. To start, let’s take a look at your personalized learning project which will help you learn about GitLab at your own pace."
+msgstr ""
+
msgid "Here you will find recent merge request activity"
msgstr "ЗдеÑÑŒ вы найдёте недавнюю активноÑÑ‚ÑŒ, ÑвÑзанную Ñ Ð·Ð°Ð¿Ñ€Ð¾Ñами на ÑлиÑние"
msgid "Hi %{username}!"
-msgstr ""
+msgstr "Привет, %{username}!"
msgid "Hide archived projects"
msgstr "Скрывать архивные проекты"
@@ -11582,6 +11908,9 @@ msgstr[1] ""
msgstr[2] ""
msgstr[3] ""
+msgid "Hide details"
+msgstr ""
+
msgid "Hide file browser"
msgstr "Скрыть файлы"
@@ -11591,6 +11920,9 @@ msgstr ""
msgid "Hide host keys manual input"
msgstr ""
+msgid "Hide list"
+msgstr ""
+
msgid "Hide marketing-related entries from help"
msgstr ""
@@ -11613,9 +11945,6 @@ msgstr[3] "Скрыть значениÑ"
msgid "Hide values"
msgstr "Скрыть значениÑ"
-msgid "Hiding all labels"
-msgstr "Ð’Ñе метки Ñкрыты"
-
msgid "High or unknown vulnerabilities present"
msgstr "ПриÑутÑтвуют уÑзвимоÑти выÑокого или неизвеÑтного уровнÑ"
@@ -11700,6 +12029,9 @@ msgstr "ID"
msgid "ID:"
msgstr "ID:"
+msgid "IDE"
+msgstr ""
+
msgid "IDE|Allow live previews of JavaScript projects in the Web IDE using CodeSandbox Live Preview."
msgstr ""
@@ -11790,11 +12122,11 @@ msgstr ""
msgid "If enabled"
msgstr "ЕÑли включено"
-msgid "If enabled, access to projects will be validated on an external service using their classification label."
+msgid "If enabled, GitLab will handle Object Storage replication using Geo. %{linkStart}More information%{linkEnd}"
msgstr ""
-msgid "If enabled, and if object storage is enabled, GitLab will handle Object Storage replication using Geo"
-msgstr "ЕÑли включено ÑовмеÑтно Ñ Ñ…Ñ€Ð°Ð½Ð¸Ð»Ð¸Ñ‰ÐµÐ¼ объектов, GitLab будет обрабатывать репликацию хранилища объектов Ñ Ð¿Ð¾Ð¼Ð¾Ñ‰ÑŒÑŽ Geo"
+msgid "If enabled, access to projects will be validated on an external service using their classification label."
+msgstr ""
msgid "If there is no previous license or if the previous license has expired, some GitLab functionality will be blocked until a new, valid license is uploaded."
msgstr ""
@@ -12034,7 +12366,7 @@ msgid "Include author name in notification email body"
msgstr "Включать Ð¸Ð¼Ñ Ð°Ð²Ñ‚Ð¾Ñ€Ð° в текÑте ÑƒÐ²ÐµÐ´Ð¾Ð¼Ð»ÐµÐ½Ð¸Ñ Ð¿Ð¾ Ñлектронной почте"
msgid "Include description in commit message"
-msgstr ""
+msgstr "Включить опиÑание и в опиÑание коммита"
msgid "Include merge request description"
msgstr ""
@@ -12157,6 +12489,9 @@ msgstr[1] "ЭкземплÑров"
msgstr[2] "ЭкземплÑры"
msgstr[3] "ЭкземплÑры"
+msgid "Instance Configuration"
+msgstr ""
+
msgid "Instance Statistics visibility"
msgstr "ВидимоÑÑ‚ÑŒ СтатиÑтики ЭкземплÑра"
@@ -12178,9 +12513,6 @@ msgstr ""
msgid "Integrations"
msgstr "Интеграции"
-msgid "Integrations allow you to integrate GitLab with other applications"
-msgstr ""
-
msgid "Integrations|All details"
msgstr ""
@@ -12292,6 +12624,9 @@ msgstr ""
msgid "Invalid query"
msgstr ""
+msgid "Invalid repository bundle for snippet with id %{snippet_id}"
+msgstr ""
+
msgid "Invalid repository path"
msgstr ""
@@ -12310,6 +12645,9 @@ msgstr ""
msgid "Invalid two-factor code."
msgstr ""
+msgid "Invalid yaml"
+msgstr ""
+
msgid "Invitation"
msgstr ""
@@ -12404,7 +12742,7 @@ msgid "IssueAnalytics|Age"
msgstr ""
msgid "IssueAnalytics|Assignees"
-msgstr ""
+msgstr "ОтветÑтвенные"
msgid "IssueAnalytics|Due date"
msgstr ""
@@ -12529,15 +12867,24 @@ msgstr "Он должен иметь Ñтроку заголовка и по кÑ
msgid "It seems like the Dependency Scanning job ran successfully, but no dependencies have been detected in your project."
msgstr "Похоже, работа по Ñканированию завиÑимоÑтей завершилаÑÑŒ уÑпешно, но в вашем проекте не было обнаружено никаких завиÑимоÑтей."
+msgid "It seems that there is currently no available data for code coverage"
+msgstr ""
+
msgid "It's you"
msgstr "Это вы"
+msgid "Iteration"
+msgstr ""
+
msgid "Iteration changed to"
msgstr ""
msgid "Iteration removed"
msgstr ""
+msgid "Iteration updated"
+msgstr ""
+
msgid "Iterations"
msgstr ""
@@ -12568,12 +12915,18 @@ msgstr "Янв."
msgid "January"
msgstr "Январь"
+msgid "Jira Issues"
+msgstr ""
+
msgid "Jira import is already running."
msgstr "Импорт из Jira уже выполнÑетÑÑ."
msgid "Jira integration not configured."
msgstr "Ð˜Ð½Ñ‚ÐµÐ³Ñ€Ð°Ñ†Ð¸Ñ Ñ Jira не наÑтроена."
+msgid "Jira project key is not configured"
+msgstr ""
+
msgid "Jira project: %{importProject}"
msgstr "Проект Jira: %{importProject}"
@@ -12754,6 +13107,9 @@ msgstr "Только Ñ"
msgid "Keep divergent refs"
msgstr ""
+msgid "Kerberos access denied"
+msgstr ""
+
msgid "Key"
msgstr "Ключ"
@@ -12763,12 +13119,15 @@ msgstr ""
msgid "Key: %{key}"
msgstr "Ключ: %{key}"
-msgid "Keyboard Shortcuts"
-msgstr "«ГорÑчие» клавиши"
-
msgid "Keyboard shortcuts"
msgstr ""
+msgid "Keys"
+msgstr ""
+
+msgid "Ki"
+msgstr ""
+
msgid "Kubernetes"
msgstr "Kubernetes"
@@ -12808,6 +13167,9 @@ msgstr ""
msgid "LDAP"
msgstr "LDAP"
+msgid "LDAP Synchronization"
+msgstr ""
+
msgid "LDAP settings"
msgstr "ÐаÑтройки LDAP"
@@ -13034,12 +13396,18 @@ msgstr "Подробнее об ÑоглаÑовании."
msgid "Learn more about custom project templates"
msgstr "Узнайте больше о пользовательÑких шаблонах проекта"
+msgid "Learn more about deploying to AWS"
+msgstr ""
+
msgid "Learn more about deploying to a cluster"
msgstr "Узнайте больше о развертывании в клаÑтер"
msgid "Learn more about group-level project templates"
msgstr "Узнать больше о шаблонах проекта группового уровнÑ"
+msgid "Learn more about job dependencies"
+msgstr ""
+
msgid "Learn more about signing commits"
msgstr "Узнайте больше о подпиÑывании коммитов"
@@ -13091,9 +13459,21 @@ msgstr "Служба комплаенÑа лицензий"
msgid "License History"
msgstr ""
+msgid "License ID:"
+msgstr ""
+
+msgid "License URL"
+msgstr ""
+
msgid "License-Check"
msgstr ""
+msgid "LicenseCompliance|%{docLinkStart}License Approvals%{docLinkEnd} are active"
+msgstr ""
+
+msgid "LicenseCompliance|%{docLinkStart}License Approvals%{docLinkEnd} are inactive"
+msgstr ""
+
msgid "LicenseCompliance|Add a license"
msgstr "Добавить лицензию"
@@ -13115,9 +13495,15 @@ msgstr "Запрещено"
msgid "LicenseCompliance|Deny"
msgstr "Запретить"
+msgid "LicenseCompliance|Learn more about %{linkStart}License Approvals%{linkEnd}"
+msgstr ""
+
msgid "LicenseCompliance|License"
msgstr "ЛицензиÑ"
+msgid "LicenseCompliance|License Approvals"
+msgstr ""
+
msgid "LicenseCompliance|License Compliance detected %d license and policy violation for the source branch only; approval required"
msgid_plural "LicenseCompliance|License Compliance detected %d licenses and policy violations for the source branch only; approval required"
msgstr[0] "Служба комплаенÑа лицензий обнаружила %d лицензию и нарушение только Ð´Ð»Ñ Ð¸Ñходной ветки; необходимо утверждение"
@@ -13348,6 +13734,9 @@ msgstr ""
msgid "List your Bitbucket Server repositories"
msgstr "СпиÑок репозиториев из Bitbucket Server"
+msgid "Lists"
+msgstr ""
+
msgid "Live preview"
msgstr "Предварительный проÑмотр в реальном времени"
@@ -13531,6 +13920,9 @@ msgstr ""
msgid "Manage project labels"
msgstr "Управление метками проекта"
+msgid "Manage storage usage"
+msgstr ""
+
msgid "Manage two-factor authentication"
msgstr "Управление двухфакторной аутентификацией"
@@ -13546,6 +13938,9 @@ msgstr "МанифеÑÑ‚"
msgid "Manifest file import"
msgstr "Импорт файла манифеÑта"
+msgid "Manifest import"
+msgstr ""
+
msgid "Manual job"
msgstr "Ручное задание"
@@ -13658,7 +14053,7 @@ msgid "Maven Metadata"
msgstr ""
msgid "Max access level"
-msgstr ""
+msgstr "МакÑимальный уровень доÑтупа"
msgid "Max seats used"
msgstr "МакÑимальное количеÑтво иÑпользуемых меÑÑ‚"
@@ -13693,6 +14088,12 @@ msgstr "МакÑÐ¸Ð¼Ð°Ð»ÑŒÐ½Ð°Ñ Ð¿Ñ€Ð¾Ð´Ð¾Ð»Ð¶Ð¸Ñ‚ÐµÐ»ÑŒÐ½Ð¾ÑÑ‚ÑŒ ÑеÑÑии
msgid "Maximum field length"
msgstr "МакÑÐ¸Ð¼Ð°Ð»ÑŒÐ½Ð°Ñ Ð´Ð»Ð¸Ð½Ð° полÑ"
+msgid "Maximum file size is 2MB. Please select a smaller file."
+msgstr ""
+
+msgid "Maximum import size (MB)"
+msgstr ""
+
msgid "Maximum job timeout"
msgstr ""
@@ -13732,6 +14133,9 @@ msgstr "МакÑимальный размер на каждый репозито
msgid "Maximum size of Elasticsearch bulk indexing requests."
msgstr ""
+msgid "Maximum size of import files."
+msgstr ""
+
msgid "Maximum size of individual attachments in comments."
msgstr "МакÑимальный размер отдельных вложений в комментариÑÑ…."
@@ -13742,7 +14146,7 @@ msgid "May"
msgstr "Май"
msgid "Measured in bytes of code. Excludes generated and vendored code."
-msgstr ""
+msgstr "ИзмерÑетÑÑ Ð² байтах кода. ИÑключает Ñгенерированный и вендорÑкий код."
msgid "Median"
msgstr "Среднее"
@@ -13783,6 +14187,9 @@ msgstr ""
msgid "Merge (when the pipeline succeeds)"
msgstr ""
+msgid "Merge Conflicts"
+msgstr ""
+
msgid "Merge Request"
msgstr "Ð—Ð°Ð¿Ñ€Ð¾Ñ Ð½Ð° ÑлиÑние"
@@ -13790,7 +14197,7 @@ msgid "Merge Request Approvals"
msgstr "ÐŸÐ¾Ð´Ñ‚Ð²ÐµÑ€Ð¶Ð´ÐµÐ½Ð¸Ñ Ð·Ð°Ð¿Ñ€Ð¾Ñов на ÑлиÑние"
msgid "Merge Request Commits"
-msgstr ""
+msgstr "Коммиты запроÑов на ÑлиÑние"
msgid "Merge Requests"
msgstr "ЗапроÑÑ‹ на ÑлиÑние"
@@ -13832,7 +14239,7 @@ msgid "Merge request approvals"
msgstr "Подтверждение запроÑов на ÑлиÑние"
msgid "Merge request approvals allow you to set the number of necessary approvals and predefine a list of approvers that will need to approve every merge request in a project."
-msgstr ""
+msgstr "Ð£Ñ‚Ð²ÐµÑ€Ð¶Ð´ÐµÐ½Ð¸Ñ Ð·Ð°Ð¿Ñ€Ð¾Ñа на ÑлиÑние позволÑÑŽÑ‚ вам наÑтроить количеÑтво необходимых утверждений и заранее определить ÑпиÑок утверждающих, которым которым необходимо будет утверждать каждый Ð·Ð°Ð¿Ñ€Ð¾Ñ Ð½Ð° ÑлиÑние в проекте."
msgid "Merge request dependencies"
msgstr "ЗавиÑимоÑти запроÑа на ÑлиÑние"
@@ -13909,6 +14316,9 @@ msgstr ""
msgid "MergeRequests|Squash task canceled: another squash is already in progress."
msgstr "Задача Ð¾Ð±ÑŠÐµÐ´Ð¸Ð½ÐµÐ½Ð¸Ñ Ð¾Ñ‚Ð¼ÐµÐ½ÐµÐ½Ð°: Ð´Ñ€ÑƒÐ³Ð°Ñ Ð¾Ð¿ÐµÑ€Ð°Ñ†Ð¸Ñ Ð¾Ð±ÑŠÐµÐ´Ð¸Ð½ÐµÐ½Ð¸Ñ ÑƒÐ¶Ðµ выполнÑетÑÑ."
+msgid "MergeRequests|This project does not allow squashing commits when merge requests are accepted."
+msgstr ""
+
msgid "MergeRequests|Thread stays resolved"
msgstr ""
@@ -14062,6 +14472,12 @@ msgstr ""
msgid "MetricsSettings|Add a button to the metrics dashboard linking directly to your existing external dashboard."
msgstr ""
+msgid "MetricsSettings|Choose whether to display dashboard metrics in UTC or the user's local timezone."
+msgstr ""
+
+msgid "MetricsSettings|Dashboard timezone"
+msgstr ""
+
msgid "MetricsSettings|External dashboard URL"
msgstr ""
@@ -14071,6 +14487,12 @@ msgstr ""
msgid "MetricsSettings|Metrics Dashboard"
msgstr ""
+msgid "MetricsSettings|UTC (Coordinated Universal Time)"
+msgstr ""
+
+msgid "MetricsSettings|User's local timezone"
+msgstr ""
+
msgid "Metrics|Add metric"
msgstr "Добавить метрику"
@@ -14086,6 +14508,9 @@ msgstr "Создать пользовательÑкую панель управÐ
msgid "Metrics|Create metric"
msgstr "Создать метрику"
+msgid "Metrics|Current"
+msgstr ""
+
msgid "Metrics|Delete metric"
msgstr "Удалить метрику"
@@ -14138,6 +14563,9 @@ msgstr ""
msgid "Metrics|Max"
msgstr "МакÑ"
+msgid "Metrics|Min"
+msgstr ""
+
msgid "Metrics|Must be a valid PromQL query."
msgstr ""
@@ -14153,6 +14581,9 @@ msgstr "Ð”Ð¾ÐºÑƒÐ¼ÐµÐ½Ñ‚Ð°Ñ†Ð¸Ñ Ð¿Ð¾ запроÑам Prometheus"
msgid "Metrics|Refresh dashboard"
msgstr "Обновить панель управлениÑ"
+msgid "Metrics|Select a value"
+msgstr ""
+
msgid "Metrics|Star dashboard"
msgstr ""
@@ -14171,12 +14602,18 @@ msgstr "Произошла ошибка при извлечении данных
msgid "Metrics|There was an error getting annotations information."
msgstr "Произошла ошибка при получении информации об аннотациÑÑ…."
+msgid "Metrics|There was an error getting dashboard validation warnings information."
+msgstr ""
+
msgid "Metrics|There was an error getting deployment information."
msgstr "Произошла ошибка при получении Ñведений о развёртывании."
msgid "Metrics|There was an error getting environments information."
msgstr "Произошла ошибка при получении информации об окружениÑÑ…."
+msgid "Metrics|There was an error getting options for variable \"%{name}\"."
+msgstr ""
+
msgid "Metrics|There was an error trying to validate your query"
msgstr "Произошла ошибка при попытке проверить ваш запроÑ"
@@ -14219,6 +14656,9 @@ msgstr ""
msgid "Metrics|You're about to permanently delete this metric. This cannot be undone."
msgstr "Ð’Ñ‹ ÑобираетеÑÑŒ навÑегда удалить Ñту метрику. Это дейÑтвие не может быть отменено."
+msgid "Metrics|Your dashboard schema is invalid. Edit the dashboard to correct the YAML schema."
+msgstr ""
+
msgid "Metrics|e.g. HTTP requests"
msgstr "например, HTTP-запроÑÑ‹"
@@ -14234,12 +14674,18 @@ msgstr "например, ÑкороÑÑ‚ÑŒ(http_requests_total[5m])"
msgid "Metrics|e.g. req/sec"
msgstr "например, запроÑов в Ñекунду"
+msgid "Mi"
+msgstr ""
+
msgid "Microsoft Azure"
msgstr ""
msgid "Middleman project with Static Site Editor support"
msgstr ""
+msgid "Migrate your data from an external source like GitHub, Bitbucket, or another instance of GitLab."
+msgstr ""
+
msgid "Migrated %{success_count}/%{total_count} files."
msgstr "ПеренеÑено %{success_count}/%{total_count} файлов."
@@ -14275,7 +14721,7 @@ msgid "MilestoneSidebar|From"
msgstr ""
msgid "MilestoneSidebar|Issues"
-msgstr ""
+msgstr "ОбÑуждениÑ"
msgid "MilestoneSidebar|Merge requests"
msgstr ""
@@ -14284,10 +14730,10 @@ msgid "MilestoneSidebar|Merged:"
msgstr ""
msgid "MilestoneSidebar|New Issue"
-msgstr ""
+msgstr "Ðовое обÑуждение"
msgid "MilestoneSidebar|New issue"
-msgstr ""
+msgstr "Ðовое обÑуждение"
msgid "MilestoneSidebar|No due date"
msgstr ""
@@ -14376,6 +14822,9 @@ msgstr ""
msgid "Minimum capacity to be available before we schedule more mirrors preemptively."
msgstr ""
+msgid "Minimum interval in days"
+msgstr ""
+
msgid "Minimum length is %{minimum_password_length} characters"
msgstr "ÐœÐ¸Ð½Ð¸Ð¼Ð°Ð»ÑŒÐ½Ð°Ñ Ð´Ð»Ð¸Ð½Ð° - %{minimum_password_length} Ñимволов"
@@ -14562,6 +15011,12 @@ msgstr "Ðайдено неÑколько типов моделей: %{model_typ
msgid "Multiple uploaders found: %{uploader_types}"
msgstr "Ðайдено неÑколько загрузчиков: %{uploader_types}"
+msgid "Must match with the %{codeStart}external_url%{codeEnd} in %{codeStart}/etc/gitlab/gitlab.rb%{codeEnd}."
+msgstr ""
+
+msgid "Must match with the %{codeStart}geo_node_name%{codeEnd} in %{codeStart}/etc/gitlab/gitlab.rb%{codeEnd}. %{linkStart}More information%{linkEnd}"
+msgstr ""
+
msgid "My Awesome Group"
msgstr ""
@@ -14676,11 +15131,17 @@ msgstr "Ðовый"
msgid "New Application"
msgstr "Ðовое Приложение"
+msgid "New Branch"
+msgstr ""
+
+msgid "New Deploy Key"
+msgstr ""
+
msgid "New Environment"
msgstr "Ðовое окружение"
-msgid "New Geo Node"
-msgstr "Ðовый узел Geo"
+msgid "New File"
+msgstr ""
msgid "New Group"
msgstr "ÐÐ¾Ð²Ð°Ñ Ð“Ñ€ÑƒÐ¿Ð¿Ð°"
@@ -14698,15 +15159,15 @@ msgstr[1] "Ðовых ОбÑуждениÑ"
msgstr[2] "Ðовых ОбÑуждений"
msgstr[3] "Ðовые ОбÑуждениÑ"
-msgid "New Iteration"
-msgstr ""
-
msgid "New Jira import"
msgstr "Ðовый импорт из Jira"
msgid "New Label"
msgstr "ÐÐ¾Ð²Ð°Ñ Ð¼ÐµÑ‚ÐºÐ°"
+msgid "New Merge Request"
+msgstr ""
+
msgid "New Milestone"
msgstr "Ðовый Этап"
@@ -14725,6 +15186,9 @@ msgstr "Ðовый проект"
msgid "New Snippet"
msgstr "Ðовый Ñниппет"
+msgid "New User"
+msgstr ""
+
msgid "New branch"
msgstr "ÐÐ¾Ð²Ð°Ñ Ð²ÐµÑ‚ÐºÐ°"
@@ -14765,6 +15229,9 @@ msgid "New issue"
msgstr "Ðовое обÑуждение"
msgid "New issue title"
+msgstr "Заголовок нового обÑуждениÑ"
+
+msgid "New iteration"
msgstr ""
msgid "New iteration created"
@@ -14828,7 +15295,7 @@ msgid "Next"
msgstr "Next"
msgid "Next commit"
-msgstr ""
+msgstr "Следующий коммит"
msgid "Next file in diff"
msgstr ""
@@ -14867,7 +15334,7 @@ msgid "No application_settings found"
msgstr ""
msgid "No approvers"
-msgstr ""
+msgstr "Без утверждающих"
msgid "No authentication methods configured."
msgstr "Методы аутентификации не наÑтроены."
@@ -14944,6 +15411,12 @@ msgstr ""
msgid "No grouping"
msgstr ""
+msgid "No iteration"
+msgstr ""
+
+msgid "No iterations to show"
+msgstr ""
+
msgid "No job log"
msgstr ""
@@ -14986,6 +15459,9 @@ msgstr "Ðет Ñтапов Ð´Ð»Ñ Ð¾Ñ‚Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð¸Ñ"
msgid "No other labels with such name or description"
msgstr "Ðет других меток Ñ Ñ‚Ð°ÐºÐ¸Ð¼ наименованием или опиÑанием"
+msgid "No panels matching properties %{opts}"
+msgstr ""
+
msgid "No parent group"
msgstr ""
@@ -15005,7 +15481,7 @@ msgid "No public groups"
msgstr "Ðет публичных групп"
msgid "No related merge requests found."
-msgstr ""
+msgstr "СвÑзанные запроÑÑ‹ на ÑлиÑние не найдены."
msgid "No repository"
msgstr "Ðет репозиториÑ"
@@ -15019,6 +15495,9 @@ msgstr ""
msgid "No schedules"
msgstr "Ðет раÑпиÑаний"
+msgid "No stack trace for this error"
+msgstr ""
+
msgid "No starrers matched your search"
msgstr ""
@@ -15031,7 +15510,7 @@ msgstr ""
msgid "No test coverage"
msgstr "Без Ð¿Ð¾ÐºÑ€Ñ‹Ñ‚Ð¸Ñ Ñ‚ÐµÑтами"
-msgid "No thanks, don't show this again"
+msgid "No thanks"
msgstr ""
msgid "No vulnerabilities present"
@@ -15046,9 +15525,6 @@ msgstr "Ðе беÑпокойтеÑÑŒ, вы вÑÑ‘ ещё можете иÑпоÐ
msgid "No, directly import the existing email addresses and usernames."
msgstr ""
-msgid "No, not interested right now"
-msgstr "Ðет, ÑÐµÐ¹Ñ‡Ð°Ñ Ð½Ðµ интереÑно"
-
msgid "No. of commits"
msgstr ""
@@ -15094,9 +15570,6 @@ msgstr "ÐедоÑтаточно данных"
msgid "Not found."
msgstr ""
-msgid "Not helpful"
-msgstr "Ðе полезно"
-
msgid "Not now"
msgstr ""
@@ -15253,6 +15726,9 @@ msgstr "Ð£Ð²ÐµÐ´Ð¾Ð¼Ð»ÐµÐ½Ð¸Ñ Ð¾Ñ‚ÐºÐ»ÑŽÑ‡ÐµÐ½Ñ‹"
msgid "Notifications on"
msgstr "Ð£Ð²ÐµÐ´Ð¾Ð¼Ð»ÐµÐ½Ð¸Ñ Ð²ÐºÐ»ÑŽÑ‡ÐµÐ½Ñ‹"
+msgid "Notify users by email when sign-in location is not recognized"
+msgstr ""
+
msgid "Nov"
msgstr "ÐоÑб."
@@ -15263,7 +15739,7 @@ msgid "Novice"
msgstr ""
msgid "Now you can access the merge request navigation tabs at the top, where they’re easier to find."
-msgstr ""
+msgstr "Теперь вы можете получить доÑтуп к вкладкам запроÑа на ÑлиÑние вверху, где их проще найти."
msgid "Nuget metadatum must have at least license_url, project_url or icon_url set"
msgstr ""
@@ -15319,9 +15795,6 @@ msgstr "Фильтр"
msgid "Oh no!"
msgstr "О, нет!"
-msgid "Ok let's go"
-msgstr ""
-
msgid "Oldest first"
msgstr ""
@@ -15334,16 +15807,49 @@ msgstr ""
msgid "On track"
msgstr "По плану"
+msgid "OnDemandScans|Attached branch"
+msgstr ""
+
+msgid "OnDemandScans|Attached branch is where the scan job runs."
+msgstr ""
+
+msgid "OnDemandScans|Could not run the scan. Please try again."
+msgstr ""
+
msgid "OnDemandScans|Create new DAST scan"
msgstr ""
+msgid "OnDemandScans|DAST will scan the target URL and any discovered sub URLs."
+msgstr ""
+
+msgid "OnDemandScans|New on-demand DAST scan"
+msgstr ""
+
msgid "OnDemandScans|On-demand Scans"
msgstr ""
+msgid "OnDemandScans|On-demand scans run outside the DevOps cycle and find vulnerabilities in your projects. %{learnMoreLinkStart}Learn more%{learnMoreLinkEnd}"
+msgstr ""
+
+msgid "OnDemandScans|Only a passive scan can be performed on demand."
+msgstr ""
+
+msgid "OnDemandScans|Passive DAST Scan"
+msgstr ""
+
+msgid "OnDemandScans|Please enter a valid URL format, ex: http://www.example.com/home"
+msgstr ""
+
+msgid "OnDemandScans|Run this scan"
+msgstr ""
+
+msgid "OnDemandScans|Scan mode"
+msgstr ""
+
msgid "OnDemandScans|Schedule or run scans immediately against target sites. Currently available on-demand scan type: DAST. %{helpLinkStart}More information%{helpLinkEnd}"
msgstr ""
-msgid "Onboarding"
+msgid "OnDemandScans|Target URL"
msgstr ""
msgid "Once imported, repositories can be mirrored over SSH. Read more %{link_start}here%{link_end}."
@@ -15371,6 +15877,9 @@ msgstr "Одна или неÑколько групп, к которым у ва
msgid "One or more of you personal access tokens were revoked"
msgstr ""
+msgid "One or more of your %{provider} projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
+msgstr ""
+
msgid "One or more of your Bitbucket projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
msgstr "Один или неÑколько проектов Bitbucket не могут быть импортировать в GitLab, потому что они иÑпользуют Subversion или Mercurial Ð´Ð»Ñ ÑƒÐ¿Ñ€Ð°Ð²Ð»ÐµÐ½Ð¸Ñ Ð²ÐµÑ€ÑиÑми, а не Git."
@@ -15872,6 +16381,9 @@ msgstr "РодительÑÐºÐ°Ñ Ñ†ÐµÐ»ÑŒ не ÑущеÑтвует."
msgid "Parent epic is not present."
msgstr ""
+msgid "Parsing error for param :embed_json. %{message}"
+msgstr ""
+
msgid "Part of merge request changes"
msgstr "ЧаÑÑ‚ÑŒ изменений запроÑа на ÑлиÑние"
@@ -15884,6 +16396,9 @@ msgstr ""
msgid "Passed"
msgstr ""
+msgid "Passed on"
+msgstr ""
+
msgid "Password"
msgstr "Пароль"
@@ -15936,7 +16451,7 @@ msgid "Path:"
msgstr "Путь:"
msgid "Paths can contain wildcards, like */welcome"
-msgstr ""
+msgstr "Путь необÑзательно должен быть абÑолютным, а может ÑвлÑÑ‚ÑŒÑÑ ÑˆÐ°Ð±Ð»Ð¾Ð½Ð¾Ð¼, например */your_group"
msgid "Pause"
msgstr "ПриоÑтановить"
@@ -15968,6 +16483,9 @@ msgstr ""
msgid "Perform common operations on GitLab project"
msgstr "ВыполнÑйте обычные дейÑÑ‚Ð²Ð¸Ñ Ð½Ð°Ð´ проектом GitLab"
+msgid "Performance and resource management"
+msgstr ""
+
msgid "Performance optimization"
msgstr "ÐžÐ¿Ñ‚Ð¸Ð¼Ð¸Ð·Ð°Ñ†Ð¸Ñ Ð¿Ñ€Ð¾Ð¸Ð·Ð²Ð¾Ð´Ð¸Ñ‚ÐµÐ»ÑŒÐ½Ð¾Ñти"
@@ -16175,8 +16693,8 @@ msgstr "ЗапуÑтить Ñборочную линию"
msgid "Pipelines|Something went wrong while cleaning runners cache."
msgstr "Что-то пошло не так, при очиÑтке кÑша обработчиков заданий."
-msgid "Pipelines|There are currently no %{scope} pipelines."
-msgstr "Ð’ наÑтоÑщее Ð²Ñ€ÐµÐ¼Ñ Ð½ÐµÑ‚ %{scope} Ñборочных линий."
+msgid "Pipelines|There are currently no finished pipelines."
+msgstr ""
msgid "Pipelines|There are currently no pipelines."
msgstr "Ð’ наÑтоÑщее Ð²Ñ€ÐµÐ¼Ñ Ð½ÐµÑ‚ Ñборочных линий."
@@ -16286,6 +16804,9 @@ msgstr "ОÑтановить Ñборочную линию"
msgid "Pipeline|Stop pipeline #%{pipelineId}?"
msgstr "ОÑтановить Ñборочную линию #%{pipelineId}?"
+msgid "Pipeline|Tag name"
+msgstr ""
+
msgid "Pipeline|Trigger author"
msgstr ""
@@ -16367,12 +16888,18 @@ msgstr ""
msgid "Please check your email (%{email}) to verify that you own this address and unlock the power of CI/CD. Didn't receive it? %{resend_link}. Wrong email address? %{update_link}."
msgstr ""
+msgid "Please choose a file"
+msgstr ""
+
msgid "Please choose a group URL with no special characters."
msgstr ""
msgid "Please complete your profile with email address"
msgstr ""
+msgid "Please convert %{linkStart}them to Git%{linkEnd}, and go through the %{linkToImportFlow} again."
+msgstr ""
+
msgid "Please convert them to %{link_to_git}, and go through the %{link_to_import_flow} again."
msgstr ""
@@ -16430,6 +16957,9 @@ msgstr ""
msgid "Please provide a valid email address."
msgstr ""
+msgid "Please provide attributes to update"
+msgstr ""
+
msgid "Please refer to <a href=\"%{docs_url}\">%{docs_url}</a>"
msgstr ""
@@ -16614,7 +17144,7 @@ msgid "Prevent users from changing their profile name"
msgstr ""
msgid "Prevent users from modifying merge request approvers list"
-msgstr ""
+msgstr "Запретить пользователÑм изменÑÑ‚ÑŒ ÑпиÑок утверждающих запроÑа на ÑлиÑние"
msgid "Prevent users from performing write operations on GitLab while performing maintenance."
msgstr ""
@@ -16662,7 +17192,7 @@ msgid "Private"
msgstr "Приватный"
msgid "Private - Project access must be granted explicitly to each user. If this project is part of a group, access will be granted to members of the group."
-msgstr ""
+msgstr "Приватный - ДоÑтуп должен быть предоÑтавлен Ñвно каждому пользователю. ЕÑли проект ÑвлÑетÑÑ Ñ‡Ð°Ñтью группы, он наÑледует учаÑтников и их привилегии."
msgid "Private - The group and its projects can only be viewed by members."
msgstr "ÐŸÑ€Ð¸Ð²Ð°Ñ‚Ð½Ð°Ñ - Группу и включённые в неё проекты могут видеть только члены группы."
@@ -17118,7 +17648,7 @@ msgid "Project URL"
msgstr "ÐÐ´Ñ€ÐµÑ ÐŸÑ€Ð¾ÐµÐºÑ‚Ð°"
msgid "Project access must be granted explicitly to each user. If this project is part of a group, access will be granted to members of the group."
-msgstr ""
+msgstr "ДоÑтуп должен быть предоÑтавлен Ñвно каждому пользователю. ЕÑли проект ÑвлÑетÑÑ Ñ‡Ð°Ñтью группы, он наÑледует учаÑтников и их привилегии."
msgid "Project already deleted"
msgstr ""
@@ -17165,6 +17695,9 @@ msgstr ""
msgid "Project has too many %{label_for_message} to search"
msgstr "Проект имеет Ñлишком много %{label_for_message} Ð´Ð»Ñ Ð¿Ð¾Ð¸Ñка"
+msgid "Project is required when cluster_type is :project"
+msgstr ""
+
msgid "Project members"
msgstr "УчаÑтники проекта"
@@ -17184,7 +17717,7 @@ msgid "Project order will not be saved as local storage is not available."
msgstr ""
msgid "Project overview"
-msgstr ""
+msgstr "Обзор проекта"
msgid "Project path"
msgstr ""
@@ -17282,7 +17815,40 @@ msgstr ""
msgid "ProjectService|Comment"
msgstr ""
-msgid "ProjectService|Comment will be posted on each event"
+msgid "ProjectService|Event will be triggered by a push to the repository"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a commit is created/updated"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a confidential issue is created/updated/closed"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a deployment finishes"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a merge request is created/updated/merged"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a new tag is pushed to the repository"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a new, unique alert is recorded"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a pipeline status changes"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a wiki page is created/updated"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when an issue is created/updated/closed"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when someone adds a comment"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when someone adds a comment on a confidential issue"
msgstr ""
msgid "ProjectService|Perform common operations on GitLab project: %{project_name}"
@@ -17333,6 +17899,9 @@ msgstr "Отключить ÑƒÐ²ÐµÐ´Ð¾Ð¼Ð»ÐµÐ½Ð¸Ñ Ð¿Ð¾ Ñлектронной п
msgid "ProjectSettings|Enable 'Delete source branch' option by default"
msgstr ""
+msgid "ProjectSettings|Enable merge trains and pipelines for merged results"
+msgstr ""
+
msgid "ProjectSettings|Every merge creates a merge commit"
msgstr "Каждый ÑлиÑние Ñоздает коммит ÑлиÑниÑ"
@@ -17363,8 +17932,8 @@ msgstr "Только быÑтрые ÑлиÑниÑ"
msgid "ProjectSettings|Forks"
msgstr "ОтветвлениÑ"
-msgid "ProjectSettings|Git Large File Storage"
-msgstr "Хранилище больших файлов Git"
+msgid "ProjectSettings|Git Large File Storage (LFS)"
+msgstr ""
msgid "ProjectSettings|Internal"
msgstr "ВнутреннÑÑ"
@@ -17399,9 +17968,6 @@ msgstr "Метод ÑлиÑниÑ"
msgid "ProjectSettings|Merge options"
msgstr "Параметры ÑлиÑниÑ"
-msgid "ProjectSettings|Merge pipelines will try to validate the post-merge result prior to merging"
-msgstr "Перед ÑлиÑнием Ñборочные линии ÑлиÑний попытаютÑÑ Ð¿Ñ€Ð¾Ð²ÐµÑ€Ð¸Ñ‚ÑŒ результат поÑле ÑлиÑниÑ"
-
msgid "ProjectSettings|Merge requests"
msgstr "ЗапроÑÑ‹ на ÑлиÑние"
@@ -17429,6 +17995,9 @@ msgstr "Страницы Ð´Ð»Ñ Ð´Ð¾ÐºÑƒÐ¼ÐµÐ½Ñ‚Ð°Ñ†Ð¸Ð¸ проекта"
msgid "ProjectSettings|Pipelines"
msgstr "Сборочные линии"
+msgid "ProjectSettings|Pipelines for merge requests must be enabled in the CI/CD configuration file, or pipelines could be unresolvable or dropped"
+msgstr ""
+
msgid "ProjectSettings|Pipelines must succeed"
msgstr "Сборочные линии должны уÑпешно выполнитьÑÑ"
@@ -17456,6 +18025,9 @@ msgstr ""
msgid "ProjectSettings|Show link to create/view merge request when pushing from the command line"
msgstr "Показать ÑÑылку Ð´Ð»Ñ ÑÐ¾Ð·Ð´Ð°Ð½Ð¸Ñ / проÑмотра запроÑа на ÑлиÑние при отправке из командной Ñтроки"
+msgid "ProjectSettings|Skipped pipelines are considered successful"
+msgstr ""
+
msgid "ProjectSettings|Snippets"
msgstr "Сниппеты"
@@ -17471,6 +18043,9 @@ msgstr "Переменные, которые поддерживаютÑÑ GitLab
msgid "ProjectSettings|These checks must pass before merge requests can be merged"
msgstr "Эти проверки должны пройти, прежде чем запроÑÑ‹ на ÑлиÑние будут объединены"
+msgid "ProjectSettings|This introduces the risk of merging changes that will not pass the pipeline."
+msgstr ""
+
msgid "ProjectSettings|This setting is applied on the server level and can be overridden by an admin."
msgstr "Эта наÑтройка применÑетÑÑ Ð½Ð° уровне Ñервера и может быть переопределена админиÑтратором."
@@ -17648,15 +18223,27 @@ msgstr "ПуÑтой"
msgid "ProjectsNew|Blank project"
msgstr "Ðовый проект"
+msgid "ProjectsNew|Connect your external repository to GitLab CI/CD."
+msgstr ""
+
msgid "ProjectsNew|Contact an administrator to enable options for importing your project."
msgstr "СвÑжитеÑÑŒ Ñ Ð°Ð´Ð¼Ð¸Ð½Ð¸Ñтратором, чтобы включить параметры импорта вашего проекта."
msgid "ProjectsNew|Create"
msgstr ""
+msgid "ProjectsNew|Create a blank project to house your files, plan your work, and collaborate on code, among other things."
+msgstr ""
+
+msgid "ProjectsNew|Create blank project"
+msgstr ""
+
msgid "ProjectsNew|Create from template"
msgstr "Создать из шаблона"
+msgid "ProjectsNew|Create new project"
+msgstr ""
+
msgid "ProjectsNew|Creating project & repository."
msgstr "Создание проекта и репозиториÑ."
@@ -17681,6 +18268,9 @@ msgstr "ПожалуйÑта подождите, Ñта Ñтраница автÐ
msgid "ProjectsNew|Project description %{tag_start}(optional)%{tag_end}"
msgstr "ОпиÑание проекта %{tag_start}(необÑзательно)%{tag_end}"
+msgid "ProjectsNew|Run CI/CD for external repository"
+msgstr ""
+
msgid "ProjectsNew|Template"
msgstr "Шаблон"
@@ -17942,6 +18532,9 @@ msgstr ""
msgid "Protected Branch"
msgstr ""
+msgid "Protected Branches"
+msgstr ""
+
msgid "Protected Environment"
msgstr ""
@@ -17954,6 +18547,9 @@ msgstr ""
msgid "Protected Tag"
msgstr "Ð—Ð°Ñ‰Ð¸Ñ‰ÐµÐ½Ð½Ð°Ñ Ð¼ÐµÑ‚ÐºÐ°"
+msgid "Protected Tags"
+msgstr ""
+
msgid "Protected branches"
msgstr "Защищённые ветви"
@@ -18000,7 +18596,7 @@ msgid "ProtectedBranch|Toggle code owner approval"
msgstr "Переключить утверждение владельцами кода"
msgid "ProtectedEnvironment|%{environment_name} will be writable for developers. Are you sure?"
-msgstr ""
+msgstr "%{environment_name} будет доÑтупно Ð´Ð»Ñ Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ñ€Ð°Ð·Ñ€Ð°Ð±Ð¾Ñ‚Ñ‡Ð¸ÐºÐ°Ð¼Ð¸. Ð’Ñ‹ уверены?"
msgid "ProtectedEnvironment|Allowed to deploy"
msgstr ""
@@ -18053,6 +18649,9 @@ msgstr ""
msgid "Provider"
msgstr "Провайдер"
+msgid "Proxy support for this API is not available currently"
+msgstr ""
+
msgid "Pseudonymizer data collection"
msgstr ""
@@ -18077,6 +18676,9 @@ msgstr ""
msgid "Publish to status page"
msgstr ""
+msgid "Published on status page"
+msgstr ""
+
msgid "Publishes this issue to the associated status page."
msgstr ""
@@ -18251,6 +18853,9 @@ msgstr ""
msgid "Recipe"
msgstr ""
+msgid "Reconfigure"
+msgstr ""
+
msgid "Recover hidden stage"
msgstr "ВоÑÑтановить Ñкрытый Ñтап"
@@ -18339,6 +18944,9 @@ msgstr ""
msgid "Registration|Your profile"
msgstr ""
+msgid "Regulate approvals by authors/committers, based on compliance frameworks. Can be changed only at the instance level."
+msgstr ""
+
msgid "Rejected (closed)"
msgstr ""
@@ -18358,7 +18966,7 @@ msgid "Related Merged Requests"
msgstr "СвÑзанные Влитые ЗапроÑÑ‹"
msgid "Related merge requests"
-msgstr ""
+msgstr "СвÑзанные запроÑÑ‹ на ÑлиÑние"
msgid "Relates to"
msgstr ""
@@ -18391,15 +18999,24 @@ msgstr ""
msgid "ReleaseAssetLinkType|Image"
msgstr ""
+msgid "ReleaseAssetLinkType|Images"
+msgstr ""
+
msgid "ReleaseAssetLinkType|Other"
msgstr ""
msgid "ReleaseAssetLinkType|Package"
msgstr ""
+msgid "ReleaseAssetLinkType|Packages"
+msgstr ""
+
msgid "ReleaseAssetLinkType|Runbook"
msgstr ""
+msgid "ReleaseAssetLinkType|Runbooks"
+msgstr ""
+
msgid "Releases"
msgstr "Релизы"
@@ -18412,6 +19029,9 @@ msgstr ""
msgid "Releases documentation"
msgstr ""
+msgid "Releases|New Release"
+msgstr ""
+
msgid "Release|Something went wrong while getting the release details"
msgstr ""
@@ -18449,22 +19069,22 @@ msgid "Remove all approvals in a merge request when new commits are pushed to it
msgstr ""
msgid "Remove all or specific assignee(s)"
-msgstr ""
+msgstr "Удалить вÑех ответÑтвенных или одного конкретного"
msgid "Remove all or specific label(s)"
msgstr "Удалить вÑе или конкретные метки(и)"
msgid "Remove approvers"
-msgstr ""
+msgstr "Удалить утверждающих"
msgid "Remove approvers?"
-msgstr ""
+msgstr "Удалить утверждающих?"
msgid "Remove asset link"
msgstr ""
msgid "Remove assignee"
-msgstr "Удалить иÑполнителÑ"
+msgstr "Удалить ответÑтвенного"
msgid "Remove avatar"
msgstr "Удалить аватар"
@@ -18484,6 +19104,9 @@ msgstr "Убрать Ñрок выполнениÑ"
msgid "Remove fork relationship"
msgstr ""
+msgid "Remove from batch"
+msgstr ""
+
msgid "Remove from board"
msgstr "Удалить из доÑки"
@@ -18536,7 +19159,7 @@ msgid "Removed"
msgstr ""
msgid "Removed %{assignee_text} %{assignee_references}."
-msgstr "Удалено %{assignee_text}%{assignee_references}."
+msgstr "Удалено %{assignee_text} %{assignee_references}."
msgid "Removed %{epic_ref} from child epics."
msgstr "Из дочерних целей удалена %{epic_ref}"
@@ -18673,6 +19296,12 @@ msgstr ""
msgid "Replication"
msgstr ""
+msgid "Replication enabled"
+msgstr ""
+
+msgid "Replication paused"
+msgstr ""
+
msgid "Reply by email"
msgstr ""
@@ -18700,15 +19329,15 @@ msgstr ""
msgid "Reported %{timeAgo} by %{reportedBy}"
msgstr "Сообщил %{reportedBy} %{timeAgo}"
-msgid "Reported Resource Changes: %{addNum} to add, %{changeNum} to change, %{deleteNum} to delete"
-msgstr "СпиÑок изменений реÑурÑов: %{addNum} к добавлению, %{changeNum} к изменению, %{deleteNum} к удалению"
-
msgid "Reporter"
msgstr "Reporter"
msgid "Reporting"
msgstr ""
+msgid "Reports"
+msgstr ""
+
msgid "Reports|%{combinedString} and %{resolvedString}"
msgstr ""
@@ -18749,6 +19378,9 @@ msgstr ""
msgid "Reports|Failure"
msgstr "Отказ"
+msgid "Reports|Identifier"
+msgstr ""
+
msgid "Reports|Metrics reports are loading"
msgstr "Отчеты по метрикам загружаютÑÑ"
@@ -18761,6 +19393,9 @@ msgstr "Отчеты о метриках не изменилиÑÑŒ"
msgid "Reports|Metrics reports failed loading results"
msgstr "Отчетам о метриках не удалоÑÑŒ загрузить результаты"
+msgid "Reports|Scanner"
+msgstr ""
+
msgid "Reports|Severity"
msgstr ""
@@ -18809,15 +19444,33 @@ msgstr "ОчиÑтка репозиториÑ"
msgid "Repository cleanup has started. You will receive an email once the cleanup operation is complete."
msgstr "ОчиÑтка Ñ€ÐµÐ¿Ð¾Ð·Ð¸Ñ‚Ð¾Ñ€Ð¸Ñ Ð½Ð°Ñ‡Ð°Ð»Ð°ÑÑŒ. Ð’Ñ‹ получите пиÑьмо поÑле ее завершениÑ."
+msgid "Repository files count over the limit"
+msgstr ""
+
+msgid "Repository has an invalid default branch name."
+msgstr ""
+
+msgid "Repository has more than one branch."
+msgstr ""
+
msgid "Repository has no locks."
msgstr ""
+msgid "Repository has tags."
+msgstr ""
+
msgid "Repository maintenance"
msgstr ""
msgid "Repository mirroring"
msgstr "Зеркалирование репозиториÑ"
+msgid "Repository must contain at least 1 file."
+msgstr ""
+
+msgid "Repository size is above the limit."
+msgstr ""
+
msgid "Repository static objects"
msgstr ""
@@ -18827,8 +19480,8 @@ msgstr "Хранилище репозиториÑ"
msgid "Repository sync capacity"
msgstr ""
-msgid "Repository: %{counter_repositories} / Wikis: %{counter_wikis} / Build Artifacts: %{counter_build_artifacts} / LFS: %{counter_lfs_objects}"
-msgstr "Репозиторий: %{counter_repositories} / Wiki: %{counter_wikis} / Ðртефакты Ñборки: %{counter_build_artifacts} / LFS: %{counter_lfs_objects}"
+msgid "Repository: %{counter_repositories} / Wikis: %{counter_wikis} / Build Artifacts: %{counter_build_artifacts} / LFS: %{counter_lfs_objects} / Snippets: %{counter_snippets}"
+msgstr ""
msgid "RepositorySettingsAccessLevel|Select"
msgstr ""
@@ -18839,6 +19492,9 @@ msgstr "Ð—Ð°Ð¿Ñ€Ð¾Ñ Ð´Ð¾Ñтупа"
msgid "Request Headers"
msgstr ""
+msgid "Request details"
+msgstr ""
+
msgid "Request parameter %{param} is missing."
msgstr ""
@@ -18949,6 +19605,9 @@ msgstr "СброÑить ключ региÑтрации обработчикоÐ
msgid "Reset template"
msgstr ""
+msgid "Reset to project defaults"
+msgstr ""
+
msgid "Resetting the authorization key for this project will require updating the authorization key in every alert source it is enabled in."
msgstr "Ð¡Ð±Ñ€Ð¾Ñ ÐºÐ»ÑŽÑ‡Ð° авторизации Ð´Ð»Ñ Ð´Ð°Ð½Ð½Ð¾Ð³Ð¾ проекта потребует Ð¾Ð±Ð½Ð¾Ð²Ð»ÐµÐ½Ð¸Ñ Ñтого ключа в каждом иÑточнике оповещений, в котором он задейÑтвован."
@@ -19045,9 +19704,6 @@ msgstr ""
msgid "Resume"
msgstr "Продолжить"
-msgid "Resume replication"
-msgstr "Возобновить репликацию"
-
msgid "Resync"
msgstr "РеÑинхронизировать"
@@ -19404,7 +20060,7 @@ msgid "Search for a group"
msgstr ""
msgid "Search for a user"
-msgstr ""
+msgstr "ПоиÑк пользователÑ"
msgid "Search for projects, issues, etc."
msgstr "ПоиÑк проектов, обÑуждений и Ñ‚.д."
@@ -19692,6 +20348,9 @@ msgstr ""
msgid "SecurityReports|False positive"
msgstr ""
+msgid "SecurityReports|Group Security Dashboard"
+msgstr ""
+
msgid "SecurityReports|Hide dismissed"
msgstr ""
@@ -19746,10 +20405,13 @@ msgstr ""
msgid "SecurityReports|Remove project from dashboard"
msgstr ""
-msgid "SecurityReports|Report type"
+msgid "SecurityReports|Return to dashboard"
msgstr ""
-msgid "SecurityReports|Return to dashboard"
+msgid "SecurityReports|Scan details"
+msgstr ""
+
+msgid "SecurityReports|Scanner type"
msgstr ""
msgid "SecurityReports|Security Dashboard"
@@ -19806,6 +20468,9 @@ msgstr ""
msgid "SecurityReports|There was an error while generating the report."
msgstr ""
+msgid "SecurityReports|Unable to add %{invalidProjectsMessage}"
+msgstr ""
+
msgid "SecurityReports|Unable to add %{invalidProjects}"
msgstr ""
@@ -19909,7 +20574,7 @@ msgid "Select an existing Kubernetes cluster or create a new one"
msgstr "Выбрать ÑущеÑтвующий клаÑтер Kubernetes или Ñоздать новый"
msgid "Select assignee"
-msgstr ""
+msgstr "Выбрать ответÑтвенного"
msgid "Select branch"
msgstr ""
@@ -19920,6 +20585,9 @@ msgstr "Выбрать ветку/тег"
msgid "Select due date"
msgstr ""
+msgid "Select file"
+msgstr ""
+
msgid "Select group or project"
msgstr "Выберите группу или проект"
@@ -19983,9 +20651,6 @@ msgstr "Выбор целевой ветки"
msgid "Select the branch you want to set as the default for this project. All merge requests and commits will automatically be made against this branch unless you specify a different one."
msgstr "Выберите ветвь, которую вы хотите уÑтановить по умолчанию Ð´Ð»Ñ Ñтого проекта. Ð’Ñе запроÑÑ‹ на ÑлиÑние и коммиты будут автоматичеÑки отправлÑÑ‚ÑŒÑÑ Ð² Ñту ветку, еÑли вы не укажете другую."
-msgid "Select the configured storage available for new repositories to be placed on."
-msgstr ""
-
msgid "Select the custom project template source group."
msgstr ""
@@ -19993,7 +20658,7 @@ msgid "Select timeframe"
msgstr ""
msgid "Select user"
-msgstr ""
+msgstr "Выбрать пользователÑ"
msgid "Selected levels cannot be used by non-admin users for groups, projects or snippets. If the public level is restricted, user profiles are only visible to logged in users."
msgstr ""
@@ -20041,7 +20706,7 @@ msgid "SelfMonitoring|Self monitoring project has been successfully deleted."
msgstr ""
msgid "Send a separate email notification to Developers."
-msgstr ""
+msgstr "Отправить отдельное уведомление по Ñлектронной почте Ð´Ð»Ñ ÑƒÑ‡Ð°Ñтников Ñ Ñ€Ð¾Ð»ÑŒÑŽ Developer."
msgid "Send confirmation email"
msgstr "Отправить пиÑьмо Ñ Ð¿Ð¾Ð´Ñ‚Ð²ÐµÑ€Ð¶Ð´ÐµÐ½Ð¸ÐµÐ¼"
@@ -20178,12 +20843,6 @@ msgstr "Служба поддержки"
msgid "Service Desk is enabled but not yet active"
msgstr ""
-msgid "Service Desk is off"
-msgstr ""
-
-msgid "Service Desk is on"
-msgstr ""
-
msgid "Service Templates"
msgstr "Шаблоны Служб"
@@ -20271,6 +20930,15 @@ msgstr "ÐаÑтройте макÑимальное чиÑло минут длÑ
msgid "Set the milestone to %{milestone_reference}."
msgstr ""
+msgid "Set the number of concurrent requests this secondary node will make to the primary node while backfilling."
+msgstr ""
+
+msgid "Set the synchronization and verification capacity for the secondary node."
+msgstr ""
+
+msgid "Set the timeout in seconds to send a secondary node status to the primary and IPs allowed for the secondary nodes."
+msgstr ""
+
msgid "Set time estimate"
msgstr ""
@@ -20310,6 +20978,9 @@ msgstr "УÑтановить приоритет"
msgid "Set weight to %{weight}."
msgstr "УÑтановить приоритет на %{weight}."
+msgid "Set what should be replicated by choosing specific projects or groups by the secondary node."
+msgstr ""
+
msgid "SetPasswordToCloneLink|set a password"
msgstr "уÑтановите пароль"
@@ -20436,6 +21107,9 @@ msgstr "Показать опиÑание коммита"
msgid "Show complete raw log"
msgstr ""
+msgid "Show details"
+msgstr ""
+
msgid "Show file browser"
msgstr ""
@@ -20445,10 +21119,13 @@ msgstr ""
msgid "Show latest version"
msgstr "Показать поÑледнюю верÑию"
+msgid "Show list"
+msgstr ""
+
msgid "Show me everything"
msgstr ""
-msgid "Show me how"
+msgid "Show me how to add a pipeline"
msgstr ""
msgid "Show me more advanced stuff"
@@ -20501,9 +21178,6 @@ msgstr ""
msgid "Showing all issues"
msgstr "Показаны вÑе обÑуждениÑ"
-msgid "Showing all labels"
-msgstr "ОтображаютÑÑ Ð²Ñе метки"
-
msgid "Showing last %{size} of log -"
msgstr ""
@@ -20615,9 +21289,6 @@ msgstr "ÐаÑтройки размера Ð´Ð»Ñ ÑтатичеÑких веб-Ñ
msgid "Skip outdated deployment jobs"
msgstr "ПропуÑтить уÑтаревшие Ð·Ð°Ð´Ð°Ð½Ð¸Ñ Ñ€Ð°Ð·Ð²Ñ‘Ñ€Ñ‚Ñ‹Ð²Ð°Ð½Ð¸Ñ"
-msgid "Skip this for now"
-msgstr "ПропуÑтить Ñто ÑейчаÑ"
-
msgid "Skipped"
msgstr ""
@@ -20762,6 +21433,9 @@ msgstr "Что-то пошло не так при переключении кнÐ
msgid "Something went wrong while adding your award. Please try again."
msgstr "Что-то пошло не так при добавлении вашей награды. ПожалуйÑта, попробуйте ещё раз."
+msgid "Something went wrong while applying the batch of suggestions. Please try again."
+msgstr ""
+
msgid "Something went wrong while applying the suggestion. Please try again."
msgstr ""
@@ -20864,10 +21538,13 @@ msgstr ""
msgid "Something went wrong while updating a requirement."
msgstr "Что-то пошло не так при обновлении требованиÑ."
+msgid "Something went wrong while updating assignees"
+msgstr ""
+
msgid "Something went wrong while updating your list settings"
msgstr ""
-msgid "Something went wrong with your automatic subscription renewal"
+msgid "Something went wrong with your automatic subscription renewal."
msgstr ""
msgid "Something went wrong, unable to add %{project} to dashboard"
@@ -20885,6 +21562,9 @@ msgstr ""
msgid "Something went wrong, unable to search projects"
msgstr ""
+msgid "Something went wrong."
+msgstr ""
+
msgid "Something went wrong. Please try again."
msgstr "Произошла ошибка. Попробуйте позже."
@@ -21068,6 +21748,9 @@ msgstr "ИÑточник (ветка или тег)"
msgid "Source code"
msgstr "ИÑходный код"
+msgid "Source code (%{fileExtension})"
+msgstr ""
+
msgid "Source is not available"
msgstr "ИÑходный текÑÑ‚ недоÑтупен"
@@ -21152,6 +21835,9 @@ msgstr "Объединить иÑторию коммитов"
msgid "Stack trace"
msgstr ""
+msgid "Stacktrace snippet"
+msgstr ""
+
msgid "Stage"
msgstr "Этап"
@@ -21257,6 +21943,9 @@ msgstr ""
msgid "Start thread & reopen %{noteable_name}"
msgstr ""
+msgid "Start using Directed Acyclic Graphs (DAG)"
+msgstr ""
+
msgid "Start your Free Gold Trial"
msgstr "Ðачните беÑплатный пробный период Gold"
@@ -21285,7 +21974,7 @@ msgid "Starts %{startsIn}"
msgstr "Будет запущен %{startsIn}"
msgid "Starts at (UTC)"
-msgstr ""
+msgstr "Опубликовать (UTC)"
msgid "State your message to activate"
msgstr ""
@@ -21719,6 +22408,21 @@ msgstr ""
msgid "SuggestedColors|Very pale orange"
msgstr ""
+msgid "Suggestion is not applicable as the suggestion was not found."
+msgstr ""
+
+msgid "Suggestions are not applicable as one or more suggestions were not found."
+msgstr ""
+
+msgid "Suggestions are not applicable as their lines cannot overlap."
+msgstr ""
+
+msgid "Suggestions must all be on the same branch."
+msgstr ""
+
+msgid "Suggestions that change line count can't be added to batches, yet."
+msgstr ""
+
msgid "Suggestions:"
msgstr "ПредложениÑ:"
@@ -21761,9 +22465,6 @@ msgstr "Синхронизировано"
msgid "Synchronization disabled"
msgstr ""
-msgid "Synchronization of container repositories is disabled."
-msgstr ""
-
msgid "System"
msgstr "СиÑтема"
@@ -21891,7 +22592,7 @@ msgid "TagsPage|Write your release notes or drag files here…"
msgstr "Ðапишите заметки к релизу или перетащите Ñюда файлы…"
msgid "TagsPage|protected"
-msgstr "защищенный"
+msgstr "защищеннаÑ"
msgid "Target Branch"
msgstr "Ветка"
@@ -21944,6 +22645,38 @@ msgstr "СоглаÑие Ñ Ð£ÑловиÑми иÑÐ¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ð½Ð¸Ñ Ð¸ ПÐ
msgid "Terms of Service and Privacy Policy"
msgstr "УÑÐ»Ð¾Ð²Ð¸Ñ Ð¸ÑÐ¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ð½Ð¸Ñ Ð¸ Политика конфиденциальноÑти"
+msgid "Terraform|%{number} Terraform report failed to generate"
+msgid_plural "Terraform|%{number} Terraform reports failed to generate"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "Terraform|%{number} Terraform report was generated in your pipelines"
+msgid_plural "Terraform|%{number} Terraform reports were generated in your pipelines"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "Terraform|A Terraform report failed to generate."
+msgstr ""
+
+msgid "Terraform|A Terraform report was generated in your pipelines."
+msgstr ""
+
+msgid "Terraform|Generating the report caused an error."
+msgstr ""
+
+msgid "Terraform|Reported Resource Changes: %{addNum} to add, %{changeNum} to change, %{deleteNum} to delete"
+msgstr ""
+
+msgid "Terraform|The Terraform report %{name} failed to generate."
+msgstr ""
+
+msgid "Terraform|The Terraform report %{name} was generated in your pipelines."
+msgstr ""
+
msgid "Test"
msgstr "ТеÑÑ‚"
@@ -21972,8 +22705,8 @@ msgstr "УбедитеÑÑŒ, что проект имеет Ð·Ð°Ð´Ð°Ð½Ð¸Ñ CI."
msgid "TestHooks|Ensure the project has CI pipelines."
msgstr "УбедитеÑÑŒ, что проект имеет Ñборочные линии CI."
-msgid "TestHooks|Ensure the project has at least one commit."
-msgstr "УбедитеÑÑŒ, что в проекте еÑÑ‚ÑŒ Ñ…Ð¾Ñ‚Ñ Ð±Ñ‹ один коммит."
+msgid "TestHooks|Ensure the project has deployments."
+msgstr ""
msgid "TestHooks|Ensure the project has issues."
msgstr "УбедитеÑÑŒ, что у проекта еÑÑ‚ÑŒ обÑуждениÑ."
@@ -22075,7 +22808,7 @@ msgstr "Трекер обÑуждений - Ñто меÑто, где можно
msgid "The Prometheus server responded with \"bad request\". Please check your queries are correct and are supported in your Prometheus version. %{documentationLink}"
msgstr "Сервер Prometheus ответил Ñ Ñ€ÐµÐ·ÑƒÐ»ÑŒÑ‚Ð°Ñ‚Ð¾Ð¼ \"bad request\". ПожалуйÑта, убедитеÑÑŒ, что запроÑÑ‹ верны и поддерживаютÑÑ Ð² вашей верÑии Prometheus. %{documentationLink}"
-msgid "The URL defined on the primary node that secondary nodes should use to contact it. Defaults to URL"
+msgid "The URL defined on the primary node that secondary nodes should use to contact it."
msgstr ""
msgid "The URL to use for connecting to Elasticsearch. Use a comma-separated list to support clustering (e.g., \"http://localhost:9200, http://localhost:9201\")."
@@ -22084,6 +22817,9 @@ msgstr "URL, иÑпользуемый Ð´Ð»Ñ Ð¿Ð¾Ð´ÐºÐ»ÑŽÑ‡ÐµÐ½Ð¸Ñ Ðº Elasticse
msgid "The X509 Certificate to use when mutual TLS is required to communicate with the external authorization service. If left blank, the server certificate is still validated when accessing over HTTPS."
msgstr "Сертификат X509, иÑпользуемый в том Ñлучае, когда Ð´Ð»Ñ ÑвÑзи Ñ Ð²Ð½ÐµÑˆÐ½ÐµÐ¹ Ñлужбой авторизации требуетÑÑ Ð²Ð·Ð°Ð¸Ð¼Ð½Ð°Ñ Ð¿Ñ€Ð¾Ð²ÐµÑ€ÐºÐ° подлинноÑти TLS. ЕÑли оÑтавить пуÑтым, Ñертификат Ñервера вÑе еще будет проверÑÑ‚ÑŒÑÑ Ð¿Ñ€Ð¸ доÑтупе через HTTPS."
+msgid "The above settings apply to all projects with the selected compliance framework(s)."
+msgstr ""
+
msgid "The amount of seconds after which a request to get a secondary node status will time out."
msgstr "КоличеÑтво Ñекунд, поÑле которых Ð·Ð°Ð¿Ñ€Ð¾Ñ Ð½Ð° получение ÑтатуÑа вторичного узла будет отключен."
@@ -22385,9 +23121,6 @@ msgstr "ВремÑ, затраченное каждым Ñлементом, Ñо
msgid "The total stage shows the time it takes between creating an issue and deploying the code to production. The data will be automatically added once you have completed the full idea to production cycle."
msgstr ""
-msgid "The unique identifier for the Geo node. Must match %{geoNodeName} if it is set in gitlab.rb, otherwise it must match %{externalUrl} with a trailing slash"
-msgstr "Уникальный идентификатор узла Geo. Должен ÑоответÑтвовать %{geoNodeName}, еÑли таковой задан в gitlab.rb, иначе он должен ÑоответÑтвовать %{externalUrl} Ñ Ð·Ð°Ð²ÐµÑ€ÑˆÐ°ÑŽÑ‰ÐµÐ¹ коÑой чертой"
-
msgid "The update action will time out after %{number_of_minutes} minutes. For big repositories, use a clone/push combination."
msgstr "Ð’Ñ€ÐµÐ¼Ñ Ð´ÐµÐ¹ÑÑ‚Ð²Ð¸Ñ Ð¾Ð±Ð½Ð¾Ð²Ð»ÐµÐ½Ð¸Ñ Ð¸Ñтечет через %{number_of_minutes} минут. Ð”Ð»Ñ Ð±Ð¾Ð»ÑŒÑˆÐ¸Ñ… репозиториев иÑпользуйте комбинацию clone/push."
@@ -22412,9 +23145,6 @@ msgstr ""
msgid "The user you are trying to deactivate has been active in the past %{minimum_inactive_days} days and cannot be deactivated"
msgstr ""
-msgid "The user-facing URL of the Geo node"
-msgstr "Обращенный к пользователю URL-Ð°Ð´Ñ€ÐµÑ ÑƒÐ·Ð»Ð° Geo"
-
msgid "The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6."
msgstr "Среднее значение в Ñ€Ñду. Пример: между 3, 5, 9, Ñреднее 5, между 3, 5, 7, 8, Ñреднее (5+7)/2 = 6."
@@ -22505,6 +23235,9 @@ msgstr "Произошла ошибка ÑÐ¾ÐµÐ´Ð¸Ð½ÐµÐ½Ð¸Ñ Ñ Ð²Ð°ÑˆÐ¸Ð¼ уÑÑ
msgid "There was a problem fetching project branches."
msgstr ""
+msgid "There was a problem fetching project tags."
+msgstr ""
+
msgid "There was a problem fetching project users."
msgstr ""
@@ -22556,6 +23289,9 @@ msgstr "Произошла ошибка при получении медианн
msgid "There was an error fetching the %{replicableType}"
msgstr ""
+msgid "There was an error fetching the Geo Settings"
+msgstr ""
+
msgid "There was an error fetching the Node's Groups"
msgstr ""
@@ -22676,6 +23412,12 @@ msgstr ""
msgid "This %{issuable} is locked. Only <strong>project members</strong> can comment."
msgstr "Этот %{issuable} заблокировано. Только <strong>учаÑтники проекта</strong> могут комментировать."
+msgid "This %{noteableTypeText} is %{confidentialLinkStart}confidential%{linkEnd} and %{lockedLinkStart}locked%{linkEnd}."
+msgstr ""
+
+msgid "This %{noteableTypeText} is locked."
+msgstr ""
+
msgid "This %{viewer} could not be displayed because %{reason}. You can %{options} instead."
msgstr ""
@@ -22694,6 +23436,9 @@ msgstr ""
msgid "This action can lead to data loss. To prevent accidental actions we ask you to confirm your intention."
msgstr "Это дейÑтвие может привеÑти к потере данных. Чтобы предотвратить Ñлучайные дейÑтвиÑ, мы проÑим Ð’Ð°Ñ Ð¿Ð¾Ð´Ñ‚Ð²ÐµÑ€Ð´Ð¸Ñ‚ÑŒ Ваше намерение."
+msgid "This also resolves all related threads"
+msgstr ""
+
msgid "This also resolves the discussion"
msgstr ""
@@ -22784,6 +23529,9 @@ msgstr ""
msgid "This epic does not exist or you don't have sufficient permission."
msgstr "Этой цели не ÑущеÑтвует, или у Ð²Ð°Ñ Ð½ÐµÐ´Ð¾Ñтаточно прав."
+msgid "This feature is currently in beta. We invite you to %{linkStart}give feedback%{linkEnd}."
+msgstr ""
+
msgid "This feature requires local storage to be enabled"
msgstr "Эта Ñ„ÑƒÐ½ÐºÑ†Ð¸Ñ Ñ‚Ñ€ÐµÐ±ÑƒÐµÑ‚, чтобы локальное хранилище было включено"
@@ -22817,8 +23565,8 @@ msgstr ""
msgid "This is a Work in Progress"
msgstr ""
-msgid "This is a confidential issue."
-msgstr "Это конфиденциальное обÑуждение."
+msgid "This is a confidential %{noteableTypeText}."
+msgstr ""
msgid "This is a delayed job to run in %{remainingTime}"
msgstr ""
@@ -22844,9 +23592,6 @@ msgstr ""
msgid "This is your current session"
msgstr "Это ваша Ñ‚ÐµÐºÑƒÑ‰Ð°Ñ ÑеÑÑиÑ"
-msgid "This issue is %{confidentialLinkStart}confidential%{linkEnd} and %{lockedLinkStart}locked%{linkEnd}."
-msgstr ""
-
msgid "This issue is confidential"
msgstr "Это обÑуждение ÑвлÑетÑÑ ÐºÐ¾Ð½Ñ„Ð¸Ð´ÐµÐ½Ñ†Ð¸Ð°Ð»ÑŒÐ½Ñ‹Ð¼"
@@ -22856,9 +23601,6 @@ msgstr ""
msgid "This issue is in a child epic of the filtered epic"
msgstr ""
-msgid "This issue is locked."
-msgstr "ОбÑуждение заблокировано."
-
msgid "This job depends on other jobs with expired/erased artifacts: %{invalid_dependencies}"
msgstr ""
@@ -22958,6 +23700,9 @@ msgstr ""
msgid "This license has already expired."
msgstr ""
+msgid "This link points to external content"
+msgstr ""
+
msgid "This may expose confidential information as the selected fork is in another namespace that can have other members."
msgstr ""
@@ -22985,6 +23730,9 @@ msgstr ""
msgid "This page will be removed in a future release."
msgstr ""
+msgid "This pipeline does not use the %{codeStart}needs%{codeEnd} keyword and can't be represented as a directed acyclic graph."
+msgstr ""
+
msgid "This pipeline makes use of a predefined CI/CD configuration enabled by %{strongStart}Auto DevOps.%{strongEnd}"
msgstr ""
@@ -23213,6 +23961,9 @@ msgstr "Ð’Ñ€ÐµÐ¼Ñ Ð¾Ñ‚ первого коммита до первого ком
msgid "Time from last commit to merge"
msgstr "Ð’Ñ€ÐµÐ¼Ñ Ñ Ð¿Ð¾Ñледнего коммита до ÑлиÑниÑ"
+msgid "Time in seconds"
+msgstr ""
+
msgid "Time in seconds GitLab will wait for a response from the external service. When the service does not respond in time, access will be denied."
msgstr ""
@@ -23638,9 +24389,15 @@ msgstr "Общий вклад"
msgid "Total artifacts size: %{total_size}"
msgstr "Общий размер артефактов: %{total_size}"
+msgid "Total cores (CPUs)"
+msgstr ""
+
msgid "Total issues"
msgstr ""
+msgid "Total memory (GB)"
+msgstr ""
+
msgid "Total test time for all commits/merges"
msgstr "Общее Ð²Ñ€ÐµÐ¼Ñ Ñ‚ÐµÑÑ‚Ð¸Ñ€Ð¾Ð²Ð°Ð½Ð¸Ñ Ñ„Ð¸ÐºÑаций/ÑлиÑний"
@@ -23842,6 +24599,9 @@ msgstr "УÑтройÑтва U2F (%{length})"
msgid "U2F only works with HTTPS-enabled websites. Contact your administrator for more details."
msgstr ""
+msgid "UI Development Kit"
+msgstr ""
+
msgid "URL"
msgstr "URL"
@@ -23914,9 +24674,6 @@ msgstr "Ðе удаетÑÑ Ð·Ð°Ð³Ñ€ÑƒÐ·Ð¸Ñ‚ÑŒ отличиÑ. %{button_try_agai
msgid "Unable to load the merge request widget. Try reloading the page."
msgstr ""
-msgid "Unable to process group import file"
-msgstr ""
-
msgid "Unable to resolve"
msgstr "Ðевозможно решить"
@@ -23932,6 +24689,9 @@ msgstr "Ðевозможно запланировать запуÑк ÑбороÑ
msgid "Unable to sign you in to the group with SAML due to \"%{reason}\""
msgstr ""
+msgid "Unable to suggest a path. Please refresh and try again."
+msgstr ""
+
msgid "Unable to update label prioritization at this time"
msgstr "Ðевозможно обновить приоритизацию меток на данный момент"
@@ -23947,6 +24707,9 @@ msgstr "ВоÑÑтановить проект"
msgid "Unarchiving the project will restore people's ability to make changes to it. The repository can be committed to, and issues, comments, and other entities can be created. %{strong_start}Once active, this project shows up in the search and on the dashboard.%{strong_end}"
msgstr "Разархивирование проекта вернёт людÑм возможноÑÑ‚ÑŒ вноÑить в него изменениÑ. Ð’ репозиторий можно будет отправлÑÑ‚ÑŒ коммиты, а также поÑвитÑÑ Ð²Ð¾Ð·Ð¼Ð¾Ð¶Ð½Ð¾ÑÑ‚ÑŒ Ñоздавать обÑуждениÑ, комментарии и другие ÑущноÑти. %{strong_start}ПоÑле активации Ñтот проект поÑвитÑÑ Ð² поиÑке и на панели инÑтрументов.%{strong_end}"
+msgid "Unassign from commenting user"
+msgstr ""
+
msgid "Unblock"
msgstr ""
@@ -24022,6 +24785,9 @@ msgstr ""
msgid "Unreachable"
msgstr ""
+msgid "Unrecognized cluster type"
+msgstr ""
+
msgid "Unresolve"
msgstr ""
@@ -24082,6 +24848,9 @@ msgstr "Отменена подпиÑка на %{quick_action_target}."
msgid "Unsubscribes from this %{quick_action_target}."
msgstr "ОтменÑет подпиÑку от на %{quick_action_target}."
+msgid "Unsupported todo type passed. Supported todo types are: %{todo_types}"
+msgstr ""
+
msgid "Until"
msgstr ""
@@ -24109,6 +24878,9 @@ msgstr "Обновить вÑе"
msgid "Update approval rule"
msgstr "Обновить правило утверждениÑ"
+msgid "Update approvers"
+msgstr ""
+
msgid "Update failed"
msgstr "Обновление не удалоÑÑŒ"
@@ -24118,6 +24890,9 @@ msgstr "Обновление не удалоÑÑŒ. Попробуйте ещё Ñ€
msgid "Update it"
msgstr ""
+msgid "Update iteration"
+msgstr ""
+
msgid "Update now"
msgstr ""
@@ -24163,7 +24938,7 @@ msgstr "Обновлено %{updated_at} %{updated_by}"
msgid "Updated at"
msgstr ""
-msgid "Updated to"
+msgid "Updated to %{linkStart}chart v%{linkEnd}"
msgstr ""
msgid "Updating"
@@ -24193,6 +24968,9 @@ msgstr ""
msgid "Upload CSV file"
msgstr ""
+msgid "Upload License"
+msgstr ""
+
msgid "Upload New File"
msgstr "Загрузить новый файл"
@@ -24274,18 +25052,21 @@ msgstr "Пакеты"
msgid "UsageQuota|Pipelines"
msgstr "Сборочные линии"
+msgid "UsageQuota|Purchase more storage"
+msgstr ""
+
msgid "UsageQuota|Repositories"
msgstr ""
msgid "UsageQuota|Repository"
msgstr "Репозиторий"
+msgid "UsageQuota|Snippets"
+msgstr ""
+
msgid "UsageQuota|Storage"
msgstr "Хранилище"
-msgid "UsageQuota|Storage usage:"
-msgstr ""
-
msgid "UsageQuota|This namespace has no projects which use shared runners"
msgstr "Ð’ Ñтом проÑтранÑтве имен нет проектов, которые иÑпользуют общие обработчики заданий"
@@ -24316,6 +25097,12 @@ msgstr "Wiki"
msgid "UsageQuota|Wikis"
msgstr ""
+msgid "UsageQuota|You used: %{usage} %{limit}"
+msgstr ""
+
+msgid "UsageQuota|out of %{formattedLimit} of your namespace storage"
+msgstr ""
+
msgid "Use %{code_start}::%{code_end} to create a %{link_start}scoped label set%{link_end} (eg. %{code_start}priority::1%{code_end})"
msgstr "ИÑпользуйте %{code_start}::%{code_end}, чтобы Ñоздать %{link_start}набор Ñелективных меток%{link_end} (например, %{code_start}priority::1%{code_end})"
@@ -24382,6 +25169,9 @@ msgstr ""
msgid "User IDs"
msgstr ""
+msgid "User Lists can only be created and modified with %{linkStart}the API%{linkEnd}"
+msgstr ""
+
msgid "User OAuth applications"
msgstr ""
@@ -24406,6 +25196,9 @@ msgstr ""
msgid "User key was successfully removed."
msgstr "Ключ Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ Ð±Ñ‹Ð» уÑпешно удален."
+msgid "User list %{name} will be removed. Are you sure?"
+msgstr ""
+
msgid "User map"
msgstr ""
@@ -24427,149 +25220,11 @@ msgstr ""
msgid "User was successfully updated."
msgstr "Пользователь уÑпешно изменен."
-msgid "UserOnboardingTour|%{activeTour}/%{totalTours}"
-msgstr "%{activeTour}/%{totalTours}"
-
-msgid "UserOnboardingTour|%{completed}/%{total} steps completed"
-msgstr "%{completed}/%{total} шагов завершено"
-
-msgid "UserOnboardingTour|%{emphasisStart}Well done!%{emphasisEnd}%{lineBreak}%{lineBreak}That's it for our guided tour, congratulations for making it all the way to the end!%{lineBreak}%{lineBreak}We hope this gave you a good overview of GitLab and how it can help you. We'll now show you how to create your own project and invite your colleagues."
-msgstr "%{emphasisStart}Браво!%{emphasisEnd}%{lineBreak}%{lineBreak}Это конец нашего обучающего курÑа, поздравлÑем Ñ Ð·Ð°Ð²ÐµÑ€ÑˆÐµÐ½Ð¸ÐµÐ¼!%{lineBreak}%{lineBreak}Мы надеемÑÑ, что ÐºÑƒÑ€Ñ Ð´Ð°Ð» вам хороший обзор GitLab и Ñможет вам помочь. Ð¡ÐµÐ¹Ñ‡Ð°Ñ Ð¼Ñ‹ покажем вам, как Ñоздать ÑобÑтвенный проект и приглаÑить коллег."
-
-msgid "UserOnboardingTour|Adding other members to a project is done through Project Settings. Click on %{emphasisStart}Settings%{emphasisEnd}."
-msgstr "Добавление других учаÑтников в проект оÑущеÑтвлÑетÑÑ Ñ‡ÐµÑ€ÐµÐ· ÐаÑтройки проекта. Ðажмите %{emphasisStart}ÐаÑтройки%{emphasisEnd}."
-
-msgid "UserOnboardingTour|Alright, that's it for Commits. Let's take a look at the %{emphasisStart}Branches%{emphasisEnd}."
-msgstr "Отлично, вот и вÑÑ‘ про Коммиты. Давайте взглÑнем на %{emphasisStart}Ветки%{emphasisEnd}."
-
-msgid "UserOnboardingTour|Awesome! Now click on %{emphasisStart}Members%{emphasisEnd}."
-msgstr "ПотрÑÑающе! Теперь нажмите на %{emphasisStart}учаÑтников%{emphasisEnd}."
-
-msgid "UserOnboardingTour|Click on one of the %{emphasisStart}Compare%{emphasisEnd} buttons to compare a branch to master."
-msgstr "Ðажмите одну из кнопок %{emphasisStart}Сравнить%{emphasisEnd}, чтобы Ñравнить ÑоответÑтвующую ветку Ñ Ð²ÐµÑ‚ÐºÐ¾Ð¹ master."
-
-msgid "UserOnboardingTour|Click on one of the %{emphasisStart}pipeline IDs%{emphasisEnd} to see the details of a pipeline."
-msgstr "Ðажмите на один из %{emphasisStart}идентификаторов Ñборочных линий%{emphasisEnd}, чтобы увидеть подробную информацию о ней."
-
-msgid "UserOnboardingTour|Click to open the latest commit to see its details."
-msgstr "Ðажмите, чтобы открыть поÑледний коммит и узнать о нём подробнее."
-
-msgid "UserOnboardingTour|Close 'Learn GitLab'"
-msgstr "Закрыть 'Изучить GitLab'"
-
-msgid "UserOnboardingTour|Commits are shown in chronological order and can be filtered by the commit message or by the branch."
-msgstr "Коммиты отображаютÑÑ Ð² хронологичеÑком порÑдке и могут быть отфильтрованы по Ñообщению коммита или по ветке."
-
-msgid "UserOnboardingTour|Create a project"
-msgstr "Создайте проект"
-
-msgid "UserOnboardingTour|Exit 'Learn GitLab'"
-msgstr "Выйти из 'Изучить GitLab'"
-
-msgid "UserOnboardingTour|Got it"
-msgstr "ЯÑно"
-
-msgid "UserOnboardingTour|Great job! %{clapHands} We hope the tour was helpful and that you learned how to use GitLab.%{lineBreak}%{lineBreak}We'd love to get your feedback on this tour.%{lineBreak}%{lineBreak}%{emphasisStart}How helpful would you say this guided tour was?%{emphasisEnd}%{lineBreak}%{lineBreak}"
-msgstr "ÐžÑ‚Ð»Ð¸Ñ‡Ð½Ð°Ñ Ñ€Ð°Ð±Ð¾Ñ‚Ð°! %{clapHands} ÐадеемÑÑ, что тур был полезен и вы узнали, как иÑпользовать GitLab.%{lineBreak}%{lineBreak}Мы были бы рады узнать ваше мнение о нём.%{lineBreak}%{lineBreak}%{emphasisStart}ÐаÑколько полезным, по-вашему, был Ñтот тур?%{emphasisEnd}%{lineBreak}%{lineBreak}"
-
-msgid "UserOnboardingTour|Guided GitLab Tour"
-msgstr "ЭкÑкурÑÐ¸Ñ Ð¿Ð¾ GitLab"
-
-msgid "UserOnboardingTour|Here you can compare the changes of this branch to another one. Changes are divided by files so that it's easier to see what was changed where."
-msgstr "ЗдеÑÑŒ вы можете Ñравнить Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ñтой ветки Ñ Ð»ÑŽÐ±Ð¾Ð¹ другой. Ð˜Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ñ€Ð°Ð·Ð´ÐµÐ»ÐµÐ½Ñ‹ по файлам, чтобы было легче увидеть, что и где было изменено."
-
-msgid "UserOnboardingTour|Here you can create a project from scratch, start with a template or import a repository from other platforms. Whatever you choose, we'll guide you through the process.%{lineBreak}%{lineBreak}Fill in your new project information and click on %{emphasisStart}Create Project%{emphasisEnd} to progress to the next step."
-msgstr "ЗдеÑÑŒ вы можете Ñоздать проект Ñ Ð½ÑƒÐ»Ñ, начать Ñ ÑˆÐ°Ð±Ð»Ð¾Ð½Ð° или импортировать репозиторий Ñ Ð´Ñ€ÑƒÐ³Ð¸Ñ… платформ. Что бы вы ни выбрали, мы будем Ñопровождать Ð²Ð°Ñ Ð² течение вÑего процеÑÑа.%{lineBreak}%{lineBreak}Заполните информацию о вашем новом проекте и нажмите %{emphasisStart}Создать проект%{emphasisEnd} Ð´Ð»Ñ Ð¿ÐµÑ€ÐµÑ…Ð¾Ð´Ð° к Ñледующему шагу."
-
-msgid "UserOnboardingTour|Here you can see the breakdown of the pipelines: its stages and jobs in each of the stages and their status.%{lineBreak}%{lineBreak}Our CI/CD pipelines are quite complex, most of our users have fewer and simpler pipelines."
-msgstr "ЗдеÑÑŒ вы можете видеть разбивку Ñборочных линий: Ñтапы, Ð·Ð°Ð´Ð°Ð½Ð¸Ñ Ð½Ð° каждом из Ñтапов, а также их ÑоÑтоÑние.%{lineBreak}%{lineBreak}Ðаши ÑобÑтвенные Ñборочные линии CI/CD довольно Ñложны, у большинÑтва наших пользователей их меньше и они проще."
-
-msgid "UserOnboardingTour|Here you can see the current members of the project (just you at the moment) and invite new members.%{lineBreak}%{lineBreak}You can invite multiple members at once (existing GitLab users or invite by email) and you can also set their roles and permissions.%{lineBreak}%{lineBreak}Add a few members and click on %{emphasisStart}Add to project%{emphasisEnd} to complete this step."
-msgstr "ЗдеÑÑŒ вы можете увидеть текущих учаÑтников проекта (в данный момент Ñто только вы) и приглаÑить новых.%{lineBreak}%{lineBreak}Можете приглаÑить Ñразу неÑкольких учаÑтников (ÑущеÑтвующих пользователей GitLab или отправкой Ð¿Ñ€Ð¸Ð³Ð»Ð°ÑˆÐµÐ½Ð¸Ñ Ð¿Ð¾ Ñлектронной почте), а также задать им роли и права в проекте.%{lineBreak}%{lineBreak}Добавьте неÑкольких учаÑтников и нажмите на %{emphasisStart}Добавить в проект%{emphasisEnd}, чтобы завершить Ñтот шаг."
-
-msgid "UserOnboardingTour|Here you can see what changes were made with this commit, on what branch and if there's a related merge request. The status of the pipeline will also show up if CI/CD is set up.%{lineBreak}%{lineBreak}You can also comment on the lines of code that were changed and start a discussion with your colleagues!"
-msgstr "ЗдеÑÑŒ вы можете увидеть какие Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ñодержит Ñтот коммит, в какой ветке, и еÑÑ‚ÑŒ ли ÑвÑзанные Ñ Ð½Ð¸Ð¼ запроÑÑ‹ на ÑлиÑние. СоÑтоÑние Ñборочной линии также будет отображатьÑÑ, еÑли наÑтроены CI/CD. %{lineBreak}%{lineBreak} Ещё вы можете прокомментировать Ñтроки кода, которые были изменены, и начать диÑкуÑÑию Ñо Ñвоими коллегами!"
-
-msgid "UserOnboardingTour|Here's an overview of branches in the %{emphasisStart}%{projectName}%{emphasisEnd} project. They're split into Active and Stale.%{lineBreak}%{lineBreak}From here, you can create a new merge request from a branch, or compare the branch to any other branch in the project. By default, it will compare it to the master branch."
-msgstr "Это обзор ветвей проекта %{emphasisStart}%{projectName}%{emphasisEnd}. Они разделены на активные и уÑтаревшие.%{lineBreak}%{lineBreak}ЗдеÑÑŒ вы можете Ñоздать новый Ð·Ð°Ð¿Ñ€Ð¾Ñ Ð½Ð° ÑлиÑние или Ñравнить одну ветку проекта Ñ Ð´Ñ€ÑƒÐ³Ð¾Ð¹. По умолчанию Ñравнение будет производитьÑÑ Ñ Ð²ÐµÑ‚ÐºÐ¾Ð¹ master."
-
-msgid "UserOnboardingTour|Invite colleagues"
-msgstr "ПриглаÑить коллег"
-
-msgid "UserOnboardingTour|Issues are great for communicating and keeping track of progress in GitLab. These are all issues that are open in the %{emphasisStart}%{projectName}%{emphasisEnd}.%{lineBreak}%{lineBreak}You can help us improve GitLab by contributing work to issues that are labeled <span class=\"badge color-label accept-mr-label\">Accepting merge requests</span>.%{lineBreak}%{lineBreak}This list can be filtered by labels, milestones, assignees, authors... We'll show you how it looks when the list is filtered by a label."
-msgstr "ОбÑÑƒÐ¶Ð´ÐµÐ½Ð¸Ñ Ð² GitLab отлично подходÑÑ‚ Ð´Ð»Ñ ÐºÐ¾Ð¼Ð¼ÑƒÐ½Ð¸ÐºÐ°Ñ†Ð¸Ð¸ и отÑÐ»ÐµÐ¶Ð¸Ð²Ð°Ð½Ð¸Ñ Ð¿Ñ€Ð¾Ð³Ñ€ÐµÑÑа. Перед вами вÑе обÑуждениÑ, открытые в %{emphasisStart}%{projectName}%{emphasisEnd}.%{lineBreak}%{lineBreak}Ð’Ñ‹ можете помочь нам улучшить GitLab, принÑв учаÑтие в работе над обÑуждениÑми Ñ Ð¼ÐµÑ‚ÐºÐ¾Ð¹ <span class=\"badge color-label accept-mr-label\">Accepting merge requests</span> (принимаютÑÑ Ð·Ð°Ð¿Ñ€Ð¾ÑÑ‹ на ÑлиÑние).%{lineBreak}%{lineBreak}Этот ÑпиÑок может быть отфильтрован по меткам, Ñтапам, иÑпонителÑм, авторам... Мы покажем вам, как выглÑдит отфильтрованный по метке ÑпиÑок."
-
-msgid "UserOnboardingTour|Learn GitLab"
-msgstr "Изучить GitLab"
-
-msgid "UserOnboardingTour|Let's take a closer look at a merge request. Click on the title of one."
-msgstr "Давайте подробнее раÑÑмотрим Ð·Ð°Ð¿Ñ€Ð¾Ñ Ð½Ð° ÑлиÑние. Ðажмите на заголовок одного из них."
-
-msgid "UserOnboardingTour|Let's take a closer look at all the commits. Click on %{emphasisStart}Commits%{emphasisEnd}."
-msgstr "Давайте подробнее раÑÑмотрим вÑе коммиты. Ðажмите на %{emphasisStart}Коммиты%{emphasisEnd}."
-
-msgid "UserOnboardingTour|Let's take a closer look at the repository of this project. Click on %{emphasisStart}Repository%{emphasisEnd}."
-msgstr "Давайте подробнее раÑÑмотрим репозиторий Ñтого проекта. Ðажмите на %{emphasisStart}Репозиторий%{emphasisEnd}."
-
-msgid "UserOnboardingTour|No thanks"
-msgstr "Ðет, ÑпаÑибо"
-
-msgid "UserOnboardingTour|Ok, let's go"
-msgstr "Хорошо, поехали"
-
-msgid "UserOnboardingTour|Ok, show me"
-msgstr "Ладно, покажите мне"
-
-msgid "UserOnboardingTour|Open one of the issues by clicking on its title."
-msgstr "Откройте одно из обÑуждений, нажав на его заголовок."
-
-msgid "UserOnboardingTour|Restart this step"
-msgstr "ПерезапуÑтить Ñтого шаг"
-
-msgid "UserOnboardingTour|Skip this step"
-msgstr "ПропуÑтить Ñтот шаг"
-
-msgid "UserOnboardingTour|Sweet! Your project was created and is ready to be used.%{lineBreak}%{lineBreak}You can start adding files to the repository or clone it. One last thing we want to show you is how to invite your colleagues to your new project."
-msgstr "ПрекраÑно! Ваш проект Ñоздан и готов к иÑпользованию.%{lineBreak}%{lineBreak}Ð’Ñ‹ можете начать, добавив файлы в репозиторий или Ñклонировав его. Одна поÑледнÑÑ Ð²ÐµÑ‰ÑŒ, которую мы хотим вам показать — Ñто как приглаÑить коллег в Ñвой новый проект."
-
-msgid "UserOnboardingTour|Take a look. Here's a nifty menu for quickly creating issues, merge requests, snippets, projects and groups. Click on it and select \"New project\" from the \"GitLab\" section to get started."
-msgstr "ВзглÑните. Вот отличное меню Ð´Ð»Ñ Ð±Ñ‹Ñтрого ÑÐ¾Ð·Ð´Ð°Ð½Ð¸Ñ Ð¾Ð±Ñуждений, запроÑов на ÑлиÑние, Ñниппетов, проектов и групп. Ðажмите на него и выберите «Ðовый проект» в разделе «GitLab», чтобы начать."
-
-msgid "UserOnboardingTour|Thanks for taking the guided tour. Remember, if you want to go through it again, you can start %{emphasisStart}Learn GitLab%{emphasisEnd} in the help menu on the top right."
-msgstr "СпаÑибо за то, что воÑпользовалиÑÑŒ нашей ÑкÑкурÑией. Помните, еÑли вы захотите её повторить, запуÑтите %{emphasisStart}Изучить GitLab%{emphasisEnd} в меню помощи в правом верхнем углу."
-
-msgid "UserOnboardingTour|Thanks for the feedback! %{thumbsUp}"
-msgstr "СпаÑибо за отзыв! %{thumbsUp}"
-
-msgid "UserOnboardingTour|That's it for issues. Let'st take a look at %{emphasisStart}Merge Requests%{emphasisEnd}."
-msgstr "Вот и вÑÑ‘ про обÑуждениÑ. Давайте раÑÑмотрим %{emphasisStart}ЗапроÑÑ‹ на ÑлиÑние%{emphasisEnd}."
-
-msgid "UserOnboardingTour|That's it for merge requests. Now for the final part of this guided tour - the %{emphasisStart}CI/CD%{emphasisEnd}."
-msgstr "Это вÑÑ‘ про запроÑÑ‹ на ÑлиÑние. Ð’ финальной чаÑти ÑкÑкурÑии Ð²Ð°Ñ Ð¶Ð´ÑƒÑ‚ %{emphasisStart}CI/CD%{emphasisEnd}."
-
-msgid "UserOnboardingTour|That's it for the Repository. Let's take a look at the %{emphasisStart}Issues%{emphasisEnd}."
-msgstr "Вот и вÑÑ‘ про Репозиторий. Давайте раÑÑмотрим %{emphasisStart}ОбÑуждениÑ%{emphasisEnd}."
-
-msgid "UserOnboardingTour|The structure of this page is very similar to issues. Status, description, discussion and the sidebar are all here.%{lineBreak}%{lineBreak}But take a look below the description and you'll notice that there's more information about the merge request, the CI/CD pipeline and the options for approving it.%{lineBreak}%{lineBreak}Alongside the discussion you can also see more information about commits in this merge request, the status of pipelines and review all changes that were made."
-msgstr "Структура Ñтой Ñтраницы очень напоминает обÑуждениÑ. СоÑтоÑние, опиÑание, диÑкуÑÑÐ¸Ñ Ð¸ Ð±Ð¾ÐºÐ¾Ð²Ð°Ñ Ð¿Ð°Ð½ÐµÐ»ÑŒ — вÑÑ‘ на меÑте.%{lineBreak}%{lineBreak} Ðо поÑмотрите ниже опиÑÐ°Ð½Ð¸Ñ Ð¸ вы заметите, что там приÑутÑтвует Ð´Ð¾Ð¿Ð¾Ð»Ð½Ð¸Ñ‚ÐµÐ»ÑŒÐ½Ð°Ñ Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð¾ запроÑе на ÑлиÑние, о Ñборочной линии CI/CD, а также ÑредÑтва Ð´Ð»Ñ ÑƒÑ‚Ð²ÐµÑ€Ð¶Ð´ÐµÐ½Ð¸Ñ Ð·Ð°Ð¿Ñ€Ð¾Ñа.%{lineBreak}%{lineBreak}Сбоку от диÑкуÑÑии вы также можете увидеть больше Ñведений о коммитах данного запроÑа на ÑлиÑние, о ÑоÑтоÑнии Ñборочных линий, а ещё проÑмотреть вÑе внеÑённые изменениÑ."
-
-msgid "UserOnboardingTour|There's a lot of information here but don't worry, we'll go through it.%{lineBreak}%{lineBreak}On the top you can see the status of the issue and when it was opened and by whom. Directly below it is the issue description and below that are other %{emphasisStart}related issues%{emphasisEnd} and %{emphasisStart}merge requests%{emphasisEnd} (if any). Then below that is the %{emphasisStart}discussion%{emphasisEnd}, that's where most of the communication happens.%{lineBreak}%{lineBreak}On the right, there's a sidebar where you can view/change the %{emphasisStart}assignee, milestone, due date, labels, weight%{emphasisEnd}, etc."
-msgstr "Информации много, но не волнуйтеÑÑŒ, мы через Ñто пройдем.%{lineBreak}%{lineBreak}Вверху вы можете видеть ÑоÑтоÑние обÑуждениÑ, когда оно было открыто и кем. ÐепоÑредÑтвенно под ним раÑположено опиÑание обÑуждениÑ, а еще ниже — другие %{emphasisStart}ÑвÑзанные обÑуждениÑ%{emphasisEnd} и %{emphasisStart}запроÑÑ‹ на ÑлиÑние%{emphasisEnd} (при наличии). Далее находитÑÑ %{emphasisStart}диÑкуÑÑиÑ%{emphasisEnd}, где проиÑходит Ð±Ð¾Ð»ÑŒÑˆÐ°Ñ Ñ‡Ð°ÑÑ‚ÑŒ общениÑ.%{lineBreak}%{lineBreak}Справа — Ð±Ð¾ÐºÐ¾Ð²Ð°Ñ Ð¿Ð°Ð½ÐµÐ»ÑŒ, где вы можете проÑмотреть / изменить %{emphasisStart}назначенных иÑполнителей, Ñтап, Ñрок выполнениÑ, метки, приоритет%{emphasisEnd} и Ñ‚. д."
-
-msgid "UserOnboardingTour|These are all the CI/CD pipelines we have for our %{emphasisStart}%{projectName}%{emphasisEnd} project.%{lineBreak}%{lineBreak}Here you can see the status of each pipeline, for what commit it's running for, its stages and the status for them."
-msgstr "Перед вами вÑе Ñборочные линии CI/CD, которые еÑÑ‚ÑŒ у Ð½Ð°Ñ Ð² проекте %{emphasisStart}%{projectName}%{emphasisEnd}.%{lineBreak}%{lineBreak}ЗдеÑÑŒ вы можете увидеть ÑоÑтоÑние каждой Ñборочной линии, Ð´Ð»Ñ ÐºÐ°ÐºÐ¾Ð³Ð¾ коммита она выполнÑетÑÑ, её Ñтапы и их ÑоÑтоÑние."
-
-msgid "UserOnboardingTour|These are all the issues that are available for community contributions. Let's take a closer look at one of them."
-msgstr "Это — вÑе обÑуждениÑ, принимающие вклад от учаÑтников ÑообщеÑтва. Давайте раÑÑмотрим одно из них."
-
-msgid "UserOnboardingTour|This is an overview of all merge requests in this project. Similarly to the issues overview it can be filtered down by things like labels, milestones, authors, assignees, etc."
-msgstr "Это обзор вÑех запроÑов на ÑлиÑние в Ñтом проекте. Подобно обзору обÑуждений, их можно отфильтровать по меткам, Ñтапам, авторам, назначенным иÑполнителÑм, и Ñ‚. д."
-
-msgid "UserOnboardingTour|This is the repository for the %{emphasisStart}%{projectName}%{emphasisEnd} project. All our code is stored here. Feel free to explore and take a closer look at folders and files.%{lineBreak}%{lineBreak}Above the file structure you can see the latest commit, who the author is and the status of the CI/CD pipeline.%{lineBreak}%{lineBreak}If you scroll down below the file structure, you'll find the Readme of this project. This is defined in the README.md file at the root of the repository."
-msgstr "Это репозиторий проекта %{emphasisStart}%{projectName}%{emphasisEnd}. ВеÑÑŒ наш код хранитÑÑ Ð·Ð´ÐµÑÑŒ. Ðе ÑтеÑнÑйтеÑÑŒ иÑÑледовать и раÑÑматривать папки и файлы.%{lineBreak}%{lineBreak}Ðад Ñтруктурой файлов вы можете увидеть поÑледний коммит, его автора и ÑоÑтоÑние ÑоответÑтвующей Ñборочной линии CI/CD.%{lineBreak}%{lineBreak}ЕÑли вы пролиÑтаете Ñтраницу ниже Ñтруктуры файлов, то обнаружите раздел \"Прочти менÑ\" проекта. Он определен в файле README.md в корне репозиториÑ."
+msgid "UserList|Delete %{name}?"
+msgstr ""
-msgid "UserOnboardingTour|Welcome to the project overview of the %{emphasisStart}%{projectName}%{emphasisEnd} project. This is the project that we use to work on GitLab. At first, a project seems like a simple repository, but at GitLab, a project is so much more.%{lineBreak}%{lineBreak}You can create projects for hosting your codebase, use it as an issue tracker, collaborate on code, and continuously build, test, and deploy your app with built-in GitLab CI/CD."
-msgstr "Добро пожаловать в обзор проекта %{emphasisStart}%{projectName}%{emphasisEnd}. Это проект, над которым мы работает в GitLab. Сперва проект выглÑдит как обычный репозиторий, но проект в GitLab — Ñто нечто большее.%{lineBreak}%{lineBreak}Ð’Ñ‹ можете Ñоздавать проекты Ð´Ð»Ñ Ñ€Ð°Ð·Ð¼ÐµÑ‰ÐµÐ½Ð¸Ñ Ñвоей кодовой базы, иÑпользовать его в качеÑтве багтрекера и трекера задач, ÑовмеÑтно работать над кодом, а так же непрерывно Ñобирать, теÑтировать и развертывать Ñвои Ð¿Ñ€Ð¸Ð»Ð¾Ð¶ÐµÐ½Ð¸Ñ Ð¿Ñ€Ð¸ помощи ÑредÑтв CI/CD, вÑтроенных в GitLab."
+msgid "UserList|created %{timeago}"
+msgstr ""
msgid "UserProfile|Activity"
msgstr "ÐктивноÑÑ‚ÑŒ"
@@ -24578,7 +25233,7 @@ msgid "UserProfile|Already reported for abuse"
msgstr "Уже Ñообщили о нарушении"
msgid "UserProfile|Blocked user"
-msgstr ""
+msgstr "Заблокированный пользователь"
msgid "UserProfile|Contributed projects"
msgstr "Вклад в проекты"
@@ -24623,7 +25278,7 @@ msgid "UserProfile|Star projects to track their progress and show your appreciat
msgstr ""
msgid "UserProfile|Starred projects"
-msgstr ""
+msgstr "Избранные проекты"
msgid "UserProfile|Subscribe"
msgstr "ПодпиÑатьÑÑ"
@@ -24638,10 +25293,10 @@ msgid "UserProfile|This user hasn't contributed to any projects"
msgstr ""
msgid "UserProfile|This user hasn't starred any projects"
-msgstr ""
+msgstr "Это пользователь не добавил в избранное ни одного проекта"
msgid "UserProfile|This user is blocked"
-msgstr ""
+msgstr "Этот пользователь заблокирован"
msgid "UserProfile|View all"
msgstr "Показать вÑе"
@@ -24689,7 +25344,7 @@ msgid "Users in License:"
msgstr ""
msgid "Users or groups set as approvers in the project's or merge request's settings."
-msgstr ""
+msgstr "Пользователи или группы, назначенные утверждающими в наÑтройках проекта или запроÑа на ÑлиÑние."
msgid "Users outside of license"
msgstr ""
@@ -24713,14 +25368,17 @@ msgid "UsersSelect|Any User"
msgstr "Любой пользователь"
msgid "UsersSelect|Assignee"
-msgstr "Ðазначенный"
+msgstr "ОтветÑтвенный"
msgid "UsersSelect|No assignee - %{openingTag} assign yourself %{closingTag}"
-msgstr "Ðет назначенного - %{openingTag} назначить ÑÐµÐ±Ñ %{closingTag}"
+msgstr "Ðет ответÑтвенного - %{openingTag} назначить ÑÐµÐ±Ñ %{closingTag}"
msgid "UsersSelect|Unassigned"
msgstr "Ðе назначено"
+msgid "Using %{codeStart}needs%{codeEnd} allows jobs to run before their stage is reached, as soon as their individual dependencies are met, which speeds up your pipelines."
+msgstr ""
+
msgid "Using %{code_start}::%{code_end} denotes a %{link_start}scoped label set%{link_end}"
msgstr ""
@@ -24805,9 +25463,6 @@ msgstr "ВерÑиÑ"
msgid "Versions"
msgstr ""
-msgid "Very helpful"
-msgstr ""
-
msgid "View Documentation"
msgstr ""
@@ -24960,6 +25615,9 @@ msgstr "Публичный"
msgid "VisibilityLevel|Unknown"
msgstr "Ðе определен"
+msgid "Visit settings page"
+msgstr ""
+
msgid "VisualReviewApp|%{stepStart}Step 1%{stepEnd}. Copy the following script:"
msgstr "%{stepStart}Шаг 1%{stepEnd}. Скопируйте Ñледующий Ñкрипт:"
@@ -25053,6 +25711,9 @@ msgstr ""
msgid "VulnerabilityManagement|Something went wrong while trying to delete the comment. Please try again later."
msgstr ""
+msgid "VulnerabilityManagement|Something went wrong while trying to refresh the vulnerability. Please try again later."
+msgstr ""
+
msgid "VulnerabilityManagement|Something went wrong while trying to retrieve the vulnerability history. Please try again later."
msgstr ""
@@ -25095,6 +25756,9 @@ msgstr ""
msgid "Vulnerability|Class"
msgstr ""
+msgid "Vulnerability|Crash Address"
+msgstr ""
+
msgid "Vulnerability|Description"
msgstr "ОпиÑание"
@@ -25170,9 +25834,6 @@ msgstr "Мы не можем определить путь Ð´Ð»Ñ ÑƒÐ´Ð°Ð»ÐµÐ½Ð¸
msgid "We couldn't reach the Prometheus server. Either the server no longer exists or the configuration details need updating."
msgstr ""
-msgid "We created a short guided tour that will help you learn the basics of GitLab and how it will help you be better at your job. It should only take a couple of minutes. You will be guided by two types of helpers, best recognized by their color."
-msgstr "Мы Ñоздали короткое руководÑтво, которое поможет вам изучить оÑновы GitLab и узнать как он поможет вам лучше работать. Это займет вÑего пару минут. Ð’Ñ‹ будете руководÑтвоватьÑÑ Ð´Ð²ÑƒÐ¼Ñ Ñ‚Ð¸Ð¿Ð°Ð¼Ð¸ подÑказок, лучше вÑего узнаваемых по Ñвоему цвету."
-
msgid "We detected potential spam in the %{humanized_resource_name}. Please solve the reCAPTCHA to proceed."
msgstr ""
@@ -25206,6 +25867,9 @@ msgstr ""
msgid "We've found no vulnerabilities"
msgstr ""
+msgid "Web Application Firewall"
+msgstr ""
+
msgid "Web IDE"
msgstr "Web IDE"
@@ -25266,9 +25930,6 @@ msgstr ""
msgid "Welcome to GitLab.com<br>@%{name}!"
msgstr "Добро пожаловать в GitLab.com<br>@%{name}!"
-msgid "Welcome to the Guided GitLab Tour"
-msgstr "Добро пожаловать на ÑкÑкурÑию по GitLab"
-
msgid "Welcome to the guided GitLab tour"
msgstr ""
@@ -25318,11 +25979,8 @@ msgstr ""
msgid "While it's rare to have no vulnerabilities, it can happen. In any event, we ask that you please double check your settings to make sure you've set up your dashboard correctly."
msgstr ""
-msgid "White helpers give contextual information."
-msgstr "Белые подÑказки дают контекÑтную информацию."
-
msgid "Who can be an approver?"
-msgstr ""
+msgstr "Кто может быть утверждающим?"
msgid "Who can see this group?"
msgstr ""
@@ -25366,12 +26024,18 @@ msgstr "По Ñтому пути уже еÑÑ‚ÑŒ Ñтраница Ñ Ñ‚Ð°ÐºÐ¸Ð¼
msgid "WikiEmptyIssueMessage|Suggest wiki improvement"
msgstr ""
+msgid "WikiEmptyIssueMessage|You must be a group member in order to add wiki pages. If you have suggestions for how to improve the wiki for this group, consider opening an issue in the %{issues_link}."
+msgstr ""
+
msgid "WikiEmptyIssueMessage|You must be a project member in order to add wiki pages. If you have suggestions for how to improve the wiki for this project, consider opening an issue in the %{issues_link}."
msgstr ""
msgid "WikiEmptyIssueMessage|issue tracker"
msgstr ""
+msgid "WikiEmpty|A wiki is where you can store all the details about your group. This can include why you've created it, its principles, how to use it, and so on."
+msgstr ""
+
msgid "WikiEmpty|A wiki is where you can store all the details about your project. This can include why you've created it, its principles, how to use it, and so on."
msgstr "Ð’ Wiki вы можете хранить вÑÑŽ информацию о вашем проекте. Это может быть Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð¾ том, почему вы Ñоздали проект, принцип работы, инÑÑ‚Ñ€ÑƒÐºÑ†Ð¸Ñ Ð¿Ð¾ иÑпользованию и так далее."
@@ -25381,12 +26045,21 @@ msgstr "Создать вашу первую Ñтраницу"
msgid "WikiEmpty|Suggest wiki improvement"
msgstr "Предложить улучшение Wiki"
+msgid "WikiEmpty|The wiki lets you write documentation for your group"
+msgstr ""
+
msgid "WikiEmpty|The wiki lets you write documentation for your project"
msgstr "Вики позволÑет пиÑать документацию Ð´Ð»Ñ Ð¿Ñ€Ð¾ÐµÐºÑ‚Ð°"
+msgid "WikiEmpty|This group has no wiki pages"
+msgstr ""
+
msgid "WikiEmpty|This project has no wiki pages"
msgstr ""
+msgid "WikiEmpty|You must be a group member in order to add wiki pages."
+msgstr ""
+
msgid "WikiEmpty|You must be a project member in order to add wiki pages."
msgstr ""
@@ -25643,13 +26316,13 @@ msgid "You can generate an access token scoped to this project for each applicat
msgstr ""
msgid "You can get started by cloning the repository or start adding files to it with one of the following options."
-msgstr ""
+msgstr "Ð’Ñ‹ можете начать Ñ ÐºÐ»Ð¾Ð½Ð¸Ñ€Ð¾Ð²Ð°Ð½Ð¸Ñ Ñ€ÐµÐ¿Ð¾Ð·Ð¸Ñ‚Ð¾Ñ€Ð¸Ñ Ð¸Ð»Ð¸ Ð´Ð¾Ð±Ð°Ð²Ð»ÐµÐ½Ð¸Ñ Ñ„Ð°Ð¹Ð»Ð¾Ð² в него одним из Ñледующих ÑпоÑобов."
msgid "You can invite a new member to <strong>%{project_name}</strong> or invite another group."
-msgstr ""
+msgstr "Ð’Ñ‹ можете приглаÑить нового учаÑтника в <strong>%{project_name}</strong> или приглаÑить другую группу."
msgid "You can invite a new member to <strong>%{project_name}</strong>."
-msgstr ""
+msgstr "Ð’Ñ‹ можете приглаÑить нового учаÑтника в <strong>%{project_name}</strong>."
msgid "You can invite another group to <strong>%{project_name}</strong>."
msgstr ""
@@ -25663,15 +26336,15 @@ msgstr ""
msgid "You can now export your security dashboard to a CSV report."
msgstr ""
+msgid "You can now manage alert endpoint configuration in the Alerts section on the Operations settings page. Fields on this page have been deprecated."
+msgstr ""
+
msgid "You can now submit a merge request to get this change into the original branch."
msgstr "Теперь вы можете отправить Ð·Ð°Ð¿Ñ€Ð¾Ñ Ð½Ð° ÑлиÑние, чтобы внеÑти Ñто изменение в иÑходную ветку."
msgid "You can now submit a merge request to get this change into the original project."
msgstr "Теперь вы можете отправить Ð·Ð°Ð¿Ñ€Ð¾Ñ Ð½Ð° ÑлиÑние, чтобы внеÑти Ñто изменение в иÑходный проект."
-msgid "You can only add files when you are on a branch"
-msgstr "Ð’Ñ‹ можете добавлÑÑ‚ÑŒ только файлы, когда находитеÑÑŒ в ветке"
-
msgid "You can only edit files when you are on a branch"
msgstr ""
@@ -25814,7 +26487,7 @@ msgid "You have no permissions"
msgstr ""
msgid "You have not added any approvers. Start by adding users or groups."
-msgstr ""
+msgstr "Ð’Ñ‹ не указали утверждающих. Ðачните, добавив пользователей или группы."
msgid "You have reached your project limit"
msgstr "Ð’Ñ‹ доÑтигли Ð¾Ð³Ñ€Ð°Ð½Ð¸Ñ‡ÐµÐ½Ð¸Ñ Ð² вашем проекте"
@@ -25891,7 +26564,7 @@ msgstr "Вам необходимо загрузить ÑкÑпортироваÐ
msgid "You need to upload a Google Takeout archive."
msgstr ""
-msgid "You reached %{usage_in_percent} of %{namespace_name}'s capacity (%{used_storage} of %{storage_limit})"
+msgid "You reached %{usage_in_percent} of %{namespace_name}'s storage capacity (%{used_storage} of %{storage_limit})"
msgstr ""
msgid "You tried to fork %{link_to_the_project} but it failed for the following reason:"
@@ -26147,7 +26820,7 @@ msgid "Your license will be included in your GitLab backup and will survive upgr
msgstr ""
msgid "Your message here"
-msgstr ""
+msgstr "Ваше Ñообщение здеÑÑŒ"
msgid "Your name"
msgstr "Ваше имÑ"
@@ -26194,13 +26867,13 @@ msgstr "По вашему запроÑу не найдено ни одного Ð
msgid "Your subscription expired!"
msgstr ""
-msgid "Your subscription has been downgraded"
+msgid "Your subscription has been downgraded."
msgstr ""
-msgid "Your subscription will automatically renew in %{remaining_days}"
+msgid "Your subscription will automatically renew in %{remaining_days}."
msgstr ""
-msgid "Your subscription will expire in %{remaining_days}"
+msgid "Your subscription will expire in %{remaining_days}."
msgstr ""
msgid "Zoom meeting added"
@@ -26256,10 +26929,10 @@ msgid "among other things"
msgstr "читать документацию"
msgid "any-approver for the merge request already exists"
-msgstr ""
+msgstr "утверждающий Ð´Ð»Ñ Ñтого запроÑа на ÑлиÑние уже ÑущеÑтвует"
msgid "any-approver for the project already exists"
-msgstr ""
+msgstr "утверждающий Ð´Ð»Ñ Ð¿Ñ€Ð¾ÐµÐºÑ‚Ð° уже ÑущеÑтвует"
msgid "archived"
msgstr ""
@@ -26282,9 +26955,6 @@ msgstr ""
msgid "branch name"
msgstr "Ð¸Ð¼Ñ Ð²ÐµÑ‚Ð²Ð¸"
-msgid "build pipeline reference mismatch"
-msgstr ""
-
msgid "by"
msgstr ""
@@ -26303,6 +26973,9 @@ msgstr "не может быть изменено"
msgid "cannot block others"
msgstr "невозможно заблокировать других"
+msgid "cannot contain HTML/XML tags, including any word between angle brackets (<,>)."
+msgstr ""
+
msgid "cannot include leading slash or directory traversal."
msgstr "не может Ñодержать Ñимвол \"/\" или обратный путь в каталогах."
@@ -26333,65 +27006,6 @@ msgstr ""
msgid "ciReport|%{remainingPackagesCount} more"
msgstr ""
-msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability"
-msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities"
-msgstr[0] ""
-msgstr[1] ""
-msgstr[2] ""
-msgstr[3] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability for the source branch only"
-msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities for the source branch only"
-msgstr[0] ""
-msgstr[1] ""
-msgstr[2] ""
-msgstr[3] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerability"
-msgid_plural "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerabilities"
-msgstr[0] ""
-msgstr[1] ""
-msgstr[2] ""
-msgstr[3] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new vulnerability"
-msgid_plural "ciReport|%{reportType} %{status} detected %{newCount} new vulnerabilities"
-msgstr[0] ""
-msgstr[1] ""
-msgstr[2] ""
-msgstr[3] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities for the source branch only"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{fixedCount} fixed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} vulnerability for the source branch only"
-msgid_plural "ciReport|%{reportType} %{status} detected %{newCount} vulnerabilities for the source branch only"
-msgstr[0] ""
-msgstr[1] ""
-msgstr[2] ""
-msgstr[3] ""
-
-msgid "ciReport|%{reportType} %{status} detected no new vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected no vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected no vulnerabilities for the source branch only"
-msgstr ""
-
msgid "ciReport|%{reportType} is loading"
msgstr ""
@@ -26410,8 +27024,8 @@ msgstr ""
msgid "ciReport|All projects"
msgstr "Ð’Ñе проекты"
-msgid "ciReport|All report types"
-msgstr "Ð’Ñе типы отчетов"
+msgid "ciReport|All scanner types"
+msgstr ""
msgid "ciReport|All severities"
msgstr ""
@@ -26434,6 +27048,9 @@ msgstr "Сканирование контейнера"
msgid "ciReport|Container scanning detects known vulnerabilities in your docker images."
msgstr "Сканирование контейнеров обнаруживает извеÑтные уÑзвимоÑти в ваших образах Docker."
+msgid "ciReport|Coverage Fuzzing"
+msgstr ""
+
msgid "ciReport|Create a merge request to implement this solution, or download and apply the patch manually."
msgstr ""
@@ -26553,7 +27170,7 @@ msgid "ciReport|View full report"
msgstr ""
msgid "closed issue"
-msgstr ""
+msgstr "закрытое обÑуждение"
msgid "comment"
msgstr "комментарий"
@@ -26594,6 +27211,9 @@ msgstr ""
msgid "customize"
msgstr ""
+msgid "data"
+msgstr ""
+
msgid "date must not be after 9999-12-31"
msgstr ""
@@ -26668,6 +27288,9 @@ msgstr ""
msgid "entries cannot contain HTML tags"
msgstr ""
+msgid "epic"
+msgstr ""
+
msgid "error"
msgstr ""
@@ -26689,9 +27312,6 @@ msgstr ""
msgid "expires on %{timebox_due_date}"
msgstr ""
-msgid "external_url"
-msgstr ""
-
msgid "failed"
msgstr "не удалоÑÑŒ"
@@ -26729,9 +27349,6 @@ msgstr ""
msgid "from"
msgstr ""
-msgid "geo_node_name"
-msgstr "geo_node_name"
-
msgid "group"
msgstr ""
@@ -26753,9 +27370,6 @@ msgstr "здеÑÑŒ"
msgid "https://your-bitbucket-server"
msgstr "https://ваш-bitbucket-server"
-msgid "image"
-msgstr ""
-
msgid "image diff"
msgstr "Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ð¾Ð±Ñ€Ð°Ð·Ð°"
@@ -26901,6 +27515,9 @@ msgstr ""
msgid "load it anyway"
msgstr ""
+msgid "loading"
+msgstr ""
+
msgid "locked by %{path_lock_user_name} %{created_at}"
msgstr ""
@@ -27031,9 +27648,6 @@ msgstr ""
msgid "mrWidget|Deployment statistics are not available currently"
msgstr "Ðа данный момент ÑтатиÑтика Ñ€Ð°Ð·Ð²Ñ‘Ñ€Ñ‚Ñ‹Ð²Ð°Ð½Ð¸Ñ Ð½ÐµÐ´Ð¾Ñтупна"
-msgid "mrWidget|Detect issues before deployment with a CI pipeline that continuously tests your code. We created a quick guide that will show you how to create one. Make your code more secure and more robust in just a minute."
-msgstr ""
-
msgid "mrWidget|Did not close"
msgstr "Ðе закрыт"
@@ -27196,6 +27810,9 @@ msgstr "Этот проект архивирован, доÑтуп на запи
msgid "mrWidget|To approve this merge request, please enter your password. This project requires all approvals to be authenticated."
msgstr "Чтобы ÑоглаÑовать Ñтот запроÑ, пожалуйÑта, введите ваш пароль. Этот проект требует Ð¿Ð¾Ð´Ñ‚Ð²ÐµÑ€Ð¶ÐµÐ½Ð¸Ñ Ð¿Ð°Ñ€Ð¾Ð»ÐµÐ¼."
+msgid "mrWidget|Use %{linkStart}CI pipelines to test your code%{linkEnd}, simply add a GitLab CI configuration file to your project. It only takes a minute to make your code more secure and robust."
+msgstr ""
+
msgid "mrWidget|When this merge request is ready, remove the WIP: prefix from the title to allow it to be merged"
msgstr ""
@@ -27232,6 +27849,9 @@ msgstr ""
msgid "must be greater than start date"
msgstr ""
+msgid "must contain only valid frameworks"
+msgstr ""
+
msgid "my-awesome-group"
msgstr ""
@@ -27244,6 +27864,9 @@ msgstr "требуют вниманиÑ"
msgid "needs to be between 10 minutes and 1 month"
msgstr "должно быть от 10 минут до 1 меÑÑца"
+msgid "never"
+msgstr ""
+
msgid "never expires"
msgstr "никогда не иÑтекает"
@@ -27271,6 +27894,9 @@ msgstr "email Ð´Ð»Ñ ÑƒÐ²ÐµÐ´Ð¾Ð¼Ð»ÐµÐ½Ð¸Ð¹"
msgid "nounSeries|%{firstItem} and %{lastItem}"
msgstr ""
+msgid "nounSeries|%{item}"
+msgstr ""
+
msgid "nounSeries|%{item}, %{nextItem}"
msgstr ""
@@ -27417,6 +28043,9 @@ msgstr ""
msgid "revised"
msgstr ""
+msgid "satisfied"
+msgstr ""
+
msgid "score"
msgstr "Ñчёт"
@@ -27447,6 +28076,9 @@ msgstr ""
msgid "severity|Unknown"
msgstr "ÐеизвеÑтнаÑ"
+msgid "should be an array of %{object_name} objects"
+msgstr ""
+
msgid "should be greater than or equal to %{access} inherited membership from group %{group_name}"
msgstr ""
@@ -27501,10 +28133,13 @@ msgstr "1/2: Выберите шаблон"
msgid "suggestPipeline|2/2: Commit your changes"
msgstr "2/2: ЗафикÑируйте изменениÑ"
-msgid "suggestPipeline|Commit the changes and your pipeline will automatically run for the first time."
-msgstr "ЗафикÑируйте Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ð¸ ÑÐ±Ð¾Ñ€Ð¾Ñ‡Ð½Ð°Ñ Ð»Ð¸Ð½Ð¸Ñ Ð°Ð²Ñ‚Ð¾Ð¼Ð°Ñ‚Ð¸Ñ‡ÐµÑки запуÑтитÑÑ Ð² первый раз."
+msgid "suggestPipeline|Choose %{boldStart}Code Quality%{boldEnd} to add a pipeline that tests the quality of your code."
+msgstr ""
-msgid "suggestPipeline|We recommend the %{boldStart}Code Quality%{boldEnd} template, which will add a report widget to your Merge Requests. This way you’ll learn about code quality degradations much sooner. %{footerStart} Goodbye technical debt! %{footerEnd}"
+msgid "suggestPipeline|The template is ready! You can now commit it to create your first pipeline."
+msgstr ""
+
+msgid "suggestPipeline|We’re adding a GitLab CI configuration file to add a pipeline to the project. You could create it manually, but we recommend that you start with a GitLab template that works out of the box."
msgstr ""
msgid "syntax is correct"
@@ -27564,6 +28199,9 @@ msgstr "Ð¸Ð¼Ñ Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ"
msgid "uses Kubernetes clusters to deploy your code!"
msgstr "иÑпользует клаÑтеры Kubernetes Ð´Ð»Ñ Ñ€Ð°Ð·Ð²ÐµÑ€Ñ‚Ñ‹Ð²Ð°Ð½Ð¸Ñ Ð²Ð°ÑˆÐµÐ³Ð¾ кода!"
+msgid "v%{version} published %{timeAgo}"
+msgstr ""
+
msgid "verify ownership"
msgstr "подтвердить право ÑобÑтвенноÑти"
diff --git a/locale/si_LK/gitlab.po b/locale/si_LK/gitlab.po
new file mode 100644
index 00000000000..b355ef66318
--- /dev/null
+++ b/locale/si_LK/gitlab.po
@@ -0,0 +1,27977 @@
+msgid ""
+msgstr ""
+"Project-Id-Version: gitlab-ee\n"
+"Report-Msgid-Bugs-To: \n"
+"Last-Translator: \n"
+"Language-Team: Sinhala\n"
+"Language: si_LK\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Crowdin-Project: gitlab-ee\n"
+"X-Crowdin-Project-ID: 288872\n"
+"X-Crowdin-Language: si-LK\n"
+"X-Crowdin-File: /master/locale/gitlab.pot\n"
+"X-Crowdin-File-ID: 6\n"
+"PO-Revision-Date: 2020-07-02 01:22\n"
+
+msgid " %{start} to %{end}"
+msgstr ""
+
+msgid " (from %{timeoutSource})"
+msgstr ""
+
+msgid " Collected %{time}"
+msgstr ""
+
+msgid " Please sign in."
+msgstr ""
+
+msgid " Try to %{action} this file again."
+msgstr ""
+
+msgid " You need to do this before %{grace_period_deadline}."
+msgstr ""
+
+msgid " and"
+msgstr ""
+
+msgid " and "
+msgstr ""
+
+msgid " and %{sliced}"
+msgstr ""
+
+msgid " degraded on %d point"
+msgid_plural " degraded on %d points"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid " improved on %d point"
+msgid_plural " improved on %d points"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid " or "
+msgstr ""
+
+msgid " or <!merge request id>"
+msgstr ""
+
+msgid " or <#issue id>"
+msgstr ""
+
+msgid " or <&epic id>"
+msgstr ""
+
+msgid " or references (e.g. path/to/project!merge_request_id)"
+msgstr ""
+
+msgid "\"%{path}\" did not exist on \"%{ref}\""
+msgstr ""
+
+msgid "%d URL scanned"
+msgid_plural "%d URLs scanned"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%d approver"
+msgid_plural "%d approvers"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%d approver (you've approved)"
+msgid_plural "%d approvers (you've approved)"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%d changed file"
+msgid_plural "%d changed files"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%d child epic"
+msgid_plural "%d child epics"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%d code quality issue"
+msgid_plural "%d code quality issues"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%d comment"
+msgid_plural "%d comments"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%d comment on this commit"
+msgid_plural "%d comments on this commit"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%d commit"
+msgid_plural "%d commits"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%d commit behind"
+msgid_plural "%d commits behind"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%d commit,"
+msgid_plural "%d commits,"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%d commits"
+msgstr ""
+
+msgid "%d contribution"
+msgid_plural "%d contributions"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%d day"
+msgid_plural "%d days"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%d error"
+msgid_plural "%d errors"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%d exporter"
+msgid_plural "%d exporters"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%d failed"
+msgid_plural "%d failed"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%d fixed test result"
+msgid_plural "%d fixed test results"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%d group selected"
+msgid_plural "%d groups selected"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%d inaccessible merge request"
+msgid_plural "%d inaccessible merge requests"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%d issue"
+msgid_plural "%d issues"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%d issue selected"
+msgid_plural "%d issues selected"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%d layer"
+msgid_plural "%d layers"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%d merge request"
+msgid_plural "%d merge requests"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%d merge request that you don't have access to."
+msgid_plural "%d merge requests that you don't have access to."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%d metric"
+msgid_plural "%d metrics"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%d milestone"
+msgid_plural "%d milestones"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%d minute"
+msgid_plural "%d minutes"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%d more comment"
+msgid_plural "%d more comments"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%d personal project will be removed and cannot be restored."
+msgid_plural "%d personal projects will be removed and cannot be restored."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%d project"
+msgid_plural "%d projects"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%d request with warnings"
+msgid_plural "%d requests with warnings"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%d second"
+msgid_plural "%d seconds"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%d shard selected"
+msgid_plural "%d shards selected"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%d tag"
+msgid_plural "%d tags"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%d unresolved thread"
+msgid_plural "%d unresolved threads"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%d vulnerability"
+msgid_plural "%d vulnerabilities"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%d vulnerability dismissed"
+msgid_plural "%d vulnerabilities dismissed"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%s additional commit has been omitted to prevent performance issues."
+msgid_plural "%s additional commits have been omitted to prevent performance issues."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{actionText} & %{openOrClose} %{noteable}"
+msgstr ""
+
+msgid "%{authorsName}'s thread"
+msgstr ""
+
+msgid "%{commit_author_link} authored %{commit_timeago}"
+msgstr ""
+
+msgid "%{completedWeight} of %{totalWeight} weight completed"
+msgstr ""
+
+msgid "%{containerScanningLinkStart}Container Scanning%{containerScanningLinkEnd} and/or %{dependencyScanningLinkStart}Dependency Scanning%{dependencyScanningLinkEnd} must be enabled. %{securityBotLinkStart}GitLab-Security-Bot%{securityBotLinkEnd} will be the author of the auto-created merge request. %{moreInfoLinkStart}More information%{moreInfoLinkEnd}."
+msgstr ""
+
+msgid "%{cores} cores"
+msgstr ""
+
+msgid "%{count} LOC/commit"
+msgstr ""
+
+msgid "%{count} approval required from %{name}"
+msgid_plural "%{count} approvals required from %{name}"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{count} approvals from %{name}"
+msgstr ""
+
+msgid "%{count} files touched"
+msgstr ""
+
+msgid "%{count} more"
+msgstr ""
+
+msgid "%{count} more assignees"
+msgstr ""
+
+msgid "%{count} more release"
+msgid_plural "%{count} more releases"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{count} of %{required} approvals from %{name}"
+msgstr ""
+
+msgid "%{count} of %{total}"
+msgstr ""
+
+msgid "%{count} participant"
+msgid_plural "%{count} participants"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{count} pending comment"
+msgid_plural "%{count} pending comments"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{count} related %{pluralized_subject}: %{links}"
+msgstr ""
+
+msgid "%{dashboard_path} could not be found."
+msgstr ""
+
+msgid "%{days} days until tags are automatically removed"
+msgstr ""
+
+msgid "%{deployLinkStart}Use a template to deploy to ECS%{deployLinkEnd}, or use a docker image to %{commandsLinkStart}run AWS commands in GitLab CI/CD%{commandsLinkEnd}."
+msgstr ""
+
+msgid "%{description}- Sentry event: %{errorUrl}- First seen: %{firstSeen}- Last seen: %{lastSeen} %{countLabel}: %{count}%{userCountLabel}: %{userCount}"
+msgstr ""
+
+msgid "%{duration}ms"
+msgstr ""
+
+msgid "%{edit_in_new_fork_notice} Try to cherry-pick this commit again."
+msgstr ""
+
+msgid "%{edit_in_new_fork_notice} Try to create a new directory again."
+msgstr ""
+
+msgid "%{edit_in_new_fork_notice} Try to revert this commit again."
+msgstr ""
+
+msgid "%{edit_in_new_fork_notice} Try to upload a file again."
+msgstr ""
+
+msgid "%{extra} more downstream pipelines"
+msgstr ""
+
+msgid "%{filePath} deleted"
+msgstr ""
+
+msgid "%{firstLabel} +%{labelCount} more"
+msgstr ""
+
+msgid "%{firstMilestoneName} + %{numberOfOtherMilestones} more"
+msgstr ""
+
+msgid "%{global_id} is not a valid id for %{expected_type}."
+msgstr ""
+
+msgid "%{group_docs_link_start}Groups%{group_docs_link_end} allow you to manage and collaborate across multiple projects. Members of a group have access to all of its projects."
+msgstr ""
+
+msgid "%{group_name} uses group managed accounts. You need to create a new GitLab account which will be managed by %{group_name}."
+msgstr ""
+
+msgid "%{host} sign-in from new location"
+msgstr ""
+
+msgid "%{icon}You are about to add %{usersTag} people to the discussion. They will all receive a notification."
+msgstr ""
+
+msgid "%{integrations_link_start}Integrations%{link_end} enable you to make third-party applications part of your GitLab workflow. If the available integrations don't meet your needs, consider using a %{webhooks_link_start}webhook%{link_end}."
+msgstr ""
+
+msgid "%{issuableType} will be removed! Are you sure?"
+msgstr ""
+
+msgid "%{issuesCount} issues in this group"
+msgstr ""
+
+msgid "%{issuesSize} issues"
+msgstr ""
+
+msgid "%{issuesSize} issues with a limit of %{maxIssueCount}"
+msgstr ""
+
+msgid "%{labelStart}Class:%{labelEnd} %{class}"
+msgstr ""
+
+msgid "%{labelStart}Evidence:%{labelEnd} %{evidence}"
+msgstr ""
+
+msgid "%{labelStart}File:%{labelEnd} %{file}"
+msgstr ""
+
+msgid "%{labelStart}Image:%{labelEnd} %{image}"
+msgstr ""
+
+msgid "%{labelStart}Method:%{labelEnd} %{method}"
+msgstr ""
+
+msgid "%{labelStart}Namespace:%{labelEnd} %{namespace}"
+msgstr ""
+
+msgid "%{labelStart}Report Type:%{labelEnd} %{reportType}"
+msgstr ""
+
+msgid "%{labelStart}Scanner:%{labelEnd} %{scanner}"
+msgstr ""
+
+msgid "%{labelStart}Severity:%{labelEnd} %{severity}"
+msgstr ""
+
+msgid "%{label_for_message} unavailable"
+msgstr ""
+
+msgid "%{lets_encrypt_link_start}Let's Encrypt%{lets_encrypt_link_end} is a free, automated, and open certificate authority (CA), that give digital certificates in order to enable HTTPS (SSL/TLS) for websites."
+msgstr ""
+
+msgid "%{level_name} is not allowed in a %{group_level_name} group."
+msgstr ""
+
+msgid "%{level_name} is not allowed since the fork source project has lower visibility."
+msgstr ""
+
+msgid "%{lineOneStart}Drag and drop to upload your designs%{lineOneEnd} or %{linkStart}click to upload%{linkEnd}."
+msgstr ""
+
+msgid "%{link_start}Learn more%{link_end} about what information is shared with GitLab Inc."
+msgstr ""
+
+msgid "%{link_start}Read more%{link_end} about role permissions"
+msgstr ""
+
+msgid "%{listToShow}, and %{awardsListLength} more."
+msgstr ""
+
+msgid "%{loadingIcon} Started"
+msgstr ""
+
+msgid "%{lock_path} is locked by GitLab User %{lock_user_id}"
+msgstr ""
+
+msgid "%{markdownDocsLinkStart}Markdown%{markdownDocsLinkEnd} and %{quickActionsDocsLinkStart}quick actions%{quickActionsDocsLinkEnd} are supported"
+msgstr ""
+
+msgid "%{mergeLength}/%{usersLength} can merge"
+msgstr ""
+
+msgid "%{mrText}, this issue will be closed automatically."
+msgstr ""
+
+msgid "%{name_with_link} has %{percent} or less Shared Runner Pipeline minutes remaining. Once it runs out, no new jobs or pipelines in its projects will run."
+msgstr ""
+
+msgid "%{name_with_link} has run out of Shared Runner Pipeline minutes so no new jobs or pipelines in its projects will run."
+msgstr ""
+
+msgid "%{namespace_name} is now read-only. You cannot: %{base_message}"
+msgstr ""
+
+msgid "%{name} contained %{resultsString}"
+msgstr ""
+
+msgid "%{name} found %{resultsString}"
+msgstr ""
+
+msgid "%{name} is already being used for another emoji"
+msgstr ""
+
+msgid "%{name} is scheduled for %{action}"
+msgstr ""
+
+msgid "%{name}'s avatar"
+msgstr ""
+
+msgid "%{name}(%{url}) has %{percent} or less Shared Runner Pipeline minutes remaining. Once it runs out, no new jobs or pipelines in its projects will run."
+msgstr ""
+
+msgid "%{name}(%{url}) has run out of Shared Runner Pipeline minutes so no new jobs or pipelines in its projects will run."
+msgstr ""
+
+msgid "%{no_of_days} day"
+msgid_plural "%{no_of_days} days"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{number_commits_behind} commits behind %{default_branch}, %{number_commits_ahead} commits ahead"
+msgstr ""
+
+msgid "%{openOrClose} %{noteable}"
+msgstr ""
+
+msgid "%{openedEpics} open, %{closedEpics} closed"
+msgstr ""
+
+msgid "%{openedIssues} open, %{closedIssues} closed"
+msgstr ""
+
+msgid "%{percentage}%% weight completed"
+msgstr ""
+
+msgid "%{percent}%% complete"
+msgstr ""
+
+msgid "%{percent}%{percentSymbol} complete"
+msgstr ""
+
+msgid "%{placeholder} is not a valid color scheme"
+msgstr ""
+
+msgid "%{placeholder} is not a valid theme"
+msgstr ""
+
+msgid "%{primary} (%{secondary})"
+msgstr ""
+
+msgid "%{ref} cannot be added: %{error}"
+msgstr ""
+
+msgid "%{releases} release"
+msgid_plural "%{releases} releases"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{remaining_approvals} left"
+msgstr ""
+
+msgid "%{reportType} %{status} detected %{critical} critical and %{high} high severity vulnerabilities out of %{total}."
+msgstr ""
+
+msgid "%{reportType} %{status} detected %{critical} critical and %{high} high severity vulnerabilities."
+msgstr ""
+
+msgid "%{reportType} %{status} detected %{critical} critical severity vulnerabilities out of %{total}."
+msgstr ""
+
+msgid "%{reportType} %{status} detected %{critical} critical severity vulnerability."
+msgid_plural "%{reportType} %{status} detected %{critical} critical severity vulnerabilities."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{reportType} %{status} detected %{high} high severity vulnerabilities out of %{total}."
+msgstr ""
+
+msgid "%{reportType} %{status} detected %{high} high severity vulnerability."
+msgid_plural "%{reportType} %{status} detected %{high} high severity vulnerabilities."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{reportType} %{status} detected %{other} vulnerability."
+msgid_plural "%{reportType} %{status} detected %{other} vulnerabilities."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{reportType} %{status} detected no new vulnerabilities."
+msgstr ""
+
+msgid "%{retryButtonStart}Try again%{retryButtonEnd} or %{newFileButtonStart}attach a new file%{newFileButtonEnd}"
+msgstr ""
+
+msgid "%{service_title} %{message}."
+msgstr ""
+
+msgid "%{size} GiB"
+msgstr ""
+
+msgid "%{size} KiB"
+msgstr ""
+
+msgid "%{size} MiB"
+msgstr ""
+
+msgid "%{size} bytes"
+msgstr ""
+
+msgid "%{spammable_titlecase} was submitted to Akismet successfully."
+msgstr ""
+
+msgid "%{spanStart}at line%{spanEnd} %{errorLine}%{errorColumn}"
+msgstr ""
+
+msgid "%{spanStart}in%{spanEnd} %{errorFn}"
+msgstr ""
+
+msgid "%{start} to %{end}"
+msgstr ""
+
+msgid "%{state} epics"
+msgstr ""
+
+msgid "%{strongStart}Note:%{strongEnd} Once a custom stage has been added you can re-order stages by dragging them into the desired position."
+msgstr ""
+
+msgid "%{strong_start}%{branch_count}%{strong_end} Branch"
+msgid_plural "%{strong_start}%{branch_count}%{strong_end} Branches"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{strong_start}%{commit_count}%{strong_end} Commit"
+msgid_plural "%{strong_start}%{commit_count}%{strong_end} Commits"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{strong_start}%{human_size}%{strong_end} Files"
+msgstr ""
+
+msgid "%{strong_start}%{human_size}%{strong_end} Storage"
+msgstr ""
+
+msgid "%{strong_start}%{release_count}%{strong_end} Release"
+msgid_plural "%{strong_start}%{release_count}%{strong_end} Releases"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{strong_start}%{tag_count}%{strong_end} Tag"
+msgid_plural "%{strong_start}%{tag_count}%{strong_end} Tags"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{tabname} changed"
+msgstr ""
+
+msgid "%{tags} tag per image name"
+msgstr ""
+
+msgid "%{tags} tags per image name"
+msgstr ""
+
+msgid "%{tag}-%{evidence}-%{filename}"
+msgstr ""
+
+msgid "%{template_project_id} is unknown or invalid"
+msgstr ""
+
+msgid "%{text} %{files}"
+msgid_plural "%{text} %{files} files"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{text} is available"
+msgstr ""
+
+msgid "%{timebox_name} should belong either to a project or a group."
+msgstr ""
+
+msgid "%{title} %{operator} %{threshold}"
+msgstr ""
+
+msgid "%{title} changes"
+msgstr ""
+
+msgid "%{token}..."
+msgstr ""
+
+msgid "%{totalCpu} (%{freeSpacePercentage}%{percentSymbol} free)"
+msgstr ""
+
+msgid "%{totalMemory} (%{freeSpacePercentage}%{percentSymbol} free)"
+msgstr ""
+
+msgid "%{totalWeight} total weight"
+msgstr ""
+
+msgid "%{total} open issue weight"
+msgstr ""
+
+msgid "%{total} open issues"
+msgstr ""
+
+msgid "%{usage_ping_link_start}Learn more%{usage_ping_link_end} about what information is shared with GitLab Inc."
+msgstr ""
+
+msgid "%{userName} (cannot merge)"
+msgstr ""
+
+msgid "%{userName}'s avatar"
+msgstr ""
+
+msgid "%{user_name} profile page"
+msgstr ""
+
+msgid "%{username}'s avatar"
+msgstr ""
+
+msgid "%{value} s"
+msgstr ""
+
+msgid "%{verb} %{time_spent_value} spent time."
+msgstr ""
+
+msgid "%{webhooks_link_start}%{webhook_type}%{link_end} enable you to send notifications to web applications in response to events in a group or project."
+msgstr ""
+
+msgid "%{webhooks_link_start}%{webhook_type}%{link_end} enable you to send notifications to web applications in response to events in a group or project. We recommend using an %{integrations_link_start}integration%{link_end} in preference to a webhook."
+msgstr ""
+
+msgid "'%{level}' is not a valid visibility level"
+msgstr ""
+
+msgid "'%{name}' stage already exists"
+msgstr ""
+
+msgid "'%{source}' is not a import source"
+msgstr ""
+
+msgid "'%{template_name}' is unknown or invalid"
+msgstr ""
+
+msgid "(%d closed)"
+msgid_plural "(%d closed)"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "(%{linkStart}Cron syntax%{linkEnd})"
+msgstr ""
+
+msgid "(%{mrCount} merged)"
+msgstr ""
+
+msgid "(No changes)"
+msgstr ""
+
+msgid "(check progress)"
+msgstr ""
+
+msgid "(external source)"
+msgstr ""
+
+msgid "(removed)"
+msgstr ""
+
+msgid "(revoked)"
+msgstr ""
+
+msgid "*"
+msgstr ""
+
+msgid "+ %{amount} more"
+msgstr ""
+
+msgid "+ %{count} more"
+msgstr ""
+
+msgid "+ %{moreCount} more"
+msgstr ""
+
+msgid "+ %{numberOfHiddenAssignees} more"
+msgstr ""
+
+msgid "+%d more"
+msgid_plural "+%d more"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "+%{approvers} more approvers"
+msgstr ""
+
+msgid "+%{tags} more"
+msgstr ""
+
+msgid ", or "
+msgstr ""
+
+msgid "- Event"
+msgid_plural "- Events"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "- Runner is active and can process any new jobs"
+msgstr ""
+
+msgid "- Runner is paused and will not receive any new jobs"
+msgstr ""
+
+msgid "- User"
+msgid_plural "- Users"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "- of - weight completed"
+msgstr ""
+
+msgid "- show less"
+msgstr ""
+
+msgid "0 for unlimited"
+msgstr ""
+
+msgid "0 for unlimited, only effective with remote storage enabled."
+msgstr ""
+
+msgid "1 %{type} addition"
+msgid_plural "%{count} %{type} additions"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "1 %{type} modification"
+msgid_plural "%{count} %{type} modifications"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "1 Day"
+msgid_plural "%d Days"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "1 closed issue"
+msgid_plural "%{issues} closed issues"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "1 closed merge request"
+msgid_plural "%{merge_requests} closed merge requests"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "1 day"
+msgid_plural "%d days"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "1 group"
+msgid_plural "%d groups"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "1 hour"
+msgid_plural "%d hours"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "1 merged merge request"
+msgid_plural "%{merge_requests} merged merge requests"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "1 minute"
+msgid_plural "%d minutes"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "1 month"
+msgstr ""
+
+msgid "1 open issue"
+msgid_plural "%{issues} open issues"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "1 open merge request"
+msgid_plural "%{merge_requests} open merge requests"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "1 pipeline"
+msgid_plural "%d pipelines"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "1 role"
+msgid_plural "%d roles"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "1 user"
+msgid_plural "%{num} users"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "1 week"
+msgstr ""
+
+msgid "1-9 contributions"
+msgstr ""
+
+msgid "10-19 contributions"
+msgstr ""
+
+msgid "1st contribution!"
+msgstr ""
+
+msgid "20-29 contributions"
+msgstr ""
+
+msgid "2FA"
+msgstr ""
+
+msgid "2FADevice|Registered On"
+msgstr ""
+
+msgid "3 days"
+msgstr ""
+
+msgid "3 hours"
+msgstr ""
+
+msgid "30 minutes"
+msgstr ""
+
+msgid "30+ contributions"
+msgstr ""
+
+msgid "403|Please contact your GitLab administrator to get permission."
+msgstr ""
+
+msgid "403|You don't have the permission to access this page."
+msgstr ""
+
+msgid "404|Make sure the address is correct and the page hasn't moved."
+msgstr ""
+
+msgid "404|Page Not Found"
+msgstr ""
+
+msgid "404|Please contact your GitLab administrator if you think this is a mistake."
+msgstr ""
+
+msgid "8 hours"
+msgstr ""
+
+msgid ":%{startLine} to %{endLine}"
+msgstr ""
+
+msgid "< 1 hour"
+msgstr ""
+
+msgid "<code>\"johnsmith@example.com\": \"@johnsmith\"</code> will add \"By <a href=\"#\">@johnsmith</a>\" to all issues and comments originally created by johnsmith@example.com, and will set <a href=\"#\">@johnsmith</a> as the assignee on all issues originally assigned to johnsmith@example.com."
+msgstr ""
+
+msgid "<code>\"johnsmith@example.com\": \"John Smith\"</code> will add \"By John Smith\" to all issues and comments originally created by johnsmith@example.com."
+msgstr ""
+
+msgid "<code>\"johnsmith@example.com\": \"johnsm...@example.com\"</code> will add \"By johnsm...@example.com\" to all issues and comments originally created by johnsmith@example.com. The email address or username is masked to ensure the user's privacy."
+msgstr ""
+
+msgid "<code>\"johnsmith@example.com\": \"johnsmith@example.com\"</code> will add \"By <a href=\"#\">johnsmith@example.com</a>\" to all issues and comments originally created by johnsmith@example.com. By default, the email address or username is masked to ensure the user's privacy. Use this option if you want to show the full email address."
+msgstr ""
+
+msgid "<code>Masked</code> to prevent the values from being displayed in job logs (must match certain regexp requirements)."
+msgstr ""
+
+msgid "<code>Protected</code> to expose them to protected branches or tags only."
+msgstr ""
+
+msgid "<namespace / project>"
+msgstr ""
+
+msgid "<no name set>"
+msgstr ""
+
+msgid "<no scopes selected>"
+msgstr ""
+
+msgid "<project name>"
+msgstr ""
+
+msgid "<strong>%{group_name}</strong> group members"
+msgstr ""
+
+msgid "<strong>%{label_name}</strong> <span>will be permanently deleted from %{subject_name}. This cannot be undone.</span>"
+msgstr ""
+
+msgid "<strong>Deletes</strong> source branch"
+msgstr ""
+
+msgid "A 'Runner' is a process which runs a job. You can set up as many Runners as you need."
+msgstr ""
+
+msgid "A .NET Core console application template, customizable for any .NET Core project"
+msgstr ""
+
+msgid "A GitBook site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
+msgid "A Hexo site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
+msgid "A Hugo site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
+msgid "A Jekyll site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
+msgid "A Let's Encrypt SSL certificate can not be obtained until your domain is verified."
+msgstr ""
+
+msgid "A Let's Encrypt account will be configured for this GitLab installation using your email address. You will receive emails to warn of expiring certificates."
+msgstr ""
+
+msgid "A basic page and serverless function that uses AWS Lambda, AWS API Gateway, and GitLab Pages"
+msgstr ""
+
+msgid "A complete DevOps platform"
+msgstr ""
+
+msgid "A default branch cannot be chosen for an empty project."
+msgstr ""
+
+msgid "A deleted user"
+msgstr ""
+
+msgid "A file has been changed."
+msgstr ""
+
+msgid "A file was not found."
+msgstr ""
+
+msgid "A file with '%{file_name}' already exists in %{branch} branch"
+msgstr ""
+
+msgid "A fork is a copy of a project.<br />Forking a repository allows you to make changes without affecting the original project."
+msgstr ""
+
+msgid "A group is a collection of several projects"
+msgstr ""
+
+msgid "A group represents your organization in GitLab. Groups allow you to manage users and collaborate across multiple projects."
+msgstr ""
+
+msgid "A member of the abuse team will review your report as soon as possible."
+msgstr ""
+
+msgid "A merge request approval is required when a security report contains a new vulnerability of high, critical, or unknown severity."
+msgstr ""
+
+msgid "A merge request approval is required when the license compliance report contains a blacklisted license."
+msgstr ""
+
+msgid "A new Auto DevOps pipeline has been created, go to %{pipelines_link_start}Pipelines page%{pipelines_link_end} for details"
+msgstr ""
+
+msgid "A new Release %{tag} for %{name} was published. Visit the %{release_link_start}Releases page%{release_link_end} to read more about it."
+msgstr ""
+
+msgid "A new Release %{tag} for %{name} was published. Visit the Releases page to read more about it:"
+msgstr ""
+
+msgid "A new branch will be created in your fork and a new merge request will be started."
+msgstr ""
+
+msgid "A new impersonation token has been created."
+msgstr ""
+
+msgid "A plain HTML site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
+msgid "A project boilerplate for Salesforce App development with Salesforce Developer tools."
+msgstr ""
+
+msgid "A project containing issues for each audit inquiry in the HIPAA Audit Protocol published by the U.S. Department of Health & Human Services"
+msgstr ""
+
+msgid "A project is where you house your files (repository), plan your work (issues), and publish your documentation (wiki), %{among_other_things_link}."
+msgstr ""
+
+msgid "A ready-to-go template for use with Android apps."
+msgstr ""
+
+msgid "A ready-to-go template for use with iOS Swift apps."
+msgstr ""
+
+msgid "A regular expression that will be used to find the test coverage output in the job log. Leave blank to disable"
+msgstr ""
+
+msgid "A secure token that identifies an external storage request."
+msgstr ""
+
+msgid "A sign-in to your account has been made from the following IP address: %{ip}"
+msgstr ""
+
+msgid "A subscription will trigger a new pipeline on the default branch of this project when a pipeline successfully completes for a new tag on the %{default_branch_docs} of the subscribed project."
+msgstr ""
+
+msgid "A suggestion is not applicable."
+msgstr ""
+
+msgid "A user with write access to the source branch selected this option"
+msgstr ""
+
+msgid "ACTION REQUIRED: Something went wrong while obtaining the Let's Encrypt certificate for GitLab Pages domain '%{domain}'"
+msgstr ""
+
+msgid "API Token"
+msgstr ""
+
+msgid "AWS Access Key"
+msgstr ""
+
+msgid "AWS Access Key. Only required if not using role instance credentials"
+msgstr ""
+
+msgid "AWS Secret Access Key"
+msgstr ""
+
+msgid "AWS Secret Access Key. Only required if not using role instance credentials"
+msgstr ""
+
+msgid "Abort"
+msgstr ""
+
+msgid "About GitLab"
+msgstr ""
+
+msgid "About GitLab CE"
+msgstr ""
+
+msgid "About auto deploy"
+msgstr ""
+
+msgid "About this feature"
+msgstr ""
+
+msgid "Abuse Reports"
+msgstr ""
+
+msgid "Abuse reports"
+msgstr ""
+
+msgid "Accept invitation"
+msgstr ""
+
+msgid "Accept terms"
+msgstr ""
+
+msgid "Acceptable for use in this project"
+msgstr ""
+
+msgid "Access Tokens"
+msgstr ""
+
+msgid "Access denied for your LDAP account."
+msgstr ""
+
+msgid "Access denied! Please verify you can add deploy keys to this repository."
+msgstr ""
+
+msgid "Access expiration date"
+msgstr ""
+
+msgid "Access forbidden. Check your access level."
+msgstr ""
+
+msgid "Access to '%{classification_label}' not allowed"
+msgstr ""
+
+msgid "Access to Pages websites are controlled based on the user's membership to a given project. By checking this box, users will be required to be logged in to have access to all Pages websites in your instance."
+msgstr ""
+
+msgid "AccessDropdown|Groups"
+msgstr ""
+
+msgid "AccessDropdown|Roles"
+msgstr ""
+
+msgid "AccessDropdown|Users"
+msgstr ""
+
+msgid "AccessTokens|Access Tokens"
+msgstr ""
+
+msgid "AccessTokens|Are you sure?"
+msgstr ""
+
+msgid "AccessTokens|Are you sure? Any RSS or calendar URLs currently in use will stop working."
+msgstr ""
+
+msgid "AccessTokens|Are you sure? Any issue email addresses currently in use will stop working."
+msgstr ""
+
+msgid "AccessTokens|Created"
+msgstr ""
+
+msgid "AccessTokens|Feed token"
+msgstr ""
+
+msgid "AccessTokens|Incoming email token"
+msgstr ""
+
+msgid "AccessTokens|It cannot be used to access any other data."
+msgstr ""
+
+msgid "AccessTokens|Keep this token secret. Anyone who gets ahold of it can access repository static objects as if they were you. You should %{reset_link_start}reset it%{reset_link_end} if that ever happens."
+msgstr ""
+
+msgid "AccessTokens|Keep this token secret. Anyone who gets ahold of it can create issues as if they were you. You should %{link_reset_it} if that ever happens."
+msgstr ""
+
+msgid "AccessTokens|Keep this token secret. Anyone who gets ahold of it can read activity and issue RSS feeds or your calendar feed as if they were you. You should %{link_reset_it} if that ever happens."
+msgstr ""
+
+msgid "AccessTokens|Personal Access Tokens"
+msgstr ""
+
+msgid "AccessTokens|Static object token"
+msgstr ""
+
+msgid "AccessTokens|They are the only accepted password when you have Two-Factor Authentication (2FA) enabled."
+msgstr ""
+
+msgid "AccessTokens|You can also use personal access tokens to authenticate against Git over HTTP."
+msgstr ""
+
+msgid "AccessTokens|You can generate a personal access token for each application you use that needs access to the GitLab API."
+msgstr ""
+
+msgid "AccessTokens|Your feed token is used to authenticate you when your RSS reader loads a personalized RSS feed or when your calendar application loads a personalized calendar, and is included in those feed URLs."
+msgstr ""
+
+msgid "AccessTokens|Your incoming email token is used to authenticate you when you create a new issue by email, and is included in your personal project-specific email addresses."
+msgstr ""
+
+msgid "AccessTokens|Your static object token is used to authenticate you when repository static objects (e.g. archives, blobs, ...) are being served from an external storage."
+msgstr ""
+
+msgid "AccessTokens|reset it"
+msgstr ""
+
+msgid "AccessibilityReport|Learn More"
+msgstr ""
+
+msgid "AccessibilityReport|Message: %{message}"
+msgstr ""
+
+msgid "AccessibilityReport|New"
+msgstr ""
+
+msgid "AccessibilityReport|The accessibility scanning found an error of the following type: %{code}"
+msgstr ""
+
+msgid "Account"
+msgstr ""
+
+msgid "Account ID"
+msgstr ""
+
+msgid "Account and limit"
+msgstr ""
+
+msgid "Account: %{account}"
+msgstr ""
+
+msgid "Action to take when receiving an alert."
+msgstr ""
+
+msgid "Actions"
+msgstr ""
+
+msgid "Activate"
+msgstr ""
+
+msgid "Activate Service Desk"
+msgstr ""
+
+msgid "Active"
+msgstr ""
+
+msgid "Active %{type} (%{token_length})"
+msgstr ""
+
+msgid "Active Sessions"
+msgstr ""
+
+msgid "Active Users:"
+msgstr ""
+
+msgid "Active users"
+msgstr ""
+
+msgid "Activity"
+msgstr ""
+
+msgid "Add"
+msgstr ""
+
+msgid "Add %d issue"
+msgid_plural "Add %d issues"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Add %{linkStart}assets%{linkEnd} to your Release. GitLab automatically includes read-only assets, like source code and release evidence."
+msgstr ""
+
+msgid "Add CHANGELOG"
+msgstr ""
+
+msgid "Add CONTRIBUTING"
+msgstr ""
+
+msgid "Add GitLab to Slack"
+msgstr ""
+
+msgid "Add Group Webhooks and GitLab Enterprise Edition."
+msgstr ""
+
+msgid "Add Jaeger URL"
+msgstr ""
+
+msgid "Add Kubernetes cluster"
+msgstr ""
+
+msgid "Add LICENSE"
+msgstr ""
+
+msgid "Add New Node"
+msgstr ""
+
+msgid "Add README"
+msgstr ""
+
+msgid "Add Variable"
+msgstr ""
+
+msgid "Add Zoom meeting"
+msgstr ""
+
+msgid "Add a %{type}"
+msgstr ""
+
+msgid "Add a GPG key"
+msgstr ""
+
+msgid "Add a Grafana button in the admin sidebar, monitoring section, to access a variety of statistics on the health and performance of GitLab."
+msgstr ""
+
+msgid "Add a To Do"
+msgstr ""
+
+msgid "Add a bullet list"
+msgstr ""
+
+msgid "Add a comment to this line"
+msgstr ""
+
+msgid "Add a general comment to this %{noteableDisplayName}."
+msgstr ""
+
+msgid "Add a general comment to this %{noteable_name}."
+msgstr ""
+
+msgid "Add a homepage to your wiki that contains information about your project and GitLab will display it here instead of this message."
+msgstr ""
+
+msgid "Add a line"
+msgstr ""
+
+msgid "Add a link"
+msgstr ""
+
+msgid "Add a new issue"
+msgstr ""
+
+msgid "Add a numbered list"
+msgstr ""
+
+msgid "Add a table"
+msgstr ""
+
+msgid "Add a task list"
+msgstr ""
+
+msgid "Add additional text to appear in all email communications. %{character_limit} character limit"
+msgstr ""
+
+msgid "Add an SSH key"
+msgstr ""
+
+msgid "Add an existing issue"
+msgstr ""
+
+msgid "Add an impersonation token"
+msgstr ""
+
+msgid "Add an issue"
+msgstr ""
+
+msgid "Add another link"
+msgstr ""
+
+msgid "Add approval rule"
+msgstr ""
+
+msgid "Add approvers"
+msgstr ""
+
+msgid "Add bold text"
+msgstr ""
+
+msgid "Add child epic to an epic"
+msgstr ""
+
+msgid "Add comment now"
+msgstr ""
+
+msgid "Add domain"
+msgstr ""
+
+msgid "Add email address"
+msgstr ""
+
+msgid "Add environment"
+msgstr ""
+
+msgid "Add header and footer to emails. Please note that color settings will only be applied within the application interface"
+msgstr ""
+
+msgid "Add image comment"
+msgstr ""
+
+msgid "Add issues"
+msgstr ""
+
+msgid "Add italic text"
+msgstr ""
+
+msgid "Add label(s)"
+msgstr ""
+
+msgid "Add license"
+msgstr ""
+
+msgid "Add list"
+msgstr ""
+
+msgid "Add new application"
+msgstr ""
+
+msgid "Add new directory"
+msgstr ""
+
+msgid "Add or subtract spent time"
+msgstr ""
+
+msgid "Add reaction"
+msgstr ""
+
+msgid "Add request manually"
+msgstr ""
+
+msgid "Add strikethrough text"
+msgstr ""
+
+msgid "Add suggestion to batch"
+msgstr ""
+
+msgid "Add system hook"
+msgstr ""
+
+msgid "Add to Slack"
+msgstr ""
+
+msgid "Add to epic"
+msgstr ""
+
+msgid "Add to merge train"
+msgstr ""
+
+msgid "Add to merge train when pipeline succeeds"
+msgstr ""
+
+msgid "Add to review"
+msgstr ""
+
+msgid "Add to tree"
+msgstr ""
+
+msgid "Add user(s) to the group:"
+msgstr ""
+
+msgid "Add users to group"
+msgstr ""
+
+msgid "Add variable"
+msgstr ""
+
+msgid "Add webhook"
+msgstr ""
+
+msgid "AddMember|No users specified."
+msgstr ""
+
+msgid "AddMember|Too many users specified (limit is %{user_limit})"
+msgstr ""
+
+msgid "Added"
+msgstr ""
+
+msgid "Added %{epic_ref} as a child epic."
+msgstr ""
+
+msgid "Added %{label_references} %{label_text}."
+msgstr ""
+
+msgid "Added a To Do."
+msgstr ""
+
+msgid "Added an issue to an epic."
+msgstr ""
+
+msgid "Added at"
+msgstr ""
+
+msgid "Added for this merge request"
+msgstr ""
+
+msgid "Added in this version"
+msgstr ""
+
+msgid "Adding new applications is disabled in your GitLab instance. Please contact your GitLab administrator to get the permission"
+msgstr ""
+
+msgid "Additional minutes"
+msgstr ""
+
+msgid "Additional text"
+msgstr ""
+
+msgid "Adds"
+msgstr ""
+
+msgid "Adds %{epic_ref} as child epic."
+msgstr ""
+
+msgid "Adds %{labels} %{label_text}."
+msgstr ""
+
+msgid "Adds a To Do."
+msgstr ""
+
+msgid "Adds a Zoom meeting"
+msgstr ""
+
+msgid "Adds an issue to an epic."
+msgstr ""
+
+msgid "Adjust your filters/search criteria above. If you believe this may be an error, please refer to the %{linkStart}Geo Troubleshooting%{linkEnd} documentation for more information."
+msgstr ""
+
+msgid "Admin Area"
+msgstr ""
+
+msgid "Admin Note"
+msgstr ""
+
+msgid "Admin Notifications"
+msgstr ""
+
+msgid "Admin Overview"
+msgstr ""
+
+msgid "Admin mode already enabled"
+msgstr ""
+
+msgid "Admin mode disabled"
+msgstr ""
+
+msgid "Admin mode enabled"
+msgstr ""
+
+msgid "Admin notes"
+msgstr ""
+
+msgid "AdminArea|Active users"
+msgstr ""
+
+msgid "AdminArea|Billable users"
+msgstr ""
+
+msgid "AdminArea|Blocked users"
+msgstr ""
+
+msgid "AdminArea|Bots"
+msgstr ""
+
+msgid "AdminArea|Developer"
+msgstr ""
+
+msgid "AdminArea|Guest"
+msgstr ""
+
+msgid "AdminArea|Included Free in license"
+msgstr ""
+
+msgid "AdminArea|Maintainer"
+msgstr ""
+
+msgid "AdminArea|Owner"
+msgstr ""
+
+msgid "AdminArea|Reporter"
+msgstr ""
+
+msgid "AdminArea|Stop all jobs"
+msgstr ""
+
+msgid "AdminArea|Stop all jobs?"
+msgstr ""
+
+msgid "AdminArea|Stop jobs"
+msgstr ""
+
+msgid "AdminArea|Stopping jobs failed"
+msgstr ""
+
+msgid "AdminArea|Total users"
+msgstr ""
+
+msgid "AdminArea|Users statistics"
+msgstr ""
+
+msgid "AdminArea|Users with highest role"
+msgstr ""
+
+msgid "AdminArea|Users without a Group and Project"
+msgstr ""
+
+msgid "AdminArea|You’re about to stop all jobs.This will halt all current jobs that are running."
+msgstr ""
+
+msgid "AdminDashboard|Error loading the statistics. Please try again"
+msgstr ""
+
+msgid "AdminNote|Note"
+msgstr ""
+
+msgid "AdminProjects| You’re about to permanently delete the project %{projectName}, its repository, and all related resources including issues, merge requests, etc.. Once you confirm and press %{strong_start}Delete project%{strong_end}, it cannot be undone or recovered."
+msgstr ""
+
+msgid "AdminProjects|Delete"
+msgstr ""
+
+msgid "AdminProjects|Delete Project %{projectName}?"
+msgstr ""
+
+msgid "AdminProjects|Delete project"
+msgstr ""
+
+msgid "AdminSettings|Apply integration settings to all Projects"
+msgstr ""
+
+msgid "AdminSettings|Auto DevOps domain"
+msgstr ""
+
+msgid "AdminSettings|Elasticsearch, PlantUML, Slack application, Third party offers, Snowplow, Amazon EKS have moved to Settings > General."
+msgstr ""
+
+msgid "AdminSettings|Enable shared runners for new projects"
+msgstr ""
+
+msgid "AdminSettings|Environment variables are protected by default"
+msgstr ""
+
+msgid "AdminSettings|Go to General Settings"
+msgstr ""
+
+msgid "AdminSettings|Integrations configured here will automatically apply to all projects on this instance."
+msgstr ""
+
+msgid "AdminSettings|Moved to integrations"
+msgstr ""
+
+msgid "AdminSettings|No required pipeline"
+msgstr ""
+
+msgid "AdminSettings|Required pipeline configuration"
+msgstr ""
+
+msgid "AdminSettings|Select a pipeline configuration file"
+msgstr ""
+
+msgid "AdminSettings|Select a template"
+msgstr ""
+
+msgid "AdminSettings|Service template allows you to set default values for integrations"
+msgstr ""
+
+msgid "AdminSettings|Set an instance-wide auto included %{link_start}pipeline configuration%{link_end}. This pipeline configuration will be run after the project's own configuration."
+msgstr ""
+
+msgid "AdminSettings|Some settings have moved"
+msgstr ""
+
+msgid "AdminSettings|Specify a domain to use by default for every project's Auto Review Apps and Auto Deploy stages."
+msgstr ""
+
+msgid "AdminSettings|The required pipeline configuration can be selected from the %{code_start}gitlab-ci%{code_end} directory inside of the configured %{link_start}instance template repository%{link_end} or from GitLab provided configurations."
+msgstr ""
+
+msgid "AdminSettings|When creating a new environment variable it will be protected by default."
+msgstr ""
+
+msgid "AdminStatistics|Active Users"
+msgstr ""
+
+msgid "AdminStatistics|Forks"
+msgstr ""
+
+msgid "AdminStatistics|Issues"
+msgstr ""
+
+msgid "AdminStatistics|Merge Requests"
+msgstr ""
+
+msgid "AdminStatistics|Milestones"
+msgstr ""
+
+msgid "AdminStatistics|Notes"
+msgstr ""
+
+msgid "AdminStatistics|SSH Keys"
+msgstr ""
+
+msgid "AdminStatistics|Snippets"
+msgstr ""
+
+msgid "AdminUsers|2FA Disabled"
+msgstr ""
+
+msgid "AdminUsers|2FA Enabled"
+msgstr ""
+
+msgid "AdminUsers|Active"
+msgstr ""
+
+msgid "AdminUsers|Admin"
+msgstr ""
+
+msgid "AdminUsers|Admins"
+msgstr ""
+
+msgid "AdminUsers|Block"
+msgstr ""
+
+msgid "AdminUsers|Block user"
+msgstr ""
+
+msgid "AdminUsers|Block user %{username}?"
+msgstr ""
+
+msgid "AdminUsers|Blocked"
+msgstr ""
+
+msgid "AdminUsers|Blocking user has the following effects:"
+msgstr ""
+
+msgid "AdminUsers|Cannot unblock LDAP blocked users"
+msgstr ""
+
+msgid "AdminUsers|Deactivate"
+msgstr ""
+
+msgid "AdminUsers|Deactivate User %{username}?"
+msgstr ""
+
+msgid "AdminUsers|Deactivate user"
+msgstr ""
+
+msgid "AdminUsers|Deactivated"
+msgstr ""
+
+msgid "AdminUsers|Deactivating a user has the following effects:"
+msgstr ""
+
+msgid "AdminUsers|Delete User %{username} and contributions?"
+msgstr ""
+
+msgid "AdminUsers|Delete User %{username}?"
+msgstr ""
+
+msgid "AdminUsers|Delete user"
+msgstr ""
+
+msgid "AdminUsers|Delete user and contributions"
+msgstr ""
+
+msgid "AdminUsers|External"
+msgstr ""
+
+msgid "AdminUsers|Is using seat"
+msgstr ""
+
+msgid "AdminUsers|It's you!"
+msgstr ""
+
+msgid "AdminUsers|New user"
+msgstr ""
+
+msgid "AdminUsers|No users found"
+msgstr ""
+
+msgid "AdminUsers|Owned groups will be left"
+msgstr ""
+
+msgid "AdminUsers|Personal projects will be left"
+msgstr ""
+
+msgid "AdminUsers|Personal projects, group and user history will be left intact"
+msgstr ""
+
+msgid "AdminUsers|Reactivating a user will:"
+msgstr ""
+
+msgid "AdminUsers|Restore user access to the account, including web, Git and API."
+msgstr ""
+
+msgid "AdminUsers|Search by name, email or username"
+msgstr ""
+
+msgid "AdminUsers|Search users"
+msgstr ""
+
+msgid "AdminUsers|Send email to users"
+msgstr ""
+
+msgid "AdminUsers|Sort by"
+msgstr ""
+
+msgid "AdminUsers|The user will be logged out"
+msgstr ""
+
+msgid "AdminUsers|The user will not be able to access git repositories"
+msgstr ""
+
+msgid "AdminUsers|The user will not be able to access the API"
+msgstr ""
+
+msgid "AdminUsers|The user will not be able to use slash commands"
+msgstr ""
+
+msgid "AdminUsers|The user will not receive any notifications"
+msgstr ""
+
+msgid "AdminUsers|To confirm, type %{projectName}"
+msgstr ""
+
+msgid "AdminUsers|To confirm, type %{username}"
+msgstr ""
+
+msgid "AdminUsers|User will not be able to access git repositories"
+msgstr ""
+
+msgid "AdminUsers|User will not be able to login"
+msgstr ""
+
+msgid "AdminUsers|When the user logs back in, their account will reactivate as a fully active account"
+msgstr ""
+
+msgid "AdminUsers|Without projects"
+msgstr ""
+
+msgid "AdminUsers|You are about to permanently delete the user %{username}. Issues, merge requests, and groups linked to them will be transferred to a system-wide \"Ghost-user\". To avoid data loss, consider using the %{strong_start}block user%{strong_end} feature instead. Once you %{strong_start}Delete user%{strong_end}, it cannot be undone or recovered."
+msgstr ""
+
+msgid "AdminUsers|You are about to permanently delete the user %{username}. This will delete all of the issues, merge requests, and groups linked to them. To avoid data loss, consider using the %{strong_start}block user%{strong_end} feature instead. Once you %{strong_start}Delete user%{strong_end}, it cannot be undone or recovered."
+msgstr ""
+
+msgid "Administration"
+msgstr ""
+
+msgid "Advanced"
+msgstr ""
+
+msgid "Advanced Settings"
+msgstr ""
+
+msgid "Advanced permissions, Large File Storage and Two-Factor authentication settings."
+msgstr ""
+
+msgid "Advanced search functionality"
+msgstr ""
+
+msgid "After a successful password update you will be redirected to login screen."
+msgstr ""
+
+msgid "After a successful password update, you will be redirected to the login page where you can log in with your new password."
+msgstr ""
+
+msgid "After that, you will not to be able to use merge approvals or code quality as well as many other features."
+msgstr ""
+
+msgid "After that, you will not to be able to use merge approvals or epics as well as many other features."
+msgstr ""
+
+msgid "After that, you will not to be able to use merge approvals or epics as well as many security features."
+msgstr ""
+
+msgid "Alert"
+msgid_plural "Alerts"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "AlertManagement| assign yourself"
+msgstr ""
+
+msgid "AlertManagement|Acknowledged"
+msgstr ""
+
+msgid "AlertManagement|Alert"
+msgstr ""
+
+msgid "AlertManagement|Alert assignee(s): %{assignees}"
+msgstr ""
+
+msgid "AlertManagement|Alert detail"
+msgstr ""
+
+msgid "AlertManagement|Alert details"
+msgstr ""
+
+msgid "AlertManagement|Alert status: %{status}"
+msgstr ""
+
+msgid "AlertManagement|Alerts"
+msgstr ""
+
+msgid "AlertManagement|All alerts"
+msgstr ""
+
+msgid "AlertManagement|Assign To"
+msgstr ""
+
+msgid "AlertManagement|Assign status"
+msgstr ""
+
+msgid "AlertManagement|Assignee"
+msgstr ""
+
+msgid "AlertManagement|Assignees"
+msgstr ""
+
+msgid "AlertManagement|Authorize external service"
+msgstr ""
+
+msgid "AlertManagement|Create issue"
+msgstr ""
+
+msgid "AlertManagement|Critical"
+msgstr ""
+
+msgid "AlertManagement|Display alerts from all your monitoring tools directly within GitLab. Streamline the investigation of your alerts and the escalation of alerts to incidents."
+msgstr ""
+
+msgid "AlertManagement|Edit"
+msgstr ""
+
+msgid "AlertManagement|Events"
+msgstr ""
+
+msgid "AlertManagement|High"
+msgstr ""
+
+msgid "AlertManagement|Info"
+msgstr ""
+
+msgid "AlertManagement|Low"
+msgstr ""
+
+msgid "AlertManagement|Medium"
+msgstr ""
+
+msgid "AlertManagement|More information"
+msgstr ""
+
+msgid "AlertManagement|No Matching Results"
+msgstr ""
+
+msgid "AlertManagement|No alert data to display."
+msgstr ""
+
+msgid "AlertManagement|No alerts available to display. See %{linkStart}enabling alert management%{linkEnd} for more information on adding alerts to the list."
+msgstr ""
+
+msgid "AlertManagement|No alerts to display."
+msgstr ""
+
+msgid "AlertManagement|None"
+msgstr ""
+
+msgid "AlertManagement|None -"
+msgstr ""
+
+msgid "AlertManagement|Open"
+msgstr ""
+
+msgid "AlertManagement|Overview"
+msgstr ""
+
+msgid "AlertManagement|Reported %{when}"
+msgstr ""
+
+msgid "AlertManagement|Reported %{when} by %{tool}"
+msgstr ""
+
+msgid "AlertManagement|Resolved"
+msgstr ""
+
+msgid "AlertManagement|Service"
+msgstr ""
+
+msgid "AlertManagement|Severity"
+msgstr ""
+
+msgid "AlertManagement|Start time"
+msgstr ""
+
+msgid "AlertManagement|Status"
+msgstr ""
+
+msgid "AlertManagement|Surface alerts in GitLab"
+msgstr ""
+
+msgid "AlertManagement|There was an error displaying the alert. Please refresh the page to try again."
+msgstr ""
+
+msgid "AlertManagement|There was an error displaying the alerts. Confirm your endpoint's configuration details to ensure alerts appear."
+msgstr ""
+
+msgid "AlertManagement|There was an error while updating the assignee(s) list. Please try again."
+msgstr ""
+
+msgid "AlertManagement|There was an error while updating the assignee(s) of the alert. Please try again."
+msgstr ""
+
+msgid "AlertManagement|There was an error while updating the status of the alert. Please try again."
+msgstr ""
+
+msgid "AlertManagement|This assignee cannot be assigned to this alert."
+msgstr ""
+
+msgid "AlertManagement|Tool"
+msgstr ""
+
+msgid "AlertManagement|Triggered"
+msgstr ""
+
+msgid "AlertManagement|Unassigned"
+msgstr ""
+
+msgid "AlertManagement|Unknown"
+msgstr ""
+
+msgid "AlertManagement|View issue"
+msgstr ""
+
+msgid "AlertService|Review your external service's documentation to learn where to provide this information to your external service, and the %{linkStart}GitLab documentation%{linkEnd} to learn more about configuring your endpoint."
+msgstr ""
+
+msgid "AlertService|You must provide this URL and authorization key to authorize an external service to send alerts to GitLab. You can provide this URL and key to multiple services. After configuring an external service, alerts from your service will display on the GitLab %{linkStart}Alerts%{linkEnd} page."
+msgstr ""
+
+msgid "Alerts"
+msgstr ""
+
+msgid "Alerts endpoint"
+msgstr ""
+
+msgid "Algorithm"
+msgstr ""
+
+msgid "All"
+msgstr ""
+
+msgid "All %{replicableType} are being scheduled for %{action}"
+msgstr ""
+
+msgid "All (default)"
+msgstr ""
+
+msgid "All Members"
+msgstr ""
+
+msgid "All branches"
+msgstr ""
+
+msgid "All changes are committed"
+msgstr ""
+
+msgid "All default stages are currently visible"
+msgstr ""
+
+msgid "All email addresses will be used to identify your commits."
+msgstr ""
+
+msgid "All environments"
+msgstr ""
+
+msgid "All features are enabled for blank projects, from templates, or when importing, but you can disable them afterward in the project settings."
+msgstr ""
+
+msgid "All groups and projects"
+msgstr ""
+
+msgid "All issues for this milestone are closed."
+msgstr ""
+
+msgid "All issues for this milestone are closed. You may close this milestone now."
+msgstr ""
+
+msgid "All merge conflicts were resolved. The merge request can now be merged."
+msgstr ""
+
+msgid "All merge request dependencies have been merged"
+msgstr ""
+
+msgid "All paths are relative to the GitLab URL. Do not include %{relative_url_link_start}relative URL%{relative_url_link_end}."
+msgstr ""
+
+msgid "All projects"
+msgstr ""
+
+msgid "All security scans are enabled because %{linkStart}Auto DevOps%{linkEnd} is enabled on this project"
+msgstr ""
+
+msgid "All threads resolved"
+msgstr ""
+
+msgid "All users"
+msgstr ""
+
+msgid "All users must have a name."
+msgstr ""
+
+msgid "Allow \"%{group_name}\" to sign you in"
+msgstr ""
+
+msgid "Allow commits from members who can merge to the target branch."
+msgstr ""
+
+msgid "Allow group owners to manage LDAP-related settings"
+msgstr ""
+
+msgid "Allow only the selected protocols to be used for Git access."
+msgstr ""
+
+msgid "Allow owners to manage default branch protection per group"
+msgstr ""
+
+msgid "Allow owners to manually add users outside of LDAP"
+msgstr ""
+
+msgid "Allow projects within this group to use Git LFS"
+msgstr ""
+
+msgid "Allow public access to pipelines and job details, including output logs and artifacts"
+msgstr ""
+
+msgid "Allow rendering of PlantUML diagrams in Asciidoc documents."
+msgstr ""
+
+msgid "Allow repository mirroring to be configured by project maintainers"
+msgstr ""
+
+msgid "Allow requests to the local network from hooks and services."
+msgstr ""
+
+msgid "Allow requests to the local network from system hooks"
+msgstr ""
+
+msgid "Allow requests to the local network from web hooks and services"
+msgstr ""
+
+msgid "Allow this key to push to repository as well? (Default only allows pull access.)"
+msgstr ""
+
+msgid "Allow this secondary node to replicate content on Object Storage"
+msgstr ""
+
+msgid "Allow users to dismiss the broadcast message"
+msgstr ""
+
+msgid "Allow users to register any application to use GitLab as an OAuth provider"
+msgstr ""
+
+msgid "Allow users to request access (if visibility is public or internal)"
+msgstr ""
+
+msgid "Allowed Geo IP"
+msgstr ""
+
+msgid "Allowed email domain restriction only permitted for top-level groups"
+msgstr ""
+
+msgid "Allowed to fail"
+msgstr ""
+
+msgid "Allows you to add and manage Kubernetes clusters."
+msgstr ""
+
+msgid "Almost there"
+msgstr ""
+
+msgid "Also called \"Issuer\" or \"Relying party trust identifier\""
+msgstr ""
+
+msgid "Also called \"Relying party service URL\" or \"Reply URL\""
+msgstr ""
+
+msgid "Alternate support URL for help page and help dropdown"
+msgstr ""
+
+msgid "Alternatively, you can convert your account to a managed account by the %{group_name} group."
+msgstr ""
+
+msgid "Amazon EKS"
+msgstr ""
+
+msgid "Amazon EKS integration allows you to provision EKS clusters from GitLab."
+msgstr ""
+
+msgid "Amazon Web Services"
+msgstr ""
+
+msgid "Amazon Web Services Logo"
+msgstr ""
+
+msgid "Amazon authentication is not %{link_start}correctly configured%{link_end}. Ask your GitLab administrator if you want to use this service."
+msgstr ""
+
+msgid "Amount of time (in hours) that users are allowed to skip forced configuration of two-factor authentication"
+msgstr ""
+
+msgid "An alert has been triggered in %{project_path}."
+msgstr ""
+
+msgid "An application called %{link_to_client} is requesting access to your GitLab account."
+msgstr ""
+
+msgid "An email notification was recently sent from the admin panel. Please wait %{wait_time_in_words} before attempting to send another message."
+msgstr ""
+
+msgid "An empty GitLab User field will add the FogBugz user's full name (e.g. \"By John Smith\") in the description of all issues and comments. It will also associate and/or assign these issues and comments with the project creator."
+msgstr ""
+
+msgid "An error has occurred"
+msgstr ""
+
+msgid "An error occurred adding a draft to the thread."
+msgstr ""
+
+msgid "An error occurred adding a new draft."
+msgstr ""
+
+msgid "An error occurred creating the new branch."
+msgstr ""
+
+msgid "An error occurred fetching the approval rules."
+msgstr ""
+
+msgid "An error occurred fetching the approvers for the new rule."
+msgstr ""
+
+msgid "An error occurred fetching the dropdown data."
+msgstr ""
+
+msgid "An error occurred fetching the project authors."
+msgstr ""
+
+msgid "An error occurred previewing the blob"
+msgstr ""
+
+msgid "An error occurred when toggling the notification subscription"
+msgstr ""
+
+msgid "An error occurred when trying to resolve a comment. Please try again."
+msgstr ""
+
+msgid "An error occurred when trying to resolve a discussion. Please try again."
+msgstr ""
+
+msgid "An error occurred when updating the issue weight"
+msgstr ""
+
+msgid "An error occurred while acknowledging the notification. Refresh the page and try again."
+msgstr ""
+
+msgid "An error occurred while adding approvers"
+msgstr ""
+
+msgid "An error occurred while adding formatted title for epic"
+msgstr ""
+
+msgid "An error occurred while checking group path. Please refresh and try again."
+msgstr ""
+
+msgid "An error occurred while committing your changes."
+msgstr ""
+
+msgid "An error occurred while decoding the file."
+msgstr ""
+
+msgid "An error occurred while deleting the approvers group"
+msgstr ""
+
+msgid "An error occurred while deleting the comment"
+msgstr ""
+
+msgid "An error occurred while deleting the pipeline."
+msgstr ""
+
+msgid "An error occurred while detecting host keys"
+msgstr ""
+
+msgid "An error occurred while disabling Service Desk."
+msgstr ""
+
+msgid "An error occurred while dismissing the alert. Refresh the page and try again."
+msgstr ""
+
+msgid "An error occurred while dismissing the feature highlight. Refresh the page and try dismissing again."
+msgstr ""
+
+msgid "An error occurred while enabling Service Desk."
+msgstr ""
+
+msgid "An error occurred while fetching coverage reports."
+msgstr ""
+
+msgid "An error occurred while fetching environments."
+msgstr ""
+
+msgid "An error occurred while fetching exposed artifacts."
+msgstr ""
+
+msgid "An error occurred while fetching folder content."
+msgstr ""
+
+msgid "An error occurred while fetching issues."
+msgstr ""
+
+msgid "An error occurred while fetching label colors."
+msgstr ""
+
+msgid "An error occurred while fetching markdown preview"
+msgstr ""
+
+msgid "An error occurred while fetching pending comments"
+msgstr ""
+
+msgid "An error occurred while fetching projects autocomplete."
+msgstr ""
+
+msgid "An error occurred while fetching sidebar data"
+msgstr ""
+
+msgid "An error occurred while fetching terraform reports."
+msgstr ""
+
+msgid "An error occurred while fetching the Service Desk address."
+msgstr ""
+
+msgid "An error occurred while fetching the board lists. Please try again."
+msgstr ""
+
+msgid "An error occurred while fetching the builds."
+msgstr ""
+
+msgid "An error occurred while fetching the job log."
+msgstr ""
+
+msgid "An error occurred while fetching the job trace."
+msgstr ""
+
+msgid "An error occurred while fetching the job."
+msgstr ""
+
+msgid "An error occurred while fetching the jobs."
+msgstr ""
+
+msgid "An error occurred while fetching the latest pipeline."
+msgstr ""
+
+msgid "An error occurred while fetching the pipeline."
+msgstr ""
+
+msgid "An error occurred while fetching the releases. Please try again."
+msgstr ""
+
+msgid "An error occurred while fetching this tab."
+msgstr ""
+
+msgid "An error occurred while generating a username. Please try again."
+msgstr ""
+
+msgid "An error occurred while getting files for - %{branchId}"
+msgstr ""
+
+msgid "An error occurred while getting projects"
+msgstr ""
+
+msgid "An error occurred while importing project: %{details}"
+msgstr ""
+
+msgid "An error occurred while initializing path locks"
+msgstr ""
+
+msgid "An error occurred while loading all the files."
+msgstr ""
+
+msgid "An error occurred while loading chart data"
+msgstr ""
+
+msgid "An error occurred while loading commit signatures"
+msgstr ""
+
+msgid "An error occurred while loading designs. Please try again."
+msgstr ""
+
+msgid "An error occurred while loading diff"
+msgstr ""
+
+msgid "An error occurred while loading filenames"
+msgstr ""
+
+msgid "An error occurred while loading group members."
+msgstr ""
+
+msgid "An error occurred while loading issues"
+msgstr ""
+
+msgid "An error occurred while loading merge requests."
+msgstr ""
+
+msgid "An error occurred while loading milestones"
+msgstr ""
+
+msgid "An error occurred while loading project creation UI"
+msgstr ""
+
+msgid "An error occurred while loading the data. Please try again."
+msgstr ""
+
+msgid "An error occurred while loading the file"
+msgstr ""
+
+msgid "An error occurred while loading the file content."
+msgstr ""
+
+msgid "An error occurred while loading the file."
+msgstr ""
+
+msgid "An error occurred while loading the file. Please try again later."
+msgstr ""
+
+msgid "An error occurred while loading the merge request changes."
+msgstr ""
+
+msgid "An error occurred while loading the merge request version data."
+msgstr ""
+
+msgid "An error occurred while loading the merge request."
+msgstr ""
+
+msgid "An error occurred while loading the pipelines jobs."
+msgstr ""
+
+msgid "An error occurred while loading the subscription details."
+msgstr ""
+
+msgid "An error occurred while making the request."
+msgstr ""
+
+msgid "An error occurred while moving the issue."
+msgstr ""
+
+msgid "An error occurred while parsing recent searches"
+msgstr ""
+
+msgid "An error occurred while parsing the file."
+msgstr ""
+
+msgid "An error occurred while removing epics."
+msgstr ""
+
+msgid "An error occurred while removing issues."
+msgstr ""
+
+msgid "An error occurred while rendering preview broadcast message"
+msgstr ""
+
+msgid "An error occurred while rendering the editor"
+msgstr ""
+
+msgid "An error occurred while reordering issues."
+msgstr ""
+
+msgid "An error occurred while retrieving calendar activity"
+msgstr ""
+
+msgid "An error occurred while retrieving diff"
+msgstr ""
+
+msgid "An error occurred while saving LDAP override status. Please try again."
+msgstr ""
+
+msgid "An error occurred while saving assignees"
+msgstr ""
+
+msgid "An error occurred while saving the approval settings"
+msgstr ""
+
+msgid "An error occurred while saving the template. Please check if the template exists."
+msgstr ""
+
+msgid "An error occurred while searching for milestones"
+msgstr ""
+
+msgid "An error occurred while subscribing to notifications."
+msgstr ""
+
+msgid "An error occurred while triggering the job."
+msgstr ""
+
+msgid "An error occurred while trying to run a new pipeline for this Merge Request."
+msgstr ""
+
+msgid "An error occurred while unsubscribing to notifications."
+msgstr ""
+
+msgid "An error occurred while updating approvers"
+msgstr ""
+
+msgid "An error occurred while updating the comment"
+msgstr ""
+
+msgid "An error occurred while validating group path"
+msgstr ""
+
+msgid "An error occurred while validating username"
+msgstr ""
+
+msgid "An error occurred. Please try again."
+msgstr ""
+
+msgid "An error ocurred while loading your content. Please try again."
+msgstr ""
+
+msgid "An example project for managing Kubernetes clusters integrated with GitLab."
+msgstr ""
+
+msgid "An instance-level serverless domain already exists."
+msgstr ""
+
+msgid "An issue already exists"
+msgstr ""
+
+msgid "An issue can be a bug, a todo or a feature request that needs to be discussed in a project. Besides, issues are searchable and filterable."
+msgstr ""
+
+msgid "An unauthenticated user"
+msgstr ""
+
+msgid "An unexpected error occurred while checking the project environment."
+msgstr ""
+
+msgid "An unexpected error occurred while checking the project runners."
+msgstr ""
+
+msgid "An unexpected error occurred while communicating with the Web Terminal."
+msgstr ""
+
+msgid "An unexpected error occurred while starting the Web Terminal."
+msgstr ""
+
+msgid "An unexpected error occurred while stopping the Web Terminal."
+msgstr ""
+
+msgid "An unknown error occurred while loading this graph."
+msgstr ""
+
+msgid "Analytics"
+msgstr ""
+
+msgid "Analyze a review version of your web application."
+msgstr ""
+
+msgid "Analyze your dependencies for known vulnerabilities."
+msgstr ""
+
+msgid "Analyze your source code and git history for secrets."
+msgstr ""
+
+msgid "Analyze your source code for known vulnerabilities."
+msgstr ""
+
+msgid "Ancestors"
+msgstr ""
+
+msgid "Anonymous"
+msgstr ""
+
+msgid "Another action is currently in progress"
+msgstr ""
+
+msgid "Another issue tracker is already in use. Only one issue tracker service can be active at a time"
+msgstr ""
+
+msgid "Anti-spam verification"
+msgstr ""
+
+msgid "Any"
+msgstr ""
+
+msgid "Any Author"
+msgstr ""
+
+msgid "Any branch"
+msgstr ""
+
+msgid "Any eligible user"
+msgstr ""
+
+msgid "Any encrypted tokens"
+msgstr ""
+
+msgid "Any label"
+msgstr ""
+
+msgid "Any member with Developer or higher permissions to the project."
+msgstr ""
+
+msgid "Any milestone"
+msgstr ""
+
+msgid "Any namespace"
+msgstr ""
+
+msgid "Any user"
+msgstr ""
+
+msgid "App ID"
+msgstr ""
+
+msgid "Appearance"
+msgstr ""
+
+msgid "Appearance was successfully created."
+msgstr ""
+
+msgid "Appearance was successfully updated."
+msgstr ""
+
+msgid "Append the comment with %{shrug}"
+msgstr ""
+
+msgid "Append the comment with %{tableflip}"
+msgstr ""
+
+msgid "Application"
+msgstr ""
+
+msgid "Application ID"
+msgstr ""
+
+msgid "Application settings saved successfully"
+msgstr ""
+
+msgid "Application settings update failed"
+msgstr ""
+
+msgid "Application uninstalled but failed to destroy: %{error_message}"
+msgstr ""
+
+msgid "Application was successfully destroyed."
+msgstr ""
+
+msgid "Application was successfully updated."
+msgstr ""
+
+msgid "Application: %{name}"
+msgstr ""
+
+msgid "Applications"
+msgstr ""
+
+msgid "Applied"
+msgstr ""
+
+msgid "Apply"
+msgstr ""
+
+msgid "Apply a label"
+msgstr ""
+
+msgid "Apply a template"
+msgstr ""
+
+msgid "Apply changes"
+msgstr ""
+
+msgid "Apply suggestion"
+msgstr ""
+
+msgid "Apply suggestions"
+msgstr ""
+
+msgid "Apply template"
+msgstr ""
+
+msgid "Apply this approval rule to any branch or a specific protected branch."
+msgstr ""
+
+msgid "Applying"
+msgstr ""
+
+msgid "Applying a template will replace the existing issue description. Any changes you have made will be lost."
+msgstr ""
+
+msgid "Applying command"
+msgstr ""
+
+msgid "Applying command to %{commandDescription}"
+msgstr ""
+
+msgid "Applying multiple commands"
+msgstr ""
+
+msgid "Applying suggestion..."
+msgstr ""
+
+msgid "Applying suggestions..."
+msgstr ""
+
+msgid "Approval rules"
+msgstr ""
+
+msgid "Approval rules reset to project defaults"
+msgstr ""
+
+msgid "ApprovalRuleRemove|%d member"
+msgid_plural "ApprovalRuleRemove|%d members"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "ApprovalRuleRemove|Approvals from this member are not revoked."
+msgid_plural "ApprovalRuleRemove|Approvals from these members are not revoked."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "ApprovalRuleRemove|You are about to remove the %{name} approver group which has %{nMembers}."
+msgstr ""
+
+msgid "ApprovalRuleSummary|%d member"
+msgid_plural "ApprovalRuleSummary|%d members"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "ApprovalRuleSummary|%{count} approval required from %{membersCount}"
+msgid_plural "ApprovalRuleSummary|%{count} approvals required from %{membersCount}"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "ApprovalRule|Approvers"
+msgstr ""
+
+msgid "ApprovalRule|Name"
+msgstr ""
+
+msgid "ApprovalRule|No. approvals required"
+msgstr ""
+
+msgid "ApprovalRule|Rule name"
+msgstr ""
+
+msgid "ApprovalRule|Target branch"
+msgstr ""
+
+msgid "ApprovalRule|e.g. QA, Security, etc."
+msgstr ""
+
+msgid "Approve"
+msgstr ""
+
+msgid "Approve a merge request"
+msgstr ""
+
+msgid "Approve the current merge request."
+msgstr ""
+
+msgid "Approved"
+msgstr ""
+
+msgid "Approved MRs"
+msgstr ""
+
+msgid "Approved by: "
+msgstr ""
+
+msgid "Approved the current merge request."
+msgstr ""
+
+msgid "Approved-By"
+msgstr ""
+
+msgid "Approver"
+msgstr ""
+
+msgid "Approvers"
+msgstr ""
+
+msgid "Apr"
+msgstr ""
+
+msgid "April"
+msgstr ""
+
+msgid "Archive"
+msgstr ""
+
+msgid "Archive jobs"
+msgstr ""
+
+msgid "Archive project"
+msgstr ""
+
+msgid "Archived"
+msgstr ""
+
+msgid "Archived project! Repository and other project resources are read only"
+msgstr ""
+
+msgid "Archived project! Repository and other project resources are read-only"
+msgstr ""
+
+msgid "Archived projects"
+msgstr ""
+
+msgid "Archiving the project will make it entirely read only. It is hidden from the dashboard and doesn't show up in searches. %{strong_start}The repository cannot be committed to, and no issues, comments, or other entities can be created.%{strong_end}"
+msgstr ""
+
+msgid "Are you setting up GitLab for a company?"
+msgstr ""
+
+msgid "Are you sure that you want to archive this project?"
+msgstr ""
+
+msgid "Are you sure that you want to unarchive this project?"
+msgstr ""
+
+msgid "Are you sure you want to cancel editing this comment?"
+msgstr ""
+
+msgid "Are you sure you want to close this blocked issue?"
+msgstr ""
+
+msgid "Are you sure you want to delete %{name}?"
+msgstr ""
+
+msgid "Are you sure you want to delete these artifacts?"
+msgstr ""
+
+msgid "Are you sure you want to delete this %{typeOfComment}?"
+msgstr ""
+
+msgid "Are you sure you want to delete this board?"
+msgstr ""
+
+msgid "Are you sure you want to delete this device? This action cannot be undone."
+msgstr ""
+
+msgid "Are you sure you want to delete this list?"
+msgstr ""
+
+msgid "Are you sure you want to delete this pipeline schedule?"
+msgstr ""
+
+msgid "Are you sure you want to delete this pipeline? Doing so will expire all pipeline caches and delete all related objects, such as builds, logs, artifacts, and triggers. This action cannot be undone."
+msgstr ""
+
+msgid "Are you sure you want to deploy this environment?"
+msgstr ""
+
+msgid "Are you sure you want to discard this comment?"
+msgstr ""
+
+msgid "Are you sure you want to erase this build?"
+msgstr ""
+
+msgid "Are you sure you want to lose unsaved changes?"
+msgstr ""
+
+msgid "Are you sure you want to lose your issue information?"
+msgstr ""
+
+msgid "Are you sure you want to merge immediately?"
+msgstr ""
+
+msgid "Are you sure you want to permanently delete this license?"
+msgstr ""
+
+msgid "Are you sure you want to re-deploy this environment?"
+msgstr ""
+
+msgid "Are you sure you want to regenerate the public key? You will have to update the public key on the remote server before mirroring will work again."
+msgstr ""
+
+msgid "Are you sure you want to remove %{group_name}?"
+msgstr ""
+
+msgid "Are you sure you want to remove the attachment?"
+msgstr ""
+
+msgid "Are you sure you want to remove the license?"
+msgstr ""
+
+msgid "Are you sure you want to remove this identity?"
+msgstr ""
+
+msgid "Are you sure you want to reset registration token?"
+msgstr ""
+
+msgid "Are you sure you want to reset the SCIM token? SCIM provisioning will stop working until the new token is updated."
+msgstr ""
+
+msgid "Are you sure you want to reset the health check token?"
+msgstr ""
+
+msgid "Are you sure you want to revoke this %{type}? This action cannot be undone."
+msgstr ""
+
+msgid "Are you sure you want to revoke this nickname?"
+msgstr ""
+
+msgid "Are you sure you want to stop this environment?"
+msgstr ""
+
+msgid "Are you sure you want to unlock %{path_lock_path}?"
+msgstr ""
+
+msgid "Are you sure you want to unsubscribe from the %{type}: %{link_to_noteable_text}?"
+msgstr ""
+
+msgid "Are you sure?"
+msgstr ""
+
+msgid "Are you sure? All commits that were signed with this GPG key will be unverified."
+msgstr ""
+
+msgid "Are you sure? Removing this GPG key does not affect already signed commits."
+msgstr ""
+
+msgid "Are you sure? The device will be signed out of GitLab."
+msgstr ""
+
+msgid "Are you sure? This will invalidate your registered applications and U2F devices."
+msgstr ""
+
+msgid "Arrange charts"
+msgstr ""
+
+msgid "Artifact"
+msgstr ""
+
+msgid "Artifact ID"
+msgstr ""
+
+msgid "Artifact could not be deleted."
+msgstr ""
+
+msgid "Artifact was successfully deleted."
+msgstr ""
+
+msgid "Artifacts"
+msgstr ""
+
+msgid "As U2F devices are only supported by a few browsers, we require that you set up a two-factor authentication app before a U2F device. That way you'll always be able to log in - even when you're using an unsupported browser."
+msgstr ""
+
+msgid "AsanaService|%{user} pushed to branch %{branch} of %{project_name} ( %{commit_url} ):"
+msgstr ""
+
+msgid "AsanaService|Asana - Teamwork without email"
+msgstr ""
+
+msgid "AsanaService|Comma-separated list of branches which will be automatically inspected. Leave blank to include all branches."
+msgstr ""
+
+msgid "AsanaService|User Personal Access Token. User must have access to task, all comments will be attributed to this user."
+msgstr ""
+
+msgid "Ascending"
+msgstr ""
+
+msgid "Ask your group maintainer to set up a group Runner."
+msgstr ""
+
+msgid "Assertion consumer service URL"
+msgstr ""
+
+msgid "Assets"
+msgstr ""
+
+msgid "Assets:"
+msgstr ""
+
+msgid "Assign"
+msgstr ""
+
+msgid "Assign Iteration"
+msgstr ""
+
+msgid "Assign custom color like #FF0000"
+msgstr ""
+
+msgid "Assign epic"
+msgstr ""
+
+msgid "Assign labels"
+msgstr ""
+
+msgid "Assign milestone"
+msgstr ""
+
+msgid "Assign some issues to this milestone."
+msgstr ""
+
+msgid "Assign to"
+msgstr ""
+
+msgid "Assign to commenting user"
+msgstr ""
+
+msgid "Assign yourself to these issues"
+msgstr ""
+
+msgid "Assign yourself to this issue"
+msgstr ""
+
+msgid "Assigned %{assignee_users_sentence}."
+msgstr ""
+
+msgid "Assigned Issues"
+msgstr ""
+
+msgid "Assigned Merge Requests"
+msgstr ""
+
+msgid "Assigned to %{assignee_name}"
+msgstr ""
+
+msgid "Assigned to me"
+msgstr ""
+
+msgid "Assignee"
+msgid_plural "%d Assignees"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Assignee has no permissions"
+msgstr ""
+
+msgid "Assignee lists not available with your current license"
+msgstr ""
+
+msgid "Assignee lists show all issues assigned to the selected user."
+msgstr ""
+
+msgid "Assignee(s)"
+msgstr ""
+
+msgid "Assignees"
+msgstr ""
+
+msgid "Assigns %{assignee_users_sentence}."
+msgstr ""
+
+msgid "At least one approval from a code owner is required to change files matching the respective CODEOWNER rules."
+msgstr ""
+
+msgid "At least one logging option is required to be enabled"
+msgstr ""
+
+msgid "At least one of group_id or project_id must be specified"
+msgstr ""
+
+msgid "At risk"
+msgstr ""
+
+msgid "Attach a file"
+msgstr ""
+
+msgid "Attach a file by drag &amp; drop or %{upload_link}"
+msgstr ""
+
+msgid "Attaching a file"
+msgid_plural "Attaching %d files"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Attaching the file failed."
+msgstr ""
+
+msgid "Audit Events"
+msgstr ""
+
+msgid "Audit Events is a way to keep track of important events that happened in GitLab."
+msgstr ""
+
+msgid "Audit Log"
+msgstr ""
+
+msgid "AuditEvents|(removed)"
+msgstr ""
+
+msgid "AuditEvents|Action"
+msgstr ""
+
+msgid "AuditEvents|At"
+msgstr ""
+
+msgid "AuditEvents|Target"
+msgstr ""
+
+msgid "AuditLogs|(removed)"
+msgstr ""
+
+msgid "AuditLogs|Action"
+msgstr ""
+
+msgid "AuditLogs|Author"
+msgstr ""
+
+msgid "AuditLogs|Date"
+msgstr ""
+
+msgid "AuditLogs|Failed to find %{type}. Please search for another %{type}."
+msgstr ""
+
+msgid "AuditLogs|Failed to find %{type}. Please try again."
+msgstr ""
+
+msgid "AuditLogs|Group Events"
+msgstr ""
+
+msgid "AuditLogs|IP Address"
+msgstr ""
+
+msgid "AuditLogs|Member Events"
+msgstr ""
+
+msgid "AuditLogs|No matching %{type} found."
+msgstr ""
+
+msgid "AuditLogs|Object"
+msgstr ""
+
+msgid "AuditLogs|Project Events"
+msgstr ""
+
+msgid "AuditLogs|Target"
+msgstr ""
+
+msgid "AuditLogs|User Events"
+msgstr ""
+
+msgid "Aug"
+msgstr ""
+
+msgid "August"
+msgstr ""
+
+msgid "Authenticate"
+msgstr ""
+
+msgid "Authenticate with GitHub"
+msgstr ""
+
+msgid "Authenticating"
+msgstr ""
+
+msgid "Authentication Failure"
+msgstr ""
+
+msgid "Authentication Log"
+msgstr ""
+
+msgid "Authentication failed: %{error_message}"
+msgstr ""
+
+msgid "Authentication log"
+msgstr ""
+
+msgid "Authentication method"
+msgstr ""
+
+msgid "Authentication method updated"
+msgstr ""
+
+msgid "Authentication via U2F device failed."
+msgstr ""
+
+msgid "Author"
+msgstr ""
+
+msgid "Authored %{timeago} by %{author}"
+msgstr ""
+
+msgid "Authorization code:"
+msgstr ""
+
+msgid "Authorization key"
+msgstr ""
+
+msgid "Authorization was granted by entering your username and password in the application."
+msgstr ""
+
+msgid "Authorize"
+msgstr ""
+
+msgid "Authorize %{link_to_client} to use your account?"
+msgstr ""
+
+msgid "Authorize external services to send alerts to GitLab"
+msgstr ""
+
+msgid "Authorized %{new_chat_name}"
+msgstr ""
+
+msgid "Authorized At"
+msgstr ""
+
+msgid "Authorized applications (%{size})"
+msgstr ""
+
+msgid "Authors: %{authors}"
+msgstr ""
+
+msgid "Auto DevOps"
+msgstr ""
+
+msgid "Auto DevOps enabled"
+msgstr ""
+
+msgid "Auto DevOps, runners and job artifacts"
+msgstr ""
+
+msgid "Auto stop successfully canceled."
+msgstr ""
+
+msgid "Auto-cancel redundant, pending pipelines"
+msgstr ""
+
+msgid "Auto-close referenced issues on default branch"
+msgstr ""
+
+msgid "AutoDevOps|Auto DevOps"
+msgstr ""
+
+msgid "AutoDevOps|Auto DevOps can automatically build, test, and deploy applications based on predefined continuous integration and delivery configuration. %{auto_devops_start}Learn more about Auto DevOps%{auto_devops_end} or use our %{quickstart_start}quick start guide%{quickstart_end} to get started right away."
+msgstr ""
+
+msgid "AutoDevOps|Auto DevOps documentation"
+msgstr ""
+
+msgid "AutoDevOps|Dismiss Auto DevOps box"
+msgstr ""
+
+msgid "AutoDevOps|Enable in settings"
+msgstr ""
+
+msgid "AutoDevOps|It will automatically build, test, and deploy your application based on a predefined CI/CD configuration."
+msgstr ""
+
+msgid "AutoDevOps|Learn more in the %{link_to_documentation}"
+msgstr ""
+
+msgid "AutoDevOps|The Auto DevOps pipeline has been enabled and will be used if no alternative CI configuration file is found. %{more_information_link}"
+msgstr ""
+
+msgid "Autocomplete"
+msgstr ""
+
+msgid "Autocomplete description"
+msgstr ""
+
+msgid "Autocomplete hint"
+msgstr ""
+
+msgid "Autocomplete usage hint"
+msgstr ""
+
+msgid "Automatic certificate management using %{lets_encrypt_link_start}Let's Encrypt%{lets_encrypt_link_end}"
+msgstr ""
+
+msgid "Automatic certificate management using Let's Encrypt"
+msgstr ""
+
+msgid "Automatically create merge requests for vulnerabilities that have fixes available."
+msgstr ""
+
+msgid "Automatically marked as default internal user"
+msgstr ""
+
+msgid "Automatically resolved"
+msgstr ""
+
+msgid "Automatically update this project's branches and tags from the upstream repository every hour."
+msgstr ""
+
+msgid "Autosave|Note"
+msgstr ""
+
+msgid "Available"
+msgstr ""
+
+msgid "Available for dependency and container scanning"
+msgstr ""
+
+msgid "Available group Runners: %{runners}"
+msgstr ""
+
+msgid "Available shared Runners:"
+msgstr ""
+
+msgid "Available specific runners"
+msgstr ""
+
+msgid "Avatar for %{assigneeName}"
+msgstr ""
+
+msgid "Avatar for %{name}"
+msgstr ""
+
+msgid "Avatar will be removed. Are you sure?"
+msgstr ""
+
+msgid "Average per day: %{average}"
+msgstr ""
+
+msgid "Back to page %{number}"
+msgstr ""
+
+msgid "Background Color"
+msgstr ""
+
+msgid "Background Jobs"
+msgstr ""
+
+msgid "Background color"
+msgstr ""
+
+msgid "Badges"
+msgstr ""
+
+msgid "Badges|A new badge was added."
+msgstr ""
+
+msgid "Badges|Add badge"
+msgstr ""
+
+msgid "Badges|Adding the badge failed, please check the entered URLs and try again."
+msgstr ""
+
+msgid "Badges|Badge image URL"
+msgstr ""
+
+msgid "Badges|Badge image preview"
+msgstr ""
+
+msgid "Badges|Delete badge"
+msgstr ""
+
+msgid "Badges|Delete badge?"
+msgstr ""
+
+msgid "Badges|Deleting the badge failed, please try again."
+msgstr ""
+
+msgid "Badges|Group Badge"
+msgstr ""
+
+msgid "Badges|Link"
+msgstr ""
+
+msgid "Badges|Name"
+msgstr ""
+
+msgid "Badges|No badge image"
+msgstr ""
+
+msgid "Badges|No image to preview"
+msgstr ""
+
+msgid "Badges|Please fill in a valid URL"
+msgstr ""
+
+msgid "Badges|Project Badge"
+msgstr ""
+
+msgid "Badges|Reload badge image"
+msgstr ""
+
+msgid "Badges|Save changes"
+msgstr ""
+
+msgid "Badges|Saving the badge failed, please check the entered URLs and try again."
+msgstr ""
+
+msgid "Badges|The %{docsLinkStart}variables%{docsLinkEnd} GitLab supports: %{placeholders}"
+msgstr ""
+
+msgid "Badges|The badge was deleted."
+msgstr ""
+
+msgid "Badges|The badge was saved."
+msgstr ""
+
+msgid "Badges|This group has no badges"
+msgstr ""
+
+msgid "Badges|This project has no badges"
+msgstr ""
+
+msgid "Badges|You are going to delete this badge. Deleted badges <strong>cannot</strong> be restored."
+msgstr ""
+
+msgid "Badges|Your badges"
+msgstr ""
+
+msgid "Badges|e.g. %{exampleUrl}"
+msgstr ""
+
+msgid "Balsamiq file could not be loaded."
+msgstr ""
+
+msgid "BambooService|A continuous integration and build server"
+msgstr ""
+
+msgid "BambooService|A user with API access, if applicable"
+msgstr ""
+
+msgid "BambooService|Atlassian Bamboo CI"
+msgstr ""
+
+msgid "BambooService|Bamboo build plan key like KEY"
+msgstr ""
+
+msgid "BambooService|Bamboo root URL like https://bamboo.example.com"
+msgstr ""
+
+msgid "BambooService|You must set up automatic revision labeling and a repository trigger in Bamboo."
+msgstr ""
+
+msgid "BatchComments|Delete all pending comments"
+msgstr ""
+
+msgid "BatchComments|Discard review?"
+msgstr ""
+
+msgid "BatchComments|You're about to discard your review which will delete all of your pending comments. The deleted comments %{strong_start}cannot%{strong_end} be restored."
+msgstr ""
+
+msgid "Be careful. Changing the project's namespace can have unintended side effects."
+msgstr ""
+
+msgid "Be careful. Renaming a project's repository can have unintended side effects."
+msgstr ""
+
+msgid "Begin with the selected commit"
+msgstr ""
+
+msgid "Below are examples of regex for existing tools:"
+msgstr ""
+
+msgid "Below are the fingerprints for the current instance SSH host keys."
+msgstr ""
+
+msgid "Below you will find all the groups that are public."
+msgstr ""
+
+msgid "Beta"
+msgstr ""
+
+msgid "Bi-weekly code coverage"
+msgstr ""
+
+msgid "Billing"
+msgstr ""
+
+msgid "BillingPlans|%{group_name} is currently using the %{plan_name} plan."
+msgstr ""
+
+msgid "BillingPlans|@%{user_name} you are currently using the %{plan_name} plan."
+msgstr ""
+
+msgid "BillingPlans|Congratulations, your new trial is activated"
+msgstr ""
+
+msgid "BillingPlans|If you would like to downgrade your plan please contact %{support_link_start}Customer Support%{support_link_end}."
+msgstr ""
+
+msgid "BillingPlans|Learn more about each plan by reading our %{faq_link}, or start a free 30-day trial of GitLab.com Gold."
+msgstr ""
+
+msgid "BillingPlans|Learn more about each plan by visiting our %{pricing_page_link}."
+msgstr ""
+
+msgid "BillingPlans|Manage plan"
+msgstr ""
+
+msgid "BillingPlans|Pricing page"
+msgstr ""
+
+msgid "BillingPlans|See all %{plan_name} features"
+msgstr ""
+
+msgid "BillingPlans|This group uses the plan associated with its parent group."
+msgstr ""
+
+msgid "BillingPlans|To manage the plan for this group, visit the billing section of %{parent_billing_page_link}."
+msgstr ""
+
+msgid "BillingPlans|Your GitLab.com %{plan} trial will <strong>expire after %{expiration_date}</strong>. You can retain access to the %{plan} features by upgrading below."
+msgstr ""
+
+msgid "BillingPlans|Your GitLab.com trial expired on %{expiration_date}. You can restore access to the features at any time by upgrading below."
+msgstr ""
+
+msgid "BillingPlans|billed annually at %{price_per_year}"
+msgstr ""
+
+msgid "BillingPlans|frequently asked questions"
+msgstr ""
+
+msgid "BillingPlans|monthly"
+msgstr ""
+
+msgid "BillingPlans|per user"
+msgstr ""
+
+msgid "BillingPlan|Upgrade"
+msgstr ""
+
+msgid "Bitbucket Server Import"
+msgstr ""
+
+msgid "Bitbucket Server import"
+msgstr ""
+
+msgid "Bitbucket import"
+msgstr ""
+
+msgid "Blame"
+msgstr ""
+
+msgid "Blocked"
+msgstr ""
+
+msgid "Blocked issue"
+msgstr ""
+
+msgid "Blocks"
+msgstr ""
+
+msgid "Blog"
+msgstr ""
+
+msgid "Board name"
+msgstr ""
+
+msgid "Board scope"
+msgstr ""
+
+msgid "Board scope affects which issues are displayed for anyone who visits this board"
+msgstr ""
+
+msgid "BoardBlankState|Add default lists"
+msgstr ""
+
+msgid "BoardBlankState|Add the following default lists to your Issue Board with one click:"
+msgstr ""
+
+msgid "BoardBlankState|Nevermind, I'll use my own"
+msgstr ""
+
+msgid "BoardBlankState|Starting out with the default set of lists will get you right on the way to making the most of your board."
+msgstr ""
+
+msgid "Boards"
+msgstr ""
+
+msgid "Boards and Board Lists"
+msgstr ""
+
+msgid "Boards|Collapse"
+msgstr ""
+
+msgid "Boards|Edit board"
+msgstr ""
+
+msgid "Boards|Expand"
+msgstr ""
+
+msgid "Boards|View scope"
+msgstr ""
+
+msgid "Branch"
+msgstr ""
+
+msgid "Branch %{branchName} was not found in this project's repository."
+msgstr ""
+
+msgid "Branch <strong>%{branch_name}</strong> was created. To set up auto deploy, choose a GitLab CI Yaml template and commit your changes. %{link_to_autodeploy_doc}"
+msgstr ""
+
+msgid "Branch has changed"
+msgstr ""
+
+msgid "Branch is already taken"
+msgstr ""
+
+msgid "Branch name"
+msgstr ""
+
+msgid "Branch not loaded - %{branchId}"
+msgstr ""
+
+msgid "Branch prefix"
+msgstr ""
+
+msgid "BranchSwitcherPlaceholder|Search branches"
+msgstr ""
+
+msgid "BranchSwitcherTitle|Switch branch"
+msgstr ""
+
+msgid "Branches"
+msgstr ""
+
+msgid "Branches|Active"
+msgstr ""
+
+msgid "Branches|Active branches"
+msgstr ""
+
+msgid "Branches|All"
+msgstr ""
+
+msgid "Branches|Cant find HEAD commit for this branch"
+msgstr ""
+
+msgid "Branches|Compare"
+msgstr ""
+
+msgid "Branches|Delete all branches that are merged into '%{default_branch}'"
+msgstr ""
+
+msgid "Branches|Delete branch"
+msgstr ""
+
+msgid "Branches|Delete merged branches"
+msgstr ""
+
+msgid "Branches|Delete protected branch"
+msgstr ""
+
+msgid "Branches|Delete protected branch '%{branch_name}'?"
+msgstr ""
+
+msgid "Branches|Deleting the '%{branch_name}' branch cannot be undone. Are you sure?"
+msgstr ""
+
+msgid "Branches|Deleting the merged branches cannot be undone. Are you sure?"
+msgstr ""
+
+msgid "Branches|Filter by branch name"
+msgstr ""
+
+msgid "Branches|Merged into %{default_branch}"
+msgstr ""
+
+msgid "Branches|New branch"
+msgstr ""
+
+msgid "Branches|No branches to show"
+msgstr ""
+
+msgid "Branches|Once you confirm and press %{delete_protected_branch}, it cannot be undone or recovered."
+msgstr ""
+
+msgid "Branches|Only a project maintainer or owner can delete a protected branch"
+msgstr ""
+
+msgid "Branches|Overview"
+msgstr ""
+
+msgid "Branches|Protected branches can be managed in %{project_settings_link}."
+msgstr ""
+
+msgid "Branches|Show active branches"
+msgstr ""
+
+msgid "Branches|Show all branches"
+msgstr ""
+
+msgid "Branches|Show more active branches"
+msgstr ""
+
+msgid "Branches|Show more stale branches"
+msgstr ""
+
+msgid "Branches|Show overview of the branches"
+msgstr ""
+
+msgid "Branches|Show stale branches"
+msgstr ""
+
+msgid "Branches|Sort by"
+msgstr ""
+
+msgid "Branches|Stale"
+msgstr ""
+
+msgid "Branches|Stale branches"
+msgstr ""
+
+msgid "Branches|The branch could not be updated automatically because it has diverged from its upstream counterpart."
+msgstr ""
+
+msgid "Branches|The default branch cannot be deleted"
+msgstr ""
+
+msgid "Branches|This branch hasn’t been merged into %{default_branch}."
+msgstr ""
+
+msgid "Branches|To avoid data loss, consider merging this branch before deleting it."
+msgstr ""
+
+msgid "Branches|To confirm, type %{branch_name_confirmation}:"
+msgstr ""
+
+msgid "Branches|To discard the local changes and overwrite the branch with the upstream version, delete it here and choose 'Update Now' above."
+msgstr ""
+
+msgid "Branches|You’re about to permanently delete the protected branch %{branch_name}."
+msgstr ""
+
+msgid "Branches|diverged from upstream"
+msgstr ""
+
+msgid "Branches|merged"
+msgstr ""
+
+msgid "Branches|project settings"
+msgstr ""
+
+msgid "Branches|protected"
+msgstr ""
+
+msgid "Broadcast Message was successfully created."
+msgstr ""
+
+msgid "Broadcast Message was successfully updated."
+msgstr ""
+
+msgid "Broadcast Messages"
+msgstr ""
+
+msgid "Browse Directory"
+msgstr ""
+
+msgid "Browse File"
+msgstr ""
+
+msgid "Browse Files"
+msgstr ""
+
+msgid "Browse artifacts"
+msgstr ""
+
+msgid "Browse files"
+msgstr ""
+
+msgid "BuildArtifacts|An error occurred while fetching the artifacts"
+msgstr ""
+
+msgid "BuildArtifacts|Loading artifacts"
+msgstr ""
+
+msgid "Built-in"
+msgstr ""
+
+msgid "Bulk request concurrency"
+msgstr ""
+
+msgid "Burndown chart"
+msgstr ""
+
+msgid "BurndownChartLabel|Open issue weight"
+msgstr ""
+
+msgid "BurndownChartLabel|Open issues"
+msgstr ""
+
+msgid "Burnup chart"
+msgstr ""
+
+msgid "Business"
+msgstr ""
+
+msgid "Business metrics (Custom)"
+msgstr ""
+
+msgid "Buy License"
+msgstr ""
+
+msgid "Buy more Pipeline minutes"
+msgstr ""
+
+msgid "By %{user_name}"
+msgstr ""
+
+msgid "By URL"
+msgstr ""
+
+msgid "By default GitLab sends emails in HTML and plain text formats so mail clients can choose what format to use. Disable this option if you only want to send emails in plain text format."
+msgstr ""
+
+msgid "By default, all projects and groups will use the global notifications setting."
+msgstr ""
+
+msgid "ByAuthor|by"
+msgstr ""
+
+msgid "CHANGELOG"
+msgstr ""
+
+msgid "CI / CD"
+msgstr ""
+
+msgid "CI / CD Analytics"
+msgstr ""
+
+msgid "CI / CD Settings"
+msgstr ""
+
+msgid "CI Lint"
+msgstr ""
+
+msgid "CI settings"
+msgstr ""
+
+msgid "CI variables"
+msgstr ""
+
+msgid "CI will run using the credentials assigned above."
+msgstr ""
+
+msgid "CI/CD"
+msgstr ""
+
+msgid "CI/CD configuration"
+msgstr ""
+
+msgid "CI/CD for external repo"
+msgstr ""
+
+msgid "CI/CD settings"
+msgstr ""
+
+msgid "CICD|Add a %{kubernetes_cluster_link_start}Kubernetes cluster integration%{link_end} with a domain or create an AUTO_DEVOPS_PLATFORM_TARGET CI variable."
+msgstr ""
+
+msgid "CICD|Auto DevOps"
+msgstr ""
+
+msgid "CICD|Automatic deployment to staging, manual deployment to production"
+msgstr ""
+
+msgid "CICD|Continuous deployment to production"
+msgstr ""
+
+msgid "CICD|Continuous deployment to production using timed incremental rollout"
+msgstr ""
+
+msgid "CICD|Default to Auto DevOps pipeline"
+msgstr ""
+
+msgid "CICD|Default to Auto DevOps pipeline for all projects"
+msgstr ""
+
+msgid "CICD|Deployment strategy"
+msgstr ""
+
+msgid "CICD|Jobs"
+msgstr ""
+
+msgid "CICD|The Auto DevOps pipeline will run if no alternative CI configuration file is found."
+msgstr ""
+
+msgid "CICD|You must add a %{base_domain_link_start}base domain%{link_end} to your %{kubernetes_cluster_link_start}Kubernetes cluster%{link_end} in order for your deployment strategy to work."
+msgstr ""
+
+msgid "CICD|group enabled"
+msgstr ""
+
+msgid "CICD|instance enabled"
+msgstr ""
+
+msgid "CLOSED"
+msgstr ""
+
+msgid "CLOSED (MOVED)"
+msgstr ""
+
+msgid "CONTRIBUTING"
+msgstr ""
+
+msgid "CPU"
+msgstr ""
+
+msgid "Callback URL"
+msgstr ""
+
+msgid "Can be manually deployed to"
+msgstr ""
+
+msgid "Can override approvers and approvals required per merge request"
+msgstr ""
+
+msgid "Can't apply as this line has changed or the suggestion already matches its content."
+msgstr ""
+
+msgid "Can't create snippet: %{err}"
+msgstr ""
+
+msgid "Can't edit as source branch was deleted"
+msgstr ""
+
+msgid "Can't find HEAD commit for this branch"
+msgstr ""
+
+msgid "Can't find variable: ZiteReader"
+msgstr ""
+
+msgid "Can't load mermaid module: %{err}"
+msgstr ""
+
+msgid "Can't remove group members without group managed account"
+msgstr ""
+
+msgid "Can't scan the code?"
+msgstr ""
+
+msgid "Can't update snippet: %{err}"
+msgstr ""
+
+msgid "Canary"
+msgstr ""
+
+msgid "Canary Deployments is a popular CI strategy, where a small portion of the fleet is updated to the new version of your application."
+msgstr ""
+
+msgid "Cancel"
+msgstr ""
+
+msgid "Cancel running"
+msgstr ""
+
+msgid "Cancel this job"
+msgstr ""
+
+msgid "Canceled deployment to"
+msgstr ""
+
+msgid "Cancelling Preview"
+msgstr ""
+
+msgid "Cannot be merged automatically"
+msgstr ""
+
+msgid "Cannot create the abuse report. The user has been deleted."
+msgstr ""
+
+msgid "Cannot create the abuse report. This user has been blocked."
+msgstr ""
+
+msgid "Cannot have multiple Jira imports running at the same time"
+msgstr ""
+
+msgid "Cannot import because issues are not available in this project."
+msgstr ""
+
+msgid "Cannot make epic confidential if it contains not-confidential issues"
+msgstr ""
+
+msgid "Cannot make epic confidential if it contains not-confidential sub-epics"
+msgstr ""
+
+msgid "Cannot merge"
+msgstr ""
+
+msgid "Cannot modify managed Kubernetes cluster"
+msgstr ""
+
+msgid "Cannot modify provider during creation"
+msgstr ""
+
+msgid "Cannot promote issue because it does not belong to a group."
+msgstr ""
+
+msgid "Cannot promote issue due to insufficient permissions."
+msgstr ""
+
+msgid "Cannot refer to a group %{timebox_type} by an internal id!"
+msgstr ""
+
+msgid "Cannot set confidential epic for not-confidential issue"
+msgstr ""
+
+msgid "Cannot show preview. For previews on sketch files, they must have the file format introduced by Sketch version 43 and above."
+msgstr ""
+
+msgid "Cannot skip two factor authentication setup"
+msgstr ""
+
+msgid "Capacity threshold"
+msgstr ""
+
+msgid "Certain user content will be moved to a system-wide \"Ghost User\" in order to maintain content for posterity. For further information, please refer to the %{link_start}user account deletion documentation.%{link_end}"
+msgstr ""
+
+msgid "Certificate"
+msgstr ""
+
+msgid "Certificate (PEM)"
+msgstr ""
+
+msgid "Certificate Issuer"
+msgstr ""
+
+msgid "Certificate Subject"
+msgstr ""
+
+msgid "Change assignee"
+msgstr ""
+
+msgid "Change assignee(s)"
+msgstr ""
+
+msgid "Change assignee(s)."
+msgstr ""
+
+msgid "Change branches"
+msgstr ""
+
+msgid "Change label"
+msgstr ""
+
+msgid "Change milestone"
+msgstr ""
+
+msgid "Change path"
+msgstr ""
+
+msgid "Change permissions"
+msgstr ""
+
+msgid "Change status"
+msgstr ""
+
+msgid "Change subscription"
+msgstr ""
+
+msgid "Change template"
+msgstr ""
+
+msgid "Change this value to influence how frequently the GitLab UI polls for updates."
+msgstr ""
+
+msgid "Change title"
+msgstr ""
+
+msgid "Change your password"
+msgstr ""
+
+msgid "Change your password or recover your current one"
+msgstr ""
+
+msgid "ChangeTypeActionLabel|Pick into branch"
+msgstr ""
+
+msgid "ChangeTypeActionLabel|Revert in branch"
+msgstr ""
+
+msgid "ChangeTypeAction|Cherry-pick"
+msgstr ""
+
+msgid "ChangeTypeAction|Revert"
+msgstr ""
+
+msgid "ChangeTypeAction|This will create a new commit in order to revert the existing changes."
+msgstr ""
+
+msgid "Changed assignee(s)."
+msgstr ""
+
+msgid "Changed the title to \"%{title_param}\"."
+msgstr ""
+
+msgid "Changes"
+msgstr ""
+
+msgid "Changes are shown as if the <b>source</b> revision was being merged into the <b>target</b> revision."
+msgstr ""
+
+msgid "Changes are still tracked. Useful for cluster/index migrations."
+msgstr ""
+
+msgid "Changes suppressed. Click to show."
+msgstr ""
+
+msgid "Changes the title to \"%{title_param}\"."
+msgstr ""
+
+msgid "Changes won't take place until the index is %{link_start}recreated%{link_end}."
+msgstr ""
+
+msgid "Changing a Release tag is only supported via Releases API. %{linkStart}More information%{linkEnd}"
+msgstr ""
+
+msgid "Changing group path can have unintended side effects."
+msgstr ""
+
+msgid "Channel handle (e.g. town-square)"
+msgstr ""
+
+msgid "Charts"
+msgstr ""
+
+msgid "Charts can't be displayed as the request for data has timed out. %{documentationLink}"
+msgstr ""
+
+msgid "Chat"
+msgstr ""
+
+msgid "ChatMessage|%{project_link}: Pipeline %{pipeline_link} of %{ref_type} %{ref_link} by %{user_combined_name} %{humanized_status} in %{duration}"
+msgstr ""
+
+msgid "ChatMessage|Branch"
+msgstr ""
+
+msgid "ChatMessage|Commit"
+msgstr ""
+
+msgid "ChatMessage|Failed job"
+msgstr ""
+
+msgid "ChatMessage|Failed stage"
+msgstr ""
+
+msgid "ChatMessage|Invalid CI config YAML file"
+msgstr ""
+
+msgid "ChatMessage|Pipeline #%{pipeline_id} %{humanized_status} in %{duration}"
+msgstr ""
+
+msgid "ChatMessage|Pipeline %{pipeline_link} of %{ref_type} %{ref_link} by %{user_combined_name} %{humanized_status}"
+msgstr ""
+
+msgid "ChatMessage|Tag"
+msgstr ""
+
+msgid "ChatMessage|and [%{count} more](%{pipeline_failed_jobs_url})"
+msgstr ""
+
+msgid "ChatMessage|has failed"
+msgstr ""
+
+msgid "ChatMessage|has passed"
+msgstr ""
+
+msgid "ChatMessage|has passed with warnings"
+msgstr ""
+
+msgid "ChatMessage|in %{duration}"
+msgstr ""
+
+msgid "ChatMessage|in %{project_link}"
+msgstr ""
+
+msgid "Check again"
+msgstr ""
+
+msgid "Check feature availability on namespace plan"
+msgstr ""
+
+msgid "Check the %{docs_link_start}documentation%{docs_link_end}."
+msgstr ""
+
+msgid "Check your .gitlab-ci.yml"
+msgstr ""
+
+msgid "Check your Docker images for known vulnerabilities."
+msgstr ""
+
+msgid "Checking %{text} availability…"
+msgstr ""
+
+msgid "Checking approval status"
+msgstr ""
+
+msgid "Checking branch availability..."
+msgstr ""
+
+msgid "Checking group path availability..."
+msgstr ""
+
+msgid "Checking username availability..."
+msgstr ""
+
+msgid "Checkout"
+msgstr ""
+
+msgid "Checkout|$%{selectedPlanPrice} per user per year"
+msgstr ""
+
+msgid "Checkout|%{cardType} ending in %{lastFourDigits}"
+msgstr ""
+
+msgid "Checkout|%{name}'s GitLab subscription"
+msgstr ""
+
+msgid "Checkout|%{selectedPlanText} plan"
+msgstr ""
+
+msgid "Checkout|%{startDate} - %{endDate}"
+msgstr ""
+
+msgid "Checkout|(x%{numberOfUsers})"
+msgstr ""
+
+msgid "Checkout|Billing address"
+msgstr ""
+
+msgid "Checkout|Checkout"
+msgstr ""
+
+msgid "Checkout|City"
+msgstr ""
+
+msgid "Checkout|Confirm purchase"
+msgstr ""
+
+msgid "Checkout|Confirming..."
+msgstr ""
+
+msgid "Checkout|Continue to billing"
+msgstr ""
+
+msgid "Checkout|Continue to payment"
+msgstr ""
+
+msgid "Checkout|Country"
+msgstr ""
+
+msgid "Checkout|Create a new group"
+msgstr ""
+
+msgid "Checkout|Credit card form failed to load. Please try again."
+msgstr ""
+
+msgid "Checkout|Credit card form failed to load: %{message}"
+msgstr ""
+
+msgid "Checkout|Edit"
+msgstr ""
+
+msgid "Checkout|Exp %{expirationMonth}/%{expirationYear}"
+msgstr ""
+
+msgid "Checkout|Failed to confirm your order! Please try again."
+msgstr ""
+
+msgid "Checkout|Failed to confirm your order: %{message}. Please try again."
+msgstr ""
+
+msgid "Checkout|Failed to load countries. Please try again."
+msgstr ""
+
+msgid "Checkout|Failed to load states. Please try again."
+msgstr ""
+
+msgid "Checkout|Failed to register credit card. Please try again."
+msgstr ""
+
+msgid "Checkout|GitLab group"
+msgstr ""
+
+msgid "Checkout|GitLab plan"
+msgstr ""
+
+msgid "Checkout|Group"
+msgstr ""
+
+msgid "Checkout|Name of company or organization using GitLab"
+msgstr ""
+
+msgid "Checkout|Need more users? Purchase GitLab for your %{company}."
+msgstr ""
+
+msgid "Checkout|Number of users"
+msgstr ""
+
+msgid "Checkout|Payment method"
+msgstr ""
+
+msgid "Checkout|Please select a country"
+msgstr ""
+
+msgid "Checkout|Please select a state"
+msgstr ""
+
+msgid "Checkout|Select"
+msgstr ""
+
+msgid "Checkout|State"
+msgstr ""
+
+msgid "Checkout|Street address"
+msgstr ""
+
+msgid "Checkout|Submitting the credit card form failed with code %{errorCode}: %{errorMessage}"
+msgstr ""
+
+msgid "Checkout|Subscription details"
+msgstr ""
+
+msgid "Checkout|Subtotal"
+msgstr ""
+
+msgid "Checkout|Tax"
+msgstr ""
+
+msgid "Checkout|Total"
+msgstr ""
+
+msgid "Checkout|Users"
+msgstr ""
+
+msgid "Checkout|You'll create your new group after checkout"
+msgstr ""
+
+msgid "Checkout|Your organization"
+msgstr ""
+
+msgid "Checkout|Your subscription will be applied to this group"
+msgstr ""
+
+msgid "Checkout|Zip code"
+msgstr ""
+
+msgid "Checkout|company or team"
+msgstr ""
+
+msgid "Cherry-pick this commit"
+msgstr ""
+
+msgid "Cherry-pick this merge request"
+msgstr ""
+
+msgid "Child"
+msgstr ""
+
+msgid "Child epic does not exist."
+msgstr ""
+
+msgid "Child epic doesn't exist."
+msgstr ""
+
+msgid "Choose <strong>Create archive</strong> and wait for archiving to complete."
+msgstr ""
+
+msgid "Choose <strong>Next</strong> at the bottom of the page."
+msgstr ""
+
+msgid "Choose a branch/tag (e.g. %{master}) or enter a commit (e.g. %{sha}) to see what's changed or to create a merge request."
+msgstr ""
+
+msgid "Choose a file"
+msgstr ""
+
+msgid "Choose a group"
+msgstr ""
+
+msgid "Choose a role permission"
+msgstr ""
+
+msgid "Choose a template"
+msgstr ""
+
+msgid "Choose a template..."
+msgstr ""
+
+msgid "Choose a type..."
+msgstr ""
+
+msgid "Choose any color."
+msgstr ""
+
+msgid "Choose between <code>clone</code> or <code>fetch</code> to get the recent application code"
+msgstr ""
+
+msgid "Choose file…"
+msgstr ""
+
+msgid "Choose the top-level group for your repository imports."
+msgstr ""
+
+msgid "Choose visibility level, enable/disable project features (issues, repository, wiki, snippets) and set permissions."
+msgstr ""
+
+msgid "Choose what content you want to see on a group’s overview page"
+msgstr ""
+
+msgid "Choose which repositories you want to connect and run CI/CD pipelines."
+msgstr ""
+
+msgid "Choose your framework"
+msgstr ""
+
+msgid "CiStatusLabel|canceled"
+msgstr ""
+
+msgid "CiStatusLabel|created"
+msgstr ""
+
+msgid "CiStatusLabel|delayed"
+msgstr ""
+
+msgid "CiStatusLabel|failed"
+msgstr ""
+
+msgid "CiStatusLabel|manual action"
+msgstr ""
+
+msgid "CiStatusLabel|passed"
+msgstr ""
+
+msgid "CiStatusLabel|passed with warnings"
+msgstr ""
+
+msgid "CiStatusLabel|pending"
+msgstr ""
+
+msgid "CiStatusLabel|preparing"
+msgstr ""
+
+msgid "CiStatusLabel|skipped"
+msgstr ""
+
+msgid "CiStatusLabel|waiting for delayed job"
+msgstr ""
+
+msgid "CiStatusLabel|waiting for manual action"
+msgstr ""
+
+msgid "CiStatusLabel|waiting for resource"
+msgstr ""
+
+msgid "CiStatusText|blocked"
+msgstr ""
+
+msgid "CiStatusText|canceled"
+msgstr ""
+
+msgid "CiStatusText|created"
+msgstr ""
+
+msgid "CiStatusText|delayed"
+msgstr ""
+
+msgid "CiStatusText|failed"
+msgstr ""
+
+msgid "CiStatusText|manual"
+msgstr ""
+
+msgid "CiStatusText|passed"
+msgstr ""
+
+msgid "CiStatusText|pending"
+msgstr ""
+
+msgid "CiStatusText|preparing"
+msgstr ""
+
+msgid "CiStatusText|skipped"
+msgstr ""
+
+msgid "CiStatusText|waiting"
+msgstr ""
+
+msgid "CiStatus|running"
+msgstr ""
+
+msgid "CiVariables|Cannot use Masked Variable with current value"
+msgstr ""
+
+msgid "CiVariables|Environments"
+msgstr ""
+
+msgid "CiVariables|Input variable key"
+msgstr ""
+
+msgid "CiVariables|Input variable value"
+msgstr ""
+
+msgid "CiVariables|Key"
+msgstr ""
+
+msgid "CiVariables|Masked"
+msgstr ""
+
+msgid "CiVariables|Protected"
+msgstr ""
+
+msgid "CiVariables|Remove variable row"
+msgstr ""
+
+msgid "CiVariables|Scope"
+msgstr ""
+
+msgid "CiVariables|Specify variable values to be used in this run. The values specified in %{linkStart}CI/CD settings%{linkEnd} will be used as default"
+msgstr ""
+
+msgid "CiVariables|State"
+msgstr ""
+
+msgid "CiVariables|Type"
+msgstr ""
+
+msgid "CiVariables|Value"
+msgstr ""
+
+msgid "CiVariables|Variables"
+msgstr ""
+
+msgid "CiVariable|* (All environments)"
+msgstr ""
+
+msgid "CiVariable|All environments"
+msgstr ""
+
+msgid "CiVariable|Create wildcard"
+msgstr ""
+
+msgid "CiVariable|Error occurred while saving variables"
+msgstr ""
+
+msgid "CiVariable|Masked"
+msgstr ""
+
+msgid "CiVariable|New environment"
+msgstr ""
+
+msgid "CiVariable|Protected"
+msgstr ""
+
+msgid "CiVariable|Search environments"
+msgstr ""
+
+msgid "CiVariable|Toggle masked"
+msgstr ""
+
+msgid "CiVariable|Toggle protected"
+msgstr ""
+
+msgid "CiVariable|Validation failed"
+msgstr ""
+
+msgid "Class"
+msgstr ""
+
+msgid "Classification Label (optional)"
+msgstr ""
+
+msgid "ClassificationLabelUnavailable|is unavailable: %{reason}"
+msgstr ""
+
+msgid "Clear"
+msgstr ""
+
+msgid "Clear chart filters"
+msgstr ""
+
+msgid "Clear due date"
+msgstr ""
+
+msgid "Clear input"
+msgstr ""
+
+msgid "Clear recent searches"
+msgstr ""
+
+msgid "Clear search"
+msgstr ""
+
+msgid "Clear search input"
+msgstr ""
+
+msgid "Clear start date"
+msgstr ""
+
+msgid "Clear templates search input"
+msgstr ""
+
+msgid "Clear weight"
+msgstr ""
+
+msgid "Cleared weight."
+msgstr ""
+
+msgid "Clears weight."
+msgstr ""
+
+msgid "Click any <strong>project name</strong> in the project list below to navigate to the project milestone."
+msgstr ""
+
+msgid "Click the <strong>Download</strong> button and wait for downloading to complete."
+msgstr ""
+
+msgid "Click the <strong>Promote</strong> button in the top right corner to promote it to a group milestone."
+msgstr ""
+
+msgid "Click the <strong>Select none</strong> button on the right, since we only need \"Google Code Project Hosting\"."
+msgstr ""
+
+msgid "Click the button below to begin the install process by navigating to the Kubernetes page"
+msgstr ""
+
+msgid "Click to expand it."
+msgstr ""
+
+msgid "Click to expand text"
+msgstr ""
+
+msgid "Client authentication certificate"
+msgstr ""
+
+msgid "Client authentication key"
+msgstr ""
+
+msgid "Client authentication key password"
+msgstr ""
+
+msgid "Clients"
+msgstr ""
+
+msgid "Clone"
+msgstr ""
+
+msgid "Clone repository"
+msgstr ""
+
+msgid "Clone with %{http_label}"
+msgstr ""
+
+msgid "Clone with %{protocol}"
+msgstr ""
+
+msgid "Clone with KRB5"
+msgstr ""
+
+msgid "Clone with SSH"
+msgstr ""
+
+msgid "Close"
+msgstr ""
+
+msgid "Close %{display_issuable_type}"
+msgstr ""
+
+msgid "Close %{tabname}"
+msgstr ""
+
+msgid "Close epic"
+msgstr ""
+
+msgid "Close milestone"
+msgstr ""
+
+msgid "Close sidebar"
+msgstr ""
+
+msgid "Close this %{quick_action_target}"
+msgstr ""
+
+msgid "Closed"
+msgstr ""
+
+msgid "Closed issues"
+msgstr ""
+
+msgid "Closed this %{quick_action_target}."
+msgstr ""
+
+msgid "Closed: %{closedIssuesCount}"
+msgstr ""
+
+msgid "Closes this %{quick_action_target}."
+msgstr ""
+
+msgid "Cluster"
+msgstr ""
+
+msgid "Cluster Health"
+msgstr ""
+
+msgid "Cluster cache cleared."
+msgstr ""
+
+msgid "Cluster does not exist"
+msgstr ""
+
+msgid "Cluster is required for Stages::ClusterEndpointInserter"
+msgstr ""
+
+msgid "Cluster level"
+msgstr ""
+
+msgid "Cluster type must be specificed for Stages::ClusterEndpointInserter"
+msgstr ""
+
+msgid "ClusterIntegration| %{custom_domain_start}More information%{custom_domain_end}."
+msgstr ""
+
+msgid "ClusterIntegration| This will permanently delete the following resources: <ul> <li>All installed applications and related resources</li> <li>The <code>gitlab-managed-apps</code> namespace</li> <li>Any project namespaces</li> <li><code>clusterroles</code></li> <li><code>clusterrolebindings</code></li> </ul>"
+msgstr ""
+
+msgid "ClusterIntegration| can be used instead of a custom domain."
+msgstr ""
+
+msgid "ClusterIntegration| is the default environment scope for this cluster. This means that all jobs, regardless of their environment, will use this cluster. %{environment_scope_start}More information%{environment_scope_end}"
+msgstr ""
+
+msgid "ClusterIntegration|%{appList} was successfully installed on your Kubernetes cluster"
+msgstr ""
+
+msgid "ClusterIntegration|%{external_ip}.nip.io"
+msgstr ""
+
+msgid "ClusterIntegration|%{title} uninstalled successfully."
+msgstr ""
+
+msgid "ClusterIntegration|%{title} updated successfully."
+msgstr ""
+
+msgid "ClusterIntegration|A cluster management project can be used to run deployment jobs with Kubernetes <code>cluster-admin</code> privileges."
+msgstr ""
+
+msgid "ClusterIntegration|A service token scoped to %{code}kube-system%{end_code} with %{code}cluster-admin%{end_code} privileges."
+msgstr ""
+
+msgid "ClusterIntegration|API URL"
+msgstr ""
+
+msgid "ClusterIntegration|API URL should be a valid http/https url."
+msgstr ""
+
+msgid "ClusterIntegration|Add Kubernetes cluster"
+msgstr ""
+
+msgid "ClusterIntegration|Add a Kubernetes cluster integration"
+msgstr ""
+
+msgid "ClusterIntegration|Adding a Kubernetes cluster to your group will automatically share the cluster across all your projects. Use review apps, deploy your applications, and easily run your pipelines for all projects using the same cluster."
+msgstr ""
+
+msgid "ClusterIntegration|Adding a Kubernetes cluster will automatically share the cluster across all projects. Use review apps, deploy your applications, and easily run your pipelines for all projects using the same cluster."
+msgstr ""
+
+msgid "ClusterIntegration|Adding an integration to your group will share the cluster across all your projects."
+msgstr ""
+
+msgid "ClusterIntegration|Adding an integration will share the cluster across all projects."
+msgstr ""
+
+msgid "ClusterIntegration|Advanced options on this Kubernetes cluster’s integration"
+msgstr ""
+
+msgid "ClusterIntegration|All data not committed to GitLab will be deleted and cannot be restored."
+msgstr ""
+
+msgid "ClusterIntegration|All data will be deleted and cannot be restored."
+msgstr ""
+
+msgid "ClusterIntegration|Allow GitLab to manage namespace and service accounts for this cluster."
+msgstr ""
+
+msgid "ClusterIntegration|Allow GitLab to manage namespace and service accounts for this cluster. %{startLink}More information%{endLink}"
+msgstr ""
+
+msgid "ClusterIntegration|Alternatively"
+msgstr ""
+
+msgid "ClusterIntegration|Amazon EKS"
+msgstr ""
+
+msgid "ClusterIntegration|An error occurred when trying to contact the Google Cloud API. Please try again later."
+msgstr ""
+
+msgid "ClusterIntegration|An error occurred while trying to fetch project zones: %{error}"
+msgstr ""
+
+msgid "ClusterIntegration|An error occurred while trying to fetch your projects: %{error}"
+msgstr ""
+
+msgid "ClusterIntegration|An error occurred while trying to fetch zone machine types: %{error}"
+msgstr ""
+
+msgid "ClusterIntegration|Any running pipelines will be canceled."
+msgstr ""
+
+msgid "ClusterIntegration|Apply for credit"
+msgstr ""
+
+msgid "ClusterIntegration|Authenticate with AWS"
+msgstr ""
+
+msgid "ClusterIntegration|Authenticate with Amazon Web Services"
+msgstr ""
+
+msgid "ClusterIntegration|Base domain"
+msgstr ""
+
+msgid "ClusterIntegration|Blocking mode"
+msgstr ""
+
+msgid "ClusterIntegration|CA Certificate"
+msgstr ""
+
+msgid "ClusterIntegration|Cert-Manager"
+msgstr ""
+
+msgid "ClusterIntegration|Cert-Manager is a native Kubernetes certificate management controller that helps with issuing certificates. Installing Cert-Manager on your cluster will issue a certificate by %{linkStart}Let's Encrypt%{linkEnd} and ensure that certificates are valid and up-to-date."
+msgstr ""
+
+msgid "ClusterIntegration|Certificate Authority bundle (PEM format)"
+msgstr ""
+
+msgid "ClusterIntegration|Choose the %{startLink}security group %{externalLinkIcon} %{endLink} to apply to the EKS-managed Elastic Network Interfaces that are created in your worker node subnets."
+msgstr ""
+
+msgid "ClusterIntegration|Choose the %{startLink}subnets %{externalLinkIcon} %{endLink} in your VPC where your worker nodes will run."
+msgstr ""
+
+msgid "ClusterIntegration|Choose the worker node %{startLink}instance type %{externalLinkIcon} %{endLink}."
+msgstr ""
+
+msgid "ClusterIntegration|Choose which applications to install on your Kubernetes cluster. Helm Tiller is required to install any of the following applications."
+msgstr ""
+
+msgid "ClusterIntegration|Choose which of your environments will use this cluster."
+msgstr ""
+
+msgid "ClusterIntegration|Clear cluster cache"
+msgstr ""
+
+msgid "ClusterIntegration|Clear the local cache of namespace and service accounts. This is necessary if your integration has become out of sync. The cache is repopulated during the next CI job that requires namespace and service accounts."
+msgstr ""
+
+msgid "ClusterIntegration|Cluster being created"
+msgstr ""
+
+msgid "ClusterIntegration|Cluster management project (alpha)"
+msgstr ""
+
+msgid "ClusterIntegration|Cluster name is required."
+msgstr ""
+
+msgid "ClusterIntegration|Cluster_applications artifact too big. Maximum allowable size: %{human_size}"
+msgstr ""
+
+msgid "ClusterIntegration|Clusters are utilized by selecting the nearest ancestor with a matching environment scope. For example, project clusters will override group clusters."
+msgstr ""
+
+msgid "ClusterIntegration|Copy API URL"
+msgstr ""
+
+msgid "ClusterIntegration|Copy CA Certificate"
+msgstr ""
+
+msgid "ClusterIntegration|Copy Ingress Endpoint"
+msgstr ""
+
+msgid "ClusterIntegration|Copy Jupyter Hostname"
+msgstr ""
+
+msgid "ClusterIntegration|Copy Knative Endpoint"
+msgstr ""
+
+msgid "ClusterIntegration|Copy Kubernetes cluster name"
+msgstr ""
+
+msgid "ClusterIntegration|Could not load IAM roles"
+msgstr ""
+
+msgid "ClusterIntegration|Could not load Key Pairs"
+msgstr ""
+
+msgid "ClusterIntegration|Could not load VPCs for the selected region"
+msgstr ""
+
+msgid "ClusterIntegration|Could not load instance types"
+msgstr ""
+
+msgid "ClusterIntegration|Could not load networks"
+msgstr ""
+
+msgid "ClusterIntegration|Could not load regions from your AWS account"
+msgstr ""
+
+msgid "ClusterIntegration|Could not load security groups for the selected VPC"
+msgstr ""
+
+msgid "ClusterIntegration|Could not load subnets for the selected VPC"
+msgstr ""
+
+msgid "ClusterIntegration|Could not load subnetworks"
+msgstr ""
+
+msgid "ClusterIntegration|Create Kubernetes cluster"
+msgstr ""
+
+msgid "ClusterIntegration|Create a provision role on %{startAwsLink}Amazon Web Services %{externalLinkIcon}%{endLink} using the account and external ID above. %{startMoreInfoLink}More information%{endLink}"
+msgstr ""
+
+msgid "ClusterIntegration|Create cluster on"
+msgstr ""
+
+msgid "ClusterIntegration|Create new cluster"
+msgstr ""
+
+msgid "ClusterIntegration|Create new cluster on EKS"
+msgstr ""
+
+msgid "ClusterIntegration|Create new cluster on GKE"
+msgstr ""
+
+msgid "ClusterIntegration|Creating Kubernetes cluster"
+msgstr ""
+
+msgid "ClusterIntegration|Crossplane"
+msgstr ""
+
+msgid "ClusterIntegration|Crossplane enables declarative provisioning of managed services from your cloud of choice using %{codeStart}kubectl%{codeEnd} or %{linkStart}GitLab Integration%{linkEnd}. Crossplane runs inside your Kubernetes cluster and supports secure connectivity and secrets management between app containers and the cloud services they depend on."
+msgstr ""
+
+msgid "ClusterIntegration|Deletes all GitLab resources attached to this cluster during removal"
+msgstr ""
+
+msgid "ClusterIntegration|Did you know?"
+msgstr ""
+
+msgid "ClusterIntegration|Elastic Kubernetes Service"
+msgstr ""
+
+msgid "ClusterIntegration|Elastic Stack"
+msgstr ""
+
+msgid "ClusterIntegration|Enable Cloud Run for Anthos"
+msgstr ""
+
+msgid "ClusterIntegration|Enable or disable GitLab's connection to your Kubernetes cluster."
+msgstr ""
+
+msgid "ClusterIntegration|Enable this setting if using role-based access control (RBAC)."
+msgstr ""
+
+msgid "ClusterIntegration|Enabled stack"
+msgstr ""
+
+msgid "ClusterIntegration|Enter new Service Token"
+msgstr ""
+
+msgid "ClusterIntegration|Enter the details for your Amazon EKS Kubernetes cluster"
+msgstr ""
+
+msgid "ClusterIntegration|Enter the details for your Kubernetes cluster"
+msgstr ""
+
+msgid "ClusterIntegration|Environment scope"
+msgstr ""
+
+msgid "ClusterIntegration|Every new Google Cloud Platform (GCP) account receives $300 in credit upon %{sign_up_link}. In partnership with Google, GitLab is able to offer an additional $200 for both new and existing GCP accounts to get started with GitLab's Google Kubernetes Engine Integration."
+msgstr ""
+
+msgid "ClusterIntegration|Failed to configure EKS provider: %{message}"
+msgstr ""
+
+msgid "ClusterIntegration|Failed to configure Google Kubernetes Engine Cluster: %{message}"
+msgstr ""
+
+msgid "ClusterIntegration|Failed to fetch CloudFormation stack: %{message}"
+msgstr ""
+
+msgid "ClusterIntegration|Failed to request to Google Cloud Platform: %{message}"
+msgstr ""
+
+msgid "ClusterIntegration|Failed to run Kubeclient: %{message}"
+msgstr ""
+
+msgid "ClusterIntegration|Fetching machine types"
+msgstr ""
+
+msgid "ClusterIntegration|Fetching projects"
+msgstr ""
+
+msgid "ClusterIntegration|Fetching zones"
+msgstr ""
+
+msgid "ClusterIntegration|Fluentd"
+msgstr ""
+
+msgid "ClusterIntegration|Fluentd is an open source data collector, which lets you unify the data collection and consumption for a better use and understanding of data. It requires at least one of the following logs to be successfully installed."
+msgstr ""
+
+msgid "ClusterIntegration|GitLab Integration"
+msgstr ""
+
+msgid "ClusterIntegration|GitLab Runner"
+msgstr ""
+
+msgid "ClusterIntegration|GitLab Runner connects to the repository and executes CI/CD jobs, pushing results back and deploying applications to production."
+msgstr ""
+
+msgid "ClusterIntegration|GitLab-managed cluster"
+msgstr ""
+
+msgid "ClusterIntegration|Global default"
+msgstr ""
+
+msgid "ClusterIntegration|Google Cloud Platform project"
+msgstr ""
+
+msgid "ClusterIntegration|Google GKE"
+msgstr ""
+
+msgid "ClusterIntegration|Google Kubernetes Engine"
+msgstr ""
+
+msgid "ClusterIntegration|Google Kubernetes Engine project"
+msgstr ""
+
+msgid "ClusterIntegration|Group cluster"
+msgstr ""
+
+msgid "ClusterIntegration|Helm Tiller"
+msgstr ""
+
+msgid "ClusterIntegration|Helm release failed to install"
+msgstr ""
+
+msgid "ClusterIntegration|Helm streamlines installing and managing Kubernetes applications. Tiller runs inside of your Kubernetes Cluster, and manages releases of your charts."
+msgstr ""
+
+msgid "ClusterIntegration|If you are setting up multiple clusters and are using Auto DevOps, %{help_link_start}read this first%{help_link_end}."
+msgstr ""
+
+msgid "ClusterIntegration|If you do not wish to delete all associated GitLab resources, you can simply remove the integration."
+msgstr ""
+
+msgid "ClusterIntegration|In order to view the health of your cluster, you must first install Prometheus in the Applications tab."
+msgstr ""
+
+msgid "ClusterIntegration|Ingress"
+msgstr ""
+
+msgid "ClusterIntegration|Ingress Endpoint"
+msgstr ""
+
+msgid "ClusterIntegration|Ingress gives you a way to route requests to services based on the request host or path, centralizing a number of services into a single entrypoint."
+msgstr ""
+
+msgid "ClusterIntegration|Installing Ingress may incur additional costs. Learn more about %{linkStart}pricing%{linkEnd}."
+msgstr ""
+
+msgid "ClusterIntegration|Instance cluster"
+msgstr ""
+
+msgid "ClusterIntegration|Instance type"
+msgstr ""
+
+msgid "ClusterIntegration|Integrate Kubernetes cluster automation"
+msgstr ""
+
+msgid "ClusterIntegration|Issuer Email"
+msgstr ""
+
+msgid "ClusterIntegration|Issuers represent a certificate authority. You must provide an email address for your Issuer."
+msgstr ""
+
+msgid "ClusterIntegration|Jupyter Hostname"
+msgstr ""
+
+msgid "ClusterIntegration|JupyterHub"
+msgstr ""
+
+msgid "ClusterIntegration|JupyterHub, a multi-user Hub, spawns, manages, and proxies multiple instances of the single-user Jupyter notebook server. JupyterHub can be used to serve notebooks to a class of students, a corporate data science group, or a scientific research group."
+msgstr ""
+
+msgid "ClusterIntegration|Key pair name"
+msgstr ""
+
+msgid "ClusterIntegration|Knative"
+msgstr ""
+
+msgid "ClusterIntegration|Knative Domain Name:"
+msgstr ""
+
+msgid "ClusterIntegration|Knative Endpoint:"
+msgstr ""
+
+msgid "ClusterIntegration|Knative domain name was updated successfully."
+msgstr ""
+
+msgid "ClusterIntegration|Knative extends Kubernetes to provide a set of middleware components that are essential to build modern, source-centric, and container-based applications that can run anywhere: on premises, in the cloud, or even in a third-party data center."
+msgstr ""
+
+msgid "ClusterIntegration|Kubernetes cluster"
+msgstr ""
+
+msgid "ClusterIntegration|Kubernetes cluster is being created..."
+msgstr ""
+
+msgid "ClusterIntegration|Kubernetes cluster name"
+msgstr ""
+
+msgid "ClusterIntegration|Kubernetes cluster was successfully created."
+msgstr ""
+
+msgid "ClusterIntegration|Kubernetes clusters allow you to use review apps, deploy your applications, run your pipelines, and much more in an easy way."
+msgstr ""
+
+msgid "ClusterIntegration|Kubernetes clusters can be used to deploy applications and to provide Review Apps for this project"
+msgstr ""
+
+msgid "ClusterIntegration|Kubernetes version"
+msgstr ""
+
+msgid "ClusterIntegration|Kubernetes version not found"
+msgstr ""
+
+msgid "ClusterIntegration|Learn more about %{help_link_start_machine_type}machine types%{help_link_end} and %{help_link_start_pricing}pricing%{help_link_end}."
+msgstr ""
+
+msgid "ClusterIntegration|Learn more about %{help_link_start}zones%{help_link_end}."
+msgstr ""
+
+msgid "ClusterIntegration|Learn more about %{startLink}Regions %{externalLinkIcon}%{endLink}."
+msgstr ""
+
+msgid "ClusterIntegration|Learn more about Kubernetes"
+msgstr ""
+
+msgid "ClusterIntegration|Learn more about group Kubernetes clusters"
+msgstr ""
+
+msgid "ClusterIntegration|Learn more about instance Kubernetes clusters"
+msgstr ""
+
+msgid "ClusterIntegration|Loading IAM Roles"
+msgstr ""
+
+msgid "ClusterIntegration|Loading Key Pairs"
+msgstr ""
+
+msgid "ClusterIntegration|Loading Regions"
+msgstr ""
+
+msgid "ClusterIntegration|Loading VPCs"
+msgstr ""
+
+msgid "ClusterIntegration|Loading instance types"
+msgstr ""
+
+msgid "ClusterIntegration|Loading networks"
+msgstr ""
+
+msgid "ClusterIntegration|Loading security groups"
+msgstr ""
+
+msgid "ClusterIntegration|Loading subnets"
+msgstr ""
+
+msgid "ClusterIntegration|Loading subnetworks"
+msgstr ""
+
+msgid "ClusterIntegration|Logging mode"
+msgstr ""
+
+msgid "ClusterIntegration|Machine type"
+msgstr ""
+
+msgid "ClusterIntegration|Make sure your account %{link_to_requirements} to create Kubernetes clusters"
+msgstr ""
+
+msgid "ClusterIntegration|Manage your Kubernetes cluster by visiting %{provider_link}"
+msgstr ""
+
+msgid "ClusterIntegration|No IAM Roles found"
+msgstr ""
+
+msgid "ClusterIntegration|No Key Pairs found"
+msgstr ""
+
+msgid "ClusterIntegration|No VPCs found"
+msgstr ""
+
+msgid "ClusterIntegration|No deployment cluster found for this job"
+msgstr ""
+
+msgid "ClusterIntegration|No deployment found for this job"
+msgstr ""
+
+msgid "ClusterIntegration|No instance type found"
+msgstr ""
+
+msgid "ClusterIntegration|No machine types matched your search"
+msgstr ""
+
+msgid "ClusterIntegration|No networks found"
+msgstr ""
+
+msgid "ClusterIntegration|No projects found"
+msgstr ""
+
+msgid "ClusterIntegration|No projects matched your search"
+msgstr ""
+
+msgid "ClusterIntegration|No region found"
+msgstr ""
+
+msgid "ClusterIntegration|No security group found"
+msgstr ""
+
+msgid "ClusterIntegration|No subnet found"
+msgstr ""
+
+msgid "ClusterIntegration|No subnetworks found"
+msgstr ""
+
+msgid "ClusterIntegration|No zones matched your search"
+msgstr ""
+
+msgid "ClusterIntegration|Number of nodes"
+msgstr ""
+
+msgid "ClusterIntegration|Number of nodes must be a numerical value."
+msgstr ""
+
+msgid "ClusterIntegration|Please enter access information for your Kubernetes cluster. If you need help, you can read our %{link_to_help_page} on Kubernetes"
+msgstr ""
+
+msgid "ClusterIntegration|Please make sure that your Google account meets the following requirements:"
+msgstr ""
+
+msgid "ClusterIntegration|Point a wildcard DNS to this generated endpoint in order to access your application after it has been deployed."
+msgstr ""
+
+msgid "ClusterIntegration|Project cluster"
+msgstr ""
+
+msgid "ClusterIntegration|Project namespace (optional, unique)"
+msgstr ""
+
+msgid "ClusterIntegration|Project namespace prefix (optional, unique)"
+msgstr ""
+
+msgid "ClusterIntegration|Prometheus"
+msgstr ""
+
+msgid "ClusterIntegration|Prometheus is an open-source monitoring system with %{linkStart}GitLab Integration%{linkEnd} to monitor deployed applications."
+msgstr ""
+
+msgid "ClusterIntegration|Provider details"
+msgstr ""
+
+msgid "ClusterIntegration|Provision Role ARN"
+msgstr ""
+
+msgid "ClusterIntegration|RBAC-enabled cluster"
+msgstr ""
+
+msgid "ClusterIntegration|Read our %{link_start}help page%{link_end} on Kubernetes cluster integration."
+msgstr ""
+
+msgid "ClusterIntegration|Real-time web application monitoring, logging and access control. %{linkStart}More information%{linkEnd}"
+msgstr ""
+
+msgid "ClusterIntegration|Region"
+msgstr ""
+
+msgid "ClusterIntegration|Remove Kubernetes cluster integration"
+msgstr ""
+
+msgid "ClusterIntegration|Remove integration"
+msgstr ""
+
+msgid "ClusterIntegration|Remove integration and resources"
+msgstr ""
+
+msgid "ClusterIntegration|Remove integration and resources?"
+msgstr ""
+
+msgid "ClusterIntegration|Remove integration?"
+msgstr ""
+
+msgid "ClusterIntegration|Remove this Kubernetes cluster's configuration from this project. This will not delete your actual Kubernetes cluster."
+msgstr ""
+
+msgid "ClusterIntegration|Removes cluster from project but keeps associated resources"
+msgstr ""
+
+msgid "ClusterIntegration|Replace this with your own hostname if you want. If you do so, point hostname to Ingress IP Address from above."
+msgstr ""
+
+msgid "ClusterIntegration|Request to begin installing failed"
+msgstr ""
+
+msgid "ClusterIntegration|Request to begin uninstalling failed"
+msgstr ""
+
+msgid "ClusterIntegration|SIEM Hostname"
+msgstr ""
+
+msgid "ClusterIntegration|SIEM Port"
+msgstr ""
+
+msgid "ClusterIntegration|SIEM Protocol"
+msgstr ""
+
+msgid "ClusterIntegration|Save changes"
+msgstr ""
+
+msgid "ClusterIntegration|Search IAM Roles"
+msgstr ""
+
+msgid "ClusterIntegration|Search Key Pairs"
+msgstr ""
+
+msgid "ClusterIntegration|Search VPCs"
+msgstr ""
+
+msgid "ClusterIntegration|Search domains"
+msgstr ""
+
+msgid "ClusterIntegration|Search instance types"
+msgstr ""
+
+msgid "ClusterIntegration|Search machine types"
+msgstr ""
+
+msgid "ClusterIntegration|Search networks"
+msgstr ""
+
+msgid "ClusterIntegration|Search projects"
+msgstr ""
+
+msgid "ClusterIntegration|Search regions"
+msgstr ""
+
+msgid "ClusterIntegration|Search security groups"
+msgstr ""
+
+msgid "ClusterIntegration|Search subnets"
+msgstr ""
+
+msgid "ClusterIntegration|Search subnetworks"
+msgstr ""
+
+msgid "ClusterIntegration|Search zones"
+msgstr ""
+
+msgid "ClusterIntegration|Security group"
+msgstr ""
+
+msgid "ClusterIntegration|See and edit the details for your Kubernetes cluster"
+msgstr ""
+
+msgid "ClusterIntegration|Select a VPC to choose a security group"
+msgstr ""
+
+msgid "ClusterIntegration|Select a VPC to choose a subnet"
+msgstr ""
+
+msgid "ClusterIntegration|Select a VPC to use for your EKS Cluster resources. To use a new VPC, first create one on %{startLink}Amazon Web Services %{externalLinkIcon} %{endLink}."
+msgstr ""
+
+msgid "ClusterIntegration|Select a network to choose a subnetwork"
+msgstr ""
+
+msgid "ClusterIntegration|Select a region to choose a Key Pair"
+msgstr ""
+
+msgid "ClusterIntegration|Select a region to choose a VPC"
+msgstr ""
+
+msgid "ClusterIntegration|Select a stack to install Crossplane."
+msgstr ""
+
+msgid "ClusterIntegration|Select a zone to choose a network"
+msgstr ""
+
+msgid "ClusterIntegration|Select existing domain or use new"
+msgstr ""
+
+msgid "ClusterIntegration|Select machine type"
+msgstr ""
+
+msgid "ClusterIntegration|Select project"
+msgstr ""
+
+msgid "ClusterIntegration|Select project and zone to choose machine type"
+msgstr ""
+
+msgid "ClusterIntegration|Select project to choose zone"
+msgstr ""
+
+msgid "ClusterIntegration|Select the key pair name that will be used to create EC2 nodes. To use a new key pair name, first create one on %{startLink}Amazon Web Services %{externalLinkIcon} %{endLink}."
+msgstr ""
+
+msgid "ClusterIntegration|Select zone"
+msgstr ""
+
+msgid "ClusterIntegration|Select zone to choose machine type"
+msgstr ""
+
+msgid "ClusterIntegration|Send Container Network Policies Logs"
+msgstr ""
+
+msgid "ClusterIntegration|Send Web Application Firewall Logs"
+msgstr ""
+
+msgid "ClusterIntegration|Service Token"
+msgstr ""
+
+msgid "ClusterIntegration|Service role"
+msgstr ""
+
+msgid "ClusterIntegration|Service token is required."
+msgstr ""
+
+msgid "ClusterIntegration|Set a prefix for your namespaces. If not set, defaults to your project path. If modified, existing environments will use their current namespaces until the cluster cache is cleared."
+msgstr ""
+
+msgid "ClusterIntegration|Set the global mode for the WAF in this cluster. This can be overridden at the environmental level."
+msgstr ""
+
+msgid "ClusterIntegration|Something went wrong on our end."
+msgstr ""
+
+msgid "ClusterIntegration|Something went wrong while creating your Kubernetes cluster"
+msgstr ""
+
+msgid "ClusterIntegration|Something went wrong while installing %{title}"
+msgstr ""
+
+msgid "ClusterIntegration|Something went wrong while trying to save your settings. Please try again."
+msgstr ""
+
+msgid "ClusterIntegration|Something went wrong while uninstalling %{title}"
+msgstr ""
+
+msgid "ClusterIntegration|Something went wrong while updating Knative domain name."
+msgstr ""
+
+msgid "ClusterIntegration|Specifying a domain will allow you to use Auto Review Apps and Auto Deploy stages for %{auto_devops_start}Auto DevOps%{auto_devops_end}. The domain should have a wildcard DNS configured matching the domain."
+msgstr ""
+
+msgid "ClusterIntegration|Subnets"
+msgstr ""
+
+msgid "ClusterIntegration|The Amazon Resource Name (ARN) associated with your role. If you do not have a provision role, first create one on %{startAwsLink}Amazon Web Services %{externalLinkIcon}%{endLink} using the above account and external IDs. %{startMoreInfoLink}More information%{endLink}"
+msgstr ""
+
+msgid "ClusterIntegration|The Kubernetes certificate used to authenticate to the cluster."
+msgstr ""
+
+msgid "ClusterIntegration|The URL used to access the Kubernetes API."
+msgstr ""
+
+msgid "ClusterIntegration|The associated IP and all deployed services will be deleted and cannot be restored. Uninstalling Knative will also remove Istio from your cluster. This will not effect any other applications."
+msgstr ""
+
+msgid "ClusterIntegration|The associated Tiller pod, the %{gitlabManagedAppsNamespace} namespace, and all of its resources will be deleted and cannot be restored."
+msgstr ""
+
+msgid "ClusterIntegration|The associated load balancer and IP will be deleted and cannot be restored."
+msgstr ""
+
+msgid "ClusterIntegration|The associated private key will be deleted and cannot be restored."
+msgstr ""
+
+msgid "ClusterIntegration|The elastic stack collects logs from all pods in your cluster"
+msgstr ""
+
+msgid "ClusterIntegration|The endpoint is in the process of being assigned. Please check your Kubernetes cluster or Quotas on Google Kubernetes Engine if it takes a long time."
+msgstr ""
+
+msgid "ClusterIntegration|The namespace associated with your project. This will be used for deploy boards, logs, and Web terminals."
+msgstr ""
+
+msgid "ClusterIntegration|There was a problem authenticating with your cluster. Please ensure your CA Certificate and Token are valid."
+msgstr ""
+
+msgid "ClusterIntegration|This account must have permissions to create a Kubernetes cluster in the %{link_to_container_project} specified below"
+msgstr ""
+
+msgid "ClusterIntegration|This option will allow you to install applications on RBAC clusters."
+msgstr ""
+
+msgid "ClusterIntegration|To access your application after deployment, point a wildcard DNS to the Knative Endpoint."
+msgstr ""
+
+msgid "ClusterIntegration|To remove your integration and resources, type %{clusterName} to confirm:"
+msgstr ""
+
+msgid "ClusterIntegration|To remove your integration, type %{clusterName} to confirm:"
+msgstr ""
+
+msgid "ClusterIntegration|Toggle Kubernetes cluster"
+msgstr ""
+
+msgid "ClusterIntegration|Uninstall %{appTitle}"
+msgstr ""
+
+msgid "ClusterIntegration|Update %{appTitle}"
+msgstr ""
+
+msgid "ClusterIntegration|Update failed. Please check the logs and try again."
+msgstr ""
+
+msgid "ClusterIntegration|Use %{query}"
+msgstr ""
+
+msgid "ClusterIntegration|Uses the Cloud Run, Istio, and HTTP Load Balancing addons for this cluster."
+msgstr ""
+
+msgid "ClusterIntegration|VPC"
+msgstr ""
+
+msgid "ClusterIntegration|Validating project billing status"
+msgstr ""
+
+msgid "ClusterIntegration|We could not verify that one of your projects on GCP has billing enabled. Please try again."
+msgstr ""
+
+msgid "ClusterIntegration|With a Kubernetes cluster associated to this project, you can use review apps, deploy your applications, run your pipelines, and much more in an easy way."
+msgstr ""
+
+msgid "ClusterIntegration|You are about to remove your cluster integration and all GitLab-created resources associated with this cluster."
+msgstr ""
+
+msgid "ClusterIntegration|You are about to remove your cluster integration."
+msgstr ""
+
+msgid "ClusterIntegration|You are about to uninstall %{appTitle} from your cluster."
+msgstr ""
+
+msgid "ClusterIntegration|You are about to update %{appTitle} on your cluster."
+msgstr ""
+
+msgid "ClusterIntegration|You must first install Helm Tiller before installing the applications below"
+msgstr ""
+
+msgid "ClusterIntegration|You must grant access to your organization’s AWS resources in order to create a new EKS cluster. To grant access, create a provision role using the account and external ID below and provide us the ARN."
+msgstr ""
+
+msgid "ClusterIntegration|You must have an RBAC-enabled cluster to install Knative."
+msgstr ""
+
+msgid "ClusterIntegration|You must specify a domain before you can install Knative."
+msgstr ""
+
+msgid "ClusterIntegration|You should select at least two subnets"
+msgstr ""
+
+msgid "ClusterIntegration|Your Elasticsearch cluster will be re-created during this upgrade. Your logs will be re-indexed, and you will lose historical logs from hosts terminated in the last 30 days."
+msgstr ""
+
+msgid "ClusterIntegration|Your account must have %{link_to_kubernetes_engine}"
+msgstr ""
+
+msgid "ClusterIntegration|Your cluster API is unreachable. Please ensure your API URL is correct."
+msgstr ""
+
+msgid "ClusterIntegration|Your service role is distinct from the provision role used when authenticating. It will allow Amazon EKS and the Kubernetes control plane to manage AWS resources on your behalf. To use a new role, first create one on %{startLink}Amazon Web Services %{externalLinkIcon} %{endLink}."
+msgstr ""
+
+msgid "ClusterIntegration|Zone"
+msgstr ""
+
+msgid "ClusterIntegration|access to Google Kubernetes Engine"
+msgstr ""
+
+msgid "ClusterIntegration|documentation"
+msgstr ""
+
+msgid "ClusterIntegration|installed via %{linkStart}Cloud Run%{linkEnd}"
+msgstr ""
+
+msgid "ClusterIntegration|meets the requirements"
+msgstr ""
+
+msgid "ClusterIntegration|sign up"
+msgstr ""
+
+msgid "ClusterIntergation|Select a VPC"
+msgstr ""
+
+msgid "ClusterIntergation|Select a network"
+msgstr ""
+
+msgid "ClusterIntergation|Select a region"
+msgstr ""
+
+msgid "ClusterIntergation|Select a security group"
+msgstr ""
+
+msgid "ClusterIntergation|Select a subnet"
+msgstr ""
+
+msgid "ClusterIntergation|Select a subnetwork"
+msgstr ""
+
+msgid "ClusterIntergation|Select an instance type"
+msgstr ""
+
+msgid "ClusterIntergation|Select key pair"
+msgstr ""
+
+msgid "ClusterIntergation|Select service role"
+msgstr ""
+
+msgid "Clusters|An error occurred while loading clusters"
+msgstr ""
+
+msgid "Code"
+msgstr ""
+
+msgid "Code Coverage: %{coveragePercentage}%{percentSymbol}"
+msgstr ""
+
+msgid "Code Coverage| Empty code coverage data"
+msgstr ""
+
+msgid "Code Coverage|Couldn't fetch the code coverage data"
+msgstr ""
+
+msgid "Code Owners"
+msgstr ""
+
+msgid "Code Owners to the merge request changes."
+msgstr ""
+
+msgid "Code Quality"
+msgstr ""
+
+msgid "Code Review"
+msgstr ""
+
+msgid "Code Review Analytics displays a table of open merge requests considered to be in code review. There are currently no merge requests in review for this project and/or filters."
+msgstr ""
+
+msgid "Code coverage statistics for master %{start_date} - %{end_date}"
+msgstr ""
+
+msgid "Code owner approval is required"
+msgstr ""
+
+msgid "Code owners"
+msgstr ""
+
+msgid "CodeOwner|Pattern"
+msgstr ""
+
+msgid "Cohorts"
+msgstr ""
+
+msgid "Cohorts|Inactive users"
+msgstr ""
+
+msgid "Cohorts|Month %{month_index}"
+msgstr ""
+
+msgid "Cohorts|New users"
+msgstr ""
+
+msgid "Cohorts|Registration month"
+msgstr ""
+
+msgid "Cohorts|Returning users"
+msgstr ""
+
+msgid "Cohorts|User cohorts are shown for the last %{months_included} months. Only users with activity are counted in the 'New users' column; inactive users are counted separately."
+msgstr ""
+
+msgid "Collapse"
+msgstr ""
+
+msgid "Collapse approvers"
+msgstr ""
+
+msgid "Collapse milestones"
+msgstr ""
+
+msgid "Collapse replies"
+msgstr ""
+
+msgid "Collapse sidebar"
+msgstr ""
+
+msgid "Collector hostname"
+msgstr ""
+
+msgid "ComboSearch is not defined"
+msgstr ""
+
+msgid "Coming soon"
+msgstr ""
+
+msgid "Comma-separated, e.g. '1.1.1.1, 2.2.2.0/24'"
+msgstr ""
+
+msgid "Command"
+msgstr ""
+
+msgid "Command line instructions"
+msgstr ""
+
+msgid "Commands applied"
+msgstr ""
+
+msgid "Commands did not apply"
+msgstr ""
+
+msgid "Comment"
+msgstr ""
+
+msgid "Comment & close %{noteable_name}"
+msgstr ""
+
+msgid "Comment & reopen %{noteable_name}"
+msgstr ""
+
+msgid "Comment & resolve thread"
+msgstr ""
+
+msgid "Comment & unresolve thread"
+msgstr ""
+
+msgid "Comment '%{label}' position"
+msgstr ""
+
+msgid "Comment form position"
+msgstr ""
+
+msgid "Comment is being updated"
+msgstr ""
+
+msgid "Comment on lines %{startLine} to %{endLine}"
+msgstr ""
+
+msgid "Comment/Reply (quoting selected text)"
+msgstr ""
+
+msgid "Comments"
+msgstr ""
+
+msgid "Commit"
+msgid_plural "Commits"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Commit %{commit_id}"
+msgstr ""
+
+msgid "Commit (when editing commit message)"
+msgstr ""
+
+msgid "Commit Message"
+msgstr ""
+
+msgid "Commit deleted"
+msgstr ""
+
+msgid "Commit message"
+msgstr ""
+
+msgid "Commit message (optional)"
+msgstr ""
+
+msgid "Commit statistics for %{ref} %{start_time} - %{end_time}"
+msgstr ""
+
+msgid "Commit to %{branchName} branch"
+msgstr ""
+
+msgid "CommitBoxTitle|Commit"
+msgstr ""
+
+msgid "CommitMessage|Add %{file_name}"
+msgstr ""
+
+msgid "CommitWidget|authored"
+msgstr ""
+
+msgid "Commits"
+msgstr ""
+
+msgid "Commits feed"
+msgstr ""
+
+msgid "Commits per day hour (UTC)"
+msgstr ""
+
+msgid "Commits per day of month"
+msgstr ""
+
+msgid "Commits per weekday"
+msgstr ""
+
+msgid "Commits to"
+msgstr ""
+
+msgid "Commits|An error occurred while fetching merge requests data."
+msgstr ""
+
+msgid "Commits|History"
+msgstr ""
+
+msgid "Commits|No related merge requests found"
+msgstr ""
+
+msgid "Committed by"
+msgstr ""
+
+msgid "Commit…"
+msgstr ""
+
+msgid "Company"
+msgstr ""
+
+msgid "Company name"
+msgstr ""
+
+msgid "Compare"
+msgstr ""
+
+msgid "Compare Git revisions"
+msgstr ""
+
+msgid "Compare Revisions"
+msgstr ""
+
+msgid "Compare changes"
+msgstr ""
+
+msgid "Compare changes with the last commit"
+msgstr ""
+
+msgid "Compare changes with the merge request target branch"
+msgstr ""
+
+msgid "Compare with previous version"
+msgstr ""
+
+msgid "CompareBranches|%{source_branch} and %{target_branch} are the same."
+msgstr ""
+
+msgid "CompareBranches|Compare"
+msgstr ""
+
+msgid "CompareBranches|Source"
+msgstr ""
+
+msgid "CompareBranches|Target"
+msgstr ""
+
+msgid "CompareBranches|There isn't anything to compare."
+msgstr ""
+
+msgid "Complete"
+msgstr ""
+
+msgid "Compliance"
+msgstr ""
+
+msgid "Compliance Dashboard"
+msgstr ""
+
+msgid "Compliance framework (optional)"
+msgstr ""
+
+msgid "Compliance frameworks"
+msgstr ""
+
+msgid "ComplianceDashboard|created by:"
+msgstr ""
+
+msgid "ComplianceFramework|GDPR"
+msgstr ""
+
+msgid "ComplianceFramework|GDPR - General Data Protection Regulation"
+msgstr ""
+
+msgid "ComplianceFramework|HIPAA"
+msgstr ""
+
+msgid "ComplianceFramework|HIPAA - Health Insurance Portability and Accountability Act"
+msgstr ""
+
+msgid "ComplianceFramework|PCI-DSS"
+msgstr ""
+
+msgid "ComplianceFramework|PCI-DSS - Payment Card Industry-Data Security Standard"
+msgstr ""
+
+msgid "ComplianceFramework|SOC 2"
+msgstr ""
+
+msgid "ComplianceFramework|SOC 2 - Service Organization Control 2"
+msgstr ""
+
+msgid "ComplianceFramework|SOX"
+msgstr ""
+
+msgid "ComplianceFramework|SOX - Sarbanes-Oxley"
+msgstr ""
+
+msgid "ComplianceFramework|This project is regulated by %{framework}."
+msgstr ""
+
+msgid "Confidence"
+msgstr ""
+
+msgid "Confidential"
+msgstr ""
+
+msgid "Confidentiality"
+msgstr ""
+
+msgid "Configuration"
+msgstr ""
+
+msgid "Configure GitLab runners to start using the Web Terminal. %{helpStart}Learn more.%{helpEnd}"
+msgstr ""
+
+msgid "Configure Gitaly timeouts."
+msgstr ""
+
+msgid "Configure Let's Encrypt"
+msgstr ""
+
+msgid "Configure Prometheus"
+msgstr ""
+
+msgid "Configure Tracing"
+msgstr ""
+
+msgid "Configure a <code>.gitlab-webide.yml</code> file in the <code>.gitlab</code> directory to start using the Web Terminal. %{helpStart}Learn more.%{helpEnd}"
+msgstr ""
+
+msgid "Configure automatic git checks and housekeeping on repositories."
+msgstr ""
+
+msgid "Configure existing installation"
+msgstr ""
+
+msgid "Configure limit for issues created per minute by web and API requests."
+msgstr ""
+
+msgid "Configure limits for web and API requests."
+msgstr ""
+
+msgid "Configure limits on the number of inbound alerts able to be sent to a project."
+msgstr ""
+
+msgid "Configure paths to be protected by Rack Attack."
+msgstr ""
+
+msgid "Configure repository mirroring."
+msgstr ""
+
+msgid "Configure storage path settings."
+msgstr ""
+
+msgid "Configure the %{link} integration."
+msgstr ""
+
+msgid "Configure the way a user creates a new account."
+msgstr ""
+
+msgid "Confirm"
+msgstr ""
+
+msgid "Confirmation email sent to %{email}"
+msgstr ""
+
+msgid "Confirmation required"
+msgstr ""
+
+msgid "Congratulations! You have enabled Two-factor Authentication!"
+msgstr ""
+
+msgid "Connect"
+msgstr ""
+
+msgid "Connect all repositories"
+msgstr ""
+
+msgid "Connect repositories from GitHub"
+msgstr ""
+
+msgid "Connect your external repositories, and CI/CD pipelines will run for new commits. A GitLab project will be created with only CI/CD features enabled."
+msgstr ""
+
+msgid "Connected"
+msgstr ""
+
+msgid "Connecting"
+msgstr ""
+
+msgid "Connecting to terminal sync service"
+msgstr ""
+
+msgid "Connecting..."
+msgstr ""
+
+msgid "Connection failed"
+msgstr ""
+
+msgid "Connection failure"
+msgstr ""
+
+msgid "Connection timed out"
+msgstr ""
+
+msgid "Connection timeout"
+msgstr ""
+
+msgid "Contact sales to upgrade"
+msgstr ""
+
+msgid "Contact support"
+msgstr ""
+
+msgid "Container Registry"
+msgstr ""
+
+msgid "Container Registry tag expiration policy"
+msgstr ""
+
+msgid "Container Scanning"
+msgstr ""
+
+msgid "Container does not exist"
+msgstr ""
+
+msgid "Container registry images"
+msgstr ""
+
+msgid "Container registry is not enabled on this GitLab instance. Ask an administrator to enable it in order for Auto DevOps to work."
+msgstr ""
+
+msgid "Container repositories sync capacity"
+msgstr ""
+
+msgid "ContainerRegistry| Please visit the %{linkStart}administration settings%{linkEnd} to enable this feature."
+msgstr ""
+
+msgid "ContainerRegistry|%{count} Image repository"
+msgid_plural "ContainerRegistry|%{count} Image repositories"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "ContainerRegistry|%{count} Tag"
+msgid_plural "ContainerRegistry|%{count} Tags"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "ContainerRegistry|%{imageName} tags"
+msgstr ""
+
+msgid "ContainerRegistry|%{title} was successfully scheduled for deletion"
+msgstr ""
+
+msgid "ContainerRegistry|Automatically remove extra images that aren't designed to be kept."
+msgstr ""
+
+msgid "ContainerRegistry|Build an image"
+msgstr ""
+
+msgid "ContainerRegistry|CLI Commands"
+msgstr ""
+
+msgid "ContainerRegistry|Container Registry"
+msgstr ""
+
+msgid "ContainerRegistry|Container Registry tag expiration and retention policy is disabled"
+msgstr ""
+
+msgid "ContainerRegistry|Copy build command"
+msgstr ""
+
+msgid "ContainerRegistry|Copy login command"
+msgstr ""
+
+msgid "ContainerRegistry|Copy push command"
+msgstr ""
+
+msgid "ContainerRegistry|Delete selected"
+msgstr ""
+
+msgid "ContainerRegistry|Docker connection error"
+msgstr ""
+
+msgid "ContainerRegistry|Docker tag expiration policy is %{toggleStatus}"
+msgstr ""
+
+msgid "ContainerRegistry|Expiration interval:"
+msgstr ""
+
+msgid "ContainerRegistry|Expiration policies help manage the storage space used by the Container Registry, but the expiration policies for this registry are disabled. Contact your administrator to enable. %{docLinkStart}More information%{docLinkEnd}"
+msgstr ""
+
+msgid "ContainerRegistry|Expiration policy is disabled"
+msgstr ""
+
+msgid "ContainerRegistry|Expiration policy successfully saved."
+msgstr ""
+
+msgid "ContainerRegistry|Expiration policy will run in %{time}"
+msgstr ""
+
+msgid "ContainerRegistry|Expiration policy:"
+msgstr ""
+
+msgid "ContainerRegistry|Expiration schedule:"
+msgstr ""
+
+msgid "ContainerRegistry|Filter by name"
+msgstr ""
+
+msgid "ContainerRegistry|If you are not already logged in, you need to authenticate to the Container Registry by using your GitLab username and password. If you have %{twofaDocLinkStart}Two-Factor Authentication%{twofaDocLinkEnd} enabled, use a %{personalAccessTokensDocLinkStart}Personal Access Token%{personalAccessTokensDocLinkEnd} instead of a password."
+msgstr ""
+
+msgid "ContainerRegistry|Image ID: %{imageId}"
+msgstr ""
+
+msgid "ContainerRegistry|Image Repositories"
+msgstr ""
+
+msgid "ContainerRegistry|Image tags"
+msgstr ""
+
+msgid "ContainerRegistry|Keep and protect the images that matter most."
+msgstr ""
+
+msgid "ContainerRegistry|Login"
+msgstr ""
+
+msgid "ContainerRegistry|Missing or insufficient permission, delete button disabled"
+msgstr ""
+
+msgid "ContainerRegistry|Number of tags to retain:"
+msgstr ""
+
+msgid "ContainerRegistry|Please contact your administrator."
+msgstr ""
+
+msgid "ContainerRegistry|Published %{timeInfo}"
+msgstr ""
+
+msgid "ContainerRegistry|Push an image"
+msgstr ""
+
+msgid "ContainerRegistry|Regular expressions such as %{codeStart}.*-master%{codeEnd} or %{codeStart}release-.*%{codeEnd} are supported"
+msgstr ""
+
+msgid "ContainerRegistry|Regular expressions such as %{codeStart}.*-test%{codeEnd} or %{codeStart}dev-.*%{codeEnd} are supported. To select all tags, use %{codeStart}.*%{codeEnd}"
+msgstr ""
+
+msgid "ContainerRegistry|Remember to run %{docLinkStart}garbage collection%{docLinkEnd} to remove the stale data from storage."
+msgstr ""
+
+msgid "ContainerRegistry|Remove repository"
+msgstr ""
+
+msgid "ContainerRegistry|Remove tag"
+msgid_plural "ContainerRegistry|Remove tags"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "ContainerRegistry|Something went wrong while fetching the expiration policy."
+msgstr ""
+
+msgid "ContainerRegistry|Something went wrong while fetching the repository list."
+msgstr ""
+
+msgid "ContainerRegistry|Something went wrong while fetching the tags list."
+msgstr ""
+
+msgid "ContainerRegistry|Something went wrong while marking the tag for deletion."
+msgstr ""
+
+msgid "ContainerRegistry|Something went wrong while marking the tags for deletion."
+msgstr ""
+
+msgid "ContainerRegistry|Something went wrong while scheduling %{title} for deletion. Please try again."
+msgstr ""
+
+msgid "ContainerRegistry|Something went wrong while updating the expiration policy."
+msgstr ""
+
+msgid "ContainerRegistry|Sorry, your filter produced no results."
+msgstr ""
+
+msgid "ContainerRegistry|Tag expiration policy"
+msgstr ""
+
+msgid "ContainerRegistry|Tag expiration policy is designed to:"
+msgstr ""
+
+msgid "ContainerRegistry|Tag successfully marked for deletion."
+msgstr ""
+
+msgid "ContainerRegistry|Tags successfully marked for deletion."
+msgstr ""
+
+msgid "ContainerRegistry|Tags with names matching this regex pattern will %{italicStart}be preserved:%{italicEnd}"
+msgstr ""
+
+msgid "ContainerRegistry|Tags with names matching this regex pattern will %{italicStart}expire:%{italicEnd}"
+msgstr ""
+
+msgid "ContainerRegistry|The Container Registry tag expiration and retention policies for this project have not been enabled."
+msgstr ""
+
+msgid "ContainerRegistry|The last tag related to this image was recently removed. This empty image and any associated data will be automatically removed as part of the regular Garbage Collection process. If you have any questions, contact your administrator."
+msgstr ""
+
+msgid "ContainerRegistry|The value of this input should be less than 255 characters"
+msgstr ""
+
+msgid "ContainerRegistry|There are no container images available in this group"
+msgstr ""
+
+msgid "ContainerRegistry|There are no container images stored for this project"
+msgstr ""
+
+msgid "ContainerRegistry|There was an error during the deletion of this image repository, please try again."
+msgstr ""
+
+msgid "ContainerRegistry|This image has no active tags"
+msgstr ""
+
+msgid "ContainerRegistry|This image repository is scheduled for deletion"
+msgstr ""
+
+msgid "ContainerRegistry|To widen your search, change or remove the filters above."
+msgstr ""
+
+msgid "ContainerRegistry|We are having trouble connecting to the Registry, which could be due to an issue with your project name or path. %{docLinkStart}More information%{docLinkEnd}"
+msgstr ""
+
+msgid "ContainerRegistry|With the Container Registry, every project can have its own space to store its Docker images. %{docLinkStart}More Information%{docLinkEnd}"
+msgstr ""
+
+msgid "ContainerRegistry|With the Container Registry, every project can have its own space to store its Docker images. Push at least one Docker image in one of this group's projects in order to show up here. %{docLinkStart}More Information%{docLinkEnd}"
+msgstr ""
+
+msgid "ContainerRegistry|With the GitLab Container Registry, every project can have its own space to store images. %{docLinkStart}More information%{docLinkEnd}"
+msgstr ""
+
+msgid "ContainerRegistry|You are about to remove %{item} tags. Are you sure?"
+msgstr ""
+
+msgid "ContainerRegistry|You are about to remove %{item}. Are you sure?"
+msgstr ""
+
+msgid "ContainerRegistry|You are about to remove repository %{title}. Once you confirm, this repository will be permanently deleted."
+msgstr ""
+
+msgid "ContainerRegistry|You can add an image to this registry with the following commands:"
+msgstr ""
+
+msgid "Contains %{count} blobs of images (%{size})"
+msgstr ""
+
+msgid "Contents of .gitlab-ci.yml"
+msgstr ""
+
+msgid "Continue"
+msgstr ""
+
+msgid "Continue to the next step"
+msgstr ""
+
+msgid "Continuous Integration and Deployment"
+msgstr ""
+
+msgid "Contribute to GitLab"
+msgstr ""
+
+msgid "Contribution"
+msgstr ""
+
+msgid "Contribution Analytics"
+msgstr ""
+
+msgid "ContributionAnalytics|<strong>%{created_count}</strong> created, <strong>%{closed_count}</strong> closed."
+msgstr ""
+
+msgid "ContributionAnalytics|<strong>%{created_count}</strong> created, <strong>%{merged_count}</strong> merged."
+msgstr ""
+
+msgid "ContributionAnalytics|<strong>%{pushes}</strong> pushes, more than <strong>%{commits}</strong> commits by <strong>%{people}</strong> contributors."
+msgstr ""
+
+msgid "ContributionAnalytics|Contribution analytics for issues, merge requests and push events since %{start_date}"
+msgstr ""
+
+msgid "ContributionAnalytics|Issues"
+msgstr ""
+
+msgid "ContributionAnalytics|Last 3 months"
+msgstr ""
+
+msgid "ContributionAnalytics|Last month"
+msgstr ""
+
+msgid "ContributionAnalytics|Last week"
+msgstr ""
+
+msgid "ContributionAnalytics|Merge Requests"
+msgstr ""
+
+msgid "ContributionAnalytics|No issues for the selected time period."
+msgstr ""
+
+msgid "ContributionAnalytics|No merge requests for the selected time period."
+msgstr ""
+
+msgid "ContributionAnalytics|No pushes for the selected time period."
+msgstr ""
+
+msgid "Contributions for <strong>%{calendar_date}</strong>"
+msgstr ""
+
+msgid "Contributions per group member"
+msgstr ""
+
+msgid "Contributors"
+msgstr ""
+
+msgid "Control emails linked to your account"
+msgstr ""
+
+msgid "Control the display of third party offers."
+msgstr ""
+
+msgid "Cookie domain"
+msgstr ""
+
+msgid "Copied"
+msgstr ""
+
+msgid "Copied labels and milestone from %{source_issuable_reference}."
+msgstr ""
+
+msgid "Copy"
+msgstr ""
+
+msgid "Copy %{field}"
+msgstr ""
+
+msgid "Copy %{http_label} clone URL"
+msgstr ""
+
+msgid "Copy %{protocol} clone URL"
+msgstr ""
+
+msgid "Copy %{proxy_url}"
+msgstr ""
+
+msgid "Copy %{type}"
+msgstr ""
+
+msgid "Copy Account ID to clipboard"
+msgstr ""
+
+msgid "Copy External ID to clipboard"
+msgstr ""
+
+msgid "Copy ID"
+msgstr ""
+
+msgid "Copy KRB5 clone URL"
+msgstr ""
+
+msgid "Copy SSH clone URL"
+msgstr ""
+
+msgid "Copy SSH public key"
+msgstr ""
+
+msgid "Copy URL"
+msgstr ""
+
+msgid "Copy branch name"
+msgstr ""
+
+msgid "Copy command"
+msgstr ""
+
+msgid "Copy commands"
+msgstr ""
+
+msgid "Copy commit SHA"
+msgstr ""
+
+msgid "Copy environment"
+msgstr ""
+
+msgid "Copy evidence SHA"
+msgstr ""
+
+msgid "Copy file contents"
+msgstr ""
+
+msgid "Copy file path"
+msgstr ""
+
+msgid "Copy key"
+msgstr ""
+
+msgid "Copy labels and milestone from %{source_issuable_reference}."
+msgstr ""
+
+msgid "Copy labels and milestone from other issue or merge request in this project"
+msgstr ""
+
+msgid "Copy link"
+msgstr ""
+
+msgid "Copy link to chart"
+msgstr ""
+
+msgid "Copy reference"
+msgstr ""
+
+msgid "Copy secret"
+msgstr ""
+
+msgid "Copy token"
+msgstr ""
+
+msgid "Copy trigger token"
+msgstr ""
+
+msgid "Copy value"
+msgstr ""
+
+msgid "Could not add admins as members"
+msgstr ""
+
+msgid "Could not authorize chat nickname. Try again!"
+msgstr ""
+
+msgid "Could not change HEAD: branch '%{branch}' does not exist"
+msgstr ""
+
+msgid "Could not connect to FogBugz, check your URL"
+msgstr ""
+
+msgid "Could not connect to Sentry. Refresh the page to try again."
+msgstr ""
+
+msgid "Could not connect to Web IDE file mirror service."
+msgstr ""
+
+msgid "Could not create Wiki Repository at this time. Please try again later."
+msgstr ""
+
+msgid "Could not create environment"
+msgstr ""
+
+msgid "Could not create group"
+msgstr ""
+
+msgid "Could not create project"
+msgstr ""
+
+msgid "Could not delete %{design}. Please try again."
+msgstr ""
+
+msgid "Could not delete chat nickname %{chat_name}."
+msgstr ""
+
+msgid "Could not find design."
+msgstr ""
+
+msgid "Could not find iteration"
+msgstr ""
+
+msgid "Could not remove the trigger."
+msgstr ""
+
+msgid "Could not restore the group"
+msgstr ""
+
+msgid "Could not revoke impersonation token %{token_name}."
+msgstr ""
+
+msgid "Could not revoke personal access token %{personal_access_token_name}."
+msgstr ""
+
+msgid "Could not revoke project access token %{project_access_token_name}."
+msgstr ""
+
+msgid "Could not save group ID"
+msgstr ""
+
+msgid "Could not save project ID"
+msgstr ""
+
+msgid "Could not save prometheus manual configuration"
+msgstr ""
+
+msgid "Could not update the LDAP settings"
+msgstr ""
+
+msgid "Could not upload your designs as one or more files uploaded are not supported."
+msgstr ""
+
+msgid "Country"
+msgstr ""
+
+msgid "Coverage"
+msgstr ""
+
+msgid "Coverage Fuzzing"
+msgstr ""
+
+msgid "Crash State"
+msgstr ""
+
+msgid "Create"
+msgstr ""
+
+msgid "Create %{environment}"
+msgstr ""
+
+msgid "Create %{type}"
+msgstr ""
+
+msgid "Create New Directory"
+msgstr ""
+
+msgid "Create New Domain"
+msgstr ""
+
+msgid "Create Project"
+msgstr ""
+
+msgid "Create a GitLab account first, and then connect it to your %{label} account."
+msgstr ""
+
+msgid "Create a Mattermost team for this group"
+msgstr ""
+
+msgid "Create a local proxy for storing frequently used upstream images. %{link_start}Learn more%{link_end} about dependency proxies."
+msgstr ""
+
+msgid "Create a merge request"
+msgstr ""
+
+msgid "Create a new branch"
+msgstr ""
+
+msgid "Create a new deploy key for this project"
+msgstr ""
+
+msgid "Create a new file as there are no files yet. Afterwards, you'll be able to commit your changes."
+msgstr ""
+
+msgid "Create a new issue"
+msgstr ""
+
+msgid "Create a new repository"
+msgstr ""
+
+msgid "Create a personal access token on your account to pull or push via %{protocol}."
+msgstr ""
+
+msgid "Create a project pre-populated with the necessary files to get you started quickly."
+msgstr ""
+
+msgid "Create an account using:"
+msgstr ""
+
+msgid "Create an issue. Issues are created for each alert triggered."
+msgstr ""
+
+msgid "Create and provide your GitHub %{link_start}Personal Access Token%{link_end}. You will need to select the <code>repo</code> scope, so we can display a list of your public and private repositories which are available to import."
+msgstr ""
+
+msgid "Create board"
+msgstr ""
+
+msgid "Create branch"
+msgstr ""
+
+msgid "Create commit"
+msgstr ""
+
+msgid "Create confidential merge request"
+msgstr ""
+
+msgid "Create confidential merge request and branch"
+msgstr ""
+
+msgid "Create directory"
+msgstr ""
+
+msgid "Create empty repository"
+msgstr ""
+
+msgid "Create epic"
+msgstr ""
+
+msgid "Create file"
+msgstr ""
+
+msgid "Create group"
+msgstr ""
+
+msgid "Create group label"
+msgstr ""
+
+msgid "Create issue"
+msgstr ""
+
+msgid "Create iteration"
+msgstr ""
+
+msgid "Create lists from labels. Issues with that label appear in that list."
+msgstr ""
+
+msgid "Create merge request"
+msgstr ""
+
+msgid "Create merge request and branch"
+msgstr ""
+
+msgid "Create milestone"
+msgstr ""
+
+msgid "Create new"
+msgstr ""
+
+msgid "Create new board"
+msgstr ""
+
+msgid "Create new branch"
+msgstr ""
+
+msgid "Create new directory"
+msgstr ""
+
+msgid "Create new file"
+msgstr ""
+
+msgid "Create new file or directory"
+msgstr ""
+
+msgid "Create new label"
+msgstr ""
+
+msgid "Create new..."
+msgstr ""
+
+msgid "Create project"
+msgstr ""
+
+msgid "Create project label"
+msgstr ""
+
+msgid "Create requirement"
+msgstr ""
+
+msgid "Create snippet"
+msgstr ""
+
+msgid "Create wildcard: %{searchTerm}"
+msgstr ""
+
+msgid "Create your first page"
+msgstr ""
+
+msgid "Create your group"
+msgstr ""
+
+msgid "Create/import your first project"
+msgstr ""
+
+msgid "CreateGroup|You don’t have permission to create a subgroup in this group."
+msgstr ""
+
+msgid "CreateGroup|You don’t have permission to create groups."
+msgstr ""
+
+msgid "CreateTag|Tag"
+msgstr ""
+
+msgid "CreateTokenToCloneLink|create a personal access token"
+msgstr ""
+
+msgid "Created"
+msgstr ""
+
+msgid "Created %{timestamp}"
+msgstr ""
+
+msgid "Created At"
+msgstr ""
+
+msgid "Created On"
+msgstr ""
+
+msgid "Created a branch and a merge request to resolve this issue."
+msgstr ""
+
+msgid "Created branch '%{branch_name}' and a merge request to resolve this issue."
+msgstr ""
+
+msgid "Created by me"
+msgstr ""
+
+msgid "Created date"
+msgstr ""
+
+msgid "Created issue %{issueLink}"
+msgstr ""
+
+msgid "Created issue %{issueLink} at %{projectLink}"
+msgstr ""
+
+msgid "Created merge request %{mergeRequestLink}"
+msgstr ""
+
+msgid "Created merge request %{mergeRequestLink} at %{projectLink}"
+msgstr ""
+
+msgid "Created on"
+msgstr ""
+
+msgid "Created on:"
+msgstr ""
+
+msgid "Creates a branch and a merge request to resolve this issue."
+msgstr ""
+
+msgid "Creates branch '%{branch_name}' and a merge request to resolve this issue."
+msgstr ""
+
+msgid "Creating"
+msgstr ""
+
+msgid "Creating epic"
+msgstr ""
+
+msgid "Creating graphs uses the data from the Prometheus server. If this takes a long time, ensure that data is available."
+msgstr ""
+
+msgid "Creation date"
+msgstr ""
+
+msgid "Credentials"
+msgstr ""
+
+msgid "CredentialsInventory|No credentials found"
+msgstr ""
+
+msgid "CredentialsInventory|Personal Access Tokens"
+msgstr ""
+
+msgid "CredentialsInventory|SSH Keys"
+msgstr ""
+
+msgid "Critical vulnerabilities present"
+msgstr ""
+
+msgid "Cron Timezone"
+msgstr ""
+
+msgid "Crossplane"
+msgstr ""
+
+msgid "Current Branch"
+msgstr ""
+
+msgid "Current Plan"
+msgstr ""
+
+msgid "Current Project"
+msgstr ""
+
+msgid "Current node"
+msgstr ""
+
+msgid "Current node must be the primary node or you will be locking yourself out"
+msgstr ""
+
+msgid "Current password"
+msgstr ""
+
+msgid "Current vulnerabilities count"
+msgstr ""
+
+msgid "CurrentUser|Buy Pipeline minutes"
+msgstr ""
+
+msgid "CurrentUser|One of your groups is running out"
+msgstr ""
+
+msgid "CurrentUser|Profile"
+msgstr ""
+
+msgid "CurrentUser|Settings"
+msgstr ""
+
+msgid "CurrentUser|Start a Gold trial"
+msgstr ""
+
+msgid "CurrentUser|Upgrade"
+msgstr ""
+
+msgid "Custom Attributes"
+msgstr ""
+
+msgid "Custom CI configuration path"
+msgstr ""
+
+msgid "Custom Git clone URL for HTTP(S)"
+msgstr ""
+
+msgid "Custom hostname (for private commit emails)"
+msgstr ""
+
+msgid "Custom notification events"
+msgstr ""
+
+msgid "Custom notification levels are the same as participating levels. With custom notification levels you will also receive notifications for select events. To find out more, check out %{notification_link}."
+msgstr ""
+
+msgid "Custom project templates"
+msgstr ""
+
+msgid "Custom project templates have not been set up for groups that you are a member of. They are enabled from a group’s settings page. Contact your group’s Owner or Maintainer to setup custom project templates."
+msgstr ""
+
+msgid "Custom range"
+msgstr ""
+
+msgid "Custom range (UTC)"
+msgstr ""
+
+msgid "CustomCycleAnalytics|Add a stage"
+msgstr ""
+
+msgid "CustomCycleAnalytics|Add stage"
+msgstr ""
+
+msgid "CustomCycleAnalytics|Editing stage"
+msgstr ""
+
+msgid "CustomCycleAnalytics|Enter a name for the stage"
+msgstr ""
+
+msgid "CustomCycleAnalytics|Name"
+msgstr ""
+
+msgid "CustomCycleAnalytics|New stage"
+msgstr ""
+
+msgid "CustomCycleAnalytics|Please select a start event first"
+msgstr ""
+
+msgid "CustomCycleAnalytics|Select start event"
+msgstr ""
+
+msgid "CustomCycleAnalytics|Select stop event"
+msgstr ""
+
+msgid "CustomCycleAnalytics|Stage name already exists"
+msgstr ""
+
+msgid "CustomCycleAnalytics|Start event"
+msgstr ""
+
+msgid "CustomCycleAnalytics|Start event changed, please select a valid stop event"
+msgstr ""
+
+msgid "CustomCycleAnalytics|Start event label"
+msgstr ""
+
+msgid "CustomCycleAnalytics|Stop event"
+msgstr ""
+
+msgid "CustomCycleAnalytics|Stop event label"
+msgstr ""
+
+msgid "CustomCycleAnalytics|Update stage"
+msgstr ""
+
+msgid "Customer Portal"
+msgstr ""
+
+msgid "Customize colors"
+msgstr ""
+
+msgid "Customize how FogBugz email addresses and usernames are imported into GitLab. In the next step, you'll be able to select the projects you want to import."
+msgstr ""
+
+msgid "Customize how Google Code email addresses and usernames are imported into GitLab. In the next step, you'll be able to select the projects you want to import."
+msgstr ""
+
+msgid "Customize icon"
+msgstr ""
+
+msgid "Customize language and region related settings."
+msgstr ""
+
+msgid "Customize name"
+msgstr ""
+
+msgid "Customize your pipeline configuration, view your pipeline status and coverage report."
+msgstr ""
+
+msgid "Customize your pipeline configuration."
+msgstr ""
+
+msgid "Cycle Time"
+msgstr ""
+
+msgid "CycleAnalyticsEvent|Issue closed"
+msgstr ""
+
+msgid "CycleAnalyticsEvent|Issue created"
+msgstr ""
+
+msgid "CycleAnalyticsEvent|Issue first added to a board"
+msgstr ""
+
+msgid "CycleAnalyticsEvent|Issue first associated with a milestone"
+msgstr ""
+
+msgid "CycleAnalyticsEvent|Issue first associated with a milestone or issue first added to a board"
+msgstr ""
+
+msgid "CycleAnalyticsEvent|Issue first mentioned in a commit"
+msgstr ""
+
+msgid "CycleAnalyticsEvent|Issue label was added"
+msgstr ""
+
+msgid "CycleAnalyticsEvent|Issue label was removed"
+msgstr ""
+
+msgid "CycleAnalyticsEvent|Issue last edited"
+msgstr ""
+
+msgid "CycleAnalyticsEvent|Merge request closed"
+msgstr ""
+
+msgid "CycleAnalyticsEvent|Merge request created"
+msgstr ""
+
+msgid "CycleAnalyticsEvent|Merge request first deployed to production"
+msgstr ""
+
+msgid "CycleAnalyticsEvent|Merge request label was added"
+msgstr ""
+
+msgid "CycleAnalyticsEvent|Merge request label was removed"
+msgstr ""
+
+msgid "CycleAnalyticsEvent|Merge request last build finish time"
+msgstr ""
+
+msgid "CycleAnalyticsEvent|Merge request last build start time"
+msgstr ""
+
+msgid "CycleAnalyticsEvent|Merge request last edited"
+msgstr ""
+
+msgid "CycleAnalyticsEvent|Merge request merged"
+msgstr ""
+
+msgid "CycleAnalyticsStage|Code"
+msgstr ""
+
+msgid "CycleAnalyticsStage|Issue"
+msgstr ""
+
+msgid "CycleAnalyticsStage|Plan"
+msgstr ""
+
+msgid "CycleAnalyticsStage|Review"
+msgstr ""
+
+msgid "CycleAnalyticsStage|Staging"
+msgstr ""
+
+msgid "CycleAnalyticsStage|Test"
+msgstr ""
+
+msgid "CycleAnalyticsStage|Total"
+msgstr ""
+
+msgid "CycleAnalyticsStage|is not available for the selected group"
+msgstr ""
+
+msgid "CycleAnalyticsStage|should be under a group"
+msgstr ""
+
+msgid "CycleAnalytics|%{selectedLabelsCount} selected (%{maxLabels} max)"
+msgstr ""
+
+msgid "CycleAnalytics|%{stageCount} stages selected"
+msgstr ""
+
+msgid "CycleAnalytics|All stages"
+msgstr ""
+
+msgid "CycleAnalytics|Date"
+msgstr ""
+
+msgid "CycleAnalytics|Days to completion"
+msgstr ""
+
+msgid "CycleAnalytics|Display chart filters"
+msgstr ""
+
+msgid "CycleAnalytics|No stages selected"
+msgstr ""
+
+msgid "CycleAnalytics|Number of tasks"
+msgstr ""
+
+msgid "CycleAnalytics|Only %{maxLabels} labels can be selected at this time"
+msgstr ""
+
+msgid "CycleAnalytics|Project selected"
+msgid_plural "CycleAnalytics|%d projects selected"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "CycleAnalytics|Select labels"
+msgstr ""
+
+msgid "CycleAnalytics|Show"
+msgstr ""
+
+msgid "CycleAnalytics|Showing %{subjectFilterText} and %{selectedLabelsCount} labels"
+msgstr ""
+
+msgid "CycleAnalytics|Showing data for group '%{groupName}' and %{selectedProjectCount} projects from %{startDate} to %{endDate}"
+msgstr ""
+
+msgid "CycleAnalytics|Showing data for group '%{groupName}' from %{startDate} to %{endDate}"
+msgstr ""
+
+msgid "CycleAnalytics|Stages"
+msgstr ""
+
+msgid "CycleAnalytics|Tasks by type"
+msgstr ""
+
+msgid "CycleAnalytics|The given date range is larger than 180 days"
+msgstr ""
+
+msgid "CycleAnalytics|Total days to completion"
+msgstr ""
+
+msgid "CycleAnalytics|Type of work"
+msgstr ""
+
+msgid "CycleAnalytics|group dropdown filter"
+msgstr ""
+
+msgid "CycleAnalytics|not allowed for the given start event"
+msgstr ""
+
+msgid "CycleAnalytics|project dropdown filter"
+msgstr ""
+
+msgid "CycleAnalytics|stage dropdown"
+msgstr ""
+
+msgid "DAG"
+msgstr ""
+
+msgid "DAG visualization requires at least 3 dependent jobs."
+msgstr ""
+
+msgid "DNS"
+msgstr ""
+
+msgid "Dashboard"
+msgstr ""
+
+msgid "Dashboard uid not found"
+msgstr ""
+
+msgid "DashboardProjects|All"
+msgstr ""
+
+msgid "DashboardProjects|Personal"
+msgstr ""
+
+msgid "DashboardProjects|Trending"
+msgstr ""
+
+msgid "Dashboards"
+msgstr ""
+
+msgid "Dashboard|%{firstProject} and %{secondProject}"
+msgstr ""
+
+msgid "Dashboard|%{firstProject}, %{rest}, and %{secondProject}"
+msgstr ""
+
+msgid "Dashboard|Unable to add %{invalidProjects}. This dashboard is available for public projects, and private projects in groups with a Silver plan."
+msgstr ""
+
+msgid "Data is still calculating..."
+msgstr ""
+
+msgid "Datasource name not found"
+msgstr ""
+
+msgid "Date"
+msgstr ""
+
+msgid "Date picker"
+msgstr ""
+
+msgid "Date range cannot exceed %{maxDateRange} days."
+msgstr ""
+
+msgid "Day of month"
+msgstr ""
+
+msgid "DayTitle|F"
+msgstr ""
+
+msgid "DayTitle|M"
+msgstr ""
+
+msgid "DayTitle|S"
+msgstr ""
+
+msgid "DayTitle|W"
+msgstr ""
+
+msgid "Days"
+msgstr ""
+
+msgid "Days to merge"
+msgstr ""
+
+msgid "Debug"
+msgstr ""
+
+msgid "Dec"
+msgstr ""
+
+msgid "December"
+msgstr ""
+
+msgid "Decline"
+msgstr ""
+
+msgid "Decline and sign out"
+msgstr ""
+
+msgid "Default Branch"
+msgstr ""
+
+msgid "Default CI configuration path"
+msgstr ""
+
+msgid "Default artifacts expiration"
+msgstr ""
+
+msgid "Default branch"
+msgstr ""
+
+msgid "Default branch and protected branches"
+msgstr ""
+
+msgid "Default classification label"
+msgstr ""
+
+msgid "Default dashboard"
+msgstr ""
+
+msgid "Default deletion adjourned period"
+msgstr ""
+
+msgid "Default description template for issues"
+msgstr ""
+
+msgid "Default description template for merge requests"
+msgstr ""
+
+msgid "Default first day of the week"
+msgstr ""
+
+msgid "Default first day of the week in calendars and date pickers."
+msgstr ""
+
+msgid "Default issue template"
+msgstr ""
+
+msgid "Default project deletion protection"
+msgstr ""
+
+msgid "Default projects limit"
+msgstr ""
+
+msgid "Default stages"
+msgstr ""
+
+msgid "Default: Directly import the Google Code email address or username"
+msgstr ""
+
+msgid "Default: Map a FogBugz account ID to a full name"
+msgstr ""
+
+msgid "Define a custom pattern with cron syntax"
+msgstr ""
+
+msgid "Define custom rules for what constitutes spam, independent of Akismet"
+msgstr ""
+
+msgid "Define environments in the deploy stage(s) in <code>.gitlab-ci.yml</code> to track deployments here."
+msgstr ""
+
+msgid "DelayedJobs|Are you sure you want to run %{jobName} immediately? Otherwise this job will run automatically after it's timer finishes."
+msgstr ""
+
+msgid "DelayedJobs|Are you sure you want to run %{job_name} immediately? This job will run automatically after it's timer finishes."
+msgstr ""
+
+msgid "DelayedJobs|Start now"
+msgstr ""
+
+msgid "DelayedJobs|Unschedule"
+msgstr ""
+
+msgid "DelayedJobs|delayed"
+msgstr ""
+
+msgid "Delete"
+msgstr ""
+
+msgid "Delete Comment"
+msgstr ""
+
+msgid "Delete Package"
+msgstr ""
+
+msgid "Delete Snippet"
+msgstr ""
+
+msgid "Delete artifacts"
+msgstr ""
+
+msgid "Delete board"
+msgstr ""
+
+msgid "Delete comment"
+msgstr ""
+
+msgid "Delete domain"
+msgstr ""
+
+msgid "Delete label"
+msgstr ""
+
+msgid "Delete label: %{label_name} ?"
+msgstr ""
+
+msgid "Delete license"
+msgstr ""
+
+msgid "Delete list"
+msgstr ""
+
+msgid "Delete pipeline"
+msgstr ""
+
+msgid "Delete project"
+msgstr ""
+
+msgid "Delete serverless domain?"
+msgstr ""
+
+msgid "Delete snippet"
+msgstr ""
+
+msgid "Delete snippet?"
+msgstr ""
+
+msgid "Delete source branch"
+msgstr ""
+
+msgid "Delete this attachment"
+msgstr ""
+
+msgid "Delete user list"
+msgstr ""
+
+msgid "Delete variable"
+msgstr ""
+
+msgid "DeleteProject|Failed to remove project repository. Please try again or contact administrator."
+msgstr ""
+
+msgid "DeleteProject|Failed to remove project snippets. Please try again or contact administrator."
+msgstr ""
+
+msgid "DeleteProject|Failed to remove some tags in project container registry. Please try again or contact administrator."
+msgstr ""
+
+msgid "DeleteProject|Failed to remove wiki repository. Please try again or contact administrator."
+msgstr ""
+
+msgid "DeleteProject|Failed to restore project repository. Please contact the administrator."
+msgstr ""
+
+msgid "DeleteProject|Failed to restore wiki repository. Please contact the administrator."
+msgstr ""
+
+msgid "Deleted"
+msgstr ""
+
+msgid "Deleted chat nickname: %{chat_name}!"
+msgstr ""
+
+msgid "Deleted in this version"
+msgstr ""
+
+msgid "Deleting"
+msgstr ""
+
+msgid "Deleting the license failed."
+msgstr ""
+
+msgid "Deleting the license failed. The license was not found."
+msgstr ""
+
+msgid "Deleting the license failed. You are not permitted to perform this action."
+msgstr ""
+
+msgid "Deletion pending. This project will be removed on %{date}. Repository and other project resources are read-only."
+msgstr ""
+
+msgid "Denied authorization of chat nickname %{user_name}."
+msgstr ""
+
+msgid "Deny"
+msgstr ""
+
+msgid "Dependencies"
+msgstr ""
+
+msgid "Dependencies help page link"
+msgstr ""
+
+msgid "Dependencies|%d additional vulnerability not shown"
+msgid_plural "Dependencies|%d additional vulnerabilities not shown"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Dependencies|%d vulnerability detected"
+msgid_plural "Dependencies|%d vulnerabilities detected"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Dependencies|%{remainingLicensesCount} more"
+msgstr ""
+
+msgid "Dependencies|All"
+msgstr ""
+
+msgid "Dependencies|Based on the %{linkStart}latest successful%{linkEnd} scan"
+msgstr ""
+
+msgid "Dependencies|Component"
+msgstr ""
+
+msgid "Dependencies|Component name"
+msgstr ""
+
+msgid "Dependencies|Export as JSON"
+msgstr ""
+
+msgid "Dependencies|Job failed to generate the dependency list"
+msgstr ""
+
+msgid "Dependencies|License"
+msgstr ""
+
+msgid "Dependencies|Location"
+msgstr ""
+
+msgid "Dependencies|Packager"
+msgstr ""
+
+msgid "Dependencies|The %{codeStartTag}dependency_scanning%{codeEndTag} job has failed and cannot generate the list. Please ensure the job is running properly and run the pipeline again."
+msgstr ""
+
+msgid "Dependencies|Toggle vulnerability list"
+msgstr ""
+
+msgid "Dependencies|Unsupported file(s) detected"
+msgstr ""
+
+msgid "Dependencies|Vulnerable components"
+msgstr ""
+
+msgid "Dependency List"
+msgstr ""
+
+msgid "Dependency List has no entries"
+msgstr ""
+
+msgid "Dependency Proxy"
+msgstr ""
+
+msgid "Dependency Scanning"
+msgstr ""
+
+msgid "Dependency proxy"
+msgstr ""
+
+msgid "Dependency proxy URL"
+msgstr ""
+
+msgid "Dependency proxy feature is limited to public groups for now."
+msgstr ""
+
+msgid "DependencyProxy|Toggle Dependency Proxy"
+msgstr ""
+
+msgid "Depends on %d merge request being merged"
+msgid_plural "Depends on %d merge requests being merged"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Depends on <strong>%d closed</strong> merge request."
+msgid_plural "Depends on <strong>%d closed</strong> merge requests."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Deploy"
+msgid_plural "Deploys"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Deploy Keys"
+msgstr ""
+
+msgid "Deploy key was successfully updated."
+msgstr ""
+
+msgid "Deploy keys allow read-only or read-write (if enabled) access to your repository. Deploy keys can be used for CI, staging or production servers. You can create a deploy key or add an existing one."
+msgstr ""
+
+msgid "Deploy progress not found. To see pods, ensure your environment matches %{linkStart}deploy board criteria%{linkEnd}."
+msgstr ""
+
+msgid "Deploy to..."
+msgstr ""
+
+msgid "DeployBoard|Matching on the %{appLabel} label has been removed for deploy boards. To see all instances on your board, you must update your chart and redeploy."
+msgstr ""
+
+msgid "DeployKeys|+%{count} others"
+msgstr ""
+
+msgid "DeployKeys|Current project"
+msgstr ""
+
+msgid "DeployKeys|Deploy key"
+msgstr ""
+
+msgid "DeployKeys|Enabled deploy keys"
+msgstr ""
+
+msgid "DeployKeys|Error enabling deploy key"
+msgstr ""
+
+msgid "DeployKeys|Error getting deploy keys"
+msgstr ""
+
+msgid "DeployKeys|Error removing deploy key"
+msgstr ""
+
+msgid "DeployKeys|Expand %{count} other projects"
+msgstr ""
+
+msgid "DeployKeys|Loading deploy keys"
+msgstr ""
+
+msgid "DeployKeys|No deploy keys found. Create one with the form above."
+msgstr ""
+
+msgid "DeployKeys|Privately accessible deploy keys"
+msgstr ""
+
+msgid "DeployKeys|Project usage"
+msgstr ""
+
+msgid "DeployKeys|Publicly accessible deploy keys"
+msgstr ""
+
+msgid "DeployKeys|Read access only"
+msgstr ""
+
+msgid "DeployKeys|Write access allowed"
+msgstr ""
+
+msgid "DeployKeys|You are going to remove this deploy key. Are you sure?"
+msgstr ""
+
+msgid "DeployTokens|Active Deploy Tokens (%{active_tokens})"
+msgstr ""
+
+msgid "DeployTokens|Add a deploy token"
+msgstr ""
+
+msgid "DeployTokens|Allows read access to the package registry"
+msgstr ""
+
+msgid "DeployTokens|Allows read-only access to the registry images"
+msgstr ""
+
+msgid "DeployTokens|Allows read-only access to the repository"
+msgstr ""
+
+msgid "DeployTokens|Allows write access to the package registry"
+msgstr ""
+
+msgid "DeployTokens|Allows write access to the registry images"
+msgstr ""
+
+msgid "DeployTokens|Copy deploy token"
+msgstr ""
+
+msgid "DeployTokens|Copy username"
+msgstr ""
+
+msgid "DeployTokens|Create deploy token"
+msgstr ""
+
+msgid "DeployTokens|Created"
+msgstr ""
+
+msgid "DeployTokens|Default format is \"gitlab+deploy-token-{n}\". Enter custom username if you want to change it."
+msgstr ""
+
+msgid "DeployTokens|Deploy Tokens"
+msgstr ""
+
+msgid "DeployTokens|Deploy tokens allow access to packages, your repository, and registry images."
+msgstr ""
+
+msgid "DeployTokens|Expires"
+msgstr ""
+
+msgid "DeployTokens|Group deploy tokens allow access to the packages, repositories, and registry images within the group."
+msgstr ""
+
+msgid "DeployTokens|Name"
+msgstr ""
+
+msgid "DeployTokens|Pick a name for the application, and we'll give you a unique deploy token."
+msgstr ""
+
+msgid "DeployTokens|Revoke"
+msgstr ""
+
+msgid "DeployTokens|Revoke %{b_start}%{name}%{b_end}?"
+msgstr ""
+
+msgid "DeployTokens|Revoke %{name}"
+msgstr ""
+
+msgid "DeployTokens|Scopes"
+msgstr ""
+
+msgid "DeployTokens|This %{entity_type} has no active Deploy Tokens."
+msgstr ""
+
+msgid "DeployTokens|This action cannot be undone."
+msgstr ""
+
+msgid "DeployTokens|Use this token as a password. Make sure you save it - you won't be able to access it again."
+msgstr ""
+
+msgid "DeployTokens|Use this username as a login."
+msgstr ""
+
+msgid "DeployTokens|Username"
+msgstr ""
+
+msgid "DeployTokens|You are about to revoke %{b_start}%{name}%{b_end}."
+msgstr ""
+
+msgid "DeployTokens|Your New Deploy Token"
+msgstr ""
+
+msgid "DeployTokens|Your new group deploy token has been created."
+msgstr ""
+
+msgid "DeployTokens|Your new project deploy token has been created."
+msgstr ""
+
+msgid "Deployed"
+msgstr ""
+
+msgid "Deployed to"
+msgstr ""
+
+msgid "Deploying to"
+msgstr ""
+
+msgid "Deploying to AWS is easy with GitLab"
+msgstr ""
+
+msgid "Deployment Frequency"
+msgstr ""
+
+msgid "Deployment|API"
+msgstr ""
+
+msgid "Deployment|This deployment was created using the API"
+msgstr ""
+
+msgid "Deployment|canceled"
+msgstr ""
+
+msgid "Deployment|created"
+msgstr ""
+
+msgid "Deployment|failed"
+msgstr ""
+
+msgid "Deployment|running"
+msgstr ""
+
+msgid "Deployment|success"
+msgstr ""
+
+msgid "Deprioritize label"
+msgstr ""
+
+msgid "Descending"
+msgstr ""
+
+msgid "Describe the goal of the changes and what reviewers should be aware of."
+msgstr ""
+
+msgid "Describe the requirement here"
+msgstr ""
+
+msgid "Description"
+msgstr ""
+
+msgid "Description parsed with %{link_start}GitLab Flavored Markdown%{link_end}"
+msgstr ""
+
+msgid "Description templates allow you to define context-specific templates for issue and merge request description fields for your project."
+msgstr ""
+
+msgid "Description:"
+msgstr ""
+
+msgid "Descriptive label"
+msgstr ""
+
+msgid "Deselect all"
+msgstr ""
+
+msgid "Design Management files and data"
+msgstr ""
+
+msgid "DesignManagement|%{current_design} of %{designs_count}"
+msgstr ""
+
+msgid "DesignManagement|%{filename} did not change."
+msgstr ""
+
+msgid "DesignManagement|Adding a design with the same filename replaces the file in a new version."
+msgstr ""
+
+msgid "DesignManagement|Are you sure you want to cancel changes to this comment?"
+msgstr ""
+
+msgid "DesignManagement|Are you sure you want to cancel creating this comment?"
+msgstr ""
+
+msgid "DesignManagement|Are you sure you want to delete the selected designs?"
+msgstr ""
+
+msgid "DesignManagement|Cancel changes"
+msgstr ""
+
+msgid "DesignManagement|Cancel comment confirmation"
+msgstr ""
+
+msgid "DesignManagement|Cancel comment update confirmation"
+msgstr ""
+
+msgid "DesignManagement|Click the image where you'd like to start a new discussion"
+msgstr ""
+
+msgid "DesignManagement|Comment"
+msgstr ""
+
+msgid "DesignManagement|Comments you resolve can be viewed and unresolved by going to the \"Resolved Comments\" section below"
+msgstr ""
+
+msgid "DesignManagement|Could not add a new comment. Please try again."
+msgstr ""
+
+msgid "DesignManagement|Could not create new discussion. Please try again."
+msgstr ""
+
+msgid "DesignManagement|Could not update discussion. Please try again."
+msgstr ""
+
+msgid "DesignManagement|Could not update note. Please try again."
+msgstr ""
+
+msgid "DesignManagement|Delete"
+msgstr ""
+
+msgid "DesignManagement|Delete designs confirmation"
+msgstr ""
+
+msgid "DesignManagement|Delete selected"
+msgstr ""
+
+msgid "DesignManagement|Deselect all"
+msgstr ""
+
+msgid "DesignManagement|Discard comment"
+msgstr ""
+
+msgid "DesignManagement|Error uploading a new design. Please try again."
+msgstr ""
+
+msgid "DesignManagement|Go back to designs"
+msgstr ""
+
+msgid "DesignManagement|Go to next design"
+msgstr ""
+
+msgid "DesignManagement|Go to previous design"
+msgstr ""
+
+msgid "DesignManagement|Keep changes"
+msgstr ""
+
+msgid "DesignManagement|Keep comment"
+msgstr ""
+
+msgid "DesignManagement|Learn more about resolving comments"
+msgstr ""
+
+msgid "DesignManagement|Requested design version does not exist. Showing latest version instead"
+msgstr ""
+
+msgid "DesignManagement|Resolve thread"
+msgstr ""
+
+msgid "DesignManagement|Resolved Comments"
+msgstr ""
+
+msgid "DesignManagement|Save comment"
+msgstr ""
+
+msgid "DesignManagement|Select all"
+msgstr ""
+
+msgid "DesignManagement|The maximum number of designs allowed to be uploaded is %{upload_limit}. Please try again."
+msgstr ""
+
+msgid "DesignManagement|To enable design management, you'll need to %{requirements_link_start}meet the requirements%{requirements_link_end}. If you need help, reach out to our %{support_link_start}support team%{support_link_end} for assistance."
+msgstr ""
+
+msgid "DesignManagement|Unresolve thread"
+msgstr ""
+
+msgid "DesignManagement|Upload designs"
+msgstr ""
+
+msgid "DesignManagement|Upload skipped."
+msgstr ""
+
+msgid "DesignManagement|and %{moreCount} more."
+msgstr ""
+
+msgid "Designs"
+msgstr ""
+
+msgid "Destroy"
+msgstr ""
+
+msgid "Detail"
+msgstr ""
+
+msgid "Details"
+msgstr ""
+
+msgid "Details (default)"
+msgstr ""
+
+msgid "Detect host keys"
+msgstr ""
+
+msgid "DevOps Score"
+msgstr ""
+
+msgid "Diff content limits"
+msgstr ""
+
+msgid "Diff limits"
+msgstr ""
+
+msgid "Difference between start date and now"
+msgstr ""
+
+msgid "DiffsCompareBaseBranch|(HEAD)"
+msgstr ""
+
+msgid "DiffsCompareBaseBranch|(base)"
+msgstr ""
+
+msgid "Diffs|No file name available"
+msgstr ""
+
+msgid "Diffs|Show unchanged lines"
+msgstr ""
+
+msgid "Diffs|Something went wrong while fetching diff lines."
+msgstr ""
+
+msgid "Direction"
+msgstr ""
+
+msgid "Directory name"
+msgstr ""
+
+msgid "Disable"
+msgstr ""
+
+msgid "Disable for this project"
+msgstr ""
+
+msgid "Disable group Runners"
+msgstr ""
+
+msgid "Disable public access to Pages sites"
+msgstr ""
+
+msgid "Disable shared Runners"
+msgstr ""
+
+msgid "Disable two-factor authentication"
+msgstr ""
+
+msgid "Disabled"
+msgstr ""
+
+msgid "Disabled mirrors can only be enabled by instance owners. It is recommended that you delete them."
+msgstr ""
+
+msgid "Discard all changes"
+msgstr ""
+
+msgid "Discard all changes?"
+msgstr ""
+
+msgid "Discard changes"
+msgstr ""
+
+msgid "Discard changes to %{path}?"
+msgstr ""
+
+msgid "Discard draft"
+msgstr ""
+
+msgid "Discard review"
+msgstr ""
+
+msgid "DiscordService|Discord Notifications"
+msgstr ""
+
+msgid "DiscordService|Receive event notifications in Discord"
+msgstr ""
+
+msgid "Discover GitLab Geo"
+msgstr ""
+
+msgid "Discover projects, groups and snippets. Share your projects with others"
+msgstr ""
+
+msgid "Discover|Check your application for security vulnerabilities that may lead to unauthorized access, data leaks, and denial of services."
+msgstr ""
+
+msgid "Discover|For code that's already live in production, our dashboards give you an easy way to prioritize any issues that are found, empowering your team to ship quickly and securely."
+msgstr ""
+
+msgid "Discover|GitLab will perform static and dynamic tests on the code of your application, looking for known flaws and report them in the merge request so you can fix them before merging."
+msgstr ""
+
+msgid "Discover|Give feedback for this page"
+msgstr ""
+
+msgid "Discover|Security capabilities, integrated into your development lifecycle"
+msgstr ""
+
+msgid "Discover|See the other features of the %{linkStart}gold plan%{linkEnd}"
+msgstr ""
+
+msgid "Discover|Start a free trial"
+msgstr ""
+
+msgid "Discover|Upgrade now"
+msgstr ""
+
+msgid "Discuss a specific suggestion or question"
+msgstr ""
+
+msgid "Discuss a specific suggestion or question that needs to be resolved"
+msgstr ""
+
+msgid "Discuss a specific suggestion or question that needs to be resolved."
+msgstr ""
+
+msgid "Discuss a specific suggestion or question."
+msgstr ""
+
+msgid "Discussion"
+msgstr ""
+
+msgid "Discussion to reply to cannot be found"
+msgstr ""
+
+msgid "Disk Usage"
+msgstr ""
+
+msgid "Dismiss"
+msgstr ""
+
+msgid "Dismiss %d selected vulnerability as"
+msgid_plural "Dismiss %d selected vulnerabilities as"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Dismiss DevOps Score introduction"
+msgstr ""
+
+msgid "Dismiss Merge Request promotion"
+msgstr ""
+
+msgid "Dismiss Selected"
+msgstr ""
+
+msgid "Dismiss Value Stream Analytics introduction box"
+msgstr ""
+
+msgid "Dismiss trial promotion"
+msgstr ""
+
+msgid "Dismissable"
+msgstr ""
+
+msgid "Dismissed"
+msgstr ""
+
+msgid "Dismissed at %{projectLink}"
+msgstr ""
+
+msgid "Dismissed on pipeline %{pipelineLink}"
+msgstr ""
+
+msgid "Dismissed on pipeline %{pipelineLink} at %{projectLink}"
+msgstr ""
+
+msgid "Display alerts from all your monitoring tools directly within GitLab."
+msgstr ""
+
+msgid "Display name"
+msgstr ""
+
+msgid "Display rendered file"
+msgstr ""
+
+msgid "Display source"
+msgstr ""
+
+msgid "Do not display offers from third parties within GitLab"
+msgstr ""
+
+msgid "Do you want to customize how Google Code email addresses and usernames are imported into GitLab?"
+msgstr ""
+
+msgid "Dockerfile"
+msgstr ""
+
+msgid "Documentation"
+msgstr ""
+
+msgid "Documentation for popular identity providers"
+msgstr ""
+
+msgid "Doing"
+msgstr ""
+
+msgid "Domain"
+msgstr ""
+
+msgid "Domain cannot be deleted while associated to one or more clusters."
+msgstr ""
+
+msgid "Domain verification is an essential security measure for public GitLab sites. Users are required to demonstrate they control a domain before it is enabled"
+msgstr ""
+
+msgid "Domain was successfully created."
+msgstr ""
+
+msgid "Domain was successfully deleted."
+msgstr ""
+
+msgid "Domain was successfully updated."
+msgstr ""
+
+msgid "Don't have an account yet?"
+msgstr ""
+
+msgid "Don't include description in commit message"
+msgstr ""
+
+msgid "Don't paste the private part of the GPG key. Paste the public part which begins with '-----BEGIN PGP PUBLIC KEY BLOCK-----'."
+msgstr ""
+
+msgid "Don't show again"
+msgstr ""
+
+msgid "Done"
+msgstr ""
+
+msgid "Download"
+msgstr ""
+
+msgid "Download %{format}"
+msgstr ""
+
+msgid "Download %{format}:"
+msgstr ""
+
+msgid "Download CSV"
+msgstr ""
+
+msgid "Download artifacts"
+msgstr ""
+
+msgid "Download as"
+msgstr ""
+
+msgid "Download asset"
+msgstr ""
+
+msgid "Download codes"
+msgstr ""
+
+msgid "Download evidence JSON"
+msgstr ""
+
+msgid "Download export"
+msgstr ""
+
+msgid "Download image"
+msgstr ""
+
+msgid "Download license"
+msgstr ""
+
+msgid "Download raw data (.csv)"
+msgstr ""
+
+msgid "Download source code"
+msgstr ""
+
+msgid "Download this directory"
+msgstr ""
+
+msgid "DownloadCommit|Email Patches"
+msgstr ""
+
+msgid "DownloadCommit|Plain Diff"
+msgstr ""
+
+msgid "DownloadSource|Download"
+msgstr ""
+
+msgid "Downstream"
+msgstr ""
+
+msgid "Downvotes"
+msgstr ""
+
+msgid "Drop your designs to start your upload."
+msgstr ""
+
+msgid "Due Date"
+msgstr ""
+
+msgid "Due date"
+msgstr ""
+
+msgid "Duration"
+msgstr ""
+
+msgid "Duration for the last 30 commits"
+msgstr ""
+
+msgid "During this process, you’ll be asked for URLs from GitLab’s side. Use the URLs shown below."
+msgstr ""
+
+msgid "Dynamic Application Security Testing (DAST)"
+msgstr ""
+
+msgid "Each Runner can be in one of the following states and/or belong to one of the following types:"
+msgstr ""
+
+msgid "Each Runner can be in one of the following states:"
+msgstr ""
+
+msgid "Edit"
+msgstr ""
+
+msgid "Edit %{issuable}"
+msgstr ""
+
+msgid "Edit %{name}"
+msgstr ""
+
+msgid "Edit Comment"
+msgstr ""
+
+msgid "Edit Deploy Key"
+msgstr ""
+
+msgid "Edit Geo Node"
+msgstr ""
+
+msgid "Edit Group Hook"
+msgstr ""
+
+msgid "Edit Label"
+msgstr ""
+
+msgid "Edit Milestone"
+msgstr ""
+
+msgid "Edit Password"
+msgstr ""
+
+msgid "Edit Pipeline Schedule %{id}"
+msgstr ""
+
+msgid "Edit Release"
+msgstr ""
+
+msgid "Edit Slack integration"
+msgstr ""
+
+msgid "Edit Snippet"
+msgstr ""
+
+msgid "Edit System Hook"
+msgstr ""
+
+msgid "Edit application"
+msgstr ""
+
+msgid "Edit board"
+msgstr ""
+
+msgid "Edit comment"
+msgstr ""
+
+msgid "Edit dashboard"
+msgstr ""
+
+msgid "Edit description"
+msgstr ""
+
+msgid "Edit environment"
+msgstr ""
+
+msgid "Edit file"
+msgstr ""
+
+msgid "Edit files in the editor and commit changes here"
+msgstr ""
+
+msgid "Edit group: %{group_name}"
+msgstr ""
+
+msgid "Edit identity for %{user_name}"
+msgstr ""
+
+msgid "Edit issues"
+msgstr ""
+
+msgid "Edit iteration"
+msgstr ""
+
+msgid "Edit public deploy key"
+msgstr ""
+
+msgid "Edit stage"
+msgstr ""
+
+msgid "Edit this release"
+msgstr ""
+
+msgid "Edit wiki page"
+msgstr ""
+
+msgid "Edit your most recent comment in a thread (from an empty textarea)"
+msgstr ""
+
+msgid "Edited %{timeago}"
+msgstr ""
+
+msgid "Editing"
+msgstr ""
+
+msgid "Elasticsearch"
+msgstr ""
+
+msgid "Elasticsearch AWS IAM credentials"
+msgstr ""
+
+msgid "Elasticsearch indexing restrictions"
+msgstr ""
+
+msgid "Elasticsearch indexing started"
+msgstr ""
+
+msgid "Elasticsearch integration. Elasticsearch AWS IAM."
+msgstr ""
+
+msgid "Elasticsearch returned status code: %{status_code}"
+msgstr ""
+
+msgid "Elastic|None. Select namespaces to index."
+msgstr ""
+
+msgid "Elastic|None. Select projects to index."
+msgstr ""
+
+msgid "Email"
+msgstr ""
+
+msgid "Email Notification"
+msgstr ""
+
+msgid "Email address"
+msgstr ""
+
+msgid "Email could not be sent"
+msgstr ""
+
+msgid "Email display name"
+msgstr ""
+
+msgid "Email not verified. Please verify your email in Salesforce."
+msgstr ""
+
+msgid "Email notification for unknown sign-ins"
+msgstr ""
+
+msgid "Email patch"
+msgstr ""
+
+msgid "Email restrictions"
+msgstr ""
+
+msgid "Email restrictions for sign-ups"
+msgstr ""
+
+msgid "Email sent"
+msgstr ""
+
+msgid "Email the pipelines status to a list of recipients."
+msgstr ""
+
+msgid "EmailError|It appears that the email is blank. Make sure your reply is at the top of the email, we can't process inline replies."
+msgstr ""
+
+msgid "EmailError|The thread you are replying to no longer exists, perhaps it was deleted? If you believe this is in error, contact a staff member."
+msgstr ""
+
+msgid "EmailError|We couldn't figure out what the email is for. Please create your issue or comment through the web interface."
+msgstr ""
+
+msgid "EmailError|We couldn't figure out what the email is in reply to. Please create your comment through the web interface."
+msgstr ""
+
+msgid "EmailError|We couldn't figure out what user corresponds to the email. Please create your comment through the web interface."
+msgstr ""
+
+msgid "EmailError|We couldn't find the project. Please check if there's any typo."
+msgstr ""
+
+msgid "EmailError|You are not allowed to perform this action. If you believe this is in error, contact a staff member."
+msgstr ""
+
+msgid "EmailError|Your account has been blocked. If you believe this is in error, contact a staff member."
+msgstr ""
+
+msgid "EmailToken|reset it"
+msgstr ""
+
+msgid "EmailToken|resetting..."
+msgstr ""
+
+msgid "Emails"
+msgstr ""
+
+msgid "Emails sent from Service Desk will have this name"
+msgstr ""
+
+msgid "Emails separated by comma"
+msgstr ""
+
+msgid "EmailsOnPushService|Disable code diffs"
+msgstr ""
+
+msgid "EmailsOnPushService|Don't include possibly sensitive code diffs in notification body."
+msgstr ""
+
+msgid "EmailsOnPushService|Email the commits and diff of each push to a list of recipients."
+msgstr ""
+
+msgid "EmailsOnPushService|Emails on push"
+msgstr ""
+
+msgid "EmailsOnPushService|Emails separated by whitespace"
+msgstr ""
+
+msgid "EmailsOnPushService|Send from committer"
+msgstr ""
+
+msgid "EmailsOnPushService|Send notifications from the committer's email address if the domain is part of the domain GitLab is running on (e.g. %{domains})."
+msgstr ""
+
+msgid "Embed"
+msgstr ""
+
+msgid "Empty file"
+msgstr ""
+
+msgid "Enable"
+msgstr ""
+
+msgid "Enable Auto DevOps"
+msgstr ""
+
+msgid "Enable HTML emails"
+msgstr ""
+
+msgid "Enable Incident Management inbound alert limit"
+msgstr ""
+
+msgid "Enable PlantUML"
+msgstr ""
+
+msgid "Enable Pseudonymizer data collection"
+msgstr ""
+
+msgid "Enable SAML authentication for this group"
+msgstr ""
+
+msgid "Enable Seat Link"
+msgstr ""
+
+msgid "Enable Spam Check via external API endpoint"
+msgstr ""
+
+msgid "Enable access to Grafana"
+msgstr ""
+
+msgid "Enable access to the Performance Bar for a given group."
+msgstr ""
+
+msgid "Enable and configure Grafana."
+msgstr ""
+
+msgid "Enable and configure Prometheus metrics."
+msgstr ""
+
+msgid "Enable classification control using an external service"
+msgstr ""
+
+msgid "Enable container expiration and retention policies for projects created earlier than GitLab 12.7."
+msgstr ""
+
+msgid "Enable email restrictions for sign ups"
+msgstr ""
+
+msgid "Enable error tracking"
+msgstr ""
+
+msgid "Enable feature to choose access level"
+msgstr ""
+
+msgid "Enable for this project"
+msgstr ""
+
+msgid "Enable group Runners"
+msgstr ""
+
+msgid "Enable header and footer in emails"
+msgstr ""
+
+msgid "Enable integration"
+msgstr ""
+
+msgid "Enable maintenance mode"
+msgstr ""
+
+msgid "Enable mirror configuration"
+msgstr ""
+
+msgid "Enable or disable Seat Link."
+msgstr ""
+
+msgid "Enable or disable keyboard shortcuts"
+msgstr ""
+
+msgid "Enable or disable the Pseudonymizer data collection."
+msgstr ""
+
+msgid "Enable or disable version check and usage ping."
+msgstr ""
+
+msgid "Enable protected paths rate limit"
+msgstr ""
+
+msgid "Enable proxy"
+msgstr ""
+
+msgid "Enable reCAPTCHA or Akismet and set IP limits. For reCAPTCHA, we currently only support %{recaptcha_v2_link_start}v2%{recaptcha_v2_link_end}"
+msgstr ""
+
+msgid "Enable shared Runners"
+msgstr ""
+
+msgid "Enable snowplow tracking"
+msgstr ""
+
+msgid "Enable two-factor authentication"
+msgstr ""
+
+msgid "Enable usage ping"
+msgstr ""
+
+msgid "Enable usage ping to get an overview of how you are using GitLab from a feature perspective."
+msgstr ""
+
+msgid "Enable/disable your service desk. %{link_start}Learn more about service desk%{link_end}."
+msgstr ""
+
+msgid "EnableReviewApp|%{stepStart}Step 1%{stepEnd}. Ensure you have Kubernetes set up and have a base domain for your %{linkStart}cluster%{linkEnd}."
+msgstr ""
+
+msgid "EnableReviewApp|%{stepStart}Step 2%{stepEnd}. Copy the following snippet:"
+msgstr ""
+
+msgid "EnableReviewApp|%{stepStart}Step 3%{stepEnd}. Add it to the project %{linkStart}gitlab-ci.yml%{linkEnd} file."
+msgstr ""
+
+msgid "EnableReviewApp|Close"
+msgstr ""
+
+msgid "EnableReviewApp|Copy snippet text"
+msgstr ""
+
+msgid "Enabled"
+msgstr ""
+
+msgid "Enabled Git access protocols"
+msgstr ""
+
+msgid "Enabled sources for code import during project creation. OmniAuth must be configured for GitHub"
+msgstr ""
+
+msgid "Enabling this will only make licensed EE features available to projects if the project namespace's plan includes the feature or if the project is public."
+msgstr ""
+
+msgid "Encountered an error while rendering: %{err}"
+msgstr ""
+
+msgid "End date"
+msgstr ""
+
+msgid "Ends at (UTC)"
+msgstr ""
+
+msgid "Enforce DNS rebinding attack protection"
+msgstr ""
+
+msgid "Enforce personal access token expiration"
+msgstr ""
+
+msgid "Ensure connectivity is available from the GitLab server to the Prometheus server"
+msgstr ""
+
+msgid "Ensure your %{linkStart}environment is part of the deploy stage%{linkEnd} of your CI pipeline to track deployments to your cluster."
+msgstr ""
+
+msgid "Enter 2FA for Admin Mode"
+msgstr ""
+
+msgid "Enter Admin Mode"
+msgstr ""
+
+msgid "Enter IP address range"
+msgstr ""
+
+msgid "Enter a number"
+msgstr ""
+
+msgid "Enter a whole number between 0 and 100"
+msgstr ""
+
+msgid "Enter at least three characters to search"
+msgstr ""
+
+msgid "Enter board name"
+msgstr ""
+
+msgid "Enter domain"
+msgstr ""
+
+msgid "Enter in your Bitbucket Server URL and personal access token below"
+msgstr ""
+
+msgid "Enter in your Phabricator Server URL and personal access token below"
+msgstr ""
+
+msgid "Enter merge request URLs"
+msgstr ""
+
+msgid "Enter new %{field_title}"
+msgstr ""
+
+msgid "Enter new AWS Secret Access Key"
+msgstr ""
+
+msgid "Enter number of issues"
+msgstr ""
+
+msgid "Enter one or more user ID separated by commas"
+msgstr ""
+
+msgid "Enter the code from the two-factor app on your mobile device. If you've lost your device, you may enter one of your recovery codes."
+msgstr ""
+
+msgid "Enter the issue description"
+msgstr ""
+
+msgid "Enter the issue title"
+msgstr ""
+
+msgid "Enter the merge request description"
+msgstr ""
+
+msgid "Enter the merge request title"
+msgstr ""
+
+msgid "Enter the name of your application, and we'll return a unique %{type}."
+msgstr ""
+
+msgid "Enter weights for storages for new repositories."
+msgstr ""
+
+msgid "Enter your password to approve"
+msgstr ""
+
+msgid "Environment"
+msgstr ""
+
+msgid "Environment does not have deployments"
+msgstr ""
+
+msgid "Environment is required for Stages::MetricEndpointInserter"
+msgstr ""
+
+msgid "Environment is required for Stages::VariableEndpointInserter"
+msgstr ""
+
+msgid "Environment scope"
+msgstr ""
+
+msgid "Environment variables are applied to all project environments in this instance via the Runner. You can use environment variables for passwords, secret keys, etc. Make variables available to the running application by prepending the variable key with <code>K8S_SECRET_</code>. You can set variables to be:"
+msgstr ""
+
+msgid "Environment variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. Additionally, they can be masked so they are hidden in job logs, though they must match certain regexp requirements to do so. You can use environment variables for passwords, secret keys, or whatever you want."
+msgstr ""
+
+msgid "Environment variables are configured by your administrator to be %{link_start}protected%{link_end} by default"
+msgstr ""
+
+msgid "Environment variables on this GitLab instance are configured to be %{link_start}protected%{link_end} by default"
+msgstr ""
+
+msgid "Environment:"
+msgstr ""
+
+msgid "EnvironmentDashboard|API"
+msgstr ""
+
+msgid "EnvironmentDashboard|Created through the Deployment API"
+msgstr ""
+
+msgid "EnvironmentDashboard|You are looking at the last updated environment"
+msgstr ""
+
+msgid "Environments"
+msgstr ""
+
+msgid "Environments Dashboard"
+msgstr ""
+
+msgid "Environments allow you to track deployments of your application %{link_to_read_more}."
+msgstr ""
+
+msgid "Environments in %{name}"
+msgstr ""
+
+msgid "EnvironmentsDashboard|Add a project to the dashboard"
+msgstr ""
+
+msgid "EnvironmentsDashboard|Add projects"
+msgstr ""
+
+msgid "EnvironmentsDashboard|Environments Dashboard"
+msgstr ""
+
+msgid "EnvironmentsDashboard|Job: %{job}"
+msgstr ""
+
+msgid "EnvironmentsDashboard|More actions"
+msgstr ""
+
+msgid "EnvironmentsDashboard|More information"
+msgstr ""
+
+msgid "EnvironmentsDashboard|Remove"
+msgstr ""
+
+msgid "EnvironmentsDashboard|The environments dashboard provides a summary of each project's environments' status, including pipeline and alert statuses."
+msgstr ""
+
+msgid "EnvironmentsDashboard|This dashboard displays a maximum of 7 projects and 3 environments per project. %{readMoreLink}"
+msgstr ""
+
+msgid "Environments|An error occurred while canceling the auto stop, please try again"
+msgstr ""
+
+msgid "Environments|An error occurred while deleting the environment. Check if the environment stopped; if not, stop it and try again."
+msgstr ""
+
+msgid "Environments|An error occurred while fetching the environments."
+msgstr ""
+
+msgid "Environments|An error occurred while making the request."
+msgstr ""
+
+msgid "Environments|An error occurred while re-deploying the environment, please try again"
+msgstr ""
+
+msgid "Environments|An error occurred while rolling back the environment, please try again"
+msgstr ""
+
+msgid "Environments|An error occurred while stopping the environment, please try again"
+msgstr ""
+
+msgid "Environments|Are you sure you want to stop this environment?"
+msgstr ""
+
+msgid "Environments|Auto stop in"
+msgstr ""
+
+msgid "Environments|Auto stops %{auto_stop_time}"
+msgstr ""
+
+msgid "Environments|Commit"
+msgstr ""
+
+msgid "Environments|Currently showing %{fetched} results."
+msgstr ""
+
+msgid "Environments|Currently showing all results."
+msgstr ""
+
+msgid "Environments|Delete"
+msgstr ""
+
+msgid "Environments|Delete environment"
+msgstr ""
+
+msgid "Environments|Deleting the '%{environmentName}' environment cannot be undone. Do you want to delete it anyway?"
+msgstr ""
+
+msgid "Environments|Deploy to..."
+msgstr ""
+
+msgid "Environments|Deployment"
+msgstr ""
+
+msgid "Environments|Enable review app"
+msgstr ""
+
+msgid "Environments|Environment"
+msgstr ""
+
+msgid "Environments|Environments"
+msgstr ""
+
+msgid "Environments|Environments are places where code gets deployed, such as staging or production."
+msgstr ""
+
+msgid "Environments|Install Elastic Stack on your cluster to enable advanced querying capabilities such as full text search."
+msgstr ""
+
+msgid "Environments|Job"
+msgstr ""
+
+msgid "Environments|Learn about environments"
+msgstr ""
+
+msgid "Environments|Learn more about stopping environments"
+msgstr ""
+
+msgid "Environments|Logs from %{start} to %{end}."
+msgstr ""
+
+msgid "Environments|New environment"
+msgstr ""
+
+msgid "Environments|No deployed environments"
+msgstr ""
+
+msgid "Environments|No deployments yet"
+msgstr ""
+
+msgid "Environments|No pod selected"
+msgstr ""
+
+msgid "Environments|No pods to display"
+msgstr ""
+
+msgid "Environments|Note that this action will stop the environment, but it will %{emphasisStart}not%{emphasisEnd} have an effect on any existing deployment due to no “stop environment action†being defined in the %{ciConfigLinkStart}.gitlab-ci.yml%{ciConfigLinkEnd} file."
+msgstr ""
+
+msgid "Environments|Note that this action will stop the environment, but it will %{emphasis_start}not%{emphasis_end} have an effect on any existing deployment due to no “stop environment action†being defined in the %{ci_config_link_start}.gitlab-ci.yml%{ci_config_link_end} file."
+msgstr ""
+
+msgid "Environments|Open live environment"
+msgstr ""
+
+msgid "Environments|Pod name"
+msgstr ""
+
+msgid "Environments|Re-deploy"
+msgstr ""
+
+msgid "Environments|Re-deploy environment %{environment_name}?"
+msgstr ""
+
+msgid "Environments|Re-deploy environment %{name}?"
+msgstr ""
+
+msgid "Environments|Re-deploy to environment"
+msgstr ""
+
+msgid "Environments|Read more about environments"
+msgstr ""
+
+msgid "Environments|Rollback"
+msgstr ""
+
+msgid "Environments|Rollback environment"
+msgstr ""
+
+msgid "Environments|Rollback environment %{environment_name}?"
+msgstr ""
+
+msgid "Environments|Rollback environment %{name}?"
+msgstr ""
+
+msgid "Environments|Select environment"
+msgstr ""
+
+msgid "Environments|Select pod"
+msgstr ""
+
+msgid "Environments|Show all"
+msgstr ""
+
+msgid "Environments|Stop"
+msgstr ""
+
+msgid "Environments|Stop environment"
+msgstr ""
+
+msgid "Environments|Stopping"
+msgstr ""
+
+msgid "Environments|There was an error fetching the logs. Please try again."
+msgstr ""
+
+msgid "Environments|This action will relaunch the job for commit %{commit_id}, putting the environment in a previous version. Are you sure you want to continue?"
+msgstr ""
+
+msgid "Environments|This action will relaunch the job for commit %{linkStart}%{commitId}%{linkEnd}, putting the environment in a previous version. Are you sure you want to continue?"
+msgstr ""
+
+msgid "Environments|This action will run the job defined by %{environment_name} for commit %{commit_id}, putting the environment in a previous version. You can revert it by re-deploying the latest version of your application. Are you sure you want to continue?"
+msgstr ""
+
+msgid "Environments|This action will run the job defined by %{name} for commit %{linkStart}%{commitId}%{linkEnd} putting the environment in a previous version. You can revert it by re-deploying the latest version of your application. Are you sure you want to continue?"
+msgstr ""
+
+msgid "Environments|Updated"
+msgstr ""
+
+msgid "Environments|You don't have any environments right now"
+msgstr ""
+
+msgid "Environments|protected"
+msgstr ""
+
+msgid "Epic"
+msgstr ""
+
+msgid "Epic cannot be found."
+msgstr ""
+
+msgid "Epic events"
+msgstr ""
+
+msgid "Epics"
+msgstr ""
+
+msgid "Epics Roadmap"
+msgstr ""
+
+msgid "Epics and Issues"
+msgstr ""
+
+msgid "Epics let you manage your portfolio of projects more efficiently and with less effort"
+msgstr ""
+
+msgid "Epics, Issues, and Merge Requests"
+msgstr ""
+
+msgid "Epics|Add a new epic"
+msgstr ""
+
+msgid "Epics|Add an existing epic"
+msgstr ""
+
+msgid "Epics|An error occurred while saving the %{epicDateType} date"
+msgstr ""
+
+msgid "Epics|An error occurred while updating labels."
+msgstr ""
+
+msgid "Epics|Are you sure you want to remove %{bStart}%{targetIssueTitle}%{bEnd} from %{bStart}%{parentEpicTitle}%{bEnd}?"
+msgstr ""
+
+msgid "Epics|How can I solve this?"
+msgstr ""
+
+msgid "Epics|More information"
+msgstr ""
+
+msgid "Epics|Remove epic"
+msgstr ""
+
+msgid "Epics|Remove issue"
+msgstr ""
+
+msgid "Epics|Show more"
+msgstr ""
+
+msgid "Epics|Something went wrong while assigning issue to epic."
+msgstr ""
+
+msgid "Epics|Something went wrong while creating child epics."
+msgstr ""
+
+msgid "Epics|Something went wrong while creating issue."
+msgstr ""
+
+msgid "Epics|Something went wrong while fetching child epics."
+msgstr ""
+
+msgid "Epics|Something went wrong while fetching group epics."
+msgstr ""
+
+msgid "Epics|Something went wrong while moving item."
+msgstr ""
+
+msgid "Epics|Something went wrong while ordering item."
+msgstr ""
+
+msgid "Epics|Something went wrong while removing issue from epic."
+msgstr ""
+
+msgid "Epics|These dates affect how your epics appear in the roadmap. Dates from milestones come from the milestones assigned to issues in the epic. You can also set fixed dates or remove them entirely."
+msgstr ""
+
+msgid "Epics|This will also remove any descendents of %{bStart}%{targetEpicTitle}%{bEnd} from %{bStart}%{parentEpicTitle}%{bEnd}. Are you sure?"
+msgstr ""
+
+msgid "Epics|To schedule your epic's %{epicDateType} date based on milestones, assign a milestone with a %{epicDateType} date to any issue in the epic."
+msgstr ""
+
+msgid "Epics|due"
+msgstr ""
+
+msgid "Epics|start"
+msgstr ""
+
+msgid "Error"
+msgstr ""
+
+msgid "Error Details"
+msgstr ""
+
+msgid "Error Tracking"
+msgstr ""
+
+msgid "Error creating epic"
+msgstr ""
+
+msgid "Error creating label."
+msgstr ""
+
+msgid "Error creating new iteration"
+msgstr ""
+
+msgid "Error creating repository for snippet with id %{snippet_id}"
+msgstr ""
+
+msgid "Error creating the snippet"
+msgstr ""
+
+msgid "Error deleting %{issuableType}"
+msgstr ""
+
+msgid "Error deleting project. Check logs for error details."
+msgstr ""
+
+msgid "Error fetching diverging counts for branches. Please try again."
+msgstr ""
+
+msgid "Error fetching forked projects. Please try again."
+msgstr ""
+
+msgid "Error fetching labels."
+msgstr ""
+
+msgid "Error fetching network graph."
+msgstr ""
+
+msgid "Error fetching payload data."
+msgstr ""
+
+msgid "Error fetching projects"
+msgstr ""
+
+msgid "Error fetching refs"
+msgstr ""
+
+msgid "Error fetching the dependency list. Please check your network connection and try again."
+msgstr ""
+
+msgid "Error loading branch data. Please try again."
+msgstr ""
+
+msgid "Error loading branches."
+msgstr ""
+
+msgid "Error loading burndown chart data"
+msgstr ""
+
+msgid "Error loading countries data."
+msgstr ""
+
+msgid "Error loading file viewer."
+msgstr ""
+
+msgid "Error loading last commit."
+msgstr ""
+
+msgid "Error loading markdown preview"
+msgstr ""
+
+msgid "Error loading merge requests."
+msgstr ""
+
+msgid "Error loading milestone tab"
+msgstr ""
+
+msgid "Error loading project data. Please try again."
+msgstr ""
+
+msgid "Error loading template types."
+msgstr ""
+
+msgid "Error loading template."
+msgstr ""
+
+msgid "Error loading viewer"
+msgstr ""
+
+msgid "Error occurred when fetching sidebar data"
+msgstr ""
+
+msgid "Error occurred when saving assignees"
+msgstr ""
+
+msgid "Error occurred when toggling the notification subscription"
+msgstr ""
+
+msgid "Error occurred while updating the issue status"
+msgstr ""
+
+msgid "Error occurred while updating the issue weight"
+msgstr ""
+
+msgid "Error occurred. A blocked user cannot be deactivated"
+msgstr ""
+
+msgid "Error occurred. A blocked user must be unblocked to be activated"
+msgstr ""
+
+msgid "Error occurred. User was not blocked"
+msgstr ""
+
+msgid "Error occurred. User was not confirmed"
+msgstr ""
+
+msgid "Error occurred. User was not unblocked"
+msgstr ""
+
+msgid "Error occurred. User was not unlocked"
+msgstr ""
+
+msgid "Error rendering markdown preview"
+msgstr ""
+
+msgid "Error saving label update."
+msgstr ""
+
+msgid "Error setting up editor. Please try again."
+msgstr ""
+
+msgid "Error updating %{issuableType}"
+msgstr ""
+
+msgid "Error updating status for all to-do items."
+msgstr ""
+
+msgid "Error updating status of to-do item."
+msgstr ""
+
+msgid "Error updating the snippet"
+msgstr ""
+
+msgid "Error uploading file"
+msgstr ""
+
+msgid "Error uploading file: %{stripped}"
+msgstr ""
+
+msgid "Error while loading the merge request. Please try again."
+msgstr ""
+
+msgid "Error while loading the project data. Please try again."
+msgstr ""
+
+msgid "Error while migrating %{upload_id}: %{error_message}"
+msgstr ""
+
+msgid "Error with Akismet. Please check the logs for more info."
+msgstr ""
+
+msgid "ErrorTracking|Active"
+msgstr ""
+
+msgid "ErrorTracking|After adding your Auth Token, use the 'Connect' button to load projects"
+msgstr ""
+
+msgid "ErrorTracking|Auth Token"
+msgstr ""
+
+msgid "ErrorTracking|Click 'Connect' to re-establish the connection to Sentry and activate the dropdown."
+msgstr ""
+
+msgid "ErrorTracking|Connection has failed. Re-check Auth Token and try again."
+msgstr ""
+
+msgid "ErrorTracking|If you self-host Sentry, enter the full URL of your Sentry instance. If you're using Sentry's hosted solution, enter https://sentry.io"
+msgstr ""
+
+msgid "ErrorTracking|No projects available"
+msgstr ""
+
+msgid "ErrorTracking|Select project"
+msgstr ""
+
+msgid "ErrorTracking|To enable project selection, enter a valid Auth Token"
+msgstr ""
+
+msgid "Errors"
+msgstr ""
+
+msgid "Errors:"
+msgstr ""
+
+msgid "Estimate"
+msgstr ""
+
+msgid "Estimated"
+msgstr ""
+
+msgid "EventFilterBy|Filter by all"
+msgstr ""
+
+msgid "EventFilterBy|Filter by comments"
+msgstr ""
+
+msgid "EventFilterBy|Filter by designs"
+msgstr ""
+
+msgid "EventFilterBy|Filter by epic events"
+msgstr ""
+
+msgid "EventFilterBy|Filter by issue events"
+msgstr ""
+
+msgid "EventFilterBy|Filter by merge events"
+msgstr ""
+
+msgid "EventFilterBy|Filter by push events"
+msgstr ""
+
+msgid "EventFilterBy|Filter by team"
+msgstr ""
+
+msgid "EventFilterBy|Filter by wiki"
+msgstr ""
+
+msgid "Events"
+msgstr ""
+
+msgid "Events in %{group_name}"
+msgstr ""
+
+msgid "Events in %{project_path}"
+msgstr ""
+
+msgid "Every %{action} attempt has failed: %{job_error_message}. Please try again."
+msgstr ""
+
+msgid "Every day"
+msgstr ""
+
+msgid "Every day (at %{time})"
+msgstr ""
+
+msgid "Every month"
+msgstr ""
+
+msgid "Every month (Day %{day} at %{time})"
+msgstr ""
+
+msgid "Every three months"
+msgstr ""
+
+msgid "Every two weeks"
+msgstr ""
+
+msgid "Every week"
+msgstr ""
+
+msgid "Every week (%{weekday} at %{time})"
+msgstr ""
+
+msgid "Everyone"
+msgstr ""
+
+msgid "Everyone With Access"
+msgstr ""
+
+msgid "Everyone can contribute"
+msgstr ""
+
+msgid "Everything on your to-do list is marked as done."
+msgstr ""
+
+msgid "Everything you need to create a GitLab Pages site using Gatsby."
+msgstr ""
+
+msgid "Everything you need to create a GitLab Pages site using GitBook."
+msgstr ""
+
+msgid "Everything you need to create a GitLab Pages site using Hexo."
+msgstr ""
+
+msgid "Everything you need to create a GitLab Pages site using Hugo."
+msgstr ""
+
+msgid "Everything you need to create a GitLab Pages site using Jekyll."
+msgstr ""
+
+msgid "Everything you need to create a GitLab Pages site using plain HTML."
+msgstr ""
+
+msgid "Evidence collection"
+msgstr ""
+
+msgid "Exactly one of %{attributes} is required"
+msgstr ""
+
+msgid "Example: <code>acme.com,acme.co.in,acme.uk</code>."
+msgstr ""
+
+msgid "Example: @sub\\.company\\.com$"
+msgstr ""
+
+msgid "Example: Usage = single query. (Requested) / (Capacity) = multiple queries combined into a formula."
+msgstr ""
+
+msgid "Except policy:"
+msgstr ""
+
+msgid "Excluding merge commits. Limited to %{limit} commits."
+msgstr ""
+
+msgid "Excluding merge commits. Limited to 6,000 commits."
+msgstr ""
+
+msgid "Existing members and groups"
+msgstr ""
+
+msgid "Existing projects may be moved into a group"
+msgstr ""
+
+msgid "Existing projects will be able to use expiration policies. Avoid enabling this if an external Container Registry is being used, as there is a performance risk if many images exist on one project."
+msgstr ""
+
+msgid "Existing shares"
+msgstr ""
+
+msgid "Existing sign in methods may be removed"
+msgstr ""
+
+msgid "Expand"
+msgstr ""
+
+msgid "Expand all"
+msgstr ""
+
+msgid "Expand approvers"
+msgstr ""
+
+msgid "Expand down"
+msgstr ""
+
+msgid "Expand dropdown"
+msgstr ""
+
+msgid "Expand milestones"
+msgstr ""
+
+msgid "Expand sidebar"
+msgstr ""
+
+msgid "Expand up"
+msgstr ""
+
+msgid "Experienced"
+msgstr ""
+
+msgid "Expiration"
+msgstr ""
+
+msgid "Expiration date"
+msgstr ""
+
+msgid "Expiration not enforced"
+msgstr ""
+
+msgid "Expiration policy for the Container Registry is a perfect solution for keeping the Registry space down while still enjoying the full power of GitLab CI/CD."
+msgstr ""
+
+msgid "Expired"
+msgstr ""
+
+msgid "Expired %{expiredOn}"
+msgstr ""
+
+msgid "Expired:"
+msgstr ""
+
+msgid "Expires"
+msgstr ""
+
+msgid "Expires at"
+msgstr ""
+
+msgid "Expires in %{expires_at}"
+msgstr ""
+
+msgid "Expires on"
+msgstr ""
+
+msgid "Expires:"
+msgstr ""
+
+msgid "Explain the problem. If appropriate, provide a link to the relevant issue or comment."
+msgstr ""
+
+msgid "Explore"
+msgstr ""
+
+msgid "Explore GitLab"
+msgstr ""
+
+msgid "Explore Groups"
+msgstr ""
+
+msgid "Explore groups"
+msgstr ""
+
+msgid "Explore projects"
+msgstr ""
+
+msgid "Explore public groups"
+msgstr ""
+
+msgid "Export as CSV"
+msgstr ""
+
+msgid "Export group"
+msgstr ""
+
+msgid "Export issues"
+msgstr ""
+
+msgid "Export project"
+msgstr ""
+
+msgid "Export this group with all related data to a new GitLab instance. Once complete, you can import the data file from the \"New Group\" page."
+msgstr ""
+
+msgid "Export this project with all its related data in order to move your project to a new GitLab instance. Once the export is finished, you can import the file from the \"New Project\" page."
+msgstr ""
+
+msgid "Export variable to pipelines running on protected branches and tags only."
+msgstr ""
+
+msgid "External Classification Policy Authorization"
+msgstr ""
+
+msgid "External ID"
+msgstr ""
+
+msgid "External URL"
+msgstr ""
+
+msgid "External Wiki"
+msgstr ""
+
+msgid "External authentication"
+msgstr ""
+
+msgid "External authorization denied access to this project"
+msgstr ""
+
+msgid "External authorization request timeout"
+msgstr ""
+
+msgid "External storage URL"
+msgstr ""
+
+msgid "External storage authentication token"
+msgstr ""
+
+msgid "ExternalAuthorizationService|Classification label"
+msgstr ""
+
+msgid "ExternalAuthorizationService|When no classification label is set the default label `%{default_label}` will be used."
+msgstr ""
+
+msgid "ExternalWikiService|External Wiki"
+msgstr ""
+
+msgid "ExternalWikiService|Replaces the link to the internal wiki with a link to an external wiki."
+msgstr ""
+
+msgid "ExternalWikiService|The URL of the external Wiki"
+msgstr ""
+
+msgid "Facebook"
+msgstr ""
+
+msgid "Failed"
+msgstr ""
+
+msgid "Failed Jobs"
+msgstr ""
+
+msgid "Failed on"
+msgstr ""
+
+msgid "Failed to add a Zoom meeting"
+msgstr ""
+
+msgid "Failed to apply commands."
+msgstr ""
+
+msgid "Failed to assign a user because no user was found."
+msgstr ""
+
+msgid "Failed to cancel auto stop because failed to update the environment."
+msgstr ""
+
+msgid "Failed to cancel auto stop because the environment is not set as auto stop."
+msgstr ""
+
+msgid "Failed to cancel auto stop because you do not have permission to update the environment."
+msgstr ""
+
+msgid "Failed to change the owner"
+msgstr ""
+
+msgid "Failed to check related branches."
+msgstr ""
+
+msgid "Failed to create Merge Request. Please try again."
+msgstr ""
+
+msgid "Failed to create a branch for this issue. Please try again."
+msgstr ""
+
+msgid "Failed to create import label for jira import."
+msgstr ""
+
+msgid "Failed to create repository"
+msgstr ""
+
+msgid "Failed to create resources"
+msgstr ""
+
+msgid "Failed to create wiki"
+msgstr ""
+
+msgid "Failed to delete board. Please try again."
+msgstr ""
+
+msgid "Failed to deploy to"
+msgstr ""
+
+msgid "Failed to enqueue the rebase operation, possibly due to a long-lived transaction. Try again later."
+msgstr ""
+
+msgid "Failed to find import label for Jira import."
+msgstr ""
+
+msgid "Failed to get ref."
+msgstr ""
+
+msgid "Failed to install."
+msgstr ""
+
+msgid "Failed to load assignees. Please try again."
+msgstr ""
+
+msgid "Failed to load authors. Please try again."
+msgstr ""
+
+msgid "Failed to load emoji list."
+msgstr ""
+
+msgid "Failed to load error details from Sentry."
+msgstr ""
+
+msgid "Failed to load errors from Sentry."
+msgstr ""
+
+msgid "Failed to load group activity metrics. Please try again."
+msgstr ""
+
+msgid "Failed to load groups & users."
+msgstr ""
+
+msgid "Failed to load labels. Please try again."
+msgstr ""
+
+msgid "Failed to load milestones. Please try again."
+msgstr ""
+
+msgid "Failed to load related branches"
+msgstr ""
+
+msgid "Failed to load stacktrace."
+msgstr ""
+
+msgid "Failed to mark this issue as a duplicate because referenced issue was not found."
+msgstr ""
+
+msgid "Failed to move this issue because label was not found."
+msgstr ""
+
+msgid "Failed to move this issue because only a single label can be provided."
+msgstr ""
+
+msgid "Failed to move this issue because target project doesn't exist."
+msgstr ""
+
+msgid "Failed to promote label due to internal error. Please contact administrators."
+msgstr ""
+
+msgid "Failed to protect the branch"
+msgstr ""
+
+msgid "Failed to protect the environment"
+msgstr ""
+
+msgid "Failed to publish issue on status page."
+msgstr ""
+
+msgid "Failed to remove a Zoom meeting"
+msgstr ""
+
+msgid "Failed to remove issue from board, please try again."
+msgstr ""
+
+msgid "Failed to remove mirror."
+msgstr ""
+
+msgid "Failed to remove the pipeline schedule"
+msgstr ""
+
+msgid "Failed to remove user identity."
+msgstr ""
+
+msgid "Failed to remove user key."
+msgstr ""
+
+msgid "Failed to reset key. Please try again."
+msgstr ""
+
+msgid "Failed to save merge conflicts resolutions. Please try again!"
+msgstr ""
+
+msgid "Failed to save new settings"
+msgstr ""
+
+msgid "Failed to save preferences (%{error_message})."
+msgstr ""
+
+msgid "Failed to save preferences."
+msgstr ""
+
+msgid "Failed to set due date because the date format is invalid."
+msgstr ""
+
+msgid "Failed to set iteration on this issue. Please try again."
+msgstr ""
+
+msgid "Failed to signing using smartcard authentication"
+msgstr ""
+
+msgid "Failed to update branch!"
+msgstr ""
+
+msgid "Failed to update environment!"
+msgstr ""
+
+msgid "Failed to update issue status"
+msgstr ""
+
+msgid "Failed to update issues, please try again."
+msgstr ""
+
+msgid "Failed to update tag!"
+msgstr ""
+
+msgid "Failed to update."
+msgstr ""
+
+msgid "Failed to upgrade."
+msgstr ""
+
+msgid "Failed to upload object map file"
+msgstr ""
+
+msgid "Failed to verify domain ownership"
+msgstr ""
+
+msgid "Failure"
+msgstr ""
+
+msgid "False positive"
+msgstr ""
+
+msgid "Fast-forward merge is not possible. Rebase the source branch onto %{targetBranch} to allow this merge request to be merged."
+msgstr ""
+
+msgid "Fast-forward merge is not possible. Rebase the source branch onto the target branch."
+msgstr ""
+
+msgid "Fast-forward merge without a merge commit"
+msgstr ""
+
+msgid "Faster as it re-uses the project workspace (falling back to clone if it doesn't exist)"
+msgstr ""
+
+msgid "Faster releases. Better code. Less pain."
+msgstr ""
+
+msgid "Favicon was successfully removed."
+msgstr ""
+
+msgid "Feature Flags"
+msgstr ""
+
+msgid "Feature flag was not removed."
+msgstr ""
+
+msgid "Feature flag was successfully removed."
+msgstr ""
+
+msgid "FeatureFlags|* (All Environments)"
+msgstr ""
+
+msgid "FeatureFlags|* (All environments)"
+msgstr ""
+
+msgid "FeatureFlags|API URL"
+msgstr ""
+
+msgid "FeatureFlags|Active"
+msgstr ""
+
+msgid "FeatureFlags|Add strategy"
+msgstr ""
+
+msgid "FeatureFlags|All users"
+msgstr ""
+
+msgid "FeatureFlags|Configure"
+msgstr ""
+
+msgid "FeatureFlags|Configure feature flags"
+msgstr ""
+
+msgid "FeatureFlags|Create feature flag"
+msgstr ""
+
+msgid "FeatureFlags|Delete %{name}?"
+msgstr ""
+
+msgid "FeatureFlags|Delete feature flag"
+msgstr ""
+
+msgid "FeatureFlags|Description"
+msgstr ""
+
+msgid "FeatureFlags|Edit Feature Flag"
+msgstr ""
+
+msgid "FeatureFlags|Edit Feature Flag User List"
+msgstr ""
+
+msgid "FeatureFlags|Edit list"
+msgstr ""
+
+msgid "FeatureFlags|Enable features for specific users and specific environments by defining feature flag strategies. By default, features are available to all users in all environments."
+msgstr ""
+
+msgid "FeatureFlags|Environment Spec"
+msgstr ""
+
+msgid "FeatureFlags|Environment Specs"
+msgstr ""
+
+msgid "FeatureFlags|Feature Flag"
+msgstr ""
+
+msgid "FeatureFlags|Feature Flag User List Details"
+msgstr ""
+
+msgid "FeatureFlags|Feature Flag behavior is built up by creating a set of rules to define the status of target environments. A default wildcard rule %{codeStart}*%{codeEnd} for %{boldStart}All Environments%{boldEnd} is set, and you are able to add as many rules as you need by choosing environment specs below. You can toggle the behavior for each of your rules to set them %{boldStart}Active%{boldEnd} or %{boldStart}Inactive%{boldEnd}."
+msgstr ""
+
+msgid "FeatureFlags|Feature Flag has no strategies"
+msgstr ""
+
+msgid "FeatureFlags|Feature Flags"
+msgstr ""
+
+msgid "FeatureFlags|Feature Flags will look different in the next milestone. No action is needed, but you may notice the functionality was changed to improve the workflow."
+msgstr ""
+
+msgid "FeatureFlags|Feature flag %{name} will be removed. Are you sure?"
+msgstr ""
+
+msgid "FeatureFlags|Feature flags allow you to configure your code into different flavors by dynamically toggling certain functionality."
+msgstr ""
+
+msgid "FeatureFlags|Flag becomes read only soon"
+msgstr ""
+
+msgid "FeatureFlags|Get started with feature flags"
+msgstr ""
+
+msgid "FeatureFlags|GitLab is moving to a new way of managing feature flags, and in 13.4, this feature flag will become read-only. Please create a new feature flag."
+msgstr ""
+
+msgid "FeatureFlags|ID"
+msgstr ""
+
+msgid "FeatureFlags|Inactive"
+msgstr ""
+
+msgid "FeatureFlags|Inactive flag for %{scope}"
+msgstr ""
+
+msgid "FeatureFlags|Include additional user IDs"
+msgstr ""
+
+msgid "FeatureFlags|Install a %{docs_link_anchored_start}compatible client library%{docs_link_anchored_end} and specify the API URL, application name, and instance ID during the configuration setup. %{docs_link_start}More Information%{docs_link_end}"
+msgstr ""
+
+msgid "FeatureFlags|Instance ID"
+msgstr ""
+
+msgid "FeatureFlags|List details"
+msgstr ""
+
+msgid "FeatureFlags|Loading feature flags"
+msgstr ""
+
+msgid "FeatureFlags|More information"
+msgstr ""
+
+msgid "FeatureFlags|Name"
+msgstr ""
+
+msgid "FeatureFlags|New"
+msgstr ""
+
+msgid "FeatureFlags|New Feature Flag"
+msgstr ""
+
+msgid "FeatureFlags|New Feature Flag User List"
+msgstr ""
+
+msgid "FeatureFlags|New feature flag"
+msgstr ""
+
+msgid "FeatureFlags|New list"
+msgstr ""
+
+msgid "FeatureFlags|Percent rollout (logged in users)"
+msgstr ""
+
+msgid "FeatureFlags|Percent rollout must be a whole number between 0 and 100"
+msgstr ""
+
+msgid "FeatureFlags|Protected"
+msgstr ""
+
+msgid "FeatureFlags|Remove"
+msgstr ""
+
+msgid "FeatureFlags|Rollout Percentage"
+msgstr ""
+
+msgid "FeatureFlags|Rollout Strategy"
+msgstr ""
+
+msgid "FeatureFlags|Status"
+msgstr ""
+
+msgid "FeatureFlags|Strategies"
+msgstr ""
+
+msgid "FeatureFlags|Target environments"
+msgstr ""
+
+msgid "FeatureFlags|There was an error fetching the feature flags."
+msgstr ""
+
+msgid "FeatureFlags|There was an error retrieving user lists"
+msgstr ""
+
+msgid "FeatureFlags|Try again in a few moments or contact your support team."
+msgstr ""
+
+msgid "FeatureFlags|User IDs"
+msgstr ""
+
+msgid "FeatureFlag|Delete strategy"
+msgstr ""
+
+msgid "FeatureFlag|List"
+msgstr ""
+
+msgid "FeatureFlag|Percentage"
+msgstr ""
+
+msgid "FeatureFlag|Select a user list"
+msgstr ""
+
+msgid "FeatureFlag|There are no configured user lists"
+msgstr ""
+
+msgid "FeatureFlag|Type"
+msgstr ""
+
+msgid "FeatureFlag|User IDs"
+msgstr ""
+
+msgid "Feb"
+msgstr ""
+
+msgid "February"
+msgstr ""
+
+msgid "Fetching incoming email"
+msgstr ""
+
+msgid "Fetching licenses failed."
+msgstr ""
+
+msgid "Fetching licenses failed. The request endpoint was not found."
+msgstr ""
+
+msgid "Fetching licenses failed. You are not permitted to perform this action."
+msgstr ""
+
+msgid "File"
+msgstr ""
+
+msgid "File Hooks"
+msgstr ""
+
+msgid "File Hooks (%{count})"
+msgstr ""
+
+msgid "File added"
+msgstr ""
+
+msgid "File browser"
+msgstr ""
+
+msgid "File deleted"
+msgstr ""
+
+msgid "File format is no longer supported"
+msgstr ""
+
+msgid "File hooks are similar to system hooks but are executed as files instead of sending data to a URL."
+msgstr ""
+
+msgid "File mode changed from %{a_mode} to %{b_mode}"
+msgstr ""
+
+msgid "File moved"
+msgstr ""
+
+msgid "File name"
+msgstr ""
+
+msgid "File renamed with no changes."
+msgstr ""
+
+msgid "File sync capacity"
+msgstr ""
+
+msgid "File templates"
+msgstr ""
+
+msgid "File upload error."
+msgstr ""
+
+msgid "Files"
+msgstr ""
+
+msgid "Files breadcrumb"
+msgstr ""
+
+msgid "Files, directories, and submodules in the path %{path} for commit reference %{ref}"
+msgstr ""
+
+msgid "Fill in the fields below, turn on <strong>%{enable_label}</strong>, and press <strong>%{save_changes}</strong>"
+msgstr ""
+
+msgid "Filter"
+msgstr ""
+
+msgid "Filter by %{issuable_type} that are currently closed."
+msgstr ""
+
+msgid "Filter by %{issuable_type} that are currently opened."
+msgstr ""
+
+msgid "Filter by %{page_context_word} that are currently opened."
+msgstr ""
+
+msgid "Filter by Git revision"
+msgstr ""
+
+msgid "Filter by commit message"
+msgstr ""
+
+msgid "Filter by issues that are currently closed."
+msgstr ""
+
+msgid "Filter by label"
+msgstr ""
+
+msgid "Filter by merge requests that are currently closed and unmerged."
+msgstr ""
+
+msgid "Filter by merge requests that are currently merged."
+msgstr ""
+
+msgid "Filter by milestone name"
+msgstr ""
+
+msgid "Filter by name"
+msgstr ""
+
+msgid "Filter by requirements that are currently archived."
+msgstr ""
+
+msgid "Filter by requirements that are currently opened."
+msgstr ""
+
+msgid "Filter by status"
+msgstr ""
+
+msgid "Filter by two-factor authentication"
+msgstr ""
+
+msgid "Filter by user"
+msgstr ""
+
+msgid "Filter pipelines"
+msgstr ""
+
+msgid "Filter projects"
+msgstr ""
+
+msgid "Filter results"
+msgstr ""
+
+msgid "Filter results by group"
+msgstr ""
+
+msgid "Filter results by project"
+msgstr ""
+
+msgid "Filter results..."
+msgstr ""
+
+msgid "Filter your projects by name"
+msgstr ""
+
+msgid "Filter..."
+msgstr ""
+
+msgid "Find File"
+msgstr ""
+
+msgid "Find bugs in your code with coverage-guided fuzzing"
+msgstr ""
+
+msgid "Find by path"
+msgstr ""
+
+msgid "Find existing members by name"
+msgstr ""
+
+msgid "Find file"
+msgstr ""
+
+msgid "Find the downloaded ZIP file and decompress it."
+msgstr ""
+
+msgid "Find the newly extracted <code>Takeout/Google Code Project Hosting/GoogleCodeProjectHosting.json</code> file."
+msgstr ""
+
+msgid "Fingerprint"
+msgstr ""
+
+msgid "Fingerprints"
+msgstr ""
+
+msgid "Finish editing this message first!"
+msgstr ""
+
+msgid "Finish review"
+msgstr ""
+
+msgid "Finish setting up your dedicated account for <strong>%{group_name}</strong>."
+msgstr ""
+
+msgid "Finished"
+msgstr ""
+
+msgid "First Name is too long (maximum is %{max_length} characters)."
+msgstr ""
+
+msgid "First Seen"
+msgstr ""
+
+msgid "First day of the week"
+msgstr ""
+
+msgid "First name"
+msgstr ""
+
+msgid "First seen"
+msgstr ""
+
+msgid "Fixed date"
+msgstr ""
+
+msgid "Fixed due date"
+msgstr ""
+
+msgid "Fixed start date"
+msgstr ""
+
+msgid "Fixed:"
+msgstr ""
+
+msgid "Flags"
+msgstr ""
+
+msgid "FlowdockService|Flowdock Git source token"
+msgstr ""
+
+msgid "FlowdockService|Flowdock is a collaboration web app for technical teams."
+msgstr ""
+
+msgid "FogBugz Email"
+msgstr ""
+
+msgid "FogBugz Import"
+msgstr ""
+
+msgid "FogBugz Password"
+msgstr ""
+
+msgid "FogBugz URL"
+msgstr ""
+
+msgid "FogBugz import"
+msgstr ""
+
+msgid "Folder/%{name}"
+msgstr ""
+
+msgid "Follow the steps below to export your Google Code project data."
+msgstr ""
+
+msgid "Font Color"
+msgstr ""
+
+msgid "Footer message"
+msgstr ""
+
+msgid "For each Jira issue successfully imported, we'll create a new GitLab issue with the following data:"
+msgstr ""
+
+msgid "For internal projects, any logged in user can view pipelines and access job details (output logs and artifacts)"
+msgstr ""
+
+msgid "For more info, read the documentation."
+msgstr ""
+
+msgid "For more information, go to the "
+msgstr ""
+
+msgid "For more information, please review %{link_start_tag}Jaeger's configuration doc%{link_end_tag}"
+msgstr ""
+
+msgid "For more information, see the File Hooks documentation."
+msgstr ""
+
+msgid "For more information, see the documentation on %{deactivating_usage_ping_link_start}deactivating the usage ping%{deactivating_usage_ping_link_end}."
+msgstr ""
+
+msgid "For more information, see the documentation on %{link_start}disabling Seat Link%{link_end}."
+msgstr ""
+
+msgid "For private projects, any member (guest or higher) can view pipelines and access job details (output logs and artifacts)"
+msgstr ""
+
+msgid "For public projects, anyone can view pipelines and access job details (output logs and artifacts)"
+msgstr ""
+
+msgid "Forgot your password?"
+msgstr ""
+
+msgid "Fork"
+msgstr ""
+
+msgid "Fork Error!"
+msgstr ""
+
+msgid "Fork project"
+msgstr ""
+
+msgid "Fork project?"
+msgstr ""
+
+msgid "ForkedFromProjectPath|Forked from"
+msgstr ""
+
+msgid "ForkedFromProjectPath|Forked from an inaccessible project"
+msgstr ""
+
+msgid "Forking in progress"
+msgstr ""
+
+msgid "Forks"
+msgstr ""
+
+msgid "Format"
+msgstr ""
+
+msgid "Format: %{dateFormat}"
+msgstr ""
+
+msgid "Forward external support email address to"
+msgstr ""
+
+msgid "Found errors in your %{gitlab_ci_yml}:"
+msgstr ""
+
+msgid "Found errors in your .gitlab-ci.yml:"
+msgstr ""
+
+msgid "Free Trial"
+msgstr ""
+
+msgid "Free Trial of GitLab.com Gold"
+msgstr ""
+
+msgid "Frequency"
+msgstr ""
+
+msgid "Friday"
+msgstr ""
+
+msgid "From"
+msgstr ""
+
+msgid "From %{providerTitle}"
+msgstr ""
+
+msgid "From <code>%{source_title}</code> into"
+msgstr ""
+
+msgid "From Bitbucket"
+msgstr ""
+
+msgid "From Bitbucket Server"
+msgstr ""
+
+msgid "From FogBugz"
+msgstr ""
+
+msgid "From GitLab.com"
+msgstr ""
+
+msgid "From Google Code"
+msgstr ""
+
+msgid "From issue creation until deploy to production"
+msgstr ""
+
+msgid "From merge request merge until deploy to production"
+msgstr ""
+
+msgid "From the Kubernetes cluster details view, install Runner from the applications list"
+msgstr ""
+
+msgid "Full name"
+msgstr ""
+
+msgid "GPG Key ID:"
+msgstr ""
+
+msgid "GPG Keys"
+msgstr ""
+
+msgid "GPG keys allow you to verify signed commits."
+msgstr ""
+
+msgid "GPG signature (loading...)"
+msgstr ""
+
+msgid "General"
+msgstr ""
+
+msgid "General Settings"
+msgstr ""
+
+msgid "General pipelines"
+msgstr ""
+
+msgid "Generate a default set of labels"
+msgstr ""
+
+msgid "Generate key"
+msgstr ""
+
+msgid "Generate new export"
+msgstr ""
+
+msgid "Geo"
+msgstr ""
+
+msgid "Geo Nodes"
+msgstr ""
+
+msgid "Geo Nodes|Cannot remove a primary node if there is a secondary node"
+msgstr ""
+
+msgid "Geo Replication"
+msgstr ""
+
+msgid "Geo Settings"
+msgstr ""
+
+msgid "Geo allows you to replicate your GitLab instance to other geographical locations."
+msgstr ""
+
+msgid "Geo nodes are paused using a command run on the node"
+msgstr ""
+
+msgid "GeoNodeStatusEvent|%{timeAgoStr} (%{pendingEvents} events)"
+msgstr ""
+
+msgid "GeoNodeSyncStatus|Node is failing or broken."
+msgstr ""
+
+msgid "GeoNodeSyncStatus|Node is slow, overloaded, or it just recovered after an outage."
+msgstr ""
+
+msgid "GeoNodes|Attachments"
+msgstr ""
+
+msgid "GeoNodes|Checksummed"
+msgstr ""
+
+msgid "GeoNodes|Consult Geo troubleshooting information"
+msgstr ""
+
+msgid "GeoNodes|Container repositories"
+msgstr ""
+
+msgid "GeoNodes|Data replication lag"
+msgstr ""
+
+msgid "GeoNodes|Design repositories"
+msgstr ""
+
+msgid "GeoNodes|Does not match the primary storage configuration"
+msgstr ""
+
+msgid "GeoNodes|Failed"
+msgstr ""
+
+msgid "GeoNodes|Full"
+msgstr ""
+
+msgid "GeoNodes|GitLab version"
+msgstr ""
+
+msgid "GeoNodes|GitLab version does not match the primary node version"
+msgstr ""
+
+msgid "GeoNodes|Health status"
+msgstr ""
+
+msgid "GeoNodes|Internal URL"
+msgstr ""
+
+msgid "GeoNodes|Job artifacts"
+msgstr ""
+
+msgid "GeoNodes|LFS objects"
+msgstr ""
+
+msgid "GeoNodes|Last event ID processed by cursor"
+msgstr ""
+
+msgid "GeoNodes|Last event ID seen from primary"
+msgstr ""
+
+msgid "GeoNodes|Learn more about Geo node statuses"
+msgstr ""
+
+msgid "GeoNodes|Loading nodes"
+msgstr ""
+
+msgid "GeoNodes|New node"
+msgstr ""
+
+msgid "GeoNodes|Node Authentication was successfully repaired."
+msgstr ""
+
+msgid "GeoNodes|Node URL"
+msgstr ""
+
+msgid "GeoNodes|Node was successfully removed."
+msgstr ""
+
+msgid "GeoNodes|Node's status was updated %{timeAgo}."
+msgstr ""
+
+msgid "GeoNodes|Not checksummed"
+msgstr ""
+
+msgid "GeoNodes|Package files"
+msgstr ""
+
+msgid "GeoNodes|Pausing replication stops the sync process. Are you sure?"
+msgstr ""
+
+msgid "GeoNodes|Removing a Geo primary node stops the synchronization to all nodes. Are you sure?"
+msgstr ""
+
+msgid "GeoNodes|Removing a Geo secondary node stops the synchronization to that node. Are you sure?"
+msgstr ""
+
+msgid "GeoNodes|Replicated data is verified with the %{nodeText} using checksums"
+msgstr ""
+
+msgid "GeoNodes|Replication slot WAL"
+msgstr ""
+
+msgid "GeoNodes|Replication slots"
+msgstr ""
+
+msgid "GeoNodes|Replication status"
+msgstr ""
+
+msgid "GeoNodes|Repositories"
+msgstr ""
+
+msgid "GeoNodes|Repository checksum progress"
+msgstr ""
+
+msgid "GeoNodes|Repository verification progress"
+msgstr ""
+
+msgid "GeoNodes|Selective (%{syncLabel})"
+msgstr ""
+
+msgid "GeoNodes|Selective synchronization"
+msgstr ""
+
+msgid "GeoNodes|Something went wrong while changing node status"
+msgstr ""
+
+msgid "GeoNodes|Something went wrong while fetching nodes"
+msgstr ""
+
+msgid "GeoNodes|Something went wrong while removing node"
+msgstr ""
+
+msgid "GeoNodes|Something went wrong while repairing node"
+msgstr ""
+
+msgid "GeoNodes|Storage config"
+msgstr ""
+
+msgid "GeoNodes|Sync settings"
+msgstr ""
+
+msgid "GeoNodes|Unused slots"
+msgstr ""
+
+msgid "GeoNodes|Unverified"
+msgstr ""
+
+msgid "GeoNodes|Updated %{timeAgo}"
+msgstr ""
+
+msgid "GeoNodes|Used slots"
+msgstr ""
+
+msgid "GeoNodes|Verified"
+msgstr ""
+
+msgid "GeoNodes|Wiki checksum progress"
+msgstr ""
+
+msgid "GeoNodes|Wiki verification progress"
+msgstr ""
+
+msgid "GeoNodes|Wikis"
+msgstr ""
+
+msgid "GeoNodes|With %{geo} you can install a special read-only and replicated instance anywhere. Before you add nodes, follow the %{instructions} in the exact order they appear."
+msgstr ""
+
+msgid "GeoNodes|You have configured Geo nodes using an insecure HTTP connection. We recommend the use of HTTPS."
+msgstr ""
+
+msgid "GeoNodes|primary node"
+msgstr ""
+
+msgid "GeoNodes|secondary nodes"
+msgstr ""
+
+msgid "Geo|%{label} can't be blank"
+msgstr ""
+
+msgid "Geo|%{label} should be between 1-999"
+msgstr ""
+
+msgid "Geo|%{name} is scheduled for forced re-download"
+msgstr ""
+
+msgid "Geo|%{name} is scheduled for re-sync"
+msgstr ""
+
+msgid "Geo|%{name} is scheduled for re-verify"
+msgstr ""
+
+msgid "Geo|Adjust your filters/search criteria above. If you believe this may be an error, please refer to the %{linkStart}Geo Troubleshooting%{linkEnd} documentation for more information."
+msgstr ""
+
+msgid "Geo|All %{replicable_name}"
+msgstr ""
+
+msgid "Geo|All projects"
+msgstr ""
+
+msgid "Geo|All projects are being scheduled for resync"
+msgstr ""
+
+msgid "Geo|All projects are being scheduled for reverify"
+msgstr ""
+
+msgid "Geo|Could not remove tracking entry for an existing project."
+msgstr ""
+
+msgid "Geo|Could not remove tracking entry for an existing upload."
+msgstr ""
+
+msgid "Geo|Failed"
+msgstr ""
+
+msgid "Geo|Filter by status"
+msgstr ""
+
+msgid "Geo|Geo Status"
+msgstr ""
+
+msgid "Geo|In progress"
+msgstr ""
+
+msgid "Geo|In sync"
+msgstr ""
+
+msgid "Geo|Last repository check run"
+msgstr ""
+
+msgid "Geo|Last successful sync"
+msgstr ""
+
+msgid "Geo|Last sync attempt"
+msgstr ""
+
+msgid "Geo|Last time verified"
+msgstr ""
+
+msgid "Geo|Never"
+msgstr ""
+
+msgid "Geo|Next sync scheduled at"
+msgstr ""
+
+msgid "Geo|Node name can't be blank"
+msgstr ""
+
+msgid "Geo|Node name should be between 1 and 255 characters"
+msgstr ""
+
+msgid "Geo|Not synced yet"
+msgstr ""
+
+msgid "Geo|Pending synchronization"
+msgstr ""
+
+msgid "Geo|Pending verification"
+msgstr ""
+
+msgid "Geo|Please refer to Geo Troubleshooting."
+msgstr ""
+
+msgid "Geo|Project"
+msgstr ""
+
+msgid "Geo|Project (ID: %{project_id}) no longer exists on the primary. It is safe to remove this entry, as this will not remove any data on disk."
+msgstr ""
+
+msgid "Geo|Projects in certain groups"
+msgstr ""
+
+msgid "Geo|Projects in certain storage shards"
+msgstr ""
+
+msgid "Geo|Redownload"
+msgstr ""
+
+msgid "Geo|Remove"
+msgstr ""
+
+msgid "Geo|Remove entry"
+msgstr ""
+
+msgid "Geo|Remove tracking database entry"
+msgstr ""
+
+msgid "Geo|Resync"
+msgstr ""
+
+msgid "Geo|Resync all"
+msgstr ""
+
+msgid "Geo|Retry count"
+msgstr ""
+
+msgid "Geo|Reverify"
+msgstr ""
+
+msgid "Geo|Reverify all"
+msgstr ""
+
+msgid "Geo|Review replication status, and resynchronize and reverify items with the primary node."
+msgstr ""
+
+msgid "Geo|Status"
+msgstr ""
+
+msgid "Geo|Synced"
+msgstr ""
+
+msgid "Geo|Synced at"
+msgstr ""
+
+msgid "Geo|Synchronization failed - %{error}"
+msgstr ""
+
+msgid "Geo|Synchronization of %{itemTitle} is disabled."
+msgstr ""
+
+msgid "Geo|The database is currently %{db_lag} behind the primary node."
+msgstr ""
+
+msgid "Geo|The node is currently %{minutes_behind} behind the primary node."
+msgstr ""
+
+msgid "Geo|There are no %{replicable_type} to show"
+msgstr ""
+
+msgid "Geo|Tracking database entry will be removed. Are you sure?"
+msgstr ""
+
+msgid "Geo|Tracking entry for project (%{project_id}) was successfully removed."
+msgstr ""
+
+msgid "Geo|Tracking entry for upload (%{type}/%{id}) was successfully removed."
+msgstr ""
+
+msgid "Geo|URL can't be blank"
+msgstr ""
+
+msgid "Geo|URL must be a valid url (ex: https://gitlab.com)"
+msgstr ""
+
+msgid "Geo|Unknown state"
+msgstr ""
+
+msgid "Geo|Verification failed - %{error}"
+msgstr ""
+
+msgid "Geo|Waiting for scheduler"
+msgstr ""
+
+msgid "Geo|You are on a secondary, <b>read-only</b> Geo node. If you want to make changes, you must visit this page on the %{primary_node}."
+msgstr ""
+
+msgid "Geo|You are on a secondary, <b>read-only</b> Geo node. You may be able to make a limited amount of changes or perform a limited amount of actions on this page."
+msgstr ""
+
+msgid "Geo|misconfigured"
+msgstr ""
+
+msgid "Geo|primary"
+msgstr ""
+
+msgid "Geo|secondary"
+msgstr ""
+
+msgid "Get a free instance review"
+msgstr ""
+
+msgid "Get started"
+msgstr ""
+
+msgid "Get started with error tracking"
+msgstr ""
+
+msgid "Get started with performance monitoring"
+msgstr ""
+
+msgid "Get started!"
+msgstr ""
+
+msgid "Getting started with releases"
+msgstr ""
+
+msgid "Git"
+msgstr ""
+
+msgid "Git LFS is not enabled on this GitLab server, contact your admin."
+msgstr ""
+
+msgid "Git LFS objects will be synced in pull mirrors if LFS is %{docs_link_start}enabled for the project%{docs_link_end}. They will <strong>not</strong> be synced in push mirrors."
+msgstr ""
+
+msgid "Git global setup"
+msgstr ""
+
+msgid "Git repository URL"
+msgstr ""
+
+msgid "Git revision"
+msgstr ""
+
+msgid "Git shallow clone"
+msgstr ""
+
+msgid "Git strategy for pipelines"
+msgstr ""
+
+msgid "Git version"
+msgstr ""
+
+msgid "GitHub API rate limit exceeded. Try again after %{reset_time}"
+msgstr ""
+
+msgid "GitHub import"
+msgstr ""
+
+msgid "GitLab / Unsubscribe"
+msgstr ""
+
+msgid "GitLab Enterprise Edition %{plan}"
+msgstr ""
+
+msgid "GitLab Group Runners can execute code for all the projects in this group."
+msgstr ""
+
+msgid "GitLab Import"
+msgstr ""
+
+msgid "GitLab Issue"
+msgstr ""
+
+msgid "GitLab Shared Runners execute code of different projects on the same Runner unless you configure GitLab Runner Autoscale with MaxBuilds 1 (which it is on GitLab.com)."
+msgstr ""
+
+msgid "GitLab Support Bot"
+msgstr ""
+
+msgid "GitLab Team Member"
+msgstr ""
+
+msgid "GitLab User"
+msgstr ""
+
+msgid "GitLab allows you to continue using your license even if you exceed the number of seats you purchased. You will be required to pay for these seats when you renew your license."
+msgstr ""
+
+msgid "GitLab commit"
+msgstr ""
+
+msgid "GitLab for Slack"
+msgstr ""
+
+msgid "GitLab is a single application for the entire software development lifecycle. From project planning and source code management to CI/CD, monitoring, and security."
+msgstr ""
+
+msgid "GitLab is obtaining a Let's Encrypt SSL certificate for this domain. This process can take some time. Please try again later."
+msgstr ""
+
+msgid "GitLab is undergoing maintenance and is operating in a read-only mode."
+msgstr ""
+
+msgid "GitLab member or Email address"
+msgstr ""
+
+msgid "GitLab metadata URL"
+msgstr ""
+
+msgid "GitLab project export"
+msgstr ""
+
+msgid "GitLab restart is required to apply changes."
+msgstr ""
+
+msgid "GitLab single sign on URL"
+msgstr ""
+
+msgid "GitLab uses %{jaeger_link} to monitor distributed systems."
+msgstr ""
+
+msgid "GitLab will run a background job that will produce pseudonymized CSVs of the GitLab database that will be uploaded to your configured object storage directory."
+msgstr ""
+
+msgid "GitLab.com import"
+msgstr ""
+
+msgid "GitLabPagesDomains|Retry"
+msgstr ""
+
+msgid "GitLabPages|%{domain} is not verified. To learn how to verify ownership, visit your %{link_start}domain details%{link_end}."
+msgstr ""
+
+msgid "GitLabPages|Access Control is enabled for this Pages website; only authorized users will be able to access it. To make your website publicly available, navigate to your project's %{strong_start}Settings > General > Visibility%{strong_end} and select %{strong_start}Everyone%{strong_end} in pages section. Read the %{link_start}documentation%{link_end} for more information."
+msgstr ""
+
+msgid "GitLabPages|Access pages"
+msgstr ""
+
+msgid "GitLabPages|Are you sure?"
+msgstr ""
+
+msgid "GitLabPages|Certificate: %{subject}"
+msgstr ""
+
+msgid "GitLabPages|Configure pages"
+msgstr ""
+
+msgid "GitLabPages|Domains"
+msgstr ""
+
+msgid "GitLabPages|Edit"
+msgstr ""
+
+msgid "GitLabPages|Expired"
+msgstr ""
+
+msgid "GitLabPages|Force HTTPS (requires valid certificates)"
+msgstr ""
+
+msgid "GitLabPages|GitLab Pages are disabled for this project. You can enable them on your project's %{strong_start}Settings > General > Visibility%{strong_end} page."
+msgstr ""
+
+msgid "GitLabPages|It may take up to 30 minutes before the site is available after the first deployment."
+msgstr ""
+
+msgid "GitLabPages|Learn how to upload your static site and have it served by GitLab by following the %{link_start}documentation on GitLab Pages%{link_end}."
+msgstr ""
+
+msgid "GitLabPages|Learn more."
+msgstr ""
+
+msgid "GitLabPages|Maximum size of pages (MB)"
+msgstr ""
+
+msgid "GitLabPages|New Domain"
+msgstr ""
+
+msgid "GitLabPages|Only project maintainers can remove pages"
+msgstr ""
+
+msgid "GitLabPages|Pages"
+msgstr ""
+
+msgid "GitLabPages|Remove"
+msgstr ""
+
+msgid "GitLabPages|Remove pages"
+msgstr ""
+
+msgid "GitLabPages|Removing pages will prevent them from being exposed to the outside world."
+msgstr ""
+
+msgid "GitLabPages|Save"
+msgstr ""
+
+msgid "GitLabPages|Something went wrong while obtaining the Let's Encrypt certificate for %{domain}. To retry visit your %{link_start}domain details%{link_end}."
+msgstr ""
+
+msgid "GitLabPages|Support for domains and certificates is disabled. Ask your system's administrator to enable it."
+msgstr ""
+
+msgid "GitLabPages|The total size of deployed static content will be limited to this size. 0 for unlimited. Leave empty to inherit the global value."
+msgstr ""
+
+msgid "GitLabPages|Unverified"
+msgstr ""
+
+msgid "GitLabPages|Verified"
+msgstr ""
+
+msgid "GitLabPages|When using Pages under the general domain of a GitLab instance (%{pages_host}), you cannot use HTTPS with sub-subdomains. This means that if your username/groupname contains a dot it will not work. This is a limitation of the HTTP Over TLS protocol. HTTP pages will continue to work provided you don't redirect HTTP to HTTPS."
+msgstr ""
+
+msgid "GitLabPages|With GitLab Pages you can host your static websites on GitLab. Combined with the power of GitLab CI and the help of GitLab Runner you can deploy static pages for your individual projects, your user or your group."
+msgstr ""
+
+msgid "GitLabPages|Your pages are served under:"
+msgstr ""
+
+msgid "Gitaly"
+msgstr ""
+
+msgid "Gitaly Servers"
+msgstr ""
+
+msgid "Gitaly|Address"
+msgstr ""
+
+msgid "Gitea Host URL"
+msgstr ""
+
+msgid "Gitea Import"
+msgstr ""
+
+msgid "Gitlab Pages"
+msgstr ""
+
+msgid "Given access %{time_ago}"
+msgstr ""
+
+msgid "Given epic is already related to this epic."
+msgstr ""
+
+msgid "Global Shortcuts"
+msgstr ""
+
+msgid "Global notification settings"
+msgstr ""
+
+msgid "Go Back"
+msgstr ""
+
+msgid "Go Micro is a framework for micro service development."
+msgstr ""
+
+msgid "Go back"
+msgstr ""
+
+msgid "Go back (while searching for files)"
+msgstr ""
+
+msgid "Go back to %{startTag}Open issues%{endTag} and select some issues to add to your board."
+msgstr ""
+
+msgid "Go full screen"
+msgstr ""
+
+msgid "Go to %{link_to_google_takeout}."
+msgstr ""
+
+msgid "Go to <strong>Issues</strong> > <strong>Boards</strong> to access your personalized learning issue board."
+msgstr ""
+
+msgid "Go to Pipelines"
+msgstr ""
+
+msgid "Go to Webhooks"
+msgstr ""
+
+msgid "Go to commits"
+msgstr ""
+
+msgid "Go to definition"
+msgstr ""
+
+msgid "Go to environments"
+msgstr ""
+
+msgid "Go to file"
+msgstr ""
+
+msgid "Go to file permalink (while viewing a file)"
+msgstr ""
+
+msgid "Go to files"
+msgstr ""
+
+msgid "Go to find file"
+msgstr ""
+
+msgid "Go to issue boards"
+msgstr ""
+
+msgid "Go to issues"
+msgstr ""
+
+msgid "Go to jobs"
+msgstr ""
+
+msgid "Go to kubernetes"
+msgstr ""
+
+msgid "Go to merge requests"
+msgstr ""
+
+msgid "Go to metrics"
+msgstr ""
+
+msgid "Go to parent"
+msgstr ""
+
+msgid "Go to project"
+msgstr ""
+
+msgid "Go to releases"
+msgstr ""
+
+msgid "Go to repository charts"
+msgstr ""
+
+msgid "Go to repository graph"
+msgstr ""
+
+msgid "Go to snippets"
+msgstr ""
+
+msgid "Go to the activity feed"
+msgstr ""
+
+msgid "Go to the milestone list"
+msgstr ""
+
+msgid "Go to the project's activity feed"
+msgstr ""
+
+msgid "Go to the project's overview page"
+msgstr ""
+
+msgid "Go to wiki"
+msgstr ""
+
+msgid "Go to your To-Do list"
+msgstr ""
+
+msgid "Go to your fork"
+msgstr ""
+
+msgid "Go to your groups"
+msgstr ""
+
+msgid "Go to your issues"
+msgstr ""
+
+msgid "Go to your merge requests"
+msgstr ""
+
+msgid "Go to your projects"
+msgstr ""
+
+msgid "Go to your snippets"
+msgstr ""
+
+msgid "Google Cloud Platform"
+msgstr ""
+
+msgid "Google Code import"
+msgstr ""
+
+msgid "Google Takeout"
+msgstr ""
+
+msgid "Google authentication is not %{link_start}properly configured%{link_end}. Ask your GitLab administrator if you want to use this service."
+msgstr ""
+
+msgid "Got it"
+msgstr ""
+
+msgid "Got it!"
+msgstr ""
+
+msgid "Grafana URL"
+msgstr ""
+
+msgid "Grafana response contains invalid json"
+msgstr ""
+
+msgid "GrafanaIntegration|API Token"
+msgstr ""
+
+msgid "GrafanaIntegration|Active"
+msgstr ""
+
+msgid "GrafanaIntegration|Embed Grafana charts in GitLab issues."
+msgstr ""
+
+msgid "GrafanaIntegration|Enter the Grafana API Token."
+msgstr ""
+
+msgid "GrafanaIntegration|Enter the base URL of the Grafana instance."
+msgstr ""
+
+msgid "GrafanaIntegration|Grafana Authentication"
+msgstr ""
+
+msgid "GrafanaIntegration|Grafana URL"
+msgstr ""
+
+msgid "Grant access"
+msgstr ""
+
+msgid "Graph"
+msgstr ""
+
+msgid "Gravatar"
+msgstr ""
+
+msgid "Gravatar enabled"
+msgstr ""
+
+msgid "Group"
+msgstr ""
+
+msgid "Group %{group_name} couldn't be exported."
+msgstr ""
+
+msgid "Group %{group_name} was exported successfully."
+msgstr ""
+
+msgid "Group %{group_name} was scheduled for deletion."
+msgstr ""
+
+msgid "Group %{group_name} was successfully created."
+msgstr ""
+
+msgid "Group Audit Events"
+msgstr ""
+
+msgid "Group CI/CD settings"
+msgstr ""
+
+msgid "Group Git LFS status:"
+msgstr ""
+
+msgid "Group Hooks"
+msgstr ""
+
+msgid "Group ID"
+msgstr ""
+
+msgid "Group ID: %{group_id}"
+msgstr ""
+
+msgid "Group Owner must have signed in with SAML before enabling Group Managed Accounts"
+msgstr ""
+
+msgid "Group Runners"
+msgstr ""
+
+msgid "Group SAML must be enabled to test"
+msgstr ""
+
+msgid "Group URL"
+msgstr ""
+
+msgid "Group avatar"
+msgstr ""
+
+msgid "Group by:"
+msgstr ""
+
+msgid "Group description"
+msgstr ""
+
+msgid "Group description (optional)"
+msgstr ""
+
+msgid "Group details"
+msgstr ""
+
+msgid "Group export could not be started."
+msgstr ""
+
+msgid "Group export error"
+msgstr ""
+
+msgid "Group export link has expired. Please generate a new export from your group settings."
+msgstr ""
+
+msgid "Group export started. A download link will be sent by email and made available on this page."
+msgstr ""
+
+msgid "Group has been already marked for deletion"
+msgstr ""
+
+msgid "Group has not been marked for deletion"
+msgstr ""
+
+msgid "Group import could not be scheduled"
+msgstr ""
+
+msgid "Group info:"
+msgstr ""
+
+msgid "Group is required when cluster_type is :group"
+msgstr ""
+
+msgid "Group maintainers can register group runners in the %{link}"
+msgstr ""
+
+msgid "Group members"
+msgstr ""
+
+msgid "Group milestone"
+msgstr ""
+
+msgid "Group name"
+msgstr ""
+
+msgid "Group name (your organization)"
+msgstr ""
+
+msgid "Group overview"
+msgstr ""
+
+msgid "Group overview content"
+msgstr ""
+
+msgid "Group path is already taken. Suggestions: "
+msgstr ""
+
+msgid "Group path is available."
+msgstr ""
+
+msgid "Group pipeline minutes were successfully reset."
+msgstr ""
+
+msgid "Group project URLs are prefixed with the group namespace"
+msgstr ""
+
+msgid "Group requires separate account"
+msgstr ""
+
+msgid "Group variables (inherited)"
+msgstr ""
+
+msgid "Group was exported"
+msgstr ""
+
+msgid "Group was successfully updated."
+msgstr ""
+
+msgid "Group: %{group_name}"
+msgstr ""
+
+msgid "Group: %{name}"
+msgstr ""
+
+msgid "GroupActivityMetrics|New Members created"
+msgstr ""
+
+msgid "GroupActivyMetrics|Issues created"
+msgstr ""
+
+msgid "GroupActivyMetrics|Merge Requests created"
+msgstr ""
+
+msgid "GroupActivyMetrics|Recent activity (last 90 days)"
+msgstr ""
+
+msgid "GroupImport|Failed to import group."
+msgstr ""
+
+msgid "GroupImport|Group '%{group_name}' is being imported."
+msgstr ""
+
+msgid "GroupImport|Group could not be imported: %{errors}"
+msgstr ""
+
+msgid "GroupImport|Please wait while we import the group for you. Refresh at will."
+msgstr ""
+
+msgid "GroupImport|The group was successfully imported."
+msgstr ""
+
+msgid "GroupImport|Unable to process group import file"
+msgstr ""
+
+msgid "GroupRoadmap|%{dateWord} – No end date"
+msgstr ""
+
+msgid "GroupRoadmap|%{startDateInWords} – %{endDateInWords}"
+msgstr ""
+
+msgid "GroupRoadmap|No start date – %{dateWord}"
+msgstr ""
+
+msgid "GroupRoadmap|Something went wrong while fetching epics"
+msgstr ""
+
+msgid "GroupRoadmap|Something went wrong while fetching milestones"
+msgstr ""
+
+msgid "GroupRoadmap|Sorry, no epics matched your search"
+msgstr ""
+
+msgid "GroupRoadmap|The roadmap shows the progress of your epics along a timeline"
+msgstr ""
+
+msgid "GroupRoadmap|To view the roadmap, add a start or due date to one of the %{linkStart}child epics%{linkEnd}."
+msgstr ""
+
+msgid "GroupRoadmap|To view the roadmap, add a start or due date to one of your epics in this group or its subgroups; from %{startDate} to %{endDate}."
+msgstr ""
+
+msgid "GroupRoadmap|To widen your search, change or remove filters; from %{startDate} to %{endDate}."
+msgstr ""
+
+msgid "GroupSAML|Certificate fingerprint"
+msgstr ""
+
+msgid "GroupSAML|Configuration"
+msgstr ""
+
+msgid "GroupSAML|Copy SAML Response XML"
+msgstr ""
+
+msgid "GroupSAML|Enable SAML authentication for this group."
+msgstr ""
+
+msgid "GroupSAML|Enforce SSO-only authentication for this group."
+msgstr ""
+
+msgid "GroupSAML|Enforce users to have dedicated group managed accounts for this group."
+msgstr ""
+
+msgid "GroupSAML|Enforced SSO"
+msgstr ""
+
+msgid "GroupSAML|Generate a SCIM token"
+msgstr ""
+
+msgid "GroupSAML|Generate a SCIM token to set up your System for Cross-Domain Identity Management."
+msgstr ""
+
+msgid "GroupSAML|Identity"
+msgstr ""
+
+msgid "GroupSAML|Identity provider single sign on URL"
+msgstr ""
+
+msgid "GroupSAML|Make sure you save this token — you won't be able to access it again."
+msgstr ""
+
+msgid "GroupSAML|Manage your group’s membership while adding another level of security with SAML."
+msgstr ""
+
+msgid "GroupSAML|Members"
+msgstr ""
+
+msgid "GroupSAML|Members will be forwarded here when signing in to your group. Get this from your identity provider, where it can also be called \"SSO Service Location\", \"SAML Token Issuance Endpoint\", or \"SAML 2.0/W-Federation URL\"."
+msgstr ""
+
+msgid "GroupSAML|NameID"
+msgstr ""
+
+msgid "GroupSAML|NameID Format"
+msgstr ""
+
+msgid "GroupSAML|Prohibit outer forks"
+msgstr ""
+
+msgid "GroupSAML|Prohibit outer forks for this group."
+msgstr ""
+
+msgid "GroupSAML|SAML Response Output"
+msgstr ""
+
+msgid "GroupSAML|SAML Response XML"
+msgstr ""
+
+msgid "GroupSAML|SAML Single Sign On"
+msgstr ""
+
+msgid "GroupSAML|SAML Single Sign On Settings"
+msgstr ""
+
+msgid "GroupSAML|SCIM API endpoint URL"
+msgstr ""
+
+msgid "GroupSAML|SCIM Token"
+msgstr ""
+
+msgid "GroupSAML|SHA1 fingerprint of the SAML token signing certificate. Get this from your identity provider, where it can also be called \"Thumbprint\"."
+msgstr ""
+
+msgid "GroupSAML|The SCIM token is now hidden. To see the value of the token again, you need to "
+msgstr ""
+
+msgid "GroupSAML|To be able to enable enforced SSO, you first need to enable SAML authentication."
+msgstr ""
+
+msgid "GroupSAML|To be able to enable group managed accounts, you first need to enable enforced SSO."
+msgstr ""
+
+msgid "GroupSAML|To be able to prohibit outer forks, you first need to enforce dedicate group managed accounts."
+msgstr ""
+
+msgid "GroupSAML|Toggle SAML authentication"
+msgstr ""
+
+msgid "GroupSAML|Valid SAML Response"
+msgstr ""
+
+msgid "GroupSAML|With group managed accounts enabled, all the users without a group managed account will be excluded from the group."
+msgstr ""
+
+msgid "GroupSAML|With prohibit outer forks flag enabled group members will be able to fork project only inside your group."
+msgstr ""
+
+msgid "GroupSAML|Your SCIM token"
+msgstr ""
+
+msgid "GroupSAML|must match stored NameID of \"%{extern_uid}\" as we use this to identify users. If the NameID changes users will be unable to sign in."
+msgstr ""
+
+msgid "GroupSAML|should be \"persistent\""
+msgstr ""
+
+msgid "GroupSAML|should be a random persistent ID, emails are discouraged"
+msgstr ""
+
+msgid "GroupSettings|Apply integration settings to all Projects"
+msgstr ""
+
+msgid "GroupSettings|Auto DevOps pipeline was updated for the group"
+msgstr ""
+
+msgid "GroupSettings|Badges"
+msgstr ""
+
+msgid "GroupSettings|Be careful. Changing a group's parent can have unintended %{side_effects_link_start}side effects%{side_effects_link_end}."
+msgstr ""
+
+msgid "GroupSettings|Cannot update the path because there are projects under this group that contain Docker images in their Container Registry. Please remove the images from your projects first and try again."
+msgstr ""
+
+msgid "GroupSettings|Change group path"
+msgstr ""
+
+msgid "GroupSettings|Changing group path can have unintended side effects."
+msgstr ""
+
+msgid "GroupSettings|Custom project templates"
+msgstr ""
+
+msgid "GroupSettings|Customize your group badges."
+msgstr ""
+
+msgid "GroupSettings|Default to Auto DevOps pipeline for all projects within this group"
+msgstr ""
+
+msgid "GroupSettings|Disable email notifications"
+msgstr ""
+
+msgid "GroupSettings|Disable group mentions"
+msgstr ""
+
+msgid "GroupSettings|Export group"
+msgstr ""
+
+msgid "GroupSettings|If the parent group's visibility is lower than the group current visibility, visibility levels for subgroups and projects will be changed to match the new parent group's visibility."
+msgstr ""
+
+msgid "GroupSettings|Integrations configured here will automatically apply to all projects in this group."
+msgstr ""
+
+msgid "GroupSettings|Learn more about badges."
+msgstr ""
+
+msgid "GroupSettings|Learn more about group-level project templates."
+msgstr ""
+
+msgid "GroupSettings|New runners registration token has been generated!"
+msgstr ""
+
+msgid "GroupSettings|Pipeline settings was updated for the group"
+msgstr ""
+
+msgid "GroupSettings|Please choose a group path with no special characters."
+msgstr ""
+
+msgid "GroupSettings|Prevent sharing a project within %{group} with other groups"
+msgstr ""
+
+msgid "GroupSettings|Select a sub-group as the custom project template source for this group."
+msgstr ""
+
+msgid "GroupSettings|The Auto DevOps pipeline will run if no alternative CI configuration file is found."
+msgstr ""
+
+msgid "GroupSettings|There was a problem updating Auto DevOps pipeline: %{error_messages}."
+msgstr ""
+
+msgid "GroupSettings|There was a problem updating the pipeline settings: %{error_messages}."
+msgstr ""
+
+msgid "GroupSettings|This setting is applied on %{ancestor_group} and has been overridden on this subgroup."
+msgstr ""
+
+msgid "GroupSettings|This setting is applied on %{ancestor_group}. To share projects in this group with another group, ask the owner to override the setting or %{remove_ancestor_share_with_group_lock}."
+msgstr ""
+
+msgid "GroupSettings|This setting is applied on %{ancestor_group}. You can override the setting or %{remove_ancestor_share_with_group_lock}."
+msgstr ""
+
+msgid "GroupSettings|This setting will be applied to all subgroups unless overridden by a group owner. Groups that already have access to the project will continue to have access unless removed manually."
+msgstr ""
+
+msgid "GroupSettings|This setting will override user notification preferences for all members of the group, subgroups, and projects."
+msgstr ""
+
+msgid "GroupSettings|This setting will prevent group members from being notified if the group is mentioned."
+msgstr ""
+
+msgid "GroupSettings|Transfer group"
+msgstr ""
+
+msgid "GroupSettings|You can only transfer the group to a group you manage."
+msgstr ""
+
+msgid "GroupSettings|You will need to update your local repositories to point to the new location."
+msgstr ""
+
+msgid "GroupSettings|cannot be changed by you"
+msgstr ""
+
+msgid "GroupSettings|cannot be disabled when the parent group \"Share with group lock\" is enabled, except by the owner of the parent group"
+msgstr ""
+
+msgid "GroupSettings|cannot change when group contains projects with NPM packages"
+msgstr ""
+
+msgid "GroupSettings|remove the share with group lock from %{ancestor_group_name}"
+msgstr ""
+
+msgid "Groups"
+msgstr ""
+
+msgid "Groups (%{count})"
+msgstr ""
+
+msgid "Groups (%{groups})"
+msgstr ""
+
+msgid "Groups and projects"
+msgstr ""
+
+msgid "Groups can also be nested by creating %{subgroup_docs_link_start}subgroups%{subgroup_docs_link_end}."
+msgstr ""
+
+msgid "Groups to synchronize"
+msgstr ""
+
+msgid "Groups with access to %{strong_start}%{group_name}%{strong_end}"
+msgstr ""
+
+msgid "Groups with access to <strong>%{project_name}</strong>"
+msgstr ""
+
+msgid "GroupsDropdown|Frequently visited"
+msgstr ""
+
+msgid "GroupsDropdown|Groups you visit often will appear here"
+msgstr ""
+
+msgid "GroupsDropdown|Loading groups"
+msgstr ""
+
+msgid "GroupsDropdown|Search your groups"
+msgstr ""
+
+msgid "GroupsDropdown|Something went wrong on our end."
+msgstr ""
+
+msgid "GroupsDropdown|Sorry, no groups matched your search"
+msgstr ""
+
+msgid "GroupsDropdown|This feature requires browser localStorage support"
+msgstr ""
+
+msgid "GroupsEmptyState|A group is a collection of several projects."
+msgstr ""
+
+msgid "GroupsEmptyState|If you organize your projects under a group, it works like a folder."
+msgstr ""
+
+msgid "GroupsEmptyState|No groups found"
+msgstr ""
+
+msgid "GroupsEmptyState|You can manage your group member’s permissions and access to each project in the group."
+msgstr ""
+
+msgid "GroupsNew|Contact an administrator to enable options for importing your group."
+msgstr ""
+
+msgid "GroupsNew|Create"
+msgstr ""
+
+msgid "GroupsNew|Create group"
+msgstr ""
+
+msgid "GroupsNew|GitLab group export"
+msgstr ""
+
+msgid "GroupsNew|Import"
+msgstr ""
+
+msgid "GroupsNew|Import group"
+msgstr ""
+
+msgid "GroupsNew|My Awesome Group"
+msgstr ""
+
+msgid "GroupsNew|No import options available"
+msgstr ""
+
+msgid "GroupsNew|To copy a GitLab group between installations, navigate to the group settings page for the original installation, generate an export file, and upload it here."
+msgstr ""
+
+msgid "GroupsTree|Are you sure you want to leave the \"%{fullName}\" group?"
+msgstr ""
+
+msgid "GroupsTree|Create a project in this group."
+msgstr ""
+
+msgid "GroupsTree|Create a subgroup in this group."
+msgstr ""
+
+msgid "GroupsTree|Edit group"
+msgstr ""
+
+msgid "GroupsTree|Failed to leave the group. Please make sure you are not the only owner."
+msgstr ""
+
+msgid "GroupsTree|Leave this group"
+msgstr ""
+
+msgid "GroupsTree|Loading groups"
+msgstr ""
+
+msgid "GroupsTree|No groups matched your search"
+msgstr ""
+
+msgid "GroupsTree|No groups or projects matched your search"
+msgstr ""
+
+msgid "GroupsTree|Search by name"
+msgstr ""
+
+msgid "Guideline"
+msgstr ""
+
+msgid "HTTP Basic: Access denied\\nYou must use a personal access token with 'api' scope for Git over HTTP.\\nYou can generate one at %{profile_personal_access_tokens_url}"
+msgstr ""
+
+msgid "Hashed Storage must be enabled to use Geo"
+msgstr ""
+
+msgid "Hashed repository storage paths"
+msgstr ""
+
+msgid "Hashed storage can't be disabled anymore for new projects"
+msgstr ""
+
+msgid "Have your users email"
+msgstr ""
+
+msgid "Header logo was successfully removed."
+msgstr ""
+
+msgid "Header message"
+msgstr ""
+
+msgid "Headings"
+msgstr ""
+
+msgid "Health"
+msgstr ""
+
+msgid "Health Check"
+msgstr ""
+
+msgid "Health information can be retrieved from the following endpoints. More information is available"
+msgstr ""
+
+msgid "HealthCheck|Access token is"
+msgstr ""
+
+msgid "HealthCheck|Healthy"
+msgstr ""
+
+msgid "HealthCheck|No Health Problems Detected"
+msgstr ""
+
+msgid "HealthCheck|Unhealthy"
+msgstr ""
+
+msgid "Hello there"
+msgstr ""
+
+msgid "Help"
+msgstr ""
+
+msgid "Help page"
+msgstr ""
+
+msgid "Help page text and support page url."
+msgstr ""
+
+msgid "Helps prevent bots from brute-force attacks."
+msgstr ""
+
+msgid "Helps prevent bots from creating accounts."
+msgstr ""
+
+msgid "Helps reduce alert volume (e.g. if creating too many issues)"
+msgstr ""
+
+msgid "Helps reduce request volume for protected paths"
+msgstr ""
+
+msgid "Here are all your projects in your group, including the one you just created. To start, let’s take a look at your personalized learning project which will help you learn about GitLab at your own pace."
+msgstr ""
+
+msgid "Here you will find recent merge request activity"
+msgstr ""
+
+msgid "Hi %{username}!"
+msgstr ""
+
+msgid "Hide archived projects"
+msgstr ""
+
+msgid "Hide chart"
+msgid_plural "Hide charts"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Hide details"
+msgstr ""
+
+msgid "Hide file browser"
+msgstr ""
+
+msgid "Hide group projects"
+msgstr ""
+
+msgid "Hide host keys manual input"
+msgstr ""
+
+msgid "Hide list"
+msgstr ""
+
+msgid "Hide marketing-related entries from help"
+msgstr ""
+
+msgid "Hide payload"
+msgstr ""
+
+msgid "Hide shared projects"
+msgstr ""
+
+msgid "Hide stage"
+msgstr ""
+
+msgid "Hide value"
+msgid_plural "Hide values"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Hide values"
+msgstr ""
+
+msgid "High or unknown vulnerabilities present"
+msgstr ""
+
+msgid "Highest number of requests per minute for each raw path, default to 300. To disable throttling set to 0."
+msgstr ""
+
+msgid "Highest role:"
+msgstr ""
+
+msgid "History"
+msgstr ""
+
+msgid "History of authentications"
+msgstr ""
+
+msgid "Homepage"
+msgstr ""
+
+msgid "Hook execution failed. Ensure the group has a project with commits."
+msgstr ""
+
+msgid "Hook was successfully created."
+msgstr ""
+
+msgid "Hook was successfully updated."
+msgstr ""
+
+msgid "Hostname"
+msgstr ""
+
+msgid "Hour (UTC)"
+msgstr ""
+
+msgid "Housekeeping"
+msgstr ""
+
+msgid "Housekeeping successfully started"
+msgstr ""
+
+msgid "Housekeeping, export, path, transfer, remove, archive."
+msgstr ""
+
+msgid "How it works"
+msgstr ""
+
+msgid "How many days need to pass between marking entity for deletion and actual removing it."
+msgstr ""
+
+msgid "How many replicas each Elasticsearch shard has."
+msgstr ""
+
+msgid "How many shards to split the Elasticsearch index over."
+msgstr ""
+
+msgid "How many users will be evaluating the trial?"
+msgstr ""
+
+msgid "However, you are already a member of this %{member_source}. Sign in using a different account to accept the invitation."
+msgstr ""
+
+msgid "I accept the %{terms_link_start}Terms of Service and Privacy Policy%{terms_link_end}"
+msgstr ""
+
+msgid "I accept the %{terms_link}"
+msgstr ""
+
+msgid "I accept the|Terms of Service and Privacy Policy"
+msgstr ""
+
+msgid "I forgot my password"
+msgstr ""
+
+msgid "I have read and agree to the Let's Encrypt %{link_start}Terms of Service%{link_end} (PDF)"
+msgstr ""
+
+msgid "I'd like to receive updates via email about GitLab"
+msgstr ""
+
+msgid "ID"
+msgstr ""
+
+msgid "ID:"
+msgstr ""
+
+msgid "IDE"
+msgstr ""
+
+msgid "IDE|Allow live previews of JavaScript projects in the Web IDE using CodeSandbox Live Preview."
+msgstr ""
+
+msgid "IDE|Back"
+msgstr ""
+
+msgid "IDE|Commit"
+msgstr ""
+
+msgid "IDE|Commit to %{branchName} branch"
+msgstr ""
+
+msgid "IDE|Edit"
+msgstr ""
+
+msgid "IDE|Get started with Live Preview"
+msgstr ""
+
+msgid "IDE|Go to project"
+msgstr ""
+
+msgid "IDE|Live Preview"
+msgstr ""
+
+msgid "IDE|Preview your web application using Web IDE client-side evaluation."
+msgstr ""
+
+msgid "IDE|Refresh preview"
+msgstr ""
+
+msgid "IDE|Review"
+msgstr ""
+
+msgid "IDE|Successful commit"
+msgstr ""
+
+msgid "IDE|This option is disabled because you are not allowed to create merge requests in this project."
+msgstr ""
+
+msgid "IDE|This option is disabled because you don't have write permissions for the current branch."
+msgstr ""
+
+msgid "INFO: Your SSH key has expired. Please generate a new key."
+msgstr ""
+
+msgid "INFO: Your SSH key is expiring soon. Please generate a new key."
+msgstr ""
+
+msgid "IP Address"
+msgstr ""
+
+msgid "IP subnet restriction only allowed for top-level groups"
+msgstr ""
+
+msgid "Identifier"
+msgstr ""
+
+msgid "Identifiers"
+msgstr ""
+
+msgid "Identities"
+msgstr ""
+
+msgid "If any indexed field exceeds this limit it will be truncated to this number of characters and the rest will not be indexed or searchable. This does not apply to repository and wiki indexing. Setting this to 0 means it is unlimited."
+msgstr ""
+
+msgid "If any job surpasses this timeout threshold, it will be marked as failed. Human readable time input language is accepted like \"1 hour\". Values without specification represent seconds."
+msgstr ""
+
+msgid "If blank, set allowable lifetime to %{instance_level_policy_in_words}, as defined by the instance admin. Once set, existing tokens for users in this group may be revoked."
+msgstr ""
+
+msgid "If checked, group owners can manage LDAP group links and LDAP member overrides"
+msgstr ""
+
+msgid "If checked, new group memberships and permissions can only be added via LDAP synchronization"
+msgstr ""
+
+msgid "If disabled, a diverged local branch will not be automatically updated with commits from its remote counterpart, to prevent local data loss. If the default branch (%{default_branch}) has diverged and cannot be updated, mirroring will fail. Other diverged branches are silently ignored."
+msgstr ""
+
+msgid "If disabled, only admins will be able to configure repository mirroring."
+msgstr ""
+
+msgid "If disabled, the access level will depend on the user's permissions in the project."
+msgstr ""
+
+msgid "If enabled"
+msgstr ""
+
+msgid "If enabled, GitLab will handle Object Storage replication using Geo. %{linkStart}More information%{linkEnd}"
+msgstr ""
+
+msgid "If enabled, access to projects will be validated on an external service using their classification label."
+msgstr ""
+
+msgid "If there is no previous license or if the previous license has expired, some GitLab functionality will be blocked until a new, valid license is uploaded."
+msgstr ""
+
+msgid "If this was a mistake you can %{leave_link_start}leave the %{source_type}%{link_end}."
+msgstr ""
+
+msgid "If this was a mistake you can leave the %{source_type}."
+msgstr ""
+
+msgid "If using GitHub, you’ll see pipeline statuses on GitHub for your commits and pull requests. %{more_info_link}"
+msgstr ""
+
+msgid "If you did not recently sign in, you should immediately %{password_link_start}change your password%{password_link_end}."
+msgstr ""
+
+msgid "If you did not recently sign in, you should immediately change your password: %{password_link}."
+msgstr ""
+
+msgid "If you lose your recovery codes you can generate new ones, invalidating all previous codes."
+msgstr ""
+
+msgid "If you reach 100%% storage capacity, you will not be able to: %{base_message}"
+msgstr ""
+
+msgid "If you recently signed in and recognize the IP address, you may disregard this email."
+msgstr ""
+
+msgid "If you remove this license, GitLab will fall back on the previous license, if any."
+msgstr ""
+
+msgid "If your HTTP repository is not publicly accessible, add your credentials."
+msgstr ""
+
+msgid "Iglu registry URL (optional)"
+msgstr ""
+
+msgid "Ignore"
+msgstr ""
+
+msgid "Ignored"
+msgstr ""
+
+msgid "Image Details"
+msgstr ""
+
+msgid "Image URL"
+msgstr ""
+
+msgid "ImageDiffViewer|2-up"
+msgstr ""
+
+msgid "ImageDiffViewer|Onion skin"
+msgstr ""
+
+msgid "ImageDiffViewer|Swipe"
+msgstr ""
+
+msgid "ImageViewerDimensions|H"
+msgstr ""
+
+msgid "ImageViewerDimensions|W"
+msgstr ""
+
+msgid "Impersonation Tokens"
+msgstr ""
+
+msgid "Impersonation has been disabled"
+msgstr ""
+
+msgid "Import"
+msgstr ""
+
+msgid "Import CSV"
+msgstr ""
+
+msgid "Import Projects from Gitea"
+msgstr ""
+
+msgid "Import all compatible projects"
+msgstr ""
+
+msgid "Import all compatible repositories"
+msgstr ""
+
+msgid "Import all projects"
+msgstr ""
+
+msgid "Import all repositories"
+msgstr ""
+
+msgid "Import an exported GitLab project"
+msgstr ""
+
+msgid "Import failed due to a GitHub error: %{original}"
+msgstr ""
+
+msgid "Import from"
+msgstr ""
+
+msgid "Import from Jira"
+msgstr ""
+
+msgid "Import in progress"
+msgstr ""
+
+msgid "Import in progress. Refresh page to see newly added issues."
+msgstr ""
+
+msgid "Import issues"
+msgstr ""
+
+msgid "Import members"
+msgstr ""
+
+msgid "Import members from another project"
+msgstr ""
+
+msgid "Import multiple repositories by uploading a manifest file."
+msgstr ""
+
+msgid "Import project"
+msgstr ""
+
+msgid "Import project members"
+msgstr ""
+
+msgid "Import projects from Bitbucket"
+msgstr ""
+
+msgid "Import projects from Bitbucket Server"
+msgstr ""
+
+msgid "Import projects from FogBugz"
+msgstr ""
+
+msgid "Import projects from GitLab.com"
+msgstr ""
+
+msgid "Import projects from Google Code"
+msgstr ""
+
+msgid "Import repositories from Bitbucket Server"
+msgstr ""
+
+msgid "Import repositories from GitHub"
+msgstr ""
+
+msgid "Import repository"
+msgstr ""
+
+msgid "Import started by: %{importInitiator}"
+msgstr ""
+
+msgid "Import tasks"
+msgstr ""
+
+msgid "Import tasks from Phabricator into issues"
+msgstr ""
+
+msgid "Import timed out. Import took longer than %{import_jobs_expiration} seconds"
+msgstr ""
+
+msgid "Import/Export illustration"
+msgstr ""
+
+msgid "ImportButtons|Connect repositories from"
+msgstr ""
+
+msgid "ImportProjects|Blocked import URL: %{message}"
+msgstr ""
+
+msgid "ImportProjects|Error importing repository %{project_safe_import_url} into %{project_full_path} - %{message}"
+msgstr ""
+
+msgid "ImportProjects|Importing the project failed"
+msgstr ""
+
+msgid "ImportProjects|Requesting your %{provider} repositories failed"
+msgstr ""
+
+msgid "ImportProjects|Select the projects you want to import"
+msgstr ""
+
+msgid "ImportProjects|The remote data could not be imported."
+msgstr ""
+
+msgid "ImportProjects|The repository could not be created."
+msgstr ""
+
+msgid "ImportProjects|Update of imported projects with realtime changes failed"
+msgstr ""
+
+msgid "Improve Issue boards"
+msgstr ""
+
+msgid "Improve Issue boards with GitLab Enterprise Edition."
+msgstr ""
+
+msgid "Improve Merge Requests and customer support with GitLab Enterprise Edition."
+msgstr ""
+
+msgid "Improve search with Advanced Global Search and GitLab Enterprise Edition."
+msgstr ""
+
+msgid "In %{time_to_now}"
+msgstr ""
+
+msgid "In order to enable instance-level analytics, please ask an admin to enable %{usage_ping_link_start}usage ping%{usage_ping_link_end}."
+msgstr ""
+
+msgid "In order to gather accurate feature usage data, it can take 1 to 2 weeks to see your index."
+msgstr ""
+
+msgid "In order to personalize your experience with GitLab<br>we would like to know a bit more about you."
+msgstr ""
+
+msgid "In order to tailor your experience with GitLab we<br>would like to know a bit more about you."
+msgstr ""
+
+msgid "In progress"
+msgstr ""
+
+msgid "In the next step, you'll be able to select the projects you want to import."
+msgstr ""
+
+msgid "Incident Management Limits"
+msgstr ""
+
+msgid "Incidents"
+msgstr ""
+
+msgid "Include a Terms of Service agreement and Privacy Policy that all users must accept."
+msgstr ""
+
+msgid "Include author name in notification email body"
+msgstr ""
+
+msgid "Include description in commit message"
+msgstr ""
+
+msgid "Include merge request description"
+msgstr ""
+
+msgid "Include the username in the URL if required: <code>https://username@gitlab.company.com/group/project.git</code>."
+msgstr ""
+
+msgid "Includes LFS objects. It can be overridden per group, or per project. 0 for unlimited."
+msgstr ""
+
+msgid "Includes an MVC structure to help you get started."
+msgstr ""
+
+msgid "Includes an MVC structure, Gemfile, Rakefile, along with many others, to help you get started."
+msgstr ""
+
+msgid "Includes an MVC structure, mvnw and pom.xml to help you get started."
+msgstr ""
+
+msgid "Includes repository storage, wiki storage, LFS objects, build artifacts and packages. 0 for unlimited."
+msgstr ""
+
+msgid "Incoming email"
+msgstr ""
+
+msgid "Incoming!"
+msgstr ""
+
+msgid "Incompatible Project"
+msgstr ""
+
+msgid "Incompatible options set!"
+msgstr ""
+
+msgid "Incompatible project"
+msgstr ""
+
+msgid "Indent"
+msgstr ""
+
+msgid "Index all projects"
+msgstr ""
+
+msgid "Indicates whether this runner can pick jobs without tags"
+msgstr ""
+
+msgid "Inform users without uploaded SSH keys that they can't push over SSH until one is added"
+msgstr ""
+
+msgid "Information about additional Pages templates and how to install them can be found in our %{pages_getting_started_guide}."
+msgstr ""
+
+msgid "Inherited:"
+msgstr ""
+
+msgid "Inline"
+msgstr ""
+
+msgid "Input host keys manually"
+msgstr ""
+
+msgid "Input your repository URL"
+msgstr ""
+
+msgid "Insert"
+msgstr ""
+
+msgid "Insert a code block"
+msgstr ""
+
+msgid "Insert a quote"
+msgstr ""
+
+msgid "Insert an image"
+msgstr ""
+
+msgid "Insert code"
+msgstr ""
+
+msgid "Insert inline code"
+msgstr ""
+
+msgid "Insert suggestion"
+msgstr ""
+
+msgid "Insights"
+msgstr ""
+
+msgid "Insights|Some items are not visible beacuse the project was filtered out in the insights.yml file (see the projects.only config for more information)."
+msgstr ""
+
+msgid "Insights|This project is filtered out in the insights.yml file (see the projects.only config for more information)."
+msgstr ""
+
+msgid "Install"
+msgstr ""
+
+msgid "Install GitLab Runner"
+msgstr ""
+
+msgid "Install Runner on Kubernetes"
+msgstr ""
+
+msgid "Install a soft token authenticator like %{free_otp_link} or Google Authenticator from your application repository and use that app to scan this QR code. More information is available in the %{help_link_start}documentation%{help_link_end}."
+msgstr ""
+
+msgid "Install on clusters"
+msgstr ""
+
+msgid "Installed"
+msgstr ""
+
+msgid "Installing"
+msgstr ""
+
+msgid "Instance"
+msgid_plural "Instances"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Instance Configuration"
+msgstr ""
+
+msgid "Instance Statistics visibility"
+msgstr ""
+
+msgid "Instance administrators group already exists"
+msgstr ""
+
+msgid "Instance does not support multiple Kubernetes clusters"
+msgstr ""
+
+msgid "Instance license"
+msgstr ""
+
+msgid "Integration"
+msgstr ""
+
+msgid "Integration Settings"
+msgstr ""
+
+msgid "Integrations"
+msgstr ""
+
+msgid "Integrations|All details"
+msgstr ""
+
+msgid "Integrations|Comment detail:"
+msgstr ""
+
+msgid "Integrations|Comment settings:"
+msgstr ""
+
+msgid "Integrations|Enable comments"
+msgstr ""
+
+msgid "Integrations|Includes Standard plus entire commit message, commit hash, and issue IDs"
+msgstr ""
+
+msgid "Integrations|Includes commit title and branch"
+msgstr ""
+
+msgid "Integrations|Standard"
+msgstr ""
+
+msgid "Integrations|When a Jira issue is mentioned in a commit or merge request a remote link and comment (if enabled) will be created."
+msgstr ""
+
+msgid "Interested parties can even contribute by pushing commits if they want to."
+msgstr ""
+
+msgid "Internal"
+msgstr ""
+
+msgid "Internal - The group and any internal projects can be viewed by any logged in user."
+msgstr ""
+
+msgid "Internal - The project can be accessed by any logged in user."
+msgstr ""
+
+msgid "Internal URL (optional)"
+msgstr ""
+
+msgid "Internal users"
+msgstr ""
+
+msgid "Interval Pattern"
+msgstr ""
+
+msgid "Introducing Value Stream Analytics"
+msgstr ""
+
+msgid "Introducing Your DevOps Score"
+msgstr ""
+
+msgid "Invalid Git ref"
+msgstr ""
+
+msgid "Invalid Insights config file detected"
+msgstr ""
+
+msgid "Invalid Login or password"
+msgstr ""
+
+msgid "Invalid URL"
+msgstr ""
+
+msgid "Invalid container_name"
+msgstr ""
+
+msgid "Invalid cursor parameter"
+msgstr ""
+
+msgid "Invalid cursor value provided"
+msgstr ""
+
+msgid "Invalid date"
+msgstr ""
+
+msgid "Invalid date format. Please use UTC format as YYYY-MM-DD"
+msgstr ""
+
+msgid "Invalid date range"
+msgstr ""
+
+msgid "Invalid feature"
+msgstr ""
+
+msgid "Invalid field"
+msgstr ""
+
+msgid "Invalid file format with specified file type"
+msgstr ""
+
+msgid "Invalid file."
+msgstr ""
+
+msgid "Invalid import params"
+msgstr ""
+
+msgid "Invalid input, please avoid emojis"
+msgstr ""
+
+msgid "Invalid login or password"
+msgstr ""
+
+msgid "Invalid pin code"
+msgstr ""
+
+msgid "Invalid pod_name"
+msgstr ""
+
+msgid "Invalid query"
+msgstr ""
+
+msgid "Invalid repository bundle for snippet with id %{snippet_id}"
+msgstr ""
+
+msgid "Invalid repository path"
+msgstr ""
+
+msgid "Invalid search parameter"
+msgstr ""
+
+msgid "Invalid server response"
+msgstr ""
+
+msgid "Invalid start or end time format"
+msgstr ""
+
+msgid "Invalid status"
+msgstr ""
+
+msgid "Invalid two-factor code."
+msgstr ""
+
+msgid "Invalid yaml"
+msgstr ""
+
+msgid "Invitation"
+msgstr ""
+
+msgid "Invite"
+msgstr ""
+
+msgid "Invite \"%{trimmed}\" by email"
+msgstr ""
+
+msgid "Invite Members"
+msgstr ""
+
+msgid "Invite group"
+msgstr ""
+
+msgid "Invite member"
+msgstr ""
+
+msgid "Invocations"
+msgstr ""
+
+msgid "Is blocked by"
+msgstr ""
+
+msgid "Is this GitLab trial for your company?"
+msgstr ""
+
+msgid "Is using license seat:"
+msgstr ""
+
+msgid "Is using seat"
+msgstr ""
+
+msgid "IssuableStatus|Closed"
+msgstr ""
+
+msgid "IssuableStatus|Closed (%{link})"
+msgstr ""
+
+msgid "IssuableStatus|duplicated"
+msgstr ""
+
+msgid "IssuableStatus|moved"
+msgstr ""
+
+msgid "IssuableStatus|promoted"
+msgstr ""
+
+msgid "Issue"
+msgstr ""
+
+msgid "Issue %{issue_reference} has already been added to epic %{epic_reference}."
+msgstr ""
+
+msgid "Issue Boards"
+msgstr ""
+
+msgid "Issue already promoted to epic."
+msgstr ""
+
+msgid "Issue cannot be found."
+msgstr ""
+
+msgid "Issue events"
+msgstr ""
+
+msgid "Issue first depoloyed to production"
+msgstr ""
+
+msgid "Issue label"
+msgstr ""
+
+msgid "Issue or Merge Request ID is required"
+msgstr ""
+
+msgid "Issue published on status page."
+msgstr ""
+
+msgid "Issue template (optional)"
+msgstr ""
+
+msgid "Issue update failed"
+msgstr ""
+
+msgid "Issue was closed by %{name} %{reason}"
+msgstr ""
+
+msgid "Issue weight"
+msgstr ""
+
+msgid "IssueAnalytics|Age"
+msgstr ""
+
+msgid "IssueAnalytics|Assignees"
+msgstr ""
+
+msgid "IssueAnalytics|Due date"
+msgstr ""
+
+msgid "IssueAnalytics|Failed to load issues. Please try again."
+msgstr ""
+
+msgid "IssueAnalytics|Issue"
+msgstr ""
+
+msgid "IssueAnalytics|Milestone"
+msgstr ""
+
+msgid "IssueAnalytics|Opened by"
+msgstr ""
+
+msgid "IssueAnalytics|Status"
+msgstr ""
+
+msgid "IssueAnalytics|Weight"
+msgstr ""
+
+msgid "IssueBoards|Board"
+msgstr ""
+
+msgid "IssueBoards|Boards"
+msgstr ""
+
+msgid "IssueBoards|Create new board"
+msgstr ""
+
+msgid "IssueBoards|Delete board"
+msgstr ""
+
+msgid "IssueBoards|No matching boards found"
+msgstr ""
+
+msgid "IssueBoards|Some of your boards are hidden, activate a license to see them again."
+msgstr ""
+
+msgid "IssueBoards|Switch board"
+msgstr ""
+
+msgid "IssueTracker|Bugzilla issue tracker"
+msgstr ""
+
+msgid "IssueTracker|Custom issue tracker"
+msgstr ""
+
+msgid "IssueTracker|GitLab issue tracker"
+msgstr ""
+
+msgid "IssueTracker|Redmine issue tracker"
+msgstr ""
+
+msgid "IssueTracker|YouTrack issue tracker"
+msgstr ""
+
+msgid "Issues"
+msgstr ""
+
+msgid "Issues Analytics"
+msgstr ""
+
+msgid "Issues Rate Limits"
+msgstr ""
+
+msgid "Issues and Merge Requests"
+msgstr ""
+
+msgid "Issues can be bugs, tasks or ideas to be discussed. Also, issues are searchable and filterable."
+msgstr ""
+
+msgid "Issues closed"
+msgstr ""
+
+msgid "Issues referenced by merge requests and commits within the default branch will be closed automatically"
+msgstr ""
+
+msgid "Issues successfully imported with the label"
+msgstr ""
+
+msgid "Issues with comments, merge requests with diffs and comments, labels, milestones, snippets, and other project entities"
+msgstr ""
+
+msgid "Issues, merge requests, pushes, and comments."
+msgstr ""
+
+msgid "IssuesAnalytics|After you begin creating issues for your projects, we can start tracking and displaying metrics for them"
+msgstr ""
+
+msgid "IssuesAnalytics|Avg/Month:"
+msgstr ""
+
+msgid "IssuesAnalytics|Issues opened"
+msgstr ""
+
+msgid "IssuesAnalytics|Issues opened per month"
+msgstr ""
+
+msgid "IssuesAnalytics|Last 12 months"
+msgstr ""
+
+msgid "IssuesAnalytics|Sorry, your filter produced no results"
+msgstr ""
+
+msgid "IssuesAnalytics|There are no issues for the projects in your group"
+msgstr ""
+
+msgid "IssuesAnalytics|To widen your search, change or remove filters in the filter bar above"
+msgstr ""
+
+msgid "IssuesAnalytics|Total:"
+msgstr ""
+
+msgid "Issue|Title"
+msgstr ""
+
+msgid "It must have a header row and at least two columns: the first column is the issue title and the second column is the issue description. The separator is automatically detected."
+msgstr ""
+
+msgid "It seems like the Dependency Scanning job ran successfully, but no dependencies have been detected in your project."
+msgstr ""
+
+msgid "It seems that there is currently no available data for code coverage"
+msgstr ""
+
+msgid "It's you"
+msgstr ""
+
+msgid "Iteration"
+msgstr ""
+
+msgid "Iteration changed to"
+msgstr ""
+
+msgid "Iteration removed"
+msgstr ""
+
+msgid "Iteration updated"
+msgstr ""
+
+msgid "Iterations"
+msgstr ""
+
+msgid "Iteration|Dates cannot overlap with other existing Iterations"
+msgstr ""
+
+msgid "Iteration|cannot be in the past"
+msgstr ""
+
+msgid "Iteration|cannot be more than 500 years in the future"
+msgstr ""
+
+msgid "I’m familiar with the basics of project management and DevOps."
+msgstr ""
+
+msgid "I’m not very familiar with the basics of project management and DevOps."
+msgstr ""
+
+msgid "Jaeger URL"
+msgstr ""
+
+msgid "Jaeger tracing"
+msgstr ""
+
+msgid "Jan"
+msgstr ""
+
+msgid "January"
+msgstr ""
+
+msgid "Jira Issues"
+msgstr ""
+
+msgid "Jira import is already running."
+msgstr ""
+
+msgid "Jira integration not configured."
+msgstr ""
+
+msgid "Jira project key is not configured"
+msgstr ""
+
+msgid "Jira project: %{importProject}"
+msgstr ""
+
+msgid "Jira service not configured."
+msgstr ""
+
+msgid "JiraService| on branch %{branch_link}"
+msgstr ""
+
+msgid "JiraService|%{user_link} mentioned this issue in %{entity_link} of %{project_link}%{branch}:{quote}%{entity_message}{quote}"
+msgstr ""
+
+msgid "JiraService|Events for %{noteable_model_name} are disabled."
+msgstr ""
+
+msgid "JiraService|If different from Web URL"
+msgstr ""
+
+msgid "JiraService|Jira API URL"
+msgstr ""
+
+msgid "JiraService|Jira comments will be created when an issue gets referenced in a commit."
+msgstr ""
+
+msgid "JiraService|Jira comments will be created when an issue gets referenced in a merge request."
+msgstr ""
+
+msgid "JiraService|Jira issue tracker"
+msgstr ""
+
+msgid "JiraService|Password or API token"
+msgstr ""
+
+msgid "JiraService|Transition ID(s)"
+msgstr ""
+
+msgid "JiraService|Use , or ; to separate multiple transition IDs"
+msgstr ""
+
+msgid "JiraService|Use a password for server version and an API token for cloud version"
+msgstr ""
+
+msgid "JiraService|Use a username for server version and an email for cloud version"
+msgstr ""
+
+msgid "JiraService|Username or Email"
+msgstr ""
+
+msgid "JiraService|Web URL"
+msgstr ""
+
+msgid "JiraService|transition ids can have only numbers which can be split with , or ;"
+msgstr ""
+
+msgid "Job"
+msgstr ""
+
+msgid "Job Failed #%{build_id}"
+msgstr ""
+
+msgid "Job ID"
+msgstr ""
+
+msgid "Job has been erased"
+msgstr ""
+
+msgid "Job has been successfully erased!"
+msgstr ""
+
+msgid "Job has wrong arguments format."
+msgstr ""
+
+msgid "Job is missing the `model_type` argument."
+msgstr ""
+
+msgid "Job is stuck. Check runners."
+msgstr ""
+
+msgid "Job logs and artifacts"
+msgstr ""
+
+msgid "Job to create self-monitoring project is in progress"
+msgstr ""
+
+msgid "Job to delete self-monitoring project is in progress"
+msgstr ""
+
+msgid "Job was retried"
+msgstr ""
+
+msgid "Jobs"
+msgstr ""
+
+msgid "Job|Browse"
+msgstr ""
+
+msgid "Job|Complete Raw"
+msgstr ""
+
+msgid "Job|Download"
+msgstr ""
+
+msgid "Job|Erase job log"
+msgstr ""
+
+msgid "Job|Job artifacts"
+msgstr ""
+
+msgid "Job|Job has been erased"
+msgstr ""
+
+msgid "Job|Job has been erased by"
+msgstr ""
+
+msgid "Job|Keep"
+msgstr ""
+
+msgid "Job|Pipeline"
+msgstr ""
+
+msgid "Job|Scroll to bottom"
+msgstr ""
+
+msgid "Job|Scroll to top"
+msgstr ""
+
+msgid "Job|Show complete raw"
+msgstr ""
+
+msgid "Job|The artifacts were removed"
+msgstr ""
+
+msgid "Job|The artifacts will be removed"
+msgstr ""
+
+msgid "Job|These artifacts are the latest. They will not be deleted (even if expired) until newer artifacts are available."
+msgstr ""
+
+msgid "Job|This job failed because the necessary resources were not successfully created."
+msgstr ""
+
+msgid "Job|This job is stuck because the project doesn't have any runners online assigned to it."
+msgstr ""
+
+msgid "Job|for"
+msgstr ""
+
+msgid "Job|into"
+msgstr ""
+
+msgid "Job|with"
+msgstr ""
+
+msgid "Join Zoom meeting"
+msgstr ""
+
+msgid "Jul"
+msgstr ""
+
+msgid "July"
+msgstr ""
+
+msgid "Jump to first unresolved thread"
+msgstr ""
+
+msgid "Jump to next unresolved thread"
+msgstr ""
+
+msgid "Jun"
+msgstr ""
+
+msgid "June"
+msgstr ""
+
+msgid "Just me"
+msgstr ""
+
+msgid "Keep divergent refs"
+msgstr ""
+
+msgid "Kerberos access denied"
+msgstr ""
+
+msgid "Key"
+msgstr ""
+
+msgid "Key (PEM)"
+msgstr ""
+
+msgid "Key: %{key}"
+msgstr ""
+
+msgid "Keyboard shortcuts"
+msgstr ""
+
+msgid "Keys"
+msgstr ""
+
+msgid "Ki"
+msgstr ""
+
+msgid "Kubernetes"
+msgstr ""
+
+msgid "Kubernetes API returned status code: %{error_code}"
+msgstr ""
+
+msgid "Kubernetes Cluster"
+msgstr ""
+
+msgid "Kubernetes Clusters"
+msgstr ""
+
+msgid "Kubernetes cluster"
+msgstr ""
+
+msgid "Kubernetes cluster creation time exceeds timeout; %{timeout}"
+msgstr ""
+
+msgid "Kubernetes cluster integration and resources are being removed."
+msgstr ""
+
+msgid "Kubernetes cluster integration was successfully removed."
+msgstr ""
+
+msgid "Kubernetes cluster was successfully updated."
+msgstr ""
+
+msgid "Kubernetes deployment not found"
+msgstr ""
+
+msgid "Kubernetes error: %{error_code}"
+msgstr ""
+
+msgid "Kubernetes popover"
+msgstr ""
+
+msgid "LDAP"
+msgstr ""
+
+msgid "LDAP Synchronization"
+msgstr ""
+
+msgid "LDAP settings"
+msgstr ""
+
+msgid "LDAP settings updated"
+msgstr ""
+
+msgid "LDAP sync in progress. This could take a few minutes. Refresh the page to see the changes."
+msgstr ""
+
+msgid "LFS"
+msgstr ""
+
+msgid "LFS objects"
+msgstr ""
+
+msgid "LFSStatus|Disabled"
+msgstr ""
+
+msgid "LFSStatus|Enabled"
+msgstr ""
+
+msgid "LICENSE"
+msgstr ""
+
+msgid "Label"
+msgstr ""
+
+msgid "Label actions dropdown"
+msgstr ""
+
+msgid "Label lists show all issues with the selected label."
+msgstr ""
+
+msgid "Label was created"
+msgstr ""
+
+msgid "Label was removed"
+msgstr ""
+
+msgid "Label was successfully updated."
+msgstr ""
+
+msgid "LabelSelect|%{firstLabelName} +%{remainingLabelCount} more"
+msgstr ""
+
+msgid "LabelSelect|%{labelsString}, and %{remainingLabelCount} more"
+msgstr ""
+
+msgid "LabelSelect|Labels"
+msgstr ""
+
+msgid "Labels"
+msgstr ""
+
+msgid "Labels can be applied to %{features}. Group labels are available for any project within the group."
+msgstr ""
+
+msgid "Labels can be applied to issues and merge requests to categorize them."
+msgstr ""
+
+msgid "Labels can be applied to issues and merge requests."
+msgstr ""
+
+msgid "Labels|<span>Promote label</span> %{labelTitle} <span>to Group Label?</span>"
+msgstr ""
+
+msgid "Labels|Promote Label"
+msgstr ""
+
+msgid "Labels|Promoting %{labelTitle} will make it available for all projects inside %{groupName}. Existing project labels with the same title will be merged. If a group label with the same title exists, it will also be merged. This action cannot be reversed."
+msgstr ""
+
+msgid "Labels|and %{count} more"
+msgstr ""
+
+msgid "Language"
+msgstr ""
+
+msgid "Large File Storage"
+msgstr ""
+
+msgid "Last %d day"
+msgid_plural "Last %d days"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Last %{days} days"
+msgstr ""
+
+msgid "Last Accessed On"
+msgstr ""
+
+msgid "Last Name is too long (maximum is %{max_length} characters)."
+msgstr ""
+
+msgid "Last Pipeline"
+msgstr ""
+
+msgid "Last Seen"
+msgstr ""
+
+msgid "Last accessed on"
+msgstr ""
+
+msgid "Last activity"
+msgstr ""
+
+msgid "Last commit"
+msgstr ""
+
+msgid "Last contact"
+msgstr ""
+
+msgid "Last edited %{date}"
+msgstr ""
+
+msgid "Last edited by %{name}"
+msgstr ""
+
+msgid "Last name"
+msgstr ""
+
+msgid "Last reply by"
+msgstr ""
+
+msgid "Last repository check (%{last_check_timestamp}) failed. See the 'repocheck.log' file for error messages."
+msgstr ""
+
+msgid "Last repository check run"
+msgstr ""
+
+msgid "Last seen"
+msgstr ""
+
+msgid "Last successful sync"
+msgstr ""
+
+msgid "Last successful update"
+msgstr ""
+
+msgid "Last time verified"
+msgstr ""
+
+msgid "Last update"
+msgstr ""
+
+msgid "Last update attempt"
+msgstr ""
+
+msgid "Last updated"
+msgstr ""
+
+msgid "Last used"
+msgstr ""
+
+msgid "Last used on:"
+msgstr ""
+
+msgid "LastCommit|authored"
+msgstr ""
+
+msgid "LastPushEvent|You pushed to"
+msgstr ""
+
+msgid "LastPushEvent|at"
+msgstr ""
+
+msgid "Latest changes"
+msgstr ""
+
+msgid "Latest pipeline for the most recent commit on this branch"
+msgstr ""
+
+msgid "Lead"
+msgstr ""
+
+msgid "Lead Time"
+msgstr ""
+
+msgid "Learn GitLab"
+msgstr ""
+
+msgid "Learn More"
+msgstr ""
+
+msgid "Learn how to %{link_start}contribute to the built-in templates%{link_end}"
+msgstr ""
+
+msgid "Learn how to %{no_packages_link_start}publish and share your packages%{no_packages_link_end} with GitLab."
+msgstr ""
+
+msgid "Learn how to enable synchronization"
+msgstr ""
+
+msgid "Learn more"
+msgstr ""
+
+msgid "Learn more about Auto DevOps"
+msgstr ""
+
+msgid "Learn more about Kubernetes"
+msgstr ""
+
+msgid "Learn more about License-Check"
+msgstr ""
+
+msgid "Learn more about Vulnerability-Check"
+msgstr ""
+
+msgid "Learn more about Web Terminal"
+msgstr ""
+
+msgid "Learn more about X.509 signed commits"
+msgstr ""
+
+msgid "Learn more about adding certificates to your project by following the %{docs_link_start}documentation on GitLab Pages%{docs_link_end}."
+msgstr ""
+
+msgid "Learn more about approvals."
+msgstr ""
+
+msgid "Learn more about custom project templates"
+msgstr ""
+
+msgid "Learn more about deploying to AWS"
+msgstr ""
+
+msgid "Learn more about deploying to a cluster"
+msgstr ""
+
+msgid "Learn more about group-level project templates"
+msgstr ""
+
+msgid "Learn more about job dependencies"
+msgstr ""
+
+msgid "Learn more about signing commits"
+msgstr ""
+
+msgid "Learn more about the dependency list"
+msgstr ""
+
+msgid "Learn more in the"
+msgstr ""
+
+msgid "Learn more in the|pipeline schedules documentation"
+msgstr ""
+
+msgid "Leave"
+msgstr ""
+
+msgid "Leave Admin Mode"
+msgstr ""
+
+msgid "Leave blank for no limit. Once set, existing personal access tokens may be revoked."
+msgstr ""
+
+msgid "Leave edit mode? All unsaved changes will be lost."
+msgstr ""
+
+msgid "Leave group"
+msgstr ""
+
+msgid "Leave project"
+msgstr ""
+
+msgid "Leave the \"File type\" and \"Delivery method\" options on their default values."
+msgstr ""
+
+msgid "Leave zen mode"
+msgstr ""
+
+msgid "Let's Encrypt does not accept emails on example.com"
+msgstr ""
+
+msgid "Let's Encrypt is a free, automated, and open certificate authority (CA) that gives digital certificates in order to enable HTTPS (SSL/TLS) for websites. Learn more about Let's Encrypt configuration by following the %{docs_link_start}documentation on GitLab Pages%{docs_link_end}."
+msgstr ""
+
+msgid "License"
+msgstr ""
+
+msgid "License Compliance"
+msgstr ""
+
+msgid "License History"
+msgstr ""
+
+msgid "License ID:"
+msgstr ""
+
+msgid "License URL"
+msgstr ""
+
+msgid "License-Check"
+msgstr ""
+
+msgid "LicenseCompliance|%{docLinkStart}License Approvals%{docLinkEnd} are active"
+msgstr ""
+
+msgid "LicenseCompliance|%{docLinkStart}License Approvals%{docLinkEnd} are inactive"
+msgstr ""
+
+msgid "LicenseCompliance|Add a license"
+msgstr ""
+
+msgid "LicenseCompliance|Add license and related policy"
+msgstr ""
+
+msgid "LicenseCompliance|Allow"
+msgstr ""
+
+msgid "LicenseCompliance|Allowed"
+msgstr ""
+
+msgid "LicenseCompliance|Cancel"
+msgstr ""
+
+msgid "LicenseCompliance|Denied"
+msgstr ""
+
+msgid "LicenseCompliance|Deny"
+msgstr ""
+
+msgid "LicenseCompliance|Learn more about %{linkStart}License Approvals%{linkEnd}"
+msgstr ""
+
+msgid "LicenseCompliance|License"
+msgstr ""
+
+msgid "LicenseCompliance|License Approvals"
+msgstr ""
+
+msgid "LicenseCompliance|License Compliance detected %d license and policy violation for the source branch only; approval required"
+msgid_plural "LicenseCompliance|License Compliance detected %d licenses and policy violations for the source branch only; approval required"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "LicenseCompliance|License Compliance detected %d license for the source branch only"
+msgid_plural "LicenseCompliance|License Compliance detected %d licenses for the source branch only"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "LicenseCompliance|License Compliance detected %d new license"
+msgid_plural "LicenseCompliance|License Compliance detected %d new licenses"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "LicenseCompliance|License Compliance detected %d new license and policy violation; approval required"
+msgid_plural "LicenseCompliance|License Compliance detected %d new licenses and policy violations; approval required"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "LicenseCompliance|License Compliance detected no licenses for the source branch only"
+msgstr ""
+
+msgid "LicenseCompliance|License Compliance detected no new licenses"
+msgstr ""
+
+msgid "LicenseCompliance|License details"
+msgstr ""
+
+msgid "LicenseCompliance|License name"
+msgstr ""
+
+msgid "LicenseCompliance|License review"
+msgstr ""
+
+msgid "LicenseCompliance|Packages"
+msgstr ""
+
+msgid "LicenseCompliance|Remove license"
+msgstr ""
+
+msgid "LicenseCompliance|Remove license?"
+msgstr ""
+
+msgid "LicenseCompliance|Submit"
+msgstr ""
+
+msgid "LicenseCompliance|There are currently no approved or blacklisted licenses in this project."
+msgstr ""
+
+msgid "LicenseCompliance|There are currently no approved or blacklisted licenses that match in this project."
+msgstr ""
+
+msgid "LicenseCompliance|This license already exists in this project."
+msgstr ""
+
+msgid "LicenseCompliance|URL"
+msgstr ""
+
+msgid "LicenseCompliance|You are about to remove the license, %{name}, from this project."
+msgstr ""
+
+msgid "LicenseManagement|Allowed"
+msgstr ""
+
+msgid "LicenseManagement|Denied"
+msgstr ""
+
+msgid "LicenseManagement|Uncategorized"
+msgstr ""
+
+msgid "Licensed Features"
+msgstr ""
+
+msgid "Licensed to"
+msgstr ""
+
+msgid "Licenses"
+msgstr ""
+
+msgid "Licenses|%{remainingComponentsCount} more"
+msgstr ""
+
+msgid "Licenses|Component"
+msgstr ""
+
+msgid "Licenses|Components"
+msgstr ""
+
+msgid "Licenses|Detected in Project"
+msgstr ""
+
+msgid "Licenses|Detected licenses that are out-of-compliance with the project's assigned policies"
+msgstr ""
+
+msgid "Licenses|Displays licenses detected in the project, based on the %{linkStart}latest successful%{linkEnd} scan"
+msgstr ""
+
+msgid "Licenses|Error fetching the license list. Please check your network connection and try again."
+msgstr ""
+
+msgid "Licenses|Learn more about license compliance"
+msgstr ""
+
+msgid "Licenses|License Compliance"
+msgstr ""
+
+msgid "Licenses|Name"
+msgstr ""
+
+msgid "Licenses|Policies"
+msgstr ""
+
+msgid "Licenses|Policy"
+msgstr ""
+
+msgid "Licenses|Policy violation: denied"
+msgstr ""
+
+msgid "Licenses|Specified policies in this project"
+msgstr ""
+
+msgid "Licenses|The license list details information about the licenses used within your project."
+msgstr ""
+
+msgid "Licenses|View license details for your project"
+msgstr ""
+
+msgid "License|Buy license"
+msgstr ""
+
+msgid "License|License"
+msgstr ""
+
+msgid "License|Licensed user count exceeded"
+msgstr ""
+
+msgid "License|You can restore access to the Gold features at any time by upgrading."
+msgstr ""
+
+msgid "License|You can start a free trial of GitLab Ultimate without any obligation or payment details."
+msgstr ""
+
+msgid "License|You do not have a license."
+msgstr ""
+
+msgid "License|Your License"
+msgstr ""
+
+msgid "License|Your free trial of GitLab Ultimate expired on %{trial_ends_on}."
+msgstr ""
+
+msgid "License|Your instance has exceeded your subscription's number of licensed users by %{extra_users_count}. You can continue to add more users and we'll include the overage in your next bill."
+msgstr ""
+
+msgid "Limit display of time tracking units to hours."
+msgstr ""
+
+msgid "Limit namespaces and projects that can be indexed"
+msgstr ""
+
+msgid "Limited to showing %d event at most"
+msgid_plural "Limited to showing %d events at most"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Line changes"
+msgstr ""
+
+msgid "Link Prometheus monitoring to GitLab."
+msgstr ""
+
+msgid "Link copied"
+msgstr ""
+
+msgid "Link title"
+msgstr ""
+
+msgid "Link title is required"
+msgstr ""
+
+msgid "Linked emails (%{email_count})"
+msgstr ""
+
+msgid "Linked issues"
+msgstr ""
+
+msgid "LinkedIn"
+msgstr ""
+
+msgid "LinkedPipelines|%{counterLabel} more downstream pipelines"
+msgstr ""
+
+msgid "Links"
+msgstr ""
+
+msgid "List"
+msgstr ""
+
+msgid "List Your Gitea Repositories"
+msgstr ""
+
+msgid "List available repositories"
+msgstr ""
+
+msgid "List of IPs and CIDRs of allowed secondary nodes. Comma-separated, e.g. \"1.1.1.1, 2.2.2.0/24\""
+msgstr ""
+
+msgid "List settings"
+msgstr ""
+
+msgid "List the merge requests that must be merged before this one."
+msgstr ""
+
+msgid "List view"
+msgstr ""
+
+msgid "List your Bitbucket Server repositories"
+msgstr ""
+
+msgid "Lists"
+msgstr ""
+
+msgid "Live preview"
+msgstr ""
+
+msgid "Loading"
+msgstr ""
+
+msgid "Loading contribution stats for group members"
+msgstr ""
+
+msgid "Loading files, directories, and submodules in the path %{path} for commit reference %{ref}"
+msgstr ""
+
+msgid "Loading functions timed out. Please reload the page to try again."
+msgstr ""
+
+msgid "Loading issues"
+msgstr ""
+
+msgid "Loading snippet"
+msgstr ""
+
+msgid "Loading the GitLab IDE..."
+msgstr ""
+
+msgid "Loading..."
+msgstr ""
+
+msgid "Loading…"
+msgstr ""
+
+msgid "Local IP addresses and domain names that hooks and services may access."
+msgstr ""
+
+msgid "Localization"
+msgstr ""
+
+msgid "Location"
+msgstr ""
+
+msgid "Lock"
+msgstr ""
+
+msgid "Lock %{issuableDisplayName}"
+msgstr ""
+
+msgid "Lock memberships to LDAP synchronization"
+msgstr ""
+
+msgid "Lock not found"
+msgstr ""
+
+msgid "Lock the discussion"
+msgstr ""
+
+msgid "Lock this %{issuableDisplayName}? Only <strong>project members</strong> will be able to comment."
+msgstr ""
+
+msgid "Lock to current projects"
+msgstr ""
+
+msgid "Locked"
+msgstr ""
+
+msgid "Locked Files"
+msgstr ""
+
+msgid "Locked by %{fileLockUserName}"
+msgstr ""
+
+msgid "Locked the discussion."
+msgstr ""
+
+msgid "Locked to current projects"
+msgstr ""
+
+msgid "Locks give the ability to lock specific file or folder."
+msgstr ""
+
+msgid "Locks the discussion."
+msgstr ""
+
+msgid "Login with smartcard"
+msgstr ""
+
+msgid "Logo was successfully removed."
+msgstr ""
+
+msgid "Logs"
+msgstr ""
+
+msgid "Logs|To see the logs, deploy your code to an environment."
+msgstr ""
+
+msgid "Low vulnerabilities present"
+msgstr ""
+
+msgid "MB"
+msgstr ""
+
+msgid "MD5"
+msgstr ""
+
+msgid "MERGED"
+msgstr ""
+
+msgid "MR widget|Take a look at our %{beginnerLinkStart}Beginner's Guide to Continuous Integration%{beginnerLinkEnd} and our %{exampleLinkStart}examples of GitLab CI/CD%{exampleLinkEnd} to see all the cool stuff you can do with it."
+msgstr ""
+
+msgid "MR widget|The pipeline will now run automatically every time you commit code. Pipelines are useful for deploying static web pages, detecting vulnerabilities in dependencies, static or dynamic application security testing (SAST and DAST), and so much more!"
+msgstr ""
+
+msgid "MRApprovals|Approvals"
+msgstr ""
+
+msgid "MRApprovals|Approved by"
+msgstr ""
+
+msgid "MRApprovals|Approvers"
+msgstr ""
+
+msgid "MRDiff|Show changes only"
+msgstr ""
+
+msgid "MRDiff|Show full file"
+msgstr ""
+
+msgid "Made this issue confidential."
+msgstr ""
+
+msgid "Maintenance mode"
+msgstr ""
+
+msgid "Make and review changes in the browser with the Web IDE"
+msgstr ""
+
+msgid "Make everyone on your team more productive regardless of their location. GitLab Geo creates read-only mirrors of your GitLab instance so you can reduce the time it takes to clone and fetch large repos."
+msgstr ""
+
+msgid "Make issue confidential"
+msgstr ""
+
+msgid "Make sure you save it - you won't be able to access it again."
+msgstr ""
+
+msgid "Make sure you're logged into the account that owns the projects you'd like to import."
+msgstr ""
+
+msgid "Make this epic confidential"
+msgstr ""
+
+msgid "Makes this issue confidential."
+msgstr ""
+
+msgid "Manage"
+msgstr ""
+
+msgid "Manage Web IDE features"
+msgstr ""
+
+msgid "Manage access"
+msgstr ""
+
+msgid "Manage all notifications"
+msgstr ""
+
+msgid "Manage applications that can use GitLab as an OAuth provider, and applications that you've authorized to use your account."
+msgstr ""
+
+msgid "Manage applications that you've authorized to use your account."
+msgstr ""
+
+msgid "Manage group labels"
+msgstr ""
+
+msgid "Manage labels"
+msgstr ""
+
+msgid "Manage milestones"
+msgstr ""
+
+msgid "Manage project labels"
+msgstr ""
+
+msgid "Manage storage usage"
+msgstr ""
+
+msgid "Manage two-factor authentication"
+msgstr ""
+
+msgid "Manage your license"
+msgstr ""
+
+msgid "Managed Account"
+msgstr ""
+
+msgid "Manifest"
+msgstr ""
+
+msgid "Manifest file import"
+msgstr ""
+
+msgid "Manifest import"
+msgstr ""
+
+msgid "Manual job"
+msgstr ""
+
+msgid "ManualOrdering|Couldn't save the order of the issues"
+msgstr ""
+
+msgid "Map a FogBugz account ID to a GitLab user"
+msgstr ""
+
+msgid "Map a Google Code user to a GitLab user"
+msgstr ""
+
+msgid "Map a Google Code user to a full email address"
+msgstr ""
+
+msgid "Map a Google Code user to a full name"
+msgstr ""
+
+msgid "Mar"
+msgstr ""
+
+msgid "March"
+msgstr ""
+
+msgid "Mark To Do as done"
+msgstr ""
+
+msgid "Mark as done"
+msgstr ""
+
+msgid "Mark as resolved"
+msgstr ""
+
+msgid "Mark this issue as a duplicate of another issue"
+msgstr ""
+
+msgid "Mark this issue as related to another issue"
+msgstr ""
+
+msgid "Markdown"
+msgstr ""
+
+msgid "Markdown enabled"
+msgstr ""
+
+msgid "Markdown is supported"
+msgstr ""
+
+msgid "Marked To Do as done."
+msgstr ""
+
+msgid "Marked this %{noun} as Work In Progress."
+msgstr ""
+
+msgid "Marked this issue as a duplicate of %{duplicate_param}."
+msgstr ""
+
+msgid "Marked this issue as related to %{issue_ref}."
+msgstr ""
+
+msgid "Marks To Do as done."
+msgstr ""
+
+msgid "Marks this %{noun} as Work In Progress."
+msgstr ""
+
+msgid "Marks this issue as a duplicate of %{duplicate_reference}."
+msgstr ""
+
+msgid "Marks this issue as related to %{issue_ref}."
+msgstr ""
+
+msgid "Mask variable"
+msgstr ""
+
+msgid "Match not found; try refining your search query."
+msgstr ""
+
+msgid "MattermostService|Add to Mattermost"
+msgstr ""
+
+msgid "MattermostService|Command trigger word"
+msgstr ""
+
+msgid "MattermostService|Fill in the word that works best for your team."
+msgstr ""
+
+msgid "MattermostService|Request URL"
+msgstr ""
+
+msgid "MattermostService|Request method"
+msgstr ""
+
+msgid "MattermostService|Response icon"
+msgstr ""
+
+msgid "MattermostService|Response username"
+msgstr ""
+
+msgid "MattermostService|See list of available commands in Mattermost after setting up this service, by entering"
+msgstr ""
+
+msgid "MattermostService|Suggestions:"
+msgstr ""
+
+msgid "MattermostService|This service allows users to perform common operations on this project by entering slash commands in Mattermost."
+msgstr ""
+
+msgid "Maven Metadata"
+msgstr ""
+
+msgid "Max access level"
+msgstr ""
+
+msgid "Max seats used"
+msgstr ""
+
+msgid "Maximum Users:"
+msgstr ""
+
+msgid "Maximum allowable lifetime for personal access token (days)"
+msgstr ""
+
+msgid "Maximum artifacts size (MB)"
+msgstr ""
+
+msgid "Maximum attachment size (MB)"
+msgstr ""
+
+msgid "Maximum bulk request size (MiB)"
+msgstr ""
+
+msgid "Maximum capacity"
+msgstr ""
+
+msgid "Maximum concurrency of Elasticsearch bulk requests per indexing operation."
+msgstr ""
+
+msgid "Maximum delay (Minutes)"
+msgstr ""
+
+msgid "Maximum duration of a session."
+msgstr ""
+
+msgid "Maximum field length"
+msgstr ""
+
+msgid "Maximum file size is 2MB. Please select a smaller file."
+msgstr ""
+
+msgid "Maximum import size (MB)"
+msgstr ""
+
+msgid "Maximum job timeout"
+msgstr ""
+
+msgid "Maximum job timeout has a value which could not be accepted"
+msgstr ""
+
+msgid "Maximum lifetime allowable for Personal Access Tokens is active, your expire date must be set before %{maximum_allowable_date}."
+msgstr ""
+
+msgid "Maximum namespace storage (MB)"
+msgstr ""
+
+msgid "Maximum number of %{name} (%{count}) exceeded"
+msgstr ""
+
+msgid "Maximum number of comments exceeded"
+msgstr ""
+
+msgid "Maximum number of mirrors that can be synchronizing at the same time."
+msgstr ""
+
+msgid "Maximum number of projects."
+msgstr ""
+
+msgid "Maximum page reached"
+msgstr ""
+
+msgid "Maximum push size (MB)"
+msgstr ""
+
+msgid "Maximum size limit for a single commit."
+msgstr ""
+
+msgid "Maximum size limit for each repository."
+msgstr ""
+
+msgid "Maximum size of Elasticsearch bulk indexing requests."
+msgstr ""
+
+msgid "Maximum size of import files."
+msgstr ""
+
+msgid "Maximum size of individual attachments in comments."
+msgstr ""
+
+msgid "Maximum time between updates that a mirror can have when scheduled to synchronize."
+msgstr ""
+
+msgid "May"
+msgstr ""
+
+msgid "Measured in bytes of code. Excludes generated and vendored code."
+msgstr ""
+
+msgid "Median"
+msgstr ""
+
+msgid "Medium vulnerabilities present"
+msgstr ""
+
+msgid "Member lock"
+msgstr ""
+
+msgid "Member since %{date}"
+msgstr ""
+
+msgid "Members"
+msgstr ""
+
+msgid "Members can be added by project <i>Maintainers</i> or <i>Owners</i>"
+msgstr ""
+
+msgid "Members of <strong>%{project_name}</strong>"
+msgstr ""
+
+msgid "Members of a group may only view projects they have permission to access"
+msgstr ""
+
+msgid "Members with access to %{strong_start}%{group_name}%{strong_end}"
+msgstr ""
+
+msgid "Members with pending access to %{strong_start}%{group_name}%{strong_end}"
+msgstr ""
+
+msgid "Memory Usage"
+msgstr ""
+
+msgid "Merge"
+msgstr ""
+
+msgid "Merge (when the pipeline succeeds)"
+msgstr ""
+
+msgid "Merge Conflicts"
+msgstr ""
+
+msgid "Merge Request"
+msgstr ""
+
+msgid "Merge Request Approvals"
+msgstr ""
+
+msgid "Merge Request Commits"
+msgstr ""
+
+msgid "Merge Requests"
+msgstr ""
+
+msgid "Merge Requests created"
+msgstr ""
+
+msgid "Merge Requests in Review"
+msgstr ""
+
+msgid "Merge automatically (%{strategy})"
+msgstr ""
+
+msgid "Merge commit message"
+msgstr ""
+
+msgid "Merge events"
+msgstr ""
+
+msgid "Merge immediately"
+msgstr ""
+
+msgid "Merge in progress"
+msgstr ""
+
+msgid "Merge options"
+msgstr ""
+
+msgid "Merge request"
+msgstr ""
+
+msgid "Merge request %{iid} authored by %{authorName}"
+msgstr ""
+
+msgid "Merge request %{mr_link} was reviewed by %{mr_author}"
+msgstr ""
+
+msgid "Merge request approvals"
+msgstr ""
+
+msgid "Merge request approvals allow you to set the number of necessary approvals and predefine a list of approvers that will need to approve every merge request in a project."
+msgstr ""
+
+msgid "Merge request dependencies"
+msgstr ""
+
+msgid "Merge requests"
+msgstr ""
+
+msgid "Merge requests approvals"
+msgstr ""
+
+msgid "Merge requests are a place to propose changes you've made to a project and discuss those changes with others"
+msgstr ""
+
+msgid "Merge requests are read-only in a secondary Geo node"
+msgstr ""
+
+msgid "Merge when pipeline succeeds"
+msgstr ""
+
+msgid "MergeConflict|Commit to source branch"
+msgstr ""
+
+msgid "MergeConflict|Committing..."
+msgstr ""
+
+msgid "MergeConflict|HEAD//our changes"
+msgstr ""
+
+msgid "MergeConflict|Use ours"
+msgstr ""
+
+msgid "MergeConflict|Use theirs"
+msgstr ""
+
+msgid "MergeConflict|conflict"
+msgstr ""
+
+msgid "MergeConflict|conflicts"
+msgstr ""
+
+msgid "MergeConflict|origin//their changes"
+msgstr ""
+
+msgid "MergeRequestDiffs|Commenting on lines %{selectStart}start%{selectEnd} to %{end}"
+msgstr ""
+
+msgid "MergeRequestDiffs|Select comment starting line"
+msgstr ""
+
+msgid "MergeRequests|Add a reply"
+msgstr ""
+
+msgid "MergeRequests|An error occurred while checking whether another squash is in progress."
+msgstr ""
+
+msgid "MergeRequests|An error occurred while saving the draft comment."
+msgstr ""
+
+msgid "MergeRequests|Failed to squash. Should be done manually."
+msgstr ""
+
+msgid "MergeRequests|Jump to next unresolved thread"
+msgstr ""
+
+msgid "MergeRequests|Reply..."
+msgstr ""
+
+msgid "MergeRequests|Resolve this thread in a new issue"
+msgstr ""
+
+msgid "MergeRequests|Saving the comment failed"
+msgstr ""
+
+msgid "MergeRequests|Squash task canceled: another squash is already in progress."
+msgstr ""
+
+msgid "MergeRequests|This project does not allow squashing commits when merge requests are accepted."
+msgstr ""
+
+msgid "MergeRequests|Thread stays resolved"
+msgstr ""
+
+msgid "MergeRequests|Thread stays unresolved"
+msgstr ""
+
+msgid "MergeRequests|Thread will be resolved"
+msgstr ""
+
+msgid "MergeRequests|Thread will be unresolved"
+msgstr ""
+
+msgid "MergeRequests|Toggle comments for this file"
+msgstr ""
+
+msgid "MergeRequests|View file @ %{commitId}"
+msgstr ""
+
+msgid "MergeRequests|View replaced file @ %{commitId}"
+msgstr ""
+
+msgid "MergeRequests|commented on commit %{commitLink}"
+msgstr ""
+
+msgid "MergeRequests|started a thread"
+msgstr ""
+
+msgid "MergeRequests|started a thread on %{linkStart}an old version of the diff%{linkEnd}"
+msgstr ""
+
+msgid "MergeRequests|started a thread on %{linkStart}the diff%{linkEnd}"
+msgstr ""
+
+msgid "MergeRequests|started a thread on an outdated change in commit %{linkStart}%{commitDisplay}%{linkEnd}"
+msgstr ""
+
+msgid "MergeRequests|started a thread on commit %{linkStart}%{commitDisplay}%{linkEnd}"
+msgstr ""
+
+msgid "MergeRequest|Compare %{target} and %{source}"
+msgstr ""
+
+msgid "MergeRequest|Error dismissing suggestion popover. Please try again."
+msgstr ""
+
+msgid "MergeRequest|Error loading full diff. Please try again."
+msgstr ""
+
+msgid "MergeRequest|No files found"
+msgstr ""
+
+msgid "MergeRequest|Search files (%{modifier_key}P)"
+msgstr ""
+
+msgid "Merged"
+msgstr ""
+
+msgid "Merged MRs"
+msgstr ""
+
+msgid "Merged branches are being deleted. This can take some time depending on the number of branches. Please refresh the page to see changes."
+msgstr ""
+
+msgid "Merged this merge request."
+msgstr ""
+
+msgid "Merges this merge request immediately."
+msgstr ""
+
+msgid "Merges this merge request when the pipeline succeeds."
+msgstr ""
+
+msgid "Merging immediately isn't recommended as it may negatively impact the existing merge train. Read the %{docsLinkStart}documentation%{docsLinkEnd} for more information."
+msgstr ""
+
+msgid "Messages"
+msgstr ""
+
+msgid "Method"
+msgstr ""
+
+msgid "Metric was successfully added."
+msgstr ""
+
+msgid "Metric was successfully updated."
+msgstr ""
+
+msgid "Metric:"
+msgstr ""
+
+msgid "MetricChart|Please select a metric"
+msgstr ""
+
+msgid "MetricChart|Selected"
+msgstr ""
+
+msgid "Metrics"
+msgstr ""
+
+msgid "Metrics - Grafana"
+msgstr ""
+
+msgid "Metrics - Prometheus"
+msgstr ""
+
+msgid "Metrics Dashboard"
+msgstr ""
+
+msgid "Metrics Dashboard YAML definition"
+msgstr ""
+
+msgid "Metrics Dashboard YAML definition is invalid:"
+msgstr ""
+
+msgid "Metrics Dashboard YAML definition is valid."
+msgstr ""
+
+msgid "Metrics and profiling"
+msgstr ""
+
+msgid "Metrics::Dashboard::Annotation|Annotation can't belong to both a cluster and an environment at the same time"
+msgstr ""
+
+msgid "Metrics::Dashboard::Annotation|Annotation has not been deleted"
+msgstr ""
+
+msgid "Metrics::Dashboard::Annotation|Annotation must belong to a cluster or an environment"
+msgstr ""
+
+msgid "Metrics::Dashboard::Annotation|Dashboard with requested path can not be found"
+msgstr ""
+
+msgid "Metrics::Dashboard::Annotation|You are not authorized to create annotation for selected cluster"
+msgstr ""
+
+msgid "Metrics::Dashboard::Annotation|You are not authorized to create annotation for selected environment"
+msgstr ""
+
+msgid "Metrics::Dashboard::Annotation|You are not authorized to delete this annotation"
+msgstr ""
+
+msgid "Metrics::Dashboard::Annotation|can't be before starting_at time"
+msgstr ""
+
+msgid "Metrics::UsersStarredDashboards|Dashboard with requested path can not be found"
+msgstr ""
+
+msgid "Metrics::UsersStarredDashboards|You are not authorized to add star to this dashboard"
+msgstr ""
+
+msgid "MetricsSettings|Add a button to the metrics dashboard linking directly to your existing external dashboard."
+msgstr ""
+
+msgid "MetricsSettings|Choose whether to display dashboard metrics in UTC or the user's local timezone."
+msgstr ""
+
+msgid "MetricsSettings|Dashboard timezone"
+msgstr ""
+
+msgid "MetricsSettings|External dashboard URL"
+msgstr ""
+
+msgid "MetricsSettings|Manage Metrics Dashboard settings."
+msgstr ""
+
+msgid "MetricsSettings|Metrics Dashboard"
+msgstr ""
+
+msgid "MetricsSettings|UTC (Coordinated Universal Time)"
+msgstr ""
+
+msgid "MetricsSettings|User's local timezone"
+msgstr ""
+
+msgid "Metrics|Add metric"
+msgstr ""
+
+msgid "Metrics|Avg"
+msgstr ""
+
+msgid "Metrics|Check out the CI/CD documentation on deploying to an environment"
+msgstr ""
+
+msgid "Metrics|Create custom dashboard %{fileName}"
+msgstr ""
+
+msgid "Metrics|Create metric"
+msgstr ""
+
+msgid "Metrics|Current"
+msgstr ""
+
+msgid "Metrics|Delete metric"
+msgstr ""
+
+msgid "Metrics|Delete metric?"
+msgstr ""
+
+msgid "Metrics|Duplicate"
+msgstr ""
+
+msgid "Metrics|Duplicate dashboard"
+msgstr ""
+
+msgid "Metrics|Duplicating..."
+msgstr ""
+
+msgid "Metrics|Edit metric"
+msgid_plural "Metrics|Edit metrics"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Metrics|Expand panel"
+msgstr ""
+
+msgid "Metrics|For grouping similar metrics"
+msgstr ""
+
+msgid "Metrics|Go back (Esc)"
+msgstr ""
+
+msgid "Metrics|Invalid time range, please verify."
+msgstr ""
+
+msgid "Metrics|Label of the y-axis (usually the unit). The x-axis always represents time."
+msgstr ""
+
+msgid "Metrics|Legend label (optional)"
+msgstr ""
+
+msgid "Metrics|Link contains an invalid time window, please verify the link to see the requested time range."
+msgstr ""
+
+msgid "Metrics|Link contains invalid chart information, please verify the link to see the expanded panel."
+msgstr ""
+
+msgid "Metrics|Manage chart links"
+msgstr ""
+
+msgid "Metrics|Max"
+msgstr ""
+
+msgid "Metrics|Min"
+msgstr ""
+
+msgid "Metrics|Must be a valid PromQL query."
+msgstr ""
+
+msgid "Metrics|New metric"
+msgstr ""
+
+msgid "Metrics|PromQL query is valid"
+msgstr ""
+
+msgid "Metrics|Prometheus Query Documentation"
+msgstr ""
+
+msgid "Metrics|Refresh dashboard"
+msgstr ""
+
+msgid "Metrics|Select a value"
+msgstr ""
+
+msgid "Metrics|Star dashboard"
+msgstr ""
+
+msgid "Metrics|There was an error creating the dashboard."
+msgstr ""
+
+msgid "Metrics|There was an error creating the dashboard. %{error}"
+msgstr ""
+
+msgid "Metrics|There was an error fetching annotations. Please try again."
+msgstr ""
+
+msgid "Metrics|There was an error fetching the environments data, please try again"
+msgstr ""
+
+msgid "Metrics|There was an error getting annotations information."
+msgstr ""
+
+msgid "Metrics|There was an error getting dashboard validation warnings information."
+msgstr ""
+
+msgid "Metrics|There was an error getting deployment information."
+msgstr ""
+
+msgid "Metrics|There was an error getting environments information."
+msgstr ""
+
+msgid "Metrics|There was an error getting options for variable \"%{name}\"."
+msgstr ""
+
+msgid "Metrics|There was an error trying to validate your query"
+msgstr ""
+
+msgid "Metrics|There was an error while retrieving metrics"
+msgstr ""
+
+msgid "Metrics|There was an error while retrieving metrics. %{message}"
+msgstr ""
+
+msgid "Metrics|Unexpected deployment data response from prometheus endpoint"
+msgstr ""
+
+msgid "Metrics|Unit label"
+msgstr ""
+
+msgid "Metrics|Unstar dashboard"
+msgstr ""
+
+msgid "Metrics|Used as a title for the chart"
+msgstr ""
+
+msgid "Metrics|Used if the query returns a single series. If it returns multiple series, their legend labels will be picked up from the response."
+msgstr ""
+
+msgid "Metrics|Validating query"
+msgstr ""
+
+msgid "Metrics|Values"
+msgstr ""
+
+msgid "Metrics|View logs"
+msgstr ""
+
+msgid "Metrics|Y-axis label"
+msgstr ""
+
+msgid "Metrics|You can save a copy of this dashboard to your repository so it can be customized. Select a file name and branch to save it."
+msgstr ""
+
+msgid "Metrics|You're about to permanently delete this metric. This cannot be undone."
+msgstr ""
+
+msgid "Metrics|Your dashboard schema is invalid. Edit the dashboard to correct the YAML schema."
+msgstr ""
+
+msgid "Metrics|e.g. HTTP requests"
+msgstr ""
+
+msgid "Metrics|e.g. Requests/second"
+msgstr ""
+
+msgid "Metrics|e.g. Throughput"
+msgstr ""
+
+msgid "Metrics|e.g. rate(http_requests_total[5m])"
+msgstr ""
+
+msgid "Metrics|e.g. req/sec"
+msgstr ""
+
+msgid "Mi"
+msgstr ""
+
+msgid "Microsoft Azure"
+msgstr ""
+
+msgid "Middleman project with Static Site Editor support"
+msgstr ""
+
+msgid "Migrate your data from an external source like GitHub, Bitbucket, or another instance of GitLab."
+msgstr ""
+
+msgid "Migrated %{success_count}/%{total_count} files."
+msgstr ""
+
+msgid "Migration successful."
+msgstr ""
+
+msgid "Milestone"
+msgid_plural "Milestones"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Milestone lists not available with your current license"
+msgstr ""
+
+msgid "Milestone lists show all issues from the selected milestone."
+msgstr ""
+
+msgid "MilestoneSidebar|Closed:"
+msgstr ""
+
+msgid "MilestoneSidebar|Copy reference"
+msgstr ""
+
+msgid "MilestoneSidebar|Due date"
+msgstr ""
+
+msgid "MilestoneSidebar|Edit"
+msgstr ""
+
+msgid "MilestoneSidebar|From"
+msgstr ""
+
+msgid "MilestoneSidebar|Issues"
+msgstr ""
+
+msgid "MilestoneSidebar|Merge requests"
+msgstr ""
+
+msgid "MilestoneSidebar|Merged:"
+msgstr ""
+
+msgid "MilestoneSidebar|New Issue"
+msgstr ""
+
+msgid "MilestoneSidebar|New issue"
+msgstr ""
+
+msgid "MilestoneSidebar|No due date"
+msgstr ""
+
+msgid "MilestoneSidebar|No start date"
+msgstr ""
+
+msgid "MilestoneSidebar|None"
+msgstr ""
+
+msgid "MilestoneSidebar|Open:"
+msgstr ""
+
+msgid "MilestoneSidebar|Reference:"
+msgstr ""
+
+msgid "MilestoneSidebar|Start date"
+msgstr ""
+
+msgid "MilestoneSidebar|Toggle sidebar"
+msgstr ""
+
+msgid "MilestoneSidebar|Until"
+msgstr ""
+
+msgid "MilestoneSidebar|complete"
+msgstr ""
+
+msgid "Milestones"
+msgstr ""
+
+msgid "Milestones| You’re about to permanently delete the milestone %{milestoneTitle} and remove it from %{issuesWithCount} and %{mergeRequestsWithCount}. Once deleted, it cannot be undone or recovered."
+msgstr ""
+
+msgid "Milestones| You’re about to permanently delete the milestone %{milestoneTitle}. This milestone is not currently used in any issues or merge requests."
+msgstr ""
+
+msgid "Milestones|Close Milestone"
+msgstr ""
+
+msgid "Milestones|Completed Issues (closed)"
+msgstr ""
+
+msgid "Milestones|Delete milestone"
+msgstr ""
+
+msgid "Milestones|Delete milestone %{milestoneTitle}?"
+msgstr ""
+
+msgid "Milestones|Failed to delete milestone %{milestoneTitle}"
+msgstr ""
+
+msgid "Milestones|Group Milestone"
+msgstr ""
+
+msgid "Milestones|Milestone %{milestoneTitle} was not found"
+msgstr ""
+
+msgid "Milestones|Ongoing Issues (open and assigned)"
+msgstr ""
+
+msgid "Milestones|Project Milestone"
+msgstr ""
+
+msgid "Milestones|Promote %{milestoneTitle} to group milestone?"
+msgstr ""
+
+msgid "Milestones|Promote Milestone"
+msgstr ""
+
+msgid "Milestones|Promote to Group Milestone"
+msgstr ""
+
+msgid "Milestones|Promoting %{milestoneTitle} will make it available for all projects inside %{groupName}. Existing project milestones with the same title will be merged."
+msgstr ""
+
+msgid "Milestones|Reopen Milestone"
+msgstr ""
+
+msgid "Milestones|This action cannot be reversed."
+msgstr ""
+
+msgid "Milestones|Unstarted Issues (open and unassigned)"
+msgstr ""
+
+msgid "Minimum capacity to be available before we schedule more mirrors preemptively."
+msgstr ""
+
+msgid "Minimum interval in days"
+msgstr ""
+
+msgid "Minimum length is %{minimum_password_length} characters"
+msgstr ""
+
+msgid "Minimum length is %{minimum_password_length} characters."
+msgstr ""
+
+msgid "Minimum password length (number of characters)"
+msgstr ""
+
+msgid "Minutes"
+msgstr ""
+
+msgid "Mirror direction"
+msgstr ""
+
+msgid "Mirror repository"
+msgstr ""
+
+msgid "Mirror settings are only available to GitLab administrators."
+msgstr ""
+
+msgid "Mirror user"
+msgstr ""
+
+msgid "Mirrored branches will have this prefix. If you enabled 'Only mirror protected branches' you need to include this prefix on protected branches in this project or nothing will be mirrored."
+msgstr ""
+
+msgid "Mirrored repositories"
+msgstr ""
+
+msgid "Mirroring repositories"
+msgstr ""
+
+msgid "Mirroring settings were successfully updated."
+msgstr ""
+
+msgid "Mirroring settings were successfully updated. The project is being updated."
+msgstr ""
+
+msgid "Mirroring was successfully disabled."
+msgstr ""
+
+msgid "Mirroring will only be available if the feature is included in the plan of the selected group or user."
+msgstr ""
+
+msgid "Missing commit signatures endpoint!"
+msgstr ""
+
+msgid "MissingSSHKeyWarningLink|Add SSH key"
+msgstr ""
+
+msgid "MissingSSHKeyWarningLink|Don't show again"
+msgstr ""
+
+msgid "MissingSSHKeyWarningLink|You won't be able to pull or push project code via SSH until you add an SSH key to your profile"
+msgstr ""
+
+msgid "ModalButton|Add projects"
+msgstr ""
+
+msgid "Modal|Cancel"
+msgstr ""
+
+msgid "Modal|Close"
+msgstr ""
+
+msgid "Modified"
+msgstr ""
+
+msgid "Modified in this version"
+msgstr ""
+
+msgid "Modify commit message"
+msgstr ""
+
+msgid "Modify commit messages"
+msgstr ""
+
+msgid "Modify merge commit"
+msgstr ""
+
+msgid "Monday"
+msgstr ""
+
+msgid "Monitor your errors by integrating with Sentry."
+msgstr ""
+
+msgid "Monitoring"
+msgstr ""
+
+msgid "Months"
+msgstr ""
+
+msgid "More"
+msgstr ""
+
+msgid "More Information"
+msgstr ""
+
+msgid "More Slack commands"
+msgstr ""
+
+msgid "More actions"
+msgstr ""
+
+msgid "More details"
+msgstr ""
+
+msgid "More info"
+msgstr ""
+
+msgid "More information"
+msgstr ""
+
+msgid "More information and share feedback"
+msgstr ""
+
+msgid "More information is available|here"
+msgstr ""
+
+msgid "More than %{number_commits_distance} commits different with %{default_branch}"
+msgstr ""
+
+msgid "Most stars"
+msgstr ""
+
+msgid "Mount point %{mounted_as} not found in %{model_class}."
+msgstr ""
+
+msgid "Move"
+msgstr ""
+
+msgid "Move issue"
+msgstr ""
+
+msgid "Move issue from one column of the board to another"
+msgstr ""
+
+msgid "Move selection down"
+msgstr ""
+
+msgid "Move selection up"
+msgstr ""
+
+msgid "Move this issue to another project."
+msgstr ""
+
+msgid "MoveIssue|Cannot move issue due to insufficient permissions!"
+msgstr ""
+
+msgid "MoveIssue|Cannot move issue to project it originates from!"
+msgstr ""
+
+msgid "Moved issue to %{label} column in the board."
+msgstr ""
+
+msgid "Moved this issue to %{path_to_project}."
+msgstr ""
+
+msgid "Moves issue to %{label} column in the board."
+msgstr ""
+
+msgid "Moves this issue to %{path_to_project}."
+msgstr ""
+
+msgid "MrDeploymentActions|Deploy"
+msgstr ""
+
+msgid "MrDeploymentActions|Re-deploy"
+msgstr ""
+
+msgid "MrDeploymentActions|Stop environment"
+msgstr ""
+
+msgid "Multiple domains are supported with comma delimiters."
+msgstr ""
+
+msgid "Multiple issue boards"
+msgstr ""
+
+msgid "Multiple model types found: %{model_types}"
+msgstr ""
+
+msgid "Multiple uploaders found: %{uploader_types}"
+msgstr ""
+
+msgid "Must match with the %{codeStart}external_url%{codeEnd} in %{codeStart}/etc/gitlab/gitlab.rb%{codeEnd}."
+msgstr ""
+
+msgid "Must match with the %{codeStart}geo_node_name%{codeEnd} in %{codeStart}/etc/gitlab/gitlab.rb%{codeEnd}. %{linkStart}More information%{linkEnd}"
+msgstr ""
+
+msgid "My Awesome Group"
+msgstr ""
+
+msgid "My company or team"
+msgstr ""
+
+msgid "My-Reaction"
+msgstr ""
+
+msgid "Name"
+msgstr ""
+
+msgid "Name has already been taken"
+msgstr ""
+
+msgid "Name new label"
+msgstr ""
+
+msgid "Name:"
+msgstr ""
+
+msgid "Namespace is empty"
+msgstr ""
+
+msgid "Namespaces to index"
+msgstr ""
+
+msgid "Naming, topics, avatar"
+msgstr ""
+
+msgid "Naming, visibility"
+msgstr ""
+
+msgid "Navigate to the project to close the milestone."
+msgstr ""
+
+msgid "Nav|Help"
+msgstr ""
+
+msgid "Nav|Home"
+msgstr ""
+
+msgid "Nav|Sign In / Register"
+msgstr ""
+
+msgid "Nav|Sign out and sign in with a different account"
+msgstr ""
+
+msgid "Need help?"
+msgstr ""
+
+msgid "Needs attention"
+msgstr ""
+
+msgid "Network"
+msgstr ""
+
+msgid "NetworkPolicies|Choose whether to enforce this policy."
+msgstr ""
+
+msgid "NetworkPolicies|Define this policy's location, conditions and actions."
+msgstr ""
+
+msgid "NetworkPolicies|Enforcement status"
+msgstr ""
+
+msgid "NetworkPolicies|Environment does not have deployment platform"
+msgstr ""
+
+msgid "NetworkPolicies|If you are using Auto DevOps, your %{monospacedStart}auto-deploy-values.yaml%{monospacedEnd} file will not be updated if you change a policy in this section. Auto DevOps users should make changes by following the %{linkStart}Container Network Policy documentation%{linkEnd}."
+msgstr ""
+
+msgid "NetworkPolicies|Invalid or empty policy"
+msgstr ""
+
+msgid "NetworkPolicies|Kubernetes error: %{error}"
+msgstr ""
+
+msgid "NetworkPolicies|Last modified"
+msgstr ""
+
+msgid "NetworkPolicies|Name"
+msgstr ""
+
+msgid "NetworkPolicies|No policies detected"
+msgstr ""
+
+msgid "NetworkPolicies|Policies are a specification of how groups of pods are allowed to communicate with each other's network endpoints."
+msgstr ""
+
+msgid "NetworkPolicies|Policy %{policyName} was successfully changed"
+msgstr ""
+
+msgid "NetworkPolicies|Policy definition"
+msgstr ""
+
+msgid "NetworkPolicies|Something went wrong, failed to update policy"
+msgstr ""
+
+msgid "NetworkPolicies|Something went wrong, unable to fetch policies"
+msgstr ""
+
+msgid "NetworkPolicies|Status"
+msgstr ""
+
+msgid "Never"
+msgstr ""
+
+msgid "New"
+msgstr ""
+
+msgid "New Application"
+msgstr ""
+
+msgid "New Branch"
+msgstr ""
+
+msgid "New Deploy Key"
+msgstr ""
+
+msgid "New Environment"
+msgstr ""
+
+msgid "New File"
+msgstr ""
+
+msgid "New Group"
+msgstr ""
+
+msgid "New Group Name"
+msgstr ""
+
+msgid "New Identity"
+msgstr ""
+
+msgid "New Issue"
+msgid_plural "New Issues"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "New Jira import"
+msgstr ""
+
+msgid "New Label"
+msgstr ""
+
+msgid "New Merge Request"
+msgstr ""
+
+msgid "New Milestone"
+msgstr ""
+
+msgid "New Pages Domain"
+msgstr ""
+
+msgid "New Password"
+msgstr ""
+
+msgid "New Pipeline Schedule"
+msgstr ""
+
+msgid "New Project"
+msgstr ""
+
+msgid "New Snippet"
+msgstr ""
+
+msgid "New User"
+msgstr ""
+
+msgid "New branch"
+msgstr ""
+
+msgid "New branch unavailable"
+msgstr ""
+
+msgid "New changes were added. %{linkStart}Reload the page to review them%{linkEnd}"
+msgstr ""
+
+msgid "New deploy key"
+msgstr ""
+
+msgid "New directory"
+msgstr ""
+
+msgid "New environment"
+msgstr ""
+
+msgid "New epic"
+msgstr ""
+
+msgid "New epic title"
+msgstr ""
+
+msgid "New file"
+msgstr ""
+
+msgid "New group"
+msgstr ""
+
+msgid "New health check access token has been generated!"
+msgstr ""
+
+msgid "New identity"
+msgstr ""
+
+msgid "New issue"
+msgstr ""
+
+msgid "New issue title"
+msgstr ""
+
+msgid "New iteration"
+msgstr ""
+
+msgid "New iteration created"
+msgstr ""
+
+msgid "New label"
+msgstr ""
+
+msgid "New merge request"
+msgstr ""
+
+msgid "New milestone"
+msgstr ""
+
+msgid "New password"
+msgstr ""
+
+msgid "New pipelines will cancel older, pending pipelines on the same branch"
+msgstr ""
+
+msgid "New project"
+msgstr ""
+
+msgid "New release"
+msgstr ""
+
+msgid "New requirement"
+msgstr ""
+
+msgid "New runners registration token has been generated!"
+msgstr ""
+
+msgid "New schedule"
+msgstr ""
+
+msgid "New snippet"
+msgstr ""
+
+msgid "New subgroup"
+msgstr ""
+
+msgid "New tag"
+msgstr ""
+
+msgid "New users set to external"
+msgstr ""
+
+msgid "New! Suggest changes directly"
+msgstr ""
+
+msgid "New..."
+msgstr ""
+
+msgid "Newest first"
+msgstr ""
+
+msgid "Newly registered users will by default be external"
+msgstr ""
+
+msgid "Next"
+msgstr ""
+
+msgid "Next commit"
+msgstr ""
+
+msgid "Next file in diff"
+msgstr ""
+
+msgid "Next unresolved discussion"
+msgstr ""
+
+msgid "Nickname"
+msgstr ""
+
+msgid "No"
+msgstr ""
+
+msgid "No %{header} for this request."
+msgstr ""
+
+msgid "No %{providerTitle} repositories found"
+msgstr ""
+
+msgid "No Epic"
+msgstr ""
+
+msgid "No Scopes"
+msgstr ""
+
+msgid "No Tag"
+msgstr ""
+
+msgid "No active admin user found"
+msgstr ""
+
+msgid "No activities found"
+msgstr ""
+
+msgid "No application_settings found"
+msgstr ""
+
+msgid "No approvers"
+msgstr ""
+
+msgid "No authentication methods configured."
+msgstr ""
+
+msgid "No available namespaces to fork the project."
+msgstr ""
+
+msgid "No branches found"
+msgstr ""
+
+msgid "No changes"
+msgstr ""
+
+msgid "No changes between %{ref_start}%{source_branch}%{ref_end} and %{ref_start}%{target_branch}%{ref_end}"
+msgstr ""
+
+msgid "No child epics match applied filters"
+msgstr ""
+
+msgid "No connection could be made to a Gitaly Server, please check your logs!"
+msgstr ""
+
+msgid "No containers available"
+msgstr ""
+
+msgid "No contributions"
+msgstr ""
+
+msgid "No contributions were found"
+msgstr ""
+
+msgid "No credit card required."
+msgstr ""
+
+msgid "No data found"
+msgstr ""
+
+msgid "No data to display"
+msgstr ""
+
+msgid "No deployments found"
+msgstr ""
+
+msgid "No due date"
+msgstr ""
+
+msgid "No endpoint provided"
+msgstr ""
+
+msgid "No errors to display."
+msgstr ""
+
+msgid "No estimate or time spent"
+msgstr ""
+
+msgid "No file chosen"
+msgstr ""
+
+msgid "No file hooks found."
+msgstr ""
+
+msgid "No file selected"
+msgstr ""
+
+msgid "No files"
+msgstr ""
+
+msgid "No files found."
+msgstr ""
+
+msgid "No forks are available to you."
+msgstr ""
+
+msgid "No grouping"
+msgstr ""
+
+msgid "No iteration"
+msgstr ""
+
+msgid "No iterations to show"
+msgstr ""
+
+msgid "No job log"
+msgstr ""
+
+msgid "No jobs to show"
+msgstr ""
+
+msgid "No label"
+msgstr ""
+
+msgid "No labels with such name or description"
+msgstr ""
+
+msgid "No license. All rights reserved"
+msgstr ""
+
+msgid "No licenses found."
+msgstr ""
+
+msgid "No matches found"
+msgstr ""
+
+msgid "No matching labels"
+msgstr ""
+
+msgid "No matching results"
+msgstr ""
+
+msgid "No merge requests found"
+msgstr ""
+
+msgid "No messages were logged"
+msgstr ""
+
+msgid "No milestone"
+msgstr ""
+
+msgid "No milestones to show"
+msgstr ""
+
+msgid "No other labels with such name or description"
+msgstr ""
+
+msgid "No panels matching properties %{opts}"
+msgstr ""
+
+msgid "No parent group"
+msgstr ""
+
+msgid "No pods available"
+msgstr ""
+
+msgid "No policy matches this license"
+msgstr ""
+
+msgid "No preview for this file type"
+msgstr ""
+
+msgid "No prioritized labels with such name or description"
+msgstr ""
+
+msgid "No public groups"
+msgstr ""
+
+msgid "No related merge requests found."
+msgstr ""
+
+msgid "No repository"
+msgstr ""
+
+msgid "No required pipeline"
+msgstr ""
+
+msgid "No runners found"
+msgstr ""
+
+msgid "No schedules"
+msgstr ""
+
+msgid "No stack trace for this error"
+msgstr ""
+
+msgid "No starrers matched your search"
+msgstr ""
+
+msgid "No start date"
+msgstr ""
+
+msgid "No template"
+msgstr ""
+
+msgid "No test coverage"
+msgstr ""
+
+msgid "No thanks"
+msgstr ""
+
+msgid "No vulnerabilities present"
+msgstr ""
+
+msgid "No webhooks found, add one in the form above."
+msgstr ""
+
+msgid "No worries, you can still use all the %{strong}%{plan_name}%{strong_close} features for now. You have %{remaining_days} to renew your subscription."
+msgstr ""
+
+msgid "No, directly import the existing email addresses and usernames."
+msgstr ""
+
+msgid "No. of commits"
+msgstr ""
+
+msgid "Nobody has starred this repository yet"
+msgstr ""
+
+msgid "Node was successfully created."
+msgstr ""
+
+msgid "Node was successfully updated."
+msgstr ""
+
+msgid "Nodes"
+msgstr ""
+
+msgid "Non-admin users can sign in with read-only access and make read-only API requests."
+msgstr ""
+
+msgid "None"
+msgstr ""
+
+msgid "Not Implemented"
+msgstr ""
+
+msgid "Not all data has been processed yet, the accuracy of the chart for the selected timeframe is limited."
+msgstr ""
+
+msgid "Not available"
+msgstr ""
+
+msgid "Not available for private projects"
+msgstr ""
+
+msgid "Not available for protected branches"
+msgstr ""
+
+msgid "Not confidential"
+msgstr ""
+
+msgid "Not enough data"
+msgstr ""
+
+msgid "Not found."
+msgstr ""
+
+msgid "Not now"
+msgstr ""
+
+msgid "Not ready yet. Try again later."
+msgstr ""
+
+msgid "Not started"
+msgstr ""
+
+msgid "Not-confidential epic cannot be assigned to a confidential parent epic"
+msgstr ""
+
+msgid "Note"
+msgstr ""
+
+msgid "Note parameters are invalid: %{errors}"
+msgstr ""
+
+msgid "Note that PostgreSQL 11 will become the minimum required PostgreSQL version in GitLab 13.0 (May 2020). PostgreSQL 9.6 and PostgreSQL 10 will no longer be supported in GitLab 13.0. Please consider upgrading your PostgreSQL version (%{db_version}) soon."
+msgstr ""
+
+msgid "Note that this invitation was sent to %{mail_to_invite_email}, but you are signed in as %{link_to_current_user} with email %{mail_to_current_user}."
+msgstr ""
+
+msgid "Note: As an administrator you may like to configure %{github_integration_link}, which will allow login via GitHub and allow connecting repositories without generating a Personal Access Token."
+msgstr ""
+
+msgid "Note: As an administrator you may like to configure %{github_integration_link}, which will allow login via GitHub and allow importing repositories without generating a Personal Access Token."
+msgstr ""
+
+msgid "Note: Consider asking your GitLab administrator to configure %{github_integration_link}, which will allow login via GitHub and allow connecting repositories without generating a Personal Access Token."
+msgstr ""
+
+msgid "Note: Consider asking your GitLab administrator to configure %{github_integration_link}, which will allow login via GitHub and allow importing repositories without generating a Personal Access Token."
+msgstr ""
+
+msgid "NoteForm|Note"
+msgstr ""
+
+msgid "Notes|Are you sure you want to cancel creating this comment?"
+msgstr ""
+
+msgid "Notes|Collapse replies"
+msgstr ""
+
+msgid "Notes|Private comments are accessible by internal staff only"
+msgstr ""
+
+msgid "Notes|Show all activity"
+msgstr ""
+
+msgid "Notes|Show comments only"
+msgstr ""
+
+msgid "Notes|Show history only"
+msgstr ""
+
+msgid "Notes|This comment has changed since you started editing, please review the %{open_link}updated comment%{close_link} to ensure information is not lost"
+msgstr ""
+
+msgid "Nothing found…"
+msgstr ""
+
+msgid "Nothing to preview."
+msgstr ""
+
+msgid "Nothing to synchronize"
+msgstr ""
+
+msgid "Notification events"
+msgstr ""
+
+msgid "Notification setting"
+msgstr ""
+
+msgid "Notification setting - %{notification_title}"
+msgstr ""
+
+msgid "Notification settings saved"
+msgstr ""
+
+msgid "NotificationEvent|Close issue"
+msgstr ""
+
+msgid "NotificationEvent|Close merge request"
+msgstr ""
+
+msgid "NotificationEvent|Failed pipeline"
+msgstr ""
+
+msgid "NotificationEvent|Fixed pipeline"
+msgstr ""
+
+msgid "NotificationEvent|Merge merge request"
+msgstr ""
+
+msgid "NotificationEvent|New epic"
+msgstr ""
+
+msgid "NotificationEvent|New issue"
+msgstr ""
+
+msgid "NotificationEvent|New merge request"
+msgstr ""
+
+msgid "NotificationEvent|New note"
+msgstr ""
+
+msgid "NotificationEvent|New release"
+msgstr ""
+
+msgid "NotificationEvent|Reassign issue"
+msgstr ""
+
+msgid "NotificationEvent|Reassign merge request"
+msgstr ""
+
+msgid "NotificationEvent|Reopen issue"
+msgstr ""
+
+msgid "NotificationEvent|Successful pipeline"
+msgstr ""
+
+msgid "NotificationLevel|Custom"
+msgstr ""
+
+msgid "NotificationLevel|Disabled"
+msgstr ""
+
+msgid "NotificationLevel|Global"
+msgstr ""
+
+msgid "NotificationLevel|On mention"
+msgstr ""
+
+msgid "NotificationLevel|Participate"
+msgstr ""
+
+msgid "NotificationLevel|Watch"
+msgstr ""
+
+msgid "NotificationSetting|Custom"
+msgstr ""
+
+msgid "Notifications"
+msgstr ""
+
+msgid "Notifications have been disabled by the project or group owner"
+msgstr ""
+
+msgid "Notifications off"
+msgstr ""
+
+msgid "Notifications on"
+msgstr ""
+
+msgid "Notify users by email when sign-in location is not recognized"
+msgstr ""
+
+msgid "Nov"
+msgstr ""
+
+msgid "November"
+msgstr ""
+
+msgid "Novice"
+msgstr ""
+
+msgid "Now you can access the merge request navigation tabs at the top, where they’re easier to find."
+msgstr ""
+
+msgid "Nuget metadatum must have at least license_url, project_url or icon_url set"
+msgstr ""
+
+msgid "Number of %{itemTitle}"
+msgstr ""
+
+msgid "Number of Elasticsearch replicas"
+msgstr ""
+
+msgid "Number of Elasticsearch shards"
+msgstr ""
+
+msgid "Number of LOCs per commit"
+msgstr ""
+
+msgid "Number of changes (branches or tags) in a single push to determine whether individual push events or bulk push event will be created. Bulk push event will be created if it surpasses that value."
+msgstr ""
+
+msgid "Number of changes (branches or tags) in a single push to determine whether webhooks and services will be fired or not. Webhooks and services won't be submitted if it surpasses that value."
+msgstr ""
+
+msgid "Number of commits"
+msgstr ""
+
+msgid "Number of commits per MR"
+msgstr ""
+
+msgid "Number of employees"
+msgstr ""
+
+msgid "Number of files touched"
+msgstr ""
+
+msgid "OK"
+msgstr ""
+
+msgid "Object Storage replication"
+msgstr ""
+
+msgid "Object does not exist on the server or you don't have permissions to access it"
+msgstr ""
+
+msgid "Oct"
+msgstr ""
+
+msgid "October"
+msgstr ""
+
+msgid "OfSearchInADropdown|Filter"
+msgstr ""
+
+msgid "Oh no!"
+msgstr ""
+
+msgid "Oldest first"
+msgstr ""
+
+msgid "OmniAuth"
+msgstr ""
+
+msgid "Omnibus Protected Paths throttle is active, and takes priority over these settings. From 12.4, Omnibus throttle is deprecated and will be removed in a future release. Please read the %{relative_url_link_start}Migrating Protected Paths documentation%{relative_url_link_end}."
+msgstr ""
+
+msgid "On track"
+msgstr ""
+
+msgid "OnDemandScans|Attached branch"
+msgstr ""
+
+msgid "OnDemandScans|Attached branch is where the scan job runs."
+msgstr ""
+
+msgid "OnDemandScans|Could not run the scan. Please try again."
+msgstr ""
+
+msgid "OnDemandScans|Create new DAST scan"
+msgstr ""
+
+msgid "OnDemandScans|DAST will scan the target URL and any discovered sub URLs."
+msgstr ""
+
+msgid "OnDemandScans|New on-demand DAST scan"
+msgstr ""
+
+msgid "OnDemandScans|On-demand Scans"
+msgstr ""
+
+msgid "OnDemandScans|On-demand scans run outside the DevOps cycle and find vulnerabilities in your projects. %{learnMoreLinkStart}Learn more%{learnMoreLinkEnd}"
+msgstr ""
+
+msgid "OnDemandScans|Only a passive scan can be performed on demand."
+msgstr ""
+
+msgid "OnDemandScans|Passive DAST Scan"
+msgstr ""
+
+msgid "OnDemandScans|Please enter a valid URL format, ex: http://www.example.com/home"
+msgstr ""
+
+msgid "OnDemandScans|Run this scan"
+msgstr ""
+
+msgid "OnDemandScans|Scan mode"
+msgstr ""
+
+msgid "OnDemandScans|Schedule or run scans immediately against target sites. Currently available on-demand scan type: DAST. %{helpLinkStart}More information%{helpLinkEnd}"
+msgstr ""
+
+msgid "OnDemandScans|Target URL"
+msgstr ""
+
+msgid "Once imported, repositories can be mirrored over SSH. Read more %{link_start}here%{link_end}."
+msgstr ""
+
+msgid "Once removed, the fork relationship cannot be restored and you will no longer be able to send merge requests to the source."
+msgstr ""
+
+msgid "Once the exported file is ready, you will receive a notification email with a download link, or you can download it from this page."
+msgstr ""
+
+msgid "Once you confirm and press \"Reduce project visibility\":"
+msgstr ""
+
+msgid "One more item"
+msgid_plural "%d more items"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "One or more groups that you don't have access to."
+msgstr ""
+
+msgid "One or more of you personal access tokens were revoked"
+msgstr ""
+
+msgid "One or more of your %{provider} projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
+msgstr ""
+
+msgid "One or more of your Bitbucket projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
+msgstr ""
+
+msgid "One or more of your Google Code projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
+msgstr ""
+
+msgid "One or more of your dependency files are not supported, and the dependency list may be incomplete. Below is a list of supported file types."
+msgstr ""
+
+msgid "One or more of your personal access tokens will expire in %{days_to_expire} days or less."
+msgstr ""
+
+msgid "Only 'Reporter' roles and above on tiers Premium / Silver and above can see Value Stream Analytics."
+msgstr ""
+
+msgid "Only 1 appearances row can exist"
+msgstr ""
+
+msgid "Only Issue ID or Merge Request ID is required"
+msgstr ""
+
+msgid "Only Project Members"
+msgstr ""
+
+msgid "Only active this projects shows up in the search and on the dashboard."
+msgstr ""
+
+msgid "Only admins"
+msgstr ""
+
+msgid "Only admins can delete project"
+msgstr ""
+
+msgid "Only mirror protected branches"
+msgstr ""
+
+msgid "Only policy:"
+msgstr ""
+
+msgid "Only proceed if you trust %{idp_url} to control your GitLab account sign in."
+msgstr ""
+
+msgid "Only project members can comment."
+msgstr ""
+
+msgid "Only project members will be imported. Group members will be skipped."
+msgstr ""
+
+msgid "Only verified users with an email address in any of these domains can be added to the group."
+msgstr ""
+
+msgid "Only ‘Reporter’ roles and above on tiers Premium / Silver and above can see Productivity Analytics."
+msgstr ""
+
+msgid "Oops, are you sure?"
+msgstr ""
+
+msgid "Open"
+msgstr ""
+
+msgid "Open Selection"
+msgstr ""
+
+msgid "Open comment type dropdown"
+msgstr ""
+
+msgid "Open errors"
+msgstr ""
+
+msgid "Open in Xcode"
+msgstr ""
+
+msgid "Open issues"
+msgstr ""
+
+msgid "Open projects"
+msgstr ""
+
+msgid "Open raw"
+msgstr ""
+
+msgid "Open sidebar"
+msgstr ""
+
+msgid "Open: %{openIssuesCount}"
+msgstr ""
+
+msgid "Open: %{open} • Closed: %{closed}"
+msgstr ""
+
+msgid "Opened"
+msgstr ""
+
+msgid "Opened MRs"
+msgstr ""
+
+msgid "Opened issues"
+msgstr ""
+
+msgid "OpenedNDaysAgo|Opened"
+msgstr ""
+
+msgid "Opens in a new window"
+msgstr ""
+
+msgid "Operation failed. Check pod logs for %{pod_name} for more details."
+msgstr ""
+
+msgid "Operation not allowed"
+msgstr ""
+
+msgid "Operation timed out. Check pod logs for %{pod_name} for more details."
+msgstr ""
+
+msgid "Operations"
+msgstr ""
+
+msgid "Operations Dashboard"
+msgstr ""
+
+msgid "Operations Settings"
+msgstr ""
+
+msgid "OperationsDashboard|Add a project to the dashboard"
+msgstr ""
+
+msgid "OperationsDashboard|Add projects"
+msgstr ""
+
+msgid "OperationsDashboard|More information"
+msgstr ""
+
+msgid "OperationsDashboard|Operations Dashboard"
+msgstr ""
+
+msgid "OperationsDashboard|The operations dashboard provides a summary of each project's operational health, including pipeline and alert statuses."
+msgstr ""
+
+msgid "Optional"
+msgstr ""
+
+msgid "Optional parameter \"variables\" must be a Hash. Ex: variables[key1]=value1"
+msgstr ""
+
+msgid "Optionally, you can %{link_to_customize} how FogBugz email addresses and usernames are imported into GitLab."
+msgstr ""
+
+msgid "Optionally, you can %{link_to_customize} how Google Code email addresses and usernames are imported into GitLab."
+msgstr ""
+
+msgid "Options"
+msgstr ""
+
+msgid "Or you can choose one of the suggested colors below"
+msgstr ""
+
+msgid "Origin"
+msgstr ""
+
+msgid "Other Labels"
+msgstr ""
+
+msgid "Other information"
+msgstr ""
+
+msgid "Other merge requests block this MR"
+msgstr ""
+
+msgid "Other visibility settings have been disabled by the administrator."
+msgstr ""
+
+msgid "Out-of-compliance with this project's policies and should be removed"
+msgstr ""
+
+msgid "Outbound requests"
+msgstr ""
+
+msgid "OutdatedBrowser|From May 2020 GitLab no longer supports Internet Explorer 11."
+msgstr ""
+
+msgid "OutdatedBrowser|GitLab may not work properly, because you are using an outdated web browser."
+msgstr ""
+
+msgid "OutdatedBrowser|Please install a %{browser_link_start}supported web browser%{browser_link_end} for a better experience."
+msgstr ""
+
+msgid "OutdatedBrowser|You can provide feedback %{feedback_link_start}on this issue%{feedback_link_end} or via your usual support channels."
+msgstr ""
+
+msgid "Outdent"
+msgstr ""
+
+msgid "Overridden"
+msgstr ""
+
+msgid "Overview"
+msgstr ""
+
+msgid "Overwrite diverged branches"
+msgstr ""
+
+msgid "Owned by anyone"
+msgstr ""
+
+msgid "Owned by me"
+msgstr ""
+
+msgid "Owner"
+msgstr ""
+
+msgid "Package Files"
+msgstr ""
+
+msgid "Package Registry"
+msgstr ""
+
+msgid "Package already exists"
+msgstr ""
+
+msgid "Package deleted successfully"
+msgstr ""
+
+msgid "Package information"
+msgstr ""
+
+msgid "Package recipe already exists"
+msgstr ""
+
+msgid "Package type must be Conan"
+msgstr ""
+
+msgid "Package type must be Maven"
+msgstr ""
+
+msgid "Package type must be NuGet"
+msgstr ""
+
+msgid "Package type must be PyPi"
+msgstr ""
+
+msgid "Package was removed"
+msgstr ""
+
+msgid "PackageRegistry|Add Conan Remote"
+msgstr ""
+
+msgid "PackageRegistry|Add NuGet Source"
+msgstr ""
+
+msgid "PackageRegistry|Conan"
+msgstr ""
+
+msgid "PackageRegistry|Conan Command"
+msgstr ""
+
+msgid "PackageRegistry|Copy .pypirc content"
+msgstr ""
+
+msgid "PackageRegistry|Copy Conan Command"
+msgstr ""
+
+msgid "PackageRegistry|Copy Conan Setup Command"
+msgstr ""
+
+msgid "PackageRegistry|Copy Maven XML"
+msgstr ""
+
+msgid "PackageRegistry|Copy Maven command"
+msgstr ""
+
+msgid "PackageRegistry|Copy Maven registry XML"
+msgstr ""
+
+msgid "PackageRegistry|Copy NuGet Command"
+msgstr ""
+
+msgid "PackageRegistry|Copy NuGet Setup Command"
+msgstr ""
+
+msgid "PackageRegistry|Copy Pip command"
+msgstr ""
+
+msgid "PackageRegistry|Copy and paste this inside your %{codeStart}pom.xml%{codeEnd} %{codeStart}dependencies%{codeEnd} block."
+msgstr ""
+
+msgid "PackageRegistry|Copy npm command"
+msgstr ""
+
+msgid "PackageRegistry|Copy npm setup command"
+msgstr ""
+
+msgid "PackageRegistry|Copy yarn command"
+msgstr ""
+
+msgid "PackageRegistry|Copy yarn setup command"
+msgstr ""
+
+msgid "PackageRegistry|Delete Package Version"
+msgstr ""
+
+msgid "PackageRegistry|Delete package"
+msgstr ""
+
+msgid "PackageRegistry|Filter by name"
+msgstr ""
+
+msgid "PackageRegistry|For more information on the Conan registry, %{linkStart}see the documentation%{linkEnd}."
+msgstr ""
+
+msgid "PackageRegistry|For more information on the Maven registry, %{linkStart}see the documentation%{linkEnd}."
+msgstr ""
+
+msgid "PackageRegistry|For more information on the NuGet registry, %{linkStart}see the documentation%{linkEnd}."
+msgstr ""
+
+msgid "PackageRegistry|For more information on the PyPi registry, %{linkStart}see the documentation%{linkEnd}."
+msgstr ""
+
+msgid "PackageRegistry|If you haven't already done so, you will need to add the below to your %{codeStart}.pypirc%{codeEnd} file."
+msgstr ""
+
+msgid "PackageRegistry|If you haven't already done so, you will need to add the below to your %{codeStart}pom.xml%{codeEnd} file."
+msgstr ""
+
+msgid "PackageRegistry|Installation"
+msgstr ""
+
+msgid "PackageRegistry|Is your favorite package manager missing? We'd love your help in building first-class support for it into GitLab! %{contributionLinkStart}Visit the contribution documentation%{contributionLinkEnd} to learn more about how to build support for new package managers into GitLab. Below is a list of package managers that are on our radar."
+msgstr ""
+
+msgid "PackageRegistry|Learn how to %{noPackagesLinkStart}publish and share your packages%{noPackagesLinkEnd} with GitLab."
+msgstr ""
+
+msgid "PackageRegistry|Manually Published"
+msgstr ""
+
+msgid "PackageRegistry|Maven"
+msgstr ""
+
+msgid "PackageRegistry|Maven Command"
+msgstr ""
+
+msgid "PackageRegistry|Maven XML"
+msgstr ""
+
+msgid "PackageRegistry|NPM"
+msgstr ""
+
+msgid "PackageRegistry|No upcoming issues"
+msgstr ""
+
+msgid "PackageRegistry|NuGet"
+msgstr ""
+
+msgid "PackageRegistry|NuGet Command"
+msgstr ""
+
+msgid "PackageRegistry|Pip Command"
+msgstr ""
+
+msgid "PackageRegistry|Pipeline %{linkStart}%{linkEnd} triggered %{timestamp} by %{author}"
+msgstr ""
+
+msgid "PackageRegistry|Published to the repository at %{timestamp}"
+msgstr ""
+
+msgid "PackageRegistry|PyPi"
+msgstr ""
+
+msgid "PackageRegistry|Registry Setup"
+msgstr ""
+
+msgid "PackageRegistry|Remove package"
+msgstr ""
+
+msgid "PackageRegistry|Sorry, your filter produced no results"
+msgstr ""
+
+msgid "PackageRegistry|There are no %{packageType} packages yet"
+msgstr ""
+
+msgid "PackageRegistry|There are no other versions of this package."
+msgstr ""
+
+msgid "PackageRegistry|There are no packages yet"
+msgstr ""
+
+msgid "PackageRegistry|There are no upcoming issues to display."
+msgstr ""
+
+msgid "PackageRegistry|There was a problem fetching the details for this package."
+msgstr ""
+
+msgid "PackageRegistry|This NuGet package has no dependencies."
+msgstr ""
+
+msgid "PackageRegistry|To widen your search, change or remove the filters above."
+msgstr ""
+
+msgid "PackageRegistry|Unable to fetch package version information."
+msgstr ""
+
+msgid "PackageRegistry|Unable to load package"
+msgstr ""
+
+msgid "PackageRegistry|Upcoming package managers"
+msgstr ""
+
+msgid "PackageRegistry|You are about to delete %{name}, this operation is irreversible, are you sure?"
+msgstr ""
+
+msgid "PackageRegistry|You are about to delete version %{version} of %{name}. Are you sure?"
+msgstr ""
+
+msgid "PackageRegistry|You may also need to setup authentication using an auth token. %{linkStart}See the documentation%{linkEnd} to find out more."
+msgstr ""
+
+msgid "PackageRegistry|npm"
+msgstr ""
+
+msgid "PackageRegistry|published by %{author}"
+msgstr ""
+
+msgid "PackageRegistry|yarn"
+msgstr ""
+
+msgid "PackageType|Conan"
+msgstr ""
+
+msgid "PackageType|Maven"
+msgstr ""
+
+msgid "PackageType|NPM"
+msgstr ""
+
+msgid "PackageType|NuGet"
+msgstr ""
+
+msgid "PackageType|PyPi"
+msgstr ""
+
+msgid "Packages"
+msgstr ""
+
+msgid "Packages & Registries"
+msgstr ""
+
+msgid "Page not found"
+msgstr ""
+
+msgid "Page was successfully deleted"
+msgstr ""
+
+msgid "Pages"
+msgstr ""
+
+msgid "Pages Domain"
+msgstr ""
+
+msgid "Pages getting started guide"
+msgstr ""
+
+msgid "Pagination|Go to first page"
+msgstr ""
+
+msgid "Pagination|Go to last page"
+msgstr ""
+
+msgid "Pagination|Go to next page"
+msgstr ""
+
+msgid "Pagination|Go to previous page"
+msgstr ""
+
+msgid "Pagination|Last »"
+msgstr ""
+
+msgid "Pagination|Next"
+msgstr ""
+
+msgid "Pagination|Prev"
+msgstr ""
+
+msgid "Pagination|« First"
+msgstr ""
+
+msgid "Parameter"
+msgstr ""
+
+msgid "Parameter \"job_id\" cannot exceed length of %{job_id_max_size}"
+msgstr ""
+
+msgid "Parent"
+msgstr ""
+
+msgid "Parent epic doesn't exist."
+msgstr ""
+
+msgid "Parent epic is not present."
+msgstr ""
+
+msgid "Parsing error for param :embed_json. %{message}"
+msgstr ""
+
+msgid "Part of merge request changes"
+msgstr ""
+
+msgid "Partial token for reference only"
+msgstr ""
+
+msgid "Participants"
+msgstr ""
+
+msgid "Passed"
+msgstr ""
+
+msgid "Passed on"
+msgstr ""
+
+msgid "Password"
+msgstr ""
+
+msgid "Password (optional)"
+msgstr ""
+
+msgid "Password Policy Guidelines"
+msgstr ""
+
+msgid "Password authentication is unavailable."
+msgstr ""
+
+msgid "Password confirmation"
+msgstr ""
+
+msgid "Password successfully changed"
+msgstr ""
+
+msgid "Password was successfully updated. Please login with it"
+msgstr ""
+
+msgid "Passwords should be unique and not used for any other sites or services."
+msgstr ""
+
+msgid "Past due"
+msgstr ""
+
+msgid "Paste a machine public key here. Read more about how to generate it"
+msgstr ""
+
+msgid "Paste a machine public key here. Read more about how to generate it %{link_start}here%{link_end}"
+msgstr ""
+
+msgid "Paste epic link"
+msgstr ""
+
+msgid "Paste issue link"
+msgstr ""
+
+msgid "Paste your public SSH key, which is usually contained in the file '~/.ssh/id_ed25519.pub' or '~/.ssh/id_rsa.pub' and begins with 'ssh-ed25519' or 'ssh-rsa'. Don't use your private SSH key."
+msgstr ""
+
+msgid "Patch to apply"
+msgstr ""
+
+msgid "Path"
+msgstr ""
+
+msgid "Path:"
+msgstr ""
+
+msgid "Paths can contain wildcards, like */welcome"
+msgstr ""
+
+msgid "Pause"
+msgstr ""
+
+msgid "Pause replication"
+msgstr ""
+
+msgid "Paused Runners don't accept new jobs"
+msgstr ""
+
+msgid "Pending"
+msgstr ""
+
+msgid "People without permission will never get a notification and won't be able to comment."
+msgstr ""
+
+msgid "People without permission will never get a notification."
+msgstr ""
+
+msgid "Percent rollout (logged in users)"
+msgstr ""
+
+msgid "Percentage"
+msgstr ""
+
+msgid "Perform advanced options such as changing path, transferring, exporting, or removing the group."
+msgstr ""
+
+msgid "Perform common operations on GitLab project"
+msgstr ""
+
+msgid "Performance and resource management"
+msgstr ""
+
+msgid "Performance optimization"
+msgstr ""
+
+msgid "PerformanceBar|Bullet notifications"
+msgstr ""
+
+msgid "PerformanceBar|Download"
+msgstr ""
+
+msgid "PerformanceBar|Elasticsearch calls"
+msgstr ""
+
+msgid "PerformanceBar|Frontend resources"
+msgstr ""
+
+msgid "PerformanceBar|Gitaly calls"
+msgstr ""
+
+msgid "PerformanceBar|Redis calls"
+msgstr ""
+
+msgid "PerformanceBar|Rugged calls"
+msgstr ""
+
+msgid "PerformanceBar|SQL queries"
+msgstr ""
+
+msgid "PerformanceBar|trace"
+msgstr ""
+
+msgid "Permissions"
+msgstr ""
+
+msgid "Permissions, LFS, 2FA"
+msgstr ""
+
+msgid "Personal Access Token"
+msgstr ""
+
+msgid "Personal project creation is not allowed. Please contact your administrator with questions"
+msgstr ""
+
+msgid "Phabricator Server Import"
+msgstr ""
+
+msgid "Phabricator Server URL"
+msgstr ""
+
+msgid "Phabricator Tasks"
+msgstr ""
+
+msgid "Pick a name"
+msgstr ""
+
+msgid "Pin code"
+msgstr ""
+
+msgid "Pipeline"
+msgstr ""
+
+msgid "Pipeline %{label}"
+msgstr ""
+
+msgid "Pipeline %{label} for \"%{dataTitle}\""
+msgstr ""
+
+msgid "Pipeline Schedule"
+msgstr ""
+
+msgid "Pipeline Schedules"
+msgstr ""
+
+msgid "Pipeline minutes quota"
+msgstr ""
+
+msgid "Pipeline subscriptions"
+msgstr ""
+
+msgid "Pipeline triggers"
+msgstr ""
+
+msgid "Pipeline: %{status}"
+msgstr ""
+
+msgid "PipelineCharts|CI / CD Analytics"
+msgstr ""
+
+msgid "PipelineCharts|Failed:"
+msgstr ""
+
+msgid "PipelineCharts|Overall statistics"
+msgstr ""
+
+msgid "PipelineCharts|Success ratio:"
+msgstr ""
+
+msgid "PipelineCharts|Successful:"
+msgstr ""
+
+msgid "PipelineCharts|Total:"
+msgstr ""
+
+msgid "PipelineSchedules|Activated"
+msgstr ""
+
+msgid "PipelineSchedules|Active"
+msgstr ""
+
+msgid "PipelineSchedules|All"
+msgstr ""
+
+msgid "PipelineSchedules|Inactive"
+msgstr ""
+
+msgid "PipelineSchedules|Next Run"
+msgstr ""
+
+msgid "PipelineSchedules|None"
+msgstr ""
+
+msgid "PipelineSchedules|Provide a short description for this pipeline"
+msgstr ""
+
+msgid "PipelineSchedules|Take ownership"
+msgstr ""
+
+msgid "PipelineSchedules|Target"
+msgstr ""
+
+msgid "PipelineSchedules|Variables"
+msgstr ""
+
+msgid "PipelineSheduleIntervalPattern|Custom"
+msgstr ""
+
+msgid "PipelineStatusTooltip|Pipeline: %{ciStatus}"
+msgstr ""
+
+msgid "PipelineStatusTooltip|Pipeline: %{ci_status}"
+msgstr ""
+
+msgid "Pipelines"
+msgstr ""
+
+msgid "Pipelines charts"
+msgstr ""
+
+msgid "Pipelines emails"
+msgstr ""
+
+msgid "Pipelines for last month (%{oneMonthAgo} - %{today})"
+msgstr ""
+
+msgid "Pipelines for last week (%{oneWeekAgo} - %{today})"
+msgstr ""
+
+msgid "Pipelines for last year"
+msgstr ""
+
+msgid "Pipelines for merge requests are configured. A detached pipeline runs in the context of the merge request, and not against the merged result. Learn more in the documentation for Pipelines for Merged Results."
+msgstr ""
+
+msgid "Pipelines must succeed for merge requests to be eligible to merge. Please enable pipelines for this project to continue. For more information, see the %{linkStart}documentation.%{linkEnd}"
+msgstr ""
+
+msgid "Pipelines settings for '%{project_name}' were successfully updated."
+msgstr ""
+
+msgid "Pipelines|API"
+msgstr ""
+
+msgid "Pipelines|Build with confidence"
+msgstr ""
+
+msgid "Pipelines|CI Lint"
+msgstr ""
+
+msgid "Pipelines|Child pipeline"
+msgstr ""
+
+msgid "Pipelines|Clear Runner Caches"
+msgstr ""
+
+msgid "Pipelines|Continuous Integration can help catch bugs by running your tests automatically, while Continuous Deployment can help you deliver code to your product environment."
+msgstr ""
+
+msgid "Pipelines|Get started with Pipelines"
+msgstr ""
+
+msgid "Pipelines|Group %{namespace_name} has %{percentage}%% or less Shared Runner Pipeline minutes remaining. Once it runs out, no new jobs or pipelines in its projects will run."
+msgstr ""
+
+msgid "Pipelines|Group %{namespace_name} has exceeded its pipeline minutes quota. Unless you buy additional pipeline minutes, no new jobs or pipelines in its projects will run."
+msgstr ""
+
+msgid "Pipelines|Loading Pipelines"
+msgstr ""
+
+msgid "Pipelines|Project cache successfully reset."
+msgstr ""
+
+msgid "Pipelines|Run Pipeline"
+msgstr ""
+
+msgid "Pipelines|Something went wrong while cleaning runners cache."
+msgstr ""
+
+msgid "Pipelines|There are currently no finished pipelines."
+msgstr ""
+
+msgid "Pipelines|There are currently no pipelines."
+msgstr ""
+
+msgid "Pipelines|There was an error fetching the pipelines. Try again in a few moments or contact your support team."
+msgstr ""
+
+msgid "Pipelines|This is a child pipeline within the parent pipeline"
+msgstr ""
+
+msgid "Pipelines|This project is not currently set up to run pipelines."
+msgstr ""
+
+msgid "Pipelines|parent"
+msgstr ""
+
+msgid "Pipeline|Branch name"
+msgstr ""
+
+msgid "Pipeline|Canceled"
+msgstr ""
+
+msgid "Pipeline|Commit"
+msgstr ""
+
+msgid "Pipeline|Could not retrieve the pipeline status. For troubleshooting steps, read the %{linkStart}documentation%{linkEnd}."
+msgstr ""
+
+msgid "Pipeline|Coverage"
+msgstr ""
+
+msgid "Pipeline|Created"
+msgstr ""
+
+msgid "Pipeline|Date"
+msgstr ""
+
+msgid "Pipeline|Detached merge request pipeline"
+msgstr ""
+
+msgid "Pipeline|Duration"
+msgstr ""
+
+msgid "Pipeline|Existing branch name or tag"
+msgstr ""
+
+msgid "Pipeline|Failed"
+msgstr ""
+
+msgid "Pipeline|Key"
+msgstr ""
+
+msgid "Pipeline|Manual"
+msgstr ""
+
+msgid "Pipeline|Merge train pipeline"
+msgstr ""
+
+msgid "Pipeline|Merged result pipeline"
+msgstr ""
+
+msgid "Pipeline|No pipeline has been run for this commit."
+msgstr ""
+
+msgid "Pipeline|Passed"
+msgstr ""
+
+msgid "Pipeline|Pending"
+msgstr ""
+
+msgid "Pipeline|Pipeline"
+msgstr ""
+
+msgid "Pipeline|Pipelines"
+msgstr ""
+
+msgid "Pipeline|Raw text search is not currently supported. Please use the available search tokens."
+msgstr ""
+
+msgid "Pipeline|Run Pipeline"
+msgstr ""
+
+msgid "Pipeline|Run for"
+msgstr ""
+
+msgid "Pipeline|Running"
+msgstr ""
+
+msgid "Pipeline|Search branches"
+msgstr ""
+
+msgid "Pipeline|Skipped"
+msgstr ""
+
+msgid "Pipeline|Specify variable values to be used in this run. The values specified in %{settings_link} will be used by default."
+msgstr ""
+
+msgid "Pipeline|Stages"
+msgstr ""
+
+msgid "Pipeline|Status"
+msgstr ""
+
+msgid "Pipeline|Stop pipeline"
+msgstr ""
+
+msgid "Pipeline|Stop pipeline #%{pipelineId}?"
+msgstr ""
+
+msgid "Pipeline|Tag name"
+msgstr ""
+
+msgid "Pipeline|Trigger author"
+msgstr ""
+
+msgid "Pipeline|Triggerer"
+msgstr ""
+
+msgid "Pipeline|Value"
+msgstr ""
+
+msgid "Pipeline|Variables"
+msgstr ""
+
+msgid "Pipeline|You’re about to stop pipeline %{pipelineId}."
+msgstr ""
+
+msgid "Pipeline|for"
+msgstr ""
+
+msgid "Pipeline|on"
+msgstr ""
+
+msgid "Pipeline|with stage"
+msgstr ""
+
+msgid "Pipeline|with stages"
+msgstr ""
+
+msgid "PivotalTrackerService|Comma-separated list of branches which will be automatically inspected. Leave blank to include all branches."
+msgstr ""
+
+msgid "PivotalTrackerService|Pivotal Tracker API token."
+msgstr ""
+
+msgid "PivotalTrackerService|Project Management Software (Source Commits Endpoint)"
+msgstr ""
+
+msgid "Plain diff"
+msgstr ""
+
+msgid "Plan"
+msgstr ""
+
+msgid "Plan:"
+msgstr ""
+
+msgid "PlantUML"
+msgstr ""
+
+msgid "Play"
+msgstr ""
+
+msgid "Play all manual"
+msgstr ""
+
+msgid "Please %{link_to_register} or %{link_to_sign_in} to comment"
+msgstr ""
+
+msgid "Please %{startTagRegister}register%{endRegisterTag} or %{startTagSignIn}sign in%{endSignInTag} to reply"
+msgstr ""
+
+msgid "Please accept the Terms of Service before continuing."
+msgstr ""
+
+msgid "Please add a comment in the text area above"
+msgstr ""
+
+msgid "Please add a list to your board first"
+msgstr ""
+
+msgid "Please check the configuration file for this chart"
+msgstr ""
+
+msgid "Please check the configuration file to ensure that a collection of charts has been declared."
+msgstr ""
+
+msgid "Please check the configuration file to ensure that it is available and the YAML is valid"
+msgstr ""
+
+msgid "Please check your email (%{email}) to verify that you own this address and unlock the power of CI/CD. Didn't receive it? %{resend_link}. Wrong email address? %{update_link}."
+msgstr ""
+
+msgid "Please choose a file"
+msgstr ""
+
+msgid "Please choose a group URL with no special characters."
+msgstr ""
+
+msgid "Please complete your profile with email address"
+msgstr ""
+
+msgid "Please convert %{linkStart}them to Git%{linkEnd}, and go through the %{linkToImportFlow} again."
+msgstr ""
+
+msgid "Please convert them to %{link_to_git}, and go through the %{link_to_import_flow} again."
+msgstr ""
+
+msgid "Please convert them to Git on Google Code, and go through the %{link_to_import_flow} again."
+msgstr ""
+
+msgid "Please create a password for your new account."
+msgstr ""
+
+msgid "Please create a username with only alphanumeric characters."
+msgstr ""
+
+msgid "Please create an index before enabling indexing"
+msgstr ""
+
+msgid "Please enable and migrate to hashed storage to avoid security issues and ensure data integrity. %{migrate_link}"
+msgstr ""
+
+msgid "Please ensure your account's %{account_link_start}recovery settings%{account_link_end} are up to date."
+msgstr ""
+
+msgid "Please enter a non-negative number"
+msgstr ""
+
+msgid "Please enter a number greater than %{number} (from the project settings)"
+msgstr ""
+
+msgid "Please enter a valid number"
+msgstr ""
+
+msgid "Please enter or upload a license."
+msgstr ""
+
+msgid "Please fill in a descriptive name for your group."
+msgstr ""
+
+msgid "Please follow the %{link_start}Let's Encrypt troubleshooting instructions%{link_end} to re-obtain your Let's Encrypt certificate."
+msgstr ""
+
+msgid "Please follow the Let's Encrypt troubleshooting instructions to re-obtain your Let's Encrypt certificate: %{docs_url}."
+msgstr ""
+
+msgid "Please migrate all existing projects to hashed storage to avoid security issues and ensure data integrity. %{migrate_link}"
+msgstr ""
+
+msgid "Please note that this application is not provided by GitLab and you should verify its authenticity before allowing access."
+msgstr ""
+
+msgid "Please provide a name"
+msgstr ""
+
+msgid "Please provide a valid URL"
+msgstr ""
+
+msgid "Please provide a valid email address."
+msgstr ""
+
+msgid "Please provide attributes to update"
+msgstr ""
+
+msgid "Please refer to <a href=\"%{docs_url}\">%{docs_url}</a>"
+msgstr ""
+
+msgid "Please retype the email address."
+msgstr ""
+
+msgid "Please select"
+msgstr ""
+
+msgid "Please select a Jira project"
+msgstr ""
+
+msgid "Please select a country"
+msgstr ""
+
+msgid "Please select a file"
+msgstr ""
+
+msgid "Please select a group."
+msgstr ""
+
+msgid "Please select a valid target branch"
+msgstr ""
+
+msgid "Please select and add a member"
+msgstr ""
+
+msgid "Please select at least one filter to see results"
+msgstr ""
+
+msgid "Please set a new password before proceeding."
+msgstr ""
+
+msgid "Please solve the reCAPTCHA"
+msgstr ""
+
+msgid "Please try again"
+msgstr ""
+
+msgid "Please type %{phrase_code} to proceed or close this modal to cancel."
+msgstr ""
+
+msgid "Please upgrade PostgreSQL to version 9.6 or greater. The status of the replication cannot be determined reliably with the current version."
+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 moment, this page will automatically refresh when ready."
+msgstr ""
+
+msgid "Please wait while we connect to your repository. Refresh at will."
+msgstr ""
+
+msgid "Please wait while we import the repository for you. Refresh at will."
+msgstr ""
+
+msgid "Plugins directory is deprecated and will be removed in 14.0. Please move this file into /file_hooks directory."
+msgstr ""
+
+msgid "Pod does not exist"
+msgstr ""
+
+msgid "Pod not found"
+msgstr ""
+
+msgid "Pods in use"
+msgstr ""
+
+msgid "Point to any links you like: documentation, built binaries, or other related materials. These can be internal or external links from your GitLab instance. Duplicate URLs are not allowed."
+msgstr ""
+
+msgid "Pre-defined push rules."
+msgstr ""
+
+msgid "Preferences"
+msgstr ""
+
+msgid "Preferences saved."
+msgstr ""
+
+msgid "Preferences|Behavior"
+msgstr ""
+
+msgid "Preferences|Choose between fixed (max. 1280px) and fluid (%{percentage}) application layout."
+msgstr ""
+
+msgid "Preferences|Choose what content you want to see on a project’s overview page."
+msgstr ""
+
+msgid "Preferences|Customize integrations with third party services."
+msgstr ""
+
+msgid "Preferences|Customize the appearance of the application header and navigation sidebar."
+msgstr ""
+
+msgid "Preferences|Default dashboard"
+msgstr ""
+
+msgid "Preferences|Display time in 24-hour format"
+msgstr ""
+
+msgid "Preferences|Enable integrated code intelligence on code views"
+msgstr ""
+
+msgid "Preferences|For example: 30 mins ago."
+msgstr ""
+
+msgid "Preferences|Integrations"
+msgstr ""
+
+msgid "Preferences|Layout width"
+msgstr ""
+
+msgid "Preferences|Must be a number between %{min} and %{max}"
+msgstr ""
+
+msgid "Preferences|Navigation theme"
+msgstr ""
+
+msgid "Preferences|Project overview content"
+msgstr ""
+
+msgid "Preferences|Render whitespace characters in the Web IDE"
+msgstr ""
+
+msgid "Preferences|Show whitespace changes in diffs"
+msgstr ""
+
+msgid "Preferences|Sourcegraph"
+msgstr ""
+
+msgid "Preferences|Syntax highlighting theme"
+msgstr ""
+
+msgid "Preferences|Tab width"
+msgstr ""
+
+msgid "Preferences|These settings will update how dates and times are displayed for you."
+msgstr ""
+
+msgid "Preferences|This feature is experimental and translations are not complete yet"
+msgstr ""
+
+msgid "Preferences|This setting allows you to customize the appearance of the syntax."
+msgstr ""
+
+msgid "Preferences|This setting allows you to customize the behavior of the system layout and default views."
+msgstr ""
+
+msgid "Preferences|Time display"
+msgstr ""
+
+msgid "Preferences|Time format"
+msgstr ""
+
+msgid "Preferences|Time preferences"
+msgstr ""
+
+msgid "Preferences|Use relative times"
+msgstr ""
+
+msgid "Press %{key}-C to copy"
+msgstr ""
+
+msgid "Prev"
+msgstr ""
+
+msgid "Prevent adding new members to project membership within this group"
+msgstr ""
+
+msgid "Prevent approval of merge requests by merge request author"
+msgstr ""
+
+msgid "Prevent approval of merge requests by merge request committers"
+msgstr ""
+
+msgid "Prevent environment from auto-stopping"
+msgstr ""
+
+msgid "Prevent users from changing their profile name"
+msgstr ""
+
+msgid "Prevent users from modifying merge request approvers list"
+msgstr ""
+
+msgid "Prevent users from performing write operations on GitLab while performing maintenance."
+msgstr ""
+
+msgid "Preview"
+msgstr ""
+
+msgid "Preview Markdown"
+msgstr ""
+
+msgid "Preview changes"
+msgstr ""
+
+msgid "Preview payload"
+msgstr ""
+
+msgid "Previous Artifacts"
+msgstr ""
+
+msgid "Previous commit"
+msgstr ""
+
+msgid "Previous file in diff"
+msgstr ""
+
+msgid "Previous unresolved discussion"
+msgstr ""
+
+msgid "Primary"
+msgstr ""
+
+msgid "Prioritize"
+msgstr ""
+
+msgid "Prioritize label"
+msgstr ""
+
+msgid "Prioritized Labels"
+msgstr ""
+
+msgid "Prioritized label"
+msgstr ""
+
+msgid "Private"
+msgstr ""
+
+msgid "Private - Project access must be granted explicitly to each user. If this project is part of a group, access will be granted to members of the group."
+msgstr ""
+
+msgid "Private - The group and its projects can only be viewed by members."
+msgstr ""
+
+msgid "Private group(s)"
+msgstr ""
+
+msgid "Private profile"
+msgstr ""
+
+msgid "Private projects Minutes cost factor"
+msgstr ""
+
+msgid "Private projects can be created in your personal namespace with:"
+msgstr ""
+
+msgid "Proceed"
+msgstr ""
+
+msgid "Productivity"
+msgstr ""
+
+msgid "Productivity Analytics"
+msgstr ""
+
+msgid "Productivity analytics can help identify the problems that are delaying your team"
+msgstr ""
+
+msgid "ProductivityAanalytics|Merge requests"
+msgstr ""
+
+msgid "ProductivityAanalytics|is earlier than the allowed minimum date"
+msgstr ""
+
+msgid "ProductivityAnalytics|Ascending"
+msgstr ""
+
+msgid "ProductivityAnalytics|Days"
+msgstr ""
+
+msgid "ProductivityAnalytics|Days to merge"
+msgstr ""
+
+msgid "ProductivityAnalytics|Descending"
+msgstr ""
+
+msgid "ProductivityAnalytics|Hours"
+msgstr ""
+
+msgid "ProductivityAnalytics|List"
+msgstr ""
+
+msgid "ProductivityAnalytics|Merge Requests"
+msgstr ""
+
+msgid "ProductivityAnalytics|Merge date"
+msgstr ""
+
+msgid "ProductivityAnalytics|Merge requests"
+msgstr ""
+
+msgid "ProductivityAnalytics|Time to merge"
+msgstr ""
+
+msgid "ProductivityAnalytics|Trendline"
+msgstr ""
+
+msgid "ProductivityAnalytics|is earlier than the given merged at after date"
+msgstr ""
+
+msgid "Profile"
+msgstr ""
+
+msgid "Profile Settings"
+msgstr ""
+
+msgid "ProfileSession|on"
+msgstr ""
+
+msgid "Profiles| You are about to permanently delete %{yourAccount}, and all of the issues, merge requests, and groups linked to your account. Once you confirm %{deleteAccount}, it cannot be undone or recovered."
+msgstr ""
+
+msgid "Profiles| You are going to change the username %{currentUsernameBold} to %{newUsernameBold}. Profile and projects will be redirected to the %{newUsername} namespace but this redirect will expire once the %{currentUsername} namespace is registered by another user or group. Please update your Git repository remotes as soon as possible."
+msgstr ""
+
+msgid "Profiles|@username"
+msgstr ""
+
+msgid "Profiles|Account scheduled for removal."
+msgstr ""
+
+msgid "Profiles|Activate signin with one of the following services"
+msgstr ""
+
+msgid "Profiles|Active"
+msgstr ""
+
+msgid "Profiles|Add key"
+msgstr ""
+
+msgid "Profiles|Add status emoji"
+msgstr ""
+
+msgid "Profiles|Avatar cropper"
+msgstr ""
+
+msgid "Profiles|Avatar will be removed. Are you sure?"
+msgstr ""
+
+msgid "Profiles|Bio"
+msgstr ""
+
+msgid "Profiles|Change username"
+msgstr ""
+
+msgid "Profiles|Changing your username can have unintended side effects."
+msgstr ""
+
+msgid "Profiles|Choose file..."
+msgstr ""
+
+msgid "Profiles|Choose to show contributions of private projects on your public profile without any project, repository or organization information"
+msgstr ""
+
+msgid "Profiles|City, country"
+msgstr ""
+
+msgid "Profiles|Clear status"
+msgstr ""
+
+msgid "Profiles|Click on icon to activate signin with one of the following services"
+msgstr ""
+
+msgid "Profiles|Commit email"
+msgstr ""
+
+msgid "Profiles|Connect"
+msgstr ""
+
+msgid "Profiles|Connected Accounts"
+msgstr ""
+
+msgid "Profiles|Current path: %{path}"
+msgstr ""
+
+msgid "Profiles|Current status"
+msgstr ""
+
+msgid "Profiles|Default notification email"
+msgstr ""
+
+msgid "Profiles|Delete Account"
+msgstr ""
+
+msgid "Profiles|Delete account"
+msgstr ""
+
+msgid "Profiles|Delete your account?"
+msgstr ""
+
+msgid "Profiles|Deleting an account has the following effects:"
+msgstr ""
+
+msgid "Profiles|Disconnect"
+msgstr ""
+
+msgid "Profiles|Do not show on profile"
+msgstr ""
+
+msgid "Profiles|Don't display activity-related personal information on your profiles"
+msgstr ""
+
+msgid "Profiles|Edit Profile"
+msgstr ""
+
+msgid "Profiles|Enter your name, so people you know can recognize you"
+msgstr ""
+
+msgid "Profiles|Expires at"
+msgstr ""
+
+msgid "Profiles|Expires:"
+msgstr ""
+
+msgid "Profiles|Feed token was successfully reset"
+msgstr ""
+
+msgid "Profiles|Full name"
+msgstr ""
+
+msgid "Profiles|Give your individual key a title"
+msgstr ""
+
+msgid "Profiles|Include private contributions on my profile"
+msgstr ""
+
+msgid "Profiles|Incoming email token was successfully reset"
+msgstr ""
+
+msgid "Profiles|Increase your account's security by enabling Two-Factor Authentication (2FA)"
+msgstr ""
+
+msgid "Profiles|Invalid password"
+msgstr ""
+
+msgid "Profiles|Invalid username"
+msgstr ""
+
+msgid "Profiles|Key"
+msgstr ""
+
+msgid "Profiles|Last used:"
+msgstr ""
+
+msgid "Profiles|Learn more"
+msgstr ""
+
+msgid "Profiles|Location"
+msgstr ""
+
+msgid "Profiles|Made a private contribution"
+msgstr ""
+
+msgid "Profiles|Main settings"
+msgstr ""
+
+msgid "Profiles|No file chosen"
+msgstr ""
+
+msgid "Profiles|Notification email"
+msgstr ""
+
+msgid "Profiles|Organization"
+msgstr ""
+
+msgid "Profiles|Path"
+msgstr ""
+
+msgid "Profiles|Position and size your new avatar"
+msgstr ""
+
+msgid "Profiles|Primary email"
+msgstr ""
+
+msgid "Profiles|Private contributions"
+msgstr ""
+
+msgid "Profiles|Profile was successfully updated"
+msgstr ""
+
+msgid "Profiles|Public Avatar"
+msgstr ""
+
+msgid "Profiles|Public email"
+msgstr ""
+
+msgid "Profiles|Remove avatar"
+msgstr ""
+
+msgid "Profiles|Set new profile picture"
+msgstr ""
+
+msgid "Profiles|Social sign-in"
+msgstr ""
+
+msgid "Profiles|Some options are unavailable for LDAP accounts"
+msgstr ""
+
+msgid "Profiles|Static object token was successfully reset"
+msgstr ""
+
+msgid "Profiles|Tell us about yourself in fewer than 250 characters"
+msgstr ""
+
+msgid "Profiles|The ability to update your name has been disabled by your administrator."
+msgstr ""
+
+msgid "Profiles|The maximum file size allowed is 200KB."
+msgstr ""
+
+msgid "Profiles|This doesn't look like a public SSH key, are you sure you want to add it?"
+msgstr ""
+
+msgid "Profiles|This email will be displayed on your public profile"
+msgstr ""
+
+msgid "Profiles|This email will be used for web based operations, such as edits and merges. %{commit_email_link_start}Learn more%{commit_email_link_end}"
+msgstr ""
+
+msgid "Profiles|This emoji and message will appear on your profile and throughout the interface."
+msgstr ""
+
+msgid "Profiles|This information will appear on your profile"
+msgstr ""
+
+msgid "Profiles|Time settings"
+msgstr ""
+
+msgid "Profiles|Two-Factor Authentication"
+msgstr ""
+
+msgid "Profiles|Type your %{confirmationValue} to confirm:"
+msgstr ""
+
+msgid "Profiles|Typically starts with \"ssh-ed25519 …\" or \"ssh-rsa …\""
+msgstr ""
+
+msgid "Profiles|Update profile settings"
+msgstr ""
+
+msgid "Profiles|Update username"
+msgstr ""
+
+msgid "Profiles|Upload new avatar"
+msgstr ""
+
+msgid "Profiles|Use a private email - %{email}"
+msgstr ""
+
+msgid "Profiles|User ID"
+msgstr ""
+
+msgid "Profiles|Username change failed - %{message}"
+msgstr ""
+
+msgid "Profiles|Username successfully changed"
+msgstr ""
+
+msgid "Profiles|Using emojis in names seems fun, but please try to set a status message instead"
+msgstr ""
+
+msgid "Profiles|What's your status?"
+msgstr ""
+
+msgid "Profiles|Who you represent or work for"
+msgstr ""
+
+msgid "Profiles|You can change your avatar here"
+msgstr ""
+
+msgid "Profiles|You can change your avatar here or remove the current avatar to revert to %{gravatar_link}"
+msgstr ""
+
+msgid "Profiles|You can set your current timezone here"
+msgstr ""
+
+msgid "Profiles|You can upload your avatar here"
+msgstr ""
+
+msgid "Profiles|You can upload your avatar here or change it at %{gravatar_link}"
+msgstr ""
+
+msgid "Profiles|You don't have access to delete this user."
+msgstr ""
+
+msgid "Profiles|You must transfer ownership or delete these groups before you can delete your account."
+msgstr ""
+
+msgid "Profiles|Your LinkedIn profile name from linkedin.com/in/profilename"
+msgstr ""
+
+msgid "Profiles|Your account is currently an owner in these groups:"
+msgstr ""
+
+msgid "Profiles|Your email address was automatically set based on your %{provider_label} account"
+msgstr ""
+
+msgid "Profiles|Your key has expired"
+msgstr ""
+
+msgid "Profiles|Your location was automatically set based on your %{provider_label} account"
+msgstr ""
+
+msgid "Profiles|Your name was automatically set based on your %{provider_label} account, so people you know can recognize you"
+msgstr ""
+
+msgid "Profiles|Your status"
+msgstr ""
+
+msgid "Profiles|e.g. My MacBook key"
+msgstr ""
+
+msgid "Profiles|username"
+msgstr ""
+
+msgid "Profiles|website.com"
+msgstr ""
+
+msgid "Profiles|your account"
+msgstr ""
+
+msgid "Profile|%{job_title} at %{organization}"
+msgstr ""
+
+msgid "Profiling - Performance bar"
+msgstr ""
+
+msgid "Programming languages used in this repository"
+msgstr ""
+
+msgid "Progress"
+msgstr ""
+
+msgid "Project"
+msgstr ""
+
+msgid "Project \"%{name}\" is no longer available. Select another project to continue."
+msgstr ""
+
+msgid "Project %{project_repo} could not be found"
+msgstr ""
+
+msgid "Project & Group can not be assigned at the same time"
+msgstr ""
+
+msgid "Project '%{project_name}' is being imported."
+msgstr ""
+
+msgid "Project '%{project_name}' is in the process of being deleted."
+msgstr ""
+
+msgid "Project '%{project_name}' is restored."
+msgstr ""
+
+msgid "Project '%{project_name}' queued for deletion."
+msgstr ""
+
+msgid "Project '%{project_name}' was successfully created."
+msgstr ""
+
+msgid "Project '%{project_name}' was successfully updated."
+msgstr ""
+
+msgid "Project '%{project_name}' will be deleted on %{date}"
+msgstr ""
+
+msgid "Project Access Tokens"
+msgstr ""
+
+msgid "Project Audit Events"
+msgstr ""
+
+msgid "Project Badges"
+msgstr ""
+
+msgid "Project Files"
+msgstr ""
+
+msgid "Project ID"
+msgstr ""
+
+msgid "Project URL"
+msgstr ""
+
+msgid "Project access must be granted explicitly to each user. If this project is part of a group, access will be granted to members of the group."
+msgstr ""
+
+msgid "Project already deleted"
+msgstr ""
+
+msgid "Project and wiki repositories"
+msgstr ""
+
+msgid "Project avatar"
+msgstr ""
+
+msgid "Project cannot be shared with the group it is in or one of its ancestors."
+msgstr ""
+
+msgid "Project clone URL"
+msgstr ""
+
+msgid "Project configuration, including services"
+msgstr ""
+
+msgid "Project description (optional)"
+msgstr ""
+
+msgid "Project details"
+msgstr ""
+
+msgid "Project does not exist or you don't have permission to perform this action"
+msgstr ""
+
+msgid "Project export could not be deleted."
+msgstr ""
+
+msgid "Project export enabled"
+msgstr ""
+
+msgid "Project export has been deleted."
+msgstr ""
+
+msgid "Project export link has expired. Please generate a new export from your project settings."
+msgstr ""
+
+msgid "Project export started. A download link will be sent by email and made available on this page."
+msgstr ""
+
+msgid "Project has too many %{label_for_message} to search"
+msgstr ""
+
+msgid "Project is required when cluster_type is :project"
+msgstr ""
+
+msgid "Project members"
+msgstr ""
+
+msgid "Project milestone"
+msgstr ""
+
+msgid "Project name"
+msgstr ""
+
+msgid "Project name suffix"
+msgstr ""
+
+msgid "Project name suffix is a user-defined string which will be appended to the project path, and will form the Service Desk email address."
+msgstr ""
+
+msgid "Project order will not be saved as local storage is not available."
+msgstr ""
+
+msgid "Project overview"
+msgstr ""
+
+msgid "Project path"
+msgstr ""
+
+msgid "Project scanning help page"
+msgstr ""
+
+msgid "Project security status"
+msgstr ""
+
+msgid "Project security status help page"
+msgstr ""
+
+msgid "Project slug"
+msgstr ""
+
+msgid "Project uploads"
+msgstr ""
+
+msgid "Project visibility level will be changed to match namespace rules when transferring to a group."
+msgstr ""
+
+msgid "Project: %{name}"
+msgstr ""
+
+msgid "ProjectActivityRSS|Subscribe"
+msgstr ""
+
+msgid "ProjectCreationLevel|Allowed to create projects"
+msgstr ""
+
+msgid "ProjectCreationLevel|Default project creation protection"
+msgstr ""
+
+msgid "ProjectCreationLevel|Developers + Maintainers"
+msgstr ""
+
+msgid "ProjectCreationLevel|Maintainers"
+msgstr ""
+
+msgid "ProjectCreationLevel|No one"
+msgstr ""
+
+msgid "ProjectFileTree|Name"
+msgstr ""
+
+msgid "ProjectLastActivity|Never"
+msgstr ""
+
+msgid "ProjectLifecycle|Stage"
+msgstr ""
+
+msgid "ProjectOverview|Fork"
+msgstr ""
+
+msgid "ProjectOverview|Forks"
+msgstr ""
+
+msgid "ProjectOverview|Go to your fork"
+msgstr ""
+
+msgid "ProjectOverview|Star"
+msgstr ""
+
+msgid "ProjectOverview|Starrer"
+msgstr ""
+
+msgid "ProjectOverview|Starrers"
+msgstr ""
+
+msgid "ProjectOverview|Unstar"
+msgstr ""
+
+msgid "ProjectOverview|You have reached your project limit"
+msgstr ""
+
+msgid "ProjectOverview|You must sign in to star a project"
+msgstr ""
+
+msgid "ProjectPage|Project ID: %{project_id}"
+msgstr ""
+
+msgid "ProjectSelect| or group"
+msgstr ""
+
+msgid "ProjectSelect|Search for project"
+msgstr ""
+
+msgid "ProjectService|%{service_title}: status off"
+msgstr ""
+
+msgid "ProjectService|%{service_title}: status on"
+msgstr ""
+
+msgid "ProjectService|Comment"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered by a push to the repository"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a commit is created/updated"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a confidential issue is created/updated/closed"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a deployment finishes"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a merge request is created/updated/merged"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a new tag is pushed to the repository"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a new, unique alert is recorded"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a pipeline status changes"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a wiki page is created/updated"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when an issue is created/updated/closed"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when someone adds a comment"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when someone adds a comment on a confidential issue"
+msgstr ""
+
+msgid "ProjectService|Perform common operations on GitLab project: %{project_name}"
+msgstr ""
+
+msgid "ProjectService|To set up this service:"
+msgstr ""
+
+msgid "ProjectSettings|Additional merge request capabilities that influence how and when merges will be performed"
+msgstr ""
+
+msgid "ProjectSettings|All discussions must be resolved"
+msgstr ""
+
+msgid "ProjectSettings|Allow users to make copies of your repository to a new project"
+msgstr ""
+
+msgid "ProjectSettings|Allow users to request access"
+msgstr ""
+
+msgid "ProjectSettings|Automatically resolve merge request diff discussions when they become outdated"
+msgstr ""
+
+msgid "ProjectSettings|Badges"
+msgstr ""
+
+msgid "ProjectSettings|Build, test, and deploy your changes"
+msgstr ""
+
+msgid "ProjectSettings|Choose your merge method, merge options, merge checks, and merge suggestions."
+msgstr ""
+
+msgid "ProjectSettings|Choose your merge method, merge options, merge checks, merge suggestions, and set up a default description template for merge requests."
+msgstr ""
+
+msgid "ProjectSettings|Contact an admin to change this setting."
+msgstr ""
+
+msgid "ProjectSettings|Container registry"
+msgstr ""
+
+msgid "ProjectSettings|Customize your project badges."
+msgstr ""
+
+msgid "ProjectSettings|Disable email notifications"
+msgstr ""
+
+msgid "ProjectSettings|Enable 'Delete source branch' option by default"
+msgstr ""
+
+msgid "ProjectSettings|Enable merge trains and pipelines for merged results"
+msgstr ""
+
+msgid "ProjectSettings|Every merge creates a merge commit"
+msgstr ""
+
+msgid "ProjectSettings|Every project can have its own space to store its Docker images"
+msgstr ""
+
+msgid "ProjectSettings|Every project can have its own space to store its packages"
+msgstr ""
+
+msgid "ProjectSettings|Everyone"
+msgstr ""
+
+msgid "ProjectSettings|Existing merge requests and protected branches are not affected"
+msgstr ""
+
+msgid "ProjectSettings|Failed to protect the tag"
+msgstr ""
+
+msgid "ProjectSettings|Failed to update tag!"
+msgstr ""
+
+msgid "ProjectSettings|Fast-forward merge"
+msgstr ""
+
+msgid "ProjectSettings|Fast-forward merges only"
+msgstr ""
+
+msgid "ProjectSettings|Forks"
+msgstr ""
+
+msgid "ProjectSettings|Git Large File Storage (LFS)"
+msgstr ""
+
+msgid "ProjectSettings|Internal"
+msgstr ""
+
+msgid "ProjectSettings|Issues"
+msgstr ""
+
+msgid "ProjectSettings|LFS objects from this repository are still available to forks. %{linkStart}How do I remove them?%{linkEnd}"
+msgstr ""
+
+msgid "ProjectSettings|Learn more about badges."
+msgstr ""
+
+msgid "ProjectSettings|Lightweight issue tracking system for this project"
+msgstr ""
+
+msgid "ProjectSettings|Manages large files such as audio, video, and graphics files"
+msgstr ""
+
+msgid "ProjectSettings|Merge checks"
+msgstr ""
+
+msgid "ProjectSettings|Merge commit"
+msgstr ""
+
+msgid "ProjectSettings|Merge commit with semi-linear history"
+msgstr ""
+
+msgid "ProjectSettings|Merge method"
+msgstr ""
+
+msgid "ProjectSettings|Merge options"
+msgstr ""
+
+msgid "ProjectSettings|Merge requests"
+msgstr ""
+
+msgid "ProjectSettings|Merge suggestions"
+msgstr ""
+
+msgid "ProjectSettings|No merge commits are created"
+msgstr ""
+
+msgid "ProjectSettings|Note: the container registry is always visible when a project is public"
+msgstr ""
+
+msgid "ProjectSettings|Only signed commits can be pushed to this repository."
+msgstr ""
+
+msgid "ProjectSettings|Packages"
+msgstr ""
+
+msgid "ProjectSettings|Pages"
+msgstr ""
+
+msgid "ProjectSettings|Pages for project documentation"
+msgstr ""
+
+msgid "ProjectSettings|Pipelines"
+msgstr ""
+
+msgid "ProjectSettings|Pipelines for merge requests must be enabled in the CI/CD configuration file, or pipelines could be unresolvable or dropped"
+msgstr ""
+
+msgid "ProjectSettings|Pipelines must succeed"
+msgstr ""
+
+msgid "ProjectSettings|Pipelines need to be configured to enable this feature."
+msgstr ""
+
+msgid "ProjectSettings|Private"
+msgstr ""
+
+msgid "ProjectSettings|Project visibility"
+msgstr ""
+
+msgid "ProjectSettings|Public"
+msgstr ""
+
+msgid "ProjectSettings|Repository"
+msgstr ""
+
+msgid "ProjectSettings|Share code pastes with others out of Git repository"
+msgstr ""
+
+msgid "ProjectSettings|Show default award emojis"
+msgstr ""
+
+msgid "ProjectSettings|Show link to create/view merge request when pushing from the command line"
+msgstr ""
+
+msgid "ProjectSettings|Skipped pipelines are considered successful"
+msgstr ""
+
+msgid "ProjectSettings|Snippets"
+msgstr ""
+
+msgid "ProjectSettings|Submit changes to be merged upstream"
+msgstr ""
+
+msgid "ProjectSettings|The commit message used to apply merge request suggestions"
+msgstr ""
+
+msgid "ProjectSettings|The variables GitLab supports:"
+msgstr ""
+
+msgid "ProjectSettings|These checks must pass before merge requests can be merged"
+msgstr ""
+
+msgid "ProjectSettings|This introduces the risk of merging changes that will not pass the pipeline."
+msgstr ""
+
+msgid "ProjectSettings|This setting is applied on the server level and can be overridden by an admin."
+msgstr ""
+
+msgid "ProjectSettings|This setting is applied on the server level but has been overridden for this project."
+msgstr ""
+
+msgid "ProjectSettings|This setting will be applied to all projects unless overridden by an admin."
+msgstr ""
+
+msgid "ProjectSettings|This setting will override user notification preferences for all project members."
+msgstr ""
+
+msgid "ProjectSettings|This will dictate the commit history when you merge a merge request"
+msgstr ""
+
+msgid "ProjectSettings|Users can only push commits to this repository that were committed with one of their own verified emails."
+msgstr ""
+
+msgid "ProjectSettings|View and edit files in this project"
+msgstr ""
+
+msgid "ProjectSettings|View and edit files in this project. Non-project members will only have read access"
+msgstr ""
+
+msgid "ProjectSettings|When conflicts arise the user is given the option to rebase"
+msgstr ""
+
+msgid "ProjectSettings|When enabled, issues, merge requests, and snippets will always show thumbs-up and thumbs-down award emoji buttons."
+msgstr ""
+
+msgid "ProjectSettings|Wiki"
+msgstr ""
+
+msgid "ProjectSettings|With GitLab Pages you can host your static websites on GitLab"
+msgstr ""
+
+msgid "ProjectSettings|With Metrics Dashboard you can visualize this project performance metrics"
+msgstr ""
+
+msgid "ProjectTemplates|.NET Core"
+msgstr ""
+
+msgid "ProjectTemplates|Android"
+msgstr ""
+
+msgid "ProjectTemplates|GitLab Cluster Management"
+msgstr ""
+
+msgid "ProjectTemplates|Go Micro"
+msgstr ""
+
+msgid "ProjectTemplates|HIPAA Audit Protocol"
+msgstr ""
+
+msgid "ProjectTemplates|Netlify/GitBook"
+msgstr ""
+
+msgid "ProjectTemplates|Netlify/Hexo"
+msgstr ""
+
+msgid "ProjectTemplates|Netlify/Hugo"
+msgstr ""
+
+msgid "ProjectTemplates|Netlify/Jekyll"
+msgstr ""
+
+msgid "ProjectTemplates|Netlify/Plain HTML"
+msgstr ""
+
+msgid "ProjectTemplates|NodeJS Express"
+msgstr ""
+
+msgid "ProjectTemplates|Pages/Gatsby"
+msgstr ""
+
+msgid "ProjectTemplates|Pages/GitBook"
+msgstr ""
+
+msgid "ProjectTemplates|Pages/Hexo"
+msgstr ""
+
+msgid "ProjectTemplates|Pages/Hugo"
+msgstr ""
+
+msgid "ProjectTemplates|Pages/Jekyll"
+msgstr ""
+
+msgid "ProjectTemplates|Pages/Plain HTML"
+msgstr ""
+
+msgid "ProjectTemplates|Ruby on Rails"
+msgstr ""
+
+msgid "ProjectTemplates|SalesforceDX"
+msgstr ""
+
+msgid "ProjectTemplates|Serverless Framework/JS"
+msgstr ""
+
+msgid "ProjectTemplates|Spring"
+msgstr ""
+
+msgid "ProjectTemplates|Static Site Editor/Middleman"
+msgstr ""
+
+msgid "ProjectTemplates|iOS (Swift)"
+msgstr ""
+
+msgid "Projects"
+msgstr ""
+
+msgid "Projects (%{count})"
+msgstr ""
+
+msgid "Projects Successfully Retrieved"
+msgstr ""
+
+msgid "Projects are graded based on the highest severity vulnerability present"
+msgstr ""
+
+msgid "Projects shared with %{group_name}"
+msgstr ""
+
+msgid "Projects that belong to a group are prefixed with the group namespace. Existing projects may be moved into a group."
+msgstr ""
+
+msgid "Projects to index"
+msgstr ""
+
+msgid "Projects with critical vulnerabilities"
+msgstr ""
+
+msgid "Projects with high or unknown vulnerabilities"
+msgstr ""
+
+msgid "Projects with low vulnerabilities"
+msgstr ""
+
+msgid "Projects with medium vulnerabilities"
+msgstr ""
+
+msgid "Projects with no vulnerabilities and security scanning enabled"
+msgstr ""
+
+msgid "Projects with write access"
+msgstr ""
+
+msgid "ProjectsDropdown|Frequently visited"
+msgstr ""
+
+msgid "ProjectsDropdown|Loading projects"
+msgstr ""
+
+msgid "ProjectsDropdown|Projects you visit often will appear here"
+msgstr ""
+
+msgid "ProjectsDropdown|Search your projects"
+msgstr ""
+
+msgid "ProjectsDropdown|Something went wrong on our end."
+msgstr ""
+
+msgid "ProjectsDropdown|Sorry, no projects matched your search"
+msgstr ""
+
+msgid "ProjectsDropdown|This feature requires browser localStorage support"
+msgstr ""
+
+msgid "ProjectsNew|Allows you to immediately clone this project’s repository. Skip this if you plan to push up an existing repository."
+msgstr ""
+
+msgid "ProjectsNew|Blank"
+msgstr ""
+
+msgid "ProjectsNew|Blank project"
+msgstr ""
+
+msgid "ProjectsNew|Connect your external repository to GitLab CI/CD."
+msgstr ""
+
+msgid "ProjectsNew|Contact an administrator to enable options for importing your project."
+msgstr ""
+
+msgid "ProjectsNew|Create"
+msgstr ""
+
+msgid "ProjectsNew|Create a blank project to house your files, plan your work, and collaborate on code, among other things."
+msgstr ""
+
+msgid "ProjectsNew|Create blank project"
+msgstr ""
+
+msgid "ProjectsNew|Create from template"
+msgstr ""
+
+msgid "ProjectsNew|Create new project"
+msgstr ""
+
+msgid "ProjectsNew|Creating project & repository."
+msgstr ""
+
+msgid "ProjectsNew|Description format"
+msgstr ""
+
+msgid "ProjectsNew|Import"
+msgstr ""
+
+msgid "ProjectsNew|Import project"
+msgstr ""
+
+msgid "ProjectsNew|Initialize repository with a README"
+msgstr ""
+
+msgid "ProjectsNew|No import options available"
+msgstr ""
+
+msgid "ProjectsNew|Please wait a moment, this page will automatically refresh when ready."
+msgstr ""
+
+msgid "ProjectsNew|Project description %{tag_start}(optional)%{tag_end}"
+msgstr ""
+
+msgid "ProjectsNew|Run CI/CD for external repository"
+msgstr ""
+
+msgid "ProjectsNew|Template"
+msgstr ""
+
+msgid "ProjectsNew|Visibility Level"
+msgstr ""
+
+msgid "ProjectsNew|Want to house several dependent projects under the same namespace? %{link_start}Create a group.%{link_end}"
+msgstr ""
+
+msgid "Prometheus"
+msgstr ""
+
+msgid "PrometheusAlerts|%{count} alerts applied"
+msgstr ""
+
+msgid "PrometheusAlerts|%{firingCount} firing"
+msgstr ""
+
+msgid "PrometheusAlerts|Add alert"
+msgstr ""
+
+msgid "PrometheusAlerts|Edit alert"
+msgstr ""
+
+msgid "PrometheusAlerts|Error creating alert"
+msgstr ""
+
+msgid "PrometheusAlerts|Error deleting alert"
+msgstr ""
+
+msgid "PrometheusAlerts|Error fetching alert"
+msgstr ""
+
+msgid "PrometheusAlerts|Error saving alert"
+msgstr ""
+
+msgid "PrometheusAlerts|Firing: %{alerts}"
+msgstr ""
+
+msgid "PrometheusAlerts|Firing: %{alert}"
+msgstr ""
+
+msgid "PrometheusAlerts|Operator"
+msgstr ""
+
+msgid "PrometheusAlerts|Select query"
+msgstr ""
+
+msgid "PrometheusAlerts|Threshold"
+msgstr ""
+
+msgid "PrometheusService|%{exporters} with %{metrics} were found"
+msgstr ""
+
+msgid "PrometheusService|<p class=\"text-tertiary\">No <a href=\"%{docsUrl}\">common metrics</a> were found</p>"
+msgstr ""
+
+msgid "PrometheusService|Active"
+msgstr ""
+
+msgid "PrometheusService|Auto configuration"
+msgstr ""
+
+msgid "PrometheusService|Automatically deploy and configure Prometheus on your clusters to monitor your project’s environments"
+msgstr ""
+
+msgid "PrometheusService|Client ID of the IAP secured resource (looks like IAP_CLIENT_ID.apps.googleusercontent.com)"
+msgstr ""
+
+msgid "PrometheusService|Common metrics"
+msgstr ""
+
+msgid "PrometheusService|Common metrics are automatically monitored based on a library of metrics from popular exporters."
+msgstr ""
+
+msgid "PrometheusService|Contents of the credentials.json file of your service account, like: { \"type\": \"service_account\", \"project_id\": ... }"
+msgstr ""
+
+msgid "PrometheusService|Custom metrics"
+msgstr ""
+
+msgid "PrometheusService|Custom metrics require Prometheus installed on a cluster with environment scope \"*\" OR a manually configured Prometheus to be available."
+msgstr ""
+
+msgid "PrometheusService|Enable Prometheus to define custom metrics, using either option above"
+msgstr ""
+
+msgid "PrometheusService|Finding and configuring metrics..."
+msgstr ""
+
+msgid "PrometheusService|Finding custom metrics..."
+msgstr ""
+
+msgid "PrometheusService|Install Prometheus on clusters"
+msgstr ""
+
+msgid "PrometheusService|Manage clusters"
+msgstr ""
+
+msgid "PrometheusService|Manual configuration"
+msgstr ""
+
+msgid "PrometheusService|Metrics"
+msgstr ""
+
+msgid "PrometheusService|Missing environment variable"
+msgstr ""
+
+msgid "PrometheusService|More information"
+msgstr ""
+
+msgid "PrometheusService|New metric"
+msgstr ""
+
+msgid "PrometheusService|No custom metrics have been created. Create one using the button above"
+msgstr ""
+
+msgid "PrometheusService|Prometheus API Base URL, like http://prometheus.example.com/"
+msgstr ""
+
+msgid "PrometheusService|Prometheus is being automatically managed on your clusters"
+msgstr ""
+
+msgid "PrometheusService|Select the Active checkbox to override the Auto Configuration with custom settings. If unchecked, Auto Configuration settings are used."
+msgstr ""
+
+msgid "PrometheusService|These metrics will only be monitored after your first deployment to an environment"
+msgstr ""
+
+msgid "PrometheusService|Time-series monitoring service"
+msgstr ""
+
+msgid "PrometheusService|To enable the installation of Prometheus on your clusters, deactivate the manual configuration below"
+msgstr ""
+
+msgid "PrometheusService|Waiting for your first deployment to an environment to find common metrics"
+msgstr ""
+
+msgid "PrometheusService|You can now manage your Prometheus settings on the %{operations_link_start}Operations%{operations_link_end} page. Fields on this page has been deprecated."
+msgstr ""
+
+msgid "Promote"
+msgstr ""
+
+msgid "Promote confidential issue to a non-confidential epic"
+msgstr ""
+
+msgid "Promote issue to an epic"
+msgstr ""
+
+msgid "Promote these project milestones into a group milestone."
+msgstr ""
+
+msgid "Promote to group label"
+msgstr ""
+
+msgid "PromoteMilestone|Only project milestones can be promoted."
+msgstr ""
+
+msgid "PromoteMilestone|Project does not belong to a group."
+msgstr ""
+
+msgid "PromoteMilestone|Promotion failed - %{message}"
+msgstr ""
+
+msgid "Promoted confidential issue to a non-confidential epic. Information in this issue is no longer confidential as epics are public to group members."
+msgstr ""
+
+msgid "Promoted issue to an epic."
+msgstr ""
+
+msgid "Promotions|Burndown Charts are visual representations of the progress of completing a milestone. At a glance, you see the current state for the completion a given milestone. Without them, you would have to organize the data from the milestone and plot it yourself to have the same sense of progress."
+msgstr ""
+
+msgid "Promotions|Buy EE"
+msgstr ""
+
+msgid "Promotions|Buy GitLab Enterprise Edition"
+msgstr ""
+
+msgid "Promotions|Contact an owner of group %{namespace_name} to upgrade the plan."
+msgstr ""
+
+msgid "Promotions|Contact owner %{link_start}%{owner_name}%{link_end} to upgrade the plan."
+msgstr ""
+
+msgid "Promotions|Contact your Administrator to upgrade your license."
+msgstr ""
+
+msgid "Promotions|Dismiss burndown charts promotion"
+msgstr ""
+
+msgid "Promotions|Don't show me this again"
+msgstr ""
+
+msgid "Promotions|Epics let you manage your portfolio of projects more efficiently and with less effort by tracking groups of issues that share a theme, across projects and milestones."
+msgstr ""
+
+msgid "Promotions|Improve issues management with Issue weight and GitLab Enterprise Edition."
+msgstr ""
+
+msgid "Promotions|Improve milestones with Burndown Charts."
+msgstr ""
+
+msgid "Promotions|Learn more"
+msgstr ""
+
+msgid "Promotions|Not now, thanks!"
+msgstr ""
+
+msgid "Promotions|See the other features in the %{subscription_link_start}bronze plan%{subscription_link_end}"
+msgstr ""
+
+msgid "Promotions|Start GitLab Ultimate trial"
+msgstr ""
+
+msgid "Promotions|This feature is locked."
+msgstr ""
+
+msgid "Promotions|Track activity with Contribution Analytics."
+msgstr ""
+
+msgid "Promotions|Try it for free"
+msgstr ""
+
+msgid "Promotions|Upgrade plan"
+msgstr ""
+
+msgid "Promotions|Upgrade your plan"
+msgstr ""
+
+msgid "Promotions|Upgrade your plan to activate Contribution Analytics."
+msgstr ""
+
+msgid "Promotions|Upgrade your plan to improve milestones with Burndown Charts."
+msgstr ""
+
+msgid "Promotions|Weight"
+msgstr ""
+
+msgid "Promotions|Weighting your issue"
+msgstr ""
+
+msgid "Promotions|When you have a lot of issues, it can be hard to get an overview. By adding a weight to your issues, you can get a better idea of the effort, cost, required time, or value of each, and so better manage them."
+msgstr ""
+
+msgid "Promotions|With Contribution Analytics you can have an overview for the activity of issues, merge requests, and push events of your organization and its members."
+msgstr ""
+
+msgid "Prompt users to upload SSH keys"
+msgstr ""
+
+msgid "Protect variable"
+msgstr ""
+
+msgid "Protected"
+msgstr ""
+
+msgid "Protected Branch"
+msgstr ""
+
+msgid "Protected Branches"
+msgstr ""
+
+msgid "Protected Environment"
+msgstr ""
+
+msgid "Protected Environments"
+msgstr ""
+
+msgid "Protected Paths"
+msgstr ""
+
+msgid "Protected Tag"
+msgstr ""
+
+msgid "Protected Tags"
+msgstr ""
+
+msgid "Protected branches"
+msgstr ""
+
+msgid "ProtectedBranch|%{wildcards_link_start}Wildcards%{wildcards_link_end} such as %{code_tag_start}*-stable%{code_tag_end} or %{code_tag_start}production/*%{code_tag_end} are supported"
+msgstr ""
+
+msgid "ProtectedBranch|Allowed to merge"
+msgstr ""
+
+msgid "ProtectedBranch|Allowed to merge:"
+msgstr ""
+
+msgid "ProtectedBranch|Allowed to push"
+msgstr ""
+
+msgid "ProtectedBranch|Allowed to push:"
+msgstr ""
+
+msgid "ProtectedBranch|Branch"
+msgstr ""
+
+msgid "ProtectedBranch|Code owner approval"
+msgstr ""
+
+msgid "ProtectedBranch|Protect"
+msgstr ""
+
+msgid "ProtectedBranch|Protect a branch"
+msgstr ""
+
+msgid "ProtectedBranch|Protected branch (%{protected_branches_count})"
+msgstr ""
+
+msgid "ProtectedBranch|Pushes that change filenames matched by the CODEOWNERS file will be rejected"
+msgstr ""
+
+msgid "ProtectedBranch|Require approval from code owners:"
+msgstr ""
+
+msgid "ProtectedBranch|There are currently no protected branches, protect a branch with the form above."
+msgstr ""
+
+msgid "ProtectedBranch|Toggle code owner approval"
+msgstr ""
+
+msgid "ProtectedEnvironment|%{environment_name} will be writable for developers. Are you sure?"
+msgstr ""
+
+msgid "ProtectedEnvironment|Allowed to deploy"
+msgstr ""
+
+msgid "ProtectedEnvironment|Environment"
+msgstr ""
+
+msgid "ProtectedEnvironment|Protect"
+msgstr ""
+
+msgid "ProtectedEnvironment|Protect an environment"
+msgstr ""
+
+msgid "ProtectedEnvironment|Protected Environment (%{protected_environments_count})"
+msgstr ""
+
+msgid "ProtectedEnvironment|Protecting an environment restricts the users who can execute deployments."
+msgstr ""
+
+msgid "ProtectedEnvironment|Select an environment"
+msgstr ""
+
+msgid "ProtectedEnvironment|Select users"
+msgstr ""
+
+msgid "ProtectedEnvironment|Select users to deploy and manage Feature Flag settings"
+msgstr ""
+
+msgid "ProtectedEnvironment|There are currently no protected environments, protect an environment with the form above."
+msgstr ""
+
+msgid "ProtectedEnvironment|Unprotect"
+msgstr ""
+
+msgid "ProtectedEnvironment|Your environment can't be unprotected"
+msgstr ""
+
+msgid "ProtectedEnvironment|Your environment has been protected."
+msgstr ""
+
+msgid "ProtectedEnvironment|Your environment has been unprotected"
+msgstr ""
+
+msgid "Protip:"
+msgstr ""
+
+msgid "Protocol"
+msgstr ""
+
+msgid "Provider"
+msgstr ""
+
+msgid "Proxy support for this API is not available currently"
+msgstr ""
+
+msgid "Pseudonymizer data collection"
+msgstr ""
+
+msgid "Public"
+msgstr ""
+
+msgid "Public - The group and any public projects can be viewed without any authentication."
+msgstr ""
+
+msgid "Public - The project can be accessed without any authentication."
+msgstr ""
+
+msgid "Public deploy keys (%{deploy_keys_count})"
+msgstr ""
+
+msgid "Public pipelines"
+msgstr ""
+
+msgid "Public projects Minutes cost factor"
+msgstr ""
+
+msgid "Publish to status page"
+msgstr ""
+
+msgid "Published on status page"
+msgstr ""
+
+msgid "Publishes this issue to the associated status page."
+msgstr ""
+
+msgid "Pull"
+msgstr ""
+
+msgid "Pull requests from fork are not supported"
+msgstr ""
+
+msgid "Puma is running with a thread count above 1 and the Rugged service is enabled. This may decrease performance in some environments. See our %{link_start}documentation%{link_end} for details of this issue."
+msgstr ""
+
+msgid "Purchase more minutes"
+msgstr ""
+
+msgid "Push"
+msgstr ""
+
+msgid "Push Rule updated successfully."
+msgstr ""
+
+msgid "Push Rules"
+msgstr ""
+
+msgid "Push Rules updated successfully."
+msgstr ""
+
+msgid "Push an existing Git repository"
+msgstr ""
+
+msgid "Push an existing folder"
+msgstr ""
+
+msgid "Push events"
+msgstr ""
+
+msgid "Push project from command line"
+msgstr ""
+
+msgid "Push to create a project"
+msgstr ""
+
+msgid "PushRule|Committer restriction"
+msgstr ""
+
+msgid "Pushed"
+msgstr ""
+
+msgid "Pushes"
+msgstr ""
+
+msgid "PushoverService|%{user_name} deleted branch \"%{ref}\"."
+msgstr ""
+
+msgid "PushoverService|%{user_name} push to branch \"%{ref}\"."
+msgstr ""
+
+msgid "PushoverService|%{user_name} pushed new branch \"%{ref}\"."
+msgstr ""
+
+msgid "PushoverService|High Priority"
+msgstr ""
+
+msgid "PushoverService|Leave blank for all active devices"
+msgstr ""
+
+msgid "PushoverService|Low Priority"
+msgstr ""
+
+msgid "PushoverService|Lowest Priority"
+msgstr ""
+
+msgid "PushoverService|Normal Priority"
+msgstr ""
+
+msgid "PushoverService|Pushover makes it easy to get real-time notifications on your Android device, iPhone, iPad, and Desktop."
+msgstr ""
+
+msgid "PushoverService|See project %{project_full_name}"
+msgstr ""
+
+msgid "PushoverService|Total commits count: %{total_commits_count}"
+msgstr ""
+
+msgid "PushoverService|Your application key"
+msgstr ""
+
+msgid "PushoverService|Your user key"
+msgstr ""
+
+msgid "Quarters"
+msgstr ""
+
+msgid "Query"
+msgstr ""
+
+msgid "Query cannot be processed"
+msgstr ""
+
+msgid "Query is valid"
+msgstr ""
+
+msgid "Queued"
+msgstr ""
+
+msgid "Quick actions"
+msgstr ""
+
+msgid "Quick actions can be used in the issues description and comment boxes."
+msgstr ""
+
+msgid "Quick range"
+msgstr ""
+
+msgid "README"
+msgstr ""
+
+msgid "Raw blob request rate limit per minute"
+msgstr ""
+
+msgid "Re-authentication period expired or never requested. Please try again"
+msgstr ""
+
+msgid "Re-authentication required"
+msgstr ""
+
+msgid "Re-verification interval"
+msgstr ""
+
+msgid "Read more"
+msgstr ""
+
+msgid "Read more about environments"
+msgstr ""
+
+msgid "Read more about project permissions <strong>%{link_to_help}</strong>"
+msgstr ""
+
+msgid "Read more about related issues"
+msgstr ""
+
+msgid "Real-time features"
+msgstr ""
+
+msgid "Rebase"
+msgstr ""
+
+msgid "Rebase in progress"
+msgstr ""
+
+msgid "Receive alerts from manually configured Prometheus servers."
+msgstr ""
+
+msgid "Receive notifications about your own activity"
+msgstr ""
+
+msgid "Recent"
+msgstr ""
+
+msgid "Recent Activity"
+msgstr ""
+
+msgid "Recent Project Activity"
+msgstr ""
+
+msgid "Recent Searches Service is unavailable"
+msgstr ""
+
+msgid "Recent searches"
+msgstr ""
+
+msgid "Recipe"
+msgstr ""
+
+msgid "Reconfigure"
+msgstr ""
+
+msgid "Recover hidden stage"
+msgstr ""
+
+msgid "Recovery Codes"
+msgstr ""
+
+msgid "Redirect to SAML provider to test configuration"
+msgstr ""
+
+msgid "Reduce project visibility"
+msgstr ""
+
+msgid "Reduce this project’s visibility?"
+msgstr ""
+
+msgid "Reference:"
+msgstr ""
+
+msgid "Refresh"
+msgstr ""
+
+msgid "Refreshing in a second to show the updated status..."
+msgid_plural "Refreshing in %d seconds to show the updated status..."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Regenerate export"
+msgstr ""
+
+msgid "Regenerate instance ID"
+msgstr ""
+
+msgid "Regenerate key"
+msgstr ""
+
+msgid "Regenerate recovery codes"
+msgstr ""
+
+msgid "Regenerating the instance ID can break integration depending on the client you are using."
+msgstr ""
+
+msgid "Regex pattern"
+msgstr ""
+
+msgid "Region that Elasticsearch is configured"
+msgstr ""
+
+msgid "Register"
+msgstr ""
+
+msgid "Register / Sign In"
+msgstr ""
+
+msgid "Register Two-Factor Authenticator"
+msgstr ""
+
+msgid "Register U2F device"
+msgstr ""
+
+msgid "Register Universal Two-Factor (U2F) Device"
+msgstr ""
+
+msgid "Register for GitLab"
+msgstr ""
+
+msgid "Register now"
+msgstr ""
+
+msgid "Register with two-factor app"
+msgstr ""
+
+msgid "Registration"
+msgstr ""
+
+msgid "Registration|Checkout"
+msgstr ""
+
+msgid "Registration|Your GitLab group"
+msgstr ""
+
+msgid "Registration|Your first project"
+msgstr ""
+
+msgid "Registration|Your profile"
+msgstr ""
+
+msgid "Regulate approvals by authors/committers, based on compliance frameworks. Can be changed only at the instance level."
+msgstr ""
+
+msgid "Rejected (closed)"
+msgstr ""
+
+msgid "Related Deployed Jobs"
+msgstr ""
+
+msgid "Related Issues"
+msgstr ""
+
+msgid "Related Jobs"
+msgstr ""
+
+msgid "Related Merge Requests"
+msgstr ""
+
+msgid "Related Merged Requests"
+msgstr ""
+
+msgid "Related merge requests"
+msgstr ""
+
+msgid "Relates to"
+msgstr ""
+
+msgid "Release"
+msgid_plural "Releases"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Release assets"
+msgstr ""
+
+msgid "Release assets documentation"
+msgstr ""
+
+msgid "Release does not have the same project as the milestone"
+msgstr ""
+
+msgid "Release notes"
+msgstr ""
+
+msgid "Release notes:"
+msgstr ""
+
+msgid "Release title"
+msgstr ""
+
+msgid "ReleaseAssetLinkType|Image"
+msgstr ""
+
+msgid "ReleaseAssetLinkType|Images"
+msgstr ""
+
+msgid "ReleaseAssetLinkType|Other"
+msgstr ""
+
+msgid "ReleaseAssetLinkType|Package"
+msgstr ""
+
+msgid "ReleaseAssetLinkType|Packages"
+msgstr ""
+
+msgid "ReleaseAssetLinkType|Runbook"
+msgstr ""
+
+msgid "ReleaseAssetLinkType|Runbooks"
+msgstr ""
+
+msgid "Releases"
+msgstr ""
+
+msgid "Releases are based on Git tags and mark specific points in a project's development history. They can contain information about the type of changes and can also deliver binaries, like compiled versions of your software."
+msgstr ""
+
+msgid "Releases are based on Git tags. We recommend tags that use semantic versioning, for example %{codeStart}v1.0%{codeEnd}, %{codeStart}v2.0-pre%{codeEnd}."
+msgstr ""
+
+msgid "Releases documentation"
+msgstr ""
+
+msgid "Releases|New Release"
+msgstr ""
+
+msgid "Release|Something went wrong while getting the release details"
+msgstr ""
+
+msgid "Release|Something went wrong while saving the release details"
+msgstr ""
+
+msgid "Remediated: needs review"
+msgstr ""
+
+msgid "Remediations"
+msgstr ""
+
+msgid "Remember me"
+msgstr ""
+
+msgid "Remind later"
+msgstr ""
+
+msgid "Remote object has no absolute path."
+msgstr ""
+
+msgid "Remove"
+msgstr ""
+
+msgid "Remove %{displayReference}"
+msgstr ""
+
+msgid "Remove Runner"
+msgstr ""
+
+msgid "Remove Zoom meeting"
+msgstr ""
+
+msgid "Remove all approvals in a merge request when new commits are pushed to its source branch"
+msgstr ""
+
+msgid "Remove all or specific assignee(s)"
+msgstr ""
+
+msgid "Remove all or specific label(s)"
+msgstr ""
+
+msgid "Remove approvers"
+msgstr ""
+
+msgid "Remove approvers?"
+msgstr ""
+
+msgid "Remove asset link"
+msgstr ""
+
+msgid "Remove assignee"
+msgstr ""
+
+msgid "Remove avatar"
+msgstr ""
+
+msgid "Remove card"
+msgstr ""
+
+msgid "Remove child epic from an epic"
+msgstr ""
+
+msgid "Remove description history"
+msgstr ""
+
+msgid "Remove due date"
+msgstr ""
+
+msgid "Remove fork relationship"
+msgstr ""
+
+msgid "Remove from batch"
+msgstr ""
+
+msgid "Remove from board"
+msgstr ""
+
+msgid "Remove from epic"
+msgstr ""
+
+msgid "Remove group"
+msgstr ""
+
+msgid "Remove iteration"
+msgstr ""
+
+msgid "Remove license"
+msgstr ""
+
+msgid "Remove limit"
+msgstr ""
+
+msgid "Remove milestone"
+msgstr ""
+
+msgid "Remove node"
+msgstr ""
+
+msgid "Remove parent epic from an epic"
+msgstr ""
+
+msgid "Remove primary node"
+msgstr ""
+
+msgid "Remove priority"
+msgstr ""
+
+msgid "Remove project"
+msgstr ""
+
+msgid "Remove secondary node"
+msgstr ""
+
+msgid "Remove spent time"
+msgstr ""
+
+msgid "Remove stage"
+msgstr ""
+
+msgid "Remove time estimate"
+msgstr ""
+
+msgid "Removed"
+msgstr ""
+
+msgid "Removed %{assignee_text} %{assignee_references}."
+msgstr ""
+
+msgid "Removed %{epic_ref} from child epics."
+msgstr ""
+
+msgid "Removed %{iteration_reference} iteration."
+msgstr ""
+
+msgid "Removed %{label_references} %{label_text}."
+msgstr ""
+
+msgid "Removed %{milestone_reference} milestone."
+msgstr ""
+
+msgid "Removed %{type} with id %{id}"
+msgstr ""
+
+msgid "Removed all labels."
+msgstr ""
+
+msgid "Removed an issue from an epic."
+msgstr ""
+
+msgid "Removed group can not be restored!"
+msgstr ""
+
+msgid "Removed parent epic %{epic_ref}."
+msgstr ""
+
+msgid "Removed projects cannot be restored!"
+msgstr ""
+
+msgid "Removed spent time."
+msgstr ""
+
+msgid "Removed the due date."
+msgstr ""
+
+msgid "Removed time estimate."
+msgstr ""
+
+msgid "Removes %{assignee_text} %{assignee_references}."
+msgstr ""
+
+msgid "Removes %{epic_ref} from child epics."
+msgstr ""
+
+msgid "Removes %{iteration_reference} iteration."
+msgstr ""
+
+msgid "Removes %{label_references} %{label_text}."
+msgstr ""
+
+msgid "Removes %{milestone_reference} milestone."
+msgstr ""
+
+msgid "Removes all labels."
+msgstr ""
+
+msgid "Removes an issue from an epic."
+msgstr ""
+
+msgid "Removes parent epic %{epic_ref}."
+msgstr ""
+
+msgid "Removes spent time."
+msgstr ""
+
+msgid "Removes the due date."
+msgstr ""
+
+msgid "Removes time estimate."
+msgstr ""
+
+msgid "Removing a project places it into a read-only state until %{date}, at which point the project will be permanantly removed. Are you ABSOLUTELY sure?"
+msgstr ""
+
+msgid "Removing a project places it into a read-only state until %{date}, at which point the project will be permanently removed."
+msgstr ""
+
+msgid "Removing license…"
+msgstr ""
+
+msgid "Removing the project will delete its repository and all related resources including issues, merge requests etc."
+msgstr ""
+
+msgid "Removing this group also removes all child projects, including archived projects, and their resources."
+msgstr ""
+
+msgid "Rename file"
+msgstr ""
+
+msgid "Rename folder"
+msgstr ""
+
+msgid "Rename/Move"
+msgstr ""
+
+msgid "Reopen"
+msgstr ""
+
+msgid "Reopen %{display_issuable_type}"
+msgstr ""
+
+msgid "Reopen epic"
+msgstr ""
+
+msgid "Reopen milestone"
+msgstr ""
+
+msgid "Reopen this %{quick_action_target}"
+msgstr ""
+
+msgid "Reopened this %{quick_action_target}."
+msgstr ""
+
+msgid "Reopens this %{quick_action_target}."
+msgstr ""
+
+msgid "Repair authentication"
+msgstr ""
+
+msgid "Replace"
+msgstr ""
+
+msgid "Replace all label(s)"
+msgstr ""
+
+msgid "Replaced all labels with %{label_references} %{label_text}."
+msgstr ""
+
+msgid "Replaces the clone URL root."
+msgstr ""
+
+msgid "Replication"
+msgstr ""
+
+msgid "Replication enabled"
+msgstr ""
+
+msgid "Replication paused"
+msgstr ""
+
+msgid "Reply by email"
+msgstr ""
+
+msgid "Reply to comment"
+msgstr ""
+
+msgid "Reply to this email directly or %{view_it_on_gitlab}."
+msgstr ""
+
+msgid "Reply..."
+msgstr ""
+
+msgid "Repo by URL"
+msgstr ""
+
+msgid "Report %{display_issuable_type} that are abusive, inappropriate or spam."
+msgstr ""
+
+msgid "Report abuse"
+msgstr ""
+
+msgid "Report abuse to admin"
+msgstr ""
+
+msgid "Reported %{timeAgo} by %{reportedBy}"
+msgstr ""
+
+msgid "Reporter"
+msgstr ""
+
+msgid "Reporting"
+msgstr ""
+
+msgid "Reports"
+msgstr ""
+
+msgid "Reports|%{combinedString} and %{resolvedString}"
+msgstr ""
+
+msgid "Reports|Accessibility scanning detected %d issue for the source branch only"
+msgid_plural "Reports|Accessibility scanning detected %d issues for the source branch only"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Reports|Accessibility scanning detected no issues for the source branch only"
+msgstr ""
+
+msgid "Reports|Accessibility scanning failed loading results"
+msgstr ""
+
+msgid "Reports|Accessibility scanning results are being parsed"
+msgstr ""
+
+msgid "Reports|Actions"
+msgstr ""
+
+msgid "Reports|An error occured while loading report"
+msgstr ""
+
+msgid "Reports|An error occurred while loading %{name} results"
+msgstr ""
+
+msgid "Reports|Class"
+msgstr ""
+
+msgid "Reports|Classname"
+msgstr ""
+
+msgid "Reports|Execution time"
+msgstr ""
+
+msgid "Reports|Failure"
+msgstr ""
+
+msgid "Reports|Identifier"
+msgstr ""
+
+msgid "Reports|Metrics reports are loading"
+msgstr ""
+
+msgid "Reports|Metrics reports changed on %{numberOfChanges} %{pointsString}"
+msgstr ""
+
+msgid "Reports|Metrics reports did not change"
+msgstr ""
+
+msgid "Reports|Metrics reports failed loading results"
+msgstr ""
+
+msgid "Reports|Scanner"
+msgstr ""
+
+msgid "Reports|Severity"
+msgstr ""
+
+msgid "Reports|System output"
+msgstr ""
+
+msgid "Reports|Test summary"
+msgstr ""
+
+msgid "Reports|Test summary failed loading results"
+msgstr ""
+
+msgid "Reports|Test summary results are being parsed"
+msgstr ""
+
+msgid "Reports|Vulnerability"
+msgstr ""
+
+msgid "Reports|no changed test results"
+msgstr ""
+
+msgid "Repository"
+msgstr ""
+
+msgid "Repository Analytics"
+msgstr ""
+
+msgid "Repository Graph"
+msgstr ""
+
+msgid "Repository Settings"
+msgstr ""
+
+msgid "Repository URL"
+msgstr ""
+
+msgid "Repository check"
+msgstr ""
+
+msgid "Repository check was triggered."
+msgstr ""
+
+msgid "Repository cleanup"
+msgstr ""
+
+msgid "Repository cleanup has started. You will receive an email once the cleanup operation is complete."
+msgstr ""
+
+msgid "Repository files count over the limit"
+msgstr ""
+
+msgid "Repository has an invalid default branch name."
+msgstr ""
+
+msgid "Repository has more than one branch."
+msgstr ""
+
+msgid "Repository has no locks."
+msgstr ""
+
+msgid "Repository has tags."
+msgstr ""
+
+msgid "Repository maintenance"
+msgstr ""
+
+msgid "Repository mirroring"
+msgstr ""
+
+msgid "Repository must contain at least 1 file."
+msgstr ""
+
+msgid "Repository size is above the limit."
+msgstr ""
+
+msgid "Repository static objects"
+msgstr ""
+
+msgid "Repository storage"
+msgstr ""
+
+msgid "Repository sync capacity"
+msgstr ""
+
+msgid "Repository: %{counter_repositories} / Wikis: %{counter_wikis} / Build Artifacts: %{counter_build_artifacts} / LFS: %{counter_lfs_objects} / Snippets: %{counter_snippets}"
+msgstr ""
+
+msgid "RepositorySettingsAccessLevel|Select"
+msgstr ""
+
+msgid "Request Access"
+msgstr ""
+
+msgid "Request Headers"
+msgstr ""
+
+msgid "Request details"
+msgstr ""
+
+msgid "Request parameter %{param} is missing."
+msgstr ""
+
+msgid "Request to link SAML account must be authorized"
+msgstr ""
+
+msgid "Requested %{time_ago}"
+msgstr ""
+
+msgid "Requested design version does not exist."
+msgstr ""
+
+msgid "Requested states are invalid"
+msgstr ""
+
+msgid "Requests"
+msgstr ""
+
+msgid "Requests Profiles"
+msgstr ""
+
+msgid "Requests to these domain(s)/address(es) on the local network will be allowed when local requests from hooks and services are not allowed. IP ranges such as 1:0:0:0:0:0:0:0/124 or 127.0.0.0/28 are supported. Domain wildcards are not supported currently. Use comma, semicolon, or newline to separate multiple entries. The allowlist can hold a maximum of 1000 entries. Domains should use IDNA encoding. Ex: example.com, 192.168.1.1, 127.0.0.0/28, xn--itlab-j1a.com."
+msgstr ""
+
+msgid "Require all users in this group to setup Two-factor authentication"
+msgstr ""
+
+msgid "Require all users to accept Terms of Service and Privacy Policy when they access GitLab."
+msgstr ""
+
+msgid "Require user password to approve"
+msgstr ""
+
+msgid "Require users to prove ownership of custom domains"
+msgstr ""
+
+msgid "Required approvals (%{approvals_given} given)"
+msgstr ""
+
+msgid "Required approvals (%{approvals_given} given, you've approved)"
+msgstr ""
+
+msgid "Requirement"
+msgstr ""
+
+msgid "Requirement %{reference} has been added"
+msgstr ""
+
+msgid "Requirement %{reference} has been archived"
+msgstr ""
+
+msgid "Requirement %{reference} has been reopened"
+msgstr ""
+
+msgid "Requirement %{reference} has been updated"
+msgstr ""
+
+msgid "Requirement title cannot have more than %{limit} characters."
+msgstr ""
+
+msgid "Requirements"
+msgstr ""
+
+msgid "Requirements can be based on users, stakeholders, system, software, or anything else you find important to capture."
+msgstr ""
+
+msgid "Requires approval from %{names}."
+msgid_plural "Requires %{count} more approvals from %{names}."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Requires approval."
+msgid_plural "Requires %d more approvals."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Requires values to meet regular expression requirements."
+msgstr ""
+
+msgid "Resend confirmation email"
+msgstr ""
+
+msgid "Resend invite"
+msgstr ""
+
+msgid "Resend it"
+msgstr ""
+
+msgid "Reset authorization key"
+msgstr ""
+
+msgid "Reset authorization key?"
+msgstr ""
+
+msgid "Reset health check access token"
+msgstr ""
+
+msgid "Reset key"
+msgstr ""
+
+msgid "Reset runners registration token"
+msgstr ""
+
+msgid "Reset template"
+msgstr ""
+
+msgid "Reset to project defaults"
+msgstr ""
+
+msgid "Resetting the authorization key for this project will require updating the authorization key in every alert source it is enabled in."
+msgstr ""
+
+msgid "Resetting the authorization key will invalidate the previous key. Existing alert configurations will need to be updated with the new key."
+msgstr ""
+
+msgid "Resolve"
+msgstr ""
+
+msgid "Resolve all threads in new issue"
+msgstr ""
+
+msgid "Resolve conflicts on source branch"
+msgstr ""
+
+msgid "Resolve thread"
+msgstr ""
+
+msgid "Resolved"
+msgstr ""
+
+msgid "Resolved 1 discussion."
+msgstr ""
+
+msgid "Resolved all discussions."
+msgstr ""
+
+msgid "Resolved by"
+msgstr ""
+
+msgid "Resolved by %{name}"
+msgstr ""
+
+msgid "Resolved by %{resolvedByName}"
+msgstr ""
+
+msgid "Resolves IP addresses once and uses them to submit requests"
+msgstr ""
+
+msgid "Response"
+msgstr ""
+
+msgid "Response Headers"
+msgstr ""
+
+msgid "Response Status"
+msgstr ""
+
+msgid "Response didn't include `service_desk_address`"
+msgstr ""
+
+msgid "Response metrics (AWS ELB)"
+msgstr ""
+
+msgid "Response metrics (Custom)"
+msgstr ""
+
+msgid "Response metrics (HA Proxy)"
+msgstr ""
+
+msgid "Response metrics (NGINX Ingress VTS)"
+msgstr ""
+
+msgid "Response metrics (NGINX Ingress)"
+msgstr ""
+
+msgid "Response metrics (NGINX)"
+msgstr ""
+
+msgid "Restart Terminal"
+msgstr ""
+
+msgid "Restore group"
+msgstr ""
+
+msgid "Restore project"
+msgstr ""
+
+msgid "Restoring the group will prevent the group, its subgroups and projects from being removed on this date."
+msgstr ""
+
+msgid "Restoring the project will prevent the project from being removed on this date and restore people's ability to make changes to it."
+msgstr ""
+
+msgid "Restrict access by IP address"
+msgstr ""
+
+msgid "Restrict membership by email"
+msgstr ""
+
+msgid "Restricts sign-ups for email addresses that match the given regex. See the %{supported_syntax_link_start}supported syntax%{supported_syntax_link_end} for more information."
+msgstr ""
+
+msgid "Resume"
+msgstr ""
+
+msgid "Resync"
+msgstr ""
+
+msgid "Resync all"
+msgstr ""
+
+msgid "Resync all %{replicableType}"
+msgstr ""
+
+msgid "Retry"
+msgstr ""
+
+msgid "Retry this job"
+msgstr ""
+
+msgid "Retry this job in order to create the necessary resources."
+msgstr ""
+
+msgid "Retry update"
+msgstr ""
+
+msgid "Retry verification"
+msgstr ""
+
+msgid "Reveal value"
+msgid_plural "Reveal values"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Reveal values"
+msgstr ""
+
+msgid "Revert this commit"
+msgstr ""
+
+msgid "Revert this merge request"
+msgstr ""
+
+msgid "Review"
+msgstr ""
+
+msgid "Review App|View app"
+msgstr ""
+
+msgid "Review App|View latest app"
+msgstr ""
+
+msgid "Review the process for configuring service providers in your identity provider — in this case, GitLab is the \"service provider\" or \"relying party\"."
+msgstr ""
+
+msgid "Review time"
+msgstr ""
+
+msgid "Review time is defined as the time it takes from first comment until merged."
+msgstr ""
+
+msgid "ReviewApp|Enable Review App"
+msgstr ""
+
+msgid "Reviewing"
+msgstr ""
+
+msgid "Reviewing (merge request !%{mergeRequestId})"
+msgstr ""
+
+msgid "Revoke"
+msgstr ""
+
+msgid "Revoked impersonation token %{token_name}!"
+msgstr ""
+
+msgid "Revoked personal access token %{personal_access_token_name}!"
+msgstr ""
+
+msgid "Revoked project access token %{project_access_token_name}!"
+msgstr ""
+
+msgid "RightSidebar|adding a"
+msgstr ""
+
+msgid "RightSidebar|deleting the"
+msgstr ""
+
+msgid "Roadmap"
+msgstr ""
+
+msgid "Role"
+msgstr ""
+
+msgid "Rollback"
+msgstr ""
+
+msgid "Rook"
+msgstr ""
+
+msgid "Rules that define what git pushes are accepted for a project in this group. All newly created projects in this group will use these settings."
+msgstr ""
+
+msgid "Rules that define what git pushes are accepted for a project. All newly created projects will use these settings."
+msgstr ""
+
+msgid "Run CI/CD pipelines for external repositories"
+msgstr ""
+
+msgid "Run housekeeping"
+msgstr ""
+
+msgid "Run tests against your code live using the Web Terminal"
+msgstr ""
+
+msgid "Run untagged jobs"
+msgstr ""
+
+msgid "Runner cannot be assigned to other projects"
+msgstr ""
+
+msgid "Runner runs jobs from all unassigned projects"
+msgstr ""
+
+msgid "Runner runs jobs from all unassigned projects in its group"
+msgstr ""
+
+msgid "Runner runs jobs from assigned projects"
+msgstr ""
+
+msgid "Runner token"
+msgstr ""
+
+msgid "Runner tokens"
+msgstr ""
+
+msgid "Runner was not updated."
+msgstr ""
+
+msgid "Runner was successfully updated."
+msgstr ""
+
+msgid "Runner will not receive any new jobs"
+msgstr ""
+
+msgid "Runners"
+msgstr ""
+
+msgid "Runners API"
+msgstr ""
+
+msgid "Runners activated for this project"
+msgstr ""
+
+msgid "Runners are processes that pick up and execute jobs for GitLab. Here you can register and see your Runners for this project."
+msgstr ""
+
+msgid "Runners can be placed on separate users, servers, and even on your local machine."
+msgstr ""
+
+msgid "Runners can be placed on separate users, servers, even on your local machine."
+msgstr ""
+
+msgid "Runners currently online: %{active_runners_count}"
+msgstr ""
+
+msgid "Runners page."
+msgstr ""
+
+msgid "Runners|You have used %{quotaUsed} out of %{quotaLimit} of your shared Runners pipeline minutes."
+msgstr ""
+
+msgid "Running"
+msgstr ""
+
+msgid "Running…"
+msgstr ""
+
+msgid "Runs a number of housekeeping tasks within the current repository, such as compressing file revisions and removing unreachable objects."
+msgstr ""
+
+msgid "SAML SSO"
+msgstr ""
+
+msgid "SAML SSO for %{group_name}"
+msgstr ""
+
+msgid "SAML discovery tokens"
+msgstr ""
+
+msgid "SAML for %{group_name}"
+msgstr ""
+
+msgid "SHA256"
+msgstr ""
+
+msgid "SSH Key"
+msgstr ""
+
+msgid "SSH Keys"
+msgstr ""
+
+msgid "SSH host key fingerprints"
+msgstr ""
+
+msgid "SSH host keys"
+msgstr ""
+
+msgid "SSH host keys are not available on this system. Please use <code>ssh-keyscan</code> command or contact your GitLab administrator for more information."
+msgstr ""
+
+msgid "SSH keys allow you to establish a secure connection between your computer and GitLab."
+msgstr ""
+
+msgid "SSH public key"
+msgstr ""
+
+msgid "SSL Verification:"
+msgstr ""
+
+msgid "Saturday"
+msgstr ""
+
+msgid "Save"
+msgstr ""
+
+msgid "Save Changes"
+msgstr ""
+
+msgid "Save anyway"
+msgstr ""
+
+msgid "Save application"
+msgstr ""
+
+msgid "Save changes"
+msgstr ""
+
+msgid "Save changes before testing"
+msgstr ""
+
+msgid "Save comment"
+msgstr ""
+
+msgid "Save expiration policy"
+msgstr ""
+
+msgid "Save password"
+msgstr ""
+
+msgid "Save pipeline schedule"
+msgstr ""
+
+msgid "Save template"
+msgstr ""
+
+msgid "Save variables"
+msgstr ""
+
+msgid "Saving"
+msgstr ""
+
+msgid "Saving project."
+msgstr ""
+
+msgid "Schedule a new pipeline"
+msgstr ""
+
+msgid "Scheduled"
+msgstr ""
+
+msgid "Scheduled to merge this merge request (%{strategy})."
+msgstr ""
+
+msgid "Scheduled to merge this merge request when the pipeline succeeds."
+msgstr ""
+
+msgid "Schedules"
+msgstr ""
+
+msgid "Schedules to merge this merge request (%{strategy})."
+msgstr ""
+
+msgid "Scheduling"
+msgstr ""
+
+msgid "Scheduling Pipelines"
+msgstr ""
+
+msgid "Scope"
+msgstr ""
+
+msgid "Scope not supported with disabled 'users_search' feature!"
+msgstr ""
+
+msgid "Scoped issue boards"
+msgstr ""
+
+msgid "Scopes"
+msgstr ""
+
+msgid "Scopes can't be blank"
+msgstr ""
+
+msgid "Scroll down"
+msgstr ""
+
+msgid "Scroll down to <strong>Google Code Project Hosting</strong> and enable the switch on the right."
+msgstr ""
+
+msgid "Scroll left"
+msgstr ""
+
+msgid "Scroll right"
+msgstr ""
+
+msgid "Scroll to bottom"
+msgstr ""
+
+msgid "Scroll to top"
+msgstr ""
+
+msgid "Scroll up"
+msgstr ""
+
+msgid "Search"
+msgstr ""
+
+msgid "Search Button"
+msgstr ""
+
+msgid "Search Milestones"
+msgstr ""
+
+msgid "Search an environment spec"
+msgstr ""
+
+msgid "Search authors"
+msgstr ""
+
+msgid "Search branches"
+msgstr ""
+
+msgid "Search branches and tags"
+msgstr ""
+
+msgid "Search by author"
+msgstr ""
+
+msgid "Search files"
+msgstr ""
+
+msgid "Search for a LDAP group"
+msgstr ""
+
+msgid "Search for a group"
+msgstr ""
+
+msgid "Search for a user"
+msgstr ""
+
+msgid "Search for projects, issues, etc."
+msgstr ""
+
+msgid "Search for this text"
+msgstr ""
+
+msgid "Search forks"
+msgstr ""
+
+msgid "Search groups"
+msgstr ""
+
+msgid "Search merge requests"
+msgstr ""
+
+msgid "Search milestones"
+msgstr ""
+
+msgid "Search or filter results..."
+msgstr ""
+
+msgid "Search or filter results…"
+msgstr ""
+
+msgid "Search or jump to…"
+msgstr ""
+
+msgid "Search project"
+msgstr ""
+
+msgid "Search projects"
+msgstr ""
+
+msgid "Search projects..."
+msgstr ""
+
+msgid "Search requirements"
+msgstr ""
+
+msgid "Search users"
+msgstr ""
+
+msgid "Search users or groups"
+msgstr ""
+
+msgid "Search your project dependencies for their licenses and apply policies."
+msgstr ""
+
+msgid "Search your projects"
+msgstr ""
+
+msgid "SearchAutocomplete|All GitLab"
+msgstr ""
+
+msgid "SearchAutocomplete|Issues I've created"
+msgstr ""
+
+msgid "SearchAutocomplete|Issues assigned to me"
+msgstr ""
+
+msgid "SearchAutocomplete|Merge requests I've created"
+msgstr ""
+
+msgid "SearchAutocomplete|Merge requests assigned to me"
+msgstr ""
+
+msgid "SearchAutocomplete|in all GitLab"
+msgstr ""
+
+msgid "SearchAutocomplete|in group %{groupName}"
+msgstr ""
+
+msgid "SearchAutocomplete|in project %{projectName}"
+msgstr ""
+
+msgid "SearchCodeResults|in"
+msgstr ""
+
+msgid "SearchCodeResults|of %{link_to_project}"
+msgstr ""
+
+msgid "SearchResults|Showing %{count} %{scope} for%{term_element}"
+msgstr ""
+
+msgid "SearchResults|Showing %{count} %{scope} for%{term_element} in your personal and project snippets"
+msgstr ""
+
+msgid "SearchResults|Showing %{from} - %{to} of %{count} %{scope} for%{term_element}"
+msgstr ""
+
+msgid "SearchResults|Showing %{from} - %{to} of %{count} %{scope} for%{term_element} in your personal and project snippets"
+msgstr ""
+
+msgid "SearchResults|We couldn't find any %{scope} matching %{term}"
+msgstr ""
+
+msgid "SearchResults|code result"
+msgid_plural "SearchResults|code results"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "SearchResults|comment"
+msgid_plural "SearchResults|comments"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "SearchResults|commit"
+msgid_plural "SearchResults|commits"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "SearchResults|issue"
+msgid_plural "SearchResults|issues"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "SearchResults|merge request"
+msgid_plural "SearchResults|merge requests"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "SearchResults|milestone"
+msgid_plural "SearchResults|milestones"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "SearchResults|project"
+msgid_plural "SearchResults|projects"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "SearchResults|snippet"
+msgid_plural "SearchResults|snippets"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "SearchResults|user"
+msgid_plural "SearchResults|users"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "SearchResults|wiki result"
+msgid_plural "SearchResults|wiki results"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Searching by both author and message is currently not supported."
+msgstr ""
+
+msgid "Seat Link"
+msgstr ""
+
+msgid "Seat Link is disabled, and cannot be configured through this form."
+msgstr ""
+
+msgid "Seats currently in use"
+msgstr ""
+
+msgid "Seats in license"
+msgstr ""
+
+msgid "Secondary"
+msgstr ""
+
+msgid "Secret"
+msgstr ""
+
+msgid "Secret Detection"
+msgstr ""
+
+msgid "Security"
+msgstr ""
+
+msgid "Security & Compliance"
+msgstr ""
+
+msgid "Security Configuration"
+msgstr ""
+
+msgid "Security Dashboard"
+msgstr ""
+
+msgid "Security dashboard"
+msgstr ""
+
+msgid "Security report is out of date. Please update your branch with the latest changes from the target branch (%{targetBranchName})"
+msgstr ""
+
+msgid "Security report is out of date. Run %{newPipelineLinkStart}a new pipeline%{newPipelineLinkEnd} for the target branch (%{targetBranchName})"
+msgstr ""
+
+msgid "SecurityConfiguration|Enabled"
+msgstr ""
+
+msgid "SecurityConfiguration|Feature documentation for %{featureName}"
+msgstr ""
+
+msgid "SecurityConfiguration|Not yet enabled"
+msgstr ""
+
+msgid "SecurityConfiguration|Security Control"
+msgstr ""
+
+msgid "SecurityConfiguration|Status"
+msgstr ""
+
+msgid "SecurityConfiguration|Testing & Compliance"
+msgstr ""
+
+msgid "SecurityReports|%{firstProject} and %{secondProject}"
+msgstr ""
+
+msgid "SecurityReports|%{firstProject}, %{secondProject}, and %{rest}"
+msgstr ""
+
+msgid "SecurityReports|Add a project to your dashboard"
+msgstr ""
+
+msgid "SecurityReports|Add or remove projects from your dashboard"
+msgstr ""
+
+msgid "SecurityReports|Add projects"
+msgstr ""
+
+msgid "SecurityReports|Comment added to '%{vulnerabilityName}'"
+msgstr ""
+
+msgid "SecurityReports|Comment deleted on '%{vulnerabilityName}'"
+msgstr ""
+
+msgid "SecurityReports|Comment edited on '%{vulnerabilityName}'"
+msgstr ""
+
+msgid "SecurityReports|Create issue"
+msgstr ""
+
+msgid "SecurityReports|Dismiss Selected"
+msgstr ""
+
+msgid "SecurityReports|Dismiss vulnerability"
+msgstr ""
+
+msgid "SecurityReports|Dismissed '%{vulnerabilityName}'"
+msgstr ""
+
+msgid "SecurityReports|Dismissed '%{vulnerabilityName}'. Turn off the hide dismissed toggle to view."
+msgstr ""
+
+msgid "SecurityReports|Each vulnerability now has a unique page that can be directly linked to, shared, referenced, and tracked as the single source of truth. Vulnerability occurrences also persist across scanner runs, which improves tracking and visibility and reduces duplicates between scans."
+msgstr ""
+
+msgid "SecurityReports|Edit dashboard"
+msgstr ""
+
+msgid "SecurityReports|Either you don't have permission to view this dashboard or the dashboard has not been setup. Please check your permission settings with your administrator or check your dashboard configurations to proceed."
+msgstr ""
+
+msgid "SecurityReports|Error fetching the vulnerability counts. Please check your network connection and try again."
+msgstr ""
+
+msgid "SecurityReports|Error fetching the vulnerability list. Please check your network connection and try again."
+msgstr ""
+
+msgid "SecurityReports|False positive"
+msgstr ""
+
+msgid "SecurityReports|Group Security Dashboard"
+msgstr ""
+
+msgid "SecurityReports|Hide dismissed"
+msgstr ""
+
+msgid "SecurityReports|Introducing standalone vulnerabilities"
+msgstr ""
+
+msgid "SecurityReports|Issue Created"
+msgstr ""
+
+msgid "SecurityReports|Learn More"
+msgstr ""
+
+msgid "SecurityReports|Learn more about setting up your dashboard"
+msgstr ""
+
+msgid "SecurityReports|Load more vulnerabilities"
+msgstr ""
+
+msgid "SecurityReports|Monitor vulnerabilities in your code"
+msgstr ""
+
+msgid "SecurityReports|More info"
+msgstr ""
+
+msgid "SecurityReports|More information"
+msgstr ""
+
+msgid "SecurityReports|No vulnerabilities found for dashboard"
+msgstr ""
+
+msgid "SecurityReports|No vulnerabilities found for this group"
+msgstr ""
+
+msgid "SecurityReports|No vulnerabilities found for this pipeline"
+msgstr ""
+
+msgid "SecurityReports|No vulnerabilities found for this project"
+msgstr ""
+
+msgid "SecurityReports|Oops, something doesn't seem right."
+msgstr ""
+
+msgid "SecurityReports|Pipeline %{pipelineLink} triggered %{timeago} by %{user}"
+msgstr ""
+
+msgid "SecurityReports|Project"
+msgstr ""
+
+msgid "SecurityReports|Projects added"
+msgstr ""
+
+msgid "SecurityReports|Remove project from dashboard"
+msgstr ""
+
+msgid "SecurityReports|Return to dashboard"
+msgstr ""
+
+msgid "SecurityReports|Scan details"
+msgstr ""
+
+msgid "SecurityReports|Scanner type"
+msgstr ""
+
+msgid "SecurityReports|Security Dashboard"
+msgstr ""
+
+msgid "SecurityReports|Security reports can only be accessed by authorized users."
+msgstr ""
+
+msgid "SecurityReports|Select a project to add by using the project search field above."
+msgstr ""
+
+msgid "SecurityReports|Select a reason"
+msgstr ""
+
+msgid "SecurityReports|Severity"
+msgstr ""
+
+msgid "SecurityReports|Status"
+msgstr ""
+
+msgid "SecurityReports|The rating \"unknown\" indicates that the underlying scanner doesn’t contain or provide a severity rating."
+msgstr ""
+
+msgid "SecurityReports|The security dashboard displays the latest security findings for projects you wish to monitor. Select \"Edit dashboard\" to add and remove projects."
+msgstr ""
+
+msgid "SecurityReports|The security dashboard displays the latest security report. Use it to find and fix vulnerabilities."
+msgstr ""
+
+msgid "SecurityReports|There was an error adding the comment."
+msgstr ""
+
+msgid "SecurityReports|There was an error creating the issue."
+msgstr ""
+
+msgid "SecurityReports|There was an error creating the merge request."
+msgstr ""
+
+msgid "SecurityReports|There was an error deleting the comment."
+msgstr ""
+
+msgid "SecurityReports|There was an error dismissing the vulnerabilities."
+msgstr ""
+
+msgid "SecurityReports|There was an error dismissing the vulnerability."
+msgstr ""
+
+msgid "SecurityReports|There was an error reverting the dismissal."
+msgstr ""
+
+msgid "SecurityReports|There was an error reverting this dismissal."
+msgstr ""
+
+msgid "SecurityReports|There was an error while generating the report."
+msgstr ""
+
+msgid "SecurityReports|Unable to add %{invalidProjectsMessage}"
+msgstr ""
+
+msgid "SecurityReports|Unable to add %{invalidProjects}"
+msgstr ""
+
+msgid "SecurityReports|Undo dismiss"
+msgstr ""
+
+msgid "SecurityReports|While it's rare to have no vulnerabilities for your group, it can happen. In any event, we ask that you double check your settings to make sure you've set up your dashboard correctly."
+msgstr ""
+
+msgid "SecurityReports|While it's rare to have no vulnerabilities for your pipeline, it can happen. In any event, we ask that you double check your settings to make sure all security scanning jobs have passed successfully."
+msgstr ""
+
+msgid "SecurityReports|While it's rare to have no vulnerabilities for your project, it can happen. In any event, we ask that you double check your settings to make sure you've set up your dashboard correctly."
+msgstr ""
+
+msgid "SecurityReports|While it's rare to have no vulnerabilities, it can happen. In any event, we ask that you please double check your settings to make sure you've set up your dashboard correctly."
+msgstr ""
+
+msgid "SecurityReports|Won't fix / Accept risk"
+msgstr ""
+
+msgid "SecurityReports|You do not have sufficient permissions to access this report"
+msgstr ""
+
+msgid "SecurityReports|You must sign in as an authorized user to see this report"
+msgstr ""
+
+msgid "SecurityReports|[No reason]"
+msgstr ""
+
+msgid "See GitLab's %{password_policy_guidelines}"
+msgstr ""
+
+msgid "See metrics"
+msgstr ""
+
+msgid "See the affected projects in the GitLab admin panel"
+msgstr ""
+
+msgid "See what's new at GitLab"
+msgstr ""
+
+msgid "Select"
+msgstr ""
+
+msgid "Select Archive Format"
+msgstr ""
+
+msgid "Select Git revision"
+msgstr ""
+
+msgid "Select GitLab project to link with your Slack team"
+msgstr ""
+
+msgid "Select Page"
+msgstr ""
+
+msgid "Select Stack"
+msgstr ""
+
+msgid "Select a file from the left sidebar to begin editing. Afterwards, you'll be able to commit your changes."
+msgstr ""
+
+msgid "Select a group to invite"
+msgstr ""
+
+msgid "Select a label"
+msgstr ""
+
+msgid "Select a namespace to fork the project"
+msgstr ""
+
+msgid "Select a new namespace"
+msgstr ""
+
+msgid "Select a project"
+msgstr ""
+
+msgid "Select a project to read Insights configuration file"
+msgstr ""
+
+msgid "Select a reason"
+msgstr ""
+
+msgid "Select a repository"
+msgstr ""
+
+msgid "Select a template repository"
+msgstr ""
+
+msgid "Select a template type"
+msgstr ""
+
+msgid "Select a timezone"
+msgstr ""
+
+msgid "Select all"
+msgstr ""
+
+msgid "Select an existing Kubernetes cluster or create a new one"
+msgstr ""
+
+msgid "Select assignee"
+msgstr ""
+
+msgid "Select branch"
+msgstr ""
+
+msgid "Select branch/tag"
+msgstr ""
+
+msgid "Select due date"
+msgstr ""
+
+msgid "Select file"
+msgstr ""
+
+msgid "Select group or project"
+msgstr ""
+
+msgid "Select groups to replicate"
+msgstr ""
+
+msgid "Select health status"
+msgstr ""
+
+msgid "Select labels"
+msgstr ""
+
+msgid "Select merge moment"
+msgstr ""
+
+msgid "Select milestone"
+msgstr ""
+
+msgid "Select private project"
+msgstr ""
+
+msgid "Select project"
+msgstr ""
+
+msgid "Select project and zone to choose machine type"
+msgstr ""
+
+msgid "Select project to choose zone"
+msgstr ""
+
+msgid "Select projects"
+msgstr ""
+
+msgid "Select projects you want to import."
+msgstr ""
+
+msgid "Select required regulatory standard"
+msgstr ""
+
+msgid "Select shards to replicate"
+msgstr ""
+
+msgid "Select source branch"
+msgstr ""
+
+msgid "Select start date"
+msgstr ""
+
+msgid "Select status"
+msgstr ""
+
+msgid "Select strategy activation method"
+msgstr ""
+
+msgid "Select subscription"
+msgstr ""
+
+msgid "Select target branch"
+msgstr ""
+
+msgid "Select the branch you want to set as the default for this project. All merge requests and commits will automatically be made against this branch unless you specify a different one."
+msgstr ""
+
+msgid "Select the custom project template source group."
+msgstr ""
+
+msgid "Select timeframe"
+msgstr ""
+
+msgid "Select user"
+msgstr ""
+
+msgid "Selected levels cannot be used by non-admin users for groups, projects or snippets. If the public level is restricted, user profiles are only visible to logged in users."
+msgstr ""
+
+msgid "Selecting a GitLab user will add a link to the GitLab user in the descriptions of issues and comments (e.g. \"By <a href=\"#\">@johnsmith</a>\"). It will also associate and/or assign these issues and comments with the selected user."
+msgstr ""
+
+msgid "Selective synchronization"
+msgstr ""
+
+msgid "Self monitoring project does not exist"
+msgstr ""
+
+msgid "Self-monitoring project does not exist. Please check logs for any error messages"
+msgstr ""
+
+msgid "Self-monitoring project has been successfully deleted"
+msgstr ""
+
+msgid "Self-monitoring project was not deleted. Please check logs for any error messages"
+msgstr ""
+
+msgid "SelfMonitoring|Disable self monitoring?"
+msgstr ""
+
+msgid "SelfMonitoring|Disabling this feature will delete the self monitoring project. Are you sure you want to delete the project?"
+msgstr ""
+
+msgid "SelfMonitoring|Enable or disable instance self monitoring"
+msgstr ""
+
+msgid "SelfMonitoring|Enabling this feature creates a %{projectLinkStart}project%{projectLinkEnd} that can be used to monitor the health of your instance."
+msgstr ""
+
+msgid "SelfMonitoring|Enabling this feature creates a project that can be used to monitor the health of your instance."
+msgstr ""
+
+msgid "SelfMonitoring|Self monitoring"
+msgstr ""
+
+msgid "SelfMonitoring|Self monitoring project has been successfully created."
+msgstr ""
+
+msgid "SelfMonitoring|Self monitoring project has been successfully deleted."
+msgstr ""
+
+msgid "Send a separate email notification to Developers."
+msgstr ""
+
+msgid "Send confirmation email"
+msgstr ""
+
+msgid "Send email"
+msgstr ""
+
+msgid "Send email notification"
+msgstr ""
+
+msgid "Send message"
+msgstr ""
+
+msgid "Send report"
+msgstr ""
+
+msgid "Send usage data"
+msgstr ""
+
+msgid "Sentry API URL"
+msgstr ""
+
+msgid "Sentry event"
+msgstr ""
+
+msgid "Sep"
+msgstr ""
+
+msgid "Separate topics with commas."
+msgstr ""
+
+msgid "September"
+msgstr ""
+
+msgid "SeriesFinalConjunction|and"
+msgstr ""
+
+msgid "Serve repository static objects (e.g. archives, blobs, ...) from an external storage (e.g. a CDN)."
+msgstr ""
+
+msgid "Server supports batch API only, please update your Git LFS client to version 1.0.1 and up."
+msgstr ""
+
+msgid "Server version"
+msgstr ""
+
+msgid "Serverless"
+msgstr ""
+
+msgid "Serverless domain"
+msgstr ""
+
+msgid "ServerlessDetails|Function invocation metrics require Prometheus to be installed first."
+msgstr ""
+
+msgid "ServerlessDetails|Install Prometheus"
+msgstr ""
+
+msgid "ServerlessDetails|Invocation metrics loading or not available at this time."
+msgstr ""
+
+msgid "ServerlessDetails|Invocations"
+msgstr ""
+
+msgid "ServerlessDetails|Kubernetes Pods"
+msgstr ""
+
+msgid "ServerlessDetails|More information"
+msgstr ""
+
+msgid "ServerlessDetails|No pods loaded at this time."
+msgstr ""
+
+msgid "ServerlessDetails|Number of Kubernetes pods in use over time based on necessity."
+msgstr ""
+
+msgid "ServerlessDetails|pod in use"
+msgstr ""
+
+msgid "ServerlessDetails|pods in use"
+msgstr ""
+
+msgid "ServerlessURL|Copy URL"
+msgstr ""
+
+msgid "Serverless| In order to start using functions as a service, you must first install Knative on your Kubernetes cluster."
+msgstr ""
+
+msgid "Serverless|Getting started with serverless"
+msgstr ""
+
+msgid "Serverless|Help shape the future of Serverless at GitLab"
+msgstr ""
+
+msgid "Serverless|If you believe none of these apply, please check back later as the function data may be in the process of becoming available."
+msgstr ""
+
+msgid "Serverless|Install Knative"
+msgstr ""
+
+msgid "Serverless|Learn more about Serverless"
+msgstr ""
+
+msgid "Serverless|No functions available"
+msgstr ""
+
+msgid "Serverless|Sign up for First Look"
+msgstr ""
+
+msgid "Serverless|The deploy job has not finished."
+msgstr ""
+
+msgid "Serverless|The functions listed in the %{startTag}serverless.yml%{endTag} file don't match the namespace of your cluster."
+msgstr ""
+
+msgid "Serverless|There is currently no function data available from Knative. This could be for a variety of reasons including:"
+msgstr ""
+
+msgid "Serverless|We are continually striving to improve our Serverless functionality. As a Knative user, we would love to hear how we can make this experience better for you. Sign up for GitLab First Look today and we will be in touch shortly."
+msgstr ""
+
+msgid "Serverless|Your %{startTag}.gitlab-ci.yml%{endTag} file is not properly configured."
+msgstr ""
+
+msgid "Serverless|Your repository does not have a corresponding %{startTag}serverless.yml%{endTag} file."
+msgstr ""
+
+msgid "Service"
+msgstr ""
+
+msgid "Service Desk"
+msgstr ""
+
+msgid "Service Desk is enabled but not yet active"
+msgstr ""
+
+msgid "Service Templates"
+msgstr ""
+
+msgid "Service URL"
+msgstr ""
+
+msgid "Session duration (minutes)"
+msgstr ""
+
+msgid "Set %{epic_ref} as the parent epic."
+msgstr ""
+
+msgid "Set a default template for issue descriptions."
+msgstr ""
+
+msgid "Set a number of approvals required, the approvers and other approval settings."
+msgstr ""
+
+msgid "Set a password on your account to pull or push via %{protocol}."
+msgstr ""
+
+msgid "Set a template repository for projects in this group"
+msgstr ""
+
+msgid "Set an instance-wide domain that will be available to all clusters when installing Knative."
+msgstr ""
+
+msgid "Set default and restrict visibility levels. Configure import sources and git access protocol."
+msgstr ""
+
+msgid "Set due date"
+msgstr ""
+
+msgid "Set instance-wide template repository"
+msgstr ""
+
+msgid "Set iteration"
+msgstr ""
+
+msgid "Set max session time for web terminal."
+msgstr ""
+
+msgid "Set milestone"
+msgstr ""
+
+msgid "Set new password"
+msgstr ""
+
+msgid "Set notification email for abuse reports."
+msgstr ""
+
+msgid "Set parent epic to an epic"
+msgstr ""
+
+msgid "Set projects and maximum size limits, session duration, user options, and check feature availability for namespace plan."
+msgstr ""
+
+msgid "Set requirements for a user to sign-in. Enable mandatory two-factor authentication."
+msgstr ""
+
+msgid "Set target branch"
+msgstr ""
+
+msgid "Set target branch to %{branch_name}."
+msgstr ""
+
+msgid "Set the default expiration time for each job's artifacts. 0 for unlimited. The default unit is in seconds, but you can define an alternative. For example: <code>4 mins 2 sec</code>, <code>2h42min</code>."
+msgstr ""
+
+msgid "Set the due date to %{due_date}."
+msgstr ""
+
+msgid "Set the duration for which the jobs will be considered as old and expired. Once that time passes, the jobs will be archived and no longer able to be retried. Make it empty to never expire jobs. It has to be no less than 1 day, for example: <code>15 days</code>, <code>1 month</code>, <code>2 years</code>."
+msgstr ""
+
+msgid "Set the iteration to %{iteration_reference}."
+msgstr ""
+
+msgid "Set the maximum file size for each job's artifacts"
+msgstr ""
+
+msgid "Set the maximum number of pipeline minutes that a group can use on shared Runners per month. 0 for unlimited."
+msgstr ""
+
+msgid "Set the milestone to %{milestone_reference}."
+msgstr ""
+
+msgid "Set the number of concurrent requests this secondary node will make to the primary node while backfilling."
+msgstr ""
+
+msgid "Set the synchronization and verification capacity for the secondary node."
+msgstr ""
+
+msgid "Set the timeout in seconds to send a secondary node status to the primary and IPs allowed for the secondary nodes."
+msgstr ""
+
+msgid "Set time estimate"
+msgstr ""
+
+msgid "Set time estimate to %{time_estimate}."
+msgstr ""
+
+msgid "Set up CI/CD"
+msgstr ""
+
+msgid "Set up Jira Integration"
+msgstr ""
+
+msgid "Set up a %{type} Runner automatically"
+msgstr ""
+
+msgid "Set up a %{type} Runner manually"
+msgstr ""
+
+msgid "Set up assertions/attributes/claims (email, first_name, last_name) and NameID according to %{docsLinkStart}the documentation %{icon}%{docsLinkEnd}"
+msgstr ""
+
+msgid "Set up new U2F device"
+msgstr ""
+
+msgid "Set up new password"
+msgstr ""
+
+msgid "Set up pipeline subscriptions for this project."
+msgstr ""
+
+msgid "Set up your project to automatically push and/or pull changes to/from another repository. Branches, tags, and commits will be synced automatically."
+msgstr ""
+
+msgid "Set weight"
+msgstr ""
+
+msgid "Set weight to %{weight}."
+msgstr ""
+
+msgid "Set what should be replicated by choosing specific projects or groups by the secondary node."
+msgstr ""
+
+msgid "SetPasswordToCloneLink|set a password"
+msgstr ""
+
+msgid "SetStatusModal|Add status emoji"
+msgstr ""
+
+msgid "SetStatusModal|Clear status"
+msgstr ""
+
+msgid "SetStatusModal|Edit status"
+msgstr ""
+
+msgid "SetStatusModal|Remove status"
+msgstr ""
+
+msgid "SetStatusModal|Set a status"
+msgstr ""
+
+msgid "SetStatusModal|Set status"
+msgstr ""
+
+msgid "SetStatusModal|Sorry, we weren't able to set your status. Please try again later."
+msgstr ""
+
+msgid "SetStatusModal|What's your status?"
+msgstr ""
+
+msgid "Sets %{epic_ref} as parent epic."
+msgstr ""
+
+msgid "Sets target branch to %{branch_name}."
+msgstr ""
+
+msgid "Sets the due date to %{due_date}."
+msgstr ""
+
+msgid "Sets the iteration to %{iteration_reference}."
+msgstr ""
+
+msgid "Sets the milestone to %{milestone_reference}."
+msgstr ""
+
+msgid "Sets time estimate to %{time_estimate}."
+msgstr ""
+
+msgid "Sets weight to %{weight}."
+msgstr ""
+
+msgid "Settings"
+msgstr ""
+
+msgid "Settings related to the use and experience of using GitLab's Package Registry."
+msgstr ""
+
+msgid "Settings to prevent self-approval across all projects in the instance. Only an administrator can modify these settings."
+msgstr ""
+
+msgid "Severity"
+msgstr ""
+
+msgid "Shards (%{shards})"
+msgstr ""
+
+msgid "Shards to synchronize"
+msgstr ""
+
+msgid "Share"
+msgstr ""
+
+msgid "Share the <strong>%{sso_label}</strong> with members so they can sign in to your group through your identity provider"
+msgstr ""
+
+msgid "Shared Runners"
+msgstr ""
+
+msgid "Shared projects"
+msgstr ""
+
+msgid "Shared runners help link"
+msgstr ""
+
+msgid "SharedRunnersMinutesSettings|By resetting the pipeline minutes for this namespace, the currently used minutes will be set to zero."
+msgstr ""
+
+msgid "SharedRunnersMinutesSettings|Reset pipeline minutes"
+msgstr ""
+
+msgid "SharedRunnersMinutesSettings|Reset used pipeline minutes"
+msgstr ""
+
+msgid "Sherlock Transactions"
+msgstr ""
+
+msgid "Should you ever lose your phone or access to your one time password secret, each of these recovery codes can be used one time each to regain access to your account. Please save them in a safe place, or you %{b_start}will%{b_end} lose access to your account."
+msgstr ""
+
+msgid "Show all activity"
+msgstr ""
+
+msgid "Show all members"
+msgstr ""
+
+msgid "Show all requirements."
+msgstr ""
+
+msgid "Show archived projects"
+msgstr ""
+
+msgid "Show archived projects only"
+msgstr ""
+
+msgid "Show command"
+msgstr ""
+
+msgid "Show comments"
+msgstr ""
+
+msgid "Show comments only"
+msgstr ""
+
+msgid "Show commit description"
+msgstr ""
+
+msgid "Show complete raw log"
+msgstr ""
+
+msgid "Show details"
+msgstr ""
+
+msgid "Show file browser"
+msgstr ""
+
+msgid "Show file contents"
+msgstr ""
+
+msgid "Show latest version"
+msgstr ""
+
+msgid "Show list"
+msgstr ""
+
+msgid "Show me everything"
+msgstr ""
+
+msgid "Show me how to add a pipeline"
+msgstr ""
+
+msgid "Show me more advanced stuff"
+msgstr ""
+
+msgid "Show only direct members"
+msgstr ""
+
+msgid "Show only inherited members"
+msgstr ""
+
+msgid "Show parent pages"
+msgstr ""
+
+msgid "Show parent subgroups"
+msgstr ""
+
+msgid "Show whitespace changes"
+msgstr ""
+
+msgid "Showing %d event"
+msgid_plural "Showing %d events"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Showing %{count} of %{total} projects"
+msgstr ""
+
+msgid "Showing %{count} project"
+msgid_plural "Showing %{count} projects"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Showing %{limit} of %{total_count} issues. "
+msgstr ""
+
+msgid "Showing %{pageSize} of %{total} issues"
+msgstr ""
+
+msgid "Showing Latest Version"
+msgstr ""
+
+msgid "Showing Version #%{versionNumber}"
+msgstr ""
+
+msgid "Showing all issues"
+msgstr ""
+
+msgid "Showing last %{size} of log -"
+msgstr ""
+
+msgid "Side-by-side"
+msgstr ""
+
+msgid "Sidebar|Assign health status"
+msgstr ""
+
+msgid "Sidebar|Change weight"
+msgstr ""
+
+msgid "Sidebar|Health status"
+msgstr ""
+
+msgid "Sidebar|No status"
+msgstr ""
+
+msgid "Sidebar|None"
+msgstr ""
+
+msgid "Sidebar|Only numeral characters allowed"
+msgstr ""
+
+msgid "Sidebar|Weight"
+msgstr ""
+
+msgid "Sign in"
+msgstr ""
+
+msgid "Sign in / Register"
+msgstr ""
+
+msgid "Sign in to \"%{group_name}\""
+msgstr ""
+
+msgid "Sign in using smart card"
+msgstr ""
+
+msgid "Sign in via 2FA code"
+msgstr ""
+
+msgid "Sign in with Single Sign-On"
+msgstr ""
+
+msgid "Sign in with smart card"
+msgstr ""
+
+msgid "Sign out"
+msgstr ""
+
+msgid "Sign out & Register"
+msgstr ""
+
+msgid "Sign up"
+msgstr ""
+
+msgid "Sign up was successful! Please confirm your email to sign in."
+msgstr ""
+
+msgid "Sign-in restrictions"
+msgstr ""
+
+msgid "Sign-up restrictions"
+msgstr ""
+
+msgid "SignUp|First Name is too long (maximum is %{max_length} characters)."
+msgstr ""
+
+msgid "SignUp|Last Name is too long (maximum is %{max_length} characters)."
+msgstr ""
+
+msgid "SignUp|Name is too long (maximum is %{max_length} characters)."
+msgstr ""
+
+msgid "SignUp|Username is too long (maximum is %{max_length} characters)."
+msgstr ""
+
+msgid "Signed in"
+msgstr ""
+
+msgid "Signed in with %{authentication} authentication"
+msgstr ""
+
+msgid "Signing in using %{label} has been disabled"
+msgstr ""
+
+msgid "Signing in using your %{label} account without a pre-existing GitLab account is not allowed."
+msgstr ""
+
+msgid "Similar issues"
+msgstr ""
+
+msgid "Single or combined queries"
+msgstr ""
+
+msgid "Size"
+msgstr ""
+
+msgid "Size and domain settings for static websites"
+msgstr ""
+
+msgid "Size limit per repository (MB)"
+msgstr ""
+
+msgid "Size settings for static websites"
+msgstr ""
+
+msgid "Skip outdated deployment jobs"
+msgstr ""
+
+msgid "Skipped"
+msgstr ""
+
+msgid "Slack application"
+msgstr ""
+
+msgid "Slack channels (e.g. general, development)"
+msgstr ""
+
+msgid "Slack integration allows you to interact with GitLab via slash commands in a chat window."
+msgstr ""
+
+msgid "SlackIntegration|%{webhooks_link_start}Add an incoming webhook%{webhooks_link_end} in your Slack team. The default channel can be overridden for each event."
+msgstr ""
+
+msgid "SlackIntegration|<strong>Note:</strong> Usernames and private channels are not supported."
+msgstr ""
+
+msgid "SlackIntegration|Paste the <strong>Webhook URL</strong> into the field below."
+msgstr ""
+
+msgid "SlackIntegration|Select events below to enable notifications. The <strong>Slack channel names</strong> and <strong>Slack username</strong> fields are optional."
+msgstr ""
+
+msgid "SlackIntegration|This service send notifications about projects' events to Slack channels. To set up this service:"
+msgstr ""
+
+msgid "SlackService|2. Paste the <strong>Token</strong> into the field below"
+msgstr ""
+
+msgid "SlackService|3. Select the <strong>Active</strong> checkbox, press <strong>Save changes</strong> and start using GitLab inside Slack!"
+msgstr ""
+
+msgid "SlackService|Fill in the word that works best for your team."
+msgstr ""
+
+msgid "SlackService|See list of available commands in Slack after setting up this service, by entering"
+msgstr ""
+
+msgid "SlackService|This service allows users to perform common operations on this project by entering slash commands in Slack."
+msgstr ""
+
+msgid "Slower but makes sure the project workspace is pristine as it clones the repository from scratch for every job"
+msgstr ""
+
+msgid "Smartcard"
+msgstr ""
+
+msgid "Smartcard authentication failed: client certificate header is missing."
+msgstr ""
+
+msgid "Snippets"
+msgstr ""
+
+msgid "Snippets with non-text files can only be edited via Git."
+msgstr ""
+
+msgid "SnippetsEmptyState|Code snippets"
+msgstr ""
+
+msgid "SnippetsEmptyState|Documentation"
+msgstr ""
+
+msgid "SnippetsEmptyState|New snippet"
+msgstr ""
+
+msgid "SnippetsEmptyState|No snippets found"
+msgstr ""
+
+msgid "SnippetsEmptyState|Store, share, and embed small pieces of code and text."
+msgstr ""
+
+msgid "SnippetsEmptyState|There are no snippets to show."
+msgstr ""
+
+msgid "Snippets|Description (optional)"
+msgstr ""
+
+msgid "Snippets|File"
+msgstr ""
+
+msgid "Snippets|Give your file a name to add code highlighting, e.g. example.rb for Ruby"
+msgstr ""
+
+msgid "Snippets|Optionally add a description about what your snippet does or how to use it..."
+msgstr ""
+
+msgid "Snippets|Optionally add a description about what your snippet does or how to use it…"
+msgstr ""
+
+msgid "Snowplow"
+msgstr ""
+
+msgid "Solution"
+msgstr ""
+
+msgid "Some child epics may be hidden due to applied filters"
+msgstr ""
+
+msgid "Some common domains are not allowed. %{read_more_link}."
+msgstr ""
+
+msgid "Some email servers do not support overriding the email sender name. Enable this option to include the name of the author of the issue, merge request or comment in the email body instead."
+msgstr ""
+
+msgid "Some of the designs you tried uploading did not change:"
+msgstr ""
+
+msgid "Some of your epics may not be visible. A roadmap is limited to the first 1,000 epics, in your selected sort order."
+msgstr ""
+
+msgid "Someone edited the issue at the same time you did. Please check out %{linkStart}the issue%{linkEnd} and make sure your changes will not unintentionally remove theirs."
+msgstr ""
+
+msgid "Someone edited this %{issueType} at the same time you did. The description has been updated and you will need to make your changes again."
+msgstr ""
+
+msgid "Someone edited this merge request at the same time you did. Please refresh the page to see changes."
+msgstr ""
+
+msgid "Something went wrong on our end"
+msgstr ""
+
+msgid "Something went wrong on our end."
+msgstr ""
+
+msgid "Something went wrong on our end. Please try again!"
+msgstr ""
+
+msgid "Something went wrong on our end. Please try again."
+msgstr ""
+
+msgid "Something went wrong trying to change the confidentiality of this issue"
+msgstr ""
+
+msgid "Something went wrong trying to change the locked state of this %{issuableDisplayName}"
+msgstr ""
+
+msgid "Something went wrong when toggling the button"
+msgstr ""
+
+msgid "Something went wrong while adding your award. Please try again."
+msgstr ""
+
+msgid "Something went wrong while applying the batch of suggestions. Please try again."
+msgstr ""
+
+msgid "Something went wrong while applying the suggestion. Please try again."
+msgstr ""
+
+msgid "Something went wrong while archiving a requirement."
+msgstr ""
+
+msgid "Something went wrong while closing the %{issuable}. Please try again later"
+msgstr ""
+
+msgid "Something went wrong while creating a requirement."
+msgstr ""
+
+msgid "Something went wrong while deleting description changes. Please try again."
+msgstr ""
+
+msgid "Something went wrong while deleting the package."
+msgstr ""
+
+msgid "Something went wrong while deleting the source branch. Please try again."
+msgstr ""
+
+msgid "Something went wrong while deleting your note. Please try again."
+msgstr ""
+
+msgid "Something went wrong while deploying this environment. Please try again."
+msgstr ""
+
+msgid "Something went wrong while editing your comment. Please try again."
+msgstr ""
+
+msgid "Something went wrong while fetching %{listType} list"
+msgstr ""
+
+msgid "Something went wrong while fetching comments. Please try again."
+msgstr ""
+
+msgid "Something went wrong while fetching description changes. Please try again."
+msgstr ""
+
+msgid "Something went wrong while fetching group member contributions"
+msgstr ""
+
+msgid "Something went wrong while fetching latest comments."
+msgstr ""
+
+msgid "Something went wrong while fetching projects"
+msgstr ""
+
+msgid "Something went wrong while fetching projects."
+msgstr ""
+
+msgid "Something went wrong while fetching related merge requests."
+msgstr ""
+
+msgid "Something went wrong while fetching requirements count."
+msgstr ""
+
+msgid "Something went wrong while fetching requirements list."
+msgstr ""
+
+msgid "Something went wrong while fetching the environments for this merge request. Please try again."
+msgstr ""
+
+msgid "Something went wrong while fetching the package."
+msgstr ""
+
+msgid "Something went wrong while fetching the packages list."
+msgstr ""
+
+msgid "Something went wrong while initializing the OpenAPI viewer"
+msgstr ""
+
+msgid "Something went wrong while merging this merge request. Please try again."
+msgstr ""
+
+msgid "Something went wrong while moving issues."
+msgstr ""
+
+msgid "Something went wrong while obtaining the Let's Encrypt certificate."
+msgstr ""
+
+msgid "Something went wrong while performing the action."
+msgstr ""
+
+msgid "Something went wrong while reopening a requirement."
+msgstr ""
+
+msgid "Something went wrong while reopening the %{issuable}. Please try again later"
+msgstr ""
+
+msgid "Something went wrong while resolving this discussion. Please try again."
+msgstr ""
+
+msgid "Something went wrong while stopping this environment. Please try again."
+msgstr ""
+
+msgid "Something went wrong while toggling auto-fix settings, please try again later."
+msgstr ""
+
+msgid "Something went wrong while updating a requirement."
+msgstr ""
+
+msgid "Something went wrong while updating assignees"
+msgstr ""
+
+msgid "Something went wrong while updating your list settings"
+msgstr ""
+
+msgid "Something went wrong with your automatic subscription renewal."
+msgstr ""
+
+msgid "Something went wrong, unable to add %{project} to dashboard"
+msgstr ""
+
+msgid "Something went wrong, unable to add projects to dashboard"
+msgstr ""
+
+msgid "Something went wrong, unable to get projects"
+msgstr ""
+
+msgid "Something went wrong, unable to remove project"
+msgstr ""
+
+msgid "Something went wrong, unable to search projects"
+msgstr ""
+
+msgid "Something went wrong."
+msgstr ""
+
+msgid "Something went wrong. Please try again."
+msgstr ""
+
+msgid "Something went wrong. Try again later."
+msgstr ""
+
+msgid "Sorry, no epics matched your search"
+msgstr ""
+
+msgid "Sorry, no projects matched your search"
+msgstr ""
+
+msgid "Sorry, you have exceeded the maximum browsable page number. Please use the API to explore further."
+msgstr ""
+
+msgid "Sorry, your filter produced no results"
+msgstr ""
+
+msgid "Sort by"
+msgstr ""
+
+msgid "Sort direction"
+msgstr ""
+
+msgid "Sort direction: Ascending"
+msgstr ""
+
+msgid "Sort direction: Descending"
+msgstr ""
+
+msgid "SortOptions|Access level, ascending"
+msgstr ""
+
+msgid "SortOptions|Access level, descending"
+msgstr ""
+
+msgid "SortOptions|Created date"
+msgstr ""
+
+msgid "SortOptions|Due date"
+msgstr ""
+
+msgid "SortOptions|Due later"
+msgstr ""
+
+msgid "SortOptions|Due soon"
+msgstr ""
+
+msgid "SortOptions|Expired date"
+msgstr ""
+
+msgid "SortOptions|Label priority"
+msgstr ""
+
+msgid "SortOptions|Largest group"
+msgstr ""
+
+msgid "SortOptions|Largest repository"
+msgstr ""
+
+msgid "SortOptions|Last Contact"
+msgstr ""
+
+msgid "SortOptions|Last created"
+msgstr ""
+
+msgid "SortOptions|Last joined"
+msgstr ""
+
+msgid "SortOptions|Last updated"
+msgstr ""
+
+msgid "SortOptions|Least popular"
+msgstr ""
+
+msgid "SortOptions|Less weight"
+msgstr ""
+
+msgid "SortOptions|Manual"
+msgstr ""
+
+msgid "SortOptions|Milestone due date"
+msgstr ""
+
+msgid "SortOptions|Milestone due later"
+msgstr ""
+
+msgid "SortOptions|Milestone due soon"
+msgstr ""
+
+msgid "SortOptions|More weight"
+msgstr ""
+
+msgid "SortOptions|Most popular"
+msgstr ""
+
+msgid "SortOptions|Most stars"
+msgstr ""
+
+msgid "SortOptions|Name"
+msgstr ""
+
+msgid "SortOptions|Name, ascending"
+msgstr ""
+
+msgid "SortOptions|Name, descending"
+msgstr ""
+
+msgid "SortOptions|Oldest created"
+msgstr ""
+
+msgid "SortOptions|Oldest joined"
+msgstr ""
+
+msgid "SortOptions|Oldest last activity"
+msgstr ""
+
+msgid "SortOptions|Oldest sign in"
+msgstr ""
+
+msgid "SortOptions|Oldest starred"
+msgstr ""
+
+msgid "SortOptions|Oldest updated"
+msgstr ""
+
+msgid "SortOptions|Popularity"
+msgstr ""
+
+msgid "SortOptions|Priority"
+msgstr ""
+
+msgid "SortOptions|Project"
+msgstr ""
+
+msgid "SortOptions|Recent last activity"
+msgstr ""
+
+msgid "SortOptions|Recent sign in"
+msgstr ""
+
+msgid "SortOptions|Recently starred"
+msgstr ""
+
+msgid "SortOptions|Size"
+msgstr ""
+
+msgid "SortOptions|Sort by:"
+msgstr ""
+
+msgid "SortOptions|Sort direction"
+msgstr ""
+
+msgid "SortOptions|Stars"
+msgstr ""
+
+msgid "SortOptions|Start date"
+msgstr ""
+
+msgid "SortOptions|Start later"
+msgstr ""
+
+msgid "SortOptions|Start soon"
+msgstr ""
+
+msgid "SortOptions|Type"
+msgstr ""
+
+msgid "SortOptions|Version"
+msgstr ""
+
+msgid "SortOptions|Weight"
+msgstr ""
+
+msgid "Source"
+msgstr ""
+
+msgid "Source (branch or tag)"
+msgstr ""
+
+msgid "Source code"
+msgstr ""
+
+msgid "Source code (%{fileExtension})"
+msgstr ""
+
+msgid "Source is not available"
+msgstr ""
+
+msgid "Source project cannot be found."
+msgstr ""
+
+msgid "Sourcegraph"
+msgstr ""
+
+msgid "SourcegraphAdmin|Block on private and internal projects"
+msgstr ""
+
+msgid "SourcegraphAdmin|Configure the URL to a Sourcegraph instance which can read your GitLab projects."
+msgstr ""
+
+msgid "SourcegraphAdmin|Enable Sourcegraph"
+msgstr ""
+
+msgid "SourcegraphAdmin|Enable code intelligence powered by %{link_start}Sourcegraph%{link_end} on your GitLab instance's code views and merge requests."
+msgstr ""
+
+msgid "SourcegraphAdmin|If checked, only public projects will have code intelligence and communicate with Sourcegraph."
+msgstr ""
+
+msgid "SourcegraphAdmin|More information"
+msgstr ""
+
+msgid "SourcegraphAdmin|Save changes"
+msgstr ""
+
+msgid "SourcegraphAdmin|Sourcegraph URL"
+msgstr ""
+
+msgid "SourcegraphAdmin|e.g. https://sourcegraph.example.com"
+msgstr ""
+
+msgid "SourcegraphPreferences|This feature is experimental and currently limited to certain projects."
+msgstr ""
+
+msgid "SourcegraphPreferences|This feature is experimental and limited to public projects."
+msgstr ""
+
+msgid "SourcegraphPreferences|This feature is experimental."
+msgstr ""
+
+msgid "SourcegraphPreferences|Uses %{link_start}Sourcegraph.com%{link_end}."
+msgstr ""
+
+msgid "SourcegraphPreferences|Uses a custom %{link_start}Sourcegraph instance%{link_end}."
+msgstr ""
+
+msgid "Spam Logs"
+msgstr ""
+
+msgid "Spam and Anti-bot Protection"
+msgstr ""
+
+msgid "Spam log successfully submitted as ham."
+msgstr ""
+
+msgid "Specific Runners"
+msgstr ""
+
+msgid "Specified URL cannot be used: \"%{reason}\""
+msgstr ""
+
+msgid "Specify an e-mail address regex pattern to identify default internal users."
+msgstr ""
+
+msgid "Specify the following URL during the Runner setup:"
+msgstr ""
+
+msgid "Speed up your DevOps<br>with GitLab"
+msgstr ""
+
+msgid "Squash commit message"
+msgstr ""
+
+msgid "Squash commits"
+msgstr ""
+
+msgid "Stack trace"
+msgstr ""
+
+msgid "Stacktrace snippet"
+msgstr ""
+
+msgid "Stage"
+msgstr ""
+
+msgid "Stage & Commit"
+msgstr ""
+
+msgid "Stage data updated"
+msgstr ""
+
+msgid "Stage removed"
+msgstr ""
+
+msgid "Standard"
+msgstr ""
+
+msgid "Star a label to make it a priority label. Order the prioritized labels to change their relative priority, by dragging."
+msgstr ""
+
+msgid "Star labels to start sorting by priority"
+msgstr ""
+
+msgid "Star toggle failed. Try again later."
+msgstr ""
+
+msgid "StarProject|Star"
+msgstr ""
+
+msgid "Starred Projects"
+msgstr ""
+
+msgid "Starred Projects' Activity"
+msgstr ""
+
+msgid "Starred projects"
+msgstr ""
+
+msgid "StarredProjectsEmptyState|Visit a project page and press on a star icon. Then, you can find the project on this page."
+msgstr ""
+
+msgid "StarredProjectsEmptyState|You don't have starred projects yet."
+msgstr ""
+
+msgid "Starrers"
+msgstr ""
+
+msgid "Stars"
+msgstr ""
+
+msgid "Start Date"
+msgstr ""
+
+msgid "Start Web Terminal"
+msgstr ""
+
+msgid "Start a %{new_merge_request} with these changes"
+msgstr ""
+
+msgid "Start a Free Gold Trial"
+msgstr ""
+
+msgid "Start a new discussion..."
+msgstr ""
+
+msgid "Start a new merge request"
+msgstr ""
+
+msgid "Start a review"
+msgstr ""
+
+msgid "Start and due date"
+msgstr ""
+
+msgid "Start by choosing a group to see how your team is spending time. You can then drill down to the project level."
+msgstr ""
+
+msgid "Start by choosing a group to start exploring the merge requests in that group. You can then proceed to filter by projects, labels, milestones and authors."
+msgstr ""
+
+msgid "Start cleanup"
+msgstr ""
+
+msgid "Start date"
+msgstr ""
+
+msgid "Start merge train"
+msgstr ""
+
+msgid "Start merge train when pipeline succeeds"
+msgstr ""
+
+msgid "Start search"
+msgstr ""
+
+msgid "Start the Runner!"
+msgstr ""
+
+msgid "Start thread"
+msgstr ""
+
+msgid "Start thread & close %{noteable_name}"
+msgstr ""
+
+msgid "Start thread & reopen %{noteable_name}"
+msgstr ""
+
+msgid "Start using Directed Acyclic Graphs (DAG)"
+msgstr ""
+
+msgid "Start your Free Gold Trial"
+msgstr ""
+
+msgid "Start your free trial"
+msgstr ""
+
+msgid "Start your trial"
+msgstr ""
+
+msgid "Started"
+msgstr ""
+
+msgid "Started %{startsIn}"
+msgstr ""
+
+msgid "Started asynchronous removal of all repository check states."
+msgstr ""
+
+msgid "Started:"
+msgstr ""
+
+msgid "Starting..."
+msgstr ""
+
+msgid "Starts %{startsIn}"
+msgstr ""
+
+msgid "Starts at (UTC)"
+msgstr ""
+
+msgid "State your message to activate"
+msgstr ""
+
+msgid "Static Application Security Testing (SAST)"
+msgstr ""
+
+msgid "StaticSiteEditor|An error occurred while submitting your changes."
+msgstr ""
+
+msgid "StaticSiteEditor|Branch could not be created."
+msgstr ""
+
+msgid "StaticSiteEditor|Could not commit the content changes."
+msgstr ""
+
+msgid "StaticSiteEditor|Could not create merge request."
+msgstr ""
+
+msgid "StaticSiteEditor|Incompatible file content"
+msgstr ""
+
+msgid "StaticSiteEditor|Return to site"
+msgstr ""
+
+msgid "StaticSiteEditor|Static site editor"
+msgstr ""
+
+msgid "StaticSiteEditor|Success!"
+msgstr ""
+
+msgid "StaticSiteEditor|Summary of changes"
+msgstr ""
+
+msgid "StaticSiteEditor|The Static Site Editor is currently configured to only edit Markdown content on pages generated from Middleman. Visit the documentation to learn more about configuring your site to use the Static Site Editor."
+msgstr ""
+
+msgid "StaticSiteEditor|Update %{sourcePath} file"
+msgstr ""
+
+msgid "StaticSiteEditor|View documentation"
+msgstr ""
+
+msgid "StaticSiteEditor|View merge request"
+msgstr ""
+
+msgid "StaticSiteEditor|You added a commit:"
+msgstr ""
+
+msgid "StaticSiteEditor|You created a merge request:"
+msgstr ""
+
+msgid "StaticSiteEditor|You created a new branch:"
+msgstr ""
+
+msgid "StaticSiteEditor|Your changes have been submitted and a merge request has been created. The changes won’t be visible on the site until the merge request has been accepted."
+msgstr ""
+
+msgid "Statistics"
+msgstr ""
+
+msgid "Status"
+msgstr ""
+
+msgid "Status:"
+msgstr ""
+
+msgid "Status: %{title}"
+msgstr ""
+
+msgid "StatusPage|AWS Secret access key"
+msgstr ""
+
+msgid "StatusPage|AWS access key ID"
+msgstr ""
+
+msgid "StatusPage|AWS documentation"
+msgstr ""
+
+msgid "StatusPage|AWS region"
+msgstr ""
+
+msgid "StatusPage|Active"
+msgstr ""
+
+msgid "StatusPage|Bucket %{docsLink}"
+msgstr ""
+
+msgid "StatusPage|Configure file storage settings to link issues in this project to an external status page."
+msgstr ""
+
+msgid "StatusPage|For help with configuration, visit %{docsLink}"
+msgstr ""
+
+msgid "StatusPage|S3 Bucket name"
+msgstr ""
+
+msgid "StatusPage|Status page"
+msgstr ""
+
+msgid "StatusPage|Status page URL"
+msgstr ""
+
+msgid "StatusPage|Status page frontend documentation"
+msgstr ""
+
+msgid "StatusPage|To publish incidents to an external status page, GitLab will store a JSON file in your Amazon S3 account in a location accessible to your external status page service. Make sure to also set up %{docsLink}"
+msgstr ""
+
+msgid "StatusPage|configuration documentation"
+msgstr ""
+
+msgid "StatusPage|your status page frontend."
+msgstr ""
+
+msgid "Stay updated about the performance and health of your environment by configuring Prometheus to monitor your deployments."
+msgstr ""
+
+msgid "Still, we recommend keeping a backup saved somewhere. Otherwise, if you ever need it and have lost it, you will need to request GitLab Inc. to send it to you again."
+msgstr ""
+
+msgid "Stop Terminal"
+msgstr ""
+
+msgid "Stop impersonation"
+msgstr ""
+
+msgid "Stop this environment"
+msgstr ""
+
+msgid "Stopped"
+msgstr ""
+
+msgid "Stopping..."
+msgstr ""
+
+msgid "Storage"
+msgstr ""
+
+msgid "Storage nodes for new repositories"
+msgstr ""
+
+msgid "Storage:"
+msgstr ""
+
+msgid "StorageSize|Unknown"
+msgstr ""
+
+msgid "Subgroup milestone"
+msgstr ""
+
+msgid "Subgroup overview"
+msgstr ""
+
+msgid "SubgroupCreationLevel|Allowed to create subgroups"
+msgstr ""
+
+msgid "SubgroupCreationlevel|Allowed to create subgroups"
+msgstr ""
+
+msgid "SubgroupCreationlevel|Maintainers"
+msgstr ""
+
+msgid "SubgroupCreationlevel|Owners"
+msgstr ""
+
+msgid "Subgroups"
+msgstr ""
+
+msgid "Subgroups and projects"
+msgstr ""
+
+msgid "Subject Key Identifier:"
+msgstr ""
+
+msgid "Subkeys"
+msgstr ""
+
+msgid "Submit %{humanized_resource_name}"
+msgstr ""
+
+msgid "Submit Changes"
+msgstr ""
+
+msgid "Submit a review"
+msgstr ""
+
+msgid "Submit as spam"
+msgstr ""
+
+msgid "Submit feedback"
+msgstr ""
+
+msgid "Submit issue"
+msgstr ""
+
+msgid "Submit review"
+msgstr ""
+
+msgid "Submit search"
+msgstr ""
+
+msgid "Submit the current review."
+msgstr ""
+
+msgid "Submitted the current review."
+msgstr ""
+
+msgid "Subscribe"
+msgstr ""
+
+msgid "Subscribe at group level"
+msgstr ""
+
+msgid "Subscribe at project level"
+msgstr ""
+
+msgid "Subscribe to RSS feed"
+msgstr ""
+
+msgid "Subscribe to calendar"
+msgstr ""
+
+msgid "Subscribed"
+msgstr ""
+
+msgid "Subscribed to this %{quick_action_target}."
+msgstr ""
+
+msgid "Subscribes to this %{quick_action_target}."
+msgstr ""
+
+msgid "Subscription"
+msgstr ""
+
+msgid "Subscription deletion failed."
+msgstr ""
+
+msgid "Subscription successfully applied to \"%{group_name}\""
+msgstr ""
+
+msgid "Subscription successfully created."
+msgstr ""
+
+msgid "Subscription successfully deleted."
+msgstr ""
+
+msgid "SubscriptionTable|Billing"
+msgstr ""
+
+msgid "SubscriptionTable|Free"
+msgstr ""
+
+msgid "SubscriptionTable|GitLab allows you to continue using your subscription even if you exceed the number of seats you purchased. You will be required to pay for these seats upon renewal."
+msgstr ""
+
+msgid "SubscriptionTable|Last invoice"
+msgstr ""
+
+msgid "SubscriptionTable|Loading subscriptions"
+msgstr ""
+
+msgid "SubscriptionTable|Manage"
+msgstr ""
+
+msgid "SubscriptionTable|Max seats used"
+msgstr ""
+
+msgid "SubscriptionTable|Next invoice"
+msgstr ""
+
+msgid "SubscriptionTable|Seats currently in use"
+msgstr ""
+
+msgid "SubscriptionTable|Seats in subscription"
+msgstr ""
+
+msgid "SubscriptionTable|Seats owed"
+msgstr ""
+
+msgid "SubscriptionTable|Subscription end date"
+msgstr ""
+
+msgid "SubscriptionTable|Subscription start date"
+msgstr ""
+
+msgid "SubscriptionTable|This is the last time the GitLab.com team was in contact with you to settle any outstanding balances."
+msgstr ""
+
+msgid "SubscriptionTable|This is the maximum number of users that have existed at the same time since this subscription started."
+msgstr ""
+
+msgid "SubscriptionTable|This is the next date when the GitLab.com team is scheduled to get in contact with you to settle any outstanding balances."
+msgstr ""
+
+msgid "SubscriptionTable|This is the number of seats you will be required to purchase if you update to a paid plan."
+msgstr ""
+
+msgid "SubscriptionTable|Trial"
+msgstr ""
+
+msgid "SubscriptionTable|Trial end date"
+msgstr ""
+
+msgid "SubscriptionTable|Trial start date"
+msgstr ""
+
+msgid "SubscriptionTable|Upgrade"
+msgstr ""
+
+msgid "SubscriptionTable|Usage"
+msgstr ""
+
+msgid "SubscriptionTable|Usage count is performed once a day at 12:00 PM."
+msgstr ""
+
+msgid "Subscriptions"
+msgstr ""
+
+msgid "Subtracted"
+msgstr ""
+
+msgid "Subtracts"
+msgstr ""
+
+msgid "Succeeded"
+msgstr ""
+
+msgid "Successfully activated"
+msgstr ""
+
+msgid "Successfully blocked"
+msgstr ""
+
+msgid "Successfully confirmed"
+msgstr ""
+
+msgid "Successfully deactivated"
+msgstr ""
+
+msgid "Successfully deleted U2F device."
+msgstr ""
+
+msgid "Successfully removed email."
+msgstr ""
+
+msgid "Successfully scheduled a pipeline to run. Go to the %{pipelines_link_start}Pipelines page%{pipelines_link_end} for details."
+msgstr ""
+
+msgid "Successfully unblocked"
+msgstr ""
+
+msgid "Successfully unlocked"
+msgstr ""
+
+msgid "Successfully verified domain ownership"
+msgstr ""
+
+msgid "Suggest code changes which can be immediately applied in one click. Try it out!"
+msgstr ""
+
+msgid "Suggested Solutions"
+msgstr ""
+
+msgid "Suggested change"
+msgstr ""
+
+msgid "Suggested solutions help link"
+msgstr ""
+
+msgid "SuggestedColors|Bright green"
+msgstr ""
+
+msgid "SuggestedColors|Dark grayish cyan"
+msgstr ""
+
+msgid "SuggestedColors|Dark moderate blue"
+msgstr ""
+
+msgid "SuggestedColors|Dark moderate orange"
+msgstr ""
+
+msgid "SuggestedColors|Dark moderate pink"
+msgstr ""
+
+msgid "SuggestedColors|Dark moderate violet"
+msgstr ""
+
+msgid "SuggestedColors|Feijoa"
+msgstr ""
+
+msgid "SuggestedColors|Lime green"
+msgstr ""
+
+msgid "SuggestedColors|Moderate blue"
+msgstr ""
+
+msgid "SuggestedColors|Pure red"
+msgstr ""
+
+msgid "SuggestedColors|Slightly desaturated blue"
+msgstr ""
+
+msgid "SuggestedColors|Slightly desaturated green"
+msgstr ""
+
+msgid "SuggestedColors|Soft orange"
+msgstr ""
+
+msgid "SuggestedColors|Soft red"
+msgstr ""
+
+msgid "SuggestedColors|Strong pink"
+msgstr ""
+
+msgid "SuggestedColors|Strong red"
+msgstr ""
+
+msgid "SuggestedColors|Strong yellow"
+msgstr ""
+
+msgid "SuggestedColors|UA blue"
+msgstr ""
+
+msgid "SuggestedColors|Very dark desaturated blue"
+msgstr ""
+
+msgid "SuggestedColors|Very dark lime green"
+msgstr ""
+
+msgid "SuggestedColors|Very pale orange"
+msgstr ""
+
+msgid "Suggestion is not applicable as the suggestion was not found."
+msgstr ""
+
+msgid "Suggestions are not applicable as one or more suggestions were not found."
+msgstr ""
+
+msgid "Suggestions are not applicable as their lines cannot overlap."
+msgstr ""
+
+msgid "Suggestions must all be on the same branch."
+msgstr ""
+
+msgid "Suggestions that change line count can't be added to batches, yet."
+msgstr ""
+
+msgid "Suggestions:"
+msgstr ""
+
+msgid "Suite"
+msgstr ""
+
+msgid "Summary"
+msgstr ""
+
+msgid "Sunday"
+msgstr ""
+
+msgid "Support"
+msgstr ""
+
+msgid "Support for custom certificates is disabled. Ask your system's administrator to enable it."
+msgstr ""
+
+msgid "Support page URL"
+msgstr ""
+
+msgid "Survey Response"
+msgstr ""
+
+msgid "Switch branch/tag"
+msgstr ""
+
+msgid "Switch to GitLab Next"
+msgstr ""
+
+msgid "Switch to the source to copy the file contents"
+msgstr ""
+
+msgid "Sync information"
+msgstr ""
+
+msgid "Synced"
+msgstr ""
+
+msgid "Synchronization disabled"
+msgstr ""
+
+msgid "System"
+msgstr ""
+
+msgid "System Hooks"
+msgstr ""
+
+msgid "System Info"
+msgstr ""
+
+msgid "System default (%{default})"
+msgstr ""
+
+msgid "System header and footer"
+msgstr ""
+
+msgid "System hook was successfully updated."
+msgstr ""
+
+msgid "System metrics (Custom)"
+msgstr ""
+
+msgid "System metrics (Kubernetes)"
+msgstr ""
+
+msgid "Table of Contents"
+msgstr ""
+
+msgid "Tag"
+msgstr ""
+
+msgid "Tag list:"
+msgstr ""
+
+msgid "Tag name"
+msgstr ""
+
+msgid "Tag this commit."
+msgstr ""
+
+msgid "Tagged this commit to %{tag_name} with \"%{message}\"."
+msgstr ""
+
+msgid "Tagged this commit to %{tag_name}."
+msgstr ""
+
+msgid "Tags"
+msgstr ""
+
+msgid "Tags feed"
+msgstr ""
+
+msgid "Tags this commit to %{tag_name} with \"%{message}\"."
+msgstr ""
+
+msgid "Tags this commit to %{tag_name}."
+msgstr ""
+
+msgid "Tags:"
+msgstr ""
+
+msgid "TagsPage|Browse commits"
+msgstr ""
+
+msgid "TagsPage|Browse files"
+msgstr ""
+
+msgid "TagsPage|Can't find HEAD commit for this tag"
+msgstr ""
+
+msgid "TagsPage|Cancel"
+msgstr ""
+
+msgid "TagsPage|Create tag"
+msgstr ""
+
+msgid "TagsPage|Delete tag"
+msgstr ""
+
+msgid "TagsPage|Deleting the %{tag_name} tag cannot be undone. Are you sure?"
+msgstr ""
+
+msgid "TagsPage|Edit release notes"
+msgstr ""
+
+msgid "TagsPage|Existing branch name, tag, or commit SHA"
+msgstr ""
+
+msgid "TagsPage|Filter by tag name"
+msgstr ""
+
+msgid "TagsPage|New Tag"
+msgstr ""
+
+msgid "TagsPage|New tag"
+msgstr ""
+
+msgid "TagsPage|Optionally, add a message to the tag. Leaving this blank creates a %{link_start}lightweight tag.%{link_end}"
+msgstr ""
+
+msgid "TagsPage|Optionally, create a public Release of your project, based on this tag. Release notes are displayed on the %{releases_page_link_start}Releases%{link_end} page. %{docs_link_start}More information%{link_end}"
+msgstr ""
+
+msgid "TagsPage|Release notes"
+msgstr ""
+
+msgid "TagsPage|Repository has no tags yet."
+msgstr ""
+
+msgid "TagsPage|Sort by"
+msgstr ""
+
+msgid "TagsPage|Tags"
+msgstr ""
+
+msgid "TagsPage|Tags give the ability to mark specific points in history as being important"
+msgstr ""
+
+msgid "TagsPage|This tag has no release notes."
+msgstr ""
+
+msgid "TagsPage|Use git tag command to add a new one:"
+msgstr ""
+
+msgid "TagsPage|Write your release notes or drag files here…"
+msgstr ""
+
+msgid "TagsPage|protected"
+msgstr ""
+
+msgid "Target Branch"
+msgstr ""
+
+msgid "Target Path"
+msgstr ""
+
+msgid "Target branch"
+msgstr ""
+
+msgid "Target-Branch"
+msgstr ""
+
+msgid "Team"
+msgstr ""
+
+msgid "Team domain"
+msgstr ""
+
+msgid "Telephone number"
+msgstr ""
+
+msgid "Telephone number (Optional)"
+msgstr ""
+
+msgid "Template"
+msgstr ""
+
+msgid "Template to append to all Service Desk issues"
+msgstr ""
+
+msgid "Template was successfully saved."
+msgstr ""
+
+msgid "Templates"
+msgstr ""
+
+msgid "Terminal"
+msgstr ""
+
+msgid "Terminal for environment"
+msgstr ""
+
+msgid "Terminal sync service is running"
+msgstr ""
+
+msgid "Terms of Service Agreement and Privacy Policy"
+msgstr ""
+
+msgid "Terms of Service and Privacy Policy"
+msgstr ""
+
+msgid "Terraform|%{number} Terraform report failed to generate"
+msgid_plural "Terraform|%{number} Terraform reports failed to generate"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Terraform|%{number} Terraform report was generated in your pipelines"
+msgid_plural "Terraform|%{number} Terraform reports were generated in your pipelines"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Terraform|A Terraform report failed to generate."
+msgstr ""
+
+msgid "Terraform|A Terraform report was generated in your pipelines."
+msgstr ""
+
+msgid "Terraform|Generating the report caused an error."
+msgstr ""
+
+msgid "Terraform|Reported Resource Changes: %{addNum} to add, %{changeNum} to change, %{deleteNum} to delete"
+msgstr ""
+
+msgid "Terraform|The Terraform report %{name} failed to generate."
+msgstr ""
+
+msgid "Terraform|The Terraform report %{name} was generated in your pipelines."
+msgstr ""
+
+msgid "Test"
+msgstr ""
+
+msgid "Test coverage parsing"
+msgstr ""
+
+msgid "Test coverage: %d hit"
+msgid_plural "Test coverage: %d hits"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Test failed."
+msgstr ""
+
+msgid "Test settings and save changes"
+msgstr ""
+
+msgid "TestHooks|Ensure one of your projects has merge requests."
+msgstr ""
+
+msgid "TestHooks|Ensure the project has CI jobs."
+msgstr ""
+
+msgid "TestHooks|Ensure the project has CI pipelines."
+msgstr ""
+
+msgid "TestHooks|Ensure the project has deployments."
+msgstr ""
+
+msgid "TestHooks|Ensure the project has issues."
+msgstr ""
+
+msgid "TestHooks|Ensure the project has merge requests."
+msgstr ""
+
+msgid "TestHooks|Ensure the project has notes."
+msgstr ""
+
+msgid "TestHooks|Ensure the wiki is enabled and has pages."
+msgstr ""
+
+msgid "TestReports|%{count} errors"
+msgstr ""
+
+msgid "TestReports|%{count} failures"
+msgstr ""
+
+msgid "TestReports|%{count} jobs"
+msgstr ""
+
+msgid "TestReports|%{rate}%{sign} success rate"
+msgstr ""
+
+msgid "TestReports|Test suites"
+msgstr ""
+
+msgid "TestReports|Tests"
+msgstr ""
+
+msgid "TestReports|There are no test cases to display."
+msgstr ""
+
+msgid "TestReports|There are no test suites to show."
+msgstr ""
+
+msgid "TestReports|There are no tests to show."
+msgstr ""
+
+msgid "TestReports|There was an error fetching the test reports."
+msgstr ""
+
+msgid "Tests"
+msgstr ""
+
+msgid "Thank you for signing up for your free trial! You will get additional instructions in your inbox shortly."
+msgstr ""
+
+msgid "Thank you for your feedback!"
+msgstr ""
+
+msgid "Thank you for your report. A GitLab administrator will look into it shortly."
+msgstr ""
+
+msgid "Thanks for your purchase!"
+msgstr ""
+
+msgid "Thanks! Don't show me this again"
+msgstr ""
+
+msgid "That's it, well done!%{celebrate}"
+msgstr ""
+
+msgid "The \"%{group_path}\" group allows you to sign in with your Single Sign-On Account"
+msgstr ""
+
+msgid "The \"Require approval from CODEOWNERS\" setting was moved to %{banner_link_start}Protected Branches%{banner_link_end}"
+msgstr ""
+
+msgid "The %{link_start}true-up model%{link_end} allows having more users, and additional users will incur a retroactive charge on renewal."
+msgstr ""
+
+msgid "The %{true_up_link_start}true-up model%{link_end} has a retroactive charge for these users at the next renewal. If you want to update your license sooner to prevent this, %{support_link_start}please contact our Support team%{link_end}."
+msgstr ""
+
+msgid "The %{type} contains the following error:"
+msgid_plural "The %{type} contains the following errors:"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "The Advanced Global Search in GitLab is a powerful search service that saves you time. Instead of creating duplicate code and wasting time, you can now search for code within other teams that can help your own project."
+msgstr ""
+
+msgid "The CSV export will be created in the background. Once finished, it will be sent to <strong>%{email}</strong> in an attachment."
+msgstr ""
+
+msgid "The Git LFS objects will <strong>not</strong> be synced."
+msgstr ""
+
+msgid "The Issue Tracker is the place to add things that need to be improved or solved in a project"
+msgstr ""
+
+msgid "The Issue Tracker is the place to add things that need to be improved or solved in a project. You can register or sign in to create issues for this project."
+msgstr ""
+
+msgid "The Prometheus server responded with \"bad request\". Please check your queries are correct and are supported in your Prometheus version. %{documentationLink}"
+msgstr ""
+
+msgid "The URL defined on the primary node that secondary nodes should use to contact it."
+msgstr ""
+
+msgid "The URL to use for connecting to Elasticsearch. Use a comma-separated list to support clustering (e.g., \"http://localhost:9200, http://localhost:9201\")."
+msgstr ""
+
+msgid "The X509 Certificate to use when mutual TLS is required to communicate with the external authorization service. If left blank, the server certificate is still validated when accessing over HTTPS."
+msgstr ""
+
+msgid "The above settings apply to all projects with the selected compliance framework(s)."
+msgstr ""
+
+msgid "The amount of seconds after which a request to get a secondary node status will time out."
+msgstr ""
+
+msgid "The application will be used where the client secret can be kept confidential. Native mobile apps and Single Page Apps are considered non-confidential."
+msgstr ""
+
+msgid "The associated issue #%{issueId} has been closed as the error is now resolved."
+msgstr ""
+
+msgid "The branch for this project has no active pipeline configuration."
+msgstr ""
+
+msgid "The branch or tag does not exist"
+msgstr ""
+
+msgid "The character highlighter helps you keep the subject line to %{titleLength} characters and wrap the body at %{bodyLength} so they are readable in git."
+msgstr ""
+
+msgid "The coding stage shows the time from the first commit to creating the merge request. The data will automatically be added here once you create your first merge request."
+msgstr ""
+
+msgid "The collection of events added to the data gathered for that stage."
+msgstr ""
+
+msgid "The commit does not exist"
+msgstr ""
+
+msgid "The connection will time out after %{timeout}. For repositories that take longer, use a clone/push combination."
+msgstr ""
+
+msgid "The content of this page is not encoded in UTF-8. Edits can only be made via the Git repository."
+msgstr ""
+
+msgid "The contents of this group, its subgroups and projects will be permanently removed after %{deletion_adjourned_period} days on %{date}. After this point, your data cannot be recovered."
+msgstr ""
+
+msgid "The current issue"
+msgstr ""
+
+msgid "The data source is connected, but there is no data to display. %{documentationLink}"
+msgstr ""
+
+msgid "The default CI configuration path for new projects."
+msgstr ""
+
+msgid "The dependency list details information about the components used within your project."
+msgstr ""
+
+msgid "The deployment of this job to %{environmentLink} did not succeed."
+msgstr ""
+
+msgid "The designs you tried uploading did not change."
+msgstr ""
+
+msgid "The directory has been successfully created."
+msgstr ""
+
+msgid "The domain you entered is misformatted."
+msgstr ""
+
+msgid "The domain you entered is not allowed."
+msgstr ""
+
+msgid "The download link will expire in 24 hours."
+msgstr ""
+
+msgid "The entered user map is not a valid JSON user map."
+msgstr ""
+
+msgid "The errors we encountered were:"
+msgstr ""
+
+msgid "The file has been successfully created."
+msgstr ""
+
+msgid "The file has been successfully deleted."
+msgstr ""
+
+msgid "The file name should have a .yml extension"
+msgstr ""
+
+msgid "The following items will NOT be exported:"
+msgstr ""
+
+msgid "The following items will be exported:"
+msgstr ""
+
+msgid "The following personal access token: %{token_names} was revoked, because a new policy to expire personal access tokens were set."
+msgid_plural "The following personal access tokens: %{token_names} were revoked, because a new policy to expire personal access tokens were set."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "The fork relationship has been removed."
+msgstr ""
+
+msgid "The global settings require you to enable Two-Factor Authentication for your account."
+msgstr ""
+
+msgid "The group and any internal projects can be viewed by any logged in user."
+msgstr ""
+
+msgid "The group and any public projects can be viewed without any authentication."
+msgstr ""
+
+msgid "The group and its projects can only be viewed by members."
+msgstr ""
+
+msgid "The group can be fully restored"
+msgstr ""
+
+msgid "The group export can be downloaded from:"
+msgstr ""
+
+msgid "The group has already been shared with this group"
+msgstr ""
+
+msgid "The group settings for %{group_links} require you to enable Two-Factor Authentication for your account. You can %{leave_group_links}."
+msgstr ""
+
+msgid "The group will be placed in 'pending removal' state"
+msgstr ""
+
+msgid "The import will time out after %{timeout}. For repositories that take longer, use a clone/push combination."
+msgstr ""
+
+msgid "The invitation could not be accepted."
+msgstr ""
+
+msgid "The invitation could not be declined."
+msgstr ""
+
+msgid "The invitation has already been accepted."
+msgstr ""
+
+msgid "The invitation was successfully resent."
+msgstr ""
+
+msgid "The issue stage shows the time it takes from creating an issue to assigning the issue to a milestone, or add the issue to a list on your Issue Board. Begin creating issues to see data for this stage."
+msgstr ""
+
+msgid "The license key is invalid. Make sure it is exactly as you received it from GitLab Inc."
+msgstr ""
+
+msgid "The license was removed. GitLab has fallen back on the previous license."
+msgstr ""
+
+msgid "The license was removed. GitLab now no longer has a valid license."
+msgstr ""
+
+msgid "The license was successfully uploaded and is now active. You can see the details below."
+msgstr ""
+
+msgid "The license was successfully uploaded and will be active from %{starts_at}. You can see the details below."
+msgstr ""
+
+msgid "The maximum file size allowed is %{size}."
+msgstr ""
+
+msgid "The maximum file size allowed is 200KB."
+msgstr ""
+
+msgid "The merge conflicts for this merge request cannot be resolved through GitLab. Please try to resolve them locally."
+msgstr ""
+
+msgid "The merge conflicts for this merge request have already been resolved."
+msgstr ""
+
+msgid "The merge conflicts for this merge request have already been resolved. Please return to the merge request."
+msgstr ""
+
+msgid "The merge request can now be merged."
+msgstr ""
+
+msgid "The name \"%{name}\" is already taken in this directory."
+msgstr ""
+
+msgid "The number of changes to be fetched from GitLab when cloning a repository. This can speed up Pipelines execution. Keep empty or set to 0 to disable shallow clone by default and make GitLab CI fetch all branches and tags each time."
+msgstr ""
+
+msgid "The number of times an upload record could not find its file"
+msgstr ""
+
+msgid "The one place for your designs"
+msgstr ""
+
+msgid "The passphrase required to decrypt the private key. This is optional and the value is encrypted at rest."
+msgstr ""
+
+msgid "The path to the CI configuration file. Defaults to <code>.gitlab-ci.yml</code>"
+msgstr ""
+
+msgid "The phase of the development lifecycle."
+msgstr ""
+
+msgid "The pipeline has been deleted"
+msgstr ""
+
+msgid "The pipelines schedule runs pipelines in the future, repeatedly, for specific branches or tags. Those scheduled pipelines will inherit limited project access based on their associated user."
+msgstr ""
+
+msgid "The planning stage shows the time from the previous step to pushing your first commit. This time will be added automatically once you push your first commit."
+msgstr ""
+
+msgid "The private key to use when a client certificate is provided. This value is encrypted at rest."
+msgstr ""
+
+msgid "The production stage shows the total time it takes between creating an issue and deploying the code to production. The data will be automatically added once you have completed the full idea to production cycle."
+msgstr ""
+
+msgid "The project can be accessed by any logged in user."
+msgstr ""
+
+msgid "The project can be accessed by any user who is logged in."
+msgstr ""
+
+msgid "The project can be accessed by anyone, regardless of authentication."
+msgstr ""
+
+msgid "The project can be accessed without any authentication."
+msgstr ""
+
+msgid "The project is accessible only by members of the project. Access must be granted explicitly to each user."
+msgstr ""
+
+msgid "The project is still being deleted. Please try again later."
+msgstr ""
+
+msgid "The project was successfully forked."
+msgstr ""
+
+msgid "The project was successfully imported."
+msgstr ""
+
+msgid "The pseudonymizer data collection is disabled. When enabled, GitLab will run a background job that will produce pseudonymized CSVs of the GitLab database that will be uploaded to your configured object storage directory."
+msgstr ""
+
+msgid "The remote mirror took to long to complete."
+msgstr ""
+
+msgid "The remote repository is being updated..."
+msgstr ""
+
+msgid "The repository can be commited to, and issues, comments and other entities can be created."
+msgstr ""
+
+msgid "The repository for this project does not exist."
+msgstr ""
+
+msgid "The repository for this project is empty"
+msgstr ""
+
+msgid "The repository is being updated..."
+msgstr ""
+
+msgid "The repository must be accessible over <code>http://</code>, <code>https://</code> or <code>git://</code>."
+msgstr ""
+
+msgid "The repository must be accessible over <code>http://</code>, <code>https://</code>, <code>ssh://</code> or <code>git://</code>."
+msgstr ""
+
+msgid "The review stage shows the time from creating the merge request to merging it. The data will automatically be added after you merge your first merge request."
+msgstr ""
+
+msgid "The roadmap shows the progress of your epics along a timeline"
+msgstr ""
+
+msgid "The schedule time must be in the future!"
+msgstr ""
+
+msgid "The snippet can be accessed without any authentication."
+msgstr ""
+
+msgid "The snippet is visible only to me."
+msgstr ""
+
+msgid "The snippet is visible only to project members."
+msgstr ""
+
+msgid "The snippet is visible to any logged in user."
+msgstr ""
+
+msgid "The specified tab is invalid, please select another"
+msgstr ""
+
+msgid "The staging stage shows the time between merging the MR and deploying code to the production environment. The data will be automatically added once you deploy to production for the first time."
+msgstr ""
+
+msgid "The status of the table below only applies to the default branch and is based on the %{linkStart}latest pipeline%{linkEnd}. Once you've enabled a scan for the default branch, any subsequent feature branch you create will include the scan."
+msgstr ""
+
+msgid "The testing stage shows the time GitLab CI takes to run every pipeline for the related merge request. The data will automatically be added after your first pipeline finishes running."
+msgstr ""
+
+msgid "The time taken by each data entry gathered by that stage."
+msgstr ""
+
+msgid "The total stage shows the time it takes between creating an issue and deploying the code to production. The data will be automatically added once you have completed the full idea to production cycle."
+msgstr ""
+
+msgid "The update action will time out after %{number_of_minutes} minutes. For big repositories, use a clone/push combination."
+msgstr ""
+
+msgid "The uploaded file is not a valid Google Takeout archive."
+msgstr ""
+
+msgid "The usage ping is disabled, and cannot be configured through this form."
+msgstr ""
+
+msgid "The user is being deleted."
+msgstr ""
+
+msgid "The user map has been saved. Continue by selecting the projects you want to import."
+msgstr ""
+
+msgid "The user map is a JSON document mapping the Google Code users that participated on your projects to the way their email addresses and usernames will be imported into GitLab. You can change this by changing the value on the right hand side of <code>:</code>. Be sure to preserve the surrounding double quotes, other punctuation and the email address or username on the left hand side."
+msgstr ""
+
+msgid "The user map is a mapping of the FogBugz users that participated on your projects to the way their email address and usernames will be imported into GitLab. You can change this by populating the table below."
+msgstr ""
+
+msgid "The user you are trying to deactivate has been active in the past %{minimum_inactive_days} days and cannot be deactivated"
+msgstr ""
+
+msgid "The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6."
+msgstr ""
+
+msgid "The vulnerability is no longer detected. Verify the vulnerability has been fixed or removed before changing its status."
+msgstr ""
+
+msgid "The vulnerability is no longer detected. Verify the vulnerability has been remediated before changing its status."
+msgstr ""
+
+msgid "There are no %{replicableTypeName} to show"
+msgstr ""
+
+msgid "There are no GPG keys associated with this account."
+msgstr ""
+
+msgid "There are no GPG keys with access to your account."
+msgstr ""
+
+msgid "There are no SSH keys associated with this account."
+msgstr ""
+
+msgid "There are no SSH keys with access to your account."
+msgstr ""
+
+msgid "There are no archived projects yet"
+msgstr ""
+
+msgid "There are no archived requirements"
+msgstr ""
+
+msgid "There are no changes"
+msgstr ""
+
+msgid "There are no charts configured for this page"
+msgstr ""
+
+msgid "There are no closed issues"
+msgstr ""
+
+msgid "There are no closed merge requests"
+msgstr ""
+
+msgid "There are no custom project templates set up for this GitLab instance. They are enabled from GitLab's Admin Area. Contact your GitLab instance administrator to setup custom project templates."
+msgstr ""
+
+msgid "There are no issues to show"
+msgstr ""
+
+msgid "There are no issues to show."
+msgstr ""
+
+msgid "There are no labels yet"
+msgstr ""
+
+msgid "There are no matching files"
+msgstr ""
+
+msgid "There are no open issues"
+msgstr ""
+
+msgid "There are no open merge requests"
+msgstr ""
+
+msgid "There are no open requirements"
+msgstr ""
+
+msgid "There are no packages yet"
+msgstr ""
+
+msgid "There are no projects shared with this group yet"
+msgstr ""
+
+msgid "There are no variables yet."
+msgstr ""
+
+msgid "There is a limit of %{ci_project_subscriptions_limit} subscriptions from or to a project."
+msgstr ""
+
+msgid "There is already a repository with that name on disk"
+msgstr ""
+
+msgid "There is no data available. Please change your selection."
+msgstr ""
+
+msgid "There was a problem communicating with your device."
+msgstr ""
+
+msgid "There was a problem fetching project branches."
+msgstr ""
+
+msgid "There was a problem fetching project tags."
+msgstr ""
+
+msgid "There was a problem fetching project users."
+msgstr ""
+
+msgid "There was a problem fetching users."
+msgstr ""
+
+msgid "There was a problem refreshing the data, please try again"
+msgstr ""
+
+msgid "There was a problem saving your custom stage, please try again"
+msgstr ""
+
+msgid "There was a problem sending the confirmation email"
+msgstr ""
+
+msgid "There was an error %{message} todo."
+msgstr ""
+
+msgid "There was an error adding a To Do."
+msgstr ""
+
+msgid "There was an error creating the dashboard, branch name is invalid."
+msgstr ""
+
+msgid "There was an error creating the dashboard, branch named: %{branch} already exists."
+msgstr ""
+
+msgid "There was an error creating the issue"
+msgstr ""
+
+msgid "There was an error deleting the To Do."
+msgstr ""
+
+msgid "There was an error fetching configuration for charts"
+msgstr ""
+
+msgid "There was an error fetching data for the selected stage"
+msgstr ""
+
+msgid "There was an error fetching data for the tasks by type chart"
+msgstr ""
+
+msgid "There was an error fetching label data for the selected group"
+msgstr ""
+
+msgid "There was an error fetching median data for stages"
+msgstr ""
+
+msgid "There was an error fetching the %{replicableType}"
+msgstr ""
+
+msgid "There was an error fetching the Geo Settings"
+msgstr ""
+
+msgid "There was an error fetching the Node's Groups"
+msgstr ""
+
+msgid "There was an error fetching the environments information."
+msgstr ""
+
+msgid "There was an error fetching the top labels for the selected group"
+msgstr ""
+
+msgid "There was an error fetching the variables."
+msgstr ""
+
+msgid "There was an error fetching value stream analytics stages."
+msgstr ""
+
+msgid "There was an error gathering the chart data"
+msgstr ""
+
+msgid "There was an error getting the epic participants."
+msgstr ""
+
+msgid "There was an error importing the Jira project."
+msgstr ""
+
+msgid "There was an error loading users activity calendar."
+msgstr ""
+
+msgid "There was an error parsing the data for this graph."
+msgstr ""
+
+msgid "There was an error removing the e-mail."
+msgstr ""
+
+msgid "There was an error removing your custom stage, please try again"
+msgstr ""
+
+msgid "There was an error resetting group pipeline minutes."
+msgstr ""
+
+msgid "There was an error resetting user pipeline minutes."
+msgstr ""
+
+msgid "There was an error saving this Geo Node."
+msgstr ""
+
+msgid "There was an error saving your changes."
+msgstr ""
+
+msgid "There was an error saving your notification settings."
+msgstr ""
+
+msgid "There was an error subscribing to this label."
+msgstr ""
+
+msgid "There was an error syncing project %{name}"
+msgstr ""
+
+msgid "There was an error syncing the %{replicableType}"
+msgstr ""
+
+msgid "There was an error trying to validate your query"
+msgstr ""
+
+msgid "There was an error updating the dashboard, branch name is invalid."
+msgstr ""
+
+msgid "There was an error updating the dashboard, branch named: %{branch} already exists."
+msgstr ""
+
+msgid "There was an error updating the stage order. Please try reloading the page."
+msgstr ""
+
+msgid "There was an error when reseting email token."
+msgstr ""
+
+msgid "There was an error when subscribing to this label."
+msgstr ""
+
+msgid "There was an error when unsubscribing from this label."
+msgstr ""
+
+msgid "There was an error while fetching value stream analytics data."
+msgstr ""
+
+msgid "There was an error while fetching value stream analytics duration data."
+msgstr ""
+
+msgid "There was an error while fetching value stream analytics duration median data."
+msgstr ""
+
+msgid "There was an error while fetching value stream analytics recent activity data."
+msgstr ""
+
+msgid "There was an error while fetching value stream analytics time summary data."
+msgstr ""
+
+msgid "There was an error with the reCAPTCHA. Please solve the reCAPTCHA again."
+msgstr ""
+
+msgid "These existing issues have a similar title. It might be better to comment there instead of creating another similar issue."
+msgstr ""
+
+msgid "These variables are configured in the parent group settings, and will be active in the current project in addition to the project variables."
+msgstr ""
+
+msgid "They can be managed using the %{link}."
+msgstr ""
+
+msgid "Third Party Advisory Link"
+msgstr ""
+
+msgid "Third party offers"
+msgstr ""
+
+msgid "This %{issuableDisplayName} is locked. Only project members can comment."
+msgstr ""
+
+msgid "This %{issuable} is locked. Only <strong>project members</strong> can comment."
+msgstr ""
+
+msgid "This %{noteableTypeText} is %{confidentialLinkStart}confidential%{linkEnd} and %{lockedLinkStart}locked%{linkEnd}."
+msgstr ""
+
+msgid "This %{noteableTypeText} is locked."
+msgstr ""
+
+msgid "This %{viewer} could not be displayed because %{reason}. You can %{options} instead."
+msgstr ""
+
+msgid "This GitLab instance does not provide any shared Runners yet. Instance administrators can register shared Runners in the admin area."
+msgstr ""
+
+msgid "This GitLab instance is licensed at the %{insufficient_license} tier. Geo is only available for users who have at least a Premium license."
+msgstr ""
+
+msgid "This Project is currently archived and read-only. Please unarchive the project first if you want to resume Pull mirroring"
+msgstr ""
+
+msgid "This URL is already used for another link; duplicate URLs are not allowed"
+msgstr ""
+
+msgid "This action can lead to data loss. To prevent accidental actions we ask you to confirm your intention."
+msgstr ""
+
+msgid "This also resolves all related threads"
+msgstr ""
+
+msgid "This also resolves the discussion"
+msgstr ""
+
+msgid "This application was created by %{link_to_owner}."
+msgstr ""
+
+msgid "This application will be able to:"
+msgstr ""
+
+msgid "This attachment has been truncated to avoid exceeding the maximum allowed attachment size of 15MB. %{written_count} of %{issues_count} issues have been included. Consider re-exporting with a narrower selection of issues."
+msgstr ""
+
+msgid "This block is self-referential"
+msgstr ""
+
+msgid "This board's scope is reduced"
+msgstr ""
+
+msgid "This branch has changed since you started editing. Would you like to create a new branch?"
+msgstr ""
+
+msgid "This chart could not be displayed"
+msgstr ""
+
+msgid "This comment has changed since you started editing, please review the %{startTag}updated comment%{endTag} to ensure information is not lost."
+msgstr ""
+
+msgid "This commit is part of merge request %{link_to_merge_request}. Comments created here will be created in the context of that merge request."
+msgstr ""
+
+msgid "This commit was signed with a <strong>verified</strong> signature and the committer email is verified to belong to the same user."
+msgstr ""
+
+msgid "This commit was signed with a different user's verified signature."
+msgstr ""
+
+msgid "This commit was signed with a verified signature, but the committer email is <strong>not verified</strong> to belong to the same user."
+msgstr ""
+
+msgid "This commit was signed with an <strong>unverified</strong> signature."
+msgstr ""
+
+msgid "This content could not be displayed because %{reason}. You can %{options} instead."
+msgstr ""
+
+msgid "This date is after the due date, so this epic won't appear in the roadmap."
+msgstr ""
+
+msgid "This date is before the start date, so this epic won't appear in the roadmap."
+msgstr ""
+
+msgid "This device has already been registered with us."
+msgstr ""
+
+msgid "This device has not been registered with us."
+msgstr ""
+
+msgid "This diff is collapsed."
+msgstr ""
+
+msgid "This diff was suppressed by a .gitattributes entry."
+msgstr ""
+
+msgid "This directory"
+msgstr ""
+
+msgid "This domain is not verified. You will need to verify ownership before access is enabled."
+msgstr ""
+
+msgid "This endpoint has been requested too many times. Try again later."
+msgstr ""
+
+msgid "This environment has no deployments yet."
+msgstr ""
+
+msgid "This environment is being deployed"
+msgstr ""
+
+msgid "This environment is being re-deployed"
+msgstr ""
+
+msgid "This epic already has the maximum number of child epics."
+msgstr ""
+
+msgid "This epic and its child elements will only be visible to team members with at minimum Reporter access."
+msgstr ""
+
+msgid "This epic does not exist or you don't have sufficient permission."
+msgstr ""
+
+msgid "This feature is currently in beta. We invite you to %{linkStart}give feedback%{linkEnd}."
+msgstr ""
+
+msgid "This feature requires local storage to be enabled"
+msgstr ""
+
+msgid "This field is required."
+msgstr ""
+
+msgid "This group"
+msgstr ""
+
+msgid "This group cannot be invited to a project inside a group with enforced SSO"
+msgstr ""
+
+msgid "This group does not provide any group Runners yet."
+msgstr ""
+
+msgid "This group has been scheduled for permanent removal on %{date}"
+msgstr ""
+
+msgid "This group, including all subgroups, projects and git repositories, will only be reachable from the specified IP address range. Multiple addresses are supported with comma delimiters.<br>Example: <code>192.168.0.0/24,192.168.1.0/24</code>. %{read_more_link}."
+msgstr ""
+
+msgid "This group, its subgroups and projects has been scheduled for removal on %{date}."
+msgstr ""
+
+msgid "This group, its subgroups and projects will be removed on %{date} since its parent group '%{parent_group_name}'' has been scheduled for removal."
+msgstr ""
+
+msgid "This is a \"Ghost User\", created to hold all issues authored by users that have since been deleted. This user cannot be removed."
+msgstr ""
+
+msgid "This is a Work in Progress"
+msgstr ""
+
+msgid "This is a confidential %{noteableTypeText}."
+msgstr ""
+
+msgid "This is a delayed job to run in %{remainingTime}"
+msgstr ""
+
+msgid "This is a list of devices that have logged into your account. Revoke any sessions that you do not recognize."
+msgstr ""
+
+msgid "This is a security log of important events involving your account."
+msgstr ""
+
+msgid "This is the author's first Merge Request to this project."
+msgstr ""
+
+msgid "This is the highest peak of users on your installation since the license started."
+msgstr ""
+
+msgid "This is the maximum number of users that have existed at the same time since the license started. This is the minimum number of seats you will need to buy when you renew your license."
+msgstr ""
+
+msgid "This is the number of currently active users on your installation, and this is the minimum number you need to purchase when you renew your license."
+msgstr ""
+
+msgid "This is your current session"
+msgstr ""
+
+msgid "This issue is confidential"
+msgstr ""
+
+msgid "This issue is currently blocked by the following issues: %{issues}."
+msgstr ""
+
+msgid "This issue is in a child epic of the filtered epic"
+msgstr ""
+
+msgid "This job depends on other jobs with expired/erased artifacts: %{invalid_dependencies}"
+msgstr ""
+
+msgid "This job depends on upstream jobs that need to succeed in order for this job to be triggered"
+msgstr ""
+
+msgid "This job does not have a trace."
+msgstr ""
+
+msgid "This job has been canceled"
+msgstr ""
+
+msgid "This job has been skipped"
+msgstr ""
+
+msgid "This job has not been triggered yet"
+msgstr ""
+
+msgid "This job has not started yet"
+msgstr ""
+
+msgid "This job is an out-of-date deployment to %{environmentLink} using cluster %{clusterNameOrLink} and namespace %{kubernetesNamespace}."
+msgstr ""
+
+msgid "This job is an out-of-date deployment to %{environmentLink} using cluster %{clusterNameOrLink} and namespace %{kubernetesNamespace}. View the %{deploymentLink}."
+msgstr ""
+
+msgid "This job is an out-of-date deployment to %{environmentLink} using cluster %{clusterNameOrLink}."
+msgstr ""
+
+msgid "This job is an out-of-date deployment to %{environmentLink} using cluster %{clusterNameOrLink}. View the %{deploymentLink}."
+msgstr ""
+
+msgid "This job is an out-of-date deployment to %{environmentLink}."
+msgstr ""
+
+msgid "This job is an out-of-date deployment to %{environmentLink}. View the %{deploymentLink}."
+msgstr ""
+
+msgid "This job is archived. Only the complete pipeline can be retried."
+msgstr ""
+
+msgid "This job is creating a deployment to %{environmentLink} using cluster %{clusterNameOrLink} and namespace %{kubernetesNamespace}."
+msgstr ""
+
+msgid "This job is creating a deployment to %{environmentLink} using cluster %{clusterNameOrLink} and namespace %{kubernetesNamespace}. This will overwrite the %{deploymentLink}."
+msgstr ""
+
+msgid "This job is creating a deployment to %{environmentLink} using cluster %{clusterNameOrLink}."
+msgstr ""
+
+msgid "This job is creating a deployment to %{environmentLink} using cluster %{clusterNameOrLink}. This will overwrite the %{deploymentLink}."
+msgstr ""
+
+msgid "This job is creating a deployment to %{environmentLink}."
+msgstr ""
+
+msgid "This job is creating a deployment to %{environmentLink}. This will overwrite the %{deploymentLink}."
+msgstr ""
+
+msgid "This job is deployed to %{environmentLink} using cluster %{clusterNameOrLink} and namespace %{kubernetesNamespace}."
+msgstr ""
+
+msgid "This job is deployed to %{environmentLink} using cluster %{clusterNameOrLink}."
+msgstr ""
+
+msgid "This job is deployed to %{environmentLink}."
+msgstr ""
+
+msgid "This job is in pending state and is waiting to be picked by a runner"
+msgstr ""
+
+msgid "This job is performing tasks that must complete before it can start"
+msgstr ""
+
+msgid "This job is preparing to start"
+msgstr ""
+
+msgid "This job is stuck because you don't have any active runners online or available with any of these tags assigned to them:"
+msgstr ""
+
+msgid "This job is stuck because you don't have any active runners that can run this job."
+msgstr ""
+
+msgid "This job is waiting for resource: "
+msgstr ""
+
+msgid "This job requires a manual action"
+msgstr ""
+
+msgid "This job requires manual intervention to start. Before starting this job, you can add variables below for last-minute configuration changes."
+msgstr ""
+
+msgid "This job will automatically run after its timer finishes. Often they are used for incremental roll-out deploys to production environments. When unscheduled it converts into a manual action."
+msgstr ""
+
+msgid "This license has already expired."
+msgstr ""
+
+msgid "This link points to external content"
+msgstr ""
+
+msgid "This may expose confidential information as the selected fork is in another namespace that can have other members."
+msgstr ""
+
+msgid "This means you can not push code until you create an empty repository or import existing one."
+msgstr ""
+
+msgid "This merge request does not have accessibility reports"
+msgstr ""
+
+msgid "This merge request is locked."
+msgstr ""
+
+msgid "This namespace has already been taken! Please choose another one."
+msgstr ""
+
+msgid "This only applies to repository indexing operations."
+msgstr ""
+
+msgid "This option is only available on GitLab.com"
+msgstr ""
+
+msgid "This page is unavailable because you are not allowed to read information across multiple projects."
+msgstr ""
+
+msgid "This page will be removed in a future release."
+msgstr ""
+
+msgid "This pipeline does not use the %{codeStart}needs%{codeEnd} keyword and can't be represented as a directed acyclic graph."
+msgstr ""
+
+msgid "This pipeline makes use of a predefined CI/CD configuration enabled by %{strongStart}Auto DevOps.%{strongEnd}"
+msgstr ""
+
+msgid "This pipeline makes use of a predefined CI/CD configuration enabled by <b>Auto DevOps.</b>"
+msgstr ""
+
+msgid "This pipeline triggered a child pipeline"
+msgstr ""
+
+msgid "This pipeline was triggered by a parent pipeline"
+msgstr ""
+
+msgid "This project"
+msgstr ""
+
+msgid "This project does not belong to a group and can therefore not make use of group Runners."
+msgstr ""
+
+msgid "This project does not have %{service_desk_link_start}Service Desk%{service_desk_link_end} enabled, so the user who created the issue will no longer receive email notifications about new activity."
+msgstr ""
+
+msgid "This project does not have a wiki homepage yet"
+msgstr ""
+
+msgid "This project does not have billing enabled. To create a cluster, <a href=%{linkToBilling} target=\"_blank\" rel=\"noopener noreferrer\">enable billing <i class=\"fa fa-external-link\" aria-hidden=\"true\"></i></a> and try again."
+msgstr ""
+
+msgid "This project has no active access tokens."
+msgstr ""
+
+msgid "This project is archived and cannot be commented on."
+msgstr ""
+
+msgid "This project path either does not exist or you do not have access."
+msgstr ""
+
+msgid "This project will be removed on %{date}"
+msgstr ""
+
+msgid "This project will be removed on %{date} since its parent group '%{parent_group_name}' has been scheduled for removal."
+msgstr ""
+
+msgid "This project will live in your group <strong>%{namespace}</strong>. A project is where you house your files (repository), plan your work (issues), publish your documentation (wiki), and so much more."
+msgstr ""
+
+msgid "This repository"
+msgstr ""
+
+msgid "This repository has never been checked."
+msgstr ""
+
+msgid "This repository is currently empty. A new Auto DevOps pipeline will be created after a new file has been pushed to a branch."
+msgstr ""
+
+msgid "This repository was last checked %{last_check_timestamp}. The check %{strong_start}failed.%{strong_end} See the 'repocheck.log' file for error messages."
+msgstr ""
+
+msgid "This repository was last checked %{last_check_timestamp}. The check passed."
+msgstr ""
+
+msgid "This runner will only run on pipelines triggered on protected branches"
+msgstr ""
+
+msgid "This setting can be overridden in each project."
+msgstr ""
+
+msgid "This setting will update the hostname that is used to generate private commit emails. %{learn_more}"
+msgstr ""
+
+msgid "This subscription is for"
+msgstr ""
+
+msgid "This timeout will take precedence when lower than project-defined timeout and accepts a human readable time input language like \"1 hour\". Values without specification represent seconds."
+msgstr ""
+
+msgid "This user cannot be unlocked manually from GitLab"
+msgstr ""
+
+msgid "This user has no active %{type}."
+msgstr ""
+
+msgid "This user has no identities"
+msgstr ""
+
+msgid "This user will be the author of all events in the activity feed that are the result of an update, like new branches being created or new commits being pushed to existing branches."
+msgstr ""
+
+msgid "This variable can not be masked."
+msgstr ""
+
+msgid "This variable does not match the expected pattern."
+msgstr ""
+
+msgid "This will help us personalize your onboarding experience."
+msgstr ""
+
+msgid "This will redirect you to an external sign in page."
+msgstr ""
+
+msgid "This will remove the fork relationship between this project and %{fork_source}."
+msgstr ""
+
+msgid "This will remove the fork relationship between this project and other projects in the fork network."
+msgstr ""
+
+msgid "Those emails automatically become issues (with the comments becoming the email conversation) listed here."
+msgstr ""
+
+msgid "Thread to reply to cannot be found"
+msgstr ""
+
+msgid "Threat Monitoring"
+msgstr ""
+
+msgid "ThreatMonitoring|Anomalous Requests"
+msgstr ""
+
+msgid "ThreatMonitoring|Application firewall not detected"
+msgstr ""
+
+msgid "ThreatMonitoring|Container Network Policies are not installed or have been disabled. To view this data, ensure your Network Policies are installed and enabled for your cluster."
+msgstr ""
+
+msgid "ThreatMonitoring|Container Network Policy"
+msgstr ""
+
+msgid "ThreatMonitoring|Container NetworkPolicies not detected"
+msgstr ""
+
+msgid "ThreatMonitoring|Dropped Packets"
+msgstr ""
+
+msgid "ThreatMonitoring|Environment"
+msgstr ""
+
+msgid "ThreatMonitoring|No environments detected"
+msgstr ""
+
+msgid "ThreatMonitoring|Operations Per Second"
+msgstr ""
+
+msgid "ThreatMonitoring|Overview"
+msgstr ""
+
+msgid "ThreatMonitoring|Packet Activity"
+msgstr ""
+
+msgid "ThreatMonitoring|Policies"
+msgstr ""
+
+msgid "ThreatMonitoring|Requests"
+msgstr ""
+
+msgid "ThreatMonitoring|Show last"
+msgstr ""
+
+msgid "ThreatMonitoring|Something went wrong, unable to fetch environments"
+msgstr ""
+
+msgid "ThreatMonitoring|Something went wrong, unable to fetch statistics"
+msgstr ""
+
+msgid "ThreatMonitoring|The firewall is not installed or has been disabled. To view this data, ensure the web application firewall is installed and enabled for your cluster."
+msgstr ""
+
+msgid "ThreatMonitoring|The graph below is an overview of traffic coming to your application as tracked by the Web Application Firewall (WAF). View the docs for instructions on how to access the WAF logs to see what type of malicious traffic is trying to access your app. The docs link is also accessible by clicking the \"?\" icon next to the title below."
+msgstr ""
+
+msgid "ThreatMonitoring|Threat Monitoring"
+msgstr ""
+
+msgid "ThreatMonitoring|Threat Monitoring help page link"
+msgstr ""
+
+msgid "ThreatMonitoring|Time"
+msgstr ""
+
+msgid "ThreatMonitoring|To view this data, ensure you have configured an environment for this project and that at least one threat monitoring feature is enabled."
+msgstr ""
+
+msgid "ThreatMonitoring|Total Packets"
+msgstr ""
+
+msgid "ThreatMonitoring|Total Requests"
+msgstr ""
+
+msgid "ThreatMonitoring|View documentation"
+msgstr ""
+
+msgid "ThreatMonitoring|Web Application Firewall"
+msgstr ""
+
+msgid "ThreatMonitoring|While it's rare to have no traffic coming to your application, it can happen. In any event, we ask that you double check your settings to make sure you've set up the WAF correctly."
+msgstr ""
+
+msgid "Thursday"
+msgstr ""
+
+msgid "Time"
+msgstr ""
+
+msgid "Time based: Yes"
+msgstr ""
+
+msgid "Time before an issue gets scheduled"
+msgstr ""
+
+msgid "Time before an issue starts implementation"
+msgstr ""
+
+msgid "Time before enforced"
+msgstr ""
+
+msgid "Time between merge request creation and merge/close"
+msgstr ""
+
+msgid "Time estimate"
+msgstr ""
+
+msgid "Time from first comment to last commit"
+msgstr ""
+
+msgid "Time from first commit until first comment"
+msgstr ""
+
+msgid "Time from last commit to merge"
+msgstr ""
+
+msgid "Time in seconds"
+msgstr ""
+
+msgid "Time in seconds GitLab will wait for a response from the external service. When the service does not respond in time, access will be denied."
+msgstr ""
+
+msgid "Time of import: %{importTime}"
+msgstr ""
+
+msgid "Time remaining"
+msgstr ""
+
+msgid "Time spent"
+msgstr ""
+
+msgid "Time to merge"
+msgstr ""
+
+msgid "Time to subtract exceeds the total time spent"
+msgstr ""
+
+msgid "Time tracking"
+msgstr ""
+
+msgid "Time until first merge request"
+msgstr ""
+
+msgid "TimeTrackingEstimated|Est"
+msgstr ""
+
+msgid "TimeTracking|%{startTag}Spent: %{endTag}%{timeSpentHumanReadable}"
+msgstr ""
+
+msgid "TimeTracking|Estimated:"
+msgstr ""
+
+msgid "TimeTracking|Over by %{timeRemainingHumanReadable}"
+msgstr ""
+
+msgid "TimeTracking|Spent"
+msgstr ""
+
+msgid "TimeTracking|Time remaining: %{timeRemainingHumanReadable}"
+msgstr ""
+
+msgid "Timeago|%s days ago"
+msgstr ""
+
+msgid "Timeago|%s days remaining"
+msgstr ""
+
+msgid "Timeago|%s hours ago"
+msgstr ""
+
+msgid "Timeago|%s hours remaining"
+msgstr ""
+
+msgid "Timeago|%s minutes ago"
+msgstr ""
+
+msgid "Timeago|%s minutes remaining"
+msgstr ""
+
+msgid "Timeago|%s months ago"
+msgstr ""
+
+msgid "Timeago|%s months remaining"
+msgstr ""
+
+msgid "Timeago|%s seconds remaining"
+msgstr ""
+
+msgid "Timeago|%s weeks ago"
+msgstr ""
+
+msgid "Timeago|%s weeks remaining"
+msgstr ""
+
+msgid "Timeago|%s years ago"
+msgstr ""
+
+msgid "Timeago|%s years remaining"
+msgstr ""
+
+msgid "Timeago|1 day ago"
+msgstr ""
+
+msgid "Timeago|1 day remaining"
+msgstr ""
+
+msgid "Timeago|1 hour ago"
+msgstr ""
+
+msgid "Timeago|1 hour remaining"
+msgstr ""
+
+msgid "Timeago|1 minute ago"
+msgstr ""
+
+msgid "Timeago|1 minute remaining"
+msgstr ""
+
+msgid "Timeago|1 month ago"
+msgstr ""
+
+msgid "Timeago|1 month remaining"
+msgstr ""
+
+msgid "Timeago|1 week ago"
+msgstr ""
+
+msgid "Timeago|1 week remaining"
+msgstr ""
+
+msgid "Timeago|1 year ago"
+msgstr ""
+
+msgid "Timeago|1 year remaining"
+msgstr ""
+
+msgid "Timeago|Past due"
+msgstr ""
+
+msgid "Timeago|in %s days"
+msgstr ""
+
+msgid "Timeago|in %s hours"
+msgstr ""
+
+msgid "Timeago|in %s minutes"
+msgstr ""
+
+msgid "Timeago|in %s months"
+msgstr ""
+
+msgid "Timeago|in %s seconds"
+msgstr ""
+
+msgid "Timeago|in %s weeks"
+msgstr ""
+
+msgid "Timeago|in %s years"
+msgstr ""
+
+msgid "Timeago|in 1 day"
+msgstr ""
+
+msgid "Timeago|in 1 hour"
+msgstr ""
+
+msgid "Timeago|in 1 minute"
+msgstr ""
+
+msgid "Timeago|in 1 month"
+msgstr ""
+
+msgid "Timeago|in 1 week"
+msgstr ""
+
+msgid "Timeago|in 1 year"
+msgstr ""
+
+msgid "Timeago|just now"
+msgstr ""
+
+msgid "Timeago|right now"
+msgstr ""
+
+msgid "Timeout"
+msgstr ""
+
+msgid "Timeout connecting to the Google API. Please try again."
+msgstr ""
+
+msgid "Time|hr"
+msgid_plural "Time|hrs"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Time|min"
+msgid_plural "Time|mins"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Time|s"
+msgstr ""
+
+msgid "Tip:"
+msgstr ""
+
+msgid "Title"
+msgstr ""
+
+msgid "Title:"
+msgstr ""
+
+msgid "Titles and Descriptions"
+msgstr ""
+
+msgid "To"
+msgstr ""
+
+msgid "To %{link_to_help} of your domain, add the above key to a TXT record within to your DNS configuration."
+msgstr ""
+
+msgid "To Do"
+msgstr ""
+
+msgid "To GitLab"
+msgstr ""
+
+msgid "To access this domain create a new DNS record"
+msgstr ""
+
+msgid "To add an SSH key you need to %{generate_link_start}generate one%{link_end} or use an %{existing_link_start}existing key%{link_end}."
+msgstr ""
+
+msgid "To add the entry manually, provide the following details to the application on your phone."
+msgstr ""
+
+msgid "To connect GitHub repositories, you can use a %{personal_access_token_link}. When you create your Personal Access Token, you will need to select the <code>repo</code> scope, so we can display a list of your public and private repositories which are available to connect."
+msgstr ""
+
+msgid "To connect GitHub repositories, you first need to authorize GitLab to access the list of your GitHub repositories."
+msgstr ""
+
+msgid "To connect GitHub repositories, you first need to authorize GitLab to access the list of your GitHub repositories:"
+msgstr ""
+
+msgid "To connect an SVN repository, check out %{svn_link}."
+msgstr ""
+
+msgid "To define internal users, first enable new users set to external"
+msgstr ""
+
+msgid "To enable it and see User Cohorts, visit %{application_settings_link_start}application settings%{application_settings_link_end}."
+msgstr ""
+
+msgid "To further protect your account, consider configuring a %{mfa_link_start}two-factor authentication%{mfa_link_end} method."
+msgstr ""
+
+msgid "To further protect your account, consider configuring a two-factor authentication method: %{mfa_link}."
+msgstr ""
+
+msgid "To get started you enter your FogBugz URL and login information below. In the next steps, you'll be able to map users and select the projects you want to import."
+msgstr ""
+
+msgid "To get started, link this page to your Jaeger server, or find out how to %{link_start_tag}install Jaeger%{link_end_tag}"
+msgstr ""
+
+msgid "To get started, please enter your Gitea Host URL and a %{link_to_personal_token}."
+msgstr ""
+
+msgid "To help improve GitLab and its user experience, GitLab will periodically collect usage information."
+msgstr ""
+
+msgid "To help improve GitLab, we would like to periodically collect usage information. This can be changed at any time in %{settings_link_start}Settings%{link_end}. %{info_link_start}More Information%{link_end}"
+msgstr ""
+
+msgid "To import an SVN repository, check out %{svn_link}."
+msgstr ""
+
+msgid "To keep this project going, create a new issue"
+msgstr ""
+
+msgid "To keep this project going, create a new merge request"
+msgstr ""
+
+msgid "To link Sentry to GitLab, enter your Sentry URL and Auth Token."
+msgstr ""
+
+msgid "To move or copy an entire GitLab project from another GitLab installation to this one, navigate to the original project's settings page, generate an export file, and upload it here."
+msgstr ""
+
+msgid "To only use CI/CD features for an external repository, choose <strong>CI/CD for external repo</strong>."
+msgstr ""
+
+msgid "To open Jaeger and easily view tracing from GitLab, link the %{link} page to your server"
+msgstr ""
+
+msgid "To preserve performance only <strong>%{display_size} of %{real_size}</strong> files are displayed."
+msgstr ""
+
+msgid "To protect this issue's confidentiality, %{forkLink} and set the fork's visibility to private."
+msgstr ""
+
+msgid "To protect this issue's confidentiality, a private fork of this project was selected."
+msgstr ""
+
+msgid "To receive alerts from manually configured Prometheus services, add the following URL and Authorization key to your Prometheus webhook config file. Learn more about %{linkStart}configuring Prometheus%{linkEnd} to send alerts to GitLab."
+msgstr ""
+
+msgid "To see all the user's personal access tokens you must impersonate them first."
+msgstr ""
+
+msgid "To see this project's operational details, %{linkStart}upgrade its group plan to Silver%{linkEnd}. You can also remove the project from the dashboard."
+msgstr ""
+
+msgid "To see this project's operational details, contact an owner of group %{groupName} to upgrade the plan. You can also remove the project from the dashboard."
+msgstr ""
+
+msgid "To set up SAML authentication for your group through an identity provider like Azure, Okta, Onelogin, Ping Identity, or your custom SAML 2.0 provider:"
+msgstr ""
+
+msgid "To set up this service:"
+msgstr ""
+
+msgid "To simplify the billing process, GitLab will collect user counts in order to prorate charges for user growth throughout the year using a quarterly reconciliation process."
+msgstr ""
+
+msgid "To specify the notification level per project of a group you belong to, you need to visit project page and change notification level there."
+msgstr ""
+
+msgid "To start serving your jobs you can add Runners to your group"
+msgstr ""
+
+msgid "To start serving your jobs you can either add specific Runners to your project or use shared Runners"
+msgstr ""
+
+msgid "To this GitLab instance"
+msgstr ""
+
+msgid "To view the roadmap, add a start or due date to one of your epics in this group or its subgroups. In the months view, only epics in the past month, current month, and next 5 months are shown."
+msgstr ""
+
+msgid "To widen your search, change or remove filters above"
+msgstr ""
+
+msgid "To widen your search, change or remove filters."
+msgstr ""
+
+msgid "To-Do List"
+msgstr ""
+
+msgid "To-do item successfully marked as done."
+msgstr ""
+
+msgid "Today"
+msgstr ""
+
+msgid "Toggle Markdown preview"
+msgstr ""
+
+msgid "Toggle Sidebar"
+msgstr ""
+
+msgid "Toggle all threads"
+msgstr ""
+
+msgid "Toggle backtrace"
+msgstr ""
+
+msgid "Toggle collapse"
+msgstr ""
+
+msgid "Toggle comments for this file"
+msgstr ""
+
+msgid "Toggle commit description"
+msgstr ""
+
+msgid "Toggle commit list"
+msgstr ""
+
+msgid "Toggle dropdown"
+msgstr ""
+
+msgid "Toggle emoji award"
+msgstr ""
+
+msgid "Toggle navigation"
+msgstr ""
+
+msgid "Toggle project"
+msgstr ""
+
+msgid "Toggle sidebar"
+msgstr ""
+
+msgid "Toggle the Performance Bar"
+msgstr ""
+
+msgid "Toggle this dialog"
+msgstr ""
+
+msgid "Toggle thread"
+msgstr ""
+
+msgid "ToggleButton|Toggle Status: OFF"
+msgstr ""
+
+msgid "ToggleButton|Toggle Status: ON"
+msgstr ""
+
+msgid "Toggled :%{name}: emoji award."
+msgstr ""
+
+msgid "Toggles :%{name}: emoji award."
+msgstr ""
+
+msgid "Tomorrow"
+msgstr ""
+
+msgid "Too many changes to show."
+msgstr ""
+
+msgid "Too many namespaces enabled. You will need to manage them via the console or the API."
+msgstr ""
+
+msgid "Too many projects enabled. You will need to manage them via the console or the API."
+msgstr ""
+
+msgid "Topics (optional)"
+msgstr ""
+
+msgid "Total"
+msgstr ""
+
+msgid "Total Contributions"
+msgstr ""
+
+msgid "Total artifacts size: %{total_size}"
+msgstr ""
+
+msgid "Total cores (CPUs)"
+msgstr ""
+
+msgid "Total issues"
+msgstr ""
+
+msgid "Total memory (GB)"
+msgstr ""
+
+msgid "Total test time for all commits/merges"
+msgstr ""
+
+msgid "Total weight"
+msgstr ""
+
+msgid "Total: %{total}"
+msgstr ""
+
+msgid "Trace"
+msgstr ""
+
+msgid "Tracing"
+msgstr ""
+
+msgid "Track groups of issues that share a theme, across projects and milestones"
+msgstr ""
+
+msgid "Track time with quick actions"
+msgstr ""
+
+msgid "Track your GitLab projects with GitLab for Slack."
+msgstr ""
+
+msgid "Track your project with Audit Events."
+msgstr ""
+
+msgid "Transfer ownership"
+msgstr ""
+
+msgid "Transfer project"
+msgstr ""
+
+msgid "TransferGroup|Cannot transfer group to one of its subgroup."
+msgstr ""
+
+msgid "TransferGroup|Cannot update the path because there are projects under this group that contain Docker images in their Container Registry. Please remove the images from your projects first and try again."
+msgstr ""
+
+msgid "TransferGroup|Database is not supported."
+msgstr ""
+
+msgid "TransferGroup|Group contains projects with NPM packages."
+msgstr ""
+
+msgid "TransferGroup|Group is already a root group."
+msgstr ""
+
+msgid "TransferGroup|Group is already associated to the parent group."
+msgstr ""
+
+msgid "TransferGroup|The parent group already has a subgroup with the same path."
+msgstr ""
+
+msgid "TransferGroup|Transfer failed: %{error_message}"
+msgstr ""
+
+msgid "TransferGroup|You don't have enough permissions."
+msgstr ""
+
+msgid "TransferProject|Cannot move project"
+msgstr ""
+
+msgid "TransferProject|Please select a new namespace for your project."
+msgstr ""
+
+msgid "TransferProject|Project cannot be transferred, because tags are present in its container registry"
+msgstr ""
+
+msgid "TransferProject|Project with same name or path in target namespace already exists"
+msgstr ""
+
+msgid "TransferProject|Root namespace can't be updated if project has NPM packages"
+msgstr ""
+
+msgid "TransferProject|Transfer failed, please contact an admin."
+msgstr ""
+
+msgid "Tree view"
+msgstr ""
+
+msgid "Trending"
+msgstr ""
+
+msgid "Trials|Go back to GitLab"
+msgstr ""
+
+msgid "Trials|Skip Trial (Continue with Free Account)"
+msgstr ""
+
+msgid "Trials|You can always resume this process by selecting your avatar and choosing 'Start a Gold trial'"
+msgstr ""
+
+msgid "Trials|You won't get a free trial right now but you can always resume this process by clicking on your avatar and choosing 'Start a free trial'"
+msgstr ""
+
+msgid "Trigger"
+msgstr ""
+
+msgid "Trigger pipelines for mirror updates"
+msgstr ""
+
+msgid "Trigger pipelines when branches or tags are updated from the upstream repository. Depending on the activity of the upstream repository, this may greatly increase the load on your CI runners. Only enable this if you know they can handle the load."
+msgstr ""
+
+msgid "Trigger removed."
+msgstr ""
+
+msgid "Trigger this manual action"
+msgstr ""
+
+msgid "Trigger token:"
+msgstr ""
+
+msgid "Trigger variables:"
+msgstr ""
+
+msgid "Trigger was created successfully."
+msgstr ""
+
+msgid "Trigger was successfully updated."
+msgstr ""
+
+msgid "Triggerer"
+msgstr ""
+
+msgid "Triggers can force a specific branch or tag to get rebuilt with an API call. These tokens will impersonate their associated user including their access to projects and their project permissions."
+msgstr ""
+
+msgid "Troubleshoot and monitor your application with tracing"
+msgstr ""
+
+msgid "Try again"
+msgstr ""
+
+msgid "Try again?"
+msgstr ""
+
+msgid "Try all GitLab has to offer for 30 days."
+msgstr ""
+
+msgid "Try changing or removing filters."
+msgstr ""
+
+msgid "Try to fork again"
+msgstr ""
+
+msgid "Try to keep the first line under 52 characters and the others under 72."
+msgstr ""
+
+msgid "Try using a different search term to find the file you are looking for."
+msgstr ""
+
+msgid "Trying to communicate with your device. Plug it in (if you haven't already) and press the button on the device now."
+msgstr ""
+
+msgid "Tuesday"
+msgstr ""
+
+msgid "Turn Off"
+msgstr ""
+
+msgid "Turn On"
+msgstr ""
+
+msgid "Turn on Service Desk"
+msgstr ""
+
+msgid "Twitter"
+msgstr ""
+
+msgid "Two-Factor Authentication"
+msgstr ""
+
+msgid "Two-Factor Authentication code"
+msgstr ""
+
+msgid "Two-factor Authentication"
+msgstr ""
+
+msgid "Two-factor Authentication Recovery codes"
+msgstr ""
+
+msgid "Two-factor Authentication has been disabled for this user"
+msgstr ""
+
+msgid "Two-factor authentication"
+msgstr ""
+
+msgid "Type"
+msgstr ""
+
+msgid "Type/State"
+msgstr ""
+
+msgid "U2F Devices (%{length})"
+msgstr ""
+
+msgid "U2F only works with HTTPS-enabled websites. Contact your administrator for more details."
+msgstr ""
+
+msgid "UI Development Kit"
+msgstr ""
+
+msgid "URL"
+msgstr ""
+
+msgid "URL is required"
+msgstr ""
+
+msgid "URL must start with %{codeStart}http://%{codeEnd}, %{codeStart}https://%{codeEnd}, or %{codeStart}ftp://%{codeEnd}"
+msgstr ""
+
+msgid "URL of the external Spam Check endpoint"
+msgstr ""
+
+msgid "URL of the external storage that will serve the repository static objects (e.g. archives, blobs, ...)."
+msgstr ""
+
+msgid "URL or request ID"
+msgstr ""
+
+msgid "UTC"
+msgstr ""
+
+msgid "Unable to apply suggestions to a deleted line."
+msgstr ""
+
+msgid "Unable to build Slack link."
+msgstr ""
+
+msgid "Unable to collect CPU info"
+msgstr ""
+
+msgid "Unable to collect memory info"
+msgstr ""
+
+msgid "Unable to connect to Elasticsearch"
+msgstr ""
+
+msgid "Unable to connect to Prometheus server"
+msgstr ""
+
+msgid "Unable to connect to server: %{error}"
+msgstr ""
+
+msgid "Unable to connect to the Jira instance. Please check your Jira integration configuration."
+msgstr ""
+
+msgid "Unable to convert Kubernetes logs encoding to UTF-8"
+msgstr ""
+
+msgid "Unable to fetch unscanned projects"
+msgstr ""
+
+msgid "Unable to fetch vulnerable projects"
+msgstr ""
+
+msgid "Unable to find Jira project to import data from."
+msgstr ""
+
+msgid "Unable to generate new instance ID"
+msgstr ""
+
+msgid "Unable to load file contents. Try again later."
+msgstr ""
+
+msgid "Unable to load the diff"
+msgstr ""
+
+msgid "Unable to load the diff. %{button_try_again}"
+msgstr ""
+
+msgid "Unable to load the merge request widget. Try reloading the page."
+msgstr ""
+
+msgid "Unable to resolve"
+msgstr ""
+
+msgid "Unable to save iteration. Please try again"
+msgstr ""
+
+msgid "Unable to save your changes. Please try again."
+msgstr ""
+
+msgid "Unable to schedule a pipeline to run immediately"
+msgstr ""
+
+msgid "Unable to sign you in to the group with SAML due to \"%{reason}\""
+msgstr ""
+
+msgid "Unable to suggest a path. Please refresh and try again."
+msgstr ""
+
+msgid "Unable to update label prioritization at this time"
+msgstr ""
+
+msgid "Unable to update this epic at this time."
+msgstr ""
+
+msgid "Unable to update this issue at this time."
+msgstr ""
+
+msgid "Unarchive project"
+msgstr ""
+
+msgid "Unarchiving the project will restore people's ability to make changes to it. The repository can be committed to, and issues, comments, and other entities can be created. %{strong_start}Once active, this project shows up in the search and on the dashboard.%{strong_end}"
+msgstr ""
+
+msgid "Unassign from commenting user"
+msgstr ""
+
+msgid "Unblock"
+msgstr ""
+
+msgid "Undo"
+msgstr ""
+
+msgid "Undo Ignore"
+msgstr ""
+
+msgid "Undo ignore"
+msgstr ""
+
+msgid "Unfortunately, your email message to GitLab could not be processed."
+msgstr ""
+
+msgid "Uninstall"
+msgstr ""
+
+msgid "Uninstalling"
+msgstr ""
+
+msgid "Units|ms"
+msgstr ""
+
+msgid "Units|s"
+msgstr ""
+
+msgid "Unknown"
+msgstr ""
+
+msgid "Unknown Error"
+msgstr ""
+
+msgid "Unknown cache key"
+msgstr ""
+
+msgid "Unknown encryption strategy: %{encrypted_strategy}!"
+msgstr ""
+
+msgid "Unknown format"
+msgstr ""
+
+msgid "Unknown response text"
+msgstr ""
+
+msgid "Unlimited"
+msgstr ""
+
+msgid "Unlock"
+msgstr ""
+
+msgid "Unlock the discussion"
+msgstr ""
+
+msgid "Unlock this %{issuableDisplayName}? <strong>Everyone</strong> will be able to comment."
+msgstr ""
+
+msgid "Unlocked"
+msgstr ""
+
+msgid "Unlocked the discussion."
+msgstr ""
+
+msgid "Unlocks the discussion."
+msgstr ""
+
+msgid "Unmarked this %{noun} as Work In Progress."
+msgstr ""
+
+msgid "Unmarks this %{noun} as Work In Progress."
+msgstr ""
+
+msgid "Unreachable"
+msgstr ""
+
+msgid "Unrecognized cluster type"
+msgstr ""
+
+msgid "Unresolve"
+msgstr ""
+
+msgid "Unresolve thread"
+msgstr ""
+
+msgid "Unresolved"
+msgstr ""
+
+msgid "UnscannedProjects|15 or more days"
+msgstr ""
+
+msgid "UnscannedProjects|30 or more days"
+msgstr ""
+
+msgid "UnscannedProjects|5 or more days"
+msgstr ""
+
+msgid "UnscannedProjects|60 or more days"
+msgstr ""
+
+msgid "UnscannedProjects|Default branch scanning by project"
+msgstr ""
+
+msgid "UnscannedProjects|Out of date"
+msgstr ""
+
+msgid "UnscannedProjects|Project scanning"
+msgstr ""
+
+msgid "UnscannedProjects|Untested"
+msgstr ""
+
+msgid "UnscannedProjects|Your projects are up do date! Nice job!"
+msgstr ""
+
+msgid "Unschedule job"
+msgstr ""
+
+msgid "Unstar"
+msgstr ""
+
+msgid "Unsubscribe"
+msgstr ""
+
+msgid "Unsubscribe at group level"
+msgstr ""
+
+msgid "Unsubscribe at project level"
+msgstr ""
+
+msgid "Unsubscribe from %{type}"
+msgstr ""
+
+msgid "Unsubscribed from this %{quick_action_target}."
+msgstr ""
+
+msgid "Unsubscribes from this %{quick_action_target}."
+msgstr ""
+
+msgid "Unsupported todo type passed. Supported todo types are: %{todo_types}"
+msgstr ""
+
+msgid "Until"
+msgstr ""
+
+msgid "Until that time, the project can be restored."
+msgstr ""
+
+msgid "Unverified"
+msgstr ""
+
+msgid "Up to date"
+msgstr ""
+
+msgid "Upcoming"
+msgstr ""
+
+msgid "Upcoming Release"
+msgstr ""
+
+msgid "Update"
+msgstr ""
+
+msgid "Update all"
+msgstr ""
+
+msgid "Update approval rule"
+msgstr ""
+
+msgid "Update approvers"
+msgstr ""
+
+msgid "Update failed"
+msgstr ""
+
+msgid "Update failed. Please try again."
+msgstr ""
+
+msgid "Update it"
+msgstr ""
+
+msgid "Update iteration"
+msgstr ""
+
+msgid "Update now"
+msgstr ""
+
+msgid "Update variable"
+msgstr ""
+
+msgid "Update your bookmarked URLs as filtered/sorted branches URL has been changed."
+msgstr ""
+
+msgid "Update your group name, description, avatar, and visibility."
+msgstr ""
+
+msgid "Update your project name, topics, description and avatar."
+msgstr ""
+
+msgid "UpdateProject|Cannot rename project because it contains container registry tags!"
+msgstr ""
+
+msgid "UpdateProject|Could not set the default branch"
+msgstr ""
+
+msgid "UpdateProject|New visibility level not allowed!"
+msgstr ""
+
+msgid "UpdateProject|Project could not be updated!"
+msgstr ""
+
+msgid "UpdateRepositoryStorage|Error moving repository storage for %{project_full_path} - %{message}"
+msgstr ""
+
+msgid "UpdateRepositoryStorage|Failed to verify %{type} repository checksum from %{old} to %{new}"
+msgstr ""
+
+msgid "UpdateRepositoryStorage|Timeout waiting for %{type} repository pushes"
+msgstr ""
+
+msgid "Updated"
+msgstr ""
+
+msgid "Updated %{updated_at} by %{updated_by}"
+msgstr ""
+
+msgid "Updated at"
+msgstr ""
+
+msgid "Updated to %{linkStart}chart v%{linkEnd}"
+msgstr ""
+
+msgid "Updating"
+msgstr ""
+
+msgid "Upgrade plan to unlock Canary Deployments feature"
+msgstr ""
+
+msgid "Upgrade your plan to activate Advanced Global Search."
+msgstr ""
+
+msgid "Upgrade your plan to activate Audit Events."
+msgstr ""
+
+msgid "Upgrade your plan to activate Group Webhooks."
+msgstr ""
+
+msgid "Upgrade your plan to improve Issue boards."
+msgstr ""
+
+msgid "Upgrade your plan to improve Merge Requests."
+msgstr ""
+
+msgid "Upload <code>GoogleCodeProjectHosting.json</code> here:"
+msgstr ""
+
+msgid "Upload CSV file"
+msgstr ""
+
+msgid "Upload License"
+msgstr ""
+
+msgid "Upload New File"
+msgstr ""
+
+msgid "Upload New License"
+msgstr ""
+
+msgid "Upload a certificate for your domain with all intermediates"
+msgstr ""
+
+msgid "Upload a private key for your certificate"
+msgstr ""
+
+msgid "Upload file"
+msgstr ""
+
+msgid "Upload object map"
+msgstr ""
+
+msgid "UploadLink|click to upload"
+msgstr ""
+
+msgid "Uploaded on"
+msgstr ""
+
+msgid "Uploaded:"
+msgstr ""
+
+msgid "Uploading changes to terminal"
+msgstr ""
+
+msgid "Uploads"
+msgstr ""
+
+msgid "Upon performing this action, the contents of this group, its subgroup and projects will be permanently removed after %{deletion_adjourned_period} days on <strong>%{date}</strong>. Until that time:"
+msgstr ""
+
+msgid "Upstream"
+msgstr ""
+
+msgid "Uptime"
+msgstr ""
+
+msgid "Upvotes"
+msgstr ""
+
+msgid "Usage"
+msgstr ""
+
+msgid "Usage ping is not enabled"
+msgstr ""
+
+msgid "Usage statistics"
+msgstr ""
+
+msgid "UsageQuota|%{help_link_start}Shared runners%{help_link_end} are disabled, so there are no limits set on pipeline usage"
+msgstr ""
+
+msgid "UsageQuota|Artifacts"
+msgstr ""
+
+msgid "UsageQuota|Build Artifacts"
+msgstr ""
+
+msgid "UsageQuota|Buy additional minutes"
+msgstr ""
+
+msgid "UsageQuota|Current period usage"
+msgstr ""
+
+msgid "UsageQuota|LFS Objects"
+msgstr ""
+
+msgid "UsageQuota|LFS Storage"
+msgstr ""
+
+msgid "UsageQuota|Packages"
+msgstr ""
+
+msgid "UsageQuota|Pipelines"
+msgstr ""
+
+msgid "UsageQuota|Purchase more storage"
+msgstr ""
+
+msgid "UsageQuota|Repositories"
+msgstr ""
+
+msgid "UsageQuota|Repository"
+msgstr ""
+
+msgid "UsageQuota|Snippets"
+msgstr ""
+
+msgid "UsageQuota|Storage"
+msgstr ""
+
+msgid "UsageQuota|This namespace has no projects which use shared runners"
+msgstr ""
+
+msgid "UsageQuota|Unlimited"
+msgstr ""
+
+msgid "UsageQuota|Usage"
+msgstr ""
+
+msgid "UsageQuota|Usage Quotas"
+msgstr ""
+
+msgid "UsageQuota|Usage of group resources across the projects in the %{strong_start}%{group_name}%{strong_end} group"
+msgstr ""
+
+msgid "UsageQuota|Usage of resources across your projects"
+msgstr ""
+
+msgid "UsageQuota|Usage quotas help link"
+msgstr ""
+
+msgid "UsageQuota|Usage since"
+msgstr ""
+
+msgid "UsageQuota|Wiki"
+msgstr ""
+
+msgid "UsageQuota|Wikis"
+msgstr ""
+
+msgid "UsageQuota|You used: %{usage} %{limit}"
+msgstr ""
+
+msgid "UsageQuota|out of %{formattedLimit} of your namespace storage"
+msgstr ""
+
+msgid "Use %{code_start}::%{code_end} to create a %{link_start}scoped label set%{link_end} (eg. %{code_start}priority::1%{code_end})"
+msgstr ""
+
+msgid "Use <code>%{native_redirect_uri}</code> for local tests"
+msgstr ""
+
+msgid "Use Service Desk to connect with your users (e.g. to offer customer support) through email right inside GitLab"
+msgstr ""
+
+msgid "Use a hardware device to add the second factor of authentication."
+msgstr ""
+
+msgid "Use an one time password authenticator on your mobile device or computer to enable two-factor authentication (2FA)."
+msgstr ""
+
+msgid "Use custom color #FF0000"
+msgstr ""
+
+msgid "Use group milestones to manage issues from multiple projects in the same milestone."
+msgstr ""
+
+msgid "Use hashed storage"
+msgstr ""
+
+msgid "Use hashed storage paths for newly created and renamed repositories. Enable immutable, hash-based paths and repository names to store repositories on disk. This prevents repositories from having to be moved or renamed when the Repository URL changes and may improve disk I/O performance. (Always enabled since 13.0)"
+msgstr ""
+
+msgid "Use one line per URI"
+msgstr ""
+
+msgid "Use template"
+msgstr ""
+
+msgid "Use the following registration token during setup:"
+msgstr ""
+
+msgid "Use your global notification setting"
+msgstr ""
+
+msgid "Use your smart card to authenticate with the LDAP server."
+msgstr ""
+
+msgid "Used by members to sign in to your group in GitLab"
+msgstr ""
+
+msgid "Used programming language"
+msgstr ""
+
+msgid "Used to help configure your identity provider"
+msgstr ""
+
+msgid "User"
+msgstr ""
+
+msgid "User %{current_user_username} has started impersonating %{username}"
+msgstr ""
+
+msgid "User %{username} was successfully removed."
+msgstr ""
+
+msgid "User Cohorts are only shown when the %{usage_ping_link_start}usage ping%{usage_ping_link_end} is enabled."
+msgstr ""
+
+msgid "User IDs"
+msgstr ""
+
+msgid "User Lists can only be created and modified with %{linkStart}the API%{linkEnd}"
+msgstr ""
+
+msgid "User OAuth applications"
+msgstr ""
+
+msgid "User Settings"
+msgstr ""
+
+msgid "User and IP Rate Limits"
+msgstr ""
+
+msgid "User identity was successfully created."
+msgstr ""
+
+msgid "User identity was successfully removed."
+msgstr ""
+
+msgid "User identity was successfully updated."
+msgstr ""
+
+msgid "User is not allowed to resolve thread"
+msgstr ""
+
+msgid "User key was successfully removed."
+msgstr ""
+
+msgid "User list %{name} will be removed. Are you sure?"
+msgstr ""
+
+msgid "User map"
+msgstr ""
+
+msgid "User pipeline minutes were successfully reset."
+msgstr ""
+
+msgid "User restrictions"
+msgstr ""
+
+msgid "User was successfully created."
+msgstr ""
+
+msgid "User was successfully removed from group and any subresources."
+msgstr ""
+
+msgid "User was successfully removed from project."
+msgstr ""
+
+msgid "User was successfully updated."
+msgstr ""
+
+msgid "UserList|Delete %{name}?"
+msgstr ""
+
+msgid "UserList|created %{timeago}"
+msgstr ""
+
+msgid "UserProfile|Activity"
+msgstr ""
+
+msgid "UserProfile|Already reported for abuse"
+msgstr ""
+
+msgid "UserProfile|Blocked user"
+msgstr ""
+
+msgid "UserProfile|Contributed projects"
+msgstr ""
+
+msgid "UserProfile|Edit profile"
+msgstr ""
+
+msgid "UserProfile|Explore public groups to find projects to contribute to."
+msgstr ""
+
+msgid "UserProfile|Groups"
+msgstr ""
+
+msgid "UserProfile|Groups are the best way to manage projects and members."
+msgstr ""
+
+msgid "UserProfile|Join or create a group to start contributing by commenting on issues or submitting merge requests!"
+msgstr ""
+
+msgid "UserProfile|Most Recent Activity"
+msgstr ""
+
+msgid "UserProfile|No snippets found."
+msgstr ""
+
+msgid "UserProfile|Overview"
+msgstr ""
+
+msgid "UserProfile|Personal projects"
+msgstr ""
+
+msgid "UserProfile|Report abuse"
+msgstr ""
+
+msgid "UserProfile|Snippets"
+msgstr ""
+
+msgid "UserProfile|Snippets in GitLab can either be private, internal, or public."
+msgstr ""
+
+msgid "UserProfile|Star projects to track their progress and show your appreciation."
+msgstr ""
+
+msgid "UserProfile|Starred projects"
+msgstr ""
+
+msgid "UserProfile|Subscribe"
+msgstr ""
+
+msgid "UserProfile|This user doesn't have any personal projects"
+msgstr ""
+
+msgid "UserProfile|This user has a private profile"
+msgstr ""
+
+msgid "UserProfile|This user hasn't contributed to any projects"
+msgstr ""
+
+msgid "UserProfile|This user hasn't starred any projects"
+msgstr ""
+
+msgid "UserProfile|This user is blocked"
+msgstr ""
+
+msgid "UserProfile|View all"
+msgstr ""
+
+msgid "UserProfile|View user in admin area"
+msgstr ""
+
+msgid "UserProfile|You can create a group for several dependent projects."
+msgstr ""
+
+msgid "UserProfile|You haven't created any personal projects."
+msgstr ""
+
+msgid "UserProfile|You haven't created any snippets."
+msgstr ""
+
+msgid "UserProfile|Your projects can be available publicly, internally, or privately, at your choice."
+msgstr ""
+
+msgid "UserProfile|at"
+msgstr ""
+
+msgid "UserProfile|made a private contribution"
+msgstr ""
+
+msgid "Username (optional)"
+msgstr ""
+
+msgid "Username is already taken."
+msgstr ""
+
+msgid "Username is available."
+msgstr ""
+
+msgid "Username is too long (maximum is %{max_length} characters)."
+msgstr ""
+
+msgid "Username or email"
+msgstr ""
+
+msgid "Users"
+msgstr ""
+
+msgid "Users in License:"
+msgstr ""
+
+msgid "Users or groups set as approvers in the project's or merge request's settings."
+msgstr ""
+
+msgid "Users outside of license"
+msgstr ""
+
+msgid "Users over License:"
+msgstr ""
+
+msgid "Users requesting access to"
+msgstr ""
+
+msgid "Users were successfully added."
+msgstr ""
+
+msgid "Users with a Guest role or those who don't belong to any projects or groups don't count towards seats in use."
+msgstr ""
+
+msgid "UsersSelect|%{name} + %{length} more"
+msgstr ""
+
+msgid "UsersSelect|Any User"
+msgstr ""
+
+msgid "UsersSelect|Assignee"
+msgstr ""
+
+msgid "UsersSelect|No assignee - %{openingTag} assign yourself %{closingTag}"
+msgstr ""
+
+msgid "UsersSelect|Unassigned"
+msgstr ""
+
+msgid "Using %{codeStart}needs%{codeEnd} allows jobs to run before their stage is reached, as soon as their individual dependencies are met, which speeds up your pipelines."
+msgstr ""
+
+msgid "Using %{code_start}::%{code_end} denotes a %{link_start}scoped label set%{link_end}"
+msgstr ""
+
+msgid "Using required encryption strategy when encrypted field is missing!"
+msgstr ""
+
+msgid "Valid from"
+msgstr ""
+
+msgid "Validate"
+msgstr ""
+
+msgid "Validate your GitLab CI configuration file"
+msgstr ""
+
+msgid "Validations failed."
+msgstr ""
+
+msgid "Validity"
+msgstr ""
+
+msgid "Value"
+msgstr ""
+
+msgid "Value Stream"
+msgstr ""
+
+msgid "Value Stream Analytics"
+msgstr ""
+
+msgid "Value Stream Analytics can help you determine your team’s velocity"
+msgstr ""
+
+msgid "Value Stream Analytics gives an overview of how much time it takes to go from idea to production in your project."
+msgstr ""
+
+msgid "ValueStreamAnalytics|%{days}d"
+msgstr ""
+
+msgid "Variable"
+msgstr ""
+
+msgid "Variable will be masked in job logs."
+msgstr ""
+
+msgid "Variables"
+msgstr ""
+
+msgid "Various container registry settings."
+msgstr ""
+
+msgid "Various email settings."
+msgstr ""
+
+msgid "Various localization settings."
+msgstr ""
+
+msgid "Various settings that affect GitLab performance."
+msgstr ""
+
+msgid "Verification capacity"
+msgstr ""
+
+msgid "Verification information"
+msgstr ""
+
+msgid "Verification status"
+msgstr ""
+
+msgid "Verified"
+msgstr ""
+
+msgid "Verify SAML Configuration"
+msgstr ""
+
+msgid "Verify configuration"
+msgstr ""
+
+msgid "Version"
+msgstr ""
+
+msgid "Versions"
+msgstr ""
+
+msgid "View Documentation"
+msgstr ""
+
+msgid "View all issues"
+msgstr ""
+
+msgid "View blame prior to this change"
+msgstr ""
+
+msgid "View chart"
+msgid_plural "View charts"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "View dependency details for your project"
+msgstr ""
+
+msgid "View deployment"
+msgstr ""
+
+msgid "View details"
+msgstr ""
+
+msgid "View details: %{details_url}"
+msgstr ""
+
+msgid "View documentation"
+msgstr ""
+
+msgid "View eligible approvers"
+msgstr ""
+
+msgid "View epics list"
+msgstr ""
+
+msgid "View exposed artifact"
+msgid_plural "View %d exposed artifacts"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "View file @ "
+msgstr ""
+
+msgid "View file @ %{commitSha}"
+msgstr ""
+
+msgid "View full dashboard"
+msgstr ""
+
+msgid "View full log"
+msgstr ""
+
+msgid "View group labels"
+msgstr ""
+
+msgid "View incident issues."
+msgstr ""
+
+msgid "View issue"
+msgstr ""
+
+msgid "View issues"
+msgstr ""
+
+msgid "View it on GitLab"
+msgstr ""
+
+msgid "View job"
+msgstr ""
+
+msgid "View job log"
+msgstr ""
+
+msgid "View jobs"
+msgstr ""
+
+msgid "View labels"
+msgstr ""
+
+msgid "View log"
+msgstr ""
+
+msgid "View open merge request"
+msgstr ""
+
+msgid "View performance dashboard."
+msgstr ""
+
+msgid "View project"
+msgstr ""
+
+msgid "View project labels"
+msgstr ""
+
+msgid "View replaced file @ "
+msgstr ""
+
+msgid "View supported languages and frameworks"
+msgstr ""
+
+msgid "View the documentation"
+msgstr ""
+
+msgid "View the latest successful deployment to this environment"
+msgstr ""
+
+msgid "View the performance dashboard at"
+msgstr ""
+
+msgid "View users statistics"
+msgstr ""
+
+msgid "Viewing commit"
+msgstr ""
+
+msgid "Visibility"
+msgstr ""
+
+msgid "Visibility and access controls"
+msgstr ""
+
+msgid "Visibility level"
+msgstr ""
+
+msgid "Visibility level:"
+msgstr ""
+
+msgid "Visibility settings have been disabled by the administrator."
+msgstr ""
+
+msgid "Visibility, project features, permissions"
+msgstr ""
+
+msgid "Visibility:"
+msgstr ""
+
+msgid "VisibilityLevel|Internal"
+msgstr ""
+
+msgid "VisibilityLevel|Private"
+msgstr ""
+
+msgid "VisibilityLevel|Public"
+msgstr ""
+
+msgid "VisibilityLevel|Unknown"
+msgstr ""
+
+msgid "Visit settings page"
+msgstr ""
+
+msgid "VisualReviewApp|%{stepStart}Step 1%{stepEnd}. Copy the following script:"
+msgstr ""
+
+msgid "VisualReviewApp|%{stepStart}Step 2%{stepEnd}. Add it to the %{headTags} tags of every page of your application, ensuring the merge request ID is set or not set as required. "
+msgstr ""
+
+msgid "VisualReviewApp|%{stepStart}Step 3%{stepEnd}. If not previously %{linkStart}configured%{linkEnd} by a developer, enter the merge request ID for the review when prompted. The ID of this merge request is %{stepStart}%{mrId}%{stepStart}."
+msgstr ""
+
+msgid "VisualReviewApp|%{stepStart}Step 4%{stepEnd}. Leave feedback in the Review App."
+msgstr ""
+
+msgid "VisualReviewApp|Cancel"
+msgstr ""
+
+msgid "VisualReviewApp|Copy merge request ID"
+msgstr ""
+
+msgid "VisualReviewApp|Copy script"
+msgstr ""
+
+msgid "VisualReviewApp|Enable Visual Reviews"
+msgstr ""
+
+msgid "VisualReviewApp|Follow the steps below to enable Visual Reviews inside your application."
+msgstr ""
+
+msgid "VisualReviewApp|No review app found or available."
+msgstr ""
+
+msgid "VisualReviewApp|Open review app"
+msgstr ""
+
+msgid "VisualReviewApp|Review"
+msgstr ""
+
+msgid "VisualReviewApp|Steps 1 and 2 (and sometimes 3) are performed once by the developer before requesting feedback. Steps 3 (if necessary), 4 is performed by the reviewer each time they perform a review."
+msgstr ""
+
+msgid "Vulnerabilities"
+msgstr ""
+
+msgid "Vulnerabilities over time"
+msgstr ""
+
+msgid "Vulnerability remediated. Review before resolving."
+msgstr ""
+
+msgid "Vulnerability resolved in %{branch}"
+msgstr ""
+
+msgid "Vulnerability resolved in the default branch"
+msgstr ""
+
+msgid "Vulnerability-Check"
+msgstr ""
+
+msgid "VulnerabilityChart|%{formattedStartDate} to today"
+msgstr ""
+
+msgid "VulnerabilityChart|Severity"
+msgstr ""
+
+msgid "VulnerabilityManagement|A true-positive and will fix"
+msgstr ""
+
+msgid "VulnerabilityManagement|Change status"
+msgstr ""
+
+msgid "VulnerabilityManagement|Confirm"
+msgstr ""
+
+msgid "VulnerabilityManagement|Confirmed %{timeago} by %{user}"
+msgstr ""
+
+msgid "VulnerabilityManagement|Detected %{timeago} in pipeline %{pipelineLink}"
+msgstr ""
+
+msgid "VulnerabilityManagement|Dismiss"
+msgstr ""
+
+msgid "VulnerabilityManagement|Dismissed %{timeago} by %{user}"
+msgstr ""
+
+msgid "VulnerabilityManagement|Resolved"
+msgstr ""
+
+msgid "VulnerabilityManagement|Resolved %{timeago} by %{user}"
+msgstr ""
+
+msgid "VulnerabilityManagement|Something went wrong while trying to delete the comment. Please try again later."
+msgstr ""
+
+msgid "VulnerabilityManagement|Something went wrong while trying to refresh the vulnerability. Please try again later."
+msgstr ""
+
+msgid "VulnerabilityManagement|Something went wrong while trying to retrieve the vulnerability history. Please try again later."
+msgstr ""
+
+msgid "VulnerabilityManagement|Something went wrong while trying to save the comment. Please try again later."
+msgstr ""
+
+msgid "VulnerabilityManagement|Something went wrong, could not create an issue."
+msgstr ""
+
+msgid "VulnerabilityManagement|Something went wrong, could not get user."
+msgstr ""
+
+msgid "VulnerabilityManagement|Something went wrong, could not update vulnerability state."
+msgstr ""
+
+msgid "VulnerabilityManagement|Verified as fixed or mitigated"
+msgstr ""
+
+msgid "VulnerabilityManagement|Will not fix or a false-positive"
+msgstr ""
+
+msgid "VulnerabilityStatusTypes|All"
+msgstr ""
+
+msgid "VulnerabilityStatusTypes|Confirmed"
+msgstr ""
+
+msgid "VulnerabilityStatusTypes|Detected"
+msgstr ""
+
+msgid "VulnerabilityStatusTypes|Dismissed"
+msgstr ""
+
+msgid "VulnerabilityStatusTypes|Resolved"
+msgstr ""
+
+msgid "Vulnerability|%{scannerName} (version %{scannerVersion})"
+msgstr ""
+
+msgid "Vulnerability|Class"
+msgstr ""
+
+msgid "Vulnerability|Crash Address"
+msgstr ""
+
+msgid "Vulnerability|Description"
+msgstr ""
+
+msgid "Vulnerability|Evidence"
+msgstr ""
+
+msgid "Vulnerability|File"
+msgstr ""
+
+msgid "Vulnerability|Identifiers"
+msgstr ""
+
+msgid "Vulnerability|Image"
+msgstr ""
+
+msgid "Vulnerability|Instances"
+msgstr ""
+
+msgid "Vulnerability|Links"
+msgstr ""
+
+msgid "Vulnerability|Method"
+msgstr ""
+
+msgid "Vulnerability|Namespace"
+msgstr ""
+
+msgid "Vulnerability|Project"
+msgstr ""
+
+msgid "Vulnerability|Scanner Provider"
+msgstr ""
+
+msgid "Vulnerability|Scanner Type"
+msgstr ""
+
+msgid "Vulnerability|Severity"
+msgstr ""
+
+msgid "Vulnerability|Status"
+msgstr ""
+
+msgid "WIP"
+msgstr ""
+
+msgid "Wait for the file to load to copy its contents"
+msgstr ""
+
+msgid "Waiting for merge (open and assigned)"
+msgstr ""
+
+msgid "Waiting for performance data"
+msgstr ""
+
+msgid "Want to see the data? Please ask an administrator for access."
+msgstr ""
+
+msgid "Warning:"
+msgstr ""
+
+msgid "Warning: Displaying this diagram might cause performance issues on this page."
+msgstr ""
+
+msgid "We are currently unable to fetch data for this graph."
+msgstr ""
+
+msgid "We could not determine the path to remove the epic"
+msgstr ""
+
+msgid "We could not determine the path to remove the issue"
+msgstr ""
+
+msgid "We couldn't reach the Prometheus server. Either the server no longer exists or the configuration details need updating."
+msgstr ""
+
+msgid "We detected potential spam in the %{humanized_resource_name}. Please solve the reCAPTCHA to proceed."
+msgstr ""
+
+msgid "We don't have enough data to show this stage."
+msgstr ""
+
+msgid "We have found the following errors:"
+msgstr ""
+
+msgid "We heard back from your device. You have been authenticated."
+msgstr ""
+
+msgid "We recommend that you buy more Pipeline minutes to avoid any interruption of service."
+msgstr ""
+
+msgid "We recommend that you buy more Pipeline minutes to resume normal service."
+msgstr ""
+
+msgid "We sent you an email with reset password instructions"
+msgstr ""
+
+msgid "We tried to automatically renew your %{strong}%{plan_name}%{strong_close} subscription for %{strong}%{namespace_name}%{strong_close} on %{expires_on} but something went wrong so your subscription was downgraded to the free plan. Don't worry, your data is safe. We suggest you check your payment method and get in touch with our support team (%{support_link}). They'll gladly help with your subscription renewal."
+msgstr ""
+
+msgid "We want to be sure it is you, please confirm you are not a robot."
+msgstr ""
+
+msgid "We will automatically renew your %{strong}%{plan_name}%{strong_close} subscription for %{strong}%{namespace_name}%{strong_close} on %{strong}%{expires_on}%{strong_close}. There's nothing that you need to do, we'll let you know when the renewal is complete. Need more seats, a higher plan or just want to review your payment method?"
+msgstr ""
+
+msgid "We've found no vulnerabilities"
+msgstr ""
+
+msgid "Web Application Firewall"
+msgstr ""
+
+msgid "Web IDE"
+msgstr ""
+
+msgid "Web Terminal"
+msgstr ""
+
+msgid "Web terminal"
+msgstr ""
+
+msgid "WebIDE|Merge request"
+msgstr ""
+
+msgid "Webhook"
+msgstr ""
+
+msgid "Webhook Logs"
+msgstr ""
+
+msgid "Webhook Settings"
+msgstr ""
+
+msgid "Webhooks"
+msgstr ""
+
+msgid "Webhooks allow you to trigger a URL if, for example, new code is pushed or a new issue is created. You can configure webhooks to listen for specific events like pushes, issues or merge requests. Group webhooks will apply to all projects in a group, allowing you to standardize webhook functionality across your entire group."
+msgstr ""
+
+msgid "Webhooks have moved. They can now be found under the Settings menu."
+msgstr ""
+
+msgid "Wednesday"
+msgstr ""
+
+msgid "Weekday"
+msgstr ""
+
+msgid "Weeks"
+msgstr ""
+
+msgid "Weight"
+msgstr ""
+
+msgid "Weight %{weight}"
+msgstr ""
+
+msgid "Welcome back! Your account had been deactivated due to inactivity but is now reactivated."
+msgstr ""
+
+msgid "Welcome to GitLab"
+msgstr ""
+
+msgid "Welcome to GitLab %{name}!"
+msgstr ""
+
+msgid "Welcome to GitLab, %{first_name}!"
+msgstr ""
+
+msgid "Welcome to GitLab.com<br>@%{name}!"
+msgstr ""
+
+msgid "Welcome to the guided GitLab tour"
+msgstr ""
+
+msgid "Welcome to your Issue Board!"
+msgstr ""
+
+msgid "What are you searching for?"
+msgstr ""
+
+msgid "What describes you best?"
+msgstr ""
+
+msgid "What’s your experience level?"
+msgstr ""
+
+msgid "When a deployment job is successful, skip older deployment jobs that are still pending"
+msgstr ""
+
+msgid "When a runner is locked, it cannot be assigned to other projects"
+msgstr ""
+
+msgid "When enabled, any user visiting %{host} will be able to create an account."
+msgstr ""
+
+msgid "When enabled, if an NPM package isn't found in the GitLab Registry, we will attempt to pull from the global NPM registry."
+msgstr ""
+
+msgid "When enabled, users cannot use GitLab until the terms have been accepted."
+msgstr ""
+
+msgid "When leaving the URL blank, classification labels can still be specified without disabling cross project features or performing external authorization checks."
+msgstr ""
+
+msgid "When this merge request is accepted"
+msgid_plural "When these merge requests are accepted"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "When using the <code>http://</code> or <code>https://</code> protocols, please provide the exact URL to the repository. HTTP redirects will not be followed."
+msgstr ""
+
+msgid "When:"
+msgstr ""
+
+msgid "While it's rare to have no vulnerabilities, it can happen. In any event, we ask that you please double check your settings to make sure you've set up your dashboard correctly."
+msgstr ""
+
+msgid "Who can be an approver?"
+msgstr ""
+
+msgid "Who can see this group?"
+msgstr ""
+
+msgid "Who will be able to see this group?"
+msgstr ""
+
+msgid "Who will be using GitLab?"
+msgstr ""
+
+msgid "Who will be using this GitLab subscription?"
+msgstr ""
+
+msgid "Who will be using this GitLab trial?"
+msgstr ""
+
+msgid "Wiki"
+msgstr ""
+
+msgid "Wiki was successfully updated."
+msgstr ""
+
+msgid "WikiClone|Clone your wiki"
+msgstr ""
+
+msgid "WikiClone|Git Access"
+msgstr ""
+
+msgid "WikiClone|Install Gollum"
+msgstr ""
+
+msgid "WikiClone|Start Gollum and edit locally"
+msgstr ""
+
+msgid "WikiEditPageTip|Tip: You can move this page by adding the path to the beginning of the title."
+msgstr ""
+
+msgid "WikiEdit|There is already a page with the same title in that path."
+msgstr ""
+
+msgid "WikiEmptyIssueMessage|Suggest wiki improvement"
+msgstr ""
+
+msgid "WikiEmptyIssueMessage|You must be a group member in order to add wiki pages. If you have suggestions for how to improve the wiki for this group, consider opening an issue in the %{issues_link}."
+msgstr ""
+
+msgid "WikiEmptyIssueMessage|You must be a project member in order to add wiki pages. If you have suggestions for how to improve the wiki for this project, consider opening an issue in the %{issues_link}."
+msgstr ""
+
+msgid "WikiEmptyIssueMessage|issue tracker"
+msgstr ""
+
+msgid "WikiEmpty|A wiki is where you can store all the details about your group. This can include why you've created it, its principles, how to use it, and so on."
+msgstr ""
+
+msgid "WikiEmpty|A wiki is where you can store all the details about your project. This can include why you've created it, its principles, how to use it, and so on."
+msgstr ""
+
+msgid "WikiEmpty|Create your first page"
+msgstr ""
+
+msgid "WikiEmpty|Suggest wiki improvement"
+msgstr ""
+
+msgid "WikiEmpty|The wiki lets you write documentation for your group"
+msgstr ""
+
+msgid "WikiEmpty|The wiki lets you write documentation for your project"
+msgstr ""
+
+msgid "WikiEmpty|This group has no wiki pages"
+msgstr ""
+
+msgid "WikiEmpty|This project has no wiki pages"
+msgstr ""
+
+msgid "WikiEmpty|You must be a group member in order to add wiki pages."
+msgstr ""
+
+msgid "WikiEmpty|You must be a project member in order to add wiki pages."
+msgstr ""
+
+msgid "WikiHistoricalPage|This is an old version of this page."
+msgstr ""
+
+msgid "WikiHistoricalPage|You can view the %{most_recent_link} or browse the %{history_link}."
+msgstr ""
+
+msgid "WikiHistoricalPage|history"
+msgstr ""
+
+msgid "WikiHistoricalPage|most recent version"
+msgstr ""
+
+msgid "WikiMarkdownDocs|More examples are in the %{docs_link}"
+msgstr ""
+
+msgid "WikiMarkdownDocs|documentation"
+msgstr ""
+
+msgid "WikiMarkdownTip|To link to a (new) page, simply type <code class=\"js-markup-link-example\">%{link_example}</code>"
+msgstr ""
+
+msgid "WikiNewPageTip|Tip: You can specify the full path for the new file. We will automatically create any missing directories."
+msgstr ""
+
+msgid "WikiPageConfirmDelete|Are you sure you want to delete this page?"
+msgstr ""
+
+msgid "WikiPageConfirmDelete|Delete page"
+msgstr ""
+
+msgid "WikiPageConfirmDelete|Delete page %{pageTitle}?"
+msgstr ""
+
+msgid "WikiPageConflictMessage|Someone edited the page the same time you did. Please check out %{page_link} and make sure your changes will not unintentionally remove theirs."
+msgstr ""
+
+msgid "WikiPageConflictMessage|the page"
+msgstr ""
+
+msgid "WikiPageCreate|Create %{pageTitle}"
+msgstr ""
+
+msgid "WikiPageEdit|Update %{pageTitle}"
+msgstr ""
+
+msgid "WikiPage|Write your content or drag files here…"
+msgstr ""
+
+msgid "Wiki|Create New Page"
+msgstr ""
+
+msgid "Wiki|Create page"
+msgstr ""
+
+msgid "Wiki|Created date"
+msgstr ""
+
+msgid "Wiki|Edit Page"
+msgstr ""
+
+msgid "Wiki|New page"
+msgstr ""
+
+msgid "Wiki|Page history"
+msgstr ""
+
+msgid "Wiki|Page title"
+msgstr ""
+
+msgid "Wiki|Page version"
+msgstr ""
+
+msgid "Wiki|Pages"
+msgstr ""
+
+msgid "Wiki|Title"
+msgstr ""
+
+msgid "Wiki|View All Pages"
+msgstr ""
+
+msgid "Wiki|Wiki Pages"
+msgstr ""
+
+msgid "Will be created"
+msgstr ""
+
+msgid "Will deploy to"
+msgstr ""
+
+msgid "With requirements, you can set criteria to check your products against."
+msgstr ""
+
+msgid "Withdraw Access Request"
+msgstr ""
+
+msgid "Won't fix / Accept risk"
+msgstr ""
+
+msgid "Work in progress (open and unassigned)"
+msgstr ""
+
+msgid "Work in progress Limit"
+msgstr ""
+
+msgid "Write"
+msgstr ""
+
+msgid "Write a comment or drag your files here…"
+msgstr ""
+
+msgid "Write a comment…"
+msgstr ""
+
+msgid "Write access allowed"
+msgstr ""
+
+msgid "Write milestone description..."
+msgstr ""
+
+msgid "Write your release notes or drag your files here…"
+msgstr ""
+
+msgid "Wrong extern UID provided. Make sure Auth0 is configured correctly."
+msgstr ""
+
+msgid "Yes"
+msgstr ""
+
+msgid "Yes or No"
+msgstr ""
+
+msgid "Yes, add it"
+msgstr ""
+
+msgid "Yes, close issue"
+msgstr ""
+
+msgid "Yes, let me map Google Code users to full names or GitLab users."
+msgstr ""
+
+msgid "Yesterday"
+msgstr ""
+
+msgid "You"
+msgstr ""
+
+msgid "You are about to delete %{domain} from your instance. This domain will no longer be available to any Knative application."
+msgstr ""
+
+msgid "You are about to transfer the control of your account to %{group_name} group. This action is NOT reversible, you won't be able to access any of your groups and projects outside of %{group_name} once this transfer is complete."
+msgstr ""
+
+msgid "You are an admin, which means granting access to <strong>%{client_name}</strong> will allow them to interact with GitLab as an admin as well. Proceed with caution."
+msgstr ""
+
+msgid "You are attempting to delete a file that has been previously updated."
+msgstr ""
+
+msgid "You are attempting to update a file that has changed since you started editing it."
+msgstr ""
+
+msgid "You are connected to the Prometheus server, but there is currently no data to display."
+msgstr ""
+
+msgid "You are going to remove %{group_name}, this will also remove all of its subgroups and projects. Removed groups CANNOT be restored! Are you ABSOLUTELY sure?"
+msgstr ""
+
+msgid "You are going to remove %{project_full_name}. Removed project CANNOT be restored! Are you ABSOLUTELY sure?"
+msgstr ""
+
+msgid "You are going to remove the fork relationship from %{project_full_name}. Are you ABSOLUTELY sure?"
+msgstr ""
+
+msgid "You are going to transfer %{project_full_name} to another owner. Are you ABSOLUTELY sure?"
+msgstr ""
+
+msgid "You are not allowed to push into this branch. Create another branch or open a merge request."
+msgstr ""
+
+msgid "You are not allowed to unlink your primary login account"
+msgstr ""
+
+msgid "You are not authorized to perform this action"
+msgstr ""
+
+msgid "You are now impersonating %{username}"
+msgstr ""
+
+msgid "You are on a read-only GitLab instance."
+msgstr ""
+
+msgid "You are receiving this message because you are a GitLab administrator for %{url}."
+msgstr ""
+
+msgid "You are trying to upload something other than an image. Please upload a .png, .jpg, .jpeg, .gif, .bmp, .tiff or .ico."
+msgstr ""
+
+msgid "You can %{linkStart}view the blob%{linkEnd} instead."
+msgstr ""
+
+msgid "You can also create a project from the command line."
+msgstr ""
+
+msgid "You can also press &#8984;-Enter"
+msgstr ""
+
+msgid "You can also press Ctrl-Enter"
+msgstr ""
+
+msgid "You can also star a label to make it a priority label."
+msgstr ""
+
+msgid "You can also test your %{gitlab_ci_yml} in %{lint_link_start}CI Lint%{lint_link_end}"
+msgstr ""
+
+msgid "You can also upload existing files from your computer using the instructions below."
+msgstr ""
+
+msgid "You can also use project access tokens to authenticate against Git over HTTP."
+msgstr ""
+
+msgid "You can always edit this later"
+msgstr ""
+
+msgid "You can apply your Trial to your Personal account or create a New Group."
+msgstr ""
+
+msgid "You can create a new one or check them in your %{pat_link_start}Personal Access Tokens%{pat_link_end} settings"
+msgstr ""
+
+msgid "You can create a new one or check them in your Personal Access Tokens settings %{pat_link}"
+msgstr ""
+
+msgid "You can create new ones at your %{pat_link_start}Personal Access Tokens%{pat_link_end} settings"
+msgstr ""
+
+msgid "You can create new ones at your Personal Access Tokens settings %{pat_link}"
+msgstr ""
+
+msgid "You can easily contribute to them by requesting to join these groups."
+msgstr ""
+
+msgid "You can easily install a Runner on a Kubernetes cluster. %{link_to_help_page}"
+msgstr ""
+
+msgid "You can filter by 'days to merge' by clicking on the columns in the chart."
+msgstr ""
+
+msgid "You can generate an access token scoped to this project for each application to use the GitLab API."
+msgstr ""
+
+msgid "You can get started by cloning the repository or start adding files to it with one of the following options."
+msgstr ""
+
+msgid "You can invite a new member to <strong>%{project_name}</strong> or invite another group."
+msgstr ""
+
+msgid "You can invite a new member to <strong>%{project_name}</strong>."
+msgstr ""
+
+msgid "You can invite another group to <strong>%{project_name}</strong>."
+msgstr ""
+
+msgid "You can move around the graph by using the arrow keys."
+msgstr ""
+
+msgid "You can notify the app / group or a project by sending them an email notification"
+msgstr ""
+
+msgid "You can now export your security dashboard to a CSV report."
+msgstr ""
+
+msgid "You can now manage alert endpoint configuration in the Alerts section on the Operations settings page. Fields on this page have been deprecated."
+msgstr ""
+
+msgid "You can now submit a merge request to get this change into the original branch."
+msgstr ""
+
+msgid "You can now submit a merge request to get this change into the original project."
+msgstr ""
+
+msgid "You can only edit files when you are on a branch"
+msgstr ""
+
+msgid "You can only merge once the items above are resolved."
+msgstr ""
+
+msgid "You can only merge once this merge request is approved."
+msgstr ""
+
+msgid "You can only transfer the project to namespaces you manage."
+msgstr ""
+
+msgid "You can only upload one design when dropping onto an existing design."
+msgstr ""
+
+msgid "You can resolve the merge conflict using either the Interactive mode, by choosing %{use_ours} or %{use_theirs} buttons, or by editing the files directly. Commit these changes into %{branch_name}"
+msgstr ""
+
+msgid "You can see your chat accounts."
+msgstr ""
+
+msgid "You can set up as many Runners as you need to run your jobs."
+msgstr ""
+
+msgid "You can set up jobs to only use Runners with specific tags. Separate tags with commas."
+msgstr ""
+
+msgid "You can specify notification level per group or per project."
+msgstr ""
+
+msgid "You can test your .gitlab-ci.yml in %{linkStart}CI Lint%{linkEnd}."
+msgstr ""
+
+msgid "You can try again using %{begin_link}basic search%{end_link}"
+msgstr ""
+
+msgid "You cannot access the raw file. Please wait a minute."
+msgstr ""
+
+msgid "You cannot impersonate a blocked user"
+msgstr ""
+
+msgid "You cannot impersonate a user who cannot log in"
+msgstr ""
+
+msgid "You cannot impersonate an internal user"
+msgstr ""
+
+msgid "You cannot play this scheduled pipeline at the moment. Please wait a minute."
+msgstr ""
+
+msgid "You cannot write to a read-only secondary GitLab Geo instance. Please use %{link_to_primary_node} instead."
+msgstr ""
+
+msgid "You cannot write to this read-only GitLab instance."
+msgstr ""
+
+msgid "You could not create a new trigger."
+msgstr ""
+
+msgid "You didn't renew your %{strong}%{plan_name}%{strong_close} subscription for %{strong}%{namespace_name}%{strong_close} so it was downgraded to the free plan."
+msgstr ""
+
+msgid "You didn't renew your %{strong}%{plan_name}%{strong_close} subscription so it was downgraded to the GitLab Core Plan."
+msgstr ""
+
+msgid "You do not have an active license"
+msgstr ""
+
+msgid "You do not have any subscriptions yet"
+msgstr ""
+
+msgid "You do not have permission to leave this %{namespaceType}."
+msgstr ""
+
+msgid "You do not have permission to run the Web Terminal. Please contact a project administrator."
+msgstr ""
+
+msgid "You do not have permissions to run the import."
+msgstr ""
+
+msgid "You do not have the correct permissions to override the settings from the LDAP group sync."
+msgstr ""
+
+msgid "You don't have any U2F devices registered yet."
+msgstr ""
+
+msgid "You don't have any active chat names."
+msgstr ""
+
+msgid "You don't have any applications"
+msgstr ""
+
+msgid "You don't have any authorized applications"
+msgstr ""
+
+msgid "You don't have any deployments right now."
+msgstr ""
+
+msgid "You don't have any open merge requests"
+msgstr ""
+
+msgid "You don't have any projects available."
+msgstr ""
+
+msgid "You don't have any recent searches"
+msgstr ""
+
+msgid "You don't have sufficient permission to perform this action."
+msgstr ""
+
+msgid "You don’t have access to Productivity Analytics in this group"
+msgstr ""
+
+msgid "You don’t have access to Value Stream Analytics for this group"
+msgstr ""
+
+msgid "You have a license that activates at a future date. Please see the License History table below."
+msgstr ""
+
+msgid "You have been granted %{access_level} access to the %{source_link} %{source_type}."
+msgstr ""
+
+msgid "You have been granted %{access_level} access to the %{source_name} %{source_type}."
+msgstr ""
+
+msgid "You have been granted %{member_human_access} access to %{label}."
+msgstr ""
+
+msgid "You have been unsubscribed from this thread."
+msgstr ""
+
+msgid "You have declined the invitation to join %{label}."
+msgstr ""
+
+msgid "You have imported from this project %{numberOfPreviousImports} times before. Each new import will create duplicate issues."
+msgstr ""
+
+msgid "You have no permissions"
+msgstr ""
+
+msgid "You have not added any approvers. Start by adding users or groups."
+msgstr ""
+
+msgid "You have reached your project limit"
+msgstr ""
+
+msgid "You have successfully purchased a %{plan} plan subscription for %{seats}. You’ll receive a receipt via email."
+msgstr ""
+
+msgid "You haven't added any issues to your project yet"
+msgstr ""
+
+msgid "You haven't selected any issues yet"
+msgstr ""
+
+msgid "You left the \"%{membershipable_human_name}\" %{source_type}."
+msgstr ""
+
+msgid "You may also add variables that are made available to the running application by prepending the variable key with <code>K8S_SECRET_</code>."
+msgstr ""
+
+msgid "You may close the milestone now."
+msgstr ""
+
+msgid "You must accept our Terms of Service and privacy policy in order to register an account"
+msgstr ""
+
+msgid "You must disassociate %{domain} from all clusters it is attached to before deletion."
+msgstr ""
+
+msgid "You must have maintainer access to force delete a lock"
+msgstr ""
+
+msgid "You must have permission to create a project in a namespace before forking."
+msgstr ""
+
+msgid "You must provide a valid current password"
+msgstr ""
+
+msgid "You must provide your current password in order to change it."
+msgstr ""
+
+msgid "You must select a stack for configuring your cloud provider. Learn more about"
+msgstr ""
+
+msgid "You must set up incoming email before it becomes active."
+msgstr ""
+
+msgid "You must upload a file with the same file name when dropping onto an existing design."
+msgstr ""
+
+msgid "You need a different license to enable FileLocks feature"
+msgstr ""
+
+msgid "You need git-lfs version %{min_git_lfs_version} (or greater) to continue. Please visit https://git-lfs.github.com"
+msgstr ""
+
+msgid "You need permission."
+msgstr ""
+
+msgid "You need to be logged in."
+msgstr ""
+
+msgid "You need to register a two-factor authentication app before you can set up a U2F device."
+msgstr ""
+
+msgid "You need to set terms to be enforced"
+msgstr ""
+
+msgid "You need to specify both an Access Token and a Host URL."
+msgstr ""
+
+msgid "You need to upload a GitLab project export archive (ending in .gz)."
+msgstr ""
+
+msgid "You need to upload a Google Takeout archive."
+msgstr ""
+
+msgid "You reached %{usage_in_percent} of %{namespace_name}'s storage capacity (%{used_storage} of %{storage_limit})"
+msgstr ""
+
+msgid "You tried to fork %{link_to_the_project} but it failed for the following reason:"
+msgstr ""
+
+msgid "You will be removed from existing projects/groups"
+msgstr ""
+
+msgid "You will be the author of all events in the activity feed that are the result of an update, like new branches being created or new commits being pushed to existing branches."
+msgstr ""
+
+msgid "You will first need to set up Jira Integration to use this feature."
+msgstr ""
+
+msgid "You will lose all changes you've made to this file. This action cannot be undone."
+msgstr ""
+
+msgid "You will lose all uncommitted changes you've made in this project. This action cannot be undone."
+msgstr ""
+
+msgid "You will need to update your local repositories to point to the new location."
+msgstr ""
+
+msgid "You will not get any notifications via email"
+msgstr ""
+
+msgid "You will only receive notifications for the events you choose"
+msgstr ""
+
+msgid "You will only receive notifications for threads you have participated in"
+msgstr ""
+
+msgid "You will receive notifications for any activity"
+msgstr ""
+
+msgid "You will receive notifications only for comments in which you were @mentioned"
+msgstr ""
+
+msgid "You won't be able to create new projects because you have reached your project limit."
+msgstr ""
+
+msgid "You won't be able to pull or push project code via %{protocol} until you %{set_password_link} on your account"
+msgstr ""
+
+msgid "You won't be able to pull or push project code via SSH until you add an SSH key to your profile"
+msgstr ""
+
+msgid "You'll be signed out from your current account automatically."
+msgstr ""
+
+msgid "You'll need to use different branch names to get a valid comparison."
+msgstr ""
+
+msgid "You're about to reduce the visibility of the project %{strong_start}%{project_name}%{strong_end} in %{strong_start}%{group_name}%{strong_end}."
+msgstr ""
+
+msgid "You're about to reduce the visibility of the project %{strong_start}%{project_name}%{strong_end}."
+msgstr ""
+
+msgid "You're at the first commit"
+msgstr ""
+
+msgid "You're at the last commit"
+msgstr ""
+
+msgid "You're not allowed to %{tag_start}edit%{tag_end} files in this project directly. Please fork this project, make your changes there, and submit a merge request."
+msgstr ""
+
+msgid "You're not allowed to make changes to this project directly. A fork of this project has been created that you can make changes in, so you can submit a merge request."
+msgstr ""
+
+msgid "You're not allowed to make changes to this project directly. A fork of this project is being created that you can make changes in, so you can submit a merge request."
+msgstr ""
+
+msgid "You're only seeing %{startTag}other activity%{endTag} in the feed. To add a comment, switch to one of the following options."
+msgstr ""
+
+msgid "You're receiving this email because of your account on %{host}."
+msgstr ""
+
+msgid "You're receiving this email because of your account on %{host}. %{manage_notifications_link} &middot; %{help_link}"
+msgstr ""
+
+msgid "You're receiving this email because of your activity on %{host}."
+msgstr ""
+
+msgid "You're receiving this email because you have been assigned an item on %{host}."
+msgstr ""
+
+msgid "You're receiving this email because you have been mentioned on %{host}."
+msgstr ""
+
+msgid "You've already enabled two-factor authentication using one time password authenticators. In order to register a different device, you must first disable two-factor authentication."
+msgstr ""
+
+msgid "YouTube"
+msgstr ""
+
+msgid "Your %{host} account was signed in to from a new location"
+msgstr ""
+
+msgid "Your %{strong}%{plan_name}%{strong_close} subscription for %{strong}%{namespace_name}%{strong_close} will expire on %{strong}%{expires_on}%{strong_close}."
+msgstr ""
+
+msgid "Your %{strong}%{plan_name}%{strong_close} subscription will expire on %{strong}%{expires_on}%{strong_close}. After that, you will not to be able to create issues or merge requests as well as many other features."
+msgstr ""
+
+msgid "Your CSV export has started. It will be emailed to %{email} when complete."
+msgstr ""
+
+msgid "Your CSV export of %{issues_count} from project %{project_link} has been added to this email as an attachment."
+msgstr ""
+
+msgid "Your CSV export of %{written_count} from project %{project_name} (%{project_url}) has been added to this email as an attachment."
+msgstr ""
+
+msgid "Your Commit Email will be used for web based operations, such as edits and merges."
+msgstr ""
+
+msgid "Your Default Notification Email will be used for account notifications if a %{openingTag}group-specific email address%{closingTag} is not set."
+msgstr ""
+
+msgid "Your DevOps Score gives an overview of how you are using GitLab from a feature perspective. View how you compare with other organizations, discover features you are not using, and learn best practices through blog posts and white papers."
+msgstr ""
+
+msgid "Your GPG keys (%{count})"
+msgstr ""
+
+msgid "Your GitLab group"
+msgstr ""
+
+msgid "Your Gitlab Gold trial will last 30 days after which point you can keep your free Gitlab account forever. We just need some additional information to activate your trial."
+msgstr ""
+
+msgid "Your Groups"
+msgstr ""
+
+msgid "Your License"
+msgstr ""
+
+msgid "Your Personal Access Tokens will expire in %{days_to_expire} days or less"
+msgstr ""
+
+msgid "Your Primary Email will be used for avatar detection."
+msgstr ""
+
+msgid "Your Projects (default)"
+msgstr ""
+
+msgid "Your Projects' Activity"
+msgstr ""
+
+msgid "Your Public Email will be displayed on your public profile."
+msgstr ""
+
+msgid "Your SSH keys (%{count})"
+msgstr ""
+
+msgid "Your To-Do List"
+msgstr ""
+
+msgid "Your U2F device did not send a valid JSON response."
+msgstr ""
+
+msgid "Your U2F device needs to be set up. Plug it in (if not already) and click the button on the left."
+msgstr ""
+
+msgid "Your U2F device was registered!"
+msgstr ""
+
+msgid "Your access request to the %{source_type} has been withdrawn."
+msgstr ""
+
+msgid "Your account has been deactivated by your administrator. Please log back in to reactivate your account."
+msgstr ""
+
+msgid "Your account is locked."
+msgstr ""
+
+msgid "Your account uses dedicated credentials for the \"%{group_name}\" group and can only be updated through SSO."
+msgstr ""
+
+msgid "Your applications (%{size})"
+msgstr ""
+
+msgid "Your authorized applications"
+msgstr ""
+
+msgid "Your browser doesn't support U2F. Please use Google Chrome desktop (version 41 or newer)."
+msgstr ""
+
+msgid "Your changes can be committed to %{branch_name} because a merge request is open."
+msgstr ""
+
+msgid "Your changes have been committed. Commit %{commitId} %{commitStats}"
+msgstr ""
+
+msgid "Your changes have been saved"
+msgstr ""
+
+msgid "Your changes have been successfully committed."
+msgstr ""
+
+msgid "Your comment could not be submitted because %{error}"
+msgstr ""
+
+msgid "Your comment could not be submitted! Please check your network connection and try again."
+msgstr ""
+
+msgid "Your comment could not be updated! Please check your network connection and try again."
+msgstr ""
+
+msgid "Your comment will be discarded."
+msgstr ""
+
+msgid "Your custom stage '%{title}' was created"
+msgstr ""
+
+msgid "Your dashboard has been copied. You can %{web_ide_link_start}edit it here%{web_ide_link_end}."
+msgstr ""
+
+msgid "Your dashboard has been updated. You can %{web_ide_link_start}edit it here%{web_ide_link_end}."
+msgstr ""
+
+msgid "Your deployment services will be broken, you will need to manually fix the services after renaming."
+msgstr ""
+
+msgid "Your device was successfully set up! Give it a name and register it with the GitLab server."
+msgstr ""
+
+msgid "Your first project"
+msgstr ""
+
+msgid "Your groups"
+msgstr ""
+
+msgid "Your instance has %{remaining_user_count} users remaining of the %{total_user_count} included in your subscription. You can add more users than the number included in your license, and we will include the overage in your next bill."
+msgstr ""
+
+msgid "Your instance is approaching its licensed user count"
+msgstr ""
+
+msgid "Your issues are being imported. Once finished, you'll get a confirmation email."
+msgstr ""
+
+msgid "Your issues will be imported in the background. Once finished, you'll get a confirmation email."
+msgstr ""
+
+msgid "Your license is valid from"
+msgstr ""
+
+msgid "Your license will be included in your GitLab backup and will survive upgrades, so in normal usage you should never need to re-upload your <code>.gitlab-license</code> file."
+msgstr ""
+
+msgid "Your message here"
+msgstr ""
+
+msgid "Your name"
+msgstr ""
+
+msgid "Your new %{type}"
+msgstr ""
+
+msgid "Your new SCIM token"
+msgstr ""
+
+msgid "Your new personal access token has been created."
+msgstr ""
+
+msgid "Your new project access token has been created."
+msgstr ""
+
+msgid "Your password isn't required to view this page. If a password or any other personal details are requested, please contact your administrator to report abuse."
+msgstr ""
+
+msgid "Your password reset token has expired."
+msgstr ""
+
+msgid "Your profile"
+msgstr ""
+
+msgid "Your project limit is %{limit} projects! Please contact your administrator to increase it"
+msgstr ""
+
+msgid "Your projects"
+msgstr ""
+
+msgid "Your request for access could not be processed: %{error_meesage}"
+msgstr ""
+
+msgid "Your request for access has been queued for review."
+msgstr ""
+
+msgid "Your response has been recorded."
+msgstr ""
+
+msgid "Your search didn't match any commits."
+msgstr ""
+
+msgid "Your subscription expired!"
+msgstr ""
+
+msgid "Your subscription has been downgraded."
+msgstr ""
+
+msgid "Your subscription will automatically renew in %{remaining_days}."
+msgstr ""
+
+msgid "Your subscription will expire in %{remaining_days}."
+msgstr ""
+
+msgid "Zoom meeting added"
+msgstr ""
+
+msgid "Zoom meeting removed"
+msgstr ""
+
+msgid "[No reason]"
+msgstr ""
+
+msgid "a deleted user"
+msgstr ""
+
+msgid "a design"
+msgstr ""
+
+msgid "about 1 hour"
+msgid_plural "about %d hours"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "activated"
+msgstr ""
+
+msgid "added %{created_at_timeago}"
+msgstr ""
+
+msgid "added a Zoom call to this issue"
+msgstr ""
+
+msgid "ago"
+msgstr ""
+
+msgid "alert"
+msgstr ""
+
+msgid "allowed to fail"
+msgstr ""
+
+msgid "already being used for another group or project %{timebox_name}."
+msgstr ""
+
+msgid "already has a \"created\" issue link"
+msgstr ""
+
+msgid "already shared with this group"
+msgstr ""
+
+msgid "among other things"
+msgstr ""
+
+msgid "any-approver for the merge request already exists"
+msgstr ""
+
+msgid "any-approver for the project already exists"
+msgstr ""
+
+msgid "archived"
+msgstr ""
+
+msgid "assign yourself"
+msgstr ""
+
+msgid "at risk"
+msgstr ""
+
+msgid "attach a new file"
+msgstr ""
+
+msgid "authored"
+msgstr ""
+
+msgid "blocks"
+msgstr ""
+
+msgid "branch name"
+msgstr ""
+
+msgid "by"
+msgstr ""
+
+msgid "by %{user}"
+msgstr ""
+
+msgid "cannot be changed if a personal project has container registry tags."
+msgstr ""
+
+msgid "cannot be enabled unless all domains have TLS certificates"
+msgstr ""
+
+msgid "cannot be modified"
+msgstr ""
+
+msgid "cannot block others"
+msgstr ""
+
+msgid "cannot contain HTML/XML tags, including any word between angle brackets (<,>)."
+msgstr ""
+
+msgid "cannot include leading slash or directory traversal."
+msgstr ""
+
+msgid "cannot itself be blocked"
+msgstr ""
+
+msgid "cannot merge"
+msgstr ""
+
+msgid "ciReport|%{linkStartTag}Learn more about Container Scanning %{linkEndTag}"
+msgstr ""
+
+msgid "ciReport|%{linkStartTag}Learn more about DAST %{linkEndTag}"
+msgstr ""
+
+msgid "ciReport|%{linkStartTag}Learn more about Dependency Scanning %{linkEndTag}"
+msgstr ""
+
+msgid "ciReport|%{linkStartTag}Learn more about SAST %{linkEndTag}"
+msgstr ""
+
+msgid "ciReport|%{linkStartTag}Learn more about Secret Scanning %{linkEndTag}"
+msgstr ""
+
+msgid "ciReport|%{linkStartTag}Learn more about codequality reports %{linkEndTag}"
+msgstr ""
+
+msgid "ciReport|%{remainingPackagesCount} more"
+msgstr ""
+
+msgid "ciReport|%{reportType} is loading"
+msgstr ""
+
+msgid "ciReport|%{reportType}: Loading resulted in an error"
+msgstr ""
+
+msgid "ciReport|(errors when loading results)"
+msgstr ""
+
+msgid "ciReport|(is loading)"
+msgstr ""
+
+msgid "ciReport|(is loading, errors when loading results)"
+msgstr ""
+
+msgid "ciReport|All projects"
+msgstr ""
+
+msgid "ciReport|All scanner types"
+msgstr ""
+
+msgid "ciReport|All severities"
+msgstr ""
+
+msgid "ciReport|Automatically apply the patch in a new branch"
+msgstr ""
+
+msgid "ciReport|Base pipeline codequality artifact not found"
+msgstr ""
+
+msgid "ciReport|Code quality"
+msgstr ""
+
+msgid "ciReport|Container Scanning"
+msgstr ""
+
+msgid "ciReport|Container scanning"
+msgstr ""
+
+msgid "ciReport|Container scanning detects known vulnerabilities in your docker images."
+msgstr ""
+
+msgid "ciReport|Coverage Fuzzing"
+msgstr ""
+
+msgid "ciReport|Create a merge request to implement this solution, or download and apply the patch manually."
+msgstr ""
+
+msgid "ciReport|Create issue"
+msgstr ""
+
+msgid "ciReport|DAST"
+msgstr ""
+
+msgid "ciReport|Dependency Scanning"
+msgstr ""
+
+msgid "ciReport|Dependency Scanning detects known vulnerabilities in your source code's dependencies."
+msgstr ""
+
+msgid "ciReport|Dependency scanning"
+msgstr ""
+
+msgid "ciReport|Download patch to resolve"
+msgstr ""
+
+msgid "ciReport|Download the patch to apply it manually"
+msgstr ""
+
+msgid "ciReport|Dynamic Application Security Testing (DAST) detects known vulnerabilities in your web application."
+msgstr ""
+
+msgid "ciReport|Failed to load %{reportName} report"
+msgstr ""
+
+msgid "ciReport|Fixed"
+msgstr ""
+
+msgid "ciReport|Fixed:"
+msgstr ""
+
+msgid "ciReport|Found %{issuesWithCount}"
+msgstr ""
+
+msgid "ciReport|Investigate this vulnerability by creating an issue"
+msgstr ""
+
+msgid "ciReport|Learn more about interacting with security reports"
+msgstr ""
+
+msgid "ciReport|Loading %{reportName} report"
+msgstr ""
+
+msgid "ciReport|Manage licenses"
+msgstr ""
+
+msgid "ciReport|New"
+msgstr ""
+
+msgid "ciReport|No changes to code quality"
+msgstr ""
+
+msgid "ciReport|No changes to performance metrics"
+msgstr ""
+
+msgid "ciReport|No code quality issues found"
+msgstr ""
+
+msgid "ciReport|Performance metrics"
+msgstr ""
+
+msgid "ciReport|Resolve with merge request"
+msgstr ""
+
+msgid "ciReport|SAST"
+msgstr ""
+
+msgid "ciReport|Secret Detection"
+msgstr ""
+
+msgid "ciReport|Secret scanning"
+msgstr ""
+
+msgid "ciReport|Secret scanning detects secrets and credentials vulnerabilities in your source code."
+msgstr ""
+
+msgid "ciReport|Security scanning"
+msgstr ""
+
+msgid "ciReport|Security scanning failed loading any results"
+msgstr ""
+
+msgid "ciReport|Solution"
+msgstr ""
+
+msgid "ciReport|Static Application Security Testing (SAST) detects known vulnerabilities in your source code."
+msgstr ""
+
+msgid "ciReport|There was an error creating the issue. Please try again."
+msgstr ""
+
+msgid "ciReport|There was an error creating the merge request. Please try again."
+msgstr ""
+
+msgid "ciReport|There was an error dismissing the vulnerability. Please try again."
+msgstr ""
+
+msgid "ciReport|There was an error fetching the codequality report."
+msgstr ""
+
+msgid "ciReport|There was an error reverting the dismissal. Please try again."
+msgstr ""
+
+msgid "ciReport|Used by %{packagesString}"
+msgid_plural "ciReport|Used by %{packagesString}, and %{lastPackage}"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "ciReport|View full report"
+msgstr ""
+
+msgid "closed issue"
+msgstr ""
+
+msgid "comment"
+msgstr ""
+
+msgid "commented on %{link_to_project}"
+msgstr ""
+
+msgid "commit %{commit_id}"
+msgstr ""
+
+msgid "committed"
+msgstr ""
+
+msgid "confidentiality|You are going to turn off the confidentiality. This means <strong>everyone</strong> will be able to see and leave a comment on this issue."
+msgstr ""
+
+msgid "confidentiality|You are going to turn on the confidentiality. This means that only team members with <strong>at least Reporter access</strong> are able to see and leave comments on the issue."
+msgstr ""
+
+msgid "connecting"
+msgstr ""
+
+msgid "container_name can contain only lowercase letters, digits, '-', and '.' and must start and end with an alphanumeric character"
+msgstr ""
+
+msgid "container_name cannot be larger than %{max_length} chars"
+msgstr ""
+
+msgid "could not read private key, is the passphrase correct?"
+msgstr ""
+
+msgid "created"
+msgstr ""
+
+msgid "created %{timeAgo}"
+msgstr ""
+
+msgid "customize"
+msgstr ""
+
+msgid "data"
+msgstr ""
+
+msgid "date must not be after 9999-12-31"
+msgstr ""
+
+msgid "day"
+msgid_plural "days"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "default branch"
+msgstr ""
+
+msgid "deleted"
+msgstr ""
+
+msgid "deploy"
+msgstr ""
+
+msgid "design"
+msgstr ""
+
+msgid "designs"
+msgstr ""
+
+msgid "detached"
+msgstr ""
+
+msgid "disabled"
+msgstr ""
+
+msgid "does not have a supported extension. Only %{extension_list} are supported"
+msgstr ""
+
+msgid "done"
+msgstr ""
+
+msgid "download it"
+msgstr ""
+
+msgid "draft"
+msgid_plural "drafts"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "e.g. %{token}"
+msgstr ""
+
+msgid "element is not a hierarchy"
+msgstr ""
+
+msgid "email '%{email}' does not match the allowed domains of %{email_domains}"
+msgstr ""
+
+msgid "email '%{email}' is not a verified email."
+msgstr ""
+
+msgid "enabled"
+msgstr ""
+
+msgid "encrypted: needs to be a :required, :optional or :migrating!"
+msgstr ""
+
+msgid "entries cannot be larger than 255 characters"
+msgstr ""
+
+msgid "entries cannot be nil"
+msgstr ""
+
+msgid "entries cannot contain HTML tags"
+msgstr ""
+
+msgid "epic"
+msgstr ""
+
+msgid "error"
+msgstr ""
+
+msgid "error code:"
+msgstr ""
+
+msgid "estimateCommand|%{slash_command} will update the estimated time with the latest command."
+msgstr ""
+
+msgid "exceeds the limit of %{bytes} bytes"
+msgstr ""
+
+msgid "exceeds the limit of %{bytes} bytes for directory name \"%{dirname}\""
+msgstr ""
+
+msgid "expired on %{timebox_due_date}"
+msgstr ""
+
+msgid "expires on %{timebox_due_date}"
+msgstr ""
+
+msgid "failed"
+msgstr ""
+
+msgid "failed to dismiss associated finding(id=%{finding_id}): %{message}"
+msgstr ""
+
+msgid "file"
+msgid_plural "files"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "finding is not found or is already attached to a vulnerability"
+msgstr ""
+
+msgid "for %{link_to_merge_request} with %{link_to_merge_request_source_branch}"
+msgstr ""
+
+msgid "for %{link_to_merge_request} with %{link_to_merge_request_source_branch} into %{link_to_merge_request_target_branch}"
+msgstr ""
+
+msgid "for %{link_to_pipeline_ref}"
+msgstr ""
+
+msgid "for %{ref}"
+msgstr ""
+
+msgid "for this project"
+msgstr ""
+
+msgid "fork this project"
+msgstr ""
+
+msgid "from"
+msgstr ""
+
+msgid "group"
+msgstr ""
+
+msgid "groups"
+msgstr ""
+
+msgid "has already been linked to another vulnerability"
+msgstr ""
+
+msgid "has already been taken"
+msgstr ""
+
+msgid "help"
+msgstr ""
+
+msgid "here"
+msgstr ""
+
+msgid "https://your-bitbucket-server"
+msgstr ""
+
+msgid "image diff"
+msgstr ""
+
+msgid "impersonation token"
+msgstr ""
+
+msgid "impersonation tokens"
+msgstr ""
+
+msgid "import flow"
+msgstr ""
+
+msgid "importing"
+msgstr ""
+
+msgid "in group %{link_to_group}"
+msgstr ""
+
+msgid "in project %{link_to_project}"
+msgstr ""
+
+msgid "index"
+msgstr ""
+
+msgid "instance completed"
+msgid_plural "instances completed"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "invalid milestone state `%{state}`"
+msgstr ""
+
+msgid "is"
+msgstr ""
+
+msgid "is already associated to a GitLab Issue. New issue will not be associated."
+msgstr ""
+
+msgid "is an invalid IP address range"
+msgstr ""
+
+msgid "is blocked by"
+msgstr ""
+
+msgid "is enabled."
+msgstr ""
+
+msgid "is invalid because there is downstream lock"
+msgstr ""
+
+msgid "is invalid because there is upstream lock"
+msgstr ""
+
+msgid "is not"
+msgstr ""
+
+msgid "is not a descendant of the Group owning the template"
+msgstr ""
+
+msgid "is not a valid X509 certificate."
+msgstr ""
+
+msgid "is not allowed. Try again with a different email address, or contact your GitLab admin."
+msgstr ""
+
+msgid "is not an email you own"
+msgstr ""
+
+msgid "is not in the group enforcing Group Managed Account"
+msgstr ""
+
+msgid "is read only"
+msgstr ""
+
+msgid "is too long (%{current_value}). The maximum size is %{max_size}."
+msgstr ""
+
+msgid "is too long (maximum is 100 entries)"
+msgstr ""
+
+msgid "is too long (maximum is 1000 entries)"
+msgstr ""
+
+msgid "issue"
+msgstr ""
+
+msgid "issues at risk"
+msgstr ""
+
+msgid "issues need attention"
+msgstr ""
+
+msgid "issues on track"
+msgstr ""
+
+msgid "it is larger than %{limit}"
+msgstr ""
+
+msgid "it is stored as a job artifact"
+msgstr ""
+
+msgid "it is stored externally"
+msgstr ""
+
+msgid "it is stored in LFS"
+msgstr ""
+
+msgid "it is too large"
+msgstr ""
+
+msgid "jigsaw is not defined"
+msgstr ""
+
+msgid "jira.issue.description.content"
+msgstr ""
+
+msgid "jira.issue.summary"
+msgstr ""
+
+msgid "latest"
+msgstr ""
+
+msgid "latest deployment"
+msgstr ""
+
+msgid "latest version"
+msgstr ""
+
+msgid "leave %{group_name}"
+msgstr ""
+
+msgid "less than a minute"
+msgstr ""
+
+msgid "level: %{level}"
+msgstr ""
+
+msgid "limit of %{project_limit} reached"
+msgstr ""
+
+msgid "load it anyway"
+msgstr ""
+
+msgid "loading"
+msgstr ""
+
+msgid "locked by %{path_lock_user_name} %{created_at}"
+msgstr ""
+
+msgid "log in"
+msgstr ""
+
+msgid "manual"
+msgstr ""
+
+msgid "math|The math in this entry is taking too long to render and may not be displayed as expected. For performance reasons, math blocks are also limited to %{maxChars} characters. Consider splitting up large formulae, splitting math blocks among multiple entries, or using an image instead."
+msgstr ""
+
+msgid "math|There was an error rendering this math block"
+msgstr ""
+
+msgid "merge request"
+msgid_plural "merge requests"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "merged %{time_ago}"
+msgstr ""
+
+msgid "missing"
+msgstr ""
+
+msgid "most recent deployment"
+msgstr ""
+
+msgid "mrWidgetCommitsAdded|%{commitCount} and %{mergeCommitCount} will be added to %{targetBranch}."
+msgstr ""
+
+msgid "mrWidgetCommitsAdded|%{commitCount} will be added to %{targetBranch}."
+msgstr ""
+
+msgid "mrWidgetCommitsAdded|1 merge commit"
+msgstr ""
+
+msgid "mrWidgetNothingToMerge|Currently there are no changes in this merge request's source branch. Please push new commits or use a different branch."
+msgstr ""
+
+msgid "mrWidgetNothingToMerge|Interested parties can even contribute by pushing commits if they want to."
+msgstr ""
+
+msgid "mrWidgetNothingToMerge|Merge requests are a place to propose changes you have made to a project and discuss those changes with others."
+msgstr ""
+
+msgid "mrWidget| Please restore it or use a different %{missingBranchName} branch"
+msgstr ""
+
+msgid "mrWidget|%{link_start}Learn more about resolving conflicts%{link_end}"
+msgstr ""
+
+msgid "mrWidget|%{metricsLinkStart} Memory %{metricsLinkEnd} usage %{emphasisStart} decreased %{emphasisEnd} from %{memoryFrom}MB to %{memoryTo}MB"
+msgstr ""
+
+msgid "mrWidget|%{metricsLinkStart} Memory %{metricsLinkEnd} usage %{emphasisStart} increased %{emphasisEnd} from %{memoryFrom}MB to %{memoryTo}MB"
+msgstr ""
+
+msgid "mrWidget|%{metricsLinkStart} Memory %{metricsLinkEnd} usage is %{emphasisStart} unchanged %{emphasisEnd} at %{memoryFrom}MB"
+msgstr ""
+
+msgid "mrWidget|%{prefixToLinkStart}No pipeline%{prefixToLinkEnd} %{addPipelineLinkStart}Add the .gitlab-ci.yml file%{addPipelineLinkEnd} to create one."
+msgstr ""
+
+msgid "mrWidget|Added to the merge train by"
+msgstr ""
+
+msgid "mrWidget|Allows commits from members who can merge to the target branch"
+msgstr ""
+
+msgid "mrWidget|An error occurred while removing your approval."
+msgstr ""
+
+msgid "mrWidget|An error occurred while retrieving approval data for this merge request."
+msgstr ""
+
+msgid "mrWidget|An error occurred while submitting your approval."
+msgstr ""
+
+msgid "mrWidget|Approval password is invalid."
+msgstr ""
+
+msgid "mrWidget|Approve"
+msgstr ""
+
+msgid "mrWidget|Approve additionally"
+msgstr ""
+
+msgid "mrWidget|Approved by"
+msgstr ""
+
+msgid "mrWidget|Are you adding technical debt or code vulnerabilities?"
+msgstr ""
+
+msgid "mrWidget|Cancel automatic merge"
+msgstr ""
+
+msgid "mrWidget|Check out branch"
+msgstr ""
+
+msgid "mrWidget|Checking ability to merge automatically…"
+msgstr ""
+
+msgid "mrWidget|Cherry-pick"
+msgstr ""
+
+msgid "mrWidget|Cherry-pick this merge request in a new merge request"
+msgstr ""
+
+msgid "mrWidget|Closed"
+msgstr ""
+
+msgid "mrWidget|Closed by"
+msgstr ""
+
+msgid "mrWidget|Closes"
+msgstr ""
+
+msgid "mrWidget|Create an issue to resolve them later"
+msgstr ""
+
+msgid "mrWidget|Delete source branch"
+msgstr ""
+
+msgid "mrWidget|Deployment statistics are not available currently"
+msgstr ""
+
+msgid "mrWidget|Did not close"
+msgstr ""
+
+msgid "mrWidget|Email patches"
+msgstr ""
+
+msgid "mrWidget|Failed to load deployment statistics"
+msgstr ""
+
+msgid "mrWidget|Fast-forward merge is not possible. To merge this request, first rebase locally."
+msgstr ""
+
+msgid "mrWidget|Fork merge requests do not create merge request pipelines which validate a post merge result"
+msgstr ""
+
+msgid "mrWidget|If the %{branch} branch exists in your local repository, you can merge this merge request manually using the"
+msgstr ""
+
+msgid "mrWidget|If the %{missingBranchName} branch exists in your local repository, you can merge this merge request manually using the command line"
+msgstr ""
+
+msgid "mrWidget|In the merge train at position %{mergeTrainPosition}"
+msgstr ""
+
+msgid "mrWidget|Loading deployment statistics"
+msgstr ""
+
+msgid "mrWidget|Mentions"
+msgstr ""
+
+msgid "mrWidget|Merge"
+msgstr ""
+
+msgid "mrWidget|Merge failed."
+msgstr ""
+
+msgid "mrWidget|Merge failed: %{mergeError}. Please try again."
+msgstr ""
+
+msgid "mrWidget|Merge locally"
+msgstr ""
+
+msgid "mrWidget|Merge request approved."
+msgstr ""
+
+msgid "mrWidget|Merged by"
+msgstr ""
+
+msgid "mrWidget|More information"
+msgstr ""
+
+msgid "mrWidget|No approval required"
+msgstr ""
+
+msgid "mrWidget|No approval required; you can still approve"
+msgstr ""
+
+msgid "mrWidget|Open in Web IDE"
+msgstr ""
+
+msgid "mrWidget|Pipeline blocked. The pipeline for this merge request requires a manual action to proceed"
+msgstr ""
+
+msgid "mrWidget|Plain diff"
+msgstr ""
+
+msgid "mrWidget|Ready to be merged automatically. Ask someone with write access to this repository to merge this request"
+msgstr ""
+
+msgid "mrWidget|Refresh"
+msgstr ""
+
+msgid "mrWidget|Refresh now"
+msgstr ""
+
+msgid "mrWidget|Refreshing now"
+msgstr ""
+
+msgid "mrWidget|Remove from merge train"
+msgstr ""
+
+msgid "mrWidget|Request to merge"
+msgstr ""
+
+msgid "mrWidget|Resolve WIP status"
+msgstr ""
+
+msgid "mrWidget|Resolve conflicts"
+msgstr ""
+
+msgid "mrWidget|Resolve these conflicts or ask someone with write access to this repository to merge it locally"
+msgstr ""
+
+msgid "mrWidget|Revert"
+msgstr ""
+
+msgid "mrWidget|Revert this merge request in a new merge request"
+msgstr ""
+
+msgid "mrWidget|Revoke approval"
+msgstr ""
+
+msgid "mrWidget|Set by"
+msgstr ""
+
+msgid "mrWidget|The changes were merged into"
+msgstr ""
+
+msgid "mrWidget|The changes were not merged into"
+msgstr ""
+
+msgid "mrWidget|The changes will be merged into"
+msgstr ""
+
+msgid "mrWidget|The pipeline for this merge request failed. Please retry the job or push a new commit to fix the failure"
+msgstr ""
+
+msgid "mrWidget|The source branch HEAD has recently changed. Please reload the page and review the changes before merging"
+msgstr ""
+
+msgid "mrWidget|The source branch has been deleted"
+msgstr ""
+
+msgid "mrWidget|The source branch is %{commitsBehindLinkStart}%{commitsBehind}%{commitsBehindLinkEnd} the target branch"
+msgstr ""
+
+msgid "mrWidget|The source branch is being deleted"
+msgstr ""
+
+msgid "mrWidget|The source branch will be deleted"
+msgstr ""
+
+msgid "mrWidget|The source branch will not be deleted"
+msgstr ""
+
+msgid "mrWidget|There are merge conflicts"
+msgstr ""
+
+msgid "mrWidget|There are unresolved threads. Please resolve these threads"
+msgstr ""
+
+msgid "mrWidget|This feature merges changes from the target branch to the source branch. You cannot use this feature since the source branch is protected."
+msgstr ""
+
+msgid "mrWidget|This merge request failed to be merged automatically"
+msgstr ""
+
+msgid "mrWidget|This merge request is in the process of being merged"
+msgstr ""
+
+msgid "mrWidget|This merge request will be added to the merge train when pipeline %{linkStart}#%{pipelineId}%{linkEnd} succeeds."
+msgstr ""
+
+msgid "mrWidget|This merge request will start a merge train when pipeline %{linkStart}#%{pipelineId}%{linkEnd} succeeds."
+msgstr ""
+
+msgid "mrWidget|This project is archived, write access has been disabled"
+msgstr ""
+
+msgid "mrWidget|To approve this merge request, please enter your password. This project requires all approvals to be authenticated."
+msgstr ""
+
+msgid "mrWidget|Use %{linkStart}CI pipelines to test your code%{linkEnd}, simply add a GitLab CI configuration file to your project. It only takes a minute to make your code more secure and robust."
+msgstr ""
+
+msgid "mrWidget|When this merge request is ready, remove the WIP: prefix from the title to allow it to be merged"
+msgstr ""
+
+msgid "mrWidget|You are not allowed to edit this project directly. Please fork to make changes."
+msgstr ""
+
+msgid "mrWidget|You can delete the source branch now"
+msgstr ""
+
+msgid "mrWidget|You can merge this merge request manually using the"
+msgstr ""
+
+msgid "mrWidget|Your password"
+msgstr ""
+
+msgid "mrWidget|branch does not exist."
+msgstr ""
+
+msgid "mrWidget|command line"
+msgstr ""
+
+msgid "mrWidget|into"
+msgstr ""
+
+msgid "mrWidget|to be added to the merge train when the pipeline succeeds"
+msgstr ""
+
+msgid "mrWidget|to be merged automatically when the pipeline succeeds"
+msgstr ""
+
+msgid "mrWidget|to start a merge train when the pipeline succeeds"
+msgstr ""
+
+msgid "must be greater than start date"
+msgstr ""
+
+msgid "must contain only valid frameworks"
+msgstr ""
+
+msgid "my-awesome-group"
+msgstr ""
+
+msgid "n/a"
+msgstr ""
+
+msgid "need attention"
+msgstr ""
+
+msgid "needs to be between 10 minutes and 1 month"
+msgstr ""
+
+msgid "never"
+msgstr ""
+
+msgid "never expires"
+msgstr ""
+
+msgid "new merge request"
+msgstr ""
+
+msgid "no contributions"
+msgstr ""
+
+msgid "no expiration"
+msgstr ""
+
+msgid "no one can merge"
+msgstr ""
+
+msgid "none"
+msgstr ""
+
+msgid "not found"
+msgstr ""
+
+msgid "notification emails"
+msgstr ""
+
+msgid "nounSeries|%{firstItem} and %{lastItem}"
+msgstr ""
+
+msgid "nounSeries|%{item}"
+msgstr ""
+
+msgid "nounSeries|%{item}, %{nextItem}"
+msgstr ""
+
+msgid "nounSeries|%{item}, and %{lastItem}"
+msgstr ""
+
+msgid "on track"
+msgstr ""
+
+msgid "open issue"
+msgstr ""
+
+msgid "opened %{timeAgoString} by %{user}"
+msgstr ""
+
+msgid "opened %{timeAgo}"
+msgstr ""
+
+msgid "or"
+msgstr ""
+
+msgid "out of %d total test"
+msgid_plural "out of %d total tests"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "parent"
+msgid_plural "parents"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "password"
+msgstr ""
+
+msgid "pending comment"
+msgstr ""
+
+msgid "pending removal"
+msgstr ""
+
+msgid "per day"
+msgstr ""
+
+msgid "personal access token"
+msgstr ""
+
+msgid "personal access tokens"
+msgstr ""
+
+msgid "pipeline"
+msgstr ""
+
+msgid "pod_name can contain only lowercase letters, digits, '-', and '.' and must start and end with an alphanumeric character"
+msgstr ""
+
+msgid "pod_name cannot be larger than %{max_length} chars"
+msgstr ""
+
+msgid "point"
+msgid_plural "points"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "private"
+msgstr ""
+
+msgid "private key does not match certificate."
+msgstr ""
+
+msgid "processing"
+msgstr ""
+
+msgid "project"
+msgid_plural "projects"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "project access token"
+msgstr ""
+
+msgid "project access tokens"
+msgstr ""
+
+msgid "project avatar"
+msgstr ""
+
+msgid "projects"
+msgstr ""
+
+msgid "push to your repository, create pipelines, create issues or add comments. To reduce storage capacity, delete unused repositories, artifacts, wikis, issues, and pipelines."
+msgstr ""
+
+msgid "quick actions"
+msgstr ""
+
+msgid "register"
+msgstr ""
+
+msgid "relates to"
+msgstr ""
+
+msgid "released %{time}"
+msgstr ""
+
+msgid "remaining"
+msgstr ""
+
+msgid "remove"
+msgstr ""
+
+msgid "remove due date"
+msgstr ""
+
+msgid "remove weight"
+msgstr ""
+
+msgid "removed a Zoom call from this issue"
+msgstr ""
+
+msgid "rendered diff"
+msgstr ""
+
+msgid "reply"
+msgid_plural "replies"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "reset it."
+msgstr ""
+
+msgid "resolved the corresponding error and closed the issue."
+msgstr ""
+
+msgid "revised"
+msgstr ""
+
+msgid "satisfied"
+msgstr ""
+
+msgid "score"
+msgstr ""
+
+msgid "security Reports|There was an error creating the merge request"
+msgstr ""
+
+msgid "settings saved, but not activated"
+msgstr ""
+
+msgid "severity|Critical"
+msgstr ""
+
+msgid "severity|High"
+msgstr ""
+
+msgid "severity|Info"
+msgstr ""
+
+msgid "severity|Low"
+msgstr ""
+
+msgid "severity|Medium"
+msgstr ""
+
+msgid "severity|None"
+msgstr ""
+
+msgid "severity|Unknown"
+msgstr ""
+
+msgid "should be an array of %{object_name} objects"
+msgstr ""
+
+msgid "should be greater than or equal to %{access} inherited membership from group %{group_name}"
+msgstr ""
+
+msgid "show %{count} more"
+msgstr ""
+
+msgid "show fewer"
+msgstr ""
+
+msgid "show less"
+msgstr ""
+
+msgid "sign in"
+msgstr ""
+
+msgid "sort:"
+msgstr ""
+
+msgid "source"
+msgstr ""
+
+msgid "source diff"
+msgstr ""
+
+msgid "specified top is not part of the tree"
+msgstr ""
+
+msgid "spendCommand|%{slash_command} will update the sum of the time spent."
+msgstr ""
+
+msgid "started"
+msgstr ""
+
+msgid "started a discussion on %{design_link}"
+msgstr ""
+
+msgid "started on %{timebox_start_date}"
+msgstr ""
+
+msgid "starts on %{timebox_start_date}"
+msgstr ""
+
+msgid "stuck"
+msgstr ""
+
+msgid "success"
+msgstr ""
+
+msgid "suggestPipeline|1/2: Choose a template"
+msgstr ""
+
+msgid "suggestPipeline|2/2: Commit your changes"
+msgstr ""
+
+msgid "suggestPipeline|Choose %{boldStart}Code Quality%{boldEnd} to add a pipeline that tests the quality of your code."
+msgstr ""
+
+msgid "suggestPipeline|The template is ready! You can now commit it to create your first pipeline."
+msgstr ""
+
+msgid "suggestPipeline|We’re adding a GitLab CI configuration file to add a pipeline to the project. You could create it manually, but we recommend that you start with a GitLab template that works out of the box."
+msgstr ""
+
+msgid "syntax is correct"
+msgstr ""
+
+msgid "syntax is incorrect"
+msgstr ""
+
+msgid "tag name"
+msgstr ""
+
+msgid "the following issue(s)"
+msgstr ""
+
+msgid "this document"
+msgstr ""
+
+msgid "to help your contributors communicate effectively!"
+msgstr ""
+
+msgid "to list"
+msgstr ""
+
+msgid "toggle collapse"
+msgstr ""
+
+msgid "toggle dropdown"
+msgstr ""
+
+msgid "triggered"
+msgstr ""
+
+msgid "unicode domains should use IDNA encoding"
+msgstr ""
+
+msgid "updated"
+msgstr ""
+
+msgid "updated %{timeAgo}"
+msgstr ""
+
+msgid "updated %{time_ago}"
+msgstr ""
+
+msgid "uploaded"
+msgstr ""
+
+msgid "uploads"
+msgstr ""
+
+msgid "user avatar"
+msgstr ""
+
+msgid "username"
+msgstr ""
+
+msgid "uses Kubernetes clusters to deploy your code!"
+msgstr ""
+
+msgid "v%{version} published %{timeAgo}"
+msgstr ""
+
+msgid "verify ownership"
+msgstr ""
+
+msgid "version %{versionIndex}"
+msgstr ""
+
+msgid "via %{closed_via}"
+msgstr ""
+
+msgid "via merge request %{link}"
+msgstr ""
+
+msgid "view it on GitLab"
+msgstr ""
+
+msgid "view the blob"
+msgstr ""
+
+msgid "view the source"
+msgstr ""
+
+msgid "vulnerability|Add a comment"
+msgstr ""
+
+msgid "vulnerability|Add a comment or reason for dismissal"
+msgstr ""
+
+msgid "vulnerability|Add comment"
+msgstr ""
+
+msgid "vulnerability|Add comment & dismiss"
+msgstr ""
+
+msgid "vulnerability|Dismiss vulnerability"
+msgstr ""
+
+msgid "vulnerability|Save comment"
+msgstr ""
+
+msgid "vulnerability|Undo dismiss"
+msgstr ""
+
+msgid "vulnerability|dismissed"
+msgstr ""
+
+msgid "wiki page"
+msgstr ""
+
+msgid "will be released %{time}"
+msgstr ""
+
+msgid "with %{additions} additions, %{deletions} deletions."
+msgstr ""
+
+msgid "with expiry changing from %{old_expiry} to %{new_expiry}"
+msgstr ""
+
+msgid "with expiry remaining unchanged at %{old_expiry}"
+msgstr ""
+
+msgid "yaml invalid"
+msgstr ""
+
diff --git a/locale/sk_SK/gitlab.po b/locale/sk_SK/gitlab.po
index f2be261bd5f..7001b0f9c0f 100644
--- a/locale/sk_SK/gitlab.po
+++ b/locale/sk_SK/gitlab.po
@@ -2,7 +2,7 @@ msgid ""
msgstr ""
"Project-Id-Version: gitlab-ee\n"
"Report-Msgid-Bugs-To: \n"
-"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Last-Translator: \n"
"Language-Team: Slovak\n"
"Language: sk_SK\n"
"MIME-Version: 1.0\n"
@@ -10,9 +10,11 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=4; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 3;\n"
"X-Crowdin-Project: gitlab-ee\n"
+"X-Crowdin-Project-ID: 288872\n"
"X-Crowdin-Language: sk\n"
"X-Crowdin-File: /master/locale/gitlab.pot\n"
-"PO-Revision-Date: 2020-06-08 15:19\n"
+"X-Crowdin-File-ID: 6\n"
+"PO-Revision-Date: 2020-07-02 01:27\n"
msgid " %{start} to %{end}"
msgstr ""
@@ -251,6 +253,13 @@ msgstr[1] ""
msgstr[2] ""
msgstr[3] ""
+msgid "%d milestone"
+msgid_plural "%d milestones"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
msgid "%d minute"
msgid_plural "%d minutes"
msgstr[0] ""
@@ -314,13 +323,6 @@ msgstr[1] ""
msgstr[2] ""
msgstr[3] ""
-msgid "%d url scanned"
-msgid_plural "%d urls scanned"
-msgstr[0] ""
-msgstr[1] ""
-msgstr[2] ""
-msgstr[3] ""
-
msgid "%d vulnerability"
msgid_plural "%d vulnerabilities"
msgstr[0] ""
@@ -412,9 +414,15 @@ msgstr[3] ""
msgid "%{count} related %{pluralized_subject}: %{links}"
msgstr ""
+msgid "%{dashboard_path} could not be found."
+msgstr ""
+
msgid "%{days} days until tags are automatically removed"
msgstr ""
+msgid "%{deployLinkStart}Use a template to deploy to ECS%{deployLinkEnd}, or use a docker image to %{commandsLinkStart}run AWS commands in GitLab CI/CD%{commandsLinkEnd}."
+msgstr ""
+
msgid "%{description}- Sentry event: %{errorUrl}- First seen: %{firstSeen}- Last seen: %{lastSeen} %{countLabel}: %{count}%{userCountLabel}: %{userCount}"
msgstr ""
@@ -457,12 +465,18 @@ msgstr ""
msgid "%{host} sign-in from new location"
msgstr ""
-msgid "%{icon}You are about to add %{usersTag} people to the discussion. Proceed with caution."
+msgid "%{icon}You are about to add %{usersTag} people to the discussion. They will all receive a notification."
+msgstr ""
+
+msgid "%{integrations_link_start}Integrations%{link_end} enable you to make third-party applications part of your GitLab workflow. If the available integrations don't meet your needs, consider using a %{webhooks_link_start}webhook%{link_end}."
msgstr ""
msgid "%{issuableType} will be removed! Are you sure?"
msgstr ""
+msgid "%{issuesCount} issues in this group"
+msgstr ""
+
msgid "%{issuesSize} issues"
msgstr ""
@@ -472,7 +486,7 @@ msgstr ""
msgid "%{labelStart}Class:%{labelEnd} %{class}"
msgstr ""
-msgid "%{labelStart}Confidence:%{labelEnd} %{confidence}"
+msgid "%{labelStart}Evidence:%{labelEnd} %{evidence}"
msgstr ""
msgid "%{labelStart}File:%{labelEnd} %{file}"
@@ -490,6 +504,9 @@ msgstr ""
msgid "%{labelStart}Report Type:%{labelEnd} %{reportType}"
msgstr ""
+msgid "%{labelStart}Scanner:%{labelEnd} %{scanner}"
+msgstr ""
+
msgid "%{labelStart}Severity:%{labelEnd} %{severity}"
msgstr ""
@@ -514,9 +531,6 @@ msgstr ""
msgid "%{link_start}Read more%{link_end} about role permissions"
msgstr ""
-msgid "%{link} can be used for binding events when something is happening within the project."
-msgstr ""
-
msgid "%{listToShow}, and %{awardsListLength} more."
msgstr ""
@@ -550,6 +564,9 @@ msgstr ""
msgid "%{name} found %{resultsString}"
msgstr ""
+msgid "%{name} is already being used for another emoji"
+msgstr ""
+
msgid "%{name} is scheduled for %{action}"
msgstr ""
@@ -599,6 +616,9 @@ msgstr ""
msgid "%{primary} (%{secondary})"
msgstr ""
+msgid "%{ref} cannot be added: %{error}"
+msgstr ""
+
msgid "%{releases} release"
msgid_plural "%{releases} releases"
msgstr[0] ""
@@ -609,13 +629,43 @@ msgstr[3] ""
msgid "%{remaining_approvals} left"
msgstr ""
-msgid "%{retryButtonStart}Try again%{retryButtonEnd} or %{newFileButtonStart}attach a new file%{newFileButtonEnd}"
+msgid "%{reportType} %{status} detected %{critical} critical and %{high} high severity vulnerabilities out of %{total}."
msgstr ""
-msgid "%{screenreaderOnlyStart}Keyboard shorcuts%{screenreaderOnlyEnd} Disabled"
+msgid "%{reportType} %{status} detected %{critical} critical and %{high} high severity vulnerabilities."
msgstr ""
-msgid "%{screenreaderOnlyStart}Keyboard shorcuts%{screenreaderOnlyEnd} Enabled"
+msgid "%{reportType} %{status} detected %{critical} critical severity vulnerabilities out of %{total}."
+msgstr ""
+
+msgid "%{reportType} %{status} detected %{critical} critical severity vulnerability."
+msgid_plural "%{reportType} %{status} detected %{critical} critical severity vulnerabilities."
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "%{reportType} %{status} detected %{high} high severity vulnerabilities out of %{total}."
+msgstr ""
+
+msgid "%{reportType} %{status} detected %{high} high severity vulnerability."
+msgid_plural "%{reportType} %{status} detected %{high} high severity vulnerabilities."
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "%{reportType} %{status} detected %{other} vulnerability."
+msgid_plural "%{reportType} %{status} detected %{other} vulnerabilities."
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "%{reportType} %{status} detected no new vulnerabilities."
+msgstr ""
+
+msgid "%{retryButtonStart}Try again%{retryButtonEnd} or %{newFileButtonStart}attach a new file%{newFileButtonEnd}"
msgstr ""
msgid "%{service_title} %{message}."
@@ -722,6 +772,12 @@ msgstr ""
msgid "%{token}..."
msgstr ""
+msgid "%{totalCpu} (%{freeSpacePercentage}%{percentSymbol} free)"
+msgstr ""
+
+msgid "%{totalMemory} (%{freeSpacePercentage}%{percentSymbol} free)"
+msgstr ""
+
msgid "%{totalWeight} total weight"
msgstr ""
@@ -752,6 +808,12 @@ msgstr ""
msgid "%{verb} %{time_spent_value} spent time."
msgstr ""
+msgid "%{webhooks_link_start}%{webhook_type}%{link_end} enable you to send notifications to web applications in response to events in a group or project."
+msgstr ""
+
+msgid "%{webhooks_link_start}%{webhook_type}%{link_end} enable you to send notifications to web applications in response to events in a group or project. We recommend using an %{integrations_link_start}integration%{link_end} in preference to a webhook."
+msgstr ""
+
msgid "'%{level}' is not a valid visibility level"
msgstr ""
@@ -780,9 +842,6 @@ msgstr ""
msgid "(No changes)"
msgstr ""
-msgid "(Show all)"
-msgstr ""
-
msgid "(check progress)"
msgstr ""
@@ -855,6 +914,9 @@ msgstr ""
msgid "0 for unlimited"
msgstr ""
+msgid "0 for unlimited, only effective with remote storage enabled."
+msgstr ""
+
msgid "1 %{type} addition"
msgid_plural "%{count} %{type} additions"
msgstr[0] ""
@@ -1032,6 +1094,12 @@ msgstr ""
msgid "<code>\"johnsmith@example.com\": \"johnsmith@example.com\"</code> will add \"By <a href=\"#\">johnsmith@example.com</a>\" to all issues and comments originally created by johnsmith@example.com. By default, the email address or username is masked to ensure the user's privacy. Use this option if you want to show the full email address."
msgstr ""
+msgid "<code>Masked</code> to prevent the values from being displayed in job logs (must match certain regexp requirements)."
+msgstr ""
+
+msgid "<code>Protected</code> to expose them to protected branches or tags only."
+msgstr ""
+
msgid "<namespace / project>"
msgstr ""
@@ -1059,9 +1127,6 @@ msgstr ""
msgid "A .NET Core console application template, customizable for any .NET Core project"
msgstr ""
-msgid "A DAG must have two dependent jobs to be visualized on this tab."
-msgstr ""
-
msgid "A GitBook site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
msgstr ""
@@ -1092,6 +1157,12 @@ msgstr ""
msgid "A deleted user"
msgstr ""
+msgid "A file has been changed."
+msgstr ""
+
+msgid "A file was not found."
+msgstr ""
+
msgid "A file with '%{file_name}' already exists in %{branch} branch"
msgstr ""
@@ -1158,7 +1229,7 @@ msgstr ""
msgid "A subscription will trigger a new pipeline on the default branch of this project when a pipeline successfully completes for a new tag on the %{default_branch_docs} of the subscribed project."
msgstr ""
-msgid "A terraform report was generated in your pipelines."
+msgid "A suggestion is not applicable."
msgstr ""
msgid "A user with write access to the source branch selected this option"
@@ -1329,6 +1400,9 @@ msgstr ""
msgid "Action to take when receiving an alert."
msgstr ""
+msgid "Actions"
+msgstr ""
+
msgid "Activate"
msgstr ""
@@ -1387,6 +1461,9 @@ msgstr ""
msgid "Add LICENSE"
msgstr ""
+msgid "Add New Node"
+msgstr ""
+
msgid "Add README"
msgstr ""
@@ -1462,6 +1539,9 @@ msgstr ""
msgid "Add approval rule"
msgstr ""
+msgid "Add approvers"
+msgstr ""
+
msgid "Add bold text"
msgstr ""
@@ -1519,6 +1599,9 @@ msgstr ""
msgid "Add strikethrough text"
msgstr ""
+msgid "Add suggestion to batch"
+msgstr ""
+
msgid "Add system hook"
msgstr ""
@@ -1618,6 +1701,9 @@ msgstr ""
msgid "Admin Note"
msgstr ""
+msgid "Admin Notifications"
+msgstr ""
+
msgid "Admin Overview"
msgstr ""
@@ -1729,6 +1815,9 @@ msgstr ""
msgid "AdminSettings|Integrations configured here will automatically apply to all projects on this instance."
msgstr ""
+msgid "AdminSettings|Moved to integrations"
+msgstr ""
+
msgid "AdminSettings|No required pipeline"
msgstr ""
@@ -1961,12 +2050,18 @@ msgstr[1] ""
msgstr[2] ""
msgstr[3] ""
+msgid "AlertManagement| assign yourself"
+msgstr ""
+
msgid "AlertManagement|Acknowledged"
msgstr ""
msgid "AlertManagement|Alert"
msgstr ""
+msgid "AlertManagement|Alert assignee(s): %{assignees}"
+msgstr ""
+
msgid "AlertManagement|Alert detail"
msgstr ""
@@ -1982,9 +2077,15 @@ msgstr ""
msgid "AlertManagement|All alerts"
msgstr ""
+msgid "AlertManagement|Assign To"
+msgstr ""
+
msgid "AlertManagement|Assign status"
msgstr ""
+msgid "AlertManagement|Assignee"
+msgstr ""
+
msgid "AlertManagement|Assignees"
msgstr ""
@@ -2003,9 +2104,6 @@ msgstr ""
msgid "AlertManagement|Edit"
msgstr ""
-msgid "AlertManagement|End time"
-msgstr ""
-
msgid "AlertManagement|Events"
msgstr ""
@@ -2024,10 +2122,13 @@ msgstr ""
msgid "AlertManagement|More information"
msgstr ""
+msgid "AlertManagement|No Matching Results"
+msgstr ""
+
msgid "AlertManagement|No alert data to display."
msgstr ""
-msgid "AlertManagement|No alerts available to display. If you think you're seeing this message in error, refresh the page."
+msgid "AlertManagement|No alerts available to display. See %{linkStart}enabling alert management%{linkEnd} for more information on adding alerts to the list."
msgstr ""
msgid "AlertManagement|No alerts to display."
@@ -2036,6 +2137,9 @@ msgstr ""
msgid "AlertManagement|None"
msgstr ""
+msgid "AlertManagement|None -"
+msgstr ""
+
msgid "AlertManagement|Open"
msgstr ""
@@ -2072,9 +2176,18 @@ msgstr ""
msgid "AlertManagement|There was an error displaying the alerts. Confirm your endpoint's configuration details to ensure alerts appear."
msgstr ""
+msgid "AlertManagement|There was an error while updating the assignee(s) list. Please try again."
+msgstr ""
+
+msgid "AlertManagement|There was an error while updating the assignee(s) of the alert. Please try again."
+msgstr ""
+
msgid "AlertManagement|There was an error while updating the status of the alert. Please try again."
msgstr ""
+msgid "AlertManagement|This assignee cannot be assigned to this alert."
+msgstr ""
+
msgid "AlertManagement|Tool"
msgstr ""
@@ -2222,6 +2335,9 @@ msgstr ""
msgid "Allow users to request access (if visibility is public or internal)"
msgstr ""
+msgid "Allowed Geo IP"
+msgstr ""
+
msgid "Allowed email domain restriction only permitted for top-level groups"
msgstr ""
@@ -2255,6 +2371,9 @@ msgstr ""
msgid "Amazon Web Services"
msgstr ""
+msgid "Amazon Web Services Logo"
+msgstr ""
+
msgid "Amazon authentication is not %{link_start}correctly configured%{link_end}. Ask your GitLab administrator if you want to use this service."
msgstr ""
@@ -2312,10 +2431,16 @@ msgstr ""
msgid "An error occurred when updating the issue weight"
msgstr ""
+msgid "An error occurred while acknowledging the notification. Refresh the page and try again."
+msgstr ""
+
+msgid "An error occurred while adding approvers"
+msgstr ""
+
msgid "An error occurred while adding formatted title for epic"
msgstr ""
-msgid "An error occurred while checking group path"
+msgid "An error occurred while checking group path. Please refresh and try again."
msgstr ""
msgid "An error occurred while committing your changes."
@@ -2459,7 +2584,7 @@ msgstr ""
msgid "An error occurred while loading milestones"
msgstr ""
-msgid "An error occurred while loading terraform report"
+msgid "An error occurred while loading project creation UI"
msgstr ""
msgid "An error occurred while loading the data. Please try again."
@@ -2513,6 +2638,9 @@ msgstr ""
msgid "An error occurred while rendering preview broadcast message"
msgstr ""
+msgid "An error occurred while rendering the editor"
+msgstr ""
+
msgid "An error occurred while reordering issues."
msgstr ""
@@ -2609,7 +2737,7 @@ msgstr ""
msgid "Analyze your dependencies for known vulnerabilities."
msgstr ""
-msgid "Analyze your source code and git history for secrets"
+msgid "Analyze your source code and git history for secrets."
msgstr ""
msgid "Analyze your source code for known vulnerabilities."
@@ -2723,12 +2851,18 @@ msgstr ""
msgid "Apply suggestion"
msgstr ""
+msgid "Apply suggestions"
+msgstr ""
+
msgid "Apply template"
msgstr ""
msgid "Apply this approval rule to any branch or a specific protected branch."
msgstr ""
+msgid "Applying"
+msgstr ""
+
msgid "Applying a template will replace the existing issue description. Any changes you have made will be lost."
msgstr ""
@@ -2741,12 +2875,18 @@ msgstr ""
msgid "Applying multiple commands"
msgstr ""
-msgid "Applying suggestion"
+msgid "Applying suggestion..."
+msgstr ""
+
+msgid "Applying suggestions..."
msgstr ""
msgid "Approval rules"
msgstr ""
+msgid "Approval rules reset to project defaults"
+msgstr ""
+
msgid "ApprovalRuleRemove|%d member"
msgid_plural "ApprovalRuleRemove|%d members"
msgstr[0] ""
@@ -2808,6 +2948,9 @@ msgstr ""
msgid "Approved"
msgstr ""
+msgid "Approved MRs"
+msgstr ""
+
msgid "Approved by: "
msgstr ""
@@ -3021,6 +3164,9 @@ msgstr ""
msgid "Assign"
msgstr ""
+msgid "Assign Iteration"
+msgstr ""
+
msgid "Assign custom color like #FF0000"
msgstr ""
@@ -3039,6 +3185,9 @@ msgstr ""
msgid "Assign to"
msgstr ""
+msgid "Assign to commenting user"
+msgstr ""
+
msgid "Assign yourself to these issues"
msgstr ""
@@ -3067,6 +3216,9 @@ msgstr[1] ""
msgstr[2] ""
msgstr[3] ""
+msgid "Assignee has no permissions"
+msgstr ""
+
msgid "Assignee lists not available with your current license"
msgstr ""
@@ -3076,6 +3228,9 @@ msgstr ""
msgid "Assignee(s)"
msgstr ""
+msgid "Assignees"
+msgstr ""
+
msgid "Assigns %{assignee_users_sentence}."
msgstr ""
@@ -3113,6 +3268,9 @@ msgstr ""
msgid "Audit Events is a way to keep track of important events that happened in GitLab."
msgstr ""
+msgid "Audit Log"
+msgstr ""
+
msgid "AuditEvents|(removed)"
msgstr ""
@@ -3149,6 +3307,9 @@ msgstr ""
msgid "AuditLogs|IP Address"
msgstr ""
+msgid "AuditLogs|Member Events"
+msgstr ""
+
msgid "AuditLogs|No matching %{type} found."
msgstr ""
@@ -3431,9 +3592,6 @@ msgstr ""
msgid "Badges|e.g. %{exampleUrl}"
msgstr ""
-msgid "Badge|New"
-msgstr ""
-
msgid "Balsamiq file could not be loaded."
msgstr ""
@@ -3482,6 +3640,12 @@ msgstr ""
msgid "Below you will find all the groups that are public."
msgstr ""
+msgid "Beta"
+msgstr ""
+
+msgid "Bi-weekly code coverage"
+msgstr ""
+
msgid "Billing"
msgstr ""
@@ -3542,9 +3706,15 @@ msgstr ""
msgid "Bitbucket Server Import"
msgstr ""
+msgid "Bitbucket Server import"
+msgstr ""
+
msgid "Bitbucket import"
msgstr ""
+msgid "Blame"
+msgstr ""
+
msgid "Blocked"
msgstr ""
@@ -3557,9 +3727,6 @@ msgstr ""
msgid "Blog"
msgstr ""
-msgid "Blue helpers indicate an action to be taken."
-msgstr ""
-
msgid "Board name"
msgstr ""
@@ -3758,6 +3925,9 @@ msgstr ""
msgid "Broadcast Message was successfully updated."
msgstr ""
+msgid "Broadcast Messages"
+msgstr ""
+
msgid "Browse Directory"
msgstr ""
@@ -3812,6 +3982,9 @@ msgstr ""
msgid "By %{user_name}"
msgstr ""
+msgid "By URL"
+msgstr ""
+
msgid "By default GitLab sends emails in HTML and plain text formats so mail clients can choose what format to use. Disable this option if you only want to send emails in plain text format."
msgstr ""
@@ -3917,6 +4090,9 @@ msgstr ""
msgid "Can override approvers and approvals required per merge request"
msgstr ""
+msgid "Can't apply as this line has changed or the suggestion already matches its content."
+msgstr ""
+
msgid "Can't create snippet: %{err}"
msgstr ""
@@ -4103,9 +4279,6 @@ msgstr ""
msgid "Changes are still tracked. Useful for cluster/index migrations."
msgstr ""
-msgid "Changes are unknown"
-msgstr ""
-
msgid "Changes suppressed. Click to show."
msgstr ""
@@ -4412,15 +4585,9 @@ msgstr ""
msgid "Choose what content you want to see on a group’s overview page"
msgstr ""
-msgid "Choose which groups you wish to synchronize to this secondary node"
-msgstr ""
-
msgid "Choose which repositories you want to connect and run CI/CD pipelines."
msgstr ""
-msgid "Choose which shards you wish to synchronize to this secondary node"
-msgstr ""
-
msgid "Choose your framework"
msgstr ""
@@ -4718,9 +4885,15 @@ msgstr ""
msgid "Cluster does not exist"
msgstr ""
+msgid "Cluster is required for Stages::ClusterEndpointInserter"
+msgstr ""
+
msgid "Cluster level"
msgstr ""
+msgid "Cluster type must be specificed for Stages::ClusterEndpointInserter"
+msgstr ""
+
msgid "ClusterIntegration| %{custom_domain_start}More information%{custom_domain_end}."
msgstr ""
@@ -4832,7 +5005,7 @@ msgstr ""
msgid "ClusterIntegration|Cert-Manager"
msgstr ""
-msgid "ClusterIntegration|Cert-Manager is a native Kubernetes certificate management controller that helps with issuing certificates. Installing Cert-Manager on your cluster will issue a certificate by %{letsEncrypt} and ensure that certificates are valid and up-to-date."
+msgid "ClusterIntegration|Cert-Manager is a native Kubernetes certificate management controller that helps with issuing certificates. Installing Cert-Manager on your cluster will issue a certificate by %{linkStart}Let's Encrypt%{linkEnd} and ensure that certificates are valid and up-to-date."
msgstr ""
msgid "ClusterIntegration|Certificate Authority bundle (PEM format)"
@@ -4859,9 +5032,6 @@ msgstr ""
msgid "ClusterIntegration|Clear the local cache of namespace and service accounts. This is necessary if your integration has become out of sync. The cache is repopulated during the next CI job that requires namespace and service accounts."
msgstr ""
-msgid "ClusterIntegration|Cloud Run"
-msgstr ""
-
msgid "ClusterIntegration|Cluster being created"
msgstr ""
@@ -4946,7 +5116,7 @@ msgstr ""
msgid "ClusterIntegration|Crossplane"
msgstr ""
-msgid "ClusterIntegration|Crossplane enables declarative provisioning of managed services from your cloud of choice using %{codeStart}kubectl%{codeEnd} or %{gitlabIntegrationLink}. Crossplane runs inside your Kubernetes cluster and supports secure connectivity and secrets management between app containers and the cloud services they depend on."
+msgid "ClusterIntegration|Crossplane enables declarative provisioning of managed services from your cloud of choice using %{codeStart}kubectl%{codeEnd} or %{linkStart}GitLab Integration%{linkEnd}. Crossplane runs inside your Kubernetes cluster and supports secure connectivity and secrets management between app containers and the cloud services they depend on."
msgstr ""
msgid "ClusterIntegration|Deletes all GitLab resources attached to this cluster during removal"
@@ -5030,9 +5200,6 @@ msgstr ""
msgid "ClusterIntegration|GitLab-managed cluster"
msgstr ""
-msgid "ClusterIntegration|Gitlab Integration"
-msgstr ""
-
msgid "ClusterIntegration|Global default"
msgstr ""
@@ -5078,7 +5245,7 @@ msgstr ""
msgid "ClusterIntegration|Ingress gives you a way to route requests to services based on the request host or path, centralizing a number of services into a single entrypoint."
msgstr ""
-msgid "ClusterIntegration|Installing Ingress may incur additional costs. Learn more about %{pricingLink}."
+msgid "ClusterIntegration|Installing Ingress may incur additional costs. Learn more about %{linkStart}pricing%{linkEnd}."
msgstr ""
msgid "ClusterIntegration|Instance cluster"
@@ -5093,7 +5260,7 @@ msgstr ""
msgid "ClusterIntegration|Issuer Email"
msgstr ""
-msgid "ClusterIntegration|Issuers represent a certificate authority. You must provide an email address for your Issuer. "
+msgid "ClusterIntegration|Issuers represent a certificate authority. You must provide an email address for your Issuer."
msgstr ""
msgid "ClusterIntegration|Jupyter Hostname"
@@ -5165,9 +5332,6 @@ msgstr ""
msgid "ClusterIntegration|Learn more about instance Kubernetes clusters"
msgstr ""
-msgid "ClusterIntegration|Let's Encrypt"
-msgstr ""
-
msgid "ClusterIntegration|Loading IAM Roles"
msgstr ""
@@ -5279,7 +5443,7 @@ msgstr ""
msgid "ClusterIntegration|Prometheus"
msgstr ""
-msgid "ClusterIntegration|Prometheus is an open-source monitoring system with %{gitlabIntegrationLink} to monitor deployed applications."
+msgid "ClusterIntegration|Prometheus is an open-source monitoring system with %{linkStart}GitLab Integration%{linkEnd} to monitor deployed applications."
msgstr ""
msgid "ClusterIntegration|Provider details"
@@ -5435,10 +5599,10 @@ msgstr ""
msgid "ClusterIntegration|Select zone to choose machine type"
msgstr ""
-msgid "ClusterIntegration|Send Cilium Logs"
+msgid "ClusterIntegration|Send Container Network Policies Logs"
msgstr ""
-msgid "ClusterIntegration|Send ModSecurity Logs"
+msgid "ClusterIntegration|Send Web Application Firewall Logs"
msgstr ""
msgid "ClusterIntegration|Service Token"
@@ -5606,15 +5770,12 @@ msgstr ""
msgid "ClusterIntegration|documentation"
msgstr ""
-msgid "ClusterIntegration|installed via %{installed_via}"
+msgid "ClusterIntegration|installed via %{linkStart}Cloud Run%{linkEnd}"
msgstr ""
msgid "ClusterIntegration|meets the requirements"
msgstr ""
-msgid "ClusterIntegration|pricing"
-msgstr ""
-
msgid "ClusterIntegration|sign up"
msgstr ""
@@ -5651,6 +5812,15 @@ msgstr ""
msgid "Code"
msgstr ""
+msgid "Code Coverage: %{coveragePercentage}%{percentSymbol}"
+msgstr ""
+
+msgid "Code Coverage| Empty code coverage data"
+msgstr ""
+
+msgid "Code Coverage|Couldn't fetch the code coverage data"
+msgstr ""
+
msgid "Code Owners"
msgstr ""
@@ -5705,7 +5875,7 @@ msgstr ""
msgid "Collapse approvers"
msgstr ""
-msgid "Collapse child epics"
+msgid "Collapse milestones"
msgstr ""
msgid "Collapse replies"
@@ -5723,6 +5893,9 @@ msgstr ""
msgid "Coming soon"
msgstr ""
+msgid "Comma-separated, e.g. '1.1.1.1, 2.2.2.0/24'"
+msgstr ""
+
msgid "Command"
msgstr ""
@@ -5895,6 +6068,12 @@ msgstr ""
msgid "Compliance framework (optional)"
msgstr ""
+msgid "Compliance frameworks"
+msgstr ""
+
+msgid "ComplianceDashboard|created by:"
+msgstr ""
+
msgid "ComplianceFramework|GDPR"
msgstr ""
@@ -6033,6 +6212,9 @@ msgstr ""
msgid "Connection timed out"
msgstr ""
+msgid "Connection timeout"
+msgstr ""
+
msgid "Contact sales to upgrade"
msgstr ""
@@ -6092,9 +6274,6 @@ msgstr ""
msgid "ContainerRegistry|CLI Commands"
msgstr ""
-msgid "ContainerRegistry|Compressed Size"
-msgstr ""
-
msgid "ContainerRegistry|Container Registry"
msgstr ""
@@ -6110,6 +6289,9 @@ msgstr ""
msgid "ContainerRegistry|Copy push command"
msgstr ""
+msgid "ContainerRegistry|Delete selected"
+msgstr ""
+
msgid "ContainerRegistry|Docker connection error"
msgstr ""
@@ -6143,16 +6325,16 @@ msgstr ""
msgid "ContainerRegistry|If you are not already logged in, you need to authenticate to the Container Registry by using your GitLab username and password. If you have %{twofaDocLinkStart}Two-Factor Authentication%{twofaDocLinkEnd} enabled, use a %{personalAccessTokensDocLinkStart}Personal Access Token%{personalAccessTokensDocLinkEnd} instead of a password."
msgstr ""
-msgid "ContainerRegistry|Image ID"
+msgid "ContainerRegistry|Image ID: %{imageId}"
msgstr ""
msgid "ContainerRegistry|Image Repositories"
msgstr ""
-msgid "ContainerRegistry|Keep and protect the images that matter most."
+msgid "ContainerRegistry|Image tags"
msgstr ""
-msgid "ContainerRegistry|Last Updated"
+msgid "ContainerRegistry|Keep and protect the images that matter most."
msgstr ""
msgid "ContainerRegistry|Login"
@@ -6167,6 +6349,9 @@ msgstr ""
msgid "ContainerRegistry|Please contact your administrator."
msgstr ""
+msgid "ContainerRegistry|Published %{timeInfo}"
+msgstr ""
+
msgid "ContainerRegistry|Push an image"
msgstr ""
@@ -6182,9 +6367,6 @@ msgstr ""
msgid "ContainerRegistry|Remove repository"
msgstr ""
-msgid "ContainerRegistry|Remove selected tags"
-msgstr ""
-
msgid "ContainerRegistry|Remove tag"
msgid_plural "ContainerRegistry|Remove tags"
msgstr[0] ""
@@ -6216,9 +6398,6 @@ msgstr ""
msgid "ContainerRegistry|Sorry, your filter produced no results."
msgstr ""
-msgid "ContainerRegistry|Tag"
-msgstr ""
-
msgid "ContainerRegistry|Tag expiration policy"
msgstr ""
@@ -6363,21 +6542,6 @@ msgstr ""
msgid "Control the display of third party offers."
msgstr ""
-msgid "Control the maximum concurrency of LFS/attachment backfill for this secondary node"
-msgstr ""
-
-msgid "Control the maximum concurrency of container repository operations for this Geo node"
-msgstr ""
-
-msgid "Control the maximum concurrency of repository backfill for this secondary node"
-msgstr ""
-
-msgid "Control the maximum concurrency of verification operations for this Geo node"
-msgstr ""
-
-msgid "Control the minimum interval in days that a repository should be reverified for this primary node"
-msgstr ""
-
msgid "Cookie domain"
msgstr ""
@@ -6519,6 +6683,9 @@ msgstr ""
msgid "Could not find design."
msgstr ""
+msgid "Could not find iteration"
+msgstr ""
+
msgid "Could not remove the trigger."
msgstr ""
@@ -6555,6 +6722,12 @@ msgstr ""
msgid "Coverage"
msgstr ""
+msgid "Coverage Fuzzing"
+msgstr ""
+
+msgid "Crash State"
+msgstr ""
+
msgid "Create"
msgstr ""
@@ -6603,6 +6776,9 @@ msgstr ""
msgid "Create a personal access token on your account to pull or push via %{protocol}."
msgstr ""
+msgid "Create a project pre-populated with the necessary files to get you started quickly."
+msgstr ""
+
msgid "Create an account using:"
msgstr ""
@@ -6771,6 +6947,9 @@ msgstr ""
msgid "Creates branch '%{branch_name}' and a merge request to resolve this issue."
msgstr ""
+msgid "Creating"
+msgstr ""
+
msgid "Creating epic"
msgstr ""
@@ -6822,7 +7001,10 @@ msgstr ""
msgid "Current vulnerabilities count"
msgstr ""
-msgid "CurrentUser|Buy CI minutes"
+msgid "CurrentUser|Buy Pipeline minutes"
+msgstr ""
+
+msgid "CurrentUser|One of your groups is running out"
msgstr ""
msgid "CurrentUser|Profile"
@@ -6837,6 +7019,9 @@ msgstr ""
msgid "CurrentUser|Upgrade"
msgstr ""
+msgid "Custom Attributes"
+msgstr ""
+
msgid "Custom CI configuration path"
msgstr ""
@@ -7102,12 +7287,18 @@ msgstr ""
msgid "DAG"
msgstr ""
+msgid "DAG visualization requires at least 3 dependent jobs."
+msgstr ""
+
msgid "DNS"
msgstr ""
msgid "Dashboard"
msgstr ""
+msgid "Dashboard uid not found"
+msgstr ""
+
msgid "DashboardProjects|All"
msgstr ""
@@ -7132,6 +7323,9 @@ msgstr ""
msgid "Data is still calculating..."
msgstr ""
+msgid "Datasource name not found"
+msgstr ""
+
msgid "Date"
msgstr ""
@@ -7312,6 +7506,9 @@ msgstr ""
msgid "Delete this attachment"
msgstr ""
+msgid "Delete user list"
+msgstr ""
+
msgid "Delete variable"
msgstr ""
@@ -7638,6 +7835,9 @@ msgstr ""
msgid "Deploying to"
msgstr ""
+msgid "Deploying to AWS is easy with GitLab"
+msgstr ""
+
msgid "Deployment Frequency"
msgstr ""
@@ -8002,6 +8202,9 @@ msgstr ""
msgid "Dismissed on pipeline %{pipelineLink} at %{projectLink}"
msgstr ""
+msgid "Display alerts from all your monitoring tools directly within GitLab."
+msgstr ""
+
msgid "Display name"
msgstr ""
@@ -8059,9 +8262,6 @@ msgstr ""
msgid "Don't show again"
msgstr ""
-msgid "Don't worry, you can access this tour by clicking on the help icon in the top right corner and choose <strong>Learn GitLab</strong>."
-msgstr ""
-
msgid "Done"
msgstr ""
@@ -8188,6 +8388,9 @@ msgstr ""
msgid "Edit Release"
msgstr ""
+msgid "Edit Slack integration"
+msgstr ""
+
msgid "Edit Snippet"
msgstr ""
@@ -8227,6 +8430,9 @@ msgstr ""
msgid "Edit issues"
msgstr ""
+msgid "Edit iteration"
+msgstr ""
+
msgid "Edit public deploy key"
msgstr ""
@@ -8275,6 +8481,9 @@ msgstr ""
msgid "Email"
msgstr ""
+msgid "Email Notification"
+msgstr ""
+
msgid "Email address"
msgstr ""
@@ -8287,6 +8496,9 @@ msgstr ""
msgid "Email not verified. Please verify your email in Salesforce."
msgstr ""
+msgid "Email notification for unknown sign-ins"
+msgstr ""
+
msgid "Email patch"
msgstr ""
@@ -8518,6 +8730,9 @@ msgstr ""
msgid "Enforce DNS rebinding attack protection"
msgstr ""
+msgid "Enforce personal access token expiration"
+msgstr ""
+
msgid "Ensure connectivity is available from the GitLab server to the Prometheus server"
msgstr ""
@@ -8587,6 +8802,9 @@ msgstr ""
msgid "Enter the name of your application, and we'll return a unique %{type}."
msgstr ""
+msgid "Enter weights for storages for new repositories."
+msgstr ""
+
msgid "Enter your password to approve"
msgstr ""
@@ -8596,15 +8814,27 @@ msgstr ""
msgid "Environment does not have deployments"
msgstr ""
+msgid "Environment is required for Stages::MetricEndpointInserter"
+msgstr ""
+
+msgid "Environment is required for Stages::VariableEndpointInserter"
+msgstr ""
+
msgid "Environment scope"
msgstr ""
+msgid "Environment variables are applied to all project environments in this instance via the Runner. You can use environment variables for passwords, secret keys, etc. Make variables available to the running application by prepending the variable key with <code>K8S_SECRET_</code>. You can set variables to be:"
+msgstr ""
+
msgid "Environment variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. Additionally, they can be masked so they are hidden in job logs, though they must match certain regexp requirements to do so. You can use environment variables for passwords, secret keys, or whatever you want."
msgstr ""
msgid "Environment variables are configured by your administrator to be %{link_start}protected%{link_end} by default"
msgstr ""
+msgid "Environment variables on this GitLab instance are configured to be %{link_start}protected%{link_end} by default"
+msgstr ""
+
msgid "Environment:"
msgstr ""
@@ -9136,6 +9366,9 @@ msgstr ""
msgid "EventFilterBy|Filter by comments"
msgstr ""
+msgid "EventFilterBy|Filter by designs"
+msgstr ""
+
msgid "EventFilterBy|Filter by epic events"
msgstr ""
@@ -9268,15 +9501,15 @@ msgstr ""
msgid "Expand approvers"
msgstr ""
-msgid "Expand child epics"
-msgstr ""
-
msgid "Expand down"
msgstr ""
msgid "Expand dropdown"
msgstr ""
+msgid "Expand milestones"
+msgstr ""
+
msgid "Expand sidebar"
msgstr ""
@@ -9292,6 +9525,9 @@ msgstr ""
msgid "Expiration date"
msgstr ""
+msgid "Expiration not enforced"
+msgstr ""
+
msgid "Expiration policy for the Container Registry is a perfect solution for keeping the Registry space down while still enjoying the full power of GitLab CI/CD."
msgstr ""
@@ -9412,6 +9648,9 @@ msgstr ""
msgid "Failed Jobs"
msgstr ""
+msgid "Failed on"
+msgstr ""
+
msgid "Failed to add a Zoom meeting"
msgstr ""
@@ -9472,13 +9711,19 @@ msgstr ""
msgid "Failed to install."
msgstr ""
+msgid "Failed to load assignees. Please try again."
+msgstr ""
+
+msgid "Failed to load authors. Please try again."
+msgstr ""
+
msgid "Failed to load emoji list."
msgstr ""
msgid "Failed to load error details from Sentry."
msgstr ""
-msgid "Failed to load errors from Sentry. Error message: %{errorMessage}"
+msgid "Failed to load errors from Sentry."
msgstr ""
msgid "Failed to load group activity metrics. Please try again."
@@ -9559,6 +9804,9 @@ msgstr ""
msgid "Failed to set due date because the date format is invalid."
msgstr ""
+msgid "Failed to set iteration on this issue. Please try again."
+msgstr ""
+
msgid "Failed to signing using smartcard authentication"
msgstr ""
@@ -9781,13 +10029,10 @@ msgstr ""
msgid "FeatureFlags|Target environments"
msgstr ""
-msgid "FeatureFlags|There are no active feature flags"
+msgid "FeatureFlags|There was an error fetching the feature flags."
msgstr ""
-msgid "FeatureFlags|There are no inactive feature flags"
-msgstr ""
-
-msgid "FeatureFlags|There was an error fetching the feature flags."
+msgid "FeatureFlags|There was an error retrieving user lists"
msgstr ""
msgid "FeatureFlags|Try again in a few moments or contact your support team."
@@ -9799,9 +10044,18 @@ msgstr ""
msgid "FeatureFlag|Delete strategy"
msgstr ""
+msgid "FeatureFlag|List"
+msgstr ""
+
msgid "FeatureFlag|Percentage"
msgstr ""
+msgid "FeatureFlag|Select a user list"
+msgstr ""
+
+msgid "FeatureFlag|There are no configured user lists"
+msgstr ""
+
msgid "FeatureFlag|Type"
msgstr ""
@@ -9958,6 +10212,12 @@ msgstr ""
msgid "Filter..."
msgstr ""
+msgid "Find File"
+msgstr ""
+
+msgid "Find bugs in your code with coverage-guided fuzzing"
+msgstr ""
+
msgid "Find by path"
msgstr ""
@@ -10222,6 +10482,9 @@ msgstr ""
msgid "Geo allows you to replicate your GitLab instance to other geographical locations."
msgstr ""
+msgid "Geo nodes are paused using a command run on the node"
+msgstr ""
+
msgid "GeoNodeStatusEvent|%{timeAgoStr} (%{pendingEvents} events)"
msgstr ""
@@ -10306,6 +10569,9 @@ msgstr ""
msgid "GeoNodes|Not checksummed"
msgstr ""
+msgid "GeoNodes|Package files"
+msgstr ""
+
msgid "GeoNodes|Pausing replication stops the sync process. Are you sure?"
msgstr ""
@@ -10324,6 +10590,9 @@ msgstr ""
msgid "GeoNodes|Replication slots"
msgstr ""
+msgid "GeoNodes|Replication status"
+msgstr ""
+
msgid "GeoNodes|Repositories"
msgstr ""
@@ -10534,6 +10803,9 @@ msgstr ""
msgid "Geo|Synchronization failed - %{error}"
msgstr ""
+msgid "Geo|Synchronization of %{itemTitle} is disabled."
+msgstr ""
+
msgid "Geo|The database is currently %{db_lag} behind the primary node."
msgstr ""
@@ -10846,6 +11118,9 @@ msgstr ""
msgid "Go to %{link_to_google_takeout}."
msgstr ""
+msgid "Go to <strong>Issues</strong> > <strong>Boards</strong> to access your personalized learning issue board."
+msgstr ""
+
msgid "Go to Pipelines"
msgstr ""
@@ -10945,9 +11220,6 @@ msgstr ""
msgid "Go to your snippets"
msgstr ""
-msgid "Golden Tanuki"
-msgstr ""
-
msgid "Google Cloud Platform"
msgstr ""
@@ -10969,6 +11241,9 @@ msgstr ""
msgid "Grafana URL"
msgstr ""
+msgid "Grafana response contains invalid json"
+msgstr ""
+
msgid "GrafanaIntegration|API Token"
msgstr ""
@@ -11017,9 +11292,6 @@ msgstr ""
msgid "Group %{group_name} was successfully created."
msgstr ""
-msgid "Group '%{group_name}' is being imported."
-msgstr ""
-
msgid "Group Audit Events"
msgstr ""
@@ -11056,9 +11328,6 @@ msgstr ""
msgid "Group by:"
msgstr ""
-msgid "Group could not be imported: %{errors}"
-msgstr ""
-
msgid "Group description"
msgstr ""
@@ -11086,9 +11355,15 @@ msgstr ""
msgid "Group has not been marked for deletion"
msgstr ""
+msgid "Group import could not be scheduled"
+msgstr ""
+
msgid "Group info:"
msgstr ""
+msgid "Group is required when cluster_type is :group"
+msgstr ""
+
msgid "Group maintainers can register group runners in the %{link}"
msgstr ""
@@ -11152,6 +11427,24 @@ msgstr ""
msgid "GroupActivyMetrics|Recent activity (last 90 days)"
msgstr ""
+msgid "GroupImport|Failed to import group."
+msgstr ""
+
+msgid "GroupImport|Group '%{group_name}' is being imported."
+msgstr ""
+
+msgid "GroupImport|Group could not be imported: %{errors}"
+msgstr ""
+
+msgid "GroupImport|Please wait while we import the group for you. Refresh at will."
+msgstr ""
+
+msgid "GroupImport|The group was successfully imported."
+msgstr ""
+
+msgid "GroupImport|Unable to process group import file"
+msgstr ""
+
msgid "GroupRoadmap|%{dateWord} – No end date"
msgstr ""
@@ -11419,6 +11712,9 @@ msgstr ""
msgid "Groups (%{groups})"
msgstr ""
+msgid "Groups and projects"
+msgstr ""
+
msgid "Groups can also be nested by creating %{subgroup_docs_link_start}subgroups%{subgroup_docs_link_end}."
msgstr ""
@@ -11464,6 +11760,33 @@ msgstr ""
msgid "GroupsEmptyState|You can manage your group member’s permissions and access to each project in the group."
msgstr ""
+msgid "GroupsNew|Contact an administrator to enable options for importing your group."
+msgstr ""
+
+msgid "GroupsNew|Create"
+msgstr ""
+
+msgid "GroupsNew|Create group"
+msgstr ""
+
+msgid "GroupsNew|GitLab group export"
+msgstr ""
+
+msgid "GroupsNew|Import"
+msgstr ""
+
+msgid "GroupsNew|Import group"
+msgstr ""
+
+msgid "GroupsNew|My Awesome Group"
+msgstr ""
+
+msgid "GroupsNew|No import options available"
+msgstr ""
+
+msgid "GroupsNew|To copy a GitLab group between installations, navigate to the group settings page for the original installation, generate an export file, and upload it here."
+msgstr ""
+
msgid "GroupsTree|Are you sure you want to leave the \"%{fullName}\" group?"
msgstr ""
@@ -11566,6 +11889,9 @@ msgstr ""
msgid "Helps reduce request volume for protected paths"
msgstr ""
+msgid "Here are all your projects in your group, including the one you just created. To start, let’s take a look at your personalized learning project which will help you learn about GitLab at your own pace."
+msgstr ""
+
msgid "Here you will find recent merge request activity"
msgstr ""
@@ -11582,6 +11908,9 @@ msgstr[1] ""
msgstr[2] ""
msgstr[3] ""
+msgid "Hide details"
+msgstr ""
+
msgid "Hide file browser"
msgstr ""
@@ -11591,6 +11920,9 @@ msgstr ""
msgid "Hide host keys manual input"
msgstr ""
+msgid "Hide list"
+msgstr ""
+
msgid "Hide marketing-related entries from help"
msgstr ""
@@ -11613,9 +11945,6 @@ msgstr[3] ""
msgid "Hide values"
msgstr ""
-msgid "Hiding all labels"
-msgstr ""
-
msgid "High or unknown vulnerabilities present"
msgstr ""
@@ -11700,6 +12029,9 @@ msgstr ""
msgid "ID:"
msgstr ""
+msgid "IDE"
+msgstr ""
+
msgid "IDE|Allow live previews of JavaScript projects in the Web IDE using CodeSandbox Live Preview."
msgstr ""
@@ -11790,10 +12122,10 @@ msgstr ""
msgid "If enabled"
msgstr ""
-msgid "If enabled, access to projects will be validated on an external service using their classification label."
+msgid "If enabled, GitLab will handle Object Storage replication using Geo. %{linkStart}More information%{linkEnd}"
msgstr ""
-msgid "If enabled, and if object storage is enabled, GitLab will handle Object Storage replication using Geo"
+msgid "If enabled, access to projects will be validated on an external service using their classification label."
msgstr ""
msgid "If there is no previous license or if the previous license has expired, some GitLab functionality will be blocked until a new, valid license is uploaded."
@@ -12157,6 +12489,9 @@ msgstr[1] ""
msgstr[2] ""
msgstr[3] ""
+msgid "Instance Configuration"
+msgstr ""
+
msgid "Instance Statistics visibility"
msgstr ""
@@ -12178,9 +12513,6 @@ msgstr ""
msgid "Integrations"
msgstr ""
-msgid "Integrations allow you to integrate GitLab with other applications"
-msgstr ""
-
msgid "Integrations|All details"
msgstr ""
@@ -12292,6 +12624,9 @@ msgstr ""
msgid "Invalid query"
msgstr ""
+msgid "Invalid repository bundle for snippet with id %{snippet_id}"
+msgstr ""
+
msgid "Invalid repository path"
msgstr ""
@@ -12310,6 +12645,9 @@ msgstr ""
msgid "Invalid two-factor code."
msgstr ""
+msgid "Invalid yaml"
+msgstr ""
+
msgid "Invitation"
msgstr ""
@@ -12529,15 +12867,24 @@ msgstr ""
msgid "It seems like the Dependency Scanning job ran successfully, but no dependencies have been detected in your project."
msgstr ""
+msgid "It seems that there is currently no available data for code coverage"
+msgstr ""
+
msgid "It's you"
msgstr ""
+msgid "Iteration"
+msgstr ""
+
msgid "Iteration changed to"
msgstr ""
msgid "Iteration removed"
msgstr ""
+msgid "Iteration updated"
+msgstr ""
+
msgid "Iterations"
msgstr ""
@@ -12568,12 +12915,18 @@ msgstr ""
msgid "January"
msgstr ""
+msgid "Jira Issues"
+msgstr ""
+
msgid "Jira import is already running."
msgstr ""
msgid "Jira integration not configured."
msgstr ""
+msgid "Jira project key is not configured"
+msgstr ""
+
msgid "Jira project: %{importProject}"
msgstr ""
@@ -12754,6 +13107,9 @@ msgstr ""
msgid "Keep divergent refs"
msgstr ""
+msgid "Kerberos access denied"
+msgstr ""
+
msgid "Key"
msgstr ""
@@ -12763,10 +13119,13 @@ msgstr ""
msgid "Key: %{key}"
msgstr ""
-msgid "Keyboard Shortcuts"
+msgid "Keyboard shortcuts"
msgstr ""
-msgid "Keyboard shortcuts"
+msgid "Keys"
+msgstr ""
+
+msgid "Ki"
msgstr ""
msgid "Kubernetes"
@@ -12808,6 +13167,9 @@ msgstr ""
msgid "LDAP"
msgstr ""
+msgid "LDAP Synchronization"
+msgstr ""
+
msgid "LDAP settings"
msgstr ""
@@ -13034,12 +13396,18 @@ msgstr ""
msgid "Learn more about custom project templates"
msgstr ""
+msgid "Learn more about deploying to AWS"
+msgstr ""
+
msgid "Learn more about deploying to a cluster"
msgstr ""
msgid "Learn more about group-level project templates"
msgstr ""
+msgid "Learn more about job dependencies"
+msgstr ""
+
msgid "Learn more about signing commits"
msgstr ""
@@ -13091,9 +13459,21 @@ msgstr ""
msgid "License History"
msgstr ""
+msgid "License ID:"
+msgstr ""
+
+msgid "License URL"
+msgstr ""
+
msgid "License-Check"
msgstr ""
+msgid "LicenseCompliance|%{docLinkStart}License Approvals%{docLinkEnd} are active"
+msgstr ""
+
+msgid "LicenseCompliance|%{docLinkStart}License Approvals%{docLinkEnd} are inactive"
+msgstr ""
+
msgid "LicenseCompliance|Add a license"
msgstr ""
@@ -13115,9 +13495,15 @@ msgstr ""
msgid "LicenseCompliance|Deny"
msgstr ""
+msgid "LicenseCompliance|Learn more about %{linkStart}License Approvals%{linkEnd}"
+msgstr ""
+
msgid "LicenseCompliance|License"
msgstr ""
+msgid "LicenseCompliance|License Approvals"
+msgstr ""
+
msgid "LicenseCompliance|License Compliance detected %d license and policy violation for the source branch only; approval required"
msgid_plural "LicenseCompliance|License Compliance detected %d licenses and policy violations for the source branch only; approval required"
msgstr[0] ""
@@ -13348,6 +13734,9 @@ msgstr ""
msgid "List your Bitbucket Server repositories"
msgstr ""
+msgid "Lists"
+msgstr ""
+
msgid "Live preview"
msgstr ""
@@ -13531,6 +13920,9 @@ msgstr ""
msgid "Manage project labels"
msgstr ""
+msgid "Manage storage usage"
+msgstr ""
+
msgid "Manage two-factor authentication"
msgstr ""
@@ -13546,6 +13938,9 @@ msgstr ""
msgid "Manifest file import"
msgstr ""
+msgid "Manifest import"
+msgstr ""
+
msgid "Manual job"
msgstr ""
@@ -13693,6 +14088,12 @@ msgstr ""
msgid "Maximum field length"
msgstr ""
+msgid "Maximum file size is 2MB. Please select a smaller file."
+msgstr ""
+
+msgid "Maximum import size (MB)"
+msgstr ""
+
msgid "Maximum job timeout"
msgstr ""
@@ -13732,6 +14133,9 @@ msgstr ""
msgid "Maximum size of Elasticsearch bulk indexing requests."
msgstr ""
+msgid "Maximum size of import files."
+msgstr ""
+
msgid "Maximum size of individual attachments in comments."
msgstr ""
@@ -13783,6 +14187,9 @@ msgstr ""
msgid "Merge (when the pipeline succeeds)"
msgstr ""
+msgid "Merge Conflicts"
+msgstr ""
+
msgid "Merge Request"
msgstr ""
@@ -13909,6 +14316,9 @@ msgstr ""
msgid "MergeRequests|Squash task canceled: another squash is already in progress."
msgstr ""
+msgid "MergeRequests|This project does not allow squashing commits when merge requests are accepted."
+msgstr ""
+
msgid "MergeRequests|Thread stays resolved"
msgstr ""
@@ -14062,6 +14472,12 @@ msgstr ""
msgid "MetricsSettings|Add a button to the metrics dashboard linking directly to your existing external dashboard."
msgstr ""
+msgid "MetricsSettings|Choose whether to display dashboard metrics in UTC or the user's local timezone."
+msgstr ""
+
+msgid "MetricsSettings|Dashboard timezone"
+msgstr ""
+
msgid "MetricsSettings|External dashboard URL"
msgstr ""
@@ -14071,6 +14487,12 @@ msgstr ""
msgid "MetricsSettings|Metrics Dashboard"
msgstr ""
+msgid "MetricsSettings|UTC (Coordinated Universal Time)"
+msgstr ""
+
+msgid "MetricsSettings|User's local timezone"
+msgstr ""
+
msgid "Metrics|Add metric"
msgstr ""
@@ -14086,6 +14508,9 @@ msgstr ""
msgid "Metrics|Create metric"
msgstr ""
+msgid "Metrics|Current"
+msgstr ""
+
msgid "Metrics|Delete metric"
msgstr ""
@@ -14138,6 +14563,9 @@ msgstr ""
msgid "Metrics|Max"
msgstr ""
+msgid "Metrics|Min"
+msgstr ""
+
msgid "Metrics|Must be a valid PromQL query."
msgstr ""
@@ -14153,6 +14581,9 @@ msgstr ""
msgid "Metrics|Refresh dashboard"
msgstr ""
+msgid "Metrics|Select a value"
+msgstr ""
+
msgid "Metrics|Star dashboard"
msgstr ""
@@ -14171,12 +14602,18 @@ msgstr ""
msgid "Metrics|There was an error getting annotations information."
msgstr ""
+msgid "Metrics|There was an error getting dashboard validation warnings information."
+msgstr ""
+
msgid "Metrics|There was an error getting deployment information."
msgstr ""
msgid "Metrics|There was an error getting environments information."
msgstr ""
+msgid "Metrics|There was an error getting options for variable \"%{name}\"."
+msgstr ""
+
msgid "Metrics|There was an error trying to validate your query"
msgstr ""
@@ -14219,6 +14656,9 @@ msgstr ""
msgid "Metrics|You're about to permanently delete this metric. This cannot be undone."
msgstr ""
+msgid "Metrics|Your dashboard schema is invalid. Edit the dashboard to correct the YAML schema."
+msgstr ""
+
msgid "Metrics|e.g. HTTP requests"
msgstr ""
@@ -14234,12 +14674,18 @@ msgstr ""
msgid "Metrics|e.g. req/sec"
msgstr ""
+msgid "Mi"
+msgstr ""
+
msgid "Microsoft Azure"
msgstr ""
msgid "Middleman project with Static Site Editor support"
msgstr ""
+msgid "Migrate your data from an external source like GitHub, Bitbucket, or another instance of GitLab."
+msgstr ""
+
msgid "Migrated %{success_count}/%{total_count} files."
msgstr ""
@@ -14376,6 +14822,9 @@ msgstr ""
msgid "Minimum capacity to be available before we schedule more mirrors preemptively."
msgstr ""
+msgid "Minimum interval in days"
+msgstr ""
+
msgid "Minimum length is %{minimum_password_length} characters"
msgstr ""
@@ -14562,6 +15011,12 @@ msgstr ""
msgid "Multiple uploaders found: %{uploader_types}"
msgstr ""
+msgid "Must match with the %{codeStart}external_url%{codeEnd} in %{codeStart}/etc/gitlab/gitlab.rb%{codeEnd}."
+msgstr ""
+
+msgid "Must match with the %{codeStart}geo_node_name%{codeEnd} in %{codeStart}/etc/gitlab/gitlab.rb%{codeEnd}. %{linkStart}More information%{linkEnd}"
+msgstr ""
+
msgid "My Awesome Group"
msgstr ""
@@ -14676,10 +15131,16 @@ msgstr ""
msgid "New Application"
msgstr ""
+msgid "New Branch"
+msgstr ""
+
+msgid "New Deploy Key"
+msgstr ""
+
msgid "New Environment"
msgstr ""
-msgid "New Geo Node"
+msgid "New File"
msgstr ""
msgid "New Group"
@@ -14698,15 +15159,15 @@ msgstr[1] ""
msgstr[2] ""
msgstr[3] ""
-msgid "New Iteration"
-msgstr ""
-
msgid "New Jira import"
msgstr ""
msgid "New Label"
msgstr ""
+msgid "New Merge Request"
+msgstr ""
+
msgid "New Milestone"
msgstr ""
@@ -14725,6 +15186,9 @@ msgstr ""
msgid "New Snippet"
msgstr ""
+msgid "New User"
+msgstr ""
+
msgid "New branch"
msgstr ""
@@ -14767,6 +15231,9 @@ msgstr ""
msgid "New issue title"
msgstr ""
+msgid "New iteration"
+msgstr ""
+
msgid "New iteration created"
msgstr ""
@@ -14944,6 +15411,12 @@ msgstr ""
msgid "No grouping"
msgstr ""
+msgid "No iteration"
+msgstr ""
+
+msgid "No iterations to show"
+msgstr ""
+
msgid "No job log"
msgstr ""
@@ -14986,6 +15459,9 @@ msgstr ""
msgid "No other labels with such name or description"
msgstr ""
+msgid "No panels matching properties %{opts}"
+msgstr ""
+
msgid "No parent group"
msgstr ""
@@ -15019,6 +15495,9 @@ msgstr ""
msgid "No schedules"
msgstr ""
+msgid "No stack trace for this error"
+msgstr ""
+
msgid "No starrers matched your search"
msgstr ""
@@ -15031,7 +15510,7 @@ msgstr ""
msgid "No test coverage"
msgstr ""
-msgid "No thanks, don't show this again"
+msgid "No thanks"
msgstr ""
msgid "No vulnerabilities present"
@@ -15046,9 +15525,6 @@ msgstr ""
msgid "No, directly import the existing email addresses and usernames."
msgstr ""
-msgid "No, not interested right now"
-msgstr ""
-
msgid "No. of commits"
msgstr ""
@@ -15094,9 +15570,6 @@ msgstr ""
msgid "Not found."
msgstr ""
-msgid "Not helpful"
-msgstr ""
-
msgid "Not now"
msgstr ""
@@ -15253,6 +15726,9 @@ msgstr ""
msgid "Notifications on"
msgstr ""
+msgid "Notify users by email when sign-in location is not recognized"
+msgstr ""
+
msgid "Nov"
msgstr ""
@@ -15319,9 +15795,6 @@ msgstr ""
msgid "Oh no!"
msgstr ""
-msgid "Ok let's go"
-msgstr ""
-
msgid "Oldest first"
msgstr ""
@@ -15334,16 +15807,49 @@ msgstr ""
msgid "On track"
msgstr ""
+msgid "OnDemandScans|Attached branch"
+msgstr ""
+
+msgid "OnDemandScans|Attached branch is where the scan job runs."
+msgstr ""
+
+msgid "OnDemandScans|Could not run the scan. Please try again."
+msgstr ""
+
msgid "OnDemandScans|Create new DAST scan"
msgstr ""
+msgid "OnDemandScans|DAST will scan the target URL and any discovered sub URLs."
+msgstr ""
+
+msgid "OnDemandScans|New on-demand DAST scan"
+msgstr ""
+
msgid "OnDemandScans|On-demand Scans"
msgstr ""
+msgid "OnDemandScans|On-demand scans run outside the DevOps cycle and find vulnerabilities in your projects. %{learnMoreLinkStart}Learn more%{learnMoreLinkEnd}"
+msgstr ""
+
+msgid "OnDemandScans|Only a passive scan can be performed on demand."
+msgstr ""
+
+msgid "OnDemandScans|Passive DAST Scan"
+msgstr ""
+
+msgid "OnDemandScans|Please enter a valid URL format, ex: http://www.example.com/home"
+msgstr ""
+
+msgid "OnDemandScans|Run this scan"
+msgstr ""
+
+msgid "OnDemandScans|Scan mode"
+msgstr ""
+
msgid "OnDemandScans|Schedule or run scans immediately against target sites. Currently available on-demand scan type: DAST. %{helpLinkStart}More information%{helpLinkEnd}"
msgstr ""
-msgid "Onboarding"
+msgid "OnDemandScans|Target URL"
msgstr ""
msgid "Once imported, repositories can be mirrored over SSH. Read more %{link_start}here%{link_end}."
@@ -15371,6 +15877,9 @@ msgstr ""
msgid "One or more of you personal access tokens were revoked"
msgstr ""
+msgid "One or more of your %{provider} projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
+msgstr ""
+
msgid "One or more of your Bitbucket projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
msgstr ""
@@ -15872,6 +16381,9 @@ msgstr ""
msgid "Parent epic is not present."
msgstr ""
+msgid "Parsing error for param :embed_json. %{message}"
+msgstr ""
+
msgid "Part of merge request changes"
msgstr ""
@@ -15884,6 +16396,9 @@ msgstr ""
msgid "Passed"
msgstr ""
+msgid "Passed on"
+msgstr ""
+
msgid "Password"
msgstr ""
@@ -15968,6 +16483,9 @@ msgstr ""
msgid "Perform common operations on GitLab project"
msgstr ""
+msgid "Performance and resource management"
+msgstr ""
+
msgid "Performance optimization"
msgstr ""
@@ -16175,7 +16693,7 @@ msgstr ""
msgid "Pipelines|Something went wrong while cleaning runners cache."
msgstr ""
-msgid "Pipelines|There are currently no %{scope} pipelines."
+msgid "Pipelines|There are currently no finished pipelines."
msgstr ""
msgid "Pipelines|There are currently no pipelines."
@@ -16286,6 +16804,9 @@ msgstr ""
msgid "Pipeline|Stop pipeline #%{pipelineId}?"
msgstr ""
+msgid "Pipeline|Tag name"
+msgstr ""
+
msgid "Pipeline|Trigger author"
msgstr ""
@@ -16367,12 +16888,18 @@ msgstr ""
msgid "Please check your email (%{email}) to verify that you own this address and unlock the power of CI/CD. Didn't receive it? %{resend_link}. Wrong email address? %{update_link}."
msgstr ""
+msgid "Please choose a file"
+msgstr ""
+
msgid "Please choose a group URL with no special characters."
msgstr ""
msgid "Please complete your profile with email address"
msgstr ""
+msgid "Please convert %{linkStart}them to Git%{linkEnd}, and go through the %{linkToImportFlow} again."
+msgstr ""
+
msgid "Please convert them to %{link_to_git}, and go through the %{link_to_import_flow} again."
msgstr ""
@@ -16430,6 +16957,9 @@ msgstr ""
msgid "Please provide a valid email address."
msgstr ""
+msgid "Please provide attributes to update"
+msgstr ""
+
msgid "Please refer to <a href=\"%{docs_url}\">%{docs_url}</a>"
msgstr ""
@@ -17165,6 +17695,9 @@ msgstr ""
msgid "Project has too many %{label_for_message} to search"
msgstr ""
+msgid "Project is required when cluster_type is :project"
+msgstr ""
+
msgid "Project members"
msgstr ""
@@ -17282,7 +17815,40 @@ msgstr ""
msgid "ProjectService|Comment"
msgstr ""
-msgid "ProjectService|Comment will be posted on each event"
+msgid "ProjectService|Event will be triggered by a push to the repository"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a commit is created/updated"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a confidential issue is created/updated/closed"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a deployment finishes"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a merge request is created/updated/merged"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a new tag is pushed to the repository"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a new, unique alert is recorded"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a pipeline status changes"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a wiki page is created/updated"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when an issue is created/updated/closed"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when someone adds a comment"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when someone adds a comment on a confidential issue"
msgstr ""
msgid "ProjectService|Perform common operations on GitLab project: %{project_name}"
@@ -17333,6 +17899,9 @@ msgstr ""
msgid "ProjectSettings|Enable 'Delete source branch' option by default"
msgstr ""
+msgid "ProjectSettings|Enable merge trains and pipelines for merged results"
+msgstr ""
+
msgid "ProjectSettings|Every merge creates a merge commit"
msgstr ""
@@ -17363,7 +17932,7 @@ msgstr ""
msgid "ProjectSettings|Forks"
msgstr ""
-msgid "ProjectSettings|Git Large File Storage"
+msgid "ProjectSettings|Git Large File Storage (LFS)"
msgstr ""
msgid "ProjectSettings|Internal"
@@ -17399,9 +17968,6 @@ msgstr ""
msgid "ProjectSettings|Merge options"
msgstr ""
-msgid "ProjectSettings|Merge pipelines will try to validate the post-merge result prior to merging"
-msgstr ""
-
msgid "ProjectSettings|Merge requests"
msgstr ""
@@ -17429,6 +17995,9 @@ msgstr ""
msgid "ProjectSettings|Pipelines"
msgstr ""
+msgid "ProjectSettings|Pipelines for merge requests must be enabled in the CI/CD configuration file, or pipelines could be unresolvable or dropped"
+msgstr ""
+
msgid "ProjectSettings|Pipelines must succeed"
msgstr ""
@@ -17456,6 +18025,9 @@ msgstr ""
msgid "ProjectSettings|Show link to create/view merge request when pushing from the command line"
msgstr ""
+msgid "ProjectSettings|Skipped pipelines are considered successful"
+msgstr ""
+
msgid "ProjectSettings|Snippets"
msgstr ""
@@ -17471,6 +18043,9 @@ msgstr ""
msgid "ProjectSettings|These checks must pass before merge requests can be merged"
msgstr ""
+msgid "ProjectSettings|This introduces the risk of merging changes that will not pass the pipeline."
+msgstr ""
+
msgid "ProjectSettings|This setting is applied on the server level and can be overridden by an admin."
msgstr ""
@@ -17648,15 +18223,27 @@ msgstr ""
msgid "ProjectsNew|Blank project"
msgstr ""
+msgid "ProjectsNew|Connect your external repository to GitLab CI/CD."
+msgstr ""
+
msgid "ProjectsNew|Contact an administrator to enable options for importing your project."
msgstr ""
msgid "ProjectsNew|Create"
msgstr ""
+msgid "ProjectsNew|Create a blank project to house your files, plan your work, and collaborate on code, among other things."
+msgstr ""
+
+msgid "ProjectsNew|Create blank project"
+msgstr ""
+
msgid "ProjectsNew|Create from template"
msgstr ""
+msgid "ProjectsNew|Create new project"
+msgstr ""
+
msgid "ProjectsNew|Creating project & repository."
msgstr ""
@@ -17681,6 +18268,9 @@ msgstr ""
msgid "ProjectsNew|Project description %{tag_start}(optional)%{tag_end}"
msgstr ""
+msgid "ProjectsNew|Run CI/CD for external repository"
+msgstr ""
+
msgid "ProjectsNew|Template"
msgstr ""
@@ -17942,6 +18532,9 @@ msgstr ""
msgid "Protected Branch"
msgstr ""
+msgid "Protected Branches"
+msgstr ""
+
msgid "Protected Environment"
msgstr ""
@@ -17954,6 +18547,9 @@ msgstr ""
msgid "Protected Tag"
msgstr ""
+msgid "Protected Tags"
+msgstr ""
+
msgid "Protected branches"
msgstr ""
@@ -18053,6 +18649,9 @@ msgstr ""
msgid "Provider"
msgstr ""
+msgid "Proxy support for this API is not available currently"
+msgstr ""
+
msgid "Pseudonymizer data collection"
msgstr ""
@@ -18077,6 +18676,9 @@ msgstr ""
msgid "Publish to status page"
msgstr ""
+msgid "Published on status page"
+msgstr ""
+
msgid "Publishes this issue to the associated status page."
msgstr ""
@@ -18251,6 +18853,9 @@ msgstr ""
msgid "Recipe"
msgstr ""
+msgid "Reconfigure"
+msgstr ""
+
msgid "Recover hidden stage"
msgstr ""
@@ -18339,6 +18944,9 @@ msgstr ""
msgid "Registration|Your profile"
msgstr ""
+msgid "Regulate approvals by authors/committers, based on compliance frameworks. Can be changed only at the instance level."
+msgstr ""
+
msgid "Rejected (closed)"
msgstr ""
@@ -18391,15 +18999,24 @@ msgstr ""
msgid "ReleaseAssetLinkType|Image"
msgstr ""
+msgid "ReleaseAssetLinkType|Images"
+msgstr ""
+
msgid "ReleaseAssetLinkType|Other"
msgstr ""
msgid "ReleaseAssetLinkType|Package"
msgstr ""
+msgid "ReleaseAssetLinkType|Packages"
+msgstr ""
+
msgid "ReleaseAssetLinkType|Runbook"
msgstr ""
+msgid "ReleaseAssetLinkType|Runbooks"
+msgstr ""
+
msgid "Releases"
msgstr ""
@@ -18412,6 +19029,9 @@ msgstr ""
msgid "Releases documentation"
msgstr ""
+msgid "Releases|New Release"
+msgstr ""
+
msgid "Release|Something went wrong while getting the release details"
msgstr ""
@@ -18484,6 +19104,9 @@ msgstr ""
msgid "Remove fork relationship"
msgstr ""
+msgid "Remove from batch"
+msgstr ""
+
msgid "Remove from board"
msgstr ""
@@ -18673,6 +19296,12 @@ msgstr ""
msgid "Replication"
msgstr ""
+msgid "Replication enabled"
+msgstr ""
+
+msgid "Replication paused"
+msgstr ""
+
msgid "Reply by email"
msgstr ""
@@ -18700,15 +19329,15 @@ msgstr ""
msgid "Reported %{timeAgo} by %{reportedBy}"
msgstr ""
-msgid "Reported Resource Changes: %{addNum} to add, %{changeNum} to change, %{deleteNum} to delete"
-msgstr ""
-
msgid "Reporter"
msgstr ""
msgid "Reporting"
msgstr ""
+msgid "Reports"
+msgstr ""
+
msgid "Reports|%{combinedString} and %{resolvedString}"
msgstr ""
@@ -18749,6 +19378,9 @@ msgstr ""
msgid "Reports|Failure"
msgstr ""
+msgid "Reports|Identifier"
+msgstr ""
+
msgid "Reports|Metrics reports are loading"
msgstr ""
@@ -18761,6 +19393,9 @@ msgstr ""
msgid "Reports|Metrics reports failed loading results"
msgstr ""
+msgid "Reports|Scanner"
+msgstr ""
+
msgid "Reports|Severity"
msgstr ""
@@ -18809,15 +19444,33 @@ msgstr ""
msgid "Repository cleanup has started. You will receive an email once the cleanup operation is complete."
msgstr ""
+msgid "Repository files count over the limit"
+msgstr ""
+
+msgid "Repository has an invalid default branch name."
+msgstr ""
+
+msgid "Repository has more than one branch."
+msgstr ""
+
msgid "Repository has no locks."
msgstr ""
+msgid "Repository has tags."
+msgstr ""
+
msgid "Repository maintenance"
msgstr ""
msgid "Repository mirroring"
msgstr ""
+msgid "Repository must contain at least 1 file."
+msgstr ""
+
+msgid "Repository size is above the limit."
+msgstr ""
+
msgid "Repository static objects"
msgstr ""
@@ -18827,7 +19480,7 @@ msgstr ""
msgid "Repository sync capacity"
msgstr ""
-msgid "Repository: %{counter_repositories} / Wikis: %{counter_wikis} / Build Artifacts: %{counter_build_artifacts} / LFS: %{counter_lfs_objects}"
+msgid "Repository: %{counter_repositories} / Wikis: %{counter_wikis} / Build Artifacts: %{counter_build_artifacts} / LFS: %{counter_lfs_objects} / Snippets: %{counter_snippets}"
msgstr ""
msgid "RepositorySettingsAccessLevel|Select"
@@ -18839,6 +19492,9 @@ msgstr ""
msgid "Request Headers"
msgstr ""
+msgid "Request details"
+msgstr ""
+
msgid "Request parameter %{param} is missing."
msgstr ""
@@ -18949,6 +19605,9 @@ msgstr ""
msgid "Reset template"
msgstr ""
+msgid "Reset to project defaults"
+msgstr ""
+
msgid "Resetting the authorization key for this project will require updating the authorization key in every alert source it is enabled in."
msgstr ""
@@ -19045,9 +19704,6 @@ msgstr ""
msgid "Resume"
msgstr ""
-msgid "Resume replication"
-msgstr ""
-
msgid "Resync"
msgstr ""
@@ -19692,6 +20348,9 @@ msgstr ""
msgid "SecurityReports|False positive"
msgstr ""
+msgid "SecurityReports|Group Security Dashboard"
+msgstr ""
+
msgid "SecurityReports|Hide dismissed"
msgstr ""
@@ -19746,10 +20405,13 @@ msgstr ""
msgid "SecurityReports|Remove project from dashboard"
msgstr ""
-msgid "SecurityReports|Report type"
+msgid "SecurityReports|Return to dashboard"
+msgstr ""
+
+msgid "SecurityReports|Scan details"
msgstr ""
-msgid "SecurityReports|Return to dashboard"
+msgid "SecurityReports|Scanner type"
msgstr ""
msgid "SecurityReports|Security Dashboard"
@@ -19806,6 +20468,9 @@ msgstr ""
msgid "SecurityReports|There was an error while generating the report."
msgstr ""
+msgid "SecurityReports|Unable to add %{invalidProjectsMessage}"
+msgstr ""
+
msgid "SecurityReports|Unable to add %{invalidProjects}"
msgstr ""
@@ -19920,6 +20585,9 @@ msgstr ""
msgid "Select due date"
msgstr ""
+msgid "Select file"
+msgstr ""
+
msgid "Select group or project"
msgstr ""
@@ -19983,9 +20651,6 @@ msgstr ""
msgid "Select the branch you want to set as the default for this project. All merge requests and commits will automatically be made against this branch unless you specify a different one."
msgstr ""
-msgid "Select the configured storage available for new repositories to be placed on."
-msgstr ""
-
msgid "Select the custom project template source group."
msgstr ""
@@ -20178,12 +20843,6 @@ msgstr ""
msgid "Service Desk is enabled but not yet active"
msgstr ""
-msgid "Service Desk is off"
-msgstr ""
-
-msgid "Service Desk is on"
-msgstr ""
-
msgid "Service Templates"
msgstr ""
@@ -20271,6 +20930,15 @@ msgstr ""
msgid "Set the milestone to %{milestone_reference}."
msgstr ""
+msgid "Set the number of concurrent requests this secondary node will make to the primary node while backfilling."
+msgstr ""
+
+msgid "Set the synchronization and verification capacity for the secondary node."
+msgstr ""
+
+msgid "Set the timeout in seconds to send a secondary node status to the primary and IPs allowed for the secondary nodes."
+msgstr ""
+
msgid "Set time estimate"
msgstr ""
@@ -20310,6 +20978,9 @@ msgstr ""
msgid "Set weight to %{weight}."
msgstr ""
+msgid "Set what should be replicated by choosing specific projects or groups by the secondary node."
+msgstr ""
+
msgid "SetPasswordToCloneLink|set a password"
msgstr ""
@@ -20436,6 +21107,9 @@ msgstr ""
msgid "Show complete raw log"
msgstr ""
+msgid "Show details"
+msgstr ""
+
msgid "Show file browser"
msgstr ""
@@ -20445,10 +21119,13 @@ msgstr ""
msgid "Show latest version"
msgstr ""
+msgid "Show list"
+msgstr ""
+
msgid "Show me everything"
msgstr ""
-msgid "Show me how"
+msgid "Show me how to add a pipeline"
msgstr ""
msgid "Show me more advanced stuff"
@@ -20501,9 +21178,6 @@ msgstr ""
msgid "Showing all issues"
msgstr ""
-msgid "Showing all labels"
-msgstr ""
-
msgid "Showing last %{size} of log -"
msgstr ""
@@ -20615,9 +21289,6 @@ msgstr ""
msgid "Skip outdated deployment jobs"
msgstr ""
-msgid "Skip this for now"
-msgstr ""
-
msgid "Skipped"
msgstr ""
@@ -20762,6 +21433,9 @@ msgstr ""
msgid "Something went wrong while adding your award. Please try again."
msgstr ""
+msgid "Something went wrong while applying the batch of suggestions. Please try again."
+msgstr ""
+
msgid "Something went wrong while applying the suggestion. Please try again."
msgstr ""
@@ -20864,10 +21538,13 @@ msgstr ""
msgid "Something went wrong while updating a requirement."
msgstr ""
+msgid "Something went wrong while updating assignees"
+msgstr ""
+
msgid "Something went wrong while updating your list settings"
msgstr ""
-msgid "Something went wrong with your automatic subscription renewal"
+msgid "Something went wrong with your automatic subscription renewal."
msgstr ""
msgid "Something went wrong, unable to add %{project} to dashboard"
@@ -20885,6 +21562,9 @@ msgstr ""
msgid "Something went wrong, unable to search projects"
msgstr ""
+msgid "Something went wrong."
+msgstr ""
+
msgid "Something went wrong. Please try again."
msgstr ""
@@ -21068,6 +21748,9 @@ msgstr ""
msgid "Source code"
msgstr ""
+msgid "Source code (%{fileExtension})"
+msgstr ""
+
msgid "Source is not available"
msgstr ""
@@ -21152,6 +21835,9 @@ msgstr ""
msgid "Stack trace"
msgstr ""
+msgid "Stacktrace snippet"
+msgstr ""
+
msgid "Stage"
msgstr ""
@@ -21257,6 +21943,9 @@ msgstr ""
msgid "Start thread & reopen %{noteable_name}"
msgstr ""
+msgid "Start using Directed Acyclic Graphs (DAG)"
+msgstr ""
+
msgid "Start your Free Gold Trial"
msgstr ""
@@ -21719,6 +22408,21 @@ msgstr ""
msgid "SuggestedColors|Very pale orange"
msgstr ""
+msgid "Suggestion is not applicable as the suggestion was not found."
+msgstr ""
+
+msgid "Suggestions are not applicable as one or more suggestions were not found."
+msgstr ""
+
+msgid "Suggestions are not applicable as their lines cannot overlap."
+msgstr ""
+
+msgid "Suggestions must all be on the same branch."
+msgstr ""
+
+msgid "Suggestions that change line count can't be added to batches, yet."
+msgstr ""
+
msgid "Suggestions:"
msgstr ""
@@ -21761,9 +22465,6 @@ msgstr ""
msgid "Synchronization disabled"
msgstr ""
-msgid "Synchronization of container repositories is disabled."
-msgstr ""
-
msgid "System"
msgstr ""
@@ -21944,6 +22645,38 @@ msgstr ""
msgid "Terms of Service and Privacy Policy"
msgstr ""
+msgid "Terraform|%{number} Terraform report failed to generate"
+msgid_plural "Terraform|%{number} Terraform reports failed to generate"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "Terraform|%{number} Terraform report was generated in your pipelines"
+msgid_plural "Terraform|%{number} Terraform reports were generated in your pipelines"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "Terraform|A Terraform report failed to generate."
+msgstr ""
+
+msgid "Terraform|A Terraform report was generated in your pipelines."
+msgstr ""
+
+msgid "Terraform|Generating the report caused an error."
+msgstr ""
+
+msgid "Terraform|Reported Resource Changes: %{addNum} to add, %{changeNum} to change, %{deleteNum} to delete"
+msgstr ""
+
+msgid "Terraform|The Terraform report %{name} failed to generate."
+msgstr ""
+
+msgid "Terraform|The Terraform report %{name} was generated in your pipelines."
+msgstr ""
+
msgid "Test"
msgstr ""
@@ -21972,7 +22705,7 @@ msgstr ""
msgid "TestHooks|Ensure the project has CI pipelines."
msgstr ""
-msgid "TestHooks|Ensure the project has at least one commit."
+msgid "TestHooks|Ensure the project has deployments."
msgstr ""
msgid "TestHooks|Ensure the project has issues."
@@ -22075,7 +22808,7 @@ msgstr ""
msgid "The Prometheus server responded with \"bad request\". Please check your queries are correct and are supported in your Prometheus version. %{documentationLink}"
msgstr ""
-msgid "The URL defined on the primary node that secondary nodes should use to contact it. Defaults to URL"
+msgid "The URL defined on the primary node that secondary nodes should use to contact it."
msgstr ""
msgid "The URL to use for connecting to Elasticsearch. Use a comma-separated list to support clustering (e.g., \"http://localhost:9200, http://localhost:9201\")."
@@ -22084,6 +22817,9 @@ msgstr ""
msgid "The X509 Certificate to use when mutual TLS is required to communicate with the external authorization service. If left blank, the server certificate is still validated when accessing over HTTPS."
msgstr ""
+msgid "The above settings apply to all projects with the selected compliance framework(s)."
+msgstr ""
+
msgid "The amount of seconds after which a request to get a secondary node status will time out."
msgstr ""
@@ -22385,9 +23121,6 @@ msgstr ""
msgid "The total stage shows the time it takes between creating an issue and deploying the code to production. The data will be automatically added once you have completed the full idea to production cycle."
msgstr ""
-msgid "The unique identifier for the Geo node. Must match %{geoNodeName} if it is set in gitlab.rb, otherwise it must match %{externalUrl} with a trailing slash"
-msgstr ""
-
msgid "The update action will time out after %{number_of_minutes} minutes. For big repositories, use a clone/push combination."
msgstr ""
@@ -22412,9 +23145,6 @@ msgstr ""
msgid "The user you are trying to deactivate has been active in the past %{minimum_inactive_days} days and cannot be deactivated"
msgstr ""
-msgid "The user-facing URL of the Geo node"
-msgstr ""
-
msgid "The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6."
msgstr ""
@@ -22505,6 +23235,9 @@ msgstr ""
msgid "There was a problem fetching project branches."
msgstr ""
+msgid "There was a problem fetching project tags."
+msgstr ""
+
msgid "There was a problem fetching project users."
msgstr ""
@@ -22556,6 +23289,9 @@ msgstr ""
msgid "There was an error fetching the %{replicableType}"
msgstr ""
+msgid "There was an error fetching the Geo Settings"
+msgstr ""
+
msgid "There was an error fetching the Node's Groups"
msgstr ""
@@ -22676,6 +23412,12 @@ msgstr ""
msgid "This %{issuable} is locked. Only <strong>project members</strong> can comment."
msgstr ""
+msgid "This %{noteableTypeText} is %{confidentialLinkStart}confidential%{linkEnd} and %{lockedLinkStart}locked%{linkEnd}."
+msgstr ""
+
+msgid "This %{noteableTypeText} is locked."
+msgstr ""
+
msgid "This %{viewer} could not be displayed because %{reason}. You can %{options} instead."
msgstr ""
@@ -22694,6 +23436,9 @@ msgstr ""
msgid "This action can lead to data loss. To prevent accidental actions we ask you to confirm your intention."
msgstr ""
+msgid "This also resolves all related threads"
+msgstr ""
+
msgid "This also resolves the discussion"
msgstr ""
@@ -22784,6 +23529,9 @@ msgstr ""
msgid "This epic does not exist or you don't have sufficient permission."
msgstr ""
+msgid "This feature is currently in beta. We invite you to %{linkStart}give feedback%{linkEnd}."
+msgstr ""
+
msgid "This feature requires local storage to be enabled"
msgstr ""
@@ -22817,7 +23565,7 @@ msgstr ""
msgid "This is a Work in Progress"
msgstr ""
-msgid "This is a confidential issue."
+msgid "This is a confidential %{noteableTypeText}."
msgstr ""
msgid "This is a delayed job to run in %{remainingTime}"
@@ -22844,9 +23592,6 @@ msgstr ""
msgid "This is your current session"
msgstr ""
-msgid "This issue is %{confidentialLinkStart}confidential%{linkEnd} and %{lockedLinkStart}locked%{linkEnd}."
-msgstr ""
-
msgid "This issue is confidential"
msgstr ""
@@ -22856,9 +23601,6 @@ msgstr ""
msgid "This issue is in a child epic of the filtered epic"
msgstr ""
-msgid "This issue is locked."
-msgstr ""
-
msgid "This job depends on other jobs with expired/erased artifacts: %{invalid_dependencies}"
msgstr ""
@@ -22958,6 +23700,9 @@ msgstr ""
msgid "This license has already expired."
msgstr ""
+msgid "This link points to external content"
+msgstr ""
+
msgid "This may expose confidential information as the selected fork is in another namespace that can have other members."
msgstr ""
@@ -22985,6 +23730,9 @@ msgstr ""
msgid "This page will be removed in a future release."
msgstr ""
+msgid "This pipeline does not use the %{codeStart}needs%{codeEnd} keyword and can't be represented as a directed acyclic graph."
+msgstr ""
+
msgid "This pipeline makes use of a predefined CI/CD configuration enabled by %{strongStart}Auto DevOps.%{strongEnd}"
msgstr ""
@@ -23213,6 +23961,9 @@ msgstr ""
msgid "Time from last commit to merge"
msgstr ""
+msgid "Time in seconds"
+msgstr ""
+
msgid "Time in seconds GitLab will wait for a response from the external service. When the service does not respond in time, access will be denied."
msgstr ""
@@ -23638,9 +24389,15 @@ msgstr ""
msgid "Total artifacts size: %{total_size}"
msgstr ""
+msgid "Total cores (CPUs)"
+msgstr ""
+
msgid "Total issues"
msgstr ""
+msgid "Total memory (GB)"
+msgstr ""
+
msgid "Total test time for all commits/merges"
msgstr ""
@@ -23842,6 +24599,9 @@ msgstr ""
msgid "U2F only works with HTTPS-enabled websites. Contact your administrator for more details."
msgstr ""
+msgid "UI Development Kit"
+msgstr ""
+
msgid "URL"
msgstr ""
@@ -23914,9 +24674,6 @@ msgstr ""
msgid "Unable to load the merge request widget. Try reloading the page."
msgstr ""
-msgid "Unable to process group import file"
-msgstr ""
-
msgid "Unable to resolve"
msgstr ""
@@ -23932,6 +24689,9 @@ msgstr ""
msgid "Unable to sign you in to the group with SAML due to \"%{reason}\""
msgstr ""
+msgid "Unable to suggest a path. Please refresh and try again."
+msgstr ""
+
msgid "Unable to update label prioritization at this time"
msgstr ""
@@ -23947,6 +24707,9 @@ msgstr ""
msgid "Unarchiving the project will restore people's ability to make changes to it. The repository can be committed to, and issues, comments, and other entities can be created. %{strong_start}Once active, this project shows up in the search and on the dashboard.%{strong_end}"
msgstr ""
+msgid "Unassign from commenting user"
+msgstr ""
+
msgid "Unblock"
msgstr ""
@@ -24022,6 +24785,9 @@ msgstr ""
msgid "Unreachable"
msgstr ""
+msgid "Unrecognized cluster type"
+msgstr ""
+
msgid "Unresolve"
msgstr ""
@@ -24082,6 +24848,9 @@ msgstr ""
msgid "Unsubscribes from this %{quick_action_target}."
msgstr ""
+msgid "Unsupported todo type passed. Supported todo types are: %{todo_types}"
+msgstr ""
+
msgid "Until"
msgstr ""
@@ -24109,6 +24878,9 @@ msgstr ""
msgid "Update approval rule"
msgstr ""
+msgid "Update approvers"
+msgstr ""
+
msgid "Update failed"
msgstr ""
@@ -24118,6 +24890,9 @@ msgstr ""
msgid "Update it"
msgstr ""
+msgid "Update iteration"
+msgstr ""
+
msgid "Update now"
msgstr ""
@@ -24163,7 +24938,7 @@ msgstr ""
msgid "Updated at"
msgstr ""
-msgid "Updated to"
+msgid "Updated to %{linkStart}chart v%{linkEnd}"
msgstr ""
msgid "Updating"
@@ -24193,6 +24968,9 @@ msgstr ""
msgid "Upload CSV file"
msgstr ""
+msgid "Upload License"
+msgstr ""
+
msgid "Upload New File"
msgstr ""
@@ -24274,16 +25052,19 @@ msgstr ""
msgid "UsageQuota|Pipelines"
msgstr ""
+msgid "UsageQuota|Purchase more storage"
+msgstr ""
+
msgid "UsageQuota|Repositories"
msgstr ""
msgid "UsageQuota|Repository"
msgstr ""
-msgid "UsageQuota|Storage"
+msgid "UsageQuota|Snippets"
msgstr ""
-msgid "UsageQuota|Storage usage:"
+msgid "UsageQuota|Storage"
msgstr ""
msgid "UsageQuota|This namespace has no projects which use shared runners"
@@ -24316,6 +25097,12 @@ msgstr ""
msgid "UsageQuota|Wikis"
msgstr ""
+msgid "UsageQuota|You used: %{usage} %{limit}"
+msgstr ""
+
+msgid "UsageQuota|out of %{formattedLimit} of your namespace storage"
+msgstr ""
+
msgid "Use %{code_start}::%{code_end} to create a %{link_start}scoped label set%{link_end} (eg. %{code_start}priority::1%{code_end})"
msgstr ""
@@ -24382,6 +25169,9 @@ msgstr ""
msgid "User IDs"
msgstr ""
+msgid "User Lists can only be created and modified with %{linkStart}the API%{linkEnd}"
+msgstr ""
+
msgid "User OAuth applications"
msgstr ""
@@ -24406,6 +25196,9 @@ msgstr ""
msgid "User key was successfully removed."
msgstr ""
+msgid "User list %{name} will be removed. Are you sure?"
+msgstr ""
+
msgid "User map"
msgstr ""
@@ -24427,148 +25220,10 @@ msgstr ""
msgid "User was successfully updated."
msgstr ""
-msgid "UserOnboardingTour|%{activeTour}/%{totalTours}"
-msgstr ""
-
-msgid "UserOnboardingTour|%{completed}/%{total} steps completed"
-msgstr ""
-
-msgid "UserOnboardingTour|%{emphasisStart}Well done!%{emphasisEnd}%{lineBreak}%{lineBreak}That's it for our guided tour, congratulations for making it all the way to the end!%{lineBreak}%{lineBreak}We hope this gave you a good overview of GitLab and how it can help you. We'll now show you how to create your own project and invite your colleagues."
-msgstr ""
-
-msgid "UserOnboardingTour|Adding other members to a project is done through Project Settings. Click on %{emphasisStart}Settings%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Alright, that's it for Commits. Let's take a look at the %{emphasisStart}Branches%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Awesome! Now click on %{emphasisStart}Members%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Click on one of the %{emphasisStart}Compare%{emphasisEnd} buttons to compare a branch to master."
-msgstr ""
-
-msgid "UserOnboardingTour|Click on one of the %{emphasisStart}pipeline IDs%{emphasisEnd} to see the details of a pipeline."
-msgstr ""
-
-msgid "UserOnboardingTour|Click to open the latest commit to see its details."
-msgstr ""
-
-msgid "UserOnboardingTour|Close 'Learn GitLab'"
-msgstr ""
-
-msgid "UserOnboardingTour|Commits are shown in chronological order and can be filtered by the commit message or by the branch."
-msgstr ""
-
-msgid "UserOnboardingTour|Create a project"
-msgstr ""
-
-msgid "UserOnboardingTour|Exit 'Learn GitLab'"
-msgstr ""
-
-msgid "UserOnboardingTour|Got it"
-msgstr ""
-
-msgid "UserOnboardingTour|Great job! %{clapHands} We hope the tour was helpful and that you learned how to use GitLab.%{lineBreak}%{lineBreak}We'd love to get your feedback on this tour.%{lineBreak}%{lineBreak}%{emphasisStart}How helpful would you say this guided tour was?%{emphasisEnd}%{lineBreak}%{lineBreak}"
-msgstr ""
-
-msgid "UserOnboardingTour|Guided GitLab Tour"
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can compare the changes of this branch to another one. Changes are divided by files so that it's easier to see what was changed where."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can create a project from scratch, start with a template or import a repository from other platforms. Whatever you choose, we'll guide you through the process.%{lineBreak}%{lineBreak}Fill in your new project information and click on %{emphasisStart}Create Project%{emphasisEnd} to progress to the next step."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can see the breakdown of the pipelines: its stages and jobs in each of the stages and their status.%{lineBreak}%{lineBreak}Our CI/CD pipelines are quite complex, most of our users have fewer and simpler pipelines."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can see the current members of the project (just you at the moment) and invite new members.%{lineBreak}%{lineBreak}You can invite multiple members at once (existing GitLab users or invite by email) and you can also set their roles and permissions.%{lineBreak}%{lineBreak}Add a few members and click on %{emphasisStart}Add to project%{emphasisEnd} to complete this step."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can see what changes were made with this commit, on what branch and if there's a related merge request. The status of the pipeline will also show up if CI/CD is set up.%{lineBreak}%{lineBreak}You can also comment on the lines of code that were changed and start a discussion with your colleagues!"
-msgstr ""
-
-msgid "UserOnboardingTour|Here's an overview of branches in the %{emphasisStart}%{projectName}%{emphasisEnd} project. They're split into Active and Stale.%{lineBreak}%{lineBreak}From here, you can create a new merge request from a branch, or compare the branch to any other branch in the project. By default, it will compare it to the master branch."
-msgstr ""
-
-msgid "UserOnboardingTour|Invite colleagues"
-msgstr ""
-
-msgid "UserOnboardingTour|Issues are great for communicating and keeping track of progress in GitLab. These are all issues that are open in the %{emphasisStart}%{projectName}%{emphasisEnd}.%{lineBreak}%{lineBreak}You can help us improve GitLab by contributing work to issues that are labeled <span class=\"badge color-label accept-mr-label\">Accepting merge requests</span>.%{lineBreak}%{lineBreak}This list can be filtered by labels, milestones, assignees, authors... We'll show you how it looks when the list is filtered by a label."
-msgstr ""
-
-msgid "UserOnboardingTour|Learn GitLab"
-msgstr ""
-
-msgid "UserOnboardingTour|Let's take a closer look at a merge request. Click on the title of one."
-msgstr ""
-
-msgid "UserOnboardingTour|Let's take a closer look at all the commits. Click on %{emphasisStart}Commits%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Let's take a closer look at the repository of this project. Click on %{emphasisStart}Repository%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|No thanks"
-msgstr ""
-
-msgid "UserOnboardingTour|Ok, let's go"
-msgstr ""
-
-msgid "UserOnboardingTour|Ok, show me"
-msgstr ""
-
-msgid "UserOnboardingTour|Open one of the issues by clicking on its title."
-msgstr ""
-
-msgid "UserOnboardingTour|Restart this step"
-msgstr ""
-
-msgid "UserOnboardingTour|Skip this step"
-msgstr ""
-
-msgid "UserOnboardingTour|Sweet! Your project was created and is ready to be used.%{lineBreak}%{lineBreak}You can start adding files to the repository or clone it. One last thing we want to show you is how to invite your colleagues to your new project."
-msgstr ""
-
-msgid "UserOnboardingTour|Take a look. Here's a nifty menu for quickly creating issues, merge requests, snippets, projects and groups. Click on it and select \"New project\" from the \"GitLab\" section to get started."
-msgstr ""
-
-msgid "UserOnboardingTour|Thanks for taking the guided tour. Remember, if you want to go through it again, you can start %{emphasisStart}Learn GitLab%{emphasisEnd} in the help menu on the top right."
-msgstr ""
-
-msgid "UserOnboardingTour|Thanks for the feedback! %{thumbsUp}"
-msgstr ""
-
-msgid "UserOnboardingTour|That's it for issues. Let'st take a look at %{emphasisStart}Merge Requests%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|That's it for merge requests. Now for the final part of this guided tour - the %{emphasisStart}CI/CD%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|That's it for the Repository. Let's take a look at the %{emphasisStart}Issues%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|The structure of this page is very similar to issues. Status, description, discussion and the sidebar are all here.%{lineBreak}%{lineBreak}But take a look below the description and you'll notice that there's more information about the merge request, the CI/CD pipeline and the options for approving it.%{lineBreak}%{lineBreak}Alongside the discussion you can also see more information about commits in this merge request, the status of pipelines and review all changes that were made."
-msgstr ""
-
-msgid "UserOnboardingTour|There's a lot of information here but don't worry, we'll go through it.%{lineBreak}%{lineBreak}On the top you can see the status of the issue and when it was opened and by whom. Directly below it is the issue description and below that are other %{emphasisStart}related issues%{emphasisEnd} and %{emphasisStart}merge requests%{emphasisEnd} (if any). Then below that is the %{emphasisStart}discussion%{emphasisEnd}, that's where most of the communication happens.%{lineBreak}%{lineBreak}On the right, there's a sidebar where you can view/change the %{emphasisStart}assignee, milestone, due date, labels, weight%{emphasisEnd}, etc."
+msgid "UserList|Delete %{name}?"
msgstr ""
-msgid "UserOnboardingTour|These are all the CI/CD pipelines we have for our %{emphasisStart}%{projectName}%{emphasisEnd} project.%{lineBreak}%{lineBreak}Here you can see the status of each pipeline, for what commit it's running for, its stages and the status for them."
-msgstr ""
-
-msgid "UserOnboardingTour|These are all the issues that are available for community contributions. Let's take a closer look at one of them."
-msgstr ""
-
-msgid "UserOnboardingTour|This is an overview of all merge requests in this project. Similarly to the issues overview it can be filtered down by things like labels, milestones, authors, assignees, etc."
-msgstr ""
-
-msgid "UserOnboardingTour|This is the repository for the %{emphasisStart}%{projectName}%{emphasisEnd} project. All our code is stored here. Feel free to explore and take a closer look at folders and files.%{lineBreak}%{lineBreak}Above the file structure you can see the latest commit, who the author is and the status of the CI/CD pipeline.%{lineBreak}%{lineBreak}If you scroll down below the file structure, you'll find the Readme of this project. This is defined in the README.md file at the root of the repository."
-msgstr ""
-
-msgid "UserOnboardingTour|Welcome to the project overview of the %{emphasisStart}%{projectName}%{emphasisEnd} project. This is the project that we use to work on GitLab. At first, a project seems like a simple repository, but at GitLab, a project is so much more.%{lineBreak}%{lineBreak}You can create projects for hosting your codebase, use it as an issue tracker, collaborate on code, and continuously build, test, and deploy your app with built-in GitLab CI/CD."
+msgid "UserList|created %{timeago}"
msgstr ""
msgid "UserProfile|Activity"
@@ -24721,6 +25376,9 @@ msgstr ""
msgid "UsersSelect|Unassigned"
msgstr ""
+msgid "Using %{codeStart}needs%{codeEnd} allows jobs to run before their stage is reached, as soon as their individual dependencies are met, which speeds up your pipelines."
+msgstr ""
+
msgid "Using %{code_start}::%{code_end} denotes a %{link_start}scoped label set%{link_end}"
msgstr ""
@@ -24805,9 +25463,6 @@ msgstr ""
msgid "Versions"
msgstr ""
-msgid "Very helpful"
-msgstr ""
-
msgid "View Documentation"
msgstr ""
@@ -24960,6 +25615,9 @@ msgstr ""
msgid "VisibilityLevel|Unknown"
msgstr ""
+msgid "Visit settings page"
+msgstr ""
+
msgid "VisualReviewApp|%{stepStart}Step 1%{stepEnd}. Copy the following script:"
msgstr ""
@@ -25053,6 +25711,9 @@ msgstr ""
msgid "VulnerabilityManagement|Something went wrong while trying to delete the comment. Please try again later."
msgstr ""
+msgid "VulnerabilityManagement|Something went wrong while trying to refresh the vulnerability. Please try again later."
+msgstr ""
+
msgid "VulnerabilityManagement|Something went wrong while trying to retrieve the vulnerability history. Please try again later."
msgstr ""
@@ -25095,6 +25756,9 @@ msgstr ""
msgid "Vulnerability|Class"
msgstr ""
+msgid "Vulnerability|Crash Address"
+msgstr ""
+
msgid "Vulnerability|Description"
msgstr ""
@@ -25170,9 +25834,6 @@ msgstr ""
msgid "We couldn't reach the Prometheus server. Either the server no longer exists or the configuration details need updating."
msgstr ""
-msgid "We created a short guided tour that will help you learn the basics of GitLab and how it will help you be better at your job. It should only take a couple of minutes. You will be guided by two types of helpers, best recognized by their color."
-msgstr ""
-
msgid "We detected potential spam in the %{humanized_resource_name}. Please solve the reCAPTCHA to proceed."
msgstr ""
@@ -25206,6 +25867,9 @@ msgstr ""
msgid "We've found no vulnerabilities"
msgstr ""
+msgid "Web Application Firewall"
+msgstr ""
+
msgid "Web IDE"
msgstr ""
@@ -25266,9 +25930,6 @@ msgstr ""
msgid "Welcome to GitLab.com<br>@%{name}!"
msgstr ""
-msgid "Welcome to the Guided GitLab Tour"
-msgstr ""
-
msgid "Welcome to the guided GitLab tour"
msgstr ""
@@ -25318,9 +25979,6 @@ msgstr ""
msgid "While it's rare to have no vulnerabilities, it can happen. In any event, we ask that you please double check your settings to make sure you've set up your dashboard correctly."
msgstr ""
-msgid "White helpers give contextual information."
-msgstr ""
-
msgid "Who can be an approver?"
msgstr ""
@@ -25366,12 +26024,18 @@ msgstr ""
msgid "WikiEmptyIssueMessage|Suggest wiki improvement"
msgstr ""
+msgid "WikiEmptyIssueMessage|You must be a group member in order to add wiki pages. If you have suggestions for how to improve the wiki for this group, consider opening an issue in the %{issues_link}."
+msgstr ""
+
msgid "WikiEmptyIssueMessage|You must be a project member in order to add wiki pages. If you have suggestions for how to improve the wiki for this project, consider opening an issue in the %{issues_link}."
msgstr ""
msgid "WikiEmptyIssueMessage|issue tracker"
msgstr ""
+msgid "WikiEmpty|A wiki is where you can store all the details about your group. This can include why you've created it, its principles, how to use it, and so on."
+msgstr ""
+
msgid "WikiEmpty|A wiki is where you can store all the details about your project. This can include why you've created it, its principles, how to use it, and so on."
msgstr ""
@@ -25381,12 +26045,21 @@ msgstr ""
msgid "WikiEmpty|Suggest wiki improvement"
msgstr ""
+msgid "WikiEmpty|The wiki lets you write documentation for your group"
+msgstr ""
+
msgid "WikiEmpty|The wiki lets you write documentation for your project"
msgstr ""
+msgid "WikiEmpty|This group has no wiki pages"
+msgstr ""
+
msgid "WikiEmpty|This project has no wiki pages"
msgstr ""
+msgid "WikiEmpty|You must be a group member in order to add wiki pages."
+msgstr ""
+
msgid "WikiEmpty|You must be a project member in order to add wiki pages."
msgstr ""
@@ -25663,13 +26336,13 @@ msgstr ""
msgid "You can now export your security dashboard to a CSV report."
msgstr ""
-msgid "You can now submit a merge request to get this change into the original branch."
+msgid "You can now manage alert endpoint configuration in the Alerts section on the Operations settings page. Fields on this page have been deprecated."
msgstr ""
-msgid "You can now submit a merge request to get this change into the original project."
+msgid "You can now submit a merge request to get this change into the original branch."
msgstr ""
-msgid "You can only add files when you are on a branch"
+msgid "You can now submit a merge request to get this change into the original project."
msgstr ""
msgid "You can only edit files when you are on a branch"
@@ -25891,7 +26564,7 @@ msgstr ""
msgid "You need to upload a Google Takeout archive."
msgstr ""
-msgid "You reached %{usage_in_percent} of %{namespace_name}'s capacity (%{used_storage} of %{storage_limit})"
+msgid "You reached %{usage_in_percent} of %{namespace_name}'s storage capacity (%{used_storage} of %{storage_limit})"
msgstr ""
msgid "You tried to fork %{link_to_the_project} but it failed for the following reason:"
@@ -26194,13 +26867,13 @@ msgstr ""
msgid "Your subscription expired!"
msgstr ""
-msgid "Your subscription has been downgraded"
+msgid "Your subscription has been downgraded."
msgstr ""
-msgid "Your subscription will automatically renew in %{remaining_days}"
+msgid "Your subscription will automatically renew in %{remaining_days}."
msgstr ""
-msgid "Your subscription will expire in %{remaining_days}"
+msgid "Your subscription will expire in %{remaining_days}."
msgstr ""
msgid "Zoom meeting added"
@@ -26282,9 +26955,6 @@ msgstr ""
msgid "branch name"
msgstr ""
-msgid "build pipeline reference mismatch"
-msgstr ""
-
msgid "by"
msgstr ""
@@ -26303,6 +26973,9 @@ msgstr ""
msgid "cannot block others"
msgstr ""
+msgid "cannot contain HTML/XML tags, including any word between angle brackets (<,>)."
+msgstr ""
+
msgid "cannot include leading slash or directory traversal."
msgstr ""
@@ -26333,65 +27006,6 @@ msgstr ""
msgid "ciReport|%{remainingPackagesCount} more"
msgstr ""
-msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability"
-msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities"
-msgstr[0] ""
-msgstr[1] ""
-msgstr[2] ""
-msgstr[3] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability for the source branch only"
-msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities for the source branch only"
-msgstr[0] ""
-msgstr[1] ""
-msgstr[2] ""
-msgstr[3] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerability"
-msgid_plural "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerabilities"
-msgstr[0] ""
-msgstr[1] ""
-msgstr[2] ""
-msgstr[3] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new vulnerability"
-msgid_plural "ciReport|%{reportType} %{status} detected %{newCount} new vulnerabilities"
-msgstr[0] ""
-msgstr[1] ""
-msgstr[2] ""
-msgstr[3] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities for the source branch only"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{fixedCount} fixed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} vulnerability for the source branch only"
-msgid_plural "ciReport|%{reportType} %{status} detected %{newCount} vulnerabilities for the source branch only"
-msgstr[0] ""
-msgstr[1] ""
-msgstr[2] ""
-msgstr[3] ""
-
-msgid "ciReport|%{reportType} %{status} detected no new vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected no vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected no vulnerabilities for the source branch only"
-msgstr ""
-
msgid "ciReport|%{reportType} is loading"
msgstr ""
@@ -26410,7 +27024,7 @@ msgstr ""
msgid "ciReport|All projects"
msgstr ""
-msgid "ciReport|All report types"
+msgid "ciReport|All scanner types"
msgstr ""
msgid "ciReport|All severities"
@@ -26434,6 +27048,9 @@ msgstr ""
msgid "ciReport|Container scanning detects known vulnerabilities in your docker images."
msgstr ""
+msgid "ciReport|Coverage Fuzzing"
+msgstr ""
+
msgid "ciReport|Create a merge request to implement this solution, or download and apply the patch manually."
msgstr ""
@@ -26594,6 +27211,9 @@ msgstr ""
msgid "customize"
msgstr ""
+msgid "data"
+msgstr ""
+
msgid "date must not be after 9999-12-31"
msgstr ""
@@ -26668,6 +27288,9 @@ msgstr ""
msgid "entries cannot contain HTML tags"
msgstr ""
+msgid "epic"
+msgstr ""
+
msgid "error"
msgstr ""
@@ -26689,9 +27312,6 @@ msgstr ""
msgid "expires on %{timebox_due_date}"
msgstr ""
-msgid "external_url"
-msgstr ""
-
msgid "failed"
msgstr ""
@@ -26729,9 +27349,6 @@ msgstr ""
msgid "from"
msgstr ""
-msgid "geo_node_name"
-msgstr ""
-
msgid "group"
msgstr ""
@@ -26753,9 +27370,6 @@ msgstr ""
msgid "https://your-bitbucket-server"
msgstr ""
-msgid "image"
-msgstr ""
-
msgid "image diff"
msgstr ""
@@ -26901,6 +27515,9 @@ msgstr ""
msgid "load it anyway"
msgstr ""
+msgid "loading"
+msgstr ""
+
msgid "locked by %{path_lock_user_name} %{created_at}"
msgstr ""
@@ -27031,9 +27648,6 @@ msgstr ""
msgid "mrWidget|Deployment statistics are not available currently"
msgstr ""
-msgid "mrWidget|Detect issues before deployment with a CI pipeline that continuously tests your code. We created a quick guide that will show you how to create one. Make your code more secure and more robust in just a minute."
-msgstr ""
-
msgid "mrWidget|Did not close"
msgstr ""
@@ -27196,6 +27810,9 @@ msgstr ""
msgid "mrWidget|To approve this merge request, please enter your password. This project requires all approvals to be authenticated."
msgstr ""
+msgid "mrWidget|Use %{linkStart}CI pipelines to test your code%{linkEnd}, simply add a GitLab CI configuration file to your project. It only takes a minute to make your code more secure and robust."
+msgstr ""
+
msgid "mrWidget|When this merge request is ready, remove the WIP: prefix from the title to allow it to be merged"
msgstr ""
@@ -27232,6 +27849,9 @@ msgstr ""
msgid "must be greater than start date"
msgstr ""
+msgid "must contain only valid frameworks"
+msgstr ""
+
msgid "my-awesome-group"
msgstr ""
@@ -27244,6 +27864,9 @@ msgstr ""
msgid "needs to be between 10 minutes and 1 month"
msgstr ""
+msgid "never"
+msgstr ""
+
msgid "never expires"
msgstr ""
@@ -27271,6 +27894,9 @@ msgstr ""
msgid "nounSeries|%{firstItem} and %{lastItem}"
msgstr ""
+msgid "nounSeries|%{item}"
+msgstr ""
+
msgid "nounSeries|%{item}, %{nextItem}"
msgstr ""
@@ -27417,6 +28043,9 @@ msgstr ""
msgid "revised"
msgstr ""
+msgid "satisfied"
+msgstr ""
+
msgid "score"
msgstr ""
@@ -27447,6 +28076,9 @@ msgstr ""
msgid "severity|Unknown"
msgstr ""
+msgid "should be an array of %{object_name} objects"
+msgstr ""
+
msgid "should be greater than or equal to %{access} inherited membership from group %{group_name}"
msgstr ""
@@ -27501,10 +28133,13 @@ msgstr ""
msgid "suggestPipeline|2/2: Commit your changes"
msgstr ""
-msgid "suggestPipeline|Commit the changes and your pipeline will automatically run for the first time."
+msgid "suggestPipeline|Choose %{boldStart}Code Quality%{boldEnd} to add a pipeline that tests the quality of your code."
msgstr ""
-msgid "suggestPipeline|We recommend the %{boldStart}Code Quality%{boldEnd} template, which will add a report widget to your Merge Requests. This way you’ll learn about code quality degradations much sooner. %{footerStart} Goodbye technical debt! %{footerEnd}"
+msgid "suggestPipeline|The template is ready! You can now commit it to create your first pipeline."
+msgstr ""
+
+msgid "suggestPipeline|We’re adding a GitLab CI configuration file to add a pipeline to the project. You could create it manually, but we recommend that you start with a GitLab template that works out of the box."
msgstr ""
msgid "syntax is correct"
@@ -27564,6 +28199,9 @@ msgstr ""
msgid "uses Kubernetes clusters to deploy your code!"
msgstr ""
+msgid "v%{version} published %{timeAgo}"
+msgstr ""
+
msgid "verify ownership"
msgstr ""
diff --git a/locale/sl_SI/gitlab.po b/locale/sl_SI/gitlab.po
index 26c53b6ac9a..03b7610d72b 100644
--- a/locale/sl_SI/gitlab.po
+++ b/locale/sl_SI/gitlab.po
@@ -2,7 +2,7 @@ msgid ""
msgstr ""
"Project-Id-Version: gitlab-ee\n"
"Report-Msgid-Bugs-To: \n"
-"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Last-Translator: \n"
"Language-Team: Slovenian\n"
"Language: sl_SI\n"
"MIME-Version: 1.0\n"
@@ -10,9 +10,11 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=4; plural=(n%100==1 ? 1 : n%100==2 ? 2 : n%100==3 || n%100==4 ? 3 : 0);\n"
"X-Crowdin-Project: gitlab-ee\n"
+"X-Crowdin-Project-ID: 288872\n"
"X-Crowdin-Language: sl\n"
"X-Crowdin-File: /master/locale/gitlab.pot\n"
-"PO-Revision-Date: 2020-06-08 15:19\n"
+"X-Crowdin-File-ID: 6\n"
+"PO-Revision-Date: 2020-07-02 01:27\n"
msgid " %{start} to %{end}"
msgstr ""
@@ -251,6 +253,13 @@ msgstr[1] ""
msgstr[2] ""
msgstr[3] ""
+msgid "%d milestone"
+msgid_plural "%d milestones"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
msgid "%d minute"
msgid_plural "%d minutes"
msgstr[0] ""
@@ -314,13 +323,6 @@ msgstr[1] ""
msgstr[2] ""
msgstr[3] ""
-msgid "%d url scanned"
-msgid_plural "%d urls scanned"
-msgstr[0] ""
-msgstr[1] ""
-msgstr[2] ""
-msgstr[3] ""
-
msgid "%d vulnerability"
msgid_plural "%d vulnerabilities"
msgstr[0] ""
@@ -412,9 +414,15 @@ msgstr[3] ""
msgid "%{count} related %{pluralized_subject}: %{links}"
msgstr ""
+msgid "%{dashboard_path} could not be found."
+msgstr ""
+
msgid "%{days} days until tags are automatically removed"
msgstr ""
+msgid "%{deployLinkStart}Use a template to deploy to ECS%{deployLinkEnd}, or use a docker image to %{commandsLinkStart}run AWS commands in GitLab CI/CD%{commandsLinkEnd}."
+msgstr ""
+
msgid "%{description}- Sentry event: %{errorUrl}- First seen: %{firstSeen}- Last seen: %{lastSeen} %{countLabel}: %{count}%{userCountLabel}: %{userCount}"
msgstr ""
@@ -457,12 +465,18 @@ msgstr ""
msgid "%{host} sign-in from new location"
msgstr ""
-msgid "%{icon}You are about to add %{usersTag} people to the discussion. Proceed with caution."
+msgid "%{icon}You are about to add %{usersTag} people to the discussion. They will all receive a notification."
+msgstr ""
+
+msgid "%{integrations_link_start}Integrations%{link_end} enable you to make third-party applications part of your GitLab workflow. If the available integrations don't meet your needs, consider using a %{webhooks_link_start}webhook%{link_end}."
msgstr ""
msgid "%{issuableType} will be removed! Are you sure?"
msgstr ""
+msgid "%{issuesCount} issues in this group"
+msgstr ""
+
msgid "%{issuesSize} issues"
msgstr ""
@@ -472,7 +486,7 @@ msgstr ""
msgid "%{labelStart}Class:%{labelEnd} %{class}"
msgstr ""
-msgid "%{labelStart}Confidence:%{labelEnd} %{confidence}"
+msgid "%{labelStart}Evidence:%{labelEnd} %{evidence}"
msgstr ""
msgid "%{labelStart}File:%{labelEnd} %{file}"
@@ -490,6 +504,9 @@ msgstr ""
msgid "%{labelStart}Report Type:%{labelEnd} %{reportType}"
msgstr ""
+msgid "%{labelStart}Scanner:%{labelEnd} %{scanner}"
+msgstr ""
+
msgid "%{labelStart}Severity:%{labelEnd} %{severity}"
msgstr ""
@@ -514,9 +531,6 @@ msgstr ""
msgid "%{link_start}Read more%{link_end} about role permissions"
msgstr ""
-msgid "%{link} can be used for binding events when something is happening within the project."
-msgstr ""
-
msgid "%{listToShow}, and %{awardsListLength} more."
msgstr ""
@@ -550,6 +564,9 @@ msgstr ""
msgid "%{name} found %{resultsString}"
msgstr ""
+msgid "%{name} is already being used for another emoji"
+msgstr ""
+
msgid "%{name} is scheduled for %{action}"
msgstr ""
@@ -599,6 +616,9 @@ msgstr ""
msgid "%{primary} (%{secondary})"
msgstr ""
+msgid "%{ref} cannot be added: %{error}"
+msgstr ""
+
msgid "%{releases} release"
msgid_plural "%{releases} releases"
msgstr[0] ""
@@ -609,13 +629,43 @@ msgstr[3] ""
msgid "%{remaining_approvals} left"
msgstr ""
-msgid "%{retryButtonStart}Try again%{retryButtonEnd} or %{newFileButtonStart}attach a new file%{newFileButtonEnd}"
+msgid "%{reportType} %{status} detected %{critical} critical and %{high} high severity vulnerabilities out of %{total}."
msgstr ""
-msgid "%{screenreaderOnlyStart}Keyboard shorcuts%{screenreaderOnlyEnd} Disabled"
+msgid "%{reportType} %{status} detected %{critical} critical and %{high} high severity vulnerabilities."
msgstr ""
-msgid "%{screenreaderOnlyStart}Keyboard shorcuts%{screenreaderOnlyEnd} Enabled"
+msgid "%{reportType} %{status} detected %{critical} critical severity vulnerabilities out of %{total}."
+msgstr ""
+
+msgid "%{reportType} %{status} detected %{critical} critical severity vulnerability."
+msgid_plural "%{reportType} %{status} detected %{critical} critical severity vulnerabilities."
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "%{reportType} %{status} detected %{high} high severity vulnerabilities out of %{total}."
+msgstr ""
+
+msgid "%{reportType} %{status} detected %{high} high severity vulnerability."
+msgid_plural "%{reportType} %{status} detected %{high} high severity vulnerabilities."
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "%{reportType} %{status} detected %{other} vulnerability."
+msgid_plural "%{reportType} %{status} detected %{other} vulnerabilities."
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "%{reportType} %{status} detected no new vulnerabilities."
+msgstr ""
+
+msgid "%{retryButtonStart}Try again%{retryButtonEnd} or %{newFileButtonStart}attach a new file%{newFileButtonEnd}"
msgstr ""
msgid "%{service_title} %{message}."
@@ -722,6 +772,12 @@ msgstr ""
msgid "%{token}..."
msgstr ""
+msgid "%{totalCpu} (%{freeSpacePercentage}%{percentSymbol} free)"
+msgstr ""
+
+msgid "%{totalMemory} (%{freeSpacePercentage}%{percentSymbol} free)"
+msgstr ""
+
msgid "%{totalWeight} total weight"
msgstr ""
@@ -752,6 +808,12 @@ msgstr ""
msgid "%{verb} %{time_spent_value} spent time."
msgstr ""
+msgid "%{webhooks_link_start}%{webhook_type}%{link_end} enable you to send notifications to web applications in response to events in a group or project."
+msgstr ""
+
+msgid "%{webhooks_link_start}%{webhook_type}%{link_end} enable you to send notifications to web applications in response to events in a group or project. We recommend using an %{integrations_link_start}integration%{link_end} in preference to a webhook."
+msgstr ""
+
msgid "'%{level}' is not a valid visibility level"
msgstr ""
@@ -780,9 +842,6 @@ msgstr ""
msgid "(No changes)"
msgstr ""
-msgid "(Show all)"
-msgstr ""
-
msgid "(check progress)"
msgstr ""
@@ -855,6 +914,9 @@ msgstr ""
msgid "0 for unlimited"
msgstr ""
+msgid "0 for unlimited, only effective with remote storage enabled."
+msgstr ""
+
msgid "1 %{type} addition"
msgid_plural "%{count} %{type} additions"
msgstr[0] ""
@@ -1032,6 +1094,12 @@ msgstr ""
msgid "<code>\"johnsmith@example.com\": \"johnsmith@example.com\"</code> will add \"By <a href=\"#\">johnsmith@example.com</a>\" to all issues and comments originally created by johnsmith@example.com. By default, the email address or username is masked to ensure the user's privacy. Use this option if you want to show the full email address."
msgstr ""
+msgid "<code>Masked</code> to prevent the values from being displayed in job logs (must match certain regexp requirements)."
+msgstr ""
+
+msgid "<code>Protected</code> to expose them to protected branches or tags only."
+msgstr ""
+
msgid "<namespace / project>"
msgstr ""
@@ -1059,9 +1127,6 @@ msgstr ""
msgid "A .NET Core console application template, customizable for any .NET Core project"
msgstr ""
-msgid "A DAG must have two dependent jobs to be visualized on this tab."
-msgstr ""
-
msgid "A GitBook site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
msgstr ""
@@ -1092,6 +1157,12 @@ msgstr ""
msgid "A deleted user"
msgstr ""
+msgid "A file has been changed."
+msgstr ""
+
+msgid "A file was not found."
+msgstr ""
+
msgid "A file with '%{file_name}' already exists in %{branch} branch"
msgstr ""
@@ -1158,7 +1229,7 @@ msgstr ""
msgid "A subscription will trigger a new pipeline on the default branch of this project when a pipeline successfully completes for a new tag on the %{default_branch_docs} of the subscribed project."
msgstr ""
-msgid "A terraform report was generated in your pipelines."
+msgid "A suggestion is not applicable."
msgstr ""
msgid "A user with write access to the source branch selected this option"
@@ -1329,6 +1400,9 @@ msgstr ""
msgid "Action to take when receiving an alert."
msgstr ""
+msgid "Actions"
+msgstr ""
+
msgid "Activate"
msgstr ""
@@ -1387,6 +1461,9 @@ msgstr ""
msgid "Add LICENSE"
msgstr ""
+msgid "Add New Node"
+msgstr ""
+
msgid "Add README"
msgstr ""
@@ -1462,6 +1539,9 @@ msgstr ""
msgid "Add approval rule"
msgstr ""
+msgid "Add approvers"
+msgstr ""
+
msgid "Add bold text"
msgstr ""
@@ -1519,6 +1599,9 @@ msgstr ""
msgid "Add strikethrough text"
msgstr ""
+msgid "Add suggestion to batch"
+msgstr ""
+
msgid "Add system hook"
msgstr ""
@@ -1618,6 +1701,9 @@ msgstr ""
msgid "Admin Note"
msgstr ""
+msgid "Admin Notifications"
+msgstr ""
+
msgid "Admin Overview"
msgstr ""
@@ -1729,6 +1815,9 @@ msgstr ""
msgid "AdminSettings|Integrations configured here will automatically apply to all projects on this instance."
msgstr ""
+msgid "AdminSettings|Moved to integrations"
+msgstr ""
+
msgid "AdminSettings|No required pipeline"
msgstr ""
@@ -1961,12 +2050,18 @@ msgstr[1] ""
msgstr[2] ""
msgstr[3] ""
+msgid "AlertManagement| assign yourself"
+msgstr ""
+
msgid "AlertManagement|Acknowledged"
msgstr ""
msgid "AlertManagement|Alert"
msgstr ""
+msgid "AlertManagement|Alert assignee(s): %{assignees}"
+msgstr ""
+
msgid "AlertManagement|Alert detail"
msgstr ""
@@ -1982,9 +2077,15 @@ msgstr ""
msgid "AlertManagement|All alerts"
msgstr ""
+msgid "AlertManagement|Assign To"
+msgstr ""
+
msgid "AlertManagement|Assign status"
msgstr ""
+msgid "AlertManagement|Assignee"
+msgstr ""
+
msgid "AlertManagement|Assignees"
msgstr ""
@@ -2003,9 +2104,6 @@ msgstr ""
msgid "AlertManagement|Edit"
msgstr ""
-msgid "AlertManagement|End time"
-msgstr ""
-
msgid "AlertManagement|Events"
msgstr ""
@@ -2024,10 +2122,13 @@ msgstr ""
msgid "AlertManagement|More information"
msgstr ""
+msgid "AlertManagement|No Matching Results"
+msgstr ""
+
msgid "AlertManagement|No alert data to display."
msgstr ""
-msgid "AlertManagement|No alerts available to display. If you think you're seeing this message in error, refresh the page."
+msgid "AlertManagement|No alerts available to display. See %{linkStart}enabling alert management%{linkEnd} for more information on adding alerts to the list."
msgstr ""
msgid "AlertManagement|No alerts to display."
@@ -2036,6 +2137,9 @@ msgstr ""
msgid "AlertManagement|None"
msgstr ""
+msgid "AlertManagement|None -"
+msgstr ""
+
msgid "AlertManagement|Open"
msgstr ""
@@ -2072,9 +2176,18 @@ msgstr ""
msgid "AlertManagement|There was an error displaying the alerts. Confirm your endpoint's configuration details to ensure alerts appear."
msgstr ""
+msgid "AlertManagement|There was an error while updating the assignee(s) list. Please try again."
+msgstr ""
+
+msgid "AlertManagement|There was an error while updating the assignee(s) of the alert. Please try again."
+msgstr ""
+
msgid "AlertManagement|There was an error while updating the status of the alert. Please try again."
msgstr ""
+msgid "AlertManagement|This assignee cannot be assigned to this alert."
+msgstr ""
+
msgid "AlertManagement|Tool"
msgstr ""
@@ -2222,6 +2335,9 @@ msgstr ""
msgid "Allow users to request access (if visibility is public or internal)"
msgstr ""
+msgid "Allowed Geo IP"
+msgstr ""
+
msgid "Allowed email domain restriction only permitted for top-level groups"
msgstr ""
@@ -2255,6 +2371,9 @@ msgstr ""
msgid "Amazon Web Services"
msgstr ""
+msgid "Amazon Web Services Logo"
+msgstr ""
+
msgid "Amazon authentication is not %{link_start}correctly configured%{link_end}. Ask your GitLab administrator if you want to use this service."
msgstr ""
@@ -2312,10 +2431,16 @@ msgstr ""
msgid "An error occurred when updating the issue weight"
msgstr ""
+msgid "An error occurred while acknowledging the notification. Refresh the page and try again."
+msgstr ""
+
+msgid "An error occurred while adding approvers"
+msgstr ""
+
msgid "An error occurred while adding formatted title for epic"
msgstr ""
-msgid "An error occurred while checking group path"
+msgid "An error occurred while checking group path. Please refresh and try again."
msgstr ""
msgid "An error occurred while committing your changes."
@@ -2459,7 +2584,7 @@ msgstr ""
msgid "An error occurred while loading milestones"
msgstr ""
-msgid "An error occurred while loading terraform report"
+msgid "An error occurred while loading project creation UI"
msgstr ""
msgid "An error occurred while loading the data. Please try again."
@@ -2513,6 +2638,9 @@ msgstr ""
msgid "An error occurred while rendering preview broadcast message"
msgstr ""
+msgid "An error occurred while rendering the editor"
+msgstr ""
+
msgid "An error occurred while reordering issues."
msgstr ""
@@ -2609,7 +2737,7 @@ msgstr ""
msgid "Analyze your dependencies for known vulnerabilities."
msgstr ""
-msgid "Analyze your source code and git history for secrets"
+msgid "Analyze your source code and git history for secrets."
msgstr ""
msgid "Analyze your source code for known vulnerabilities."
@@ -2723,12 +2851,18 @@ msgstr ""
msgid "Apply suggestion"
msgstr ""
+msgid "Apply suggestions"
+msgstr ""
+
msgid "Apply template"
msgstr ""
msgid "Apply this approval rule to any branch or a specific protected branch."
msgstr ""
+msgid "Applying"
+msgstr ""
+
msgid "Applying a template will replace the existing issue description. Any changes you have made will be lost."
msgstr ""
@@ -2741,12 +2875,18 @@ msgstr ""
msgid "Applying multiple commands"
msgstr ""
-msgid "Applying suggestion"
+msgid "Applying suggestion..."
+msgstr ""
+
+msgid "Applying suggestions..."
msgstr ""
msgid "Approval rules"
msgstr ""
+msgid "Approval rules reset to project defaults"
+msgstr ""
+
msgid "ApprovalRuleRemove|%d member"
msgid_plural "ApprovalRuleRemove|%d members"
msgstr[0] ""
@@ -2808,6 +2948,9 @@ msgstr ""
msgid "Approved"
msgstr ""
+msgid "Approved MRs"
+msgstr ""
+
msgid "Approved by: "
msgstr ""
@@ -3021,6 +3164,9 @@ msgstr ""
msgid "Assign"
msgstr ""
+msgid "Assign Iteration"
+msgstr ""
+
msgid "Assign custom color like #FF0000"
msgstr ""
@@ -3039,6 +3185,9 @@ msgstr ""
msgid "Assign to"
msgstr ""
+msgid "Assign to commenting user"
+msgstr ""
+
msgid "Assign yourself to these issues"
msgstr ""
@@ -3067,6 +3216,9 @@ msgstr[1] ""
msgstr[2] ""
msgstr[3] ""
+msgid "Assignee has no permissions"
+msgstr ""
+
msgid "Assignee lists not available with your current license"
msgstr ""
@@ -3076,6 +3228,9 @@ msgstr ""
msgid "Assignee(s)"
msgstr ""
+msgid "Assignees"
+msgstr ""
+
msgid "Assigns %{assignee_users_sentence}."
msgstr ""
@@ -3113,6 +3268,9 @@ msgstr ""
msgid "Audit Events is a way to keep track of important events that happened in GitLab."
msgstr ""
+msgid "Audit Log"
+msgstr ""
+
msgid "AuditEvents|(removed)"
msgstr ""
@@ -3149,6 +3307,9 @@ msgstr ""
msgid "AuditLogs|IP Address"
msgstr ""
+msgid "AuditLogs|Member Events"
+msgstr ""
+
msgid "AuditLogs|No matching %{type} found."
msgstr ""
@@ -3431,9 +3592,6 @@ msgstr ""
msgid "Badges|e.g. %{exampleUrl}"
msgstr ""
-msgid "Badge|New"
-msgstr ""
-
msgid "Balsamiq file could not be loaded."
msgstr ""
@@ -3482,6 +3640,12 @@ msgstr ""
msgid "Below you will find all the groups that are public."
msgstr ""
+msgid "Beta"
+msgstr ""
+
+msgid "Bi-weekly code coverage"
+msgstr ""
+
msgid "Billing"
msgstr ""
@@ -3542,9 +3706,15 @@ msgstr ""
msgid "Bitbucket Server Import"
msgstr ""
+msgid "Bitbucket Server import"
+msgstr ""
+
msgid "Bitbucket import"
msgstr ""
+msgid "Blame"
+msgstr ""
+
msgid "Blocked"
msgstr ""
@@ -3557,9 +3727,6 @@ msgstr ""
msgid "Blog"
msgstr ""
-msgid "Blue helpers indicate an action to be taken."
-msgstr ""
-
msgid "Board name"
msgstr ""
@@ -3758,6 +3925,9 @@ msgstr ""
msgid "Broadcast Message was successfully updated."
msgstr ""
+msgid "Broadcast Messages"
+msgstr ""
+
msgid "Browse Directory"
msgstr ""
@@ -3812,6 +3982,9 @@ msgstr ""
msgid "By %{user_name}"
msgstr ""
+msgid "By URL"
+msgstr ""
+
msgid "By default GitLab sends emails in HTML and plain text formats so mail clients can choose what format to use. Disable this option if you only want to send emails in plain text format."
msgstr ""
@@ -3917,6 +4090,9 @@ msgstr ""
msgid "Can override approvers and approvals required per merge request"
msgstr ""
+msgid "Can't apply as this line has changed or the suggestion already matches its content."
+msgstr ""
+
msgid "Can't create snippet: %{err}"
msgstr ""
@@ -4103,9 +4279,6 @@ msgstr ""
msgid "Changes are still tracked. Useful for cluster/index migrations."
msgstr ""
-msgid "Changes are unknown"
-msgstr ""
-
msgid "Changes suppressed. Click to show."
msgstr ""
@@ -4412,15 +4585,9 @@ msgstr ""
msgid "Choose what content you want to see on a group’s overview page"
msgstr ""
-msgid "Choose which groups you wish to synchronize to this secondary node"
-msgstr ""
-
msgid "Choose which repositories you want to connect and run CI/CD pipelines."
msgstr ""
-msgid "Choose which shards you wish to synchronize to this secondary node"
-msgstr ""
-
msgid "Choose your framework"
msgstr ""
@@ -4718,9 +4885,15 @@ msgstr ""
msgid "Cluster does not exist"
msgstr ""
+msgid "Cluster is required for Stages::ClusterEndpointInserter"
+msgstr ""
+
msgid "Cluster level"
msgstr ""
+msgid "Cluster type must be specificed for Stages::ClusterEndpointInserter"
+msgstr ""
+
msgid "ClusterIntegration| %{custom_domain_start}More information%{custom_domain_end}."
msgstr ""
@@ -4832,7 +5005,7 @@ msgstr ""
msgid "ClusterIntegration|Cert-Manager"
msgstr ""
-msgid "ClusterIntegration|Cert-Manager is a native Kubernetes certificate management controller that helps with issuing certificates. Installing Cert-Manager on your cluster will issue a certificate by %{letsEncrypt} and ensure that certificates are valid and up-to-date."
+msgid "ClusterIntegration|Cert-Manager is a native Kubernetes certificate management controller that helps with issuing certificates. Installing Cert-Manager on your cluster will issue a certificate by %{linkStart}Let's Encrypt%{linkEnd} and ensure that certificates are valid and up-to-date."
msgstr ""
msgid "ClusterIntegration|Certificate Authority bundle (PEM format)"
@@ -4859,9 +5032,6 @@ msgstr ""
msgid "ClusterIntegration|Clear the local cache of namespace and service accounts. This is necessary if your integration has become out of sync. The cache is repopulated during the next CI job that requires namespace and service accounts."
msgstr ""
-msgid "ClusterIntegration|Cloud Run"
-msgstr ""
-
msgid "ClusterIntegration|Cluster being created"
msgstr ""
@@ -4946,7 +5116,7 @@ msgstr ""
msgid "ClusterIntegration|Crossplane"
msgstr ""
-msgid "ClusterIntegration|Crossplane enables declarative provisioning of managed services from your cloud of choice using %{codeStart}kubectl%{codeEnd} or %{gitlabIntegrationLink}. Crossplane runs inside your Kubernetes cluster and supports secure connectivity and secrets management between app containers and the cloud services they depend on."
+msgid "ClusterIntegration|Crossplane enables declarative provisioning of managed services from your cloud of choice using %{codeStart}kubectl%{codeEnd} or %{linkStart}GitLab Integration%{linkEnd}. Crossplane runs inside your Kubernetes cluster and supports secure connectivity and secrets management between app containers and the cloud services they depend on."
msgstr ""
msgid "ClusterIntegration|Deletes all GitLab resources attached to this cluster during removal"
@@ -5030,9 +5200,6 @@ msgstr ""
msgid "ClusterIntegration|GitLab-managed cluster"
msgstr ""
-msgid "ClusterIntegration|Gitlab Integration"
-msgstr ""
-
msgid "ClusterIntegration|Global default"
msgstr ""
@@ -5078,7 +5245,7 @@ msgstr ""
msgid "ClusterIntegration|Ingress gives you a way to route requests to services based on the request host or path, centralizing a number of services into a single entrypoint."
msgstr ""
-msgid "ClusterIntegration|Installing Ingress may incur additional costs. Learn more about %{pricingLink}."
+msgid "ClusterIntegration|Installing Ingress may incur additional costs. Learn more about %{linkStart}pricing%{linkEnd}."
msgstr ""
msgid "ClusterIntegration|Instance cluster"
@@ -5093,7 +5260,7 @@ msgstr ""
msgid "ClusterIntegration|Issuer Email"
msgstr ""
-msgid "ClusterIntegration|Issuers represent a certificate authority. You must provide an email address for your Issuer. "
+msgid "ClusterIntegration|Issuers represent a certificate authority. You must provide an email address for your Issuer."
msgstr ""
msgid "ClusterIntegration|Jupyter Hostname"
@@ -5165,9 +5332,6 @@ msgstr ""
msgid "ClusterIntegration|Learn more about instance Kubernetes clusters"
msgstr ""
-msgid "ClusterIntegration|Let's Encrypt"
-msgstr ""
-
msgid "ClusterIntegration|Loading IAM Roles"
msgstr ""
@@ -5279,7 +5443,7 @@ msgstr ""
msgid "ClusterIntegration|Prometheus"
msgstr ""
-msgid "ClusterIntegration|Prometheus is an open-source monitoring system with %{gitlabIntegrationLink} to monitor deployed applications."
+msgid "ClusterIntegration|Prometheus is an open-source monitoring system with %{linkStart}GitLab Integration%{linkEnd} to monitor deployed applications."
msgstr ""
msgid "ClusterIntegration|Provider details"
@@ -5435,10 +5599,10 @@ msgstr ""
msgid "ClusterIntegration|Select zone to choose machine type"
msgstr ""
-msgid "ClusterIntegration|Send Cilium Logs"
+msgid "ClusterIntegration|Send Container Network Policies Logs"
msgstr ""
-msgid "ClusterIntegration|Send ModSecurity Logs"
+msgid "ClusterIntegration|Send Web Application Firewall Logs"
msgstr ""
msgid "ClusterIntegration|Service Token"
@@ -5606,15 +5770,12 @@ msgstr ""
msgid "ClusterIntegration|documentation"
msgstr ""
-msgid "ClusterIntegration|installed via %{installed_via}"
+msgid "ClusterIntegration|installed via %{linkStart}Cloud Run%{linkEnd}"
msgstr ""
msgid "ClusterIntegration|meets the requirements"
msgstr ""
-msgid "ClusterIntegration|pricing"
-msgstr ""
-
msgid "ClusterIntegration|sign up"
msgstr ""
@@ -5651,6 +5812,15 @@ msgstr ""
msgid "Code"
msgstr ""
+msgid "Code Coverage: %{coveragePercentage}%{percentSymbol}"
+msgstr ""
+
+msgid "Code Coverage| Empty code coverage data"
+msgstr ""
+
+msgid "Code Coverage|Couldn't fetch the code coverage data"
+msgstr ""
+
msgid "Code Owners"
msgstr ""
@@ -5705,7 +5875,7 @@ msgstr ""
msgid "Collapse approvers"
msgstr ""
-msgid "Collapse child epics"
+msgid "Collapse milestones"
msgstr ""
msgid "Collapse replies"
@@ -5723,6 +5893,9 @@ msgstr ""
msgid "Coming soon"
msgstr ""
+msgid "Comma-separated, e.g. '1.1.1.1, 2.2.2.0/24'"
+msgstr ""
+
msgid "Command"
msgstr ""
@@ -5895,6 +6068,12 @@ msgstr ""
msgid "Compliance framework (optional)"
msgstr ""
+msgid "Compliance frameworks"
+msgstr ""
+
+msgid "ComplianceDashboard|created by:"
+msgstr ""
+
msgid "ComplianceFramework|GDPR"
msgstr ""
@@ -6033,6 +6212,9 @@ msgstr ""
msgid "Connection timed out"
msgstr ""
+msgid "Connection timeout"
+msgstr ""
+
msgid "Contact sales to upgrade"
msgstr ""
@@ -6092,9 +6274,6 @@ msgstr ""
msgid "ContainerRegistry|CLI Commands"
msgstr ""
-msgid "ContainerRegistry|Compressed Size"
-msgstr ""
-
msgid "ContainerRegistry|Container Registry"
msgstr ""
@@ -6110,6 +6289,9 @@ msgstr ""
msgid "ContainerRegistry|Copy push command"
msgstr ""
+msgid "ContainerRegistry|Delete selected"
+msgstr ""
+
msgid "ContainerRegistry|Docker connection error"
msgstr ""
@@ -6143,16 +6325,16 @@ msgstr ""
msgid "ContainerRegistry|If you are not already logged in, you need to authenticate to the Container Registry by using your GitLab username and password. If you have %{twofaDocLinkStart}Two-Factor Authentication%{twofaDocLinkEnd} enabled, use a %{personalAccessTokensDocLinkStart}Personal Access Token%{personalAccessTokensDocLinkEnd} instead of a password."
msgstr ""
-msgid "ContainerRegistry|Image ID"
+msgid "ContainerRegistry|Image ID: %{imageId}"
msgstr ""
msgid "ContainerRegistry|Image Repositories"
msgstr ""
-msgid "ContainerRegistry|Keep and protect the images that matter most."
+msgid "ContainerRegistry|Image tags"
msgstr ""
-msgid "ContainerRegistry|Last Updated"
+msgid "ContainerRegistry|Keep and protect the images that matter most."
msgstr ""
msgid "ContainerRegistry|Login"
@@ -6167,6 +6349,9 @@ msgstr ""
msgid "ContainerRegistry|Please contact your administrator."
msgstr ""
+msgid "ContainerRegistry|Published %{timeInfo}"
+msgstr ""
+
msgid "ContainerRegistry|Push an image"
msgstr ""
@@ -6182,9 +6367,6 @@ msgstr ""
msgid "ContainerRegistry|Remove repository"
msgstr ""
-msgid "ContainerRegistry|Remove selected tags"
-msgstr ""
-
msgid "ContainerRegistry|Remove tag"
msgid_plural "ContainerRegistry|Remove tags"
msgstr[0] ""
@@ -6216,9 +6398,6 @@ msgstr ""
msgid "ContainerRegistry|Sorry, your filter produced no results."
msgstr ""
-msgid "ContainerRegistry|Tag"
-msgstr ""
-
msgid "ContainerRegistry|Tag expiration policy"
msgstr ""
@@ -6363,21 +6542,6 @@ msgstr ""
msgid "Control the display of third party offers."
msgstr ""
-msgid "Control the maximum concurrency of LFS/attachment backfill for this secondary node"
-msgstr ""
-
-msgid "Control the maximum concurrency of container repository operations for this Geo node"
-msgstr ""
-
-msgid "Control the maximum concurrency of repository backfill for this secondary node"
-msgstr ""
-
-msgid "Control the maximum concurrency of verification operations for this Geo node"
-msgstr ""
-
-msgid "Control the minimum interval in days that a repository should be reverified for this primary node"
-msgstr ""
-
msgid "Cookie domain"
msgstr ""
@@ -6519,6 +6683,9 @@ msgstr ""
msgid "Could not find design."
msgstr ""
+msgid "Could not find iteration"
+msgstr ""
+
msgid "Could not remove the trigger."
msgstr ""
@@ -6555,6 +6722,12 @@ msgstr ""
msgid "Coverage"
msgstr ""
+msgid "Coverage Fuzzing"
+msgstr ""
+
+msgid "Crash State"
+msgstr ""
+
msgid "Create"
msgstr ""
@@ -6603,6 +6776,9 @@ msgstr ""
msgid "Create a personal access token on your account to pull or push via %{protocol}."
msgstr ""
+msgid "Create a project pre-populated with the necessary files to get you started quickly."
+msgstr ""
+
msgid "Create an account using:"
msgstr ""
@@ -6771,6 +6947,9 @@ msgstr ""
msgid "Creates branch '%{branch_name}' and a merge request to resolve this issue."
msgstr ""
+msgid "Creating"
+msgstr ""
+
msgid "Creating epic"
msgstr ""
@@ -6822,7 +7001,10 @@ msgstr ""
msgid "Current vulnerabilities count"
msgstr ""
-msgid "CurrentUser|Buy CI minutes"
+msgid "CurrentUser|Buy Pipeline minutes"
+msgstr ""
+
+msgid "CurrentUser|One of your groups is running out"
msgstr ""
msgid "CurrentUser|Profile"
@@ -6837,6 +7019,9 @@ msgstr ""
msgid "CurrentUser|Upgrade"
msgstr ""
+msgid "Custom Attributes"
+msgstr ""
+
msgid "Custom CI configuration path"
msgstr ""
@@ -7102,12 +7287,18 @@ msgstr ""
msgid "DAG"
msgstr ""
+msgid "DAG visualization requires at least 3 dependent jobs."
+msgstr ""
+
msgid "DNS"
msgstr ""
msgid "Dashboard"
msgstr ""
+msgid "Dashboard uid not found"
+msgstr ""
+
msgid "DashboardProjects|All"
msgstr ""
@@ -7132,6 +7323,9 @@ msgstr ""
msgid "Data is still calculating..."
msgstr ""
+msgid "Datasource name not found"
+msgstr ""
+
msgid "Date"
msgstr ""
@@ -7312,6 +7506,9 @@ msgstr ""
msgid "Delete this attachment"
msgstr ""
+msgid "Delete user list"
+msgstr ""
+
msgid "Delete variable"
msgstr ""
@@ -7638,6 +7835,9 @@ msgstr ""
msgid "Deploying to"
msgstr ""
+msgid "Deploying to AWS is easy with GitLab"
+msgstr ""
+
msgid "Deployment Frequency"
msgstr ""
@@ -8002,6 +8202,9 @@ msgstr ""
msgid "Dismissed on pipeline %{pipelineLink} at %{projectLink}"
msgstr ""
+msgid "Display alerts from all your monitoring tools directly within GitLab."
+msgstr ""
+
msgid "Display name"
msgstr ""
@@ -8059,9 +8262,6 @@ msgstr ""
msgid "Don't show again"
msgstr ""
-msgid "Don't worry, you can access this tour by clicking on the help icon in the top right corner and choose <strong>Learn GitLab</strong>."
-msgstr ""
-
msgid "Done"
msgstr ""
@@ -8188,6 +8388,9 @@ msgstr ""
msgid "Edit Release"
msgstr ""
+msgid "Edit Slack integration"
+msgstr ""
+
msgid "Edit Snippet"
msgstr ""
@@ -8227,6 +8430,9 @@ msgstr ""
msgid "Edit issues"
msgstr ""
+msgid "Edit iteration"
+msgstr ""
+
msgid "Edit public deploy key"
msgstr ""
@@ -8275,6 +8481,9 @@ msgstr ""
msgid "Email"
msgstr ""
+msgid "Email Notification"
+msgstr ""
+
msgid "Email address"
msgstr ""
@@ -8287,6 +8496,9 @@ msgstr ""
msgid "Email not verified. Please verify your email in Salesforce."
msgstr ""
+msgid "Email notification for unknown sign-ins"
+msgstr ""
+
msgid "Email patch"
msgstr ""
@@ -8518,6 +8730,9 @@ msgstr ""
msgid "Enforce DNS rebinding attack protection"
msgstr ""
+msgid "Enforce personal access token expiration"
+msgstr ""
+
msgid "Ensure connectivity is available from the GitLab server to the Prometheus server"
msgstr ""
@@ -8587,6 +8802,9 @@ msgstr ""
msgid "Enter the name of your application, and we'll return a unique %{type}."
msgstr ""
+msgid "Enter weights for storages for new repositories."
+msgstr ""
+
msgid "Enter your password to approve"
msgstr ""
@@ -8596,15 +8814,27 @@ msgstr ""
msgid "Environment does not have deployments"
msgstr ""
+msgid "Environment is required for Stages::MetricEndpointInserter"
+msgstr ""
+
+msgid "Environment is required for Stages::VariableEndpointInserter"
+msgstr ""
+
msgid "Environment scope"
msgstr ""
+msgid "Environment variables are applied to all project environments in this instance via the Runner. You can use environment variables for passwords, secret keys, etc. Make variables available to the running application by prepending the variable key with <code>K8S_SECRET_</code>. You can set variables to be:"
+msgstr ""
+
msgid "Environment variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. Additionally, they can be masked so they are hidden in job logs, though they must match certain regexp requirements to do so. You can use environment variables for passwords, secret keys, or whatever you want."
msgstr ""
msgid "Environment variables are configured by your administrator to be %{link_start}protected%{link_end} by default"
msgstr ""
+msgid "Environment variables on this GitLab instance are configured to be %{link_start}protected%{link_end} by default"
+msgstr ""
+
msgid "Environment:"
msgstr ""
@@ -9136,6 +9366,9 @@ msgstr ""
msgid "EventFilterBy|Filter by comments"
msgstr ""
+msgid "EventFilterBy|Filter by designs"
+msgstr ""
+
msgid "EventFilterBy|Filter by epic events"
msgstr ""
@@ -9268,15 +9501,15 @@ msgstr ""
msgid "Expand approvers"
msgstr ""
-msgid "Expand child epics"
-msgstr ""
-
msgid "Expand down"
msgstr ""
msgid "Expand dropdown"
msgstr ""
+msgid "Expand milestones"
+msgstr ""
+
msgid "Expand sidebar"
msgstr ""
@@ -9292,6 +9525,9 @@ msgstr ""
msgid "Expiration date"
msgstr ""
+msgid "Expiration not enforced"
+msgstr ""
+
msgid "Expiration policy for the Container Registry is a perfect solution for keeping the Registry space down while still enjoying the full power of GitLab CI/CD."
msgstr ""
@@ -9412,6 +9648,9 @@ msgstr ""
msgid "Failed Jobs"
msgstr ""
+msgid "Failed on"
+msgstr ""
+
msgid "Failed to add a Zoom meeting"
msgstr ""
@@ -9472,13 +9711,19 @@ msgstr ""
msgid "Failed to install."
msgstr ""
+msgid "Failed to load assignees. Please try again."
+msgstr ""
+
+msgid "Failed to load authors. Please try again."
+msgstr ""
+
msgid "Failed to load emoji list."
msgstr ""
msgid "Failed to load error details from Sentry."
msgstr ""
-msgid "Failed to load errors from Sentry. Error message: %{errorMessage}"
+msgid "Failed to load errors from Sentry."
msgstr ""
msgid "Failed to load group activity metrics. Please try again."
@@ -9559,6 +9804,9 @@ msgstr ""
msgid "Failed to set due date because the date format is invalid."
msgstr ""
+msgid "Failed to set iteration on this issue. Please try again."
+msgstr ""
+
msgid "Failed to signing using smartcard authentication"
msgstr ""
@@ -9781,13 +10029,10 @@ msgstr ""
msgid "FeatureFlags|Target environments"
msgstr ""
-msgid "FeatureFlags|There are no active feature flags"
+msgid "FeatureFlags|There was an error fetching the feature flags."
msgstr ""
-msgid "FeatureFlags|There are no inactive feature flags"
-msgstr ""
-
-msgid "FeatureFlags|There was an error fetching the feature flags."
+msgid "FeatureFlags|There was an error retrieving user lists"
msgstr ""
msgid "FeatureFlags|Try again in a few moments or contact your support team."
@@ -9799,9 +10044,18 @@ msgstr ""
msgid "FeatureFlag|Delete strategy"
msgstr ""
+msgid "FeatureFlag|List"
+msgstr ""
+
msgid "FeatureFlag|Percentage"
msgstr ""
+msgid "FeatureFlag|Select a user list"
+msgstr ""
+
+msgid "FeatureFlag|There are no configured user lists"
+msgstr ""
+
msgid "FeatureFlag|Type"
msgstr ""
@@ -9958,6 +10212,12 @@ msgstr ""
msgid "Filter..."
msgstr ""
+msgid "Find File"
+msgstr ""
+
+msgid "Find bugs in your code with coverage-guided fuzzing"
+msgstr ""
+
msgid "Find by path"
msgstr ""
@@ -10222,6 +10482,9 @@ msgstr ""
msgid "Geo allows you to replicate your GitLab instance to other geographical locations."
msgstr ""
+msgid "Geo nodes are paused using a command run on the node"
+msgstr ""
+
msgid "GeoNodeStatusEvent|%{timeAgoStr} (%{pendingEvents} events)"
msgstr ""
@@ -10306,6 +10569,9 @@ msgstr ""
msgid "GeoNodes|Not checksummed"
msgstr ""
+msgid "GeoNodes|Package files"
+msgstr ""
+
msgid "GeoNodes|Pausing replication stops the sync process. Are you sure?"
msgstr ""
@@ -10324,6 +10590,9 @@ msgstr ""
msgid "GeoNodes|Replication slots"
msgstr ""
+msgid "GeoNodes|Replication status"
+msgstr ""
+
msgid "GeoNodes|Repositories"
msgstr ""
@@ -10534,6 +10803,9 @@ msgstr ""
msgid "Geo|Synchronization failed - %{error}"
msgstr ""
+msgid "Geo|Synchronization of %{itemTitle} is disabled."
+msgstr ""
+
msgid "Geo|The database is currently %{db_lag} behind the primary node."
msgstr ""
@@ -10846,6 +11118,9 @@ msgstr ""
msgid "Go to %{link_to_google_takeout}."
msgstr ""
+msgid "Go to <strong>Issues</strong> > <strong>Boards</strong> to access your personalized learning issue board."
+msgstr ""
+
msgid "Go to Pipelines"
msgstr ""
@@ -10945,9 +11220,6 @@ msgstr ""
msgid "Go to your snippets"
msgstr ""
-msgid "Golden Tanuki"
-msgstr ""
-
msgid "Google Cloud Platform"
msgstr ""
@@ -10969,6 +11241,9 @@ msgstr ""
msgid "Grafana URL"
msgstr ""
+msgid "Grafana response contains invalid json"
+msgstr ""
+
msgid "GrafanaIntegration|API Token"
msgstr ""
@@ -11017,9 +11292,6 @@ msgstr ""
msgid "Group %{group_name} was successfully created."
msgstr ""
-msgid "Group '%{group_name}' is being imported."
-msgstr ""
-
msgid "Group Audit Events"
msgstr ""
@@ -11056,9 +11328,6 @@ msgstr ""
msgid "Group by:"
msgstr ""
-msgid "Group could not be imported: %{errors}"
-msgstr ""
-
msgid "Group description"
msgstr ""
@@ -11086,9 +11355,15 @@ msgstr ""
msgid "Group has not been marked for deletion"
msgstr ""
+msgid "Group import could not be scheduled"
+msgstr ""
+
msgid "Group info:"
msgstr ""
+msgid "Group is required when cluster_type is :group"
+msgstr ""
+
msgid "Group maintainers can register group runners in the %{link}"
msgstr ""
@@ -11152,6 +11427,24 @@ msgstr ""
msgid "GroupActivyMetrics|Recent activity (last 90 days)"
msgstr ""
+msgid "GroupImport|Failed to import group."
+msgstr ""
+
+msgid "GroupImport|Group '%{group_name}' is being imported."
+msgstr ""
+
+msgid "GroupImport|Group could not be imported: %{errors}"
+msgstr ""
+
+msgid "GroupImport|Please wait while we import the group for you. Refresh at will."
+msgstr ""
+
+msgid "GroupImport|The group was successfully imported."
+msgstr ""
+
+msgid "GroupImport|Unable to process group import file"
+msgstr ""
+
msgid "GroupRoadmap|%{dateWord} – No end date"
msgstr ""
@@ -11419,6 +11712,9 @@ msgstr ""
msgid "Groups (%{groups})"
msgstr ""
+msgid "Groups and projects"
+msgstr ""
+
msgid "Groups can also be nested by creating %{subgroup_docs_link_start}subgroups%{subgroup_docs_link_end}."
msgstr ""
@@ -11464,6 +11760,33 @@ msgstr ""
msgid "GroupsEmptyState|You can manage your group member’s permissions and access to each project in the group."
msgstr ""
+msgid "GroupsNew|Contact an administrator to enable options for importing your group."
+msgstr ""
+
+msgid "GroupsNew|Create"
+msgstr ""
+
+msgid "GroupsNew|Create group"
+msgstr ""
+
+msgid "GroupsNew|GitLab group export"
+msgstr ""
+
+msgid "GroupsNew|Import"
+msgstr ""
+
+msgid "GroupsNew|Import group"
+msgstr ""
+
+msgid "GroupsNew|My Awesome Group"
+msgstr ""
+
+msgid "GroupsNew|No import options available"
+msgstr ""
+
+msgid "GroupsNew|To copy a GitLab group between installations, navigate to the group settings page for the original installation, generate an export file, and upload it here."
+msgstr ""
+
msgid "GroupsTree|Are you sure you want to leave the \"%{fullName}\" group?"
msgstr ""
@@ -11566,6 +11889,9 @@ msgstr ""
msgid "Helps reduce request volume for protected paths"
msgstr ""
+msgid "Here are all your projects in your group, including the one you just created. To start, let’s take a look at your personalized learning project which will help you learn about GitLab at your own pace."
+msgstr ""
+
msgid "Here you will find recent merge request activity"
msgstr ""
@@ -11582,6 +11908,9 @@ msgstr[1] ""
msgstr[2] ""
msgstr[3] ""
+msgid "Hide details"
+msgstr ""
+
msgid "Hide file browser"
msgstr ""
@@ -11591,6 +11920,9 @@ msgstr ""
msgid "Hide host keys manual input"
msgstr ""
+msgid "Hide list"
+msgstr ""
+
msgid "Hide marketing-related entries from help"
msgstr ""
@@ -11613,9 +11945,6 @@ msgstr[3] ""
msgid "Hide values"
msgstr ""
-msgid "Hiding all labels"
-msgstr ""
-
msgid "High or unknown vulnerabilities present"
msgstr ""
@@ -11700,6 +12029,9 @@ msgstr ""
msgid "ID:"
msgstr ""
+msgid "IDE"
+msgstr ""
+
msgid "IDE|Allow live previews of JavaScript projects in the Web IDE using CodeSandbox Live Preview."
msgstr ""
@@ -11790,10 +12122,10 @@ msgstr ""
msgid "If enabled"
msgstr ""
-msgid "If enabled, access to projects will be validated on an external service using their classification label."
+msgid "If enabled, GitLab will handle Object Storage replication using Geo. %{linkStart}More information%{linkEnd}"
msgstr ""
-msgid "If enabled, and if object storage is enabled, GitLab will handle Object Storage replication using Geo"
+msgid "If enabled, access to projects will be validated on an external service using their classification label."
msgstr ""
msgid "If there is no previous license or if the previous license has expired, some GitLab functionality will be blocked until a new, valid license is uploaded."
@@ -12157,6 +12489,9 @@ msgstr[1] ""
msgstr[2] ""
msgstr[3] ""
+msgid "Instance Configuration"
+msgstr ""
+
msgid "Instance Statistics visibility"
msgstr ""
@@ -12178,9 +12513,6 @@ msgstr ""
msgid "Integrations"
msgstr ""
-msgid "Integrations allow you to integrate GitLab with other applications"
-msgstr ""
-
msgid "Integrations|All details"
msgstr ""
@@ -12292,6 +12624,9 @@ msgstr ""
msgid "Invalid query"
msgstr ""
+msgid "Invalid repository bundle for snippet with id %{snippet_id}"
+msgstr ""
+
msgid "Invalid repository path"
msgstr ""
@@ -12310,6 +12645,9 @@ msgstr ""
msgid "Invalid two-factor code."
msgstr ""
+msgid "Invalid yaml"
+msgstr ""
+
msgid "Invitation"
msgstr ""
@@ -12529,15 +12867,24 @@ msgstr ""
msgid "It seems like the Dependency Scanning job ran successfully, but no dependencies have been detected in your project."
msgstr ""
+msgid "It seems that there is currently no available data for code coverage"
+msgstr ""
+
msgid "It's you"
msgstr ""
+msgid "Iteration"
+msgstr ""
+
msgid "Iteration changed to"
msgstr ""
msgid "Iteration removed"
msgstr ""
+msgid "Iteration updated"
+msgstr ""
+
msgid "Iterations"
msgstr ""
@@ -12568,12 +12915,18 @@ msgstr ""
msgid "January"
msgstr ""
+msgid "Jira Issues"
+msgstr ""
+
msgid "Jira import is already running."
msgstr ""
msgid "Jira integration not configured."
msgstr ""
+msgid "Jira project key is not configured"
+msgstr ""
+
msgid "Jira project: %{importProject}"
msgstr ""
@@ -12754,6 +13107,9 @@ msgstr ""
msgid "Keep divergent refs"
msgstr ""
+msgid "Kerberos access denied"
+msgstr ""
+
msgid "Key"
msgstr ""
@@ -12763,10 +13119,13 @@ msgstr ""
msgid "Key: %{key}"
msgstr ""
-msgid "Keyboard Shortcuts"
+msgid "Keyboard shortcuts"
msgstr ""
-msgid "Keyboard shortcuts"
+msgid "Keys"
+msgstr ""
+
+msgid "Ki"
msgstr ""
msgid "Kubernetes"
@@ -12808,6 +13167,9 @@ msgstr ""
msgid "LDAP"
msgstr ""
+msgid "LDAP Synchronization"
+msgstr ""
+
msgid "LDAP settings"
msgstr ""
@@ -13034,12 +13396,18 @@ msgstr ""
msgid "Learn more about custom project templates"
msgstr ""
+msgid "Learn more about deploying to AWS"
+msgstr ""
+
msgid "Learn more about deploying to a cluster"
msgstr ""
msgid "Learn more about group-level project templates"
msgstr ""
+msgid "Learn more about job dependencies"
+msgstr ""
+
msgid "Learn more about signing commits"
msgstr ""
@@ -13091,9 +13459,21 @@ msgstr ""
msgid "License History"
msgstr ""
+msgid "License ID:"
+msgstr ""
+
+msgid "License URL"
+msgstr ""
+
msgid "License-Check"
msgstr ""
+msgid "LicenseCompliance|%{docLinkStart}License Approvals%{docLinkEnd} are active"
+msgstr ""
+
+msgid "LicenseCompliance|%{docLinkStart}License Approvals%{docLinkEnd} are inactive"
+msgstr ""
+
msgid "LicenseCompliance|Add a license"
msgstr ""
@@ -13115,9 +13495,15 @@ msgstr ""
msgid "LicenseCompliance|Deny"
msgstr ""
+msgid "LicenseCompliance|Learn more about %{linkStart}License Approvals%{linkEnd}"
+msgstr ""
+
msgid "LicenseCompliance|License"
msgstr ""
+msgid "LicenseCompliance|License Approvals"
+msgstr ""
+
msgid "LicenseCompliance|License Compliance detected %d license and policy violation for the source branch only; approval required"
msgid_plural "LicenseCompliance|License Compliance detected %d licenses and policy violations for the source branch only; approval required"
msgstr[0] ""
@@ -13348,6 +13734,9 @@ msgstr ""
msgid "List your Bitbucket Server repositories"
msgstr ""
+msgid "Lists"
+msgstr ""
+
msgid "Live preview"
msgstr ""
@@ -13531,6 +13920,9 @@ msgstr ""
msgid "Manage project labels"
msgstr ""
+msgid "Manage storage usage"
+msgstr ""
+
msgid "Manage two-factor authentication"
msgstr ""
@@ -13546,6 +13938,9 @@ msgstr ""
msgid "Manifest file import"
msgstr ""
+msgid "Manifest import"
+msgstr ""
+
msgid "Manual job"
msgstr ""
@@ -13693,6 +14088,12 @@ msgstr ""
msgid "Maximum field length"
msgstr ""
+msgid "Maximum file size is 2MB. Please select a smaller file."
+msgstr ""
+
+msgid "Maximum import size (MB)"
+msgstr ""
+
msgid "Maximum job timeout"
msgstr ""
@@ -13732,6 +14133,9 @@ msgstr ""
msgid "Maximum size of Elasticsearch bulk indexing requests."
msgstr ""
+msgid "Maximum size of import files."
+msgstr ""
+
msgid "Maximum size of individual attachments in comments."
msgstr ""
@@ -13783,6 +14187,9 @@ msgstr ""
msgid "Merge (when the pipeline succeeds)"
msgstr ""
+msgid "Merge Conflicts"
+msgstr ""
+
msgid "Merge Request"
msgstr ""
@@ -13909,6 +14316,9 @@ msgstr ""
msgid "MergeRequests|Squash task canceled: another squash is already in progress."
msgstr ""
+msgid "MergeRequests|This project does not allow squashing commits when merge requests are accepted."
+msgstr ""
+
msgid "MergeRequests|Thread stays resolved"
msgstr ""
@@ -14062,6 +14472,12 @@ msgstr ""
msgid "MetricsSettings|Add a button to the metrics dashboard linking directly to your existing external dashboard."
msgstr ""
+msgid "MetricsSettings|Choose whether to display dashboard metrics in UTC or the user's local timezone."
+msgstr ""
+
+msgid "MetricsSettings|Dashboard timezone"
+msgstr ""
+
msgid "MetricsSettings|External dashboard URL"
msgstr ""
@@ -14071,6 +14487,12 @@ msgstr ""
msgid "MetricsSettings|Metrics Dashboard"
msgstr ""
+msgid "MetricsSettings|UTC (Coordinated Universal Time)"
+msgstr ""
+
+msgid "MetricsSettings|User's local timezone"
+msgstr ""
+
msgid "Metrics|Add metric"
msgstr ""
@@ -14086,6 +14508,9 @@ msgstr ""
msgid "Metrics|Create metric"
msgstr ""
+msgid "Metrics|Current"
+msgstr ""
+
msgid "Metrics|Delete metric"
msgstr ""
@@ -14138,6 +14563,9 @@ msgstr ""
msgid "Metrics|Max"
msgstr ""
+msgid "Metrics|Min"
+msgstr ""
+
msgid "Metrics|Must be a valid PromQL query."
msgstr ""
@@ -14153,6 +14581,9 @@ msgstr ""
msgid "Metrics|Refresh dashboard"
msgstr ""
+msgid "Metrics|Select a value"
+msgstr ""
+
msgid "Metrics|Star dashboard"
msgstr ""
@@ -14171,12 +14602,18 @@ msgstr ""
msgid "Metrics|There was an error getting annotations information."
msgstr ""
+msgid "Metrics|There was an error getting dashboard validation warnings information."
+msgstr ""
+
msgid "Metrics|There was an error getting deployment information."
msgstr ""
msgid "Metrics|There was an error getting environments information."
msgstr ""
+msgid "Metrics|There was an error getting options for variable \"%{name}\"."
+msgstr ""
+
msgid "Metrics|There was an error trying to validate your query"
msgstr ""
@@ -14219,6 +14656,9 @@ msgstr ""
msgid "Metrics|You're about to permanently delete this metric. This cannot be undone."
msgstr ""
+msgid "Metrics|Your dashboard schema is invalid. Edit the dashboard to correct the YAML schema."
+msgstr ""
+
msgid "Metrics|e.g. HTTP requests"
msgstr ""
@@ -14234,12 +14674,18 @@ msgstr ""
msgid "Metrics|e.g. req/sec"
msgstr ""
+msgid "Mi"
+msgstr ""
+
msgid "Microsoft Azure"
msgstr ""
msgid "Middleman project with Static Site Editor support"
msgstr ""
+msgid "Migrate your data from an external source like GitHub, Bitbucket, or another instance of GitLab."
+msgstr ""
+
msgid "Migrated %{success_count}/%{total_count} files."
msgstr ""
@@ -14376,6 +14822,9 @@ msgstr ""
msgid "Minimum capacity to be available before we schedule more mirrors preemptively."
msgstr ""
+msgid "Minimum interval in days"
+msgstr ""
+
msgid "Minimum length is %{minimum_password_length} characters"
msgstr ""
@@ -14562,6 +15011,12 @@ msgstr ""
msgid "Multiple uploaders found: %{uploader_types}"
msgstr ""
+msgid "Must match with the %{codeStart}external_url%{codeEnd} in %{codeStart}/etc/gitlab/gitlab.rb%{codeEnd}."
+msgstr ""
+
+msgid "Must match with the %{codeStart}geo_node_name%{codeEnd} in %{codeStart}/etc/gitlab/gitlab.rb%{codeEnd}. %{linkStart}More information%{linkEnd}"
+msgstr ""
+
msgid "My Awesome Group"
msgstr ""
@@ -14676,10 +15131,16 @@ msgstr ""
msgid "New Application"
msgstr ""
+msgid "New Branch"
+msgstr ""
+
+msgid "New Deploy Key"
+msgstr ""
+
msgid "New Environment"
msgstr ""
-msgid "New Geo Node"
+msgid "New File"
msgstr ""
msgid "New Group"
@@ -14698,15 +15159,15 @@ msgstr[1] ""
msgstr[2] ""
msgstr[3] ""
-msgid "New Iteration"
-msgstr ""
-
msgid "New Jira import"
msgstr ""
msgid "New Label"
msgstr ""
+msgid "New Merge Request"
+msgstr ""
+
msgid "New Milestone"
msgstr ""
@@ -14725,6 +15186,9 @@ msgstr ""
msgid "New Snippet"
msgstr ""
+msgid "New User"
+msgstr ""
+
msgid "New branch"
msgstr ""
@@ -14767,6 +15231,9 @@ msgstr ""
msgid "New issue title"
msgstr ""
+msgid "New iteration"
+msgstr ""
+
msgid "New iteration created"
msgstr ""
@@ -14944,6 +15411,12 @@ msgstr ""
msgid "No grouping"
msgstr ""
+msgid "No iteration"
+msgstr ""
+
+msgid "No iterations to show"
+msgstr ""
+
msgid "No job log"
msgstr ""
@@ -14986,6 +15459,9 @@ msgstr ""
msgid "No other labels with such name or description"
msgstr ""
+msgid "No panels matching properties %{opts}"
+msgstr ""
+
msgid "No parent group"
msgstr ""
@@ -15019,6 +15495,9 @@ msgstr ""
msgid "No schedules"
msgstr ""
+msgid "No stack trace for this error"
+msgstr ""
+
msgid "No starrers matched your search"
msgstr ""
@@ -15031,7 +15510,7 @@ msgstr ""
msgid "No test coverage"
msgstr ""
-msgid "No thanks, don't show this again"
+msgid "No thanks"
msgstr ""
msgid "No vulnerabilities present"
@@ -15046,9 +15525,6 @@ msgstr ""
msgid "No, directly import the existing email addresses and usernames."
msgstr ""
-msgid "No, not interested right now"
-msgstr ""
-
msgid "No. of commits"
msgstr ""
@@ -15094,9 +15570,6 @@ msgstr ""
msgid "Not found."
msgstr ""
-msgid "Not helpful"
-msgstr ""
-
msgid "Not now"
msgstr ""
@@ -15253,6 +15726,9 @@ msgstr ""
msgid "Notifications on"
msgstr ""
+msgid "Notify users by email when sign-in location is not recognized"
+msgstr ""
+
msgid "Nov"
msgstr ""
@@ -15319,9 +15795,6 @@ msgstr ""
msgid "Oh no!"
msgstr ""
-msgid "Ok let's go"
-msgstr ""
-
msgid "Oldest first"
msgstr ""
@@ -15334,16 +15807,49 @@ msgstr ""
msgid "On track"
msgstr ""
+msgid "OnDemandScans|Attached branch"
+msgstr ""
+
+msgid "OnDemandScans|Attached branch is where the scan job runs."
+msgstr ""
+
+msgid "OnDemandScans|Could not run the scan. Please try again."
+msgstr ""
+
msgid "OnDemandScans|Create new DAST scan"
msgstr ""
+msgid "OnDemandScans|DAST will scan the target URL and any discovered sub URLs."
+msgstr ""
+
+msgid "OnDemandScans|New on-demand DAST scan"
+msgstr ""
+
msgid "OnDemandScans|On-demand Scans"
msgstr ""
+msgid "OnDemandScans|On-demand scans run outside the DevOps cycle and find vulnerabilities in your projects. %{learnMoreLinkStart}Learn more%{learnMoreLinkEnd}"
+msgstr ""
+
+msgid "OnDemandScans|Only a passive scan can be performed on demand."
+msgstr ""
+
+msgid "OnDemandScans|Passive DAST Scan"
+msgstr ""
+
+msgid "OnDemandScans|Please enter a valid URL format, ex: http://www.example.com/home"
+msgstr ""
+
+msgid "OnDemandScans|Run this scan"
+msgstr ""
+
+msgid "OnDemandScans|Scan mode"
+msgstr ""
+
msgid "OnDemandScans|Schedule or run scans immediately against target sites. Currently available on-demand scan type: DAST. %{helpLinkStart}More information%{helpLinkEnd}"
msgstr ""
-msgid "Onboarding"
+msgid "OnDemandScans|Target URL"
msgstr ""
msgid "Once imported, repositories can be mirrored over SSH. Read more %{link_start}here%{link_end}."
@@ -15371,6 +15877,9 @@ msgstr ""
msgid "One or more of you personal access tokens were revoked"
msgstr ""
+msgid "One or more of your %{provider} projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
+msgstr ""
+
msgid "One or more of your Bitbucket projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
msgstr ""
@@ -15872,6 +16381,9 @@ msgstr ""
msgid "Parent epic is not present."
msgstr ""
+msgid "Parsing error for param :embed_json. %{message}"
+msgstr ""
+
msgid "Part of merge request changes"
msgstr ""
@@ -15884,6 +16396,9 @@ msgstr ""
msgid "Passed"
msgstr ""
+msgid "Passed on"
+msgstr ""
+
msgid "Password"
msgstr ""
@@ -15968,6 +16483,9 @@ msgstr ""
msgid "Perform common operations on GitLab project"
msgstr ""
+msgid "Performance and resource management"
+msgstr ""
+
msgid "Performance optimization"
msgstr ""
@@ -16175,7 +16693,7 @@ msgstr ""
msgid "Pipelines|Something went wrong while cleaning runners cache."
msgstr ""
-msgid "Pipelines|There are currently no %{scope} pipelines."
+msgid "Pipelines|There are currently no finished pipelines."
msgstr ""
msgid "Pipelines|There are currently no pipelines."
@@ -16286,6 +16804,9 @@ msgstr ""
msgid "Pipeline|Stop pipeline #%{pipelineId}?"
msgstr ""
+msgid "Pipeline|Tag name"
+msgstr ""
+
msgid "Pipeline|Trigger author"
msgstr ""
@@ -16367,12 +16888,18 @@ msgstr ""
msgid "Please check your email (%{email}) to verify that you own this address and unlock the power of CI/CD. Didn't receive it? %{resend_link}. Wrong email address? %{update_link}."
msgstr ""
+msgid "Please choose a file"
+msgstr ""
+
msgid "Please choose a group URL with no special characters."
msgstr ""
msgid "Please complete your profile with email address"
msgstr ""
+msgid "Please convert %{linkStart}them to Git%{linkEnd}, and go through the %{linkToImportFlow} again."
+msgstr ""
+
msgid "Please convert them to %{link_to_git}, and go through the %{link_to_import_flow} again."
msgstr ""
@@ -16430,6 +16957,9 @@ msgstr ""
msgid "Please provide a valid email address."
msgstr ""
+msgid "Please provide attributes to update"
+msgstr ""
+
msgid "Please refer to <a href=\"%{docs_url}\">%{docs_url}</a>"
msgstr ""
@@ -17165,6 +17695,9 @@ msgstr ""
msgid "Project has too many %{label_for_message} to search"
msgstr ""
+msgid "Project is required when cluster_type is :project"
+msgstr ""
+
msgid "Project members"
msgstr ""
@@ -17282,7 +17815,40 @@ msgstr ""
msgid "ProjectService|Comment"
msgstr ""
-msgid "ProjectService|Comment will be posted on each event"
+msgid "ProjectService|Event will be triggered by a push to the repository"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a commit is created/updated"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a confidential issue is created/updated/closed"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a deployment finishes"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a merge request is created/updated/merged"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a new tag is pushed to the repository"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a new, unique alert is recorded"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a pipeline status changes"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a wiki page is created/updated"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when an issue is created/updated/closed"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when someone adds a comment"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when someone adds a comment on a confidential issue"
msgstr ""
msgid "ProjectService|Perform common operations on GitLab project: %{project_name}"
@@ -17333,6 +17899,9 @@ msgstr ""
msgid "ProjectSettings|Enable 'Delete source branch' option by default"
msgstr ""
+msgid "ProjectSettings|Enable merge trains and pipelines for merged results"
+msgstr ""
+
msgid "ProjectSettings|Every merge creates a merge commit"
msgstr ""
@@ -17363,7 +17932,7 @@ msgstr ""
msgid "ProjectSettings|Forks"
msgstr ""
-msgid "ProjectSettings|Git Large File Storage"
+msgid "ProjectSettings|Git Large File Storage (LFS)"
msgstr ""
msgid "ProjectSettings|Internal"
@@ -17399,9 +17968,6 @@ msgstr ""
msgid "ProjectSettings|Merge options"
msgstr ""
-msgid "ProjectSettings|Merge pipelines will try to validate the post-merge result prior to merging"
-msgstr ""
-
msgid "ProjectSettings|Merge requests"
msgstr ""
@@ -17429,6 +17995,9 @@ msgstr ""
msgid "ProjectSettings|Pipelines"
msgstr ""
+msgid "ProjectSettings|Pipelines for merge requests must be enabled in the CI/CD configuration file, or pipelines could be unresolvable or dropped"
+msgstr ""
+
msgid "ProjectSettings|Pipelines must succeed"
msgstr ""
@@ -17456,6 +18025,9 @@ msgstr ""
msgid "ProjectSettings|Show link to create/view merge request when pushing from the command line"
msgstr ""
+msgid "ProjectSettings|Skipped pipelines are considered successful"
+msgstr ""
+
msgid "ProjectSettings|Snippets"
msgstr ""
@@ -17471,6 +18043,9 @@ msgstr ""
msgid "ProjectSettings|These checks must pass before merge requests can be merged"
msgstr ""
+msgid "ProjectSettings|This introduces the risk of merging changes that will not pass the pipeline."
+msgstr ""
+
msgid "ProjectSettings|This setting is applied on the server level and can be overridden by an admin."
msgstr ""
@@ -17648,15 +18223,27 @@ msgstr ""
msgid "ProjectsNew|Blank project"
msgstr ""
+msgid "ProjectsNew|Connect your external repository to GitLab CI/CD."
+msgstr ""
+
msgid "ProjectsNew|Contact an administrator to enable options for importing your project."
msgstr ""
msgid "ProjectsNew|Create"
msgstr ""
+msgid "ProjectsNew|Create a blank project to house your files, plan your work, and collaborate on code, among other things."
+msgstr ""
+
+msgid "ProjectsNew|Create blank project"
+msgstr ""
+
msgid "ProjectsNew|Create from template"
msgstr ""
+msgid "ProjectsNew|Create new project"
+msgstr ""
+
msgid "ProjectsNew|Creating project & repository."
msgstr ""
@@ -17681,6 +18268,9 @@ msgstr ""
msgid "ProjectsNew|Project description %{tag_start}(optional)%{tag_end}"
msgstr ""
+msgid "ProjectsNew|Run CI/CD for external repository"
+msgstr ""
+
msgid "ProjectsNew|Template"
msgstr ""
@@ -17942,6 +18532,9 @@ msgstr ""
msgid "Protected Branch"
msgstr ""
+msgid "Protected Branches"
+msgstr ""
+
msgid "Protected Environment"
msgstr ""
@@ -17954,6 +18547,9 @@ msgstr ""
msgid "Protected Tag"
msgstr ""
+msgid "Protected Tags"
+msgstr ""
+
msgid "Protected branches"
msgstr ""
@@ -18053,6 +18649,9 @@ msgstr ""
msgid "Provider"
msgstr ""
+msgid "Proxy support for this API is not available currently"
+msgstr ""
+
msgid "Pseudonymizer data collection"
msgstr ""
@@ -18077,6 +18676,9 @@ msgstr ""
msgid "Publish to status page"
msgstr ""
+msgid "Published on status page"
+msgstr ""
+
msgid "Publishes this issue to the associated status page."
msgstr ""
@@ -18251,6 +18853,9 @@ msgstr ""
msgid "Recipe"
msgstr ""
+msgid "Reconfigure"
+msgstr ""
+
msgid "Recover hidden stage"
msgstr ""
@@ -18339,6 +18944,9 @@ msgstr ""
msgid "Registration|Your profile"
msgstr ""
+msgid "Regulate approvals by authors/committers, based on compliance frameworks. Can be changed only at the instance level."
+msgstr ""
+
msgid "Rejected (closed)"
msgstr ""
@@ -18391,15 +18999,24 @@ msgstr ""
msgid "ReleaseAssetLinkType|Image"
msgstr ""
+msgid "ReleaseAssetLinkType|Images"
+msgstr ""
+
msgid "ReleaseAssetLinkType|Other"
msgstr ""
msgid "ReleaseAssetLinkType|Package"
msgstr ""
+msgid "ReleaseAssetLinkType|Packages"
+msgstr ""
+
msgid "ReleaseAssetLinkType|Runbook"
msgstr ""
+msgid "ReleaseAssetLinkType|Runbooks"
+msgstr ""
+
msgid "Releases"
msgstr ""
@@ -18412,6 +19029,9 @@ msgstr ""
msgid "Releases documentation"
msgstr ""
+msgid "Releases|New Release"
+msgstr ""
+
msgid "Release|Something went wrong while getting the release details"
msgstr ""
@@ -18484,6 +19104,9 @@ msgstr ""
msgid "Remove fork relationship"
msgstr ""
+msgid "Remove from batch"
+msgstr ""
+
msgid "Remove from board"
msgstr ""
@@ -18673,6 +19296,12 @@ msgstr ""
msgid "Replication"
msgstr ""
+msgid "Replication enabled"
+msgstr ""
+
+msgid "Replication paused"
+msgstr ""
+
msgid "Reply by email"
msgstr ""
@@ -18700,15 +19329,15 @@ msgstr ""
msgid "Reported %{timeAgo} by %{reportedBy}"
msgstr ""
-msgid "Reported Resource Changes: %{addNum} to add, %{changeNum} to change, %{deleteNum} to delete"
-msgstr ""
-
msgid "Reporter"
msgstr ""
msgid "Reporting"
msgstr ""
+msgid "Reports"
+msgstr ""
+
msgid "Reports|%{combinedString} and %{resolvedString}"
msgstr ""
@@ -18749,6 +19378,9 @@ msgstr ""
msgid "Reports|Failure"
msgstr ""
+msgid "Reports|Identifier"
+msgstr ""
+
msgid "Reports|Metrics reports are loading"
msgstr ""
@@ -18761,6 +19393,9 @@ msgstr ""
msgid "Reports|Metrics reports failed loading results"
msgstr ""
+msgid "Reports|Scanner"
+msgstr ""
+
msgid "Reports|Severity"
msgstr ""
@@ -18809,15 +19444,33 @@ msgstr ""
msgid "Repository cleanup has started. You will receive an email once the cleanup operation is complete."
msgstr ""
+msgid "Repository files count over the limit"
+msgstr ""
+
+msgid "Repository has an invalid default branch name."
+msgstr ""
+
+msgid "Repository has more than one branch."
+msgstr ""
+
msgid "Repository has no locks."
msgstr ""
+msgid "Repository has tags."
+msgstr ""
+
msgid "Repository maintenance"
msgstr ""
msgid "Repository mirroring"
msgstr ""
+msgid "Repository must contain at least 1 file."
+msgstr ""
+
+msgid "Repository size is above the limit."
+msgstr ""
+
msgid "Repository static objects"
msgstr ""
@@ -18827,7 +19480,7 @@ msgstr ""
msgid "Repository sync capacity"
msgstr ""
-msgid "Repository: %{counter_repositories} / Wikis: %{counter_wikis} / Build Artifacts: %{counter_build_artifacts} / LFS: %{counter_lfs_objects}"
+msgid "Repository: %{counter_repositories} / Wikis: %{counter_wikis} / Build Artifacts: %{counter_build_artifacts} / LFS: %{counter_lfs_objects} / Snippets: %{counter_snippets}"
msgstr ""
msgid "RepositorySettingsAccessLevel|Select"
@@ -18839,6 +19492,9 @@ msgstr ""
msgid "Request Headers"
msgstr ""
+msgid "Request details"
+msgstr ""
+
msgid "Request parameter %{param} is missing."
msgstr ""
@@ -18949,6 +19605,9 @@ msgstr ""
msgid "Reset template"
msgstr ""
+msgid "Reset to project defaults"
+msgstr ""
+
msgid "Resetting the authorization key for this project will require updating the authorization key in every alert source it is enabled in."
msgstr ""
@@ -19045,9 +19704,6 @@ msgstr ""
msgid "Resume"
msgstr ""
-msgid "Resume replication"
-msgstr ""
-
msgid "Resync"
msgstr ""
@@ -19692,6 +20348,9 @@ msgstr ""
msgid "SecurityReports|False positive"
msgstr ""
+msgid "SecurityReports|Group Security Dashboard"
+msgstr ""
+
msgid "SecurityReports|Hide dismissed"
msgstr ""
@@ -19746,10 +20405,13 @@ msgstr ""
msgid "SecurityReports|Remove project from dashboard"
msgstr ""
-msgid "SecurityReports|Report type"
+msgid "SecurityReports|Return to dashboard"
+msgstr ""
+
+msgid "SecurityReports|Scan details"
msgstr ""
-msgid "SecurityReports|Return to dashboard"
+msgid "SecurityReports|Scanner type"
msgstr ""
msgid "SecurityReports|Security Dashboard"
@@ -19806,6 +20468,9 @@ msgstr ""
msgid "SecurityReports|There was an error while generating the report."
msgstr ""
+msgid "SecurityReports|Unable to add %{invalidProjectsMessage}"
+msgstr ""
+
msgid "SecurityReports|Unable to add %{invalidProjects}"
msgstr ""
@@ -19920,6 +20585,9 @@ msgstr ""
msgid "Select due date"
msgstr ""
+msgid "Select file"
+msgstr ""
+
msgid "Select group or project"
msgstr ""
@@ -19983,9 +20651,6 @@ msgstr ""
msgid "Select the branch you want to set as the default for this project. All merge requests and commits will automatically be made against this branch unless you specify a different one."
msgstr ""
-msgid "Select the configured storage available for new repositories to be placed on."
-msgstr ""
-
msgid "Select the custom project template source group."
msgstr ""
@@ -20178,12 +20843,6 @@ msgstr ""
msgid "Service Desk is enabled but not yet active"
msgstr ""
-msgid "Service Desk is off"
-msgstr ""
-
-msgid "Service Desk is on"
-msgstr ""
-
msgid "Service Templates"
msgstr ""
@@ -20271,6 +20930,15 @@ msgstr ""
msgid "Set the milestone to %{milestone_reference}."
msgstr ""
+msgid "Set the number of concurrent requests this secondary node will make to the primary node while backfilling."
+msgstr ""
+
+msgid "Set the synchronization and verification capacity for the secondary node."
+msgstr ""
+
+msgid "Set the timeout in seconds to send a secondary node status to the primary and IPs allowed for the secondary nodes."
+msgstr ""
+
msgid "Set time estimate"
msgstr ""
@@ -20310,6 +20978,9 @@ msgstr ""
msgid "Set weight to %{weight}."
msgstr ""
+msgid "Set what should be replicated by choosing specific projects or groups by the secondary node."
+msgstr ""
+
msgid "SetPasswordToCloneLink|set a password"
msgstr ""
@@ -20436,6 +21107,9 @@ msgstr ""
msgid "Show complete raw log"
msgstr ""
+msgid "Show details"
+msgstr ""
+
msgid "Show file browser"
msgstr ""
@@ -20445,10 +21119,13 @@ msgstr ""
msgid "Show latest version"
msgstr ""
+msgid "Show list"
+msgstr ""
+
msgid "Show me everything"
msgstr ""
-msgid "Show me how"
+msgid "Show me how to add a pipeline"
msgstr ""
msgid "Show me more advanced stuff"
@@ -20501,9 +21178,6 @@ msgstr ""
msgid "Showing all issues"
msgstr ""
-msgid "Showing all labels"
-msgstr ""
-
msgid "Showing last %{size} of log -"
msgstr ""
@@ -20615,9 +21289,6 @@ msgstr ""
msgid "Skip outdated deployment jobs"
msgstr ""
-msgid "Skip this for now"
-msgstr ""
-
msgid "Skipped"
msgstr ""
@@ -20762,6 +21433,9 @@ msgstr ""
msgid "Something went wrong while adding your award. Please try again."
msgstr ""
+msgid "Something went wrong while applying the batch of suggestions. Please try again."
+msgstr ""
+
msgid "Something went wrong while applying the suggestion. Please try again."
msgstr ""
@@ -20864,10 +21538,13 @@ msgstr ""
msgid "Something went wrong while updating a requirement."
msgstr ""
+msgid "Something went wrong while updating assignees"
+msgstr ""
+
msgid "Something went wrong while updating your list settings"
msgstr ""
-msgid "Something went wrong with your automatic subscription renewal"
+msgid "Something went wrong with your automatic subscription renewal."
msgstr ""
msgid "Something went wrong, unable to add %{project} to dashboard"
@@ -20885,6 +21562,9 @@ msgstr ""
msgid "Something went wrong, unable to search projects"
msgstr ""
+msgid "Something went wrong."
+msgstr ""
+
msgid "Something went wrong. Please try again."
msgstr ""
@@ -21068,6 +21748,9 @@ msgstr ""
msgid "Source code"
msgstr ""
+msgid "Source code (%{fileExtension})"
+msgstr ""
+
msgid "Source is not available"
msgstr ""
@@ -21152,6 +21835,9 @@ msgstr ""
msgid "Stack trace"
msgstr ""
+msgid "Stacktrace snippet"
+msgstr ""
+
msgid "Stage"
msgstr ""
@@ -21257,6 +21943,9 @@ msgstr ""
msgid "Start thread & reopen %{noteable_name}"
msgstr ""
+msgid "Start using Directed Acyclic Graphs (DAG)"
+msgstr ""
+
msgid "Start your Free Gold Trial"
msgstr ""
@@ -21719,6 +22408,21 @@ msgstr ""
msgid "SuggestedColors|Very pale orange"
msgstr ""
+msgid "Suggestion is not applicable as the suggestion was not found."
+msgstr ""
+
+msgid "Suggestions are not applicable as one or more suggestions were not found."
+msgstr ""
+
+msgid "Suggestions are not applicable as their lines cannot overlap."
+msgstr ""
+
+msgid "Suggestions must all be on the same branch."
+msgstr ""
+
+msgid "Suggestions that change line count can't be added to batches, yet."
+msgstr ""
+
msgid "Suggestions:"
msgstr ""
@@ -21761,9 +22465,6 @@ msgstr ""
msgid "Synchronization disabled"
msgstr ""
-msgid "Synchronization of container repositories is disabled."
-msgstr ""
-
msgid "System"
msgstr ""
@@ -21944,6 +22645,38 @@ msgstr ""
msgid "Terms of Service and Privacy Policy"
msgstr ""
+msgid "Terraform|%{number} Terraform report failed to generate"
+msgid_plural "Terraform|%{number} Terraform reports failed to generate"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "Terraform|%{number} Terraform report was generated in your pipelines"
+msgid_plural "Terraform|%{number} Terraform reports were generated in your pipelines"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "Terraform|A Terraform report failed to generate."
+msgstr ""
+
+msgid "Terraform|A Terraform report was generated in your pipelines."
+msgstr ""
+
+msgid "Terraform|Generating the report caused an error."
+msgstr ""
+
+msgid "Terraform|Reported Resource Changes: %{addNum} to add, %{changeNum} to change, %{deleteNum} to delete"
+msgstr ""
+
+msgid "Terraform|The Terraform report %{name} failed to generate."
+msgstr ""
+
+msgid "Terraform|The Terraform report %{name} was generated in your pipelines."
+msgstr ""
+
msgid "Test"
msgstr ""
@@ -21972,7 +22705,7 @@ msgstr ""
msgid "TestHooks|Ensure the project has CI pipelines."
msgstr ""
-msgid "TestHooks|Ensure the project has at least one commit."
+msgid "TestHooks|Ensure the project has deployments."
msgstr ""
msgid "TestHooks|Ensure the project has issues."
@@ -22075,7 +22808,7 @@ msgstr ""
msgid "The Prometheus server responded with \"bad request\". Please check your queries are correct and are supported in your Prometheus version. %{documentationLink}"
msgstr ""
-msgid "The URL defined on the primary node that secondary nodes should use to contact it. Defaults to URL"
+msgid "The URL defined on the primary node that secondary nodes should use to contact it."
msgstr ""
msgid "The URL to use for connecting to Elasticsearch. Use a comma-separated list to support clustering (e.g., \"http://localhost:9200, http://localhost:9201\")."
@@ -22084,6 +22817,9 @@ msgstr ""
msgid "The X509 Certificate to use when mutual TLS is required to communicate with the external authorization service. If left blank, the server certificate is still validated when accessing over HTTPS."
msgstr ""
+msgid "The above settings apply to all projects with the selected compliance framework(s)."
+msgstr ""
+
msgid "The amount of seconds after which a request to get a secondary node status will time out."
msgstr ""
@@ -22385,9 +23121,6 @@ msgstr ""
msgid "The total stage shows the time it takes between creating an issue and deploying the code to production. The data will be automatically added once you have completed the full idea to production cycle."
msgstr ""
-msgid "The unique identifier for the Geo node. Must match %{geoNodeName} if it is set in gitlab.rb, otherwise it must match %{externalUrl} with a trailing slash"
-msgstr ""
-
msgid "The update action will time out after %{number_of_minutes} minutes. For big repositories, use a clone/push combination."
msgstr ""
@@ -22412,9 +23145,6 @@ msgstr ""
msgid "The user you are trying to deactivate has been active in the past %{minimum_inactive_days} days and cannot be deactivated"
msgstr ""
-msgid "The user-facing URL of the Geo node"
-msgstr ""
-
msgid "The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6."
msgstr ""
@@ -22505,6 +23235,9 @@ msgstr ""
msgid "There was a problem fetching project branches."
msgstr ""
+msgid "There was a problem fetching project tags."
+msgstr ""
+
msgid "There was a problem fetching project users."
msgstr ""
@@ -22556,6 +23289,9 @@ msgstr ""
msgid "There was an error fetching the %{replicableType}"
msgstr ""
+msgid "There was an error fetching the Geo Settings"
+msgstr ""
+
msgid "There was an error fetching the Node's Groups"
msgstr ""
@@ -22676,6 +23412,12 @@ msgstr ""
msgid "This %{issuable} is locked. Only <strong>project members</strong> can comment."
msgstr ""
+msgid "This %{noteableTypeText} is %{confidentialLinkStart}confidential%{linkEnd} and %{lockedLinkStart}locked%{linkEnd}."
+msgstr ""
+
+msgid "This %{noteableTypeText} is locked."
+msgstr ""
+
msgid "This %{viewer} could not be displayed because %{reason}. You can %{options} instead."
msgstr ""
@@ -22694,6 +23436,9 @@ msgstr ""
msgid "This action can lead to data loss. To prevent accidental actions we ask you to confirm your intention."
msgstr ""
+msgid "This also resolves all related threads"
+msgstr ""
+
msgid "This also resolves the discussion"
msgstr ""
@@ -22784,6 +23529,9 @@ msgstr ""
msgid "This epic does not exist or you don't have sufficient permission."
msgstr ""
+msgid "This feature is currently in beta. We invite you to %{linkStart}give feedback%{linkEnd}."
+msgstr ""
+
msgid "This feature requires local storage to be enabled"
msgstr ""
@@ -22817,7 +23565,7 @@ msgstr ""
msgid "This is a Work in Progress"
msgstr ""
-msgid "This is a confidential issue."
+msgid "This is a confidential %{noteableTypeText}."
msgstr ""
msgid "This is a delayed job to run in %{remainingTime}"
@@ -22844,9 +23592,6 @@ msgstr ""
msgid "This is your current session"
msgstr ""
-msgid "This issue is %{confidentialLinkStart}confidential%{linkEnd} and %{lockedLinkStart}locked%{linkEnd}."
-msgstr ""
-
msgid "This issue is confidential"
msgstr ""
@@ -22856,9 +23601,6 @@ msgstr ""
msgid "This issue is in a child epic of the filtered epic"
msgstr ""
-msgid "This issue is locked."
-msgstr ""
-
msgid "This job depends on other jobs with expired/erased artifacts: %{invalid_dependencies}"
msgstr ""
@@ -22958,6 +23700,9 @@ msgstr ""
msgid "This license has already expired."
msgstr ""
+msgid "This link points to external content"
+msgstr ""
+
msgid "This may expose confidential information as the selected fork is in another namespace that can have other members."
msgstr ""
@@ -22985,6 +23730,9 @@ msgstr ""
msgid "This page will be removed in a future release."
msgstr ""
+msgid "This pipeline does not use the %{codeStart}needs%{codeEnd} keyword and can't be represented as a directed acyclic graph."
+msgstr ""
+
msgid "This pipeline makes use of a predefined CI/CD configuration enabled by %{strongStart}Auto DevOps.%{strongEnd}"
msgstr ""
@@ -23213,6 +23961,9 @@ msgstr ""
msgid "Time from last commit to merge"
msgstr ""
+msgid "Time in seconds"
+msgstr ""
+
msgid "Time in seconds GitLab will wait for a response from the external service. When the service does not respond in time, access will be denied."
msgstr ""
@@ -23638,9 +24389,15 @@ msgstr ""
msgid "Total artifacts size: %{total_size}"
msgstr ""
+msgid "Total cores (CPUs)"
+msgstr ""
+
msgid "Total issues"
msgstr ""
+msgid "Total memory (GB)"
+msgstr ""
+
msgid "Total test time for all commits/merges"
msgstr ""
@@ -23842,6 +24599,9 @@ msgstr ""
msgid "U2F only works with HTTPS-enabled websites. Contact your administrator for more details."
msgstr ""
+msgid "UI Development Kit"
+msgstr ""
+
msgid "URL"
msgstr ""
@@ -23914,9 +24674,6 @@ msgstr ""
msgid "Unable to load the merge request widget. Try reloading the page."
msgstr ""
-msgid "Unable to process group import file"
-msgstr ""
-
msgid "Unable to resolve"
msgstr ""
@@ -23932,6 +24689,9 @@ msgstr ""
msgid "Unable to sign you in to the group with SAML due to \"%{reason}\""
msgstr ""
+msgid "Unable to suggest a path. Please refresh and try again."
+msgstr ""
+
msgid "Unable to update label prioritization at this time"
msgstr ""
@@ -23947,6 +24707,9 @@ msgstr ""
msgid "Unarchiving the project will restore people's ability to make changes to it. The repository can be committed to, and issues, comments, and other entities can be created. %{strong_start}Once active, this project shows up in the search and on the dashboard.%{strong_end}"
msgstr ""
+msgid "Unassign from commenting user"
+msgstr ""
+
msgid "Unblock"
msgstr ""
@@ -24022,6 +24785,9 @@ msgstr ""
msgid "Unreachable"
msgstr ""
+msgid "Unrecognized cluster type"
+msgstr ""
+
msgid "Unresolve"
msgstr ""
@@ -24082,6 +24848,9 @@ msgstr ""
msgid "Unsubscribes from this %{quick_action_target}."
msgstr ""
+msgid "Unsupported todo type passed. Supported todo types are: %{todo_types}"
+msgstr ""
+
msgid "Until"
msgstr ""
@@ -24109,6 +24878,9 @@ msgstr ""
msgid "Update approval rule"
msgstr ""
+msgid "Update approvers"
+msgstr ""
+
msgid "Update failed"
msgstr ""
@@ -24118,6 +24890,9 @@ msgstr ""
msgid "Update it"
msgstr ""
+msgid "Update iteration"
+msgstr ""
+
msgid "Update now"
msgstr ""
@@ -24163,7 +24938,7 @@ msgstr ""
msgid "Updated at"
msgstr ""
-msgid "Updated to"
+msgid "Updated to %{linkStart}chart v%{linkEnd}"
msgstr ""
msgid "Updating"
@@ -24193,6 +24968,9 @@ msgstr ""
msgid "Upload CSV file"
msgstr ""
+msgid "Upload License"
+msgstr ""
+
msgid "Upload New File"
msgstr ""
@@ -24274,16 +25052,19 @@ msgstr ""
msgid "UsageQuota|Pipelines"
msgstr ""
+msgid "UsageQuota|Purchase more storage"
+msgstr ""
+
msgid "UsageQuota|Repositories"
msgstr ""
msgid "UsageQuota|Repository"
msgstr ""
-msgid "UsageQuota|Storage"
+msgid "UsageQuota|Snippets"
msgstr ""
-msgid "UsageQuota|Storage usage:"
+msgid "UsageQuota|Storage"
msgstr ""
msgid "UsageQuota|This namespace has no projects which use shared runners"
@@ -24316,6 +25097,12 @@ msgstr ""
msgid "UsageQuota|Wikis"
msgstr ""
+msgid "UsageQuota|You used: %{usage} %{limit}"
+msgstr ""
+
+msgid "UsageQuota|out of %{formattedLimit} of your namespace storage"
+msgstr ""
+
msgid "Use %{code_start}::%{code_end} to create a %{link_start}scoped label set%{link_end} (eg. %{code_start}priority::1%{code_end})"
msgstr ""
@@ -24382,6 +25169,9 @@ msgstr ""
msgid "User IDs"
msgstr ""
+msgid "User Lists can only be created and modified with %{linkStart}the API%{linkEnd}"
+msgstr ""
+
msgid "User OAuth applications"
msgstr ""
@@ -24406,6 +25196,9 @@ msgstr ""
msgid "User key was successfully removed."
msgstr ""
+msgid "User list %{name} will be removed. Are you sure?"
+msgstr ""
+
msgid "User map"
msgstr ""
@@ -24427,148 +25220,10 @@ msgstr ""
msgid "User was successfully updated."
msgstr ""
-msgid "UserOnboardingTour|%{activeTour}/%{totalTours}"
-msgstr ""
-
-msgid "UserOnboardingTour|%{completed}/%{total} steps completed"
-msgstr ""
-
-msgid "UserOnboardingTour|%{emphasisStart}Well done!%{emphasisEnd}%{lineBreak}%{lineBreak}That's it for our guided tour, congratulations for making it all the way to the end!%{lineBreak}%{lineBreak}We hope this gave you a good overview of GitLab and how it can help you. We'll now show you how to create your own project and invite your colleagues."
-msgstr ""
-
-msgid "UserOnboardingTour|Adding other members to a project is done through Project Settings. Click on %{emphasisStart}Settings%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Alright, that's it for Commits. Let's take a look at the %{emphasisStart}Branches%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Awesome! Now click on %{emphasisStart}Members%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Click on one of the %{emphasisStart}Compare%{emphasisEnd} buttons to compare a branch to master."
-msgstr ""
-
-msgid "UserOnboardingTour|Click on one of the %{emphasisStart}pipeline IDs%{emphasisEnd} to see the details of a pipeline."
-msgstr ""
-
-msgid "UserOnboardingTour|Click to open the latest commit to see its details."
-msgstr ""
-
-msgid "UserOnboardingTour|Close 'Learn GitLab'"
-msgstr ""
-
-msgid "UserOnboardingTour|Commits are shown in chronological order and can be filtered by the commit message or by the branch."
-msgstr ""
-
-msgid "UserOnboardingTour|Create a project"
-msgstr ""
-
-msgid "UserOnboardingTour|Exit 'Learn GitLab'"
-msgstr ""
-
-msgid "UserOnboardingTour|Got it"
-msgstr ""
-
-msgid "UserOnboardingTour|Great job! %{clapHands} We hope the tour was helpful and that you learned how to use GitLab.%{lineBreak}%{lineBreak}We'd love to get your feedback on this tour.%{lineBreak}%{lineBreak}%{emphasisStart}How helpful would you say this guided tour was?%{emphasisEnd}%{lineBreak}%{lineBreak}"
-msgstr ""
-
-msgid "UserOnboardingTour|Guided GitLab Tour"
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can compare the changes of this branch to another one. Changes are divided by files so that it's easier to see what was changed where."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can create a project from scratch, start with a template or import a repository from other platforms. Whatever you choose, we'll guide you through the process.%{lineBreak}%{lineBreak}Fill in your new project information and click on %{emphasisStart}Create Project%{emphasisEnd} to progress to the next step."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can see the breakdown of the pipelines: its stages and jobs in each of the stages and their status.%{lineBreak}%{lineBreak}Our CI/CD pipelines are quite complex, most of our users have fewer and simpler pipelines."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can see the current members of the project (just you at the moment) and invite new members.%{lineBreak}%{lineBreak}You can invite multiple members at once (existing GitLab users or invite by email) and you can also set their roles and permissions.%{lineBreak}%{lineBreak}Add a few members and click on %{emphasisStart}Add to project%{emphasisEnd} to complete this step."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can see what changes were made with this commit, on what branch and if there's a related merge request. The status of the pipeline will also show up if CI/CD is set up.%{lineBreak}%{lineBreak}You can also comment on the lines of code that were changed and start a discussion with your colleagues!"
-msgstr ""
-
-msgid "UserOnboardingTour|Here's an overview of branches in the %{emphasisStart}%{projectName}%{emphasisEnd} project. They're split into Active and Stale.%{lineBreak}%{lineBreak}From here, you can create a new merge request from a branch, or compare the branch to any other branch in the project. By default, it will compare it to the master branch."
-msgstr ""
-
-msgid "UserOnboardingTour|Invite colleagues"
-msgstr ""
-
-msgid "UserOnboardingTour|Issues are great for communicating and keeping track of progress in GitLab. These are all issues that are open in the %{emphasisStart}%{projectName}%{emphasisEnd}.%{lineBreak}%{lineBreak}You can help us improve GitLab by contributing work to issues that are labeled <span class=\"badge color-label accept-mr-label\">Accepting merge requests</span>.%{lineBreak}%{lineBreak}This list can be filtered by labels, milestones, assignees, authors... We'll show you how it looks when the list is filtered by a label."
-msgstr ""
-
-msgid "UserOnboardingTour|Learn GitLab"
-msgstr ""
-
-msgid "UserOnboardingTour|Let's take a closer look at a merge request. Click on the title of one."
-msgstr ""
-
-msgid "UserOnboardingTour|Let's take a closer look at all the commits. Click on %{emphasisStart}Commits%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Let's take a closer look at the repository of this project. Click on %{emphasisStart}Repository%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|No thanks"
-msgstr ""
-
-msgid "UserOnboardingTour|Ok, let's go"
-msgstr ""
-
-msgid "UserOnboardingTour|Ok, show me"
-msgstr ""
-
-msgid "UserOnboardingTour|Open one of the issues by clicking on its title."
-msgstr ""
-
-msgid "UserOnboardingTour|Restart this step"
-msgstr ""
-
-msgid "UserOnboardingTour|Skip this step"
-msgstr ""
-
-msgid "UserOnboardingTour|Sweet! Your project was created and is ready to be used.%{lineBreak}%{lineBreak}You can start adding files to the repository or clone it. One last thing we want to show you is how to invite your colleagues to your new project."
-msgstr ""
-
-msgid "UserOnboardingTour|Take a look. Here's a nifty menu for quickly creating issues, merge requests, snippets, projects and groups. Click on it and select \"New project\" from the \"GitLab\" section to get started."
-msgstr ""
-
-msgid "UserOnboardingTour|Thanks for taking the guided tour. Remember, if you want to go through it again, you can start %{emphasisStart}Learn GitLab%{emphasisEnd} in the help menu on the top right."
-msgstr ""
-
-msgid "UserOnboardingTour|Thanks for the feedback! %{thumbsUp}"
-msgstr ""
-
-msgid "UserOnboardingTour|That's it for issues. Let'st take a look at %{emphasisStart}Merge Requests%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|That's it for merge requests. Now for the final part of this guided tour - the %{emphasisStart}CI/CD%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|That's it for the Repository. Let's take a look at the %{emphasisStart}Issues%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|The structure of this page is very similar to issues. Status, description, discussion and the sidebar are all here.%{lineBreak}%{lineBreak}But take a look below the description and you'll notice that there's more information about the merge request, the CI/CD pipeline and the options for approving it.%{lineBreak}%{lineBreak}Alongside the discussion you can also see more information about commits in this merge request, the status of pipelines and review all changes that were made."
-msgstr ""
-
-msgid "UserOnboardingTour|There's a lot of information here but don't worry, we'll go through it.%{lineBreak}%{lineBreak}On the top you can see the status of the issue and when it was opened and by whom. Directly below it is the issue description and below that are other %{emphasisStart}related issues%{emphasisEnd} and %{emphasisStart}merge requests%{emphasisEnd} (if any). Then below that is the %{emphasisStart}discussion%{emphasisEnd}, that's where most of the communication happens.%{lineBreak}%{lineBreak}On the right, there's a sidebar where you can view/change the %{emphasisStart}assignee, milestone, due date, labels, weight%{emphasisEnd}, etc."
+msgid "UserList|Delete %{name}?"
msgstr ""
-msgid "UserOnboardingTour|These are all the CI/CD pipelines we have for our %{emphasisStart}%{projectName}%{emphasisEnd} project.%{lineBreak}%{lineBreak}Here you can see the status of each pipeline, for what commit it's running for, its stages and the status for them."
-msgstr ""
-
-msgid "UserOnboardingTour|These are all the issues that are available for community contributions. Let's take a closer look at one of them."
-msgstr ""
-
-msgid "UserOnboardingTour|This is an overview of all merge requests in this project. Similarly to the issues overview it can be filtered down by things like labels, milestones, authors, assignees, etc."
-msgstr ""
-
-msgid "UserOnboardingTour|This is the repository for the %{emphasisStart}%{projectName}%{emphasisEnd} project. All our code is stored here. Feel free to explore and take a closer look at folders and files.%{lineBreak}%{lineBreak}Above the file structure you can see the latest commit, who the author is and the status of the CI/CD pipeline.%{lineBreak}%{lineBreak}If you scroll down below the file structure, you'll find the Readme of this project. This is defined in the README.md file at the root of the repository."
-msgstr ""
-
-msgid "UserOnboardingTour|Welcome to the project overview of the %{emphasisStart}%{projectName}%{emphasisEnd} project. This is the project that we use to work on GitLab. At first, a project seems like a simple repository, but at GitLab, a project is so much more.%{lineBreak}%{lineBreak}You can create projects for hosting your codebase, use it as an issue tracker, collaborate on code, and continuously build, test, and deploy your app with built-in GitLab CI/CD."
+msgid "UserList|created %{timeago}"
msgstr ""
msgid "UserProfile|Activity"
@@ -24721,6 +25376,9 @@ msgstr ""
msgid "UsersSelect|Unassigned"
msgstr ""
+msgid "Using %{codeStart}needs%{codeEnd} allows jobs to run before their stage is reached, as soon as their individual dependencies are met, which speeds up your pipelines."
+msgstr ""
+
msgid "Using %{code_start}::%{code_end} denotes a %{link_start}scoped label set%{link_end}"
msgstr ""
@@ -24805,9 +25463,6 @@ msgstr ""
msgid "Versions"
msgstr ""
-msgid "Very helpful"
-msgstr ""
-
msgid "View Documentation"
msgstr ""
@@ -24960,6 +25615,9 @@ msgstr ""
msgid "VisibilityLevel|Unknown"
msgstr ""
+msgid "Visit settings page"
+msgstr ""
+
msgid "VisualReviewApp|%{stepStart}Step 1%{stepEnd}. Copy the following script:"
msgstr ""
@@ -25053,6 +25711,9 @@ msgstr ""
msgid "VulnerabilityManagement|Something went wrong while trying to delete the comment. Please try again later."
msgstr ""
+msgid "VulnerabilityManagement|Something went wrong while trying to refresh the vulnerability. Please try again later."
+msgstr ""
+
msgid "VulnerabilityManagement|Something went wrong while trying to retrieve the vulnerability history. Please try again later."
msgstr ""
@@ -25095,6 +25756,9 @@ msgstr ""
msgid "Vulnerability|Class"
msgstr ""
+msgid "Vulnerability|Crash Address"
+msgstr ""
+
msgid "Vulnerability|Description"
msgstr ""
@@ -25170,9 +25834,6 @@ msgstr ""
msgid "We couldn't reach the Prometheus server. Either the server no longer exists or the configuration details need updating."
msgstr ""
-msgid "We created a short guided tour that will help you learn the basics of GitLab and how it will help you be better at your job. It should only take a couple of minutes. You will be guided by two types of helpers, best recognized by their color."
-msgstr ""
-
msgid "We detected potential spam in the %{humanized_resource_name}. Please solve the reCAPTCHA to proceed."
msgstr ""
@@ -25206,6 +25867,9 @@ msgstr ""
msgid "We've found no vulnerabilities"
msgstr ""
+msgid "Web Application Firewall"
+msgstr ""
+
msgid "Web IDE"
msgstr ""
@@ -25266,9 +25930,6 @@ msgstr ""
msgid "Welcome to GitLab.com<br>@%{name}!"
msgstr ""
-msgid "Welcome to the Guided GitLab Tour"
-msgstr ""
-
msgid "Welcome to the guided GitLab tour"
msgstr ""
@@ -25318,9 +25979,6 @@ msgstr ""
msgid "While it's rare to have no vulnerabilities, it can happen. In any event, we ask that you please double check your settings to make sure you've set up your dashboard correctly."
msgstr ""
-msgid "White helpers give contextual information."
-msgstr ""
-
msgid "Who can be an approver?"
msgstr ""
@@ -25366,12 +26024,18 @@ msgstr ""
msgid "WikiEmptyIssueMessage|Suggest wiki improvement"
msgstr ""
+msgid "WikiEmptyIssueMessage|You must be a group member in order to add wiki pages. If you have suggestions for how to improve the wiki for this group, consider opening an issue in the %{issues_link}."
+msgstr ""
+
msgid "WikiEmptyIssueMessage|You must be a project member in order to add wiki pages. If you have suggestions for how to improve the wiki for this project, consider opening an issue in the %{issues_link}."
msgstr ""
msgid "WikiEmptyIssueMessage|issue tracker"
msgstr ""
+msgid "WikiEmpty|A wiki is where you can store all the details about your group. This can include why you've created it, its principles, how to use it, and so on."
+msgstr ""
+
msgid "WikiEmpty|A wiki is where you can store all the details about your project. This can include why you've created it, its principles, how to use it, and so on."
msgstr ""
@@ -25381,12 +26045,21 @@ msgstr ""
msgid "WikiEmpty|Suggest wiki improvement"
msgstr ""
+msgid "WikiEmpty|The wiki lets you write documentation for your group"
+msgstr ""
+
msgid "WikiEmpty|The wiki lets you write documentation for your project"
msgstr ""
+msgid "WikiEmpty|This group has no wiki pages"
+msgstr ""
+
msgid "WikiEmpty|This project has no wiki pages"
msgstr ""
+msgid "WikiEmpty|You must be a group member in order to add wiki pages."
+msgstr ""
+
msgid "WikiEmpty|You must be a project member in order to add wiki pages."
msgstr ""
@@ -25663,13 +26336,13 @@ msgstr ""
msgid "You can now export your security dashboard to a CSV report."
msgstr ""
-msgid "You can now submit a merge request to get this change into the original branch."
+msgid "You can now manage alert endpoint configuration in the Alerts section on the Operations settings page. Fields on this page have been deprecated."
msgstr ""
-msgid "You can now submit a merge request to get this change into the original project."
+msgid "You can now submit a merge request to get this change into the original branch."
msgstr ""
-msgid "You can only add files when you are on a branch"
+msgid "You can now submit a merge request to get this change into the original project."
msgstr ""
msgid "You can only edit files when you are on a branch"
@@ -25891,7 +26564,7 @@ msgstr ""
msgid "You need to upload a Google Takeout archive."
msgstr ""
-msgid "You reached %{usage_in_percent} of %{namespace_name}'s capacity (%{used_storage} of %{storage_limit})"
+msgid "You reached %{usage_in_percent} of %{namespace_name}'s storage capacity (%{used_storage} of %{storage_limit})"
msgstr ""
msgid "You tried to fork %{link_to_the_project} but it failed for the following reason:"
@@ -26194,13 +26867,13 @@ msgstr ""
msgid "Your subscription expired!"
msgstr ""
-msgid "Your subscription has been downgraded"
+msgid "Your subscription has been downgraded."
msgstr ""
-msgid "Your subscription will automatically renew in %{remaining_days}"
+msgid "Your subscription will automatically renew in %{remaining_days}."
msgstr ""
-msgid "Your subscription will expire in %{remaining_days}"
+msgid "Your subscription will expire in %{remaining_days}."
msgstr ""
msgid "Zoom meeting added"
@@ -26282,9 +26955,6 @@ msgstr ""
msgid "branch name"
msgstr ""
-msgid "build pipeline reference mismatch"
-msgstr ""
-
msgid "by"
msgstr ""
@@ -26303,6 +26973,9 @@ msgstr ""
msgid "cannot block others"
msgstr ""
+msgid "cannot contain HTML/XML tags, including any word between angle brackets (<,>)."
+msgstr ""
+
msgid "cannot include leading slash or directory traversal."
msgstr ""
@@ -26333,65 +27006,6 @@ msgstr ""
msgid "ciReport|%{remainingPackagesCount} more"
msgstr ""
-msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability"
-msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities"
-msgstr[0] ""
-msgstr[1] ""
-msgstr[2] ""
-msgstr[3] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability for the source branch only"
-msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities for the source branch only"
-msgstr[0] ""
-msgstr[1] ""
-msgstr[2] ""
-msgstr[3] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerability"
-msgid_plural "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerabilities"
-msgstr[0] ""
-msgstr[1] ""
-msgstr[2] ""
-msgstr[3] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new vulnerability"
-msgid_plural "ciReport|%{reportType} %{status} detected %{newCount} new vulnerabilities"
-msgstr[0] ""
-msgstr[1] ""
-msgstr[2] ""
-msgstr[3] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities for the source branch only"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{fixedCount} fixed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} vulnerability for the source branch only"
-msgid_plural "ciReport|%{reportType} %{status} detected %{newCount} vulnerabilities for the source branch only"
-msgstr[0] ""
-msgstr[1] ""
-msgstr[2] ""
-msgstr[3] ""
-
-msgid "ciReport|%{reportType} %{status} detected no new vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected no vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected no vulnerabilities for the source branch only"
-msgstr ""
-
msgid "ciReport|%{reportType} is loading"
msgstr ""
@@ -26410,7 +27024,7 @@ msgstr ""
msgid "ciReport|All projects"
msgstr ""
-msgid "ciReport|All report types"
+msgid "ciReport|All scanner types"
msgstr ""
msgid "ciReport|All severities"
@@ -26434,6 +27048,9 @@ msgstr ""
msgid "ciReport|Container scanning detects known vulnerabilities in your docker images."
msgstr ""
+msgid "ciReport|Coverage Fuzzing"
+msgstr ""
+
msgid "ciReport|Create a merge request to implement this solution, or download and apply the patch manually."
msgstr ""
@@ -26594,6 +27211,9 @@ msgstr ""
msgid "customize"
msgstr ""
+msgid "data"
+msgstr ""
+
msgid "date must not be after 9999-12-31"
msgstr ""
@@ -26668,6 +27288,9 @@ msgstr ""
msgid "entries cannot contain HTML tags"
msgstr ""
+msgid "epic"
+msgstr ""
+
msgid "error"
msgstr ""
@@ -26689,9 +27312,6 @@ msgstr ""
msgid "expires on %{timebox_due_date}"
msgstr ""
-msgid "external_url"
-msgstr ""
-
msgid "failed"
msgstr ""
@@ -26729,9 +27349,6 @@ msgstr ""
msgid "from"
msgstr ""
-msgid "geo_node_name"
-msgstr ""
-
msgid "group"
msgstr ""
@@ -26753,9 +27370,6 @@ msgstr ""
msgid "https://your-bitbucket-server"
msgstr ""
-msgid "image"
-msgstr ""
-
msgid "image diff"
msgstr ""
@@ -26901,6 +27515,9 @@ msgstr ""
msgid "load it anyway"
msgstr ""
+msgid "loading"
+msgstr ""
+
msgid "locked by %{path_lock_user_name} %{created_at}"
msgstr ""
@@ -27031,9 +27648,6 @@ msgstr ""
msgid "mrWidget|Deployment statistics are not available currently"
msgstr ""
-msgid "mrWidget|Detect issues before deployment with a CI pipeline that continuously tests your code. We created a quick guide that will show you how to create one. Make your code more secure and more robust in just a minute."
-msgstr ""
-
msgid "mrWidget|Did not close"
msgstr ""
@@ -27196,6 +27810,9 @@ msgstr ""
msgid "mrWidget|To approve this merge request, please enter your password. This project requires all approvals to be authenticated."
msgstr ""
+msgid "mrWidget|Use %{linkStart}CI pipelines to test your code%{linkEnd}, simply add a GitLab CI configuration file to your project. It only takes a minute to make your code more secure and robust."
+msgstr ""
+
msgid "mrWidget|When this merge request is ready, remove the WIP: prefix from the title to allow it to be merged"
msgstr ""
@@ -27232,6 +27849,9 @@ msgstr ""
msgid "must be greater than start date"
msgstr ""
+msgid "must contain only valid frameworks"
+msgstr ""
+
msgid "my-awesome-group"
msgstr ""
@@ -27244,6 +27864,9 @@ msgstr ""
msgid "needs to be between 10 minutes and 1 month"
msgstr ""
+msgid "never"
+msgstr ""
+
msgid "never expires"
msgstr ""
@@ -27271,6 +27894,9 @@ msgstr ""
msgid "nounSeries|%{firstItem} and %{lastItem}"
msgstr ""
+msgid "nounSeries|%{item}"
+msgstr ""
+
msgid "nounSeries|%{item}, %{nextItem}"
msgstr ""
@@ -27417,6 +28043,9 @@ msgstr ""
msgid "revised"
msgstr ""
+msgid "satisfied"
+msgstr ""
+
msgid "score"
msgstr ""
@@ -27447,6 +28076,9 @@ msgstr ""
msgid "severity|Unknown"
msgstr ""
+msgid "should be an array of %{object_name} objects"
+msgstr ""
+
msgid "should be greater than or equal to %{access} inherited membership from group %{group_name}"
msgstr ""
@@ -27501,10 +28133,13 @@ msgstr ""
msgid "suggestPipeline|2/2: Commit your changes"
msgstr ""
-msgid "suggestPipeline|Commit the changes and your pipeline will automatically run for the first time."
+msgid "suggestPipeline|Choose %{boldStart}Code Quality%{boldEnd} to add a pipeline that tests the quality of your code."
msgstr ""
-msgid "suggestPipeline|We recommend the %{boldStart}Code Quality%{boldEnd} template, which will add a report widget to your Merge Requests. This way you’ll learn about code quality degradations much sooner. %{footerStart} Goodbye technical debt! %{footerEnd}"
+msgid "suggestPipeline|The template is ready! You can now commit it to create your first pipeline."
+msgstr ""
+
+msgid "suggestPipeline|We’re adding a GitLab CI configuration file to add a pipeline to the project. You could create it manually, but we recommend that you start with a GitLab template that works out of the box."
msgstr ""
msgid "syntax is correct"
@@ -27564,6 +28199,9 @@ msgstr ""
msgid "uses Kubernetes clusters to deploy your code!"
msgstr ""
+msgid "v%{version} published %{timeAgo}"
+msgstr ""
+
msgid "verify ownership"
msgstr ""
diff --git a/locale/sq_AL/gitlab.po b/locale/sq_AL/gitlab.po
index 79f7b5a1c5a..03d9222ce4c 100644
--- a/locale/sq_AL/gitlab.po
+++ b/locale/sq_AL/gitlab.po
@@ -2,7 +2,7 @@ msgid ""
msgstr ""
"Project-Id-Version: gitlab-ee\n"
"Report-Msgid-Bugs-To: \n"
-"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Last-Translator: \n"
"Language-Team: Albanian\n"
"Language: sq_AL\n"
"MIME-Version: 1.0\n"
@@ -10,9 +10,11 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Crowdin-Project: gitlab-ee\n"
+"X-Crowdin-Project-ID: 288872\n"
"X-Crowdin-Language: sq\n"
"X-Crowdin-File: /master/locale/gitlab.pot\n"
-"PO-Revision-Date: 2020-06-08 15:19\n"
+"X-Crowdin-File-ID: 6\n"
+"PO-Revision-Date: 2020-07-02 01:28\n"
msgid " %{start} to %{end}"
msgstr ""
@@ -197,6 +199,11 @@ msgid_plural "%d metrics"
msgstr[0] ""
msgstr[1] ""
+msgid "%d milestone"
+msgid_plural "%d milestones"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "%d minute"
msgid_plural "%d minutes"
msgstr[0] ""
@@ -242,11 +249,6 @@ msgid_plural "%d unresolved threads"
msgstr[0] ""
msgstr[1] ""
-msgid "%d url scanned"
-msgid_plural "%d urls scanned"
-msgstr[0] ""
-msgstr[1] ""
-
msgid "%d vulnerability"
msgid_plural "%d vulnerabilities"
msgstr[0] ""
@@ -324,9 +326,15 @@ msgstr[1] ""
msgid "%{count} related %{pluralized_subject}: %{links}"
msgstr ""
+msgid "%{dashboard_path} could not be found."
+msgstr ""
+
msgid "%{days} days until tags are automatically removed"
msgstr ""
+msgid "%{deployLinkStart}Use a template to deploy to ECS%{deployLinkEnd}, or use a docker image to %{commandsLinkStart}run AWS commands in GitLab CI/CD%{commandsLinkEnd}."
+msgstr ""
+
msgid "%{description}- Sentry event: %{errorUrl}- First seen: %{firstSeen}- Last seen: %{lastSeen} %{countLabel}: %{count}%{userCountLabel}: %{userCount}"
msgstr ""
@@ -369,12 +377,18 @@ msgstr ""
msgid "%{host} sign-in from new location"
msgstr ""
-msgid "%{icon}You are about to add %{usersTag} people to the discussion. Proceed with caution."
+msgid "%{icon}You are about to add %{usersTag} people to the discussion. They will all receive a notification."
+msgstr ""
+
+msgid "%{integrations_link_start}Integrations%{link_end} enable you to make third-party applications part of your GitLab workflow. If the available integrations don't meet your needs, consider using a %{webhooks_link_start}webhook%{link_end}."
msgstr ""
msgid "%{issuableType} will be removed! Are you sure?"
msgstr ""
+msgid "%{issuesCount} issues in this group"
+msgstr ""
+
msgid "%{issuesSize} issues"
msgstr ""
@@ -384,7 +398,7 @@ msgstr ""
msgid "%{labelStart}Class:%{labelEnd} %{class}"
msgstr ""
-msgid "%{labelStart}Confidence:%{labelEnd} %{confidence}"
+msgid "%{labelStart}Evidence:%{labelEnd} %{evidence}"
msgstr ""
msgid "%{labelStart}File:%{labelEnd} %{file}"
@@ -402,6 +416,9 @@ msgstr ""
msgid "%{labelStart}Report Type:%{labelEnd} %{reportType}"
msgstr ""
+msgid "%{labelStart}Scanner:%{labelEnd} %{scanner}"
+msgstr ""
+
msgid "%{labelStart}Severity:%{labelEnd} %{severity}"
msgstr ""
@@ -426,9 +443,6 @@ msgstr ""
msgid "%{link_start}Read more%{link_end} about role permissions"
msgstr ""
-msgid "%{link} can be used for binding events when something is happening within the project."
-msgstr ""
-
msgid "%{listToShow}, and %{awardsListLength} more."
msgstr ""
@@ -462,6 +476,9 @@ msgstr ""
msgid "%{name} found %{resultsString}"
msgstr ""
+msgid "%{name} is already being used for another emoji"
+msgstr ""
+
msgid "%{name} is scheduled for %{action}"
msgstr ""
@@ -509,6 +526,9 @@ msgstr ""
msgid "%{primary} (%{secondary})"
msgstr ""
+msgid "%{ref} cannot be added: %{error}"
+msgstr ""
+
msgid "%{releases} release"
msgid_plural "%{releases} releases"
msgstr[0] ""
@@ -517,13 +537,37 @@ msgstr[1] ""
msgid "%{remaining_approvals} left"
msgstr ""
-msgid "%{retryButtonStart}Try again%{retryButtonEnd} or %{newFileButtonStart}attach a new file%{newFileButtonEnd}"
+msgid "%{reportType} %{status} detected %{critical} critical and %{high} high severity vulnerabilities out of %{total}."
+msgstr ""
+
+msgid "%{reportType} %{status} detected %{critical} critical and %{high} high severity vulnerabilities."
+msgstr ""
+
+msgid "%{reportType} %{status} detected %{critical} critical severity vulnerabilities out of %{total}."
+msgstr ""
+
+msgid "%{reportType} %{status} detected %{critical} critical severity vulnerability."
+msgid_plural "%{reportType} %{status} detected %{critical} critical severity vulnerabilities."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{reportType} %{status} detected %{high} high severity vulnerabilities out of %{total}."
msgstr ""
-msgid "%{screenreaderOnlyStart}Keyboard shorcuts%{screenreaderOnlyEnd} Disabled"
+msgid "%{reportType} %{status} detected %{high} high severity vulnerability."
+msgid_plural "%{reportType} %{status} detected %{high} high severity vulnerabilities."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{reportType} %{status} detected %{other} vulnerability."
+msgid_plural "%{reportType} %{status} detected %{other} vulnerabilities."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{reportType} %{status} detected no new vulnerabilities."
msgstr ""
-msgid "%{screenreaderOnlyStart}Keyboard shorcuts%{screenreaderOnlyEnd} Enabled"
+msgid "%{retryButtonStart}Try again%{retryButtonEnd} or %{newFileButtonStart}attach a new file%{newFileButtonEnd}"
msgstr ""
msgid "%{service_title} %{message}."
@@ -620,6 +664,12 @@ msgstr ""
msgid "%{token}..."
msgstr ""
+msgid "%{totalCpu} (%{freeSpacePercentage}%{percentSymbol} free)"
+msgstr ""
+
+msgid "%{totalMemory} (%{freeSpacePercentage}%{percentSymbol} free)"
+msgstr ""
+
msgid "%{totalWeight} total weight"
msgstr ""
@@ -650,6 +700,12 @@ msgstr ""
msgid "%{verb} %{time_spent_value} spent time."
msgstr ""
+msgid "%{webhooks_link_start}%{webhook_type}%{link_end} enable you to send notifications to web applications in response to events in a group or project."
+msgstr ""
+
+msgid "%{webhooks_link_start}%{webhook_type}%{link_end} enable you to send notifications to web applications in response to events in a group or project. We recommend using an %{integrations_link_start}integration%{link_end} in preference to a webhook."
+msgstr ""
+
msgid "'%{level}' is not a valid visibility level"
msgstr ""
@@ -676,9 +732,6 @@ msgstr ""
msgid "(No changes)"
msgstr ""
-msgid "(Show all)"
-msgstr ""
-
msgid "(check progress)"
msgstr ""
@@ -745,6 +798,9 @@ msgstr ""
msgid "0 for unlimited"
msgstr ""
+msgid "0 for unlimited, only effective with remote storage enabled."
+msgstr ""
+
msgid "1 %{type} addition"
msgid_plural "%{count} %{type} additions"
msgstr[0] ""
@@ -892,6 +948,12 @@ msgstr ""
msgid "<code>\"johnsmith@example.com\": \"johnsmith@example.com\"</code> will add \"By <a href=\"#\">johnsmith@example.com</a>\" to all issues and comments originally created by johnsmith@example.com. By default, the email address or username is masked to ensure the user's privacy. Use this option if you want to show the full email address."
msgstr ""
+msgid "<code>Masked</code> to prevent the values from being displayed in job logs (must match certain regexp requirements)."
+msgstr ""
+
+msgid "<code>Protected</code> to expose them to protected branches or tags only."
+msgstr ""
+
msgid "<namespace / project>"
msgstr ""
@@ -919,9 +981,6 @@ msgstr ""
msgid "A .NET Core console application template, customizable for any .NET Core project"
msgstr ""
-msgid "A DAG must have two dependent jobs to be visualized on this tab."
-msgstr ""
-
msgid "A GitBook site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
msgstr ""
@@ -952,6 +1011,12 @@ msgstr ""
msgid "A deleted user"
msgstr ""
+msgid "A file has been changed."
+msgstr ""
+
+msgid "A file was not found."
+msgstr ""
+
msgid "A file with '%{file_name}' already exists in %{branch} branch"
msgstr ""
@@ -1018,7 +1083,7 @@ msgstr ""
msgid "A subscription will trigger a new pipeline on the default branch of this project when a pipeline successfully completes for a new tag on the %{default_branch_docs} of the subscribed project."
msgstr ""
-msgid "A terraform report was generated in your pipelines."
+msgid "A suggestion is not applicable."
msgstr ""
msgid "A user with write access to the source branch selected this option"
@@ -1189,6 +1254,9 @@ msgstr ""
msgid "Action to take when receiving an alert."
msgstr ""
+msgid "Actions"
+msgstr ""
+
msgid "Activate"
msgstr ""
@@ -1245,6 +1313,9 @@ msgstr ""
msgid "Add LICENSE"
msgstr ""
+msgid "Add New Node"
+msgstr ""
+
msgid "Add README"
msgstr ""
@@ -1320,6 +1391,9 @@ msgstr ""
msgid "Add approval rule"
msgstr ""
+msgid "Add approvers"
+msgstr ""
+
msgid "Add bold text"
msgstr ""
@@ -1377,6 +1451,9 @@ msgstr ""
msgid "Add strikethrough text"
msgstr ""
+msgid "Add suggestion to batch"
+msgstr ""
+
msgid "Add system hook"
msgstr ""
@@ -1476,6 +1553,9 @@ msgstr ""
msgid "Admin Note"
msgstr ""
+msgid "Admin Notifications"
+msgstr ""
+
msgid "Admin Overview"
msgstr ""
@@ -1587,6 +1667,9 @@ msgstr ""
msgid "AdminSettings|Integrations configured here will automatically apply to all projects on this instance."
msgstr ""
+msgid "AdminSettings|Moved to integrations"
+msgstr ""
+
msgid "AdminSettings|No required pipeline"
msgstr ""
@@ -1817,12 +1900,18 @@ msgid_plural "Alerts"
msgstr[0] ""
msgstr[1] ""
+msgid "AlertManagement| assign yourself"
+msgstr ""
+
msgid "AlertManagement|Acknowledged"
msgstr ""
msgid "AlertManagement|Alert"
msgstr ""
+msgid "AlertManagement|Alert assignee(s): %{assignees}"
+msgstr ""
+
msgid "AlertManagement|Alert detail"
msgstr ""
@@ -1838,9 +1927,15 @@ msgstr ""
msgid "AlertManagement|All alerts"
msgstr ""
+msgid "AlertManagement|Assign To"
+msgstr ""
+
msgid "AlertManagement|Assign status"
msgstr ""
+msgid "AlertManagement|Assignee"
+msgstr ""
+
msgid "AlertManagement|Assignees"
msgstr ""
@@ -1859,9 +1954,6 @@ msgstr ""
msgid "AlertManagement|Edit"
msgstr ""
-msgid "AlertManagement|End time"
-msgstr ""
-
msgid "AlertManagement|Events"
msgstr ""
@@ -1880,10 +1972,13 @@ msgstr ""
msgid "AlertManagement|More information"
msgstr ""
+msgid "AlertManagement|No Matching Results"
+msgstr ""
+
msgid "AlertManagement|No alert data to display."
msgstr ""
-msgid "AlertManagement|No alerts available to display. If you think you're seeing this message in error, refresh the page."
+msgid "AlertManagement|No alerts available to display. See %{linkStart}enabling alert management%{linkEnd} for more information on adding alerts to the list."
msgstr ""
msgid "AlertManagement|No alerts to display."
@@ -1892,6 +1987,9 @@ msgstr ""
msgid "AlertManagement|None"
msgstr ""
+msgid "AlertManagement|None -"
+msgstr ""
+
msgid "AlertManagement|Open"
msgstr ""
@@ -1928,9 +2026,18 @@ msgstr ""
msgid "AlertManagement|There was an error displaying the alerts. Confirm your endpoint's configuration details to ensure alerts appear."
msgstr ""
+msgid "AlertManagement|There was an error while updating the assignee(s) list. Please try again."
+msgstr ""
+
+msgid "AlertManagement|There was an error while updating the assignee(s) of the alert. Please try again."
+msgstr ""
+
msgid "AlertManagement|There was an error while updating the status of the alert. Please try again."
msgstr ""
+msgid "AlertManagement|This assignee cannot be assigned to this alert."
+msgstr ""
+
msgid "AlertManagement|Tool"
msgstr ""
@@ -2078,6 +2185,9 @@ msgstr ""
msgid "Allow users to request access (if visibility is public or internal)"
msgstr ""
+msgid "Allowed Geo IP"
+msgstr ""
+
msgid "Allowed email domain restriction only permitted for top-level groups"
msgstr ""
@@ -2111,6 +2221,9 @@ msgstr ""
msgid "Amazon Web Services"
msgstr ""
+msgid "Amazon Web Services Logo"
+msgstr ""
+
msgid "Amazon authentication is not %{link_start}correctly configured%{link_end}. Ask your GitLab administrator if you want to use this service."
msgstr ""
@@ -2168,10 +2281,16 @@ msgstr ""
msgid "An error occurred when updating the issue weight"
msgstr ""
+msgid "An error occurred while acknowledging the notification. Refresh the page and try again."
+msgstr ""
+
+msgid "An error occurred while adding approvers"
+msgstr ""
+
msgid "An error occurred while adding formatted title for epic"
msgstr ""
-msgid "An error occurred while checking group path"
+msgid "An error occurred while checking group path. Please refresh and try again."
msgstr ""
msgid "An error occurred while committing your changes."
@@ -2315,7 +2434,7 @@ msgstr ""
msgid "An error occurred while loading milestones"
msgstr ""
-msgid "An error occurred while loading terraform report"
+msgid "An error occurred while loading project creation UI"
msgstr ""
msgid "An error occurred while loading the data. Please try again."
@@ -2369,6 +2488,9 @@ msgstr ""
msgid "An error occurred while rendering preview broadcast message"
msgstr ""
+msgid "An error occurred while rendering the editor"
+msgstr ""
+
msgid "An error occurred while reordering issues."
msgstr ""
@@ -2465,7 +2587,7 @@ msgstr ""
msgid "Analyze your dependencies for known vulnerabilities."
msgstr ""
-msgid "Analyze your source code and git history for secrets"
+msgid "Analyze your source code and git history for secrets."
msgstr ""
msgid "Analyze your source code for known vulnerabilities."
@@ -2579,12 +2701,18 @@ msgstr ""
msgid "Apply suggestion"
msgstr ""
+msgid "Apply suggestions"
+msgstr ""
+
msgid "Apply template"
msgstr ""
msgid "Apply this approval rule to any branch or a specific protected branch."
msgstr ""
+msgid "Applying"
+msgstr ""
+
msgid "Applying a template will replace the existing issue description. Any changes you have made will be lost."
msgstr ""
@@ -2597,12 +2725,18 @@ msgstr ""
msgid "Applying multiple commands"
msgstr ""
-msgid "Applying suggestion"
+msgid "Applying suggestion..."
+msgstr ""
+
+msgid "Applying suggestions..."
msgstr ""
msgid "Approval rules"
msgstr ""
+msgid "Approval rules reset to project defaults"
+msgstr ""
+
msgid "ApprovalRuleRemove|%d member"
msgid_plural "ApprovalRuleRemove|%d members"
msgstr[0] ""
@@ -2656,6 +2790,9 @@ msgstr ""
msgid "Approved"
msgstr ""
+msgid "Approved MRs"
+msgstr ""
+
msgid "Approved by: "
msgstr ""
@@ -2869,6 +3006,9 @@ msgstr ""
msgid "Assign"
msgstr ""
+msgid "Assign Iteration"
+msgstr ""
+
msgid "Assign custom color like #FF0000"
msgstr ""
@@ -2887,6 +3027,9 @@ msgstr ""
msgid "Assign to"
msgstr ""
+msgid "Assign to commenting user"
+msgstr ""
+
msgid "Assign yourself to these issues"
msgstr ""
@@ -2913,6 +3056,9 @@ msgid_plural "%d Assignees"
msgstr[0] ""
msgstr[1] ""
+msgid "Assignee has no permissions"
+msgstr ""
+
msgid "Assignee lists not available with your current license"
msgstr ""
@@ -2922,6 +3068,9 @@ msgstr ""
msgid "Assignee(s)"
msgstr ""
+msgid "Assignees"
+msgstr ""
+
msgid "Assigns %{assignee_users_sentence}."
msgstr ""
@@ -2957,6 +3106,9 @@ msgstr ""
msgid "Audit Events is a way to keep track of important events that happened in GitLab."
msgstr ""
+msgid "Audit Log"
+msgstr ""
+
msgid "AuditEvents|(removed)"
msgstr ""
@@ -2993,6 +3145,9 @@ msgstr ""
msgid "AuditLogs|IP Address"
msgstr ""
+msgid "AuditLogs|Member Events"
+msgstr ""
+
msgid "AuditLogs|No matching %{type} found."
msgstr ""
@@ -3275,9 +3430,6 @@ msgstr ""
msgid "Badges|e.g. %{exampleUrl}"
msgstr ""
-msgid "Badge|New"
-msgstr ""
-
msgid "Balsamiq file could not be loaded."
msgstr ""
@@ -3326,6 +3478,12 @@ msgstr ""
msgid "Below you will find all the groups that are public."
msgstr ""
+msgid "Beta"
+msgstr ""
+
+msgid "Bi-weekly code coverage"
+msgstr ""
+
msgid "Billing"
msgstr ""
@@ -3386,9 +3544,15 @@ msgstr ""
msgid "Bitbucket Server Import"
msgstr ""
+msgid "Bitbucket Server import"
+msgstr ""
+
msgid "Bitbucket import"
msgstr ""
+msgid "Blame"
+msgstr ""
+
msgid "Blocked"
msgstr ""
@@ -3401,9 +3565,6 @@ msgstr ""
msgid "Blog"
msgstr ""
-msgid "Blue helpers indicate an action to be taken."
-msgstr ""
-
msgid "Board name"
msgstr ""
@@ -3602,6 +3763,9 @@ msgstr ""
msgid "Broadcast Message was successfully updated."
msgstr ""
+msgid "Broadcast Messages"
+msgstr ""
+
msgid "Browse Directory"
msgstr ""
@@ -3656,6 +3820,9 @@ msgstr ""
msgid "By %{user_name}"
msgstr ""
+msgid "By URL"
+msgstr ""
+
msgid "By default GitLab sends emails in HTML and plain text formats so mail clients can choose what format to use. Disable this option if you only want to send emails in plain text format."
msgstr ""
@@ -3761,6 +3928,9 @@ msgstr ""
msgid "Can override approvers and approvals required per merge request"
msgstr ""
+msgid "Can't apply as this line has changed or the suggestion already matches its content."
+msgstr ""
+
msgid "Can't create snippet: %{err}"
msgstr ""
@@ -3947,9 +4117,6 @@ msgstr ""
msgid "Changes are still tracked. Useful for cluster/index migrations."
msgstr ""
-msgid "Changes are unknown"
-msgstr ""
-
msgid "Changes suppressed. Click to show."
msgstr ""
@@ -4256,15 +4423,9 @@ msgstr ""
msgid "Choose what content you want to see on a group’s overview page"
msgstr ""
-msgid "Choose which groups you wish to synchronize to this secondary node"
-msgstr ""
-
msgid "Choose which repositories you want to connect and run CI/CD pipelines."
msgstr ""
-msgid "Choose which shards you wish to synchronize to this secondary node"
-msgstr ""
-
msgid "Choose your framework"
msgstr ""
@@ -4562,9 +4723,15 @@ msgstr ""
msgid "Cluster does not exist"
msgstr ""
+msgid "Cluster is required for Stages::ClusterEndpointInserter"
+msgstr ""
+
msgid "Cluster level"
msgstr ""
+msgid "Cluster type must be specificed for Stages::ClusterEndpointInserter"
+msgstr ""
+
msgid "ClusterIntegration| %{custom_domain_start}More information%{custom_domain_end}."
msgstr ""
@@ -4676,7 +4843,7 @@ msgstr ""
msgid "ClusterIntegration|Cert-Manager"
msgstr ""
-msgid "ClusterIntegration|Cert-Manager is a native Kubernetes certificate management controller that helps with issuing certificates. Installing Cert-Manager on your cluster will issue a certificate by %{letsEncrypt} and ensure that certificates are valid and up-to-date."
+msgid "ClusterIntegration|Cert-Manager is a native Kubernetes certificate management controller that helps with issuing certificates. Installing Cert-Manager on your cluster will issue a certificate by %{linkStart}Let's Encrypt%{linkEnd} and ensure that certificates are valid and up-to-date."
msgstr ""
msgid "ClusterIntegration|Certificate Authority bundle (PEM format)"
@@ -4703,9 +4870,6 @@ msgstr ""
msgid "ClusterIntegration|Clear the local cache of namespace and service accounts. This is necessary if your integration has become out of sync. The cache is repopulated during the next CI job that requires namespace and service accounts."
msgstr ""
-msgid "ClusterIntegration|Cloud Run"
-msgstr ""
-
msgid "ClusterIntegration|Cluster being created"
msgstr ""
@@ -4790,7 +4954,7 @@ msgstr ""
msgid "ClusterIntegration|Crossplane"
msgstr ""
-msgid "ClusterIntegration|Crossplane enables declarative provisioning of managed services from your cloud of choice using %{codeStart}kubectl%{codeEnd} or %{gitlabIntegrationLink}. Crossplane runs inside your Kubernetes cluster and supports secure connectivity and secrets management between app containers and the cloud services they depend on."
+msgid "ClusterIntegration|Crossplane enables declarative provisioning of managed services from your cloud of choice using %{codeStart}kubectl%{codeEnd} or %{linkStart}GitLab Integration%{linkEnd}. Crossplane runs inside your Kubernetes cluster and supports secure connectivity and secrets management between app containers and the cloud services they depend on."
msgstr ""
msgid "ClusterIntegration|Deletes all GitLab resources attached to this cluster during removal"
@@ -4874,9 +5038,6 @@ msgstr ""
msgid "ClusterIntegration|GitLab-managed cluster"
msgstr ""
-msgid "ClusterIntegration|Gitlab Integration"
-msgstr ""
-
msgid "ClusterIntegration|Global default"
msgstr ""
@@ -4922,7 +5083,7 @@ msgstr ""
msgid "ClusterIntegration|Ingress gives you a way to route requests to services based on the request host or path, centralizing a number of services into a single entrypoint."
msgstr ""
-msgid "ClusterIntegration|Installing Ingress may incur additional costs. Learn more about %{pricingLink}."
+msgid "ClusterIntegration|Installing Ingress may incur additional costs. Learn more about %{linkStart}pricing%{linkEnd}."
msgstr ""
msgid "ClusterIntegration|Instance cluster"
@@ -4937,7 +5098,7 @@ msgstr ""
msgid "ClusterIntegration|Issuer Email"
msgstr ""
-msgid "ClusterIntegration|Issuers represent a certificate authority. You must provide an email address for your Issuer. "
+msgid "ClusterIntegration|Issuers represent a certificate authority. You must provide an email address for your Issuer."
msgstr ""
msgid "ClusterIntegration|Jupyter Hostname"
@@ -5009,9 +5170,6 @@ msgstr ""
msgid "ClusterIntegration|Learn more about instance Kubernetes clusters"
msgstr ""
-msgid "ClusterIntegration|Let's Encrypt"
-msgstr ""
-
msgid "ClusterIntegration|Loading IAM Roles"
msgstr ""
@@ -5123,7 +5281,7 @@ msgstr ""
msgid "ClusterIntegration|Prometheus"
msgstr ""
-msgid "ClusterIntegration|Prometheus is an open-source monitoring system with %{gitlabIntegrationLink} to monitor deployed applications."
+msgid "ClusterIntegration|Prometheus is an open-source monitoring system with %{linkStart}GitLab Integration%{linkEnd} to monitor deployed applications."
msgstr ""
msgid "ClusterIntegration|Provider details"
@@ -5279,10 +5437,10 @@ msgstr ""
msgid "ClusterIntegration|Select zone to choose machine type"
msgstr ""
-msgid "ClusterIntegration|Send Cilium Logs"
+msgid "ClusterIntegration|Send Container Network Policies Logs"
msgstr ""
-msgid "ClusterIntegration|Send ModSecurity Logs"
+msgid "ClusterIntegration|Send Web Application Firewall Logs"
msgstr ""
msgid "ClusterIntegration|Service Token"
@@ -5450,15 +5608,12 @@ msgstr ""
msgid "ClusterIntegration|documentation"
msgstr ""
-msgid "ClusterIntegration|installed via %{installed_via}"
+msgid "ClusterIntegration|installed via %{linkStart}Cloud Run%{linkEnd}"
msgstr ""
msgid "ClusterIntegration|meets the requirements"
msgstr ""
-msgid "ClusterIntegration|pricing"
-msgstr ""
-
msgid "ClusterIntegration|sign up"
msgstr ""
@@ -5495,6 +5650,15 @@ msgstr ""
msgid "Code"
msgstr ""
+msgid "Code Coverage: %{coveragePercentage}%{percentSymbol}"
+msgstr ""
+
+msgid "Code Coverage| Empty code coverage data"
+msgstr ""
+
+msgid "Code Coverage|Couldn't fetch the code coverage data"
+msgstr ""
+
msgid "Code Owners"
msgstr ""
@@ -5549,7 +5713,7 @@ msgstr ""
msgid "Collapse approvers"
msgstr ""
-msgid "Collapse child epics"
+msgid "Collapse milestones"
msgstr ""
msgid "Collapse replies"
@@ -5567,6 +5731,9 @@ msgstr ""
msgid "Coming soon"
msgstr ""
+msgid "Comma-separated, e.g. '1.1.1.1, 2.2.2.0/24'"
+msgstr ""
+
msgid "Command"
msgstr ""
@@ -5737,6 +5904,12 @@ msgstr ""
msgid "Compliance framework (optional)"
msgstr ""
+msgid "Compliance frameworks"
+msgstr ""
+
+msgid "ComplianceDashboard|created by:"
+msgstr ""
+
msgid "ComplianceFramework|GDPR"
msgstr ""
@@ -5875,6 +6048,9 @@ msgstr ""
msgid "Connection timed out"
msgstr ""
+msgid "Connection timeout"
+msgstr ""
+
msgid "Contact sales to upgrade"
msgstr ""
@@ -5930,9 +6106,6 @@ msgstr ""
msgid "ContainerRegistry|CLI Commands"
msgstr ""
-msgid "ContainerRegistry|Compressed Size"
-msgstr ""
-
msgid "ContainerRegistry|Container Registry"
msgstr ""
@@ -5948,6 +6121,9 @@ msgstr ""
msgid "ContainerRegistry|Copy push command"
msgstr ""
+msgid "ContainerRegistry|Delete selected"
+msgstr ""
+
msgid "ContainerRegistry|Docker connection error"
msgstr ""
@@ -5981,16 +6157,16 @@ msgstr ""
msgid "ContainerRegistry|If you are not already logged in, you need to authenticate to the Container Registry by using your GitLab username and password. If you have %{twofaDocLinkStart}Two-Factor Authentication%{twofaDocLinkEnd} enabled, use a %{personalAccessTokensDocLinkStart}Personal Access Token%{personalAccessTokensDocLinkEnd} instead of a password."
msgstr ""
-msgid "ContainerRegistry|Image ID"
+msgid "ContainerRegistry|Image ID: %{imageId}"
msgstr ""
msgid "ContainerRegistry|Image Repositories"
msgstr ""
-msgid "ContainerRegistry|Keep and protect the images that matter most."
+msgid "ContainerRegistry|Image tags"
msgstr ""
-msgid "ContainerRegistry|Last Updated"
+msgid "ContainerRegistry|Keep and protect the images that matter most."
msgstr ""
msgid "ContainerRegistry|Login"
@@ -6005,6 +6181,9 @@ msgstr ""
msgid "ContainerRegistry|Please contact your administrator."
msgstr ""
+msgid "ContainerRegistry|Published %{timeInfo}"
+msgstr ""
+
msgid "ContainerRegistry|Push an image"
msgstr ""
@@ -6020,9 +6199,6 @@ msgstr ""
msgid "ContainerRegistry|Remove repository"
msgstr ""
-msgid "ContainerRegistry|Remove selected tags"
-msgstr ""
-
msgid "ContainerRegistry|Remove tag"
msgid_plural "ContainerRegistry|Remove tags"
msgstr[0] ""
@@ -6052,9 +6228,6 @@ msgstr ""
msgid "ContainerRegistry|Sorry, your filter produced no results."
msgstr ""
-msgid "ContainerRegistry|Tag"
-msgstr ""
-
msgid "ContainerRegistry|Tag expiration policy"
msgstr ""
@@ -6199,21 +6372,6 @@ msgstr ""
msgid "Control the display of third party offers."
msgstr ""
-msgid "Control the maximum concurrency of LFS/attachment backfill for this secondary node"
-msgstr ""
-
-msgid "Control the maximum concurrency of container repository operations for this Geo node"
-msgstr ""
-
-msgid "Control the maximum concurrency of repository backfill for this secondary node"
-msgstr ""
-
-msgid "Control the maximum concurrency of verification operations for this Geo node"
-msgstr ""
-
-msgid "Control the minimum interval in days that a repository should be reverified for this primary node"
-msgstr ""
-
msgid "Cookie domain"
msgstr ""
@@ -6355,6 +6513,9 @@ msgstr ""
msgid "Could not find design."
msgstr ""
+msgid "Could not find iteration"
+msgstr ""
+
msgid "Could not remove the trigger."
msgstr ""
@@ -6391,6 +6552,12 @@ msgstr ""
msgid "Coverage"
msgstr ""
+msgid "Coverage Fuzzing"
+msgstr ""
+
+msgid "Crash State"
+msgstr ""
+
msgid "Create"
msgstr ""
@@ -6439,6 +6606,9 @@ msgstr ""
msgid "Create a personal access token on your account to pull or push via %{protocol}."
msgstr ""
+msgid "Create a project pre-populated with the necessary files to get you started quickly."
+msgstr ""
+
msgid "Create an account using:"
msgstr ""
@@ -6607,6 +6777,9 @@ msgstr ""
msgid "Creates branch '%{branch_name}' and a merge request to resolve this issue."
msgstr ""
+msgid "Creating"
+msgstr ""
+
msgid "Creating epic"
msgstr ""
@@ -6658,7 +6831,10 @@ msgstr ""
msgid "Current vulnerabilities count"
msgstr ""
-msgid "CurrentUser|Buy CI minutes"
+msgid "CurrentUser|Buy Pipeline minutes"
+msgstr ""
+
+msgid "CurrentUser|One of your groups is running out"
msgstr ""
msgid "CurrentUser|Profile"
@@ -6673,6 +6849,9 @@ msgstr ""
msgid "CurrentUser|Upgrade"
msgstr ""
+msgid "Custom Attributes"
+msgstr ""
+
msgid "Custom CI configuration path"
msgstr ""
@@ -6936,12 +7115,18 @@ msgstr ""
msgid "DAG"
msgstr ""
+msgid "DAG visualization requires at least 3 dependent jobs."
+msgstr ""
+
msgid "DNS"
msgstr ""
msgid "Dashboard"
msgstr ""
+msgid "Dashboard uid not found"
+msgstr ""
+
msgid "DashboardProjects|All"
msgstr ""
@@ -6966,6 +7151,9 @@ msgstr ""
msgid "Data is still calculating..."
msgstr ""
+msgid "Datasource name not found"
+msgstr ""
+
msgid "Date"
msgstr ""
@@ -7146,6 +7334,9 @@ msgstr ""
msgid "Delete this attachment"
msgstr ""
+msgid "Delete user list"
+msgstr ""
+
msgid "Delete variable"
msgstr ""
@@ -7462,6 +7653,9 @@ msgstr ""
msgid "Deploying to"
msgstr ""
+msgid "Deploying to AWS is easy with GitLab"
+msgstr ""
+
msgid "Deployment Frequency"
msgstr ""
@@ -7824,6 +8018,9 @@ msgstr ""
msgid "Dismissed on pipeline %{pipelineLink} at %{projectLink}"
msgstr ""
+msgid "Display alerts from all your monitoring tools directly within GitLab."
+msgstr ""
+
msgid "Display name"
msgstr ""
@@ -7881,9 +8078,6 @@ msgstr ""
msgid "Don't show again"
msgstr ""
-msgid "Don't worry, you can access this tour by clicking on the help icon in the top right corner and choose <strong>Learn GitLab</strong>."
-msgstr ""
-
msgid "Done"
msgstr ""
@@ -8010,6 +8204,9 @@ msgstr ""
msgid "Edit Release"
msgstr ""
+msgid "Edit Slack integration"
+msgstr ""
+
msgid "Edit Snippet"
msgstr ""
@@ -8049,6 +8246,9 @@ msgstr ""
msgid "Edit issues"
msgstr ""
+msgid "Edit iteration"
+msgstr ""
+
msgid "Edit public deploy key"
msgstr ""
@@ -8097,6 +8297,9 @@ msgstr ""
msgid "Email"
msgstr ""
+msgid "Email Notification"
+msgstr ""
+
msgid "Email address"
msgstr ""
@@ -8109,6 +8312,9 @@ msgstr ""
msgid "Email not verified. Please verify your email in Salesforce."
msgstr ""
+msgid "Email notification for unknown sign-ins"
+msgstr ""
+
msgid "Email patch"
msgstr ""
@@ -8340,6 +8546,9 @@ msgstr ""
msgid "Enforce DNS rebinding attack protection"
msgstr ""
+msgid "Enforce personal access token expiration"
+msgstr ""
+
msgid "Ensure connectivity is available from the GitLab server to the Prometheus server"
msgstr ""
@@ -8409,6 +8618,9 @@ msgstr ""
msgid "Enter the name of your application, and we'll return a unique %{type}."
msgstr ""
+msgid "Enter weights for storages for new repositories."
+msgstr ""
+
msgid "Enter your password to approve"
msgstr ""
@@ -8418,15 +8630,27 @@ msgstr ""
msgid "Environment does not have deployments"
msgstr ""
+msgid "Environment is required for Stages::MetricEndpointInserter"
+msgstr ""
+
+msgid "Environment is required for Stages::VariableEndpointInserter"
+msgstr ""
+
msgid "Environment scope"
msgstr ""
+msgid "Environment variables are applied to all project environments in this instance via the Runner. You can use environment variables for passwords, secret keys, etc. Make variables available to the running application by prepending the variable key with <code>K8S_SECRET_</code>. You can set variables to be:"
+msgstr ""
+
msgid "Environment variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. Additionally, they can be masked so they are hidden in job logs, though they must match certain regexp requirements to do so. You can use environment variables for passwords, secret keys, or whatever you want."
msgstr ""
msgid "Environment variables are configured by your administrator to be %{link_start}protected%{link_end} by default"
msgstr ""
+msgid "Environment variables on this GitLab instance are configured to be %{link_start}protected%{link_end} by default"
+msgstr ""
+
msgid "Environment:"
msgstr ""
@@ -8958,6 +9182,9 @@ msgstr ""
msgid "EventFilterBy|Filter by comments"
msgstr ""
+msgid "EventFilterBy|Filter by designs"
+msgstr ""
+
msgid "EventFilterBy|Filter by epic events"
msgstr ""
@@ -9090,15 +9317,15 @@ msgstr ""
msgid "Expand approvers"
msgstr ""
-msgid "Expand child epics"
-msgstr ""
-
msgid "Expand down"
msgstr ""
msgid "Expand dropdown"
msgstr ""
+msgid "Expand milestones"
+msgstr ""
+
msgid "Expand sidebar"
msgstr ""
@@ -9114,6 +9341,9 @@ msgstr ""
msgid "Expiration date"
msgstr ""
+msgid "Expiration not enforced"
+msgstr ""
+
msgid "Expiration policy for the Container Registry is a perfect solution for keeping the Registry space down while still enjoying the full power of GitLab CI/CD."
msgstr ""
@@ -9234,6 +9464,9 @@ msgstr ""
msgid "Failed Jobs"
msgstr ""
+msgid "Failed on"
+msgstr ""
+
msgid "Failed to add a Zoom meeting"
msgstr ""
@@ -9294,13 +9527,19 @@ msgstr ""
msgid "Failed to install."
msgstr ""
+msgid "Failed to load assignees. Please try again."
+msgstr ""
+
+msgid "Failed to load authors. Please try again."
+msgstr ""
+
msgid "Failed to load emoji list."
msgstr ""
msgid "Failed to load error details from Sentry."
msgstr ""
-msgid "Failed to load errors from Sentry. Error message: %{errorMessage}"
+msgid "Failed to load errors from Sentry."
msgstr ""
msgid "Failed to load group activity metrics. Please try again."
@@ -9381,6 +9620,9 @@ msgstr ""
msgid "Failed to set due date because the date format is invalid."
msgstr ""
+msgid "Failed to set iteration on this issue. Please try again."
+msgstr ""
+
msgid "Failed to signing using smartcard authentication"
msgstr ""
@@ -9603,13 +9845,10 @@ msgstr ""
msgid "FeatureFlags|Target environments"
msgstr ""
-msgid "FeatureFlags|There are no active feature flags"
-msgstr ""
-
-msgid "FeatureFlags|There are no inactive feature flags"
+msgid "FeatureFlags|There was an error fetching the feature flags."
msgstr ""
-msgid "FeatureFlags|There was an error fetching the feature flags."
+msgid "FeatureFlags|There was an error retrieving user lists"
msgstr ""
msgid "FeatureFlags|Try again in a few moments or contact your support team."
@@ -9621,9 +9860,18 @@ msgstr ""
msgid "FeatureFlag|Delete strategy"
msgstr ""
+msgid "FeatureFlag|List"
+msgstr ""
+
msgid "FeatureFlag|Percentage"
msgstr ""
+msgid "FeatureFlag|Select a user list"
+msgstr ""
+
+msgid "FeatureFlag|There are no configured user lists"
+msgstr ""
+
msgid "FeatureFlag|Type"
msgstr ""
@@ -9780,6 +10028,12 @@ msgstr ""
msgid "Filter..."
msgstr ""
+msgid "Find File"
+msgstr ""
+
+msgid "Find bugs in your code with coverage-guided fuzzing"
+msgstr ""
+
msgid "Find by path"
msgstr ""
@@ -10044,6 +10298,9 @@ msgstr ""
msgid "Geo allows you to replicate your GitLab instance to other geographical locations."
msgstr ""
+msgid "Geo nodes are paused using a command run on the node"
+msgstr ""
+
msgid "GeoNodeStatusEvent|%{timeAgoStr} (%{pendingEvents} events)"
msgstr ""
@@ -10128,6 +10385,9 @@ msgstr ""
msgid "GeoNodes|Not checksummed"
msgstr ""
+msgid "GeoNodes|Package files"
+msgstr ""
+
msgid "GeoNodes|Pausing replication stops the sync process. Are you sure?"
msgstr ""
@@ -10146,6 +10406,9 @@ msgstr ""
msgid "GeoNodes|Replication slots"
msgstr ""
+msgid "GeoNodes|Replication status"
+msgstr ""
+
msgid "GeoNodes|Repositories"
msgstr ""
@@ -10356,6 +10619,9 @@ msgstr ""
msgid "Geo|Synchronization failed - %{error}"
msgstr ""
+msgid "Geo|Synchronization of %{itemTitle} is disabled."
+msgstr ""
+
msgid "Geo|The database is currently %{db_lag} behind the primary node."
msgstr ""
@@ -10668,6 +10934,9 @@ msgstr ""
msgid "Go to %{link_to_google_takeout}."
msgstr ""
+msgid "Go to <strong>Issues</strong> > <strong>Boards</strong> to access your personalized learning issue board."
+msgstr ""
+
msgid "Go to Pipelines"
msgstr ""
@@ -10767,9 +11036,6 @@ msgstr ""
msgid "Go to your snippets"
msgstr ""
-msgid "Golden Tanuki"
-msgstr ""
-
msgid "Google Cloud Platform"
msgstr ""
@@ -10791,6 +11057,9 @@ msgstr ""
msgid "Grafana URL"
msgstr ""
+msgid "Grafana response contains invalid json"
+msgstr ""
+
msgid "GrafanaIntegration|API Token"
msgstr ""
@@ -10839,9 +11108,6 @@ msgstr ""
msgid "Group %{group_name} was successfully created."
msgstr ""
-msgid "Group '%{group_name}' is being imported."
-msgstr ""
-
msgid "Group Audit Events"
msgstr ""
@@ -10878,9 +11144,6 @@ msgstr ""
msgid "Group by:"
msgstr ""
-msgid "Group could not be imported: %{errors}"
-msgstr ""
-
msgid "Group description"
msgstr ""
@@ -10908,9 +11171,15 @@ msgstr ""
msgid "Group has not been marked for deletion"
msgstr ""
+msgid "Group import could not be scheduled"
+msgstr ""
+
msgid "Group info:"
msgstr ""
+msgid "Group is required when cluster_type is :group"
+msgstr ""
+
msgid "Group maintainers can register group runners in the %{link}"
msgstr ""
@@ -10974,6 +11243,24 @@ msgstr ""
msgid "GroupActivyMetrics|Recent activity (last 90 days)"
msgstr ""
+msgid "GroupImport|Failed to import group."
+msgstr ""
+
+msgid "GroupImport|Group '%{group_name}' is being imported."
+msgstr ""
+
+msgid "GroupImport|Group could not be imported: %{errors}"
+msgstr ""
+
+msgid "GroupImport|Please wait while we import the group for you. Refresh at will."
+msgstr ""
+
+msgid "GroupImport|The group was successfully imported."
+msgstr ""
+
+msgid "GroupImport|Unable to process group import file"
+msgstr ""
+
msgid "GroupRoadmap|%{dateWord} – No end date"
msgstr ""
@@ -11241,6 +11528,9 @@ msgstr ""
msgid "Groups (%{groups})"
msgstr ""
+msgid "Groups and projects"
+msgstr ""
+
msgid "Groups can also be nested by creating %{subgroup_docs_link_start}subgroups%{subgroup_docs_link_end}."
msgstr ""
@@ -11286,6 +11576,33 @@ msgstr ""
msgid "GroupsEmptyState|You can manage your group member’s permissions and access to each project in the group."
msgstr ""
+msgid "GroupsNew|Contact an administrator to enable options for importing your group."
+msgstr ""
+
+msgid "GroupsNew|Create"
+msgstr ""
+
+msgid "GroupsNew|Create group"
+msgstr ""
+
+msgid "GroupsNew|GitLab group export"
+msgstr ""
+
+msgid "GroupsNew|Import"
+msgstr ""
+
+msgid "GroupsNew|Import group"
+msgstr ""
+
+msgid "GroupsNew|My Awesome Group"
+msgstr ""
+
+msgid "GroupsNew|No import options available"
+msgstr ""
+
+msgid "GroupsNew|To copy a GitLab group between installations, navigate to the group settings page for the original installation, generate an export file, and upload it here."
+msgstr ""
+
msgid "GroupsTree|Are you sure you want to leave the \"%{fullName}\" group?"
msgstr ""
@@ -11388,6 +11705,9 @@ msgstr ""
msgid "Helps reduce request volume for protected paths"
msgstr ""
+msgid "Here are all your projects in your group, including the one you just created. To start, let’s take a look at your personalized learning project which will help you learn about GitLab at your own pace."
+msgstr ""
+
msgid "Here you will find recent merge request activity"
msgstr ""
@@ -11402,6 +11722,9 @@ msgid_plural "Hide charts"
msgstr[0] ""
msgstr[1] ""
+msgid "Hide details"
+msgstr ""
+
msgid "Hide file browser"
msgstr ""
@@ -11411,6 +11734,9 @@ msgstr ""
msgid "Hide host keys manual input"
msgstr ""
+msgid "Hide list"
+msgstr ""
+
msgid "Hide marketing-related entries from help"
msgstr ""
@@ -11431,9 +11757,6 @@ msgstr[1] ""
msgid "Hide values"
msgstr ""
-msgid "Hiding all labels"
-msgstr ""
-
msgid "High or unknown vulnerabilities present"
msgstr ""
@@ -11518,6 +11841,9 @@ msgstr ""
msgid "ID:"
msgstr ""
+msgid "IDE"
+msgstr ""
+
msgid "IDE|Allow live previews of JavaScript projects in the Web IDE using CodeSandbox Live Preview."
msgstr ""
@@ -11608,10 +11934,10 @@ msgstr ""
msgid "If enabled"
msgstr ""
-msgid "If enabled, access to projects will be validated on an external service using their classification label."
+msgid "If enabled, GitLab will handle Object Storage replication using Geo. %{linkStart}More information%{linkEnd}"
msgstr ""
-msgid "If enabled, and if object storage is enabled, GitLab will handle Object Storage replication using Geo"
+msgid "If enabled, access to projects will be validated on an external service using their classification label."
msgstr ""
msgid "If there is no previous license or if the previous license has expired, some GitLab functionality will be blocked until a new, valid license is uploaded."
@@ -11973,6 +12299,9 @@ msgid_plural "Instances"
msgstr[0] ""
msgstr[1] ""
+msgid "Instance Configuration"
+msgstr ""
+
msgid "Instance Statistics visibility"
msgstr ""
@@ -11994,9 +12323,6 @@ msgstr ""
msgid "Integrations"
msgstr ""
-msgid "Integrations allow you to integrate GitLab with other applications"
-msgstr ""
-
msgid "Integrations|All details"
msgstr ""
@@ -12108,6 +12434,9 @@ msgstr ""
msgid "Invalid query"
msgstr ""
+msgid "Invalid repository bundle for snippet with id %{snippet_id}"
+msgstr ""
+
msgid "Invalid repository path"
msgstr ""
@@ -12126,6 +12455,9 @@ msgstr ""
msgid "Invalid two-factor code."
msgstr ""
+msgid "Invalid yaml"
+msgstr ""
+
msgid "Invitation"
msgstr ""
@@ -12345,15 +12677,24 @@ msgstr ""
msgid "It seems like the Dependency Scanning job ran successfully, but no dependencies have been detected in your project."
msgstr ""
+msgid "It seems that there is currently no available data for code coverage"
+msgstr ""
+
msgid "It's you"
msgstr ""
+msgid "Iteration"
+msgstr ""
+
msgid "Iteration changed to"
msgstr ""
msgid "Iteration removed"
msgstr ""
+msgid "Iteration updated"
+msgstr ""
+
msgid "Iterations"
msgstr ""
@@ -12384,12 +12725,18 @@ msgstr ""
msgid "January"
msgstr ""
+msgid "Jira Issues"
+msgstr ""
+
msgid "Jira import is already running."
msgstr ""
msgid "Jira integration not configured."
msgstr ""
+msgid "Jira project key is not configured"
+msgstr ""
+
msgid "Jira project: %{importProject}"
msgstr ""
@@ -12570,6 +12917,9 @@ msgstr ""
msgid "Keep divergent refs"
msgstr ""
+msgid "Kerberos access denied"
+msgstr ""
+
msgid "Key"
msgstr ""
@@ -12579,10 +12929,13 @@ msgstr ""
msgid "Key: %{key}"
msgstr ""
-msgid "Keyboard Shortcuts"
+msgid "Keyboard shortcuts"
msgstr ""
-msgid "Keyboard shortcuts"
+msgid "Keys"
+msgstr ""
+
+msgid "Ki"
msgstr ""
msgid "Kubernetes"
@@ -12624,6 +12977,9 @@ msgstr ""
msgid "LDAP"
msgstr ""
+msgid "LDAP Synchronization"
+msgstr ""
+
msgid "LDAP settings"
msgstr ""
@@ -12848,12 +13204,18 @@ msgstr ""
msgid "Learn more about custom project templates"
msgstr ""
+msgid "Learn more about deploying to AWS"
+msgstr ""
+
msgid "Learn more about deploying to a cluster"
msgstr ""
msgid "Learn more about group-level project templates"
msgstr ""
+msgid "Learn more about job dependencies"
+msgstr ""
+
msgid "Learn more about signing commits"
msgstr ""
@@ -12905,9 +13267,21 @@ msgstr ""
msgid "License History"
msgstr ""
+msgid "License ID:"
+msgstr ""
+
+msgid "License URL"
+msgstr ""
+
msgid "License-Check"
msgstr ""
+msgid "LicenseCompliance|%{docLinkStart}License Approvals%{docLinkEnd} are active"
+msgstr ""
+
+msgid "LicenseCompliance|%{docLinkStart}License Approvals%{docLinkEnd} are inactive"
+msgstr ""
+
msgid "LicenseCompliance|Add a license"
msgstr ""
@@ -12929,9 +13303,15 @@ msgstr ""
msgid "LicenseCompliance|Deny"
msgstr ""
+msgid "LicenseCompliance|Learn more about %{linkStart}License Approvals%{linkEnd}"
+msgstr ""
+
msgid "LicenseCompliance|License"
msgstr ""
+msgid "LicenseCompliance|License Approvals"
+msgstr ""
+
msgid "LicenseCompliance|License Compliance detected %d license and policy violation for the source branch only; approval required"
msgid_plural "LicenseCompliance|License Compliance detected %d licenses and policy violations for the source branch only; approval required"
msgstr[0] ""
@@ -13152,6 +13532,9 @@ msgstr ""
msgid "List your Bitbucket Server repositories"
msgstr ""
+msgid "Lists"
+msgstr ""
+
msgid "Live preview"
msgstr ""
@@ -13335,6 +13718,9 @@ msgstr ""
msgid "Manage project labels"
msgstr ""
+msgid "Manage storage usage"
+msgstr ""
+
msgid "Manage two-factor authentication"
msgstr ""
@@ -13350,6 +13736,9 @@ msgstr ""
msgid "Manifest file import"
msgstr ""
+msgid "Manifest import"
+msgstr ""
+
msgid "Manual job"
msgstr ""
@@ -13497,6 +13886,12 @@ msgstr ""
msgid "Maximum field length"
msgstr ""
+msgid "Maximum file size is 2MB. Please select a smaller file."
+msgstr ""
+
+msgid "Maximum import size (MB)"
+msgstr ""
+
msgid "Maximum job timeout"
msgstr ""
@@ -13536,6 +13931,9 @@ msgstr ""
msgid "Maximum size of Elasticsearch bulk indexing requests."
msgstr ""
+msgid "Maximum size of import files."
+msgstr ""
+
msgid "Maximum size of individual attachments in comments."
msgstr ""
@@ -13587,6 +13985,9 @@ msgstr ""
msgid "Merge (when the pipeline succeeds)"
msgstr ""
+msgid "Merge Conflicts"
+msgstr ""
+
msgid "Merge Request"
msgstr ""
@@ -13713,6 +14114,9 @@ msgstr ""
msgid "MergeRequests|Squash task canceled: another squash is already in progress."
msgstr ""
+msgid "MergeRequests|This project does not allow squashing commits when merge requests are accepted."
+msgstr ""
+
msgid "MergeRequests|Thread stays resolved"
msgstr ""
@@ -13866,6 +14270,12 @@ msgstr ""
msgid "MetricsSettings|Add a button to the metrics dashboard linking directly to your existing external dashboard."
msgstr ""
+msgid "MetricsSettings|Choose whether to display dashboard metrics in UTC or the user's local timezone."
+msgstr ""
+
+msgid "MetricsSettings|Dashboard timezone"
+msgstr ""
+
msgid "MetricsSettings|External dashboard URL"
msgstr ""
@@ -13875,6 +14285,12 @@ msgstr ""
msgid "MetricsSettings|Metrics Dashboard"
msgstr ""
+msgid "MetricsSettings|UTC (Coordinated Universal Time)"
+msgstr ""
+
+msgid "MetricsSettings|User's local timezone"
+msgstr ""
+
msgid "Metrics|Add metric"
msgstr ""
@@ -13890,6 +14306,9 @@ msgstr ""
msgid "Metrics|Create metric"
msgstr ""
+msgid "Metrics|Current"
+msgstr ""
+
msgid "Metrics|Delete metric"
msgstr ""
@@ -13940,6 +14359,9 @@ msgstr ""
msgid "Metrics|Max"
msgstr ""
+msgid "Metrics|Min"
+msgstr ""
+
msgid "Metrics|Must be a valid PromQL query."
msgstr ""
@@ -13955,6 +14377,9 @@ msgstr ""
msgid "Metrics|Refresh dashboard"
msgstr ""
+msgid "Metrics|Select a value"
+msgstr ""
+
msgid "Metrics|Star dashboard"
msgstr ""
@@ -13973,12 +14398,18 @@ msgstr ""
msgid "Metrics|There was an error getting annotations information."
msgstr ""
+msgid "Metrics|There was an error getting dashboard validation warnings information."
+msgstr ""
+
msgid "Metrics|There was an error getting deployment information."
msgstr ""
msgid "Metrics|There was an error getting environments information."
msgstr ""
+msgid "Metrics|There was an error getting options for variable \"%{name}\"."
+msgstr ""
+
msgid "Metrics|There was an error trying to validate your query"
msgstr ""
@@ -14021,6 +14452,9 @@ msgstr ""
msgid "Metrics|You're about to permanently delete this metric. This cannot be undone."
msgstr ""
+msgid "Metrics|Your dashboard schema is invalid. Edit the dashboard to correct the YAML schema."
+msgstr ""
+
msgid "Metrics|e.g. HTTP requests"
msgstr ""
@@ -14036,12 +14470,18 @@ msgstr ""
msgid "Metrics|e.g. req/sec"
msgstr ""
+msgid "Mi"
+msgstr ""
+
msgid "Microsoft Azure"
msgstr ""
msgid "Middleman project with Static Site Editor support"
msgstr ""
+msgid "Migrate your data from an external source like GitHub, Bitbucket, or another instance of GitLab."
+msgstr ""
+
msgid "Migrated %{success_count}/%{total_count} files."
msgstr ""
@@ -14176,6 +14616,9 @@ msgstr ""
msgid "Minimum capacity to be available before we schedule more mirrors preemptively."
msgstr ""
+msgid "Minimum interval in days"
+msgstr ""
+
msgid "Minimum length is %{minimum_password_length} characters"
msgstr ""
@@ -14362,6 +14805,12 @@ msgstr ""
msgid "Multiple uploaders found: %{uploader_types}"
msgstr ""
+msgid "Must match with the %{codeStart}external_url%{codeEnd} in %{codeStart}/etc/gitlab/gitlab.rb%{codeEnd}."
+msgstr ""
+
+msgid "Must match with the %{codeStart}geo_node_name%{codeEnd} in %{codeStart}/etc/gitlab/gitlab.rb%{codeEnd}. %{linkStart}More information%{linkEnd}"
+msgstr ""
+
msgid "My Awesome Group"
msgstr ""
@@ -14476,10 +14925,16 @@ msgstr ""
msgid "New Application"
msgstr ""
+msgid "New Branch"
+msgstr ""
+
+msgid "New Deploy Key"
+msgstr ""
+
msgid "New Environment"
msgstr ""
-msgid "New Geo Node"
+msgid "New File"
msgstr ""
msgid "New Group"
@@ -14496,15 +14951,15 @@ msgid_plural "New Issues"
msgstr[0] ""
msgstr[1] ""
-msgid "New Iteration"
-msgstr ""
-
msgid "New Jira import"
msgstr ""
msgid "New Label"
msgstr ""
+msgid "New Merge Request"
+msgstr ""
+
msgid "New Milestone"
msgstr ""
@@ -14523,6 +14978,9 @@ msgstr ""
msgid "New Snippet"
msgstr ""
+msgid "New User"
+msgstr ""
+
msgid "New branch"
msgstr ""
@@ -14565,6 +15023,9 @@ msgstr ""
msgid "New issue title"
msgstr ""
+msgid "New iteration"
+msgstr ""
+
msgid "New iteration created"
msgstr ""
@@ -14742,6 +15203,12 @@ msgstr ""
msgid "No grouping"
msgstr ""
+msgid "No iteration"
+msgstr ""
+
+msgid "No iterations to show"
+msgstr ""
+
msgid "No job log"
msgstr ""
@@ -14784,6 +15251,9 @@ msgstr ""
msgid "No other labels with such name or description"
msgstr ""
+msgid "No panels matching properties %{opts}"
+msgstr ""
+
msgid "No parent group"
msgstr ""
@@ -14817,6 +15287,9 @@ msgstr ""
msgid "No schedules"
msgstr ""
+msgid "No stack trace for this error"
+msgstr ""
+
msgid "No starrers matched your search"
msgstr ""
@@ -14829,7 +15302,7 @@ msgstr ""
msgid "No test coverage"
msgstr ""
-msgid "No thanks, don't show this again"
+msgid "No thanks"
msgstr ""
msgid "No vulnerabilities present"
@@ -14844,9 +15317,6 @@ msgstr ""
msgid "No, directly import the existing email addresses and usernames."
msgstr ""
-msgid "No, not interested right now"
-msgstr ""
-
msgid "No. of commits"
msgstr ""
@@ -14892,9 +15362,6 @@ msgstr ""
msgid "Not found."
msgstr ""
-msgid "Not helpful"
-msgstr ""
-
msgid "Not now"
msgstr ""
@@ -15051,6 +15518,9 @@ msgstr ""
msgid "Notifications on"
msgstr ""
+msgid "Notify users by email when sign-in location is not recognized"
+msgstr ""
+
msgid "Nov"
msgstr ""
@@ -15117,9 +15587,6 @@ msgstr ""
msgid "Oh no!"
msgstr ""
-msgid "Ok let's go"
-msgstr ""
-
msgid "Oldest first"
msgstr ""
@@ -15132,16 +15599,49 @@ msgstr ""
msgid "On track"
msgstr ""
+msgid "OnDemandScans|Attached branch"
+msgstr ""
+
+msgid "OnDemandScans|Attached branch is where the scan job runs."
+msgstr ""
+
+msgid "OnDemandScans|Could not run the scan. Please try again."
+msgstr ""
+
msgid "OnDemandScans|Create new DAST scan"
msgstr ""
+msgid "OnDemandScans|DAST will scan the target URL and any discovered sub URLs."
+msgstr ""
+
+msgid "OnDemandScans|New on-demand DAST scan"
+msgstr ""
+
msgid "OnDemandScans|On-demand Scans"
msgstr ""
+msgid "OnDemandScans|On-demand scans run outside the DevOps cycle and find vulnerabilities in your projects. %{learnMoreLinkStart}Learn more%{learnMoreLinkEnd}"
+msgstr ""
+
+msgid "OnDemandScans|Only a passive scan can be performed on demand."
+msgstr ""
+
+msgid "OnDemandScans|Passive DAST Scan"
+msgstr ""
+
+msgid "OnDemandScans|Please enter a valid URL format, ex: http://www.example.com/home"
+msgstr ""
+
+msgid "OnDemandScans|Run this scan"
+msgstr ""
+
+msgid "OnDemandScans|Scan mode"
+msgstr ""
+
msgid "OnDemandScans|Schedule or run scans immediately against target sites. Currently available on-demand scan type: DAST. %{helpLinkStart}More information%{helpLinkEnd}"
msgstr ""
-msgid "Onboarding"
+msgid "OnDemandScans|Target URL"
msgstr ""
msgid "Once imported, repositories can be mirrored over SSH. Read more %{link_start}here%{link_end}."
@@ -15167,6 +15667,9 @@ msgstr ""
msgid "One or more of you personal access tokens were revoked"
msgstr ""
+msgid "One or more of your %{provider} projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
+msgstr ""
+
msgid "One or more of your Bitbucket projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
msgstr ""
@@ -15668,6 +16171,9 @@ msgstr ""
msgid "Parent epic is not present."
msgstr ""
+msgid "Parsing error for param :embed_json. %{message}"
+msgstr ""
+
msgid "Part of merge request changes"
msgstr ""
@@ -15680,6 +16186,9 @@ msgstr ""
msgid "Passed"
msgstr ""
+msgid "Passed on"
+msgstr ""
+
msgid "Password"
msgstr ""
@@ -15764,6 +16273,9 @@ msgstr ""
msgid "Perform common operations on GitLab project"
msgstr ""
+msgid "Performance and resource management"
+msgstr ""
+
msgid "Performance optimization"
msgstr ""
@@ -15971,7 +16483,7 @@ msgstr ""
msgid "Pipelines|Something went wrong while cleaning runners cache."
msgstr ""
-msgid "Pipelines|There are currently no %{scope} pipelines."
+msgid "Pipelines|There are currently no finished pipelines."
msgstr ""
msgid "Pipelines|There are currently no pipelines."
@@ -16082,6 +16594,9 @@ msgstr ""
msgid "Pipeline|Stop pipeline #%{pipelineId}?"
msgstr ""
+msgid "Pipeline|Tag name"
+msgstr ""
+
msgid "Pipeline|Trigger author"
msgstr ""
@@ -16163,12 +16678,18 @@ msgstr ""
msgid "Please check your email (%{email}) to verify that you own this address and unlock the power of CI/CD. Didn't receive it? %{resend_link}. Wrong email address? %{update_link}."
msgstr ""
+msgid "Please choose a file"
+msgstr ""
+
msgid "Please choose a group URL with no special characters."
msgstr ""
msgid "Please complete your profile with email address"
msgstr ""
+msgid "Please convert %{linkStart}them to Git%{linkEnd}, and go through the %{linkToImportFlow} again."
+msgstr ""
+
msgid "Please convert them to %{link_to_git}, and go through the %{link_to_import_flow} again."
msgstr ""
@@ -16226,6 +16747,9 @@ msgstr ""
msgid "Please provide a valid email address."
msgstr ""
+msgid "Please provide attributes to update"
+msgstr ""
+
msgid "Please refer to <a href=\"%{docs_url}\">%{docs_url}</a>"
msgstr ""
@@ -16961,6 +17485,9 @@ msgstr ""
msgid "Project has too many %{label_for_message} to search"
msgstr ""
+msgid "Project is required when cluster_type is :project"
+msgstr ""
+
msgid "Project members"
msgstr ""
@@ -17078,7 +17605,40 @@ msgstr ""
msgid "ProjectService|Comment"
msgstr ""
-msgid "ProjectService|Comment will be posted on each event"
+msgid "ProjectService|Event will be triggered by a push to the repository"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a commit is created/updated"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a confidential issue is created/updated/closed"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a deployment finishes"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a merge request is created/updated/merged"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a new tag is pushed to the repository"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a new, unique alert is recorded"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a pipeline status changes"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a wiki page is created/updated"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when an issue is created/updated/closed"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when someone adds a comment"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when someone adds a comment on a confidential issue"
msgstr ""
msgid "ProjectService|Perform common operations on GitLab project: %{project_name}"
@@ -17129,6 +17689,9 @@ msgstr ""
msgid "ProjectSettings|Enable 'Delete source branch' option by default"
msgstr ""
+msgid "ProjectSettings|Enable merge trains and pipelines for merged results"
+msgstr ""
+
msgid "ProjectSettings|Every merge creates a merge commit"
msgstr ""
@@ -17159,7 +17722,7 @@ msgstr ""
msgid "ProjectSettings|Forks"
msgstr ""
-msgid "ProjectSettings|Git Large File Storage"
+msgid "ProjectSettings|Git Large File Storage (LFS)"
msgstr ""
msgid "ProjectSettings|Internal"
@@ -17195,9 +17758,6 @@ msgstr ""
msgid "ProjectSettings|Merge options"
msgstr ""
-msgid "ProjectSettings|Merge pipelines will try to validate the post-merge result prior to merging"
-msgstr ""
-
msgid "ProjectSettings|Merge requests"
msgstr ""
@@ -17225,6 +17785,9 @@ msgstr ""
msgid "ProjectSettings|Pipelines"
msgstr ""
+msgid "ProjectSettings|Pipelines for merge requests must be enabled in the CI/CD configuration file, or pipelines could be unresolvable or dropped"
+msgstr ""
+
msgid "ProjectSettings|Pipelines must succeed"
msgstr ""
@@ -17252,6 +17815,9 @@ msgstr ""
msgid "ProjectSettings|Show link to create/view merge request when pushing from the command line"
msgstr ""
+msgid "ProjectSettings|Skipped pipelines are considered successful"
+msgstr ""
+
msgid "ProjectSettings|Snippets"
msgstr ""
@@ -17267,6 +17833,9 @@ msgstr ""
msgid "ProjectSettings|These checks must pass before merge requests can be merged"
msgstr ""
+msgid "ProjectSettings|This introduces the risk of merging changes that will not pass the pipeline."
+msgstr ""
+
msgid "ProjectSettings|This setting is applied on the server level and can be overridden by an admin."
msgstr ""
@@ -17444,15 +18013,27 @@ msgstr ""
msgid "ProjectsNew|Blank project"
msgstr ""
+msgid "ProjectsNew|Connect your external repository to GitLab CI/CD."
+msgstr ""
+
msgid "ProjectsNew|Contact an administrator to enable options for importing your project."
msgstr ""
msgid "ProjectsNew|Create"
msgstr ""
+msgid "ProjectsNew|Create a blank project to house your files, plan your work, and collaborate on code, among other things."
+msgstr ""
+
+msgid "ProjectsNew|Create blank project"
+msgstr ""
+
msgid "ProjectsNew|Create from template"
msgstr ""
+msgid "ProjectsNew|Create new project"
+msgstr ""
+
msgid "ProjectsNew|Creating project & repository."
msgstr ""
@@ -17477,6 +18058,9 @@ msgstr ""
msgid "ProjectsNew|Project description %{tag_start}(optional)%{tag_end}"
msgstr ""
+msgid "ProjectsNew|Run CI/CD for external repository"
+msgstr ""
+
msgid "ProjectsNew|Template"
msgstr ""
@@ -17738,6 +18322,9 @@ msgstr ""
msgid "Protected Branch"
msgstr ""
+msgid "Protected Branches"
+msgstr ""
+
msgid "Protected Environment"
msgstr ""
@@ -17750,6 +18337,9 @@ msgstr ""
msgid "Protected Tag"
msgstr ""
+msgid "Protected Tags"
+msgstr ""
+
msgid "Protected branches"
msgstr ""
@@ -17849,6 +18439,9 @@ msgstr ""
msgid "Provider"
msgstr ""
+msgid "Proxy support for this API is not available currently"
+msgstr ""
+
msgid "Pseudonymizer data collection"
msgstr ""
@@ -17873,6 +18466,9 @@ msgstr ""
msgid "Publish to status page"
msgstr ""
+msgid "Published on status page"
+msgstr ""
+
msgid "Publishes this issue to the associated status page."
msgstr ""
@@ -18047,6 +18643,9 @@ msgstr ""
msgid "Recipe"
msgstr ""
+msgid "Reconfigure"
+msgstr ""
+
msgid "Recover hidden stage"
msgstr ""
@@ -18133,6 +18732,9 @@ msgstr ""
msgid "Registration|Your profile"
msgstr ""
+msgid "Regulate approvals by authors/committers, based on compliance frameworks. Can be changed only at the instance level."
+msgstr ""
+
msgid "Rejected (closed)"
msgstr ""
@@ -18183,15 +18785,24 @@ msgstr ""
msgid "ReleaseAssetLinkType|Image"
msgstr ""
+msgid "ReleaseAssetLinkType|Images"
+msgstr ""
+
msgid "ReleaseAssetLinkType|Other"
msgstr ""
msgid "ReleaseAssetLinkType|Package"
msgstr ""
+msgid "ReleaseAssetLinkType|Packages"
+msgstr ""
+
msgid "ReleaseAssetLinkType|Runbook"
msgstr ""
+msgid "ReleaseAssetLinkType|Runbooks"
+msgstr ""
+
msgid "Releases"
msgstr ""
@@ -18204,6 +18815,9 @@ msgstr ""
msgid "Releases documentation"
msgstr ""
+msgid "Releases|New Release"
+msgstr ""
+
msgid "Release|Something went wrong while getting the release details"
msgstr ""
@@ -18276,6 +18890,9 @@ msgstr ""
msgid "Remove fork relationship"
msgstr ""
+msgid "Remove from batch"
+msgstr ""
+
msgid "Remove from board"
msgstr ""
@@ -18465,6 +19082,12 @@ msgstr ""
msgid "Replication"
msgstr ""
+msgid "Replication enabled"
+msgstr ""
+
+msgid "Replication paused"
+msgstr ""
+
msgid "Reply by email"
msgstr ""
@@ -18492,15 +19115,15 @@ msgstr ""
msgid "Reported %{timeAgo} by %{reportedBy}"
msgstr ""
-msgid "Reported Resource Changes: %{addNum} to add, %{changeNum} to change, %{deleteNum} to delete"
-msgstr ""
-
msgid "Reporter"
msgstr ""
msgid "Reporting"
msgstr ""
+msgid "Reports"
+msgstr ""
+
msgid "Reports|%{combinedString} and %{resolvedString}"
msgstr ""
@@ -18539,6 +19162,9 @@ msgstr ""
msgid "Reports|Failure"
msgstr ""
+msgid "Reports|Identifier"
+msgstr ""
+
msgid "Reports|Metrics reports are loading"
msgstr ""
@@ -18551,6 +19177,9 @@ msgstr ""
msgid "Reports|Metrics reports failed loading results"
msgstr ""
+msgid "Reports|Scanner"
+msgstr ""
+
msgid "Reports|Severity"
msgstr ""
@@ -18599,15 +19228,33 @@ msgstr ""
msgid "Repository cleanup has started. You will receive an email once the cleanup operation is complete."
msgstr ""
+msgid "Repository files count over the limit"
+msgstr ""
+
+msgid "Repository has an invalid default branch name."
+msgstr ""
+
+msgid "Repository has more than one branch."
+msgstr ""
+
msgid "Repository has no locks."
msgstr ""
+msgid "Repository has tags."
+msgstr ""
+
msgid "Repository maintenance"
msgstr ""
msgid "Repository mirroring"
msgstr ""
+msgid "Repository must contain at least 1 file."
+msgstr ""
+
+msgid "Repository size is above the limit."
+msgstr ""
+
msgid "Repository static objects"
msgstr ""
@@ -18617,7 +19264,7 @@ msgstr ""
msgid "Repository sync capacity"
msgstr ""
-msgid "Repository: %{counter_repositories} / Wikis: %{counter_wikis} / Build Artifacts: %{counter_build_artifacts} / LFS: %{counter_lfs_objects}"
+msgid "Repository: %{counter_repositories} / Wikis: %{counter_wikis} / Build Artifacts: %{counter_build_artifacts} / LFS: %{counter_lfs_objects} / Snippets: %{counter_snippets}"
msgstr ""
msgid "RepositorySettingsAccessLevel|Select"
@@ -18629,6 +19276,9 @@ msgstr ""
msgid "Request Headers"
msgstr ""
+msgid "Request details"
+msgstr ""
+
msgid "Request parameter %{param} is missing."
msgstr ""
@@ -18735,6 +19385,9 @@ msgstr ""
msgid "Reset template"
msgstr ""
+msgid "Reset to project defaults"
+msgstr ""
+
msgid "Resetting the authorization key for this project will require updating the authorization key in every alert source it is enabled in."
msgstr ""
@@ -18831,9 +19484,6 @@ msgstr ""
msgid "Resume"
msgstr ""
-msgid "Resume replication"
-msgstr ""
-
msgid "Resync"
msgstr ""
@@ -19456,6 +20106,9 @@ msgstr ""
msgid "SecurityReports|False positive"
msgstr ""
+msgid "SecurityReports|Group Security Dashboard"
+msgstr ""
+
msgid "SecurityReports|Hide dismissed"
msgstr ""
@@ -19510,10 +20163,13 @@ msgstr ""
msgid "SecurityReports|Remove project from dashboard"
msgstr ""
-msgid "SecurityReports|Report type"
+msgid "SecurityReports|Return to dashboard"
msgstr ""
-msgid "SecurityReports|Return to dashboard"
+msgid "SecurityReports|Scan details"
+msgstr ""
+
+msgid "SecurityReports|Scanner type"
msgstr ""
msgid "SecurityReports|Security Dashboard"
@@ -19570,6 +20226,9 @@ msgstr ""
msgid "SecurityReports|There was an error while generating the report."
msgstr ""
+msgid "SecurityReports|Unable to add %{invalidProjectsMessage}"
+msgstr ""
+
msgid "SecurityReports|Unable to add %{invalidProjects}"
msgstr ""
@@ -19684,6 +20343,9 @@ msgstr ""
msgid "Select due date"
msgstr ""
+msgid "Select file"
+msgstr ""
+
msgid "Select group or project"
msgstr ""
@@ -19747,9 +20409,6 @@ msgstr ""
msgid "Select the branch you want to set as the default for this project. All merge requests and commits will automatically be made against this branch unless you specify a different one."
msgstr ""
-msgid "Select the configured storage available for new repositories to be placed on."
-msgstr ""
-
msgid "Select the custom project template source group."
msgstr ""
@@ -19942,12 +20601,6 @@ msgstr ""
msgid "Service Desk is enabled but not yet active"
msgstr ""
-msgid "Service Desk is off"
-msgstr ""
-
-msgid "Service Desk is on"
-msgstr ""
-
msgid "Service Templates"
msgstr ""
@@ -20035,6 +20688,15 @@ msgstr ""
msgid "Set the milestone to %{milestone_reference}."
msgstr ""
+msgid "Set the number of concurrent requests this secondary node will make to the primary node while backfilling."
+msgstr ""
+
+msgid "Set the synchronization and verification capacity for the secondary node."
+msgstr ""
+
+msgid "Set the timeout in seconds to send a secondary node status to the primary and IPs allowed for the secondary nodes."
+msgstr ""
+
msgid "Set time estimate"
msgstr ""
@@ -20074,6 +20736,9 @@ msgstr ""
msgid "Set weight to %{weight}."
msgstr ""
+msgid "Set what should be replicated by choosing specific projects or groups by the secondary node."
+msgstr ""
+
msgid "SetPasswordToCloneLink|set a password"
msgstr ""
@@ -20200,6 +20865,9 @@ msgstr ""
msgid "Show complete raw log"
msgstr ""
+msgid "Show details"
+msgstr ""
+
msgid "Show file browser"
msgstr ""
@@ -20209,10 +20877,13 @@ msgstr ""
msgid "Show latest version"
msgstr ""
+msgid "Show list"
+msgstr ""
+
msgid "Show me everything"
msgstr ""
-msgid "Show me how"
+msgid "Show me how to add a pipeline"
msgstr ""
msgid "Show me more advanced stuff"
@@ -20261,9 +20932,6 @@ msgstr ""
msgid "Showing all issues"
msgstr ""
-msgid "Showing all labels"
-msgstr ""
-
msgid "Showing last %{size} of log -"
msgstr ""
@@ -20375,9 +21043,6 @@ msgstr ""
msgid "Skip outdated deployment jobs"
msgstr ""
-msgid "Skip this for now"
-msgstr ""
-
msgid "Skipped"
msgstr ""
@@ -20522,6 +21187,9 @@ msgstr ""
msgid "Something went wrong while adding your award. Please try again."
msgstr ""
+msgid "Something went wrong while applying the batch of suggestions. Please try again."
+msgstr ""
+
msgid "Something went wrong while applying the suggestion. Please try again."
msgstr ""
@@ -20624,10 +21292,13 @@ msgstr ""
msgid "Something went wrong while updating a requirement."
msgstr ""
+msgid "Something went wrong while updating assignees"
+msgstr ""
+
msgid "Something went wrong while updating your list settings"
msgstr ""
-msgid "Something went wrong with your automatic subscription renewal"
+msgid "Something went wrong with your automatic subscription renewal."
msgstr ""
msgid "Something went wrong, unable to add %{project} to dashboard"
@@ -20645,6 +21316,9 @@ msgstr ""
msgid "Something went wrong, unable to search projects"
msgstr ""
+msgid "Something went wrong."
+msgstr ""
+
msgid "Something went wrong. Please try again."
msgstr ""
@@ -20828,6 +21502,9 @@ msgstr ""
msgid "Source code"
msgstr ""
+msgid "Source code (%{fileExtension})"
+msgstr ""
+
msgid "Source is not available"
msgstr ""
@@ -20912,6 +21589,9 @@ msgstr ""
msgid "Stack trace"
msgstr ""
+msgid "Stacktrace snippet"
+msgstr ""
+
msgid "Stage"
msgstr ""
@@ -21017,6 +21697,9 @@ msgstr ""
msgid "Start thread & reopen %{noteable_name}"
msgstr ""
+msgid "Start using Directed Acyclic Graphs (DAG)"
+msgstr ""
+
msgid "Start your Free Gold Trial"
msgstr ""
@@ -21479,6 +22162,21 @@ msgstr ""
msgid "SuggestedColors|Very pale orange"
msgstr ""
+msgid "Suggestion is not applicable as the suggestion was not found."
+msgstr ""
+
+msgid "Suggestions are not applicable as one or more suggestions were not found."
+msgstr ""
+
+msgid "Suggestions are not applicable as their lines cannot overlap."
+msgstr ""
+
+msgid "Suggestions must all be on the same branch."
+msgstr ""
+
+msgid "Suggestions that change line count can't be added to batches, yet."
+msgstr ""
+
msgid "Suggestions:"
msgstr ""
@@ -21521,9 +22219,6 @@ msgstr ""
msgid "Synchronization disabled"
msgstr ""
-msgid "Synchronization of container repositories is disabled."
-msgstr ""
-
msgid "System"
msgstr ""
@@ -21704,6 +22399,34 @@ msgstr ""
msgid "Terms of Service and Privacy Policy"
msgstr ""
+msgid "Terraform|%{number} Terraform report failed to generate"
+msgid_plural "Terraform|%{number} Terraform reports failed to generate"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Terraform|%{number} Terraform report was generated in your pipelines"
+msgid_plural "Terraform|%{number} Terraform reports were generated in your pipelines"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Terraform|A Terraform report failed to generate."
+msgstr ""
+
+msgid "Terraform|A Terraform report was generated in your pipelines."
+msgstr ""
+
+msgid "Terraform|Generating the report caused an error."
+msgstr ""
+
+msgid "Terraform|Reported Resource Changes: %{addNum} to add, %{changeNum} to change, %{deleteNum} to delete"
+msgstr ""
+
+msgid "Terraform|The Terraform report %{name} failed to generate."
+msgstr ""
+
+msgid "Terraform|The Terraform report %{name} was generated in your pipelines."
+msgstr ""
+
msgid "Test"
msgstr ""
@@ -21730,7 +22453,7 @@ msgstr ""
msgid "TestHooks|Ensure the project has CI pipelines."
msgstr ""
-msgid "TestHooks|Ensure the project has at least one commit."
+msgid "TestHooks|Ensure the project has deployments."
msgstr ""
msgid "TestHooks|Ensure the project has issues."
@@ -21831,7 +22554,7 @@ msgstr ""
msgid "The Prometheus server responded with \"bad request\". Please check your queries are correct and are supported in your Prometheus version. %{documentationLink}"
msgstr ""
-msgid "The URL defined on the primary node that secondary nodes should use to contact it. Defaults to URL"
+msgid "The URL defined on the primary node that secondary nodes should use to contact it."
msgstr ""
msgid "The URL to use for connecting to Elasticsearch. Use a comma-separated list to support clustering (e.g., \"http://localhost:9200, http://localhost:9201\")."
@@ -21840,6 +22563,9 @@ msgstr ""
msgid "The X509 Certificate to use when mutual TLS is required to communicate with the external authorization service. If left blank, the server certificate is still validated when accessing over HTTPS."
msgstr ""
+msgid "The above settings apply to all projects with the selected compliance framework(s)."
+msgstr ""
+
msgid "The amount of seconds after which a request to get a secondary node status will time out."
msgstr ""
@@ -22139,9 +22865,6 @@ msgstr ""
msgid "The total stage shows the time it takes between creating an issue and deploying the code to production. The data will be automatically added once you have completed the full idea to production cycle."
msgstr ""
-msgid "The unique identifier for the Geo node. Must match %{geoNodeName} if it is set in gitlab.rb, otherwise it must match %{externalUrl} with a trailing slash"
-msgstr ""
-
msgid "The update action will time out after %{number_of_minutes} minutes. For big repositories, use a clone/push combination."
msgstr ""
@@ -22166,9 +22889,6 @@ msgstr ""
msgid "The user you are trying to deactivate has been active in the past %{minimum_inactive_days} days and cannot be deactivated"
msgstr ""
-msgid "The user-facing URL of the Geo node"
-msgstr ""
-
msgid "The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6."
msgstr ""
@@ -22259,6 +22979,9 @@ msgstr ""
msgid "There was a problem fetching project branches."
msgstr ""
+msgid "There was a problem fetching project tags."
+msgstr ""
+
msgid "There was a problem fetching project users."
msgstr ""
@@ -22310,6 +23033,9 @@ msgstr ""
msgid "There was an error fetching the %{replicableType}"
msgstr ""
+msgid "There was an error fetching the Geo Settings"
+msgstr ""
+
msgid "There was an error fetching the Node's Groups"
msgstr ""
@@ -22430,6 +23156,12 @@ msgstr ""
msgid "This %{issuable} is locked. Only <strong>project members</strong> can comment."
msgstr ""
+msgid "This %{noteableTypeText} is %{confidentialLinkStart}confidential%{linkEnd} and %{lockedLinkStart}locked%{linkEnd}."
+msgstr ""
+
+msgid "This %{noteableTypeText} is locked."
+msgstr ""
+
msgid "This %{viewer} could not be displayed because %{reason}. You can %{options} instead."
msgstr ""
@@ -22448,6 +23180,9 @@ msgstr ""
msgid "This action can lead to data loss. To prevent accidental actions we ask you to confirm your intention."
msgstr ""
+msgid "This also resolves all related threads"
+msgstr ""
+
msgid "This also resolves the discussion"
msgstr ""
@@ -22538,6 +23273,9 @@ msgstr ""
msgid "This epic does not exist or you don't have sufficient permission."
msgstr ""
+msgid "This feature is currently in beta. We invite you to %{linkStart}give feedback%{linkEnd}."
+msgstr ""
+
msgid "This feature requires local storage to be enabled"
msgstr ""
@@ -22571,7 +23309,7 @@ msgstr ""
msgid "This is a Work in Progress"
msgstr ""
-msgid "This is a confidential issue."
+msgid "This is a confidential %{noteableTypeText}."
msgstr ""
msgid "This is a delayed job to run in %{remainingTime}"
@@ -22598,9 +23336,6 @@ msgstr ""
msgid "This is your current session"
msgstr ""
-msgid "This issue is %{confidentialLinkStart}confidential%{linkEnd} and %{lockedLinkStart}locked%{linkEnd}."
-msgstr ""
-
msgid "This issue is confidential"
msgstr ""
@@ -22610,9 +23345,6 @@ msgstr ""
msgid "This issue is in a child epic of the filtered epic"
msgstr ""
-msgid "This issue is locked."
-msgstr ""
-
msgid "This job depends on other jobs with expired/erased artifacts: %{invalid_dependencies}"
msgstr ""
@@ -22712,6 +23444,9 @@ msgstr ""
msgid "This license has already expired."
msgstr ""
+msgid "This link points to external content"
+msgstr ""
+
msgid "This may expose confidential information as the selected fork is in another namespace that can have other members."
msgstr ""
@@ -22739,6 +23474,9 @@ msgstr ""
msgid "This page will be removed in a future release."
msgstr ""
+msgid "This pipeline does not use the %{codeStart}needs%{codeEnd} keyword and can't be represented as a directed acyclic graph."
+msgstr ""
+
msgid "This pipeline makes use of a predefined CI/CD configuration enabled by %{strongStart}Auto DevOps.%{strongEnd}"
msgstr ""
@@ -22967,6 +23705,9 @@ msgstr ""
msgid "Time from last commit to merge"
msgstr ""
+msgid "Time in seconds"
+msgstr ""
+
msgid "Time in seconds GitLab will wait for a response from the external service. When the service does not respond in time, access will be denied."
msgstr ""
@@ -23388,9 +24129,15 @@ msgstr ""
msgid "Total artifacts size: %{total_size}"
msgstr ""
+msgid "Total cores (CPUs)"
+msgstr ""
+
msgid "Total issues"
msgstr ""
+msgid "Total memory (GB)"
+msgstr ""
+
msgid "Total test time for all commits/merges"
msgstr ""
@@ -23592,6 +24339,9 @@ msgstr ""
msgid "U2F only works with HTTPS-enabled websites. Contact your administrator for more details."
msgstr ""
+msgid "UI Development Kit"
+msgstr ""
+
msgid "URL"
msgstr ""
@@ -23664,9 +24414,6 @@ msgstr ""
msgid "Unable to load the merge request widget. Try reloading the page."
msgstr ""
-msgid "Unable to process group import file"
-msgstr ""
-
msgid "Unable to resolve"
msgstr ""
@@ -23682,6 +24429,9 @@ msgstr ""
msgid "Unable to sign you in to the group with SAML due to \"%{reason}\""
msgstr ""
+msgid "Unable to suggest a path. Please refresh and try again."
+msgstr ""
+
msgid "Unable to update label prioritization at this time"
msgstr ""
@@ -23697,6 +24447,9 @@ msgstr ""
msgid "Unarchiving the project will restore people's ability to make changes to it. The repository can be committed to, and issues, comments, and other entities can be created. %{strong_start}Once active, this project shows up in the search and on the dashboard.%{strong_end}"
msgstr ""
+msgid "Unassign from commenting user"
+msgstr ""
+
msgid "Unblock"
msgstr ""
@@ -23772,6 +24525,9 @@ msgstr ""
msgid "Unreachable"
msgstr ""
+msgid "Unrecognized cluster type"
+msgstr ""
+
msgid "Unresolve"
msgstr ""
@@ -23832,6 +24588,9 @@ msgstr ""
msgid "Unsubscribes from this %{quick_action_target}."
msgstr ""
+msgid "Unsupported todo type passed. Supported todo types are: %{todo_types}"
+msgstr ""
+
msgid "Until"
msgstr ""
@@ -23859,6 +24618,9 @@ msgstr ""
msgid "Update approval rule"
msgstr ""
+msgid "Update approvers"
+msgstr ""
+
msgid "Update failed"
msgstr ""
@@ -23868,6 +24630,9 @@ msgstr ""
msgid "Update it"
msgstr ""
+msgid "Update iteration"
+msgstr ""
+
msgid "Update now"
msgstr ""
@@ -23913,7 +24678,7 @@ msgstr ""
msgid "Updated at"
msgstr ""
-msgid "Updated to"
+msgid "Updated to %{linkStart}chart v%{linkEnd}"
msgstr ""
msgid "Updating"
@@ -23943,6 +24708,9 @@ msgstr ""
msgid "Upload CSV file"
msgstr ""
+msgid "Upload License"
+msgstr ""
+
msgid "Upload New File"
msgstr ""
@@ -24024,16 +24792,19 @@ msgstr ""
msgid "UsageQuota|Pipelines"
msgstr ""
+msgid "UsageQuota|Purchase more storage"
+msgstr ""
+
msgid "UsageQuota|Repositories"
msgstr ""
msgid "UsageQuota|Repository"
msgstr ""
-msgid "UsageQuota|Storage"
+msgid "UsageQuota|Snippets"
msgstr ""
-msgid "UsageQuota|Storage usage:"
+msgid "UsageQuota|Storage"
msgstr ""
msgid "UsageQuota|This namespace has no projects which use shared runners"
@@ -24066,6 +24837,12 @@ msgstr ""
msgid "UsageQuota|Wikis"
msgstr ""
+msgid "UsageQuota|You used: %{usage} %{limit}"
+msgstr ""
+
+msgid "UsageQuota|out of %{formattedLimit} of your namespace storage"
+msgstr ""
+
msgid "Use %{code_start}::%{code_end} to create a %{link_start}scoped label set%{link_end} (eg. %{code_start}priority::1%{code_end})"
msgstr ""
@@ -24132,6 +24909,9 @@ msgstr ""
msgid "User IDs"
msgstr ""
+msgid "User Lists can only be created and modified with %{linkStart}the API%{linkEnd}"
+msgstr ""
+
msgid "User OAuth applications"
msgstr ""
@@ -24156,6 +24936,9 @@ msgstr ""
msgid "User key was successfully removed."
msgstr ""
+msgid "User list %{name} will be removed. Are you sure?"
+msgstr ""
+
msgid "User map"
msgstr ""
@@ -24177,148 +24960,10 @@ msgstr ""
msgid "User was successfully updated."
msgstr ""
-msgid "UserOnboardingTour|%{activeTour}/%{totalTours}"
-msgstr ""
-
-msgid "UserOnboardingTour|%{completed}/%{total} steps completed"
-msgstr ""
-
-msgid "UserOnboardingTour|%{emphasisStart}Well done!%{emphasisEnd}%{lineBreak}%{lineBreak}That's it for our guided tour, congratulations for making it all the way to the end!%{lineBreak}%{lineBreak}We hope this gave you a good overview of GitLab and how it can help you. We'll now show you how to create your own project and invite your colleagues."
-msgstr ""
-
-msgid "UserOnboardingTour|Adding other members to a project is done through Project Settings. Click on %{emphasisStart}Settings%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Alright, that's it for Commits. Let's take a look at the %{emphasisStart}Branches%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Awesome! Now click on %{emphasisStart}Members%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Click on one of the %{emphasisStart}Compare%{emphasisEnd} buttons to compare a branch to master."
-msgstr ""
-
-msgid "UserOnboardingTour|Click on one of the %{emphasisStart}pipeline IDs%{emphasisEnd} to see the details of a pipeline."
-msgstr ""
-
-msgid "UserOnboardingTour|Click to open the latest commit to see its details."
-msgstr ""
-
-msgid "UserOnboardingTour|Close 'Learn GitLab'"
-msgstr ""
-
-msgid "UserOnboardingTour|Commits are shown in chronological order and can be filtered by the commit message or by the branch."
+msgid "UserList|Delete %{name}?"
msgstr ""
-msgid "UserOnboardingTour|Create a project"
-msgstr ""
-
-msgid "UserOnboardingTour|Exit 'Learn GitLab'"
-msgstr ""
-
-msgid "UserOnboardingTour|Got it"
-msgstr ""
-
-msgid "UserOnboardingTour|Great job! %{clapHands} We hope the tour was helpful and that you learned how to use GitLab.%{lineBreak}%{lineBreak}We'd love to get your feedback on this tour.%{lineBreak}%{lineBreak}%{emphasisStart}How helpful would you say this guided tour was?%{emphasisEnd}%{lineBreak}%{lineBreak}"
-msgstr ""
-
-msgid "UserOnboardingTour|Guided GitLab Tour"
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can compare the changes of this branch to another one. Changes are divided by files so that it's easier to see what was changed where."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can create a project from scratch, start with a template or import a repository from other platforms. Whatever you choose, we'll guide you through the process.%{lineBreak}%{lineBreak}Fill in your new project information and click on %{emphasisStart}Create Project%{emphasisEnd} to progress to the next step."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can see the breakdown of the pipelines: its stages and jobs in each of the stages and their status.%{lineBreak}%{lineBreak}Our CI/CD pipelines are quite complex, most of our users have fewer and simpler pipelines."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can see the current members of the project (just you at the moment) and invite new members.%{lineBreak}%{lineBreak}You can invite multiple members at once (existing GitLab users or invite by email) and you can also set their roles and permissions.%{lineBreak}%{lineBreak}Add a few members and click on %{emphasisStart}Add to project%{emphasisEnd} to complete this step."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can see what changes were made with this commit, on what branch and if there's a related merge request. The status of the pipeline will also show up if CI/CD is set up.%{lineBreak}%{lineBreak}You can also comment on the lines of code that were changed and start a discussion with your colleagues!"
-msgstr ""
-
-msgid "UserOnboardingTour|Here's an overview of branches in the %{emphasisStart}%{projectName}%{emphasisEnd} project. They're split into Active and Stale.%{lineBreak}%{lineBreak}From here, you can create a new merge request from a branch, or compare the branch to any other branch in the project. By default, it will compare it to the master branch."
-msgstr ""
-
-msgid "UserOnboardingTour|Invite colleagues"
-msgstr ""
-
-msgid "UserOnboardingTour|Issues are great for communicating and keeping track of progress in GitLab. These are all issues that are open in the %{emphasisStart}%{projectName}%{emphasisEnd}.%{lineBreak}%{lineBreak}You can help us improve GitLab by contributing work to issues that are labeled <span class=\"badge color-label accept-mr-label\">Accepting merge requests</span>.%{lineBreak}%{lineBreak}This list can be filtered by labels, milestones, assignees, authors... We'll show you how it looks when the list is filtered by a label."
-msgstr ""
-
-msgid "UserOnboardingTour|Learn GitLab"
-msgstr ""
-
-msgid "UserOnboardingTour|Let's take a closer look at a merge request. Click on the title of one."
-msgstr ""
-
-msgid "UserOnboardingTour|Let's take a closer look at all the commits. Click on %{emphasisStart}Commits%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Let's take a closer look at the repository of this project. Click on %{emphasisStart}Repository%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|No thanks"
-msgstr ""
-
-msgid "UserOnboardingTour|Ok, let's go"
-msgstr ""
-
-msgid "UserOnboardingTour|Ok, show me"
-msgstr ""
-
-msgid "UserOnboardingTour|Open one of the issues by clicking on its title."
-msgstr ""
-
-msgid "UserOnboardingTour|Restart this step"
-msgstr ""
-
-msgid "UserOnboardingTour|Skip this step"
-msgstr ""
-
-msgid "UserOnboardingTour|Sweet! Your project was created and is ready to be used.%{lineBreak}%{lineBreak}You can start adding files to the repository or clone it. One last thing we want to show you is how to invite your colleagues to your new project."
-msgstr ""
-
-msgid "UserOnboardingTour|Take a look. Here's a nifty menu for quickly creating issues, merge requests, snippets, projects and groups. Click on it and select \"New project\" from the \"GitLab\" section to get started."
-msgstr ""
-
-msgid "UserOnboardingTour|Thanks for taking the guided tour. Remember, if you want to go through it again, you can start %{emphasisStart}Learn GitLab%{emphasisEnd} in the help menu on the top right."
-msgstr ""
-
-msgid "UserOnboardingTour|Thanks for the feedback! %{thumbsUp}"
-msgstr ""
-
-msgid "UserOnboardingTour|That's it for issues. Let'st take a look at %{emphasisStart}Merge Requests%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|That's it for merge requests. Now for the final part of this guided tour - the %{emphasisStart}CI/CD%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|That's it for the Repository. Let's take a look at the %{emphasisStart}Issues%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|The structure of this page is very similar to issues. Status, description, discussion and the sidebar are all here.%{lineBreak}%{lineBreak}But take a look below the description and you'll notice that there's more information about the merge request, the CI/CD pipeline and the options for approving it.%{lineBreak}%{lineBreak}Alongside the discussion you can also see more information about commits in this merge request, the status of pipelines and review all changes that were made."
-msgstr ""
-
-msgid "UserOnboardingTour|There's a lot of information here but don't worry, we'll go through it.%{lineBreak}%{lineBreak}On the top you can see the status of the issue and when it was opened and by whom. Directly below it is the issue description and below that are other %{emphasisStart}related issues%{emphasisEnd} and %{emphasisStart}merge requests%{emphasisEnd} (if any). Then below that is the %{emphasisStart}discussion%{emphasisEnd}, that's where most of the communication happens.%{lineBreak}%{lineBreak}On the right, there's a sidebar where you can view/change the %{emphasisStart}assignee, milestone, due date, labels, weight%{emphasisEnd}, etc."
-msgstr ""
-
-msgid "UserOnboardingTour|These are all the CI/CD pipelines we have for our %{emphasisStart}%{projectName}%{emphasisEnd} project.%{lineBreak}%{lineBreak}Here you can see the status of each pipeline, for what commit it's running for, its stages and the status for them."
-msgstr ""
-
-msgid "UserOnboardingTour|These are all the issues that are available for community contributions. Let's take a closer look at one of them."
-msgstr ""
-
-msgid "UserOnboardingTour|This is an overview of all merge requests in this project. Similarly to the issues overview it can be filtered down by things like labels, milestones, authors, assignees, etc."
-msgstr ""
-
-msgid "UserOnboardingTour|This is the repository for the %{emphasisStart}%{projectName}%{emphasisEnd} project. All our code is stored here. Feel free to explore and take a closer look at folders and files.%{lineBreak}%{lineBreak}Above the file structure you can see the latest commit, who the author is and the status of the CI/CD pipeline.%{lineBreak}%{lineBreak}If you scroll down below the file structure, you'll find the Readme of this project. This is defined in the README.md file at the root of the repository."
-msgstr ""
-
-msgid "UserOnboardingTour|Welcome to the project overview of the %{emphasisStart}%{projectName}%{emphasisEnd} project. This is the project that we use to work on GitLab. At first, a project seems like a simple repository, but at GitLab, a project is so much more.%{lineBreak}%{lineBreak}You can create projects for hosting your codebase, use it as an issue tracker, collaborate on code, and continuously build, test, and deploy your app with built-in GitLab CI/CD."
+msgid "UserList|created %{timeago}"
msgstr ""
msgid "UserProfile|Activity"
@@ -24471,6 +25116,9 @@ msgstr ""
msgid "UsersSelect|Unassigned"
msgstr ""
+msgid "Using %{codeStart}needs%{codeEnd} allows jobs to run before their stage is reached, as soon as their individual dependencies are met, which speeds up your pipelines."
+msgstr ""
+
msgid "Using %{code_start}::%{code_end} denotes a %{link_start}scoped label set%{link_end}"
msgstr ""
@@ -24555,9 +25203,6 @@ msgstr ""
msgid "Versions"
msgstr ""
-msgid "Very helpful"
-msgstr ""
-
msgid "View Documentation"
msgstr ""
@@ -24706,6 +25351,9 @@ msgstr ""
msgid "VisibilityLevel|Unknown"
msgstr ""
+msgid "Visit settings page"
+msgstr ""
+
msgid "VisualReviewApp|%{stepStart}Step 1%{stepEnd}. Copy the following script:"
msgstr ""
@@ -24799,6 +25447,9 @@ msgstr ""
msgid "VulnerabilityManagement|Something went wrong while trying to delete the comment. Please try again later."
msgstr ""
+msgid "VulnerabilityManagement|Something went wrong while trying to refresh the vulnerability. Please try again later."
+msgstr ""
+
msgid "VulnerabilityManagement|Something went wrong while trying to retrieve the vulnerability history. Please try again later."
msgstr ""
@@ -24841,6 +25492,9 @@ msgstr ""
msgid "Vulnerability|Class"
msgstr ""
+msgid "Vulnerability|Crash Address"
+msgstr ""
+
msgid "Vulnerability|Description"
msgstr ""
@@ -24916,9 +25570,6 @@ msgstr ""
msgid "We couldn't reach the Prometheus server. Either the server no longer exists or the configuration details need updating."
msgstr ""
-msgid "We created a short guided tour that will help you learn the basics of GitLab and how it will help you be better at your job. It should only take a couple of minutes. You will be guided by two types of helpers, best recognized by their color."
-msgstr ""
-
msgid "We detected potential spam in the %{humanized_resource_name}. Please solve the reCAPTCHA to proceed."
msgstr ""
@@ -24952,6 +25603,9 @@ msgstr ""
msgid "We've found no vulnerabilities"
msgstr ""
+msgid "Web Application Firewall"
+msgstr ""
+
msgid "Web IDE"
msgstr ""
@@ -25012,9 +25666,6 @@ msgstr ""
msgid "Welcome to GitLab.com<br>@%{name}!"
msgstr ""
-msgid "Welcome to the Guided GitLab Tour"
-msgstr ""
-
msgid "Welcome to the guided GitLab tour"
msgstr ""
@@ -25062,9 +25713,6 @@ msgstr ""
msgid "While it's rare to have no vulnerabilities, it can happen. In any event, we ask that you please double check your settings to make sure you've set up your dashboard correctly."
msgstr ""
-msgid "White helpers give contextual information."
-msgstr ""
-
msgid "Who can be an approver?"
msgstr ""
@@ -25110,12 +25758,18 @@ msgstr ""
msgid "WikiEmptyIssueMessage|Suggest wiki improvement"
msgstr ""
+msgid "WikiEmptyIssueMessage|You must be a group member in order to add wiki pages. If you have suggestions for how to improve the wiki for this group, consider opening an issue in the %{issues_link}."
+msgstr ""
+
msgid "WikiEmptyIssueMessage|You must be a project member in order to add wiki pages. If you have suggestions for how to improve the wiki for this project, consider opening an issue in the %{issues_link}."
msgstr ""
msgid "WikiEmptyIssueMessage|issue tracker"
msgstr ""
+msgid "WikiEmpty|A wiki is where you can store all the details about your group. This can include why you've created it, its principles, how to use it, and so on."
+msgstr ""
+
msgid "WikiEmpty|A wiki is where you can store all the details about your project. This can include why you've created it, its principles, how to use it, and so on."
msgstr ""
@@ -25125,12 +25779,21 @@ msgstr ""
msgid "WikiEmpty|Suggest wiki improvement"
msgstr ""
+msgid "WikiEmpty|The wiki lets you write documentation for your group"
+msgstr ""
+
msgid "WikiEmpty|The wiki lets you write documentation for your project"
msgstr ""
+msgid "WikiEmpty|This group has no wiki pages"
+msgstr ""
+
msgid "WikiEmpty|This project has no wiki pages"
msgstr ""
+msgid "WikiEmpty|You must be a group member in order to add wiki pages."
+msgstr ""
+
msgid "WikiEmpty|You must be a project member in order to add wiki pages."
msgstr ""
@@ -25407,13 +26070,13 @@ msgstr ""
msgid "You can now export your security dashboard to a CSV report."
msgstr ""
-msgid "You can now submit a merge request to get this change into the original branch."
+msgid "You can now manage alert endpoint configuration in the Alerts section on the Operations settings page. Fields on this page have been deprecated."
msgstr ""
-msgid "You can now submit a merge request to get this change into the original project."
+msgid "You can now submit a merge request to get this change into the original branch."
msgstr ""
-msgid "You can only add files when you are on a branch"
+msgid "You can now submit a merge request to get this change into the original project."
msgstr ""
msgid "You can only edit files when you are on a branch"
@@ -25635,7 +26298,7 @@ msgstr ""
msgid "You need to upload a Google Takeout archive."
msgstr ""
-msgid "You reached %{usage_in_percent} of %{namespace_name}'s capacity (%{used_storage} of %{storage_limit})"
+msgid "You reached %{usage_in_percent} of %{namespace_name}'s storage capacity (%{used_storage} of %{storage_limit})"
msgstr ""
msgid "You tried to fork %{link_to_the_project} but it failed for the following reason:"
@@ -25938,13 +26601,13 @@ msgstr ""
msgid "Your subscription expired!"
msgstr ""
-msgid "Your subscription has been downgraded"
+msgid "Your subscription has been downgraded."
msgstr ""
-msgid "Your subscription will automatically renew in %{remaining_days}"
+msgid "Your subscription will automatically renew in %{remaining_days}."
msgstr ""
-msgid "Your subscription will expire in %{remaining_days}"
+msgid "Your subscription will expire in %{remaining_days}."
msgstr ""
msgid "Zoom meeting added"
@@ -26024,9 +26687,6 @@ msgstr ""
msgid "branch name"
msgstr ""
-msgid "build pipeline reference mismatch"
-msgstr ""
-
msgid "by"
msgstr ""
@@ -26045,6 +26705,9 @@ msgstr ""
msgid "cannot block others"
msgstr ""
+msgid "cannot contain HTML/XML tags, including any word between angle brackets (<,>)."
+msgstr ""
+
msgid "cannot include leading slash or directory traversal."
msgstr ""
@@ -26075,55 +26738,6 @@ msgstr ""
msgid "ciReport|%{remainingPackagesCount} more"
msgstr ""
-msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability"
-msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability for the source branch only"
-msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities for the source branch only"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerability"
-msgid_plural "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerabilities"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new vulnerability"
-msgid_plural "ciReport|%{reportType} %{status} detected %{newCount} new vulnerabilities"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities for the source branch only"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{fixedCount} fixed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} vulnerability for the source branch only"
-msgid_plural "ciReport|%{reportType} %{status} detected %{newCount} vulnerabilities for the source branch only"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "ciReport|%{reportType} %{status} detected no new vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected no vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected no vulnerabilities for the source branch only"
-msgstr ""
-
msgid "ciReport|%{reportType} is loading"
msgstr ""
@@ -26142,7 +26756,7 @@ msgstr ""
msgid "ciReport|All projects"
msgstr ""
-msgid "ciReport|All report types"
+msgid "ciReport|All scanner types"
msgstr ""
msgid "ciReport|All severities"
@@ -26166,6 +26780,9 @@ msgstr ""
msgid "ciReport|Container scanning detects known vulnerabilities in your docker images."
msgstr ""
+msgid "ciReport|Coverage Fuzzing"
+msgstr ""
+
msgid "ciReport|Create a merge request to implement this solution, or download and apply the patch manually."
msgstr ""
@@ -26324,6 +26941,9 @@ msgstr ""
msgid "customize"
msgstr ""
+msgid "data"
+msgstr ""
+
msgid "date must not be after 9999-12-31"
msgstr ""
@@ -26394,6 +27014,9 @@ msgstr ""
msgid "entries cannot contain HTML tags"
msgstr ""
+msgid "epic"
+msgstr ""
+
msgid "error"
msgstr ""
@@ -26415,9 +27038,6 @@ msgstr ""
msgid "expires on %{timebox_due_date}"
msgstr ""
-msgid "external_url"
-msgstr ""
-
msgid "failed"
msgstr ""
@@ -26453,9 +27073,6 @@ msgstr ""
msgid "from"
msgstr ""
-msgid "geo_node_name"
-msgstr ""
-
msgid "group"
msgstr ""
@@ -26477,9 +27094,6 @@ msgstr ""
msgid "https://your-bitbucket-server"
msgstr ""
-msgid "image"
-msgstr ""
-
msgid "image diff"
msgstr ""
@@ -26623,6 +27237,9 @@ msgstr ""
msgid "load it anyway"
msgstr ""
+msgid "loading"
+msgstr ""
+
msgid "locked by %{path_lock_user_name} %{created_at}"
msgstr ""
@@ -26751,9 +27368,6 @@ msgstr ""
msgid "mrWidget|Deployment statistics are not available currently"
msgstr ""
-msgid "mrWidget|Detect issues before deployment with a CI pipeline that continuously tests your code. We created a quick guide that will show you how to create one. Make your code more secure and more robust in just a minute."
-msgstr ""
-
msgid "mrWidget|Did not close"
msgstr ""
@@ -26916,6 +27530,9 @@ msgstr ""
msgid "mrWidget|To approve this merge request, please enter your password. This project requires all approvals to be authenticated."
msgstr ""
+msgid "mrWidget|Use %{linkStart}CI pipelines to test your code%{linkEnd}, simply add a GitLab CI configuration file to your project. It only takes a minute to make your code more secure and robust."
+msgstr ""
+
msgid "mrWidget|When this merge request is ready, remove the WIP: prefix from the title to allow it to be merged"
msgstr ""
@@ -26952,6 +27569,9 @@ msgstr ""
msgid "must be greater than start date"
msgstr ""
+msgid "must contain only valid frameworks"
+msgstr ""
+
msgid "my-awesome-group"
msgstr ""
@@ -26964,6 +27584,9 @@ msgstr ""
msgid "needs to be between 10 minutes and 1 month"
msgstr ""
+msgid "never"
+msgstr ""
+
msgid "never expires"
msgstr ""
@@ -26991,6 +27614,9 @@ msgstr ""
msgid "nounSeries|%{firstItem} and %{lastItem}"
msgstr ""
+msgid "nounSeries|%{item}"
+msgstr ""
+
msgid "nounSeries|%{item}, %{nextItem}"
msgstr ""
@@ -27127,6 +27753,9 @@ msgstr ""
msgid "revised"
msgstr ""
+msgid "satisfied"
+msgstr ""
+
msgid "score"
msgstr ""
@@ -27157,6 +27786,9 @@ msgstr ""
msgid "severity|Unknown"
msgstr ""
+msgid "should be an array of %{object_name} objects"
+msgstr ""
+
msgid "should be greater than or equal to %{access} inherited membership from group %{group_name}"
msgstr ""
@@ -27211,10 +27843,13 @@ msgstr ""
msgid "suggestPipeline|2/2: Commit your changes"
msgstr ""
-msgid "suggestPipeline|Commit the changes and your pipeline will automatically run for the first time."
+msgid "suggestPipeline|Choose %{boldStart}Code Quality%{boldEnd} to add a pipeline that tests the quality of your code."
msgstr ""
-msgid "suggestPipeline|We recommend the %{boldStart}Code Quality%{boldEnd} template, which will add a report widget to your Merge Requests. This way you’ll learn about code quality degradations much sooner. %{footerStart} Goodbye technical debt! %{footerEnd}"
+msgid "suggestPipeline|The template is ready! You can now commit it to create your first pipeline."
+msgstr ""
+
+msgid "suggestPipeline|We’re adding a GitLab CI configuration file to add a pipeline to the project. You could create it manually, but we recommend that you start with a GitLab template that works out of the box."
msgstr ""
msgid "syntax is correct"
@@ -27274,6 +27909,9 @@ msgstr ""
msgid "uses Kubernetes clusters to deploy your code!"
msgstr ""
+msgid "v%{version} published %{timeAgo}"
+msgstr ""
+
msgid "verify ownership"
msgstr ""
diff --git a/locale/sr_CS/gitlab.po b/locale/sr_CS/gitlab.po
index ee9f5a37b3b..775e41c86ac 100644
--- a/locale/sr_CS/gitlab.po
+++ b/locale/sr_CS/gitlab.po
@@ -2,7 +2,7 @@ msgid ""
msgstr ""
"Project-Id-Version: gitlab-ee\n"
"Report-Msgid-Bugs-To: \n"
-"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Last-Translator: \n"
"Language-Team: Serbian (Latin)\n"
"Language: sr_CS\n"
"MIME-Version: 1.0\n"
@@ -10,9 +10,11 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
"X-Crowdin-Project: gitlab-ee\n"
+"X-Crowdin-Project-ID: 288872\n"
"X-Crowdin-Language: sr-CS\n"
"X-Crowdin-File: /master/locale/gitlab.pot\n"
-"PO-Revision-Date: 2020-06-08 15:13\n"
+"X-Crowdin-File-ID: 6\n"
+"PO-Revision-Date: 2020-07-02 01:23\n"
msgid " %{start} to %{end}"
msgstr ""
@@ -224,6 +226,12 @@ msgstr[0] ""
msgstr[1] ""
msgstr[2] ""
+msgid "%d milestone"
+msgid_plural "%d milestones"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+
msgid "%d minute"
msgid_plural "%d minutes"
msgstr[0] ""
@@ -278,12 +286,6 @@ msgstr[0] ""
msgstr[1] ""
msgstr[2] ""
-msgid "%d url scanned"
-msgid_plural "%d urls scanned"
-msgstr[0] ""
-msgstr[1] ""
-msgstr[2] ""
-
msgid "%d vulnerability"
msgid_plural "%d vulnerabilities"
msgstr[0] ""
@@ -368,9 +370,15 @@ msgstr[2] ""
msgid "%{count} related %{pluralized_subject}: %{links}"
msgstr ""
+msgid "%{dashboard_path} could not be found."
+msgstr ""
+
msgid "%{days} days until tags are automatically removed"
msgstr ""
+msgid "%{deployLinkStart}Use a template to deploy to ECS%{deployLinkEnd}, or use a docker image to %{commandsLinkStart}run AWS commands in GitLab CI/CD%{commandsLinkEnd}."
+msgstr ""
+
msgid "%{description}- Sentry event: %{errorUrl}- First seen: %{firstSeen}- Last seen: %{lastSeen} %{countLabel}: %{count}%{userCountLabel}: %{userCount}"
msgstr ""
@@ -413,12 +421,18 @@ msgstr ""
msgid "%{host} sign-in from new location"
msgstr ""
-msgid "%{icon}You are about to add %{usersTag} people to the discussion. Proceed with caution."
+msgid "%{icon}You are about to add %{usersTag} people to the discussion. They will all receive a notification."
+msgstr ""
+
+msgid "%{integrations_link_start}Integrations%{link_end} enable you to make third-party applications part of your GitLab workflow. If the available integrations don't meet your needs, consider using a %{webhooks_link_start}webhook%{link_end}."
msgstr ""
msgid "%{issuableType} will be removed! Are you sure?"
msgstr ""
+msgid "%{issuesCount} issues in this group"
+msgstr ""
+
msgid "%{issuesSize} issues"
msgstr ""
@@ -428,7 +442,7 @@ msgstr ""
msgid "%{labelStart}Class:%{labelEnd} %{class}"
msgstr ""
-msgid "%{labelStart}Confidence:%{labelEnd} %{confidence}"
+msgid "%{labelStart}Evidence:%{labelEnd} %{evidence}"
msgstr ""
msgid "%{labelStart}File:%{labelEnd} %{file}"
@@ -446,6 +460,9 @@ msgstr ""
msgid "%{labelStart}Report Type:%{labelEnd} %{reportType}"
msgstr ""
+msgid "%{labelStart}Scanner:%{labelEnd} %{scanner}"
+msgstr ""
+
msgid "%{labelStart}Severity:%{labelEnd} %{severity}"
msgstr ""
@@ -470,9 +487,6 @@ msgstr ""
msgid "%{link_start}Read more%{link_end} about role permissions"
msgstr ""
-msgid "%{link} can be used for binding events when something is happening within the project."
-msgstr ""
-
msgid "%{listToShow}, and %{awardsListLength} more."
msgstr ""
@@ -506,6 +520,9 @@ msgstr ""
msgid "%{name} found %{resultsString}"
msgstr ""
+msgid "%{name} is already being used for another emoji"
+msgstr ""
+
msgid "%{name} is scheduled for %{action}"
msgstr ""
@@ -554,6 +571,9 @@ msgstr ""
msgid "%{primary} (%{secondary})"
msgstr ""
+msgid "%{ref} cannot be added: %{error}"
+msgstr ""
+
msgid "%{releases} release"
msgid_plural "%{releases} releases"
msgstr[0] ""
@@ -563,13 +583,40 @@ msgstr[2] ""
msgid "%{remaining_approvals} left"
msgstr ""
-msgid "%{retryButtonStart}Try again%{retryButtonEnd} or %{newFileButtonStart}attach a new file%{newFileButtonEnd}"
+msgid "%{reportType} %{status} detected %{critical} critical and %{high} high severity vulnerabilities out of %{total}."
msgstr ""
-msgid "%{screenreaderOnlyStart}Keyboard shorcuts%{screenreaderOnlyEnd} Disabled"
+msgid "%{reportType} %{status} detected %{critical} critical and %{high} high severity vulnerabilities."
msgstr ""
-msgid "%{screenreaderOnlyStart}Keyboard shorcuts%{screenreaderOnlyEnd} Enabled"
+msgid "%{reportType} %{status} detected %{critical} critical severity vulnerabilities out of %{total}."
+msgstr ""
+
+msgid "%{reportType} %{status} detected %{critical} critical severity vulnerability."
+msgid_plural "%{reportType} %{status} detected %{critical} critical severity vulnerabilities."
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+
+msgid "%{reportType} %{status} detected %{high} high severity vulnerabilities out of %{total}."
+msgstr ""
+
+msgid "%{reportType} %{status} detected %{high} high severity vulnerability."
+msgid_plural "%{reportType} %{status} detected %{high} high severity vulnerabilities."
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+
+msgid "%{reportType} %{status} detected %{other} vulnerability."
+msgid_plural "%{reportType} %{status} detected %{other} vulnerabilities."
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+
+msgid "%{reportType} %{status} detected no new vulnerabilities."
+msgstr ""
+
+msgid "%{retryButtonStart}Try again%{retryButtonEnd} or %{newFileButtonStart}attach a new file%{newFileButtonEnd}"
msgstr ""
msgid "%{service_title} %{message}."
@@ -671,6 +718,12 @@ msgstr ""
msgid "%{token}..."
msgstr ""
+msgid "%{totalCpu} (%{freeSpacePercentage}%{percentSymbol} free)"
+msgstr ""
+
+msgid "%{totalMemory} (%{freeSpacePercentage}%{percentSymbol} free)"
+msgstr ""
+
msgid "%{totalWeight} total weight"
msgstr ""
@@ -701,6 +754,12 @@ msgstr ""
msgid "%{verb} %{time_spent_value} spent time."
msgstr ""
+msgid "%{webhooks_link_start}%{webhook_type}%{link_end} enable you to send notifications to web applications in response to events in a group or project."
+msgstr ""
+
+msgid "%{webhooks_link_start}%{webhook_type}%{link_end} enable you to send notifications to web applications in response to events in a group or project. We recommend using an %{integrations_link_start}integration%{link_end} in preference to a webhook."
+msgstr ""
+
msgid "'%{level}' is not a valid visibility level"
msgstr ""
@@ -728,9 +787,6 @@ msgstr ""
msgid "(No changes)"
msgstr ""
-msgid "(Show all)"
-msgstr ""
-
msgid "(check progress)"
msgstr ""
@@ -800,6 +856,9 @@ msgstr ""
msgid "0 for unlimited"
msgstr ""
+msgid "0 for unlimited, only effective with remote storage enabled."
+msgstr ""
+
msgid "1 %{type} addition"
msgid_plural "%{count} %{type} additions"
msgstr[0] ""
@@ -962,6 +1021,12 @@ msgstr ""
msgid "<code>\"johnsmith@example.com\": \"johnsmith@example.com\"</code> will add \"By <a href=\"#\">johnsmith@example.com</a>\" to all issues and comments originally created by johnsmith@example.com. By default, the email address or username is masked to ensure the user's privacy. Use this option if you want to show the full email address."
msgstr ""
+msgid "<code>Masked</code> to prevent the values from being displayed in job logs (must match certain regexp requirements)."
+msgstr ""
+
+msgid "<code>Protected</code> to expose them to protected branches or tags only."
+msgstr ""
+
msgid "<namespace / project>"
msgstr ""
@@ -989,9 +1054,6 @@ msgstr ""
msgid "A .NET Core console application template, customizable for any .NET Core project"
msgstr ""
-msgid "A DAG must have two dependent jobs to be visualized on this tab."
-msgstr ""
-
msgid "A GitBook site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
msgstr ""
@@ -1022,6 +1084,12 @@ msgstr ""
msgid "A deleted user"
msgstr ""
+msgid "A file has been changed."
+msgstr ""
+
+msgid "A file was not found."
+msgstr ""
+
msgid "A file with '%{file_name}' already exists in %{branch} branch"
msgstr ""
@@ -1088,7 +1156,7 @@ msgstr ""
msgid "A subscription will trigger a new pipeline on the default branch of this project when a pipeline successfully completes for a new tag on the %{default_branch_docs} of the subscribed project."
msgstr ""
-msgid "A terraform report was generated in your pipelines."
+msgid "A suggestion is not applicable."
msgstr ""
msgid "A user with write access to the source branch selected this option"
@@ -1259,6 +1327,9 @@ msgstr ""
msgid "Action to take when receiving an alert."
msgstr ""
+msgid "Actions"
+msgstr ""
+
msgid "Activate"
msgstr ""
@@ -1316,6 +1387,9 @@ msgstr ""
msgid "Add LICENSE"
msgstr ""
+msgid "Add New Node"
+msgstr ""
+
msgid "Add README"
msgstr ""
@@ -1391,6 +1465,9 @@ msgstr ""
msgid "Add approval rule"
msgstr ""
+msgid "Add approvers"
+msgstr ""
+
msgid "Add bold text"
msgstr ""
@@ -1448,6 +1525,9 @@ msgstr ""
msgid "Add strikethrough text"
msgstr ""
+msgid "Add suggestion to batch"
+msgstr ""
+
msgid "Add system hook"
msgstr ""
@@ -1547,6 +1627,9 @@ msgstr ""
msgid "Admin Note"
msgstr ""
+msgid "Admin Notifications"
+msgstr ""
+
msgid "Admin Overview"
msgstr ""
@@ -1658,6 +1741,9 @@ msgstr ""
msgid "AdminSettings|Integrations configured here will automatically apply to all projects on this instance."
msgstr ""
+msgid "AdminSettings|Moved to integrations"
+msgstr ""
+
msgid "AdminSettings|No required pipeline"
msgstr ""
@@ -1889,12 +1975,18 @@ msgstr[0] ""
msgstr[1] ""
msgstr[2] ""
+msgid "AlertManagement| assign yourself"
+msgstr ""
+
msgid "AlertManagement|Acknowledged"
msgstr ""
msgid "AlertManagement|Alert"
msgstr ""
+msgid "AlertManagement|Alert assignee(s): %{assignees}"
+msgstr ""
+
msgid "AlertManagement|Alert detail"
msgstr ""
@@ -1910,9 +2002,15 @@ msgstr ""
msgid "AlertManagement|All alerts"
msgstr ""
+msgid "AlertManagement|Assign To"
+msgstr ""
+
msgid "AlertManagement|Assign status"
msgstr ""
+msgid "AlertManagement|Assignee"
+msgstr ""
+
msgid "AlertManagement|Assignees"
msgstr ""
@@ -1931,9 +2029,6 @@ msgstr ""
msgid "AlertManagement|Edit"
msgstr ""
-msgid "AlertManagement|End time"
-msgstr ""
-
msgid "AlertManagement|Events"
msgstr ""
@@ -1952,10 +2047,13 @@ msgstr ""
msgid "AlertManagement|More information"
msgstr ""
+msgid "AlertManagement|No Matching Results"
+msgstr ""
+
msgid "AlertManagement|No alert data to display."
msgstr ""
-msgid "AlertManagement|No alerts available to display. If you think you're seeing this message in error, refresh the page."
+msgid "AlertManagement|No alerts available to display. See %{linkStart}enabling alert management%{linkEnd} for more information on adding alerts to the list."
msgstr ""
msgid "AlertManagement|No alerts to display."
@@ -1964,6 +2062,9 @@ msgstr ""
msgid "AlertManagement|None"
msgstr ""
+msgid "AlertManagement|None -"
+msgstr ""
+
msgid "AlertManagement|Open"
msgstr ""
@@ -2000,9 +2101,18 @@ msgstr ""
msgid "AlertManagement|There was an error displaying the alerts. Confirm your endpoint's configuration details to ensure alerts appear."
msgstr ""
+msgid "AlertManagement|There was an error while updating the assignee(s) list. Please try again."
+msgstr ""
+
+msgid "AlertManagement|There was an error while updating the assignee(s) of the alert. Please try again."
+msgstr ""
+
msgid "AlertManagement|There was an error while updating the status of the alert. Please try again."
msgstr ""
+msgid "AlertManagement|This assignee cannot be assigned to this alert."
+msgstr ""
+
msgid "AlertManagement|Tool"
msgstr ""
@@ -2150,6 +2260,9 @@ msgstr ""
msgid "Allow users to request access (if visibility is public or internal)"
msgstr ""
+msgid "Allowed Geo IP"
+msgstr ""
+
msgid "Allowed email domain restriction only permitted for top-level groups"
msgstr ""
@@ -2183,6 +2296,9 @@ msgstr ""
msgid "Amazon Web Services"
msgstr ""
+msgid "Amazon Web Services Logo"
+msgstr ""
+
msgid "Amazon authentication is not %{link_start}correctly configured%{link_end}. Ask your GitLab administrator if you want to use this service."
msgstr ""
@@ -2240,10 +2356,16 @@ msgstr ""
msgid "An error occurred when updating the issue weight"
msgstr ""
+msgid "An error occurred while acknowledging the notification. Refresh the page and try again."
+msgstr ""
+
+msgid "An error occurred while adding approvers"
+msgstr ""
+
msgid "An error occurred while adding formatted title for epic"
msgstr ""
-msgid "An error occurred while checking group path"
+msgid "An error occurred while checking group path. Please refresh and try again."
msgstr ""
msgid "An error occurred while committing your changes."
@@ -2387,7 +2509,7 @@ msgstr ""
msgid "An error occurred while loading milestones"
msgstr ""
-msgid "An error occurred while loading terraform report"
+msgid "An error occurred while loading project creation UI"
msgstr ""
msgid "An error occurred while loading the data. Please try again."
@@ -2441,6 +2563,9 @@ msgstr ""
msgid "An error occurred while rendering preview broadcast message"
msgstr ""
+msgid "An error occurred while rendering the editor"
+msgstr ""
+
msgid "An error occurred while reordering issues."
msgstr ""
@@ -2537,7 +2662,7 @@ msgstr ""
msgid "Analyze your dependencies for known vulnerabilities."
msgstr ""
-msgid "Analyze your source code and git history for secrets"
+msgid "Analyze your source code and git history for secrets."
msgstr ""
msgid "Analyze your source code for known vulnerabilities."
@@ -2651,12 +2776,18 @@ msgstr ""
msgid "Apply suggestion"
msgstr ""
+msgid "Apply suggestions"
+msgstr ""
+
msgid "Apply template"
msgstr ""
msgid "Apply this approval rule to any branch or a specific protected branch."
msgstr ""
+msgid "Applying"
+msgstr ""
+
msgid "Applying a template will replace the existing issue description. Any changes you have made will be lost."
msgstr ""
@@ -2669,12 +2800,18 @@ msgstr ""
msgid "Applying multiple commands"
msgstr ""
-msgid "Applying suggestion"
+msgid "Applying suggestion..."
+msgstr ""
+
+msgid "Applying suggestions..."
msgstr ""
msgid "Approval rules"
msgstr ""
+msgid "Approval rules reset to project defaults"
+msgstr ""
+
msgid "ApprovalRuleRemove|%d member"
msgid_plural "ApprovalRuleRemove|%d members"
msgstr[0] ""
@@ -2732,6 +2869,9 @@ msgstr ""
msgid "Approved"
msgstr ""
+msgid "Approved MRs"
+msgstr ""
+
msgid "Approved by: "
msgstr ""
@@ -2945,6 +3085,9 @@ msgstr ""
msgid "Assign"
msgstr ""
+msgid "Assign Iteration"
+msgstr ""
+
msgid "Assign custom color like #FF0000"
msgstr ""
@@ -2963,6 +3106,9 @@ msgstr ""
msgid "Assign to"
msgstr ""
+msgid "Assign to commenting user"
+msgstr ""
+
msgid "Assign yourself to these issues"
msgstr ""
@@ -2990,6 +3136,9 @@ msgstr[0] ""
msgstr[1] ""
msgstr[2] ""
+msgid "Assignee has no permissions"
+msgstr ""
+
msgid "Assignee lists not available with your current license"
msgstr ""
@@ -2999,6 +3148,9 @@ msgstr ""
msgid "Assignee(s)"
msgstr ""
+msgid "Assignees"
+msgstr ""
+
msgid "Assigns %{assignee_users_sentence}."
msgstr ""
@@ -3035,6 +3187,9 @@ msgstr ""
msgid "Audit Events is a way to keep track of important events that happened in GitLab."
msgstr ""
+msgid "Audit Log"
+msgstr ""
+
msgid "AuditEvents|(removed)"
msgstr ""
@@ -3071,6 +3226,9 @@ msgstr ""
msgid "AuditLogs|IP Address"
msgstr ""
+msgid "AuditLogs|Member Events"
+msgstr ""
+
msgid "AuditLogs|No matching %{type} found."
msgstr ""
@@ -3353,9 +3511,6 @@ msgstr ""
msgid "Badges|e.g. %{exampleUrl}"
msgstr ""
-msgid "Badge|New"
-msgstr ""
-
msgid "Balsamiq file could not be loaded."
msgstr ""
@@ -3404,6 +3559,12 @@ msgstr ""
msgid "Below you will find all the groups that are public."
msgstr ""
+msgid "Beta"
+msgstr ""
+
+msgid "Bi-weekly code coverage"
+msgstr ""
+
msgid "Billing"
msgstr ""
@@ -3464,9 +3625,15 @@ msgstr ""
msgid "Bitbucket Server Import"
msgstr ""
+msgid "Bitbucket Server import"
+msgstr ""
+
msgid "Bitbucket import"
msgstr ""
+msgid "Blame"
+msgstr ""
+
msgid "Blocked"
msgstr ""
@@ -3479,9 +3646,6 @@ msgstr ""
msgid "Blog"
msgstr ""
-msgid "Blue helpers indicate an action to be taken."
-msgstr ""
-
msgid "Board name"
msgstr ""
@@ -3680,6 +3844,9 @@ msgstr ""
msgid "Broadcast Message was successfully updated."
msgstr ""
+msgid "Broadcast Messages"
+msgstr ""
+
msgid "Browse Directory"
msgstr ""
@@ -3734,6 +3901,9 @@ msgstr ""
msgid "By %{user_name}"
msgstr ""
+msgid "By URL"
+msgstr ""
+
msgid "By default GitLab sends emails in HTML and plain text formats so mail clients can choose what format to use. Disable this option if you only want to send emails in plain text format."
msgstr ""
@@ -3839,6 +4009,9 @@ msgstr ""
msgid "Can override approvers and approvals required per merge request"
msgstr ""
+msgid "Can't apply as this line has changed or the suggestion already matches its content."
+msgstr ""
+
msgid "Can't create snippet: %{err}"
msgstr ""
@@ -4025,9 +4198,6 @@ msgstr ""
msgid "Changes are still tracked. Useful for cluster/index migrations."
msgstr ""
-msgid "Changes are unknown"
-msgstr ""
-
msgid "Changes suppressed. Click to show."
msgstr ""
@@ -4334,15 +4504,9 @@ msgstr ""
msgid "Choose what content you want to see on a group’s overview page"
msgstr ""
-msgid "Choose which groups you wish to synchronize to this secondary node"
-msgstr ""
-
msgid "Choose which repositories you want to connect and run CI/CD pipelines."
msgstr ""
-msgid "Choose which shards you wish to synchronize to this secondary node"
-msgstr ""
-
msgid "Choose your framework"
msgstr ""
@@ -4640,9 +4804,15 @@ msgstr ""
msgid "Cluster does not exist"
msgstr ""
+msgid "Cluster is required for Stages::ClusterEndpointInserter"
+msgstr ""
+
msgid "Cluster level"
msgstr ""
+msgid "Cluster type must be specificed for Stages::ClusterEndpointInserter"
+msgstr ""
+
msgid "ClusterIntegration| %{custom_domain_start}More information%{custom_domain_end}."
msgstr ""
@@ -4754,7 +4924,7 @@ msgstr ""
msgid "ClusterIntegration|Cert-Manager"
msgstr ""
-msgid "ClusterIntegration|Cert-Manager is a native Kubernetes certificate management controller that helps with issuing certificates. Installing Cert-Manager on your cluster will issue a certificate by %{letsEncrypt} and ensure that certificates are valid and up-to-date."
+msgid "ClusterIntegration|Cert-Manager is a native Kubernetes certificate management controller that helps with issuing certificates. Installing Cert-Manager on your cluster will issue a certificate by %{linkStart}Let's Encrypt%{linkEnd} and ensure that certificates are valid and up-to-date."
msgstr ""
msgid "ClusterIntegration|Certificate Authority bundle (PEM format)"
@@ -4781,9 +4951,6 @@ msgstr ""
msgid "ClusterIntegration|Clear the local cache of namespace and service accounts. This is necessary if your integration has become out of sync. The cache is repopulated during the next CI job that requires namespace and service accounts."
msgstr ""
-msgid "ClusterIntegration|Cloud Run"
-msgstr ""
-
msgid "ClusterIntegration|Cluster being created"
msgstr ""
@@ -4868,7 +5035,7 @@ msgstr ""
msgid "ClusterIntegration|Crossplane"
msgstr ""
-msgid "ClusterIntegration|Crossplane enables declarative provisioning of managed services from your cloud of choice using %{codeStart}kubectl%{codeEnd} or %{gitlabIntegrationLink}. Crossplane runs inside your Kubernetes cluster and supports secure connectivity and secrets management between app containers and the cloud services they depend on."
+msgid "ClusterIntegration|Crossplane enables declarative provisioning of managed services from your cloud of choice using %{codeStart}kubectl%{codeEnd} or %{linkStart}GitLab Integration%{linkEnd}. Crossplane runs inside your Kubernetes cluster and supports secure connectivity and secrets management between app containers and the cloud services they depend on."
msgstr ""
msgid "ClusterIntegration|Deletes all GitLab resources attached to this cluster during removal"
@@ -4952,9 +5119,6 @@ msgstr ""
msgid "ClusterIntegration|GitLab-managed cluster"
msgstr ""
-msgid "ClusterIntegration|Gitlab Integration"
-msgstr ""
-
msgid "ClusterIntegration|Global default"
msgstr ""
@@ -5000,7 +5164,7 @@ msgstr ""
msgid "ClusterIntegration|Ingress gives you a way to route requests to services based on the request host or path, centralizing a number of services into a single entrypoint."
msgstr ""
-msgid "ClusterIntegration|Installing Ingress may incur additional costs. Learn more about %{pricingLink}."
+msgid "ClusterIntegration|Installing Ingress may incur additional costs. Learn more about %{linkStart}pricing%{linkEnd}."
msgstr ""
msgid "ClusterIntegration|Instance cluster"
@@ -5015,7 +5179,7 @@ msgstr ""
msgid "ClusterIntegration|Issuer Email"
msgstr ""
-msgid "ClusterIntegration|Issuers represent a certificate authority. You must provide an email address for your Issuer. "
+msgid "ClusterIntegration|Issuers represent a certificate authority. You must provide an email address for your Issuer."
msgstr ""
msgid "ClusterIntegration|Jupyter Hostname"
@@ -5087,9 +5251,6 @@ msgstr ""
msgid "ClusterIntegration|Learn more about instance Kubernetes clusters"
msgstr ""
-msgid "ClusterIntegration|Let's Encrypt"
-msgstr ""
-
msgid "ClusterIntegration|Loading IAM Roles"
msgstr ""
@@ -5201,7 +5362,7 @@ msgstr ""
msgid "ClusterIntegration|Prometheus"
msgstr ""
-msgid "ClusterIntegration|Prometheus is an open-source monitoring system with %{gitlabIntegrationLink} to monitor deployed applications."
+msgid "ClusterIntegration|Prometheus is an open-source monitoring system with %{linkStart}GitLab Integration%{linkEnd} to monitor deployed applications."
msgstr ""
msgid "ClusterIntegration|Provider details"
@@ -5357,10 +5518,10 @@ msgstr ""
msgid "ClusterIntegration|Select zone to choose machine type"
msgstr ""
-msgid "ClusterIntegration|Send Cilium Logs"
+msgid "ClusterIntegration|Send Container Network Policies Logs"
msgstr ""
-msgid "ClusterIntegration|Send ModSecurity Logs"
+msgid "ClusterIntegration|Send Web Application Firewall Logs"
msgstr ""
msgid "ClusterIntegration|Service Token"
@@ -5528,15 +5689,12 @@ msgstr ""
msgid "ClusterIntegration|documentation"
msgstr ""
-msgid "ClusterIntegration|installed via %{installed_via}"
+msgid "ClusterIntegration|installed via %{linkStart}Cloud Run%{linkEnd}"
msgstr ""
msgid "ClusterIntegration|meets the requirements"
msgstr ""
-msgid "ClusterIntegration|pricing"
-msgstr ""
-
msgid "ClusterIntegration|sign up"
msgstr ""
@@ -5573,6 +5731,15 @@ msgstr ""
msgid "Code"
msgstr ""
+msgid "Code Coverage: %{coveragePercentage}%{percentSymbol}"
+msgstr ""
+
+msgid "Code Coverage| Empty code coverage data"
+msgstr ""
+
+msgid "Code Coverage|Couldn't fetch the code coverage data"
+msgstr ""
+
msgid "Code Owners"
msgstr ""
@@ -5627,7 +5794,7 @@ msgstr ""
msgid "Collapse approvers"
msgstr ""
-msgid "Collapse child epics"
+msgid "Collapse milestones"
msgstr ""
msgid "Collapse replies"
@@ -5645,6 +5812,9 @@ msgstr ""
msgid "Coming soon"
msgstr ""
+msgid "Comma-separated, e.g. '1.1.1.1, 2.2.2.0/24'"
+msgstr ""
+
msgid "Command"
msgstr ""
@@ -5816,6 +5986,12 @@ msgstr ""
msgid "Compliance framework (optional)"
msgstr ""
+msgid "Compliance frameworks"
+msgstr ""
+
+msgid "ComplianceDashboard|created by:"
+msgstr ""
+
msgid "ComplianceFramework|GDPR"
msgstr ""
@@ -5954,6 +6130,9 @@ msgstr ""
msgid "Connection timed out"
msgstr ""
+msgid "Connection timeout"
+msgstr ""
+
msgid "Contact sales to upgrade"
msgstr ""
@@ -6011,9 +6190,6 @@ msgstr ""
msgid "ContainerRegistry|CLI Commands"
msgstr ""
-msgid "ContainerRegistry|Compressed Size"
-msgstr ""
-
msgid "ContainerRegistry|Container Registry"
msgstr ""
@@ -6029,6 +6205,9 @@ msgstr ""
msgid "ContainerRegistry|Copy push command"
msgstr ""
+msgid "ContainerRegistry|Delete selected"
+msgstr ""
+
msgid "ContainerRegistry|Docker connection error"
msgstr ""
@@ -6062,16 +6241,16 @@ msgstr ""
msgid "ContainerRegistry|If you are not already logged in, you need to authenticate to the Container Registry by using your GitLab username and password. If you have %{twofaDocLinkStart}Two-Factor Authentication%{twofaDocLinkEnd} enabled, use a %{personalAccessTokensDocLinkStart}Personal Access Token%{personalAccessTokensDocLinkEnd} instead of a password."
msgstr ""
-msgid "ContainerRegistry|Image ID"
+msgid "ContainerRegistry|Image ID: %{imageId}"
msgstr ""
msgid "ContainerRegistry|Image Repositories"
msgstr ""
-msgid "ContainerRegistry|Keep and protect the images that matter most."
+msgid "ContainerRegistry|Image tags"
msgstr ""
-msgid "ContainerRegistry|Last Updated"
+msgid "ContainerRegistry|Keep and protect the images that matter most."
msgstr ""
msgid "ContainerRegistry|Login"
@@ -6086,6 +6265,9 @@ msgstr ""
msgid "ContainerRegistry|Please contact your administrator."
msgstr ""
+msgid "ContainerRegistry|Published %{timeInfo}"
+msgstr ""
+
msgid "ContainerRegistry|Push an image"
msgstr ""
@@ -6101,9 +6283,6 @@ msgstr ""
msgid "ContainerRegistry|Remove repository"
msgstr ""
-msgid "ContainerRegistry|Remove selected tags"
-msgstr ""
-
msgid "ContainerRegistry|Remove tag"
msgid_plural "ContainerRegistry|Remove tags"
msgstr[0] ""
@@ -6134,9 +6313,6 @@ msgstr ""
msgid "ContainerRegistry|Sorry, your filter produced no results."
msgstr ""
-msgid "ContainerRegistry|Tag"
-msgstr ""
-
msgid "ContainerRegistry|Tag expiration policy"
msgstr ""
@@ -6281,21 +6457,6 @@ msgstr ""
msgid "Control the display of third party offers."
msgstr ""
-msgid "Control the maximum concurrency of LFS/attachment backfill for this secondary node"
-msgstr ""
-
-msgid "Control the maximum concurrency of container repository operations for this Geo node"
-msgstr ""
-
-msgid "Control the maximum concurrency of repository backfill for this secondary node"
-msgstr ""
-
-msgid "Control the maximum concurrency of verification operations for this Geo node"
-msgstr ""
-
-msgid "Control the minimum interval in days that a repository should be reverified for this primary node"
-msgstr ""
-
msgid "Cookie domain"
msgstr ""
@@ -6437,6 +6598,9 @@ msgstr ""
msgid "Could not find design."
msgstr ""
+msgid "Could not find iteration"
+msgstr ""
+
msgid "Could not remove the trigger."
msgstr ""
@@ -6473,6 +6637,12 @@ msgstr ""
msgid "Coverage"
msgstr ""
+msgid "Coverage Fuzzing"
+msgstr ""
+
+msgid "Crash State"
+msgstr ""
+
msgid "Create"
msgstr ""
@@ -6521,6 +6691,9 @@ msgstr ""
msgid "Create a personal access token on your account to pull or push via %{protocol}."
msgstr ""
+msgid "Create a project pre-populated with the necessary files to get you started quickly."
+msgstr ""
+
msgid "Create an account using:"
msgstr ""
@@ -6689,6 +6862,9 @@ msgstr ""
msgid "Creates branch '%{branch_name}' and a merge request to resolve this issue."
msgstr ""
+msgid "Creating"
+msgstr ""
+
msgid "Creating epic"
msgstr ""
@@ -6740,7 +6916,10 @@ msgstr ""
msgid "Current vulnerabilities count"
msgstr ""
-msgid "CurrentUser|Buy CI minutes"
+msgid "CurrentUser|Buy Pipeline minutes"
+msgstr ""
+
+msgid "CurrentUser|One of your groups is running out"
msgstr ""
msgid "CurrentUser|Profile"
@@ -6755,6 +6934,9 @@ msgstr ""
msgid "CurrentUser|Upgrade"
msgstr ""
+msgid "Custom Attributes"
+msgstr ""
+
msgid "Custom CI configuration path"
msgstr ""
@@ -7019,12 +7201,18 @@ msgstr ""
msgid "DAG"
msgstr ""
+msgid "DAG visualization requires at least 3 dependent jobs."
+msgstr ""
+
msgid "DNS"
msgstr ""
msgid "Dashboard"
msgstr ""
+msgid "Dashboard uid not found"
+msgstr ""
+
msgid "DashboardProjects|All"
msgstr ""
@@ -7049,6 +7237,9 @@ msgstr ""
msgid "Data is still calculating..."
msgstr ""
+msgid "Datasource name not found"
+msgstr ""
+
msgid "Date"
msgstr ""
@@ -7229,6 +7420,9 @@ msgstr ""
msgid "Delete this attachment"
msgstr ""
+msgid "Delete user list"
+msgstr ""
+
msgid "Delete variable"
msgstr ""
@@ -7550,6 +7744,9 @@ msgstr ""
msgid "Deploying to"
msgstr ""
+msgid "Deploying to AWS is easy with GitLab"
+msgstr ""
+
msgid "Deployment Frequency"
msgstr ""
@@ -7913,6 +8110,9 @@ msgstr ""
msgid "Dismissed on pipeline %{pipelineLink} at %{projectLink}"
msgstr ""
+msgid "Display alerts from all your monitoring tools directly within GitLab."
+msgstr ""
+
msgid "Display name"
msgstr ""
@@ -7970,9 +8170,6 @@ msgstr ""
msgid "Don't show again"
msgstr ""
-msgid "Don't worry, you can access this tour by clicking on the help icon in the top right corner and choose <strong>Learn GitLab</strong>."
-msgstr ""
-
msgid "Done"
msgstr ""
@@ -8099,6 +8296,9 @@ msgstr ""
msgid "Edit Release"
msgstr ""
+msgid "Edit Slack integration"
+msgstr ""
+
msgid "Edit Snippet"
msgstr ""
@@ -8138,6 +8338,9 @@ msgstr ""
msgid "Edit issues"
msgstr ""
+msgid "Edit iteration"
+msgstr ""
+
msgid "Edit public deploy key"
msgstr ""
@@ -8186,6 +8389,9 @@ msgstr ""
msgid "Email"
msgstr ""
+msgid "Email Notification"
+msgstr ""
+
msgid "Email address"
msgstr ""
@@ -8198,6 +8404,9 @@ msgstr ""
msgid "Email not verified. Please verify your email in Salesforce."
msgstr ""
+msgid "Email notification for unknown sign-ins"
+msgstr ""
+
msgid "Email patch"
msgstr ""
@@ -8429,6 +8638,9 @@ msgstr ""
msgid "Enforce DNS rebinding attack protection"
msgstr ""
+msgid "Enforce personal access token expiration"
+msgstr ""
+
msgid "Ensure connectivity is available from the GitLab server to the Prometheus server"
msgstr ""
@@ -8498,6 +8710,9 @@ msgstr ""
msgid "Enter the name of your application, and we'll return a unique %{type}."
msgstr ""
+msgid "Enter weights for storages for new repositories."
+msgstr ""
+
msgid "Enter your password to approve"
msgstr ""
@@ -8507,15 +8722,27 @@ msgstr ""
msgid "Environment does not have deployments"
msgstr ""
+msgid "Environment is required for Stages::MetricEndpointInserter"
+msgstr ""
+
+msgid "Environment is required for Stages::VariableEndpointInserter"
+msgstr ""
+
msgid "Environment scope"
msgstr ""
+msgid "Environment variables are applied to all project environments in this instance via the Runner. You can use environment variables for passwords, secret keys, etc. Make variables available to the running application by prepending the variable key with <code>K8S_SECRET_</code>. You can set variables to be:"
+msgstr ""
+
msgid "Environment variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. Additionally, they can be masked so they are hidden in job logs, though they must match certain regexp requirements to do so. You can use environment variables for passwords, secret keys, or whatever you want."
msgstr ""
msgid "Environment variables are configured by your administrator to be %{link_start}protected%{link_end} by default"
msgstr ""
+msgid "Environment variables on this GitLab instance are configured to be %{link_start}protected%{link_end} by default"
+msgstr ""
+
msgid "Environment:"
msgstr ""
@@ -9047,6 +9274,9 @@ msgstr ""
msgid "EventFilterBy|Filter by comments"
msgstr ""
+msgid "EventFilterBy|Filter by designs"
+msgstr ""
+
msgid "EventFilterBy|Filter by epic events"
msgstr ""
@@ -9179,15 +9409,15 @@ msgstr ""
msgid "Expand approvers"
msgstr ""
-msgid "Expand child epics"
-msgstr ""
-
msgid "Expand down"
msgstr ""
msgid "Expand dropdown"
msgstr ""
+msgid "Expand milestones"
+msgstr ""
+
msgid "Expand sidebar"
msgstr ""
@@ -9203,6 +9433,9 @@ msgstr ""
msgid "Expiration date"
msgstr ""
+msgid "Expiration not enforced"
+msgstr ""
+
msgid "Expiration policy for the Container Registry is a perfect solution for keeping the Registry space down while still enjoying the full power of GitLab CI/CD."
msgstr ""
@@ -9323,6 +9556,9 @@ msgstr ""
msgid "Failed Jobs"
msgstr ""
+msgid "Failed on"
+msgstr ""
+
msgid "Failed to add a Zoom meeting"
msgstr ""
@@ -9383,13 +9619,19 @@ msgstr ""
msgid "Failed to install."
msgstr ""
+msgid "Failed to load assignees. Please try again."
+msgstr ""
+
+msgid "Failed to load authors. Please try again."
+msgstr ""
+
msgid "Failed to load emoji list."
msgstr ""
msgid "Failed to load error details from Sentry."
msgstr ""
-msgid "Failed to load errors from Sentry. Error message: %{errorMessage}"
+msgid "Failed to load errors from Sentry."
msgstr ""
msgid "Failed to load group activity metrics. Please try again."
@@ -9470,6 +9712,9 @@ msgstr ""
msgid "Failed to set due date because the date format is invalid."
msgstr ""
+msgid "Failed to set iteration on this issue. Please try again."
+msgstr ""
+
msgid "Failed to signing using smartcard authentication"
msgstr ""
@@ -9692,13 +9937,10 @@ msgstr ""
msgid "FeatureFlags|Target environments"
msgstr ""
-msgid "FeatureFlags|There are no active feature flags"
-msgstr ""
-
-msgid "FeatureFlags|There are no inactive feature flags"
+msgid "FeatureFlags|There was an error fetching the feature flags."
msgstr ""
-msgid "FeatureFlags|There was an error fetching the feature flags."
+msgid "FeatureFlags|There was an error retrieving user lists"
msgstr ""
msgid "FeatureFlags|Try again in a few moments or contact your support team."
@@ -9710,9 +9952,18 @@ msgstr ""
msgid "FeatureFlag|Delete strategy"
msgstr ""
+msgid "FeatureFlag|List"
+msgstr ""
+
msgid "FeatureFlag|Percentage"
msgstr ""
+msgid "FeatureFlag|Select a user list"
+msgstr ""
+
+msgid "FeatureFlag|There are no configured user lists"
+msgstr ""
+
msgid "FeatureFlag|Type"
msgstr ""
@@ -9869,6 +10120,12 @@ msgstr ""
msgid "Filter..."
msgstr ""
+msgid "Find File"
+msgstr ""
+
+msgid "Find bugs in your code with coverage-guided fuzzing"
+msgstr ""
+
msgid "Find by path"
msgstr ""
@@ -10133,6 +10390,9 @@ msgstr ""
msgid "Geo allows you to replicate your GitLab instance to other geographical locations."
msgstr ""
+msgid "Geo nodes are paused using a command run on the node"
+msgstr ""
+
msgid "GeoNodeStatusEvent|%{timeAgoStr} (%{pendingEvents} events)"
msgstr ""
@@ -10217,6 +10477,9 @@ msgstr ""
msgid "GeoNodes|Not checksummed"
msgstr ""
+msgid "GeoNodes|Package files"
+msgstr ""
+
msgid "GeoNodes|Pausing replication stops the sync process. Are you sure?"
msgstr ""
@@ -10235,6 +10498,9 @@ msgstr ""
msgid "GeoNodes|Replication slots"
msgstr ""
+msgid "GeoNodes|Replication status"
+msgstr ""
+
msgid "GeoNodes|Repositories"
msgstr ""
@@ -10445,6 +10711,9 @@ msgstr ""
msgid "Geo|Synchronization failed - %{error}"
msgstr ""
+msgid "Geo|Synchronization of %{itemTitle} is disabled."
+msgstr ""
+
msgid "Geo|The database is currently %{db_lag} behind the primary node."
msgstr ""
@@ -10757,6 +11026,9 @@ msgstr ""
msgid "Go to %{link_to_google_takeout}."
msgstr ""
+msgid "Go to <strong>Issues</strong> > <strong>Boards</strong> to access your personalized learning issue board."
+msgstr ""
+
msgid "Go to Pipelines"
msgstr ""
@@ -10856,9 +11128,6 @@ msgstr ""
msgid "Go to your snippets"
msgstr ""
-msgid "Golden Tanuki"
-msgstr ""
-
msgid "Google Cloud Platform"
msgstr ""
@@ -10880,6 +11149,9 @@ msgstr ""
msgid "Grafana URL"
msgstr ""
+msgid "Grafana response contains invalid json"
+msgstr ""
+
msgid "GrafanaIntegration|API Token"
msgstr ""
@@ -10928,9 +11200,6 @@ msgstr ""
msgid "Group %{group_name} was successfully created."
msgstr ""
-msgid "Group '%{group_name}' is being imported."
-msgstr ""
-
msgid "Group Audit Events"
msgstr ""
@@ -10967,9 +11236,6 @@ msgstr ""
msgid "Group by:"
msgstr ""
-msgid "Group could not be imported: %{errors}"
-msgstr ""
-
msgid "Group description"
msgstr ""
@@ -10997,9 +11263,15 @@ msgstr ""
msgid "Group has not been marked for deletion"
msgstr ""
+msgid "Group import could not be scheduled"
+msgstr ""
+
msgid "Group info:"
msgstr ""
+msgid "Group is required when cluster_type is :group"
+msgstr ""
+
msgid "Group maintainers can register group runners in the %{link}"
msgstr ""
@@ -11063,6 +11335,24 @@ msgstr ""
msgid "GroupActivyMetrics|Recent activity (last 90 days)"
msgstr ""
+msgid "GroupImport|Failed to import group."
+msgstr ""
+
+msgid "GroupImport|Group '%{group_name}' is being imported."
+msgstr ""
+
+msgid "GroupImport|Group could not be imported: %{errors}"
+msgstr ""
+
+msgid "GroupImport|Please wait while we import the group for you. Refresh at will."
+msgstr ""
+
+msgid "GroupImport|The group was successfully imported."
+msgstr ""
+
+msgid "GroupImport|Unable to process group import file"
+msgstr ""
+
msgid "GroupRoadmap|%{dateWord} – No end date"
msgstr ""
@@ -11330,6 +11620,9 @@ msgstr ""
msgid "Groups (%{groups})"
msgstr ""
+msgid "Groups and projects"
+msgstr ""
+
msgid "Groups can also be nested by creating %{subgroup_docs_link_start}subgroups%{subgroup_docs_link_end}."
msgstr ""
@@ -11375,6 +11668,33 @@ msgstr ""
msgid "GroupsEmptyState|You can manage your group member’s permissions and access to each project in the group."
msgstr ""
+msgid "GroupsNew|Contact an administrator to enable options for importing your group."
+msgstr ""
+
+msgid "GroupsNew|Create"
+msgstr ""
+
+msgid "GroupsNew|Create group"
+msgstr ""
+
+msgid "GroupsNew|GitLab group export"
+msgstr ""
+
+msgid "GroupsNew|Import"
+msgstr ""
+
+msgid "GroupsNew|Import group"
+msgstr ""
+
+msgid "GroupsNew|My Awesome Group"
+msgstr ""
+
+msgid "GroupsNew|No import options available"
+msgstr ""
+
+msgid "GroupsNew|To copy a GitLab group between installations, navigate to the group settings page for the original installation, generate an export file, and upload it here."
+msgstr ""
+
msgid "GroupsTree|Are you sure you want to leave the \"%{fullName}\" group?"
msgstr ""
@@ -11477,6 +11797,9 @@ msgstr ""
msgid "Helps reduce request volume for protected paths"
msgstr ""
+msgid "Here are all your projects in your group, including the one you just created. To start, let’s take a look at your personalized learning project which will help you learn about GitLab at your own pace."
+msgstr ""
+
msgid "Here you will find recent merge request activity"
msgstr ""
@@ -11492,6 +11815,9 @@ msgstr[0] ""
msgstr[1] ""
msgstr[2] ""
+msgid "Hide details"
+msgstr ""
+
msgid "Hide file browser"
msgstr ""
@@ -11501,6 +11827,9 @@ msgstr ""
msgid "Hide host keys manual input"
msgstr ""
+msgid "Hide list"
+msgstr ""
+
msgid "Hide marketing-related entries from help"
msgstr ""
@@ -11522,9 +11851,6 @@ msgstr[2] ""
msgid "Hide values"
msgstr ""
-msgid "Hiding all labels"
-msgstr ""
-
msgid "High or unknown vulnerabilities present"
msgstr ""
@@ -11609,6 +11935,9 @@ msgstr ""
msgid "ID:"
msgstr ""
+msgid "IDE"
+msgstr ""
+
msgid "IDE|Allow live previews of JavaScript projects in the Web IDE using CodeSandbox Live Preview."
msgstr ""
@@ -11699,10 +12028,10 @@ msgstr ""
msgid "If enabled"
msgstr ""
-msgid "If enabled, access to projects will be validated on an external service using their classification label."
+msgid "If enabled, GitLab will handle Object Storage replication using Geo. %{linkStart}More information%{linkEnd}"
msgstr ""
-msgid "If enabled, and if object storage is enabled, GitLab will handle Object Storage replication using Geo"
+msgid "If enabled, access to projects will be validated on an external service using their classification label."
msgstr ""
msgid "If there is no previous license or if the previous license has expired, some GitLab functionality will be blocked until a new, valid license is uploaded."
@@ -12065,6 +12394,9 @@ msgstr[0] ""
msgstr[1] ""
msgstr[2] ""
+msgid "Instance Configuration"
+msgstr ""
+
msgid "Instance Statistics visibility"
msgstr ""
@@ -12086,9 +12418,6 @@ msgstr ""
msgid "Integrations"
msgstr ""
-msgid "Integrations allow you to integrate GitLab with other applications"
-msgstr ""
-
msgid "Integrations|All details"
msgstr ""
@@ -12200,6 +12529,9 @@ msgstr ""
msgid "Invalid query"
msgstr ""
+msgid "Invalid repository bundle for snippet with id %{snippet_id}"
+msgstr ""
+
msgid "Invalid repository path"
msgstr ""
@@ -12218,6 +12550,9 @@ msgstr ""
msgid "Invalid two-factor code."
msgstr ""
+msgid "Invalid yaml"
+msgstr ""
+
msgid "Invitation"
msgstr ""
@@ -12437,15 +12772,24 @@ msgstr ""
msgid "It seems like the Dependency Scanning job ran successfully, but no dependencies have been detected in your project."
msgstr ""
+msgid "It seems that there is currently no available data for code coverage"
+msgstr ""
+
msgid "It's you"
msgstr ""
+msgid "Iteration"
+msgstr ""
+
msgid "Iteration changed to"
msgstr ""
msgid "Iteration removed"
msgstr ""
+msgid "Iteration updated"
+msgstr ""
+
msgid "Iterations"
msgstr ""
@@ -12476,12 +12820,18 @@ msgstr ""
msgid "January"
msgstr ""
+msgid "Jira Issues"
+msgstr ""
+
msgid "Jira import is already running."
msgstr ""
msgid "Jira integration not configured."
msgstr ""
+msgid "Jira project key is not configured"
+msgstr ""
+
msgid "Jira project: %{importProject}"
msgstr ""
@@ -12662,6 +13012,9 @@ msgstr ""
msgid "Keep divergent refs"
msgstr ""
+msgid "Kerberos access denied"
+msgstr ""
+
msgid "Key"
msgstr ""
@@ -12671,10 +13024,13 @@ msgstr ""
msgid "Key: %{key}"
msgstr ""
-msgid "Keyboard Shortcuts"
+msgid "Keyboard shortcuts"
msgstr ""
-msgid "Keyboard shortcuts"
+msgid "Keys"
+msgstr ""
+
+msgid "Ki"
msgstr ""
msgid "Kubernetes"
@@ -12716,6 +13072,9 @@ msgstr ""
msgid "LDAP"
msgstr ""
+msgid "LDAP Synchronization"
+msgstr ""
+
msgid "LDAP settings"
msgstr ""
@@ -12941,12 +13300,18 @@ msgstr ""
msgid "Learn more about custom project templates"
msgstr ""
+msgid "Learn more about deploying to AWS"
+msgstr ""
+
msgid "Learn more about deploying to a cluster"
msgstr ""
msgid "Learn more about group-level project templates"
msgstr ""
+msgid "Learn more about job dependencies"
+msgstr ""
+
msgid "Learn more about signing commits"
msgstr ""
@@ -12998,9 +13363,21 @@ msgstr ""
msgid "License History"
msgstr ""
+msgid "License ID:"
+msgstr ""
+
+msgid "License URL"
+msgstr ""
+
msgid "License-Check"
msgstr ""
+msgid "LicenseCompliance|%{docLinkStart}License Approvals%{docLinkEnd} are active"
+msgstr ""
+
+msgid "LicenseCompliance|%{docLinkStart}License Approvals%{docLinkEnd} are inactive"
+msgstr ""
+
msgid "LicenseCompliance|Add a license"
msgstr ""
@@ -13022,9 +13399,15 @@ msgstr ""
msgid "LicenseCompliance|Deny"
msgstr ""
+msgid "LicenseCompliance|Learn more about %{linkStart}License Approvals%{linkEnd}"
+msgstr ""
+
msgid "LicenseCompliance|License"
msgstr ""
+msgid "LicenseCompliance|License Approvals"
+msgstr ""
+
msgid "LicenseCompliance|License Compliance detected %d license and policy violation for the source branch only; approval required"
msgid_plural "LicenseCompliance|License Compliance detected %d licenses and policy violations for the source branch only; approval required"
msgstr[0] ""
@@ -13250,6 +13633,9 @@ msgstr ""
msgid "List your Bitbucket Server repositories"
msgstr ""
+msgid "Lists"
+msgstr ""
+
msgid "Live preview"
msgstr ""
@@ -13433,6 +13819,9 @@ msgstr ""
msgid "Manage project labels"
msgstr ""
+msgid "Manage storage usage"
+msgstr ""
+
msgid "Manage two-factor authentication"
msgstr ""
@@ -13448,6 +13837,9 @@ msgstr ""
msgid "Manifest file import"
msgstr ""
+msgid "Manifest import"
+msgstr ""
+
msgid "Manual job"
msgstr ""
@@ -13595,6 +13987,12 @@ msgstr ""
msgid "Maximum field length"
msgstr ""
+msgid "Maximum file size is 2MB. Please select a smaller file."
+msgstr ""
+
+msgid "Maximum import size (MB)"
+msgstr ""
+
msgid "Maximum job timeout"
msgstr ""
@@ -13634,6 +14032,9 @@ msgstr ""
msgid "Maximum size of Elasticsearch bulk indexing requests."
msgstr ""
+msgid "Maximum size of import files."
+msgstr ""
+
msgid "Maximum size of individual attachments in comments."
msgstr ""
@@ -13685,6 +14086,9 @@ msgstr ""
msgid "Merge (when the pipeline succeeds)"
msgstr ""
+msgid "Merge Conflicts"
+msgstr ""
+
msgid "Merge Request"
msgstr ""
@@ -13811,6 +14215,9 @@ msgstr ""
msgid "MergeRequests|Squash task canceled: another squash is already in progress."
msgstr ""
+msgid "MergeRequests|This project does not allow squashing commits when merge requests are accepted."
+msgstr ""
+
msgid "MergeRequests|Thread stays resolved"
msgstr ""
@@ -13964,6 +14371,12 @@ msgstr ""
msgid "MetricsSettings|Add a button to the metrics dashboard linking directly to your existing external dashboard."
msgstr ""
+msgid "MetricsSettings|Choose whether to display dashboard metrics in UTC or the user's local timezone."
+msgstr ""
+
+msgid "MetricsSettings|Dashboard timezone"
+msgstr ""
+
msgid "MetricsSettings|External dashboard URL"
msgstr ""
@@ -13973,6 +14386,12 @@ msgstr ""
msgid "MetricsSettings|Metrics Dashboard"
msgstr ""
+msgid "MetricsSettings|UTC (Coordinated Universal Time)"
+msgstr ""
+
+msgid "MetricsSettings|User's local timezone"
+msgstr ""
+
msgid "Metrics|Add metric"
msgstr ""
@@ -13988,6 +14407,9 @@ msgstr ""
msgid "Metrics|Create metric"
msgstr ""
+msgid "Metrics|Current"
+msgstr ""
+
msgid "Metrics|Delete metric"
msgstr ""
@@ -14039,6 +14461,9 @@ msgstr ""
msgid "Metrics|Max"
msgstr ""
+msgid "Metrics|Min"
+msgstr ""
+
msgid "Metrics|Must be a valid PromQL query."
msgstr ""
@@ -14054,6 +14479,9 @@ msgstr ""
msgid "Metrics|Refresh dashboard"
msgstr ""
+msgid "Metrics|Select a value"
+msgstr ""
+
msgid "Metrics|Star dashboard"
msgstr ""
@@ -14072,12 +14500,18 @@ msgstr ""
msgid "Metrics|There was an error getting annotations information."
msgstr ""
+msgid "Metrics|There was an error getting dashboard validation warnings information."
+msgstr ""
+
msgid "Metrics|There was an error getting deployment information."
msgstr ""
msgid "Metrics|There was an error getting environments information."
msgstr ""
+msgid "Metrics|There was an error getting options for variable \"%{name}\"."
+msgstr ""
+
msgid "Metrics|There was an error trying to validate your query"
msgstr ""
@@ -14120,6 +14554,9 @@ msgstr ""
msgid "Metrics|You're about to permanently delete this metric. This cannot be undone."
msgstr ""
+msgid "Metrics|Your dashboard schema is invalid. Edit the dashboard to correct the YAML schema."
+msgstr ""
+
msgid "Metrics|e.g. HTTP requests"
msgstr ""
@@ -14135,12 +14572,18 @@ msgstr ""
msgid "Metrics|e.g. req/sec"
msgstr ""
+msgid "Mi"
+msgstr ""
+
msgid "Microsoft Azure"
msgstr ""
msgid "Middleman project with Static Site Editor support"
msgstr ""
+msgid "Migrate your data from an external source like GitHub, Bitbucket, or another instance of GitLab."
+msgstr ""
+
msgid "Migrated %{success_count}/%{total_count} files."
msgstr ""
@@ -14276,6 +14719,9 @@ msgstr ""
msgid "Minimum capacity to be available before we schedule more mirrors preemptively."
msgstr ""
+msgid "Minimum interval in days"
+msgstr ""
+
msgid "Minimum length is %{minimum_password_length} characters"
msgstr ""
@@ -14462,6 +14908,12 @@ msgstr ""
msgid "Multiple uploaders found: %{uploader_types}"
msgstr ""
+msgid "Must match with the %{codeStart}external_url%{codeEnd} in %{codeStart}/etc/gitlab/gitlab.rb%{codeEnd}."
+msgstr ""
+
+msgid "Must match with the %{codeStart}geo_node_name%{codeEnd} in %{codeStart}/etc/gitlab/gitlab.rb%{codeEnd}. %{linkStart}More information%{linkEnd}"
+msgstr ""
+
msgid "My Awesome Group"
msgstr ""
@@ -14576,10 +15028,16 @@ msgstr ""
msgid "New Application"
msgstr ""
+msgid "New Branch"
+msgstr ""
+
+msgid "New Deploy Key"
+msgstr ""
+
msgid "New Environment"
msgstr ""
-msgid "New Geo Node"
+msgid "New File"
msgstr ""
msgid "New Group"
@@ -14597,15 +15055,15 @@ msgstr[0] ""
msgstr[1] ""
msgstr[2] ""
-msgid "New Iteration"
-msgstr ""
-
msgid "New Jira import"
msgstr ""
msgid "New Label"
msgstr ""
+msgid "New Merge Request"
+msgstr ""
+
msgid "New Milestone"
msgstr ""
@@ -14624,6 +15082,9 @@ msgstr ""
msgid "New Snippet"
msgstr ""
+msgid "New User"
+msgstr ""
+
msgid "New branch"
msgstr ""
@@ -14666,6 +15127,9 @@ msgstr ""
msgid "New issue title"
msgstr ""
+msgid "New iteration"
+msgstr ""
+
msgid "New iteration created"
msgstr ""
@@ -14843,6 +15307,12 @@ msgstr ""
msgid "No grouping"
msgstr ""
+msgid "No iteration"
+msgstr ""
+
+msgid "No iterations to show"
+msgstr ""
+
msgid "No job log"
msgstr ""
@@ -14885,6 +15355,9 @@ msgstr ""
msgid "No other labels with such name or description"
msgstr ""
+msgid "No panels matching properties %{opts}"
+msgstr ""
+
msgid "No parent group"
msgstr ""
@@ -14918,6 +15391,9 @@ msgstr ""
msgid "No schedules"
msgstr ""
+msgid "No stack trace for this error"
+msgstr ""
+
msgid "No starrers matched your search"
msgstr ""
@@ -14930,7 +15406,7 @@ msgstr ""
msgid "No test coverage"
msgstr ""
-msgid "No thanks, don't show this again"
+msgid "No thanks"
msgstr ""
msgid "No vulnerabilities present"
@@ -14945,9 +15421,6 @@ msgstr ""
msgid "No, directly import the existing email addresses and usernames."
msgstr ""
-msgid "No, not interested right now"
-msgstr ""
-
msgid "No. of commits"
msgstr ""
@@ -14993,9 +15466,6 @@ msgstr ""
msgid "Not found."
msgstr ""
-msgid "Not helpful"
-msgstr ""
-
msgid "Not now"
msgstr ""
@@ -15152,6 +15622,9 @@ msgstr ""
msgid "Notifications on"
msgstr ""
+msgid "Notify users by email when sign-in location is not recognized"
+msgstr ""
+
msgid "Nov"
msgstr ""
@@ -15218,9 +15691,6 @@ msgstr ""
msgid "Oh no!"
msgstr ""
-msgid "Ok let's go"
-msgstr ""
-
msgid "Oldest first"
msgstr ""
@@ -15233,16 +15703,49 @@ msgstr ""
msgid "On track"
msgstr ""
+msgid "OnDemandScans|Attached branch"
+msgstr ""
+
+msgid "OnDemandScans|Attached branch is where the scan job runs."
+msgstr ""
+
+msgid "OnDemandScans|Could not run the scan. Please try again."
+msgstr ""
+
msgid "OnDemandScans|Create new DAST scan"
msgstr ""
+msgid "OnDemandScans|DAST will scan the target URL and any discovered sub URLs."
+msgstr ""
+
+msgid "OnDemandScans|New on-demand DAST scan"
+msgstr ""
+
msgid "OnDemandScans|On-demand Scans"
msgstr ""
+msgid "OnDemandScans|On-demand scans run outside the DevOps cycle and find vulnerabilities in your projects. %{learnMoreLinkStart}Learn more%{learnMoreLinkEnd}"
+msgstr ""
+
+msgid "OnDemandScans|Only a passive scan can be performed on demand."
+msgstr ""
+
+msgid "OnDemandScans|Passive DAST Scan"
+msgstr ""
+
+msgid "OnDemandScans|Please enter a valid URL format, ex: http://www.example.com/home"
+msgstr ""
+
+msgid "OnDemandScans|Run this scan"
+msgstr ""
+
+msgid "OnDemandScans|Scan mode"
+msgstr ""
+
msgid "OnDemandScans|Schedule or run scans immediately against target sites. Currently available on-demand scan type: DAST. %{helpLinkStart}More information%{helpLinkEnd}"
msgstr ""
-msgid "Onboarding"
+msgid "OnDemandScans|Target URL"
msgstr ""
msgid "Once imported, repositories can be mirrored over SSH. Read more %{link_start}here%{link_end}."
@@ -15269,6 +15772,9 @@ msgstr ""
msgid "One or more of you personal access tokens were revoked"
msgstr ""
+msgid "One or more of your %{provider} projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
+msgstr ""
+
msgid "One or more of your Bitbucket projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
msgstr ""
@@ -15770,6 +16276,9 @@ msgstr ""
msgid "Parent epic is not present."
msgstr ""
+msgid "Parsing error for param :embed_json. %{message}"
+msgstr ""
+
msgid "Part of merge request changes"
msgstr ""
@@ -15782,6 +16291,9 @@ msgstr ""
msgid "Passed"
msgstr ""
+msgid "Passed on"
+msgstr ""
+
msgid "Password"
msgstr ""
@@ -15866,6 +16378,9 @@ msgstr ""
msgid "Perform common operations on GitLab project"
msgstr ""
+msgid "Performance and resource management"
+msgstr ""
+
msgid "Performance optimization"
msgstr ""
@@ -16073,7 +16588,7 @@ msgstr ""
msgid "Pipelines|Something went wrong while cleaning runners cache."
msgstr ""
-msgid "Pipelines|There are currently no %{scope} pipelines."
+msgid "Pipelines|There are currently no finished pipelines."
msgstr ""
msgid "Pipelines|There are currently no pipelines."
@@ -16184,6 +16699,9 @@ msgstr ""
msgid "Pipeline|Stop pipeline #%{pipelineId}?"
msgstr ""
+msgid "Pipeline|Tag name"
+msgstr ""
+
msgid "Pipeline|Trigger author"
msgstr ""
@@ -16265,12 +16783,18 @@ msgstr ""
msgid "Please check your email (%{email}) to verify that you own this address and unlock the power of CI/CD. Didn't receive it? %{resend_link}. Wrong email address? %{update_link}."
msgstr ""
+msgid "Please choose a file"
+msgstr ""
+
msgid "Please choose a group URL with no special characters."
msgstr ""
msgid "Please complete your profile with email address"
msgstr ""
+msgid "Please convert %{linkStart}them to Git%{linkEnd}, and go through the %{linkToImportFlow} again."
+msgstr ""
+
msgid "Please convert them to %{link_to_git}, and go through the %{link_to_import_flow} again."
msgstr ""
@@ -16328,6 +16852,9 @@ msgstr ""
msgid "Please provide a valid email address."
msgstr ""
+msgid "Please provide attributes to update"
+msgstr ""
+
msgid "Please refer to <a href=\"%{docs_url}\">%{docs_url}</a>"
msgstr ""
@@ -17063,6 +17590,9 @@ msgstr ""
msgid "Project has too many %{label_for_message} to search"
msgstr ""
+msgid "Project is required when cluster_type is :project"
+msgstr ""
+
msgid "Project members"
msgstr ""
@@ -17180,7 +17710,40 @@ msgstr ""
msgid "ProjectService|Comment"
msgstr ""
-msgid "ProjectService|Comment will be posted on each event"
+msgid "ProjectService|Event will be triggered by a push to the repository"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a commit is created/updated"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a confidential issue is created/updated/closed"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a deployment finishes"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a merge request is created/updated/merged"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a new tag is pushed to the repository"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a new, unique alert is recorded"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a pipeline status changes"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a wiki page is created/updated"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when an issue is created/updated/closed"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when someone adds a comment"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when someone adds a comment on a confidential issue"
msgstr ""
msgid "ProjectService|Perform common operations on GitLab project: %{project_name}"
@@ -17231,6 +17794,9 @@ msgstr ""
msgid "ProjectSettings|Enable 'Delete source branch' option by default"
msgstr ""
+msgid "ProjectSettings|Enable merge trains and pipelines for merged results"
+msgstr ""
+
msgid "ProjectSettings|Every merge creates a merge commit"
msgstr ""
@@ -17261,7 +17827,7 @@ msgstr ""
msgid "ProjectSettings|Forks"
msgstr ""
-msgid "ProjectSettings|Git Large File Storage"
+msgid "ProjectSettings|Git Large File Storage (LFS)"
msgstr ""
msgid "ProjectSettings|Internal"
@@ -17297,9 +17863,6 @@ msgstr ""
msgid "ProjectSettings|Merge options"
msgstr ""
-msgid "ProjectSettings|Merge pipelines will try to validate the post-merge result prior to merging"
-msgstr ""
-
msgid "ProjectSettings|Merge requests"
msgstr ""
@@ -17327,6 +17890,9 @@ msgstr ""
msgid "ProjectSettings|Pipelines"
msgstr ""
+msgid "ProjectSettings|Pipelines for merge requests must be enabled in the CI/CD configuration file, or pipelines could be unresolvable or dropped"
+msgstr ""
+
msgid "ProjectSettings|Pipelines must succeed"
msgstr ""
@@ -17354,6 +17920,9 @@ msgstr ""
msgid "ProjectSettings|Show link to create/view merge request when pushing from the command line"
msgstr ""
+msgid "ProjectSettings|Skipped pipelines are considered successful"
+msgstr ""
+
msgid "ProjectSettings|Snippets"
msgstr ""
@@ -17369,6 +17938,9 @@ msgstr ""
msgid "ProjectSettings|These checks must pass before merge requests can be merged"
msgstr ""
+msgid "ProjectSettings|This introduces the risk of merging changes that will not pass the pipeline."
+msgstr ""
+
msgid "ProjectSettings|This setting is applied on the server level and can be overridden by an admin."
msgstr ""
@@ -17546,15 +18118,27 @@ msgstr ""
msgid "ProjectsNew|Blank project"
msgstr ""
+msgid "ProjectsNew|Connect your external repository to GitLab CI/CD."
+msgstr ""
+
msgid "ProjectsNew|Contact an administrator to enable options for importing your project."
msgstr ""
msgid "ProjectsNew|Create"
msgstr ""
+msgid "ProjectsNew|Create a blank project to house your files, plan your work, and collaborate on code, among other things."
+msgstr ""
+
+msgid "ProjectsNew|Create blank project"
+msgstr ""
+
msgid "ProjectsNew|Create from template"
msgstr ""
+msgid "ProjectsNew|Create new project"
+msgstr ""
+
msgid "ProjectsNew|Creating project & repository."
msgstr ""
@@ -17579,6 +18163,9 @@ msgstr ""
msgid "ProjectsNew|Project description %{tag_start}(optional)%{tag_end}"
msgstr ""
+msgid "ProjectsNew|Run CI/CD for external repository"
+msgstr ""
+
msgid "ProjectsNew|Template"
msgstr ""
@@ -17840,6 +18427,9 @@ msgstr ""
msgid "Protected Branch"
msgstr ""
+msgid "Protected Branches"
+msgstr ""
+
msgid "Protected Environment"
msgstr ""
@@ -17852,6 +18442,9 @@ msgstr ""
msgid "Protected Tag"
msgstr ""
+msgid "Protected Tags"
+msgstr ""
+
msgid "Protected branches"
msgstr ""
@@ -17951,6 +18544,9 @@ msgstr ""
msgid "Provider"
msgstr ""
+msgid "Proxy support for this API is not available currently"
+msgstr ""
+
msgid "Pseudonymizer data collection"
msgstr ""
@@ -17975,6 +18571,9 @@ msgstr ""
msgid "Publish to status page"
msgstr ""
+msgid "Published on status page"
+msgstr ""
+
msgid "Publishes this issue to the associated status page."
msgstr ""
@@ -18149,6 +18748,9 @@ msgstr ""
msgid "Recipe"
msgstr ""
+msgid "Reconfigure"
+msgstr ""
+
msgid "Recover hidden stage"
msgstr ""
@@ -18236,6 +18838,9 @@ msgstr ""
msgid "Registration|Your profile"
msgstr ""
+msgid "Regulate approvals by authors/committers, based on compliance frameworks. Can be changed only at the instance level."
+msgstr ""
+
msgid "Rejected (closed)"
msgstr ""
@@ -18287,15 +18892,24 @@ msgstr ""
msgid "ReleaseAssetLinkType|Image"
msgstr ""
+msgid "ReleaseAssetLinkType|Images"
+msgstr ""
+
msgid "ReleaseAssetLinkType|Other"
msgstr ""
msgid "ReleaseAssetLinkType|Package"
msgstr ""
+msgid "ReleaseAssetLinkType|Packages"
+msgstr ""
+
msgid "ReleaseAssetLinkType|Runbook"
msgstr ""
+msgid "ReleaseAssetLinkType|Runbooks"
+msgstr ""
+
msgid "Releases"
msgstr ""
@@ -18308,6 +18922,9 @@ msgstr ""
msgid "Releases documentation"
msgstr ""
+msgid "Releases|New Release"
+msgstr ""
+
msgid "Release|Something went wrong while getting the release details"
msgstr ""
@@ -18380,6 +18997,9 @@ msgstr ""
msgid "Remove fork relationship"
msgstr ""
+msgid "Remove from batch"
+msgstr ""
+
msgid "Remove from board"
msgstr ""
@@ -18569,6 +19189,12 @@ msgstr ""
msgid "Replication"
msgstr ""
+msgid "Replication enabled"
+msgstr ""
+
+msgid "Replication paused"
+msgstr ""
+
msgid "Reply by email"
msgstr ""
@@ -18596,15 +19222,15 @@ msgstr ""
msgid "Reported %{timeAgo} by %{reportedBy}"
msgstr ""
-msgid "Reported Resource Changes: %{addNum} to add, %{changeNum} to change, %{deleteNum} to delete"
-msgstr ""
-
msgid "Reporter"
msgstr ""
msgid "Reporting"
msgstr ""
+msgid "Reports"
+msgstr ""
+
msgid "Reports|%{combinedString} and %{resolvedString}"
msgstr ""
@@ -18644,6 +19270,9 @@ msgstr ""
msgid "Reports|Failure"
msgstr ""
+msgid "Reports|Identifier"
+msgstr ""
+
msgid "Reports|Metrics reports are loading"
msgstr ""
@@ -18656,6 +19285,9 @@ msgstr ""
msgid "Reports|Metrics reports failed loading results"
msgstr ""
+msgid "Reports|Scanner"
+msgstr ""
+
msgid "Reports|Severity"
msgstr ""
@@ -18704,15 +19336,33 @@ msgstr ""
msgid "Repository cleanup has started. You will receive an email once the cleanup operation is complete."
msgstr ""
+msgid "Repository files count over the limit"
+msgstr ""
+
+msgid "Repository has an invalid default branch name."
+msgstr ""
+
+msgid "Repository has more than one branch."
+msgstr ""
+
msgid "Repository has no locks."
msgstr ""
+msgid "Repository has tags."
+msgstr ""
+
msgid "Repository maintenance"
msgstr ""
msgid "Repository mirroring"
msgstr ""
+msgid "Repository must contain at least 1 file."
+msgstr ""
+
+msgid "Repository size is above the limit."
+msgstr ""
+
msgid "Repository static objects"
msgstr ""
@@ -18722,7 +19372,7 @@ msgstr ""
msgid "Repository sync capacity"
msgstr ""
-msgid "Repository: %{counter_repositories} / Wikis: %{counter_wikis} / Build Artifacts: %{counter_build_artifacts} / LFS: %{counter_lfs_objects}"
+msgid "Repository: %{counter_repositories} / Wikis: %{counter_wikis} / Build Artifacts: %{counter_build_artifacts} / LFS: %{counter_lfs_objects} / Snippets: %{counter_snippets}"
msgstr ""
msgid "RepositorySettingsAccessLevel|Select"
@@ -18734,6 +19384,9 @@ msgstr ""
msgid "Request Headers"
msgstr ""
+msgid "Request details"
+msgstr ""
+
msgid "Request parameter %{param} is missing."
msgstr ""
@@ -18842,6 +19495,9 @@ msgstr ""
msgid "Reset template"
msgstr ""
+msgid "Reset to project defaults"
+msgstr ""
+
msgid "Resetting the authorization key for this project will require updating the authorization key in every alert source it is enabled in."
msgstr ""
@@ -18938,9 +19594,6 @@ msgstr ""
msgid "Resume"
msgstr ""
-msgid "Resume replication"
-msgstr ""
-
msgid "Resync"
msgstr ""
@@ -19574,6 +20227,9 @@ msgstr ""
msgid "SecurityReports|False positive"
msgstr ""
+msgid "SecurityReports|Group Security Dashboard"
+msgstr ""
+
msgid "SecurityReports|Hide dismissed"
msgstr ""
@@ -19628,10 +20284,13 @@ msgstr ""
msgid "SecurityReports|Remove project from dashboard"
msgstr ""
-msgid "SecurityReports|Report type"
+msgid "SecurityReports|Return to dashboard"
msgstr ""
-msgid "SecurityReports|Return to dashboard"
+msgid "SecurityReports|Scan details"
+msgstr ""
+
+msgid "SecurityReports|Scanner type"
msgstr ""
msgid "SecurityReports|Security Dashboard"
@@ -19688,6 +20347,9 @@ msgstr ""
msgid "SecurityReports|There was an error while generating the report."
msgstr ""
+msgid "SecurityReports|Unable to add %{invalidProjectsMessage}"
+msgstr ""
+
msgid "SecurityReports|Unable to add %{invalidProjects}"
msgstr ""
@@ -19802,6 +20464,9 @@ msgstr ""
msgid "Select due date"
msgstr ""
+msgid "Select file"
+msgstr ""
+
msgid "Select group or project"
msgstr ""
@@ -19865,9 +20530,6 @@ msgstr ""
msgid "Select the branch you want to set as the default for this project. All merge requests and commits will automatically be made against this branch unless you specify a different one."
msgstr ""
-msgid "Select the configured storage available for new repositories to be placed on."
-msgstr ""
-
msgid "Select the custom project template source group."
msgstr ""
@@ -20060,12 +20722,6 @@ msgstr ""
msgid "Service Desk is enabled but not yet active"
msgstr ""
-msgid "Service Desk is off"
-msgstr ""
-
-msgid "Service Desk is on"
-msgstr ""
-
msgid "Service Templates"
msgstr ""
@@ -20153,6 +20809,15 @@ msgstr ""
msgid "Set the milestone to %{milestone_reference}."
msgstr ""
+msgid "Set the number of concurrent requests this secondary node will make to the primary node while backfilling."
+msgstr ""
+
+msgid "Set the synchronization and verification capacity for the secondary node."
+msgstr ""
+
+msgid "Set the timeout in seconds to send a secondary node status to the primary and IPs allowed for the secondary nodes."
+msgstr ""
+
msgid "Set time estimate"
msgstr ""
@@ -20192,6 +20857,9 @@ msgstr ""
msgid "Set weight to %{weight}."
msgstr ""
+msgid "Set what should be replicated by choosing specific projects or groups by the secondary node."
+msgstr ""
+
msgid "SetPasswordToCloneLink|set a password"
msgstr ""
@@ -20318,6 +20986,9 @@ msgstr ""
msgid "Show complete raw log"
msgstr ""
+msgid "Show details"
+msgstr ""
+
msgid "Show file browser"
msgstr ""
@@ -20327,10 +20998,13 @@ msgstr ""
msgid "Show latest version"
msgstr ""
+msgid "Show list"
+msgstr ""
+
msgid "Show me everything"
msgstr ""
-msgid "Show me how"
+msgid "Show me how to add a pipeline"
msgstr ""
msgid "Show me more advanced stuff"
@@ -20381,9 +21055,6 @@ msgstr ""
msgid "Showing all issues"
msgstr ""
-msgid "Showing all labels"
-msgstr ""
-
msgid "Showing last %{size} of log -"
msgstr ""
@@ -20495,9 +21166,6 @@ msgstr ""
msgid "Skip outdated deployment jobs"
msgstr ""
-msgid "Skip this for now"
-msgstr ""
-
msgid "Skipped"
msgstr ""
@@ -20642,6 +21310,9 @@ msgstr ""
msgid "Something went wrong while adding your award. Please try again."
msgstr ""
+msgid "Something went wrong while applying the batch of suggestions. Please try again."
+msgstr ""
+
msgid "Something went wrong while applying the suggestion. Please try again."
msgstr ""
@@ -20744,10 +21415,13 @@ msgstr ""
msgid "Something went wrong while updating a requirement."
msgstr ""
+msgid "Something went wrong while updating assignees"
+msgstr ""
+
msgid "Something went wrong while updating your list settings"
msgstr ""
-msgid "Something went wrong with your automatic subscription renewal"
+msgid "Something went wrong with your automatic subscription renewal."
msgstr ""
msgid "Something went wrong, unable to add %{project} to dashboard"
@@ -20765,6 +21439,9 @@ msgstr ""
msgid "Something went wrong, unable to search projects"
msgstr ""
+msgid "Something went wrong."
+msgstr ""
+
msgid "Something went wrong. Please try again."
msgstr ""
@@ -20948,6 +21625,9 @@ msgstr ""
msgid "Source code"
msgstr ""
+msgid "Source code (%{fileExtension})"
+msgstr ""
+
msgid "Source is not available"
msgstr ""
@@ -21032,6 +21712,9 @@ msgstr ""
msgid "Stack trace"
msgstr ""
+msgid "Stacktrace snippet"
+msgstr ""
+
msgid "Stage"
msgstr ""
@@ -21137,6 +21820,9 @@ msgstr ""
msgid "Start thread & reopen %{noteable_name}"
msgstr ""
+msgid "Start using Directed Acyclic Graphs (DAG)"
+msgstr ""
+
msgid "Start your Free Gold Trial"
msgstr ""
@@ -21599,6 +22285,21 @@ msgstr ""
msgid "SuggestedColors|Very pale orange"
msgstr ""
+msgid "Suggestion is not applicable as the suggestion was not found."
+msgstr ""
+
+msgid "Suggestions are not applicable as one or more suggestions were not found."
+msgstr ""
+
+msgid "Suggestions are not applicable as their lines cannot overlap."
+msgstr ""
+
+msgid "Suggestions must all be on the same branch."
+msgstr ""
+
+msgid "Suggestions that change line count can't be added to batches, yet."
+msgstr ""
+
msgid "Suggestions:"
msgstr ""
@@ -21641,9 +22342,6 @@ msgstr ""
msgid "Synchronization disabled"
msgstr ""
-msgid "Synchronization of container repositories is disabled."
-msgstr ""
-
msgid "System"
msgstr ""
@@ -21824,6 +22522,36 @@ msgstr ""
msgid "Terms of Service and Privacy Policy"
msgstr ""
+msgid "Terraform|%{number} Terraform report failed to generate"
+msgid_plural "Terraform|%{number} Terraform reports failed to generate"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+
+msgid "Terraform|%{number} Terraform report was generated in your pipelines"
+msgid_plural "Terraform|%{number} Terraform reports were generated in your pipelines"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+
+msgid "Terraform|A Terraform report failed to generate."
+msgstr ""
+
+msgid "Terraform|A Terraform report was generated in your pipelines."
+msgstr ""
+
+msgid "Terraform|Generating the report caused an error."
+msgstr ""
+
+msgid "Terraform|Reported Resource Changes: %{addNum} to add, %{changeNum} to change, %{deleteNum} to delete"
+msgstr ""
+
+msgid "Terraform|The Terraform report %{name} failed to generate."
+msgstr ""
+
+msgid "Terraform|The Terraform report %{name} was generated in your pipelines."
+msgstr ""
+
msgid "Test"
msgstr ""
@@ -21851,7 +22579,7 @@ msgstr ""
msgid "TestHooks|Ensure the project has CI pipelines."
msgstr ""
-msgid "TestHooks|Ensure the project has at least one commit."
+msgid "TestHooks|Ensure the project has deployments."
msgstr ""
msgid "TestHooks|Ensure the project has issues."
@@ -21953,7 +22681,7 @@ msgstr ""
msgid "The Prometheus server responded with \"bad request\". Please check your queries are correct and are supported in your Prometheus version. %{documentationLink}"
msgstr ""
-msgid "The URL defined on the primary node that secondary nodes should use to contact it. Defaults to URL"
+msgid "The URL defined on the primary node that secondary nodes should use to contact it."
msgstr ""
msgid "The URL to use for connecting to Elasticsearch. Use a comma-separated list to support clustering (e.g., \"http://localhost:9200, http://localhost:9201\")."
@@ -21962,6 +22690,9 @@ msgstr ""
msgid "The X509 Certificate to use when mutual TLS is required to communicate with the external authorization service. If left blank, the server certificate is still validated when accessing over HTTPS."
msgstr ""
+msgid "The above settings apply to all projects with the selected compliance framework(s)."
+msgstr ""
+
msgid "The amount of seconds after which a request to get a secondary node status will time out."
msgstr ""
@@ -22262,9 +22993,6 @@ msgstr ""
msgid "The total stage shows the time it takes between creating an issue and deploying the code to production. The data will be automatically added once you have completed the full idea to production cycle."
msgstr ""
-msgid "The unique identifier for the Geo node. Must match %{geoNodeName} if it is set in gitlab.rb, otherwise it must match %{externalUrl} with a trailing slash"
-msgstr ""
-
msgid "The update action will time out after %{number_of_minutes} minutes. For big repositories, use a clone/push combination."
msgstr ""
@@ -22289,9 +23017,6 @@ msgstr ""
msgid "The user you are trying to deactivate has been active in the past %{minimum_inactive_days} days and cannot be deactivated"
msgstr ""
-msgid "The user-facing URL of the Geo node"
-msgstr ""
-
msgid "The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6."
msgstr ""
@@ -22382,6 +23107,9 @@ msgstr ""
msgid "There was a problem fetching project branches."
msgstr ""
+msgid "There was a problem fetching project tags."
+msgstr ""
+
msgid "There was a problem fetching project users."
msgstr ""
@@ -22433,6 +23161,9 @@ msgstr ""
msgid "There was an error fetching the %{replicableType}"
msgstr ""
+msgid "There was an error fetching the Geo Settings"
+msgstr ""
+
msgid "There was an error fetching the Node's Groups"
msgstr ""
@@ -22553,6 +23284,12 @@ msgstr ""
msgid "This %{issuable} is locked. Only <strong>project members</strong> can comment."
msgstr ""
+msgid "This %{noteableTypeText} is %{confidentialLinkStart}confidential%{linkEnd} and %{lockedLinkStart}locked%{linkEnd}."
+msgstr ""
+
+msgid "This %{noteableTypeText} is locked."
+msgstr ""
+
msgid "This %{viewer} could not be displayed because %{reason}. You can %{options} instead."
msgstr ""
@@ -22571,6 +23308,9 @@ msgstr ""
msgid "This action can lead to data loss. To prevent accidental actions we ask you to confirm your intention."
msgstr ""
+msgid "This also resolves all related threads"
+msgstr ""
+
msgid "This also resolves the discussion"
msgstr ""
@@ -22661,6 +23401,9 @@ msgstr ""
msgid "This epic does not exist or you don't have sufficient permission."
msgstr ""
+msgid "This feature is currently in beta. We invite you to %{linkStart}give feedback%{linkEnd}."
+msgstr ""
+
msgid "This feature requires local storage to be enabled"
msgstr ""
@@ -22694,7 +23437,7 @@ msgstr ""
msgid "This is a Work in Progress"
msgstr ""
-msgid "This is a confidential issue."
+msgid "This is a confidential %{noteableTypeText}."
msgstr ""
msgid "This is a delayed job to run in %{remainingTime}"
@@ -22721,9 +23464,6 @@ msgstr ""
msgid "This is your current session"
msgstr ""
-msgid "This issue is %{confidentialLinkStart}confidential%{linkEnd} and %{lockedLinkStart}locked%{linkEnd}."
-msgstr ""
-
msgid "This issue is confidential"
msgstr ""
@@ -22733,9 +23473,6 @@ msgstr ""
msgid "This issue is in a child epic of the filtered epic"
msgstr ""
-msgid "This issue is locked."
-msgstr ""
-
msgid "This job depends on other jobs with expired/erased artifacts: %{invalid_dependencies}"
msgstr ""
@@ -22835,6 +23572,9 @@ msgstr ""
msgid "This license has already expired."
msgstr ""
+msgid "This link points to external content"
+msgstr ""
+
msgid "This may expose confidential information as the selected fork is in another namespace that can have other members."
msgstr ""
@@ -22862,6 +23602,9 @@ msgstr ""
msgid "This page will be removed in a future release."
msgstr ""
+msgid "This pipeline does not use the %{codeStart}needs%{codeEnd} keyword and can't be represented as a directed acyclic graph."
+msgstr ""
+
msgid "This pipeline makes use of a predefined CI/CD configuration enabled by %{strongStart}Auto DevOps.%{strongEnd}"
msgstr ""
@@ -23090,6 +23833,9 @@ msgstr ""
msgid "Time from last commit to merge"
msgstr ""
+msgid "Time in seconds"
+msgstr ""
+
msgid "Time in seconds GitLab will wait for a response from the external service. When the service does not respond in time, access will be denied."
msgstr ""
@@ -23513,9 +24259,15 @@ msgstr ""
msgid "Total artifacts size: %{total_size}"
msgstr ""
+msgid "Total cores (CPUs)"
+msgstr ""
+
msgid "Total issues"
msgstr ""
+msgid "Total memory (GB)"
+msgstr ""
+
msgid "Total test time for all commits/merges"
msgstr ""
@@ -23717,6 +24469,9 @@ msgstr ""
msgid "U2F only works with HTTPS-enabled websites. Contact your administrator for more details."
msgstr ""
+msgid "UI Development Kit"
+msgstr ""
+
msgid "URL"
msgstr ""
@@ -23789,9 +24544,6 @@ msgstr ""
msgid "Unable to load the merge request widget. Try reloading the page."
msgstr ""
-msgid "Unable to process group import file"
-msgstr ""
-
msgid "Unable to resolve"
msgstr ""
@@ -23807,6 +24559,9 @@ msgstr ""
msgid "Unable to sign you in to the group with SAML due to \"%{reason}\""
msgstr ""
+msgid "Unable to suggest a path. Please refresh and try again."
+msgstr ""
+
msgid "Unable to update label prioritization at this time"
msgstr ""
@@ -23822,6 +24577,9 @@ msgstr ""
msgid "Unarchiving the project will restore people's ability to make changes to it. The repository can be committed to, and issues, comments, and other entities can be created. %{strong_start}Once active, this project shows up in the search and on the dashboard.%{strong_end}"
msgstr ""
+msgid "Unassign from commenting user"
+msgstr ""
+
msgid "Unblock"
msgstr ""
@@ -23897,6 +24655,9 @@ msgstr ""
msgid "Unreachable"
msgstr ""
+msgid "Unrecognized cluster type"
+msgstr ""
+
msgid "Unresolve"
msgstr ""
@@ -23957,6 +24718,9 @@ msgstr ""
msgid "Unsubscribes from this %{quick_action_target}."
msgstr ""
+msgid "Unsupported todo type passed. Supported todo types are: %{todo_types}"
+msgstr ""
+
msgid "Until"
msgstr ""
@@ -23984,6 +24748,9 @@ msgstr ""
msgid "Update approval rule"
msgstr ""
+msgid "Update approvers"
+msgstr ""
+
msgid "Update failed"
msgstr ""
@@ -23993,6 +24760,9 @@ msgstr ""
msgid "Update it"
msgstr ""
+msgid "Update iteration"
+msgstr ""
+
msgid "Update now"
msgstr ""
@@ -24038,7 +24808,7 @@ msgstr ""
msgid "Updated at"
msgstr ""
-msgid "Updated to"
+msgid "Updated to %{linkStart}chart v%{linkEnd}"
msgstr ""
msgid "Updating"
@@ -24068,6 +24838,9 @@ msgstr ""
msgid "Upload CSV file"
msgstr ""
+msgid "Upload License"
+msgstr ""
+
msgid "Upload New File"
msgstr ""
@@ -24149,16 +24922,19 @@ msgstr ""
msgid "UsageQuota|Pipelines"
msgstr ""
+msgid "UsageQuota|Purchase more storage"
+msgstr ""
+
msgid "UsageQuota|Repositories"
msgstr ""
msgid "UsageQuota|Repository"
msgstr ""
-msgid "UsageQuota|Storage"
+msgid "UsageQuota|Snippets"
msgstr ""
-msgid "UsageQuota|Storage usage:"
+msgid "UsageQuota|Storage"
msgstr ""
msgid "UsageQuota|This namespace has no projects which use shared runners"
@@ -24191,6 +24967,12 @@ msgstr ""
msgid "UsageQuota|Wikis"
msgstr ""
+msgid "UsageQuota|You used: %{usage} %{limit}"
+msgstr ""
+
+msgid "UsageQuota|out of %{formattedLimit} of your namespace storage"
+msgstr ""
+
msgid "Use %{code_start}::%{code_end} to create a %{link_start}scoped label set%{link_end} (eg. %{code_start}priority::1%{code_end})"
msgstr ""
@@ -24257,6 +25039,9 @@ msgstr ""
msgid "User IDs"
msgstr ""
+msgid "User Lists can only be created and modified with %{linkStart}the API%{linkEnd}"
+msgstr ""
+
msgid "User OAuth applications"
msgstr ""
@@ -24281,6 +25066,9 @@ msgstr ""
msgid "User key was successfully removed."
msgstr ""
+msgid "User list %{name} will be removed. Are you sure?"
+msgstr ""
+
msgid "User map"
msgstr ""
@@ -24302,148 +25090,10 @@ msgstr ""
msgid "User was successfully updated."
msgstr ""
-msgid "UserOnboardingTour|%{activeTour}/%{totalTours}"
-msgstr ""
-
-msgid "UserOnboardingTour|%{completed}/%{total} steps completed"
-msgstr ""
-
-msgid "UserOnboardingTour|%{emphasisStart}Well done!%{emphasisEnd}%{lineBreak}%{lineBreak}That's it for our guided tour, congratulations for making it all the way to the end!%{lineBreak}%{lineBreak}We hope this gave you a good overview of GitLab and how it can help you. We'll now show you how to create your own project and invite your colleagues."
-msgstr ""
-
-msgid "UserOnboardingTour|Adding other members to a project is done through Project Settings. Click on %{emphasisStart}Settings%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Alright, that's it for Commits. Let's take a look at the %{emphasisStart}Branches%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Awesome! Now click on %{emphasisStart}Members%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Click on one of the %{emphasisStart}Compare%{emphasisEnd} buttons to compare a branch to master."
-msgstr ""
-
-msgid "UserOnboardingTour|Click on one of the %{emphasisStart}pipeline IDs%{emphasisEnd} to see the details of a pipeline."
-msgstr ""
-
-msgid "UserOnboardingTour|Click to open the latest commit to see its details."
-msgstr ""
-
-msgid "UserOnboardingTour|Close 'Learn GitLab'"
-msgstr ""
-
-msgid "UserOnboardingTour|Commits are shown in chronological order and can be filtered by the commit message or by the branch."
-msgstr ""
-
-msgid "UserOnboardingTour|Create a project"
-msgstr ""
-
-msgid "UserOnboardingTour|Exit 'Learn GitLab'"
-msgstr ""
-
-msgid "UserOnboardingTour|Got it"
-msgstr ""
-
-msgid "UserOnboardingTour|Great job! %{clapHands} We hope the tour was helpful and that you learned how to use GitLab.%{lineBreak}%{lineBreak}We'd love to get your feedback on this tour.%{lineBreak}%{lineBreak}%{emphasisStart}How helpful would you say this guided tour was?%{emphasisEnd}%{lineBreak}%{lineBreak}"
-msgstr ""
-
-msgid "UserOnboardingTour|Guided GitLab Tour"
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can compare the changes of this branch to another one. Changes are divided by files so that it's easier to see what was changed where."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can create a project from scratch, start with a template or import a repository from other platforms. Whatever you choose, we'll guide you through the process.%{lineBreak}%{lineBreak}Fill in your new project information and click on %{emphasisStart}Create Project%{emphasisEnd} to progress to the next step."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can see the breakdown of the pipelines: its stages and jobs in each of the stages and their status.%{lineBreak}%{lineBreak}Our CI/CD pipelines are quite complex, most of our users have fewer and simpler pipelines."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can see the current members of the project (just you at the moment) and invite new members.%{lineBreak}%{lineBreak}You can invite multiple members at once (existing GitLab users or invite by email) and you can also set their roles and permissions.%{lineBreak}%{lineBreak}Add a few members and click on %{emphasisStart}Add to project%{emphasisEnd} to complete this step."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can see what changes were made with this commit, on what branch and if there's a related merge request. The status of the pipeline will also show up if CI/CD is set up.%{lineBreak}%{lineBreak}You can also comment on the lines of code that were changed and start a discussion with your colleagues!"
-msgstr ""
-
-msgid "UserOnboardingTour|Here's an overview of branches in the %{emphasisStart}%{projectName}%{emphasisEnd} project. They're split into Active and Stale.%{lineBreak}%{lineBreak}From here, you can create a new merge request from a branch, or compare the branch to any other branch in the project. By default, it will compare it to the master branch."
-msgstr ""
-
-msgid "UserOnboardingTour|Invite colleagues"
-msgstr ""
-
-msgid "UserOnboardingTour|Issues are great for communicating and keeping track of progress in GitLab. These are all issues that are open in the %{emphasisStart}%{projectName}%{emphasisEnd}.%{lineBreak}%{lineBreak}You can help us improve GitLab by contributing work to issues that are labeled <span class=\"badge color-label accept-mr-label\">Accepting merge requests</span>.%{lineBreak}%{lineBreak}This list can be filtered by labels, milestones, assignees, authors... We'll show you how it looks when the list is filtered by a label."
-msgstr ""
-
-msgid "UserOnboardingTour|Learn GitLab"
-msgstr ""
-
-msgid "UserOnboardingTour|Let's take a closer look at a merge request. Click on the title of one."
-msgstr ""
-
-msgid "UserOnboardingTour|Let's take a closer look at all the commits. Click on %{emphasisStart}Commits%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Let's take a closer look at the repository of this project. Click on %{emphasisStart}Repository%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|No thanks"
-msgstr ""
-
-msgid "UserOnboardingTour|Ok, let's go"
-msgstr ""
-
-msgid "UserOnboardingTour|Ok, show me"
-msgstr ""
-
-msgid "UserOnboardingTour|Open one of the issues by clicking on its title."
-msgstr ""
-
-msgid "UserOnboardingTour|Restart this step"
-msgstr ""
-
-msgid "UserOnboardingTour|Skip this step"
-msgstr ""
-
-msgid "UserOnboardingTour|Sweet! Your project was created and is ready to be used.%{lineBreak}%{lineBreak}You can start adding files to the repository or clone it. One last thing we want to show you is how to invite your colleagues to your new project."
-msgstr ""
-
-msgid "UserOnboardingTour|Take a look. Here's a nifty menu for quickly creating issues, merge requests, snippets, projects and groups. Click on it and select \"New project\" from the \"GitLab\" section to get started."
-msgstr ""
-
-msgid "UserOnboardingTour|Thanks for taking the guided tour. Remember, if you want to go through it again, you can start %{emphasisStart}Learn GitLab%{emphasisEnd} in the help menu on the top right."
-msgstr ""
-
-msgid "UserOnboardingTour|Thanks for the feedback! %{thumbsUp}"
-msgstr ""
-
-msgid "UserOnboardingTour|That's it for issues. Let'st take a look at %{emphasisStart}Merge Requests%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|That's it for merge requests. Now for the final part of this guided tour - the %{emphasisStart}CI/CD%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|That's it for the Repository. Let's take a look at the %{emphasisStart}Issues%{emphasisEnd}."
+msgid "UserList|Delete %{name}?"
msgstr ""
-msgid "UserOnboardingTour|The structure of this page is very similar to issues. Status, description, discussion and the sidebar are all here.%{lineBreak}%{lineBreak}But take a look below the description and you'll notice that there's more information about the merge request, the CI/CD pipeline and the options for approving it.%{lineBreak}%{lineBreak}Alongside the discussion you can also see more information about commits in this merge request, the status of pipelines and review all changes that were made."
-msgstr ""
-
-msgid "UserOnboardingTour|There's a lot of information here but don't worry, we'll go through it.%{lineBreak}%{lineBreak}On the top you can see the status of the issue and when it was opened and by whom. Directly below it is the issue description and below that are other %{emphasisStart}related issues%{emphasisEnd} and %{emphasisStart}merge requests%{emphasisEnd} (if any). Then below that is the %{emphasisStart}discussion%{emphasisEnd}, that's where most of the communication happens.%{lineBreak}%{lineBreak}On the right, there's a sidebar where you can view/change the %{emphasisStart}assignee, milestone, due date, labels, weight%{emphasisEnd}, etc."
-msgstr ""
-
-msgid "UserOnboardingTour|These are all the CI/CD pipelines we have for our %{emphasisStart}%{projectName}%{emphasisEnd} project.%{lineBreak}%{lineBreak}Here you can see the status of each pipeline, for what commit it's running for, its stages and the status for them."
-msgstr ""
-
-msgid "UserOnboardingTour|These are all the issues that are available for community contributions. Let's take a closer look at one of them."
-msgstr ""
-
-msgid "UserOnboardingTour|This is an overview of all merge requests in this project. Similarly to the issues overview it can be filtered down by things like labels, milestones, authors, assignees, etc."
-msgstr ""
-
-msgid "UserOnboardingTour|This is the repository for the %{emphasisStart}%{projectName}%{emphasisEnd} project. All our code is stored here. Feel free to explore and take a closer look at folders and files.%{lineBreak}%{lineBreak}Above the file structure you can see the latest commit, who the author is and the status of the CI/CD pipeline.%{lineBreak}%{lineBreak}If you scroll down below the file structure, you'll find the Readme of this project. This is defined in the README.md file at the root of the repository."
-msgstr ""
-
-msgid "UserOnboardingTour|Welcome to the project overview of the %{emphasisStart}%{projectName}%{emphasisEnd} project. This is the project that we use to work on GitLab. At first, a project seems like a simple repository, but at GitLab, a project is so much more.%{lineBreak}%{lineBreak}You can create projects for hosting your codebase, use it as an issue tracker, collaborate on code, and continuously build, test, and deploy your app with built-in GitLab CI/CD."
+msgid "UserList|created %{timeago}"
msgstr ""
msgid "UserProfile|Activity"
@@ -24596,6 +25246,9 @@ msgstr ""
msgid "UsersSelect|Unassigned"
msgstr ""
+msgid "Using %{codeStart}needs%{codeEnd} allows jobs to run before their stage is reached, as soon as their individual dependencies are met, which speeds up your pipelines."
+msgstr ""
+
msgid "Using %{code_start}::%{code_end} denotes a %{link_start}scoped label set%{link_end}"
msgstr ""
@@ -24680,9 +25333,6 @@ msgstr ""
msgid "Versions"
msgstr ""
-msgid "Very helpful"
-msgstr ""
-
msgid "View Documentation"
msgstr ""
@@ -24833,6 +25483,9 @@ msgstr ""
msgid "VisibilityLevel|Unknown"
msgstr ""
+msgid "Visit settings page"
+msgstr ""
+
msgid "VisualReviewApp|%{stepStart}Step 1%{stepEnd}. Copy the following script:"
msgstr ""
@@ -24926,6 +25579,9 @@ msgstr ""
msgid "VulnerabilityManagement|Something went wrong while trying to delete the comment. Please try again later."
msgstr ""
+msgid "VulnerabilityManagement|Something went wrong while trying to refresh the vulnerability. Please try again later."
+msgstr ""
+
msgid "VulnerabilityManagement|Something went wrong while trying to retrieve the vulnerability history. Please try again later."
msgstr ""
@@ -24968,6 +25624,9 @@ msgstr ""
msgid "Vulnerability|Class"
msgstr ""
+msgid "Vulnerability|Crash Address"
+msgstr ""
+
msgid "Vulnerability|Description"
msgstr ""
@@ -25043,9 +25702,6 @@ msgstr ""
msgid "We couldn't reach the Prometheus server. Either the server no longer exists or the configuration details need updating."
msgstr ""
-msgid "We created a short guided tour that will help you learn the basics of GitLab and how it will help you be better at your job. It should only take a couple of minutes. You will be guided by two types of helpers, best recognized by their color."
-msgstr ""
-
msgid "We detected potential spam in the %{humanized_resource_name}. Please solve the reCAPTCHA to proceed."
msgstr ""
@@ -25079,6 +25735,9 @@ msgstr ""
msgid "We've found no vulnerabilities"
msgstr ""
+msgid "Web Application Firewall"
+msgstr ""
+
msgid "Web IDE"
msgstr ""
@@ -25139,9 +25798,6 @@ msgstr ""
msgid "Welcome to GitLab.com<br>@%{name}!"
msgstr ""
-msgid "Welcome to the Guided GitLab Tour"
-msgstr ""
-
msgid "Welcome to the guided GitLab tour"
msgstr ""
@@ -25190,9 +25846,6 @@ msgstr ""
msgid "While it's rare to have no vulnerabilities, it can happen. In any event, we ask that you please double check your settings to make sure you've set up your dashboard correctly."
msgstr ""
-msgid "White helpers give contextual information."
-msgstr ""
-
msgid "Who can be an approver?"
msgstr ""
@@ -25238,12 +25891,18 @@ msgstr ""
msgid "WikiEmptyIssueMessage|Suggest wiki improvement"
msgstr ""
+msgid "WikiEmptyIssueMessage|You must be a group member in order to add wiki pages. If you have suggestions for how to improve the wiki for this group, consider opening an issue in the %{issues_link}."
+msgstr ""
+
msgid "WikiEmptyIssueMessage|You must be a project member in order to add wiki pages. If you have suggestions for how to improve the wiki for this project, consider opening an issue in the %{issues_link}."
msgstr ""
msgid "WikiEmptyIssueMessage|issue tracker"
msgstr ""
+msgid "WikiEmpty|A wiki is where you can store all the details about your group. This can include why you've created it, its principles, how to use it, and so on."
+msgstr ""
+
msgid "WikiEmpty|A wiki is where you can store all the details about your project. This can include why you've created it, its principles, how to use it, and so on."
msgstr ""
@@ -25253,12 +25912,21 @@ msgstr ""
msgid "WikiEmpty|Suggest wiki improvement"
msgstr ""
+msgid "WikiEmpty|The wiki lets you write documentation for your group"
+msgstr ""
+
msgid "WikiEmpty|The wiki lets you write documentation for your project"
msgstr ""
+msgid "WikiEmpty|This group has no wiki pages"
+msgstr ""
+
msgid "WikiEmpty|This project has no wiki pages"
msgstr ""
+msgid "WikiEmpty|You must be a group member in order to add wiki pages."
+msgstr ""
+
msgid "WikiEmpty|You must be a project member in order to add wiki pages."
msgstr ""
@@ -25535,13 +26203,13 @@ msgstr ""
msgid "You can now export your security dashboard to a CSV report."
msgstr ""
-msgid "You can now submit a merge request to get this change into the original branch."
+msgid "You can now manage alert endpoint configuration in the Alerts section on the Operations settings page. Fields on this page have been deprecated."
msgstr ""
-msgid "You can now submit a merge request to get this change into the original project."
+msgid "You can now submit a merge request to get this change into the original branch."
msgstr ""
-msgid "You can only add files when you are on a branch"
+msgid "You can now submit a merge request to get this change into the original project."
msgstr ""
msgid "You can only edit files when you are on a branch"
@@ -25763,7 +26431,7 @@ msgstr ""
msgid "You need to upload a Google Takeout archive."
msgstr ""
-msgid "You reached %{usage_in_percent} of %{namespace_name}'s capacity (%{used_storage} of %{storage_limit})"
+msgid "You reached %{usage_in_percent} of %{namespace_name}'s storage capacity (%{used_storage} of %{storage_limit})"
msgstr ""
msgid "You tried to fork %{link_to_the_project} but it failed for the following reason:"
@@ -26066,13 +26734,13 @@ msgstr ""
msgid "Your subscription expired!"
msgstr ""
-msgid "Your subscription has been downgraded"
+msgid "Your subscription has been downgraded."
msgstr ""
-msgid "Your subscription will automatically renew in %{remaining_days}"
+msgid "Your subscription will automatically renew in %{remaining_days}."
msgstr ""
-msgid "Your subscription will expire in %{remaining_days}"
+msgid "Your subscription will expire in %{remaining_days}."
msgstr ""
msgid "Zoom meeting added"
@@ -26153,9 +26821,6 @@ msgstr ""
msgid "branch name"
msgstr ""
-msgid "build pipeline reference mismatch"
-msgstr ""
-
msgid "by"
msgstr ""
@@ -26174,6 +26839,9 @@ msgstr ""
msgid "cannot block others"
msgstr ""
+msgid "cannot contain HTML/XML tags, including any word between angle brackets (<,>)."
+msgstr ""
+
msgid "cannot include leading slash or directory traversal."
msgstr ""
@@ -26204,60 +26872,6 @@ msgstr ""
msgid "ciReport|%{remainingPackagesCount} more"
msgstr ""
-msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability"
-msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities"
-msgstr[0] ""
-msgstr[1] ""
-msgstr[2] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability for the source branch only"
-msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities for the source branch only"
-msgstr[0] ""
-msgstr[1] ""
-msgstr[2] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerability"
-msgid_plural "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerabilities"
-msgstr[0] ""
-msgstr[1] ""
-msgstr[2] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new vulnerability"
-msgid_plural "ciReport|%{reportType} %{status} detected %{newCount} new vulnerabilities"
-msgstr[0] ""
-msgstr[1] ""
-msgstr[2] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities for the source branch only"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{fixedCount} fixed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} vulnerability for the source branch only"
-msgid_plural "ciReport|%{reportType} %{status} detected %{newCount} vulnerabilities for the source branch only"
-msgstr[0] ""
-msgstr[1] ""
-msgstr[2] ""
-
-msgid "ciReport|%{reportType} %{status} detected no new vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected no vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected no vulnerabilities for the source branch only"
-msgstr ""
-
msgid "ciReport|%{reportType} is loading"
msgstr ""
@@ -26276,7 +26890,7 @@ msgstr ""
msgid "ciReport|All projects"
msgstr ""
-msgid "ciReport|All report types"
+msgid "ciReport|All scanner types"
msgstr ""
msgid "ciReport|All severities"
@@ -26300,6 +26914,9 @@ msgstr ""
msgid "ciReport|Container scanning detects known vulnerabilities in your docker images."
msgstr ""
+msgid "ciReport|Coverage Fuzzing"
+msgstr ""
+
msgid "ciReport|Create a merge request to implement this solution, or download and apply the patch manually."
msgstr ""
@@ -26459,6 +27076,9 @@ msgstr ""
msgid "customize"
msgstr ""
+msgid "data"
+msgstr ""
+
msgid "date must not be after 9999-12-31"
msgstr ""
@@ -26531,6 +27151,9 @@ msgstr ""
msgid "entries cannot contain HTML tags"
msgstr ""
+msgid "epic"
+msgstr ""
+
msgid "error"
msgstr ""
@@ -26552,9 +27175,6 @@ msgstr ""
msgid "expires on %{timebox_due_date}"
msgstr ""
-msgid "external_url"
-msgstr ""
-
msgid "failed"
msgstr ""
@@ -26591,9 +27211,6 @@ msgstr ""
msgid "from"
msgstr ""
-msgid "geo_node_name"
-msgstr ""
-
msgid "group"
msgstr ""
@@ -26615,9 +27232,6 @@ msgstr ""
msgid "https://your-bitbucket-server"
msgstr ""
-msgid "image"
-msgstr ""
-
msgid "image diff"
msgstr ""
@@ -26762,6 +27376,9 @@ msgstr ""
msgid "load it anyway"
msgstr ""
+msgid "loading"
+msgstr ""
+
msgid "locked by %{path_lock_user_name} %{created_at}"
msgstr ""
@@ -26891,9 +27508,6 @@ msgstr ""
msgid "mrWidget|Deployment statistics are not available currently"
msgstr ""
-msgid "mrWidget|Detect issues before deployment with a CI pipeline that continuously tests your code. We created a quick guide that will show you how to create one. Make your code more secure and more robust in just a minute."
-msgstr ""
-
msgid "mrWidget|Did not close"
msgstr ""
@@ -27056,6 +27670,9 @@ msgstr ""
msgid "mrWidget|To approve this merge request, please enter your password. This project requires all approvals to be authenticated."
msgstr ""
+msgid "mrWidget|Use %{linkStart}CI pipelines to test your code%{linkEnd}, simply add a GitLab CI configuration file to your project. It only takes a minute to make your code more secure and robust."
+msgstr ""
+
msgid "mrWidget|When this merge request is ready, remove the WIP: prefix from the title to allow it to be merged"
msgstr ""
@@ -27092,6 +27709,9 @@ msgstr ""
msgid "must be greater than start date"
msgstr ""
+msgid "must contain only valid frameworks"
+msgstr ""
+
msgid "my-awesome-group"
msgstr ""
@@ -27104,6 +27724,9 @@ msgstr ""
msgid "needs to be between 10 minutes and 1 month"
msgstr ""
+msgid "never"
+msgstr ""
+
msgid "never expires"
msgstr ""
@@ -27131,6 +27754,9 @@ msgstr ""
msgid "nounSeries|%{firstItem} and %{lastItem}"
msgstr ""
+msgid "nounSeries|%{item}"
+msgstr ""
+
msgid "nounSeries|%{item}, %{nextItem}"
msgstr ""
@@ -27272,6 +27898,9 @@ msgstr ""
msgid "revised"
msgstr ""
+msgid "satisfied"
+msgstr ""
+
msgid "score"
msgstr ""
@@ -27302,6 +27931,9 @@ msgstr ""
msgid "severity|Unknown"
msgstr ""
+msgid "should be an array of %{object_name} objects"
+msgstr ""
+
msgid "should be greater than or equal to %{access} inherited membership from group %{group_name}"
msgstr ""
@@ -27356,10 +27988,13 @@ msgstr ""
msgid "suggestPipeline|2/2: Commit your changes"
msgstr ""
-msgid "suggestPipeline|Commit the changes and your pipeline will automatically run for the first time."
+msgid "suggestPipeline|Choose %{boldStart}Code Quality%{boldEnd} to add a pipeline that tests the quality of your code."
msgstr ""
-msgid "suggestPipeline|We recommend the %{boldStart}Code Quality%{boldEnd} template, which will add a report widget to your Merge Requests. This way you’ll learn about code quality degradations much sooner. %{footerStart} Goodbye technical debt! %{footerEnd}"
+msgid "suggestPipeline|The template is ready! You can now commit it to create your first pipeline."
+msgstr ""
+
+msgid "suggestPipeline|We’re adding a GitLab CI configuration file to add a pipeline to the project. You could create it manually, but we recommend that you start with a GitLab template that works out of the box."
msgstr ""
msgid "syntax is correct"
@@ -27419,6 +28054,9 @@ msgstr ""
msgid "uses Kubernetes clusters to deploy your code!"
msgstr ""
+msgid "v%{version} published %{timeAgo}"
+msgstr ""
+
msgid "verify ownership"
msgstr ""
diff --git a/locale/sr_SP/gitlab.po b/locale/sr_SP/gitlab.po
index 3718b435c89..3a5de20ab5d 100644
--- a/locale/sr_SP/gitlab.po
+++ b/locale/sr_SP/gitlab.po
@@ -2,7 +2,7 @@ msgid ""
msgstr ""
"Project-Id-Version: gitlab-ee\n"
"Report-Msgid-Bugs-To: \n"
-"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Last-Translator: \n"
"Language-Team: Serbian (Cyrillic)\n"
"Language: sr_SP\n"
"MIME-Version: 1.0\n"
@@ -10,9 +10,11 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
"X-Crowdin-Project: gitlab-ee\n"
+"X-Crowdin-Project-ID: 288872\n"
"X-Crowdin-Language: sr\n"
"X-Crowdin-File: /master/locale/gitlab.pot\n"
-"PO-Revision-Date: 2020-06-08 15:17\n"
+"X-Crowdin-File-ID: 6\n"
+"PO-Revision-Date: 2020-07-02 01:28\n"
msgid " %{start} to %{end}"
msgstr ""
@@ -224,6 +226,12 @@ msgstr[0] ""
msgstr[1] ""
msgstr[2] ""
+msgid "%d milestone"
+msgid_plural "%d milestones"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+
msgid "%d minute"
msgid_plural "%d minutes"
msgstr[0] ""
@@ -278,12 +286,6 @@ msgstr[0] ""
msgstr[1] ""
msgstr[2] ""
-msgid "%d url scanned"
-msgid_plural "%d urls scanned"
-msgstr[0] ""
-msgstr[1] ""
-msgstr[2] ""
-
msgid "%d vulnerability"
msgid_plural "%d vulnerabilities"
msgstr[0] ""
@@ -368,9 +370,15 @@ msgstr[2] ""
msgid "%{count} related %{pluralized_subject}: %{links}"
msgstr ""
+msgid "%{dashboard_path} could not be found."
+msgstr ""
+
msgid "%{days} days until tags are automatically removed"
msgstr ""
+msgid "%{deployLinkStart}Use a template to deploy to ECS%{deployLinkEnd}, or use a docker image to %{commandsLinkStart}run AWS commands in GitLab CI/CD%{commandsLinkEnd}."
+msgstr ""
+
msgid "%{description}- Sentry event: %{errorUrl}- First seen: %{firstSeen}- Last seen: %{lastSeen} %{countLabel}: %{count}%{userCountLabel}: %{userCount}"
msgstr ""
@@ -413,12 +421,18 @@ msgstr ""
msgid "%{host} sign-in from new location"
msgstr ""
-msgid "%{icon}You are about to add %{usersTag} people to the discussion. Proceed with caution."
+msgid "%{icon}You are about to add %{usersTag} people to the discussion. They will all receive a notification."
+msgstr ""
+
+msgid "%{integrations_link_start}Integrations%{link_end} enable you to make third-party applications part of your GitLab workflow. If the available integrations don't meet your needs, consider using a %{webhooks_link_start}webhook%{link_end}."
msgstr ""
msgid "%{issuableType} will be removed! Are you sure?"
msgstr ""
+msgid "%{issuesCount} issues in this group"
+msgstr ""
+
msgid "%{issuesSize} issues"
msgstr ""
@@ -428,7 +442,7 @@ msgstr ""
msgid "%{labelStart}Class:%{labelEnd} %{class}"
msgstr ""
-msgid "%{labelStart}Confidence:%{labelEnd} %{confidence}"
+msgid "%{labelStart}Evidence:%{labelEnd} %{evidence}"
msgstr ""
msgid "%{labelStart}File:%{labelEnd} %{file}"
@@ -446,6 +460,9 @@ msgstr ""
msgid "%{labelStart}Report Type:%{labelEnd} %{reportType}"
msgstr ""
+msgid "%{labelStart}Scanner:%{labelEnd} %{scanner}"
+msgstr ""
+
msgid "%{labelStart}Severity:%{labelEnd} %{severity}"
msgstr ""
@@ -470,9 +487,6 @@ msgstr ""
msgid "%{link_start}Read more%{link_end} about role permissions"
msgstr ""
-msgid "%{link} can be used for binding events when something is happening within the project."
-msgstr ""
-
msgid "%{listToShow}, and %{awardsListLength} more."
msgstr ""
@@ -506,6 +520,9 @@ msgstr ""
msgid "%{name} found %{resultsString}"
msgstr ""
+msgid "%{name} is already being used for another emoji"
+msgstr ""
+
msgid "%{name} is scheduled for %{action}"
msgstr ""
@@ -554,6 +571,9 @@ msgstr ""
msgid "%{primary} (%{secondary})"
msgstr ""
+msgid "%{ref} cannot be added: %{error}"
+msgstr ""
+
msgid "%{releases} release"
msgid_plural "%{releases} releases"
msgstr[0] ""
@@ -563,13 +583,40 @@ msgstr[2] ""
msgid "%{remaining_approvals} left"
msgstr ""
-msgid "%{retryButtonStart}Try again%{retryButtonEnd} or %{newFileButtonStart}attach a new file%{newFileButtonEnd}"
+msgid "%{reportType} %{status} detected %{critical} critical and %{high} high severity vulnerabilities out of %{total}."
msgstr ""
-msgid "%{screenreaderOnlyStart}Keyboard shorcuts%{screenreaderOnlyEnd} Disabled"
+msgid "%{reportType} %{status} detected %{critical} critical and %{high} high severity vulnerabilities."
msgstr ""
-msgid "%{screenreaderOnlyStart}Keyboard shorcuts%{screenreaderOnlyEnd} Enabled"
+msgid "%{reportType} %{status} detected %{critical} critical severity vulnerabilities out of %{total}."
+msgstr ""
+
+msgid "%{reportType} %{status} detected %{critical} critical severity vulnerability."
+msgid_plural "%{reportType} %{status} detected %{critical} critical severity vulnerabilities."
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+
+msgid "%{reportType} %{status} detected %{high} high severity vulnerabilities out of %{total}."
+msgstr ""
+
+msgid "%{reportType} %{status} detected %{high} high severity vulnerability."
+msgid_plural "%{reportType} %{status} detected %{high} high severity vulnerabilities."
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+
+msgid "%{reportType} %{status} detected %{other} vulnerability."
+msgid_plural "%{reportType} %{status} detected %{other} vulnerabilities."
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+
+msgid "%{reportType} %{status} detected no new vulnerabilities."
+msgstr ""
+
+msgid "%{retryButtonStart}Try again%{retryButtonEnd} or %{newFileButtonStart}attach a new file%{newFileButtonEnd}"
msgstr ""
msgid "%{service_title} %{message}."
@@ -671,6 +718,12 @@ msgstr ""
msgid "%{token}..."
msgstr ""
+msgid "%{totalCpu} (%{freeSpacePercentage}%{percentSymbol} free)"
+msgstr ""
+
+msgid "%{totalMemory} (%{freeSpacePercentage}%{percentSymbol} free)"
+msgstr ""
+
msgid "%{totalWeight} total weight"
msgstr ""
@@ -701,6 +754,12 @@ msgstr ""
msgid "%{verb} %{time_spent_value} spent time."
msgstr ""
+msgid "%{webhooks_link_start}%{webhook_type}%{link_end} enable you to send notifications to web applications in response to events in a group or project."
+msgstr ""
+
+msgid "%{webhooks_link_start}%{webhook_type}%{link_end} enable you to send notifications to web applications in response to events in a group or project. We recommend using an %{integrations_link_start}integration%{link_end} in preference to a webhook."
+msgstr ""
+
msgid "'%{level}' is not a valid visibility level"
msgstr ""
@@ -728,9 +787,6 @@ msgstr ""
msgid "(No changes)"
msgstr ""
-msgid "(Show all)"
-msgstr ""
-
msgid "(check progress)"
msgstr ""
@@ -800,6 +856,9 @@ msgstr ""
msgid "0 for unlimited"
msgstr ""
+msgid "0 for unlimited, only effective with remote storage enabled."
+msgstr ""
+
msgid "1 %{type} addition"
msgid_plural "%{count} %{type} additions"
msgstr[0] ""
@@ -962,6 +1021,12 @@ msgstr ""
msgid "<code>\"johnsmith@example.com\": \"johnsmith@example.com\"</code> will add \"By <a href=\"#\">johnsmith@example.com</a>\" to all issues and comments originally created by johnsmith@example.com. By default, the email address or username is masked to ensure the user's privacy. Use this option if you want to show the full email address."
msgstr ""
+msgid "<code>Masked</code> to prevent the values from being displayed in job logs (must match certain regexp requirements)."
+msgstr ""
+
+msgid "<code>Protected</code> to expose them to protected branches or tags only."
+msgstr ""
+
msgid "<namespace / project>"
msgstr ""
@@ -989,9 +1054,6 @@ msgstr ""
msgid "A .NET Core console application template, customizable for any .NET Core project"
msgstr ""
-msgid "A DAG must have two dependent jobs to be visualized on this tab."
-msgstr ""
-
msgid "A GitBook site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
msgstr ""
@@ -1022,6 +1084,12 @@ msgstr ""
msgid "A deleted user"
msgstr ""
+msgid "A file has been changed."
+msgstr ""
+
+msgid "A file was not found."
+msgstr ""
+
msgid "A file with '%{file_name}' already exists in %{branch} branch"
msgstr ""
@@ -1088,7 +1156,7 @@ msgstr ""
msgid "A subscription will trigger a new pipeline on the default branch of this project when a pipeline successfully completes for a new tag on the %{default_branch_docs} of the subscribed project."
msgstr ""
-msgid "A terraform report was generated in your pipelines."
+msgid "A suggestion is not applicable."
msgstr ""
msgid "A user with write access to the source branch selected this option"
@@ -1259,6 +1327,9 @@ msgstr ""
msgid "Action to take when receiving an alert."
msgstr ""
+msgid "Actions"
+msgstr ""
+
msgid "Activate"
msgstr ""
@@ -1316,6 +1387,9 @@ msgstr ""
msgid "Add LICENSE"
msgstr ""
+msgid "Add New Node"
+msgstr ""
+
msgid "Add README"
msgstr ""
@@ -1391,6 +1465,9 @@ msgstr ""
msgid "Add approval rule"
msgstr ""
+msgid "Add approvers"
+msgstr ""
+
msgid "Add bold text"
msgstr ""
@@ -1448,6 +1525,9 @@ msgstr ""
msgid "Add strikethrough text"
msgstr ""
+msgid "Add suggestion to batch"
+msgstr ""
+
msgid "Add system hook"
msgstr ""
@@ -1547,6 +1627,9 @@ msgstr ""
msgid "Admin Note"
msgstr ""
+msgid "Admin Notifications"
+msgstr ""
+
msgid "Admin Overview"
msgstr ""
@@ -1658,6 +1741,9 @@ msgstr ""
msgid "AdminSettings|Integrations configured here will automatically apply to all projects on this instance."
msgstr ""
+msgid "AdminSettings|Moved to integrations"
+msgstr ""
+
msgid "AdminSettings|No required pipeline"
msgstr ""
@@ -1889,12 +1975,18 @@ msgstr[0] ""
msgstr[1] ""
msgstr[2] ""
+msgid "AlertManagement| assign yourself"
+msgstr ""
+
msgid "AlertManagement|Acknowledged"
msgstr ""
msgid "AlertManagement|Alert"
msgstr ""
+msgid "AlertManagement|Alert assignee(s): %{assignees}"
+msgstr ""
+
msgid "AlertManagement|Alert detail"
msgstr ""
@@ -1910,9 +2002,15 @@ msgstr ""
msgid "AlertManagement|All alerts"
msgstr ""
+msgid "AlertManagement|Assign To"
+msgstr ""
+
msgid "AlertManagement|Assign status"
msgstr ""
+msgid "AlertManagement|Assignee"
+msgstr ""
+
msgid "AlertManagement|Assignees"
msgstr ""
@@ -1931,9 +2029,6 @@ msgstr ""
msgid "AlertManagement|Edit"
msgstr ""
-msgid "AlertManagement|End time"
-msgstr ""
-
msgid "AlertManagement|Events"
msgstr ""
@@ -1952,10 +2047,13 @@ msgstr ""
msgid "AlertManagement|More information"
msgstr ""
+msgid "AlertManagement|No Matching Results"
+msgstr ""
+
msgid "AlertManagement|No alert data to display."
msgstr ""
-msgid "AlertManagement|No alerts available to display. If you think you're seeing this message in error, refresh the page."
+msgid "AlertManagement|No alerts available to display. See %{linkStart}enabling alert management%{linkEnd} for more information on adding alerts to the list."
msgstr ""
msgid "AlertManagement|No alerts to display."
@@ -1964,6 +2062,9 @@ msgstr ""
msgid "AlertManagement|None"
msgstr ""
+msgid "AlertManagement|None -"
+msgstr ""
+
msgid "AlertManagement|Open"
msgstr ""
@@ -2000,9 +2101,18 @@ msgstr ""
msgid "AlertManagement|There was an error displaying the alerts. Confirm your endpoint's configuration details to ensure alerts appear."
msgstr ""
+msgid "AlertManagement|There was an error while updating the assignee(s) list. Please try again."
+msgstr ""
+
+msgid "AlertManagement|There was an error while updating the assignee(s) of the alert. Please try again."
+msgstr ""
+
msgid "AlertManagement|There was an error while updating the status of the alert. Please try again."
msgstr ""
+msgid "AlertManagement|This assignee cannot be assigned to this alert."
+msgstr ""
+
msgid "AlertManagement|Tool"
msgstr ""
@@ -2150,6 +2260,9 @@ msgstr ""
msgid "Allow users to request access (if visibility is public or internal)"
msgstr ""
+msgid "Allowed Geo IP"
+msgstr ""
+
msgid "Allowed email domain restriction only permitted for top-level groups"
msgstr ""
@@ -2183,6 +2296,9 @@ msgstr ""
msgid "Amazon Web Services"
msgstr ""
+msgid "Amazon Web Services Logo"
+msgstr ""
+
msgid "Amazon authentication is not %{link_start}correctly configured%{link_end}. Ask your GitLab administrator if you want to use this service."
msgstr ""
@@ -2240,10 +2356,16 @@ msgstr ""
msgid "An error occurred when updating the issue weight"
msgstr ""
+msgid "An error occurred while acknowledging the notification. Refresh the page and try again."
+msgstr ""
+
+msgid "An error occurred while adding approvers"
+msgstr ""
+
msgid "An error occurred while adding formatted title for epic"
msgstr ""
-msgid "An error occurred while checking group path"
+msgid "An error occurred while checking group path. Please refresh and try again."
msgstr ""
msgid "An error occurred while committing your changes."
@@ -2387,7 +2509,7 @@ msgstr ""
msgid "An error occurred while loading milestones"
msgstr ""
-msgid "An error occurred while loading terraform report"
+msgid "An error occurred while loading project creation UI"
msgstr ""
msgid "An error occurred while loading the data. Please try again."
@@ -2441,6 +2563,9 @@ msgstr ""
msgid "An error occurred while rendering preview broadcast message"
msgstr ""
+msgid "An error occurred while rendering the editor"
+msgstr ""
+
msgid "An error occurred while reordering issues."
msgstr ""
@@ -2537,7 +2662,7 @@ msgstr ""
msgid "Analyze your dependencies for known vulnerabilities."
msgstr ""
-msgid "Analyze your source code and git history for secrets"
+msgid "Analyze your source code and git history for secrets."
msgstr ""
msgid "Analyze your source code for known vulnerabilities."
@@ -2651,12 +2776,18 @@ msgstr ""
msgid "Apply suggestion"
msgstr ""
+msgid "Apply suggestions"
+msgstr ""
+
msgid "Apply template"
msgstr ""
msgid "Apply this approval rule to any branch or a specific protected branch."
msgstr ""
+msgid "Applying"
+msgstr ""
+
msgid "Applying a template will replace the existing issue description. Any changes you have made will be lost."
msgstr ""
@@ -2669,12 +2800,18 @@ msgstr ""
msgid "Applying multiple commands"
msgstr ""
-msgid "Applying suggestion"
+msgid "Applying suggestion..."
+msgstr ""
+
+msgid "Applying suggestions..."
msgstr ""
msgid "Approval rules"
msgstr ""
+msgid "Approval rules reset to project defaults"
+msgstr ""
+
msgid "ApprovalRuleRemove|%d member"
msgid_plural "ApprovalRuleRemove|%d members"
msgstr[0] ""
@@ -2732,6 +2869,9 @@ msgstr ""
msgid "Approved"
msgstr ""
+msgid "Approved MRs"
+msgstr ""
+
msgid "Approved by: "
msgstr ""
@@ -2945,6 +3085,9 @@ msgstr ""
msgid "Assign"
msgstr ""
+msgid "Assign Iteration"
+msgstr ""
+
msgid "Assign custom color like #FF0000"
msgstr ""
@@ -2963,6 +3106,9 @@ msgstr ""
msgid "Assign to"
msgstr ""
+msgid "Assign to commenting user"
+msgstr ""
+
msgid "Assign yourself to these issues"
msgstr ""
@@ -2990,6 +3136,9 @@ msgstr[0] ""
msgstr[1] ""
msgstr[2] ""
+msgid "Assignee has no permissions"
+msgstr ""
+
msgid "Assignee lists not available with your current license"
msgstr ""
@@ -2999,6 +3148,9 @@ msgstr ""
msgid "Assignee(s)"
msgstr ""
+msgid "Assignees"
+msgstr ""
+
msgid "Assigns %{assignee_users_sentence}."
msgstr ""
@@ -3035,6 +3187,9 @@ msgstr ""
msgid "Audit Events is a way to keep track of important events that happened in GitLab."
msgstr ""
+msgid "Audit Log"
+msgstr ""
+
msgid "AuditEvents|(removed)"
msgstr ""
@@ -3071,6 +3226,9 @@ msgstr ""
msgid "AuditLogs|IP Address"
msgstr ""
+msgid "AuditLogs|Member Events"
+msgstr ""
+
msgid "AuditLogs|No matching %{type} found."
msgstr ""
@@ -3353,9 +3511,6 @@ msgstr ""
msgid "Badges|e.g. %{exampleUrl}"
msgstr ""
-msgid "Badge|New"
-msgstr ""
-
msgid "Balsamiq file could not be loaded."
msgstr ""
@@ -3404,6 +3559,12 @@ msgstr ""
msgid "Below you will find all the groups that are public."
msgstr ""
+msgid "Beta"
+msgstr ""
+
+msgid "Bi-weekly code coverage"
+msgstr ""
+
msgid "Billing"
msgstr ""
@@ -3464,9 +3625,15 @@ msgstr ""
msgid "Bitbucket Server Import"
msgstr ""
+msgid "Bitbucket Server import"
+msgstr ""
+
msgid "Bitbucket import"
msgstr ""
+msgid "Blame"
+msgstr ""
+
msgid "Blocked"
msgstr ""
@@ -3479,9 +3646,6 @@ msgstr ""
msgid "Blog"
msgstr ""
-msgid "Blue helpers indicate an action to be taken."
-msgstr ""
-
msgid "Board name"
msgstr ""
@@ -3680,6 +3844,9 @@ msgstr ""
msgid "Broadcast Message was successfully updated."
msgstr ""
+msgid "Broadcast Messages"
+msgstr ""
+
msgid "Browse Directory"
msgstr ""
@@ -3734,6 +3901,9 @@ msgstr ""
msgid "By %{user_name}"
msgstr ""
+msgid "By URL"
+msgstr ""
+
msgid "By default GitLab sends emails in HTML and plain text formats so mail clients can choose what format to use. Disable this option if you only want to send emails in plain text format."
msgstr ""
@@ -3839,6 +4009,9 @@ msgstr ""
msgid "Can override approvers and approvals required per merge request"
msgstr ""
+msgid "Can't apply as this line has changed or the suggestion already matches its content."
+msgstr ""
+
msgid "Can't create snippet: %{err}"
msgstr ""
@@ -4025,9 +4198,6 @@ msgstr ""
msgid "Changes are still tracked. Useful for cluster/index migrations."
msgstr ""
-msgid "Changes are unknown"
-msgstr ""
-
msgid "Changes suppressed. Click to show."
msgstr ""
@@ -4334,15 +4504,9 @@ msgstr ""
msgid "Choose what content you want to see on a group’s overview page"
msgstr ""
-msgid "Choose which groups you wish to synchronize to this secondary node"
-msgstr ""
-
msgid "Choose which repositories you want to connect and run CI/CD pipelines."
msgstr ""
-msgid "Choose which shards you wish to synchronize to this secondary node"
-msgstr ""
-
msgid "Choose your framework"
msgstr ""
@@ -4640,9 +4804,15 @@ msgstr ""
msgid "Cluster does not exist"
msgstr ""
+msgid "Cluster is required for Stages::ClusterEndpointInserter"
+msgstr ""
+
msgid "Cluster level"
msgstr ""
+msgid "Cluster type must be specificed for Stages::ClusterEndpointInserter"
+msgstr ""
+
msgid "ClusterIntegration| %{custom_domain_start}More information%{custom_domain_end}."
msgstr ""
@@ -4754,7 +4924,7 @@ msgstr ""
msgid "ClusterIntegration|Cert-Manager"
msgstr ""
-msgid "ClusterIntegration|Cert-Manager is a native Kubernetes certificate management controller that helps with issuing certificates. Installing Cert-Manager on your cluster will issue a certificate by %{letsEncrypt} and ensure that certificates are valid and up-to-date."
+msgid "ClusterIntegration|Cert-Manager is a native Kubernetes certificate management controller that helps with issuing certificates. Installing Cert-Manager on your cluster will issue a certificate by %{linkStart}Let's Encrypt%{linkEnd} and ensure that certificates are valid and up-to-date."
msgstr ""
msgid "ClusterIntegration|Certificate Authority bundle (PEM format)"
@@ -4781,9 +4951,6 @@ msgstr ""
msgid "ClusterIntegration|Clear the local cache of namespace and service accounts. This is necessary if your integration has become out of sync. The cache is repopulated during the next CI job that requires namespace and service accounts."
msgstr ""
-msgid "ClusterIntegration|Cloud Run"
-msgstr ""
-
msgid "ClusterIntegration|Cluster being created"
msgstr ""
@@ -4868,7 +5035,7 @@ msgstr ""
msgid "ClusterIntegration|Crossplane"
msgstr ""
-msgid "ClusterIntegration|Crossplane enables declarative provisioning of managed services from your cloud of choice using %{codeStart}kubectl%{codeEnd} or %{gitlabIntegrationLink}. Crossplane runs inside your Kubernetes cluster and supports secure connectivity and secrets management between app containers and the cloud services they depend on."
+msgid "ClusterIntegration|Crossplane enables declarative provisioning of managed services from your cloud of choice using %{codeStart}kubectl%{codeEnd} or %{linkStart}GitLab Integration%{linkEnd}. Crossplane runs inside your Kubernetes cluster and supports secure connectivity and secrets management between app containers and the cloud services they depend on."
msgstr ""
msgid "ClusterIntegration|Deletes all GitLab resources attached to this cluster during removal"
@@ -4952,9 +5119,6 @@ msgstr ""
msgid "ClusterIntegration|GitLab-managed cluster"
msgstr ""
-msgid "ClusterIntegration|Gitlab Integration"
-msgstr ""
-
msgid "ClusterIntegration|Global default"
msgstr ""
@@ -5000,7 +5164,7 @@ msgstr ""
msgid "ClusterIntegration|Ingress gives you a way to route requests to services based on the request host or path, centralizing a number of services into a single entrypoint."
msgstr ""
-msgid "ClusterIntegration|Installing Ingress may incur additional costs. Learn more about %{pricingLink}."
+msgid "ClusterIntegration|Installing Ingress may incur additional costs. Learn more about %{linkStart}pricing%{linkEnd}."
msgstr ""
msgid "ClusterIntegration|Instance cluster"
@@ -5015,7 +5179,7 @@ msgstr ""
msgid "ClusterIntegration|Issuer Email"
msgstr ""
-msgid "ClusterIntegration|Issuers represent a certificate authority. You must provide an email address for your Issuer. "
+msgid "ClusterIntegration|Issuers represent a certificate authority. You must provide an email address for your Issuer."
msgstr ""
msgid "ClusterIntegration|Jupyter Hostname"
@@ -5087,9 +5251,6 @@ msgstr ""
msgid "ClusterIntegration|Learn more about instance Kubernetes clusters"
msgstr ""
-msgid "ClusterIntegration|Let's Encrypt"
-msgstr ""
-
msgid "ClusterIntegration|Loading IAM Roles"
msgstr ""
@@ -5201,7 +5362,7 @@ msgstr ""
msgid "ClusterIntegration|Prometheus"
msgstr ""
-msgid "ClusterIntegration|Prometheus is an open-source monitoring system with %{gitlabIntegrationLink} to monitor deployed applications."
+msgid "ClusterIntegration|Prometheus is an open-source monitoring system with %{linkStart}GitLab Integration%{linkEnd} to monitor deployed applications."
msgstr ""
msgid "ClusterIntegration|Provider details"
@@ -5357,10 +5518,10 @@ msgstr ""
msgid "ClusterIntegration|Select zone to choose machine type"
msgstr ""
-msgid "ClusterIntegration|Send Cilium Logs"
+msgid "ClusterIntegration|Send Container Network Policies Logs"
msgstr ""
-msgid "ClusterIntegration|Send ModSecurity Logs"
+msgid "ClusterIntegration|Send Web Application Firewall Logs"
msgstr ""
msgid "ClusterIntegration|Service Token"
@@ -5528,15 +5689,12 @@ msgstr ""
msgid "ClusterIntegration|documentation"
msgstr ""
-msgid "ClusterIntegration|installed via %{installed_via}"
+msgid "ClusterIntegration|installed via %{linkStart}Cloud Run%{linkEnd}"
msgstr ""
msgid "ClusterIntegration|meets the requirements"
msgstr ""
-msgid "ClusterIntegration|pricing"
-msgstr ""
-
msgid "ClusterIntegration|sign up"
msgstr ""
@@ -5573,6 +5731,15 @@ msgstr ""
msgid "Code"
msgstr ""
+msgid "Code Coverage: %{coveragePercentage}%{percentSymbol}"
+msgstr ""
+
+msgid "Code Coverage| Empty code coverage data"
+msgstr ""
+
+msgid "Code Coverage|Couldn't fetch the code coverage data"
+msgstr ""
+
msgid "Code Owners"
msgstr ""
@@ -5627,7 +5794,7 @@ msgstr ""
msgid "Collapse approvers"
msgstr ""
-msgid "Collapse child epics"
+msgid "Collapse milestones"
msgstr ""
msgid "Collapse replies"
@@ -5645,6 +5812,9 @@ msgstr ""
msgid "Coming soon"
msgstr ""
+msgid "Comma-separated, e.g. '1.1.1.1, 2.2.2.0/24'"
+msgstr ""
+
msgid "Command"
msgstr ""
@@ -5816,6 +5986,12 @@ msgstr ""
msgid "Compliance framework (optional)"
msgstr ""
+msgid "Compliance frameworks"
+msgstr ""
+
+msgid "ComplianceDashboard|created by:"
+msgstr ""
+
msgid "ComplianceFramework|GDPR"
msgstr ""
@@ -5954,6 +6130,9 @@ msgstr ""
msgid "Connection timed out"
msgstr ""
+msgid "Connection timeout"
+msgstr ""
+
msgid "Contact sales to upgrade"
msgstr ""
@@ -6011,9 +6190,6 @@ msgstr ""
msgid "ContainerRegistry|CLI Commands"
msgstr ""
-msgid "ContainerRegistry|Compressed Size"
-msgstr ""
-
msgid "ContainerRegistry|Container Registry"
msgstr ""
@@ -6029,6 +6205,9 @@ msgstr ""
msgid "ContainerRegistry|Copy push command"
msgstr ""
+msgid "ContainerRegistry|Delete selected"
+msgstr ""
+
msgid "ContainerRegistry|Docker connection error"
msgstr ""
@@ -6062,16 +6241,16 @@ msgstr ""
msgid "ContainerRegistry|If you are not already logged in, you need to authenticate to the Container Registry by using your GitLab username and password. If you have %{twofaDocLinkStart}Two-Factor Authentication%{twofaDocLinkEnd} enabled, use a %{personalAccessTokensDocLinkStart}Personal Access Token%{personalAccessTokensDocLinkEnd} instead of a password."
msgstr ""
-msgid "ContainerRegistry|Image ID"
+msgid "ContainerRegistry|Image ID: %{imageId}"
msgstr ""
msgid "ContainerRegistry|Image Repositories"
msgstr ""
-msgid "ContainerRegistry|Keep and protect the images that matter most."
+msgid "ContainerRegistry|Image tags"
msgstr ""
-msgid "ContainerRegistry|Last Updated"
+msgid "ContainerRegistry|Keep and protect the images that matter most."
msgstr ""
msgid "ContainerRegistry|Login"
@@ -6086,6 +6265,9 @@ msgstr ""
msgid "ContainerRegistry|Please contact your administrator."
msgstr ""
+msgid "ContainerRegistry|Published %{timeInfo}"
+msgstr ""
+
msgid "ContainerRegistry|Push an image"
msgstr ""
@@ -6101,9 +6283,6 @@ msgstr ""
msgid "ContainerRegistry|Remove repository"
msgstr ""
-msgid "ContainerRegistry|Remove selected tags"
-msgstr ""
-
msgid "ContainerRegistry|Remove tag"
msgid_plural "ContainerRegistry|Remove tags"
msgstr[0] ""
@@ -6134,9 +6313,6 @@ msgstr ""
msgid "ContainerRegistry|Sorry, your filter produced no results."
msgstr ""
-msgid "ContainerRegistry|Tag"
-msgstr ""
-
msgid "ContainerRegistry|Tag expiration policy"
msgstr ""
@@ -6281,21 +6457,6 @@ msgstr ""
msgid "Control the display of third party offers."
msgstr ""
-msgid "Control the maximum concurrency of LFS/attachment backfill for this secondary node"
-msgstr ""
-
-msgid "Control the maximum concurrency of container repository operations for this Geo node"
-msgstr ""
-
-msgid "Control the maximum concurrency of repository backfill for this secondary node"
-msgstr ""
-
-msgid "Control the maximum concurrency of verification operations for this Geo node"
-msgstr ""
-
-msgid "Control the minimum interval in days that a repository should be reverified for this primary node"
-msgstr ""
-
msgid "Cookie domain"
msgstr ""
@@ -6437,6 +6598,9 @@ msgstr ""
msgid "Could not find design."
msgstr ""
+msgid "Could not find iteration"
+msgstr ""
+
msgid "Could not remove the trigger."
msgstr ""
@@ -6473,6 +6637,12 @@ msgstr ""
msgid "Coverage"
msgstr ""
+msgid "Coverage Fuzzing"
+msgstr ""
+
+msgid "Crash State"
+msgstr ""
+
msgid "Create"
msgstr ""
@@ -6521,6 +6691,9 @@ msgstr ""
msgid "Create a personal access token on your account to pull or push via %{protocol}."
msgstr ""
+msgid "Create a project pre-populated with the necessary files to get you started quickly."
+msgstr ""
+
msgid "Create an account using:"
msgstr ""
@@ -6689,6 +6862,9 @@ msgstr ""
msgid "Creates branch '%{branch_name}' and a merge request to resolve this issue."
msgstr ""
+msgid "Creating"
+msgstr ""
+
msgid "Creating epic"
msgstr ""
@@ -6740,7 +6916,10 @@ msgstr ""
msgid "Current vulnerabilities count"
msgstr ""
-msgid "CurrentUser|Buy CI minutes"
+msgid "CurrentUser|Buy Pipeline minutes"
+msgstr ""
+
+msgid "CurrentUser|One of your groups is running out"
msgstr ""
msgid "CurrentUser|Profile"
@@ -6755,6 +6934,9 @@ msgstr ""
msgid "CurrentUser|Upgrade"
msgstr ""
+msgid "Custom Attributes"
+msgstr ""
+
msgid "Custom CI configuration path"
msgstr ""
@@ -7019,12 +7201,18 @@ msgstr ""
msgid "DAG"
msgstr ""
+msgid "DAG visualization requires at least 3 dependent jobs."
+msgstr ""
+
msgid "DNS"
msgstr ""
msgid "Dashboard"
msgstr ""
+msgid "Dashboard uid not found"
+msgstr ""
+
msgid "DashboardProjects|All"
msgstr ""
@@ -7049,6 +7237,9 @@ msgstr ""
msgid "Data is still calculating..."
msgstr ""
+msgid "Datasource name not found"
+msgstr ""
+
msgid "Date"
msgstr ""
@@ -7229,6 +7420,9 @@ msgstr ""
msgid "Delete this attachment"
msgstr ""
+msgid "Delete user list"
+msgstr ""
+
msgid "Delete variable"
msgstr ""
@@ -7550,6 +7744,9 @@ msgstr ""
msgid "Deploying to"
msgstr ""
+msgid "Deploying to AWS is easy with GitLab"
+msgstr ""
+
msgid "Deployment Frequency"
msgstr ""
@@ -7913,6 +8110,9 @@ msgstr ""
msgid "Dismissed on pipeline %{pipelineLink} at %{projectLink}"
msgstr ""
+msgid "Display alerts from all your monitoring tools directly within GitLab."
+msgstr ""
+
msgid "Display name"
msgstr ""
@@ -7970,9 +8170,6 @@ msgstr ""
msgid "Don't show again"
msgstr ""
-msgid "Don't worry, you can access this tour by clicking on the help icon in the top right corner and choose <strong>Learn GitLab</strong>."
-msgstr ""
-
msgid "Done"
msgstr ""
@@ -8099,6 +8296,9 @@ msgstr ""
msgid "Edit Release"
msgstr ""
+msgid "Edit Slack integration"
+msgstr ""
+
msgid "Edit Snippet"
msgstr ""
@@ -8138,6 +8338,9 @@ msgstr ""
msgid "Edit issues"
msgstr ""
+msgid "Edit iteration"
+msgstr ""
+
msgid "Edit public deploy key"
msgstr ""
@@ -8186,6 +8389,9 @@ msgstr ""
msgid "Email"
msgstr ""
+msgid "Email Notification"
+msgstr ""
+
msgid "Email address"
msgstr ""
@@ -8198,6 +8404,9 @@ msgstr ""
msgid "Email not verified. Please verify your email in Salesforce."
msgstr ""
+msgid "Email notification for unknown sign-ins"
+msgstr ""
+
msgid "Email patch"
msgstr ""
@@ -8429,6 +8638,9 @@ msgstr ""
msgid "Enforce DNS rebinding attack protection"
msgstr ""
+msgid "Enforce personal access token expiration"
+msgstr ""
+
msgid "Ensure connectivity is available from the GitLab server to the Prometheus server"
msgstr ""
@@ -8498,6 +8710,9 @@ msgstr ""
msgid "Enter the name of your application, and we'll return a unique %{type}."
msgstr ""
+msgid "Enter weights for storages for new repositories."
+msgstr ""
+
msgid "Enter your password to approve"
msgstr ""
@@ -8507,15 +8722,27 @@ msgstr ""
msgid "Environment does not have deployments"
msgstr ""
+msgid "Environment is required for Stages::MetricEndpointInserter"
+msgstr ""
+
+msgid "Environment is required for Stages::VariableEndpointInserter"
+msgstr ""
+
msgid "Environment scope"
msgstr ""
+msgid "Environment variables are applied to all project environments in this instance via the Runner. You can use environment variables for passwords, secret keys, etc. Make variables available to the running application by prepending the variable key with <code>K8S_SECRET_</code>. You can set variables to be:"
+msgstr ""
+
msgid "Environment variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. Additionally, they can be masked so they are hidden in job logs, though they must match certain regexp requirements to do so. You can use environment variables for passwords, secret keys, or whatever you want."
msgstr ""
msgid "Environment variables are configured by your administrator to be %{link_start}protected%{link_end} by default"
msgstr ""
+msgid "Environment variables on this GitLab instance are configured to be %{link_start}protected%{link_end} by default"
+msgstr ""
+
msgid "Environment:"
msgstr ""
@@ -9047,6 +9274,9 @@ msgstr ""
msgid "EventFilterBy|Filter by comments"
msgstr ""
+msgid "EventFilterBy|Filter by designs"
+msgstr ""
+
msgid "EventFilterBy|Filter by epic events"
msgstr ""
@@ -9179,15 +9409,15 @@ msgstr ""
msgid "Expand approvers"
msgstr ""
-msgid "Expand child epics"
-msgstr ""
-
msgid "Expand down"
msgstr ""
msgid "Expand dropdown"
msgstr ""
+msgid "Expand milestones"
+msgstr ""
+
msgid "Expand sidebar"
msgstr ""
@@ -9203,6 +9433,9 @@ msgstr ""
msgid "Expiration date"
msgstr ""
+msgid "Expiration not enforced"
+msgstr ""
+
msgid "Expiration policy for the Container Registry is a perfect solution for keeping the Registry space down while still enjoying the full power of GitLab CI/CD."
msgstr ""
@@ -9323,6 +9556,9 @@ msgstr ""
msgid "Failed Jobs"
msgstr ""
+msgid "Failed on"
+msgstr ""
+
msgid "Failed to add a Zoom meeting"
msgstr ""
@@ -9383,13 +9619,19 @@ msgstr ""
msgid "Failed to install."
msgstr ""
+msgid "Failed to load assignees. Please try again."
+msgstr ""
+
+msgid "Failed to load authors. Please try again."
+msgstr ""
+
msgid "Failed to load emoji list."
msgstr ""
msgid "Failed to load error details from Sentry."
msgstr ""
-msgid "Failed to load errors from Sentry. Error message: %{errorMessage}"
+msgid "Failed to load errors from Sentry."
msgstr ""
msgid "Failed to load group activity metrics. Please try again."
@@ -9470,6 +9712,9 @@ msgstr ""
msgid "Failed to set due date because the date format is invalid."
msgstr ""
+msgid "Failed to set iteration on this issue. Please try again."
+msgstr ""
+
msgid "Failed to signing using smartcard authentication"
msgstr ""
@@ -9692,13 +9937,10 @@ msgstr ""
msgid "FeatureFlags|Target environments"
msgstr ""
-msgid "FeatureFlags|There are no active feature flags"
-msgstr ""
-
-msgid "FeatureFlags|There are no inactive feature flags"
+msgid "FeatureFlags|There was an error fetching the feature flags."
msgstr ""
-msgid "FeatureFlags|There was an error fetching the feature flags."
+msgid "FeatureFlags|There was an error retrieving user lists"
msgstr ""
msgid "FeatureFlags|Try again in a few moments or contact your support team."
@@ -9710,9 +9952,18 @@ msgstr ""
msgid "FeatureFlag|Delete strategy"
msgstr ""
+msgid "FeatureFlag|List"
+msgstr ""
+
msgid "FeatureFlag|Percentage"
msgstr ""
+msgid "FeatureFlag|Select a user list"
+msgstr ""
+
+msgid "FeatureFlag|There are no configured user lists"
+msgstr ""
+
msgid "FeatureFlag|Type"
msgstr ""
@@ -9869,6 +10120,12 @@ msgstr ""
msgid "Filter..."
msgstr ""
+msgid "Find File"
+msgstr ""
+
+msgid "Find bugs in your code with coverage-guided fuzzing"
+msgstr ""
+
msgid "Find by path"
msgstr ""
@@ -10133,6 +10390,9 @@ msgstr ""
msgid "Geo allows you to replicate your GitLab instance to other geographical locations."
msgstr ""
+msgid "Geo nodes are paused using a command run on the node"
+msgstr ""
+
msgid "GeoNodeStatusEvent|%{timeAgoStr} (%{pendingEvents} events)"
msgstr ""
@@ -10217,6 +10477,9 @@ msgstr ""
msgid "GeoNodes|Not checksummed"
msgstr ""
+msgid "GeoNodes|Package files"
+msgstr ""
+
msgid "GeoNodes|Pausing replication stops the sync process. Are you sure?"
msgstr ""
@@ -10235,6 +10498,9 @@ msgstr ""
msgid "GeoNodes|Replication slots"
msgstr ""
+msgid "GeoNodes|Replication status"
+msgstr ""
+
msgid "GeoNodes|Repositories"
msgstr ""
@@ -10445,6 +10711,9 @@ msgstr ""
msgid "Geo|Synchronization failed - %{error}"
msgstr ""
+msgid "Geo|Synchronization of %{itemTitle} is disabled."
+msgstr ""
+
msgid "Geo|The database is currently %{db_lag} behind the primary node."
msgstr ""
@@ -10757,6 +11026,9 @@ msgstr ""
msgid "Go to %{link_to_google_takeout}."
msgstr ""
+msgid "Go to <strong>Issues</strong> > <strong>Boards</strong> to access your personalized learning issue board."
+msgstr ""
+
msgid "Go to Pipelines"
msgstr ""
@@ -10856,9 +11128,6 @@ msgstr ""
msgid "Go to your snippets"
msgstr ""
-msgid "Golden Tanuki"
-msgstr ""
-
msgid "Google Cloud Platform"
msgstr ""
@@ -10880,6 +11149,9 @@ msgstr ""
msgid "Grafana URL"
msgstr ""
+msgid "Grafana response contains invalid json"
+msgstr ""
+
msgid "GrafanaIntegration|API Token"
msgstr ""
@@ -10928,9 +11200,6 @@ msgstr ""
msgid "Group %{group_name} was successfully created."
msgstr ""
-msgid "Group '%{group_name}' is being imported."
-msgstr ""
-
msgid "Group Audit Events"
msgstr ""
@@ -10967,9 +11236,6 @@ msgstr ""
msgid "Group by:"
msgstr ""
-msgid "Group could not be imported: %{errors}"
-msgstr ""
-
msgid "Group description"
msgstr ""
@@ -10997,9 +11263,15 @@ msgstr ""
msgid "Group has not been marked for deletion"
msgstr ""
+msgid "Group import could not be scheduled"
+msgstr ""
+
msgid "Group info:"
msgstr ""
+msgid "Group is required when cluster_type is :group"
+msgstr ""
+
msgid "Group maintainers can register group runners in the %{link}"
msgstr ""
@@ -11063,6 +11335,24 @@ msgstr ""
msgid "GroupActivyMetrics|Recent activity (last 90 days)"
msgstr ""
+msgid "GroupImport|Failed to import group."
+msgstr ""
+
+msgid "GroupImport|Group '%{group_name}' is being imported."
+msgstr ""
+
+msgid "GroupImport|Group could not be imported: %{errors}"
+msgstr ""
+
+msgid "GroupImport|Please wait while we import the group for you. Refresh at will."
+msgstr ""
+
+msgid "GroupImport|The group was successfully imported."
+msgstr ""
+
+msgid "GroupImport|Unable to process group import file"
+msgstr ""
+
msgid "GroupRoadmap|%{dateWord} – No end date"
msgstr ""
@@ -11330,6 +11620,9 @@ msgstr ""
msgid "Groups (%{groups})"
msgstr ""
+msgid "Groups and projects"
+msgstr ""
+
msgid "Groups can also be nested by creating %{subgroup_docs_link_start}subgroups%{subgroup_docs_link_end}."
msgstr ""
@@ -11375,6 +11668,33 @@ msgstr ""
msgid "GroupsEmptyState|You can manage your group member’s permissions and access to each project in the group."
msgstr ""
+msgid "GroupsNew|Contact an administrator to enable options for importing your group."
+msgstr ""
+
+msgid "GroupsNew|Create"
+msgstr ""
+
+msgid "GroupsNew|Create group"
+msgstr ""
+
+msgid "GroupsNew|GitLab group export"
+msgstr ""
+
+msgid "GroupsNew|Import"
+msgstr ""
+
+msgid "GroupsNew|Import group"
+msgstr ""
+
+msgid "GroupsNew|My Awesome Group"
+msgstr ""
+
+msgid "GroupsNew|No import options available"
+msgstr ""
+
+msgid "GroupsNew|To copy a GitLab group between installations, navigate to the group settings page for the original installation, generate an export file, and upload it here."
+msgstr ""
+
msgid "GroupsTree|Are you sure you want to leave the \"%{fullName}\" group?"
msgstr ""
@@ -11477,6 +11797,9 @@ msgstr ""
msgid "Helps reduce request volume for protected paths"
msgstr ""
+msgid "Here are all your projects in your group, including the one you just created. To start, let’s take a look at your personalized learning project which will help you learn about GitLab at your own pace."
+msgstr ""
+
msgid "Here you will find recent merge request activity"
msgstr ""
@@ -11492,6 +11815,9 @@ msgstr[0] ""
msgstr[1] ""
msgstr[2] ""
+msgid "Hide details"
+msgstr ""
+
msgid "Hide file browser"
msgstr ""
@@ -11501,6 +11827,9 @@ msgstr ""
msgid "Hide host keys manual input"
msgstr ""
+msgid "Hide list"
+msgstr ""
+
msgid "Hide marketing-related entries from help"
msgstr ""
@@ -11522,9 +11851,6 @@ msgstr[2] ""
msgid "Hide values"
msgstr ""
-msgid "Hiding all labels"
-msgstr ""
-
msgid "High or unknown vulnerabilities present"
msgstr ""
@@ -11609,6 +11935,9 @@ msgstr ""
msgid "ID:"
msgstr ""
+msgid "IDE"
+msgstr ""
+
msgid "IDE|Allow live previews of JavaScript projects in the Web IDE using CodeSandbox Live Preview."
msgstr ""
@@ -11699,10 +12028,10 @@ msgstr ""
msgid "If enabled"
msgstr ""
-msgid "If enabled, access to projects will be validated on an external service using their classification label."
+msgid "If enabled, GitLab will handle Object Storage replication using Geo. %{linkStart}More information%{linkEnd}"
msgstr ""
-msgid "If enabled, and if object storage is enabled, GitLab will handle Object Storage replication using Geo"
+msgid "If enabled, access to projects will be validated on an external service using their classification label."
msgstr ""
msgid "If there is no previous license or if the previous license has expired, some GitLab functionality will be blocked until a new, valid license is uploaded."
@@ -12065,6 +12394,9 @@ msgstr[0] ""
msgstr[1] ""
msgstr[2] ""
+msgid "Instance Configuration"
+msgstr ""
+
msgid "Instance Statistics visibility"
msgstr ""
@@ -12086,9 +12418,6 @@ msgstr ""
msgid "Integrations"
msgstr ""
-msgid "Integrations allow you to integrate GitLab with other applications"
-msgstr ""
-
msgid "Integrations|All details"
msgstr ""
@@ -12200,6 +12529,9 @@ msgstr ""
msgid "Invalid query"
msgstr ""
+msgid "Invalid repository bundle for snippet with id %{snippet_id}"
+msgstr ""
+
msgid "Invalid repository path"
msgstr ""
@@ -12218,6 +12550,9 @@ msgstr ""
msgid "Invalid two-factor code."
msgstr ""
+msgid "Invalid yaml"
+msgstr ""
+
msgid "Invitation"
msgstr ""
@@ -12437,15 +12772,24 @@ msgstr ""
msgid "It seems like the Dependency Scanning job ran successfully, but no dependencies have been detected in your project."
msgstr ""
+msgid "It seems that there is currently no available data for code coverage"
+msgstr ""
+
msgid "It's you"
msgstr ""
+msgid "Iteration"
+msgstr ""
+
msgid "Iteration changed to"
msgstr ""
msgid "Iteration removed"
msgstr ""
+msgid "Iteration updated"
+msgstr ""
+
msgid "Iterations"
msgstr ""
@@ -12476,12 +12820,18 @@ msgstr ""
msgid "January"
msgstr ""
+msgid "Jira Issues"
+msgstr ""
+
msgid "Jira import is already running."
msgstr ""
msgid "Jira integration not configured."
msgstr ""
+msgid "Jira project key is not configured"
+msgstr ""
+
msgid "Jira project: %{importProject}"
msgstr ""
@@ -12662,6 +13012,9 @@ msgstr ""
msgid "Keep divergent refs"
msgstr ""
+msgid "Kerberos access denied"
+msgstr ""
+
msgid "Key"
msgstr ""
@@ -12671,10 +13024,13 @@ msgstr ""
msgid "Key: %{key}"
msgstr ""
-msgid "Keyboard Shortcuts"
+msgid "Keyboard shortcuts"
msgstr ""
-msgid "Keyboard shortcuts"
+msgid "Keys"
+msgstr ""
+
+msgid "Ki"
msgstr ""
msgid "Kubernetes"
@@ -12716,6 +13072,9 @@ msgstr ""
msgid "LDAP"
msgstr ""
+msgid "LDAP Synchronization"
+msgstr ""
+
msgid "LDAP settings"
msgstr ""
@@ -12941,12 +13300,18 @@ msgstr ""
msgid "Learn more about custom project templates"
msgstr ""
+msgid "Learn more about deploying to AWS"
+msgstr ""
+
msgid "Learn more about deploying to a cluster"
msgstr ""
msgid "Learn more about group-level project templates"
msgstr ""
+msgid "Learn more about job dependencies"
+msgstr ""
+
msgid "Learn more about signing commits"
msgstr ""
@@ -12998,9 +13363,21 @@ msgstr ""
msgid "License History"
msgstr ""
+msgid "License ID:"
+msgstr ""
+
+msgid "License URL"
+msgstr ""
+
msgid "License-Check"
msgstr ""
+msgid "LicenseCompliance|%{docLinkStart}License Approvals%{docLinkEnd} are active"
+msgstr ""
+
+msgid "LicenseCompliance|%{docLinkStart}License Approvals%{docLinkEnd} are inactive"
+msgstr ""
+
msgid "LicenseCompliance|Add a license"
msgstr ""
@@ -13022,9 +13399,15 @@ msgstr ""
msgid "LicenseCompliance|Deny"
msgstr ""
+msgid "LicenseCompliance|Learn more about %{linkStart}License Approvals%{linkEnd}"
+msgstr ""
+
msgid "LicenseCompliance|License"
msgstr ""
+msgid "LicenseCompliance|License Approvals"
+msgstr ""
+
msgid "LicenseCompliance|License Compliance detected %d license and policy violation for the source branch only; approval required"
msgid_plural "LicenseCompliance|License Compliance detected %d licenses and policy violations for the source branch only; approval required"
msgstr[0] ""
@@ -13250,6 +13633,9 @@ msgstr ""
msgid "List your Bitbucket Server repositories"
msgstr ""
+msgid "Lists"
+msgstr ""
+
msgid "Live preview"
msgstr ""
@@ -13433,6 +13819,9 @@ msgstr ""
msgid "Manage project labels"
msgstr ""
+msgid "Manage storage usage"
+msgstr ""
+
msgid "Manage two-factor authentication"
msgstr ""
@@ -13448,6 +13837,9 @@ msgstr ""
msgid "Manifest file import"
msgstr ""
+msgid "Manifest import"
+msgstr ""
+
msgid "Manual job"
msgstr ""
@@ -13595,6 +13987,12 @@ msgstr ""
msgid "Maximum field length"
msgstr ""
+msgid "Maximum file size is 2MB. Please select a smaller file."
+msgstr ""
+
+msgid "Maximum import size (MB)"
+msgstr ""
+
msgid "Maximum job timeout"
msgstr ""
@@ -13634,6 +14032,9 @@ msgstr ""
msgid "Maximum size of Elasticsearch bulk indexing requests."
msgstr ""
+msgid "Maximum size of import files."
+msgstr ""
+
msgid "Maximum size of individual attachments in comments."
msgstr ""
@@ -13685,6 +14086,9 @@ msgstr ""
msgid "Merge (when the pipeline succeeds)"
msgstr ""
+msgid "Merge Conflicts"
+msgstr ""
+
msgid "Merge Request"
msgstr ""
@@ -13811,6 +14215,9 @@ msgstr ""
msgid "MergeRequests|Squash task canceled: another squash is already in progress."
msgstr ""
+msgid "MergeRequests|This project does not allow squashing commits when merge requests are accepted."
+msgstr ""
+
msgid "MergeRequests|Thread stays resolved"
msgstr ""
@@ -13964,6 +14371,12 @@ msgstr ""
msgid "MetricsSettings|Add a button to the metrics dashboard linking directly to your existing external dashboard."
msgstr ""
+msgid "MetricsSettings|Choose whether to display dashboard metrics in UTC or the user's local timezone."
+msgstr ""
+
+msgid "MetricsSettings|Dashboard timezone"
+msgstr ""
+
msgid "MetricsSettings|External dashboard URL"
msgstr ""
@@ -13973,6 +14386,12 @@ msgstr ""
msgid "MetricsSettings|Metrics Dashboard"
msgstr ""
+msgid "MetricsSettings|UTC (Coordinated Universal Time)"
+msgstr ""
+
+msgid "MetricsSettings|User's local timezone"
+msgstr ""
+
msgid "Metrics|Add metric"
msgstr ""
@@ -13988,6 +14407,9 @@ msgstr ""
msgid "Metrics|Create metric"
msgstr ""
+msgid "Metrics|Current"
+msgstr ""
+
msgid "Metrics|Delete metric"
msgstr ""
@@ -14039,6 +14461,9 @@ msgstr ""
msgid "Metrics|Max"
msgstr ""
+msgid "Metrics|Min"
+msgstr ""
+
msgid "Metrics|Must be a valid PromQL query."
msgstr ""
@@ -14054,6 +14479,9 @@ msgstr ""
msgid "Metrics|Refresh dashboard"
msgstr ""
+msgid "Metrics|Select a value"
+msgstr ""
+
msgid "Metrics|Star dashboard"
msgstr ""
@@ -14072,12 +14500,18 @@ msgstr ""
msgid "Metrics|There was an error getting annotations information."
msgstr ""
+msgid "Metrics|There was an error getting dashboard validation warnings information."
+msgstr ""
+
msgid "Metrics|There was an error getting deployment information."
msgstr ""
msgid "Metrics|There was an error getting environments information."
msgstr ""
+msgid "Metrics|There was an error getting options for variable \"%{name}\"."
+msgstr ""
+
msgid "Metrics|There was an error trying to validate your query"
msgstr ""
@@ -14120,6 +14554,9 @@ msgstr ""
msgid "Metrics|You're about to permanently delete this metric. This cannot be undone."
msgstr ""
+msgid "Metrics|Your dashboard schema is invalid. Edit the dashboard to correct the YAML schema."
+msgstr ""
+
msgid "Metrics|e.g. HTTP requests"
msgstr ""
@@ -14135,12 +14572,18 @@ msgstr ""
msgid "Metrics|e.g. req/sec"
msgstr ""
+msgid "Mi"
+msgstr ""
+
msgid "Microsoft Azure"
msgstr ""
msgid "Middleman project with Static Site Editor support"
msgstr ""
+msgid "Migrate your data from an external source like GitHub, Bitbucket, or another instance of GitLab."
+msgstr ""
+
msgid "Migrated %{success_count}/%{total_count} files."
msgstr ""
@@ -14276,6 +14719,9 @@ msgstr ""
msgid "Minimum capacity to be available before we schedule more mirrors preemptively."
msgstr ""
+msgid "Minimum interval in days"
+msgstr ""
+
msgid "Minimum length is %{minimum_password_length} characters"
msgstr ""
@@ -14462,6 +14908,12 @@ msgstr ""
msgid "Multiple uploaders found: %{uploader_types}"
msgstr ""
+msgid "Must match with the %{codeStart}external_url%{codeEnd} in %{codeStart}/etc/gitlab/gitlab.rb%{codeEnd}."
+msgstr ""
+
+msgid "Must match with the %{codeStart}geo_node_name%{codeEnd} in %{codeStart}/etc/gitlab/gitlab.rb%{codeEnd}. %{linkStart}More information%{linkEnd}"
+msgstr ""
+
msgid "My Awesome Group"
msgstr ""
@@ -14576,10 +15028,16 @@ msgstr ""
msgid "New Application"
msgstr ""
+msgid "New Branch"
+msgstr ""
+
+msgid "New Deploy Key"
+msgstr ""
+
msgid "New Environment"
msgstr ""
-msgid "New Geo Node"
+msgid "New File"
msgstr ""
msgid "New Group"
@@ -14597,15 +15055,15 @@ msgstr[0] ""
msgstr[1] ""
msgstr[2] ""
-msgid "New Iteration"
-msgstr ""
-
msgid "New Jira import"
msgstr ""
msgid "New Label"
msgstr ""
+msgid "New Merge Request"
+msgstr ""
+
msgid "New Milestone"
msgstr ""
@@ -14624,6 +15082,9 @@ msgstr ""
msgid "New Snippet"
msgstr ""
+msgid "New User"
+msgstr ""
+
msgid "New branch"
msgstr ""
@@ -14666,6 +15127,9 @@ msgstr ""
msgid "New issue title"
msgstr ""
+msgid "New iteration"
+msgstr ""
+
msgid "New iteration created"
msgstr ""
@@ -14843,6 +15307,12 @@ msgstr ""
msgid "No grouping"
msgstr ""
+msgid "No iteration"
+msgstr ""
+
+msgid "No iterations to show"
+msgstr ""
+
msgid "No job log"
msgstr ""
@@ -14885,6 +15355,9 @@ msgstr ""
msgid "No other labels with such name or description"
msgstr ""
+msgid "No panels matching properties %{opts}"
+msgstr ""
+
msgid "No parent group"
msgstr ""
@@ -14918,6 +15391,9 @@ msgstr ""
msgid "No schedules"
msgstr ""
+msgid "No stack trace for this error"
+msgstr ""
+
msgid "No starrers matched your search"
msgstr ""
@@ -14930,7 +15406,7 @@ msgstr ""
msgid "No test coverage"
msgstr ""
-msgid "No thanks, don't show this again"
+msgid "No thanks"
msgstr ""
msgid "No vulnerabilities present"
@@ -14945,9 +15421,6 @@ msgstr ""
msgid "No, directly import the existing email addresses and usernames."
msgstr ""
-msgid "No, not interested right now"
-msgstr ""
-
msgid "No. of commits"
msgstr ""
@@ -14993,9 +15466,6 @@ msgstr ""
msgid "Not found."
msgstr ""
-msgid "Not helpful"
-msgstr ""
-
msgid "Not now"
msgstr ""
@@ -15152,6 +15622,9 @@ msgstr ""
msgid "Notifications on"
msgstr ""
+msgid "Notify users by email when sign-in location is not recognized"
+msgstr ""
+
msgid "Nov"
msgstr ""
@@ -15218,9 +15691,6 @@ msgstr ""
msgid "Oh no!"
msgstr ""
-msgid "Ok let's go"
-msgstr ""
-
msgid "Oldest first"
msgstr ""
@@ -15233,16 +15703,49 @@ msgstr ""
msgid "On track"
msgstr ""
+msgid "OnDemandScans|Attached branch"
+msgstr ""
+
+msgid "OnDemandScans|Attached branch is where the scan job runs."
+msgstr ""
+
+msgid "OnDemandScans|Could not run the scan. Please try again."
+msgstr ""
+
msgid "OnDemandScans|Create new DAST scan"
msgstr ""
+msgid "OnDemandScans|DAST will scan the target URL and any discovered sub URLs."
+msgstr ""
+
+msgid "OnDemandScans|New on-demand DAST scan"
+msgstr ""
+
msgid "OnDemandScans|On-demand Scans"
msgstr ""
+msgid "OnDemandScans|On-demand scans run outside the DevOps cycle and find vulnerabilities in your projects. %{learnMoreLinkStart}Learn more%{learnMoreLinkEnd}"
+msgstr ""
+
+msgid "OnDemandScans|Only a passive scan can be performed on demand."
+msgstr ""
+
+msgid "OnDemandScans|Passive DAST Scan"
+msgstr ""
+
+msgid "OnDemandScans|Please enter a valid URL format, ex: http://www.example.com/home"
+msgstr ""
+
+msgid "OnDemandScans|Run this scan"
+msgstr ""
+
+msgid "OnDemandScans|Scan mode"
+msgstr ""
+
msgid "OnDemandScans|Schedule or run scans immediately against target sites. Currently available on-demand scan type: DAST. %{helpLinkStart}More information%{helpLinkEnd}"
msgstr ""
-msgid "Onboarding"
+msgid "OnDemandScans|Target URL"
msgstr ""
msgid "Once imported, repositories can be mirrored over SSH. Read more %{link_start}here%{link_end}."
@@ -15269,6 +15772,9 @@ msgstr ""
msgid "One or more of you personal access tokens were revoked"
msgstr ""
+msgid "One or more of your %{provider} projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
+msgstr ""
+
msgid "One or more of your Bitbucket projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
msgstr ""
@@ -15770,6 +16276,9 @@ msgstr ""
msgid "Parent epic is not present."
msgstr ""
+msgid "Parsing error for param :embed_json. %{message}"
+msgstr ""
+
msgid "Part of merge request changes"
msgstr ""
@@ -15782,6 +16291,9 @@ msgstr ""
msgid "Passed"
msgstr ""
+msgid "Passed on"
+msgstr ""
+
msgid "Password"
msgstr ""
@@ -15866,6 +16378,9 @@ msgstr ""
msgid "Perform common operations on GitLab project"
msgstr ""
+msgid "Performance and resource management"
+msgstr ""
+
msgid "Performance optimization"
msgstr ""
@@ -16073,7 +16588,7 @@ msgstr ""
msgid "Pipelines|Something went wrong while cleaning runners cache."
msgstr ""
-msgid "Pipelines|There are currently no %{scope} pipelines."
+msgid "Pipelines|There are currently no finished pipelines."
msgstr ""
msgid "Pipelines|There are currently no pipelines."
@@ -16184,6 +16699,9 @@ msgstr ""
msgid "Pipeline|Stop pipeline #%{pipelineId}?"
msgstr ""
+msgid "Pipeline|Tag name"
+msgstr ""
+
msgid "Pipeline|Trigger author"
msgstr ""
@@ -16265,12 +16783,18 @@ msgstr ""
msgid "Please check your email (%{email}) to verify that you own this address and unlock the power of CI/CD. Didn't receive it? %{resend_link}. Wrong email address? %{update_link}."
msgstr ""
+msgid "Please choose a file"
+msgstr ""
+
msgid "Please choose a group URL with no special characters."
msgstr ""
msgid "Please complete your profile with email address"
msgstr ""
+msgid "Please convert %{linkStart}them to Git%{linkEnd}, and go through the %{linkToImportFlow} again."
+msgstr ""
+
msgid "Please convert them to %{link_to_git}, and go through the %{link_to_import_flow} again."
msgstr ""
@@ -16328,6 +16852,9 @@ msgstr ""
msgid "Please provide a valid email address."
msgstr ""
+msgid "Please provide attributes to update"
+msgstr ""
+
msgid "Please refer to <a href=\"%{docs_url}\">%{docs_url}</a>"
msgstr ""
@@ -17063,6 +17590,9 @@ msgstr ""
msgid "Project has too many %{label_for_message} to search"
msgstr ""
+msgid "Project is required when cluster_type is :project"
+msgstr ""
+
msgid "Project members"
msgstr ""
@@ -17180,7 +17710,40 @@ msgstr ""
msgid "ProjectService|Comment"
msgstr ""
-msgid "ProjectService|Comment will be posted on each event"
+msgid "ProjectService|Event will be triggered by a push to the repository"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a commit is created/updated"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a confidential issue is created/updated/closed"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a deployment finishes"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a merge request is created/updated/merged"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a new tag is pushed to the repository"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a new, unique alert is recorded"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a pipeline status changes"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a wiki page is created/updated"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when an issue is created/updated/closed"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when someone adds a comment"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when someone adds a comment on a confidential issue"
msgstr ""
msgid "ProjectService|Perform common operations on GitLab project: %{project_name}"
@@ -17231,6 +17794,9 @@ msgstr ""
msgid "ProjectSettings|Enable 'Delete source branch' option by default"
msgstr ""
+msgid "ProjectSettings|Enable merge trains and pipelines for merged results"
+msgstr ""
+
msgid "ProjectSettings|Every merge creates a merge commit"
msgstr ""
@@ -17261,7 +17827,7 @@ msgstr ""
msgid "ProjectSettings|Forks"
msgstr ""
-msgid "ProjectSettings|Git Large File Storage"
+msgid "ProjectSettings|Git Large File Storage (LFS)"
msgstr ""
msgid "ProjectSettings|Internal"
@@ -17297,9 +17863,6 @@ msgstr ""
msgid "ProjectSettings|Merge options"
msgstr ""
-msgid "ProjectSettings|Merge pipelines will try to validate the post-merge result prior to merging"
-msgstr ""
-
msgid "ProjectSettings|Merge requests"
msgstr ""
@@ -17327,6 +17890,9 @@ msgstr ""
msgid "ProjectSettings|Pipelines"
msgstr ""
+msgid "ProjectSettings|Pipelines for merge requests must be enabled in the CI/CD configuration file, or pipelines could be unresolvable or dropped"
+msgstr ""
+
msgid "ProjectSettings|Pipelines must succeed"
msgstr ""
@@ -17354,6 +17920,9 @@ msgstr ""
msgid "ProjectSettings|Show link to create/view merge request when pushing from the command line"
msgstr ""
+msgid "ProjectSettings|Skipped pipelines are considered successful"
+msgstr ""
+
msgid "ProjectSettings|Snippets"
msgstr ""
@@ -17369,6 +17938,9 @@ msgstr ""
msgid "ProjectSettings|These checks must pass before merge requests can be merged"
msgstr ""
+msgid "ProjectSettings|This introduces the risk of merging changes that will not pass the pipeline."
+msgstr ""
+
msgid "ProjectSettings|This setting is applied on the server level and can be overridden by an admin."
msgstr ""
@@ -17546,15 +18118,27 @@ msgstr ""
msgid "ProjectsNew|Blank project"
msgstr ""
+msgid "ProjectsNew|Connect your external repository to GitLab CI/CD."
+msgstr ""
+
msgid "ProjectsNew|Contact an administrator to enable options for importing your project."
msgstr ""
msgid "ProjectsNew|Create"
msgstr ""
+msgid "ProjectsNew|Create a blank project to house your files, plan your work, and collaborate on code, among other things."
+msgstr ""
+
+msgid "ProjectsNew|Create blank project"
+msgstr ""
+
msgid "ProjectsNew|Create from template"
msgstr ""
+msgid "ProjectsNew|Create new project"
+msgstr ""
+
msgid "ProjectsNew|Creating project & repository."
msgstr ""
@@ -17579,6 +18163,9 @@ msgstr ""
msgid "ProjectsNew|Project description %{tag_start}(optional)%{tag_end}"
msgstr ""
+msgid "ProjectsNew|Run CI/CD for external repository"
+msgstr ""
+
msgid "ProjectsNew|Template"
msgstr ""
@@ -17840,6 +18427,9 @@ msgstr ""
msgid "Protected Branch"
msgstr ""
+msgid "Protected Branches"
+msgstr ""
+
msgid "Protected Environment"
msgstr ""
@@ -17852,6 +18442,9 @@ msgstr ""
msgid "Protected Tag"
msgstr ""
+msgid "Protected Tags"
+msgstr ""
+
msgid "Protected branches"
msgstr ""
@@ -17951,6 +18544,9 @@ msgstr ""
msgid "Provider"
msgstr ""
+msgid "Proxy support for this API is not available currently"
+msgstr ""
+
msgid "Pseudonymizer data collection"
msgstr ""
@@ -17975,6 +18571,9 @@ msgstr ""
msgid "Publish to status page"
msgstr ""
+msgid "Published on status page"
+msgstr ""
+
msgid "Publishes this issue to the associated status page."
msgstr ""
@@ -18149,6 +18748,9 @@ msgstr ""
msgid "Recipe"
msgstr ""
+msgid "Reconfigure"
+msgstr ""
+
msgid "Recover hidden stage"
msgstr ""
@@ -18236,6 +18838,9 @@ msgstr ""
msgid "Registration|Your profile"
msgstr ""
+msgid "Regulate approvals by authors/committers, based on compliance frameworks. Can be changed only at the instance level."
+msgstr ""
+
msgid "Rejected (closed)"
msgstr ""
@@ -18287,15 +18892,24 @@ msgstr ""
msgid "ReleaseAssetLinkType|Image"
msgstr ""
+msgid "ReleaseAssetLinkType|Images"
+msgstr ""
+
msgid "ReleaseAssetLinkType|Other"
msgstr ""
msgid "ReleaseAssetLinkType|Package"
msgstr ""
+msgid "ReleaseAssetLinkType|Packages"
+msgstr ""
+
msgid "ReleaseAssetLinkType|Runbook"
msgstr ""
+msgid "ReleaseAssetLinkType|Runbooks"
+msgstr ""
+
msgid "Releases"
msgstr ""
@@ -18308,6 +18922,9 @@ msgstr ""
msgid "Releases documentation"
msgstr ""
+msgid "Releases|New Release"
+msgstr ""
+
msgid "Release|Something went wrong while getting the release details"
msgstr ""
@@ -18380,6 +18997,9 @@ msgstr ""
msgid "Remove fork relationship"
msgstr ""
+msgid "Remove from batch"
+msgstr ""
+
msgid "Remove from board"
msgstr ""
@@ -18569,6 +19189,12 @@ msgstr ""
msgid "Replication"
msgstr ""
+msgid "Replication enabled"
+msgstr ""
+
+msgid "Replication paused"
+msgstr ""
+
msgid "Reply by email"
msgstr ""
@@ -18596,15 +19222,15 @@ msgstr ""
msgid "Reported %{timeAgo} by %{reportedBy}"
msgstr ""
-msgid "Reported Resource Changes: %{addNum} to add, %{changeNum} to change, %{deleteNum} to delete"
-msgstr ""
-
msgid "Reporter"
msgstr ""
msgid "Reporting"
msgstr ""
+msgid "Reports"
+msgstr ""
+
msgid "Reports|%{combinedString} and %{resolvedString}"
msgstr ""
@@ -18644,6 +19270,9 @@ msgstr ""
msgid "Reports|Failure"
msgstr ""
+msgid "Reports|Identifier"
+msgstr ""
+
msgid "Reports|Metrics reports are loading"
msgstr ""
@@ -18656,6 +19285,9 @@ msgstr ""
msgid "Reports|Metrics reports failed loading results"
msgstr ""
+msgid "Reports|Scanner"
+msgstr ""
+
msgid "Reports|Severity"
msgstr ""
@@ -18704,15 +19336,33 @@ msgstr ""
msgid "Repository cleanup has started. You will receive an email once the cleanup operation is complete."
msgstr ""
+msgid "Repository files count over the limit"
+msgstr ""
+
+msgid "Repository has an invalid default branch name."
+msgstr ""
+
+msgid "Repository has more than one branch."
+msgstr ""
+
msgid "Repository has no locks."
msgstr ""
+msgid "Repository has tags."
+msgstr ""
+
msgid "Repository maintenance"
msgstr ""
msgid "Repository mirroring"
msgstr ""
+msgid "Repository must contain at least 1 file."
+msgstr ""
+
+msgid "Repository size is above the limit."
+msgstr ""
+
msgid "Repository static objects"
msgstr ""
@@ -18722,7 +19372,7 @@ msgstr ""
msgid "Repository sync capacity"
msgstr ""
-msgid "Repository: %{counter_repositories} / Wikis: %{counter_wikis} / Build Artifacts: %{counter_build_artifacts} / LFS: %{counter_lfs_objects}"
+msgid "Repository: %{counter_repositories} / Wikis: %{counter_wikis} / Build Artifacts: %{counter_build_artifacts} / LFS: %{counter_lfs_objects} / Snippets: %{counter_snippets}"
msgstr ""
msgid "RepositorySettingsAccessLevel|Select"
@@ -18734,6 +19384,9 @@ msgstr ""
msgid "Request Headers"
msgstr ""
+msgid "Request details"
+msgstr ""
+
msgid "Request parameter %{param} is missing."
msgstr ""
@@ -18842,6 +19495,9 @@ msgstr ""
msgid "Reset template"
msgstr ""
+msgid "Reset to project defaults"
+msgstr ""
+
msgid "Resetting the authorization key for this project will require updating the authorization key in every alert source it is enabled in."
msgstr ""
@@ -18938,9 +19594,6 @@ msgstr ""
msgid "Resume"
msgstr ""
-msgid "Resume replication"
-msgstr ""
-
msgid "Resync"
msgstr ""
@@ -19574,6 +20227,9 @@ msgstr ""
msgid "SecurityReports|False positive"
msgstr ""
+msgid "SecurityReports|Group Security Dashboard"
+msgstr ""
+
msgid "SecurityReports|Hide dismissed"
msgstr ""
@@ -19628,10 +20284,13 @@ msgstr ""
msgid "SecurityReports|Remove project from dashboard"
msgstr ""
-msgid "SecurityReports|Report type"
+msgid "SecurityReports|Return to dashboard"
msgstr ""
-msgid "SecurityReports|Return to dashboard"
+msgid "SecurityReports|Scan details"
+msgstr ""
+
+msgid "SecurityReports|Scanner type"
msgstr ""
msgid "SecurityReports|Security Dashboard"
@@ -19688,6 +20347,9 @@ msgstr ""
msgid "SecurityReports|There was an error while generating the report."
msgstr ""
+msgid "SecurityReports|Unable to add %{invalidProjectsMessage}"
+msgstr ""
+
msgid "SecurityReports|Unable to add %{invalidProjects}"
msgstr ""
@@ -19802,6 +20464,9 @@ msgstr ""
msgid "Select due date"
msgstr ""
+msgid "Select file"
+msgstr ""
+
msgid "Select group or project"
msgstr ""
@@ -19865,9 +20530,6 @@ msgstr ""
msgid "Select the branch you want to set as the default for this project. All merge requests and commits will automatically be made against this branch unless you specify a different one."
msgstr ""
-msgid "Select the configured storage available for new repositories to be placed on."
-msgstr ""
-
msgid "Select the custom project template source group."
msgstr ""
@@ -20060,12 +20722,6 @@ msgstr ""
msgid "Service Desk is enabled but not yet active"
msgstr ""
-msgid "Service Desk is off"
-msgstr ""
-
-msgid "Service Desk is on"
-msgstr ""
-
msgid "Service Templates"
msgstr ""
@@ -20153,6 +20809,15 @@ msgstr ""
msgid "Set the milestone to %{milestone_reference}."
msgstr ""
+msgid "Set the number of concurrent requests this secondary node will make to the primary node while backfilling."
+msgstr ""
+
+msgid "Set the synchronization and verification capacity for the secondary node."
+msgstr ""
+
+msgid "Set the timeout in seconds to send a secondary node status to the primary and IPs allowed for the secondary nodes."
+msgstr ""
+
msgid "Set time estimate"
msgstr ""
@@ -20192,6 +20857,9 @@ msgstr ""
msgid "Set weight to %{weight}."
msgstr ""
+msgid "Set what should be replicated by choosing specific projects or groups by the secondary node."
+msgstr ""
+
msgid "SetPasswordToCloneLink|set a password"
msgstr ""
@@ -20318,6 +20986,9 @@ msgstr ""
msgid "Show complete raw log"
msgstr ""
+msgid "Show details"
+msgstr ""
+
msgid "Show file browser"
msgstr ""
@@ -20327,10 +20998,13 @@ msgstr ""
msgid "Show latest version"
msgstr ""
+msgid "Show list"
+msgstr ""
+
msgid "Show me everything"
msgstr ""
-msgid "Show me how"
+msgid "Show me how to add a pipeline"
msgstr ""
msgid "Show me more advanced stuff"
@@ -20381,9 +21055,6 @@ msgstr ""
msgid "Showing all issues"
msgstr ""
-msgid "Showing all labels"
-msgstr ""
-
msgid "Showing last %{size} of log -"
msgstr ""
@@ -20495,9 +21166,6 @@ msgstr ""
msgid "Skip outdated deployment jobs"
msgstr ""
-msgid "Skip this for now"
-msgstr ""
-
msgid "Skipped"
msgstr ""
@@ -20642,6 +21310,9 @@ msgstr ""
msgid "Something went wrong while adding your award. Please try again."
msgstr ""
+msgid "Something went wrong while applying the batch of suggestions. Please try again."
+msgstr ""
+
msgid "Something went wrong while applying the suggestion. Please try again."
msgstr ""
@@ -20744,10 +21415,13 @@ msgstr ""
msgid "Something went wrong while updating a requirement."
msgstr ""
+msgid "Something went wrong while updating assignees"
+msgstr ""
+
msgid "Something went wrong while updating your list settings"
msgstr ""
-msgid "Something went wrong with your automatic subscription renewal"
+msgid "Something went wrong with your automatic subscription renewal."
msgstr ""
msgid "Something went wrong, unable to add %{project} to dashboard"
@@ -20765,6 +21439,9 @@ msgstr ""
msgid "Something went wrong, unable to search projects"
msgstr ""
+msgid "Something went wrong."
+msgstr ""
+
msgid "Something went wrong. Please try again."
msgstr ""
@@ -20948,6 +21625,9 @@ msgstr ""
msgid "Source code"
msgstr ""
+msgid "Source code (%{fileExtension})"
+msgstr ""
+
msgid "Source is not available"
msgstr ""
@@ -21032,6 +21712,9 @@ msgstr ""
msgid "Stack trace"
msgstr ""
+msgid "Stacktrace snippet"
+msgstr ""
+
msgid "Stage"
msgstr ""
@@ -21137,6 +21820,9 @@ msgstr ""
msgid "Start thread & reopen %{noteable_name}"
msgstr ""
+msgid "Start using Directed Acyclic Graphs (DAG)"
+msgstr ""
+
msgid "Start your Free Gold Trial"
msgstr ""
@@ -21599,6 +22285,21 @@ msgstr ""
msgid "SuggestedColors|Very pale orange"
msgstr ""
+msgid "Suggestion is not applicable as the suggestion was not found."
+msgstr ""
+
+msgid "Suggestions are not applicable as one or more suggestions were not found."
+msgstr ""
+
+msgid "Suggestions are not applicable as their lines cannot overlap."
+msgstr ""
+
+msgid "Suggestions must all be on the same branch."
+msgstr ""
+
+msgid "Suggestions that change line count can't be added to batches, yet."
+msgstr ""
+
msgid "Suggestions:"
msgstr ""
@@ -21641,9 +22342,6 @@ msgstr ""
msgid "Synchronization disabled"
msgstr ""
-msgid "Synchronization of container repositories is disabled."
-msgstr ""
-
msgid "System"
msgstr ""
@@ -21824,6 +22522,36 @@ msgstr ""
msgid "Terms of Service and Privacy Policy"
msgstr ""
+msgid "Terraform|%{number} Terraform report failed to generate"
+msgid_plural "Terraform|%{number} Terraform reports failed to generate"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+
+msgid "Terraform|%{number} Terraform report was generated in your pipelines"
+msgid_plural "Terraform|%{number} Terraform reports were generated in your pipelines"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+
+msgid "Terraform|A Terraform report failed to generate."
+msgstr ""
+
+msgid "Terraform|A Terraform report was generated in your pipelines."
+msgstr ""
+
+msgid "Terraform|Generating the report caused an error."
+msgstr ""
+
+msgid "Terraform|Reported Resource Changes: %{addNum} to add, %{changeNum} to change, %{deleteNum} to delete"
+msgstr ""
+
+msgid "Terraform|The Terraform report %{name} failed to generate."
+msgstr ""
+
+msgid "Terraform|The Terraform report %{name} was generated in your pipelines."
+msgstr ""
+
msgid "Test"
msgstr ""
@@ -21851,7 +22579,7 @@ msgstr ""
msgid "TestHooks|Ensure the project has CI pipelines."
msgstr ""
-msgid "TestHooks|Ensure the project has at least one commit."
+msgid "TestHooks|Ensure the project has deployments."
msgstr ""
msgid "TestHooks|Ensure the project has issues."
@@ -21953,7 +22681,7 @@ msgstr ""
msgid "The Prometheus server responded with \"bad request\". Please check your queries are correct and are supported in your Prometheus version. %{documentationLink}"
msgstr ""
-msgid "The URL defined on the primary node that secondary nodes should use to contact it. Defaults to URL"
+msgid "The URL defined on the primary node that secondary nodes should use to contact it."
msgstr ""
msgid "The URL to use for connecting to Elasticsearch. Use a comma-separated list to support clustering (e.g., \"http://localhost:9200, http://localhost:9201\")."
@@ -21962,6 +22690,9 @@ msgstr ""
msgid "The X509 Certificate to use when mutual TLS is required to communicate with the external authorization service. If left blank, the server certificate is still validated when accessing over HTTPS."
msgstr ""
+msgid "The above settings apply to all projects with the selected compliance framework(s)."
+msgstr ""
+
msgid "The amount of seconds after which a request to get a secondary node status will time out."
msgstr ""
@@ -22262,9 +22993,6 @@ msgstr ""
msgid "The total stage shows the time it takes between creating an issue and deploying the code to production. The data will be automatically added once you have completed the full idea to production cycle."
msgstr ""
-msgid "The unique identifier for the Geo node. Must match %{geoNodeName} if it is set in gitlab.rb, otherwise it must match %{externalUrl} with a trailing slash"
-msgstr ""
-
msgid "The update action will time out after %{number_of_minutes} minutes. For big repositories, use a clone/push combination."
msgstr ""
@@ -22289,9 +23017,6 @@ msgstr ""
msgid "The user you are trying to deactivate has been active in the past %{minimum_inactive_days} days and cannot be deactivated"
msgstr ""
-msgid "The user-facing URL of the Geo node"
-msgstr ""
-
msgid "The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6."
msgstr ""
@@ -22382,6 +23107,9 @@ msgstr ""
msgid "There was a problem fetching project branches."
msgstr ""
+msgid "There was a problem fetching project tags."
+msgstr ""
+
msgid "There was a problem fetching project users."
msgstr ""
@@ -22433,6 +23161,9 @@ msgstr ""
msgid "There was an error fetching the %{replicableType}"
msgstr ""
+msgid "There was an error fetching the Geo Settings"
+msgstr ""
+
msgid "There was an error fetching the Node's Groups"
msgstr ""
@@ -22553,6 +23284,12 @@ msgstr ""
msgid "This %{issuable} is locked. Only <strong>project members</strong> can comment."
msgstr ""
+msgid "This %{noteableTypeText} is %{confidentialLinkStart}confidential%{linkEnd} and %{lockedLinkStart}locked%{linkEnd}."
+msgstr ""
+
+msgid "This %{noteableTypeText} is locked."
+msgstr ""
+
msgid "This %{viewer} could not be displayed because %{reason}. You can %{options} instead."
msgstr ""
@@ -22571,6 +23308,9 @@ msgstr ""
msgid "This action can lead to data loss. To prevent accidental actions we ask you to confirm your intention."
msgstr ""
+msgid "This also resolves all related threads"
+msgstr ""
+
msgid "This also resolves the discussion"
msgstr ""
@@ -22661,6 +23401,9 @@ msgstr ""
msgid "This epic does not exist or you don't have sufficient permission."
msgstr ""
+msgid "This feature is currently in beta. We invite you to %{linkStart}give feedback%{linkEnd}."
+msgstr ""
+
msgid "This feature requires local storage to be enabled"
msgstr ""
@@ -22694,7 +23437,7 @@ msgstr ""
msgid "This is a Work in Progress"
msgstr ""
-msgid "This is a confidential issue."
+msgid "This is a confidential %{noteableTypeText}."
msgstr ""
msgid "This is a delayed job to run in %{remainingTime}"
@@ -22721,9 +23464,6 @@ msgstr ""
msgid "This is your current session"
msgstr ""
-msgid "This issue is %{confidentialLinkStart}confidential%{linkEnd} and %{lockedLinkStart}locked%{linkEnd}."
-msgstr ""
-
msgid "This issue is confidential"
msgstr ""
@@ -22733,9 +23473,6 @@ msgstr ""
msgid "This issue is in a child epic of the filtered epic"
msgstr ""
-msgid "This issue is locked."
-msgstr ""
-
msgid "This job depends on other jobs with expired/erased artifacts: %{invalid_dependencies}"
msgstr ""
@@ -22835,6 +23572,9 @@ msgstr ""
msgid "This license has already expired."
msgstr ""
+msgid "This link points to external content"
+msgstr ""
+
msgid "This may expose confidential information as the selected fork is in another namespace that can have other members."
msgstr ""
@@ -22862,6 +23602,9 @@ msgstr ""
msgid "This page will be removed in a future release."
msgstr ""
+msgid "This pipeline does not use the %{codeStart}needs%{codeEnd} keyword and can't be represented as a directed acyclic graph."
+msgstr ""
+
msgid "This pipeline makes use of a predefined CI/CD configuration enabled by %{strongStart}Auto DevOps.%{strongEnd}"
msgstr ""
@@ -23090,6 +23833,9 @@ msgstr ""
msgid "Time from last commit to merge"
msgstr ""
+msgid "Time in seconds"
+msgstr ""
+
msgid "Time in seconds GitLab will wait for a response from the external service. When the service does not respond in time, access will be denied."
msgstr ""
@@ -23513,9 +24259,15 @@ msgstr ""
msgid "Total artifacts size: %{total_size}"
msgstr ""
+msgid "Total cores (CPUs)"
+msgstr ""
+
msgid "Total issues"
msgstr ""
+msgid "Total memory (GB)"
+msgstr ""
+
msgid "Total test time for all commits/merges"
msgstr ""
@@ -23717,6 +24469,9 @@ msgstr ""
msgid "U2F only works with HTTPS-enabled websites. Contact your administrator for more details."
msgstr ""
+msgid "UI Development Kit"
+msgstr ""
+
msgid "URL"
msgstr ""
@@ -23789,9 +24544,6 @@ msgstr ""
msgid "Unable to load the merge request widget. Try reloading the page."
msgstr ""
-msgid "Unable to process group import file"
-msgstr ""
-
msgid "Unable to resolve"
msgstr ""
@@ -23807,6 +24559,9 @@ msgstr ""
msgid "Unable to sign you in to the group with SAML due to \"%{reason}\""
msgstr ""
+msgid "Unable to suggest a path. Please refresh and try again."
+msgstr ""
+
msgid "Unable to update label prioritization at this time"
msgstr ""
@@ -23822,6 +24577,9 @@ msgstr ""
msgid "Unarchiving the project will restore people's ability to make changes to it. The repository can be committed to, and issues, comments, and other entities can be created. %{strong_start}Once active, this project shows up in the search and on the dashboard.%{strong_end}"
msgstr ""
+msgid "Unassign from commenting user"
+msgstr ""
+
msgid "Unblock"
msgstr ""
@@ -23897,6 +24655,9 @@ msgstr ""
msgid "Unreachable"
msgstr ""
+msgid "Unrecognized cluster type"
+msgstr ""
+
msgid "Unresolve"
msgstr ""
@@ -23957,6 +24718,9 @@ msgstr ""
msgid "Unsubscribes from this %{quick_action_target}."
msgstr ""
+msgid "Unsupported todo type passed. Supported todo types are: %{todo_types}"
+msgstr ""
+
msgid "Until"
msgstr ""
@@ -23984,6 +24748,9 @@ msgstr ""
msgid "Update approval rule"
msgstr ""
+msgid "Update approvers"
+msgstr ""
+
msgid "Update failed"
msgstr ""
@@ -23993,6 +24760,9 @@ msgstr ""
msgid "Update it"
msgstr ""
+msgid "Update iteration"
+msgstr ""
+
msgid "Update now"
msgstr ""
@@ -24038,7 +24808,7 @@ msgstr ""
msgid "Updated at"
msgstr ""
-msgid "Updated to"
+msgid "Updated to %{linkStart}chart v%{linkEnd}"
msgstr ""
msgid "Updating"
@@ -24068,6 +24838,9 @@ msgstr ""
msgid "Upload CSV file"
msgstr ""
+msgid "Upload License"
+msgstr ""
+
msgid "Upload New File"
msgstr ""
@@ -24149,16 +24922,19 @@ msgstr ""
msgid "UsageQuota|Pipelines"
msgstr ""
+msgid "UsageQuota|Purchase more storage"
+msgstr ""
+
msgid "UsageQuota|Repositories"
msgstr ""
msgid "UsageQuota|Repository"
msgstr ""
-msgid "UsageQuota|Storage"
+msgid "UsageQuota|Snippets"
msgstr ""
-msgid "UsageQuota|Storage usage:"
+msgid "UsageQuota|Storage"
msgstr ""
msgid "UsageQuota|This namespace has no projects which use shared runners"
@@ -24191,6 +24967,12 @@ msgstr ""
msgid "UsageQuota|Wikis"
msgstr ""
+msgid "UsageQuota|You used: %{usage} %{limit}"
+msgstr ""
+
+msgid "UsageQuota|out of %{formattedLimit} of your namespace storage"
+msgstr ""
+
msgid "Use %{code_start}::%{code_end} to create a %{link_start}scoped label set%{link_end} (eg. %{code_start}priority::1%{code_end})"
msgstr ""
@@ -24257,6 +25039,9 @@ msgstr ""
msgid "User IDs"
msgstr ""
+msgid "User Lists can only be created and modified with %{linkStart}the API%{linkEnd}"
+msgstr ""
+
msgid "User OAuth applications"
msgstr ""
@@ -24281,6 +25066,9 @@ msgstr ""
msgid "User key was successfully removed."
msgstr ""
+msgid "User list %{name} will be removed. Are you sure?"
+msgstr ""
+
msgid "User map"
msgstr ""
@@ -24302,148 +25090,10 @@ msgstr ""
msgid "User was successfully updated."
msgstr ""
-msgid "UserOnboardingTour|%{activeTour}/%{totalTours}"
-msgstr ""
-
-msgid "UserOnboardingTour|%{completed}/%{total} steps completed"
-msgstr ""
-
-msgid "UserOnboardingTour|%{emphasisStart}Well done!%{emphasisEnd}%{lineBreak}%{lineBreak}That's it for our guided tour, congratulations for making it all the way to the end!%{lineBreak}%{lineBreak}We hope this gave you a good overview of GitLab and how it can help you. We'll now show you how to create your own project and invite your colleagues."
-msgstr ""
-
-msgid "UserOnboardingTour|Adding other members to a project is done through Project Settings. Click on %{emphasisStart}Settings%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Alright, that's it for Commits. Let's take a look at the %{emphasisStart}Branches%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Awesome! Now click on %{emphasisStart}Members%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Click on one of the %{emphasisStart}Compare%{emphasisEnd} buttons to compare a branch to master."
-msgstr ""
-
-msgid "UserOnboardingTour|Click on one of the %{emphasisStart}pipeline IDs%{emphasisEnd} to see the details of a pipeline."
-msgstr ""
-
-msgid "UserOnboardingTour|Click to open the latest commit to see its details."
-msgstr ""
-
-msgid "UserOnboardingTour|Close 'Learn GitLab'"
-msgstr ""
-
-msgid "UserOnboardingTour|Commits are shown in chronological order and can be filtered by the commit message or by the branch."
-msgstr ""
-
-msgid "UserOnboardingTour|Create a project"
-msgstr ""
-
-msgid "UserOnboardingTour|Exit 'Learn GitLab'"
-msgstr ""
-
-msgid "UserOnboardingTour|Got it"
-msgstr ""
-
-msgid "UserOnboardingTour|Great job! %{clapHands} We hope the tour was helpful and that you learned how to use GitLab.%{lineBreak}%{lineBreak}We'd love to get your feedback on this tour.%{lineBreak}%{lineBreak}%{emphasisStart}How helpful would you say this guided tour was?%{emphasisEnd}%{lineBreak}%{lineBreak}"
-msgstr ""
-
-msgid "UserOnboardingTour|Guided GitLab Tour"
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can compare the changes of this branch to another one. Changes are divided by files so that it's easier to see what was changed where."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can create a project from scratch, start with a template or import a repository from other platforms. Whatever you choose, we'll guide you through the process.%{lineBreak}%{lineBreak}Fill in your new project information and click on %{emphasisStart}Create Project%{emphasisEnd} to progress to the next step."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can see the breakdown of the pipelines: its stages and jobs in each of the stages and their status.%{lineBreak}%{lineBreak}Our CI/CD pipelines are quite complex, most of our users have fewer and simpler pipelines."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can see the current members of the project (just you at the moment) and invite new members.%{lineBreak}%{lineBreak}You can invite multiple members at once (existing GitLab users or invite by email) and you can also set their roles and permissions.%{lineBreak}%{lineBreak}Add a few members and click on %{emphasisStart}Add to project%{emphasisEnd} to complete this step."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can see what changes were made with this commit, on what branch and if there's a related merge request. The status of the pipeline will also show up if CI/CD is set up.%{lineBreak}%{lineBreak}You can also comment on the lines of code that were changed and start a discussion with your colleagues!"
-msgstr ""
-
-msgid "UserOnboardingTour|Here's an overview of branches in the %{emphasisStart}%{projectName}%{emphasisEnd} project. They're split into Active and Stale.%{lineBreak}%{lineBreak}From here, you can create a new merge request from a branch, or compare the branch to any other branch in the project. By default, it will compare it to the master branch."
-msgstr ""
-
-msgid "UserOnboardingTour|Invite colleagues"
-msgstr ""
-
-msgid "UserOnboardingTour|Issues are great for communicating and keeping track of progress in GitLab. These are all issues that are open in the %{emphasisStart}%{projectName}%{emphasisEnd}.%{lineBreak}%{lineBreak}You can help us improve GitLab by contributing work to issues that are labeled <span class=\"badge color-label accept-mr-label\">Accepting merge requests</span>.%{lineBreak}%{lineBreak}This list can be filtered by labels, milestones, assignees, authors... We'll show you how it looks when the list is filtered by a label."
-msgstr ""
-
-msgid "UserOnboardingTour|Learn GitLab"
-msgstr ""
-
-msgid "UserOnboardingTour|Let's take a closer look at a merge request. Click on the title of one."
-msgstr ""
-
-msgid "UserOnboardingTour|Let's take a closer look at all the commits. Click on %{emphasisStart}Commits%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Let's take a closer look at the repository of this project. Click on %{emphasisStart}Repository%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|No thanks"
-msgstr ""
-
-msgid "UserOnboardingTour|Ok, let's go"
-msgstr ""
-
-msgid "UserOnboardingTour|Ok, show me"
-msgstr ""
-
-msgid "UserOnboardingTour|Open one of the issues by clicking on its title."
-msgstr ""
-
-msgid "UserOnboardingTour|Restart this step"
-msgstr ""
-
-msgid "UserOnboardingTour|Skip this step"
-msgstr ""
-
-msgid "UserOnboardingTour|Sweet! Your project was created and is ready to be used.%{lineBreak}%{lineBreak}You can start adding files to the repository or clone it. One last thing we want to show you is how to invite your colleagues to your new project."
-msgstr ""
-
-msgid "UserOnboardingTour|Take a look. Here's a nifty menu for quickly creating issues, merge requests, snippets, projects and groups. Click on it and select \"New project\" from the \"GitLab\" section to get started."
-msgstr ""
-
-msgid "UserOnboardingTour|Thanks for taking the guided tour. Remember, if you want to go through it again, you can start %{emphasisStart}Learn GitLab%{emphasisEnd} in the help menu on the top right."
-msgstr ""
-
-msgid "UserOnboardingTour|Thanks for the feedback! %{thumbsUp}"
-msgstr ""
-
-msgid "UserOnboardingTour|That's it for issues. Let'st take a look at %{emphasisStart}Merge Requests%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|That's it for merge requests. Now for the final part of this guided tour - the %{emphasisStart}CI/CD%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|That's it for the Repository. Let's take a look at the %{emphasisStart}Issues%{emphasisEnd}."
+msgid "UserList|Delete %{name}?"
msgstr ""
-msgid "UserOnboardingTour|The structure of this page is very similar to issues. Status, description, discussion and the sidebar are all here.%{lineBreak}%{lineBreak}But take a look below the description and you'll notice that there's more information about the merge request, the CI/CD pipeline and the options for approving it.%{lineBreak}%{lineBreak}Alongside the discussion you can also see more information about commits in this merge request, the status of pipelines and review all changes that were made."
-msgstr ""
-
-msgid "UserOnboardingTour|There's a lot of information here but don't worry, we'll go through it.%{lineBreak}%{lineBreak}On the top you can see the status of the issue and when it was opened and by whom. Directly below it is the issue description and below that are other %{emphasisStart}related issues%{emphasisEnd} and %{emphasisStart}merge requests%{emphasisEnd} (if any). Then below that is the %{emphasisStart}discussion%{emphasisEnd}, that's where most of the communication happens.%{lineBreak}%{lineBreak}On the right, there's a sidebar where you can view/change the %{emphasisStart}assignee, milestone, due date, labels, weight%{emphasisEnd}, etc."
-msgstr ""
-
-msgid "UserOnboardingTour|These are all the CI/CD pipelines we have for our %{emphasisStart}%{projectName}%{emphasisEnd} project.%{lineBreak}%{lineBreak}Here you can see the status of each pipeline, for what commit it's running for, its stages and the status for them."
-msgstr ""
-
-msgid "UserOnboardingTour|These are all the issues that are available for community contributions. Let's take a closer look at one of them."
-msgstr ""
-
-msgid "UserOnboardingTour|This is an overview of all merge requests in this project. Similarly to the issues overview it can be filtered down by things like labels, milestones, authors, assignees, etc."
-msgstr ""
-
-msgid "UserOnboardingTour|This is the repository for the %{emphasisStart}%{projectName}%{emphasisEnd} project. All our code is stored here. Feel free to explore and take a closer look at folders and files.%{lineBreak}%{lineBreak}Above the file structure you can see the latest commit, who the author is and the status of the CI/CD pipeline.%{lineBreak}%{lineBreak}If you scroll down below the file structure, you'll find the Readme of this project. This is defined in the README.md file at the root of the repository."
-msgstr ""
-
-msgid "UserOnboardingTour|Welcome to the project overview of the %{emphasisStart}%{projectName}%{emphasisEnd} project. This is the project that we use to work on GitLab. At first, a project seems like a simple repository, but at GitLab, a project is so much more.%{lineBreak}%{lineBreak}You can create projects for hosting your codebase, use it as an issue tracker, collaborate on code, and continuously build, test, and deploy your app with built-in GitLab CI/CD."
+msgid "UserList|created %{timeago}"
msgstr ""
msgid "UserProfile|Activity"
@@ -24596,6 +25246,9 @@ msgstr ""
msgid "UsersSelect|Unassigned"
msgstr ""
+msgid "Using %{codeStart}needs%{codeEnd} allows jobs to run before their stage is reached, as soon as their individual dependencies are met, which speeds up your pipelines."
+msgstr ""
+
msgid "Using %{code_start}::%{code_end} denotes a %{link_start}scoped label set%{link_end}"
msgstr ""
@@ -24680,9 +25333,6 @@ msgstr ""
msgid "Versions"
msgstr ""
-msgid "Very helpful"
-msgstr ""
-
msgid "View Documentation"
msgstr ""
@@ -24833,6 +25483,9 @@ msgstr ""
msgid "VisibilityLevel|Unknown"
msgstr ""
+msgid "Visit settings page"
+msgstr ""
+
msgid "VisualReviewApp|%{stepStart}Step 1%{stepEnd}. Copy the following script:"
msgstr ""
@@ -24926,6 +25579,9 @@ msgstr ""
msgid "VulnerabilityManagement|Something went wrong while trying to delete the comment. Please try again later."
msgstr ""
+msgid "VulnerabilityManagement|Something went wrong while trying to refresh the vulnerability. Please try again later."
+msgstr ""
+
msgid "VulnerabilityManagement|Something went wrong while trying to retrieve the vulnerability history. Please try again later."
msgstr ""
@@ -24968,6 +25624,9 @@ msgstr ""
msgid "Vulnerability|Class"
msgstr ""
+msgid "Vulnerability|Crash Address"
+msgstr ""
+
msgid "Vulnerability|Description"
msgstr ""
@@ -25043,9 +25702,6 @@ msgstr ""
msgid "We couldn't reach the Prometheus server. Either the server no longer exists or the configuration details need updating."
msgstr ""
-msgid "We created a short guided tour that will help you learn the basics of GitLab and how it will help you be better at your job. It should only take a couple of minutes. You will be guided by two types of helpers, best recognized by their color."
-msgstr ""
-
msgid "We detected potential spam in the %{humanized_resource_name}. Please solve the reCAPTCHA to proceed."
msgstr ""
@@ -25079,6 +25735,9 @@ msgstr ""
msgid "We've found no vulnerabilities"
msgstr ""
+msgid "Web Application Firewall"
+msgstr ""
+
msgid "Web IDE"
msgstr ""
@@ -25139,9 +25798,6 @@ msgstr ""
msgid "Welcome to GitLab.com<br>@%{name}!"
msgstr ""
-msgid "Welcome to the Guided GitLab Tour"
-msgstr ""
-
msgid "Welcome to the guided GitLab tour"
msgstr ""
@@ -25190,9 +25846,6 @@ msgstr ""
msgid "While it's rare to have no vulnerabilities, it can happen. In any event, we ask that you please double check your settings to make sure you've set up your dashboard correctly."
msgstr ""
-msgid "White helpers give contextual information."
-msgstr ""
-
msgid "Who can be an approver?"
msgstr ""
@@ -25238,12 +25891,18 @@ msgstr ""
msgid "WikiEmptyIssueMessage|Suggest wiki improvement"
msgstr ""
+msgid "WikiEmptyIssueMessage|You must be a group member in order to add wiki pages. If you have suggestions for how to improve the wiki for this group, consider opening an issue in the %{issues_link}."
+msgstr ""
+
msgid "WikiEmptyIssueMessage|You must be a project member in order to add wiki pages. If you have suggestions for how to improve the wiki for this project, consider opening an issue in the %{issues_link}."
msgstr ""
msgid "WikiEmptyIssueMessage|issue tracker"
msgstr ""
+msgid "WikiEmpty|A wiki is where you can store all the details about your group. This can include why you've created it, its principles, how to use it, and so on."
+msgstr ""
+
msgid "WikiEmpty|A wiki is where you can store all the details about your project. This can include why you've created it, its principles, how to use it, and so on."
msgstr ""
@@ -25253,12 +25912,21 @@ msgstr ""
msgid "WikiEmpty|Suggest wiki improvement"
msgstr ""
+msgid "WikiEmpty|The wiki lets you write documentation for your group"
+msgstr ""
+
msgid "WikiEmpty|The wiki lets you write documentation for your project"
msgstr ""
+msgid "WikiEmpty|This group has no wiki pages"
+msgstr ""
+
msgid "WikiEmpty|This project has no wiki pages"
msgstr ""
+msgid "WikiEmpty|You must be a group member in order to add wiki pages."
+msgstr ""
+
msgid "WikiEmpty|You must be a project member in order to add wiki pages."
msgstr ""
@@ -25535,13 +26203,13 @@ msgstr ""
msgid "You can now export your security dashboard to a CSV report."
msgstr ""
-msgid "You can now submit a merge request to get this change into the original branch."
+msgid "You can now manage alert endpoint configuration in the Alerts section on the Operations settings page. Fields on this page have been deprecated."
msgstr ""
-msgid "You can now submit a merge request to get this change into the original project."
+msgid "You can now submit a merge request to get this change into the original branch."
msgstr ""
-msgid "You can only add files when you are on a branch"
+msgid "You can now submit a merge request to get this change into the original project."
msgstr ""
msgid "You can only edit files when you are on a branch"
@@ -25763,7 +26431,7 @@ msgstr ""
msgid "You need to upload a Google Takeout archive."
msgstr ""
-msgid "You reached %{usage_in_percent} of %{namespace_name}'s capacity (%{used_storage} of %{storage_limit})"
+msgid "You reached %{usage_in_percent} of %{namespace_name}'s storage capacity (%{used_storage} of %{storage_limit})"
msgstr ""
msgid "You tried to fork %{link_to_the_project} but it failed for the following reason:"
@@ -26066,13 +26734,13 @@ msgstr ""
msgid "Your subscription expired!"
msgstr ""
-msgid "Your subscription has been downgraded"
+msgid "Your subscription has been downgraded."
msgstr ""
-msgid "Your subscription will automatically renew in %{remaining_days}"
+msgid "Your subscription will automatically renew in %{remaining_days}."
msgstr ""
-msgid "Your subscription will expire in %{remaining_days}"
+msgid "Your subscription will expire in %{remaining_days}."
msgstr ""
msgid "Zoom meeting added"
@@ -26153,9 +26821,6 @@ msgstr ""
msgid "branch name"
msgstr ""
-msgid "build pipeline reference mismatch"
-msgstr ""
-
msgid "by"
msgstr ""
@@ -26174,6 +26839,9 @@ msgstr ""
msgid "cannot block others"
msgstr ""
+msgid "cannot contain HTML/XML tags, including any word between angle brackets (<,>)."
+msgstr ""
+
msgid "cannot include leading slash or directory traversal."
msgstr ""
@@ -26204,60 +26872,6 @@ msgstr ""
msgid "ciReport|%{remainingPackagesCount} more"
msgstr ""
-msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability"
-msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities"
-msgstr[0] ""
-msgstr[1] ""
-msgstr[2] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability for the source branch only"
-msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities for the source branch only"
-msgstr[0] ""
-msgstr[1] ""
-msgstr[2] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerability"
-msgid_plural "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerabilities"
-msgstr[0] ""
-msgstr[1] ""
-msgstr[2] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new vulnerability"
-msgid_plural "ciReport|%{reportType} %{status} detected %{newCount} new vulnerabilities"
-msgstr[0] ""
-msgstr[1] ""
-msgstr[2] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities for the source branch only"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{fixedCount} fixed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} vulnerability for the source branch only"
-msgid_plural "ciReport|%{reportType} %{status} detected %{newCount} vulnerabilities for the source branch only"
-msgstr[0] ""
-msgstr[1] ""
-msgstr[2] ""
-
-msgid "ciReport|%{reportType} %{status} detected no new vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected no vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected no vulnerabilities for the source branch only"
-msgstr ""
-
msgid "ciReport|%{reportType} is loading"
msgstr ""
@@ -26276,7 +26890,7 @@ msgstr ""
msgid "ciReport|All projects"
msgstr ""
-msgid "ciReport|All report types"
+msgid "ciReport|All scanner types"
msgstr ""
msgid "ciReport|All severities"
@@ -26300,6 +26914,9 @@ msgstr ""
msgid "ciReport|Container scanning detects known vulnerabilities in your docker images."
msgstr ""
+msgid "ciReport|Coverage Fuzzing"
+msgstr ""
+
msgid "ciReport|Create a merge request to implement this solution, or download and apply the patch manually."
msgstr ""
@@ -26459,6 +27076,9 @@ msgstr ""
msgid "customize"
msgstr ""
+msgid "data"
+msgstr ""
+
msgid "date must not be after 9999-12-31"
msgstr ""
@@ -26531,6 +27151,9 @@ msgstr ""
msgid "entries cannot contain HTML tags"
msgstr ""
+msgid "epic"
+msgstr ""
+
msgid "error"
msgstr ""
@@ -26552,9 +27175,6 @@ msgstr ""
msgid "expires on %{timebox_due_date}"
msgstr ""
-msgid "external_url"
-msgstr ""
-
msgid "failed"
msgstr ""
@@ -26591,9 +27211,6 @@ msgstr ""
msgid "from"
msgstr ""
-msgid "geo_node_name"
-msgstr ""
-
msgid "group"
msgstr ""
@@ -26615,9 +27232,6 @@ msgstr ""
msgid "https://your-bitbucket-server"
msgstr ""
-msgid "image"
-msgstr ""
-
msgid "image diff"
msgstr ""
@@ -26762,6 +27376,9 @@ msgstr ""
msgid "load it anyway"
msgstr ""
+msgid "loading"
+msgstr ""
+
msgid "locked by %{path_lock_user_name} %{created_at}"
msgstr ""
@@ -26891,9 +27508,6 @@ msgstr ""
msgid "mrWidget|Deployment statistics are not available currently"
msgstr ""
-msgid "mrWidget|Detect issues before deployment with a CI pipeline that continuously tests your code. We created a quick guide that will show you how to create one. Make your code more secure and more robust in just a minute."
-msgstr ""
-
msgid "mrWidget|Did not close"
msgstr ""
@@ -27056,6 +27670,9 @@ msgstr ""
msgid "mrWidget|To approve this merge request, please enter your password. This project requires all approvals to be authenticated."
msgstr ""
+msgid "mrWidget|Use %{linkStart}CI pipelines to test your code%{linkEnd}, simply add a GitLab CI configuration file to your project. It only takes a minute to make your code more secure and robust."
+msgstr ""
+
msgid "mrWidget|When this merge request is ready, remove the WIP: prefix from the title to allow it to be merged"
msgstr ""
@@ -27092,6 +27709,9 @@ msgstr ""
msgid "must be greater than start date"
msgstr ""
+msgid "must contain only valid frameworks"
+msgstr ""
+
msgid "my-awesome-group"
msgstr ""
@@ -27104,6 +27724,9 @@ msgstr ""
msgid "needs to be between 10 minutes and 1 month"
msgstr ""
+msgid "never"
+msgstr ""
+
msgid "never expires"
msgstr ""
@@ -27131,6 +27754,9 @@ msgstr ""
msgid "nounSeries|%{firstItem} and %{lastItem}"
msgstr ""
+msgid "nounSeries|%{item}"
+msgstr ""
+
msgid "nounSeries|%{item}, %{nextItem}"
msgstr ""
@@ -27272,6 +27898,9 @@ msgstr ""
msgid "revised"
msgstr ""
+msgid "satisfied"
+msgstr ""
+
msgid "score"
msgstr ""
@@ -27302,6 +27931,9 @@ msgstr ""
msgid "severity|Unknown"
msgstr ""
+msgid "should be an array of %{object_name} objects"
+msgstr ""
+
msgid "should be greater than or equal to %{access} inherited membership from group %{group_name}"
msgstr ""
@@ -27356,10 +27988,13 @@ msgstr ""
msgid "suggestPipeline|2/2: Commit your changes"
msgstr ""
-msgid "suggestPipeline|Commit the changes and your pipeline will automatically run for the first time."
+msgid "suggestPipeline|Choose %{boldStart}Code Quality%{boldEnd} to add a pipeline that tests the quality of your code."
msgstr ""
-msgid "suggestPipeline|We recommend the %{boldStart}Code Quality%{boldEnd} template, which will add a report widget to your Merge Requests. This way you’ll learn about code quality degradations much sooner. %{footerStart} Goodbye technical debt! %{footerEnd}"
+msgid "suggestPipeline|The template is ready! You can now commit it to create your first pipeline."
+msgstr ""
+
+msgid "suggestPipeline|We’re adding a GitLab CI configuration file to add a pipeline to the project. You could create it manually, but we recommend that you start with a GitLab template that works out of the box."
msgstr ""
msgid "syntax is correct"
@@ -27419,6 +28054,9 @@ msgstr ""
msgid "uses Kubernetes clusters to deploy your code!"
msgstr ""
+msgid "v%{version} published %{timeAgo}"
+msgstr ""
+
msgid "verify ownership"
msgstr ""
diff --git a/locale/sv_SE/gitlab.po b/locale/sv_SE/gitlab.po
index 0cb313ec7b9..4e6e1709178 100644
--- a/locale/sv_SE/gitlab.po
+++ b/locale/sv_SE/gitlab.po
@@ -2,7 +2,7 @@ msgid ""
msgstr ""
"Project-Id-Version: gitlab-ee\n"
"Report-Msgid-Bugs-To: \n"
-"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Last-Translator: \n"
"Language-Team: Swedish\n"
"Language: sv_SE\n"
"MIME-Version: 1.0\n"
@@ -10,9 +10,11 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Crowdin-Project: gitlab-ee\n"
+"X-Crowdin-Project-ID: 288872\n"
"X-Crowdin-Language: sv-SE\n"
"X-Crowdin-File: /master/locale/gitlab.pot\n"
-"PO-Revision-Date: 2020-06-08 15:14\n"
+"X-Crowdin-File-ID: 6\n"
+"PO-Revision-Date: 2020-07-02 01:26\n"
msgid " %{start} to %{end}"
msgstr ""
@@ -197,6 +199,11 @@ msgid_plural "%d metrics"
msgstr[0] ""
msgstr[1] ""
+msgid "%d milestone"
+msgid_plural "%d milestones"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "%d minute"
msgid_plural "%d minutes"
msgstr[0] ""
@@ -242,11 +249,6 @@ msgid_plural "%d unresolved threads"
msgstr[0] ""
msgstr[1] ""
-msgid "%d url scanned"
-msgid_plural "%d urls scanned"
-msgstr[0] ""
-msgstr[1] ""
-
msgid "%d vulnerability"
msgid_plural "%d vulnerabilities"
msgstr[0] ""
@@ -324,9 +326,15 @@ msgstr[1] ""
msgid "%{count} related %{pluralized_subject}: %{links}"
msgstr ""
+msgid "%{dashboard_path} could not be found."
+msgstr ""
+
msgid "%{days} days until tags are automatically removed"
msgstr ""
+msgid "%{deployLinkStart}Use a template to deploy to ECS%{deployLinkEnd}, or use a docker image to %{commandsLinkStart}run AWS commands in GitLab CI/CD%{commandsLinkEnd}."
+msgstr ""
+
msgid "%{description}- Sentry event: %{errorUrl}- First seen: %{firstSeen}- Last seen: %{lastSeen} %{countLabel}: %{count}%{userCountLabel}: %{userCount}"
msgstr ""
@@ -369,12 +377,18 @@ msgstr ""
msgid "%{host} sign-in from new location"
msgstr ""
-msgid "%{icon}You are about to add %{usersTag} people to the discussion. Proceed with caution."
+msgid "%{icon}You are about to add %{usersTag} people to the discussion. They will all receive a notification."
+msgstr ""
+
+msgid "%{integrations_link_start}Integrations%{link_end} enable you to make third-party applications part of your GitLab workflow. If the available integrations don't meet your needs, consider using a %{webhooks_link_start}webhook%{link_end}."
msgstr ""
msgid "%{issuableType} will be removed! Are you sure?"
msgstr ""
+msgid "%{issuesCount} issues in this group"
+msgstr ""
+
msgid "%{issuesSize} issues"
msgstr ""
@@ -384,7 +398,7 @@ msgstr ""
msgid "%{labelStart}Class:%{labelEnd} %{class}"
msgstr ""
-msgid "%{labelStart}Confidence:%{labelEnd} %{confidence}"
+msgid "%{labelStart}Evidence:%{labelEnd} %{evidence}"
msgstr ""
msgid "%{labelStart}File:%{labelEnd} %{file}"
@@ -402,6 +416,9 @@ msgstr ""
msgid "%{labelStart}Report Type:%{labelEnd} %{reportType}"
msgstr ""
+msgid "%{labelStart}Scanner:%{labelEnd} %{scanner}"
+msgstr ""
+
msgid "%{labelStart}Severity:%{labelEnd} %{severity}"
msgstr ""
@@ -426,9 +443,6 @@ msgstr ""
msgid "%{link_start}Read more%{link_end} about role permissions"
msgstr ""
-msgid "%{link} can be used for binding events when something is happening within the project."
-msgstr ""
-
msgid "%{listToShow}, and %{awardsListLength} more."
msgstr ""
@@ -462,6 +476,9 @@ msgstr ""
msgid "%{name} found %{resultsString}"
msgstr ""
+msgid "%{name} is already being used for another emoji"
+msgstr ""
+
msgid "%{name} is scheduled for %{action}"
msgstr ""
@@ -509,6 +526,9 @@ msgstr ""
msgid "%{primary} (%{secondary})"
msgstr ""
+msgid "%{ref} cannot be added: %{error}"
+msgstr ""
+
msgid "%{releases} release"
msgid_plural "%{releases} releases"
msgstr[0] ""
@@ -517,13 +537,37 @@ msgstr[1] ""
msgid "%{remaining_approvals} left"
msgstr ""
-msgid "%{retryButtonStart}Try again%{retryButtonEnd} or %{newFileButtonStart}attach a new file%{newFileButtonEnd}"
+msgid "%{reportType} %{status} detected %{critical} critical and %{high} high severity vulnerabilities out of %{total}."
+msgstr ""
+
+msgid "%{reportType} %{status} detected %{critical} critical and %{high} high severity vulnerabilities."
+msgstr ""
+
+msgid "%{reportType} %{status} detected %{critical} critical severity vulnerabilities out of %{total}."
+msgstr ""
+
+msgid "%{reportType} %{status} detected %{critical} critical severity vulnerability."
+msgid_plural "%{reportType} %{status} detected %{critical} critical severity vulnerabilities."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{reportType} %{status} detected %{high} high severity vulnerabilities out of %{total}."
msgstr ""
-msgid "%{screenreaderOnlyStart}Keyboard shorcuts%{screenreaderOnlyEnd} Disabled"
+msgid "%{reportType} %{status} detected %{high} high severity vulnerability."
+msgid_plural "%{reportType} %{status} detected %{high} high severity vulnerabilities."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{reportType} %{status} detected %{other} vulnerability."
+msgid_plural "%{reportType} %{status} detected %{other} vulnerabilities."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{reportType} %{status} detected no new vulnerabilities."
msgstr ""
-msgid "%{screenreaderOnlyStart}Keyboard shorcuts%{screenreaderOnlyEnd} Enabled"
+msgid "%{retryButtonStart}Try again%{retryButtonEnd} or %{newFileButtonStart}attach a new file%{newFileButtonEnd}"
msgstr ""
msgid "%{service_title} %{message}."
@@ -620,6 +664,12 @@ msgstr ""
msgid "%{token}..."
msgstr ""
+msgid "%{totalCpu} (%{freeSpacePercentage}%{percentSymbol} free)"
+msgstr ""
+
+msgid "%{totalMemory} (%{freeSpacePercentage}%{percentSymbol} free)"
+msgstr ""
+
msgid "%{totalWeight} total weight"
msgstr ""
@@ -650,6 +700,12 @@ msgstr ""
msgid "%{verb} %{time_spent_value} spent time."
msgstr ""
+msgid "%{webhooks_link_start}%{webhook_type}%{link_end} enable you to send notifications to web applications in response to events in a group or project."
+msgstr ""
+
+msgid "%{webhooks_link_start}%{webhook_type}%{link_end} enable you to send notifications to web applications in response to events in a group or project. We recommend using an %{integrations_link_start}integration%{link_end} in preference to a webhook."
+msgstr ""
+
msgid "'%{level}' is not a valid visibility level"
msgstr ""
@@ -676,9 +732,6 @@ msgstr ""
msgid "(No changes)"
msgstr ""
-msgid "(Show all)"
-msgstr ""
-
msgid "(check progress)"
msgstr ""
@@ -745,6 +798,9 @@ msgstr ""
msgid "0 for unlimited"
msgstr ""
+msgid "0 for unlimited, only effective with remote storage enabled."
+msgstr ""
+
msgid "1 %{type} addition"
msgid_plural "%{count} %{type} additions"
msgstr[0] ""
@@ -892,6 +948,12 @@ msgstr ""
msgid "<code>\"johnsmith@example.com\": \"johnsmith@example.com\"</code> will add \"By <a href=\"#\">johnsmith@example.com</a>\" to all issues and comments originally created by johnsmith@example.com. By default, the email address or username is masked to ensure the user's privacy. Use this option if you want to show the full email address."
msgstr ""
+msgid "<code>Masked</code> to prevent the values from being displayed in job logs (must match certain regexp requirements)."
+msgstr ""
+
+msgid "<code>Protected</code> to expose them to protected branches or tags only."
+msgstr ""
+
msgid "<namespace / project>"
msgstr ""
@@ -919,9 +981,6 @@ msgstr ""
msgid "A .NET Core console application template, customizable for any .NET Core project"
msgstr ""
-msgid "A DAG must have two dependent jobs to be visualized on this tab."
-msgstr ""
-
msgid "A GitBook site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
msgstr ""
@@ -952,6 +1011,12 @@ msgstr ""
msgid "A deleted user"
msgstr ""
+msgid "A file has been changed."
+msgstr ""
+
+msgid "A file was not found."
+msgstr ""
+
msgid "A file with '%{file_name}' already exists in %{branch} branch"
msgstr ""
@@ -1018,7 +1083,7 @@ msgstr ""
msgid "A subscription will trigger a new pipeline on the default branch of this project when a pipeline successfully completes for a new tag on the %{default_branch_docs} of the subscribed project."
msgstr ""
-msgid "A terraform report was generated in your pipelines."
+msgid "A suggestion is not applicable."
msgstr ""
msgid "A user with write access to the source branch selected this option"
@@ -1189,6 +1254,9 @@ msgstr ""
msgid "Action to take when receiving an alert."
msgstr ""
+msgid "Actions"
+msgstr ""
+
msgid "Activate"
msgstr ""
@@ -1245,6 +1313,9 @@ msgstr ""
msgid "Add LICENSE"
msgstr ""
+msgid "Add New Node"
+msgstr ""
+
msgid "Add README"
msgstr ""
@@ -1320,6 +1391,9 @@ msgstr ""
msgid "Add approval rule"
msgstr ""
+msgid "Add approvers"
+msgstr ""
+
msgid "Add bold text"
msgstr ""
@@ -1377,6 +1451,9 @@ msgstr ""
msgid "Add strikethrough text"
msgstr ""
+msgid "Add suggestion to batch"
+msgstr ""
+
msgid "Add system hook"
msgstr ""
@@ -1476,6 +1553,9 @@ msgstr ""
msgid "Admin Note"
msgstr ""
+msgid "Admin Notifications"
+msgstr ""
+
msgid "Admin Overview"
msgstr ""
@@ -1587,6 +1667,9 @@ msgstr ""
msgid "AdminSettings|Integrations configured here will automatically apply to all projects on this instance."
msgstr ""
+msgid "AdminSettings|Moved to integrations"
+msgstr ""
+
msgid "AdminSettings|No required pipeline"
msgstr ""
@@ -1817,12 +1900,18 @@ msgid_plural "Alerts"
msgstr[0] ""
msgstr[1] ""
+msgid "AlertManagement| assign yourself"
+msgstr ""
+
msgid "AlertManagement|Acknowledged"
msgstr ""
msgid "AlertManagement|Alert"
msgstr ""
+msgid "AlertManagement|Alert assignee(s): %{assignees}"
+msgstr ""
+
msgid "AlertManagement|Alert detail"
msgstr ""
@@ -1838,9 +1927,15 @@ msgstr ""
msgid "AlertManagement|All alerts"
msgstr ""
+msgid "AlertManagement|Assign To"
+msgstr ""
+
msgid "AlertManagement|Assign status"
msgstr ""
+msgid "AlertManagement|Assignee"
+msgstr ""
+
msgid "AlertManagement|Assignees"
msgstr ""
@@ -1859,9 +1954,6 @@ msgstr ""
msgid "AlertManagement|Edit"
msgstr ""
-msgid "AlertManagement|End time"
-msgstr ""
-
msgid "AlertManagement|Events"
msgstr ""
@@ -1880,10 +1972,13 @@ msgstr ""
msgid "AlertManagement|More information"
msgstr ""
+msgid "AlertManagement|No Matching Results"
+msgstr ""
+
msgid "AlertManagement|No alert data to display."
msgstr ""
-msgid "AlertManagement|No alerts available to display. If you think you're seeing this message in error, refresh the page."
+msgid "AlertManagement|No alerts available to display. See %{linkStart}enabling alert management%{linkEnd} for more information on adding alerts to the list."
msgstr ""
msgid "AlertManagement|No alerts to display."
@@ -1892,6 +1987,9 @@ msgstr ""
msgid "AlertManagement|None"
msgstr ""
+msgid "AlertManagement|None -"
+msgstr ""
+
msgid "AlertManagement|Open"
msgstr ""
@@ -1928,9 +2026,18 @@ msgstr ""
msgid "AlertManagement|There was an error displaying the alerts. Confirm your endpoint's configuration details to ensure alerts appear."
msgstr ""
+msgid "AlertManagement|There was an error while updating the assignee(s) list. Please try again."
+msgstr ""
+
+msgid "AlertManagement|There was an error while updating the assignee(s) of the alert. Please try again."
+msgstr ""
+
msgid "AlertManagement|There was an error while updating the status of the alert. Please try again."
msgstr ""
+msgid "AlertManagement|This assignee cannot be assigned to this alert."
+msgstr ""
+
msgid "AlertManagement|Tool"
msgstr ""
@@ -2078,6 +2185,9 @@ msgstr ""
msgid "Allow users to request access (if visibility is public or internal)"
msgstr ""
+msgid "Allowed Geo IP"
+msgstr ""
+
msgid "Allowed email domain restriction only permitted for top-level groups"
msgstr ""
@@ -2111,6 +2221,9 @@ msgstr ""
msgid "Amazon Web Services"
msgstr ""
+msgid "Amazon Web Services Logo"
+msgstr ""
+
msgid "Amazon authentication is not %{link_start}correctly configured%{link_end}. Ask your GitLab administrator if you want to use this service."
msgstr ""
@@ -2168,10 +2281,16 @@ msgstr ""
msgid "An error occurred when updating the issue weight"
msgstr ""
+msgid "An error occurred while acknowledging the notification. Refresh the page and try again."
+msgstr ""
+
+msgid "An error occurred while adding approvers"
+msgstr ""
+
msgid "An error occurred while adding formatted title for epic"
msgstr ""
-msgid "An error occurred while checking group path"
+msgid "An error occurred while checking group path. Please refresh and try again."
msgstr ""
msgid "An error occurred while committing your changes."
@@ -2315,7 +2434,7 @@ msgstr ""
msgid "An error occurred while loading milestones"
msgstr ""
-msgid "An error occurred while loading terraform report"
+msgid "An error occurred while loading project creation UI"
msgstr ""
msgid "An error occurred while loading the data. Please try again."
@@ -2369,6 +2488,9 @@ msgstr ""
msgid "An error occurred while rendering preview broadcast message"
msgstr ""
+msgid "An error occurred while rendering the editor"
+msgstr ""
+
msgid "An error occurred while reordering issues."
msgstr ""
@@ -2465,7 +2587,7 @@ msgstr ""
msgid "Analyze your dependencies for known vulnerabilities."
msgstr ""
-msgid "Analyze your source code and git history for secrets"
+msgid "Analyze your source code and git history for secrets."
msgstr ""
msgid "Analyze your source code for known vulnerabilities."
@@ -2579,12 +2701,18 @@ msgstr ""
msgid "Apply suggestion"
msgstr ""
+msgid "Apply suggestions"
+msgstr ""
+
msgid "Apply template"
msgstr ""
msgid "Apply this approval rule to any branch or a specific protected branch."
msgstr ""
+msgid "Applying"
+msgstr ""
+
msgid "Applying a template will replace the existing issue description. Any changes you have made will be lost."
msgstr ""
@@ -2597,12 +2725,18 @@ msgstr ""
msgid "Applying multiple commands"
msgstr ""
-msgid "Applying suggestion"
+msgid "Applying suggestion..."
+msgstr ""
+
+msgid "Applying suggestions..."
msgstr ""
msgid "Approval rules"
msgstr ""
+msgid "Approval rules reset to project defaults"
+msgstr ""
+
msgid "ApprovalRuleRemove|%d member"
msgid_plural "ApprovalRuleRemove|%d members"
msgstr[0] ""
@@ -2656,6 +2790,9 @@ msgstr ""
msgid "Approved"
msgstr ""
+msgid "Approved MRs"
+msgstr ""
+
msgid "Approved by: "
msgstr ""
@@ -2869,6 +3006,9 @@ msgstr ""
msgid "Assign"
msgstr ""
+msgid "Assign Iteration"
+msgstr ""
+
msgid "Assign custom color like #FF0000"
msgstr ""
@@ -2887,6 +3027,9 @@ msgstr ""
msgid "Assign to"
msgstr ""
+msgid "Assign to commenting user"
+msgstr ""
+
msgid "Assign yourself to these issues"
msgstr ""
@@ -2913,6 +3056,9 @@ msgid_plural "%d Assignees"
msgstr[0] ""
msgstr[1] ""
+msgid "Assignee has no permissions"
+msgstr ""
+
msgid "Assignee lists not available with your current license"
msgstr ""
@@ -2922,6 +3068,9 @@ msgstr ""
msgid "Assignee(s)"
msgstr ""
+msgid "Assignees"
+msgstr ""
+
msgid "Assigns %{assignee_users_sentence}."
msgstr ""
@@ -2957,6 +3106,9 @@ msgstr ""
msgid "Audit Events is a way to keep track of important events that happened in GitLab."
msgstr ""
+msgid "Audit Log"
+msgstr ""
+
msgid "AuditEvents|(removed)"
msgstr ""
@@ -2993,6 +3145,9 @@ msgstr ""
msgid "AuditLogs|IP Address"
msgstr ""
+msgid "AuditLogs|Member Events"
+msgstr ""
+
msgid "AuditLogs|No matching %{type} found."
msgstr ""
@@ -3275,9 +3430,6 @@ msgstr ""
msgid "Badges|e.g. %{exampleUrl}"
msgstr ""
-msgid "Badge|New"
-msgstr ""
-
msgid "Balsamiq file could not be loaded."
msgstr ""
@@ -3326,6 +3478,12 @@ msgstr ""
msgid "Below you will find all the groups that are public."
msgstr ""
+msgid "Beta"
+msgstr ""
+
+msgid "Bi-weekly code coverage"
+msgstr ""
+
msgid "Billing"
msgstr ""
@@ -3386,9 +3544,15 @@ msgstr ""
msgid "Bitbucket Server Import"
msgstr ""
+msgid "Bitbucket Server import"
+msgstr ""
+
msgid "Bitbucket import"
msgstr ""
+msgid "Blame"
+msgstr ""
+
msgid "Blocked"
msgstr ""
@@ -3401,9 +3565,6 @@ msgstr ""
msgid "Blog"
msgstr ""
-msgid "Blue helpers indicate an action to be taken."
-msgstr ""
-
msgid "Board name"
msgstr ""
@@ -3602,6 +3763,9 @@ msgstr ""
msgid "Broadcast Message was successfully updated."
msgstr ""
+msgid "Broadcast Messages"
+msgstr ""
+
msgid "Browse Directory"
msgstr ""
@@ -3656,6 +3820,9 @@ msgstr ""
msgid "By %{user_name}"
msgstr ""
+msgid "By URL"
+msgstr ""
+
msgid "By default GitLab sends emails in HTML and plain text formats so mail clients can choose what format to use. Disable this option if you only want to send emails in plain text format."
msgstr ""
@@ -3761,6 +3928,9 @@ msgstr ""
msgid "Can override approvers and approvals required per merge request"
msgstr ""
+msgid "Can't apply as this line has changed or the suggestion already matches its content."
+msgstr ""
+
msgid "Can't create snippet: %{err}"
msgstr ""
@@ -3947,9 +4117,6 @@ msgstr ""
msgid "Changes are still tracked. Useful for cluster/index migrations."
msgstr ""
-msgid "Changes are unknown"
-msgstr ""
-
msgid "Changes suppressed. Click to show."
msgstr ""
@@ -4256,15 +4423,9 @@ msgstr ""
msgid "Choose what content you want to see on a group’s overview page"
msgstr ""
-msgid "Choose which groups you wish to synchronize to this secondary node"
-msgstr ""
-
msgid "Choose which repositories you want to connect and run CI/CD pipelines."
msgstr ""
-msgid "Choose which shards you wish to synchronize to this secondary node"
-msgstr ""
-
msgid "Choose your framework"
msgstr ""
@@ -4562,9 +4723,15 @@ msgstr ""
msgid "Cluster does not exist"
msgstr ""
+msgid "Cluster is required for Stages::ClusterEndpointInserter"
+msgstr ""
+
msgid "Cluster level"
msgstr ""
+msgid "Cluster type must be specificed for Stages::ClusterEndpointInserter"
+msgstr ""
+
msgid "ClusterIntegration| %{custom_domain_start}More information%{custom_domain_end}."
msgstr ""
@@ -4676,7 +4843,7 @@ msgstr ""
msgid "ClusterIntegration|Cert-Manager"
msgstr ""
-msgid "ClusterIntegration|Cert-Manager is a native Kubernetes certificate management controller that helps with issuing certificates. Installing Cert-Manager on your cluster will issue a certificate by %{letsEncrypt} and ensure that certificates are valid and up-to-date."
+msgid "ClusterIntegration|Cert-Manager is a native Kubernetes certificate management controller that helps with issuing certificates. Installing Cert-Manager on your cluster will issue a certificate by %{linkStart}Let's Encrypt%{linkEnd} and ensure that certificates are valid and up-to-date."
msgstr ""
msgid "ClusterIntegration|Certificate Authority bundle (PEM format)"
@@ -4703,9 +4870,6 @@ msgstr ""
msgid "ClusterIntegration|Clear the local cache of namespace and service accounts. This is necessary if your integration has become out of sync. The cache is repopulated during the next CI job that requires namespace and service accounts."
msgstr ""
-msgid "ClusterIntegration|Cloud Run"
-msgstr ""
-
msgid "ClusterIntegration|Cluster being created"
msgstr ""
@@ -4790,7 +4954,7 @@ msgstr ""
msgid "ClusterIntegration|Crossplane"
msgstr ""
-msgid "ClusterIntegration|Crossplane enables declarative provisioning of managed services from your cloud of choice using %{codeStart}kubectl%{codeEnd} or %{gitlabIntegrationLink}. Crossplane runs inside your Kubernetes cluster and supports secure connectivity and secrets management between app containers and the cloud services they depend on."
+msgid "ClusterIntegration|Crossplane enables declarative provisioning of managed services from your cloud of choice using %{codeStart}kubectl%{codeEnd} or %{linkStart}GitLab Integration%{linkEnd}. Crossplane runs inside your Kubernetes cluster and supports secure connectivity and secrets management between app containers and the cloud services they depend on."
msgstr ""
msgid "ClusterIntegration|Deletes all GitLab resources attached to this cluster during removal"
@@ -4874,9 +5038,6 @@ msgstr ""
msgid "ClusterIntegration|GitLab-managed cluster"
msgstr ""
-msgid "ClusterIntegration|Gitlab Integration"
-msgstr ""
-
msgid "ClusterIntegration|Global default"
msgstr ""
@@ -4922,7 +5083,7 @@ msgstr ""
msgid "ClusterIntegration|Ingress gives you a way to route requests to services based on the request host or path, centralizing a number of services into a single entrypoint."
msgstr ""
-msgid "ClusterIntegration|Installing Ingress may incur additional costs. Learn more about %{pricingLink}."
+msgid "ClusterIntegration|Installing Ingress may incur additional costs. Learn more about %{linkStart}pricing%{linkEnd}."
msgstr ""
msgid "ClusterIntegration|Instance cluster"
@@ -4937,7 +5098,7 @@ msgstr ""
msgid "ClusterIntegration|Issuer Email"
msgstr ""
-msgid "ClusterIntegration|Issuers represent a certificate authority. You must provide an email address for your Issuer. "
+msgid "ClusterIntegration|Issuers represent a certificate authority. You must provide an email address for your Issuer."
msgstr ""
msgid "ClusterIntegration|Jupyter Hostname"
@@ -5009,9 +5170,6 @@ msgstr ""
msgid "ClusterIntegration|Learn more about instance Kubernetes clusters"
msgstr ""
-msgid "ClusterIntegration|Let's Encrypt"
-msgstr ""
-
msgid "ClusterIntegration|Loading IAM Roles"
msgstr ""
@@ -5123,7 +5281,7 @@ msgstr ""
msgid "ClusterIntegration|Prometheus"
msgstr ""
-msgid "ClusterIntegration|Prometheus is an open-source monitoring system with %{gitlabIntegrationLink} to monitor deployed applications."
+msgid "ClusterIntegration|Prometheus is an open-source monitoring system with %{linkStart}GitLab Integration%{linkEnd} to monitor deployed applications."
msgstr ""
msgid "ClusterIntegration|Provider details"
@@ -5279,10 +5437,10 @@ msgstr ""
msgid "ClusterIntegration|Select zone to choose machine type"
msgstr ""
-msgid "ClusterIntegration|Send Cilium Logs"
+msgid "ClusterIntegration|Send Container Network Policies Logs"
msgstr ""
-msgid "ClusterIntegration|Send ModSecurity Logs"
+msgid "ClusterIntegration|Send Web Application Firewall Logs"
msgstr ""
msgid "ClusterIntegration|Service Token"
@@ -5450,15 +5608,12 @@ msgstr ""
msgid "ClusterIntegration|documentation"
msgstr ""
-msgid "ClusterIntegration|installed via %{installed_via}"
+msgid "ClusterIntegration|installed via %{linkStart}Cloud Run%{linkEnd}"
msgstr ""
msgid "ClusterIntegration|meets the requirements"
msgstr ""
-msgid "ClusterIntegration|pricing"
-msgstr ""
-
msgid "ClusterIntegration|sign up"
msgstr ""
@@ -5495,6 +5650,15 @@ msgstr ""
msgid "Code"
msgstr ""
+msgid "Code Coverage: %{coveragePercentage}%{percentSymbol}"
+msgstr ""
+
+msgid "Code Coverage| Empty code coverage data"
+msgstr ""
+
+msgid "Code Coverage|Couldn't fetch the code coverage data"
+msgstr ""
+
msgid "Code Owners"
msgstr ""
@@ -5549,7 +5713,7 @@ msgstr ""
msgid "Collapse approvers"
msgstr ""
-msgid "Collapse child epics"
+msgid "Collapse milestones"
msgstr ""
msgid "Collapse replies"
@@ -5567,6 +5731,9 @@ msgstr ""
msgid "Coming soon"
msgstr ""
+msgid "Comma-separated, e.g. '1.1.1.1, 2.2.2.0/24'"
+msgstr ""
+
msgid "Command"
msgstr ""
@@ -5737,6 +5904,12 @@ msgstr ""
msgid "Compliance framework (optional)"
msgstr ""
+msgid "Compliance frameworks"
+msgstr ""
+
+msgid "ComplianceDashboard|created by:"
+msgstr ""
+
msgid "ComplianceFramework|GDPR"
msgstr ""
@@ -5875,6 +6048,9 @@ msgstr ""
msgid "Connection timed out"
msgstr ""
+msgid "Connection timeout"
+msgstr ""
+
msgid "Contact sales to upgrade"
msgstr ""
@@ -5930,9 +6106,6 @@ msgstr ""
msgid "ContainerRegistry|CLI Commands"
msgstr ""
-msgid "ContainerRegistry|Compressed Size"
-msgstr ""
-
msgid "ContainerRegistry|Container Registry"
msgstr ""
@@ -5948,6 +6121,9 @@ msgstr ""
msgid "ContainerRegistry|Copy push command"
msgstr ""
+msgid "ContainerRegistry|Delete selected"
+msgstr ""
+
msgid "ContainerRegistry|Docker connection error"
msgstr ""
@@ -5981,16 +6157,16 @@ msgstr ""
msgid "ContainerRegistry|If you are not already logged in, you need to authenticate to the Container Registry by using your GitLab username and password. If you have %{twofaDocLinkStart}Two-Factor Authentication%{twofaDocLinkEnd} enabled, use a %{personalAccessTokensDocLinkStart}Personal Access Token%{personalAccessTokensDocLinkEnd} instead of a password."
msgstr ""
-msgid "ContainerRegistry|Image ID"
+msgid "ContainerRegistry|Image ID: %{imageId}"
msgstr ""
msgid "ContainerRegistry|Image Repositories"
msgstr ""
-msgid "ContainerRegistry|Keep and protect the images that matter most."
+msgid "ContainerRegistry|Image tags"
msgstr ""
-msgid "ContainerRegistry|Last Updated"
+msgid "ContainerRegistry|Keep and protect the images that matter most."
msgstr ""
msgid "ContainerRegistry|Login"
@@ -6005,6 +6181,9 @@ msgstr ""
msgid "ContainerRegistry|Please contact your administrator."
msgstr ""
+msgid "ContainerRegistry|Published %{timeInfo}"
+msgstr ""
+
msgid "ContainerRegistry|Push an image"
msgstr ""
@@ -6020,9 +6199,6 @@ msgstr ""
msgid "ContainerRegistry|Remove repository"
msgstr ""
-msgid "ContainerRegistry|Remove selected tags"
-msgstr ""
-
msgid "ContainerRegistry|Remove tag"
msgid_plural "ContainerRegistry|Remove tags"
msgstr[0] ""
@@ -6052,9 +6228,6 @@ msgstr ""
msgid "ContainerRegistry|Sorry, your filter produced no results."
msgstr ""
-msgid "ContainerRegistry|Tag"
-msgstr ""
-
msgid "ContainerRegistry|Tag expiration policy"
msgstr ""
@@ -6199,21 +6372,6 @@ msgstr ""
msgid "Control the display of third party offers."
msgstr ""
-msgid "Control the maximum concurrency of LFS/attachment backfill for this secondary node"
-msgstr ""
-
-msgid "Control the maximum concurrency of container repository operations for this Geo node"
-msgstr ""
-
-msgid "Control the maximum concurrency of repository backfill for this secondary node"
-msgstr ""
-
-msgid "Control the maximum concurrency of verification operations for this Geo node"
-msgstr ""
-
-msgid "Control the minimum interval in days that a repository should be reverified for this primary node"
-msgstr ""
-
msgid "Cookie domain"
msgstr ""
@@ -6355,6 +6513,9 @@ msgstr ""
msgid "Could not find design."
msgstr ""
+msgid "Could not find iteration"
+msgstr ""
+
msgid "Could not remove the trigger."
msgstr ""
@@ -6391,6 +6552,12 @@ msgstr ""
msgid "Coverage"
msgstr ""
+msgid "Coverage Fuzzing"
+msgstr ""
+
+msgid "Crash State"
+msgstr ""
+
msgid "Create"
msgstr ""
@@ -6439,6 +6606,9 @@ msgstr ""
msgid "Create a personal access token on your account to pull or push via %{protocol}."
msgstr ""
+msgid "Create a project pre-populated with the necessary files to get you started quickly."
+msgstr ""
+
msgid "Create an account using:"
msgstr ""
@@ -6607,6 +6777,9 @@ msgstr ""
msgid "Creates branch '%{branch_name}' and a merge request to resolve this issue."
msgstr ""
+msgid "Creating"
+msgstr ""
+
msgid "Creating epic"
msgstr ""
@@ -6658,7 +6831,10 @@ msgstr ""
msgid "Current vulnerabilities count"
msgstr ""
-msgid "CurrentUser|Buy CI minutes"
+msgid "CurrentUser|Buy Pipeline minutes"
+msgstr ""
+
+msgid "CurrentUser|One of your groups is running out"
msgstr ""
msgid "CurrentUser|Profile"
@@ -6673,6 +6849,9 @@ msgstr ""
msgid "CurrentUser|Upgrade"
msgstr ""
+msgid "Custom Attributes"
+msgstr ""
+
msgid "Custom CI configuration path"
msgstr ""
@@ -6936,12 +7115,18 @@ msgstr ""
msgid "DAG"
msgstr ""
+msgid "DAG visualization requires at least 3 dependent jobs."
+msgstr ""
+
msgid "DNS"
msgstr ""
msgid "Dashboard"
msgstr ""
+msgid "Dashboard uid not found"
+msgstr ""
+
msgid "DashboardProjects|All"
msgstr ""
@@ -6966,6 +7151,9 @@ msgstr ""
msgid "Data is still calculating..."
msgstr ""
+msgid "Datasource name not found"
+msgstr ""
+
msgid "Date"
msgstr ""
@@ -7146,6 +7334,9 @@ msgstr ""
msgid "Delete this attachment"
msgstr ""
+msgid "Delete user list"
+msgstr ""
+
msgid "Delete variable"
msgstr ""
@@ -7462,6 +7653,9 @@ msgstr ""
msgid "Deploying to"
msgstr ""
+msgid "Deploying to AWS is easy with GitLab"
+msgstr ""
+
msgid "Deployment Frequency"
msgstr ""
@@ -7824,6 +8018,9 @@ msgstr ""
msgid "Dismissed on pipeline %{pipelineLink} at %{projectLink}"
msgstr ""
+msgid "Display alerts from all your monitoring tools directly within GitLab."
+msgstr ""
+
msgid "Display name"
msgstr ""
@@ -7881,9 +8078,6 @@ msgstr ""
msgid "Don't show again"
msgstr ""
-msgid "Don't worry, you can access this tour by clicking on the help icon in the top right corner and choose <strong>Learn GitLab</strong>."
-msgstr ""
-
msgid "Done"
msgstr ""
@@ -8010,6 +8204,9 @@ msgstr ""
msgid "Edit Release"
msgstr ""
+msgid "Edit Slack integration"
+msgstr ""
+
msgid "Edit Snippet"
msgstr ""
@@ -8049,6 +8246,9 @@ msgstr ""
msgid "Edit issues"
msgstr ""
+msgid "Edit iteration"
+msgstr ""
+
msgid "Edit public deploy key"
msgstr ""
@@ -8097,6 +8297,9 @@ msgstr ""
msgid "Email"
msgstr ""
+msgid "Email Notification"
+msgstr ""
+
msgid "Email address"
msgstr ""
@@ -8109,6 +8312,9 @@ msgstr ""
msgid "Email not verified. Please verify your email in Salesforce."
msgstr ""
+msgid "Email notification for unknown sign-ins"
+msgstr ""
+
msgid "Email patch"
msgstr ""
@@ -8340,6 +8546,9 @@ msgstr ""
msgid "Enforce DNS rebinding attack protection"
msgstr ""
+msgid "Enforce personal access token expiration"
+msgstr ""
+
msgid "Ensure connectivity is available from the GitLab server to the Prometheus server"
msgstr ""
@@ -8409,6 +8618,9 @@ msgstr ""
msgid "Enter the name of your application, and we'll return a unique %{type}."
msgstr ""
+msgid "Enter weights for storages for new repositories."
+msgstr ""
+
msgid "Enter your password to approve"
msgstr ""
@@ -8418,15 +8630,27 @@ msgstr ""
msgid "Environment does not have deployments"
msgstr ""
+msgid "Environment is required for Stages::MetricEndpointInserter"
+msgstr ""
+
+msgid "Environment is required for Stages::VariableEndpointInserter"
+msgstr ""
+
msgid "Environment scope"
msgstr ""
+msgid "Environment variables are applied to all project environments in this instance via the Runner. You can use environment variables for passwords, secret keys, etc. Make variables available to the running application by prepending the variable key with <code>K8S_SECRET_</code>. You can set variables to be:"
+msgstr ""
+
msgid "Environment variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. Additionally, they can be masked so they are hidden in job logs, though they must match certain regexp requirements to do so. You can use environment variables for passwords, secret keys, or whatever you want."
msgstr ""
msgid "Environment variables are configured by your administrator to be %{link_start}protected%{link_end} by default"
msgstr ""
+msgid "Environment variables on this GitLab instance are configured to be %{link_start}protected%{link_end} by default"
+msgstr ""
+
msgid "Environment:"
msgstr ""
@@ -8958,6 +9182,9 @@ msgstr ""
msgid "EventFilterBy|Filter by comments"
msgstr ""
+msgid "EventFilterBy|Filter by designs"
+msgstr ""
+
msgid "EventFilterBy|Filter by epic events"
msgstr ""
@@ -9090,15 +9317,15 @@ msgstr ""
msgid "Expand approvers"
msgstr ""
-msgid "Expand child epics"
-msgstr ""
-
msgid "Expand down"
msgstr ""
msgid "Expand dropdown"
msgstr ""
+msgid "Expand milestones"
+msgstr ""
+
msgid "Expand sidebar"
msgstr ""
@@ -9114,6 +9341,9 @@ msgstr ""
msgid "Expiration date"
msgstr ""
+msgid "Expiration not enforced"
+msgstr ""
+
msgid "Expiration policy for the Container Registry is a perfect solution for keeping the Registry space down while still enjoying the full power of GitLab CI/CD."
msgstr ""
@@ -9234,6 +9464,9 @@ msgstr ""
msgid "Failed Jobs"
msgstr ""
+msgid "Failed on"
+msgstr ""
+
msgid "Failed to add a Zoom meeting"
msgstr ""
@@ -9294,13 +9527,19 @@ msgstr ""
msgid "Failed to install."
msgstr ""
+msgid "Failed to load assignees. Please try again."
+msgstr ""
+
+msgid "Failed to load authors. Please try again."
+msgstr ""
+
msgid "Failed to load emoji list."
msgstr ""
msgid "Failed to load error details from Sentry."
msgstr ""
-msgid "Failed to load errors from Sentry. Error message: %{errorMessage}"
+msgid "Failed to load errors from Sentry."
msgstr ""
msgid "Failed to load group activity metrics. Please try again."
@@ -9381,6 +9620,9 @@ msgstr ""
msgid "Failed to set due date because the date format is invalid."
msgstr ""
+msgid "Failed to set iteration on this issue. Please try again."
+msgstr ""
+
msgid "Failed to signing using smartcard authentication"
msgstr ""
@@ -9603,13 +9845,10 @@ msgstr ""
msgid "FeatureFlags|Target environments"
msgstr ""
-msgid "FeatureFlags|There are no active feature flags"
-msgstr ""
-
-msgid "FeatureFlags|There are no inactive feature flags"
+msgid "FeatureFlags|There was an error fetching the feature flags."
msgstr ""
-msgid "FeatureFlags|There was an error fetching the feature flags."
+msgid "FeatureFlags|There was an error retrieving user lists"
msgstr ""
msgid "FeatureFlags|Try again in a few moments or contact your support team."
@@ -9621,9 +9860,18 @@ msgstr ""
msgid "FeatureFlag|Delete strategy"
msgstr ""
+msgid "FeatureFlag|List"
+msgstr ""
+
msgid "FeatureFlag|Percentage"
msgstr ""
+msgid "FeatureFlag|Select a user list"
+msgstr ""
+
+msgid "FeatureFlag|There are no configured user lists"
+msgstr ""
+
msgid "FeatureFlag|Type"
msgstr ""
@@ -9780,6 +10028,12 @@ msgstr ""
msgid "Filter..."
msgstr ""
+msgid "Find File"
+msgstr ""
+
+msgid "Find bugs in your code with coverage-guided fuzzing"
+msgstr ""
+
msgid "Find by path"
msgstr ""
@@ -10044,6 +10298,9 @@ msgstr ""
msgid "Geo allows you to replicate your GitLab instance to other geographical locations."
msgstr ""
+msgid "Geo nodes are paused using a command run on the node"
+msgstr ""
+
msgid "GeoNodeStatusEvent|%{timeAgoStr} (%{pendingEvents} events)"
msgstr ""
@@ -10128,6 +10385,9 @@ msgstr ""
msgid "GeoNodes|Not checksummed"
msgstr ""
+msgid "GeoNodes|Package files"
+msgstr ""
+
msgid "GeoNodes|Pausing replication stops the sync process. Are you sure?"
msgstr ""
@@ -10146,6 +10406,9 @@ msgstr ""
msgid "GeoNodes|Replication slots"
msgstr ""
+msgid "GeoNodes|Replication status"
+msgstr ""
+
msgid "GeoNodes|Repositories"
msgstr ""
@@ -10356,6 +10619,9 @@ msgstr ""
msgid "Geo|Synchronization failed - %{error}"
msgstr ""
+msgid "Geo|Synchronization of %{itemTitle} is disabled."
+msgstr ""
+
msgid "Geo|The database is currently %{db_lag} behind the primary node."
msgstr ""
@@ -10668,6 +10934,9 @@ msgstr ""
msgid "Go to %{link_to_google_takeout}."
msgstr ""
+msgid "Go to <strong>Issues</strong> > <strong>Boards</strong> to access your personalized learning issue board."
+msgstr ""
+
msgid "Go to Pipelines"
msgstr ""
@@ -10767,9 +11036,6 @@ msgstr ""
msgid "Go to your snippets"
msgstr ""
-msgid "Golden Tanuki"
-msgstr ""
-
msgid "Google Cloud Platform"
msgstr ""
@@ -10791,6 +11057,9 @@ msgstr ""
msgid "Grafana URL"
msgstr ""
+msgid "Grafana response contains invalid json"
+msgstr ""
+
msgid "GrafanaIntegration|API Token"
msgstr ""
@@ -10839,9 +11108,6 @@ msgstr ""
msgid "Group %{group_name} was successfully created."
msgstr ""
-msgid "Group '%{group_name}' is being imported."
-msgstr ""
-
msgid "Group Audit Events"
msgstr ""
@@ -10878,9 +11144,6 @@ msgstr ""
msgid "Group by:"
msgstr ""
-msgid "Group could not be imported: %{errors}"
-msgstr ""
-
msgid "Group description"
msgstr ""
@@ -10908,9 +11171,15 @@ msgstr ""
msgid "Group has not been marked for deletion"
msgstr ""
+msgid "Group import could not be scheduled"
+msgstr ""
+
msgid "Group info:"
msgstr ""
+msgid "Group is required when cluster_type is :group"
+msgstr ""
+
msgid "Group maintainers can register group runners in the %{link}"
msgstr ""
@@ -10974,6 +11243,24 @@ msgstr ""
msgid "GroupActivyMetrics|Recent activity (last 90 days)"
msgstr ""
+msgid "GroupImport|Failed to import group."
+msgstr ""
+
+msgid "GroupImport|Group '%{group_name}' is being imported."
+msgstr ""
+
+msgid "GroupImport|Group could not be imported: %{errors}"
+msgstr ""
+
+msgid "GroupImport|Please wait while we import the group for you. Refresh at will."
+msgstr ""
+
+msgid "GroupImport|The group was successfully imported."
+msgstr ""
+
+msgid "GroupImport|Unable to process group import file"
+msgstr ""
+
msgid "GroupRoadmap|%{dateWord} – No end date"
msgstr ""
@@ -11241,6 +11528,9 @@ msgstr ""
msgid "Groups (%{groups})"
msgstr ""
+msgid "Groups and projects"
+msgstr ""
+
msgid "Groups can also be nested by creating %{subgroup_docs_link_start}subgroups%{subgroup_docs_link_end}."
msgstr ""
@@ -11286,6 +11576,33 @@ msgstr ""
msgid "GroupsEmptyState|You can manage your group member’s permissions and access to each project in the group."
msgstr ""
+msgid "GroupsNew|Contact an administrator to enable options for importing your group."
+msgstr ""
+
+msgid "GroupsNew|Create"
+msgstr ""
+
+msgid "GroupsNew|Create group"
+msgstr ""
+
+msgid "GroupsNew|GitLab group export"
+msgstr ""
+
+msgid "GroupsNew|Import"
+msgstr ""
+
+msgid "GroupsNew|Import group"
+msgstr ""
+
+msgid "GroupsNew|My Awesome Group"
+msgstr ""
+
+msgid "GroupsNew|No import options available"
+msgstr ""
+
+msgid "GroupsNew|To copy a GitLab group between installations, navigate to the group settings page for the original installation, generate an export file, and upload it here."
+msgstr ""
+
msgid "GroupsTree|Are you sure you want to leave the \"%{fullName}\" group?"
msgstr ""
@@ -11388,6 +11705,9 @@ msgstr ""
msgid "Helps reduce request volume for protected paths"
msgstr ""
+msgid "Here are all your projects in your group, including the one you just created. To start, let’s take a look at your personalized learning project which will help you learn about GitLab at your own pace."
+msgstr ""
+
msgid "Here you will find recent merge request activity"
msgstr ""
@@ -11402,6 +11722,9 @@ msgid_plural "Hide charts"
msgstr[0] ""
msgstr[1] ""
+msgid "Hide details"
+msgstr ""
+
msgid "Hide file browser"
msgstr ""
@@ -11411,6 +11734,9 @@ msgstr ""
msgid "Hide host keys manual input"
msgstr ""
+msgid "Hide list"
+msgstr ""
+
msgid "Hide marketing-related entries from help"
msgstr ""
@@ -11431,9 +11757,6 @@ msgstr[1] ""
msgid "Hide values"
msgstr ""
-msgid "Hiding all labels"
-msgstr ""
-
msgid "High or unknown vulnerabilities present"
msgstr ""
@@ -11518,6 +11841,9 @@ msgstr ""
msgid "ID:"
msgstr ""
+msgid "IDE"
+msgstr ""
+
msgid "IDE|Allow live previews of JavaScript projects in the Web IDE using CodeSandbox Live Preview."
msgstr ""
@@ -11608,10 +11934,10 @@ msgstr ""
msgid "If enabled"
msgstr ""
-msgid "If enabled, access to projects will be validated on an external service using their classification label."
+msgid "If enabled, GitLab will handle Object Storage replication using Geo. %{linkStart}More information%{linkEnd}"
msgstr ""
-msgid "If enabled, and if object storage is enabled, GitLab will handle Object Storage replication using Geo"
+msgid "If enabled, access to projects will be validated on an external service using their classification label."
msgstr ""
msgid "If there is no previous license or if the previous license has expired, some GitLab functionality will be blocked until a new, valid license is uploaded."
@@ -11973,6 +12299,9 @@ msgid_plural "Instances"
msgstr[0] ""
msgstr[1] ""
+msgid "Instance Configuration"
+msgstr ""
+
msgid "Instance Statistics visibility"
msgstr ""
@@ -11994,9 +12323,6 @@ msgstr ""
msgid "Integrations"
msgstr ""
-msgid "Integrations allow you to integrate GitLab with other applications"
-msgstr ""
-
msgid "Integrations|All details"
msgstr ""
@@ -12108,6 +12434,9 @@ msgstr ""
msgid "Invalid query"
msgstr ""
+msgid "Invalid repository bundle for snippet with id %{snippet_id}"
+msgstr ""
+
msgid "Invalid repository path"
msgstr ""
@@ -12126,6 +12455,9 @@ msgstr ""
msgid "Invalid two-factor code."
msgstr ""
+msgid "Invalid yaml"
+msgstr ""
+
msgid "Invitation"
msgstr ""
@@ -12345,15 +12677,24 @@ msgstr ""
msgid "It seems like the Dependency Scanning job ran successfully, but no dependencies have been detected in your project."
msgstr ""
+msgid "It seems that there is currently no available data for code coverage"
+msgstr ""
+
msgid "It's you"
msgstr ""
+msgid "Iteration"
+msgstr ""
+
msgid "Iteration changed to"
msgstr ""
msgid "Iteration removed"
msgstr ""
+msgid "Iteration updated"
+msgstr ""
+
msgid "Iterations"
msgstr ""
@@ -12384,12 +12725,18 @@ msgstr ""
msgid "January"
msgstr ""
+msgid "Jira Issues"
+msgstr ""
+
msgid "Jira import is already running."
msgstr ""
msgid "Jira integration not configured."
msgstr ""
+msgid "Jira project key is not configured"
+msgstr ""
+
msgid "Jira project: %{importProject}"
msgstr ""
@@ -12570,6 +12917,9 @@ msgstr ""
msgid "Keep divergent refs"
msgstr ""
+msgid "Kerberos access denied"
+msgstr ""
+
msgid "Key"
msgstr ""
@@ -12579,10 +12929,13 @@ msgstr ""
msgid "Key: %{key}"
msgstr ""
-msgid "Keyboard Shortcuts"
+msgid "Keyboard shortcuts"
msgstr ""
-msgid "Keyboard shortcuts"
+msgid "Keys"
+msgstr ""
+
+msgid "Ki"
msgstr ""
msgid "Kubernetes"
@@ -12624,6 +12977,9 @@ msgstr ""
msgid "LDAP"
msgstr ""
+msgid "LDAP Synchronization"
+msgstr ""
+
msgid "LDAP settings"
msgstr ""
@@ -12848,12 +13204,18 @@ msgstr ""
msgid "Learn more about custom project templates"
msgstr ""
+msgid "Learn more about deploying to AWS"
+msgstr ""
+
msgid "Learn more about deploying to a cluster"
msgstr ""
msgid "Learn more about group-level project templates"
msgstr ""
+msgid "Learn more about job dependencies"
+msgstr ""
+
msgid "Learn more about signing commits"
msgstr ""
@@ -12905,9 +13267,21 @@ msgstr ""
msgid "License History"
msgstr ""
+msgid "License ID:"
+msgstr ""
+
+msgid "License URL"
+msgstr ""
+
msgid "License-Check"
msgstr ""
+msgid "LicenseCompliance|%{docLinkStart}License Approvals%{docLinkEnd} are active"
+msgstr ""
+
+msgid "LicenseCompliance|%{docLinkStart}License Approvals%{docLinkEnd} are inactive"
+msgstr ""
+
msgid "LicenseCompliance|Add a license"
msgstr ""
@@ -12929,9 +13303,15 @@ msgstr ""
msgid "LicenseCompliance|Deny"
msgstr ""
+msgid "LicenseCompliance|Learn more about %{linkStart}License Approvals%{linkEnd}"
+msgstr ""
+
msgid "LicenseCompliance|License"
msgstr ""
+msgid "LicenseCompliance|License Approvals"
+msgstr ""
+
msgid "LicenseCompliance|License Compliance detected %d license and policy violation for the source branch only; approval required"
msgid_plural "LicenseCompliance|License Compliance detected %d licenses and policy violations for the source branch only; approval required"
msgstr[0] ""
@@ -13152,6 +13532,9 @@ msgstr ""
msgid "List your Bitbucket Server repositories"
msgstr ""
+msgid "Lists"
+msgstr ""
+
msgid "Live preview"
msgstr ""
@@ -13335,6 +13718,9 @@ msgstr ""
msgid "Manage project labels"
msgstr ""
+msgid "Manage storage usage"
+msgstr ""
+
msgid "Manage two-factor authentication"
msgstr ""
@@ -13350,6 +13736,9 @@ msgstr ""
msgid "Manifest file import"
msgstr ""
+msgid "Manifest import"
+msgstr ""
+
msgid "Manual job"
msgstr ""
@@ -13497,6 +13886,12 @@ msgstr ""
msgid "Maximum field length"
msgstr ""
+msgid "Maximum file size is 2MB. Please select a smaller file."
+msgstr ""
+
+msgid "Maximum import size (MB)"
+msgstr ""
+
msgid "Maximum job timeout"
msgstr ""
@@ -13536,6 +13931,9 @@ msgstr ""
msgid "Maximum size of Elasticsearch bulk indexing requests."
msgstr ""
+msgid "Maximum size of import files."
+msgstr ""
+
msgid "Maximum size of individual attachments in comments."
msgstr ""
@@ -13587,6 +13985,9 @@ msgstr ""
msgid "Merge (when the pipeline succeeds)"
msgstr ""
+msgid "Merge Conflicts"
+msgstr ""
+
msgid "Merge Request"
msgstr ""
@@ -13713,6 +14114,9 @@ msgstr ""
msgid "MergeRequests|Squash task canceled: another squash is already in progress."
msgstr ""
+msgid "MergeRequests|This project does not allow squashing commits when merge requests are accepted."
+msgstr ""
+
msgid "MergeRequests|Thread stays resolved"
msgstr ""
@@ -13866,6 +14270,12 @@ msgstr ""
msgid "MetricsSettings|Add a button to the metrics dashboard linking directly to your existing external dashboard."
msgstr ""
+msgid "MetricsSettings|Choose whether to display dashboard metrics in UTC or the user's local timezone."
+msgstr ""
+
+msgid "MetricsSettings|Dashboard timezone"
+msgstr ""
+
msgid "MetricsSettings|External dashboard URL"
msgstr ""
@@ -13875,6 +14285,12 @@ msgstr ""
msgid "MetricsSettings|Metrics Dashboard"
msgstr ""
+msgid "MetricsSettings|UTC (Coordinated Universal Time)"
+msgstr ""
+
+msgid "MetricsSettings|User's local timezone"
+msgstr ""
+
msgid "Metrics|Add metric"
msgstr ""
@@ -13890,6 +14306,9 @@ msgstr ""
msgid "Metrics|Create metric"
msgstr ""
+msgid "Metrics|Current"
+msgstr ""
+
msgid "Metrics|Delete metric"
msgstr ""
@@ -13940,6 +14359,9 @@ msgstr ""
msgid "Metrics|Max"
msgstr ""
+msgid "Metrics|Min"
+msgstr ""
+
msgid "Metrics|Must be a valid PromQL query."
msgstr ""
@@ -13955,6 +14377,9 @@ msgstr ""
msgid "Metrics|Refresh dashboard"
msgstr ""
+msgid "Metrics|Select a value"
+msgstr ""
+
msgid "Metrics|Star dashboard"
msgstr ""
@@ -13973,12 +14398,18 @@ msgstr ""
msgid "Metrics|There was an error getting annotations information."
msgstr ""
+msgid "Metrics|There was an error getting dashboard validation warnings information."
+msgstr ""
+
msgid "Metrics|There was an error getting deployment information."
msgstr ""
msgid "Metrics|There was an error getting environments information."
msgstr ""
+msgid "Metrics|There was an error getting options for variable \"%{name}\"."
+msgstr ""
+
msgid "Metrics|There was an error trying to validate your query"
msgstr ""
@@ -14021,6 +14452,9 @@ msgstr ""
msgid "Metrics|You're about to permanently delete this metric. This cannot be undone."
msgstr ""
+msgid "Metrics|Your dashboard schema is invalid. Edit the dashboard to correct the YAML schema."
+msgstr ""
+
msgid "Metrics|e.g. HTTP requests"
msgstr ""
@@ -14036,12 +14470,18 @@ msgstr ""
msgid "Metrics|e.g. req/sec"
msgstr ""
+msgid "Mi"
+msgstr ""
+
msgid "Microsoft Azure"
msgstr ""
msgid "Middleman project with Static Site Editor support"
msgstr ""
+msgid "Migrate your data from an external source like GitHub, Bitbucket, or another instance of GitLab."
+msgstr ""
+
msgid "Migrated %{success_count}/%{total_count} files."
msgstr ""
@@ -14176,6 +14616,9 @@ msgstr ""
msgid "Minimum capacity to be available before we schedule more mirrors preemptively."
msgstr ""
+msgid "Minimum interval in days"
+msgstr ""
+
msgid "Minimum length is %{minimum_password_length} characters"
msgstr ""
@@ -14362,6 +14805,12 @@ msgstr ""
msgid "Multiple uploaders found: %{uploader_types}"
msgstr ""
+msgid "Must match with the %{codeStart}external_url%{codeEnd} in %{codeStart}/etc/gitlab/gitlab.rb%{codeEnd}."
+msgstr ""
+
+msgid "Must match with the %{codeStart}geo_node_name%{codeEnd} in %{codeStart}/etc/gitlab/gitlab.rb%{codeEnd}. %{linkStart}More information%{linkEnd}"
+msgstr ""
+
msgid "My Awesome Group"
msgstr ""
@@ -14476,10 +14925,16 @@ msgstr ""
msgid "New Application"
msgstr ""
+msgid "New Branch"
+msgstr ""
+
+msgid "New Deploy Key"
+msgstr ""
+
msgid "New Environment"
msgstr ""
-msgid "New Geo Node"
+msgid "New File"
msgstr ""
msgid "New Group"
@@ -14496,15 +14951,15 @@ msgid_plural "New Issues"
msgstr[0] ""
msgstr[1] ""
-msgid "New Iteration"
-msgstr ""
-
msgid "New Jira import"
msgstr ""
msgid "New Label"
msgstr ""
+msgid "New Merge Request"
+msgstr ""
+
msgid "New Milestone"
msgstr ""
@@ -14523,6 +14978,9 @@ msgstr ""
msgid "New Snippet"
msgstr ""
+msgid "New User"
+msgstr ""
+
msgid "New branch"
msgstr ""
@@ -14565,6 +15023,9 @@ msgstr ""
msgid "New issue title"
msgstr ""
+msgid "New iteration"
+msgstr ""
+
msgid "New iteration created"
msgstr ""
@@ -14742,6 +15203,12 @@ msgstr ""
msgid "No grouping"
msgstr ""
+msgid "No iteration"
+msgstr ""
+
+msgid "No iterations to show"
+msgstr ""
+
msgid "No job log"
msgstr ""
@@ -14784,6 +15251,9 @@ msgstr ""
msgid "No other labels with such name or description"
msgstr ""
+msgid "No panels matching properties %{opts}"
+msgstr ""
+
msgid "No parent group"
msgstr ""
@@ -14817,6 +15287,9 @@ msgstr ""
msgid "No schedules"
msgstr ""
+msgid "No stack trace for this error"
+msgstr ""
+
msgid "No starrers matched your search"
msgstr ""
@@ -14829,7 +15302,7 @@ msgstr ""
msgid "No test coverage"
msgstr ""
-msgid "No thanks, don't show this again"
+msgid "No thanks"
msgstr ""
msgid "No vulnerabilities present"
@@ -14844,9 +15317,6 @@ msgstr ""
msgid "No, directly import the existing email addresses and usernames."
msgstr ""
-msgid "No, not interested right now"
-msgstr ""
-
msgid "No. of commits"
msgstr ""
@@ -14892,9 +15362,6 @@ msgstr ""
msgid "Not found."
msgstr ""
-msgid "Not helpful"
-msgstr ""
-
msgid "Not now"
msgstr ""
@@ -15051,6 +15518,9 @@ msgstr ""
msgid "Notifications on"
msgstr ""
+msgid "Notify users by email when sign-in location is not recognized"
+msgstr ""
+
msgid "Nov"
msgstr ""
@@ -15117,9 +15587,6 @@ msgstr ""
msgid "Oh no!"
msgstr ""
-msgid "Ok let's go"
-msgstr ""
-
msgid "Oldest first"
msgstr ""
@@ -15132,16 +15599,49 @@ msgstr ""
msgid "On track"
msgstr ""
+msgid "OnDemandScans|Attached branch"
+msgstr ""
+
+msgid "OnDemandScans|Attached branch is where the scan job runs."
+msgstr ""
+
+msgid "OnDemandScans|Could not run the scan. Please try again."
+msgstr ""
+
msgid "OnDemandScans|Create new DAST scan"
msgstr ""
+msgid "OnDemandScans|DAST will scan the target URL and any discovered sub URLs."
+msgstr ""
+
+msgid "OnDemandScans|New on-demand DAST scan"
+msgstr ""
+
msgid "OnDemandScans|On-demand Scans"
msgstr ""
+msgid "OnDemandScans|On-demand scans run outside the DevOps cycle and find vulnerabilities in your projects. %{learnMoreLinkStart}Learn more%{learnMoreLinkEnd}"
+msgstr ""
+
+msgid "OnDemandScans|Only a passive scan can be performed on demand."
+msgstr ""
+
+msgid "OnDemandScans|Passive DAST Scan"
+msgstr ""
+
+msgid "OnDemandScans|Please enter a valid URL format, ex: http://www.example.com/home"
+msgstr ""
+
+msgid "OnDemandScans|Run this scan"
+msgstr ""
+
+msgid "OnDemandScans|Scan mode"
+msgstr ""
+
msgid "OnDemandScans|Schedule or run scans immediately against target sites. Currently available on-demand scan type: DAST. %{helpLinkStart}More information%{helpLinkEnd}"
msgstr ""
-msgid "Onboarding"
+msgid "OnDemandScans|Target URL"
msgstr ""
msgid "Once imported, repositories can be mirrored over SSH. Read more %{link_start}here%{link_end}."
@@ -15167,6 +15667,9 @@ msgstr ""
msgid "One or more of you personal access tokens were revoked"
msgstr ""
+msgid "One or more of your %{provider} projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
+msgstr ""
+
msgid "One or more of your Bitbucket projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
msgstr ""
@@ -15668,6 +16171,9 @@ msgstr ""
msgid "Parent epic is not present."
msgstr ""
+msgid "Parsing error for param :embed_json. %{message}"
+msgstr ""
+
msgid "Part of merge request changes"
msgstr ""
@@ -15680,6 +16186,9 @@ msgstr ""
msgid "Passed"
msgstr ""
+msgid "Passed on"
+msgstr ""
+
msgid "Password"
msgstr ""
@@ -15764,6 +16273,9 @@ msgstr ""
msgid "Perform common operations on GitLab project"
msgstr ""
+msgid "Performance and resource management"
+msgstr ""
+
msgid "Performance optimization"
msgstr ""
@@ -15971,7 +16483,7 @@ msgstr ""
msgid "Pipelines|Something went wrong while cleaning runners cache."
msgstr ""
-msgid "Pipelines|There are currently no %{scope} pipelines."
+msgid "Pipelines|There are currently no finished pipelines."
msgstr ""
msgid "Pipelines|There are currently no pipelines."
@@ -16082,6 +16594,9 @@ msgstr ""
msgid "Pipeline|Stop pipeline #%{pipelineId}?"
msgstr ""
+msgid "Pipeline|Tag name"
+msgstr ""
+
msgid "Pipeline|Trigger author"
msgstr ""
@@ -16163,12 +16678,18 @@ msgstr ""
msgid "Please check your email (%{email}) to verify that you own this address and unlock the power of CI/CD. Didn't receive it? %{resend_link}. Wrong email address? %{update_link}."
msgstr ""
+msgid "Please choose a file"
+msgstr ""
+
msgid "Please choose a group URL with no special characters."
msgstr ""
msgid "Please complete your profile with email address"
msgstr ""
+msgid "Please convert %{linkStart}them to Git%{linkEnd}, and go through the %{linkToImportFlow} again."
+msgstr ""
+
msgid "Please convert them to %{link_to_git}, and go through the %{link_to_import_flow} again."
msgstr ""
@@ -16226,6 +16747,9 @@ msgstr ""
msgid "Please provide a valid email address."
msgstr ""
+msgid "Please provide attributes to update"
+msgstr ""
+
msgid "Please refer to <a href=\"%{docs_url}\">%{docs_url}</a>"
msgstr ""
@@ -16961,6 +17485,9 @@ msgstr ""
msgid "Project has too many %{label_for_message} to search"
msgstr ""
+msgid "Project is required when cluster_type is :project"
+msgstr ""
+
msgid "Project members"
msgstr ""
@@ -17078,7 +17605,40 @@ msgstr ""
msgid "ProjectService|Comment"
msgstr ""
-msgid "ProjectService|Comment will be posted on each event"
+msgid "ProjectService|Event will be triggered by a push to the repository"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a commit is created/updated"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a confidential issue is created/updated/closed"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a deployment finishes"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a merge request is created/updated/merged"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a new tag is pushed to the repository"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a new, unique alert is recorded"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a pipeline status changes"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a wiki page is created/updated"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when an issue is created/updated/closed"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when someone adds a comment"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when someone adds a comment on a confidential issue"
msgstr ""
msgid "ProjectService|Perform common operations on GitLab project: %{project_name}"
@@ -17129,6 +17689,9 @@ msgstr ""
msgid "ProjectSettings|Enable 'Delete source branch' option by default"
msgstr ""
+msgid "ProjectSettings|Enable merge trains and pipelines for merged results"
+msgstr ""
+
msgid "ProjectSettings|Every merge creates a merge commit"
msgstr ""
@@ -17159,7 +17722,7 @@ msgstr ""
msgid "ProjectSettings|Forks"
msgstr ""
-msgid "ProjectSettings|Git Large File Storage"
+msgid "ProjectSettings|Git Large File Storage (LFS)"
msgstr ""
msgid "ProjectSettings|Internal"
@@ -17195,9 +17758,6 @@ msgstr ""
msgid "ProjectSettings|Merge options"
msgstr ""
-msgid "ProjectSettings|Merge pipelines will try to validate the post-merge result prior to merging"
-msgstr ""
-
msgid "ProjectSettings|Merge requests"
msgstr ""
@@ -17225,6 +17785,9 @@ msgstr ""
msgid "ProjectSettings|Pipelines"
msgstr ""
+msgid "ProjectSettings|Pipelines for merge requests must be enabled in the CI/CD configuration file, or pipelines could be unresolvable or dropped"
+msgstr ""
+
msgid "ProjectSettings|Pipelines must succeed"
msgstr ""
@@ -17252,6 +17815,9 @@ msgstr ""
msgid "ProjectSettings|Show link to create/view merge request when pushing from the command line"
msgstr ""
+msgid "ProjectSettings|Skipped pipelines are considered successful"
+msgstr ""
+
msgid "ProjectSettings|Snippets"
msgstr ""
@@ -17267,6 +17833,9 @@ msgstr ""
msgid "ProjectSettings|These checks must pass before merge requests can be merged"
msgstr ""
+msgid "ProjectSettings|This introduces the risk of merging changes that will not pass the pipeline."
+msgstr ""
+
msgid "ProjectSettings|This setting is applied on the server level and can be overridden by an admin."
msgstr ""
@@ -17444,15 +18013,27 @@ msgstr ""
msgid "ProjectsNew|Blank project"
msgstr ""
+msgid "ProjectsNew|Connect your external repository to GitLab CI/CD."
+msgstr ""
+
msgid "ProjectsNew|Contact an administrator to enable options for importing your project."
msgstr ""
msgid "ProjectsNew|Create"
msgstr ""
+msgid "ProjectsNew|Create a blank project to house your files, plan your work, and collaborate on code, among other things."
+msgstr ""
+
+msgid "ProjectsNew|Create blank project"
+msgstr ""
+
msgid "ProjectsNew|Create from template"
msgstr ""
+msgid "ProjectsNew|Create new project"
+msgstr ""
+
msgid "ProjectsNew|Creating project & repository."
msgstr ""
@@ -17477,6 +18058,9 @@ msgstr ""
msgid "ProjectsNew|Project description %{tag_start}(optional)%{tag_end}"
msgstr ""
+msgid "ProjectsNew|Run CI/CD for external repository"
+msgstr ""
+
msgid "ProjectsNew|Template"
msgstr ""
@@ -17738,6 +18322,9 @@ msgstr ""
msgid "Protected Branch"
msgstr ""
+msgid "Protected Branches"
+msgstr ""
+
msgid "Protected Environment"
msgstr ""
@@ -17750,6 +18337,9 @@ msgstr ""
msgid "Protected Tag"
msgstr ""
+msgid "Protected Tags"
+msgstr ""
+
msgid "Protected branches"
msgstr ""
@@ -17849,6 +18439,9 @@ msgstr ""
msgid "Provider"
msgstr ""
+msgid "Proxy support for this API is not available currently"
+msgstr ""
+
msgid "Pseudonymizer data collection"
msgstr ""
@@ -17873,6 +18466,9 @@ msgstr ""
msgid "Publish to status page"
msgstr ""
+msgid "Published on status page"
+msgstr ""
+
msgid "Publishes this issue to the associated status page."
msgstr ""
@@ -18047,6 +18643,9 @@ msgstr ""
msgid "Recipe"
msgstr ""
+msgid "Reconfigure"
+msgstr ""
+
msgid "Recover hidden stage"
msgstr ""
@@ -18133,6 +18732,9 @@ msgstr ""
msgid "Registration|Your profile"
msgstr ""
+msgid "Regulate approvals by authors/committers, based on compliance frameworks. Can be changed only at the instance level."
+msgstr ""
+
msgid "Rejected (closed)"
msgstr ""
@@ -18183,15 +18785,24 @@ msgstr ""
msgid "ReleaseAssetLinkType|Image"
msgstr ""
+msgid "ReleaseAssetLinkType|Images"
+msgstr ""
+
msgid "ReleaseAssetLinkType|Other"
msgstr ""
msgid "ReleaseAssetLinkType|Package"
msgstr ""
+msgid "ReleaseAssetLinkType|Packages"
+msgstr ""
+
msgid "ReleaseAssetLinkType|Runbook"
msgstr ""
+msgid "ReleaseAssetLinkType|Runbooks"
+msgstr ""
+
msgid "Releases"
msgstr ""
@@ -18204,6 +18815,9 @@ msgstr ""
msgid "Releases documentation"
msgstr ""
+msgid "Releases|New Release"
+msgstr ""
+
msgid "Release|Something went wrong while getting the release details"
msgstr ""
@@ -18276,6 +18890,9 @@ msgstr ""
msgid "Remove fork relationship"
msgstr ""
+msgid "Remove from batch"
+msgstr ""
+
msgid "Remove from board"
msgstr ""
@@ -18465,6 +19082,12 @@ msgstr ""
msgid "Replication"
msgstr ""
+msgid "Replication enabled"
+msgstr ""
+
+msgid "Replication paused"
+msgstr ""
+
msgid "Reply by email"
msgstr ""
@@ -18492,15 +19115,15 @@ msgstr ""
msgid "Reported %{timeAgo} by %{reportedBy}"
msgstr ""
-msgid "Reported Resource Changes: %{addNum} to add, %{changeNum} to change, %{deleteNum} to delete"
-msgstr ""
-
msgid "Reporter"
msgstr ""
msgid "Reporting"
msgstr ""
+msgid "Reports"
+msgstr ""
+
msgid "Reports|%{combinedString} and %{resolvedString}"
msgstr ""
@@ -18539,6 +19162,9 @@ msgstr ""
msgid "Reports|Failure"
msgstr ""
+msgid "Reports|Identifier"
+msgstr ""
+
msgid "Reports|Metrics reports are loading"
msgstr ""
@@ -18551,6 +19177,9 @@ msgstr ""
msgid "Reports|Metrics reports failed loading results"
msgstr ""
+msgid "Reports|Scanner"
+msgstr ""
+
msgid "Reports|Severity"
msgstr ""
@@ -18599,15 +19228,33 @@ msgstr ""
msgid "Repository cleanup has started. You will receive an email once the cleanup operation is complete."
msgstr ""
+msgid "Repository files count over the limit"
+msgstr ""
+
+msgid "Repository has an invalid default branch name."
+msgstr ""
+
+msgid "Repository has more than one branch."
+msgstr ""
+
msgid "Repository has no locks."
msgstr ""
+msgid "Repository has tags."
+msgstr ""
+
msgid "Repository maintenance"
msgstr ""
msgid "Repository mirroring"
msgstr ""
+msgid "Repository must contain at least 1 file."
+msgstr ""
+
+msgid "Repository size is above the limit."
+msgstr ""
+
msgid "Repository static objects"
msgstr ""
@@ -18617,7 +19264,7 @@ msgstr ""
msgid "Repository sync capacity"
msgstr ""
-msgid "Repository: %{counter_repositories} / Wikis: %{counter_wikis} / Build Artifacts: %{counter_build_artifacts} / LFS: %{counter_lfs_objects}"
+msgid "Repository: %{counter_repositories} / Wikis: %{counter_wikis} / Build Artifacts: %{counter_build_artifacts} / LFS: %{counter_lfs_objects} / Snippets: %{counter_snippets}"
msgstr ""
msgid "RepositorySettingsAccessLevel|Select"
@@ -18629,6 +19276,9 @@ msgstr ""
msgid "Request Headers"
msgstr ""
+msgid "Request details"
+msgstr ""
+
msgid "Request parameter %{param} is missing."
msgstr ""
@@ -18735,6 +19385,9 @@ msgstr ""
msgid "Reset template"
msgstr ""
+msgid "Reset to project defaults"
+msgstr ""
+
msgid "Resetting the authorization key for this project will require updating the authorization key in every alert source it is enabled in."
msgstr ""
@@ -18831,9 +19484,6 @@ msgstr ""
msgid "Resume"
msgstr ""
-msgid "Resume replication"
-msgstr ""
-
msgid "Resync"
msgstr ""
@@ -19456,6 +20106,9 @@ msgstr ""
msgid "SecurityReports|False positive"
msgstr ""
+msgid "SecurityReports|Group Security Dashboard"
+msgstr ""
+
msgid "SecurityReports|Hide dismissed"
msgstr ""
@@ -19510,10 +20163,13 @@ msgstr ""
msgid "SecurityReports|Remove project from dashboard"
msgstr ""
-msgid "SecurityReports|Report type"
+msgid "SecurityReports|Return to dashboard"
msgstr ""
-msgid "SecurityReports|Return to dashboard"
+msgid "SecurityReports|Scan details"
+msgstr ""
+
+msgid "SecurityReports|Scanner type"
msgstr ""
msgid "SecurityReports|Security Dashboard"
@@ -19570,6 +20226,9 @@ msgstr ""
msgid "SecurityReports|There was an error while generating the report."
msgstr ""
+msgid "SecurityReports|Unable to add %{invalidProjectsMessage}"
+msgstr ""
+
msgid "SecurityReports|Unable to add %{invalidProjects}"
msgstr ""
@@ -19684,6 +20343,9 @@ msgstr ""
msgid "Select due date"
msgstr ""
+msgid "Select file"
+msgstr ""
+
msgid "Select group or project"
msgstr ""
@@ -19747,9 +20409,6 @@ msgstr ""
msgid "Select the branch you want to set as the default for this project. All merge requests and commits will automatically be made against this branch unless you specify a different one."
msgstr ""
-msgid "Select the configured storage available for new repositories to be placed on."
-msgstr ""
-
msgid "Select the custom project template source group."
msgstr ""
@@ -19942,12 +20601,6 @@ msgstr ""
msgid "Service Desk is enabled but not yet active"
msgstr ""
-msgid "Service Desk is off"
-msgstr ""
-
-msgid "Service Desk is on"
-msgstr ""
-
msgid "Service Templates"
msgstr ""
@@ -20035,6 +20688,15 @@ msgstr ""
msgid "Set the milestone to %{milestone_reference}."
msgstr ""
+msgid "Set the number of concurrent requests this secondary node will make to the primary node while backfilling."
+msgstr ""
+
+msgid "Set the synchronization and verification capacity for the secondary node."
+msgstr ""
+
+msgid "Set the timeout in seconds to send a secondary node status to the primary and IPs allowed for the secondary nodes."
+msgstr ""
+
msgid "Set time estimate"
msgstr ""
@@ -20074,6 +20736,9 @@ msgstr ""
msgid "Set weight to %{weight}."
msgstr ""
+msgid "Set what should be replicated by choosing specific projects or groups by the secondary node."
+msgstr ""
+
msgid "SetPasswordToCloneLink|set a password"
msgstr ""
@@ -20200,6 +20865,9 @@ msgstr ""
msgid "Show complete raw log"
msgstr ""
+msgid "Show details"
+msgstr ""
+
msgid "Show file browser"
msgstr ""
@@ -20209,10 +20877,13 @@ msgstr ""
msgid "Show latest version"
msgstr ""
+msgid "Show list"
+msgstr ""
+
msgid "Show me everything"
msgstr ""
-msgid "Show me how"
+msgid "Show me how to add a pipeline"
msgstr ""
msgid "Show me more advanced stuff"
@@ -20261,9 +20932,6 @@ msgstr ""
msgid "Showing all issues"
msgstr ""
-msgid "Showing all labels"
-msgstr ""
-
msgid "Showing last %{size} of log -"
msgstr ""
@@ -20375,9 +21043,6 @@ msgstr ""
msgid "Skip outdated deployment jobs"
msgstr ""
-msgid "Skip this for now"
-msgstr ""
-
msgid "Skipped"
msgstr ""
@@ -20522,6 +21187,9 @@ msgstr ""
msgid "Something went wrong while adding your award. Please try again."
msgstr ""
+msgid "Something went wrong while applying the batch of suggestions. Please try again."
+msgstr ""
+
msgid "Something went wrong while applying the suggestion. Please try again."
msgstr ""
@@ -20624,10 +21292,13 @@ msgstr ""
msgid "Something went wrong while updating a requirement."
msgstr ""
+msgid "Something went wrong while updating assignees"
+msgstr ""
+
msgid "Something went wrong while updating your list settings"
msgstr ""
-msgid "Something went wrong with your automatic subscription renewal"
+msgid "Something went wrong with your automatic subscription renewal."
msgstr ""
msgid "Something went wrong, unable to add %{project} to dashboard"
@@ -20645,6 +21316,9 @@ msgstr ""
msgid "Something went wrong, unable to search projects"
msgstr ""
+msgid "Something went wrong."
+msgstr ""
+
msgid "Something went wrong. Please try again."
msgstr ""
@@ -20828,6 +21502,9 @@ msgstr ""
msgid "Source code"
msgstr ""
+msgid "Source code (%{fileExtension})"
+msgstr ""
+
msgid "Source is not available"
msgstr ""
@@ -20912,6 +21589,9 @@ msgstr ""
msgid "Stack trace"
msgstr ""
+msgid "Stacktrace snippet"
+msgstr ""
+
msgid "Stage"
msgstr ""
@@ -21017,6 +21697,9 @@ msgstr ""
msgid "Start thread & reopen %{noteable_name}"
msgstr ""
+msgid "Start using Directed Acyclic Graphs (DAG)"
+msgstr ""
+
msgid "Start your Free Gold Trial"
msgstr ""
@@ -21479,6 +22162,21 @@ msgstr ""
msgid "SuggestedColors|Very pale orange"
msgstr ""
+msgid "Suggestion is not applicable as the suggestion was not found."
+msgstr ""
+
+msgid "Suggestions are not applicable as one or more suggestions were not found."
+msgstr ""
+
+msgid "Suggestions are not applicable as their lines cannot overlap."
+msgstr ""
+
+msgid "Suggestions must all be on the same branch."
+msgstr ""
+
+msgid "Suggestions that change line count can't be added to batches, yet."
+msgstr ""
+
msgid "Suggestions:"
msgstr ""
@@ -21521,9 +22219,6 @@ msgstr ""
msgid "Synchronization disabled"
msgstr ""
-msgid "Synchronization of container repositories is disabled."
-msgstr ""
-
msgid "System"
msgstr ""
@@ -21704,6 +22399,34 @@ msgstr ""
msgid "Terms of Service and Privacy Policy"
msgstr ""
+msgid "Terraform|%{number} Terraform report failed to generate"
+msgid_plural "Terraform|%{number} Terraform reports failed to generate"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Terraform|%{number} Terraform report was generated in your pipelines"
+msgid_plural "Terraform|%{number} Terraform reports were generated in your pipelines"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Terraform|A Terraform report failed to generate."
+msgstr ""
+
+msgid "Terraform|A Terraform report was generated in your pipelines."
+msgstr ""
+
+msgid "Terraform|Generating the report caused an error."
+msgstr ""
+
+msgid "Terraform|Reported Resource Changes: %{addNum} to add, %{changeNum} to change, %{deleteNum} to delete"
+msgstr ""
+
+msgid "Terraform|The Terraform report %{name} failed to generate."
+msgstr ""
+
+msgid "Terraform|The Terraform report %{name} was generated in your pipelines."
+msgstr ""
+
msgid "Test"
msgstr ""
@@ -21730,7 +22453,7 @@ msgstr ""
msgid "TestHooks|Ensure the project has CI pipelines."
msgstr ""
-msgid "TestHooks|Ensure the project has at least one commit."
+msgid "TestHooks|Ensure the project has deployments."
msgstr ""
msgid "TestHooks|Ensure the project has issues."
@@ -21831,7 +22554,7 @@ msgstr ""
msgid "The Prometheus server responded with \"bad request\". Please check your queries are correct and are supported in your Prometheus version. %{documentationLink}"
msgstr ""
-msgid "The URL defined on the primary node that secondary nodes should use to contact it. Defaults to URL"
+msgid "The URL defined on the primary node that secondary nodes should use to contact it."
msgstr ""
msgid "The URL to use for connecting to Elasticsearch. Use a comma-separated list to support clustering (e.g., \"http://localhost:9200, http://localhost:9201\")."
@@ -21840,6 +22563,9 @@ msgstr ""
msgid "The X509 Certificate to use when mutual TLS is required to communicate with the external authorization service. If left blank, the server certificate is still validated when accessing over HTTPS."
msgstr ""
+msgid "The above settings apply to all projects with the selected compliance framework(s)."
+msgstr ""
+
msgid "The amount of seconds after which a request to get a secondary node status will time out."
msgstr ""
@@ -22139,9 +22865,6 @@ msgstr ""
msgid "The total stage shows the time it takes between creating an issue and deploying the code to production. The data will be automatically added once you have completed the full idea to production cycle."
msgstr ""
-msgid "The unique identifier for the Geo node. Must match %{geoNodeName} if it is set in gitlab.rb, otherwise it must match %{externalUrl} with a trailing slash"
-msgstr ""
-
msgid "The update action will time out after %{number_of_minutes} minutes. For big repositories, use a clone/push combination."
msgstr ""
@@ -22166,9 +22889,6 @@ msgstr ""
msgid "The user you are trying to deactivate has been active in the past %{minimum_inactive_days} days and cannot be deactivated"
msgstr ""
-msgid "The user-facing URL of the Geo node"
-msgstr ""
-
msgid "The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6."
msgstr ""
@@ -22259,6 +22979,9 @@ msgstr ""
msgid "There was a problem fetching project branches."
msgstr ""
+msgid "There was a problem fetching project tags."
+msgstr ""
+
msgid "There was a problem fetching project users."
msgstr ""
@@ -22310,6 +23033,9 @@ msgstr ""
msgid "There was an error fetching the %{replicableType}"
msgstr ""
+msgid "There was an error fetching the Geo Settings"
+msgstr ""
+
msgid "There was an error fetching the Node's Groups"
msgstr ""
@@ -22430,6 +23156,12 @@ msgstr ""
msgid "This %{issuable} is locked. Only <strong>project members</strong> can comment."
msgstr ""
+msgid "This %{noteableTypeText} is %{confidentialLinkStart}confidential%{linkEnd} and %{lockedLinkStart}locked%{linkEnd}."
+msgstr ""
+
+msgid "This %{noteableTypeText} is locked."
+msgstr ""
+
msgid "This %{viewer} could not be displayed because %{reason}. You can %{options} instead."
msgstr ""
@@ -22448,6 +23180,9 @@ msgstr ""
msgid "This action can lead to data loss. To prevent accidental actions we ask you to confirm your intention."
msgstr ""
+msgid "This also resolves all related threads"
+msgstr ""
+
msgid "This also resolves the discussion"
msgstr ""
@@ -22538,6 +23273,9 @@ msgstr ""
msgid "This epic does not exist or you don't have sufficient permission."
msgstr ""
+msgid "This feature is currently in beta. We invite you to %{linkStart}give feedback%{linkEnd}."
+msgstr ""
+
msgid "This feature requires local storage to be enabled"
msgstr ""
@@ -22571,7 +23309,7 @@ msgstr ""
msgid "This is a Work in Progress"
msgstr ""
-msgid "This is a confidential issue."
+msgid "This is a confidential %{noteableTypeText}."
msgstr ""
msgid "This is a delayed job to run in %{remainingTime}"
@@ -22598,9 +23336,6 @@ msgstr ""
msgid "This is your current session"
msgstr ""
-msgid "This issue is %{confidentialLinkStart}confidential%{linkEnd} and %{lockedLinkStart}locked%{linkEnd}."
-msgstr ""
-
msgid "This issue is confidential"
msgstr ""
@@ -22610,9 +23345,6 @@ msgstr ""
msgid "This issue is in a child epic of the filtered epic"
msgstr ""
-msgid "This issue is locked."
-msgstr ""
-
msgid "This job depends on other jobs with expired/erased artifacts: %{invalid_dependencies}"
msgstr ""
@@ -22712,6 +23444,9 @@ msgstr ""
msgid "This license has already expired."
msgstr ""
+msgid "This link points to external content"
+msgstr ""
+
msgid "This may expose confidential information as the selected fork is in another namespace that can have other members."
msgstr ""
@@ -22739,6 +23474,9 @@ msgstr ""
msgid "This page will be removed in a future release."
msgstr ""
+msgid "This pipeline does not use the %{codeStart}needs%{codeEnd} keyword and can't be represented as a directed acyclic graph."
+msgstr ""
+
msgid "This pipeline makes use of a predefined CI/CD configuration enabled by %{strongStart}Auto DevOps.%{strongEnd}"
msgstr ""
@@ -22967,6 +23705,9 @@ msgstr ""
msgid "Time from last commit to merge"
msgstr ""
+msgid "Time in seconds"
+msgstr ""
+
msgid "Time in seconds GitLab will wait for a response from the external service. When the service does not respond in time, access will be denied."
msgstr ""
@@ -23388,9 +24129,15 @@ msgstr ""
msgid "Total artifacts size: %{total_size}"
msgstr ""
+msgid "Total cores (CPUs)"
+msgstr ""
+
msgid "Total issues"
msgstr ""
+msgid "Total memory (GB)"
+msgstr ""
+
msgid "Total test time for all commits/merges"
msgstr ""
@@ -23592,6 +24339,9 @@ msgstr ""
msgid "U2F only works with HTTPS-enabled websites. Contact your administrator for more details."
msgstr ""
+msgid "UI Development Kit"
+msgstr ""
+
msgid "URL"
msgstr ""
@@ -23664,9 +24414,6 @@ msgstr ""
msgid "Unable to load the merge request widget. Try reloading the page."
msgstr ""
-msgid "Unable to process group import file"
-msgstr ""
-
msgid "Unable to resolve"
msgstr ""
@@ -23682,6 +24429,9 @@ msgstr ""
msgid "Unable to sign you in to the group with SAML due to \"%{reason}\""
msgstr ""
+msgid "Unable to suggest a path. Please refresh and try again."
+msgstr ""
+
msgid "Unable to update label prioritization at this time"
msgstr ""
@@ -23697,6 +24447,9 @@ msgstr ""
msgid "Unarchiving the project will restore people's ability to make changes to it. The repository can be committed to, and issues, comments, and other entities can be created. %{strong_start}Once active, this project shows up in the search and on the dashboard.%{strong_end}"
msgstr ""
+msgid "Unassign from commenting user"
+msgstr ""
+
msgid "Unblock"
msgstr ""
@@ -23772,6 +24525,9 @@ msgstr ""
msgid "Unreachable"
msgstr ""
+msgid "Unrecognized cluster type"
+msgstr ""
+
msgid "Unresolve"
msgstr ""
@@ -23832,6 +24588,9 @@ msgstr ""
msgid "Unsubscribes from this %{quick_action_target}."
msgstr ""
+msgid "Unsupported todo type passed. Supported todo types are: %{todo_types}"
+msgstr ""
+
msgid "Until"
msgstr ""
@@ -23859,6 +24618,9 @@ msgstr ""
msgid "Update approval rule"
msgstr ""
+msgid "Update approvers"
+msgstr ""
+
msgid "Update failed"
msgstr ""
@@ -23868,6 +24630,9 @@ msgstr ""
msgid "Update it"
msgstr ""
+msgid "Update iteration"
+msgstr ""
+
msgid "Update now"
msgstr ""
@@ -23913,7 +24678,7 @@ msgstr ""
msgid "Updated at"
msgstr ""
-msgid "Updated to"
+msgid "Updated to %{linkStart}chart v%{linkEnd}"
msgstr ""
msgid "Updating"
@@ -23943,6 +24708,9 @@ msgstr ""
msgid "Upload CSV file"
msgstr ""
+msgid "Upload License"
+msgstr ""
+
msgid "Upload New File"
msgstr ""
@@ -24024,16 +24792,19 @@ msgstr ""
msgid "UsageQuota|Pipelines"
msgstr ""
+msgid "UsageQuota|Purchase more storage"
+msgstr ""
+
msgid "UsageQuota|Repositories"
msgstr ""
msgid "UsageQuota|Repository"
msgstr ""
-msgid "UsageQuota|Storage"
+msgid "UsageQuota|Snippets"
msgstr ""
-msgid "UsageQuota|Storage usage:"
+msgid "UsageQuota|Storage"
msgstr ""
msgid "UsageQuota|This namespace has no projects which use shared runners"
@@ -24066,6 +24837,12 @@ msgstr ""
msgid "UsageQuota|Wikis"
msgstr ""
+msgid "UsageQuota|You used: %{usage} %{limit}"
+msgstr ""
+
+msgid "UsageQuota|out of %{formattedLimit} of your namespace storage"
+msgstr ""
+
msgid "Use %{code_start}::%{code_end} to create a %{link_start}scoped label set%{link_end} (eg. %{code_start}priority::1%{code_end})"
msgstr ""
@@ -24132,6 +24909,9 @@ msgstr ""
msgid "User IDs"
msgstr ""
+msgid "User Lists can only be created and modified with %{linkStart}the API%{linkEnd}"
+msgstr ""
+
msgid "User OAuth applications"
msgstr ""
@@ -24156,6 +24936,9 @@ msgstr ""
msgid "User key was successfully removed."
msgstr ""
+msgid "User list %{name} will be removed. Are you sure?"
+msgstr ""
+
msgid "User map"
msgstr ""
@@ -24177,148 +24960,10 @@ msgstr ""
msgid "User was successfully updated."
msgstr ""
-msgid "UserOnboardingTour|%{activeTour}/%{totalTours}"
-msgstr ""
-
-msgid "UserOnboardingTour|%{completed}/%{total} steps completed"
-msgstr ""
-
-msgid "UserOnboardingTour|%{emphasisStart}Well done!%{emphasisEnd}%{lineBreak}%{lineBreak}That's it for our guided tour, congratulations for making it all the way to the end!%{lineBreak}%{lineBreak}We hope this gave you a good overview of GitLab and how it can help you. We'll now show you how to create your own project and invite your colleagues."
-msgstr ""
-
-msgid "UserOnboardingTour|Adding other members to a project is done through Project Settings. Click on %{emphasisStart}Settings%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Alright, that's it for Commits. Let's take a look at the %{emphasisStart}Branches%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Awesome! Now click on %{emphasisStart}Members%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Click on one of the %{emphasisStart}Compare%{emphasisEnd} buttons to compare a branch to master."
-msgstr ""
-
-msgid "UserOnboardingTour|Click on one of the %{emphasisStart}pipeline IDs%{emphasisEnd} to see the details of a pipeline."
-msgstr ""
-
-msgid "UserOnboardingTour|Click to open the latest commit to see its details."
-msgstr ""
-
-msgid "UserOnboardingTour|Close 'Learn GitLab'"
-msgstr ""
-
-msgid "UserOnboardingTour|Commits are shown in chronological order and can be filtered by the commit message or by the branch."
+msgid "UserList|Delete %{name}?"
msgstr ""
-msgid "UserOnboardingTour|Create a project"
-msgstr ""
-
-msgid "UserOnboardingTour|Exit 'Learn GitLab'"
-msgstr ""
-
-msgid "UserOnboardingTour|Got it"
-msgstr ""
-
-msgid "UserOnboardingTour|Great job! %{clapHands} We hope the tour was helpful and that you learned how to use GitLab.%{lineBreak}%{lineBreak}We'd love to get your feedback on this tour.%{lineBreak}%{lineBreak}%{emphasisStart}How helpful would you say this guided tour was?%{emphasisEnd}%{lineBreak}%{lineBreak}"
-msgstr ""
-
-msgid "UserOnboardingTour|Guided GitLab Tour"
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can compare the changes of this branch to another one. Changes are divided by files so that it's easier to see what was changed where."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can create a project from scratch, start with a template or import a repository from other platforms. Whatever you choose, we'll guide you through the process.%{lineBreak}%{lineBreak}Fill in your new project information and click on %{emphasisStart}Create Project%{emphasisEnd} to progress to the next step."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can see the breakdown of the pipelines: its stages and jobs in each of the stages and their status.%{lineBreak}%{lineBreak}Our CI/CD pipelines are quite complex, most of our users have fewer and simpler pipelines."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can see the current members of the project (just you at the moment) and invite new members.%{lineBreak}%{lineBreak}You can invite multiple members at once (existing GitLab users or invite by email) and you can also set their roles and permissions.%{lineBreak}%{lineBreak}Add a few members and click on %{emphasisStart}Add to project%{emphasisEnd} to complete this step."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can see what changes were made with this commit, on what branch and if there's a related merge request. The status of the pipeline will also show up if CI/CD is set up.%{lineBreak}%{lineBreak}You can also comment on the lines of code that were changed and start a discussion with your colleagues!"
-msgstr ""
-
-msgid "UserOnboardingTour|Here's an overview of branches in the %{emphasisStart}%{projectName}%{emphasisEnd} project. They're split into Active and Stale.%{lineBreak}%{lineBreak}From here, you can create a new merge request from a branch, or compare the branch to any other branch in the project. By default, it will compare it to the master branch."
-msgstr ""
-
-msgid "UserOnboardingTour|Invite colleagues"
-msgstr ""
-
-msgid "UserOnboardingTour|Issues are great for communicating and keeping track of progress in GitLab. These are all issues that are open in the %{emphasisStart}%{projectName}%{emphasisEnd}.%{lineBreak}%{lineBreak}You can help us improve GitLab by contributing work to issues that are labeled <span class=\"badge color-label accept-mr-label\">Accepting merge requests</span>.%{lineBreak}%{lineBreak}This list can be filtered by labels, milestones, assignees, authors... We'll show you how it looks when the list is filtered by a label."
-msgstr ""
-
-msgid "UserOnboardingTour|Learn GitLab"
-msgstr ""
-
-msgid "UserOnboardingTour|Let's take a closer look at a merge request. Click on the title of one."
-msgstr ""
-
-msgid "UserOnboardingTour|Let's take a closer look at all the commits. Click on %{emphasisStart}Commits%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Let's take a closer look at the repository of this project. Click on %{emphasisStart}Repository%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|No thanks"
-msgstr ""
-
-msgid "UserOnboardingTour|Ok, let's go"
-msgstr ""
-
-msgid "UserOnboardingTour|Ok, show me"
-msgstr ""
-
-msgid "UserOnboardingTour|Open one of the issues by clicking on its title."
-msgstr ""
-
-msgid "UserOnboardingTour|Restart this step"
-msgstr ""
-
-msgid "UserOnboardingTour|Skip this step"
-msgstr ""
-
-msgid "UserOnboardingTour|Sweet! Your project was created and is ready to be used.%{lineBreak}%{lineBreak}You can start adding files to the repository or clone it. One last thing we want to show you is how to invite your colleagues to your new project."
-msgstr ""
-
-msgid "UserOnboardingTour|Take a look. Here's a nifty menu for quickly creating issues, merge requests, snippets, projects and groups. Click on it and select \"New project\" from the \"GitLab\" section to get started."
-msgstr ""
-
-msgid "UserOnboardingTour|Thanks for taking the guided tour. Remember, if you want to go through it again, you can start %{emphasisStart}Learn GitLab%{emphasisEnd} in the help menu on the top right."
-msgstr ""
-
-msgid "UserOnboardingTour|Thanks for the feedback! %{thumbsUp}"
-msgstr ""
-
-msgid "UserOnboardingTour|That's it for issues. Let'st take a look at %{emphasisStart}Merge Requests%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|That's it for merge requests. Now for the final part of this guided tour - the %{emphasisStart}CI/CD%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|That's it for the Repository. Let's take a look at the %{emphasisStart}Issues%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|The structure of this page is very similar to issues. Status, description, discussion and the sidebar are all here.%{lineBreak}%{lineBreak}But take a look below the description and you'll notice that there's more information about the merge request, the CI/CD pipeline and the options for approving it.%{lineBreak}%{lineBreak}Alongside the discussion you can also see more information about commits in this merge request, the status of pipelines and review all changes that were made."
-msgstr ""
-
-msgid "UserOnboardingTour|There's a lot of information here but don't worry, we'll go through it.%{lineBreak}%{lineBreak}On the top you can see the status of the issue and when it was opened and by whom. Directly below it is the issue description and below that are other %{emphasisStart}related issues%{emphasisEnd} and %{emphasisStart}merge requests%{emphasisEnd} (if any). Then below that is the %{emphasisStart}discussion%{emphasisEnd}, that's where most of the communication happens.%{lineBreak}%{lineBreak}On the right, there's a sidebar where you can view/change the %{emphasisStart}assignee, milestone, due date, labels, weight%{emphasisEnd}, etc."
-msgstr ""
-
-msgid "UserOnboardingTour|These are all the CI/CD pipelines we have for our %{emphasisStart}%{projectName}%{emphasisEnd} project.%{lineBreak}%{lineBreak}Here you can see the status of each pipeline, for what commit it's running for, its stages and the status for them."
-msgstr ""
-
-msgid "UserOnboardingTour|These are all the issues that are available for community contributions. Let's take a closer look at one of them."
-msgstr ""
-
-msgid "UserOnboardingTour|This is an overview of all merge requests in this project. Similarly to the issues overview it can be filtered down by things like labels, milestones, authors, assignees, etc."
-msgstr ""
-
-msgid "UserOnboardingTour|This is the repository for the %{emphasisStart}%{projectName}%{emphasisEnd} project. All our code is stored here. Feel free to explore and take a closer look at folders and files.%{lineBreak}%{lineBreak}Above the file structure you can see the latest commit, who the author is and the status of the CI/CD pipeline.%{lineBreak}%{lineBreak}If you scroll down below the file structure, you'll find the Readme of this project. This is defined in the README.md file at the root of the repository."
-msgstr ""
-
-msgid "UserOnboardingTour|Welcome to the project overview of the %{emphasisStart}%{projectName}%{emphasisEnd} project. This is the project that we use to work on GitLab. At first, a project seems like a simple repository, but at GitLab, a project is so much more.%{lineBreak}%{lineBreak}You can create projects for hosting your codebase, use it as an issue tracker, collaborate on code, and continuously build, test, and deploy your app with built-in GitLab CI/CD."
+msgid "UserList|created %{timeago}"
msgstr ""
msgid "UserProfile|Activity"
@@ -24471,6 +25116,9 @@ msgstr ""
msgid "UsersSelect|Unassigned"
msgstr ""
+msgid "Using %{codeStart}needs%{codeEnd} allows jobs to run before their stage is reached, as soon as their individual dependencies are met, which speeds up your pipelines."
+msgstr ""
+
msgid "Using %{code_start}::%{code_end} denotes a %{link_start}scoped label set%{link_end}"
msgstr ""
@@ -24555,9 +25203,6 @@ msgstr ""
msgid "Versions"
msgstr ""
-msgid "Very helpful"
-msgstr ""
-
msgid "View Documentation"
msgstr ""
@@ -24706,6 +25351,9 @@ msgstr ""
msgid "VisibilityLevel|Unknown"
msgstr ""
+msgid "Visit settings page"
+msgstr ""
+
msgid "VisualReviewApp|%{stepStart}Step 1%{stepEnd}. Copy the following script:"
msgstr ""
@@ -24799,6 +25447,9 @@ msgstr ""
msgid "VulnerabilityManagement|Something went wrong while trying to delete the comment. Please try again later."
msgstr ""
+msgid "VulnerabilityManagement|Something went wrong while trying to refresh the vulnerability. Please try again later."
+msgstr ""
+
msgid "VulnerabilityManagement|Something went wrong while trying to retrieve the vulnerability history. Please try again later."
msgstr ""
@@ -24841,6 +25492,9 @@ msgstr ""
msgid "Vulnerability|Class"
msgstr ""
+msgid "Vulnerability|Crash Address"
+msgstr ""
+
msgid "Vulnerability|Description"
msgstr ""
@@ -24916,9 +25570,6 @@ msgstr ""
msgid "We couldn't reach the Prometheus server. Either the server no longer exists or the configuration details need updating."
msgstr ""
-msgid "We created a short guided tour that will help you learn the basics of GitLab and how it will help you be better at your job. It should only take a couple of minutes. You will be guided by two types of helpers, best recognized by their color."
-msgstr ""
-
msgid "We detected potential spam in the %{humanized_resource_name}. Please solve the reCAPTCHA to proceed."
msgstr ""
@@ -24952,6 +25603,9 @@ msgstr ""
msgid "We've found no vulnerabilities"
msgstr ""
+msgid "Web Application Firewall"
+msgstr ""
+
msgid "Web IDE"
msgstr ""
@@ -25012,9 +25666,6 @@ msgstr ""
msgid "Welcome to GitLab.com<br>@%{name}!"
msgstr ""
-msgid "Welcome to the Guided GitLab Tour"
-msgstr ""
-
msgid "Welcome to the guided GitLab tour"
msgstr ""
@@ -25062,9 +25713,6 @@ msgstr ""
msgid "While it's rare to have no vulnerabilities, it can happen. In any event, we ask that you please double check your settings to make sure you've set up your dashboard correctly."
msgstr ""
-msgid "White helpers give contextual information."
-msgstr ""
-
msgid "Who can be an approver?"
msgstr ""
@@ -25110,12 +25758,18 @@ msgstr ""
msgid "WikiEmptyIssueMessage|Suggest wiki improvement"
msgstr ""
+msgid "WikiEmptyIssueMessage|You must be a group member in order to add wiki pages. If you have suggestions for how to improve the wiki for this group, consider opening an issue in the %{issues_link}."
+msgstr ""
+
msgid "WikiEmptyIssueMessage|You must be a project member in order to add wiki pages. If you have suggestions for how to improve the wiki for this project, consider opening an issue in the %{issues_link}."
msgstr ""
msgid "WikiEmptyIssueMessage|issue tracker"
msgstr ""
+msgid "WikiEmpty|A wiki is where you can store all the details about your group. This can include why you've created it, its principles, how to use it, and so on."
+msgstr ""
+
msgid "WikiEmpty|A wiki is where you can store all the details about your project. This can include why you've created it, its principles, how to use it, and so on."
msgstr ""
@@ -25125,12 +25779,21 @@ msgstr ""
msgid "WikiEmpty|Suggest wiki improvement"
msgstr ""
+msgid "WikiEmpty|The wiki lets you write documentation for your group"
+msgstr ""
+
msgid "WikiEmpty|The wiki lets you write documentation for your project"
msgstr ""
+msgid "WikiEmpty|This group has no wiki pages"
+msgstr ""
+
msgid "WikiEmpty|This project has no wiki pages"
msgstr ""
+msgid "WikiEmpty|You must be a group member in order to add wiki pages."
+msgstr ""
+
msgid "WikiEmpty|You must be a project member in order to add wiki pages."
msgstr ""
@@ -25407,13 +26070,13 @@ msgstr ""
msgid "You can now export your security dashboard to a CSV report."
msgstr ""
-msgid "You can now submit a merge request to get this change into the original branch."
+msgid "You can now manage alert endpoint configuration in the Alerts section on the Operations settings page. Fields on this page have been deprecated."
msgstr ""
-msgid "You can now submit a merge request to get this change into the original project."
+msgid "You can now submit a merge request to get this change into the original branch."
msgstr ""
-msgid "You can only add files when you are on a branch"
+msgid "You can now submit a merge request to get this change into the original project."
msgstr ""
msgid "You can only edit files when you are on a branch"
@@ -25635,7 +26298,7 @@ msgstr ""
msgid "You need to upload a Google Takeout archive."
msgstr ""
-msgid "You reached %{usage_in_percent} of %{namespace_name}'s capacity (%{used_storage} of %{storage_limit})"
+msgid "You reached %{usage_in_percent} of %{namespace_name}'s storage capacity (%{used_storage} of %{storage_limit})"
msgstr ""
msgid "You tried to fork %{link_to_the_project} but it failed for the following reason:"
@@ -25938,13 +26601,13 @@ msgstr ""
msgid "Your subscription expired!"
msgstr ""
-msgid "Your subscription has been downgraded"
+msgid "Your subscription has been downgraded."
msgstr ""
-msgid "Your subscription will automatically renew in %{remaining_days}"
+msgid "Your subscription will automatically renew in %{remaining_days}."
msgstr ""
-msgid "Your subscription will expire in %{remaining_days}"
+msgid "Your subscription will expire in %{remaining_days}."
msgstr ""
msgid "Zoom meeting added"
@@ -26024,9 +26687,6 @@ msgstr ""
msgid "branch name"
msgstr ""
-msgid "build pipeline reference mismatch"
-msgstr ""
-
msgid "by"
msgstr ""
@@ -26045,6 +26705,9 @@ msgstr ""
msgid "cannot block others"
msgstr ""
+msgid "cannot contain HTML/XML tags, including any word between angle brackets (<,>)."
+msgstr ""
+
msgid "cannot include leading slash or directory traversal."
msgstr ""
@@ -26075,55 +26738,6 @@ msgstr ""
msgid "ciReport|%{remainingPackagesCount} more"
msgstr ""
-msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability"
-msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability for the source branch only"
-msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities for the source branch only"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerability"
-msgid_plural "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerabilities"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new vulnerability"
-msgid_plural "ciReport|%{reportType} %{status} detected %{newCount} new vulnerabilities"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities for the source branch only"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{fixedCount} fixed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} vulnerability for the source branch only"
-msgid_plural "ciReport|%{reportType} %{status} detected %{newCount} vulnerabilities for the source branch only"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "ciReport|%{reportType} %{status} detected no new vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected no vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected no vulnerabilities for the source branch only"
-msgstr ""
-
msgid "ciReport|%{reportType} is loading"
msgstr ""
@@ -26142,7 +26756,7 @@ msgstr ""
msgid "ciReport|All projects"
msgstr ""
-msgid "ciReport|All report types"
+msgid "ciReport|All scanner types"
msgstr ""
msgid "ciReport|All severities"
@@ -26166,6 +26780,9 @@ msgstr ""
msgid "ciReport|Container scanning detects known vulnerabilities in your docker images."
msgstr ""
+msgid "ciReport|Coverage Fuzzing"
+msgstr ""
+
msgid "ciReport|Create a merge request to implement this solution, or download and apply the patch manually."
msgstr ""
@@ -26324,6 +26941,9 @@ msgstr ""
msgid "customize"
msgstr ""
+msgid "data"
+msgstr ""
+
msgid "date must not be after 9999-12-31"
msgstr ""
@@ -26394,6 +27014,9 @@ msgstr ""
msgid "entries cannot contain HTML tags"
msgstr ""
+msgid "epic"
+msgstr ""
+
msgid "error"
msgstr ""
@@ -26415,9 +27038,6 @@ msgstr ""
msgid "expires on %{timebox_due_date}"
msgstr ""
-msgid "external_url"
-msgstr ""
-
msgid "failed"
msgstr ""
@@ -26453,9 +27073,6 @@ msgstr ""
msgid "from"
msgstr ""
-msgid "geo_node_name"
-msgstr ""
-
msgid "group"
msgstr ""
@@ -26477,9 +27094,6 @@ msgstr ""
msgid "https://your-bitbucket-server"
msgstr ""
-msgid "image"
-msgstr ""
-
msgid "image diff"
msgstr ""
@@ -26623,6 +27237,9 @@ msgstr ""
msgid "load it anyway"
msgstr ""
+msgid "loading"
+msgstr ""
+
msgid "locked by %{path_lock_user_name} %{created_at}"
msgstr ""
@@ -26751,9 +27368,6 @@ msgstr ""
msgid "mrWidget|Deployment statistics are not available currently"
msgstr ""
-msgid "mrWidget|Detect issues before deployment with a CI pipeline that continuously tests your code. We created a quick guide that will show you how to create one. Make your code more secure and more robust in just a minute."
-msgstr ""
-
msgid "mrWidget|Did not close"
msgstr ""
@@ -26916,6 +27530,9 @@ msgstr ""
msgid "mrWidget|To approve this merge request, please enter your password. This project requires all approvals to be authenticated."
msgstr ""
+msgid "mrWidget|Use %{linkStart}CI pipelines to test your code%{linkEnd}, simply add a GitLab CI configuration file to your project. It only takes a minute to make your code more secure and robust."
+msgstr ""
+
msgid "mrWidget|When this merge request is ready, remove the WIP: prefix from the title to allow it to be merged"
msgstr ""
@@ -26952,6 +27569,9 @@ msgstr ""
msgid "must be greater than start date"
msgstr ""
+msgid "must contain only valid frameworks"
+msgstr ""
+
msgid "my-awesome-group"
msgstr ""
@@ -26964,6 +27584,9 @@ msgstr ""
msgid "needs to be between 10 minutes and 1 month"
msgstr ""
+msgid "never"
+msgstr ""
+
msgid "never expires"
msgstr ""
@@ -26991,6 +27614,9 @@ msgstr ""
msgid "nounSeries|%{firstItem} and %{lastItem}"
msgstr ""
+msgid "nounSeries|%{item}"
+msgstr ""
+
msgid "nounSeries|%{item}, %{nextItem}"
msgstr ""
@@ -27127,6 +27753,9 @@ msgstr ""
msgid "revised"
msgstr ""
+msgid "satisfied"
+msgstr ""
+
msgid "score"
msgstr ""
@@ -27157,6 +27786,9 @@ msgstr ""
msgid "severity|Unknown"
msgstr ""
+msgid "should be an array of %{object_name} objects"
+msgstr ""
+
msgid "should be greater than or equal to %{access} inherited membership from group %{group_name}"
msgstr ""
@@ -27211,10 +27843,13 @@ msgstr ""
msgid "suggestPipeline|2/2: Commit your changes"
msgstr ""
-msgid "suggestPipeline|Commit the changes and your pipeline will automatically run for the first time."
+msgid "suggestPipeline|Choose %{boldStart}Code Quality%{boldEnd} to add a pipeline that tests the quality of your code."
msgstr ""
-msgid "suggestPipeline|We recommend the %{boldStart}Code Quality%{boldEnd} template, which will add a report widget to your Merge Requests. This way you’ll learn about code quality degradations much sooner. %{footerStart} Goodbye technical debt! %{footerEnd}"
+msgid "suggestPipeline|The template is ready! You can now commit it to create your first pipeline."
+msgstr ""
+
+msgid "suggestPipeline|We’re adding a GitLab CI configuration file to add a pipeline to the project. You could create it manually, but we recommend that you start with a GitLab template that works out of the box."
msgstr ""
msgid "syntax is correct"
@@ -27274,6 +27909,9 @@ msgstr ""
msgid "uses Kubernetes clusters to deploy your code!"
msgstr ""
+msgid "v%{version} published %{timeAgo}"
+msgstr ""
+
msgid "verify ownership"
msgstr ""
diff --git a/locale/sw_KE/gitlab.po b/locale/sw_KE/gitlab.po
index dc8caef887f..e8ed71576f2 100644
--- a/locale/sw_KE/gitlab.po
+++ b/locale/sw_KE/gitlab.po
@@ -2,7 +2,7 @@ msgid ""
msgstr ""
"Project-Id-Version: gitlab-ee\n"
"Report-Msgid-Bugs-To: \n"
-"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Last-Translator: \n"
"Language-Team: Swahili\n"
"Language: sw_KE\n"
"MIME-Version: 1.0\n"
@@ -10,9 +10,11 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Crowdin-Project: gitlab-ee\n"
+"X-Crowdin-Project-ID: 288872\n"
"X-Crowdin-Language: sw\n"
"X-Crowdin-File: /master/locale/gitlab.pot\n"
-"PO-Revision-Date: 2020-06-08 15:13\n"
+"X-Crowdin-File-ID: 6\n"
+"PO-Revision-Date: 2020-07-02 01:22\n"
msgid " %{start} to %{end}"
msgstr ""
@@ -197,6 +199,11 @@ msgid_plural "%d metrics"
msgstr[0] ""
msgstr[1] ""
+msgid "%d milestone"
+msgid_plural "%d milestones"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "%d minute"
msgid_plural "%d minutes"
msgstr[0] ""
@@ -242,11 +249,6 @@ msgid_plural "%d unresolved threads"
msgstr[0] ""
msgstr[1] ""
-msgid "%d url scanned"
-msgid_plural "%d urls scanned"
-msgstr[0] ""
-msgstr[1] ""
-
msgid "%d vulnerability"
msgid_plural "%d vulnerabilities"
msgstr[0] ""
@@ -324,9 +326,15 @@ msgstr[1] ""
msgid "%{count} related %{pluralized_subject}: %{links}"
msgstr ""
+msgid "%{dashboard_path} could not be found."
+msgstr ""
+
msgid "%{days} days until tags are automatically removed"
msgstr ""
+msgid "%{deployLinkStart}Use a template to deploy to ECS%{deployLinkEnd}, or use a docker image to %{commandsLinkStart}run AWS commands in GitLab CI/CD%{commandsLinkEnd}."
+msgstr ""
+
msgid "%{description}- Sentry event: %{errorUrl}- First seen: %{firstSeen}- Last seen: %{lastSeen} %{countLabel}: %{count}%{userCountLabel}: %{userCount}"
msgstr ""
@@ -369,12 +377,18 @@ msgstr ""
msgid "%{host} sign-in from new location"
msgstr ""
-msgid "%{icon}You are about to add %{usersTag} people to the discussion. Proceed with caution."
+msgid "%{icon}You are about to add %{usersTag} people to the discussion. They will all receive a notification."
+msgstr ""
+
+msgid "%{integrations_link_start}Integrations%{link_end} enable you to make third-party applications part of your GitLab workflow. If the available integrations don't meet your needs, consider using a %{webhooks_link_start}webhook%{link_end}."
msgstr ""
msgid "%{issuableType} will be removed! Are you sure?"
msgstr ""
+msgid "%{issuesCount} issues in this group"
+msgstr ""
+
msgid "%{issuesSize} issues"
msgstr ""
@@ -384,7 +398,7 @@ msgstr ""
msgid "%{labelStart}Class:%{labelEnd} %{class}"
msgstr ""
-msgid "%{labelStart}Confidence:%{labelEnd} %{confidence}"
+msgid "%{labelStart}Evidence:%{labelEnd} %{evidence}"
msgstr ""
msgid "%{labelStart}File:%{labelEnd} %{file}"
@@ -402,6 +416,9 @@ msgstr ""
msgid "%{labelStart}Report Type:%{labelEnd} %{reportType}"
msgstr ""
+msgid "%{labelStart}Scanner:%{labelEnd} %{scanner}"
+msgstr ""
+
msgid "%{labelStart}Severity:%{labelEnd} %{severity}"
msgstr ""
@@ -426,9 +443,6 @@ msgstr ""
msgid "%{link_start}Read more%{link_end} about role permissions"
msgstr ""
-msgid "%{link} can be used for binding events when something is happening within the project."
-msgstr ""
-
msgid "%{listToShow}, and %{awardsListLength} more."
msgstr ""
@@ -462,6 +476,9 @@ msgstr ""
msgid "%{name} found %{resultsString}"
msgstr ""
+msgid "%{name} is already being used for another emoji"
+msgstr ""
+
msgid "%{name} is scheduled for %{action}"
msgstr ""
@@ -509,6 +526,9 @@ msgstr ""
msgid "%{primary} (%{secondary})"
msgstr ""
+msgid "%{ref} cannot be added: %{error}"
+msgstr ""
+
msgid "%{releases} release"
msgid_plural "%{releases} releases"
msgstr[0] ""
@@ -517,13 +537,37 @@ msgstr[1] ""
msgid "%{remaining_approvals} left"
msgstr ""
-msgid "%{retryButtonStart}Try again%{retryButtonEnd} or %{newFileButtonStart}attach a new file%{newFileButtonEnd}"
+msgid "%{reportType} %{status} detected %{critical} critical and %{high} high severity vulnerabilities out of %{total}."
+msgstr ""
+
+msgid "%{reportType} %{status} detected %{critical} critical and %{high} high severity vulnerabilities."
+msgstr ""
+
+msgid "%{reportType} %{status} detected %{critical} critical severity vulnerabilities out of %{total}."
+msgstr ""
+
+msgid "%{reportType} %{status} detected %{critical} critical severity vulnerability."
+msgid_plural "%{reportType} %{status} detected %{critical} critical severity vulnerabilities."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{reportType} %{status} detected %{high} high severity vulnerabilities out of %{total}."
msgstr ""
-msgid "%{screenreaderOnlyStart}Keyboard shorcuts%{screenreaderOnlyEnd} Disabled"
+msgid "%{reportType} %{status} detected %{high} high severity vulnerability."
+msgid_plural "%{reportType} %{status} detected %{high} high severity vulnerabilities."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{reportType} %{status} detected %{other} vulnerability."
+msgid_plural "%{reportType} %{status} detected %{other} vulnerabilities."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{reportType} %{status} detected no new vulnerabilities."
msgstr ""
-msgid "%{screenreaderOnlyStart}Keyboard shorcuts%{screenreaderOnlyEnd} Enabled"
+msgid "%{retryButtonStart}Try again%{retryButtonEnd} or %{newFileButtonStart}attach a new file%{newFileButtonEnd}"
msgstr ""
msgid "%{service_title} %{message}."
@@ -620,6 +664,12 @@ msgstr ""
msgid "%{token}..."
msgstr ""
+msgid "%{totalCpu} (%{freeSpacePercentage}%{percentSymbol} free)"
+msgstr ""
+
+msgid "%{totalMemory} (%{freeSpacePercentage}%{percentSymbol} free)"
+msgstr ""
+
msgid "%{totalWeight} total weight"
msgstr ""
@@ -650,6 +700,12 @@ msgstr ""
msgid "%{verb} %{time_spent_value} spent time."
msgstr ""
+msgid "%{webhooks_link_start}%{webhook_type}%{link_end} enable you to send notifications to web applications in response to events in a group or project."
+msgstr ""
+
+msgid "%{webhooks_link_start}%{webhook_type}%{link_end} enable you to send notifications to web applications in response to events in a group or project. We recommend using an %{integrations_link_start}integration%{link_end} in preference to a webhook."
+msgstr ""
+
msgid "'%{level}' is not a valid visibility level"
msgstr ""
@@ -676,9 +732,6 @@ msgstr ""
msgid "(No changes)"
msgstr ""
-msgid "(Show all)"
-msgstr ""
-
msgid "(check progress)"
msgstr ""
@@ -745,6 +798,9 @@ msgstr ""
msgid "0 for unlimited"
msgstr ""
+msgid "0 for unlimited, only effective with remote storage enabled."
+msgstr ""
+
msgid "1 %{type} addition"
msgid_plural "%{count} %{type} additions"
msgstr[0] ""
@@ -892,6 +948,12 @@ msgstr ""
msgid "<code>\"johnsmith@example.com\": \"johnsmith@example.com\"</code> will add \"By <a href=\"#\">johnsmith@example.com</a>\" to all issues and comments originally created by johnsmith@example.com. By default, the email address or username is masked to ensure the user's privacy. Use this option if you want to show the full email address."
msgstr ""
+msgid "<code>Masked</code> to prevent the values from being displayed in job logs (must match certain regexp requirements)."
+msgstr ""
+
+msgid "<code>Protected</code> to expose them to protected branches or tags only."
+msgstr ""
+
msgid "<namespace / project>"
msgstr ""
@@ -919,9 +981,6 @@ msgstr ""
msgid "A .NET Core console application template, customizable for any .NET Core project"
msgstr ""
-msgid "A DAG must have two dependent jobs to be visualized on this tab."
-msgstr ""
-
msgid "A GitBook site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
msgstr ""
@@ -952,6 +1011,12 @@ msgstr ""
msgid "A deleted user"
msgstr ""
+msgid "A file has been changed."
+msgstr ""
+
+msgid "A file was not found."
+msgstr ""
+
msgid "A file with '%{file_name}' already exists in %{branch} branch"
msgstr ""
@@ -1018,7 +1083,7 @@ msgstr ""
msgid "A subscription will trigger a new pipeline on the default branch of this project when a pipeline successfully completes for a new tag on the %{default_branch_docs} of the subscribed project."
msgstr ""
-msgid "A terraform report was generated in your pipelines."
+msgid "A suggestion is not applicable."
msgstr ""
msgid "A user with write access to the source branch selected this option"
@@ -1189,6 +1254,9 @@ msgstr ""
msgid "Action to take when receiving an alert."
msgstr ""
+msgid "Actions"
+msgstr ""
+
msgid "Activate"
msgstr ""
@@ -1245,6 +1313,9 @@ msgstr ""
msgid "Add LICENSE"
msgstr ""
+msgid "Add New Node"
+msgstr ""
+
msgid "Add README"
msgstr ""
@@ -1320,6 +1391,9 @@ msgstr ""
msgid "Add approval rule"
msgstr ""
+msgid "Add approvers"
+msgstr ""
+
msgid "Add bold text"
msgstr ""
@@ -1377,6 +1451,9 @@ msgstr ""
msgid "Add strikethrough text"
msgstr ""
+msgid "Add suggestion to batch"
+msgstr ""
+
msgid "Add system hook"
msgstr ""
@@ -1476,6 +1553,9 @@ msgstr ""
msgid "Admin Note"
msgstr ""
+msgid "Admin Notifications"
+msgstr ""
+
msgid "Admin Overview"
msgstr ""
@@ -1587,6 +1667,9 @@ msgstr ""
msgid "AdminSettings|Integrations configured here will automatically apply to all projects on this instance."
msgstr ""
+msgid "AdminSettings|Moved to integrations"
+msgstr ""
+
msgid "AdminSettings|No required pipeline"
msgstr ""
@@ -1817,12 +1900,18 @@ msgid_plural "Alerts"
msgstr[0] ""
msgstr[1] ""
+msgid "AlertManagement| assign yourself"
+msgstr ""
+
msgid "AlertManagement|Acknowledged"
msgstr ""
msgid "AlertManagement|Alert"
msgstr ""
+msgid "AlertManagement|Alert assignee(s): %{assignees}"
+msgstr ""
+
msgid "AlertManagement|Alert detail"
msgstr ""
@@ -1838,9 +1927,15 @@ msgstr ""
msgid "AlertManagement|All alerts"
msgstr ""
+msgid "AlertManagement|Assign To"
+msgstr ""
+
msgid "AlertManagement|Assign status"
msgstr ""
+msgid "AlertManagement|Assignee"
+msgstr ""
+
msgid "AlertManagement|Assignees"
msgstr ""
@@ -1859,9 +1954,6 @@ msgstr ""
msgid "AlertManagement|Edit"
msgstr ""
-msgid "AlertManagement|End time"
-msgstr ""
-
msgid "AlertManagement|Events"
msgstr ""
@@ -1880,10 +1972,13 @@ msgstr ""
msgid "AlertManagement|More information"
msgstr ""
+msgid "AlertManagement|No Matching Results"
+msgstr ""
+
msgid "AlertManagement|No alert data to display."
msgstr ""
-msgid "AlertManagement|No alerts available to display. If you think you're seeing this message in error, refresh the page."
+msgid "AlertManagement|No alerts available to display. See %{linkStart}enabling alert management%{linkEnd} for more information on adding alerts to the list."
msgstr ""
msgid "AlertManagement|No alerts to display."
@@ -1892,6 +1987,9 @@ msgstr ""
msgid "AlertManagement|None"
msgstr ""
+msgid "AlertManagement|None -"
+msgstr ""
+
msgid "AlertManagement|Open"
msgstr ""
@@ -1928,9 +2026,18 @@ msgstr ""
msgid "AlertManagement|There was an error displaying the alerts. Confirm your endpoint's configuration details to ensure alerts appear."
msgstr ""
+msgid "AlertManagement|There was an error while updating the assignee(s) list. Please try again."
+msgstr ""
+
+msgid "AlertManagement|There was an error while updating the assignee(s) of the alert. Please try again."
+msgstr ""
+
msgid "AlertManagement|There was an error while updating the status of the alert. Please try again."
msgstr ""
+msgid "AlertManagement|This assignee cannot be assigned to this alert."
+msgstr ""
+
msgid "AlertManagement|Tool"
msgstr ""
@@ -2078,6 +2185,9 @@ msgstr ""
msgid "Allow users to request access (if visibility is public or internal)"
msgstr ""
+msgid "Allowed Geo IP"
+msgstr ""
+
msgid "Allowed email domain restriction only permitted for top-level groups"
msgstr ""
@@ -2111,6 +2221,9 @@ msgstr ""
msgid "Amazon Web Services"
msgstr ""
+msgid "Amazon Web Services Logo"
+msgstr ""
+
msgid "Amazon authentication is not %{link_start}correctly configured%{link_end}. Ask your GitLab administrator if you want to use this service."
msgstr ""
@@ -2168,10 +2281,16 @@ msgstr ""
msgid "An error occurred when updating the issue weight"
msgstr ""
+msgid "An error occurred while acknowledging the notification. Refresh the page and try again."
+msgstr ""
+
+msgid "An error occurred while adding approvers"
+msgstr ""
+
msgid "An error occurred while adding formatted title for epic"
msgstr ""
-msgid "An error occurred while checking group path"
+msgid "An error occurred while checking group path. Please refresh and try again."
msgstr ""
msgid "An error occurred while committing your changes."
@@ -2315,7 +2434,7 @@ msgstr ""
msgid "An error occurred while loading milestones"
msgstr ""
-msgid "An error occurred while loading terraform report"
+msgid "An error occurred while loading project creation UI"
msgstr ""
msgid "An error occurred while loading the data. Please try again."
@@ -2369,6 +2488,9 @@ msgstr ""
msgid "An error occurred while rendering preview broadcast message"
msgstr ""
+msgid "An error occurred while rendering the editor"
+msgstr ""
+
msgid "An error occurred while reordering issues."
msgstr ""
@@ -2465,7 +2587,7 @@ msgstr ""
msgid "Analyze your dependencies for known vulnerabilities."
msgstr ""
-msgid "Analyze your source code and git history for secrets"
+msgid "Analyze your source code and git history for secrets."
msgstr ""
msgid "Analyze your source code for known vulnerabilities."
@@ -2579,12 +2701,18 @@ msgstr ""
msgid "Apply suggestion"
msgstr ""
+msgid "Apply suggestions"
+msgstr ""
+
msgid "Apply template"
msgstr ""
msgid "Apply this approval rule to any branch or a specific protected branch."
msgstr ""
+msgid "Applying"
+msgstr ""
+
msgid "Applying a template will replace the existing issue description. Any changes you have made will be lost."
msgstr ""
@@ -2597,12 +2725,18 @@ msgstr ""
msgid "Applying multiple commands"
msgstr ""
-msgid "Applying suggestion"
+msgid "Applying suggestion..."
+msgstr ""
+
+msgid "Applying suggestions..."
msgstr ""
msgid "Approval rules"
msgstr ""
+msgid "Approval rules reset to project defaults"
+msgstr ""
+
msgid "ApprovalRuleRemove|%d member"
msgid_plural "ApprovalRuleRemove|%d members"
msgstr[0] ""
@@ -2656,6 +2790,9 @@ msgstr ""
msgid "Approved"
msgstr ""
+msgid "Approved MRs"
+msgstr ""
+
msgid "Approved by: "
msgstr ""
@@ -2869,6 +3006,9 @@ msgstr ""
msgid "Assign"
msgstr ""
+msgid "Assign Iteration"
+msgstr ""
+
msgid "Assign custom color like #FF0000"
msgstr ""
@@ -2887,6 +3027,9 @@ msgstr ""
msgid "Assign to"
msgstr ""
+msgid "Assign to commenting user"
+msgstr ""
+
msgid "Assign yourself to these issues"
msgstr ""
@@ -2913,6 +3056,9 @@ msgid_plural "%d Assignees"
msgstr[0] ""
msgstr[1] ""
+msgid "Assignee has no permissions"
+msgstr ""
+
msgid "Assignee lists not available with your current license"
msgstr ""
@@ -2922,6 +3068,9 @@ msgstr ""
msgid "Assignee(s)"
msgstr ""
+msgid "Assignees"
+msgstr ""
+
msgid "Assigns %{assignee_users_sentence}."
msgstr ""
@@ -2957,6 +3106,9 @@ msgstr ""
msgid "Audit Events is a way to keep track of important events that happened in GitLab."
msgstr ""
+msgid "Audit Log"
+msgstr ""
+
msgid "AuditEvents|(removed)"
msgstr ""
@@ -2993,6 +3145,9 @@ msgstr ""
msgid "AuditLogs|IP Address"
msgstr ""
+msgid "AuditLogs|Member Events"
+msgstr ""
+
msgid "AuditLogs|No matching %{type} found."
msgstr ""
@@ -3275,9 +3430,6 @@ msgstr ""
msgid "Badges|e.g. %{exampleUrl}"
msgstr ""
-msgid "Badge|New"
-msgstr ""
-
msgid "Balsamiq file could not be loaded."
msgstr ""
@@ -3326,6 +3478,12 @@ msgstr ""
msgid "Below you will find all the groups that are public."
msgstr ""
+msgid "Beta"
+msgstr ""
+
+msgid "Bi-weekly code coverage"
+msgstr ""
+
msgid "Billing"
msgstr ""
@@ -3386,9 +3544,15 @@ msgstr ""
msgid "Bitbucket Server Import"
msgstr ""
+msgid "Bitbucket Server import"
+msgstr ""
+
msgid "Bitbucket import"
msgstr ""
+msgid "Blame"
+msgstr ""
+
msgid "Blocked"
msgstr ""
@@ -3401,9 +3565,6 @@ msgstr ""
msgid "Blog"
msgstr ""
-msgid "Blue helpers indicate an action to be taken."
-msgstr ""
-
msgid "Board name"
msgstr ""
@@ -3602,6 +3763,9 @@ msgstr ""
msgid "Broadcast Message was successfully updated."
msgstr ""
+msgid "Broadcast Messages"
+msgstr ""
+
msgid "Browse Directory"
msgstr ""
@@ -3656,6 +3820,9 @@ msgstr ""
msgid "By %{user_name}"
msgstr ""
+msgid "By URL"
+msgstr ""
+
msgid "By default GitLab sends emails in HTML and plain text formats so mail clients can choose what format to use. Disable this option if you only want to send emails in plain text format."
msgstr ""
@@ -3761,6 +3928,9 @@ msgstr ""
msgid "Can override approvers and approvals required per merge request"
msgstr ""
+msgid "Can't apply as this line has changed or the suggestion already matches its content."
+msgstr ""
+
msgid "Can't create snippet: %{err}"
msgstr ""
@@ -3947,9 +4117,6 @@ msgstr ""
msgid "Changes are still tracked. Useful for cluster/index migrations."
msgstr ""
-msgid "Changes are unknown"
-msgstr ""
-
msgid "Changes suppressed. Click to show."
msgstr ""
@@ -4256,15 +4423,9 @@ msgstr ""
msgid "Choose what content you want to see on a group’s overview page"
msgstr ""
-msgid "Choose which groups you wish to synchronize to this secondary node"
-msgstr ""
-
msgid "Choose which repositories you want to connect and run CI/CD pipelines."
msgstr ""
-msgid "Choose which shards you wish to synchronize to this secondary node"
-msgstr ""
-
msgid "Choose your framework"
msgstr ""
@@ -4562,9 +4723,15 @@ msgstr ""
msgid "Cluster does not exist"
msgstr ""
+msgid "Cluster is required for Stages::ClusterEndpointInserter"
+msgstr ""
+
msgid "Cluster level"
msgstr ""
+msgid "Cluster type must be specificed for Stages::ClusterEndpointInserter"
+msgstr ""
+
msgid "ClusterIntegration| %{custom_domain_start}More information%{custom_domain_end}."
msgstr ""
@@ -4676,7 +4843,7 @@ msgstr ""
msgid "ClusterIntegration|Cert-Manager"
msgstr ""
-msgid "ClusterIntegration|Cert-Manager is a native Kubernetes certificate management controller that helps with issuing certificates. Installing Cert-Manager on your cluster will issue a certificate by %{letsEncrypt} and ensure that certificates are valid and up-to-date."
+msgid "ClusterIntegration|Cert-Manager is a native Kubernetes certificate management controller that helps with issuing certificates. Installing Cert-Manager on your cluster will issue a certificate by %{linkStart}Let's Encrypt%{linkEnd} and ensure that certificates are valid and up-to-date."
msgstr ""
msgid "ClusterIntegration|Certificate Authority bundle (PEM format)"
@@ -4703,9 +4870,6 @@ msgstr ""
msgid "ClusterIntegration|Clear the local cache of namespace and service accounts. This is necessary if your integration has become out of sync. The cache is repopulated during the next CI job that requires namespace and service accounts."
msgstr ""
-msgid "ClusterIntegration|Cloud Run"
-msgstr ""
-
msgid "ClusterIntegration|Cluster being created"
msgstr ""
@@ -4790,7 +4954,7 @@ msgstr ""
msgid "ClusterIntegration|Crossplane"
msgstr ""
-msgid "ClusterIntegration|Crossplane enables declarative provisioning of managed services from your cloud of choice using %{codeStart}kubectl%{codeEnd} or %{gitlabIntegrationLink}. Crossplane runs inside your Kubernetes cluster and supports secure connectivity and secrets management between app containers and the cloud services they depend on."
+msgid "ClusterIntegration|Crossplane enables declarative provisioning of managed services from your cloud of choice using %{codeStart}kubectl%{codeEnd} or %{linkStart}GitLab Integration%{linkEnd}. Crossplane runs inside your Kubernetes cluster and supports secure connectivity and secrets management between app containers and the cloud services they depend on."
msgstr ""
msgid "ClusterIntegration|Deletes all GitLab resources attached to this cluster during removal"
@@ -4874,9 +5038,6 @@ msgstr ""
msgid "ClusterIntegration|GitLab-managed cluster"
msgstr ""
-msgid "ClusterIntegration|Gitlab Integration"
-msgstr ""
-
msgid "ClusterIntegration|Global default"
msgstr ""
@@ -4922,7 +5083,7 @@ msgstr ""
msgid "ClusterIntegration|Ingress gives you a way to route requests to services based on the request host or path, centralizing a number of services into a single entrypoint."
msgstr ""
-msgid "ClusterIntegration|Installing Ingress may incur additional costs. Learn more about %{pricingLink}."
+msgid "ClusterIntegration|Installing Ingress may incur additional costs. Learn more about %{linkStart}pricing%{linkEnd}."
msgstr ""
msgid "ClusterIntegration|Instance cluster"
@@ -4937,7 +5098,7 @@ msgstr ""
msgid "ClusterIntegration|Issuer Email"
msgstr ""
-msgid "ClusterIntegration|Issuers represent a certificate authority. You must provide an email address for your Issuer. "
+msgid "ClusterIntegration|Issuers represent a certificate authority. You must provide an email address for your Issuer."
msgstr ""
msgid "ClusterIntegration|Jupyter Hostname"
@@ -5009,9 +5170,6 @@ msgstr ""
msgid "ClusterIntegration|Learn more about instance Kubernetes clusters"
msgstr ""
-msgid "ClusterIntegration|Let's Encrypt"
-msgstr ""
-
msgid "ClusterIntegration|Loading IAM Roles"
msgstr ""
@@ -5123,7 +5281,7 @@ msgstr ""
msgid "ClusterIntegration|Prometheus"
msgstr ""
-msgid "ClusterIntegration|Prometheus is an open-source monitoring system with %{gitlabIntegrationLink} to monitor deployed applications."
+msgid "ClusterIntegration|Prometheus is an open-source monitoring system with %{linkStart}GitLab Integration%{linkEnd} to monitor deployed applications."
msgstr ""
msgid "ClusterIntegration|Provider details"
@@ -5279,10 +5437,10 @@ msgstr ""
msgid "ClusterIntegration|Select zone to choose machine type"
msgstr ""
-msgid "ClusterIntegration|Send Cilium Logs"
+msgid "ClusterIntegration|Send Container Network Policies Logs"
msgstr ""
-msgid "ClusterIntegration|Send ModSecurity Logs"
+msgid "ClusterIntegration|Send Web Application Firewall Logs"
msgstr ""
msgid "ClusterIntegration|Service Token"
@@ -5450,15 +5608,12 @@ msgstr ""
msgid "ClusterIntegration|documentation"
msgstr ""
-msgid "ClusterIntegration|installed via %{installed_via}"
+msgid "ClusterIntegration|installed via %{linkStart}Cloud Run%{linkEnd}"
msgstr ""
msgid "ClusterIntegration|meets the requirements"
msgstr ""
-msgid "ClusterIntegration|pricing"
-msgstr ""
-
msgid "ClusterIntegration|sign up"
msgstr ""
@@ -5495,6 +5650,15 @@ msgstr ""
msgid "Code"
msgstr ""
+msgid "Code Coverage: %{coveragePercentage}%{percentSymbol}"
+msgstr ""
+
+msgid "Code Coverage| Empty code coverage data"
+msgstr ""
+
+msgid "Code Coverage|Couldn't fetch the code coverage data"
+msgstr ""
+
msgid "Code Owners"
msgstr ""
@@ -5549,7 +5713,7 @@ msgstr ""
msgid "Collapse approvers"
msgstr ""
-msgid "Collapse child epics"
+msgid "Collapse milestones"
msgstr ""
msgid "Collapse replies"
@@ -5567,6 +5731,9 @@ msgstr ""
msgid "Coming soon"
msgstr ""
+msgid "Comma-separated, e.g. '1.1.1.1, 2.2.2.0/24'"
+msgstr ""
+
msgid "Command"
msgstr ""
@@ -5737,6 +5904,12 @@ msgstr ""
msgid "Compliance framework (optional)"
msgstr ""
+msgid "Compliance frameworks"
+msgstr ""
+
+msgid "ComplianceDashboard|created by:"
+msgstr ""
+
msgid "ComplianceFramework|GDPR"
msgstr ""
@@ -5875,6 +6048,9 @@ msgstr ""
msgid "Connection timed out"
msgstr ""
+msgid "Connection timeout"
+msgstr ""
+
msgid "Contact sales to upgrade"
msgstr ""
@@ -5930,9 +6106,6 @@ msgstr ""
msgid "ContainerRegistry|CLI Commands"
msgstr ""
-msgid "ContainerRegistry|Compressed Size"
-msgstr ""
-
msgid "ContainerRegistry|Container Registry"
msgstr ""
@@ -5948,6 +6121,9 @@ msgstr ""
msgid "ContainerRegistry|Copy push command"
msgstr ""
+msgid "ContainerRegistry|Delete selected"
+msgstr ""
+
msgid "ContainerRegistry|Docker connection error"
msgstr ""
@@ -5981,16 +6157,16 @@ msgstr ""
msgid "ContainerRegistry|If you are not already logged in, you need to authenticate to the Container Registry by using your GitLab username and password. If you have %{twofaDocLinkStart}Two-Factor Authentication%{twofaDocLinkEnd} enabled, use a %{personalAccessTokensDocLinkStart}Personal Access Token%{personalAccessTokensDocLinkEnd} instead of a password."
msgstr ""
-msgid "ContainerRegistry|Image ID"
+msgid "ContainerRegistry|Image ID: %{imageId}"
msgstr ""
msgid "ContainerRegistry|Image Repositories"
msgstr ""
-msgid "ContainerRegistry|Keep and protect the images that matter most."
+msgid "ContainerRegistry|Image tags"
msgstr ""
-msgid "ContainerRegistry|Last Updated"
+msgid "ContainerRegistry|Keep and protect the images that matter most."
msgstr ""
msgid "ContainerRegistry|Login"
@@ -6005,6 +6181,9 @@ msgstr ""
msgid "ContainerRegistry|Please contact your administrator."
msgstr ""
+msgid "ContainerRegistry|Published %{timeInfo}"
+msgstr ""
+
msgid "ContainerRegistry|Push an image"
msgstr ""
@@ -6020,9 +6199,6 @@ msgstr ""
msgid "ContainerRegistry|Remove repository"
msgstr ""
-msgid "ContainerRegistry|Remove selected tags"
-msgstr ""
-
msgid "ContainerRegistry|Remove tag"
msgid_plural "ContainerRegistry|Remove tags"
msgstr[0] ""
@@ -6052,9 +6228,6 @@ msgstr ""
msgid "ContainerRegistry|Sorry, your filter produced no results."
msgstr ""
-msgid "ContainerRegistry|Tag"
-msgstr ""
-
msgid "ContainerRegistry|Tag expiration policy"
msgstr ""
@@ -6199,21 +6372,6 @@ msgstr ""
msgid "Control the display of third party offers."
msgstr ""
-msgid "Control the maximum concurrency of LFS/attachment backfill for this secondary node"
-msgstr ""
-
-msgid "Control the maximum concurrency of container repository operations for this Geo node"
-msgstr ""
-
-msgid "Control the maximum concurrency of repository backfill for this secondary node"
-msgstr ""
-
-msgid "Control the maximum concurrency of verification operations for this Geo node"
-msgstr ""
-
-msgid "Control the minimum interval in days that a repository should be reverified for this primary node"
-msgstr ""
-
msgid "Cookie domain"
msgstr ""
@@ -6355,6 +6513,9 @@ msgstr ""
msgid "Could not find design."
msgstr ""
+msgid "Could not find iteration"
+msgstr ""
+
msgid "Could not remove the trigger."
msgstr ""
@@ -6391,6 +6552,12 @@ msgstr ""
msgid "Coverage"
msgstr ""
+msgid "Coverage Fuzzing"
+msgstr ""
+
+msgid "Crash State"
+msgstr ""
+
msgid "Create"
msgstr ""
@@ -6439,6 +6606,9 @@ msgstr ""
msgid "Create a personal access token on your account to pull or push via %{protocol}."
msgstr ""
+msgid "Create a project pre-populated with the necessary files to get you started quickly."
+msgstr ""
+
msgid "Create an account using:"
msgstr ""
@@ -6607,6 +6777,9 @@ msgstr ""
msgid "Creates branch '%{branch_name}' and a merge request to resolve this issue."
msgstr ""
+msgid "Creating"
+msgstr ""
+
msgid "Creating epic"
msgstr ""
@@ -6658,7 +6831,10 @@ msgstr ""
msgid "Current vulnerabilities count"
msgstr ""
-msgid "CurrentUser|Buy CI minutes"
+msgid "CurrentUser|Buy Pipeline minutes"
+msgstr ""
+
+msgid "CurrentUser|One of your groups is running out"
msgstr ""
msgid "CurrentUser|Profile"
@@ -6673,6 +6849,9 @@ msgstr ""
msgid "CurrentUser|Upgrade"
msgstr ""
+msgid "Custom Attributes"
+msgstr ""
+
msgid "Custom CI configuration path"
msgstr ""
@@ -6936,12 +7115,18 @@ msgstr ""
msgid "DAG"
msgstr ""
+msgid "DAG visualization requires at least 3 dependent jobs."
+msgstr ""
+
msgid "DNS"
msgstr ""
msgid "Dashboard"
msgstr ""
+msgid "Dashboard uid not found"
+msgstr ""
+
msgid "DashboardProjects|All"
msgstr ""
@@ -6966,6 +7151,9 @@ msgstr ""
msgid "Data is still calculating..."
msgstr ""
+msgid "Datasource name not found"
+msgstr ""
+
msgid "Date"
msgstr ""
@@ -7146,6 +7334,9 @@ msgstr ""
msgid "Delete this attachment"
msgstr ""
+msgid "Delete user list"
+msgstr ""
+
msgid "Delete variable"
msgstr ""
@@ -7462,6 +7653,9 @@ msgstr ""
msgid "Deploying to"
msgstr ""
+msgid "Deploying to AWS is easy with GitLab"
+msgstr ""
+
msgid "Deployment Frequency"
msgstr ""
@@ -7824,6 +8018,9 @@ msgstr ""
msgid "Dismissed on pipeline %{pipelineLink} at %{projectLink}"
msgstr ""
+msgid "Display alerts from all your monitoring tools directly within GitLab."
+msgstr ""
+
msgid "Display name"
msgstr ""
@@ -7881,9 +8078,6 @@ msgstr ""
msgid "Don't show again"
msgstr ""
-msgid "Don't worry, you can access this tour by clicking on the help icon in the top right corner and choose <strong>Learn GitLab</strong>."
-msgstr ""
-
msgid "Done"
msgstr ""
@@ -8010,6 +8204,9 @@ msgstr ""
msgid "Edit Release"
msgstr ""
+msgid "Edit Slack integration"
+msgstr ""
+
msgid "Edit Snippet"
msgstr ""
@@ -8049,6 +8246,9 @@ msgstr ""
msgid "Edit issues"
msgstr ""
+msgid "Edit iteration"
+msgstr ""
+
msgid "Edit public deploy key"
msgstr ""
@@ -8097,6 +8297,9 @@ msgstr ""
msgid "Email"
msgstr ""
+msgid "Email Notification"
+msgstr ""
+
msgid "Email address"
msgstr ""
@@ -8109,6 +8312,9 @@ msgstr ""
msgid "Email not verified. Please verify your email in Salesforce."
msgstr ""
+msgid "Email notification for unknown sign-ins"
+msgstr ""
+
msgid "Email patch"
msgstr ""
@@ -8340,6 +8546,9 @@ msgstr ""
msgid "Enforce DNS rebinding attack protection"
msgstr ""
+msgid "Enforce personal access token expiration"
+msgstr ""
+
msgid "Ensure connectivity is available from the GitLab server to the Prometheus server"
msgstr ""
@@ -8409,6 +8618,9 @@ msgstr ""
msgid "Enter the name of your application, and we'll return a unique %{type}."
msgstr ""
+msgid "Enter weights for storages for new repositories."
+msgstr ""
+
msgid "Enter your password to approve"
msgstr ""
@@ -8418,15 +8630,27 @@ msgstr ""
msgid "Environment does not have deployments"
msgstr ""
+msgid "Environment is required for Stages::MetricEndpointInserter"
+msgstr ""
+
+msgid "Environment is required for Stages::VariableEndpointInserter"
+msgstr ""
+
msgid "Environment scope"
msgstr ""
+msgid "Environment variables are applied to all project environments in this instance via the Runner. You can use environment variables for passwords, secret keys, etc. Make variables available to the running application by prepending the variable key with <code>K8S_SECRET_</code>. You can set variables to be:"
+msgstr ""
+
msgid "Environment variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. Additionally, they can be masked so they are hidden in job logs, though they must match certain regexp requirements to do so. You can use environment variables for passwords, secret keys, or whatever you want."
msgstr ""
msgid "Environment variables are configured by your administrator to be %{link_start}protected%{link_end} by default"
msgstr ""
+msgid "Environment variables on this GitLab instance are configured to be %{link_start}protected%{link_end} by default"
+msgstr ""
+
msgid "Environment:"
msgstr ""
@@ -8958,6 +9182,9 @@ msgstr ""
msgid "EventFilterBy|Filter by comments"
msgstr ""
+msgid "EventFilterBy|Filter by designs"
+msgstr ""
+
msgid "EventFilterBy|Filter by epic events"
msgstr ""
@@ -9090,15 +9317,15 @@ msgstr ""
msgid "Expand approvers"
msgstr ""
-msgid "Expand child epics"
-msgstr ""
-
msgid "Expand down"
msgstr ""
msgid "Expand dropdown"
msgstr ""
+msgid "Expand milestones"
+msgstr ""
+
msgid "Expand sidebar"
msgstr ""
@@ -9114,6 +9341,9 @@ msgstr ""
msgid "Expiration date"
msgstr ""
+msgid "Expiration not enforced"
+msgstr ""
+
msgid "Expiration policy for the Container Registry is a perfect solution for keeping the Registry space down while still enjoying the full power of GitLab CI/CD."
msgstr ""
@@ -9234,6 +9464,9 @@ msgstr ""
msgid "Failed Jobs"
msgstr ""
+msgid "Failed on"
+msgstr ""
+
msgid "Failed to add a Zoom meeting"
msgstr ""
@@ -9294,13 +9527,19 @@ msgstr ""
msgid "Failed to install."
msgstr ""
+msgid "Failed to load assignees. Please try again."
+msgstr ""
+
+msgid "Failed to load authors. Please try again."
+msgstr ""
+
msgid "Failed to load emoji list."
msgstr ""
msgid "Failed to load error details from Sentry."
msgstr ""
-msgid "Failed to load errors from Sentry. Error message: %{errorMessage}"
+msgid "Failed to load errors from Sentry."
msgstr ""
msgid "Failed to load group activity metrics. Please try again."
@@ -9381,6 +9620,9 @@ msgstr ""
msgid "Failed to set due date because the date format is invalid."
msgstr ""
+msgid "Failed to set iteration on this issue. Please try again."
+msgstr ""
+
msgid "Failed to signing using smartcard authentication"
msgstr ""
@@ -9603,13 +9845,10 @@ msgstr ""
msgid "FeatureFlags|Target environments"
msgstr ""
-msgid "FeatureFlags|There are no active feature flags"
-msgstr ""
-
-msgid "FeatureFlags|There are no inactive feature flags"
+msgid "FeatureFlags|There was an error fetching the feature flags."
msgstr ""
-msgid "FeatureFlags|There was an error fetching the feature flags."
+msgid "FeatureFlags|There was an error retrieving user lists"
msgstr ""
msgid "FeatureFlags|Try again in a few moments or contact your support team."
@@ -9621,9 +9860,18 @@ msgstr ""
msgid "FeatureFlag|Delete strategy"
msgstr ""
+msgid "FeatureFlag|List"
+msgstr ""
+
msgid "FeatureFlag|Percentage"
msgstr ""
+msgid "FeatureFlag|Select a user list"
+msgstr ""
+
+msgid "FeatureFlag|There are no configured user lists"
+msgstr ""
+
msgid "FeatureFlag|Type"
msgstr ""
@@ -9780,6 +10028,12 @@ msgstr ""
msgid "Filter..."
msgstr ""
+msgid "Find File"
+msgstr ""
+
+msgid "Find bugs in your code with coverage-guided fuzzing"
+msgstr ""
+
msgid "Find by path"
msgstr ""
@@ -10044,6 +10298,9 @@ msgstr ""
msgid "Geo allows you to replicate your GitLab instance to other geographical locations."
msgstr ""
+msgid "Geo nodes are paused using a command run on the node"
+msgstr ""
+
msgid "GeoNodeStatusEvent|%{timeAgoStr} (%{pendingEvents} events)"
msgstr ""
@@ -10128,6 +10385,9 @@ msgstr ""
msgid "GeoNodes|Not checksummed"
msgstr ""
+msgid "GeoNodes|Package files"
+msgstr ""
+
msgid "GeoNodes|Pausing replication stops the sync process. Are you sure?"
msgstr ""
@@ -10146,6 +10406,9 @@ msgstr ""
msgid "GeoNodes|Replication slots"
msgstr ""
+msgid "GeoNodes|Replication status"
+msgstr ""
+
msgid "GeoNodes|Repositories"
msgstr ""
@@ -10356,6 +10619,9 @@ msgstr ""
msgid "Geo|Synchronization failed - %{error}"
msgstr ""
+msgid "Geo|Synchronization of %{itemTitle} is disabled."
+msgstr ""
+
msgid "Geo|The database is currently %{db_lag} behind the primary node."
msgstr ""
@@ -10668,6 +10934,9 @@ msgstr ""
msgid "Go to %{link_to_google_takeout}."
msgstr ""
+msgid "Go to <strong>Issues</strong> > <strong>Boards</strong> to access your personalized learning issue board."
+msgstr ""
+
msgid "Go to Pipelines"
msgstr ""
@@ -10767,9 +11036,6 @@ msgstr ""
msgid "Go to your snippets"
msgstr ""
-msgid "Golden Tanuki"
-msgstr ""
-
msgid "Google Cloud Platform"
msgstr ""
@@ -10791,6 +11057,9 @@ msgstr ""
msgid "Grafana URL"
msgstr ""
+msgid "Grafana response contains invalid json"
+msgstr ""
+
msgid "GrafanaIntegration|API Token"
msgstr ""
@@ -10839,9 +11108,6 @@ msgstr ""
msgid "Group %{group_name} was successfully created."
msgstr ""
-msgid "Group '%{group_name}' is being imported."
-msgstr ""
-
msgid "Group Audit Events"
msgstr ""
@@ -10878,9 +11144,6 @@ msgstr ""
msgid "Group by:"
msgstr ""
-msgid "Group could not be imported: %{errors}"
-msgstr ""
-
msgid "Group description"
msgstr ""
@@ -10908,9 +11171,15 @@ msgstr ""
msgid "Group has not been marked for deletion"
msgstr ""
+msgid "Group import could not be scheduled"
+msgstr ""
+
msgid "Group info:"
msgstr ""
+msgid "Group is required when cluster_type is :group"
+msgstr ""
+
msgid "Group maintainers can register group runners in the %{link}"
msgstr ""
@@ -10974,6 +11243,24 @@ msgstr ""
msgid "GroupActivyMetrics|Recent activity (last 90 days)"
msgstr ""
+msgid "GroupImport|Failed to import group."
+msgstr ""
+
+msgid "GroupImport|Group '%{group_name}' is being imported."
+msgstr ""
+
+msgid "GroupImport|Group could not be imported: %{errors}"
+msgstr ""
+
+msgid "GroupImport|Please wait while we import the group for you. Refresh at will."
+msgstr ""
+
+msgid "GroupImport|The group was successfully imported."
+msgstr ""
+
+msgid "GroupImport|Unable to process group import file"
+msgstr ""
+
msgid "GroupRoadmap|%{dateWord} – No end date"
msgstr ""
@@ -11241,6 +11528,9 @@ msgstr ""
msgid "Groups (%{groups})"
msgstr ""
+msgid "Groups and projects"
+msgstr ""
+
msgid "Groups can also be nested by creating %{subgroup_docs_link_start}subgroups%{subgroup_docs_link_end}."
msgstr ""
@@ -11286,6 +11576,33 @@ msgstr ""
msgid "GroupsEmptyState|You can manage your group member’s permissions and access to each project in the group."
msgstr ""
+msgid "GroupsNew|Contact an administrator to enable options for importing your group."
+msgstr ""
+
+msgid "GroupsNew|Create"
+msgstr ""
+
+msgid "GroupsNew|Create group"
+msgstr ""
+
+msgid "GroupsNew|GitLab group export"
+msgstr ""
+
+msgid "GroupsNew|Import"
+msgstr ""
+
+msgid "GroupsNew|Import group"
+msgstr ""
+
+msgid "GroupsNew|My Awesome Group"
+msgstr ""
+
+msgid "GroupsNew|No import options available"
+msgstr ""
+
+msgid "GroupsNew|To copy a GitLab group between installations, navigate to the group settings page for the original installation, generate an export file, and upload it here."
+msgstr ""
+
msgid "GroupsTree|Are you sure you want to leave the \"%{fullName}\" group?"
msgstr ""
@@ -11388,6 +11705,9 @@ msgstr ""
msgid "Helps reduce request volume for protected paths"
msgstr ""
+msgid "Here are all your projects in your group, including the one you just created. To start, let’s take a look at your personalized learning project which will help you learn about GitLab at your own pace."
+msgstr ""
+
msgid "Here you will find recent merge request activity"
msgstr ""
@@ -11402,6 +11722,9 @@ msgid_plural "Hide charts"
msgstr[0] ""
msgstr[1] ""
+msgid "Hide details"
+msgstr ""
+
msgid "Hide file browser"
msgstr ""
@@ -11411,6 +11734,9 @@ msgstr ""
msgid "Hide host keys manual input"
msgstr ""
+msgid "Hide list"
+msgstr ""
+
msgid "Hide marketing-related entries from help"
msgstr ""
@@ -11431,9 +11757,6 @@ msgstr[1] ""
msgid "Hide values"
msgstr ""
-msgid "Hiding all labels"
-msgstr ""
-
msgid "High or unknown vulnerabilities present"
msgstr ""
@@ -11518,6 +11841,9 @@ msgstr ""
msgid "ID:"
msgstr ""
+msgid "IDE"
+msgstr ""
+
msgid "IDE|Allow live previews of JavaScript projects in the Web IDE using CodeSandbox Live Preview."
msgstr ""
@@ -11608,10 +11934,10 @@ msgstr ""
msgid "If enabled"
msgstr ""
-msgid "If enabled, access to projects will be validated on an external service using their classification label."
+msgid "If enabled, GitLab will handle Object Storage replication using Geo. %{linkStart}More information%{linkEnd}"
msgstr ""
-msgid "If enabled, and if object storage is enabled, GitLab will handle Object Storage replication using Geo"
+msgid "If enabled, access to projects will be validated on an external service using their classification label."
msgstr ""
msgid "If there is no previous license or if the previous license has expired, some GitLab functionality will be blocked until a new, valid license is uploaded."
@@ -11973,6 +12299,9 @@ msgid_plural "Instances"
msgstr[0] ""
msgstr[1] ""
+msgid "Instance Configuration"
+msgstr ""
+
msgid "Instance Statistics visibility"
msgstr ""
@@ -11994,9 +12323,6 @@ msgstr ""
msgid "Integrations"
msgstr ""
-msgid "Integrations allow you to integrate GitLab with other applications"
-msgstr ""
-
msgid "Integrations|All details"
msgstr ""
@@ -12108,6 +12434,9 @@ msgstr ""
msgid "Invalid query"
msgstr ""
+msgid "Invalid repository bundle for snippet with id %{snippet_id}"
+msgstr ""
+
msgid "Invalid repository path"
msgstr ""
@@ -12126,6 +12455,9 @@ msgstr ""
msgid "Invalid two-factor code."
msgstr ""
+msgid "Invalid yaml"
+msgstr ""
+
msgid "Invitation"
msgstr ""
@@ -12345,15 +12677,24 @@ msgstr ""
msgid "It seems like the Dependency Scanning job ran successfully, but no dependencies have been detected in your project."
msgstr ""
+msgid "It seems that there is currently no available data for code coverage"
+msgstr ""
+
msgid "It's you"
msgstr ""
+msgid "Iteration"
+msgstr ""
+
msgid "Iteration changed to"
msgstr ""
msgid "Iteration removed"
msgstr ""
+msgid "Iteration updated"
+msgstr ""
+
msgid "Iterations"
msgstr ""
@@ -12384,12 +12725,18 @@ msgstr ""
msgid "January"
msgstr ""
+msgid "Jira Issues"
+msgstr ""
+
msgid "Jira import is already running."
msgstr ""
msgid "Jira integration not configured."
msgstr ""
+msgid "Jira project key is not configured"
+msgstr ""
+
msgid "Jira project: %{importProject}"
msgstr ""
@@ -12570,6 +12917,9 @@ msgstr ""
msgid "Keep divergent refs"
msgstr ""
+msgid "Kerberos access denied"
+msgstr ""
+
msgid "Key"
msgstr ""
@@ -12579,10 +12929,13 @@ msgstr ""
msgid "Key: %{key}"
msgstr ""
-msgid "Keyboard Shortcuts"
+msgid "Keyboard shortcuts"
msgstr ""
-msgid "Keyboard shortcuts"
+msgid "Keys"
+msgstr ""
+
+msgid "Ki"
msgstr ""
msgid "Kubernetes"
@@ -12624,6 +12977,9 @@ msgstr ""
msgid "LDAP"
msgstr ""
+msgid "LDAP Synchronization"
+msgstr ""
+
msgid "LDAP settings"
msgstr ""
@@ -12848,12 +13204,18 @@ msgstr ""
msgid "Learn more about custom project templates"
msgstr ""
+msgid "Learn more about deploying to AWS"
+msgstr ""
+
msgid "Learn more about deploying to a cluster"
msgstr ""
msgid "Learn more about group-level project templates"
msgstr ""
+msgid "Learn more about job dependencies"
+msgstr ""
+
msgid "Learn more about signing commits"
msgstr ""
@@ -12905,9 +13267,21 @@ msgstr ""
msgid "License History"
msgstr ""
+msgid "License ID:"
+msgstr ""
+
+msgid "License URL"
+msgstr ""
+
msgid "License-Check"
msgstr ""
+msgid "LicenseCompliance|%{docLinkStart}License Approvals%{docLinkEnd} are active"
+msgstr ""
+
+msgid "LicenseCompliance|%{docLinkStart}License Approvals%{docLinkEnd} are inactive"
+msgstr ""
+
msgid "LicenseCompliance|Add a license"
msgstr ""
@@ -12929,9 +13303,15 @@ msgstr ""
msgid "LicenseCompliance|Deny"
msgstr ""
+msgid "LicenseCompliance|Learn more about %{linkStart}License Approvals%{linkEnd}"
+msgstr ""
+
msgid "LicenseCompliance|License"
msgstr ""
+msgid "LicenseCompliance|License Approvals"
+msgstr ""
+
msgid "LicenseCompliance|License Compliance detected %d license and policy violation for the source branch only; approval required"
msgid_plural "LicenseCompliance|License Compliance detected %d licenses and policy violations for the source branch only; approval required"
msgstr[0] ""
@@ -13152,6 +13532,9 @@ msgstr ""
msgid "List your Bitbucket Server repositories"
msgstr ""
+msgid "Lists"
+msgstr ""
+
msgid "Live preview"
msgstr ""
@@ -13335,6 +13718,9 @@ msgstr ""
msgid "Manage project labels"
msgstr ""
+msgid "Manage storage usage"
+msgstr ""
+
msgid "Manage two-factor authentication"
msgstr ""
@@ -13350,6 +13736,9 @@ msgstr ""
msgid "Manifest file import"
msgstr ""
+msgid "Manifest import"
+msgstr ""
+
msgid "Manual job"
msgstr ""
@@ -13497,6 +13886,12 @@ msgstr ""
msgid "Maximum field length"
msgstr ""
+msgid "Maximum file size is 2MB. Please select a smaller file."
+msgstr ""
+
+msgid "Maximum import size (MB)"
+msgstr ""
+
msgid "Maximum job timeout"
msgstr ""
@@ -13536,6 +13931,9 @@ msgstr ""
msgid "Maximum size of Elasticsearch bulk indexing requests."
msgstr ""
+msgid "Maximum size of import files."
+msgstr ""
+
msgid "Maximum size of individual attachments in comments."
msgstr ""
@@ -13587,6 +13985,9 @@ msgstr ""
msgid "Merge (when the pipeline succeeds)"
msgstr ""
+msgid "Merge Conflicts"
+msgstr ""
+
msgid "Merge Request"
msgstr ""
@@ -13713,6 +14114,9 @@ msgstr ""
msgid "MergeRequests|Squash task canceled: another squash is already in progress."
msgstr ""
+msgid "MergeRequests|This project does not allow squashing commits when merge requests are accepted."
+msgstr ""
+
msgid "MergeRequests|Thread stays resolved"
msgstr ""
@@ -13866,6 +14270,12 @@ msgstr ""
msgid "MetricsSettings|Add a button to the metrics dashboard linking directly to your existing external dashboard."
msgstr ""
+msgid "MetricsSettings|Choose whether to display dashboard metrics in UTC or the user's local timezone."
+msgstr ""
+
+msgid "MetricsSettings|Dashboard timezone"
+msgstr ""
+
msgid "MetricsSettings|External dashboard URL"
msgstr ""
@@ -13875,6 +14285,12 @@ msgstr ""
msgid "MetricsSettings|Metrics Dashboard"
msgstr ""
+msgid "MetricsSettings|UTC (Coordinated Universal Time)"
+msgstr ""
+
+msgid "MetricsSettings|User's local timezone"
+msgstr ""
+
msgid "Metrics|Add metric"
msgstr ""
@@ -13890,6 +14306,9 @@ msgstr ""
msgid "Metrics|Create metric"
msgstr ""
+msgid "Metrics|Current"
+msgstr ""
+
msgid "Metrics|Delete metric"
msgstr ""
@@ -13940,6 +14359,9 @@ msgstr ""
msgid "Metrics|Max"
msgstr ""
+msgid "Metrics|Min"
+msgstr ""
+
msgid "Metrics|Must be a valid PromQL query."
msgstr ""
@@ -13955,6 +14377,9 @@ msgstr ""
msgid "Metrics|Refresh dashboard"
msgstr ""
+msgid "Metrics|Select a value"
+msgstr ""
+
msgid "Metrics|Star dashboard"
msgstr ""
@@ -13973,12 +14398,18 @@ msgstr ""
msgid "Metrics|There was an error getting annotations information."
msgstr ""
+msgid "Metrics|There was an error getting dashboard validation warnings information."
+msgstr ""
+
msgid "Metrics|There was an error getting deployment information."
msgstr ""
msgid "Metrics|There was an error getting environments information."
msgstr ""
+msgid "Metrics|There was an error getting options for variable \"%{name}\"."
+msgstr ""
+
msgid "Metrics|There was an error trying to validate your query"
msgstr ""
@@ -14021,6 +14452,9 @@ msgstr ""
msgid "Metrics|You're about to permanently delete this metric. This cannot be undone."
msgstr ""
+msgid "Metrics|Your dashboard schema is invalid. Edit the dashboard to correct the YAML schema."
+msgstr ""
+
msgid "Metrics|e.g. HTTP requests"
msgstr ""
@@ -14036,12 +14470,18 @@ msgstr ""
msgid "Metrics|e.g. req/sec"
msgstr ""
+msgid "Mi"
+msgstr ""
+
msgid "Microsoft Azure"
msgstr ""
msgid "Middleman project with Static Site Editor support"
msgstr ""
+msgid "Migrate your data from an external source like GitHub, Bitbucket, or another instance of GitLab."
+msgstr ""
+
msgid "Migrated %{success_count}/%{total_count} files."
msgstr ""
@@ -14176,6 +14616,9 @@ msgstr ""
msgid "Minimum capacity to be available before we schedule more mirrors preemptively."
msgstr ""
+msgid "Minimum interval in days"
+msgstr ""
+
msgid "Minimum length is %{minimum_password_length} characters"
msgstr ""
@@ -14362,6 +14805,12 @@ msgstr ""
msgid "Multiple uploaders found: %{uploader_types}"
msgstr ""
+msgid "Must match with the %{codeStart}external_url%{codeEnd} in %{codeStart}/etc/gitlab/gitlab.rb%{codeEnd}."
+msgstr ""
+
+msgid "Must match with the %{codeStart}geo_node_name%{codeEnd} in %{codeStart}/etc/gitlab/gitlab.rb%{codeEnd}. %{linkStart}More information%{linkEnd}"
+msgstr ""
+
msgid "My Awesome Group"
msgstr ""
@@ -14476,10 +14925,16 @@ msgstr ""
msgid "New Application"
msgstr ""
+msgid "New Branch"
+msgstr ""
+
+msgid "New Deploy Key"
+msgstr ""
+
msgid "New Environment"
msgstr ""
-msgid "New Geo Node"
+msgid "New File"
msgstr ""
msgid "New Group"
@@ -14496,15 +14951,15 @@ msgid_plural "New Issues"
msgstr[0] ""
msgstr[1] ""
-msgid "New Iteration"
-msgstr ""
-
msgid "New Jira import"
msgstr ""
msgid "New Label"
msgstr ""
+msgid "New Merge Request"
+msgstr ""
+
msgid "New Milestone"
msgstr ""
@@ -14523,6 +14978,9 @@ msgstr ""
msgid "New Snippet"
msgstr ""
+msgid "New User"
+msgstr ""
+
msgid "New branch"
msgstr ""
@@ -14565,6 +15023,9 @@ msgstr ""
msgid "New issue title"
msgstr ""
+msgid "New iteration"
+msgstr ""
+
msgid "New iteration created"
msgstr ""
@@ -14742,6 +15203,12 @@ msgstr ""
msgid "No grouping"
msgstr ""
+msgid "No iteration"
+msgstr ""
+
+msgid "No iterations to show"
+msgstr ""
+
msgid "No job log"
msgstr ""
@@ -14784,6 +15251,9 @@ msgstr ""
msgid "No other labels with such name or description"
msgstr ""
+msgid "No panels matching properties %{opts}"
+msgstr ""
+
msgid "No parent group"
msgstr ""
@@ -14817,6 +15287,9 @@ msgstr ""
msgid "No schedules"
msgstr ""
+msgid "No stack trace for this error"
+msgstr ""
+
msgid "No starrers matched your search"
msgstr ""
@@ -14829,7 +15302,7 @@ msgstr ""
msgid "No test coverage"
msgstr ""
-msgid "No thanks, don't show this again"
+msgid "No thanks"
msgstr ""
msgid "No vulnerabilities present"
@@ -14844,9 +15317,6 @@ msgstr ""
msgid "No, directly import the existing email addresses and usernames."
msgstr ""
-msgid "No, not interested right now"
-msgstr ""
-
msgid "No. of commits"
msgstr ""
@@ -14892,9 +15362,6 @@ msgstr ""
msgid "Not found."
msgstr ""
-msgid "Not helpful"
-msgstr ""
-
msgid "Not now"
msgstr ""
@@ -15051,6 +15518,9 @@ msgstr ""
msgid "Notifications on"
msgstr ""
+msgid "Notify users by email when sign-in location is not recognized"
+msgstr ""
+
msgid "Nov"
msgstr ""
@@ -15117,9 +15587,6 @@ msgstr ""
msgid "Oh no!"
msgstr ""
-msgid "Ok let's go"
-msgstr ""
-
msgid "Oldest first"
msgstr ""
@@ -15132,16 +15599,49 @@ msgstr ""
msgid "On track"
msgstr ""
+msgid "OnDemandScans|Attached branch"
+msgstr ""
+
+msgid "OnDemandScans|Attached branch is where the scan job runs."
+msgstr ""
+
+msgid "OnDemandScans|Could not run the scan. Please try again."
+msgstr ""
+
msgid "OnDemandScans|Create new DAST scan"
msgstr ""
+msgid "OnDemandScans|DAST will scan the target URL and any discovered sub URLs."
+msgstr ""
+
+msgid "OnDemandScans|New on-demand DAST scan"
+msgstr ""
+
msgid "OnDemandScans|On-demand Scans"
msgstr ""
+msgid "OnDemandScans|On-demand scans run outside the DevOps cycle and find vulnerabilities in your projects. %{learnMoreLinkStart}Learn more%{learnMoreLinkEnd}"
+msgstr ""
+
+msgid "OnDemandScans|Only a passive scan can be performed on demand."
+msgstr ""
+
+msgid "OnDemandScans|Passive DAST Scan"
+msgstr ""
+
+msgid "OnDemandScans|Please enter a valid URL format, ex: http://www.example.com/home"
+msgstr ""
+
+msgid "OnDemandScans|Run this scan"
+msgstr ""
+
+msgid "OnDemandScans|Scan mode"
+msgstr ""
+
msgid "OnDemandScans|Schedule or run scans immediately against target sites. Currently available on-demand scan type: DAST. %{helpLinkStart}More information%{helpLinkEnd}"
msgstr ""
-msgid "Onboarding"
+msgid "OnDemandScans|Target URL"
msgstr ""
msgid "Once imported, repositories can be mirrored over SSH. Read more %{link_start}here%{link_end}."
@@ -15167,6 +15667,9 @@ msgstr ""
msgid "One or more of you personal access tokens were revoked"
msgstr ""
+msgid "One or more of your %{provider} projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
+msgstr ""
+
msgid "One or more of your Bitbucket projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
msgstr ""
@@ -15668,6 +16171,9 @@ msgstr ""
msgid "Parent epic is not present."
msgstr ""
+msgid "Parsing error for param :embed_json. %{message}"
+msgstr ""
+
msgid "Part of merge request changes"
msgstr ""
@@ -15680,6 +16186,9 @@ msgstr ""
msgid "Passed"
msgstr ""
+msgid "Passed on"
+msgstr ""
+
msgid "Password"
msgstr ""
@@ -15764,6 +16273,9 @@ msgstr ""
msgid "Perform common operations on GitLab project"
msgstr ""
+msgid "Performance and resource management"
+msgstr ""
+
msgid "Performance optimization"
msgstr ""
@@ -15971,7 +16483,7 @@ msgstr ""
msgid "Pipelines|Something went wrong while cleaning runners cache."
msgstr ""
-msgid "Pipelines|There are currently no %{scope} pipelines."
+msgid "Pipelines|There are currently no finished pipelines."
msgstr ""
msgid "Pipelines|There are currently no pipelines."
@@ -16082,6 +16594,9 @@ msgstr ""
msgid "Pipeline|Stop pipeline #%{pipelineId}?"
msgstr ""
+msgid "Pipeline|Tag name"
+msgstr ""
+
msgid "Pipeline|Trigger author"
msgstr ""
@@ -16163,12 +16678,18 @@ msgstr ""
msgid "Please check your email (%{email}) to verify that you own this address and unlock the power of CI/CD. Didn't receive it? %{resend_link}. Wrong email address? %{update_link}."
msgstr ""
+msgid "Please choose a file"
+msgstr ""
+
msgid "Please choose a group URL with no special characters."
msgstr ""
msgid "Please complete your profile with email address"
msgstr ""
+msgid "Please convert %{linkStart}them to Git%{linkEnd}, and go through the %{linkToImportFlow} again."
+msgstr ""
+
msgid "Please convert them to %{link_to_git}, and go through the %{link_to_import_flow} again."
msgstr ""
@@ -16226,6 +16747,9 @@ msgstr ""
msgid "Please provide a valid email address."
msgstr ""
+msgid "Please provide attributes to update"
+msgstr ""
+
msgid "Please refer to <a href=\"%{docs_url}\">%{docs_url}</a>"
msgstr ""
@@ -16961,6 +17485,9 @@ msgstr ""
msgid "Project has too many %{label_for_message} to search"
msgstr ""
+msgid "Project is required when cluster_type is :project"
+msgstr ""
+
msgid "Project members"
msgstr ""
@@ -17078,7 +17605,40 @@ msgstr ""
msgid "ProjectService|Comment"
msgstr ""
-msgid "ProjectService|Comment will be posted on each event"
+msgid "ProjectService|Event will be triggered by a push to the repository"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a commit is created/updated"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a confidential issue is created/updated/closed"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a deployment finishes"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a merge request is created/updated/merged"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a new tag is pushed to the repository"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a new, unique alert is recorded"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a pipeline status changes"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a wiki page is created/updated"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when an issue is created/updated/closed"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when someone adds a comment"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when someone adds a comment on a confidential issue"
msgstr ""
msgid "ProjectService|Perform common operations on GitLab project: %{project_name}"
@@ -17129,6 +17689,9 @@ msgstr ""
msgid "ProjectSettings|Enable 'Delete source branch' option by default"
msgstr ""
+msgid "ProjectSettings|Enable merge trains and pipelines for merged results"
+msgstr ""
+
msgid "ProjectSettings|Every merge creates a merge commit"
msgstr ""
@@ -17159,7 +17722,7 @@ msgstr ""
msgid "ProjectSettings|Forks"
msgstr ""
-msgid "ProjectSettings|Git Large File Storage"
+msgid "ProjectSettings|Git Large File Storage (LFS)"
msgstr ""
msgid "ProjectSettings|Internal"
@@ -17195,9 +17758,6 @@ msgstr ""
msgid "ProjectSettings|Merge options"
msgstr ""
-msgid "ProjectSettings|Merge pipelines will try to validate the post-merge result prior to merging"
-msgstr ""
-
msgid "ProjectSettings|Merge requests"
msgstr ""
@@ -17225,6 +17785,9 @@ msgstr ""
msgid "ProjectSettings|Pipelines"
msgstr ""
+msgid "ProjectSettings|Pipelines for merge requests must be enabled in the CI/CD configuration file, or pipelines could be unresolvable or dropped"
+msgstr ""
+
msgid "ProjectSettings|Pipelines must succeed"
msgstr ""
@@ -17252,6 +17815,9 @@ msgstr ""
msgid "ProjectSettings|Show link to create/view merge request when pushing from the command line"
msgstr ""
+msgid "ProjectSettings|Skipped pipelines are considered successful"
+msgstr ""
+
msgid "ProjectSettings|Snippets"
msgstr ""
@@ -17267,6 +17833,9 @@ msgstr ""
msgid "ProjectSettings|These checks must pass before merge requests can be merged"
msgstr ""
+msgid "ProjectSettings|This introduces the risk of merging changes that will not pass the pipeline."
+msgstr ""
+
msgid "ProjectSettings|This setting is applied on the server level and can be overridden by an admin."
msgstr ""
@@ -17444,15 +18013,27 @@ msgstr ""
msgid "ProjectsNew|Blank project"
msgstr ""
+msgid "ProjectsNew|Connect your external repository to GitLab CI/CD."
+msgstr ""
+
msgid "ProjectsNew|Contact an administrator to enable options for importing your project."
msgstr ""
msgid "ProjectsNew|Create"
msgstr ""
+msgid "ProjectsNew|Create a blank project to house your files, plan your work, and collaborate on code, among other things."
+msgstr ""
+
+msgid "ProjectsNew|Create blank project"
+msgstr ""
+
msgid "ProjectsNew|Create from template"
msgstr ""
+msgid "ProjectsNew|Create new project"
+msgstr ""
+
msgid "ProjectsNew|Creating project & repository."
msgstr ""
@@ -17477,6 +18058,9 @@ msgstr ""
msgid "ProjectsNew|Project description %{tag_start}(optional)%{tag_end}"
msgstr ""
+msgid "ProjectsNew|Run CI/CD for external repository"
+msgstr ""
+
msgid "ProjectsNew|Template"
msgstr ""
@@ -17738,6 +18322,9 @@ msgstr ""
msgid "Protected Branch"
msgstr ""
+msgid "Protected Branches"
+msgstr ""
+
msgid "Protected Environment"
msgstr ""
@@ -17750,6 +18337,9 @@ msgstr ""
msgid "Protected Tag"
msgstr ""
+msgid "Protected Tags"
+msgstr ""
+
msgid "Protected branches"
msgstr ""
@@ -17849,6 +18439,9 @@ msgstr ""
msgid "Provider"
msgstr ""
+msgid "Proxy support for this API is not available currently"
+msgstr ""
+
msgid "Pseudonymizer data collection"
msgstr ""
@@ -17873,6 +18466,9 @@ msgstr ""
msgid "Publish to status page"
msgstr ""
+msgid "Published on status page"
+msgstr ""
+
msgid "Publishes this issue to the associated status page."
msgstr ""
@@ -18047,6 +18643,9 @@ msgstr ""
msgid "Recipe"
msgstr ""
+msgid "Reconfigure"
+msgstr ""
+
msgid "Recover hidden stage"
msgstr ""
@@ -18133,6 +18732,9 @@ msgstr ""
msgid "Registration|Your profile"
msgstr ""
+msgid "Regulate approvals by authors/committers, based on compliance frameworks. Can be changed only at the instance level."
+msgstr ""
+
msgid "Rejected (closed)"
msgstr ""
@@ -18183,15 +18785,24 @@ msgstr ""
msgid "ReleaseAssetLinkType|Image"
msgstr ""
+msgid "ReleaseAssetLinkType|Images"
+msgstr ""
+
msgid "ReleaseAssetLinkType|Other"
msgstr ""
msgid "ReleaseAssetLinkType|Package"
msgstr ""
+msgid "ReleaseAssetLinkType|Packages"
+msgstr ""
+
msgid "ReleaseAssetLinkType|Runbook"
msgstr ""
+msgid "ReleaseAssetLinkType|Runbooks"
+msgstr ""
+
msgid "Releases"
msgstr ""
@@ -18204,6 +18815,9 @@ msgstr ""
msgid "Releases documentation"
msgstr ""
+msgid "Releases|New Release"
+msgstr ""
+
msgid "Release|Something went wrong while getting the release details"
msgstr ""
@@ -18276,6 +18890,9 @@ msgstr ""
msgid "Remove fork relationship"
msgstr ""
+msgid "Remove from batch"
+msgstr ""
+
msgid "Remove from board"
msgstr ""
@@ -18465,6 +19082,12 @@ msgstr ""
msgid "Replication"
msgstr ""
+msgid "Replication enabled"
+msgstr ""
+
+msgid "Replication paused"
+msgstr ""
+
msgid "Reply by email"
msgstr ""
@@ -18492,15 +19115,15 @@ msgstr ""
msgid "Reported %{timeAgo} by %{reportedBy}"
msgstr ""
-msgid "Reported Resource Changes: %{addNum} to add, %{changeNum} to change, %{deleteNum} to delete"
-msgstr ""
-
msgid "Reporter"
msgstr ""
msgid "Reporting"
msgstr ""
+msgid "Reports"
+msgstr ""
+
msgid "Reports|%{combinedString} and %{resolvedString}"
msgstr ""
@@ -18539,6 +19162,9 @@ msgstr ""
msgid "Reports|Failure"
msgstr ""
+msgid "Reports|Identifier"
+msgstr ""
+
msgid "Reports|Metrics reports are loading"
msgstr ""
@@ -18551,6 +19177,9 @@ msgstr ""
msgid "Reports|Metrics reports failed loading results"
msgstr ""
+msgid "Reports|Scanner"
+msgstr ""
+
msgid "Reports|Severity"
msgstr ""
@@ -18599,15 +19228,33 @@ msgstr ""
msgid "Repository cleanup has started. You will receive an email once the cleanup operation is complete."
msgstr ""
+msgid "Repository files count over the limit"
+msgstr ""
+
+msgid "Repository has an invalid default branch name."
+msgstr ""
+
+msgid "Repository has more than one branch."
+msgstr ""
+
msgid "Repository has no locks."
msgstr ""
+msgid "Repository has tags."
+msgstr ""
+
msgid "Repository maintenance"
msgstr ""
msgid "Repository mirroring"
msgstr ""
+msgid "Repository must contain at least 1 file."
+msgstr ""
+
+msgid "Repository size is above the limit."
+msgstr ""
+
msgid "Repository static objects"
msgstr ""
@@ -18617,7 +19264,7 @@ msgstr ""
msgid "Repository sync capacity"
msgstr ""
-msgid "Repository: %{counter_repositories} / Wikis: %{counter_wikis} / Build Artifacts: %{counter_build_artifacts} / LFS: %{counter_lfs_objects}"
+msgid "Repository: %{counter_repositories} / Wikis: %{counter_wikis} / Build Artifacts: %{counter_build_artifacts} / LFS: %{counter_lfs_objects} / Snippets: %{counter_snippets}"
msgstr ""
msgid "RepositorySettingsAccessLevel|Select"
@@ -18629,6 +19276,9 @@ msgstr ""
msgid "Request Headers"
msgstr ""
+msgid "Request details"
+msgstr ""
+
msgid "Request parameter %{param} is missing."
msgstr ""
@@ -18735,6 +19385,9 @@ msgstr ""
msgid "Reset template"
msgstr ""
+msgid "Reset to project defaults"
+msgstr ""
+
msgid "Resetting the authorization key for this project will require updating the authorization key in every alert source it is enabled in."
msgstr ""
@@ -18831,9 +19484,6 @@ msgstr ""
msgid "Resume"
msgstr ""
-msgid "Resume replication"
-msgstr ""
-
msgid "Resync"
msgstr ""
@@ -19456,6 +20106,9 @@ msgstr ""
msgid "SecurityReports|False positive"
msgstr ""
+msgid "SecurityReports|Group Security Dashboard"
+msgstr ""
+
msgid "SecurityReports|Hide dismissed"
msgstr ""
@@ -19510,10 +20163,13 @@ msgstr ""
msgid "SecurityReports|Remove project from dashboard"
msgstr ""
-msgid "SecurityReports|Report type"
+msgid "SecurityReports|Return to dashboard"
msgstr ""
-msgid "SecurityReports|Return to dashboard"
+msgid "SecurityReports|Scan details"
+msgstr ""
+
+msgid "SecurityReports|Scanner type"
msgstr ""
msgid "SecurityReports|Security Dashboard"
@@ -19570,6 +20226,9 @@ msgstr ""
msgid "SecurityReports|There was an error while generating the report."
msgstr ""
+msgid "SecurityReports|Unable to add %{invalidProjectsMessage}"
+msgstr ""
+
msgid "SecurityReports|Unable to add %{invalidProjects}"
msgstr ""
@@ -19684,6 +20343,9 @@ msgstr ""
msgid "Select due date"
msgstr ""
+msgid "Select file"
+msgstr ""
+
msgid "Select group or project"
msgstr ""
@@ -19747,9 +20409,6 @@ msgstr ""
msgid "Select the branch you want to set as the default for this project. All merge requests and commits will automatically be made against this branch unless you specify a different one."
msgstr ""
-msgid "Select the configured storage available for new repositories to be placed on."
-msgstr ""
-
msgid "Select the custom project template source group."
msgstr ""
@@ -19942,12 +20601,6 @@ msgstr ""
msgid "Service Desk is enabled but not yet active"
msgstr ""
-msgid "Service Desk is off"
-msgstr ""
-
-msgid "Service Desk is on"
-msgstr ""
-
msgid "Service Templates"
msgstr ""
@@ -20035,6 +20688,15 @@ msgstr ""
msgid "Set the milestone to %{milestone_reference}."
msgstr ""
+msgid "Set the number of concurrent requests this secondary node will make to the primary node while backfilling."
+msgstr ""
+
+msgid "Set the synchronization and verification capacity for the secondary node."
+msgstr ""
+
+msgid "Set the timeout in seconds to send a secondary node status to the primary and IPs allowed for the secondary nodes."
+msgstr ""
+
msgid "Set time estimate"
msgstr ""
@@ -20074,6 +20736,9 @@ msgstr ""
msgid "Set weight to %{weight}."
msgstr ""
+msgid "Set what should be replicated by choosing specific projects or groups by the secondary node."
+msgstr ""
+
msgid "SetPasswordToCloneLink|set a password"
msgstr ""
@@ -20200,6 +20865,9 @@ msgstr ""
msgid "Show complete raw log"
msgstr ""
+msgid "Show details"
+msgstr ""
+
msgid "Show file browser"
msgstr ""
@@ -20209,10 +20877,13 @@ msgstr ""
msgid "Show latest version"
msgstr ""
+msgid "Show list"
+msgstr ""
+
msgid "Show me everything"
msgstr ""
-msgid "Show me how"
+msgid "Show me how to add a pipeline"
msgstr ""
msgid "Show me more advanced stuff"
@@ -20261,9 +20932,6 @@ msgstr ""
msgid "Showing all issues"
msgstr ""
-msgid "Showing all labels"
-msgstr ""
-
msgid "Showing last %{size} of log -"
msgstr ""
@@ -20375,9 +21043,6 @@ msgstr ""
msgid "Skip outdated deployment jobs"
msgstr ""
-msgid "Skip this for now"
-msgstr ""
-
msgid "Skipped"
msgstr ""
@@ -20522,6 +21187,9 @@ msgstr ""
msgid "Something went wrong while adding your award. Please try again."
msgstr ""
+msgid "Something went wrong while applying the batch of suggestions. Please try again."
+msgstr ""
+
msgid "Something went wrong while applying the suggestion. Please try again."
msgstr ""
@@ -20624,10 +21292,13 @@ msgstr ""
msgid "Something went wrong while updating a requirement."
msgstr ""
+msgid "Something went wrong while updating assignees"
+msgstr ""
+
msgid "Something went wrong while updating your list settings"
msgstr ""
-msgid "Something went wrong with your automatic subscription renewal"
+msgid "Something went wrong with your automatic subscription renewal."
msgstr ""
msgid "Something went wrong, unable to add %{project} to dashboard"
@@ -20645,6 +21316,9 @@ msgstr ""
msgid "Something went wrong, unable to search projects"
msgstr ""
+msgid "Something went wrong."
+msgstr ""
+
msgid "Something went wrong. Please try again."
msgstr ""
@@ -20828,6 +21502,9 @@ msgstr ""
msgid "Source code"
msgstr ""
+msgid "Source code (%{fileExtension})"
+msgstr ""
+
msgid "Source is not available"
msgstr ""
@@ -20912,6 +21589,9 @@ msgstr ""
msgid "Stack trace"
msgstr ""
+msgid "Stacktrace snippet"
+msgstr ""
+
msgid "Stage"
msgstr ""
@@ -21017,6 +21697,9 @@ msgstr ""
msgid "Start thread & reopen %{noteable_name}"
msgstr ""
+msgid "Start using Directed Acyclic Graphs (DAG)"
+msgstr ""
+
msgid "Start your Free Gold Trial"
msgstr ""
@@ -21479,6 +22162,21 @@ msgstr ""
msgid "SuggestedColors|Very pale orange"
msgstr ""
+msgid "Suggestion is not applicable as the suggestion was not found."
+msgstr ""
+
+msgid "Suggestions are not applicable as one or more suggestions were not found."
+msgstr ""
+
+msgid "Suggestions are not applicable as their lines cannot overlap."
+msgstr ""
+
+msgid "Suggestions must all be on the same branch."
+msgstr ""
+
+msgid "Suggestions that change line count can't be added to batches, yet."
+msgstr ""
+
msgid "Suggestions:"
msgstr ""
@@ -21521,9 +22219,6 @@ msgstr ""
msgid "Synchronization disabled"
msgstr ""
-msgid "Synchronization of container repositories is disabled."
-msgstr ""
-
msgid "System"
msgstr ""
@@ -21704,6 +22399,34 @@ msgstr ""
msgid "Terms of Service and Privacy Policy"
msgstr ""
+msgid "Terraform|%{number} Terraform report failed to generate"
+msgid_plural "Terraform|%{number} Terraform reports failed to generate"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Terraform|%{number} Terraform report was generated in your pipelines"
+msgid_plural "Terraform|%{number} Terraform reports were generated in your pipelines"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Terraform|A Terraform report failed to generate."
+msgstr ""
+
+msgid "Terraform|A Terraform report was generated in your pipelines."
+msgstr ""
+
+msgid "Terraform|Generating the report caused an error."
+msgstr ""
+
+msgid "Terraform|Reported Resource Changes: %{addNum} to add, %{changeNum} to change, %{deleteNum} to delete"
+msgstr ""
+
+msgid "Terraform|The Terraform report %{name} failed to generate."
+msgstr ""
+
+msgid "Terraform|The Terraform report %{name} was generated in your pipelines."
+msgstr ""
+
msgid "Test"
msgstr ""
@@ -21730,7 +22453,7 @@ msgstr ""
msgid "TestHooks|Ensure the project has CI pipelines."
msgstr ""
-msgid "TestHooks|Ensure the project has at least one commit."
+msgid "TestHooks|Ensure the project has deployments."
msgstr ""
msgid "TestHooks|Ensure the project has issues."
@@ -21831,7 +22554,7 @@ msgstr ""
msgid "The Prometheus server responded with \"bad request\". Please check your queries are correct and are supported in your Prometheus version. %{documentationLink}"
msgstr ""
-msgid "The URL defined on the primary node that secondary nodes should use to contact it. Defaults to URL"
+msgid "The URL defined on the primary node that secondary nodes should use to contact it."
msgstr ""
msgid "The URL to use for connecting to Elasticsearch. Use a comma-separated list to support clustering (e.g., \"http://localhost:9200, http://localhost:9201\")."
@@ -21840,6 +22563,9 @@ msgstr ""
msgid "The X509 Certificate to use when mutual TLS is required to communicate with the external authorization service. If left blank, the server certificate is still validated when accessing over HTTPS."
msgstr ""
+msgid "The above settings apply to all projects with the selected compliance framework(s)."
+msgstr ""
+
msgid "The amount of seconds after which a request to get a secondary node status will time out."
msgstr ""
@@ -22139,9 +22865,6 @@ msgstr ""
msgid "The total stage shows the time it takes between creating an issue and deploying the code to production. The data will be automatically added once you have completed the full idea to production cycle."
msgstr ""
-msgid "The unique identifier for the Geo node. Must match %{geoNodeName} if it is set in gitlab.rb, otherwise it must match %{externalUrl} with a trailing slash"
-msgstr ""
-
msgid "The update action will time out after %{number_of_minutes} minutes. For big repositories, use a clone/push combination."
msgstr ""
@@ -22166,9 +22889,6 @@ msgstr ""
msgid "The user you are trying to deactivate has been active in the past %{minimum_inactive_days} days and cannot be deactivated"
msgstr ""
-msgid "The user-facing URL of the Geo node"
-msgstr ""
-
msgid "The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6."
msgstr ""
@@ -22259,6 +22979,9 @@ msgstr ""
msgid "There was a problem fetching project branches."
msgstr ""
+msgid "There was a problem fetching project tags."
+msgstr ""
+
msgid "There was a problem fetching project users."
msgstr ""
@@ -22310,6 +23033,9 @@ msgstr ""
msgid "There was an error fetching the %{replicableType}"
msgstr ""
+msgid "There was an error fetching the Geo Settings"
+msgstr ""
+
msgid "There was an error fetching the Node's Groups"
msgstr ""
@@ -22430,6 +23156,12 @@ msgstr ""
msgid "This %{issuable} is locked. Only <strong>project members</strong> can comment."
msgstr ""
+msgid "This %{noteableTypeText} is %{confidentialLinkStart}confidential%{linkEnd} and %{lockedLinkStart}locked%{linkEnd}."
+msgstr ""
+
+msgid "This %{noteableTypeText} is locked."
+msgstr ""
+
msgid "This %{viewer} could not be displayed because %{reason}. You can %{options} instead."
msgstr ""
@@ -22448,6 +23180,9 @@ msgstr ""
msgid "This action can lead to data loss. To prevent accidental actions we ask you to confirm your intention."
msgstr ""
+msgid "This also resolves all related threads"
+msgstr ""
+
msgid "This also resolves the discussion"
msgstr ""
@@ -22538,6 +23273,9 @@ msgstr ""
msgid "This epic does not exist or you don't have sufficient permission."
msgstr ""
+msgid "This feature is currently in beta. We invite you to %{linkStart}give feedback%{linkEnd}."
+msgstr ""
+
msgid "This feature requires local storage to be enabled"
msgstr ""
@@ -22571,7 +23309,7 @@ msgstr ""
msgid "This is a Work in Progress"
msgstr ""
-msgid "This is a confidential issue."
+msgid "This is a confidential %{noteableTypeText}."
msgstr ""
msgid "This is a delayed job to run in %{remainingTime}"
@@ -22598,9 +23336,6 @@ msgstr ""
msgid "This is your current session"
msgstr ""
-msgid "This issue is %{confidentialLinkStart}confidential%{linkEnd} and %{lockedLinkStart}locked%{linkEnd}."
-msgstr ""
-
msgid "This issue is confidential"
msgstr ""
@@ -22610,9 +23345,6 @@ msgstr ""
msgid "This issue is in a child epic of the filtered epic"
msgstr ""
-msgid "This issue is locked."
-msgstr ""
-
msgid "This job depends on other jobs with expired/erased artifacts: %{invalid_dependencies}"
msgstr ""
@@ -22712,6 +23444,9 @@ msgstr ""
msgid "This license has already expired."
msgstr ""
+msgid "This link points to external content"
+msgstr ""
+
msgid "This may expose confidential information as the selected fork is in another namespace that can have other members."
msgstr ""
@@ -22739,6 +23474,9 @@ msgstr ""
msgid "This page will be removed in a future release."
msgstr ""
+msgid "This pipeline does not use the %{codeStart}needs%{codeEnd} keyword and can't be represented as a directed acyclic graph."
+msgstr ""
+
msgid "This pipeline makes use of a predefined CI/CD configuration enabled by %{strongStart}Auto DevOps.%{strongEnd}"
msgstr ""
@@ -22967,6 +23705,9 @@ msgstr ""
msgid "Time from last commit to merge"
msgstr ""
+msgid "Time in seconds"
+msgstr ""
+
msgid "Time in seconds GitLab will wait for a response from the external service. When the service does not respond in time, access will be denied."
msgstr ""
@@ -23388,9 +24129,15 @@ msgstr ""
msgid "Total artifacts size: %{total_size}"
msgstr ""
+msgid "Total cores (CPUs)"
+msgstr ""
+
msgid "Total issues"
msgstr ""
+msgid "Total memory (GB)"
+msgstr ""
+
msgid "Total test time for all commits/merges"
msgstr ""
@@ -23592,6 +24339,9 @@ msgstr ""
msgid "U2F only works with HTTPS-enabled websites. Contact your administrator for more details."
msgstr ""
+msgid "UI Development Kit"
+msgstr ""
+
msgid "URL"
msgstr ""
@@ -23664,9 +24414,6 @@ msgstr ""
msgid "Unable to load the merge request widget. Try reloading the page."
msgstr ""
-msgid "Unable to process group import file"
-msgstr ""
-
msgid "Unable to resolve"
msgstr ""
@@ -23682,6 +24429,9 @@ msgstr ""
msgid "Unable to sign you in to the group with SAML due to \"%{reason}\""
msgstr ""
+msgid "Unable to suggest a path. Please refresh and try again."
+msgstr ""
+
msgid "Unable to update label prioritization at this time"
msgstr ""
@@ -23697,6 +24447,9 @@ msgstr ""
msgid "Unarchiving the project will restore people's ability to make changes to it. The repository can be committed to, and issues, comments, and other entities can be created. %{strong_start}Once active, this project shows up in the search and on the dashboard.%{strong_end}"
msgstr ""
+msgid "Unassign from commenting user"
+msgstr ""
+
msgid "Unblock"
msgstr ""
@@ -23772,6 +24525,9 @@ msgstr ""
msgid "Unreachable"
msgstr ""
+msgid "Unrecognized cluster type"
+msgstr ""
+
msgid "Unresolve"
msgstr ""
@@ -23832,6 +24588,9 @@ msgstr ""
msgid "Unsubscribes from this %{quick_action_target}."
msgstr ""
+msgid "Unsupported todo type passed. Supported todo types are: %{todo_types}"
+msgstr ""
+
msgid "Until"
msgstr ""
@@ -23859,6 +24618,9 @@ msgstr ""
msgid "Update approval rule"
msgstr ""
+msgid "Update approvers"
+msgstr ""
+
msgid "Update failed"
msgstr ""
@@ -23868,6 +24630,9 @@ msgstr ""
msgid "Update it"
msgstr ""
+msgid "Update iteration"
+msgstr ""
+
msgid "Update now"
msgstr ""
@@ -23913,7 +24678,7 @@ msgstr ""
msgid "Updated at"
msgstr ""
-msgid "Updated to"
+msgid "Updated to %{linkStart}chart v%{linkEnd}"
msgstr ""
msgid "Updating"
@@ -23943,6 +24708,9 @@ msgstr ""
msgid "Upload CSV file"
msgstr ""
+msgid "Upload License"
+msgstr ""
+
msgid "Upload New File"
msgstr ""
@@ -24024,16 +24792,19 @@ msgstr ""
msgid "UsageQuota|Pipelines"
msgstr ""
+msgid "UsageQuota|Purchase more storage"
+msgstr ""
+
msgid "UsageQuota|Repositories"
msgstr ""
msgid "UsageQuota|Repository"
msgstr ""
-msgid "UsageQuota|Storage"
+msgid "UsageQuota|Snippets"
msgstr ""
-msgid "UsageQuota|Storage usage:"
+msgid "UsageQuota|Storage"
msgstr ""
msgid "UsageQuota|This namespace has no projects which use shared runners"
@@ -24066,6 +24837,12 @@ msgstr ""
msgid "UsageQuota|Wikis"
msgstr ""
+msgid "UsageQuota|You used: %{usage} %{limit}"
+msgstr ""
+
+msgid "UsageQuota|out of %{formattedLimit} of your namespace storage"
+msgstr ""
+
msgid "Use %{code_start}::%{code_end} to create a %{link_start}scoped label set%{link_end} (eg. %{code_start}priority::1%{code_end})"
msgstr ""
@@ -24132,6 +24909,9 @@ msgstr ""
msgid "User IDs"
msgstr ""
+msgid "User Lists can only be created and modified with %{linkStart}the API%{linkEnd}"
+msgstr ""
+
msgid "User OAuth applications"
msgstr ""
@@ -24156,6 +24936,9 @@ msgstr ""
msgid "User key was successfully removed."
msgstr ""
+msgid "User list %{name} will be removed. Are you sure?"
+msgstr ""
+
msgid "User map"
msgstr ""
@@ -24177,148 +24960,10 @@ msgstr ""
msgid "User was successfully updated."
msgstr ""
-msgid "UserOnboardingTour|%{activeTour}/%{totalTours}"
-msgstr ""
-
-msgid "UserOnboardingTour|%{completed}/%{total} steps completed"
-msgstr ""
-
-msgid "UserOnboardingTour|%{emphasisStart}Well done!%{emphasisEnd}%{lineBreak}%{lineBreak}That's it for our guided tour, congratulations for making it all the way to the end!%{lineBreak}%{lineBreak}We hope this gave you a good overview of GitLab and how it can help you. We'll now show you how to create your own project and invite your colleagues."
-msgstr ""
-
-msgid "UserOnboardingTour|Adding other members to a project is done through Project Settings. Click on %{emphasisStart}Settings%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Alright, that's it for Commits. Let's take a look at the %{emphasisStart}Branches%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Awesome! Now click on %{emphasisStart}Members%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Click on one of the %{emphasisStart}Compare%{emphasisEnd} buttons to compare a branch to master."
-msgstr ""
-
-msgid "UserOnboardingTour|Click on one of the %{emphasisStart}pipeline IDs%{emphasisEnd} to see the details of a pipeline."
-msgstr ""
-
-msgid "UserOnboardingTour|Click to open the latest commit to see its details."
-msgstr ""
-
-msgid "UserOnboardingTour|Close 'Learn GitLab'"
-msgstr ""
-
-msgid "UserOnboardingTour|Commits are shown in chronological order and can be filtered by the commit message or by the branch."
+msgid "UserList|Delete %{name}?"
msgstr ""
-msgid "UserOnboardingTour|Create a project"
-msgstr ""
-
-msgid "UserOnboardingTour|Exit 'Learn GitLab'"
-msgstr ""
-
-msgid "UserOnboardingTour|Got it"
-msgstr ""
-
-msgid "UserOnboardingTour|Great job! %{clapHands} We hope the tour was helpful and that you learned how to use GitLab.%{lineBreak}%{lineBreak}We'd love to get your feedback on this tour.%{lineBreak}%{lineBreak}%{emphasisStart}How helpful would you say this guided tour was?%{emphasisEnd}%{lineBreak}%{lineBreak}"
-msgstr ""
-
-msgid "UserOnboardingTour|Guided GitLab Tour"
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can compare the changes of this branch to another one. Changes are divided by files so that it's easier to see what was changed where."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can create a project from scratch, start with a template or import a repository from other platforms. Whatever you choose, we'll guide you through the process.%{lineBreak}%{lineBreak}Fill in your new project information and click on %{emphasisStart}Create Project%{emphasisEnd} to progress to the next step."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can see the breakdown of the pipelines: its stages and jobs in each of the stages and their status.%{lineBreak}%{lineBreak}Our CI/CD pipelines are quite complex, most of our users have fewer and simpler pipelines."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can see the current members of the project (just you at the moment) and invite new members.%{lineBreak}%{lineBreak}You can invite multiple members at once (existing GitLab users or invite by email) and you can also set their roles and permissions.%{lineBreak}%{lineBreak}Add a few members and click on %{emphasisStart}Add to project%{emphasisEnd} to complete this step."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can see what changes were made with this commit, on what branch and if there's a related merge request. The status of the pipeline will also show up if CI/CD is set up.%{lineBreak}%{lineBreak}You can also comment on the lines of code that were changed and start a discussion with your colleagues!"
-msgstr ""
-
-msgid "UserOnboardingTour|Here's an overview of branches in the %{emphasisStart}%{projectName}%{emphasisEnd} project. They're split into Active and Stale.%{lineBreak}%{lineBreak}From here, you can create a new merge request from a branch, or compare the branch to any other branch in the project. By default, it will compare it to the master branch."
-msgstr ""
-
-msgid "UserOnboardingTour|Invite colleagues"
-msgstr ""
-
-msgid "UserOnboardingTour|Issues are great for communicating and keeping track of progress in GitLab. These are all issues that are open in the %{emphasisStart}%{projectName}%{emphasisEnd}.%{lineBreak}%{lineBreak}You can help us improve GitLab by contributing work to issues that are labeled <span class=\"badge color-label accept-mr-label\">Accepting merge requests</span>.%{lineBreak}%{lineBreak}This list can be filtered by labels, milestones, assignees, authors... We'll show you how it looks when the list is filtered by a label."
-msgstr ""
-
-msgid "UserOnboardingTour|Learn GitLab"
-msgstr ""
-
-msgid "UserOnboardingTour|Let's take a closer look at a merge request. Click on the title of one."
-msgstr ""
-
-msgid "UserOnboardingTour|Let's take a closer look at all the commits. Click on %{emphasisStart}Commits%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Let's take a closer look at the repository of this project. Click on %{emphasisStart}Repository%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|No thanks"
-msgstr ""
-
-msgid "UserOnboardingTour|Ok, let's go"
-msgstr ""
-
-msgid "UserOnboardingTour|Ok, show me"
-msgstr ""
-
-msgid "UserOnboardingTour|Open one of the issues by clicking on its title."
-msgstr ""
-
-msgid "UserOnboardingTour|Restart this step"
-msgstr ""
-
-msgid "UserOnboardingTour|Skip this step"
-msgstr ""
-
-msgid "UserOnboardingTour|Sweet! Your project was created and is ready to be used.%{lineBreak}%{lineBreak}You can start adding files to the repository or clone it. One last thing we want to show you is how to invite your colleagues to your new project."
-msgstr ""
-
-msgid "UserOnboardingTour|Take a look. Here's a nifty menu for quickly creating issues, merge requests, snippets, projects and groups. Click on it and select \"New project\" from the \"GitLab\" section to get started."
-msgstr ""
-
-msgid "UserOnboardingTour|Thanks for taking the guided tour. Remember, if you want to go through it again, you can start %{emphasisStart}Learn GitLab%{emphasisEnd} in the help menu on the top right."
-msgstr ""
-
-msgid "UserOnboardingTour|Thanks for the feedback! %{thumbsUp}"
-msgstr ""
-
-msgid "UserOnboardingTour|That's it for issues. Let'st take a look at %{emphasisStart}Merge Requests%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|That's it for merge requests. Now for the final part of this guided tour - the %{emphasisStart}CI/CD%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|That's it for the Repository. Let's take a look at the %{emphasisStart}Issues%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|The structure of this page is very similar to issues. Status, description, discussion and the sidebar are all here.%{lineBreak}%{lineBreak}But take a look below the description and you'll notice that there's more information about the merge request, the CI/CD pipeline and the options for approving it.%{lineBreak}%{lineBreak}Alongside the discussion you can also see more information about commits in this merge request, the status of pipelines and review all changes that were made."
-msgstr ""
-
-msgid "UserOnboardingTour|There's a lot of information here but don't worry, we'll go through it.%{lineBreak}%{lineBreak}On the top you can see the status of the issue and when it was opened and by whom. Directly below it is the issue description and below that are other %{emphasisStart}related issues%{emphasisEnd} and %{emphasisStart}merge requests%{emphasisEnd} (if any). Then below that is the %{emphasisStart}discussion%{emphasisEnd}, that's where most of the communication happens.%{lineBreak}%{lineBreak}On the right, there's a sidebar where you can view/change the %{emphasisStart}assignee, milestone, due date, labels, weight%{emphasisEnd}, etc."
-msgstr ""
-
-msgid "UserOnboardingTour|These are all the CI/CD pipelines we have for our %{emphasisStart}%{projectName}%{emphasisEnd} project.%{lineBreak}%{lineBreak}Here you can see the status of each pipeline, for what commit it's running for, its stages and the status for them."
-msgstr ""
-
-msgid "UserOnboardingTour|These are all the issues that are available for community contributions. Let's take a closer look at one of them."
-msgstr ""
-
-msgid "UserOnboardingTour|This is an overview of all merge requests in this project. Similarly to the issues overview it can be filtered down by things like labels, milestones, authors, assignees, etc."
-msgstr ""
-
-msgid "UserOnboardingTour|This is the repository for the %{emphasisStart}%{projectName}%{emphasisEnd} project. All our code is stored here. Feel free to explore and take a closer look at folders and files.%{lineBreak}%{lineBreak}Above the file structure you can see the latest commit, who the author is and the status of the CI/CD pipeline.%{lineBreak}%{lineBreak}If you scroll down below the file structure, you'll find the Readme of this project. This is defined in the README.md file at the root of the repository."
-msgstr ""
-
-msgid "UserOnboardingTour|Welcome to the project overview of the %{emphasisStart}%{projectName}%{emphasisEnd} project. This is the project that we use to work on GitLab. At first, a project seems like a simple repository, but at GitLab, a project is so much more.%{lineBreak}%{lineBreak}You can create projects for hosting your codebase, use it as an issue tracker, collaborate on code, and continuously build, test, and deploy your app with built-in GitLab CI/CD."
+msgid "UserList|created %{timeago}"
msgstr ""
msgid "UserProfile|Activity"
@@ -24471,6 +25116,9 @@ msgstr ""
msgid "UsersSelect|Unassigned"
msgstr ""
+msgid "Using %{codeStart}needs%{codeEnd} allows jobs to run before their stage is reached, as soon as their individual dependencies are met, which speeds up your pipelines."
+msgstr ""
+
msgid "Using %{code_start}::%{code_end} denotes a %{link_start}scoped label set%{link_end}"
msgstr ""
@@ -24555,9 +25203,6 @@ msgstr ""
msgid "Versions"
msgstr ""
-msgid "Very helpful"
-msgstr ""
-
msgid "View Documentation"
msgstr ""
@@ -24706,6 +25351,9 @@ msgstr ""
msgid "VisibilityLevel|Unknown"
msgstr ""
+msgid "Visit settings page"
+msgstr ""
+
msgid "VisualReviewApp|%{stepStart}Step 1%{stepEnd}. Copy the following script:"
msgstr ""
@@ -24799,6 +25447,9 @@ msgstr ""
msgid "VulnerabilityManagement|Something went wrong while trying to delete the comment. Please try again later."
msgstr ""
+msgid "VulnerabilityManagement|Something went wrong while trying to refresh the vulnerability. Please try again later."
+msgstr ""
+
msgid "VulnerabilityManagement|Something went wrong while trying to retrieve the vulnerability history. Please try again later."
msgstr ""
@@ -24841,6 +25492,9 @@ msgstr ""
msgid "Vulnerability|Class"
msgstr ""
+msgid "Vulnerability|Crash Address"
+msgstr ""
+
msgid "Vulnerability|Description"
msgstr ""
@@ -24916,9 +25570,6 @@ msgstr ""
msgid "We couldn't reach the Prometheus server. Either the server no longer exists or the configuration details need updating."
msgstr ""
-msgid "We created a short guided tour that will help you learn the basics of GitLab and how it will help you be better at your job. It should only take a couple of minutes. You will be guided by two types of helpers, best recognized by their color."
-msgstr ""
-
msgid "We detected potential spam in the %{humanized_resource_name}. Please solve the reCAPTCHA to proceed."
msgstr ""
@@ -24952,6 +25603,9 @@ msgstr ""
msgid "We've found no vulnerabilities"
msgstr ""
+msgid "Web Application Firewall"
+msgstr ""
+
msgid "Web IDE"
msgstr ""
@@ -25012,9 +25666,6 @@ msgstr ""
msgid "Welcome to GitLab.com<br>@%{name}!"
msgstr ""
-msgid "Welcome to the Guided GitLab Tour"
-msgstr ""
-
msgid "Welcome to the guided GitLab tour"
msgstr ""
@@ -25062,9 +25713,6 @@ msgstr ""
msgid "While it's rare to have no vulnerabilities, it can happen. In any event, we ask that you please double check your settings to make sure you've set up your dashboard correctly."
msgstr ""
-msgid "White helpers give contextual information."
-msgstr ""
-
msgid "Who can be an approver?"
msgstr ""
@@ -25110,12 +25758,18 @@ msgstr ""
msgid "WikiEmptyIssueMessage|Suggest wiki improvement"
msgstr ""
+msgid "WikiEmptyIssueMessage|You must be a group member in order to add wiki pages. If you have suggestions for how to improve the wiki for this group, consider opening an issue in the %{issues_link}."
+msgstr ""
+
msgid "WikiEmptyIssueMessage|You must be a project member in order to add wiki pages. If you have suggestions for how to improve the wiki for this project, consider opening an issue in the %{issues_link}."
msgstr ""
msgid "WikiEmptyIssueMessage|issue tracker"
msgstr ""
+msgid "WikiEmpty|A wiki is where you can store all the details about your group. This can include why you've created it, its principles, how to use it, and so on."
+msgstr ""
+
msgid "WikiEmpty|A wiki is where you can store all the details about your project. This can include why you've created it, its principles, how to use it, and so on."
msgstr ""
@@ -25125,12 +25779,21 @@ msgstr ""
msgid "WikiEmpty|Suggest wiki improvement"
msgstr ""
+msgid "WikiEmpty|The wiki lets you write documentation for your group"
+msgstr ""
+
msgid "WikiEmpty|The wiki lets you write documentation for your project"
msgstr ""
+msgid "WikiEmpty|This group has no wiki pages"
+msgstr ""
+
msgid "WikiEmpty|This project has no wiki pages"
msgstr ""
+msgid "WikiEmpty|You must be a group member in order to add wiki pages."
+msgstr ""
+
msgid "WikiEmpty|You must be a project member in order to add wiki pages."
msgstr ""
@@ -25407,13 +26070,13 @@ msgstr ""
msgid "You can now export your security dashboard to a CSV report."
msgstr ""
-msgid "You can now submit a merge request to get this change into the original branch."
+msgid "You can now manage alert endpoint configuration in the Alerts section on the Operations settings page. Fields on this page have been deprecated."
msgstr ""
-msgid "You can now submit a merge request to get this change into the original project."
+msgid "You can now submit a merge request to get this change into the original branch."
msgstr ""
-msgid "You can only add files when you are on a branch"
+msgid "You can now submit a merge request to get this change into the original project."
msgstr ""
msgid "You can only edit files when you are on a branch"
@@ -25635,7 +26298,7 @@ msgstr ""
msgid "You need to upload a Google Takeout archive."
msgstr ""
-msgid "You reached %{usage_in_percent} of %{namespace_name}'s capacity (%{used_storage} of %{storage_limit})"
+msgid "You reached %{usage_in_percent} of %{namespace_name}'s storage capacity (%{used_storage} of %{storage_limit})"
msgstr ""
msgid "You tried to fork %{link_to_the_project} but it failed for the following reason:"
@@ -25938,13 +26601,13 @@ msgstr ""
msgid "Your subscription expired!"
msgstr ""
-msgid "Your subscription has been downgraded"
+msgid "Your subscription has been downgraded."
msgstr ""
-msgid "Your subscription will automatically renew in %{remaining_days}"
+msgid "Your subscription will automatically renew in %{remaining_days}."
msgstr ""
-msgid "Your subscription will expire in %{remaining_days}"
+msgid "Your subscription will expire in %{remaining_days}."
msgstr ""
msgid "Zoom meeting added"
@@ -26024,9 +26687,6 @@ msgstr ""
msgid "branch name"
msgstr ""
-msgid "build pipeline reference mismatch"
-msgstr ""
-
msgid "by"
msgstr ""
@@ -26045,6 +26705,9 @@ msgstr ""
msgid "cannot block others"
msgstr ""
+msgid "cannot contain HTML/XML tags, including any word between angle brackets (<,>)."
+msgstr ""
+
msgid "cannot include leading slash or directory traversal."
msgstr ""
@@ -26075,55 +26738,6 @@ msgstr ""
msgid "ciReport|%{remainingPackagesCount} more"
msgstr ""
-msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability"
-msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability for the source branch only"
-msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities for the source branch only"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerability"
-msgid_plural "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerabilities"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new vulnerability"
-msgid_plural "ciReport|%{reportType} %{status} detected %{newCount} new vulnerabilities"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities for the source branch only"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{fixedCount} fixed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} vulnerability for the source branch only"
-msgid_plural "ciReport|%{reportType} %{status} detected %{newCount} vulnerabilities for the source branch only"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "ciReport|%{reportType} %{status} detected no new vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected no vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected no vulnerabilities for the source branch only"
-msgstr ""
-
msgid "ciReport|%{reportType} is loading"
msgstr ""
@@ -26142,7 +26756,7 @@ msgstr ""
msgid "ciReport|All projects"
msgstr ""
-msgid "ciReport|All report types"
+msgid "ciReport|All scanner types"
msgstr ""
msgid "ciReport|All severities"
@@ -26166,6 +26780,9 @@ msgstr ""
msgid "ciReport|Container scanning detects known vulnerabilities in your docker images."
msgstr ""
+msgid "ciReport|Coverage Fuzzing"
+msgstr ""
+
msgid "ciReport|Create a merge request to implement this solution, or download and apply the patch manually."
msgstr ""
@@ -26324,6 +26941,9 @@ msgstr ""
msgid "customize"
msgstr ""
+msgid "data"
+msgstr ""
+
msgid "date must not be after 9999-12-31"
msgstr ""
@@ -26394,6 +27014,9 @@ msgstr ""
msgid "entries cannot contain HTML tags"
msgstr ""
+msgid "epic"
+msgstr ""
+
msgid "error"
msgstr ""
@@ -26415,9 +27038,6 @@ msgstr ""
msgid "expires on %{timebox_due_date}"
msgstr ""
-msgid "external_url"
-msgstr ""
-
msgid "failed"
msgstr ""
@@ -26453,9 +27073,6 @@ msgstr ""
msgid "from"
msgstr ""
-msgid "geo_node_name"
-msgstr ""
-
msgid "group"
msgstr ""
@@ -26477,9 +27094,6 @@ msgstr ""
msgid "https://your-bitbucket-server"
msgstr ""
-msgid "image"
-msgstr ""
-
msgid "image diff"
msgstr ""
@@ -26623,6 +27237,9 @@ msgstr ""
msgid "load it anyway"
msgstr ""
+msgid "loading"
+msgstr ""
+
msgid "locked by %{path_lock_user_name} %{created_at}"
msgstr ""
@@ -26751,9 +27368,6 @@ msgstr ""
msgid "mrWidget|Deployment statistics are not available currently"
msgstr ""
-msgid "mrWidget|Detect issues before deployment with a CI pipeline that continuously tests your code. We created a quick guide that will show you how to create one. Make your code more secure and more robust in just a minute."
-msgstr ""
-
msgid "mrWidget|Did not close"
msgstr ""
@@ -26916,6 +27530,9 @@ msgstr ""
msgid "mrWidget|To approve this merge request, please enter your password. This project requires all approvals to be authenticated."
msgstr ""
+msgid "mrWidget|Use %{linkStart}CI pipelines to test your code%{linkEnd}, simply add a GitLab CI configuration file to your project. It only takes a minute to make your code more secure and robust."
+msgstr ""
+
msgid "mrWidget|When this merge request is ready, remove the WIP: prefix from the title to allow it to be merged"
msgstr ""
@@ -26952,6 +27569,9 @@ msgstr ""
msgid "must be greater than start date"
msgstr ""
+msgid "must contain only valid frameworks"
+msgstr ""
+
msgid "my-awesome-group"
msgstr ""
@@ -26964,6 +27584,9 @@ msgstr ""
msgid "needs to be between 10 minutes and 1 month"
msgstr ""
+msgid "never"
+msgstr ""
+
msgid "never expires"
msgstr ""
@@ -26991,6 +27614,9 @@ msgstr ""
msgid "nounSeries|%{firstItem} and %{lastItem}"
msgstr ""
+msgid "nounSeries|%{item}"
+msgstr ""
+
msgid "nounSeries|%{item}, %{nextItem}"
msgstr ""
@@ -27127,6 +27753,9 @@ msgstr ""
msgid "revised"
msgstr ""
+msgid "satisfied"
+msgstr ""
+
msgid "score"
msgstr ""
@@ -27157,6 +27786,9 @@ msgstr ""
msgid "severity|Unknown"
msgstr ""
+msgid "should be an array of %{object_name} objects"
+msgstr ""
+
msgid "should be greater than or equal to %{access} inherited membership from group %{group_name}"
msgstr ""
@@ -27211,10 +27843,13 @@ msgstr ""
msgid "suggestPipeline|2/2: Commit your changes"
msgstr ""
-msgid "suggestPipeline|Commit the changes and your pipeline will automatically run for the first time."
+msgid "suggestPipeline|Choose %{boldStart}Code Quality%{boldEnd} to add a pipeline that tests the quality of your code."
msgstr ""
-msgid "suggestPipeline|We recommend the %{boldStart}Code Quality%{boldEnd} template, which will add a report widget to your Merge Requests. This way you’ll learn about code quality degradations much sooner. %{footerStart} Goodbye technical debt! %{footerEnd}"
+msgid "suggestPipeline|The template is ready! You can now commit it to create your first pipeline."
+msgstr ""
+
+msgid "suggestPipeline|We’re adding a GitLab CI configuration file to add a pipeline to the project. You could create it manually, but we recommend that you start with a GitLab template that works out of the box."
msgstr ""
msgid "syntax is correct"
@@ -27274,6 +27909,9 @@ msgstr ""
msgid "uses Kubernetes clusters to deploy your code!"
msgstr ""
+msgid "v%{version} published %{timeAgo}"
+msgstr ""
+
msgid "verify ownership"
msgstr ""
diff --git a/locale/tr_TR/gitlab.po b/locale/tr_TR/gitlab.po
index 224144637ca..c818845d974 100644
--- a/locale/tr_TR/gitlab.po
+++ b/locale/tr_TR/gitlab.po
@@ -2,7 +2,7 @@ msgid ""
msgstr ""
"Project-Id-Version: gitlab-ee\n"
"Report-Msgid-Bugs-To: \n"
-"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Last-Translator: \n"
"Language-Team: Turkish\n"
"Language: tr_TR\n"
"MIME-Version: 1.0\n"
@@ -10,9 +10,11 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Crowdin-Project: gitlab-ee\n"
+"X-Crowdin-Project-ID: 288872\n"
"X-Crowdin-Language: tr\n"
"X-Crowdin-File: /master/locale/gitlab.pot\n"
-"PO-Revision-Date: 2020-06-08 15:11\n"
+"X-Crowdin-File-ID: 6\n"
+"PO-Revision-Date: 2020-07-02 01:23\n"
msgid " %{start} to %{end}"
msgstr ""
@@ -106,8 +108,8 @@ msgstr[1] "%d yorum"
msgid "%d comment on this commit"
msgid_plural "%d comments on this commit"
-msgstr[0] ""
-msgstr[1] ""
+msgstr[0] "Bu işlem hakkında %d yorum"
+msgstr[1] "Bu işlem hakkında %d yorum"
msgid "%d commit"
msgid_plural "%d commits"
@@ -134,8 +136,8 @@ msgstr[1] "%d katkı"
msgid "%d day"
msgid_plural "%d days"
-msgstr[0] ""
-msgstr[1] ""
+msgstr[0] "%d gün"
+msgstr[1] "%d gün"
msgid "%d error"
msgid_plural "%d errors"
@@ -197,6 +199,11 @@ msgid_plural "%d metrics"
msgstr[0] "%d metrik"
msgstr[1] "%d metrik"
+msgid "%d milestone"
+msgid_plural "%d milestones"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "%d minute"
msgid_plural "%d minutes"
msgstr[0] "%d dakika"
@@ -242,11 +249,6 @@ msgid_plural "%d unresolved threads"
msgstr[0] "%d çözümlenmemiş konu"
msgstr[1] "%d çözümlenmemiş konu"
-msgid "%d url scanned"
-msgid_plural "%d urls scanned"
-msgstr[0] ""
-msgstr[1] ""
-
msgid "%d vulnerability"
msgid_plural "%d vulnerabilities"
msgstr[0] ""
@@ -324,9 +326,15 @@ msgstr[1] "%{count} bekleyen yorum"
msgid "%{count} related %{pluralized_subject}: %{links}"
msgstr ""
+msgid "%{dashboard_path} could not be found."
+msgstr ""
+
msgid "%{days} days until tags are automatically removed"
msgstr ""
+msgid "%{deployLinkStart}Use a template to deploy to ECS%{deployLinkEnd}, or use a docker image to %{commandsLinkStart}run AWS commands in GitLab CI/CD%{commandsLinkEnd}."
+msgstr ""
+
msgid "%{description}- Sentry event: %{errorUrl}- First seen: %{firstSeen}- Last seen: %{lastSeen} %{countLabel}: %{count}%{userCountLabel}: %{userCount}"
msgstr ""
@@ -369,12 +377,18 @@ msgstr ""
msgid "%{host} sign-in from new location"
msgstr ""
-msgid "%{icon}You are about to add %{usersTag} people to the discussion. Proceed with caution."
+msgid "%{icon}You are about to add %{usersTag} people to the discussion. They will all receive a notification."
+msgstr ""
+
+msgid "%{integrations_link_start}Integrations%{link_end} enable you to make third-party applications part of your GitLab workflow. If the available integrations don't meet your needs, consider using a %{webhooks_link_start}webhook%{link_end}."
msgstr ""
msgid "%{issuableType} will be removed! Are you sure?"
msgstr "%{issuableType} kaldırılacak! Emin misiniz?"
+msgid "%{issuesCount} issues in this group"
+msgstr ""
+
msgid "%{issuesSize} issues"
msgstr "%{issuesSize} sorun"
@@ -384,7 +398,7 @@ msgstr ""
msgid "%{labelStart}Class:%{labelEnd} %{class}"
msgstr ""
-msgid "%{labelStart}Confidence:%{labelEnd} %{confidence}"
+msgid "%{labelStart}Evidence:%{labelEnd} %{evidence}"
msgstr ""
msgid "%{labelStart}File:%{labelEnd} %{file}"
@@ -402,6 +416,9 @@ msgstr ""
msgid "%{labelStart}Report Type:%{labelEnd} %{reportType}"
msgstr ""
+msgid "%{labelStart}Scanner:%{labelEnd} %{scanner}"
+msgstr ""
+
msgid "%{labelStart}Severity:%{labelEnd} %{severity}"
msgstr ""
@@ -426,9 +443,6 @@ msgstr ""
msgid "%{link_start}Read more%{link_end} about role permissions"
msgstr "Rol izinleri hakkında %{link_start}daha fazla bilgi edinin%{link_end}"
-msgid "%{link} can be used for binding events when something is happening within the project."
-msgstr ""
-
msgid "%{listToShow}, and %{awardsListLength} more."
msgstr "%{listToShow}, ve %{awardsListLength} dahası."
@@ -462,6 +476,9 @@ msgstr "%{name} %{resultsString} ifadesini içeriyor"
msgid "%{name} found %{resultsString}"
msgstr "%{name} bulundu %{resultsString}"
+msgid "%{name} is already being used for another emoji"
+msgstr ""
+
msgid "%{name} is scheduled for %{action}"
msgstr ""
@@ -509,6 +526,9 @@ msgstr "%{placeholder} geçerli bir tema değil"
msgid "%{primary} (%{secondary})"
msgstr "%{primary} (%{secondary})"
+msgid "%{ref} cannot be added: %{error}"
+msgstr ""
+
msgid "%{releases} release"
msgid_plural "%{releases} releases"
msgstr[0] "%{releases} sürüm"
@@ -517,13 +537,37 @@ msgstr[1] "%{releases} sürüm"
msgid "%{remaining_approvals} left"
msgstr ""
-msgid "%{retryButtonStart}Try again%{retryButtonEnd} or %{newFileButtonStart}attach a new file%{newFileButtonEnd}"
+msgid "%{reportType} %{status} detected %{critical} critical and %{high} high severity vulnerabilities out of %{total}."
msgstr ""
-msgid "%{screenreaderOnlyStart}Keyboard shorcuts%{screenreaderOnlyEnd} Disabled"
+msgid "%{reportType} %{status} detected %{critical} critical and %{high} high severity vulnerabilities."
msgstr ""
-msgid "%{screenreaderOnlyStart}Keyboard shorcuts%{screenreaderOnlyEnd} Enabled"
+msgid "%{reportType} %{status} detected %{critical} critical severity vulnerabilities out of %{total}."
+msgstr ""
+
+msgid "%{reportType} %{status} detected %{critical} critical severity vulnerability."
+msgid_plural "%{reportType} %{status} detected %{critical} critical severity vulnerabilities."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{reportType} %{status} detected %{high} high severity vulnerabilities out of %{total}."
+msgstr ""
+
+msgid "%{reportType} %{status} detected %{high} high severity vulnerability."
+msgid_plural "%{reportType} %{status} detected %{high} high severity vulnerabilities."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{reportType} %{status} detected %{other} vulnerability."
+msgid_plural "%{reportType} %{status} detected %{other} vulnerabilities."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{reportType} %{status} detected no new vulnerabilities."
+msgstr ""
+
+msgid "%{retryButtonStart}Try again%{retryButtonEnd} or %{newFileButtonStart}attach a new file%{newFileButtonEnd}"
msgstr ""
msgid "%{service_title} %{message}."
@@ -620,6 +664,12 @@ msgstr "%{title} deÄŸiÅŸiklik"
msgid "%{token}..."
msgstr "%{token}..."
+msgid "%{totalCpu} (%{freeSpacePercentage}%{percentSymbol} free)"
+msgstr ""
+
+msgid "%{totalMemory} (%{freeSpacePercentage}%{percentSymbol} free)"
+msgstr ""
+
msgid "%{totalWeight} total weight"
msgstr "%{totalWeight} toplam ağırlık"
@@ -650,6 +700,12 @@ msgstr "%{value} s"
msgid "%{verb} %{time_spent_value} spent time."
msgstr ""
+msgid "%{webhooks_link_start}%{webhook_type}%{link_end} enable you to send notifications to web applications in response to events in a group or project."
+msgstr ""
+
+msgid "%{webhooks_link_start}%{webhook_type}%{link_end} enable you to send notifications to web applications in response to events in a group or project. We recommend using an %{integrations_link_start}integration%{link_end} in preference to a webhook."
+msgstr ""
+
msgid "'%{level}' is not a valid visibility level"
msgstr "'%{level}' geçerli bir görünürlük seviyesi değil"
@@ -676,9 +732,6 @@ msgstr "(%{mrCount} birleÅŸtirildi)"
msgid "(No changes)"
msgstr "(DeÄŸiÅŸiklik yok)"
-msgid "(Show all)"
-msgstr "(Tümünü göster)"
-
msgid "(check progress)"
msgstr "(ilerlemeyi kontrol et)"
@@ -745,6 +798,9 @@ msgstr "- daha az göster"
msgid "0 for unlimited"
msgstr "Sınırsız için 0"
+msgid "0 for unlimited, only effective with remote storage enabled."
+msgstr ""
+
msgid "1 %{type} addition"
msgid_plural "%{count} %{type} additions"
msgstr[0] "1 %{type} ek"
@@ -892,6 +948,12 @@ msgstr ""
msgid "<code>\"johnsmith@example.com\": \"johnsmith@example.com\"</code> will add \"By <a href=\"#\">johnsmith@example.com</a>\" to all issues and comments originally created by johnsmith@example.com. By default, the email address or username is masked to ensure the user's privacy. Use this option if you want to show the full email address."
msgstr ""
+msgid "<code>Masked</code> to prevent the values from being displayed in job logs (must match certain regexp requirements)."
+msgstr ""
+
+msgid "<code>Protected</code> to expose them to protected branches or tags only."
+msgstr ""
+
msgid "<namespace / project>"
msgstr ""
@@ -919,9 +981,6 @@ msgstr ""
msgid "A .NET Core console application template, customizable for any .NET Core project"
msgstr ""
-msgid "A DAG must have two dependent jobs to be visualized on this tab."
-msgstr ""
-
msgid "A GitBook site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
msgstr ""
@@ -952,6 +1011,12 @@ msgstr "Boş bir proje için bir varsayılan dal seçilemez."
msgid "A deleted user"
msgstr "Silinmiş kullanıcı"
+msgid "A file has been changed."
+msgstr "Bir dosya deÄŸiÅŸtirildi."
+
+msgid "A file was not found."
+msgstr "Bir dosya bulunamadı."
+
msgid "A file with '%{file_name}' already exists in %{branch} branch"
msgstr ""
@@ -1018,8 +1083,8 @@ msgstr ""
msgid "A subscription will trigger a new pipeline on the default branch of this project when a pipeline successfully completes for a new tag on the %{default_branch_docs} of the subscribed project."
msgstr ""
-msgid "A terraform report was generated in your pipelines."
-msgstr "İş hattınızda yeni bir terraform raporu oluşturuldu."
+msgid "A suggestion is not applicable."
+msgstr ""
msgid "A user with write access to the source branch selected this option"
msgstr "Bu dala yazma yetkisi olan kullanıcı bu seçeneği seçti"
@@ -1189,6 +1254,9 @@ msgstr "Hesap: %{account}"
msgid "Action to take when receiving an alert."
msgstr "Bir uyarı alırken yapılması gerekenler."
+msgid "Actions"
+msgstr ""
+
msgid "Activate"
msgstr "EtkinleÅŸtir"
@@ -1208,7 +1276,7 @@ msgid "Active Users:"
msgstr "Etkin Kullanıcılar:"
msgid "Active users"
-msgstr ""
+msgstr "Aktif kullanıcılar"
msgid "Activity"
msgstr "Etkinlik"
@@ -1245,6 +1313,9 @@ msgstr "Kubernetes kümesi ekle"
msgid "Add LICENSE"
msgstr "LÄ°SANS ekle"
+msgid "Add New Node"
+msgstr ""
+
msgid "Add README"
msgstr "BENÄ°OKU ekle"
@@ -1320,6 +1391,9 @@ msgstr "Başka bir bağlantı ekle"
msgid "Add approval rule"
msgstr "Onay kuralı ekle"
+msgid "Add approvers"
+msgstr ""
+
msgid "Add bold text"
msgstr "Kalın metin ekle"
@@ -1377,6 +1451,9 @@ msgstr "Ä°steÄŸi elle ekle"
msgid "Add strikethrough text"
msgstr ""
+msgid "Add suggestion to batch"
+msgstr ""
+
msgid "Add system hook"
msgstr "Sistem kancası ekle"
@@ -1474,6 +1551,9 @@ msgid "Admin Area"
msgstr "Yönetici alanı"
msgid "Admin Note"
+msgstr "Yönetici Mesajı"
+
+msgid "Admin Notifications"
msgstr ""
msgid "Admin Overview"
@@ -1587,6 +1667,9 @@ msgstr ""
msgid "AdminSettings|Integrations configured here will automatically apply to all projects on this instance."
msgstr ""
+msgid "AdminSettings|Moved to integrations"
+msgstr ""
+
msgid "AdminSettings|No required pipeline"
msgstr "Gerekli iş hattı yok"
@@ -1817,12 +1900,18 @@ msgid_plural "Alerts"
msgstr[0] "Uyarı"
msgstr[1] "Uyarı"
+msgid "AlertManagement| assign yourself"
+msgstr ""
+
msgid "AlertManagement|Acknowledged"
msgstr ""
msgid "AlertManagement|Alert"
msgstr "Uyarı"
+msgid "AlertManagement|Alert assignee(s): %{assignees}"
+msgstr ""
+
msgid "AlertManagement|Alert detail"
msgstr ""
@@ -1838,9 +1927,15 @@ msgstr ""
msgid "AlertManagement|All alerts"
msgstr ""
+msgid "AlertManagement|Assign To"
+msgstr ""
+
msgid "AlertManagement|Assign status"
msgstr ""
+msgid "AlertManagement|Assignee"
+msgstr ""
+
msgid "AlertManagement|Assignees"
msgstr ""
@@ -1859,9 +1954,6 @@ msgstr ""
msgid "AlertManagement|Edit"
msgstr ""
-msgid "AlertManagement|End time"
-msgstr "Bitiş zamanı"
-
msgid "AlertManagement|Events"
msgstr "Olaylar"
@@ -1880,10 +1972,13 @@ msgstr ""
msgid "AlertManagement|More information"
msgstr "Daha fazla bilgi"
+msgid "AlertManagement|No Matching Results"
+msgstr ""
+
msgid "AlertManagement|No alert data to display."
msgstr ""
-msgid "AlertManagement|No alerts available to display. If you think you're seeing this message in error, refresh the page."
+msgid "AlertManagement|No alerts available to display. See %{linkStart}enabling alert management%{linkEnd} for more information on adding alerts to the list."
msgstr ""
msgid "AlertManagement|No alerts to display."
@@ -1892,6 +1987,9 @@ msgstr ""
msgid "AlertManagement|None"
msgstr ""
+msgid "AlertManagement|None -"
+msgstr ""
+
msgid "AlertManagement|Open"
msgstr ""
@@ -1928,9 +2026,18 @@ msgstr ""
msgid "AlertManagement|There was an error displaying the alerts. Confirm your endpoint's configuration details to ensure alerts appear."
msgstr ""
+msgid "AlertManagement|There was an error while updating the assignee(s) list. Please try again."
+msgstr ""
+
+msgid "AlertManagement|There was an error while updating the assignee(s) of the alert. Please try again."
+msgstr ""
+
msgid "AlertManagement|There was an error while updating the status of the alert. Please try again."
msgstr ""
+msgid "AlertManagement|This assignee cannot be assigned to this alert."
+msgstr ""
+
msgid "AlertManagement|Tool"
msgstr ""
@@ -2078,6 +2185,9 @@ msgstr ""
msgid "Allow users to request access (if visibility is public or internal)"
msgstr "Kullanıcıların erişim isteğinde bulunmalarına izin ver (görünürlük genel veya dahili ise)"
+msgid "Allowed Geo IP"
+msgstr ""
+
msgid "Allowed email domain restriction only permitted for top-level groups"
msgstr ""
@@ -2111,6 +2221,9 @@ msgstr ""
msgid "Amazon Web Services"
msgstr "Amazon Web Servisleri"
+msgid "Amazon Web Services Logo"
+msgstr ""
+
msgid "Amazon authentication is not %{link_start}correctly configured%{link_end}. Ask your GitLab administrator if you want to use this service."
msgstr ""
@@ -2168,10 +2281,16 @@ msgstr ""
msgid "An error occurred when updating the issue weight"
msgstr "Sorun ağırlığı güncellenirken bir hata oluştu"
+msgid "An error occurred while acknowledging the notification. Refresh the page and try again."
+msgstr ""
+
+msgid "An error occurred while adding approvers"
+msgstr ""
+
msgid "An error occurred while adding formatted title for epic"
msgstr ""
-msgid "An error occurred while checking group path"
+msgid "An error occurred while checking group path. Please refresh and try again."
msgstr ""
msgid "An error occurred while committing your changes."
@@ -2315,8 +2434,8 @@ msgstr "Birleştirme istekleri yüklenirken bir hata oluştu."
msgid "An error occurred while loading milestones"
msgstr ""
-msgid "An error occurred while loading terraform report"
-msgstr "Terraform raporu yüklenirken bir hata oluştu"
+msgid "An error occurred while loading project creation UI"
+msgstr ""
msgid "An error occurred while loading the data. Please try again."
msgstr "Veriler yüklenirken bir hata oluştu. Lütfen tekrar deneyin."
@@ -2369,6 +2488,9 @@ msgstr "Sorunlar kaldırılırken bir hata oluştu."
msgid "An error occurred while rendering preview broadcast message"
msgstr "Önizleme yayını iletisi oluşturulurken bir hata oluştu"
+msgid "An error occurred while rendering the editor"
+msgstr ""
+
msgid "An error occurred while reordering issues."
msgstr "Sorunları yeniden sıralarken bir hata oluştu."
@@ -2465,7 +2587,7 @@ msgstr "Web uygulamanızın inceleme sürümünü analiz edin."
msgid "Analyze your dependencies for known vulnerabilities."
msgstr ""
-msgid "Analyze your source code and git history for secrets"
+msgid "Analyze your source code and git history for secrets."
msgstr ""
msgid "Analyze your source code for known vulnerabilities."
@@ -2579,12 +2701,18 @@ msgstr ""
msgid "Apply suggestion"
msgstr "Öneriyi uygula"
+msgid "Apply suggestions"
+msgstr ""
+
msgid "Apply template"
msgstr "Åžablonu uygula"
msgid "Apply this approval rule to any branch or a specific protected branch."
msgstr ""
+msgid "Applying"
+msgstr ""
+
msgid "Applying a template will replace the existing issue description. Any changes you have made will be lost."
msgstr ""
@@ -2597,12 +2725,18 @@ msgstr ""
msgid "Applying multiple commands"
msgstr "Birden çok komut uygulanıyor"
-msgid "Applying suggestion"
-msgstr "Öneri uygulanıyor"
+msgid "Applying suggestion..."
+msgstr ""
+
+msgid "Applying suggestions..."
+msgstr ""
msgid "Approval rules"
msgstr "Onay kuralları"
+msgid "Approval rules reset to project defaults"
+msgstr ""
+
msgid "ApprovalRuleRemove|%d member"
msgid_plural "ApprovalRuleRemove|%d members"
msgstr[0] "%d üye"
@@ -2656,6 +2790,9 @@ msgstr "Geçerli birleştirme talebini onayla."
msgid "Approved"
msgstr ""
+msgid "Approved MRs"
+msgstr ""
+
msgid "Approved by: "
msgstr "Onaylayan: "
@@ -2869,6 +3006,9 @@ msgstr "Varlıklar:"
msgid "Assign"
msgstr "Atama"
+msgid "Assign Iteration"
+msgstr ""
+
msgid "Assign custom color like #FF0000"
msgstr "#FF0000 gibi özel renk ata"
@@ -2887,6 +3027,9 @@ msgstr "Bu dönüm noktasına bazı sorunları atayın."
msgid "Assign to"
msgstr "Ata"
+msgid "Assign to commenting user"
+msgstr ""
+
msgid "Assign yourself to these issues"
msgstr "Kendinizi bu soruna atayın"
@@ -2913,6 +3056,9 @@ msgid_plural "%d Assignees"
msgstr[0] "Vekil"
msgstr[1] "%d Vekil"
+msgid "Assignee has no permissions"
+msgstr ""
+
msgid "Assignee lists not available with your current license"
msgstr "Atanan listeleri mevcut lisansınızla birlikte kullanılamıyor"
@@ -2922,6 +3068,9 @@ msgstr "Atanan listeleri, seçilen kullanıcıya atanan tüm sorunları gösteri
msgid "Assignee(s)"
msgstr "Vekil(ler)"
+msgid "Assignees"
+msgstr ""
+
msgid "Assigns %{assignee_users_sentence}."
msgstr ""
@@ -2957,6 +3106,9 @@ msgstr "Denetim Etkinlikleri"
msgid "Audit Events is a way to keep track of important events that happened in GitLab."
msgstr ""
+msgid "Audit Log"
+msgstr ""
+
msgid "AuditEvents|(removed)"
msgstr "(kaldırıldı)"
@@ -2993,6 +3145,9 @@ msgstr ""
msgid "AuditLogs|IP Address"
msgstr "IP Adresi"
+msgid "AuditLogs|Member Events"
+msgstr ""
+
msgid "AuditLogs|No matching %{type} found."
msgstr ""
@@ -3275,9 +3430,6 @@ msgstr "Rozetleriniz"
msgid "Badges|e.g. %{exampleUrl}"
msgstr "örn. %{exampleUrl}"
-msgid "Badge|New"
-msgstr "Yeni"
-
msgid "Balsamiq file could not be loaded."
msgstr "Balsamiq dosyası yüklenemedi."
@@ -3326,6 +3478,12 @@ msgstr ""
msgid "Below you will find all the groups that are public."
msgstr ""
+msgid "Beta"
+msgstr ""
+
+msgid "Bi-weekly code coverage"
+msgstr ""
+
msgid "Billing"
msgstr "Fatura"
@@ -3386,9 +3544,15 @@ msgstr "Yükselt"
msgid "Bitbucket Server Import"
msgstr ""
+msgid "Bitbucket Server import"
+msgstr ""
+
msgid "Bitbucket import"
msgstr ""
+msgid "Blame"
+msgstr ""
+
msgid "Blocked"
msgstr "EngellenmiÅŸ"
@@ -3401,9 +3565,6 @@ msgstr ""
msgid "Blog"
msgstr "Blog"
-msgid "Blue helpers indicate an action to be taken."
-msgstr ""
-
msgid "Board name"
msgstr "Pano adı"
@@ -3602,6 +3763,9 @@ msgstr ""
msgid "Broadcast Message was successfully updated."
msgstr "Yayın Mesajı başarıyla güncellendi."
+msgid "Broadcast Messages"
+msgstr ""
+
msgid "Browse Directory"
msgstr "Dizine Gözat"
@@ -3656,6 +3820,9 @@ msgstr ""
msgid "By %{user_name}"
msgstr "%{user_name} tarafından"
+msgid "By URL"
+msgstr ""
+
msgid "By default GitLab sends emails in HTML and plain text formats so mail clients can choose what format to use. Disable this option if you only want to send emails in plain text format."
msgstr "Öntanımlı olarak GitLab, e-postaları HTML ve düz metin biçimlerinde gönderir, böylece posta istemcileri hangi biçimi kullanacaklarını seçebilir. Yalnızca düz metin biçiminde e-postalar göndermek istiyorsanız bu seçeneği devre dışı bırakın."
@@ -3761,6 +3928,9 @@ msgstr ""
msgid "Can override approvers and approvals required per merge request"
msgstr "Birleştirme isteği için gereken onaylayıcıları ve onayları geçersiz kılabilir"
+msgid "Can't apply as this line has changed or the suggestion already matches its content."
+msgstr ""
+
msgid "Can't create snippet: %{err}"
msgstr ""
@@ -3947,9 +4117,6 @@ msgstr ""
msgid "Changes are still tracked. Useful for cluster/index migrations."
msgstr ""
-msgid "Changes are unknown"
-msgstr "DeÄŸiÅŸiklikler bilinmiyor"
-
msgid "Changes suppressed. Click to show."
msgstr "Değişiklikler bastırıldı. Göstermek için tıkla."
@@ -4256,15 +4423,9 @@ msgstr "Görünürlük seviyesini seçin, proje özelliklerini etkinleştirin/de
msgid "Choose what content you want to see on a group’s overview page"
msgstr "Bir grubun genel bakış sayfasında hangi içeriği görmek istediğinizi seçin"
-msgid "Choose which groups you wish to synchronize to this secondary node"
-msgstr ""
-
msgid "Choose which repositories you want to connect and run CI/CD pipelines."
msgstr ""
-msgid "Choose which shards you wish to synchronize to this secondary node"
-msgstr ""
-
msgid "Choose your framework"
msgstr ""
@@ -4562,9 +4723,15 @@ msgstr ""
msgid "Cluster does not exist"
msgstr ""
+msgid "Cluster is required for Stages::ClusterEndpointInserter"
+msgstr ""
+
msgid "Cluster level"
msgstr ""
+msgid "Cluster type must be specificed for Stages::ClusterEndpointInserter"
+msgstr ""
+
msgid "ClusterIntegration| %{custom_domain_start}More information%{custom_domain_end}."
msgstr "%{custom_domain_start}Daha fazla bilgi%{custom_domain_end}."
@@ -4676,7 +4843,7 @@ msgstr "CA Sertifikası"
msgid "ClusterIntegration|Cert-Manager"
msgstr "Sertifika Yöneticisi"
-msgid "ClusterIntegration|Cert-Manager is a native Kubernetes certificate management controller that helps with issuing certificates. Installing Cert-Manager on your cluster will issue a certificate by %{letsEncrypt} and ensure that certificates are valid and up-to-date."
+msgid "ClusterIntegration|Cert-Manager is a native Kubernetes certificate management controller that helps with issuing certificates. Installing Cert-Manager on your cluster will issue a certificate by %{linkStart}Let's Encrypt%{linkEnd} and ensure that certificates are valid and up-to-date."
msgstr ""
msgid "ClusterIntegration|Certificate Authority bundle (PEM format)"
@@ -4703,9 +4870,6 @@ msgstr ""
msgid "ClusterIntegration|Clear the local cache of namespace and service accounts. This is necessary if your integration has become out of sync. The cache is repopulated during the next CI job that requires namespace and service accounts."
msgstr ""
-msgid "ClusterIntegration|Cloud Run"
-msgstr ""
-
msgid "ClusterIntegration|Cluster being created"
msgstr ""
@@ -4790,7 +4954,7 @@ msgstr ""
msgid "ClusterIntegration|Crossplane"
msgstr ""
-msgid "ClusterIntegration|Crossplane enables declarative provisioning of managed services from your cloud of choice using %{codeStart}kubectl%{codeEnd} or %{gitlabIntegrationLink}. Crossplane runs inside your Kubernetes cluster and supports secure connectivity and secrets management between app containers and the cloud services they depend on."
+msgid "ClusterIntegration|Crossplane enables declarative provisioning of managed services from your cloud of choice using %{codeStart}kubectl%{codeEnd} or %{linkStart}GitLab Integration%{linkEnd}. Crossplane runs inside your Kubernetes cluster and supports secure connectivity and secrets management between app containers and the cloud services they depend on."
msgstr ""
msgid "ClusterIntegration|Deletes all GitLab resources attached to this cluster during removal"
@@ -4874,9 +5038,6 @@ msgstr "GitLab Çalıştırıcı; depoya bağlanır ve CI/CD işlerini yürütü
msgid "ClusterIntegration|GitLab-managed cluster"
msgstr ""
-msgid "ClusterIntegration|Gitlab Integration"
-msgstr ""
-
msgid "ClusterIntegration|Global default"
msgstr "Genel varsayılan"
@@ -4922,8 +5083,8 @@ msgstr "Ingress bitiş noktası"
msgid "ClusterIntegration|Ingress gives you a way to route requests to services based on the request host or path, centralizing a number of services into a single entrypoint."
msgstr "Ingress; istekleri, istek ana bilgisayarına veya yoluna dayanarak hizmetlere yönlendirerek bir dizi hizmeti tek bir giriş noktasına merkezileştirme yolu sunar."
-msgid "ClusterIntegration|Installing Ingress may incur additional costs. Learn more about %{pricingLink}."
-msgstr "Ingress'ın yüklenmesi ek ücrete neden olabilir. %{pricingLink} hakkında daha fazla bilgi edinin."
+msgid "ClusterIntegration|Installing Ingress may incur additional costs. Learn more about %{linkStart}pricing%{linkEnd}."
+msgstr ""
msgid "ClusterIntegration|Instance cluster"
msgstr ""
@@ -4937,8 +5098,8 @@ msgstr "Kubernetes küme otomasyonunu bütünleştir"
msgid "ClusterIntegration|Issuer Email"
msgstr "Yayınlayıcı e-postası"
-msgid "ClusterIntegration|Issuers represent a certificate authority. You must provide an email address for your Issuer. "
-msgstr "Yayınlayıcı, bir sertifika yetkilisini temsil eder. Yayınlayıcınız için bir e-posta adresi sağlamalısınız. "
+msgid "ClusterIntegration|Issuers represent a certificate authority. You must provide an email address for your Issuer."
+msgstr ""
msgid "ClusterIntegration|Jupyter Hostname"
msgstr "Jupyter ana bilgisayar adı"
@@ -5009,9 +5170,6 @@ msgstr ""
msgid "ClusterIntegration|Learn more about instance Kubernetes clusters"
msgstr ""
-msgid "ClusterIntegration|Let's Encrypt"
-msgstr ""
-
msgid "ClusterIntegration|Loading IAM Roles"
msgstr ""
@@ -5123,7 +5281,7 @@ msgstr ""
msgid "ClusterIntegration|Prometheus"
msgstr "Prometheus"
-msgid "ClusterIntegration|Prometheus is an open-source monitoring system with %{gitlabIntegrationLink} to monitor deployed applications."
+msgid "ClusterIntegration|Prometheus is an open-source monitoring system with %{linkStart}GitLab Integration%{linkEnd} to monitor deployed applications."
msgstr ""
msgid "ClusterIntegration|Provider details"
@@ -5279,10 +5437,10 @@ msgstr "Alan seç"
msgid "ClusterIntegration|Select zone to choose machine type"
msgstr "Makine türünü seçmek için alan seç"
-msgid "ClusterIntegration|Send Cilium Logs"
+msgid "ClusterIntegration|Send Container Network Policies Logs"
msgstr ""
-msgid "ClusterIntegration|Send ModSecurity Logs"
+msgid "ClusterIntegration|Send Web Application Firewall Logs"
msgstr ""
msgid "ClusterIntegration|Service Token"
@@ -5450,15 +5608,12 @@ msgstr ""
msgid "ClusterIntegration|documentation"
msgstr "belgeleme"
-msgid "ClusterIntegration|installed via %{installed_via}"
+msgid "ClusterIntegration|installed via %{linkStart}Cloud Run%{linkEnd}"
msgstr ""
msgid "ClusterIntegration|meets the requirements"
msgstr ""
-msgid "ClusterIntegration|pricing"
-msgstr "fiyatlandırma"
-
msgid "ClusterIntegration|sign up"
msgstr "kaydol"
@@ -5495,6 +5650,15 @@ msgstr ""
msgid "Code"
msgstr ""
+msgid "Code Coverage: %{coveragePercentage}%{percentSymbol}"
+msgstr ""
+
+msgid "Code Coverage| Empty code coverage data"
+msgstr ""
+
+msgid "Code Coverage|Couldn't fetch the code coverage data"
+msgstr ""
+
msgid "Code Owners"
msgstr "Kod Sahipleri"
@@ -5549,7 +5713,7 @@ msgstr "Daralt"
msgid "Collapse approvers"
msgstr "Onaylananları daralt"
-msgid "Collapse child epics"
+msgid "Collapse milestones"
msgstr ""
msgid "Collapse replies"
@@ -5567,6 +5731,9 @@ msgstr ""
msgid "Coming soon"
msgstr ""
+msgid "Comma-separated, e.g. '1.1.1.1, 2.2.2.0/24'"
+msgstr ""
+
msgid "Command"
msgstr ""
@@ -5737,6 +5904,12 @@ msgstr ""
msgid "Compliance framework (optional)"
msgstr ""
+msgid "Compliance frameworks"
+msgstr ""
+
+msgid "ComplianceDashboard|created by:"
+msgstr ""
+
msgid "ComplianceFramework|GDPR"
msgstr ""
@@ -5875,6 +6048,9 @@ msgstr ""
msgid "Connection timed out"
msgstr "Bağlantı zaman aşımına uğradı"
+msgid "Connection timeout"
+msgstr ""
+
msgid "Contact sales to upgrade"
msgstr "Yükseltmek için satıcı ile iletişim kurun"
@@ -5930,9 +6106,6 @@ msgstr ""
msgid "ContainerRegistry|CLI Commands"
msgstr ""
-msgid "ContainerRegistry|Compressed Size"
-msgstr ""
-
msgid "ContainerRegistry|Container Registry"
msgstr ""
@@ -5948,6 +6121,9 @@ msgstr ""
msgid "ContainerRegistry|Copy push command"
msgstr ""
+msgid "ContainerRegistry|Delete selected"
+msgstr ""
+
msgid "ContainerRegistry|Docker connection error"
msgstr ""
@@ -5981,17 +6157,17 @@ msgstr ""
msgid "ContainerRegistry|If you are not already logged in, you need to authenticate to the Container Registry by using your GitLab username and password. If you have %{twofaDocLinkStart}Two-Factor Authentication%{twofaDocLinkEnd} enabled, use a %{personalAccessTokensDocLinkStart}Personal Access Token%{personalAccessTokensDocLinkEnd} instead of a password."
msgstr ""
-msgid "ContainerRegistry|Image ID"
+msgid "ContainerRegistry|Image ID: %{imageId}"
msgstr ""
msgid "ContainerRegistry|Image Repositories"
msgstr ""
-msgid "ContainerRegistry|Keep and protect the images that matter most."
+msgid "ContainerRegistry|Image tags"
msgstr ""
-msgid "ContainerRegistry|Last Updated"
-msgstr "Son Güncelleme"
+msgid "ContainerRegistry|Keep and protect the images that matter most."
+msgstr ""
msgid "ContainerRegistry|Login"
msgstr ""
@@ -6005,6 +6181,9 @@ msgstr ""
msgid "ContainerRegistry|Please contact your administrator."
msgstr ""
+msgid "ContainerRegistry|Published %{timeInfo}"
+msgstr ""
+
msgid "ContainerRegistry|Push an image"
msgstr ""
@@ -6020,9 +6199,6 @@ msgstr ""
msgid "ContainerRegistry|Remove repository"
msgstr "Depoyu kaldır"
-msgid "ContainerRegistry|Remove selected tags"
-msgstr ""
-
msgid "ContainerRegistry|Remove tag"
msgid_plural "ContainerRegistry|Remove tags"
msgstr[0] ""
@@ -6052,9 +6228,6 @@ msgstr ""
msgid "ContainerRegistry|Sorry, your filter produced no results."
msgstr ""
-msgid "ContainerRegistry|Tag"
-msgstr "Etiket"
-
msgid "ContainerRegistry|Tag expiration policy"
msgstr ""
@@ -6199,21 +6372,6 @@ msgstr "Hesabınıza bağlı e-postaları kontrol edin"
msgid "Control the display of third party offers."
msgstr ""
-msgid "Control the maximum concurrency of LFS/attachment backfill for this secondary node"
-msgstr ""
-
-msgid "Control the maximum concurrency of container repository operations for this Geo node"
-msgstr ""
-
-msgid "Control the maximum concurrency of repository backfill for this secondary node"
-msgstr ""
-
-msgid "Control the maximum concurrency of verification operations for this Geo node"
-msgstr ""
-
-msgid "Control the minimum interval in days that a repository should be reverified for this primary node"
-msgstr ""
-
msgid "Cookie domain"
msgstr ""
@@ -6355,6 +6513,9 @@ msgstr ""
msgid "Could not find design."
msgstr ""
+msgid "Could not find iteration"
+msgstr ""
+
msgid "Could not remove the trigger."
msgstr ""
@@ -6391,6 +6552,12 @@ msgstr "Ãœlke"
msgid "Coverage"
msgstr "Kapsam"
+msgid "Coverage Fuzzing"
+msgstr ""
+
+msgid "Crash State"
+msgstr ""
+
msgid "Create"
msgstr "OluÅŸtur"
@@ -6439,6 +6606,9 @@ msgstr "Yeni bir depo oluÅŸtur"
msgid "Create a personal access token on your account to pull or push via %{protocol}."
msgstr ""
+msgid "Create a project pre-populated with the necessary files to get you started quickly."
+msgstr ""
+
msgid "Create an account using:"
msgstr ""
@@ -6607,6 +6777,9 @@ msgstr ""
msgid "Creates branch '%{branch_name}' and a merge request to resolve this issue."
msgstr ""
+msgid "Creating"
+msgstr ""
+
msgid "Creating epic"
msgstr ""
@@ -6658,7 +6831,10 @@ msgstr "Mevcut ÅŸifre"
msgid "Current vulnerabilities count"
msgstr ""
-msgid "CurrentUser|Buy CI minutes"
+msgid "CurrentUser|Buy Pipeline minutes"
+msgstr ""
+
+msgid "CurrentUser|One of your groups is running out"
msgstr ""
msgid "CurrentUser|Profile"
@@ -6673,6 +6849,9 @@ msgstr "Altın denemesini başlat"
msgid "CurrentUser|Upgrade"
msgstr ""
+msgid "Custom Attributes"
+msgstr ""
+
msgid "Custom CI configuration path"
msgstr ""
@@ -6936,12 +7115,18 @@ msgstr ""
msgid "DAG"
msgstr ""
+msgid "DAG visualization requires at least 3 dependent jobs."
+msgstr ""
+
msgid "DNS"
msgstr "DNS"
msgid "Dashboard"
msgstr "Kontrol paneli"
+msgid "Dashboard uid not found"
+msgstr ""
+
msgid "DashboardProjects|All"
msgstr "Tümü"
@@ -6966,6 +7151,9 @@ msgstr ""
msgid "Data is still calculating..."
msgstr "Veri hala hesaplanıyor..."
+msgid "Datasource name not found"
+msgstr ""
+
msgid "Date"
msgstr ""
@@ -7146,6 +7334,9 @@ msgstr "Kaynak dalı sil"
msgid "Delete this attachment"
msgstr "Bu eki sil"
+msgid "Delete user list"
+msgstr ""
+
msgid "Delete variable"
msgstr "DeÄŸiÅŸkeni sil"
@@ -7462,6 +7653,9 @@ msgstr ""
msgid "Deploying to"
msgstr ""
+msgid "Deploying to AWS is easy with GitLab"
+msgstr ""
+
msgid "Deployment Frequency"
msgstr ""
@@ -7824,6 +8018,9 @@ msgstr ""
msgid "Dismissed on pipeline %{pipelineLink} at %{projectLink}"
msgstr ""
+msgid "Display alerts from all your monitoring tools directly within GitLab."
+msgstr ""
+
msgid "Display name"
msgstr ""
@@ -7881,9 +8078,6 @@ msgstr ""
msgid "Don't show again"
msgstr "Bir daha gösterme"
-msgid "Don't worry, you can access this tour by clicking on the help icon in the top right corner and choose <strong>Learn GitLab</strong>."
-msgstr ""
-
msgid "Done"
msgstr "Tamamlandı"
@@ -8010,6 +8204,9 @@ msgstr ""
msgid "Edit Release"
msgstr "Sürümü düzenle"
+msgid "Edit Slack integration"
+msgstr ""
+
msgid "Edit Snippet"
msgstr "Parçacığı Düzenle"
@@ -8049,6 +8246,9 @@ msgstr ""
msgid "Edit issues"
msgstr "Sorunları düzenle"
+msgid "Edit iteration"
+msgstr ""
+
msgid "Edit public deploy key"
msgstr "Genel dağıtım anahtarını düzenle"
@@ -8097,6 +8297,9 @@ msgstr ""
msgid "Email"
msgstr "E-posta"
+msgid "Email Notification"
+msgstr ""
+
msgid "Email address"
msgstr "E-posta adresi"
@@ -8109,6 +8312,9 @@ msgstr ""
msgid "Email not verified. Please verify your email in Salesforce."
msgstr ""
+msgid "Email notification for unknown sign-ins"
+msgstr ""
+
msgid "Email patch"
msgstr "E-posta yaması"
@@ -8340,6 +8546,9 @@ msgstr ""
msgid "Enforce DNS rebinding attack protection"
msgstr ""
+msgid "Enforce personal access token expiration"
+msgstr ""
+
msgid "Ensure connectivity is available from the GitLab server to the Prometheus server"
msgstr ""
@@ -8409,6 +8618,9 @@ msgstr "Birleştirme isteğinin başlığını girin"
msgid "Enter the name of your application, and we'll return a unique %{type}."
msgstr ""
+msgid "Enter weights for storages for new repositories."
+msgstr ""
+
msgid "Enter your password to approve"
msgstr ""
@@ -8418,15 +8630,27 @@ msgstr ""
msgid "Environment does not have deployments"
msgstr ""
+msgid "Environment is required for Stages::MetricEndpointInserter"
+msgstr ""
+
+msgid "Environment is required for Stages::VariableEndpointInserter"
+msgstr ""
+
msgid "Environment scope"
msgstr ""
+msgid "Environment variables are applied to all project environments in this instance via the Runner. You can use environment variables for passwords, secret keys, etc. Make variables available to the running application by prepending the variable key with <code>K8S_SECRET_</code>. You can set variables to be:"
+msgstr ""
+
msgid "Environment variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. Additionally, they can be masked so they are hidden in job logs, though they must match certain regexp requirements to do so. You can use environment variables for passwords, secret keys, or whatever you want."
msgstr ""
msgid "Environment variables are configured by your administrator to be %{link_start}protected%{link_end} by default"
msgstr ""
+msgid "Environment variables on this GitLab instance are configured to be %{link_start}protected%{link_end} by default"
+msgstr ""
+
msgid "Environment:"
msgstr ""
@@ -8958,6 +9182,9 @@ msgstr "Tümünü süz"
msgid "EventFilterBy|Filter by comments"
msgstr "Yorumlara göre süz"
+msgid "EventFilterBy|Filter by designs"
+msgstr ""
+
msgid "EventFilterBy|Filter by epic events"
msgstr ""
@@ -9090,15 +9317,15 @@ msgstr "Tümünü genişlet"
msgid "Expand approvers"
msgstr "Onaylayanları genişlet"
-msgid "Expand child epics"
-msgstr ""
-
msgid "Expand down"
msgstr "Aşağı doğru genişlet"
msgid "Expand dropdown"
msgstr "Açılır listeyi genişlet"
+msgid "Expand milestones"
+msgstr ""
+
msgid "Expand sidebar"
msgstr "Kenar çubuğunu genişlet"
@@ -9114,6 +9341,9 @@ msgstr "Süre sonu"
msgid "Expiration date"
msgstr ""
+msgid "Expiration not enforced"
+msgstr ""
+
msgid "Expiration policy for the Container Registry is a perfect solution for keeping the Registry space down while still enjoying the full power of GitLab CI/CD."
msgstr ""
@@ -9234,6 +9464,9 @@ msgstr ""
msgid "Failed Jobs"
msgstr ""
+msgid "Failed on"
+msgstr ""
+
msgid "Failed to add a Zoom meeting"
msgstr ""
@@ -9294,13 +9527,19 @@ msgstr ""
msgid "Failed to install."
msgstr ""
+msgid "Failed to load assignees. Please try again."
+msgstr ""
+
+msgid "Failed to load authors. Please try again."
+msgstr ""
+
msgid "Failed to load emoji list."
msgstr ""
msgid "Failed to load error details from Sentry."
msgstr ""
-msgid "Failed to load errors from Sentry. Error message: %{errorMessage}"
+msgid "Failed to load errors from Sentry."
msgstr ""
msgid "Failed to load group activity metrics. Please try again."
@@ -9381,6 +9620,9 @@ msgstr ""
msgid "Failed to set due date because the date format is invalid."
msgstr ""
+msgid "Failed to set iteration on this issue. Please try again."
+msgstr ""
+
msgid "Failed to signing using smartcard authentication"
msgstr ""
@@ -9603,13 +9845,10 @@ msgstr ""
msgid "FeatureFlags|Target environments"
msgstr "Hedef ortamlar"
-msgid "FeatureFlags|There are no active feature flags"
-msgstr ""
-
-msgid "FeatureFlags|There are no inactive feature flags"
+msgid "FeatureFlags|There was an error fetching the feature flags."
msgstr ""
-msgid "FeatureFlags|There was an error fetching the feature flags."
+msgid "FeatureFlags|There was an error retrieving user lists"
msgstr ""
msgid "FeatureFlags|Try again in a few moments or contact your support team."
@@ -9621,9 +9860,18 @@ msgstr "Kullanıcı Kimlikleri"
msgid "FeatureFlag|Delete strategy"
msgstr ""
+msgid "FeatureFlag|List"
+msgstr ""
+
msgid "FeatureFlag|Percentage"
msgstr ""
+msgid "FeatureFlag|Select a user list"
+msgstr ""
+
+msgid "FeatureFlag|There are no configured user lists"
+msgstr ""
+
msgid "FeatureFlag|Type"
msgstr ""
@@ -9780,6 +10028,12 @@ msgstr "Projelerinizi isme göre filtreleyin"
msgid "Filter..."
msgstr "Süzgeç..."
+msgid "Find File"
+msgstr ""
+
+msgid "Find bugs in your code with coverage-guided fuzzing"
+msgstr ""
+
msgid "Find by path"
msgstr "Yolu bul"
@@ -10044,6 +10298,9 @@ msgstr "CoÄŸrafi Ayarlar"
msgid "Geo allows you to replicate your GitLab instance to other geographical locations."
msgstr ""
+msgid "Geo nodes are paused using a command run on the node"
+msgstr ""
+
msgid "GeoNodeStatusEvent|%{timeAgoStr} (%{pendingEvents} events)"
msgstr ""
@@ -10128,6 +10385,9 @@ msgstr ""
msgid "GeoNodes|Not checksummed"
msgstr ""
+msgid "GeoNodes|Package files"
+msgstr ""
+
msgid "GeoNodes|Pausing replication stops the sync process. Are you sure?"
msgstr ""
@@ -10146,6 +10406,9 @@ msgstr ""
msgid "GeoNodes|Replication slots"
msgstr ""
+msgid "GeoNodes|Replication status"
+msgstr ""
+
msgid "GeoNodes|Repositories"
msgstr "Depolar"
@@ -10356,6 +10619,9 @@ msgstr ""
msgid "Geo|Synchronization failed - %{error}"
msgstr ""
+msgid "Geo|Synchronization of %{itemTitle} is disabled."
+msgstr ""
+
msgid "Geo|The database is currently %{db_lag} behind the primary node."
msgstr ""
@@ -10668,6 +10934,9 @@ msgstr "Tam ekrana git"
msgid "Go to %{link_to_google_takeout}."
msgstr ""
+msgid "Go to <strong>Issues</strong> > <strong>Boards</strong> to access your personalized learning issue board."
+msgstr ""
+
msgid "Go to Pipelines"
msgstr ""
@@ -10767,9 +11036,6 @@ msgstr "Projelerinize gidin"
msgid "Go to your snippets"
msgstr "Parçacıklarınıza gidin"
-msgid "Golden Tanuki"
-msgstr ""
-
msgid "Google Cloud Platform"
msgstr "Google Cloud Platformu"
@@ -10791,6 +11057,9 @@ msgstr ""
msgid "Grafana URL"
msgstr ""
+msgid "Grafana response contains invalid json"
+msgstr ""
+
msgid "GrafanaIntegration|API Token"
msgstr ""
@@ -10839,9 +11108,6 @@ msgstr ""
msgid "Group %{group_name} was successfully created."
msgstr ""
-msgid "Group '%{group_name}' is being imported."
-msgstr ""
-
msgid "Group Audit Events"
msgstr ""
@@ -10878,9 +11144,6 @@ msgstr "Grup profil resmi"
msgid "Group by:"
msgstr ""
-msgid "Group could not be imported: %{errors}"
-msgstr ""
-
msgid "Group description"
msgstr "Grubun açıklaması"
@@ -10908,9 +11171,15 @@ msgstr "Grup zaten silinmek üzere işaretlendi"
msgid "Group has not been marked for deletion"
msgstr "Grup silinmek üzere işaretlenmedi"
+msgid "Group import could not be scheduled"
+msgstr ""
+
msgid "Group info:"
msgstr ""
+msgid "Group is required when cluster_type is :group"
+msgstr ""
+
msgid "Group maintainers can register group runners in the %{link}"
msgstr "Grup sorumluları, grup çalıştırıcılarını %{link} içinde kaydedebilir"
@@ -10974,6 +11243,24 @@ msgstr ""
msgid "GroupActivyMetrics|Recent activity (last 90 days)"
msgstr ""
+msgid "GroupImport|Failed to import group."
+msgstr ""
+
+msgid "GroupImport|Group '%{group_name}' is being imported."
+msgstr ""
+
+msgid "GroupImport|Group could not be imported: %{errors}"
+msgstr ""
+
+msgid "GroupImport|Please wait while we import the group for you. Refresh at will."
+msgstr ""
+
+msgid "GroupImport|The group was successfully imported."
+msgstr ""
+
+msgid "GroupImport|Unable to process group import file"
+msgstr ""
+
msgid "GroupRoadmap|%{dateWord} – No end date"
msgstr ""
@@ -11241,6 +11528,9 @@ msgstr ""
msgid "Groups (%{groups})"
msgstr ""
+msgid "Groups and projects"
+msgstr ""
+
msgid "Groups can also be nested by creating %{subgroup_docs_link_start}subgroups%{subgroup_docs_link_end}."
msgstr ""
@@ -11286,6 +11576,33 @@ msgstr "Grup bulunamadı"
msgid "GroupsEmptyState|You can manage your group member’s permissions and access to each project in the group."
msgstr ""
+msgid "GroupsNew|Contact an administrator to enable options for importing your group."
+msgstr ""
+
+msgid "GroupsNew|Create"
+msgstr ""
+
+msgid "GroupsNew|Create group"
+msgstr ""
+
+msgid "GroupsNew|GitLab group export"
+msgstr ""
+
+msgid "GroupsNew|Import"
+msgstr ""
+
+msgid "GroupsNew|Import group"
+msgstr ""
+
+msgid "GroupsNew|My Awesome Group"
+msgstr ""
+
+msgid "GroupsNew|No import options available"
+msgstr ""
+
+msgid "GroupsNew|To copy a GitLab group between installations, navigate to the group settings page for the original installation, generate an export file, and upload it here."
+msgstr ""
+
msgid "GroupsTree|Are you sure you want to leave the \"%{fullName}\" group?"
msgstr ""
@@ -11388,6 +11705,9 @@ msgstr ""
msgid "Helps reduce request volume for protected paths"
msgstr ""
+msgid "Here are all your projects in your group, including the one you just created. To start, let’s take a look at your personalized learning project which will help you learn about GitLab at your own pace."
+msgstr ""
+
msgid "Here you will find recent merge request activity"
msgstr ""
@@ -11402,6 +11722,9 @@ msgid_plural "Hide charts"
msgstr[0] ""
msgstr[1] ""
+msgid "Hide details"
+msgstr ""
+
msgid "Hide file browser"
msgstr "Dosya tarayıcısını gizle"
@@ -11411,6 +11734,9 @@ msgstr "Grup projelerini gizle"
msgid "Hide host keys manual input"
msgstr ""
+msgid "Hide list"
+msgstr ""
+
msgid "Hide marketing-related entries from help"
msgstr ""
@@ -11431,9 +11757,6 @@ msgstr[1] "DeÄŸerleri gizle"
msgid "Hide values"
msgstr "DeÄŸerleri gizle"
-msgid "Hiding all labels"
-msgstr ""
-
msgid "High or unknown vulnerabilities present"
msgstr ""
@@ -11518,6 +11841,9 @@ msgstr ""
msgid "ID:"
msgstr ""
+msgid "IDE"
+msgstr ""
+
msgid "IDE|Allow live previews of JavaScript projects in the Web IDE using CodeSandbox Live Preview."
msgstr ""
@@ -11608,10 +11934,10 @@ msgstr ""
msgid "If enabled"
msgstr ""
-msgid "If enabled, access to projects will be validated on an external service using their classification label."
+msgid "If enabled, GitLab will handle Object Storage replication using Geo. %{linkStart}More information%{linkEnd}"
msgstr ""
-msgid "If enabled, and if object storage is enabled, GitLab will handle Object Storage replication using Geo"
+msgid "If enabled, access to projects will be validated on an external service using their classification label."
msgstr ""
msgid "If there is no previous license or if the previous license has expired, some GitLab functionality will be blocked until a new, valid license is uploaded."
@@ -11973,6 +12299,9 @@ msgid_plural "Instances"
msgstr[0] ""
msgstr[1] ""
+msgid "Instance Configuration"
+msgstr ""
+
msgid "Instance Statistics visibility"
msgstr ""
@@ -11994,9 +12323,6 @@ msgstr ""
msgid "Integrations"
msgstr "Entegrasyonlar"
-msgid "Integrations allow you to integrate GitLab with other applications"
-msgstr ""
-
msgid "Integrations|All details"
msgstr ""
@@ -12108,6 +12434,9 @@ msgstr ""
msgid "Invalid query"
msgstr ""
+msgid "Invalid repository bundle for snippet with id %{snippet_id}"
+msgstr ""
+
msgid "Invalid repository path"
msgstr "Geçersiz depo yolu"
@@ -12126,6 +12455,9 @@ msgstr ""
msgid "Invalid two-factor code."
msgstr "Geçersiz iki adımlı doğrulama kodu."
+msgid "Invalid yaml"
+msgstr ""
+
msgid "Invitation"
msgstr ""
@@ -12345,15 +12677,24 @@ msgstr ""
msgid "It seems like the Dependency Scanning job ran successfully, but no dependencies have been detected in your project."
msgstr ""
+msgid "It seems that there is currently no available data for code coverage"
+msgstr ""
+
msgid "It's you"
msgstr "Bu sensin"
+msgid "Iteration"
+msgstr ""
+
msgid "Iteration changed to"
msgstr ""
msgid "Iteration removed"
msgstr ""
+msgid "Iteration updated"
+msgstr ""
+
msgid "Iterations"
msgstr ""
@@ -12384,12 +12725,18 @@ msgstr "Oca"
msgid "January"
msgstr "Ocak"
+msgid "Jira Issues"
+msgstr ""
+
msgid "Jira import is already running."
msgstr ""
msgid "Jira integration not configured."
msgstr ""
+msgid "Jira project key is not configured"
+msgstr ""
+
msgid "Jira project: %{importProject}"
msgstr ""
@@ -12570,6 +12917,9 @@ msgstr ""
msgid "Keep divergent refs"
msgstr ""
+msgid "Kerberos access denied"
+msgstr ""
+
msgid "Key"
msgstr ""
@@ -12579,12 +12929,15 @@ msgstr ""
msgid "Key: %{key}"
msgstr ""
-msgid "Keyboard Shortcuts"
-msgstr "Klavye Kısayolları"
-
msgid "Keyboard shortcuts"
msgstr "Klavye kısayolları"
+msgid "Keys"
+msgstr ""
+
+msgid "Ki"
+msgstr ""
+
msgid "Kubernetes"
msgstr ""
@@ -12624,6 +12977,9 @@ msgstr ""
msgid "LDAP"
msgstr ""
+msgid "LDAP Synchronization"
+msgstr ""
+
msgid "LDAP settings"
msgstr "LDAP ayarları"
@@ -12848,12 +13204,18 @@ msgstr "Onaylar hakkında daha fazla bilgi edinin."
msgid "Learn more about custom project templates"
msgstr ""
+msgid "Learn more about deploying to AWS"
+msgstr ""
+
msgid "Learn more about deploying to a cluster"
msgstr ""
msgid "Learn more about group-level project templates"
msgstr ""
+msgid "Learn more about job dependencies"
+msgstr ""
+
msgid "Learn more about signing commits"
msgstr ""
@@ -12905,9 +13267,21 @@ msgstr ""
msgid "License History"
msgstr ""
+msgid "License ID:"
+msgstr ""
+
+msgid "License URL"
+msgstr ""
+
msgid "License-Check"
msgstr ""
+msgid "LicenseCompliance|%{docLinkStart}License Approvals%{docLinkEnd} are active"
+msgstr ""
+
+msgid "LicenseCompliance|%{docLinkStart}License Approvals%{docLinkEnd} are inactive"
+msgstr ""
+
msgid "LicenseCompliance|Add a license"
msgstr "Bir lisans ekle"
@@ -12929,9 +13303,15 @@ msgstr ""
msgid "LicenseCompliance|Deny"
msgstr ""
+msgid "LicenseCompliance|Learn more about %{linkStart}License Approvals%{linkEnd}"
+msgstr ""
+
msgid "LicenseCompliance|License"
msgstr "Lisans"
+msgid "LicenseCompliance|License Approvals"
+msgstr ""
+
msgid "LicenseCompliance|License Compliance detected %d license and policy violation for the source branch only; approval required"
msgid_plural "LicenseCompliance|License Compliance detected %d licenses and policy violations for the source branch only; approval required"
msgstr[0] ""
@@ -13152,6 +13532,9 @@ msgstr ""
msgid "List your Bitbucket Server repositories"
msgstr ""
+msgid "Lists"
+msgstr ""
+
msgid "Live preview"
msgstr "Canlı önizleme"
@@ -13335,6 +13718,9 @@ msgstr ""
msgid "Manage project labels"
msgstr "Proje etiketlerini yönet"
+msgid "Manage storage usage"
+msgstr ""
+
msgid "Manage two-factor authentication"
msgstr ""
@@ -13350,6 +13736,9 @@ msgstr ""
msgid "Manifest file import"
msgstr ""
+msgid "Manifest import"
+msgstr ""
+
msgid "Manual job"
msgstr ""
@@ -13497,6 +13886,12 @@ msgstr ""
msgid "Maximum field length"
msgstr ""
+msgid "Maximum file size is 2MB. Please select a smaller file."
+msgstr ""
+
+msgid "Maximum import size (MB)"
+msgstr ""
+
msgid "Maximum job timeout"
msgstr ""
@@ -13536,6 +13931,9 @@ msgstr ""
msgid "Maximum size of Elasticsearch bulk indexing requests."
msgstr ""
+msgid "Maximum size of import files."
+msgstr ""
+
msgid "Maximum size of individual attachments in comments."
msgstr ""
@@ -13587,6 +13985,9 @@ msgstr ""
msgid "Merge (when the pipeline succeeds)"
msgstr ""
+msgid "Merge Conflicts"
+msgstr ""
+
msgid "Merge Request"
msgstr "BirleÅŸtirme Ä°steÄŸi"
@@ -13713,6 +14114,9 @@ msgstr "Yorum kaydedilemedi"
msgid "MergeRequests|Squash task canceled: another squash is already in progress."
msgstr ""
+msgid "MergeRequests|This project does not allow squashing commits when merge requests are accepted."
+msgstr ""
+
msgid "MergeRequests|Thread stays resolved"
msgstr ""
@@ -13866,6 +14270,12 @@ msgstr ""
msgid "MetricsSettings|Add a button to the metrics dashboard linking directly to your existing external dashboard."
msgstr ""
+msgid "MetricsSettings|Choose whether to display dashboard metrics in UTC or the user's local timezone."
+msgstr ""
+
+msgid "MetricsSettings|Dashboard timezone"
+msgstr ""
+
msgid "MetricsSettings|External dashboard URL"
msgstr ""
@@ -13875,6 +14285,12 @@ msgstr ""
msgid "MetricsSettings|Metrics Dashboard"
msgstr ""
+msgid "MetricsSettings|UTC (Coordinated Universal Time)"
+msgstr ""
+
+msgid "MetricsSettings|User's local timezone"
+msgstr ""
+
msgid "Metrics|Add metric"
msgstr "Metrik ekle"
@@ -13890,6 +14306,9 @@ msgstr ""
msgid "Metrics|Create metric"
msgstr "Metrik oluÅŸtur"
+msgid "Metrics|Current"
+msgstr ""
+
msgid "Metrics|Delete metric"
msgstr ""
@@ -13940,6 +14359,9 @@ msgstr ""
msgid "Metrics|Max"
msgstr ""
+msgid "Metrics|Min"
+msgstr ""
+
msgid "Metrics|Must be a valid PromQL query."
msgstr ""
@@ -13955,6 +14377,9 @@ msgstr ""
msgid "Metrics|Refresh dashboard"
msgstr ""
+msgid "Metrics|Select a value"
+msgstr ""
+
msgid "Metrics|Star dashboard"
msgstr ""
@@ -13973,12 +14398,18 @@ msgstr ""
msgid "Metrics|There was an error getting annotations information."
msgstr ""
+msgid "Metrics|There was an error getting dashboard validation warnings information."
+msgstr ""
+
msgid "Metrics|There was an error getting deployment information."
msgstr ""
msgid "Metrics|There was an error getting environments information."
msgstr ""
+msgid "Metrics|There was an error getting options for variable \"%{name}\"."
+msgstr ""
+
msgid "Metrics|There was an error trying to validate your query"
msgstr ""
@@ -14021,6 +14452,9 @@ msgstr ""
msgid "Metrics|You're about to permanently delete this metric. This cannot be undone."
msgstr ""
+msgid "Metrics|Your dashboard schema is invalid. Edit the dashboard to correct the YAML schema."
+msgstr ""
+
msgid "Metrics|e.g. HTTP requests"
msgstr "ör. HTTP istekleri"
@@ -14036,12 +14470,18 @@ msgstr "ör.oran (http_requests_total[5m])"
msgid "Metrics|e.g. req/sec"
msgstr "ör. ist/sn"
+msgid "Mi"
+msgstr ""
+
msgid "Microsoft Azure"
msgstr "Microsoft Azure"
msgid "Middleman project with Static Site Editor support"
msgstr ""
+msgid "Migrate your data from an external source like GitHub, Bitbucket, or another instance of GitLab."
+msgstr ""
+
msgid "Migrated %{success_count}/%{total_count} files."
msgstr ""
@@ -14176,6 +14616,9 @@ msgstr ""
msgid "Minimum capacity to be available before we schedule more mirrors preemptively."
msgstr ""
+msgid "Minimum interval in days"
+msgstr ""
+
msgid "Minimum length is %{minimum_password_length} characters"
msgstr ""
@@ -14362,6 +14805,12 @@ msgstr ""
msgid "Multiple uploaders found: %{uploader_types}"
msgstr ""
+msgid "Must match with the %{codeStart}external_url%{codeEnd} in %{codeStart}/etc/gitlab/gitlab.rb%{codeEnd}."
+msgstr ""
+
+msgid "Must match with the %{codeStart}geo_node_name%{codeEnd} in %{codeStart}/etc/gitlab/gitlab.rb%{codeEnd}. %{linkStart}More information%{linkEnd}"
+msgstr ""
+
msgid "My Awesome Group"
msgstr ""
@@ -14476,10 +14925,16 @@ msgstr "Yeni"
msgid "New Application"
msgstr "Yeni Uygulama"
+msgid "New Branch"
+msgstr ""
+
+msgid "New Deploy Key"
+msgstr ""
+
msgid "New Environment"
msgstr "Yeni Ortam"
-msgid "New Geo Node"
+msgid "New File"
msgstr ""
msgid "New Group"
@@ -14496,15 +14951,15 @@ msgid_plural "New Issues"
msgstr[0] "Yeni Sorun"
msgstr[1] "Yeni Sorunlar"
-msgid "New Iteration"
-msgstr ""
-
msgid "New Jira import"
msgstr ""
msgid "New Label"
msgstr "Yeni Etiket"
+msgid "New Merge Request"
+msgstr ""
+
msgid "New Milestone"
msgstr "Yeni dönüm noktası"
@@ -14523,6 +14978,9 @@ msgstr ""
msgid "New Snippet"
msgstr "Yeni Parçacık"
+msgid "New User"
+msgstr ""
+
msgid "New branch"
msgstr "Yeni dal"
@@ -14565,6 +15023,9 @@ msgstr "Yeni sorun"
msgid "New issue title"
msgstr "Yeni sorun başlığı"
+msgid "New iteration"
+msgstr ""
+
msgid "New iteration created"
msgstr ""
@@ -14742,6 +15203,12 @@ msgstr "Kullanabileceğiniz çatal yok."
msgid "No grouping"
msgstr ""
+msgid "No iteration"
+msgstr ""
+
+msgid "No iterations to show"
+msgstr ""
+
msgid "No job log"
msgstr ""
@@ -14784,6 +15251,9 @@ msgstr "Gösterilecek dönüm noktası yok"
msgid "No other labels with such name or description"
msgstr ""
+msgid "No panels matching properties %{opts}"
+msgstr ""
+
msgid "No parent group"
msgstr ""
@@ -14817,6 +15287,9 @@ msgstr ""
msgid "No schedules"
msgstr ""
+msgid "No stack trace for this error"
+msgstr ""
+
msgid "No starrers matched your search"
msgstr ""
@@ -14829,7 +15302,7 @@ msgstr ""
msgid "No test coverage"
msgstr ""
-msgid "No thanks, don't show this again"
+msgid "No thanks"
msgstr ""
msgid "No vulnerabilities present"
@@ -14844,9 +15317,6 @@ msgstr ""
msgid "No, directly import the existing email addresses and usernames."
msgstr ""
-msgid "No, not interested right now"
-msgstr ""
-
msgid "No. of commits"
msgstr ""
@@ -14892,9 +15362,6 @@ msgstr ""
msgid "Not found."
msgstr ""
-msgid "Not helpful"
-msgstr ""
-
msgid "Not now"
msgstr ""
@@ -15051,6 +15518,9 @@ msgstr "Bildirimler kapalı"
msgid "Notifications on"
msgstr "Bildirimler açık"
+msgid "Notify users by email when sign-in location is not recognized"
+msgstr ""
+
msgid "Nov"
msgstr "Kas"
@@ -15117,9 +15587,6 @@ msgstr "Süzgeç"
msgid "Oh no!"
msgstr ""
-msgid "Ok let's go"
-msgstr ""
-
msgid "Oldest first"
msgstr "Önce en eski"
@@ -15132,16 +15599,49 @@ msgstr ""
msgid "On track"
msgstr ""
+msgid "OnDemandScans|Attached branch"
+msgstr ""
+
+msgid "OnDemandScans|Attached branch is where the scan job runs."
+msgstr ""
+
+msgid "OnDemandScans|Could not run the scan. Please try again."
+msgstr ""
+
msgid "OnDemandScans|Create new DAST scan"
msgstr ""
+msgid "OnDemandScans|DAST will scan the target URL and any discovered sub URLs."
+msgstr ""
+
+msgid "OnDemandScans|New on-demand DAST scan"
+msgstr ""
+
msgid "OnDemandScans|On-demand Scans"
msgstr ""
+msgid "OnDemandScans|On-demand scans run outside the DevOps cycle and find vulnerabilities in your projects. %{learnMoreLinkStart}Learn more%{learnMoreLinkEnd}"
+msgstr ""
+
+msgid "OnDemandScans|Only a passive scan can be performed on demand."
+msgstr ""
+
+msgid "OnDemandScans|Passive DAST Scan"
+msgstr ""
+
+msgid "OnDemandScans|Please enter a valid URL format, ex: http://www.example.com/home"
+msgstr ""
+
+msgid "OnDemandScans|Run this scan"
+msgstr ""
+
+msgid "OnDemandScans|Scan mode"
+msgstr ""
+
msgid "OnDemandScans|Schedule or run scans immediately against target sites. Currently available on-demand scan type: DAST. %{helpLinkStart}More information%{helpLinkEnd}"
msgstr ""
-msgid "Onboarding"
+msgid "OnDemandScans|Target URL"
msgstr ""
msgid "Once imported, repositories can be mirrored over SSH. Read more %{link_start}here%{link_end}."
@@ -15167,6 +15667,9 @@ msgstr ""
msgid "One or more of you personal access tokens were revoked"
msgstr ""
+msgid "One or more of your %{provider} projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
+msgstr ""
+
msgid "One or more of your Bitbucket projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
msgstr ""
@@ -15668,6 +16171,9 @@ msgstr ""
msgid "Parent epic is not present."
msgstr ""
+msgid "Parsing error for param :embed_json. %{message}"
+msgstr ""
+
msgid "Part of merge request changes"
msgstr ""
@@ -15680,6 +16186,9 @@ msgstr ""
msgid "Passed"
msgstr ""
+msgid "Passed on"
+msgstr ""
+
msgid "Password"
msgstr "Åžifre"
@@ -15764,6 +16273,9 @@ msgstr ""
msgid "Perform common operations on GitLab project"
msgstr ""
+msgid "Performance and resource management"
+msgstr ""
+
msgid "Performance optimization"
msgstr ""
@@ -15971,7 +16483,7 @@ msgstr ""
msgid "Pipelines|Something went wrong while cleaning runners cache."
msgstr ""
-msgid "Pipelines|There are currently no %{scope} pipelines."
+msgid "Pipelines|There are currently no finished pipelines."
msgstr ""
msgid "Pipelines|There are currently no pipelines."
@@ -16082,6 +16594,9 @@ msgstr ""
msgid "Pipeline|Stop pipeline #%{pipelineId}?"
msgstr ""
+msgid "Pipeline|Tag name"
+msgstr ""
+
msgid "Pipeline|Trigger author"
msgstr ""
@@ -16163,12 +16678,18 @@ msgstr ""
msgid "Please check your email (%{email}) to verify that you own this address and unlock the power of CI/CD. Didn't receive it? %{resend_link}. Wrong email address? %{update_link}."
msgstr ""
+msgid "Please choose a file"
+msgstr ""
+
msgid "Please choose a group URL with no special characters."
msgstr ""
msgid "Please complete your profile with email address"
msgstr ""
+msgid "Please convert %{linkStart}them to Git%{linkEnd}, and go through the %{linkToImportFlow} again."
+msgstr ""
+
msgid "Please convert them to %{link_to_git}, and go through the %{link_to_import_flow} again."
msgstr ""
@@ -16226,6 +16747,9 @@ msgstr ""
msgid "Please provide a valid email address."
msgstr ""
+msgid "Please provide attributes to update"
+msgstr ""
+
msgid "Please refer to <a href=\"%{docs_url}\">%{docs_url}</a>"
msgstr ""
@@ -16961,6 +17485,9 @@ msgstr ""
msgid "Project has too many %{label_for_message} to search"
msgstr ""
+msgid "Project is required when cluster_type is :project"
+msgstr ""
+
msgid "Project members"
msgstr "Proje üyeleri"
@@ -17078,7 +17605,40 @@ msgstr ""
msgid "ProjectService|Comment"
msgstr "Yorum"
-msgid "ProjectService|Comment will be posted on each event"
+msgid "ProjectService|Event will be triggered by a push to the repository"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a commit is created/updated"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a confidential issue is created/updated/closed"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a deployment finishes"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a merge request is created/updated/merged"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a new tag is pushed to the repository"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a new, unique alert is recorded"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a pipeline status changes"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a wiki page is created/updated"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when an issue is created/updated/closed"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when someone adds a comment"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when someone adds a comment on a confidential issue"
msgstr ""
msgid "ProjectService|Perform common operations on GitLab project: %{project_name}"
@@ -17129,6 +17689,9 @@ msgstr "E-posta bildirimlerini devre dışı bırak"
msgid "ProjectSettings|Enable 'Delete source branch' option by default"
msgstr ""
+msgid "ProjectSettings|Enable merge trains and pipelines for merged results"
+msgstr ""
+
msgid "ProjectSettings|Every merge creates a merge commit"
msgstr ""
@@ -17159,7 +17722,7 @@ msgstr ""
msgid "ProjectSettings|Forks"
msgstr "Çatallar"
-msgid "ProjectSettings|Git Large File Storage"
+msgid "ProjectSettings|Git Large File Storage (LFS)"
msgstr ""
msgid "ProjectSettings|Internal"
@@ -17195,9 +17758,6 @@ msgstr "Birleştirme yöntemi"
msgid "ProjectSettings|Merge options"
msgstr "Birleştirme seçenekleri"
-msgid "ProjectSettings|Merge pipelines will try to validate the post-merge result prior to merging"
-msgstr ""
-
msgid "ProjectSettings|Merge requests"
msgstr "BirleÅŸtirme istekleri"
@@ -17225,6 +17785,9 @@ msgstr "Proje belgeleme sayfaları"
msgid "ProjectSettings|Pipelines"
msgstr "İş hatları"
+msgid "ProjectSettings|Pipelines for merge requests must be enabled in the CI/CD configuration file, or pipelines could be unresolvable or dropped"
+msgstr ""
+
msgid "ProjectSettings|Pipelines must succeed"
msgstr ""
@@ -17252,6 +17815,9 @@ msgstr ""
msgid "ProjectSettings|Show link to create/view merge request when pushing from the command line"
msgstr ""
+msgid "ProjectSettings|Skipped pipelines are considered successful"
+msgstr ""
+
msgid "ProjectSettings|Snippets"
msgstr "Parçacıklar"
@@ -17267,6 +17833,9 @@ msgstr ""
msgid "ProjectSettings|These checks must pass before merge requests can be merged"
msgstr ""
+msgid "ProjectSettings|This introduces the risk of merging changes that will not pass the pipeline."
+msgstr ""
+
msgid "ProjectSettings|This setting is applied on the server level and can be overridden by an admin."
msgstr ""
@@ -17444,15 +18013,27 @@ msgstr "BoÅŸ"
msgid "ProjectsNew|Blank project"
msgstr "BoÅŸ proje"
+msgid "ProjectsNew|Connect your external repository to GitLab CI/CD."
+msgstr ""
+
msgid "ProjectsNew|Contact an administrator to enable options for importing your project."
msgstr ""
msgid "ProjectsNew|Create"
msgstr ""
+msgid "ProjectsNew|Create a blank project to house your files, plan your work, and collaborate on code, among other things."
+msgstr ""
+
+msgid "ProjectsNew|Create blank project"
+msgstr ""
+
msgid "ProjectsNew|Create from template"
msgstr "Åžablondan oluÅŸtur"
+msgid "ProjectsNew|Create new project"
+msgstr ""
+
msgid "ProjectsNew|Creating project & repository."
msgstr "Proje ve depo oluÅŸturma."
@@ -17477,6 +18058,9 @@ msgstr ""
msgid "ProjectsNew|Project description %{tag_start}(optional)%{tag_end}"
msgstr "Proje açıklaması %{tag_start}(isteğe bağlı)%{tag_end}"
+msgid "ProjectsNew|Run CI/CD for external repository"
+msgstr ""
+
msgid "ProjectsNew|Template"
msgstr "Åžablon"
@@ -17738,6 +18322,9 @@ msgstr ""
msgid "Protected Branch"
msgstr ""
+msgid "Protected Branches"
+msgstr ""
+
msgid "Protected Environment"
msgstr ""
@@ -17750,6 +18337,9 @@ msgstr "Korunan Yollar"
msgid "Protected Tag"
msgstr ""
+msgid "Protected Tags"
+msgstr ""
+
msgid "Protected branches"
msgstr "Korunan dallar"
@@ -17849,6 +18439,9 @@ msgstr ""
msgid "Provider"
msgstr ""
+msgid "Proxy support for this API is not available currently"
+msgstr ""
+
msgid "Pseudonymizer data collection"
msgstr ""
@@ -17873,6 +18466,9 @@ msgstr ""
msgid "Publish to status page"
msgstr ""
+msgid "Published on status page"
+msgstr ""
+
msgid "Publishes this issue to the associated status page."
msgstr ""
@@ -18047,6 +18643,9 @@ msgstr "Son aramalar"
msgid "Recipe"
msgstr ""
+msgid "Reconfigure"
+msgstr ""
+
msgid "Recover hidden stage"
msgstr ""
@@ -18133,6 +18732,9 @@ msgstr ""
msgid "Registration|Your profile"
msgstr ""
+msgid "Regulate approvals by authors/committers, based on compliance frameworks. Can be changed only at the instance level."
+msgstr ""
+
msgid "Rejected (closed)"
msgstr ""
@@ -18183,15 +18785,24 @@ msgstr ""
msgid "ReleaseAssetLinkType|Image"
msgstr ""
+msgid "ReleaseAssetLinkType|Images"
+msgstr ""
+
msgid "ReleaseAssetLinkType|Other"
msgstr ""
msgid "ReleaseAssetLinkType|Package"
msgstr ""
+msgid "ReleaseAssetLinkType|Packages"
+msgstr ""
+
msgid "ReleaseAssetLinkType|Runbook"
msgstr ""
+msgid "ReleaseAssetLinkType|Runbooks"
+msgstr ""
+
msgid "Releases"
msgstr "Sürümler"
@@ -18204,6 +18815,9 @@ msgstr ""
msgid "Releases documentation"
msgstr ""
+msgid "Releases|New Release"
+msgstr ""
+
msgid "Release|Something went wrong while getting the release details"
msgstr ""
@@ -18276,6 +18890,9 @@ msgstr "Bitiş tarihini kaldır"
msgid "Remove fork relationship"
msgstr "Çatal ilişkisini kaldır"
+msgid "Remove from batch"
+msgstr ""
+
msgid "Remove from board"
msgstr ""
@@ -18465,6 +19082,12 @@ msgstr ""
msgid "Replication"
msgstr ""
+msgid "Replication enabled"
+msgstr ""
+
+msgid "Replication paused"
+msgstr ""
+
msgid "Reply by email"
msgstr ""
@@ -18492,15 +19115,15 @@ msgstr "Kötüye kullanımı yöneticiye bildir"
msgid "Reported %{timeAgo} by %{reportedBy}"
msgstr "%{reportedBy} tarafından %{timeAgo} bildirildi"
-msgid "Reported Resource Changes: %{addNum} to add, %{changeNum} to change, %{deleteNum} to delete"
-msgstr ""
-
msgid "Reporter"
msgstr ""
msgid "Reporting"
msgstr ""
+msgid "Reports"
+msgstr ""
+
msgid "Reports|%{combinedString} and %{resolvedString}"
msgstr ""
@@ -18539,6 +19162,9 @@ msgstr ""
msgid "Reports|Failure"
msgstr ""
+msgid "Reports|Identifier"
+msgstr ""
+
msgid "Reports|Metrics reports are loading"
msgstr ""
@@ -18551,6 +19177,9 @@ msgstr "Metriklerin raporları değişmedi"
msgid "Reports|Metrics reports failed loading results"
msgstr ""
+msgid "Reports|Scanner"
+msgstr ""
+
msgid "Reports|Severity"
msgstr ""
@@ -18599,15 +19228,33 @@ msgstr ""
msgid "Repository cleanup has started. You will receive an email once the cleanup operation is complete."
msgstr ""
+msgid "Repository files count over the limit"
+msgstr ""
+
+msgid "Repository has an invalid default branch name."
+msgstr ""
+
+msgid "Repository has more than one branch."
+msgstr ""
+
msgid "Repository has no locks."
msgstr "Depo kilidi yok."
+msgid "Repository has tags."
+msgstr ""
+
msgid "Repository maintenance"
msgstr ""
msgid "Repository mirroring"
msgstr ""
+msgid "Repository must contain at least 1 file."
+msgstr ""
+
+msgid "Repository size is above the limit."
+msgstr ""
+
msgid "Repository static objects"
msgstr ""
@@ -18617,7 +19264,7 @@ msgstr ""
msgid "Repository sync capacity"
msgstr ""
-msgid "Repository: %{counter_repositories} / Wikis: %{counter_wikis} / Build Artifacts: %{counter_build_artifacts} / LFS: %{counter_lfs_objects}"
+msgid "Repository: %{counter_repositories} / Wikis: %{counter_wikis} / Build Artifacts: %{counter_build_artifacts} / LFS: %{counter_lfs_objects} / Snippets: %{counter_snippets}"
msgstr ""
msgid "RepositorySettingsAccessLevel|Select"
@@ -18629,6 +19276,9 @@ msgstr ""
msgid "Request Headers"
msgstr ""
+msgid "Request details"
+msgstr ""
+
msgid "Request parameter %{param} is missing."
msgstr ""
@@ -18735,6 +19385,9 @@ msgstr ""
msgid "Reset template"
msgstr ""
+msgid "Reset to project defaults"
+msgstr ""
+
msgid "Resetting the authorization key for this project will require updating the authorization key in every alert source it is enabled in."
msgstr ""
@@ -18831,9 +19484,6 @@ msgstr ""
msgid "Resume"
msgstr ""
-msgid "Resume replication"
-msgstr ""
-
msgid "Resync"
msgstr ""
@@ -19456,6 +20106,9 @@ msgstr ""
msgid "SecurityReports|False positive"
msgstr ""
+msgid "SecurityReports|Group Security Dashboard"
+msgstr ""
+
msgid "SecurityReports|Hide dismissed"
msgstr ""
@@ -19510,10 +20163,13 @@ msgstr ""
msgid "SecurityReports|Remove project from dashboard"
msgstr ""
-msgid "SecurityReports|Report type"
+msgid "SecurityReports|Return to dashboard"
msgstr ""
-msgid "SecurityReports|Return to dashboard"
+msgid "SecurityReports|Scan details"
+msgstr ""
+
+msgid "SecurityReports|Scanner type"
msgstr ""
msgid "SecurityReports|Security Dashboard"
@@ -19570,6 +20226,9 @@ msgstr ""
msgid "SecurityReports|There was an error while generating the report."
msgstr ""
+msgid "SecurityReports|Unable to add %{invalidProjectsMessage}"
+msgstr ""
+
msgid "SecurityReports|Unable to add %{invalidProjects}"
msgstr ""
@@ -19684,6 +20343,9 @@ msgstr ""
msgid "Select due date"
msgstr ""
+msgid "Select file"
+msgstr ""
+
msgid "Select group or project"
msgstr ""
@@ -19747,9 +20409,6 @@ msgstr "Hedef dalı seç"
msgid "Select the branch you want to set as the default for this project. All merge requests and commits will automatically be made against this branch unless you specify a different one."
msgstr ""
-msgid "Select the configured storage available for new repositories to be placed on."
-msgstr ""
-
msgid "Select the custom project template source group."
msgstr "Özel proje şablonu kaynak grubunu seçin."
@@ -19942,12 +20601,6 @@ msgstr "Servis Masası"
msgid "Service Desk is enabled but not yet active"
msgstr ""
-msgid "Service Desk is off"
-msgstr "Servis Masası kapalı"
-
-msgid "Service Desk is on"
-msgstr "Servis Masası açık"
-
msgid "Service Templates"
msgstr ""
@@ -20035,6 +20688,15 @@ msgstr ""
msgid "Set the milestone to %{milestone_reference}."
msgstr ""
+msgid "Set the number of concurrent requests this secondary node will make to the primary node while backfilling."
+msgstr ""
+
+msgid "Set the synchronization and verification capacity for the secondary node."
+msgstr ""
+
+msgid "Set the timeout in seconds to send a secondary node status to the primary and IPs allowed for the secondary nodes."
+msgstr ""
+
msgid "Set time estimate"
msgstr ""
@@ -20074,6 +20736,9 @@ msgstr ""
msgid "Set weight to %{weight}."
msgstr ""
+msgid "Set what should be replicated by choosing specific projects or groups by the secondary node."
+msgstr ""
+
msgid "SetPasswordToCloneLink|set a password"
msgstr "ÅŸifre ayarla"
@@ -20200,6 +20865,9 @@ msgstr ""
msgid "Show complete raw log"
msgstr ""
+msgid "Show details"
+msgstr ""
+
msgid "Show file browser"
msgstr ""
@@ -20209,10 +20877,13 @@ msgstr ""
msgid "Show latest version"
msgstr ""
+msgid "Show list"
+msgstr ""
+
msgid "Show me everything"
msgstr ""
-msgid "Show me how"
+msgid "Show me how to add a pipeline"
msgstr ""
msgid "Show me more advanced stuff"
@@ -20261,9 +20932,6 @@ msgstr "#%{versionNumber} Sürümü Gösteriliyor"
msgid "Showing all issues"
msgstr "Tüm sorunlar gösteriliyor"
-msgid "Showing all labels"
-msgstr "Tüm etiketler gösteriliyor"
-
msgid "Showing last %{size} of log -"
msgstr ""
@@ -20375,9 +21043,6 @@ msgstr ""
msgid "Skip outdated deployment jobs"
msgstr ""
-msgid "Skip this for now"
-msgstr ""
-
msgid "Skipped"
msgstr ""
@@ -20522,6 +21187,9 @@ msgstr ""
msgid "Something went wrong while adding your award. Please try again."
msgstr ""
+msgid "Something went wrong while applying the batch of suggestions. Please try again."
+msgstr ""
+
msgid "Something went wrong while applying the suggestion. Please try again."
msgstr ""
@@ -20624,10 +21292,13 @@ msgstr ""
msgid "Something went wrong while updating a requirement."
msgstr ""
+msgid "Something went wrong while updating assignees"
+msgstr ""
+
msgid "Something went wrong while updating your list settings"
msgstr ""
-msgid "Something went wrong with your automatic subscription renewal"
+msgid "Something went wrong with your automatic subscription renewal."
msgstr ""
msgid "Something went wrong, unable to add %{project} to dashboard"
@@ -20645,6 +21316,9 @@ msgstr ""
msgid "Something went wrong, unable to search projects"
msgstr "Bir şeyler ters gitti, projeler aranamıyor"
+msgid "Something went wrong."
+msgstr ""
+
msgid "Something went wrong. Please try again."
msgstr ""
@@ -20828,6 +21502,9 @@ msgstr "Kaynak (dal veya etiket)"
msgid "Source code"
msgstr "Kaynak kodu"
+msgid "Source code (%{fileExtension})"
+msgstr ""
+
msgid "Source is not available"
msgstr ""
@@ -20912,6 +21589,9 @@ msgstr ""
msgid "Stack trace"
msgstr ""
+msgid "Stacktrace snippet"
+msgstr ""
+
msgid "Stage"
msgstr "AÅŸama"
@@ -21017,6 +21697,9 @@ msgstr ""
msgid "Start thread & reopen %{noteable_name}"
msgstr ""
+msgid "Start using Directed Acyclic Graphs (DAG)"
+msgstr ""
+
msgid "Start your Free Gold Trial"
msgstr "Ücretsiz Altın Denemesini Başlat"
@@ -21479,6 +22162,21 @@ msgstr ""
msgid "SuggestedColors|Very pale orange"
msgstr ""
+msgid "Suggestion is not applicable as the suggestion was not found."
+msgstr ""
+
+msgid "Suggestions are not applicable as one or more suggestions were not found."
+msgstr ""
+
+msgid "Suggestions are not applicable as their lines cannot overlap."
+msgstr ""
+
+msgid "Suggestions must all be on the same branch."
+msgstr ""
+
+msgid "Suggestions that change line count can't be added to batches, yet."
+msgstr ""
+
msgid "Suggestions:"
msgstr ""
@@ -21521,9 +22219,6 @@ msgstr ""
msgid "Synchronization disabled"
msgstr ""
-msgid "Synchronization of container repositories is disabled."
-msgstr ""
-
msgid "System"
msgstr "Sistem"
@@ -21704,6 +22399,34 @@ msgstr "Hizmet Koşulları Sözleşmesi ve Gizlilik Politikası"
msgid "Terms of Service and Privacy Policy"
msgstr "Hizmet Koşulları ve Gizlilik Politikası"
+msgid "Terraform|%{number} Terraform report failed to generate"
+msgid_plural "Terraform|%{number} Terraform reports failed to generate"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Terraform|%{number} Terraform report was generated in your pipelines"
+msgid_plural "Terraform|%{number} Terraform reports were generated in your pipelines"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Terraform|A Terraform report failed to generate."
+msgstr ""
+
+msgid "Terraform|A Terraform report was generated in your pipelines."
+msgstr ""
+
+msgid "Terraform|Generating the report caused an error."
+msgstr ""
+
+msgid "Terraform|Reported Resource Changes: %{addNum} to add, %{changeNum} to change, %{deleteNum} to delete"
+msgstr ""
+
+msgid "Terraform|The Terraform report %{name} failed to generate."
+msgstr ""
+
+msgid "Terraform|The Terraform report %{name} was generated in your pipelines."
+msgstr ""
+
msgid "Test"
msgstr ""
@@ -21730,7 +22453,7 @@ msgstr ""
msgid "TestHooks|Ensure the project has CI pipelines."
msgstr ""
-msgid "TestHooks|Ensure the project has at least one commit."
+msgid "TestHooks|Ensure the project has deployments."
msgstr ""
msgid "TestHooks|Ensure the project has issues."
@@ -21831,7 +22554,7 @@ msgstr ""
msgid "The Prometheus server responded with \"bad request\". Please check your queries are correct and are supported in your Prometheus version. %{documentationLink}"
msgstr ""
-msgid "The URL defined on the primary node that secondary nodes should use to contact it. Defaults to URL"
+msgid "The URL defined on the primary node that secondary nodes should use to contact it."
msgstr ""
msgid "The URL to use for connecting to Elasticsearch. Use a comma-separated list to support clustering (e.g., \"http://localhost:9200, http://localhost:9201\")."
@@ -21840,6 +22563,9 @@ msgstr ""
msgid "The X509 Certificate to use when mutual TLS is required to communicate with the external authorization service. If left blank, the server certificate is still validated when accessing over HTTPS."
msgstr ""
+msgid "The above settings apply to all projects with the selected compliance framework(s)."
+msgstr ""
+
msgid "The amount of seconds after which a request to get a secondary node status will time out."
msgstr "İkincil düğüm durumu alma isteğinin zaman aşımına uğrayacağı saniye cinsinden süre."
@@ -22139,9 +22865,6 @@ msgstr ""
msgid "The total stage shows the time it takes between creating an issue and deploying the code to production. The data will be automatically added once you have completed the full idea to production cycle."
msgstr ""
-msgid "The unique identifier for the Geo node. Must match %{geoNodeName} if it is set in gitlab.rb, otherwise it must match %{externalUrl} with a trailing slash"
-msgstr ""
-
msgid "The update action will time out after %{number_of_minutes} minutes. For big repositories, use a clone/push combination."
msgstr ""
@@ -22166,9 +22889,6 @@ msgstr ""
msgid "The user you are trying to deactivate has been active in the past %{minimum_inactive_days} days and cannot be deactivated"
msgstr ""
-msgid "The user-facing URL of the Geo node"
-msgstr ""
-
msgid "The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6."
msgstr ""
@@ -22259,6 +22979,9 @@ msgstr ""
msgid "There was a problem fetching project branches."
msgstr ""
+msgid "There was a problem fetching project tags."
+msgstr ""
+
msgid "There was a problem fetching project users."
msgstr ""
@@ -22310,6 +23033,9 @@ msgstr ""
msgid "There was an error fetching the %{replicableType}"
msgstr ""
+msgid "There was an error fetching the Geo Settings"
+msgstr ""
+
msgid "There was an error fetching the Node's Groups"
msgstr ""
@@ -22430,6 +23156,12 @@ msgstr ""
msgid "This %{issuable} is locked. Only <strong>project members</strong> can comment."
msgstr ""
+msgid "This %{noteableTypeText} is %{confidentialLinkStart}confidential%{linkEnd} and %{lockedLinkStart}locked%{linkEnd}."
+msgstr ""
+
+msgid "This %{noteableTypeText} is locked."
+msgstr ""
+
msgid "This %{viewer} could not be displayed because %{reason}. You can %{options} instead."
msgstr ""
@@ -22448,6 +23180,9 @@ msgstr ""
msgid "This action can lead to data loss. To prevent accidental actions we ask you to confirm your intention."
msgstr "Bu işlem veri kaybına yol açabilir. Yanlışlıkla yapılacak işlemleri önlemek için niyetinizi onaylamanızı istiyoruz."
+msgid "This also resolves all related threads"
+msgstr ""
+
msgid "This also resolves the discussion"
msgstr ""
@@ -22538,6 +23273,9 @@ msgstr ""
msgid "This epic does not exist or you don't have sufficient permission."
msgstr ""
+msgid "This feature is currently in beta. We invite you to %{linkStart}give feedback%{linkEnd}."
+msgstr ""
+
msgid "This feature requires local storage to be enabled"
msgstr ""
@@ -22571,8 +23309,8 @@ msgstr ""
msgid "This is a Work in Progress"
msgstr ""
-msgid "This is a confidential issue."
-msgstr "Bu gizli bir sorundur."
+msgid "This is a confidential %{noteableTypeText}."
+msgstr ""
msgid "This is a delayed job to run in %{remainingTime}"
msgstr ""
@@ -22598,9 +23336,6 @@ msgstr ""
msgid "This is your current session"
msgstr "Bu sizin mevcut oturumunuz"
-msgid "This issue is %{confidentialLinkStart}confidential%{linkEnd} and %{lockedLinkStart}locked%{linkEnd}."
-msgstr ""
-
msgid "This issue is confidential"
msgstr "Bu sorun gizlidir"
@@ -22610,9 +23345,6 @@ msgstr ""
msgid "This issue is in a child epic of the filtered epic"
msgstr ""
-msgid "This issue is locked."
-msgstr "Bu sorun kilitlendi."
-
msgid "This job depends on other jobs with expired/erased artifacts: %{invalid_dependencies}"
msgstr ""
@@ -22712,6 +23444,9 @@ msgstr ""
msgid "This license has already expired."
msgstr ""
+msgid "This link points to external content"
+msgstr ""
+
msgid "This may expose confidential information as the selected fork is in another namespace that can have other members."
msgstr ""
@@ -22739,6 +23474,9 @@ msgstr ""
msgid "This page will be removed in a future release."
msgstr ""
+msgid "This pipeline does not use the %{codeStart}needs%{codeEnd} keyword and can't be represented as a directed acyclic graph."
+msgstr ""
+
msgid "This pipeline makes use of a predefined CI/CD configuration enabled by %{strongStart}Auto DevOps.%{strongEnd}"
msgstr ""
@@ -22967,6 +23705,9 @@ msgstr ""
msgid "Time from last commit to merge"
msgstr "Son işlemden birleştirmeye kadar geçen süre"
+msgid "Time in seconds"
+msgstr ""
+
msgid "Time in seconds GitLab will wait for a response from the external service. When the service does not respond in time, access will be denied."
msgstr ""
@@ -23388,9 +24129,15 @@ msgstr "Toplam Katkı"
msgid "Total artifacts size: %{total_size}"
msgstr ""
+msgid "Total cores (CPUs)"
+msgstr ""
+
msgid "Total issues"
msgstr ""
+msgid "Total memory (GB)"
+msgstr ""
+
msgid "Total test time for all commits/merges"
msgstr ""
@@ -23592,6 +24339,9 @@ msgstr ""
msgid "U2F only works with HTTPS-enabled websites. Contact your administrator for more details."
msgstr ""
+msgid "UI Development Kit"
+msgstr ""
+
msgid "URL"
msgstr ""
@@ -23664,9 +24414,6 @@ msgstr ""
msgid "Unable to load the merge request widget. Try reloading the page."
msgstr ""
-msgid "Unable to process group import file"
-msgstr ""
-
msgid "Unable to resolve"
msgstr ""
@@ -23682,6 +24429,9 @@ msgstr ""
msgid "Unable to sign you in to the group with SAML due to \"%{reason}\""
msgstr ""
+msgid "Unable to suggest a path. Please refresh and try again."
+msgstr ""
+
msgid "Unable to update label prioritization at this time"
msgstr ""
@@ -23697,6 +24447,9 @@ msgstr ""
msgid "Unarchiving the project will restore people's ability to make changes to it. The repository can be committed to, and issues, comments, and other entities can be created. %{strong_start}Once active, this project shows up in the search and on the dashboard.%{strong_end}"
msgstr ""
+msgid "Unassign from commenting user"
+msgstr ""
+
msgid "Unblock"
msgstr "Engeli kaldır"
@@ -23772,6 +24525,9 @@ msgstr ""
msgid "Unreachable"
msgstr ""
+msgid "Unrecognized cluster type"
+msgstr ""
+
msgid "Unresolve"
msgstr ""
@@ -23832,6 +24588,9 @@ msgstr ""
msgid "Unsubscribes from this %{quick_action_target}."
msgstr ""
+msgid "Unsupported todo type passed. Supported todo types are: %{todo_types}"
+msgstr ""
+
msgid "Until"
msgstr ""
@@ -23859,6 +24618,9 @@ msgstr ""
msgid "Update approval rule"
msgstr ""
+msgid "Update approvers"
+msgstr ""
+
msgid "Update failed"
msgstr ""
@@ -23868,6 +24630,9 @@ msgstr ""
msgid "Update it"
msgstr ""
+msgid "Update iteration"
+msgstr ""
+
msgid "Update now"
msgstr ""
@@ -23913,8 +24678,8 @@ msgstr "%{updated_by} tarafından %{updated_at} zamanında güncellendi"
msgid "Updated at"
msgstr "Güncellendi:"
-msgid "Updated to"
-msgstr "Güncellendi:"
+msgid "Updated to %{linkStart}chart v%{linkEnd}"
+msgstr ""
msgid "Updating"
msgstr "Güncelleniyor"
@@ -23943,6 +24708,9 @@ msgstr ""
msgid "Upload CSV file"
msgstr ""
+msgid "Upload License"
+msgstr ""
+
msgid "Upload New File"
msgstr "Yeni Dosya Yükle"
@@ -24024,18 +24792,21 @@ msgstr "Paketler"
msgid "UsageQuota|Pipelines"
msgstr "İş hatları"
+msgid "UsageQuota|Purchase more storage"
+msgstr ""
+
msgid "UsageQuota|Repositories"
msgstr ""
msgid "UsageQuota|Repository"
msgstr "Depo"
+msgid "UsageQuota|Snippets"
+msgstr ""
+
msgid "UsageQuota|Storage"
msgstr "Depolama"
-msgid "UsageQuota|Storage usage:"
-msgstr ""
-
msgid "UsageQuota|This namespace has no projects which use shared runners"
msgstr "Bu isim alanının paylaşılan çalıştırıcıları kullanan projeleri yok"
@@ -24066,6 +24837,12 @@ msgstr "Viki"
msgid "UsageQuota|Wikis"
msgstr ""
+msgid "UsageQuota|You used: %{usage} %{limit}"
+msgstr ""
+
+msgid "UsageQuota|out of %{formattedLimit} of your namespace storage"
+msgstr ""
+
msgid "Use %{code_start}::%{code_end} to create a %{link_start}scoped label set%{link_end} (eg. %{code_start}priority::1%{code_end})"
msgstr ""
@@ -24132,6 +24909,9 @@ msgstr ""
msgid "User IDs"
msgstr ""
+msgid "User Lists can only be created and modified with %{linkStart}the API%{linkEnd}"
+msgstr ""
+
msgid "User OAuth applications"
msgstr ""
@@ -24156,6 +24936,9 @@ msgstr ""
msgid "User key was successfully removed."
msgstr ""
+msgid "User list %{name} will be removed. Are you sure?"
+msgstr ""
+
msgid "User map"
msgstr "Kullanıcı haritası"
@@ -24177,148 +24960,10 @@ msgstr "Kullanıcı projeden başarıyla kaldırıldı."
msgid "User was successfully updated."
msgstr "Kullanıcı başarıyla güncelleştirildi."
-msgid "UserOnboardingTour|%{activeTour}/%{totalTours}"
-msgstr ""
-
-msgid "UserOnboardingTour|%{completed}/%{total} steps completed"
-msgstr "%{completed}/%{total} adım tamamlandı"
-
-msgid "UserOnboardingTour|%{emphasisStart}Well done!%{emphasisEnd}%{lineBreak}%{lineBreak}That's it for our guided tour, congratulations for making it all the way to the end!%{lineBreak}%{lineBreak}We hope this gave you a good overview of GitLab and how it can help you. We'll now show you how to create your own project and invite your colleagues."
-msgstr ""
-
-msgid "UserOnboardingTour|Adding other members to a project is done through Project Settings. Click on %{emphasisStart}Settings%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Alright, that's it for Commits. Let's take a look at the %{emphasisStart}Branches%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Awesome! Now click on %{emphasisStart}Members%{emphasisEnd}."
-msgstr "Harika! Şimdi %{emphasisStart}Üyeler%{emphasisEnd} üzerine tıklayın."
-
-msgid "UserOnboardingTour|Click on one of the %{emphasisStart}Compare%{emphasisEnd} buttons to compare a branch to master."
-msgstr ""
-
-msgid "UserOnboardingTour|Click on one of the %{emphasisStart}pipeline IDs%{emphasisEnd} to see the details of a pipeline."
-msgstr ""
-
-msgid "UserOnboardingTour|Click to open the latest commit to see its details."
-msgstr ""
-
-msgid "UserOnboardingTour|Close 'Learn GitLab'"
-msgstr ""
-
-msgid "UserOnboardingTour|Commits are shown in chronological order and can be filtered by the commit message or by the branch."
-msgstr ""
-
-msgid "UserOnboardingTour|Create a project"
-msgstr "Bir proje oluÅŸtur"
-
-msgid "UserOnboardingTour|Exit 'Learn GitLab'"
-msgstr ""
-
-msgid "UserOnboardingTour|Got it"
-msgstr ""
-
-msgid "UserOnboardingTour|Great job! %{clapHands} We hope the tour was helpful and that you learned how to use GitLab.%{lineBreak}%{lineBreak}We'd love to get your feedback on this tour.%{lineBreak}%{lineBreak}%{emphasisStart}How helpful would you say this guided tour was?%{emphasisEnd}%{lineBreak}%{lineBreak}"
-msgstr ""
-
-msgid "UserOnboardingTour|Guided GitLab Tour"
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can compare the changes of this branch to another one. Changes are divided by files so that it's easier to see what was changed where."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can create a project from scratch, start with a template or import a repository from other platforms. Whatever you choose, we'll guide you through the process.%{lineBreak}%{lineBreak}Fill in your new project information and click on %{emphasisStart}Create Project%{emphasisEnd} to progress to the next step."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can see the breakdown of the pipelines: its stages and jobs in each of the stages and their status.%{lineBreak}%{lineBreak}Our CI/CD pipelines are quite complex, most of our users have fewer and simpler pipelines."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can see the current members of the project (just you at the moment) and invite new members.%{lineBreak}%{lineBreak}You can invite multiple members at once (existing GitLab users or invite by email) and you can also set their roles and permissions.%{lineBreak}%{lineBreak}Add a few members and click on %{emphasisStart}Add to project%{emphasisEnd} to complete this step."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can see what changes were made with this commit, on what branch and if there's a related merge request. The status of the pipeline will also show up if CI/CD is set up.%{lineBreak}%{lineBreak}You can also comment on the lines of code that were changed and start a discussion with your colleagues!"
-msgstr ""
-
-msgid "UserOnboardingTour|Here's an overview of branches in the %{emphasisStart}%{projectName}%{emphasisEnd} project. They're split into Active and Stale.%{lineBreak}%{lineBreak}From here, you can create a new merge request from a branch, or compare the branch to any other branch in the project. By default, it will compare it to the master branch."
-msgstr ""
-
-msgid "UserOnboardingTour|Invite colleagues"
-msgstr ""
-
-msgid "UserOnboardingTour|Issues are great for communicating and keeping track of progress in GitLab. These are all issues that are open in the %{emphasisStart}%{projectName}%{emphasisEnd}.%{lineBreak}%{lineBreak}You can help us improve GitLab by contributing work to issues that are labeled <span class=\"badge color-label accept-mr-label\">Accepting merge requests</span>.%{lineBreak}%{lineBreak}This list can be filtered by labels, milestones, assignees, authors... We'll show you how it looks when the list is filtered by a label."
-msgstr ""
-
-msgid "UserOnboardingTour|Learn GitLab"
-msgstr ""
-
-msgid "UserOnboardingTour|Let's take a closer look at a merge request. Click on the title of one."
-msgstr ""
-
-msgid "UserOnboardingTour|Let's take a closer look at all the commits. Click on %{emphasisStart}Commits%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Let's take a closer look at the repository of this project. Click on %{emphasisStart}Repository%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|No thanks"
-msgstr ""
-
-msgid "UserOnboardingTour|Ok, let's go"
-msgstr ""
-
-msgid "UserOnboardingTour|Ok, show me"
-msgstr ""
-
-msgid "UserOnboardingTour|Open one of the issues by clicking on its title."
-msgstr ""
-
-msgid "UserOnboardingTour|Restart this step"
-msgstr ""
-
-msgid "UserOnboardingTour|Skip this step"
-msgstr ""
-
-msgid "UserOnboardingTour|Sweet! Your project was created and is ready to be used.%{lineBreak}%{lineBreak}You can start adding files to the repository or clone it. One last thing we want to show you is how to invite your colleagues to your new project."
-msgstr ""
-
-msgid "UserOnboardingTour|Take a look. Here's a nifty menu for quickly creating issues, merge requests, snippets, projects and groups. Click on it and select \"New project\" from the \"GitLab\" section to get started."
-msgstr ""
-
-msgid "UserOnboardingTour|Thanks for taking the guided tour. Remember, if you want to go through it again, you can start %{emphasisStart}Learn GitLab%{emphasisEnd} in the help menu on the top right."
-msgstr ""
-
-msgid "UserOnboardingTour|Thanks for the feedback! %{thumbsUp}"
-msgstr ""
-
-msgid "UserOnboardingTour|That's it for issues. Let'st take a look at %{emphasisStart}Merge Requests%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|That's it for merge requests. Now for the final part of this guided tour - the %{emphasisStart}CI/CD%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|That's it for the Repository. Let's take a look at the %{emphasisStart}Issues%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|The structure of this page is very similar to issues. Status, description, discussion and the sidebar are all here.%{lineBreak}%{lineBreak}But take a look below the description and you'll notice that there's more information about the merge request, the CI/CD pipeline and the options for approving it.%{lineBreak}%{lineBreak}Alongside the discussion you can also see more information about commits in this merge request, the status of pipelines and review all changes that were made."
-msgstr ""
-
-msgid "UserOnboardingTour|There's a lot of information here but don't worry, we'll go through it.%{lineBreak}%{lineBreak}On the top you can see the status of the issue and when it was opened and by whom. Directly below it is the issue description and below that are other %{emphasisStart}related issues%{emphasisEnd} and %{emphasisStart}merge requests%{emphasisEnd} (if any). Then below that is the %{emphasisStart}discussion%{emphasisEnd}, that's where most of the communication happens.%{lineBreak}%{lineBreak}On the right, there's a sidebar where you can view/change the %{emphasisStart}assignee, milestone, due date, labels, weight%{emphasisEnd}, etc."
-msgstr ""
-
-msgid "UserOnboardingTour|These are all the CI/CD pipelines we have for our %{emphasisStart}%{projectName}%{emphasisEnd} project.%{lineBreak}%{lineBreak}Here you can see the status of each pipeline, for what commit it's running for, its stages and the status for them."
-msgstr ""
-
-msgid "UserOnboardingTour|These are all the issues that are available for community contributions. Let's take a closer look at one of them."
-msgstr ""
-
-msgid "UserOnboardingTour|This is an overview of all merge requests in this project. Similarly to the issues overview it can be filtered down by things like labels, milestones, authors, assignees, etc."
+msgid "UserList|Delete %{name}?"
msgstr ""
-msgid "UserOnboardingTour|This is the repository for the %{emphasisStart}%{projectName}%{emphasisEnd} project. All our code is stored here. Feel free to explore and take a closer look at folders and files.%{lineBreak}%{lineBreak}Above the file structure you can see the latest commit, who the author is and the status of the CI/CD pipeline.%{lineBreak}%{lineBreak}If you scroll down below the file structure, you'll find the Readme of this project. This is defined in the README.md file at the root of the repository."
-msgstr ""
-
-msgid "UserOnboardingTour|Welcome to the project overview of the %{emphasisStart}%{projectName}%{emphasisEnd} project. This is the project that we use to work on GitLab. At first, a project seems like a simple repository, but at GitLab, a project is so much more.%{lineBreak}%{lineBreak}You can create projects for hosting your codebase, use it as an issue tracker, collaborate on code, and continuously build, test, and deploy your app with built-in GitLab CI/CD."
+msgid "UserList|created %{timeago}"
msgstr ""
msgid "UserProfile|Activity"
@@ -24471,6 +25116,9 @@ msgstr "Vekil yok - %{openingTag} kendinizi atayın %{closingTag}"
msgid "UsersSelect|Unassigned"
msgstr ""
+msgid "Using %{codeStart}needs%{codeEnd} allows jobs to run before their stage is reached, as soon as their individual dependencies are met, which speeds up your pipelines."
+msgstr ""
+
msgid "Using %{code_start}::%{code_end} denotes a %{link_start}scoped label set%{link_end}"
msgstr ""
@@ -24555,9 +25203,6 @@ msgstr "Sürüm"
msgid "Versions"
msgstr ""
-msgid "Very helpful"
-msgstr ""
-
msgid "View Documentation"
msgstr ""
@@ -24706,6 +25351,9 @@ msgstr "Genel"
msgid "VisibilityLevel|Unknown"
msgstr "Bilinmeyen"
+msgid "Visit settings page"
+msgstr ""
+
msgid "VisualReviewApp|%{stepStart}Step 1%{stepEnd}. Copy the following script:"
msgstr ""
@@ -24799,6 +25447,9 @@ msgstr "%{user} tarafından %{timeago} çözüldü"
msgid "VulnerabilityManagement|Something went wrong while trying to delete the comment. Please try again later."
msgstr ""
+msgid "VulnerabilityManagement|Something went wrong while trying to refresh the vulnerability. Please try again later."
+msgstr ""
+
msgid "VulnerabilityManagement|Something went wrong while trying to retrieve the vulnerability history. Please try again later."
msgstr ""
@@ -24841,6 +25492,9 @@ msgstr ""
msgid "Vulnerability|Class"
msgstr ""
+msgid "Vulnerability|Crash Address"
+msgstr ""
+
msgid "Vulnerability|Description"
msgstr "Açıklama"
@@ -24916,9 +25570,6 @@ msgstr ""
msgid "We couldn't reach the Prometheus server. Either the server no longer exists or the configuration details need updating."
msgstr ""
-msgid "We created a short guided tour that will help you learn the basics of GitLab and how it will help you be better at your job. It should only take a couple of minutes. You will be guided by two types of helpers, best recognized by their color."
-msgstr ""
-
msgid "We detected potential spam in the %{humanized_resource_name}. Please solve the reCAPTCHA to proceed."
msgstr ""
@@ -24952,6 +25603,9 @@ msgstr ""
msgid "We've found no vulnerabilities"
msgstr ""
+msgid "Web Application Firewall"
+msgstr ""
+
msgid "Web IDE"
msgstr "Web IDE"
@@ -25012,9 +25666,6 @@ msgstr ""
msgid "Welcome to GitLab.com<br>@%{name}!"
msgstr ""
-msgid "Welcome to the Guided GitLab Tour"
-msgstr ""
-
msgid "Welcome to the guided GitLab tour"
msgstr ""
@@ -25062,9 +25713,6 @@ msgstr ""
msgid "While it's rare to have no vulnerabilities, it can happen. In any event, we ask that you please double check your settings to make sure you've set up your dashboard correctly."
msgstr ""
-msgid "White helpers give contextual information."
-msgstr ""
-
msgid "Who can be an approver?"
msgstr ""
@@ -25110,12 +25758,18 @@ msgstr ""
msgid "WikiEmptyIssueMessage|Suggest wiki improvement"
msgstr ""
+msgid "WikiEmptyIssueMessage|You must be a group member in order to add wiki pages. If you have suggestions for how to improve the wiki for this group, consider opening an issue in the %{issues_link}."
+msgstr ""
+
msgid "WikiEmptyIssueMessage|You must be a project member in order to add wiki pages. If you have suggestions for how to improve the wiki for this project, consider opening an issue in the %{issues_link}."
msgstr ""
msgid "WikiEmptyIssueMessage|issue tracker"
msgstr ""
+msgid "WikiEmpty|A wiki is where you can store all the details about your group. This can include why you've created it, its principles, how to use it, and so on."
+msgstr ""
+
msgid "WikiEmpty|A wiki is where you can store all the details about your project. This can include why you've created it, its principles, how to use it, and so on."
msgstr ""
@@ -25125,12 +25779,21 @@ msgstr "İlk sayfanızı oluşturun"
msgid "WikiEmpty|Suggest wiki improvement"
msgstr ""
+msgid "WikiEmpty|The wiki lets you write documentation for your group"
+msgstr ""
+
msgid "WikiEmpty|The wiki lets you write documentation for your project"
msgstr ""
+msgid "WikiEmpty|This group has no wiki pages"
+msgstr ""
+
msgid "WikiEmpty|This project has no wiki pages"
msgstr ""
+msgid "WikiEmpty|You must be a group member in order to add wiki pages."
+msgstr ""
+
msgid "WikiEmpty|You must be a project member in order to add wiki pages."
msgstr ""
@@ -25407,13 +26070,13 @@ msgstr ""
msgid "You can now export your security dashboard to a CSV report."
msgstr ""
-msgid "You can now submit a merge request to get this change into the original branch."
+msgid "You can now manage alert endpoint configuration in the Alerts section on the Operations settings page. Fields on this page have been deprecated."
msgstr ""
-msgid "You can now submit a merge request to get this change into the original project."
+msgid "You can now submit a merge request to get this change into the original branch."
msgstr ""
-msgid "You can only add files when you are on a branch"
+msgid "You can now submit a merge request to get this change into the original project."
msgstr ""
msgid "You can only edit files when you are on a branch"
@@ -25635,7 +26298,7 @@ msgstr ""
msgid "You need to upload a Google Takeout archive."
msgstr ""
-msgid "You reached %{usage_in_percent} of %{namespace_name}'s capacity (%{used_storage} of %{storage_limit})"
+msgid "You reached %{usage_in_percent} of %{namespace_name}'s storage capacity (%{used_storage} of %{storage_limit})"
msgstr ""
msgid "You tried to fork %{link_to_the_project} but it failed for the following reason:"
@@ -25938,13 +26601,13 @@ msgstr ""
msgid "Your subscription expired!"
msgstr "AboneliÄŸiniz sona erdi!"
-msgid "Your subscription has been downgraded"
+msgid "Your subscription has been downgraded."
msgstr ""
-msgid "Your subscription will automatically renew in %{remaining_days}"
+msgid "Your subscription will automatically renew in %{remaining_days}."
msgstr ""
-msgid "Your subscription will expire in %{remaining_days}"
+msgid "Your subscription will expire in %{remaining_days}."
msgstr ""
msgid "Zoom meeting added"
@@ -26024,9 +26687,6 @@ msgstr ""
msgid "branch name"
msgstr "dal adı"
-msgid "build pipeline reference mismatch"
-msgstr ""
-
msgid "by"
msgstr "tarafından"
@@ -26045,6 +26705,9 @@ msgstr ""
msgid "cannot block others"
msgstr ""
+msgid "cannot contain HTML/XML tags, including any word between angle brackets (<,>)."
+msgstr ""
+
msgid "cannot include leading slash or directory traversal."
msgstr ""
@@ -26075,55 +26738,6 @@ msgstr ""
msgid "ciReport|%{remainingPackagesCount} more"
msgstr ""
-msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability"
-msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities"
-msgstr[0] "%{reportType} %{status} %{dismissedCount} reddedilen güvenlik açığı tespit edildi"
-msgstr[1] "%{reportType} %{status} %{dismissedCount} reddedilen güvenlik açığı tespit edildi"
-
-msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability for the source branch only"
-msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities for the source branch only"
-msgstr[0] "%{reportType} %{status} yalnızca kaynak dal için %{dismissedCount} reddedilen güvenlik açığı tespit edildi"
-msgstr[1] "%{reportType} %{status} yalnızca kaynak dal için %{dismissedCount} reddedilen güvenlik açığı tespit edildi"
-
-msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerability"
-msgid_plural "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerabilities"
-msgstr[0] "%{reportType} %{status} %{fixedCount} sabit güvenlik açığı tespit edildi"
-msgstr[1] "%{reportType} %{status} %{fixedCount} sabit güvenlik açığı tespit edildi"
-
-msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
-msgstr "%{reportType} %{status} %{fixedCount} sabit ve %{dismissedCount} reddedilmiş güvenlik açığı tespit edildi"
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new vulnerability"
-msgid_plural "ciReport|%{reportType} %{status} detected %{newCount} new vulnerabilities"
-msgstr[0] "%{reportType} %{status} %{newCount} yeni güvenlik açığı tespit edildi"
-msgstr[1] "%{reportType} %{status} %{newCount} yeni güvenlik açığı tespit edildi"
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
-msgstr "%{reportType} %{status} %{newCount} yeni, %{fixedCount} sabit ve %{dismissedCount} reddedilmiş güvenlik açığı tespit edildi"
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities"
-msgstr "%{reportType} %{status} %{newCount} yeni ve %{dismissedCount} reddedilmiş güvenlik açığı tespit edildi"
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities for the source branch only"
-msgstr "%{reportType} %{status} yalnızca kaynak dal için %{newCount} yeni ve %{dismissedCount} reddedilen güvenlik açığı tespit etti"
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{fixedCount} fixed vulnerabilities"
-msgstr "%{reportType} %{status} %{newCount} yeni ve %{fixedCount} sabit güvenlik tespit edildi"
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} vulnerability for the source branch only"
-msgid_plural "ciReport|%{reportType} %{status} detected %{newCount} vulnerabilities for the source branch only"
-msgstr[0] "%{reportType} %{status} yalnızca kaynak dal için %{newCount} güvenlik açığı tespit edildi"
-msgstr[1] "%{reportType} %{status} yalnızca kaynak dal için %{newCount} güvenlik açığı tespit edildi"
-
-msgid "ciReport|%{reportType} %{status} detected no new vulnerabilities"
-msgstr "%{reportType} %{status} yeni güvenlik açığı tespit edilmedi"
-
-msgid "ciReport|%{reportType} %{status} detected no vulnerabilities"
-msgstr "%{reportType} %{status} güvenlik açığı tespit edilmedi"
-
-msgid "ciReport|%{reportType} %{status} detected no vulnerabilities for the source branch only"
-msgstr "%{reportType} %{status} yalnızca kaynak dal için hiçbir güvenlik açığı tespit edilmedi"
-
msgid "ciReport|%{reportType} is loading"
msgstr "%{reportType} yükleniyor"
@@ -26142,8 +26756,8 @@ msgstr ""
msgid "ciReport|All projects"
msgstr "Tüm projeler"
-msgid "ciReport|All report types"
-msgstr "Tüm rapor türleri"
+msgid "ciReport|All scanner types"
+msgstr ""
msgid "ciReport|All severities"
msgstr ""
@@ -26166,6 +26780,9 @@ msgstr ""
msgid "ciReport|Container scanning detects known vulnerabilities in your docker images."
msgstr ""
+msgid "ciReport|Coverage Fuzzing"
+msgstr ""
+
msgid "ciReport|Create a merge request to implement this solution, or download and apply the patch manually."
msgstr "Bu çözümü uygulamak için bir birleştirme talebi oluşturun veya yamayı elle indirin ve uygulayın."
@@ -26324,6 +26941,9 @@ msgstr "%{timeAgo} oluÅŸturuldu"
msgid "customize"
msgstr "özelleştir"
+msgid "data"
+msgstr ""
+
msgid "date must not be after 9999-12-31"
msgstr ""
@@ -26394,6 +27014,9 @@ msgstr "giriÅŸler boÅŸ olamaz"
msgid "entries cannot contain HTML tags"
msgstr "girişler HTML etiketleri içeremez"
+msgid "epic"
+msgstr ""
+
msgid "error"
msgstr "hata"
@@ -26415,9 +27038,6 @@ msgstr ""
msgid "expires on %{timebox_due_date}"
msgstr ""
-msgid "external_url"
-msgstr ""
-
msgid "failed"
msgstr "başarısız"
@@ -26453,9 +27073,6 @@ msgstr "projeyi çatalla"
msgid "from"
msgstr ""
-msgid "geo_node_name"
-msgstr ""
-
msgid "group"
msgstr "grup"
@@ -26477,9 +27094,6 @@ msgstr "burada"
msgid "https://your-bitbucket-server"
msgstr "https://sizin-bitbucket-sunucunuz"
-msgid "image"
-msgstr ""
-
msgid "image diff"
msgstr "resim deÄŸiÅŸikliÄŸi"
@@ -26623,6 +27237,9 @@ msgstr ""
msgid "load it anyway"
msgstr ""
+msgid "loading"
+msgstr ""
+
msgid "locked by %{path_lock_user_name} %{created_at}"
msgstr "%{path_lock_user_name} tarafından kilitlendi %{created_at}"
@@ -26751,9 +27368,6 @@ msgstr "Kaynak dalı sil"
msgid "mrWidget|Deployment statistics are not available currently"
msgstr ""
-msgid "mrWidget|Detect issues before deployment with a CI pipeline that continuously tests your code. We created a quick guide that will show you how to create one. Make your code more secure and more robust in just a minute."
-msgstr ""
-
msgid "mrWidget|Did not close"
msgstr "Kapatılmadı"
@@ -26916,6 +27530,9 @@ msgstr "Bu proje arşivlendi, yazma erişimi devre dışı bırakıldı"
msgid "mrWidget|To approve this merge request, please enter your password. This project requires all approvals to be authenticated."
msgstr ""
+msgid "mrWidget|Use %{linkStart}CI pipelines to test your code%{linkEnd}, simply add a GitLab CI configuration file to your project. It only takes a minute to make your code more secure and robust."
+msgstr ""
+
msgid "mrWidget|When this merge request is ready, remove the WIP: prefix from the title to allow it to be merged"
msgstr ""
@@ -26952,6 +27569,9 @@ msgstr ""
msgid "must be greater than start date"
msgstr ""
+msgid "must contain only valid frameworks"
+msgstr ""
+
msgid "my-awesome-group"
msgstr ""
@@ -26964,6 +27584,9 @@ msgstr ""
msgid "needs to be between 10 minutes and 1 month"
msgstr ""
+msgid "never"
+msgstr ""
+
msgid "never expires"
msgstr ""
@@ -26991,6 +27614,9 @@ msgstr "bildirim e-postaları"
msgid "nounSeries|%{firstItem} and %{lastItem}"
msgstr ""
+msgid "nounSeries|%{item}"
+msgstr ""
+
msgid "nounSeries|%{item}, %{nextItem}"
msgstr ""
@@ -27127,6 +27753,9 @@ msgstr "ilgili hatayı çözdü ve sorunu kapattı."
msgid "revised"
msgstr ""
+msgid "satisfied"
+msgstr ""
+
msgid "score"
msgstr "puan"
@@ -27157,6 +27786,9 @@ msgstr ""
msgid "severity|Unknown"
msgstr "Bilinmeyen"
+msgid "should be an array of %{object_name} objects"
+msgstr ""
+
msgid "should be greater than or equal to %{access} inherited membership from group %{group_name}"
msgstr ""
@@ -27211,10 +27843,13 @@ msgstr ""
msgid "suggestPipeline|2/2: Commit your changes"
msgstr ""
-msgid "suggestPipeline|Commit the changes and your pipeline will automatically run for the first time."
+msgid "suggestPipeline|Choose %{boldStart}Code Quality%{boldEnd} to add a pipeline that tests the quality of your code."
msgstr ""
-msgid "suggestPipeline|We recommend the %{boldStart}Code Quality%{boldEnd} template, which will add a report widget to your Merge Requests. This way you’ll learn about code quality degradations much sooner. %{footerStart} Goodbye technical debt! %{footerEnd}"
+msgid "suggestPipeline|The template is ready! You can now commit it to create your first pipeline."
+msgstr ""
+
+msgid "suggestPipeline|We’re adding a GitLab CI configuration file to add a pipeline to the project. You could create it manually, but we recommend that you start with a GitLab template that works out of the box."
msgstr ""
msgid "syntax is correct"
@@ -27274,6 +27909,9 @@ msgstr "kullanıcı adı"
msgid "uses Kubernetes clusters to deploy your code!"
msgstr ""
+msgid "v%{version} published %{timeAgo}"
+msgstr ""
+
msgid "verify ownership"
msgstr "sahipliÄŸi doÄŸrula"
diff --git a/locale/uk/gitlab.po b/locale/uk/gitlab.po
index badb6ef33cd..ad01e5f2058 100644
--- a/locale/uk/gitlab.po
+++ b/locale/uk/gitlab.po
@@ -2,7 +2,7 @@ msgid ""
msgstr ""
"Project-Id-Version: gitlab-ee\n"
"Report-Msgid-Bugs-To: \n"
-"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Last-Translator: \n"
"Language-Team: Ukrainian\n"
"Language: uk_UA\n"
"MIME-Version: 1.0\n"
@@ -10,9 +10,11 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=4; plural=((n%10==1 && n%100!=11) ? 0 : ((n%10 >= 2 && n%10 <=4 && (n%100 < 12 || n%100 > 14)) ? 1 : ((n%10 == 0 || (n%10 >= 5 && n%10 <=9)) || (n%100 >= 11 && n%100 <= 14)) ? 2 : 3));\n"
"X-Crowdin-Project: gitlab-ee\n"
+"X-Crowdin-Project-ID: 288872\n"
"X-Crowdin-Language: uk\n"
"X-Crowdin-File: /master/locale/gitlab.pot\n"
-"PO-Revision-Date: 2020-06-08 16:58\n"
+"X-Crowdin-File-ID: 6\n"
+"PO-Revision-Date: 2020-07-02 01:21\n"
msgid " %{start} to %{end}"
msgstr "%{start} до %{end}"
@@ -162,10 +164,10 @@ msgstr[3] "%d внеÑків"
msgid "%d day"
msgid_plural "%d days"
-msgstr[0] ""
-msgstr[1] ""
-msgstr[2] ""
-msgstr[3] ""
+msgstr[0] "%d день"
+msgstr[1] "%d дні"
+msgstr[2] "%d днів"
+msgstr[3] "%d днів"
msgid "%d error"
msgid_plural "%d errors"
@@ -183,10 +185,10 @@ msgstr[3] "%d екÑпортерів"
msgid "%d failed"
msgid_plural "%d failed"
-msgstr[0] ""
-msgstr[1] ""
-msgstr[2] ""
-msgstr[3] ""
+msgstr[0] "%d невдало"
+msgstr[1] "%d невдалих"
+msgstr[2] "%d невдалих"
+msgstr[3] "%d невдалих"
msgid "%d fixed test result"
msgid_plural "%d fixed test results"
@@ -251,6 +253,13 @@ msgstr[1] "%d метрики"
msgstr[2] "%d метрик"
msgstr[3] "%d метрик"
+msgid "%d milestone"
+msgid_plural "%d milestones"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
msgid "%d minute"
msgid_plural "%d minutes"
msgstr[0] "%d хвилина"
@@ -314,19 +323,12 @@ msgstr[1] ""
msgstr[2] ""
msgstr[3] ""
-msgid "%d url scanned"
-msgid_plural "%d urls scanned"
-msgstr[0] ""
-msgstr[1] ""
-msgstr[2] ""
-msgstr[3] ""
-
msgid "%d vulnerability"
msgid_plural "%d vulnerabilities"
-msgstr[0] ""
-msgstr[1] ""
-msgstr[2] ""
-msgstr[3] ""
+msgstr[0] "%d вразливіÑÑ‚ÑŒ"
+msgstr[1] "%d вразливоÑÑ‚Ñ–"
+msgstr[2] "%d вразливоÑтей"
+msgstr[3] "%d вразливоÑтей"
msgid "%d vulnerability dismissed"
msgid_plural "%d vulnerabilities dismissed"
@@ -412,9 +414,15 @@ msgstr[3] "%{count} коментарів в очікуванні"
msgid "%{count} related %{pluralized_subject}: %{links}"
msgstr "%{count} пов’Ñзаних %{pluralized_subject}: %{links}"
+msgid "%{dashboard_path} could not be found."
+msgstr ""
+
msgid "%{days} days until tags are automatically removed"
msgstr "%{days} днів до того, Ñк теги будуть видалені автоматично"
+msgid "%{deployLinkStart}Use a template to deploy to ECS%{deployLinkEnd}, or use a docker image to %{commandsLinkStart}run AWS commands in GitLab CI/CD%{commandsLinkEnd}."
+msgstr ""
+
msgid "%{description}- Sentry event: %{errorUrl}- First seen: %{firstSeen}- Last seen: %{lastSeen} %{countLabel}: %{count}%{userCountLabel}: %{userCount}"
msgstr "%{description}- Ð¿Ð¾Ð´Ñ–Ñ Ñƒ Sentry: %{errorUrl}- Вперше помічено: %{firstSeen}- ВоÑтаннє помічено: %{lastSeen} %{countLabel}: %{count}%{userCountLabel}: %{userCount}"
@@ -457,12 +465,18 @@ msgstr "%{group_name} викориÑтовує облікові запиÑи кÐ
msgid "%{host} sign-in from new location"
msgstr ""
-msgid "%{icon}You are about to add %{usersTag} people to the discussion. Proceed with caution."
-msgstr "%{icon}Ви збираєтеÑÑŒ додати %{usersTag} людей до обговореннÑ. Будьте обережні."
+msgid "%{icon}You are about to add %{usersTag} people to the discussion. They will all receive a notification."
+msgstr ""
+
+msgid "%{integrations_link_start}Integrations%{link_end} enable you to make third-party applications part of your GitLab workflow. If the available integrations don't meet your needs, consider using a %{webhooks_link_start}webhook%{link_end}."
+msgstr ""
msgid "%{issuableType} will be removed! Are you sure?"
msgstr "%{issuableType} буде видалено! Ви впевнені?"
+msgid "%{issuesCount} issues in this group"
+msgstr ""
+
msgid "%{issuesSize} issues"
msgstr "%{issuesSize} задач"
@@ -472,7 +486,7 @@ msgstr "%{issuesSize} задач з обмеженнÑм %{maxIssueCount}"
msgid "%{labelStart}Class:%{labelEnd} %{class}"
msgstr ""
-msgid "%{labelStart}Confidence:%{labelEnd} %{confidence}"
+msgid "%{labelStart}Evidence:%{labelEnd} %{evidence}"
msgstr ""
msgid "%{labelStart}File:%{labelEnd} %{file}"
@@ -490,6 +504,9 @@ msgstr ""
msgid "%{labelStart}Report Type:%{labelEnd} %{reportType}"
msgstr ""
+msgid "%{labelStart}Scanner:%{labelEnd} %{scanner}"
+msgstr ""
+
msgid "%{labelStart}Severity:%{labelEnd} %{severity}"
msgstr ""
@@ -514,9 +531,6 @@ msgstr "%{link_start}ДізнатиÑÑ Ð±Ñ–Ð»ÑŒÑˆÐµ%{link_end} про те, ÑÐ
msgid "%{link_start}Read more%{link_end} about role permissions"
msgstr "%{link_start}Читати більше%{link_end} про дозволи ролей"
-msgid "%{link} can be used for binding events when something is happening within the project."
-msgstr "%{link} може бути викориÑтаний Ð´Ð»Ñ Ð·Ð²'ÑÐ·ÑƒÐ²Ð°Ð½Ð½Ñ Ð¿Ð¾Ð´Ñ–Ð¹, коли щоÑÑŒ відбуваєтьÑÑ Ð² проєкті."
-
msgid "%{listToShow}, and %{awardsListLength} more."
msgstr "%{listToShow} та ще %{awardsListLength}."
@@ -550,6 +564,9 @@ msgstr "%{name} міÑтить %{resultsString}"
msgid "%{name} found %{resultsString}"
msgstr "%{name} знайдено %{resultsString}"
+msgid "%{name} is already being used for another emoji"
+msgstr ""
+
msgid "%{name} is scheduled for %{action}"
msgstr "%{name} заплановано на %{action}"
@@ -599,6 +616,9 @@ msgstr "%{placeholder} не є коректною темою"
msgid "%{primary} (%{secondary})"
msgstr "%{primary} (%{secondary})"
+msgid "%{ref} cannot be added: %{error}"
+msgstr ""
+
msgid "%{releases} release"
msgid_plural "%{releases} releases"
msgstr[0] "%{releases} реліз"
@@ -607,16 +627,46 @@ msgstr[2] "%{releases} релізів"
msgstr[3] "%{releases} релізів"
msgid "%{remaining_approvals} left"
+msgstr "%{remaining_approvals} залишилоÑÑ"
+
+msgid "%{reportType} %{status} detected %{critical} critical and %{high} high severity vulnerabilities out of %{total}."
msgstr ""
-msgid "%{retryButtonStart}Try again%{retryButtonEnd} or %{newFileButtonStart}attach a new file%{newFileButtonEnd}"
+msgid "%{reportType} %{status} detected %{critical} critical and %{high} high severity vulnerabilities."
msgstr ""
-msgid "%{screenreaderOnlyStart}Keyboard shorcuts%{screenreaderOnlyEnd} Disabled"
-msgstr "%{screenreaderOnlyStart}ГарÑчі клавіші%{screenreaderOnlyEnd} Вимкнено"
+msgid "%{reportType} %{status} detected %{critical} critical severity vulnerabilities out of %{total}."
+msgstr ""
+
+msgid "%{reportType} %{status} detected %{critical} critical severity vulnerability."
+msgid_plural "%{reportType} %{status} detected %{critical} critical severity vulnerabilities."
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
-msgid "%{screenreaderOnlyStart}Keyboard shorcuts%{screenreaderOnlyEnd} Enabled"
-msgstr "%{screenreaderOnlyStart}ГарÑчі клавіші%{screenreaderOnlyEnd} Увімкнено"
+msgid "%{reportType} %{status} detected %{high} high severity vulnerabilities out of %{total}."
+msgstr ""
+
+msgid "%{reportType} %{status} detected %{high} high severity vulnerability."
+msgid_plural "%{reportType} %{status} detected %{high} high severity vulnerabilities."
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "%{reportType} %{status} detected %{other} vulnerability."
+msgid_plural "%{reportType} %{status} detected %{other} vulnerabilities."
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "%{reportType} %{status} detected no new vulnerabilities."
+msgstr ""
+
+msgid "%{retryButtonStart}Try again%{retryButtonEnd} or %{newFileButtonStart}attach a new file%{newFileButtonEnd}"
+msgstr ""
msgid "%{service_title} %{message}."
msgstr "%{service_title} %{message}."
@@ -722,6 +772,12 @@ msgstr "%{title} зміни"
msgid "%{token}..."
msgstr "%{token}..."
+msgid "%{totalCpu} (%{freeSpacePercentage}%{percentSymbol} free)"
+msgstr ""
+
+msgid "%{totalMemory} (%{freeSpacePercentage}%{percentSymbol} free)"
+msgstr ""
+
msgid "%{totalWeight} total weight"
msgstr "%{totalWeight} загальна вага"
@@ -752,6 +808,12 @@ msgstr ""
msgid "%{verb} %{time_spent_value} spent time."
msgstr "%{verb} %{time_spent_value} витрачено чаÑу."
+msgid "%{webhooks_link_start}%{webhook_type}%{link_end} enable you to send notifications to web applications in response to events in a group or project."
+msgstr ""
+
+msgid "%{webhooks_link_start}%{webhook_type}%{link_end} enable you to send notifications to web applications in response to events in a group or project. We recommend using an %{integrations_link_start}integration%{link_end} in preference to a webhook."
+msgstr ""
+
msgid "'%{level}' is not a valid visibility level"
msgstr "\"%{level}\" не Ñ” допуÑтимим рівнем видимоÑÑ‚Ñ–"
@@ -780,9 +842,6 @@ msgstr "(%{mrCount} злито)"
msgid "(No changes)"
msgstr "(Ðемає змін)"
-msgid "(Show all)"
-msgstr "(Показати вÑе)"
-
msgid "(check progress)"
msgstr "(перевірити прогреÑ)"
@@ -855,6 +914,9 @@ msgstr "- показати менше"
msgid "0 for unlimited"
msgstr "0 Ð´Ð»Ñ Ð½ÐµÐ¾Ð±Ð¼ÐµÐ¶ÐµÐ½Ð¾Ð³Ð¾"
+msgid "0 for unlimited, only effective with remote storage enabled."
+msgstr ""
+
msgid "1 %{type} addition"
msgid_plural "%{count} %{type} additions"
msgstr[0] "1 %{type} доповненнÑ"
@@ -1015,7 +1077,7 @@ msgid "8 hours"
msgstr "8 годин"
msgid ":%{startLine} to %{endLine}"
-msgstr ""
+msgstr ":%{startLine} до %{endLine}"
msgid "< 1 hour"
msgstr "< 1 години"
@@ -1032,9 +1094,15 @@ msgstr "<code>\"johnsmith@example.com\": \"johnsm...@example.com\"</code> дод
msgid "<code>\"johnsmith@example.com\": \"johnsmith@example.com\"</code> will add \"By <a href=\"#\">johnsmith@example.com</a>\" to all issues and comments originally created by johnsmith@example.com. By default, the email address or username is masked to ensure the user's privacy. Use this option if you want to show the full email address."
msgstr "<code>\"johnsmith@example.com\": \"johnsmith@example.com\"</code> додаÑÑ‚ÑŒ \"<a href=\"#\">johnsmith@example.com</a>\" до вÑÑ–Ñ… задач та коментарів, Ñкі були Ñтворені johnsmith@example.com. За замовчуваннÑм Ñ–Ð¼â€™Ñ ÐºÐ¾Ñ€Ð¸Ñтувача та його електронна адреÑа заблоковані Ð´Ð»Ñ Ð·Ð°Ð±ÐµÐ·Ð¿ÐµÑ‡ÐµÐ½Ð½Ñ ÐºÐ¾Ð½Ñ„Ñ–Ð´ÐµÐ½Ñ†Ñ–Ð¹Ð½Ð¾ÑÑ‚Ñ–. ВикориÑтовуйте цю опцію, Ñкщо ви хочете показувати електронну адреÑу повніÑÑ‚ÑŽ."
-msgid "<namespace / project>"
+msgid "<code>Masked</code> to prevent the values from being displayed in job logs (must match certain regexp requirements)."
+msgstr ""
+
+msgid "<code>Protected</code> to expose them to protected branches or tags only."
msgstr ""
+msgid "<namespace / project>"
+msgstr "<проÑÑ‚Ñ–Ñ€ імен / проєкт>"
+
msgid "<no name set>"
msgstr "<Ñ–Ð¼â€™Ñ Ð½Ðµ задане>"
@@ -1042,7 +1110,7 @@ msgid "<no scopes selected>"
msgstr "<облаÑÑ‚ÑŒ дії не вибрано>"
msgid "<project name>"
-msgstr ""
+msgstr "<Ñ–Ð¼â€™Ñ Ð¿Ñ€Ð¾Ñ”ÐºÑ‚Ñƒ>"
msgid "<strong>%{group_name}</strong> group members"
msgstr "<strong>%{group_name}</strong> кориÑтувачі групи"
@@ -1059,9 +1127,6 @@ msgstr "'Runner' — це процеÑ, Ñкий виконує завданнÑ
msgid "A .NET Core console application template, customizable for any .NET Core project"
msgstr "Шаблон конÑольного заÑтоÑунку .NET Core, Ñкий налаштовуєтьÑÑ Ð´Ð»Ñ Ð±ÑƒÐ´ÑŒ-Ñкого проєкту .NET Core"
-msgid "A DAG must have two dependent jobs to be visualized on this tab."
-msgstr ""
-
msgid "A GitBook site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
msgstr "Сайт GitBook, Ñкий викориÑтовує Netlify Ð´Ð»Ñ CI/CD заміÑÑ‚ÑŒ GitLab, але вÑе ще з уÑіма іншими чудовими функціÑми GitLab."
@@ -1092,6 +1157,12 @@ msgstr "Ðе можна обирати уÑтавну гілку Ð´Ð»Ñ Ð¿Ð¾Ñ€Ð¾
msgid "A deleted user"
msgstr "Видалений кориÑтувач"
+msgid "A file has been changed."
+msgstr ""
+
+msgid "A file was not found."
+msgstr "Файл не знайдено."
+
msgid "A file with '%{file_name}' already exists in %{branch} branch"
msgstr "Файл з '%{file_name}' вже Ñ–Ñнує в гілці %{branch}"
@@ -1099,7 +1170,7 @@ msgid "A fork is a copy of a project.<br />Forking a repository allows you to ma
msgstr "Форк - це ÐºÐ¾Ð¿Ñ–Ñ Ð¿Ñ€Ð¾Ñ”ÐºÑ‚Ñƒ.<br />Форк репозиторію дозволÑÑ” вноÑити зміни без впливу на оригінальний проєкт."
msgid "A group is a collection of several projects"
-msgstr ""
+msgstr "Група — набір із декількох проєктів"
msgid "A group represents your organization in GitLab. Groups allow you to manage users and collaborate across multiple projects."
msgstr ""
@@ -1158,7 +1229,7 @@ msgstr ""
msgid "A subscription will trigger a new pipeline on the default branch of this project when a pipeline successfully completes for a new tag on the %{default_branch_docs} of the subscribed project."
msgstr ""
-msgid "A terraform report was generated in your pipelines."
+msgid "A suggestion is not applicable."
msgstr ""
msgid "A user with write access to the source branch selected this option"
@@ -1210,7 +1281,7 @@ msgid "Accept terms"
msgstr "ПрийнÑти умови"
msgid "Acceptable for use in this project"
-msgstr ""
+msgstr "Дозволений Ð´Ð»Ñ Ð²Ð¸ÐºÐ¾Ñ€Ð¸ÑÑ‚Ð°Ð½Ð½Ñ Ð² цьому проєкті"
msgid "Access Tokens"
msgstr "Токени доÑтупу"
@@ -1329,6 +1400,9 @@ msgstr "Обліковий запиÑ: %{account}"
msgid "Action to take when receiving an alert."
msgstr "Дії Ð´Ð»Ñ Ð²Ð¸ÐºÐ¾Ð½Ð°Ð½Ð½Ñ Ð¿Ñ€Ð¸ отриманні попередженнÑ."
+msgid "Actions"
+msgstr ""
+
msgid "Activate"
msgstr "Ðктивувати"
@@ -1348,7 +1422,7 @@ msgid "Active Users:"
msgstr "Ðктивні кориÑтувачі:"
msgid "Active users"
-msgstr ""
+msgstr "Ðктивні кориÑтувачі"
msgid "Activity"
msgstr "ÐктивніÑÑ‚ÑŒ"
@@ -1387,6 +1461,9 @@ msgstr "Додати Kubernetes-клаÑтер"
msgid "Add LICENSE"
msgstr "Додати файл ліцензії (LICENSE)"
+msgid "Add New Node"
+msgstr ""
+
msgid "Add README"
msgstr "Додати інÑтрукцію (README)"
@@ -1397,7 +1474,7 @@ msgid "Add Zoom meeting"
msgstr "Додати Zoom-зуÑтріч"
msgid "Add a %{type}"
-msgstr ""
+msgstr "Додати %{type}"
msgid "Add a GPG key"
msgstr "Додати GPG ключ"
@@ -1412,7 +1489,7 @@ msgid "Add a bullet list"
msgstr "Додати ненумерований ÑпиÑок"
msgid "Add a comment to this line"
-msgstr ""
+msgstr "Додати коментар до цього Ñ€Ñдка"
msgid "Add a general comment to this %{noteableDisplayName}."
msgstr "Додайте загальний коментар до цього %{noteableDisplayName}."
@@ -1424,13 +1501,13 @@ msgid "Add a homepage to your wiki that contains information about your project
msgstr "Додати домашню Ñторінку в вікі, Ñка міÑтить інформацію про ваш проєкт, Ñ– GitLab відображатиме його тут заміÑÑ‚ÑŒ цього повідомленнÑ."
msgid "Add a line"
-msgstr ""
+msgstr "Додати Ñ€Ñдок"
msgid "Add a link"
msgstr "Додати поÑиланнÑ"
msgid "Add a new issue"
-msgstr ""
+msgstr "Додати нову задачу"
msgid "Add a numbered list"
msgstr "Додати нумерований ÑпиÑок"
@@ -1462,6 +1539,9 @@ msgstr "Додати ще одне поÑиланнÑ"
msgid "Add approval rule"
msgstr "Додати правило затвердженнÑ"
+msgid "Add approvers"
+msgstr ""
+
msgid "Add bold text"
msgstr "Додати жирний текÑÑ‚"
@@ -1519,6 +1599,9 @@ msgstr "Додати запит вручну"
msgid "Add strikethrough text"
msgstr ""
+msgid "Add suggestion to batch"
+msgstr ""
+
msgid "Add system hook"
msgstr "Додати ÑиÑтемний хук"
@@ -1618,6 +1701,9 @@ msgstr "ОблаÑÑ‚ÑŒ адмініÑтратора"
msgid "Admin Note"
msgstr ""
+msgid "Admin Notifications"
+msgstr ""
+
msgid "Admin Overview"
msgstr "ОглÑд адмініÑтратора"
@@ -1715,7 +1801,7 @@ msgid "AdminSettings|Auto DevOps domain"
msgstr "Домен Auto DevOps"
msgid "AdminSettings|Elasticsearch, PlantUML, Slack application, Third party offers, Snowplow, Amazon EKS have moved to Settings > General."
-msgstr ""
+msgstr "Elasticsearch, PlantUML, заÑтоÑунок Slack, пропозиції від третіх оÑіб, Snowplow, Amazon EKS були переміщені до Ðалаштувань > Загальне."
msgid "AdminSettings|Enable shared runners for new projects"
msgstr "Увімкнути загальні runner'и Ð´Ð»Ñ Ð½Ð¾Ð²Ð¸Ñ… проектів"
@@ -1729,6 +1815,9 @@ msgstr "Перейти до загальних налаштувань"
msgid "AdminSettings|Integrations configured here will automatically apply to all projects on this instance."
msgstr "Інтеграції, налаштовані тут, будуть автоматично заÑтоÑовуватиÑÑ Ð´Ð¾ вÑÑ–Ñ… проєктів у цьому інÑтанÑÑ–."
+msgid "AdminSettings|Moved to integrations"
+msgstr ""
+
msgid "AdminSettings|No required pipeline"
msgstr "ВідÑутній обов’Ñзковий конвеєр"
@@ -1961,12 +2050,18 @@ msgstr[1] "ПопередженнÑ"
msgstr[2] "Попереджень"
msgstr[3] "Попереджень"
+msgid "AlertManagement| assign yourself"
+msgstr ""
+
msgid "AlertManagement|Acknowledged"
msgstr ""
msgid "AlertManagement|Alert"
msgstr "ПопередженнÑ"
+msgid "AlertManagement|Alert assignee(s): %{assignees}"
+msgstr ""
+
msgid "AlertManagement|Alert detail"
msgstr ""
@@ -1977,14 +2072,20 @@ msgid "AlertManagement|Alert status: %{status}"
msgstr ""
msgid "AlertManagement|Alerts"
-msgstr ""
+msgstr "ПопередженнÑ"
msgid "AlertManagement|All alerts"
msgstr ""
+msgid "AlertManagement|Assign To"
+msgstr ""
+
msgid "AlertManagement|Assign status"
msgstr ""
+msgid "AlertManagement|Assignee"
+msgstr ""
+
msgid "AlertManagement|Assignees"
msgstr ""
@@ -1992,52 +2093,55 @@ msgid "AlertManagement|Authorize external service"
msgstr ""
msgid "AlertManagement|Create issue"
-msgstr ""
+msgstr "Створити задачу"
msgid "AlertManagement|Critical"
-msgstr ""
+msgstr "Критичне"
msgid "AlertManagement|Display alerts from all your monitoring tools directly within GitLab. Streamline the investigation of your alerts and the escalation of alerts to incidents."
msgstr ""
msgid "AlertManagement|Edit"
-msgstr ""
-
-msgid "AlertManagement|End time"
-msgstr "Ð§Ð°Ñ Ð·Ð°Ð²ÐµÑ€ÑˆÐµÐ½Ð½Ñ"
+msgstr "Редагувати"
msgid "AlertManagement|Events"
msgstr "Події"
msgid "AlertManagement|High"
-msgstr ""
+msgstr "ВиÑоке"
msgid "AlertManagement|Info"
-msgstr ""
+msgstr "Інформаційне"
msgid "AlertManagement|Low"
-msgstr ""
+msgstr "Ðизьке"
msgid "AlertManagement|Medium"
-msgstr ""
+msgstr "Середнє"
msgid "AlertManagement|More information"
msgstr "Детальніше"
+msgid "AlertManagement|No Matching Results"
+msgstr ""
+
msgid "AlertManagement|No alert data to display."
msgstr ""
-msgid "AlertManagement|No alerts available to display. If you think you're seeing this message in error, refresh the page."
-msgstr "Ðемає повідомлень Ð´Ð»Ñ Ð²Ñ–Ð´Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð½Ñ. Якщо ви вважаєте, що бачите це Ð¿Ð¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ Ð¿Ð¾Ð¼Ð¸Ð»ÐºÐ¾Ð²Ð¾, оновіть Ñторінку."
+msgid "AlertManagement|No alerts available to display. See %{linkStart}enabling alert management%{linkEnd} for more information on adding alerts to the list."
+msgstr ""
msgid "AlertManagement|No alerts to display."
msgstr "Ðемає попереджень Ð´Ð»Ñ Ð²Ñ–Ð´Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð½Ñ."
msgid "AlertManagement|None"
+msgstr "Ðемає"
+
+msgid "AlertManagement|None -"
msgstr ""
msgid "AlertManagement|Open"
-msgstr ""
+msgstr "Відкриті"
msgid "AlertManagement|Overview"
msgstr "ОглÑд"
@@ -2052,7 +2156,7 @@ msgid "AlertManagement|Resolved"
msgstr "Вирішено"
msgid "AlertManagement|Service"
-msgstr ""
+msgstr "СервіÑ"
msgid "AlertManagement|Severity"
msgstr "Рівень"
@@ -2072,23 +2176,32 @@ msgstr ""
msgid "AlertManagement|There was an error displaying the alerts. Confirm your endpoint's configuration details to ensure alerts appear."
msgstr ""
+msgid "AlertManagement|There was an error while updating the assignee(s) list. Please try again."
+msgstr ""
+
+msgid "AlertManagement|There was an error while updating the assignee(s) of the alert. Please try again."
+msgstr ""
+
msgid "AlertManagement|There was an error while updating the status of the alert. Please try again."
msgstr ""
-msgid "AlertManagement|Tool"
+msgid "AlertManagement|This assignee cannot be assigned to this alert."
msgstr ""
+msgid "AlertManagement|Tool"
+msgstr "ІнÑтрумент"
+
msgid "AlertManagement|Triggered"
msgstr ""
msgid "AlertManagement|Unassigned"
-msgstr ""
+msgstr "Ðепризначені"
msgid "AlertManagement|Unknown"
-msgstr ""
+msgstr "Ðевідоме"
msgid "AlertManagement|View issue"
-msgstr ""
+msgstr "ПереглÑнути задачу"
msgid "AlertService|Review your external service's documentation to learn where to provide this information to your external service, and the %{linkStart}GitLab documentation%{linkEnd} to learn more about configuring your endpoint."
msgstr ""
@@ -2222,6 +2335,9 @@ msgstr "Дозволити кориÑтувачам реєÑтрувати буÐ
msgid "Allow users to request access (if visibility is public or internal)"
msgstr "Дозволити кориÑтувачам запрошувати доÑтуп, (Ñкщо видиміÑÑ‚ÑŒ загальнодоÑтупна або внутрішнÑ)"
+msgid "Allowed Geo IP"
+msgstr ""
+
msgid "Allowed email domain restriction only permitted for top-level groups"
msgstr "Ð’ÑÑ‚Ð°Ð½Ð¾Ð²Ð»ÐµÐ½Ð½Ñ Ð´Ð¾Ð·Ð²Ð¾Ð»ÐµÐ½Ð¸Ñ… доменів Ð°Ð´Ñ€ÐµÑ ÐµÐ»ÐµÐºÑ‚Ñ€Ð¾Ð½Ð½Ð¾Ñ— пошти допуÑкаєтьÑÑ Ð»Ð¸ÑˆÐµ Ð´Ð»Ñ Ð³Ñ€ÑƒÐ¿ найвищого рівнÑ"
@@ -2255,6 +2371,9 @@ msgstr "Ð†Ð½Ñ‚ÐµÐ³Ñ€Ð°Ñ†Ñ–Ñ Ð· Amazon EKS дозволÑÑ” Ñтворювати
msgid "Amazon Web Services"
msgstr "Amazon Web Services"
+msgid "Amazon Web Services Logo"
+msgstr ""
+
msgid "Amazon authentication is not %{link_start}correctly configured%{link_end}. Ask your GitLab administrator if you want to use this service."
msgstr "Amazon-автентифікацію не %{link_start}налаштовано коректно%{link_end}. ЗвернітьÑÑ Ð´Ð¾ вашого адмініÑтратора GitLab, Ñкщо хочете викориÑтовувати цю можливіÑÑ‚ÑŒ."
@@ -2312,11 +2431,17 @@ msgstr "Помилка при вирішенні обговореннÑ. БудÑ
msgid "An error occurred when updating the issue weight"
msgstr "Збій під Ñ‡Ð°Ñ Ð¾Ð½Ð¾Ð²Ð»ÐµÐ½Ð½Ñ Ð²Ð°Ð³Ð¸ задачі"
+msgid "An error occurred while acknowledging the notification. Refresh the page and try again."
+msgstr ""
+
+msgid "An error occurred while adding approvers"
+msgstr ""
+
msgid "An error occurred while adding formatted title for epic"
msgstr "Помилка під Ñ‡Ð°Ñ Ð´Ð¾Ð´Ð°Ð²Ð°Ð½Ð½Ñ Ð²Ñ–Ð´Ñ„Ð¾Ñ€Ð¼Ð°Ñ‚Ð¾Ð²Ð°Ð½Ð¾Ð³Ð¾ заголовка Ð´Ð»Ñ ÐµÐ¿Ñ–ÐºÐ°"
-msgid "An error occurred while checking group path"
-msgstr "Помилка при перевірці шлÑху групи"
+msgid "An error occurred while checking group path. Please refresh and try again."
+msgstr ""
msgid "An error occurred while committing your changes."
msgstr "Помилка при коміті ваших змін."
@@ -2459,8 +2584,8 @@ msgstr "Помилка при завантаженні результатів з
msgid "An error occurred while loading milestones"
msgstr ""
-msgid "An error occurred while loading terraform report"
-msgstr "СталаÑÑ Ð¿Ð¾Ð¼Ð¸Ð»ÐºÐ° під Ñ‡Ð°Ñ Ð·Ð°Ð²Ð°Ð½Ñ‚Ð°Ð¶ÐµÐ½Ð½Ñ Ñ€ÐµÐ·ÑƒÐ»ÑŒÑ‚Ð°Ñ‚Ñ–Ð² Terraform"
+msgid "An error occurred while loading project creation UI"
+msgstr ""
msgid "An error occurred while loading the data. Please try again."
msgstr "Під Ñ‡Ð°Ñ Ð·Ð°Ð²Ð°Ð½Ñ‚Ð°Ð¶ÐµÐ½Ð½Ñ Ð´Ð°Ð½Ð¸Ñ… ÑталаÑÑ Ð¿Ð¾Ð¼Ð¸Ð»ÐºÐ°. Будь лаÑка, Ñпробуйте ще раз."
@@ -2513,6 +2638,9 @@ msgstr "Помилка при видаленні задач."
msgid "An error occurred while rendering preview broadcast message"
msgstr "Помилка при попередньому переглÑді Ð¾Ð³Ð¾Ð»Ð¾ÑˆÐµÐ½Ð½Ñ Ð´Ð»Ñ ÐºÐ¾Ñ€Ð¸Ñтувачів"
+msgid "An error occurred while rendering the editor"
+msgstr ""
+
msgid "An error occurred while reordering issues."
msgstr "Помилка при зміні порÑдку задач."
@@ -2574,13 +2702,13 @@ msgid "An instance-level serverless domain already exists."
msgstr ""
msgid "An issue already exists"
-msgstr ""
+msgstr "Ð¦Ñ Ð·Ð°Ð´Ð°Ñ‡Ð° вже Ñ–Ñнує"
msgid "An issue can be a bug, a todo or a feature request that needs to be discussed in a project. Besides, issues are searchable and filterable."
msgstr "Задача може опиÑувати помилку, бути нагадуваннÑм або опиÑом нової функціональноÑÑ‚Ñ– Ð´Ð»Ñ Ð¾Ð±Ð³Ð¾Ð²Ð¾Ñ€ÐµÐ½Ð½Ñ Ð² проекті. Крім того задачі можна шукати та фільтрувати."
msgid "An unauthenticated user"
-msgstr ""
+msgstr "Ðеавторизований кориÑтувач"
msgid "An unexpected error occurred while checking the project environment."
msgstr "Ðеочікувана помилка при перевірці Ñередовища проекту."
@@ -2609,7 +2737,7 @@ msgstr "Проаналізуйте верÑÑ–ÑŽ Ð´Ð»Ñ Ð¿Ð¾Ð¿ÐµÑ€ÐµÐ´Ð½ÑŒÐ¾Ð³Ð¾
msgid "Analyze your dependencies for known vulnerabilities."
msgstr "Проаналізуйте ваші залежноÑÑ‚Ñ– на предмет відомих вразливоÑтей."
-msgid "Analyze your source code and git history for secrets"
+msgid "Analyze your source code and git history for secrets."
msgstr ""
msgid "Analyze your source code for known vulnerabilities."
@@ -2646,13 +2774,13 @@ msgid "Any encrypted tokens"
msgstr "Будь-Ñкі зашифровані токени"
msgid "Any label"
-msgstr ""
+msgstr "Будь-Ñка мітка"
msgid "Any member with Developer or higher permissions to the project."
msgstr "Будь-Ñкий учаÑник з рівнем доÑтупу Розробник або вище до цього проєкту."
msgid "Any milestone"
-msgstr ""
+msgstr "Будь-Ñкий етап"
msgid "Any namespace"
msgstr "Будь-Ñкий проÑÑ‚Ñ–Ñ€ імен"
@@ -2718,17 +2846,23 @@ msgid "Apply a template"
msgstr "ЗаÑтоÑувати шаблон"
msgid "Apply changes"
-msgstr ""
+msgstr "ЗаÑтоÑувати зміни"
msgid "Apply suggestion"
msgstr "ЗаÑтоÑувати пропозицію"
+msgid "Apply suggestions"
+msgstr "ЗаÑтоÑувати пропозиції"
+
msgid "Apply template"
msgstr "ЗаÑтоÑувати шаблон"
msgid "Apply this approval rule to any branch or a specific protected branch."
msgstr ""
+msgid "Applying"
+msgstr ""
+
msgid "Applying a template will replace the existing issue description. Any changes you have made will be lost."
msgstr "ЗаÑтоÑÑƒÐ²Ð°Ð½Ð½Ñ ÑˆÐ°Ð±Ð»Ð¾Ð½Ñƒ замінить Ñ–Ñнуючий Ð¾Ð¿Ð¸Ñ Ð¿Ñ€Ð¾Ð±Ð»ÐµÐ¼Ð¸. Будь-Ñкі зроблені вами зміни будуть втрачені."
@@ -2741,12 +2875,18 @@ msgstr "Ð’Ð¸ÐºÐ¾Ð½Ð°Ð½Ð½Ñ ÐºÐ¾Ð¼Ð°Ð½Ð´Ð¸ Ð´Ð»Ñ %{commandDescription}"
msgid "Applying multiple commands"
msgstr "Ð’Ð¸ÐºÐ¾Ð½Ð°Ð½Ð½Ñ Ð´ÐµÐºÑ–Ð»ÑŒÐºÐ¾Ñ… команд"
-msgid "Applying suggestion"
-msgstr "ЗаÑтоÑÑƒÐ²Ð°Ð½Ð½Ñ Ð¿Ñ€Ð¾Ð¿Ð¾Ð·Ð¸Ñ†Ñ–Ñ—"
+msgid "Applying suggestion..."
+msgstr "ЗаÑтоÑÑƒÐ²Ð°Ð½Ð½Ñ Ð¿Ñ€Ð¾Ð¿Ð¾Ð·Ð¸Ñ†Ñ–Ñ—..."
+
+msgid "Applying suggestions..."
+msgstr ""
msgid "Approval rules"
msgstr "Правила затвердженнÑ"
+msgid "Approval rules reset to project defaults"
+msgstr ""
+
msgid "ApprovalRuleRemove|%d member"
msgid_plural "ApprovalRuleRemove|%d members"
msgstr[0] "%d учаÑник"
@@ -2808,6 +2948,9 @@ msgstr "Затвердити поточний запит на злиттÑ."
msgid "Approved"
msgstr ""
+msgid "Approved MRs"
+msgstr ""
+
msgid "Approved by: "
msgstr "Затверджено кориÑтувачем: "
@@ -3021,6 +3164,9 @@ msgstr "РеÑурÑи:"
msgid "Assign"
msgstr "Призначити"
+msgid "Assign Iteration"
+msgstr ""
+
msgid "Assign custom color like #FF0000"
msgstr "Призначити влаÑний колір типу #FF0000"
@@ -3039,6 +3185,9 @@ msgstr "Призначити деÑкі задачі до цього етапу.
msgid "Assign to"
msgstr "Призначити"
+msgid "Assign to commenting user"
+msgstr ""
+
msgid "Assign yourself to these issues"
msgstr "Призначити ці задачі Ñобі"
@@ -3067,6 +3216,9 @@ msgstr[1] "%d Виконавці"
msgstr[2] "%d Виконавців"
msgstr[3] "%d Виконавців"
+msgid "Assignee has no permissions"
+msgstr ""
+
msgid "Assignee lists not available with your current license"
msgstr "СпиÑки виконавців не доÑтупні з вашою поточною ліцензією"
@@ -3076,6 +3228,9 @@ msgstr "СпиÑки виконавців показують уÑÑ– задачі
msgid "Assignee(s)"
msgstr "Виконавець(ці)"
+msgid "Assignees"
+msgstr ""
+
msgid "Assigns %{assignee_users_sentence}."
msgstr "Призначає %{assignee_users_sentence}."
@@ -3113,6 +3268,9 @@ msgstr "Події аудиту"
msgid "Audit Events is a way to keep track of important events that happened in GitLab."
msgstr "Події аудиту дозволÑÑŽÑ‚ÑŒ відÑтежувати важливі події, що відбуваютьÑÑ Ð² GitLab."
+msgid "Audit Log"
+msgstr ""
+
msgid "AuditEvents|(removed)"
msgstr "(видалено)"
@@ -3149,6 +3307,9 @@ msgstr ""
msgid "AuditLogs|IP Address"
msgstr "IP-адреÑа"
+msgid "AuditLogs|Member Events"
+msgstr ""
+
msgid "AuditLogs|No matching %{type} found."
msgstr ""
@@ -3431,9 +3592,6 @@ msgstr "Ваші значки"
msgid "Badges|e.g. %{exampleUrl}"
msgstr "напр. %{exampleUrl}"
-msgid "Badge|New"
-msgstr "Ðовий"
-
msgid "Balsamiq file could not be loaded."
msgstr "Ðе вдалоÑÑ Ð·Ð°Ð²Ð°Ð½Ñ‚Ð°Ð¶Ð¸Ñ‚Ð¸ файл Balsamiq."
@@ -3482,6 +3640,12 @@ msgstr "Ðижче наведено відбитки SSH-ключів хоÑта
msgid "Below you will find all the groups that are public."
msgstr "Ðижче ви знайдете вÑÑ– загальнодоÑтупні групи."
+msgid "Beta"
+msgstr "Бета"
+
+msgid "Bi-weekly code coverage"
+msgstr ""
+
msgid "Billing"
msgstr "Білінг"
@@ -3542,9 +3706,15 @@ msgstr "Підвищити"
msgid "Bitbucket Server Import"
msgstr "Імпорт з Bitbucket Server"
+msgid "Bitbucket Server import"
+msgstr ""
+
msgid "Bitbucket import"
msgstr "Імпорт з Bitbucket"
+msgid "Blame"
+msgstr ""
+
msgid "Blocked"
msgstr "Заблокований"
@@ -3557,9 +3727,6 @@ msgstr ""
msgid "Blog"
msgstr "Блог"
-msgid "Blue helpers indicate an action to be taken."
-msgstr "Сині вказівники вказують на дії, Ñкі необхідно виконати."
-
msgid "Board name"
msgstr "Ðазва дошки"
@@ -3747,7 +3914,7 @@ msgid "Branches|merged"
msgstr "злита"
msgid "Branches|project settings"
-msgstr "ÐалаштуваннÑÑ… проекту"
+msgstr "ÐалаштуваннÑÑ… проєкту"
msgid "Branches|protected"
msgstr "захищена"
@@ -3758,6 +3925,9 @@ msgstr "ÐžÐ³Ð¾Ð»Ð¾ÑˆÐµÐ½Ð½Ñ ÑƒÑпішно Ñтворено."
msgid "Broadcast Message was successfully updated."
msgstr "ÐžÐ³Ð¾Ð»Ð¾ÑˆÐµÐ½Ð½Ñ ÑƒÑпішно оновлено."
+msgid "Broadcast Messages"
+msgstr ""
+
msgid "Browse Directory"
msgstr "ПереглÑнути каталог"
@@ -3812,6 +3982,9 @@ msgstr ""
msgid "By %{user_name}"
msgstr "Від %{user_name}"
+msgid "By URL"
+msgstr ""
+
msgid "By default GitLab sends emails in HTML and plain text formats so mail clients can choose what format to use. Disable this option if you only want to send emails in plain text format."
msgstr "За замовчуваннÑм GitLab відправлÑÑ” електронні лиÑти в форматі HTML та у звичайному текÑтовому форматі, щоб поштові клієнти могли вибирати Ñкий з форматів викориÑтовувати. Вимкніть це налаштуваннÑ, Ñкщо ви хочете відправлÑти лиÑти лише у звичайному текÑтовому форматі."
@@ -3837,7 +4010,7 @@ msgid "CI Lint"
msgstr "Перевірка CI конфігурації"
msgid "CI settings"
-msgstr ""
+msgstr "ÐÐ°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ CI"
msgid "CI variables"
msgstr "Змінні CI"
@@ -3917,6 +4090,9 @@ msgstr "Можна вручну розгорнути на"
msgid "Can override approvers and approvals required per merge request"
msgstr "Можна змінювати необхідні Ð·Ð°Ñ‚Ð²ÐµÑ€Ð´Ð¶ÐµÐ½Ð½Ñ Ñ‚Ð° затверджуючих оÑіб Ð´Ð»Ñ Ð·Ð°Ð¿Ð¸Ñ‚Ñ–Ð² на злиттÑ"
+msgid "Can't apply as this line has changed or the suggestion already matches its content."
+msgstr ""
+
msgid "Can't create snippet: %{err}"
msgstr ""
@@ -4053,7 +4229,7 @@ msgid "Change permissions"
msgstr "Змінити права доÑтупу"
msgid "Change status"
-msgstr ""
+msgstr "Змінити ÑтатуÑ"
msgid "Change subscription"
msgstr ""
@@ -4103,9 +4279,6 @@ msgstr "Зміни відображаютьÑÑ Ñ‚Ð°Ðº, ніби <b>редакц
msgid "Changes are still tracked. Useful for cluster/index migrations."
msgstr ""
-msgid "Changes are unknown"
-msgstr "Зміни невідомі"
-
msgid "Changes suppressed. Click to show."
msgstr "Зміни приховано. ÐатиÑніть щоб показати."
@@ -4125,7 +4298,7 @@ msgid "Channel handle (e.g. town-square)"
msgstr ""
msgid "Charts"
-msgstr ""
+msgstr "Графіки"
msgid "Charts can't be displayed as the request for data has timed out. %{documentationLink}"
msgstr "Графіки не можуть відображатиÑÑ, оÑкільки вичерпано Ñ‡Ð°Ñ Ð·Ð°Ð¿Ð¸Ñ‚Ñƒ даних. %{documentationLink}"
@@ -4230,7 +4403,7 @@ msgid "Checkout|(x%{numberOfUsers})"
msgstr "(x%{numberOfUsers})"
msgid "Checkout|Billing address"
-msgstr ""
+msgstr "Платіжна адреÑа"
msgid "Checkout|Checkout"
msgstr "ÐžÑ„Ð¾Ñ€Ð¼Ð»ÐµÐ½Ð½Ñ Ð·Ð°Ð¼Ð¾Ð²Ð»ÐµÐ½Ð½Ñ"
@@ -4317,7 +4490,7 @@ msgid "Checkout|State"
msgstr ""
msgid "Checkout|Street address"
-msgstr ""
+msgstr "ÐдреÑа"
msgid "Checkout|Submitting the credit card form failed with code %{errorCode}: %{errorMessage}"
msgstr "Ðе вдалоÑÑ Ð²Ñ–Ð´Ð¿Ñ€Ð°Ð²Ð¸Ñ‚Ð¸ форму кредитної картки з кодом %{errorCode}: %{errorMessage}"
@@ -4412,15 +4585,9 @@ msgstr "Виберіть рівень видимоÑÑ‚Ñ–, увімкніть/вÐ
msgid "Choose what content you want to see on a group’s overview page"
msgstr "Вибрати вміÑÑ‚ оглÑдової Ñторінки групи"
-msgid "Choose which groups you wish to synchronize to this secondary node"
-msgstr "Виберіть, Ñкі групи ви хочете Ñинхронізувати з вторинним вузлом."
-
msgid "Choose which repositories you want to connect and run CI/CD pipelines."
msgstr "Виберіть, Ñкі репозиторії ви хочете підключити Ñ– запуÑтити конвеєри CI/CD."
-msgid "Choose which shards you wish to synchronize to this secondary node"
-msgstr "Виберіть, Ñкі Ñегменти ви хочете Ñинхронізувати з цим вторинним вузлом."
-
msgid "Choose your framework"
msgstr ""
@@ -4590,7 +4757,7 @@ msgid "Clear chart filters"
msgstr "ОчиÑтити фільтри графіків"
msgid "Clear due date"
-msgstr ""
+msgstr "ОчиÑтити дату завершеннÑ"
msgid "Clear input"
msgstr "ОчиÑтити ввід"
@@ -4605,7 +4772,7 @@ msgid "Clear search input"
msgstr "ОчиÑтити поле вводу"
msgid "Clear start date"
-msgstr ""
+msgstr "ОчиÑтити дату початку"
msgid "Clear templates search input"
msgstr "ОчиÑтити поле вводу Ð´Ð»Ñ Ð¿Ð¾ÑˆÑƒÐºÑƒ шаблонів"
@@ -4718,9 +4885,15 @@ msgstr "Кеш клаÑтера очищено."
msgid "Cluster does not exist"
msgstr "КлаÑтер не Ñ–Ñнує"
+msgid "Cluster is required for Stages::ClusterEndpointInserter"
+msgstr ""
+
msgid "Cluster level"
msgstr "Рівень клаÑтера"
+msgid "Cluster type must be specificed for Stages::ClusterEndpointInserter"
+msgstr ""
+
msgid "ClusterIntegration| %{custom_domain_start}More information%{custom_domain_end}."
msgstr "%{custom_domain_start}Детальніше%{custom_domain_end}."
@@ -4832,8 +5005,8 @@ msgstr "Сертифікат центру Ñертифікації"
msgid "ClusterIntegration|Cert-Manager"
msgstr "Cert-Manager"
-msgid "ClusterIntegration|Cert-Manager is a native Kubernetes certificate management controller that helps with issuing certificates. Installing Cert-Manager on your cluster will issue a certificate by %{letsEncrypt} and ensure that certificates are valid and up-to-date."
-msgstr "Cert-Manager Ñ” контролером ÑƒÐ¿Ñ€Ð°Ð²Ð»Ñ–Ð½Ð½Ñ Ñертифікатами Ð´Ð»Ñ Kubernetes, Ñкий допомагає Ñ—Ñ… випуÑкати. Ð’ÑÑ‚Ð°Ð½Ð¾Ð²Ð»ÐµÐ½Ð½Ñ Cert-Manager на ваш клаÑтер випуÑтить Ñертифікат від %{letsEncrypt} Ñ– забезпечуватиме актуальніÑÑ‚ÑŒ Ñертифікатів."
+msgid "ClusterIntegration|Cert-Manager is a native Kubernetes certificate management controller that helps with issuing certificates. Installing Cert-Manager on your cluster will issue a certificate by %{linkStart}Let's Encrypt%{linkEnd} and ensure that certificates are valid and up-to-date."
+msgstr ""
msgid "ClusterIntegration|Certificate Authority bundle (PEM format)"
msgstr "Ðабір Ñертифікатів (формат PEM)"
@@ -4859,9 +5032,6 @@ msgstr "ОчиÑтити кеш клаÑтера"
msgid "ClusterIntegration|Clear the local cache of namespace and service accounts. This is necessary if your integration has become out of sync. The cache is repopulated during the next CI job that requires namespace and service accounts."
msgstr ""
-msgid "ClusterIntegration|Cloud Run"
-msgstr "Cloud Run"
-
msgid "ClusterIntegration|Cluster being created"
msgstr ""
@@ -4946,7 +5116,7 @@ msgstr "Ð¡Ñ‚Ð²Ð¾Ñ€ÐµÐ½Ð½Ñ Kubernetes-клаÑтера"
msgid "ClusterIntegration|Crossplane"
msgstr "Crossplane"
-msgid "ClusterIntegration|Crossplane enables declarative provisioning of managed services from your cloud of choice using %{codeStart}kubectl%{codeEnd} or %{gitlabIntegrationLink}. Crossplane runs inside your Kubernetes cluster and supports secure connectivity and secrets management between app containers and the cloud services they depend on."
+msgid "ClusterIntegration|Crossplane enables declarative provisioning of managed services from your cloud of choice using %{codeStart}kubectl%{codeEnd} or %{linkStart}GitLab Integration%{linkEnd}. Crossplane runs inside your Kubernetes cluster and supports secure connectivity and secrets management between app containers and the cloud services they depend on."
msgstr ""
msgid "ClusterIntegration|Deletes all GitLab resources attached to this cluster during removal"
@@ -5007,7 +5177,7 @@ msgid "ClusterIntegration|Fetching machine types"
msgstr "ÐžÑ‚Ñ€Ð¸Ð¼Ð°Ð½Ð½Ñ Ñ‚Ð¸Ð¿Ñ–Ð² машин"
msgid "ClusterIntegration|Fetching projects"
-msgstr "ÐžÑ‚Ñ€Ð¸Ð¼Ð°Ð½Ð½Ñ Ð¿Ñ€Ð¾ÐµÐºÑ‚Ñ–Ð²"
+msgstr "ÐžÑ‚Ñ€Ð¸Ð¼Ð°Ð½Ð½Ñ Ð¿Ñ€Ð¾Ñ”ÐºÑ‚Ñ–Ð²"
msgid "ClusterIntegration|Fetching zones"
msgstr "ÐžÑ‚Ñ€Ð¸Ð¼Ð°Ð½Ð½Ñ Ð·Ð¾Ð½"
@@ -5030,14 +5200,11 @@ msgstr "Runner Gitlab з’єднуєтьÑÑ Ñ–Ð· репозиторієм та
msgid "ClusterIntegration|GitLab-managed cluster"
msgstr "КлаÑтер, що керуєтьÑÑ GitLab"
-msgid "ClusterIntegration|Gitlab Integration"
-msgstr "Ð†Ð½Ñ‚ÐµÐ³Ñ€Ð°Ñ†Ñ–Ñ Ð· Gitlab"
-
msgid "ClusterIntegration|Global default"
msgstr ""
msgid "ClusterIntegration|Google Cloud Platform project"
-msgstr "Проект Google Cloud Platform"
+msgstr "Проєкт Google Cloud Platform"
msgid "ClusterIntegration|Google GKE"
msgstr "Google GKE"
@@ -5046,7 +5213,7 @@ msgid "ClusterIntegration|Google Kubernetes Engine"
msgstr "Google Kubernetes Engine"
msgid "ClusterIntegration|Google Kubernetes Engine project"
-msgstr "проекті Google Kubernetes Engine"
+msgstr "проєкті Google Kubernetes Engine"
msgid "ClusterIntegration|Group cluster"
msgstr "КлаÑтер групи"
@@ -5078,8 +5245,8 @@ msgstr "Кінцева точка Ingress"
msgid "ClusterIntegration|Ingress gives you a way to route requests to services based on the request host or path, centralizing a number of services into a single entrypoint."
msgstr "Ingress дозволÑÑ” вам маршрутизувати запити до Ñлужб на оÑнові запитаного хоÑта або шлÑху, об'єднуючи Ñ€Ñд ÑервіÑів в одну точку входу."
-msgid "ClusterIntegration|Installing Ingress may incur additional costs. Learn more about %{pricingLink}."
-msgstr "Ð’ÑÑ‚Ð°Ð½Ð¾Ð²Ð»ÐµÐ½Ð½Ñ Ingress може викликати додаткові витрати. ДізнайтеÑÑ Ð±Ñ–Ð»ÑŒÑˆÐµ про%{pricingLink}."
+msgid "ClusterIntegration|Installing Ingress may incur additional costs. Learn more about %{linkStart}pricing%{linkEnd}."
+msgstr ""
msgid "ClusterIntegration|Instance cluster"
msgstr "КлаÑтер інÑтанÑу"
@@ -5093,8 +5260,8 @@ msgstr "Ð†Ð½Ñ‚ÐµÐ³Ñ€Ð°Ñ†Ñ–Ñ ÐºÐ»Ð°Ñтерної автоматизації Kub
msgid "ClusterIntegration|Issuer Email"
msgstr "Електронна пошта емітента"
-msgid "ClusterIntegration|Issuers represent a certificate authority. You must provide an email address for your Issuer. "
-msgstr "Емітенти предÑтавлÑÑŽÑ‚ÑŒ центр Ñертифікації. Ви повинні надати адреÑу електронної пошти вашого емітента. "
+msgid "ClusterIntegration|Issuers represent a certificate authority. You must provide an email address for your Issuer."
+msgstr ""
msgid "ClusterIntegration|Jupyter Hostname"
msgstr "Ð†Ð¼â€™Ñ Ñ…Ð¾Ñта Jupyter"
@@ -5165,9 +5332,6 @@ msgstr "ДізнайтеÑÑ Ð±Ñ–Ð»ÑŒÑˆÐµ про групові клаÑтери
msgid "ClusterIntegration|Learn more about instance Kubernetes clusters"
msgstr "ДізнайтеÑÑ Ð±Ñ–Ð»ÑŒÑˆÐµ про клаÑтери Kubernetes інÑтанÑу"
-msgid "ClusterIntegration|Let's Encrypt"
-msgstr "Let's Encrypt"
-
msgid "ClusterIntegration|Loading IAM Roles"
msgstr "Ð—Ð°Ð²Ð°Ð½Ñ‚Ð°Ð¶ÐµÐ½Ð½Ñ Ñ€Ð¾Ð»ÐµÐ¹ IAM"
@@ -5279,8 +5443,8 @@ msgstr "ÐŸÑ€ÐµÑ„Ñ–ÐºÑ Ð¿Ñ€Ð¾Ñтору імен проекту (не обовâ€
msgid "ClusterIntegration|Prometheus"
msgstr "Prometheus"
-msgid "ClusterIntegration|Prometheus is an open-source monitoring system with %{gitlabIntegrationLink} to monitor deployed applications."
-msgstr "Prometheus — це ÑиÑтема моніторингу з відкритим вихідним кодом з %{gitlabIntegrationLink} Ð´Ð»Ñ Ð¼Ð¾Ð½Ñ–Ñ‚Ð¾Ñ€Ð¸Ð½Ð³Ñƒ розгорнутих додатків."
+msgid "ClusterIntegration|Prometheus is an open-source monitoring system with %{linkStart}GitLab Integration%{linkEnd} to monitor deployed applications."
+msgstr ""
msgid "ClusterIntegration|Provider details"
msgstr ""
@@ -5435,10 +5599,10 @@ msgstr "Вибрати зону"
msgid "ClusterIntegration|Select zone to choose machine type"
msgstr "Виберіть зону, щоб вибрати тип машин"
-msgid "ClusterIntegration|Send Cilium Logs"
+msgid "ClusterIntegration|Send Container Network Policies Logs"
msgstr ""
-msgid "ClusterIntegration|Send ModSecurity Logs"
+msgid "ClusterIntegration|Send Web Application Firewall Logs"
msgstr ""
msgid "ClusterIntegration|Service Token"
@@ -5606,15 +5770,12 @@ msgstr "доÑтуп до Google Kubernetes Engine"
msgid "ClusterIntegration|documentation"
msgstr "документації"
-msgid "ClusterIntegration|installed via %{installed_via}"
-msgstr "вÑтановлено через %{installed_via}"
+msgid "ClusterIntegration|installed via %{linkStart}Cloud Run%{linkEnd}"
+msgstr ""
msgid "ClusterIntegration|meets the requirements"
msgstr "задовольнÑÑ” вимогам"
-msgid "ClusterIntegration|pricing"
-msgstr "вартіÑÑ‚ÑŒ"
-
msgid "ClusterIntegration|sign up"
msgstr "реєÑтрації"
@@ -5651,6 +5812,15 @@ msgstr ""
msgid "Code"
msgstr "Код"
+msgid "Code Coverage: %{coveragePercentage}%{percentSymbol}"
+msgstr ""
+
+msgid "Code Coverage| Empty code coverage data"
+msgstr ""
+
+msgid "Code Coverage|Couldn't fetch the code coverage data"
+msgstr ""
+
msgid "Code Owners"
msgstr "ВлаÑники коду"
@@ -5705,8 +5875,8 @@ msgstr "Згорнути"
msgid "Collapse approvers"
msgstr "Згорнути ÑпиÑок затверджуючих оÑіб"
-msgid "Collapse child epics"
-msgstr "Згорнути дочірні епіки"
+msgid "Collapse milestones"
+msgstr ""
msgid "Collapse replies"
msgstr ""
@@ -5721,6 +5891,9 @@ msgid "ComboSearch is not defined"
msgstr "ComboSearch не визначено"
msgid "Coming soon"
+msgstr "Ðезабаром"
+
+msgid "Comma-separated, e.g. '1.1.1.1, 2.2.2.0/24'"
msgstr ""
msgid "Command"
@@ -5895,6 +6068,12 @@ msgstr ""
msgid "Compliance framework (optional)"
msgstr ""
+msgid "Compliance frameworks"
+msgstr ""
+
+msgid "ComplianceDashboard|created by:"
+msgstr ""
+
msgid "ComplianceFramework|GDPR"
msgstr "GDPR"
@@ -6033,6 +6212,9 @@ msgstr "Помилка з’єднаннÑ"
msgid "Connection timed out"
msgstr "Минув Ñ‡Ð°Ñ Ð¾Ñ‡Ñ–ÐºÑƒÐ²Ð°Ð½Ð½Ñ Ð¿Ñ–Ð´ÐºÐ»ÑŽÑ‡ÐµÐ½Ð½Ñ"
+msgid "Connection timeout"
+msgstr ""
+
msgid "Contact sales to upgrade"
msgstr "ЗвернітьÑÑ Ð´Ð¾ відділу продажів Ð´Ð»Ñ Ð¿ÐµÑ€ÐµÑ…Ð¾Ð´Ñƒ на вищий тарифний план"
@@ -6092,9 +6274,6 @@ msgstr "Створити образ"
msgid "ContainerRegistry|CLI Commands"
msgstr ""
-msgid "ContainerRegistry|Compressed Size"
-msgstr ""
-
msgid "ContainerRegistry|Container Registry"
msgstr "РеєÑÑ‚Ñ€ контейнерів"
@@ -6110,6 +6289,9 @@ msgstr "Скопіювати команду входу"
msgid "ContainerRegistry|Copy push command"
msgstr "Скопіювати команду відправленнÑ"
+msgid "ContainerRegistry|Delete selected"
+msgstr ""
+
msgid "ContainerRegistry|Docker connection error"
msgstr "Помилка Ð·â€™Ñ”Ð´Ð½Ð°Ð½Ð½Ñ Docker"
@@ -6143,18 +6325,18 @@ msgstr ""
msgid "ContainerRegistry|If you are not already logged in, you need to authenticate to the Container Registry by using your GitLab username and password. If you have %{twofaDocLinkStart}Two-Factor Authentication%{twofaDocLinkEnd} enabled, use a %{personalAccessTokensDocLinkStart}Personal Access Token%{personalAccessTokensDocLinkEnd} instead of a password."
msgstr "Якщо ви ще не виконали вхід вам необхідно автентифікуватиÑÑ Ð² РеєÑтрі Контейнерів за допомогою імені кориÑтувача та Ð¿Ð°Ñ€Ð¾Ð»Ñ GitLab. Якщо у Ð²Ð°Ñ Ð²Ð²Ñ–Ð¼ÐºÐ½ÐµÐ½Ð° %{twofaDocLinkStart}Двофакторна автентифікаціÑ%{twofaDocLinkEnd}, викориÑтовуйте %{personalAccessTokensDocLinkStart}ПерÑональний Токен ДоÑтупу%{personalAccessTokensDocLinkEnd} заміÑÑ‚ÑŒ паролю."
-msgid "ContainerRegistry|Image ID"
-msgstr "Ідентифікатор образу"
+msgid "ContainerRegistry|Image ID: %{imageId}"
+msgstr ""
msgid "ContainerRegistry|Image Repositories"
msgstr ""
+msgid "ContainerRegistry|Image tags"
+msgstr ""
+
msgid "ContainerRegistry|Keep and protect the images that matter most."
msgstr "Зберігати Ñ– захищати образи, Ñкі мають найбільше значеннÑ."
-msgid "ContainerRegistry|Last Updated"
-msgstr "ВоÑтаннє оновлено"
-
msgid "ContainerRegistry|Login"
msgstr "Увійти"
@@ -6167,6 +6349,9 @@ msgstr "КількіÑÑ‚ÑŒ тегів Ð´Ð»Ñ Ð·Ð±ÐµÑ€ÐµÐ¶ÐµÐ½Ð½Ñ:"
msgid "ContainerRegistry|Please contact your administrator."
msgstr "Будь лаÑка, зв'ÑжітьÑÑ Ð· адмініÑтратором."
+msgid "ContainerRegistry|Published %{timeInfo}"
+msgstr ""
+
msgid "ContainerRegistry|Push an image"
msgstr "Відправити образ"
@@ -6182,9 +6367,6 @@ msgstr "Ðе забудьте запуÑтити %{docLinkStart}збірку ÑÐ
msgid "ContainerRegistry|Remove repository"
msgstr "Видалити репозиторій"
-msgid "ContainerRegistry|Remove selected tags"
-msgstr "Видалити вибрані теги"
-
msgid "ContainerRegistry|Remove tag"
msgid_plural "ContainerRegistry|Remove tags"
msgstr[0] "Видалити тег"
@@ -6202,10 +6384,10 @@ msgid "ContainerRegistry|Something went wrong while fetching the tags list."
msgstr "ЩоÑÑŒ пішло не так при отриманні ÑпиÑку тегів."
msgid "ContainerRegistry|Something went wrong while marking the tag for deletion."
-msgstr ""
+msgstr "ЩоÑÑŒ пішло не так при позначці тегу до видаленнÑ."
msgid "ContainerRegistry|Something went wrong while marking the tags for deletion."
-msgstr ""
+msgstr "ЩоÑÑŒ пішло не так при позначці тегів до видаленнÑ."
msgid "ContainerRegistry|Something went wrong while scheduling %{title} for deletion. Please try again."
msgstr ""
@@ -6216,9 +6398,6 @@ msgstr ""
msgid "ContainerRegistry|Sorry, your filter produced no results."
msgstr ""
-msgid "ContainerRegistry|Tag"
-msgstr "Тег"
-
msgid "ContainerRegistry|Tag expiration policy"
msgstr "Політика Ð·Ð°ÐºÑ–Ð½Ñ‡ÐµÐ½Ð½Ñ Ñ‚ÐµÑ€Ð¼Ñ–Ð½Ñƒ дії тегу"
@@ -6322,7 +6501,7 @@ msgid "ContributionAnalytics|<strong>%{pushes}</strong> pushes, more than <stron
msgstr ""
msgid "ContributionAnalytics|Contribution analytics for issues, merge requests and push events since %{start_date}"
-msgstr ""
+msgstr "Ðналітика внеÑків Ð´Ð»Ñ Ð·Ð°Ð´Ð°Ñ‡, запитів на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ñ‚Ð° подій відправки з %{start_date}"
msgid "ContributionAnalytics|Issues"
msgstr "Задачі"
@@ -6363,21 +6542,6 @@ msgstr "Керувати адреÑами електронної пошти, пÐ
msgid "Control the display of third party offers."
msgstr "Керувати відображеннÑм Ñторонніх пропозицій."
-msgid "Control the maximum concurrency of LFS/attachment backfill for this secondary node"
-msgstr "Керувати макÑимальною кількіÑÑ‚ÑŽ потоків фонового Ð·Ð°Ð²Ð°Ð½Ñ‚Ð°Ð¶ÐµÐ½Ð½Ñ LFS/вкладень Ð´Ð»Ñ Ñ†ÑŒÐ¾Ð³Ð¾ вторинного вузла."
-
-msgid "Control the maximum concurrency of container repository operations for this Geo node"
-msgstr ""
-
-msgid "Control the maximum concurrency of repository backfill for this secondary node"
-msgstr "Задати макÑимальну кількіÑÑ‚ÑŒ потоків Ð´Ð»Ñ Ñ„Ð¾Ð½Ð¾Ð²Ð¾Ð³Ð¾ Ð·Ð°Ð²Ð°Ð½Ñ‚Ð°Ð¶ÐµÐ½Ð½Ñ Ñ€ÐµÐ¿Ð¾Ð·Ð¸Ñ‚Ð¾Ñ€Ñ–Ñ—Ð² Ð´Ð»Ñ Ñ†ÑŒÐ¾Ð³Ð¾ вторинного вузла"
-
-msgid "Control the maximum concurrency of verification operations for this Geo node"
-msgstr ""
-
-msgid "Control the minimum interval in days that a repository should be reverified for this primary node"
-msgstr ""
-
msgid "Cookie domain"
msgstr "Домен cookie"
@@ -6403,7 +6567,7 @@ msgid "Copy %{proxy_url}"
msgstr "Скопіювати %{proxy_url}"
msgid "Copy %{type}"
-msgstr ""
+msgstr "Скопіювати %{type}"
msgid "Copy Account ID to clipboard"
msgstr "Скопіювати ідентифікатор облікового запиÑу в буфер обміну"
@@ -6519,6 +6683,9 @@ msgstr "Ðе вдалоÑÑ Ð²Ð¸Ð´Ð°Ð»Ð¸Ñ‚Ð¸ пÑевдонім Ð´Ð»Ñ Ñ‡Ð°Ñ‚Ñƒ
msgid "Could not find design."
msgstr "Ðе вдалоÑÑ Ð·Ð½Ð°Ð¹Ñ‚Ð¸ дизайн."
+msgid "Could not find iteration"
+msgstr ""
+
msgid "Could not remove the trigger."
msgstr "Ðе вдалоÑÑ Ð²Ð¸Ð´Ð°Ð»Ð¸Ñ‚Ð¸ тригер."
@@ -6555,6 +6722,12 @@ msgstr "Країна"
msgid "Coverage"
msgstr "ПокриттÑ"
+msgid "Coverage Fuzzing"
+msgstr ""
+
+msgid "Crash State"
+msgstr ""
+
msgid "Create"
msgstr "Створити"
@@ -6562,7 +6735,7 @@ msgid "Create %{environment}"
msgstr "Створити %{environment}"
msgid "Create %{type}"
-msgstr ""
+msgstr "Створити %{type}"
msgid "Create New Directory"
msgstr "Створити новий каталог"
@@ -6603,6 +6776,9 @@ msgstr "Створити новий репозиторій"
msgid "Create a personal access token on your account to pull or push via %{protocol}."
msgstr "Створіть токен доÑтупу Ð´Ð»Ñ Ð²Ð°ÑˆÐ¾Ð³Ð¾ аккаунта, щоб відправлÑти та отримувати через %{protocol}."
+msgid "Create a project pre-populated with the necessary files to get you started quickly."
+msgstr ""
+
msgid "Create an account using:"
msgstr "Створити обліковий Ð·Ð°Ð¿Ð¸Ñ Ð·Ð° допомогою:"
@@ -6664,7 +6840,7 @@ msgid "Create milestone"
msgstr "Створити етап"
msgid "Create new"
-msgstr ""
+msgstr "Створити новий"
msgid "Create new board"
msgstr "Створіть нову дошку"
@@ -6771,6 +6947,9 @@ msgstr "Створює гілку та запит на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ð´Ð»Ñ Ð²Ð¸Ñ
msgid "Creates branch '%{branch_name}' and a merge request to resolve this issue."
msgstr "Створює гілку \"%{branch_name}\" та запит на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ð´Ð»Ñ Ð²Ð¸Ñ€Ñ–ÑˆÐµÐ½Ð½Ñ Ñ†Ñ–Ñ”Ñ— проблеми."
+msgid "Creating"
+msgstr ""
+
msgid "Creating epic"
msgstr "Ð¡Ñ‚Ð²Ð¾Ñ€ÐµÐ½Ð½Ñ ÐµÐ¿Ñ–ÐºÑƒ"
@@ -6822,7 +7001,10 @@ msgstr "Поточний пароль"
msgid "Current vulnerabilities count"
msgstr "Поточна кількіÑÑ‚ÑŒ вразливоÑтей"
-msgid "CurrentUser|Buy CI minutes"
+msgid "CurrentUser|Buy Pipeline minutes"
+msgstr ""
+
+msgid "CurrentUser|One of your groups is running out"
msgstr ""
msgid "CurrentUser|Profile"
@@ -6837,6 +7019,9 @@ msgstr "Розпочати пробну верÑÑ–ÑŽ Gold"
msgid "CurrentUser|Upgrade"
msgstr ""
+msgid "Custom Attributes"
+msgstr "ВлаÑні атрибути"
+
msgid "Custom CI configuration path"
msgstr "КориÑтувацький шлÑÑ… до конфігурації CI"
@@ -6892,7 +7077,7 @@ msgid "CustomCycleAnalytics|Select stop event"
msgstr "Вибрати завершальну подію"
msgid "CustomCycleAnalytics|Stage name already exists"
-msgstr ""
+msgstr "Ім'Ñ Ñтадії вже Ñ–Ñнує"
msgid "CustomCycleAnalytics|Start event"
msgstr "Початкова подіÑ"
@@ -6940,7 +7125,7 @@ msgid "Customize your pipeline configuration."
msgstr "Ðалаштувати конфігурацію конвеєра."
msgid "Cycle Time"
-msgstr ""
+msgstr "Ð§Ð°Ñ Ñ†Ð¸ÐºÐ»Ñƒ"
msgid "CycleAnalyticsEvent|Issue closed"
msgstr "Задачу закрито"
@@ -7102,12 +7287,18 @@ msgstr "Випадаючий ÑпиÑок Ñтадій"
msgid "DAG"
msgstr "DAG"
+msgid "DAG visualization requires at least 3 dependent jobs."
+msgstr ""
+
msgid "DNS"
msgstr "DNS"
msgid "Dashboard"
msgstr "Панель керуваннÑ"
+msgid "Dashboard uid not found"
+msgstr ""
+
msgid "DashboardProjects|All"
msgstr "Ð’ÑÑ–"
@@ -7132,6 +7323,9 @@ msgstr "Ðе вдалоÑÑ Ð´Ð¾Ð´Ð°Ñ‚Ð¸ %{invalidProjects}. Ð¦Ñ Ð¿Ð°Ð½ÐµÐ»ÑŒ Ð
msgid "Data is still calculating..."
msgstr "Дані вÑе ще обчиÑлюютьÑÑ..."
+msgid "Datasource name not found"
+msgstr ""
+
msgid "Date"
msgstr "Дата"
@@ -7312,6 +7506,9 @@ msgstr "Видалити гілку-джерело"
msgid "Delete this attachment"
msgstr "Видалити це вкладеннÑ"
+msgid "Delete user list"
+msgstr ""
+
msgid "Delete variable"
msgstr "Видалити змінну"
@@ -7594,7 +7791,7 @@ msgid "DeployTokens|Revoke"
msgstr "Відкликати"
msgid "DeployTokens|Revoke %{b_start}%{name}%{b_end}?"
-msgstr ""
+msgstr "Відкликати %{b_start}%{name}%{b_end}?"
msgid "DeployTokens|Revoke %{name}"
msgstr "Відкликати %{name}"
@@ -7638,6 +7835,9 @@ msgstr "Розгорнуто на"
msgid "Deploying to"
msgstr "Ð Ð¾Ð·Ð³Ð¾Ñ€Ñ‚Ð°Ð½Ð½Ñ Ð´Ð¾"
+msgid "Deploying to AWS is easy with GitLab"
+msgstr ""
+
msgid "Deployment Frequency"
msgstr "ЧаÑтота РозгортаннÑ"
@@ -7708,7 +7908,7 @@ msgid "DesignManagement|Are you sure you want to cancel changes to this comment?
msgstr ""
msgid "DesignManagement|Are you sure you want to cancel creating this comment?"
-msgstr ""
+msgstr "Ви впевнені, що хочете ÑкаÑувати ÑÑ‚Ð²Ð¾Ñ€ÐµÐ½Ð½Ñ Ñ†ÑŒÐ¾Ð³Ð¾ коментарÑ?"
msgid "DesignManagement|Are you sure you want to delete the selected designs?"
msgstr "Ви впевнені, що хочете видалити вибрані дизайни?"
@@ -7732,13 +7932,13 @@ msgid "DesignManagement|Comments you resolve can be viewed and unresolved by goi
msgstr ""
msgid "DesignManagement|Could not add a new comment. Please try again."
-msgstr ""
+msgstr "Ðе вдалоÑÑ Ð´Ð¾Ð´Ð°Ñ‚Ð¸ новий коментар. Будь лаÑка, Ñпробуйте ще раз."
msgid "DesignManagement|Could not create new discussion. Please try again."
-msgstr ""
+msgstr "Ðе вдалоÑÑ Ñтворити нове обговореннÑ. Будь лаÑка, Ñпробуйте ще раз."
msgid "DesignManagement|Could not update discussion. Please try again."
-msgstr ""
+msgstr "Ðе вдалоÑÑ Ð¾Ð½Ð¾Ð²Ð¸Ñ‚Ð¸ обговореннÑ. Будь лаÑка, Ñпробуйте ще раз."
msgid "DesignManagement|Could not update note. Please try again."
msgstr ""
@@ -7831,7 +8031,7 @@ msgid "Detect host keys"
msgstr "ВиÑÐ²Ð»ÐµÐ½Ð½Ñ ÐºÐ»ÑŽÑ‡Ñ–Ð² хоÑта"
msgid "DevOps Score"
-msgstr ""
+msgstr "Оцінка DevOps"
msgid "Diff content limits"
msgstr "ÐžÐ±Ð¼ÐµÐ¶ÐµÐ½Ð½Ñ Ð¿Ð¾Ñ€Ñ–Ð²Ð½ÑÐ½Ð½Ñ Ð·Ð¼Ñ–Ñту"
@@ -8002,6 +8202,9 @@ msgstr "Відхилено в конвеєрі %{pipelineLink}"
msgid "Dismissed on pipeline %{pipelineLink} at %{projectLink}"
msgstr "Відхилено в конвеєрі %{pipelineLink} в %{projectLink}"
+msgid "Display alerts from all your monitoring tools directly within GitLab."
+msgstr ""
+
msgid "Display name"
msgstr "Ð†Ð¼â€™Ñ Ð´Ð»Ñ Ð²Ñ–Ð´Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð½Ñ"
@@ -8059,9 +8262,6 @@ msgstr "Ðе вÑтавлÑйте приватну чаÑтину GPG ключа
msgid "Don't show again"
msgstr "Ðе показувати знову"
-msgid "Don't worry, you can access this tour by clicking on the help icon in the top right corner and choose <strong>Learn GitLab</strong>."
-msgstr "Ðе хвилюйтеÑÑ, ви завжди зможете отримати доÑтуп до цієї екÑкурÑÑ–Ñ—, натиÑнувши кнопку довідки у верхньому правому куті Ñ– вибравши <strong>ДізнатиÑÑ Ð¿Ñ€Ð¾ GitLab</strong>."
-
msgid "Done"
msgstr "Готово"
@@ -8188,6 +8388,9 @@ msgstr "Редагувати Розклад Конвеєра %{id}"
msgid "Edit Release"
msgstr "Редагувати Реліз"
+msgid "Edit Slack integration"
+msgstr ""
+
msgid "Edit Snippet"
msgstr "Редагувати Ñніпет"
@@ -8227,6 +8430,9 @@ msgstr "Редагувати ідентифікацію Ð´Ð»Ñ %{user_name}"
msgid "Edit issues"
msgstr "Редагувати задачі"
+msgid "Edit iteration"
+msgstr ""
+
msgid "Edit public deploy key"
msgstr "Редагувати публічний ключ Ð´Ð»Ñ Ñ€Ð¾Ð·Ð³Ð¾Ñ€Ñ‚Ð°Ð½Ð½Ñ"
@@ -8275,6 +8481,9 @@ msgstr "Ðемає. Виберіть проекти Ð´Ð»Ñ Ñ–Ð½Ð´ÐµÐºÑуванÐ
msgid "Email"
msgstr "Електронна пошта"
+msgid "Email Notification"
+msgstr ""
+
msgid "Email address"
msgstr "ÐдреÑа електронної пошти"
@@ -8287,6 +8496,9 @@ msgstr ""
msgid "Email not verified. Please verify your email in Salesforce."
msgstr "Електронну пошту не перевірено. Будь лаÑка, перевірте Ñ—Ñ— в Salesforce."
+msgid "Email notification for unknown sign-ins"
+msgstr ""
+
msgid "Email patch"
msgstr "Email-патч"
@@ -8518,6 +8730,9 @@ msgstr "ЗавершуєтьÑÑ Ð¾ (за Грінвічем)"
msgid "Enforce DNS rebinding attack protection"
msgstr "ПримуÑово активувати захиÑÑ‚ від DNS rebinding"
+msgid "Enforce personal access token expiration"
+msgstr ""
+
msgid "Ensure connectivity is available from the GitLab server to the Prometheus server"
msgstr "ПереконайтеÑÑ, що між Ñерверами GitLab та Prometheus Ñ–Ñнує з’єднаннÑ"
@@ -8587,6 +8802,9 @@ msgstr "Введіть назву запиту на злиттÑ"
msgid "Enter the name of your application, and we'll return a unique %{type}."
msgstr ""
+msgid "Enter weights for storages for new repositories."
+msgstr ""
+
msgid "Enter your password to approve"
msgstr "Введіть пароль Ð´Ð»Ñ Ð·Ð°Ñ‚Ð²ÐµÑ€Ð´Ð¶ÐµÐ½Ð½Ñ"
@@ -8594,17 +8812,29 @@ msgid "Environment"
msgstr "Середовище"
msgid "Environment does not have deployments"
+msgstr "Середовище не має розгортань"
+
+msgid "Environment is required for Stages::MetricEndpointInserter"
+msgstr ""
+
+msgid "Environment is required for Stages::VariableEndpointInserter"
msgstr ""
msgid "Environment scope"
msgstr ""
+msgid "Environment variables are applied to all project environments in this instance via the Runner. You can use environment variables for passwords, secret keys, etc. Make variables available to the running application by prepending the variable key with <code>K8S_SECRET_</code>. You can set variables to be:"
+msgstr ""
+
msgid "Environment variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. Additionally, they can be masked so they are hidden in job logs, though they must match certain regexp requirements to do so. You can use environment variables for passwords, secret keys, or whatever you want."
msgstr "Змінні Ñередовища передаютьÑÑ Ð´Ð¾ Ñередовища через runner. Вони можуть бути захищеними, тобто бути видимими лише Ð´Ð»Ñ Ð·Ð°Ñ…Ð¸Ñ‰ÐµÐ½Ð¸Ñ… гілок та тегів. Додатково, вони можуть бути замаÑковані, щоб не відображатиÑÑ Ñƒ журналі завдань, але в цьому випадку вони мають задовільнÑти певним регулÑними виразам. Ви можете викориÑтовувати змінні Ñередовища Ð´Ð»Ñ Ð¿Ð°Ñ€Ð¾Ð»Ñ–Ð², Ñекретних ключів тощо."
msgid "Environment variables are configured by your administrator to be %{link_start}protected%{link_end} by default"
msgstr "Змінні Ñередовища налаштовані адмініÑтратором бути %{link_start}захищеними%{link_end} за замовчуваннÑм"
+msgid "Environment variables on this GitLab instance are configured to be %{link_start}protected%{link_end} by default"
+msgstr ""
+
msgid "Environment:"
msgstr "Середовище:"
@@ -8615,7 +8845,7 @@ msgid "EnvironmentDashboard|Created through the Deployment API"
msgstr "Створено через Deployment API"
msgid "EnvironmentDashboard|You are looking at the last updated environment"
-msgstr ""
+msgstr "Ви переглÑдаєте оÑтаннє оновлене Ñередовище"
msgid "Environments"
msgstr "Середовища"
@@ -8645,7 +8875,7 @@ msgid "EnvironmentsDashboard|More actions"
msgstr "Додаткові дії"
msgid "EnvironmentsDashboard|More information"
-msgstr ""
+msgstr "Детальніше"
msgid "EnvironmentsDashboard|Remove"
msgstr "Видалити"
@@ -8654,7 +8884,7 @@ msgid "EnvironmentsDashboard|The environments dashboard provides a summary of ea
msgstr "Панель ÐºÐµÑ€ÑƒÐ²Ð°Ð½Ð½Ñ Ñередовищами міÑтить інформацію про Ñтан кожного із проектних Ñередовищ включно зі Ñтаном конвеєрів та попереджень."
msgid "EnvironmentsDashboard|This dashboard displays a maximum of 7 projects and 3 environments per project. %{readMoreLink}"
-msgstr ""
+msgstr "Ð¦Ñ Ð¿Ð°Ð½ÐµÐ»ÑŒ ÐºÐµÑ€ÑƒÐ²Ð°Ð½Ð½Ñ Ð²Ñ–Ð´Ð¾Ð±Ñ€Ð°Ð¶Ð°Ñ” до 7 проєктів Ñ– 3 Ñередовищ на проєкт. %{readMoreLink}"
msgid "Environments|An error occurred while canceling the auto stop, please try again"
msgstr ""
@@ -8690,7 +8920,7 @@ msgid "Environments|Commit"
msgstr "Коміт"
msgid "Environments|Currently showing %{fetched} results."
-msgstr ""
+msgstr "Ðаразі відображаєтьÑÑ %{fetched} результатів."
msgid "Environments|Currently showing all results."
msgstr ""
@@ -8702,7 +8932,7 @@ msgid "Environments|Delete environment"
msgstr "Видалити Ñередовище"
msgid "Environments|Deleting the '%{environmentName}' environment cannot be undone. Do you want to delete it anyway?"
-msgstr ""
+msgstr "Ð’Ð¸Ð´Ð°Ð»ÐµÐ½Ð½Ñ Ñередовища '%{environmentName}' незворотнє. Ви вÑе одно хочете його видалити?"
msgid "Environments|Deploy to..."
msgstr "Розгортати до..."
@@ -8810,7 +9040,7 @@ msgid "Environments|Stopping"
msgstr "Зупинка"
msgid "Environments|There was an error fetching the logs. Please try again."
-msgstr ""
+msgstr "СталаÑÑ Ð¿Ð¾Ð¼Ð¸Ð»ÐºÐ° під Ñ‡Ð°Ñ Ð¾Ñ‚Ñ€Ð¸Ð¼Ð°Ð½Ð½Ñ Ð»Ð¾Ð³Ñ–Ð². Будь лаÑка, Ñпробуйте ще раз."
msgid "Environments|This action will relaunch the job for commit %{commit_id}, putting the environment in a previous version. Are you sure you want to continue?"
msgstr "Ð¦Ñ Ð´Ñ–Ñ Ð¿Ð¾Ð²Ñ‚Ð¾Ñ€Ð½Ð¾ запуÑтить Ð·Ð°Ð²Ð´Ð°Ð½Ð½Ñ Ð´Ð»Ñ ÐºÐ¾Ð¼Ñ–Ñ‚Ñƒ %{commit_id}, повертаючи Ñередовище до попередньої верÑÑ–Ñ—. Ви впевнені, що хочете продовжити?"
@@ -9107,7 +9337,7 @@ msgid "ErrorTracking|Connection has failed. Re-check Auth Token and try again."
msgstr "Ð—â€™Ñ”Ð´Ð½Ð°Ð½Ð½Ñ Ð½ÐµÐ²Ð´Ð°Ð»Ðµ. Перевірте токер автентифікації та Ñпробуйте знову."
msgid "ErrorTracking|If you self-host Sentry, enter the full URL of your Sentry instance. If you're using Sentry's hosted solution, enter https://sentry.io"
-msgstr ""
+msgstr "Якщо ви розміщуєте Sentry ÑамоÑтійно, введіть повну URL-адреÑу вашого інÑтанÑу Sentry. Якщо ви викориÑтовуєте Ñ€Ñ–ÑˆÐµÐ½Ð½Ñ Ð½Ð° інфраÑтруктурі Sentry, введіть https://sentry.io"
msgid "ErrorTracking|No projects available"
msgstr "Ðемає доÑтупних проектів"
@@ -9136,6 +9366,9 @@ msgstr "Фільтрувати по вÑім"
msgid "EventFilterBy|Filter by comments"
msgstr "Фільтрувати по коментарÑм"
+msgid "EventFilterBy|Filter by designs"
+msgstr ""
+
msgid "EventFilterBy|Filter by epic events"
msgstr "Фільтрувати за подіÑми епіків"
@@ -9268,15 +9501,15 @@ msgstr "Розгорнути вÑе"
msgid "Expand approvers"
msgstr "Розгорнути ÑпиÑок затверджуючих оÑіб"
-msgid "Expand child epics"
-msgstr "Розгорнути дочірні епіки"
-
msgid "Expand down"
msgstr "Розгорнути вниз"
msgid "Expand dropdown"
msgstr "Розгорнути випадаючий ÑпиÑок"
+msgid "Expand milestones"
+msgstr ""
+
msgid "Expand sidebar"
msgstr "Розгорніть бічну панель"
@@ -9292,6 +9525,9 @@ msgstr "Термін дії"
msgid "Expiration date"
msgstr "Термін дії"
+msgid "Expiration not enforced"
+msgstr ""
+
msgid "Expiration policy for the Container Registry is a perfect solution for keeping the Registry space down while still enjoying the full power of GitLab CI/CD."
msgstr "Політика Ð·Ð°ÐºÑ–Ð½Ñ‡ÐµÐ½Ð½Ñ Ñ‚ÐµÑ€Ð¼Ñ–Ð½Ñƒ дії Ð´Ð»Ñ Container Registry - це ідеальне Ñ€Ñ–ÑˆÐµÐ½Ð½Ñ Ð´Ð»Ñ ÐµÐºÐ¾Ð½Ð¾Ð¼Ñ–Ñ— проÑтору в РеєÑтрі при цьому зі збереженнÑм вÑієї потужноÑÑ‚Ñ– GitLab CI/CD. "
@@ -9412,6 +9648,9 @@ msgstr "Ðевдало"
msgid "Failed Jobs"
msgstr "Провалені завданнÑ"
+msgid "Failed on"
+msgstr ""
+
msgid "Failed to add a Zoom meeting"
msgstr "Ðе вдалоÑÑ Ð´Ð¾Ð´Ð°Ñ‚Ð¸ Zoom-зуÑтріч"
@@ -9422,13 +9661,13 @@ msgid "Failed to assign a user because no user was found."
msgstr "Ðе вдалоÑÑ Ð¿Ñ€Ð¸Ð·Ð½Ð°Ñ‡Ð¸Ñ‚Ð¸ кориÑтувача, тому що жодного кориÑтувача не було знайдено."
msgid "Failed to cancel auto stop because failed to update the environment."
-msgstr ""
+msgstr "Ðе вдалоÑÑ ÑкаÑувати автоматичну зупинку, оÑкільки не вдалоÑÑ Ð¾Ð½Ð¾Ð²Ð¸Ñ‚Ð¸ Ñередовище."
msgid "Failed to cancel auto stop because the environment is not set as auto stop."
msgstr ""
msgid "Failed to cancel auto stop because you do not have permission to update the environment."
-msgstr ""
+msgstr "Ðе вдалоÑÑ ÑкаÑувати автоматичну зупинку, оÑкільки ви не маєте дозволу на Ð¾Ð½Ð¾Ð²Ð»ÐµÐ½Ð½Ñ Ñередовища."
msgid "Failed to change the owner"
msgstr "Ðе вдалоÑÑ Ð·Ð¼Ñ–Ð½Ð¸Ñ‚Ð¸ влаÑника"
@@ -9472,14 +9711,20 @@ msgstr "Ðе вдалоÑÑ Ð¾Ñ‚Ñ€Ð¸Ð¼Ð°Ñ‚Ð¸ ref."
msgid "Failed to install."
msgstr "Ðе вдалоÑÑ Ð²Ñтановити."
+msgid "Failed to load assignees. Please try again."
+msgstr ""
+
+msgid "Failed to load authors. Please try again."
+msgstr ""
+
msgid "Failed to load emoji list."
msgstr "Ðе вдалоÑÑ Ð·Ð°Ð²Ð°Ð½Ñ‚Ð°Ð¶Ð¸Ñ‚Ð¸ ÑпиÑок Ñмайликів."
msgid "Failed to load error details from Sentry."
msgstr "Ðе вдалоÑÑ Ð·Ð°Ð²Ð°Ð½Ñ‚Ð°Ð¶Ð¸Ñ‚Ð¸ деталі помилок із Sentry."
-msgid "Failed to load errors from Sentry. Error message: %{errorMessage}"
-msgstr "Ðе вдалоÑÑ Ð·Ð°Ð²Ð°Ð½Ñ‚Ð°Ð¶Ð¸Ñ‚Ð¸ помилки із Sentry. Ð Ñаме: %{errorMessage}"
+msgid "Failed to load errors from Sentry."
+msgstr "Помилка при завантаженні помилок із Sentry."
msgid "Failed to load group activity metrics. Please try again."
msgstr ""
@@ -9559,6 +9804,9 @@ msgstr "Ðе вдалоÑÑ Ð·Ð±ÐµÑ€ÐµÐ³Ñ‚Ð¸ налаштуваннÑ."
msgid "Failed to set due date because the date format is invalid."
msgstr "Ðе вдалоÑÑ Ð²Ñтановити заплановану дату Ð²Ð¸ÐºÐ¾Ð½Ð°Ð½Ð½Ñ Ñ‚Ð¾Ð¼Ñƒ що формат дати Ñ” неправильним."
+msgid "Failed to set iteration on this issue. Please try again."
+msgstr ""
+
msgid "Failed to signing using smartcard authentication"
msgstr "Ðе вдалоÑÑ Ð¿Ñ–Ð´Ð¿Ð¸Ñати з викориÑтаннÑм перевірки автентичноÑÑ‚Ñ– Ñмарт-карти"
@@ -9752,7 +10000,7 @@ msgid "FeatureFlags|New feature flag"
msgstr "Ðовий перемикач функції"
msgid "FeatureFlags|New list"
-msgstr ""
+msgstr "Ðовий ÑпиÑок"
msgid "FeatureFlags|Percent rollout (logged in users)"
msgstr "Процент Ñ€Ð¾Ð·Ð³Ð¾Ñ€Ñ‚Ð°Ð½Ð½Ñ (за кориÑтувачами, що здійÑнили вхід)"
@@ -9781,15 +10029,12 @@ msgstr ""
msgid "FeatureFlags|Target environments"
msgstr "Цільові Ñередовища"
-msgid "FeatureFlags|There are no active feature flags"
-msgstr "Ðемає активних перемикачів функцій"
-
-msgid "FeatureFlags|There are no inactive feature flags"
-msgstr "Ðемає неактивних перемикачів функцій"
-
msgid "FeatureFlags|There was an error fetching the feature flags."
msgstr "Помилка при отриманні перемикачів функцій."
+msgid "FeatureFlags|There was an error retrieving user lists"
+msgstr ""
+
msgid "FeatureFlags|Try again in a few moments or contact your support team."
msgstr "Повторіть Ñпробу через деÑкий Ñ‡Ð°Ñ Ð°Ð±Ð¾ звернітьÑÑ Ð´Ð¾ вашої Ñлужби підтримки."
@@ -9799,9 +10044,18 @@ msgstr "Ідентифікатори кориÑтувачів"
msgid "FeatureFlag|Delete strategy"
msgstr ""
+msgid "FeatureFlag|List"
+msgstr "СпиÑок"
+
msgid "FeatureFlag|Percentage"
msgstr "ВідÑоток"
+msgid "FeatureFlag|Select a user list"
+msgstr "Виберіть ÑпиÑок кориÑтувачів"
+
+msgid "FeatureFlag|There are no configured user lists"
+msgstr ""
+
msgid "FeatureFlag|Type"
msgstr "Тип"
@@ -9958,6 +10212,12 @@ msgstr "Відфільтрувати ваші проекти за іменами
msgid "Filter..."
msgstr "Фільтр..."
+msgid "Find File"
+msgstr ""
+
+msgid "Find bugs in your code with coverage-guided fuzzing"
+msgstr ""
+
msgid "Find by path"
msgstr "Пошук по шлÑху"
@@ -10211,7 +10471,7 @@ msgid "Geo Nodes"
msgstr "Гео-Вузли"
msgid "Geo Nodes|Cannot remove a primary node if there is a secondary node"
-msgstr ""
+msgstr "Ðеможливо видалити оÑновний вузол, Ñкщо Ñ” вторинний"
msgid "Geo Replication"
msgstr ""
@@ -10222,6 +10482,9 @@ msgstr ""
msgid "Geo allows you to replicate your GitLab instance to other geographical locations."
msgstr "Geo дозволÑÑ” вам реплікувати ваш інÑÑ‚Ð°Ð½Ñ GitLab у інших географічних локаціÑÑ…."
+msgid "Geo nodes are paused using a command run on the node"
+msgstr ""
+
msgid "GeoNodeStatusEvent|%{timeAgoStr} (%{pendingEvents} events)"
msgstr "%{timeAgoStr} (%{pendingEvents} подій)"
@@ -10301,19 +10564,22 @@ msgid "GeoNodes|Node was successfully removed."
msgstr "Вузол уÑпішно видалено."
msgid "GeoNodes|Node's status was updated %{timeAgo}."
-msgstr ""
+msgstr "Ð¡Ñ‚Ð°Ñ‚ÑƒÑ Ð²ÑƒÐ·Ð»Ð° оновлено %{timeAgo}."
msgid "GeoNodes|Not checksummed"
msgstr "Без контрольної Ñуми"
-msgid "GeoNodes|Pausing replication stops the sync process. Are you sure?"
+msgid "GeoNodes|Package files"
msgstr ""
+msgid "GeoNodes|Pausing replication stops the sync process. Are you sure?"
+msgstr "ÐŸÑ€Ð¸Ð·ÑƒÐ¿Ð¸Ð½ÐµÐ½Ð½Ñ Ñ€ÐµÐ¿Ð»Ñ–ÐºÐ°Ñ†Ñ–Ñ— зупинÑÑ” Ð¿Ñ€Ð¾Ñ†ÐµÑ Ñинхронізації. Ви впевнені?"
+
msgid "GeoNodes|Removing a Geo primary node stops the synchronization to all nodes. Are you sure?"
-msgstr ""
+msgstr "Ð’Ð¸Ð´Ð°Ð»ÐµÐ½Ð½Ñ Ð¾Ñновного вузла Geo зупинить Ñинхронізацію вÑÑ–Ñ… вузлів. Ви впевнені?"
msgid "GeoNodes|Removing a Geo secondary node stops the synchronization to that node. Are you sure?"
-msgstr ""
+msgstr "Ð’Ð¸Ð´Ð°Ð»ÐµÐ½Ð½Ñ Ð²Ñ‚Ð¾Ñ€Ð¸Ð½Ð½Ð¾Ð³Ð¾ вузла Geo зупинÑÑ” Ñинхронізацію з цим вузлом. Ви впевнені?"
msgid "GeoNodes|Replicated data is verified with the %{nodeText} using checksums"
msgstr ""
@@ -10324,6 +10590,9 @@ msgstr "Слот реплікації WAL"
msgid "GeoNodes|Replication slots"
msgstr "Слоти реплікації"
+msgid "GeoNodes|Replication status"
+msgstr ""
+
msgid "GeoNodes|Repositories"
msgstr "Репозиторії"
@@ -10517,7 +10786,7 @@ msgid "Geo|Reverify"
msgstr "Повторно перевірити"
msgid "Geo|Reverify all"
-msgstr ""
+msgstr "Повторно перевірити вÑе"
msgid "Geo|Review replication status, and resynchronize and reverify items with the primary node."
msgstr ""
@@ -10534,6 +10803,9 @@ msgstr "Синхронізовано"
msgid "Geo|Synchronization failed - %{error}"
msgstr "Ð¡Ð¸Ð½Ñ…Ñ€Ð¾Ð½Ñ–Ð·Ð°Ñ†Ñ–Ñ Ð½ÐµÐ²Ð´Ð°Ð»Ð°: %{error}"
+msgid "Geo|Synchronization of %{itemTitle} is disabled."
+msgstr ""
+
msgid "Geo|The database is currently %{db_lag} behind the primary node."
msgstr "База даних зараз %{db_lag} позаду оÑновного вузла."
@@ -10772,7 +11044,7 @@ msgid "GitLabPages|Save"
msgstr "Зберегти"
msgid "GitLabPages|Something went wrong while obtaining the Let's Encrypt certificate for %{domain}. To retry visit your %{link_start}domain details%{link_end}."
-msgstr ""
+msgstr "ЩоÑÑŒ пішло не так при отриманні Ñертифікату Let's Encrypt Ð´Ð»Ñ %{domain}. Перевірте %{link_start}деталі домену%{link_end}, щоб повторити Ñпробу."
msgid "GitLabPages|Support for domains and certificates is disabled. Ask your system's administrator to enable it."
msgstr "Підтримку доменів та Ñертифікатів вимкнено. ЗвернітьÑÑ Ð´Ð¾ адмініÑтратора Ð´Ð»Ñ Ñ‚Ð¾Ð³Ð¾, щоб Ñ—Ñ… увімкнути."
@@ -10787,7 +11059,7 @@ msgid "GitLabPages|Verified"
msgstr "Підтверджено"
msgid "GitLabPages|When using Pages under the general domain of a GitLab instance (%{pages_host}), you cannot use HTTPS with sub-subdomains. This means that if your username/groupname contains a dot it will not work. This is a limitation of the HTTP Over TLS protocol. HTTP pages will continue to work provided you don't redirect HTTP to HTTPS."
-msgstr ""
+msgstr "ВикориÑтовуючи Pages під загальним доменом інÑтанÑу GitLab (%{pages_host}), ви не можете викориÑтовувати HTTPS через піддомени. Це означає, що Ñкщо ваше ім'Ñ ÐºÐ¾Ñ€Ð¸Ñтувача/групи міÑтить крапку, воно не Ñпрацює. Це Ð¾Ð±Ð¼ÐµÐ¶ÐµÐ½Ð½Ñ Ð¿Ñ€Ð¾Ñ‚Ð¾ÐºÐ¾Ð»Ñƒ HTTP Over TLS. HTTP-Ñторінки продовжать працювати, Ñкщо ви не перенаправлÑєте HTTP на HTTPS."
msgid "GitLabPages|With GitLab Pages you can host your static websites on GitLab. Combined with the power of GitLab CI and the help of GitLab Runner you can deploy static pages for your individual projects, your user or your group."
msgstr "За допомогою GitLab Pages ви можете розміщувати Ñвої Ñтатичні Ñайти на GitLab. Разом із можливоÑÑ‚Ñми GitLab CI та за допомогою Gitlab Runner'ів ми можете розгортати Ñтатичні Ñторінки Ð´Ð»Ñ Ð²Ð°ÑˆÐ¸Ñ… окремих проектів, кориÑтувача чи групи."
@@ -10846,6 +11118,9 @@ msgstr "Ðа повний екран"
msgid "Go to %{link_to_google_takeout}."
msgstr "Перейти до %{link_to_google_takeout}."
+msgid "Go to <strong>Issues</strong> > <strong>Boards</strong> to access your personalized learning issue board."
+msgstr ""
+
msgid "Go to Pipelines"
msgstr "Перейти до конвеєрів"
@@ -10945,9 +11220,6 @@ msgstr "Перейти до ваших проектів"
msgid "Go to your snippets"
msgstr "Перейти до ваших Ñніпетів"
-msgid "Golden Tanuki"
-msgstr "Золотий Танукі"
-
msgid "Google Cloud Platform"
msgstr "Google Cloud Platform"
@@ -10969,6 +11241,9 @@ msgstr "Зрозуміло!"
msgid "Grafana URL"
msgstr "URL-адреÑа Grafana"
+msgid "Grafana response contains invalid json"
+msgstr ""
+
msgid "GrafanaIntegration|API Token"
msgstr "Токен API"
@@ -11017,9 +11292,6 @@ msgstr "Групу %{group_name} було призначено Ð´Ð»Ñ Ð²Ð¸Ð´Ð°Ð»
msgid "Group %{group_name} was successfully created."
msgstr "Групу %{group_name} уÑпішно Ñтворено."
-msgid "Group '%{group_name}' is being imported."
-msgstr ""
-
msgid "Group Audit Events"
msgstr ""
@@ -11056,9 +11328,6 @@ msgstr "Ðватар групи"
msgid "Group by:"
msgstr ""
-msgid "Group could not be imported: %{errors}"
-msgstr ""
-
msgid "Group description"
msgstr "ÐžÐ¿Ð¸Ñ Ð³Ñ€ÑƒÐ¿Ð¸"
@@ -11081,14 +11350,20 @@ msgid "Group export started. A download link will be sent by email and made avai
msgstr ""
msgid "Group has been already marked for deletion"
-msgstr ""
+msgstr "Група вже позначена Ð´Ð»Ñ Ð²Ð¸Ð´Ð°Ð»ÐµÐ½Ð½Ñ"
msgid "Group has not been marked for deletion"
+msgstr "Група не була позначена Ð´Ð»Ñ Ð²Ð¸Ð´Ð°Ð»ÐµÐ½Ð½Ñ"
+
+msgid "Group import could not be scheduled"
msgstr ""
msgid "Group info:"
msgstr "Ð†Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ñ–Ñ Ð¿Ñ€Ð¾ групу:"
+msgid "Group is required when cluster_type is :group"
+msgstr ""
+
msgid "Group maintainers can register group runners in the %{link}"
msgstr "Керівники групи можуть зареєÑтрувати групові runner'и через %{link}"
@@ -11123,7 +11398,7 @@ msgid "Group project URLs are prefixed with the group namespace"
msgstr ""
msgid "Group requires separate account"
-msgstr ""
+msgstr "Група вимагає окремий обліковий запиÑ"
msgid "Group variables (inherited)"
msgstr "Змінні групи (уÑпадковані)"
@@ -11150,13 +11425,31 @@ msgid "GroupActivyMetrics|Merge Requests created"
msgstr ""
msgid "GroupActivyMetrics|Recent activity (last 90 days)"
+msgstr "ОÑÑ‚Ð°Ð½Ð½Ñ Ð°ÐºÑ‚Ð¸Ð²Ð½Ñ–ÑÑ‚ÑŒ (за оÑтанні 90 днів)"
+
+msgid "GroupImport|Failed to import group."
+msgstr ""
+
+msgid "GroupImport|Group '%{group_name}' is being imported."
+msgstr ""
+
+msgid "GroupImport|Group could not be imported: %{errors}"
+msgstr ""
+
+msgid "GroupImport|Please wait while we import the group for you. Refresh at will."
+msgstr ""
+
+msgid "GroupImport|The group was successfully imported."
+msgstr ""
+
+msgid "GroupImport|Unable to process group import file"
msgstr ""
msgid "GroupRoadmap|%{dateWord} – No end date"
msgstr ""
msgid "GroupRoadmap|%{startDateInWords} – %{endDateInWords}"
-msgstr ""
+msgstr "%{startDateInWords} – %{endDateInWords}"
msgid "GroupRoadmap|No start date – %{dateWord}"
msgstr ""
@@ -11210,7 +11503,7 @@ msgid "GroupSAML|Generate a SCIM token to set up your System for Cross-Domain Id
msgstr "Згенерувати токен SCIM Ð´Ð»Ñ Ð½Ð°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ ÑиÑтеми ÐºÐµÑ€ÑƒÐ²Ð°Ð½Ð½Ñ Ð¼Ñ–Ð¶Ð´Ð¾Ð¼ÐµÐ½Ð½Ð¾ÑŽ ідентифікацією."
msgid "GroupSAML|Identity"
-msgstr ""
+msgstr "ІдентифікаціÑ"
msgid "GroupSAML|Identity provider single sign on URL"
msgstr "URL провайдера ідентифікації Ð´Ð»Ñ Ñ”Ð´Ð¸Ð½Ð¾Ð³Ð¾ входу"
@@ -11419,6 +11712,9 @@ msgstr "Групи (%{count})"
msgid "Groups (%{groups})"
msgstr "Групи (%{groups})"
+msgid "Groups and projects"
+msgstr ""
+
msgid "Groups can also be nested by creating %{subgroup_docs_link_start}subgroups%{subgroup_docs_link_end}."
msgstr "Групи також можуть бути вкладеними при викориÑтанні %{subgroup_docs_link_start}підгруп%{subgroup_docs_link_end}."
@@ -11464,6 +11760,33 @@ msgstr "Групи не знайдені"
msgid "GroupsEmptyState|You can manage your group member’s permissions and access to each project in the group."
msgstr "Ви можете керувати правами доÑтупу членів групи мати доÑтуп до кожного проекту в ній."
+msgid "GroupsNew|Contact an administrator to enable options for importing your group."
+msgstr ""
+
+msgid "GroupsNew|Create"
+msgstr ""
+
+msgid "GroupsNew|Create group"
+msgstr ""
+
+msgid "GroupsNew|GitLab group export"
+msgstr ""
+
+msgid "GroupsNew|Import"
+msgstr ""
+
+msgid "GroupsNew|Import group"
+msgstr ""
+
+msgid "GroupsNew|My Awesome Group"
+msgstr ""
+
+msgid "GroupsNew|No import options available"
+msgstr ""
+
+msgid "GroupsNew|To copy a GitLab group between installations, navigate to the group settings page for the original installation, generate an export file, and upload it here."
+msgstr ""
+
msgid "GroupsTree|Are you sure you want to leave the \"%{fullName}\" group?"
msgstr "Ви впевнені, що хочете залишити групу \"%{fullName}\"?"
@@ -11489,7 +11812,7 @@ msgid "GroupsTree|No groups matched your search"
msgstr "Жодна группа не задовольнÑÑ” параметрам вашого запиту"
msgid "GroupsTree|No groups or projects matched your search"
-msgstr ""
+msgstr "Жодна группа чи проєкт не задовольнÑÑ” параметрам вашого запиту"
msgid "GroupsTree|Search by name"
msgstr "Пошук за іменем"
@@ -11566,6 +11889,9 @@ msgstr "Допомагає зменшити об’єм попереджень (
msgid "Helps reduce request volume for protected paths"
msgstr "Допомагає зменшити об’єм запитів Ð´Ð»Ñ Ð·Ð°Ñ…Ð¸Ñ‰ÐµÐ½Ð¸Ñ… шлÑхів"
+msgid "Here are all your projects in your group, including the one you just created. To start, let’s take a look at your personalized learning project which will help you learn about GitLab at your own pace."
+msgstr ""
+
msgid "Here you will find recent merge request activity"
msgstr ""
@@ -11582,6 +11908,9 @@ msgstr[1] ""
msgstr[2] ""
msgstr[3] ""
+msgid "Hide details"
+msgstr ""
+
msgid "Hide file browser"
msgstr "Сховати файловий менеджер"
@@ -11591,6 +11920,9 @@ msgstr "Приховати проекти групи"
msgid "Hide host keys manual input"
msgstr "Сховати ввід ключів хоÑта"
+msgid "Hide list"
+msgstr ""
+
msgid "Hide marketing-related entries from help"
msgstr "Сховати маркетингові розділи із допомоги"
@@ -11613,9 +11945,6 @@ msgstr[3] "Сховати значень"
msgid "Hide values"
msgstr "Сховати значеннÑ"
-msgid "Hiding all labels"
-msgstr "ÐŸÑ€Ð¸Ñ…Ð¾Ð²ÑƒÐ²Ð°Ð½Ð½Ñ Ð²ÑÑ–Ñ… міток"
-
msgid "High or unknown vulnerabilities present"
msgstr "ПриÑутні вразливоÑÑ‚Ñ– виÑокого або невідомого рівнÑ"
@@ -11632,7 +11961,7 @@ msgid "History of authentications"
msgstr "ІÑÑ‚Ð¾Ñ€Ñ–Ñ Ð°Ð²Ñ‚ÐµÐ½Ñ‚Ð¸Ñ„Ñ–ÐºÐ°Ñ†Ñ–Ð¹"
msgid "Homepage"
-msgstr ""
+msgstr "Ð”Ð¾Ð¼Ð°ÑˆÐ½Ñ Ñторінка"
msgid "Hook execution failed. Ensure the group has a project with commits."
msgstr "Ðевдале Ð²Ð¸ÐºÐ¾Ð½Ð°Ð½Ð½Ñ Ñ…ÑƒÐºÐ°. ПереконайтеÑÑ, що Ñ†Ñ Ð³Ñ€ÑƒÐ¿Ð° має проект із комітами."
@@ -11644,7 +11973,7 @@ msgid "Hook was successfully updated."
msgstr "Хук уÑпішно оновлено."
msgid "Hostname"
-msgstr ""
+msgstr "Ім'Ñ Ñ…Ð¾Ñта"
msgid "Hour (UTC)"
msgstr "Година (UTC)"
@@ -11700,6 +12029,9 @@ msgstr "ID"
msgid "ID:"
msgstr "ID:"
+msgid "IDE"
+msgstr ""
+
msgid "IDE|Allow live previews of JavaScript projects in the Web IDE using CodeSandbox Live Preview."
msgstr ""
@@ -11740,7 +12072,7 @@ msgid "IDE|This option is disabled because you are not allowed to create merge r
msgstr "Ð¦Ñ Ð¾Ð¿Ñ†Ñ–Ñ Ð²Ð¸Ð¼ÐºÐ½ÐµÐ½Ð°, тому що ви не можете Ñтворювати запити на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ð² цьому проєкті."
msgid "IDE|This option is disabled because you don't have write permissions for the current branch."
-msgstr ""
+msgstr "Ð¦Ñ Ð¾Ð¿Ñ†Ñ–Ñ Ð²Ð¸Ð¼ÐºÐ½ÐµÐ½Ð°, тому що ви не маєте дозволу на Ð·Ð°Ð¿Ð¸Ñ Ð² поточну гілку."
msgid "INFO: Your SSH key has expired. Please generate a new key."
msgstr ""
@@ -11790,12 +12122,12 @@ msgstr "Якщо це відключено, то рівень доÑтупу бÑ
msgid "If enabled"
msgstr "Якщо увімкнено"
+msgid "If enabled, GitLab will handle Object Storage replication using Geo. %{linkStart}More information%{linkEnd}"
+msgstr ""
+
msgid "If enabled, access to projects will be validated on an external service using their classification label."
msgstr "Якщо це дозволено, доÑтуп до проектів буде перевірÑтиÑÑ Ð·Ð¾Ð²Ð½Ñ–ÑˆÐ½ÑŒÐ¾ÑŽ Ñлужбою з викориÑтаннÑм Ñ—Ñ… мітки клаÑифікації."
-msgid "If enabled, and if object storage is enabled, GitLab will handle Object Storage replication using Geo"
-msgstr ""
-
msgid "If there is no previous license or if the previous license has expired, some GitLab functionality will be blocked until a new, valid license is uploaded."
msgstr ""
@@ -12010,7 +12342,7 @@ msgid "In order to gather accurate feature usage data, it can take 1 to 2 weeks
msgstr "Збір точних даних про викориÑÑ‚Ð°Ð½Ð½Ñ Ð´Ð»Ñ Ð²Ñ–Ð´Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð½Ñ Ð²Ð°ÑˆÐ¾Ð³Ð¾ індекÑу може зайнÑти від 1 до 2 тижнів."
msgid "In order to personalize your experience with GitLab<br>we would like to know a bit more about you."
-msgstr ""
+msgstr "Ð”Ð»Ñ Ñ‚Ð¾Ð³Ð¾, щоб перÑоналізувати ваш доÑвід роботи з GitLab<br>,ми хотіли б дізнатиÑÑ Ð±Ñ–Ð»ÑŒÑˆÐµ про ваÑ."
msgid "In order to tailor your experience with GitLab we<br>would like to know a bit more about you."
msgstr "Ð”Ð»Ñ Ñ‚Ð¾Ð³Ð¾, щоб адаптувати GitLab до Ð²Ð°Ñ Ð½Ð°Ð¼<br>потрібно більше про Ð²Ð°Ñ Ð´Ñ–Ð·Ð½Ð°Ñ‚Ð¸ÑÑ."
@@ -12157,6 +12489,9 @@ msgstr[1] "ІнÑтанÑів"
msgstr[2] "ІнÑтанÑів"
msgstr[3] "ІнÑтанÑів"
+msgid "Instance Configuration"
+msgstr ""
+
msgid "Instance Statistics visibility"
msgstr "СтатиÑтика видимоÑÑ‚Ñ– інÑтанÑа"
@@ -12178,11 +12513,8 @@ msgstr "ÐÐ°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ð†Ð½Ñ‚ÐµÐ³Ñ€Ð°Ñ†Ñ–Ñ—"
msgid "Integrations"
msgstr "Інтеграції"
-msgid "Integrations allow you to integrate GitLab with other applications"
-msgstr ""
-
msgid "Integrations|All details"
-msgstr ""
+msgstr "Ð’ÑÑ– деталі"
msgid "Integrations|Comment detail:"
msgstr ""
@@ -12230,7 +12562,7 @@ msgid "Introducing Value Stream Analytics"
msgstr ""
msgid "Introducing Your DevOps Score"
-msgstr ""
+msgstr "ПредÑтавлÑємо вашу оцінку DevOps"
msgid "Invalid Git ref"
msgstr "ÐедійÑне поÑÐ¸Ð»Ð°Ð½Ð½Ñ Git"
@@ -12251,7 +12583,7 @@ msgid "Invalid cursor parameter"
msgstr ""
msgid "Invalid cursor value provided"
-msgstr ""
+msgstr "Ðадано неправильне Ð·Ð½Ð°Ñ‡ÐµÐ½Ð½Ñ ÐºÑƒÑ€Ñора"
msgid "Invalid date"
msgstr "Ðеправильна дата"
@@ -12292,6 +12624,9 @@ msgstr ""
msgid "Invalid query"
msgstr "Ðеправильний запит"
+msgid "Invalid repository bundle for snippet with id %{snippet_id}"
+msgstr ""
+
msgid "Invalid repository path"
msgstr "Ðеправильний шлÑÑ… до репозиторію"
@@ -12310,6 +12645,9 @@ msgstr ""
msgid "Invalid two-factor code."
msgstr "ÐедійÑний двофакторний код підтвердженнÑ."
+msgid "Invalid yaml"
+msgstr ""
+
msgid "Invitation"
msgstr "ЗапрошеннÑ"
@@ -12368,10 +12706,10 @@ msgid "Issue Boards"
msgstr "Дошки Ð¾Ð±Ð³Ð¾Ð²Ð¾Ñ€ÐµÐ½Ð½Ñ Ð·Ð°Ð´Ð°Ñ‡"
msgid "Issue already promoted to epic."
-msgstr ""
+msgstr "Задачу вже переведено до епіку."
msgid "Issue cannot be found."
-msgstr ""
+msgstr "Ðеможливо знайти задачу."
msgid "Issue events"
msgstr "Задачі"
@@ -12383,7 +12721,7 @@ msgid "Issue label"
msgstr "Мітка задачі"
msgid "Issue or Merge Request ID is required"
-msgstr ""
+msgstr "Потрібен ідентифікатор задачі або запиту на злиттÑ"
msgid "Issue published on status page."
msgstr ""
@@ -12401,10 +12739,10 @@ msgid "Issue weight"
msgstr "Вага задачі"
msgid "IssueAnalytics|Age"
-msgstr ""
+msgstr "Вік"
msgid "IssueAnalytics|Assignees"
-msgstr ""
+msgstr "Виконавці"
msgid "IssueAnalytics|Due date"
msgstr ""
@@ -12413,19 +12751,19 @@ msgid "IssueAnalytics|Failed to load issues. Please try again."
msgstr ""
msgid "IssueAnalytics|Issue"
-msgstr ""
+msgstr "Задача"
msgid "IssueAnalytics|Milestone"
-msgstr ""
+msgstr "Етап"
msgid "IssueAnalytics|Opened by"
msgstr ""
msgid "IssueAnalytics|Status"
-msgstr ""
+msgstr "СтатуÑ"
msgid "IssueAnalytics|Weight"
-msgstr ""
+msgstr "Вага"
msgid "IssueBoards|Board"
msgstr "Дошка"
@@ -12467,7 +12805,7 @@ msgid "Issues"
msgstr "Задачі"
msgid "Issues Analytics"
-msgstr ""
+msgstr "Ðналітика Задач"
msgid "Issues Rate Limits"
msgstr ""
@@ -12529,15 +12867,24 @@ msgstr "Має ÑкладатиÑÑ Ñ–Ð· Ñ€Ñдка заголовка Ñ– мін
msgid "It seems like the Dependency Scanning job ran successfully, but no dependencies have been detected in your project."
msgstr ""
+msgid "It seems that there is currently no available data for code coverage"
+msgstr ""
+
msgid "It's you"
msgstr "Це ви"
+msgid "Iteration"
+msgstr ""
+
msgid "Iteration changed to"
msgstr ""
msgid "Iteration removed"
msgstr ""
+msgid "Iteration updated"
+msgstr ""
+
msgid "Iterations"
msgstr ""
@@ -12568,12 +12915,18 @@ msgstr "Ñіч."
msgid "January"
msgstr "Ñічень"
+msgid "Jira Issues"
+msgstr ""
+
msgid "Jira import is already running."
msgstr ""
msgid "Jira integration not configured."
msgstr ""
+msgid "Jira project key is not configured"
+msgstr ""
+
msgid "Jira project: %{importProject}"
msgstr ""
@@ -12749,11 +13102,14 @@ msgid "June"
msgstr "червень"
msgid "Just me"
-msgstr ""
+msgstr "Лише Ñ"
msgid "Keep divergent refs"
msgstr ""
+msgid "Kerberos access denied"
+msgstr ""
+
msgid "Key"
msgstr "Ключ"
@@ -12763,10 +13119,13 @@ msgstr "Ключ (PEM)"
msgid "Key: %{key}"
msgstr "Ключ: %{key}"
-msgid "Keyboard Shortcuts"
+msgid "Keyboard shortcuts"
msgstr "Комбінації клавіш"
-msgid "Keyboard shortcuts"
+msgid "Keys"
+msgstr ""
+
+msgid "Ki"
msgstr ""
msgid "Kubernetes"
@@ -12782,7 +13141,7 @@ msgid "Kubernetes Clusters"
msgstr "КлаÑтери Kubernetes"
msgid "Kubernetes cluster"
-msgstr ""
+msgstr "КлаÑтер Kubernetes"
msgid "Kubernetes cluster creation time exceeds timeout; %{timeout}"
msgstr "ÐŸÐµÑ€ÐµÐ²Ð¸Ñ‰ÐµÐ½Ð½Ñ Ð»Ñ–Ð¼Ñ–Ñ‚Ñƒ чаÑу при Ñтворенні Kubernetes-клаÑтера; %{timeout}"
@@ -12808,6 +13167,9 @@ msgstr "Підказка Kubernetes"
msgid "LDAP"
msgstr "LDAP"
+msgid "LDAP Synchronization"
+msgstr ""
+
msgid "LDAP settings"
msgstr "ÐÐ°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ LDAP"
@@ -12939,7 +13301,7 @@ msgid "Last repository check (%{last_check_timestamp}) failed. See the 'repochec
msgstr ""
msgid "Last repository check run"
-msgstr ""
+msgstr "ОÑтанній запуÑк перевірки репозиторію"
msgid "Last seen"
msgstr "ОÑÑ‚Ð°Ð½Ð½Ñ Ð°ÐºÑ‚Ð¸Ð²Ð½Ñ–ÑÑ‚ÑŒ"
@@ -13034,12 +13396,18 @@ msgstr "ДізнайтеÑÑ Ð±Ñ–Ð»ÑŒÑˆÐµ про затвердженнÑ."
msgid "Learn more about custom project templates"
msgstr "Докладніше про влаÑні шаблони проектів"
+msgid "Learn more about deploying to AWS"
+msgstr ""
+
msgid "Learn more about deploying to a cluster"
msgstr "ДізнайтеÑÑ Ð±Ñ–Ð»ÑŒÑˆÐµ про Ñ€Ð¾Ð·Ð³Ð¾Ñ€Ñ‚Ð°Ð½Ð½Ñ Ð² клаÑтер"
msgid "Learn more about group-level project templates"
msgstr "Докладніше про шаблони проектів на рівні групи"
+msgid "Learn more about job dependencies"
+msgstr ""
+
msgid "Learn more about signing commits"
msgstr "Докладніше про підпиÑÑƒÐ²Ð°Ð½Ð½Ñ ÐºÐ¾Ð¼Ñ–Ñ‚Ñ–Ð²"
@@ -13091,14 +13459,26 @@ msgstr "ВідповідніÑÑ‚ÑŒ ліцензіÑм"
msgid "License History"
msgstr ""
+msgid "License ID:"
+msgstr ""
+
+msgid "License URL"
+msgstr ""
+
msgid "License-Check"
msgstr "License-Check"
+msgid "LicenseCompliance|%{docLinkStart}License Approvals%{docLinkEnd} are active"
+msgstr ""
+
+msgid "LicenseCompliance|%{docLinkStart}License Approvals%{docLinkEnd} are inactive"
+msgstr ""
+
msgid "LicenseCompliance|Add a license"
msgstr "Додати ліцензію"
msgid "LicenseCompliance|Add license and related policy"
-msgstr ""
+msgstr "Додати ліцензію та відповідну політику"
msgid "LicenseCompliance|Allow"
msgstr "Дозволити"
@@ -13115,9 +13495,15 @@ msgstr ""
msgid "LicenseCompliance|Deny"
msgstr ""
+msgid "LicenseCompliance|Learn more about %{linkStart}License Approvals%{linkEnd}"
+msgstr ""
+
msgid "LicenseCompliance|License"
msgstr "ЛіцензіÑ"
+msgid "LicenseCompliance|License Approvals"
+msgstr ""
+
msgid "LicenseCompliance|License Compliance detected %d license and policy violation for the source branch only; approval required"
msgid_plural "LicenseCompliance|License Compliance detected %d licenses and policy violations for the source branch only; approval required"
msgstr[0] ""
@@ -13207,7 +13593,7 @@ msgid "Licenses"
msgstr "Ліцензії"
msgid "Licenses|%{remainingComponentsCount} more"
-msgstr ""
+msgstr "Ще %{remainingComponentsCount}"
msgid "Licenses|Component"
msgstr "Компонент"
@@ -13216,7 +13602,7 @@ msgid "Licenses|Components"
msgstr "Компоненти"
msgid "Licenses|Detected in Project"
-msgstr ""
+msgstr "ВиÑвлено в проєкті"
msgid "Licenses|Detected licenses that are out-of-compliance with the project's assigned policies"
msgstr ""
@@ -13225,22 +13611,22 @@ msgid "Licenses|Displays licenses detected in the project, based on the %{linkSt
msgstr ""
msgid "Licenses|Error fetching the license list. Please check your network connection and try again."
-msgstr ""
+msgstr "Помилка під Ñ‡Ð°Ñ Ð¾Ñ‚Ñ€Ð¸Ð¼Ð°Ð½Ð½Ñ ÑпиÑку ліцензій. Будь лаÑка, перевірте Ð¿Ñ–Ð´ÐºÐ»ÑŽÑ‡ÐµÐ½Ð½Ñ Ð´Ð¾ мережі та Ñпробуйте ще раз."
msgid "Licenses|Learn more about license compliance"
msgstr ""
msgid "Licenses|License Compliance"
-msgstr ""
+msgstr "ВідповідніÑÑ‚ÑŒ Ліцензії"
msgid "Licenses|Name"
msgstr "Ðазва"
msgid "Licenses|Policies"
-msgstr ""
+msgstr "Політики"
msgid "Licenses|Policy"
-msgstr ""
+msgstr "Політика"
msgid "Licenses|Policy violation: denied"
msgstr ""
@@ -13249,10 +13635,10 @@ msgid "Licenses|Specified policies in this project"
msgstr ""
msgid "Licenses|The license list details information about the licenses used within your project."
-msgstr ""
+msgstr "СпиÑок ліцензій міÑтить детальну інформацію про Ñ‚Ñ–, Ñкі викориÑтовуютьÑÑ Ñƒ вашому проєкті."
msgid "Licenses|View license details for your project"
-msgstr ""
+msgstr "ПереглÑд деталей ліцензії Ð´Ð»Ñ Ð²Ð°ÑˆÐ¾Ð³Ð¾ проєкту"
msgid "License|Buy license"
msgstr "Придбати ліцензію"
@@ -13348,6 +13734,9 @@ msgstr "Режим ÑпиÑку"
msgid "List your Bitbucket Server repositories"
msgstr "СпиÑко репозиторіїв вашого Bitbucket Server"
+msgid "Lists"
+msgstr ""
+
msgid "Live preview"
msgstr "Попередній переглÑд"
@@ -13531,6 +13920,9 @@ msgstr ""
msgid "Manage project labels"
msgstr "ÐšÐµÑ€ÑƒÐ²Ð°Ð½Ð½Ñ Ð¼Ñ–Ñ‚ÐºÐ°Ð¼Ð¸ проекту"
+msgid "Manage storage usage"
+msgstr ""
+
msgid "Manage two-factor authentication"
msgstr "ÐšÐµÑ€ÑƒÐ²Ð°Ð½Ð½Ñ Ð´Ð²Ð¾Ñ„Ð°ÐºÑ‚Ð¾Ñ€Ð½Ð¾ÑŽ автентифікацією"
@@ -13546,6 +13938,9 @@ msgstr "МаніфеÑÑ‚"
msgid "Manifest file import"
msgstr "Імпортувати файл маніфеÑту"
+msgid "Manifest import"
+msgstr ""
+
msgid "Manual job"
msgstr "Ручне завданнÑ"
@@ -13667,7 +14062,7 @@ msgid "Maximum Users:"
msgstr "МакÑимальна КількіÑÑ‚ÑŒ КориÑтувачів:"
msgid "Maximum allowable lifetime for personal access token (days)"
-msgstr ""
+msgstr "МакÑимально допуÑтимий термін дії токена перÑонального доÑтупу (днів)"
msgid "Maximum artifacts size (MB)"
msgstr "МакÑимальний розмір артефактів (МБ)"
@@ -13693,6 +14088,12 @@ msgstr ""
msgid "Maximum field length"
msgstr ""
+msgid "Maximum file size is 2MB. Please select a smaller file."
+msgstr ""
+
+msgid "Maximum import size (MB)"
+msgstr "МакÑимальний розмір Ð´Ð»Ñ Ñ–Ð¼Ð¿Ð¾Ñ€Ñ‚Ñƒ (МБ)"
+
msgid "Maximum job timeout"
msgstr "МакÑимальний Ñ‡Ð°Ñ Ð²Ð¸ÐºÐ¾Ð½Ð°Ð½Ð½Ñ Ð·Ð°Ð²Ð´Ð°Ð½Ð½Ñ"
@@ -13732,6 +14133,9 @@ msgstr ""
msgid "Maximum size of Elasticsearch bulk indexing requests."
msgstr ""
+msgid "Maximum size of import files."
+msgstr ""
+
msgid "Maximum size of individual attachments in comments."
msgstr ""
@@ -13783,6 +14187,9 @@ msgstr "ЗлиттÑ"
msgid "Merge (when the pipeline succeeds)"
msgstr "Злити (коли конвеєр уÑпішно завершитьÑÑ)"
+msgid "Merge Conflicts"
+msgstr ""
+
msgid "Merge Request"
msgstr "Запит на злиттÑ"
@@ -13909,6 +14316,9 @@ msgstr "Помилка при збереженні коментарÑ"
msgid "MergeRequests|Squash task canceled: another squash is already in progress."
msgstr "ÐžÐ±â€™Ñ”Ð´Ð½Ð°Ð½Ð½Ñ ÐºÐ¾Ð¼Ñ–Ñ‚Ñ–Ð² відхилено: інше Ð¾Ð±â€™Ñ”Ð´Ð½Ð°Ð½Ð½Ñ Ð²Ð¶Ðµ відбуваєтьÑÑ."
+msgid "MergeRequests|This project does not allow squashing commits when merge requests are accepted."
+msgstr ""
+
msgid "MergeRequests|Thread stays resolved"
msgstr "ÐžÐ±Ð³Ð¾Ð²Ð¾Ñ€ÐµÐ½Ð½Ñ Ð·Ð°Ð»Ð¸ÑˆÐ°Ñ”Ñ‚ÑŒÑÑ Ð·Ð°ÐºÑ€Ð¸Ñ‚Ð¸Ð¼"
@@ -14062,6 +14472,12 @@ msgstr ""
msgid "MetricsSettings|Add a button to the metrics dashboard linking directly to your existing external dashboard."
msgstr ""
+msgid "MetricsSettings|Choose whether to display dashboard metrics in UTC or the user's local timezone."
+msgstr ""
+
+msgid "MetricsSettings|Dashboard timezone"
+msgstr ""
+
msgid "MetricsSettings|External dashboard URL"
msgstr ""
@@ -14071,6 +14487,12 @@ msgstr ""
msgid "MetricsSettings|Metrics Dashboard"
msgstr ""
+msgid "MetricsSettings|UTC (Coordinated Universal Time)"
+msgstr ""
+
+msgid "MetricsSettings|User's local timezone"
+msgstr ""
+
msgid "Metrics|Add metric"
msgstr "Додати метрику"
@@ -14086,6 +14508,9 @@ msgstr ""
msgid "Metrics|Create metric"
msgstr "Створити метрику"
+msgid "Metrics|Current"
+msgstr ""
+
msgid "Metrics|Delete metric"
msgstr "Видалити метрику"
@@ -14138,6 +14563,9 @@ msgstr ""
msgid "Metrics|Max"
msgstr "МакÑ"
+msgid "Metrics|Min"
+msgstr ""
+
msgid "Metrics|Must be a valid PromQL query."
msgstr "Має бути коректним запитом PromQL."
@@ -14153,14 +14581,17 @@ msgstr "Ð”Ð¾ÐºÑƒÐ¼ÐµÐ½Ñ‚Ð°Ñ†Ñ–Ñ Ð¿Ð¾ запитам Prometheus"
msgid "Metrics|Refresh dashboard"
msgstr ""
+msgid "Metrics|Select a value"
+msgstr ""
+
msgid "Metrics|Star dashboard"
msgstr ""
msgid "Metrics|There was an error creating the dashboard."
-msgstr ""
+msgstr "СталаÑÑ Ð¿Ð¾Ð¼Ð¸Ð»ÐºÐ° під Ñ‡Ð°Ñ ÑÑ‚Ð²Ð¾Ñ€ÐµÐ½Ð½Ñ Ð¿Ð°Ð½ÐµÐ»Ñ– керуваннÑ."
msgid "Metrics|There was an error creating the dashboard. %{error}"
-msgstr ""
+msgstr "СталаÑÑ Ð¿Ð¾Ð¼Ð¸Ð»ÐºÐ° під Ñ‡Ð°Ñ ÑÑ‚Ð²Ð¾Ñ€ÐµÐ½Ð½Ñ Ð¿Ð°Ð½ÐµÐ»Ñ– керуваннÑ. %{error}"
msgid "Metrics|There was an error fetching annotations. Please try again."
msgstr ""
@@ -14171,12 +14602,18 @@ msgstr "ТрапилаÑÑ Ð¿Ð¾Ð¼Ð¸Ð»ÐºÐ° під Ñ‡Ð°Ñ Ð¾Ñ‚Ñ€Ð¸Ð¼Ð°Ð½Ð½Ñ Ñ–Ð½Ñ
msgid "Metrics|There was an error getting annotations information."
msgstr ""
+msgid "Metrics|There was an error getting dashboard validation warnings information."
+msgstr ""
+
msgid "Metrics|There was an error getting deployment information."
msgstr "ТрапилаÑÑ Ð¿Ð¾Ð¼Ð¸Ð»ÐºÐ° під Ñ‡Ð°Ñ Ð¾Ñ‚Ñ€Ð¸Ð¼Ð°Ð½Ð½Ñ Ñ–Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ñ–Ñ— про розгортаннÑ."
msgid "Metrics|There was an error getting environments information."
msgstr "ТрапилаÑÑ Ð¿Ð¾Ð¼Ð¸Ð»ÐºÐ° під Ñ‡Ð°Ñ Ð¾Ñ‚Ñ€Ð¸Ð¼Ð°Ð½Ð½Ñ Ñ–Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ñ–Ñ— про Ñередовища."
+msgid "Metrics|There was an error getting options for variable \"%{name}\"."
+msgstr ""
+
msgid "Metrics|There was an error trying to validate your query"
msgstr "Помилка при перевірці вашого запиту"
@@ -14184,7 +14621,7 @@ msgid "Metrics|There was an error while retrieving metrics"
msgstr "ТрапилаÑÑ Ð¿Ð¾Ð¼Ð¸Ð»ÐºÐ° під Ñ‡Ð°Ñ Ð¾Ñ‚Ñ€Ð¸Ð¼Ð°Ð½Ð½Ñ Ð¼ÐµÑ‚Ñ€Ð¸Ðº"
msgid "Metrics|There was an error while retrieving metrics. %{message}"
-msgstr ""
+msgstr "СталаÑÑ Ð¿Ð¾Ð¼Ð¸Ð»ÐºÐ° під Ñ‡Ð°Ñ Ð¾Ñ‚Ñ€Ð¸Ð¼Ð°Ð½Ð½Ñ Ð¼ÐµÑ‚Ñ€Ð¸Ðº. %{message}"
msgid "Metrics|Unexpected deployment data response from prometheus endpoint"
msgstr "Ðеочікувана відповідь про Ñ€Ð¾Ð·Ð³Ð¾Ñ€Ñ‚Ð°Ð½Ð½Ñ Ð²Ñ–Ð´ кінцевої точки Prometheus"
@@ -14214,11 +14651,14 @@ msgid "Metrics|Y-axis label"
msgstr "Ðазва оÑÑ– Y"
msgid "Metrics|You can save a copy of this dashboard to your repository so it can be customized. Select a file name and branch to save it."
-msgstr ""
+msgstr "Ви можете зберегти копію цієї панелі ÐºÐµÑ€ÑƒÐ²Ð°Ð½Ð½Ñ Ð´Ð¾ вашого репозиторію так, щоб Ñ—Ñ— можна було налаштувати. Виберіть ім'Ñ Ñ„Ð°Ð¹Ð»Ñƒ Ñ– гілку Ð´Ð»Ñ Ñ—Ñ— збереженнÑ."
msgid "Metrics|You're about to permanently delete this metric. This cannot be undone."
msgstr "Ви збираєтеÑÑ Ð¾Ñтаточно видалити цю метрику. Це не можна відмінити."
+msgid "Metrics|Your dashboard schema is invalid. Edit the dashboard to correct the YAML schema."
+msgstr ""
+
msgid "Metrics|e.g. HTTP requests"
msgstr "напр. HTTP-запити"
@@ -14234,12 +14674,18 @@ msgstr "напр. rate(http_requests_total[5m])"
msgid "Metrics|e.g. req/sec"
msgstr "напр. зап/Ñек"
+msgid "Mi"
+msgstr ""
+
msgid "Microsoft Azure"
msgstr "Microsoft Azure"
msgid "Middleman project with Static Site Editor support"
msgstr ""
+msgid "Migrate your data from an external source like GitHub, Bitbucket, or another instance of GitLab."
+msgstr ""
+
msgid "Migrated %{success_count}/%{total_count} files."
msgstr "ПеренеÑено %{success_count}/%{total_count} файлів."
@@ -14260,34 +14706,34 @@ msgid "Milestone lists show all issues from the selected milestone."
msgstr "У ÑпиÑках етапу відображаютьÑÑ Ð²ÑÑ– задачі Ð´Ð»Ñ Ð²Ð¸Ð±Ñ€Ð°Ð½Ð¾Ð³Ð¾ етапу."
msgid "MilestoneSidebar|Closed:"
-msgstr ""
+msgstr "Закрито:"
msgid "MilestoneSidebar|Copy reference"
-msgstr ""
+msgstr "Копіювати поÑиланнÑ"
msgid "MilestoneSidebar|Due date"
msgstr ""
msgid "MilestoneSidebar|Edit"
-msgstr ""
+msgstr "Редагувати"
msgid "MilestoneSidebar|From"
msgstr ""
msgid "MilestoneSidebar|Issues"
-msgstr ""
+msgstr "Задачі"
msgid "MilestoneSidebar|Merge requests"
msgstr ""
msgid "MilestoneSidebar|Merged:"
-msgstr ""
+msgstr "Об'єднано:"
msgid "MilestoneSidebar|New Issue"
-msgstr ""
+msgstr "Ðова задача"
msgid "MilestoneSidebar|New issue"
-msgstr ""
+msgstr "Ðова задача"
msgid "MilestoneSidebar|No due date"
msgstr ""
@@ -14376,6 +14822,9 @@ msgstr ""
msgid "Minimum capacity to be available before we schedule more mirrors preemptively."
msgstr "Мінімальна доÑтупна пропуÑкна здатніÑÑ‚ÑŒ необхідна Ð´Ð»Ñ Ð·Ð°Ð¿ÑƒÑку попередньої Ñинхронізації дзеркал."
+msgid "Minimum interval in days"
+msgstr ""
+
msgid "Minimum length is %{minimum_password_length} characters"
msgstr "Мінімально довжина Ñкладає %{minimum_password_length} Ñимволів"
@@ -14431,7 +14880,7 @@ msgid "MissingSSHKeyWarningLink|Don't show again"
msgstr "Більше не показувати"
msgid "MissingSSHKeyWarningLink|You won't be able to pull or push project code via SSH until you add an SSH key to your profile"
-msgstr ""
+msgstr "Ви не зможете відправлÑти або отримувати код проєкту за допомогою SSH, поки не додаÑте SSH ключ до вашого профілю"
msgid "ModalButton|Add projects"
msgstr ""
@@ -14443,7 +14892,7 @@ msgid "Modal|Close"
msgstr "Закрити"
msgid "Modified"
-msgstr ""
+msgstr "Змінено"
msgid "Modified in this version"
msgstr "Змінено в цій верÑÑ–Ñ—"
@@ -14562,6 +15011,12 @@ msgstr "Знайдено декілька типів моделей: %{model_typ
msgid "Multiple uploaders found: %{uploader_types}"
msgstr "Знайдено декілька завантажувачів: %{uploader_types}"
+msgid "Must match with the %{codeStart}external_url%{codeEnd} in %{codeStart}/etc/gitlab/gitlab.rb%{codeEnd}."
+msgstr ""
+
+msgid "Must match with the %{codeStart}geo_node_name%{codeEnd} in %{codeStart}/etc/gitlab/gitlab.rb%{codeEnd}. %{linkStart}More information%{linkEnd}"
+msgstr ""
+
msgid "My Awesome Group"
msgstr ""
@@ -14644,7 +15099,7 @@ msgid "NetworkPolicies|Last modified"
msgstr ""
msgid "NetworkPolicies|Name"
-msgstr ""
+msgstr "Ðазва"
msgid "NetworkPolicies|No policies detected"
msgstr ""
@@ -14676,10 +15131,16 @@ msgstr "Ðовий"
msgid "New Application"
msgstr "Ðовий додаток"
+msgid "New Branch"
+msgstr ""
+
+msgid "New Deploy Key"
+msgstr ""
+
msgid "New Environment"
msgstr "Ðове Ñередовище"
-msgid "New Geo Node"
+msgid "New File"
msgstr ""
msgid "New Group"
@@ -14698,15 +15159,15 @@ msgstr[1] "Ðові задачі"
msgstr[2] "Ðових задач"
msgstr[3] "Ðових задач"
-msgid "New Iteration"
-msgstr ""
-
msgid "New Jira import"
msgstr ""
msgid "New Label"
msgstr "Ðова мітка"
+msgid "New Merge Request"
+msgstr ""
+
msgid "New Milestone"
msgstr "Ðовий етап"
@@ -14720,11 +15181,14 @@ msgid "New Pipeline Schedule"
msgstr "Ðовий розклад Конвеєра"
msgid "New Project"
-msgstr ""
+msgstr "Ðовий Проєкт"
msgid "New Snippet"
msgstr "Ðовий Ñніпет"
+msgid "New User"
+msgstr ""
+
msgid "New branch"
msgstr "Ðова гілка"
@@ -14767,6 +15231,9 @@ msgstr "Ðова задача"
msgid "New issue title"
msgstr "Ðазва нової задачі"
+msgid "New iteration"
+msgstr ""
+
msgid "New iteration created"
msgstr ""
@@ -14786,7 +15253,7 @@ msgid "New pipelines will cancel older, pending pipelines on the same branch"
msgstr "Ðові конвеєри ÑкаÑують Ñтарі, що очікують на тій же гілці"
msgid "New project"
-msgstr ""
+msgstr "Ðовий проєкт"
msgid "New release"
msgstr "Ðовий реліз"
@@ -14828,7 +15295,7 @@ msgid "Next"
msgstr "Далі"
msgid "Next commit"
-msgstr ""
+msgstr "ÐаÑтупний коміт"
msgid "Next file in diff"
msgstr ""
@@ -14944,6 +15411,12 @@ msgstr ""
msgid "No grouping"
msgstr ""
+msgid "No iteration"
+msgstr ""
+
+msgid "No iterations to show"
+msgstr ""
+
msgid "No job log"
msgstr "Ðемає журналу завданнÑ"
@@ -14951,7 +15424,7 @@ msgid "No jobs to show"
msgstr "Ðемає завдань Ð´Ð»Ñ Ð²Ñ–Ð´Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð½Ñ"
msgid "No label"
-msgstr ""
+msgstr "Без мітки"
msgid "No labels with such name or description"
msgstr "Ðемає міток з таким іменем або опиÑом"
@@ -14986,6 +15459,9 @@ msgstr "Ðемає етапів Ð´Ð»Ñ Ð¿Ð¾ÐºÐ°Ð·Ñƒ"
msgid "No other labels with such name or description"
msgstr "Ðемає інших міток з таким іменем або опиÑом"
+msgid "No panels matching properties %{opts}"
+msgstr ""
+
msgid "No parent group"
msgstr "Ðемає батьківÑької групи"
@@ -15019,6 +15495,9 @@ msgstr "Runner'ів не знайдено"
msgid "No schedules"
msgstr "Ðемає розкладів"
+msgid "No stack trace for this error"
+msgstr ""
+
msgid "No starrers matched your search"
msgstr "Ðіхто з тих, хто додав в обране не задовільнÑÑ” вашому пошуку"
@@ -15031,8 +15510,8 @@ msgstr "Шаблон відÑутній"
msgid "No test coverage"
msgstr ""
-msgid "No thanks, don't show this again"
-msgstr "ÐÑ–, дÑкую, більше не показуйте мені цього"
+msgid "No thanks"
+msgstr ""
msgid "No vulnerabilities present"
msgstr "ВразливоÑÑ‚Ñ– відÑутні"
@@ -15041,14 +15520,11 @@ msgid "No webhooks found, add one in the form above."
msgstr "Ðе знайдено жодного вебхука, додайте його у формі вище."
msgid "No worries, you can still use all the %{strong}%{plan_name}%{strong_close} features for now. You have %{remaining_days} to renew your subscription."
-msgstr ""
+msgstr "Ðе хвилюйтеÑÑ, на даний момент ви можете викориÑтовувати вÑÑ– функції %{strong}%{plan_name}%{strong_close}. Ви маєте %{remaining_days} щоб продовжити підпиÑку."
msgid "No, directly import the existing email addresses and usernames."
msgstr "ÐÑ–, безпоÑередньо імпортувати Ñ–Ñнуючі адреÑи електронної пошти та імена кориÑтувачів."
-msgid "No, not interested right now"
-msgstr "ÐÑ–, зараз не цікаво"
-
msgid "No. of commits"
msgstr ""
@@ -15094,9 +15570,6 @@ msgstr "ÐедоÑтатньо даних"
msgid "Not found."
msgstr "Ðе знайдено."
-msgid "Not helpful"
-msgstr "Ðе кориÑна"
-
msgid "Not now"
msgstr "Пізніше"
@@ -15253,6 +15726,9 @@ msgstr "Ð¡Ð¿Ð¾Ð²Ñ–Ñ‰ÐµÐ½Ð½Ñ Ð²Ð¸Ð¼ÐºÐ½ÐµÐ½Ð¾"
msgid "Notifications on"
msgstr "Ð¡Ð¿Ð¾Ð²Ñ–Ñ‰ÐµÐ½Ð½Ñ ÑƒÐ²Ñ–Ð¼ÐºÐ½ÐµÐ½Ð¾"
+msgid "Notify users by email when sign-in location is not recognized"
+msgstr ""
+
msgid "Nov"
msgstr "лиÑÑ‚."
@@ -15319,9 +15795,6 @@ msgstr "Фільтр"
msgid "Oh no!"
msgstr "О ні!"
-msgid "Ok let's go"
-msgstr "Гаразд, уперед"
-
msgid "Oldest first"
msgstr ""
@@ -15334,17 +15807,50 @@ msgstr ""
msgid "On track"
msgstr ""
+msgid "OnDemandScans|Attached branch"
+msgstr ""
+
+msgid "OnDemandScans|Attached branch is where the scan job runs."
+msgstr ""
+
+msgid "OnDemandScans|Could not run the scan. Please try again."
+msgstr ""
+
msgid "OnDemandScans|Create new DAST scan"
msgstr ""
+msgid "OnDemandScans|DAST will scan the target URL and any discovered sub URLs."
+msgstr ""
+
+msgid "OnDemandScans|New on-demand DAST scan"
+msgstr ""
+
msgid "OnDemandScans|On-demand Scans"
msgstr ""
+msgid "OnDemandScans|On-demand scans run outside the DevOps cycle and find vulnerabilities in your projects. %{learnMoreLinkStart}Learn more%{learnMoreLinkEnd}"
+msgstr ""
+
+msgid "OnDemandScans|Only a passive scan can be performed on demand."
+msgstr ""
+
+msgid "OnDemandScans|Passive DAST Scan"
+msgstr ""
+
+msgid "OnDemandScans|Please enter a valid URL format, ex: http://www.example.com/home"
+msgstr ""
+
+msgid "OnDemandScans|Run this scan"
+msgstr ""
+
+msgid "OnDemandScans|Scan mode"
+msgstr ""
+
msgid "OnDemandScans|Schedule or run scans immediately against target sites. Currently available on-demand scan type: DAST. %{helpLinkStart}More information%{helpLinkEnd}"
msgstr ""
-msgid "Onboarding"
-msgstr "ОзнайомленнÑ"
+msgid "OnDemandScans|Target URL"
+msgstr ""
msgid "Once imported, repositories can be mirrored over SSH. Read more %{link_start}here%{link_end}."
msgstr "ПіÑÐ»Ñ Ñ–Ð¼Ð¿Ð¾Ñ€Ñ‚Ñƒ, репозиторії можуть бути віддзеркалені через SSH. ДізнайтеÑÑ Ð±Ñ–Ð»ÑŒÑˆÐµ %{link_start}тут%{link_end}."
@@ -15371,6 +15877,9 @@ msgstr "Одна або декілька груп, до Ñких ви не маÑ
msgid "One or more of you personal access tokens were revoked"
msgstr "Один або декілька ваших токенів оÑобиÑтого доÑтупу були анульовані"
+msgid "One or more of your %{provider} projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
+msgstr ""
+
msgid "One or more of your Bitbucket projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
msgstr "Один або декілька ваших проектів Bitbucket не можна імпортувати безпоÑередньо в GitLab, оÑкільки вони викориÑтовують Subversion або Mercurial Ð´Ð»Ñ ÐºÐ¾Ð½Ñ‚Ñ€Ð¾Ð»ÑŽ верÑій заміÑÑ‚ÑŒ Git."
@@ -15456,7 +15965,7 @@ msgid "Open sidebar"
msgstr "Розгорніть бічну панель"
msgid "Open: %{openIssuesCount}"
-msgstr ""
+msgstr "Відкрито: %{openIssuesCount}"
msgid "Open: %{open} • Closed: %{closed}"
msgstr ""
@@ -15585,7 +16094,7 @@ msgid "Package Files"
msgstr ""
msgid "Package Registry"
-msgstr ""
+msgstr "РеєÑÑ‚Ñ€ пакетів"
msgid "Package already exists"
msgstr ""
@@ -15618,19 +16127,19 @@ msgid "PackageRegistry|Add Conan Remote"
msgstr ""
msgid "PackageRegistry|Add NuGet Source"
-msgstr ""
+msgstr "Додати джерело NuGet"
msgid "PackageRegistry|Conan"
-msgstr ""
+msgstr "Conan"
msgid "PackageRegistry|Conan Command"
-msgstr ""
+msgstr "Команда Conan"
msgid "PackageRegistry|Copy .pypirc content"
msgstr ""
msgid "PackageRegistry|Copy Conan Command"
-msgstr ""
+msgstr "Скопіювати команду Conan"
msgid "PackageRegistry|Copy Conan Setup Command"
msgstr ""
@@ -15651,7 +16160,7 @@ msgid "PackageRegistry|Copy NuGet Setup Command"
msgstr ""
msgid "PackageRegistry|Copy Pip command"
-msgstr ""
+msgstr "Скопіювати команду pip"
msgid "PackageRegistry|Copy and paste this inside your %{codeStart}pom.xml%{codeEnd} %{codeStart}dependencies%{codeEnd} block."
msgstr ""
@@ -15672,25 +16181,25 @@ msgid "PackageRegistry|Delete Package Version"
msgstr "Видалити верÑÑ–ÑŽ пакету"
msgid "PackageRegistry|Delete package"
-msgstr ""
+msgstr "Видалити пакет"
msgid "PackageRegistry|Filter by name"
msgstr ""
msgid "PackageRegistry|For more information on the Conan registry, %{linkStart}see the documentation%{linkEnd}."
-msgstr ""
+msgstr "Щоб дізнатиÑÑ Ð±Ñ–Ð»ÑŒÑˆÐµ про реєÑÑ‚Ñ€ Conan, %{linkStart}переглÑньте документацію%{linkEnd}."
msgid "PackageRegistry|For more information on the Maven registry, %{linkStart}see the documentation%{linkEnd}."
-msgstr ""
+msgstr "Щоб дізнатиÑÑ Ð±Ñ–Ð»ÑŒÑˆÐµ про реєÑÑ‚Ñ€ Maven, %{linkStart}переглÑньте документацію%{linkEnd}."
msgid "PackageRegistry|For more information on the NuGet registry, %{linkStart}see the documentation%{linkEnd}."
-msgstr ""
+msgstr "Щоб дізнатиÑÑ Ð±Ñ–Ð»ÑŒÑˆÐµ про реєÑÑ‚Ñ€ NuGet, %{linkStart}переглÑньте документацію%{linkEnd}."
msgid "PackageRegistry|For more information on the PyPi registry, %{linkStart}see the documentation%{linkEnd}."
-msgstr ""
+msgstr "Щоб дізнатиÑÑ Ð±Ñ–Ð»ÑŒÑˆÐµ про реєÑÑ‚Ñ€ PyPi, %{linkStart}переглÑньте документацію%{linkEnd}."
msgid "PackageRegistry|If you haven't already done so, you will need to add the below to your %{codeStart}.pypirc%{codeEnd} file."
-msgstr ""
+msgstr "Якщо ви ще не зробили цього, вам потрібно буде додати розміщене нижче в Ñвій файл %{codeStart}.pypirc%{codeEnd}."
msgid "PackageRegistry|If you haven't already done so, you will need to add the below to your %{codeStart}pom.xml%{codeEnd} file."
msgstr "Якщо ви ще не зробили цього, вам потрібно буде додати розміщене нижче в Ñвій файл %{codeStart}pom.xml%{codeEnd}."
@@ -15708,7 +16217,7 @@ msgid "PackageRegistry|Manually Published"
msgstr ""
msgid "PackageRegistry|Maven"
-msgstr ""
+msgstr "Maven"
msgid "PackageRegistry|Maven Command"
msgstr "Команда Maven"
@@ -15717,19 +16226,19 @@ msgid "PackageRegistry|Maven XML"
msgstr "Maven XML"
msgid "PackageRegistry|NPM"
-msgstr ""
+msgstr "NPM"
msgid "PackageRegistry|No upcoming issues"
msgstr ""
msgid "PackageRegistry|NuGet"
-msgstr ""
+msgstr "NuGet"
msgid "PackageRegistry|NuGet Command"
-msgstr ""
+msgstr "Команда NNGet"
msgid "PackageRegistry|Pip Command"
-msgstr ""
+msgstr "Команда pip"
msgid "PackageRegistry|Pipeline %{linkStart}%{linkEnd} triggered %{timestamp} by %{author}"
msgstr ""
@@ -15816,7 +16325,7 @@ msgid "Packages"
msgstr "Пакети"
msgid "Packages & Registries"
-msgstr ""
+msgstr "Пакети та реєÑтри"
msgid "Page not found"
msgstr "Сторінку не знайдено"
@@ -15861,7 +16370,7 @@ msgid "Parameter"
msgstr "Параметр"
msgid "Parameter \"job_id\" cannot exceed length of %{job_id_max_size}"
-msgstr ""
+msgstr "Параметр \"job_id\" не може перевищувати довжину %{job_id_max_size}"
msgid "Parent"
msgstr "Джерело"
@@ -15872,6 +16381,9 @@ msgstr "БатьківÑький епік не Ñ–Ñнує."
msgid "Parent epic is not present."
msgstr "БатьківÑький епік відÑутній."
+msgid "Parsing error for param :embed_json. %{message}"
+msgstr ""
+
msgid "Part of merge request changes"
msgstr "ЧаÑтина змін у запиті на злиттÑ"
@@ -15884,6 +16396,9 @@ msgstr "УчаÑники"
msgid "Passed"
msgstr "УÑпішно пройдено"
+msgid "Passed on"
+msgstr ""
+
msgid "Password"
msgstr "Пароль"
@@ -15891,7 +16406,7 @@ msgid "Password (optional)"
msgstr "Пароль (необов'Ñзково)"
msgid "Password Policy Guidelines"
-msgstr ""
+msgstr "Керівництво щодо політики паролів"
msgid "Password authentication is unavailable."
msgstr "Парольна Ð°Ð²Ñ‚ÐµÐ½Ñ‚Ð¸Ñ„Ñ–ÐºÐ°Ñ†Ñ–Ñ Ð½ÐµÐ´Ð¾Ñтупна."
@@ -15960,7 +16475,7 @@ msgid "Percent rollout (logged in users)"
msgstr ""
msgid "Percentage"
-msgstr ""
+msgstr "ВідÑоток"
msgid "Perform advanced options such as changing path, transferring, exporting, or removing the group."
msgstr ""
@@ -15968,6 +16483,9 @@ msgstr ""
msgid "Perform common operations on GitLab project"
msgstr "Виконати звичайні операції на проекті GitLab"
+msgid "Performance and resource management"
+msgstr ""
+
msgid "Performance optimization"
msgstr "ÐžÐ¿Ñ‚Ð¸Ð¼Ñ–Ð·Ð°Ñ†Ñ–Ñ Ð¿Ñ€Ð¾Ð´ÑƒÐºÑ‚Ð¸Ð²Ð½Ð¾ÑÑ‚Ñ–"
@@ -16175,8 +16693,8 @@ msgstr "ЗапуÑтити Конвеєр"
msgid "Pipelines|Something went wrong while cleaning runners cache."
msgstr "Помилка при очищенні кеша runner'ів."
-msgid "Pipelines|There are currently no %{scope} pipelines."
-msgstr "Ð’ даний Ñ‡Ð°Ñ Ð½ÐµÐ¼Ð°Ñ” %{scope} конвеєрів."
+msgid "Pipelines|There are currently no finished pipelines."
+msgstr ""
msgid "Pipelines|There are currently no pipelines."
msgstr "Ð’ даний Ñ‡Ð°Ñ Ð½ÐµÐ¼Ð°Ñ” конвеєрів."
@@ -16194,10 +16712,10 @@ msgid "Pipelines|parent"
msgstr ""
msgid "Pipeline|Branch name"
-msgstr ""
+msgstr "Ім'Ñ Ð³Ñ–Ð»ÐºÐ¸"
msgid "Pipeline|Canceled"
-msgstr ""
+msgstr "СкаÑовано"
msgid "Pipeline|Commit"
msgstr "Коміт"
@@ -16245,7 +16763,7 @@ msgid "Pipeline|Passed"
msgstr ""
msgid "Pipeline|Pending"
-msgstr ""
+msgstr "Очікує"
msgid "Pipeline|Pipeline"
msgstr "Конвеєр"
@@ -16263,13 +16781,13 @@ msgid "Pipeline|Run for"
msgstr "ЗапуÑтити длÑ"
msgid "Pipeline|Running"
-msgstr ""
+msgstr "Запущено"
msgid "Pipeline|Search branches"
msgstr "Пошук гілки"
msgid "Pipeline|Skipped"
-msgstr ""
+msgstr "Пропущено"
msgid "Pipeline|Specify variable values to be used in this run. The values specified in %{settings_link} will be used by default."
msgstr "Вкажіть Ð·Ð½Ð°Ñ‡ÐµÐ½Ð½Ñ Ð·Ð¼Ñ–Ð½Ð½Ð¸Ñ…, Ñкі будуть викориÑтані в цьому запуÑку. Інакше будуть викориÑтані значеннÑ, вказані в %{settings_link}."
@@ -16286,9 +16804,12 @@ msgstr "Зупинити конвеєр"
msgid "Pipeline|Stop pipeline #%{pipelineId}?"
msgstr "Зупинити конвеєр #%{pipelineId}?"
-msgid "Pipeline|Trigger author"
+msgid "Pipeline|Tag name"
msgstr ""
+msgid "Pipeline|Trigger author"
+msgstr "Ðвтор тригера"
+
msgid "Pipeline|Triggerer"
msgstr "Запущено"
@@ -16367,12 +16888,18 @@ msgstr "Будь лаÑка, переконайтеÑÑ, що файл конфÑ
msgid "Please check your email (%{email}) to verify that you own this address and unlock the power of CI/CD. Didn't receive it? %{resend_link}. Wrong email address? %{update_link}."
msgstr "Будь лаÑка, перевірте Ñвою поштову Ñкриньку (%{email}) Ð´Ð»Ñ Ð¿Ñ–Ð´Ñ‚Ð²ÐµÑ€Ð´Ð¶ÐµÐ½Ð½Ñ Ñ‚Ð¾Ð³Ð¾, що ви нею володієте, щоб розкрити потенціал CI/CD. Ðе отримали? %{resend_link}. Ðеправильна адреÑа? %{update_link}."
+msgid "Please choose a file"
+msgstr ""
+
msgid "Please choose a group URL with no special characters."
msgstr "Будь лаÑка задайте URL групи без Ñпеціальних Ñимволів."
msgid "Please complete your profile with email address"
msgstr "Будь лаÑка, доповніть Ñвій профіль адреÑою електронної пошти"
+msgid "Please convert %{linkStart}them to Git%{linkEnd}, and go through the %{linkToImportFlow} again."
+msgstr ""
+
msgid "Please convert them to %{link_to_git}, and go through the %{link_to_import_flow} again."
msgstr "Будь лаÑка Ñконвертуйте Ñ—Ñ… в %{link_to_git} Ñ– виконайте знову %{link_to_import_flow}."
@@ -16430,6 +16957,9 @@ msgstr ""
msgid "Please provide a valid email address."
msgstr "Будь лаÑка, вкажіть дійÑну e-mail адреÑу."
+msgid "Please provide attributes to update"
+msgstr ""
+
msgid "Please refer to <a href=\"%{docs_url}\">%{docs_url}</a>"
msgstr "Будь лаÑка переглÑньте <a href=\"%{docs_url}\">%{docs_url}</a>"
@@ -16557,7 +17087,7 @@ msgid "Preferences|Render whitespace characters in the Web IDE"
msgstr ""
msgid "Preferences|Show whitespace changes in diffs"
-msgstr ""
+msgstr "Показувати зміни пробілів у відмінноÑÑ‚ÑÑ…"
msgid "Preferences|Sourcegraph"
msgstr "Sourcegraph"
@@ -17165,6 +17695,9 @@ msgstr ""
msgid "Project has too many %{label_for_message} to search"
msgstr "Ð’ проекті занадто багато %{label_for_message} Ð´Ð»Ñ Ð¿Ð¾ÑˆÑƒÐºÑƒ"
+msgid "Project is required when cluster_type is :project"
+msgstr ""
+
msgid "Project members"
msgstr "УчаÑники проекту"
@@ -17282,7 +17815,40 @@ msgstr "%{service_title}: ÑÑ‚Ð°Ñ‚ÑƒÑ ÑƒÐ²Ñ–Ð¼ÐºÐ½ÐµÐ½Ð¾"
msgid "ProjectService|Comment"
msgstr "Коментар"
-msgid "ProjectService|Comment will be posted on each event"
+msgid "ProjectService|Event will be triggered by a push to the repository"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a commit is created/updated"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a confidential issue is created/updated/closed"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a deployment finishes"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a merge request is created/updated/merged"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a new tag is pushed to the repository"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a new, unique alert is recorded"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a pipeline status changes"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a wiki page is created/updated"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when an issue is created/updated/closed"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when someone adds a comment"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when someone adds a comment on a confidential issue"
msgstr ""
msgid "ProjectService|Perform common operations on GitLab project: %{project_name}"
@@ -17333,6 +17899,9 @@ msgstr "Вимкнути ÑÐ¿Ð¾Ð²Ñ–Ñ‰ÐµÐ½Ð½Ñ ÐµÐ»ÐµÐºÑ‚Ñ€Ð¾Ð½Ð½Ð¾ÑŽ поштоÑ
msgid "ProjectSettings|Enable 'Delete source branch' option by default"
msgstr "Увімкнути за замовчуваннÑм можливіÑÑ‚ÑŒ \"Видалити гілку-джерело\""
+msgid "ProjectSettings|Enable merge trains and pipelines for merged results"
+msgstr ""
+
msgid "ProjectSettings|Every merge creates a merge commit"
msgstr "Кожне Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ñтворить окремий коміт"
@@ -17363,8 +17932,8 @@ msgstr "Лише fast-forward злиттÑ"
msgid "ProjectSettings|Forks"
msgstr "Форки"
-msgid "ProjectSettings|Git Large File Storage"
-msgstr "Сховище великих файлів Git (Git LFS)"
+msgid "ProjectSettings|Git Large File Storage (LFS)"
+msgstr ""
msgid "ProjectSettings|Internal"
msgstr "Внутрішні"
@@ -17399,9 +17968,6 @@ msgstr "Метод злиттÑ"
msgid "ProjectSettings|Merge options"
msgstr "Параметри злиттÑ"
-msgid "ProjectSettings|Merge pipelines will try to validate the post-merge result prior to merging"
-msgstr "Конвеєри Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ð½Ð°Ð¼Ð°Ð³Ð°Ñ‚Ð¸Ð¼ÑƒÑ‚ÑŒÑÑ Ð¿ÐµÑ€ÐµÐ²Ñ–Ñ€Ñти результат, Ñкий утворитьÑÑ Ð¿Ñ–ÑÐ»Ñ Ð·Ð»Ð¸Ñ‚Ñ‚Ñ, перед його виконаннÑм"
-
msgid "ProjectSettings|Merge requests"
msgstr "Запити на злиттÑ"
@@ -17429,6 +17995,9 @@ msgstr "Pages Ð´Ð»Ñ Ð¿Ñ€Ð¾Ñ”ÐºÑ‚Ð½Ð¾Ñ— документації"
msgid "ProjectSettings|Pipelines"
msgstr "Конвеєри"
+msgid "ProjectSettings|Pipelines for merge requests must be enabled in the CI/CD configuration file, or pipelines could be unresolvable or dropped"
+msgstr ""
+
msgid "ProjectSettings|Pipelines must succeed"
msgstr "Конвеєри мають бути уÑпішними"
@@ -17456,6 +18025,9 @@ msgstr ""
msgid "ProjectSettings|Show link to create/view merge request when pushing from the command line"
msgstr "Показувати поÑÐ¸Ð»Ð°Ð½Ð½Ñ Ð´Ð»Ñ ÑÑ‚Ð²Ð¾Ñ€ÐµÐ½Ð½Ñ Ñ‡Ð¸ переглÑду запиту на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ð¿Ñ€Ð¸ відправці із командного Ñ€Ñдка"
+msgid "ProjectSettings|Skipped pipelines are considered successful"
+msgstr ""
+
msgid "ProjectSettings|Snippets"
msgstr "Сніпети"
@@ -17471,6 +18043,9 @@ msgstr ""
msgid "ProjectSettings|These checks must pass before merge requests can be merged"
msgstr "Ðеобхідно щоб ці перевірки уÑпішно виконалиÑÑ Ð¿ÐµÑ€ÐµÐ´ тим, Ñк запит на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ð·Ð¼Ð¾Ð¶Ðµ бути злитий"
+msgid "ProjectSettings|This introduces the risk of merging changes that will not pass the pipeline."
+msgstr ""
+
msgid "ProjectSettings|This setting is applied on the server level and can be overridden by an admin."
msgstr "Цей параметр заÑтоÑовуєтьÑÑ Ð½Ð° рівні Ñервера та може бути перевизначений адмініÑтратором."
@@ -17648,15 +18223,27 @@ msgstr "ПуÑтий"
msgid "ProjectsNew|Blank project"
msgstr "ПуÑтий проект"
+msgid "ProjectsNew|Connect your external repository to GitLab CI/CD."
+msgstr ""
+
msgid "ProjectsNew|Contact an administrator to enable options for importing your project."
msgstr "ЗвернітьÑÑ Ð´Ð¾ адмініÑтратора Ð´Ð»Ñ Ð°ÐºÑ‚Ð¸Ð²Ð°Ñ†Ñ–Ñ— варіантів імпорту Ð´Ð»Ñ Ð²Ð°ÑˆÐ¾Ð³Ð¾ проекту."
msgid "ProjectsNew|Create"
+msgstr "Створити"
+
+msgid "ProjectsNew|Create a blank project to house your files, plan your work, and collaborate on code, among other things."
+msgstr ""
+
+msgid "ProjectsNew|Create blank project"
msgstr ""
msgid "ProjectsNew|Create from template"
msgstr "Створити із шаблону"
+msgid "ProjectsNew|Create new project"
+msgstr "Створити новий проєкт"
+
msgid "ProjectsNew|Creating project & repository."
msgstr "Ð¡Ñ‚Ð²Ð¾Ñ€ÐµÐ½Ð½Ñ Ð¿Ñ€Ð¾ÐµÐºÑ‚Ñƒ та репозиторію."
@@ -17681,6 +18268,9 @@ msgstr "Будь лаÑка, почекайте, Ñторінку буде онÐ
msgid "ProjectsNew|Project description %{tag_start}(optional)%{tag_end}"
msgstr "ÐžÐ¿Ð¸Ñ Ð¿Ñ€Ð¾ÐµÐºÑ‚Ñƒ %{tag_start}(необов’Ñзково)%{tag_end}"
+msgid "ProjectsNew|Run CI/CD for external repository"
+msgstr ""
+
msgid "ProjectsNew|Template"
msgstr "Шаблон"
@@ -17919,7 +18509,7 @@ msgid "Promotions|Upgrade your plan to improve milestones with Burndown Charts."
msgstr ""
msgid "Promotions|Weight"
-msgstr ""
+msgstr "Вага"
msgid "Promotions|Weighting your issue"
msgstr "ÐÐ°Ð´Ð°Ð½Ð½Ñ Ð²Ð°Ð³Ð¸ вашій задачі"
@@ -17942,6 +18532,9 @@ msgstr "Захищено"
msgid "Protected Branch"
msgstr "Захищена гілка"
+msgid "Protected Branches"
+msgstr ""
+
msgid "Protected Environment"
msgstr "Захищене Ñередовище"
@@ -17954,6 +18547,9 @@ msgstr "Захищені шлÑхи"
msgid "Protected Tag"
msgstr "Захищений тег"
+msgid "Protected Tags"
+msgstr ""
+
msgid "Protected branches"
msgstr "Захищені гілки"
@@ -18053,6 +18649,9 @@ msgstr "Протокол"
msgid "Provider"
msgstr "ПоÑтачальник"
+msgid "Proxy support for this API is not available currently"
+msgstr ""
+
msgid "Pseudonymizer data collection"
msgstr "Збір даних Pseudonymizer"
@@ -18077,6 +18676,9 @@ msgstr ""
msgid "Publish to status page"
msgstr ""
+msgid "Published on status page"
+msgstr ""
+
msgid "Publishes this issue to the associated status page."
msgstr ""
@@ -18251,6 +18853,9 @@ msgstr "ОÑтанні пошукові запити"
msgid "Recipe"
msgstr ""
+msgid "Reconfigure"
+msgstr ""
+
msgid "Recover hidden stage"
msgstr ""
@@ -18339,6 +18944,9 @@ msgstr ""
msgid "Registration|Your profile"
msgstr ""
+msgid "Regulate approvals by authors/committers, based on compliance frameworks. Can be changed only at the instance level."
+msgstr ""
+
msgid "Rejected (closed)"
msgstr ""
@@ -18391,15 +18999,24 @@ msgstr "Ðазва релізу"
msgid "ReleaseAssetLinkType|Image"
msgstr ""
+msgid "ReleaseAssetLinkType|Images"
+msgstr ""
+
msgid "ReleaseAssetLinkType|Other"
msgstr ""
msgid "ReleaseAssetLinkType|Package"
msgstr ""
+msgid "ReleaseAssetLinkType|Packages"
+msgstr ""
+
msgid "ReleaseAssetLinkType|Runbook"
msgstr ""
+msgid "ReleaseAssetLinkType|Runbooks"
+msgstr ""
+
msgid "Releases"
msgstr "Релізи"
@@ -18412,6 +19029,9 @@ msgstr ""
msgid "Releases documentation"
msgstr ""
+msgid "Releases|New Release"
+msgstr ""
+
msgid "Release|Something went wrong while getting the release details"
msgstr "Проблема при отриманні деталей релізу"
@@ -18484,6 +19104,9 @@ msgstr "Видалити заплановану дату завершеннÑ"
msgid "Remove fork relationship"
msgstr "Видалити зв’Ñзок форку"
+msgid "Remove from batch"
+msgstr ""
+
msgid "Remove from board"
msgstr "Видалити з дошки"
@@ -18673,6 +19296,12 @@ msgstr "Замінює кореневу URL-адреÑу Ð´Ð»Ñ ÐºÐ»Ð¾Ð½ÑƒÐ²Ð°Ð½
msgid "Replication"
msgstr ""
+msgid "Replication enabled"
+msgstr ""
+
+msgid "Replication paused"
+msgstr ""
+
msgid "Reply by email"
msgstr "ВідповіÑти по електнонній пошті"
@@ -18700,15 +19329,15 @@ msgstr "Повідомити адмініÑтратора про порушенÐ
msgid "Reported %{timeAgo} by %{reportedBy}"
msgstr "Повідомлено %{timeAgo} кориÑтувачем %{reportedBy}"
-msgid "Reported Resource Changes: %{addNum} to add, %{changeNum} to change, %{deleteNum} to delete"
-msgstr ""
-
msgid "Reporter"
msgstr "Репортер"
msgid "Reporting"
msgstr "ЗвітуваннÑ"
+msgid "Reports"
+msgstr ""
+
msgid "Reports|%{combinedString} and %{resolvedString}"
msgstr ""
@@ -18749,6 +19378,9 @@ msgstr "Ð§Ð°Ñ Ð²Ð¸ÐºÐ¾Ð½Ð°Ð½Ð½Ñ"
msgid "Reports|Failure"
msgstr "Помилка"
+msgid "Reports|Identifier"
+msgstr ""
+
msgid "Reports|Metrics reports are loading"
msgstr "Звіти по метрикам завантажуютьÑÑ"
@@ -18761,6 +19393,9 @@ msgstr "Звіти по метрикам не змінилиÑÑ"
msgid "Reports|Metrics reports failed loading results"
msgstr "Помилка при завантаженні результатів Ð´Ð»Ñ Ð·Ð²Ñ–Ñ‚Ñ–Ð² по метрикам"
+msgid "Reports|Scanner"
+msgstr ""
+
msgid "Reports|Severity"
msgstr "Рівень"
@@ -18809,15 +19444,33 @@ msgstr "ÐžÑ‡Ð¸Ñ‰ÐµÐ½Ð½Ñ Ñ€ÐµÐ¿Ð¾Ð·Ð¸Ñ‚Ð¾Ñ€Ñ–ÑŽ"
msgid "Repository cleanup has started. You will receive an email once the cleanup operation is complete."
msgstr "ОчиÑтку репозиторію розпочато. Ви отримаєте Ð¿Ð¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ Ð¿Ð¾ електронній пошті піÑÐ»Ñ Ñ—Ñ— завершеннÑ."
+msgid "Repository files count over the limit"
+msgstr ""
+
+msgid "Repository has an invalid default branch name."
+msgstr ""
+
+msgid "Repository has more than one branch."
+msgstr ""
+
msgid "Repository has no locks."
msgstr "Репозиторій не має блокувань."
+msgid "Repository has tags."
+msgstr ""
+
msgid "Repository maintenance"
msgstr "ОбÑÐ»ÑƒÐ³Ð¾Ð²ÑƒÐ²Ð°Ð½Ð½Ñ Ñ€ÐµÐ¿Ð¾Ð·Ð¸Ñ‚Ð¾Ñ€Ñ–ÑŽ"
msgid "Repository mirroring"
msgstr "Ð’Ñ–Ð´Ð´Ð·ÐµÑ€ÐºÐ°Ð»ÐµÐ½Ð½Ñ Ñ€ÐµÐ¿Ð¾Ð·Ð¸Ñ‚Ð¾Ñ€Ñ–ÑŽ"
+msgid "Repository must contain at least 1 file."
+msgstr ""
+
+msgid "Repository size is above the limit."
+msgstr ""
+
msgid "Repository static objects"
msgstr "Статичні об’єкти репозиторію"
@@ -18827,8 +19480,8 @@ msgstr "Сховище репозиторію"
msgid "Repository sync capacity"
msgstr ""
-msgid "Repository: %{counter_repositories} / Wikis: %{counter_wikis} / Build Artifacts: %{counter_build_artifacts} / LFS: %{counter_lfs_objects}"
-msgstr "Репозиторій: %{counter_repositories} / Wiki: %{counter_wikis} / Ðртифакти збірки: %{counter_build_artifacts} / LFS: %{counter_lfs_objects}"
+msgid "Repository: %{counter_repositories} / Wikis: %{counter_wikis} / Build Artifacts: %{counter_build_artifacts} / LFS: %{counter_lfs_objects} / Snippets: %{counter_snippets}"
+msgstr ""
msgid "RepositorySettingsAccessLevel|Select"
msgstr "Вибрати"
@@ -18839,6 +19492,9 @@ msgstr "Запит доÑтупу"
msgid "Request Headers"
msgstr ""
+msgid "Request details"
+msgstr ""
+
msgid "Request parameter %{param} is missing."
msgstr "Параметр запиту %{param} відÑутній."
@@ -18949,6 +19605,9 @@ msgstr "Перегенерувати реєÑтраційний токен runne
msgid "Reset template"
msgstr "Скинути шаблон"
+msgid "Reset to project defaults"
+msgstr ""
+
msgid "Resetting the authorization key for this project will require updating the authorization key in every alert source it is enabled in."
msgstr "Ð¡ÐºÐ¸Ð´Ð°Ð½Ð½Ñ ÐºÐ»ÑŽÑ‡Ð° авторизації Ð´Ð»Ñ Ñ†ÑŒÐ¾Ð³Ð¾ проекту вимагатиме Ð¾Ð½Ð¾Ð²Ð»ÐµÐ½Ð½Ñ ÐºÐ»ÑŽÑ‡Ð° авторизації в кожному із джерел попереджень, в Ñкому він увімкнений."
@@ -19045,9 +19704,6 @@ msgstr ""
msgid "Resume"
msgstr "Продовжити"
-msgid "Resume replication"
-msgstr "Відновити реплікацію"
-
msgid "Resync"
msgstr ""
@@ -19692,6 +20348,9 @@ msgstr ""
msgid "SecurityReports|False positive"
msgstr ""
+msgid "SecurityReports|Group Security Dashboard"
+msgstr ""
+
msgid "SecurityReports|Hide dismissed"
msgstr ""
@@ -19746,12 +20405,15 @@ msgstr "Додані проєкти"
msgid "SecurityReports|Remove project from dashboard"
msgstr ""
-msgid "SecurityReports|Report type"
-msgstr "Тип звіту"
-
msgid "SecurityReports|Return to dashboard"
msgstr ""
+msgid "SecurityReports|Scan details"
+msgstr ""
+
+msgid "SecurityReports|Scanner type"
+msgstr ""
+
msgid "SecurityReports|Security Dashboard"
msgstr ""
@@ -19806,6 +20468,9 @@ msgstr ""
msgid "SecurityReports|There was an error while generating the report."
msgstr ""
+msgid "SecurityReports|Unable to add %{invalidProjectsMessage}"
+msgstr ""
+
msgid "SecurityReports|Unable to add %{invalidProjects}"
msgstr ""
@@ -19920,6 +20585,9 @@ msgstr "Виберіть гілку або тег"
msgid "Select due date"
msgstr ""
+msgid "Select file"
+msgstr ""
+
msgid "Select group or project"
msgstr "Вибрати групу чи проект"
@@ -19983,9 +20651,6 @@ msgstr "Вибір цільової гілки"
msgid "Select the branch you want to set as the default for this project. All merge requests and commits will automatically be made against this branch unless you specify a different one."
msgstr "Виберіть гілку по замовчанню Ð´Ð»Ñ Ñ†ÑŒÐ¾Ð³Ð¾ проекту. Ð’ÑÑ– запити на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ñ‚Ð° коміти будуть автоматично зроблені в ній, Ñкщо ви тільки не виберете іншу."
-msgid "Select the configured storage available for new repositories to be placed on."
-msgstr ""
-
msgid "Select the custom project template source group."
msgstr "Вкажіть групу, де розміщені влаÑні шаблони проектів."
@@ -20178,12 +20843,6 @@ msgstr "Service Desk"
msgid "Service Desk is enabled but not yet active"
msgstr "Service Desk увімкнено але він ще не активний"
-msgid "Service Desk is off"
-msgstr "Service Desk вимкнено"
-
-msgid "Service Desk is on"
-msgstr "Service Desk увімкнено"
-
msgid "Service Templates"
msgstr "Шаблони ÑервіÑів"
@@ -20271,6 +20930,15 @@ msgstr "Ð’Ñтановити макÑимальну кількіÑÑ‚ÑŒ хвилÐ
msgid "Set the milestone to %{milestone_reference}."
msgstr "Ð’Ñтановити етап %{milestone_reference}."
+msgid "Set the number of concurrent requests this secondary node will make to the primary node while backfilling."
+msgstr ""
+
+msgid "Set the synchronization and verification capacity for the secondary node."
+msgstr ""
+
+msgid "Set the timeout in seconds to send a secondary node status to the primary and IPs allowed for the secondary nodes."
+msgstr ""
+
msgid "Set time estimate"
msgstr "Ð’Ñтановити запланований чаÑ"
@@ -20310,6 +20978,9 @@ msgstr "Ð’Ñтановити вагу"
msgid "Set weight to %{weight}."
msgstr "Ð’Ñтановити вагу %{weight}."
+msgid "Set what should be replicated by choosing specific projects or groups by the secondary node."
+msgstr ""
+
msgid "SetPasswordToCloneLink|set a password"
msgstr "вÑтановити пароль"
@@ -20436,6 +21107,9 @@ msgstr "Показати Ð¾Ð¿Ð¸Ñ ÐºÐ¾Ð¼Ñ–Ñ‚Ñƒ"
msgid "Show complete raw log"
msgstr "Показати повний неформатований журнал"
+msgid "Show details"
+msgstr ""
+
msgid "Show file browser"
msgstr "Показати файловий менеджер"
@@ -20445,10 +21119,13 @@ msgstr ""
msgid "Show latest version"
msgstr "Показати оÑтанню верÑÑ–ÑŽ"
+msgid "Show list"
+msgstr ""
+
msgid "Show me everything"
msgstr ""
-msgid "Show me how"
+msgid "Show me how to add a pipeline"
msgstr ""
msgid "Show me more advanced stuff"
@@ -20501,9 +21178,6 @@ msgstr "ПоказуєтьÑÑ Ð²ÐµÑ€ÑÑ–Ñ #%{versionNumber}"
msgid "Showing all issues"
msgstr "Ð’Ñ–Ð´Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð½Ñ ÑƒÑÑ–Ñ… задач"
-msgid "Showing all labels"
-msgstr "Ð’Ñ–Ð´Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð½Ñ ÑƒÑÑ–Ñ… міток"
-
msgid "Showing last %{size} of log -"
msgstr "ПоказуютьÑÑ Ð¾Ñтанні %{size} журналу -"
@@ -20615,9 +21289,6 @@ msgstr ""
msgid "Skip outdated deployment jobs"
msgstr ""
-msgid "Skip this for now"
-msgstr "Поки що пропуÑтити це"
-
msgid "Skipped"
msgstr "Пропущені"
@@ -20762,17 +21433,20 @@ msgstr "Помилка при перемиканні кнопки"
msgid "Something went wrong while adding your award. Please try again."
msgstr "Проблема при додаванні вашої нагороди. Будь лаÑка, Ñпробуйте знову."
+msgid "Something went wrong while applying the batch of suggestions. Please try again."
+msgstr ""
+
msgid "Something went wrong while applying the suggestion. Please try again."
msgstr "Помилка при заÑтоÑуванні пропозиції. Будь лаÑка, Ñпробуйте знову."
msgid "Something went wrong while archiving a requirement."
-msgstr ""
+msgstr "ЩоÑÑŒ пішло не так під Ñ‡Ð°Ñ Ð°Ñ€Ñ…Ñ–Ð²ÑƒÐ²Ð°Ð½Ð½Ñ Ð²Ð¸Ð¼Ð¾Ð³Ð¸."
msgid "Something went wrong while closing the %{issuable}. Please try again later"
msgstr "Помилка при закритті %{issuable}. Будь лаÑка, Ñпробуйте пізніше"
msgid "Something went wrong while creating a requirement."
-msgstr ""
+msgstr "ЩоÑÑŒ пішло не так під Ñ‡Ð°Ñ ÑÑ‚Ð²Ð¾Ñ€ÐµÐ½Ð½Ñ Ð²Ð¸Ð¼Ð¾Ð³Ð¸."
msgid "Something went wrong while deleting description changes. Please try again."
msgstr ""
@@ -20864,10 +21538,13 @@ msgstr ""
msgid "Something went wrong while updating a requirement."
msgstr "ЩоÑÑŒ пішо не так під Ñ‡Ð°Ñ Ð¾Ð½Ð¾Ð²Ð»ÐµÐ½Ð½Ñ Ð²Ð¸Ð¼Ð¾Ð³Ð¸."
+msgid "Something went wrong while updating assignees"
+msgstr ""
+
msgid "Something went wrong while updating your list settings"
msgstr ""
-msgid "Something went wrong with your automatic subscription renewal"
+msgid "Something went wrong with your automatic subscription renewal."
msgstr ""
msgid "Something went wrong, unable to add %{project} to dashboard"
@@ -20885,6 +21562,9 @@ msgstr "Проблема, не вдалоÑÑ Ð²Ð¸Ð´Ð°Ð»Ð¸Ñ‚Ð¸ проект"
msgid "Something went wrong, unable to search projects"
msgstr "Помилка, не вдалоÑÑ Ð·Ð´Ñ–Ð¹Ð½Ñтити пошук проектів"
+msgid "Something went wrong."
+msgstr ""
+
msgid "Something went wrong. Please try again."
msgstr "ЩоÑÑŒ пішло не так. Будь лаÑка Ñпробуйте ще раз."
@@ -21068,6 +21748,9 @@ msgstr "Джерело (гілка або тег)"
msgid "Source code"
msgstr "Код"
+msgid "Source code (%{fileExtension})"
+msgstr ""
+
msgid "Source is not available"
msgstr "Джерело недоÑтупне"
@@ -21152,6 +21835,9 @@ msgstr "Виконати об'Ñ”Ð´Ð½Ð°Ð½Ð½Ñ (squash) комітів"
msgid "Stack trace"
msgstr "ТраÑÑƒÐ²Ð°Ð½Ð½Ñ Ñтеку"
+msgid "Stacktrace snippet"
+msgstr ""
+
msgid "Stage"
msgstr "СтадіÑ"
@@ -21257,6 +21943,9 @@ msgstr "Розпочати Ð¾Ð±Ð³Ð¾Ð²Ð¾Ñ€ÐµÐ½Ð½Ñ Ñ‚Ð° закрити %{noteable
msgid "Start thread & reopen %{noteable_name}"
msgstr "Розпочати Ð¾Ð±Ð³Ð¾Ð²Ð¾Ñ€ÐµÐ½Ð½Ñ Ñ‚Ð° повторно відкрити %{noteable_name}"
+msgid "Start using Directed Acyclic Graphs (DAG)"
+msgstr ""
+
msgid "Start your Free Gold Trial"
msgstr ""
@@ -21719,6 +22408,21 @@ msgstr "Дуже темний лаймово-Ñиній"
msgid "SuggestedColors|Very pale orange"
msgstr "Дуже блідий оранжевий"
+msgid "Suggestion is not applicable as the suggestion was not found."
+msgstr ""
+
+msgid "Suggestions are not applicable as one or more suggestions were not found."
+msgstr ""
+
+msgid "Suggestions are not applicable as their lines cannot overlap."
+msgstr ""
+
+msgid "Suggestions must all be on the same branch."
+msgstr ""
+
+msgid "Suggestions that change line count can't be added to batches, yet."
+msgstr ""
+
msgid "Suggestions:"
msgstr "Пропозиції:"
@@ -21761,9 +22465,6 @@ msgstr ""
msgid "Synchronization disabled"
msgstr ""
-msgid "Synchronization of container repositories is disabled."
-msgstr ""
-
msgid "System"
msgstr "СиÑтемні"
@@ -21944,6 +22645,38 @@ msgstr "Угода про Ð½Ð°Ð´Ð°Ð½Ð½Ñ Ð¿Ð¾Ñлуг Ñ– політика кон
msgid "Terms of Service and Privacy Policy"
msgstr "Правилами кориÑÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ ÑервіÑом Ñ– політика конфіденційноÑÑ‚Ñ–"
+msgid "Terraform|%{number} Terraform report failed to generate"
+msgid_plural "Terraform|%{number} Terraform reports failed to generate"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "Terraform|%{number} Terraform report was generated in your pipelines"
+msgid_plural "Terraform|%{number} Terraform reports were generated in your pipelines"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "Terraform|A Terraform report failed to generate."
+msgstr ""
+
+msgid "Terraform|A Terraform report was generated in your pipelines."
+msgstr ""
+
+msgid "Terraform|Generating the report caused an error."
+msgstr ""
+
+msgid "Terraform|Reported Resource Changes: %{addNum} to add, %{changeNum} to change, %{deleteNum} to delete"
+msgstr ""
+
+msgid "Terraform|The Terraform report %{name} failed to generate."
+msgstr ""
+
+msgid "Terraform|The Terraform report %{name} was generated in your pipelines."
+msgstr ""
+
msgid "Test"
msgstr "ТеÑÑ‚"
@@ -21972,8 +22705,8 @@ msgstr "ПереконайтеÑÑ, що проект має CI завданнÑ
msgid "TestHooks|Ensure the project has CI pipelines."
msgstr "ПереконайтеÑÑ, що проект має CI конвеєри."
-msgid "TestHooks|Ensure the project has at least one commit."
-msgstr "ПереконайтеÑÑ, що проект має принаймні один коміт."
+msgid "TestHooks|Ensure the project has deployments."
+msgstr ""
msgid "TestHooks|Ensure the project has issues."
msgstr "ПереконайтеÑÑ, що проект має задачі."
@@ -22075,7 +22808,7 @@ msgstr "Трекер задач — це міÑце, де можна додатÐ
msgid "The Prometheus server responded with \"bad request\". Please check your queries are correct and are supported in your Prometheus version. %{documentationLink}"
msgstr ""
-msgid "The URL defined on the primary node that secondary nodes should use to contact it. Defaults to URL"
+msgid "The URL defined on the primary node that secondary nodes should use to contact it."
msgstr ""
msgid "The URL to use for connecting to Elasticsearch. Use a comma-separated list to support clustering (e.g., \"http://localhost:9200, http://localhost:9201\")."
@@ -22084,6 +22817,9 @@ msgstr "URL-адреÑа Ð´Ð»Ñ Ð¿Ñ–Ð´ÐºÐ»ÑŽÑ‡ÐµÐ½Ð½Ñ Ð´Ð¾ Elasticsearch. ВиÐ
msgid "The X509 Certificate to use when mutual TLS is required to communicate with the external authorization service. If left blank, the server certificate is still validated when accessing over HTTPS."
msgstr "Сертифікат X509 викориÑтовуєтьÑÑ Ð´Ð»Ñ Ð²Ð·Ð°Ñ”Ð¼Ð½Ð¾Ñ— перевірки автентичноÑÑ‚Ñ– TLS Ñ– необхідний Ð´Ð»Ñ Ð·Ð²'Ñзку з зовнішньою Ñлужбою авторизації. Якщо залишити порожнім, Ñертифікат Ñервера буде перевірÑтиÑÑŒ при доÑтупі через HTTPS."
+msgid "The above settings apply to all projects with the selected compliance framework(s)."
+msgstr ""
+
msgid "The amount of seconds after which a request to get a secondary node status will time out."
msgstr "МакÑимальний Ñ‡Ð°Ñ Ð¾Ñ‚Ñ€Ð¸Ð¼Ð°Ð½Ð½Ñ Ñ–Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ñ–Ñ— про Ñтан вторинного вузла."
@@ -22173,10 +22909,10 @@ msgstr "ÐаÑтупні елементи будуть екÑпортовані:
msgid "The following personal access token: %{token_names} was revoked, because a new policy to expire personal access tokens were set."
msgid_plural "The following personal access tokens: %{token_names} were revoked, because a new policy to expire personal access tokens were set."
-msgstr[0] ""
-msgstr[1] ""
-msgstr[2] ""
-msgstr[3] ""
+msgstr[0] "ÐаÑтупний перÑональний токен доÑтупу: %{token_names} анульовано, оÑкільки була задана нова політика щодо терміну дії перÑональних токенів доÑтупу."
+msgstr[1] "ÐаÑтупні перÑональні токени доÑтупу: %{token_names} анульовано, оÑкільки була задана нова політика щодо терміну дії перÑональних токенів доÑтупу."
+msgstr[2] "ÐаÑтупні перÑональні токени доÑтупу: %{token_names} анульовано, оÑкільки була задана нова політика щодо терміну дії перÑональних токенів доÑтупу."
+msgstr[3] "ÐаÑтупні перÑональні токени доÑтупу: %{token_names} анульовано, оÑкільки була задана нова політика щодо терміну дії перÑональних токенів доÑтупу."
msgid "The fork relationship has been removed."
msgstr "Зв'Ñзок форку видалено."
@@ -22227,7 +22963,7 @@ msgid "The issue stage shows the time it takes from creating an issue to assigni
msgstr "Ð¡Ñ‚Ð°Ð´Ñ–Ñ Ð—Ð°Ð´Ð°Ñ‡Ð° показує, Ñкільки чаÑу потрібно від ÑÑ‚Ð²Ð¾Ñ€ÐµÐ½Ð½Ñ Ð·Ð°Ð´Ð°Ñ‡Ñ– до Ð²ÐºÐ»ÑŽÑ‡ÐµÐ½Ð½Ñ Ñ—Ñ— до ÑкогоÑÑŒ етапу, або Ð´Ð¾Ð´Ð°Ð²Ð°Ð½Ð½Ñ Ð·Ð°Ð´Ð°Ñ‡Ñ– на дошку. Почніть Ñтворювати задачі, щоб переглÑдати дані Ð´Ð»Ñ Ñ†Ñ–Ñ”Ñ— Ñтадії."
msgid "The license key is invalid. Make sure it is exactly as you received it from GitLab Inc."
-msgstr ""
+msgstr "Ваш ліцензійний ключ недійÑний. ПереконайтеÑÑ, що він збігаєтьÑÑ Ð· тим, що ви отримали від GitLab Inc."
msgid "The license was removed. GitLab has fallen back on the previous license."
msgstr "Ліцензію було видалено. GitLab викориÑтовує попередню ліцензію."
@@ -22385,9 +23121,6 @@ msgstr "ЧаÑ, витрачений на кожен елемент, зібраÐ
msgid "The total stage shows the time it takes between creating an issue and deploying the code to production. The data will be automatically added once you have completed the full idea to production cycle."
msgstr ""
-msgid "The unique identifier for the Geo node. Must match %{geoNodeName} if it is set in gitlab.rb, otherwise it must match %{externalUrl} with a trailing slash"
-msgstr ""
-
msgid "The update action will time out after %{number_of_minutes} minutes. For big repositories, use a clone/push combination."
msgstr "Ð§Ð°Ñ Ð´Ñ–Ñ— Ð¾Ð½Ð¾Ð²Ð»ÐµÐ½Ð½Ñ Ñплине через %{number_of_minutes} хвилин. Ð”Ð»Ñ Ð²ÐµÐ»Ð¸ÐºÐ¸Ñ… репозиторіїв викориÑтовуйте комбінацію clone/push."
@@ -22412,9 +23145,6 @@ msgstr "Мапа кориÑтувачів — це правила імпорту
msgid "The user you are trying to deactivate has been active in the past %{minimum_inactive_days} days and cannot be deactivated"
msgstr "КориÑтувач Ñкого ви збираєтеÑÑ Ð´ÐµÐ°ÐºÑ‚Ð¸Ð²ÑƒÐ²Ð°Ñ‚Ð¸ був активним протÑгом оÑтанніх %{minimum_inactive_days} днів Ñ– не може бути деактивований"
-msgid "The user-facing URL of the Geo node"
-msgstr "URL-адреÑа вузла Geo, видима Ð´Ð»Ñ ÐºÐ¾Ñ€Ð¸Ñтувача"
-
msgid "The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6."
msgstr "Середнє Ð·Ð½Ð°Ñ‡ÐµÐ½Ð½Ñ Ð² Ñ€Ñдку. Приклад: між 3, 5, 9, Ñередніми 5, між 3, 5, 7, 8, Ñередніми (5 + 7) / 2 = 6."
@@ -22485,7 +23215,7 @@ msgid "There are no packages yet"
msgstr "Ðаразі пакети відÑутні"
msgid "There are no projects shared with this group yet"
-msgstr ""
+msgstr "Ще немає Ñпільних проєктів з цією групою"
msgid "There are no variables yet."
msgstr ""
@@ -22505,6 +23235,9 @@ msgstr "Проблема Ð¿Ñ–Ð´ÐºÐ»ÑŽÑ‡ÐµÐ½Ð½Ñ Ð´Ð¾ вашого приÑтро
msgid "There was a problem fetching project branches."
msgstr ""
+msgid "There was a problem fetching project tags."
+msgstr ""
+
msgid "There was a problem fetching project users."
msgstr ""
@@ -22512,7 +23245,7 @@ msgid "There was a problem fetching users."
msgstr ""
msgid "There was a problem refreshing the data, please try again"
-msgstr ""
+msgstr "Під Ñ‡Ð°Ñ Ð¾Ð½Ð¾Ð²Ð»ÐµÐ½Ð½Ñ Ð´Ð°Ð½Ð¸Ñ… ÑталаÑÑ Ð¿Ð¾Ð¼Ð¸Ð»ÐºÐ°. Будь лаÑка, Ñпробуйте ще раз"
msgid "There was a problem saving your custom stage, please try again"
msgstr "Проблема зі збереженнÑм вашої кориÑтувацької Ñтадії, будь лаÑка, Ñпробуйте ще раз"
@@ -22527,10 +23260,10 @@ msgid "There was an error adding a To Do."
msgstr "Помилка при додаванні нагадуваннÑ."
msgid "There was an error creating the dashboard, branch name is invalid."
-msgstr ""
+msgstr "СталаÑÑ Ð¿Ð¾Ð¼Ð¸Ð»ÐºÐ° при Ñтворенні інформаційної панелі, назва гілки недійÑна."
msgid "There was an error creating the dashboard, branch named: %{branch} already exists."
-msgstr ""
+msgstr "СталаÑÑ Ð¿Ð¾Ð¼Ð¸Ð»ÐºÐ° при Ñтворенні інформаційної панелі, гілка під назвою: %{branch} вже Ñ–Ñнує."
msgid "There was an error creating the issue"
msgstr "Помилка при Ñтворенні задачі"
@@ -22554,6 +23287,9 @@ msgid "There was an error fetching median data for stages"
msgstr ""
msgid "There was an error fetching the %{replicableType}"
+msgstr "СталаÑÑ Ð¿Ð¾Ð¼Ð¸Ð»ÐºÐ° при отриманні %{replicableType}"
+
+msgid "There was an error fetching the Geo Settings"
msgstr ""
msgid "There was an error fetching the Node's Groups"
@@ -22620,10 +23356,10 @@ msgid "There was an error trying to validate your query"
msgstr "Помилка при перевірці вашого запиту"
msgid "There was an error updating the dashboard, branch name is invalid."
-msgstr ""
+msgstr "СталаÑÑ Ð¿Ð¾Ð¼Ð¸Ð»ÐºÐ° при оновлені інформаційної панелі, назва гілки недійÑна."
msgid "There was an error updating the dashboard, branch named: %{branch} already exists."
-msgstr ""
+msgstr "СталаÑÑ Ð¿Ð¾Ð¼Ð¸Ð»ÐºÐ° при оновлені інформаційної панелі, гілка під назвою: %{branch} вже Ñ–Ñнує."
msgid "There was an error updating the stage order. Please try reloading the page."
msgstr ""
@@ -22676,6 +23412,12 @@ msgstr "Ð¦Ñ %{issuableDisplayName} заблокована. Лише учаÑнÐ
msgid "This %{issuable} is locked. Only <strong>project members</strong> can comment."
msgstr "Ð¦Ñ %{issuable} заблокована. Лише <strong>учаÑники проекту</strong> можуть коментувати."
+msgid "This %{noteableTypeText} is %{confidentialLinkStart}confidential%{linkEnd} and %{lockedLinkStart}locked%{linkEnd}."
+msgstr ""
+
+msgid "This %{noteableTypeText} is locked."
+msgstr ""
+
msgid "This %{viewer} could not be displayed because %{reason}. You can %{options} instead."
msgstr "Цей %{viewer} не може бути відображено через %{reason}. ЗаміÑÑ‚ÑŒ цього можна %{options}."
@@ -22694,8 +23436,11 @@ msgstr ""
msgid "This action can lead to data loss. To prevent accidental actions we ask you to confirm your intention."
msgstr "Ð¦Ñ Ð´Ñ–Ñ Ð¼Ð¾Ð¶Ðµ призвеÑти до втрати даних. Щоб уникнути випадкових дій, проÑимо Ð²Ð°Ñ Ð¿Ñ–Ð´Ñ‚Ð²ÐµÑ€Ð´Ð¸Ñ‚Ð¸ Ñвій намір."
+msgid "This also resolves all related threads"
+msgstr ""
+
msgid "This also resolves the discussion"
-msgstr "Також ще завершує обговореннÑ"
+msgstr ""
msgid "This application was created by %{link_to_owner}."
msgstr "Цей заÑтоÑунок було Ñтворено %{link_to_owner}."
@@ -22784,6 +23529,9 @@ msgstr ""
msgid "This epic does not exist or you don't have sufficient permission."
msgstr "Цей епік не Ñ–Ñнує або у Ð²Ð°Ñ Ð½ÐµÐ¼Ð°Ñ” доÑтатніх дозволів."
+msgid "This feature is currently in beta. We invite you to %{linkStart}give feedback%{linkEnd}."
+msgstr ""
+
msgid "This feature requires local storage to be enabled"
msgstr "Ð¦Ñ Ñ„ÑƒÐ½Ñ†Ñ–Ð¾Ð½Ð°Ð»ÑŒÐ½Ñ–ÑÑ‚ÑŒ вимагає ÑƒÐ²Ñ–Ð¼ÐºÐ½ÐµÐ½Ð½Ñ Ð»Ð¾ÐºÐ°Ð»ÑŒÐ½Ð¾Ð³Ð¾ Ñховища"
@@ -22817,8 +23565,8 @@ msgstr "Це — кориÑтувач \"Ghost User\", Ñкий було Ñтво
msgid "This is a Work in Progress"
msgstr "Це Ñ” WIP (в процеÑÑ–)"
-msgid "This is a confidential issue."
-msgstr "Це конфіденційна задача."
+msgid "This is a confidential %{noteableTypeText}."
+msgstr ""
msgid "This is a delayed job to run in %{remainingTime}"
msgstr "Це завданнÑ, відкладено на %{remainingTime}"
@@ -22844,9 +23592,6 @@ msgstr ""
msgid "This is your current session"
msgstr "Це ваш поточний ÑеанÑ"
-msgid "This issue is %{confidentialLinkStart}confidential%{linkEnd} and %{lockedLinkStart}locked%{linkEnd}."
-msgstr "Ð¦Ñ Ð·Ð°Ð´Ð°Ñ‡Ð° Ñ” %{confidentialLinkStart}конфіденційною%{linkEnd} та %{lockedLinkStart}заблокованою%{linkEnd}."
-
msgid "This issue is confidential"
msgstr "Ð¦Ñ Ð·Ð°Ð´Ð°Ñ‡Ð° Ñ” конфіденційною"
@@ -22856,9 +23601,6 @@ msgstr ""
msgid "This issue is in a child epic of the filtered epic"
msgstr ""
-msgid "This issue is locked."
-msgstr "Ð¦Ñ Ð·Ð°Ð´Ð°Ñ‡Ð° заблокована."
-
msgid "This job depends on other jobs with expired/erased artifacts: %{invalid_dependencies}"
msgstr "Це Ð·Ð°Ð²Ð´Ð°Ð½Ð½Ñ Ð·Ð°Ð»ÐµÐ¶Ð¸Ñ‚ÑŒ від інших завдань із заÑтарілими/Ñтертими артефактами: %{invalid_dependencies}"
@@ -22958,6 +23700,9 @@ msgstr "Ð—Ð°Ð²Ð´Ð°Ð½Ð½Ñ Ð±ÑƒÐ´Ðµ запущено автоматично піÑ
msgid "This license has already expired."
msgstr ""
+msgid "This link points to external content"
+msgstr ""
+
msgid "This may expose confidential information as the selected fork is in another namespace that can have other members."
msgstr "Це може розкрити конфіденційну інформацію, оÑкільки вибраний форк знаходитьÑÑ Ð² іншому проÑторі імен, Ñкий може мати інших учаÑників."
@@ -22985,6 +23730,9 @@ msgstr "Ð¦Ñ Ñторінка недоÑтупна, тому що ви не мо
msgid "This page will be removed in a future release."
msgstr "Цю Ñторінку буде видалено у майбутній верÑÑ–Ñ—."
+msgid "This pipeline does not use the %{codeStart}needs%{codeEnd} keyword and can't be represented as a directed acyclic graph."
+msgstr ""
+
msgid "This pipeline makes use of a predefined CI/CD configuration enabled by %{strongStart}Auto DevOps.%{strongEnd}"
msgstr "Цей конвеєр викориÑтовує попередньо визначену конфігурацію CI / CD, увімкнену за допомогою %{strongStart}Auto DevOps.%{strongEnd}"
@@ -23213,6 +23961,9 @@ msgstr "Ð§Ð°Ñ Ð²Ñ–Ð´ першого коміту до першого комен
msgid "Time from last commit to merge"
msgstr "Ð§Ð°Ñ Ð²Ñ–Ð´ оÑтаннього коміту до злиттÑ"
+msgid "Time in seconds"
+msgstr ""
+
msgid "Time in seconds GitLab will wait for a response from the external service. When the service does not respond in time, access will be denied."
msgstr "Ð§Ð°Ñ Ð² Ñекундах, протÑгом Ñкого GitLab чекатиме відповіді від зовнішньої Ñлужби. Якщо вона не відповіÑÑ‚ÑŒ вчаÑно, доÑтуп буде заборонений."
@@ -23638,9 +24389,15 @@ msgstr "Загальна кількіÑÑ‚ÑŒ внеÑків"
msgid "Total artifacts size: %{total_size}"
msgstr "Загальний розмір артефактів: %{total_size}"
+msgid "Total cores (CPUs)"
+msgstr ""
+
msgid "Total issues"
msgstr "Ð’Ñього задач"
+msgid "Total memory (GB)"
+msgstr ""
+
msgid "Total test time for all commits/merges"
msgstr "Загальний чаÑ, щоб перевірити вÑÑ– коміти/злиттÑ"
@@ -23842,6 +24599,9 @@ msgstr "U2F приÑтрої (%{length})"
msgid "U2F only works with HTTPS-enabled websites. Contact your administrator for more details."
msgstr "U2F працює лише з веб-Ñайтами з підтримкою HTTPS. Ð”Ð»Ñ Ð¾Ñ‚Ñ€Ð¸Ð¼Ð°Ð½Ð½Ñ Ð±Ñ–Ð»ÑŒÑˆ детальної інформації звернітьÑÑ Ð´Ð¾ адмініÑтратора."
+msgid "UI Development Kit"
+msgstr ""
+
msgid "URL"
msgstr "URL"
@@ -23861,7 +24621,7 @@ msgid "URL or request ID"
msgstr "URL або ідентифікатор запиту"
msgid "UTC"
-msgstr ""
+msgstr "UTC"
msgid "Unable to apply suggestions to a deleted line."
msgstr "Ðеможливо заÑтоÑувати пропозиції до видаленого Ñ€Ñдка."
@@ -23914,9 +24674,6 @@ msgstr "Ðеможливо завантажити порівнÑÐ½Ð½Ñ (diff). %
msgid "Unable to load the merge request widget. Try reloading the page."
msgstr "Ðе вдалоÑÑ Ð·Ð°Ð²Ð°Ð½Ñ‚Ð°Ð¶Ð¸Ñ‚Ð¸ віджет запиту на злиттÑ. Спробуйте перезавантажити Ñторінку."
-msgid "Unable to process group import file"
-msgstr ""
-
msgid "Unable to resolve"
msgstr "Ðе вдалоÑÑ Ð²Ð¸Ñ€Ñ–ÑˆÐ¸Ñ‚Ð¸"
@@ -23932,6 +24689,9 @@ msgstr "ÐевдалоÑÑ Ð·Ð°Ð¿ÑƒÑтити конвеєр негайно"
msgid "Unable to sign you in to the group with SAML due to \"%{reason}\""
msgstr "Ðеможливо увійти до групи за допомогою SAML через \"%{reason}\""
+msgid "Unable to suggest a path. Please refresh and try again."
+msgstr ""
+
msgid "Unable to update label prioritization at this time"
msgstr "Ðаразі неможливо оновити пріоретизацію міток"
@@ -23945,6 +24705,9 @@ msgid "Unarchive project"
msgstr "Розархівувати проєкт"
msgid "Unarchiving the project will restore people's ability to make changes to it. The repository can be committed to, and issues, comments, and other entities can be created. %{strong_start}Once active, this project shows up in the search and on the dashboard.%{strong_end}"
+msgstr "Ð Ð¾Ð·Ð°Ñ€Ñ…Ñ–Ð²ÑƒÐ²Ð°Ð½Ð½Ñ Ð¿Ñ€Ð¾Ñ”ÐºÑ‚Ñƒ знову даÑÑ‚ÑŒ можливіÑÑ‚ÑŒ людÑм вноÑити до нього зміни. До Ñховища можна буде відправлÑти коміти, також можуть бути Ñтворені задачі, коментарі та інше. %{strong_start}ПіÑÐ»Ñ Ð°ÐºÑ‚Ð¸Ð²Ð°Ñ†Ñ–Ñ—, цей проєкт з'ÑвлÑєтьÑÑ Ð² пошуку та на панелі керуваннÑ.%{strong_end}"
+
+msgid "Unassign from commenting user"
msgstr ""
msgid "Unblock"
@@ -24022,6 +24785,9 @@ msgstr "Знімає Ð¿Ð¾Ð·Ð½Ð°Ñ‡ÐµÐ½Ð½Ñ WIP (в процеÑÑ–) з цього
msgid "Unreachable"
msgstr ""
+msgid "Unrecognized cluster type"
+msgstr ""
+
msgid "Unresolve"
msgstr ""
@@ -24082,6 +24848,9 @@ msgstr "ВідпиÑано від %{quick_action_target}."
msgid "Unsubscribes from this %{quick_action_target}."
msgstr "ВідпиÑує від %{quick_action_target}."
+msgid "Unsupported todo type passed. Supported todo types are: %{todo_types}"
+msgstr ""
+
msgid "Until"
msgstr "До"
@@ -24109,6 +24878,9 @@ msgstr "Оновити вÑе"
msgid "Update approval rule"
msgstr "Оновити правило затвердженнÑ"
+msgid "Update approvers"
+msgstr ""
+
msgid "Update failed"
msgstr "ÐžÐ½Ð¾Ð²Ð»ÐµÐ½Ð½Ñ Ð½Ðµ вдалоÑÑ"
@@ -24118,6 +24890,9 @@ msgstr "ÐžÐ½Ð¾Ð²Ð»ÐµÐ½Ð½Ñ Ð½ÐµÑƒÑпішне. Будь лаÑка, Ñпробу
msgid "Update it"
msgstr "Оновити це"
+msgid "Update iteration"
+msgstr ""
+
msgid "Update now"
msgstr "Оновити зараз"
@@ -24163,8 +24938,8 @@ msgstr "Оновлено %{updated_at} %{updated_by}"
msgid "Updated at"
msgstr "Оновлено"
-msgid "Updated to"
-msgstr "Оновлено до"
+msgid "Updated to %{linkStart}chart v%{linkEnd}"
+msgstr ""
msgid "Updating"
msgstr "ОновленнÑ"
@@ -24193,6 +24968,9 @@ msgstr "ÐадіÑлати <code>GoogleCodeProjectHosting.json</code> тут:"
msgid "Upload CSV file"
msgstr "Завантажити CSV файл"
+msgid "Upload License"
+msgstr ""
+
msgid "Upload New File"
msgstr "ÐадіÑлати новий файл"
@@ -24274,18 +25052,21 @@ msgstr "Пакети"
msgid "UsageQuota|Pipelines"
msgstr "Конвеєри"
+msgid "UsageQuota|Purchase more storage"
+msgstr ""
+
msgid "UsageQuota|Repositories"
msgstr ""
msgid "UsageQuota|Repository"
msgstr "Репозиторій"
+msgid "UsageQuota|Snippets"
+msgstr ""
+
msgid "UsageQuota|Storage"
msgstr "Сховище"
-msgid "UsageQuota|Storage usage:"
-msgstr ""
-
msgid "UsageQuota|This namespace has no projects which use shared runners"
msgstr "Цей проÑÑ‚Ñ–Ñ€ імен не міÑтить проектів, що викориÑтовують загальні runner'и"
@@ -24314,6 +25095,12 @@ msgid "UsageQuota|Wiki"
msgstr "Вікі"
msgid "UsageQuota|Wikis"
+msgstr "Вікі"
+
+msgid "UsageQuota|You used: %{usage} %{limit}"
+msgstr ""
+
+msgid "UsageQuota|out of %{formattedLimit} of your namespace storage"
msgstr ""
msgid "Use %{code_start}::%{code_end} to create a %{link_start}scoped label set%{link_end} (eg. %{code_start}priority::1%{code_end})"
@@ -24382,6 +25169,9 @@ msgstr "Когорти КориÑтувачів показуютьÑÑ Ð»Ð¸ÑˆÐµ
msgid "User IDs"
msgstr ""
+msgid "User Lists can only be created and modified with %{linkStart}the API%{linkEnd}"
+msgstr ""
+
msgid "User OAuth applications"
msgstr "OAuth заÑтоÑунки кориÑтувача"
@@ -24406,6 +25196,9 @@ msgstr ""
msgid "User key was successfully removed."
msgstr "Ключ кориÑтувача уÑпішно видалено."
+msgid "User list %{name} will be removed. Are you sure?"
+msgstr ""
+
msgid "User map"
msgstr "Мапа кориÑтувачів"
@@ -24427,149 +25220,11 @@ msgstr "КориÑтувача уÑпішно видалено із проект
msgid "User was successfully updated."
msgstr "КориÑтувача було уÑпішно оновлено."
-msgid "UserOnboardingTour|%{activeTour}/%{totalTours}"
-msgstr "%{activeTour}/%{totalTours}"
-
-msgid "UserOnboardingTour|%{completed}/%{total} steps completed"
-msgstr "%{completed}/%{total} кроків завершено"
-
-msgid "UserOnboardingTour|%{emphasisStart}Well done!%{emphasisEnd}%{lineBreak}%{lineBreak}That's it for our guided tour, congratulations for making it all the way to the end!%{lineBreak}%{lineBreak}We hope this gave you a good overview of GitLab and how it can help you. We'll now show you how to create your own project and invite your colleagues."
-msgstr "%{emphasisStart}Чудово!%{emphasisEnd}%{lineBreak}%{lineBreak}Ðа цьому вашу екÑкурÑÑ–ÑŽ завершено, вітаємо із проходженнÑм Ñ—Ñ— до кінцÑ!%{lineBreak}%{lineBreak}СподіваємоÑÑ, що вона дала вам хороший оглÑд GitLab Ñ– те, Ñк він може вам допомогти. Зараз ми вам покажемо Ñк Ñтворити влаÑний проект та запроÑити ваших колег."
-
-msgid "UserOnboardingTour|Adding other members to a project is done through Project Settings. Click on %{emphasisStart}Settings%{emphasisEnd}."
-msgstr "Ð”Ð¾Ð´Ð°Ð²Ð°Ð½Ð½Ñ ÑƒÑ‡Ð°Ñників до проекту відбуваєтьÑÑ Ñ‡ÐµÑ€ÐµÐ· Ð½Ð°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ð¿Ñ€Ð¾ÐµÐºÑ‚Ñƒ. Перейдіть до %{emphasisStart}налаштувань%{emphasisEnd}."
-
-msgid "UserOnboardingTour|Alright, that's it for Commits. Let's take a look at the %{emphasisStart}Branches%{emphasisEnd}."
-msgstr "Отже це вÑе про коміти. Тепер давайте розглÑнемо %{emphasisStart}гілки%{emphasisEnd}."
-
-msgid "UserOnboardingTour|Awesome! Now click on %{emphasisStart}Members%{emphasisEnd}."
-msgstr "Чудово! Тепер натиÑніть %{emphasisStart}УчаÑники%{emphasisEnd}."
-
-msgid "UserOnboardingTour|Click on one of the %{emphasisStart}Compare%{emphasisEnd} buttons to compare a branch to master."
-msgstr "ÐатиÑніть на одну з кнопок %{emphasisStart}порівнÑннÑ%{emphasisEnd}, щоб порівнÑти ÑкуÑÑŒ гілку із master."
-
-msgid "UserOnboardingTour|Click on one of the %{emphasisStart}pipeline IDs%{emphasisEnd} to see the details of a pipeline."
-msgstr "ÐатиÑніть на %{emphasisStart}ідентифікатор конвеєру%{emphasisEnd} щоб переглÑнути його деталі."
-
-msgid "UserOnboardingTour|Click to open the latest commit to see its details."
-msgstr "ÐатиÑніть щоб відкрити оÑтанній коміт та переглÑнути його деталі."
-
-msgid "UserOnboardingTour|Close 'Learn GitLab'"
-msgstr "Закрити \"ДізнатиÑÑ Ð¿Ñ€Ð¾ GitLab\""
-
-msgid "UserOnboardingTour|Commits are shown in chronological order and can be filtered by the commit message or by the branch."
-msgstr "Коміти відображаютьÑÑ Ð² хронологічному порÑдку та можуть бути відфільтровані за повідомленнÑм коміту та за гілкою."
-
-msgid "UserOnboardingTour|Create a project"
-msgstr "Створити проєкт"
-
-msgid "UserOnboardingTour|Exit 'Learn GitLab'"
-msgstr "Вийти з \"ДізнатиÑÑ Ð¿Ñ€Ð¾ GitLab\""
-
-msgid "UserOnboardingTour|Got it"
-msgstr "Зрозуміло"
-
-msgid "UserOnboardingTour|Great job! %{clapHands} We hope the tour was helpful and that you learned how to use GitLab.%{lineBreak}%{lineBreak}We'd love to get your feedback on this tour.%{lineBreak}%{lineBreak}%{emphasisStart}How helpful would you say this guided tour was?%{emphasisEnd}%{lineBreak}%{lineBreak}"
-msgstr "Чудово! %{clapHands} Ми ÑподіваємоÑÑ, що Ñ†Ñ ÐµÐºÑкурÑÑ–Ñ Ð±ÑƒÐ»Ð° кориÑною Ñ– ви дізналиÑÑ Ñк кориÑтуватиÑÑ GitLab.%{lineBreak}%{lineBreak}Ми хотіли б отримати від Ð²Ð°Ñ Ð²Ñ–Ð´Ð³ÑƒÐº про неї.%{lineBreak}%{lineBreak}%{emphasisStart}ÐаÑкільки кориÑною Ð±ÑƒÐ»Ñ Ñ†Ñ ÐµÐºÑкурÑÑ–Ñ?%{emphasisEnd}%{lineBreak}%{lineBreak}"
-
-msgid "UserOnboardingTour|Guided GitLab Tour"
-msgstr "ЕкÑкурÑÑ–Ñ Ð¿Ð¾ GitLab"
-
-msgid "UserOnboardingTour|Here you can compare the changes of this branch to another one. Changes are divided by files so that it's easier to see what was changed where."
-msgstr "Тут ви можете порівнювати зміни цієї вітки із ÑкоюÑÑŒ іншою. Зміни поділені на файли Ð´Ð»Ñ Ñ‚Ð¾Ð³Ð¾, щоб було наочніше що де змінилоÑÑ."
-
-msgid "UserOnboardingTour|Here you can create a project from scratch, start with a template or import a repository from other platforms. Whatever you choose, we'll guide you through the process.%{lineBreak}%{lineBreak}Fill in your new project information and click on %{emphasisStart}Create Project%{emphasisEnd} to progress to the next step."
-msgstr "Тут ви можете Ñтворити порожній проект, викориÑтати шаблон або імпортувати репозиторій з інших платформ. Що б ви не вибрали, ми підказуватимемо вам.%{lineBreak}%{lineBreak}Введіть інформацію про новий проект та натиÑніть %{emphasisStart}Створити проект%{emphasisEnd} Ð´Ð»Ñ Ð¿ÐµÑ€ÐµÑ…Ð¾Ð´Ñƒ на наÑтупний крок."
-
-msgid "UserOnboardingTour|Here you can see the breakdown of the pipelines: its stages and jobs in each of the stages and their status.%{lineBreak}%{lineBreak}Our CI/CD pipelines are quite complex, most of our users have fewer and simpler pipelines."
-msgstr "Тут ви можете бачити детальну інформацію про конвеєри: їхні Ñтадії та Ð·Ð°Ð²Ð´Ð°Ð½Ð½Ñ Ð² кожній із цих Ñтадій, а також їхній ÑтатуÑ.%{lineBreak}%{lineBreak}Ðаші конвеєри CI/CD доÑить Ñкладні, більшіÑÑ‚ÑŒ кориÑтувачів має менше конвеєрів Ñ– вони Ñ” проÑтішими."
-
-msgid "UserOnboardingTour|Here you can see the current members of the project (just you at the moment) and invite new members.%{lineBreak}%{lineBreak}You can invite multiple members at once (existing GitLab users or invite by email) and you can also set their roles and permissions.%{lineBreak}%{lineBreak}Add a few members and click on %{emphasisStart}Add to project%{emphasisEnd} to complete this step."
-msgstr "Тут ви можете бачити поточних учаÑників проекту (наразі лише ви) та запрошувати нових учаÑників.%{lineBreak}%{lineBreak}Ви можете запрошувати відразу по кілька кориÑтувачів за один раз (Ñ–Ñнуючих кориÑтувачів GitLab чи через адреÑу електронної пошти), а також вÑтановлювати Ñ—Ñ… ролі та дозволи.%{lineBreak}%{lineBreak}Додайте кілька учаÑників, натиÑнувши на %{emphasisStart}Додати до проекту%{emphasisEnd} Ð´Ð»Ñ Ð·Ð°Ð²ÐµÑ€ÑˆÐµÐ½Ð½Ñ Ñ†ÑŒÐ¾Ð³Ð¾ кроку."
-
-msgid "UserOnboardingTour|Here you can see what changes were made with this commit, on what branch and if there's a related merge request. The status of the pipeline will also show up if CI/CD is set up.%{lineBreak}%{lineBreak}You can also comment on the lines of code that were changed and start a discussion with your colleagues!"
-msgstr "Тут ви можете бачити Ñкі зміни були виконані в цьому коміті, у Ñкій гілці та чи Ñ–Ñнує пов'Ñзаний запит на злиттÑ. Також відображатиметьÑÑ Ñтан конвеєра, Ñкщо налаштовано CI/CD.%{lineBreak}%{lineBreak}Ви також можете залишати коментарі до змінених Ñ€Ñдків коду Ñ– розпочинати Ð¾Ð±Ð³Ð¾Ð²Ð¾Ñ€ÐµÐ½Ð½Ñ Ñ–Ð· колегами!"
-
-msgid "UserOnboardingTour|Here's an overview of branches in the %{emphasisStart}%{projectName}%{emphasisEnd} project. They're split into Active and Stale.%{lineBreak}%{lineBreak}From here, you can create a new merge request from a branch, or compare the branch to any other branch in the project. By default, it will compare it to the master branch."
-msgstr "ОÑÑŒ оглÑд гілок в проекті %{emphasisStart}%{projectName}%{emphasisEnd}. Вони розбиті на \"Ðктивні\" та \"ЗаÑтарілі\".%{lineBreak}%{lineBreak}Тут ви можете Ñтворити новий запит на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ñ–Ð· ÑкоїÑÑŒ гілки або порівнÑти дві гілки з проекту між Ñобою. За замовчуваннÑм буде відбуватиÑÑ Ð¿Ð¾Ñ€Ñ–Ð²Ð½ÑÐ½Ð½Ñ Ñ–Ð· гілкою master."
-
-msgid "UserOnboardingTour|Invite colleagues"
-msgstr "ЗапроÑити колег"
-
-msgid "UserOnboardingTour|Issues are great for communicating and keeping track of progress in GitLab. These are all issues that are open in the %{emphasisStart}%{projectName}%{emphasisEnd}.%{lineBreak}%{lineBreak}You can help us improve GitLab by contributing work to issues that are labeled <span class=\"badge color-label accept-mr-label\">Accepting merge requests</span>.%{lineBreak}%{lineBreak}This list can be filtered by labels, milestones, assignees, authors... We'll show you how it looks when the list is filtered by a label."
-msgstr "Задачі добре підходÑÑ‚ÑŒ Ð´Ð»Ñ ÐºÐ¾Ð¼ÑƒÐ½Ñ–ÐºÐ°Ñ†Ñ–Ñ— та відÑÑ‚ÐµÐ¶ÐµÐ½Ð½Ñ Ð¿Ñ€Ð¾Ð³Ñ€ÐµÑу в GitLab. ОÑÑŒ задачі, Ñкі відкриті в %{emphasisStart}%{projectName}%{emphasisEnd}.%{lineBreak}%{lineBreak}Ви можете допомогти нам покращити GitLab шлÑхом внеÑків у задачі, що відмічені <span class=\"badge color-label accept-mr-label\">ПриймаютьÑÑ Ð·Ð°Ð¿Ð¸Ñ‚Ð¸ на злиттÑ</span>.%{lineBreak}%{lineBreak}Цей ÑпиÑок може бути відвільтрований за мітками, етапами, виконавцÑми, авторами... Ми продоемонÑтруємо Ñк виглÑдає ÑпиÑок, відфільтрований за міткою."
-
-msgid "UserOnboardingTour|Learn GitLab"
-msgstr "ДізнатиÑÑ Ð¿Ñ€Ð¾ GitLab"
-
-msgid "UserOnboardingTour|Let's take a closer look at a merge request. Click on the title of one."
-msgstr "Давайте розглÑнемо детальніше запити на злиттÑ. ÐатиÑніть на заголовко одного з них."
-
-msgid "UserOnboardingTour|Let's take a closer look at all the commits. Click on %{emphasisStart}Commits%{emphasisEnd}."
-msgstr "Давайте розглÑнемо детальніше вÑÑ– коміти. ÐатиÑтніть на %{emphasisStart}Коміти%{emphasisEnd}."
-
-msgid "UserOnboardingTour|Let's take a closer look at the repository of this project. Click on %{emphasisStart}Repository%{emphasisEnd}."
-msgstr "Давайте розглÑнемо детальніше репозиторій цього проекту. ÐатиÑніть на %{emphasisStart}Репозиторій%{emphasisEnd}."
-
-msgid "UserOnboardingTour|No thanks"
-msgstr "ÐÑ–, дÑкую"
-
-msgid "UserOnboardingTour|Ok, let's go"
-msgstr "Гаразд, уперед"
-
-msgid "UserOnboardingTour|Ok, show me"
-msgstr "Гаразд, показати"
-
-msgid "UserOnboardingTour|Open one of the issues by clicking on its title."
-msgstr "Відкрийте одну із задач, натиÑнувши на Ñ—Ñ— заголовок."
-
-msgid "UserOnboardingTour|Restart this step"
-msgstr "ПерезапуÑтити цей крок"
-
-msgid "UserOnboardingTour|Skip this step"
-msgstr "ПропуÑтити цей крок"
-
-msgid "UserOnboardingTour|Sweet! Your project was created and is ready to be used.%{lineBreak}%{lineBreak}You can start adding files to the repository or clone it. One last thing we want to show you is how to invite your colleagues to your new project."
-msgstr "Чудово! Ваш проект Ñтворено Ñ– він готовий до викориÑтаннÑ.%{lineBreak}%{lineBreak}Ви можете почати додавати файли до репозиторію чи клонувати його. ОÑтаннє, що ми хочемо вам показати, Ñк запрошувати колег до вашого проекту."
-
-msgid "UserOnboardingTour|Take a look. Here's a nifty menu for quickly creating issues, merge requests, snippets, projects and groups. Click on it and select \"New project\" from the \"GitLab\" section to get started."
-msgstr "ПоглÑньте. ОÑÑŒ зручне меню Ð´Ð»Ñ ÑˆÐ²Ð¸Ð´ÐºÐ¾Ð³Ð¾ ÑÑ‚Ð²Ð¾Ñ€ÐµÐ½Ð½Ñ Ð·Ð°Ð´Ð°Ñ‡, запитів на злиттÑ, Ñніпетів, проектів та груп. Ð”Ð»Ñ Ð¿Ð¾Ñ‡Ð°Ñ‚ÐºÑƒ натиÑніть на нього та виберіть \"Ðовий проект\" із розділу \"GitLab\"."
-
-msgid "UserOnboardingTour|Thanks for taking the guided tour. Remember, if you want to go through it again, you can start %{emphasisStart}Learn GitLab%{emphasisEnd} in the help menu on the top right."
-msgstr "ДÑкуємо за Ð¿Ñ€Ð¾Ñ…Ð¾Ð´Ð¶ÐµÐ½Ð½Ñ ÐµÐºÑкурÑÑ–Ñ—. Пам’Ñтайте, що Ð´Ð»Ñ Ñ—Ñ— повторного проходженнÑ, ви можете вибрати %{emphasisStart}ДізнатиÑÑ Ð¿Ñ€Ð¾ GitLab%{emphasisEnd} в меню допомоги в правому верхньому куті."
-
-msgid "UserOnboardingTour|Thanks for the feedback! %{thumbsUp}"
-msgstr "СпаÑибі за ваш відгук! %{thumbsUp}"
-
-msgid "UserOnboardingTour|That's it for issues. Let'st take a look at %{emphasisStart}Merge Requests%{emphasisEnd}."
-msgstr "Отже це вÑе про задачі. Тепер давайте розглÑнемо %{emphasisStart}запити на злиттÑ%{emphasisEnd}."
-
-msgid "UserOnboardingTour|That's it for merge requests. Now for the final part of this guided tour - the %{emphasisStart}CI/CD%{emphasisEnd}."
-msgstr "Отже це вÑе про запити на злиттÑ. ОÑтанній розділ цієї екÑкурÑÑ–Ñ— — %{emphasisStart}CI/CD%{emphasisEnd}."
-
-msgid "UserOnboardingTour|That's it for the Repository. Let's take a look at the %{emphasisStart}Issues%{emphasisEnd}."
-msgstr "Отже, це вÑе про репозиторій. Тепер давайте розглÑнемо %{emphasisStart}задачі%{emphasisEnd}."
-
-msgid "UserOnboardingTour|The structure of this page is very similar to issues. Status, description, discussion and the sidebar are all here.%{lineBreak}%{lineBreak}But take a look below the description and you'll notice that there's more information about the merge request, the CI/CD pipeline and the options for approving it.%{lineBreak}%{lineBreak}Alongside the discussion you can also see more information about commits in this merge request, the status of pipelines and review all changes that were made."
-msgstr "Структура цієї Ñторінки дуже Ñхожа на Ñтруктуру Ñторінки задач. Тут приÑтуні ÑтатуÑ, опиÑ, Ð¾Ð±Ð³Ð¾Ð²Ð¾Ñ€ÐµÐ½Ð½Ñ Ñ‚Ð° бічна панель.%{lineBreak}%{lineBreak}Ðле під опиÑом ви помітите більше інформації про запит на злиттÑ, конвеєр CI/CD та варіанти Ð´Ð»Ñ Ð¹Ð¾Ð³Ð¾ затвердженнÑ.%{lineBreak}%{lineBreak}Поруч із обговореннÑми ви також можете бачити інформацію про коміти в цьому запиті на злиттÑ, Ñтан конвеєрів та переглÑдати вÑÑ– зроблені зміни."
-
-msgid "UserOnboardingTour|There's a lot of information here but don't worry, we'll go through it.%{lineBreak}%{lineBreak}On the top you can see the status of the issue and when it was opened and by whom. Directly below it is the issue description and below that are other %{emphasisStart}related issues%{emphasisEnd} and %{emphasisStart}merge requests%{emphasisEnd} (if any). Then below that is the %{emphasisStart}discussion%{emphasisEnd}, that's where most of the communication happens.%{lineBreak}%{lineBreak}On the right, there's a sidebar where you can view/change the %{emphasisStart}assignee, milestone, due date, labels, weight%{emphasisEnd}, etc."
-msgstr "Тут багато інформації, але не хвилюйтеÑÑ, ми розберемоÑÑ.%{lineBreak}%{lineBreak}Угорі ви можете бачити Ñтан задачі, коли вона була Ñтворена Ñ– ким. Ðижче ви можете бачити Ð¾Ð¿Ð¸Ñ Ð·Ð°Ð´Ð°Ñ‡Ñ–, а ще нижче — інші %{emphasisStart}пов'Ñзані задачі%{emphasisEnd} та %{emphasisStart}запити на злиттÑ%{emphasisEnd} (Ñкщо Ñ”). Далі розміщене %{emphasisStart}обговореннÑ%{emphasisEnd}, де Ð²Ñ–Ð´Ð±ÑƒÐ²Ð°Ñ”Ñ‚ÑŒÑ Ð¾Ñновне ÑпілкуваннÑ.%{lineBreak}%{lineBreak}Справа знаходитьÑÑ Ð±Ñ–Ñ‡Ð½Ð° панель, де ви можете переглÑнути чи змінити %{emphasisStart}виконавцÑ, етап, заплановану дату завершеннÑ, мітки, вагу%{emphasisEnd} Ñ– Ñ‚.д."
-
-msgid "UserOnboardingTour|These are all the CI/CD pipelines we have for our %{emphasisStart}%{projectName}%{emphasisEnd} project.%{lineBreak}%{lineBreak}Here you can see the status of each pipeline, for what commit it's running for, its stages and the status for them."
-msgstr "ОÑÑŒ уÑÑ– конвеєри CI/CD, Ñкі Ñ” в проекті %{emphasisStart}%{projectName}%{emphasisEnd}.%{lineBreak}%{lineBreak}Тут ви можете бачити Ñтан кожного конвеєра, Ð´Ð»Ñ Ñкого коміту його було запущено, Ñтадії та Ñтан кожної з них."
-
-msgid "UserOnboardingTour|These are all the issues that are available for community contributions. Let's take a closer look at one of them."
-msgstr "ОÑÑŒ уÑÑ– задачі, де приймаютьÑÑ Ð²Ð½ÐµÑки від Ñпільноти. Давайте уважніше розглÑнемо одну з них."
-
-msgid "UserOnboardingTour|This is an overview of all merge requests in this project. Similarly to the issues overview it can be filtered down by things like labels, milestones, authors, assignees, etc."
-msgstr "Це оглÑд уÑÑ–Ñ… запитів на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ð² цьому проекті. Ðналогічно до оглÑду задач, його можна фільтрувати за мітками, етапами, авторами, виконавцÑми Ñ– Ñ‚. д."
-
-msgid "UserOnboardingTour|This is the repository for the %{emphasisStart}%{projectName}%{emphasisEnd} project. All our code is stored here. Feel free to explore and take a closer look at folders and files.%{lineBreak}%{lineBreak}Above the file structure you can see the latest commit, who the author is and the status of the CI/CD pipeline.%{lineBreak}%{lineBreak}If you scroll down below the file structure, you'll find the Readme of this project. This is defined in the README.md file at the root of the repository."
-msgstr "Це репозиторій Ð´Ð»Ñ Ð½Ð°ÑˆÐ¾Ð³Ð¾ проекту %{emphasisStart}%{projectName}%{emphasisEnd}. ВеÑÑŒ наш код зберігаєтьÑÑ Ñ‚ÑƒÑ‚. Можете його доÑлідити Ñ– детально розглÑнути файли та папки.%{lineBreak}%{lineBreak}Ðад файловою Ñтруктурою ви можете бачити оÑтанній коміт, його автора та Ñтан конвеєра CI/CD.%{lineBreak}%{lineBreak}Якщо ви прокрутите нижче, то під файловою Ñтруктурою ви побачите інÑтрукцію Ð´Ð»Ñ Ñ†ÑŒÐ¾Ð³Ð¾ проекту (Readme). Вона визначена у файлі README.md в корені репозиторію."
+msgid "UserList|Delete %{name}?"
+msgstr ""
-msgid "UserOnboardingTour|Welcome to the project overview of the %{emphasisStart}%{projectName}%{emphasisEnd} project. This is the project that we use to work on GitLab. At first, a project seems like a simple repository, but at GitLab, a project is so much more.%{lineBreak}%{lineBreak}You can create projects for hosting your codebase, use it as an issue tracker, collaborate on code, and continuously build, test, and deploy your app with built-in GitLab CI/CD."
-msgstr "ЛаÑкаво проÑимо до оглÑду проекту %{emphasisStart}%{projectName}%{emphasisEnd}. Це проект, Ñкий ми викориÑтовуємо Ð´Ð»Ñ Ñ€Ð¾Ð±Ð¾Ñ‚Ð¸ над GitLab. Ðа перший поглÑд здаєтьÑÑ, що проект — це лише репозиторій, але в GitLab проекти міÑÑ‚ÑÑ‚ÑŒ значно більше.%{lineBreak}%{lineBreak}Ви можете Ñтворювати проекти Ð´Ð»Ñ Ð·Ð±ÐµÑ€Ñ–Ð³Ð°Ð½Ð½Ñ Ð²Ð°ÑˆÐ¾Ð³Ð¾ коду, викориÑтовувати його Ñк трекер задач, працювати над кодом, та безперервно збирати, теÑтувати та розгортати ваш заÑтоÑунок за допомогою вбудованого GitLab CI/CD."
+msgid "UserList|created %{timeago}"
+msgstr ""
msgid "UserProfile|Activity"
msgstr "ÐктивніÑÑ‚ÑŒ"
@@ -24721,6 +25376,9 @@ msgstr "Ðемає Ð²Ð¸ÐºÐ¾Ð½Ð°Ð²Ñ†Ñ â€” %{openingTag} призначити нÐ
msgid "UsersSelect|Unassigned"
msgstr "Ðепризначено"
+msgid "Using %{codeStart}needs%{codeEnd} allows jobs to run before their stage is reached, as soon as their individual dependencies are met, which speeds up your pipelines."
+msgstr ""
+
msgid "Using %{code_start}::%{code_end} denotes a %{link_start}scoped label set%{link_end}"
msgstr "ВикориÑÑ‚Ð°Ð½Ð½Ñ %{code_start}::%{code_end} вказує на %{link_start}набір Ñелективних міток%{link_end}"
@@ -24761,7 +25419,7 @@ msgid "ValueStreamAnalytics|%{days}d"
msgstr ""
msgid "Variable"
-msgstr ""
+msgstr "Змінна"
msgid "Variable will be masked in job logs."
msgstr ""
@@ -24803,10 +25461,7 @@ msgid "Version"
msgstr "ВерÑÑ–Ñ"
msgid "Versions"
-msgstr ""
-
-msgid "Very helpful"
-msgstr "Дуже кориÑна"
+msgstr "ВерÑÑ–Ñ—"
msgid "View Documentation"
msgstr "ПереглÑнути документацію"
@@ -24960,6 +25615,9 @@ msgstr "Публічний"
msgid "VisibilityLevel|Unknown"
msgstr "Ðевідомий"
+msgid "Visit settings page"
+msgstr ""
+
msgid "VisualReviewApp|%{stepStart}Step 1%{stepEnd}. Copy the following script:"
msgstr "%{stepStart}Крок 1%{stepEnd}. Скопіюйте наÑтупний Ñкрипт:"
@@ -25053,6 +25711,9 @@ msgstr "Вирішено %{timeago} кориÑтувачем %{user}"
msgid "VulnerabilityManagement|Something went wrong while trying to delete the comment. Please try again later."
msgstr ""
+msgid "VulnerabilityManagement|Something went wrong while trying to refresh the vulnerability. Please try again later."
+msgstr ""
+
msgid "VulnerabilityManagement|Something went wrong while trying to retrieve the vulnerability history. Please try again later."
msgstr ""
@@ -25095,6 +25756,9 @@ msgstr ""
msgid "Vulnerability|Class"
msgstr "КлаÑ"
+msgid "Vulnerability|Crash Address"
+msgstr ""
+
msgid "Vulnerability|Description"
msgstr "ОпиÑ"
@@ -25170,9 +25834,6 @@ msgstr "Ми не змогли визначити шлÑÑ… до видаленн
msgid "We couldn't reach the Prometheus server. Either the server no longer exists or the configuration details need updating."
msgstr "Ðе вдалоÑÑ Ð¿Ñ–Ð´ÐºÑŽÑ‡Ð¸Ñ‚Ð¸ÑÑ Ð´Ð¾ Ñервера Prometheus. Ðбо Ñервер більше не Ñ–Ñнує, або необхідно оновити деталі конфігурації."
-msgid "We created a short guided tour that will help you learn the basics of GitLab and how it will help you be better at your job. It should only take a couple of minutes. You will be guided by two types of helpers, best recognized by their color."
-msgstr "Ми Ñтворили невелику екÑкурÑÑ–ÑŽ, Ñка допоможе розібратиÑÑ Ñ–Ð· базовими понÑÑ‚Ñ‚Ñми GitLab Ñ– те, Ñк він допоможе вам у роботі. Це займе вÑього декілька хвилин. Слідуйте за двома типами вказівників, що відрізнÑÑŽÑ‚ÑŒÑÑ Ð·Ð° кольором."
-
msgid "We detected potential spam in the %{humanized_resource_name}. Please solve the reCAPTCHA to proceed."
msgstr "Ми виÑвили потенційний Ñпам у %{humanized_resource_name}. Будь лаÑка, введіть цей код Ð¿Ñ–Ð´Ñ‚Ð²ÐµÑ€Ð´Ð¶ÐµÐ½Ð½Ñ reCAPTCHA, щоб продовжити."
@@ -25206,6 +25867,9 @@ msgstr ""
msgid "We've found no vulnerabilities"
msgstr "Ми не виÑвили вразливоÑтей"
+msgid "Web Application Firewall"
+msgstr ""
+
msgid "Web IDE"
msgstr "Веб-IDE"
@@ -25266,9 +25930,6 @@ msgstr "ЛаÑкаво проÑимо до GitLab, %{first_name}!"
msgid "Welcome to GitLab.com<br>@%{name}!"
msgstr "ЛаÑкаво проÑимо до GitLab.com<br>@%{name}!"
-msgid "Welcome to the Guided GitLab Tour"
-msgstr "ЛаÑкаво проÑимо на екÑкурÑÑ–ÑŽ по GitLab"
-
msgid "Welcome to the guided GitLab tour"
msgstr ""
@@ -25318,9 +25979,6 @@ msgstr "Коли:"
msgid "While it's rare to have no vulnerabilities, it can happen. In any event, we ask that you please double check your settings to make sure you've set up your dashboard correctly."
msgstr "Хоча й рідко, але можливо не мати вразливоÑтей. Ð’ будь-Ñкому разі, ми проÑимо Ð²Ð°Ñ Ð¿ÐµÑ€ÐµÐ²Ñ–Ñ€Ð¸Ñ‚Ð¸ ваші налаштуваннÑ, щоб впевнитиÑÑ, що ваша панель налаштована правильно."
-msgid "White helpers give contextual information."
-msgstr "Білі вказівники дають контекÑтну інформацію."
-
msgid "Who can be an approver?"
msgstr "Хто може затверджувати?"
@@ -25366,12 +26024,18 @@ msgstr "Вже Ñ–Ñнує Ñторінка з таким шлÑхом Ñ– загÐ
msgid "WikiEmptyIssueMessage|Suggest wiki improvement"
msgstr "Запропонувати Ð¿Ð¾ÐºÑ€Ð°Ñ‰ÐµÐ½Ð½Ñ Ð²Ñ–ÐºÑ–"
+msgid "WikiEmptyIssueMessage|You must be a group member in order to add wiki pages. If you have suggestions for how to improve the wiki for this group, consider opening an issue in the %{issues_link}."
+msgstr ""
+
msgid "WikiEmptyIssueMessage|You must be a project member in order to add wiki pages. If you have suggestions for how to improve the wiki for this project, consider opening an issue in the %{issues_link}."
msgstr "Ви маєте бути учаÑником проекту Ð´Ð»Ñ Ñ‚Ð¾Ð³Ð¾, щоб додавати вікі Ñторінки. Якщо у Ð²Ð°Ñ Ñ” пропозиції ÑтоÑовно Ð¿Ð¾ÐºÑ€Ð°Ñ‰ÐµÐ½Ð½Ñ Ð²Ñ–ÐºÑ– цього проекту, відкрийте задачу в %{issues_link}."
msgid "WikiEmptyIssueMessage|issue tracker"
msgstr "РеєÑÑ‚Ñ€ задач"
+msgid "WikiEmpty|A wiki is where you can store all the details about your group. This can include why you've created it, its principles, how to use it, and so on."
+msgstr ""
+
msgid "WikiEmpty|A wiki is where you can store all the details about your project. This can include why you've created it, its principles, how to use it, and so on."
msgstr "Вікі дозволÑÑ” зберігати інформацію про ваш проект. Ðаприклад причину ÑтвореннÑ, принципи, Ñк його викориÑтовувати Ñ– Ñ‚. д."
@@ -25381,12 +26045,21 @@ msgstr "Створіть вашу першу Ñторінку"
msgid "WikiEmpty|Suggest wiki improvement"
msgstr "Запропонувати Ð¿Ð¾ÐºÑ€Ð°Ñ‰ÐµÐ½Ð½Ñ Ð²Ñ–ÐºÑ–"
+msgid "WikiEmpty|The wiki lets you write documentation for your group"
+msgstr ""
+
msgid "WikiEmpty|The wiki lets you write documentation for your project"
msgstr "Вікі дозволÑÑŽÑ‚ÑŒ пиÑати документацію Ð´Ð»Ñ Ð²Ð°ÑˆÐ¾Ð³Ð¾ проекту"
+msgid "WikiEmpty|This group has no wiki pages"
+msgstr ""
+
msgid "WikiEmpty|This project has no wiki pages"
msgstr "Цей проєкт не має вікі Ñторінок"
+msgid "WikiEmpty|You must be a group member in order to add wiki pages."
+msgstr ""
+
msgid "WikiEmpty|You must be a project member in order to add wiki pages."
msgstr "Ви повинні бути учаÑником проекту Ð´Ð»Ñ Ñ‚Ð¾Ð³Ð¾, щоб додавати вікі Ñторінки."
@@ -25619,16 +26292,16 @@ msgid "You can apply your Trial to your Personal account or create a New Group."
msgstr "Ви можете заÑтоÑувати пробну верÑÑ–ÑŽ до ОÑобиÑтого облікового запиÑу або Ñтворити Ðову Групу."
msgid "You can create a new one or check them in your %{pat_link_start}Personal Access Tokens%{pat_link_end} settings"
-msgstr ""
+msgstr "Ви можете Ñтворити новий або перевірити Ñ—Ñ… в налаштуваннÑÑ… %{pat_link_start}ОÑобиÑтих Токенів ДоÑтупу%{pat_link_end}"
msgid "You can create a new one or check them in your Personal Access Tokens settings %{pat_link}"
-msgstr ""
+msgstr "Ви можете Ñтворити новий або перевірити Ñ—Ñ… в налаштуваннÑÑ… ОÑобиÑтих Токенів ДоÑтупу %{pat_link}"
msgid "You can create new ones at your %{pat_link_start}Personal Access Tokens%{pat_link_end} settings"
-msgstr ""
+msgstr "Ви можете Ñтворити нові в налаштуваннÑÑ… %{pat_link_start}ОÑобиÑтих Токенів ДоÑтупу%{pat_link_end}"
msgid "You can create new ones at your Personal Access Tokens settings %{pat_link}"
-msgstr ""
+msgstr "Ви можете Ñтворити нові в налаштуваннÑÑ… ОÑобиÑтих Токенів ДоÑтупу %{pat_link}"
msgid "You can easily contribute to them by requesting to join these groups."
msgstr "Ви можете легко робити внеÑки до них, запроÑивши доÑтуп до цих груп."
@@ -25663,15 +26336,15 @@ msgstr ""
msgid "You can now export your security dashboard to a CSV report."
msgstr ""
+msgid "You can now manage alert endpoint configuration in the Alerts section on the Operations settings page. Fields on this page have been deprecated."
+msgstr ""
+
msgid "You can now submit a merge request to get this change into the original branch."
msgstr "Ви можете тепер відправити запит на злиттÑ, щоб Ñ†Ñ Ð·Ð¼Ñ–Ð½Ð° попала у вихідну гілку."
msgid "You can now submit a merge request to get this change into the original project."
msgstr "Ви можете тепер відправити запит на злиттÑ, щоб Ñ†Ñ Ð·Ð¼Ñ–Ð½Ð° потрапила у вихідний проект."
-msgid "You can only add files when you are on a branch"
-msgstr "Ви можете додавати файли тільки коли перебуваєте в гілці"
-
msgid "You can only edit files when you are on a branch"
msgstr "Ви можете редагувати файли, лише перебуваючи у ÑкійÑÑŒ гілці"
@@ -25820,7 +26493,7 @@ msgid "You have reached your project limit"
msgstr "Ви доÑÑгли Ñвого ліміту по кількоÑÑ‚Ñ– проектів"
msgid "You have successfully purchased a %{plan} plan subscription for %{seats}. You’ll receive a receipt via email."
-msgstr ""
+msgstr "Ви уÑпішно придбали підпиÑку на план %{plan} на %{seats}. Ви отримаєте чек електронною поштою."
msgid "You haven't added any issues to your project yet"
msgstr "Ви ще не додавали ніÑких задач до ваших проектів"
@@ -25891,7 +26564,7 @@ msgstr "Ви повинні завантажити екÑпортований а
msgid "You need to upload a Google Takeout archive."
msgstr "Вам потрібно завантажити архів Google Takeout."
-msgid "You reached %{usage_in_percent} of %{namespace_name}'s capacity (%{used_storage} of %{storage_limit})"
+msgid "You reached %{usage_in_percent} of %{namespace_name}'s storage capacity (%{used_storage} of %{storage_limit})"
msgstr ""
msgid "You tried to fork %{link_to_the_project} but it failed for the following reason:"
@@ -26042,7 +26715,7 @@ msgid "Your Projects (default)"
msgstr "Ваші проєкти (за замовчуваннÑм)"
msgid "Your Projects' Activity"
-msgstr "ÐктивніÑÑ‚ÑŒ ваших проектів"
+msgstr "ÐктивніÑÑ‚ÑŒ ваших проєктів"
msgid "Your Public Email will be displayed on your public profile."
msgstr "Ваша публічна адреÑа електронної пошти буде відображатиÑÑ Ð² публічному профілі."
@@ -26162,7 +26835,7 @@ msgid "Your new personal access token has been created."
msgstr "Ваш новий перÑональний токен доÑтупу Ñтворено."
msgid "Your new project access token has been created."
-msgstr ""
+msgstr "Ваш новий токен доÑтупу до проекту було Ñтворено."
msgid "Your password isn't required to view this page. If a password or any other personal details are requested, please contact your administrator to report abuse."
msgstr "Ваш пароль не потрібен Ð´Ð»Ñ Ð¿ÐµÑ€ÐµÐ³Ð»Ñду цієї Ñторінки. Якщо вимагаєтьÑÑ Ð²Ð²ÐµÑти пароль або будь-Ñкі інші оÑобиÑÑ‚Ñ– дані, зв’ÑжітьÑÑ Ð·Ñ– Ñвоїм адмініÑтратором, щоб повідомити про порушеннÑ."
@@ -26194,13 +26867,13 @@ msgstr ""
msgid "Your subscription expired!"
msgstr "Термін дії вашої підпиÑки закінчивÑÑ!"
-msgid "Your subscription has been downgraded"
+msgid "Your subscription has been downgraded."
msgstr ""
-msgid "Your subscription will automatically renew in %{remaining_days}"
+msgid "Your subscription will automatically renew in %{remaining_days}."
msgstr ""
-msgid "Your subscription will expire in %{remaining_days}"
+msgid "Your subscription will expire in %{remaining_days}."
msgstr ""
msgid "Zoom meeting added"
@@ -26282,9 +26955,6 @@ msgstr ""
msgid "branch name"
msgstr "ім'Ñ Ð³Ñ–Ð»ÐºÐ¸"
-msgid "build pipeline reference mismatch"
-msgstr ""
-
msgid "by"
msgstr "від"
@@ -26303,6 +26973,9 @@ msgstr "не може бути змінено"
msgid "cannot block others"
msgstr "не може блокувати інших"
+msgid "cannot contain HTML/XML tags, including any word between angle brackets (<,>)."
+msgstr ""
+
msgid "cannot include leading slash or directory traversal."
msgstr "не може починатиÑÑ Ñ–Ð· \"/\" або міÑтити директорії."
@@ -26333,65 +27006,6 @@ msgstr "%{linkStartTag}ДізнатиÑÑ Ð±Ñ–Ð»ÑŒÑˆÐµ про звіти про
msgid "ciReport|%{remainingPackagesCount} more"
msgstr "%{remainingPackagesCount} більше"
-msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability"
-msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities"
-msgstr[0] "%{reportType} %{status} виÑвив %{dismissedCount} відхилену вразливіÑÑ‚ÑŒ"
-msgstr[1] "%{reportType} %{status} виÑвив %{dismissedCount} відхилені вразливоÑÑ‚Ñ–"
-msgstr[2] "%{reportType} %{status} виÑвив %{dismissedCount} відхилених вразливоÑтей"
-msgstr[3] "%{reportType} %{status} виÑвив %{dismissedCount} відхилених вразливоÑтей"
-
-msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability for the source branch only"
-msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities for the source branch only"
-msgstr[0] "%{reportType} %{status} виÑвив %{dismissedCount} відхилену вразливіÑÑ‚ÑŒ лише Ð´Ð»Ñ Ð³Ñ–Ð»ÐºÐ¸-джерела"
-msgstr[1] "%{reportType} %{status} виÑвив %{dismissedCount} відхилені вразливоÑÑ‚Ñ– лише Ð´Ð»Ñ Ð³Ñ–Ð»ÐºÐ¸-джерела"
-msgstr[2] "%{reportType} %{status} виÑвив %{dismissedCount} відхилених вразливоÑтей лише Ð´Ð»Ñ Ð³Ñ–Ð»ÐºÐ¸-джерела"
-msgstr[3] "%{reportType} %{status} виÑвив %{dismissedCount} відхилених вразливоÑтей лише Ð´Ð»Ñ Ð³Ñ–Ð»ÐºÐ¸-джерела"
-
-msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerability"
-msgid_plural "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerabilities"
-msgstr[0] "%{reportType} %{status} виÑвив %{fixedCount} виправлену вразливіÑÑ‚ÑŒ"
-msgstr[1] "%{reportType} %{status} виÑвив %{fixedCount} виправлені вразливоÑÑ‚Ñ–"
-msgstr[2] "%{reportType} %{status} виÑвив %{fixedCount} виправлених вразливоÑтей"
-msgstr[3] "%{reportType} %{status} виÑвив %{fixedCount} виправлених вразливоÑтей"
-
-msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
-msgstr "%{reportType} %{status} виÑвив %{fixedCount} вирішених та %{dismissedCount} відхилених вразливоÑтей"
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new vulnerability"
-msgid_plural "ciReport|%{reportType} %{status} detected %{newCount} new vulnerabilities"
-msgstr[0] "%{reportType} %{status} виÑвив %{newCount} нову вразливіÑÑ‚ÑŒ"
-msgstr[1] "%{reportType} %{status} виÑвив %{newCount} нові вразливоÑÑ‚Ñ–"
-msgstr[2] "%{reportType} %{status} виÑвив %{newCount} нових вразливоÑтей"
-msgstr[3] "%{reportType} %{status} виÑвив %{newCount} нових вразливоÑтей"
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
-msgstr "%{reportType} %{status} виÑвив %{newCount} нову, %{fixedCount} вирішених та %{dismissedCount} відхилених вразливоÑтей"
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities"
-msgstr "%{reportType} %{status} виÑвив %{newCount} нових та %{dismissedCount} відхилених вразливоÑтей"
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities for the source branch only"
-msgstr "%{reportType} %{status} виÑвив %{newCount} новий та %{dismissedCount} відхилених вразливоÑтей лише Ð´Ð»Ñ Ð³Ñ–Ð»ÐºÐ¸-джерела"
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{fixedCount} fixed vulnerabilities"
-msgstr "%{reportType} %{status} виÑвив %{newCount} нових та %{fixedCount} виправлених вразливоÑтей"
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} vulnerability for the source branch only"
-msgid_plural "ciReport|%{reportType} %{status} detected %{newCount} vulnerabilities for the source branch only"
-msgstr[0] "%{reportType} %{status} виÑвив %{newCount} вразливіÑÑ‚ÑŒ лише Ð´Ð»Ñ Ð³Ñ–Ð»ÐºÐ¸-джерела"
-msgstr[1] "%{reportType} %{status} виÑвив %{newCount} вразливоÑÑ‚Ñ– лише Ð´Ð»Ñ Ð³Ñ–Ð»ÐºÐ¸-джерела"
-msgstr[2] "%{reportType} %{status} виÑвив %{newCount} вразливоÑтей лише Ð´Ð»Ñ Ð³Ñ–Ð»ÐºÐ¸-джерела"
-msgstr[3] "%{reportType} %{status} виÑвив %{newCount} вразливоÑтей лише Ð´Ð»Ñ Ð³Ñ–Ð»ÐºÐ¸-джерела"
-
-msgid "ciReport|%{reportType} %{status} detected no new vulnerabilities"
-msgstr "%{reportType} %{status} не виÑвив нових вразливоÑтей"
-
-msgid "ciReport|%{reportType} %{status} detected no vulnerabilities"
-msgstr "%{reportType}%{status} не виÑвив вразливоÑтей"
-
-msgid "ciReport|%{reportType} %{status} detected no vulnerabilities for the source branch only"
-msgstr "%{reportType} %{status} не виÑвив нових вразливоÑтей лише Ð´Ð»Ñ Ð³Ñ–Ð»ÐºÐ¸-джерела"
-
msgid "ciReport|%{reportType} is loading"
msgstr "%{reportType} завантажуєтьÑÑ"
@@ -26410,8 +27024,8 @@ msgstr "(завантажуєтьÑÑ, помилки під Ñ‡Ð°Ñ Ð·Ð°Ð²Ð°Ð½Ñ‚
msgid "ciReport|All projects"
msgstr "Ð’ÑÑ– проекти"
-msgid "ciReport|All report types"
-msgstr "Ð’ÑÑ– типи звітів"
+msgid "ciReport|All scanner types"
+msgstr ""
msgid "ciReport|All severities"
msgstr "Ð’ÑÑ– рівні"
@@ -26434,6 +27048,9 @@ msgstr "Ð¡ÐºÐ°Ð½ÑƒÐ²Ð°Ð½Ð½Ñ ÐºÐ¾Ð½Ñ‚ÐµÐ¹Ð½ÐµÑ€Ð°"
msgid "ciReport|Container scanning detects known vulnerabilities in your docker images."
msgstr "Ð¡ÐºÐ°Ð½ÑƒÐ²Ð°Ð½Ð½Ñ ÐºÐ¾Ð½Ñ‚ÐµÐ¹Ð½ÐµÑ€Ñ–Ð² виÑвлÑÑ” відомі вразливоÑÑ‚Ñ– у ваших Docker образах."
+msgid "ciReport|Coverage Fuzzing"
+msgstr ""
+
msgid "ciReport|Create a merge request to implement this solution, or download and apply the patch manually."
msgstr "Створити запит на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ð´Ð»Ñ Ñ€ÐµÐ°Ð»Ñ–Ð·Ð°Ñ†Ñ–Ñ— цього Ñ€Ñ–ÑˆÐµÐ½Ð½Ñ Ð°Ð±Ð¾ завантажити Ñ– заÑтоÑувати патч вручну."
@@ -26553,7 +27170,7 @@ msgid "ciReport|View full report"
msgstr "ПереглÑнути повний звіт"
msgid "closed issue"
-msgstr ""
+msgstr "закрита задача"
msgid "comment"
msgstr "коментар"
@@ -26594,6 +27211,9 @@ msgstr ""
msgid "customize"
msgstr "налаштувати"
+msgid "data"
+msgstr ""
+
msgid "date must not be after 9999-12-31"
msgstr "дата не повинна бути пізніше 9999-12-31"
@@ -26668,6 +27288,9 @@ msgstr "елементи не можуть бути порожніми"
msgid "entries cannot contain HTML tags"
msgstr "елементи не можуть міÑтити тегів HTML"
+msgid "epic"
+msgstr ""
+
msgid "error"
msgstr "помилка"
@@ -26689,9 +27312,6 @@ msgstr ""
msgid "expires on %{timebox_due_date}"
msgstr ""
-msgid "external_url"
-msgstr ""
-
msgid "failed"
msgstr "невдало"
@@ -26729,9 +27349,6 @@ msgstr "зробити форк цього проєкту"
msgid "from"
msgstr "від"
-msgid "geo_node_name"
-msgstr ""
-
msgid "group"
msgstr "група"
@@ -26753,9 +27370,6 @@ msgstr "тут"
msgid "https://your-bitbucket-server"
msgstr "https://your-bitbucket-server"
-msgid "image"
-msgstr ""
-
msgid "image diff"
msgstr "Ñ€Ñ–Ð·Ð½Ð¸Ñ†Ñ Ð·Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð½Ñ"
@@ -26866,7 +27480,7 @@ msgid "it is stored in LFS"
msgstr "зберігаєтьÑÑ Ð² LFS"
msgid "it is too large"
-msgstr ""
+msgstr "те, що він занадто великий"
msgid "jigsaw is not defined"
msgstr "jigsaw не визначено"
@@ -26901,6 +27515,9 @@ msgstr "доÑÑгнуто ліміт %{project_limit}"
msgid "load it anyway"
msgstr ""
+msgid "loading"
+msgstr ""
+
msgid "locked by %{path_lock_user_name} %{created_at}"
msgstr "заблоковано %{path_lock_user_name} %{created_at}"
@@ -27031,9 +27648,6 @@ msgstr "Видалити гілку-джерело"
msgid "mrWidget|Deployment statistics are not available currently"
msgstr "СтатиÑтика Ñ€Ð¾Ð·Ð³Ð¾Ñ€Ñ‚Ð°Ð½Ð½Ñ Ð½Ð°Ñ€Ð°Ð·Ñ– недоÑтупна"
-msgid "mrWidget|Detect issues before deployment with a CI pipeline that continuously tests your code. We created a quick guide that will show you how to create one. Make your code more secure and more robust in just a minute."
-msgstr ""
-
msgid "mrWidget|Did not close"
msgstr "Ðе закрив"
@@ -27196,6 +27810,9 @@ msgstr "Цей проект заархівований, доÑтуп до зап
msgid "mrWidget|To approve this merge request, please enter your password. This project requires all approvals to be authenticated."
msgstr "Ð”Ð»Ñ Ð·Ð°Ñ‚Ð²ÐµÑ€Ð´Ð¶ÐµÐ½Ð½Ñ Ñ†ÑŒÐ¾Ð³Ð¾ запиту на злиттÑ, введіть ваш пароль. Цей проект вимагає Ð¿Ñ€Ð¾Ñ…Ð¾Ð´Ð¶ÐµÐ½Ð½Ñ Ð°Ð²Ñ‚ÐµÐ½Ñ‚Ð¸Ñ„Ñ–ÐºÐ°Ñ†Ñ–Ñ— Ð´Ð»Ñ Ð²ÑÑ–Ñ… затверджень."
+msgid "mrWidget|Use %{linkStart}CI pipelines to test your code%{linkEnd}, simply add a GitLab CI configuration file to your project. It only takes a minute to make your code more secure and robust."
+msgstr ""
+
msgid "mrWidget|When this merge request is ready, remove the WIP: prefix from the title to allow it to be merged"
msgstr "Коли цей запит на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ð±ÑƒÐ´Ðµ готовий, видаліть Ð¿Ñ€ÐµÑ„Ñ–ÐºÑ \"WIP:\" із заголовку, щоб можна було виконати злиттÑ"
@@ -27232,6 +27849,9 @@ msgstr "Ñтворити ланцюжок змін піÑÐ»Ñ ÑƒÑпішного
msgid "must be greater than start date"
msgstr "повинна бути пізніша за дату початку"
+msgid "must contain only valid frameworks"
+msgstr ""
+
msgid "my-awesome-group"
msgstr ""
@@ -27244,6 +27864,9 @@ msgstr ""
msgid "needs to be between 10 minutes and 1 month"
msgstr "має бути між 10 хвилинами та 1 міÑÑцем"
+msgid "never"
+msgstr ""
+
msgid "never expires"
msgstr ""
@@ -27271,6 +27894,9 @@ msgstr "ÐŸÐ¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ ÐµÐ»ÐµÐºÑ‚Ñ€Ð¾Ð½Ð½Ð¾ÑŽ поштою"
msgid "nounSeries|%{firstItem} and %{lastItem}"
msgstr "%{firstItem} Ñ– %{lastItem}"
+msgid "nounSeries|%{item}"
+msgstr ""
+
msgid "nounSeries|%{item}, %{nextItem}"
msgstr "%{item}, %{nextItem}"
@@ -27417,6 +28043,9 @@ msgstr ""
msgid "revised"
msgstr ""
+msgid "satisfied"
+msgstr ""
+
msgid "score"
msgstr "результат"
@@ -27447,6 +28076,9 @@ msgstr "ВідÑутній"
msgid "severity|Unknown"
msgstr "Ðевідомий"
+msgid "should be an array of %{object_name} objects"
+msgstr ""
+
msgid "should be greater than or equal to %{access} inherited membership from group %{group_name}"
msgstr "має бути вищим або рівним %{access}, уÑпадкованому членÑтву з групи %{group_name}"
@@ -27501,10 +28133,13 @@ msgstr ""
msgid "suggestPipeline|2/2: Commit your changes"
msgstr ""
-msgid "suggestPipeline|Commit the changes and your pipeline will automatically run for the first time."
+msgid "suggestPipeline|Choose %{boldStart}Code Quality%{boldEnd} to add a pipeline that tests the quality of your code."
+msgstr ""
+
+msgid "suggestPipeline|The template is ready! You can now commit it to create your first pipeline."
msgstr ""
-msgid "suggestPipeline|We recommend the %{boldStart}Code Quality%{boldEnd} template, which will add a report widget to your Merge Requests. This way you’ll learn about code quality degradations much sooner. %{footerStart} Goodbye technical debt! %{footerEnd}"
+msgid "suggestPipeline|We’re adding a GitLab CI configuration file to add a pipeline to the project. You could create it manually, but we recommend that you start with a GitLab template that works out of the box."
msgstr ""
msgid "syntax is correct"
@@ -27550,7 +28185,7 @@ msgid "updated %{time_ago}"
msgstr "оновлено %{time_ago}"
msgid "uploaded"
-msgstr ""
+msgstr "завантажено"
msgid "uploads"
msgstr ""
@@ -27564,6 +28199,9 @@ msgstr "ім'Ñ ÐºÐ¾Ñ€Ð¸Ñтувача"
msgid "uses Kubernetes clusters to deploy your code!"
msgstr "викориÑтовує клаÑтери Kubernetes Ð´Ð»Ñ Ñ€Ð¾Ð·Ð³Ð¾Ñ€Ñ‚Ð°Ð½Ð½Ñ ÐºÐ¾Ð´Ñƒ!"
+msgid "v%{version} published %{timeAgo}"
+msgstr ""
+
msgid "verify ownership"
msgstr "перевірити право влаÑноÑÑ‚Ñ–"
diff --git a/locale/ur_PK/gitlab.po b/locale/ur_PK/gitlab.po
index 2af74f66d3e..273553c6b5c 100644
--- a/locale/ur_PK/gitlab.po
+++ b/locale/ur_PK/gitlab.po
@@ -2,7 +2,7 @@ msgid ""
msgstr ""
"Project-Id-Version: gitlab-ee\n"
"Report-Msgid-Bugs-To: \n"
-"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Last-Translator: \n"
"Language-Team: Urdu (Pakistan)\n"
"Language: ur_PK\n"
"MIME-Version: 1.0\n"
@@ -10,9 +10,11 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Crowdin-Project: gitlab-ee\n"
+"X-Crowdin-Project-ID: 288872\n"
"X-Crowdin-Language: ur-PK\n"
"X-Crowdin-File: /master/locale/gitlab.pot\n"
-"PO-Revision-Date: 2020-06-08 15:08\n"
+"X-Crowdin-File-ID: 6\n"
+"PO-Revision-Date: 2020-07-02 01:19\n"
msgid " %{start} to %{end}"
msgstr ""
@@ -197,6 +199,11 @@ msgid_plural "%d metrics"
msgstr[0] ""
msgstr[1] ""
+msgid "%d milestone"
+msgid_plural "%d milestones"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "%d minute"
msgid_plural "%d minutes"
msgstr[0] ""
@@ -242,11 +249,6 @@ msgid_plural "%d unresolved threads"
msgstr[0] ""
msgstr[1] ""
-msgid "%d url scanned"
-msgid_plural "%d urls scanned"
-msgstr[0] ""
-msgstr[1] ""
-
msgid "%d vulnerability"
msgid_plural "%d vulnerabilities"
msgstr[0] ""
@@ -324,9 +326,15 @@ msgstr[1] ""
msgid "%{count} related %{pluralized_subject}: %{links}"
msgstr ""
+msgid "%{dashboard_path} could not be found."
+msgstr ""
+
msgid "%{days} days until tags are automatically removed"
msgstr ""
+msgid "%{deployLinkStart}Use a template to deploy to ECS%{deployLinkEnd}, or use a docker image to %{commandsLinkStart}run AWS commands in GitLab CI/CD%{commandsLinkEnd}."
+msgstr ""
+
msgid "%{description}- Sentry event: %{errorUrl}- First seen: %{firstSeen}- Last seen: %{lastSeen} %{countLabel}: %{count}%{userCountLabel}: %{userCount}"
msgstr ""
@@ -369,12 +377,18 @@ msgstr ""
msgid "%{host} sign-in from new location"
msgstr ""
-msgid "%{icon}You are about to add %{usersTag} people to the discussion. Proceed with caution."
+msgid "%{icon}You are about to add %{usersTag} people to the discussion. They will all receive a notification."
+msgstr ""
+
+msgid "%{integrations_link_start}Integrations%{link_end} enable you to make third-party applications part of your GitLab workflow. If the available integrations don't meet your needs, consider using a %{webhooks_link_start}webhook%{link_end}."
msgstr ""
msgid "%{issuableType} will be removed! Are you sure?"
msgstr ""
+msgid "%{issuesCount} issues in this group"
+msgstr ""
+
msgid "%{issuesSize} issues"
msgstr ""
@@ -384,7 +398,7 @@ msgstr ""
msgid "%{labelStart}Class:%{labelEnd} %{class}"
msgstr ""
-msgid "%{labelStart}Confidence:%{labelEnd} %{confidence}"
+msgid "%{labelStart}Evidence:%{labelEnd} %{evidence}"
msgstr ""
msgid "%{labelStart}File:%{labelEnd} %{file}"
@@ -402,6 +416,9 @@ msgstr ""
msgid "%{labelStart}Report Type:%{labelEnd} %{reportType}"
msgstr ""
+msgid "%{labelStart}Scanner:%{labelEnd} %{scanner}"
+msgstr ""
+
msgid "%{labelStart}Severity:%{labelEnd} %{severity}"
msgstr ""
@@ -426,9 +443,6 @@ msgstr ""
msgid "%{link_start}Read more%{link_end} about role permissions"
msgstr ""
-msgid "%{link} can be used for binding events when something is happening within the project."
-msgstr ""
-
msgid "%{listToShow}, and %{awardsListLength} more."
msgstr ""
@@ -462,6 +476,9 @@ msgstr ""
msgid "%{name} found %{resultsString}"
msgstr ""
+msgid "%{name} is already being used for another emoji"
+msgstr ""
+
msgid "%{name} is scheduled for %{action}"
msgstr ""
@@ -509,6 +526,9 @@ msgstr ""
msgid "%{primary} (%{secondary})"
msgstr ""
+msgid "%{ref} cannot be added: %{error}"
+msgstr ""
+
msgid "%{releases} release"
msgid_plural "%{releases} releases"
msgstr[0] ""
@@ -517,13 +537,37 @@ msgstr[1] ""
msgid "%{remaining_approvals} left"
msgstr ""
-msgid "%{retryButtonStart}Try again%{retryButtonEnd} or %{newFileButtonStart}attach a new file%{newFileButtonEnd}"
+msgid "%{reportType} %{status} detected %{critical} critical and %{high} high severity vulnerabilities out of %{total}."
+msgstr ""
+
+msgid "%{reportType} %{status} detected %{critical} critical and %{high} high severity vulnerabilities."
+msgstr ""
+
+msgid "%{reportType} %{status} detected %{critical} critical severity vulnerabilities out of %{total}."
+msgstr ""
+
+msgid "%{reportType} %{status} detected %{critical} critical severity vulnerability."
+msgid_plural "%{reportType} %{status} detected %{critical} critical severity vulnerabilities."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{reportType} %{status} detected %{high} high severity vulnerabilities out of %{total}."
msgstr ""
-msgid "%{screenreaderOnlyStart}Keyboard shorcuts%{screenreaderOnlyEnd} Disabled"
+msgid "%{reportType} %{status} detected %{high} high severity vulnerability."
+msgid_plural "%{reportType} %{status} detected %{high} high severity vulnerabilities."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{reportType} %{status} detected %{other} vulnerability."
+msgid_plural "%{reportType} %{status} detected %{other} vulnerabilities."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{reportType} %{status} detected no new vulnerabilities."
msgstr ""
-msgid "%{screenreaderOnlyStart}Keyboard shorcuts%{screenreaderOnlyEnd} Enabled"
+msgid "%{retryButtonStart}Try again%{retryButtonEnd} or %{newFileButtonStart}attach a new file%{newFileButtonEnd}"
msgstr ""
msgid "%{service_title} %{message}."
@@ -620,6 +664,12 @@ msgstr ""
msgid "%{token}..."
msgstr ""
+msgid "%{totalCpu} (%{freeSpacePercentage}%{percentSymbol} free)"
+msgstr ""
+
+msgid "%{totalMemory} (%{freeSpacePercentage}%{percentSymbol} free)"
+msgstr ""
+
msgid "%{totalWeight} total weight"
msgstr ""
@@ -650,6 +700,12 @@ msgstr ""
msgid "%{verb} %{time_spent_value} spent time."
msgstr ""
+msgid "%{webhooks_link_start}%{webhook_type}%{link_end} enable you to send notifications to web applications in response to events in a group or project."
+msgstr ""
+
+msgid "%{webhooks_link_start}%{webhook_type}%{link_end} enable you to send notifications to web applications in response to events in a group or project. We recommend using an %{integrations_link_start}integration%{link_end} in preference to a webhook."
+msgstr ""
+
msgid "'%{level}' is not a valid visibility level"
msgstr ""
@@ -676,9 +732,6 @@ msgstr ""
msgid "(No changes)"
msgstr ""
-msgid "(Show all)"
-msgstr ""
-
msgid "(check progress)"
msgstr ""
@@ -745,6 +798,9 @@ msgstr ""
msgid "0 for unlimited"
msgstr ""
+msgid "0 for unlimited, only effective with remote storage enabled."
+msgstr ""
+
msgid "1 %{type} addition"
msgid_plural "%{count} %{type} additions"
msgstr[0] ""
@@ -892,6 +948,12 @@ msgstr ""
msgid "<code>\"johnsmith@example.com\": \"johnsmith@example.com\"</code> will add \"By <a href=\"#\">johnsmith@example.com</a>\" to all issues and comments originally created by johnsmith@example.com. By default, the email address or username is masked to ensure the user's privacy. Use this option if you want to show the full email address."
msgstr ""
+msgid "<code>Masked</code> to prevent the values from being displayed in job logs (must match certain regexp requirements)."
+msgstr ""
+
+msgid "<code>Protected</code> to expose them to protected branches or tags only."
+msgstr ""
+
msgid "<namespace / project>"
msgstr ""
@@ -919,9 +981,6 @@ msgstr ""
msgid "A .NET Core console application template, customizable for any .NET Core project"
msgstr ""
-msgid "A DAG must have two dependent jobs to be visualized on this tab."
-msgstr ""
-
msgid "A GitBook site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
msgstr ""
@@ -952,6 +1011,12 @@ msgstr ""
msgid "A deleted user"
msgstr ""
+msgid "A file has been changed."
+msgstr ""
+
+msgid "A file was not found."
+msgstr ""
+
msgid "A file with '%{file_name}' already exists in %{branch} branch"
msgstr ""
@@ -1018,7 +1083,7 @@ msgstr ""
msgid "A subscription will trigger a new pipeline on the default branch of this project when a pipeline successfully completes for a new tag on the %{default_branch_docs} of the subscribed project."
msgstr ""
-msgid "A terraform report was generated in your pipelines."
+msgid "A suggestion is not applicable."
msgstr ""
msgid "A user with write access to the source branch selected this option"
@@ -1189,6 +1254,9 @@ msgstr ""
msgid "Action to take when receiving an alert."
msgstr ""
+msgid "Actions"
+msgstr ""
+
msgid "Activate"
msgstr ""
@@ -1245,6 +1313,9 @@ msgstr ""
msgid "Add LICENSE"
msgstr ""
+msgid "Add New Node"
+msgstr ""
+
msgid "Add README"
msgstr ""
@@ -1320,6 +1391,9 @@ msgstr ""
msgid "Add approval rule"
msgstr ""
+msgid "Add approvers"
+msgstr ""
+
msgid "Add bold text"
msgstr ""
@@ -1377,6 +1451,9 @@ msgstr ""
msgid "Add strikethrough text"
msgstr ""
+msgid "Add suggestion to batch"
+msgstr ""
+
msgid "Add system hook"
msgstr ""
@@ -1476,6 +1553,9 @@ msgstr ""
msgid "Admin Note"
msgstr ""
+msgid "Admin Notifications"
+msgstr ""
+
msgid "Admin Overview"
msgstr ""
@@ -1587,6 +1667,9 @@ msgstr ""
msgid "AdminSettings|Integrations configured here will automatically apply to all projects on this instance."
msgstr ""
+msgid "AdminSettings|Moved to integrations"
+msgstr ""
+
msgid "AdminSettings|No required pipeline"
msgstr ""
@@ -1817,12 +1900,18 @@ msgid_plural "Alerts"
msgstr[0] ""
msgstr[1] ""
+msgid "AlertManagement| assign yourself"
+msgstr ""
+
msgid "AlertManagement|Acknowledged"
msgstr ""
msgid "AlertManagement|Alert"
msgstr ""
+msgid "AlertManagement|Alert assignee(s): %{assignees}"
+msgstr ""
+
msgid "AlertManagement|Alert detail"
msgstr ""
@@ -1838,9 +1927,15 @@ msgstr ""
msgid "AlertManagement|All alerts"
msgstr ""
+msgid "AlertManagement|Assign To"
+msgstr ""
+
msgid "AlertManagement|Assign status"
msgstr ""
+msgid "AlertManagement|Assignee"
+msgstr ""
+
msgid "AlertManagement|Assignees"
msgstr ""
@@ -1859,9 +1954,6 @@ msgstr ""
msgid "AlertManagement|Edit"
msgstr ""
-msgid "AlertManagement|End time"
-msgstr ""
-
msgid "AlertManagement|Events"
msgstr ""
@@ -1880,10 +1972,13 @@ msgstr ""
msgid "AlertManagement|More information"
msgstr ""
+msgid "AlertManagement|No Matching Results"
+msgstr ""
+
msgid "AlertManagement|No alert data to display."
msgstr ""
-msgid "AlertManagement|No alerts available to display. If you think you're seeing this message in error, refresh the page."
+msgid "AlertManagement|No alerts available to display. See %{linkStart}enabling alert management%{linkEnd} for more information on adding alerts to the list."
msgstr ""
msgid "AlertManagement|No alerts to display."
@@ -1892,6 +1987,9 @@ msgstr ""
msgid "AlertManagement|None"
msgstr ""
+msgid "AlertManagement|None -"
+msgstr ""
+
msgid "AlertManagement|Open"
msgstr ""
@@ -1928,9 +2026,18 @@ msgstr ""
msgid "AlertManagement|There was an error displaying the alerts. Confirm your endpoint's configuration details to ensure alerts appear."
msgstr ""
+msgid "AlertManagement|There was an error while updating the assignee(s) list. Please try again."
+msgstr ""
+
+msgid "AlertManagement|There was an error while updating the assignee(s) of the alert. Please try again."
+msgstr ""
+
msgid "AlertManagement|There was an error while updating the status of the alert. Please try again."
msgstr ""
+msgid "AlertManagement|This assignee cannot be assigned to this alert."
+msgstr ""
+
msgid "AlertManagement|Tool"
msgstr ""
@@ -2078,6 +2185,9 @@ msgstr ""
msgid "Allow users to request access (if visibility is public or internal)"
msgstr ""
+msgid "Allowed Geo IP"
+msgstr ""
+
msgid "Allowed email domain restriction only permitted for top-level groups"
msgstr ""
@@ -2111,6 +2221,9 @@ msgstr ""
msgid "Amazon Web Services"
msgstr ""
+msgid "Amazon Web Services Logo"
+msgstr ""
+
msgid "Amazon authentication is not %{link_start}correctly configured%{link_end}. Ask your GitLab administrator if you want to use this service."
msgstr ""
@@ -2168,10 +2281,16 @@ msgstr ""
msgid "An error occurred when updating the issue weight"
msgstr ""
+msgid "An error occurred while acknowledging the notification. Refresh the page and try again."
+msgstr ""
+
+msgid "An error occurred while adding approvers"
+msgstr ""
+
msgid "An error occurred while adding formatted title for epic"
msgstr ""
-msgid "An error occurred while checking group path"
+msgid "An error occurred while checking group path. Please refresh and try again."
msgstr ""
msgid "An error occurred while committing your changes."
@@ -2315,7 +2434,7 @@ msgstr ""
msgid "An error occurred while loading milestones"
msgstr ""
-msgid "An error occurred while loading terraform report"
+msgid "An error occurred while loading project creation UI"
msgstr ""
msgid "An error occurred while loading the data. Please try again."
@@ -2369,6 +2488,9 @@ msgstr ""
msgid "An error occurred while rendering preview broadcast message"
msgstr ""
+msgid "An error occurred while rendering the editor"
+msgstr ""
+
msgid "An error occurred while reordering issues."
msgstr ""
@@ -2465,7 +2587,7 @@ msgstr ""
msgid "Analyze your dependencies for known vulnerabilities."
msgstr ""
-msgid "Analyze your source code and git history for secrets"
+msgid "Analyze your source code and git history for secrets."
msgstr ""
msgid "Analyze your source code for known vulnerabilities."
@@ -2579,12 +2701,18 @@ msgstr ""
msgid "Apply suggestion"
msgstr ""
+msgid "Apply suggestions"
+msgstr ""
+
msgid "Apply template"
msgstr ""
msgid "Apply this approval rule to any branch or a specific protected branch."
msgstr ""
+msgid "Applying"
+msgstr ""
+
msgid "Applying a template will replace the existing issue description. Any changes you have made will be lost."
msgstr ""
@@ -2597,12 +2725,18 @@ msgstr ""
msgid "Applying multiple commands"
msgstr ""
-msgid "Applying suggestion"
+msgid "Applying suggestion..."
+msgstr ""
+
+msgid "Applying suggestions..."
msgstr ""
msgid "Approval rules"
msgstr ""
+msgid "Approval rules reset to project defaults"
+msgstr ""
+
msgid "ApprovalRuleRemove|%d member"
msgid_plural "ApprovalRuleRemove|%d members"
msgstr[0] ""
@@ -2656,6 +2790,9 @@ msgstr ""
msgid "Approved"
msgstr ""
+msgid "Approved MRs"
+msgstr ""
+
msgid "Approved by: "
msgstr ""
@@ -2869,6 +3006,9 @@ msgstr ""
msgid "Assign"
msgstr ""
+msgid "Assign Iteration"
+msgstr ""
+
msgid "Assign custom color like #FF0000"
msgstr ""
@@ -2887,6 +3027,9 @@ msgstr ""
msgid "Assign to"
msgstr ""
+msgid "Assign to commenting user"
+msgstr ""
+
msgid "Assign yourself to these issues"
msgstr ""
@@ -2913,6 +3056,9 @@ msgid_plural "%d Assignees"
msgstr[0] ""
msgstr[1] ""
+msgid "Assignee has no permissions"
+msgstr ""
+
msgid "Assignee lists not available with your current license"
msgstr ""
@@ -2922,6 +3068,9 @@ msgstr ""
msgid "Assignee(s)"
msgstr ""
+msgid "Assignees"
+msgstr ""
+
msgid "Assigns %{assignee_users_sentence}."
msgstr ""
@@ -2957,6 +3106,9 @@ msgstr ""
msgid "Audit Events is a way to keep track of important events that happened in GitLab."
msgstr ""
+msgid "Audit Log"
+msgstr ""
+
msgid "AuditEvents|(removed)"
msgstr ""
@@ -2993,6 +3145,9 @@ msgstr ""
msgid "AuditLogs|IP Address"
msgstr ""
+msgid "AuditLogs|Member Events"
+msgstr ""
+
msgid "AuditLogs|No matching %{type} found."
msgstr ""
@@ -3275,9 +3430,6 @@ msgstr ""
msgid "Badges|e.g. %{exampleUrl}"
msgstr ""
-msgid "Badge|New"
-msgstr ""
-
msgid "Balsamiq file could not be loaded."
msgstr ""
@@ -3326,6 +3478,12 @@ msgstr ""
msgid "Below you will find all the groups that are public."
msgstr ""
+msgid "Beta"
+msgstr ""
+
+msgid "Bi-weekly code coverage"
+msgstr ""
+
msgid "Billing"
msgstr ""
@@ -3386,9 +3544,15 @@ msgstr ""
msgid "Bitbucket Server Import"
msgstr ""
+msgid "Bitbucket Server import"
+msgstr ""
+
msgid "Bitbucket import"
msgstr ""
+msgid "Blame"
+msgstr ""
+
msgid "Blocked"
msgstr ""
@@ -3401,9 +3565,6 @@ msgstr ""
msgid "Blog"
msgstr ""
-msgid "Blue helpers indicate an action to be taken."
-msgstr ""
-
msgid "Board name"
msgstr ""
@@ -3602,6 +3763,9 @@ msgstr ""
msgid "Broadcast Message was successfully updated."
msgstr ""
+msgid "Broadcast Messages"
+msgstr ""
+
msgid "Browse Directory"
msgstr ""
@@ -3656,6 +3820,9 @@ msgstr ""
msgid "By %{user_name}"
msgstr ""
+msgid "By URL"
+msgstr ""
+
msgid "By default GitLab sends emails in HTML and plain text formats so mail clients can choose what format to use. Disable this option if you only want to send emails in plain text format."
msgstr ""
@@ -3761,6 +3928,9 @@ msgstr ""
msgid "Can override approvers and approvals required per merge request"
msgstr ""
+msgid "Can't apply as this line has changed or the suggestion already matches its content."
+msgstr ""
+
msgid "Can't create snippet: %{err}"
msgstr ""
@@ -3947,9 +4117,6 @@ msgstr ""
msgid "Changes are still tracked. Useful for cluster/index migrations."
msgstr ""
-msgid "Changes are unknown"
-msgstr ""
-
msgid "Changes suppressed. Click to show."
msgstr ""
@@ -4256,15 +4423,9 @@ msgstr ""
msgid "Choose what content you want to see on a group’s overview page"
msgstr ""
-msgid "Choose which groups you wish to synchronize to this secondary node"
-msgstr ""
-
msgid "Choose which repositories you want to connect and run CI/CD pipelines."
msgstr ""
-msgid "Choose which shards you wish to synchronize to this secondary node"
-msgstr ""
-
msgid "Choose your framework"
msgstr ""
@@ -4562,9 +4723,15 @@ msgstr ""
msgid "Cluster does not exist"
msgstr ""
+msgid "Cluster is required for Stages::ClusterEndpointInserter"
+msgstr ""
+
msgid "Cluster level"
msgstr ""
+msgid "Cluster type must be specificed for Stages::ClusterEndpointInserter"
+msgstr ""
+
msgid "ClusterIntegration| %{custom_domain_start}More information%{custom_domain_end}."
msgstr ""
@@ -4676,7 +4843,7 @@ msgstr ""
msgid "ClusterIntegration|Cert-Manager"
msgstr ""
-msgid "ClusterIntegration|Cert-Manager is a native Kubernetes certificate management controller that helps with issuing certificates. Installing Cert-Manager on your cluster will issue a certificate by %{letsEncrypt} and ensure that certificates are valid and up-to-date."
+msgid "ClusterIntegration|Cert-Manager is a native Kubernetes certificate management controller that helps with issuing certificates. Installing Cert-Manager on your cluster will issue a certificate by %{linkStart}Let's Encrypt%{linkEnd} and ensure that certificates are valid and up-to-date."
msgstr ""
msgid "ClusterIntegration|Certificate Authority bundle (PEM format)"
@@ -4703,9 +4870,6 @@ msgstr ""
msgid "ClusterIntegration|Clear the local cache of namespace and service accounts. This is necessary if your integration has become out of sync. The cache is repopulated during the next CI job that requires namespace and service accounts."
msgstr ""
-msgid "ClusterIntegration|Cloud Run"
-msgstr ""
-
msgid "ClusterIntegration|Cluster being created"
msgstr ""
@@ -4790,7 +4954,7 @@ msgstr ""
msgid "ClusterIntegration|Crossplane"
msgstr ""
-msgid "ClusterIntegration|Crossplane enables declarative provisioning of managed services from your cloud of choice using %{codeStart}kubectl%{codeEnd} or %{gitlabIntegrationLink}. Crossplane runs inside your Kubernetes cluster and supports secure connectivity and secrets management between app containers and the cloud services they depend on."
+msgid "ClusterIntegration|Crossplane enables declarative provisioning of managed services from your cloud of choice using %{codeStart}kubectl%{codeEnd} or %{linkStart}GitLab Integration%{linkEnd}. Crossplane runs inside your Kubernetes cluster and supports secure connectivity and secrets management between app containers and the cloud services they depend on."
msgstr ""
msgid "ClusterIntegration|Deletes all GitLab resources attached to this cluster during removal"
@@ -4874,9 +5038,6 @@ msgstr ""
msgid "ClusterIntegration|GitLab-managed cluster"
msgstr ""
-msgid "ClusterIntegration|Gitlab Integration"
-msgstr ""
-
msgid "ClusterIntegration|Global default"
msgstr ""
@@ -4922,7 +5083,7 @@ msgstr ""
msgid "ClusterIntegration|Ingress gives you a way to route requests to services based on the request host or path, centralizing a number of services into a single entrypoint."
msgstr ""
-msgid "ClusterIntegration|Installing Ingress may incur additional costs. Learn more about %{pricingLink}."
+msgid "ClusterIntegration|Installing Ingress may incur additional costs. Learn more about %{linkStart}pricing%{linkEnd}."
msgstr ""
msgid "ClusterIntegration|Instance cluster"
@@ -4937,7 +5098,7 @@ msgstr ""
msgid "ClusterIntegration|Issuer Email"
msgstr ""
-msgid "ClusterIntegration|Issuers represent a certificate authority. You must provide an email address for your Issuer. "
+msgid "ClusterIntegration|Issuers represent a certificate authority. You must provide an email address for your Issuer."
msgstr ""
msgid "ClusterIntegration|Jupyter Hostname"
@@ -5009,9 +5170,6 @@ msgstr ""
msgid "ClusterIntegration|Learn more about instance Kubernetes clusters"
msgstr ""
-msgid "ClusterIntegration|Let's Encrypt"
-msgstr ""
-
msgid "ClusterIntegration|Loading IAM Roles"
msgstr ""
@@ -5123,7 +5281,7 @@ msgstr ""
msgid "ClusterIntegration|Prometheus"
msgstr ""
-msgid "ClusterIntegration|Prometheus is an open-source monitoring system with %{gitlabIntegrationLink} to monitor deployed applications."
+msgid "ClusterIntegration|Prometheus is an open-source monitoring system with %{linkStart}GitLab Integration%{linkEnd} to monitor deployed applications."
msgstr ""
msgid "ClusterIntegration|Provider details"
@@ -5279,10 +5437,10 @@ msgstr ""
msgid "ClusterIntegration|Select zone to choose machine type"
msgstr ""
-msgid "ClusterIntegration|Send Cilium Logs"
+msgid "ClusterIntegration|Send Container Network Policies Logs"
msgstr ""
-msgid "ClusterIntegration|Send ModSecurity Logs"
+msgid "ClusterIntegration|Send Web Application Firewall Logs"
msgstr ""
msgid "ClusterIntegration|Service Token"
@@ -5450,15 +5608,12 @@ msgstr ""
msgid "ClusterIntegration|documentation"
msgstr ""
-msgid "ClusterIntegration|installed via %{installed_via}"
+msgid "ClusterIntegration|installed via %{linkStart}Cloud Run%{linkEnd}"
msgstr ""
msgid "ClusterIntegration|meets the requirements"
msgstr ""
-msgid "ClusterIntegration|pricing"
-msgstr ""
-
msgid "ClusterIntegration|sign up"
msgstr ""
@@ -5495,6 +5650,15 @@ msgstr ""
msgid "Code"
msgstr ""
+msgid "Code Coverage: %{coveragePercentage}%{percentSymbol}"
+msgstr ""
+
+msgid "Code Coverage| Empty code coverage data"
+msgstr ""
+
+msgid "Code Coverage|Couldn't fetch the code coverage data"
+msgstr ""
+
msgid "Code Owners"
msgstr ""
@@ -5549,7 +5713,7 @@ msgstr ""
msgid "Collapse approvers"
msgstr ""
-msgid "Collapse child epics"
+msgid "Collapse milestones"
msgstr ""
msgid "Collapse replies"
@@ -5567,6 +5731,9 @@ msgstr ""
msgid "Coming soon"
msgstr ""
+msgid "Comma-separated, e.g. '1.1.1.1, 2.2.2.0/24'"
+msgstr ""
+
msgid "Command"
msgstr ""
@@ -5737,6 +5904,12 @@ msgstr ""
msgid "Compliance framework (optional)"
msgstr ""
+msgid "Compliance frameworks"
+msgstr ""
+
+msgid "ComplianceDashboard|created by:"
+msgstr ""
+
msgid "ComplianceFramework|GDPR"
msgstr ""
@@ -5875,6 +6048,9 @@ msgstr ""
msgid "Connection timed out"
msgstr ""
+msgid "Connection timeout"
+msgstr ""
+
msgid "Contact sales to upgrade"
msgstr ""
@@ -5930,9 +6106,6 @@ msgstr ""
msgid "ContainerRegistry|CLI Commands"
msgstr ""
-msgid "ContainerRegistry|Compressed Size"
-msgstr ""
-
msgid "ContainerRegistry|Container Registry"
msgstr ""
@@ -5948,6 +6121,9 @@ msgstr ""
msgid "ContainerRegistry|Copy push command"
msgstr ""
+msgid "ContainerRegistry|Delete selected"
+msgstr ""
+
msgid "ContainerRegistry|Docker connection error"
msgstr ""
@@ -5981,16 +6157,16 @@ msgstr ""
msgid "ContainerRegistry|If you are not already logged in, you need to authenticate to the Container Registry by using your GitLab username and password. If you have %{twofaDocLinkStart}Two-Factor Authentication%{twofaDocLinkEnd} enabled, use a %{personalAccessTokensDocLinkStart}Personal Access Token%{personalAccessTokensDocLinkEnd} instead of a password."
msgstr ""
-msgid "ContainerRegistry|Image ID"
+msgid "ContainerRegistry|Image ID: %{imageId}"
msgstr ""
msgid "ContainerRegistry|Image Repositories"
msgstr ""
-msgid "ContainerRegistry|Keep and protect the images that matter most."
+msgid "ContainerRegistry|Image tags"
msgstr ""
-msgid "ContainerRegistry|Last Updated"
+msgid "ContainerRegistry|Keep and protect the images that matter most."
msgstr ""
msgid "ContainerRegistry|Login"
@@ -6005,6 +6181,9 @@ msgstr ""
msgid "ContainerRegistry|Please contact your administrator."
msgstr ""
+msgid "ContainerRegistry|Published %{timeInfo}"
+msgstr ""
+
msgid "ContainerRegistry|Push an image"
msgstr ""
@@ -6020,9 +6199,6 @@ msgstr ""
msgid "ContainerRegistry|Remove repository"
msgstr ""
-msgid "ContainerRegistry|Remove selected tags"
-msgstr ""
-
msgid "ContainerRegistry|Remove tag"
msgid_plural "ContainerRegistry|Remove tags"
msgstr[0] ""
@@ -6052,9 +6228,6 @@ msgstr ""
msgid "ContainerRegistry|Sorry, your filter produced no results."
msgstr ""
-msgid "ContainerRegistry|Tag"
-msgstr ""
-
msgid "ContainerRegistry|Tag expiration policy"
msgstr ""
@@ -6199,21 +6372,6 @@ msgstr ""
msgid "Control the display of third party offers."
msgstr ""
-msgid "Control the maximum concurrency of LFS/attachment backfill for this secondary node"
-msgstr ""
-
-msgid "Control the maximum concurrency of container repository operations for this Geo node"
-msgstr ""
-
-msgid "Control the maximum concurrency of repository backfill for this secondary node"
-msgstr ""
-
-msgid "Control the maximum concurrency of verification operations for this Geo node"
-msgstr ""
-
-msgid "Control the minimum interval in days that a repository should be reverified for this primary node"
-msgstr ""
-
msgid "Cookie domain"
msgstr ""
@@ -6355,6 +6513,9 @@ msgstr ""
msgid "Could not find design."
msgstr ""
+msgid "Could not find iteration"
+msgstr ""
+
msgid "Could not remove the trigger."
msgstr ""
@@ -6391,6 +6552,12 @@ msgstr ""
msgid "Coverage"
msgstr ""
+msgid "Coverage Fuzzing"
+msgstr ""
+
+msgid "Crash State"
+msgstr ""
+
msgid "Create"
msgstr ""
@@ -6439,6 +6606,9 @@ msgstr ""
msgid "Create a personal access token on your account to pull or push via %{protocol}."
msgstr ""
+msgid "Create a project pre-populated with the necessary files to get you started quickly."
+msgstr ""
+
msgid "Create an account using:"
msgstr ""
@@ -6607,6 +6777,9 @@ msgstr ""
msgid "Creates branch '%{branch_name}' and a merge request to resolve this issue."
msgstr ""
+msgid "Creating"
+msgstr ""
+
msgid "Creating epic"
msgstr ""
@@ -6658,7 +6831,10 @@ msgstr ""
msgid "Current vulnerabilities count"
msgstr ""
-msgid "CurrentUser|Buy CI minutes"
+msgid "CurrentUser|Buy Pipeline minutes"
+msgstr ""
+
+msgid "CurrentUser|One of your groups is running out"
msgstr ""
msgid "CurrentUser|Profile"
@@ -6673,6 +6849,9 @@ msgstr ""
msgid "CurrentUser|Upgrade"
msgstr ""
+msgid "Custom Attributes"
+msgstr ""
+
msgid "Custom CI configuration path"
msgstr ""
@@ -6936,12 +7115,18 @@ msgstr ""
msgid "DAG"
msgstr ""
+msgid "DAG visualization requires at least 3 dependent jobs."
+msgstr ""
+
msgid "DNS"
msgstr ""
msgid "Dashboard"
msgstr ""
+msgid "Dashboard uid not found"
+msgstr ""
+
msgid "DashboardProjects|All"
msgstr ""
@@ -6966,6 +7151,9 @@ msgstr ""
msgid "Data is still calculating..."
msgstr ""
+msgid "Datasource name not found"
+msgstr ""
+
msgid "Date"
msgstr ""
@@ -7146,6 +7334,9 @@ msgstr ""
msgid "Delete this attachment"
msgstr ""
+msgid "Delete user list"
+msgstr ""
+
msgid "Delete variable"
msgstr ""
@@ -7462,6 +7653,9 @@ msgstr ""
msgid "Deploying to"
msgstr ""
+msgid "Deploying to AWS is easy with GitLab"
+msgstr ""
+
msgid "Deployment Frequency"
msgstr ""
@@ -7824,6 +8018,9 @@ msgstr ""
msgid "Dismissed on pipeline %{pipelineLink} at %{projectLink}"
msgstr ""
+msgid "Display alerts from all your monitoring tools directly within GitLab."
+msgstr ""
+
msgid "Display name"
msgstr ""
@@ -7881,9 +8078,6 @@ msgstr ""
msgid "Don't show again"
msgstr ""
-msgid "Don't worry, you can access this tour by clicking on the help icon in the top right corner and choose <strong>Learn GitLab</strong>."
-msgstr ""
-
msgid "Done"
msgstr ""
@@ -8010,6 +8204,9 @@ msgstr ""
msgid "Edit Release"
msgstr ""
+msgid "Edit Slack integration"
+msgstr ""
+
msgid "Edit Snippet"
msgstr ""
@@ -8049,6 +8246,9 @@ msgstr ""
msgid "Edit issues"
msgstr ""
+msgid "Edit iteration"
+msgstr ""
+
msgid "Edit public deploy key"
msgstr ""
@@ -8097,6 +8297,9 @@ msgstr ""
msgid "Email"
msgstr ""
+msgid "Email Notification"
+msgstr ""
+
msgid "Email address"
msgstr ""
@@ -8109,6 +8312,9 @@ msgstr ""
msgid "Email not verified. Please verify your email in Salesforce."
msgstr ""
+msgid "Email notification for unknown sign-ins"
+msgstr ""
+
msgid "Email patch"
msgstr ""
@@ -8340,6 +8546,9 @@ msgstr ""
msgid "Enforce DNS rebinding attack protection"
msgstr ""
+msgid "Enforce personal access token expiration"
+msgstr ""
+
msgid "Ensure connectivity is available from the GitLab server to the Prometheus server"
msgstr ""
@@ -8409,6 +8618,9 @@ msgstr ""
msgid "Enter the name of your application, and we'll return a unique %{type}."
msgstr ""
+msgid "Enter weights for storages for new repositories."
+msgstr ""
+
msgid "Enter your password to approve"
msgstr ""
@@ -8418,15 +8630,27 @@ msgstr ""
msgid "Environment does not have deployments"
msgstr ""
+msgid "Environment is required for Stages::MetricEndpointInserter"
+msgstr ""
+
+msgid "Environment is required for Stages::VariableEndpointInserter"
+msgstr ""
+
msgid "Environment scope"
msgstr ""
+msgid "Environment variables are applied to all project environments in this instance via the Runner. You can use environment variables for passwords, secret keys, etc. Make variables available to the running application by prepending the variable key with <code>K8S_SECRET_</code>. You can set variables to be:"
+msgstr ""
+
msgid "Environment variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. Additionally, they can be masked so they are hidden in job logs, though they must match certain regexp requirements to do so. You can use environment variables for passwords, secret keys, or whatever you want."
msgstr ""
msgid "Environment variables are configured by your administrator to be %{link_start}protected%{link_end} by default"
msgstr ""
+msgid "Environment variables on this GitLab instance are configured to be %{link_start}protected%{link_end} by default"
+msgstr ""
+
msgid "Environment:"
msgstr ""
@@ -8958,6 +9182,9 @@ msgstr ""
msgid "EventFilterBy|Filter by comments"
msgstr ""
+msgid "EventFilterBy|Filter by designs"
+msgstr ""
+
msgid "EventFilterBy|Filter by epic events"
msgstr ""
@@ -9090,15 +9317,15 @@ msgstr ""
msgid "Expand approvers"
msgstr ""
-msgid "Expand child epics"
-msgstr ""
-
msgid "Expand down"
msgstr ""
msgid "Expand dropdown"
msgstr ""
+msgid "Expand milestones"
+msgstr ""
+
msgid "Expand sidebar"
msgstr ""
@@ -9114,6 +9341,9 @@ msgstr ""
msgid "Expiration date"
msgstr ""
+msgid "Expiration not enforced"
+msgstr ""
+
msgid "Expiration policy for the Container Registry is a perfect solution for keeping the Registry space down while still enjoying the full power of GitLab CI/CD."
msgstr ""
@@ -9234,6 +9464,9 @@ msgstr ""
msgid "Failed Jobs"
msgstr ""
+msgid "Failed on"
+msgstr ""
+
msgid "Failed to add a Zoom meeting"
msgstr ""
@@ -9294,13 +9527,19 @@ msgstr ""
msgid "Failed to install."
msgstr ""
+msgid "Failed to load assignees. Please try again."
+msgstr ""
+
+msgid "Failed to load authors. Please try again."
+msgstr ""
+
msgid "Failed to load emoji list."
msgstr ""
msgid "Failed to load error details from Sentry."
msgstr ""
-msgid "Failed to load errors from Sentry. Error message: %{errorMessage}"
+msgid "Failed to load errors from Sentry."
msgstr ""
msgid "Failed to load group activity metrics. Please try again."
@@ -9381,6 +9620,9 @@ msgstr ""
msgid "Failed to set due date because the date format is invalid."
msgstr ""
+msgid "Failed to set iteration on this issue. Please try again."
+msgstr ""
+
msgid "Failed to signing using smartcard authentication"
msgstr ""
@@ -9603,13 +9845,10 @@ msgstr ""
msgid "FeatureFlags|Target environments"
msgstr ""
-msgid "FeatureFlags|There are no active feature flags"
-msgstr ""
-
-msgid "FeatureFlags|There are no inactive feature flags"
+msgid "FeatureFlags|There was an error fetching the feature flags."
msgstr ""
-msgid "FeatureFlags|There was an error fetching the feature flags."
+msgid "FeatureFlags|There was an error retrieving user lists"
msgstr ""
msgid "FeatureFlags|Try again in a few moments or contact your support team."
@@ -9621,9 +9860,18 @@ msgstr ""
msgid "FeatureFlag|Delete strategy"
msgstr ""
+msgid "FeatureFlag|List"
+msgstr ""
+
msgid "FeatureFlag|Percentage"
msgstr ""
+msgid "FeatureFlag|Select a user list"
+msgstr ""
+
+msgid "FeatureFlag|There are no configured user lists"
+msgstr ""
+
msgid "FeatureFlag|Type"
msgstr ""
@@ -9780,6 +10028,12 @@ msgstr ""
msgid "Filter..."
msgstr ""
+msgid "Find File"
+msgstr ""
+
+msgid "Find bugs in your code with coverage-guided fuzzing"
+msgstr ""
+
msgid "Find by path"
msgstr ""
@@ -10044,6 +10298,9 @@ msgstr ""
msgid "Geo allows you to replicate your GitLab instance to other geographical locations."
msgstr ""
+msgid "Geo nodes are paused using a command run on the node"
+msgstr ""
+
msgid "GeoNodeStatusEvent|%{timeAgoStr} (%{pendingEvents} events)"
msgstr ""
@@ -10128,6 +10385,9 @@ msgstr ""
msgid "GeoNodes|Not checksummed"
msgstr ""
+msgid "GeoNodes|Package files"
+msgstr ""
+
msgid "GeoNodes|Pausing replication stops the sync process. Are you sure?"
msgstr ""
@@ -10146,6 +10406,9 @@ msgstr ""
msgid "GeoNodes|Replication slots"
msgstr ""
+msgid "GeoNodes|Replication status"
+msgstr ""
+
msgid "GeoNodes|Repositories"
msgstr ""
@@ -10356,6 +10619,9 @@ msgstr ""
msgid "Geo|Synchronization failed - %{error}"
msgstr ""
+msgid "Geo|Synchronization of %{itemTitle} is disabled."
+msgstr ""
+
msgid "Geo|The database is currently %{db_lag} behind the primary node."
msgstr ""
@@ -10668,6 +10934,9 @@ msgstr ""
msgid "Go to %{link_to_google_takeout}."
msgstr ""
+msgid "Go to <strong>Issues</strong> > <strong>Boards</strong> to access your personalized learning issue board."
+msgstr ""
+
msgid "Go to Pipelines"
msgstr ""
@@ -10767,9 +11036,6 @@ msgstr ""
msgid "Go to your snippets"
msgstr ""
-msgid "Golden Tanuki"
-msgstr ""
-
msgid "Google Cloud Platform"
msgstr ""
@@ -10791,6 +11057,9 @@ msgstr ""
msgid "Grafana URL"
msgstr ""
+msgid "Grafana response contains invalid json"
+msgstr ""
+
msgid "GrafanaIntegration|API Token"
msgstr ""
@@ -10839,9 +11108,6 @@ msgstr ""
msgid "Group %{group_name} was successfully created."
msgstr ""
-msgid "Group '%{group_name}' is being imported."
-msgstr ""
-
msgid "Group Audit Events"
msgstr ""
@@ -10878,9 +11144,6 @@ msgstr ""
msgid "Group by:"
msgstr ""
-msgid "Group could not be imported: %{errors}"
-msgstr ""
-
msgid "Group description"
msgstr ""
@@ -10908,9 +11171,15 @@ msgstr ""
msgid "Group has not been marked for deletion"
msgstr ""
+msgid "Group import could not be scheduled"
+msgstr ""
+
msgid "Group info:"
msgstr ""
+msgid "Group is required when cluster_type is :group"
+msgstr ""
+
msgid "Group maintainers can register group runners in the %{link}"
msgstr ""
@@ -10974,6 +11243,24 @@ msgstr ""
msgid "GroupActivyMetrics|Recent activity (last 90 days)"
msgstr ""
+msgid "GroupImport|Failed to import group."
+msgstr ""
+
+msgid "GroupImport|Group '%{group_name}' is being imported."
+msgstr ""
+
+msgid "GroupImport|Group could not be imported: %{errors}"
+msgstr ""
+
+msgid "GroupImport|Please wait while we import the group for you. Refresh at will."
+msgstr ""
+
+msgid "GroupImport|The group was successfully imported."
+msgstr ""
+
+msgid "GroupImport|Unable to process group import file"
+msgstr ""
+
msgid "GroupRoadmap|%{dateWord} – No end date"
msgstr ""
@@ -11241,6 +11528,9 @@ msgstr ""
msgid "Groups (%{groups})"
msgstr ""
+msgid "Groups and projects"
+msgstr ""
+
msgid "Groups can also be nested by creating %{subgroup_docs_link_start}subgroups%{subgroup_docs_link_end}."
msgstr ""
@@ -11286,6 +11576,33 @@ msgstr ""
msgid "GroupsEmptyState|You can manage your group member’s permissions and access to each project in the group."
msgstr ""
+msgid "GroupsNew|Contact an administrator to enable options for importing your group."
+msgstr ""
+
+msgid "GroupsNew|Create"
+msgstr ""
+
+msgid "GroupsNew|Create group"
+msgstr ""
+
+msgid "GroupsNew|GitLab group export"
+msgstr ""
+
+msgid "GroupsNew|Import"
+msgstr ""
+
+msgid "GroupsNew|Import group"
+msgstr ""
+
+msgid "GroupsNew|My Awesome Group"
+msgstr ""
+
+msgid "GroupsNew|No import options available"
+msgstr ""
+
+msgid "GroupsNew|To copy a GitLab group between installations, navigate to the group settings page for the original installation, generate an export file, and upload it here."
+msgstr ""
+
msgid "GroupsTree|Are you sure you want to leave the \"%{fullName}\" group?"
msgstr ""
@@ -11388,6 +11705,9 @@ msgstr ""
msgid "Helps reduce request volume for protected paths"
msgstr ""
+msgid "Here are all your projects in your group, including the one you just created. To start, let’s take a look at your personalized learning project which will help you learn about GitLab at your own pace."
+msgstr ""
+
msgid "Here you will find recent merge request activity"
msgstr ""
@@ -11402,6 +11722,9 @@ msgid_plural "Hide charts"
msgstr[0] ""
msgstr[1] ""
+msgid "Hide details"
+msgstr ""
+
msgid "Hide file browser"
msgstr ""
@@ -11411,6 +11734,9 @@ msgstr ""
msgid "Hide host keys manual input"
msgstr ""
+msgid "Hide list"
+msgstr ""
+
msgid "Hide marketing-related entries from help"
msgstr ""
@@ -11431,9 +11757,6 @@ msgstr[1] ""
msgid "Hide values"
msgstr ""
-msgid "Hiding all labels"
-msgstr ""
-
msgid "High or unknown vulnerabilities present"
msgstr ""
@@ -11518,6 +11841,9 @@ msgstr ""
msgid "ID:"
msgstr ""
+msgid "IDE"
+msgstr ""
+
msgid "IDE|Allow live previews of JavaScript projects in the Web IDE using CodeSandbox Live Preview."
msgstr ""
@@ -11608,10 +11934,10 @@ msgstr ""
msgid "If enabled"
msgstr ""
-msgid "If enabled, access to projects will be validated on an external service using their classification label."
+msgid "If enabled, GitLab will handle Object Storage replication using Geo. %{linkStart}More information%{linkEnd}"
msgstr ""
-msgid "If enabled, and if object storage is enabled, GitLab will handle Object Storage replication using Geo"
+msgid "If enabled, access to projects will be validated on an external service using their classification label."
msgstr ""
msgid "If there is no previous license or if the previous license has expired, some GitLab functionality will be blocked until a new, valid license is uploaded."
@@ -11973,6 +12299,9 @@ msgid_plural "Instances"
msgstr[0] ""
msgstr[1] ""
+msgid "Instance Configuration"
+msgstr ""
+
msgid "Instance Statistics visibility"
msgstr ""
@@ -11994,9 +12323,6 @@ msgstr ""
msgid "Integrations"
msgstr ""
-msgid "Integrations allow you to integrate GitLab with other applications"
-msgstr ""
-
msgid "Integrations|All details"
msgstr ""
@@ -12108,6 +12434,9 @@ msgstr ""
msgid "Invalid query"
msgstr ""
+msgid "Invalid repository bundle for snippet with id %{snippet_id}"
+msgstr ""
+
msgid "Invalid repository path"
msgstr ""
@@ -12126,6 +12455,9 @@ msgstr ""
msgid "Invalid two-factor code."
msgstr ""
+msgid "Invalid yaml"
+msgstr ""
+
msgid "Invitation"
msgstr ""
@@ -12345,15 +12677,24 @@ msgstr ""
msgid "It seems like the Dependency Scanning job ran successfully, but no dependencies have been detected in your project."
msgstr ""
+msgid "It seems that there is currently no available data for code coverage"
+msgstr ""
+
msgid "It's you"
msgstr ""
+msgid "Iteration"
+msgstr ""
+
msgid "Iteration changed to"
msgstr ""
msgid "Iteration removed"
msgstr ""
+msgid "Iteration updated"
+msgstr ""
+
msgid "Iterations"
msgstr ""
@@ -12384,12 +12725,18 @@ msgstr ""
msgid "January"
msgstr ""
+msgid "Jira Issues"
+msgstr ""
+
msgid "Jira import is already running."
msgstr ""
msgid "Jira integration not configured."
msgstr ""
+msgid "Jira project key is not configured"
+msgstr ""
+
msgid "Jira project: %{importProject}"
msgstr ""
@@ -12570,6 +12917,9 @@ msgstr ""
msgid "Keep divergent refs"
msgstr ""
+msgid "Kerberos access denied"
+msgstr ""
+
msgid "Key"
msgstr ""
@@ -12579,10 +12929,13 @@ msgstr ""
msgid "Key: %{key}"
msgstr ""
-msgid "Keyboard Shortcuts"
+msgid "Keyboard shortcuts"
msgstr ""
-msgid "Keyboard shortcuts"
+msgid "Keys"
+msgstr ""
+
+msgid "Ki"
msgstr ""
msgid "Kubernetes"
@@ -12624,6 +12977,9 @@ msgstr ""
msgid "LDAP"
msgstr ""
+msgid "LDAP Synchronization"
+msgstr ""
+
msgid "LDAP settings"
msgstr ""
@@ -12848,12 +13204,18 @@ msgstr ""
msgid "Learn more about custom project templates"
msgstr ""
+msgid "Learn more about deploying to AWS"
+msgstr ""
+
msgid "Learn more about deploying to a cluster"
msgstr ""
msgid "Learn more about group-level project templates"
msgstr ""
+msgid "Learn more about job dependencies"
+msgstr ""
+
msgid "Learn more about signing commits"
msgstr ""
@@ -12905,9 +13267,21 @@ msgstr ""
msgid "License History"
msgstr ""
+msgid "License ID:"
+msgstr ""
+
+msgid "License URL"
+msgstr ""
+
msgid "License-Check"
msgstr ""
+msgid "LicenseCompliance|%{docLinkStart}License Approvals%{docLinkEnd} are active"
+msgstr ""
+
+msgid "LicenseCompliance|%{docLinkStart}License Approvals%{docLinkEnd} are inactive"
+msgstr ""
+
msgid "LicenseCompliance|Add a license"
msgstr ""
@@ -12929,9 +13303,15 @@ msgstr ""
msgid "LicenseCompliance|Deny"
msgstr ""
+msgid "LicenseCompliance|Learn more about %{linkStart}License Approvals%{linkEnd}"
+msgstr ""
+
msgid "LicenseCompliance|License"
msgstr ""
+msgid "LicenseCompliance|License Approvals"
+msgstr ""
+
msgid "LicenseCompliance|License Compliance detected %d license and policy violation for the source branch only; approval required"
msgid_plural "LicenseCompliance|License Compliance detected %d licenses and policy violations for the source branch only; approval required"
msgstr[0] ""
@@ -13152,6 +13532,9 @@ msgstr ""
msgid "List your Bitbucket Server repositories"
msgstr ""
+msgid "Lists"
+msgstr ""
+
msgid "Live preview"
msgstr ""
@@ -13335,6 +13718,9 @@ msgstr ""
msgid "Manage project labels"
msgstr ""
+msgid "Manage storage usage"
+msgstr ""
+
msgid "Manage two-factor authentication"
msgstr ""
@@ -13350,6 +13736,9 @@ msgstr ""
msgid "Manifest file import"
msgstr ""
+msgid "Manifest import"
+msgstr ""
+
msgid "Manual job"
msgstr ""
@@ -13497,6 +13886,12 @@ msgstr ""
msgid "Maximum field length"
msgstr ""
+msgid "Maximum file size is 2MB. Please select a smaller file."
+msgstr ""
+
+msgid "Maximum import size (MB)"
+msgstr ""
+
msgid "Maximum job timeout"
msgstr ""
@@ -13536,6 +13931,9 @@ msgstr ""
msgid "Maximum size of Elasticsearch bulk indexing requests."
msgstr ""
+msgid "Maximum size of import files."
+msgstr ""
+
msgid "Maximum size of individual attachments in comments."
msgstr ""
@@ -13587,6 +13985,9 @@ msgstr ""
msgid "Merge (when the pipeline succeeds)"
msgstr ""
+msgid "Merge Conflicts"
+msgstr ""
+
msgid "Merge Request"
msgstr ""
@@ -13713,6 +14114,9 @@ msgstr ""
msgid "MergeRequests|Squash task canceled: another squash is already in progress."
msgstr ""
+msgid "MergeRequests|This project does not allow squashing commits when merge requests are accepted."
+msgstr ""
+
msgid "MergeRequests|Thread stays resolved"
msgstr ""
@@ -13866,6 +14270,12 @@ msgstr ""
msgid "MetricsSettings|Add a button to the metrics dashboard linking directly to your existing external dashboard."
msgstr ""
+msgid "MetricsSettings|Choose whether to display dashboard metrics in UTC or the user's local timezone."
+msgstr ""
+
+msgid "MetricsSettings|Dashboard timezone"
+msgstr ""
+
msgid "MetricsSettings|External dashboard URL"
msgstr ""
@@ -13875,6 +14285,12 @@ msgstr ""
msgid "MetricsSettings|Metrics Dashboard"
msgstr ""
+msgid "MetricsSettings|UTC (Coordinated Universal Time)"
+msgstr ""
+
+msgid "MetricsSettings|User's local timezone"
+msgstr ""
+
msgid "Metrics|Add metric"
msgstr ""
@@ -13890,6 +14306,9 @@ msgstr ""
msgid "Metrics|Create metric"
msgstr ""
+msgid "Metrics|Current"
+msgstr ""
+
msgid "Metrics|Delete metric"
msgstr ""
@@ -13940,6 +14359,9 @@ msgstr ""
msgid "Metrics|Max"
msgstr ""
+msgid "Metrics|Min"
+msgstr ""
+
msgid "Metrics|Must be a valid PromQL query."
msgstr ""
@@ -13955,6 +14377,9 @@ msgstr ""
msgid "Metrics|Refresh dashboard"
msgstr ""
+msgid "Metrics|Select a value"
+msgstr ""
+
msgid "Metrics|Star dashboard"
msgstr ""
@@ -13973,12 +14398,18 @@ msgstr ""
msgid "Metrics|There was an error getting annotations information."
msgstr ""
+msgid "Metrics|There was an error getting dashboard validation warnings information."
+msgstr ""
+
msgid "Metrics|There was an error getting deployment information."
msgstr ""
msgid "Metrics|There was an error getting environments information."
msgstr ""
+msgid "Metrics|There was an error getting options for variable \"%{name}\"."
+msgstr ""
+
msgid "Metrics|There was an error trying to validate your query"
msgstr ""
@@ -14021,6 +14452,9 @@ msgstr ""
msgid "Metrics|You're about to permanently delete this metric. This cannot be undone."
msgstr ""
+msgid "Metrics|Your dashboard schema is invalid. Edit the dashboard to correct the YAML schema."
+msgstr ""
+
msgid "Metrics|e.g. HTTP requests"
msgstr ""
@@ -14036,12 +14470,18 @@ msgstr ""
msgid "Metrics|e.g. req/sec"
msgstr ""
+msgid "Mi"
+msgstr ""
+
msgid "Microsoft Azure"
msgstr ""
msgid "Middleman project with Static Site Editor support"
msgstr ""
+msgid "Migrate your data from an external source like GitHub, Bitbucket, or another instance of GitLab."
+msgstr ""
+
msgid "Migrated %{success_count}/%{total_count} files."
msgstr ""
@@ -14176,6 +14616,9 @@ msgstr ""
msgid "Minimum capacity to be available before we schedule more mirrors preemptively."
msgstr ""
+msgid "Minimum interval in days"
+msgstr ""
+
msgid "Minimum length is %{minimum_password_length} characters"
msgstr ""
@@ -14362,6 +14805,12 @@ msgstr ""
msgid "Multiple uploaders found: %{uploader_types}"
msgstr ""
+msgid "Must match with the %{codeStart}external_url%{codeEnd} in %{codeStart}/etc/gitlab/gitlab.rb%{codeEnd}."
+msgstr ""
+
+msgid "Must match with the %{codeStart}geo_node_name%{codeEnd} in %{codeStart}/etc/gitlab/gitlab.rb%{codeEnd}. %{linkStart}More information%{linkEnd}"
+msgstr ""
+
msgid "My Awesome Group"
msgstr ""
@@ -14476,10 +14925,16 @@ msgstr ""
msgid "New Application"
msgstr ""
+msgid "New Branch"
+msgstr ""
+
+msgid "New Deploy Key"
+msgstr ""
+
msgid "New Environment"
msgstr ""
-msgid "New Geo Node"
+msgid "New File"
msgstr ""
msgid "New Group"
@@ -14496,15 +14951,15 @@ msgid_plural "New Issues"
msgstr[0] ""
msgstr[1] ""
-msgid "New Iteration"
-msgstr ""
-
msgid "New Jira import"
msgstr ""
msgid "New Label"
msgstr ""
+msgid "New Merge Request"
+msgstr ""
+
msgid "New Milestone"
msgstr ""
@@ -14523,6 +14978,9 @@ msgstr ""
msgid "New Snippet"
msgstr ""
+msgid "New User"
+msgstr ""
+
msgid "New branch"
msgstr ""
@@ -14565,6 +15023,9 @@ msgstr ""
msgid "New issue title"
msgstr ""
+msgid "New iteration"
+msgstr ""
+
msgid "New iteration created"
msgstr ""
@@ -14742,6 +15203,12 @@ msgstr ""
msgid "No grouping"
msgstr ""
+msgid "No iteration"
+msgstr ""
+
+msgid "No iterations to show"
+msgstr ""
+
msgid "No job log"
msgstr ""
@@ -14784,6 +15251,9 @@ msgstr ""
msgid "No other labels with such name or description"
msgstr ""
+msgid "No panels matching properties %{opts}"
+msgstr ""
+
msgid "No parent group"
msgstr ""
@@ -14817,6 +15287,9 @@ msgstr ""
msgid "No schedules"
msgstr ""
+msgid "No stack trace for this error"
+msgstr ""
+
msgid "No starrers matched your search"
msgstr ""
@@ -14829,7 +15302,7 @@ msgstr ""
msgid "No test coverage"
msgstr ""
-msgid "No thanks, don't show this again"
+msgid "No thanks"
msgstr ""
msgid "No vulnerabilities present"
@@ -14844,9 +15317,6 @@ msgstr ""
msgid "No, directly import the existing email addresses and usernames."
msgstr ""
-msgid "No, not interested right now"
-msgstr ""
-
msgid "No. of commits"
msgstr ""
@@ -14892,9 +15362,6 @@ msgstr ""
msgid "Not found."
msgstr ""
-msgid "Not helpful"
-msgstr ""
-
msgid "Not now"
msgstr ""
@@ -15051,6 +15518,9 @@ msgstr ""
msgid "Notifications on"
msgstr ""
+msgid "Notify users by email when sign-in location is not recognized"
+msgstr ""
+
msgid "Nov"
msgstr ""
@@ -15117,9 +15587,6 @@ msgstr ""
msgid "Oh no!"
msgstr ""
-msgid "Ok let's go"
-msgstr ""
-
msgid "Oldest first"
msgstr ""
@@ -15132,16 +15599,49 @@ msgstr ""
msgid "On track"
msgstr ""
+msgid "OnDemandScans|Attached branch"
+msgstr ""
+
+msgid "OnDemandScans|Attached branch is where the scan job runs."
+msgstr ""
+
+msgid "OnDemandScans|Could not run the scan. Please try again."
+msgstr ""
+
msgid "OnDemandScans|Create new DAST scan"
msgstr ""
+msgid "OnDemandScans|DAST will scan the target URL and any discovered sub URLs."
+msgstr ""
+
+msgid "OnDemandScans|New on-demand DAST scan"
+msgstr ""
+
msgid "OnDemandScans|On-demand Scans"
msgstr ""
+msgid "OnDemandScans|On-demand scans run outside the DevOps cycle and find vulnerabilities in your projects. %{learnMoreLinkStart}Learn more%{learnMoreLinkEnd}"
+msgstr ""
+
+msgid "OnDemandScans|Only a passive scan can be performed on demand."
+msgstr ""
+
+msgid "OnDemandScans|Passive DAST Scan"
+msgstr ""
+
+msgid "OnDemandScans|Please enter a valid URL format, ex: http://www.example.com/home"
+msgstr ""
+
+msgid "OnDemandScans|Run this scan"
+msgstr ""
+
+msgid "OnDemandScans|Scan mode"
+msgstr ""
+
msgid "OnDemandScans|Schedule or run scans immediately against target sites. Currently available on-demand scan type: DAST. %{helpLinkStart}More information%{helpLinkEnd}"
msgstr ""
-msgid "Onboarding"
+msgid "OnDemandScans|Target URL"
msgstr ""
msgid "Once imported, repositories can be mirrored over SSH. Read more %{link_start}here%{link_end}."
@@ -15167,6 +15667,9 @@ msgstr ""
msgid "One or more of you personal access tokens were revoked"
msgstr ""
+msgid "One or more of your %{provider} projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
+msgstr ""
+
msgid "One or more of your Bitbucket projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
msgstr ""
@@ -15668,6 +16171,9 @@ msgstr ""
msgid "Parent epic is not present."
msgstr ""
+msgid "Parsing error for param :embed_json. %{message}"
+msgstr ""
+
msgid "Part of merge request changes"
msgstr ""
@@ -15680,6 +16186,9 @@ msgstr ""
msgid "Passed"
msgstr ""
+msgid "Passed on"
+msgstr ""
+
msgid "Password"
msgstr ""
@@ -15764,6 +16273,9 @@ msgstr ""
msgid "Perform common operations on GitLab project"
msgstr ""
+msgid "Performance and resource management"
+msgstr ""
+
msgid "Performance optimization"
msgstr ""
@@ -15971,7 +16483,7 @@ msgstr ""
msgid "Pipelines|Something went wrong while cleaning runners cache."
msgstr ""
-msgid "Pipelines|There are currently no %{scope} pipelines."
+msgid "Pipelines|There are currently no finished pipelines."
msgstr ""
msgid "Pipelines|There are currently no pipelines."
@@ -16082,6 +16594,9 @@ msgstr ""
msgid "Pipeline|Stop pipeline #%{pipelineId}?"
msgstr ""
+msgid "Pipeline|Tag name"
+msgstr ""
+
msgid "Pipeline|Trigger author"
msgstr ""
@@ -16163,12 +16678,18 @@ msgstr ""
msgid "Please check your email (%{email}) to verify that you own this address and unlock the power of CI/CD. Didn't receive it? %{resend_link}. Wrong email address? %{update_link}."
msgstr ""
+msgid "Please choose a file"
+msgstr ""
+
msgid "Please choose a group URL with no special characters."
msgstr ""
msgid "Please complete your profile with email address"
msgstr ""
+msgid "Please convert %{linkStart}them to Git%{linkEnd}, and go through the %{linkToImportFlow} again."
+msgstr ""
+
msgid "Please convert them to %{link_to_git}, and go through the %{link_to_import_flow} again."
msgstr ""
@@ -16226,6 +16747,9 @@ msgstr ""
msgid "Please provide a valid email address."
msgstr ""
+msgid "Please provide attributes to update"
+msgstr ""
+
msgid "Please refer to <a href=\"%{docs_url}\">%{docs_url}</a>"
msgstr ""
@@ -16961,6 +17485,9 @@ msgstr ""
msgid "Project has too many %{label_for_message} to search"
msgstr ""
+msgid "Project is required when cluster_type is :project"
+msgstr ""
+
msgid "Project members"
msgstr ""
@@ -17078,7 +17605,40 @@ msgstr ""
msgid "ProjectService|Comment"
msgstr ""
-msgid "ProjectService|Comment will be posted on each event"
+msgid "ProjectService|Event will be triggered by a push to the repository"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a commit is created/updated"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a confidential issue is created/updated/closed"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a deployment finishes"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a merge request is created/updated/merged"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a new tag is pushed to the repository"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a new, unique alert is recorded"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a pipeline status changes"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a wiki page is created/updated"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when an issue is created/updated/closed"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when someone adds a comment"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when someone adds a comment on a confidential issue"
msgstr ""
msgid "ProjectService|Perform common operations on GitLab project: %{project_name}"
@@ -17129,6 +17689,9 @@ msgstr ""
msgid "ProjectSettings|Enable 'Delete source branch' option by default"
msgstr ""
+msgid "ProjectSettings|Enable merge trains and pipelines for merged results"
+msgstr ""
+
msgid "ProjectSettings|Every merge creates a merge commit"
msgstr ""
@@ -17159,7 +17722,7 @@ msgstr ""
msgid "ProjectSettings|Forks"
msgstr ""
-msgid "ProjectSettings|Git Large File Storage"
+msgid "ProjectSettings|Git Large File Storage (LFS)"
msgstr ""
msgid "ProjectSettings|Internal"
@@ -17195,9 +17758,6 @@ msgstr ""
msgid "ProjectSettings|Merge options"
msgstr ""
-msgid "ProjectSettings|Merge pipelines will try to validate the post-merge result prior to merging"
-msgstr ""
-
msgid "ProjectSettings|Merge requests"
msgstr ""
@@ -17225,6 +17785,9 @@ msgstr ""
msgid "ProjectSettings|Pipelines"
msgstr ""
+msgid "ProjectSettings|Pipelines for merge requests must be enabled in the CI/CD configuration file, or pipelines could be unresolvable or dropped"
+msgstr ""
+
msgid "ProjectSettings|Pipelines must succeed"
msgstr ""
@@ -17252,6 +17815,9 @@ msgstr ""
msgid "ProjectSettings|Show link to create/view merge request when pushing from the command line"
msgstr ""
+msgid "ProjectSettings|Skipped pipelines are considered successful"
+msgstr ""
+
msgid "ProjectSettings|Snippets"
msgstr ""
@@ -17267,6 +17833,9 @@ msgstr ""
msgid "ProjectSettings|These checks must pass before merge requests can be merged"
msgstr ""
+msgid "ProjectSettings|This introduces the risk of merging changes that will not pass the pipeline."
+msgstr ""
+
msgid "ProjectSettings|This setting is applied on the server level and can be overridden by an admin."
msgstr ""
@@ -17444,15 +18013,27 @@ msgstr ""
msgid "ProjectsNew|Blank project"
msgstr ""
+msgid "ProjectsNew|Connect your external repository to GitLab CI/CD."
+msgstr ""
+
msgid "ProjectsNew|Contact an administrator to enable options for importing your project."
msgstr ""
msgid "ProjectsNew|Create"
msgstr ""
+msgid "ProjectsNew|Create a blank project to house your files, plan your work, and collaborate on code, among other things."
+msgstr ""
+
+msgid "ProjectsNew|Create blank project"
+msgstr ""
+
msgid "ProjectsNew|Create from template"
msgstr ""
+msgid "ProjectsNew|Create new project"
+msgstr ""
+
msgid "ProjectsNew|Creating project & repository."
msgstr ""
@@ -17477,6 +18058,9 @@ msgstr ""
msgid "ProjectsNew|Project description %{tag_start}(optional)%{tag_end}"
msgstr ""
+msgid "ProjectsNew|Run CI/CD for external repository"
+msgstr ""
+
msgid "ProjectsNew|Template"
msgstr ""
@@ -17738,6 +18322,9 @@ msgstr ""
msgid "Protected Branch"
msgstr ""
+msgid "Protected Branches"
+msgstr ""
+
msgid "Protected Environment"
msgstr ""
@@ -17750,6 +18337,9 @@ msgstr ""
msgid "Protected Tag"
msgstr ""
+msgid "Protected Tags"
+msgstr ""
+
msgid "Protected branches"
msgstr ""
@@ -17849,6 +18439,9 @@ msgstr ""
msgid "Provider"
msgstr ""
+msgid "Proxy support for this API is not available currently"
+msgstr ""
+
msgid "Pseudonymizer data collection"
msgstr ""
@@ -17873,6 +18466,9 @@ msgstr ""
msgid "Publish to status page"
msgstr ""
+msgid "Published on status page"
+msgstr ""
+
msgid "Publishes this issue to the associated status page."
msgstr ""
@@ -18047,6 +18643,9 @@ msgstr ""
msgid "Recipe"
msgstr ""
+msgid "Reconfigure"
+msgstr ""
+
msgid "Recover hidden stage"
msgstr ""
@@ -18133,6 +18732,9 @@ msgstr ""
msgid "Registration|Your profile"
msgstr ""
+msgid "Regulate approvals by authors/committers, based on compliance frameworks. Can be changed only at the instance level."
+msgstr ""
+
msgid "Rejected (closed)"
msgstr ""
@@ -18183,15 +18785,24 @@ msgstr ""
msgid "ReleaseAssetLinkType|Image"
msgstr ""
+msgid "ReleaseAssetLinkType|Images"
+msgstr ""
+
msgid "ReleaseAssetLinkType|Other"
msgstr ""
msgid "ReleaseAssetLinkType|Package"
msgstr ""
+msgid "ReleaseAssetLinkType|Packages"
+msgstr ""
+
msgid "ReleaseAssetLinkType|Runbook"
msgstr ""
+msgid "ReleaseAssetLinkType|Runbooks"
+msgstr ""
+
msgid "Releases"
msgstr ""
@@ -18204,6 +18815,9 @@ msgstr ""
msgid "Releases documentation"
msgstr ""
+msgid "Releases|New Release"
+msgstr ""
+
msgid "Release|Something went wrong while getting the release details"
msgstr ""
@@ -18276,6 +18890,9 @@ msgstr ""
msgid "Remove fork relationship"
msgstr ""
+msgid "Remove from batch"
+msgstr ""
+
msgid "Remove from board"
msgstr ""
@@ -18465,6 +19082,12 @@ msgstr ""
msgid "Replication"
msgstr ""
+msgid "Replication enabled"
+msgstr ""
+
+msgid "Replication paused"
+msgstr ""
+
msgid "Reply by email"
msgstr ""
@@ -18492,15 +19115,15 @@ msgstr ""
msgid "Reported %{timeAgo} by %{reportedBy}"
msgstr ""
-msgid "Reported Resource Changes: %{addNum} to add, %{changeNum} to change, %{deleteNum} to delete"
-msgstr ""
-
msgid "Reporter"
msgstr ""
msgid "Reporting"
msgstr ""
+msgid "Reports"
+msgstr ""
+
msgid "Reports|%{combinedString} and %{resolvedString}"
msgstr ""
@@ -18539,6 +19162,9 @@ msgstr ""
msgid "Reports|Failure"
msgstr ""
+msgid "Reports|Identifier"
+msgstr ""
+
msgid "Reports|Metrics reports are loading"
msgstr ""
@@ -18551,6 +19177,9 @@ msgstr ""
msgid "Reports|Metrics reports failed loading results"
msgstr ""
+msgid "Reports|Scanner"
+msgstr ""
+
msgid "Reports|Severity"
msgstr ""
@@ -18599,15 +19228,33 @@ msgstr ""
msgid "Repository cleanup has started. You will receive an email once the cleanup operation is complete."
msgstr ""
+msgid "Repository files count over the limit"
+msgstr ""
+
+msgid "Repository has an invalid default branch name."
+msgstr ""
+
+msgid "Repository has more than one branch."
+msgstr ""
+
msgid "Repository has no locks."
msgstr ""
+msgid "Repository has tags."
+msgstr ""
+
msgid "Repository maintenance"
msgstr ""
msgid "Repository mirroring"
msgstr ""
+msgid "Repository must contain at least 1 file."
+msgstr ""
+
+msgid "Repository size is above the limit."
+msgstr ""
+
msgid "Repository static objects"
msgstr ""
@@ -18617,7 +19264,7 @@ msgstr ""
msgid "Repository sync capacity"
msgstr ""
-msgid "Repository: %{counter_repositories} / Wikis: %{counter_wikis} / Build Artifacts: %{counter_build_artifacts} / LFS: %{counter_lfs_objects}"
+msgid "Repository: %{counter_repositories} / Wikis: %{counter_wikis} / Build Artifacts: %{counter_build_artifacts} / LFS: %{counter_lfs_objects} / Snippets: %{counter_snippets}"
msgstr ""
msgid "RepositorySettingsAccessLevel|Select"
@@ -18629,6 +19276,9 @@ msgstr ""
msgid "Request Headers"
msgstr ""
+msgid "Request details"
+msgstr ""
+
msgid "Request parameter %{param} is missing."
msgstr ""
@@ -18735,6 +19385,9 @@ msgstr ""
msgid "Reset template"
msgstr ""
+msgid "Reset to project defaults"
+msgstr ""
+
msgid "Resetting the authorization key for this project will require updating the authorization key in every alert source it is enabled in."
msgstr ""
@@ -18831,9 +19484,6 @@ msgstr ""
msgid "Resume"
msgstr ""
-msgid "Resume replication"
-msgstr ""
-
msgid "Resync"
msgstr ""
@@ -19456,6 +20106,9 @@ msgstr ""
msgid "SecurityReports|False positive"
msgstr ""
+msgid "SecurityReports|Group Security Dashboard"
+msgstr ""
+
msgid "SecurityReports|Hide dismissed"
msgstr ""
@@ -19510,10 +20163,13 @@ msgstr ""
msgid "SecurityReports|Remove project from dashboard"
msgstr ""
-msgid "SecurityReports|Report type"
+msgid "SecurityReports|Return to dashboard"
msgstr ""
-msgid "SecurityReports|Return to dashboard"
+msgid "SecurityReports|Scan details"
+msgstr ""
+
+msgid "SecurityReports|Scanner type"
msgstr ""
msgid "SecurityReports|Security Dashboard"
@@ -19570,6 +20226,9 @@ msgstr ""
msgid "SecurityReports|There was an error while generating the report."
msgstr ""
+msgid "SecurityReports|Unable to add %{invalidProjectsMessage}"
+msgstr ""
+
msgid "SecurityReports|Unable to add %{invalidProjects}"
msgstr ""
@@ -19684,6 +20343,9 @@ msgstr ""
msgid "Select due date"
msgstr ""
+msgid "Select file"
+msgstr ""
+
msgid "Select group or project"
msgstr ""
@@ -19747,9 +20409,6 @@ msgstr ""
msgid "Select the branch you want to set as the default for this project. All merge requests and commits will automatically be made against this branch unless you specify a different one."
msgstr ""
-msgid "Select the configured storage available for new repositories to be placed on."
-msgstr ""
-
msgid "Select the custom project template source group."
msgstr ""
@@ -19942,12 +20601,6 @@ msgstr ""
msgid "Service Desk is enabled but not yet active"
msgstr ""
-msgid "Service Desk is off"
-msgstr ""
-
-msgid "Service Desk is on"
-msgstr ""
-
msgid "Service Templates"
msgstr ""
@@ -20035,6 +20688,15 @@ msgstr ""
msgid "Set the milestone to %{milestone_reference}."
msgstr ""
+msgid "Set the number of concurrent requests this secondary node will make to the primary node while backfilling."
+msgstr ""
+
+msgid "Set the synchronization and verification capacity for the secondary node."
+msgstr ""
+
+msgid "Set the timeout in seconds to send a secondary node status to the primary and IPs allowed for the secondary nodes."
+msgstr ""
+
msgid "Set time estimate"
msgstr ""
@@ -20074,6 +20736,9 @@ msgstr ""
msgid "Set weight to %{weight}."
msgstr ""
+msgid "Set what should be replicated by choosing specific projects or groups by the secondary node."
+msgstr ""
+
msgid "SetPasswordToCloneLink|set a password"
msgstr ""
@@ -20200,6 +20865,9 @@ msgstr ""
msgid "Show complete raw log"
msgstr ""
+msgid "Show details"
+msgstr ""
+
msgid "Show file browser"
msgstr ""
@@ -20209,10 +20877,13 @@ msgstr ""
msgid "Show latest version"
msgstr ""
+msgid "Show list"
+msgstr ""
+
msgid "Show me everything"
msgstr ""
-msgid "Show me how"
+msgid "Show me how to add a pipeline"
msgstr ""
msgid "Show me more advanced stuff"
@@ -20261,9 +20932,6 @@ msgstr ""
msgid "Showing all issues"
msgstr ""
-msgid "Showing all labels"
-msgstr ""
-
msgid "Showing last %{size} of log -"
msgstr ""
@@ -20375,9 +21043,6 @@ msgstr ""
msgid "Skip outdated deployment jobs"
msgstr ""
-msgid "Skip this for now"
-msgstr ""
-
msgid "Skipped"
msgstr ""
@@ -20522,6 +21187,9 @@ msgstr ""
msgid "Something went wrong while adding your award. Please try again."
msgstr ""
+msgid "Something went wrong while applying the batch of suggestions. Please try again."
+msgstr ""
+
msgid "Something went wrong while applying the suggestion. Please try again."
msgstr ""
@@ -20624,10 +21292,13 @@ msgstr ""
msgid "Something went wrong while updating a requirement."
msgstr ""
+msgid "Something went wrong while updating assignees"
+msgstr ""
+
msgid "Something went wrong while updating your list settings"
msgstr ""
-msgid "Something went wrong with your automatic subscription renewal"
+msgid "Something went wrong with your automatic subscription renewal."
msgstr ""
msgid "Something went wrong, unable to add %{project} to dashboard"
@@ -20645,6 +21316,9 @@ msgstr ""
msgid "Something went wrong, unable to search projects"
msgstr ""
+msgid "Something went wrong."
+msgstr ""
+
msgid "Something went wrong. Please try again."
msgstr ""
@@ -20828,6 +21502,9 @@ msgstr ""
msgid "Source code"
msgstr ""
+msgid "Source code (%{fileExtension})"
+msgstr ""
+
msgid "Source is not available"
msgstr ""
@@ -20912,6 +21589,9 @@ msgstr ""
msgid "Stack trace"
msgstr ""
+msgid "Stacktrace snippet"
+msgstr ""
+
msgid "Stage"
msgstr ""
@@ -21017,6 +21697,9 @@ msgstr ""
msgid "Start thread & reopen %{noteable_name}"
msgstr ""
+msgid "Start using Directed Acyclic Graphs (DAG)"
+msgstr ""
+
msgid "Start your Free Gold Trial"
msgstr ""
@@ -21479,6 +22162,21 @@ msgstr ""
msgid "SuggestedColors|Very pale orange"
msgstr ""
+msgid "Suggestion is not applicable as the suggestion was not found."
+msgstr ""
+
+msgid "Suggestions are not applicable as one or more suggestions were not found."
+msgstr ""
+
+msgid "Suggestions are not applicable as their lines cannot overlap."
+msgstr ""
+
+msgid "Suggestions must all be on the same branch."
+msgstr ""
+
+msgid "Suggestions that change line count can't be added to batches, yet."
+msgstr ""
+
msgid "Suggestions:"
msgstr ""
@@ -21521,9 +22219,6 @@ msgstr ""
msgid "Synchronization disabled"
msgstr ""
-msgid "Synchronization of container repositories is disabled."
-msgstr ""
-
msgid "System"
msgstr ""
@@ -21704,6 +22399,34 @@ msgstr ""
msgid "Terms of Service and Privacy Policy"
msgstr ""
+msgid "Terraform|%{number} Terraform report failed to generate"
+msgid_plural "Terraform|%{number} Terraform reports failed to generate"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Terraform|%{number} Terraform report was generated in your pipelines"
+msgid_plural "Terraform|%{number} Terraform reports were generated in your pipelines"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Terraform|A Terraform report failed to generate."
+msgstr ""
+
+msgid "Terraform|A Terraform report was generated in your pipelines."
+msgstr ""
+
+msgid "Terraform|Generating the report caused an error."
+msgstr ""
+
+msgid "Terraform|Reported Resource Changes: %{addNum} to add, %{changeNum} to change, %{deleteNum} to delete"
+msgstr ""
+
+msgid "Terraform|The Terraform report %{name} failed to generate."
+msgstr ""
+
+msgid "Terraform|The Terraform report %{name} was generated in your pipelines."
+msgstr ""
+
msgid "Test"
msgstr ""
@@ -21730,7 +22453,7 @@ msgstr ""
msgid "TestHooks|Ensure the project has CI pipelines."
msgstr ""
-msgid "TestHooks|Ensure the project has at least one commit."
+msgid "TestHooks|Ensure the project has deployments."
msgstr ""
msgid "TestHooks|Ensure the project has issues."
@@ -21831,7 +22554,7 @@ msgstr ""
msgid "The Prometheus server responded with \"bad request\". Please check your queries are correct and are supported in your Prometheus version. %{documentationLink}"
msgstr ""
-msgid "The URL defined on the primary node that secondary nodes should use to contact it. Defaults to URL"
+msgid "The URL defined on the primary node that secondary nodes should use to contact it."
msgstr ""
msgid "The URL to use for connecting to Elasticsearch. Use a comma-separated list to support clustering (e.g., \"http://localhost:9200, http://localhost:9201\")."
@@ -21840,6 +22563,9 @@ msgstr ""
msgid "The X509 Certificate to use when mutual TLS is required to communicate with the external authorization service. If left blank, the server certificate is still validated when accessing over HTTPS."
msgstr ""
+msgid "The above settings apply to all projects with the selected compliance framework(s)."
+msgstr ""
+
msgid "The amount of seconds after which a request to get a secondary node status will time out."
msgstr ""
@@ -22139,9 +22865,6 @@ msgstr ""
msgid "The total stage shows the time it takes between creating an issue and deploying the code to production. The data will be automatically added once you have completed the full idea to production cycle."
msgstr ""
-msgid "The unique identifier for the Geo node. Must match %{geoNodeName} if it is set in gitlab.rb, otherwise it must match %{externalUrl} with a trailing slash"
-msgstr ""
-
msgid "The update action will time out after %{number_of_minutes} minutes. For big repositories, use a clone/push combination."
msgstr ""
@@ -22166,9 +22889,6 @@ msgstr ""
msgid "The user you are trying to deactivate has been active in the past %{minimum_inactive_days} days and cannot be deactivated"
msgstr ""
-msgid "The user-facing URL of the Geo node"
-msgstr ""
-
msgid "The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6."
msgstr ""
@@ -22259,6 +22979,9 @@ msgstr ""
msgid "There was a problem fetching project branches."
msgstr ""
+msgid "There was a problem fetching project tags."
+msgstr ""
+
msgid "There was a problem fetching project users."
msgstr ""
@@ -22310,6 +23033,9 @@ msgstr ""
msgid "There was an error fetching the %{replicableType}"
msgstr ""
+msgid "There was an error fetching the Geo Settings"
+msgstr ""
+
msgid "There was an error fetching the Node's Groups"
msgstr ""
@@ -22430,6 +23156,12 @@ msgstr ""
msgid "This %{issuable} is locked. Only <strong>project members</strong> can comment."
msgstr ""
+msgid "This %{noteableTypeText} is %{confidentialLinkStart}confidential%{linkEnd} and %{lockedLinkStart}locked%{linkEnd}."
+msgstr ""
+
+msgid "This %{noteableTypeText} is locked."
+msgstr ""
+
msgid "This %{viewer} could not be displayed because %{reason}. You can %{options} instead."
msgstr ""
@@ -22448,6 +23180,9 @@ msgstr ""
msgid "This action can lead to data loss. To prevent accidental actions we ask you to confirm your intention."
msgstr ""
+msgid "This also resolves all related threads"
+msgstr ""
+
msgid "This also resolves the discussion"
msgstr ""
@@ -22538,6 +23273,9 @@ msgstr ""
msgid "This epic does not exist or you don't have sufficient permission."
msgstr ""
+msgid "This feature is currently in beta. We invite you to %{linkStart}give feedback%{linkEnd}."
+msgstr ""
+
msgid "This feature requires local storage to be enabled"
msgstr ""
@@ -22571,7 +23309,7 @@ msgstr ""
msgid "This is a Work in Progress"
msgstr ""
-msgid "This is a confidential issue."
+msgid "This is a confidential %{noteableTypeText}."
msgstr ""
msgid "This is a delayed job to run in %{remainingTime}"
@@ -22598,9 +23336,6 @@ msgstr ""
msgid "This is your current session"
msgstr ""
-msgid "This issue is %{confidentialLinkStart}confidential%{linkEnd} and %{lockedLinkStart}locked%{linkEnd}."
-msgstr ""
-
msgid "This issue is confidential"
msgstr ""
@@ -22610,9 +23345,6 @@ msgstr ""
msgid "This issue is in a child epic of the filtered epic"
msgstr ""
-msgid "This issue is locked."
-msgstr ""
-
msgid "This job depends on other jobs with expired/erased artifacts: %{invalid_dependencies}"
msgstr ""
@@ -22712,6 +23444,9 @@ msgstr ""
msgid "This license has already expired."
msgstr ""
+msgid "This link points to external content"
+msgstr ""
+
msgid "This may expose confidential information as the selected fork is in another namespace that can have other members."
msgstr ""
@@ -22739,6 +23474,9 @@ msgstr ""
msgid "This page will be removed in a future release."
msgstr ""
+msgid "This pipeline does not use the %{codeStart}needs%{codeEnd} keyword and can't be represented as a directed acyclic graph."
+msgstr ""
+
msgid "This pipeline makes use of a predefined CI/CD configuration enabled by %{strongStart}Auto DevOps.%{strongEnd}"
msgstr ""
@@ -22967,6 +23705,9 @@ msgstr ""
msgid "Time from last commit to merge"
msgstr ""
+msgid "Time in seconds"
+msgstr ""
+
msgid "Time in seconds GitLab will wait for a response from the external service. When the service does not respond in time, access will be denied."
msgstr ""
@@ -23388,9 +24129,15 @@ msgstr ""
msgid "Total artifacts size: %{total_size}"
msgstr ""
+msgid "Total cores (CPUs)"
+msgstr ""
+
msgid "Total issues"
msgstr ""
+msgid "Total memory (GB)"
+msgstr ""
+
msgid "Total test time for all commits/merges"
msgstr ""
@@ -23592,6 +24339,9 @@ msgstr ""
msgid "U2F only works with HTTPS-enabled websites. Contact your administrator for more details."
msgstr ""
+msgid "UI Development Kit"
+msgstr ""
+
msgid "URL"
msgstr ""
@@ -23664,9 +24414,6 @@ msgstr ""
msgid "Unable to load the merge request widget. Try reloading the page."
msgstr ""
-msgid "Unable to process group import file"
-msgstr ""
-
msgid "Unable to resolve"
msgstr ""
@@ -23682,6 +24429,9 @@ msgstr ""
msgid "Unable to sign you in to the group with SAML due to \"%{reason}\""
msgstr ""
+msgid "Unable to suggest a path. Please refresh and try again."
+msgstr ""
+
msgid "Unable to update label prioritization at this time"
msgstr ""
@@ -23697,6 +24447,9 @@ msgstr ""
msgid "Unarchiving the project will restore people's ability to make changes to it. The repository can be committed to, and issues, comments, and other entities can be created. %{strong_start}Once active, this project shows up in the search and on the dashboard.%{strong_end}"
msgstr ""
+msgid "Unassign from commenting user"
+msgstr ""
+
msgid "Unblock"
msgstr ""
@@ -23772,6 +24525,9 @@ msgstr ""
msgid "Unreachable"
msgstr ""
+msgid "Unrecognized cluster type"
+msgstr ""
+
msgid "Unresolve"
msgstr ""
@@ -23832,6 +24588,9 @@ msgstr ""
msgid "Unsubscribes from this %{quick_action_target}."
msgstr ""
+msgid "Unsupported todo type passed. Supported todo types are: %{todo_types}"
+msgstr ""
+
msgid "Until"
msgstr ""
@@ -23859,6 +24618,9 @@ msgstr ""
msgid "Update approval rule"
msgstr ""
+msgid "Update approvers"
+msgstr ""
+
msgid "Update failed"
msgstr ""
@@ -23868,6 +24630,9 @@ msgstr ""
msgid "Update it"
msgstr ""
+msgid "Update iteration"
+msgstr ""
+
msgid "Update now"
msgstr ""
@@ -23913,7 +24678,7 @@ msgstr ""
msgid "Updated at"
msgstr ""
-msgid "Updated to"
+msgid "Updated to %{linkStart}chart v%{linkEnd}"
msgstr ""
msgid "Updating"
@@ -23943,6 +24708,9 @@ msgstr ""
msgid "Upload CSV file"
msgstr ""
+msgid "Upload License"
+msgstr ""
+
msgid "Upload New File"
msgstr ""
@@ -24024,16 +24792,19 @@ msgstr ""
msgid "UsageQuota|Pipelines"
msgstr ""
+msgid "UsageQuota|Purchase more storage"
+msgstr ""
+
msgid "UsageQuota|Repositories"
msgstr ""
msgid "UsageQuota|Repository"
msgstr ""
-msgid "UsageQuota|Storage"
+msgid "UsageQuota|Snippets"
msgstr ""
-msgid "UsageQuota|Storage usage:"
+msgid "UsageQuota|Storage"
msgstr ""
msgid "UsageQuota|This namespace has no projects which use shared runners"
@@ -24066,6 +24837,12 @@ msgstr ""
msgid "UsageQuota|Wikis"
msgstr ""
+msgid "UsageQuota|You used: %{usage} %{limit}"
+msgstr ""
+
+msgid "UsageQuota|out of %{formattedLimit} of your namespace storage"
+msgstr ""
+
msgid "Use %{code_start}::%{code_end} to create a %{link_start}scoped label set%{link_end} (eg. %{code_start}priority::1%{code_end})"
msgstr ""
@@ -24132,6 +24909,9 @@ msgstr ""
msgid "User IDs"
msgstr ""
+msgid "User Lists can only be created and modified with %{linkStart}the API%{linkEnd}"
+msgstr ""
+
msgid "User OAuth applications"
msgstr ""
@@ -24156,6 +24936,9 @@ msgstr ""
msgid "User key was successfully removed."
msgstr ""
+msgid "User list %{name} will be removed. Are you sure?"
+msgstr ""
+
msgid "User map"
msgstr ""
@@ -24177,148 +24960,10 @@ msgstr ""
msgid "User was successfully updated."
msgstr ""
-msgid "UserOnboardingTour|%{activeTour}/%{totalTours}"
-msgstr ""
-
-msgid "UserOnboardingTour|%{completed}/%{total} steps completed"
-msgstr ""
-
-msgid "UserOnboardingTour|%{emphasisStart}Well done!%{emphasisEnd}%{lineBreak}%{lineBreak}That's it for our guided tour, congratulations for making it all the way to the end!%{lineBreak}%{lineBreak}We hope this gave you a good overview of GitLab and how it can help you. We'll now show you how to create your own project and invite your colleagues."
-msgstr ""
-
-msgid "UserOnboardingTour|Adding other members to a project is done through Project Settings. Click on %{emphasisStart}Settings%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Alright, that's it for Commits. Let's take a look at the %{emphasisStart}Branches%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Awesome! Now click on %{emphasisStart}Members%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Click on one of the %{emphasisStart}Compare%{emphasisEnd} buttons to compare a branch to master."
-msgstr ""
-
-msgid "UserOnboardingTour|Click on one of the %{emphasisStart}pipeline IDs%{emphasisEnd} to see the details of a pipeline."
-msgstr ""
-
-msgid "UserOnboardingTour|Click to open the latest commit to see its details."
-msgstr ""
-
-msgid "UserOnboardingTour|Close 'Learn GitLab'"
-msgstr ""
-
-msgid "UserOnboardingTour|Commits are shown in chronological order and can be filtered by the commit message or by the branch."
+msgid "UserList|Delete %{name}?"
msgstr ""
-msgid "UserOnboardingTour|Create a project"
-msgstr ""
-
-msgid "UserOnboardingTour|Exit 'Learn GitLab'"
-msgstr ""
-
-msgid "UserOnboardingTour|Got it"
-msgstr ""
-
-msgid "UserOnboardingTour|Great job! %{clapHands} We hope the tour was helpful and that you learned how to use GitLab.%{lineBreak}%{lineBreak}We'd love to get your feedback on this tour.%{lineBreak}%{lineBreak}%{emphasisStart}How helpful would you say this guided tour was?%{emphasisEnd}%{lineBreak}%{lineBreak}"
-msgstr ""
-
-msgid "UserOnboardingTour|Guided GitLab Tour"
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can compare the changes of this branch to another one. Changes are divided by files so that it's easier to see what was changed where."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can create a project from scratch, start with a template or import a repository from other platforms. Whatever you choose, we'll guide you through the process.%{lineBreak}%{lineBreak}Fill in your new project information and click on %{emphasisStart}Create Project%{emphasisEnd} to progress to the next step."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can see the breakdown of the pipelines: its stages and jobs in each of the stages and their status.%{lineBreak}%{lineBreak}Our CI/CD pipelines are quite complex, most of our users have fewer and simpler pipelines."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can see the current members of the project (just you at the moment) and invite new members.%{lineBreak}%{lineBreak}You can invite multiple members at once (existing GitLab users or invite by email) and you can also set their roles and permissions.%{lineBreak}%{lineBreak}Add a few members and click on %{emphasisStart}Add to project%{emphasisEnd} to complete this step."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can see what changes were made with this commit, on what branch and if there's a related merge request. The status of the pipeline will also show up if CI/CD is set up.%{lineBreak}%{lineBreak}You can also comment on the lines of code that were changed and start a discussion with your colleagues!"
-msgstr ""
-
-msgid "UserOnboardingTour|Here's an overview of branches in the %{emphasisStart}%{projectName}%{emphasisEnd} project. They're split into Active and Stale.%{lineBreak}%{lineBreak}From here, you can create a new merge request from a branch, or compare the branch to any other branch in the project. By default, it will compare it to the master branch."
-msgstr ""
-
-msgid "UserOnboardingTour|Invite colleagues"
-msgstr ""
-
-msgid "UserOnboardingTour|Issues are great for communicating and keeping track of progress in GitLab. These are all issues that are open in the %{emphasisStart}%{projectName}%{emphasisEnd}.%{lineBreak}%{lineBreak}You can help us improve GitLab by contributing work to issues that are labeled <span class=\"badge color-label accept-mr-label\">Accepting merge requests</span>.%{lineBreak}%{lineBreak}This list can be filtered by labels, milestones, assignees, authors... We'll show you how it looks when the list is filtered by a label."
-msgstr ""
-
-msgid "UserOnboardingTour|Learn GitLab"
-msgstr ""
-
-msgid "UserOnboardingTour|Let's take a closer look at a merge request. Click on the title of one."
-msgstr ""
-
-msgid "UserOnboardingTour|Let's take a closer look at all the commits. Click on %{emphasisStart}Commits%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Let's take a closer look at the repository of this project. Click on %{emphasisStart}Repository%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|No thanks"
-msgstr ""
-
-msgid "UserOnboardingTour|Ok, let's go"
-msgstr ""
-
-msgid "UserOnboardingTour|Ok, show me"
-msgstr ""
-
-msgid "UserOnboardingTour|Open one of the issues by clicking on its title."
-msgstr ""
-
-msgid "UserOnboardingTour|Restart this step"
-msgstr ""
-
-msgid "UserOnboardingTour|Skip this step"
-msgstr ""
-
-msgid "UserOnboardingTour|Sweet! Your project was created and is ready to be used.%{lineBreak}%{lineBreak}You can start adding files to the repository or clone it. One last thing we want to show you is how to invite your colleagues to your new project."
-msgstr ""
-
-msgid "UserOnboardingTour|Take a look. Here's a nifty menu for quickly creating issues, merge requests, snippets, projects and groups. Click on it and select \"New project\" from the \"GitLab\" section to get started."
-msgstr ""
-
-msgid "UserOnboardingTour|Thanks for taking the guided tour. Remember, if you want to go through it again, you can start %{emphasisStart}Learn GitLab%{emphasisEnd} in the help menu on the top right."
-msgstr ""
-
-msgid "UserOnboardingTour|Thanks for the feedback! %{thumbsUp}"
-msgstr ""
-
-msgid "UserOnboardingTour|That's it for issues. Let'st take a look at %{emphasisStart}Merge Requests%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|That's it for merge requests. Now for the final part of this guided tour - the %{emphasisStart}CI/CD%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|That's it for the Repository. Let's take a look at the %{emphasisStart}Issues%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|The structure of this page is very similar to issues. Status, description, discussion and the sidebar are all here.%{lineBreak}%{lineBreak}But take a look below the description and you'll notice that there's more information about the merge request, the CI/CD pipeline and the options for approving it.%{lineBreak}%{lineBreak}Alongside the discussion you can also see more information about commits in this merge request, the status of pipelines and review all changes that were made."
-msgstr ""
-
-msgid "UserOnboardingTour|There's a lot of information here but don't worry, we'll go through it.%{lineBreak}%{lineBreak}On the top you can see the status of the issue and when it was opened and by whom. Directly below it is the issue description and below that are other %{emphasisStart}related issues%{emphasisEnd} and %{emphasisStart}merge requests%{emphasisEnd} (if any). Then below that is the %{emphasisStart}discussion%{emphasisEnd}, that's where most of the communication happens.%{lineBreak}%{lineBreak}On the right, there's a sidebar where you can view/change the %{emphasisStart}assignee, milestone, due date, labels, weight%{emphasisEnd}, etc."
-msgstr ""
-
-msgid "UserOnboardingTour|These are all the CI/CD pipelines we have for our %{emphasisStart}%{projectName}%{emphasisEnd} project.%{lineBreak}%{lineBreak}Here you can see the status of each pipeline, for what commit it's running for, its stages and the status for them."
-msgstr ""
-
-msgid "UserOnboardingTour|These are all the issues that are available for community contributions. Let's take a closer look at one of them."
-msgstr ""
-
-msgid "UserOnboardingTour|This is an overview of all merge requests in this project. Similarly to the issues overview it can be filtered down by things like labels, milestones, authors, assignees, etc."
-msgstr ""
-
-msgid "UserOnboardingTour|This is the repository for the %{emphasisStart}%{projectName}%{emphasisEnd} project. All our code is stored here. Feel free to explore and take a closer look at folders and files.%{lineBreak}%{lineBreak}Above the file structure you can see the latest commit, who the author is and the status of the CI/CD pipeline.%{lineBreak}%{lineBreak}If you scroll down below the file structure, you'll find the Readme of this project. This is defined in the README.md file at the root of the repository."
-msgstr ""
-
-msgid "UserOnboardingTour|Welcome to the project overview of the %{emphasisStart}%{projectName}%{emphasisEnd} project. This is the project that we use to work on GitLab. At first, a project seems like a simple repository, but at GitLab, a project is so much more.%{lineBreak}%{lineBreak}You can create projects for hosting your codebase, use it as an issue tracker, collaborate on code, and continuously build, test, and deploy your app with built-in GitLab CI/CD."
+msgid "UserList|created %{timeago}"
msgstr ""
msgid "UserProfile|Activity"
@@ -24471,6 +25116,9 @@ msgstr ""
msgid "UsersSelect|Unassigned"
msgstr ""
+msgid "Using %{codeStart}needs%{codeEnd} allows jobs to run before their stage is reached, as soon as their individual dependencies are met, which speeds up your pipelines."
+msgstr ""
+
msgid "Using %{code_start}::%{code_end} denotes a %{link_start}scoped label set%{link_end}"
msgstr ""
@@ -24555,9 +25203,6 @@ msgstr ""
msgid "Versions"
msgstr ""
-msgid "Very helpful"
-msgstr ""
-
msgid "View Documentation"
msgstr ""
@@ -24706,6 +25351,9 @@ msgstr ""
msgid "VisibilityLevel|Unknown"
msgstr ""
+msgid "Visit settings page"
+msgstr ""
+
msgid "VisualReviewApp|%{stepStart}Step 1%{stepEnd}. Copy the following script:"
msgstr ""
@@ -24799,6 +25447,9 @@ msgstr ""
msgid "VulnerabilityManagement|Something went wrong while trying to delete the comment. Please try again later."
msgstr ""
+msgid "VulnerabilityManagement|Something went wrong while trying to refresh the vulnerability. Please try again later."
+msgstr ""
+
msgid "VulnerabilityManagement|Something went wrong while trying to retrieve the vulnerability history. Please try again later."
msgstr ""
@@ -24841,6 +25492,9 @@ msgstr ""
msgid "Vulnerability|Class"
msgstr ""
+msgid "Vulnerability|Crash Address"
+msgstr ""
+
msgid "Vulnerability|Description"
msgstr ""
@@ -24916,9 +25570,6 @@ msgstr ""
msgid "We couldn't reach the Prometheus server. Either the server no longer exists or the configuration details need updating."
msgstr ""
-msgid "We created a short guided tour that will help you learn the basics of GitLab and how it will help you be better at your job. It should only take a couple of minutes. You will be guided by two types of helpers, best recognized by their color."
-msgstr ""
-
msgid "We detected potential spam in the %{humanized_resource_name}. Please solve the reCAPTCHA to proceed."
msgstr ""
@@ -24952,6 +25603,9 @@ msgstr ""
msgid "We've found no vulnerabilities"
msgstr ""
+msgid "Web Application Firewall"
+msgstr ""
+
msgid "Web IDE"
msgstr ""
@@ -25012,9 +25666,6 @@ msgstr ""
msgid "Welcome to GitLab.com<br>@%{name}!"
msgstr ""
-msgid "Welcome to the Guided GitLab Tour"
-msgstr ""
-
msgid "Welcome to the guided GitLab tour"
msgstr ""
@@ -25062,9 +25713,6 @@ msgstr ""
msgid "While it's rare to have no vulnerabilities, it can happen. In any event, we ask that you please double check your settings to make sure you've set up your dashboard correctly."
msgstr ""
-msgid "White helpers give contextual information."
-msgstr ""
-
msgid "Who can be an approver?"
msgstr ""
@@ -25110,12 +25758,18 @@ msgstr ""
msgid "WikiEmptyIssueMessage|Suggest wiki improvement"
msgstr ""
+msgid "WikiEmptyIssueMessage|You must be a group member in order to add wiki pages. If you have suggestions for how to improve the wiki for this group, consider opening an issue in the %{issues_link}."
+msgstr ""
+
msgid "WikiEmptyIssueMessage|You must be a project member in order to add wiki pages. If you have suggestions for how to improve the wiki for this project, consider opening an issue in the %{issues_link}."
msgstr ""
msgid "WikiEmptyIssueMessage|issue tracker"
msgstr ""
+msgid "WikiEmpty|A wiki is where you can store all the details about your group. This can include why you've created it, its principles, how to use it, and so on."
+msgstr ""
+
msgid "WikiEmpty|A wiki is where you can store all the details about your project. This can include why you've created it, its principles, how to use it, and so on."
msgstr ""
@@ -25125,12 +25779,21 @@ msgstr ""
msgid "WikiEmpty|Suggest wiki improvement"
msgstr ""
+msgid "WikiEmpty|The wiki lets you write documentation for your group"
+msgstr ""
+
msgid "WikiEmpty|The wiki lets you write documentation for your project"
msgstr ""
+msgid "WikiEmpty|This group has no wiki pages"
+msgstr ""
+
msgid "WikiEmpty|This project has no wiki pages"
msgstr ""
+msgid "WikiEmpty|You must be a group member in order to add wiki pages."
+msgstr ""
+
msgid "WikiEmpty|You must be a project member in order to add wiki pages."
msgstr ""
@@ -25407,13 +26070,13 @@ msgstr ""
msgid "You can now export your security dashboard to a CSV report."
msgstr ""
-msgid "You can now submit a merge request to get this change into the original branch."
+msgid "You can now manage alert endpoint configuration in the Alerts section on the Operations settings page. Fields on this page have been deprecated."
msgstr ""
-msgid "You can now submit a merge request to get this change into the original project."
+msgid "You can now submit a merge request to get this change into the original branch."
msgstr ""
-msgid "You can only add files when you are on a branch"
+msgid "You can now submit a merge request to get this change into the original project."
msgstr ""
msgid "You can only edit files when you are on a branch"
@@ -25635,7 +26298,7 @@ msgstr ""
msgid "You need to upload a Google Takeout archive."
msgstr ""
-msgid "You reached %{usage_in_percent} of %{namespace_name}'s capacity (%{used_storage} of %{storage_limit})"
+msgid "You reached %{usage_in_percent} of %{namespace_name}'s storage capacity (%{used_storage} of %{storage_limit})"
msgstr ""
msgid "You tried to fork %{link_to_the_project} but it failed for the following reason:"
@@ -25938,13 +26601,13 @@ msgstr ""
msgid "Your subscription expired!"
msgstr ""
-msgid "Your subscription has been downgraded"
+msgid "Your subscription has been downgraded."
msgstr ""
-msgid "Your subscription will automatically renew in %{remaining_days}"
+msgid "Your subscription will automatically renew in %{remaining_days}."
msgstr ""
-msgid "Your subscription will expire in %{remaining_days}"
+msgid "Your subscription will expire in %{remaining_days}."
msgstr ""
msgid "Zoom meeting added"
@@ -26024,9 +26687,6 @@ msgstr ""
msgid "branch name"
msgstr ""
-msgid "build pipeline reference mismatch"
-msgstr ""
-
msgid "by"
msgstr ""
@@ -26045,6 +26705,9 @@ msgstr ""
msgid "cannot block others"
msgstr ""
+msgid "cannot contain HTML/XML tags, including any word between angle brackets (<,>)."
+msgstr ""
+
msgid "cannot include leading slash or directory traversal."
msgstr ""
@@ -26075,55 +26738,6 @@ msgstr ""
msgid "ciReport|%{remainingPackagesCount} more"
msgstr ""
-msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability"
-msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability for the source branch only"
-msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities for the source branch only"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerability"
-msgid_plural "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerabilities"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new vulnerability"
-msgid_plural "ciReport|%{reportType} %{status} detected %{newCount} new vulnerabilities"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities for the source branch only"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{fixedCount} fixed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} vulnerability for the source branch only"
-msgid_plural "ciReport|%{reportType} %{status} detected %{newCount} vulnerabilities for the source branch only"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "ciReport|%{reportType} %{status} detected no new vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected no vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected no vulnerabilities for the source branch only"
-msgstr ""
-
msgid "ciReport|%{reportType} is loading"
msgstr ""
@@ -26142,7 +26756,7 @@ msgstr ""
msgid "ciReport|All projects"
msgstr ""
-msgid "ciReport|All report types"
+msgid "ciReport|All scanner types"
msgstr ""
msgid "ciReport|All severities"
@@ -26166,6 +26780,9 @@ msgstr ""
msgid "ciReport|Container scanning detects known vulnerabilities in your docker images."
msgstr ""
+msgid "ciReport|Coverage Fuzzing"
+msgstr ""
+
msgid "ciReport|Create a merge request to implement this solution, or download and apply the patch manually."
msgstr ""
@@ -26324,6 +26941,9 @@ msgstr ""
msgid "customize"
msgstr ""
+msgid "data"
+msgstr ""
+
msgid "date must not be after 9999-12-31"
msgstr ""
@@ -26394,6 +27014,9 @@ msgstr ""
msgid "entries cannot contain HTML tags"
msgstr ""
+msgid "epic"
+msgstr ""
+
msgid "error"
msgstr ""
@@ -26415,9 +27038,6 @@ msgstr ""
msgid "expires on %{timebox_due_date}"
msgstr ""
-msgid "external_url"
-msgstr ""
-
msgid "failed"
msgstr ""
@@ -26453,9 +27073,6 @@ msgstr ""
msgid "from"
msgstr ""
-msgid "geo_node_name"
-msgstr ""
-
msgid "group"
msgstr ""
@@ -26477,9 +27094,6 @@ msgstr ""
msgid "https://your-bitbucket-server"
msgstr ""
-msgid "image"
-msgstr ""
-
msgid "image diff"
msgstr ""
@@ -26623,6 +27237,9 @@ msgstr ""
msgid "load it anyway"
msgstr ""
+msgid "loading"
+msgstr ""
+
msgid "locked by %{path_lock_user_name} %{created_at}"
msgstr ""
@@ -26751,9 +27368,6 @@ msgstr ""
msgid "mrWidget|Deployment statistics are not available currently"
msgstr ""
-msgid "mrWidget|Detect issues before deployment with a CI pipeline that continuously tests your code. We created a quick guide that will show you how to create one. Make your code more secure and more robust in just a minute."
-msgstr ""
-
msgid "mrWidget|Did not close"
msgstr ""
@@ -26916,6 +27530,9 @@ msgstr ""
msgid "mrWidget|To approve this merge request, please enter your password. This project requires all approvals to be authenticated."
msgstr ""
+msgid "mrWidget|Use %{linkStart}CI pipelines to test your code%{linkEnd}, simply add a GitLab CI configuration file to your project. It only takes a minute to make your code more secure and robust."
+msgstr ""
+
msgid "mrWidget|When this merge request is ready, remove the WIP: prefix from the title to allow it to be merged"
msgstr ""
@@ -26952,6 +27569,9 @@ msgstr ""
msgid "must be greater than start date"
msgstr ""
+msgid "must contain only valid frameworks"
+msgstr ""
+
msgid "my-awesome-group"
msgstr ""
@@ -26964,6 +27584,9 @@ msgstr ""
msgid "needs to be between 10 minutes and 1 month"
msgstr ""
+msgid "never"
+msgstr ""
+
msgid "never expires"
msgstr ""
@@ -26991,6 +27614,9 @@ msgstr ""
msgid "nounSeries|%{firstItem} and %{lastItem}"
msgstr ""
+msgid "nounSeries|%{item}"
+msgstr ""
+
msgid "nounSeries|%{item}, %{nextItem}"
msgstr ""
@@ -27127,6 +27753,9 @@ msgstr ""
msgid "revised"
msgstr ""
+msgid "satisfied"
+msgstr ""
+
msgid "score"
msgstr ""
@@ -27157,6 +27786,9 @@ msgstr ""
msgid "severity|Unknown"
msgstr ""
+msgid "should be an array of %{object_name} objects"
+msgstr ""
+
msgid "should be greater than or equal to %{access} inherited membership from group %{group_name}"
msgstr ""
@@ -27211,10 +27843,13 @@ msgstr ""
msgid "suggestPipeline|2/2: Commit your changes"
msgstr ""
-msgid "suggestPipeline|Commit the changes and your pipeline will automatically run for the first time."
+msgid "suggestPipeline|Choose %{boldStart}Code Quality%{boldEnd} to add a pipeline that tests the quality of your code."
msgstr ""
-msgid "suggestPipeline|We recommend the %{boldStart}Code Quality%{boldEnd} template, which will add a report widget to your Merge Requests. This way you’ll learn about code quality degradations much sooner. %{footerStart} Goodbye technical debt! %{footerEnd}"
+msgid "suggestPipeline|The template is ready! You can now commit it to create your first pipeline."
+msgstr ""
+
+msgid "suggestPipeline|We’re adding a GitLab CI configuration file to add a pipeline to the project. You could create it manually, but we recommend that you start with a GitLab template that works out of the box."
msgstr ""
msgid "syntax is correct"
@@ -27274,6 +27909,9 @@ msgstr ""
msgid "uses Kubernetes clusters to deploy your code!"
msgstr ""
+msgid "v%{version} published %{timeAgo}"
+msgstr ""
+
msgid "verify ownership"
msgstr ""
diff --git a/locale/uz_UZ/gitlab.po b/locale/uz_UZ/gitlab.po
index 01b1e2e76eb..17fa23eb4a2 100644
--- a/locale/uz_UZ/gitlab.po
+++ b/locale/uz_UZ/gitlab.po
@@ -2,7 +2,7 @@ msgid ""
msgstr ""
"Project-Id-Version: gitlab-ee\n"
"Report-Msgid-Bugs-To: \n"
-"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Last-Translator: \n"
"Language-Team: Uzbek\n"
"Language: uz_UZ\n"
"MIME-Version: 1.0\n"
@@ -10,9 +10,11 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
"X-Crowdin-Project: gitlab-ee\n"
+"X-Crowdin-Project-ID: 288872\n"
"X-Crowdin-Language: uz\n"
"X-Crowdin-File: /master/locale/gitlab.pot\n"
-"PO-Revision-Date: 2020-06-08 15:12\n"
+"X-Crowdin-File-ID: 6\n"
+"PO-Revision-Date: 2020-07-02 01:22\n"
msgid " %{start} to %{end}"
msgstr ""
@@ -197,6 +199,11 @@ msgid_plural "%d metrics"
msgstr[0] ""
msgstr[1] ""
+msgid "%d milestone"
+msgid_plural "%d milestones"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "%d minute"
msgid_plural "%d minutes"
msgstr[0] ""
@@ -242,11 +249,6 @@ msgid_plural "%d unresolved threads"
msgstr[0] ""
msgstr[1] ""
-msgid "%d url scanned"
-msgid_plural "%d urls scanned"
-msgstr[0] ""
-msgstr[1] ""
-
msgid "%d vulnerability"
msgid_plural "%d vulnerabilities"
msgstr[0] ""
@@ -324,9 +326,15 @@ msgstr[1] ""
msgid "%{count} related %{pluralized_subject}: %{links}"
msgstr ""
+msgid "%{dashboard_path} could not be found."
+msgstr ""
+
msgid "%{days} days until tags are automatically removed"
msgstr ""
+msgid "%{deployLinkStart}Use a template to deploy to ECS%{deployLinkEnd}, or use a docker image to %{commandsLinkStart}run AWS commands in GitLab CI/CD%{commandsLinkEnd}."
+msgstr ""
+
msgid "%{description}- Sentry event: %{errorUrl}- First seen: %{firstSeen}- Last seen: %{lastSeen} %{countLabel}: %{count}%{userCountLabel}: %{userCount}"
msgstr ""
@@ -369,12 +377,18 @@ msgstr ""
msgid "%{host} sign-in from new location"
msgstr ""
-msgid "%{icon}You are about to add %{usersTag} people to the discussion. Proceed with caution."
+msgid "%{icon}You are about to add %{usersTag} people to the discussion. They will all receive a notification."
+msgstr ""
+
+msgid "%{integrations_link_start}Integrations%{link_end} enable you to make third-party applications part of your GitLab workflow. If the available integrations don't meet your needs, consider using a %{webhooks_link_start}webhook%{link_end}."
msgstr ""
msgid "%{issuableType} will be removed! Are you sure?"
msgstr ""
+msgid "%{issuesCount} issues in this group"
+msgstr ""
+
msgid "%{issuesSize} issues"
msgstr ""
@@ -384,7 +398,7 @@ msgstr ""
msgid "%{labelStart}Class:%{labelEnd} %{class}"
msgstr ""
-msgid "%{labelStart}Confidence:%{labelEnd} %{confidence}"
+msgid "%{labelStart}Evidence:%{labelEnd} %{evidence}"
msgstr ""
msgid "%{labelStart}File:%{labelEnd} %{file}"
@@ -402,6 +416,9 @@ msgstr ""
msgid "%{labelStart}Report Type:%{labelEnd} %{reportType}"
msgstr ""
+msgid "%{labelStart}Scanner:%{labelEnd} %{scanner}"
+msgstr ""
+
msgid "%{labelStart}Severity:%{labelEnd} %{severity}"
msgstr ""
@@ -426,9 +443,6 @@ msgstr ""
msgid "%{link_start}Read more%{link_end} about role permissions"
msgstr ""
-msgid "%{link} can be used for binding events when something is happening within the project."
-msgstr ""
-
msgid "%{listToShow}, and %{awardsListLength} more."
msgstr ""
@@ -462,6 +476,9 @@ msgstr ""
msgid "%{name} found %{resultsString}"
msgstr ""
+msgid "%{name} is already being used for another emoji"
+msgstr ""
+
msgid "%{name} is scheduled for %{action}"
msgstr ""
@@ -509,6 +526,9 @@ msgstr ""
msgid "%{primary} (%{secondary})"
msgstr ""
+msgid "%{ref} cannot be added: %{error}"
+msgstr ""
+
msgid "%{releases} release"
msgid_plural "%{releases} releases"
msgstr[0] ""
@@ -517,13 +537,37 @@ msgstr[1] ""
msgid "%{remaining_approvals} left"
msgstr ""
-msgid "%{retryButtonStart}Try again%{retryButtonEnd} or %{newFileButtonStart}attach a new file%{newFileButtonEnd}"
+msgid "%{reportType} %{status} detected %{critical} critical and %{high} high severity vulnerabilities out of %{total}."
+msgstr ""
+
+msgid "%{reportType} %{status} detected %{critical} critical and %{high} high severity vulnerabilities."
+msgstr ""
+
+msgid "%{reportType} %{status} detected %{critical} critical severity vulnerabilities out of %{total}."
+msgstr ""
+
+msgid "%{reportType} %{status} detected %{critical} critical severity vulnerability."
+msgid_plural "%{reportType} %{status} detected %{critical} critical severity vulnerabilities."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{reportType} %{status} detected %{high} high severity vulnerabilities out of %{total}."
msgstr ""
-msgid "%{screenreaderOnlyStart}Keyboard shorcuts%{screenreaderOnlyEnd} Disabled"
+msgid "%{reportType} %{status} detected %{high} high severity vulnerability."
+msgid_plural "%{reportType} %{status} detected %{high} high severity vulnerabilities."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{reportType} %{status} detected %{other} vulnerability."
+msgid_plural "%{reportType} %{status} detected %{other} vulnerabilities."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{reportType} %{status} detected no new vulnerabilities."
msgstr ""
-msgid "%{screenreaderOnlyStart}Keyboard shorcuts%{screenreaderOnlyEnd} Enabled"
+msgid "%{retryButtonStart}Try again%{retryButtonEnd} or %{newFileButtonStart}attach a new file%{newFileButtonEnd}"
msgstr ""
msgid "%{service_title} %{message}."
@@ -620,6 +664,12 @@ msgstr ""
msgid "%{token}..."
msgstr ""
+msgid "%{totalCpu} (%{freeSpacePercentage}%{percentSymbol} free)"
+msgstr ""
+
+msgid "%{totalMemory} (%{freeSpacePercentage}%{percentSymbol} free)"
+msgstr ""
+
msgid "%{totalWeight} total weight"
msgstr ""
@@ -650,6 +700,12 @@ msgstr ""
msgid "%{verb} %{time_spent_value} spent time."
msgstr ""
+msgid "%{webhooks_link_start}%{webhook_type}%{link_end} enable you to send notifications to web applications in response to events in a group or project."
+msgstr ""
+
+msgid "%{webhooks_link_start}%{webhook_type}%{link_end} enable you to send notifications to web applications in response to events in a group or project. We recommend using an %{integrations_link_start}integration%{link_end} in preference to a webhook."
+msgstr ""
+
msgid "'%{level}' is not a valid visibility level"
msgstr ""
@@ -676,9 +732,6 @@ msgstr ""
msgid "(No changes)"
msgstr ""
-msgid "(Show all)"
-msgstr ""
-
msgid "(check progress)"
msgstr ""
@@ -745,6 +798,9 @@ msgstr ""
msgid "0 for unlimited"
msgstr ""
+msgid "0 for unlimited, only effective with remote storage enabled."
+msgstr ""
+
msgid "1 %{type} addition"
msgid_plural "%{count} %{type} additions"
msgstr[0] ""
@@ -892,6 +948,12 @@ msgstr ""
msgid "<code>\"johnsmith@example.com\": \"johnsmith@example.com\"</code> will add \"By <a href=\"#\">johnsmith@example.com</a>\" to all issues and comments originally created by johnsmith@example.com. By default, the email address or username is masked to ensure the user's privacy. Use this option if you want to show the full email address."
msgstr ""
+msgid "<code>Masked</code> to prevent the values from being displayed in job logs (must match certain regexp requirements)."
+msgstr ""
+
+msgid "<code>Protected</code> to expose them to protected branches or tags only."
+msgstr ""
+
msgid "<namespace / project>"
msgstr ""
@@ -919,9 +981,6 @@ msgstr ""
msgid "A .NET Core console application template, customizable for any .NET Core project"
msgstr ""
-msgid "A DAG must have two dependent jobs to be visualized on this tab."
-msgstr ""
-
msgid "A GitBook site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
msgstr ""
@@ -952,6 +1011,12 @@ msgstr ""
msgid "A deleted user"
msgstr ""
+msgid "A file has been changed."
+msgstr ""
+
+msgid "A file was not found."
+msgstr ""
+
msgid "A file with '%{file_name}' already exists in %{branch} branch"
msgstr ""
@@ -1018,7 +1083,7 @@ msgstr ""
msgid "A subscription will trigger a new pipeline on the default branch of this project when a pipeline successfully completes for a new tag on the %{default_branch_docs} of the subscribed project."
msgstr ""
-msgid "A terraform report was generated in your pipelines."
+msgid "A suggestion is not applicable."
msgstr ""
msgid "A user with write access to the source branch selected this option"
@@ -1189,6 +1254,9 @@ msgstr ""
msgid "Action to take when receiving an alert."
msgstr ""
+msgid "Actions"
+msgstr ""
+
msgid "Activate"
msgstr ""
@@ -1245,6 +1313,9 @@ msgstr ""
msgid "Add LICENSE"
msgstr ""
+msgid "Add New Node"
+msgstr ""
+
msgid "Add README"
msgstr ""
@@ -1320,6 +1391,9 @@ msgstr ""
msgid "Add approval rule"
msgstr ""
+msgid "Add approvers"
+msgstr ""
+
msgid "Add bold text"
msgstr ""
@@ -1377,6 +1451,9 @@ msgstr ""
msgid "Add strikethrough text"
msgstr ""
+msgid "Add suggestion to batch"
+msgstr ""
+
msgid "Add system hook"
msgstr ""
@@ -1476,6 +1553,9 @@ msgstr ""
msgid "Admin Note"
msgstr ""
+msgid "Admin Notifications"
+msgstr ""
+
msgid "Admin Overview"
msgstr ""
@@ -1587,6 +1667,9 @@ msgstr ""
msgid "AdminSettings|Integrations configured here will automatically apply to all projects on this instance."
msgstr ""
+msgid "AdminSettings|Moved to integrations"
+msgstr ""
+
msgid "AdminSettings|No required pipeline"
msgstr ""
@@ -1817,12 +1900,18 @@ msgid_plural "Alerts"
msgstr[0] ""
msgstr[1] ""
+msgid "AlertManagement| assign yourself"
+msgstr ""
+
msgid "AlertManagement|Acknowledged"
msgstr ""
msgid "AlertManagement|Alert"
msgstr ""
+msgid "AlertManagement|Alert assignee(s): %{assignees}"
+msgstr ""
+
msgid "AlertManagement|Alert detail"
msgstr ""
@@ -1838,9 +1927,15 @@ msgstr ""
msgid "AlertManagement|All alerts"
msgstr ""
+msgid "AlertManagement|Assign To"
+msgstr ""
+
msgid "AlertManagement|Assign status"
msgstr ""
+msgid "AlertManagement|Assignee"
+msgstr ""
+
msgid "AlertManagement|Assignees"
msgstr ""
@@ -1859,9 +1954,6 @@ msgstr ""
msgid "AlertManagement|Edit"
msgstr ""
-msgid "AlertManagement|End time"
-msgstr ""
-
msgid "AlertManagement|Events"
msgstr ""
@@ -1880,10 +1972,13 @@ msgstr ""
msgid "AlertManagement|More information"
msgstr ""
+msgid "AlertManagement|No Matching Results"
+msgstr ""
+
msgid "AlertManagement|No alert data to display."
msgstr ""
-msgid "AlertManagement|No alerts available to display. If you think you're seeing this message in error, refresh the page."
+msgid "AlertManagement|No alerts available to display. See %{linkStart}enabling alert management%{linkEnd} for more information on adding alerts to the list."
msgstr ""
msgid "AlertManagement|No alerts to display."
@@ -1892,6 +1987,9 @@ msgstr ""
msgid "AlertManagement|None"
msgstr ""
+msgid "AlertManagement|None -"
+msgstr ""
+
msgid "AlertManagement|Open"
msgstr ""
@@ -1928,9 +2026,18 @@ msgstr ""
msgid "AlertManagement|There was an error displaying the alerts. Confirm your endpoint's configuration details to ensure alerts appear."
msgstr ""
+msgid "AlertManagement|There was an error while updating the assignee(s) list. Please try again."
+msgstr ""
+
+msgid "AlertManagement|There was an error while updating the assignee(s) of the alert. Please try again."
+msgstr ""
+
msgid "AlertManagement|There was an error while updating the status of the alert. Please try again."
msgstr ""
+msgid "AlertManagement|This assignee cannot be assigned to this alert."
+msgstr ""
+
msgid "AlertManagement|Tool"
msgstr ""
@@ -2078,6 +2185,9 @@ msgstr ""
msgid "Allow users to request access (if visibility is public or internal)"
msgstr ""
+msgid "Allowed Geo IP"
+msgstr ""
+
msgid "Allowed email domain restriction only permitted for top-level groups"
msgstr ""
@@ -2111,6 +2221,9 @@ msgstr ""
msgid "Amazon Web Services"
msgstr ""
+msgid "Amazon Web Services Logo"
+msgstr ""
+
msgid "Amazon authentication is not %{link_start}correctly configured%{link_end}. Ask your GitLab administrator if you want to use this service."
msgstr ""
@@ -2168,10 +2281,16 @@ msgstr ""
msgid "An error occurred when updating the issue weight"
msgstr ""
+msgid "An error occurred while acknowledging the notification. Refresh the page and try again."
+msgstr ""
+
+msgid "An error occurred while adding approvers"
+msgstr ""
+
msgid "An error occurred while adding formatted title for epic"
msgstr ""
-msgid "An error occurred while checking group path"
+msgid "An error occurred while checking group path. Please refresh and try again."
msgstr ""
msgid "An error occurred while committing your changes."
@@ -2315,7 +2434,7 @@ msgstr ""
msgid "An error occurred while loading milestones"
msgstr ""
-msgid "An error occurred while loading terraform report"
+msgid "An error occurred while loading project creation UI"
msgstr ""
msgid "An error occurred while loading the data. Please try again."
@@ -2369,6 +2488,9 @@ msgstr ""
msgid "An error occurred while rendering preview broadcast message"
msgstr ""
+msgid "An error occurred while rendering the editor"
+msgstr ""
+
msgid "An error occurred while reordering issues."
msgstr ""
@@ -2465,7 +2587,7 @@ msgstr ""
msgid "Analyze your dependencies for known vulnerabilities."
msgstr ""
-msgid "Analyze your source code and git history for secrets"
+msgid "Analyze your source code and git history for secrets."
msgstr ""
msgid "Analyze your source code for known vulnerabilities."
@@ -2579,12 +2701,18 @@ msgstr ""
msgid "Apply suggestion"
msgstr ""
+msgid "Apply suggestions"
+msgstr ""
+
msgid "Apply template"
msgstr ""
msgid "Apply this approval rule to any branch or a specific protected branch."
msgstr ""
+msgid "Applying"
+msgstr ""
+
msgid "Applying a template will replace the existing issue description. Any changes you have made will be lost."
msgstr ""
@@ -2597,12 +2725,18 @@ msgstr ""
msgid "Applying multiple commands"
msgstr ""
-msgid "Applying suggestion"
+msgid "Applying suggestion..."
+msgstr ""
+
+msgid "Applying suggestions..."
msgstr ""
msgid "Approval rules"
msgstr ""
+msgid "Approval rules reset to project defaults"
+msgstr ""
+
msgid "ApprovalRuleRemove|%d member"
msgid_plural "ApprovalRuleRemove|%d members"
msgstr[0] ""
@@ -2656,6 +2790,9 @@ msgstr ""
msgid "Approved"
msgstr ""
+msgid "Approved MRs"
+msgstr ""
+
msgid "Approved by: "
msgstr ""
@@ -2869,6 +3006,9 @@ msgstr ""
msgid "Assign"
msgstr ""
+msgid "Assign Iteration"
+msgstr ""
+
msgid "Assign custom color like #FF0000"
msgstr ""
@@ -2887,6 +3027,9 @@ msgstr ""
msgid "Assign to"
msgstr ""
+msgid "Assign to commenting user"
+msgstr ""
+
msgid "Assign yourself to these issues"
msgstr ""
@@ -2913,6 +3056,9 @@ msgid_plural "%d Assignees"
msgstr[0] ""
msgstr[1] ""
+msgid "Assignee has no permissions"
+msgstr ""
+
msgid "Assignee lists not available with your current license"
msgstr ""
@@ -2922,6 +3068,9 @@ msgstr ""
msgid "Assignee(s)"
msgstr ""
+msgid "Assignees"
+msgstr ""
+
msgid "Assigns %{assignee_users_sentence}."
msgstr ""
@@ -2957,6 +3106,9 @@ msgstr ""
msgid "Audit Events is a way to keep track of important events that happened in GitLab."
msgstr ""
+msgid "Audit Log"
+msgstr ""
+
msgid "AuditEvents|(removed)"
msgstr ""
@@ -2993,6 +3145,9 @@ msgstr ""
msgid "AuditLogs|IP Address"
msgstr ""
+msgid "AuditLogs|Member Events"
+msgstr ""
+
msgid "AuditLogs|No matching %{type} found."
msgstr ""
@@ -3275,9 +3430,6 @@ msgstr ""
msgid "Badges|e.g. %{exampleUrl}"
msgstr ""
-msgid "Badge|New"
-msgstr ""
-
msgid "Balsamiq file could not be loaded."
msgstr ""
@@ -3326,6 +3478,12 @@ msgstr ""
msgid "Below you will find all the groups that are public."
msgstr ""
+msgid "Beta"
+msgstr ""
+
+msgid "Bi-weekly code coverage"
+msgstr ""
+
msgid "Billing"
msgstr ""
@@ -3386,9 +3544,15 @@ msgstr ""
msgid "Bitbucket Server Import"
msgstr ""
+msgid "Bitbucket Server import"
+msgstr ""
+
msgid "Bitbucket import"
msgstr ""
+msgid "Blame"
+msgstr ""
+
msgid "Blocked"
msgstr ""
@@ -3401,9 +3565,6 @@ msgstr ""
msgid "Blog"
msgstr ""
-msgid "Blue helpers indicate an action to be taken."
-msgstr ""
-
msgid "Board name"
msgstr ""
@@ -3602,6 +3763,9 @@ msgstr ""
msgid "Broadcast Message was successfully updated."
msgstr ""
+msgid "Broadcast Messages"
+msgstr ""
+
msgid "Browse Directory"
msgstr ""
@@ -3656,6 +3820,9 @@ msgstr ""
msgid "By %{user_name}"
msgstr ""
+msgid "By URL"
+msgstr ""
+
msgid "By default GitLab sends emails in HTML and plain text formats so mail clients can choose what format to use. Disable this option if you only want to send emails in plain text format."
msgstr ""
@@ -3761,6 +3928,9 @@ msgstr ""
msgid "Can override approvers and approvals required per merge request"
msgstr ""
+msgid "Can't apply as this line has changed or the suggestion already matches its content."
+msgstr ""
+
msgid "Can't create snippet: %{err}"
msgstr ""
@@ -3947,9 +4117,6 @@ msgstr ""
msgid "Changes are still tracked. Useful for cluster/index migrations."
msgstr ""
-msgid "Changes are unknown"
-msgstr ""
-
msgid "Changes suppressed. Click to show."
msgstr ""
@@ -4256,15 +4423,9 @@ msgstr ""
msgid "Choose what content you want to see on a group’s overview page"
msgstr ""
-msgid "Choose which groups you wish to synchronize to this secondary node"
-msgstr ""
-
msgid "Choose which repositories you want to connect and run CI/CD pipelines."
msgstr ""
-msgid "Choose which shards you wish to synchronize to this secondary node"
-msgstr ""
-
msgid "Choose your framework"
msgstr ""
@@ -4562,9 +4723,15 @@ msgstr ""
msgid "Cluster does not exist"
msgstr ""
+msgid "Cluster is required for Stages::ClusterEndpointInserter"
+msgstr ""
+
msgid "Cluster level"
msgstr ""
+msgid "Cluster type must be specificed for Stages::ClusterEndpointInserter"
+msgstr ""
+
msgid "ClusterIntegration| %{custom_domain_start}More information%{custom_domain_end}."
msgstr ""
@@ -4676,7 +4843,7 @@ msgstr ""
msgid "ClusterIntegration|Cert-Manager"
msgstr ""
-msgid "ClusterIntegration|Cert-Manager is a native Kubernetes certificate management controller that helps with issuing certificates. Installing Cert-Manager on your cluster will issue a certificate by %{letsEncrypt} and ensure that certificates are valid and up-to-date."
+msgid "ClusterIntegration|Cert-Manager is a native Kubernetes certificate management controller that helps with issuing certificates. Installing Cert-Manager on your cluster will issue a certificate by %{linkStart}Let's Encrypt%{linkEnd} and ensure that certificates are valid and up-to-date."
msgstr ""
msgid "ClusterIntegration|Certificate Authority bundle (PEM format)"
@@ -4703,9 +4870,6 @@ msgstr ""
msgid "ClusterIntegration|Clear the local cache of namespace and service accounts. This is necessary if your integration has become out of sync. The cache is repopulated during the next CI job that requires namespace and service accounts."
msgstr ""
-msgid "ClusterIntegration|Cloud Run"
-msgstr ""
-
msgid "ClusterIntegration|Cluster being created"
msgstr ""
@@ -4790,7 +4954,7 @@ msgstr ""
msgid "ClusterIntegration|Crossplane"
msgstr ""
-msgid "ClusterIntegration|Crossplane enables declarative provisioning of managed services from your cloud of choice using %{codeStart}kubectl%{codeEnd} or %{gitlabIntegrationLink}. Crossplane runs inside your Kubernetes cluster and supports secure connectivity and secrets management between app containers and the cloud services they depend on."
+msgid "ClusterIntegration|Crossplane enables declarative provisioning of managed services from your cloud of choice using %{codeStart}kubectl%{codeEnd} or %{linkStart}GitLab Integration%{linkEnd}. Crossplane runs inside your Kubernetes cluster and supports secure connectivity and secrets management between app containers and the cloud services they depend on."
msgstr ""
msgid "ClusterIntegration|Deletes all GitLab resources attached to this cluster during removal"
@@ -4874,9 +5038,6 @@ msgstr ""
msgid "ClusterIntegration|GitLab-managed cluster"
msgstr ""
-msgid "ClusterIntegration|Gitlab Integration"
-msgstr ""
-
msgid "ClusterIntegration|Global default"
msgstr ""
@@ -4922,7 +5083,7 @@ msgstr ""
msgid "ClusterIntegration|Ingress gives you a way to route requests to services based on the request host or path, centralizing a number of services into a single entrypoint."
msgstr ""
-msgid "ClusterIntegration|Installing Ingress may incur additional costs. Learn more about %{pricingLink}."
+msgid "ClusterIntegration|Installing Ingress may incur additional costs. Learn more about %{linkStart}pricing%{linkEnd}."
msgstr ""
msgid "ClusterIntegration|Instance cluster"
@@ -4937,7 +5098,7 @@ msgstr ""
msgid "ClusterIntegration|Issuer Email"
msgstr ""
-msgid "ClusterIntegration|Issuers represent a certificate authority. You must provide an email address for your Issuer. "
+msgid "ClusterIntegration|Issuers represent a certificate authority. You must provide an email address for your Issuer."
msgstr ""
msgid "ClusterIntegration|Jupyter Hostname"
@@ -5009,9 +5170,6 @@ msgstr ""
msgid "ClusterIntegration|Learn more about instance Kubernetes clusters"
msgstr ""
-msgid "ClusterIntegration|Let's Encrypt"
-msgstr ""
-
msgid "ClusterIntegration|Loading IAM Roles"
msgstr ""
@@ -5123,7 +5281,7 @@ msgstr ""
msgid "ClusterIntegration|Prometheus"
msgstr ""
-msgid "ClusterIntegration|Prometheus is an open-source monitoring system with %{gitlabIntegrationLink} to monitor deployed applications."
+msgid "ClusterIntegration|Prometheus is an open-source monitoring system with %{linkStart}GitLab Integration%{linkEnd} to monitor deployed applications."
msgstr ""
msgid "ClusterIntegration|Provider details"
@@ -5279,10 +5437,10 @@ msgstr ""
msgid "ClusterIntegration|Select zone to choose machine type"
msgstr ""
-msgid "ClusterIntegration|Send Cilium Logs"
+msgid "ClusterIntegration|Send Container Network Policies Logs"
msgstr ""
-msgid "ClusterIntegration|Send ModSecurity Logs"
+msgid "ClusterIntegration|Send Web Application Firewall Logs"
msgstr ""
msgid "ClusterIntegration|Service Token"
@@ -5450,15 +5608,12 @@ msgstr ""
msgid "ClusterIntegration|documentation"
msgstr ""
-msgid "ClusterIntegration|installed via %{installed_via}"
+msgid "ClusterIntegration|installed via %{linkStart}Cloud Run%{linkEnd}"
msgstr ""
msgid "ClusterIntegration|meets the requirements"
msgstr ""
-msgid "ClusterIntegration|pricing"
-msgstr ""
-
msgid "ClusterIntegration|sign up"
msgstr ""
@@ -5495,6 +5650,15 @@ msgstr ""
msgid "Code"
msgstr ""
+msgid "Code Coverage: %{coveragePercentage}%{percentSymbol}"
+msgstr ""
+
+msgid "Code Coverage| Empty code coverage data"
+msgstr ""
+
+msgid "Code Coverage|Couldn't fetch the code coverage data"
+msgstr ""
+
msgid "Code Owners"
msgstr ""
@@ -5549,7 +5713,7 @@ msgstr ""
msgid "Collapse approvers"
msgstr ""
-msgid "Collapse child epics"
+msgid "Collapse milestones"
msgstr ""
msgid "Collapse replies"
@@ -5567,6 +5731,9 @@ msgstr ""
msgid "Coming soon"
msgstr ""
+msgid "Comma-separated, e.g. '1.1.1.1, 2.2.2.0/24'"
+msgstr ""
+
msgid "Command"
msgstr ""
@@ -5737,6 +5904,12 @@ msgstr ""
msgid "Compliance framework (optional)"
msgstr ""
+msgid "Compliance frameworks"
+msgstr ""
+
+msgid "ComplianceDashboard|created by:"
+msgstr ""
+
msgid "ComplianceFramework|GDPR"
msgstr ""
@@ -5875,6 +6048,9 @@ msgstr ""
msgid "Connection timed out"
msgstr ""
+msgid "Connection timeout"
+msgstr ""
+
msgid "Contact sales to upgrade"
msgstr ""
@@ -5930,9 +6106,6 @@ msgstr ""
msgid "ContainerRegistry|CLI Commands"
msgstr ""
-msgid "ContainerRegistry|Compressed Size"
-msgstr ""
-
msgid "ContainerRegistry|Container Registry"
msgstr ""
@@ -5948,6 +6121,9 @@ msgstr ""
msgid "ContainerRegistry|Copy push command"
msgstr ""
+msgid "ContainerRegistry|Delete selected"
+msgstr ""
+
msgid "ContainerRegistry|Docker connection error"
msgstr ""
@@ -5981,16 +6157,16 @@ msgstr ""
msgid "ContainerRegistry|If you are not already logged in, you need to authenticate to the Container Registry by using your GitLab username and password. If you have %{twofaDocLinkStart}Two-Factor Authentication%{twofaDocLinkEnd} enabled, use a %{personalAccessTokensDocLinkStart}Personal Access Token%{personalAccessTokensDocLinkEnd} instead of a password."
msgstr ""
-msgid "ContainerRegistry|Image ID"
+msgid "ContainerRegistry|Image ID: %{imageId}"
msgstr ""
msgid "ContainerRegistry|Image Repositories"
msgstr ""
-msgid "ContainerRegistry|Keep and protect the images that matter most."
+msgid "ContainerRegistry|Image tags"
msgstr ""
-msgid "ContainerRegistry|Last Updated"
+msgid "ContainerRegistry|Keep and protect the images that matter most."
msgstr ""
msgid "ContainerRegistry|Login"
@@ -6005,6 +6181,9 @@ msgstr ""
msgid "ContainerRegistry|Please contact your administrator."
msgstr ""
+msgid "ContainerRegistry|Published %{timeInfo}"
+msgstr ""
+
msgid "ContainerRegistry|Push an image"
msgstr ""
@@ -6020,9 +6199,6 @@ msgstr ""
msgid "ContainerRegistry|Remove repository"
msgstr ""
-msgid "ContainerRegistry|Remove selected tags"
-msgstr ""
-
msgid "ContainerRegistry|Remove tag"
msgid_plural "ContainerRegistry|Remove tags"
msgstr[0] ""
@@ -6052,9 +6228,6 @@ msgstr ""
msgid "ContainerRegistry|Sorry, your filter produced no results."
msgstr ""
-msgid "ContainerRegistry|Tag"
-msgstr ""
-
msgid "ContainerRegistry|Tag expiration policy"
msgstr ""
@@ -6199,21 +6372,6 @@ msgstr ""
msgid "Control the display of third party offers."
msgstr ""
-msgid "Control the maximum concurrency of LFS/attachment backfill for this secondary node"
-msgstr ""
-
-msgid "Control the maximum concurrency of container repository operations for this Geo node"
-msgstr ""
-
-msgid "Control the maximum concurrency of repository backfill for this secondary node"
-msgstr ""
-
-msgid "Control the maximum concurrency of verification operations for this Geo node"
-msgstr ""
-
-msgid "Control the minimum interval in days that a repository should be reverified for this primary node"
-msgstr ""
-
msgid "Cookie domain"
msgstr ""
@@ -6355,6 +6513,9 @@ msgstr ""
msgid "Could not find design."
msgstr ""
+msgid "Could not find iteration"
+msgstr ""
+
msgid "Could not remove the trigger."
msgstr ""
@@ -6391,6 +6552,12 @@ msgstr ""
msgid "Coverage"
msgstr ""
+msgid "Coverage Fuzzing"
+msgstr ""
+
+msgid "Crash State"
+msgstr ""
+
msgid "Create"
msgstr ""
@@ -6439,6 +6606,9 @@ msgstr ""
msgid "Create a personal access token on your account to pull or push via %{protocol}."
msgstr ""
+msgid "Create a project pre-populated with the necessary files to get you started quickly."
+msgstr ""
+
msgid "Create an account using:"
msgstr ""
@@ -6607,6 +6777,9 @@ msgstr ""
msgid "Creates branch '%{branch_name}' and a merge request to resolve this issue."
msgstr ""
+msgid "Creating"
+msgstr ""
+
msgid "Creating epic"
msgstr ""
@@ -6658,7 +6831,10 @@ msgstr ""
msgid "Current vulnerabilities count"
msgstr ""
-msgid "CurrentUser|Buy CI minutes"
+msgid "CurrentUser|Buy Pipeline minutes"
+msgstr ""
+
+msgid "CurrentUser|One of your groups is running out"
msgstr ""
msgid "CurrentUser|Profile"
@@ -6673,6 +6849,9 @@ msgstr ""
msgid "CurrentUser|Upgrade"
msgstr ""
+msgid "Custom Attributes"
+msgstr ""
+
msgid "Custom CI configuration path"
msgstr ""
@@ -6936,12 +7115,18 @@ msgstr ""
msgid "DAG"
msgstr ""
+msgid "DAG visualization requires at least 3 dependent jobs."
+msgstr ""
+
msgid "DNS"
msgstr ""
msgid "Dashboard"
msgstr ""
+msgid "Dashboard uid not found"
+msgstr ""
+
msgid "DashboardProjects|All"
msgstr ""
@@ -6966,6 +7151,9 @@ msgstr ""
msgid "Data is still calculating..."
msgstr ""
+msgid "Datasource name not found"
+msgstr ""
+
msgid "Date"
msgstr ""
@@ -7146,6 +7334,9 @@ msgstr ""
msgid "Delete this attachment"
msgstr ""
+msgid "Delete user list"
+msgstr ""
+
msgid "Delete variable"
msgstr ""
@@ -7462,6 +7653,9 @@ msgstr ""
msgid "Deploying to"
msgstr ""
+msgid "Deploying to AWS is easy with GitLab"
+msgstr ""
+
msgid "Deployment Frequency"
msgstr ""
@@ -7824,6 +8018,9 @@ msgstr ""
msgid "Dismissed on pipeline %{pipelineLink} at %{projectLink}"
msgstr ""
+msgid "Display alerts from all your monitoring tools directly within GitLab."
+msgstr ""
+
msgid "Display name"
msgstr ""
@@ -7881,9 +8078,6 @@ msgstr ""
msgid "Don't show again"
msgstr ""
-msgid "Don't worry, you can access this tour by clicking on the help icon in the top right corner and choose <strong>Learn GitLab</strong>."
-msgstr ""
-
msgid "Done"
msgstr ""
@@ -8010,6 +8204,9 @@ msgstr ""
msgid "Edit Release"
msgstr ""
+msgid "Edit Slack integration"
+msgstr ""
+
msgid "Edit Snippet"
msgstr ""
@@ -8049,6 +8246,9 @@ msgstr ""
msgid "Edit issues"
msgstr ""
+msgid "Edit iteration"
+msgstr ""
+
msgid "Edit public deploy key"
msgstr ""
@@ -8097,6 +8297,9 @@ msgstr ""
msgid "Email"
msgstr ""
+msgid "Email Notification"
+msgstr ""
+
msgid "Email address"
msgstr ""
@@ -8109,6 +8312,9 @@ msgstr ""
msgid "Email not verified. Please verify your email in Salesforce."
msgstr ""
+msgid "Email notification for unknown sign-ins"
+msgstr ""
+
msgid "Email patch"
msgstr ""
@@ -8340,6 +8546,9 @@ msgstr ""
msgid "Enforce DNS rebinding attack protection"
msgstr ""
+msgid "Enforce personal access token expiration"
+msgstr ""
+
msgid "Ensure connectivity is available from the GitLab server to the Prometheus server"
msgstr ""
@@ -8409,6 +8618,9 @@ msgstr ""
msgid "Enter the name of your application, and we'll return a unique %{type}."
msgstr ""
+msgid "Enter weights for storages for new repositories."
+msgstr ""
+
msgid "Enter your password to approve"
msgstr ""
@@ -8418,15 +8630,27 @@ msgstr ""
msgid "Environment does not have deployments"
msgstr ""
+msgid "Environment is required for Stages::MetricEndpointInserter"
+msgstr ""
+
+msgid "Environment is required for Stages::VariableEndpointInserter"
+msgstr ""
+
msgid "Environment scope"
msgstr ""
+msgid "Environment variables are applied to all project environments in this instance via the Runner. You can use environment variables for passwords, secret keys, etc. Make variables available to the running application by prepending the variable key with <code>K8S_SECRET_</code>. You can set variables to be:"
+msgstr ""
+
msgid "Environment variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. Additionally, they can be masked so they are hidden in job logs, though they must match certain regexp requirements to do so. You can use environment variables for passwords, secret keys, or whatever you want."
msgstr ""
msgid "Environment variables are configured by your administrator to be %{link_start}protected%{link_end} by default"
msgstr ""
+msgid "Environment variables on this GitLab instance are configured to be %{link_start}protected%{link_end} by default"
+msgstr ""
+
msgid "Environment:"
msgstr ""
@@ -8958,6 +9182,9 @@ msgstr ""
msgid "EventFilterBy|Filter by comments"
msgstr ""
+msgid "EventFilterBy|Filter by designs"
+msgstr ""
+
msgid "EventFilterBy|Filter by epic events"
msgstr ""
@@ -9090,15 +9317,15 @@ msgstr ""
msgid "Expand approvers"
msgstr ""
-msgid "Expand child epics"
-msgstr ""
-
msgid "Expand down"
msgstr ""
msgid "Expand dropdown"
msgstr ""
+msgid "Expand milestones"
+msgstr ""
+
msgid "Expand sidebar"
msgstr ""
@@ -9114,6 +9341,9 @@ msgstr ""
msgid "Expiration date"
msgstr ""
+msgid "Expiration not enforced"
+msgstr ""
+
msgid "Expiration policy for the Container Registry is a perfect solution for keeping the Registry space down while still enjoying the full power of GitLab CI/CD."
msgstr ""
@@ -9234,6 +9464,9 @@ msgstr ""
msgid "Failed Jobs"
msgstr ""
+msgid "Failed on"
+msgstr ""
+
msgid "Failed to add a Zoom meeting"
msgstr ""
@@ -9294,13 +9527,19 @@ msgstr ""
msgid "Failed to install."
msgstr ""
+msgid "Failed to load assignees. Please try again."
+msgstr ""
+
+msgid "Failed to load authors. Please try again."
+msgstr ""
+
msgid "Failed to load emoji list."
msgstr ""
msgid "Failed to load error details from Sentry."
msgstr ""
-msgid "Failed to load errors from Sentry. Error message: %{errorMessage}"
+msgid "Failed to load errors from Sentry."
msgstr ""
msgid "Failed to load group activity metrics. Please try again."
@@ -9381,6 +9620,9 @@ msgstr ""
msgid "Failed to set due date because the date format is invalid."
msgstr ""
+msgid "Failed to set iteration on this issue. Please try again."
+msgstr ""
+
msgid "Failed to signing using smartcard authentication"
msgstr ""
@@ -9603,13 +9845,10 @@ msgstr ""
msgid "FeatureFlags|Target environments"
msgstr ""
-msgid "FeatureFlags|There are no active feature flags"
-msgstr ""
-
-msgid "FeatureFlags|There are no inactive feature flags"
+msgid "FeatureFlags|There was an error fetching the feature flags."
msgstr ""
-msgid "FeatureFlags|There was an error fetching the feature flags."
+msgid "FeatureFlags|There was an error retrieving user lists"
msgstr ""
msgid "FeatureFlags|Try again in a few moments or contact your support team."
@@ -9621,9 +9860,18 @@ msgstr ""
msgid "FeatureFlag|Delete strategy"
msgstr ""
+msgid "FeatureFlag|List"
+msgstr ""
+
msgid "FeatureFlag|Percentage"
msgstr ""
+msgid "FeatureFlag|Select a user list"
+msgstr ""
+
+msgid "FeatureFlag|There are no configured user lists"
+msgstr ""
+
msgid "FeatureFlag|Type"
msgstr ""
@@ -9780,6 +10028,12 @@ msgstr ""
msgid "Filter..."
msgstr ""
+msgid "Find File"
+msgstr ""
+
+msgid "Find bugs in your code with coverage-guided fuzzing"
+msgstr ""
+
msgid "Find by path"
msgstr ""
@@ -10044,6 +10298,9 @@ msgstr ""
msgid "Geo allows you to replicate your GitLab instance to other geographical locations."
msgstr ""
+msgid "Geo nodes are paused using a command run on the node"
+msgstr ""
+
msgid "GeoNodeStatusEvent|%{timeAgoStr} (%{pendingEvents} events)"
msgstr ""
@@ -10128,6 +10385,9 @@ msgstr ""
msgid "GeoNodes|Not checksummed"
msgstr ""
+msgid "GeoNodes|Package files"
+msgstr ""
+
msgid "GeoNodes|Pausing replication stops the sync process. Are you sure?"
msgstr ""
@@ -10146,6 +10406,9 @@ msgstr ""
msgid "GeoNodes|Replication slots"
msgstr ""
+msgid "GeoNodes|Replication status"
+msgstr ""
+
msgid "GeoNodes|Repositories"
msgstr ""
@@ -10356,6 +10619,9 @@ msgstr ""
msgid "Geo|Synchronization failed - %{error}"
msgstr ""
+msgid "Geo|Synchronization of %{itemTitle} is disabled."
+msgstr ""
+
msgid "Geo|The database is currently %{db_lag} behind the primary node."
msgstr ""
@@ -10668,6 +10934,9 @@ msgstr ""
msgid "Go to %{link_to_google_takeout}."
msgstr ""
+msgid "Go to <strong>Issues</strong> > <strong>Boards</strong> to access your personalized learning issue board."
+msgstr ""
+
msgid "Go to Pipelines"
msgstr ""
@@ -10767,9 +11036,6 @@ msgstr ""
msgid "Go to your snippets"
msgstr ""
-msgid "Golden Tanuki"
-msgstr ""
-
msgid "Google Cloud Platform"
msgstr ""
@@ -10791,6 +11057,9 @@ msgstr ""
msgid "Grafana URL"
msgstr ""
+msgid "Grafana response contains invalid json"
+msgstr ""
+
msgid "GrafanaIntegration|API Token"
msgstr ""
@@ -10839,9 +11108,6 @@ msgstr ""
msgid "Group %{group_name} was successfully created."
msgstr ""
-msgid "Group '%{group_name}' is being imported."
-msgstr ""
-
msgid "Group Audit Events"
msgstr ""
@@ -10878,9 +11144,6 @@ msgstr ""
msgid "Group by:"
msgstr ""
-msgid "Group could not be imported: %{errors}"
-msgstr ""
-
msgid "Group description"
msgstr ""
@@ -10908,9 +11171,15 @@ msgstr ""
msgid "Group has not been marked for deletion"
msgstr ""
+msgid "Group import could not be scheduled"
+msgstr ""
+
msgid "Group info:"
msgstr ""
+msgid "Group is required when cluster_type is :group"
+msgstr ""
+
msgid "Group maintainers can register group runners in the %{link}"
msgstr ""
@@ -10974,6 +11243,24 @@ msgstr ""
msgid "GroupActivyMetrics|Recent activity (last 90 days)"
msgstr ""
+msgid "GroupImport|Failed to import group."
+msgstr ""
+
+msgid "GroupImport|Group '%{group_name}' is being imported."
+msgstr ""
+
+msgid "GroupImport|Group could not be imported: %{errors}"
+msgstr ""
+
+msgid "GroupImport|Please wait while we import the group for you. Refresh at will."
+msgstr ""
+
+msgid "GroupImport|The group was successfully imported."
+msgstr ""
+
+msgid "GroupImport|Unable to process group import file"
+msgstr ""
+
msgid "GroupRoadmap|%{dateWord} – No end date"
msgstr ""
@@ -11241,6 +11528,9 @@ msgstr ""
msgid "Groups (%{groups})"
msgstr ""
+msgid "Groups and projects"
+msgstr ""
+
msgid "Groups can also be nested by creating %{subgroup_docs_link_start}subgroups%{subgroup_docs_link_end}."
msgstr ""
@@ -11286,6 +11576,33 @@ msgstr ""
msgid "GroupsEmptyState|You can manage your group member’s permissions and access to each project in the group."
msgstr ""
+msgid "GroupsNew|Contact an administrator to enable options for importing your group."
+msgstr ""
+
+msgid "GroupsNew|Create"
+msgstr ""
+
+msgid "GroupsNew|Create group"
+msgstr ""
+
+msgid "GroupsNew|GitLab group export"
+msgstr ""
+
+msgid "GroupsNew|Import"
+msgstr ""
+
+msgid "GroupsNew|Import group"
+msgstr ""
+
+msgid "GroupsNew|My Awesome Group"
+msgstr ""
+
+msgid "GroupsNew|No import options available"
+msgstr ""
+
+msgid "GroupsNew|To copy a GitLab group between installations, navigate to the group settings page for the original installation, generate an export file, and upload it here."
+msgstr ""
+
msgid "GroupsTree|Are you sure you want to leave the \"%{fullName}\" group?"
msgstr ""
@@ -11388,6 +11705,9 @@ msgstr ""
msgid "Helps reduce request volume for protected paths"
msgstr ""
+msgid "Here are all your projects in your group, including the one you just created. To start, let’s take a look at your personalized learning project which will help you learn about GitLab at your own pace."
+msgstr ""
+
msgid "Here you will find recent merge request activity"
msgstr ""
@@ -11402,6 +11722,9 @@ msgid_plural "Hide charts"
msgstr[0] ""
msgstr[1] ""
+msgid "Hide details"
+msgstr ""
+
msgid "Hide file browser"
msgstr ""
@@ -11411,6 +11734,9 @@ msgstr ""
msgid "Hide host keys manual input"
msgstr ""
+msgid "Hide list"
+msgstr ""
+
msgid "Hide marketing-related entries from help"
msgstr ""
@@ -11431,9 +11757,6 @@ msgstr[1] ""
msgid "Hide values"
msgstr ""
-msgid "Hiding all labels"
-msgstr ""
-
msgid "High or unknown vulnerabilities present"
msgstr ""
@@ -11518,6 +11841,9 @@ msgstr ""
msgid "ID:"
msgstr ""
+msgid "IDE"
+msgstr ""
+
msgid "IDE|Allow live previews of JavaScript projects in the Web IDE using CodeSandbox Live Preview."
msgstr ""
@@ -11608,10 +11934,10 @@ msgstr ""
msgid "If enabled"
msgstr ""
-msgid "If enabled, access to projects will be validated on an external service using their classification label."
+msgid "If enabled, GitLab will handle Object Storage replication using Geo. %{linkStart}More information%{linkEnd}"
msgstr ""
-msgid "If enabled, and if object storage is enabled, GitLab will handle Object Storage replication using Geo"
+msgid "If enabled, access to projects will be validated on an external service using their classification label."
msgstr ""
msgid "If there is no previous license or if the previous license has expired, some GitLab functionality will be blocked until a new, valid license is uploaded."
@@ -11973,6 +12299,9 @@ msgid_plural "Instances"
msgstr[0] ""
msgstr[1] ""
+msgid "Instance Configuration"
+msgstr ""
+
msgid "Instance Statistics visibility"
msgstr ""
@@ -11994,9 +12323,6 @@ msgstr ""
msgid "Integrations"
msgstr ""
-msgid "Integrations allow you to integrate GitLab with other applications"
-msgstr ""
-
msgid "Integrations|All details"
msgstr ""
@@ -12108,6 +12434,9 @@ msgstr ""
msgid "Invalid query"
msgstr ""
+msgid "Invalid repository bundle for snippet with id %{snippet_id}"
+msgstr ""
+
msgid "Invalid repository path"
msgstr ""
@@ -12126,6 +12455,9 @@ msgstr ""
msgid "Invalid two-factor code."
msgstr ""
+msgid "Invalid yaml"
+msgstr ""
+
msgid "Invitation"
msgstr ""
@@ -12345,15 +12677,24 @@ msgstr ""
msgid "It seems like the Dependency Scanning job ran successfully, but no dependencies have been detected in your project."
msgstr ""
+msgid "It seems that there is currently no available data for code coverage"
+msgstr ""
+
msgid "It's you"
msgstr ""
+msgid "Iteration"
+msgstr ""
+
msgid "Iteration changed to"
msgstr ""
msgid "Iteration removed"
msgstr ""
+msgid "Iteration updated"
+msgstr ""
+
msgid "Iterations"
msgstr ""
@@ -12384,12 +12725,18 @@ msgstr ""
msgid "January"
msgstr ""
+msgid "Jira Issues"
+msgstr ""
+
msgid "Jira import is already running."
msgstr ""
msgid "Jira integration not configured."
msgstr ""
+msgid "Jira project key is not configured"
+msgstr ""
+
msgid "Jira project: %{importProject}"
msgstr ""
@@ -12570,6 +12917,9 @@ msgstr ""
msgid "Keep divergent refs"
msgstr ""
+msgid "Kerberos access denied"
+msgstr ""
+
msgid "Key"
msgstr ""
@@ -12579,10 +12929,13 @@ msgstr ""
msgid "Key: %{key}"
msgstr ""
-msgid "Keyboard Shortcuts"
+msgid "Keyboard shortcuts"
msgstr ""
-msgid "Keyboard shortcuts"
+msgid "Keys"
+msgstr ""
+
+msgid "Ki"
msgstr ""
msgid "Kubernetes"
@@ -12624,6 +12977,9 @@ msgstr ""
msgid "LDAP"
msgstr ""
+msgid "LDAP Synchronization"
+msgstr ""
+
msgid "LDAP settings"
msgstr ""
@@ -12848,12 +13204,18 @@ msgstr ""
msgid "Learn more about custom project templates"
msgstr ""
+msgid "Learn more about deploying to AWS"
+msgstr ""
+
msgid "Learn more about deploying to a cluster"
msgstr ""
msgid "Learn more about group-level project templates"
msgstr ""
+msgid "Learn more about job dependencies"
+msgstr ""
+
msgid "Learn more about signing commits"
msgstr ""
@@ -12905,9 +13267,21 @@ msgstr ""
msgid "License History"
msgstr ""
+msgid "License ID:"
+msgstr ""
+
+msgid "License URL"
+msgstr ""
+
msgid "License-Check"
msgstr ""
+msgid "LicenseCompliance|%{docLinkStart}License Approvals%{docLinkEnd} are active"
+msgstr ""
+
+msgid "LicenseCompliance|%{docLinkStart}License Approvals%{docLinkEnd} are inactive"
+msgstr ""
+
msgid "LicenseCompliance|Add a license"
msgstr ""
@@ -12929,9 +13303,15 @@ msgstr ""
msgid "LicenseCompliance|Deny"
msgstr ""
+msgid "LicenseCompliance|Learn more about %{linkStart}License Approvals%{linkEnd}"
+msgstr ""
+
msgid "LicenseCompliance|License"
msgstr ""
+msgid "LicenseCompliance|License Approvals"
+msgstr ""
+
msgid "LicenseCompliance|License Compliance detected %d license and policy violation for the source branch only; approval required"
msgid_plural "LicenseCompliance|License Compliance detected %d licenses and policy violations for the source branch only; approval required"
msgstr[0] ""
@@ -13152,6 +13532,9 @@ msgstr ""
msgid "List your Bitbucket Server repositories"
msgstr ""
+msgid "Lists"
+msgstr ""
+
msgid "Live preview"
msgstr ""
@@ -13335,6 +13718,9 @@ msgstr ""
msgid "Manage project labels"
msgstr ""
+msgid "Manage storage usage"
+msgstr ""
+
msgid "Manage two-factor authentication"
msgstr ""
@@ -13350,6 +13736,9 @@ msgstr ""
msgid "Manifest file import"
msgstr ""
+msgid "Manifest import"
+msgstr ""
+
msgid "Manual job"
msgstr ""
@@ -13497,6 +13886,12 @@ msgstr ""
msgid "Maximum field length"
msgstr ""
+msgid "Maximum file size is 2MB. Please select a smaller file."
+msgstr ""
+
+msgid "Maximum import size (MB)"
+msgstr ""
+
msgid "Maximum job timeout"
msgstr ""
@@ -13536,6 +13931,9 @@ msgstr ""
msgid "Maximum size of Elasticsearch bulk indexing requests."
msgstr ""
+msgid "Maximum size of import files."
+msgstr ""
+
msgid "Maximum size of individual attachments in comments."
msgstr ""
@@ -13587,6 +13985,9 @@ msgstr ""
msgid "Merge (when the pipeline succeeds)"
msgstr ""
+msgid "Merge Conflicts"
+msgstr ""
+
msgid "Merge Request"
msgstr ""
@@ -13713,6 +14114,9 @@ msgstr ""
msgid "MergeRequests|Squash task canceled: another squash is already in progress."
msgstr ""
+msgid "MergeRequests|This project does not allow squashing commits when merge requests are accepted."
+msgstr ""
+
msgid "MergeRequests|Thread stays resolved"
msgstr ""
@@ -13866,6 +14270,12 @@ msgstr ""
msgid "MetricsSettings|Add a button to the metrics dashboard linking directly to your existing external dashboard."
msgstr ""
+msgid "MetricsSettings|Choose whether to display dashboard metrics in UTC or the user's local timezone."
+msgstr ""
+
+msgid "MetricsSettings|Dashboard timezone"
+msgstr ""
+
msgid "MetricsSettings|External dashboard URL"
msgstr ""
@@ -13875,6 +14285,12 @@ msgstr ""
msgid "MetricsSettings|Metrics Dashboard"
msgstr ""
+msgid "MetricsSettings|UTC (Coordinated Universal Time)"
+msgstr ""
+
+msgid "MetricsSettings|User's local timezone"
+msgstr ""
+
msgid "Metrics|Add metric"
msgstr ""
@@ -13890,6 +14306,9 @@ msgstr ""
msgid "Metrics|Create metric"
msgstr ""
+msgid "Metrics|Current"
+msgstr ""
+
msgid "Metrics|Delete metric"
msgstr ""
@@ -13940,6 +14359,9 @@ msgstr ""
msgid "Metrics|Max"
msgstr ""
+msgid "Metrics|Min"
+msgstr ""
+
msgid "Metrics|Must be a valid PromQL query."
msgstr ""
@@ -13955,6 +14377,9 @@ msgstr ""
msgid "Metrics|Refresh dashboard"
msgstr ""
+msgid "Metrics|Select a value"
+msgstr ""
+
msgid "Metrics|Star dashboard"
msgstr ""
@@ -13973,12 +14398,18 @@ msgstr ""
msgid "Metrics|There was an error getting annotations information."
msgstr ""
+msgid "Metrics|There was an error getting dashboard validation warnings information."
+msgstr ""
+
msgid "Metrics|There was an error getting deployment information."
msgstr ""
msgid "Metrics|There was an error getting environments information."
msgstr ""
+msgid "Metrics|There was an error getting options for variable \"%{name}\"."
+msgstr ""
+
msgid "Metrics|There was an error trying to validate your query"
msgstr ""
@@ -14021,6 +14452,9 @@ msgstr ""
msgid "Metrics|You're about to permanently delete this metric. This cannot be undone."
msgstr ""
+msgid "Metrics|Your dashboard schema is invalid. Edit the dashboard to correct the YAML schema."
+msgstr ""
+
msgid "Metrics|e.g. HTTP requests"
msgstr ""
@@ -14036,12 +14470,18 @@ msgstr ""
msgid "Metrics|e.g. req/sec"
msgstr ""
+msgid "Mi"
+msgstr ""
+
msgid "Microsoft Azure"
msgstr ""
msgid "Middleman project with Static Site Editor support"
msgstr ""
+msgid "Migrate your data from an external source like GitHub, Bitbucket, or another instance of GitLab."
+msgstr ""
+
msgid "Migrated %{success_count}/%{total_count} files."
msgstr ""
@@ -14176,6 +14616,9 @@ msgstr ""
msgid "Minimum capacity to be available before we schedule more mirrors preemptively."
msgstr ""
+msgid "Minimum interval in days"
+msgstr ""
+
msgid "Minimum length is %{minimum_password_length} characters"
msgstr ""
@@ -14362,6 +14805,12 @@ msgstr ""
msgid "Multiple uploaders found: %{uploader_types}"
msgstr ""
+msgid "Must match with the %{codeStart}external_url%{codeEnd} in %{codeStart}/etc/gitlab/gitlab.rb%{codeEnd}."
+msgstr ""
+
+msgid "Must match with the %{codeStart}geo_node_name%{codeEnd} in %{codeStart}/etc/gitlab/gitlab.rb%{codeEnd}. %{linkStart}More information%{linkEnd}"
+msgstr ""
+
msgid "My Awesome Group"
msgstr ""
@@ -14476,10 +14925,16 @@ msgstr ""
msgid "New Application"
msgstr ""
+msgid "New Branch"
+msgstr ""
+
+msgid "New Deploy Key"
+msgstr ""
+
msgid "New Environment"
msgstr ""
-msgid "New Geo Node"
+msgid "New File"
msgstr ""
msgid "New Group"
@@ -14496,15 +14951,15 @@ msgid_plural "New Issues"
msgstr[0] ""
msgstr[1] ""
-msgid "New Iteration"
-msgstr ""
-
msgid "New Jira import"
msgstr ""
msgid "New Label"
msgstr ""
+msgid "New Merge Request"
+msgstr ""
+
msgid "New Milestone"
msgstr ""
@@ -14523,6 +14978,9 @@ msgstr ""
msgid "New Snippet"
msgstr ""
+msgid "New User"
+msgstr ""
+
msgid "New branch"
msgstr ""
@@ -14565,6 +15023,9 @@ msgstr ""
msgid "New issue title"
msgstr ""
+msgid "New iteration"
+msgstr ""
+
msgid "New iteration created"
msgstr ""
@@ -14742,6 +15203,12 @@ msgstr ""
msgid "No grouping"
msgstr ""
+msgid "No iteration"
+msgstr ""
+
+msgid "No iterations to show"
+msgstr ""
+
msgid "No job log"
msgstr ""
@@ -14784,6 +15251,9 @@ msgstr ""
msgid "No other labels with such name or description"
msgstr ""
+msgid "No panels matching properties %{opts}"
+msgstr ""
+
msgid "No parent group"
msgstr ""
@@ -14817,6 +15287,9 @@ msgstr ""
msgid "No schedules"
msgstr ""
+msgid "No stack trace for this error"
+msgstr ""
+
msgid "No starrers matched your search"
msgstr ""
@@ -14829,7 +15302,7 @@ msgstr ""
msgid "No test coverage"
msgstr ""
-msgid "No thanks, don't show this again"
+msgid "No thanks"
msgstr ""
msgid "No vulnerabilities present"
@@ -14844,9 +15317,6 @@ msgstr ""
msgid "No, directly import the existing email addresses and usernames."
msgstr ""
-msgid "No, not interested right now"
-msgstr ""
-
msgid "No. of commits"
msgstr ""
@@ -14892,9 +15362,6 @@ msgstr ""
msgid "Not found."
msgstr ""
-msgid "Not helpful"
-msgstr ""
-
msgid "Not now"
msgstr ""
@@ -15051,6 +15518,9 @@ msgstr ""
msgid "Notifications on"
msgstr ""
+msgid "Notify users by email when sign-in location is not recognized"
+msgstr ""
+
msgid "Nov"
msgstr ""
@@ -15117,9 +15587,6 @@ msgstr ""
msgid "Oh no!"
msgstr ""
-msgid "Ok let's go"
-msgstr ""
-
msgid "Oldest first"
msgstr ""
@@ -15132,16 +15599,49 @@ msgstr ""
msgid "On track"
msgstr ""
+msgid "OnDemandScans|Attached branch"
+msgstr ""
+
+msgid "OnDemandScans|Attached branch is where the scan job runs."
+msgstr ""
+
+msgid "OnDemandScans|Could not run the scan. Please try again."
+msgstr ""
+
msgid "OnDemandScans|Create new DAST scan"
msgstr ""
+msgid "OnDemandScans|DAST will scan the target URL and any discovered sub URLs."
+msgstr ""
+
+msgid "OnDemandScans|New on-demand DAST scan"
+msgstr ""
+
msgid "OnDemandScans|On-demand Scans"
msgstr ""
+msgid "OnDemandScans|On-demand scans run outside the DevOps cycle and find vulnerabilities in your projects. %{learnMoreLinkStart}Learn more%{learnMoreLinkEnd}"
+msgstr ""
+
+msgid "OnDemandScans|Only a passive scan can be performed on demand."
+msgstr ""
+
+msgid "OnDemandScans|Passive DAST Scan"
+msgstr ""
+
+msgid "OnDemandScans|Please enter a valid URL format, ex: http://www.example.com/home"
+msgstr ""
+
+msgid "OnDemandScans|Run this scan"
+msgstr ""
+
+msgid "OnDemandScans|Scan mode"
+msgstr ""
+
msgid "OnDemandScans|Schedule or run scans immediately against target sites. Currently available on-demand scan type: DAST. %{helpLinkStart}More information%{helpLinkEnd}"
msgstr ""
-msgid "Onboarding"
+msgid "OnDemandScans|Target URL"
msgstr ""
msgid "Once imported, repositories can be mirrored over SSH. Read more %{link_start}here%{link_end}."
@@ -15167,6 +15667,9 @@ msgstr ""
msgid "One or more of you personal access tokens were revoked"
msgstr ""
+msgid "One or more of your %{provider} projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
+msgstr ""
+
msgid "One or more of your Bitbucket projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
msgstr ""
@@ -15668,6 +16171,9 @@ msgstr ""
msgid "Parent epic is not present."
msgstr ""
+msgid "Parsing error for param :embed_json. %{message}"
+msgstr ""
+
msgid "Part of merge request changes"
msgstr ""
@@ -15680,6 +16186,9 @@ msgstr ""
msgid "Passed"
msgstr ""
+msgid "Passed on"
+msgstr ""
+
msgid "Password"
msgstr ""
@@ -15764,6 +16273,9 @@ msgstr ""
msgid "Perform common operations on GitLab project"
msgstr ""
+msgid "Performance and resource management"
+msgstr ""
+
msgid "Performance optimization"
msgstr ""
@@ -15971,7 +16483,7 @@ msgstr ""
msgid "Pipelines|Something went wrong while cleaning runners cache."
msgstr ""
-msgid "Pipelines|There are currently no %{scope} pipelines."
+msgid "Pipelines|There are currently no finished pipelines."
msgstr ""
msgid "Pipelines|There are currently no pipelines."
@@ -16082,6 +16594,9 @@ msgstr ""
msgid "Pipeline|Stop pipeline #%{pipelineId}?"
msgstr ""
+msgid "Pipeline|Tag name"
+msgstr ""
+
msgid "Pipeline|Trigger author"
msgstr ""
@@ -16163,12 +16678,18 @@ msgstr ""
msgid "Please check your email (%{email}) to verify that you own this address and unlock the power of CI/CD. Didn't receive it? %{resend_link}. Wrong email address? %{update_link}."
msgstr ""
+msgid "Please choose a file"
+msgstr ""
+
msgid "Please choose a group URL with no special characters."
msgstr ""
msgid "Please complete your profile with email address"
msgstr ""
+msgid "Please convert %{linkStart}them to Git%{linkEnd}, and go through the %{linkToImportFlow} again."
+msgstr ""
+
msgid "Please convert them to %{link_to_git}, and go through the %{link_to_import_flow} again."
msgstr ""
@@ -16226,6 +16747,9 @@ msgstr ""
msgid "Please provide a valid email address."
msgstr ""
+msgid "Please provide attributes to update"
+msgstr ""
+
msgid "Please refer to <a href=\"%{docs_url}\">%{docs_url}</a>"
msgstr ""
@@ -16961,6 +17485,9 @@ msgstr ""
msgid "Project has too many %{label_for_message} to search"
msgstr ""
+msgid "Project is required when cluster_type is :project"
+msgstr ""
+
msgid "Project members"
msgstr ""
@@ -17078,7 +17605,40 @@ msgstr ""
msgid "ProjectService|Comment"
msgstr ""
-msgid "ProjectService|Comment will be posted on each event"
+msgid "ProjectService|Event will be triggered by a push to the repository"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a commit is created/updated"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a confidential issue is created/updated/closed"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a deployment finishes"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a merge request is created/updated/merged"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a new tag is pushed to the repository"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a new, unique alert is recorded"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a pipeline status changes"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a wiki page is created/updated"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when an issue is created/updated/closed"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when someone adds a comment"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when someone adds a comment on a confidential issue"
msgstr ""
msgid "ProjectService|Perform common operations on GitLab project: %{project_name}"
@@ -17129,6 +17689,9 @@ msgstr ""
msgid "ProjectSettings|Enable 'Delete source branch' option by default"
msgstr ""
+msgid "ProjectSettings|Enable merge trains and pipelines for merged results"
+msgstr ""
+
msgid "ProjectSettings|Every merge creates a merge commit"
msgstr ""
@@ -17159,7 +17722,7 @@ msgstr ""
msgid "ProjectSettings|Forks"
msgstr ""
-msgid "ProjectSettings|Git Large File Storage"
+msgid "ProjectSettings|Git Large File Storage (LFS)"
msgstr ""
msgid "ProjectSettings|Internal"
@@ -17195,9 +17758,6 @@ msgstr ""
msgid "ProjectSettings|Merge options"
msgstr ""
-msgid "ProjectSettings|Merge pipelines will try to validate the post-merge result prior to merging"
-msgstr ""
-
msgid "ProjectSettings|Merge requests"
msgstr ""
@@ -17225,6 +17785,9 @@ msgstr ""
msgid "ProjectSettings|Pipelines"
msgstr ""
+msgid "ProjectSettings|Pipelines for merge requests must be enabled in the CI/CD configuration file, or pipelines could be unresolvable or dropped"
+msgstr ""
+
msgid "ProjectSettings|Pipelines must succeed"
msgstr ""
@@ -17252,6 +17815,9 @@ msgstr ""
msgid "ProjectSettings|Show link to create/view merge request when pushing from the command line"
msgstr ""
+msgid "ProjectSettings|Skipped pipelines are considered successful"
+msgstr ""
+
msgid "ProjectSettings|Snippets"
msgstr ""
@@ -17267,6 +17833,9 @@ msgstr ""
msgid "ProjectSettings|These checks must pass before merge requests can be merged"
msgstr ""
+msgid "ProjectSettings|This introduces the risk of merging changes that will not pass the pipeline."
+msgstr ""
+
msgid "ProjectSettings|This setting is applied on the server level and can be overridden by an admin."
msgstr ""
@@ -17444,15 +18013,27 @@ msgstr ""
msgid "ProjectsNew|Blank project"
msgstr ""
+msgid "ProjectsNew|Connect your external repository to GitLab CI/CD."
+msgstr ""
+
msgid "ProjectsNew|Contact an administrator to enable options for importing your project."
msgstr ""
msgid "ProjectsNew|Create"
msgstr ""
+msgid "ProjectsNew|Create a blank project to house your files, plan your work, and collaborate on code, among other things."
+msgstr ""
+
+msgid "ProjectsNew|Create blank project"
+msgstr ""
+
msgid "ProjectsNew|Create from template"
msgstr ""
+msgid "ProjectsNew|Create new project"
+msgstr ""
+
msgid "ProjectsNew|Creating project & repository."
msgstr ""
@@ -17477,6 +18058,9 @@ msgstr ""
msgid "ProjectsNew|Project description %{tag_start}(optional)%{tag_end}"
msgstr ""
+msgid "ProjectsNew|Run CI/CD for external repository"
+msgstr ""
+
msgid "ProjectsNew|Template"
msgstr ""
@@ -17738,6 +18322,9 @@ msgstr ""
msgid "Protected Branch"
msgstr ""
+msgid "Protected Branches"
+msgstr ""
+
msgid "Protected Environment"
msgstr ""
@@ -17750,6 +18337,9 @@ msgstr ""
msgid "Protected Tag"
msgstr ""
+msgid "Protected Tags"
+msgstr ""
+
msgid "Protected branches"
msgstr ""
@@ -17849,6 +18439,9 @@ msgstr ""
msgid "Provider"
msgstr ""
+msgid "Proxy support for this API is not available currently"
+msgstr ""
+
msgid "Pseudonymizer data collection"
msgstr ""
@@ -17873,6 +18466,9 @@ msgstr ""
msgid "Publish to status page"
msgstr ""
+msgid "Published on status page"
+msgstr ""
+
msgid "Publishes this issue to the associated status page."
msgstr ""
@@ -18047,6 +18643,9 @@ msgstr ""
msgid "Recipe"
msgstr ""
+msgid "Reconfigure"
+msgstr ""
+
msgid "Recover hidden stage"
msgstr ""
@@ -18133,6 +18732,9 @@ msgstr ""
msgid "Registration|Your profile"
msgstr ""
+msgid "Regulate approvals by authors/committers, based on compliance frameworks. Can be changed only at the instance level."
+msgstr ""
+
msgid "Rejected (closed)"
msgstr ""
@@ -18183,15 +18785,24 @@ msgstr ""
msgid "ReleaseAssetLinkType|Image"
msgstr ""
+msgid "ReleaseAssetLinkType|Images"
+msgstr ""
+
msgid "ReleaseAssetLinkType|Other"
msgstr ""
msgid "ReleaseAssetLinkType|Package"
msgstr ""
+msgid "ReleaseAssetLinkType|Packages"
+msgstr ""
+
msgid "ReleaseAssetLinkType|Runbook"
msgstr ""
+msgid "ReleaseAssetLinkType|Runbooks"
+msgstr ""
+
msgid "Releases"
msgstr ""
@@ -18204,6 +18815,9 @@ msgstr ""
msgid "Releases documentation"
msgstr ""
+msgid "Releases|New Release"
+msgstr ""
+
msgid "Release|Something went wrong while getting the release details"
msgstr ""
@@ -18276,6 +18890,9 @@ msgstr ""
msgid "Remove fork relationship"
msgstr ""
+msgid "Remove from batch"
+msgstr ""
+
msgid "Remove from board"
msgstr ""
@@ -18465,6 +19082,12 @@ msgstr ""
msgid "Replication"
msgstr ""
+msgid "Replication enabled"
+msgstr ""
+
+msgid "Replication paused"
+msgstr ""
+
msgid "Reply by email"
msgstr ""
@@ -18492,15 +19115,15 @@ msgstr ""
msgid "Reported %{timeAgo} by %{reportedBy}"
msgstr ""
-msgid "Reported Resource Changes: %{addNum} to add, %{changeNum} to change, %{deleteNum} to delete"
-msgstr ""
-
msgid "Reporter"
msgstr ""
msgid "Reporting"
msgstr ""
+msgid "Reports"
+msgstr ""
+
msgid "Reports|%{combinedString} and %{resolvedString}"
msgstr ""
@@ -18539,6 +19162,9 @@ msgstr ""
msgid "Reports|Failure"
msgstr ""
+msgid "Reports|Identifier"
+msgstr ""
+
msgid "Reports|Metrics reports are loading"
msgstr ""
@@ -18551,6 +19177,9 @@ msgstr ""
msgid "Reports|Metrics reports failed loading results"
msgstr ""
+msgid "Reports|Scanner"
+msgstr ""
+
msgid "Reports|Severity"
msgstr ""
@@ -18599,15 +19228,33 @@ msgstr ""
msgid "Repository cleanup has started. You will receive an email once the cleanup operation is complete."
msgstr ""
+msgid "Repository files count over the limit"
+msgstr ""
+
+msgid "Repository has an invalid default branch name."
+msgstr ""
+
+msgid "Repository has more than one branch."
+msgstr ""
+
msgid "Repository has no locks."
msgstr ""
+msgid "Repository has tags."
+msgstr ""
+
msgid "Repository maintenance"
msgstr ""
msgid "Repository mirroring"
msgstr ""
+msgid "Repository must contain at least 1 file."
+msgstr ""
+
+msgid "Repository size is above the limit."
+msgstr ""
+
msgid "Repository static objects"
msgstr ""
@@ -18617,7 +19264,7 @@ msgstr ""
msgid "Repository sync capacity"
msgstr ""
-msgid "Repository: %{counter_repositories} / Wikis: %{counter_wikis} / Build Artifacts: %{counter_build_artifacts} / LFS: %{counter_lfs_objects}"
+msgid "Repository: %{counter_repositories} / Wikis: %{counter_wikis} / Build Artifacts: %{counter_build_artifacts} / LFS: %{counter_lfs_objects} / Snippets: %{counter_snippets}"
msgstr ""
msgid "RepositorySettingsAccessLevel|Select"
@@ -18629,6 +19276,9 @@ msgstr ""
msgid "Request Headers"
msgstr ""
+msgid "Request details"
+msgstr ""
+
msgid "Request parameter %{param} is missing."
msgstr ""
@@ -18735,6 +19385,9 @@ msgstr ""
msgid "Reset template"
msgstr ""
+msgid "Reset to project defaults"
+msgstr ""
+
msgid "Resetting the authorization key for this project will require updating the authorization key in every alert source it is enabled in."
msgstr ""
@@ -18831,9 +19484,6 @@ msgstr ""
msgid "Resume"
msgstr ""
-msgid "Resume replication"
-msgstr ""
-
msgid "Resync"
msgstr ""
@@ -19456,6 +20106,9 @@ msgstr ""
msgid "SecurityReports|False positive"
msgstr ""
+msgid "SecurityReports|Group Security Dashboard"
+msgstr ""
+
msgid "SecurityReports|Hide dismissed"
msgstr ""
@@ -19510,10 +20163,13 @@ msgstr ""
msgid "SecurityReports|Remove project from dashboard"
msgstr ""
-msgid "SecurityReports|Report type"
+msgid "SecurityReports|Return to dashboard"
msgstr ""
-msgid "SecurityReports|Return to dashboard"
+msgid "SecurityReports|Scan details"
+msgstr ""
+
+msgid "SecurityReports|Scanner type"
msgstr ""
msgid "SecurityReports|Security Dashboard"
@@ -19570,6 +20226,9 @@ msgstr ""
msgid "SecurityReports|There was an error while generating the report."
msgstr ""
+msgid "SecurityReports|Unable to add %{invalidProjectsMessage}"
+msgstr ""
+
msgid "SecurityReports|Unable to add %{invalidProjects}"
msgstr ""
@@ -19684,6 +20343,9 @@ msgstr ""
msgid "Select due date"
msgstr ""
+msgid "Select file"
+msgstr ""
+
msgid "Select group or project"
msgstr ""
@@ -19747,9 +20409,6 @@ msgstr ""
msgid "Select the branch you want to set as the default for this project. All merge requests and commits will automatically be made against this branch unless you specify a different one."
msgstr ""
-msgid "Select the configured storage available for new repositories to be placed on."
-msgstr ""
-
msgid "Select the custom project template source group."
msgstr ""
@@ -19942,12 +20601,6 @@ msgstr ""
msgid "Service Desk is enabled but not yet active"
msgstr ""
-msgid "Service Desk is off"
-msgstr ""
-
-msgid "Service Desk is on"
-msgstr ""
-
msgid "Service Templates"
msgstr ""
@@ -20035,6 +20688,15 @@ msgstr ""
msgid "Set the milestone to %{milestone_reference}."
msgstr ""
+msgid "Set the number of concurrent requests this secondary node will make to the primary node while backfilling."
+msgstr ""
+
+msgid "Set the synchronization and verification capacity for the secondary node."
+msgstr ""
+
+msgid "Set the timeout in seconds to send a secondary node status to the primary and IPs allowed for the secondary nodes."
+msgstr ""
+
msgid "Set time estimate"
msgstr ""
@@ -20074,6 +20736,9 @@ msgstr ""
msgid "Set weight to %{weight}."
msgstr ""
+msgid "Set what should be replicated by choosing specific projects or groups by the secondary node."
+msgstr ""
+
msgid "SetPasswordToCloneLink|set a password"
msgstr ""
@@ -20200,6 +20865,9 @@ msgstr ""
msgid "Show complete raw log"
msgstr ""
+msgid "Show details"
+msgstr ""
+
msgid "Show file browser"
msgstr ""
@@ -20209,10 +20877,13 @@ msgstr ""
msgid "Show latest version"
msgstr ""
+msgid "Show list"
+msgstr ""
+
msgid "Show me everything"
msgstr ""
-msgid "Show me how"
+msgid "Show me how to add a pipeline"
msgstr ""
msgid "Show me more advanced stuff"
@@ -20261,9 +20932,6 @@ msgstr ""
msgid "Showing all issues"
msgstr ""
-msgid "Showing all labels"
-msgstr ""
-
msgid "Showing last %{size} of log -"
msgstr ""
@@ -20375,9 +21043,6 @@ msgstr ""
msgid "Skip outdated deployment jobs"
msgstr ""
-msgid "Skip this for now"
-msgstr ""
-
msgid "Skipped"
msgstr ""
@@ -20522,6 +21187,9 @@ msgstr ""
msgid "Something went wrong while adding your award. Please try again."
msgstr ""
+msgid "Something went wrong while applying the batch of suggestions. Please try again."
+msgstr ""
+
msgid "Something went wrong while applying the suggestion. Please try again."
msgstr ""
@@ -20624,10 +21292,13 @@ msgstr ""
msgid "Something went wrong while updating a requirement."
msgstr ""
+msgid "Something went wrong while updating assignees"
+msgstr ""
+
msgid "Something went wrong while updating your list settings"
msgstr ""
-msgid "Something went wrong with your automatic subscription renewal"
+msgid "Something went wrong with your automatic subscription renewal."
msgstr ""
msgid "Something went wrong, unable to add %{project} to dashboard"
@@ -20645,6 +21316,9 @@ msgstr ""
msgid "Something went wrong, unable to search projects"
msgstr ""
+msgid "Something went wrong."
+msgstr ""
+
msgid "Something went wrong. Please try again."
msgstr ""
@@ -20828,6 +21502,9 @@ msgstr ""
msgid "Source code"
msgstr ""
+msgid "Source code (%{fileExtension})"
+msgstr ""
+
msgid "Source is not available"
msgstr ""
@@ -20912,6 +21589,9 @@ msgstr ""
msgid "Stack trace"
msgstr ""
+msgid "Stacktrace snippet"
+msgstr ""
+
msgid "Stage"
msgstr ""
@@ -21017,6 +21697,9 @@ msgstr ""
msgid "Start thread & reopen %{noteable_name}"
msgstr ""
+msgid "Start using Directed Acyclic Graphs (DAG)"
+msgstr ""
+
msgid "Start your Free Gold Trial"
msgstr ""
@@ -21479,6 +22162,21 @@ msgstr ""
msgid "SuggestedColors|Very pale orange"
msgstr ""
+msgid "Suggestion is not applicable as the suggestion was not found."
+msgstr ""
+
+msgid "Suggestions are not applicable as one or more suggestions were not found."
+msgstr ""
+
+msgid "Suggestions are not applicable as their lines cannot overlap."
+msgstr ""
+
+msgid "Suggestions must all be on the same branch."
+msgstr ""
+
+msgid "Suggestions that change line count can't be added to batches, yet."
+msgstr ""
+
msgid "Suggestions:"
msgstr ""
@@ -21521,9 +22219,6 @@ msgstr ""
msgid "Synchronization disabled"
msgstr ""
-msgid "Synchronization of container repositories is disabled."
-msgstr ""
-
msgid "System"
msgstr ""
@@ -21704,6 +22399,34 @@ msgstr ""
msgid "Terms of Service and Privacy Policy"
msgstr ""
+msgid "Terraform|%{number} Terraform report failed to generate"
+msgid_plural "Terraform|%{number} Terraform reports failed to generate"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Terraform|%{number} Terraform report was generated in your pipelines"
+msgid_plural "Terraform|%{number} Terraform reports were generated in your pipelines"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Terraform|A Terraform report failed to generate."
+msgstr ""
+
+msgid "Terraform|A Terraform report was generated in your pipelines."
+msgstr ""
+
+msgid "Terraform|Generating the report caused an error."
+msgstr ""
+
+msgid "Terraform|Reported Resource Changes: %{addNum} to add, %{changeNum} to change, %{deleteNum} to delete"
+msgstr ""
+
+msgid "Terraform|The Terraform report %{name} failed to generate."
+msgstr ""
+
+msgid "Terraform|The Terraform report %{name} was generated in your pipelines."
+msgstr ""
+
msgid "Test"
msgstr ""
@@ -21730,7 +22453,7 @@ msgstr ""
msgid "TestHooks|Ensure the project has CI pipelines."
msgstr ""
-msgid "TestHooks|Ensure the project has at least one commit."
+msgid "TestHooks|Ensure the project has deployments."
msgstr ""
msgid "TestHooks|Ensure the project has issues."
@@ -21831,7 +22554,7 @@ msgstr ""
msgid "The Prometheus server responded with \"bad request\". Please check your queries are correct and are supported in your Prometheus version. %{documentationLink}"
msgstr ""
-msgid "The URL defined on the primary node that secondary nodes should use to contact it. Defaults to URL"
+msgid "The URL defined on the primary node that secondary nodes should use to contact it."
msgstr ""
msgid "The URL to use for connecting to Elasticsearch. Use a comma-separated list to support clustering (e.g., \"http://localhost:9200, http://localhost:9201\")."
@@ -21840,6 +22563,9 @@ msgstr ""
msgid "The X509 Certificate to use when mutual TLS is required to communicate with the external authorization service. If left blank, the server certificate is still validated when accessing over HTTPS."
msgstr ""
+msgid "The above settings apply to all projects with the selected compliance framework(s)."
+msgstr ""
+
msgid "The amount of seconds after which a request to get a secondary node status will time out."
msgstr ""
@@ -22139,9 +22865,6 @@ msgstr ""
msgid "The total stage shows the time it takes between creating an issue and deploying the code to production. The data will be automatically added once you have completed the full idea to production cycle."
msgstr ""
-msgid "The unique identifier for the Geo node. Must match %{geoNodeName} if it is set in gitlab.rb, otherwise it must match %{externalUrl} with a trailing slash"
-msgstr ""
-
msgid "The update action will time out after %{number_of_minutes} minutes. For big repositories, use a clone/push combination."
msgstr ""
@@ -22166,9 +22889,6 @@ msgstr ""
msgid "The user you are trying to deactivate has been active in the past %{minimum_inactive_days} days and cannot be deactivated"
msgstr ""
-msgid "The user-facing URL of the Geo node"
-msgstr ""
-
msgid "The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6."
msgstr ""
@@ -22259,6 +22979,9 @@ msgstr ""
msgid "There was a problem fetching project branches."
msgstr ""
+msgid "There was a problem fetching project tags."
+msgstr ""
+
msgid "There was a problem fetching project users."
msgstr ""
@@ -22310,6 +23033,9 @@ msgstr ""
msgid "There was an error fetching the %{replicableType}"
msgstr ""
+msgid "There was an error fetching the Geo Settings"
+msgstr ""
+
msgid "There was an error fetching the Node's Groups"
msgstr ""
@@ -22430,6 +23156,12 @@ msgstr ""
msgid "This %{issuable} is locked. Only <strong>project members</strong> can comment."
msgstr ""
+msgid "This %{noteableTypeText} is %{confidentialLinkStart}confidential%{linkEnd} and %{lockedLinkStart}locked%{linkEnd}."
+msgstr ""
+
+msgid "This %{noteableTypeText} is locked."
+msgstr ""
+
msgid "This %{viewer} could not be displayed because %{reason}. You can %{options} instead."
msgstr ""
@@ -22448,6 +23180,9 @@ msgstr ""
msgid "This action can lead to data loss. To prevent accidental actions we ask you to confirm your intention."
msgstr ""
+msgid "This also resolves all related threads"
+msgstr ""
+
msgid "This also resolves the discussion"
msgstr ""
@@ -22538,6 +23273,9 @@ msgstr ""
msgid "This epic does not exist or you don't have sufficient permission."
msgstr ""
+msgid "This feature is currently in beta. We invite you to %{linkStart}give feedback%{linkEnd}."
+msgstr ""
+
msgid "This feature requires local storage to be enabled"
msgstr ""
@@ -22571,7 +23309,7 @@ msgstr ""
msgid "This is a Work in Progress"
msgstr ""
-msgid "This is a confidential issue."
+msgid "This is a confidential %{noteableTypeText}."
msgstr ""
msgid "This is a delayed job to run in %{remainingTime}"
@@ -22598,9 +23336,6 @@ msgstr ""
msgid "This is your current session"
msgstr ""
-msgid "This issue is %{confidentialLinkStart}confidential%{linkEnd} and %{lockedLinkStart}locked%{linkEnd}."
-msgstr ""
-
msgid "This issue is confidential"
msgstr ""
@@ -22610,9 +23345,6 @@ msgstr ""
msgid "This issue is in a child epic of the filtered epic"
msgstr ""
-msgid "This issue is locked."
-msgstr ""
-
msgid "This job depends on other jobs with expired/erased artifacts: %{invalid_dependencies}"
msgstr ""
@@ -22712,6 +23444,9 @@ msgstr ""
msgid "This license has already expired."
msgstr ""
+msgid "This link points to external content"
+msgstr ""
+
msgid "This may expose confidential information as the selected fork is in another namespace that can have other members."
msgstr ""
@@ -22739,6 +23474,9 @@ msgstr ""
msgid "This page will be removed in a future release."
msgstr ""
+msgid "This pipeline does not use the %{codeStart}needs%{codeEnd} keyword and can't be represented as a directed acyclic graph."
+msgstr ""
+
msgid "This pipeline makes use of a predefined CI/CD configuration enabled by %{strongStart}Auto DevOps.%{strongEnd}"
msgstr ""
@@ -22967,6 +23705,9 @@ msgstr ""
msgid "Time from last commit to merge"
msgstr ""
+msgid "Time in seconds"
+msgstr ""
+
msgid "Time in seconds GitLab will wait for a response from the external service. When the service does not respond in time, access will be denied."
msgstr ""
@@ -23388,9 +24129,15 @@ msgstr ""
msgid "Total artifacts size: %{total_size}"
msgstr ""
+msgid "Total cores (CPUs)"
+msgstr ""
+
msgid "Total issues"
msgstr ""
+msgid "Total memory (GB)"
+msgstr ""
+
msgid "Total test time for all commits/merges"
msgstr ""
@@ -23592,6 +24339,9 @@ msgstr ""
msgid "U2F only works with HTTPS-enabled websites. Contact your administrator for more details."
msgstr ""
+msgid "UI Development Kit"
+msgstr ""
+
msgid "URL"
msgstr ""
@@ -23664,9 +24414,6 @@ msgstr ""
msgid "Unable to load the merge request widget. Try reloading the page."
msgstr ""
-msgid "Unable to process group import file"
-msgstr ""
-
msgid "Unable to resolve"
msgstr ""
@@ -23682,6 +24429,9 @@ msgstr ""
msgid "Unable to sign you in to the group with SAML due to \"%{reason}\""
msgstr ""
+msgid "Unable to suggest a path. Please refresh and try again."
+msgstr ""
+
msgid "Unable to update label prioritization at this time"
msgstr ""
@@ -23697,6 +24447,9 @@ msgstr ""
msgid "Unarchiving the project will restore people's ability to make changes to it. The repository can be committed to, and issues, comments, and other entities can be created. %{strong_start}Once active, this project shows up in the search and on the dashboard.%{strong_end}"
msgstr ""
+msgid "Unassign from commenting user"
+msgstr ""
+
msgid "Unblock"
msgstr ""
@@ -23772,6 +24525,9 @@ msgstr ""
msgid "Unreachable"
msgstr ""
+msgid "Unrecognized cluster type"
+msgstr ""
+
msgid "Unresolve"
msgstr ""
@@ -23832,6 +24588,9 @@ msgstr ""
msgid "Unsubscribes from this %{quick_action_target}."
msgstr ""
+msgid "Unsupported todo type passed. Supported todo types are: %{todo_types}"
+msgstr ""
+
msgid "Until"
msgstr ""
@@ -23859,6 +24618,9 @@ msgstr ""
msgid "Update approval rule"
msgstr ""
+msgid "Update approvers"
+msgstr ""
+
msgid "Update failed"
msgstr ""
@@ -23868,6 +24630,9 @@ msgstr ""
msgid "Update it"
msgstr ""
+msgid "Update iteration"
+msgstr ""
+
msgid "Update now"
msgstr ""
@@ -23913,7 +24678,7 @@ msgstr ""
msgid "Updated at"
msgstr ""
-msgid "Updated to"
+msgid "Updated to %{linkStart}chart v%{linkEnd}"
msgstr ""
msgid "Updating"
@@ -23943,6 +24708,9 @@ msgstr ""
msgid "Upload CSV file"
msgstr ""
+msgid "Upload License"
+msgstr ""
+
msgid "Upload New File"
msgstr ""
@@ -24024,16 +24792,19 @@ msgstr ""
msgid "UsageQuota|Pipelines"
msgstr ""
+msgid "UsageQuota|Purchase more storage"
+msgstr ""
+
msgid "UsageQuota|Repositories"
msgstr ""
msgid "UsageQuota|Repository"
msgstr ""
-msgid "UsageQuota|Storage"
+msgid "UsageQuota|Snippets"
msgstr ""
-msgid "UsageQuota|Storage usage:"
+msgid "UsageQuota|Storage"
msgstr ""
msgid "UsageQuota|This namespace has no projects which use shared runners"
@@ -24066,6 +24837,12 @@ msgstr ""
msgid "UsageQuota|Wikis"
msgstr ""
+msgid "UsageQuota|You used: %{usage} %{limit}"
+msgstr ""
+
+msgid "UsageQuota|out of %{formattedLimit} of your namespace storage"
+msgstr ""
+
msgid "Use %{code_start}::%{code_end} to create a %{link_start}scoped label set%{link_end} (eg. %{code_start}priority::1%{code_end})"
msgstr ""
@@ -24132,6 +24909,9 @@ msgstr ""
msgid "User IDs"
msgstr ""
+msgid "User Lists can only be created and modified with %{linkStart}the API%{linkEnd}"
+msgstr ""
+
msgid "User OAuth applications"
msgstr ""
@@ -24156,6 +24936,9 @@ msgstr ""
msgid "User key was successfully removed."
msgstr ""
+msgid "User list %{name} will be removed. Are you sure?"
+msgstr ""
+
msgid "User map"
msgstr ""
@@ -24177,148 +24960,10 @@ msgstr ""
msgid "User was successfully updated."
msgstr ""
-msgid "UserOnboardingTour|%{activeTour}/%{totalTours}"
-msgstr ""
-
-msgid "UserOnboardingTour|%{completed}/%{total} steps completed"
-msgstr ""
-
-msgid "UserOnboardingTour|%{emphasisStart}Well done!%{emphasisEnd}%{lineBreak}%{lineBreak}That's it for our guided tour, congratulations for making it all the way to the end!%{lineBreak}%{lineBreak}We hope this gave you a good overview of GitLab and how it can help you. We'll now show you how to create your own project and invite your colleagues."
-msgstr ""
-
-msgid "UserOnboardingTour|Adding other members to a project is done through Project Settings. Click on %{emphasisStart}Settings%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Alright, that's it for Commits. Let's take a look at the %{emphasisStart}Branches%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Awesome! Now click on %{emphasisStart}Members%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Click on one of the %{emphasisStart}Compare%{emphasisEnd} buttons to compare a branch to master."
-msgstr ""
-
-msgid "UserOnboardingTour|Click on one of the %{emphasisStart}pipeline IDs%{emphasisEnd} to see the details of a pipeline."
-msgstr ""
-
-msgid "UserOnboardingTour|Click to open the latest commit to see its details."
-msgstr ""
-
-msgid "UserOnboardingTour|Close 'Learn GitLab'"
-msgstr ""
-
-msgid "UserOnboardingTour|Commits are shown in chronological order and can be filtered by the commit message or by the branch."
+msgid "UserList|Delete %{name}?"
msgstr ""
-msgid "UserOnboardingTour|Create a project"
-msgstr ""
-
-msgid "UserOnboardingTour|Exit 'Learn GitLab'"
-msgstr ""
-
-msgid "UserOnboardingTour|Got it"
-msgstr ""
-
-msgid "UserOnboardingTour|Great job! %{clapHands} We hope the tour was helpful and that you learned how to use GitLab.%{lineBreak}%{lineBreak}We'd love to get your feedback on this tour.%{lineBreak}%{lineBreak}%{emphasisStart}How helpful would you say this guided tour was?%{emphasisEnd}%{lineBreak}%{lineBreak}"
-msgstr ""
-
-msgid "UserOnboardingTour|Guided GitLab Tour"
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can compare the changes of this branch to another one. Changes are divided by files so that it's easier to see what was changed where."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can create a project from scratch, start with a template or import a repository from other platforms. Whatever you choose, we'll guide you through the process.%{lineBreak}%{lineBreak}Fill in your new project information and click on %{emphasisStart}Create Project%{emphasisEnd} to progress to the next step."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can see the breakdown of the pipelines: its stages and jobs in each of the stages and their status.%{lineBreak}%{lineBreak}Our CI/CD pipelines are quite complex, most of our users have fewer and simpler pipelines."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can see the current members of the project (just you at the moment) and invite new members.%{lineBreak}%{lineBreak}You can invite multiple members at once (existing GitLab users or invite by email) and you can also set their roles and permissions.%{lineBreak}%{lineBreak}Add a few members and click on %{emphasisStart}Add to project%{emphasisEnd} to complete this step."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can see what changes were made with this commit, on what branch and if there's a related merge request. The status of the pipeline will also show up if CI/CD is set up.%{lineBreak}%{lineBreak}You can also comment on the lines of code that were changed and start a discussion with your colleagues!"
-msgstr ""
-
-msgid "UserOnboardingTour|Here's an overview of branches in the %{emphasisStart}%{projectName}%{emphasisEnd} project. They're split into Active and Stale.%{lineBreak}%{lineBreak}From here, you can create a new merge request from a branch, or compare the branch to any other branch in the project. By default, it will compare it to the master branch."
-msgstr ""
-
-msgid "UserOnboardingTour|Invite colleagues"
-msgstr ""
-
-msgid "UserOnboardingTour|Issues are great for communicating and keeping track of progress in GitLab. These are all issues that are open in the %{emphasisStart}%{projectName}%{emphasisEnd}.%{lineBreak}%{lineBreak}You can help us improve GitLab by contributing work to issues that are labeled <span class=\"badge color-label accept-mr-label\">Accepting merge requests</span>.%{lineBreak}%{lineBreak}This list can be filtered by labels, milestones, assignees, authors... We'll show you how it looks when the list is filtered by a label."
-msgstr ""
-
-msgid "UserOnboardingTour|Learn GitLab"
-msgstr ""
-
-msgid "UserOnboardingTour|Let's take a closer look at a merge request. Click on the title of one."
-msgstr ""
-
-msgid "UserOnboardingTour|Let's take a closer look at all the commits. Click on %{emphasisStart}Commits%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Let's take a closer look at the repository of this project. Click on %{emphasisStart}Repository%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|No thanks"
-msgstr ""
-
-msgid "UserOnboardingTour|Ok, let's go"
-msgstr ""
-
-msgid "UserOnboardingTour|Ok, show me"
-msgstr ""
-
-msgid "UserOnboardingTour|Open one of the issues by clicking on its title."
-msgstr ""
-
-msgid "UserOnboardingTour|Restart this step"
-msgstr ""
-
-msgid "UserOnboardingTour|Skip this step"
-msgstr ""
-
-msgid "UserOnboardingTour|Sweet! Your project was created and is ready to be used.%{lineBreak}%{lineBreak}You can start adding files to the repository or clone it. One last thing we want to show you is how to invite your colleagues to your new project."
-msgstr ""
-
-msgid "UserOnboardingTour|Take a look. Here's a nifty menu for quickly creating issues, merge requests, snippets, projects and groups. Click on it and select \"New project\" from the \"GitLab\" section to get started."
-msgstr ""
-
-msgid "UserOnboardingTour|Thanks for taking the guided tour. Remember, if you want to go through it again, you can start %{emphasisStart}Learn GitLab%{emphasisEnd} in the help menu on the top right."
-msgstr ""
-
-msgid "UserOnboardingTour|Thanks for the feedback! %{thumbsUp}"
-msgstr ""
-
-msgid "UserOnboardingTour|That's it for issues. Let'st take a look at %{emphasisStart}Merge Requests%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|That's it for merge requests. Now for the final part of this guided tour - the %{emphasisStart}CI/CD%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|That's it for the Repository. Let's take a look at the %{emphasisStart}Issues%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|The structure of this page is very similar to issues. Status, description, discussion and the sidebar are all here.%{lineBreak}%{lineBreak}But take a look below the description and you'll notice that there's more information about the merge request, the CI/CD pipeline and the options for approving it.%{lineBreak}%{lineBreak}Alongside the discussion you can also see more information about commits in this merge request, the status of pipelines and review all changes that were made."
-msgstr ""
-
-msgid "UserOnboardingTour|There's a lot of information here but don't worry, we'll go through it.%{lineBreak}%{lineBreak}On the top you can see the status of the issue and when it was opened and by whom. Directly below it is the issue description and below that are other %{emphasisStart}related issues%{emphasisEnd} and %{emphasisStart}merge requests%{emphasisEnd} (if any). Then below that is the %{emphasisStart}discussion%{emphasisEnd}, that's where most of the communication happens.%{lineBreak}%{lineBreak}On the right, there's a sidebar where you can view/change the %{emphasisStart}assignee, milestone, due date, labels, weight%{emphasisEnd}, etc."
-msgstr ""
-
-msgid "UserOnboardingTour|These are all the CI/CD pipelines we have for our %{emphasisStart}%{projectName}%{emphasisEnd} project.%{lineBreak}%{lineBreak}Here you can see the status of each pipeline, for what commit it's running for, its stages and the status for them."
-msgstr ""
-
-msgid "UserOnboardingTour|These are all the issues that are available for community contributions. Let's take a closer look at one of them."
-msgstr ""
-
-msgid "UserOnboardingTour|This is an overview of all merge requests in this project. Similarly to the issues overview it can be filtered down by things like labels, milestones, authors, assignees, etc."
-msgstr ""
-
-msgid "UserOnboardingTour|This is the repository for the %{emphasisStart}%{projectName}%{emphasisEnd} project. All our code is stored here. Feel free to explore and take a closer look at folders and files.%{lineBreak}%{lineBreak}Above the file structure you can see the latest commit, who the author is and the status of the CI/CD pipeline.%{lineBreak}%{lineBreak}If you scroll down below the file structure, you'll find the Readme of this project. This is defined in the README.md file at the root of the repository."
-msgstr ""
-
-msgid "UserOnboardingTour|Welcome to the project overview of the %{emphasisStart}%{projectName}%{emphasisEnd} project. This is the project that we use to work on GitLab. At first, a project seems like a simple repository, but at GitLab, a project is so much more.%{lineBreak}%{lineBreak}You can create projects for hosting your codebase, use it as an issue tracker, collaborate on code, and continuously build, test, and deploy your app with built-in GitLab CI/CD."
+msgid "UserList|created %{timeago}"
msgstr ""
msgid "UserProfile|Activity"
@@ -24471,6 +25116,9 @@ msgstr ""
msgid "UsersSelect|Unassigned"
msgstr ""
+msgid "Using %{codeStart}needs%{codeEnd} allows jobs to run before their stage is reached, as soon as their individual dependencies are met, which speeds up your pipelines."
+msgstr ""
+
msgid "Using %{code_start}::%{code_end} denotes a %{link_start}scoped label set%{link_end}"
msgstr ""
@@ -24555,9 +25203,6 @@ msgstr ""
msgid "Versions"
msgstr ""
-msgid "Very helpful"
-msgstr ""
-
msgid "View Documentation"
msgstr ""
@@ -24706,6 +25351,9 @@ msgstr ""
msgid "VisibilityLevel|Unknown"
msgstr ""
+msgid "Visit settings page"
+msgstr ""
+
msgid "VisualReviewApp|%{stepStart}Step 1%{stepEnd}. Copy the following script:"
msgstr ""
@@ -24799,6 +25447,9 @@ msgstr ""
msgid "VulnerabilityManagement|Something went wrong while trying to delete the comment. Please try again later."
msgstr ""
+msgid "VulnerabilityManagement|Something went wrong while trying to refresh the vulnerability. Please try again later."
+msgstr ""
+
msgid "VulnerabilityManagement|Something went wrong while trying to retrieve the vulnerability history. Please try again later."
msgstr ""
@@ -24841,6 +25492,9 @@ msgstr ""
msgid "Vulnerability|Class"
msgstr ""
+msgid "Vulnerability|Crash Address"
+msgstr ""
+
msgid "Vulnerability|Description"
msgstr ""
@@ -24916,9 +25570,6 @@ msgstr ""
msgid "We couldn't reach the Prometheus server. Either the server no longer exists or the configuration details need updating."
msgstr ""
-msgid "We created a short guided tour that will help you learn the basics of GitLab and how it will help you be better at your job. It should only take a couple of minutes. You will be guided by two types of helpers, best recognized by their color."
-msgstr ""
-
msgid "We detected potential spam in the %{humanized_resource_name}. Please solve the reCAPTCHA to proceed."
msgstr ""
@@ -24952,6 +25603,9 @@ msgstr ""
msgid "We've found no vulnerabilities"
msgstr ""
+msgid "Web Application Firewall"
+msgstr ""
+
msgid "Web IDE"
msgstr ""
@@ -25012,9 +25666,6 @@ msgstr ""
msgid "Welcome to GitLab.com<br>@%{name}!"
msgstr ""
-msgid "Welcome to the Guided GitLab Tour"
-msgstr ""
-
msgid "Welcome to the guided GitLab tour"
msgstr ""
@@ -25062,9 +25713,6 @@ msgstr ""
msgid "While it's rare to have no vulnerabilities, it can happen. In any event, we ask that you please double check your settings to make sure you've set up your dashboard correctly."
msgstr ""
-msgid "White helpers give contextual information."
-msgstr ""
-
msgid "Who can be an approver?"
msgstr ""
@@ -25110,12 +25758,18 @@ msgstr ""
msgid "WikiEmptyIssueMessage|Suggest wiki improvement"
msgstr ""
+msgid "WikiEmptyIssueMessage|You must be a group member in order to add wiki pages. If you have suggestions for how to improve the wiki for this group, consider opening an issue in the %{issues_link}."
+msgstr ""
+
msgid "WikiEmptyIssueMessage|You must be a project member in order to add wiki pages. If you have suggestions for how to improve the wiki for this project, consider opening an issue in the %{issues_link}."
msgstr ""
msgid "WikiEmptyIssueMessage|issue tracker"
msgstr ""
+msgid "WikiEmpty|A wiki is where you can store all the details about your group. This can include why you've created it, its principles, how to use it, and so on."
+msgstr ""
+
msgid "WikiEmpty|A wiki is where you can store all the details about your project. This can include why you've created it, its principles, how to use it, and so on."
msgstr ""
@@ -25125,12 +25779,21 @@ msgstr ""
msgid "WikiEmpty|Suggest wiki improvement"
msgstr ""
+msgid "WikiEmpty|The wiki lets you write documentation for your group"
+msgstr ""
+
msgid "WikiEmpty|The wiki lets you write documentation for your project"
msgstr ""
+msgid "WikiEmpty|This group has no wiki pages"
+msgstr ""
+
msgid "WikiEmpty|This project has no wiki pages"
msgstr ""
+msgid "WikiEmpty|You must be a group member in order to add wiki pages."
+msgstr ""
+
msgid "WikiEmpty|You must be a project member in order to add wiki pages."
msgstr ""
@@ -25407,13 +26070,13 @@ msgstr ""
msgid "You can now export your security dashboard to a CSV report."
msgstr ""
-msgid "You can now submit a merge request to get this change into the original branch."
+msgid "You can now manage alert endpoint configuration in the Alerts section on the Operations settings page. Fields on this page have been deprecated."
msgstr ""
-msgid "You can now submit a merge request to get this change into the original project."
+msgid "You can now submit a merge request to get this change into the original branch."
msgstr ""
-msgid "You can only add files when you are on a branch"
+msgid "You can now submit a merge request to get this change into the original project."
msgstr ""
msgid "You can only edit files when you are on a branch"
@@ -25635,7 +26298,7 @@ msgstr ""
msgid "You need to upload a Google Takeout archive."
msgstr ""
-msgid "You reached %{usage_in_percent} of %{namespace_name}'s capacity (%{used_storage} of %{storage_limit})"
+msgid "You reached %{usage_in_percent} of %{namespace_name}'s storage capacity (%{used_storage} of %{storage_limit})"
msgstr ""
msgid "You tried to fork %{link_to_the_project} but it failed for the following reason:"
@@ -25938,13 +26601,13 @@ msgstr ""
msgid "Your subscription expired!"
msgstr ""
-msgid "Your subscription has been downgraded"
+msgid "Your subscription has been downgraded."
msgstr ""
-msgid "Your subscription will automatically renew in %{remaining_days}"
+msgid "Your subscription will automatically renew in %{remaining_days}."
msgstr ""
-msgid "Your subscription will expire in %{remaining_days}"
+msgid "Your subscription will expire in %{remaining_days}."
msgstr ""
msgid "Zoom meeting added"
@@ -26024,9 +26687,6 @@ msgstr ""
msgid "branch name"
msgstr ""
-msgid "build pipeline reference mismatch"
-msgstr ""
-
msgid "by"
msgstr ""
@@ -26045,6 +26705,9 @@ msgstr ""
msgid "cannot block others"
msgstr ""
+msgid "cannot contain HTML/XML tags, including any word between angle brackets (<,>)."
+msgstr ""
+
msgid "cannot include leading slash or directory traversal."
msgstr ""
@@ -26075,55 +26738,6 @@ msgstr ""
msgid "ciReport|%{remainingPackagesCount} more"
msgstr ""
-msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability"
-msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability for the source branch only"
-msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities for the source branch only"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerability"
-msgid_plural "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerabilities"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new vulnerability"
-msgid_plural "ciReport|%{reportType} %{status} detected %{newCount} new vulnerabilities"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities for the source branch only"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{fixedCount} fixed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} vulnerability for the source branch only"
-msgid_plural "ciReport|%{reportType} %{status} detected %{newCount} vulnerabilities for the source branch only"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "ciReport|%{reportType} %{status} detected no new vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected no vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected no vulnerabilities for the source branch only"
-msgstr ""
-
msgid "ciReport|%{reportType} is loading"
msgstr ""
@@ -26142,7 +26756,7 @@ msgstr ""
msgid "ciReport|All projects"
msgstr ""
-msgid "ciReport|All report types"
+msgid "ciReport|All scanner types"
msgstr ""
msgid "ciReport|All severities"
@@ -26166,6 +26780,9 @@ msgstr ""
msgid "ciReport|Container scanning detects known vulnerabilities in your docker images."
msgstr ""
+msgid "ciReport|Coverage Fuzzing"
+msgstr ""
+
msgid "ciReport|Create a merge request to implement this solution, or download and apply the patch manually."
msgstr ""
@@ -26324,6 +26941,9 @@ msgstr ""
msgid "customize"
msgstr ""
+msgid "data"
+msgstr ""
+
msgid "date must not be after 9999-12-31"
msgstr ""
@@ -26394,6 +27014,9 @@ msgstr ""
msgid "entries cannot contain HTML tags"
msgstr ""
+msgid "epic"
+msgstr ""
+
msgid "error"
msgstr ""
@@ -26415,9 +27038,6 @@ msgstr ""
msgid "expires on %{timebox_due_date}"
msgstr ""
-msgid "external_url"
-msgstr ""
-
msgid "failed"
msgstr ""
@@ -26453,9 +27073,6 @@ msgstr ""
msgid "from"
msgstr ""
-msgid "geo_node_name"
-msgstr ""
-
msgid "group"
msgstr ""
@@ -26477,9 +27094,6 @@ msgstr ""
msgid "https://your-bitbucket-server"
msgstr ""
-msgid "image"
-msgstr ""
-
msgid "image diff"
msgstr ""
@@ -26623,6 +27237,9 @@ msgstr ""
msgid "load it anyway"
msgstr ""
+msgid "loading"
+msgstr ""
+
msgid "locked by %{path_lock_user_name} %{created_at}"
msgstr ""
@@ -26751,9 +27368,6 @@ msgstr ""
msgid "mrWidget|Deployment statistics are not available currently"
msgstr ""
-msgid "mrWidget|Detect issues before deployment with a CI pipeline that continuously tests your code. We created a quick guide that will show you how to create one. Make your code more secure and more robust in just a minute."
-msgstr ""
-
msgid "mrWidget|Did not close"
msgstr ""
@@ -26916,6 +27530,9 @@ msgstr ""
msgid "mrWidget|To approve this merge request, please enter your password. This project requires all approvals to be authenticated."
msgstr ""
+msgid "mrWidget|Use %{linkStart}CI pipelines to test your code%{linkEnd}, simply add a GitLab CI configuration file to your project. It only takes a minute to make your code more secure and robust."
+msgstr ""
+
msgid "mrWidget|When this merge request is ready, remove the WIP: prefix from the title to allow it to be merged"
msgstr ""
@@ -26952,6 +27569,9 @@ msgstr ""
msgid "must be greater than start date"
msgstr ""
+msgid "must contain only valid frameworks"
+msgstr ""
+
msgid "my-awesome-group"
msgstr ""
@@ -26964,6 +27584,9 @@ msgstr ""
msgid "needs to be between 10 minutes and 1 month"
msgstr ""
+msgid "never"
+msgstr ""
+
msgid "never expires"
msgstr ""
@@ -26991,6 +27614,9 @@ msgstr ""
msgid "nounSeries|%{firstItem} and %{lastItem}"
msgstr ""
+msgid "nounSeries|%{item}"
+msgstr ""
+
msgid "nounSeries|%{item}, %{nextItem}"
msgstr ""
@@ -27127,6 +27753,9 @@ msgstr ""
msgid "revised"
msgstr ""
+msgid "satisfied"
+msgstr ""
+
msgid "score"
msgstr ""
@@ -27157,6 +27786,9 @@ msgstr ""
msgid "severity|Unknown"
msgstr ""
+msgid "should be an array of %{object_name} objects"
+msgstr ""
+
msgid "should be greater than or equal to %{access} inherited membership from group %{group_name}"
msgstr ""
@@ -27211,10 +27843,13 @@ msgstr ""
msgid "suggestPipeline|2/2: Commit your changes"
msgstr ""
-msgid "suggestPipeline|Commit the changes and your pipeline will automatically run for the first time."
+msgid "suggestPipeline|Choose %{boldStart}Code Quality%{boldEnd} to add a pipeline that tests the quality of your code."
msgstr ""
-msgid "suggestPipeline|We recommend the %{boldStart}Code Quality%{boldEnd} template, which will add a report widget to your Merge Requests. This way you’ll learn about code quality degradations much sooner. %{footerStart} Goodbye technical debt! %{footerEnd}"
+msgid "suggestPipeline|The template is ready! You can now commit it to create your first pipeline."
+msgstr ""
+
+msgid "suggestPipeline|We’re adding a GitLab CI configuration file to add a pipeline to the project. You could create it manually, but we recommend that you start with a GitLab template that works out of the box."
msgstr ""
msgid "syntax is correct"
@@ -27274,6 +27909,9 @@ msgstr ""
msgid "uses Kubernetes clusters to deploy your code!"
msgstr ""
+msgid "v%{version} published %{timeAgo}"
+msgstr ""
+
msgid "verify ownership"
msgstr ""
diff --git a/locale/vi_VN/gitlab.po b/locale/vi_VN/gitlab.po
index deaca86ee6a..c00423e7462 100644
--- a/locale/vi_VN/gitlab.po
+++ b/locale/vi_VN/gitlab.po
@@ -2,7 +2,7 @@ msgid ""
msgstr ""
"Project-Id-Version: gitlab-ee\n"
"Report-Msgid-Bugs-To: \n"
-"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Last-Translator: \n"
"Language-Team: Vietnamese\n"
"Language: vi_VN\n"
"MIME-Version: 1.0\n"
@@ -10,9 +10,11 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=1; plural=0;\n"
"X-Crowdin-Project: gitlab-ee\n"
+"X-Crowdin-Project-ID: 288872\n"
"X-Crowdin-Language: vi\n"
"X-Crowdin-File: /master/locale/gitlab.pot\n"
-"PO-Revision-Date: 2020-06-08 15:08\n"
+"X-Crowdin-File-ID: 6\n"
+"PO-Revision-Date: 2020-07-02 01:19\n"
msgid " %{start} to %{end}"
msgstr ""
@@ -170,6 +172,10 @@ msgid "%d metric"
msgid_plural "%d metrics"
msgstr[0] ""
+msgid "%d milestone"
+msgid_plural "%d milestones"
+msgstr[0] ""
+
msgid "%d minute"
msgid_plural "%d minutes"
msgstr[0] ""
@@ -206,10 +212,6 @@ msgid "%d unresolved thread"
msgid_plural "%d unresolved threads"
msgstr[0] ""
-msgid "%d url scanned"
-msgid_plural "%d urls scanned"
-msgstr[0] ""
-
msgid "%d vulnerability"
msgid_plural "%d vulnerabilities"
msgstr[0] ""
@@ -280,9 +282,15 @@ msgstr[0] ""
msgid "%{count} related %{pluralized_subject}: %{links}"
msgstr ""
+msgid "%{dashboard_path} could not be found."
+msgstr ""
+
msgid "%{days} days until tags are automatically removed"
msgstr ""
+msgid "%{deployLinkStart}Use a template to deploy to ECS%{deployLinkEnd}, or use a docker image to %{commandsLinkStart}run AWS commands in GitLab CI/CD%{commandsLinkEnd}."
+msgstr ""
+
msgid "%{description}- Sentry event: %{errorUrl}- First seen: %{firstSeen}- Last seen: %{lastSeen} %{countLabel}: %{count}%{userCountLabel}: %{userCount}"
msgstr ""
@@ -325,12 +333,18 @@ msgstr ""
msgid "%{host} sign-in from new location"
msgstr ""
-msgid "%{icon}You are about to add %{usersTag} people to the discussion. Proceed with caution."
+msgid "%{icon}You are about to add %{usersTag} people to the discussion. They will all receive a notification."
+msgstr ""
+
+msgid "%{integrations_link_start}Integrations%{link_end} enable you to make third-party applications part of your GitLab workflow. If the available integrations don't meet your needs, consider using a %{webhooks_link_start}webhook%{link_end}."
msgstr ""
msgid "%{issuableType} will be removed! Are you sure?"
msgstr ""
+msgid "%{issuesCount} issues in this group"
+msgstr ""
+
msgid "%{issuesSize} issues"
msgstr ""
@@ -340,7 +354,7 @@ msgstr ""
msgid "%{labelStart}Class:%{labelEnd} %{class}"
msgstr ""
-msgid "%{labelStart}Confidence:%{labelEnd} %{confidence}"
+msgid "%{labelStart}Evidence:%{labelEnd} %{evidence}"
msgstr ""
msgid "%{labelStart}File:%{labelEnd} %{file}"
@@ -358,6 +372,9 @@ msgstr ""
msgid "%{labelStart}Report Type:%{labelEnd} %{reportType}"
msgstr ""
+msgid "%{labelStart}Scanner:%{labelEnd} %{scanner}"
+msgstr ""
+
msgid "%{labelStart}Severity:%{labelEnd} %{severity}"
msgstr ""
@@ -382,9 +399,6 @@ msgstr ""
msgid "%{link_start}Read more%{link_end} about role permissions"
msgstr ""
-msgid "%{link} can be used for binding events when something is happening within the project."
-msgstr ""
-
msgid "%{listToShow}, and %{awardsListLength} more."
msgstr ""
@@ -418,6 +432,9 @@ msgstr ""
msgid "%{name} found %{resultsString}"
msgstr ""
+msgid "%{name} is already being used for another emoji"
+msgstr ""
+
msgid "%{name} is scheduled for %{action}"
msgstr ""
@@ -464,6 +481,9 @@ msgstr ""
msgid "%{primary} (%{secondary})"
msgstr ""
+msgid "%{ref} cannot be added: %{error}"
+msgstr ""
+
msgid "%{releases} release"
msgid_plural "%{releases} releases"
msgstr[0] ""
@@ -471,13 +491,34 @@ msgstr[0] ""
msgid "%{remaining_approvals} left"
msgstr ""
-msgid "%{retryButtonStart}Try again%{retryButtonEnd} or %{newFileButtonStart}attach a new file%{newFileButtonEnd}"
+msgid "%{reportType} %{status} detected %{critical} critical and %{high} high severity vulnerabilities out of %{total}."
+msgstr ""
+
+msgid "%{reportType} %{status} detected %{critical} critical and %{high} high severity vulnerabilities."
msgstr ""
-msgid "%{screenreaderOnlyStart}Keyboard shorcuts%{screenreaderOnlyEnd} Disabled"
+msgid "%{reportType} %{status} detected %{critical} critical severity vulnerabilities out of %{total}."
msgstr ""
-msgid "%{screenreaderOnlyStart}Keyboard shorcuts%{screenreaderOnlyEnd} Enabled"
+msgid "%{reportType} %{status} detected %{critical} critical severity vulnerability."
+msgid_plural "%{reportType} %{status} detected %{critical} critical severity vulnerabilities."
+msgstr[0] ""
+
+msgid "%{reportType} %{status} detected %{high} high severity vulnerabilities out of %{total}."
+msgstr ""
+
+msgid "%{reportType} %{status} detected %{high} high severity vulnerability."
+msgid_plural "%{reportType} %{status} detected %{high} high severity vulnerabilities."
+msgstr[0] ""
+
+msgid "%{reportType} %{status} detected %{other} vulnerability."
+msgid_plural "%{reportType} %{status} detected %{other} vulnerabilities."
+msgstr[0] ""
+
+msgid "%{reportType} %{status} detected no new vulnerabilities."
+msgstr ""
+
+msgid "%{retryButtonStart}Try again%{retryButtonEnd} or %{newFileButtonStart}attach a new file%{newFileButtonEnd}"
msgstr ""
msgid "%{service_title} %{message}."
@@ -569,6 +610,12 @@ msgstr ""
msgid "%{token}..."
msgstr ""
+msgid "%{totalCpu} (%{freeSpacePercentage}%{percentSymbol} free)"
+msgstr ""
+
+msgid "%{totalMemory} (%{freeSpacePercentage}%{percentSymbol} free)"
+msgstr ""
+
msgid "%{totalWeight} total weight"
msgstr ""
@@ -599,6 +646,12 @@ msgstr ""
msgid "%{verb} %{time_spent_value} spent time."
msgstr ""
+msgid "%{webhooks_link_start}%{webhook_type}%{link_end} enable you to send notifications to web applications in response to events in a group or project."
+msgstr ""
+
+msgid "%{webhooks_link_start}%{webhook_type}%{link_end} enable you to send notifications to web applications in response to events in a group or project. We recommend using an %{integrations_link_start}integration%{link_end} in preference to a webhook."
+msgstr ""
+
msgid "'%{level}' is not a valid visibility level"
msgstr ""
@@ -624,9 +677,6 @@ msgstr ""
msgid "(No changes)"
msgstr ""
-msgid "(Show all)"
-msgstr ""
-
msgid "(check progress)"
msgstr ""
@@ -690,6 +740,9 @@ msgstr ""
msgid "0 for unlimited"
msgstr ""
+msgid "0 for unlimited, only effective with remote storage enabled."
+msgstr ""
+
msgid "1 %{type} addition"
msgid_plural "%{count} %{type} additions"
msgstr[0] ""
@@ -822,6 +875,12 @@ msgstr ""
msgid "<code>\"johnsmith@example.com\": \"johnsmith@example.com\"</code> will add \"By <a href=\"#\">johnsmith@example.com</a>\" to all issues and comments originally created by johnsmith@example.com. By default, the email address or username is masked to ensure the user's privacy. Use this option if you want to show the full email address."
msgstr ""
+msgid "<code>Masked</code> to prevent the values from being displayed in job logs (must match certain regexp requirements)."
+msgstr ""
+
+msgid "<code>Protected</code> to expose them to protected branches or tags only."
+msgstr ""
+
msgid "<namespace / project>"
msgstr ""
@@ -849,9 +908,6 @@ msgstr ""
msgid "A .NET Core console application template, customizable for any .NET Core project"
msgstr ""
-msgid "A DAG must have two dependent jobs to be visualized on this tab."
-msgstr ""
-
msgid "A GitBook site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
msgstr ""
@@ -882,6 +938,12 @@ msgstr ""
msgid "A deleted user"
msgstr ""
+msgid "A file has been changed."
+msgstr ""
+
+msgid "A file was not found."
+msgstr ""
+
msgid "A file with '%{file_name}' already exists in %{branch} branch"
msgstr ""
@@ -948,7 +1010,7 @@ msgstr ""
msgid "A subscription will trigger a new pipeline on the default branch of this project when a pipeline successfully completes for a new tag on the %{default_branch_docs} of the subscribed project."
msgstr ""
-msgid "A terraform report was generated in your pipelines."
+msgid "A suggestion is not applicable."
msgstr ""
msgid "A user with write access to the source branch selected this option"
@@ -1119,6 +1181,9 @@ msgstr ""
msgid "Action to take when receiving an alert."
msgstr ""
+msgid "Actions"
+msgstr ""
+
msgid "Activate"
msgstr ""
@@ -1174,6 +1239,9 @@ msgstr ""
msgid "Add LICENSE"
msgstr ""
+msgid "Add New Node"
+msgstr ""
+
msgid "Add README"
msgstr ""
@@ -1249,6 +1317,9 @@ msgstr ""
msgid "Add approval rule"
msgstr ""
+msgid "Add approvers"
+msgstr ""
+
msgid "Add bold text"
msgstr ""
@@ -1306,6 +1377,9 @@ msgstr ""
msgid "Add strikethrough text"
msgstr ""
+msgid "Add suggestion to batch"
+msgstr ""
+
msgid "Add system hook"
msgstr ""
@@ -1405,6 +1479,9 @@ msgstr ""
msgid "Admin Note"
msgstr ""
+msgid "Admin Notifications"
+msgstr ""
+
msgid "Admin Overview"
msgstr ""
@@ -1516,6 +1593,9 @@ msgstr ""
msgid "AdminSettings|Integrations configured here will automatically apply to all projects on this instance."
msgstr ""
+msgid "AdminSettings|Moved to integrations"
+msgstr ""
+
msgid "AdminSettings|No required pipeline"
msgstr ""
@@ -1745,12 +1825,18 @@ msgid "Alert"
msgid_plural "Alerts"
msgstr[0] ""
+msgid "AlertManagement| assign yourself"
+msgstr ""
+
msgid "AlertManagement|Acknowledged"
msgstr ""
msgid "AlertManagement|Alert"
msgstr ""
+msgid "AlertManagement|Alert assignee(s): %{assignees}"
+msgstr ""
+
msgid "AlertManagement|Alert detail"
msgstr ""
@@ -1766,9 +1852,15 @@ msgstr ""
msgid "AlertManagement|All alerts"
msgstr ""
+msgid "AlertManagement|Assign To"
+msgstr ""
+
msgid "AlertManagement|Assign status"
msgstr ""
+msgid "AlertManagement|Assignee"
+msgstr ""
+
msgid "AlertManagement|Assignees"
msgstr ""
@@ -1787,9 +1879,6 @@ msgstr ""
msgid "AlertManagement|Edit"
msgstr ""
-msgid "AlertManagement|End time"
-msgstr ""
-
msgid "AlertManagement|Events"
msgstr ""
@@ -1808,10 +1897,13 @@ msgstr ""
msgid "AlertManagement|More information"
msgstr ""
+msgid "AlertManagement|No Matching Results"
+msgstr ""
+
msgid "AlertManagement|No alert data to display."
msgstr ""
-msgid "AlertManagement|No alerts available to display. If you think you're seeing this message in error, refresh the page."
+msgid "AlertManagement|No alerts available to display. See %{linkStart}enabling alert management%{linkEnd} for more information on adding alerts to the list."
msgstr ""
msgid "AlertManagement|No alerts to display."
@@ -1820,6 +1912,9 @@ msgstr ""
msgid "AlertManagement|None"
msgstr ""
+msgid "AlertManagement|None -"
+msgstr ""
+
msgid "AlertManagement|Open"
msgstr ""
@@ -1856,9 +1951,18 @@ msgstr ""
msgid "AlertManagement|There was an error displaying the alerts. Confirm your endpoint's configuration details to ensure alerts appear."
msgstr ""
+msgid "AlertManagement|There was an error while updating the assignee(s) list. Please try again."
+msgstr ""
+
+msgid "AlertManagement|There was an error while updating the assignee(s) of the alert. Please try again."
+msgstr ""
+
msgid "AlertManagement|There was an error while updating the status of the alert. Please try again."
msgstr ""
+msgid "AlertManagement|This assignee cannot be assigned to this alert."
+msgstr ""
+
msgid "AlertManagement|Tool"
msgstr ""
@@ -2006,6 +2110,9 @@ msgstr ""
msgid "Allow users to request access (if visibility is public or internal)"
msgstr ""
+msgid "Allowed Geo IP"
+msgstr ""
+
msgid "Allowed email domain restriction only permitted for top-level groups"
msgstr ""
@@ -2039,6 +2146,9 @@ msgstr ""
msgid "Amazon Web Services"
msgstr ""
+msgid "Amazon Web Services Logo"
+msgstr ""
+
msgid "Amazon authentication is not %{link_start}correctly configured%{link_end}. Ask your GitLab administrator if you want to use this service."
msgstr ""
@@ -2096,10 +2206,16 @@ msgstr ""
msgid "An error occurred when updating the issue weight"
msgstr ""
+msgid "An error occurred while acknowledging the notification. Refresh the page and try again."
+msgstr ""
+
+msgid "An error occurred while adding approvers"
+msgstr ""
+
msgid "An error occurred while adding formatted title for epic"
msgstr ""
-msgid "An error occurred while checking group path"
+msgid "An error occurred while checking group path. Please refresh and try again."
msgstr ""
msgid "An error occurred while committing your changes."
@@ -2243,7 +2359,7 @@ msgstr ""
msgid "An error occurred while loading milestones"
msgstr ""
-msgid "An error occurred while loading terraform report"
+msgid "An error occurred while loading project creation UI"
msgstr ""
msgid "An error occurred while loading the data. Please try again."
@@ -2297,6 +2413,9 @@ msgstr ""
msgid "An error occurred while rendering preview broadcast message"
msgstr ""
+msgid "An error occurred while rendering the editor"
+msgstr ""
+
msgid "An error occurred while reordering issues."
msgstr ""
@@ -2393,7 +2512,7 @@ msgstr ""
msgid "Analyze your dependencies for known vulnerabilities."
msgstr ""
-msgid "Analyze your source code and git history for secrets"
+msgid "Analyze your source code and git history for secrets."
msgstr ""
msgid "Analyze your source code for known vulnerabilities."
@@ -2507,12 +2626,18 @@ msgstr ""
msgid "Apply suggestion"
msgstr ""
+msgid "Apply suggestions"
+msgstr ""
+
msgid "Apply template"
msgstr ""
msgid "Apply this approval rule to any branch or a specific protected branch."
msgstr ""
+msgid "Applying"
+msgstr ""
+
msgid "Applying a template will replace the existing issue description. Any changes you have made will be lost."
msgstr ""
@@ -2525,12 +2650,18 @@ msgstr ""
msgid "Applying multiple commands"
msgstr ""
-msgid "Applying suggestion"
+msgid "Applying suggestion..."
+msgstr ""
+
+msgid "Applying suggestions..."
msgstr ""
msgid "Approval rules"
msgstr ""
+msgid "Approval rules reset to project defaults"
+msgstr ""
+
msgid "ApprovalRuleRemove|%d member"
msgid_plural "ApprovalRuleRemove|%d members"
msgstr[0] ""
@@ -2580,6 +2711,9 @@ msgstr ""
msgid "Approved"
msgstr ""
+msgid "Approved MRs"
+msgstr ""
+
msgid "Approved by: "
msgstr ""
@@ -2793,6 +2927,9 @@ msgstr ""
msgid "Assign"
msgstr ""
+msgid "Assign Iteration"
+msgstr ""
+
msgid "Assign custom color like #FF0000"
msgstr ""
@@ -2811,6 +2948,9 @@ msgstr ""
msgid "Assign to"
msgstr ""
+msgid "Assign to commenting user"
+msgstr ""
+
msgid "Assign yourself to these issues"
msgstr ""
@@ -2836,6 +2976,9 @@ msgid "Assignee"
msgid_plural "%d Assignees"
msgstr[0] ""
+msgid "Assignee has no permissions"
+msgstr ""
+
msgid "Assignee lists not available with your current license"
msgstr ""
@@ -2845,6 +2988,9 @@ msgstr ""
msgid "Assignee(s)"
msgstr ""
+msgid "Assignees"
+msgstr ""
+
msgid "Assigns %{assignee_users_sentence}."
msgstr ""
@@ -2879,6 +3025,9 @@ msgstr ""
msgid "Audit Events is a way to keep track of important events that happened in GitLab."
msgstr ""
+msgid "Audit Log"
+msgstr ""
+
msgid "AuditEvents|(removed)"
msgstr ""
@@ -2915,6 +3064,9 @@ msgstr ""
msgid "AuditLogs|IP Address"
msgstr ""
+msgid "AuditLogs|Member Events"
+msgstr ""
+
msgid "AuditLogs|No matching %{type} found."
msgstr ""
@@ -3197,9 +3349,6 @@ msgstr ""
msgid "Badges|e.g. %{exampleUrl}"
msgstr ""
-msgid "Badge|New"
-msgstr ""
-
msgid "Balsamiq file could not be loaded."
msgstr ""
@@ -3248,6 +3397,12 @@ msgstr ""
msgid "Below you will find all the groups that are public."
msgstr ""
+msgid "Beta"
+msgstr ""
+
+msgid "Bi-weekly code coverage"
+msgstr ""
+
msgid "Billing"
msgstr ""
@@ -3308,9 +3463,15 @@ msgstr ""
msgid "Bitbucket Server Import"
msgstr ""
+msgid "Bitbucket Server import"
+msgstr ""
+
msgid "Bitbucket import"
msgstr ""
+msgid "Blame"
+msgstr ""
+
msgid "Blocked"
msgstr ""
@@ -3323,9 +3484,6 @@ msgstr ""
msgid "Blog"
msgstr ""
-msgid "Blue helpers indicate an action to be taken."
-msgstr ""
-
msgid "Board name"
msgstr ""
@@ -3524,6 +3682,9 @@ msgstr ""
msgid "Broadcast Message was successfully updated."
msgstr ""
+msgid "Broadcast Messages"
+msgstr ""
+
msgid "Browse Directory"
msgstr ""
@@ -3578,6 +3739,9 @@ msgstr ""
msgid "By %{user_name}"
msgstr ""
+msgid "By URL"
+msgstr ""
+
msgid "By default GitLab sends emails in HTML and plain text formats so mail clients can choose what format to use. Disable this option if you only want to send emails in plain text format."
msgstr ""
@@ -3683,6 +3847,9 @@ msgstr ""
msgid "Can override approvers and approvals required per merge request"
msgstr ""
+msgid "Can't apply as this line has changed or the suggestion already matches its content."
+msgstr ""
+
msgid "Can't create snippet: %{err}"
msgstr ""
@@ -3869,9 +4036,6 @@ msgstr ""
msgid "Changes are still tracked. Useful for cluster/index migrations."
msgstr ""
-msgid "Changes are unknown"
-msgstr ""
-
msgid "Changes suppressed. Click to show."
msgstr ""
@@ -4178,15 +4342,9 @@ msgstr ""
msgid "Choose what content you want to see on a group’s overview page"
msgstr ""
-msgid "Choose which groups you wish to synchronize to this secondary node"
-msgstr ""
-
msgid "Choose which repositories you want to connect and run CI/CD pipelines."
msgstr ""
-msgid "Choose which shards you wish to synchronize to this secondary node"
-msgstr ""
-
msgid "Choose your framework"
msgstr ""
@@ -4484,9 +4642,15 @@ msgstr ""
msgid "Cluster does not exist"
msgstr ""
+msgid "Cluster is required for Stages::ClusterEndpointInserter"
+msgstr ""
+
msgid "Cluster level"
msgstr ""
+msgid "Cluster type must be specificed for Stages::ClusterEndpointInserter"
+msgstr ""
+
msgid "ClusterIntegration| %{custom_domain_start}More information%{custom_domain_end}."
msgstr ""
@@ -4598,7 +4762,7 @@ msgstr ""
msgid "ClusterIntegration|Cert-Manager"
msgstr ""
-msgid "ClusterIntegration|Cert-Manager is a native Kubernetes certificate management controller that helps with issuing certificates. Installing Cert-Manager on your cluster will issue a certificate by %{letsEncrypt} and ensure that certificates are valid and up-to-date."
+msgid "ClusterIntegration|Cert-Manager is a native Kubernetes certificate management controller that helps with issuing certificates. Installing Cert-Manager on your cluster will issue a certificate by %{linkStart}Let's Encrypt%{linkEnd} and ensure that certificates are valid and up-to-date."
msgstr ""
msgid "ClusterIntegration|Certificate Authority bundle (PEM format)"
@@ -4625,9 +4789,6 @@ msgstr ""
msgid "ClusterIntegration|Clear the local cache of namespace and service accounts. This is necessary if your integration has become out of sync. The cache is repopulated during the next CI job that requires namespace and service accounts."
msgstr ""
-msgid "ClusterIntegration|Cloud Run"
-msgstr ""
-
msgid "ClusterIntegration|Cluster being created"
msgstr ""
@@ -4712,7 +4873,7 @@ msgstr ""
msgid "ClusterIntegration|Crossplane"
msgstr ""
-msgid "ClusterIntegration|Crossplane enables declarative provisioning of managed services from your cloud of choice using %{codeStart}kubectl%{codeEnd} or %{gitlabIntegrationLink}. Crossplane runs inside your Kubernetes cluster and supports secure connectivity and secrets management between app containers and the cloud services they depend on."
+msgid "ClusterIntegration|Crossplane enables declarative provisioning of managed services from your cloud of choice using %{codeStart}kubectl%{codeEnd} or %{linkStart}GitLab Integration%{linkEnd}. Crossplane runs inside your Kubernetes cluster and supports secure connectivity and secrets management between app containers and the cloud services they depend on."
msgstr ""
msgid "ClusterIntegration|Deletes all GitLab resources attached to this cluster during removal"
@@ -4796,9 +4957,6 @@ msgstr ""
msgid "ClusterIntegration|GitLab-managed cluster"
msgstr ""
-msgid "ClusterIntegration|Gitlab Integration"
-msgstr ""
-
msgid "ClusterIntegration|Global default"
msgstr ""
@@ -4844,7 +5002,7 @@ msgstr ""
msgid "ClusterIntegration|Ingress gives you a way to route requests to services based on the request host or path, centralizing a number of services into a single entrypoint."
msgstr ""
-msgid "ClusterIntegration|Installing Ingress may incur additional costs. Learn more about %{pricingLink}."
+msgid "ClusterIntegration|Installing Ingress may incur additional costs. Learn more about %{linkStart}pricing%{linkEnd}."
msgstr ""
msgid "ClusterIntegration|Instance cluster"
@@ -4859,7 +5017,7 @@ msgstr ""
msgid "ClusterIntegration|Issuer Email"
msgstr ""
-msgid "ClusterIntegration|Issuers represent a certificate authority. You must provide an email address for your Issuer. "
+msgid "ClusterIntegration|Issuers represent a certificate authority. You must provide an email address for your Issuer."
msgstr ""
msgid "ClusterIntegration|Jupyter Hostname"
@@ -4931,9 +5089,6 @@ msgstr ""
msgid "ClusterIntegration|Learn more about instance Kubernetes clusters"
msgstr ""
-msgid "ClusterIntegration|Let's Encrypt"
-msgstr ""
-
msgid "ClusterIntegration|Loading IAM Roles"
msgstr ""
@@ -5045,7 +5200,7 @@ msgstr ""
msgid "ClusterIntegration|Prometheus"
msgstr ""
-msgid "ClusterIntegration|Prometheus is an open-source monitoring system with %{gitlabIntegrationLink} to monitor deployed applications."
+msgid "ClusterIntegration|Prometheus is an open-source monitoring system with %{linkStart}GitLab Integration%{linkEnd} to monitor deployed applications."
msgstr ""
msgid "ClusterIntegration|Provider details"
@@ -5201,10 +5356,10 @@ msgstr ""
msgid "ClusterIntegration|Select zone to choose machine type"
msgstr ""
-msgid "ClusterIntegration|Send Cilium Logs"
+msgid "ClusterIntegration|Send Container Network Policies Logs"
msgstr ""
-msgid "ClusterIntegration|Send ModSecurity Logs"
+msgid "ClusterIntegration|Send Web Application Firewall Logs"
msgstr ""
msgid "ClusterIntegration|Service Token"
@@ -5372,15 +5527,12 @@ msgstr ""
msgid "ClusterIntegration|documentation"
msgstr ""
-msgid "ClusterIntegration|installed via %{installed_via}"
+msgid "ClusterIntegration|installed via %{linkStart}Cloud Run%{linkEnd}"
msgstr ""
msgid "ClusterIntegration|meets the requirements"
msgstr ""
-msgid "ClusterIntegration|pricing"
-msgstr ""
-
msgid "ClusterIntegration|sign up"
msgstr ""
@@ -5417,6 +5569,15 @@ msgstr ""
msgid "Code"
msgstr ""
+msgid "Code Coverage: %{coveragePercentage}%{percentSymbol}"
+msgstr ""
+
+msgid "Code Coverage| Empty code coverage data"
+msgstr ""
+
+msgid "Code Coverage|Couldn't fetch the code coverage data"
+msgstr ""
+
msgid "Code Owners"
msgstr ""
@@ -5471,7 +5632,7 @@ msgstr ""
msgid "Collapse approvers"
msgstr ""
-msgid "Collapse child epics"
+msgid "Collapse milestones"
msgstr ""
msgid "Collapse replies"
@@ -5489,6 +5650,9 @@ msgstr ""
msgid "Coming soon"
msgstr ""
+msgid "Comma-separated, e.g. '1.1.1.1, 2.2.2.0/24'"
+msgstr ""
+
msgid "Command"
msgstr ""
@@ -5658,6 +5822,12 @@ msgstr ""
msgid "Compliance framework (optional)"
msgstr ""
+msgid "Compliance frameworks"
+msgstr ""
+
+msgid "ComplianceDashboard|created by:"
+msgstr ""
+
msgid "ComplianceFramework|GDPR"
msgstr ""
@@ -5796,6 +5966,9 @@ msgstr ""
msgid "Connection timed out"
msgstr ""
+msgid "Connection timeout"
+msgstr ""
+
msgid "Contact sales to upgrade"
msgstr ""
@@ -5849,9 +6022,6 @@ msgstr ""
msgid "ContainerRegistry|CLI Commands"
msgstr ""
-msgid "ContainerRegistry|Compressed Size"
-msgstr ""
-
msgid "ContainerRegistry|Container Registry"
msgstr ""
@@ -5867,6 +6037,9 @@ msgstr ""
msgid "ContainerRegistry|Copy push command"
msgstr ""
+msgid "ContainerRegistry|Delete selected"
+msgstr ""
+
msgid "ContainerRegistry|Docker connection error"
msgstr ""
@@ -5900,16 +6073,16 @@ msgstr ""
msgid "ContainerRegistry|If you are not already logged in, you need to authenticate to the Container Registry by using your GitLab username and password. If you have %{twofaDocLinkStart}Two-Factor Authentication%{twofaDocLinkEnd} enabled, use a %{personalAccessTokensDocLinkStart}Personal Access Token%{personalAccessTokensDocLinkEnd} instead of a password."
msgstr ""
-msgid "ContainerRegistry|Image ID"
+msgid "ContainerRegistry|Image ID: %{imageId}"
msgstr ""
msgid "ContainerRegistry|Image Repositories"
msgstr ""
-msgid "ContainerRegistry|Keep and protect the images that matter most."
+msgid "ContainerRegistry|Image tags"
msgstr ""
-msgid "ContainerRegistry|Last Updated"
+msgid "ContainerRegistry|Keep and protect the images that matter most."
msgstr ""
msgid "ContainerRegistry|Login"
@@ -5924,6 +6097,9 @@ msgstr ""
msgid "ContainerRegistry|Please contact your administrator."
msgstr ""
+msgid "ContainerRegistry|Published %{timeInfo}"
+msgstr ""
+
msgid "ContainerRegistry|Push an image"
msgstr ""
@@ -5939,9 +6115,6 @@ msgstr ""
msgid "ContainerRegistry|Remove repository"
msgstr ""
-msgid "ContainerRegistry|Remove selected tags"
-msgstr ""
-
msgid "ContainerRegistry|Remove tag"
msgid_plural "ContainerRegistry|Remove tags"
msgstr[0] ""
@@ -5970,9 +6143,6 @@ msgstr ""
msgid "ContainerRegistry|Sorry, your filter produced no results."
msgstr ""
-msgid "ContainerRegistry|Tag"
-msgstr ""
-
msgid "ContainerRegistry|Tag expiration policy"
msgstr ""
@@ -6117,21 +6287,6 @@ msgstr ""
msgid "Control the display of third party offers."
msgstr ""
-msgid "Control the maximum concurrency of LFS/attachment backfill for this secondary node"
-msgstr ""
-
-msgid "Control the maximum concurrency of container repository operations for this Geo node"
-msgstr ""
-
-msgid "Control the maximum concurrency of repository backfill for this secondary node"
-msgstr ""
-
-msgid "Control the maximum concurrency of verification operations for this Geo node"
-msgstr ""
-
-msgid "Control the minimum interval in days that a repository should be reverified for this primary node"
-msgstr ""
-
msgid "Cookie domain"
msgstr ""
@@ -6273,6 +6428,9 @@ msgstr ""
msgid "Could not find design."
msgstr ""
+msgid "Could not find iteration"
+msgstr ""
+
msgid "Could not remove the trigger."
msgstr ""
@@ -6309,6 +6467,12 @@ msgstr ""
msgid "Coverage"
msgstr ""
+msgid "Coverage Fuzzing"
+msgstr ""
+
+msgid "Crash State"
+msgstr ""
+
msgid "Create"
msgstr ""
@@ -6357,6 +6521,9 @@ msgstr ""
msgid "Create a personal access token on your account to pull or push via %{protocol}."
msgstr ""
+msgid "Create a project pre-populated with the necessary files to get you started quickly."
+msgstr ""
+
msgid "Create an account using:"
msgstr ""
@@ -6525,6 +6692,9 @@ msgstr ""
msgid "Creates branch '%{branch_name}' and a merge request to resolve this issue."
msgstr ""
+msgid "Creating"
+msgstr ""
+
msgid "Creating epic"
msgstr ""
@@ -6576,7 +6746,10 @@ msgstr ""
msgid "Current vulnerabilities count"
msgstr ""
-msgid "CurrentUser|Buy CI minutes"
+msgid "CurrentUser|Buy Pipeline minutes"
+msgstr ""
+
+msgid "CurrentUser|One of your groups is running out"
msgstr ""
msgid "CurrentUser|Profile"
@@ -6591,6 +6764,9 @@ msgstr ""
msgid "CurrentUser|Upgrade"
msgstr ""
+msgid "Custom Attributes"
+msgstr ""
+
msgid "Custom CI configuration path"
msgstr ""
@@ -6853,12 +7029,18 @@ msgstr ""
msgid "DAG"
msgstr ""
+msgid "DAG visualization requires at least 3 dependent jobs."
+msgstr ""
+
msgid "DNS"
msgstr ""
msgid "Dashboard"
msgstr ""
+msgid "Dashboard uid not found"
+msgstr ""
+
msgid "DashboardProjects|All"
msgstr ""
@@ -6883,6 +7065,9 @@ msgstr ""
msgid "Data is still calculating..."
msgstr ""
+msgid "Datasource name not found"
+msgstr ""
+
msgid "Date"
msgstr ""
@@ -7063,6 +7248,9 @@ msgstr ""
msgid "Delete this attachment"
msgstr ""
+msgid "Delete user list"
+msgstr ""
+
msgid "Delete variable"
msgstr ""
@@ -7374,6 +7562,9 @@ msgstr ""
msgid "Deploying to"
msgstr ""
+msgid "Deploying to AWS is easy with GitLab"
+msgstr ""
+
msgid "Deployment Frequency"
msgstr ""
@@ -7735,6 +7926,9 @@ msgstr ""
msgid "Dismissed on pipeline %{pipelineLink} at %{projectLink}"
msgstr ""
+msgid "Display alerts from all your monitoring tools directly within GitLab."
+msgstr ""
+
msgid "Display name"
msgstr ""
@@ -7792,9 +7986,6 @@ msgstr ""
msgid "Don't show again"
msgstr ""
-msgid "Don't worry, you can access this tour by clicking on the help icon in the top right corner and choose <strong>Learn GitLab</strong>."
-msgstr ""
-
msgid "Done"
msgstr ""
@@ -7921,6 +8112,9 @@ msgstr ""
msgid "Edit Release"
msgstr ""
+msgid "Edit Slack integration"
+msgstr ""
+
msgid "Edit Snippet"
msgstr ""
@@ -7960,6 +8154,9 @@ msgstr ""
msgid "Edit issues"
msgstr ""
+msgid "Edit iteration"
+msgstr ""
+
msgid "Edit public deploy key"
msgstr ""
@@ -8008,6 +8205,9 @@ msgstr ""
msgid "Email"
msgstr ""
+msgid "Email Notification"
+msgstr ""
+
msgid "Email address"
msgstr ""
@@ -8020,6 +8220,9 @@ msgstr ""
msgid "Email not verified. Please verify your email in Salesforce."
msgstr ""
+msgid "Email notification for unknown sign-ins"
+msgstr ""
+
msgid "Email patch"
msgstr ""
@@ -8251,6 +8454,9 @@ msgstr ""
msgid "Enforce DNS rebinding attack protection"
msgstr ""
+msgid "Enforce personal access token expiration"
+msgstr ""
+
msgid "Ensure connectivity is available from the GitLab server to the Prometheus server"
msgstr ""
@@ -8320,6 +8526,9 @@ msgstr ""
msgid "Enter the name of your application, and we'll return a unique %{type}."
msgstr ""
+msgid "Enter weights for storages for new repositories."
+msgstr ""
+
msgid "Enter your password to approve"
msgstr ""
@@ -8329,15 +8538,27 @@ msgstr ""
msgid "Environment does not have deployments"
msgstr ""
+msgid "Environment is required for Stages::MetricEndpointInserter"
+msgstr ""
+
+msgid "Environment is required for Stages::VariableEndpointInserter"
+msgstr ""
+
msgid "Environment scope"
msgstr ""
+msgid "Environment variables are applied to all project environments in this instance via the Runner. You can use environment variables for passwords, secret keys, etc. Make variables available to the running application by prepending the variable key with <code>K8S_SECRET_</code>. You can set variables to be:"
+msgstr ""
+
msgid "Environment variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. Additionally, they can be masked so they are hidden in job logs, though they must match certain regexp requirements to do so. You can use environment variables for passwords, secret keys, or whatever you want."
msgstr ""
msgid "Environment variables are configured by your administrator to be %{link_start}protected%{link_end} by default"
msgstr ""
+msgid "Environment variables on this GitLab instance are configured to be %{link_start}protected%{link_end} by default"
+msgstr ""
+
msgid "Environment:"
msgstr ""
@@ -8869,6 +9090,9 @@ msgstr ""
msgid "EventFilterBy|Filter by comments"
msgstr ""
+msgid "EventFilterBy|Filter by designs"
+msgstr ""
+
msgid "EventFilterBy|Filter by epic events"
msgstr ""
@@ -9001,15 +9225,15 @@ msgstr ""
msgid "Expand approvers"
msgstr ""
-msgid "Expand child epics"
-msgstr ""
-
msgid "Expand down"
msgstr ""
msgid "Expand dropdown"
msgstr ""
+msgid "Expand milestones"
+msgstr ""
+
msgid "Expand sidebar"
msgstr ""
@@ -9025,6 +9249,9 @@ msgstr ""
msgid "Expiration date"
msgstr ""
+msgid "Expiration not enforced"
+msgstr ""
+
msgid "Expiration policy for the Container Registry is a perfect solution for keeping the Registry space down while still enjoying the full power of GitLab CI/CD."
msgstr ""
@@ -9145,6 +9372,9 @@ msgstr ""
msgid "Failed Jobs"
msgstr ""
+msgid "Failed on"
+msgstr ""
+
msgid "Failed to add a Zoom meeting"
msgstr ""
@@ -9205,13 +9435,19 @@ msgstr ""
msgid "Failed to install."
msgstr ""
+msgid "Failed to load assignees. Please try again."
+msgstr ""
+
+msgid "Failed to load authors. Please try again."
+msgstr ""
+
msgid "Failed to load emoji list."
msgstr ""
msgid "Failed to load error details from Sentry."
msgstr ""
-msgid "Failed to load errors from Sentry. Error message: %{errorMessage}"
+msgid "Failed to load errors from Sentry."
msgstr ""
msgid "Failed to load group activity metrics. Please try again."
@@ -9292,6 +9528,9 @@ msgstr ""
msgid "Failed to set due date because the date format is invalid."
msgstr ""
+msgid "Failed to set iteration on this issue. Please try again."
+msgstr ""
+
msgid "Failed to signing using smartcard authentication"
msgstr ""
@@ -9514,13 +9753,10 @@ msgstr ""
msgid "FeatureFlags|Target environments"
msgstr ""
-msgid "FeatureFlags|There are no active feature flags"
-msgstr ""
-
-msgid "FeatureFlags|There are no inactive feature flags"
+msgid "FeatureFlags|There was an error fetching the feature flags."
msgstr ""
-msgid "FeatureFlags|There was an error fetching the feature flags."
+msgid "FeatureFlags|There was an error retrieving user lists"
msgstr ""
msgid "FeatureFlags|Try again in a few moments or contact your support team."
@@ -9532,9 +9768,18 @@ msgstr ""
msgid "FeatureFlag|Delete strategy"
msgstr ""
+msgid "FeatureFlag|List"
+msgstr ""
+
msgid "FeatureFlag|Percentage"
msgstr ""
+msgid "FeatureFlag|Select a user list"
+msgstr ""
+
+msgid "FeatureFlag|There are no configured user lists"
+msgstr ""
+
msgid "FeatureFlag|Type"
msgstr ""
@@ -9691,6 +9936,12 @@ msgstr ""
msgid "Filter..."
msgstr ""
+msgid "Find File"
+msgstr ""
+
+msgid "Find bugs in your code with coverage-guided fuzzing"
+msgstr ""
+
msgid "Find by path"
msgstr ""
@@ -9955,6 +10206,9 @@ msgstr ""
msgid "Geo allows you to replicate your GitLab instance to other geographical locations."
msgstr ""
+msgid "Geo nodes are paused using a command run on the node"
+msgstr ""
+
msgid "GeoNodeStatusEvent|%{timeAgoStr} (%{pendingEvents} events)"
msgstr ""
@@ -10039,6 +10293,9 @@ msgstr ""
msgid "GeoNodes|Not checksummed"
msgstr ""
+msgid "GeoNodes|Package files"
+msgstr ""
+
msgid "GeoNodes|Pausing replication stops the sync process. Are you sure?"
msgstr ""
@@ -10057,6 +10314,9 @@ msgstr ""
msgid "GeoNodes|Replication slots"
msgstr ""
+msgid "GeoNodes|Replication status"
+msgstr ""
+
msgid "GeoNodes|Repositories"
msgstr ""
@@ -10267,6 +10527,9 @@ msgstr ""
msgid "Geo|Synchronization failed - %{error}"
msgstr ""
+msgid "Geo|Synchronization of %{itemTitle} is disabled."
+msgstr ""
+
msgid "Geo|The database is currently %{db_lag} behind the primary node."
msgstr ""
@@ -10579,6 +10842,9 @@ msgstr ""
msgid "Go to %{link_to_google_takeout}."
msgstr ""
+msgid "Go to <strong>Issues</strong> > <strong>Boards</strong> to access your personalized learning issue board."
+msgstr ""
+
msgid "Go to Pipelines"
msgstr ""
@@ -10678,9 +10944,6 @@ msgstr ""
msgid "Go to your snippets"
msgstr ""
-msgid "Golden Tanuki"
-msgstr ""
-
msgid "Google Cloud Platform"
msgstr ""
@@ -10702,6 +10965,9 @@ msgstr ""
msgid "Grafana URL"
msgstr ""
+msgid "Grafana response contains invalid json"
+msgstr ""
+
msgid "GrafanaIntegration|API Token"
msgstr ""
@@ -10750,9 +11016,6 @@ msgstr ""
msgid "Group %{group_name} was successfully created."
msgstr ""
-msgid "Group '%{group_name}' is being imported."
-msgstr ""
-
msgid "Group Audit Events"
msgstr ""
@@ -10789,9 +11052,6 @@ msgstr ""
msgid "Group by:"
msgstr ""
-msgid "Group could not be imported: %{errors}"
-msgstr ""
-
msgid "Group description"
msgstr ""
@@ -10819,9 +11079,15 @@ msgstr ""
msgid "Group has not been marked for deletion"
msgstr ""
+msgid "Group import could not be scheduled"
+msgstr ""
+
msgid "Group info:"
msgstr ""
+msgid "Group is required when cluster_type is :group"
+msgstr ""
+
msgid "Group maintainers can register group runners in the %{link}"
msgstr ""
@@ -10885,6 +11151,24 @@ msgstr ""
msgid "GroupActivyMetrics|Recent activity (last 90 days)"
msgstr ""
+msgid "GroupImport|Failed to import group."
+msgstr ""
+
+msgid "GroupImport|Group '%{group_name}' is being imported."
+msgstr ""
+
+msgid "GroupImport|Group could not be imported: %{errors}"
+msgstr ""
+
+msgid "GroupImport|Please wait while we import the group for you. Refresh at will."
+msgstr ""
+
+msgid "GroupImport|The group was successfully imported."
+msgstr ""
+
+msgid "GroupImport|Unable to process group import file"
+msgstr ""
+
msgid "GroupRoadmap|%{dateWord} – No end date"
msgstr ""
@@ -11152,6 +11436,9 @@ msgstr ""
msgid "Groups (%{groups})"
msgstr ""
+msgid "Groups and projects"
+msgstr ""
+
msgid "Groups can also be nested by creating %{subgroup_docs_link_start}subgroups%{subgroup_docs_link_end}."
msgstr ""
@@ -11197,6 +11484,33 @@ msgstr ""
msgid "GroupsEmptyState|You can manage your group member’s permissions and access to each project in the group."
msgstr ""
+msgid "GroupsNew|Contact an administrator to enable options for importing your group."
+msgstr ""
+
+msgid "GroupsNew|Create"
+msgstr ""
+
+msgid "GroupsNew|Create group"
+msgstr ""
+
+msgid "GroupsNew|GitLab group export"
+msgstr ""
+
+msgid "GroupsNew|Import"
+msgstr ""
+
+msgid "GroupsNew|Import group"
+msgstr ""
+
+msgid "GroupsNew|My Awesome Group"
+msgstr ""
+
+msgid "GroupsNew|No import options available"
+msgstr ""
+
+msgid "GroupsNew|To copy a GitLab group between installations, navigate to the group settings page for the original installation, generate an export file, and upload it here."
+msgstr ""
+
msgid "GroupsTree|Are you sure you want to leave the \"%{fullName}\" group?"
msgstr ""
@@ -11299,6 +11613,9 @@ msgstr ""
msgid "Helps reduce request volume for protected paths"
msgstr ""
+msgid "Here are all your projects in your group, including the one you just created. To start, let’s take a look at your personalized learning project which will help you learn about GitLab at your own pace."
+msgstr ""
+
msgid "Here you will find recent merge request activity"
msgstr ""
@@ -11312,6 +11629,9 @@ msgid "Hide chart"
msgid_plural "Hide charts"
msgstr[0] ""
+msgid "Hide details"
+msgstr ""
+
msgid "Hide file browser"
msgstr ""
@@ -11321,6 +11641,9 @@ msgstr ""
msgid "Hide host keys manual input"
msgstr ""
+msgid "Hide list"
+msgstr ""
+
msgid "Hide marketing-related entries from help"
msgstr ""
@@ -11340,9 +11663,6 @@ msgstr[0] ""
msgid "Hide values"
msgstr ""
-msgid "Hiding all labels"
-msgstr ""
-
msgid "High or unknown vulnerabilities present"
msgstr ""
@@ -11427,6 +11747,9 @@ msgstr ""
msgid "ID:"
msgstr ""
+msgid "IDE"
+msgstr ""
+
msgid "IDE|Allow live previews of JavaScript projects in the Web IDE using CodeSandbox Live Preview."
msgstr ""
@@ -11517,10 +11840,10 @@ msgstr ""
msgid "If enabled"
msgstr ""
-msgid "If enabled, access to projects will be validated on an external service using their classification label."
+msgid "If enabled, GitLab will handle Object Storage replication using Geo. %{linkStart}More information%{linkEnd}"
msgstr ""
-msgid "If enabled, and if object storage is enabled, GitLab will handle Object Storage replication using Geo"
+msgid "If enabled, access to projects will be validated on an external service using their classification label."
msgstr ""
msgid "If there is no previous license or if the previous license has expired, some GitLab functionality will be blocked until a new, valid license is uploaded."
@@ -11881,6 +12204,9 @@ msgid "Instance"
msgid_plural "Instances"
msgstr[0] ""
+msgid "Instance Configuration"
+msgstr ""
+
msgid "Instance Statistics visibility"
msgstr ""
@@ -11902,9 +12228,6 @@ msgstr ""
msgid "Integrations"
msgstr ""
-msgid "Integrations allow you to integrate GitLab with other applications"
-msgstr ""
-
msgid "Integrations|All details"
msgstr ""
@@ -12016,6 +12339,9 @@ msgstr ""
msgid "Invalid query"
msgstr ""
+msgid "Invalid repository bundle for snippet with id %{snippet_id}"
+msgstr ""
+
msgid "Invalid repository path"
msgstr ""
@@ -12034,6 +12360,9 @@ msgstr ""
msgid "Invalid two-factor code."
msgstr ""
+msgid "Invalid yaml"
+msgstr ""
+
msgid "Invitation"
msgstr ""
@@ -12253,15 +12582,24 @@ msgstr ""
msgid "It seems like the Dependency Scanning job ran successfully, but no dependencies have been detected in your project."
msgstr ""
+msgid "It seems that there is currently no available data for code coverage"
+msgstr ""
+
msgid "It's you"
msgstr ""
+msgid "Iteration"
+msgstr ""
+
msgid "Iteration changed to"
msgstr ""
msgid "Iteration removed"
msgstr ""
+msgid "Iteration updated"
+msgstr ""
+
msgid "Iterations"
msgstr ""
@@ -12292,12 +12630,18 @@ msgstr ""
msgid "January"
msgstr ""
+msgid "Jira Issues"
+msgstr ""
+
msgid "Jira import is already running."
msgstr ""
msgid "Jira integration not configured."
msgstr ""
+msgid "Jira project key is not configured"
+msgstr ""
+
msgid "Jira project: %{importProject}"
msgstr ""
@@ -12478,6 +12822,9 @@ msgstr ""
msgid "Keep divergent refs"
msgstr ""
+msgid "Kerberos access denied"
+msgstr ""
+
msgid "Key"
msgstr ""
@@ -12487,10 +12834,13 @@ msgstr ""
msgid "Key: %{key}"
msgstr ""
-msgid "Keyboard Shortcuts"
+msgid "Keyboard shortcuts"
msgstr ""
-msgid "Keyboard shortcuts"
+msgid "Keys"
+msgstr ""
+
+msgid "Ki"
msgstr ""
msgid "Kubernetes"
@@ -12532,6 +12882,9 @@ msgstr ""
msgid "LDAP"
msgstr ""
+msgid "LDAP Synchronization"
+msgstr ""
+
msgid "LDAP settings"
msgstr ""
@@ -12755,12 +13108,18 @@ msgstr ""
msgid "Learn more about custom project templates"
msgstr ""
+msgid "Learn more about deploying to AWS"
+msgstr ""
+
msgid "Learn more about deploying to a cluster"
msgstr ""
msgid "Learn more about group-level project templates"
msgstr ""
+msgid "Learn more about job dependencies"
+msgstr ""
+
msgid "Learn more about signing commits"
msgstr ""
@@ -12812,9 +13171,21 @@ msgstr ""
msgid "License History"
msgstr ""
+msgid "License ID:"
+msgstr ""
+
+msgid "License URL"
+msgstr ""
+
msgid "License-Check"
msgstr ""
+msgid "LicenseCompliance|%{docLinkStart}License Approvals%{docLinkEnd} are active"
+msgstr ""
+
+msgid "LicenseCompliance|%{docLinkStart}License Approvals%{docLinkEnd} are inactive"
+msgstr ""
+
msgid "LicenseCompliance|Add a license"
msgstr ""
@@ -12836,9 +13207,15 @@ msgstr ""
msgid "LicenseCompliance|Deny"
msgstr ""
+msgid "LicenseCompliance|Learn more about %{linkStart}License Approvals%{linkEnd}"
+msgstr ""
+
msgid "LicenseCompliance|License"
msgstr ""
+msgid "LicenseCompliance|License Approvals"
+msgstr ""
+
msgid "LicenseCompliance|License Compliance detected %d license and policy violation for the source branch only; approval required"
msgid_plural "LicenseCompliance|License Compliance detected %d licenses and policy violations for the source branch only; approval required"
msgstr[0] ""
@@ -13054,6 +13431,9 @@ msgstr ""
msgid "List your Bitbucket Server repositories"
msgstr ""
+msgid "Lists"
+msgstr ""
+
msgid "Live preview"
msgstr ""
@@ -13237,6 +13617,9 @@ msgstr ""
msgid "Manage project labels"
msgstr ""
+msgid "Manage storage usage"
+msgstr ""
+
msgid "Manage two-factor authentication"
msgstr ""
@@ -13252,6 +13635,9 @@ msgstr ""
msgid "Manifest file import"
msgstr ""
+msgid "Manifest import"
+msgstr ""
+
msgid "Manual job"
msgstr ""
@@ -13399,6 +13785,12 @@ msgstr ""
msgid "Maximum field length"
msgstr ""
+msgid "Maximum file size is 2MB. Please select a smaller file."
+msgstr ""
+
+msgid "Maximum import size (MB)"
+msgstr ""
+
msgid "Maximum job timeout"
msgstr ""
@@ -13438,6 +13830,9 @@ msgstr ""
msgid "Maximum size of Elasticsearch bulk indexing requests."
msgstr ""
+msgid "Maximum size of import files."
+msgstr ""
+
msgid "Maximum size of individual attachments in comments."
msgstr ""
@@ -13489,6 +13884,9 @@ msgstr ""
msgid "Merge (when the pipeline succeeds)"
msgstr ""
+msgid "Merge Conflicts"
+msgstr ""
+
msgid "Merge Request"
msgstr ""
@@ -13615,6 +14013,9 @@ msgstr ""
msgid "MergeRequests|Squash task canceled: another squash is already in progress."
msgstr ""
+msgid "MergeRequests|This project does not allow squashing commits when merge requests are accepted."
+msgstr ""
+
msgid "MergeRequests|Thread stays resolved"
msgstr ""
@@ -13768,6 +14169,12 @@ msgstr ""
msgid "MetricsSettings|Add a button to the metrics dashboard linking directly to your existing external dashboard."
msgstr ""
+msgid "MetricsSettings|Choose whether to display dashboard metrics in UTC or the user's local timezone."
+msgstr ""
+
+msgid "MetricsSettings|Dashboard timezone"
+msgstr ""
+
msgid "MetricsSettings|External dashboard URL"
msgstr ""
@@ -13777,6 +14184,12 @@ msgstr ""
msgid "MetricsSettings|Metrics Dashboard"
msgstr ""
+msgid "MetricsSettings|UTC (Coordinated Universal Time)"
+msgstr ""
+
+msgid "MetricsSettings|User's local timezone"
+msgstr ""
+
msgid "Metrics|Add metric"
msgstr ""
@@ -13792,6 +14205,9 @@ msgstr ""
msgid "Metrics|Create metric"
msgstr ""
+msgid "Metrics|Current"
+msgstr ""
+
msgid "Metrics|Delete metric"
msgstr ""
@@ -13841,6 +14257,9 @@ msgstr ""
msgid "Metrics|Max"
msgstr ""
+msgid "Metrics|Min"
+msgstr ""
+
msgid "Metrics|Must be a valid PromQL query."
msgstr ""
@@ -13856,6 +14275,9 @@ msgstr ""
msgid "Metrics|Refresh dashboard"
msgstr ""
+msgid "Metrics|Select a value"
+msgstr ""
+
msgid "Metrics|Star dashboard"
msgstr ""
@@ -13874,12 +14296,18 @@ msgstr ""
msgid "Metrics|There was an error getting annotations information."
msgstr ""
+msgid "Metrics|There was an error getting dashboard validation warnings information."
+msgstr ""
+
msgid "Metrics|There was an error getting deployment information."
msgstr ""
msgid "Metrics|There was an error getting environments information."
msgstr ""
+msgid "Metrics|There was an error getting options for variable \"%{name}\"."
+msgstr ""
+
msgid "Metrics|There was an error trying to validate your query"
msgstr ""
@@ -13922,6 +14350,9 @@ msgstr ""
msgid "Metrics|You're about to permanently delete this metric. This cannot be undone."
msgstr ""
+msgid "Metrics|Your dashboard schema is invalid. Edit the dashboard to correct the YAML schema."
+msgstr ""
+
msgid "Metrics|e.g. HTTP requests"
msgstr ""
@@ -13937,12 +14368,18 @@ msgstr ""
msgid "Metrics|e.g. req/sec"
msgstr ""
+msgid "Mi"
+msgstr ""
+
msgid "Microsoft Azure"
msgstr ""
msgid "Middleman project with Static Site Editor support"
msgstr ""
+msgid "Migrate your data from an external source like GitHub, Bitbucket, or another instance of GitLab."
+msgstr ""
+
msgid "Migrated %{success_count}/%{total_count} files."
msgstr ""
@@ -14076,6 +14513,9 @@ msgstr ""
msgid "Minimum capacity to be available before we schedule more mirrors preemptively."
msgstr ""
+msgid "Minimum interval in days"
+msgstr ""
+
msgid "Minimum length is %{minimum_password_length} characters"
msgstr ""
@@ -14262,6 +14702,12 @@ msgstr ""
msgid "Multiple uploaders found: %{uploader_types}"
msgstr ""
+msgid "Must match with the %{codeStart}external_url%{codeEnd} in %{codeStart}/etc/gitlab/gitlab.rb%{codeEnd}."
+msgstr ""
+
+msgid "Must match with the %{codeStart}geo_node_name%{codeEnd} in %{codeStart}/etc/gitlab/gitlab.rb%{codeEnd}. %{linkStart}More information%{linkEnd}"
+msgstr ""
+
msgid "My Awesome Group"
msgstr ""
@@ -14376,10 +14822,16 @@ msgstr ""
msgid "New Application"
msgstr ""
+msgid "New Branch"
+msgstr ""
+
+msgid "New Deploy Key"
+msgstr ""
+
msgid "New Environment"
msgstr ""
-msgid "New Geo Node"
+msgid "New File"
msgstr ""
msgid "New Group"
@@ -14395,15 +14847,15 @@ msgid "New Issue"
msgid_plural "New Issues"
msgstr[0] ""
-msgid "New Iteration"
-msgstr ""
-
msgid "New Jira import"
msgstr ""
msgid "New Label"
msgstr ""
+msgid "New Merge Request"
+msgstr ""
+
msgid "New Milestone"
msgstr ""
@@ -14422,6 +14874,9 @@ msgstr ""
msgid "New Snippet"
msgstr ""
+msgid "New User"
+msgstr ""
+
msgid "New branch"
msgstr ""
@@ -14464,6 +14919,9 @@ msgstr ""
msgid "New issue title"
msgstr ""
+msgid "New iteration"
+msgstr ""
+
msgid "New iteration created"
msgstr ""
@@ -14641,6 +15099,12 @@ msgstr ""
msgid "No grouping"
msgstr ""
+msgid "No iteration"
+msgstr ""
+
+msgid "No iterations to show"
+msgstr ""
+
msgid "No job log"
msgstr ""
@@ -14683,6 +15147,9 @@ msgstr ""
msgid "No other labels with such name or description"
msgstr ""
+msgid "No panels matching properties %{opts}"
+msgstr ""
+
msgid "No parent group"
msgstr ""
@@ -14716,6 +15183,9 @@ msgstr ""
msgid "No schedules"
msgstr ""
+msgid "No stack trace for this error"
+msgstr ""
+
msgid "No starrers matched your search"
msgstr ""
@@ -14728,7 +15198,7 @@ msgstr ""
msgid "No test coverage"
msgstr ""
-msgid "No thanks, don't show this again"
+msgid "No thanks"
msgstr ""
msgid "No vulnerabilities present"
@@ -14743,9 +15213,6 @@ msgstr ""
msgid "No, directly import the existing email addresses and usernames."
msgstr ""
-msgid "No, not interested right now"
-msgstr ""
-
msgid "No. of commits"
msgstr ""
@@ -14791,9 +15258,6 @@ msgstr ""
msgid "Not found."
msgstr ""
-msgid "Not helpful"
-msgstr ""
-
msgid "Not now"
msgstr ""
@@ -14950,6 +15414,9 @@ msgstr ""
msgid "Notifications on"
msgstr ""
+msgid "Notify users by email when sign-in location is not recognized"
+msgstr ""
+
msgid "Nov"
msgstr ""
@@ -15016,9 +15483,6 @@ msgstr ""
msgid "Oh no!"
msgstr ""
-msgid "Ok let's go"
-msgstr ""
-
msgid "Oldest first"
msgstr ""
@@ -15031,16 +15495,49 @@ msgstr ""
msgid "On track"
msgstr ""
+msgid "OnDemandScans|Attached branch"
+msgstr ""
+
+msgid "OnDemandScans|Attached branch is where the scan job runs."
+msgstr ""
+
+msgid "OnDemandScans|Could not run the scan. Please try again."
+msgstr ""
+
msgid "OnDemandScans|Create new DAST scan"
msgstr ""
+msgid "OnDemandScans|DAST will scan the target URL and any discovered sub URLs."
+msgstr ""
+
+msgid "OnDemandScans|New on-demand DAST scan"
+msgstr ""
+
msgid "OnDemandScans|On-demand Scans"
msgstr ""
+msgid "OnDemandScans|On-demand scans run outside the DevOps cycle and find vulnerabilities in your projects. %{learnMoreLinkStart}Learn more%{learnMoreLinkEnd}"
+msgstr ""
+
+msgid "OnDemandScans|Only a passive scan can be performed on demand."
+msgstr ""
+
+msgid "OnDemandScans|Passive DAST Scan"
+msgstr ""
+
+msgid "OnDemandScans|Please enter a valid URL format, ex: http://www.example.com/home"
+msgstr ""
+
+msgid "OnDemandScans|Run this scan"
+msgstr ""
+
+msgid "OnDemandScans|Scan mode"
+msgstr ""
+
msgid "OnDemandScans|Schedule or run scans immediately against target sites. Currently available on-demand scan type: DAST. %{helpLinkStart}More information%{helpLinkEnd}"
msgstr ""
-msgid "Onboarding"
+msgid "OnDemandScans|Target URL"
msgstr ""
msgid "Once imported, repositories can be mirrored over SSH. Read more %{link_start}here%{link_end}."
@@ -15065,6 +15562,9 @@ msgstr ""
msgid "One or more of you personal access tokens were revoked"
msgstr ""
+msgid "One or more of your %{provider} projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
+msgstr ""
+
msgid "One or more of your Bitbucket projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
msgstr ""
@@ -15566,6 +16066,9 @@ msgstr ""
msgid "Parent epic is not present."
msgstr ""
+msgid "Parsing error for param :embed_json. %{message}"
+msgstr ""
+
msgid "Part of merge request changes"
msgstr ""
@@ -15578,6 +16081,9 @@ msgstr ""
msgid "Passed"
msgstr ""
+msgid "Passed on"
+msgstr ""
+
msgid "Password"
msgstr ""
@@ -15662,6 +16168,9 @@ msgstr ""
msgid "Perform common operations on GitLab project"
msgstr ""
+msgid "Performance and resource management"
+msgstr ""
+
msgid "Performance optimization"
msgstr ""
@@ -15869,7 +16378,7 @@ msgstr ""
msgid "Pipelines|Something went wrong while cleaning runners cache."
msgstr ""
-msgid "Pipelines|There are currently no %{scope} pipelines."
+msgid "Pipelines|There are currently no finished pipelines."
msgstr ""
msgid "Pipelines|There are currently no pipelines."
@@ -15980,6 +16489,9 @@ msgstr ""
msgid "Pipeline|Stop pipeline #%{pipelineId}?"
msgstr ""
+msgid "Pipeline|Tag name"
+msgstr ""
+
msgid "Pipeline|Trigger author"
msgstr ""
@@ -16061,12 +16573,18 @@ msgstr ""
msgid "Please check your email (%{email}) to verify that you own this address and unlock the power of CI/CD. Didn't receive it? %{resend_link}. Wrong email address? %{update_link}."
msgstr ""
+msgid "Please choose a file"
+msgstr ""
+
msgid "Please choose a group URL with no special characters."
msgstr ""
msgid "Please complete your profile with email address"
msgstr ""
+msgid "Please convert %{linkStart}them to Git%{linkEnd}, and go through the %{linkToImportFlow} again."
+msgstr ""
+
msgid "Please convert them to %{link_to_git}, and go through the %{link_to_import_flow} again."
msgstr ""
@@ -16124,6 +16642,9 @@ msgstr ""
msgid "Please provide a valid email address."
msgstr ""
+msgid "Please provide attributes to update"
+msgstr ""
+
msgid "Please refer to <a href=\"%{docs_url}\">%{docs_url}</a>"
msgstr ""
@@ -16859,6 +17380,9 @@ msgstr ""
msgid "Project has too many %{label_for_message} to search"
msgstr ""
+msgid "Project is required when cluster_type is :project"
+msgstr ""
+
msgid "Project members"
msgstr ""
@@ -16976,7 +17500,40 @@ msgstr ""
msgid "ProjectService|Comment"
msgstr ""
-msgid "ProjectService|Comment will be posted on each event"
+msgid "ProjectService|Event will be triggered by a push to the repository"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a commit is created/updated"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a confidential issue is created/updated/closed"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a deployment finishes"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a merge request is created/updated/merged"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a new tag is pushed to the repository"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a new, unique alert is recorded"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a pipeline status changes"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a wiki page is created/updated"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when an issue is created/updated/closed"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when someone adds a comment"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when someone adds a comment on a confidential issue"
msgstr ""
msgid "ProjectService|Perform common operations on GitLab project: %{project_name}"
@@ -17027,6 +17584,9 @@ msgstr ""
msgid "ProjectSettings|Enable 'Delete source branch' option by default"
msgstr ""
+msgid "ProjectSettings|Enable merge trains and pipelines for merged results"
+msgstr ""
+
msgid "ProjectSettings|Every merge creates a merge commit"
msgstr ""
@@ -17057,7 +17617,7 @@ msgstr ""
msgid "ProjectSettings|Forks"
msgstr ""
-msgid "ProjectSettings|Git Large File Storage"
+msgid "ProjectSettings|Git Large File Storage (LFS)"
msgstr ""
msgid "ProjectSettings|Internal"
@@ -17093,9 +17653,6 @@ msgstr ""
msgid "ProjectSettings|Merge options"
msgstr ""
-msgid "ProjectSettings|Merge pipelines will try to validate the post-merge result prior to merging"
-msgstr ""
-
msgid "ProjectSettings|Merge requests"
msgstr ""
@@ -17123,6 +17680,9 @@ msgstr ""
msgid "ProjectSettings|Pipelines"
msgstr ""
+msgid "ProjectSettings|Pipelines for merge requests must be enabled in the CI/CD configuration file, or pipelines could be unresolvable or dropped"
+msgstr ""
+
msgid "ProjectSettings|Pipelines must succeed"
msgstr ""
@@ -17150,6 +17710,9 @@ msgstr ""
msgid "ProjectSettings|Show link to create/view merge request when pushing from the command line"
msgstr ""
+msgid "ProjectSettings|Skipped pipelines are considered successful"
+msgstr ""
+
msgid "ProjectSettings|Snippets"
msgstr ""
@@ -17165,6 +17728,9 @@ msgstr ""
msgid "ProjectSettings|These checks must pass before merge requests can be merged"
msgstr ""
+msgid "ProjectSettings|This introduces the risk of merging changes that will not pass the pipeline."
+msgstr ""
+
msgid "ProjectSettings|This setting is applied on the server level and can be overridden by an admin."
msgstr ""
@@ -17342,15 +17908,27 @@ msgstr ""
msgid "ProjectsNew|Blank project"
msgstr ""
+msgid "ProjectsNew|Connect your external repository to GitLab CI/CD."
+msgstr ""
+
msgid "ProjectsNew|Contact an administrator to enable options for importing your project."
msgstr ""
msgid "ProjectsNew|Create"
msgstr ""
+msgid "ProjectsNew|Create a blank project to house your files, plan your work, and collaborate on code, among other things."
+msgstr ""
+
+msgid "ProjectsNew|Create blank project"
+msgstr ""
+
msgid "ProjectsNew|Create from template"
msgstr ""
+msgid "ProjectsNew|Create new project"
+msgstr ""
+
msgid "ProjectsNew|Creating project & repository."
msgstr ""
@@ -17375,6 +17953,9 @@ msgstr ""
msgid "ProjectsNew|Project description %{tag_start}(optional)%{tag_end}"
msgstr ""
+msgid "ProjectsNew|Run CI/CD for external repository"
+msgstr ""
+
msgid "ProjectsNew|Template"
msgstr ""
@@ -17636,6 +18217,9 @@ msgstr ""
msgid "Protected Branch"
msgstr ""
+msgid "Protected Branches"
+msgstr ""
+
msgid "Protected Environment"
msgstr ""
@@ -17648,6 +18232,9 @@ msgstr ""
msgid "Protected Tag"
msgstr ""
+msgid "Protected Tags"
+msgstr ""
+
msgid "Protected branches"
msgstr ""
@@ -17747,6 +18334,9 @@ msgstr ""
msgid "Provider"
msgstr ""
+msgid "Proxy support for this API is not available currently"
+msgstr ""
+
msgid "Pseudonymizer data collection"
msgstr ""
@@ -17771,6 +18361,9 @@ msgstr ""
msgid "Publish to status page"
msgstr ""
+msgid "Published on status page"
+msgstr ""
+
msgid "Publishes this issue to the associated status page."
msgstr ""
@@ -17945,6 +18538,9 @@ msgstr ""
msgid "Recipe"
msgstr ""
+msgid "Reconfigure"
+msgstr ""
+
msgid "Recover hidden stage"
msgstr ""
@@ -18030,6 +18626,9 @@ msgstr ""
msgid "Registration|Your profile"
msgstr ""
+msgid "Regulate approvals by authors/committers, based on compliance frameworks. Can be changed only at the instance level."
+msgstr ""
+
msgid "Rejected (closed)"
msgstr ""
@@ -18079,15 +18678,24 @@ msgstr ""
msgid "ReleaseAssetLinkType|Image"
msgstr ""
+msgid "ReleaseAssetLinkType|Images"
+msgstr ""
+
msgid "ReleaseAssetLinkType|Other"
msgstr ""
msgid "ReleaseAssetLinkType|Package"
msgstr ""
+msgid "ReleaseAssetLinkType|Packages"
+msgstr ""
+
msgid "ReleaseAssetLinkType|Runbook"
msgstr ""
+msgid "ReleaseAssetLinkType|Runbooks"
+msgstr ""
+
msgid "Releases"
msgstr ""
@@ -18100,6 +18708,9 @@ msgstr ""
msgid "Releases documentation"
msgstr ""
+msgid "Releases|New Release"
+msgstr ""
+
msgid "Release|Something went wrong while getting the release details"
msgstr ""
@@ -18172,6 +18783,9 @@ msgstr ""
msgid "Remove fork relationship"
msgstr ""
+msgid "Remove from batch"
+msgstr ""
+
msgid "Remove from board"
msgstr ""
@@ -18361,6 +18975,12 @@ msgstr ""
msgid "Replication"
msgstr ""
+msgid "Replication enabled"
+msgstr ""
+
+msgid "Replication paused"
+msgstr ""
+
msgid "Reply by email"
msgstr ""
@@ -18388,15 +19008,15 @@ msgstr ""
msgid "Reported %{timeAgo} by %{reportedBy}"
msgstr ""
-msgid "Reported Resource Changes: %{addNum} to add, %{changeNum} to change, %{deleteNum} to delete"
-msgstr ""
-
msgid "Reporter"
msgstr ""
msgid "Reporting"
msgstr ""
+msgid "Reports"
+msgstr ""
+
msgid "Reports|%{combinedString} and %{resolvedString}"
msgstr ""
@@ -18434,6 +19054,9 @@ msgstr ""
msgid "Reports|Failure"
msgstr ""
+msgid "Reports|Identifier"
+msgstr ""
+
msgid "Reports|Metrics reports are loading"
msgstr ""
@@ -18446,6 +19069,9 @@ msgstr ""
msgid "Reports|Metrics reports failed loading results"
msgstr ""
+msgid "Reports|Scanner"
+msgstr ""
+
msgid "Reports|Severity"
msgstr ""
@@ -18494,15 +19120,33 @@ msgstr ""
msgid "Repository cleanup has started. You will receive an email once the cleanup operation is complete."
msgstr ""
+msgid "Repository files count over the limit"
+msgstr ""
+
+msgid "Repository has an invalid default branch name."
+msgstr ""
+
+msgid "Repository has more than one branch."
+msgstr ""
+
msgid "Repository has no locks."
msgstr ""
+msgid "Repository has tags."
+msgstr ""
+
msgid "Repository maintenance"
msgstr ""
msgid "Repository mirroring"
msgstr ""
+msgid "Repository must contain at least 1 file."
+msgstr ""
+
+msgid "Repository size is above the limit."
+msgstr ""
+
msgid "Repository static objects"
msgstr ""
@@ -18512,7 +19156,7 @@ msgstr ""
msgid "Repository sync capacity"
msgstr ""
-msgid "Repository: %{counter_repositories} / Wikis: %{counter_wikis} / Build Artifacts: %{counter_build_artifacts} / LFS: %{counter_lfs_objects}"
+msgid "Repository: %{counter_repositories} / Wikis: %{counter_wikis} / Build Artifacts: %{counter_build_artifacts} / LFS: %{counter_lfs_objects} / Snippets: %{counter_snippets}"
msgstr ""
msgid "RepositorySettingsAccessLevel|Select"
@@ -18524,6 +19168,9 @@ msgstr ""
msgid "Request Headers"
msgstr ""
+msgid "Request details"
+msgstr ""
+
msgid "Request parameter %{param} is missing."
msgstr ""
@@ -18628,6 +19275,9 @@ msgstr ""
msgid "Reset template"
msgstr ""
+msgid "Reset to project defaults"
+msgstr ""
+
msgid "Resetting the authorization key for this project will require updating the authorization key in every alert source it is enabled in."
msgstr ""
@@ -18724,9 +19374,6 @@ msgstr ""
msgid "Resume"
msgstr ""
-msgid "Resume replication"
-msgstr ""
-
msgid "Resync"
msgstr ""
@@ -19338,6 +19985,9 @@ msgstr ""
msgid "SecurityReports|False positive"
msgstr ""
+msgid "SecurityReports|Group Security Dashboard"
+msgstr ""
+
msgid "SecurityReports|Hide dismissed"
msgstr ""
@@ -19392,10 +20042,13 @@ msgstr ""
msgid "SecurityReports|Remove project from dashboard"
msgstr ""
-msgid "SecurityReports|Report type"
+msgid "SecurityReports|Return to dashboard"
+msgstr ""
+
+msgid "SecurityReports|Scan details"
msgstr ""
-msgid "SecurityReports|Return to dashboard"
+msgid "SecurityReports|Scanner type"
msgstr ""
msgid "SecurityReports|Security Dashboard"
@@ -19452,6 +20105,9 @@ msgstr ""
msgid "SecurityReports|There was an error while generating the report."
msgstr ""
+msgid "SecurityReports|Unable to add %{invalidProjectsMessage}"
+msgstr ""
+
msgid "SecurityReports|Unable to add %{invalidProjects}"
msgstr ""
@@ -19566,6 +20222,9 @@ msgstr ""
msgid "Select due date"
msgstr ""
+msgid "Select file"
+msgstr ""
+
msgid "Select group or project"
msgstr ""
@@ -19629,9 +20288,6 @@ msgstr ""
msgid "Select the branch you want to set as the default for this project. All merge requests and commits will automatically be made against this branch unless you specify a different one."
msgstr ""
-msgid "Select the configured storage available for new repositories to be placed on."
-msgstr ""
-
msgid "Select the custom project template source group."
msgstr ""
@@ -19824,12 +20480,6 @@ msgstr ""
msgid "Service Desk is enabled but not yet active"
msgstr ""
-msgid "Service Desk is off"
-msgstr ""
-
-msgid "Service Desk is on"
-msgstr ""
-
msgid "Service Templates"
msgstr ""
@@ -19917,6 +20567,15 @@ msgstr ""
msgid "Set the milestone to %{milestone_reference}."
msgstr ""
+msgid "Set the number of concurrent requests this secondary node will make to the primary node while backfilling."
+msgstr ""
+
+msgid "Set the synchronization and verification capacity for the secondary node."
+msgstr ""
+
+msgid "Set the timeout in seconds to send a secondary node status to the primary and IPs allowed for the secondary nodes."
+msgstr ""
+
msgid "Set time estimate"
msgstr ""
@@ -19956,6 +20615,9 @@ msgstr ""
msgid "Set weight to %{weight}."
msgstr ""
+msgid "Set what should be replicated by choosing specific projects or groups by the secondary node."
+msgstr ""
+
msgid "SetPasswordToCloneLink|set a password"
msgstr ""
@@ -20082,6 +20744,9 @@ msgstr ""
msgid "Show complete raw log"
msgstr ""
+msgid "Show details"
+msgstr ""
+
msgid "Show file browser"
msgstr ""
@@ -20091,10 +20756,13 @@ msgstr ""
msgid "Show latest version"
msgstr ""
+msgid "Show list"
+msgstr ""
+
msgid "Show me everything"
msgstr ""
-msgid "Show me how"
+msgid "Show me how to add a pipeline"
msgstr ""
msgid "Show me more advanced stuff"
@@ -20141,9 +20809,6 @@ msgstr ""
msgid "Showing all issues"
msgstr ""
-msgid "Showing all labels"
-msgstr ""
-
msgid "Showing last %{size} of log -"
msgstr ""
@@ -20255,9 +20920,6 @@ msgstr ""
msgid "Skip outdated deployment jobs"
msgstr ""
-msgid "Skip this for now"
-msgstr ""
-
msgid "Skipped"
msgstr ""
@@ -20402,6 +21064,9 @@ msgstr ""
msgid "Something went wrong while adding your award. Please try again."
msgstr ""
+msgid "Something went wrong while applying the batch of suggestions. Please try again."
+msgstr ""
+
msgid "Something went wrong while applying the suggestion. Please try again."
msgstr ""
@@ -20504,10 +21169,13 @@ msgstr ""
msgid "Something went wrong while updating a requirement."
msgstr ""
+msgid "Something went wrong while updating assignees"
+msgstr ""
+
msgid "Something went wrong while updating your list settings"
msgstr ""
-msgid "Something went wrong with your automatic subscription renewal"
+msgid "Something went wrong with your automatic subscription renewal."
msgstr ""
msgid "Something went wrong, unable to add %{project} to dashboard"
@@ -20525,6 +21193,9 @@ msgstr ""
msgid "Something went wrong, unable to search projects"
msgstr ""
+msgid "Something went wrong."
+msgstr ""
+
msgid "Something went wrong. Please try again."
msgstr ""
@@ -20708,6 +21379,9 @@ msgstr ""
msgid "Source code"
msgstr ""
+msgid "Source code (%{fileExtension})"
+msgstr ""
+
msgid "Source is not available"
msgstr ""
@@ -20792,6 +21466,9 @@ msgstr ""
msgid "Stack trace"
msgstr ""
+msgid "Stacktrace snippet"
+msgstr ""
+
msgid "Stage"
msgstr ""
@@ -20897,6 +21574,9 @@ msgstr ""
msgid "Start thread & reopen %{noteable_name}"
msgstr ""
+msgid "Start using Directed Acyclic Graphs (DAG)"
+msgstr ""
+
msgid "Start your Free Gold Trial"
msgstr ""
@@ -21359,6 +22039,21 @@ msgstr ""
msgid "SuggestedColors|Very pale orange"
msgstr ""
+msgid "Suggestion is not applicable as the suggestion was not found."
+msgstr ""
+
+msgid "Suggestions are not applicable as one or more suggestions were not found."
+msgstr ""
+
+msgid "Suggestions are not applicable as their lines cannot overlap."
+msgstr ""
+
+msgid "Suggestions must all be on the same branch."
+msgstr ""
+
+msgid "Suggestions that change line count can't be added to batches, yet."
+msgstr ""
+
msgid "Suggestions:"
msgstr ""
@@ -21401,9 +22096,6 @@ msgstr ""
msgid "Synchronization disabled"
msgstr ""
-msgid "Synchronization of container repositories is disabled."
-msgstr ""
-
msgid "System"
msgstr ""
@@ -21584,6 +22276,32 @@ msgstr ""
msgid "Terms of Service and Privacy Policy"
msgstr ""
+msgid "Terraform|%{number} Terraform report failed to generate"
+msgid_plural "Terraform|%{number} Terraform reports failed to generate"
+msgstr[0] ""
+
+msgid "Terraform|%{number} Terraform report was generated in your pipelines"
+msgid_plural "Terraform|%{number} Terraform reports were generated in your pipelines"
+msgstr[0] ""
+
+msgid "Terraform|A Terraform report failed to generate."
+msgstr ""
+
+msgid "Terraform|A Terraform report was generated in your pipelines."
+msgstr ""
+
+msgid "Terraform|Generating the report caused an error."
+msgstr ""
+
+msgid "Terraform|Reported Resource Changes: %{addNum} to add, %{changeNum} to change, %{deleteNum} to delete"
+msgstr ""
+
+msgid "Terraform|The Terraform report %{name} failed to generate."
+msgstr ""
+
+msgid "Terraform|The Terraform report %{name} was generated in your pipelines."
+msgstr ""
+
msgid "Test"
msgstr ""
@@ -21609,7 +22327,7 @@ msgstr ""
msgid "TestHooks|Ensure the project has CI pipelines."
msgstr ""
-msgid "TestHooks|Ensure the project has at least one commit."
+msgid "TestHooks|Ensure the project has deployments."
msgstr ""
msgid "TestHooks|Ensure the project has issues."
@@ -21709,7 +22427,7 @@ msgstr ""
msgid "The Prometheus server responded with \"bad request\". Please check your queries are correct and are supported in your Prometheus version. %{documentationLink}"
msgstr ""
-msgid "The URL defined on the primary node that secondary nodes should use to contact it. Defaults to URL"
+msgid "The URL defined on the primary node that secondary nodes should use to contact it."
msgstr ""
msgid "The URL to use for connecting to Elasticsearch. Use a comma-separated list to support clustering (e.g., \"http://localhost:9200, http://localhost:9201\")."
@@ -21718,6 +22436,9 @@ msgstr ""
msgid "The X509 Certificate to use when mutual TLS is required to communicate with the external authorization service. If left blank, the server certificate is still validated when accessing over HTTPS."
msgstr ""
+msgid "The above settings apply to all projects with the selected compliance framework(s)."
+msgstr ""
+
msgid "The amount of seconds after which a request to get a secondary node status will time out."
msgstr ""
@@ -22016,9 +22737,6 @@ msgstr ""
msgid "The total stage shows the time it takes between creating an issue and deploying the code to production. The data will be automatically added once you have completed the full idea to production cycle."
msgstr ""
-msgid "The unique identifier for the Geo node. Must match %{geoNodeName} if it is set in gitlab.rb, otherwise it must match %{externalUrl} with a trailing slash"
-msgstr ""
-
msgid "The update action will time out after %{number_of_minutes} minutes. For big repositories, use a clone/push combination."
msgstr ""
@@ -22043,9 +22761,6 @@ msgstr ""
msgid "The user you are trying to deactivate has been active in the past %{minimum_inactive_days} days and cannot be deactivated"
msgstr ""
-msgid "The user-facing URL of the Geo node"
-msgstr ""
-
msgid "The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6."
msgstr ""
@@ -22136,6 +22851,9 @@ msgstr ""
msgid "There was a problem fetching project branches."
msgstr ""
+msgid "There was a problem fetching project tags."
+msgstr ""
+
msgid "There was a problem fetching project users."
msgstr ""
@@ -22187,6 +22905,9 @@ msgstr ""
msgid "There was an error fetching the %{replicableType}"
msgstr ""
+msgid "There was an error fetching the Geo Settings"
+msgstr ""
+
msgid "There was an error fetching the Node's Groups"
msgstr ""
@@ -22307,6 +23028,12 @@ msgstr ""
msgid "This %{issuable} is locked. Only <strong>project members</strong> can comment."
msgstr ""
+msgid "This %{noteableTypeText} is %{confidentialLinkStart}confidential%{linkEnd} and %{lockedLinkStart}locked%{linkEnd}."
+msgstr ""
+
+msgid "This %{noteableTypeText} is locked."
+msgstr ""
+
msgid "This %{viewer} could not be displayed because %{reason}. You can %{options} instead."
msgstr ""
@@ -22325,6 +23052,9 @@ msgstr ""
msgid "This action can lead to data loss. To prevent accidental actions we ask you to confirm your intention."
msgstr ""
+msgid "This also resolves all related threads"
+msgstr ""
+
msgid "This also resolves the discussion"
msgstr ""
@@ -22415,6 +23145,9 @@ msgstr ""
msgid "This epic does not exist or you don't have sufficient permission."
msgstr ""
+msgid "This feature is currently in beta. We invite you to %{linkStart}give feedback%{linkEnd}."
+msgstr ""
+
msgid "This feature requires local storage to be enabled"
msgstr ""
@@ -22448,7 +23181,7 @@ msgstr ""
msgid "This is a Work in Progress"
msgstr ""
-msgid "This is a confidential issue."
+msgid "This is a confidential %{noteableTypeText}."
msgstr ""
msgid "This is a delayed job to run in %{remainingTime}"
@@ -22475,9 +23208,6 @@ msgstr ""
msgid "This is your current session"
msgstr ""
-msgid "This issue is %{confidentialLinkStart}confidential%{linkEnd} and %{lockedLinkStart}locked%{linkEnd}."
-msgstr ""
-
msgid "This issue is confidential"
msgstr ""
@@ -22487,9 +23217,6 @@ msgstr ""
msgid "This issue is in a child epic of the filtered epic"
msgstr ""
-msgid "This issue is locked."
-msgstr ""
-
msgid "This job depends on other jobs with expired/erased artifacts: %{invalid_dependencies}"
msgstr ""
@@ -22589,6 +23316,9 @@ msgstr ""
msgid "This license has already expired."
msgstr ""
+msgid "This link points to external content"
+msgstr ""
+
msgid "This may expose confidential information as the selected fork is in another namespace that can have other members."
msgstr ""
@@ -22616,6 +23346,9 @@ msgstr ""
msgid "This page will be removed in a future release."
msgstr ""
+msgid "This pipeline does not use the %{codeStart}needs%{codeEnd} keyword and can't be represented as a directed acyclic graph."
+msgstr ""
+
msgid "This pipeline makes use of a predefined CI/CD configuration enabled by %{strongStart}Auto DevOps.%{strongEnd}"
msgstr ""
@@ -22844,6 +23577,9 @@ msgstr ""
msgid "Time from last commit to merge"
msgstr ""
+msgid "Time in seconds"
+msgstr ""
+
msgid "Time in seconds GitLab will wait for a response from the external service. When the service does not respond in time, access will be denied."
msgstr ""
@@ -23263,9 +23999,15 @@ msgstr ""
msgid "Total artifacts size: %{total_size}"
msgstr ""
+msgid "Total cores (CPUs)"
+msgstr ""
+
msgid "Total issues"
msgstr ""
+msgid "Total memory (GB)"
+msgstr ""
+
msgid "Total test time for all commits/merges"
msgstr ""
@@ -23467,6 +24209,9 @@ msgstr ""
msgid "U2F only works with HTTPS-enabled websites. Contact your administrator for more details."
msgstr ""
+msgid "UI Development Kit"
+msgstr ""
+
msgid "URL"
msgstr ""
@@ -23539,9 +24284,6 @@ msgstr ""
msgid "Unable to load the merge request widget. Try reloading the page."
msgstr ""
-msgid "Unable to process group import file"
-msgstr ""
-
msgid "Unable to resolve"
msgstr ""
@@ -23557,6 +24299,9 @@ msgstr ""
msgid "Unable to sign you in to the group with SAML due to \"%{reason}\""
msgstr ""
+msgid "Unable to suggest a path. Please refresh and try again."
+msgstr ""
+
msgid "Unable to update label prioritization at this time"
msgstr ""
@@ -23572,6 +24317,9 @@ msgstr ""
msgid "Unarchiving the project will restore people's ability to make changes to it. The repository can be committed to, and issues, comments, and other entities can be created. %{strong_start}Once active, this project shows up in the search and on the dashboard.%{strong_end}"
msgstr ""
+msgid "Unassign from commenting user"
+msgstr ""
+
msgid "Unblock"
msgstr ""
@@ -23647,6 +24395,9 @@ msgstr ""
msgid "Unreachable"
msgstr ""
+msgid "Unrecognized cluster type"
+msgstr ""
+
msgid "Unresolve"
msgstr ""
@@ -23707,6 +24458,9 @@ msgstr ""
msgid "Unsubscribes from this %{quick_action_target}."
msgstr ""
+msgid "Unsupported todo type passed. Supported todo types are: %{todo_types}"
+msgstr ""
+
msgid "Until"
msgstr ""
@@ -23734,6 +24488,9 @@ msgstr ""
msgid "Update approval rule"
msgstr ""
+msgid "Update approvers"
+msgstr ""
+
msgid "Update failed"
msgstr ""
@@ -23743,6 +24500,9 @@ msgstr ""
msgid "Update it"
msgstr ""
+msgid "Update iteration"
+msgstr ""
+
msgid "Update now"
msgstr ""
@@ -23788,7 +24548,7 @@ msgstr ""
msgid "Updated at"
msgstr ""
-msgid "Updated to"
+msgid "Updated to %{linkStart}chart v%{linkEnd}"
msgstr ""
msgid "Updating"
@@ -23818,6 +24578,9 @@ msgstr ""
msgid "Upload CSV file"
msgstr ""
+msgid "Upload License"
+msgstr ""
+
msgid "Upload New File"
msgstr ""
@@ -23899,16 +24662,19 @@ msgstr ""
msgid "UsageQuota|Pipelines"
msgstr ""
+msgid "UsageQuota|Purchase more storage"
+msgstr ""
+
msgid "UsageQuota|Repositories"
msgstr ""
msgid "UsageQuota|Repository"
msgstr ""
-msgid "UsageQuota|Storage"
+msgid "UsageQuota|Snippets"
msgstr ""
-msgid "UsageQuota|Storage usage:"
+msgid "UsageQuota|Storage"
msgstr ""
msgid "UsageQuota|This namespace has no projects which use shared runners"
@@ -23941,6 +24707,12 @@ msgstr ""
msgid "UsageQuota|Wikis"
msgstr ""
+msgid "UsageQuota|You used: %{usage} %{limit}"
+msgstr ""
+
+msgid "UsageQuota|out of %{formattedLimit} of your namespace storage"
+msgstr ""
+
msgid "Use %{code_start}::%{code_end} to create a %{link_start}scoped label set%{link_end} (eg. %{code_start}priority::1%{code_end})"
msgstr ""
@@ -24007,6 +24779,9 @@ msgstr ""
msgid "User IDs"
msgstr ""
+msgid "User Lists can only be created and modified with %{linkStart}the API%{linkEnd}"
+msgstr ""
+
msgid "User OAuth applications"
msgstr ""
@@ -24031,6 +24806,9 @@ msgstr ""
msgid "User key was successfully removed."
msgstr ""
+msgid "User list %{name} will be removed. Are you sure?"
+msgstr ""
+
msgid "User map"
msgstr ""
@@ -24052,148 +24830,10 @@ msgstr ""
msgid "User was successfully updated."
msgstr ""
-msgid "UserOnboardingTour|%{activeTour}/%{totalTours}"
-msgstr ""
-
-msgid "UserOnboardingTour|%{completed}/%{total} steps completed"
-msgstr ""
-
-msgid "UserOnboardingTour|%{emphasisStart}Well done!%{emphasisEnd}%{lineBreak}%{lineBreak}That's it for our guided tour, congratulations for making it all the way to the end!%{lineBreak}%{lineBreak}We hope this gave you a good overview of GitLab and how it can help you. We'll now show you how to create your own project and invite your colleagues."
-msgstr ""
-
-msgid "UserOnboardingTour|Adding other members to a project is done through Project Settings. Click on %{emphasisStart}Settings%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Alright, that's it for Commits. Let's take a look at the %{emphasisStart}Branches%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Awesome! Now click on %{emphasisStart}Members%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Click on one of the %{emphasisStart}Compare%{emphasisEnd} buttons to compare a branch to master."
-msgstr ""
-
-msgid "UserOnboardingTour|Click on one of the %{emphasisStart}pipeline IDs%{emphasisEnd} to see the details of a pipeline."
-msgstr ""
-
-msgid "UserOnboardingTour|Click to open the latest commit to see its details."
-msgstr ""
-
-msgid "UserOnboardingTour|Close 'Learn GitLab'"
-msgstr ""
-
-msgid "UserOnboardingTour|Commits are shown in chronological order and can be filtered by the commit message or by the branch."
-msgstr ""
-
-msgid "UserOnboardingTour|Create a project"
-msgstr ""
-
-msgid "UserOnboardingTour|Exit 'Learn GitLab'"
-msgstr ""
-
-msgid "UserOnboardingTour|Got it"
-msgstr ""
-
-msgid "UserOnboardingTour|Great job! %{clapHands} We hope the tour was helpful and that you learned how to use GitLab.%{lineBreak}%{lineBreak}We'd love to get your feedback on this tour.%{lineBreak}%{lineBreak}%{emphasisStart}How helpful would you say this guided tour was?%{emphasisEnd}%{lineBreak}%{lineBreak}"
-msgstr ""
-
-msgid "UserOnboardingTour|Guided GitLab Tour"
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can compare the changes of this branch to another one. Changes are divided by files so that it's easier to see what was changed where."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can create a project from scratch, start with a template or import a repository from other platforms. Whatever you choose, we'll guide you through the process.%{lineBreak}%{lineBreak}Fill in your new project information and click on %{emphasisStart}Create Project%{emphasisEnd} to progress to the next step."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can see the breakdown of the pipelines: its stages and jobs in each of the stages and their status.%{lineBreak}%{lineBreak}Our CI/CD pipelines are quite complex, most of our users have fewer and simpler pipelines."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can see the current members of the project (just you at the moment) and invite new members.%{lineBreak}%{lineBreak}You can invite multiple members at once (existing GitLab users or invite by email) and you can also set their roles and permissions.%{lineBreak}%{lineBreak}Add a few members and click on %{emphasisStart}Add to project%{emphasisEnd} to complete this step."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can see what changes were made with this commit, on what branch and if there's a related merge request. The status of the pipeline will also show up if CI/CD is set up.%{lineBreak}%{lineBreak}You can also comment on the lines of code that were changed and start a discussion with your colleagues!"
-msgstr ""
-
-msgid "UserOnboardingTour|Here's an overview of branches in the %{emphasisStart}%{projectName}%{emphasisEnd} project. They're split into Active and Stale.%{lineBreak}%{lineBreak}From here, you can create a new merge request from a branch, or compare the branch to any other branch in the project. By default, it will compare it to the master branch."
-msgstr ""
-
-msgid "UserOnboardingTour|Invite colleagues"
-msgstr ""
-
-msgid "UserOnboardingTour|Issues are great for communicating and keeping track of progress in GitLab. These are all issues that are open in the %{emphasisStart}%{projectName}%{emphasisEnd}.%{lineBreak}%{lineBreak}You can help us improve GitLab by contributing work to issues that are labeled <span class=\"badge color-label accept-mr-label\">Accepting merge requests</span>.%{lineBreak}%{lineBreak}This list can be filtered by labels, milestones, assignees, authors... We'll show you how it looks when the list is filtered by a label."
-msgstr ""
-
-msgid "UserOnboardingTour|Learn GitLab"
-msgstr ""
-
-msgid "UserOnboardingTour|Let's take a closer look at a merge request. Click on the title of one."
-msgstr ""
-
-msgid "UserOnboardingTour|Let's take a closer look at all the commits. Click on %{emphasisStart}Commits%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Let's take a closer look at the repository of this project. Click on %{emphasisStart}Repository%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|No thanks"
-msgstr ""
-
-msgid "UserOnboardingTour|Ok, let's go"
-msgstr ""
-
-msgid "UserOnboardingTour|Ok, show me"
-msgstr ""
-
-msgid "UserOnboardingTour|Open one of the issues by clicking on its title."
-msgstr ""
-
-msgid "UserOnboardingTour|Restart this step"
-msgstr ""
-
-msgid "UserOnboardingTour|Skip this step"
-msgstr ""
-
-msgid "UserOnboardingTour|Sweet! Your project was created and is ready to be used.%{lineBreak}%{lineBreak}You can start adding files to the repository or clone it. One last thing we want to show you is how to invite your colleagues to your new project."
-msgstr ""
-
-msgid "UserOnboardingTour|Take a look. Here's a nifty menu for quickly creating issues, merge requests, snippets, projects and groups. Click on it and select \"New project\" from the \"GitLab\" section to get started."
-msgstr ""
-
-msgid "UserOnboardingTour|Thanks for taking the guided tour. Remember, if you want to go through it again, you can start %{emphasisStart}Learn GitLab%{emphasisEnd} in the help menu on the top right."
-msgstr ""
-
-msgid "UserOnboardingTour|Thanks for the feedback! %{thumbsUp}"
-msgstr ""
-
-msgid "UserOnboardingTour|That's it for issues. Let'st take a look at %{emphasisStart}Merge Requests%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|That's it for merge requests. Now for the final part of this guided tour - the %{emphasisStart}CI/CD%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|That's it for the Repository. Let's take a look at the %{emphasisStart}Issues%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|The structure of this page is very similar to issues. Status, description, discussion and the sidebar are all here.%{lineBreak}%{lineBreak}But take a look below the description and you'll notice that there's more information about the merge request, the CI/CD pipeline and the options for approving it.%{lineBreak}%{lineBreak}Alongside the discussion you can also see more information about commits in this merge request, the status of pipelines and review all changes that were made."
-msgstr ""
-
-msgid "UserOnboardingTour|There's a lot of information here but don't worry, we'll go through it.%{lineBreak}%{lineBreak}On the top you can see the status of the issue and when it was opened and by whom. Directly below it is the issue description and below that are other %{emphasisStart}related issues%{emphasisEnd} and %{emphasisStart}merge requests%{emphasisEnd} (if any). Then below that is the %{emphasisStart}discussion%{emphasisEnd}, that's where most of the communication happens.%{lineBreak}%{lineBreak}On the right, there's a sidebar where you can view/change the %{emphasisStart}assignee, milestone, due date, labels, weight%{emphasisEnd}, etc."
-msgstr ""
-
-msgid "UserOnboardingTour|These are all the CI/CD pipelines we have for our %{emphasisStart}%{projectName}%{emphasisEnd} project.%{lineBreak}%{lineBreak}Here you can see the status of each pipeline, for what commit it's running for, its stages and the status for them."
-msgstr ""
-
-msgid "UserOnboardingTour|These are all the issues that are available for community contributions. Let's take a closer look at one of them."
+msgid "UserList|Delete %{name}?"
msgstr ""
-msgid "UserOnboardingTour|This is an overview of all merge requests in this project. Similarly to the issues overview it can be filtered down by things like labels, milestones, authors, assignees, etc."
-msgstr ""
-
-msgid "UserOnboardingTour|This is the repository for the %{emphasisStart}%{projectName}%{emphasisEnd} project. All our code is stored here. Feel free to explore and take a closer look at folders and files.%{lineBreak}%{lineBreak}Above the file structure you can see the latest commit, who the author is and the status of the CI/CD pipeline.%{lineBreak}%{lineBreak}If you scroll down below the file structure, you'll find the Readme of this project. This is defined in the README.md file at the root of the repository."
-msgstr ""
-
-msgid "UserOnboardingTour|Welcome to the project overview of the %{emphasisStart}%{projectName}%{emphasisEnd} project. This is the project that we use to work on GitLab. At first, a project seems like a simple repository, but at GitLab, a project is so much more.%{lineBreak}%{lineBreak}You can create projects for hosting your codebase, use it as an issue tracker, collaborate on code, and continuously build, test, and deploy your app with built-in GitLab CI/CD."
+msgid "UserList|created %{timeago}"
msgstr ""
msgid "UserProfile|Activity"
@@ -24346,6 +24986,9 @@ msgstr ""
msgid "UsersSelect|Unassigned"
msgstr ""
+msgid "Using %{codeStart}needs%{codeEnd} allows jobs to run before their stage is reached, as soon as their individual dependencies are met, which speeds up your pipelines."
+msgstr ""
+
msgid "Using %{code_start}::%{code_end} denotes a %{link_start}scoped label set%{link_end}"
msgstr ""
@@ -24430,9 +25073,6 @@ msgstr ""
msgid "Versions"
msgstr ""
-msgid "Very helpful"
-msgstr ""
-
msgid "View Documentation"
msgstr ""
@@ -24579,6 +25219,9 @@ msgstr ""
msgid "VisibilityLevel|Unknown"
msgstr ""
+msgid "Visit settings page"
+msgstr ""
+
msgid "VisualReviewApp|%{stepStart}Step 1%{stepEnd}. Copy the following script:"
msgstr ""
@@ -24672,6 +25315,9 @@ msgstr ""
msgid "VulnerabilityManagement|Something went wrong while trying to delete the comment. Please try again later."
msgstr ""
+msgid "VulnerabilityManagement|Something went wrong while trying to refresh the vulnerability. Please try again later."
+msgstr ""
+
msgid "VulnerabilityManagement|Something went wrong while trying to retrieve the vulnerability history. Please try again later."
msgstr ""
@@ -24714,6 +25360,9 @@ msgstr ""
msgid "Vulnerability|Class"
msgstr ""
+msgid "Vulnerability|Crash Address"
+msgstr ""
+
msgid "Vulnerability|Description"
msgstr ""
@@ -24789,9 +25438,6 @@ msgstr ""
msgid "We couldn't reach the Prometheus server. Either the server no longer exists or the configuration details need updating."
msgstr ""
-msgid "We created a short guided tour that will help you learn the basics of GitLab and how it will help you be better at your job. It should only take a couple of minutes. You will be guided by two types of helpers, best recognized by their color."
-msgstr ""
-
msgid "We detected potential spam in the %{humanized_resource_name}. Please solve the reCAPTCHA to proceed."
msgstr ""
@@ -24825,6 +25471,9 @@ msgstr ""
msgid "We've found no vulnerabilities"
msgstr ""
+msgid "Web Application Firewall"
+msgstr ""
+
msgid "Web IDE"
msgstr ""
@@ -24885,9 +25534,6 @@ msgstr ""
msgid "Welcome to GitLab.com<br>@%{name}!"
msgstr ""
-msgid "Welcome to the Guided GitLab Tour"
-msgstr ""
-
msgid "Welcome to the guided GitLab tour"
msgstr ""
@@ -24934,9 +25580,6 @@ msgstr ""
msgid "While it's rare to have no vulnerabilities, it can happen. In any event, we ask that you please double check your settings to make sure you've set up your dashboard correctly."
msgstr ""
-msgid "White helpers give contextual information."
-msgstr ""
-
msgid "Who can be an approver?"
msgstr ""
@@ -24982,12 +25625,18 @@ msgstr ""
msgid "WikiEmptyIssueMessage|Suggest wiki improvement"
msgstr ""
+msgid "WikiEmptyIssueMessage|You must be a group member in order to add wiki pages. If you have suggestions for how to improve the wiki for this group, consider opening an issue in the %{issues_link}."
+msgstr ""
+
msgid "WikiEmptyIssueMessage|You must be a project member in order to add wiki pages. If you have suggestions for how to improve the wiki for this project, consider opening an issue in the %{issues_link}."
msgstr ""
msgid "WikiEmptyIssueMessage|issue tracker"
msgstr ""
+msgid "WikiEmpty|A wiki is where you can store all the details about your group. This can include why you've created it, its principles, how to use it, and so on."
+msgstr ""
+
msgid "WikiEmpty|A wiki is where you can store all the details about your project. This can include why you've created it, its principles, how to use it, and so on."
msgstr ""
@@ -24997,12 +25646,21 @@ msgstr ""
msgid "WikiEmpty|Suggest wiki improvement"
msgstr ""
+msgid "WikiEmpty|The wiki lets you write documentation for your group"
+msgstr ""
+
msgid "WikiEmpty|The wiki lets you write documentation for your project"
msgstr ""
+msgid "WikiEmpty|This group has no wiki pages"
+msgstr ""
+
msgid "WikiEmpty|This project has no wiki pages"
msgstr ""
+msgid "WikiEmpty|You must be a group member in order to add wiki pages."
+msgstr ""
+
msgid "WikiEmpty|You must be a project member in order to add wiki pages."
msgstr ""
@@ -25279,13 +25937,13 @@ msgstr ""
msgid "You can now export your security dashboard to a CSV report."
msgstr ""
-msgid "You can now submit a merge request to get this change into the original branch."
+msgid "You can now manage alert endpoint configuration in the Alerts section on the Operations settings page. Fields on this page have been deprecated."
msgstr ""
-msgid "You can now submit a merge request to get this change into the original project."
+msgid "You can now submit a merge request to get this change into the original branch."
msgstr ""
-msgid "You can only add files when you are on a branch"
+msgid "You can now submit a merge request to get this change into the original project."
msgstr ""
msgid "You can only edit files when you are on a branch"
@@ -25507,7 +26165,7 @@ msgstr ""
msgid "You need to upload a Google Takeout archive."
msgstr ""
-msgid "You reached %{usage_in_percent} of %{namespace_name}'s capacity (%{used_storage} of %{storage_limit})"
+msgid "You reached %{usage_in_percent} of %{namespace_name}'s storage capacity (%{used_storage} of %{storage_limit})"
msgstr ""
msgid "You tried to fork %{link_to_the_project} but it failed for the following reason:"
@@ -25810,13 +26468,13 @@ msgstr ""
msgid "Your subscription expired!"
msgstr ""
-msgid "Your subscription has been downgraded"
+msgid "Your subscription has been downgraded."
msgstr ""
-msgid "Your subscription will automatically renew in %{remaining_days}"
+msgid "Your subscription will automatically renew in %{remaining_days}."
msgstr ""
-msgid "Your subscription will expire in %{remaining_days}"
+msgid "Your subscription will expire in %{remaining_days}."
msgstr ""
msgid "Zoom meeting added"
@@ -25895,9 +26553,6 @@ msgstr ""
msgid "branch name"
msgstr ""
-msgid "build pipeline reference mismatch"
-msgstr ""
-
msgid "by"
msgstr ""
@@ -25916,6 +26571,9 @@ msgstr ""
msgid "cannot block others"
msgstr ""
+msgid "cannot contain HTML/XML tags, including any word between angle brackets (<,>)."
+msgstr ""
+
msgid "cannot include leading slash or directory traversal."
msgstr ""
@@ -25946,50 +26604,6 @@ msgstr ""
msgid "ciReport|%{remainingPackagesCount} more"
msgstr ""
-msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability"
-msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities"
-msgstr[0] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability for the source branch only"
-msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities for the source branch only"
-msgstr[0] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerability"
-msgid_plural "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerabilities"
-msgstr[0] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new vulnerability"
-msgid_plural "ciReport|%{reportType} %{status} detected %{newCount} new vulnerabilities"
-msgstr[0] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities for the source branch only"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{fixedCount} fixed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} vulnerability for the source branch only"
-msgid_plural "ciReport|%{reportType} %{status} detected %{newCount} vulnerabilities for the source branch only"
-msgstr[0] ""
-
-msgid "ciReport|%{reportType} %{status} detected no new vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected no vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected no vulnerabilities for the source branch only"
-msgstr ""
-
msgid "ciReport|%{reportType} is loading"
msgstr ""
@@ -26008,7 +26622,7 @@ msgstr ""
msgid "ciReport|All projects"
msgstr ""
-msgid "ciReport|All report types"
+msgid "ciReport|All scanner types"
msgstr ""
msgid "ciReport|All severities"
@@ -26032,6 +26646,9 @@ msgstr ""
msgid "ciReport|Container scanning detects known vulnerabilities in your docker images."
msgstr ""
+msgid "ciReport|Coverage Fuzzing"
+msgstr ""
+
msgid "ciReport|Create a merge request to implement this solution, or download and apply the patch manually."
msgstr ""
@@ -26189,6 +26806,9 @@ msgstr ""
msgid "customize"
msgstr ""
+msgid "data"
+msgstr ""
+
msgid "date must not be after 9999-12-31"
msgstr ""
@@ -26257,6 +26877,9 @@ msgstr ""
msgid "entries cannot contain HTML tags"
msgstr ""
+msgid "epic"
+msgstr ""
+
msgid "error"
msgstr ""
@@ -26278,9 +26901,6 @@ msgstr ""
msgid "expires on %{timebox_due_date}"
msgstr ""
-msgid "external_url"
-msgstr ""
-
msgid "failed"
msgstr ""
@@ -26315,9 +26935,6 @@ msgstr ""
msgid "from"
msgstr ""
-msgid "geo_node_name"
-msgstr ""
-
msgid "group"
msgstr ""
@@ -26339,9 +26956,6 @@ msgstr ""
msgid "https://your-bitbucket-server"
msgstr ""
-msgid "image"
-msgstr ""
-
msgid "image diff"
msgstr ""
@@ -26484,6 +27098,9 @@ msgstr ""
msgid "load it anyway"
msgstr ""
+msgid "loading"
+msgstr ""
+
msgid "locked by %{path_lock_user_name} %{created_at}"
msgstr ""
@@ -26611,9 +27228,6 @@ msgstr ""
msgid "mrWidget|Deployment statistics are not available currently"
msgstr ""
-msgid "mrWidget|Detect issues before deployment with a CI pipeline that continuously tests your code. We created a quick guide that will show you how to create one. Make your code more secure and more robust in just a minute."
-msgstr ""
-
msgid "mrWidget|Did not close"
msgstr ""
@@ -26776,6 +27390,9 @@ msgstr ""
msgid "mrWidget|To approve this merge request, please enter your password. This project requires all approvals to be authenticated."
msgstr ""
+msgid "mrWidget|Use %{linkStart}CI pipelines to test your code%{linkEnd}, simply add a GitLab CI configuration file to your project. It only takes a minute to make your code more secure and robust."
+msgstr ""
+
msgid "mrWidget|When this merge request is ready, remove the WIP: prefix from the title to allow it to be merged"
msgstr ""
@@ -26812,6 +27429,9 @@ msgstr ""
msgid "must be greater than start date"
msgstr ""
+msgid "must contain only valid frameworks"
+msgstr ""
+
msgid "my-awesome-group"
msgstr ""
@@ -26824,6 +27444,9 @@ msgstr ""
msgid "needs to be between 10 minutes and 1 month"
msgstr ""
+msgid "never"
+msgstr ""
+
msgid "never expires"
msgstr ""
@@ -26851,6 +27474,9 @@ msgstr ""
msgid "nounSeries|%{firstItem} and %{lastItem}"
msgstr ""
+msgid "nounSeries|%{item}"
+msgstr ""
+
msgid "nounSeries|%{item}, %{nextItem}"
msgstr ""
@@ -26982,6 +27608,9 @@ msgstr ""
msgid "revised"
msgstr ""
+msgid "satisfied"
+msgstr ""
+
msgid "score"
msgstr ""
@@ -27012,6 +27641,9 @@ msgstr ""
msgid "severity|Unknown"
msgstr ""
+msgid "should be an array of %{object_name} objects"
+msgstr ""
+
msgid "should be greater than or equal to %{access} inherited membership from group %{group_name}"
msgstr ""
@@ -27066,10 +27698,13 @@ msgstr ""
msgid "suggestPipeline|2/2: Commit your changes"
msgstr ""
-msgid "suggestPipeline|Commit the changes and your pipeline will automatically run for the first time."
+msgid "suggestPipeline|Choose %{boldStart}Code Quality%{boldEnd} to add a pipeline that tests the quality of your code."
msgstr ""
-msgid "suggestPipeline|We recommend the %{boldStart}Code Quality%{boldEnd} template, which will add a report widget to your Merge Requests. This way you’ll learn about code quality degradations much sooner. %{footerStart} Goodbye technical debt! %{footerEnd}"
+msgid "suggestPipeline|The template is ready! You can now commit it to create your first pipeline."
+msgstr ""
+
+msgid "suggestPipeline|We’re adding a GitLab CI configuration file to add a pipeline to the project. You could create it manually, but we recommend that you start with a GitLab template that works out of the box."
msgstr ""
msgid "syntax is correct"
@@ -27129,6 +27764,9 @@ msgstr ""
msgid "uses Kubernetes clusters to deploy your code!"
msgstr ""
+msgid "v%{version} published %{timeAgo}"
+msgstr ""
+
msgid "verify ownership"
msgstr ""
diff --git a/locale/zh_CN/gitlab.po b/locale/zh_CN/gitlab.po
index d64092020ab..5855cc8cae2 100644
--- a/locale/zh_CN/gitlab.po
+++ b/locale/zh_CN/gitlab.po
@@ -2,7 +2,7 @@ msgid ""
msgstr ""
"Project-Id-Version: gitlab-ee\n"
"Report-Msgid-Bugs-To: \n"
-"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Last-Translator: \n"
"Language-Team: Chinese Simplified\n"
"Language: zh_CN\n"
"MIME-Version: 1.0\n"
@@ -10,9 +10,11 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=1; plural=0;\n"
"X-Crowdin-Project: gitlab-ee\n"
+"X-Crowdin-Project-ID: 288872\n"
"X-Crowdin-Language: zh-CN\n"
"X-Crowdin-File: /master/locale/gitlab.pot\n"
-"PO-Revision-Date: 2020-06-08 15:08\n"
+"X-Crowdin-File-ID: 6\n"
+"PO-Revision-Date: 2020-07-02 01:23\n"
msgid " %{start} to %{end}"
msgstr ""
@@ -170,6 +172,10 @@ msgid "%d metric"
msgid_plural "%d metrics"
msgstr[0] "%d 个指标"
+msgid "%d milestone"
+msgid_plural "%d milestones"
+msgstr[0] ""
+
msgid "%d minute"
msgid_plural "%d minutes"
msgstr[0] "%d分"
@@ -206,10 +212,6 @@ msgid "%d unresolved thread"
msgid_plural "%d unresolved threads"
msgstr[0] ""
-msgid "%d url scanned"
-msgid_plural "%d urls scanned"
-msgstr[0] ""
-
msgid "%d vulnerability"
msgid_plural "%d vulnerabilities"
msgstr[0] ""
@@ -226,7 +228,7 @@ msgid "%{actionText} & %{openOrClose} %{noteable}"
msgstr "%{actionText} 和 %{openOrClose} %{noteable}"
msgid "%{authorsName}'s thread"
-msgstr "%{authorsName}çš„è¯é¢˜"
+msgstr "%{authorsName}的主题"
msgid "%{commit_author_link} authored %{commit_timeago}"
msgstr "ç”± %{commit_author_link} æ交于 %{commit_timeago}"
@@ -280,9 +282,15 @@ msgstr[0] "%{count}个待处ç†çš„评论"
msgid "%{count} related %{pluralized_subject}: %{links}"
msgstr "%{count}个相关的%{pluralized_subject}: %{links}"
+msgid "%{dashboard_path} could not be found."
+msgstr ""
+
msgid "%{days} days until tags are automatically removed"
msgstr "%{days}天,直到标签被自动删除"
+msgid "%{deployLinkStart}Use a template to deploy to ECS%{deployLinkEnd}, or use a docker image to %{commandsLinkStart}run AWS commands in GitLab CI/CD%{commandsLinkEnd}."
+msgstr ""
+
msgid "%{description}- Sentry event: %{errorUrl}- First seen: %{firstSeen}- Last seen: %{lastSeen} %{countLabel}: %{count}%{userCountLabel}: %{userCount}"
msgstr "%{description}- Sentry事件: %{errorUrl}- 首次出现: %{firstSeen}- 最åŽå‡ºçŽ°: %{lastSeen} %{countLabel}: %{count}%{userCountLabel}: %{userCount}"
@@ -325,12 +333,18 @@ msgstr "%{group_name}使用由群组托管å¸æˆ·ã€‚您需è¦åˆ›å»ºä¸€ä¸ªæ–°çš„Gi
msgid "%{host} sign-in from new location"
msgstr ""
-msgid "%{icon}You are about to add %{usersTag} people to the discussion. Proceed with caution."
-msgstr "%{icon} å³å°†åœ¨è®¨è®ºä¸­æ·»åŠ  %{usersTag} 个人, 请仔细检查。"
+msgid "%{icon}You are about to add %{usersTag} people to the discussion. They will all receive a notification."
+msgstr ""
+
+msgid "%{integrations_link_start}Integrations%{link_end} enable you to make third-party applications part of your GitLab workflow. If the available integrations don't meet your needs, consider using a %{webhooks_link_start}webhook%{link_end}."
+msgstr ""
msgid "%{issuableType} will be removed! Are you sure?"
msgstr "%{issuableType} 将被删除ï¼æ‚¨ç¡®å®šå—?"
+msgid "%{issuesCount} issues in this group"
+msgstr ""
+
msgid "%{issuesSize} issues"
msgstr "%{issuesSize}个议题"
@@ -340,7 +354,7 @@ msgstr "%{issuesSize}个议题,上é™ä¸º%{maxIssueCount}"
msgid "%{labelStart}Class:%{labelEnd} %{class}"
msgstr ""
-msgid "%{labelStart}Confidence:%{labelEnd} %{confidence}"
+msgid "%{labelStart}Evidence:%{labelEnd} %{evidence}"
msgstr ""
msgid "%{labelStart}File:%{labelEnd} %{file}"
@@ -358,6 +372,9 @@ msgstr ""
msgid "%{labelStart}Report Type:%{labelEnd} %{reportType}"
msgstr ""
+msgid "%{labelStart}Scanner:%{labelEnd} %{scanner}"
+msgstr ""
+
msgid "%{labelStart}Severity:%{labelEnd} %{severity}"
msgstr ""
@@ -382,9 +399,6 @@ msgstr ""
msgid "%{link_start}Read more%{link_end} about role permissions"
msgstr "%{link_start}查看更多%{link_end} 关于角色æƒé™"
-msgid "%{link} can be used for binding events when something is happening within the project."
-msgstr "%{link}å¯ä»¥ç”¨äºŽå…³è”项目内å‘生æŸä»¶äº‹æƒ…的相关事件。"
-
msgid "%{listToShow}, and %{awardsListLength} more."
msgstr "%{listToShow},还有 %{awardsListLength} 个。"
@@ -418,6 +432,9 @@ msgstr "%{name} åŒ…å« %{resultsString}"
msgid "%{name} found %{resultsString}"
msgstr "%{name} 找到了 %{resultsString}"
+msgid "%{name} is already being used for another emoji"
+msgstr ""
+
msgid "%{name} is scheduled for %{action}"
msgstr ""
@@ -464,6 +481,9 @@ msgstr ""
msgid "%{primary} (%{secondary})"
msgstr "%{primary} (%{secondary})"
+msgid "%{ref} cannot be added: %{error}"
+msgstr ""
+
msgid "%{releases} release"
msgid_plural "%{releases} releases"
msgstr[0] "%{releases}个å‘布"
@@ -471,13 +491,34 @@ msgstr[0] "%{releases}个å‘布"
msgid "%{remaining_approvals} left"
msgstr ""
-msgid "%{retryButtonStart}Try again%{retryButtonEnd} or %{newFileButtonStart}attach a new file%{newFileButtonEnd}"
+msgid "%{reportType} %{status} detected %{critical} critical and %{high} high severity vulnerabilities out of %{total}."
+msgstr ""
+
+msgid "%{reportType} %{status} detected %{critical} critical and %{high} high severity vulnerabilities."
+msgstr ""
+
+msgid "%{reportType} %{status} detected %{critical} critical severity vulnerabilities out of %{total}."
+msgstr ""
+
+msgid "%{reportType} %{status} detected %{critical} critical severity vulnerability."
+msgid_plural "%{reportType} %{status} detected %{critical} critical severity vulnerabilities."
+msgstr[0] ""
+
+msgid "%{reportType} %{status} detected %{high} high severity vulnerabilities out of %{total}."
msgstr ""
-msgid "%{screenreaderOnlyStart}Keyboard shorcuts%{screenreaderOnlyEnd} Disabled"
+msgid "%{reportType} %{status} detected %{high} high severity vulnerability."
+msgid_plural "%{reportType} %{status} detected %{high} high severity vulnerabilities."
+msgstr[0] ""
+
+msgid "%{reportType} %{status} detected %{other} vulnerability."
+msgid_plural "%{reportType} %{status} detected %{other} vulnerabilities."
+msgstr[0] ""
+
+msgid "%{reportType} %{status} detected no new vulnerabilities."
msgstr ""
-msgid "%{screenreaderOnlyStart}Keyboard shorcuts%{screenreaderOnlyEnd} Enabled"
+msgid "%{retryButtonStart}Try again%{retryButtonEnd} or %{newFileButtonStart}attach a new file%{newFileButtonEnd}"
msgstr ""
msgid "%{service_title} %{message}."
@@ -569,6 +610,12 @@ msgstr "%{title}更改"
msgid "%{token}..."
msgstr ""
+msgid "%{totalCpu} (%{freeSpacePercentage}%{percentSymbol} free)"
+msgstr ""
+
+msgid "%{totalMemory} (%{freeSpacePercentage}%{percentSymbol} free)"
+msgstr ""
+
msgid "%{totalWeight} total weight"
msgstr "%{totalWeight}总æƒé‡"
@@ -599,6 +646,12 @@ msgstr ""
msgid "%{verb} %{time_spent_value} spent time."
msgstr "%{verb} 耗时 %{time_spent_value}"
+msgid "%{webhooks_link_start}%{webhook_type}%{link_end} enable you to send notifications to web applications in response to events in a group or project."
+msgstr ""
+
+msgid "%{webhooks_link_start}%{webhook_type}%{link_end} enable you to send notifications to web applications in response to events in a group or project. We recommend using an %{integrations_link_start}integration%{link_end} in preference to a webhook."
+msgstr ""
+
msgid "'%{level}' is not a valid visibility level"
msgstr "“%{level}â€ä¸æ˜¯æœ‰æ•ˆçš„å¯è§æ€§çº§åˆ«"
@@ -624,9 +677,6 @@ msgstr "(%{mrCount} å·²åˆå¹¶)"
msgid "(No changes)"
msgstr "(æ— å˜æ›´å†…容)"
-msgid "(Show all)"
-msgstr "(全部显示)"
-
msgid "(check progress)"
msgstr "(检查进度)"
@@ -690,6 +740,9 @@ msgstr "- 显示较少"
msgid "0 for unlimited"
msgstr "0 表示无é™åˆ¶"
+msgid "0 for unlimited, only effective with remote storage enabled."
+msgstr ""
+
msgid "1 %{type} addition"
msgid_plural "%{count} %{type} additions"
msgstr[0] "%{count} 个 %{type} 的添加"
@@ -822,6 +875,12 @@ msgstr "<code>\"johnsmith@example.com\": \"johnsm...@example.com\"</code> 将会
msgid "<code>\"johnsmith@example.com\": \"johnsmith@example.com\"</code> will add \"By <a href=\"#\">johnsmith@example.com</a>\" to all issues and comments originally created by johnsmith@example.com. By default, the email address or username is masked to ensure the user's privacy. Use this option if you want to show the full email address."
msgstr "<code>\"johnsmith@example.com\": \"johnsmith@example.com\"</code> 将会把“由<a href=\"#\">@johnsmith</a>â€æ·»åŠ åˆ°åŽŸæœ¬ç”±johnsmith@example.com创建的所有议题和评论中。 为ä¿æŠ¤ç”¨æˆ·çš„éšç§ï¼Œç”µå­é‚®ä»¶åœ°å€æˆ–用户å默认将被å±è”½ã€‚如需显示完整邮件地å€ï¼Œå¯ä½¿ç”¨æ­¤é€‰é¡¹ã€‚"
+msgid "<code>Masked</code> to prevent the values from being displayed in job logs (must match certain regexp requirements)."
+msgstr ""
+
+msgid "<code>Protected</code> to expose them to protected branches or tags only."
+msgstr ""
+
msgid "<namespace / project>"
msgstr ""
@@ -849,9 +908,6 @@ msgstr "Runner是一个执行任务的进程。您å¯ä»¥æ ¹æ®éœ€è¦é…置任æ„
msgid "A .NET Core console application template, customizable for any .NET Core project"
msgstr ".NET Core控制å°åº”用程åºæ¨¡æ¿ï¼Œå¯é’ˆå¯¹ä»»ä½•.NET Core项目进行自定义"
-msgid "A DAG must have two dependent jobs to be visualized on this tab."
-msgstr ""
-
msgid "A GitBook site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
msgstr "一个GitBook站点,它使用NetLifyæ¥ä»£æ›¿Gitlabçš„CI/CD,但ä»ç„¶å…·æœ‰æ‰€æœ‰å…¶ä»–主è¦çš„Gitlab功能。"
@@ -882,6 +938,12 @@ msgstr "无法为空项目选择默认分支。"
msgid "A deleted user"
msgstr "已删除的用户"
+msgid "A file has been changed."
+msgstr ""
+
+msgid "A file was not found."
+msgstr ""
+
msgid "A file with '%{file_name}' already exists in %{branch} branch"
msgstr "文件å为%{file_name}的文件已ç»å­˜åœ¨äºŽ%{branch}分支中"
@@ -948,7 +1010,7 @@ msgstr ""
msgid "A subscription will trigger a new pipeline on the default branch of this project when a pipeline successfully completes for a new tag on the %{default_branch_docs} of the subscribed project."
msgstr ""
-msgid "A terraform report was generated in your pipelines."
+msgid "A suggestion is not applicable."
msgstr ""
msgid "A user with write access to the source branch selected this option"
@@ -1119,6 +1181,9 @@ msgstr "å¸æˆ·ï¼š%{account}"
msgid "Action to take when receiving an alert."
msgstr "接收警报时è¦æ‰§è¡Œçš„æ“作。"
+msgid "Actions"
+msgstr ""
+
msgid "Activate"
msgstr "激活"
@@ -1174,6 +1239,9 @@ msgstr "添加 Kubernetes 集群"
msgid "Add LICENSE"
msgstr "添加LICENSE"
+msgid "Add New Node"
+msgstr ""
+
msgid "Add README"
msgstr "添加自述文件"
@@ -1249,6 +1317,9 @@ msgstr ""
msgid "Add approval rule"
msgstr "添加批准规则"
+msgid "Add approvers"
+msgstr ""
+
msgid "Add bold text"
msgstr "添加粗体文本"
@@ -1306,6 +1377,9 @@ msgstr "手动添加请求"
msgid "Add strikethrough text"
msgstr ""
+msgid "Add suggestion to batch"
+msgstr ""
+
msgid "Add system hook"
msgstr "添加系统钩å­"
@@ -1405,6 +1479,9 @@ msgstr "管ç†ä¸­å¿ƒ"
msgid "Admin Note"
msgstr ""
+msgid "Admin Notifications"
+msgstr ""
+
msgid "Admin Overview"
msgstr "管ç†æ¦‚览"
@@ -1516,6 +1593,9 @@ msgstr ""
msgid "AdminSettings|Integrations configured here will automatically apply to all projects on this instance."
msgstr ""
+msgid "AdminSettings|Moved to integrations"
+msgstr ""
+
msgid "AdminSettings|No required pipeline"
msgstr "没有必需的æµæ°´çº¿"
@@ -1745,12 +1825,18 @@ msgid "Alert"
msgid_plural "Alerts"
msgstr[0] "警报"
+msgid "AlertManagement| assign yourself"
+msgstr ""
+
msgid "AlertManagement|Acknowledged"
msgstr ""
msgid "AlertManagement|Alert"
msgstr ""
+msgid "AlertManagement|Alert assignee(s): %{assignees}"
+msgstr ""
+
msgid "AlertManagement|Alert detail"
msgstr ""
@@ -1766,9 +1852,15 @@ msgstr ""
msgid "AlertManagement|All alerts"
msgstr ""
+msgid "AlertManagement|Assign To"
+msgstr ""
+
msgid "AlertManagement|Assign status"
msgstr ""
+msgid "AlertManagement|Assignee"
+msgstr ""
+
msgid "AlertManagement|Assignees"
msgstr ""
@@ -1787,9 +1879,6 @@ msgstr ""
msgid "AlertManagement|Edit"
msgstr ""
-msgid "AlertManagement|End time"
-msgstr ""
-
msgid "AlertManagement|Events"
msgstr ""
@@ -1808,10 +1897,13 @@ msgstr ""
msgid "AlertManagement|More information"
msgstr ""
+msgid "AlertManagement|No Matching Results"
+msgstr ""
+
msgid "AlertManagement|No alert data to display."
msgstr ""
-msgid "AlertManagement|No alerts available to display. If you think you're seeing this message in error, refresh the page."
+msgid "AlertManagement|No alerts available to display. See %{linkStart}enabling alert management%{linkEnd} for more information on adding alerts to the list."
msgstr ""
msgid "AlertManagement|No alerts to display."
@@ -1820,6 +1912,9 @@ msgstr ""
msgid "AlertManagement|None"
msgstr ""
+msgid "AlertManagement|None -"
+msgstr ""
+
msgid "AlertManagement|Open"
msgstr ""
@@ -1856,9 +1951,18 @@ msgstr ""
msgid "AlertManagement|There was an error displaying the alerts. Confirm your endpoint's configuration details to ensure alerts appear."
msgstr ""
+msgid "AlertManagement|There was an error while updating the assignee(s) list. Please try again."
+msgstr ""
+
+msgid "AlertManagement|There was an error while updating the assignee(s) of the alert. Please try again."
+msgstr ""
+
msgid "AlertManagement|There was an error while updating the status of the alert. Please try again."
msgstr ""
+msgid "AlertManagement|This assignee cannot be assigned to this alert."
+msgstr ""
+
msgid "AlertManagement|Tool"
msgstr ""
@@ -2006,6 +2110,9 @@ msgstr "å…许用户注册任何应用程åºï¼Œä½¿ç”¨ GitLab 作为 OAuth æä¾›
msgid "Allow users to request access (if visibility is public or internal)"
msgstr "å…许用户请求访问(如果å¯è§æ€§æ˜¯å…¬å¼€æˆ–内部的)"
+msgid "Allowed Geo IP"
+msgstr ""
+
msgid "Allowed email domain restriction only permitted for top-level groups"
msgstr "å…许使用电å­é‚®ä»¶åŸŸåé™åˆ¶ä»…å¯ç”¨äºŽé¡¶çº§ç¾¤ç»„"
@@ -2039,6 +2146,9 @@ msgstr "Amazon EKS集æˆå…许您从GitLab管ç†EKS集群。"
msgid "Amazon Web Services"
msgstr "Amazon网络æœåŠ¡"
+msgid "Amazon Web Services Logo"
+msgstr ""
+
msgid "Amazon authentication is not %{link_start}correctly configured%{link_end}. Ask your GitLab administrator if you want to use this service."
msgstr "Amazon的身份验è¯æœª%{link_start}正确é…ç½®%{link_end}。如需使用这项æœåŠ¡ï¼Œè¯·è”ç³»GitLab管ç†å‘˜ã€‚"
@@ -2061,7 +2171,7 @@ msgid "An error has occurred"
msgstr "å‘生错误"
msgid "An error occurred adding a draft to the thread."
-msgstr "å‘è¯é¢˜æ·»åŠ è‰ç¨¿æ—¶å‡ºé”™ã€‚"
+msgstr "å‘主题添加è‰ç¨¿æ—¶å‡ºé”™ã€‚"
msgid "An error occurred adding a new draft."
msgstr "添加新è‰ç¨¿æ—¶å‡ºé”™ã€‚"
@@ -2096,11 +2206,17 @@ msgstr "å°è¯•è§£å†³è®¨è®ºæ—¶å‡ºé”™ã€‚请å†è¯•ä¸€æ¬¡ã€‚"
msgid "An error occurred when updating the issue weight"
msgstr "更新议题æƒé‡æ—¶å‘生错误"
+msgid "An error occurred while acknowledging the notification. Refresh the page and try again."
+msgstr ""
+
+msgid "An error occurred while adding approvers"
+msgstr ""
+
msgid "An error occurred while adding formatted title for epic"
msgstr ""
-msgid "An error occurred while checking group path"
-msgstr "检查群组路径时å‘生错误"
+msgid "An error occurred while checking group path. Please refresh and try again."
+msgstr ""
msgid "An error occurred while committing your changes."
msgstr "æ交更改时å‘生错误。"
@@ -2124,7 +2240,7 @@ msgid "An error occurred while disabling Service Desk."
msgstr "ç¦ç”¨æœåŠ¡å°æ—¶å‘生错误。"
msgid "An error occurred while dismissing the alert. Refresh the page and try again."
-msgstr "消除警告时å‘生错误。请刷新页é¢å¹¶å†æ¬¡å°è¯•ã€‚"
+msgstr "消除警报时å‘生错误。请刷新页é¢å¹¶å†æ¬¡å°è¯•ã€‚"
msgid "An error occurred while dismissing the feature highlight. Refresh the page and try dismissing again."
msgstr "关闭功能çªå‡ºæ˜¾ç¤ºæ—¶å‘生错误。请刷新页é¢å¹¶å†æ¬¡å°è¯•ã€‚"
@@ -2243,7 +2359,7 @@ msgstr "加载åˆå¹¶è¯·æ±‚æ—¶å‘生错误。"
msgid "An error occurred while loading milestones"
msgstr ""
-msgid "An error occurred while loading terraform report"
+msgid "An error occurred while loading project creation UI"
msgstr ""
msgid "An error occurred while loading the data. Please try again."
@@ -2297,6 +2413,9 @@ msgstr "删除议题时å‘生错误。"
msgid "An error occurred while rendering preview broadcast message"
msgstr "渲染广播消æ¯æ—¶å‘生错误"
+msgid "An error occurred while rendering the editor"
+msgstr ""
+
msgid "An error occurred while reordering issues."
msgstr "é‡æ–°æŽ’åºè®®é¢˜æ—¶å‘生错误。"
@@ -2393,7 +2512,7 @@ msgstr "分æžWeb应用程åºçš„审阅版本。"
msgid "Analyze your dependencies for known vulnerabilities."
msgstr ""
-msgid "Analyze your source code and git history for secrets"
+msgid "Analyze your source code and git history for secrets."
msgstr ""
msgid "Analyze your source code for known vulnerabilities."
@@ -2507,12 +2626,18 @@ msgstr ""
msgid "Apply suggestion"
msgstr "应用建议"
+msgid "Apply suggestions"
+msgstr ""
+
msgid "Apply template"
msgstr "应用模æ¿"
msgid "Apply this approval rule to any branch or a specific protected branch."
msgstr ""
+msgid "Applying"
+msgstr ""
+
msgid "Applying a template will replace the existing issue description. Any changes you have made will be lost."
msgstr "使用模æ¿å°†æ›¿æ¢å½“å‰çš„议题æ述。您所åšçš„任何更改都将丢失。"
@@ -2525,12 +2650,18 @@ msgstr "正在执行命令以 %{commandDescription}"
msgid "Applying multiple commands"
msgstr "正在执行多个命令"
-msgid "Applying suggestion"
-msgstr "正在应用建议"
+msgid "Applying suggestion..."
+msgstr ""
+
+msgid "Applying suggestions..."
+msgstr ""
msgid "Approval rules"
msgstr "批准规则"
+msgid "Approval rules reset to project defaults"
+msgstr ""
+
msgid "ApprovalRuleRemove|%d member"
msgid_plural "ApprovalRuleRemove|%d members"
msgstr[0] "%d æˆå‘˜"
@@ -2580,6 +2711,9 @@ msgstr "批准当å‰åˆå¹¶è¯·æ±‚。"
msgid "Approved"
msgstr ""
+msgid "Approved MRs"
+msgstr ""
+
msgid "Approved by: "
msgstr ""
@@ -2793,6 +2927,9 @@ msgstr "资æº:"
msgid "Assign"
msgstr "指派"
+msgid "Assign Iteration"
+msgstr ""
+
msgid "Assign custom color like #FF0000"
msgstr "分é…自定义颜色,如FF0000"
@@ -2811,6 +2948,9 @@ msgstr "为此里程碑分é…一些议题。"
msgid "Assign to"
msgstr "分é…到"
+msgid "Assign to commenting user"
+msgstr ""
+
msgid "Assign yourself to these issues"
msgstr "将这些议题分é…给自己"
@@ -2836,6 +2976,9 @@ msgid "Assignee"
msgid_plural "%d Assignees"
msgstr[0] "%dä½æŒ‡æ´¾äºº"
+msgid "Assignee has no permissions"
+msgstr ""
+
msgid "Assignee lists not available with your current license"
msgstr "当å‰è®¸å¯è¯æ— æ³•ä½¿ç”¨æŒ‡æ´¾åˆ—表"
@@ -2845,6 +2988,9 @@ msgstr "指派列表显示分é…给选定用户的所有议题。"
msgid "Assignee(s)"
msgstr "指派人"
+msgid "Assignees"
+msgstr ""
+
msgid "Assigns %{assignee_users_sentence}."
msgstr "指派 %{assignee_users_sentence}。"
@@ -2879,6 +3025,9 @@ msgstr "审计事件"
msgid "Audit Events is a way to keep track of important events that happened in GitLab."
msgstr "审计事件用æ¥è·Ÿè¸ªGitLab中å‘生的é‡è¦äº‹ä»¶ã€‚"
+msgid "Audit Log"
+msgstr ""
+
msgid "AuditEvents|(removed)"
msgstr "(已删除)"
@@ -2915,6 +3064,9 @@ msgstr ""
msgid "AuditLogs|IP Address"
msgstr ""
+msgid "AuditLogs|Member Events"
+msgstr ""
+
msgid "AuditLogs|No matching %{type} found."
msgstr ""
@@ -3197,9 +3349,6 @@ msgstr "您的徽章"
msgid "Badges|e.g. %{exampleUrl}"
msgstr "例如 %{exampleUrl}"
-msgid "Badge|New"
-msgstr "新建"
-
msgid "Balsamiq file could not be loaded."
msgstr "无法加载 balsamiq 文件。"
@@ -3248,6 +3397,12 @@ msgstr "下é¢æ˜¯å½“å‰å®žä¾‹SSH主机密钥的指纹。"
msgid "Below you will find all the groups that are public."
msgstr "您将在下é¢æ‰¾åˆ°æ‰€æœ‰å…¬å¼€çš„群组。"
+msgid "Beta"
+msgstr ""
+
+msgid "Bi-weekly code coverage"
+msgstr ""
+
msgid "Billing"
msgstr "计费"
@@ -3308,9 +3463,15 @@ msgstr "å‡çº§"
msgid "Bitbucket Server Import"
msgstr "BitbucketæœåŠ¡å™¨å¯¼å…¥"
+msgid "Bitbucket Server import"
+msgstr ""
+
msgid "Bitbucket import"
msgstr "从 Bitbucket 导入"
+msgid "Blame"
+msgstr ""
+
msgid "Blocked"
msgstr "å·²ç¦ç”¨"
@@ -3323,9 +3484,6 @@ msgstr "阻止"
msgid "Blog"
msgstr "åšå®¢"
-msgid "Blue helpers indicate an action to be taken."
-msgstr "è“色助手表示需è¦é‡‡å–的行动。"
-
msgid "Board name"
msgstr "看æ¿å称"
@@ -3524,6 +3682,9 @@ msgstr "广播消æ¯å·²æˆåŠŸåˆ›å»ºã€‚"
msgid "Broadcast Message was successfully updated."
msgstr "广播消æ¯å·²æˆåŠŸæ›´æ–°ã€‚"
+msgid "Broadcast Messages"
+msgstr ""
+
msgid "Browse Directory"
msgstr "æµè§ˆç›®å½•"
@@ -3578,6 +3739,9 @@ msgstr ""
msgid "By %{user_name}"
msgstr "ç”± %{user_name}"
+msgid "By URL"
+msgstr ""
+
msgid "By default GitLab sends emails in HTML and plain text formats so mail clients can choose what format to use. Disable this option if you only want to send emails in plain text format."
msgstr "默认情况下,GitLab 以 HTML 和纯文本格å¼å‘é€ç”µå­é‚®ä»¶ï¼Œå› æ­¤é‚®ä»¶å®¢æˆ·ç«¯å¯ä»¥é€‰æ‹©ä½¿ç”¨å“ªç§æ ¼å¼ã€‚如果您åªæƒ³ä»¥çº¯æ–‡æœ¬æ ¼å¼å‘é€ç”µå­é‚®ä»¶ï¼Œè¯·ç¦ç”¨æ­¤é€‰é¡¹ã€‚"
@@ -3683,6 +3847,9 @@ msgstr "å¯ä»¥æ‰‹åŠ¨éƒ¨ç½²åˆ°"
msgid "Can override approvers and approvals required per merge request"
msgstr "å¯ä»¥è¦†ç›–æ¯ä¸ªåˆå¹¶è¯·æ±‚所需的核准人和核准信æ¯"
+msgid "Can't apply as this line has changed or the suggestion already matches its content."
+msgstr ""
+
msgid "Can't create snippet: %{err}"
msgstr ""
@@ -3869,9 +4036,6 @@ msgstr "差异显示方å¼ä¾<b>æº</b>版本åˆå¹¶åˆ°<b>目标</b>版本的形å
msgid "Changes are still tracked. Useful for cluster/index migrations."
msgstr ""
-msgid "Changes are unknown"
-msgstr ""
-
msgid "Changes suppressed. Click to show."
msgstr "仅显示部分。点此显示全部。"
@@ -4178,15 +4342,9 @@ msgstr "选择å¯è§æ€§çº§åˆ«ï¼Œå¯ç”¨/ç¦ç”¨é¡¹ç›®åŠŸèƒ½ï¼ˆè®®é¢˜ï¼Œä»“库,wi
msgid "Choose what content you want to see on a group’s overview page"
msgstr "选择è¦åœ¨ç¾¤ç»„概述页é¢ä¸Šçœ‹åˆ°çš„内容"
-msgid "Choose which groups you wish to synchronize to this secondary node"
-msgstr ""
-
msgid "Choose which repositories you want to connect and run CI/CD pipelines."
msgstr "请选择è¦è¿žæŽ¥å¹¶è¿è¡Œ CI/CD æµæ°´çº¿çš„代ç ä»“库。"
-msgid "Choose which shards you wish to synchronize to this secondary node"
-msgstr ""
-
msgid "Choose your framework"
msgstr ""
@@ -4484,9 +4642,15 @@ msgstr "群集缓存已清除。"
msgid "Cluster does not exist"
msgstr ""
+msgid "Cluster is required for Stages::ClusterEndpointInserter"
+msgstr ""
+
msgid "Cluster level"
msgstr ""
+msgid "Cluster type must be specificed for Stages::ClusterEndpointInserter"
+msgstr ""
+
msgid "ClusterIntegration| %{custom_domain_start}More information%{custom_domain_end}."
msgstr "%{custom_domain_start}更多信æ¯%{custom_domain_end}。"
@@ -4598,8 +4762,8 @@ msgstr "CAè¯ä¹¦"
msgid "ClusterIntegration|Cert-Manager"
msgstr "Cert-Manager"
-msgid "ClusterIntegration|Cert-Manager is a native Kubernetes certificate management controller that helps with issuing certificates. Installing Cert-Manager on your cluster will issue a certificate by %{letsEncrypt} and ensure that certificates are valid and up-to-date."
-msgstr "Cert-Manager 是一个本地的 Kubernetes è¯ä¹¦ç®¡ç†æŽ§åˆ¶å™¨ï¼Œå¯å¸®åŠ©é¢å‘è¯ä¹¦ã€‚在群集上安装 Cert-Manager 将通过 %{letsEncrypt} é¢å‘è¯ä¹¦ï¼Œå¹¶ç¡®ä¿è¯ä¹¦æœ‰æ•ˆä¸”是最新的。"
+msgid "ClusterIntegration|Cert-Manager is a native Kubernetes certificate management controller that helps with issuing certificates. Installing Cert-Manager on your cluster will issue a certificate by %{linkStart}Let's Encrypt%{linkEnd} and ensure that certificates are valid and up-to-date."
+msgstr ""
msgid "ClusterIntegration|Certificate Authority bundle (PEM format)"
msgstr "è¯ä¹¦æŽˆæƒåŒ…(PEMæ ¼å¼)"
@@ -4625,9 +4789,6 @@ msgstr "清除集群缓存"
msgid "ClusterIntegration|Clear the local cache of namespace and service accounts. This is necessary if your integration has become out of sync. The cache is repopulated during the next CI job that requires namespace and service accounts."
msgstr "清除å称空间和æœåŠ¡å¸æˆ·çš„本地缓存。如集æˆå·²å¤±åŽ»åŒæ­¥ï¼Œåˆ™æ­¤æ“作为必须。缓存将在所需å称空间和æœåŠ¡å¸æˆ·çš„下一个CI作业期间é‡æ–°ç”Ÿæˆã€‚"
-msgid "ClusterIntegration|Cloud Run"
-msgstr "Cloud Run"
-
msgid "ClusterIntegration|Cluster being created"
msgstr ""
@@ -4712,7 +4873,7 @@ msgstr "正在创建 Kubernetes 集群"
msgid "ClusterIntegration|Crossplane"
msgstr "Crossplane"
-msgid "ClusterIntegration|Crossplane enables declarative provisioning of managed services from your cloud of choice using %{codeStart}kubectl%{codeEnd} or %{gitlabIntegrationLink}. Crossplane runs inside your Kubernetes cluster and supports secure connectivity and secrets management between app containers and the cloud services they depend on."
+msgid "ClusterIntegration|Crossplane enables declarative provisioning of managed services from your cloud of choice using %{codeStart}kubectl%{codeEnd} or %{linkStart}GitLab Integration%{linkEnd}. Crossplane runs inside your Kubernetes cluster and supports secure connectivity and secrets management between app containers and the cloud services they depend on."
msgstr ""
msgid "ClusterIntegration|Deletes all GitLab resources attached to this cluster during removal"
@@ -4796,9 +4957,6 @@ msgstr "GitLab Runner连接到仓库并执行CI/CD作业,返回结果并将应
msgid "ClusterIntegration|GitLab-managed cluster"
msgstr "GitLab管ç†çš„集群"
-msgid "ClusterIntegration|Gitlab Integration"
-msgstr "GitLab集æˆ"
-
msgid "ClusterIntegration|Global default"
msgstr ""
@@ -4844,8 +5002,8 @@ msgstr "Ingress 节点"
msgid "ClusterIntegration|Ingress gives you a way to route requests to services based on the request host or path, centralizing a number of services into a single entrypoint."
msgstr "Ingress为您æ供了一ç§åŸºäºŽè¯·æ±‚主机或路径将请求路由到æœåŠ¡çš„方法,将多个æœåŠ¡é›†ä¸­åˆ°ä¸€ä¸ªå…¥å£ç‚¹ã€‚"
-msgid "ClusterIntegration|Installing Ingress may incur additional costs. Learn more about %{pricingLink}."
-msgstr "安装 Ingress å¯èƒ½ä¼šäº§ç”Ÿé¢å¤–费用。了解更多 %{pricingLink}。"
+msgid "ClusterIntegration|Installing Ingress may incur additional costs. Learn more about %{linkStart}pricing%{linkEnd}."
+msgstr ""
msgid "ClusterIntegration|Instance cluster"
msgstr "实例集群"
@@ -4859,8 +5017,8 @@ msgstr "集æˆKubernetes集群自动化"
msgid "ClusterIntegration|Issuer Email"
msgstr "ç­¾å‘人电å­é‚®ä»¶"
-msgid "ClusterIntegration|Issuers represent a certificate authority. You must provide an email address for your Issuer. "
-msgstr "ç­¾å‘人代表è¯ä¹¦é¢å‘机构。您必须为您的签å‘人æ供电å­é‚®ä»¶åœ°å€ã€‚ "
+msgid "ClusterIntegration|Issuers represent a certificate authority. You must provide an email address for your Issuer."
+msgstr ""
msgid "ClusterIntegration|Jupyter Hostname"
msgstr "Jupyter主机å"
@@ -4931,9 +5089,6 @@ msgstr "了解更多的群组级Kubernetes集群信æ¯"
msgid "ClusterIntegration|Learn more about instance Kubernetes clusters"
msgstr "了解有关实例级Kubernetes集群更多信æ¯"
-msgid "ClusterIntegration|Let's Encrypt"
-msgstr "Let's Encrypt"
-
msgid "ClusterIntegration|Loading IAM Roles"
msgstr "加载IAM角色"
@@ -5045,8 +5200,8 @@ msgstr "项目命å空间å‰ç¼€(å¯é€‰ï¼Œå”¯ä¸€)"
msgid "ClusterIntegration|Prometheus"
msgstr "Prometheus"
-msgid "ClusterIntegration|Prometheus is an open-source monitoring system with %{gitlabIntegrationLink} to monitor deployed applications."
-msgstr "Prometheus是一个开æºç›‘控系统,其中 %{gitlabIntegrationLink} 用于监控已部署的应用程åºã€‚"
+msgid "ClusterIntegration|Prometheus is an open-source monitoring system with %{linkStart}GitLab Integration%{linkEnd} to monitor deployed applications."
+msgstr ""
msgid "ClusterIntegration|Provider details"
msgstr ""
@@ -5201,10 +5356,10 @@ msgstr "选择地域"
msgid "ClusterIntegration|Select zone to choose machine type"
msgstr "按地域选择实例类型"
-msgid "ClusterIntegration|Send Cilium Logs"
+msgid "ClusterIntegration|Send Container Network Policies Logs"
msgstr ""
-msgid "ClusterIntegration|Send ModSecurity Logs"
+msgid "ClusterIntegration|Send Web Application Firewall Logs"
msgstr ""
msgid "ClusterIntegration|Service Token"
@@ -5372,15 +5527,12 @@ msgstr "访问 Google Kubernetes Engine"
msgid "ClusterIntegration|documentation"
msgstr "文档"
-msgid "ClusterIntegration|installed via %{installed_via}"
-msgstr "通过%{installed_via}安装"
+msgid "ClusterIntegration|installed via %{linkStart}Cloud Run%{linkEnd}"
+msgstr ""
msgid "ClusterIntegration|meets the requirements"
msgstr "符åˆç›¸å…³è¦æ±‚"
-msgid "ClusterIntegration|pricing"
-msgstr "定价"
-
msgid "ClusterIntegration|sign up"
msgstr "注册"
@@ -5417,6 +5569,15 @@ msgstr ""
msgid "Code"
msgstr "代ç "
+msgid "Code Coverage: %{coveragePercentage}%{percentSymbol}"
+msgstr ""
+
+msgid "Code Coverage| Empty code coverage data"
+msgstr ""
+
+msgid "Code Coverage|Couldn't fetch the code coverage data"
+msgstr ""
+
msgid "Code Owners"
msgstr "代ç æ‰€æœ‰è€…"
@@ -5471,7 +5632,7 @@ msgstr "收起"
msgid "Collapse approvers"
msgstr "折å æ ¸å‡†äºº"
-msgid "Collapse child epics"
+msgid "Collapse milestones"
msgstr ""
msgid "Collapse replies"
@@ -5489,6 +5650,9 @@ msgstr "ComboSearch未定义"
msgid "Coming soon"
msgstr ""
+msgid "Comma-separated, e.g. '1.1.1.1, 2.2.2.0/24'"
+msgstr ""
+
msgid "Command"
msgstr "命令"
@@ -5511,10 +5675,10 @@ msgid "Comment & reopen %{noteable_name}"
msgstr "评论并é‡æ–°å¼€å¯ %{noteable_name}"
msgid "Comment & resolve thread"
-msgstr "评论并解决è¯é¢˜"
+msgstr "评论并解决主题"
msgid "Comment & unresolve thread"
-msgstr "评论并将è¯é¢˜ç½®ä¸ºæœªè§£å†³"
+msgstr "评论并将主题置为未解决"
msgid "Comment '%{label}' position"
msgstr ""
@@ -5658,6 +5822,12 @@ msgstr ""
msgid "Compliance framework (optional)"
msgstr ""
+msgid "Compliance frameworks"
+msgstr ""
+
+msgid "ComplianceDashboard|created by:"
+msgstr ""
+
msgid "ComplianceFramework|GDPR"
msgstr ""
@@ -5734,7 +5904,7 @@ msgid "Configure limits for web and API requests."
msgstr "é…ç½® web å’Œ API 请求é™åˆ¶ã€‚"
msgid "Configure limits on the number of inbound alerts able to be sent to a project."
-msgstr "é…ç½®å¯ä»¥å‘é€åˆ°é¡¹ç›®çš„传入警告的数é‡ã€‚"
+msgstr "é…ç½®å¯ä»¥å‘é€åˆ°é¡¹ç›®çš„传入警报的数é‡ã€‚"
msgid "Configure paths to be protected by Rack Attack."
msgstr ""
@@ -5796,6 +5966,9 @@ msgstr "连接失败"
msgid "Connection timed out"
msgstr "连接超时"
+msgid "Connection timeout"
+msgstr ""
+
msgid "Contact sales to upgrade"
msgstr "è”系销售人员进行å‡çº§"
@@ -5849,9 +6022,6 @@ msgstr ""
msgid "ContainerRegistry|CLI Commands"
msgstr ""
-msgid "ContainerRegistry|Compressed Size"
-msgstr ""
-
msgid "ContainerRegistry|Container Registry"
msgstr "容器镜åƒåº“"
@@ -5867,6 +6037,9 @@ msgstr "å¤åˆ¶ç™»å½•å‘½ä»¤"
msgid "ContainerRegistry|Copy push command"
msgstr "å¤åˆ¶æŽ¨é€å‘½ä»¤"
+msgid "ContainerRegistry|Delete selected"
+msgstr ""
+
msgid "ContainerRegistry|Docker connection error"
msgstr "Docker连接错误"
@@ -5900,18 +6073,18 @@ msgstr ""
msgid "ContainerRegistry|If you are not already logged in, you need to authenticate to the Container Registry by using your GitLab username and password. If you have %{twofaDocLinkStart}Two-Factor Authentication%{twofaDocLinkEnd} enabled, use a %{personalAccessTokensDocLinkStart}Personal Access Token%{personalAccessTokensDocLinkEnd} instead of a password."
msgstr "如果您尚未登录,您需è¦ä½¿ç”¨æ‚¨çš„GitLab用户å和密ç æ¥è¿›è¡Œèº«ä»½è®¤è¯ã€‚如果您å¯ç”¨ %{twofaDocLinkStart}åŒé‡èº«ä»½éªŒè¯%{twofaDocLinkEnd} ,请使用%{personalAccessTokensDocLinkStart}个人访问令牌%{personalAccessTokensDocLinkEnd}而ä¸æ˜¯å¯†ç ã€‚"
-msgid "ContainerRegistry|Image ID"
-msgstr "é•œåƒID"
+msgid "ContainerRegistry|Image ID: %{imageId}"
+msgstr ""
msgid "ContainerRegistry|Image Repositories"
msgstr ""
+msgid "ContainerRegistry|Image tags"
+msgstr ""
+
msgid "ContainerRegistry|Keep and protect the images that matter most."
msgstr "ä¿ç•™å’Œä¿æŠ¤æœ€é‡è¦çš„é•œåƒã€‚"
-msgid "ContainerRegistry|Last Updated"
-msgstr "最近更新时间"
-
msgid "ContainerRegistry|Login"
msgstr ""
@@ -5924,6 +6097,9 @@ msgstr "è¦ä¿ç•™çš„标签数é‡ï¼š"
msgid "ContainerRegistry|Please contact your administrator."
msgstr ""
+msgid "ContainerRegistry|Published %{timeInfo}"
+msgstr ""
+
msgid "ContainerRegistry|Push an image"
msgstr ""
@@ -5939,9 +6115,6 @@ msgstr ""
msgid "ContainerRegistry|Remove repository"
msgstr "删除仓库"
-msgid "ContainerRegistry|Remove selected tags"
-msgstr "删除选中的标签"
-
msgid "ContainerRegistry|Remove tag"
msgid_plural "ContainerRegistry|Remove tags"
msgstr[0] "删除标签"
@@ -5970,9 +6143,6 @@ msgstr "更新到期政策时出了错。"
msgid "ContainerRegistry|Sorry, your filter produced no results."
msgstr ""
-msgid "ContainerRegistry|Tag"
-msgstr "标签"
-
msgid "ContainerRegistry|Tag expiration policy"
msgstr "标签过期策略"
@@ -6117,21 +6287,6 @@ msgstr "控制与您å¸æˆ·å…³è”的电å­é‚®ä»¶"
msgid "Control the display of third party offers."
msgstr "控制第三方优惠的显示。"
-msgid "Control the maximum concurrency of LFS/attachment backfill for this secondary node"
-msgstr ""
-
-msgid "Control the maximum concurrency of container repository operations for this Geo node"
-msgstr ""
-
-msgid "Control the maximum concurrency of repository backfill for this secondary node"
-msgstr "控制此次è¦èŠ‚点的åŒæ­¥ä»“库的最大并å‘"
-
-msgid "Control the maximum concurrency of verification operations for this Geo node"
-msgstr ""
-
-msgid "Control the minimum interval in days that a repository should be reverified for this primary node"
-msgstr ""
-
msgid "Cookie domain"
msgstr "Cookie域"
@@ -6273,6 +6428,9 @@ msgstr "无法删除èŠå¤©æ˜µç§° %{chat_name}。"
msgid "Could not find design."
msgstr ""
+msgid "Could not find iteration"
+msgstr ""
+
msgid "Could not remove the trigger."
msgstr "无法删除触å‘器。"
@@ -6309,6 +6467,12 @@ msgstr "国家/地区"
msgid "Coverage"
msgstr "覆盖率"
+msgid "Coverage Fuzzing"
+msgstr ""
+
+msgid "Crash State"
+msgstr ""
+
msgid "Create"
msgstr "创建"
@@ -6357,6 +6521,9 @@ msgstr "创建一个新仓库"
msgid "Create a personal access token on your account to pull or push via %{protocol}."
msgstr "在å¸æˆ·ä¸Šåˆ›å»ºä¸ªäººè®¿é—®ä»¤ç‰Œï¼Œä»¥é€šè¿‡ %{protocol} æ¥æ‹‰å–或推é€ã€‚"
+msgid "Create a project pre-populated with the necessary files to get you started quickly."
+msgstr ""
+
msgid "Create an account using:"
msgstr "使用以下方å¼åˆ›å»ºå¸æˆ·ï¼š"
@@ -6525,6 +6692,9 @@ msgstr "创建分支和åˆå¹¶è¯·æ±‚æ¥è§£å†³è¿™ä¸ªè®®é¢˜ã€‚"
msgid "Creates branch '%{branch_name}' and a merge request to resolve this issue."
msgstr "创建分支“%{branch_name}â€å’Œåˆå¹¶è¯·æ±‚以解决此议题。"
+msgid "Creating"
+msgstr ""
+
msgid "Creating epic"
msgstr "创建å²è¯—中"
@@ -6576,7 +6746,10 @@ msgstr "当å‰å¯†ç "
msgid "Current vulnerabilities count"
msgstr "当å‰æ¼æ´žæ•°é‡"
-msgid "CurrentUser|Buy CI minutes"
+msgid "CurrentUser|Buy Pipeline minutes"
+msgstr ""
+
+msgid "CurrentUser|One of your groups is running out"
msgstr ""
msgid "CurrentUser|Profile"
@@ -6591,6 +6764,9 @@ msgstr "开始试用金牌计划"
msgid "CurrentUser|Upgrade"
msgstr ""
+msgid "Custom Attributes"
+msgstr ""
+
msgid "Custom CI configuration path"
msgstr "自定义CIé…置路径"
@@ -6853,12 +7029,18 @@ msgstr "阶段下拉列表"
msgid "DAG"
msgstr ""
+msgid "DAG visualization requires at least 3 dependent jobs."
+msgstr ""
+
msgid "DNS"
msgstr "DNS"
msgid "Dashboard"
msgstr "仪表æ¿"
+msgid "Dashboard uid not found"
+msgstr ""
+
msgid "DashboardProjects|All"
msgstr "所有"
@@ -6883,6 +7065,9 @@ msgstr "无法添加%{invalidProjects}。此仪表æ¿å¯ç”¨äºŽå…¬å¼€é¡¹ç›®ï¼Œä»¥
msgid "Data is still calculating..."
msgstr "æ•°æ®ä»åœ¨è®¡ç®—中……"
+msgid "Datasource name not found"
+msgstr ""
+
msgid "Date"
msgstr "日期"
@@ -7063,6 +7248,9 @@ msgstr "删除æºåˆ†æ”¯"
msgid "Delete this attachment"
msgstr "删除此附件"
+msgid "Delete user list"
+msgstr ""
+
msgid "Delete variable"
msgstr ""
@@ -7374,6 +7562,9 @@ msgstr "已部署到"
msgid "Deploying to"
msgstr "正在部署到"
+msgid "Deploying to AWS is easy with GitLab"
+msgstr ""
+
msgid "Deployment Frequency"
msgstr ""
@@ -7735,6 +7926,9 @@ msgstr "在æµæ°´çº¿%{pipelineLink}上忽略"
msgid "Dismissed on pipeline %{pipelineLink} at %{projectLink}"
msgstr "在%{projectLink} 中的æµæ°´çº¿ %{pipelineLink}上忽略"
+msgid "Display alerts from all your monitoring tools directly within GitLab."
+msgstr ""
+
msgid "Display name"
msgstr "显示å称"
@@ -7792,9 +7986,6 @@ msgstr "ä¸è¦æŠŠGPG密钥中的ç§é’¥éƒ¨åˆ†è´´è¿‡æ¥ï¼Œè¯·åªç²˜è´´å…¬é’¥éƒ¨åˆ†
msgid "Don't show again"
msgstr "ä¸å†æ˜¾ç¤º"
-msgid "Don't worry, you can access this tour by clicking on the help icon in the top right corner and choose <strong>Learn GitLab</strong>."
-msgstr "ä¸ç”¨æ‹…心,您å¯ä»¥é€šè¿‡å•å‡»å³ä¸Šè§’的帮助图标æ¥è®¿é—®æ­¤å¯¼è§ˆï¼Œç„¶åŽé€‰æ‹© <strong>Learn GitLab</strong>。"
-
msgid "Done"
msgstr "完æˆ"
@@ -7921,6 +8112,9 @@ msgstr "编辑 %{id} æµæ°´çº¿è®¡åˆ’"
msgid "Edit Release"
msgstr "编辑å‘布"
+msgid "Edit Slack integration"
+msgstr ""
+
msgid "Edit Snippet"
msgstr "编辑代ç ç‰‡æ®µ"
@@ -7960,6 +8154,9 @@ msgstr "编辑 %{user_name} 的身份信æ¯"
msgid "Edit issues"
msgstr "编辑议题"
+msgid "Edit iteration"
+msgstr ""
+
msgid "Edit public deploy key"
msgstr "编辑公共部署密钥"
@@ -7973,7 +8170,7 @@ msgid "Edit wiki page"
msgstr "编辑Wiki页é¢"
msgid "Edit your most recent comment in a thread (from an empty textarea)"
-msgstr "编辑你在最近è¯é¢˜ä¸­çš„评论(从空白文本区)"
+msgstr "编辑你在最近主题中的评论(从空白文本区)"
msgid "Edited %{timeago}"
msgstr ""
@@ -8008,6 +8205,9 @@ msgstr "无。请选择è¦å»ºç«‹ç´¢å¼•çš„项目。"
msgid "Email"
msgstr "电å­é‚®ä»¶"
+msgid "Email Notification"
+msgstr ""
+
msgid "Email address"
msgstr "电å­é‚®ä»¶åœ°å€"
@@ -8020,6 +8220,9 @@ msgstr ""
msgid "Email not verified. Please verify your email in Salesforce."
msgstr "电å­é‚®ä»¶æœªéªŒè¯ã€‚请在Salesforce中验è¯æ‚¨çš„电å­é‚®ä»¶ã€‚"
+msgid "Email notification for unknown sign-ins"
+msgstr ""
+
msgid "Email patch"
msgstr "电å­é‚®ä»¶è¡¥ä¸"
@@ -8111,7 +8314,7 @@ msgid "Enable HTML emails"
msgstr "å¯ç”¨ HTML 电å­é‚®ä»¶"
msgid "Enable Incident Management inbound alert limit"
-msgstr "å¯ç”¨äº‹ä»¶ç®¡ç†ä¼ å…¥è­¦å‘Šé™åˆ¶"
+msgstr "å¯ç”¨äº‹ä»¶ç®¡ç†ä¼ å…¥è­¦æŠ¥é™åˆ¶"
msgid "Enable PlantUML"
msgstr "å¯ç”¨PlantUML"
@@ -8251,6 +8454,9 @@ msgstr "结æŸäºŽ(UTC)"
msgid "Enforce DNS rebinding attack protection"
msgstr "强制DNSé‡æ–°ç»‘定攻击ä¿æŠ¤"
+msgid "Enforce personal access token expiration"
+msgstr ""
+
msgid "Ensure connectivity is available from the GitLab server to the Prometheus server"
msgstr "ç¡®ä¿ä»ŽGitLabæœåŠ¡å™¨åˆ°PrometheusæœåŠ¡å™¨çš„连接"
@@ -8320,6 +8526,9 @@ msgstr "输入åˆå¹¶è¯·æ±‚标题"
msgid "Enter the name of your application, and we'll return a unique %{type}."
msgstr ""
+msgid "Enter weights for storages for new repositories."
+msgstr ""
+
msgid "Enter your password to approve"
msgstr "输入批准密ç "
@@ -8329,15 +8538,27 @@ msgstr "环境"
msgid "Environment does not have deployments"
msgstr ""
+msgid "Environment is required for Stages::MetricEndpointInserter"
+msgstr ""
+
+msgid "Environment is required for Stages::VariableEndpointInserter"
+msgstr ""
+
msgid "Environment scope"
msgstr ""
+msgid "Environment variables are applied to all project environments in this instance via the Runner. You can use environment variables for passwords, secret keys, etc. Make variables available to the running application by prepending the variable key with <code>K8S_SECRET_</code>. You can set variables to be:"
+msgstr ""
+
msgid "Environment variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. Additionally, they can be masked so they are hidden in job logs, though they must match certain regexp requirements to do so. You can use environment variables for passwords, secret keys, or whatever you want."
msgstr "环境å˜é‡é€šè¿‡Runner应用于环境。è¦ä¿æŠ¤å®ƒä»¬ï¼Œå¯å°†ä»–们仅开放给给å—ä¿æŠ¤çš„分支或标签。此外,它们å¯ä»¥è¢«å±è”½ï¼Œä»Žè€Œåœ¨ä½œä¸šæ—¥å¿—中éšè—它们,但必须符åˆä¸€å®šçš„正则表达å¼è¦æ±‚æ‰èƒ½è¢«å±è”½ã€‚您å¯ä»¥å°†çŽ¯å¢ƒå˜é‡ç”¨äºŽå¯†ç ã€å¯†é’¥æˆ–任何您想è¦ä½¿ç”¨çš„地方。"
msgid "Environment variables are configured by your administrator to be %{link_start}protected%{link_end} by default"
msgstr "环境å˜é‡å·²è¢«ç®¡ç†å‘˜é…置为%{link_start}å—ä¿æŠ¤çš„(默认)%{link_end}"
+msgid "Environment variables on this GitLab instance are configured to be %{link_start}protected%{link_end} by default"
+msgstr ""
+
msgid "Environment:"
msgstr "环境:"
@@ -8453,7 +8674,7 @@ msgid "Environments|Environments"
msgstr "环境"
msgid "Environments|Environments are places where code gets deployed, such as staging or production."
-msgstr "环境是指部署代ç çš„ä½ç½®ï¼Œä¾‹å¦‚预生产或生产。"
+msgstr "环境是指部署代ç çš„ä½ç½®ï¼Œä¾‹å¦‚预å‘布或生产。"
msgid "Environments|Install Elastic Stack on your cluster to enable advanced querying capabilities such as full text search."
msgstr "在群集上安装Elastic Stack,以å¯ç”¨é«˜çº§æŸ¥è¯¢åŠŸèƒ½ï¼Œä¾‹å¦‚全文æœç´¢ã€‚"
@@ -8869,6 +9090,9 @@ msgstr "全部"
msgid "EventFilterBy|Filter by comments"
msgstr "åªæ˜¾ç¤ºè¯„论事件"
+msgid "EventFilterBy|Filter by designs"
+msgstr ""
+
msgid "EventFilterBy|Filter by epic events"
msgstr "按å²è¯—事件筛选"
@@ -9001,15 +9225,15 @@ msgstr "展开全部"
msgid "Expand approvers"
msgstr "展开核准人"
-msgid "Expand child epics"
-msgstr ""
-
msgid "Expand down"
msgstr "å‘下展开"
msgid "Expand dropdown"
msgstr "展开下拉列表"
+msgid "Expand milestones"
+msgstr ""
+
msgid "Expand sidebar"
msgstr "展开侧边æ "
@@ -9025,6 +9249,9 @@ msgstr "过期时间"
msgid "Expiration date"
msgstr "到期时间"
+msgid "Expiration not enforced"
+msgstr ""
+
msgid "Expiration policy for the Container Registry is a perfect solution for keeping the Registry space down while still enjoying the full power of GitLab CI/CD."
msgstr "容器镜åƒåº“的过期策略作为一个较为ç†æƒ³çš„解决方案,å¯åœ¨ä¿æŒGitLab CI/CD的全部功能正常工作的åŒæ—¶å‡å°‘注册表空间å ç”¨ã€‚"
@@ -9145,6 +9372,9 @@ msgstr "已失败"
msgid "Failed Jobs"
msgstr "失败的作业"
+msgid "Failed on"
+msgstr ""
+
msgid "Failed to add a Zoom meeting"
msgstr "无法添加Zoom会议"
@@ -9205,14 +9435,20 @@ msgstr "获å–ref失败。"
msgid "Failed to install."
msgstr "安装失败。"
+msgid "Failed to load assignees. Please try again."
+msgstr ""
+
+msgid "Failed to load authors. Please try again."
+msgstr ""
+
msgid "Failed to load emoji list."
msgstr "无法加载表情列表。"
msgid "Failed to load error details from Sentry."
msgstr "无法从Sentry加载错误详细信æ¯ã€‚"
-msgid "Failed to load errors from Sentry. Error message: %{errorMessage}"
-msgstr "无法从Sentry加载错误。错误消æ¯: %{errorMessage}"
+msgid "Failed to load errors from Sentry."
+msgstr ""
msgid "Failed to load group activity metrics. Please try again."
msgstr ""
@@ -9292,6 +9528,9 @@ msgstr "无法ä¿å­˜å好设置。"
msgid "Failed to set due date because the date format is invalid."
msgstr "由于日期格å¼æ— æ•ˆï¼Œè®¾ç½®åˆ°æœŸæ—¥æœŸå¤±è´¥ã€‚"
+msgid "Failed to set iteration on this issue. Please try again."
+msgstr ""
+
msgid "Failed to signing using smartcard authentication"
msgstr "无法使用智能å¡èº«ä»½éªŒè¯è¿›è¡Œç™»å½•"
@@ -9514,15 +9753,12 @@ msgstr ""
msgid "FeatureFlags|Target environments"
msgstr "目标环境"
-msgid "FeatureFlags|There are no active feature flags"
-msgstr "没有å¯ç”¨çš„功能标志"
-
-msgid "FeatureFlags|There are no inactive feature flags"
-msgstr "没有ç¦ç”¨çš„功能标志"
-
msgid "FeatureFlags|There was an error fetching the feature flags."
msgstr "获å–功能标志时出错。"
+msgid "FeatureFlags|There was an error retrieving user lists"
+msgstr ""
+
msgid "FeatureFlags|Try again in a few moments or contact your support team."
msgstr "请ç¨åŽé‡è¯•æˆ–è”系支æŒå›¢é˜Ÿã€‚"
@@ -9532,9 +9768,18 @@ msgstr "用户ID"
msgid "FeatureFlag|Delete strategy"
msgstr ""
+msgid "FeatureFlag|List"
+msgstr ""
+
msgid "FeatureFlag|Percentage"
msgstr ""
+msgid "FeatureFlag|Select a user list"
+msgstr ""
+
+msgid "FeatureFlag|There are no configured user lists"
+msgstr ""
+
msgid "FeatureFlag|Type"
msgstr ""
@@ -9691,6 +9936,12 @@ msgstr "按å称过滤您的项目"
msgid "Filter..."
msgstr "过滤..."
+msgid "Find File"
+msgstr ""
+
+msgid "Find bugs in your code with coverage-guided fuzzing"
+msgstr ""
+
msgid "Find by path"
msgstr "按路径查找"
@@ -9955,6 +10206,9 @@ msgstr "Geo设置"
msgid "Geo allows you to replicate your GitLab instance to other geographical locations."
msgstr "Geoå…许您将您的GitLab实例å¤åˆ¶åˆ°å…¶ä»–地ç†ä½ç½®ã€‚"
+msgid "Geo nodes are paused using a command run on the node"
+msgstr ""
+
msgid "GeoNodeStatusEvent|%{timeAgoStr} (%{pendingEvents} events)"
msgstr "%{timeAgoStr} (%{pendingEvents}事件)"
@@ -10039,6 +10293,9 @@ msgstr ""
msgid "GeoNodes|Not checksummed"
msgstr "未校验"
+msgid "GeoNodes|Package files"
+msgstr ""
+
msgid "GeoNodes|Pausing replication stops the sync process. Are you sure?"
msgstr "æš‚åœå¤åˆ¶å°†åœæ­¢åŒæ­¥è¿›ç¨‹ã€‚确定继续å—?"
@@ -10057,6 +10314,9 @@ msgstr "å¤åˆ¶æ§½ WAL"
msgid "GeoNodes|Replication slots"
msgstr "å¤åˆ¶æ§½"
+msgid "GeoNodes|Replication status"
+msgstr ""
+
msgid "GeoNodes|Repositories"
msgstr "仓库"
@@ -10267,6 +10527,9 @@ msgstr "åŒæ­¥äºŽ"
msgid "Geo|Synchronization failed - %{error}"
msgstr "åŒæ­¥å¤±è´¥ - %{error}"
+msgid "Geo|Synchronization of %{itemTitle} is disabled."
+msgstr ""
+
msgid "Geo|The database is currently %{db_lag} behind the primary node."
msgstr "æ•°æ®åº“当å‰è½åŽäºŽä¸»èŠ‚点%{db_lag}。"
@@ -10579,6 +10842,9 @@ msgstr "å…¨å±æ¨¡å¼"
msgid "Go to %{link_to_google_takeout}."
msgstr "转至 %{link_to_google_takeout}。"
+msgid "Go to <strong>Issues</strong> > <strong>Boards</strong> to access your personalized learning issue board."
+msgstr ""
+
msgid "Go to Pipelines"
msgstr ""
@@ -10678,9 +10944,6 @@ msgstr "转到您的项目"
msgid "Go to your snippets"
msgstr "转到您的代ç ç‰‡æ®µ"
-msgid "Golden Tanuki"
-msgstr "金狸"
-
msgid "Google Cloud Platform"
msgstr "谷歌云平å°"
@@ -10702,6 +10965,9 @@ msgstr "了解ï¼"
msgid "Grafana URL"
msgstr "Grafana网å€"
+msgid "Grafana response contains invalid json"
+msgstr ""
+
msgid "GrafanaIntegration|API Token"
msgstr "API令牌"
@@ -10750,9 +11016,6 @@ msgstr "群组%{group_name}已安排删除。"
msgid "Group %{group_name} was successfully created."
msgstr "群组 %{group_name} å·²æˆåŠŸåˆ›å»ºã€‚"
-msgid "Group '%{group_name}' is being imported."
-msgstr ""
-
msgid "Group Audit Events"
msgstr ""
@@ -10789,9 +11052,6 @@ msgstr "群组头åƒ"
msgid "Group by:"
msgstr ""
-msgid "Group could not be imported: %{errors}"
-msgstr ""
-
msgid "Group description"
msgstr "群组æè¿°"
@@ -10819,9 +11079,15 @@ msgstr "群组已标记为将被删除"
msgid "Group has not been marked for deletion"
msgstr "群组未标记为将被删除"
+msgid "Group import could not be scheduled"
+msgstr ""
+
msgid "Group info:"
msgstr "群组信æ¯"
+msgid "Group is required when cluster_type is :group"
+msgstr ""
+
msgid "Group maintainers can register group runners in the %{link}"
msgstr "群组维护者å¯ä»¥åœ¨é€šè¿‡ %{link} 注册群组级 Runner"
@@ -10885,6 +11151,24 @@ msgstr ""
msgid "GroupActivyMetrics|Recent activity (last 90 days)"
msgstr ""
+msgid "GroupImport|Failed to import group."
+msgstr ""
+
+msgid "GroupImport|Group '%{group_name}' is being imported."
+msgstr ""
+
+msgid "GroupImport|Group could not be imported: %{errors}"
+msgstr ""
+
+msgid "GroupImport|Please wait while we import the group for you. Refresh at will."
+msgstr ""
+
+msgid "GroupImport|The group was successfully imported."
+msgstr ""
+
+msgid "GroupImport|Unable to process group import file"
+msgstr ""
+
msgid "GroupRoadmap|%{dateWord} – No end date"
msgstr ""
@@ -11152,6 +11436,9 @@ msgstr "群组(%{count})"
msgid "Groups (%{groups})"
msgstr ""
+msgid "Groups and projects"
+msgstr ""
+
msgid "Groups can also be nested by creating %{subgroup_docs_link_start}subgroups%{subgroup_docs_link_end}."
msgstr "也å¯ä»¥é€šè¿‡åˆ›å»º %{subgroup_docs_link_start}å­ç¾¤ç»„æ¥åµŒå¥—群组%{subgroup_docs_link_end}。"
@@ -11197,6 +11484,33 @@ msgstr "找ä¸åˆ°ç¾¤ç»„"
msgid "GroupsEmptyState|You can manage your group member’s permissions and access to each project in the group."
msgstr "您å¯ä»¥ç®¡ç†ç¾¤ç»„æˆå‘˜çš„æƒé™å¹¶è®¿é—®ç¾¤ç»„中的æ¯ä¸ªé¡¹ç›®ã€‚"
+msgid "GroupsNew|Contact an administrator to enable options for importing your group."
+msgstr ""
+
+msgid "GroupsNew|Create"
+msgstr ""
+
+msgid "GroupsNew|Create group"
+msgstr ""
+
+msgid "GroupsNew|GitLab group export"
+msgstr ""
+
+msgid "GroupsNew|Import"
+msgstr ""
+
+msgid "GroupsNew|Import group"
+msgstr ""
+
+msgid "GroupsNew|My Awesome Group"
+msgstr ""
+
+msgid "GroupsNew|No import options available"
+msgstr ""
+
+msgid "GroupsNew|To copy a GitLab group between installations, navigate to the group settings page for the original installation, generate an export file, and upload it here."
+msgstr ""
+
msgid "GroupsTree|Are you sure you want to leave the \"%{fullName}\" group?"
msgstr "您确定è¦é€€å‡ºç¾¤ç»„“%{fullName}â€å—?"
@@ -11299,6 +11613,9 @@ msgstr "有助于å‡å°‘警报数é‡ï¼ˆä¾‹å¦‚,如果创建太多议题)"
msgid "Helps reduce request volume for protected paths"
msgstr "有助于帮助å‡å°‘å—ä¿æŠ¤è·¯å¾„的请求é‡"
+msgid "Here are all your projects in your group, including the one you just created. To start, let’s take a look at your personalized learning project which will help you learn about GitLab at your own pace."
+msgstr ""
+
msgid "Here you will find recent merge request activity"
msgstr ""
@@ -11312,6 +11629,9 @@ msgid "Hide chart"
msgid_plural "Hide charts"
msgstr[0] ""
+msgid "Hide details"
+msgstr ""
+
msgid "Hide file browser"
msgstr "éšè—文件æµè§ˆå™¨"
@@ -11321,6 +11641,9 @@ msgstr "éšè—群组项目"
msgid "Hide host keys manual input"
msgstr "éšè—主机密钥手动输入"
+msgid "Hide list"
+msgstr ""
+
msgid "Hide marketing-related entries from help"
msgstr "在帮助中éšè—与市场推广有关的æ¡ç›®"
@@ -11340,9 +11663,6 @@ msgstr[0] "éšè—值"
msgid "Hide values"
msgstr "éšè—值"
-msgid "Hiding all labels"
-msgstr "éšè—所有标记"
-
msgid "High or unknown vulnerabilities present"
msgstr "存在高å±æˆ–未知æ¼æ´ž"
@@ -11427,6 +11747,9 @@ msgstr "ID"
msgid "ID:"
msgstr "ID:"
+msgid "IDE"
+msgstr ""
+
msgid "IDE|Allow live previews of JavaScript projects in the Web IDE using CodeSandbox Live Preview."
msgstr ""
@@ -11517,12 +11840,12 @@ msgstr "如果ç¦ç”¨ï¼Œåˆ™è®¿é—®çº§åˆ«å°†å–决于用户在项目中的æƒé™ã€‚
msgid "If enabled"
msgstr "如果已å¯ç”¨"
+msgid "If enabled, GitLab will handle Object Storage replication using Geo. %{linkStart}More information%{linkEnd}"
+msgstr ""
+
msgid "If enabled, access to projects will be validated on an external service using their classification label."
msgstr "如果å¯ç”¨ï¼Œåˆ™ä½¿ç”¨å¤–部æœåŠ¡ä¸Šçš„分类标签æ¥éªŒè¯å¯¹é¡¹ç›®çš„访问æƒé™ã€‚"
-msgid "If enabled, and if object storage is enabled, GitLab will handle Object Storage replication using Geo"
-msgstr ""
-
msgid "If there is no previous license or if the previous license has expired, some GitLab functionality will be blocked until a new, valid license is uploaded."
msgstr ""
@@ -11881,6 +12204,9 @@ msgid "Instance"
msgid_plural "Instances"
msgstr[0] "实例"
+msgid "Instance Configuration"
+msgstr ""
+
msgid "Instance Statistics visibility"
msgstr "实例统计信æ¯å¯è§æ€§"
@@ -11902,9 +12228,6 @@ msgstr ""
msgid "Integrations"
msgstr "集æˆ"
-msgid "Integrations allow you to integrate GitLab with other applications"
-msgstr ""
-
msgid "Integrations|All details"
msgstr ""
@@ -12016,6 +12339,9 @@ msgstr ""
msgid "Invalid query"
msgstr "无效的查询"
+msgid "Invalid repository bundle for snippet with id %{snippet_id}"
+msgstr ""
+
msgid "Invalid repository path"
msgstr "无效的仓库路径"
@@ -12034,6 +12360,9 @@ msgstr ""
msgid "Invalid two-factor code."
msgstr "无效的åŒé‡è®¤è¯ç ã€‚"
+msgid "Invalid yaml"
+msgstr ""
+
msgid "Invitation"
msgstr "邀请"
@@ -12253,15 +12582,24 @@ msgstr "它必须有标题行和至少有两列:第一æ æ˜¯è®®é¢˜æ ‡é¢˜ï¼Œç¬¬
msgid "It seems like the Dependency Scanning job ran successfully, but no dependencies have been detected in your project."
msgstr ""
+msgid "It seems that there is currently no available data for code coverage"
+msgstr ""
+
msgid "It's you"
msgstr "你自己"
+msgid "Iteration"
+msgstr ""
+
msgid "Iteration changed to"
msgstr ""
msgid "Iteration removed"
msgstr ""
+msgid "Iteration updated"
+msgstr ""
+
msgid "Iterations"
msgstr ""
@@ -12292,12 +12630,18 @@ msgstr "1月"
msgid "January"
msgstr "1月"
+msgid "Jira Issues"
+msgstr ""
+
msgid "Jira import is already running."
msgstr ""
msgid "Jira integration not configured."
msgstr ""
+msgid "Jira project key is not configured"
+msgstr ""
+
msgid "Jira project: %{importProject}"
msgstr ""
@@ -12478,6 +12822,9 @@ msgstr ""
msgid "Keep divergent refs"
msgstr ""
+msgid "Kerberos access denied"
+msgstr ""
+
msgid "Key"
msgstr "é”®"
@@ -12487,12 +12834,15 @@ msgstr "秘钥 (PEM)"
msgid "Key: %{key}"
msgstr "密钥: %{key}"
-msgid "Keyboard Shortcuts"
-msgstr "键盘快æ·é”®"
-
msgid "Keyboard shortcuts"
msgstr ""
+msgid "Keys"
+msgstr ""
+
+msgid "Ki"
+msgstr ""
+
msgid "Kubernetes"
msgstr "Kubernetes"
@@ -12532,6 +12882,9 @@ msgstr "Kubernetes弹出信æ¯"
msgid "LDAP"
msgstr "LDAP"
+msgid "LDAP Synchronization"
+msgstr ""
+
msgid "LDAP settings"
msgstr "LDAP 设置"
@@ -12755,12 +13108,18 @@ msgstr "了解有关核准的更多信æ¯ã€‚"
msgid "Learn more about custom project templates"
msgstr "了解更多关于自定义项目模æ¿"
+msgid "Learn more about deploying to AWS"
+msgstr ""
+
msgid "Learn more about deploying to a cluster"
msgstr "了解有关部署到群集的详细信æ¯"
msgid "Learn more about group-level project templates"
msgstr "了解更多关于群组级项目模æ¿"
+msgid "Learn more about job dependencies"
+msgstr ""
+
msgid "Learn more about signing commits"
msgstr "了解更多有关签åæ交的详细信æ¯"
@@ -12812,9 +13171,21 @@ msgstr "许å¯è¯åˆè§„"
msgid "License History"
msgstr ""
+msgid "License ID:"
+msgstr ""
+
+msgid "License URL"
+msgstr ""
+
msgid "License-Check"
msgstr "许å¯è¯æ£€æŸ¥"
+msgid "LicenseCompliance|%{docLinkStart}License Approvals%{docLinkEnd} are active"
+msgstr ""
+
+msgid "LicenseCompliance|%{docLinkStart}License Approvals%{docLinkEnd} are inactive"
+msgstr ""
+
msgid "LicenseCompliance|Add a license"
msgstr "添加许å¯è¯"
@@ -12836,9 +13207,15 @@ msgstr ""
msgid "LicenseCompliance|Deny"
msgstr ""
+msgid "LicenseCompliance|Learn more about %{linkStart}License Approvals%{linkEnd}"
+msgstr ""
+
msgid "LicenseCompliance|License"
msgstr "许å¯è¯"
+msgid "LicenseCompliance|License Approvals"
+msgstr ""
+
msgid "LicenseCompliance|License Compliance detected %d license and policy violation for the source branch only; approval required"
msgid_plural "LicenseCompliance|License Compliance detected %d licenses and policy violations for the source branch only; approval required"
msgstr[0] ""
@@ -13019,7 +13396,7 @@ msgid "Linked emails (%{email_count})"
msgstr "链接的电å­é‚®ä»¶ (%{email_count})"
msgid "Linked issues"
-msgstr "å…³è”议题"
+msgstr "相关议题"
msgid "LinkedIn"
msgstr "领英(LinkedIn)"
@@ -13054,6 +13431,9 @@ msgstr "列表视图"
msgid "List your Bitbucket Server repositories"
msgstr "列出您的 Bitbucket 库"
+msgid "Lists"
+msgstr ""
+
msgid "Live preview"
msgstr "实时预览"
@@ -13237,6 +13617,9 @@ msgstr ""
msgid "Manage project labels"
msgstr "管ç†é¡¹ç›®æ ‡è®°"
+msgid "Manage storage usage"
+msgstr ""
+
msgid "Manage two-factor authentication"
msgstr "管ç†åŒé‡è®¤è¯"
@@ -13252,6 +13635,9 @@ msgstr "Manifest"
msgid "Manifest file import"
msgstr "Manifest文件导入"
+msgid "Manifest import"
+msgstr ""
+
msgid "Manual job"
msgstr "手动作业"
@@ -13399,6 +13785,12 @@ msgstr ""
msgid "Maximum field length"
msgstr ""
+msgid "Maximum file size is 2MB. Please select a smaller file."
+msgstr ""
+
+msgid "Maximum import size (MB)"
+msgstr ""
+
msgid "Maximum job timeout"
msgstr "最大作业超时"
@@ -13438,6 +13830,9 @@ msgstr ""
msgid "Maximum size of Elasticsearch bulk indexing requests."
msgstr ""
+msgid "Maximum size of import files."
+msgstr ""
+
msgid "Maximum size of individual attachments in comments."
msgstr ""
@@ -13489,6 +13884,9 @@ msgstr "åˆå¹¶"
msgid "Merge (when the pipeline succeeds)"
msgstr "åˆå¹¶(当æµæ°´çº¿æˆåŠŸæ—¶)"
+msgid "Merge Conflicts"
+msgstr ""
+
msgid "Merge Request"
msgstr "åˆå¹¶è¯·æ±‚"
@@ -13607,7 +14005,7 @@ msgid "MergeRequests|Reply..."
msgstr "回å¤..."
msgid "MergeRequests|Resolve this thread in a new issue"
-msgstr "创建新议题以解决此è¯é¢˜"
+msgstr "创建新议题以解决此主题"
msgid "MergeRequests|Saving the comment failed"
msgstr "ä¿å­˜è¯„论失败"
@@ -13615,17 +14013,20 @@ msgstr "ä¿å­˜è¯„论失败"
msgid "MergeRequests|Squash task canceled: another squash is already in progress."
msgstr "压缩(Squash)任务已å–消:å¦ä¸€ä¸ªåŽ‹ç¼©å·²åœ¨è¿›è¡Œä¸­ã€‚"
+msgid "MergeRequests|This project does not allow squashing commits when merge requests are accepted."
+msgstr ""
+
msgid "MergeRequests|Thread stays resolved"
-msgstr "è¯é¢˜ä¿æŒå·²è§£å†³çŠ¶æ€"
+msgstr "主题ä¿æŒå·²è§£å†³çŠ¶æ€"
msgid "MergeRequests|Thread stays unresolved"
-msgstr "è¯é¢˜ä¿æŒæœªè§£å†³çŠ¶æ€"
+msgstr "主题ä¿æŒæœªè§£å†³çŠ¶æ€"
msgid "MergeRequests|Thread will be resolved"
-msgstr "è¯é¢˜å°†ç½®ä¸ºè§£å†³çŠ¶æ€"
+msgstr "主题将置为解决状æ€"
msgid "MergeRequests|Thread will be unresolved"
-msgstr "è¯é¢˜å°†ç½®ä¸ºæœªè§£å†³çŠ¶æ€"
+msgstr "主题将置为未解决状æ€"
msgid "MergeRequests|Toggle comments for this file"
msgstr "开关此文件的讨论"
@@ -13640,19 +14041,19 @@ msgid "MergeRequests|commented on commit %{commitLink}"
msgstr "讨论%{commitLink}æ交"
msgid "MergeRequests|started a thread"
-msgstr "å¼€å¯æ–°è¯é¢˜"
+msgstr "å¼€å¯æ–°ä¸»é¢˜"
msgid "MergeRequests|started a thread on %{linkStart}an old version of the diff%{linkEnd}"
-msgstr "å¼€å¯æœ‰å…³%{linkStart}旧版本差异%{linkEnd}çš„è¯é¢˜"
+msgstr "å¼€å¯æœ‰å…³%{linkStart}旧版本差异%{linkEnd}的主题"
msgid "MergeRequests|started a thread on %{linkStart}the diff%{linkEnd}"
-msgstr "å¼€å¯æœ‰å…³%{linkStart}差异%{linkEnd}çš„è¯é¢˜"
+msgstr "å¼€å¯æœ‰å…³%{linkStart}差异%{linkEnd}的主题"
msgid "MergeRequests|started a thread on an outdated change in commit %{linkStart}%{commitDisplay}%{linkEnd}"
-msgstr "å¼€å¯æœ‰å…³%{linkStart}%{commitDisplay}%{linkEnd}中较旧的å˜æ›´çš„è¯é¢˜"
+msgstr "å¼€å¯æœ‰å…³%{linkStart}%{commitDisplay}%{linkEnd}中较旧的å˜æ›´çš„主题"
msgid "MergeRequests|started a thread on commit %{linkStart}%{commitDisplay}%{linkEnd}"
-msgstr "å¼€å¯æœ‰å…³æ交%{linkStart}%{commitDisplay}%{linkEnd}çš„è¯é¢˜"
+msgstr "å¼€å¯æœ‰å…³æ交%{linkStart}%{commitDisplay}%{linkEnd}的主题"
msgid "MergeRequest|Compare %{target} and %{source}"
msgstr ""
@@ -13768,6 +14169,12 @@ msgstr ""
msgid "MetricsSettings|Add a button to the metrics dashboard linking directly to your existing external dashboard."
msgstr ""
+msgid "MetricsSettings|Choose whether to display dashboard metrics in UTC or the user's local timezone."
+msgstr ""
+
+msgid "MetricsSettings|Dashboard timezone"
+msgstr ""
+
msgid "MetricsSettings|External dashboard URL"
msgstr ""
@@ -13777,6 +14184,12 @@ msgstr ""
msgid "MetricsSettings|Metrics Dashboard"
msgstr ""
+msgid "MetricsSettings|UTC (Coordinated Universal Time)"
+msgstr ""
+
+msgid "MetricsSettings|User's local timezone"
+msgstr ""
+
msgid "Metrics|Add metric"
msgstr "添加指标"
@@ -13792,6 +14205,9 @@ msgstr "创建自定义仪表æ¿%{fileName}"
msgid "Metrics|Create metric"
msgstr "创建指标"
+msgid "Metrics|Current"
+msgstr ""
+
msgid "Metrics|Delete metric"
msgstr "删除指标"
@@ -13841,6 +14257,9 @@ msgstr ""
msgid "Metrics|Max"
msgstr "最大值"
+msgid "Metrics|Min"
+msgstr ""
+
msgid "Metrics|Must be a valid PromQL query."
msgstr "必须是有效的 PromQL 查询。"
@@ -13856,6 +14275,9 @@ msgstr "Prometheus查询文档"
msgid "Metrics|Refresh dashboard"
msgstr ""
+msgid "Metrics|Select a value"
+msgstr ""
+
msgid "Metrics|Star dashboard"
msgstr ""
@@ -13874,12 +14296,18 @@ msgstr "获å–环境数æ®æ—¶å‡ºé”™ï¼Œè¯·é‡è¯•"
msgid "Metrics|There was an error getting annotations information."
msgstr ""
+msgid "Metrics|There was an error getting dashboard validation warnings information."
+msgstr ""
+
msgid "Metrics|There was an error getting deployment information."
msgstr "获å–部署信æ¯æ—¶å‡ºé”™ã€‚"
msgid "Metrics|There was an error getting environments information."
msgstr "获å–环境信æ¯æ—¶å‡ºé”™ã€‚"
+msgid "Metrics|There was an error getting options for variable \"%{name}\"."
+msgstr ""
+
msgid "Metrics|There was an error trying to validate your query"
msgstr "å°è¯•éªŒè¯æ‚¨çš„查询时出错"
@@ -13922,6 +14350,9 @@ msgstr "您å¯ä»¥å°†æ­¤ä»ªè¡¨æ¿çš„副本ä¿å­˜åˆ°ä»“库以便自定义。请选
msgid "Metrics|You're about to permanently delete this metric. This cannot be undone."
msgstr "您å³å°†æ°¸ä¹…删除此指标且无法撤消。"
+msgid "Metrics|Your dashboard schema is invalid. Edit the dashboard to correct the YAML schema."
+msgstr ""
+
msgid "Metrics|e.g. HTTP requests"
msgstr "例如:HTTP 请求"
@@ -13937,12 +14368,18 @@ msgstr "例如:速率(http_requests_total[5m])"
msgid "Metrics|e.g. req/sec"
msgstr "例如:req / sec"
+msgid "Mi"
+msgstr ""
+
msgid "Microsoft Azure"
msgstr "Microsoft Azure"
msgid "Middleman project with Static Site Editor support"
msgstr ""
+msgid "Migrate your data from an external source like GitHub, Bitbucket, or another instance of GitLab."
+msgstr ""
+
msgid "Migrated %{success_count}/%{total_count} files."
msgstr "å·²è¿ç§» %{success_count}/%{total_count} 文件。"
@@ -14076,6 +14513,9 @@ msgstr ""
msgid "Minimum capacity to be available before we schedule more mirrors preemptively."
msgstr "在我们预先安排更多镜åƒä¹‹å‰å¯ç”¨çš„最å°å®¹é‡ã€‚"
+msgid "Minimum interval in days"
+msgstr ""
+
msgid "Minimum length is %{minimum_password_length} characters"
msgstr "最å°é•¿åº¦ä¸º%{minimum_password_length}个字符"
@@ -14262,6 +14702,12 @@ msgstr "找到多个模型类型: %{model_types}"
msgid "Multiple uploaders found: %{uploader_types}"
msgstr "找到多个上传器: %{uploader_types}"
+msgid "Must match with the %{codeStart}external_url%{codeEnd} in %{codeStart}/etc/gitlab/gitlab.rb%{codeEnd}."
+msgstr ""
+
+msgid "Must match with the %{codeStart}geo_node_name%{codeEnd} in %{codeStart}/etc/gitlab/gitlab.rb%{codeEnd}. %{linkStart}More information%{linkEnd}"
+msgstr ""
+
msgid "My Awesome Group"
msgstr ""
@@ -14371,16 +14817,22 @@ msgid "Never"
msgstr "从ä¸"
msgid "New"
-msgstr "新增"
+msgstr "新建"
msgid "New Application"
msgstr "新建应用"
+msgid "New Branch"
+msgstr ""
+
+msgid "New Deploy Key"
+msgstr ""
+
msgid "New Environment"
msgstr "新环境"
-msgid "New Geo Node"
-msgstr "新建Geo节点"
+msgid "New File"
+msgstr ""
msgid "New Group"
msgstr "新建群组"
@@ -14395,15 +14847,15 @@ msgid "New Issue"
msgid_plural "New Issues"
msgstr[0] "新建议题"
-msgid "New Iteration"
-msgstr ""
-
msgid "New Jira import"
msgstr ""
msgid "New Label"
msgstr "新标签"
+msgid "New Merge Request"
+msgstr ""
+
msgid "New Milestone"
msgstr "新里程碑"
@@ -14422,6 +14874,9 @@ msgstr "新建项目"
msgid "New Snippet"
msgstr "新建代ç ç‰‡æ®µ"
+msgid "New User"
+msgstr ""
+
msgid "New branch"
msgstr "新建分支"
@@ -14464,6 +14919,9 @@ msgstr "新建议题"
msgid "New issue title"
msgstr "新议题标题"
+msgid "New iteration"
+msgstr ""
+
msgid "New iteration created"
msgstr ""
@@ -14641,6 +15099,12 @@ msgstr "没有您å¯ç”¨çš„派生。"
msgid "No grouping"
msgstr ""
+msgid "No iteration"
+msgstr ""
+
+msgid "No iterations to show"
+msgstr ""
+
msgid "No job log"
msgstr "没有作业日志"
@@ -14683,6 +15147,9 @@ msgstr "没有è¦æ˜¾ç¤ºçš„里程碑"
msgid "No other labels with such name or description"
msgstr "没有其他具有此类å称或æ述的标记"
+msgid "No panels matching properties %{opts}"
+msgstr ""
+
msgid "No parent group"
msgstr "父群组ä¸å­˜åœ¨"
@@ -14716,6 +15183,9 @@ msgstr "未找到Runner"
msgid "No schedules"
msgstr "无计划"
+msgid "No stack trace for this error"
+msgstr ""
+
msgid "No starrers matched your search"
msgstr "没有符åˆæ‚¨æœç´¢æ¡ä»¶çš„星标用户"
@@ -14728,7 +15198,7 @@ msgstr "没有模æ¿"
msgid "No test coverage"
msgstr ""
-msgid "No thanks, don't show this again"
+msgid "No thanks"
msgstr ""
msgid "No vulnerabilities present"
@@ -14743,9 +15213,6 @@ msgstr ""
msgid "No, directly import the existing email addresses and usernames."
msgstr "å¦, 请直接导入现有电å­é‚®ä»¶åœ°å€å’Œç”¨æˆ·å。"
-msgid "No, not interested right now"
-msgstr "ä¸ï¼Œæš‚æ—¶ä¸æ„Ÿå…´è¶£"
-
msgid "No. of commits"
msgstr ""
@@ -14791,9 +15258,6 @@ msgstr "æ•°æ®ä¸è¶³"
msgid "Not found."
msgstr "未找到。"
-msgid "Not helpful"
-msgstr "没有帮助"
-
msgid "Not now"
msgstr "æš‚ä¸"
@@ -14950,6 +15414,9 @@ msgstr "ç¦ç”¨é€šçŸ¥"
msgid "Notifications on"
msgstr "å¯ç”¨é€šçŸ¥"
+msgid "Notify users by email when sign-in location is not recognized"
+msgstr ""
+
msgid "Nov"
msgstr "11月"
@@ -15016,9 +15483,6 @@ msgstr "过滤"
msgid "Oh no!"
msgstr ""
-msgid "Ok let's go"
-msgstr "好的,我们开始å§"
-
msgid "Oldest first"
msgstr ""
@@ -15031,17 +15495,50 @@ msgstr ""
msgid "On track"
msgstr ""
+msgid "OnDemandScans|Attached branch"
+msgstr ""
+
+msgid "OnDemandScans|Attached branch is where the scan job runs."
+msgstr ""
+
+msgid "OnDemandScans|Could not run the scan. Please try again."
+msgstr ""
+
msgid "OnDemandScans|Create new DAST scan"
msgstr ""
+msgid "OnDemandScans|DAST will scan the target URL and any discovered sub URLs."
+msgstr ""
+
+msgid "OnDemandScans|New on-demand DAST scan"
+msgstr ""
+
msgid "OnDemandScans|On-demand Scans"
msgstr ""
+msgid "OnDemandScans|On-demand scans run outside the DevOps cycle and find vulnerabilities in your projects. %{learnMoreLinkStart}Learn more%{learnMoreLinkEnd}"
+msgstr ""
+
+msgid "OnDemandScans|Only a passive scan can be performed on demand."
+msgstr ""
+
+msgid "OnDemandScans|Passive DAST Scan"
+msgstr ""
+
+msgid "OnDemandScans|Please enter a valid URL format, ex: http://www.example.com/home"
+msgstr ""
+
+msgid "OnDemandScans|Run this scan"
+msgstr ""
+
+msgid "OnDemandScans|Scan mode"
+msgstr ""
+
msgid "OnDemandScans|Schedule or run scans immediately against target sites. Currently available on-demand scan type: DAST. %{helpLinkStart}More information%{helpLinkEnd}"
msgstr ""
-msgid "Onboarding"
-msgstr "新手上路"
+msgid "OnDemandScans|Target URL"
+msgstr ""
msgid "Once imported, repositories can be mirrored over SSH. Read more %{link_start}here%{link_end}."
msgstr "仓库导入åŽï¼Œå¯ä»¥é€šè¿‡SSH进行镜åƒã€‚点击%{link_start}此处%{link_end}了解更多."
@@ -15065,6 +15562,9 @@ msgstr "您无æƒè®¿é—®çš„一个或多个群组。"
msgid "One or more of you personal access tokens were revoked"
msgstr "您的一个或多个个人访问令牌已被å–消"
+msgid "One or more of your %{provider} projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
+msgstr ""
+
msgid "One or more of your Bitbucket projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
msgstr "您的一个或多个Bitbucket项目无法直接导入GitLab,因为它们使用Subversion或Mercurial进行版本控制,而ä¸æ˜¯Git。"
@@ -15566,6 +16066,9 @@ msgstr "父å²è¯—ä¸å­˜åœ¨ã€‚"
msgid "Parent epic is not present."
msgstr "父å²è¯—ä¸å­˜åœ¨ã€‚"
+msgid "Parsing error for param :embed_json. %{message}"
+msgstr ""
+
msgid "Part of merge request changes"
msgstr "包å«äºŽåˆå¹¶è¯·æ±‚å˜æ›´ä¸­"
@@ -15578,6 +16081,9 @@ msgstr "å‚与者"
msgid "Passed"
msgstr "已通过"
+msgid "Passed on"
+msgstr ""
+
msgid "Password"
msgstr "密ç "
@@ -15662,6 +16168,9 @@ msgstr ""
msgid "Perform common operations on GitLab project"
msgstr "在GitLab项目上执行常è§æ“作"
+msgid "Performance and resource management"
+msgstr ""
+
msgid "Performance optimization"
msgstr "性能优化"
@@ -15869,8 +16378,8 @@ msgstr "è¿è¡Œæµæ°´çº¿"
msgid "Pipelines|Something went wrong while cleaning runners cache."
msgstr "清ç†runner缓存时å‘生错误。"
-msgid "Pipelines|There are currently no %{scope} pipelines."
-msgstr "当å‰æ— %{scope}çš„æµæ°´çº¿ã€‚"
+msgid "Pipelines|There are currently no finished pipelines."
+msgstr ""
msgid "Pipelines|There are currently no pipelines."
msgstr "当å‰æ— æµæ°´çº¿ã€‚"
@@ -15980,6 +16489,9 @@ msgstr "åœæ­¢æµæ°´çº¿"
msgid "Pipeline|Stop pipeline #%{pipelineId}?"
msgstr "åœæ­¢æµæ°´çº¿ï¼ƒ%{pipelineId}å—?"
+msgid "Pipeline|Tag name"
+msgstr ""
+
msgid "Pipeline|Trigger author"
msgstr ""
@@ -16061,12 +16573,18 @@ msgstr "请检查é…置文件以确ä¿å®ƒå¯ç”¨ä¸”YAML有效"
msgid "Please check your email (%{email}) to verify that you own this address and unlock the power of CI/CD. Didn't receive it? %{resend_link}. Wrong email address? %{update_link}."
msgstr "请检查您的电å­é‚®ä»¶(%{email})以确认您拥有此电å­é‚®ç®±å¹¶è§£é”CI/CD的强大功能。还没收到邮件? %{resend_link}。或是电å­é‚®ä»¶åœ°å€é”™è¯¯ï¼Ÿ%{update_link}。"
+msgid "Please choose a file"
+msgstr ""
+
msgid "Please choose a group URL with no special characters."
msgstr "请选择无特殊字符的群组URL。"
msgid "Please complete your profile with email address"
msgstr "请在您的个人资料中填写电å­é‚®ä»¶åœ°å€"
+msgid "Please convert %{linkStart}them to Git%{linkEnd}, and go through the %{linkToImportFlow} again."
+msgstr ""
+
msgid "Please convert them to %{link_to_git}, and go through the %{link_to_import_flow} again."
msgstr "请将它们先%{link_to_git}, 然åŽå†æ¬¡ä½¿ç”¨%{link_to_import_flow}。"
@@ -16124,6 +16642,9 @@ msgstr ""
msgid "Please provide a valid email address."
msgstr "请æ供有效的电å­é‚®ä»¶åœ°å€ã€‚"
+msgid "Please provide attributes to update"
+msgstr ""
+
msgid "Please refer to <a href=\"%{docs_url}\">%{docs_url}</a>"
msgstr "请å‚考<a href=\"%{docs_url}\">%{docs_url}</a>"
@@ -16859,6 +17380,9 @@ msgstr ""
msgid "Project has too many %{label_for_message} to search"
msgstr "项目有太多个 %{label_for_message} è¦æœç´¢"
+msgid "Project is required when cluster_type is :project"
+msgstr ""
+
msgid "Project members"
msgstr "项目æˆå‘˜"
@@ -16976,8 +17500,41 @@ msgstr "%{service_title}:状æ€å¼€å¯"
msgid "ProjectService|Comment"
msgstr "评论"
-msgid "ProjectService|Comment will be posted on each event"
-msgstr "评论将å‘布在æ¯ä¸ªäº‹ä»¶ä¸Š"
+msgid "ProjectService|Event will be triggered by a push to the repository"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a commit is created/updated"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a confidential issue is created/updated/closed"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a deployment finishes"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a merge request is created/updated/merged"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a new tag is pushed to the repository"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a new, unique alert is recorded"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a pipeline status changes"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a wiki page is created/updated"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when an issue is created/updated/closed"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when someone adds a comment"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when someone adds a comment on a confidential issue"
+msgstr ""
msgid "ProjectService|Perform common operations on GitLab project: %{project_name}"
msgstr "在GitLab项目上执行常è§æ“作: %{project_name}"
@@ -17027,6 +17584,9 @@ msgstr "ç¦ç”¨ç”µå­é‚®ä»¶é€šçŸ¥"
msgid "ProjectSettings|Enable 'Delete source branch' option by default"
msgstr "默认å¯ç”¨\"删除æºåˆ†æ”¯\"选项"
+msgid "ProjectSettings|Enable merge trains and pipelines for merged results"
+msgstr ""
+
msgid "ProjectSettings|Every merge creates a merge commit"
msgstr "æ¯æ¬¡åˆå¹¶éƒ½ä¼šåˆ›å»ºåˆå¹¶æ交"
@@ -17057,8 +17617,8 @@ msgstr "ä»…å¿«è¿›å¼(Fast-forward)åˆå¹¶"
msgid "ProjectSettings|Forks"
msgstr "派生"
-msgid "ProjectSettings|Git Large File Storage"
-msgstr "Git 大文件存储"
+msgid "ProjectSettings|Git Large File Storage (LFS)"
+msgstr ""
msgid "ProjectSettings|Internal"
msgstr "内部"
@@ -17093,9 +17653,6 @@ msgstr "åˆå¹¶æ–¹æ³•"
msgid "ProjectSettings|Merge options"
msgstr "åˆå¹¶é€‰é¡¹"
-msgid "ProjectSettings|Merge pipelines will try to validate the post-merge result prior to merging"
-msgstr "åˆå¹¶æµæ°´çº¿å°†å°è¯•åœ¨è¿›è¡Œå®žé™…åˆå¹¶ä¹‹å‰éªŒè¯åˆå¹¶ç»“æžœ"
-
msgid "ProjectSettings|Merge requests"
msgstr "åˆå¹¶è¯·æ±‚"
@@ -17123,6 +17680,9 @@ msgstr "用于项目文档的Pages"
msgid "ProjectSettings|Pipelines"
msgstr "æµæ°´çº¿"
+msgid "ProjectSettings|Pipelines for merge requests must be enabled in the CI/CD configuration file, or pipelines could be unresolvable or dropped"
+msgstr ""
+
msgid "ProjectSettings|Pipelines must succeed"
msgstr "æµæ°´çº¿å¿…é¡»æˆåŠŸ"
@@ -17150,6 +17710,9 @@ msgstr ""
msgid "ProjectSettings|Show link to create/view merge request when pushing from the command line"
msgstr "从命令行推é€æ—¶æ˜¾ç¤ºåˆ›å»º/查看åˆå¹¶è¯·æ±‚的链接"
+msgid "ProjectSettings|Skipped pipelines are considered successful"
+msgstr ""
+
msgid "ProjectSettings|Snippets"
msgstr "代ç ç‰‡æ®µ"
@@ -17165,6 +17728,9 @@ msgstr "GitLab支æŒçš„å˜é‡ä¸ºï¼š"
msgid "ProjectSettings|These checks must pass before merge requests can be merged"
msgstr "åˆå¹¶è¯·æ±‚在通过这些检查åŽæ‰å¯è¢«åˆå¹¶"
+msgid "ProjectSettings|This introduces the risk of merging changes that will not pass the pipeline."
+msgstr ""
+
msgid "ProjectSettings|This setting is applied on the server level and can be overridden by an admin."
msgstr "此设置已应用于æœåŠ¡å™¨çº§åˆ«ï¼Œå¯ç”±ç®¡ç†å‘˜ä¿®æ”¹ã€‚"
@@ -17342,15 +17908,27 @@ msgstr "空白"
msgid "ProjectsNew|Blank project"
msgstr "空白项目"
+msgid "ProjectsNew|Connect your external repository to GitLab CI/CD."
+msgstr ""
+
msgid "ProjectsNew|Contact an administrator to enable options for importing your project."
msgstr "è”系管ç†å‘˜å¯ç”¨å¯¼å…¥é¡¹ç›®çš„选项。"
msgid "ProjectsNew|Create"
msgstr ""
+msgid "ProjectsNew|Create a blank project to house your files, plan your work, and collaborate on code, among other things."
+msgstr ""
+
+msgid "ProjectsNew|Create blank project"
+msgstr ""
+
msgid "ProjectsNew|Create from template"
msgstr "从模æ¿åˆ›å»º"
+msgid "ProjectsNew|Create new project"
+msgstr ""
+
msgid "ProjectsNew|Creating project & repository."
msgstr "创建项目和仓库。"
@@ -17375,6 +17953,9 @@ msgstr "请ç¨ç­‰ï¼Œæ­¤é¡µé¢ä¼šåœ¨å‡†å¤‡å¥½åŽè‡ªåŠ¨åˆ·æ–°ã€‚"
msgid "ProjectsNew|Project description %{tag_start}(optional)%{tag_end}"
msgstr "项目æè¿°%{tag_start}(å¯é€‰)%{tag_end}"
+msgid "ProjectsNew|Run CI/CD for external repository"
+msgstr ""
+
msgid "ProjectsNew|Template"
msgstr "模æ¿"
@@ -17388,7 +17969,7 @@ msgid "Prometheus"
msgstr ""
msgid "PrometheusAlerts|%{count} alerts applied"
-msgstr "%{count}项警告已应用"
+msgstr "%{count}项警报已应用"
msgid "PrometheusAlerts|%{firingCount} firing"
msgstr ""
@@ -17636,6 +18217,9 @@ msgstr "å—ä¿æŠ¤"
msgid "Protected Branch"
msgstr "ä¿æŠ¤åˆ†æ”¯"
+msgid "Protected Branches"
+msgstr ""
+
msgid "Protected Environment"
msgstr "å—ä¿æŠ¤çš„环境"
@@ -17648,6 +18232,9 @@ msgstr "å—ä¿æŠ¤çš„路径"
msgid "Protected Tag"
msgstr "å—ä¿æŠ¤çš„标签"
+msgid "Protected Tags"
+msgstr ""
+
msgid "Protected branches"
msgstr "å—ä¿æŠ¤çš„分支"
@@ -17747,6 +18334,9 @@ msgstr ""
msgid "Provider"
msgstr "æ供者"
+msgid "Proxy support for this API is not available currently"
+msgstr ""
+
msgid "Pseudonymizer data collection"
msgstr "匿å化数æ®æ”¶é›†"
@@ -17771,6 +18361,9 @@ msgstr ""
msgid "Publish to status page"
msgstr ""
+msgid "Published on status page"
+msgstr ""
+
msgid "Publishes this issue to the associated status page."
msgstr ""
@@ -17945,6 +18538,9 @@ msgstr "最近的æœç´¢"
msgid "Recipe"
msgstr "Recipe"
+msgid "Reconfigure"
+msgstr ""
+
msgid "Recover hidden stage"
msgstr ""
@@ -18030,6 +18626,9 @@ msgstr ""
msgid "Registration|Your profile"
msgstr ""
+msgid "Regulate approvals by authors/committers, based on compliance frameworks. Can be changed only at the instance level."
+msgstr ""
+
msgid "Rejected (closed)"
msgstr ""
@@ -18079,15 +18678,24 @@ msgstr "å‘布标题"
msgid "ReleaseAssetLinkType|Image"
msgstr ""
+msgid "ReleaseAssetLinkType|Images"
+msgstr ""
+
msgid "ReleaseAssetLinkType|Other"
msgstr ""
msgid "ReleaseAssetLinkType|Package"
msgstr ""
+msgid "ReleaseAssetLinkType|Packages"
+msgstr ""
+
msgid "ReleaseAssetLinkType|Runbook"
msgstr ""
+msgid "ReleaseAssetLinkType|Runbooks"
+msgstr ""
+
msgid "Releases"
msgstr "å‘布"
@@ -18100,6 +18708,9 @@ msgstr ""
msgid "Releases documentation"
msgstr ""
+msgid "Releases|New Release"
+msgstr ""
+
msgid "Release|Something went wrong while getting the release details"
msgstr "获å–å‘布详细信æ¯æ—¶å‡ºé”™"
@@ -18172,6 +18783,9 @@ msgstr "删除截止日期"
msgid "Remove fork relationship"
msgstr "删除派生关系"
+msgid "Remove from batch"
+msgstr ""
+
msgid "Remove from board"
msgstr "从看æ¿ç§»é™¤"
@@ -18361,6 +18975,12 @@ msgstr "替æ¢å…‹éš†URL根地å€ã€‚"
msgid "Replication"
msgstr ""
+msgid "Replication enabled"
+msgstr ""
+
+msgid "Replication paused"
+msgstr ""
+
msgid "Reply by email"
msgstr "通过电å­é‚®ä»¶å›žå¤"
@@ -18388,15 +19008,15 @@ msgstr "å‘管ç†å‘˜æŠ¥å‘Šæ»¥ç”¨è¡Œä¸º"
msgid "Reported %{timeAgo} by %{reportedBy}"
msgstr "由%{reportedBy}报告于%{timeAgo}"
-msgid "Reported Resource Changes: %{addNum} to add, %{changeNum} to change, %{deleteNum} to delete"
-msgstr ""
-
msgid "Reporter"
msgstr ""
msgid "Reporting"
msgstr "报告"
+msgid "Reports"
+msgstr ""
+
msgid "Reports|%{combinedString} and %{resolvedString}"
msgstr ""
@@ -18434,6 +19054,9 @@ msgstr "执行时间"
msgid "Reports|Failure"
msgstr "失败"
+msgid "Reports|Identifier"
+msgstr ""
+
msgid "Reports|Metrics reports are loading"
msgstr "正在加载指标报告"
@@ -18446,6 +19069,9 @@ msgstr "指标报告无å˜åŒ–"
msgid "Reports|Metrics reports failed loading results"
msgstr "指标报告加载结果失败"
+msgid "Reports|Scanner"
+msgstr ""
+
msgid "Reports|Severity"
msgstr "严é‡çº§åˆ«"
@@ -18494,15 +19120,33 @@ msgstr "仓库清ç†"
msgid "Repository cleanup has started. You will receive an email once the cleanup operation is complete."
msgstr "仓库清ç†å·²ç»å¼€å§‹ã€‚一旦清ç†å®Œæˆï¼Œæ‚¨å°†æ”¶åˆ°ä¸€å°ç”µå­é‚®ä»¶ã€‚"
+msgid "Repository files count over the limit"
+msgstr ""
+
+msgid "Repository has an invalid default branch name."
+msgstr ""
+
+msgid "Repository has more than one branch."
+msgstr ""
+
msgid "Repository has no locks."
msgstr "当å‰ä»“库无加é”文件。"
+msgid "Repository has tags."
+msgstr ""
+
msgid "Repository maintenance"
msgstr "仓库维护"
msgid "Repository mirroring"
msgstr "仓库镜åƒ"
+msgid "Repository must contain at least 1 file."
+msgstr ""
+
+msgid "Repository size is above the limit."
+msgstr ""
+
msgid "Repository static objects"
msgstr "仓库é™æ€å¯¹è±¡"
@@ -18512,8 +19156,8 @@ msgstr "仓库存储"
msgid "Repository sync capacity"
msgstr ""
-msgid "Repository: %{counter_repositories} / Wikis: %{counter_wikis} / Build Artifacts: %{counter_build_artifacts} / LFS: %{counter_lfs_objects}"
-msgstr "仓库: %{counter_repositories} /Wiki: %{counter_wikis} /构建 产物: %{counter_build_artifacts} /LFS: %{counter_lfs_objects}"
+msgid "Repository: %{counter_repositories} / Wikis: %{counter_wikis} / Build Artifacts: %{counter_build_artifacts} / LFS: %{counter_lfs_objects} / Snippets: %{counter_snippets}"
+msgstr ""
msgid "RepositorySettingsAccessLevel|Select"
msgstr "选择"
@@ -18524,6 +19168,9 @@ msgstr "申请æƒé™"
msgid "Request Headers"
msgstr ""
+msgid "Request details"
+msgstr ""
+
msgid "Request parameter %{param} is missing."
msgstr "请求å‚æ•°%{param}缺失。"
@@ -18628,6 +19275,9 @@ msgstr "é‡ç½® Runner 注册令牌"
msgid "Reset template"
msgstr "é‡ç½®æ¨¡æ¿"
+msgid "Reset to project defaults"
+msgstr ""
+
msgid "Resetting the authorization key for this project will require updating the authorization key in every alert source it is enabled in."
msgstr "é‡ç½®æ­¤é¡¹ç›®çš„授æƒå¯†é’¥å°†éœ€è¦æ›´æ–°æ¯ä¸ªè­¦æŠ¥æºä¸­å¯ç”¨çš„授æƒå¯†é’¥ã€‚"
@@ -18638,13 +19288,13 @@ msgid "Resolve"
msgstr "解决"
msgid "Resolve all threads in new issue"
-msgstr "在新议题中解决所有è¯é¢˜"
+msgstr "在新议题中解决所有主题"
msgid "Resolve conflicts on source branch"
msgstr "在æºåˆ†æ”¯ä¸Šè§£å†³å†²çª"
msgid "Resolve thread"
-msgstr "解决è¯é¢˜"
+msgstr "解决主题"
msgid "Resolved"
msgstr "已解决"
@@ -18724,9 +19374,6 @@ msgstr ""
msgid "Resume"
msgstr "æ¢å¤"
-msgid "Resume replication"
-msgstr "æ¢å¤å¤åˆ¶"
-
msgid "Resync"
msgstr "é‡æ–°åŒæ­¥"
@@ -19056,7 +19703,7 @@ msgid "Search Milestones"
msgstr ""
msgid "Search an environment spec"
-msgstr "æœç´¢çŽ¯å¢ƒè§„范"
+msgstr "æœç´¢çŽ¯å¢ƒè§„则"
msgid "Search authors"
msgstr ""
@@ -19338,6 +19985,9 @@ msgstr ""
msgid "SecurityReports|False positive"
msgstr ""
+msgid "SecurityReports|Group Security Dashboard"
+msgstr ""
+
msgid "SecurityReports|Hide dismissed"
msgstr ""
@@ -19392,10 +20042,13 @@ msgstr ""
msgid "SecurityReports|Remove project from dashboard"
msgstr ""
-msgid "SecurityReports|Report type"
+msgid "SecurityReports|Return to dashboard"
msgstr ""
-msgid "SecurityReports|Return to dashboard"
+msgid "SecurityReports|Scan details"
+msgstr ""
+
+msgid "SecurityReports|Scanner type"
msgstr ""
msgid "SecurityReports|Security Dashboard"
@@ -19452,6 +20105,9 @@ msgstr ""
msgid "SecurityReports|There was an error while generating the report."
msgstr ""
+msgid "SecurityReports|Unable to add %{invalidProjectsMessage}"
+msgstr ""
+
msgid "SecurityReports|Unable to add %{invalidProjects}"
msgstr ""
@@ -19566,6 +20222,9 @@ msgstr "选择分支/标签"
msgid "Select due date"
msgstr ""
+msgid "Select file"
+msgstr ""
+
msgid "Select group or project"
msgstr "选择群组或项目"
@@ -19629,9 +20288,6 @@ msgstr "选择目标分支"
msgid "Select the branch you want to set as the default for this project. All merge requests and commits will automatically be made against this branch unless you specify a different one."
msgstr "选择当å‰é¡¹ç›®çš„默认分支。除éžå¦è¡ŒæŒ‡å®šï¼Œå¦åˆ™æ‰€æœ‰åˆå¹¶è¯·æ±‚å’Œæ交都将指å‘此分支。"
-msgid "Select the configured storage available for new repositories to be placed on."
-msgstr ""
-
msgid "Select the custom project template source group."
msgstr "选择自定义项目模æ¿æºç¾¤ç»„。"
@@ -19824,12 +20480,6 @@ msgstr "æœåŠ¡å°"
msgid "Service Desk is enabled but not yet active"
msgstr "æœåŠ¡å°å·²å¯ç”¨ä½†å°šæœªæ¿€æ´»"
-msgid "Service Desk is off"
-msgstr "æœåŠ¡å°å·²å…³é—­"
-
-msgid "Service Desk is on"
-msgstr "æœåŠ¡å°å·²å¼€å¯"
-
msgid "Service Templates"
msgstr "æœåŠ¡æ¨¡æ¿"
@@ -19917,6 +20567,15 @@ msgstr "设置群组æ¯ä¸ªæœˆå¯åœ¨å…±äº«Runner上使用的最大æµæ°´çº¿åˆ†é’Ÿ
msgid "Set the milestone to %{milestone_reference}."
msgstr "将里程碑设置为%{milestone_reference}。"
+msgid "Set the number of concurrent requests this secondary node will make to the primary node while backfilling."
+msgstr ""
+
+msgid "Set the synchronization and verification capacity for the secondary node."
+msgstr ""
+
+msgid "Set the timeout in seconds to send a secondary node status to the primary and IPs allowed for the secondary nodes."
+msgstr ""
+
msgid "Set time estimate"
msgstr "设置时间估计"
@@ -19956,6 +20615,9 @@ msgstr "设置æƒé‡"
msgid "Set weight to %{weight}."
msgstr "å°†æƒé‡è®¾ç½®ä¸º%{weight}。"
+msgid "Set what should be replicated by choosing specific projects or groups by the secondary node."
+msgstr ""
+
msgid "SetPasswordToCloneLink|set a password"
msgstr "设置密ç "
@@ -20082,6 +20744,9 @@ msgstr "显示æ交æè¿°"
msgid "Show complete raw log"
msgstr "显示完整的原始日志"
+msgid "Show details"
+msgstr ""
+
msgid "Show file browser"
msgstr "显示文件æµè§ˆå™¨"
@@ -20091,10 +20756,13 @@ msgstr ""
msgid "Show latest version"
msgstr "显示最新版本"
+msgid "Show list"
+msgstr ""
+
msgid "Show me everything"
msgstr ""
-msgid "Show me how"
+msgid "Show me how to add a pipeline"
msgstr ""
msgid "Show me more advanced stuff"
@@ -20141,9 +20809,6 @@ msgstr "显示版本#%{versionNumber}"
msgid "Showing all issues"
msgstr "显示所有议题"
-msgid "Showing all labels"
-msgstr "显示所有标记"
-
msgid "Showing last %{size} of log -"
msgstr "显示日志的最åŽ%{size} -"
@@ -20255,9 +20920,6 @@ msgstr "é™æ€ç½‘站大å°è®¾ç½®"
msgid "Skip outdated deployment jobs"
msgstr ""
-msgid "Skip this for now"
-msgstr "暂时跳过"
-
msgid "Skipped"
msgstr "已跳过"
@@ -20402,6 +21064,9 @@ msgstr "点击按钮时出错"
msgid "Something went wrong while adding your award. Please try again."
msgstr "添加赞èµæ—¶å‡ºé”™ã€‚请å†è¯•ä¸€æ¬¡ã€‚"
+msgid "Something went wrong while applying the batch of suggestions. Please try again."
+msgstr ""
+
msgid "Something went wrong while applying the suggestion. Please try again."
msgstr "应用åŒæ„时出了点问题。请å†è¯•ä¸€æ¬¡ã€‚"
@@ -20504,10 +21169,13 @@ msgstr ""
msgid "Something went wrong while updating a requirement."
msgstr ""
+msgid "Something went wrong while updating assignees"
+msgstr ""
+
msgid "Something went wrong while updating your list settings"
msgstr "更新列表设置时出现错误"
-msgid "Something went wrong with your automatic subscription renewal"
+msgid "Something went wrong with your automatic subscription renewal."
msgstr ""
msgid "Something went wrong, unable to add %{project} to dashboard"
@@ -20525,6 +21193,9 @@ msgstr "删除项目时出错"
msgid "Something went wrong, unable to search projects"
msgstr "出错了,无法æœç´¢é¡¹ç›®"
+msgid "Something went wrong."
+msgstr ""
+
msgid "Something went wrong. Please try again."
msgstr "出现错误。请é‡è¯•ã€‚"
@@ -20541,7 +21212,7 @@ msgid "Sorry, you have exceeded the maximum browsable page number. Please use th
msgstr ""
msgid "Sorry, your filter produced no results"
-msgstr "抱歉,您的过滤器没有产生结果"
+msgstr "对ä¸èµ·ï¼Œæ— ç¬¦åˆè¿‡æ»¤å™¨çš„结果"
msgid "Sort by"
msgstr "排åº"
@@ -20708,6 +21379,9 @@ msgstr "æº(分支或标签)"
msgid "Source code"
msgstr "æºä»£ç "
+msgid "Source code (%{fileExtension})"
+msgstr ""
+
msgid "Source is not available"
msgstr "æºä¸å¯ç”¨"
@@ -20792,6 +21466,9 @@ msgstr "压缩æ交"
msgid "Stack trace"
msgstr "堆栈跟踪"
+msgid "Stacktrace snippet"
+msgstr ""
+
msgid "Stage"
msgstr "æš‚å­˜"
@@ -20889,13 +21566,16 @@ msgid "Start the Runner!"
msgstr "å¯åŠ¨ Runner!"
msgid "Start thread"
-msgstr "å¼€å¯è¯é¢˜"
+msgstr "å¼€å¯ä¸»é¢˜"
msgid "Start thread & close %{noteable_name}"
-msgstr "开始è¯é¢˜å¹¶å…³é—­%{noteable_name}"
+msgstr "开始主题并关闭%{noteable_name}"
msgid "Start thread & reopen %{noteable_name}"
-msgstr "å¼€å¯è¯é¢˜å¹¶é‡æ–°æ‰“å¼€%{noteable_name}"
+msgstr "å¼€å¯ä¸»é¢˜å¹¶é‡æ–°æ‰“å¼€%{noteable_name}"
+
+msgid "Start using Directed Acyclic Graphs (DAG)"
+msgstr ""
msgid "Start your Free Gold Trial"
msgstr "开始å…费的金牌试用"
@@ -21359,6 +22039,21 @@ msgstr "æžæš—ç°ç»¿è‰²"
msgid "SuggestedColors|Very pale orange"
msgstr "æžæ·¡æ©™è‰²"
+msgid "Suggestion is not applicable as the suggestion was not found."
+msgstr ""
+
+msgid "Suggestions are not applicable as one or more suggestions were not found."
+msgstr ""
+
+msgid "Suggestions are not applicable as their lines cannot overlap."
+msgstr ""
+
+msgid "Suggestions must all be on the same branch."
+msgstr ""
+
+msgid "Suggestions that change line count can't be added to batches, yet."
+msgstr ""
+
msgid "Suggestions:"
msgstr "建议:"
@@ -21401,9 +22096,6 @@ msgstr ""
msgid "Synchronization disabled"
msgstr ""
-msgid "Synchronization of container repositories is disabled."
-msgstr ""
-
msgid "System"
msgstr "系统"
@@ -21584,6 +22276,32 @@ msgstr "æœåŠ¡æ¡æ¬¾å议和éšç§æ”¿ç­–"
msgid "Terms of Service and Privacy Policy"
msgstr "æœåŠ¡æ¡æ¬¾å’Œéšç§æ”¿ç­–"
+msgid "Terraform|%{number} Terraform report failed to generate"
+msgid_plural "Terraform|%{number} Terraform reports failed to generate"
+msgstr[0] ""
+
+msgid "Terraform|%{number} Terraform report was generated in your pipelines"
+msgid_plural "Terraform|%{number} Terraform reports were generated in your pipelines"
+msgstr[0] ""
+
+msgid "Terraform|A Terraform report failed to generate."
+msgstr ""
+
+msgid "Terraform|A Terraform report was generated in your pipelines."
+msgstr ""
+
+msgid "Terraform|Generating the report caused an error."
+msgstr ""
+
+msgid "Terraform|Reported Resource Changes: %{addNum} to add, %{changeNum} to change, %{deleteNum} to delete"
+msgstr ""
+
+msgid "Terraform|The Terraform report %{name} failed to generate."
+msgstr ""
+
+msgid "Terraform|The Terraform report %{name} was generated in your pipelines."
+msgstr ""
+
msgid "Test"
msgstr "测试"
@@ -21609,8 +22327,8 @@ msgstr "ç¡®ä¿é¡¹ç›®å…·æœ‰CI作业。"
msgid "TestHooks|Ensure the project has CI pipelines."
msgstr "ç¡®ä¿é¡¹ç›®å…·æœ‰CIæµæ°´çº¿ã€‚"
-msgid "TestHooks|Ensure the project has at least one commit."
-msgstr "ç¡®ä¿é¡¹ç›®è‡³å°‘有一次æ交。"
+msgid "TestHooks|Ensure the project has deployments."
+msgstr ""
msgid "TestHooks|Ensure the project has issues."
msgstr "ç¡®ä¿é¡¹ç›®æœ‰è®®é¢˜ã€‚"
@@ -21709,7 +22427,7 @@ msgstr "议题跟踪用于管ç†éœ€æ±‚改进或者解决的问题。请注册或
msgid "The Prometheus server responded with \"bad request\". Please check your queries are correct and are supported in your Prometheus version. %{documentationLink}"
msgstr "PrometheusæœåŠ¡å™¨ä»¥â€œé”™è¯¯è¯·æ±‚â€å“应。请检查您的查询是å¦æ­£ç¡®ï¼Œå¹¶ä¸”当å‰çš„Prometheus版本支æŒã€‚ %{documentationLink}"
-msgid "The URL defined on the primary node that secondary nodes should use to contact it. Defaults to URL"
+msgid "The URL defined on the primary node that secondary nodes should use to contact it."
msgstr ""
msgid "The URL to use for connecting to Elasticsearch. Use a comma-separated list to support clustering (e.g., \"http://localhost:9200, http://localhost:9201\")."
@@ -21718,6 +22436,9 @@ msgstr "用于连接到Elasticsearchçš„URL。使用逗å·åˆ†éš”的列表æ¥æ”¯æŒ
msgid "The X509 Certificate to use when mutual TLS is required to communicate with the external authorization service. If left blank, the server certificate is still validated when accessing over HTTPS."
msgstr "在需è¦ç›¸äº’ TLS 与外部授æƒæœåŠ¡é€šä¿¡æ—¶ä½¿ç”¨çš„ X509 è¯ä¹¦ã€‚如果ä¿ç•™ä¸ºç©º, 则在访问 HTTPS æ—¶ä»ç„¶éªŒè¯æœåŠ¡å™¨è¯ä¹¦ã€‚"
+msgid "The above settings apply to all projects with the selected compliance framework(s)."
+msgstr ""
+
msgid "The amount of seconds after which a request to get a secondary node status will time out."
msgstr "获å–辅助节点状æ€è¯·æ±‚超时的秒数。"
@@ -22016,9 +22737,6 @@ msgstr "该阶段æ¯æ¡æ•°æ®æ‰€èŠ±çš„时间"
msgid "The total stage shows the time it takes between creating an issue and deploying the code to production. The data will be automatically added once you have completed the full idea to production cycle."
msgstr "总体阶段概述了从创建一个议题到将代ç éƒ¨ç½²åˆ°ç”Ÿäº§çŽ¯å¢ƒçš„时间。当完æˆæƒ³æ³•åˆ°éƒ¨ç½²ç”Ÿäº§çš„循环,数æ®å°†è‡ªåŠ¨æ·»åŠ åˆ°æ­¤å¤„。"
-msgid "The unique identifier for the Geo node. Must match %{geoNodeName} if it is set in gitlab.rb, otherwise it must match %{externalUrl} with a trailing slash"
-msgstr ""
-
msgid "The update action will time out after %{number_of_minutes} minutes. For big repositories, use a clone/push combination."
msgstr "æ›´æ–°æ“作将在 %{number_of_minutes} 分钟åŽè¶…时。对于大型仓库,请使用clone/push组åˆã€‚"
@@ -22043,9 +22761,6 @@ msgstr "用户映射是å‚与项目的 FogBugz 用户的电å­é‚®ä»¶åœ°å€å’Œç”¨
msgid "The user you are trying to deactivate has been active in the past %{minimum_inactive_days} days and cannot be deactivated"
msgstr "您正在å°è¯•å†»ç»“的用户在过去%{minimum_inactive_days}天一直处于活动状æ€ï¼Œå› æ­¤æ— æ³•å†»ç»“。"
-msgid "The user-facing URL of the Geo node"
-msgstr "Geo节点供用户访问的URL。"
-
msgid "The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6."
msgstr "中ä½æ•°æ˜¯ä¸€ä¸ªæ•°åˆ—中最中间的值。例如在 3ã€5ã€9 之间,中ä½æ•°æ˜¯ 5。在 3ã€5ã€7ã€8 之间,中ä½æ•°æ˜¯ (5 + 7)/ 2 = 6。"
@@ -22136,6 +22851,9 @@ msgstr "与您的设备通信时出现问题。"
msgid "There was a problem fetching project branches."
msgstr ""
+msgid "There was a problem fetching project tags."
+msgstr ""
+
msgid "There was a problem fetching project users."
msgstr ""
@@ -22187,6 +22905,9 @@ msgstr "获å–阶段中ä½æ•°æ®æ—¶å‡ºé”™"
msgid "There was an error fetching the %{replicableType}"
msgstr ""
+msgid "There was an error fetching the Geo Settings"
+msgstr ""
+
msgid "There was an error fetching the Node's Groups"
msgstr ""
@@ -22307,6 +23028,12 @@ msgstr "æ­¤%{issuableDisplayName}被é”定。åªæœ‰é¡¹ç›®æˆå‘˜å¯ä»¥è¯„论。"
msgid "This %{issuable} is locked. Only <strong>project members</strong> can comment."
msgstr "æ­¤%{issuable}已被é”定。åªæœ‰<strong>项目æˆå‘˜</strong>å¯ä»¥å‘表评论。"
+msgid "This %{noteableTypeText} is %{confidentialLinkStart}confidential%{linkEnd} and %{lockedLinkStart}locked%{linkEnd}."
+msgstr ""
+
+msgid "This %{noteableTypeText} is locked."
+msgstr ""
+
msgid "This %{viewer} could not be displayed because %{reason}. You can %{options} instead."
msgstr "因为 %{reason}无法显示 %{viewer} 。您å¯ä»¥æ”¹ä¸º %{options}。"
@@ -22325,8 +23052,11 @@ msgstr ""
msgid "This action can lead to data loss. To prevent accidental actions we ask you to confirm your intention."
msgstr "æ­¤æ“作å¯èƒ½å¯¼è‡´æ•°æ®ä¸¢å¤±ã€‚为防止æ„外,我们会è¦æ±‚您确认您的æ“作。"
+msgid "This also resolves all related threads"
+msgstr ""
+
msgid "This also resolves the discussion"
-msgstr "这也解决了讨论"
+msgstr ""
msgid "This application was created by %{link_to_owner}."
msgstr "这个应用程åºæ˜¯ç”± %{link_to_owner} 创建的。"
@@ -22415,6 +23145,9 @@ msgstr ""
msgid "This epic does not exist or you don't have sufficient permission."
msgstr "æ­¤å²è¯—ä¸å­˜åœ¨æˆ–者您没有足够的æƒé™ã€‚"
+msgid "This feature is currently in beta. We invite you to %{linkStart}give feedback%{linkEnd}."
+msgstr ""
+
msgid "This feature requires local storage to be enabled"
msgstr "此功能需è¦æœ¬åœ°å­˜å‚¨ä»¥å¯ç”¨"
@@ -22448,8 +23181,8 @@ msgstr "此用户为“幽çµç”¨æˆ·â€ï¼Œç”¨äºŽæŒæœ‰è¢«åˆ é™¤ç”¨æˆ·çš„所有议
msgid "This is a Work in Progress"
msgstr "当å‰å¤„于工作进行中"
-msgid "This is a confidential issue."
-msgstr "当å‰è®®é¢˜ä¸ºç§å¯†è®®é¢˜ã€‚"
+msgid "This is a confidential %{noteableTypeText}."
+msgstr ""
msgid "This is a delayed job to run in %{remainingTime}"
msgstr "这是一个将在%{remainingTime}åŽè¿è¡Œçš„延时作业。"
@@ -22475,9 +23208,6 @@ msgstr ""
msgid "This is your current session"
msgstr "这是您当å‰çš„会è¯"
-msgid "This issue is %{confidentialLinkStart}confidential%{linkEnd} and %{lockedLinkStart}locked%{linkEnd}."
-msgstr "此问题是%{confidentialLinkStart}机密的%{linkEnd}å’Œ%{lockedLinkStart}被é”定的%{linkEnd}。"
-
msgid "This issue is confidential"
msgstr "当å‰è®®é¢˜ä¸ºç§å¯†è®®é¢˜"
@@ -22487,9 +23217,6 @@ msgstr ""
msgid "This issue is in a child epic of the filtered epic"
msgstr ""
-msgid "This issue is locked."
-msgstr "此议题已é”定。"
-
msgid "This job depends on other jobs with expired/erased artifacts: %{invalid_dependencies}"
msgstr "该作业ä¾èµ–于其他产物已过期/已删除的作业:%{invalid_dependencies}"
@@ -22589,6 +23316,9 @@ msgstr "该作业将在计时器完æˆåŽè‡ªåŠ¨è¿è¡Œã€‚它们通常用于生产
msgid "This license has already expired."
msgstr ""
+msgid "This link points to external content"
+msgstr ""
+
msgid "This may expose confidential information as the selected fork is in another namespace that can have other members."
msgstr "æ­¤æ“作å¯èƒ½ä¼šæ³„æ¼æœºå¯†ä¿¡æ¯ï¼Œå› ä¸ºé€‰å®šçš„派生ä½äºŽå¦ä¸€ä¸ªå字空间中,å¯èƒ½åŒ…å«å…¶ä»–æˆå‘˜ã€‚"
@@ -22616,6 +23346,9 @@ msgstr "此页é¢ä¸å¯ç”¨ï¼Œæ‚¨æ— æƒè·¨é¡¹ç›®é˜…读相关信æ¯ã€‚"
msgid "This page will be removed in a future release."
msgstr "此页é¢å°†åœ¨å°†æ¥çš„版本中删除。"
+msgid "This pipeline does not use the %{codeStart}needs%{codeEnd} keyword and can't be represented as a directed acyclic graph."
+msgstr ""
+
msgid "This pipeline makes use of a predefined CI/CD configuration enabled by %{strongStart}Auto DevOps.%{strongEnd}"
msgstr "æ­¤æµæ°´çº¿ä½¿ç”¨äº† %{strongStart}Auto DevOps 预先定义的并已å¯ç”¨çš„ CI/CD é…置。%{strongEnd}"
@@ -22844,6 +23577,9 @@ msgstr "第一次æ交至第一个评论的时间"
msgid "Time from last commit to merge"
msgstr "最åŽä¸€æ¬¡æ交到åˆå¹¶çš„时间"
+msgid "Time in seconds"
+msgstr ""
+
msgid "Time in seconds GitLab will wait for a response from the external service. When the service does not respond in time, access will be denied."
msgstr "GitLab等待外部æœåŠ¡çš„å“应时间(秒)。当æœåŠ¡æ²¡æœ‰åŠæ—¶å“应时,访问将被拒ç»ã€‚"
@@ -23225,7 +23961,7 @@ msgid "Toggle this dialog"
msgstr "切æ¢æ­¤å¯¹è¯æ¡†"
msgid "Toggle thread"
-msgstr "切æ¢è¯é¢˜"
+msgstr "切æ¢ä¸»é¢˜"
msgid "ToggleButton|Toggle Status: OFF"
msgstr "开关状æ€ï¼šå…³é—­"
@@ -23263,9 +23999,15 @@ msgstr "贡献总计"
msgid "Total artifacts size: %{total_size}"
msgstr "所有产物大å°ï¼š %{total_size}"
+msgid "Total cores (CPUs)"
+msgstr ""
+
msgid "Total issues"
msgstr "总议题数"
+msgid "Total memory (GB)"
+msgstr ""
+
msgid "Total test time for all commits/merges"
msgstr "所有æ交和åˆå¹¶çš„总测试时间"
@@ -23467,6 +24209,9 @@ msgstr "U2F设备(%{length})"
msgid "U2F only works with HTTPS-enabled websites. Contact your administrator for more details."
msgstr "U2Fåªæ”¯æŒåœ¨HTTPS的网站。请è”系管ç†å‘˜èŽ·å¾—更多信æ¯"
+msgid "UI Development Kit"
+msgstr ""
+
msgid "URL"
msgstr "URL"
@@ -23539,9 +24284,6 @@ msgstr "无法加载差异。 %{button_try_again}"
msgid "Unable to load the merge request widget. Try reloading the page."
msgstr "无法加载åˆå¹¶è¯·æ±‚部件。请å°è¯•é‡æ–°åŠ è½½é¡µé¢ã€‚"
-msgid "Unable to process group import file"
-msgstr ""
-
msgid "Unable to resolve"
msgstr "无法解决"
@@ -23557,6 +24299,9 @@ msgstr "无法安排æµæ°´çº¿ç«‹å³è¿è¡Œ"
msgid "Unable to sign you in to the group with SAML due to \"%{reason}\""
msgstr "由于\"%{reason}\"的原因,您暂时ä¸èƒ½è¿›å…¥é…置了SAML 的群组"
+msgid "Unable to suggest a path. Please refresh and try again."
+msgstr ""
+
msgid "Unable to update label prioritization at this time"
msgstr "ç›®å‰æ— æ³•æ›´æ–°æ ‡è®°ä¼˜å…ˆçº§"
@@ -23572,6 +24317,9 @@ msgstr "å–消归档项目"
msgid "Unarchiving the project will restore people's ability to make changes to it. The repository can be committed to, and issues, comments, and other entities can be created. %{strong_start}Once active, this project shows up in the search and on the dashboard.%{strong_end}"
msgstr "å–消项目存档将æ¢å¤ç”¨æˆ·å¯¹å…¶è¿›è¡Œæ›´æ”¹çš„能力。æ交å¯ä»¥è¢«æŽ¨é€åˆ°ä»“库, 并且å¯ä»¥åˆ›å»ºè®®é¢˜ã€è¯„论和其他对象。%{strong_start}项目æ¢å¤åŽ, å°†å¯æ˜¾ç¤ºåœ¨æœç´¢å’Œä»ªè¡¨æ¿ä¸Šã€‚%{strong_end}"
+msgid "Unassign from commenting user"
+msgstr ""
+
msgid "Unblock"
msgstr "解除ç¦ç”¨"
@@ -23647,11 +24395,14 @@ msgstr "å–消 %{noun} 的“正在进行中â€æ ‡è®°ã€‚"
msgid "Unreachable"
msgstr ""
+msgid "Unrecognized cluster type"
+msgstr ""
+
msgid "Unresolve"
msgstr ""
msgid "Unresolve thread"
-msgstr "å°†è¯é¢˜ç½®ä¸ºæœªè§£å†³"
+msgstr "将主题置为未解决"
msgid "Unresolved"
msgstr ""
@@ -23707,6 +24458,9 @@ msgstr "å–消订阅%{quick_action_target}."
msgid "Unsubscribes from this %{quick_action_target}."
msgstr "å–消订阅%{quick_action_target}."
+msgid "Unsupported todo type passed. Supported todo types are: %{todo_types}"
+msgstr ""
+
msgid "Until"
msgstr "直到"
@@ -23734,6 +24488,9 @@ msgstr "全部更新"
msgid "Update approval rule"
msgstr "更新批准规则"
+msgid "Update approvers"
+msgstr ""
+
msgid "Update failed"
msgstr "更新失败"
@@ -23743,6 +24500,9 @@ msgstr "更新失败。请é‡è¯•ã€‚"
msgid "Update it"
msgstr "æ›´æ–°"
+msgid "Update iteration"
+msgstr ""
+
msgid "Update now"
msgstr "ç«‹å³æ›´æ–°"
@@ -23788,8 +24548,8 @@ msgstr "由%{updated_by}更新于%{updated_at}"
msgid "Updated at"
msgstr "更新于"
-msgid "Updated to"
-msgstr "更新到"
+msgid "Updated to %{linkStart}chart v%{linkEnd}"
+msgstr ""
msgid "Updating"
msgstr "更新中"
@@ -23818,6 +24578,9 @@ msgstr "在这里上传 <code>GoogleCodeProjectHosting.json</code>:"
msgid "Upload CSV file"
msgstr "上传CSV文件"
+msgid "Upload License"
+msgstr ""
+
msgid "Upload New File"
msgstr "上传新文件"
@@ -23899,18 +24662,21 @@ msgstr "软件包"
msgid "UsageQuota|Pipelines"
msgstr "æµæ°´çº¿"
+msgid "UsageQuota|Purchase more storage"
+msgstr ""
+
msgid "UsageQuota|Repositories"
msgstr ""
msgid "UsageQuota|Repository"
msgstr "仓库"
+msgid "UsageQuota|Snippets"
+msgstr ""
+
msgid "UsageQuota|Storage"
msgstr "存储"
-msgid "UsageQuota|Storage usage:"
-msgstr ""
-
msgid "UsageQuota|This namespace has no projects which use shared runners"
msgstr "此命å空间没有使用共享Runner的项目"
@@ -23941,6 +24707,12 @@ msgstr "Wiki"
msgid "UsageQuota|Wikis"
msgstr ""
+msgid "UsageQuota|You used: %{usage} %{limit}"
+msgstr ""
+
+msgid "UsageQuota|out of %{formattedLimit} of your namespace storage"
+msgstr ""
+
msgid "Use %{code_start}::%{code_end} to create a %{link_start}scoped label set%{link_end} (eg. %{code_start}priority::1%{code_end})"
msgstr "使用 %{code_start}::%{code_end} 创建 %{link_start}有范围标签集%{link_end} (例如 %{code_start}priority::1%{code_end})"
@@ -24007,6 +24779,9 @@ msgstr "用户世代表仅在å¯ç”¨ %{usage_ping_link_start}使用情况检测(u
msgid "User IDs"
msgstr ""
+msgid "User Lists can only be created and modified with %{linkStart}the API%{linkEnd}"
+msgstr ""
+
msgid "User OAuth applications"
msgstr "用户的 OAuth 应用程åº"
@@ -24031,6 +24806,9 @@ msgstr ""
msgid "User key was successfully removed."
msgstr "用户密钥已æˆåŠŸåˆ é™¤ã€‚"
+msgid "User list %{name} will be removed. Are you sure?"
+msgstr ""
+
msgid "User map"
msgstr "用户映射"
@@ -24052,149 +24830,11 @@ msgstr "用户已æˆåŠŸä»Žé¡¹ç›®ä¸­åˆ é™¤ã€‚"
msgid "User was successfully updated."
msgstr "用户已æˆåŠŸæ›´æ–°ã€‚"
-msgid "UserOnboardingTour|%{activeTour}/%{totalTours}"
-msgstr "%{activeTour}/%{totalTours}"
-
-msgid "UserOnboardingTour|%{completed}/%{total} steps completed"
-msgstr "%{completed}/%{total}步已完æˆ"
-
-msgid "UserOnboardingTour|%{emphasisStart}Well done!%{emphasisEnd}%{lineBreak}%{lineBreak}That's it for our guided tour, congratulations for making it all the way to the end!%{lineBreak}%{lineBreak}We hope this gave you a good overview of GitLab and how it can help you. We'll now show you how to create your own project and invite your colleagues."
-msgstr "%{emphasisStart}太棒了ï¼%{emphasisEnd}%{lineBreak}%{lineBreak}我们的导览之旅到这里就结æŸäº†ï¼Œç¥è´ºæ‚¨å®Œæˆå…¨éƒ¨å¯¼è§ˆå†…容ï¼%{lineBreak}%{lineBreak}我们希望这个导览对您了解GitLab有所帮助。下é¢å°†å‘您展示如何创建自己的项目并将您的åŒäº‹åŠ å…¥é¡¹ç›®ã€‚"
-
-msgid "UserOnboardingTour|Adding other members to a project is done through Project Settings. Click on %{emphasisStart}Settings%{emphasisEnd}."
-msgstr "å¯ä»¥é€šè¿‡é¡¹ç›®è®¾ç½®æ¥æ·»åŠ å…¶ä»–æˆå‘˜åˆ°é¡¹ç›®ã€‚点击 %{emphasisStart}设置%{emphasisEnd}。"
-
-msgid "UserOnboardingTour|Alright, that's it for Commits. Let's take a look at the %{emphasisStart}Branches%{emphasisEnd}."
-msgstr "好的,æ交就介ç»åˆ°è¿™é‡Œã€‚让我们一起æ¥äº†è§£ä¸€ä¸‹%{emphasisStart}分支%{emphasisEnd}。"
-
-msgid "UserOnboardingTour|Awesome! Now click on %{emphasisStart}Members%{emphasisEnd}."
-msgstr "好æžäº†ï¼çŽ°åœ¨ç‚¹å‡» %{emphasisStart}æˆå‘˜%{emphasisEnd}。"
-
-msgid "UserOnboardingTour|Click on one of the %{emphasisStart}Compare%{emphasisEnd} buttons to compare a branch to master."
-msgstr "点击任一一个%{emphasisStart}比较%{emphasisEnd}按钮,将分支与分支进行比较。"
-
-msgid "UserOnboardingTour|Click on one of the %{emphasisStart}pipeline IDs%{emphasisEnd} to see the details of a pipeline."
-msgstr "点击任一一个%{emphasisStart}æµæ°´çº¿ID%{emphasisEnd}以查看æµæ°´çº¿çš„详细信æ¯ã€‚"
-
-msgid "UserOnboardingTour|Click to open the latest commit to see its details."
-msgstr "点击打开最新æ交æ¥æŸ¥çœ‹è¯¦æƒ…。"
-
-msgid "UserOnboardingTour|Close 'Learn GitLab'"
-msgstr "关闭 '学习 GitLab'"
-
-msgid "UserOnboardingTour|Commits are shown in chronological order and can be filtered by the commit message or by the branch."
-msgstr "æ交按时间顺åºæ˜¾ç¤ºï¼Œå¹¶å¯é€šè¿‡æ交消æ¯æˆ–分支进行过滤。"
-
-msgid "UserOnboardingTour|Create a project"
-msgstr "创建一个新项目"
-
-msgid "UserOnboardingTour|Exit 'Learn GitLab'"
-msgstr "退出'学习 GitLab'"
-
-msgid "UserOnboardingTour|Got it"
-msgstr "知é“了"
-
-msgid "UserOnboardingTour|Great job! %{clapHands} We hope the tour was helpful and that you learned how to use GitLab.%{lineBreak}%{lineBreak}We'd love to get your feedback on this tour.%{lineBreak}%{lineBreak}%{emphasisStart}How helpful would you say this guided tour was?%{emphasisEnd}%{lineBreak}%{lineBreak}"
-msgstr "好æžäº†ï¼ %{clapHands} 我们希望这个指å—对您有所帮助,并且您学会了如何使用GitLab。%{lineBreak}%{lineBreak}我们希望能收到您对本指å—çš„å馈。%{lineBreak}%{lineBreak}%{emphasisStart}您认为该指å—的用途有多大?%{emphasisEnd}%{lineBreak}%{lineBreak}"
-
-msgid "UserOnboardingTour|Guided GitLab Tour"
-msgstr "GitLab导览"
-
-msgid "UserOnboardingTour|Here you can compare the changes of this branch to another one. Changes are divided by files so that it's easier to see what was changed where."
-msgstr "在这里,您å¯ä»¥å°†æ­¤åˆ†æ”¯çš„å˜æ›´ä¸Žå¦ä¸€ä¸ªåˆ†æ”¯è¿›è¡Œæ¯”较。å˜æ›´ä¾ç…§æ–‡ä»¶åˆ†ç»„,方便您查看更改的ä½ç½®ã€‚"
-
-msgid "UserOnboardingTour|Here you can create a project from scratch, start with a template or import a repository from other platforms. Whatever you choose, we'll guide you through the process.%{lineBreak}%{lineBreak}Fill in your new project information and click on %{emphasisStart}Create Project%{emphasisEnd} to progress to the next step."
-msgstr "在这里,您å¯ä»¥ä»Žé›¶å¼€å§‹åˆ›å»ºé¡¹ç›®ï¼Œä»Žæ¨¡æ¿å¼€å§‹æˆ–从其他平å°å¯¼å…¥ä»“库。无论您选择什么,我们都会指导您完æˆæ•´ä¸ªè¿‡ç¨‹ã€‚%{lineBreak}%{lineBreak}填写项目的信æ¯ï¼Œç„¶åŽå•å‡»%{emphasisStart}创建项目%{emphasisEnd}以进入下一步。"
-
-msgid "UserOnboardingTour|Here you can see the breakdown of the pipelines: its stages and jobs in each of the stages and their status.%{lineBreak}%{lineBreak}Our CI/CD pipelines are quite complex, most of our users have fewer and simpler pipelines."
-msgstr "在这里您å¯ä»¥çœ‹åˆ°æµæ°´çº¿çš„组æˆï¼šæ‰€æœ‰é˜¶æ®µå’Œæ¯ä¸ªé˜¶æ®µçš„作业åŠå…¶çŠ¶æ€ã€‚%{lineBreak}%{lineBreak}我们的CI/CDæµæ°´çº¿éžå¸¸å¤æ‚,大多数用户拥有更少和更简å•çš„æµæ°´çº¿ã€‚"
-
-msgid "UserOnboardingTour|Here you can see the current members of the project (just you at the moment) and invite new members.%{lineBreak}%{lineBreak}You can invite multiple members at once (existing GitLab users or invite by email) and you can also set their roles and permissions.%{lineBreak}%{lineBreak}Add a few members and click on %{emphasisStart}Add to project%{emphasisEnd} to complete this step."
-msgstr "在这里,您å¯ä»¥çœ‹åˆ°é¡¹ç›®çš„当å‰æˆå‘˜(ç›®å‰æœ‰æ‚¨è‡ªå·±)并邀请新æˆå‘˜ã€‚%{lineBreak}%{lineBreak}您å¯ä»¥ä¸€æ¬¡é‚€è¯·å¤šä¸ªæˆå‘˜(现有GitLab用户或通过电å­é‚®ä»¶é‚€è¯·),您也å¯ä»¥è®¾ç½®ä»–们的角色和æƒé™ã€‚%{lineBreak}%{lineBreak}请添加几个æˆå‘˜ï¼Œç„¶åŽå•å‡»%{emphasisStart}添加到项目%{emphasisEnd}以完æˆæ­¤æ­¥éª¤ã€‚"
-
-msgid "UserOnboardingTour|Here you can see what changes were made with this commit, on what branch and if there's a related merge request. The status of the pipeline will also show up if CI/CD is set up.%{lineBreak}%{lineBreak}You can also comment on the lines of code that were changed and start a discussion with your colleagues!"
-msgstr "在这里,您å¯ä»¥çœ‹åˆ°æ­¤æ交包å«äº†å“ªäº›æ›´æ”¹ï¼Œåœ¨å“ªä¸ªåˆ†æ”¯ä¸Šä»¥åŠæ˜¯å¦å­˜åœ¨ç›¸å…³çš„åˆå¹¶è¯·æ±‚。如果设置了CI/CD,æµæ°´çº¿çš„状æ€ä¹Ÿä¼šæ˜¾ç¤ºå‡ºæ¥ã€‚%{lineBreak}%{lineBreak}您还å¯ä»¥å¯¹å·²æ›´æ”¹çš„代ç è¡Œè¿›è¡Œè¯„论,并与åŒäº‹å¼€å§‹è®¨è®ºï¼"
-
-msgid "UserOnboardingTour|Here's an overview of branches in the %{emphasisStart}%{projectName}%{emphasisEnd} project. They're split into Active and Stale.%{lineBreak}%{lineBreak}From here, you can create a new merge request from a branch, or compare the branch to any other branch in the project. By default, it will compare it to the master branch."
-msgstr "这里是%{emphasisStart}%{projectName}%{emphasisEnd}项目中分支的概述。分支被分类为活跃和éžæ´»è·ƒã€‚%{lineBreak}%{lineBreak}从此处,您å¯ä»¥ä»Žåˆ†æ”¯åˆ›å»ºæ–°çš„åˆå¹¶è¯·æ±‚,或者将分支与项目中的任何其他分支进行比较。默认情况下,它会将其与主分支进行比较。"
-
-msgid "UserOnboardingTour|Invite colleagues"
-msgstr "邀请åŒäº‹"
-
-msgid "UserOnboardingTour|Issues are great for communicating and keeping track of progress in GitLab. These are all issues that are open in the %{emphasisStart}%{projectName}%{emphasisEnd}.%{lineBreak}%{lineBreak}You can help us improve GitLab by contributing work to issues that are labeled <span class=\"badge color-label accept-mr-label\">Accepting merge requests</span>.%{lineBreak}%{lineBreak}This list can be filtered by labels, milestones, assignees, authors... We'll show you how it looks when the list is filtered by a label."
-msgstr "议题éžå¸¸é€‚åˆåœ¨GitLab中进行沟通和跟踪进展。这些是在%{emphasisStart}%{projectName}%{emphasisEnd}中开å¯çš„全部议题。%{lineBreak}%{lineBreak}您å¯ä»¥é€šè¿‡ä¸ºæ ‡è®°ä¸º<span class=\"badge color-label accept-mr-label\">Accepting merge requests</span>的议题作出贡献æ¥å¸®åŠ©æˆ‘们改进GitLab。%{lineBreak}%{lineBreak}此列表å¯ä»¥æŒ‰æ ‡ç­¾ï¼Œé‡Œç¨‹ç¢‘,å—让人,作者进行过滤......我们将å‘您展示列表按标签过滤时的样å­ã€‚"
-
-msgid "UserOnboardingTour|Learn GitLab"
-msgstr "学习GitLab"
-
-msgid "UserOnboardingTour|Let's take a closer look at a merge request. Click on the title of one."
-msgstr "让我们仔细查看åˆå¹¶è¯·æ±‚。点击一个标题。"
-
-msgid "UserOnboardingTour|Let's take a closer look at all the commits. Click on %{emphasisStart}Commits%{emphasisEnd}."
-msgstr "让我们仔细查看所有æ交。点击 %{emphasisStart}æ交%{emphasisEnd}。"
-
-msgid "UserOnboardingTour|Let's take a closer look at the repository of this project. Click on %{emphasisStart}Repository%{emphasisEnd}."
-msgstr "让我们仔细看看这个项目的仓库。å•å‡»%{emphasisStart}Repository%{emphasisEnd}。"
-
-msgid "UserOnboardingTour|No thanks"
-msgstr "ä¸ï¼Œè°¢è°¢"
-
-msgid "UserOnboardingTour|Ok, let's go"
-msgstr "好,我们开始å§"
-
-msgid "UserOnboardingTour|Ok, show me"
-msgstr "好,请展示"
-
-msgid "UserOnboardingTour|Open one of the issues by clicking on its title."
-msgstr "å•å‡»å…¶æ ‡é¢˜æ‰“开任一议题。"
-
-msgid "UserOnboardingTour|Restart this step"
-msgstr "é‡æ–°å¼€å§‹æ­¤æ­¥éª¤"
-
-msgid "UserOnboardingTour|Skip this step"
-msgstr "跳过此步骤"
-
-msgid "UserOnboardingTour|Sweet! Your project was created and is ready to be used.%{lineBreak}%{lineBreak}You can start adding files to the repository or clone it. One last thing we want to show you is how to invite your colleagues to your new project."
-msgstr "好æžäº†ï¼æ‚¨çš„项目已创建æˆåŠŸå¯ä»¥ä½¿ç”¨ã€‚%{lineBreak}%{lineBreak}您å¯ä»¥å°†æ–‡ä»¶æ·»åŠ åˆ°ä»“库或克隆长裤。我们è¦å±•ç¤ºçš„最åŽä¸€ä»¶äº‹å°±æ˜¯å¦‚何邀请您的åŒäº‹åŠ å…¥æ‚¨çš„新项目。"
-
-msgid "UserOnboardingTour|Take a look. Here's a nifty menu for quickly creating issues, merge requests, snippets, projects and groups. Click on it and select \"New project\" from the \"GitLab\" section to get started."
-msgstr "这里是个éžå¸¸æœ‰ç”¨çš„èœå•ï¼Œå¯ä»¥å¿«é€Ÿåˆ›å»ºé—®é¢˜ã€åˆå¹¶è¯·æ±‚ã€ä»£ç ã€é¡¹ç›®å’Œç¾¤ç»„。点击并从“GitLabâ€éƒ¨åˆ†ä¸­é€‰æ‹©â€œæ–°å»ºé¡¹ç›®â€å¼€å§‹ã€‚"
-
-msgid "UserOnboardingTour|Thanks for taking the guided tour. Remember, if you want to go through it again, you can start %{emphasisStart}Learn GitLab%{emphasisEnd} in the help menu on the top right."
-msgstr "感谢您æµè§ˆGitLab导览。如需å†æ¬¡æŸ¥çœ‹ï¼Œå¯åœ¨å³ä¸Šè§’的帮助èœå•ä¸­ç‚¹å‡»%{emphasisStart}学习GitLab%{emphasisEnd}。"
-
-msgid "UserOnboardingTour|Thanks for the feedback! %{thumbsUp}"
-msgstr "æ„Ÿè°¢åé¦ˆï¼ %{thumbsUp}"
-
-msgid "UserOnboardingTour|That's it for issues. Let'st take a look at %{emphasisStart}Merge Requests%{emphasisEnd}."
-msgstr "这就是议题。接下æ¥è®©æˆ‘们进入%{emphasisStart}åˆå¹¶è¯·æ±‚%{emphasisEnd}。 "
-
-msgid "UserOnboardingTour|That's it for merge requests. Now for the final part of this guided tour - the %{emphasisStart}CI/CD%{emphasisEnd}."
-msgstr "刚刚介ç»äº†åˆå¹¶è¯·æ±‚。现在为本导览的最åŽä¸€éƒ¨åˆ† - %{emphasisStart}CI/CD%{emphasisEnd}。"
-
-msgid "UserOnboardingTour|That's it for the Repository. Let's take a look at the %{emphasisStart}Issues%{emphasisEnd}."
-msgstr "仓库就介ç»åˆ°è¿™é‡Œã€‚接下æ¥æˆ‘们一起æ¥äº†è§£%{emphasisStart}议题%{emphasisEnd}。"
-
-msgid "UserOnboardingTour|The structure of this page is very similar to issues. Status, description, discussion and the sidebar are all here.%{lineBreak}%{lineBreak}But take a look below the description and you'll notice that there's more information about the merge request, the CI/CD pipeline and the options for approving it.%{lineBreak}%{lineBreak}Alongside the discussion you can also see more information about commits in this merge request, the status of pipelines and review all changes that were made."
-msgstr "此页é¢çš„结构éžå¸¸ç±»ä¼¼äºŽè®®é¢˜ã€‚状æ€ã€æè¿°ã€è®¨è®ºå’Œä¾§è¾¹æ æ±‡èšäºŽæ­¤ã€‚%{lineBreak}%{lineBreak}ä½ å¯ä»¥ä»Žæ述中了解到关于åˆå¹¶è¯·æ±‚ã€CI/CDæµæ°´çº¿å’Œæ ¸å‡†é€‰é¡¹çš„更多信æ¯ã€‚%{lineBreak}%{lineBreak}除讨论以外,您还å¯ä»¥çœ‹åˆ°å½“å‰åˆå¹¶è¯·æ±‚中的æ交ã€æµæ°´çº¿çŠ¶æ€çš„进一步信æ¯ä»¥åŠæŸ¥çœ‹å…¶ä¸­åŒ…å«çš„所有å˜æ›´ã€‚"
-
-msgid "UserOnboardingTour|There's a lot of information here but don't worry, we'll go through it.%{lineBreak}%{lineBreak}On the top you can see the status of the issue and when it was opened and by whom. Directly below it is the issue description and below that are other %{emphasisStart}related issues%{emphasisEnd} and %{emphasisStart}merge requests%{emphasisEnd} (if any). Then below that is the %{emphasisStart}discussion%{emphasisEnd}, that's where most of the communication happens.%{lineBreak}%{lineBreak}On the right, there's a sidebar where you can view/change the %{emphasisStart}assignee, milestone, due date, labels, weight%{emphasisEnd}, etc."
-msgstr "这里有éžå¸¸å¤šçš„ä¿¡æ¯ï¼Œä½†ä¸ç”¨æ‹…心,我们会一一解释。%{lineBreak}%{lineBreak}在界é¢é¡¶éƒ¨ï¼Œæ‚¨å¯ä»¥çœ‹åˆ°è®®é¢˜çš„状æ€ä»¥åŠå¼€å¯æ—¶é—´å’Œç”±è°æ‰“开。在正下方的是议题æ述,å†ä¸‹é¢æ˜¯å…¶ä»– %{emphasisStart}相关议题%{emphasisEnd}åŠ%{emphasisStart}åˆå¹¶è¯·æ±‚%{emphasisEnd}(如果有的è¯)。然åŽå†ä¸‹é¢æ˜¯%{emphasisStart}讨论区%{emphasisEnd},包å«äº†å¤§éƒ¨åˆ†å…³äºŽè®®é¢˜çš„交æµä¸Žæ²Ÿé€šã€‚%{lineBreak}%{lineBreak}ç•Œé¢å³ä¾§æ˜¯ä¾§è¾¹æ ï¼Œæ‚¨å¯ä»¥åœ¨å…¶ä¸­æŸ¥çœ‹/更改%{emphasisStart}å—让人,里程碑,截止日期,标签,æƒé‡%{emphasisEnd}等。"
-
-msgid "UserOnboardingTour|These are all the CI/CD pipelines we have for our %{emphasisStart}%{projectName}%{emphasisEnd} project.%{lineBreak}%{lineBreak}Here you can see the status of each pipeline, for what commit it's running for, its stages and the status for them."
-msgstr "这些是%{emphasisStart}%{projectName}%{emphasisEnd}项目所有的CI/CDæµæ°´çº¿ã€‚%{lineBreak}%{lineBreak}您å¯ä»¥åœ¨è¿™é‡Œçœ‹åˆ°æ¯ä¸€æ¡æµæ°´çº¿çš„状æ€ã€å¯¹åº”çš„æ交ã€é˜¶æ®µåŠå…¶çŠ¶æ€ã€‚"
-
-msgid "UserOnboardingTour|These are all the issues that are available for community contributions. Let's take a closer look at one of them."
-msgstr "这些是所有开放社区贡献的议题。让我们仔细看一下其中的一个。"
-
-msgid "UserOnboardingTour|This is an overview of all merge requests in this project. Similarly to the issues overview it can be filtered down by things like labels, milestones, authors, assignees, etc."
-msgstr "这就是对当å‰é¡¹ç›®æ‰€æœ‰åˆå¹¶è¯·æ±‚的概览。与议题概览类似,它å¯ä»¥é€šè¿‡æ ‡è®°ã€é‡Œç¨‹ç¢‘ã€ä½œè€…ã€å—让人等等æ¡ä»¶æ¥è¿‡æ»¤ã€‚"
-
-msgid "UserOnboardingTour|This is the repository for the %{emphasisStart}%{projectName}%{emphasisEnd} project. All our code is stored here. Feel free to explore and take a closer look at folders and files.%{lineBreak}%{lineBreak}Above the file structure you can see the latest commit, who the author is and the status of the CI/CD pipeline.%{lineBreak}%{lineBreak}If you scroll down below the file structure, you'll find the Readme of this project. This is defined in the README.md file at the root of the repository."
-msgstr "这是%{emphasisStart}%{projectName}%{emphasisEnd}项目的仓库。项目的所有代ç éƒ½å­˜å‚¨åœ¨è¿™é‡Œã€‚您å¯ä»¥éšæ„æµè§ˆå¹¶è¿›ä¸€æ­¥æŸ¥çœ‹æ–‡ä»¶å¤¹å’Œæ–‡ä»¶ã€‚%{lineBreak}%{lineBreak}在文件夹结构上方,您å¯ä»¥çœ‹åˆ°æœ€æ–°çš„æ交,作者是è°ä»¥åŠCI/CDæµæ°´çº¿çš„状æ€ã€‚%{lineBreak}%{lineBreak}如果å‘下滚动,在文件夹结构下方,您将找到此项目的自述文件。这在仓库根目录下的README.md文件中定义。"
+msgid "UserList|Delete %{name}?"
+msgstr ""
-msgid "UserOnboardingTour|Welcome to the project overview of the %{emphasisStart}%{projectName}%{emphasisEnd} project. This is the project that we use to work on GitLab. At first, a project seems like a simple repository, but at GitLab, a project is so much more.%{lineBreak}%{lineBreak}You can create projects for hosting your codebase, use it as an issue tracker, collaborate on code, and continuously build, test, and deploy your app with built-in GitLab CI/CD."
-msgstr "欢迎æ¥åˆ°%{emphasisStart}%{projectName}%{emphasisEnd}项目的概述。这将是我们在GitLab上进行开å‘工作的项目。一般地讲,项目åªåŒ…å«ä»£ç ä»“库,但GitLab项目å´ä¸ä»…如此。%{lineBreak}%{lineBreak}在GitLab项目里é¢ï¼Œæ‚¨å¯ä»¥åˆ›å»ºé¡¹ç›®æ¥æ‰˜ç®¡ä½ çš„代ç ï¼Œä½¿ç”¨å®ƒä½œä¸ºè®®é¢˜è¿½è¸ªå™¨ï¼Œå作开å‘代ç ï¼Œå¹¶ä¸”通过内置的GitLab CI/CD进行æŒç»­æž„建ã€æµ‹è¯•å’Œéƒ¨ç½²åº”用。"
+msgid "UserList|created %{timeago}"
+msgstr ""
msgid "UserProfile|Activity"
msgstr "活动"
@@ -24346,6 +24986,9 @@ msgstr "没有指派人 - %{openingTag}分é…给自己%{closingTag}"
msgid "UsersSelect|Unassigned"
msgstr "未分é…"
+msgid "Using %{codeStart}needs%{codeEnd} allows jobs to run before their stage is reached, as soon as their individual dependencies are met, which speeds up your pipelines."
+msgstr ""
+
msgid "Using %{code_start}::%{code_end} denotes a %{link_start}scoped label set%{link_end}"
msgstr "使用 %{code_start}::%{code_end} 表示 %{link_start}范围标签集%{link_end}"
@@ -24430,9 +25073,6 @@ msgstr "版本"
msgid "Versions"
msgstr ""
-msgid "Very helpful"
-msgstr "很有帮助"
-
msgid "View Documentation"
msgstr "查看文档"
@@ -24579,6 +25219,9 @@ msgstr "公开"
msgid "VisibilityLevel|Unknown"
msgstr "未知"
+msgid "Visit settings page"
+msgstr ""
+
msgid "VisualReviewApp|%{stepStart}Step 1%{stepEnd}. Copy the following script:"
msgstr "%{stepStart}步骤 1%{stepEnd}. å¤åˆ¶ä»¥ä¸‹è„šæœ¬ï¼š"
@@ -24672,6 +25315,9 @@ msgstr ""
msgid "VulnerabilityManagement|Something went wrong while trying to delete the comment. Please try again later."
msgstr ""
+msgid "VulnerabilityManagement|Something went wrong while trying to refresh the vulnerability. Please try again later."
+msgstr ""
+
msgid "VulnerabilityManagement|Something went wrong while trying to retrieve the vulnerability history. Please try again later."
msgstr ""
@@ -24714,6 +25360,9 @@ msgstr ""
msgid "Vulnerability|Class"
msgstr "类型"
+msgid "Vulnerability|Crash Address"
+msgstr ""
+
msgid "Vulnerability|Description"
msgstr "说明"
@@ -24789,9 +25438,6 @@ msgstr "我们无法确定删除议题的路径"
msgid "We couldn't reach the Prometheus server. Either the server no longer exists or the configuration details need updating."
msgstr "无法连接PrometheusæœåŠ¡å™¨ã€‚æœåŠ¡å™¨ä¸å†å­˜åœ¨ï¼Œæˆ–者é…置信æ¯éœ€è¦æ›´æ–°ã€‚"
-msgid "We created a short guided tour that will help you learn the basics of GitLab and how it will help you be better at your job. It should only take a couple of minutes. You will be guided by two types of helpers, best recognized by their color."
-msgstr "我们准备了一个简短的指å—,以帮助您了解GitLab的基础知识以åŠå¦‚何帮助您更好地完æˆå·¥ä½œã€‚阅读指å—åªéœ€å‡ åˆ†é’Ÿæ—¶é—´ã€‚该指å—å«æœ‰é€šè¿‡ä¸åŒé¢œè‰²æ ‡ç¤ºçš„两ç§å¸®åŠ©ç¬¦æ¥ä¸ºæ‚¨æ供导引。"
-
msgid "We detected potential spam in the %{humanized_resource_name}. Please solve the reCAPTCHA to proceed."
msgstr "我们在%{humanized_resource_name}检测到潜在滥用行为。请输入此reCAPTCHA验è¯ç å¹¶ç»§ç»­ã€‚"
@@ -24825,6 +25471,9 @@ msgstr ""
msgid "We've found no vulnerabilities"
msgstr "未å‘现安全æ¼æ´ž"
+msgid "Web Application Firewall"
+msgstr ""
+
msgid "Web IDE"
msgstr "Web IDE"
@@ -24885,9 +25534,6 @@ msgstr ""
msgid "Welcome to GitLab.com<br>@%{name}!"
msgstr ""
-msgid "Welcome to the Guided GitLab Tour"
-msgstr "欢迎æ¥åˆ°Guided GitLab Tour"
-
msgid "Welcome to the guided GitLab tour"
msgstr ""
@@ -24934,11 +25580,8 @@ msgstr "当:"
msgid "While it's rare to have no vulnerabilities, it can happen. In any event, we ask that you please double check your settings to make sure you've set up your dashboard correctly."
msgstr "虽然没有å‘现æ¼æ´žï¼Œè¿™ç§çŽ°è±¡å¾ˆç½•è§ï¼Œä½†ä¹Ÿæ˜¯æœ‰å¯èƒ½çš„。无论如何,建议您仔细检查设置以确ä¿ä»ªè¡¨æ¿çš„é…置正确。"
-msgid "White helpers give contextual information."
-msgstr "白色助手给出了上下文信æ¯ã€‚"
-
msgid "Who can be an approver?"
-msgstr "è°å¯ä»¥æ˜¯å®¡æ ¸è€…?"
+msgstr "è°å¯ä»¥æ˜¯æ ¸å‡†äººï¼Ÿ"
msgid "Who can see this group?"
msgstr "哪些人å¯ä»¥çœ‹åˆ°è¿™ä¸ªç¾¤ç»„?"
@@ -24982,12 +25625,18 @@ msgstr "在该路径中已ç»æœ‰ä¸€ä¸ªå…·æœ‰ç›¸åŒæ ‡é¢˜çš„页é¢ã€‚"
msgid "WikiEmptyIssueMessage|Suggest wiki improvement"
msgstr "Wiki 改进建议"
+msgid "WikiEmptyIssueMessage|You must be a group member in order to add wiki pages. If you have suggestions for how to improve the wiki for this group, consider opening an issue in the %{issues_link}."
+msgstr ""
+
msgid "WikiEmptyIssueMessage|You must be a project member in order to add wiki pages. If you have suggestions for how to improve the wiki for this project, consider opening an issue in the %{issues_link}."
msgstr "åªæœ‰è¯¥é¡¹ç›®çš„æˆå‘˜æ‰å¯ä»¥æ·»åŠ  Wiki 页é¢ï¼Œå¦‚果您有任何关于项目 Wiki 的改进建议,请通过 %{issues_link} æ交议题。"
msgid "WikiEmptyIssueMessage|issue tracker"
msgstr "议题跟踪"
+msgid "WikiEmpty|A wiki is where you can store all the details about your group. This can include why you've created it, its principles, how to use it, and so on."
+msgstr ""
+
msgid "WikiEmpty|A wiki is where you can store all the details about your project. This can include why you've created it, its principles, how to use it, and so on."
msgstr "您å¯ä»¥ä½¿ç”¨ Wiki æ¥ä¿å­˜é¡¹ç›®çš„详细信æ¯ï¼Œä¾‹å¦‚:创建该项目的原因, 项目的原ç†åŠé¡¹ç›®çš„使用说明等等。"
@@ -24997,12 +25646,21 @@ msgstr "创建您的第一个页é¢"
msgid "WikiEmpty|Suggest wiki improvement"
msgstr "Wiki 改进建议"
+msgid "WikiEmpty|The wiki lets you write documentation for your group"
+msgstr ""
+
msgid "WikiEmpty|The wiki lets you write documentation for your project"
msgstr "您å¯ä»¥ä½¿ç”¨ Wiki 为您的项目编写文档"
+msgid "WikiEmpty|This group has no wiki pages"
+msgstr ""
+
msgid "WikiEmpty|This project has no wiki pages"
msgstr "该项目无任何 Wiki 页é¢"
+msgid "WikiEmpty|You must be a group member in order to add wiki pages."
+msgstr ""
+
msgid "WikiEmpty|You must be a project member in order to add wiki pages."
msgstr "åªæœ‰é¡¹ç›®æˆå‘˜æ‰å¯ä»¥æ·»åŠ  Wiki 页é¢ã€‚"
@@ -25279,15 +25937,15 @@ msgstr ""
msgid "You can now export your security dashboard to a CSV report."
msgstr ""
+msgid "You can now manage alert endpoint configuration in the Alerts section on the Operations settings page. Fields on this page have been deprecated."
+msgstr ""
+
msgid "You can now submit a merge request to get this change into the original branch."
msgstr "您现在å¯ä»¥æ交åˆå¹¶è¯·æ±‚以将此更改å‘é€åˆ°æºåˆ†æ”¯ã€‚"
msgid "You can now submit a merge request to get this change into the original project."
msgstr "您现在å¯ä»¥æ交åˆå¹¶è¯·æ±‚以将此更改添加到æºé¡¹ç›®ä¸­ã€‚"
-msgid "You can only add files when you are on a branch"
-msgstr "åªèƒ½åœ¨åˆ†æ”¯ä¸Šæ·»åŠ æ–‡ä»¶"
-
msgid "You can only edit files when you are on a branch"
msgstr "åªèƒ½åœ¨åˆ†æ”¯ä¸Šç¼–辑文件"
@@ -25507,7 +26165,7 @@ msgstr "您需è¦ä¸Šä¼ GitLab项目导出文件(以.gz结尾)."
msgid "You need to upload a Google Takeout archive."
msgstr "您需è¦ä¸Šä¼ Google Takeout文件。"
-msgid "You reached %{usage_in_percent} of %{namespace_name}'s capacity (%{used_storage} of %{storage_limit})"
+msgid "You reached %{usage_in_percent} of %{namespace_name}'s storage capacity (%{used_storage} of %{storage_limit})"
msgstr ""
msgid "You tried to fork %{link_to_the_project} but it failed for the following reason:"
@@ -25810,13 +26468,13 @@ msgstr ""
msgid "Your subscription expired!"
msgstr ""
-msgid "Your subscription has been downgraded"
+msgid "Your subscription has been downgraded."
msgstr ""
-msgid "Your subscription will automatically renew in %{remaining_days}"
+msgid "Your subscription will automatically renew in %{remaining_days}."
msgstr ""
-msgid "Your subscription will expire in %{remaining_days}"
+msgid "Your subscription will expire in %{remaining_days}."
msgstr ""
msgid "Zoom meeting added"
@@ -25895,9 +26553,6 @@ msgstr "阻止"
msgid "branch name"
msgstr "分支å称"
-msgid "build pipeline reference mismatch"
-msgstr ""
-
msgid "by"
msgstr "æ¥è‡ª"
@@ -25916,6 +26571,9 @@ msgstr "无法修改"
msgid "cannot block others"
msgstr "ä¸ä¼šé˜»å¡žå…¶ä»–"
+msgid "cannot contain HTML/XML tags, including any word between angle brackets (<,>)."
+msgstr ""
+
msgid "cannot include leading slash or directory traversal."
msgstr "ä¸èƒ½ä»¥æ–œçº¿å¼€å¤´æˆ–转到仓库以外目录。"
@@ -25946,50 +26604,6 @@ msgstr "%{linkStartTag}了解有关代ç è´¨é‡æŠ¥å‘Šçš„更多信æ¯%{linkEndTag
msgid "ciReport|%{remainingPackagesCount} more"
msgstr "还有%{remainingPackagesCount} 个"
-msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability"
-msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities"
-msgstr[0] "%{reportType} %{status} 检测到 %{dismissedCount} 个已忽略的æ¼æ´ž"
-
-msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability for the source branch only"
-msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities for the source branch only"
-msgstr[0] "%{reportType} %{status} æºåˆ†æ”¯æ£€æµ‹åˆ° %{dismissedCount} 个已忽略的æ¼æ´ž"
-
-msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerability"
-msgid_plural "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerabilities"
-msgstr[0] "%{reportType}%{status} 检测到%{fixedCount}个安全æ¼æ´žå·²ä¿®å¤ã€‚"
-
-msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
-msgstr "%{reportType} %{status} 检测到 %{fixedCount} 个已修å¤çš„å’Œ %{dismissedCount} 个已忽略的æ¼æ´ž"
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new vulnerability"
-msgid_plural "ciReport|%{reportType} %{status} detected %{newCount} new vulnerabilities"
-msgstr[0] "%{reportType}%{status} 检测到%{newCount}个新的安全æ¼æ´žã€‚"
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
-msgstr "%{reportType} %{status} 检测到 %{newCount} 个新增的ã€%{fixedCount} 个已修å¤çš„å’Œ %{dismissedCount} 个已忽略的æ¼æ´ž"
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities"
-msgstr "%{reportType} %{status} 检测到 %{newCount} 个新增的和 %{dismissedCount} 个已忽略的æ¼æ´ž"
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities for the source branch only"
-msgstr "%{reportType} %{status} æºåˆ†æ”¯æ£€æµ‹åˆ° %{newCount} 个新增的和 %{dismissedCount} 个已忽略的æ¼æ´ž"
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{fixedCount} fixed vulnerabilities"
-msgstr "%{reportType}%{status}检测到%{newCount}个新的安全æ¼æ´žå’Œ%{fixedCount}个已修å¤æ¼æ´ž"
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} vulnerability for the source branch only"
-msgid_plural "ciReport|%{reportType} %{status} detected %{newCount} vulnerabilities for the source branch only"
-msgstr[0] "%{reportType}%{status}检测到æºåˆ†æ”¯æœ‰%{newCount} 个安全æ¼æ´ž"
-
-msgid "ciReport|%{reportType} %{status} detected no new vulnerabilities"
-msgstr "%{reportType}%{status}未å‘现新的安全æ¼æ´ž"
-
-msgid "ciReport|%{reportType} %{status} detected no vulnerabilities"
-msgstr "%{reportType} %{status} 未å‘现安全æ¼æ´ž"
-
-msgid "ciReport|%{reportType} %{status} detected no vulnerabilities for the source branch only"
-msgstr "%{reportType}%{status}未检测到仅在æºåˆ†æ”¯ä¸Šå­˜åœ¨çš„安全æ¼æ´ž"
-
msgid "ciReport|%{reportType} is loading"
msgstr "%{reportType} 加载中"
@@ -26008,8 +26622,8 @@ msgstr "(正在加载,加载结果时出错)"
msgid "ciReport|All projects"
msgstr "所有项目"
-msgid "ciReport|All report types"
-msgstr "所有报告类型"
+msgid "ciReport|All scanner types"
+msgstr ""
msgid "ciReport|All severities"
msgstr "全部严é‡çº§åˆ«"
@@ -26032,6 +26646,9 @@ msgstr "容器安全扫æ"
msgid "ciReport|Container scanning detects known vulnerabilities in your docker images."
msgstr "容器扫æå¯ä»¥æ£€æµ‹Dockeré•œåƒä¸­ä¸­å·²çŸ¥çš„安全æ¼æ´žã€‚"
+msgid "ciReport|Coverage Fuzzing"
+msgstr ""
+
msgid "ciReport|Create a merge request to implement this solution, or download and apply the patch manually."
msgstr "创建一个åˆå¹¶è¯·æ±‚æ¥æ‰§è¡Œè¿™ä¸ªè§£å†³æ–¹æ¡ˆï¼Œæˆ–者下载并手动应用补ä¸ã€‚"
@@ -26189,6 +26806,9 @@ msgstr ""
msgid "customize"
msgstr "自定义"
+msgid "data"
+msgstr ""
+
msgid "date must not be after 9999-12-31"
msgstr "日期ä¸èƒ½åœ¨9999-12-31之åŽ"
@@ -26257,6 +26877,9 @@ msgstr "æ¡ç›®ä¸èƒ½ä¸ºç©º"
msgid "entries cannot contain HTML tags"
msgstr "æ¡ç›®ä¸èƒ½åŒ…å«HTML标记"
+msgid "epic"
+msgstr ""
+
msgid "error"
msgstr "错误"
@@ -26278,9 +26901,6 @@ msgstr ""
msgid "expires on %{timebox_due_date}"
msgstr ""
-msgid "external_url"
-msgstr ""
-
msgid "failed"
msgstr "已失败"
@@ -26315,9 +26935,6 @@ msgstr "派生此项目"
msgid "from"
msgstr "æ¥è‡ª"
-msgid "geo_node_name"
-msgstr ""
-
msgid "group"
msgstr "群组"
@@ -26339,9 +26956,6 @@ msgstr "此处"
msgid "https://your-bitbucket-server"
msgstr "https://your-bitbucket-server"
-msgid "image"
-msgstr ""
-
msgid "image diff"
msgstr "图åƒå·®å¼‚"
@@ -26484,6 +27098,9 @@ msgstr "已达到%{project_limit}çš„é™åˆ¶"
msgid "load it anyway"
msgstr ""
+msgid "loading"
+msgstr ""
+
msgid "locked by %{path_lock_user_name} %{created_at}"
msgstr "被 %{path_lock_user_name} 在 %{created_at} é”定"
@@ -26573,7 +27190,7 @@ msgid "mrWidget|Approve additionally"
msgstr "å¦å¤–核准"
msgid "mrWidget|Approved by"
-msgstr "批准人:"
+msgstr "核准人:"
msgid "mrWidget|Are you adding technical debt or code vulnerabilities?"
msgstr ""
@@ -26611,9 +27228,6 @@ msgstr "删除æºåˆ†æ”¯"
msgid "mrWidget|Deployment statistics are not available currently"
msgstr "部署统计信æ¯å½“å‰ä¸å¯ç”¨"
-msgid "mrWidget|Detect issues before deployment with a CI pipeline that continuously tests your code. We created a quick guide that will show you how to create one. Make your code more secure and more robust in just a minute."
-msgstr ""
-
msgid "mrWidget|Did not close"
msgstr "未关闭"
@@ -26753,7 +27367,7 @@ msgid "mrWidget|There are merge conflicts"
msgstr "存在åˆå¹¶å†²çª"
msgid "mrWidget|There are unresolved threads. Please resolve these threads"
-msgstr "有未解决的è¯é¢˜ã€‚请先解决这些è¯é¢˜è®¨è®º"
+msgstr "有未解决的主题。请先解决这些主题讨论"
msgid "mrWidget|This feature merges changes from the target branch to the source branch. You cannot use this feature since the source branch is protected."
msgstr "此功能是从目标分支åˆå¹¶åˆ°æºåˆ†æ”¯çš„更改。您无法使用此功能,因为æºåˆ†æ”¯å—到ä¿æŠ¤ã€‚"
@@ -26776,6 +27390,9 @@ msgstr "该项目已存档,ç¦æ­¢å†™å…¥"
msgid "mrWidget|To approve this merge request, please enter your password. This project requires all approvals to be authenticated."
msgstr "è¦æ‰¹å‡†æ­¤åˆå¹¶è¯·æ±‚,请输入您的密ç ã€‚此项目需è¦æ‰€æœ‰æ‰¹å‡†æ‰èƒ½è®¤è¯ã€‚"
+msgid "mrWidget|Use %{linkStart}CI pipelines to test your code%{linkEnd}, simply add a GitLab CI configuration file to your project. It only takes a minute to make your code more secure and robust."
+msgstr ""
+
msgid "mrWidget|When this merge request is ready, remove the WIP: prefix from the title to allow it to be merged"
msgstr "当此åˆå¹¶è¯·æ±‚准备就绪时,删除 “WIP:†å‰ç¼€ä»¥å…许åˆå¹¶"
@@ -26812,6 +27429,9 @@ msgstr "æµæ°´çº¿æˆåŠŸæ—¶å¯åŠ¨åˆå¹¶é˜Ÿåˆ—"
msgid "must be greater than start date"
msgstr "必须大于开始日期"
+msgid "must contain only valid frameworks"
+msgstr ""
+
msgid "my-awesome-group"
msgstr ""
@@ -26824,6 +27444,9 @@ msgstr ""
msgid "needs to be between 10 minutes and 1 month"
msgstr "需è¦åœ¨10分钟到1个月之间"
+msgid "never"
+msgstr ""
+
msgid "never expires"
msgstr "æ°¸ä¸è¿‡æœŸ"
@@ -26851,6 +27474,9 @@ msgstr "通知邮件"
msgid "nounSeries|%{firstItem} and %{lastItem}"
msgstr "%{firstItem} 和 %{lastItem}"
+msgid "nounSeries|%{item}"
+msgstr ""
+
msgid "nounSeries|%{item}, %{nextItem}"
msgstr "%{item}, %{nextItem}"
@@ -26982,6 +27608,9 @@ msgstr "解决了相应的错误并关闭了该议题。"
msgid "revised"
msgstr ""
+msgid "satisfied"
+msgstr ""
+
msgid "score"
msgstr "分"
@@ -27012,6 +27641,9 @@ msgstr "æ— "
msgid "severity|Unknown"
msgstr "未知"
+msgid "should be an array of %{object_name} objects"
+msgstr ""
+
msgid "should be greater than or equal to %{access} inherited membership from group %{group_name}"
msgstr "应该从高于或等于从%{group_name} 群组继承的æˆå‘˜èº«ä»½%{access}"
@@ -27066,10 +27698,13 @@ msgstr ""
msgid "suggestPipeline|2/2: Commit your changes"
msgstr ""
-msgid "suggestPipeline|Commit the changes and your pipeline will automatically run for the first time."
+msgid "suggestPipeline|Choose %{boldStart}Code Quality%{boldEnd} to add a pipeline that tests the quality of your code."
+msgstr ""
+
+msgid "suggestPipeline|The template is ready! You can now commit it to create your first pipeline."
msgstr ""
-msgid "suggestPipeline|We recommend the %{boldStart}Code Quality%{boldEnd} template, which will add a report widget to your Merge Requests. This way you’ll learn about code quality degradations much sooner. %{footerStart} Goodbye technical debt! %{footerEnd}"
+msgid "suggestPipeline|We’re adding a GitLab CI configuration file to add a pipeline to the project. You could create it manually, but we recommend that you start with a GitLab template that works out of the box."
msgstr ""
msgid "syntax is correct"
@@ -27129,6 +27764,9 @@ msgstr "用户å"
msgid "uses Kubernetes clusters to deploy your code!"
msgstr "使用 Kubernetes 集群æ¥éƒ¨ç½²ä»£ç ï¼"
+msgid "v%{version} published %{timeAgo}"
+msgstr ""
+
msgid "verify ownership"
msgstr "验è¯æ‰€æœ‰æƒ"
diff --git a/locale/zh_HK/gitlab.po b/locale/zh_HK/gitlab.po
index 2d0e8b52885..c1e2837a10a 100644
--- a/locale/zh_HK/gitlab.po
+++ b/locale/zh_HK/gitlab.po
@@ -2,7 +2,7 @@ msgid ""
msgstr ""
"Project-Id-Version: gitlab-ee\n"
"Report-Msgid-Bugs-To: \n"
-"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Last-Translator: \n"
"Language-Team: Chinese Traditional, Hong Kong\n"
"Language: zh_HK\n"
"MIME-Version: 1.0\n"
@@ -10,15 +10,17 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=1; plural=0;\n"
"X-Crowdin-Project: gitlab-ee\n"
+"X-Crowdin-Project-ID: 288872\n"
"X-Crowdin-Language: zh-HK\n"
"X-Crowdin-File: /master/locale/gitlab.pot\n"
-"PO-Revision-Date: 2020-06-08 15:12\n"
+"X-Crowdin-File-ID: 6\n"
+"PO-Revision-Date: 2020-07-02 01:21\n"
msgid " %{start} to %{end}"
msgstr ""
msgid " (from %{timeoutSource})"
-msgstr ""
+msgstr " (來自 %{timeoutSource})"
msgid " Collected %{time}"
msgstr ""
@@ -36,10 +38,10 @@ msgid " and"
msgstr " 和"
msgid " and "
-msgstr ""
+msgstr "和"
msgid " and %{sliced}"
-msgstr ""
+msgstr " 和 %{sliced}"
msgid " degraded on %d point"
msgid_plural " degraded on %d points"
@@ -53,19 +55,19 @@ msgid " or "
msgstr "或"
msgid " or <!merge request id>"
-msgstr ""
+msgstr "或 <!åˆä½µè«‹æ±‚ id>"
msgid " or <#issue id>"
msgstr "或 <#issue id>"
msgid " or <&epic id>"
-msgstr ""
+msgstr " 或 <#å²è©© id>"
msgid " or references (e.g. path/to/project!merge_request_id)"
-msgstr ""
+msgstr " 或引用 (例如:專案/路徑!åˆä½µè«‹æ±‚_ID)"
msgid "\"%{path}\" did not exist on \"%{ref}\""
-msgstr ""
+msgstr "「%{ref}ã€ä¸Šæ²’有「%{path}ã€"
msgid "%d URL scanned"
msgid_plural "%d URLs scanned"
@@ -109,14 +111,14 @@ msgstr[0] "è½å¾Œ %d 個æ交"
msgid "%d commit,"
msgid_plural "%d commits,"
-msgstr[0] ""
+msgstr[0] "%d 次æ交,"
msgid "%d commits"
msgstr "%d 個æ交"
msgid "%d contribution"
msgid_plural "%d contributions"
-msgstr[0] ""
+msgstr[0] "%d 個貢ç»"
msgid "%d day"
msgid_plural "%d days"
@@ -144,7 +146,7 @@ msgstr[0] ""
msgid "%d inaccessible merge request"
msgid_plural "%d inaccessible merge requests"
-msgstr[0] ""
+msgstr[0] "%d 個無法存å–çš„åˆä½µè«‹æ±‚"
msgid "%d issue"
msgid_plural "%d issues"
@@ -164,19 +166,23 @@ msgstr[0] "%d 個åˆä½µè«‹æ±‚"
msgid "%d merge request that you don't have access to."
msgid_plural "%d merge requests that you don't have access to."
-msgstr[0] ""
+msgstr[0] "%d 個您無法存å–çš„åˆä½µè«‹æ±‚。"
msgid "%d metric"
msgid_plural "%d metrics"
msgstr[0] "%d 指標"
+msgid "%d milestone"
+msgid_plural "%d milestones"
+msgstr[0] ""
+
msgid "%d minute"
msgid_plural "%d minutes"
-msgstr[0] ""
+msgstr[0] "%d分"
msgid "%d more comment"
msgid_plural "%d more comments"
-msgstr[0] ""
+msgstr[0] "還有 %d 則留言"
msgid "%d personal project will be removed and cannot be restored."
msgid_plural "%d personal projects will be removed and cannot be restored."
@@ -184,15 +190,15 @@ msgstr[0] ""
msgid "%d project"
msgid_plural "%d projects"
-msgstr[0] ""
+msgstr[0] "%d 個專案"
msgid "%d request with warnings"
msgid_plural "%d requests with warnings"
-msgstr[0] ""
+msgstr[0] "%d 個有警告的請求"
msgid "%d second"
msgid_plural "%d seconds"
-msgstr[0] ""
+msgstr[0] "%d秒"
msgid "%d shard selected"
msgid_plural "%d shards selected"
@@ -200,16 +206,12 @@ msgstr[0] ""
msgid "%d tag"
msgid_plural "%d tags"
-msgstr[0] ""
+msgstr[0] "%d 個標籤"
msgid "%d unresolved thread"
msgid_plural "%d unresolved threads"
msgstr[0] ""
-msgid "%d url scanned"
-msgid_plural "%d urls scanned"
-msgstr[0] ""
-
msgid "%d vulnerability"
msgid_plural "%d vulnerabilities"
msgstr[0] ""
@@ -226,7 +228,7 @@ msgid "%{actionText} & %{openOrClose} %{noteable}"
msgstr "%{actionText} 和 %{openOrClose} %{noteable}"
msgid "%{authorsName}'s thread"
-msgstr ""
+msgstr "%{authorsName} 的話題"
msgid "%{commit_author_link} authored %{commit_timeago}"
msgstr "ç”± %{commit_author_link} æ交於 %{commit_timeago}"
@@ -238,10 +240,10 @@ msgid "%{containerScanningLinkStart}Container Scanning%{containerScanningLinkEnd
msgstr ""
msgid "%{cores} cores"
-msgstr ""
+msgstr "%{cores} 核心"
msgid "%{count} LOC/commit"
-msgstr ""
+msgstr "%{count} 程å¼ç¢¼è¡Œæ•¸/æ交"
msgid "%{count} approval required from %{name}"
msgid_plural "%{count} approvals required from %{name}"
@@ -251,7 +253,7 @@ msgid "%{count} approvals from %{name}"
msgstr "éœ€è¦ %{count} 個由 %{name} 的批准"
msgid "%{count} files touched"
-msgstr ""
+msgstr "å·²é¸æ“‡ %{count} 個檔案"
msgid "%{count} more"
msgstr "還有 %{count} 項"
@@ -261,7 +263,7 @@ msgstr "%{count} å以上的指派人"
msgid "%{count} more release"
msgid_plural "%{count} more releases"
-msgstr[0] ""
+msgstr[0] "還有 %{count} 個發布版"
msgid "%{count} of %{required} approvals from %{name}"
msgstr "%{name} 的 %{count}/%{required} 個批准"
@@ -278,31 +280,37 @@ msgid_plural "%{count} pending comments"
msgstr[0] "%{count} 個待處ç†è©•è«–"
msgid "%{count} related %{pluralized_subject}: %{links}"
+msgstr "%{count} 個相關的 %{pluralized_subject}:%{links}"
+
+msgid "%{dashboard_path} could not be found."
msgstr ""
msgid "%{days} days until tags are automatically removed"
+msgstr "%{days}天,直到標籤被自動刪除"
+
+msgid "%{deployLinkStart}Use a template to deploy to ECS%{deployLinkEnd}, or use a docker image to %{commandsLinkStart}run AWS commands in GitLab CI/CD%{commandsLinkEnd}."
msgstr ""
msgid "%{description}- Sentry event: %{errorUrl}- First seen: %{firstSeen}- Last seen: %{lastSeen} %{countLabel}: %{count}%{userCountLabel}: %{userCount}"
-msgstr ""
+msgstr "%{description}- Sentry 事件:%{errorUrl}- 首次出ç¾ï¼š%{firstSeen}- 最後出ç¾ï¼š%{lastSeen} %{countLabel}:%{count}%{userCountLabel}:%{userCount}"
msgid "%{duration}ms"
-msgstr ""
+msgstr "%{duration} 毫秒"
msgid "%{edit_in_new_fork_notice} Try to cherry-pick this commit again."
-msgstr ""
+msgstr "%{edit_in_new_fork_notice} è«‹å†æ¬¡å˜—試æ€é¸æ­¤æ交。"
msgid "%{edit_in_new_fork_notice} Try to create a new directory again."
-msgstr ""
+msgstr "%{edit_in_new_fork_notice} è«‹å†æ¬¡å˜—試建立新目錄。"
msgid "%{edit_in_new_fork_notice} Try to revert this commit again."
-msgstr ""
+msgstr "%{edit_in_new_fork_notice} è«‹å†æ¬¡å˜—試還原此æ交。"
msgid "%{edit_in_new_fork_notice} Try to upload a file again."
-msgstr ""
+msgstr "%{edit_in_new_fork_notice} è«‹å†æ¬¡å˜—試上傳檔案。"
msgid "%{extra} more downstream pipelines"
-msgstr ""
+msgstr "還有 %{extra} 個下游æµæ°´ç·š"
msgid "%{filePath} deleted"
msgstr "已刪除 %{filePath}"
@@ -314,33 +322,39 @@ msgid "%{firstMilestoneName} + %{numberOfOtherMilestones} more"
msgstr ""
msgid "%{global_id} is not a valid id for %{expected_type}."
-msgstr ""
+msgstr "%{global_id} ä¸æ˜¯æœ‰æ•ˆçš„ %{expected_type} ID。"
msgid "%{group_docs_link_start}Groups%{group_docs_link_end} allow you to manage and collaborate across multiple projects. Members of a group have access to all of its projects."
msgstr "%{group_docs_link_start}群組%{group_docs_link_end}å…許您管ç†ã€å”作多個項目。群組的æˆå“¡å¯ä»¥è¨ªå•ç¾¤çµ„下的所有項目。"
msgid "%{group_name} uses group managed accounts. You need to create a new GitLab account which will be managed by %{group_name}."
-msgstr ""
+msgstr "%{group_name} 使用群組管ç†å¸³æˆ¶ã€‚您需è¦å»ºç«‹ä¸€å€‹æ–°çš„ GitLab 帳戶,該帳戶將由 %{group_name} 群組管ç†ã€‚"
msgid "%{host} sign-in from new location"
msgstr ""
-msgid "%{icon}You are about to add %{usersTag} people to the discussion. Proceed with caution."
+msgid "%{icon}You are about to add %{usersTag} people to the discussion. They will all receive a notification."
+msgstr ""
+
+msgid "%{integrations_link_start}Integrations%{link_end} enable you to make third-party applications part of your GitLab workflow. If the available integrations don't meet your needs, consider using a %{webhooks_link_start}webhook%{link_end}."
msgstr ""
msgid "%{issuableType} will be removed! Are you sure?"
msgstr "將會移除 %{issuableType}ï¼ç¢ºå®šï¼Ÿ"
-msgid "%{issuesSize} issues"
+msgid "%{issuesCount} issues in this group"
msgstr ""
+msgid "%{issuesSize} issues"
+msgstr "%{issuesSize} 個議題"
+
msgid "%{issuesSize} issues with a limit of %{maxIssueCount}"
-msgstr ""
+msgstr "%{issuesSize} å€‹è­°é¡Œï¼Œä¸Šé™ %{maxIssueCount} 個。"
msgid "%{labelStart}Class:%{labelEnd} %{class}"
msgstr ""
-msgid "%{labelStart}Confidence:%{labelEnd} %{confidence}"
+msgid "%{labelStart}Evidence:%{labelEnd} %{evidence}"
msgstr ""
msgid "%{labelStart}File:%{labelEnd} %{file}"
@@ -358,20 +372,23 @@ msgstr ""
msgid "%{labelStart}Report Type:%{labelEnd} %{reportType}"
msgstr ""
+msgid "%{labelStart}Scanner:%{labelEnd} %{scanner}"
+msgstr ""
+
msgid "%{labelStart}Severity:%{labelEnd} %{severity}"
msgstr ""
msgid "%{label_for_message} unavailable"
-msgstr ""
+msgstr "%{label_for_message} 無法使用"
msgid "%{lets_encrypt_link_start}Let's Encrypt%{lets_encrypt_link_end} is a free, automated, and open certificate authority (CA), that give digital certificates in order to enable HTTPS (SSL/TLS) for websites."
-msgstr ""
+msgstr "%{lets_encrypt_link_start}Let's Encrypt%{lets_encrypt_link_end} 是一個å…è²»ã€è‡ªå‹•åŒ–且開放的憑證頒發機構 (CA),æ供數ä½æ†‘證以便為網站啟用 HTTPS(SSL/TLS)。"
msgid "%{level_name} is not allowed in a %{group_level_name} group."
-msgstr ""
+msgstr "%{group_level_name} 群組ä¸å…許 %{level_name} 。"
msgid "%{level_name} is not allowed since the fork source project has lower visibility."
-msgstr ""
+msgstr "由於分å‰çš„æºé …ç›®å¯è¦‹æ€§è¼ƒä½Žï¼Œå› æ­¤ä¸å…許使用 %{level_name}。"
msgid "%{lineOneStart}Drag and drop to upload your designs%{lineOneEnd} or %{linkStart}click to upload%{linkEnd}."
msgstr ""
@@ -380,13 +397,10 @@ msgid "%{link_start}Learn more%{link_end} about what information is shared with
msgstr ""
msgid "%{link_start}Read more%{link_end} about role permissions"
-msgstr ""
-
-msgid "%{link} can be used for binding events when something is happening within the project."
-msgstr ""
+msgstr "%{link_start}了解更多%{link_end} 關於角色權é™"
msgid "%{listToShow}, and %{awardsListLength} more."
-msgstr ""
+msgstr "%{listToShow},還有 %{awardsListLength} 個。"
msgid "%{loadingIcon} Started"
msgstr "%{loadingIcon} 開始"
@@ -398,10 +412,10 @@ msgid "%{markdownDocsLinkStart}Markdown%{markdownDocsLinkEnd} and %{quickActions
msgstr ""
msgid "%{mergeLength}/%{usersLength} can merge"
-msgstr ""
+msgstr "%{mergeLength}/%{usersLength} å¯ä»¥åˆä½µ"
msgid "%{mrText}, this issue will be closed automatically."
-msgstr ""
+msgstr "%{mrText},此議題將自動關閉。"
msgid "%{name_with_link} has %{percent} or less Shared Runner Pipeline minutes remaining. Once it runs out, no new jobs or pipelines in its projects will run."
msgstr ""
@@ -413,9 +427,12 @@ msgid "%{namespace_name} is now read-only. You cannot: %{base_message}"
msgstr ""
msgid "%{name} contained %{resultsString}"
-msgstr ""
+msgstr "%{name} åŒ…å« %{resultsString}"
msgid "%{name} found %{resultsString}"
+msgstr "%{name} 找到了 %{resultsString}"
+
+msgid "%{name} is already being used for another emoji"
msgstr ""
msgid "%{name} is scheduled for %{action}"
@@ -441,10 +458,10 @@ msgid "%{openOrClose} %{noteable}"
msgstr "%{openOrClose} %{noteable}"
msgid "%{openedEpics} open, %{closedEpics} closed"
-msgstr ""
+msgstr "%{openedEpics} 個開放,%{closedEpics} 個關閉"
msgid "%{openedIssues} open, %{closedIssues} closed"
-msgstr ""
+msgstr "%{openedIssues} 個開放,%{closedIssues} 個關閉"
msgid "%{percentage}%% weight completed"
msgstr ""
@@ -453,7 +470,7 @@ msgid "%{percent}%% complete"
msgstr "%{percent}%% 完æˆ"
msgid "%{percent}%{percentSymbol} complete"
-msgstr ""
+msgstr "%{percent}%{percentSymbol} 完æˆ"
msgid "%{placeholder} is not a valid color scheme"
msgstr ""
@@ -462,93 +479,117 @@ msgid "%{placeholder} is not a valid theme"
msgstr ""
msgid "%{primary} (%{secondary})"
+msgstr "%{primary} (%{secondary})"
+
+msgid "%{ref} cannot be added: %{error}"
msgstr ""
msgid "%{releases} release"
msgid_plural "%{releases} releases"
-msgstr[0] ""
+msgstr[0] "%{releases} 個發布版"
msgid "%{remaining_approvals} left"
msgstr ""
-msgid "%{retryButtonStart}Try again%{retryButtonEnd} or %{newFileButtonStart}attach a new file%{newFileButtonEnd}"
+msgid "%{reportType} %{status} detected %{critical} critical and %{high} high severity vulnerabilities out of %{total}."
+msgstr ""
+
+msgid "%{reportType} %{status} detected %{critical} critical and %{high} high severity vulnerabilities."
msgstr ""
-msgid "%{screenreaderOnlyStart}Keyboard shorcuts%{screenreaderOnlyEnd} Disabled"
+msgid "%{reportType} %{status} detected %{critical} critical severity vulnerabilities out of %{total}."
msgstr ""
-msgid "%{screenreaderOnlyStart}Keyboard shorcuts%{screenreaderOnlyEnd} Enabled"
+msgid "%{reportType} %{status} detected %{critical} critical severity vulnerability."
+msgid_plural "%{reportType} %{status} detected %{critical} critical severity vulnerabilities."
+msgstr[0] ""
+
+msgid "%{reportType} %{status} detected %{high} high severity vulnerabilities out of %{total}."
+msgstr ""
+
+msgid "%{reportType} %{status} detected %{high} high severity vulnerability."
+msgid_plural "%{reportType} %{status} detected %{high} high severity vulnerabilities."
+msgstr[0] ""
+
+msgid "%{reportType} %{status} detected %{other} vulnerability."
+msgid_plural "%{reportType} %{status} detected %{other} vulnerabilities."
+msgstr[0] ""
+
+msgid "%{reportType} %{status} detected no new vulnerabilities."
+msgstr ""
+
+msgid "%{retryButtonStart}Try again%{retryButtonEnd} or %{newFileButtonStart}attach a new file%{newFileButtonEnd}"
msgstr ""
msgid "%{service_title} %{message}."
msgstr ""
msgid "%{size} GiB"
-msgstr ""
+msgstr "%{size} GiB"
msgid "%{size} KiB"
-msgstr ""
+msgstr "%{size} KiB"
msgid "%{size} MiB"
-msgstr ""
+msgstr "%{size} MiB"
msgid "%{size} bytes"
-msgstr ""
+msgstr "%{size} ä½å…ƒçµ„"
msgid "%{spammable_titlecase} was submitted to Akismet successfully."
-msgstr ""
+msgstr "%{spammable_titlecase} æˆåŠŸåœ°æ交給 Akismet。"
msgid "%{spanStart}at line%{spanEnd} %{errorLine}%{errorColumn}"
-msgstr ""
+msgstr "%{spanStart}在行號%{spanEnd} %{errorLine}%{errorColumn}"
msgid "%{spanStart}in%{spanEnd} %{errorFn}"
-msgstr ""
+msgstr "%{spanStart}æ–¼%{spanEnd} %{errorFn}"
msgid "%{start} to %{end}"
-msgstr ""
+msgstr "%{start} 到 %{end}"
msgid "%{state} epics"
-msgstr ""
+msgstr "%{state} å²è©©"
msgid "%{strongStart}Note:%{strongEnd} Once a custom stage has been added you can re-order stages by dragging them into the desired position."
msgstr ""
msgid "%{strong_start}%{branch_count}%{strong_end} Branch"
msgid_plural "%{strong_start}%{branch_count}%{strong_end} Branches"
-msgstr[0] ""
+msgstr[0] "%{strong_start}%{branch_count}%{strong_end} 個分支"
msgid "%{strong_start}%{commit_count}%{strong_end} Commit"
msgid_plural "%{strong_start}%{commit_count}%{strong_end} Commits"
-msgstr[0] ""
+msgstr[0] "%{strong_start}%{commit_count}%{strong_end} 次æ交"
msgid "%{strong_start}%{human_size}%{strong_end} Files"
-msgstr ""
+msgstr "%{strong_start}%{human_size}%{strong_end} 檔案"
msgid "%{strong_start}%{human_size}%{strong_end} Storage"
msgstr ""
msgid "%{strong_start}%{release_count}%{strong_end} Release"
msgid_plural "%{strong_start}%{release_count}%{strong_end} Releases"
-msgstr[0] ""
+msgstr[0] "%{strong_start}%{release_count}%{strong_end} 個發行版"
msgid "%{strong_start}%{tag_count}%{strong_end} Tag"
msgid_plural "%{strong_start}%{tag_count}%{strong_end} Tags"
-msgstr[0] ""
+msgstr[0] "%{strong_start}%{tag_count}%{strong_end} 個標籤"
msgid "%{tabname} changed"
-msgstr ""
+msgstr "%{tabname} 已變更"
msgid "%{tags} tag per image name"
-msgstr ""
+msgstr "æ¯å€‹æ˜ åƒæª”å稱具有 %{tags} 個標籤"
msgid "%{tags} tags per image name"
-msgstr ""
+msgstr "æ¯å€‹æ˜ åƒæª”å稱具有 %{tags} 個標籤"
msgid "%{tag}-%{evidence}-%{filename}"
msgstr ""
msgid "%{template_project_id} is unknown or invalid"
-msgstr ""
+msgstr "%{template_project_id} 為未知或無效"
msgid "%{text} %{files}"
msgid_plural "%{text} %{files} files"
@@ -561,7 +602,7 @@ msgid "%{timebox_name} should belong either to a project or a group."
msgstr ""
msgid "%{title} %{operator} %{threshold}"
-msgstr ""
+msgstr "%{title} %{operator} %{threshold}"
msgid "%{title} changes"
msgstr "%{title} 變更"
@@ -569,72 +610,81 @@ msgstr "%{title} 變更"
msgid "%{token}..."
msgstr ""
-msgid "%{totalWeight} total weight"
+msgid "%{totalCpu} (%{freeSpacePercentage}%{percentSymbol} free)"
msgstr ""
-msgid "%{total} open issue weight"
+msgid "%{totalMemory} (%{freeSpacePercentage}%{percentSymbol} free)"
msgstr ""
+msgid "%{totalWeight} total weight"
+msgstr "%{totalWeight} 總權é‡"
+
+msgid "%{total} open issue weight"
+msgstr "%{total} 開放議題權é‡"
+
msgid "%{total} open issues"
-msgstr ""
+msgstr "%{total} 個開啟中的議題"
msgid "%{usage_ping_link_start}Learn more%{usage_ping_link_end} about what information is shared with GitLab Inc."
msgstr "%{usage_ping_link_start}了解更多%{usage_ping_link_end} 關於與GitLab Inc.共享的信æ¯"
msgid "%{userName} (cannot merge)"
-msgstr ""
+msgstr "%{userName} (無法åˆä½µ)"
msgid "%{userName}'s avatar"
-msgstr ""
+msgstr "%{userName} 的大頭貼"
msgid "%{user_name} profile page"
-msgstr ""
+msgstr "%{user_name} 的個人資料"
msgid "%{username}'s avatar"
-msgstr ""
+msgstr "%{username} 的大頭貼"
msgid "%{value} s"
msgstr ""
msgid "%{verb} %{time_spent_value} spent time."
+msgstr "%{verb} 耗時 %{time_spent_value}"
+
+msgid "%{webhooks_link_start}%{webhook_type}%{link_end} enable you to send notifications to web applications in response to events in a group or project."
msgstr ""
-msgid "'%{level}' is not a valid visibility level"
+msgid "%{webhooks_link_start}%{webhook_type}%{link_end} enable you to send notifications to web applications in response to events in a group or project. We recommend using an %{integrations_link_start}integration%{link_end} in preference to a webhook."
msgstr ""
+msgid "'%{level}' is not a valid visibility level"
+msgstr "「%{level}ã€ä¸æ˜¯æœ‰æ•ˆçš„å¯è¦‹æ€§ç´šåˆ¥"
+
msgid "'%{name}' stage already exists"
msgstr ""
msgid "'%{source}' is not a import source"
-msgstr ""
+msgstr "「%{source}ã€ä¸æ˜¯ä¸€å€‹åŒ¯å…¥ä¾†æº"
msgid "'%{template_name}' is unknown or invalid"
-msgstr ""
+msgstr "「%{template_name}ã€æœªçŸ¥æˆ–無效"
msgid "(%d closed)"
msgid_plural "(%d closed)"
-msgstr[0] ""
+msgstr[0] "(%d 個已關閉)"
msgid "(%{linkStart}Cron syntax%{linkEnd})"
msgstr ""
msgid "(%{mrCount} merged)"
-msgstr ""
+msgstr "(%{mrCount} å·²åˆä½µ)"
msgid "(No changes)"
-msgstr ""
-
-msgid "(Show all)"
-msgstr ""
+msgstr "(無變更)"
msgid "(check progress)"
-msgstr ""
+msgstr "(檢查進度)"
msgid "(external source)"
-msgstr ""
+msgstr "(外部資æº)"
msgid "(removed)"
-msgstr ""
+msgstr "(已刪除)"
msgid "(revoked)"
msgstr ""
@@ -643,7 +693,7 @@ msgid "*"
msgstr ""
msgid "+ %{amount} more"
-msgstr ""
+msgstr "+ 其餘 %{amount} 項"
msgid "+ %{count} more"
msgstr "+ %{count} 以上"
@@ -652,7 +702,7 @@ msgid "+ %{moreCount} more"
msgstr "+ %{moreCount} 更多"
msgid "+ %{numberOfHiddenAssignees} more"
-msgstr ""
+msgstr "+ 其餘 %{numberOfHiddenAssignees} 項"
msgid "+%d more"
msgid_plural "+%d more"
@@ -669,7 +719,7 @@ msgstr "或"
msgid "- Event"
msgid_plural "- Events"
-msgstr[0] ""
+msgstr[0] "- 事件"
msgid "- Runner is active and can process any new jobs"
msgstr "- 執行器為啟用狀態,並且å¯ä»¥è™•ç†æ–°çš„任何工作。"
@@ -679,7 +729,7 @@ msgstr "- 執行器為暫åœç‹€æ…‹ï¼Œä¸”å°‡ä¸æœƒæŽ¥å—任何新的工作"
msgid "- User"
msgid_plural "- Users"
-msgstr[0] ""
+msgstr[0] "- 使用者"
msgid "- of - weight completed"
msgstr ""
@@ -688,6 +738,9 @@ msgid "- show less"
msgstr "顯示較少"
msgid "0 for unlimited"
+msgstr "0 表示無é™åˆ¶"
+
+msgid "0 for unlimited, only effective with remote storage enabled."
msgstr ""
msgid "1 %{type} addition"
@@ -704,15 +757,15 @@ msgstr[0] "%d 天"
msgid "1 closed issue"
msgid_plural "%{issues} closed issues"
-msgstr[0] ""
+msgstr[0] "%{issues} 個已關閉的議題"
msgid "1 closed merge request"
msgid_plural "%{merge_requests} closed merge requests"
-msgstr[0] ""
+msgstr[0] "%{merge_requests} 個已關閉的åˆä½µè«‹æ±‚"
msgid "1 day"
msgid_plural "%d days"
-msgstr[0] ""
+msgstr[0] "%d天"
msgid "1 group"
msgid_plural "%d groups"
@@ -720,22 +773,22 @@ msgstr[0] "%d 個群組"
msgid "1 hour"
msgid_plural "%d hours"
-msgstr[0] ""
+msgstr[0] "%d時"
msgid "1 merged merge request"
msgid_plural "%{merge_requests} merged merge requests"
-msgstr[0] ""
+msgstr[0] "%{merge_requests} 個已åˆä½µçš„åˆä½µè«‹æ±‚"
msgid "1 minute"
msgid_plural "%d minutes"
-msgstr[0] ""
+msgstr[0] "%d分"
msgid "1 month"
msgstr ""
msgid "1 open issue"
msgid_plural "%{issues} open issues"
-msgstr[0] ""
+msgstr[0] "%{issues} 個開放議題"
msgid "1 open merge request"
msgid_plural "%{merge_requests} open merge requests"
@@ -757,22 +810,22 @@ msgid "1 week"
msgstr "1 星期"
msgid "1-9 contributions"
-msgstr ""
+msgstr "1-9 é …è²¢ç»"
msgid "10-19 contributions"
-msgstr ""
+msgstr "10-19 é …è²¢ç»"
msgid "1st contribution!"
msgstr "第一個貢ç»!"
msgid "20-29 contributions"
-msgstr ""
+msgstr "20-29 é …è²¢ç»"
msgid "2FA"
msgstr "é›™é‡èªè­‰"
msgid "2FADevice|Registered On"
-msgstr ""
+msgstr "註冊於"
msgid "3 days"
msgstr "3 天"
@@ -784,7 +837,7 @@ msgid "30 minutes"
msgstr "30 分é˜"
msgid "30+ contributions"
-msgstr ""
+msgstr "è¶…éŽ 30 é …è²¢ç»"
msgid "403|Please contact your GitLab administrator to get permission."
msgstr "403|è«‹å‘ä½ çš„ GitLab 管ç†å“¡ç”³è«‹ä½¿ç”¨æ¬Šé™ã€‚"
@@ -808,7 +861,7 @@ msgid ":%{startLine} to %{endLine}"
msgstr ""
msgid "< 1 hour"
-msgstr ""
+msgstr "< 1 å°æ™‚"
msgid "<code>\"johnsmith@example.com\": \"@johnsmith\"</code> will add \"By <a href=\"#\">@johnsmith</a>\" to all issues and comments originally created by johnsmith@example.com, and will set <a href=\"#\">@johnsmith</a> as the assignee on all issues originally assigned to johnsmith@example.com."
msgstr "<code>\"johnsmith@example.com\": \"@johnsmith\"</code> 將會在所有原本由 johnsmith@example.com 建立的議題和留言中加上「來自 <a href=\"#\">@johnsmith</a>ã€ä¸¦å°‡åŽŸæœ¬åˆ†é…給 johnsmith@example.com 的所有議題設定 <a href=\"#\">@johnsmith</a> 為被指派人。"
@@ -820,16 +873,22 @@ msgid "<code>\"johnsmith@example.com\": \"johnsm...@example.com\"</code> will ad
msgstr "<code>\"johnsmith@example.com\": \"johnsm...@example.com\"</code> 將會在所有原本由 johnsmith@example.com 建立的議題和留言中加上「來自 johnsm...@example.comã€ã€‚é›»å­ä¿¡ç®±ä½å€æˆ–使用者å稱將å—é®è”½ï¼Œä¿éšœç”¨å®¶ç§éš±ã€‚"
msgid "<code>\"johnsmith@example.com\": \"johnsmith@example.com\"</code> will add \"By <a href=\"#\">johnsmith@example.com</a>\" to all issues and comments originally created by johnsmith@example.com. By default, the email address or username is masked to ensure the user's privacy. Use this option if you want to show the full email address."
+msgstr "<code>\"johnsmith@example.com\": \"johnsmith@example.com\"</code> 將會把「由 <a href=\"#\">@johnsmith</a>ã€åŠ å…¥åˆ°åŽŸæœ¬ç”± johnsmith@example.com 建立的所有議題和留言中。為ä¿è­·ä½¿ç”¨è€…çš„éš±ç§ï¼Œé›»å­éƒµä»¶åœ°å€æˆ–使用者å稱é è¨­æœƒè¢«é®ä½ã€‚如需顯示完整郵件地å€ï¼Œå¯ä½¿ç”¨æ­¤é¸é …。"
+
+msgid "<code>Masked</code> to prevent the values from being displayed in job logs (must match certain regexp requirements)."
+msgstr ""
+
+msgid "<code>Protected</code> to expose them to protected branches or tags only."
msgstr ""
msgid "<namespace / project>"
msgstr ""
msgid "<no name set>"
-msgstr ""
+msgstr "<未設定å稱>"
msgid "<no scopes selected>"
-msgstr ""
+msgstr "<未é¸æ“‡ç¯„åœ>"
msgid "<project name>"
msgstr ""
@@ -844,49 +903,52 @@ msgid "<strong>Deletes</strong> source branch"
msgstr "<strong>刪除</strong>來æºåˆ†æ”¯"
msgid "A 'Runner' is a process which runs a job. You can set up as many Runners as you need."
-msgstr ""
+msgstr "「執行器ã€æ˜¯ä¸€å€‹åŸ·è¡Œä½œæ¥­çš„行程。您å¯ä»¥æ ¹æ“šéœ€è¦é…置任æ„個執行器。"
msgid "A .NET Core console application template, customizable for any .NET Core project"
-msgstr ""
-
-msgid "A DAG must have two dependent jobs to be visualized on this tab."
-msgstr ""
+msgstr ".NET Core 控制å°æ‡‰ç”¨ç¨‹å¼ç¯„本,å¯é‡å°ä»»ä½• .NET Core 專案自訂"
msgid "A GitBook site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
-msgstr ""
+msgstr "一個 GitBook ç«™å°ï¼Œä½¿ç”¨ Netlify 代替 GitLab çš„ CI/CD,但ä»å…·æœ‰æ‰€æœ‰å…¶ä»–主è¦çš„ GitLab 功能。"
msgid "A Hexo site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
-msgstr ""
+msgstr "一個 Hexo ç«™å°ï¼Œä½¿ç”¨ Netlify 代替 GitLab çš„ CI/CD,但ä»å…·æœ‰æ‰€æœ‰å…¶ä»–主è¦çš„ GitLab 功能。"
msgid "A Hugo site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
-msgstr ""
+msgstr "一個 Hugo ç«™å°ï¼Œä½¿ç”¨ Netlify 代替 GitLab çš„ CI/CD,但ä»å…·æœ‰æ‰€æœ‰å…¶ä»–主è¦çš„ GitLab 功能。"
msgid "A Jekyll site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
-msgstr ""
+msgstr "一個 Jekyll ç«™å°ï¼Œä½¿ç”¨ Netlify 代替 GitLab çš„ CI/CD,但ä»å…·æœ‰æ‰€æœ‰å…¶ä»–主è¦çš„ GitLab 功能。"
msgid "A Let's Encrypt SSL certificate can not be obtained until your domain is verified."
-msgstr ""
+msgstr "在網域驗證之å‰ï¼Œæˆ‘們無法å–å¾— Encrype SSL 憑證。"
msgid "A Let's Encrypt account will be configured for this GitLab installation using your email address. You will receive emails to warn of expiring certificates."
-msgstr ""
+msgstr "將會使用您的電å­ä¿¡ç®±ä½å€è¨­å®šæ­¤ GitLab 實體的 Let's Encrypt 帳戶。您會收到警告您憑證å³å°‡åˆ°æœŸçš„é›»å­éƒµä»¶ã€‚"
msgid "A basic page and serverless function that uses AWS Lambda, AWS API Gateway, and GitLab Pages"
-msgstr ""
+msgstr "使用 AWS Lambdaã€AWS API é–˜é“åŠ GitLab Pages 的基本é é¢å’Œç„¡ä¼ºæœå™¨åŠŸèƒ½"
msgid "A complete DevOps platform"
msgstr ""
msgid "A default branch cannot be chosen for an empty project."
-msgstr ""
+msgstr "無法設定空專案的é è¨­åˆ†æ”¯ã€‚"
msgid "A deleted user"
+msgstr "已刪除的使用者"
+
+msgid "A file has been changed."
msgstr ""
-msgid "A file with '%{file_name}' already exists in %{branch} branch"
+msgid "A file was not found."
msgstr ""
+msgid "A file with '%{file_name}' already exists in %{branch} branch"
+msgstr "「%{file_name}ã€æª”å已經在 %{branch} 分支"
+
msgid "A fork is a copy of a project.<br />Forking a repository allows you to make changes without affecting the original project."
-msgstr ""
+msgstr "分å‰æ˜¯å°ˆæ¡ˆçš„複本。<br />分å‰ç‰ˆæœ¬åº«è®“您在ä¸å½±éŸ¿åŽŸå§‹å°ˆæ¡ˆçš„情æ³ä¸‹ï¼Œé€²è¡Œè®Šæ›´ã€‚"
msgid "A group is a collection of several projects"
msgstr ""
@@ -895,34 +957,34 @@ msgid "A group represents your organization in GitLab. Groups allow you to manag
msgstr ""
msgid "A member of the abuse team will review your report as soon as possible."
-msgstr ""
+msgstr "濫用審查團隊æˆå“¡å°‡æœƒå„˜å¿«æª¢é–±æ‚¨çš„報告。"
msgid "A merge request approval is required when a security report contains a new vulnerability of high, critical, or unknown severity."
-msgstr ""
+msgstr "當安全報告有高ã€åš´é‡æˆ–未知嚴é‡æ€§çš„æ–°æ¼æ´žæ™‚,需è¦å…ˆæ ¸å‡†åˆä½µè«‹æ±‚。"
msgid "A merge request approval is required when the license compliance report contains a blacklisted license."
-msgstr ""
+msgstr "當授權æ¢æ¬¾åˆè¦æ€§å ±å‘ŠåŒ…å«æœ‰åœ¨é»‘å單內的授權æ¢æ¬¾æ™‚,需è¦å…ˆæ ¸å‡†åˆä½µè«‹æ±‚。"
msgid "A new Auto DevOps pipeline has been created, go to %{pipelines_link_start}Pipelines page%{pipelines_link_end} for details"
-msgstr ""
+msgstr "已建立新的自動 DevOps æµæ°´ç·šï¼Œè«‹å‰å¾€ %{pipelines_link_start}æµæ°´ç·šé é¢%{pipelines_link_end} 檢視詳細訊æ¯ã€‚"
msgid "A new Release %{tag} for %{name} was published. Visit the %{release_link_start}Releases page%{release_link_end} to read more about it."
-msgstr ""
+msgstr "%{name} 的新版本 %{tag} å·²ç¶“ç™¼å¸ƒã€‚è«‹å­˜å– %{release_link_start}發布版é é¢%{release_link_end} 了解更多資訊。"
msgid "A new Release %{tag} for %{name} was published. Visit the Releases page to read more about it:"
-msgstr ""
+msgstr "%{name} 的新版本 %{tag} 已經發布。請存å–發布版é é¢äº†è§£æ›´å¤šè³‡è¨Šã€‚"
msgid "A new branch will be created in your fork and a new merge request will be started."
msgstr "將會å†å‰µå»ºä¸€å€‹æ–°çš„分支,並建立一個新的åˆä½µè«‹æ±‚。"
msgid "A new impersonation token has been created."
-msgstr ""
+msgstr "已建立新的身份模擬權æ–。"
msgid "A plain HTML site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
-msgstr ""
+msgstr "一個純 HTML ç«™å°ï¼Œä½¿ç”¨ Netlify 代替 GitLab çš„ CI/CD,但ä»å…·æœ‰æ‰€æœ‰å…¶ä»–主è¦çš„ GitLab 功能。"
msgid "A project boilerplate for Salesforce App development with Salesforce Developer tools."
-msgstr ""
+msgstr "使用 Salesforce 開發者工具開發 Salesforce 應用程å¼çš„專案範本。"
msgid "A project containing issues for each audit inquiry in the HIPAA Audit Protocol published by the U.S. Department of Health & Human Services"
msgstr ""
@@ -931,16 +993,16 @@ msgid "A project is where you house your files (repository), plan your work (iss
msgstr "一個專案æ供了以下功能,存放你的文件(存儲庫),計劃你的工作(è­°é¡Œ),並發布你的文件(維基), %{among_other_things_link}。"
msgid "A ready-to-go template for use with Android apps."
-msgstr ""
+msgstr "é©ç”¨æ–¼ Android 應用程å¼ï¼Œæº–備就緒的範本。"
msgid "A ready-to-go template for use with iOS Swift apps."
-msgstr ""
+msgstr "é©ç”¨æ–¼ iOS Swift 應用程å¼ï¼Œæº–備就緒的範本。"
msgid "A regular expression that will be used to find the test coverage output in the job log. Leave blank to disable"
-msgstr ""
+msgstr "用來在作業記錄中尋找測試覆蓋率輸出的正è¦è¡¨ç¤ºå¼ã€‚空白則åœç”¨"
msgid "A secure token that identifies an external storage request."
-msgstr ""
+msgstr "一個用於識別外部儲存請求的安全權æ–。"
msgid "A sign-in to your account has been made from the following IP address: %{ip}"
msgstr ""
@@ -948,7 +1010,7 @@ msgstr ""
msgid "A subscription will trigger a new pipeline on the default branch of this project when a pipeline successfully completes for a new tag on the %{default_branch_docs} of the subscribed project."
msgstr ""
-msgid "A terraform report was generated in your pipelines."
+msgid "A suggestion is not applicable."
msgstr ""
msgid "A user with write access to the source branch selected this option"
@@ -958,34 +1020,34 @@ msgid "ACTION REQUIRED: Something went wrong while obtaining the Let's Encrypt c
msgstr ""
msgid "API Token"
-msgstr ""
+msgstr "API 權æ–"
msgid "AWS Access Key"
-msgstr ""
+msgstr "AWS å­˜å–金鑰"
msgid "AWS Access Key. Only required if not using role instance credentials"
-msgstr ""
+msgstr "AWS å­˜å–金鑰。åªåœ¨ä¸ä½¿ç”¨è§’色實體憑證時需è¦"
msgid "AWS Secret Access Key"
-msgstr ""
+msgstr "AWS 密碼存å–金鑰"
msgid "AWS Secret Access Key. Only required if not using role instance credentials"
-msgstr ""
+msgstr "AWS 密碼存å–金鑰。åªåœ¨ä¸ä½¿ç”¨è§’色實體憑證時需è¦"
msgid "Abort"
-msgstr ""
+msgstr "中止"
msgid "About GitLab"
-msgstr ""
+msgstr "關於 GitLab"
msgid "About GitLab CE"
-msgstr ""
+msgstr "關於 GitLab CE"
msgid "About auto deploy"
msgstr "關於自動部署"
msgid "About this feature"
-msgstr ""
+msgstr "關於此功能"
msgid "Abuse Reports"
msgstr "濫用報告"
@@ -994,7 +1056,7 @@ msgid "Abuse reports"
msgstr "濫用報告"
msgid "Accept invitation"
-msgstr ""
+msgstr "接å—邀請"
msgid "Accept terms"
msgstr "接å—æ¢æ¬¾"
@@ -1006,91 +1068,91 @@ msgid "Access Tokens"
msgstr "å­˜å–憑證 (access token)"
msgid "Access denied for your LDAP account."
-msgstr ""
+msgstr "å­˜å–您的 LDAP 帳號時被拒絕。"
msgid "Access denied! Please verify you can add deploy keys to this repository."
-msgstr ""
+msgstr "å­˜å–被拒ï¼è«‹æª¢æŸ¥æ‚¨æ˜¯å¦å¯ä»¥åœ¨æ­¤ç‰ˆæœ¬åº«éƒ¨å±¬é‡‘鑰。"
msgid "Access expiration date"
-msgstr ""
+msgstr "å­˜å–éŽæœŸæ™‚é–“"
msgid "Access forbidden. Check your access level."
-msgstr ""
+msgstr "ç¦æ­¢å­˜å–。請檢查您的存å–權é™ã€‚"
msgid "Access to '%{classification_label}' not allowed"
-msgstr ""
+msgstr "ä¸å…許存å–「%{classification_label}ã€"
msgid "Access to Pages websites are controlled based on the user's membership to a given project. By checking this box, users will be required to be logged in to have access to all Pages websites in your instance."
msgstr ""
msgid "AccessDropdown|Groups"
-msgstr ""
+msgstr "群組"
msgid "AccessDropdown|Roles"
-msgstr ""
+msgstr "角色"
msgid "AccessDropdown|Users"
-msgstr ""
+msgstr "使用者"
msgid "AccessTokens|Access Tokens"
-msgstr ""
+msgstr "å­˜å–權æ–"
msgid "AccessTokens|Are you sure?"
-msgstr ""
+msgstr "你確定嗎?"
msgid "AccessTokens|Are you sure? Any RSS or calendar URLs currently in use will stop working."
-msgstr ""
+msgstr "你確定嗎?目å‰æ­£åœ¨ä½¿ç”¨çš„所有 RSS 或日曆 URL 都將åœæ­¢é‹ä½œã€‚"
msgid "AccessTokens|Are you sure? Any issue email addresses currently in use will stop working."
-msgstr ""
+msgstr "你確定嗎?目å‰æ­£åœ¨ä½¿ç”¨çš„所有議題電å­éƒµä»¶ä½å€éƒ½å°‡åœæ­¢é‹ä½œã€‚"
msgid "AccessTokens|Created"
-msgstr ""
+msgstr "已建立"
msgid "AccessTokens|Feed token"
-msgstr ""
+msgstr "Feed 權æ–"
msgid "AccessTokens|Incoming email token"
-msgstr ""
+msgstr "傳入電å­éƒµä»¶æ¬Šæ–"
msgid "AccessTokens|It cannot be used to access any other data."
-msgstr ""
+msgstr "無法用於存å–其它資料。"
msgid "AccessTokens|Keep this token secret. Anyone who gets ahold of it can access repository static objects as if they were you. You should %{reset_link_start}reset it%{reset_link_end} if that ever happens."
-msgstr ""
+msgstr "請確ä¿è©²æ¬Šæ–的安全。任何æŒæœ‰è©²æ¬Šæ–的人,都å¯ä»¥ä»¥æ‚¨çš„身份來建立議題。如果權æ–洩露,您應該 %{reset_link_start}é‡è¨­%{reset_link_end}。"
msgid "AccessTokens|Keep this token secret. Anyone who gets ahold of it can create issues as if they were you. You should %{link_reset_it} if that ever happens."
-msgstr ""
+msgstr "請確ä¿è©²æ¬Šæ–的安全。任何æŒæœ‰è©²æ¬Šæ–的人,都å¯ä»¥ä»¥ä½ çš„身份來建立議題。如果權æ–洩露,你應該 %{link_reset_it}。"
msgid "AccessTokens|Keep this token secret. Anyone who gets ahold of it can read activity and issue RSS feeds or your calendar feed as if they were you. You should %{link_reset_it} if that ever happens."
-msgstr ""
+msgstr "請確ä¿è©²æ¬Šæ–的安全。任何æŒæœ‰è©²æ¬Šæ–的人,都å¯ä»¥ä»¥ä½ çš„身份來閱讀活動記錄ã€è­°é¡Œ RSS 動態或行事曆動態。如果權æ–洩露,你應該 %{link_reset_it} 。"
msgid "AccessTokens|Personal Access Tokens"
-msgstr ""
+msgstr "個人存å–權æ–"
msgid "AccessTokens|Static object token"
-msgstr ""
+msgstr "éœæ…‹ç‰©ä»¶æ¬Šæ–"
msgid "AccessTokens|They are the only accepted password when you have Two-Factor Authentication (2FA) enabled."
-msgstr ""
+msgstr "當您啟用兩步驟èªè­‰ (2FA) 時,它們將是唯一能接å—的密碼。"
msgid "AccessTokens|You can also use personal access tokens to authenticate against Git over HTTP."
-msgstr ""
+msgstr "您還å¯ä»¥ä½¿ç”¨å€‹äººå­˜å–權æ–é€éŽ HTTP 進行 Git 驗證。"
msgid "AccessTokens|You can generate a personal access token for each application you use that needs access to the GitLab API."
-msgstr ""
+msgstr "您å¯ä»¥ç‚ºæ¯å€‹éœ€è¦å­˜å– GitLab API 的應用程å¼ç”Ÿæˆå€‹äººå­˜å–權æ–。"
msgid "AccessTokens|Your feed token is used to authenticate you when your RSS reader loads a personalized RSS feed or when your calendar application loads a personalized calendar, and is included in those feed URLs."
-msgstr ""
+msgstr "當您的 RSS 閱讀器載入個人化的 RSS 動態,或是行事曆應用程å¼è¼‰å…¥å€‹äººåŒ–的行事曆時,動態權æ–用來驗證身份,且權æ–會在動態網å€ä¸­ã€‚"
msgid "AccessTokens|Your incoming email token is used to authenticate you when you create a new issue by email, and is included in your personal project-specific email addresses."
-msgstr ""
+msgstr "當你使用電å­éƒµä»¶å»ºç«‹æ–°è­°é¡Œçš„時候,將使用傳入郵件權æ–進行驗證,該權æ–也包å«åœ¨å€‹äººå°ˆæ¡ˆæŒ‡å®šçš„é›»å­éƒµä»¶åœ°å€ä¸­ã€‚"
msgid "AccessTokens|Your static object token is used to authenticate you when repository static objects (e.g. archives, blobs, ...) are being served from an external storage."
-msgstr ""
+msgstr "您的éœæ…‹ç‰©ä»¶æ¬Šæ–用來從外部儲存空間存å–éœæ…‹ç‰©ä»¶ï¼ˆä¾‹å¦‚:å°å­˜æª”ã€Blob 等等)時å°æ‚¨é€²è¡Œèº«ä»½é©—證。"
msgid "AccessTokens|reset it"
-msgstr ""
+msgstr "é‡è¨­æ¬Šæ–"
msgid "AccessibilityReport|Learn More"
msgstr ""
@@ -1108,22 +1170,25 @@ msgid "Account"
msgstr "帳戶"
msgid "Account ID"
-msgstr ""
+msgstr "帳戶 ID"
msgid "Account and limit"
-msgstr ""
+msgstr "帳戶和é™åˆ¶"
msgid "Account: %{account}"
-msgstr ""
+msgstr "帳戶:%{account}"
msgid "Action to take when receiving an alert."
+msgstr "接收警示時è¦åŸ·è¡Œçš„動作。"
+
+msgid "Actions"
msgstr ""
msgid "Activate"
-msgstr ""
+msgstr "啟用"
msgid "Activate Service Desk"
-msgstr ""
+msgstr "啟用æœå‹™å°"
msgid "Active"
msgstr "啟用"
@@ -1132,7 +1197,7 @@ msgid "Active %{type} (%{token_length})"
msgstr ""
msgid "Active Sessions"
-msgstr ""
+msgstr "使用中的工作階段"
msgid "Active Users:"
msgstr ""
@@ -1148,7 +1213,7 @@ msgstr "增加"
msgid "Add %d issue"
msgid_plural "Add %d issues"
-msgstr[0] ""
+msgstr[0] "加入 %d 個議題"
msgid "Add %{linkStart}assets%{linkEnd} to your Release. GitLab automatically includes read-only assets, like source code and release evidence."
msgstr ""
@@ -1160,55 +1225,58 @@ msgid "Add CONTRIBUTING"
msgstr "新增 CONTRIBUTING"
msgid "Add GitLab to Slack"
-msgstr ""
+msgstr "將 GitLab 加入到 Slack"
msgid "Add Group Webhooks and GitLab Enterprise Edition."
-msgstr ""
+msgstr "加入群組 Webhooks åŠ GitLab ä¼æ¥­ç‰ˆã€‚"
msgid "Add Jaeger URL"
-msgstr ""
+msgstr "加入 Jaeger 網å€"
msgid "Add Kubernetes cluster"
msgstr "增加 Kubernetes å¢é›†"
msgid "Add LICENSE"
+msgstr "加入授權æ¢æ¬¾"
+
+msgid "Add New Node"
msgstr ""
msgid "Add README"
-msgstr ""
+msgstr "加入說明檔案"
msgid "Add Variable"
msgstr ""
msgid "Add Zoom meeting"
-msgstr ""
+msgstr "加入 Zoom 會議"
msgid "Add a %{type}"
msgstr ""
msgid "Add a GPG key"
-msgstr ""
+msgstr "加入 GPG 金鑰"
msgid "Add a Grafana button in the admin sidebar, monitoring section, to access a variety of statistics on the health and performance of GitLab."
-msgstr ""
+msgstr "在管ç†å´é‚Šæ¬„的監控部分加入 Grafana 按鈕,以便存å–關於GitLab 執行狀æ³å’Œæ•ˆèƒ½çš„å„種統計資料。"
msgid "Add a To Do"
-msgstr ""
+msgstr "加入待辦事項"
msgid "Add a bullet list"
-msgstr ""
+msgstr "加入項目清單"
msgid "Add a comment to this line"
msgstr ""
msgid "Add a general comment to this %{noteableDisplayName}."
-msgstr ""
+msgstr "加入一般留言至 %{noteableDisplayName}。"
msgid "Add a general comment to this %{noteable_name}."
msgstr "å° %{noteable_name} 新增一般留言。"
msgid "Add a homepage to your wiki that contains information about your project and GitLab will display it here instead of this message."
-msgstr ""
+msgstr "在 wiki 中加入首é ï¼Œå…¶ä¸­åŒ…å«æœ‰é—œå°ˆæ¡ˆçš„資訊,GitLab 將在這裡顯示該首é ï¼Œè€Œä¸æ˜¯æ­¤è¨Šæ¯ã€‚"
msgid "Add a line"
msgstr ""
@@ -1220,19 +1288,19 @@ msgid "Add a new issue"
msgstr ""
msgid "Add a numbered list"
-msgstr ""
+msgstr "加入編號列表"
msgid "Add a table"
msgstr "新增表格"
msgid "Add a task list"
-msgstr ""
+msgstr "加入作業列表"
msgid "Add additional text to appear in all email communications. %{character_limit} character limit"
-msgstr ""
+msgstr "è¦åœ¨æ‰€æœ‰é›»å­éƒµä»¶åŠ å…¥çš„附加文字。長度ä¸èƒ½è¶…éŽ %{character_limit} å­—å…ƒ"
msgid "Add an SSH key"
-msgstr ""
+msgstr "加入 SSH 金鑰"
msgid "Add an existing issue"
msgstr ""
@@ -1241,19 +1309,22 @@ msgid "Add an impersonation token"
msgstr ""
msgid "Add an issue"
-msgstr ""
+msgstr "加入議題"
msgid "Add another link"
msgstr ""
msgid "Add approval rule"
+msgstr "加入核准è¦å‰‡"
+
+msgid "Add approvers"
msgstr ""
msgid "Add bold text"
-msgstr ""
+msgstr "加入粗體文字"
msgid "Add child epic to an epic"
-msgstr ""
+msgstr "加入å­å²è©©åˆ°å²è©©"
msgid "Add comment now"
msgstr "ç«‹å³ç•™è¨€"
@@ -1262,34 +1333,34 @@ msgid "Add domain"
msgstr ""
msgid "Add email address"
-msgstr ""
+msgstr "加入電å­éƒµä»¶åœ°å€"
msgid "Add environment"
msgstr ""
msgid "Add header and footer to emails. Please note that color settings will only be applied within the application interface"
-msgstr ""
+msgstr "加入é é¦–å’Œé å°¾åˆ°é›»å­éƒµä»¶ã€‚請注æ„,é¡è‰²è¨­å®šåªæœƒå¥—用於應用程å¼ä»‹é¢"
msgid "Add image comment"
msgstr "增加圖片留言"
msgid "Add issues"
-msgstr ""
+msgstr "加入議題"
msgid "Add italic text"
-msgstr ""
+msgstr "加入斜體文字"
msgid "Add label(s)"
msgstr "新增標籤"
msgid "Add license"
-msgstr ""
+msgstr "加入授權æ¢æ¬¾"
msgid "Add list"
-msgstr ""
+msgstr "加入列表"
msgid "Add new application"
-msgstr ""
+msgstr "新增應用程å¼"
msgid "Add new directory"
msgstr "添加新目錄"
@@ -1298,79 +1369,82 @@ msgid "Add or subtract spent time"
msgstr "增加或減少所花時間"
msgid "Add reaction"
-msgstr ""
+msgstr "加入回應"
msgid "Add request manually"
-msgstr ""
+msgstr "手動加入請求"
msgid "Add strikethrough text"
msgstr ""
-msgid "Add system hook"
+msgid "Add suggestion to batch"
msgstr ""
+msgid "Add system hook"
+msgstr "加入系統掛鉤"
+
msgid "Add to Slack"
-msgstr ""
+msgstr "加到 Slack"
msgid "Add to epic"
-msgstr ""
+msgstr "加到å²è©©"
msgid "Add to merge train"
-msgstr ""
+msgstr "加到åˆä½µéˆ"
msgid "Add to merge train when pipeline succeeds"
-msgstr ""
+msgstr "æµæ°´ç·šæˆåŠŸå¾ŒåŠ åˆ°åˆä½µä½‡åˆ—"
msgid "Add to review"
-msgstr ""
+msgstr "加入待審閱"
msgid "Add to tree"
-msgstr ""
+msgstr "加入到樹"
msgid "Add user(s) to the group:"
-msgstr ""
+msgstr "å‘群組加入使用者:"
msgid "Add users to group"
-msgstr ""
+msgstr "å‘群組加入使用者"
msgid "Add variable"
msgstr ""
msgid "Add webhook"
-msgstr ""
+msgstr "加入 Webhook"
msgid "AddMember|No users specified."
-msgstr ""
+msgstr "未指定使用者。"
msgid "AddMember|Too many users specified (limit is %{user_limit})"
-msgstr ""
+msgstr "指定éŽå¤šä½¿ç”¨è€…(é™åˆ¶ç‚º %{user_limit})"
msgid "Added"
-msgstr ""
+msgstr "已加入"
msgid "Added %{epic_ref} as a child epic."
-msgstr ""
+msgstr "已加入 %{epic_ref} 為å­å²è©©ã€‚"
msgid "Added %{label_references} %{label_text}."
-msgstr ""
+msgstr "已加入 %{label_references} %{label_text}。"
msgid "Added a To Do."
-msgstr ""
+msgstr "已加入待辦事項。"
msgid "Added an issue to an epic."
-msgstr ""
+msgstr "å·²å‘å²è©©åŠ å…¥è­°é¡Œã€‚"
msgid "Added at"
-msgstr ""
+msgstr "加入時間"
msgid "Added for this merge request"
msgstr ""
msgid "Added in this version"
-msgstr ""
+msgstr "此版本新增"
msgid "Adding new applications is disabled in your GitLab instance. Please contact your GitLab administrator to get the permission"
-msgstr ""
+msgstr "ç›®å‰çš„ GitLab 實體ä¸å…許加入新應用程å¼ã€‚è«‹è¯çµ¡æ‚¨çš„ GitLab 管ç†å“¡ä»¥ç²å¾—相關權é™ã€‚"
msgid "Additional minutes"
msgstr "é¡å¤–的筆記"
@@ -1382,19 +1456,19 @@ msgid "Adds"
msgstr "增加"
msgid "Adds %{epic_ref} as child epic."
-msgstr ""
+msgstr "加入 %{epic_ref} 作為å­å²è©©ã€‚"
msgid "Adds %{labels} %{label_text}."
-msgstr ""
+msgstr "加入 %{labels}%{label_text}。"
msgid "Adds a To Do."
-msgstr ""
+msgstr "加入待辦事項。"
msgid "Adds a Zoom meeting"
-msgstr ""
+msgstr "加入 Zoom 會議"
msgid "Adds an issue to an epic."
-msgstr ""
+msgstr "å‘å²è©©åŠ å…¥è­°é¡Œã€‚"
msgid "Adjust your filters/search criteria above. If you believe this may be an error, please refer to the %{linkStart}Geo Troubleshooting%{linkEnd} documentation for more information."
msgstr ""
@@ -1405,20 +1479,23 @@ msgstr "管ç†å€åŸŸ"
msgid "Admin Note"
msgstr ""
+msgid "Admin Notifications"
+msgstr ""
+
msgid "Admin Overview"
msgstr "管ç†ç¸½è¦½"
msgid "Admin mode already enabled"
-msgstr ""
+msgstr "管ç†å“¡æ¨¡å¼å·²å•Ÿç”¨"
msgid "Admin mode disabled"
-msgstr ""
+msgstr "管ç†å“¡æ¨¡å¼å·²åœç”¨"
msgid "Admin mode enabled"
-msgstr ""
+msgstr "管ç†å“¡æ¨¡å¼å·²å•Ÿç”¨"
msgid "Admin notes"
-msgstr ""
+msgstr "管ç†å“¡å‚™è¨»"
msgid "AdminArea|Active users"
msgstr ""
@@ -1478,10 +1555,10 @@ msgid "AdminArea|You’re about to stop all jobs.This will halt all current jobs
msgstr "您將åœæ­¢æ‰€æœ‰ä»»å‹™ï¼Œé€™å°‡æœƒæš«åœæ‰€æœ‰æ­£åœ¨é‹è¡Œçš„任務。"
msgid "AdminDashboard|Error loading the statistics. Please try again"
-msgstr ""
+msgstr "載入統計資料時發生錯誤。請å†è©¦ä¸€æ¬¡"
msgid "AdminNote|Note"
-msgstr ""
+msgstr " 備註"
msgid "AdminProjects| You’re about to permanently delete the project %{projectName}, its repository, and all related resources including issues, merge requests, etc.. Once you confirm and press %{strong_start}Delete project%{strong_end}, it cannot be undone or recovered."
msgstr "項目管ç†|你正打算永久刪除項目 %{projectName},其儲存庫,以åŠæ‰€æœ‰ç›¸é—œè³‡æºå¦‚議題,版本åˆä½µè«‹æ±‚等等。如確èªä¸¦æŒ‰ä¸‹%{strong_start}刪除項目%{strong_end}後,此æ“作將無法復原。"
@@ -1499,16 +1576,16 @@ msgid "AdminSettings|Apply integration settings to all Projects"
msgstr ""
msgid "AdminSettings|Auto DevOps domain"
-msgstr ""
+msgstr "Auto DevOps 網域"
msgid "AdminSettings|Elasticsearch, PlantUML, Slack application, Third party offers, Snowplow, Amazon EKS have moved to Settings > General."
msgstr ""
msgid "AdminSettings|Enable shared runners for new projects"
-msgstr ""
+msgstr "啟用新專案的共享執行器"
msgid "AdminSettings|Environment variables are protected by default"
-msgstr ""
+msgstr "環境變數é è¨­å—ä¿è­·"
msgid "AdminSettings|Go to General Settings"
msgstr ""
@@ -1516,23 +1593,26 @@ msgstr ""
msgid "AdminSettings|Integrations configured here will automatically apply to all projects on this instance."
msgstr ""
-msgid "AdminSettings|No required pipeline"
+msgid "AdminSettings|Moved to integrations"
msgstr ""
+msgid "AdminSettings|No required pipeline"
+msgstr "沒有強制性的æµæ°´ç·š"
+
msgid "AdminSettings|Required pipeline configuration"
-msgstr ""
+msgstr "強制æµæ°´ç·šè¨­å®š"
msgid "AdminSettings|Select a pipeline configuration file"
-msgstr ""
+msgstr "é¸æ“‡æµæ°´ç·šè¨­å®šæª”"
msgid "AdminSettings|Select a template"
-msgstr ""
+msgstr "é¸æ“‡ç¯„本"
msgid "AdminSettings|Service template allows you to set default values for integrations"
msgstr ""
msgid "AdminSettings|Set an instance-wide auto included %{link_start}pipeline configuration%{link_end}. This pipeline configuration will be run after the project's own configuration."
-msgstr ""
+msgstr "設定實體範åœè‡ªå‹•åŒ…å«çš„ %{link_start}æµæ°´ç·šè¨­å®š%{link_end}。此æµæ°´ç·šè¨­å®šå°‡åœ¨å°ˆæ¡ˆæœ¬èº«çš„設定後執行。"
msgid "AdminSettings|Some settings have moved"
msgstr ""
@@ -1541,40 +1621,40 @@ msgid "AdminSettings|Specify a domain to use by default for every project's Auto
msgstr "為æ¯å€‹é …目的自動複閱應用åŠè‡ªå‹•éƒ¨ç½²æŒ‡å®šä¸€å€‹é è¨­çš„網域"
msgid "AdminSettings|The required pipeline configuration can be selected from the %{code_start}gitlab-ci%{code_end} directory inside of the configured %{link_start}instance template repository%{link_end} or from GitLab provided configurations."
-msgstr ""
+msgstr "強制æµæ°´ç·šè¨­å®šå¯å¾žè¨­å®šçš„ %{link_start}實體範本庫%{link_end} 中的 %{code_start}gitlab-ci%{code_end} 目錄é¸æ“‡ï¼Œæˆ–使用 GitLab æ供的設定檔。"
msgid "AdminSettings|When creating a new environment variable it will be protected by default."
-msgstr ""
+msgstr "建立新的環境變數時,它會被é è¨­ä¿è­·ã€‚"
msgid "AdminStatistics|Active Users"
-msgstr ""
+msgstr "æ´»èºä½¿ç”¨è€…"
msgid "AdminStatistics|Forks"
-msgstr ""
+msgstr "分å‰"
msgid "AdminStatistics|Issues"
-msgstr ""
+msgstr "議題"
msgid "AdminStatistics|Merge Requests"
-msgstr ""
+msgstr "åˆä½µè«‹æ±‚"
msgid "AdminStatistics|Milestones"
-msgstr ""
+msgstr "里程碑"
msgid "AdminStatistics|Notes"
-msgstr ""
+msgstr "備註"
msgid "AdminStatistics|SSH Keys"
-msgstr ""
+msgstr "SSH 金鑰"
msgid "AdminStatistics|Snippets"
-msgstr ""
+msgstr "程å¼ç¢¼ç‰‡æ®µ"
msgid "AdminUsers|2FA Disabled"
-msgstr ""
+msgstr "未啟用兩步驟驗證"
msgid "AdminUsers|2FA Enabled"
-msgstr ""
+msgstr "已啟用兩步驟驗證"
msgid "AdminUsers|Active"
msgstr "æ´»èº"
@@ -1586,37 +1666,37 @@ msgid "AdminUsers|Admins"
msgstr "管ç†å“¡"
msgid "AdminUsers|Block"
-msgstr ""
+msgstr "å°éŽ–"
msgid "AdminUsers|Block user"
msgstr "å°éŽ–使用者"
msgid "AdminUsers|Block user %{username}?"
-msgstr ""
+msgstr "å°éŽ– %{username} 使用者?"
msgid "AdminUsers|Blocked"
msgstr "å·²å°éŽ–"
msgid "AdminUsers|Blocking user has the following effects:"
-msgstr ""
+msgstr "å°éŽ–使用者具有以下效果:"
msgid "AdminUsers|Cannot unblock LDAP blocked users"
msgstr "無法解å°éŽ–已被å°éŽ–çš„ LDAP 使用者"
msgid "AdminUsers|Deactivate"
-msgstr ""
+msgstr "å‡çµ"
msgid "AdminUsers|Deactivate User %{username}?"
-msgstr ""
+msgstr "å‡çµ %{username} 使用者?"
msgid "AdminUsers|Deactivate user"
-msgstr ""
+msgstr "å‡çµä½¿ç”¨è€…"
msgid "AdminUsers|Deactivated"
-msgstr ""
+msgstr "å·²å‡çµ"
msgid "AdminUsers|Deactivating a user has the following effects:"
-msgstr ""
+msgstr "å‡çµä½¿ç”¨è€…具有以下效果:"
msgid "AdminUsers|Delete User %{username} and contributions?"
msgstr "刪除使用者 %{username} åŠå…¶è²¢ç»ï¼Ÿ"
@@ -1637,55 +1717,55 @@ msgid "AdminUsers|Is using seat"
msgstr ""
msgid "AdminUsers|It's you!"
-msgstr ""
+msgstr "這就是你ï¼"
msgid "AdminUsers|New user"
-msgstr ""
+msgstr "新增使用者"
msgid "AdminUsers|No users found"
-msgstr ""
+msgstr "未找到使用者"
msgid "AdminUsers|Owned groups will be left"
-msgstr ""
+msgstr "將會ä¿ç•™æ“有的群組"
msgid "AdminUsers|Personal projects will be left"
-msgstr ""
+msgstr "將會ä¿ç•™å€‹äººå°ˆæ¡ˆ"
msgid "AdminUsers|Personal projects, group and user history will be left intact"
-msgstr ""
+msgstr "個人專案ã€ç¾¤çµ„和使用者歷å²è¨˜éŒ„將維æŒä¸è®Š"
msgid "AdminUsers|Reactivating a user will:"
-msgstr ""
+msgstr "å–消å‡çµä½¿ç”¨è€…將會:"
msgid "AdminUsers|Restore user access to the account, including web, Git and API."
-msgstr ""
+msgstr "還原使用者存å–帳戶的權é™ï¼ŒåŒ…括網é ã€Git å’Œ API。"
msgid "AdminUsers|Search by name, email or username"
-msgstr ""
+msgstr "ä¾åå­—ã€é›»å­éƒµä»¶æˆ–使用者å稱æœå°‹"
msgid "AdminUsers|Search users"
-msgstr ""
+msgstr "æœå°‹ä½¿ç”¨è€…"
msgid "AdminUsers|Send email to users"
-msgstr ""
+msgstr "å‘使用者傳é€é›»å­éƒµä»¶"
msgid "AdminUsers|Sort by"
-msgstr ""
+msgstr "排åºæ–¹å¼"
msgid "AdminUsers|The user will be logged out"
-msgstr ""
+msgstr "此使用者將會登出"
msgid "AdminUsers|The user will not be able to access git repositories"
-msgstr ""
+msgstr "æ­¤ä½¿ç”¨è€…å°‡ç„¡æ³•å­˜å– git 版本庫"
msgid "AdminUsers|The user will not be able to access the API"
-msgstr ""
+msgstr "æ­¤ä½¿ç”¨è€…å°‡ç„¡æ³•å­˜å– API"
msgid "AdminUsers|The user will not be able to use slash commands"
-msgstr ""
+msgstr "此使用者將無法使用斜線指令"
msgid "AdminUsers|The user will not receive any notifications"
-msgstr ""
+msgstr "此使用者將ä¸æœƒæ”¶åˆ°ä»»ä½•é€šçŸ¥"
msgid "AdminUsers|To confirm, type %{projectName}"
msgstr "請輸入 %{projectName} 以進行確èª"
@@ -1694,28 +1774,28 @@ msgid "AdminUsers|To confirm, type %{username}"
msgstr "請輸入 %{username} 以進行確èª"
msgid "AdminUsers|User will not be able to access git repositories"
-msgstr ""
+msgstr "ä½¿ç”¨è€…å°‡ç„¡æ³•å­˜å– git 版本庫"
msgid "AdminUsers|User will not be able to login"
-msgstr ""
+msgstr "使用者將無法登入"
msgid "AdminUsers|When the user logs back in, their account will reactivate as a fully active account"
msgstr ""
msgid "AdminUsers|Without projects"
-msgstr ""
+msgstr "沒有專案"
msgid "AdminUsers|You are about to permanently delete the user %{username}. Issues, merge requests, and groups linked to them will be transferred to a system-wide \"Ghost-user\". To avoid data loss, consider using the %{strong_start}block user%{strong_end} feature instead. Once you %{strong_start}Delete user%{strong_end}, it cannot be undone or recovered."
-msgstr ""
+msgstr "您å³å°‡æ°¸ä¹…刪除使用者 %{username}。該使用者的議題ã€åˆä½µè«‹æ±‚以åŠç›¸é—œçš„群組將被轉移到系統的「幽éˆä½¿ç”¨è€…ã€ã€‚為é¿å…資料éºå¤±ï¼Œå»ºè­°æ‚¨ä½¿ç”¨ %{strong_start}å°éŽ–使用者%{strong_end} 功能。一旦您 %{strong_start}刪除使用者%{strong_end},則無法復原。"
msgid "AdminUsers|You are about to permanently delete the user %{username}. This will delete all of the issues, merge requests, and groups linked to them. To avoid data loss, consider using the %{strong_start}block user%{strong_end} feature instead. Once you %{strong_start}Delete user%{strong_end}, it cannot be undone or recovered."
-msgstr ""
+msgstr "您å³å°‡æ°¸ä¹…刪除使用者 %{username}。此æ“作會刪除該使用者的所有議題ã€åˆä½µè«‹æ±‚以åŠç›¸é—œçš„群組。為é¿å…資料éºå¤±ï¼Œå»ºè­°æ‚¨ä½¿ç”¨ %{strong_start}å°éŽ–使用者%{strong_end} 功能。一旦您 %{strong_start}刪除使用者%{strong_end},則無法復原。"
msgid "Administration"
msgstr ""
msgid "Advanced"
-msgstr ""
+msgstr "進階"
msgid "Advanced Settings"
msgstr ""
@@ -1724,13 +1804,13 @@ msgid "Advanced permissions, Large File Storage and Two-Factor authentication se
msgstr "é€²éšŽæ¬Šé™ ï¼Œå¤§æª”æ¡ˆå„²å­˜èˆ‡é›™é‡é‘‘證設定"
msgid "Advanced search functionality"
-msgstr ""
+msgstr "進階æœå°‹åŠŸèƒ½"
msgid "After a successful password update you will be redirected to login screen."
-msgstr ""
+msgstr "密碼更新æˆåŠŸå¾Œï¼Œæ‚¨å°‡è¢«é‡æ–°å°Žå‘至登入é é¢ã€‚"
msgid "After a successful password update, you will be redirected to the login page where you can log in with your new password."
-msgstr ""
+msgstr "密碼更新æˆåŠŸå¾Œï¼Œæ‚¨å°‡è¢«é‡æ–°å°Žå‘到登入é é¢ï¼Œæ‚¨å¯ä»¥ç”¨æ–°å¯†ç¢¼é‡æ–°ç™»å…¥ã€‚"
msgid "After that, you will not to be able to use merge approvals or code quality as well as many other features."
msgstr ""
@@ -1745,12 +1825,18 @@ msgid "Alert"
msgid_plural "Alerts"
msgstr[0] "æ示"
+msgid "AlertManagement| assign yourself"
+msgstr ""
+
msgid "AlertManagement|Acknowledged"
msgstr ""
msgid "AlertManagement|Alert"
msgstr ""
+msgid "AlertManagement|Alert assignee(s): %{assignees}"
+msgstr ""
+
msgid "AlertManagement|Alert detail"
msgstr ""
@@ -1766,9 +1852,15 @@ msgstr ""
msgid "AlertManagement|All alerts"
msgstr ""
+msgid "AlertManagement|Assign To"
+msgstr ""
+
msgid "AlertManagement|Assign status"
msgstr ""
+msgid "AlertManagement|Assignee"
+msgstr ""
+
msgid "AlertManagement|Assignees"
msgstr ""
@@ -1787,9 +1879,6 @@ msgstr ""
msgid "AlertManagement|Edit"
msgstr ""
-msgid "AlertManagement|End time"
-msgstr ""
-
msgid "AlertManagement|Events"
msgstr ""
@@ -1808,10 +1897,13 @@ msgstr ""
msgid "AlertManagement|More information"
msgstr ""
+msgid "AlertManagement|No Matching Results"
+msgstr ""
+
msgid "AlertManagement|No alert data to display."
msgstr ""
-msgid "AlertManagement|No alerts available to display. If you think you're seeing this message in error, refresh the page."
+msgid "AlertManagement|No alerts available to display. See %{linkStart}enabling alert management%{linkEnd} for more information on adding alerts to the list."
msgstr ""
msgid "AlertManagement|No alerts to display."
@@ -1820,6 +1912,9 @@ msgstr ""
msgid "AlertManagement|None"
msgstr ""
+msgid "AlertManagement|None -"
+msgstr ""
+
msgid "AlertManagement|Open"
msgstr ""
@@ -1856,9 +1951,18 @@ msgstr ""
msgid "AlertManagement|There was an error displaying the alerts. Confirm your endpoint's configuration details to ensure alerts appear."
msgstr ""
+msgid "AlertManagement|There was an error while updating the assignee(s) list. Please try again."
+msgstr ""
+
+msgid "AlertManagement|There was an error while updating the assignee(s) of the alert. Please try again."
+msgstr ""
+
msgid "AlertManagement|There was an error while updating the status of the alert. Please try again."
msgstr ""
+msgid "AlertManagement|This assignee cannot be assigned to this alert."
+msgstr ""
+
msgid "AlertManagement|Tool"
msgstr ""
@@ -1881,13 +1985,13 @@ msgid "AlertService|You must provide this URL and authorization key to authorize
msgstr ""
msgid "Alerts"
-msgstr ""
+msgstr "警示"
msgid "Alerts endpoint"
-msgstr ""
+msgstr "警示端點"
msgid "Algorithm"
-msgstr ""
+msgstr "演算法"
msgid "All"
msgstr "全部"
@@ -1899,10 +2003,10 @@ msgid "All (default)"
msgstr ""
msgid "All Members"
-msgstr ""
+msgstr "所有æˆå“¡"
msgid "All branches"
-msgstr ""
+msgstr "所有分支"
msgid "All changes are committed"
msgstr "所有改變都已經æ交"
@@ -1911,7 +2015,7 @@ msgid "All default stages are currently visible"
msgstr ""
msgid "All email addresses will be used to identify your commits."
-msgstr ""
+msgstr "所有電å­éƒµä»¶åœ°å€éƒ½å¯ç”¨æ–¼è­˜åˆ¥æ‚¨çš„æ交。"
msgid "All environments"
msgstr ""
@@ -1920,28 +2024,28 @@ msgid "All features are enabled for blank projects, from templates, or when impo
msgstr "從模æ¿å»ºç«‹æˆ–導入專案時將啟用所有功能,您å¯ä»¥åœ¨å°ˆæ¡ˆè¨­ç½®ä¸­å°‡å…¶é—œé–‰ã€‚"
msgid "All groups and projects"
-msgstr ""
+msgstr "所有群組和專案"
msgid "All issues for this milestone are closed."
-msgstr ""
+msgstr "此里程碑的所有議題å‡å·²é—œé–‰ã€‚"
msgid "All issues for this milestone are closed. You may close this milestone now."
msgstr "此里程碑è£çš„所有議題å‡å·²é—œé–‰ã€‚ä½ ç¾åœ¨å¯ä»¥é—œé–‰æ­¤é‡Œç¨‹ç¢‘。"
msgid "All merge conflicts were resolved. The merge request can now be merged."
-msgstr ""
+msgstr "所有åˆä½µè¡çªå‡å·²è§£æ±ºã€‚æ­¤åˆä½µè«‹æ±‚ç¾åœ¨å¯ä»¥åˆä½µã€‚"
msgid "All merge request dependencies have been merged"
-msgstr ""
+msgstr "所有åˆä½µè«‹æ±‚ä¾è³´é—œä¿‚å‡å·²åˆä½µ"
msgid "All paths are relative to the GitLab URL. Do not include %{relative_url_link_start}relative URL%{relative_url_link_end}."
-msgstr ""
+msgstr "所有路徑都相å°æ–¼ GitLab 網å€ã€‚è«‹ä¸è¦åŒ…å«%{relative_url_link_start}相å°ç¶²å€%{relative_url_link_end}。"
msgid "All projects"
-msgstr ""
+msgstr "所有專案"
msgid "All security scans are enabled because %{linkStart}Auto DevOps%{linkEnd} is enabled on this project"
-msgstr ""
+msgstr "因為這個專案已開啟 %{linkStart}Auto DevOps%{linkEnd},已啟用所有安全掃æ"
msgid "All threads resolved"
msgstr ""
@@ -1950,19 +2054,19 @@ msgid "All users"
msgstr "所有使用者"
msgid "All users must have a name."
-msgstr ""
+msgstr "所有的使用者都必須具有å稱。"
msgid "Allow \"%{group_name}\" to sign you in"
-msgstr ""
+msgstr "å…許「%{group_name}ã€ä»¥æ‚¨çš„身份登入"
msgid "Allow commits from members who can merge to the target branch."
msgstr "容許授權開發者å‘å…±åŒå”作分支æ交"
msgid "Allow group owners to manage LDAP-related settings"
-msgstr ""
+msgstr "å…è¨±ç¾¤çµ„æ‰€æœ‰è€…ç®¡ç† LDAP 相關的設定"
msgid "Allow only the selected protocols to be used for Git access."
-msgstr ""
+msgstr "僅å…許é¸å–之用於 Git å­˜å–的通訊å”定。"
msgid "Allow owners to manage default branch protection per group"
msgstr ""
@@ -1974,25 +2078,25 @@ msgid "Allow projects within this group to use Git LFS"
msgstr "容許本項目採用 Git LFS"
msgid "Allow public access to pipelines and job details, including output logs and artifacts"
-msgstr ""
+msgstr "å…許所有人存å–æµæ°´ç·šå’Œä½œæ¥­è©³ç´°è³‡è¨Šï¼ŒåŒ…括輸出日誌和產物"
msgid "Allow rendering of PlantUML diagrams in Asciidoc documents."
msgstr "å…許在 Asciidoc 文件中渲染 PlantUML 圖片"
msgid "Allow repository mirroring to be configured by project maintainers"
-msgstr ""
+msgstr "å…許專案維護者設定版本庫é¡åƒç«™"
msgid "Allow requests to the local network from hooks and services."
msgstr "å…許來自鉤å­å’Œæœå‹™çš„å°æœ¬åœ°ç¶²çµ¡çš„請求。"
msgid "Allow requests to the local network from system hooks"
-msgstr ""
+msgstr "å…許系統掛鉤å‘本機網路請求"
msgid "Allow requests to the local network from web hooks and services"
-msgstr ""
+msgstr "å…許 Webhook åŠæœå‹™å‘本機網路請求"
msgid "Allow this key to push to repository as well? (Default only allows pull access.)"
-msgstr ""
+msgstr "åŒæ™‚å…許此金鑰推é€åˆ°ç‰ˆæœ¬åº«å—Žï¼Ÿï¼ˆé è¨­åªæœ‰æ‹‰å–權é™ï¼‰"
msgid "Allow this secondary node to replicate content on Object Storage"
msgstr ""
@@ -2001,82 +2105,88 @@ msgid "Allow users to dismiss the broadcast message"
msgstr ""
msgid "Allow users to register any application to use GitLab as an OAuth provider"
-msgstr ""
+msgstr "å…許使用者註冊任何應用程å¼ï¼Œä½¿ç”¨ GitLab 作為 OAuth æ供者"
msgid "Allow users to request access (if visibility is public or internal)"
+msgstr "å…許使用者請求存å–(如果å¯è¦‹æ€§æ˜¯å…¬é–‹æˆ–內部)"
+
+msgid "Allowed Geo IP"
msgstr ""
msgid "Allowed email domain restriction only permitted for top-level groups"
-msgstr ""
+msgstr "åªå…許頂層群組使用電å­éƒµä»¶ç¶²åŸŸé™åˆ¶"
msgid "Allowed to fail"
-msgstr ""
+msgstr "å…許失敗"
msgid "Allows you to add and manage Kubernetes clusters."
msgstr "å…許您增加和管ç†Kuberneteså¢é›†ã€‚"
msgid "Almost there"
-msgstr ""
+msgstr "å³å°‡å®Œæˆ"
msgid "Also called \"Issuer\" or \"Relying party trust identifier\""
-msgstr ""
+msgstr "也稱為「簽發者ã€æˆ–「信任憑證者信任識別碼ã€"
msgid "Also called \"Relying party service URL\" or \"Reply URL\""
-msgstr ""
+msgstr "也稱為「信任憑證者æœå‹™ç¶²å€ã€æˆ–「回覆網å€ã€"
msgid "Alternate support URL for help page and help dropdown"
-msgstr ""
+msgstr "說明é é¢å’Œèªªæ˜Žä¸‹æ‹‰é¸å–®çš„備用支æ´ç¶²å€"
msgid "Alternatively, you can convert your account to a managed account by the %{group_name} group."
msgstr ""
msgid "Amazon EKS"
-msgstr ""
+msgstr "Amazon EKS"
msgid "Amazon EKS integration allows you to provision EKS clusters from GitLab."
-msgstr ""
+msgstr "Amazon EKS æ•´åˆè®“您能從 GitLab ç®¡ç† EKS å¢é›†ã€‚"
msgid "Amazon Web Services"
+msgstr "Amazon 網路æœå‹™"
+
+msgid "Amazon Web Services Logo"
msgstr ""
msgid "Amazon authentication is not %{link_start}correctly configured%{link_end}. Ask your GitLab administrator if you want to use this service."
-msgstr ""
+msgstr "Amazon 身份驗證未%{link_start}正確設定%{link_end}。如需使用這項æœå‹™ï¼Œè«‹è¯çµ¡ GitLab 管ç†å“¡ã€‚"
msgid "Amount of time (in hours) that users are allowed to skip forced configuration of two-factor authentication"
-msgstr ""
+msgstr "å…許使用者跳éŽå¼·åˆ¶è¨­å®šå…©æ­¥é©Ÿé©—證的時間(以å°æ™‚為單ä½ï¼‰"
msgid "An alert has been triggered in %{project_path}."
msgstr ""
msgid "An application called %{link_to_client} is requesting access to your GitLab account."
-msgstr ""
+msgstr "æ‡‰ç”¨ç¨‹å¼ %{link_to_client} 請求存å–您的 GitLab 帳戶。"
msgid "An email notification was recently sent from the admin panel. Please wait %{wait_time_in_words} before attempting to send another message."
msgstr ""
msgid "An empty GitLab User field will add the FogBugz user's full name (e.g. \"By John Smith\") in the description of all issues and comments. It will also associate and/or assign these issues and comments with the project creator."
-msgstr ""
+msgstr "空 GitLab 使用者欄ä½å°‡åœ¨æ‰€æœ‰è­°é¡ŒåŠç•™è¨€çš„æ述中加入 FogBugz 使用者的全å(例如「由 John Smithã€ï¼‰ã€‚其還會與專案建立者關è¯å’Œï¼æˆ–分é…這些議題或留言。"
msgid "An error has occurred"
-msgstr ""
+msgstr "發生錯誤"
msgid "An error occurred adding a draft to the thread."
-msgstr ""
+msgstr "å‘話題加入è‰ç¨¿æ™‚發生錯誤。"
msgid "An error occurred adding a new draft."
-msgstr ""
+msgstr "加入新è‰ç¨¿æ™‚發生錯誤。"
msgid "An error occurred creating the new branch."
-msgstr ""
+msgstr "建立新分支時發生錯誤。"
msgid "An error occurred fetching the approval rules."
-msgstr ""
+msgstr "抓å–核准è¦å‰‡æ™‚發生錯誤。"
msgid "An error occurred fetching the approvers for the new rule."
-msgstr ""
+msgstr "抓å–æ–°è¦å‰‡çš„核准者時發生錯誤。"
msgid "An error occurred fetching the dropdown data."
-msgstr ""
+msgstr "抓å–下拉資料時發生錯誤。"
msgid "An error occurred fetching the project authors."
msgstr ""
@@ -2088,40 +2198,46 @@ msgid "An error occurred when toggling the notification subscription"
msgstr "切æ›è¨‚閱通知時發生錯誤"
msgid "An error occurred when trying to resolve a comment. Please try again."
-msgstr ""
+msgstr "嘗試解決留言時發生錯誤。請å†è©¦ä¸€æ¬¡ã€‚"
msgid "An error occurred when trying to resolve a discussion. Please try again."
-msgstr ""
+msgstr "嘗試解決討論時發生錯誤。請å†è©¦ä¸€æ¬¡ã€‚"
msgid "An error occurred when updating the issue weight"
msgstr "更新議題權é‡æ™‚發生錯誤"
+msgid "An error occurred while acknowledging the notification. Refresh the page and try again."
+msgstr ""
+
+msgid "An error occurred while adding approvers"
+msgstr ""
+
msgid "An error occurred while adding formatted title for epic"
msgstr ""
-msgid "An error occurred while checking group path"
+msgid "An error occurred while checking group path. Please refresh and try again."
msgstr ""
msgid "An error occurred while committing your changes."
-msgstr ""
+msgstr "æ交您的變更時發生錯誤。"
msgid "An error occurred while decoding the file."
msgstr ""
msgid "An error occurred while deleting the approvers group"
-msgstr ""
+msgstr "刪除核准者群組時發生錯誤"
msgid "An error occurred while deleting the comment"
-msgstr ""
+msgstr "刪除留言時發生錯誤"
msgid "An error occurred while deleting the pipeline."
-msgstr ""
+msgstr "刪除æµæ°´ç·šæ™‚發生錯誤。"
msgid "An error occurred while detecting host keys"
-msgstr ""
+msgstr "åµæ¸¬ä¸»æ©Ÿé‡‘鑰時發生錯誤"
msgid "An error occurred while disabling Service Desk."
-msgstr ""
+msgstr "åœç”¨æœå‹™å°æ™‚發生錯誤。"
msgid "An error occurred while dismissing the alert. Refresh the page and try again."
msgstr "當解除通知時錯誤發生。請嘗試é‡æ–°æ•´ç†é é¢ä¸¦é‡è©¦ã€‚"
@@ -2130,34 +2246,34 @@ msgid "An error occurred while dismissing the feature highlight. Refresh the pag
msgstr "解除亮高顯示時發生錯誤,請é‡æ–°æ•´ç†é é¢å†æ¬¡å˜—試。"
msgid "An error occurred while enabling Service Desk."
-msgstr ""
+msgstr "啟用æœå‹™å°æ™‚發生錯誤。"
msgid "An error occurred while fetching coverage reports."
msgstr ""
msgid "An error occurred while fetching environments."
-msgstr ""
+msgstr "抓å–環境時發生錯誤。"
msgid "An error occurred while fetching exposed artifacts."
-msgstr ""
+msgstr "抓å–公開產物時發生錯誤。"
msgid "An error occurred while fetching folder content."
-msgstr ""
+msgstr "抓å–資料夾內容時發生錯誤。"
msgid "An error occurred while fetching issues."
-msgstr ""
+msgstr "抓å–議題時發生錯誤。"
msgid "An error occurred while fetching label colors."
-msgstr ""
+msgstr "抓å–標籤é¡è‰²æ™‚發生錯誤。"
msgid "An error occurred while fetching markdown preview"
msgstr "è®€å– markdown é è¦½æ™‚發生錯誤"
msgid "An error occurred while fetching pending comments"
-msgstr ""
+msgstr "抓å–待處ç†ç•™è¨€æ™‚發生錯誤"
msgid "An error occurred while fetching projects autocomplete."
-msgstr ""
+msgstr "抓å–專案自動完æˆæ™‚發生錯誤。"
msgid "An error occurred while fetching sidebar data"
msgstr "讀å–å´é‚Šæ¬„資料時發生錯誤"
@@ -2166,76 +2282,76 @@ msgid "An error occurred while fetching terraform reports."
msgstr ""
msgid "An error occurred while fetching the Service Desk address."
-msgstr ""
+msgstr "抓å–æœå‹™å°ä½å€æ™‚發生錯誤。"
msgid "An error occurred while fetching the board lists. Please try again."
-msgstr ""
+msgstr "抓å–看æ¿åˆ—表時發生錯誤。請å†è©¦ä¸€æ¬¡ã€‚"
msgid "An error occurred while fetching the builds."
-msgstr ""
+msgstr "抓å–組建時發生錯誤。"
msgid "An error occurred while fetching the job log."
-msgstr ""
+msgstr "抓å–作業日誌時發生錯誤。"
msgid "An error occurred while fetching the job trace."
-msgstr ""
+msgstr "抓å–作業追蹤時發生錯誤。"
msgid "An error occurred while fetching the job."
-msgstr ""
+msgstr "抓å–作業時發生錯誤。"
msgid "An error occurred while fetching the jobs."
-msgstr ""
+msgstr "抓å–作業時發生錯誤。"
msgid "An error occurred while fetching the latest pipeline."
-msgstr ""
+msgstr "抓å–最後一個æµæ°´ç·šæ™‚發生錯誤。"
msgid "An error occurred while fetching the pipeline."
msgstr "讀å–æµæ°´ç·šæ™‚發生錯誤"
msgid "An error occurred while fetching the releases. Please try again."
-msgstr ""
+msgstr "抓å–發行版本時發生錯誤。請é‡è©¦ã€‚"
msgid "An error occurred while fetching this tab."
-msgstr ""
+msgstr "抓å–此分é æ™‚發生錯誤。"
msgid "An error occurred while generating a username. Please try again."
-msgstr ""
+msgstr "產生使用者å稱時發生錯誤。請é‡è©¦ã€‚"
msgid "An error occurred while getting files for - %{branchId}"
-msgstr ""
+msgstr "å–å¾— %{branchId} 的檔案時發生錯誤"
msgid "An error occurred while getting projects"
msgstr "讀å–專案時發生錯誤"
msgid "An error occurred while importing project: %{details}"
-msgstr ""
+msgstr "匯入專案時發生錯誤:%{details}"
msgid "An error occurred while initializing path locks"
-msgstr ""
+msgstr "åˆå§‹åŒ–路徑鎖時發生錯誤"
msgid "An error occurred while loading all the files."
-msgstr ""
+msgstr "載入所有檔案時發生錯誤。"
msgid "An error occurred while loading chart data"
-msgstr ""
+msgstr "載入圖表資料時發生錯誤"
msgid "An error occurred while loading commit signatures"
msgstr "載入上交簽署時發生錯誤"
msgid "An error occurred while loading designs. Please try again."
-msgstr ""
+msgstr "載入設計時發生錯誤。請é‡è©¦ã€‚"
msgid "An error occurred while loading diff"
-msgstr ""
+msgstr "載入差異時發生錯誤"
msgid "An error occurred while loading filenames"
-msgstr ""
+msgstr "載入檔å時發生錯誤"
msgid "An error occurred while loading group members."
-msgstr ""
+msgstr "讀å–群組æˆå“¡æ™‚發生錯誤。"
msgid "An error occurred while loading issues"
-msgstr ""
+msgstr "載入議題時發生錯誤"
msgid "An error occurred while loading merge requests."
msgstr ""
@@ -2243,80 +2359,83 @@ msgstr ""
msgid "An error occurred while loading milestones"
msgstr ""
-msgid "An error occurred while loading terraform report"
+msgid "An error occurred while loading project creation UI"
msgstr ""
msgid "An error occurred while loading the data. Please try again."
-msgstr ""
+msgstr "載入資料時發生錯誤。請é‡è©¦ã€‚"
msgid "An error occurred while loading the file"
-msgstr ""
+msgstr "載入檔案時發生錯誤"
msgid "An error occurred while loading the file content."
-msgstr ""
+msgstr "載入檔案內容時發生錯誤。"
msgid "An error occurred while loading the file."
-msgstr ""
+msgstr "載入檔案時發生錯誤。"
msgid "An error occurred while loading the file. Please try again later."
msgstr ""
msgid "An error occurred while loading the merge request changes."
-msgstr ""
+msgstr "載入åˆä½µè«‹æ±‚的變更內容時發生錯誤。"
msgid "An error occurred while loading the merge request version data."
-msgstr ""
+msgstr "載入åˆä½µè«‹æ±‚的版本資料時發生錯誤。"
msgid "An error occurred while loading the merge request."
-msgstr ""
+msgstr "載入åˆä½µè«‹æ±‚時發生錯誤。"
msgid "An error occurred while loading the pipelines jobs."
-msgstr ""
+msgstr "載入æµæ°´ç·šä½œæ¥­æ™‚發生錯誤。"
msgid "An error occurred while loading the subscription details."
-msgstr ""
+msgstr "載入訂閱詳細資訊時發生錯誤。"
msgid "An error occurred while making the request."
-msgstr ""
+msgstr "建立請求時發生錯誤。"
msgid "An error occurred while moving the issue."
-msgstr ""
+msgstr "移動議題時發生錯誤。"
msgid "An error occurred while parsing recent searches"
-msgstr ""
+msgstr "解æžæœ€è¿‘æœå°‹æ™‚發生錯誤"
msgid "An error occurred while parsing the file."
msgstr ""
msgid "An error occurred while removing epics."
-msgstr ""
+msgstr "移除å²è©©æ™‚發生錯誤。"
msgid "An error occurred while removing issues."
-msgstr ""
+msgstr "移除議題時發生錯誤。"
msgid "An error occurred while rendering preview broadcast message"
+msgstr "繪製廣播訊æ¯æ™‚發生錯誤"
+
+msgid "An error occurred while rendering the editor"
msgstr ""
msgid "An error occurred while reordering issues."
-msgstr ""
+msgstr "é‡æ–°æŽ’åºè­°é¡Œæ™‚發生錯誤。"
msgid "An error occurred while retrieving calendar activity"
-msgstr ""
+msgstr "抓å–日曆活動時發生錯誤"
msgid "An error occurred while retrieving diff"
-msgstr ""
+msgstr "å–得差異時發生錯誤"
msgid "An error occurred while saving LDAP override status. Please try again."
-msgstr ""
+msgstr "儲存 LDAP 覆蓋狀態時發生錯誤,請é‡è©¦ã€‚"
msgid "An error occurred while saving assignees"
msgstr "儲存指派人時發生錯誤"
msgid "An error occurred while saving the approval settings"
-msgstr ""
+msgstr "儲存核准設定時發生錯誤"
msgid "An error occurred while saving the template. Please check if the template exists."
-msgstr ""
+msgstr "儲存範本時發生錯誤。請檢查範本是å¦å­˜åœ¨ã€‚"
msgid "An error occurred while searching for milestones"
msgstr ""
@@ -2325,28 +2444,28 @@ msgid "An error occurred while subscribing to notifications."
msgstr "訂閱通知時出錯"
msgid "An error occurred while triggering the job."
-msgstr ""
+msgstr "觸發作業時發生錯誤。"
msgid "An error occurred while trying to run a new pipeline for this Merge Request."
-msgstr ""
+msgstr "嘗試執行此åˆä½µè«‹æ±‚çš„æ–°æµæ°´ç·šæ™‚發生錯誤。"
msgid "An error occurred while unsubscribing to notifications."
msgstr "å–消訂閱通知時出錯"
msgid "An error occurred while updating approvers"
-msgstr ""
+msgstr "更新核准者時發生錯誤"
msgid "An error occurred while updating the comment"
-msgstr ""
+msgstr "更新留言時發生錯誤"
msgid "An error occurred while validating group path"
-msgstr ""
+msgstr "驗證群組路徑時發生錯誤"
msgid "An error occurred while validating username"
-msgstr ""
+msgstr "驗證使用者å稱時發生錯誤"
msgid "An error occurred. Please try again."
-msgstr ""
+msgstr "發生了錯誤,請å†è©¦ä¸€æ¬¡ã€‚"
msgid "An error ocurred while loading your content. Please try again."
msgstr ""
@@ -2361,39 +2480,39 @@ msgid "An issue already exists"
msgstr ""
msgid "An issue can be a bug, a todo or a feature request that needs to be discussed in a project. Besides, issues are searchable and filterable."
-msgstr ""
+msgstr "è­°é¡Œå¯ä»¥æ˜¯éœ€è¦åœ¨å°ˆæ¡ˆä¸­è¨Žè«–的缺陷ã€å¾…辦事項或功能請求。此外,議題是å¯æœå°‹å’ŒéŽæ¿¾çš„。"
msgid "An unauthenticated user"
msgstr ""
msgid "An unexpected error occurred while checking the project environment."
-msgstr ""
+msgstr "檢查專案環境時發生æ„外錯誤。"
msgid "An unexpected error occurred while checking the project runners."
-msgstr ""
+msgstr "檢查專案 Runner 時發生æ„外錯誤。"
msgid "An unexpected error occurred while communicating with the Web Terminal."
-msgstr ""
+msgstr "與網é çµ‚端機通信時發生æ„外錯誤。"
msgid "An unexpected error occurred while starting the Web Terminal."
-msgstr ""
+msgstr "啟動網é çµ‚端機時發生æ„外錯誤。"
msgid "An unexpected error occurred while stopping the Web Terminal."
-msgstr ""
+msgstr "åœæ­¢ç¶²é çµ‚端機時發生æ„外錯誤。"
msgid "An unknown error occurred while loading this graph."
msgstr ""
msgid "Analytics"
-msgstr ""
+msgstr "分æž"
msgid "Analyze a review version of your web application."
-msgstr ""
+msgstr "分æžç¶²é æ‡‰ç”¨ç¨‹å¼çš„審閱版本。"
msgid "Analyze your dependencies for known vulnerabilities."
msgstr ""
-msgid "Analyze your source code and git history for secrets"
+msgid "Analyze your source code and git history for secrets."
msgstr ""
msgid "Analyze your source code for known vulnerabilities."
@@ -2415,7 +2534,7 @@ msgid "Anti-spam verification"
msgstr ""
msgid "Any"
-msgstr ""
+msgstr "任何"
msgid "Any Author"
msgstr ""
@@ -2507,12 +2626,18 @@ msgstr ""
msgid "Apply suggestion"
msgstr ""
+msgid "Apply suggestions"
+msgstr ""
+
msgid "Apply template"
msgstr ""
msgid "Apply this approval rule to any branch or a specific protected branch."
msgstr ""
+msgid "Applying"
+msgstr ""
+
msgid "Applying a template will replace the existing issue description. Any changes you have made will be lost."
msgstr ""
@@ -2525,12 +2650,18 @@ msgstr ""
msgid "Applying multiple commands"
msgstr ""
-msgid "Applying suggestion"
+msgid "Applying suggestion..."
+msgstr ""
+
+msgid "Applying suggestions..."
msgstr ""
msgid "Approval rules"
msgstr ""
+msgid "Approval rules reset to project defaults"
+msgstr ""
+
msgid "ApprovalRuleRemove|%d member"
msgid_plural "ApprovalRuleRemove|%d members"
msgstr[0] ""
@@ -2569,28 +2700,31 @@ msgid "ApprovalRule|e.g. QA, Security, etc."
msgstr ""
msgid "Approve"
-msgstr ""
+msgstr "核准"
msgid "Approve a merge request"
-msgstr ""
+msgstr "核准åˆä½µè«‹æ±‚"
msgid "Approve the current merge request."
-msgstr ""
+msgstr "核准目å‰åˆä½µè«‹æ±‚。"
msgid "Approved"
msgstr ""
+msgid "Approved MRs"
+msgstr ""
+
msgid "Approved by: "
msgstr ""
msgid "Approved the current merge request."
-msgstr ""
+msgstr "核准了目å‰çš„åˆä½µè«‹æ±‚。"
msgid "Approved-By"
msgstr ""
msgid "Approver"
-msgstr ""
+msgstr "核准人"
msgid "Approvers"
msgstr ""
@@ -2605,37 +2739,37 @@ msgid "Archive"
msgstr ""
msgid "Archive jobs"
-msgstr ""
+msgstr "歸檔作業"
msgid "Archive project"
-msgstr ""
+msgstr "歸檔專案"
msgid "Archived"
msgstr ""
msgid "Archived project! Repository and other project resources are read only"
-msgstr ""
+msgstr "已歸檔專案ï¼ç‰ˆæœ¬åº«å’Œå…¶ä»–專案資æºå‡ç‚ºå”¯è®€"
msgid "Archived project! Repository and other project resources are read-only"
-msgstr ""
+msgstr "已歸檔專案ï¼ç‰ˆæœ¬åº«å’Œå…¶ä»–專案資æºå‡ç‚ºå”¯è®€"
msgid "Archived projects"
-msgstr ""
+msgstr "歸檔專案"
msgid "Archiving the project will make it entirely read only. It is hidden from the dashboard and doesn't show up in searches. %{strong_start}The repository cannot be committed to, and no issues, comments, or other entities can be created.%{strong_end}"
-msgstr ""
+msgstr "歸檔專案將使其完全唯讀。儀表æ¿ä¸­å’Œæœå°‹çµæžœä¸­éƒ½ä¸æœƒå‡ºç¾è©²å°ˆæ¡ˆã€‚%{strong_start}程å¼ç¢¼å°‡ç„¡æ³•æ交到版本庫,也無法建立任何議題ã€ç•™è¨€ç­‰å…¶ä»–。%{strong_end}"
msgid "Are you setting up GitLab for a company?"
-msgstr ""
+msgstr "您正在為公å¸è¨­å®š GitLab 嗎?"
msgid "Are you sure that you want to archive this project?"
-msgstr ""
+msgstr "確定è¦æ­¸æª”此專案嗎?"
msgid "Are you sure that you want to unarchive this project?"
-msgstr ""
+msgstr "確定è¦å–消歸檔此專案嗎?"
msgid "Are you sure you want to cancel editing this comment?"
-msgstr ""
+msgstr "確定è¦å–消編輯此留言嗎?"
msgid "Are you sure you want to close this blocked issue?"
msgstr ""
@@ -2644,19 +2778,19 @@ msgid "Are you sure you want to delete %{name}?"
msgstr ""
msgid "Are you sure you want to delete these artifacts?"
-msgstr ""
+msgstr "確定è¦åˆªé™¤é€™äº›ç”¢ç‰©å—Žï¼Ÿ"
msgid "Are you sure you want to delete this %{typeOfComment}?"
-msgstr ""
+msgstr "確定è¦åˆªé™¤æ­¤%{typeOfComment}嗎?"
msgid "Are you sure you want to delete this board?"
-msgstr ""
+msgstr "確定è¦åˆªé™¤æ­¤çœ‹æ¿å—Žï¼Ÿ"
msgid "Are you sure you want to delete this device? This action cannot be undone."
-msgstr ""
+msgstr "您確定è¦åˆªé™¤æ­¤è£ç½®å—Žï¼Ÿæ­¤å‹•ä½œç„¡æ³•å¾©åŽŸã€‚"
msgid "Are you sure you want to delete this list?"
-msgstr ""
+msgstr "確定è¦åˆªé™¤æ­¤åˆ—表嗎?"
msgid "Are you sure you want to delete this pipeline schedule?"
msgstr "確定è¦åˆªé™¤æ­¤æµæ°´ç·šè¨ˆåŠƒå—Žï¼Ÿ"
@@ -2671,19 +2805,19 @@ msgid "Are you sure you want to discard this comment?"
msgstr ""
msgid "Are you sure you want to erase this build?"
-msgstr ""
+msgstr "你確定è¦åˆªé™¤é€™å€‹çµ„建嗎?"
msgid "Are you sure you want to lose unsaved changes?"
-msgstr ""
+msgstr "確定è¦æ”¾æ£„未儲存的變更嗎?"
msgid "Are you sure you want to lose your issue information?"
-msgstr ""
+msgstr "您確定è¦æ¨æ£„議題資訊嗎?"
msgid "Are you sure you want to merge immediately?"
-msgstr ""
+msgstr "您確定è¦ç«‹å³åˆä½µå—Žï¼Ÿ"
msgid "Are you sure you want to permanently delete this license?"
-msgstr ""
+msgstr "您確定è¦æ°¸ä¹…刪除此授權æ¢æ¬¾å—Žï¼Ÿ"
msgid "Are you sure you want to re-deploy this environment?"
msgstr ""
@@ -2692,16 +2826,16 @@ msgid "Are you sure you want to regenerate the public key? You will have to upda
msgstr ""
msgid "Are you sure you want to remove %{group_name}?"
-msgstr ""
+msgstr "您確定è¦ç§»é™¤ %{group_name} 嗎?"
msgid "Are you sure you want to remove the attachment?"
-msgstr ""
+msgstr "您確定è¦ç§»é™¤æ­¤é™„件嗎?"
msgid "Are you sure you want to remove the license?"
msgstr ""
msgid "Are you sure you want to remove this identity?"
-msgstr ""
+msgstr "您確定è¦ç§»é™¤é€™å€‹èº«ä»½è­˜åˆ¥å—Žï¼Ÿ"
msgid "Are you sure you want to reset registration token?"
msgstr "確定è¦é‡ç½®è¨»å†Šä»¤ç‰Œå—Žï¼Ÿ"
@@ -2793,6 +2927,9 @@ msgstr ""
msgid "Assign"
msgstr "指派"
+msgid "Assign Iteration"
+msgstr ""
+
msgid "Assign custom color like #FF0000"
msgstr ""
@@ -2811,6 +2948,9 @@ msgstr "分é…一些議題到這個里程碑。"
msgid "Assign to"
msgstr ""
+msgid "Assign to commenting user"
+msgstr ""
+
msgid "Assign yourself to these issues"
msgstr ""
@@ -2836,6 +2976,9 @@ msgid "Assignee"
msgid_plural "%d Assignees"
msgstr[0] ""
+msgid "Assignee has no permissions"
+msgstr ""
+
msgid "Assignee lists not available with your current license"
msgstr "您目å‰çš„許å¯è­‰ä¸æ”¯æ´æŒ‡æ´¾äººåå–®"
@@ -2845,6 +2988,9 @@ msgstr "指派人å單顯示了分é…給é¸å®šä½¿ç”¨è€…的所有議題。"
msgid "Assignee(s)"
msgstr "指派人"
+msgid "Assignees"
+msgstr ""
+
msgid "Assigns %{assignee_users_sentence}."
msgstr ""
@@ -2879,6 +3025,9 @@ msgstr ""
msgid "Audit Events is a way to keep track of important events that happened in GitLab."
msgstr ""
+msgid "Audit Log"
+msgstr ""
+
msgid "AuditEvents|(removed)"
msgstr ""
@@ -2915,6 +3064,9 @@ msgstr ""
msgid "AuditLogs|IP Address"
msgstr ""
+msgid "AuditLogs|Member Events"
+msgstr ""
+
msgid "AuditLogs|No matching %{type} found."
msgstr ""
@@ -3197,9 +3349,6 @@ msgstr "您的徽章"
msgid "Badges|e.g. %{exampleUrl}"
msgstr ""
-msgid "Badge|New"
-msgstr ""
-
msgid "Balsamiq file could not be loaded."
msgstr ""
@@ -3248,6 +3397,12 @@ msgstr ""
msgid "Below you will find all the groups that are public."
msgstr ""
+msgid "Beta"
+msgstr ""
+
+msgid "Bi-weekly code coverage"
+msgstr ""
+
msgid "Billing"
msgstr ""
@@ -3308,9 +3463,15 @@ msgstr ""
msgid "Bitbucket Server Import"
msgstr ""
+msgid "Bitbucket Server import"
+msgstr ""
+
msgid "Bitbucket import"
msgstr ""
+msgid "Blame"
+msgstr ""
+
msgid "Blocked"
msgstr ""
@@ -3323,9 +3484,6 @@ msgstr ""
msgid "Blog"
msgstr ""
-msgid "Blue helpers indicate an action to be taken."
-msgstr ""
-
msgid "Board name"
msgstr ""
@@ -3524,6 +3682,9 @@ msgstr ""
msgid "Broadcast Message was successfully updated."
msgstr ""
+msgid "Broadcast Messages"
+msgstr ""
+
msgid "Browse Directory"
msgstr "ç€è¦½ç›®éŒ„"
@@ -3578,6 +3739,9 @@ msgstr ""
msgid "By %{user_name}"
msgstr ""
+msgid "By URL"
+msgstr ""
+
msgid "By default GitLab sends emails in HTML and plain text formats so mail clients can choose what format to use. Disable this option if you only want to send emails in plain text format."
msgstr ""
@@ -3672,7 +3836,7 @@ msgid "CONTRIBUTING"
msgstr ""
msgid "CPU"
-msgstr ""
+msgstr "CPU"
msgid "Callback URL"
msgstr ""
@@ -3683,6 +3847,9 @@ msgstr ""
msgid "Can override approvers and approvals required per merge request"
msgstr ""
+msgid "Can't apply as this line has changed or the suggestion already matches its content."
+msgstr ""
+
msgid "Can't create snippet: %{err}"
msgstr ""
@@ -3869,9 +4036,6 @@ msgstr ""
msgid "Changes are still tracked. Useful for cluster/index migrations."
msgstr ""
-msgid "Changes are unknown"
-msgstr ""
-
msgid "Changes suppressed. Click to show."
msgstr ""
@@ -3897,7 +4061,7 @@ msgid "Charts can't be displayed as the request for data has timed out. %{docume
msgstr ""
msgid "Chat"
-msgstr ""
+msgstr "å³æ™‚通訊"
msgid "ChatMessage|%{project_link}: Pipeline %{pipeline_link} of %{ref_type} %{ref_link} by %{user_combined_name} %{humanized_status} in %{duration}"
msgstr ""
@@ -4178,15 +4342,9 @@ msgstr ""
msgid "Choose what content you want to see on a group’s overview page"
msgstr ""
-msgid "Choose which groups you wish to synchronize to this secondary node"
-msgstr ""
-
msgid "Choose which repositories you want to connect and run CI/CD pipelines."
msgstr ""
-msgid "Choose which shards you wish to synchronize to this secondary node"
-msgstr ""
-
msgid "Choose your framework"
msgstr ""
@@ -4484,9 +4642,15 @@ msgstr ""
msgid "Cluster does not exist"
msgstr ""
+msgid "Cluster is required for Stages::ClusterEndpointInserter"
+msgstr ""
+
msgid "Cluster level"
msgstr ""
+msgid "Cluster type must be specificed for Stages::ClusterEndpointInserter"
+msgstr ""
+
msgid "ClusterIntegration| %{custom_domain_start}More information%{custom_domain_end}."
msgstr ""
@@ -4598,7 +4762,7 @@ msgstr ""
msgid "ClusterIntegration|Cert-Manager"
msgstr ""
-msgid "ClusterIntegration|Cert-Manager is a native Kubernetes certificate management controller that helps with issuing certificates. Installing Cert-Manager on your cluster will issue a certificate by %{letsEncrypt} and ensure that certificates are valid and up-to-date."
+msgid "ClusterIntegration|Cert-Manager is a native Kubernetes certificate management controller that helps with issuing certificates. Installing Cert-Manager on your cluster will issue a certificate by %{linkStart}Let's Encrypt%{linkEnd} and ensure that certificates are valid and up-to-date."
msgstr ""
msgid "ClusterIntegration|Certificate Authority bundle (PEM format)"
@@ -4625,9 +4789,6 @@ msgstr ""
msgid "ClusterIntegration|Clear the local cache of namespace and service accounts. This is necessary if your integration has become out of sync. The cache is repopulated during the next CI job that requires namespace and service accounts."
msgstr ""
-msgid "ClusterIntegration|Cloud Run"
-msgstr ""
-
msgid "ClusterIntegration|Cluster being created"
msgstr ""
@@ -4712,7 +4873,7 @@ msgstr ""
msgid "ClusterIntegration|Crossplane"
msgstr ""
-msgid "ClusterIntegration|Crossplane enables declarative provisioning of managed services from your cloud of choice using %{codeStart}kubectl%{codeEnd} or %{gitlabIntegrationLink}. Crossplane runs inside your Kubernetes cluster and supports secure connectivity and secrets management between app containers and the cloud services they depend on."
+msgid "ClusterIntegration|Crossplane enables declarative provisioning of managed services from your cloud of choice using %{codeStart}kubectl%{codeEnd} or %{linkStart}GitLab Integration%{linkEnd}. Crossplane runs inside your Kubernetes cluster and supports secure connectivity and secrets management between app containers and the cloud services they depend on."
msgstr ""
msgid "ClusterIntegration|Deletes all GitLab resources attached to this cluster during removal"
@@ -4796,9 +4957,6 @@ msgstr ""
msgid "ClusterIntegration|GitLab-managed cluster"
msgstr ""
-msgid "ClusterIntegration|Gitlab Integration"
-msgstr ""
-
msgid "ClusterIntegration|Global default"
msgstr ""
@@ -4844,7 +5002,7 @@ msgstr ""
msgid "ClusterIntegration|Ingress gives you a way to route requests to services based on the request host or path, centralizing a number of services into a single entrypoint."
msgstr ""
-msgid "ClusterIntegration|Installing Ingress may incur additional costs. Learn more about %{pricingLink}."
+msgid "ClusterIntegration|Installing Ingress may incur additional costs. Learn more about %{linkStart}pricing%{linkEnd}."
msgstr ""
msgid "ClusterIntegration|Instance cluster"
@@ -4859,7 +5017,7 @@ msgstr ""
msgid "ClusterIntegration|Issuer Email"
msgstr ""
-msgid "ClusterIntegration|Issuers represent a certificate authority. You must provide an email address for your Issuer. "
+msgid "ClusterIntegration|Issuers represent a certificate authority. You must provide an email address for your Issuer."
msgstr ""
msgid "ClusterIntegration|Jupyter Hostname"
@@ -4931,9 +5089,6 @@ msgstr ""
msgid "ClusterIntegration|Learn more about instance Kubernetes clusters"
msgstr ""
-msgid "ClusterIntegration|Let's Encrypt"
-msgstr ""
-
msgid "ClusterIntegration|Loading IAM Roles"
msgstr ""
@@ -5045,7 +5200,7 @@ msgstr ""
msgid "ClusterIntegration|Prometheus"
msgstr ""
-msgid "ClusterIntegration|Prometheus is an open-source monitoring system with %{gitlabIntegrationLink} to monitor deployed applications."
+msgid "ClusterIntegration|Prometheus is an open-source monitoring system with %{linkStart}GitLab Integration%{linkEnd} to monitor deployed applications."
msgstr ""
msgid "ClusterIntegration|Provider details"
@@ -5201,10 +5356,10 @@ msgstr ""
msgid "ClusterIntegration|Select zone to choose machine type"
msgstr ""
-msgid "ClusterIntegration|Send Cilium Logs"
+msgid "ClusterIntegration|Send Container Network Policies Logs"
msgstr ""
-msgid "ClusterIntegration|Send ModSecurity Logs"
+msgid "ClusterIntegration|Send Web Application Firewall Logs"
msgstr ""
msgid "ClusterIntegration|Service Token"
@@ -5372,15 +5527,12 @@ msgstr ""
msgid "ClusterIntegration|documentation"
msgstr ""
-msgid "ClusterIntegration|installed via %{installed_via}"
+msgid "ClusterIntegration|installed via %{linkStart}Cloud Run%{linkEnd}"
msgstr ""
msgid "ClusterIntegration|meets the requirements"
msgstr ""
-msgid "ClusterIntegration|pricing"
-msgstr ""
-
msgid "ClusterIntegration|sign up"
msgstr ""
@@ -5417,6 +5569,15 @@ msgstr ""
msgid "Code"
msgstr ""
+msgid "Code Coverage: %{coveragePercentage}%{percentSymbol}"
+msgstr ""
+
+msgid "Code Coverage| Empty code coverage data"
+msgstr ""
+
+msgid "Code Coverage|Couldn't fetch the code coverage data"
+msgstr ""
+
msgid "Code Owners"
msgstr ""
@@ -5471,7 +5632,7 @@ msgstr ""
msgid "Collapse approvers"
msgstr ""
-msgid "Collapse child epics"
+msgid "Collapse milestones"
msgstr ""
msgid "Collapse replies"
@@ -5489,6 +5650,9 @@ msgstr ""
msgid "Coming soon"
msgstr ""
+msgid "Comma-separated, e.g. '1.1.1.1, 2.2.2.0/24'"
+msgstr ""
+
msgid "Command"
msgstr ""
@@ -5658,6 +5822,12 @@ msgstr ""
msgid "Compliance framework (optional)"
msgstr ""
+msgid "Compliance frameworks"
+msgstr ""
+
+msgid "ComplianceDashboard|created by:"
+msgstr ""
+
msgid "ComplianceFramework|GDPR"
msgstr ""
@@ -5796,6 +5966,9 @@ msgstr ""
msgid "Connection timed out"
msgstr ""
+msgid "Connection timeout"
+msgstr ""
+
msgid "Contact sales to upgrade"
msgstr ""
@@ -5849,9 +6022,6 @@ msgstr ""
msgid "ContainerRegistry|CLI Commands"
msgstr ""
-msgid "ContainerRegistry|Compressed Size"
-msgstr ""
-
msgid "ContainerRegistry|Container Registry"
msgstr ""
@@ -5867,6 +6037,9 @@ msgstr ""
msgid "ContainerRegistry|Copy push command"
msgstr ""
+msgid "ContainerRegistry|Delete selected"
+msgstr ""
+
msgid "ContainerRegistry|Docker connection error"
msgstr ""
@@ -5900,16 +6073,16 @@ msgstr ""
msgid "ContainerRegistry|If you are not already logged in, you need to authenticate to the Container Registry by using your GitLab username and password. If you have %{twofaDocLinkStart}Two-Factor Authentication%{twofaDocLinkEnd} enabled, use a %{personalAccessTokensDocLinkStart}Personal Access Token%{personalAccessTokensDocLinkEnd} instead of a password."
msgstr ""
-msgid "ContainerRegistry|Image ID"
+msgid "ContainerRegistry|Image ID: %{imageId}"
msgstr ""
msgid "ContainerRegistry|Image Repositories"
msgstr ""
-msgid "ContainerRegistry|Keep and protect the images that matter most."
+msgid "ContainerRegistry|Image tags"
msgstr ""
-msgid "ContainerRegistry|Last Updated"
+msgid "ContainerRegistry|Keep and protect the images that matter most."
msgstr ""
msgid "ContainerRegistry|Login"
@@ -5924,6 +6097,9 @@ msgstr ""
msgid "ContainerRegistry|Please contact your administrator."
msgstr ""
+msgid "ContainerRegistry|Published %{timeInfo}"
+msgstr ""
+
msgid "ContainerRegistry|Push an image"
msgstr ""
@@ -5939,9 +6115,6 @@ msgstr ""
msgid "ContainerRegistry|Remove repository"
msgstr ""
-msgid "ContainerRegistry|Remove selected tags"
-msgstr ""
-
msgid "ContainerRegistry|Remove tag"
msgid_plural "ContainerRegistry|Remove tags"
msgstr[0] ""
@@ -5970,9 +6143,6 @@ msgstr ""
msgid "ContainerRegistry|Sorry, your filter produced no results."
msgstr ""
-msgid "ContainerRegistry|Tag"
-msgstr ""
-
msgid "ContainerRegistry|Tag expiration policy"
msgstr ""
@@ -6117,21 +6287,6 @@ msgstr ""
msgid "Control the display of third party offers."
msgstr ""
-msgid "Control the maximum concurrency of LFS/attachment backfill for this secondary node"
-msgstr ""
-
-msgid "Control the maximum concurrency of container repository operations for this Geo node"
-msgstr ""
-
-msgid "Control the maximum concurrency of repository backfill for this secondary node"
-msgstr ""
-
-msgid "Control the maximum concurrency of verification operations for this Geo node"
-msgstr ""
-
-msgid "Control the minimum interval in days that a repository should be reverified for this primary node"
-msgstr ""
-
msgid "Cookie domain"
msgstr ""
@@ -6273,6 +6428,9 @@ msgstr ""
msgid "Could not find design."
msgstr ""
+msgid "Could not find iteration"
+msgstr ""
+
msgid "Could not remove the trigger."
msgstr ""
@@ -6309,6 +6467,12 @@ msgstr ""
msgid "Coverage"
msgstr ""
+msgid "Coverage Fuzzing"
+msgstr ""
+
+msgid "Crash State"
+msgstr ""
+
msgid "Create"
msgstr ""
@@ -6357,6 +6521,9 @@ msgstr ""
msgid "Create a personal access token on your account to pull or push via %{protocol}."
msgstr "在帳戶上創建個人訪å•ä»¤ç‰Œï¼Œä»¥é€šéŽ %{protocol} 來拉å–或推é€ã€‚"
+msgid "Create a project pre-populated with the necessary files to get you started quickly."
+msgstr ""
+
msgid "Create an account using:"
msgstr ""
@@ -6525,6 +6692,9 @@ msgstr ""
msgid "Creates branch '%{branch_name}' and a merge request to resolve this issue."
msgstr ""
+msgid "Creating"
+msgstr ""
+
msgid "Creating epic"
msgstr ""
@@ -6576,7 +6746,10 @@ msgstr ""
msgid "Current vulnerabilities count"
msgstr ""
-msgid "CurrentUser|Buy CI minutes"
+msgid "CurrentUser|Buy Pipeline minutes"
+msgstr ""
+
+msgid "CurrentUser|One of your groups is running out"
msgstr ""
msgid "CurrentUser|Profile"
@@ -6591,6 +6764,9 @@ msgstr ""
msgid "CurrentUser|Upgrade"
msgstr ""
+msgid "Custom Attributes"
+msgstr ""
+
msgid "Custom CI configuration path"
msgstr ""
@@ -6853,12 +7029,18 @@ msgstr ""
msgid "DAG"
msgstr ""
-msgid "DNS"
+msgid "DAG visualization requires at least 3 dependent jobs."
msgstr ""
+msgid "DNS"
+msgstr "DNS"
+
msgid "Dashboard"
msgstr ""
+msgid "Dashboard uid not found"
+msgstr ""
+
msgid "DashboardProjects|All"
msgstr ""
@@ -6883,6 +7065,9 @@ msgstr ""
msgid "Data is still calculating..."
msgstr ""
+msgid "Datasource name not found"
+msgstr ""
+
msgid "Date"
msgstr ""
@@ -7063,6 +7248,9 @@ msgstr ""
msgid "Delete this attachment"
msgstr ""
+msgid "Delete user list"
+msgstr ""
+
msgid "Delete variable"
msgstr ""
@@ -7374,6 +7562,9 @@ msgstr ""
msgid "Deploying to"
msgstr ""
+msgid "Deploying to AWS is easy with GitLab"
+msgstr ""
+
msgid "Deployment Frequency"
msgstr ""
@@ -7735,6 +7926,9 @@ msgstr ""
msgid "Dismissed on pipeline %{pipelineLink} at %{projectLink}"
msgstr ""
+msgid "Display alerts from all your monitoring tools directly within GitLab."
+msgstr ""
+
msgid "Display name"
msgstr ""
@@ -7792,9 +7986,6 @@ msgstr ""
msgid "Don't show again"
msgstr "ä¸å†é¡¯ç¤º"
-msgid "Don't worry, you can access this tour by clicking on the help icon in the top right corner and choose <strong>Learn GitLab</strong>."
-msgstr ""
-
msgid "Done"
msgstr ""
@@ -7921,6 +8112,9 @@ msgstr "編輯 %{id} æµæ°´ç·šè¨ˆåŠƒ"
msgid "Edit Release"
msgstr ""
+msgid "Edit Slack integration"
+msgstr ""
+
msgid "Edit Snippet"
msgstr ""
@@ -7960,6 +8154,9 @@ msgstr ""
msgid "Edit issues"
msgstr "編輯議題"
+msgid "Edit iteration"
+msgstr ""
+
msgid "Edit public deploy key"
msgstr ""
@@ -8008,6 +8205,9 @@ msgstr ""
msgid "Email"
msgstr ""
+msgid "Email Notification"
+msgstr ""
+
msgid "Email address"
msgstr ""
@@ -8020,6 +8220,9 @@ msgstr ""
msgid "Email not verified. Please verify your email in Salesforce."
msgstr ""
+msgid "Email notification for unknown sign-ins"
+msgstr ""
+
msgid "Email patch"
msgstr ""
@@ -8251,6 +8454,9 @@ msgstr ""
msgid "Enforce DNS rebinding attack protection"
msgstr ""
+msgid "Enforce personal access token expiration"
+msgstr ""
+
msgid "Ensure connectivity is available from the GitLab server to the Prometheus server"
msgstr ""
@@ -8320,6 +8526,9 @@ msgstr ""
msgid "Enter the name of your application, and we'll return a unique %{type}."
msgstr ""
+msgid "Enter weights for storages for new repositories."
+msgstr ""
+
msgid "Enter your password to approve"
msgstr ""
@@ -8329,15 +8538,27 @@ msgstr ""
msgid "Environment does not have deployments"
msgstr ""
+msgid "Environment is required for Stages::MetricEndpointInserter"
+msgstr ""
+
+msgid "Environment is required for Stages::VariableEndpointInserter"
+msgstr ""
+
msgid "Environment scope"
msgstr ""
+msgid "Environment variables are applied to all project environments in this instance via the Runner. You can use environment variables for passwords, secret keys, etc. Make variables available to the running application by prepending the variable key with <code>K8S_SECRET_</code>. You can set variables to be:"
+msgstr ""
+
msgid "Environment variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. Additionally, they can be masked so they are hidden in job logs, though they must match certain regexp requirements to do so. You can use environment variables for passwords, secret keys, or whatever you want."
msgstr ""
msgid "Environment variables are configured by your administrator to be %{link_start}protected%{link_end} by default"
msgstr ""
+msgid "Environment variables on this GitLab instance are configured to be %{link_start}protected%{link_end} by default"
+msgstr ""
+
msgid "Environment:"
msgstr ""
@@ -8869,6 +9090,9 @@ msgstr "全部"
msgid "EventFilterBy|Filter by comments"
msgstr "按評論éŽæ¿¾"
+msgid "EventFilterBy|Filter by designs"
+msgstr ""
+
msgid "EventFilterBy|Filter by epic events"
msgstr ""
@@ -9001,15 +9225,15 @@ msgstr ""
msgid "Expand approvers"
msgstr ""
-msgid "Expand child epics"
-msgstr ""
-
msgid "Expand down"
msgstr ""
msgid "Expand dropdown"
msgstr ""
+msgid "Expand milestones"
+msgstr ""
+
msgid "Expand sidebar"
msgstr ""
@@ -9025,6 +9249,9 @@ msgstr ""
msgid "Expiration date"
msgstr ""
+msgid "Expiration not enforced"
+msgstr ""
+
msgid "Expiration policy for the Container Registry is a perfect solution for keeping the Registry space down while still enjoying the full power of GitLab CI/CD."
msgstr ""
@@ -9145,6 +9372,9 @@ msgstr ""
msgid "Failed Jobs"
msgstr ""
+msgid "Failed on"
+msgstr ""
+
msgid "Failed to add a Zoom meeting"
msgstr ""
@@ -9205,13 +9435,19 @@ msgstr ""
msgid "Failed to install."
msgstr ""
+msgid "Failed to load assignees. Please try again."
+msgstr ""
+
+msgid "Failed to load authors. Please try again."
+msgstr ""
+
msgid "Failed to load emoji list."
msgstr ""
msgid "Failed to load error details from Sentry."
msgstr ""
-msgid "Failed to load errors from Sentry. Error message: %{errorMessage}"
+msgid "Failed to load errors from Sentry."
msgstr ""
msgid "Failed to load group activity metrics. Please try again."
@@ -9292,6 +9528,9 @@ msgstr ""
msgid "Failed to set due date because the date format is invalid."
msgstr ""
+msgid "Failed to set iteration on this issue. Please try again."
+msgstr ""
+
msgid "Failed to signing using smartcard authentication"
msgstr ""
@@ -9514,13 +9753,10 @@ msgstr ""
msgid "FeatureFlags|Target environments"
msgstr ""
-msgid "FeatureFlags|There are no active feature flags"
-msgstr ""
-
-msgid "FeatureFlags|There are no inactive feature flags"
+msgid "FeatureFlags|There was an error fetching the feature flags."
msgstr ""
-msgid "FeatureFlags|There was an error fetching the feature flags."
+msgid "FeatureFlags|There was an error retrieving user lists"
msgstr ""
msgid "FeatureFlags|Try again in a few moments or contact your support team."
@@ -9532,9 +9768,18 @@ msgstr ""
msgid "FeatureFlag|Delete strategy"
msgstr ""
+msgid "FeatureFlag|List"
+msgstr ""
+
msgid "FeatureFlag|Percentage"
msgstr ""
+msgid "FeatureFlag|Select a user list"
+msgstr ""
+
+msgid "FeatureFlag|There are no configured user lists"
+msgstr ""
+
msgid "FeatureFlag|Type"
msgstr ""
@@ -9691,6 +9936,12 @@ msgstr ""
msgid "Filter..."
msgstr ""
+msgid "Find File"
+msgstr ""
+
+msgid "Find bugs in your code with coverage-guided fuzzing"
+msgstr ""
+
msgid "Find by path"
msgstr "按路徑查找"
@@ -9938,7 +10189,7 @@ msgid "Generate new export"
msgstr ""
msgid "Geo"
-msgstr ""
+msgstr "Geo"
msgid "Geo Nodes"
msgstr ""
@@ -9955,6 +10206,9 @@ msgstr ""
msgid "Geo allows you to replicate your GitLab instance to other geographical locations."
msgstr ""
+msgid "Geo nodes are paused using a command run on the node"
+msgstr ""
+
msgid "GeoNodeStatusEvent|%{timeAgoStr} (%{pendingEvents} events)"
msgstr ""
@@ -10039,6 +10293,9 @@ msgstr ""
msgid "GeoNodes|Not checksummed"
msgstr ""
+msgid "GeoNodes|Package files"
+msgstr ""
+
msgid "GeoNodes|Pausing replication stops the sync process. Are you sure?"
msgstr ""
@@ -10057,6 +10314,9 @@ msgstr ""
msgid "GeoNodes|Replication slots"
msgstr ""
+msgid "GeoNodes|Replication status"
+msgstr ""
+
msgid "GeoNodes|Repositories"
msgstr ""
@@ -10267,6 +10527,9 @@ msgstr ""
msgid "Geo|Synchronization failed - %{error}"
msgstr ""
+msgid "Geo|Synchronization of %{itemTitle} is disabled."
+msgstr ""
+
msgid "Geo|The database is currently %{db_lag} behind the primary node."
msgstr ""
@@ -10334,7 +10597,7 @@ msgid "Getting started with releases"
msgstr ""
msgid "Git"
-msgstr ""
+msgstr "Git"
msgid "Git LFS is not enabled on this GitLab server, contact your admin."
msgstr ""
@@ -10579,6 +10842,9 @@ msgstr ""
msgid "Go to %{link_to_google_takeout}."
msgstr ""
+msgid "Go to <strong>Issues</strong> > <strong>Boards</strong> to access your personalized learning issue board."
+msgstr ""
+
msgid "Go to Pipelines"
msgstr ""
@@ -10678,9 +10944,6 @@ msgstr ""
msgid "Go to your snippets"
msgstr ""
-msgid "Golden Tanuki"
-msgstr ""
-
msgid "Google Cloud Platform"
msgstr ""
@@ -10702,6 +10965,9 @@ msgstr ""
msgid "Grafana URL"
msgstr ""
+msgid "Grafana response contains invalid json"
+msgstr ""
+
msgid "GrafanaIntegration|API Token"
msgstr ""
@@ -10750,9 +11016,6 @@ msgstr ""
msgid "Group %{group_name} was successfully created."
msgstr ""
-msgid "Group '%{group_name}' is being imported."
-msgstr ""
-
msgid "Group Audit Events"
msgstr ""
@@ -10789,9 +11052,6 @@ msgstr ""
msgid "Group by:"
msgstr ""
-msgid "Group could not be imported: %{errors}"
-msgstr ""
-
msgid "Group description"
msgstr ""
@@ -10819,9 +11079,15 @@ msgstr ""
msgid "Group has not been marked for deletion"
msgstr ""
+msgid "Group import could not be scheduled"
+msgstr ""
+
msgid "Group info:"
msgstr ""
+msgid "Group is required when cluster_type is :group"
+msgstr ""
+
msgid "Group maintainers can register group runners in the %{link}"
msgstr ""
@@ -10885,6 +11151,24 @@ msgstr ""
msgid "GroupActivyMetrics|Recent activity (last 90 days)"
msgstr ""
+msgid "GroupImport|Failed to import group."
+msgstr ""
+
+msgid "GroupImport|Group '%{group_name}' is being imported."
+msgstr ""
+
+msgid "GroupImport|Group could not be imported: %{errors}"
+msgstr ""
+
+msgid "GroupImport|Please wait while we import the group for you. Refresh at will."
+msgstr ""
+
+msgid "GroupImport|The group was successfully imported."
+msgstr ""
+
+msgid "GroupImport|Unable to process group import file"
+msgstr ""
+
msgid "GroupRoadmap|%{dateWord} – No end date"
msgstr ""
@@ -11152,6 +11436,9 @@ msgstr ""
msgid "Groups (%{groups})"
msgstr ""
+msgid "Groups and projects"
+msgstr ""
+
msgid "Groups can also be nested by creating %{subgroup_docs_link_start}subgroups%{subgroup_docs_link_end}."
msgstr ""
@@ -11197,6 +11484,33 @@ msgstr ""
msgid "GroupsEmptyState|You can manage your group member’s permissions and access to each project in the group."
msgstr ""
+msgid "GroupsNew|Contact an administrator to enable options for importing your group."
+msgstr ""
+
+msgid "GroupsNew|Create"
+msgstr ""
+
+msgid "GroupsNew|Create group"
+msgstr ""
+
+msgid "GroupsNew|GitLab group export"
+msgstr ""
+
+msgid "GroupsNew|Import"
+msgstr ""
+
+msgid "GroupsNew|Import group"
+msgstr ""
+
+msgid "GroupsNew|My Awesome Group"
+msgstr ""
+
+msgid "GroupsNew|No import options available"
+msgstr ""
+
+msgid "GroupsNew|To copy a GitLab group between installations, navigate to the group settings page for the original installation, generate an export file, and upload it here."
+msgstr ""
+
msgid "GroupsTree|Are you sure you want to leave the \"%{fullName}\" group?"
msgstr ""
@@ -11299,6 +11613,9 @@ msgstr ""
msgid "Helps reduce request volume for protected paths"
msgstr ""
+msgid "Here are all your projects in your group, including the one you just created. To start, let’s take a look at your personalized learning project which will help you learn about GitLab at your own pace."
+msgstr ""
+
msgid "Here you will find recent merge request activity"
msgstr ""
@@ -11312,6 +11629,9 @@ msgid "Hide chart"
msgid_plural "Hide charts"
msgstr[0] ""
+msgid "Hide details"
+msgstr ""
+
msgid "Hide file browser"
msgstr ""
@@ -11321,6 +11641,9 @@ msgstr ""
msgid "Hide host keys manual input"
msgstr ""
+msgid "Hide list"
+msgstr ""
+
msgid "Hide marketing-related entries from help"
msgstr ""
@@ -11340,9 +11663,6 @@ msgstr[0] ""
msgid "Hide values"
msgstr ""
-msgid "Hiding all labels"
-msgstr ""
-
msgid "High or unknown vulnerabilities present"
msgstr ""
@@ -11422,9 +11742,12 @@ msgid "I'd like to receive updates via email about GitLab"
msgstr ""
msgid "ID"
-msgstr ""
+msgstr "ID"
msgid "ID:"
+msgstr "ID:"
+
+msgid "IDE"
msgstr ""
msgid "IDE|Allow live previews of JavaScript projects in the Web IDE using CodeSandbox Live Preview."
@@ -11517,10 +11840,10 @@ msgstr ""
msgid "If enabled"
msgstr ""
-msgid "If enabled, access to projects will be validated on an external service using their classification label."
+msgid "If enabled, GitLab will handle Object Storage replication using Geo. %{linkStart}More information%{linkEnd}"
msgstr ""
-msgid "If enabled, and if object storage is enabled, GitLab will handle Object Storage replication using Geo"
+msgid "If enabled, access to projects will be validated on an external service using their classification label."
msgstr ""
msgid "If there is no previous license or if the previous license has expired, some GitLab functionality will be blocked until a new, valid license is uploaded."
@@ -11881,6 +12204,9 @@ msgid "Instance"
msgid_plural "Instances"
msgstr[0] ""
+msgid "Instance Configuration"
+msgstr ""
+
msgid "Instance Statistics visibility"
msgstr ""
@@ -11902,9 +12228,6 @@ msgstr ""
msgid "Integrations"
msgstr ""
-msgid "Integrations allow you to integrate GitLab with other applications"
-msgstr ""
-
msgid "Integrations|All details"
msgstr ""
@@ -12016,6 +12339,9 @@ msgstr ""
msgid "Invalid query"
msgstr ""
+msgid "Invalid repository bundle for snippet with id %{snippet_id}"
+msgstr ""
+
msgid "Invalid repository path"
msgstr ""
@@ -12034,6 +12360,9 @@ msgstr ""
msgid "Invalid two-factor code."
msgstr ""
+msgid "Invalid yaml"
+msgstr ""
+
msgid "Invitation"
msgstr ""
@@ -12253,15 +12582,24 @@ msgstr ""
msgid "It seems like the Dependency Scanning job ran successfully, but no dependencies have been detected in your project."
msgstr ""
+msgid "It seems that there is currently no available data for code coverage"
+msgstr ""
+
msgid "It's you"
msgstr ""
+msgid "Iteration"
+msgstr ""
+
msgid "Iteration changed to"
msgstr ""
msgid "Iteration removed"
msgstr ""
+msgid "Iteration updated"
+msgstr ""
+
msgid "Iterations"
msgstr ""
@@ -12292,12 +12630,18 @@ msgstr "一月"
msgid "January"
msgstr "一月"
+msgid "Jira Issues"
+msgstr ""
+
msgid "Jira import is already running."
msgstr ""
msgid "Jira integration not configured."
msgstr ""
+msgid "Jira project key is not configured"
+msgstr ""
+
msgid "Jira project: %{importProject}"
msgstr ""
@@ -12353,7 +12697,7 @@ msgid "JiraService|transition ids can have only numbers which can be split with
msgstr ""
msgid "Job"
-msgstr ""
+msgstr "作業"
msgid "Job Failed #%{build_id}"
msgstr ""
@@ -12478,19 +12822,25 @@ msgstr ""
msgid "Keep divergent refs"
msgstr ""
-msgid "Key"
+msgid "Kerberos access denied"
msgstr ""
+msgid "Key"
+msgstr "金鑰"
+
msgid "Key (PEM)"
msgstr ""
msgid "Key: %{key}"
msgstr ""
-msgid "Keyboard Shortcuts"
+msgid "Keyboard shortcuts"
msgstr ""
-msgid "Keyboard shortcuts"
+msgid "Keys"
+msgstr ""
+
+msgid "Ki"
msgstr ""
msgid "Kubernetes"
@@ -12532,6 +12882,9 @@ msgstr ""
msgid "LDAP"
msgstr ""
+msgid "LDAP Synchronization"
+msgstr ""
+
msgid "LDAP settings"
msgstr ""
@@ -12542,7 +12895,7 @@ msgid "LDAP sync in progress. This could take a few minutes. Refresh the page to
msgstr ""
msgid "LFS"
-msgstr ""
+msgstr "LFS"
msgid "LFS objects"
msgstr ""
@@ -12755,12 +13108,18 @@ msgstr ""
msgid "Learn more about custom project templates"
msgstr ""
+msgid "Learn more about deploying to AWS"
+msgstr ""
+
msgid "Learn more about deploying to a cluster"
msgstr ""
msgid "Learn more about group-level project templates"
msgstr ""
+msgid "Learn more about job dependencies"
+msgstr ""
+
msgid "Learn more about signing commits"
msgstr ""
@@ -12812,9 +13171,21 @@ msgstr ""
msgid "License History"
msgstr ""
+msgid "License ID:"
+msgstr ""
+
+msgid "License URL"
+msgstr ""
+
msgid "License-Check"
msgstr ""
+msgid "LicenseCompliance|%{docLinkStart}License Approvals%{docLinkEnd} are active"
+msgstr ""
+
+msgid "LicenseCompliance|%{docLinkStart}License Approvals%{docLinkEnd} are inactive"
+msgstr ""
+
msgid "LicenseCompliance|Add a license"
msgstr ""
@@ -12836,9 +13207,15 @@ msgstr ""
msgid "LicenseCompliance|Deny"
msgstr ""
+msgid "LicenseCompliance|Learn more about %{linkStart}License Approvals%{linkEnd}"
+msgstr ""
+
msgid "LicenseCompliance|License"
msgstr ""
+msgid "LicenseCompliance|License Approvals"
+msgstr ""
+
msgid "LicenseCompliance|License Compliance detected %d license and policy violation for the source branch only; approval required"
msgid_plural "LicenseCompliance|License Compliance detected %d licenses and policy violations for the source branch only; approval required"
msgstr[0] ""
@@ -13054,6 +13431,9 @@ msgstr "列表顯示"
msgid "List your Bitbucket Server repositories"
msgstr ""
+msgid "Lists"
+msgstr ""
+
msgid "Live preview"
msgstr ""
@@ -13151,10 +13531,10 @@ msgid "Low vulnerabilities present"
msgstr ""
msgid "MB"
-msgstr ""
+msgstr "MB"
msgid "MD5"
-msgstr ""
+msgstr "MD5"
msgid "MERGED"
msgstr ""
@@ -13237,6 +13617,9 @@ msgstr ""
msgid "Manage project labels"
msgstr "管ç†å°ˆæ¡ˆæ¨™ç±¤"
+msgid "Manage storage usage"
+msgstr ""
+
msgid "Manage two-factor authentication"
msgstr ""
@@ -13252,6 +13635,9 @@ msgstr ""
msgid "Manifest file import"
msgstr ""
+msgid "Manifest import"
+msgstr ""
+
msgid "Manual job"
msgstr ""
@@ -13399,6 +13785,12 @@ msgstr ""
msgid "Maximum field length"
msgstr ""
+msgid "Maximum file size is 2MB. Please select a smaller file."
+msgstr ""
+
+msgid "Maximum import size (MB)"
+msgstr ""
+
msgid "Maximum job timeout"
msgstr ""
@@ -13438,6 +13830,9 @@ msgstr ""
msgid "Maximum size of Elasticsearch bulk indexing requests."
msgstr ""
+msgid "Maximum size of import files."
+msgstr ""
+
msgid "Maximum size of individual attachments in comments."
msgstr ""
@@ -13489,6 +13884,9 @@ msgstr ""
msgid "Merge (when the pipeline succeeds)"
msgstr ""
+msgid "Merge Conflicts"
+msgstr ""
+
msgid "Merge Request"
msgstr "åˆä½µè«‹æ±‚"
@@ -13615,6 +14013,9 @@ msgstr ""
msgid "MergeRequests|Squash task canceled: another squash is already in progress."
msgstr ""
+msgid "MergeRequests|This project does not allow squashing commits when merge requests are accepted."
+msgstr ""
+
msgid "MergeRequests|Thread stays resolved"
msgstr ""
@@ -13768,6 +14169,12 @@ msgstr ""
msgid "MetricsSettings|Add a button to the metrics dashboard linking directly to your existing external dashboard."
msgstr ""
+msgid "MetricsSettings|Choose whether to display dashboard metrics in UTC or the user's local timezone."
+msgstr ""
+
+msgid "MetricsSettings|Dashboard timezone"
+msgstr ""
+
msgid "MetricsSettings|External dashboard URL"
msgstr ""
@@ -13777,6 +14184,12 @@ msgstr ""
msgid "MetricsSettings|Metrics Dashboard"
msgstr ""
+msgid "MetricsSettings|UTC (Coordinated Universal Time)"
+msgstr ""
+
+msgid "MetricsSettings|User's local timezone"
+msgstr ""
+
msgid "Metrics|Add metric"
msgstr ""
@@ -13792,6 +14205,9 @@ msgstr ""
msgid "Metrics|Create metric"
msgstr ""
+msgid "Metrics|Current"
+msgstr ""
+
msgid "Metrics|Delete metric"
msgstr ""
@@ -13841,6 +14257,9 @@ msgstr ""
msgid "Metrics|Max"
msgstr ""
+msgid "Metrics|Min"
+msgstr ""
+
msgid "Metrics|Must be a valid PromQL query."
msgstr ""
@@ -13856,6 +14275,9 @@ msgstr ""
msgid "Metrics|Refresh dashboard"
msgstr ""
+msgid "Metrics|Select a value"
+msgstr ""
+
msgid "Metrics|Star dashboard"
msgstr ""
@@ -13874,12 +14296,18 @@ msgstr ""
msgid "Metrics|There was an error getting annotations information."
msgstr ""
+msgid "Metrics|There was an error getting dashboard validation warnings information."
+msgstr ""
+
msgid "Metrics|There was an error getting deployment information."
msgstr ""
msgid "Metrics|There was an error getting environments information."
msgstr ""
+msgid "Metrics|There was an error getting options for variable \"%{name}\"."
+msgstr ""
+
msgid "Metrics|There was an error trying to validate your query"
msgstr ""
@@ -13922,6 +14350,9 @@ msgstr ""
msgid "Metrics|You're about to permanently delete this metric. This cannot be undone."
msgstr ""
+msgid "Metrics|Your dashboard schema is invalid. Edit the dashboard to correct the YAML schema."
+msgstr ""
+
msgid "Metrics|e.g. HTTP requests"
msgstr ""
@@ -13937,12 +14368,18 @@ msgstr ""
msgid "Metrics|e.g. req/sec"
msgstr ""
+msgid "Mi"
+msgstr ""
+
msgid "Microsoft Azure"
msgstr ""
msgid "Middleman project with Static Site Editor support"
msgstr ""
+msgid "Migrate your data from an external source like GitHub, Bitbucket, or another instance of GitLab."
+msgstr ""
+
msgid "Migrated %{success_count}/%{total_count} files."
msgstr ""
@@ -14076,6 +14513,9 @@ msgstr ""
msgid "Minimum capacity to be available before we schedule more mirrors preemptively."
msgstr ""
+msgid "Minimum interval in days"
+msgstr ""
+
msgid "Minimum length is %{minimum_password_length} characters"
msgstr ""
@@ -14206,7 +14646,7 @@ msgid "Mount point %{mounted_as} not found in %{model_class}."
msgstr ""
msgid "Move"
-msgstr ""
+msgstr "移動"
msgid "Move issue"
msgstr "移動議題"
@@ -14262,6 +14702,12 @@ msgstr ""
msgid "Multiple uploaders found: %{uploader_types}"
msgstr ""
+msgid "Must match with the %{codeStart}external_url%{codeEnd} in %{codeStart}/etc/gitlab/gitlab.rb%{codeEnd}."
+msgstr ""
+
+msgid "Must match with the %{codeStart}geo_node_name%{codeEnd} in %{codeStart}/etc/gitlab/gitlab.rb%{codeEnd}. %{linkStart}More information%{linkEnd}"
+msgstr ""
+
msgid "My Awesome Group"
msgstr ""
@@ -14371,15 +14817,21 @@ msgid "Never"
msgstr ""
msgid "New"
-msgstr ""
+msgstr "新增"
msgid "New Application"
msgstr ""
+msgid "New Branch"
+msgstr ""
+
+msgid "New Deploy Key"
+msgstr ""
+
msgid "New Environment"
msgstr ""
-msgid "New Geo Node"
+msgid "New File"
msgstr ""
msgid "New Group"
@@ -14395,15 +14847,15 @@ msgid "New Issue"
msgid_plural "New Issues"
msgstr[0] "新議題"
-msgid "New Iteration"
-msgstr ""
-
msgid "New Jira import"
msgstr ""
msgid "New Label"
msgstr ""
+msgid "New Merge Request"
+msgstr ""
+
msgid "New Milestone"
msgstr "新建里程碑"
@@ -14422,6 +14874,9 @@ msgstr ""
msgid "New Snippet"
msgstr ""
+msgid "New User"
+msgstr ""
+
msgid "New branch"
msgstr "新增分支"
@@ -14464,6 +14919,9 @@ msgstr "新議題"
msgid "New issue title"
msgstr ""
+msgid "New iteration"
+msgstr ""
+
msgid "New iteration created"
msgstr ""
@@ -14537,7 +14995,7 @@ msgid "Nickname"
msgstr ""
msgid "No"
-msgstr ""
+msgstr "å¦"
msgid "No %{header} for this request."
msgstr ""
@@ -14641,6 +15099,12 @@ msgstr ""
msgid "No grouping"
msgstr ""
+msgid "No iteration"
+msgstr ""
+
+msgid "No iterations to show"
+msgstr ""
+
msgid "No job log"
msgstr ""
@@ -14683,6 +15147,9 @@ msgstr "沒有è¦é¡¯ç¤ºçš„里程碑"
msgid "No other labels with such name or description"
msgstr ""
+msgid "No panels matching properties %{opts}"
+msgstr ""
+
msgid "No parent group"
msgstr ""
@@ -14716,6 +15183,9 @@ msgstr ""
msgid "No schedules"
msgstr "沒有計劃"
+msgid "No stack trace for this error"
+msgstr ""
+
msgid "No starrers matched your search"
msgstr ""
@@ -14728,7 +15198,7 @@ msgstr ""
msgid "No test coverage"
msgstr ""
-msgid "No thanks, don't show this again"
+msgid "No thanks"
msgstr ""
msgid "No vulnerabilities present"
@@ -14743,9 +15213,6 @@ msgstr ""
msgid "No, directly import the existing email addresses and usernames."
msgstr ""
-msgid "No, not interested right now"
-msgstr ""
-
msgid "No. of commits"
msgstr ""
@@ -14791,9 +15258,6 @@ msgstr "數據ä¸è¶³"
msgid "Not found."
msgstr ""
-msgid "Not helpful"
-msgstr ""
-
msgid "Not now"
msgstr ""
@@ -14950,6 +15414,9 @@ msgstr "關閉通知"
msgid "Notifications on"
msgstr "開啟通知"
+msgid "Notify users by email when sign-in location is not recognized"
+msgstr ""
+
msgid "Nov"
msgstr "å一月"
@@ -14996,7 +15463,7 @@ msgid "Number of files touched"
msgstr ""
msgid "OK"
-msgstr ""
+msgstr "確定"
msgid "Object Storage replication"
msgstr ""
@@ -15016,9 +15483,6 @@ msgstr "篩é¸"
msgid "Oh no!"
msgstr ""
-msgid "Ok let's go"
-msgstr ""
-
msgid "Oldest first"
msgstr ""
@@ -15031,16 +15495,49 @@ msgstr ""
msgid "On track"
msgstr ""
+msgid "OnDemandScans|Attached branch"
+msgstr ""
+
+msgid "OnDemandScans|Attached branch is where the scan job runs."
+msgstr ""
+
+msgid "OnDemandScans|Could not run the scan. Please try again."
+msgstr ""
+
msgid "OnDemandScans|Create new DAST scan"
msgstr ""
+msgid "OnDemandScans|DAST will scan the target URL and any discovered sub URLs."
+msgstr ""
+
+msgid "OnDemandScans|New on-demand DAST scan"
+msgstr ""
+
msgid "OnDemandScans|On-demand Scans"
msgstr ""
+msgid "OnDemandScans|On-demand scans run outside the DevOps cycle and find vulnerabilities in your projects. %{learnMoreLinkStart}Learn more%{learnMoreLinkEnd}"
+msgstr ""
+
+msgid "OnDemandScans|Only a passive scan can be performed on demand."
+msgstr ""
+
+msgid "OnDemandScans|Passive DAST Scan"
+msgstr ""
+
+msgid "OnDemandScans|Please enter a valid URL format, ex: http://www.example.com/home"
+msgstr ""
+
+msgid "OnDemandScans|Run this scan"
+msgstr ""
+
+msgid "OnDemandScans|Scan mode"
+msgstr ""
+
msgid "OnDemandScans|Schedule or run scans immediately against target sites. Currently available on-demand scan type: DAST. %{helpLinkStart}More information%{helpLinkEnd}"
msgstr ""
-msgid "Onboarding"
+msgid "OnDemandScans|Target URL"
msgstr ""
msgid "Once imported, repositories can be mirrored over SSH. Read more %{link_start}here%{link_end}."
@@ -15065,6 +15562,9 @@ msgstr ""
msgid "One or more of you personal access tokens were revoked"
msgstr ""
+msgid "One or more of your %{provider} projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
+msgstr ""
+
msgid "One or more of your Bitbucket projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
msgstr ""
@@ -15566,6 +16066,9 @@ msgstr ""
msgid "Parent epic is not present."
msgstr ""
+msgid "Parsing error for param :embed_json. %{message}"
+msgstr ""
+
msgid "Part of merge request changes"
msgstr ""
@@ -15578,6 +16081,9 @@ msgstr ""
msgid "Passed"
msgstr ""
+msgid "Passed on"
+msgstr ""
+
msgid "Password"
msgstr ""
@@ -15662,6 +16168,9 @@ msgstr ""
msgid "Perform common operations on GitLab project"
msgstr ""
+msgid "Performance and resource management"
+msgstr ""
+
msgid "Performance optimization"
msgstr ""
@@ -15869,7 +16378,7 @@ msgstr ""
msgid "Pipelines|Something went wrong while cleaning runners cache."
msgstr ""
-msgid "Pipelines|There are currently no %{scope} pipelines."
+msgid "Pipelines|There are currently no finished pipelines."
msgstr ""
msgid "Pipelines|There are currently no pipelines."
@@ -15980,6 +16489,9 @@ msgstr ""
msgid "Pipeline|Stop pipeline #%{pipelineId}?"
msgstr ""
+msgid "Pipeline|Tag name"
+msgstr ""
+
msgid "Pipeline|Trigger author"
msgstr ""
@@ -16029,7 +16541,7 @@ msgid "PlantUML"
msgstr ""
msgid "Play"
-msgstr ""
+msgstr "執行"
msgid "Play all manual"
msgstr ""
@@ -16061,12 +16573,18 @@ msgstr ""
msgid "Please check your email (%{email}) to verify that you own this address and unlock the power of CI/CD. Didn't receive it? %{resend_link}. Wrong email address? %{update_link}."
msgstr ""
+msgid "Please choose a file"
+msgstr ""
+
msgid "Please choose a group URL with no special characters."
msgstr ""
msgid "Please complete your profile with email address"
msgstr ""
+msgid "Please convert %{linkStart}them to Git%{linkEnd}, and go through the %{linkToImportFlow} again."
+msgstr ""
+
msgid "Please convert them to %{link_to_git}, and go through the %{link_to_import_flow} again."
msgstr ""
@@ -16124,6 +16642,9 @@ msgstr ""
msgid "Please provide a valid email address."
msgstr ""
+msgid "Please provide attributes to update"
+msgstr ""
+
msgid "Please refer to <a href=\"%{docs_url}\">%{docs_url}</a>"
msgstr ""
@@ -16859,6 +17380,9 @@ msgstr ""
msgid "Project has too many %{label_for_message} to search"
msgstr ""
+msgid "Project is required when cluster_type is :project"
+msgstr ""
+
msgid "Project members"
msgstr ""
@@ -16976,7 +17500,40 @@ msgstr ""
msgid "ProjectService|Comment"
msgstr ""
-msgid "ProjectService|Comment will be posted on each event"
+msgid "ProjectService|Event will be triggered by a push to the repository"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a commit is created/updated"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a confidential issue is created/updated/closed"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a deployment finishes"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a merge request is created/updated/merged"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a new tag is pushed to the repository"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a new, unique alert is recorded"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a pipeline status changes"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a wiki page is created/updated"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when an issue is created/updated/closed"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when someone adds a comment"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when someone adds a comment on a confidential issue"
msgstr ""
msgid "ProjectService|Perform common operations on GitLab project: %{project_name}"
@@ -17027,6 +17584,9 @@ msgstr ""
msgid "ProjectSettings|Enable 'Delete source branch' option by default"
msgstr ""
+msgid "ProjectSettings|Enable merge trains and pipelines for merged results"
+msgstr ""
+
msgid "ProjectSettings|Every merge creates a merge commit"
msgstr ""
@@ -17057,7 +17617,7 @@ msgstr ""
msgid "ProjectSettings|Forks"
msgstr ""
-msgid "ProjectSettings|Git Large File Storage"
+msgid "ProjectSettings|Git Large File Storage (LFS)"
msgstr ""
msgid "ProjectSettings|Internal"
@@ -17093,9 +17653,6 @@ msgstr ""
msgid "ProjectSettings|Merge options"
msgstr ""
-msgid "ProjectSettings|Merge pipelines will try to validate the post-merge result prior to merging"
-msgstr ""
-
msgid "ProjectSettings|Merge requests"
msgstr ""
@@ -17123,6 +17680,9 @@ msgstr ""
msgid "ProjectSettings|Pipelines"
msgstr ""
+msgid "ProjectSettings|Pipelines for merge requests must be enabled in the CI/CD configuration file, or pipelines could be unresolvable or dropped"
+msgstr ""
+
msgid "ProjectSettings|Pipelines must succeed"
msgstr ""
@@ -17150,6 +17710,9 @@ msgstr ""
msgid "ProjectSettings|Show link to create/view merge request when pushing from the command line"
msgstr ""
+msgid "ProjectSettings|Skipped pipelines are considered successful"
+msgstr ""
+
msgid "ProjectSettings|Snippets"
msgstr ""
@@ -17165,6 +17728,9 @@ msgstr ""
msgid "ProjectSettings|These checks must pass before merge requests can be merged"
msgstr ""
+msgid "ProjectSettings|This introduces the risk of merging changes that will not pass the pipeline."
+msgstr ""
+
msgid "ProjectSettings|This setting is applied on the server level and can be overridden by an admin."
msgstr ""
@@ -17342,15 +17908,27 @@ msgstr ""
msgid "ProjectsNew|Blank project"
msgstr ""
+msgid "ProjectsNew|Connect your external repository to GitLab CI/CD."
+msgstr ""
+
msgid "ProjectsNew|Contact an administrator to enable options for importing your project."
msgstr ""
msgid "ProjectsNew|Create"
msgstr ""
+msgid "ProjectsNew|Create a blank project to house your files, plan your work, and collaborate on code, among other things."
+msgstr ""
+
+msgid "ProjectsNew|Create blank project"
+msgstr ""
+
msgid "ProjectsNew|Create from template"
msgstr ""
+msgid "ProjectsNew|Create new project"
+msgstr ""
+
msgid "ProjectsNew|Creating project & repository."
msgstr ""
@@ -17375,6 +17953,9 @@ msgstr ""
msgid "ProjectsNew|Project description %{tag_start}(optional)%{tag_end}"
msgstr ""
+msgid "ProjectsNew|Run CI/CD for external repository"
+msgstr ""
+
msgid "ProjectsNew|Template"
msgstr ""
@@ -17636,6 +18217,9 @@ msgstr ""
msgid "Protected Branch"
msgstr ""
+msgid "Protected Branches"
+msgstr ""
+
msgid "Protected Environment"
msgstr ""
@@ -17648,6 +18232,9 @@ msgstr ""
msgid "Protected Tag"
msgstr ""
+msgid "Protected Tags"
+msgstr ""
+
msgid "Protected branches"
msgstr ""
@@ -17747,6 +18334,9 @@ msgstr ""
msgid "Provider"
msgstr ""
+msgid "Proxy support for this API is not available currently"
+msgstr ""
+
msgid "Pseudonymizer data collection"
msgstr ""
@@ -17771,6 +18361,9 @@ msgstr ""
msgid "Publish to status page"
msgstr ""
+msgid "Published on status page"
+msgstr ""
+
msgid "Publishes this issue to the associated status page."
msgstr ""
@@ -17945,6 +18538,9 @@ msgstr ""
msgid "Recipe"
msgstr ""
+msgid "Reconfigure"
+msgstr ""
+
msgid "Recover hidden stage"
msgstr ""
@@ -18030,6 +18626,9 @@ msgstr ""
msgid "Registration|Your profile"
msgstr ""
+msgid "Regulate approvals by authors/committers, based on compliance frameworks. Can be changed only at the instance level."
+msgstr ""
+
msgid "Rejected (closed)"
msgstr ""
@@ -18079,15 +18678,24 @@ msgstr ""
msgid "ReleaseAssetLinkType|Image"
msgstr ""
+msgid "ReleaseAssetLinkType|Images"
+msgstr ""
+
msgid "ReleaseAssetLinkType|Other"
msgstr ""
msgid "ReleaseAssetLinkType|Package"
msgstr ""
+msgid "ReleaseAssetLinkType|Packages"
+msgstr ""
+
msgid "ReleaseAssetLinkType|Runbook"
msgstr ""
+msgid "ReleaseAssetLinkType|Runbooks"
+msgstr ""
+
msgid "Releases"
msgstr ""
@@ -18100,6 +18708,9 @@ msgstr ""
msgid "Releases documentation"
msgstr ""
+msgid "Releases|New Release"
+msgstr ""
+
msgid "Release|Something went wrong while getting the release details"
msgstr ""
@@ -18172,6 +18783,9 @@ msgstr "刪除截止日期"
msgid "Remove fork relationship"
msgstr ""
+msgid "Remove from batch"
+msgstr ""
+
msgid "Remove from board"
msgstr ""
@@ -18361,6 +18975,12 @@ msgstr ""
msgid "Replication"
msgstr ""
+msgid "Replication enabled"
+msgstr ""
+
+msgid "Replication paused"
+msgstr ""
+
msgid "Reply by email"
msgstr ""
@@ -18388,15 +19008,15 @@ msgstr ""
msgid "Reported %{timeAgo} by %{reportedBy}"
msgstr ""
-msgid "Reported Resource Changes: %{addNum} to add, %{changeNum} to change, %{deleteNum} to delete"
-msgstr ""
-
msgid "Reporter"
msgstr ""
msgid "Reporting"
msgstr ""
+msgid "Reports"
+msgstr ""
+
msgid "Reports|%{combinedString} and %{resolvedString}"
msgstr ""
@@ -18434,6 +19054,9 @@ msgstr ""
msgid "Reports|Failure"
msgstr ""
+msgid "Reports|Identifier"
+msgstr ""
+
msgid "Reports|Metrics reports are loading"
msgstr ""
@@ -18446,6 +19069,9 @@ msgstr ""
msgid "Reports|Metrics reports failed loading results"
msgstr ""
+msgid "Reports|Scanner"
+msgstr ""
+
msgid "Reports|Severity"
msgstr ""
@@ -18494,15 +19120,33 @@ msgstr ""
msgid "Repository cleanup has started. You will receive an email once the cleanup operation is complete."
msgstr ""
+msgid "Repository files count over the limit"
+msgstr ""
+
+msgid "Repository has an invalid default branch name."
+msgstr ""
+
+msgid "Repository has more than one branch."
+msgstr ""
+
msgid "Repository has no locks."
msgstr ""
+msgid "Repository has tags."
+msgstr ""
+
msgid "Repository maintenance"
msgstr ""
msgid "Repository mirroring"
msgstr ""
+msgid "Repository must contain at least 1 file."
+msgstr ""
+
+msgid "Repository size is above the limit."
+msgstr ""
+
msgid "Repository static objects"
msgstr ""
@@ -18512,7 +19156,7 @@ msgstr ""
msgid "Repository sync capacity"
msgstr ""
-msgid "Repository: %{counter_repositories} / Wikis: %{counter_wikis} / Build Artifacts: %{counter_build_artifacts} / LFS: %{counter_lfs_objects}"
+msgid "Repository: %{counter_repositories} / Wikis: %{counter_wikis} / Build Artifacts: %{counter_build_artifacts} / LFS: %{counter_lfs_objects} / Snippets: %{counter_snippets}"
msgstr ""
msgid "RepositorySettingsAccessLevel|Select"
@@ -18524,6 +19168,9 @@ msgstr "申請權é™"
msgid "Request Headers"
msgstr ""
+msgid "Request details"
+msgstr ""
+
msgid "Request parameter %{param} is missing."
msgstr ""
@@ -18628,6 +19275,9 @@ msgstr "é‡ç½® Runner 註冊令牌"
msgid "Reset template"
msgstr ""
+msgid "Reset to project defaults"
+msgstr ""
+
msgid "Resetting the authorization key for this project will require updating the authorization key in every alert source it is enabled in."
msgstr ""
@@ -18724,9 +19374,6 @@ msgstr ""
msgid "Resume"
msgstr ""
-msgid "Resume replication"
-msgstr ""
-
msgid "Resync"
msgstr ""
@@ -19338,6 +19985,9 @@ msgstr ""
msgid "SecurityReports|False positive"
msgstr ""
+msgid "SecurityReports|Group Security Dashboard"
+msgstr ""
+
msgid "SecurityReports|Hide dismissed"
msgstr ""
@@ -19392,10 +20042,13 @@ msgstr ""
msgid "SecurityReports|Remove project from dashboard"
msgstr ""
-msgid "SecurityReports|Report type"
+msgid "SecurityReports|Return to dashboard"
msgstr ""
-msgid "SecurityReports|Return to dashboard"
+msgid "SecurityReports|Scan details"
+msgstr ""
+
+msgid "SecurityReports|Scanner type"
msgstr ""
msgid "SecurityReports|Security Dashboard"
@@ -19452,6 +20105,9 @@ msgstr ""
msgid "SecurityReports|There was an error while generating the report."
msgstr ""
+msgid "SecurityReports|Unable to add %{invalidProjectsMessage}"
+msgstr ""
+
msgid "SecurityReports|Unable to add %{invalidProjects}"
msgstr ""
@@ -19566,6 +20222,9 @@ msgstr "é¸æ“‡åˆ†æ”¯/標籤"
msgid "Select due date"
msgstr ""
+msgid "Select file"
+msgstr ""
+
msgid "Select group or project"
msgstr ""
@@ -19629,9 +20288,6 @@ msgstr "é¸æ“‡ç›®æ¨™åˆ†æ”¯"
msgid "Select the branch you want to set as the default for this project. All merge requests and commits will automatically be made against this branch unless you specify a different one."
msgstr ""
-msgid "Select the configured storage available for new repositories to be placed on."
-msgstr ""
-
msgid "Select the custom project template source group."
msgstr ""
@@ -19824,12 +20480,6 @@ msgstr ""
msgid "Service Desk is enabled but not yet active"
msgstr ""
-msgid "Service Desk is off"
-msgstr ""
-
-msgid "Service Desk is on"
-msgstr ""
-
msgid "Service Templates"
msgstr ""
@@ -19917,6 +20567,15 @@ msgstr ""
msgid "Set the milestone to %{milestone_reference}."
msgstr ""
+msgid "Set the number of concurrent requests this secondary node will make to the primary node while backfilling."
+msgstr ""
+
+msgid "Set the synchronization and verification capacity for the secondary node."
+msgstr ""
+
+msgid "Set the timeout in seconds to send a secondary node status to the primary and IPs allowed for the secondary nodes."
+msgstr ""
+
msgid "Set time estimate"
msgstr ""
@@ -19956,6 +20615,9 @@ msgstr ""
msgid "Set weight to %{weight}."
msgstr ""
+msgid "Set what should be replicated by choosing specific projects or groups by the secondary node."
+msgstr ""
+
msgid "SetPasswordToCloneLink|set a password"
msgstr "設置密碼"
@@ -20082,6 +20744,9 @@ msgstr ""
msgid "Show complete raw log"
msgstr ""
+msgid "Show details"
+msgstr ""
+
msgid "Show file browser"
msgstr ""
@@ -20091,10 +20756,13 @@ msgstr ""
msgid "Show latest version"
msgstr ""
+msgid "Show list"
+msgstr ""
+
msgid "Show me everything"
msgstr ""
-msgid "Show me how"
+msgid "Show me how to add a pipeline"
msgstr ""
msgid "Show me more advanced stuff"
@@ -20141,9 +20809,6 @@ msgstr ""
msgid "Showing all issues"
msgstr ""
-msgid "Showing all labels"
-msgstr ""
-
msgid "Showing last %{size} of log -"
msgstr ""
@@ -20255,9 +20920,6 @@ msgstr ""
msgid "Skip outdated deployment jobs"
msgstr ""
-msgid "Skip this for now"
-msgstr ""
-
msgid "Skipped"
msgstr ""
@@ -20402,6 +21064,9 @@ msgstr ""
msgid "Something went wrong while adding your award. Please try again."
msgstr ""
+msgid "Something went wrong while applying the batch of suggestions. Please try again."
+msgstr ""
+
msgid "Something went wrong while applying the suggestion. Please try again."
msgstr ""
@@ -20504,10 +21169,13 @@ msgstr ""
msgid "Something went wrong while updating a requirement."
msgstr ""
+msgid "Something went wrong while updating assignees"
+msgstr ""
+
msgid "Something went wrong while updating your list settings"
msgstr ""
-msgid "Something went wrong with your automatic subscription renewal"
+msgid "Something went wrong with your automatic subscription renewal."
msgstr ""
msgid "Something went wrong, unable to add %{project} to dashboard"
@@ -20525,6 +21193,9 @@ msgstr ""
msgid "Something went wrong, unable to search projects"
msgstr ""
+msgid "Something went wrong."
+msgstr ""
+
msgid "Something went wrong. Please try again."
msgstr ""
@@ -20708,6 +21379,9 @@ msgstr ""
msgid "Source code"
msgstr "æºä»£ç¢¼"
+msgid "Source code (%{fileExtension})"
+msgstr ""
+
msgid "Source is not available"
msgstr ""
@@ -20792,6 +21466,9 @@ msgstr ""
msgid "Stack trace"
msgstr ""
+msgid "Stacktrace snippet"
+msgstr ""
+
msgid "Stage"
msgstr ""
@@ -20897,6 +21574,9 @@ msgstr ""
msgid "Start thread & reopen %{noteable_name}"
msgstr ""
+msgid "Start using Directed Acyclic Graphs (DAG)"
+msgstr ""
+
msgid "Start your Free Gold Trial"
msgstr ""
@@ -21359,6 +22039,21 @@ msgstr ""
msgid "SuggestedColors|Very pale orange"
msgstr ""
+msgid "Suggestion is not applicable as the suggestion was not found."
+msgstr ""
+
+msgid "Suggestions are not applicable as one or more suggestions were not found."
+msgstr ""
+
+msgid "Suggestions are not applicable as their lines cannot overlap."
+msgstr ""
+
+msgid "Suggestions must all be on the same branch."
+msgstr ""
+
+msgid "Suggestions that change line count can't be added to batches, yet."
+msgstr ""
+
msgid "Suggestions:"
msgstr ""
@@ -21401,9 +22096,6 @@ msgstr ""
msgid "Synchronization disabled"
msgstr ""
-msgid "Synchronization of container repositories is disabled."
-msgstr ""
-
msgid "System"
msgstr ""
@@ -21432,7 +22124,7 @@ msgid "Table of Contents"
msgstr ""
msgid "Tag"
-msgstr ""
+msgstr "標籤"
msgid "Tag list:"
msgstr ""
@@ -21584,6 +22276,32 @@ msgstr ""
msgid "Terms of Service and Privacy Policy"
msgstr ""
+msgid "Terraform|%{number} Terraform report failed to generate"
+msgid_plural "Terraform|%{number} Terraform reports failed to generate"
+msgstr[0] ""
+
+msgid "Terraform|%{number} Terraform report was generated in your pipelines"
+msgid_plural "Terraform|%{number} Terraform reports were generated in your pipelines"
+msgstr[0] ""
+
+msgid "Terraform|A Terraform report failed to generate."
+msgstr ""
+
+msgid "Terraform|A Terraform report was generated in your pipelines."
+msgstr ""
+
+msgid "Terraform|Generating the report caused an error."
+msgstr ""
+
+msgid "Terraform|Reported Resource Changes: %{addNum} to add, %{changeNum} to change, %{deleteNum} to delete"
+msgstr ""
+
+msgid "Terraform|The Terraform report %{name} failed to generate."
+msgstr ""
+
+msgid "Terraform|The Terraform report %{name} was generated in your pipelines."
+msgstr ""
+
msgid "Test"
msgstr ""
@@ -21609,7 +22327,7 @@ msgstr ""
msgid "TestHooks|Ensure the project has CI pipelines."
msgstr ""
-msgid "TestHooks|Ensure the project has at least one commit."
+msgid "TestHooks|Ensure the project has deployments."
msgstr ""
msgid "TestHooks|Ensure the project has issues."
@@ -21709,7 +22427,7 @@ msgstr ""
msgid "The Prometheus server responded with \"bad request\". Please check your queries are correct and are supported in your Prometheus version. %{documentationLink}"
msgstr ""
-msgid "The URL defined on the primary node that secondary nodes should use to contact it. Defaults to URL"
+msgid "The URL defined on the primary node that secondary nodes should use to contact it."
msgstr ""
msgid "The URL to use for connecting to Elasticsearch. Use a comma-separated list to support clustering (e.g., \"http://localhost:9200, http://localhost:9201\")."
@@ -21718,6 +22436,9 @@ msgstr ""
msgid "The X509 Certificate to use when mutual TLS is required to communicate with the external authorization service. If left blank, the server certificate is still validated when accessing over HTTPS."
msgstr ""
+msgid "The above settings apply to all projects with the selected compliance framework(s)."
+msgstr ""
+
msgid "The amount of seconds after which a request to get a secondary node status will time out."
msgstr ""
@@ -22016,9 +22737,6 @@ msgstr "該階段æ¯æ¢æ•¸æ“šæ‰€èŠ±çš„時間"
msgid "The total stage shows the time it takes between creating an issue and deploying the code to production. The data will be automatically added once you have completed the full idea to production cycle."
msgstr ""
-msgid "The unique identifier for the Geo node. Must match %{geoNodeName} if it is set in gitlab.rb, otherwise it must match %{externalUrl} with a trailing slash"
-msgstr ""
-
msgid "The update action will time out after %{number_of_minutes} minutes. For big repositories, use a clone/push combination."
msgstr ""
@@ -22043,9 +22761,6 @@ msgstr ""
msgid "The user you are trying to deactivate has been active in the past %{minimum_inactive_days} days and cannot be deactivated"
msgstr ""
-msgid "The user-facing URL of the Geo node"
-msgstr ""
-
msgid "The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6."
msgstr "中ä½æ•¸æ˜¯å£¹å€‹æ•¸åˆ—中最中間的值。例如在 3ã€5ã€9 之間,中ä½æ•¸æ˜¯ 5。在 3ã€5ã€7ã€8 之間,中ä½æ•¸æ˜¯ (5 + 7)/ 2 = 6。"
@@ -22136,6 +22851,9 @@ msgstr ""
msgid "There was a problem fetching project branches."
msgstr ""
+msgid "There was a problem fetching project tags."
+msgstr ""
+
msgid "There was a problem fetching project users."
msgstr ""
@@ -22187,6 +22905,9 @@ msgstr ""
msgid "There was an error fetching the %{replicableType}"
msgstr ""
+msgid "There was an error fetching the Geo Settings"
+msgstr ""
+
msgid "There was an error fetching the Node's Groups"
msgstr ""
@@ -22307,6 +23028,12 @@ msgstr ""
msgid "This %{issuable} is locked. Only <strong>project members</strong> can comment."
msgstr ""
+msgid "This %{noteableTypeText} is %{confidentialLinkStart}confidential%{linkEnd} and %{lockedLinkStart}locked%{linkEnd}."
+msgstr ""
+
+msgid "This %{noteableTypeText} is locked."
+msgstr ""
+
msgid "This %{viewer} could not be displayed because %{reason}. You can %{options} instead."
msgstr ""
@@ -22325,6 +23052,9 @@ msgstr ""
msgid "This action can lead to data loss. To prevent accidental actions we ask you to confirm your intention."
msgstr ""
+msgid "This also resolves all related threads"
+msgstr ""
+
msgid "This also resolves the discussion"
msgstr ""
@@ -22415,6 +23145,9 @@ msgstr ""
msgid "This epic does not exist or you don't have sufficient permission."
msgstr ""
+msgid "This feature is currently in beta. We invite you to %{linkStart}give feedback%{linkEnd}."
+msgstr ""
+
msgid "This feature requires local storage to be enabled"
msgstr ""
@@ -22448,8 +23181,8 @@ msgstr ""
msgid "This is a Work in Progress"
msgstr ""
-msgid "This is a confidential issue."
-msgstr "這是個隱密議題。"
+msgid "This is a confidential %{noteableTypeText}."
+msgstr ""
msgid "This is a delayed job to run in %{remainingTime}"
msgstr ""
@@ -22475,9 +23208,6 @@ msgstr ""
msgid "This is your current session"
msgstr ""
-msgid "This issue is %{confidentialLinkStart}confidential%{linkEnd} and %{lockedLinkStart}locked%{linkEnd}."
-msgstr ""
-
msgid "This issue is confidential"
msgstr "這個議題是隱密的"
@@ -22487,9 +23217,6 @@ msgstr ""
msgid "This issue is in a child epic of the filtered epic"
msgstr ""
-msgid "This issue is locked."
-msgstr "這個議題已被鎖定。"
-
msgid "This job depends on other jobs with expired/erased artifacts: %{invalid_dependencies}"
msgstr ""
@@ -22589,6 +23316,9 @@ msgstr ""
msgid "This license has already expired."
msgstr ""
+msgid "This link points to external content"
+msgstr ""
+
msgid "This may expose confidential information as the selected fork is in another namespace that can have other members."
msgstr ""
@@ -22616,6 +23346,9 @@ msgstr ""
msgid "This page will be removed in a future release."
msgstr ""
+msgid "This pipeline does not use the %{codeStart}needs%{codeEnd} keyword and can't be represented as a directed acyclic graph."
+msgstr ""
+
msgid "This pipeline makes use of a predefined CI/CD configuration enabled by %{strongStart}Auto DevOps.%{strongEnd}"
msgstr ""
@@ -22844,6 +23577,9 @@ msgstr ""
msgid "Time from last commit to merge"
msgstr ""
+msgid "Time in seconds"
+msgstr ""
+
msgid "Time in seconds GitLab will wait for a response from the external service. When the service does not respond in time, access will be denied."
msgstr ""
@@ -23039,7 +23775,7 @@ msgid "Titles and Descriptions"
msgstr ""
msgid "To"
-msgstr ""
+msgstr "到"
msgid "To %{link_to_help} of your domain, add the above key to a TXT record within to your DNS configuration."
msgstr ""
@@ -23263,9 +23999,15 @@ msgstr ""
msgid "Total artifacts size: %{total_size}"
msgstr ""
+msgid "Total cores (CPUs)"
+msgstr ""
+
msgid "Total issues"
msgstr ""
+msgid "Total memory (GB)"
+msgstr ""
+
msgid "Total test time for all commits/merges"
msgstr "所有æ交和åˆä½µçš„總測試時間"
@@ -23467,9 +24209,12 @@ msgstr ""
msgid "U2F only works with HTTPS-enabled websites. Contact your administrator for more details."
msgstr ""
-msgid "URL"
+msgid "UI Development Kit"
msgstr ""
+msgid "URL"
+msgstr "URL"
+
msgid "URL is required"
msgstr ""
@@ -23539,9 +24284,6 @@ msgstr ""
msgid "Unable to load the merge request widget. Try reloading the page."
msgstr ""
-msgid "Unable to process group import file"
-msgstr ""
-
msgid "Unable to resolve"
msgstr ""
@@ -23557,6 +24299,9 @@ msgstr ""
msgid "Unable to sign you in to the group with SAML due to \"%{reason}\""
msgstr ""
+msgid "Unable to suggest a path. Please refresh and try again."
+msgstr ""
+
msgid "Unable to update label prioritization at this time"
msgstr ""
@@ -23572,6 +24317,9 @@ msgstr ""
msgid "Unarchiving the project will restore people's ability to make changes to it. The repository can be committed to, and issues, comments, and other entities can be created. %{strong_start}Once active, this project shows up in the search and on the dashboard.%{strong_end}"
msgstr ""
+msgid "Unassign from commenting user"
+msgstr ""
+
msgid "Unblock"
msgstr ""
@@ -23647,6 +24395,9 @@ msgstr ""
msgid "Unreachable"
msgstr ""
+msgid "Unrecognized cluster type"
+msgstr ""
+
msgid "Unresolve"
msgstr ""
@@ -23707,6 +24458,9 @@ msgstr ""
msgid "Unsubscribes from this %{quick_action_target}."
msgstr ""
+msgid "Unsupported todo type passed. Supported todo types are: %{todo_types}"
+msgstr ""
+
msgid "Until"
msgstr ""
@@ -23734,6 +24488,9 @@ msgstr ""
msgid "Update approval rule"
msgstr ""
+msgid "Update approvers"
+msgstr ""
+
msgid "Update failed"
msgstr ""
@@ -23743,6 +24500,9 @@ msgstr ""
msgid "Update it"
msgstr ""
+msgid "Update iteration"
+msgstr ""
+
msgid "Update now"
msgstr ""
@@ -23788,7 +24548,7 @@ msgstr ""
msgid "Updated at"
msgstr ""
-msgid "Updated to"
+msgid "Updated to %{linkStart}chart v%{linkEnd}"
msgstr ""
msgid "Updating"
@@ -23818,6 +24578,9 @@ msgstr ""
msgid "Upload CSV file"
msgstr ""
+msgid "Upload License"
+msgstr ""
+
msgid "Upload New File"
msgstr "上傳新文件"
@@ -23899,16 +24662,19 @@ msgstr ""
msgid "UsageQuota|Pipelines"
msgstr ""
+msgid "UsageQuota|Purchase more storage"
+msgstr ""
+
msgid "UsageQuota|Repositories"
msgstr ""
msgid "UsageQuota|Repository"
msgstr ""
-msgid "UsageQuota|Storage"
+msgid "UsageQuota|Snippets"
msgstr ""
-msgid "UsageQuota|Storage usage:"
+msgid "UsageQuota|Storage"
msgstr ""
msgid "UsageQuota|This namespace has no projects which use shared runners"
@@ -23941,6 +24707,12 @@ msgstr ""
msgid "UsageQuota|Wikis"
msgstr ""
+msgid "UsageQuota|You used: %{usage} %{limit}"
+msgstr ""
+
+msgid "UsageQuota|out of %{formattedLimit} of your namespace storage"
+msgstr ""
+
msgid "Use %{code_start}::%{code_end} to create a %{link_start}scoped label set%{link_end} (eg. %{code_start}priority::1%{code_end})"
msgstr ""
@@ -24007,6 +24779,9 @@ msgstr ""
msgid "User IDs"
msgstr ""
+msgid "User Lists can only be created and modified with %{linkStart}the API%{linkEnd}"
+msgstr ""
+
msgid "User OAuth applications"
msgstr ""
@@ -24031,6 +24806,9 @@ msgstr ""
msgid "User key was successfully removed."
msgstr ""
+msgid "User list %{name} will be removed. Are you sure?"
+msgstr ""
+
msgid "User map"
msgstr ""
@@ -24052,148 +24830,10 @@ msgstr ""
msgid "User was successfully updated."
msgstr ""
-msgid "UserOnboardingTour|%{activeTour}/%{totalTours}"
-msgstr ""
-
-msgid "UserOnboardingTour|%{completed}/%{total} steps completed"
-msgstr ""
-
-msgid "UserOnboardingTour|%{emphasisStart}Well done!%{emphasisEnd}%{lineBreak}%{lineBreak}That's it for our guided tour, congratulations for making it all the way to the end!%{lineBreak}%{lineBreak}We hope this gave you a good overview of GitLab and how it can help you. We'll now show you how to create your own project and invite your colleagues."
-msgstr ""
-
-msgid "UserOnboardingTour|Adding other members to a project is done through Project Settings. Click on %{emphasisStart}Settings%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Alright, that's it for Commits. Let's take a look at the %{emphasisStart}Branches%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Awesome! Now click on %{emphasisStart}Members%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Click on one of the %{emphasisStart}Compare%{emphasisEnd} buttons to compare a branch to master."
-msgstr ""
-
-msgid "UserOnboardingTour|Click on one of the %{emphasisStart}pipeline IDs%{emphasisEnd} to see the details of a pipeline."
-msgstr ""
-
-msgid "UserOnboardingTour|Click to open the latest commit to see its details."
-msgstr ""
-
-msgid "UserOnboardingTour|Close 'Learn GitLab'"
-msgstr ""
-
-msgid "UserOnboardingTour|Commits are shown in chronological order and can be filtered by the commit message or by the branch."
-msgstr ""
-
-msgid "UserOnboardingTour|Create a project"
-msgstr ""
-
-msgid "UserOnboardingTour|Exit 'Learn GitLab'"
-msgstr ""
-
-msgid "UserOnboardingTour|Got it"
-msgstr ""
-
-msgid "UserOnboardingTour|Great job! %{clapHands} We hope the tour was helpful and that you learned how to use GitLab.%{lineBreak}%{lineBreak}We'd love to get your feedback on this tour.%{lineBreak}%{lineBreak}%{emphasisStart}How helpful would you say this guided tour was?%{emphasisEnd}%{lineBreak}%{lineBreak}"
-msgstr ""
-
-msgid "UserOnboardingTour|Guided GitLab Tour"
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can compare the changes of this branch to another one. Changes are divided by files so that it's easier to see what was changed where."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can create a project from scratch, start with a template or import a repository from other platforms. Whatever you choose, we'll guide you through the process.%{lineBreak}%{lineBreak}Fill in your new project information and click on %{emphasisStart}Create Project%{emphasisEnd} to progress to the next step."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can see the breakdown of the pipelines: its stages and jobs in each of the stages and their status.%{lineBreak}%{lineBreak}Our CI/CD pipelines are quite complex, most of our users have fewer and simpler pipelines."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can see the current members of the project (just you at the moment) and invite new members.%{lineBreak}%{lineBreak}You can invite multiple members at once (existing GitLab users or invite by email) and you can also set their roles and permissions.%{lineBreak}%{lineBreak}Add a few members and click on %{emphasisStart}Add to project%{emphasisEnd} to complete this step."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can see what changes were made with this commit, on what branch and if there's a related merge request. The status of the pipeline will also show up if CI/CD is set up.%{lineBreak}%{lineBreak}You can also comment on the lines of code that were changed and start a discussion with your colleagues!"
-msgstr ""
-
-msgid "UserOnboardingTour|Here's an overview of branches in the %{emphasisStart}%{projectName}%{emphasisEnd} project. They're split into Active and Stale.%{lineBreak}%{lineBreak}From here, you can create a new merge request from a branch, or compare the branch to any other branch in the project. By default, it will compare it to the master branch."
-msgstr ""
-
-msgid "UserOnboardingTour|Invite colleagues"
-msgstr ""
-
-msgid "UserOnboardingTour|Issues are great for communicating and keeping track of progress in GitLab. These are all issues that are open in the %{emphasisStart}%{projectName}%{emphasisEnd}.%{lineBreak}%{lineBreak}You can help us improve GitLab by contributing work to issues that are labeled <span class=\"badge color-label accept-mr-label\">Accepting merge requests</span>.%{lineBreak}%{lineBreak}This list can be filtered by labels, milestones, assignees, authors... We'll show you how it looks when the list is filtered by a label."
-msgstr ""
-
-msgid "UserOnboardingTour|Learn GitLab"
-msgstr ""
-
-msgid "UserOnboardingTour|Let's take a closer look at a merge request. Click on the title of one."
-msgstr ""
-
-msgid "UserOnboardingTour|Let's take a closer look at all the commits. Click on %{emphasisStart}Commits%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Let's take a closer look at the repository of this project. Click on %{emphasisStart}Repository%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|No thanks"
-msgstr ""
-
-msgid "UserOnboardingTour|Ok, let's go"
-msgstr ""
-
-msgid "UserOnboardingTour|Ok, show me"
-msgstr ""
-
-msgid "UserOnboardingTour|Open one of the issues by clicking on its title."
-msgstr ""
-
-msgid "UserOnboardingTour|Restart this step"
-msgstr ""
-
-msgid "UserOnboardingTour|Skip this step"
-msgstr ""
-
-msgid "UserOnboardingTour|Sweet! Your project was created and is ready to be used.%{lineBreak}%{lineBreak}You can start adding files to the repository or clone it. One last thing we want to show you is how to invite your colleagues to your new project."
-msgstr ""
-
-msgid "UserOnboardingTour|Take a look. Here's a nifty menu for quickly creating issues, merge requests, snippets, projects and groups. Click on it and select \"New project\" from the \"GitLab\" section to get started."
-msgstr ""
-
-msgid "UserOnboardingTour|Thanks for taking the guided tour. Remember, if you want to go through it again, you can start %{emphasisStart}Learn GitLab%{emphasisEnd} in the help menu on the top right."
-msgstr ""
-
-msgid "UserOnboardingTour|Thanks for the feedback! %{thumbsUp}"
-msgstr ""
-
-msgid "UserOnboardingTour|That's it for issues. Let'st take a look at %{emphasisStart}Merge Requests%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|That's it for merge requests. Now for the final part of this guided tour - the %{emphasisStart}CI/CD%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|That's it for the Repository. Let's take a look at the %{emphasisStart}Issues%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|The structure of this page is very similar to issues. Status, description, discussion and the sidebar are all here.%{lineBreak}%{lineBreak}But take a look below the description and you'll notice that there's more information about the merge request, the CI/CD pipeline and the options for approving it.%{lineBreak}%{lineBreak}Alongside the discussion you can also see more information about commits in this merge request, the status of pipelines and review all changes that were made."
-msgstr ""
-
-msgid "UserOnboardingTour|There's a lot of information here but don't worry, we'll go through it.%{lineBreak}%{lineBreak}On the top you can see the status of the issue and when it was opened and by whom. Directly below it is the issue description and below that are other %{emphasisStart}related issues%{emphasisEnd} and %{emphasisStart}merge requests%{emphasisEnd} (if any). Then below that is the %{emphasisStart}discussion%{emphasisEnd}, that's where most of the communication happens.%{lineBreak}%{lineBreak}On the right, there's a sidebar where you can view/change the %{emphasisStart}assignee, milestone, due date, labels, weight%{emphasisEnd}, etc."
-msgstr ""
-
-msgid "UserOnboardingTour|These are all the CI/CD pipelines we have for our %{emphasisStart}%{projectName}%{emphasisEnd} project.%{lineBreak}%{lineBreak}Here you can see the status of each pipeline, for what commit it's running for, its stages and the status for them."
+msgid "UserList|Delete %{name}?"
msgstr ""
-msgid "UserOnboardingTour|These are all the issues that are available for community contributions. Let's take a closer look at one of them."
-msgstr ""
-
-msgid "UserOnboardingTour|This is an overview of all merge requests in this project. Similarly to the issues overview it can be filtered down by things like labels, milestones, authors, assignees, etc."
-msgstr ""
-
-msgid "UserOnboardingTour|This is the repository for the %{emphasisStart}%{projectName}%{emphasisEnd} project. All our code is stored here. Feel free to explore and take a closer look at folders and files.%{lineBreak}%{lineBreak}Above the file structure you can see the latest commit, who the author is and the status of the CI/CD pipeline.%{lineBreak}%{lineBreak}If you scroll down below the file structure, you'll find the Readme of this project. This is defined in the README.md file at the root of the repository."
-msgstr ""
-
-msgid "UserOnboardingTour|Welcome to the project overview of the %{emphasisStart}%{projectName}%{emphasisEnd} project. This is the project that we use to work on GitLab. At first, a project seems like a simple repository, but at GitLab, a project is so much more.%{lineBreak}%{lineBreak}You can create projects for hosting your codebase, use it as an issue tracker, collaborate on code, and continuously build, test, and deploy your app with built-in GitLab CI/CD."
+msgid "UserList|created %{timeago}"
msgstr ""
msgid "UserProfile|Activity"
@@ -24346,6 +24986,9 @@ msgstr ""
msgid "UsersSelect|Unassigned"
msgstr ""
+msgid "Using %{codeStart}needs%{codeEnd} allows jobs to run before their stage is reached, as soon as their individual dependencies are met, which speeds up your pipelines."
+msgstr ""
+
msgid "Using %{code_start}::%{code_end} denotes a %{link_start}scoped label set%{link_end}"
msgstr ""
@@ -24430,9 +25073,6 @@ msgstr ""
msgid "Versions"
msgstr ""
-msgid "Very helpful"
-msgstr ""
-
msgid "View Documentation"
msgstr ""
@@ -24579,6 +25219,9 @@ msgstr "公開"
msgid "VisibilityLevel|Unknown"
msgstr "未知"
+msgid "Visit settings page"
+msgstr ""
+
msgid "VisualReviewApp|%{stepStart}Step 1%{stepEnd}. Copy the following script:"
msgstr ""
@@ -24672,6 +25315,9 @@ msgstr ""
msgid "VulnerabilityManagement|Something went wrong while trying to delete the comment. Please try again later."
msgstr ""
+msgid "VulnerabilityManagement|Something went wrong while trying to refresh the vulnerability. Please try again later."
+msgstr ""
+
msgid "VulnerabilityManagement|Something went wrong while trying to retrieve the vulnerability history. Please try again later."
msgstr ""
@@ -24714,6 +25360,9 @@ msgstr ""
msgid "Vulnerability|Class"
msgstr ""
+msgid "Vulnerability|Crash Address"
+msgstr ""
+
msgid "Vulnerability|Description"
msgstr ""
@@ -24789,9 +25438,6 @@ msgstr ""
msgid "We couldn't reach the Prometheus server. Either the server no longer exists or the configuration details need updating."
msgstr ""
-msgid "We created a short guided tour that will help you learn the basics of GitLab and how it will help you be better at your job. It should only take a couple of minutes. You will be guided by two types of helpers, best recognized by their color."
-msgstr ""
-
msgid "We detected potential spam in the %{humanized_resource_name}. Please solve the reCAPTCHA to proceed."
msgstr ""
@@ -24825,6 +25471,9 @@ msgstr ""
msgid "We've found no vulnerabilities"
msgstr ""
+msgid "Web Application Firewall"
+msgstr ""
+
msgid "Web IDE"
msgstr "ç¶²é  IDE"
@@ -24885,9 +25534,6 @@ msgstr ""
msgid "Welcome to GitLab.com<br>@%{name}!"
msgstr ""
-msgid "Welcome to the Guided GitLab Tour"
-msgstr ""
-
msgid "Welcome to the guided GitLab tour"
msgstr ""
@@ -24934,9 +25580,6 @@ msgstr ""
msgid "While it's rare to have no vulnerabilities, it can happen. In any event, we ask that you please double check your settings to make sure you've set up your dashboard correctly."
msgstr ""
-msgid "White helpers give contextual information."
-msgstr ""
-
msgid "Who can be an approver?"
msgstr ""
@@ -24956,7 +25599,7 @@ msgid "Who will be using this GitLab trial?"
msgstr ""
msgid "Wiki"
-msgstr ""
+msgstr "Wiki"
msgid "Wiki was successfully updated."
msgstr ""
@@ -24982,12 +25625,18 @@ msgstr ""
msgid "WikiEmptyIssueMessage|Suggest wiki improvement"
msgstr ""
+msgid "WikiEmptyIssueMessage|You must be a group member in order to add wiki pages. If you have suggestions for how to improve the wiki for this group, consider opening an issue in the %{issues_link}."
+msgstr ""
+
msgid "WikiEmptyIssueMessage|You must be a project member in order to add wiki pages. If you have suggestions for how to improve the wiki for this project, consider opening an issue in the %{issues_link}."
msgstr ""
msgid "WikiEmptyIssueMessage|issue tracker"
msgstr ""
+msgid "WikiEmpty|A wiki is where you can store all the details about your group. This can include why you've created it, its principles, how to use it, and so on."
+msgstr ""
+
msgid "WikiEmpty|A wiki is where you can store all the details about your project. This can include why you've created it, its principles, how to use it, and so on."
msgstr ""
@@ -24997,12 +25646,21 @@ msgstr ""
msgid "WikiEmpty|Suggest wiki improvement"
msgstr ""
+msgid "WikiEmpty|The wiki lets you write documentation for your group"
+msgstr ""
+
msgid "WikiEmpty|The wiki lets you write documentation for your project"
msgstr ""
+msgid "WikiEmpty|This group has no wiki pages"
+msgstr ""
+
msgid "WikiEmpty|This project has no wiki pages"
msgstr ""
+msgid "WikiEmpty|You must be a group member in order to add wiki pages."
+msgstr ""
+
msgid "WikiEmpty|You must be a project member in order to add wiki pages."
msgstr ""
@@ -25133,7 +25791,7 @@ msgid "Wrong extern UID provided. Make sure Auth0 is configured correctly."
msgstr ""
msgid "Yes"
-msgstr ""
+msgstr "是"
msgid "Yes or No"
msgstr ""
@@ -25151,7 +25809,7 @@ msgid "Yesterday"
msgstr "昨天"
msgid "You"
-msgstr ""
+msgstr "您"
msgid "You are about to delete %{domain} from your instance. This domain will no longer be available to any Knative application."
msgstr ""
@@ -25279,15 +25937,15 @@ msgstr ""
msgid "You can now export your security dashboard to a CSV report."
msgstr ""
+msgid "You can now manage alert endpoint configuration in the Alerts section on the Operations settings page. Fields on this page have been deprecated."
+msgstr ""
+
msgid "You can now submit a merge request to get this change into the original branch."
msgstr ""
msgid "You can now submit a merge request to get this change into the original project."
msgstr ""
-msgid "You can only add files when you are on a branch"
-msgstr "åªèƒ½åœ¨åˆ†æ”¯ä¸Šæ·»åŠ æ–‡ä»¶"
-
msgid "You can only edit files when you are on a branch"
msgstr ""
@@ -25507,7 +26165,7 @@ msgstr ""
msgid "You need to upload a Google Takeout archive."
msgstr ""
-msgid "You reached %{usage_in_percent} of %{namespace_name}'s capacity (%{used_storage} of %{storage_limit})"
+msgid "You reached %{usage_in_percent} of %{namespace_name}'s storage capacity (%{used_storage} of %{storage_limit})"
msgstr ""
msgid "You tried to fork %{link_to_the_project} but it failed for the following reason:"
@@ -25810,13 +26468,13 @@ msgstr ""
msgid "Your subscription expired!"
msgstr ""
-msgid "Your subscription has been downgraded"
+msgid "Your subscription has been downgraded."
msgstr ""
-msgid "Your subscription will automatically renew in %{remaining_days}"
+msgid "Your subscription will automatically renew in %{remaining_days}."
msgstr ""
-msgid "Your subscription will expire in %{remaining_days}"
+msgid "Your subscription will expire in %{remaining_days}."
msgstr ""
msgid "Zoom meeting added"
@@ -25848,7 +26506,7 @@ msgid "added a Zoom call to this issue"
msgstr ""
msgid "ago"
-msgstr ""
+msgstr "å‰"
msgid "alert"
msgstr ""
@@ -25895,11 +26553,8 @@ msgstr ""
msgid "branch name"
msgstr "分支å稱"
-msgid "build pipeline reference mismatch"
-msgstr ""
-
msgid "by"
-msgstr ""
+msgstr "來自"
msgid "by %{user}"
msgstr ""
@@ -25916,6 +26571,9 @@ msgstr ""
msgid "cannot block others"
msgstr ""
+msgid "cannot contain HTML/XML tags, including any word between angle brackets (<,>)."
+msgstr ""
+
msgid "cannot include leading slash or directory traversal."
msgstr ""
@@ -25946,50 +26604,6 @@ msgstr ""
msgid "ciReport|%{remainingPackagesCount} more"
msgstr ""
-msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability"
-msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities"
-msgstr[0] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability for the source branch only"
-msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities for the source branch only"
-msgstr[0] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerability"
-msgid_plural "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerabilities"
-msgstr[0] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new vulnerability"
-msgid_plural "ciReport|%{reportType} %{status} detected %{newCount} new vulnerabilities"
-msgstr[0] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities for the source branch only"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{fixedCount} fixed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} vulnerability for the source branch only"
-msgid_plural "ciReport|%{reportType} %{status} detected %{newCount} vulnerabilities for the source branch only"
-msgstr[0] ""
-
-msgid "ciReport|%{reportType} %{status} detected no new vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected no vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected no vulnerabilities for the source branch only"
-msgstr ""
-
msgid "ciReport|%{reportType} is loading"
msgstr ""
@@ -26008,7 +26622,7 @@ msgstr ""
msgid "ciReport|All projects"
msgstr ""
-msgid "ciReport|All report types"
+msgid "ciReport|All scanner types"
msgstr ""
msgid "ciReport|All severities"
@@ -26032,6 +26646,9 @@ msgstr ""
msgid "ciReport|Container scanning detects known vulnerabilities in your docker images."
msgstr ""
+msgid "ciReport|Coverage Fuzzing"
+msgstr ""
+
msgid "ciReport|Create a merge request to implement this solution, or download and apply the patch manually."
msgstr ""
@@ -26189,6 +26806,9 @@ msgstr ""
msgid "customize"
msgstr ""
+msgid "data"
+msgstr ""
+
msgid "date must not be after 9999-12-31"
msgstr ""
@@ -26257,6 +26877,9 @@ msgstr ""
msgid "entries cannot contain HTML tags"
msgstr ""
+msgid "epic"
+msgstr ""
+
msgid "error"
msgstr ""
@@ -26278,9 +26901,6 @@ msgstr ""
msgid "expires on %{timebox_due_date}"
msgstr ""
-msgid "external_url"
-msgstr ""
-
msgid "failed"
msgstr ""
@@ -26315,9 +26935,6 @@ msgstr ""
msgid "from"
msgstr ""
-msgid "geo_node_name"
-msgstr ""
-
msgid "group"
msgstr ""
@@ -26339,9 +26956,6 @@ msgstr ""
msgid "https://your-bitbucket-server"
msgstr ""
-msgid "image"
-msgstr ""
-
msgid "image diff"
msgstr ""
@@ -26484,6 +27098,9 @@ msgstr ""
msgid "load it anyway"
msgstr ""
+msgid "loading"
+msgstr ""
+
msgid "locked by %{path_lock_user_name} %{created_at}"
msgstr ""
@@ -26611,9 +27228,6 @@ msgstr ""
msgid "mrWidget|Deployment statistics are not available currently"
msgstr ""
-msgid "mrWidget|Detect issues before deployment with a CI pipeline that continuously tests your code. We created a quick guide that will show you how to create one. Make your code more secure and more robust in just a minute."
-msgstr ""
-
msgid "mrWidget|Did not close"
msgstr ""
@@ -26776,6 +27390,9 @@ msgstr ""
msgid "mrWidget|To approve this merge request, please enter your password. This project requires all approvals to be authenticated."
msgstr ""
+msgid "mrWidget|Use %{linkStart}CI pipelines to test your code%{linkEnd}, simply add a GitLab CI configuration file to your project. It only takes a minute to make your code more secure and robust."
+msgstr ""
+
msgid "mrWidget|When this merge request is ready, remove the WIP: prefix from the title to allow it to be merged"
msgstr ""
@@ -26812,11 +27429,14 @@ msgstr ""
msgid "must be greater than start date"
msgstr ""
+msgid "must contain only valid frameworks"
+msgstr ""
+
msgid "my-awesome-group"
msgstr ""
msgid "n/a"
-msgstr ""
+msgstr "ä¸é©ç”¨"
msgid "need attention"
msgstr ""
@@ -26824,6 +27444,9 @@ msgstr ""
msgid "needs to be between 10 minutes and 1 month"
msgstr ""
+msgid "never"
+msgstr ""
+
msgid "never expires"
msgstr ""
@@ -26851,6 +27474,9 @@ msgstr "通知郵件"
msgid "nounSeries|%{firstItem} and %{lastItem}"
msgstr ""
+msgid "nounSeries|%{item}"
+msgstr ""
+
msgid "nounSeries|%{item}, %{nextItem}"
msgstr ""
@@ -26982,6 +27608,9 @@ msgstr ""
msgid "revised"
msgstr ""
+msgid "satisfied"
+msgstr ""
+
msgid "score"
msgstr ""
@@ -27012,6 +27641,9 @@ msgstr ""
msgid "severity|Unknown"
msgstr ""
+msgid "should be an array of %{object_name} objects"
+msgstr ""
+
msgid "should be greater than or equal to %{access} inherited membership from group %{group_name}"
msgstr ""
@@ -27066,10 +27698,13 @@ msgstr ""
msgid "suggestPipeline|2/2: Commit your changes"
msgstr ""
-msgid "suggestPipeline|Commit the changes and your pipeline will automatically run for the first time."
+msgid "suggestPipeline|Choose %{boldStart}Code Quality%{boldEnd} to add a pipeline that tests the quality of your code."
+msgstr ""
+
+msgid "suggestPipeline|The template is ready! You can now commit it to create your first pipeline."
msgstr ""
-msgid "suggestPipeline|We recommend the %{boldStart}Code Quality%{boldEnd} template, which will add a report widget to your Merge Requests. This way you’ll learn about code quality degradations much sooner. %{footerStart} Goodbye technical debt! %{footerEnd}"
+msgid "suggestPipeline|We’re adding a GitLab CI configuration file to add a pipeline to the project. You could create it manually, but we recommend that you start with a GitLab template that works out of the box."
msgstr ""
msgid "syntax is correct"
@@ -27129,6 +27764,9 @@ msgstr ""
msgid "uses Kubernetes clusters to deploy your code!"
msgstr ""
+msgid "v%{version} published %{timeAgo}"
+msgstr ""
+
msgid "verify ownership"
msgstr ""
diff --git a/locale/zh_TW/gitlab.po b/locale/zh_TW/gitlab.po
index dc88bafd0ed..f0afb673ffe 100644
--- a/locale/zh_TW/gitlab.po
+++ b/locale/zh_TW/gitlab.po
@@ -2,7 +2,7 @@ msgid ""
msgstr ""
"Project-Id-Version: gitlab-ee\n"
"Report-Msgid-Bugs-To: \n"
-"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Last-Translator: \n"
"Language-Team: Chinese Traditional\n"
"Language: zh_TW\n"
"MIME-Version: 1.0\n"
@@ -10,9 +10,11 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=1; plural=0;\n"
"X-Crowdin-Project: gitlab-ee\n"
+"X-Crowdin-Project-ID: 288872\n"
"X-Crowdin-Language: zh-TW\n"
"X-Crowdin-File: /master/locale/gitlab.pot\n"
-"PO-Revision-Date: 2020-06-08 15:08\n"
+"X-Crowdin-File-ID: 6\n"
+"PO-Revision-Date: 2020-07-02 01:19\n"
msgid " %{start} to %{end}"
msgstr ""
@@ -170,6 +172,10 @@ msgid "%d metric"
msgid_plural "%d metrics"
msgstr[0] "%d 個指標"
+msgid "%d milestone"
+msgid_plural "%d milestones"
+msgstr[0] ""
+
msgid "%d minute"
msgid_plural "%d minutes"
msgstr[0] "%d分"
@@ -206,10 +212,6 @@ msgid "%d unresolved thread"
msgid_plural "%d unresolved threads"
msgstr[0] ""
-msgid "%d url scanned"
-msgid_plural "%d urls scanned"
-msgstr[0] ""
-
msgid "%d vulnerability"
msgid_plural "%d vulnerabilities"
msgstr[0] ""
@@ -280,9 +282,15 @@ msgstr[0] "%{count} 則待處ç†ç•™è¨€"
msgid "%{count} related %{pluralized_subject}: %{links}"
msgstr "%{count} 個相關的 %{pluralized_subject}:%{links}"
+msgid "%{dashboard_path} could not be found."
+msgstr ""
+
msgid "%{days} days until tags are automatically removed"
msgstr "%{days}天,直到標籤被自動刪除"
+msgid "%{deployLinkStart}Use a template to deploy to ECS%{deployLinkEnd}, or use a docker image to %{commandsLinkStart}run AWS commands in GitLab CI/CD%{commandsLinkEnd}."
+msgstr ""
+
msgid "%{description}- Sentry event: %{errorUrl}- First seen: %{firstSeen}- Last seen: %{lastSeen} %{countLabel}: %{count}%{userCountLabel}: %{userCount}"
msgstr "%{description}- Sentry 事件:%{errorUrl}- 首次出ç¾ï¼š%{firstSeen}- 最後出ç¾ï¼š%{lastSeen} %{countLabel}:%{count}%{userCountLabel}:%{userCount}"
@@ -325,12 +333,18 @@ msgstr "%{group_name} 使用群組管ç†å¸³æˆ¶ã€‚您需è¦å»ºç«‹ä¸€å€‹æ–°çš„ Git
msgid "%{host} sign-in from new location"
msgstr ""
-msgid "%{icon}You are about to add %{usersTag} people to the discussion. Proceed with caution."
-msgstr "%{icon} 您正打算加入 %{usersTag} 個人到討論中,請å°å¿ƒã€‚"
+msgid "%{icon}You are about to add %{usersTag} people to the discussion. They will all receive a notification."
+msgstr ""
+
+msgid "%{integrations_link_start}Integrations%{link_end} enable you to make third-party applications part of your GitLab workflow. If the available integrations don't meet your needs, consider using a %{webhooks_link_start}webhook%{link_end}."
+msgstr ""
msgid "%{issuableType} will be removed! Are you sure?"
msgstr "%{issuableType} 將被刪除ï¼æ‚¨ç¢ºå®šå—Žï¼Ÿ"
+msgid "%{issuesCount} issues in this group"
+msgstr ""
+
msgid "%{issuesSize} issues"
msgstr "%{issuesSize} 個議題"
@@ -340,7 +354,7 @@ msgstr "%{issuesSize} å€‹è­°é¡Œï¼Œä¸Šé™ %{maxIssueCount} 個。"
msgid "%{labelStart}Class:%{labelEnd} %{class}"
msgstr ""
-msgid "%{labelStart}Confidence:%{labelEnd} %{confidence}"
+msgid "%{labelStart}Evidence:%{labelEnd} %{evidence}"
msgstr ""
msgid "%{labelStart}File:%{labelEnd} %{file}"
@@ -358,6 +372,9 @@ msgstr ""
msgid "%{labelStart}Report Type:%{labelEnd} %{reportType}"
msgstr ""
+msgid "%{labelStart}Scanner:%{labelEnd} %{scanner}"
+msgstr ""
+
msgid "%{labelStart}Severity:%{labelEnd} %{severity}"
msgstr ""
@@ -382,9 +399,6 @@ msgstr ""
msgid "%{link_start}Read more%{link_end} about role permissions"
msgstr "%{link_start}了解更多%{link_end} 關於角色權é™"
-msgid "%{link} can be used for binding events when something is happening within the project."
-msgstr "%{link} å¯ä»¥ç”¨æ–¼ç¶å®šå°ˆæ¡ˆå…§ç™¼ç”ŸæŸä»¶äº‹æƒ…的相關事件。"
-
msgid "%{listToShow}, and %{awardsListLength} more."
msgstr "%{listToShow},還有 %{awardsListLength} 個。"
@@ -418,6 +432,9 @@ msgstr "%{name} åŒ…å« %{resultsString}"
msgid "%{name} found %{resultsString}"
msgstr "%{name} 找到了 %{resultsString}"
+msgid "%{name} is already being used for another emoji"
+msgstr ""
+
msgid "%{name} is scheduled for %{action}"
msgstr ""
@@ -464,6 +481,9 @@ msgstr ""
msgid "%{primary} (%{secondary})"
msgstr "%{primary} (%{secondary})"
+msgid "%{ref} cannot be added: %{error}"
+msgstr ""
+
msgid "%{releases} release"
msgid_plural "%{releases} releases"
msgstr[0] "%{releases} 個發布版"
@@ -471,13 +491,34 @@ msgstr[0] "%{releases} 個發布版"
msgid "%{remaining_approvals} left"
msgstr ""
-msgid "%{retryButtonStart}Try again%{retryButtonEnd} or %{newFileButtonStart}attach a new file%{newFileButtonEnd}"
+msgid "%{reportType} %{status} detected %{critical} critical and %{high} high severity vulnerabilities out of %{total}."
+msgstr ""
+
+msgid "%{reportType} %{status} detected %{critical} critical and %{high} high severity vulnerabilities."
msgstr ""
-msgid "%{screenreaderOnlyStart}Keyboard shorcuts%{screenreaderOnlyEnd} Disabled"
+msgid "%{reportType} %{status} detected %{critical} critical severity vulnerabilities out of %{total}."
msgstr ""
-msgid "%{screenreaderOnlyStart}Keyboard shorcuts%{screenreaderOnlyEnd} Enabled"
+msgid "%{reportType} %{status} detected %{critical} critical severity vulnerability."
+msgid_plural "%{reportType} %{status} detected %{critical} critical severity vulnerabilities."
+msgstr[0] ""
+
+msgid "%{reportType} %{status} detected %{high} high severity vulnerabilities out of %{total}."
+msgstr ""
+
+msgid "%{reportType} %{status} detected %{high} high severity vulnerability."
+msgid_plural "%{reportType} %{status} detected %{high} high severity vulnerabilities."
+msgstr[0] ""
+
+msgid "%{reportType} %{status} detected %{other} vulnerability."
+msgid_plural "%{reportType} %{status} detected %{other} vulnerabilities."
+msgstr[0] ""
+
+msgid "%{reportType} %{status} detected no new vulnerabilities."
+msgstr ""
+
+msgid "%{retryButtonStart}Try again%{retryButtonEnd} or %{newFileButtonStart}attach a new file%{newFileButtonEnd}"
msgstr ""
msgid "%{service_title} %{message}."
@@ -569,6 +610,12 @@ msgstr "%{title} 變更"
msgid "%{token}..."
msgstr ""
+msgid "%{totalCpu} (%{freeSpacePercentage}%{percentSymbol} free)"
+msgstr ""
+
+msgid "%{totalMemory} (%{freeSpacePercentage}%{percentSymbol} free)"
+msgstr ""
+
msgid "%{totalWeight} total weight"
msgstr "%{totalWeight} 總權é‡"
@@ -599,6 +646,12 @@ msgstr ""
msgid "%{verb} %{time_spent_value} spent time."
msgstr "%{verb} 耗時 %{time_spent_value}"
+msgid "%{webhooks_link_start}%{webhook_type}%{link_end} enable you to send notifications to web applications in response to events in a group or project."
+msgstr ""
+
+msgid "%{webhooks_link_start}%{webhook_type}%{link_end} enable you to send notifications to web applications in response to events in a group or project. We recommend using an %{integrations_link_start}integration%{link_end} in preference to a webhook."
+msgstr ""
+
msgid "'%{level}' is not a valid visibility level"
msgstr "「%{level}ã€ä¸æ˜¯æœ‰æ•ˆçš„å¯è¦‹æ€§ç´šåˆ¥"
@@ -624,9 +677,6 @@ msgstr "(%{mrCount} å·²åˆä½µ)"
msgid "(No changes)"
msgstr "(無變更)"
-msgid "(Show all)"
-msgstr "(全部顯示)"
-
msgid "(check progress)"
msgstr "(檢查進度)"
@@ -690,6 +740,9 @@ msgstr "- 顯示較少內容"
msgid "0 for unlimited"
msgstr "0 表示無é™åˆ¶"
+msgid "0 for unlimited, only effective with remote storage enabled."
+msgstr ""
+
msgid "1 %{type} addition"
msgid_plural "%{count} %{type} additions"
msgstr[0] "%{count} 個%{type}加入"
@@ -822,6 +875,12 @@ msgstr "<code>\"johnsmith@example.com\": \"johnsm...@example.com\"</code> 將會
msgid "<code>\"johnsmith@example.com\": \"johnsmith@example.com\"</code> will add \"By <a href=\"#\">johnsmith@example.com</a>\" to all issues and comments originally created by johnsmith@example.com. By default, the email address or username is masked to ensure the user's privacy. Use this option if you want to show the full email address."
msgstr "<code>\"johnsmith@example.com\": \"johnsmith@example.com\"</code> 將會把「由 <a href=\"#\">@johnsmith</a>ã€åŠ å…¥åˆ°åŽŸæœ¬ç”± johnsmith@example.com 建立的所有議題和留言中。為ä¿è­·ä½¿ç”¨è€…çš„éš±ç§ï¼Œé›»å­éƒµä»¶åœ°å€æˆ–使用者å稱é è¨­æœƒè¢«é®ä½ã€‚如需顯示完整郵件地å€ï¼Œå¯ä½¿ç”¨æ­¤é¸é …。"
+msgid "<code>Masked</code> to prevent the values from being displayed in job logs (must match certain regexp requirements)."
+msgstr ""
+
+msgid "<code>Protected</code> to expose them to protected branches or tags only."
+msgstr ""
+
msgid "<namespace / project>"
msgstr ""
@@ -849,9 +908,6 @@ msgstr "「執行器ã€æ˜¯ä¸€å€‹åŸ·è¡Œä½œæ¥­çš„行程。您å¯ä»¥æ ¹æ“šéœ€è¦é…
msgid "A .NET Core console application template, customizable for any .NET Core project"
msgstr ".NET Core 控制å°æ‡‰ç”¨ç¨‹å¼ç¯„本,å¯é‡å°ä»»ä½• .NET Core 專案自訂"
-msgid "A DAG must have two dependent jobs to be visualized on this tab."
-msgstr ""
-
msgid "A GitBook site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
msgstr "一個 GitBook ç«™å°ï¼Œä½¿ç”¨ Netlify 代替 GitLab çš„ CI/CD,但ä»å…·æœ‰æ‰€æœ‰å…¶ä»–主è¦çš„ GitLab 功能。"
@@ -882,6 +938,12 @@ msgstr "無法設定空專案的é è¨­åˆ†æ”¯ã€‚"
msgid "A deleted user"
msgstr "已刪除的使用者"
+msgid "A file has been changed."
+msgstr ""
+
+msgid "A file was not found."
+msgstr ""
+
msgid "A file with '%{file_name}' already exists in %{branch} branch"
msgstr "「%{file_name}ã€æª”å已經在 %{branch} 分支"
@@ -948,7 +1010,7 @@ msgstr ""
msgid "A subscription will trigger a new pipeline on the default branch of this project when a pipeline successfully completes for a new tag on the %{default_branch_docs} of the subscribed project."
msgstr ""
-msgid "A terraform report was generated in your pipelines."
+msgid "A suggestion is not applicable."
msgstr ""
msgid "A user with write access to the source branch selected this option"
@@ -1119,6 +1181,9 @@ msgstr "帳戶:%{account}"
msgid "Action to take when receiving an alert."
msgstr "接收警示時è¦åŸ·è¡Œçš„動作。"
+msgid "Actions"
+msgstr ""
+
msgid "Activate"
msgstr "啟用"
@@ -1174,6 +1239,9 @@ msgstr "加入 Kubernetes å¢é›†"
msgid "Add LICENSE"
msgstr "加入授權æ¢æ¬¾"
+msgid "Add New Node"
+msgstr ""
+
msgid "Add README"
msgstr "加入說明檔案"
@@ -1249,6 +1317,9 @@ msgstr ""
msgid "Add approval rule"
msgstr "加入核准è¦å‰‡"
+msgid "Add approvers"
+msgstr ""
+
msgid "Add bold text"
msgstr "加入粗體文字"
@@ -1306,6 +1377,9 @@ msgstr "手動加入請求"
msgid "Add strikethrough text"
msgstr ""
+msgid "Add suggestion to batch"
+msgstr ""
+
msgid "Add system hook"
msgstr "加入系統掛鉤"
@@ -1405,6 +1479,9 @@ msgstr "管ç†ä¸­å¿ƒ"
msgid "Admin Note"
msgstr ""
+msgid "Admin Notifications"
+msgstr ""
+
msgid "Admin Overview"
msgstr "管ç†æ¦‚覽"
@@ -1516,6 +1593,9 @@ msgstr ""
msgid "AdminSettings|Integrations configured here will automatically apply to all projects on this instance."
msgstr ""
+msgid "AdminSettings|Moved to integrations"
+msgstr ""
+
msgid "AdminSettings|No required pipeline"
msgstr "沒有強制性的æµæ°´ç·š"
@@ -1745,12 +1825,18 @@ msgid "Alert"
msgid_plural "Alerts"
msgstr[0] "警示"
+msgid "AlertManagement| assign yourself"
+msgstr ""
+
msgid "AlertManagement|Acknowledged"
msgstr ""
msgid "AlertManagement|Alert"
msgstr ""
+msgid "AlertManagement|Alert assignee(s): %{assignees}"
+msgstr ""
+
msgid "AlertManagement|Alert detail"
msgstr ""
@@ -1766,9 +1852,15 @@ msgstr ""
msgid "AlertManagement|All alerts"
msgstr ""
+msgid "AlertManagement|Assign To"
+msgstr ""
+
msgid "AlertManagement|Assign status"
msgstr ""
+msgid "AlertManagement|Assignee"
+msgstr ""
+
msgid "AlertManagement|Assignees"
msgstr ""
@@ -1787,9 +1879,6 @@ msgstr ""
msgid "AlertManagement|Edit"
msgstr ""
-msgid "AlertManagement|End time"
-msgstr ""
-
msgid "AlertManagement|Events"
msgstr ""
@@ -1808,10 +1897,13 @@ msgstr ""
msgid "AlertManagement|More information"
msgstr ""
+msgid "AlertManagement|No Matching Results"
+msgstr ""
+
msgid "AlertManagement|No alert data to display."
msgstr ""
-msgid "AlertManagement|No alerts available to display. If you think you're seeing this message in error, refresh the page."
+msgid "AlertManagement|No alerts available to display. See %{linkStart}enabling alert management%{linkEnd} for more information on adding alerts to the list."
msgstr ""
msgid "AlertManagement|No alerts to display."
@@ -1820,6 +1912,9 @@ msgstr ""
msgid "AlertManagement|None"
msgstr ""
+msgid "AlertManagement|None -"
+msgstr ""
+
msgid "AlertManagement|Open"
msgstr ""
@@ -1856,9 +1951,18 @@ msgstr ""
msgid "AlertManagement|There was an error displaying the alerts. Confirm your endpoint's configuration details to ensure alerts appear."
msgstr ""
+msgid "AlertManagement|There was an error while updating the assignee(s) list. Please try again."
+msgstr ""
+
+msgid "AlertManagement|There was an error while updating the assignee(s) of the alert. Please try again."
+msgstr ""
+
msgid "AlertManagement|There was an error while updating the status of the alert. Please try again."
msgstr ""
+msgid "AlertManagement|This assignee cannot be assigned to this alert."
+msgstr ""
+
msgid "AlertManagement|Tool"
msgstr ""
@@ -2006,6 +2110,9 @@ msgstr "å…許使用者註冊任何應用程å¼ï¼Œä½¿ç”¨ GitLab 作為 OAuth æ
msgid "Allow users to request access (if visibility is public or internal)"
msgstr "å…許使用者請求存å–(如果å¯è¦‹æ€§æ˜¯å…¬é–‹æˆ–內部)"
+msgid "Allowed Geo IP"
+msgstr ""
+
msgid "Allowed email domain restriction only permitted for top-level groups"
msgstr "åªå…許頂層群組使用電å­éƒµä»¶ç¶²åŸŸé™åˆ¶"
@@ -2039,6 +2146,9 @@ msgstr "Amazon EKS æ•´åˆè®“您能從 GitLab ç®¡ç† EKS å¢é›†ã€‚"
msgid "Amazon Web Services"
msgstr "Amazon 網路æœå‹™"
+msgid "Amazon Web Services Logo"
+msgstr ""
+
msgid "Amazon authentication is not %{link_start}correctly configured%{link_end}. Ask your GitLab administrator if you want to use this service."
msgstr "Amazon 身份驗證未%{link_start}正確設定%{link_end}。如需使用這項æœå‹™ï¼Œè«‹è¯çµ¡ GitLab 管ç†å“¡ã€‚"
@@ -2096,11 +2206,17 @@ msgstr "嘗試解決討論時發生錯誤。請å†è©¦ä¸€æ¬¡ã€‚"
msgid "An error occurred when updating the issue weight"
msgstr "更新議題權é‡æ™‚發生錯誤"
+msgid "An error occurred while acknowledging the notification. Refresh the page and try again."
+msgstr ""
+
+msgid "An error occurred while adding approvers"
+msgstr ""
+
msgid "An error occurred while adding formatted title for epic"
msgstr ""
-msgid "An error occurred while checking group path"
-msgstr "檢查群組路徑時發生錯誤"
+msgid "An error occurred while checking group path. Please refresh and try again."
+msgstr ""
msgid "An error occurred while committing your changes."
msgstr "æ交您的變更時發生錯誤。"
@@ -2243,7 +2359,7 @@ msgstr ""
msgid "An error occurred while loading milestones"
msgstr ""
-msgid "An error occurred while loading terraform report"
+msgid "An error occurred while loading project creation UI"
msgstr ""
msgid "An error occurred while loading the data. Please try again."
@@ -2297,6 +2413,9 @@ msgstr "移除議題時發生錯誤。"
msgid "An error occurred while rendering preview broadcast message"
msgstr "繪製廣播訊æ¯æ™‚發生錯誤"
+msgid "An error occurred while rendering the editor"
+msgstr ""
+
msgid "An error occurred while reordering issues."
msgstr "é‡æ–°æŽ’åºè­°é¡Œæ™‚發生錯誤。"
@@ -2393,7 +2512,7 @@ msgstr "分æžç¶²é æ‡‰ç”¨ç¨‹å¼çš„審閱版本。"
msgid "Analyze your dependencies for known vulnerabilities."
msgstr ""
-msgid "Analyze your source code and git history for secrets"
+msgid "Analyze your source code and git history for secrets."
msgstr ""
msgid "Analyze your source code for known vulnerabilities."
@@ -2507,12 +2626,18 @@ msgstr ""
msgid "Apply suggestion"
msgstr ""
+msgid "Apply suggestions"
+msgstr ""
+
msgid "Apply template"
msgstr ""
msgid "Apply this approval rule to any branch or a specific protected branch."
msgstr ""
+msgid "Applying"
+msgstr ""
+
msgid "Applying a template will replace the existing issue description. Any changes you have made will be lost."
msgstr ""
@@ -2525,12 +2650,18 @@ msgstr ""
msgid "Applying multiple commands"
msgstr ""
-msgid "Applying suggestion"
+msgid "Applying suggestion..."
+msgstr ""
+
+msgid "Applying suggestions..."
msgstr ""
msgid "Approval rules"
msgstr ""
+msgid "Approval rules reset to project defaults"
+msgstr ""
+
msgid "ApprovalRuleRemove|%d member"
msgid_plural "ApprovalRuleRemove|%d members"
msgstr[0] ""
@@ -2580,6 +2711,9 @@ msgstr "核准目å‰åˆä½µè«‹æ±‚。"
msgid "Approved"
msgstr ""
+msgid "Approved MRs"
+msgstr ""
+
msgid "Approved by: "
msgstr ""
@@ -2793,6 +2927,9 @@ msgstr ""
msgid "Assign"
msgstr ""
+msgid "Assign Iteration"
+msgstr ""
+
msgid "Assign custom color like #FF0000"
msgstr ""
@@ -2811,6 +2948,9 @@ msgstr ""
msgid "Assign to"
msgstr ""
+msgid "Assign to commenting user"
+msgstr ""
+
msgid "Assign yourself to these issues"
msgstr ""
@@ -2836,6 +2976,9 @@ msgid "Assignee"
msgid_plural "%d Assignees"
msgstr[0] ""
+msgid "Assignee has no permissions"
+msgstr ""
+
msgid "Assignee lists not available with your current license"
msgstr ""
@@ -2845,6 +2988,9 @@ msgstr ""
msgid "Assignee(s)"
msgstr ""
+msgid "Assignees"
+msgstr ""
+
msgid "Assigns %{assignee_users_sentence}."
msgstr ""
@@ -2879,6 +3025,9 @@ msgstr ""
msgid "Audit Events is a way to keep track of important events that happened in GitLab."
msgstr ""
+msgid "Audit Log"
+msgstr ""
+
msgid "AuditEvents|(removed)"
msgstr ""
@@ -2915,6 +3064,9 @@ msgstr ""
msgid "AuditLogs|IP Address"
msgstr ""
+msgid "AuditLogs|Member Events"
+msgstr ""
+
msgid "AuditLogs|No matching %{type} found."
msgstr ""
@@ -3197,9 +3349,6 @@ msgstr ""
msgid "Badges|e.g. %{exampleUrl}"
msgstr ""
-msgid "Badge|New"
-msgstr ""
-
msgid "Balsamiq file could not be loaded."
msgstr ""
@@ -3248,6 +3397,12 @@ msgstr ""
msgid "Below you will find all the groups that are public."
msgstr ""
+msgid "Beta"
+msgstr ""
+
+msgid "Bi-weekly code coverage"
+msgstr ""
+
msgid "Billing"
msgstr ""
@@ -3308,9 +3463,15 @@ msgstr ""
msgid "Bitbucket Server Import"
msgstr ""
+msgid "Bitbucket Server import"
+msgstr ""
+
msgid "Bitbucket import"
msgstr ""
+msgid "Blame"
+msgstr ""
+
msgid "Blocked"
msgstr ""
@@ -3323,9 +3484,6 @@ msgstr ""
msgid "Blog"
msgstr ""
-msgid "Blue helpers indicate an action to be taken."
-msgstr ""
-
msgid "Board name"
msgstr ""
@@ -3524,6 +3682,9 @@ msgstr ""
msgid "Broadcast Message was successfully updated."
msgstr ""
+msgid "Broadcast Messages"
+msgstr ""
+
msgid "Browse Directory"
msgstr ""
@@ -3578,6 +3739,9 @@ msgstr ""
msgid "By %{user_name}"
msgstr ""
+msgid "By URL"
+msgstr ""
+
msgid "By default GitLab sends emails in HTML and plain text formats so mail clients can choose what format to use. Disable this option if you only want to send emails in plain text format."
msgstr ""
@@ -3683,6 +3847,9 @@ msgstr ""
msgid "Can override approvers and approvals required per merge request"
msgstr ""
+msgid "Can't apply as this line has changed or the suggestion already matches its content."
+msgstr ""
+
msgid "Can't create snippet: %{err}"
msgstr ""
@@ -3869,9 +4036,6 @@ msgstr ""
msgid "Changes are still tracked. Useful for cluster/index migrations."
msgstr ""
-msgid "Changes are unknown"
-msgstr ""
-
msgid "Changes suppressed. Click to show."
msgstr ""
@@ -4178,15 +4342,9 @@ msgstr ""
msgid "Choose what content you want to see on a group’s overview page"
msgstr ""
-msgid "Choose which groups you wish to synchronize to this secondary node"
-msgstr ""
-
msgid "Choose which repositories you want to connect and run CI/CD pipelines."
msgstr ""
-msgid "Choose which shards you wish to synchronize to this secondary node"
-msgstr ""
-
msgid "Choose your framework"
msgstr ""
@@ -4484,9 +4642,15 @@ msgstr ""
msgid "Cluster does not exist"
msgstr ""
+msgid "Cluster is required for Stages::ClusterEndpointInserter"
+msgstr ""
+
msgid "Cluster level"
msgstr ""
+msgid "Cluster type must be specificed for Stages::ClusterEndpointInserter"
+msgstr ""
+
msgid "ClusterIntegration| %{custom_domain_start}More information%{custom_domain_end}."
msgstr ""
@@ -4598,7 +4762,7 @@ msgstr ""
msgid "ClusterIntegration|Cert-Manager"
msgstr ""
-msgid "ClusterIntegration|Cert-Manager is a native Kubernetes certificate management controller that helps with issuing certificates. Installing Cert-Manager on your cluster will issue a certificate by %{letsEncrypt} and ensure that certificates are valid and up-to-date."
+msgid "ClusterIntegration|Cert-Manager is a native Kubernetes certificate management controller that helps with issuing certificates. Installing Cert-Manager on your cluster will issue a certificate by %{linkStart}Let's Encrypt%{linkEnd} and ensure that certificates are valid and up-to-date."
msgstr ""
msgid "ClusterIntegration|Certificate Authority bundle (PEM format)"
@@ -4625,9 +4789,6 @@ msgstr ""
msgid "ClusterIntegration|Clear the local cache of namespace and service accounts. This is necessary if your integration has become out of sync. The cache is repopulated during the next CI job that requires namespace and service accounts."
msgstr ""
-msgid "ClusterIntegration|Cloud Run"
-msgstr ""
-
msgid "ClusterIntegration|Cluster being created"
msgstr ""
@@ -4712,7 +4873,7 @@ msgstr ""
msgid "ClusterIntegration|Crossplane"
msgstr ""
-msgid "ClusterIntegration|Crossplane enables declarative provisioning of managed services from your cloud of choice using %{codeStart}kubectl%{codeEnd} or %{gitlabIntegrationLink}. Crossplane runs inside your Kubernetes cluster and supports secure connectivity and secrets management between app containers and the cloud services they depend on."
+msgid "ClusterIntegration|Crossplane enables declarative provisioning of managed services from your cloud of choice using %{codeStart}kubectl%{codeEnd} or %{linkStart}GitLab Integration%{linkEnd}. Crossplane runs inside your Kubernetes cluster and supports secure connectivity and secrets management between app containers and the cloud services they depend on."
msgstr ""
msgid "ClusterIntegration|Deletes all GitLab resources attached to this cluster during removal"
@@ -4796,9 +4957,6 @@ msgstr ""
msgid "ClusterIntegration|GitLab-managed cluster"
msgstr ""
-msgid "ClusterIntegration|Gitlab Integration"
-msgstr ""
-
msgid "ClusterIntegration|Global default"
msgstr ""
@@ -4844,7 +5002,7 @@ msgstr ""
msgid "ClusterIntegration|Ingress gives you a way to route requests to services based on the request host or path, centralizing a number of services into a single entrypoint."
msgstr ""
-msgid "ClusterIntegration|Installing Ingress may incur additional costs. Learn more about %{pricingLink}."
+msgid "ClusterIntegration|Installing Ingress may incur additional costs. Learn more about %{linkStart}pricing%{linkEnd}."
msgstr ""
msgid "ClusterIntegration|Instance cluster"
@@ -4859,7 +5017,7 @@ msgstr ""
msgid "ClusterIntegration|Issuer Email"
msgstr ""
-msgid "ClusterIntegration|Issuers represent a certificate authority. You must provide an email address for your Issuer. "
+msgid "ClusterIntegration|Issuers represent a certificate authority. You must provide an email address for your Issuer."
msgstr ""
msgid "ClusterIntegration|Jupyter Hostname"
@@ -4931,9 +5089,6 @@ msgstr ""
msgid "ClusterIntegration|Learn more about instance Kubernetes clusters"
msgstr ""
-msgid "ClusterIntegration|Let's Encrypt"
-msgstr ""
-
msgid "ClusterIntegration|Loading IAM Roles"
msgstr ""
@@ -5045,7 +5200,7 @@ msgstr ""
msgid "ClusterIntegration|Prometheus"
msgstr ""
-msgid "ClusterIntegration|Prometheus is an open-source monitoring system with %{gitlabIntegrationLink} to monitor deployed applications."
+msgid "ClusterIntegration|Prometheus is an open-source monitoring system with %{linkStart}GitLab Integration%{linkEnd} to monitor deployed applications."
msgstr ""
msgid "ClusterIntegration|Provider details"
@@ -5201,10 +5356,10 @@ msgstr ""
msgid "ClusterIntegration|Select zone to choose machine type"
msgstr ""
-msgid "ClusterIntegration|Send Cilium Logs"
+msgid "ClusterIntegration|Send Container Network Policies Logs"
msgstr ""
-msgid "ClusterIntegration|Send ModSecurity Logs"
+msgid "ClusterIntegration|Send Web Application Firewall Logs"
msgstr ""
msgid "ClusterIntegration|Service Token"
@@ -5372,15 +5527,12 @@ msgstr ""
msgid "ClusterIntegration|documentation"
msgstr ""
-msgid "ClusterIntegration|installed via %{installed_via}"
+msgid "ClusterIntegration|installed via %{linkStart}Cloud Run%{linkEnd}"
msgstr ""
msgid "ClusterIntegration|meets the requirements"
msgstr ""
-msgid "ClusterIntegration|pricing"
-msgstr ""
-
msgid "ClusterIntegration|sign up"
msgstr ""
@@ -5417,6 +5569,15 @@ msgstr ""
msgid "Code"
msgstr ""
+msgid "Code Coverage: %{coveragePercentage}%{percentSymbol}"
+msgstr ""
+
+msgid "Code Coverage| Empty code coverage data"
+msgstr ""
+
+msgid "Code Coverage|Couldn't fetch the code coverage data"
+msgstr ""
+
msgid "Code Owners"
msgstr ""
@@ -5471,7 +5632,7 @@ msgstr ""
msgid "Collapse approvers"
msgstr ""
-msgid "Collapse child epics"
+msgid "Collapse milestones"
msgstr ""
msgid "Collapse replies"
@@ -5489,6 +5650,9 @@ msgstr ""
msgid "Coming soon"
msgstr ""
+msgid "Comma-separated, e.g. '1.1.1.1, 2.2.2.0/24'"
+msgstr ""
+
msgid "Command"
msgstr ""
@@ -5658,6 +5822,12 @@ msgstr ""
msgid "Compliance framework (optional)"
msgstr ""
+msgid "Compliance frameworks"
+msgstr ""
+
+msgid "ComplianceDashboard|created by:"
+msgstr ""
+
msgid "ComplianceFramework|GDPR"
msgstr ""
@@ -5796,6 +5966,9 @@ msgstr ""
msgid "Connection timed out"
msgstr ""
+msgid "Connection timeout"
+msgstr ""
+
msgid "Contact sales to upgrade"
msgstr ""
@@ -5849,9 +6022,6 @@ msgstr ""
msgid "ContainerRegistry|CLI Commands"
msgstr ""
-msgid "ContainerRegistry|Compressed Size"
-msgstr ""
-
msgid "ContainerRegistry|Container Registry"
msgstr ""
@@ -5867,6 +6037,9 @@ msgstr ""
msgid "ContainerRegistry|Copy push command"
msgstr ""
+msgid "ContainerRegistry|Delete selected"
+msgstr ""
+
msgid "ContainerRegistry|Docker connection error"
msgstr ""
@@ -5900,16 +6073,16 @@ msgstr ""
msgid "ContainerRegistry|If you are not already logged in, you need to authenticate to the Container Registry by using your GitLab username and password. If you have %{twofaDocLinkStart}Two-Factor Authentication%{twofaDocLinkEnd} enabled, use a %{personalAccessTokensDocLinkStart}Personal Access Token%{personalAccessTokensDocLinkEnd} instead of a password."
msgstr ""
-msgid "ContainerRegistry|Image ID"
+msgid "ContainerRegistry|Image ID: %{imageId}"
msgstr ""
msgid "ContainerRegistry|Image Repositories"
msgstr ""
-msgid "ContainerRegistry|Keep and protect the images that matter most."
+msgid "ContainerRegistry|Image tags"
msgstr ""
-msgid "ContainerRegistry|Last Updated"
+msgid "ContainerRegistry|Keep and protect the images that matter most."
msgstr ""
msgid "ContainerRegistry|Login"
@@ -5924,6 +6097,9 @@ msgstr ""
msgid "ContainerRegistry|Please contact your administrator."
msgstr ""
+msgid "ContainerRegistry|Published %{timeInfo}"
+msgstr ""
+
msgid "ContainerRegistry|Push an image"
msgstr ""
@@ -5939,9 +6115,6 @@ msgstr ""
msgid "ContainerRegistry|Remove repository"
msgstr ""
-msgid "ContainerRegistry|Remove selected tags"
-msgstr ""
-
msgid "ContainerRegistry|Remove tag"
msgid_plural "ContainerRegistry|Remove tags"
msgstr[0] ""
@@ -5970,9 +6143,6 @@ msgstr ""
msgid "ContainerRegistry|Sorry, your filter produced no results."
msgstr ""
-msgid "ContainerRegistry|Tag"
-msgstr ""
-
msgid "ContainerRegistry|Tag expiration policy"
msgstr ""
@@ -6117,21 +6287,6 @@ msgstr ""
msgid "Control the display of third party offers."
msgstr ""
-msgid "Control the maximum concurrency of LFS/attachment backfill for this secondary node"
-msgstr ""
-
-msgid "Control the maximum concurrency of container repository operations for this Geo node"
-msgstr ""
-
-msgid "Control the maximum concurrency of repository backfill for this secondary node"
-msgstr ""
-
-msgid "Control the maximum concurrency of verification operations for this Geo node"
-msgstr ""
-
-msgid "Control the minimum interval in days that a repository should be reverified for this primary node"
-msgstr ""
-
msgid "Cookie domain"
msgstr ""
@@ -6273,6 +6428,9 @@ msgstr ""
msgid "Could not find design."
msgstr ""
+msgid "Could not find iteration"
+msgstr ""
+
msgid "Could not remove the trigger."
msgstr ""
@@ -6309,6 +6467,12 @@ msgstr ""
msgid "Coverage"
msgstr ""
+msgid "Coverage Fuzzing"
+msgstr ""
+
+msgid "Crash State"
+msgstr ""
+
msgid "Create"
msgstr ""
@@ -6357,6 +6521,9 @@ msgstr ""
msgid "Create a personal access token on your account to pull or push via %{protocol}."
msgstr ""
+msgid "Create a project pre-populated with the necessary files to get you started quickly."
+msgstr ""
+
msgid "Create an account using:"
msgstr ""
@@ -6525,6 +6692,9 @@ msgstr ""
msgid "Creates branch '%{branch_name}' and a merge request to resolve this issue."
msgstr ""
+msgid "Creating"
+msgstr ""
+
msgid "Creating epic"
msgstr ""
@@ -6576,7 +6746,10 @@ msgstr ""
msgid "Current vulnerabilities count"
msgstr ""
-msgid "CurrentUser|Buy CI minutes"
+msgid "CurrentUser|Buy Pipeline minutes"
+msgstr ""
+
+msgid "CurrentUser|One of your groups is running out"
msgstr ""
msgid "CurrentUser|Profile"
@@ -6591,6 +6764,9 @@ msgstr ""
msgid "CurrentUser|Upgrade"
msgstr ""
+msgid "Custom Attributes"
+msgstr ""
+
msgid "Custom CI configuration path"
msgstr ""
@@ -6853,12 +7029,18 @@ msgstr ""
msgid "DAG"
msgstr ""
+msgid "DAG visualization requires at least 3 dependent jobs."
+msgstr ""
+
msgid "DNS"
msgstr "DNS"
msgid "Dashboard"
msgstr ""
+msgid "Dashboard uid not found"
+msgstr ""
+
msgid "DashboardProjects|All"
msgstr ""
@@ -6883,6 +7065,9 @@ msgstr ""
msgid "Data is still calculating..."
msgstr ""
+msgid "Datasource name not found"
+msgstr ""
+
msgid "Date"
msgstr ""
@@ -7063,6 +7248,9 @@ msgstr ""
msgid "Delete this attachment"
msgstr ""
+msgid "Delete user list"
+msgstr ""
+
msgid "Delete variable"
msgstr ""
@@ -7374,6 +7562,9 @@ msgstr ""
msgid "Deploying to"
msgstr ""
+msgid "Deploying to AWS is easy with GitLab"
+msgstr ""
+
msgid "Deployment Frequency"
msgstr ""
@@ -7735,6 +7926,9 @@ msgstr ""
msgid "Dismissed on pipeline %{pipelineLink} at %{projectLink}"
msgstr ""
+msgid "Display alerts from all your monitoring tools directly within GitLab."
+msgstr ""
+
msgid "Display name"
msgstr ""
@@ -7792,9 +7986,6 @@ msgstr ""
msgid "Don't show again"
msgstr ""
-msgid "Don't worry, you can access this tour by clicking on the help icon in the top right corner and choose <strong>Learn GitLab</strong>."
-msgstr ""
-
msgid "Done"
msgstr ""
@@ -7921,6 +8112,9 @@ msgstr ""
msgid "Edit Release"
msgstr ""
+msgid "Edit Slack integration"
+msgstr ""
+
msgid "Edit Snippet"
msgstr ""
@@ -7960,6 +8154,9 @@ msgstr ""
msgid "Edit issues"
msgstr ""
+msgid "Edit iteration"
+msgstr ""
+
msgid "Edit public deploy key"
msgstr ""
@@ -8008,6 +8205,9 @@ msgstr ""
msgid "Email"
msgstr ""
+msgid "Email Notification"
+msgstr ""
+
msgid "Email address"
msgstr ""
@@ -8020,6 +8220,9 @@ msgstr ""
msgid "Email not verified. Please verify your email in Salesforce."
msgstr ""
+msgid "Email notification for unknown sign-ins"
+msgstr ""
+
msgid "Email patch"
msgstr ""
@@ -8251,6 +8454,9 @@ msgstr ""
msgid "Enforce DNS rebinding attack protection"
msgstr ""
+msgid "Enforce personal access token expiration"
+msgstr ""
+
msgid "Ensure connectivity is available from the GitLab server to the Prometheus server"
msgstr ""
@@ -8320,6 +8526,9 @@ msgstr ""
msgid "Enter the name of your application, and we'll return a unique %{type}."
msgstr ""
+msgid "Enter weights for storages for new repositories."
+msgstr ""
+
msgid "Enter your password to approve"
msgstr ""
@@ -8329,15 +8538,27 @@ msgstr ""
msgid "Environment does not have deployments"
msgstr ""
+msgid "Environment is required for Stages::MetricEndpointInserter"
+msgstr ""
+
+msgid "Environment is required for Stages::VariableEndpointInserter"
+msgstr ""
+
msgid "Environment scope"
msgstr ""
+msgid "Environment variables are applied to all project environments in this instance via the Runner. You can use environment variables for passwords, secret keys, etc. Make variables available to the running application by prepending the variable key with <code>K8S_SECRET_</code>. You can set variables to be:"
+msgstr ""
+
msgid "Environment variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. Additionally, they can be masked so they are hidden in job logs, though they must match certain regexp requirements to do so. You can use environment variables for passwords, secret keys, or whatever you want."
msgstr ""
msgid "Environment variables are configured by your administrator to be %{link_start}protected%{link_end} by default"
msgstr ""
+msgid "Environment variables on this GitLab instance are configured to be %{link_start}protected%{link_end} by default"
+msgstr ""
+
msgid "Environment:"
msgstr ""
@@ -8869,6 +9090,9 @@ msgstr ""
msgid "EventFilterBy|Filter by comments"
msgstr ""
+msgid "EventFilterBy|Filter by designs"
+msgstr ""
+
msgid "EventFilterBy|Filter by epic events"
msgstr ""
@@ -9001,15 +9225,15 @@ msgstr ""
msgid "Expand approvers"
msgstr ""
-msgid "Expand child epics"
-msgstr ""
-
msgid "Expand down"
msgstr ""
msgid "Expand dropdown"
msgstr ""
+msgid "Expand milestones"
+msgstr ""
+
msgid "Expand sidebar"
msgstr ""
@@ -9025,6 +9249,9 @@ msgstr ""
msgid "Expiration date"
msgstr ""
+msgid "Expiration not enforced"
+msgstr ""
+
msgid "Expiration policy for the Container Registry is a perfect solution for keeping the Registry space down while still enjoying the full power of GitLab CI/CD."
msgstr ""
@@ -9145,6 +9372,9 @@ msgstr ""
msgid "Failed Jobs"
msgstr ""
+msgid "Failed on"
+msgstr ""
+
msgid "Failed to add a Zoom meeting"
msgstr ""
@@ -9205,13 +9435,19 @@ msgstr ""
msgid "Failed to install."
msgstr ""
+msgid "Failed to load assignees. Please try again."
+msgstr ""
+
+msgid "Failed to load authors. Please try again."
+msgstr ""
+
msgid "Failed to load emoji list."
msgstr ""
msgid "Failed to load error details from Sentry."
msgstr ""
-msgid "Failed to load errors from Sentry. Error message: %{errorMessage}"
+msgid "Failed to load errors from Sentry."
msgstr ""
msgid "Failed to load group activity metrics. Please try again."
@@ -9292,6 +9528,9 @@ msgstr ""
msgid "Failed to set due date because the date format is invalid."
msgstr ""
+msgid "Failed to set iteration on this issue. Please try again."
+msgstr ""
+
msgid "Failed to signing using smartcard authentication"
msgstr ""
@@ -9514,13 +9753,10 @@ msgstr ""
msgid "FeatureFlags|Target environments"
msgstr ""
-msgid "FeatureFlags|There are no active feature flags"
-msgstr ""
-
-msgid "FeatureFlags|There are no inactive feature flags"
+msgid "FeatureFlags|There was an error fetching the feature flags."
msgstr ""
-msgid "FeatureFlags|There was an error fetching the feature flags."
+msgid "FeatureFlags|There was an error retrieving user lists"
msgstr ""
msgid "FeatureFlags|Try again in a few moments or contact your support team."
@@ -9532,9 +9768,18 @@ msgstr ""
msgid "FeatureFlag|Delete strategy"
msgstr ""
+msgid "FeatureFlag|List"
+msgstr ""
+
msgid "FeatureFlag|Percentage"
msgstr ""
+msgid "FeatureFlag|Select a user list"
+msgstr ""
+
+msgid "FeatureFlag|There are no configured user lists"
+msgstr ""
+
msgid "FeatureFlag|Type"
msgstr ""
@@ -9691,6 +9936,12 @@ msgstr ""
msgid "Filter..."
msgstr ""
+msgid "Find File"
+msgstr ""
+
+msgid "Find bugs in your code with coverage-guided fuzzing"
+msgstr ""
+
msgid "Find by path"
msgstr ""
@@ -9955,6 +10206,9 @@ msgstr ""
msgid "Geo allows you to replicate your GitLab instance to other geographical locations."
msgstr ""
+msgid "Geo nodes are paused using a command run on the node"
+msgstr ""
+
msgid "GeoNodeStatusEvent|%{timeAgoStr} (%{pendingEvents} events)"
msgstr ""
@@ -10039,6 +10293,9 @@ msgstr ""
msgid "GeoNodes|Not checksummed"
msgstr ""
+msgid "GeoNodes|Package files"
+msgstr ""
+
msgid "GeoNodes|Pausing replication stops the sync process. Are you sure?"
msgstr ""
@@ -10057,6 +10314,9 @@ msgstr ""
msgid "GeoNodes|Replication slots"
msgstr ""
+msgid "GeoNodes|Replication status"
+msgstr ""
+
msgid "GeoNodes|Repositories"
msgstr ""
@@ -10267,6 +10527,9 @@ msgstr ""
msgid "Geo|Synchronization failed - %{error}"
msgstr ""
+msgid "Geo|Synchronization of %{itemTitle} is disabled."
+msgstr ""
+
msgid "Geo|The database is currently %{db_lag} behind the primary node."
msgstr ""
@@ -10579,6 +10842,9 @@ msgstr ""
msgid "Go to %{link_to_google_takeout}."
msgstr ""
+msgid "Go to <strong>Issues</strong> > <strong>Boards</strong> to access your personalized learning issue board."
+msgstr ""
+
msgid "Go to Pipelines"
msgstr ""
@@ -10678,9 +10944,6 @@ msgstr ""
msgid "Go to your snippets"
msgstr ""
-msgid "Golden Tanuki"
-msgstr ""
-
msgid "Google Cloud Platform"
msgstr ""
@@ -10702,6 +10965,9 @@ msgstr ""
msgid "Grafana URL"
msgstr ""
+msgid "Grafana response contains invalid json"
+msgstr ""
+
msgid "GrafanaIntegration|API Token"
msgstr ""
@@ -10750,9 +11016,6 @@ msgstr ""
msgid "Group %{group_name} was successfully created."
msgstr ""
-msgid "Group '%{group_name}' is being imported."
-msgstr ""
-
msgid "Group Audit Events"
msgstr ""
@@ -10789,9 +11052,6 @@ msgstr ""
msgid "Group by:"
msgstr ""
-msgid "Group could not be imported: %{errors}"
-msgstr ""
-
msgid "Group description"
msgstr ""
@@ -10819,9 +11079,15 @@ msgstr ""
msgid "Group has not been marked for deletion"
msgstr ""
+msgid "Group import could not be scheduled"
+msgstr ""
+
msgid "Group info:"
msgstr ""
+msgid "Group is required when cluster_type is :group"
+msgstr ""
+
msgid "Group maintainers can register group runners in the %{link}"
msgstr ""
@@ -10885,6 +11151,24 @@ msgstr ""
msgid "GroupActivyMetrics|Recent activity (last 90 days)"
msgstr ""
+msgid "GroupImport|Failed to import group."
+msgstr ""
+
+msgid "GroupImport|Group '%{group_name}' is being imported."
+msgstr ""
+
+msgid "GroupImport|Group could not be imported: %{errors}"
+msgstr ""
+
+msgid "GroupImport|Please wait while we import the group for you. Refresh at will."
+msgstr ""
+
+msgid "GroupImport|The group was successfully imported."
+msgstr ""
+
+msgid "GroupImport|Unable to process group import file"
+msgstr ""
+
msgid "GroupRoadmap|%{dateWord} – No end date"
msgstr ""
@@ -11152,6 +11436,9 @@ msgstr ""
msgid "Groups (%{groups})"
msgstr ""
+msgid "Groups and projects"
+msgstr ""
+
msgid "Groups can also be nested by creating %{subgroup_docs_link_start}subgroups%{subgroup_docs_link_end}."
msgstr ""
@@ -11197,6 +11484,33 @@ msgstr ""
msgid "GroupsEmptyState|You can manage your group member’s permissions and access to each project in the group."
msgstr ""
+msgid "GroupsNew|Contact an administrator to enable options for importing your group."
+msgstr ""
+
+msgid "GroupsNew|Create"
+msgstr ""
+
+msgid "GroupsNew|Create group"
+msgstr ""
+
+msgid "GroupsNew|GitLab group export"
+msgstr ""
+
+msgid "GroupsNew|Import"
+msgstr ""
+
+msgid "GroupsNew|Import group"
+msgstr ""
+
+msgid "GroupsNew|My Awesome Group"
+msgstr ""
+
+msgid "GroupsNew|No import options available"
+msgstr ""
+
+msgid "GroupsNew|To copy a GitLab group between installations, navigate to the group settings page for the original installation, generate an export file, and upload it here."
+msgstr ""
+
msgid "GroupsTree|Are you sure you want to leave the \"%{fullName}\" group?"
msgstr ""
@@ -11299,6 +11613,9 @@ msgstr ""
msgid "Helps reduce request volume for protected paths"
msgstr ""
+msgid "Here are all your projects in your group, including the one you just created. To start, let’s take a look at your personalized learning project which will help you learn about GitLab at your own pace."
+msgstr ""
+
msgid "Here you will find recent merge request activity"
msgstr ""
@@ -11312,6 +11629,9 @@ msgid "Hide chart"
msgid_plural "Hide charts"
msgstr[0] ""
+msgid "Hide details"
+msgstr ""
+
msgid "Hide file browser"
msgstr ""
@@ -11321,6 +11641,9 @@ msgstr ""
msgid "Hide host keys manual input"
msgstr ""
+msgid "Hide list"
+msgstr ""
+
msgid "Hide marketing-related entries from help"
msgstr ""
@@ -11340,9 +11663,6 @@ msgstr[0] ""
msgid "Hide values"
msgstr ""
-msgid "Hiding all labels"
-msgstr ""
-
msgid "High or unknown vulnerabilities present"
msgstr ""
@@ -11427,6 +11747,9 @@ msgstr "ID"
msgid "ID:"
msgstr "ID:"
+msgid "IDE"
+msgstr ""
+
msgid "IDE|Allow live previews of JavaScript projects in the Web IDE using CodeSandbox Live Preview."
msgstr ""
@@ -11517,10 +11840,10 @@ msgstr ""
msgid "If enabled"
msgstr ""
-msgid "If enabled, access to projects will be validated on an external service using their classification label."
+msgid "If enabled, GitLab will handle Object Storage replication using Geo. %{linkStart}More information%{linkEnd}"
msgstr ""
-msgid "If enabled, and if object storage is enabled, GitLab will handle Object Storage replication using Geo"
+msgid "If enabled, access to projects will be validated on an external service using their classification label."
msgstr ""
msgid "If there is no previous license or if the previous license has expired, some GitLab functionality will be blocked until a new, valid license is uploaded."
@@ -11881,6 +12204,9 @@ msgid "Instance"
msgid_plural "Instances"
msgstr[0] ""
+msgid "Instance Configuration"
+msgstr ""
+
msgid "Instance Statistics visibility"
msgstr ""
@@ -11902,9 +12228,6 @@ msgstr ""
msgid "Integrations"
msgstr ""
-msgid "Integrations allow you to integrate GitLab with other applications"
-msgstr ""
-
msgid "Integrations|All details"
msgstr ""
@@ -12016,6 +12339,9 @@ msgstr ""
msgid "Invalid query"
msgstr ""
+msgid "Invalid repository bundle for snippet with id %{snippet_id}"
+msgstr ""
+
msgid "Invalid repository path"
msgstr ""
@@ -12034,6 +12360,9 @@ msgstr ""
msgid "Invalid two-factor code."
msgstr ""
+msgid "Invalid yaml"
+msgstr ""
+
msgid "Invitation"
msgstr ""
@@ -12253,15 +12582,24 @@ msgstr ""
msgid "It seems like the Dependency Scanning job ran successfully, but no dependencies have been detected in your project."
msgstr ""
+msgid "It seems that there is currently no available data for code coverage"
+msgstr ""
+
msgid "It's you"
msgstr ""
+msgid "Iteration"
+msgstr ""
+
msgid "Iteration changed to"
msgstr ""
msgid "Iteration removed"
msgstr ""
+msgid "Iteration updated"
+msgstr ""
+
msgid "Iterations"
msgstr ""
@@ -12292,12 +12630,18 @@ msgstr "1月"
msgid "January"
msgstr ""
+msgid "Jira Issues"
+msgstr ""
+
msgid "Jira import is already running."
msgstr ""
msgid "Jira integration not configured."
msgstr ""
+msgid "Jira project key is not configured"
+msgstr ""
+
msgid "Jira project: %{importProject}"
msgstr ""
@@ -12478,6 +12822,9 @@ msgstr ""
msgid "Keep divergent refs"
msgstr ""
+msgid "Kerberos access denied"
+msgstr ""
+
msgid "Key"
msgstr "金鑰"
@@ -12487,10 +12834,13 @@ msgstr ""
msgid "Key: %{key}"
msgstr ""
-msgid "Keyboard Shortcuts"
+msgid "Keyboard shortcuts"
msgstr ""
-msgid "Keyboard shortcuts"
+msgid "Keys"
+msgstr ""
+
+msgid "Ki"
msgstr ""
msgid "Kubernetes"
@@ -12532,6 +12882,9 @@ msgstr ""
msgid "LDAP"
msgstr ""
+msgid "LDAP Synchronization"
+msgstr ""
+
msgid "LDAP settings"
msgstr ""
@@ -12755,12 +13108,18 @@ msgstr ""
msgid "Learn more about custom project templates"
msgstr ""
+msgid "Learn more about deploying to AWS"
+msgstr ""
+
msgid "Learn more about deploying to a cluster"
msgstr ""
msgid "Learn more about group-level project templates"
msgstr ""
+msgid "Learn more about job dependencies"
+msgstr ""
+
msgid "Learn more about signing commits"
msgstr ""
@@ -12812,9 +13171,21 @@ msgstr ""
msgid "License History"
msgstr ""
+msgid "License ID:"
+msgstr ""
+
+msgid "License URL"
+msgstr ""
+
msgid "License-Check"
msgstr ""
+msgid "LicenseCompliance|%{docLinkStart}License Approvals%{docLinkEnd} are active"
+msgstr ""
+
+msgid "LicenseCompliance|%{docLinkStart}License Approvals%{docLinkEnd} are inactive"
+msgstr ""
+
msgid "LicenseCompliance|Add a license"
msgstr ""
@@ -12836,9 +13207,15 @@ msgstr ""
msgid "LicenseCompliance|Deny"
msgstr ""
+msgid "LicenseCompliance|Learn more about %{linkStart}License Approvals%{linkEnd}"
+msgstr ""
+
msgid "LicenseCompliance|License"
msgstr ""
+msgid "LicenseCompliance|License Approvals"
+msgstr ""
+
msgid "LicenseCompliance|License Compliance detected %d license and policy violation for the source branch only; approval required"
msgid_plural "LicenseCompliance|License Compliance detected %d licenses and policy violations for the source branch only; approval required"
msgstr[0] ""
@@ -13054,6 +13431,9 @@ msgstr ""
msgid "List your Bitbucket Server repositories"
msgstr ""
+msgid "Lists"
+msgstr ""
+
msgid "Live preview"
msgstr ""
@@ -13237,6 +13617,9 @@ msgstr ""
msgid "Manage project labels"
msgstr ""
+msgid "Manage storage usage"
+msgstr ""
+
msgid "Manage two-factor authentication"
msgstr ""
@@ -13252,6 +13635,9 @@ msgstr ""
msgid "Manifest file import"
msgstr ""
+msgid "Manifest import"
+msgstr ""
+
msgid "Manual job"
msgstr ""
@@ -13399,6 +13785,12 @@ msgstr ""
msgid "Maximum field length"
msgstr ""
+msgid "Maximum file size is 2MB. Please select a smaller file."
+msgstr ""
+
+msgid "Maximum import size (MB)"
+msgstr ""
+
msgid "Maximum job timeout"
msgstr ""
@@ -13438,6 +13830,9 @@ msgstr ""
msgid "Maximum size of Elasticsearch bulk indexing requests."
msgstr ""
+msgid "Maximum size of import files."
+msgstr ""
+
msgid "Maximum size of individual attachments in comments."
msgstr ""
@@ -13489,6 +13884,9 @@ msgstr ""
msgid "Merge (when the pipeline succeeds)"
msgstr ""
+msgid "Merge Conflicts"
+msgstr ""
+
msgid "Merge Request"
msgstr ""
@@ -13615,6 +14013,9 @@ msgstr ""
msgid "MergeRequests|Squash task canceled: another squash is already in progress."
msgstr ""
+msgid "MergeRequests|This project does not allow squashing commits when merge requests are accepted."
+msgstr ""
+
msgid "MergeRequests|Thread stays resolved"
msgstr ""
@@ -13768,6 +14169,12 @@ msgstr ""
msgid "MetricsSettings|Add a button to the metrics dashboard linking directly to your existing external dashboard."
msgstr ""
+msgid "MetricsSettings|Choose whether to display dashboard metrics in UTC or the user's local timezone."
+msgstr ""
+
+msgid "MetricsSettings|Dashboard timezone"
+msgstr ""
+
msgid "MetricsSettings|External dashboard URL"
msgstr ""
@@ -13777,6 +14184,12 @@ msgstr ""
msgid "MetricsSettings|Metrics Dashboard"
msgstr ""
+msgid "MetricsSettings|UTC (Coordinated Universal Time)"
+msgstr ""
+
+msgid "MetricsSettings|User's local timezone"
+msgstr ""
+
msgid "Metrics|Add metric"
msgstr ""
@@ -13792,6 +14205,9 @@ msgstr ""
msgid "Metrics|Create metric"
msgstr ""
+msgid "Metrics|Current"
+msgstr ""
+
msgid "Metrics|Delete metric"
msgstr ""
@@ -13841,6 +14257,9 @@ msgstr ""
msgid "Metrics|Max"
msgstr ""
+msgid "Metrics|Min"
+msgstr ""
+
msgid "Metrics|Must be a valid PromQL query."
msgstr ""
@@ -13856,6 +14275,9 @@ msgstr ""
msgid "Metrics|Refresh dashboard"
msgstr ""
+msgid "Metrics|Select a value"
+msgstr ""
+
msgid "Metrics|Star dashboard"
msgstr ""
@@ -13874,12 +14296,18 @@ msgstr ""
msgid "Metrics|There was an error getting annotations information."
msgstr ""
+msgid "Metrics|There was an error getting dashboard validation warnings information."
+msgstr ""
+
msgid "Metrics|There was an error getting deployment information."
msgstr ""
msgid "Metrics|There was an error getting environments information."
msgstr ""
+msgid "Metrics|There was an error getting options for variable \"%{name}\"."
+msgstr ""
+
msgid "Metrics|There was an error trying to validate your query"
msgstr ""
@@ -13922,6 +14350,9 @@ msgstr ""
msgid "Metrics|You're about to permanently delete this metric. This cannot be undone."
msgstr ""
+msgid "Metrics|Your dashboard schema is invalid. Edit the dashboard to correct the YAML schema."
+msgstr ""
+
msgid "Metrics|e.g. HTTP requests"
msgstr ""
@@ -13937,12 +14368,18 @@ msgstr ""
msgid "Metrics|e.g. req/sec"
msgstr ""
+msgid "Mi"
+msgstr ""
+
msgid "Microsoft Azure"
msgstr ""
msgid "Middleman project with Static Site Editor support"
msgstr ""
+msgid "Migrate your data from an external source like GitHub, Bitbucket, or another instance of GitLab."
+msgstr ""
+
msgid "Migrated %{success_count}/%{total_count} files."
msgstr ""
@@ -14076,6 +14513,9 @@ msgstr ""
msgid "Minimum capacity to be available before we schedule more mirrors preemptively."
msgstr ""
+msgid "Minimum interval in days"
+msgstr ""
+
msgid "Minimum length is %{minimum_password_length} characters"
msgstr ""
@@ -14262,6 +14702,12 @@ msgstr ""
msgid "Multiple uploaders found: %{uploader_types}"
msgstr ""
+msgid "Must match with the %{codeStart}external_url%{codeEnd} in %{codeStart}/etc/gitlab/gitlab.rb%{codeEnd}."
+msgstr ""
+
+msgid "Must match with the %{codeStart}geo_node_name%{codeEnd} in %{codeStart}/etc/gitlab/gitlab.rb%{codeEnd}. %{linkStart}More information%{linkEnd}"
+msgstr ""
+
msgid "My Awesome Group"
msgstr ""
@@ -14376,10 +14822,16 @@ msgstr "新增"
msgid "New Application"
msgstr ""
+msgid "New Branch"
+msgstr ""
+
+msgid "New Deploy Key"
+msgstr ""
+
msgid "New Environment"
msgstr ""
-msgid "New Geo Node"
+msgid "New File"
msgstr ""
msgid "New Group"
@@ -14395,15 +14847,15 @@ msgid "New Issue"
msgid_plural "New Issues"
msgstr[0] ""
-msgid "New Iteration"
-msgstr ""
-
msgid "New Jira import"
msgstr ""
msgid "New Label"
msgstr ""
+msgid "New Merge Request"
+msgstr ""
+
msgid "New Milestone"
msgstr ""
@@ -14422,6 +14874,9 @@ msgstr ""
msgid "New Snippet"
msgstr ""
+msgid "New User"
+msgstr ""
+
msgid "New branch"
msgstr ""
@@ -14464,6 +14919,9 @@ msgstr ""
msgid "New issue title"
msgstr ""
+msgid "New iteration"
+msgstr ""
+
msgid "New iteration created"
msgstr ""
@@ -14641,6 +15099,12 @@ msgstr ""
msgid "No grouping"
msgstr ""
+msgid "No iteration"
+msgstr ""
+
+msgid "No iterations to show"
+msgstr ""
+
msgid "No job log"
msgstr ""
@@ -14683,6 +15147,9 @@ msgstr ""
msgid "No other labels with such name or description"
msgstr ""
+msgid "No panels matching properties %{opts}"
+msgstr ""
+
msgid "No parent group"
msgstr ""
@@ -14716,6 +15183,9 @@ msgstr ""
msgid "No schedules"
msgstr ""
+msgid "No stack trace for this error"
+msgstr ""
+
msgid "No starrers matched your search"
msgstr ""
@@ -14728,7 +15198,7 @@ msgstr ""
msgid "No test coverage"
msgstr ""
-msgid "No thanks, don't show this again"
+msgid "No thanks"
msgstr ""
msgid "No vulnerabilities present"
@@ -14743,9 +15213,6 @@ msgstr ""
msgid "No, directly import the existing email addresses and usernames."
msgstr ""
-msgid "No, not interested right now"
-msgstr ""
-
msgid "No. of commits"
msgstr ""
@@ -14791,9 +15258,6 @@ msgstr ""
msgid "Not found."
msgstr ""
-msgid "Not helpful"
-msgstr ""
-
msgid "Not now"
msgstr ""
@@ -14950,6 +15414,9 @@ msgstr ""
msgid "Notifications on"
msgstr ""
+msgid "Notify users by email when sign-in location is not recognized"
+msgstr ""
+
msgid "Nov"
msgstr "11月"
@@ -15016,9 +15483,6 @@ msgstr ""
msgid "Oh no!"
msgstr ""
-msgid "Ok let's go"
-msgstr ""
-
msgid "Oldest first"
msgstr ""
@@ -15031,16 +15495,49 @@ msgstr ""
msgid "On track"
msgstr ""
+msgid "OnDemandScans|Attached branch"
+msgstr ""
+
+msgid "OnDemandScans|Attached branch is where the scan job runs."
+msgstr ""
+
+msgid "OnDemandScans|Could not run the scan. Please try again."
+msgstr ""
+
msgid "OnDemandScans|Create new DAST scan"
msgstr ""
+msgid "OnDemandScans|DAST will scan the target URL and any discovered sub URLs."
+msgstr ""
+
+msgid "OnDemandScans|New on-demand DAST scan"
+msgstr ""
+
msgid "OnDemandScans|On-demand Scans"
msgstr ""
+msgid "OnDemandScans|On-demand scans run outside the DevOps cycle and find vulnerabilities in your projects. %{learnMoreLinkStart}Learn more%{learnMoreLinkEnd}"
+msgstr ""
+
+msgid "OnDemandScans|Only a passive scan can be performed on demand."
+msgstr ""
+
+msgid "OnDemandScans|Passive DAST Scan"
+msgstr ""
+
+msgid "OnDemandScans|Please enter a valid URL format, ex: http://www.example.com/home"
+msgstr ""
+
+msgid "OnDemandScans|Run this scan"
+msgstr ""
+
+msgid "OnDemandScans|Scan mode"
+msgstr ""
+
msgid "OnDemandScans|Schedule or run scans immediately against target sites. Currently available on-demand scan type: DAST. %{helpLinkStart}More information%{helpLinkEnd}"
msgstr ""
-msgid "Onboarding"
+msgid "OnDemandScans|Target URL"
msgstr ""
msgid "Once imported, repositories can be mirrored over SSH. Read more %{link_start}here%{link_end}."
@@ -15065,6 +15562,9 @@ msgstr ""
msgid "One or more of you personal access tokens were revoked"
msgstr ""
+msgid "One or more of your %{provider} projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
+msgstr ""
+
msgid "One or more of your Bitbucket projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
msgstr ""
@@ -15566,6 +16066,9 @@ msgstr ""
msgid "Parent epic is not present."
msgstr ""
+msgid "Parsing error for param :embed_json. %{message}"
+msgstr ""
+
msgid "Part of merge request changes"
msgstr ""
@@ -15578,6 +16081,9 @@ msgstr ""
msgid "Passed"
msgstr ""
+msgid "Passed on"
+msgstr ""
+
msgid "Password"
msgstr ""
@@ -15662,6 +16168,9 @@ msgstr ""
msgid "Perform common operations on GitLab project"
msgstr ""
+msgid "Performance and resource management"
+msgstr ""
+
msgid "Performance optimization"
msgstr ""
@@ -15869,7 +16378,7 @@ msgstr ""
msgid "Pipelines|Something went wrong while cleaning runners cache."
msgstr ""
-msgid "Pipelines|There are currently no %{scope} pipelines."
+msgid "Pipelines|There are currently no finished pipelines."
msgstr ""
msgid "Pipelines|There are currently no pipelines."
@@ -15980,6 +16489,9 @@ msgstr ""
msgid "Pipeline|Stop pipeline #%{pipelineId}?"
msgstr ""
+msgid "Pipeline|Tag name"
+msgstr ""
+
msgid "Pipeline|Trigger author"
msgstr ""
@@ -16061,12 +16573,18 @@ msgstr ""
msgid "Please check your email (%{email}) to verify that you own this address and unlock the power of CI/CD. Didn't receive it? %{resend_link}. Wrong email address? %{update_link}."
msgstr ""
+msgid "Please choose a file"
+msgstr ""
+
msgid "Please choose a group URL with no special characters."
msgstr ""
msgid "Please complete your profile with email address"
msgstr ""
+msgid "Please convert %{linkStart}them to Git%{linkEnd}, and go through the %{linkToImportFlow} again."
+msgstr ""
+
msgid "Please convert them to %{link_to_git}, and go through the %{link_to_import_flow} again."
msgstr ""
@@ -16124,6 +16642,9 @@ msgstr ""
msgid "Please provide a valid email address."
msgstr ""
+msgid "Please provide attributes to update"
+msgstr ""
+
msgid "Please refer to <a href=\"%{docs_url}\">%{docs_url}</a>"
msgstr ""
@@ -16859,6 +17380,9 @@ msgstr ""
msgid "Project has too many %{label_for_message} to search"
msgstr ""
+msgid "Project is required when cluster_type is :project"
+msgstr ""
+
msgid "Project members"
msgstr ""
@@ -16976,7 +17500,40 @@ msgstr ""
msgid "ProjectService|Comment"
msgstr ""
-msgid "ProjectService|Comment will be posted on each event"
+msgid "ProjectService|Event will be triggered by a push to the repository"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a commit is created/updated"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a confidential issue is created/updated/closed"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a deployment finishes"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a merge request is created/updated/merged"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a new tag is pushed to the repository"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a new, unique alert is recorded"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a pipeline status changes"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when a wiki page is created/updated"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when an issue is created/updated/closed"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when someone adds a comment"
+msgstr ""
+
+msgid "ProjectService|Event will be triggered when someone adds a comment on a confidential issue"
msgstr ""
msgid "ProjectService|Perform common operations on GitLab project: %{project_name}"
@@ -17027,6 +17584,9 @@ msgstr ""
msgid "ProjectSettings|Enable 'Delete source branch' option by default"
msgstr ""
+msgid "ProjectSettings|Enable merge trains and pipelines for merged results"
+msgstr ""
+
msgid "ProjectSettings|Every merge creates a merge commit"
msgstr ""
@@ -17057,7 +17617,7 @@ msgstr ""
msgid "ProjectSettings|Forks"
msgstr ""
-msgid "ProjectSettings|Git Large File Storage"
+msgid "ProjectSettings|Git Large File Storage (LFS)"
msgstr ""
msgid "ProjectSettings|Internal"
@@ -17093,9 +17653,6 @@ msgstr ""
msgid "ProjectSettings|Merge options"
msgstr ""
-msgid "ProjectSettings|Merge pipelines will try to validate the post-merge result prior to merging"
-msgstr ""
-
msgid "ProjectSettings|Merge requests"
msgstr ""
@@ -17123,6 +17680,9 @@ msgstr ""
msgid "ProjectSettings|Pipelines"
msgstr ""
+msgid "ProjectSettings|Pipelines for merge requests must be enabled in the CI/CD configuration file, or pipelines could be unresolvable or dropped"
+msgstr ""
+
msgid "ProjectSettings|Pipelines must succeed"
msgstr ""
@@ -17150,6 +17710,9 @@ msgstr ""
msgid "ProjectSettings|Show link to create/view merge request when pushing from the command line"
msgstr ""
+msgid "ProjectSettings|Skipped pipelines are considered successful"
+msgstr ""
+
msgid "ProjectSettings|Snippets"
msgstr ""
@@ -17165,6 +17728,9 @@ msgstr ""
msgid "ProjectSettings|These checks must pass before merge requests can be merged"
msgstr ""
+msgid "ProjectSettings|This introduces the risk of merging changes that will not pass the pipeline."
+msgstr ""
+
msgid "ProjectSettings|This setting is applied on the server level and can be overridden by an admin."
msgstr ""
@@ -17342,15 +17908,27 @@ msgstr ""
msgid "ProjectsNew|Blank project"
msgstr ""
+msgid "ProjectsNew|Connect your external repository to GitLab CI/CD."
+msgstr ""
+
msgid "ProjectsNew|Contact an administrator to enable options for importing your project."
msgstr ""
msgid "ProjectsNew|Create"
msgstr ""
+msgid "ProjectsNew|Create a blank project to house your files, plan your work, and collaborate on code, among other things."
+msgstr ""
+
+msgid "ProjectsNew|Create blank project"
+msgstr ""
+
msgid "ProjectsNew|Create from template"
msgstr ""
+msgid "ProjectsNew|Create new project"
+msgstr ""
+
msgid "ProjectsNew|Creating project & repository."
msgstr ""
@@ -17375,6 +17953,9 @@ msgstr ""
msgid "ProjectsNew|Project description %{tag_start}(optional)%{tag_end}"
msgstr ""
+msgid "ProjectsNew|Run CI/CD for external repository"
+msgstr ""
+
msgid "ProjectsNew|Template"
msgstr ""
@@ -17636,6 +18217,9 @@ msgstr ""
msgid "Protected Branch"
msgstr ""
+msgid "Protected Branches"
+msgstr ""
+
msgid "Protected Environment"
msgstr ""
@@ -17648,6 +18232,9 @@ msgstr ""
msgid "Protected Tag"
msgstr ""
+msgid "Protected Tags"
+msgstr ""
+
msgid "Protected branches"
msgstr ""
@@ -17747,6 +18334,9 @@ msgstr ""
msgid "Provider"
msgstr ""
+msgid "Proxy support for this API is not available currently"
+msgstr ""
+
msgid "Pseudonymizer data collection"
msgstr ""
@@ -17771,6 +18361,9 @@ msgstr ""
msgid "Publish to status page"
msgstr ""
+msgid "Published on status page"
+msgstr ""
+
msgid "Publishes this issue to the associated status page."
msgstr ""
@@ -17945,6 +18538,9 @@ msgstr ""
msgid "Recipe"
msgstr ""
+msgid "Reconfigure"
+msgstr ""
+
msgid "Recover hidden stage"
msgstr ""
@@ -18030,6 +18626,9 @@ msgstr ""
msgid "Registration|Your profile"
msgstr ""
+msgid "Regulate approvals by authors/committers, based on compliance frameworks. Can be changed only at the instance level."
+msgstr ""
+
msgid "Rejected (closed)"
msgstr ""
@@ -18079,15 +18678,24 @@ msgstr ""
msgid "ReleaseAssetLinkType|Image"
msgstr ""
+msgid "ReleaseAssetLinkType|Images"
+msgstr ""
+
msgid "ReleaseAssetLinkType|Other"
msgstr ""
msgid "ReleaseAssetLinkType|Package"
msgstr ""
+msgid "ReleaseAssetLinkType|Packages"
+msgstr ""
+
msgid "ReleaseAssetLinkType|Runbook"
msgstr ""
+msgid "ReleaseAssetLinkType|Runbooks"
+msgstr ""
+
msgid "Releases"
msgstr ""
@@ -18100,6 +18708,9 @@ msgstr ""
msgid "Releases documentation"
msgstr ""
+msgid "Releases|New Release"
+msgstr ""
+
msgid "Release|Something went wrong while getting the release details"
msgstr ""
@@ -18172,6 +18783,9 @@ msgstr ""
msgid "Remove fork relationship"
msgstr ""
+msgid "Remove from batch"
+msgstr ""
+
msgid "Remove from board"
msgstr ""
@@ -18361,6 +18975,12 @@ msgstr ""
msgid "Replication"
msgstr ""
+msgid "Replication enabled"
+msgstr ""
+
+msgid "Replication paused"
+msgstr ""
+
msgid "Reply by email"
msgstr ""
@@ -18388,15 +19008,15 @@ msgstr ""
msgid "Reported %{timeAgo} by %{reportedBy}"
msgstr ""
-msgid "Reported Resource Changes: %{addNum} to add, %{changeNum} to change, %{deleteNum} to delete"
-msgstr ""
-
msgid "Reporter"
msgstr ""
msgid "Reporting"
msgstr ""
+msgid "Reports"
+msgstr ""
+
msgid "Reports|%{combinedString} and %{resolvedString}"
msgstr ""
@@ -18434,6 +19054,9 @@ msgstr ""
msgid "Reports|Failure"
msgstr ""
+msgid "Reports|Identifier"
+msgstr ""
+
msgid "Reports|Metrics reports are loading"
msgstr ""
@@ -18446,6 +19069,9 @@ msgstr ""
msgid "Reports|Metrics reports failed loading results"
msgstr ""
+msgid "Reports|Scanner"
+msgstr ""
+
msgid "Reports|Severity"
msgstr ""
@@ -18494,15 +19120,33 @@ msgstr ""
msgid "Repository cleanup has started. You will receive an email once the cleanup operation is complete."
msgstr ""
+msgid "Repository files count over the limit"
+msgstr ""
+
+msgid "Repository has an invalid default branch name."
+msgstr ""
+
+msgid "Repository has more than one branch."
+msgstr ""
+
msgid "Repository has no locks."
msgstr ""
+msgid "Repository has tags."
+msgstr ""
+
msgid "Repository maintenance"
msgstr ""
msgid "Repository mirroring"
msgstr ""
+msgid "Repository must contain at least 1 file."
+msgstr ""
+
+msgid "Repository size is above the limit."
+msgstr ""
+
msgid "Repository static objects"
msgstr ""
@@ -18512,7 +19156,7 @@ msgstr ""
msgid "Repository sync capacity"
msgstr ""
-msgid "Repository: %{counter_repositories} / Wikis: %{counter_wikis} / Build Artifacts: %{counter_build_artifacts} / LFS: %{counter_lfs_objects}"
+msgid "Repository: %{counter_repositories} / Wikis: %{counter_wikis} / Build Artifacts: %{counter_build_artifacts} / LFS: %{counter_lfs_objects} / Snippets: %{counter_snippets}"
msgstr ""
msgid "RepositorySettingsAccessLevel|Select"
@@ -18524,6 +19168,9 @@ msgstr ""
msgid "Request Headers"
msgstr ""
+msgid "Request details"
+msgstr ""
+
msgid "Request parameter %{param} is missing."
msgstr ""
@@ -18628,6 +19275,9 @@ msgstr ""
msgid "Reset template"
msgstr ""
+msgid "Reset to project defaults"
+msgstr ""
+
msgid "Resetting the authorization key for this project will require updating the authorization key in every alert source it is enabled in."
msgstr ""
@@ -18724,9 +19374,6 @@ msgstr ""
msgid "Resume"
msgstr ""
-msgid "Resume replication"
-msgstr ""
-
msgid "Resync"
msgstr ""
@@ -19338,6 +19985,9 @@ msgstr ""
msgid "SecurityReports|False positive"
msgstr ""
+msgid "SecurityReports|Group Security Dashboard"
+msgstr ""
+
msgid "SecurityReports|Hide dismissed"
msgstr ""
@@ -19392,10 +20042,13 @@ msgstr ""
msgid "SecurityReports|Remove project from dashboard"
msgstr ""
-msgid "SecurityReports|Report type"
+msgid "SecurityReports|Return to dashboard"
+msgstr ""
+
+msgid "SecurityReports|Scan details"
msgstr ""
-msgid "SecurityReports|Return to dashboard"
+msgid "SecurityReports|Scanner type"
msgstr ""
msgid "SecurityReports|Security Dashboard"
@@ -19452,6 +20105,9 @@ msgstr ""
msgid "SecurityReports|There was an error while generating the report."
msgstr ""
+msgid "SecurityReports|Unable to add %{invalidProjectsMessage}"
+msgstr ""
+
msgid "SecurityReports|Unable to add %{invalidProjects}"
msgstr ""
@@ -19566,6 +20222,9 @@ msgstr ""
msgid "Select due date"
msgstr ""
+msgid "Select file"
+msgstr ""
+
msgid "Select group or project"
msgstr ""
@@ -19629,9 +20288,6 @@ msgstr ""
msgid "Select the branch you want to set as the default for this project. All merge requests and commits will automatically be made against this branch unless you specify a different one."
msgstr ""
-msgid "Select the configured storage available for new repositories to be placed on."
-msgstr ""
-
msgid "Select the custom project template source group."
msgstr ""
@@ -19824,12 +20480,6 @@ msgstr ""
msgid "Service Desk is enabled but not yet active"
msgstr ""
-msgid "Service Desk is off"
-msgstr ""
-
-msgid "Service Desk is on"
-msgstr ""
-
msgid "Service Templates"
msgstr ""
@@ -19917,6 +20567,15 @@ msgstr ""
msgid "Set the milestone to %{milestone_reference}."
msgstr ""
+msgid "Set the number of concurrent requests this secondary node will make to the primary node while backfilling."
+msgstr ""
+
+msgid "Set the synchronization and verification capacity for the secondary node."
+msgstr ""
+
+msgid "Set the timeout in seconds to send a secondary node status to the primary and IPs allowed for the secondary nodes."
+msgstr ""
+
msgid "Set time estimate"
msgstr ""
@@ -19956,6 +20615,9 @@ msgstr ""
msgid "Set weight to %{weight}."
msgstr ""
+msgid "Set what should be replicated by choosing specific projects or groups by the secondary node."
+msgstr ""
+
msgid "SetPasswordToCloneLink|set a password"
msgstr ""
@@ -20082,6 +20744,9 @@ msgstr ""
msgid "Show complete raw log"
msgstr ""
+msgid "Show details"
+msgstr ""
+
msgid "Show file browser"
msgstr ""
@@ -20091,10 +20756,13 @@ msgstr ""
msgid "Show latest version"
msgstr ""
+msgid "Show list"
+msgstr ""
+
msgid "Show me everything"
msgstr ""
-msgid "Show me how"
+msgid "Show me how to add a pipeline"
msgstr ""
msgid "Show me more advanced stuff"
@@ -20141,9 +20809,6 @@ msgstr ""
msgid "Showing all issues"
msgstr ""
-msgid "Showing all labels"
-msgstr ""
-
msgid "Showing last %{size} of log -"
msgstr ""
@@ -20255,9 +20920,6 @@ msgstr ""
msgid "Skip outdated deployment jobs"
msgstr ""
-msgid "Skip this for now"
-msgstr ""
-
msgid "Skipped"
msgstr ""
@@ -20402,6 +21064,9 @@ msgstr ""
msgid "Something went wrong while adding your award. Please try again."
msgstr ""
+msgid "Something went wrong while applying the batch of suggestions. Please try again."
+msgstr ""
+
msgid "Something went wrong while applying the suggestion. Please try again."
msgstr ""
@@ -20504,10 +21169,13 @@ msgstr ""
msgid "Something went wrong while updating a requirement."
msgstr ""
+msgid "Something went wrong while updating assignees"
+msgstr ""
+
msgid "Something went wrong while updating your list settings"
msgstr ""
-msgid "Something went wrong with your automatic subscription renewal"
+msgid "Something went wrong with your automatic subscription renewal."
msgstr ""
msgid "Something went wrong, unable to add %{project} to dashboard"
@@ -20525,6 +21193,9 @@ msgstr ""
msgid "Something went wrong, unable to search projects"
msgstr ""
+msgid "Something went wrong."
+msgstr ""
+
msgid "Something went wrong. Please try again."
msgstr ""
@@ -20708,6 +21379,9 @@ msgstr ""
msgid "Source code"
msgstr ""
+msgid "Source code (%{fileExtension})"
+msgstr ""
+
msgid "Source is not available"
msgstr ""
@@ -20792,6 +21466,9 @@ msgstr ""
msgid "Stack trace"
msgstr ""
+msgid "Stacktrace snippet"
+msgstr ""
+
msgid "Stage"
msgstr ""
@@ -20897,6 +21574,9 @@ msgstr ""
msgid "Start thread & reopen %{noteable_name}"
msgstr ""
+msgid "Start using Directed Acyclic Graphs (DAG)"
+msgstr ""
+
msgid "Start your Free Gold Trial"
msgstr ""
@@ -21359,6 +22039,21 @@ msgstr ""
msgid "SuggestedColors|Very pale orange"
msgstr ""
+msgid "Suggestion is not applicable as the suggestion was not found."
+msgstr ""
+
+msgid "Suggestions are not applicable as one or more suggestions were not found."
+msgstr ""
+
+msgid "Suggestions are not applicable as their lines cannot overlap."
+msgstr ""
+
+msgid "Suggestions must all be on the same branch."
+msgstr ""
+
+msgid "Suggestions that change line count can't be added to batches, yet."
+msgstr ""
+
msgid "Suggestions:"
msgstr ""
@@ -21401,9 +22096,6 @@ msgstr ""
msgid "Synchronization disabled"
msgstr ""
-msgid "Synchronization of container repositories is disabled."
-msgstr ""
-
msgid "System"
msgstr ""
@@ -21584,6 +22276,32 @@ msgstr ""
msgid "Terms of Service and Privacy Policy"
msgstr ""
+msgid "Terraform|%{number} Terraform report failed to generate"
+msgid_plural "Terraform|%{number} Terraform reports failed to generate"
+msgstr[0] ""
+
+msgid "Terraform|%{number} Terraform report was generated in your pipelines"
+msgid_plural "Terraform|%{number} Terraform reports were generated in your pipelines"
+msgstr[0] ""
+
+msgid "Terraform|A Terraform report failed to generate."
+msgstr ""
+
+msgid "Terraform|A Terraform report was generated in your pipelines."
+msgstr ""
+
+msgid "Terraform|Generating the report caused an error."
+msgstr ""
+
+msgid "Terraform|Reported Resource Changes: %{addNum} to add, %{changeNum} to change, %{deleteNum} to delete"
+msgstr ""
+
+msgid "Terraform|The Terraform report %{name} failed to generate."
+msgstr ""
+
+msgid "Terraform|The Terraform report %{name} was generated in your pipelines."
+msgstr ""
+
msgid "Test"
msgstr ""
@@ -21609,7 +22327,7 @@ msgstr ""
msgid "TestHooks|Ensure the project has CI pipelines."
msgstr ""
-msgid "TestHooks|Ensure the project has at least one commit."
+msgid "TestHooks|Ensure the project has deployments."
msgstr ""
msgid "TestHooks|Ensure the project has issues."
@@ -21709,7 +22427,7 @@ msgstr ""
msgid "The Prometheus server responded with \"bad request\". Please check your queries are correct and are supported in your Prometheus version. %{documentationLink}"
msgstr ""
-msgid "The URL defined on the primary node that secondary nodes should use to contact it. Defaults to URL"
+msgid "The URL defined on the primary node that secondary nodes should use to contact it."
msgstr ""
msgid "The URL to use for connecting to Elasticsearch. Use a comma-separated list to support clustering (e.g., \"http://localhost:9200, http://localhost:9201\")."
@@ -21718,6 +22436,9 @@ msgstr ""
msgid "The X509 Certificate to use when mutual TLS is required to communicate with the external authorization service. If left blank, the server certificate is still validated when accessing over HTTPS."
msgstr ""
+msgid "The above settings apply to all projects with the selected compliance framework(s)."
+msgstr ""
+
msgid "The amount of seconds after which a request to get a secondary node status will time out."
msgstr ""
@@ -22016,9 +22737,6 @@ msgstr ""
msgid "The total stage shows the time it takes between creating an issue and deploying the code to production. The data will be automatically added once you have completed the full idea to production cycle."
msgstr ""
-msgid "The unique identifier for the Geo node. Must match %{geoNodeName} if it is set in gitlab.rb, otherwise it must match %{externalUrl} with a trailing slash"
-msgstr ""
-
msgid "The update action will time out after %{number_of_minutes} minutes. For big repositories, use a clone/push combination."
msgstr ""
@@ -22043,9 +22761,6 @@ msgstr ""
msgid "The user you are trying to deactivate has been active in the past %{minimum_inactive_days} days and cannot be deactivated"
msgstr ""
-msgid "The user-facing URL of the Geo node"
-msgstr ""
-
msgid "The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6."
msgstr ""
@@ -22136,6 +22851,9 @@ msgstr ""
msgid "There was a problem fetching project branches."
msgstr ""
+msgid "There was a problem fetching project tags."
+msgstr ""
+
msgid "There was a problem fetching project users."
msgstr ""
@@ -22187,6 +22905,9 @@ msgstr ""
msgid "There was an error fetching the %{replicableType}"
msgstr ""
+msgid "There was an error fetching the Geo Settings"
+msgstr ""
+
msgid "There was an error fetching the Node's Groups"
msgstr ""
@@ -22307,6 +23028,12 @@ msgstr ""
msgid "This %{issuable} is locked. Only <strong>project members</strong> can comment."
msgstr ""
+msgid "This %{noteableTypeText} is %{confidentialLinkStart}confidential%{linkEnd} and %{lockedLinkStart}locked%{linkEnd}."
+msgstr ""
+
+msgid "This %{noteableTypeText} is locked."
+msgstr ""
+
msgid "This %{viewer} could not be displayed because %{reason}. You can %{options} instead."
msgstr ""
@@ -22325,6 +23052,9 @@ msgstr ""
msgid "This action can lead to data loss. To prevent accidental actions we ask you to confirm your intention."
msgstr ""
+msgid "This also resolves all related threads"
+msgstr ""
+
msgid "This also resolves the discussion"
msgstr ""
@@ -22415,6 +23145,9 @@ msgstr ""
msgid "This epic does not exist or you don't have sufficient permission."
msgstr ""
+msgid "This feature is currently in beta. We invite you to %{linkStart}give feedback%{linkEnd}."
+msgstr ""
+
msgid "This feature requires local storage to be enabled"
msgstr ""
@@ -22448,7 +23181,7 @@ msgstr ""
msgid "This is a Work in Progress"
msgstr ""
-msgid "This is a confidential issue."
+msgid "This is a confidential %{noteableTypeText}."
msgstr ""
msgid "This is a delayed job to run in %{remainingTime}"
@@ -22475,9 +23208,6 @@ msgstr ""
msgid "This is your current session"
msgstr ""
-msgid "This issue is %{confidentialLinkStart}confidential%{linkEnd} and %{lockedLinkStart}locked%{linkEnd}."
-msgstr ""
-
msgid "This issue is confidential"
msgstr ""
@@ -22487,9 +23217,6 @@ msgstr ""
msgid "This issue is in a child epic of the filtered epic"
msgstr ""
-msgid "This issue is locked."
-msgstr ""
-
msgid "This job depends on other jobs with expired/erased artifacts: %{invalid_dependencies}"
msgstr ""
@@ -22589,6 +23316,9 @@ msgstr ""
msgid "This license has already expired."
msgstr ""
+msgid "This link points to external content"
+msgstr ""
+
msgid "This may expose confidential information as the selected fork is in another namespace that can have other members."
msgstr ""
@@ -22616,6 +23346,9 @@ msgstr ""
msgid "This page will be removed in a future release."
msgstr ""
+msgid "This pipeline does not use the %{codeStart}needs%{codeEnd} keyword and can't be represented as a directed acyclic graph."
+msgstr ""
+
msgid "This pipeline makes use of a predefined CI/CD configuration enabled by %{strongStart}Auto DevOps.%{strongEnd}"
msgstr ""
@@ -22844,6 +23577,9 @@ msgstr ""
msgid "Time from last commit to merge"
msgstr ""
+msgid "Time in seconds"
+msgstr ""
+
msgid "Time in seconds GitLab will wait for a response from the external service. When the service does not respond in time, access will be denied."
msgstr ""
@@ -23263,9 +23999,15 @@ msgstr ""
msgid "Total artifacts size: %{total_size}"
msgstr ""
+msgid "Total cores (CPUs)"
+msgstr ""
+
msgid "Total issues"
msgstr ""
+msgid "Total memory (GB)"
+msgstr ""
+
msgid "Total test time for all commits/merges"
msgstr ""
@@ -23467,6 +24209,9 @@ msgstr ""
msgid "U2F only works with HTTPS-enabled websites. Contact your administrator for more details."
msgstr ""
+msgid "UI Development Kit"
+msgstr ""
+
msgid "URL"
msgstr "URL"
@@ -23539,9 +24284,6 @@ msgstr ""
msgid "Unable to load the merge request widget. Try reloading the page."
msgstr ""
-msgid "Unable to process group import file"
-msgstr ""
-
msgid "Unable to resolve"
msgstr ""
@@ -23557,6 +24299,9 @@ msgstr ""
msgid "Unable to sign you in to the group with SAML due to \"%{reason}\""
msgstr ""
+msgid "Unable to suggest a path. Please refresh and try again."
+msgstr ""
+
msgid "Unable to update label prioritization at this time"
msgstr ""
@@ -23572,6 +24317,9 @@ msgstr ""
msgid "Unarchiving the project will restore people's ability to make changes to it. The repository can be committed to, and issues, comments, and other entities can be created. %{strong_start}Once active, this project shows up in the search and on the dashboard.%{strong_end}"
msgstr ""
+msgid "Unassign from commenting user"
+msgstr ""
+
msgid "Unblock"
msgstr ""
@@ -23647,6 +24395,9 @@ msgstr ""
msgid "Unreachable"
msgstr ""
+msgid "Unrecognized cluster type"
+msgstr ""
+
msgid "Unresolve"
msgstr ""
@@ -23707,6 +24458,9 @@ msgstr ""
msgid "Unsubscribes from this %{quick_action_target}."
msgstr ""
+msgid "Unsupported todo type passed. Supported todo types are: %{todo_types}"
+msgstr ""
+
msgid "Until"
msgstr ""
@@ -23734,6 +24488,9 @@ msgstr ""
msgid "Update approval rule"
msgstr ""
+msgid "Update approvers"
+msgstr ""
+
msgid "Update failed"
msgstr ""
@@ -23743,6 +24500,9 @@ msgstr ""
msgid "Update it"
msgstr ""
+msgid "Update iteration"
+msgstr ""
+
msgid "Update now"
msgstr ""
@@ -23788,7 +24548,7 @@ msgstr ""
msgid "Updated at"
msgstr ""
-msgid "Updated to"
+msgid "Updated to %{linkStart}chart v%{linkEnd}"
msgstr ""
msgid "Updating"
@@ -23818,6 +24578,9 @@ msgstr ""
msgid "Upload CSV file"
msgstr ""
+msgid "Upload License"
+msgstr ""
+
msgid "Upload New File"
msgstr ""
@@ -23899,16 +24662,19 @@ msgstr ""
msgid "UsageQuota|Pipelines"
msgstr ""
+msgid "UsageQuota|Purchase more storage"
+msgstr ""
+
msgid "UsageQuota|Repositories"
msgstr ""
msgid "UsageQuota|Repository"
msgstr ""
-msgid "UsageQuota|Storage"
+msgid "UsageQuota|Snippets"
msgstr ""
-msgid "UsageQuota|Storage usage:"
+msgid "UsageQuota|Storage"
msgstr ""
msgid "UsageQuota|This namespace has no projects which use shared runners"
@@ -23941,6 +24707,12 @@ msgstr ""
msgid "UsageQuota|Wikis"
msgstr ""
+msgid "UsageQuota|You used: %{usage} %{limit}"
+msgstr ""
+
+msgid "UsageQuota|out of %{formattedLimit} of your namespace storage"
+msgstr ""
+
msgid "Use %{code_start}::%{code_end} to create a %{link_start}scoped label set%{link_end} (eg. %{code_start}priority::1%{code_end})"
msgstr ""
@@ -24007,6 +24779,9 @@ msgstr ""
msgid "User IDs"
msgstr ""
+msgid "User Lists can only be created and modified with %{linkStart}the API%{linkEnd}"
+msgstr ""
+
msgid "User OAuth applications"
msgstr ""
@@ -24031,6 +24806,9 @@ msgstr ""
msgid "User key was successfully removed."
msgstr ""
+msgid "User list %{name} will be removed. Are you sure?"
+msgstr ""
+
msgid "User map"
msgstr ""
@@ -24052,148 +24830,10 @@ msgstr ""
msgid "User was successfully updated."
msgstr ""
-msgid "UserOnboardingTour|%{activeTour}/%{totalTours}"
-msgstr ""
-
-msgid "UserOnboardingTour|%{completed}/%{total} steps completed"
-msgstr ""
-
-msgid "UserOnboardingTour|%{emphasisStart}Well done!%{emphasisEnd}%{lineBreak}%{lineBreak}That's it for our guided tour, congratulations for making it all the way to the end!%{lineBreak}%{lineBreak}We hope this gave you a good overview of GitLab and how it can help you. We'll now show you how to create your own project and invite your colleagues."
-msgstr ""
-
-msgid "UserOnboardingTour|Adding other members to a project is done through Project Settings. Click on %{emphasisStart}Settings%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Alright, that's it for Commits. Let's take a look at the %{emphasisStart}Branches%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Awesome! Now click on %{emphasisStart}Members%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Click on one of the %{emphasisStart}Compare%{emphasisEnd} buttons to compare a branch to master."
-msgstr ""
-
-msgid "UserOnboardingTour|Click on one of the %{emphasisStart}pipeline IDs%{emphasisEnd} to see the details of a pipeline."
-msgstr ""
-
-msgid "UserOnboardingTour|Click to open the latest commit to see its details."
-msgstr ""
-
-msgid "UserOnboardingTour|Close 'Learn GitLab'"
-msgstr ""
-
-msgid "UserOnboardingTour|Commits are shown in chronological order and can be filtered by the commit message or by the branch."
-msgstr ""
-
-msgid "UserOnboardingTour|Create a project"
-msgstr ""
-
-msgid "UserOnboardingTour|Exit 'Learn GitLab'"
-msgstr ""
-
-msgid "UserOnboardingTour|Got it"
-msgstr ""
-
-msgid "UserOnboardingTour|Great job! %{clapHands} We hope the tour was helpful and that you learned how to use GitLab.%{lineBreak}%{lineBreak}We'd love to get your feedback on this tour.%{lineBreak}%{lineBreak}%{emphasisStart}How helpful would you say this guided tour was?%{emphasisEnd}%{lineBreak}%{lineBreak}"
-msgstr ""
-
-msgid "UserOnboardingTour|Guided GitLab Tour"
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can compare the changes of this branch to another one. Changes are divided by files so that it's easier to see what was changed where."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can create a project from scratch, start with a template or import a repository from other platforms. Whatever you choose, we'll guide you through the process.%{lineBreak}%{lineBreak}Fill in your new project information and click on %{emphasisStart}Create Project%{emphasisEnd} to progress to the next step."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can see the breakdown of the pipelines: its stages and jobs in each of the stages and their status.%{lineBreak}%{lineBreak}Our CI/CD pipelines are quite complex, most of our users have fewer and simpler pipelines."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can see the current members of the project (just you at the moment) and invite new members.%{lineBreak}%{lineBreak}You can invite multiple members at once (existing GitLab users or invite by email) and you can also set their roles and permissions.%{lineBreak}%{lineBreak}Add a few members and click on %{emphasisStart}Add to project%{emphasisEnd} to complete this step."
-msgstr ""
-
-msgid "UserOnboardingTour|Here you can see what changes were made with this commit, on what branch and if there's a related merge request. The status of the pipeline will also show up if CI/CD is set up.%{lineBreak}%{lineBreak}You can also comment on the lines of code that were changed and start a discussion with your colleagues!"
-msgstr ""
-
-msgid "UserOnboardingTour|Here's an overview of branches in the %{emphasisStart}%{projectName}%{emphasisEnd} project. They're split into Active and Stale.%{lineBreak}%{lineBreak}From here, you can create a new merge request from a branch, or compare the branch to any other branch in the project. By default, it will compare it to the master branch."
-msgstr ""
-
-msgid "UserOnboardingTour|Invite colleagues"
-msgstr ""
-
-msgid "UserOnboardingTour|Issues are great for communicating and keeping track of progress in GitLab. These are all issues that are open in the %{emphasisStart}%{projectName}%{emphasisEnd}.%{lineBreak}%{lineBreak}You can help us improve GitLab by contributing work to issues that are labeled <span class=\"badge color-label accept-mr-label\">Accepting merge requests</span>.%{lineBreak}%{lineBreak}This list can be filtered by labels, milestones, assignees, authors... We'll show you how it looks when the list is filtered by a label."
-msgstr ""
-
-msgid "UserOnboardingTour|Learn GitLab"
-msgstr ""
-
-msgid "UserOnboardingTour|Let's take a closer look at a merge request. Click on the title of one."
-msgstr ""
-
-msgid "UserOnboardingTour|Let's take a closer look at all the commits. Click on %{emphasisStart}Commits%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|Let's take a closer look at the repository of this project. Click on %{emphasisStart}Repository%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|No thanks"
-msgstr ""
-
-msgid "UserOnboardingTour|Ok, let's go"
-msgstr ""
-
-msgid "UserOnboardingTour|Ok, show me"
-msgstr ""
-
-msgid "UserOnboardingTour|Open one of the issues by clicking on its title."
-msgstr ""
-
-msgid "UserOnboardingTour|Restart this step"
-msgstr ""
-
-msgid "UserOnboardingTour|Skip this step"
-msgstr ""
-
-msgid "UserOnboardingTour|Sweet! Your project was created and is ready to be used.%{lineBreak}%{lineBreak}You can start adding files to the repository or clone it. One last thing we want to show you is how to invite your colleagues to your new project."
-msgstr ""
-
-msgid "UserOnboardingTour|Take a look. Here's a nifty menu for quickly creating issues, merge requests, snippets, projects and groups. Click on it and select \"New project\" from the \"GitLab\" section to get started."
-msgstr ""
-
-msgid "UserOnboardingTour|Thanks for taking the guided tour. Remember, if you want to go through it again, you can start %{emphasisStart}Learn GitLab%{emphasisEnd} in the help menu on the top right."
-msgstr ""
-
-msgid "UserOnboardingTour|Thanks for the feedback! %{thumbsUp}"
-msgstr ""
-
-msgid "UserOnboardingTour|That's it for issues. Let'st take a look at %{emphasisStart}Merge Requests%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|That's it for merge requests. Now for the final part of this guided tour - the %{emphasisStart}CI/CD%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|That's it for the Repository. Let's take a look at the %{emphasisStart}Issues%{emphasisEnd}."
-msgstr ""
-
-msgid "UserOnboardingTour|The structure of this page is very similar to issues. Status, description, discussion and the sidebar are all here.%{lineBreak}%{lineBreak}But take a look below the description and you'll notice that there's more information about the merge request, the CI/CD pipeline and the options for approving it.%{lineBreak}%{lineBreak}Alongside the discussion you can also see more information about commits in this merge request, the status of pipelines and review all changes that were made."
-msgstr ""
-
-msgid "UserOnboardingTour|There's a lot of information here but don't worry, we'll go through it.%{lineBreak}%{lineBreak}On the top you can see the status of the issue and when it was opened and by whom. Directly below it is the issue description and below that are other %{emphasisStart}related issues%{emphasisEnd} and %{emphasisStart}merge requests%{emphasisEnd} (if any). Then below that is the %{emphasisStart}discussion%{emphasisEnd}, that's where most of the communication happens.%{lineBreak}%{lineBreak}On the right, there's a sidebar where you can view/change the %{emphasisStart}assignee, milestone, due date, labels, weight%{emphasisEnd}, etc."
-msgstr ""
-
-msgid "UserOnboardingTour|These are all the CI/CD pipelines we have for our %{emphasisStart}%{projectName}%{emphasisEnd} project.%{lineBreak}%{lineBreak}Here you can see the status of each pipeline, for what commit it's running for, its stages and the status for them."
-msgstr ""
-
-msgid "UserOnboardingTour|These are all the issues that are available for community contributions. Let's take a closer look at one of them."
+msgid "UserList|Delete %{name}?"
msgstr ""
-msgid "UserOnboardingTour|This is an overview of all merge requests in this project. Similarly to the issues overview it can be filtered down by things like labels, milestones, authors, assignees, etc."
-msgstr ""
-
-msgid "UserOnboardingTour|This is the repository for the %{emphasisStart}%{projectName}%{emphasisEnd} project. All our code is stored here. Feel free to explore and take a closer look at folders and files.%{lineBreak}%{lineBreak}Above the file structure you can see the latest commit, who the author is and the status of the CI/CD pipeline.%{lineBreak}%{lineBreak}If you scroll down below the file structure, you'll find the Readme of this project. This is defined in the README.md file at the root of the repository."
-msgstr ""
-
-msgid "UserOnboardingTour|Welcome to the project overview of the %{emphasisStart}%{projectName}%{emphasisEnd} project. This is the project that we use to work on GitLab. At first, a project seems like a simple repository, but at GitLab, a project is so much more.%{lineBreak}%{lineBreak}You can create projects for hosting your codebase, use it as an issue tracker, collaborate on code, and continuously build, test, and deploy your app with built-in GitLab CI/CD."
+msgid "UserList|created %{timeago}"
msgstr ""
msgid "UserProfile|Activity"
@@ -24346,6 +24986,9 @@ msgstr ""
msgid "UsersSelect|Unassigned"
msgstr ""
+msgid "Using %{codeStart}needs%{codeEnd} allows jobs to run before their stage is reached, as soon as their individual dependencies are met, which speeds up your pipelines."
+msgstr ""
+
msgid "Using %{code_start}::%{code_end} denotes a %{link_start}scoped label set%{link_end}"
msgstr ""
@@ -24430,9 +25073,6 @@ msgstr ""
msgid "Versions"
msgstr ""
-msgid "Very helpful"
-msgstr ""
-
msgid "View Documentation"
msgstr ""
@@ -24579,6 +25219,9 @@ msgstr ""
msgid "VisibilityLevel|Unknown"
msgstr ""
+msgid "Visit settings page"
+msgstr ""
+
msgid "VisualReviewApp|%{stepStart}Step 1%{stepEnd}. Copy the following script:"
msgstr ""
@@ -24672,6 +25315,9 @@ msgstr ""
msgid "VulnerabilityManagement|Something went wrong while trying to delete the comment. Please try again later."
msgstr ""
+msgid "VulnerabilityManagement|Something went wrong while trying to refresh the vulnerability. Please try again later."
+msgstr ""
+
msgid "VulnerabilityManagement|Something went wrong while trying to retrieve the vulnerability history. Please try again later."
msgstr ""
@@ -24714,6 +25360,9 @@ msgstr ""
msgid "Vulnerability|Class"
msgstr ""
+msgid "Vulnerability|Crash Address"
+msgstr ""
+
msgid "Vulnerability|Description"
msgstr ""
@@ -24789,9 +25438,6 @@ msgstr ""
msgid "We couldn't reach the Prometheus server. Either the server no longer exists or the configuration details need updating."
msgstr ""
-msgid "We created a short guided tour that will help you learn the basics of GitLab and how it will help you be better at your job. It should only take a couple of minutes. You will be guided by two types of helpers, best recognized by their color."
-msgstr ""
-
msgid "We detected potential spam in the %{humanized_resource_name}. Please solve the reCAPTCHA to proceed."
msgstr ""
@@ -24825,6 +25471,9 @@ msgstr ""
msgid "We've found no vulnerabilities"
msgstr ""
+msgid "Web Application Firewall"
+msgstr ""
+
msgid "Web IDE"
msgstr ""
@@ -24885,9 +25534,6 @@ msgstr ""
msgid "Welcome to GitLab.com<br>@%{name}!"
msgstr ""
-msgid "Welcome to the Guided GitLab Tour"
-msgstr ""
-
msgid "Welcome to the guided GitLab tour"
msgstr ""
@@ -24934,9 +25580,6 @@ msgstr ""
msgid "While it's rare to have no vulnerabilities, it can happen. In any event, we ask that you please double check your settings to make sure you've set up your dashboard correctly."
msgstr ""
-msgid "White helpers give contextual information."
-msgstr ""
-
msgid "Who can be an approver?"
msgstr ""
@@ -24982,12 +25625,18 @@ msgstr ""
msgid "WikiEmptyIssueMessage|Suggest wiki improvement"
msgstr ""
+msgid "WikiEmptyIssueMessage|You must be a group member in order to add wiki pages. If you have suggestions for how to improve the wiki for this group, consider opening an issue in the %{issues_link}."
+msgstr ""
+
msgid "WikiEmptyIssueMessage|You must be a project member in order to add wiki pages. If you have suggestions for how to improve the wiki for this project, consider opening an issue in the %{issues_link}."
msgstr ""
msgid "WikiEmptyIssueMessage|issue tracker"
msgstr ""
+msgid "WikiEmpty|A wiki is where you can store all the details about your group. This can include why you've created it, its principles, how to use it, and so on."
+msgstr ""
+
msgid "WikiEmpty|A wiki is where you can store all the details about your project. This can include why you've created it, its principles, how to use it, and so on."
msgstr ""
@@ -24997,12 +25646,21 @@ msgstr ""
msgid "WikiEmpty|Suggest wiki improvement"
msgstr ""
+msgid "WikiEmpty|The wiki lets you write documentation for your group"
+msgstr ""
+
msgid "WikiEmpty|The wiki lets you write documentation for your project"
msgstr ""
+msgid "WikiEmpty|This group has no wiki pages"
+msgstr ""
+
msgid "WikiEmpty|This project has no wiki pages"
msgstr ""
+msgid "WikiEmpty|You must be a group member in order to add wiki pages."
+msgstr ""
+
msgid "WikiEmpty|You must be a project member in order to add wiki pages."
msgstr ""
@@ -25279,13 +25937,13 @@ msgstr ""
msgid "You can now export your security dashboard to a CSV report."
msgstr ""
-msgid "You can now submit a merge request to get this change into the original branch."
+msgid "You can now manage alert endpoint configuration in the Alerts section on the Operations settings page. Fields on this page have been deprecated."
msgstr ""
-msgid "You can now submit a merge request to get this change into the original project."
+msgid "You can now submit a merge request to get this change into the original branch."
msgstr ""
-msgid "You can only add files when you are on a branch"
+msgid "You can now submit a merge request to get this change into the original project."
msgstr ""
msgid "You can only edit files when you are on a branch"
@@ -25507,7 +26165,7 @@ msgstr ""
msgid "You need to upload a Google Takeout archive."
msgstr ""
-msgid "You reached %{usage_in_percent} of %{namespace_name}'s capacity (%{used_storage} of %{storage_limit})"
+msgid "You reached %{usage_in_percent} of %{namespace_name}'s storage capacity (%{used_storage} of %{storage_limit})"
msgstr ""
msgid "You tried to fork %{link_to_the_project} but it failed for the following reason:"
@@ -25810,13 +26468,13 @@ msgstr ""
msgid "Your subscription expired!"
msgstr ""
-msgid "Your subscription has been downgraded"
+msgid "Your subscription has been downgraded."
msgstr ""
-msgid "Your subscription will automatically renew in %{remaining_days}"
+msgid "Your subscription will automatically renew in %{remaining_days}."
msgstr ""
-msgid "Your subscription will expire in %{remaining_days}"
+msgid "Your subscription will expire in %{remaining_days}."
msgstr ""
msgid "Zoom meeting added"
@@ -25895,9 +26553,6 @@ msgstr ""
msgid "branch name"
msgstr ""
-msgid "build pipeline reference mismatch"
-msgstr ""
-
msgid "by"
msgstr "來自"
@@ -25916,6 +26571,9 @@ msgstr ""
msgid "cannot block others"
msgstr ""
+msgid "cannot contain HTML/XML tags, including any word between angle brackets (<,>)."
+msgstr ""
+
msgid "cannot include leading slash or directory traversal."
msgstr ""
@@ -25946,50 +26604,6 @@ msgstr ""
msgid "ciReport|%{remainingPackagesCount} more"
msgstr ""
-msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability"
-msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities"
-msgstr[0] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability for the source branch only"
-msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities for the source branch only"
-msgstr[0] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerability"
-msgid_plural "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerabilities"
-msgstr[0] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new vulnerability"
-msgid_plural "ciReport|%{reportType} %{status} detected %{newCount} new vulnerabilities"
-msgstr[0] ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities for the source branch only"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{fixedCount} fixed vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected %{newCount} vulnerability for the source branch only"
-msgid_plural "ciReport|%{reportType} %{status} detected %{newCount} vulnerabilities for the source branch only"
-msgstr[0] ""
-
-msgid "ciReport|%{reportType} %{status} detected no new vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected no vulnerabilities"
-msgstr ""
-
-msgid "ciReport|%{reportType} %{status} detected no vulnerabilities for the source branch only"
-msgstr ""
-
msgid "ciReport|%{reportType} is loading"
msgstr ""
@@ -26008,7 +26622,7 @@ msgstr ""
msgid "ciReport|All projects"
msgstr ""
-msgid "ciReport|All report types"
+msgid "ciReport|All scanner types"
msgstr ""
msgid "ciReport|All severities"
@@ -26032,6 +26646,9 @@ msgstr ""
msgid "ciReport|Container scanning detects known vulnerabilities in your docker images."
msgstr ""
+msgid "ciReport|Coverage Fuzzing"
+msgstr ""
+
msgid "ciReport|Create a merge request to implement this solution, or download and apply the patch manually."
msgstr ""
@@ -26189,6 +26806,9 @@ msgstr ""
msgid "customize"
msgstr ""
+msgid "data"
+msgstr ""
+
msgid "date must not be after 9999-12-31"
msgstr ""
@@ -26257,6 +26877,9 @@ msgstr ""
msgid "entries cannot contain HTML tags"
msgstr ""
+msgid "epic"
+msgstr ""
+
msgid "error"
msgstr ""
@@ -26278,9 +26901,6 @@ msgstr ""
msgid "expires on %{timebox_due_date}"
msgstr ""
-msgid "external_url"
-msgstr ""
-
msgid "failed"
msgstr ""
@@ -26315,9 +26935,6 @@ msgstr ""
msgid "from"
msgstr ""
-msgid "geo_node_name"
-msgstr ""
-
msgid "group"
msgstr ""
@@ -26339,9 +26956,6 @@ msgstr ""
msgid "https://your-bitbucket-server"
msgstr ""
-msgid "image"
-msgstr ""
-
msgid "image diff"
msgstr ""
@@ -26484,6 +27098,9 @@ msgstr ""
msgid "load it anyway"
msgstr ""
+msgid "loading"
+msgstr ""
+
msgid "locked by %{path_lock_user_name} %{created_at}"
msgstr ""
@@ -26611,9 +27228,6 @@ msgstr ""
msgid "mrWidget|Deployment statistics are not available currently"
msgstr ""
-msgid "mrWidget|Detect issues before deployment with a CI pipeline that continuously tests your code. We created a quick guide that will show you how to create one. Make your code more secure and more robust in just a minute."
-msgstr ""
-
msgid "mrWidget|Did not close"
msgstr ""
@@ -26776,6 +27390,9 @@ msgstr ""
msgid "mrWidget|To approve this merge request, please enter your password. This project requires all approvals to be authenticated."
msgstr ""
+msgid "mrWidget|Use %{linkStart}CI pipelines to test your code%{linkEnd}, simply add a GitLab CI configuration file to your project. It only takes a minute to make your code more secure and robust."
+msgstr ""
+
msgid "mrWidget|When this merge request is ready, remove the WIP: prefix from the title to allow it to be merged"
msgstr ""
@@ -26812,6 +27429,9 @@ msgstr ""
msgid "must be greater than start date"
msgstr ""
+msgid "must contain only valid frameworks"
+msgstr ""
+
msgid "my-awesome-group"
msgstr ""
@@ -26824,6 +27444,9 @@ msgstr ""
msgid "needs to be between 10 minutes and 1 month"
msgstr ""
+msgid "never"
+msgstr ""
+
msgid "never expires"
msgstr ""
@@ -26851,6 +27474,9 @@ msgstr ""
msgid "nounSeries|%{firstItem} and %{lastItem}"
msgstr ""
+msgid "nounSeries|%{item}"
+msgstr ""
+
msgid "nounSeries|%{item}, %{nextItem}"
msgstr ""
@@ -26982,6 +27608,9 @@ msgstr ""
msgid "revised"
msgstr ""
+msgid "satisfied"
+msgstr ""
+
msgid "score"
msgstr ""
@@ -27012,6 +27641,9 @@ msgstr ""
msgid "severity|Unknown"
msgstr ""
+msgid "should be an array of %{object_name} objects"
+msgstr ""
+
msgid "should be greater than or equal to %{access} inherited membership from group %{group_name}"
msgstr ""
@@ -27066,10 +27698,13 @@ msgstr ""
msgid "suggestPipeline|2/2: Commit your changes"
msgstr ""
-msgid "suggestPipeline|Commit the changes and your pipeline will automatically run for the first time."
+msgid "suggestPipeline|Choose %{boldStart}Code Quality%{boldEnd} to add a pipeline that tests the quality of your code."
msgstr ""
-msgid "suggestPipeline|We recommend the %{boldStart}Code Quality%{boldEnd} template, which will add a report widget to your Merge Requests. This way you’ll learn about code quality degradations much sooner. %{footerStart} Goodbye technical debt! %{footerEnd}"
+msgid "suggestPipeline|The template is ready! You can now commit it to create your first pipeline."
+msgstr ""
+
+msgid "suggestPipeline|We’re adding a GitLab CI configuration file to add a pipeline to the project. You could create it manually, but we recommend that you start with a GitLab template that works out of the box."
msgstr ""
msgid "syntax is correct"
@@ -27129,6 +27764,9 @@ msgstr ""
msgid "uses Kubernetes clusters to deploy your code!"
msgstr ""
+msgid "v%{version} published %{timeAgo}"
+msgstr ""
+
msgid "verify ownership"
msgstr ""
diff --git a/package.json b/package.json
index de9d845cc9f..a8dfc5d49a8 100644
--- a/package.json
+++ b/package.json
@@ -7,6 +7,8 @@
"dev-server": "NODE_OPTIONS=\"--max-old-space-size=3584\" node scripts/frontend/webpack_dev_server.js",
"eslint": "eslint --cache --max-warnings 0 --report-unused-disable-directives --ext .js,.vue .",
"eslint-fix": "eslint --cache --max-warnings 0 --report-unused-disable-directives --ext .js,.vue --fix .",
+ "eslint-staged": "git diff --cached --name-only | grep -E \"(.*)\\.(js|vue)$\" | xargs eslint --cache --max-warnings 0 --report-unused-disable-directives",
+ "eslint-staged-fix": "git diff --cached --name-only | grep -E \"(.*)\\.(js|vue)$\" | xargs eslint --cache --max-warnings 0 --report-unused-disable-directives --fix",
"eslint-report": "eslint --max-warnings 0 --ext .js,.vue --format html --output-file ./eslint-report.html --no-inline-config .",
"file-coverage": "scripts/frontend/file_test_coverage.js",
"prejest": "yarn check-dependencies",
@@ -24,7 +26,7 @@
"prettier-staged-save": "node ./scripts/frontend/prettier.js save",
"prettier-all": "node ./scripts/frontend/prettier.js check-all",
"prettier-all-save": "node ./scripts/frontend/prettier.js save-all",
- "stylelint": "yarn stylelint-file app/assets/stylesheets/**/*.* ee/app/assets/stylesheets/**/*.* !**/vendors/**",
+ "stylelint": "yarn stylelint-file 'app/assets/stylesheets/**/*.*' 'ee/app/assets/stylesheets/**/*.*' '!**/vendors/**'",
"stylelint-file": "BROWSERSLIST_IGNORE_OLD_DATA=true node node_modules/stylelint/bin/stylelint.js",
"stylelint-create-utility-map": "node scripts/frontend/stylelint/stylelint-utility-map.js",
"test": "node scripts/frontend/test",
@@ -40,14 +42,14 @@
"@babel/plugin-syntax-import-meta": "^7.10.1",
"@babel/preset-env": "^7.10.1",
"@gitlab/at.js": "1.5.5",
- "@gitlab/svgs": "1.140.0",
- "@gitlab/ui": "17.0.1",
+ "@gitlab/svgs": "1.152.0",
+ "@gitlab/ui": "17.33.0",
"@gitlab/visual-review-tools": "1.6.1",
"@rails/actioncable": "^6.0.3-1",
"@sentry/browser": "^5.10.2",
"@sourcegraph/code-host-integration": "0.0.48",
- "@toast-ui/editor": "^2.0.1",
- "@toast-ui/vue-editor": "^2.0.1",
+ "@toast-ui/editor": "^2.2.0",
+ "@toast-ui/vue-editor": "^2.2.0",
"apollo-cache-inmemory": "^1.6.3",
"apollo-client": "^2.6.4",
"apollo-link": "^1.2.11",
@@ -61,7 +63,6 @@
"bootstrap": "4.4.1",
"brace-expansion": "^1.1.8",
"cache-loader": "^4.1.0",
- "classlist-polyfill": "^1.2.0",
"clipboard": "^1.7.1",
"codemirror": "^5.48.4",
"codesandbox-api": "0.0.23",
@@ -84,12 +85,13 @@
"emoji-unicode-version": "^0.2.1",
"exports-loader": "^0.7.0",
"file-loader": "^5.1.0",
- "formdata-polyfill": "^3.0.19",
"fuzzaldrin-plus": "^0.6.0",
"glob": "^7.1.6",
"graphql": "^14.0.2",
+ "graphql-tag": "^2.10.1",
"immer": "^5.2.1",
"imports-loader": "^0.8.0",
+ "ipaddr.js": "^1.9.1",
"jed": "^1.1.1",
"jest-transform-graphql": "^2.1.0",
"jquery": "^3.4.1",
@@ -97,6 +99,7 @@
"jquery.caret": "^0.3.1",
"jquery.waitforimages": "^2.2.0",
"js-cookie": "^2.2.1",
+ "js-yaml": "^3.13.1",
"jszip": "^3.1.3",
"jszip-utils": "^0.0.2",
"katex": "^0.10.0",
@@ -105,9 +108,9 @@
"mermaid": "^8.5.2",
"mersenne-twister": "1.1.0",
"minimatch": "^3.0.4",
- "mitt": "^1.2.0",
- "monaco-editor": "^0.18.1",
- "monaco-editor-webpack-plugin": "^1.7.0",
+ "monaco-editor": "^0.20.0",
+ "monaco-editor-webpack-plugin": "^1.9.0",
+ "monaco-yaml": "^2.4.0",
"mousetrap": "^1.4.6",
"pdfjs-dist": "^2.0.943",
"pikaday": "^1.8.0",
@@ -125,7 +128,6 @@
"stickyfilljs": "^2.1.0",
"string-hash": "1.1.3",
"style-loader": "^1.1.3",
- "svg4everybody": "2.1.9",
"swagger-ui-dist": "^3.26.2",
"three": "^0.84.0",
"three-orbit-controls": "^82.1.0",
@@ -134,8 +136,7 @@
"tiptap": "^1.8.0",
"tiptap-commands": "^1.4.0",
"tiptap-extensions": "^1.8.0",
- "tributejs": "4.1.3",
- "unfetch": "^4.1.0",
+ "tributejs": "5.1.3",
"url-loader": "^3.0.0",
"uuid": "8.1.0",
"visibilityjs": "^1.2.4",
@@ -155,10 +156,11 @@
"xterm": "3.14.5"
},
"devDependencies": {
- "acorn": "^6.3.0",
"@babel/plugin-transform-modules-commonjs": "^7.10.1",
"@gitlab/eslint-plugin": "3.1.0",
+ "@testing-library/dom": "^7.16.2",
"@vue/test-utils": "^1.0.0-beta.30",
+ "acorn": "^6.3.0",
"axios-mock-adapter": "^1.15.0",
"babel-jest": "^24.1.0",
"babel-plugin-dynamic-import-node": "^2.2.0",
@@ -175,7 +177,6 @@
"eslint-plugin-no-jquery": "^2.3.0",
"gettext-extractor": "^3.4.3",
"gettext-extractor-vue": "^4.0.2",
- "graphql-tag": "^2.10.1",
"istanbul-lib-coverage": "^3.0.0",
"istanbul-lib-report": "^3.0.0",
"istanbul-reports": "^3.0.0",
@@ -216,11 +217,11 @@
"yarn-deduplicate": "^1.1.1"
},
"blockedDependencies": {
- "bootstrap-vue": "https://docs.gitlab.com/ee/development/fe_guide/dependencies.md#bootstrapvue"
+ "bootstrap-vue": "https://docs.gitlab.com/ee/development/fe_guide/dependencies.html#bootstrapvue"
},
"resolutions": {
"chokidar": "^3.4.0",
- "monaco-editor": "0.18.1",
+ "monaco-editor": "0.20.0",
"vue-jest/ts-jest": "24.0.0"
},
"engines": {
diff --git a/public/robots.txt b/public/robots.txt
index 20cd97a596f..12ceba88395 100644
--- a/public/robots.txt
+++ b/public/robots.txt
@@ -8,7 +8,10 @@
# Only some crawlers respect this setting, e.g. Googlebot does not
# Crawl-delay: 1
-# Based on details in https://gitlab.com/gitlab-org/gitlab/blob/master/config/routes.rb, https://gitlab.com/gitlab-org/gitlab/blob/master/spec/routing, and using application
+# Based on details in https://gitlab.com/gitlab-org/gitlab/blob/master/config/routes.rb,
+# https://gitlab.com/gitlab-org/gitlab/blob/master/spec/routing, and using application
+
+# Global routes
User-Agent: *
Disallow: /autocomplete/users
Disallow: /search
@@ -16,62 +19,48 @@ Disallow: /api
Disallow: /admin
Disallow: /profile
Disallow: /dashboard
-Disallow: /projects/new
-Disallow: /groups/new
-Disallow: /groups/*/edit
Disallow: /users
Disallow: /help
+Disallow: /s/
# Only specifically allow the Sign In page to avoid very ugly search results
Allow: /users/sign_in
-# Global snippets
+# Generic resource routes like new, edit, raw
+# This will block routes like:
+# - /projects/new
+# - /gitlab-org/gitlab-foss/issues/123/-/edit
User-Agent: *
-Disallow: /s/
-Disallow: /snippets/new
-Disallow: /snippets/*/edit
-Disallow: /snippets/*/raw
+Disallow: /*/new
+Disallow: /*/edit
+Disallow: /*/raw
+
+# Group details
+User-Agent: *
+Disallow: /groups/*/analytics
+Disallow: /groups/*/contribution_analytics
+Disallow: /groups/*/group_members
# Project details
User-Agent: *
Disallow: /*/*.git
-Disallow: /*/*/fork/new
-Disallow: /*/-/archive/
-Disallow: /*/*/repository/archive*
-Disallow: /*/*/activity
-Disallow: /*/*/new
-Disallow: /*/*/edit
-Disallow: /*/*/raw
-Disallow: /*/*/blame
-Disallow: /*/*/commits/*/*
-Disallow: /*/*/commit/*.patch
-Disallow: /*/*/commit/*.diff
-Disallow: /*/*/compare
-Disallow: /*/*/branches/new
-Disallow: /*/*/tags/new
-Disallow: /*/*/network
-Disallow: /*/*/graphs
-Disallow: /*/*/milestones/new
-Disallow: /*/*/milestones/*/edit
-Disallow: /*/*/issues/new
-Disallow: /*/*/issues/*/edit
-Disallow: /*/*/-/merge_requests/new
-Disallow: /*/*/-/merge_requests/*.patch
-Disallow: /*/*/-/merge_requests/*.diff
-Disallow: /*/*/-/merge_requests/*/edit
-Disallow: /*/*/-/merge_requests/*/diffs
-Disallow: /*/*/project_members/import
-Disallow: /*/*/labels/new
-Disallow: /*/*/labels/*/edit
-Disallow: /*/*/wikis/*/edit
-Disallow: /*/*/snippets/new
-Disallow: /*/*/snippets/*/edit
-Disallow: /*/*/snippets/*/raw
-Disallow: /*/*/deploy_keys
-Disallow: /*/*/hooks
-Disallow: /*/*/services
-Disallow: /*/*/protected_branches
-Disallow: /*/*/uploads/
-Disallow: /*/-/group_members
+Disallow: /*/archive/
+Disallow: /*/repository/archive*
+Disallow: /*/activity
+Disallow: /*/blame
+Disallow: /*/commits
+Disallow: /*/commit
+Disallow: /*/commit/*.patch
+Disallow: /*/commit/*.diff
+Disallow: /*/compare
+Disallow: /*/network
+Disallow: /*/graphs
+Disallow: /*/merge_requests/*.patch
+Disallow: /*/merge_requests/*.diff
+Disallow: /*/merge_requests/*/diffs
+Disallow: /*/deploy_keys
+Disallow: /*/hooks
+Disallow: /*/services
+Disallow: /*/protected_branches
+Disallow: /*/uploads/
Disallow: /*/project_members
-Disallow: /groups/*/-/contribution_analytics
-Disallow: /groups/*/-/analytics
+Disallow: /*/settings
diff --git a/qa/Dockerfile b/qa/Dockerfile
index 7f90e4bf5bf..6310e4b290d 100644
--- a/qa/Dockerfile
+++ b/qa/Dockerfile
@@ -3,11 +3,10 @@ LABEL maintainer="GitLab Quality Department <quality@gitlab.com>"
ENV DEBIAN_FRONTEND="noninteractive"
ENV DOCKER_VERSION="17.09.0-ce"
-ENV CHROME_VERSION="83.0.4103.61-1"
-ENV CHROME_DRIVER_VERSION="83.0.4103.39"
+ENV CHROME_VERSION="84.0.4147.89-1"
+ENV CHROME_DRIVER_VERSION="84.0.4147.30"
ENV CHROME_DEB="google-chrome-stable_${CHROME_VERSION}_amd64.deb"
ENV CHROME_URL="https://s3.amazonaws.com/gitlab-google-chrome-stable/${CHROME_DEB}"
-ENV K3D_VERSION="1.3.4"
##
# Add support for stretch-backports
@@ -49,12 +48,6 @@ RUN unzip chromedriver_linux64.zip -d /usr/local/bin
RUN rm -f chromedriver_linux64.zip
##
-# Install K3d local cluster support
-# https://github.com/rancher/k3d
-#
-RUN curl -s https://raw.githubusercontent.com/rancher/k3d/master/install.sh | TAG="v${K3D_VERSION}" bash
-
-##
# Install gcloud and kubectl CLI used in Auto DevOps test to create K8s
# clusters
#
diff --git a/qa/Gemfile b/qa/Gemfile
index d5c682ef76f..e2951db534a 100644
--- a/qa/Gemfile
+++ b/qa/Gemfile
@@ -15,6 +15,7 @@ gem 'rspec_junit_formatter', '~> 0.4.1'
gem 'faker', '~> 1.6', '>= 1.6.6'
gem 'knapsack', '~> 1.17'
gem 'parallel_tests', '~> 2.29'
+gem 'rotp', '~> 3.1.0'
group :test do
gem 'pry-byebug', '~> 3.5.1', platform: :mri
diff --git a/qa/Gemfile.lock b/qa/Gemfile.lock
index 23324fccdec..c2b876e3b04 100644
--- a/qa/Gemfile.lock
+++ b/qa/Gemfile.lock
@@ -78,6 +78,7 @@ GEM
http-cookie (>= 1.0.2, < 2.0)
mime-types (>= 1.16, < 4.0)
netrc (~> 0.8)
+ rotp (3.1.0)
rspec (3.9.0)
rspec-core (~> 3.9.0)
rspec-expectations (~> 3.9.0)
@@ -129,6 +130,7 @@ DEPENDENCIES
pry-byebug (~> 3.5.1)
rake (~> 12.3.0)
rest-client (~> 2.1.0)
+ rotp (~> 3.1.0)
rspec (~> 3.7)
rspec-retry (~> 0.6.1)
rspec_junit_formatter (~> 0.4.1)
diff --git a/qa/bin/rubymine b/qa/bin/rubymine
new file mode 100755
index 00000000000..0be0cf0ec33
--- /dev/null
+++ b/qa/bin/rubymine
@@ -0,0 +1,9 @@
+#!/usr/bin/env ruby
+
+require_relative '../qa'
+
+ARGV.unshift('Test::Instance::All', ENV['GITLAB_URL'], '--')
+
+QA::Scenario
+ .const_get(ARGV.shift)
+ .launch!(ARGV)
diff --git a/qa/qa.rb b/qa/qa.rb
index 4649f452c6f..823adade6f3 100644
--- a/qa/qa.rb
+++ b/qa/qa.rb
@@ -79,6 +79,7 @@ module QA
autoload :PersonalAccessToken, 'qa/resource/personal_access_token'
autoload :User, 'qa/resource/user'
autoload :ProjectMilestone, 'qa/resource/project_milestone'
+ autoload :GroupMilestone, 'qa/resource/group_milestone'
autoload :Members, 'qa/resource/members'
autoload :File, 'qa/resource/file'
autoload :Fork, 'qa/resource/fork'
@@ -181,6 +182,7 @@ module QA
autoload :Login, 'qa/page/main/login'
autoload :Menu, 'qa/page/main/menu'
autoload :OAuth, 'qa/page/main/oauth'
+ autoload :TwoFactorAuth, 'qa/page/main/two_factor_auth'
autoload :SignUp, 'qa/page/main/sign_up'
autoload :Terms, 'qa/page/main/terms'
end
@@ -207,6 +209,11 @@ module QA
autoload :Show, 'qa/page/group/show'
autoload :Menu, 'qa/page/group/menu'
+ module Milestone
+ autoload :Index, 'qa/page/group/milestone/index'
+ autoload :New, 'qa/page/group/milestone/new'
+ end
+
module SubMenus
autoload :Common, 'qa/page/group/sub_menus/common'
autoload :Members, 'qa/page/group/sub_menus/members'
@@ -217,6 +224,12 @@ module QA
end
end
+ module Milestone
+ autoload :Index, 'qa/page/milestone/index'
+ autoload :New, 'qa/page/milestone/new'
+ autoload :Show, 'qa/page/milestone/show'
+ end
+
module File
autoload :Form, 'qa/page/file/form'
autoload :Show, 'qa/page/file/show'
@@ -254,6 +267,12 @@ module QA
autoload :Show, 'qa/page/project/pipeline/show'
end
+ module Tag
+ autoload :Index, 'qa/page/project/tag/index'
+ autoload :New, 'qa/page/project/tag/new'
+ autoload :Show, 'qa/page/project/tag/show'
+ end
+
module Job
autoload :Show, 'qa/page/project/job/show'
end
@@ -273,6 +292,7 @@ module QA
autoload :Runners, 'qa/page/project/settings/runners'
autoload :MergeRequest, 'qa/page/project/settings/merge_request'
autoload :MirroringRepositories, 'qa/page/project/settings/mirroring_repositories'
+ autoload :ProtectedTags, 'qa/page/project/settings/protected_tags'
autoload :VisibilityFeaturesPermissions, 'qa/page/project/settings/visibility_features_permissions'
module Services
@@ -301,6 +321,7 @@ module QA
autoload :New, 'qa/page/project/issue/new'
autoload :Show, 'qa/page/project/issue/show'
autoload :Index, 'qa/page/project/issue/index'
+ autoload :JiraImport, 'qa/page/project/issue/jira_import'
end
module Fork
@@ -334,6 +355,8 @@ module QA
autoload :Edit, 'qa/page/project/wiki/edit'
autoload :Show, 'qa/page/project/wiki/show'
autoload :GitAccess, 'qa/page/project/wiki/git_access'
+ autoload :Sidebar, 'qa/page/project/wiki/sidebar'
+ autoload :List, 'qa/page/project/wiki/list'
end
module WebIDE
@@ -342,6 +365,7 @@ module QA
module Snippet
autoload :New, 'qa/page/project/snippet/new'
+ autoload :Show, 'qa/page/project/snippet/show'
end
end
@@ -356,7 +380,6 @@ module QA
module Issuable
autoload :New, 'qa/page/issuable/new'
- autoload :Sidebar, 'qa/page/issuable/sidebar'
end
module Alert
@@ -439,9 +462,13 @@ module QA
autoload :ConfirmModal, 'qa/page/component/confirm_modal'
autoload :CustomMetric, 'qa/page/component/custom_metric'
autoload :DesignManagement, 'qa/page/component/design_management'
+ autoload :ProjectSelector, 'qa/page/component/project_selector'
+ autoload :Snippet, 'qa/page/component/snippet'
+ autoload :NewSnippet, 'qa/page/component/new_snippet'
module Issuable
autoload :Common, 'qa/page/component/issuable/common'
+ autoload :Sidebar, 'qa/page/component/issuable/sidebar'
end
module IssueBoard
@@ -460,6 +487,10 @@ module QA
autoload :Templates, 'qa/page/component/project/templates'
end
end
+
+ module Modal
+ autoload :DeleteWiki, 'qa/page/modal/delete_wiki'
+ end
end
##
@@ -555,6 +586,7 @@ module QA
autoload :Retrier, 'qa/support/retrier'
autoload :Waiter, 'qa/support/waiter'
autoload :WaitForRequests, 'qa/support/wait_for_requests'
+ autoload :OTP, 'qa/support/otp'
end
end
diff --git a/qa/qa/fixtures/monitored_auto_devops/.gitlab-ci.yml b/qa/qa/fixtures/monitored_auto_devops/.gitlab-ci.yml
deleted file mode 100644
index 052ba1c14fb..00000000000
--- a/qa/qa/fixtures/monitored_auto_devops/.gitlab-ci.yml
+++ /dev/null
@@ -1,338 +0,0 @@
-# This is stripped down version of the .gitlab-ci.yml found
-# here: https://gitlab.com/joshlambert/autodevops-deploy.
-#
-# It performs only the deploy stage.
-
-image: alpine:latest
-
-variables:
- # AUTO_DEVOPS_DOMAIN is the application deployment domain and should be set as a variable at the group or project level.
- AUTO_DEVOPS_DOMAIN: $AUTO_DEVOPS_DOMAIN
-
- POSTGRES_USER: user
- POSTGRES_PASSWORD: testing-password
- POSTGRES_ENABLED: 'false'
- POSTGRES_DB: $CI_ENVIRONMENT_SLUG
-
- KUBERNETES_VERSION: 1.11.6
- HELM_VERSION: 2.12.2
-
- DOCKER_DRIVER: overlay2
-
-stages:
- - production
-
-# This job continuously deploys to production on every push to `master`.
-
-production:
- stage: production
- tags:
- - qa
- script:
- - check_kube_domain
- - install_dependencies
- - download_chart
- - ensure_namespace
- - initialize_tiller
- - create_secret
- - deploy
- environment:
- name: production
- url: http://$CI_PROJECT_PATH_SLUG.$AUTO_DEVOPS_DOMAIN
- artifacts:
- paths: [environment_url.txt]
- only:
- refs:
- - master
- kubernetes: active
-
-# ---------------------------------------------------------------------------
-
-.auto_devops: &auto_devops |
- # Auto DevOps variables and functions
- [[ "$TRACE" ]] && set -x
- auto_database_url=postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${CI_ENVIRONMENT_SLUG}-postgres:5432/${POSTGRES_DB}
- export DATABASE_URL=${DATABASE_URL-$auto_database_url}
- export CI_APPLICATION_REPOSITORY=$CI_REGISTRY_IMAGE/$CI_COMMIT_REF_SLUG
- export CI_APPLICATION_TAG=$CI_COMMIT_SHA
- export CI_CONTAINER_NAME=ci_job_build_${CI_JOB_ID}
- export TILLER_NAMESPACE=$KUBE_NAMESPACE
- # Extract "MAJOR.MINOR" from CI_SERVER_VERSION and generate "MAJOR-MINOR-stable" for Security Products
- export SP_VERSION=$(echo "$CI_SERVER_VERSION" | sed 's/^\([0-9]*\)\.\([0-9]*\).*/\1-\2-stable/')
-
-
- function get_replicas() {
- track="${1:-stable}"
- percentage="${2:-100}"
-
- env_track=$( echo $track | tr -s '[:lower:]' '[:upper:]' )
- env_slug=$( echo ${CI_ENVIRONMENT_SLUG//-/_} | tr -s '[:lower:]' '[:upper:]' )
-
- if [[ "$track" == "stable" ]] || [[ "$track" == "rollout" ]]; then
- # for stable track get number of replicas from `PRODUCTION_REPLICAS`
- eval new_replicas=\$${env_slug}_REPLICAS
- if [[ -z "$new_replicas" ]]; then
- new_replicas=$REPLICAS
- fi
- else
- # for all tracks get number of replicas from `CANARY_PRODUCTION_REPLICAS`
- eval new_replicas=\$${env_track}_${env_slug}_REPLICAS
- if [[ -z "$new_replicas" ]]; then
- eval new_replicas=\${env_track}_REPLICAS
- fi
- fi
-
- replicas="${new_replicas:-1}"
- replicas="$(($replicas * $percentage / 100))"
-
- # always return at least one replicas
- if [[ $replicas -gt 0 ]]; then
- echo "$replicas"
- else
- echo 1
- fi
- }
-
-
- # Extracts variables prefixed with K8S_SECRET_
- # and creates a Kubernetes secret.
- #
- # e.g. If we have the following environment variables:
- # K8S_SECRET_A=value1
- # K8S_SECRET_B=multi\ word\ value
- #
- # Then we will create a secret with the following key-value pairs:
- # data:
- # A: dmFsdWUxCg==
- # B: bXVsdGkgd29yZCB2YWx1ZQo=
- function create_application_secret() {
- track="${1-stable}"
- export APPLICATION_SECRET_NAME=$(application_secret_name "$track")
-
- env | sed -n "s/^K8S_SECRET_\(.*\)$/\1/p" > k8s_prefixed_variables
-
- kubectl create secret \
- -n "$KUBE_NAMESPACE" generic "$APPLICATION_SECRET_NAME" \
- --from-env-file k8s_prefixed_variables -o yaml --dry-run |
- kubectl replace -n "$KUBE_NAMESPACE" --force -f -
-
- export APPLICATION_SECRET_CHECKSUM=$(cat k8s_prefixed_variables | sha256sum | cut -d ' ' -f 1)
-
- rm k8s_prefixed_variables
- }
-
- function deploy_name() {
- name="$CI_ENVIRONMENT_SLUG"
- track="${1-stable}"
-
- if [[ "$track" != "stable" ]]; then
- name="$name-$track"
- fi
-
- echo $name
- }
-
- function application_secret_name() {
- track="${1-stable}"
- name=$(deploy_name "$track")
-
- echo "${name}-secret"
- }
-
-
- function deploy() {
- track="${1-stable}"
- percentage="${2:-100}"
- name=$(deploy_name "$track")
-
- replicas="1"
- service_enabled="true"
- postgres_enabled="$POSTGRES_ENABLED"
-
- # if track is different than stable,
- # re-use all attached resources
- if [[ "$track" != "stable" ]]; then
- service_enabled="false"
- postgres_enabled="false"
- fi
-
- replicas=$(get_replicas "$track" "$percentage")
-
- if [[ "$CI_PROJECT_VISIBILITY" != "public" ]]; then
- secret_name='gitlab-registry'
- else
- secret_name=''
- fi
-
- create_application_secret "$track"
-
- env_slug=$(echo ${CI_ENVIRONMENT_SLUG//-/_} | tr -s '[:lower:]' '[:upper:]')
- eval env_ADDITIONAL_HOSTS=\$${env_slug}_ADDITIONAL_HOSTS
- if [ -n "$env_ADDITIONAL_HOSTS" ]; then
- additional_hosts="{$env_ADDITIONAL_HOSTS}"
- elif [ -n "$ADDITIONAL_HOSTS" ]; then
- additional_hosts="{$ADDITIONAL_HOSTS}"
- fi
-
- if [[ -n "$DB_INITIALIZE" && -z "$(helm ls -q "^$name$")" ]]; then
- echo "Deploying first release with database initialization..."
- helm upgrade --install \
- --wait \
- --set service.enabled="$service_enabled" \
- --set releaseOverride="$CI_ENVIRONMENT_SLUG" \
- --set image.repository="registry.gitlab.com/joshlambert/ruby-gke/master" \
- --set image.tag="63492726c2264a0277141d6a6573c3d22ecd7de3" \
- --set image.pullPolicy=IfNotPresent \
- --set image.secrets[0].name="$secret_name" \
- --set application.track="$track" \
- --set application.database_url="$DATABASE_URL" \
- --set application.secretName="$APPLICATION_SECRET_NAME" \
- --set application.secretChecksum="$APPLICATION_SECRET_CHECKSUM" \
- --set service.url="$CI_ENVIRONMENT_URL" \
- --set service.additionalHosts="$additional_hosts" \
- --set replicaCount="$replicas" \
- --set postgresql.enabled="$postgres_enabled" \
- --set postgresql.nameOverride="postgres" \
- --set postgresql.postgresUser="$POSTGRES_USER" \
- --set postgresql.postgresPassword="$POSTGRES_PASSWORD" \
- --set postgresql.postgresDatabase="$POSTGRES_DB" \
- --set application.initializeCommand="$DB_INITIALIZE" \
- --set gitlab.app="$CI_PROJECT_PATH_SLUG" \
- --set gitlab.env="$CI_ENVIRONMENT_SLUG" \
- --namespace="$KUBE_NAMESPACE" \
- "$name" \
- chart/
-
- echo "Deploying second release..."
- helm upgrade --reuse-values \
- --wait \
- --set application.initializeCommand="" \
- --set application.migrateCommand="$DB_MIGRATE" \
- --namespace="$KUBE_NAMESPACE" \
- "$name" \
- chart/
- else
- echo "Deploying new release..."
- helm upgrade --install \
- --wait \
- --set service.enabled="$service_enabled" \
- --set releaseOverride="$CI_ENVIRONMENT_SLUG" \
- --set image.repository="registry.gitlab.com/joshlambert/ruby-gke/master" \
- --set image.tag="63492726c2264a0277141d6a6573c3d22ecd7de3" \
- --set image.pullPolicy=IfNotPresent \
- --set image.secrets[0].name="$secret_name" \
- --set application.track="$track" \
- --set application.database_url="$DATABASE_URL" \
- --set application.secretName="$APPLICATION_SECRET_NAME" \
- --set application.secretChecksum="$APPLICATION_SECRET_CHECKSUM" \
- --set service.url="$CI_ENVIRONMENT_URL" \
- --set service.additionalHosts="$additional_hosts" \
- --set replicaCount="$replicas" \
- --set postgresql.enabled="$postgres_enabled" \
- --set postgresql.nameOverride="postgres" \
- --set postgresql.postgresUser="$POSTGRES_USER" \
- --set postgresql.postgresPassword="$POSTGRES_PASSWORD" \
- --set postgresql.postgresDatabase="$POSTGRES_DB" \
- --set application.migrateCommand="$DB_MIGRATE" \
- --set gitlab.app="$CI_PROJECT_PATH_SLUG" \
- --set gitlab.env="$CI_ENVIRONMENT_SLUG" \
- --namespace="$KUBE_NAMESPACE" \
- "$name" \
- chart/
- fi
-
- kubectl rollout status -n "$KUBE_NAMESPACE" -w "deployment/$name"
- }
-
-
- function install_dependencies() {
- apk add -U openssl curl tar gzip bash ca-certificates git
- wget -q -O /etc/apk/keys/sgerrand.rsa.pub https://alpine-pkgs.sgerrand.com/sgerrand.rsa.pub
- wget https://github.com/sgerrand/alpine-pkg-glibc/releases/download/2.23-r3/glibc-2.23-r3.apk
- apk add glibc-2.23-r3.apk
- rm glibc-2.23-r3.apk
-
- curl "https://kubernetes-helm.storage.googleapis.com/helm-v${HELM_VERSION}-linux-amd64.tar.gz" | tar zx
- mv linux-amd64/helm /usr/bin/
- mv linux-amd64/tiller /usr/bin/
- helm version --client
- tiller -version
-
- curl -L -o /usr/bin/kubectl "https://storage.googleapis.com/kubernetes-release/release/v${KUBERNETES_VERSION}/bin/linux/amd64/kubectl"
- chmod +x /usr/bin/kubectl
- kubectl version --client
- }
-
- function download_chart() {
- if [[ ! -d chart ]]; then
- auto_chart=${AUTO_DEVOPS_CHART:-gitlab/auto-deploy-app}
- auto_chart_name=$(basename $auto_chart)
- auto_chart_name=${auto_chart_name%.tgz}
- else
- auto_chart="chart"
- auto_chart_name="chart"
- fi
-
- helm init --client-only
- helm repo add gitlab https://charts.gitlab.io
- if [[ ! -d "$auto_chart" ]]; then
- helm fetch ${auto_chart} --untar
- fi
- if [ "$auto_chart_name" != "chart" ]; then
- mv ${auto_chart_name} chart
- fi
-
- helm dependency update chart/
- helm dependency build chart/
- }
-
- function ensure_namespace() {
- kubectl describe namespace "$KUBE_NAMESPACE" || kubectl create namespace "$KUBE_NAMESPACE"
- }
-
- function check_kube_domain() {
- if [ -z ${AUTO_DEVOPS_DOMAIN+x} ]; then
- echo "In order to deploy or use Review Apps, AUTO_DEVOPS_DOMAIN variable must be set"
- echo "You can do it in Auto DevOps project settings or defining a secret variable at group or project level"
- echo "You can also manually add it in .gitlab-ci.yml"
- false
- else
- true
- fi
- }
-
- function initialize_tiller() {
- echo "Checking Tiller..."
-
- export HELM_HOST="localhost:44134"
- tiller -listen ${HELM_HOST} -alsologtostderr > /dev/null 2>&1 &
- echo "Tiller is listening on ${HELM_HOST}"
-
- if ! helm version --debug; then
- echo "Failed to init Tiller."
- return 1
- fi
- echo ""
- }
-
- function create_secret() {
- echo "Create secret..."
- if [[ "$CI_PROJECT_VISIBILITY" == "public" ]]; then
- return
- fi
-
- kubectl create secret -n "$KUBE_NAMESPACE" \
- docker-registry gitlab-registry \
- --docker-server="$CI_REGISTRY" \
- --docker-username="$CI_REGISTRY_USER" \
- --docker-password="$CI_REGISTRY_PASSWORD" \
- --docker-email="$GITLAB_USER_EMAIL" \
- -o yaml --dry-run | kubectl replace -n "$KUBE_NAMESPACE" --force -f -
- }
-
- function persist_environment_url() {
- echo $CI_ENVIRONMENT_URL > environment_url.txt
- }
-
-before_script:
- - *auto_devops
diff --git a/qa/qa/flow/login.rb b/qa/qa/flow/login.rb
index 8ad303df4de..d4d5cc2dcfc 100644
--- a/qa/qa/flow/login.rb
+++ b/qa/qa/flow/login.rb
@@ -22,9 +22,9 @@ module QA
end
end
- def sign_in(as: nil, address: :gitlab)
+ def sign_in(as: nil, address: :gitlab, skip_page_validation: false)
Runtime::Browser.visit(address, Page::Main::Login)
- Page::Main::Login.perform { |login| login.sign_in_using_credentials(user: as) }
+ Page::Main::Login.perform { |login| login.sign_in_using_credentials(user: as, skip_page_validation: skip_page_validation) }
end
def sign_in_as_admin(address: :gitlab)
diff --git a/qa/qa/git/repository.rb b/qa/qa/git/repository.rb
index 43608827b2e..2c8e362edd6 100644
--- a/qa/qa/git/repository.rb
+++ b/qa/qa/git/repository.rb
@@ -118,6 +118,10 @@ module QA
run("git push #{uri} #{branch}", max_attempts: 3).to_s
end
+ def push_all_branches
+ run("git push --all").to_s
+ end
+
def merge(branch)
run("git merge #{branch}")
end
diff --git a/qa/qa/page/component/ci_badge_link.rb b/qa/qa/page/component/ci_badge_link.rb
index 8629399e911..4c053f1d6a9 100644
--- a/qa/qa/page/component/ci_badge_link.rb
+++ b/qa/qa/page/component/ci_badge_link.rb
@@ -43,7 +43,7 @@ module QA
private
def completed?(timeout: 60)
- wait_until(reload: false, max_duration: timeout) do
+ wait_until(reload: false, sleep_interval: 3.0, max_duration: timeout) do
COMPLETED_STATUSES.include?(status_badge)
end
end
diff --git a/qa/qa/page/component/dropdown_filter.rb b/qa/qa/page/component/dropdown_filter.rb
index a39a04a668d..30c6f13eaf7 100644
--- a/qa/qa/page/component/dropdown_filter.rb
+++ b/qa/qa/page/component/dropdown_filter.rb
@@ -8,7 +8,7 @@ module QA
page.has_css?('.dropdown-input-field', wait: QA::Support::Repeater::DEFAULT_MAX_WAIT_TIME)
find('.dropdown-input-field').set(item)
- click_link item
+ click_on item
end
end
end
diff --git a/qa/qa/page/component/issuable/sidebar.rb b/qa/qa/page/component/issuable/sidebar.rb
new file mode 100644
index 00000000000..4e94049efe7
--- /dev/null
+++ b/qa/qa/page/component/issuable/sidebar.rb
@@ -0,0 +1,129 @@
+# frozen_string_literal: true
+
+module QA
+ module Page
+ module Component
+ module Issuable
+ module Sidebar
+ extend QA::Page::PageConcern
+
+ def self.included(base)
+ super
+
+ base.view 'app/assets/javascripts/sidebar/components/assignees/assignee_avatar.vue' do
+ element :avatar_image
+ end
+
+ base.view 'app/assets/javascripts/sidebar/components/assignees/uncollapsed_assignee_list.vue' do
+ element :more_assignees_link
+ end
+
+ base.view 'app/helpers/dropdowns_helper.rb' do
+ element :dropdown_input_field
+ end
+
+ base.view 'app/views/shared/issuable/_sidebar.html.haml' do
+ element :assignee_block
+ element :dropdown_menu_labels
+ element :edit_labels_link
+ element :edit_milestone_link
+ element :labels_block
+ element :milestone_block
+ element :milestone_link
+ end
+ end
+
+ def assign_milestone(milestone)
+ click_element(:edit_milestone_link)
+ within_element(:milestone_block) do
+ click_link("#{milestone.title}")
+ end
+
+ wait_until(reload: false) do
+ has_element?(:milestone_block, text: milestone.title, wait: 0)
+ end
+
+ refresh
+ end
+
+ def click_milestone_link
+ click_element(:milestone_link)
+ end
+
+ def has_assignee?(username)
+ page.within(element_selector_css(:assignee_block)) do
+ has_text?(username)
+ end
+ end
+
+ def has_avatar_image_count?(count)
+ wait_assignees_block_finish_loading do
+ all_elements(:avatar_image, count: count)
+ end
+ end
+
+ def has_label?(label)
+ within_element(:labels_block) do
+ !!has_element?(:label, label_name: label)
+ end
+ end
+
+ def has_milestone?(milestone_title)
+ wait_milestone_block_finish_loading do
+ has_element?(:milestone_link, title: milestone_title)
+ end
+ end
+
+ def more_assignees_link
+ find_element(:more_assignees_link)
+ end
+
+ def select_labels_and_refresh(labels)
+ Support::Retrier.retry_until do
+ click_element(:edit_labels_link)
+ has_element?(:dropdown_menu_labels, text: labels.first)
+ end
+
+ labels.each do |label|
+ within_element(:dropdown_menu_labels, text: label) do
+ send_keys_to_element(:dropdown_input_field, [label, :enter])
+ end
+ end
+
+ click_element(:edit_labels_link)
+
+ labels.each do |label|
+ has_element?(:labels_block, text: label, wait: 0)
+ end
+
+ refresh
+ end
+
+ def toggle_more_assignees_link
+ click_element(:more_assignees_link)
+ end
+
+ private
+
+ def wait_assignees_block_finish_loading
+ within_element(:assignee_block) do
+ wait_until(reload: false, max_duration: 10, sleep_interval: 1) do
+ finished_loading_block?
+ yield
+ end
+ end
+ end
+
+ def wait_milestone_block_finish_loading
+ within_element(:milestone_block) do
+ wait_until(reload: false, max_duration: 10, sleep_interval: 1) do
+ finished_loading_block?
+ yield
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/page/component/issue_board/show.rb b/qa/qa/page/component/issue_board/show.rb
index 0c840eba7ce..9e843630115 100644
--- a/qa/qa/page/component/issue_board/show.rb
+++ b/qa/qa/page/component/issue_board/show.rb
@@ -39,11 +39,6 @@ module QA
element :boards_list
end
- view 'app/views/shared/boards/components/_board.html.haml' do
- element :board_list
- element :board_list_header
- end
-
view 'app/assets/javascripts/boards/toggle_focus.js' do
element :focus_mode_button
end
diff --git a/qa/qa/page/component/new_snippet.rb b/qa/qa/page/component/new_snippet.rb
new file mode 100644
index 00000000000..18f2e237097
--- /dev/null
+++ b/qa/qa/page/component/new_snippet.rb
@@ -0,0 +1,80 @@
+# frozen_string_literal: true
+
+module QA
+ module Page
+ module Component
+ module NewSnippet
+ extend QA::Page::PageConcern
+
+ def self.included(base)
+ super
+
+ base.view 'app/assets/javascripts/snippets/components/edit.vue' do
+ element :snippet_title_field, required: true
+ element :submit_button
+ end
+
+ base.view 'app/assets/javascripts/snippets/components/snippet_description_edit.vue' do
+ element :snippet_description_field
+ element :description_placeholder, required: true
+ end
+
+ base.view 'app/assets/javascripts/snippets/components/snippet_blob_edit.vue' do
+ element :file_name_field
+ end
+
+ base.view 'app/views/shared/form_elements/_description.html.haml' do
+ element :issuable_form_description
+ end
+
+ base.view 'app/views/shared/snippets/_form.html.haml' do
+ element :snippet_description_field
+ element :description_placeholder
+ element :snippet_title_field
+ element :file_name_field
+ element :submit_button
+ end
+
+ base.view 'app/views/shared/_zen.html.haml' do
+ # This 'element' is here only to ensure the changes in the view source aren't mistakenly changed
+ element :_, "qa_selector = local_assigns.fetch(:qa_selector, '')" # rubocop:disable QA/ElementWithPattern
+ end
+ end
+
+ def fill_title(title)
+ fill_element :snippet_title_field, title
+ end
+
+ def fill_description(description)
+ click_element :description_placeholder
+ fill_element :snippet_description_field, description
+ end
+
+ def set_visibility(visibility)
+ choose visibility
+ end
+
+ def fill_file_name(name)
+ finished_loading?
+ fill_element :file_name_field, name
+ end
+
+ def fill_file_content(content)
+ finished_loading?
+ text_area.set content
+ end
+
+ def click_create_snippet_button
+ wait_until(reload: false) { !find_element(:submit_button).disabled? }
+ click_element(:submit_button, Page::Dashboard::Snippet::Show)
+ end
+
+ private
+
+ def text_area
+ find('#editor textarea', visible: false)
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/page/component/project_selector.rb b/qa/qa/page/component/project_selector.rb
new file mode 100644
index 00000000000..80ed6b8e53b
--- /dev/null
+++ b/qa/qa/page/component/project_selector.rb
@@ -0,0 +1,28 @@
+# frozen_string_literal: true
+
+module QA
+ module Page
+ module Component
+ module ProjectSelector
+ extend QA::Page::PageConcern
+
+ def self.included(base)
+ super
+
+ base.view 'app/assets/javascripts/vue_shared/components/project_selector/project_selector.vue' do
+ element :project_search_field
+ element :project_list_item
+ end
+ end
+
+ def fill_project_search_input(project_name)
+ fill_element :project_search_field, project_name
+ end
+
+ def select_project
+ click_element :project_list_item
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/page/component/snippet.rb b/qa/qa/page/component/snippet.rb
new file mode 100644
index 00000000000..4ff19c01f1f
--- /dev/null
+++ b/qa/qa/page/component/snippet.rb
@@ -0,0 +1,180 @@
+# frozen_string_literal: true
+
+module QA
+ module Page
+ module Component
+ module Snippet
+ extend QA::Page::PageConcern
+
+ def self.included(base)
+ super
+
+ base.view 'app/assets/javascripts/snippets/components/snippet_title.vue' do
+ element :snippet_title_content, required: true
+ end
+
+ base.view 'app/assets/javascripts/snippets/components/snippet_description_view.vue' do
+ element :snippet_description_content
+ end
+
+ base.view 'app/assets/javascripts/snippets/components/snippet_header.vue' do
+ element :snippet_container
+ end
+
+ base.view 'app/assets/javascripts/blob/components/blob_header_filepath.vue' do
+ element :file_title_content
+ end
+
+ base.view 'app/assets/javascripts/vue_shared/components/blob_viewers/simple_viewer.vue' do
+ element :file_content
+ end
+
+ base.view 'app/assets/javascripts/blob/components/blob_content.vue' do
+ element :file_content
+ end
+
+ base.view 'app/assets/javascripts/snippets/components/snippet_header.vue' do
+ element :snippet_action_button
+ element :delete_snippet_button
+ end
+
+ base.view 'app/assets/javascripts/snippets/components/snippet_blob_view.vue' do
+ element :clone_button
+ end
+
+ base.view 'app/assets/javascripts/vue_shared/components/clone_dropdown.vue' do
+ element :copy_http_url_button
+ element :copy_ssh_url_button
+ end
+
+ base.view 'app/views/shared/notes/_comment_button.html.haml' do
+ element :comment_button
+ end
+
+ base.view 'app/views/shared/notes/_form.html.haml' do
+ element :note_field
+ end
+
+ base.view 'app/views/snippets/notes/_actions.html.haml' do
+ element :edit_comment_button
+ end
+
+ base.view 'app/views/shared/notes/_edit_form.html.haml' do
+ element :edit_note_field
+ element :save_comment_button
+ end
+
+ base.view 'app/views/shared/notes/_note.html.haml' do
+ element :note_content
+ element :note_author_content
+ end
+
+ base.view 'app/views/projects/notes/_more_actions_dropdown.html.haml' do
+ element :more_actions_dropdown
+ element :delete_comment_button
+ end
+ end
+
+ def has_snippet_title?(snippet_title)
+ has_element? :snippet_title_content, text: snippet_title
+ end
+
+ def has_snippet_description?(snippet_description)
+ has_element? :snippet_description_content, text: snippet_description
+ end
+
+ def has_no_snippet_description?
+ has_no_element?(:snippet_description_field)
+ end
+
+ def has_visibility_type?(visibility_type)
+ within_element(:snippet_container) do
+ has_text?(visibility_type)
+ end
+ end
+
+ def has_file_name?(file_name)
+ within_element(:file_title_content) do
+ has_text?(file_name)
+ end
+ end
+
+ def has_file_content?(file_content)
+ finished_loading?
+ within_element(:file_content) do
+ has_text?(file_content)
+ end
+ end
+
+ def click_edit_button
+ finished_loading?
+ click_element(:snippet_action_button, action: 'Edit')
+ end
+
+ def click_delete_button
+ finished_loading?
+ click_element(:snippet_action_button, action: 'Delete')
+ click_element(:delete_snippet_button)
+ # wait for the page to reload after deletion
+ wait_until(reload: false) do
+ has_no_element?(:delete_snippet_button) &&
+ has_no_element?(:snippet_action_button, action: 'Delete')
+ end
+ end
+
+ def get_repository_uri_http
+ finished_loading?
+ click_element(:clone_button)
+ Git::Location.new(find_element(:copy_http_url_button)['data-clipboard-text']).uri.to_s
+ end
+
+ def get_repository_uri_ssh
+ finished_loading?
+ click_element(:clone_button)
+ Git::Location.new(find_element(:copy_ssh_url_button)['data-clipboard-text']).uri.to_s
+ end
+
+ def add_comment(comment)
+ finished_loading?
+ fill_element(:note_field, comment)
+ click_element(:comment_button)
+ end
+
+ def has_comment_author?(author_username)
+ finished_loading?
+ within_element(:note_author_content) do
+ has_text?('@' + author_username)
+ end
+ end
+
+ def has_comment_content?(comment_content)
+ finished_loading?
+ within_element(:note_content) do
+ has_text?(comment_content)
+ end
+ end
+
+ def has_syntax_highlighting?(language)
+ within_element(:file_content) do
+ find('.line')['lang'].to_s == language
+ end
+ end
+
+ def edit_comment(comment)
+ finished_loading?
+ click_element(:edit_comment_button)
+ fill_element(:edit_note_field, comment)
+ click_element(:save_comment_button)
+ end
+
+ def delete_comment(comment)
+ finished_loading?
+ click_element(:more_actions_dropdown)
+ accept_alert do
+ click_element(:delete_comment_button)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/page/component/web_ide/modal/create_new_file.rb b/qa/qa/page/component/web_ide/modal/create_new_file.rb
index 48eb32fefd6..7c55f775476 100644
--- a/qa/qa/page/component/web_ide/modal/create_new_file.rb
+++ b/qa/qa/page/component/web_ide/modal/create_new_file.rb
@@ -9,7 +9,7 @@ module QA
view 'app/assets/javascripts/ide/components/new_dropdown/modal.vue' do
element :file_name_field, required: true
element :new_file_modal, required: true
- element :template_list, required: true
+ element :template_list
end
end
end
diff --git a/qa/qa/page/dashboard/snippet/new.rb b/qa/qa/page/dashboard/snippet/new.rb
index bcfb4734b59..683e118aa19 100644
--- a/qa/qa/page/dashboard/snippet/new.rb
+++ b/qa/qa/page/dashboard/snippet/new.rb
@@ -5,70 +5,7 @@ module QA
module Dashboard
module Snippet
class New < Page::Base
- view 'app/assets/javascripts/snippets/components/edit.vue' do
- element :snippet_title_field, required: true
- element :submit_button
- end
-
- view 'app/assets/javascripts/snippets/components/snippet_description_edit.vue' do
- element :snippet_description_field
- element :description_placeholder, required: true
- end
-
- view 'app/assets/javascripts/snippets/components/snippet_blob_edit.vue' do
- element :file_name_field
- end
-
- view 'app/views/shared/form_elements/_description.html.haml' do
- element :issuable_form_description
- end
-
- view 'app/views/shared/snippets/_form.html.haml' do
- element :snippet_description_field
- element :description_placeholder
- element :snippet_title_field
- element :file_name_field
- element :submit_button
- end
-
- view 'app/views/shared/_zen.html.haml' do
- # This 'element' is here only to ensure the changes in the view source aren't mistakenly changed
- element :_, "qa_selector = local_assigns.fetch(:qa_selector, '')" # rubocop:disable QA/ElementWithPattern
- end
-
- def fill_title(title)
- fill_element :snippet_title_field, title
- end
-
- def fill_description(description)
- click_element :description_placeholder
- fill_element :snippet_description_field, description
- end
-
- def set_visibility(visibility)
- choose visibility
- end
-
- def fill_file_name(name)
- finished_loading?
- fill_element :file_name_field, name
- end
-
- def fill_file_content(content)
- finished_loading?
- text_area.set content
- end
-
- def click_create_snippet_button
- wait_until(reload: false) { !find_element(:submit_button).disabled? }
- click_element :submit_button
- end
-
- private
-
- def text_area
- find('#editor textarea', visible: false)
- end
+ include Page::Component::NewSnippet
end
end
end
diff --git a/qa/qa/page/dashboard/snippet/show.rb b/qa/qa/page/dashboard/snippet/show.rb
index da494ae70ec..73e6abe174f 100644
--- a/qa/qa/page/dashboard/snippet/show.rb
+++ b/qa/qa/page/dashboard/snippet/show.rb
@@ -5,102 +5,7 @@ module QA
module Dashboard
module Snippet
class Show < Page::Base
- view 'app/assets/javascripts/snippets/components/snippet_description_view.vue' do
- element :snippet_description_content
- end
-
- view 'app/assets/javascripts/snippets/components/snippet_title.vue' do
- element :snippet_title_content, required: true
- end
-
- view 'app/assets/javascripts/snippets/components/snippet_header.vue' do
- element :snippet_container
- end
-
- view 'app/assets/javascripts/blob/components/blob_header_filepath.vue' do
- element :file_title_content
- end
-
- view 'app/assets/javascripts/vue_shared/components/blob_viewers/simple_viewer.vue' do
- element :file_content
- end
-
- view 'app/assets/javascripts/blob/components/blob_content.vue' do
- element :file_content
- end
-
- view 'app/assets/javascripts/snippets/components/snippet_header.vue' do
- element :snippet_action_button
- element :delete_snippet_button
- end
-
- view 'app/assets/javascripts/snippets/components/snippet_blob_view.vue' do
- element :clone_button
- end
-
- view 'app/assets/javascripts/vue_shared/components/clone_dropdown.vue' do
- element :copy_http_url_button
- element :copy_ssh_url_button
- end
-
- def has_snippet_title?(snippet_title)
- has_element? :snippet_title_content, text: snippet_title
- end
-
- def has_snippet_description?(snippet_description)
- has_element? :snippet_description_content, text: snippet_description
- end
-
- def has_no_snippet_description?
- has_no_element?(:snippet_description_field)
- end
-
- def has_visibility_type?(visibility_type)
- within_element(:snippet_container) do
- has_text?(visibility_type)
- end
- end
-
- def has_file_name?(file_name)
- within_element(:file_title_content) do
- has_text?(file_name)
- end
- end
-
- def has_file_content?(file_content)
- finished_loading?
- within_element(:file_content) do
- has_text?(file_content)
- end
- end
-
- def click_edit_button
- finished_loading?
- click_element(:snippet_action_button, action: 'Edit')
- end
-
- def click_delete_button
- finished_loading?
- click_element(:snippet_action_button, action: 'Delete')
- click_element(:delete_snippet_button)
- # wait for the page to reload after deletion
- wait_until(reload: false) do
- has_no_element?(:delete_snippet_button) &&
- has_no_element?(:snippet_action_button, action: 'Delete')
- end
- end
-
- def get_repository_uri_http
- finished_loading?
- click_element(:clone_button)
- Git::Location.new(find_element(:copy_http_url_button)['data-clipboard-text']).uri.to_s
- end
-
- def get_repository_uri_ssh
- finished_loading?
- click_element(:clone_button)
- Git::Location.new(find_element(:copy_ssh_url_button)['data-clipboard-text']).uri.to_s
- end
+ include Page::Component::Snippet
end
end
end
diff --git a/qa/qa/page/group/menu.rb b/qa/qa/page/group/menu.rb
index 380984c283e..7689dd7e5c8 100644
--- a/qa/qa/page/group/menu.rb
+++ b/qa/qa/page/group/menu.rb
@@ -7,9 +7,11 @@ module QA
include SubMenus::Common
view 'app/views/layouts/nav/sidebar/_group.html.haml' do
- element :group_settings_item
- element :group_members_item
element :general_settings_link
+ element :group_issues_item
+ element :group_members_item
+ element :group_milestones_link
+ element :group_settings_item
end
view 'app/views/layouts/nav/sidebar/_analytics_links.html.haml' do
@@ -44,6 +46,25 @@ module QA
end
end
end
+
+ def go_to_milestones
+ hover_issues do
+ within_submenu do
+ click_element(:group_milestones_link)
+ end
+ end
+ end
+
+ private
+
+ def hover_issues
+ within_sidebar do
+ scroll_to_element(:group_issues_item)
+ find_element(:group_issues_item).hover
+
+ yield
+ end
+ end
end
end
end
diff --git a/qa/qa/page/group/milestone/index.rb b/qa/qa/page/group/milestone/index.rb
new file mode 100644
index 00000000000..a86772a070d
--- /dev/null
+++ b/qa/qa/page/group/milestone/index.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+module QA
+ module Page
+ module Group
+ module Milestone
+ class Index < Page::Milestone::Index
+ view 'app/views/groups/milestones/index.html.haml' do
+ element :new_group_milestone_link
+ end
+
+ def click_new_milestone_link
+ click_element(:new_group_milestone_link)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/page/group/milestone/new.rb b/qa/qa/page/group/milestone/new.rb
new file mode 100644
index 00000000000..451117a2163
--- /dev/null
+++ b/qa/qa/page/group/milestone/new.rb
@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+
+module QA
+ module Page
+ module Group
+ module Milestone
+ class New < Page::Milestone::New
+ view 'app/views/groups/milestones/_form.html.haml' do
+ element :create_milestone_button
+ element :milestone_description_field
+ element :milestone_title_field
+ end
+
+ def click_create_milestone_button
+ click_element(:create_milestone_button)
+ end
+
+ def set_description(description)
+ fill_element(:milestone_description_field, description)
+ end
+
+ def set_title(title)
+ fill_element(:milestone_title_field, title)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/page/group/sub_menus/members.rb b/qa/qa/page/group/sub_menus/members.rb
index 33c4caaddcb..895da639c02 100644
--- a/qa/qa/page/group/sub_menus/members.rb
+++ b/qa/qa/page/group/sub_menus/members.rb
@@ -7,6 +7,10 @@ module QA
class Members < Page::Base
include Page::Component::UsersSelect
+ view 'app/assets/javascripts/vue_shared/components/remove_member_modal.vue' do
+ element :remove_member_modal_content
+ end
+
view 'app/views/shared/members/_invite_member.html.haml' do
element :member_select_field
element :invite_member_button
@@ -32,10 +36,12 @@ module QA
end
def remove_member(username)
- page.accept_confirm do
- within_element(:member_row, text: username) do
- click_element :delete_member_button
- end
+ within_element(:member_row, text: username) do
+ click_element :delete_member_button
+ end
+
+ within_element(:remove_member_modal_content) do
+ click_button("Remove member")
end
end
end
diff --git a/qa/qa/page/issuable/sidebar.rb b/qa/qa/page/issuable/sidebar.rb
deleted file mode 100644
index af5eee35a2d..00000000000
--- a/qa/qa/page/issuable/sidebar.rb
+++ /dev/null
@@ -1,27 +0,0 @@
-# frozen_string_literal: true
-
-module QA
- module Page
- module Issuable
- class Sidebar < Page::Base
- view 'app/views/shared/issuable/_sidebar.html.haml' do
- element :labels_block
- element :milestone_block
- element :milestone_link
- end
-
- def has_label?(label)
- within_element(:labels_block) do
- has_element?(:label, label_name: label)
- end
- end
-
- def has_milestone?(milestone_title)
- within_element(:milestone_block) do
- has_element?(:milestone_link, title: milestone_title)
- end
- end
- end
- end
- end
-end
diff --git a/qa/qa/page/main/menu.rb b/qa/qa/page/main/menu.rb
index 6af18cb1d2b..416946f44f0 100644
--- a/qa/qa/page/main/menu.rb
+++ b/qa/qa/page/main/menu.rb
@@ -22,6 +22,9 @@ module QA
element :groups_dropdown, required: true
element :more_dropdown
element :snippets_link
+ element :groups_link
+ element :activity_link
+ element :milestones_link
end
view 'app/views/layouts/nav/projects_dropdown/_show.html.haml' do
@@ -53,10 +56,10 @@ module QA
end
end
- def go_to_snippets
+ def go_to_more_dropdown_option(option_name)
within_top_menu do
click_element :more_dropdown
- click_element :snippets_link
+ click_element option_name
end
end
@@ -148,3 +151,5 @@ module QA
end
end
end
+
+QA::Page::Main::Menu.prepend_if_ee('QA::EE::Page::Main::Menu')
diff --git a/qa/qa/page/main/two_factor_auth.rb b/qa/qa/page/main/two_factor_auth.rb
new file mode 100644
index 00000000000..003bd8dd1b1
--- /dev/null
+++ b/qa/qa/page/main/two_factor_auth.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+module QA
+ module Page
+ module Main
+ class TwoFactorAuth < Page::Base
+ view 'app/views/devise/sessions/two_factor.html.haml' do
+ element :verify_code_button
+ element :two_fa_code_field
+ end
+
+ def click_verify_code_button
+ click_element :verify_code_button
+ end
+
+ def set_2fa_code(code)
+ fill_element(:two_fa_code_field, code)
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/page/merge_request/show.rb b/qa/qa/page/merge_request/show.rb
index 07f8e568b2a..b9a2bf4ee69 100644
--- a/qa/qa/page/merge_request/show.rb
+++ b/qa/qa/page/merge_request/show.rb
@@ -5,6 +5,7 @@ module QA
module MergeRequest
class Show < Page::Base
include Page::Component::Note
+ include Page::Component::Issuable::Sidebar
view 'app/assets/javascripts/mr_tabs_popover/components/popover.vue' do
element :dismiss_popover_button
@@ -64,11 +65,6 @@ module QA
element :new_diff_line
end
- view 'app/views/shared/issuable/_sidebar.html.haml' do
- element :assignee_block
- element :labels_block
- end
-
view 'app/views/projects/merge_requests/_mr_title.html.haml' do
element :edit_button
end
@@ -178,18 +174,6 @@ module QA
has_element?(:merge_button)
end
- def has_assignee?(username)
- page.within(element_selector_css(:assignee_block)) do
- has_text?(username)
- end
- end
-
- def has_label?(label)
- within_element(:labels_block) do
- !!has_element?(:label, label_name: label)
- end
- end
-
def has_pipeline_status?(text)
# Pipelines can be slow, so we wait a bit longer than the usual 10 seconds
has_element?(:merge_request_pipeline_info_content, text: text, wait: 30)
diff --git a/qa/qa/page/milestone/index.rb b/qa/qa/page/milestone/index.rb
new file mode 100644
index 00000000000..2e86dd4c0ab
--- /dev/null
+++ b/qa/qa/page/milestone/index.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+module QA
+ module Page
+ module Milestone
+ class Index < Page::Base
+ view 'app/views/shared/milestones/_milestone.html.haml' do
+ element :milestone_link
+ end
+
+ def click_milestone(milestone)
+ click_element(:milestone_link, milestone_title: milestone.title)
+ end
+
+ def has_milestone?(milestone)
+ has_element?(:milestone_link, milestone_title: milestone.title)
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/page/milestone/new.rb b/qa/qa/page/milestone/new.rb
new file mode 100644
index 00000000000..655254d74fa
--- /dev/null
+++ b/qa/qa/page/milestone/new.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+module QA
+ module Page
+ module Milestone
+ class New < Page::Base
+ view 'app/views/shared/milestones/_form_dates.html.haml' do
+ element :due_date_field
+ element :start_date_field
+ end
+
+ def set_due_date(due_date)
+ fill_element(:due_date_field, due_date.to_s + "\n")
+ end
+
+ def set_start_date(start_date)
+ fill_element(:start_date_field, start_date.to_s + "\n")
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/page/milestone/show.rb b/qa/qa/page/milestone/show.rb
new file mode 100644
index 00000000000..42efbd4ea30
--- /dev/null
+++ b/qa/qa/page/milestone/show.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+module QA
+ module Page
+ module Milestone
+ class Show < Page::Base
+ include Support::Dates
+
+ view 'app/views/shared/milestones/_description.html.haml' do
+ element :milestone_description_content
+ element :milestone_title_content, required: true
+ end
+
+ view 'app/views/shared/milestones/_sidebar.html.haml' do
+ element :due_date_content
+ element :start_date_content
+ end
+
+ def has_due_date?(due_date)
+ formatted_due_date = format_date(due_date)
+ has_element?(:due_date_content, text: formatted_due_date)
+ end
+
+ def has_start_date?(start_date)
+ formatted_start_date = format_date(start_date)
+ has_element?(:start_date_content, text: formatted_start_date)
+ end
+ end
+ end
+ end
+end
+
+QA::Page::Milestone::Show.prepend_if_ee('QA::EE::Page::Milestone::Show')
diff --git a/qa/qa/page/modal/delete_wiki.rb b/qa/qa/page/modal/delete_wiki.rb
new file mode 100644
index 00000000000..4f0bc34ee88
--- /dev/null
+++ b/qa/qa/page/modal/delete_wiki.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+module QA
+ module Page
+ module Modal
+ class DeleteWiki < Base
+ view 'app/assets/javascripts/pages/shared/wikis/components/delete_wiki_modal.vue' do
+ element :confirm_deletion_button, required: true
+ end
+
+ def confirm_deletion
+ click_element :confirm_deletion_button
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/page/profile/two_factor_auth.rb b/qa/qa/page/profile/two_factor_auth.rb
index a3ff5f603fa..b5a4d04b377 100644
--- a/qa/qa/page/profile/two_factor_auth.rb
+++ b/qa/qa/page/profile/two_factor_auth.rb
@@ -8,9 +8,35 @@ module QA
element :configure_it_later_button
end
+ view 'app/views/profiles/two_factor_auths/show.html.haml' do
+ element :otp_secret_content
+ element :pin_code_field
+ element :register_2fa_app_button
+ end
+
+ view 'app/views/profiles/two_factor_auths/_codes.html.haml' do
+ element :proceed_button
+ end
+
def click_configure_it_later_button
click_element :configure_it_later_button
end
+
+ def otp_secret_content
+ find_element(:otp_secret_content).text.gsub('Key:', '').delete(' ')
+ end
+
+ def set_pin_code(pin_code)
+ fill_element(:pin_code_field, pin_code)
+ end
+
+ def click_register_2fa_app_button
+ click_element :register_2fa_app_button
+ end
+
+ def click_proceed_button
+ click_element :proceed_button
+ end
end
end
end
diff --git a/qa/qa/page/project/issue/index.rb b/qa/qa/page/project/issue/index.rb
index ace2537fc0e..e0c10220fbc 100644
--- a/qa/qa/page/project/issue/index.rb
+++ b/qa/qa/page/project/issue/index.rb
@@ -18,6 +18,11 @@ module QA
element :export_issues_modal
end
+ view 'app/views/projects/issues/import_csv/_button.html.haml' do
+ element :import_issues_button
+ element :import_from_jira_link
+ end
+
view 'app/views/projects/issues/_issue.html.haml' do
element :issue
element :issue_link, 'link_to issue.title' # rubocop:disable QA/ElementWithPattern
@@ -51,10 +56,25 @@ module QA
click_element(:export_issues_button)
end
+ def click_import_from_jira_link
+ click_element(:import_from_jira_link)
+ end
+
+ def click_import_issues_dropdown
+ # When there are no issues, the image that loads causes the buttons to jump
+ has_loaded_all_images?
+ click_element(:import_issues_button)
+ end
+
def export_issues_modal
find_element(:export_issues_modal)
end
+ def go_to_jira_import_form
+ click_import_issues_dropdown
+ click_import_from_jira_link
+ end
+
def has_assignee_link_count?(count)
all_elements(:assignee_link, count: count)
end
diff --git a/qa/qa/page/project/issue/jira_import.rb b/qa/qa/page/project/issue/jira_import.rb
new file mode 100644
index 00000000000..d3be24464ab
--- /dev/null
+++ b/qa/qa/page/project/issue/jira_import.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+module QA
+ module Page
+ module Project
+ module Issue
+ class JiraImport < Page::Base
+ view 'app/assets/javascripts/jira_import/components/jira_import_form.vue' do
+ element :jira_project_dropdown
+ element :jira_issues_import_button
+ end
+
+ def select_jira_project(jira_project)
+ select_element(:jira_project_dropdown, jira_project)
+ end
+
+ def select_project_and_import(jira_project)
+ select_jira_project(jira_project)
+ click_element(:jira_issues_import_button)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/page/project/issue/show.rb b/qa/qa/page/project/issue/show.rb
index dd74ff28763..04f0f34cbbb 100644
--- a/qa/qa/page/project/issue/show.rb
+++ b/qa/qa/page/project/issue/show.rb
@@ -8,6 +8,7 @@ module QA
include Page::Component::Issuable::Common
include Page::Component::Note
include Page::Component::DesignManagement
+ include Page::Component::Issuable::Sidebar
view 'app/assets/javascripts/notes/components/comment_form.vue' do
element :comment_button
@@ -23,45 +24,25 @@ module QA
element :noteable_note_item
end
- view 'app/assets/javascripts/sidebar/components/assignees/assignee_avatar.vue' do
- element :avatar_image
- end
-
- view 'app/assets/javascripts/sidebar/components/assignees/uncollapsed_assignee_list.vue' do
- element :more_assignees_link
- end
-
view 'app/assets/javascripts/vue_shared/components/issue/related_issuable_item.vue' do
element :remove_related_issue_button
end
- view 'app/helpers/dropdowns_helper.rb' do
- element :dropdown_input_field
- end
-
view 'app/views/shared/issuable/_close_reopen_button.html.haml' do
element :close_issue_button
element :reopen_issue_button
end
- view 'app/views/shared/issuable/_sidebar.html.haml' do
- element :assignee_block
- element :labels_block
- element :edit_link_labels
- element :dropdown_menu_labels
- element :milestone_link
- end
-
view 'app/views/shared/notes/_form.html.haml' do
element :new_note_form, 'new-note' # rubocop:disable QA/ElementWithPattern
element :new_note_form, 'attr: :note' # rubocop:disable QA/ElementWithPattern
end
view 'app/views/projects/issues/_tabs.html.haml' do
- element :discussion_tab_link
- element :discussion_tab_content
- element :designs_tab_link
element :designs_tab_content
+ element :designs_tab_link
+ element :discussion_tab_content
+ element :discussion_tab_link
end
def click_discussion_tab
@@ -74,10 +55,6 @@ module QA
active_element?(:designs_tab_content)
end
- def click_milestone_link
- click_element(:milestone_link)
- end
-
def click_remove_related_issue_button
click_element(:remove_related_issue_button)
end
@@ -90,7 +67,7 @@ module QA
# attachment option should be an absolute path
def comment(text, attachment: nil, filter: :all_activities)
method("select_#{filter}_filter").call
- fill_element :comment_input, text
+ fill_element :comment_input, "#{text}\n"
unless attachment.nil?
QA::Page::Component::Dropzone.new(self, '.new-note')
@@ -100,20 +77,10 @@ module QA
click_element :comment_button
end
- def has_avatar_image_count?(count)
- wait_assignees_block_finish_loading do
- all_elements(:avatar_image, count: count)
- end
- end
-
def has_comment?(comment_text)
has_element?(:noteable_note_item, text: comment_text, wait: QA::Support::Repeater::DEFAULT_MAX_WAIT_TIME)
end
- def more_assignees_link
- find_element(:more_assignees_link)
- end
-
def noteable_note_item
find_element(:noteable_note_item)
end
@@ -130,35 +97,6 @@ module QA
select_filter_with_text('Show history only')
end
- def select_labels_and_refresh(labels)
- Support::Retrier.retry_until do
- click_element(:edit_link_labels)
- has_element?(:dropdown_menu_labels, text: labels.first)
- end
-
- labels.each do |label|
- within_element(:dropdown_menu_labels, text: label) do
- send_keys_to_element(:dropdown_input_field, [label, :enter])
- end
- end
-
- click_element(:edit_link_labels)
-
- labels.each do |label|
- has_element?(:labels_block, text: label, wait: 0)
- end
-
- refresh
- end
-
- def text_of_labels_block
- find_element(:labels_block)
- end
-
- def toggle_more_assignees_link
- click_element(:more_assignees_link)
- end
-
private
def select_filter_with_text(text)
@@ -168,15 +106,6 @@ module QA
find_element(:filter_options, text: text).click
end
end
-
- def wait_assignees_block_finish_loading
- within_element(:assignee_block) do
- wait_until(reload: false, max_duration: 10, sleep_interval: 1) do
- finished_loading_block?
- yield
- end
- end
- end
end
end
end
diff --git a/qa/qa/page/project/job/show.rb b/qa/qa/page/project/job/show.rb
index 971b8c5e5f8..6243dc92b45 100644
--- a/qa/qa/page/project/job/show.rb
+++ b/qa/qa/page/project/job/show.rb
@@ -23,6 +23,9 @@ module QA
raise "Timed out waiting for the build trace to load" unless loaded?
raise "Timed out waiting for the status to be a valid completed state" unless completed?(timeout: timeout)
+ job_log = find_element(:job_log_content).text
+ QA::Runtime::Logger.debug(" \n\n ------- Job log: ------- \n\n #{job_log} \n -------")
+
passed?
end
diff --git a/qa/qa/page/project/menu.rb b/qa/qa/page/project/menu.rb
index 3d4d0ff9d22..9faf1bd5f8f 100644
--- a/qa/qa/page/project/menu.rb
+++ b/qa/qa/page/project/menu.rb
@@ -15,11 +15,14 @@ module QA
view 'app/views/layouts/nav/sidebar/_project.html.haml' do
element :activity_link
element :merge_requests_link
- element :wiki_link
element :snippets_link
element :members_link
end
+ view 'app/views/layouts/nav/sidebar/_wiki_link.html.haml' do
+ element :wiki_link
+ end
+
def click_merge_requests
within_sidebar do
click_element(:merge_requests_link)
diff --git a/qa/qa/page/project/milestone/index.rb b/qa/qa/page/project/milestone/index.rb
index 6895c44f72f..d25a3389ae8 100644
--- a/qa/qa/page/project/milestone/index.rb
+++ b/qa/qa/page/project/milestone/index.rb
@@ -4,13 +4,25 @@ module QA
module Page
module Project
module Milestone
- class Index < Page::Base
+ class Index < Page::Milestone::Index
view 'app/views/projects/milestones/index.html.haml' do
- element :new_project_milestone
+ element :new_project_milestone_link
end
- def click_new_milestone
- click_element :new_project_milestone
+ view 'app/views/shared/milestones/_milestone.html.haml' do
+ element :milestone_link
+ end
+
+ def click_new_milestone_link
+ click_element :new_project_milestone_link
+ end
+
+ def has_milestone?(milestone)
+ has_element? :milestone_link, milestone_title: milestone.title
+ end
+
+ def click_milestone(milestone)
+ click_element :milestone_link, milestone_title: milestone.title
end
end
end
diff --git a/qa/qa/page/project/milestone/new.rb b/qa/qa/page/project/milestone/new.rb
index 751fb141684..98f3a0ef4ab 100644
--- a/qa/qa/page/project/milestone/new.rb
+++ b/qa/qa/page/project/milestone/new.rb
@@ -4,23 +4,36 @@ module QA
module Page
module Project
module Milestone
- class New < Page::Base
+ class New < Page::Milestone::New
view 'app/views/projects/milestones/_form.html.haml' do
- element :milestone_create_button
- element :milestone_title
- element :milestone_description
+ element :create_milestone_button
+ element :milestone_description_field
+ element :milestone_title_field
+ end
+
+ view 'app/views/shared/milestones/_form_dates.html.haml' do
+ element :due_date_field
+ element :start_date_field
+ end
+
+ def click_create_milestone_button
+ click_element :create_milestone_button
end
def set_title(title)
- fill_element :milestone_title, title
+ fill_element :milestone_title_field, title
end
def set_description(description)
- fill_element :milestone_description, description
+ fill_element :milestone_description_field, description
+ end
+
+ def set_due_date(due_date)
+ fill_element :due_date_field, due_date.to_s + "\n"
end
- def click_milestone_create_button
- click_element :milestone_create_button
+ def set_start_date(start_date)
+ fill_element :start_date_field, start_date.to_s + "\n"
end
end
end
diff --git a/qa/qa/page/project/operations/kubernetes/show.rb b/qa/qa/page/project/operations/kubernetes/show.rb
index 46fddfa6078..e1612718883 100644
--- a/qa/qa/page/project/operations/kubernetes/show.rb
+++ b/qa/qa/page/project/operations/kubernetes/show.rb
@@ -29,6 +29,14 @@ module QA
element :uninstall_button
end
+ view 'app/views/clusters/clusters/_health.html.haml' do
+ element :cluster_health_section
+ end
+
+ view 'app/views/clusters/clusters/_health_tab.html.haml' do
+ element :health, required: true
+ end
+
def open_details
has_element?(:details, wait: 30)
click_element :details
@@ -71,11 +79,32 @@ module QA
def save_domain
click_element :save_changes_button, Page::Project::Operations::Kubernetes::Show
end
+
+ def wait_for_cluster_health
+ wait_until(max_duration: 120, sleep_interval: 3, reload: true) do
+ has_cluster_health_graphs?
+ end
+ end
+
+ def open_health
+ has_element?(:health, wait: 30)
+ click_element :health
+ end
+
+ def has_cluster_health_graphs?
+ within_cluster_health_section do
+ has_text?('CPU Usage')
+ end
+ end
+
+ def within_cluster_health_section
+ within_element :cluster_health_section do
+ yield
+ end
+ end
end
end
end
end
end
end
-
-QA::Page::Project::Operations::Kubernetes::Show.prepend_if_ee('QA::EE::Page::Project::Operations::Kubernetes::Show')
diff --git a/qa/qa/page/project/operations/metrics/show.rb b/qa/qa/page/project/operations/metrics/show.rb
index a1c15e72f44..e9e4923a0e2 100644
--- a/qa/qa/page/project/operations/metrics/show.rb
+++ b/qa/qa/page/project/operations/metrics/show.rb
@@ -1,5 +1,7 @@
# frozen_string_literal: true
+require 'securerandom'
+
module QA
module Page
module Project
@@ -60,7 +62,7 @@ module QA
def duplicate_dashboard(save_as = 'test_duplication.yml', commit_option = 'Commit to master branch')
click_element :dashboards_filter_dropdown
click_on 'Duplicate dashboard'
- fill_element :duplicate_dashboard_filename_field, save_as
+ fill_element :duplicate_dashboard_filename_field, "#{SecureRandom.hex(8)}-#{save_as}"
choose commit_option
within('.modal-content') { click_button(class: 'btn-success') }
end
diff --git a/qa/qa/page/project/pipeline/index.rb b/qa/qa/page/project/pipeline/index.rb
index 54e4d0fb2fc..aa2ef2f058f 100644
--- a/qa/qa/page/project/pipeline/index.rb
+++ b/qa/qa/page/project/pipeline/index.rb
@@ -5,11 +5,11 @@ module QA
module Project
module Pipeline
class Index < QA::Page::Base
- view 'app/assets/javascripts/pipelines/components/pipeline_url.vue' do
+ view 'app/assets/javascripts/pipelines/components/pipelines_list/pipeline_url.vue' do
element :pipeline_url_link
end
- view 'app/assets/javascripts/pipelines/components/pipelines_table_row.vue' do
+ view 'app/assets/javascripts/pipelines/components/pipelines_list/pipelines_table_row.vue' do
element :pipeline_commit_status
element :pipeline_retry_button
end
diff --git a/qa/qa/page/project/pipeline/show.rb b/qa/qa/page/project/pipeline/show.rb
index d22dfefc096..43003fe1953 100644
--- a/qa/qa/page/project/pipeline/show.rb
+++ b/qa/qa/page/project/pipeline/show.rb
@@ -22,6 +22,7 @@ module QA
view 'app/assets/javascripts/pipelines/components/graph/linked_pipeline.vue' do
element :linked_pipeline_button
+ element :child_pipeline
end
view 'app/assets/javascripts/vue_shared/components/ci_icon.vue' do
@@ -60,6 +61,10 @@ module QA
end
end
+ def has_child_pipeline?
+ has_element? :child_pipeline
+ end
+
def click_job(job_name)
click_element(:job_link, text: job_name)
end
diff --git a/qa/qa/page/project/settings/incidents.rb b/qa/qa/page/project/settings/incidents.rb
index 94d5fc369ad..9b523e2aa9e 100644
--- a/qa/qa/page/project/settings/incidents.rb
+++ b/qa/qa/page/project/settings/incidents.rb
@@ -5,10 +5,11 @@ module QA
module Project
module Settings
class Incidents < Page::Base
- view 'app/views/projects/settings/operations/_incidents.html.haml' do
+ view 'app/assets/javascripts/incidents_settings/components/alerts_form.vue' do
element :create_issue_checkbox
element :incident_templates_dropdown
element :save_changes_button
+ element :incident_templates_item
end
def enable_issues_for_incidents
@@ -16,8 +17,9 @@ module QA
end
def select_issue_template(template)
+ click_element(:incident_templates_dropdown)
within_element :incident_templates_dropdown do
- find(:option, template).select_option
+ find_element(:incident_templates_item, text: template).click
end
end
diff --git a/qa/qa/page/project/settings/operations.rb b/qa/qa/page/project/settings/operations.rb
index f6e005d3189..b39b8f92cc7 100644
--- a/qa/qa/page/project/settings/operations.rb
+++ b/qa/qa/page/project/settings/operations.rb
@@ -7,7 +7,7 @@ module QA
class Operations < Page::Base
include QA::Page::Settings::Common
- view 'app/views/projects/settings/operations/_incidents.html.haml' do
+ view 'app/assets/javascripts/incidents_settings/components/incidents_settings_tabs.vue' do
element :incidents_settings_content
end
diff --git a/qa/qa/page/project/settings/protected_tags.rb b/qa/qa/page/project/settings/protected_tags.rb
new file mode 100644
index 00000000000..bf8f349cfd5
--- /dev/null
+++ b/qa/qa/page/project/settings/protected_tags.rb
@@ -0,0 +1,46 @@
+# frozen_string_literal: true
+
+module QA
+ module Page
+ module Project
+ module Settings
+ class ProtectedTags < Page::Base
+ include Page::Component::DropdownFilter
+
+ view 'app/views/projects/protected_tags/shared/_dropdown.html.haml' do
+ element :tags_dropdown
+ end
+
+ view 'app/views/projects/protected_tags/_create_protected_tag.html.haml' do
+ element :access_levels_content
+ element :access_levels_dropdown
+ end
+
+ view 'app/views/projects/protected_tags/shared/_create_protected_tag.html.haml' do
+ element :protect_tag_button
+ end
+
+ def set_tag(tag_name)
+ click_element :tags_dropdown
+ filter_and_select(tag_name)
+ end
+
+ def choose_access_level_role(role)
+ return if find_element(:access_levels_dropdown).text == role
+
+ click_element :access_levels_dropdown
+ within_element(:access_levels_content) do
+ click_on role
+ end
+ end
+
+ def click_protect_tag_button
+ click_element :protect_tag_button
+ end
+ end
+ end
+ end
+ end
+end
+
+QA::Page::Project::Settings::ProtectedTags.prepend_if_ee('QA::EE::Page::Project::Settings::ProtectedTags')
diff --git a/qa/qa/page/project/settings/repository.rb b/qa/qa/page/project/settings/repository.rb
index 8e9a24a4741..fd3a590c2c1 100644
--- a/qa/qa/page/project/settings/repository.rb
+++ b/qa/qa/page/project/settings/repository.rb
@@ -23,6 +23,10 @@ module QA
element :deploy_keys_settings
end
+ view 'app/views/projects/protected_tags/shared/_index.html.haml' do
+ element :protected_tag_settings_content
+ end
+
def expand_deploy_tokens(&block)
expand_section(:deploy_tokens_settings) do
Settings::DeployTokens.perform(&block)
@@ -46,6 +50,12 @@ module QA
MirroringRepositories.perform(&block)
end
end
+
+ def expand_protected_tags(&block)
+ expand_section(:protected_tag_settings_content) do
+ ProtectedTags.perform(&block)
+ end
+ end
end
end
end
diff --git a/qa/qa/page/project/settings/runners.rb b/qa/qa/page/project/settings/runners.rb
index adc92b4acaf..af4dbb08430 100644
--- a/qa/qa/page/project/settings/runners.rb
+++ b/qa/qa/page/project/settings/runners.rb
@@ -13,7 +13,7 @@ module QA
##
# TODO, phase-out CSS classes added in Ruby helpers.
#
- view 'app/helpers/runners_helper.rb' do
+ view 'app/helpers/ci/runners_helper.rb' do
# rubocop:disable Lint/InterpolationCheck
element :runner_status, 'runner-status-#{status}' # rubocop:disable QA/ElementWithPattern
# rubocop:enable Lint/InterpolationCheck
diff --git a/qa/qa/page/project/settings/services/jira.rb b/qa/qa/page/project/settings/services/jira.rb
index 9f9331bac94..75baedb1b0f 100644
--- a/qa/qa/page/project/settings/services/jira.rb
+++ b/qa/qa/page/project/settings/services/jira.rb
@@ -6,11 +6,11 @@ module QA
module Settings
module Services
class Jira < QA::Page::Base
- view 'app/views/shared/_field.html.haml' do
- element :url_field, 'data: { qa_selector: "#{name.downcase.gsub' # rubocop:disable QA/ElementWithPattern
- element :username_field, 'data: { qa_selector: "#{name.downcase.gsub' # rubocop:disable QA/ElementWithPattern
- element :password_field, 'data: { qa_selector: "#{name.downcase.gsub' # rubocop:disable QA/ElementWithPattern
- element :jira_issue_transition_id_field, 'data: { qa_selector: "#{name.downcase.gsub' # rubocop:disable QA/ElementWithPattern
+ view 'app/assets/javascripts/integrations/edit/components/dynamic_field.vue' do
+ element :service_url_field, ':data-qa-selector="`${fieldId}_field`"' # rubocop:disable QA/ElementWithPattern
+ element :service_username_field, ':data-qa-selector="`${fieldId}_field`"' # rubocop:disable QA/ElementWithPattern
+ element :service_password_field, ':data-qa-selector="`${fieldId}_field`"' # rubocop:disable QA/ElementWithPattern
+ element :service_jira_issue_transition_id_field, ':data-qa-selector="`${fieldId}_field`"' # rubocop:disable QA/ElementWithPattern
end
view 'app/helpers/services_helper.rb' do
@@ -34,19 +34,19 @@ module QA
private
def set_jira_server_url(url)
- fill_element(:url_field, url)
+ fill_element(:service_url_field, url)
end
def set_username(username)
- fill_element(:username_field, username)
+ fill_element(:service_username_field, username)
end
def set_password(password)
- fill_element(:password_field, password)
+ fill_element(:service_password_field, password)
end
def set_transaction_ids(transaction_ids)
- fill_element(:jira_issue_transition_id_field, transaction_ids)
+ fill_element(:service_jira_issue_transition_id_field, transaction_ids)
end
def click_save_changes_button
diff --git a/qa/qa/page/project/show.rb b/qa/qa/page/project/show.rb
index 00298ff9fb5..2354a0d9332 100644
--- a/qa/qa/page/project/show.rb
+++ b/qa/qa/page/project/show.rb
@@ -51,9 +51,12 @@ module QA
element :quick_actions
end
- view 'app/views/projects/tree/_tree_header.html.haml' do
+ view 'app/assets/javascripts/repository/components/breadcrumbs.vue' do
element :add_to_tree
element :new_file_option
+ end
+
+ view 'app/assets/javascripts/repository/components/web_ide_link.vue' do
element :web_ide_button
end
diff --git a/qa/qa/page/project/snippet/new.rb b/qa/qa/page/project/snippet/new.rb
index 1463dfc2c7f..7431d6c1bf8 100644
--- a/qa/qa/page/project/snippet/new.rb
+++ b/qa/qa/page/project/snippet/new.rb
@@ -4,7 +4,8 @@ module QA
module Page
module Project
module Snippet
- class New < Page::Dashboard::Snippet::New
+ class New < Page::Base
+ include Page::Component::NewSnippet
include Component::LazyLoader
view 'app/views/shared/empty_states/_snippets.html.haml' do
element :create_first_snippet_link
diff --git a/qa/qa/page/project/snippet/show.rb b/qa/qa/page/project/snippet/show.rb
new file mode 100644
index 00000000000..f66fa2cbe51
--- /dev/null
+++ b/qa/qa/page/project/snippet/show.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+module QA
+ module Page
+ module Project
+ module Snippet
+ class Show < Page::Base
+ include Page::Component::Snippet
+
+ view 'app/views/projects/notes/_actions.html.haml' do
+ element :edit_comment_button
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/page/project/sub_menus/issues.rb b/qa/qa/page/project/sub_menus/issues.rb
index c15a8ec4cc7..124faf0d346 100644
--- a/qa/qa/page/project/sub_menus/issues.rb
+++ b/qa/qa/page/project/sub_menus/issues.rb
@@ -50,6 +50,14 @@ module QA
end
end
+ def go_to_milestones
+ hover_issues do
+ within_submenu do
+ click_element(:milestones_link)
+ end
+ end
+ end
+
private
def hover_issues
diff --git a/qa/qa/page/project/sub_menus/repository.rb b/qa/qa/page/project/sub_menus/repository.rb
index 38d6b8e50f4..c78c7521b64 100644
--- a/qa/qa/page/project/sub_menus/repository.rb
+++ b/qa/qa/page/project/sub_menus/repository.rb
@@ -14,15 +14,16 @@ module QA
include QA::Page::Project::SubMenus::Common
view 'app/views/layouts/nav/sidebar/_project.html.haml' do
- element :project_menu_repo
+ element :repository_link
element :branches_link
+ element :tags_link
end
end
end
def click_repository
within_sidebar do
- click_element(:project_menu_repo)
+ click_element(:repository_link)
end
end
@@ -34,11 +35,19 @@ module QA
end
end
+ def go_to_repository_tags
+ hover_repository do
+ within_submenu do
+ click_element(:tags_link)
+ end
+ end
+ end
+
private
def hover_repository
within_sidebar do
- find_element(:project_menu_repo).hover
+ find_element(:repository_link).hover
yield
end
diff --git a/qa/qa/page/project/tag/index.rb b/qa/qa/page/project/tag/index.rb
new file mode 100644
index 00000000000..b8f7bd3b0b4
--- /dev/null
+++ b/qa/qa/page/project/tag/index.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+module QA
+ module Page
+ module Project
+ module Tag
+ class Index < Page::Base
+ view 'app/views/projects/tags/index.html.haml' do
+ element :new_tag_button
+ end
+
+ def click_new_tag_button
+ click_element :new_tag_button
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/page/project/tag/new.rb b/qa/qa/page/project/tag/new.rb
new file mode 100644
index 00000000000..dc59c07ec98
--- /dev/null
+++ b/qa/qa/page/project/tag/new.rb
@@ -0,0 +1,41 @@
+# frozen_string_literal: true
+
+module QA
+ module Page
+ module Project
+ module Tag
+ class New < Page::Base
+ view 'app/views/projects/tags/new.html.haml' do
+ element :tag_name_field
+ element :tag_message_field
+ element :release_notes_field
+ element :create_tag_button
+ end
+
+ view 'app/views/shared/_zen.html.haml' do
+ # This partial adds the `release_notes_field` selector passed from 'app/views/projects/tags/new.html.haml'
+ # The checks below ensure that required lines are not removed without updating this page object
+ element :_, "qa_selector = local_assigns.fetch(:qa_selector, '')" # rubocop:disable QA/ElementWithPattern
+ element :_, "text_area_tag attr, current_text, data: { qa_selector: qa_selector }" # rubocop:disable QA/ElementWithPattern
+ end
+
+ def fill_tag_name(text)
+ fill_element(:tag_name_field, text)
+ end
+
+ def fill_tag_message(text)
+ fill_element(:tag_message_field, text)
+ end
+
+ def fill_release_notes(text)
+ fill_element(:release_notes_field, text)
+ end
+
+ def click_create_tag_button
+ click_element :create_tag_button
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/page/project/tag/show.rb b/qa/qa/page/project/tag/show.rb
new file mode 100644
index 00000000000..1974448a7c5
--- /dev/null
+++ b/qa/qa/page/project/tag/show.rb
@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+
+module QA
+ module Page
+ module Project
+ module Tag
+ class Show < Page::Base
+ view 'app/views/projects/tags/show.html.haml' do
+ element :tag_name_content
+ element :tag_message_content
+ element :tag_release_notes_content
+ end
+
+ def has_tag_name?(text)
+ has_element?(:tag_name_content, text: text)
+ end
+
+ def has_tag_message?(text)
+ has_element?(:tag_message_content, text: text)
+ end
+
+ def has_tag_release_notes?(text)
+ has_element?(:tag_release_notes_content, text: text)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/page/project/web_ide/edit.rb b/qa/qa/page/project/web_ide/edit.rb
index 29f431d81df..b46d2d32f1f 100644
--- a/qa/qa/page/project/web_ide/edit.rb
+++ b/qa/qa/page/project/web_ide/edit.rb
@@ -50,6 +50,15 @@ module QA
element :first_file_button
end
+ view 'app/assets/javascripts/vue_shared/components/file_row.vue' do
+ element :file_name_content
+ end
+
+ view 'app/assets/javascripts/ide/components/new_dropdown/index.vue' do
+ element :dropdown_button
+ element :rename_move_button
+ end
+
def has_file?(file_name)
within_element(:file_list) do
page.has_content? file_name
@@ -132,6 +141,14 @@ module QA
fill_element(:file_name_field, file_name)
click_button('Create file')
end
+
+ def rename_file(file_name, new_file_name)
+ click_element(:file_name_content, text: file_name)
+ click_element(:dropdown_button)
+ click_element(:rename_move_button, Page::Component::WebIDE::Modal::CreateNewFile)
+ fill_element(:file_name_field, new_file_name)
+ click_button('Rename file')
+ end
end
end
end
diff --git a/qa/qa/page/project/wiki/edit.rb b/qa/qa/page/project/wiki/edit.rb
index 96301e33733..6f3be904eb3 100644
--- a/qa/qa/page/project/wiki/edit.rb
+++ b/qa/qa/page/project/wiki/edit.rb
@@ -4,7 +4,9 @@ module QA
module Page
module Project
module Wiki
- class Edit < Page::Base
+ class Edit < Base
+ include Wiki::Sidebar
+
view 'app/views/shared/wikis/_form.html.haml' do
element :wiki_title_textbox
element :wiki_content_textarea
@@ -13,6 +15,10 @@ module QA
element :create_page_button
end
+ view 'app/assets/javascripts/pages/shared/wikis/components/delete_wiki_modal.vue' do
+ element :delete_button
+ end
+
def set_title(title)
fill_element :wiki_title_textbox, title
end
@@ -32,6 +38,11 @@ module QA
def click_create_page
click_element :create_page_button
end
+
+ def delete_page
+ click_element :delete_button, Page::Modal::DeleteWiki
+ Page::Modal::DeleteWiki.perform(&:confirm_deletion)
+ end
end
end
end
diff --git a/qa/qa/page/project/wiki/list.rb b/qa/qa/page/project/wiki/list.rb
new file mode 100644
index 00000000000..785847011bf
--- /dev/null
+++ b/qa/qa/page/project/wiki/list.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+module QA
+ module Page
+ module Project
+ module Wiki
+ class List < Base
+ view 'app/views/shared/wikis/_pages_wiki_page.html.haml' do
+ element :wiki_page_link
+ end
+
+ def click_page_link(page_title)
+ click_element :wiki_page_link, page_name: page_title
+ end
+
+ def has_page_listed?(page_title)
+ has_element? :wiki_page_link, page_name: page_title
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/page/project/wiki/show.rb b/qa/qa/page/project/wiki/show.rb
index 7e4e714f4a6..cdd18e420d1 100644
--- a/qa/qa/page/project/wiki/show.rb
+++ b/qa/qa/page/project/wiki/show.rb
@@ -4,13 +4,10 @@ module QA
module Page
module Project
module Wiki
- class Show < Page::Base
+ class Show < Base
+ include Wiki::Sidebar
include Component::LazyLoader
- view 'app/views/shared/wikis/_sidebar.html.haml' do
- element :clone_repository_link
- end
-
view 'app/views/shared/wikis/show.html.haml' do
element :wiki_page_title
element :wiki_page_content
@@ -54,14 +51,6 @@ module QA
click_element(:edit_page_button)
end
- def click_clone_repository
- click_element(:clone_repository_link)
- end
-
- def wiki_text
- find_element(:wiki_page_content).text
- end
-
def has_title?(title)
has_element?(:wiki_page_title, title)
end
@@ -69,6 +58,10 @@ module QA
def has_content?(content)
has_element?(:wiki_page_content, content)
end
+
+ def has_no_page?
+ has_element? :create_first_page_link
+ end
end
end
end
diff --git a/qa/qa/page/project/wiki/sidebar.rb b/qa/qa/page/project/wiki/sidebar.rb
new file mode 100644
index 00000000000..dc27c23e4c3
--- /dev/null
+++ b/qa/qa/page/project/wiki/sidebar.rb
@@ -0,0 +1,42 @@
+# frozen_string_literal: true
+
+module QA
+ module Page
+ module Project
+ module Wiki
+ module Sidebar
+ extend QA::Page::PageConcern
+
+ def self.included(base)
+ super
+
+ base.view 'app/views/shared/wikis/_sidebar.html.haml' do
+ element :clone_repository_link
+ element :view_all_pages_button
+ end
+
+ base.view 'app/views/shared/wikis/_sidebar_wiki_page.html.haml' do
+ element :wiki_page_link
+ end
+ end
+
+ def click_clone_repository
+ click_element(:clone_repository_link)
+ end
+
+ def click_view_all_pages
+ click_element(:view_all_pages_button)
+ end
+
+ def click_page_link(page_title)
+ click_element :wiki_page_link, page_name: page_title
+ end
+
+ def has_page_listed?(page_title)
+ has_element? :wiki_page_link, page_name: page_title
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/resource/api_fabricator.rb b/qa/qa/resource/api_fabricator.rb
index 591aa449219..babdfc96265 100644
--- a/qa/qa/resource/api_fabricator.rb
+++ b/qa/qa/resource/api_fabricator.rb
@@ -83,13 +83,13 @@ module QA
end
def api_get_from(get_path)
- url = Runtime::API::Request.new(api_client, get_path).url
- response = get(url)
+ request = Runtime::API::Request.new(api_client, get_path)
+ response = get(request.url)
if response.code == HTTP_STATUS_SERVER_ERROR
- raise InternalServerError, "Failed to GET #{url} - (#{response.code}): `#{response}`."
+ raise InternalServerError, "Failed to GET #{request.mask_url} - (#{response.code}): `#{response}`."
elsif response.code != HTTP_STATUS_OK
- raise ResourceNotFoundError, "Resource at #{url} could not be found (#{response.code}): `#{response}`."
+ raise ResourceNotFoundError, "Resource at #{request.mask_url} could not be found (#{response.code}): `#{response}`."
end
response
@@ -108,11 +108,11 @@ module QA
end
def api_delete
- url = Runtime::API::Request.new(api_client, api_delete_path).url
- response = delete(url)
+ request = Runtime::API::Request.new(api_client, api_delete_path)
+ response = delete(request.url)
unless [HTTP_STATUS_NO_CONTENT, HTTP_STATUS_ACCEPTED].include? response.code
- raise ResourceNotDeletedError, "Resource at #{url} could not be deleted (#{response.code}): `#{response}`."
+ raise ResourceNotDeletedError, "Resource at #{request.mask_url} could not be deleted (#{response.code}): `#{response}`."
end
response
diff --git a/qa/qa/resource/group.rb b/qa/qa/resource/group.rb
index 850d6205305..75dcb4db55f 100644
--- a/qa/qa/resource/group.rb
+++ b/qa/qa/resource/group.rb
@@ -59,6 +59,10 @@ module QA
"/groups/#{CGI.escape("#{sandbox.path}/#{path}")}"
end
+ def api_put_path
+ "/groups/#{id}"
+ end
+
def api_post_path
'/groups'
end
@@ -75,6 +79,15 @@ module QA
def api_delete_path
"/groups/#{id}"
end
+
+ def set_require_two_factor_authentication(value:)
+ put_body = { require_two_factor_authentication: value }
+ response = put Runtime::API::Request.new(api_client, api_put_path).url, put_body
+
+ unless response.code == HTTP_STATUS_OK
+ raise ResourceUpdateFailedError, "Could not update require_two_factor_authentication to #{value}. Request returned (#{response.code}): `#{response}`."
+ end
+ end
end
end
end
diff --git a/qa/qa/resource/group_milestone.rb b/qa/qa/resource/group_milestone.rb
new file mode 100644
index 00000000000..1fb07fdbd0b
--- /dev/null
+++ b/qa/qa/resource/group_milestone.rb
@@ -0,0 +1,57 @@
+# frozen_string_literal: true
+
+module QA
+ module Resource
+ class GroupMilestone < Base
+ attr_writer :start_date, :due_date
+
+ attribute :id
+ attribute :title
+ attribute :description
+
+ attribute :group do
+ Group.fabricate_via_api! do |resource|
+ resource.name = 'group-with-milestone'
+ end
+ end
+
+ def initialize
+ @title = "group-milestone-#{SecureRandom.hex(4)}"
+ @description = "My awesome group milestone."
+ end
+
+ def api_get_path
+ "/groups/#{group.id}/milestones/#{id}"
+ end
+
+ def api_post_path
+ "/groups/#{group.id}/milestones"
+ end
+
+ def api_post_body
+ {
+ title: title,
+ description: description
+ }.tap do |hash|
+ hash[:start_date] = @start_date if @start_date
+ hash[:due_date] = @due_date if @due_date
+ end
+ end
+
+ def fabricate!
+ group.visit!
+
+ Page::Group::Menu.perform(&:go_to_milestones)
+ Page::Group::Milestone::Index.perform(&:click_new_milestone_link)
+
+ Page::Group::Milestone::New.perform do |new_milestone|
+ new_milestone.set_title(@title)
+ new_milestone.set_description(@description)
+ new_milestone.set_start_date(@start_date) if @start_date
+ new_milestone.set_due_date(@due_date) if @due_date
+ new_milestone.click_create_milestone_button
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/resource/issue.rb b/qa/qa/resource/issue.rb
index b4295a35263..d96d8d744d2 100644
--- a/qa/qa/resource/issue.rb
+++ b/qa/qa/resource/issue.rb
@@ -34,6 +34,7 @@ module QA
Page::Project::Issue::New.perform do |new_page|
new_page.fill_title(@title)
new_page.fill_description(@description)
+ new_page.choose_milestone(@milestone) if @milestone
new_page.create_new_issue
end
end
diff --git a/qa/qa/resource/members.rb b/qa/qa/resource/members.rb
index 38a620a5427..4ebed37ca23 100644
--- a/qa/qa/resource/members.rb
+++ b/qa/qa/resource/members.rb
@@ -8,10 +8,14 @@ module QA
#
module Members
def add_member(user, access_level = AccessLevel::DEVELOPER)
+ QA::Runtime::Logger.debug(%Q[Adding user #{user.username} to #{full_path} #{self.class.name}])
+
post Runtime::API::Request.new(api_client, api_members_path).url, { user_id: user.id, access_level: access_level }
end
def remove_member(user)
+ QA::Runtime::Logger.debug(%Q[Removing user #{user.username} from #{full_path} #{self.class.name}])
+
delete Runtime::API::Request.new(api_client, "#{api_members_path}/#{user.id}").url
end
diff --git a/qa/qa/resource/project.rb b/qa/qa/resource/project.rb
index 645f4e97ee0..358e87b0eb9 100644
--- a/qa/qa/resource/project.rb
+++ b/qa/qa/resource/project.rb
@@ -96,7 +96,11 @@ module QA
end
def has_file?(file_path)
- repository_tree.any? { |file| file[:path] == file_path }
+ response = repository_tree
+
+ raise ResourceNotFoundError, "#{response[:message]}" if response.is_a?(Hash) && response.has_key?(:message)
+
+ response.any? { |file| file[:path] == file_path }
end
def api_get_path
diff --git a/qa/qa/resource/project_milestone.rb b/qa/qa/resource/project_milestone.rb
index 385b9f0c96b..c9218e03e35 100644
--- a/qa/qa/resource/project_milestone.rb
+++ b/qa/qa/resource/project_milestone.rb
@@ -7,6 +7,7 @@ module QA
attribute :id
attribute :title
+ attribute :description
attribute :project do
Project.fabricate_via_api! do |resource|
@@ -16,6 +17,7 @@ module QA
def initialize
@title = "project-milestone-#{SecureRandom.hex(4)}"
+ @description = "My awesome project milestone."
end
def api_get_path
@@ -28,12 +30,28 @@ module QA
def api_post_body
{
- title: title
+ title: title,
+ description: description
}.tap do |hash|
hash[:start_date] = @start_date if @start_date
hash[:due_date] = @due_date if @due_date
end
end
+
+ def fabricate!
+ project.visit!
+
+ Page::Project::Menu.perform(&:go_to_milestones)
+ Page::Project::Milestone::Index.perform(&:click_new_milestone_link)
+
+ Page::Project::Milestone::New.perform do |new_milestone|
+ new_milestone.set_title(@title)
+ new_milestone.set_description(@description)
+ new_milestone.set_start_date(@start_date) if @start_date
+ new_milestone.set_due_date(@due_date) if @due_date
+ new_milestone.click_create_milestone_button
+ end
+ end
end
end
end
diff --git a/qa/qa/resource/repository/commit.rb b/qa/qa/resource/repository/commit.rb
index e3fb5bf486d..3243eacdb28 100644
--- a/qa/qa/resource/repository/commit.rb
+++ b/qa/qa/resource/repository/commit.rb
@@ -29,6 +29,30 @@ module QA
@add_files = files
end
+ def add_directory(dir)
+ raise "Must set directory as a Pathname" unless dir.is_a?(Pathname)
+
+ files_to_add = []
+
+ dir.each_child do |child|
+ case child.ftype?
+ when "file"
+ files_to_add.append({
+ file_path: child.to_s,
+ content: child.read
+ })
+ when "directory"
+ add_directory(child)
+ else
+ continue
+ end
+ end
+
+ validate_files!(files_to_add)
+
+ @add_files.merge(files_to_add)
+ end
+
def update_files(files)
validate_files!(files)
diff --git a/qa/qa/resource/sandbox.rb b/qa/qa/resource/sandbox.rb
index 032ff65c58b..b351d92092f 100644
--- a/qa/qa/resource/sandbox.rb
+++ b/qa/qa/resource/sandbox.rb
@@ -14,6 +14,7 @@ module QA
attribute :id
attribute :runners_token
attribute :name
+ attribute :full_path
def initialize
@path = Runtime::Namespace.sandbox_name
diff --git a/qa/qa/resource/user.rb b/qa/qa/resource/user.rb
index e6dbe3faa61..41908a71cf9 100644
--- a/qa/qa/resource/user.rb
+++ b/qa/qa/resource/user.rb
@@ -117,7 +117,10 @@ module QA
user.password = password
end
else
- self.fabricate!
+ self.fabricate! do |user|
+ user.username = username if username
+ user.password = password if password
+ end
end
end
diff --git a/qa/qa/resource/wiki/project_page.rb b/qa/qa/resource/wiki/project_page.rb
index 5d0a0a37765..8bcc4bfe220 100644
--- a/qa/qa/resource/wiki/project_page.rb
+++ b/qa/qa/resource/wiki/project_page.rb
@@ -34,7 +34,7 @@ module QA
rescue ResourceURLMissingError
# TODO
# workaround
- project.web_url.concat("/-/wikis/#{slug}")
+ "#{project.web_url}/-/wikis/#{slug}"
end
def api_get_path
diff --git a/qa/qa/runtime/env.rb b/qa/qa/runtime/env.rb
index 677fba7ced7..804ebf27851 100644
--- a/qa/qa/runtime/env.rb
+++ b/qa/qa/runtime/env.rb
@@ -1,6 +1,7 @@
# frozen_string_literal: true
require 'gitlab/qa'
+require 'uri'
module QA
module Runtime
@@ -23,10 +24,39 @@ module QA
SUPPORTED_FEATURES
end
- def dot_com?
- Runtime::Scenario.gitlab_address.include?(".com")
+ def address_matches?(*options)
+ return false unless Runtime::Scenario.attributes[:gitlab_address]
+
+ opts = {}
+ opts[:domain] = '.+'
+ opts[:tld] = '.com'
+
+ uri = URI(Runtime::Scenario.gitlab_address)
+
+ if options.any?
+ options.each do |option|
+ opts[:domain] = 'gitlab' if option == :production
+
+ if option.is_a?(Hash) && !option[:subdomain].nil?
+ opts.merge!(option)
+
+ opts[:subdomain] = case option[:subdomain]
+ when Array
+ "(#{option[:subdomain].join("|")})."
+ when Regexp
+ option[:subdomain]
+ else
+ "(#{option[:subdomain]})."
+ end
+ end
+ end
+ end
+
+ uri.host.match?(/^#{opts[:subdomain]}#{opts[:domain]}#{opts[:tld]}$/)
end
+ alias_method :dot_com?, :address_matches?
+
def additional_repository_storage
ENV['QA_ADDITIONAL_REPOSITORY_STORAGE']
end
@@ -194,6 +224,14 @@ module QA
ENV['GITLAB_QA_PASSWORD_6']
end
+ def gitlab_qa_2fa_owner_username_1
+ ENV['GITLAB_QA_2FA_OWNER_USERNAME_1'] || 'gitlab-qa-2fa-owner-user1'
+ end
+
+ def gitlab_qa_2fa_owner_password_1
+ ENV['GITLAB_QA_2FA_OWNER_PASSWORD_1']
+ end
+
def gitlab_qa_1p_email
ENV['GITLAB_QA_1P_EMAIL']
end
diff --git a/qa/qa/service/praefect_manager.rb b/qa/qa/service/praefect_manager.rb
index d8fa72456ad..a0433689e99 100644
--- a/qa/qa/service/praefect_manager.rb
+++ b/qa/qa/service/praefect_manager.rb
@@ -6,21 +6,177 @@ module QA
include Service::Shellout
def initialize
+ @gitlab = 'gitlab-gitaly-ha'
@praefect = 'praefect'
- @first_node = 'gitaly1'
- @second_node = 'gitaly2'
- @primary_node = @first_node
- @secondary_node = @second_node
+ @postgres = 'postgres'
+ @primary_node = 'gitaly1'
+ @secondary_node = 'gitaly2'
+ @tertiary_node = 'gitaly3'
+ @virtual_storage = 'default'
end
- def stop_primary_node
- shell "docker stop #{@primary_node}"
- @secondary_node, @primary_node = @primary_node, @secondary_node
+ def enable_writes
+ shell "docker exec #{@praefect} bash -c '/opt/gitlab/embedded/bin/praefect -config /var/opt/gitlab/praefect/config.toml enable-writes -virtual-storage #{@virtual_storage}'"
end
- def reset
- shell "docker start #{@primary_node}"
- shell "docker start #{@secondary_node}"
+ def replicated?(project_id)
+ shell %(docker exec gitlab-gitaly-ha bash -c 'gitlab-rake "gitlab:praefect:replicas[#{project_id}]"') do |line|
+ # The output of the rake task looks something like this:
+ #
+ # Project name | gitaly1 (primary) | gitaly2 | gitaly3
+ # ----------------------------------------------------------------------------------------------------------------------------------------------------------------
+ # gitaly_cluster-3aff1f2bd14e6c98 | 23c4422629234d62b62adacafd0a33a8364e8619 | 23c4422629234d62b62adacafd0a33a8364e8619 | 23c4422629234d62b62adacafd0a33a8364e8619
+ #
+ # We want to confirm that the checksums are identical
+ break line.split('|').map(&:strip)[1..3].uniq.one? if line.start_with?("gitaly_cluster")
+ end
+ end
+
+ def start_praefect
+ start_node(@praefect)
+ end
+
+ def stop_praefect
+ stop_node(@praefect)
+ end
+
+ def start_node(name)
+ shell "docker start #{name}"
+ end
+
+ def stop_node(name)
+ shell "docker stop #{name}"
+ end
+
+ def trigger_failover_by_stopping_primary_node
+ stop_node(@primary_node)
+ end
+
+ def clear_replication_queue
+ QA::Runtime::Logger.debug("Clearing the replication queue")
+ shell <<~CMD
+ docker exec --env PGPASSWORD=SQL_PASSWORD #{@postgres} \
+ bash -c "psql -U postgres -d praefect_production -h postgres.test \
+ -c \\"delete from replication_queue_job_lock; delete from replication_queue_lock; delete from replication_queue;\\""
+ CMD
+ end
+
+ def create_stalled_replication_queue
+ QA::Runtime::Logger.debug("Setting jobs in replication queue to `in_progress` and acquiring locks")
+ shell <<~CMD
+ docker exec --env PGPASSWORD=SQL_PASSWORD #{@postgres} \
+ bash -c "psql -U postgres -d praefect_production -h postgres.test \
+ -c \\"update replication_queue set state = 'in_progress';
+ insert into replication_queue_job_lock (job_id, lock_id, triggered_at)
+ select id, rq.lock_id, created_at from replication_queue rq
+ left join replication_queue_job_lock rqjl on rq.id = rqjl.job_id
+ where state = 'in_progress' and rqjl.job_id is null;
+ update replication_queue_lock set acquired = 't';\\""
+ CMD
+ end
+
+ def replication_queue_lock_count
+ result = []
+ cmd = <<~CMD
+ docker exec --env PGPASSWORD=SQL_PASSWORD #{@postgres} \
+ bash -c "psql -U postgres -d praefect_production -h postgres.test \
+ -c \\"select count(*) from replication_queue_lock where acquired = 't';\\""
+ CMD
+ shell cmd do |line|
+ result << line
+ end
+ # The result looks like:
+ # count
+ # -----
+ # 1
+ result[2].to_i
+ end
+
+ def reset_cluster
+ start_node(@praefect)
+ start_node(@primary_node)
+ start_node(@secondary_node)
+ start_node(@tertiary_node)
+ enable_writes
+ end
+
+ def wait_for_praefect
+ wait_until_shell_command_matches(
+ "docker exec #{@praefect} bash -c 'cat /var/log/gitlab/praefect/current'",
+ /listening at tcp address/
+ )
+ end
+
+ def wait_for_sql_ping
+ wait_until_shell_command_matches(
+ "docker exec #{@praefect} bash -c '/opt/gitlab/embedded/bin/praefect -config /var/opt/gitlab/praefect/config.toml sql-ping'",
+ /praefect sql-ping: OK/
+ )
+ end
+
+ def wait_for_storage_nodes
+ nodes_confirmed = {
+ @primary_node => false,
+ @secondary_node => false,
+ @tertiary_node => false
+ }
+
+ wait_until_shell_command("docker exec #{@praefect} bash -c '/opt/gitlab/embedded/bin/praefect -config /var/opt/gitlab/praefect/config.toml dial-nodes'") do |line|
+ QA::Runtime::Logger.info(line.chomp)
+
+ nodes_confirmed.each_key do |node|
+ nodes_confirmed[node] = true if line =~ /SUCCESS: confirmed Gitaly storage "#{node}" in virtual storages \[#{@virtual_storage}\] is served/
+ end
+
+ nodes_confirmed.values.all?
+ end
+ end
+
+ def wait_for_gitaly_check
+ storage_ok = false
+ check_finished = false
+
+ wait_until_shell_command("docker exec #{@gitlab} bash -c 'gitlab-rake gitlab:gitaly:check'") do |line|
+ QA::Runtime::Logger.info(line.chomp)
+
+ storage_ok = true if line =~ /Gitaly: ... #{@virtual_storage} ... OK/
+ check_finished = true if line =~ /Checking Gitaly ... Finished/
+
+ storage_ok && check_finished
+ end
+ end
+
+ def wait_for_gitlab_shell_check
+ wait_until_shell_command_matches(
+ "docker exec #{@gitlab} bash -c 'gitlab-rake gitlab:gitlab_shell:check'",
+ /Checking GitLab Shell ... Finished/
+ )
+ end
+
+ def wait_for_reliable_connection
+ wait_for_praefect
+ wait_for_sql_ping
+ wait_for_storage_nodes
+ wait_for_gitaly_check
+ wait_for_gitlab_shell_check
+ end
+
+ private
+
+ def wait_until_shell_command(cmd)
+ Support::Waiter.wait_until do
+ shell cmd do |line|
+ break true if yield line
+ end
+ end
+ end
+
+ def wait_until_shell_command_matches(cmd, regex)
+ wait_until_shell_command(cmd) do |line|
+ QA::Runtime::Logger.info(line.chomp)
+
+ line =~ regex
+ end
end
end
end
diff --git a/qa/qa/service/shellout.rb b/qa/qa/service/shellout.rb
index 217df669db3..6efe50c4ae2 100644
--- a/qa/qa/service/shellout.rb
+++ b/qa/qa/service/shellout.rb
@@ -19,6 +19,13 @@ module QA
Open3.popen2e(*command) do |stdin, out, wait|
stdin.puts(stdin_data) if stdin_data
stdin.close if stdin_data
+
+ if block_given?
+ out.each do |line|
+ yield line
+ end
+ end
+
out.each_char { |char| print char }
if wait.value.exited? && wait.value.exitstatus.nonzero?
diff --git a/qa/qa/specs/features/api/1_manage/rate_limits_spec.rb b/qa/qa/specs/features/api/1_manage/rate_limits_spec.rb
index 1bf435014af..d836bdde9d5 100644
--- a/qa/qa/specs/features/api/1_manage/rate_limits_spec.rb
+++ b/qa/qa/specs/features/api/1_manage/rate_limits_spec.rb
@@ -3,7 +3,7 @@
require 'airborne'
module QA
- context 'Manage with IP rate limits', :requires_admin do
+ RSpec.describe 'Manage with IP rate limits', :requires_admin do
describe 'Users API' do
let(:api_client) { Runtime::API::Client.new(:gitlab, ip_limits: true) }
let(:request) { Runtime::API::Request.new(api_client, '/users') }
diff --git a/qa/qa/specs/features/api/1_manage/users_spec.rb b/qa/qa/specs/features/api/1_manage/users_spec.rb
index fbc26e81b69..0bd52cbdaa4 100644
--- a/qa/qa/specs/features/api/1_manage/users_spec.rb
+++ b/qa/qa/specs/features/api/1_manage/users_spec.rb
@@ -3,13 +3,10 @@
require 'airborne'
module QA
- context 'Manage' do
+ RSpec.describe 'Manage' do
describe 'Users API' do
- before(:context) do
- @api_client = Runtime::API::Client.new(:gitlab)
- end
-
- let(:request) { Runtime::API::Request.new(@api_client, '/users') }
+ let(:api_client) { Runtime::API::Client.new(:gitlab) }
+ let(:request) { Runtime::API::Request.new(api_client, '/users') }
it 'GET /users' do
get request.url
diff --git a/qa/qa/specs/features/api/2_plan/.gitkeep b/qa/qa/specs/features/api/2_plan/.gitkeep
deleted file mode 100644
index e69de29bb2d..00000000000
--- a/qa/qa/specs/features/api/2_plan/.gitkeep
+++ /dev/null
diff --git a/qa/qa/specs/features/api/2_plan/closes_issue_via_pushing_a_commit_spec.rb b/qa/qa/specs/features/api/2_plan/closes_issue_via_pushing_a_commit_spec.rb
index 58d716f759e..d72389f1d9d 100644
--- a/qa/qa/specs/features/api/2_plan/closes_issue_via_pushing_a_commit_spec.rb
+++ b/qa/qa/specs/features/api/2_plan/closes_issue_via_pushing_a_commit_spec.rb
@@ -3,7 +3,7 @@
require 'airborne'
module QA
- context 'Plan' do
+ RSpec.describe 'Plan' do
include Support::Api
describe 'Issue' do
diff --git a/qa/qa/specs/features/api/3_create/repository/changing_repository_storage_spec.rb b/qa/qa/specs/features/api/3_create/repository/changing_repository_storage_spec.rb
index d5ab6a3544d..11e7db5b097 100644
--- a/qa/qa/specs/features/api/3_create/repository/changing_repository_storage_spec.rb
+++ b/qa/qa/specs/features/api/3_create/repository/changing_repository_storage_spec.rb
@@ -1,8 +1,8 @@
# frozen_string_literal: true
module QA
- context 'Create' do
- describe 'Changing Gitaly repository storage', :orchestrated, :requires_admin do
+ RSpec.describe 'Create' do
+ describe 'Changing Gitaly repository storage', :requires_admin do
shared_examples 'repository storage move' do
it 'confirms a `finished` status after moving project repository storage' do
expect(project).to have_file('README.md')
@@ -24,7 +24,7 @@ module QA
end
end
- context 'when moving from one Gitaly storage to another', :repository_storage do
+ context 'when moving from one Gitaly storage to another', :orchestrated, :repository_storage do
let(:project) do
Resource::Project.fabricate_via_api! do |project|
project.name = 'repo-storage-move-status'
@@ -36,7 +36,9 @@ module QA
it_behaves_like 'repository storage move'
end
- context 'when moving from Gitaly to Gitaly Cluster', :requires_praefect do
+ # Note: This test doesn't have the :orchestrated tag because it runs in the Test::Integration::Praefect
+ # scenario with other tests that aren't considered orchestrated.
+ context 'when moving from Gitaly to Gitaly Cluster', :requires_praefect, quarantine: { issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/227127', type: :investigating } do
let(:project) do
Resource::Project.fabricate_via_api! do |project|
project.name = 'repo-storage-move'
diff --git a/qa/qa/specs/features/api/3_create/repository/files_spec.rb b/qa/qa/specs/features/api/3_create/repository/files_spec.rb
index 92858ba4107..1a74b2c9da7 100644
--- a/qa/qa/specs/features/api/3_create/repository/files_spec.rb
+++ b/qa/qa/specs/features/api/3_create/repository/files_spec.rb
@@ -4,7 +4,7 @@ require 'airborne'
require 'securerandom'
module QA
- describe 'API basics' do
+ RSpec.describe 'API basics' do
before(:context) do
@api_client = Runtime::API::Client.new(:gitlab)
end
diff --git a/qa/qa/specs/features/api/3_create/repository/praefect_replication_queue_spec.rb b/qa/qa/specs/features/api/3_create/repository/praefect_replication_queue_spec.rb
new file mode 100644
index 00000000000..a4040a46b84
--- /dev/null
+++ b/qa/qa/specs/features/api/3_create/repository/praefect_replication_queue_spec.rb
@@ -0,0 +1,64 @@
+# frozen_string_literal: true
+
+require 'parallel'
+
+module QA
+ RSpec.describe 'Create' do
+ context 'Gitaly Cluster replication queue', :orchestrated, :gitaly_ha, :skip_live_env do
+ let(:praefect_manager) { Service::PraefectManager.new }
+ let(:project) do
+ Resource::Project.fabricate! do |project|
+ project.name = "gitaly_cluster"
+ project.initialize_with_readme = true
+ end
+ end
+
+ after do
+ praefect_manager.reset_cluster
+ praefect_manager.clear_replication_queue
+ end
+
+ it 'allows replication of different repository after interruption' do
+ # We want to fill the replication queue with 10 `in_progress` jobs,
+ # while a lock has been acquired, which is when the problem occurred
+ # as reported in https://gitlab.com/gitlab-org/gitaly/-/issues/2801
+ #
+ # We'll do this by creating 10 branches and pushing them all at once,
+ # and then stop Praefect when a lock is acquired, set all the jobs
+ # to `in_progress`, and create a job lock for each one.
+ queue_size_target = 10
+
+ Git::Repository.perform do |repository|
+ repository.uri = project.repository_http_location.uri
+ repository.use_default_credentials
+ repository.clone
+ repository.configure_identity('GitLab QA', 'root@gitlab.com')
+ 1.upto(queue_size_target) do |i|
+ repository.checkout("branch#{i}", new_branch: true)
+ repository.commit_file("file#{i}", SecureRandom.random_bytes(10000000), "Add file#{i}")
+ end
+ repository.push_all_branches
+ end
+
+ count = 0
+ while count < 1
+ count = praefect_manager.replication_queue_lock_count
+ QA::Runtime::Logger.debug("Lock count: #{count}")
+ end
+
+ praefect_manager.stop_praefect
+ praefect_manager.create_stalled_replication_queue
+
+ praefect_manager.start_praefect
+ praefect_manager.wait_for_reliable_connection
+
+ # Create a new project, push to it, and check that replication occurs
+ project_push = Resource::Repository::ProjectPush.fabricate! do |push|
+ push.project_name = "gitaly_cluster"
+ end
+
+ expect(praefect_manager.replicated?(project_push.project.id)).to be true
+ end
+ end
+ end
+end
diff --git a/qa/qa/specs/features/api/3_create/repository/project_archive_compare_spec.rb b/qa/qa/specs/features/api/3_create/repository/project_archive_compare_spec.rb
index 3ad56e21ad4..e66e8f8c9d4 100644
--- a/qa/qa/specs/features/api/3_create/repository/project_archive_compare_spec.rb
+++ b/qa/qa/specs/features/api/3_create/repository/project_archive_compare_spec.rb
@@ -5,33 +5,36 @@ require 'securerandom'
require 'digest'
module QA
- context 'Create' do
+ RSpec.describe 'Create' do
describe 'Compare archives of different user projects with the same name and check they\'re different' do
include Support::Api
+ let(:project_name) { "project-archive-download-#{SecureRandom.hex(8)}" }
- before do
- @project_name = "project-archive-download-#{SecureRandom.hex(8)}"
- @archive_types = %w(tar.gz tar.bz2 tar zip)
- @users = {
+ let(:archive_types) { %w(tar.gz tar.bz2 tar zip) }
+
+ let(:users) do
+ {
user1: { username: Runtime::Env.gitlab_qa_username_1, password: Runtime::Env.gitlab_qa_password_1 },
user2: { username: Runtime::Env.gitlab_qa_username_2, password: Runtime::Env.gitlab_qa_password_2 }
}
+ end
- @users.each do |_, user_info|
+ before do
+ users.each do |_, user_info|
user_info[:user] = Resource::User.fabricate_or_use(user_info[:username], user_info[:password])
user_info[:api_client] = Runtime::API::Client.new(:gitlab, user: user_info[:user])
user_info[:api_client].personal_access_token
- user_info[:project] = create_project(user_info[:user], user_info[:api_client], @project_name)
+ user_info[:project] = create_project(user_info[:user], user_info[:api_client], project_name)
end
end
it 'download archives of each user project then check they are different' do
archive_checksums = {}
- @users.each do |user_key, user_info|
+ users.each do |user_key, user_info|
archive_checksums[user_key] = {}
- @archive_types.each do |type|
+ archive_types.each do |type|
archive_path = download_project_archive_via_api(user_info[:api_client], user_info[:project], type).path
archive_checksums[user_key][type] = Digest::MD5.hexdigest(File.read(archive_path))
end
diff --git a/qa/qa/specs/features/api/4_verify/.gitkeep b/qa/qa/specs/features/api/4_verify/.gitkeep
deleted file mode 100644
index e69de29bb2d..00000000000
--- a/qa/qa/specs/features/api/4_verify/.gitkeep
+++ /dev/null
diff --git a/qa/qa/specs/features/api/4_verify/pipeline_deletion_spec.rb b/qa/qa/specs/features/api/4_verify/pipeline_deletion_spec.rb
new file mode 100644
index 00000000000..a406fa409d5
--- /dev/null
+++ b/qa/qa/specs/features/api/4_verify/pipeline_deletion_spec.rb
@@ -0,0 +1,63 @@
+# frozen_string_literal: true
+
+module QA
+ RSpec.describe 'Verify' do
+ include Support::Api
+
+ let(:api_client) { Runtime::API::Client.new(:gitlab) }
+
+ describe 'Pipeline', :runner do
+ let(:project) do
+ Resource::Project.fabricate_via_api! do |project|
+ project.name = 'project-with-pipeline'
+ end
+ end
+
+ let!(:runner) do
+ Resource::Runner.fabricate! do |runner|
+ runner.project = project
+ runner.name = project.name
+ runner.tags = [project.name]
+ end
+ end
+
+ let!(:ci_file) do
+ Resource::Repository::Commit.fabricate_via_api! do |commit|
+ commit.project = project
+ commit.commit_message = 'Add .gitlab-ci.yml'
+ commit.add_files(
+ [
+ {
+ file_path: '.gitlab-ci.yml',
+ content: <<~YAML
+ job1:
+ tags:
+ - #{project.name}
+ script: echo 'OK'
+ YAML
+ }
+ ]
+ )
+ end
+ end
+
+ let!(:pipeline_id) do
+ pipeline_create_request = Runtime::API::Request.new(api_client, "/projects/#{project.id}/pipeline?ref=master")
+ JSON.parse(post(pipeline_create_request.url, nil))['id']
+ end
+
+ let(:pipeline_data_request) { Runtime::API::Request.new(api_client, "/projects/#{project.id}/pipelines/#{pipeline_id}") }
+
+ after do
+ runner.remove_via_api!
+ end
+
+ context 'when deleted via API' do
+ it 'is not found' do
+ delete(pipeline_data_request.url)
+ expect(JSON.parse(get(pipeline_data_request.url))['message'].downcase).to have_content('404 not found')
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/specs/features/api/1_manage/.gitkeep b/qa/qa/specs/features/api/8_monitor/.gitkeep
index e69de29bb2d..e69de29bb2d 100644
--- a/qa/qa/specs/features/api/1_manage/.gitkeep
+++ b/qa/qa/specs/features/api/8_monitor/.gitkeep
diff --git a/qa/qa/specs/features/browser_ui/1_manage/group/create_group_with_mattermost_team_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/group/create_group_with_mattermost_team_spec.rb
index 7143cc574b8..5b89bcc7375 100644
--- a/qa/qa/specs/features/browser_ui/1_manage/group/create_group_with_mattermost_team_spec.rb
+++ b/qa/qa/specs/features/browser_ui/1_manage/group/create_group_with_mattermost_team_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
- context 'Configure', :orchestrated, :mattermost do
+ RSpec.describe 'Configure', :orchestrated, :mattermost do
describe 'Mattermost support' do
it 'user creates a group with a mattermost team' do
Flow::Login.sign_in
diff --git a/qa/qa/specs/features/browser_ui/1_manage/group/transfer_project_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/group/transfer_project_spec.rb
index 9eab03323a8..f307d286587 100644
--- a/qa/qa/specs/features/browser_ui/1_manage/group/transfer_project_spec.rb
+++ b/qa/qa/specs/features/browser_ui/1_manage/group/transfer_project_spec.rb
@@ -1,24 +1,32 @@
# frozen_string_literal: true
module QA
- context 'Manage' do
+ RSpec.describe 'Manage' do
describe 'Project transfer between groups' do
- it 'user transfers a project between groups' do
- Flow::Login.sign_in
-
- source_group = Resource::Group.fabricate_via_api! do |group|
+ let(:source_group) do
+ Resource::Group.fabricate_via_api! do |group|
group.path = 'source-group'
end
+ end
- target_group = Resource::Group.fabricate_via_api! do |group|
+ let(:target_group) do
+ Resource::Group.fabricate_via_api! do |group|
group.path = 'target-group'
end
+ end
- project = Resource::Project.fabricate_via_api! do |project|
+ let(:project) do
+ Resource::Project.fabricate_via_api! do |project|
project.group = source_group
project.name = 'transfer-project'
project.initialize_with_readme = true
end
+ end
+
+ let(:edited_readme_content) { 'Here is the edited content.' }
+
+ before do
+ Flow::Login.sign_in
project.visit!
@@ -28,14 +36,14 @@ module QA
Page::File::Show.perform(&:click_edit)
- edited_readme_content = 'Here is the edited content.'
-
Page::File::Edit.perform do |file|
file.remove_content
file.add_content(edited_readme_content)
file.commit_changes
end
+ end
+ it 'user transfers a project between groups' do
Page::File::Show.perform(&:go_to_general_settings)
Page::Project::Settings::Main.perform(&:expand_advanced_settings)
diff --git a/qa/qa/specs/features/browser_ui/1_manage/login/log_in_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/login/log_in_spec.rb
index 1050005a231..9cb765705e0 100644
--- a/qa/qa/specs/features/browser_ui/1_manage/login/log_in_spec.rb
+++ b/qa/qa/specs/features/browser_ui/1_manage/login/log_in_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
- context 'Manage', :smoke do
+ RSpec.describe 'Manage', :smoke do
describe 'basic user login' do
it 'user logs in using basic credentials and logs out' do
Flow::Login.sign_in
diff --git a/qa/qa/specs/features/browser_ui/1_manage/login/log_in_with_2fa_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/login/log_in_with_2fa_spec.rb
new file mode 100644
index 00000000000..d0ab945124b
--- /dev/null
+++ b/qa/qa/specs/features/browser_ui/1_manage/login/log_in_with_2fa_spec.rb
@@ -0,0 +1,110 @@
+# frozen_string_literal: true
+
+module QA
+ context 'Manage', :requires_admin, :skip_live_env do
+ describe '2FA' do
+ let(:owner_user) do
+ Resource::User.fabricate_or_use(Runtime::Env.gitlab_qa_2fa_owner_username_1, Runtime::Env.gitlab_qa_2fa_owner_password_1)
+ end
+
+ let(:sandbox_group) do
+ Resource::Sandbox.fabricate! do |sandbox_group|
+ sandbox_group.path = "gitlab-qa-2fa-sandbox-group"
+ sandbox_group.api_client = owner_api_client
+ end
+ end
+
+ let(:group) do
+ QA::Resource::Group.fabricate_via_api! do |group|
+ group.sandbox = sandbox_group
+ group.api_client = owner_api_client
+ group.name = 'group-with-2fa'
+ end
+ end
+
+ let(:developer_user) do
+ Resource::User.fabricate_via_api! do |resource|
+ resource.api_client = admin_api_client
+ end
+ end
+
+ let(:two_fa_expected_text) { /The group settings for.*require you to enable Two-Factor Authentication for your account.*You need to do this before/ }
+
+ before do
+ group.add_member(developer_user, Resource::Members::AccessLevel::DEVELOPER)
+ end
+
+ it 'allows enforcing 2FA via UI and logging in with 2FA' do
+ enforce_two_factor_authentication_on_group(group)
+
+ enable_two_factor_authentication_for_user(developer_user)
+
+ Flow::Login.sign_in(as: developer_user, skip_page_validation: true)
+
+ Page::Main::TwoFactorAuth.perform do |two_fa_auth|
+ two_fa_auth.set_2fa_code('000000')
+ two_fa_auth.click_verify_code_button
+ end
+
+ expect(page).to have_text('Invalid two-factor code')
+
+ Page::Main::TwoFactorAuth.perform do |two_fa_auth|
+ two_fa_auth.set_2fa_code(@otp.fresh_otp)
+ two_fa_auth.click_verify_code_button
+ end
+
+ expect(Page::Main::Menu.perform(&:signed_in?)).to be_truthy
+ end
+
+ after do
+ group.set_require_two_factor_authentication(value: 'false')
+ group.remove_via_api! do |resource|
+ resource.api_client = admin_api_client
+ end
+ developer_user.remove_via_api!
+ end
+
+ def admin_api_client
+ @admin_api_client ||= Runtime::API::Client.as_admin
+ end
+
+ def owner_api_client
+ @owner_api_client ||= Runtime::API::Client.new(:gitlab, user: owner_user)
+ end
+
+ # We are intentionally using the UI to enforce 2FA to exercise the flow with UI.
+ # Any future tests should use the API for this purpose.
+ def enforce_two_factor_authentication_on_group(group)
+ Flow::Login.while_signed_in(as: owner_user) do
+ group.visit!
+
+ Page::Group::Menu.perform(&:click_group_general_settings_item)
+ Page::Group::Settings::General.perform(&:set_require_2fa_enabled)
+
+ expect(page).to have_text(two_fa_expected_text)
+
+ Page::Profile::TwoFactorAuth.perform(&:click_configure_it_later_button)
+
+ expect(page).not_to have_text(two_fa_expected_text)
+ end
+ end
+
+ def enable_two_factor_authentication_for_user(user)
+ Flow::Login.while_signed_in(as: user) do
+ expect(page).to have_text(two_fa_expected_text)
+
+ Page::Profile::TwoFactorAuth.perform do |two_fa_auth|
+ @otp = QA::Support::OTP.new(two_fa_auth.otp_secret_content)
+
+ two_fa_auth.set_pin_code(@otp.fresh_otp)
+ two_fa_auth.click_register_2fa_app_button
+
+ expect(two_fa_auth).to have_text('Congratulations! You have enabled Two-factor Authentication!')
+
+ two_fa_auth.click_proceed_button
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/specs/features/browser_ui/1_manage/login/log_into_gitlab_via_ldap_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/login/log_into_gitlab_via_ldap_spec.rb
index 46a0f1a4c8b..5933637045f 100644
--- a/qa/qa/specs/features/browser_ui/1_manage/login/log_into_gitlab_via_ldap_spec.rb
+++ b/qa/qa/specs/features/browser_ui/1_manage/login/log_into_gitlab_via_ldap_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
- context 'Manage', :orchestrated, :ldap_no_tls, :ldap_tls do
+ RSpec.describe 'Manage', :orchestrated, :ldap_no_tls, :ldap_tls do
describe 'LDAP login' do
it 'user logs into GitLab using LDAP credentials' do
Flow::Login.sign_in
diff --git a/qa/qa/specs/features/browser_ui/1_manage/login/log_into_mattermost_via_gitlab_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/login/log_into_mattermost_via_gitlab_spec.rb
index a680cfa96bd..c7bd372c144 100644
--- a/qa/qa/specs/features/browser_ui/1_manage/login/log_into_mattermost_via_gitlab_spec.rb
+++ b/qa/qa/specs/features/browser_ui/1_manage/login/log_into_mattermost_via_gitlab_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
- context 'Manage', :orchestrated, :mattermost do
+ RSpec.describe 'Manage', :orchestrated, :mattermost do
describe 'Mattermost login' do
it 'user logs into Mattermost using GitLab OAuth' do
Flow::Login.sign_in
diff --git a/qa/qa/specs/features/browser_ui/1_manage/login/login_via_instance_wide_saml_sso_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/login/login_via_instance_wide_saml_sso_spec.rb
index ad67f02eaca..505da623d66 100644
--- a/qa/qa/specs/features/browser_ui/1_manage/login/login_via_instance_wide_saml_sso_spec.rb
+++ b/qa/qa/specs/features/browser_ui/1_manage/login/login_via_instance_wide_saml_sso_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
- context 'Manage', :orchestrated, :instance_saml do
+ RSpec.describe 'Manage', :orchestrated, :instance_saml do
describe 'Instance wide SAML SSO' do
it 'User logs in to gitlab with SAML SSO' do
Runtime::Browser.visit(:gitlab, Page::Main::Login)
diff --git a/qa/qa/specs/features/browser_ui/1_manage/login/register_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/login/register_spec.rb
index a0e3fe0d91a..9dfeec37869 100644
--- a/qa/qa/specs/features/browser_ui/1_manage/login/register_spec.rb
+++ b/qa/qa/specs/features/browser_ui/1_manage/login/register_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
- shared_examples 'registration and login' do
+ RSpec.shared_examples 'registration and login' do
it 'user registers and logs in' do
Runtime::Browser.visit(:gitlab, Page::Main::Login)
@@ -13,13 +13,13 @@ module QA
end
end
- context 'Manage', :skip_signup_disabled do
+ RSpec.describe 'Manage', :skip_signup_disabled do
describe 'standard' do
it_behaves_like 'registration and login'
end
end
- context 'Manage', :orchestrated, :ldap_no_tls, :skip_signup_disabled do
+ RSpec.describe 'Manage', :orchestrated, :ldap_no_tls, :skip_signup_disabled do
describe 'while LDAP is enabled' do
it_behaves_like 'registration and login'
end
diff --git a/qa/qa/specs/features/browser_ui/1_manage/project/add_project_member_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/project/add_project_member_spec.rb
index 67055537567..8d1fa3ee62d 100644
--- a/qa/qa/specs/features/browser_ui/1_manage/project/add_project_member_spec.rb
+++ b/qa/qa/specs/features/browser_ui/1_manage/project/add_project_member_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
- context 'Manage' do
+ RSpec.describe 'Manage' do
describe 'Add project member' do
it 'user adds project member' do
Flow::Login.sign_in
diff --git a/qa/qa/specs/features/browser_ui/1_manage/project/create_project_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/project/create_project_spec.rb
index 9ca933a957f..80b5e332abe 100644
--- a/qa/qa/specs/features/browser_ui/1_manage/project/create_project_spec.rb
+++ b/qa/qa/specs/features/browser_ui/1_manage/project/create_project_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
- context 'Manage', :smoke do
+ RSpec.describe 'Manage', :smoke do
describe 'Project creation' do
it 'user creates a new project' do
Flow::Login.sign_in
diff --git a/qa/qa/specs/features/browser_ui/1_manage/project/dashboard_images_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/project/dashboard_images_spec.rb
index 1f1adf9afb4..6ce97188d66 100644
--- a/qa/qa/specs/features/browser_ui/1_manage/project/dashboard_images_spec.rb
+++ b/qa/qa/specs/features/browser_ui/1_manage/project/dashboard_images_spec.rb
@@ -3,7 +3,7 @@
require 'nokogiri'
module QA
- context 'Manage', quarantine: { issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/212145', type: :stale } do
+ RSpec.describe 'Manage', quarantine: { issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/212145', type: :stale } do
describe 'Check for broken images', :requires_admin do
before(:context) do
admin = QA::Resource::User.new.tap do |user|
diff --git a/qa/qa/specs/features/browser_ui/1_manage/project/import_github_repo_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/project/import_github_repo_spec.rb
index 409d67d51b1..94cf1fe1f8c 100644
--- a/qa/qa/specs/features/browser_ui/1_manage/project/import_github_repo_spec.rb
+++ b/qa/qa/specs/features/browser_ui/1_manage/project/import_github_repo_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
- context 'Manage', :github, quarantine: { issue: 'https://gitlab.com/gitlab-org/gitlab/issues/26952', type: :bug } do
+ RSpec.describe 'Manage', :github, quarantine: { issue: 'https://gitlab.com/gitlab-org/gitlab/issues/26952', type: :bug } do
describe 'Project import from GitHub' do
let(:imported_project) do
Resource::ProjectImportedFromGithub.fabricate! do |project|
@@ -62,12 +62,9 @@ module QA
Page::Project::Issue::Show.perform do |issue_page|
expect(issue_page).to have_comment(comment_text)
- end
-
- Page::Issuable::Sidebar.perform do |issuable|
- expect(issuable).to have_label('enhancement')
- expect(issuable).to have_label('help wanted')
- expect(issuable).to have_label('good first issue')
+ expect(issue_page).to have_label('enhancement')
+ expect(issue_page).to have_label('help wanted')
+ expect(issue_page).to have_label('good first issue')
end
end
end
@@ -91,9 +88,9 @@ module QA
expect(page).to have_content('[Review comment] Nice blank line.')
expect(page).to have_content('[Single diff comment] Much better without this line!')
- Page::Issuable::Sidebar.perform do |issuable|
- expect(issuable).to have_label('bug')
- expect(issuable).to have_label('enhancement')
+ Page::MergeRequest::Show.perform do |merge_request|
+ expect(merge_request).to have_label('bug')
+ expect(merge_request).to have_label('enhancement')
end
end
diff --git a/qa/qa/specs/features/browser_ui/1_manage/project/protected_tags_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/project/protected_tags_spec.rb
new file mode 100644
index 00000000000..e40dde64675
--- /dev/null
+++ b/qa/qa/specs/features/browser_ui/1_manage/project/protected_tags_spec.rb
@@ -0,0 +1,109 @@
+# frozen_string_literal: true
+
+module QA
+ RSpec.describe 'Manage' do
+ describe 'Repository tags' do
+ let(:project) do
+ Resource::Project.fabricate_via_api! do |project|
+ project.name = 'project-for-tags'
+ project.initialize_with_readme = true
+ end
+ end
+
+ let(:developer_user) { Resource::User.fabricate_or_use(Runtime::Env.gitlab_qa_username_1, Runtime::Env.gitlab_qa_password_1) }
+ let(:maintainer_user) { Resource::User.fabricate_or_use(Runtime::Env.gitlab_qa_username_2, Runtime::Env.gitlab_qa_password_2) }
+ let(:tag_name) { 'v0.0.1' }
+ let(:tag_message) { 'Version 0.0.1' }
+ let(:tag_release_notes) { 'Release It!' }
+
+ shared_examples 'successful tag creation' do |user|
+ it "can be created by #{user}" do
+ Flow::Login.sign_in(as: send(user))
+
+ create_tag_for_project(project, tag_name, tag_message, tag_release_notes)
+
+ Page::Project::Tag::Show.perform do |show|
+ expect(show).to have_tag_name(tag_name)
+ expect(show).to have_tag_message(tag_message)
+ expect(show).to have_tag_release_notes(tag_release_notes)
+ expect(show).not_to have_element(:create_tag_button)
+ end
+ end
+ end
+
+ shared_examples 'unsuccessful tag creation' do |user|
+ it "cannot be created by an unauthorized #{user}" do
+ Flow::Login.sign_in(as: send(user))
+
+ create_tag_for_project(project, tag_name, tag_message, tag_release_notes)
+
+ Page::Project::Tag::New.perform do |new_tag|
+ expect(new_tag).to have_content('You are not allowed to create this tag as it is protected.')
+ expect(new_tag).to have_element(:create_tag_button)
+ end
+ end
+ end
+
+ context 'when not protected' do
+ before do
+ add_members_to_project(project)
+ end
+
+ it_behaves_like 'successful tag creation', :developer_user
+ it_behaves_like 'successful tag creation', :maintainer_user
+ end
+
+ context 'when protected' do
+ before do
+ add_members_to_project(project)
+
+ Flow::Login.sign_in
+
+ protect_tag_for_project(project, 'v*', 'Maintainers')
+
+ Page::Main::Menu.perform(&:sign_out)
+ end
+
+ it_behaves_like 'unsuccessful tag creation', :developer_user
+ it_behaves_like 'successful tag creation', :maintainer_user
+ end
+
+ def create_tag_for_project(project, name, message, release_notes)
+ project.visit!
+
+ Page::Project::Menu.perform(&:go_to_repository_tags)
+ Page::Project::Tag::Index.perform(&:click_new_tag_button)
+
+ Page::Project::Tag::New.perform do |new_tag|
+ new_tag.fill_tag_name(name)
+ new_tag.fill_tag_message(message)
+ new_tag.fill_release_notes(release_notes)
+ new_tag.click_create_tag_button
+ end
+ end
+
+ def protect_tag_for_project(project, tag, role)
+ project.visit!
+
+ Page::Project::Menu.perform(&:go_to_repository_settings)
+
+ Page::Project::Settings::Repository.perform do |setting|
+ setting.expand_protected_tags do |protected_tags|
+ protected_tags.set_tag(tag)
+ protected_tags.choose_access_level_role(role)
+
+ protected_tags.click_protect_tag_button
+ end
+ end
+ end
+
+ def add_members_to_project(project)
+ @developer_user = developer_user
+ @maintainer_user = maintainer_user
+
+ project.add_member(@developer_user, Resource::Members::AccessLevel::DEVELOPER)
+ project.add_member(@maintainer_user, Resource::Members::AccessLevel::MAINTAINER)
+ end
+ end
+ end
+end
diff --git a/qa/qa/specs/features/browser_ui/1_manage/project/view_project_activity_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/project/view_project_activity_spec.rb
index d483dcc97a7..b98d2982684 100644
--- a/qa/qa/specs/features/browser_ui/1_manage/project/view_project_activity_spec.rb
+++ b/qa/qa/specs/features/browser_ui/1_manage/project/view_project_activity_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
- context 'Manage' do
+ RSpec.describe 'Manage' do
describe 'Project activity' do
it 'user creates an event in the activity page upon Git push' do
Flow::Login.sign_in
diff --git a/qa/qa/specs/features/browser_ui/2_plan/email/trigger_email_notification_spec.rb b/qa/qa/specs/features/browser_ui/2_plan/email/trigger_email_notification_spec.rb
index 0a577aa07f8..3717bc8a9ff 100644
--- a/qa/qa/specs/features/browser_ui/2_plan/email/trigger_email_notification_spec.rb
+++ b/qa/qa/specs/features/browser_ui/2_plan/email/trigger_email_notification_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
- context 'Plan', :orchestrated, :smtp do
+ RSpec.describe 'Plan', :orchestrated, :smtp do
describe 'Email Notification' do
include Support::Api
diff --git a/qa/qa/specs/features/browser_ui/2_plan/issue/check_mentions_for_xss_spec.rb b/qa/qa/specs/features/browser_ui/2_plan/issue/check_mentions_for_xss_spec.rb
index 57d2c02a27b..784f474a7d5 100644
--- a/qa/qa/specs/features/browser_ui/2_plan/issue/check_mentions_for_xss_spec.rb
+++ b/qa/qa/specs/features/browser_ui/2_plan/issue/check_mentions_for_xss_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
- context 'Plan', :reliable do
+ RSpec.describe 'Plan', :reliable do
describe 'check xss occurence in @mentions in issues', :requires_admin do
it 'mentions a user in a comment' do
QA::Runtime::Env.personal_access_token = QA::Runtime::Env.admin_personal_access_token
diff --git a/qa/qa/specs/features/browser_ui/2_plan/issue/collapse_comments_in_discussions_spec.rb b/qa/qa/specs/features/browser_ui/2_plan/issue/collapse_comments_in_discussions_spec.rb
index 33d2c7026b3..478f6b8177c 100644
--- a/qa/qa/specs/features/browser_ui/2_plan/issue/collapse_comments_in_discussions_spec.rb
+++ b/qa/qa/specs/features/browser_ui/2_plan/issue/collapse_comments_in_discussions_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
- context 'Plan', :reliable do
+ RSpec.describe 'Plan', :reliable do
describe 'collapse comments in issue discussions' do
let(:my_first_reply) { 'My first reply' }
diff --git a/qa/qa/specs/features/browser_ui/2_plan/issue/comment_issue_spec.rb b/qa/qa/specs/features/browser_ui/2_plan/issue/comment_issue_spec.rb
index 4667eb6c587..0347de42b96 100644
--- a/qa/qa/specs/features/browser_ui/2_plan/issue/comment_issue_spec.rb
+++ b/qa/qa/specs/features/browser_ui/2_plan/issue/comment_issue_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
- context 'Plan', :reliable do
+ RSpec.describe 'Plan', :reliable do
describe 'Issue comments' do
before do
Flow::Login.sign_in
diff --git a/qa/qa/specs/features/browser_ui/2_plan/issue/create_issue_spec.rb b/qa/qa/specs/features/browser_ui/2_plan/issue/create_issue_spec.rb
index 57b0859856e..e41024e5d14 100644
--- a/qa/qa/specs/features/browser_ui/2_plan/issue/create_issue_spec.rb
+++ b/qa/qa/specs/features/browser_ui/2_plan/issue/create_issue_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
- context 'Plan', :smoke do
+ RSpec.describe 'Plan', :smoke do
describe 'Issue creation' do
let(:closed_issue) { Resource::Issue.fabricate_via_api! }
diff --git a/qa/qa/specs/features/browser_ui/2_plan/issue/export_as_csv_spec.rb b/qa/qa/specs/features/browser_ui/2_plan/issue/export_as_csv_spec.rb
index 3727aae2270..aa03a514f04 100644
--- a/qa/qa/specs/features/browser_ui/2_plan/issue/export_as_csv_spec.rb
+++ b/qa/qa/specs/features/browser_ui/2_plan/issue/export_as_csv_spec.rb
@@ -3,7 +3,7 @@
require 'securerandom'
module QA
- context 'Plan', :reliable do
+ RSpec.describe 'Plan', :reliable do
describe 'Issues list' do
let(:project) do
Resource::Project.fabricate_via_api! do |project|
diff --git a/qa/qa/specs/features/browser_ui/2_plan/issue/filter_issue_comments_spec.rb b/qa/qa/specs/features/browser_ui/2_plan/issue/filter_issue_comments_spec.rb
index b7687f785a8..082933e9878 100644
--- a/qa/qa/specs/features/browser_ui/2_plan/issue/filter_issue_comments_spec.rb
+++ b/qa/qa/specs/features/browser_ui/2_plan/issue/filter_issue_comments_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
- context 'Plan', :reliable do
+ RSpec.describe 'Plan', :reliable do
describe 'filter issue comments activities' do
before do
Flow::Login.sign_in
diff --git a/qa/qa/specs/features/browser_ui/2_plan/issue/issue_suggestions_spec.rb b/qa/qa/specs/features/browser_ui/2_plan/issue/issue_suggestions_spec.rb
index 623573a1397..43f4415c90d 100644
--- a/qa/qa/specs/features/browser_ui/2_plan/issue/issue_suggestions_spec.rb
+++ b/qa/qa/specs/features/browser_ui/2_plan/issue/issue_suggestions_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
- context 'Plan', :reliable do
+ RSpec.describe 'Plan', :reliable do
describe 'issue suggestions' do
let(:issue_title) { 'Issue Lists are awesome' }
diff --git a/qa/qa/specs/features/browser_ui/2_plan/issue/jira_issue_import_spec.rb b/qa/qa/specs/features/browser_ui/2_plan/issue/jira_issue_import_spec.rb
new file mode 100644
index 00000000000..a2e7d10f313
--- /dev/null
+++ b/qa/qa/specs/features/browser_ui/2_plan/issue/jira_issue_import_spec.rb
@@ -0,0 +1,77 @@
+# frozen_string_literal: true
+
+module QA
+ RSpec.describe 'Plan' do
+ describe 'Jira issue import', :jira, :orchestrated, :requires_admin do
+ let(:jira_project_key) { "JITD" }
+ let(:jira_issue_title) { "[#{jira_project_key}-1] Jira to GitLab Test Issue" }
+ let(:jira_issue_description) { "This issue is for testing importing Jira issues to GitLab." }
+ let(:jira_issue_label_1) { "jira-import::#{jira_project_key}-1" }
+ let(:jira_issue_label_2) { "QA" }
+ let(:project) do
+ Resource::Project.fabricate_via_api! do |project|
+ project.name = "jira_issue_import"
+ end
+ end
+
+ it 'imports issues from Jira' do
+ set_up_jira_integration
+ import_jira_issues
+
+ QA::Support::Retrier.retry_on_exception do
+ Page::Project::Menu.perform(&:click_issues)
+
+ Page::Project::Issue::Index.perform do |issues_page|
+ expect(issues_page).to have_content("2 issues successfully imported")
+
+ issues_page.click_issue_link(jira_issue_title)
+ end
+ end
+
+ expect(page).to have_content(jira_issue_description)
+
+ Page::Project::Issue::Show.perform do |issue|
+ expect(issue).to have_label(jira_issue_label_1)
+ expect(issue).to have_label(jira_issue_label_2)
+ end
+ end
+
+ private
+
+ def set_up_jira_integration
+ # Retry is required because allow_local_requests_from_web_hooks_and_services
+ # takes some time to get enabled.
+ # Bug issue: https://gitlab.com/gitlab-org/gitlab/-/issues/217010
+ QA::Support::Retrier.retry_on_exception(max_attempts: 5, sleep_interval: 3) do
+ Runtime::ApplicationSettings.set_application_settings(allow_local_requests_from_web_hooks_and_services: true)
+
+ page.visit Runtime::Scenario.gitlab_address
+ Flow::Login.sign_in_unless_signed_in
+
+ project.visit!
+
+ Page::Project::Menu.perform(&:go_to_integrations_settings)
+ QA::Page::Project::Settings::Integrations.perform(&:click_jira_link)
+
+ QA::Page::Project::Settings::Services::Jira.perform do |jira|
+ jira.setup_service_with(url: Vendor::Jira::JiraAPI.perform(&:base_url))
+ end
+
+ expect(page).not_to have_text("Url is blocked")
+ expect(page).to have_text("Jira activated")
+ end
+ end
+
+ def import_jira_issues
+ Page::Project::Menu.perform(&:click_issues)
+ Page::Project::Issue::Index.perform(&:go_to_jira_import_form)
+
+ Page::Project::Issue::JiraImport.perform do |form|
+ form.select_project_and_import(jira_project_key)
+ end
+
+ expect(page).to have_content("Import in progress")
+ end
+ end
+ end
+end
diff --git a/qa/qa/specs/features/browser_ui/2_plan/issue/mentions_spec.rb b/qa/qa/specs/features/browser_ui/2_plan/issue/mentions_spec.rb
index 3e575517ecb..50df1c3ef01 100644
--- a/qa/qa/specs/features/browser_ui/2_plan/issue/mentions_spec.rb
+++ b/qa/qa/specs/features/browser_ui/2_plan/issue/mentions_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
- context 'Plan', :smoke, :reliable do
+ RSpec.describe 'Plan', :smoke, :reliable do
describe 'mention' do
before do
Flow::Login.sign_in
diff --git a/qa/qa/specs/features/browser_ui/2_plan/issue_boards/focus_mode_spec.rb b/qa/qa/specs/features/browser_ui/2_plan/issue_boards/focus_mode_spec.rb
index c81a6f9281c..932eef8e38b 100644
--- a/qa/qa/specs/features/browser_ui/2_plan/issue_boards/focus_mode_spec.rb
+++ b/qa/qa/specs/features/browser_ui/2_plan/issue_boards/focus_mode_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
- context 'Plan', :reliable do
+ RSpec.describe 'Plan', :reliable do
describe 'Issue board focus mode' do
let(:project) do
QA::Resource::Project.fabricate_via_api! do |project|
diff --git a/qa/qa/specs/features/browser_ui/2_plan/milestone/assign_milestone_spec.rb b/qa/qa/specs/features/browser_ui/2_plan/milestone/assign_milestone_spec.rb
new file mode 100644
index 00000000000..115701c5c02
--- /dev/null
+++ b/qa/qa/specs/features/browser_ui/2_plan/milestone/assign_milestone_spec.rb
@@ -0,0 +1,85 @@
+# frozen_string_literal: true
+
+module QA
+ RSpec.describe 'Plan' do
+ describe 'Milestones' do
+ include Support::Dates
+
+ let(:start_date) { current_date_yyyy_mm_dd }
+ let(:due_date) { next_month_yyyy_mm_dd }
+
+ let(:group) do
+ Resource::Group.fabricate_via_api! do |group|
+ group.name = 'group-to-test-milestones'
+ end
+ end
+
+ let(:project) do
+ Resource::Project.fabricate_via_api! do |project|
+ project.name = 'project-to-test-milestones'
+ end
+ end
+
+ let(:issue) do
+ Resource::Issue.fabricate_via_api! do |issue|
+ issue.project = project
+ end
+ end
+
+ before do
+ Flow::Login.sign_in
+ end
+
+ shared_examples 'milestone assigned to existing issue' do
+ it 'is assigned to an existing issue' do
+ issue.visit!
+
+ Page::Project::Issue::Show.perform do |existing_issue|
+ existing_issue.assign_milestone(milestone)
+
+ expect(existing_issue).to have_milestone(milestone.title)
+ end
+ end
+ end
+
+ shared_examples 'milestone assigned to new issue' do
+ it 'is assigned to a new issue' do
+ Resource::Issue.fabricate_via_browser_ui! do |new_issue|
+ new_issue.project = project
+ new_issue.milestone = milestone
+ end
+
+ Page::Project::Issue::Show.perform do |issue|
+ expect(issue).to have_milestone(milestone.title)
+ end
+ end
+ end
+
+ context 'Group milestone' do
+ let(:milestone) do
+ Resource::GroupMilestone.fabricate_via_api! do |milestone|
+ milestone.group = group
+ milestone.start_date = start_date
+ milestone.due_date = due_date
+ end
+ end
+
+ it_behaves_like 'milestone assigned to existing issue'
+ it_behaves_like 'milestone assigned to new issue'
+ end
+
+ context 'Project milestone' do
+ let(:milestone) do
+ Resource::ProjectMilestone.fabricate_via_api! do |milestone|
+ milestone.project = project
+ milestone.start_date = start_date
+ milestone.due_date = due_date
+ end
+ end
+
+ it_behaves_like 'milestone assigned to existing issue'
+ it_behaves_like 'milestone assigned to new issue'
+ end
+ end
+ end
+end
diff --git a/qa/qa/specs/features/browser_ui/2_plan/milestone/create_group_milestone_spec.rb b/qa/qa/specs/features/browser_ui/2_plan/milestone/create_group_milestone_spec.rb
new file mode 100644
index 00000000000..35c42796aeb
--- /dev/null
+++ b/qa/qa/specs/features/browser_ui/2_plan/milestone/create_group_milestone_spec.rb
@@ -0,0 +1,41 @@
+# frozen_string_literal: true
+
+module QA
+ RSpec.describe 'Plan' do
+ describe 'Group milestone' do
+ include Support::Dates
+
+ let(:title) { 'Group milestone' }
+ let(:description) { 'This milestone tests out group milestones.' }
+ let(:start_date) { current_date_yyyy_mm_dd }
+ let(:due_date) { next_month_yyyy_mm_dd }
+
+ before do
+ Flow::Login.sign_in
+ end
+
+ it 'creates a group milestone' do
+ group_milestone = Resource::GroupMilestone.fabricate_via_browser_ui! do |milestone|
+ milestone.title = title
+ milestone.description = description
+ milestone.start_date = start_date
+ milestone.due_date = due_date
+ end
+
+ Page::Group::Menu.perform(&:go_to_milestones)
+ Page::Group::Milestone::Index.perform do |milestone_list|
+ expect(milestone_list).to have_milestone(group_milestone)
+
+ milestone_list.click_milestone(group_milestone)
+ end
+
+ Page::Milestone::Show.perform do |milestone|
+ expect(milestone).to have_element(:milestone_title_content, text: title)
+ expect(milestone).to have_element(:milestone_description_content, text: description)
+ expect(milestone).to have_start_date(start_date)
+ expect(milestone).to have_due_date(due_date)
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/specs/features/browser_ui/2_plan/milestone/create_project_milestone_spec.rb b/qa/qa/specs/features/browser_ui/2_plan/milestone/create_project_milestone_spec.rb
new file mode 100644
index 00000000000..143fdf5728b
--- /dev/null
+++ b/qa/qa/specs/features/browser_ui/2_plan/milestone/create_project_milestone_spec.rb
@@ -0,0 +1,41 @@
+# frozen_string_literal: true
+
+module QA
+ RSpec.describe 'Plan' do
+ describe 'Project milestone' do
+ include Support::Dates
+
+ let(:title) { 'Project milestone' }
+ let(:description) { 'This issue tests out project milestones.' }
+ let(:start_date) { current_date_yyyy_mm_dd }
+ let(:due_date) { next_month_yyyy_mm_dd }
+
+ before do
+ Flow::Login.sign_in
+ end
+
+ it 'creates a project milestone' do
+ project_milestone = Resource::ProjectMilestone.fabricate_via_browser_ui! do |milestone|
+ milestone.title = title
+ milestone.description = description
+ milestone.start_date = start_date
+ milestone.due_date = due_date
+ end
+
+ Page::Project::Menu.perform(&:go_to_milestones)
+ Page::Project::Milestone::Index.perform do |milestone_list|
+ expect(milestone_list).to have_milestone(project_milestone)
+
+ milestone_list.click_milestone(project_milestone)
+ end
+
+ Page::Milestone::Show.perform do |milestone|
+ expect(milestone).to have_element(:milestone_title_content, text: title)
+ expect(milestone).to have_element(:milestone_description_content, text: description)
+ expect(milestone).to have_start_date(start_date)
+ expect(milestone).to have_due_date(due_date)
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/specs/features/browser_ui/3_create/gitaly/high_availability_spec.rb b/qa/qa/specs/features/browser_ui/3_create/gitaly/high_availability_spec.rb
index 3bb03f68d51..97a76c1aa01 100644
--- a/qa/qa/specs/features/browser_ui/3_create/gitaly/high_availability_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/gitaly/high_availability_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
- context 'Create' do
+ RSpec.describe 'Create' do
context 'Gitaly' do
describe 'High Availability', :orchestrated, :gitaly_ha do
let(:project) do
@@ -10,15 +10,15 @@ module QA
end
end
let(:initial_file) { 'pushed_to_primary.txt' }
- let(:final_file) { 'pushed_to_secondary.txt' }
+ let(:final_file) { 'committed_to_primary.txt' }
+ let(:praefect_manager) { Service::PraefectManager.new }
before do
- @praefect_manager = Service::PraefectManager.new
Flow::Login.sign_in
end
after do
- @praefect_manager.reset
+ praefect_manager.reset_cluster
end
it 'makes sure that automatic failover is happening' do
@@ -30,7 +30,7 @@ module QA
push.file_content = "This should exist on both nodes"
end
- @praefect_manager.stop_primary_node
+ praefect_manager.trigger_failover_by_stopping_primary_node
project.visit!
@@ -41,11 +41,13 @@ module QA
expect(show).to have_file(initial_file)
end
+ praefect_manager.enable_writes
+
Resource::Repository::Commit.fabricate_via_api! do |commit|
commit.project = project
commit.add_files([
{
- file_path: 'committed_to_primary.txt',
+ file_path: final_file,
content: 'This should exist on both nodes too'
}
])
diff --git a/qa/qa/specs/features/browser_ui/3_create/jira/jira_basic_integration_spec.rb b/qa/qa/specs/features/browser_ui/3_create/jira/jira_basic_integration_spec.rb
index 05a932fd53e..28338475cb5 100644
--- a/qa/qa/specs/features/browser_ui/3_create/jira/jira_basic_integration_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/jira/jira_basic_integration_spec.rb
@@ -1,23 +1,24 @@
# frozen_string_literal: true
module QA
- context 'Create' do
+ RSpec.describe 'Create' do
include Support::Api
describe 'Jira integration', :jira, :orchestrated, :requires_admin do
let(:jira_project_key) { 'JITP' }
+ let(:project) do
+ Resource::Project.fabricate_via_api! do |project|
+ project.name = "project_with_jira_integration"
+ end
+ end
- before(:all) do
+ before do
page.visit Vendor::Jira::JiraAPI.perform(&:base_url)
QA::Support::Retrier.retry_until(sleep_interval: 3, reload_page: page, max_attempts: 20, raise_on_failure: true) do
page.has_text? 'Welcome to Jira'
end
- @project = Resource::Project.fabricate_via_api! do |project|
- project.name = "project_with_jira_integration"
- end
-
# Retry is required because allow_local_requests_from_web_hooks_and_services
# takes some time to get enabled.
# Bug issue: https://gitlab.com/gitlab-org/gitlab/-/issues/217010
@@ -27,7 +28,7 @@ module QA
page.visit Runtime::Scenario.gitlab_address
Flow::Login.sign_in_unless_signed_in
- @project.visit!
+ project.visit!
Page::Project::Menu.perform(&:go_to_integrations_settings)
QA::Page::Project::Settings::Integrations.perform(&:click_jira_link)
@@ -67,9 +68,11 @@ module QA
expect_issue_done(issue_key)
end
+ private
+
def create_mr_with_description(description)
Resource::MergeRequest.fabricate! do |merge_request|
- merge_request.project = @project
+ merge_request.project = project
merge_request.target_new_branch = !master_branch_exists?
merge_request.description = description
end
@@ -80,7 +83,7 @@ module QA
push.branch_name = 'master'
push.commit_message = commit_message
push.file_content = commit_message
- push.project = @project
+ push.project = project
push.new_branch = !master_branch_exists?
end
end
@@ -98,7 +101,7 @@ module QA
end
def master_branch_exists?
- @project.repository_branches.map { |item| item[:name] }.include?("master")
+ project.repository_branches.map { |item| item[:name] }.include?("master")
end
end
end
diff --git a/qa/qa/specs/features/browser_ui/3_create/merge_request/create_merge_request_spec.rb b/qa/qa/specs/features/browser_ui/3_create/merge_request/create_merge_request_spec.rb
index 4a9901f2a84..a002779d7d9 100644
--- a/qa/qa/specs/features/browser_ui/3_create/merge_request/create_merge_request_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/merge_request/create_merge_request_spec.rb
@@ -1,29 +1,31 @@
# frozen_string_literal: true
module QA
- context 'Create' do
+ RSpec.describe 'Create' do
describe 'Create a new merge request' do
- before do
- Flow::Login.sign_in
-
- @project = Resource::Project.fabricate_via_api! do |project|
+ let(:project) do
+ Resource::Project.fabricate_via_api! do |project|
project.name = 'project'
end
+ end
+
+ let(:merge_request_title) { 'One merge request to rule them all' }
+ let(:merge_request_description) { '... to find them, to bring them all, and in the darkness bind them' }
- @merge_request_title = 'One merge request to rule them all'
- @merge_request_description = '... to find them, to bring them all, and in the darkness bind them'
+ before do
+ Flow::Login.sign_in
end
it 'creates a basic merge request' do
Resource::MergeRequest.fabricate_via_browser_ui! do |merge_request|
- merge_request.project = @project
- merge_request.title = @merge_request_title
- merge_request.description = @merge_request_description
+ merge_request.project = project
+ merge_request.title = merge_request_title
+ merge_request.description = merge_request_description
end
Page::MergeRequest::Show.perform do |merge_request|
- expect(merge_request).to have_title(@merge_request_title)
- expect(merge_request).to have_description(@merge_request_description)
+ expect(merge_request).to have_title(merge_request_title)
+ expect(merge_request).to have_description(merge_request_description)
end
end
@@ -31,32 +33,29 @@ module QA
gitlab_account_username = "@#{Runtime::User.username}"
milestone = Resource::ProjectMilestone.fabricate_via_api! do |milestone|
- milestone.project = @project
+ milestone.project = project
end
label = Resource::Label.fabricate_via_api! do |label|
- label.project = @project
+ label.project = project
label.title = 'label'
end
Resource::MergeRequest.fabricate_via_browser_ui! do |merge_request|
- merge_request.title = @merge_request_title
- merge_request.description = @merge_request_description
- merge_request.project = @project
+ merge_request.title = merge_request_title
+ merge_request.description = merge_request_description
+ merge_request.project = project
merge_request.milestone = milestone
merge_request.assignee = 'me'
merge_request.labels.push(label)
end
Page::MergeRequest::Show.perform do |merge_request|
- expect(merge_request).to have_title(@merge_request_title)
- expect(merge_request).to have_description(@merge_request_description)
+ expect(merge_request).to have_title(merge_request_title)
+ expect(merge_request).to have_description(merge_request_description)
expect(merge_request).to have_assignee(gitlab_account_username)
expect(merge_request).to have_label(label.title)
- end
-
- Page::Issuable::Sidebar.perform do |sidebar|
- expect(sidebar).to have_milestone(milestone.title)
+ expect(merge_request).to have_milestone(milestone.title)
end
end
end
diff --git a/qa/qa/specs/features/browser_ui/3_create/merge_request/merge_merge_request_from_fork_spec.rb b/qa/qa/specs/features/browser_ui/3_create/merge_request/merge_merge_request_from_fork_spec.rb
index 3964ae7eada..5b89bf046fb 100644
--- a/qa/qa/specs/features/browser_ui/3_create/merge_request/merge_merge_request_from_fork_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/merge_request/merge_merge_request_from_fork_spec.rb
@@ -1,19 +1,16 @@
# frozen_string_literal: true
module QA
- context 'Create' do
+ RSpec.describe 'Create' do
describe 'Merge request creation from fork' do
- it 'user forks a project, submits a merge request and maintainer merges it' do
- Flow::Login.sign_in
-
- merge_request = Resource::MergeRequestFromFork.fabricate_via_browser_ui! do |merge_request|
+ let(:merge_request) do
+ Resource::MergeRequestFromFork.fabricate_via_api! do |merge_request|
merge_request.fork_branch = 'feature-branch'
end
+ end
- merge_request.project.api_put(auto_devops_enabled: false)
-
- Page::Main::Menu.perform(&:sign_out)
- Page::Main::Login.perform(&:sign_in_using_credentials)
+ it 'can merge feature branch fork to mainline' do
+ Flow::Login.sign_in
merge_request.visit!
diff --git a/qa/qa/specs/features/browser_ui/3_create/merge_request/rebase_merge_request_spec.rb b/qa/qa/specs/features/browser_ui/3_create/merge_request/rebase_merge_request_spec.rb
index 4e189faec6e..a1c604bdcfc 100644
--- a/qa/qa/specs/features/browser_ui/3_create/merge_request/rebase_merge_request_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/merge_request/rebase_merge_request_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
- context 'Create', quarantine: { issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/30226', type: :bug } do
+ RSpec.describe 'Create', quarantine: { issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/30226', type: :bug } do
describe 'Merge request rebasing' do
it 'user rebases source branch of merge request' do
Flow::Login.sign_in
diff --git a/qa/qa/specs/features/browser_ui/3_create/merge_request/squash_merge_request_spec.rb b/qa/qa/specs/features/browser_ui/3_create/merge_request/squash_merge_request_spec.rb
index 9236609934e..cb660a3e40b 100644
--- a/qa/qa/specs/features/browser_ui/3_create/merge_request/squash_merge_request_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/merge_request/squash_merge_request_spec.rb
@@ -1,19 +1,23 @@
# frozen_string_literal: true
module QA
- context 'Create' do
+ RSpec.describe 'Create' do
describe 'Merge request squashing' do
- it 'user squashes commits while merging' do
- Flow::Login.sign_in
-
- project = Resource::Project.fabricate_via_api! do |project|
+ let(:project) do
+ Resource::Project.fabricate_via_api! do |project|
project.name = "squash-before-merge"
end
+ end
- merge_request = Resource::MergeRequest.fabricate! do |merge_request|
+ let(:merge_request) do
+ Resource::MergeRequest.fabricate_via_api! do |merge_request|
merge_request.project = project
merge_request.title = 'Squashing commits'
end
+ end
+
+ before do
+ Flow::Login.sign_in
Resource::Repository::ProjectPush.fabricate! do |push|
push.project = project
@@ -25,7 +29,9 @@ module QA
end
merge_request.visit!
+ end
+ it 'user squashes commits while merging' do
Page::MergeRequest::Show.perform do |merge_request_page|
merge_request_page.retry_on_exception(reload: true) do
expect(merge_request_page).to have_text('to be squashed')
@@ -34,13 +40,9 @@ module QA
merge_request_page.mark_to_squash
merge_request_page.merge!
- merge_request.project.visit!
-
Git::Repository.perform do |repository|
- repository.uri = merge_request.project.repository_http_location.uri
-
+ repository.uri = project.repository_http_location.uri
repository.use_default_credentials
-
repository.clone
expect(repository.commits.size).to eq 3
diff --git a/qa/qa/specs/features/browser_ui/3_create/merge_request/view_merge_request_diff_patch_spec.rb b/qa/qa/specs/features/browser_ui/3_create/merge_request/view_merge_request_diff_patch_spec.rb
index 604b6c10aee..3c2c068dfd1 100644
--- a/qa/qa/specs/features/browser_ui/3_create/merge_request/view_merge_request_diff_patch_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/merge_request/view_merge_request_diff_patch_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
- context 'Create' do
+ RSpec.describe 'Create' do
describe 'Download merge request patch and diff' do
before(:context) do
@merge_request = Resource::MergeRequest.fabricate_via_api! do |merge_request|
diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/add_file_template_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/add_file_template_spec.rb
index d5346546efe..c02632c2c60 100644
--- a/qa/qa/specs/features/browser_ui/3_create/repository/add_file_template_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/repository/add_file_template_spec.rb
@@ -1,12 +1,14 @@
# frozen_string_literal: true
+require 'securerandom'
+
module QA
- context 'Create' do
+ RSpec.describe 'Create' do
describe 'File templates' do
include Runtime::Fixtures
- before(:all) do
- @project = Resource::Project.fabricate_via_api! do |project|
+ let(:project) do
+ Resource::Project.fabricate_via_api! do |project|
project.name = 'file-template-project'
project.description = 'Add file templates via the Files view'
project.initialize_with_readme = true
@@ -46,7 +48,7 @@ module QA
Flow::Login.sign_in
- @project.visit!
+ project.visit!
Page::Project::Show.perform(&:create_new_file!)
Page::File::Form.perform do |form|
@@ -54,12 +56,14 @@ module QA
expect(form).to have_normalized_ws_text(content[0..100])
+ form.add_name("#{SecureRandom.hex(8)}/#{template[:file_name]}")
form.commit_changes
- expect(form).to have_content('The file has been successfully created.')
- expect(form).to have_content(template[:file_name])
- expect(form).to have_content('Add new file')
- expect(form).to have_normalized_ws_text(content[0..100])
+ aggregate_failures "indications of file created" do
+ expect(form).to have_content(template[:file_name])
+ expect(form).to have_normalized_ws_text(content[0..100])
+ expect(form).to have_content('Add new file')
+ end
end
end
end
diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/add_list_delete_branches_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/add_list_delete_branches_spec.rb
index bf5a9501cba..cab909756c1 100644
--- a/qa/qa/specs/features/browser_ui/3_create/repository/add_list_delete_branches_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/repository/add_list_delete_branches_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
- context 'Create' do
+ RSpec.describe 'Create' do
describe 'Create, list, and delete branches via web' do
master_branch = 'master'
second_branch = 'second-branch'
diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/add_ssh_key_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/add_ssh_key_spec.rb
deleted file mode 100644
index 68bbc1719fc..00000000000
--- a/qa/qa/specs/features/browser_ui/3_create/repository/add_ssh_key_spec.rb
+++ /dev/null
@@ -1,30 +0,0 @@
-# frozen_string_literal: true
-
-module QA
- context 'Create' do
- describe 'SSH keys support' do
- let(:key_title) { "key for ssh tests #{Time.now.to_f}" }
-
- it 'user adds and then removes an SSH key', :smoke do
- Flow::Login.sign_in
-
- key = Resource::SSHKey.fabricate_via_browser_ui! do |resource|
- resource.title = key_title
- end
-
- expect(page).to have_content(key.title)
- expect(page).to have_content(key.md5_fingerprint)
-
- Page::Main::Menu.perform(&:click_settings_link)
- Page::Profile::Menu.perform(&:click_ssh_keys)
-
- Page::Profile::SSHKeys.perform do |ssh_keys|
- ssh_keys.remove_key(key_title)
- end
-
- expect(page).not_to have_content("Title: #{key_title}")
- expect(page).not_to have_content(key.md5_fingerprint)
- end
- end
- end
-end
diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/clone_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/clone_spec.rb
index 0650c8395c7..c9cbc68c254 100644
--- a/qa/qa/specs/features/browser_ui/3_create/repository/clone_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/repository/clone_spec.rb
@@ -1,16 +1,18 @@
# frozen_string_literal: true
module QA
- context 'Create' do
+ RSpec.describe 'Create' do
describe 'Git clone over HTTP', :ldap_no_tls do
- before(:all) do
- @project = Resource::Project.fabricate_via_api! do |scenario|
+ let(:project) do
+ Resource::Project.fabricate_via_api! do |scenario|
scenario.name = 'project-with-code'
scenario.description = 'project for git clone tests'
end
+ end
+ before do
Git::Repository.perform do |repository|
- repository.uri = @project.repository_http_location.uri
+ repository.uri = project.repository_http_location.uri
repository.use_default_credentials
repository.act do
@@ -21,12 +23,12 @@ module QA
push_changes
end
end
- @project.wait_for_push_new_branch
+ project.wait_for_push_new_branch
end
it 'user performs a deep clone' do
Git::Repository.perform do |repository|
- repository.uri = @project.repository_http_location.uri
+ repository.uri = project.repository_http_location.uri
repository.use_default_credentials
repository.clone
@@ -37,7 +39,7 @@ module QA
it 'user performs a shallow clone' do
Git::Repository.perform do |repository|
- repository.uri = @project.repository_http_location.uri
+ repository.uri = project.repository_http_location.uri
repository.use_default_credentials
repository.shallow_clone
diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/create_edit_delete_file_via_web_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/create_edit_delete_file_via_web_spec.rb
index 51a1c19f0f7..d66f0ddcda6 100644
--- a/qa/qa/specs/features/browser_ui/3_create/repository/create_edit_delete_file_via_web_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/repository/create_edit_delete_file_via_web_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
- context 'Create' do
+ RSpec.describe 'Create' do
describe 'Files management' do
it 'user creates, edits and deletes a file via the Web' do
Runtime::Browser.visit(:gitlab, Page::Main::Login)
diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/move_project_create_fork_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/move_project_create_fork_spec.rb
index d0123da53bb..f586c25165c 100644
--- a/qa/qa/specs/features/browser_ui/3_create/repository/move_project_create_fork_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/repository/move_project_create_fork_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
- context 'Create', quarantine: { issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/217002', type: :investigating } do
+ RSpec.describe 'Create', quarantine: { issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/217002', type: :investigating } do
describe 'Gitaly repository storage', :orchestrated, :repository_storage, :requires_admin do
let(:user) { Resource::User.fabricate_or_use(Runtime::Env.gitlab_qa_username_1, Runtime::Env.gitlab_qa_password_1) }
let(:parent_project) do
diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/protocol_v2_push_http_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/protocol_v2_push_http_spec.rb
index b210e747614..e3b0d7de9ec 100644
--- a/qa/qa/specs/features/browser_ui/3_create/repository/protocol_v2_push_http_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/repository/protocol_v2_push_http_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
- context 'Create' do
+ RSpec.describe 'Create' do
describe 'Push over HTTP using Git protocol version 2', :requires_git_protocol_v2 do
it 'user pushes to the repository' do
Flow::Login.sign_in
diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/protocol_v2_push_ssh_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/protocol_v2_push_ssh_spec.rb
index e3d5b755317..90beff343ab 100644
--- a/qa/qa/specs/features/browser_ui/3_create/repository/protocol_v2_push_ssh_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/repository/protocol_v2_push_ssh_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
- context 'Create' do
+ RSpec.describe 'Create' do
describe 'Push over SSH using Git protocol version 2', :requires_git_protocol_v2 do
# Note: If you run this test against GDK make sure you've enabled sshd and
# enabled setting the Git protocol by adding `AcceptEnv GIT_PROTOCOL` to
diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/push_http_private_token_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/push_http_private_token_spec.rb
index d8aaffc3713..c01558d3702 100644
--- a/qa/qa/specs/features/browser_ui/3_create/repository/push_http_private_token_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/repository/push_http_private_token_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
- context 'Create' do
+ RSpec.describe 'Create' do
describe 'Git push over HTTP', :ldap_no_tls do
it 'user using a personal access token pushes code to the repository' do
Flow::Login.sign_in
diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/push_mirroring_over_http_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/push_mirroring_over_http_spec.rb
index ae95f5a7a44..254e32a88ce 100644
--- a/qa/qa/specs/features/browser_ui/3_create/repository/push_mirroring_over_http_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/repository/push_mirroring_over_http_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
- context 'Create' do
+ RSpec.describe 'Create' do
describe 'Push mirror a repository over HTTP' do
it 'configures and syncs a (push) mirrored repository' do
Runtime::Browser.visit(:gitlab, Page::Main::Login)
diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/push_over_http_file_size_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/push_over_http_file_size_spec.rb
index 21ae10774c9..443ace0c9f0 100644
--- a/qa/qa/specs/features/browser_ui/3_create/repository/push_over_http_file_size_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/repository/push_over_http_file_size_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
- context 'Create', :requires_admin do
+ RSpec.describe 'Create', :requires_admin do
describe 'push after setting the file size limit via admin/application_settings' do
# Note: The file size limits in this test should be greater than the limits in
# ee/browser_ui/3_create/repository/push_rules_spec to prevent that test from
diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/push_over_http_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/push_over_http_spec.rb
index 6c0d55cc69a..b918b2ff268 100644
--- a/qa/qa/specs/features/browser_ui/3_create/repository/push_over_http_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/repository/push_over_http_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
- context 'Create' do
+ RSpec.describe 'Create' do
describe 'Git push over HTTP', :ldap_no_tls do
it 'user pushes code to the repository' do
Flow::Login.sign_in
diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/push_protected_branch_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/push_protected_branch_spec.rb
index 455db4d811d..3e1e470d8c3 100644
--- a/qa/qa/specs/features/browser_ui/3_create/repository/push_protected_branch_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/repository/push_protected_branch_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
- context 'Create' do
+ RSpec.describe 'Create' do
describe 'Protected branch support', :ldap_no_tls do
let(:branch_name) { 'protected-branch' }
let(:commit_message) { 'Protected push commit message' }
diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/ssh_key_support_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/ssh_key_support_spec.rb
new file mode 100644
index 00000000000..d67e4a4ea83
--- /dev/null
+++ b/qa/qa/specs/features/browser_ui/3_create/repository/ssh_key_support_spec.rb
@@ -0,0 +1,35 @@
+# frozen_string_literal: true
+
+module QA
+ RSpec.describe 'SSH keys support', :smoke do
+ key_title = "key for ssh tests #{Time.now.to_f}"
+ key = nil
+
+ before do
+ Flow::Login.sign_in
+ end
+
+ it 'user can add an SSH key' do
+ key = Resource::SSHKey.fabricate_via_browser_ui! do |resource|
+ resource.title = key_title
+ end
+
+ expect(page).to have_content(key.title)
+ expect(page).to have_content(key.md5_fingerprint)
+ end
+
+ # Note this context ensures that the example it contains is executed after the example above. Be aware of the order of execution if you add new examples in either context.
+ context 'after adding an ssh key' do
+ it 'can delete an ssh key' do
+ Page::Main::Menu.perform(&:click_settings_link)
+ Page::Profile::Menu.perform(&:click_ssh_keys)
+ Page::Profile::SSHKeys.perform do |ssh_keys|
+ ssh_keys.remove_key(key_title)
+ end
+
+ expect(page).not_to have_content("Title: #{key_title}")
+ expect(page).not_to have_content(key.md5_fingerprint)
+ end
+ end
+ end
+end
diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/use_ssh_key_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/use_ssh_key_spec.rb
index 1a3c6d03098..e91717b0f5f 100644
--- a/qa/qa/specs/features/browser_ui/3_create/repository/use_ssh_key_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/repository/use_ssh_key_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
- context 'Create' do
+ RSpec.describe 'Create' do
describe 'SSH key support' do
# Note: If you run this test against GDK make sure you've enabled sshd
# See: https://gitlab.com/gitlab-org/gitlab-qa/blob/master/docs/run_qa_against_gdk.md
diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/user_views_commit_diff_patch_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/user_views_commit_diff_patch_spec.rb
index aee62bacfa8..cf91b829817 100644
--- a/qa/qa/specs/features/browser_ui/3_create/repository/user_views_commit_diff_patch_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/repository/user_views_commit_diff_patch_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
- context 'Create' do
+ RSpec.describe 'Create' do
describe 'Commit data' do
before(:context) do
# Get the user's details to confirm they're included in the email patch
diff --git a/qa/qa/specs/features/browser_ui/3_create/snippet/add_comment_to_snippet_spec.rb b/qa/qa/specs/features/browser_ui/3_create/snippet/add_comment_to_snippet_spec.rb
new file mode 100644
index 00000000000..a867d9cb973
--- /dev/null
+++ b/qa/qa/specs/features/browser_ui/3_create/snippet/add_comment_to_snippet_spec.rb
@@ -0,0 +1,81 @@
+# frozen_string_literal: true
+
+module QA
+ RSpec.describe 'Create' do
+ describe 'Adding comments on snippets' do
+ let(:comment_author) { Resource::User.fabricate_or_use(Runtime::Env.gitlab_qa_username_1, Runtime::Env.gitlab_qa_password_1) }
+ let(:comment_content) { 'Comment 123' }
+ let(:edited_comment_content) { 'Nice snippet!' }
+
+ let(:personal_snippet) do
+ Resource::Snippet.fabricate! do |snippet|
+ snippet.title = 'Personal snippet with a comment'
+ end
+ end
+
+ let(:project_snippet) do
+ Resource::ProjectSnippet.fabricate! do |snippet|
+ snippet.title = 'Project snippet with a comment'
+ end
+ end
+
+ before do
+ Flow::Login.sign_in
+ end
+
+ shared_examples 'comments on snippets' do |snippet_type|
+ it "adds, edits, and deletes a comment on a #{snippet_type}" do
+ send(snippet_type)
+
+ Page::Main::Menu.perform(&:sign_out)
+
+ Flow::Login.sign_in(as: comment_author)
+
+ send(snippet_type).visit!
+
+ create_comment
+ verify_comment_content(comment_author.username, comment_content)
+
+ edit_comment
+ verify_comment_content(comment_author.username, edited_comment_content)
+
+ delete_comment
+ verify_comment_deleted
+ end
+ end
+
+ it_behaves_like 'comments on snippets', :personal_snippet
+ it_behaves_like 'comments on snippets', :project_snippet
+
+ def create_comment
+ Page::Dashboard::Snippet::Show.perform do |snippet|
+ snippet.add_comment(comment_content)
+ end
+ end
+
+ def edit_comment
+ Page::Dashboard::Snippet::Show.perform do |snippet|
+ snippet.edit_comment(edited_comment_content)
+ end
+ end
+
+ def delete_comment
+ Page::Dashboard::Snippet::Show.perform do |snippet|
+ snippet.delete_comment(edited_comment_content)
+ end
+ end
+
+ def verify_comment_content(author, comment_content)
+ Page::Dashboard::Snippet::Show.perform do |comment|
+ expect(comment).to have_comment_author(author)
+ expect(comment).to have_comment_content(comment_content)
+ end
+ end
+
+ def verify_comment_deleted
+ expect(page).not_to have_content(comment_author.username)
+ expect(page).not_to have_content(edited_comment_content)
+ end
+ end
+ end
+end
diff --git a/qa/qa/specs/features/browser_ui/3_create/snippet/clone_push_pull_personal_snippet_spec.rb b/qa/qa/specs/features/browser_ui/3_create/snippet/clone_push_pull_personal_snippet_spec.rb
index 341ff39fdf1..e6589851dd9 100644
--- a/qa/qa/specs/features/browser_ui/3_create/snippet/clone_push_pull_personal_snippet_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/snippet/clone_push_pull_personal_snippet_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
- context 'Create' do
+ RSpec.describe 'Create' do
describe 'Version control for personal snippets' do
let(:new_file) { 'new_snippet_file' }
let(:changed_content) { 'changes' }
diff --git a/qa/qa/specs/features/browser_ui/3_create/snippet/clone_push_pull_project_snippet_spec.rb b/qa/qa/specs/features/browser_ui/3_create/snippet/clone_push_pull_project_snippet_spec.rb
index a3011550db8..1660944fccd 100644
--- a/qa/qa/specs/features/browser_ui/3_create/snippet/clone_push_pull_project_snippet_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/snippet/clone_push_pull_project_snippet_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
- context 'Create' do
+ RSpec.describe 'Create' do
describe 'Version control for project snippets' do
let(:new_file) { 'new_snippet_file' }
let(:changed_content) { 'changes' }
diff --git a/qa/qa/specs/features/browser_ui/3_create/snippet/create_personal_snippet_spec.rb b/qa/qa/specs/features/browser_ui/3_create/snippet/create_personal_snippet_spec.rb
index 451a7847f8b..d2b86904cd3 100644
--- a/qa/qa/specs/features/browser_ui/3_create/snippet/create_personal_snippet_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/snippet/create_personal_snippet_spec.rb
@@ -1,27 +1,30 @@
# frozen_string_literal: true
module QA
- context 'Create', :smoke do
+ RSpec.describe 'Create', :smoke do
describe 'Personal snippet creation' do
it 'User creates a personal snippet' do
Flow::Login.sign_in
- Page::Main::Menu.perform(&:go_to_snippets)
+ Page::Main::Menu.perform do |menu|
+ menu.go_to_more_dropdown_option(:snippets_link)
+ end
Resource::Snippet.fabricate_via_browser_ui! do |snippet|
snippet.title = 'Snippet title'
snippet.description = 'Snippet description'
snippet.visibility = 'Private'
- snippet.file_name = 'New snippet file name'
- snippet.file_content = 'Snippet file text'
+ snippet.file_name = 'ruby_file.rb'
+ snippet.file_content = 'File.read("test.txt").split(/\n/)'
end
Page::Dashboard::Snippet::Show.perform do |snippet|
expect(snippet).to have_snippet_title('Snippet title')
expect(snippet).to have_snippet_description('Snippet description')
expect(snippet).to have_visibility_type(/private/i)
- expect(snippet).to have_file_name('New snippet file name')
- expect(snippet).to have_file_content('Snippet file text')
+ expect(snippet).to have_file_name('ruby_file.rb')
+ expect(snippet).to have_file_content('File.read("test.txt").split(/\n/)')
+ expect(snippet).to have_syntax_highlighting('ruby')
end
end
end
diff --git a/qa/qa/specs/features/browser_ui/3_create/snippet/create_project_snippet_spec.rb b/qa/qa/specs/features/browser_ui/3_create/snippet/create_project_snippet_spec.rb
index 8fc4427bda7..05795e9b51e 100644
--- a/qa/qa/specs/features/browser_ui/3_create/snippet/create_project_snippet_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/snippet/create_project_snippet_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
- context 'Create' do # to be converted to a smoke test once proved to be stable
+ RSpec.describe 'Create' do # to be converted to a smoke test once proved to be stable
describe 'Project snippet creation' do
it 'User creates a project snippet' do
Flow::Login.sign_in
diff --git a/qa/qa/specs/features/browser_ui/3_create/web_ide/add_file_template_spec.rb b/qa/qa/specs/features/browser_ui/3_create/web_ide/add_file_template_spec.rb
index 7c9db5ee496..1e3cb0e2ffc 100644
--- a/qa/qa/specs/features/browser_ui/3_create/web_ide/add_file_template_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/web_ide/add_file_template_spec.rb
@@ -1,7 +1,9 @@
# frozen_string_literal: true
+require 'securerandom'
+
module QA
- context 'Create' do
+ RSpec.describe 'Create' do
describe 'Web IDE file templates' do
include Runtime::Fixtures
@@ -53,10 +55,11 @@ module QA
ide.create_new_file_from_template template[:file_name], template[:name]
expect(ide.has_file?(template[:file_name])).to be_truthy
-
expect(ide).to have_button('Undo')
expect(ide).to have_normalized_ws_text(content[0..100])
+ ide.rename_file(template[:file_name], "#{SecureRandom.hex(8)}/#{template[:file_name]}")
+
ide.commit_changes
expect(ide).to have_content(template[:file_name])
diff --git a/qa/qa/specs/features/browser_ui/3_create/web_ide/create_first_file_in_web_ide_spec.rb b/qa/qa/specs/features/browser_ui/3_create/web_ide/create_first_file_in_web_ide_spec.rb
index 3bf6e156967..fbf70153e1d 100644
--- a/qa/qa/specs/features/browser_ui/3_create/web_ide/create_first_file_in_web_ide_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/web_ide/create_first_file_in_web_ide_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
- context 'Create' do
+ RSpec.describe 'Create' do
describe 'First file using Web IDE' do
let(:project) do
Resource::Project.fabricate_via_api! do |project|
diff --git a/qa/qa/specs/features/browser_ui/3_create/web_ide/review_merge_request_spec.rb b/qa/qa/specs/features/browser_ui/3_create/web_ide/review_merge_request_spec.rb
index ed988bdf046..7e0d8822101 100644
--- a/qa/qa/specs/features/browser_ui/3_create/web_ide/review_merge_request_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/web_ide/review_merge_request_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
- context 'Create' do
+ RSpec.describe 'Create' do
describe 'Review a merge request in Web IDE' do
let(:new_file) { 'awesome_new_file.txt' }
let(:original_text) { 'Text' }
diff --git a/qa/qa/specs/features/browser_ui/3_create/wiki/project_based_content_creation_spec.rb b/qa/qa/specs/features/browser_ui/3_create/wiki/project_based_content_creation_spec.rb
index 1d0c8ee60d4..77a5998362c 100644
--- a/qa/qa/specs/features/browser_ui/3_create/wiki/project_based_content_creation_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/wiki/project_based_content_creation_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
- context 'Create' do
+ RSpec.describe 'Create' do
context 'Wiki' do
describe 'testing wiki content creation inside a project' do
let(:new_wiki_title) { "just_another_wiki_page" }
diff --git a/qa/qa/specs/features/browser_ui/3_create/wiki/project_based_content_manipulation_spec.rb b/qa/qa/specs/features/browser_ui/3_create/wiki/project_based_content_manipulation_spec.rb
index 10370c80476..dbc7798a594 100644
--- a/qa/qa/specs/features/browser_ui/3_create/wiki/project_based_content_manipulation_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/wiki/project_based_content_manipulation_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
- context 'Create' do
+ RSpec.describe 'Create' do
context 'Wiki' do
describe 'testing wiki content manipulation inside a project' do
let(:new_wiki_title) { "just_another_wiki_page" }
diff --git a/qa/qa/specs/features/browser_ui/3_create/wiki/project_based_list_spec.rb b/qa/qa/specs/features/browser_ui/3_create/wiki/project_based_list_spec.rb
new file mode 100644
index 00000000000..d7f59abc361
--- /dev/null
+++ b/qa/qa/specs/features/browser_ui/3_create/wiki/project_based_list_spec.rb
@@ -0,0 +1,73 @@
+# frozen_string_literal: true
+
+module QA
+ RSpec.describe 'Create' do
+ context 'Wiki' do
+ let(:small_number_of_pages) { 5 }
+ let(:large_number_of_pages) { 15 }
+ let(:random_page) { "bulk_#{rand(0..4)}" }
+
+ let(:small_wiki) { create_wiki_pages small_number_of_pages }
+ let(:large_wiki) { create_wiki_pages large_number_of_pages }
+
+ before do
+ Flow::Login.sign_in
+ end
+
+ context 'Sidebar' do
+ it 'has all expected links that work' do
+ small_wiki.visit!
+
+ small_number_of_pages.times do |index|
+ Page::Project::Wiki::Show.perform do |list|
+ expect(list).to have_page_listed "bulk_#{index}"
+ end
+ end
+
+ Page::Project::Wiki::Show.perform do |list|
+ list.click_page_link random_page
+ end
+
+ Page::Project::Wiki::Show.perform do |wiki|
+ expect(wiki).to have_title random_page
+ end
+ end
+ end
+
+ context 'Page List' do
+ it 'has all expected links that work' do
+ large_wiki.visit!
+
+ Page::Project::Wiki::Show.perform(&:click_view_all_pages)
+
+ large_number_of_pages.times do |index|
+ Page::Project::Wiki::List.perform do |list|
+ expect(list).to have_page_listed "bulk_#{index}"
+ end
+ end
+
+ Page::Project::Wiki::List.perform do |list|
+ list.click_page_link random_page
+ end
+
+ Page::Project::Wiki::Show.perform do |wiki|
+ expect(wiki).to have_title random_page
+ end
+ end
+ end
+
+ private
+
+ def create_wiki_pages(no_of_pages)
+ wiki = Resource::Wiki::ProjectPage.fabricate_via_api!
+ no_of_pages.times do |index|
+ Resource::Wiki::ProjectPage.fabricate_via_api! do |page|
+ page.title = "bulk_#{index}"
+ page.project = wiki.project
+ end
+ end
+ wiki
+ end
+ end
+ end
+end
diff --git a/qa/qa/specs/features/browser_ui/3_create/wiki/project_based_page_deletion_spec.rb b/qa/qa/specs/features/browser_ui/3_create/wiki/project_based_page_deletion_spec.rb
new file mode 100644
index 00000000000..923c7332748
--- /dev/null
+++ b/qa/qa/specs/features/browser_ui/3_create/wiki/project_based_page_deletion_spec.rb
@@ -0,0 +1,26 @@
+# frozen_string_literal: true
+
+module QA
+ RSpec.describe 'Create' do
+ context 'Wiki' do
+ let(:initial_wiki) { Resource::Wiki::ProjectPage.fabricate_via_api! }
+
+ before do
+ Flow::Login.sign_in
+ end
+
+ context 'Page deletion' do
+ it 'has removed the deleted page correctly' do
+ initial_wiki.visit!
+
+ Page::Project::Wiki::Show.perform(&:click_edit)
+ Page::Project::Wiki::Edit.perform(&:delete_page)
+
+ Page::Project::Wiki::Show.perform do |wiki|
+ expect(wiki).to have_no_page
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/specs/features/browser_ui/4_verify/ci_variable/add_remove_ci_variable_spec.rb b/qa/qa/specs/features/browser_ui/4_verify/ci_variable/add_remove_ci_variable_spec.rb
index f05634bc3c8..41baaa02544 100644
--- a/qa/qa/specs/features/browser_ui/4_verify/ci_variable/add_remove_ci_variable_spec.rb
+++ b/qa/qa/specs/features/browser_ui/4_verify/ci_variable/add_remove_ci_variable_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
- context 'Verify' do
+ RSpec.describe 'Verify', quarantine: { issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/229724', type: :investigating } do
describe 'Add or Remove CI variable via UI', :smoke do
let!(:project) do
Resource::Project.fabricate_via_api! do |project|
diff --git a/qa/qa/specs/features/browser_ui/4_verify/pipeline/create_and_process_pipeline_spec.rb b/qa/qa/specs/features/browser_ui/4_verify/pipeline/create_and_process_pipeline_spec.rb
index 2502e8beda7..68b4a38a043 100644
--- a/qa/qa/specs/features/browser_ui/4_verify/pipeline/create_and_process_pipeline_spec.rb
+++ b/qa/qa/specs/features/browser_ui/4_verify/pipeline/create_and_process_pipeline_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
- context 'Verify', :docker, :runner do
+ RSpec.describe 'Verify', :docker, :runner do
describe 'Pipeline creation and processing' do
let(:executor) { "qa-runner-#{Time.now.to_i}" }
let(:max_wait) { 30 }
diff --git a/qa/qa/specs/features/browser_ui/4_verify/runner/register_runner_spec.rb b/qa/qa/specs/features/browser_ui/4_verify/runner/register_runner_spec.rb
index f8a589ad93b..82b15acb664 100644
--- a/qa/qa/specs/features/browser_ui/4_verify/runner/register_runner_spec.rb
+++ b/qa/qa/specs/features/browser_ui/4_verify/runner/register_runner_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
- context 'Verify', :docker, :runner do
+ RSpec.describe 'Verify', :docker, :runner do
describe 'Runner registration' do
let(:executor) { "qa-runner-#{Time.now.to_i}" }
let!(:runner) do
diff --git a/qa/qa/specs/features/browser_ui/4_verify/testing/view_code_coverage_spec.rb b/qa/qa/specs/features/browser_ui/4_verify/testing/view_code_coverage_spec.rb
index 4d549dde858..0436da40982 100644
--- a/qa/qa/specs/features/browser_ui/4_verify/testing/view_code_coverage_spec.rb
+++ b/qa/qa/specs/features/browser_ui/4_verify/testing/view_code_coverage_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
- context 'Verify', :docker, :runner do
+ RSpec.describe 'Verify', :docker, :runner do
describe 'Code coverage statistics' do
let(:simplecov) { '\(\d+.\d+\%\) covered' }
let(:executor) { "qa-runner-#{Time.now.to_i}" }
diff --git a/qa/qa/specs/features/browser_ui/6_release/deploy_key/add_deploy_key_spec.rb b/qa/qa/specs/features/browser_ui/6_release/deploy_key/add_deploy_key_spec.rb
index 89aba112407..d67fd96d338 100644
--- a/qa/qa/specs/features/browser_ui/6_release/deploy_key/add_deploy_key_spec.rb
+++ b/qa/qa/specs/features/browser_ui/6_release/deploy_key/add_deploy_key_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
- context 'Release' do
+ RSpec.describe 'Release' do
describe 'Deploy key creation' do
it 'user adds a deploy key' do
Flow::Login.sign_in
diff --git a/qa/qa/specs/features/browser_ui/6_release/deploy_key/clone_using_deploy_key_spec.rb b/qa/qa/specs/features/browser_ui/6_release/deploy_key/clone_using_deploy_key_spec.rb
index 73f2fef5f1d..18eb52830a2 100644
--- a/qa/qa/specs/features/browser_ui/6_release/deploy_key/clone_using_deploy_key_spec.rb
+++ b/qa/qa/specs/features/browser_ui/6_release/deploy_key/clone_using_deploy_key_spec.rb
@@ -3,7 +3,7 @@
require 'digest/sha1'
module QA
- context 'Release', :docker, :runner do
+ RSpec.describe 'Release', :docker, :runner do
describe 'Git clone using a deploy key' do
before do
Flow::Login.sign_in
@@ -20,7 +20,7 @@ module QA
resource.project = @project
resource.name = @runner_name
resource.tags = %w[qa docker]
- resource.image = 'gitlab/gitlab-runner:ubuntu'
+ resource.image = 'gitlab/gitlab-runner:alpine'
end
end
@@ -51,6 +51,7 @@ module QA
gitlab_ci = <<~YAML
cat-config:
script:
+ - apk add --update --no-cache openssh-client
- mkdir -p ~/.ssh
- ssh-keyscan -p #{@repository_location.port} #{@repository_location.host} >> ~/.ssh/known_hosts
- eval $(ssh-agent -s)
diff --git a/qa/qa/specs/features/browser_ui/6_release/deploy_token/add_deploy_token_spec.rb b/qa/qa/specs/features/browser_ui/6_release/deploy_token/add_deploy_token_spec.rb
index a07f0bd5e54..9bee5c5ee08 100644
--- a/qa/qa/specs/features/browser_ui/6_release/deploy_token/add_deploy_token_spec.rb
+++ b/qa/qa/specs/features/browser_ui/6_release/deploy_token/add_deploy_token_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
- context 'Release', quarantine: { issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/213222', type: :flaky } do
+ RSpec.describe 'Release', quarantine: { issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/213222', type: :flaky } do
describe 'Deploy token creation' do
it 'user adds a deploy token' do
Flow::Login.sign_in
diff --git a/qa/qa/specs/features/browser_ui/6_release/pipeline/parent_child_pipelines_dependent_relationship_spec.rb b/qa/qa/specs/features/browser_ui/6_release/pipeline/parent_child_pipelines_dependent_relationship_spec.rb
index e1d8c50ab75..673125c90f2 100644
--- a/qa/qa/specs/features/browser_ui/6_release/pipeline/parent_child_pipelines_dependent_relationship_spec.rb
+++ b/qa/qa/specs/features/browser_ui/6_release/pipeline/parent_child_pipelines_dependent_relationship_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
- context 'Release', :docker, quarantine: { issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/217250', type: :investigating } do
+ RSpec.describe 'Release', :docker, :runner, :reliable do
describe 'Parent-child pipelines dependent relationship' do
let!(:project) do
Resource::Project.fabricate_via_api! do |project|
@@ -29,12 +29,8 @@ module QA
view_pipelines
Page::Project::Pipeline::Show.perform do |parent_pipeline|
+ expect(parent_pipeline).to have_child_pipeline
expect(parent_pipeline).to have_passed
-
- parent_pipeline.retry_on_exception(sleep_interval: 1.0) do
- parent_pipeline.click_linked_job(project.name)
- end
- expect(parent_pipeline).to have_job("child_job")
end
end
@@ -43,12 +39,8 @@ module QA
view_pipelines
Page::Project::Pipeline::Show.perform do |parent_pipeline|
+ expect(parent_pipeline).to have_child_pipeline
expect(parent_pipeline).to have_failed
-
- parent_pipeline.retry_on_exception(sleep_interval: 1.0) do
- parent_pipeline.click_linked_job(project.name)
- end
- expect(parent_pipeline).to have_job("child_job")
end
end
diff --git a/qa/qa/specs/features/browser_ui/6_release/pipeline/parent_child_pipelines_independent_relationship_spec.rb b/qa/qa/specs/features/browser_ui/6_release/pipeline/parent_child_pipelines_independent_relationship_spec.rb
index c365e084991..05b9859f112 100644
--- a/qa/qa/specs/features/browser_ui/6_release/pipeline/parent_child_pipelines_independent_relationship_spec.rb
+++ b/qa/qa/specs/features/browser_ui/6_release/pipeline/parent_child_pipelines_independent_relationship_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
- context 'Release', :docker, quarantine: { issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/217250', type: :investigating } do
+ RSpec.describe 'Release', :docker, :runner, :reliable do
describe 'Parent-child pipelines independent relationship' do
let!(:project) do
Resource::Project.fabricate_via_api! do |project|
@@ -29,12 +29,8 @@ module QA
view_pipelines
Page::Project::Pipeline::Show.perform do |parent_pipeline|
+ expect(parent_pipeline).to have_child_pipeline
expect(parent_pipeline).to have_passed
-
- parent_pipeline.retry_on_exception(reload: true, sleep_interval: 1.0) do
- parent_pipeline.click_linked_job(project.name)
- end
- expect(parent_pipeline).to have_job("child_job")
end
end
@@ -43,12 +39,8 @@ module QA
view_pipelines
Page::Project::Pipeline::Show.perform do |parent_pipeline|
+ expect(parent_pipeline).to have_child_pipeline
expect(parent_pipeline).to have_passed
-
- parent_pipeline.retry_on_exception(reload: true, sleep_interval: 1.0) do
- parent_pipeline.click_linked_job(project.name)
- end
- expect(parent_pipeline).to have_job("child_job")
end
end
diff --git a/qa/qa/specs/features/browser_ui/7_configure/auto_devops/create_project_with_auto_devops_spec.rb b/qa/qa/specs/features/browser_ui/7_configure/auto_devops/create_project_with_auto_devops_spec.rb
index 292fc40bec4..ad87ee173f5 100644
--- a/qa/qa/specs/features/browser_ui/7_configure/auto_devops/create_project_with_auto_devops_spec.rb
+++ b/qa/qa/specs/features/browser_ui/7_configure/auto_devops/create_project_with_auto_devops_spec.rb
@@ -3,7 +3,7 @@
require 'pathname'
module QA
- context 'Configure' do
+ RSpec.describe 'Configure' do
let(:project) do
Resource::Project.fabricate_via_api! do |project|
project.name = Runtime::Env.auto_devops_project_name || 'autodevops-project'
diff --git a/qa/qa/specs/features/browser_ui/7_configure/kubernetes/kubernetes_integration_spec.rb b/qa/qa/specs/features/browser_ui/7_configure/kubernetes/kubernetes_integration_spec.rb
index 0e9c369e97f..5073b715341 100644
--- a/qa/qa/specs/features/browser_ui/7_configure/kubernetes/kubernetes_integration_spec.rb
+++ b/qa/qa/specs/features/browser_ui/7_configure/kubernetes/kubernetes_integration_spec.rb
@@ -1,8 +1,8 @@
# frozen_string_literal: true
module QA
- context 'Configure' do
- describe 'Kubernetes Cluster Integration', :orchestrated, :kubernetes, :requires_admin, :skip_live_env do
+ RSpec.describe 'Configure' do
+ describe 'Kubernetes Cluster Integration', :orchestrated, :kubernetes, :requires_admin, :skip_live_env, quarantine: { issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/225315', type: :flaky } do
context 'Project Clusters' do
let!(:cluster) { Service::KubernetesCluster.new(provider_class: Service::ClusterProvider::K3s).create! }
let(:project) do
diff --git a/qa/qa/specs/features/browser_ui/8_monitor/all_monitor_core_features_spec.rb b/qa/qa/specs/features/browser_ui/8_monitor/all_monitor_core_features_spec.rb
index a9ed6651069..9cfdc4277a7 100644
--- a/qa/qa/specs/features/browser_ui/8_monitor/all_monitor_core_features_spec.rb
+++ b/qa/qa/specs/features/browser_ui/8_monitor/all_monitor_core_features_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
- context 'Monitor' do
+ RSpec.describe 'Monitor' do
describe 'with Prometheus in a Gitlab-managed cluster', :orchestrated, :kubernetes do
before :all do
@cluster = Service::KubernetesCluster.new.create!
@@ -64,6 +64,19 @@ module QA
end
end
+ it 'observes cluster health graph' do
+ Page::Project::Menu.perform(&:go_to_operations_kubernetes)
+
+ Page::Project::Operations::Kubernetes::Index.perform do |cluster_list|
+ cluster_list.click_on_cluster(@cluster)
+ end
+
+ Page::Project::Operations::Kubernetes::Show.perform do |cluster_panel|
+ cluster_panel.open_health
+ cluster_panel.wait_for_cluster_health
+ end
+ end
+
private
def deploy_project_with_prometheus
diff --git a/qa/qa/specs/features/browser_ui/non_devops/performance_bar_spec.rb b/qa/qa/specs/features/browser_ui/non_devops/performance_bar_spec.rb
index c6d5fba919b..73bb6aeb5fd 100644
--- a/qa/qa/specs/features/browser_ui/non_devops/performance_bar_spec.rb
+++ b/qa/qa/specs/features/browser_ui/non_devops/performance_bar_spec.rb
@@ -1,8 +1,8 @@
# frozen_string_literal: true
module QA
- context 'Non-devops' do
- describe 'Performance bar display', :requires_admin do
+ RSpec.describe 'Non-devops' do
+ describe 'Performance bar display', :requires_admin, :skip_live_env do
context 'when logged in as an admin user' do
# performance metrics: pg, gitaly, redis, rugged (feature flagged), total (not always provided)
let(:minimum_metrics_count) { 3 }
diff --git a/qa/qa/specs/helpers/quarantine.rb b/qa/qa/specs/helpers/quarantine.rb
index dd3a50ac128..6a3becf0ee5 100644
--- a/qa/qa/specs/helpers/quarantine.rb
+++ b/qa/qa/specs/helpers/quarantine.rb
@@ -18,6 +18,10 @@ module QA
config.before do |example|
Quarantine.skip_or_run_quarantined_tests_or_contexts(config.inclusion_filter.rules, example)
+
+ if example.metadata.key?(:only)
+ skip('Test is not compatible with this environment') unless Runtime::Env.address_matches?(example.metadata[:only])
+ end
end
end
end
@@ -46,21 +50,15 @@ module QA
skip("Only running tests tagged with :quarantine and any of #{included_filters.keys}") if should_skip_when_focused?(example.metadata, included_filters)
else
if example.metadata.key?(:quarantine)
- quarantine_message = %w(In quarantine)
quarantine_tag = example.metadata[:quarantine]
- if !!quarantine_tag
- quarantine_message << case quarantine_tag
- when String
- ": #{quarantine_tag}"
- when Hash
- ": #{quarantine_tag[:issue]}"
- else
- ''
- end
+ if quarantine_tag&.is_a?(Hash) && quarantine_tag&.key?(:only)
+ # If the :quarantine hash contains :only, we respect that.
+ # For instance `quarantine: { only: { subdomain: :staging } }` will only quarantine the test when it runs against staging.
+ return unless Runtime::Env.address_matches?(quarantine_tag[:only])
end
- skip(quarantine_message.join(' ').strip)
+ skip(quarantine_message(quarantine_tag))
end
end
end
@@ -69,6 +67,20 @@ module QA
filter.reject { |key, _| key == :quarantine }
end
+ def quarantine_message(quarantine_tag)
+ quarantine_message = %w(In quarantine)
+ quarantine_message << case quarantine_tag
+ when String
+ ": #{quarantine_tag}"
+ when Hash
+ quarantine_tag.key?(:issue) ? ": #{quarantine_tag[:issue]}" : ''
+ else
+ ''
+ end
+
+ quarantine_message.join(' ').strip
+ end
+
# Checks if a test or context should be skipped.
#
# Returns true if
diff --git a/qa/qa/support/dates.rb b/qa/qa/support/dates.rb
index 47fc721afc1..3d1f146730b 100644
--- a/qa/qa/support/dates.rb
+++ b/qa/qa/support/dates.rb
@@ -11,6 +11,11 @@ module QA
current_date.next_month.strftime("%Y/%m/%d")
end
+ def format_date(date)
+ new_date = DateTime.strptime(date, "%Y/%m/%d")
+ new_date.strftime("%b %-d, %Y")
+ end
+
private
def current_date
diff --git a/qa/qa/support/otp.rb b/qa/qa/support/otp.rb
new file mode 100644
index 00000000000..0d7c394cf69
--- /dev/null
+++ b/qa/qa/support/otp.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+require 'rotp'
+
+module QA
+ module Support
+ class OTP
+ def initialize(secret)
+ @rotp = ROTP::TOTP.new(secret)
+ end
+
+ def fresh_otp
+ otps = []
+
+ # Fetches a fresh OTP and returns it only after rotp provides the same OTP twice
+ # An OTP is valid for 30 seconds so 70 attempts with 0.5 interval would ensure we complete 1 cycle
+ Support::Retrier.retry_until(max_attempts: 70, sleep_interval: 0.5) do
+ otps << @rotp.now
+ otps.size >= 3 && otps[-1] == otps[-2] && otps[-1] != otps[-3]
+ end
+
+ otps.last
+ end
+ end
+ end
+end
diff --git a/qa/spec/runtime/api/request_spec.rb b/qa/spec/runtime/api/request_spec.rb
index a20f1cf8559..8354eff6234 100644
--- a/qa/spec/runtime/api/request_spec.rb
+++ b/qa/spec/runtime/api/request_spec.rb
@@ -22,6 +22,12 @@ describe QA::Runtime::API::Request do
end
end
+ describe '#mask_url' do
+ it 'returns the full API request url with the token masked' do
+ expect(request.mask_url).to eq 'http://example.com/api/v4/users?private_token=[****]'
+ end
+ end
+
describe '#request_path' do
it 'prepends the api path' do
expect(request.request_path('/users')).to eq '/api/v4/users'
diff --git a/qa/spec/runtime/env_spec.rb b/qa/spec/runtime/env_spec.rb
index f2e5eb35871..0cfb9a70c88 100644
--- a/qa/spec/runtime/env_spec.rb
+++ b/qa/spec/runtime/env_spec.rb
@@ -341,7 +341,7 @@ describe QA::Runtime::Env do
end
end
- describe '.dot_com?' do
+ describe '.address_matches?' do
it 'returns true when url has .com' do
QA::Runtime::Scenario.define(:gitlab_address, "https://staging.gitlab.com")
@@ -351,7 +351,45 @@ describe QA::Runtime::Env do
it 'returns false when url does not have .com' do
QA::Runtime::Scenario.define(:gitlab_address, "https://gitlab.test")
- expect(described_class.dot_com?).to be_falsy
+ expect(described_class.dot_com?).to be_falsey
+ end
+
+ context 'with arguments' do
+ it 'returns true when :subdomain is set' do
+ QA::Runtime::Scenario.define(:gitlab_address, "https://staging.gitlab.com")
+
+ expect(described_class.dot_com?(subdomain: :staging)).to be_truthy
+ end
+
+ it 'matches multiple subdomains' do
+ QA::Runtime::Scenario.define(:gitlab_address, "https://staging.gitlab.com")
+
+ expect(described_class.address_matches?(subdomain: [:release, :staging])).to be_truthy
+ expect(described_class.address_matches?(:production, subdomain: [:release, :staging])).to be_truthy
+ end
+
+ it 'matches :production' do
+ QA::Runtime::Scenario.define(:gitlab_address, "https://gitlab.com/")
+
+ expect(described_class.address_matches?(:production)).to be_truthy
+ end
+
+ it 'doesnt match with mismatching switches' do
+ QA::Runtime::Scenario.define(:gitlab_address, 'https://gitlab.test')
+
+ aggregate_failures do
+ expect(described_class.address_matches?(tld: '.net')).to be_falsey
+ expect(described_class.address_matches?(:production)).to be_falsey
+ expect(described_class.address_matches?(subdomain: [:staging])).to be_falsey
+ expect(described_class.address_matches?(domain: 'example')).to be_falsey
+ end
+ end
+ end
+
+ it 'returns false for mismatching' do
+ QA::Runtime::Scenario.define(:gitlab_address, "https://staging.gitlab.com")
+
+ expect(described_class.address_matches?(:production)).to be_falsey
end
end
end
diff --git a/qa/spec/specs/helpers/quarantine_spec.rb b/qa/spec/specs/helpers/quarantine_spec.rb
index 1f09c3f73ac..9686a9771c4 100644
--- a/qa/spec/specs/helpers/quarantine_spec.rb
+++ b/qa/spec/specs/helpers/quarantine_spec.rb
@@ -124,7 +124,7 @@ describe QA::Specs::Helpers::Quarantine do
end
end
- describe '.skip_or_run_quarantined_tests' do
+ describe '.skip_or_run_quarantined_tests_or_contexts' do
context 'with no tag focused' do
before do
described_class.configure_rspec
@@ -148,6 +148,37 @@ describe QA::Specs::Helpers::Quarantine do
expect(group.examples.first.execution_result.status).to eq(:passed)
end
+ context 'with environment set' do
+ before do
+ QA::Runtime::Scenario.define(:gitlab_address, 'https://staging.gitlab.com')
+ described_class.configure_rspec
+ end
+
+ it 'is skipped when set on contexts or descriptions' do
+ group = describe_successfully 'Quarantined in staging', quarantine: { only: { subdomain: :staging } } do
+ it('runs in staging') {}
+ end
+
+ expect(group.examples.first.execution_result.status).to eq(:pending)
+ expect(group.examples.first.execution_result.pending_message)
+ .to eq('In quarantine')
+ end
+
+ it 'is skipped only in staging' do
+ group = describe_successfully do
+ it('skipped in staging', quarantine: { only: { subdomain: :staging } }) {}
+ it('runs in staging', quarantine: { only: :production }) {}
+ it('skipped in staging also', quarantine: { only: { subdomain: %i[release staging] } }) {}
+ it('runs in any env') {}
+ end
+
+ expect(group.examples[0].execution_result.status).to eq(:pending)
+ expect(group.examples[1].execution_result.status).to eq(:passed)
+ expect(group.examples[2].execution_result.status).to eq(:pending)
+ expect(group.examples[3].execution_result.status).to eq(:passed)
+ end
+ end
+
context 'quarantine message' do
shared_examples 'test with quarantine message' do |quarantine_tag|
it 'outputs the quarantine message' do
@@ -280,4 +311,94 @@ describe QA::Specs::Helpers::Quarantine do
end
end
end
+
+ describe 'running against specific environments' do
+ before do
+ QA::Runtime::Scenario.define(:gitlab_address, 'https://staging.gitlab.com')
+ described_class.configure_rspec
+ end
+
+ describe 'description and context blocks' do
+ context 'with environment set' do
+ it 'can apply to contexts or descriptions' do
+ group = describe_successfully 'Runs in staging', only: { subdomain: :staging } do
+ it('runs in staging') {}
+ end
+
+ expect(group.examples[0].execution_result.status).to eq(:passed)
+ end
+ end
+
+ context 'with different environment set' do
+ before do
+ QA::Runtime::Scenario.define(:gitlab_address, 'https://gitlab.com')
+ described_class.configure_rspec
+ end
+
+ it 'does not run against production' do
+ group = describe_successfully 'Runs in staging', :something, only: { subdomain: :staging } do
+ it('runs in staging') {}
+ end
+
+ expect(group.examples[0].execution_result.status).to eq(:pending)
+ end
+ end
+ end
+
+ it 'runs only in staging' do
+ group = describe_successfully do
+ it('runs in staging', only: { subdomain: :staging }) {}
+ it('doesnt run in staging', only: :production) {}
+ it('runs in staging also', only: { subdomain: %i[release staging] }) {}
+ it('runs in any env') {}
+ end
+
+ expect(group.examples[0].execution_result.status).to eq(:passed)
+ expect(group.examples[1].execution_result.status).to eq(:pending)
+ expect(group.examples[2].execution_result.status).to eq(:passed)
+ expect(group.examples[3].execution_result.status).to eq(:passed)
+ end
+
+ context 'custom env' do
+ before do
+ QA::Runtime::Scenario.define(:gitlab_address, 'https://release.gitlab.net')
+ end
+
+ it 'runs on a custom environment' do
+ group = describe_successfully do
+ it('runs on release gitlab net', only: { tld: '.net', subdomain: :release, domain: 'gitlab' } ) {}
+ it('does not run on release', only: :production ) {}
+ end
+
+ expect(group.examples.first.execution_result.status).to eq(:passed)
+ expect(group.examples.last.execution_result.status).to eq(:pending)
+ end
+ end
+
+ context 'production' do
+ before do
+ QA::Runtime::Scenario.define(:gitlab_address, 'https://gitlab.com/')
+ end
+
+ it 'runs on production' do
+ group = describe_successfully do
+ it('runs on prod', only: :production ) {}
+ it('does not run in prod', only: { subdomain: :staging }) {}
+ it('runs in prod and staging', only: { subdomain: /(staging.)?/, domain: 'gitlab' }) {}
+ end
+
+ expect(group.examples[0].execution_result.status).to eq(:passed)
+ expect(group.examples[1].execution_result.status).to eq(:pending)
+ expect(group.examples[2].execution_result.status).to eq(:passed)
+ end
+ end
+
+ it 'outputs a message for invalid environments' do
+ group = describe_successfully do
+ it('will skip', only: :production) {}
+ end
+
+ expect(group.examples.first.execution_result.pending_message).to match(/[Tt]est.*not compatible.*environment/)
+ end
+ end
end
diff --git a/rubocop/cop/api/grape_api_instance.rb b/rubocop/cop/api/grape_api_instance.rb
new file mode 100644
index 00000000000..de11b9ef3f6
--- /dev/null
+++ b/rubocop/cop/api/grape_api_instance.rb
@@ -0,0 +1,42 @@
+# frozen_string_literal: true
+
+module RuboCop
+ module Cop
+ module API
+ class GrapeAPIInstance < RuboCop::Cop::Cop
+ # This cop checks that APIs subclass Grape::API::Instance with Grape v1.3+.
+ #
+ # @example
+ #
+ # # bad
+ # module API
+ # class Projects < Grape::API
+ # end
+ # end
+ #
+ # # good
+ # module API
+ # class Projects < Grape::API::Instance
+ # end
+ # end
+ MSG = 'Inherit from Grape::API::Instance instead of Grape::API. ' \
+ 'For more details check the https://gitlab.com/gitlab-org/gitlab/-/issues/215230.'
+
+ def_node_matcher :grape_api_definition, <<~PATTERN
+ (class
+ (const _ _)
+ (const
+ (const nil? :Grape) :API)
+ ...
+ )
+ PATTERN
+
+ def on_class(node)
+ grape_api_definition(node) do
+ add_offense(node.children[1])
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/rubocop/cop/api/grape_array_missing_coerce.rb b/rubocop/cop/api/grape_array_missing_coerce.rb
new file mode 100644
index 00000000000..3d7a6a72d81
--- /dev/null
+++ b/rubocop/cop/api/grape_array_missing_coerce.rb
@@ -0,0 +1,83 @@
+# frozen_string_literal: true
+
+module RuboCop
+ module Cop
+ module API
+ class GrapeArrayMissingCoerce < RuboCop::Cop::Cop
+ # This cop checks that Grape API parameters using an Array type
+ # implement a coerce_with method:
+ #
+ # https://github.com/ruby-grape/grape/blob/master/UPGRADING.md#ensure-that-array-types-have-explicit-coercions
+ #
+ # @example
+ #
+ # # bad
+ # requires :values, type: Array[String]
+ #
+ # # good
+ # requires :values, type: Array[String], coerce_with: Validations::Types::CommaSeparatedToArray.coerce
+ #
+ # end
+ MSG = 'This Grape parameter defines an Array but is missing a coerce_with definition. ' \
+ 'For more details, see https://github.com/ruby-grape/grape/blob/master/UPGRADING.md#ensure-that-array-types-have-explicit-coercions'
+
+ def_node_matcher :grape_api_instance?, <<~PATTERN
+ (class
+ (const _ _)
+ (const
+ (const
+ (const nil? :Grape) :API) :Instance)
+ ...
+ )
+ PATTERN
+
+ def_node_matcher :grape_api_param_block?, <<~PATTERN
+ (send _ {:requires :optional}
+ (sym _)
+ $_)
+ PATTERN
+
+ def_node_matcher :grape_type_def?, <<~PATTERN
+ (sym :type)
+ PATTERN
+
+ def_node_matcher :grape_array_type?, <<~PATTERN
+ (send
+ (const nil? :Array) :[]
+ (const nil? _))
+ PATTERN
+
+ def_node_matcher :grape_coerce_with?, <<~PATTERN
+ (sym :coerce_with)
+ PATTERN
+
+ def on_class(node)
+ @grape_api ||= grape_api_instance?(node)
+ end
+
+ def on_send(node)
+ return unless @grape_api
+
+ match = grape_api_param_block?(node)
+
+ return unless match.is_a?(RuboCop::AST::HashNode)
+
+ is_array_type = false
+ has_coerce_method = false
+
+ match.each_pair do |first, second|
+ has_coerce_method ||= grape_coerce_with?(first)
+
+ if grape_type_def?(first) && grape_array_type?(second)
+ is_array_type = true
+ end
+ end
+
+ if is_array_type && !has_coerce_method
+ add_offense(node)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/rubocop/cop/graphql/authorize_types.rb b/rubocop/cop/graphql/authorize_types.rb
index 7aaa9299362..1dba719cdff 100644
--- a/rubocop/cop/graphql/authorize_types.rb
+++ b/rubocop/cop/graphql/authorize_types.rb
@@ -7,39 +7,30 @@ module RuboCop
MSG = 'Add an `authorize :ability` call to the type: '\
'https://docs.gitlab.com/ee/development/api_graphql_styleguide.html#type-authorization'
- TYPES_DIR = 'app/graphql/types'
-
# We want to exclude our own basetypes and scalars
- WHITELISTED_TYPES = %w[BaseEnum BaseScalar BasePermissionType MutationType
- QueryType GraphQL::Schema BaseUnion].freeze
+ ALLOWED_TYPES = %w[BaseEnum BaseScalar BasePermissionType MutationType
+ QueryType GraphQL::Schema BaseUnion].freeze
def_node_search :authorize?, <<~PATTERN
(send nil? :authorize ...)
PATTERN
def on_class(node)
- return unless in_type?(node)
- return if whitelisted?(class_constant(node))
- return if whitelisted?(superclass_constant(node))
+ return if allowed?(class_constant(node))
+ return if allowed?(superclass_constant(node))
add_offense(node, location: :expression) unless authorize?(node)
end
private
- def in_type?(node)
- path = node.location.expression.source_buffer.name
-
- path.include? TYPES_DIR
- end
-
- def whitelisted?(class_node)
+ def allowed?(class_node)
class_const = class_node&.const_name
return false unless class_const
return true if class_const.end_with?('Enum')
- WHITELISTED_TYPES.any? { |whitelisted| class_node.const_name.include?(whitelisted) }
+ ALLOWED_TYPES.any? { |allowed| class_node.const_name.include?(allowed) }
end
def class_constant(node)
diff --git a/rubocop/cop/migration/drop_table.rb b/rubocop/cop/migration/drop_table.rb
index 2a0f57c0c13..531cbb14021 100644
--- a/rubocop/cop/migration/drop_table.rb
+++ b/rubocop/cop/migration/drop_table.rb
@@ -17,6 +17,7 @@ module RuboCop
def on_def(node)
return unless in_deployment_migration?(node)
+ return if down_method?(node)
node.each_descendant(:send) do |send_node|
next unless offensible?(send_node)
@@ -27,6 +28,10 @@ module RuboCop
private
+ def down_method?(node)
+ node.method?(:down)
+ end
+
def offensible?(node)
drop_table?(node) || drop_table_in_execute?(node)
end
diff --git a/rubocop/cop/migration/with_lock_retries_disallowed_method.rb b/rubocop/cop/migration/with_lock_retries_disallowed_method.rb
index 309ddcc9746..9bf81e7db0c 100644
--- a/rubocop/cop/migration/with_lock_retries_disallowed_method.rb
+++ b/rubocop/cop/migration/with_lock_retries_disallowed_method.rb
@@ -10,6 +10,7 @@ module RuboCop
ALLOWED_MIGRATION_METHODS = %i[
create_table
+ create_hash_partitions
drop_table
add_foreign_key
remove_foreign_key
diff --git a/scripts/frontend/merge_coverage_frontend.js b/scripts/frontend/merge_coverage_frontend.js
index 507695b45e5..99034176b29 100644
--- a/scripts/frontend/merge_coverage_frontend.js
+++ b/scripts/frontend/merge_coverage_frontend.js
@@ -26,6 +26,6 @@ reportFiles
const context = createContext({ coverageMap: coverageMap, dir: 'coverage-frontend' });
-['json', 'lcov', 'text-summary', 'clover'].forEach(reporter => {
+['json', 'lcov', 'text-summary', 'clover', 'cobertura'].forEach(reporter => {
create(reporter, {}).execute(context);
});
diff --git a/scripts/frontend/prettier.js b/scripts/frontend/prettier.js
index 45cdef2ba86..7772f80c233 100644
--- a/scripts/frontend/prettier.js
+++ b/scripts/frontend/prettier.js
@@ -3,7 +3,7 @@ const prettier = require('prettier');
const fs = require('fs');
const { getStagedFiles } = require('./frontend_script_utils');
-const matchExtensions = ['js', 'vue'];
+const matchExtensions = ['js', 'vue', 'graphql'];
// This will improve glob performance by excluding certain directories.
// The .prettierignore file will also be respected, but after the glob has executed.
diff --git a/scripts/gitaly_test.rb b/scripts/gitaly_test.rb
index c69c4ea747b..54bf07b3773 100644
--- a/scripts/gitaly_test.rb
+++ b/scripts/gitaly_test.rb
@@ -6,8 +6,16 @@
require 'securerandom'
require 'socket'
+require 'logger'
module GitalyTest
+ LOGGER = begin
+ default_name = ENV['CI'] ? 'DEBUG' : 'WARN'
+ level_name = ENV['GITLAB_TESTING_LOG_LEVEL']&.upcase
+ level = Logger.const_get(level_name || default_name, true) # rubocop: disable Gitlab/ConstGetInheritFalse
+ Logger.new(STDOUT, level: level, formatter: ->(_, _, _, msg) { msg })
+ end
+
def tmp_tests_gitaly_dir
File.expand_path('../tmp/tests/gitaly', __dir__)
end
@@ -98,7 +106,7 @@ module GitalyTest
end
def check_gitaly_config!
- puts "Checking gitaly-ruby Gemfile..."
+ LOGGER.debug "Checking gitaly-ruby Gemfile...\n"
unless File.exist?(gemfile)
message = "#{gemfile} does not exist."
@@ -106,8 +114,9 @@ module GitalyTest
abort message
end
- puts 'Checking gitaly-ruby bundle...'
- abort 'bundle check failed' unless system(env, 'bundle', 'check', chdir: File.dirname(gemfile))
+ LOGGER.debug "Checking gitaly-ruby bundle...\n"
+ out = ENV['CI'] ? STDOUT : '/dev/null'
+ abort 'bundle check failed' unless system(env, 'bundle', 'check', out: out, chdir: File.dirname(gemfile))
end
def read_socket_path(service)
@@ -126,22 +135,22 @@ module GitalyTest
end
def try_connect!(service)
- print "Trying to connect to #{service}: "
+ LOGGER.debug "Trying to connect to #{service}: "
timeout = 20
delay = 0.1
socket = read_socket_path(service)
Integer(timeout / delay).times do
UNIXSocket.new(socket)
- puts ' OK'
+ LOGGER.debug " OK\n"
return
rescue Errno::ENOENT, Errno::ECONNREFUSED
- print '.'
+ LOGGER.debug '.'
sleep delay
end
- puts ' FAILED'
+ LOGGER.warn " FAILED to connect to #{service}\n"
raise "could not connect to #{socket}"
end
diff --git a/scripts/lint-doc.sh b/scripts/lint-doc.sh
index ca9747987df..72e6334d0fc 100755
--- a/scripts/lint-doc.sh
+++ b/scripts/lint-doc.sh
@@ -41,14 +41,15 @@ fi
# Do not use 'README.md', instead use 'index.md'
# Number of 'README.md's as of 2020-05-28
-NUMBER_READMES=45
+NUMBER_READMES=44
FIND_READMES=$(find doc/ -name "README.md" | wc -l)
echo '=> Checking for new README.md files...'
echo
if [ ${FIND_READMES} -ne $NUMBER_READMES ]
then
echo
- echo ' ✖ ERROR: New README.md file(s) detected, prefer index.md over README.md.' >&2
+ echo ' ✖ ERROR: The number of README.md file(s) has changed. Use index.md instead of README.md.' >&2
+ echo ' ✖ If removing a README.md file, update NUMBER_READMES in lint-doc.sh.' >&2
echo ' https://docs.gitlab.com/ee/development/documentation/styleguide.html#work-with-directories-and-files'
echo
((ERRORCODE++))
diff --git a/scripts/merge-simplecov b/scripts/merge-simplecov
index 746be3317a7..c00dae81c4d 100755
--- a/scripts/merge-simplecov
+++ b/scripts/merge-simplecov
@@ -2,6 +2,7 @@
require_relative '../spec/simplecov_env'
SimpleCovEnv.configure_profile
+SimpleCovEnv.configure_formatter
module SimpleCov
module ResultMerger
diff --git a/scripts/prepare_build.sh b/scripts/prepare_build.sh
index d2cc16f4f8b..1243609dc24 100644
--- a/scripts/prepare_build.sh
+++ b/scripts/prepare_build.sh
@@ -2,7 +2,7 @@
export SETUP_DB=${SETUP_DB:-true}
export USE_BUNDLE_INSTALL=${USE_BUNDLE_INSTALL:-true}
-export BUNDLE_INSTALL_FLAGS="--without=production --jobs=$(nproc) --path=vendor --retry=3 --quiet"
+export BUNDLE_INSTALL_FLAGS=${BUNDLE_INSTALL_FLAGS:-"--without=production --without=development --jobs=$(nproc) --path=vendor --retry=3 --quiet"}
if [ "$USE_BUNDLE_INSTALL" != "false" ]; then
bundle --version
@@ -11,7 +11,7 @@ if [ "$USE_BUNDLE_INSTALL" != "false" ]; then
# When we test multiple versions of PG in the same pipeline, we have a single `setup-test-env`
# job but the `pg` gem needs to be rebuilt since it includes extensions (https://guides.rubygems.org/gems-with-extensions).
# Uncomment the following line if multiple versions of PG are tested in the same pipeline.
- # run_timed_command "bundle pristine pg"
+ run_timed_command "bundle pristine pg"
fi
# Only install knapsack after bundle install! Otherwise oddly some native
diff --git a/scripts/review_apps/automated_cleanup.rb b/scripts/review_apps/automated_cleanup.rb
index a9659071a2f..e40c6cd7276 100755
--- a/scripts/review_apps/automated_cleanup.rb
+++ b/scripts/review_apps/automated_cleanup.rb
@@ -1,8 +1,8 @@
# frozen_string_literal: true
require 'gitlab'
-require_relative File.expand_path('../../lib/quality/helm3_client.rb', __dir__)
-require_relative File.expand_path('../../lib/quality/kubernetes_client.rb', __dir__)
+require_relative File.expand_path('../../tooling/lib/tooling/helm3_client.rb', __dir__)
+require_relative File.expand_path('../../tooling/lib/tooling/kubernetes_client.rb', __dir__)
class AutomatedCleanup
attr_reader :project_path, :gitlab_token
@@ -40,15 +40,15 @@ class AutomatedCleanup
end
def review_apps_namespace
- self.class.ee? ? 'review-apps-ee' : 'review-apps-ce'
+ 'review-apps'
end
def helm
- @helm ||= Quality::Helm3Client.new(namespace: review_apps_namespace)
+ @helm ||= Tooling::Helm3Client.new(namespace: review_apps_namespace)
end
def kubernetes
- @kubernetes ||= Quality::KubernetesClient.new(namespace: review_apps_namespace)
+ @kubernetes ||= Tooling::KubernetesClient.new(namespace: review_apps_namespace)
end
def perform_gitlab_environment_cleanup!(days_for_stop:, days_for_delete:)
@@ -76,7 +76,7 @@ class AutomatedCleanup
if deployed_at < delete_threshold
deleted_environment = delete_environment(environment, deployment)
if deleted_environment
- release = Quality::Helm3Client::Release.new(environment.slug, 1, deployed_at.to_s, nil, nil, review_apps_namespace)
+ release = Tooling::Helm3Client::Release.new(environment.slug, 1, deployed_at.to_s, nil, nil, review_apps_namespace)
releases_to_delete << release
end
else
@@ -157,11 +157,11 @@ class AutomatedCleanup
helm.delete(release_name: releases_names)
kubernetes.cleanup(release_name: releases_names, wait: false)
- rescue Quality::Helm3Client::CommandFailedError => ex
+ rescue Tooling::Helm3Client::CommandFailedError => ex
raise ex unless ignore_exception?(ex.message, IGNORED_HELM_ERRORS)
puts "Ignoring the following Helm error:\n#{ex}\n"
- rescue Quality::KubernetesClient::CommandFailedError => ex
+ rescue Tooling::KubernetesClient::CommandFailedError => ex
raise ex unless ignore_exception?(ex.message, IGNORED_KUBERNETES_ERRORS)
puts "Ignoring the following Kubernetes error:\n#{ex}\n"
diff --git a/scripts/review_apps/base-config.yaml b/scripts/review_apps/base-config.yaml
index 6fb6943fb90..82be2d3a691 100644
--- a/scripts/review_apps/base-config.yaml
+++ b/scripts/review_apps/base-config.yaml
@@ -7,7 +7,7 @@ global:
external-dns.alpha.kubernetes.io/ttl: 10
configureCertmanager: false
tls:
- secretName: tls-cert
+ secretName: review-apps-tls
initialRootPassword:
secret: shared-gitlab-initial-root-password
certmanager:
@@ -61,11 +61,11 @@ gitlab:
task-runner:
resources:
requests:
- cpu: 50m
- memory: 350M
+ cpu: 300m
+ memory: 800M
limits:
- cpu: 100m
- memory: 700M
+ cpu: 450m
+ memory: 1200M
webservice:
resources:
requests:
@@ -137,10 +137,10 @@ postgresql:
enabled: false
resources:
requests:
- cpu: 347m
+ cpu: 550m
memory: 250M
limits:
- cpu: 520m
+ cpu: 825m
memory: 375M
prometheus:
install: false
diff --git a/scripts/review_apps/gcp_cleanup.sh b/scripts/review_apps/gcp_cleanup.sh
index f289a50f629..3225631e8c7 100755
--- a/scripts/review_apps/gcp_cleanup.sh
+++ b/scripts/review_apps/gcp_cleanup.sh
@@ -11,7 +11,7 @@ function setup_gcp_dependencies() {
# These scripts require the following environment variables:
# - REVIEW_APPS_GCP_REGION - e.g `us-central1`
-# - KUBE_NAMESPACE - e.g `review-apps-ee`
+# - KUBE_NAMESPACE - e.g `review-apps`
function delete_firewall_rules() {
if [[ ${#@} -eq 0 ]]; then
diff --git a/scripts/review_apps/review-apps.sh b/scripts/review_apps/review-apps.sh
index 1214ee5f462..74291f6aef4 100755
--- a/scripts/review_apps/review-apps.sh
+++ b/scripts/review_apps/review-apps.sh
@@ -66,7 +66,7 @@ function kubectl_cleanup_release() {
local release="${2}"
echoinfo "Deleting all K8s resources matching '${release}'..." true
- kubectl --namespace "${namespace}" get ingress,svc,pdb,hpa,deploy,statefulset,job,pod,secret,configmap,pvc,secret,clusterrole,clusterrolebinding,role,rolebinding,sa,crd 2>&1 \
+ kubectl --namespace "${namespace}" get ingress,svc,pdb,hpa,deploy,statefulset,job,pod,secret,configmap,pvc,clusterrole,clusterrolebinding,role,rolebinding,sa,crd 2>&1 \
| grep "${release}" \
| awk '{print $1}' \
| xargs kubectl --namespace "${namespace}" delete \
@@ -126,6 +126,38 @@ function get_pod() {
echo "${pod_name}"
}
+function run_task() {
+ local namespace="${KUBE_NAMESPACE}"
+ local ruby_cmd="${1}"
+ local task_runner_pod=$(get_pod "task-runner")
+
+ kubectl exec -it --namespace "${namespace}" "${task_runner_pod}" -- gitlab-rails runner "${ruby_cmd}"
+}
+
+function disable_sign_ups() {
+ if [ -z ${REVIEW_APPS_ROOT_TOKEN+x} ]; then
+ echoerr "In order to protect Review Apps, REVIEW_APPS_ROOT_TOKEN variable must be set"
+ false
+ else
+ true
+ fi
+
+ # Create the root token
+ local ruby_cmd="token = User.find_by_username('root').personal_access_tokens.create(scopes: [:api], name: 'Token to disable sign-ups'); token.set_token('${REVIEW_APPS_ROOT_TOKEN}'); begin; token.save!; rescue(ActiveRecord::RecordNotUnique); end"
+ run_task "${ruby_cmd}"
+
+ # Disable sign-ups
+ curl --silent --show-error --request PUT --header "PRIVATE-TOKEN: ${REVIEW_APPS_ROOT_TOKEN}" "${CI_ENVIRONMENT_URL}/api/v4/application/settings?signup_enabled=false"
+
+ local signup_enabled=$(curl --silent --show-error --request GET --header "PRIVATE-TOKEN: ${REVIEW_APPS_ROOT_TOKEN}" "${CI_ENVIRONMENT_URL}/api/v4/application/settings" | jq ".signup_enabled")
+ if [[ "${signup_enabled}" == "false" ]]; then
+ echoinfo "Sign-ups have been disabled successfully."
+ else
+ echoerr "Sign-ups should be disabled but are still enabled!"
+ false
+ fi
+}
+
function check_kube_domain() {
echoinfo "Checking that Kube domain exists..." true
@@ -181,6 +213,32 @@ function install_external_dns() {
fi
}
+# This script is used to install cert-manager in the cluster
+# The installation steps are documented in
+# https://gitlab.com/gitlab-org/quality/team-tasks/snippets/1990286
+function install_certmanager() {
+ local namespace="${KUBE_NAMESPACE}"
+ local release="cert-manager-review-app-helm3"
+
+ echoinfo "Installing cert-manager..." true
+
+ if ! deploy_exists "${namespace}" "${release}" || previous_deploy_failed "${namespace}" "${release}" ; then
+ kubectl apply \
+ -f https://raw.githubusercontent.com/jetstack/cert-manager/release-0.10/deploy/manifests/00-crds.yaml
+
+ echoinfo "Installing cert-manager Helm chart"
+ helm repo add jetstack https://charts.jetstack.io
+ helm repo update
+
+ helm install "${release}" jetstack/cert-manager \
+ --namespace "${namespace}" \
+ --version v0.15.1 \
+ --set installCRDS=true
+ else
+ echoinfo "The cert-manager Helm chart is already successfully deployed."
+ fi
+}
+
function create_application_secret() {
local namespace="${KUBE_NAMESPACE}"
local release="${CI_ENVIRONMENT_SLUG}"
@@ -233,6 +291,17 @@ function base_config_changed() {
curl "${CI_API_V4_URL}/projects/${CI_MERGE_REQUEST_PROJECT_ID}/merge_requests/${CI_MERGE_REQUEST_IID}/changes" | jq '.changes | any(.old_path == "scripts/review_apps/base-config.yaml")'
}
+function parse_gitaly_image_tag() {
+ local gitaly_version="${GITALY_VERSION}"
+
+ # prepend semver version with `v`
+ if [[ $gitaly_version =~ ^[0-9]+\.[0-9]+\.[0-9]+(-rc[0-9]+)?(-ee)?$ ]]; then
+ echo "v${gitaly_version}"
+ else
+ echo "${gitaly_version}"
+ fi
+}
+
function deploy() {
local namespace="${KUBE_NAMESPACE}"
local release="${CI_ENVIRONMENT_SLUG}"
@@ -248,6 +317,7 @@ function deploy() {
gitlab_webservice_image_repository="${IMAGE_REPOSITORY}/gitlab-webservice-ee"
gitlab_task_runner_image_repository="${IMAGE_REPOSITORY}/gitlab-task-runner-ee"
gitlab_gitaly_image_repository="${IMAGE_REPOSITORY}/gitaly"
+ gitaly_image_tag=$(parse_gitaly_image_tag)
gitlab_shell_image_repository="${IMAGE_REPOSITORY}/gitlab-shell"
gitlab_workhorse_image_repository="${IMAGE_REPOSITORY}/gitlab-workhorse-ee"
@@ -269,7 +339,7 @@ HELM_CMD=$(cat << EOF
--set gitlab.migrations.image.repository="${gitlab_migrations_image_repository}" \
--set gitlab.migrations.image.tag="${CI_COMMIT_REF_SLUG}" \
--set gitlab.gitaly.image.repository="${gitlab_gitaly_image_repository}" \
- --set gitlab.gitaly.image.tag="v${GITALY_VERSION}" \
+ --set gitlab.gitaly.image.tag="${gitaly_image_tag}" \
--set gitlab.gitlab-shell.image.repository="${gitlab_shell_image_repository}" \
--set gitlab.gitlab-shell.image.tag="v${GITLAB_SHELL_VERSION}" \
--set gitlab.sidekiq.annotations.commit="${CI_COMMIT_SHORT_SHA}" \
diff --git a/scripts/rspec_helpers.sh b/scripts/rspec_helpers.sh
index 110567a15be..1ecf9a566d7 100644
--- a/scripts/rspec_helpers.sh
+++ b/scripts/rspec_helpers.sh
@@ -119,12 +119,12 @@ function rspec_matched_tests() {
local test_file_count=$(wc -w "${matching_tests_file}" | awk {'print $1'})
if [[ "${test_file_count}" -gt "${test_file_count_threshold}" ]]; then
- echo "There are more than ${test_file_count_threshold} FOSS test files matched,"
+ echo "This job is intentionally failed because there are more than ${test_file_count_threshold} FOSS test files matched,"
echo "which would take too long to run in this job."
echo "To reduce the likelihood of breaking FOSS pipelines,"
echo "please add [RUN AS-IF-FOSS] to the MR title and restart the pipeline."
echo "This would run all as-if-foss jobs in this merge request"
- echo "and remove this job from the pipeline."
+ echo "and remove this failing job from the pipeline."
exit 1
fi
diff --git a/scripts/trigger-build b/scripts/trigger-build
index b8bea95a069..9f0df21e7f1 100755
--- a/scripts/trigger-build
+++ b/scripts/trigger-build
@@ -184,6 +184,7 @@ module Trigger
edition = Trigger.ee? ? 'EE' : 'CE'
{
+ "ee" => Trigger.ee? ? "true" : "false",
"GITLAB_VERSION" => ENV['CI_COMMIT_SHA'],
"GITLAB_TAG" => ENV['CI_COMMIT_TAG'],
"GITLAB_ASSETS_TAG" => ENV['CI_COMMIT_TAG'] ? ENV['CI_COMMIT_REF_NAME'] : ENV['CI_COMMIT_SHA'],
diff --git a/spec/bin/feature_flag_spec.rb b/spec/bin/feature_flag_spec.rb
new file mode 100644
index 00000000000..3a315a13686
--- /dev/null
+++ b/spec/bin/feature_flag_spec.rb
@@ -0,0 +1,191 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+load File.expand_path('../../bin/feature-flag', __dir__)
+
+RSpec.describe 'bin/feature-flag' do
+ using RSpec::Parameterized::TableSyntax
+
+ describe FeatureFlagCreator do
+ let(:argv) { %w[feature-flag-name -t development -g group::memory -i https://url] }
+ let(:options) { FeatureFlagOptionParser.parse(argv) }
+ let(:creator) { described_class.new(options) }
+ let(:existing_flag) { File.join('config', 'feature_flags', 'development', 'existing-feature-flag.yml') }
+
+ before do
+ # create a dummy feature flag
+ FileUtils.mkdir_p(File.dirname(existing_flag))
+ File.write(existing_flag, '{}')
+
+ # ignore writes
+ allow(File).to receive(:write).and_return(true)
+
+ # ignore stdin
+ allow($stdin).to receive(:gets).and_raise('EOF')
+
+ # ignore Git commands
+ allow(creator).to receive(:branch_name) { 'feature-branch' }
+ end
+
+ after do
+ FileUtils.rm_f(existing_flag)
+ end
+
+ subject { creator.execute }
+
+ it 'properly creates a feature flag' do
+ expect(File).to receive(:write).with(
+ File.join('config', 'feature_flags', 'development', 'feature-flag-name.yml'),
+ anything)
+
+ expect do
+ subject
+ end.to output(/name: feature-flag-name/).to_stdout
+ end
+
+ context 'when running on master' do
+ it 'requires feature branch' do
+ expect(creator).to receive(:branch_name) { 'master' }
+
+ expect { subject }.to raise_error(FeatureFlagHelpers::Abort, /Create a branch first/)
+ end
+ end
+
+ context 'validates feature flag name' do
+ where(:argv, :ex) do
+ %w[.invalid.feature.flag] | /Provide a name for the feature flag that is/
+ %w[existing-feature-flag] | /already exists!/
+ end
+
+ with_them do
+ it do
+ expect { subject }.to raise_error(ex)
+ end
+ end
+ end
+ end
+
+ describe FeatureFlagOptionParser do
+ describe '.parse' do
+ where(:param, :argv, :result) do
+ :name | %w[foo] | 'foo'
+ :amend | %w[foo --amend] | true
+ :force | %w[foo -f] | true
+ :force | %w[foo --force] | true
+ :ee | %w[foo -e] | true
+ :ee | %w[foo --ee] | true
+ :introduced_by_url | %w[foo -m https://url] | 'https://url'
+ :introduced_by_url | %w[foo --introduced-by-url https://url] | 'https://url'
+ :rollout_issue_url | %w[foo -i https://url] | 'https://url'
+ :rollout_issue_url | %w[foo --rollout-issue-url https://url] | 'https://url'
+ :dry_run | %w[foo -n] | true
+ :dry_run | %w[foo --dry-run] | true
+ :type | %w[foo -t development] | :development
+ :type | %w[foo --type development] | :development
+ :type | %w[foo -t invalid] | nil
+ :type | %w[foo --type invalid] | nil
+ :group | %w[foo -g group::memory] | 'group::memory'
+ :group | %w[foo --group group::memory] | 'group::memory'
+ :group | %w[foo -g invalid] | nil
+ :group | %w[foo --group invalid] | nil
+ end
+
+ with_them do
+ it do
+ options = described_class.parse(Array(argv))
+
+ expect(options.public_send(param)).to eq(result)
+ end
+ end
+
+ it 'missing feature flag name' do
+ expect do
+ expect { described_class.parse(%w[--amend]) }.to output(/Feature flag name is required/).to_stdout
+ end.to raise_error(FeatureFlagHelpers::Abort)
+ end
+
+ it 'parses -h' do
+ expect do
+ expect { described_class.parse(%w[foo -h]) }.to output(/Usage:/).to_stdout
+ end.to raise_error(FeatureFlagHelpers::Done)
+ end
+ end
+
+ describe '.read_type' do
+ let(:type) { 'development' }
+
+ it 'reads type from $stdin' do
+ expect($stdin).to receive(:gets).and_return(type)
+ expect do
+ expect(described_class.read_type).to eq(:development)
+ end.to output(/specify the type/).to_stdout
+ end
+
+ context 'invalid type given' do
+ let(:type) { 'invalid' }
+
+ it 'shows error message and retries' do
+ expect($stdin).to receive(:gets).and_return(type)
+ expect($stdin).to receive(:gets).and_raise('EOF')
+
+ expect do
+ expect { described_class.read_type }.to raise_error(/EOF/)
+ end.to output(/specify the type/).to_stdout
+ .and output(/Invalid type specified/).to_stderr
+ end
+ end
+ end
+
+ describe '.read_group' do
+ let(:group) { 'group::memory' }
+
+ it 'reads type from $stdin' do
+ expect($stdin).to receive(:gets).and_return(group)
+ expect do
+ expect(described_class.read_group).to eq('group::memory')
+ end.to output(/specify the group/).to_stdout
+ end
+
+ context 'invalid group given' do
+ let(:type) { 'invalid' }
+
+ it 'shows error message and retries' do
+ expect($stdin).to receive(:gets).and_return(type)
+ expect($stdin).to receive(:gets).and_raise('EOF')
+
+ expect do
+ expect { described_class.read_group }.to raise_error(/EOF/)
+ end.to output(/specify the group/).to_stdout
+ .and output(/Group needs to include/).to_stderr
+ end
+ end
+ end
+
+ describe '.rollout_issue_url' do
+ let(:options) { OpenStruct.new(name: 'foo', type: :development) }
+ let(:url) { 'https://issue' }
+
+ it 'reads type from $stdin' do
+ expect($stdin).to receive(:gets).and_return(url)
+ expect do
+ expect(described_class.read_issue_url(options)).to eq('https://issue')
+ end.to output(/Paste URL here/).to_stdout
+ end
+
+ context 'invalid URL given' do
+ let(:type) { 'invalid' }
+
+ it 'shows error message and retries' do
+ expect($stdin).to receive(:gets).and_return(type)
+ expect($stdin).to receive(:gets).and_raise('EOF')
+
+ expect do
+ expect { described_class.read_issue_url(options) }.to raise_error(/EOF/)
+ end.to output(/Paste URL here/).to_stdout
+ .and output(/URL needs to start/).to_stderr
+ end
+ end
+ end
+ end
+end
diff --git a/spec/config/object_store_settings_spec.rb b/spec/config/object_store_settings_spec.rb
index 67e77aa4466..4a800261625 100644
--- a/spec/config/object_store_settings_spec.rb
+++ b/spec/config/object_store_settings_spec.rb
@@ -4,9 +4,105 @@ require 'spec_helper'
require Rails.root.join('config', 'object_store_settings.rb')
RSpec.describe ObjectStoreSettings do
- describe '.parse' do
+ describe '#parse!' do
+ let(:settings) { Settingslogic.new(config) }
+
+ subject { described_class.new(settings).parse! }
+
+ context 'with valid config' do
+ let(:connection) do
+ {
+ 'provider' => 'AWS',
+ 'aws_access_key_id' => 'AWS_ACCESS_KEY_ID',
+ 'aws_secret_access_key' => 'AWS_SECRET_ACCESS_KEY',
+ 'region' => 'us-east-1'
+ }
+ end
+ let(:config) do
+ {
+ 'lfs' => { 'enabled' => true },
+ 'artifacts' => { 'enabled' => true },
+ 'external_diffs' => { 'enabled' => false },
+ 'object_store' => {
+ 'enabled' => true,
+ 'connection' => connection,
+ 'proxy_download' => true,
+ 'objects' => {
+ 'artifacts' => {
+ 'bucket' => 'artifacts',
+ 'proxy_download' => false
+ },
+ 'lfs' => {
+ 'bucket' => 'lfs-objects'
+ },
+ 'external_diffs' => {
+ 'bucket' => 'external_diffs',
+ 'enabled' => false
+ }
+ }
+ }
+ }
+ end
+
+ it 'sets correct default values' do
+ subject
+
+ expect(settings.artifacts['enabled']).to be true
+ expect(settings.artifacts['object_store']['enabled']).to be true
+ expect(settings.artifacts['object_store']['connection']).to eq(connection)
+ expect(settings.artifacts['object_store']['direct_upload']).to be true
+ expect(settings.artifacts['object_store']['background_upload']).to be false
+ expect(settings.artifacts['object_store']['proxy_download']).to be false
+ expect(settings.artifacts['object_store']['remote_directory']).to eq('artifacts')
+
+ expect(settings.lfs['enabled']).to be true
+ expect(settings.lfs['object_store']['enabled']).to be true
+ expect(settings.lfs['object_store']['connection']).to eq(connection)
+ expect(settings.lfs['object_store']['direct_upload']).to be true
+ expect(settings.lfs['object_store']['background_upload']).to be false
+ expect(settings.lfs['object_store']['proxy_download']).to be true
+ expect(settings.lfs['object_store']['remote_directory']).to eq('lfs-objects')
+
+ expect(settings.external_diffs['enabled']).to be false
+ expect(settings.external_diffs['object_store']['enabled']).to be false
+ expect(settings.external_diffs['object_store']['remote_directory']).to eq('external_diffs')
+ end
+
+ it 'raises an error when a bucket is missing' do
+ config['object_store']['objects']['lfs'].delete('bucket')
+
+ expect { subject }.to raise_error(/Object storage for lfs must have a bucket specified/)
+ end
+
+ context 'with legacy config' do
+ let(:legacy_settings) do
+ {
+ 'enabled' => true,
+ 'remote_directory' => 'some-bucket',
+ 'direct_upload' => true,
+ 'background_upload' => false,
+ 'proxy_download' => false
+ }
+ end
+
+ before do
+ settings.lfs['object_store'] = described_class.legacy_parse(legacy_settings)
+ end
+
+ it 'does not alter config if legacy settings are specified' do
+ subject
+
+ expect(settings.artifacts['object_store']).to be_nil
+ expect(settings.lfs['object_store']['remote_directory']).to eq('some-bucket')
+ expect(settings.external_diffs['object_store']).to be_nil
+ end
+ end
+ end
+ end
+
+ describe '.legacy_parse' do
it 'sets correct default values' do
- settings = described_class.parse(nil)
+ settings = described_class.legacy_parse(nil)
expect(settings['enabled']).to be false
expect(settings['direct_upload']).to be false
@@ -20,7 +116,7 @@ RSpec.describe ObjectStoreSettings do
'remote_directory' => 'artifacts'
})
- settings = described_class.parse(original_settings)
+ settings = described_class.legacy_parse(original_settings)
expect(settings['enabled']).to be true
expect(settings['direct_upload']).to be false
diff --git a/spec/config/settings_spec.rb b/spec/config/settings_spec.rb
index 9db3d35cbe5..ed873478fc9 100644
--- a/spec/config/settings_spec.rb
+++ b/spec/config/settings_spec.rb
@@ -112,4 +112,26 @@ RSpec.describe Settings do
end
end
end
+
+ describe '.cron_for_usage_ping' do
+ it 'returns correct crontab for some manually calculated example' do
+ allow(Gitlab::CurrentSettings)
+ .to receive(:uuid) { 'd9e2f4e8-db1f-4e51-b03d-f427e1965c4a'}
+
+ expect(described_class.send(:cron_for_usage_ping)).to eq('21 18 * * 4')
+ end
+
+ it 'returns min, hour, day in the valid range' do
+ allow(Gitlab::CurrentSettings)
+ .to receive(:uuid) { SecureRandom.uuid }
+
+ 10.times do
+ cron = described_class.send(:cron_for_usage_ping).split(/\s/)
+
+ expect(cron[0].to_i).to be_between(0, 59)
+ expect(cron[1].to_i).to be_between(0, 23)
+ expect(cron[4].to_i).to be_between(0, 6)
+ end
+ end
+ end
end
diff --git a/spec/controllers/admin/application_settings_controller_spec.rb b/spec/controllers/admin/application_settings_controller_spec.rb
index 8ab29a72477..7b8528009d8 100644
--- a/spec/controllers/admin/application_settings_controller_spec.rb
+++ b/spec/controllers/admin/application_settings_controller_spec.rb
@@ -128,6 +128,13 @@ RSpec.describe Admin::ApplicationSettingsController do
expect(ApplicationSetting.current.repository_storages_weighted_default).to eq(75)
end
+ it "updates default_branch_name setting" do
+ put :update, params: { application_setting: { default_branch_name: "example_branch_name" } }
+
+ expect(response).to redirect_to(general_admin_application_settings_path)
+ expect(ApplicationSetting.current.default_branch_name).to eq("example_branch_name")
+ end
+
context 'external policy classification settings' do
let(:settings) do
{
diff --git a/spec/controllers/admin/clusters_controller_spec.rb b/spec/controllers/admin/clusters_controller_spec.rb
index d899e86ae5f..2e0ee671d3f 100644
--- a/spec/controllers/admin/clusters_controller_spec.rb
+++ b/spec/controllers/admin/clusters_controller_spec.rb
@@ -171,6 +171,39 @@ RSpec.describe Admin::ClustersController do
end
end
+ it_behaves_like 'GET #metrics_dashboard for dashboard', 'Cluster health' do
+ let(:cluster) { create(:cluster, :instance, :provided_by_gcp) }
+
+ let(:metrics_dashboard_req_params) do
+ {
+ id: cluster.id
+ }
+ end
+ end
+
+ describe 'GET #prometheus_proxy' do
+ let(:user) { admin }
+ let(:proxyable) do
+ create(:cluster, :instance, :provided_by_gcp)
+ end
+
+ it_behaves_like 'metrics dashboard prometheus api proxy' do
+ context 'with anonymous user' do
+ let(:prometheus_body) { nil }
+
+ before do
+ sign_out(admin)
+ end
+
+ it 'returns 404' do
+ get :prometheus_proxy, params: prometheus_proxy_params
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+ end
+ end
+
describe 'POST #create_gcp' do
let(:legacy_abac_param) { 'true' }
let(:params) do
diff --git a/spec/controllers/admin/jobs_controller_spec.rb b/spec/controllers/admin/jobs_controller_spec.rb
new file mode 100644
index 00000000000..2d1482f40d4
--- /dev/null
+++ b/spec/controllers/admin/jobs_controller_spec.rb
@@ -0,0 +1,32 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Admin::JobsController do
+ describe 'GET #index' do
+ context 'with an authenticated admin user' do
+ it 'paginates builds without a total count', :aggregate_failures do
+ stub_const("Admin::JobsController::BUILDS_PER_PAGE", 1)
+
+ sign_in(create(:admin))
+ create_list(:ci_build, 2)
+
+ get :index
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(assigns(:builds)).to be_a(Kaminari::PaginatableWithoutCount)
+ expect(assigns(:builds).count).to be(1)
+ end
+ end
+
+ context 'without admin access' do
+ it 'returns `not_found`' do
+ sign_in(create(:user))
+
+ get :index
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+ end
+end
diff --git a/spec/controllers/admin/services_controller_spec.rb b/spec/controllers/admin/services_controller_spec.rb
index 2ad4989af4f..8e78cc75369 100644
--- a/spec/controllers/admin/services_controller_spec.rb
+++ b/spec/controllers/admin/services_controller_spec.rb
@@ -10,7 +10,7 @@ RSpec.describe Admin::ServicesController do
end
describe 'GET #edit' do
- let!(:service) do
+ let(:service) do
create(:jira_service, :template)
end
@@ -19,6 +19,26 @@ RSpec.describe Admin::ServicesController do
expect(response).to have_gitlab_http_status(:ok)
end
+
+ context 'when integration does not exists' do
+ it 'redirects to the admin application integration page' do
+ get :edit, params: { id: 'invalid' }
+
+ expect(response).to redirect_to(admin_application_settings_services_path)
+ end
+ end
+
+ context 'when instance integration exists' do
+ before do
+ create(:jira_service, :instance)
+ end
+
+ it 'redirects to the admin application integration page' do
+ get :edit, params: { id: service.id }
+
+ expect(response).to redirect_to(admin_application_settings_services_path)
+ end
+ end
end
describe "#update" do
diff --git a/spec/controllers/application_controller_spec.rb b/spec/controllers/application_controller_spec.rb
index 4002b7aca63..aec629ba330 100644
--- a/spec/controllers/application_controller_spec.rb
+++ b/spec/controllers/application_controller_spec.rb
@@ -968,4 +968,26 @@ RSpec.describe ApplicationController do
end
end
end
+
+ describe 'locale' do
+ let(:user) { create(:user, preferred_language: 'uk') }
+
+ controller(described_class) do
+ def index
+ :ok
+ end
+ end
+
+ before do
+ sign_in(user)
+
+ allow(Gitlab::I18n).to receive(:with_locale).and_call_original
+ end
+
+ it "sets user's locale" do
+ expect(Gitlab::I18n).to receive(:with_locale).with('uk')
+
+ get :index
+ end
+ end
end
diff --git a/spec/controllers/autocomplete_controller_spec.rb b/spec/controllers/autocomplete_controller_spec.rb
index aeb3f4dcb17..e7c0bc43e86 100644
--- a/spec/controllers/autocomplete_controller_spec.rb
+++ b/spec/controllers/autocomplete_controller_spec.rb
@@ -365,6 +365,56 @@ RSpec.describe AutocompleteController do
end
end
+ context 'GET deploy_keys_with_owners' do
+ let!(:deploy_key) { create(:deploy_key, user: user) }
+ let!(:deploy_keys_project) { create(:deploy_keys_project, :write_access, project: project, deploy_key: deploy_key) }
+
+ context 'unauthorized user' do
+ it 'returns a not found response' do
+ get(:deploy_keys_with_owners, params: { project_id: project.id })
+
+ expect(response).to have_gitlab_http_status(:redirect)
+ end
+ end
+
+ context 'when the user who can read the project is logged in' do
+ before do
+ sign_in(user)
+ end
+
+ it 'renders the deploy key in a json payload, with its owner' do
+ get(:deploy_keys_with_owners, params: { project_id: project.id })
+
+ expect(json_response.count).to eq(1)
+ expect(json_response.first['title']).to eq(deploy_key.title)
+ expect(json_response.first['owner']['id']).to eq(deploy_key.user.id)
+ end
+
+ context 'with an unknown project' do
+ it 'returns a not found response' do
+ get(:deploy_keys_with_owners, params: { project_id: 9999 })
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+
+ context 'and the user cannot read the owner of the key' do
+ before do
+ allow(Ability).to receive(:allowed?).and_call_original
+ allow(Ability).to receive(:allowed?).with(user, :read_user, deploy_key.user).and_return(false)
+ end
+
+ it 'returns a payload without owner' do
+ get(:deploy_keys_with_owners, params: { project_id: project.id })
+
+ expect(json_response.count).to eq(1)
+ expect(json_response.first['title']).to eq(deploy_key.title)
+ expect(json_response.first['owner']).to be_nil
+ end
+ end
+ end
+ end
+
context 'Get merge_request_target_branches' do
let!(:merge_request) { create(:merge_request, source_project: project, target_branch: 'feature') }
diff --git a/spec/controllers/concerns/controller_with_feature_category/config_spec.rb b/spec/controllers/concerns/controller_with_feature_category/config_spec.rb
new file mode 100644
index 00000000000..9b8ffd2baab
--- /dev/null
+++ b/spec/controllers/concerns/controller_with_feature_category/config_spec.rb
@@ -0,0 +1,53 @@
+# frozen_string_literal: true
+
+require "fast_spec_helper"
+require "rspec-parameterized"
+require_relative "../../../../app/controllers/concerns/controller_with_feature_category/config"
+
+RSpec.describe ControllerWithFeatureCategory::Config do
+ describe "#matches?" do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:only_actions, :except_actions, :if_proc, :unless_proc, :test_action, :expected) do
+ nil | nil | nil | nil | "action" | true
+ [:included] | nil | nil | nil | "action" | false
+ [:included] | nil | nil | nil | "included" | true
+ nil | [:excluded] | nil | nil | "excluded" | false
+ nil | nil | true | nil | "action" | true
+ [:included] | nil | true | nil | "action" | false
+ [:included] | nil | true | nil | "included" | true
+ nil | [:excluded] | true | nil | "excluded" | false
+ nil | nil | false | nil | "action" | false
+ [:included] | nil | false | nil | "action" | false
+ [:included] | nil | false | nil | "included" | false
+ nil | [:excluded] | false | nil | "excluded" | false
+ nil | nil | nil | true | "action" | false
+ [:included] | nil | nil | true | "action" | false
+ [:included] | nil | nil | true | "included" | false
+ nil | [:excluded] | nil | true | "excluded" | false
+ nil | nil | nil | false | "action" | true
+ [:included] | nil | nil | false | "action" | false
+ [:included] | nil | nil | false | "included" | true
+ nil | [:excluded] | nil | false | "excluded" | false
+ nil | nil | true | false | "action" | true
+ [:included] | nil | true | false | "action" | false
+ [:included] | nil | true | false | "included" | true
+ nil | [:excluded] | true | false | "excluded" | false
+ nil | nil | false | true | "action" | false
+ [:included] | nil | false | true | "action" | false
+ [:included] | nil | false | true | "included" | false
+ nil | [:excluded] | false | true | "excluded" | false
+ end
+
+ with_them do
+ let(:config) do
+ if_to_proc = if_proc.nil? ? nil : -> (_) { if_proc }
+ unless_to_proc = unless_proc.nil? ? nil : -> (_) { unless_proc }
+
+ described_class.new(:category, only_actions, except_actions, if_to_proc, unless_to_proc)
+ end
+
+ specify { expect(config.matches?(test_action)).to be(expected) }
+ end
+ end
+end
diff --git a/spec/controllers/concerns/controller_with_feature_category_spec.rb b/spec/controllers/concerns/controller_with_feature_category_spec.rb
new file mode 100644
index 00000000000..e603a7d14c4
--- /dev/null
+++ b/spec/controllers/concerns/controller_with_feature_category_spec.rb
@@ -0,0 +1,66 @@
+# frozen_string_literal: true
+
+require 'fast_spec_helper'
+require_relative "../../../app/controllers/concerns/controller_with_feature_category"
+require_relative "../../../app/controllers/concerns/controller_with_feature_category/config"
+
+RSpec.describe ControllerWithFeatureCategory do
+ describe ".feature_category_for_action" do
+ let(:base_controller) do
+ Class.new do
+ include ControllerWithFeatureCategory
+ end
+ end
+
+ let(:controller) do
+ Class.new(base_controller) do
+ feature_category :baz
+ feature_category :foo, except: %w(update edit)
+ feature_category :bar, only: %w(index show)
+ feature_category :quux, only: %w(destroy)
+ feature_category :quuz, only: %w(destroy)
+ end
+ end
+
+ let(:subclass) do
+ Class.new(controller) do
+ feature_category :qux, only: %w(index)
+ end
+ end
+
+ it "is nil when nothing was defined" do
+ expect(base_controller.feature_category_for_action("hello")).to be_nil
+ end
+
+ it "returns the expected category", :aggregate_failures do
+ expect(controller.feature_category_for_action("update")).to eq(:baz)
+ expect(controller.feature_category_for_action("hello")).to eq(:foo)
+ expect(controller.feature_category_for_action("index")).to eq(:bar)
+ end
+
+ it "returns the closest match for categories defined in subclasses" do
+ expect(subclass.feature_category_for_action("index")).to eq(:qux)
+ expect(subclass.feature_category_for_action("show")).to eq(:bar)
+ end
+
+ it "returns the last defined feature category when multiple match" do
+ expect(controller.feature_category_for_action("destroy")).to eq(:quuz)
+ end
+
+ it "raises an error when using including and excluding the same action" do
+ expect do
+ Class.new(base_controller) do
+ feature_category :hello, only: [:world], except: [:world]
+ end
+ end.to raise_error(%r(cannot configure both `only` and `except`))
+ end
+
+ it "raises an error when using unknown arguments" do
+ expect do
+ Class.new(base_controller) do
+ feature_category :hello, hello: :world
+ end
+ end.to raise_error(%r(unknown arguments))
+ end
+ end
+end
diff --git a/spec/controllers/concerns/metrics_dashboard_spec.rb b/spec/controllers/concerns/metrics_dashboard_spec.rb
index 39ddf687dca..f0c9874965e 100644
--- a/spec/controllers/concerns/metrics_dashboard_spec.rb
+++ b/spec/controllers/concerns/metrics_dashboard_spec.rb
@@ -76,6 +76,22 @@ RSpec.describe MetricsDashboard do
end
end
+ context 'when dashboard path includes encoded characters' do
+ let(:params) { { dashboard_path: 'dashboard%26copy.yml' } }
+
+ before do
+ allow(controller)
+ .to receive(:metrics_dashboard_params)
+ .and_return(params)
+ end
+
+ it 'decodes dashboard path' do
+ expect(::Gitlab::Metrics::Dashboard::Finder).to receive(:find).with(anything, anything, hash_including(dashboard_path: 'dashboard&copy.yml'))
+
+ json_response
+ end
+ end
+
context 'when parameters are provided and the list of all dashboards is required' do
before do
allow(controller).to receive(:include_all_dashboards?).and_return(true)
@@ -88,13 +104,28 @@ RSpec.describe MetricsDashboard do
context 'in all_dashboard list' do
let(:system_dashboard) { json_response['all_dashboards'].find { |dashboard| dashboard["system_dashboard"] == true } }
- let(:project_dashboard) { json_response['all_dashboards'].find { |dashboard| dashboard["system_dashboard"] == false } }
+
+ let(:project_dashboard) do
+ json_response['all_dashboards'].find do |dashboard|
+ dashboard['path'] == '.gitlab/dashboards/test.yml'
+ end
+ end
it 'includes project_blob_path only for project dashboards' do
expect(system_dashboard['project_blob_path']).to be_nil
expect(project_dashboard['project_blob_path']).to eq("/#{project.namespace.path}/#{project.name}/-/blob/master/.gitlab/dashboards/test.yml")
end
+ it 'allows editing only for project dashboards' do
+ expect(system_dashboard['can_edit']).to be(false)
+ expect(project_dashboard['can_edit']).to be(true)
+ end
+
+ it 'includes out_of_the_box_dashboard key' do
+ expect(system_dashboard['out_of_the_box_dashboard']).to be(true)
+ expect(project_dashboard['out_of_the_box_dashboard']).to be(false)
+ end
+
describe 'project permissions' do
using RSpec::Parameterized::TableSyntax
diff --git a/spec/controllers/concerns/renders_commits_spec.rb b/spec/controllers/concerns/renders_commits_spec.rb
index 0bffb39d608..7be5f75c19d 100644
--- a/spec/controllers/concerns/renders_commits_spec.rb
+++ b/spec/controllers/concerns/renders_commits_spec.rb
@@ -46,7 +46,7 @@ RSpec.describe RendersCommits do
it 'avoids N + 1' do
stub_const("MergeRequestDiff::COMMITS_SAFE_SIZE", 5)
- control_count = ActiveRecord::QueryRecorder.new(skip_cached: false) do
+ control_count = ActiveRecord::QueryRecorder.new do
go
end.count
diff --git a/spec/controllers/concerns/sorting_preference_spec.rb b/spec/controllers/concerns/sorting_preference_spec.rb
index 4f9506d4675..c0091e8b694 100644
--- a/spec/controllers/concerns/sorting_preference_spec.rb
+++ b/spec/controllers/concerns/sorting_preference_spec.rb
@@ -75,7 +75,7 @@ RSpec.describe SortingPreference do
it 'sets the cookie with the right values and flags' do
subject
- expect(cookies['issue_sort']).to eq(value: 'popularity', secure: false, httponly: false)
+ expect(cookies['issue_sort']).to eq(expires: nil, value: 'popularity', secure: false, httponly: false)
end
end
@@ -86,7 +86,7 @@ RSpec.describe SortingPreference do
it 'sets the cookie with the right values and flags' do
subject
- expect(cookies['issue_sort']).to eq(value: 'created_asc', secure: false, httponly: false)
+ expect(cookies['issue_sort']).to eq(expires: nil, value: 'created_asc', secure: false, httponly: false)
end
end
end
diff --git a/spec/controllers/dashboard/projects_controller_spec.rb b/spec/controllers/dashboard/projects_controller_spec.rb
index ee043fde0ff..1e1d9519f78 100644
--- a/spec/controllers/dashboard/projects_controller_spec.rb
+++ b/spec/controllers/dashboard/projects_controller_spec.rb
@@ -5,13 +5,14 @@ require 'spec_helper'
RSpec.describe Dashboard::ProjectsController do
include ExternalAuthorizationServiceHelpers
+ let_it_be(:user) { create(:user) }
+
describe '#index' do
context 'user not logged in' do
it_behaves_like 'authenticates sessionless user', :index, :atom
end
context 'user logged in' do
- let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project) }
let_it_be(:project2) { create(:project) }
@@ -71,8 +72,6 @@ RSpec.describe Dashboard::ProjectsController do
context 'json requests' do
render_views
- let(:user) { create(:user) }
-
before do
sign_in(user)
end
@@ -114,16 +113,14 @@ RSpec.describe Dashboard::ProjectsController do
end
context 'atom requests' do
- let(:user) { create(:user) }
-
before do
sign_in(user)
end
describe '#index' do
- context 'project pagination' do
- let(:projects) { create_list(:project, 2, creator: user) }
+ let_it_be(:projects) { create_list(:project, 2, creator: user) }
+ context 'project pagination' do
before do
allow(Kaminari.config).to receive(:default_per_page).and_return(1)
@@ -138,6 +135,37 @@ RSpec.describe Dashboard::ProjectsController do
expect(assigns(:events).count).to eq(2)
end
end
+
+ describe 'rendering' do
+ include DesignManagementTestHelpers
+ render_views
+
+ let(:project) { projects.first }
+ let!(:design_event) { create(:design_event, project: project) }
+ let!(:wiki_page_event) { create(:wiki_page_event, project: project) }
+ let!(:issue_event) { create(:closed_issue_event, project: project) }
+ let(:design) { design_event.design }
+ let(:wiki_page) { wiki_page_event.wiki_page }
+ let(:issue) { issue_event.issue }
+
+ before do
+ enable_design_management
+ project.add_developer(user)
+ end
+
+ it 'renders all kinds of event without error', :aggregate_failures do
+ get :index, format: :atom
+
+ expect(assigns(:events)).to include(design_event, wiki_page_event, issue_event)
+ expect(response).to render_template('dashboard/projects/index')
+ expect(response.body).to include(
+ "uploaded design #{design.to_reference}",
+ "created wiki page #{wiki_page.title}",
+ "joined project #{project.full_name}",
+ "closed issue #{issue.to_reference}"
+ )
+ end
+ end
end
end
end
diff --git a/spec/controllers/dashboard/snippets_controller_spec.rb b/spec/controllers/dashboard/snippets_controller_spec.rb
index 3c316d07408..d981f738e70 100644
--- a/spec/controllers/dashboard/snippets_controller_spec.rb
+++ b/spec/controllers/dashboard/snippets_controller_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
RSpec.describe Dashboard::SnippetsController do
- let(:user) { create(:user) }
+ let_it_be(:user) { create(:user) }
before do
sign_in(user)
@@ -26,5 +26,7 @@ RSpec.describe Dashboard::SnippetsController do
get :index
end
+
+ it_behaves_like 'snippets sort order'
end
end
diff --git a/spec/controllers/dashboard/todos_controller_spec.rb b/spec/controllers/dashboard/todos_controller_spec.rb
index f0aa351bee0..2e3328ae4d2 100644
--- a/spec/controllers/dashboard/todos_controller_spec.rb
+++ b/spec/controllers/dashboard/todos_controller_spec.rb
@@ -42,6 +42,15 @@ RSpec.describe Dashboard::TodosController do
expect(response).to have_gitlab_http_status(:ok)
end
+
+ context 'tracking visits' do
+ let_it_be(:authorized_project) { create(:project, :public) }
+
+ it_behaves_like 'tracking unique visits', :index do
+ let(:request_params) { { project_id: authorized_project.id } }
+ let(:target_id) { 'u_analytics_todos' }
+ end
+ end
end
context "with render_views" do
diff --git a/spec/controllers/dashboard_controller_spec.rb b/spec/controllers/dashboard_controller_spec.rb
index d27817c0a82..c838affa239 100644
--- a/spec/controllers/dashboard_controller_spec.rb
+++ b/spec/controllers/dashboard_controller_spec.rb
@@ -24,15 +24,20 @@ RSpec.describe DashboardController do
end
describe "GET activity as JSON" do
+ include DesignManagementTestHelpers
render_views
let(:user) { create(:user) }
let(:project) { create(:project, :public, issues_access_level: ProjectFeature::PRIVATE) }
+ let(:other_project) { create(:project, :public) }
before do
+ enable_design_management
create(:event, :created, project: project, target: create(:issue))
create(:wiki_page_event, :created, project: project)
create(:wiki_page_event, :updated, project: project)
+ create(:design_event, project: project)
+ create(:design_event, author: user, project: other_project)
sign_in(user)
@@ -42,12 +47,13 @@ RSpec.describe DashboardController do
context 'when user has permission to see the event' do
before do
project.add_developer(user)
+ other_project.add_developer(user)
end
it 'returns count' do
get :activity, params: { format: :json }
- expect(json_response['count']).to eq(3)
+ expect(json_response['count']).to eq(6)
end
end
diff --git a/spec/controllers/every_controller_spec.rb b/spec/controllers/every_controller_spec.rb
new file mode 100644
index 00000000000..4785ee9ed8f
--- /dev/null
+++ b/spec/controllers/every_controller_spec.rb
@@ -0,0 +1,82 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe "Every controller" do
+ context "feature categories" do
+ let_it_be(:feature_categories) do
+ YAML.load_file(Rails.root.join('config', 'feature_categories.yml')).map(&:to_sym).to_set
+ end
+
+ let_it_be(:controller_actions) do
+ # This will return tuples of all controller actions defined in the routes
+ # Only for controllers inheriting ApplicationController
+ # Excluding controllers from gems (OAuth, Sidekiq)
+ Rails.application.routes.routes
+ .map { |route| route.required_defaults.presence }
+ .compact
+ .select { |route| route[:controller].present? && route[:action].present? }
+ .map { |route| [constantize_controller(route[:controller]), route[:action]] }
+ .reject { |route| route.first.nil? || !route.first.include?(ControllerWithFeatureCategory) }
+ end
+
+ let_it_be(:routes_without_category) do
+ controller_actions.map do |controller, action|
+ "#{controller}##{action}" unless controller.feature_category_for_action(action)
+ end.compact
+ end
+
+ it "has feature categories" do
+ pending("We'll work on defining categories for all controllers: "\
+ "https://gitlab.com/gitlab-com/gl-infra/scalability/-/issues/463")
+
+ expect(routes_without_category).to be_empty, "#{routes_without_category.first(10)} did not have a category"
+ end
+
+ it "completed controllers don't get new routes without categories" do
+ completed_controllers = [Projects::MergeRequestsController].map(&:to_s)
+
+ newly_introduced_missing_category = routes_without_category.select do |route|
+ completed_controllers.any? { |controller| route.start_with?(controller) }
+ end
+
+ expect(newly_introduced_missing_category).to be_empty
+ end
+
+ it "recognizes the feature categories" do
+ routes_unknown_category = controller_actions.map do |controller, action|
+ used_category = controller.feature_category_for_action(action)
+ next unless used_category
+ next if used_category == :not_owned
+
+ ["#{controller}##{action}", used_category] unless feature_categories.include?(used_category)
+ end.compact
+
+ expect(routes_unknown_category).to be_empty, "#{routes_unknown_category.first(10)} had an unknown category"
+ end
+
+ it "doesn't define or exclude categories on removed actions", :aggregate_failures do
+ controller_actions.group_by(&:first).each do |controller, controller_action|
+ existing_actions = controller_action.map(&:last)
+ used_actions = actions_defined_in_feature_category_config(controller)
+ non_existing_used_actions = used_actions - existing_actions
+
+ expect(non_existing_used_actions).to be_empty,
+ "#{controller} used #{non_existing_used_actions} to define feature category, but the route does not exist"
+ end
+ end
+ end
+
+ def constantize_controller(name)
+ "#{name.camelize}Controller".constantize
+ rescue NameError
+ nil # some controllers, like the omniauth ones are dynamic
+ end
+
+ def actions_defined_in_feature_category_config(controller)
+ feature_category_configs = controller.send(:class_attributes)[:feature_category_config]
+ feature_category_configs.map do |config|
+ Array(config.send(:only)) + Array(config.send(:except))
+ end.flatten.uniq.map(&:to_s)
+ end
+end
diff --git a/spec/controllers/groups/clusters_controller_spec.rb b/spec/controllers/groups/clusters_controller_spec.rb
index 6765cf0990a..1593e1290c4 100644
--- a/spec/controllers/groups/clusters_controller_spec.rb
+++ b/spec/controllers/groups/clusters_controller_spec.rb
@@ -192,6 +192,46 @@ RSpec.describe Groups::ClustersController do
end
end
+ it_behaves_like 'GET #metrics_dashboard for dashboard', 'Cluster health' do
+ let(:cluster) { create(:cluster, :provided_by_gcp, cluster_type: :group_type, groups: [group]) }
+
+ let(:metrics_dashboard_req_params) do
+ {
+ id: cluster.id,
+ group_id: group.name
+ }
+ end
+ end
+
+ describe 'GET #prometheus_proxy' do
+ let(:proxyable) do
+ create(:cluster, :provided_by_gcp, cluster_type: :group_type, groups: [group])
+ end
+
+ it_behaves_like 'metrics dashboard prometheus api proxy' do
+ let(:proxyable_params) do
+ {
+ id: proxyable.id.to_s,
+ group_id: group.name
+ }
+ end
+
+ context 'with anonymous user' do
+ let(:prometheus_body) { nil }
+
+ before do
+ sign_out(user)
+ end
+
+ it 'returns 404' do
+ get :prometheus_proxy, params: prometheus_proxy_params
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+ end
+ end
+
describe 'POST create for new cluster' do
let(:legacy_abac_param) { 'true' }
let(:params) do
diff --git a/spec/controllers/groups/imports_controller_spec.rb b/spec/controllers/groups/imports_controller_spec.rb
index eb43a62b75b..7372c2e9575 100644
--- a/spec/controllers/groups/imports_controller_spec.rb
+++ b/spec/controllers/groups/imports_controller_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Groups::ImportsController do
+RSpec.describe Groups::ImportsController do
describe 'GET #show' do
let_it_be(:user) { create(:user) }
let_it_be(:group) { create(:group, :private) }
diff --git a/spec/controllers/groups/runners_controller_spec.rb b/spec/controllers/groups/runners_controller_spec.rb
index 376cd569952..91ff0a53ec7 100644
--- a/spec/controllers/groups/runners_controller_spec.rb
+++ b/spec/controllers/groups/runners_controller_spec.rb
@@ -6,6 +6,9 @@ RSpec.describe Groups::RunnersController do
let(:user) { create(:user) }
let(:group) { create(:group) }
let(:runner) { create(:ci_runner, :group, groups: [group]) }
+ let(:project) { create(:project, group: group) }
+ let(:runner_project) { create(:ci_runner, :project, projects: [project]) }
+ let(:params_runner_project) { { group_id: group, id: runner_project } }
let(:params) { { group_id: group, id: runner } }
before do
@@ -24,6 +27,13 @@ RSpec.describe Groups::RunnersController do
expect(response).to have_gitlab_http_status(:ok)
expect(response).to render_template(:show)
end
+
+ it 'renders show with 200 status code project runner' do
+ get :show, params: { group_id: group, id: runner_project }
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to render_template(:show)
+ end
end
context 'when user is not owner' do
@@ -36,6 +46,12 @@ RSpec.describe Groups::RunnersController do
expect(response).to have_gitlab_http_status(:not_found)
end
+
+ it 'renders a 404 project runner' do
+ get :show, params: { group_id: group, id: runner_project }
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
end
end
@@ -51,6 +67,13 @@ RSpec.describe Groups::RunnersController do
expect(response).to have_gitlab_http_status(:ok)
expect(response).to render_template(:edit)
end
+
+ it 'renders show with 200 status code project runner' do
+ get :edit, params: { group_id: group, id: runner_project }
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to render_template(:edit)
+ end
end
context 'when user is not owner' do
@@ -63,6 +86,12 @@ RSpec.describe Groups::RunnersController do
expect(response).to have_gitlab_http_status(:not_found)
end
+
+ it 'renders a 404 project runner' do
+ get :edit, params: { group_id: group, id: runner_project }
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
end
end
@@ -82,6 +111,17 @@ RSpec.describe Groups::RunnersController do
expect(response).to have_gitlab_http_status(:found)
expect(runner.reload.description).to eq(new_desc)
end
+
+ it 'updates the project runner, ticks the queue, and redirects project runner' do
+ new_desc = runner_project.description.swapcase
+
+ expect do
+ post :update, params: params_runner_project.merge(runner: { description: new_desc } )
+ end.to change { runner_project.ensure_runner_queue_value }
+
+ expect(response).to have_gitlab_http_status(:found)
+ expect(runner_project.reload.description).to eq(new_desc)
+ end
end
context 'when user is not an owner' do
@@ -99,6 +139,17 @@ RSpec.describe Groups::RunnersController do
expect(response).to have_gitlab_http_status(:not_found)
expect(runner.reload.description).to eq(old_desc)
end
+
+ it 'rejects the update and responds 404 project runner' do
+ old_desc = runner_project.description
+
+ expect do
+ post :update, params: params_runner_project.merge(runner: { description: old_desc.swapcase } )
+ end.not_to change { runner_project.ensure_runner_queue_value }
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ expect(runner_project.reload.description).to eq(old_desc)
+ end
end
end
@@ -114,6 +165,31 @@ RSpec.describe Groups::RunnersController do
expect(response).to have_gitlab_http_status(:found)
expect(Ci::Runner.find_by(id: runner.id)).to be_nil
end
+
+ it 'destroys the project runner and redirects' do
+ delete :destroy, params: params_runner_project
+
+ expect(response).to have_gitlab_http_status(:found)
+ expect(Ci::Runner.find_by(id: runner_project.id)).to be_nil
+ end
+ end
+
+ context 'when user is an owner and runner in multiple projects' do
+ let(:project_2) { create(:project, group: group) }
+ let(:runner_project_2) { create(:ci_runner, :project, projects: [project, project_2]) }
+ let(:params_runner_project_2) { { group_id: group, id: runner_project_2 } }
+
+ before do
+ group.add_owner(user)
+ end
+
+ it 'does not destroy the project runner' do
+ delete :destroy, params: params_runner_project_2
+
+ expect(response).to have_gitlab_http_status(:found)
+ expect(flash[:alert]).to eq('Runner was not deleted because it is assigned to multiple projects.')
+ expect(Ci::Runner.find_by(id: runner_project_2.id)).to be_present
+ end
end
context 'when user is not an owner' do
@@ -127,6 +203,13 @@ RSpec.describe Groups::RunnersController do
expect(response).to have_gitlab_http_status(:not_found)
expect(Ci::Runner.find_by(id: runner.id)).to be_present
end
+
+ it 'responds 404 and does not destroy the project runner' do
+ delete :destroy, params: params_runner_project
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ expect(Ci::Runner.find_by(id: runner_project.id)).to be_present
+ end
end
end
@@ -146,6 +229,17 @@ RSpec.describe Groups::RunnersController do
expect(response).to have_gitlab_http_status(:found)
expect(runner.reload.active).to eq(true)
end
+
+ it 'marks the project runner as active, ticks the queue, and redirects' do
+ runner_project.update(active: false)
+
+ expect do
+ post :resume, params: params_runner_project
+ end.to change { runner_project.ensure_runner_queue_value }
+
+ expect(response).to have_gitlab_http_status(:found)
+ expect(runner_project.reload.active).to eq(true)
+ end
end
context 'when user is not an owner' do
@@ -163,6 +257,17 @@ RSpec.describe Groups::RunnersController do
expect(response).to have_gitlab_http_status(:not_found)
expect(runner.reload.active).to eq(false)
end
+
+ it 'responds 404 and does not activate the project runner' do
+ runner_project.update(active: false)
+
+ expect do
+ post :resume, params: params_runner_project
+ end.not_to change { runner_project.ensure_runner_queue_value }
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ expect(runner_project.reload.active).to eq(false)
+ end
end
end
@@ -182,6 +287,17 @@ RSpec.describe Groups::RunnersController do
expect(response).to have_gitlab_http_status(:found)
expect(runner.reload.active).to eq(false)
end
+
+ it 'marks the project runner as inactive, ticks the queue, and redirects' do
+ runner_project.update(active: true)
+
+ expect do
+ post :pause, params: params_runner_project
+ end.to change { runner_project.ensure_runner_queue_value }
+
+ expect(response).to have_gitlab_http_status(:found)
+ expect(runner_project.reload.active).to eq(false)
+ end
end
context 'when user is not an owner' do
@@ -199,6 +315,17 @@ RSpec.describe Groups::RunnersController do
expect(response).to have_gitlab_http_status(:not_found)
expect(runner.reload.active).to eq(true)
end
+
+ it 'responds 404 and does not update the project runner or queue' do
+ runner_project.update(active: true)
+
+ expect do
+ post :pause, params: params
+ end.not_to change { runner_project.ensure_runner_queue_value }
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ expect(runner_project.reload.active).to eq(true)
+ end
end
end
end
diff --git a/spec/controllers/groups/settings/ci_cd_controller_spec.rb b/spec/controllers/groups/settings/ci_cd_controller_spec.rb
index 55c19de4aa1..f11bb66caab 100644
--- a/spec/controllers/groups/settings/ci_cd_controller_spec.rb
+++ b/spec/controllers/groups/settings/ci_cd_controller_spec.rb
@@ -5,8 +5,15 @@ require 'spec_helper'
RSpec.describe Groups::Settings::CiCdController do
include ExternalAuthorizationServiceHelpers
- let(:group) { create(:group) }
- let(:user) { create(:user) }
+ let_it_be(:group) { create(:group) }
+ let_it_be(:sub_group) { create(:group, parent: group) }
+ let_it_be(:user) { create(:user) }
+ let_it_be(:project) { create(:project, group: group) }
+ let_it_be(:project_2) { create(:project, group: sub_group) }
+ let_it_be(:runner_group) { create(:ci_runner, :group, groups: [group]) }
+ let_it_be(:runner_project_1) { create(:ci_runner, :project, projects: [project])}
+ let_it_be(:runner_project_2) { create(:ci_runner, :project, projects: [project_2])}
+ let_it_be(:runner_project_3) { create(:ci_runner, :project, projects: [project, project_2])}
before do
sign_in(user)
@@ -18,11 +25,12 @@ RSpec.describe Groups::Settings::CiCdController do
group.add_owner(user)
end
- it 'renders show with 200 status code' do
+ it 'renders show with 200 status code and correct runners' do
get :show, params: { group_id: group }
expect(response).to have_gitlab_http_status(:ok)
expect(response).to render_template(:show)
+ expect(assigns(:group_runners)).to match_array([runner_group, runner_project_1, runner_project_2, runner_project_3])
end
end
@@ -35,6 +43,7 @@ RSpec.describe Groups::Settings::CiCdController do
get :show, params: { group_id: group }
expect(response).to have_gitlab_http_status(:not_found)
+ expect(assigns(:group_runners)).to be_nil
end
end
diff --git a/spec/controllers/groups_controller_spec.rb b/spec/controllers/groups_controller_spec.rb
index dce7105c073..469e58c94e7 100644
--- a/spec/controllers/groups_controller_spec.rb
+++ b/spec/controllers/groups_controller_spec.rb
@@ -52,8 +52,6 @@ RSpec.describe GroupsController do
expect(assigns(:events).map(&:id)).to contain_exactly(event.id)
end
end
-
- it_behaves_like 'namespace storage limit alert'
end
describe 'GET #show' do
@@ -941,7 +939,7 @@ RSpec.describe GroupsController do
allow(Gitlab::ApplicationRateLimiter)
.to receive(:increment)
- .and_return(Gitlab::ApplicationRateLimiter.rate_limits[:group_export][:threshold] + 1)
+ .and_return(Gitlab::ApplicationRateLimiter.rate_limits[:group_export][:threshold].call + 1)
end
it 'throttles the endpoint' do
@@ -1015,7 +1013,7 @@ RSpec.describe GroupsController do
allow(Gitlab::ApplicationRateLimiter)
.to receive(:increment)
- .and_return(Gitlab::ApplicationRateLimiter.rate_limits[:group_download_export][:threshold] + 1)
+ .and_return(Gitlab::ApplicationRateLimiter.rate_limits[:group_download_export][:threshold].call + 1)
end
it 'throttles the endpoint' do
@@ -1138,6 +1136,36 @@ RSpec.describe GroupsController do
it_behaves_like 'disabled when using an external authorization service'
end
+ describe "GET #activity as JSON" do
+ include DesignManagementTestHelpers
+ render_views
+
+ let(:project) { create(:project, :public, group: group) }
+ let(:other_project) { create(:project, :public, group: group) }
+
+ def get_activity
+ get :activity, params: { format: :json, id: group.to_param }
+ end
+
+ before do
+ enable_design_management
+ issue = create(:issue, project: project)
+ create(:event, :created, project: project, target: issue)
+ create(:design_event, project: project)
+ create(:design_event, project: other_project)
+
+ sign_in(user)
+
+ request.cookies[:event_filter] = 'all'
+ end
+
+ it 'returns count' do
+ get_activity
+
+ expect(json_response['count']).to eq(3)
+ end
+ end
+
describe 'GET #issues' do
subject { get :issues, params: { id: group.to_param } }
diff --git a/spec/controllers/import/bitbucket_controller_spec.rb b/spec/controllers/import/bitbucket_controller_spec.rb
index ec38a635c2d..0427715d1ac 100644
--- a/spec/controllers/import/bitbucket_controller_spec.rb
+++ b/spec/controllers/import/bitbucket_controller_spec.rb
@@ -58,12 +58,12 @@ RSpec.describe Import::BitbucketController do
before do
@repo = double(name: 'vim', slug: 'vim', owner: 'asd', full_name: 'asd/vim', clone_url: 'http://test.host/demo/url.git', 'valid?' => true)
@invalid_repo = double(name: 'mercurialrepo', slug: 'mercurialrepo', owner: 'asd', full_name: 'asd/mercurialrepo', clone_url: 'http://test.host/demo/mercurialrepo.git', 'valid?' => false)
+ allow(controller).to receive(:provider_url).and_return('http://demobitbucket.org')
assign_session_tokens
- stub_feature_flags(new_import_ui: false)
end
- it_behaves_like 'import controller with new_import_ui feature flag' do
+ it_behaves_like 'import controller status' do
before do
allow(controller).to receive(:provider_url).and_return('http://demobitbucket.org')
end
@@ -75,44 +75,16 @@ RSpec.describe Import::BitbucketController do
let(:client_repos_field) { :repos }
end
- context 'with new_import_ui feature flag enabled' do
- before do
- stub_feature_flags(new_import_ui: true)
- allow(controller).to receive(:provider_url).and_return('http://demobitbucket.org')
- end
-
- it 'returns invalid repos' do
- allow_any_instance_of(Bitbucket::Client).to receive(:repos).and_return([@repo, @invalid_repo])
-
- get :status, format: :json
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(json_response['incompatible_repos'].length).to eq(1)
- expect(json_response.dig("incompatible_repos", 0, "id")).to eq(@invalid_repo.full_name)
- expect(json_response['provider_repos'].length).to eq(1)
- expect(json_response.dig("provider_repos", 0, "id")).to eq(@repo.full_name)
- end
- end
-
- it "assigns variables" do
- @project = create(:project, import_type: 'bitbucket', creator_id: user.id)
- allow_any_instance_of(Bitbucket::Client).to receive(:repos).and_return([@repo])
+ it 'returns invalid repos' do
+ allow_any_instance_of(Bitbucket::Client).to receive(:repos).and_return([@repo, @invalid_repo])
- get :status
-
- expect(assigns(:already_added_projects)).to eq([@project])
- expect(assigns(:repos)).to eq([@repo])
- expect(assigns(:incompatible_repos)).to eq([])
- end
+ get :status, format: :json
- it "does not show already added project" do
- @project = create(:project, import_type: 'bitbucket', creator_id: user.id, import_source: 'asd/vim')
- allow_any_instance_of(Bitbucket::Client).to receive(:repos).and_return([@repo])
-
- get :status
-
- expect(assigns(:already_added_projects)).to eq([@project])
- expect(assigns(:repos)).to eq([])
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response['incompatible_repos'].length).to eq(1)
+ expect(json_response.dig("incompatible_repos", 0, "id")).to eq(@invalid_repo.full_name)
+ expect(json_response['provider_repos'].length).to eq(1)
+ expect(json_response.dig("provider_repos", 0, "id")).to eq(@repo.full_name)
end
context 'when filtering' do
diff --git a/spec/controllers/import/bitbucket_server_controller_spec.rb b/spec/controllers/import/bitbucket_server_controller_spec.rb
index af471b478fa..bb80de6425f 100644
--- a/spec/controllers/import/bitbucket_server_controller_spec.rb
+++ b/spec/controllers/import/bitbucket_server_controller_spec.rb
@@ -6,6 +6,7 @@ RSpec.describe Import::BitbucketServerController do
let(:user) { create(:user) }
let(:project_key) { 'test-project' }
let(:repo_slug) { 'some-repo' }
+ let(:repo_id) { "#{project_key}/#{repo_slug}" }
let(:client) { instance_double(BitbucketServer::Client) }
def assign_session_tokens
@@ -46,7 +47,7 @@ RSpec.describe Import::BitbucketServerController do
.to receive(:new).with(project_key, repo_slug, anything, project_name, user.namespace, user, anything)
.and_return(double(execute: project))
- post :create, params: { project: project_key, repository: repo_slug }, format: :json
+ post :create, params: { repo_id: repo_id }, format: :json
expect(response).to have_gitlab_http_status(:ok)
end
@@ -59,20 +60,20 @@ RSpec.describe Import::BitbucketServerController do
.to receive(:new).with(project_key, repo_slug, anything, project_name, user.namespace, user, anything)
.and_return(double(execute: project))
- post :create, params: { project: project_key, repository: repo_slug, format: :json }
+ post :create, params: { repo_id: repo_id }, format: :json
expect(response).to have_gitlab_http_status(:ok)
end
end
it 'returns an error when an invalid project key is used' do
- post :create, params: { project: 'some&project' }
+ post :create, params: { repo_id: 'some&project/repo' }
expect(response).to have_gitlab_http_status(:unprocessable_entity)
end
it 'returns an error when an invalid repository slug is used' do
- post :create, params: { project: 'some-project', repository: 'try*this' }
+ post :create, params: { repo_id: 'some-project/try*this' }
expect(response).to have_gitlab_http_status(:unprocessable_entity)
end
@@ -80,7 +81,7 @@ RSpec.describe Import::BitbucketServerController do
it 'returns an error when the project cannot be found' do
allow(client).to receive(:repo).with(project_key, repo_slug).and_return(nil)
- post :create, params: { project: project_key, repository: repo_slug }, format: :json
+ post :create, params: { repo_id: repo_id }, format: :json
expect(response).to have_gitlab_http_status(:unprocessable_entity)
end
@@ -90,15 +91,15 @@ RSpec.describe Import::BitbucketServerController do
.to receive(:new).with(project_key, repo_slug, anything, project_name, user.namespace, user, anything)
.and_return(double(execute: build(:project)))
- post :create, params: { project: project_key, repository: repo_slug }, format: :json
+ post :create, params: { repo_id: repo_id }, format: :json
expect(response).to have_gitlab_http_status(:unprocessable_entity)
end
it "returns an error when the server can't be contacted" do
- expect(client).to receive(:repo).with(project_key, repo_slug).and_raise(::BitbucketServer::Connection::ConnectionError)
+ allow(client).to receive(:repo).with(project_key, repo_slug).and_raise(::BitbucketServer::Connection::ConnectionError)
- post :create, params: { project: project_key, repository: repo_slug }, format: :json
+ post :create, params: { repo_id: repo_id }, format: :json
expect(response).to have_gitlab_http_status(:unprocessable_entity)
end
@@ -123,7 +124,9 @@ RSpec.describe Import::BitbucketServerController do
end
it 'sets the session variables' do
- post :configure, params: { personal_access_token: token, bitbucket_username: username, bitbucket_server_url: url }
+ allow(controller).to receive(:allow_local_requests?).and_return(true)
+
+ post :configure, params: { personal_access_token: token, bitbucket_server_username: username, bitbucket_server_url: url }
expect(session[:bitbucket_server_url]).to eq(url)
expect(session[:bitbucket_server_username]).to eq(username)
@@ -145,28 +148,21 @@ RSpec.describe Import::BitbucketServerController do
@invalid_repo = double(slug: 'invalid', project_key: 'foobar', full_name: 'asd/foobar', "valid?" => false, browse_url: 'http://bad-repo', name: 'invalid')
@created_repo = double(slug: 'created', project_key: 'existing', full_name: 'group/created', "valid?" => true, browse_url: 'http://existing')
assign_session_tokens
- stub_feature_flags(new_import_ui: false)
end
- context 'with new_import_ui feature flag enabled' do
- before do
- stub_feature_flags(new_import_ui: true)
- end
-
- it 'returns invalid repos' do
- allow(client).to receive(:repos).with(filter: nil, limit: 25, page_offset: 0).and_return([@repo, @invalid_repo])
+ it 'returns invalid repos' do
+ allow(client).to receive(:repos).with(filter: nil, limit: 25, page_offset: 0).and_return([@repo, @invalid_repo])
- get :status, format: :json
+ get :status, format: :json
- expect(response).to have_gitlab_http_status(:ok)
- expect(json_response['incompatible_repos'].length).to eq(1)
- expect(json_response.dig("incompatible_repos", 0, "id")).to eq(@invalid_repo.full_name)
- expect(json_response['provider_repos'].length).to eq(1)
- expect(json_response.dig("provider_repos", 0, "id")).to eq(@repo.full_name)
- end
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response['incompatible_repos'].length).to eq(1)
+ expect(json_response.dig("incompatible_repos", 0, "id")).to eq(@invalid_repo.full_name)
+ expect(json_response['provider_repos'].length).to eq(1)
+ expect(json_response.dig("provider_repos", 0, "id")).to eq(@repo.full_name)
end
- it_behaves_like 'import controller with new_import_ui feature flag' do
+ it_behaves_like 'import controller status' do
let(:repo) { @repo }
let(:repo_id) { @repo.full_name }
let(:import_source) { @repo.browse_url }
@@ -174,47 +170,14 @@ RSpec.describe Import::BitbucketServerController do
let(:client_repos_field) { :repos }
end
- it 'assigns repository categories' do
- created_project = create(:project, :import_finished, import_type: 'bitbucket_server', creator_id: user.id, import_source: @created_repo.browse_url)
-
- expect(repos).to receive(:partition).and_return([[@repo, @created_repo], [@invalid_repo]])
- expect(repos).to receive(:current_page).and_return(1)
- expect(repos).to receive(:next_page).and_return(2)
- expect(repos).to receive(:prev_page).and_return(nil)
- expect(client).to receive(:repos).and_return(repos)
-
- get :status
-
- expect(assigns(:already_added_projects)).to eq([created_project])
- expect(assigns(:repos)).to eq([@repo])
- expect(assigns(:incompatible_repos)).to eq([@invalid_repo])
- end
-
context 'when filtering' do
let(:filter) { 'test' }
it 'passes filter param to bitbucket client' do
- expect(repos).to receive(:partition).and_return([[@repo, @created_repo], [@invalid_repo]])
- expect(client).to receive(:repos).with(filter: filter, limit: 25, page_offset: 0).and_return(repos)
+ expect(client).to receive(:repos).with(filter: filter, limit: 25, page_offset: 0).and_return([@repo])
get :status, params: { filter: filter }, as: :json
end
end
end
-
- describe 'GET jobs' do
- before do
- assign_session_tokens
- end
-
- it 'returns a list of imported projects' do
- created_project = create(:project, import_type: 'bitbucket_server', creator_id: user.id)
-
- get :jobs
-
- expect(json_response.count).to eq(1)
- expect(json_response.first['id']).to eq(created_project.id)
- expect(json_response.first['import_status']).to eq('none')
- end
- end
end
diff --git a/spec/controllers/import/fogbugz_controller_spec.rb b/spec/controllers/import/fogbugz_controller_spec.rb
index aabbcb30358..376c089df78 100644
--- a/spec/controllers/import/fogbugz_controller_spec.rb
+++ b/spec/controllers/import/fogbugz_controller_spec.rb
@@ -82,36 +82,15 @@ RSpec.describe Import::FogbugzController do
before do
@repo = OpenStruct.new(id: 'demo', name: 'vim')
stub_client(valid?: true)
- stub_feature_flags(new_import_ui: false)
end
- it_behaves_like 'import controller with new_import_ui feature flag' do
+ it_behaves_like 'import controller status' do
let(:repo) { @repo }
let(:repo_id) { @repo.id }
let(:import_source) { @repo.name }
let(:provider_name) { 'fogbugz' }
let(:client_repos_field) { :repos }
end
-
- it 'assigns variables' do
- @project = create(:project, import_type: 'fogbugz', creator_id: user.id)
- stub_client(repos: [@repo])
-
- get :status
-
- expect(assigns(:already_added_projects)).to eq([@project])
- expect(assigns(:repos)).to eq([@repo])
- end
-
- it 'does not show already added project' do
- @project = create(:project, import_type: 'fogbugz', creator_id: user.id, import_source: 'vim')
- stub_client(repos: [@repo])
-
- get :status
-
- expect(assigns(:already_added_projects)).to eq([@project])
- expect(assigns(:repos)).to eq([])
- end
end
describe 'POST create' do
diff --git a/spec/controllers/import/gitlab_controller_spec.rb b/spec/controllers/import/gitlab_controller_spec.rb
index 1cd0593f762..42c4348dac2 100644
--- a/spec/controllers/import/gitlab_controller_spec.rb
+++ b/spec/controllers/import/gitlab_controller_spec.rb
@@ -36,36 +36,15 @@ RSpec.describe Import::GitlabController do
before do
@repo = OpenStruct.new(id: 1, path: 'vim', path_with_namespace: 'asd/vim', web_url: 'https://gitlab.com/asd/vim')
assign_session_token
- stub_feature_flags(new_import_ui: false)
end
- it_behaves_like 'import controller with new_import_ui feature flag' do
+ it_behaves_like 'import controller status' do
let(:repo) { @repo }
let(:repo_id) { @repo.id }
let(:import_source) { @repo.path_with_namespace }
let(:provider_name) { 'gitlab' }
let(:client_repos_field) { :projects }
end
-
- it "assigns variables" do
- @project = create(:project, import_type: 'gitlab', creator_id: user.id)
- stub_client(projects: [@repo])
-
- get :status
-
- expect(assigns(:already_added_projects)).to eq([@project])
- expect(assigns(:repos)).to eq([@repo])
- end
-
- it "does not show already added project" do
- @project = create(:project, import_type: 'gitlab', creator_id: user.id, import_source: 'asd/vim')
- stub_client(projects: [@repo])
-
- get :status
-
- expect(assigns(:already_added_projects)).to eq([@project])
- expect(assigns(:repos)).to eq([])
- end
end
describe "POST create" do
diff --git a/spec/controllers/instance_statistics/cohorts_controller_spec.rb b/spec/controllers/instance_statistics/cohorts_controller_spec.rb
index b92fcb2575c..c16ac0dced1 100644
--- a/spec/controllers/instance_statistics/cohorts_controller_spec.rb
+++ b/spec/controllers/instance_statistics/cohorts_controller_spec.rb
@@ -18,4 +18,11 @@ RSpec.describe InstanceStatistics::CohortsController do
expect(response).to have_gitlab_http_status(:not_found)
end
+
+ describe 'GET #index' do
+ it_behaves_like 'tracking unique visits', :index do
+ let(:request_params) { {} }
+ let(:target_id) { 'i_analytics_cohorts' }
+ end
+ end
end
diff --git a/spec/controllers/instance_statistics/dev_ops_score_controller_spec.rb b/spec/controllers/instance_statistics/dev_ops_score_controller_spec.rb
index d729682bef0..c9677a64eef 100644
--- a/spec/controllers/instance_statistics/dev_ops_score_controller_spec.rb
+++ b/spec/controllers/instance_statistics/dev_ops_score_controller_spec.rb
@@ -4,4 +4,17 @@ require 'spec_helper'
RSpec.describe InstanceStatistics::DevOpsScoreController do
it_behaves_like 'instance statistics availability'
+
+ describe 'GET #index' do
+ let(:user) { create(:user) }
+
+ before do
+ sign_in(user)
+ end
+
+ it_behaves_like 'tracking unique visits', :index do
+ let(:request_params) { {} }
+ let(:target_id) { 'i_analytics_dev_ops_score' }
+ end
+ end
end
diff --git a/spec/controllers/invites_controller_spec.rb b/spec/controllers/invites_controller_spec.rb
index f2821bb67e8..a9e4073780d 100644
--- a/spec/controllers/invites_controller_spec.rb
+++ b/spec/controllers/invites_controller_spec.rb
@@ -4,21 +4,44 @@ require 'spec_helper'
RSpec.describe InvitesController do
let(:token) { '123456' }
- let(:user) { create(:user) }
- let(:member) { create(:project_member, invite_token: token, invite_email: 'test@abc.com', user: user) }
+ let_it_be(:user) { create(:user) }
+ let(:member) { create(:project_member, :invited, invite_token: token, invite_email: user.email) }
+ let(:project_members) { member.source.users }
before do
controller.instance_variable_set(:@member, member)
sign_in(user)
end
- describe 'GET #accept' do
+ describe 'GET #show' do
+ it 'accepts user if invite email matches signed in user' do
+ expect do
+ get :show, params: { id: token }
+ end.to change { project_members.include?(user) }.from(false).to(true)
+
+ expect(response).to have_gitlab_http_status(:found)
+ expect(flash[:notice]).to include 'You have been granted'
+ end
+
+ it 'forces re-confirmation if email does not match signed in user' do
+ member.invite_email = 'bogus@email.com'
+
+ expect do
+ get :show, params: { id: token }
+ end.not_to change { project_members.include?(user) }
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(flash[:notice]).to be_nil
+ end
+ end
+
+ describe 'POST #accept' do
it 'accepts user' do
- get :accept, params: { id: token }
- member.reload
+ expect do
+ post :accept, params: { id: token }
+ end.to change { project_members.include?(user) }.from(false).to(true)
expect(response).to have_gitlab_http_status(:found)
- expect(member.user).to eq(user)
expect(flash[:notice]).to include 'You have been granted'
end
end
@@ -26,8 +49,8 @@ RSpec.describe InvitesController do
describe 'GET #decline' do
it 'declines user' do
get :decline, params: { id: token }
- expect {member.reload}.to raise_error ActiveRecord::RecordNotFound
+ expect { member.reload }.to raise_error ActiveRecord::RecordNotFound
expect(response).to have_gitlab_http_status(:found)
expect(flash[:notice]).to include 'You have declined the invitation to join'
end
diff --git a/spec/controllers/oauth/applications_controller_spec.rb b/spec/controllers/oauth/applications_controller_spec.rb
index f20204b6718..38f46ee7b15 100644
--- a/spec/controllers/oauth/applications_controller_spec.rb
+++ b/spec/controllers/oauth/applications_controller_spec.rb
@@ -121,6 +121,22 @@ RSpec.describe Oauth::ApplicationsController do
end
end
+ describe 'locale' do
+ let(:user) { create(:user, preferred_language: 'uk') }
+
+ before do
+ sign_in(user)
+
+ allow(Gitlab::I18n).to receive(:with_locale).and_call_original
+ end
+
+ it "sets user's locale" do
+ expect(Gitlab::I18n).to receive(:with_locale).with('uk')
+
+ get :new
+ end
+ end
+
def disable_user_oauth
allow(Gitlab::CurrentSettings.current_application_settings).to receive(:user_oauth_applications?).and_return(false)
end
diff --git a/spec/controllers/profiles/keys_controller_spec.rb b/spec/controllers/profiles/keys_controller_spec.rb
index 258ed62262a..66f6135df1e 100644
--- a/spec/controllers/profiles/keys_controller_spec.rb
+++ b/spec/controllers/profiles/keys_controller_spec.rb
@@ -20,69 +20,4 @@ RSpec.describe Profiles::KeysController do
expect(Key.last.expires_at).to be_like_time(expires_at)
end
end
-
- describe "#get_keys" do
- describe "non existent user" do
- it "does not generally work" do
- get :get_keys, params: { username: 'not-existent' }
-
- expect(response).not_to be_successful
- end
- end
-
- describe "user with no keys" do
- it "does generally work" do
- get :get_keys, params: { username: user.username }
-
- expect(response).to be_successful
- end
-
- it "renders all keys separated with a new line" do
- get :get_keys, params: { username: user.username }
-
- expect(response.body).to eq("")
- end
-
- it "responds with text/plain content type" do
- get :get_keys, params: { username: user.username }
- expect(response.content_type).to eq("text/plain")
- end
- end
-
- describe "user with keys" do
- let!(:key) { create(:key, user: user) }
- let!(:another_key) { create(:another_key, user: user) }
- let!(:deploy_key) { create(:deploy_key, user: user) }
-
- it "does generally work" do
- get :get_keys, params: { username: user.username }
-
- expect(response).to be_successful
- end
-
- it "renders all non deploy keys separated with a new line" do
- get :get_keys, params: { username: user.username }
-
- expect(response.body).not_to eq('')
- expect(response.body).to eq(user.all_ssh_keys.join("\n"))
-
- expect(response.body).to include(key.key.sub(' dummy@gitlab.com', ''))
- expect(response.body).to include(another_key.key.sub(' dummy@gitlab.com', ''))
-
- expect(response.body).not_to include(deploy_key.key)
- end
-
- it "does not render the comment of the key" do
- get :get_keys, params: { username: user.username }
-
- expect(response.body).not_to match(/dummy@gitlab.com/)
- end
-
- it "responds with text/plain content type" do
- get :get_keys, params: { username: user.username }
-
- expect(response.content_type).to eq("text/plain")
- end
- end
- end
end
diff --git a/spec/controllers/projects/clusters_controller_spec.rb b/spec/controllers/projects/clusters_controller_spec.rb
index 5645e25b741..da4faad2a39 100644
--- a/spec/controllers/projects/clusters_controller_spec.rb
+++ b/spec/controllers/projects/clusters_controller_spec.rb
@@ -200,6 +200,48 @@ RSpec.describe Projects::ClustersController do
end
end
+ describe 'GET #prometheus_proxy' do
+ let(:proxyable) do
+ create(:cluster, :provided_by_gcp, projects: [project])
+ end
+
+ it_behaves_like 'metrics dashboard prometheus api proxy' do
+ let(:proxyable_params) do
+ {
+ id: proxyable.id.to_s,
+ namespace_id: project.namespace.full_path,
+ project_id: project.name
+ }
+ end
+
+ context 'with anonymous user' do
+ let(:prometheus_body) { nil }
+
+ before do
+ sign_out(user)
+ end
+
+ it 'redirects to signin page' do
+ get :prometheus_proxy, params: prometheus_proxy_params
+
+ expect(response).to redirect_to(new_user_session_path)
+ end
+ end
+ end
+ end
+
+ it_behaves_like 'GET #metrics_dashboard for dashboard', 'Cluster health' do
+ let(:cluster) { create(:cluster, :provided_by_gcp, projects: [project]) }
+
+ let(:metrics_dashboard_req_params) do
+ {
+ id: cluster.id,
+ namespace_id: project.namespace.full_path,
+ project_id: project.name
+ }
+ end
+ end
+
describe 'POST create for new cluster' do
let(:legacy_abac_param) { 'true' }
let(:params) do
diff --git a/spec/controllers/projects/cycle_analytics_controller_spec.rb b/spec/controllers/projects/cycle_analytics_controller_spec.rb
index 8feb964cdde..ec853b74b9b 100644
--- a/spec/controllers/projects/cycle_analytics_controller_spec.rb
+++ b/spec/controllers/projects/cycle_analytics_controller_spec.rb
@@ -25,6 +25,13 @@ RSpec.describe Projects::CycleAnalyticsController do
end
end
+ context 'tracking visits to html page' do
+ it_behaves_like 'tracking unique visits', :show do
+ let(:request_params) { { namespace_id: project.namespace, project_id: project } }
+ let(:target_id) { 'p_analytics_valuestream' }
+ end
+ end
+
describe 'cycle analytics not set up flag' do
context 'with no data' do
it 'is true' do
diff --git a/spec/controllers/projects/deployments_controller_spec.rb b/spec/controllers/projects/deployments_controller_spec.rb
index 85dd86d91e9..c6532e83441 100644
--- a/spec/controllers/projects/deployments_controller_spec.rb
+++ b/spec/controllers/projects/deployments_controller_spec.rb
@@ -36,6 +36,52 @@ RSpec.describe Projects::DeploymentsController do
expect(response).to be_ok
expect(response).to match_response_schema('deployments')
end
+
+ context 'anonymous user' do
+ let(:anonymous_user) { create(:user) }
+
+ before do
+ sign_in(anonymous_user)
+ end
+
+ context 'project and metrics dashboard are public' do
+ before do
+ project.update!(
+ visibility_level: Gitlab::VisibilityLevel::PUBLIC,
+ project_feature_attributes: {
+ metrics_dashboard_access_level: Gitlab::VisibilityLevel::PUBLIC
+ }
+ )
+ end
+
+ it 'returns a list with deployments information' do
+ create(:deployment, :success, environment: environment)
+
+ get :index, params: deployment_params
+
+ expect(response).to be_ok
+ end
+ end
+
+ context 'project and metrics dashboard are private' do
+ before do
+ project.update!(
+ visibility_level: Gitlab::VisibilityLevel::PRIVATE,
+ project_feature_attributes: {
+ metrics_dashboard_access_level: Gitlab::VisibilityLevel::PRIVATE
+ }
+ )
+ end
+
+ it 'responds with not found' do
+ create(:deployment, :success, environment: environment)
+
+ get :index, params: deployment_params
+
+ expect(response).to be_not_found
+ end
+ end
+ end
end
describe 'GET #metrics' do
diff --git a/spec/controllers/projects/discussions_controller_spec.rb b/spec/controllers/projects/discussions_controller_spec.rb
index f2efd40afdb..f9d16e761cb 100644
--- a/spec/controllers/projects/discussions_controller_spec.rb
+++ b/spec/controllers/projects/discussions_controller_spec.rb
@@ -182,7 +182,8 @@ RSpec.describe Projects::DiscussionsController do
it "unresolves the discussion" do
delete :unresolve, params: request_params
- expect(note.reload.discussion.resolved?).to be false
+ # discussion is memoized and reload doesn't clear the memoization
+ expect(Note.find(note.id).discussion.resolved?).to be false
end
it "returns status 200" do
diff --git a/spec/controllers/projects/environments/prometheus_api_controller_spec.rb b/spec/controllers/projects/environments/prometheus_api_controller_spec.rb
index 17952aa0683..68d50cf19f0 100644
--- a/spec/controllers/projects/environments/prometheus_api_controller_spec.rb
+++ b/spec/controllers/projects/environments/prometheus_api_controller_spec.rb
@@ -3,215 +3,73 @@
require 'spec_helper'
RSpec.describe Projects::Environments::PrometheusApiController do
- let_it_be(:project) { create(:project) }
- let_it_be(:environment) { create(:environment, project: project) }
let_it_be(:user) { create(:user) }
+ let_it_be(:project) { create(:project) }
+ let_it_be(:proxyable) { create(:environment, project: project) }
before do
project.add_reporter(user)
sign_in(user)
end
- describe 'GET #proxy' do
- let(:prometheus_proxy_service) { instance_double(Prometheus::ProxyService) }
-
- let(:expected_params) do
- ActionController::Parameters.new(
- environment_params(
- proxy_path: 'query',
- controller: 'projects/environments/prometheus_api',
- action: 'proxy'
- )
- ).permit!
- end
-
- context 'with valid requests' do
- before do
- allow(Prometheus::ProxyService).to receive(:new)
- .with(environment, 'GET', 'query', expected_params)
- .and_return(prometheus_proxy_service)
-
- allow(prometheus_proxy_service).to receive(:execute)
- .and_return(service_result)
+ describe 'GET #prometheus_proxy' do
+ it_behaves_like 'metrics dashboard prometheus api proxy' do
+ let(:proxyable_params) do
+ {
+ id: proxyable.id.to_s,
+ namespace_id: project.namespace.full_path,
+ project_id: project.name
+ }
end
- context 'with success result' do
- let(:service_result) { { status: :success, body: prometheus_body } }
+ context 'with variables' do
let(:prometheus_body) { '{"status":"success"}' }
- let(:prometheus_json_body) { Gitlab::Json.parse(prometheus_body) }
+ let(:pod_name) { "pod1" }
- it 'returns prometheus response' do
- get :proxy, params: environment_params
-
- expect(Prometheus::ProxyService).to have_received(:new)
- .with(environment, 'GET', 'query', expected_params)
- expect(response).to have_gitlab_http_status(:ok)
- expect(json_response).to eq(prometheus_json_body)
+ before do
+ expected_params[:query] = %{up{pod_name="#{pod_name}"}}
+ expected_params[:variables] = { 'pod_name' => pod_name }
end
- context 'with format string' do
- before do
- expected_params[:query] = %{up{environment="#{environment.slug}"}}
- end
-
- it 'replaces variables with values' do
- get :proxy, params: environment_params.merge(query: 'up{environment="{{ci_environment_slug}}"}')
-
- expect(Prometheus::ProxyService).to have_received(:new)
- .with(environment, 'GET', 'query', expected_params)
- end
-
- context 'with nil query' do
- let(:params_without_query) do
- environment_params.except(:query)
- end
-
- before do
- expected_params.delete(:query)
- end
-
- it 'does not raise error' do
- get :proxy, params: params_without_query
+ it 'replaces variables with values' do
+ get :prometheus_proxy, params: prometheus_proxy_params.merge(
+ query: 'up{pod_name="{{pod_name}}"}', variables: { 'pod_name' => pod_name }
+ )
- expect(Prometheus::ProxyService).to have_received(:new)
- .with(environment, 'GET', 'query', expected_params)
- end
- end
+ expect(response).to have_gitlab_http_status(:success)
+ expect(Prometheus::ProxyService).to have_received(:new)
+ .with(proxyable, 'GET', 'query', expected_params)
end
- context 'with variables' do
- let(:pod_name) { "pod1" }
-
- before do
- expected_params[:query] = %{up{pod_name="#{pod_name}"}}
- expected_params[:variables] = { 'pod_name' => pod_name }
- end
-
- it 'replaces variables with values' do
- get :proxy, params: environment_params.merge(
- query: 'up{pod_name="{{pod_name}}"}', variables: { 'pod_name' => pod_name }
+ context 'with invalid variables' do
+ let(:params_with_invalid_variables) do
+ prometheus_proxy_params.merge(
+ query: 'up{pod_name="{{pod_name}}"}', variables: ['a']
)
-
- expect(response).to have_gitlab_http_status(:success)
- expect(Prometheus::ProxyService).to have_received(:new)
- .with(environment, 'GET', 'query', expected_params)
- end
-
- context 'with invalid variables' do
- let(:params_with_invalid_variables) do
- environment_params.merge(
- query: 'up{pod_name="{{pod_name}}"}', variables: ['a']
- )
- end
-
- it 'returns 400' do
- get :proxy, params: params_with_invalid_variables
-
- expect(response).to have_gitlab_http_status(:bad_request)
- expect(Prometheus::ProxyService).not_to receive(:new)
- end
- end
- end
- end
-
- context 'with nil result' do
- let(:service_result) { nil }
-
- it 'returns 204 no_content' do
- get :proxy, params: environment_params
-
- expect(json_response['status']).to eq(_('processing'))
- expect(json_response['message']).to eq(_('Not ready yet. Try again later.'))
- expect(response).to have_gitlab_http_status(:no_content)
- end
- end
-
- context 'with 404 result' do
- let(:service_result) { { http_status: 404, status: :success, body: '{"body": "value"}' } }
-
- it 'returns body' do
- get :proxy, params: environment_params
-
- expect(response).to have_gitlab_http_status(:not_found)
- expect(json_response['body']).to eq('value')
- end
- end
-
- context 'with error result' do
- context 'with http_status' do
- let(:service_result) do
- { http_status: :service_unavailable, status: :error, message: 'error message' }
- end
-
- it 'sets the http response status code' do
- get :proxy, params: environment_params
-
- expect(response).to have_gitlab_http_status(:service_unavailable)
- expect(json_response['status']).to eq('error')
- expect(json_response['message']).to eq('error message')
end
- end
-
- context 'without http_status' do
- let(:service_result) { { status: :error, message: 'error message' } }
- it 'returns bad_request' do
- get :proxy, params: environment_params
+ it 'returns 400' do
+ get :prometheus_proxy, params: params_with_invalid_variables
expect(response).to have_gitlab_http_status(:bad_request)
- expect(json_response['status']).to eq('error')
- expect(json_response['message']).to eq('error message')
+ expect(Prometheus::ProxyService).not_to receive(:new)
end
end
end
- end
- context 'with inappropriate requests' do
context 'with anonymous user' do
+ let(:prometheus_body) { nil }
+
before do
sign_out(user)
end
it 'redirects to signin page' do
- get :proxy, params: environment_params
+ get :prometheus_proxy, params: prometheus_proxy_params
expect(response).to redirect_to(new_user_session_path)
end
end
-
- context 'without correct permissions' do
- before do
- project.team.truncate
- end
-
- it 'returns 404' do
- get :proxy, params: environment_params
-
- expect(response).to have_gitlab_http_status(:not_found)
- end
- end
end
-
- context 'with invalid environment id' do
- let(:other_environment) { create(:environment) }
-
- it 'returns 404' do
- get :proxy, params: environment_params(id: other_environment.id)
-
- expect(response).to have_gitlab_http_status(:not_found)
- end
- end
- end
-
- private
-
- def environment_params(params = {})
- {
- id: environment.id.to_s,
- namespace_id: project.namespace.full_path,
- project_id: project.name,
- proxy_path: 'query',
- query: '1'
- }.merge(params)
end
end
diff --git a/spec/controllers/projects/environments_controller_spec.rb b/spec/controllers/projects/environments_controller_spec.rb
index cca4b597f4c..85ec1f7396d 100644
--- a/spec/controllers/projects/environments_controller_spec.rb
+++ b/spec/controllers/projects/environments_controller_spec.rb
@@ -546,28 +546,20 @@ RSpec.describe Projects::EnvironmentsController do
end
describe 'GET #metrics_dashboard' do
- shared_examples_for 'correctly formatted response' do |status_code|
- it 'returns a json object with the correct keys' do
- get :metrics_dashboard, params: environment_params(dashboard_params)
-
- # Exlcude `all_dashboards` to handle separately.
- found_keys = json_response.keys - ['all_dashboards']
-
- expect(response).to have_gitlab_http_status(status_code)
- expect(found_keys).to contain_exactly(*expected_keys)
- end
- end
+ let(:metrics_dashboard_req_params) { environment_params(dashboard_params) }
shared_examples_for '200 response' do
- let(:expected_keys) { %w(dashboard status metrics_data) }
-
- it_behaves_like 'correctly formatted response', :ok
+ it_behaves_like 'GET #metrics_dashboard correctly formatted response' do
+ let(:expected_keys) { %w(dashboard status metrics_data) }
+ let(:status_code) { :ok }
+ end
end
shared_examples_for 'error response' do |status_code|
- let(:expected_keys) { %w(message status) }
-
- it_behaves_like 'correctly formatted response', status_code
+ it_behaves_like 'GET #metrics_dashboard correctly formatted response' do
+ let(:expected_keys) { %w(message status) }
+ let(:status_code) { status_code }
+ end
end
shared_examples_for 'includes all dashboards' do
@@ -581,29 +573,14 @@ RSpec.describe Projects::EnvironmentsController do
end
shared_examples_for 'the default dashboard' do
- it_behaves_like '200 response'
it_behaves_like 'includes all dashboards'
-
- it 'is the default dashboard' do
- get :metrics_dashboard, params: environment_params(dashboard_params)
-
- expect(json_response['dashboard']['dashboard']).to eq('Environment metrics')
- end
+ it_behaves_like 'GET #metrics_dashboard for dashboard', 'Environment metrics'
end
shared_examples_for 'the specified dashboard' do |expected_dashboard|
- it_behaves_like '200 response'
it_behaves_like 'includes all dashboards'
- it 'has the correct name' do
- get :metrics_dashboard, params: environment_params(dashboard_params)
-
- dashboard_name = json_response['dashboard']['dashboard']
-
- # 'Environment metrics' is the default dashboard.
- expect(dashboard_name).not_to eq('Environment metrics')
- expect(dashboard_name).to eq(expected_dashboard)
- end
+ it_behaves_like 'GET #metrics_dashboard for dashboard', expected_dashboard
context 'when the dashboard cannot not be processed' do
before do
diff --git a/spec/controllers/projects/graphs_controller_spec.rb b/spec/controllers/projects/graphs_controller_spec.rb
index 12cef6bea09..49def8f80b0 100644
--- a/spec/controllers/projects/graphs_controller_spec.rb
+++ b/spec/controllers/projects/graphs_controller_spec.rb
@@ -80,6 +80,15 @@ RSpec.describe Projects::GraphsController do
expect(assigns[:daily_coverage_options]).to be_nil
end
end
+
+ it_behaves_like 'tracking unique visits', :charts do
+ before do
+ sign_in(user)
+ end
+
+ let(:request_params) { { namespace_id: project.namespace.path, project_id: project.path, id: 'master' } }
+ let(:target_id) { 'p_analytics_repo' }
+ end
end
context 'when languages were previously detected' do
diff --git a/spec/controllers/projects/imports_controller_spec.rb b/spec/controllers/projects/imports_controller_spec.rb
index 29cfd1c352e..029b4210f19 100644
--- a/spec/controllers/projects/imports_controller_spec.rb
+++ b/spec/controllers/projects/imports_controller_spec.rb
@@ -8,33 +8,15 @@ RSpec.describe Projects::ImportsController do
before do
sign_in(user)
- project.add_maintainer(user)
end
describe 'GET #show' do
- context 'when repository does not exists' do
- it 'renders template' do
- get :show, params: { namespace_id: project.namespace.to_param, project_id: project }
-
- expect(response).to render_template :show
- end
-
- it 'sets flash.now if params is present' do
- get :show, params: { namespace_id: project.namespace.to_param, project_id: project, continue: { to: '/', notice_now: 'Started' } }
-
- expect(flash.now[:notice]).to eq 'Started'
+ context 'when the user has maintainer rights' do
+ before do
+ project.add_maintainer(user)
end
- end
-
- context 'when repository exists' do
- let(:project) { create(:project_empty_repo, import_url: 'https://github.com/vim/vim.git') }
- let(:import_state) { project.import_state }
-
- context 'when import is in progress' do
- before do
- import_state.update(status: :started)
- end
+ context 'when repository does not exists' do
it 'renders template' do
get :show, params: { namespace_id: project.namespace.to_param, project_id: project }
@@ -42,82 +24,138 @@ RSpec.describe Projects::ImportsController do
end
it 'sets flash.now if params is present' do
- get :show, params: { namespace_id: project.namespace.to_param, project_id: project, continue: { to: '/', notice_now: 'In progress' } }
+ get :show, params: { namespace_id: project.namespace.to_param, project_id: project, continue: { to: '/', notice_now: 'Started' } }
- expect(flash.now[:notice]).to eq 'In progress'
+ expect(flash.now[:notice]).to eq 'Started'
end
end
- context 'when import failed' do
- before do
- import_state.update(status: :failed)
- end
+ context 'when repository exists' do
+ let(:project) { create(:project_empty_repo, import_url: 'https://github.com/vim/vim.git') }
+ let(:import_state) { project.import_state }
- it 'redirects to new_namespace_project_import_path' do
- get :show, params: { namespace_id: project.namespace.to_param, project_id: project }
+ context 'when import is in progress' do
+ before do
+ import_state.update(status: :started)
+ end
- expect(response).to redirect_to new_project_import_path(project)
- end
- end
+ it 'renders template' do
+ get :show, params: { namespace_id: project.namespace.to_param, project_id: project }
- context 'when import finished' do
- before do
- import_state.update(status: :finished)
+ expect(response).to render_template :show
+ end
+
+ it 'sets flash.now if params is present' do
+ get :show, params: { namespace_id: project.namespace.to_param, project_id: project, continue: { to: '/', notice_now: 'In progress' } }
+
+ expect(flash.now[:notice]).to eq 'In progress'
+ end
end
- context 'when project is a fork' do
- it 'redirects to namespace_project_path' do
- allow_any_instance_of(Project).to receive(:forked?).and_return(true)
+ context 'when import failed' do
+ before do
+ import_state.update(status: :failed)
+ end
+ it 'redirects to new_namespace_project_import_path' do
get :show, params: { namespace_id: project.namespace.to_param, project_id: project }
- expect(flash[:notice]).to eq 'The project was successfully forked.'
- expect(response).to redirect_to project_path(project)
+ expect(response).to redirect_to new_project_import_path(project)
end
end
- context 'when project is external' do
- it 'redirects to namespace_project_path' do
- get :show, params: { namespace_id: project.namespace.to_param, project_id: project }
+ context 'when import finished' do
+ before do
+ import_state.update(status: :finished)
+ end
- expect(flash[:notice]).to eq 'The project was successfully imported.'
- expect(response).to redirect_to project_path(project)
+ context 'when project is a fork' do
+ it 'redirects to namespace_project_path' do
+ allow_any_instance_of(Project).to receive(:forked?).and_return(true)
+
+ get :show, params: { namespace_id: project.namespace.to_param, project_id: project }
+
+ expect(flash[:notice]).to eq 'The project was successfully forked.'
+ expect(response).to redirect_to project_path(project)
+ end
end
- end
- context 'when continue params is present' do
- let(:params) do
- {
- to: project_path(project),
- notice: 'Finished'
- }
+ context 'when project is external' do
+ it 'redirects to namespace_project_path' do
+ get :show, params: { namespace_id: project.namespace.to_param, project_id: project }
+
+ expect(flash[:notice]).to eq 'The project was successfully imported.'
+ expect(response).to redirect_to project_path(project)
+ end
end
- it 'redirects to internal params[:to]' do
- get :show, params: { namespace_id: project.namespace.to_param, project_id: project, continue: params }
+ context 'when continue params is present' do
+ let(:params) do
+ {
+ to: project_path(project),
+ notice: 'Finished'
+ }
+ end
+
+ it 'redirects to internal params[:to]' do
+ get :show, params: { namespace_id: project.namespace.to_param, project_id: project, continue: params }
+
+ expect(flash[:notice]).to eq params[:notice]
+ expect(response).to redirect_to params[:to]
+ end
- expect(flash[:notice]).to eq params[:notice]
- expect(response).to redirect_to params[:to]
+ it 'does not redirect to external params[:to]' do
+ params[:to] = "//google.com"
+
+ get :show, params: { namespace_id: project.namespace.to_param, project_id: project, continue: params }
+ expect(response).not_to redirect_to params[:to]
+ end
end
+ end
- it 'does not redirect to external params[:to]' do
- params[:to] = "//google.com"
+ context 'when import never happened' do
+ before do
+ import_state.update(status: :none)
+ end
- get :show, params: { namespace_id: project.namespace.to_param, project_id: project, continue: params }
- expect(response).not_to redirect_to params[:to]
+ it 'redirects to namespace_project_path' do
+ get :show, params: { namespace_id: project.namespace.to_param, project_id: project }
+
+ expect(response).to redirect_to project_path(project)
end
end
end
+ end
+
+ context 'when project is in group' do
+ let(:project) { create(:project_empty_repo, import_url: 'https://github.com/vim/vim.git', namespace: group) }
+
+ context 'when user has developer access to group and import is in progress' do
+ let(:import_state) { project.import_state }
- context 'when import never happened' do
before do
- import_state.update(status: :none)
+ group.add_developer(user)
+ import_state.update!(status: :started)
end
- it 'redirects to namespace_project_path' do
- get :show, params: { namespace_id: project.namespace.to_param, project_id: project }
+ context 'when group allows developers to create projects' do
+ let(:group) { create(:group, project_creation_level: Gitlab::Access::DEVELOPER_MAINTAINER_PROJECT_ACCESS) }
- expect(response).to redirect_to project_path(project)
+ it 'renders template' do
+ get :show, params: { namespace_id: project.namespace.to_param, project_id: project }
+
+ expect(response).to render_template :show
+ end
+ end
+
+ context 'when group prohibits developers to create projects' do
+ let(:group) { create(:group, project_creation_level: Gitlab::Access::MAINTAINER_PROJECT_ACCESS) }
+
+ it 'returns 404 response' do
+ get :show, params: { namespace_id: project.namespace.to_param, project_id: project }
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
end
end
end
@@ -128,6 +166,7 @@ RSpec.describe Projects::ImportsController do
let(:project) { create(:project) }
before do
+ project.add_maintainer(user)
allow(RepositoryImportWorker).to receive(:perform_async)
post :create, params: { project: params, namespace_id: project.namespace.to_param, project_id: project }
diff --git a/spec/controllers/projects/issues_controller_spec.rb b/spec/controllers/projects/issues_controller_spec.rb
index bcd1a53bd47..f9580c79390 100644
--- a/spec/controllers/projects/issues_controller_spec.rb
+++ b/spec/controllers/projects/issues_controller_spec.rb
@@ -237,7 +237,7 @@ RSpec.describe Projects::IssuesController do
context 'external issue tracker' do
let!(:service) do
- create(:custom_issue_tracker_service, project: project, title: 'Custom Issue Tracker', new_issue_url: 'http://test.com')
+ create(:custom_issue_tracker_service, project: project, new_issue_url: 'http://test.com')
end
before do
@@ -1564,6 +1564,43 @@ RSpec.describe Projects::IssuesController do
end
end
+ describe 'GET service_desk' do
+ let_it_be(:project) { create(:project_empty_repo, :public) }
+ let_it_be(:support_bot) { User.support_bot }
+ let_it_be(:other_user) { create(:user) }
+ let_it_be(:service_desk_issue_1) { create(:issue, project: project, author: support_bot) }
+ let_it_be(:service_desk_issue_2) { create(:issue, project: project, author: support_bot, assignees: [other_user]) }
+ let_it_be(:other_user_issue) { create(:issue, project: project, author: other_user) }
+
+ def get_service_desk(extra_params = {})
+ get :service_desk, params: extra_params.merge(namespace_id: project.namespace, project_id: project)
+ end
+
+ it 'adds an author filter for the support bot user' do
+ get_service_desk
+
+ expect(assigns(:issues)).to contain_exactly(service_desk_issue_1, service_desk_issue_2)
+ end
+
+ it 'does not allow any other author to be set' do
+ get_service_desk(author_username: other_user.username)
+
+ expect(assigns(:issues)).to contain_exactly(service_desk_issue_1, service_desk_issue_2)
+ end
+
+ it 'supports other filters' do
+ get_service_desk(assignee_username: other_user.username)
+
+ expect(assigns(:issues)).to contain_exactly(service_desk_issue_2)
+ end
+
+ it 'allows an assignee to be specified by id' do
+ get_service_desk(assignee_id: other_user.id)
+
+ expect(assigns(:users)).to contain_exactly(other_user, support_bot)
+ end
+ end
+
describe 'GET #discussions' do
let!(:discussion) { create(:discussion_note_on_issue, noteable: issue, project: issue.project) }
diff --git a/spec/controllers/projects/jobs_controller_spec.rb b/spec/controllers/projects/jobs_controller_spec.rb
index 44dcb0caab2..818b1c30b37 100644
--- a/spec/controllers/projects/jobs_controller_spec.rb
+++ b/spec/controllers/projects/jobs_controller_spec.rb
@@ -646,109 +646,6 @@ RSpec.describe Projects::JobsController, :clean_gitlab_redis_shared_state do
end
end
- describe 'GET legacy trace.json' do
- before do
- stub_feature_flags(job_log_json: false)
- get_trace
- end
-
- context 'when job has a trace artifact' do
- let(:job) { create(:ci_build, :trace_artifact, pipeline: pipeline) }
-
- it 'returns a trace' do
- expect(response).to have_gitlab_http_status(:ok)
- expect(json_response['id']).to eq job.id
- expect(json_response['status']).to eq job.status
- expect(json_response['state']).to be_present
- expect(json_response['append']).not_to be_nil
- expect(json_response['truncated']).not_to be_nil
- expect(json_response['size']).to be_present
- expect(json_response['total']).to be_present
- expect(json_response['html']).to eq(job.trace.html)
- end
- end
-
- context 'when job has a trace' do
- let(:job) { create(:ci_build, :trace_live, pipeline: pipeline) }
-
- it 'returns a trace' do
- expect(response).to have_gitlab_http_status(:ok)
- expect(json_response['id']).to eq job.id
- expect(json_response['status']).to eq job.status
- expect(json_response['html']).to eq('<span>BUILD TRACE</span>')
- end
- end
-
- context 'when job has no traces' do
- let(:job) { create(:ci_build, pipeline: pipeline) }
-
- it 'returns no traces' do
- expect(response).to have_gitlab_http_status(:ok)
- expect(json_response['id']).to eq job.id
- expect(json_response['status']).to eq job.status
- expect(json_response['html']).to be_nil
- end
- end
-
- context 'when job has a trace with ANSI sequence and Unicode' do
- let(:job) { create(:ci_build, :unicode_trace_live, pipeline: pipeline) }
-
- it 'returns a trace with Unicode' do
- expect(response).to have_gitlab_http_status(:ok)
- expect(json_response['id']).to eq job.id
- expect(json_response['status']).to eq job.status
- expect(json_response['html']).to include("ヾ(´༎ຶД༎ຶ`)ノ")
- end
- end
-
- context 'when trace artifact is in ObjectStorage' do
- let(:url) { 'http://object-storage/trace' }
- let(:file_path) { expand_fixture_path('trace/sample_trace') }
- let!(:job) { create(:ci_build, :success, :trace_artifact, pipeline: pipeline) }
-
- before do
- allow_any_instance_of(JobArtifactUploader).to receive(:file_storage?) { false }
- allow_any_instance_of(JobArtifactUploader).to receive(:url) { url }
- allow_any_instance_of(JobArtifactUploader).to receive(:size) { File.size(file_path) }
- end
-
- context 'when there are no network issues' do
- before do
- stub_remote_url_206(url, file_path)
-
- get_trace
- end
-
- it 'returns a trace' do
- expect(response).to have_gitlab_http_status(:ok)
- expect(json_response['id']).to eq job.id
- expect(json_response['status']).to eq job.status
- expect(json_response['html']).to eq(job.trace.html)
- end
- end
-
- context 'when there is a network issue' do
- before do
- stub_remote_url_500(url)
- end
-
- it 'returns a trace' do
- expect { get_trace }.to raise_error(Gitlab::HttpIO::FailedToGetChunkError)
- end
- end
- end
-
- def get_trace
- get :trace,
- params: {
- namespace_id: project.namespace,
- project_id: project,
- id: job.id
- },
- format: :json
- end
- end
-
describe 'GET status.json' do
let(:job) { create(:ci_build, pipeline: pipeline) }
let(:status) { job.detailed_status(double('user')) }
diff --git a/spec/controllers/projects/logs_controller_spec.rb b/spec/controllers/projects/logs_controller_spec.rb
index 1eb5a6fcc12..0f34e536064 100644
--- a/spec/controllers/projects/logs_controller_spec.rb
+++ b/spec/controllers/projects/logs_controller_spec.rb
@@ -104,6 +104,34 @@ RSpec.describe Projects::LogsController do
expect(response.headers['Poll-Interval']).to eq('3000')
end
+ context 'with gitlab managed apps logs' do
+ it 'uses cluster finder services to select cluster', :aggregate_failures do
+ cluster_list = [cluster]
+ service_params = { params: ActionController::Parameters.new(pod_name: pod_name).permit! }
+ request_params = {
+ namespace_id: project.namespace,
+ project_id: project,
+ cluster_id: cluster.id,
+ pod_name: pod_name,
+ format: :json
+ }
+
+ expect_next_instance_of(ClusterAncestorsFinder, project, user) do |finder|
+ expect(finder).to receive(:execute).and_return(cluster_list)
+ expect(cluster_list).to receive(:find).and_call_original
+ end
+
+ expect_next_instance_of(service, cluster, Gitlab::Kubernetes::Helm::NAMESPACE, service_params) do |instance|
+ expect(instance).to receive(:execute).and_return(service_result)
+ end
+
+ get endpoint, params: request_params
+
+ expect(response).to have_gitlab_http_status(:success)
+ expect(json_response).to eq(service_result_json)
+ end
+ end
+
context 'when service is processing' do
let(:service_result) { nil }
diff --git a/spec/controllers/projects/merge_requests/diffs_controller_spec.rb b/spec/controllers/projects/merge_requests/diffs_controller_spec.rb
index 02b4c2d1da9..217447c2ad6 100644
--- a/spec/controllers/projects/merge_requests/diffs_controller_spec.rb
+++ b/spec/controllers/projects/merge_requests/diffs_controller_spec.rb
@@ -91,6 +91,17 @@ RSpec.describe Projects::MergeRequests::DiffsController do
end
end
+ shared_examples "diff note on-demand position creation" do
+ it "updates diff discussion positions" do
+ service = double("service")
+
+ expect(Discussions::CaptureDiffNotePositionsService).to receive(:new).with(merge_request).and_return(service)
+ expect(service).to receive(:execute)
+
+ go
+ end
+ end
+
let(:project) { create(:project, :repository) }
let(:user) { create(:user) }
let(:merge_request) { create(:merge_request_with_diffs, target_project: project, source_project: project) }
@@ -146,6 +157,7 @@ RSpec.describe Projects::MergeRequests::DiffsController do
it_behaves_like 'persisted preferred diff view cookie'
it_behaves_like 'cached diff collection'
+ it_behaves_like 'diff note on-demand position creation'
end
describe 'GET diffs_metadata' do
diff --git a/spec/controllers/projects/merge_requests/drafts_controller_spec.rb b/spec/controllers/projects/merge_requests/drafts_controller_spec.rb
index 7d74e872d29..af39d4dec72 100644
--- a/spec/controllers/projects/merge_requests/drafts_controller_spec.rb
+++ b/spec/controllers/projects/merge_requests/drafts_controller_spec.rb
@@ -331,8 +331,10 @@ RSpec.describe Projects::MergeRequests::DraftsController do
notes = merge_request.notes.reload
expect(notes.pluck(:note)).to include(*drafts.map(&:note))
- expect(note.discussion.notes.last.note).to eq(draft_reply.note)
- expect(diff_note.discussion.notes.last.note).to eq(diff_draft_reply.note)
+
+ # discussion is memoized and reload doesn't clear the memoization
+ expect(Note.find(note.id).discussion.notes.last.note).to eq(draft_reply.note)
+ expect(Note.find(diff_note.id).discussion.notes.last.note).to eq(diff_draft_reply.note)
end
it 'can publish just a single draft note' do
@@ -376,7 +378,8 @@ RSpec.describe Projects::MergeRequests::DraftsController do
post :publish, params: params
- discussion = note.discussion
+ # discussion is memoized and reload doesn't clear the memoization
+ discussion = Note.find(note.id).discussion
expect(discussion.notes.last.note).to eq(draft_reply.note)
expect(discussion.resolved?).to eq(false)
diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb
index 382593fd7cb..4327e0bbb7a 100644
--- a/spec/controllers/projects/merge_requests_controller_spec.rb
+++ b/spec/controllers/projects/merge_requests_controller_spec.rb
@@ -442,7 +442,7 @@ RSpec.describe Projects::MergeRequestsController do
merge_request.update(squash: false)
merge_with_sha(squash: '1')
- expect(merge_request.reload.squash).to be_truthy
+ expect(merge_request.reload.squash_on_merge?).to be_truthy
end
end
@@ -451,7 +451,7 @@ RSpec.describe Projects::MergeRequestsController do
merge_request.update(squash: true)
merge_with_sha(squash: '0')
- expect(merge_request.reload.squash).to be_falsey
+ expect(merge_request.reload.squash_on_merge?).to be_falsey
end
end
diff --git a/spec/controllers/projects/notes_controller_spec.rb b/spec/controllers/projects/notes_controller_spec.rb
index b3a83723189..9728fad417e 100644
--- a/spec/controllers/projects/notes_controller_spec.rb
+++ b/spec/controllers/projects/notes_controller_spec.rb
@@ -38,9 +38,9 @@ RSpec.describe Projects::NotesController do
end
it 'passes last_fetched_at from headers to NotesFinder and MergeIntoNotesService' do
- last_fetched_at = 3.hours.ago.to_i
+ last_fetched_at = Time.zone.at(3.hours.ago.to_i) # remove nanoseconds
- request.headers['X-Last-Fetched-At'] = last_fetched_at
+ request.headers['X-Last-Fetched-At'] = microseconds(last_fetched_at)
expect(NotesFinder).to receive(:new)
.with(anything, hash_including(last_fetched_at: last_fetched_at))
@@ -84,6 +84,81 @@ RSpec.describe Projects::NotesController do
end
end
+ context 'for multiple pages of notes', :aggregate_failures do
+ # 3 pages worth: 1 normal page, 1 oversized due to clashing updated_at,
+ # and a final, short page
+ let!(:page_1) { create_list(:note, 2, noteable: issue, project: project, updated_at: 3.days.ago) }
+ let!(:page_2) { create_list(:note, 3, noteable: issue, project: project, updated_at: 2.days.ago) }
+ let!(:page_3) { create_list(:note, 2, noteable: issue, project: project, updated_at: 1.day.ago) }
+
+ # Include a resource event in the middle page as well
+ let!(:resource_event) { create(:resource_state_event, issue: issue, user: user, created_at: 2.days.ago) }
+
+ let(:page_1_boundary) { microseconds(page_1.last.updated_at + NotesFinder::FETCH_OVERLAP) }
+ let(:page_2_boundary) { microseconds(page_2.last.updated_at + NotesFinder::FETCH_OVERLAP) }
+
+ around do |example|
+ Timecop.freeze do
+ example.run
+ end
+ end
+
+ before do
+ stub_const('Gitlab::UpdatedNotesPaginator::LIMIT', 2)
+ end
+
+ context 'feature flag enabled' do
+ before do
+ stub_feature_flags(paginated_notes: true)
+ end
+
+ it 'returns the first page of notes' do
+ get :index, params: request_params
+
+ expect(json_response['notes'].count).to eq(page_1.count)
+ expect(json_response['more']).to be_truthy
+ expect(json_response['last_fetched_at']).to eq(page_1_boundary)
+ expect(response.headers['Poll-Interval'].to_i).to eq(1)
+ end
+
+ it 'returns the second page of notes' do
+ request.headers['X-Last-Fetched-At'] = page_1_boundary
+
+ get :index, params: request_params
+
+ expect(json_response['notes'].count).to eq(page_2.count + 1) # resource event
+ expect(json_response['more']).to be_truthy
+ expect(json_response['last_fetched_at']).to eq(page_2_boundary)
+ expect(response.headers['Poll-Interval'].to_i).to eq(1)
+ end
+
+ it 'returns the final page of notes' do
+ request.headers['X-Last-Fetched-At'] = page_2_boundary
+
+ get :index, params: request_params
+
+ expect(json_response['notes'].count).to eq(page_3.count)
+ expect(json_response['more']).to be_falsy
+ expect(json_response['last_fetched_at']).to eq(microseconds(Time.zone.now))
+ expect(response.headers['Poll-Interval'].to_i).to be > 1
+ end
+ end
+
+ context 'feature flag disabled' do
+ before do
+ stub_feature_flags(paginated_notes: false)
+ end
+
+ it 'returns all notes' do
+ get :index, params: request_params
+
+ expect(json_response['notes'].count).to eq((page_1 + page_2 + page_3).size + 1)
+ expect(json_response['more']).to be_falsy
+ expect(json_response['last_fetched_at']).to eq(microseconds(Time.zone.now))
+ end
+ end
+ end
+
context 'for a discussion note' do
let(:project) { create(:project, :repository) }
let!(:note) { create(:discussion_note_on_merge_request, project: project) }
@@ -870,4 +945,9 @@ RSpec.describe Projects::NotesController do
end
end
end
+
+ # Convert a time to an integer number of microseconds
+ def microseconds(time)
+ (time.to_i * 1_000_000) + time.usec
+ end
end
diff --git a/spec/controllers/projects/pipelines/stages_controller_spec.rb b/spec/controllers/projects/pipelines/stages_controller_spec.rb
new file mode 100644
index 00000000000..6e8c08d95a1
--- /dev/null
+++ b/spec/controllers/projects/pipelines/stages_controller_spec.rb
@@ -0,0 +1,72 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Projects::Pipelines::StagesController do
+ let(:user) { create(:user) }
+ let(:project) { create(:project, :repository) }
+
+ before do
+ sign_in(user)
+ end
+
+ describe 'POST #play_manual.json' do
+ let(:pipeline) { create(:ci_pipeline, project: project) }
+ let(:stage_name) { 'test' }
+
+ before do
+ create_manual_build(pipeline, 'test', 'rspec 1/2')
+ create_manual_build(pipeline, 'test', 'rspec 2/2')
+
+ pipeline.reload
+ end
+
+ context 'when user does not have access' do
+ it 'returns not authorized' do
+ play_manual_stage!
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+
+ context 'when user has access' do
+ before do
+ project.add_maintainer(user)
+ end
+
+ context 'when the stage does not exists' do
+ let(:stage_name) { 'deploy' }
+
+ it 'fails to play all manual' do
+ play_manual_stage!
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+
+ context 'when the stage exists' do
+ it 'starts all manual jobs' do
+ expect(pipeline.builds.manual.count).to eq(2)
+
+ play_manual_stage!
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(pipeline.builds.manual.count).to eq(0)
+ end
+ end
+ end
+
+ def play_manual_stage!
+ post :play_manual, params: {
+ namespace_id: project.namespace,
+ project_id: project,
+ pipeline_id: pipeline.id,
+ stage_name: stage_name
+ }, format: :json
+ end
+
+ def create_manual_build(pipeline, stage, name)
+ create(:ci_build, :manual, pipeline: pipeline, stage: stage, name: name)
+ end
+ end
+end
diff --git a/spec/controllers/projects/pipelines/tests_controller_spec.rb b/spec/controllers/projects/pipelines/tests_controller_spec.rb
new file mode 100644
index 00000000000..e2abd1238c5
--- /dev/null
+++ b/spec/controllers/projects/pipelines/tests_controller_spec.rb
@@ -0,0 +1,112 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Projects::Pipelines::TestsController do
+ let(:user) { create(:user) }
+ let(:project) { create(:project, :public, :repository) }
+ let(:pipeline) { create(:ci_pipeline, project: project) }
+
+ before do
+ sign_in(user)
+ end
+
+ describe 'GET #summary.json' do
+ context 'when pipeline has build report results' do
+ let(:pipeline) { create(:ci_pipeline, :with_report_results, project: project) }
+
+ it 'renders test report summary data' do
+ get_tests_summary_json
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response['total_count']).to eq(2)
+ end
+ end
+
+ context 'when pipeline does not have build report results' do
+ it 'renders test report summary data' do
+ get_tests_summary_json
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response['total_count']).to eq(0)
+ end
+ end
+
+ context 'when feature is disabled' do
+ before do
+ stub_feature_flags(build_report_summary: false)
+ end
+
+ it 'returns 404' do
+ get_tests_summary_json
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ expect(response.body).to be_empty
+ end
+ end
+ end
+
+ describe 'GET #show.json' do
+ context 'when pipeline has build report results' do
+ let(:pipeline) { create(:ci_pipeline, :with_report_results, project: project) }
+ let(:suite_name) { 'test' }
+ let(:build_ids) { pipeline.latest_builds.pluck(:id) }
+
+ it 'renders test suite data' do
+ get_tests_show_json(build_ids)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response['name']).to eq('test')
+ end
+ end
+
+ context 'when pipeline does not have build report results' do
+ let(:pipeline) { create(:ci_empty_pipeline) }
+ let(:suite_name) { 'test' }
+
+ it 'renders 404' do
+ get_tests_show_json([])
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ expect(response.body).to be_empty
+ end
+ end
+
+ context 'when feature is disabled' do
+ let(:suite_name) { 'test' }
+
+ before do
+ stub_feature_flags(build_report_summary: false)
+ end
+
+ it 'returns 404' do
+ get_tests_show_json([])
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ expect(response.body).to be_empty
+ end
+ end
+ end
+
+ def get_tests_summary_json
+ get :summary,
+ params: {
+ namespace_id: project.namespace,
+ project_id: project,
+ pipeline_id: pipeline.id
+ },
+ format: :json
+ end
+
+ def get_tests_show_json(build_ids)
+ get :show,
+ params: {
+ namespace_id: project.namespace,
+ project_id: project,
+ pipeline_id: pipeline.id,
+ suite_name: suite_name,
+ build_ids: build_ids
+ },
+ format: :json
+ end
+end
diff --git a/spec/controllers/projects/pipelines_controller_spec.rb b/spec/controllers/projects/pipelines_controller_spec.rb
index ca09d2b1428..872f0e97b09 100644
--- a/spec/controllers/projects/pipelines_controller_spec.rb
+++ b/spec/controllers/projects/pipelines_controller_spec.rb
@@ -37,16 +37,13 @@ RSpec.describe Projects::PipelinesController do
expect(json_response).to include('pipelines')
expect(json_response['pipelines'].count).to eq 6
expect(json_response['count']['all']).to eq '6'
- expect(json_response['count']['running']).to eq '2'
- expect(json_response['count']['pending']).to eq '1'
- expect(json_response['count']['finished']).to eq '3'
json_response.dig('pipelines', 0, 'details', 'stages').tap do |stages|
expect(stages.count).to eq 3
end
end
- it 'does not execute N+1 queries' do
+ it 'executes N+1 queries' do
get_pipelines_index_json
control_count = ActiveRecord::QueryRecorder.new do
@@ -56,10 +53,31 @@ RSpec.describe Projects::PipelinesController do
create_all_pipeline_types
# There appears to be one extra query for Pipelines#has_warnings? for some reason
- expect { get_pipelines_index_json }.not_to exceed_query_limit(control_count + 1)
+ expect { get_pipelines_index_json }.not_to exceed_query_limit(control_count + 7)
expect(response).to have_gitlab_http_status(:ok)
expect(json_response['pipelines'].count).to eq 12
end
+
+ context 'with build_report_summary turned off' do
+ before do
+ stub_feature_flags(build_report_summary: false)
+ end
+
+ it 'does not execute N+1 queries' do
+ get_pipelines_index_json
+
+ control_count = ActiveRecord::QueryRecorder.new do
+ get_pipelines_index_json
+ end.count
+
+ create_all_pipeline_types
+
+ # There appears to be one extra query for Pipelines#has_warnings? for some reason
+ expect { get_pipelines_index_json }.not_to exceed_query_limit(control_count + 1)
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response['pipelines'].count).to eq 12
+ end
+ end
end
it 'does not include coverage data for the pipelines' do
@@ -77,9 +95,9 @@ RSpec.describe Projects::PipelinesController do
expect(::Gitlab::GitalyClient).to receive(:allow_ref_name_caching).and_call_original
- # ListCommitsByOid, RepositoryExists, HasLocalBranches
+ # ListCommitsByOid, RepositoryExists, HasLocalBranches, ListCommitsByRefNames
expect { get_pipelines_index_json }
- .to change { Gitlab::GitalyClient.get_request_count }.by(3)
+ .to change { Gitlab::GitalyClient.get_request_count }.by(4)
end
end
@@ -101,23 +119,27 @@ RSpec.describe Projects::PipelinesController do
end
end
- context 'filter by scope' do
- it 'returns matched pipelines' do
- get_pipelines_index_json(scope: 'running')
+ context 'when user tries to access legacy scope via URL' do
+ it 'redirects to all pipelines with that status instead' do
+ get_pipelines_index_html(scope: 'running')
- check_pipeline_response(returned: 2, all: 6, running: 2, pending: 1, finished: 3)
+ expect(response).to redirect_to(project_pipelines_path(project, status: 'running', format: :html))
end
+ end
+ context 'filter by scope' do
context 'scope is branches or tags' do
before do
create(:ci_pipeline, :failed, project: project, ref: 'v1.0.0', tag: true)
+ create(:ci_pipeline, :failed, project: project, ref: 'master', tag: false)
+ create(:ci_pipeline, :failed, project: project, ref: 'feature', tag: false)
end
context 'when scope is branches' do
it 'returns matched pipelines' do
get_pipelines_index_json(scope: 'branches')
- check_pipeline_response(returned: 1, all: 7, running: 2, pending: 1, finished: 4)
+ check_pipeline_response(returned: 2, all: 9)
end
end
@@ -125,7 +147,7 @@ RSpec.describe Projects::PipelinesController do
it 'returns matched pipelines' do
get_pipelines_index_json(scope: 'tags')
- check_pipeline_response(returned: 1, all: 7, running: 2, pending: 1, finished: 4)
+ check_pipeline_response(returned: 1, all: 9)
end
end
end
@@ -138,7 +160,7 @@ RSpec.describe Projects::PipelinesController do
it 'returns matched pipelines' do
get_pipelines_index_json(username: user.username)
- check_pipeline_response(returned: 1, all: 1, running: 1, pending: 0, finished: 0)
+ check_pipeline_response(returned: 1, all: 1)
end
end
@@ -146,7 +168,7 @@ RSpec.describe Projects::PipelinesController do
it 'returns empty' do
get_pipelines_index_json(username: 'invalid-username')
- check_pipeline_response(returned: 0, all: 0, running: 0, pending: 0, finished: 0)
+ check_pipeline_response(returned: 0, all: 0)
end
end
end
@@ -158,7 +180,7 @@ RSpec.describe Projects::PipelinesController do
it 'returns matched pipelines' do
get_pipelines_index_json(ref: 'branch-1')
- check_pipeline_response(returned: 1, all: 1, running: 1, pending: 0, finished: 0)
+ check_pipeline_response(returned: 1, all: 1)
end
end
@@ -166,7 +188,7 @@ RSpec.describe Projects::PipelinesController do
it 'returns empty list' do
get_pipelines_index_json(ref: 'invalid-ref')
- check_pipeline_response(returned: 0, all: 0, running: 0, pending: 0, finished: 0)
+ check_pipeline_response(returned: 0, all: 0)
end
end
end
@@ -176,15 +198,7 @@ RSpec.describe Projects::PipelinesController do
it 'returns matched pipelines' do
get_pipelines_index_json(status: 'success')
- check_pipeline_response(returned: 1, all: 1, running: 0, pending: 0, finished: 1)
- end
-
- context 'when filter by unrelated scope' do
- it 'returns empty list' do
- get_pipelines_index_json(status: 'success', scope: 'running')
-
- check_pipeline_response(returned: 0, all: 1, running: 0, pending: 0, finished: 1)
- end
+ check_pipeline_response(returned: 1, all: 1)
end
end
@@ -192,7 +206,7 @@ RSpec.describe Projects::PipelinesController do
it 'returns empty list' do
get_pipelines_index_json(status: 'manual')
- check_pipeline_response(returned: 0, all: 0, running: 0, pending: 0, finished: 0)
+ check_pipeline_response(returned: 0, all: 0)
end
end
@@ -200,11 +214,19 @@ RSpec.describe Projects::PipelinesController do
it 'returns all list' do
get_pipelines_index_json(status: 'invalid-status')
- check_pipeline_response(returned: 6, all: 6, running: 2, pending: 1, finished: 3)
+ check_pipeline_response(returned: 6, all: 6)
end
end
end
+ def get_pipelines_index_html(params = {})
+ get :index, params: {
+ namespace_id: project.namespace,
+ project_id: project
+ }.merge(params),
+ format: :html
+ end
+
def get_pipelines_index_json(params = {})
get :index, params: {
namespace_id: project.namespace,
@@ -234,7 +256,8 @@ RSpec.describe Projects::PipelinesController do
user = create(:user)
pipeline = create(:ci_empty_pipeline, status: status,
project: project,
- sha: sha,
+ sha: sha.id,
+ ref: sha.id.first(8),
user: user,
merge_request: merge_request)
@@ -260,15 +283,12 @@ RSpec.describe Projects::PipelinesController do
)
end
- def check_pipeline_response(returned:, all:, running:, pending:, finished:)
+ def check_pipeline_response(returned:, all:)
aggregate_failures do
expect(response).to match_response_schema('pipeline')
expect(json_response['pipelines'].count).to eq returned
expect(json_response['count']['all'].to_i).to eq all
- expect(json_response['count']['running'].to_i).to eq running
- expect(json_response['count']['pending'].to_i).to eq pending
- expect(json_response['count']['finished'].to_i).to eq finished
end
end
end
@@ -689,6 +709,15 @@ RSpec.describe Projects::PipelinesController do
end
end
+ describe 'GET #charts' do
+ let(:pipeline) { create(:ci_pipeline, project: project) }
+
+ it_behaves_like 'tracking unique visits', :charts do
+ let(:request_params) { { namespace_id: project.namespace, project_id: project, id: pipeline.id } }
+ let(:target_id) { 'p_analytics_pipelines' }
+ end
+ end
+
describe 'POST create' do
let(:project) { create(:project, :public, :repository) }
diff --git a/spec/controllers/projects/project_members_controller_spec.rb b/spec/controllers/projects/project_members_controller_spec.rb
index 7457e4c5023..40a220d57a7 100644
--- a/spec/controllers/projects/project_members_controller_spec.rb
+++ b/spec/controllers/projects/project_members_controller_spec.rb
@@ -106,6 +106,29 @@ RSpec.describe Projects::ProjectMembersController do
expect(response).to redirect_to(project_project_members_path(project))
end
end
+
+ context 'adding project bot' do
+ let_it_be(:project_bot) { create(:user, :project_bot) }
+
+ before do
+ project.add_maintainer(user)
+
+ unrelated_project = create(:project)
+ unrelated_project.add_maintainer(project_bot)
+ end
+
+ it 'returns error' do
+ post :create, params: {
+ namespace_id: project.namespace,
+ project_id: project,
+ user_ids: project_bot.id,
+ access_level: Gitlab::Access::GUEST
+ }
+
+ expect(flash[:alert]).to include('project bots cannot be added to other groups / projects')
+ expect(response).to redirect_to(project_project_members_path(project))
+ end
+ end
end
describe 'PUT update' do
diff --git a/spec/controllers/projects/refs_controller_spec.rb b/spec/controllers/projects/refs_controller_spec.rb
index a6a4aff7ce9..d10351feb9e 100644
--- a/spec/controllers/projects/refs_controller_spec.rb
+++ b/spec/controllers/projects/refs_controller_spec.rb
@@ -41,19 +41,12 @@ RSpec.describe Projects::RefsController do
expect { xhr_get }.not_to raise_error
end
- it 'renders 404 for non-JS requests' do
+ it 'renders 404 for HTML requests' do
xhr_get
expect(response).to be_not_found
end
- it 'renders JS' do
- expect(::Gitlab::GitalyClient).to receive(:allow_ref_name_caching).and_call_original
-
- xhr_get(:js)
- expect(response).to be_successful
- end
-
context 'when json is requested' do
it 'renders JSON' do
expect(::Gitlab::GitalyClient).to receive(:allow_ref_name_caching).and_call_original
diff --git a/spec/controllers/projects/releases_controller_spec.rb b/spec/controllers/projects/releases_controller_spec.rb
index 96c38c1b726..45beccfeef5 100644
--- a/spec/controllers/projects/releases_controller_spec.rb
+++ b/spec/controllers/projects/releases_controller_spec.rb
@@ -3,6 +3,8 @@
require 'spec_helper'
RSpec.describe Projects::ReleasesController do
+ include AccessMatchersForController
+
let!(:project) { create(:project, :repository, :public) }
let_it_be(:private_project) { create(:project, :repository, :private) }
let_it_be(:developer) { create(:user) }
@@ -118,6 +120,15 @@ RSpec.describe Projects::ReleasesController do
end
end
+ describe 'GET #new' do
+ let(:request) do
+ get :new, params: { namespace_id: project.namespace, project_id: project }
+ end
+
+ it { expect { request }.to be_denied_for(:reporter).of(project) }
+ it { expect { request }.to be_allowed_for(:developer).of(project) }
+ end
+
describe 'GET #edit' do
subject do
get :edit, params: { namespace_id: project.namespace, project_id: project, tag: tag }
diff --git a/spec/controllers/projects/service_desk_controller_spec.rb b/spec/controllers/projects/service_desk_controller_spec.rb
new file mode 100644
index 00000000000..1c4d6665414
--- /dev/null
+++ b/spec/controllers/projects/service_desk_controller_spec.rb
@@ -0,0 +1,111 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Projects::ServiceDeskController do
+ let_it_be(:project) do
+ create(:project, :private, :custom_repo, service_desk_enabled: true,
+ files: { '.gitlab/issue_templates/service_desk.md' => 'template' })
+ end
+
+ let_it_be(:user) { create(:user) }
+
+ before do
+ allow(Gitlab::IncomingEmail).to receive(:enabled?) { true }
+ allow(Gitlab::IncomingEmail).to receive(:supports_wildcard?) { true }
+
+ project.add_maintainer(user)
+ sign_in(user)
+ end
+
+ describe 'GET service desk properties' do
+ it 'returns service_desk JSON data' do
+ get :show, params: { namespace_id: project.namespace.to_param, project_id: project }, format: :json
+
+ expect(json_response["service_desk_address"]).to match(/\A[^@]+@[^@]+\z/)
+ expect(json_response["service_desk_enabled"]).to be_truthy
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+
+ context 'when user is not project maintainer' do
+ let(:guest) { create(:user) }
+
+ it 'renders 404' do
+ project.add_guest(guest)
+ sign_in(guest)
+
+ get :show, params: { namespace_id: project.namespace.to_param, project_id: project }, format: :json
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+
+ context 'when issue template is present' do
+ it 'returns template_file_missing as false' do
+ create(:service_desk_setting, project: project, issue_template_key: 'service_desk')
+
+ get :show, params: { namespace_id: project.namespace.to_param, project_id: project }, format: :json
+
+ response_hash = Gitlab::Json.parse(response.body)
+ expect(response_hash['template_file_missing']).to eq(false)
+ end
+ end
+
+ context 'when issue template file becomes outdated' do
+ it 'returns template_file_missing as true' do
+ service = ServiceDeskSetting.new(project_id: project.id, issue_template_key: 'deleted')
+ service.save!(validate: false)
+
+ get :show, params: { namespace_id: project.namespace.to_param, project_id: project }, format: :json
+
+ expect(json_response['template_file_missing']).to eq(true)
+ end
+ end
+ end
+
+ describe 'PUT service desk properties' do
+ it 'toggles services desk incoming email' do
+ project.update!(service_desk_enabled: false)
+
+ put :update, params: { namespace_id: project.namespace.to_param,
+ project_id: project,
+ service_desk_enabled: true }, format: :json
+
+ expect(json_response["service_desk_address"]).to be_present
+ expect(json_response["service_desk_enabled"]).to be_truthy
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+
+ it 'sets issue_template_key' do
+ put :update, params: { namespace_id: project.namespace.to_param,
+ project_id: project,
+ issue_template_key: 'service_desk' }, format: :json
+
+ settings = project.service_desk_setting
+ expect(settings).to be_present
+ expect(settings.issue_template_key).to eq('service_desk')
+ expect(json_response['template_file_missing']).to eq(false)
+ expect(json_response['issue_template_key']).to eq('service_desk')
+ end
+
+ it 'returns an error when update of service desk settings fails' do
+ put :update, params: { namespace_id: project.namespace.to_param,
+ project_id: project,
+ issue_template_key: 'invalid key' }, format: :json
+
+ expect(response).to have_gitlab_http_status(:unprocessable_entity)
+ expect(json_response['message']).to eq('Issue template key is empty or does not exist')
+ end
+
+ context 'when user cannot admin the project' do
+ let(:other_user) { create(:user) }
+
+ it 'renders 404' do
+ sign_in(other_user)
+ put :update, params: { namespace_id: project.namespace.to_param, project_id: project, service_desk_enabled: true }, format: :json
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+ end
+end
diff --git a/spec/controllers/projects/services_controller_spec.rb b/spec/controllers/projects/services_controller_spec.rb
index 04c74dfdefe..e8a23dcfafb 100644
--- a/spec/controllers/projects/services_controller_spec.rb
+++ b/spec/controllers/projects/services_controller_spec.rb
@@ -137,7 +137,7 @@ RSpec.describe Projects::ServicesController do
let(:params) { project_params(service: service_params) }
let(:message) { 'Jira activated.' }
- let(:redirect_url) { project_settings_integrations_path(project) }
+ let(:redirect_url) { edit_project_service_path(project, service) }
before do
put :update, params: params
@@ -179,6 +179,23 @@ RSpec.describe Projects::ServicesController do
it_behaves_like 'service update'
end
+
+ context 'wehn param `inherit_from_id` is set to empty string' do
+ let(:service_params) { { inherit_from_id: '' } }
+
+ it 'sets inherit_from_id to nil' do
+ expect(service.reload.inherit_from_id).to eq(nil)
+ end
+ end
+
+ context 'wehn param `inherit_from_id` is set to some value' do
+ let(:instance_service) { create(:jira_service, :instance) }
+ let(:service_params) { { inherit_from_id: instance_service.id } }
+
+ it 'sets inherit_from_id to value' do
+ expect(service.reload.inherit_from_id).to eq(instance_service.id)
+ end
+ end
end
describe 'as JSON' do
diff --git a/spec/controllers/projects/settings/operations_controller_spec.rb b/spec/controllers/projects/settings/operations_controller_spec.rb
index 6b440e910ad..d4f3c5d0c9b 100644
--- a/spec/controllers/projects/settings/operations_controller_spec.rb
+++ b/spec/controllers/projects/settings/operations_controller_spec.rb
@@ -151,7 +151,8 @@ RSpec.describe Projects::Settings::OperationsController do
incident_management_setting_attributes: {
create_issue: 'false',
send_email: 'false',
- issue_template_key: 'some-other-template'
+ issue_template_key: 'some-other-template',
+ pagerduty_active: 'true'
}
}
end
@@ -159,7 +160,6 @@ RSpec.describe Projects::Settings::OperationsController do
it_behaves_like 'PATCHable'
context 'updating each incident management setting' do
- let(:project) { create(:project) }
let(:new_incident_management_settings) { {} }
before do
@@ -185,6 +185,98 @@ RSpec.describe Projects::Settings::OperationsController do
it_behaves_like 'a gitlab tracking event', { issue_template_key: nil }, 'disabled_issue_template_on_alerts'
it_behaves_like 'a gitlab tracking event', { send_email: '1' }, 'enabled_sending_emails'
it_behaves_like 'a gitlab tracking event', { send_email: '0' }, 'disabled_sending_emails'
+ it_behaves_like 'a gitlab tracking event', { pagerduty_active: '1' }, 'enabled_pagerduty_webhook'
+ it_behaves_like 'a gitlab tracking event', { pagerduty_active: '0' }, 'disabled_pagerduty_webhook'
+ end
+ end
+
+ describe 'POST #reset_pagerduty_token' do
+ before do
+ project.add_maintainer(user)
+ end
+
+ context 'with existing incident management setting has active PagerDuty webhook' do
+ let!(:incident_management_setting) do
+ create(:project_incident_management_setting, project: project, pagerduty_active: true)
+ end
+
+ let!(:old_token) { incident_management_setting.pagerduty_token }
+
+ it 'returns newly reset token' do
+ reset_pagerduty_token
+
+ new_token = incident_management_setting.reload.pagerduty_token
+ new_webhook_url = project_incidents_pagerduty_url(project, token: new_token)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response['pagerduty_webhook_url']).to eq(new_webhook_url)
+ expect(json_response['pagerduty_token']).to eq(new_token)
+ expect(old_token).not_to eq(new_token)
+ end
+ end
+
+ context 'without existing incident management setting' do
+ it 'does not reset a token' do
+ reset_pagerduty_token
+
+ new_webhook_url = project_incidents_pagerduty_url(project, token: nil)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response['pagerduty_webhook_url']).to eq(new_webhook_url)
+ expect(project.incident_management_setting.pagerduty_token).to be_nil
+ end
+ end
+
+ context 'when update fails' do
+ let(:operations_update_service) { spy(:operations_update_service) }
+ let(:pagerduty_token_params) do
+ { incident_management_setting_attributes: { regenerate_token: true } }
+ end
+
+ before do
+ expect(::Projects::Operations::UpdateService)
+ .to receive(:new).with(project, user, pagerduty_token_params)
+ .and_return(operations_update_service)
+ expect(operations_update_service).to receive(:execute)
+ .and_return(status: :error)
+ end
+
+ it 'returns unprocessable_entity' do
+ reset_pagerduty_token
+
+ expect(response).to have_gitlab_http_status(:unprocessable_entity)
+ expect(json_response).to be_empty
+ end
+ end
+
+ context 'with insufficient permissions' do
+ before do
+ project.add_reporter(user)
+ end
+
+ it 'returns 404' do
+ reset_pagerduty_token
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+
+ context 'as an anonymous user' do
+ before do
+ sign_out(user)
+ end
+
+ it 'returns a redirect' do
+ reset_pagerduty_token
+
+ expect(response).to have_gitlab_http_status(:redirect)
+ end
+ end
+
+ private
+
+ def reset_pagerduty_token
+ post :reset_pagerduty_token, params: project_params(project), format: :json
end
end
end
@@ -296,9 +388,7 @@ RSpec.describe Projects::Settings::OperationsController do
end
end
- describe 'POST reset_alerting_token' do
- let(:project) { create(:project) }
-
+ describe 'POST #reset_alerting_token' do
before do
project.add_maintainer(user)
end
diff --git a/spec/controllers/projects/snippets/blobs_controller_spec.rb b/spec/controllers/projects/snippets/blobs_controller_spec.rb
new file mode 100644
index 00000000000..ca656705e07
--- /dev/null
+++ b/spec/controllers/projects/snippets/blobs_controller_spec.rb
@@ -0,0 +1,85 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Projects::Snippets::BlobsController do
+ using RSpec::Parameterized::TableSyntax
+ include SnippetHelpers
+
+ let_it_be(:author) { create(:user) }
+ let_it_be(:developer) { create(:user) }
+ let_it_be(:other_user) { create(:user) }
+
+ let(:visibility) { :public }
+ let(:project_visibility) { :public }
+ let(:project) { create(:project, project_visibility) }
+ let(:snippet) { create(:project_snippet, visibility, :repository, project: project, author: author) }
+
+ before do
+ project.add_maintainer(author)
+ project.add_developer(developer)
+ end
+
+ describe 'GET #raw' do
+ let(:filepath) { 'file1' }
+ let(:ref) { TestEnv::BRANCH_SHA['snippet/single-file'] }
+ let(:inline) { nil }
+
+ subject do
+ get(:raw,
+ params: {
+ namespace_id: project.namespace,
+ project_id: project,
+ snippet_id: snippet,
+ path: filepath,
+ ref: ref,
+ inline: inline
+ })
+ end
+
+ context 'with a snippet without a repository' do
+ let(:snippet) { create(:project_snippet, visibility, project: project, author: author) }
+
+ it_behaves_like 'raw snippet without repository', :not_found
+ end
+
+ where(:project_visibility_level, :snippet_visibility_level, :user, :status) do
+ :public | :public | :author | :ok
+ :public | :public | :developer | :ok
+ :public | :public | :other_user | :ok
+ :public | :public | nil | :ok
+
+ :public | :private | :author | :ok
+ :public | :private | :developer | :ok
+ :public | :private | :other_user | :not_found
+ :public | :private | nil | :not_found
+
+ :private | :public | :author | :ok
+ :private | :public | :developer | :ok
+ :private | :public | :other_user | :not_found
+ :private | :public | nil | :redirect
+
+ :private | :private | :author | :ok
+ :private | :private | :developer | :ok
+ :private | :private | :other_user | :not_found
+ :private | :private | nil | :redirect
+ end
+
+ with_them do
+ let(:visibility) { snippet_visibility_level }
+ let(:project_visibility) { project_visibility_level }
+
+ before do
+ sign_in_as(user)
+
+ subject
+ end
+
+ it 'responds with correct status' do
+ expect(response).to have_gitlab_http_status(status)
+ end
+ end
+
+ it_behaves_like 'raw snippet blob'
+ end
+end
diff --git a/spec/controllers/projects/snippets_controller_spec.rb b/spec/controllers/projects/snippets_controller_spec.rb
index 8bbfaa8d327..6fcb24da3cd 100644
--- a/spec/controllers/projects/snippets_controller_spec.rb
+++ b/spec/controllers/projects/snippets_controller_spec.rb
@@ -15,14 +15,18 @@ RSpec.describe Projects::SnippetsController do
end
describe 'GET #index' do
+ let(:base_params) do
+ {
+ namespace_id: project.namespace,
+ project_id: project
+ }
+ end
+
+ subject { get :index, params: base_params }
+
it_behaves_like 'paginated collection' do
let(:collection) { project.snippets }
- let(:params) do
- {
- namespace_id: project.namespace,
- project_id: project
- }
- end
+ let(:params) { base_params }
before do
create(:project_snippet, :public, project: project, author: user)
@@ -35,7 +39,11 @@ RSpec.describe Projects::SnippetsController do
.to receive(:new).with(nil, project: project)
.and_return(service)
- get :index, params: { namespace_id: project.namespace, project_id: project }
+ subject
+ end
+
+ it_behaves_like 'snippets sort order' do
+ let(:params) { base_params }
end
context 'when the project snippet is private' do
@@ -43,7 +51,7 @@ RSpec.describe Projects::SnippetsController do
context 'when anonymous' do
it 'does not include the private snippet' do
- get :index, params: { namespace_id: project.namespace, project_id: project }
+ subject
expect(assigns(:snippets)).not_to include(project_snippet)
expect(response).to have_gitlab_http_status(:ok)
@@ -56,7 +64,7 @@ RSpec.describe Projects::SnippetsController do
end
it 'renders the snippet' do
- get :index, params: { namespace_id: project.namespace, project_id: project }
+ subject
expect(assigns(:snippets)).to include(project_snippet)
expect(response).to have_gitlab_http_status(:ok)
@@ -69,7 +77,7 @@ RSpec.describe Projects::SnippetsController do
end
it 'renders the snippet' do
- get :index, params: { namespace_id: project.namespace, project_id: project }
+ subject
expect(assigns(:snippets)).to include(project_snippet)
expect(response).to have_gitlab_http_status(:ok)
diff --git a/spec/controllers/projects/stages_controller_spec.rb b/spec/controllers/projects/stages_controller_spec.rb
deleted file mode 100644
index dcf8607ae18..00000000000
--- a/spec/controllers/projects/stages_controller_spec.rb
+++ /dev/null
@@ -1,72 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe Projects::StagesController do
- let(:user) { create(:user) }
- let(:project) { create(:project, :repository) }
-
- before do
- sign_in(user)
- end
-
- describe 'POST #play_manual.json' do
- let(:pipeline) { create(:ci_pipeline, project: project) }
- let(:stage_name) { 'test' }
-
- before do
- create_manual_build(pipeline, 'test', 'rspec 1/2')
- create_manual_build(pipeline, 'test', 'rspec 2/2')
-
- pipeline.reload
- end
-
- context 'when user does not have access' do
- it 'returns not authorized' do
- play_manual_stage!
-
- expect(response).to have_gitlab_http_status(:not_found)
- end
- end
-
- context 'when user has access' do
- before do
- project.add_maintainer(user)
- end
-
- context 'when the stage does not exists' do
- let(:stage_name) { 'deploy' }
-
- it 'fails to play all manual' do
- play_manual_stage!
-
- expect(response).to have_gitlab_http_status(:not_found)
- end
- end
-
- context 'when the stage exists' do
- it 'starts all manual jobs' do
- expect(pipeline.builds.manual.count).to eq(2)
-
- play_manual_stage!
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(pipeline.builds.manual.count).to eq(0)
- end
- end
- end
-
- def play_manual_stage!
- post :play_manual, params: {
- namespace_id: project.namespace,
- project_id: project,
- id: pipeline.id,
- stage_name: stage_name
- }, format: :json
- end
-
- def create_manual_build(pipeline, stage, name)
- create(:ci_build, :manual, pipeline: pipeline, stage: stage, name: name)
- end
- end
-end
diff --git a/spec/controllers/projects/tree_controller_spec.rb b/spec/controllers/projects/tree_controller_spec.rb
index f6ec04d4dd7..8e4e275bdbe 100644
--- a/spec/controllers/projects/tree_controller_spec.rb
+++ b/spec/controllers/projects/tree_controller_spec.rb
@@ -89,34 +89,6 @@ RSpec.describe Projects::TreeController do
end
end
- describe "GET show" do
- context 'lfs_blob_ids instance variable' do
- let(:id) { 'master' }
-
- context 'with vue tree view enabled' do
- before do
- get(:show, params: { namespace_id: project.namespace.to_param, project_id: project, id: id })
- end
-
- it 'is not set' do
- expect(assigns[:lfs_blob_ids]).to be_nil
- end
- end
-
- context 'with vue tree view disabled' do
- before do
- stub_feature_flags(vue_file_list: false)
-
- get(:show, params: { namespace_id: project.namespace.to_param, project_id: project, id: id })
- end
-
- it 'is set' do
- expect(assigns[:lfs_blob_ids]).not_to be_nil
- end
- end
- end
- end
-
describe 'GET show with whitespace in ref' do
render_views
diff --git a/spec/controllers/projects/wikis_controller_spec.rb b/spec/controllers/projects/wikis_controller_spec.rb
index 4e58822b613..7243588681d 100644
--- a/spec/controllers/projects/wikis_controller_spec.rb
+++ b/spec/controllers/projects/wikis_controller_spec.rb
@@ -4,7 +4,7 @@ require 'spec_helper'
RSpec.describe Projects::WikisController do
it_behaves_like 'wiki controller actions' do
- let(:container) { create(:project, :public, :repository, namespace: user.namespace) }
+ let(:container) { create(:project, :public, namespace: user.namespace) }
let(:routing_params) { { namespace_id: container.namespace, project_id: container } }
end
end
diff --git a/spec/controllers/projects_controller_spec.rb b/spec/controllers/projects_controller_spec.rb
index 8aae9ef85be..e59493827ba 100644
--- a/spec/controllers/projects_controller_spec.rb
+++ b/spec/controllers/projects_controller_spec.rb
@@ -6,7 +6,7 @@ RSpec.describe ProjectsController do
include ExternalAuthorizationServiceHelpers
include ProjectForksHelper
- let(:project) { create(:project) }
+ let(:project) { create(:project, service_desk_enabled: false) }
let(:public_project) { create(:project, :public) }
let(:user) { create(:user) }
let(:jpg) { fixture_file_upload('spec/fixtures/rails_sample.jpg', 'image/jpg') }
@@ -86,11 +86,13 @@ RSpec.describe ProjectsController do
end
describe "GET #activity as JSON" do
+ include DesignManagementTestHelpers
render_views
let(:project) { create(:project, :public, issues_access_level: ProjectFeature::PRIVATE) }
before do
+ enable_design_management
create(:event, :created, project: project, target: create(:issue))
sign_in(user)
@@ -103,11 +105,32 @@ RSpec.describe ProjectsController do
project.add_developer(user)
end
- it 'returns count' do
+ def get_activity(project)
get :activity, params: { namespace_id: project.namespace, id: project, format: :json }
+ end
+
+ it 'returns count' do
+ get_activity(project)
expect(json_response['count']).to eq(1)
end
+
+ context 'design events are visible' do
+ include DesignManagementTestHelpers
+ let(:other_project) { create(:project, namespace: user.namespace) }
+
+ before do
+ enable_design_management
+ create(:design_event, project: project)
+ request.cookies[:event_filter] = EventFilter::DESIGNS
+ end
+
+ it 'returns correct count' do
+ get_activity(project)
+
+ expect(json_response['count']).to eq(1)
+ end
+ end
end
context 'when user has no permission to see the event' do
@@ -350,45 +373,6 @@ RSpec.describe ProjectsController do
.not_to exceed_query_limit(2).for_query(expected_query)
end
end
-
- context 'lfs_blob_ids instance variable' do
- let(:project) { create(:project, :public, :repository) }
-
- before do
- sign_in(user)
- end
-
- context 'with vue tree view enabled' do
- before do
- get :show, params: { namespace_id: project.namespace, id: project }
- end
-
- it 'is not set' do
- expect(assigns[:lfs_blob_ids]).to be_nil
- end
- end
-
- context 'with vue tree view disabled' do
- before do
- stub_feature_flags(vue_file_list: false)
-
- get :show, params: { namespace_id: project.namespace, id: project }
- end
-
- it 'is set' do
- expect(assigns[:lfs_blob_ids]).not_to be_nil
- end
- end
- end
-
- context 'namespace storage limit' do
- let_it_be(:project) { create(:project, :public, :repository ) }
- let(:namespace) { project.namespace }
-
- subject { get :show, params: { namespace_id: namespace, id: project } }
-
- it_behaves_like 'namespace storage limit alert'
- end
end
describe 'GET edit' do
@@ -1192,7 +1176,7 @@ RSpec.describe ProjectsController do
before do
allow(Gitlab::ApplicationRateLimiter)
.to receive(:increment)
- .and_return(Gitlab::ApplicationRateLimiter.rate_limits["project_#{action}".to_sym][:threshold] + 1)
+ .and_return(Gitlab::ApplicationRateLimiter.rate_limits["project_#{action}".to_sym][:threshold].call + 1)
end
it 'prevents requesting project export' do
@@ -1259,7 +1243,7 @@ RSpec.describe ProjectsController do
before do
allow(Gitlab::ApplicationRateLimiter)
.to receive(:increment)
- .and_return(Gitlab::ApplicationRateLimiter.rate_limits[:project_download_export][:threshold] + 1)
+ .and_return(Gitlab::ApplicationRateLimiter.rate_limits[:project_download_export][:threshold].call + 1)
end
it 'prevents requesting project export' do
@@ -1400,6 +1384,27 @@ RSpec.describe ProjectsController do
end
end
+ it 'updates Service Desk attributes' do
+ project.add_maintainer(user)
+ sign_in(user)
+ allow(Gitlab::IncomingEmail).to receive(:enabled?) { true }
+ allow(Gitlab::IncomingEmail).to receive(:supports_wildcard?) { true }
+ params = {
+ service_desk_enabled: true
+ }
+
+ put :update,
+ params: {
+ namespace_id: project.namespace,
+ id: project,
+ project: params
+ }
+ project.reload
+
+ expect(response).to have_gitlab_http_status(:found)
+ expect(project.service_desk_enabled).to eq(true)
+ end
+
def project_moved_message(redirect_route, project)
"Project '#{redirect_route.path}' was moved to '#{project.full_path}'. Please update any links and bookmarks that may still have the old path."
end
diff --git a/spec/controllers/registrations/experience_levels_controller_spec.rb b/spec/controllers/registrations/experience_levels_controller_spec.rb
index 1fc728f5de8..5a217a3a684 100644
--- a/spec/controllers/registrations/experience_levels_controller_spec.rb
+++ b/spec/controllers/registrations/experience_levels_controller_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Registrations::ExperienceLevelsController do
+RSpec.describe Registrations::ExperienceLevelsController do
let_it_be(:namespace) { create(:group, path: 'group-path' ) }
let_it_be(:user) { create(:user) }
@@ -99,34 +99,51 @@ describe Registrations::ExperienceLevelsController do
end
describe 'applying the chosen level' do
- context "when an 'onboarding_issues_settings' cookie does not exist" do
- let(:params) { super().merge(experience_level: :novice) }
-
- it 'does not change the cookie' do
- expect { subject }.not_to change { cookies[:onboarding_issues_settings] }
- end
- end
-
- context "when an 'onboarding_issues_settings' cookie does exist" do
+ context 'when a "Learn GitLab" project is available' do
before do
- request.cookies[:onboarding_issues_settings] = '{}'
+ allow_next_instance_of(LearnGitlab) do |learn_gitlab|
+ allow(learn_gitlab).to receive(:available?).and_return(true)
+ allow(learn_gitlab).to receive(:label).and_return(double(id: 1))
+ end
end
context 'when novice' do
let(:params) { super().merge(experience_level: :novice) }
- it "adds a 'hideAdvanced' setting to the cookie" do
- expect { subject }.to change { Gitlab::Json.parse(cookies[:onboarding_issues_settings])['hideAdvanced'] }.from(nil).to(true)
+ it 'adds a BoardLabel' do
+ expect_next_instance_of(Boards::UpdateService) do |service|
+ expect(service).to receive(:execute)
+ end
+
+ subject
end
end
context 'when experienced' do
let(:params) { super().merge(experience_level: :experienced) }
- it 'does not change the cookie' do
- expect { subject }.not_to change { cookies[:onboarding_issues_settings] }
+ it 'does not add a BoardLabel' do
+ expect(Boards::UpdateService).not_to receive(:new)
+
+ subject
+ end
+ end
+ end
+
+ context 'when no "Learn GitLab" project exists' do
+ let(:params) { super().merge(experience_level: :novice) }
+
+ before do
+ allow_next_instance_of(LearnGitlab) do |learn_gitlab|
+ allow(learn_gitlab).to receive(:available?).and_return(false)
end
end
+
+ it 'does not add a BoardLabel' do
+ expect(Boards::UpdateService).not_to receive(:new)
+
+ subject
+ end
end
end
end
diff --git a/spec/controllers/search_controller_spec.rb b/spec/controllers/search_controller_spec.rb
index bae6bd07b67..0849fb00e73 100644
--- a/spec/controllers/search_controller_spec.rb
+++ b/spec/controllers/search_controller_spec.rb
@@ -211,4 +211,9 @@ RSpec.describe SearchController do
end.to raise_error(ActionController::ParameterMissing)
end
end
+
+ describe 'GET #autocomplete' do
+ it_behaves_like 'when the user cannot read cross project', :autocomplete, { term: 'hello' }
+ it_behaves_like 'with external authorization service enabled', :autocomplete, { term: 'hello' }
+ end
end
diff --git a/spec/controllers/snippets/blobs_controller_spec.rb b/spec/controllers/snippets/blobs_controller_spec.rb
new file mode 100644
index 00000000000..b9f58587a58
--- /dev/null
+++ b/spec/controllers/snippets/blobs_controller_spec.rb
@@ -0,0 +1,61 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Snippets::BlobsController do
+ using RSpec::Parameterized::TableSyntax
+ include SnippetHelpers
+
+ describe 'GET #raw' do
+ let_it_be(:author) { create(:user) }
+ let_it_be(:other_user) { create(:user) }
+
+ let(:visibility) { :public }
+ let(:snippet) { create(:personal_snippet, visibility, :repository, author: author) }
+ let(:filepath) { 'file1' }
+ let(:ref) { TestEnv::BRANCH_SHA['snippet/single-file'] }
+ let(:inline) { nil }
+
+ subject do
+ get(:raw,
+ params: {
+ snippet_id: snippet,
+ path: filepath,
+ ref: ref,
+ inline: inline
+ })
+ end
+
+ where(:snippet_visibility_level, :user, :status) do
+ :public | :author | :ok
+ :public | :other_user | :ok
+ :public | nil | :ok
+
+ :private | :author | :ok
+ :private | :other_user | :not_found
+ :private | nil | :redirect
+ end
+
+ with_them do
+ let(:visibility) { snippet_visibility_level }
+
+ before do
+ sign_in_as(user)
+
+ subject
+ end
+
+ it 'responds with correct status' do
+ expect(response).to have_gitlab_http_status(status)
+ end
+ end
+
+ it_behaves_like 'raw snippet blob'
+
+ context 'with a snippet without a repository' do
+ let(:snippet) { create(:personal_snippet, visibility, author: author) }
+
+ it_behaves_like 'raw snippet without repository', :redirect
+ end
+ end
+end
diff --git a/spec/controllers/snippets_controller_spec.rb b/spec/controllers/snippets_controller_spec.rb
index 70df1faf7dd..92370b3381a 100644
--- a/spec/controllers/snippets_controller_spec.rb
+++ b/spec/controllers/snippets_controller_spec.rb
@@ -6,6 +6,8 @@ RSpec.describe SnippetsController do
let_it_be(:user) { create(:user) }
describe 'GET #index' do
+ let(:base_params) { { username: user.username } }
+
context 'when username parameter is present' do
it_behaves_like 'paginated collection' do
let(:collection) { Snippet.all }
@@ -38,6 +40,10 @@ RSpec.describe SnippetsController do
expect(response).to redirect_to(dashboard_snippets_path)
end
end
+
+ it_behaves_like 'snippets sort order' do
+ let(:params) { base_params }
+ end
end
describe 'GET #new' do
diff --git a/spec/controllers/users_controller_spec.rb b/spec/controllers/users_controller_spec.rb
index bec4b24484a..99c3b82bd0d 100644
--- a/spec/controllers/users_controller_spec.rb
+++ b/spec/controllers/users_controller_spec.rb
@@ -114,6 +114,71 @@ RSpec.describe UsersController do
end
end
+ describe "#ssh_keys" do
+ describe "non existent user" do
+ it "does not generally work" do
+ get :ssh_keys, params: { username: 'not-existent' }
+
+ expect(response).not_to be_successful
+ end
+ end
+
+ describe "user with no keys" do
+ it "does generally work" do
+ get :ssh_keys, params: { username: user.username }
+
+ expect(response).to be_successful
+ end
+
+ it "renders all keys separated with a new line" do
+ get :ssh_keys, params: { username: user.username }
+
+ expect(response.body).to eq("")
+ end
+
+ it "responds with text/plain content type" do
+ get :ssh_keys, params: { username: user.username }
+ expect(response.content_type).to eq("text/plain")
+ end
+ end
+
+ describe "user with keys" do
+ let!(:key) { create(:key, user: user) }
+ let!(:another_key) { create(:another_key, user: user) }
+ let!(:deploy_key) { create(:deploy_key, user: user) }
+
+ it "does generally work" do
+ get :ssh_keys, params: { username: user.username }
+
+ expect(response).to be_successful
+ end
+
+ it "renders all non deploy keys separated with a new line" do
+ get :ssh_keys, params: { username: user.username }
+
+ expect(response.body).not_to eq('')
+ expect(response.body).to eq(user.all_ssh_keys.join("\n"))
+
+ expect(response.body).to include(key.key.sub(' dummy@gitlab.com', ''))
+ expect(response.body).to include(another_key.key.sub(' dummy@gitlab.com', ''))
+
+ expect(response.body).not_to include(deploy_key.key)
+ end
+
+ it "does not render the comment of the key" do
+ get :ssh_keys, params: { username: user.username }
+
+ expect(response.body).not_to match(/dummy@gitlab.com/)
+ end
+
+ it "responds with text/plain content type" do
+ get :ssh_keys, params: { username: user.username }
+
+ expect(response.content_type).to eq("text/plain")
+ end
+ end
+ end
+
describe 'GET #calendar' do
context 'for user' do
let(:project) { create(:project) }
diff --git a/spec/db/schema_spec.rb b/spec/db/schema_spec.rb
index 95d7981b85c..f31da943957 100644
--- a/spec/db/schema_spec.rb
+++ b/spec/db/schema_spec.rb
@@ -8,9 +8,10 @@ RSpec.describe 'Database schema' do
let(:connection) { ActiveRecord::Base.connection }
let(:tables) { connection.tables }
+ let(:columns_name_with_jsonb) { retrieve_columns_name_with_jsonb }
- # Use if you are certain that this column should not have a foreign key
- # EE: edit the ee/spec/db/schema_support.rb
+ # List of columns historically missing a FK, don't add more columns
+ # See: https://docs.gitlab.com/ce/development/foreign_keys.html#naming-foreign-keys
IGNORED_FK_COLUMNS = {
abuse_reports: %w[reporter_id user_id],
application_settings: %w[performance_bar_allowed_group_id slack_app_id snowplow_app_id eks_account_id eks_access_key_id],
@@ -62,6 +63,7 @@ RSpec.describe 'Database schema' do
oauth_access_tokens: %w[resource_owner_id application_id],
oauth_applications: %w[owner_id],
open_project_tracker_data: %w[closed_status_id],
+ product_analytics_events_experimental: %w[event_id txn_id user_id],
project_group_links: %w[group_id],
project_statistics: %w[namespace_id],
projects: %w[creator_id ci_id mirror_user_id],
@@ -118,7 +120,11 @@ RSpec.describe 'Database schema' do
let(:ignored_columns) { ignored_fk_columns(table) }
it 'do have the foreign keys' do
- expect(column_names_with_id - ignored_columns).to contain_exactly(*foreign_keys_columns)
+ expect(column_names_with_id - ignored_columns).to match_array(foreign_keys_columns)
+ end
+
+ it 'and having foreign key are not in the ignore list' do
+ expect(ignored_columns).to match_array(ignored_columns - foreign_keys)
end
end
end
@@ -169,8 +175,84 @@ RSpec.describe 'Database schema' do
end
end
+ # These pre-existing columns does not use a schema validation yet
+ IGNORED_JSONB_COLUMNS = {
+ "ApplicationSetting" => %w[repository_storages_weighted],
+ "AlertManagement::Alert" => %w[payload],
+ "Ci::BuildMetadata" => %w[config_options config_variables],
+ "Geo::Event" => %w[payload],
+ "GeoNodeStatus" => %w[status],
+ "Operations::FeatureFlagScope" => %w[strategies],
+ "Operations::FeatureFlags::Strategy" => %w[parameters],
+ "Packages::Composer::Metadatum" => %w[composer_json],
+ "Releases::Evidence" => %w[summary]
+ }.freeze
+
+ # We are skipping GEO models for now as it adds up complexity
+ describe 'for jsonb columns' do
+ it 'uses json schema validator' do
+ columns_name_with_jsonb.each do |hash|
+ next if models_by_table_name[hash["table_name"]].nil?
+
+ models_by_table_name[hash["table_name"]].each do |model|
+ jsonb_columns = [hash["column_name"]] - ignored_jsonb_columns(model.name)
+
+ expect(model).to validate_jsonb_schema(jsonb_columns)
+ end
+ end
+ end
+ end
+
+ context 'existence of Postgres schemas' do
+ def get_schemas
+ sql = <<~SQL
+ SELECT schema_name FROM
+ information_schema.schemata
+ WHERE
+ NOT schema_name ~* '^pg_' AND NOT schema_name = 'information_schema'
+ AND catalog_name = current_database()
+ SQL
+
+ ApplicationRecord.connection.select_all(sql).map do |row|
+ row['schema_name']
+ end
+ end
+
+ it 'we have a public schema' do
+ expect(get_schemas).to include('public')
+ end
+
+ Gitlab::Database::EXTRA_SCHEMAS.each do |schema|
+ it "we have a '#{schema}' schema'" do
+ expect(get_schemas).to include(schema.to_s)
+ end
+ end
+
+ it 'we do not have unexpected schemas' do
+ expect(get_schemas.size).to eq(Gitlab::Database::EXTRA_SCHEMAS.size + 1)
+ end
+ end
+
private
+ def retrieve_columns_name_with_jsonb
+ sql = <<~SQL
+ SELECT table_name, column_name, data_type
+ FROM information_schema.columns
+ WHERE table_catalog = '#{ApplicationRecord.connection_config[:database]}'
+ AND table_schema = 'public'
+ AND table_name NOT LIKE 'pg_%'
+ AND data_type = 'jsonb'
+ ORDER BY table_name, column_name, data_type
+ SQL
+
+ ApplicationRecord.connection.select_all(sql).to_a
+ end
+
+ def models_by_table_name
+ @models_by_table_name ||= ApplicationRecord.descendants.reject(&:abstract_class).group_by(&:table_name)
+ end
+
def ignored_fk_columns(column)
IGNORED_FK_COLUMNS.fetch(column, [])
end
@@ -178,4 +260,8 @@ RSpec.describe 'Database schema' do
def ignored_limit_enums(model)
IGNORED_LIMIT_ENUMS.fetch(model, [])
end
+
+ def ignored_jsonb_columns(model)
+ IGNORED_JSONB_COLUMNS.fetch(model, [])
+ end
end
diff --git a/spec/factories/alert_management/alerts.rb b/spec/factories/alert_management/alerts.rb
index 8724a626d77..ef511aa54b8 100644
--- a/spec/factories/alert_management/alerts.rb
+++ b/spec/factories/alert_management/alerts.rb
@@ -16,7 +16,9 @@ FactoryBot.define do
end
trait :with_issue do
- issue
+ after(:create) do |alert|
+ create(:issue, alert_management_alert: alert, project: alert.project)
+ end
end
trait :with_assignee do |alert|
@@ -73,10 +75,30 @@ FactoryBot.define do
without_ended_at
end
- trait :low_severity do
+ trait :critical do
+ severity { 'critical' }
+ end
+
+ trait :high do
+ severity { 'high' }
+ end
+
+ trait :medium do
+ severity { 'medium' }
+ end
+
+ trait :low do
severity { 'low' }
end
+ trait :info do
+ severity { 'info' }
+ end
+
+ trait :unknown do
+ severity { 'unknown' }
+ end
+
trait :prometheus do
monitoring_tool { Gitlab::AlertManagement::AlertParams::MONITORING_TOOLS[:prometheus] }
end
@@ -89,7 +111,7 @@ FactoryBot.define do
with_monitoring_tool
with_host
with_description
- low_severity
+ low
end
end
end
diff --git a/spec/factories/approvals.rb b/spec/factories/approvals.rb
new file mode 100644
index 00000000000..91e6fa54894
--- /dev/null
+++ b/spec/factories/approvals.rb
@@ -0,0 +1,10 @@
+# frozen_string_literal: true
+
+# Read about factories at https://github.com/thoughtbot/factory_bot
+
+FactoryBot.define do
+ factory :approval do
+ merge_request
+ user
+ end
+end
diff --git a/spec/factories/background_migration_jobs.rb b/spec/factories/background_migration_jobs.rb
new file mode 100644
index 00000000000..048e4f4fcf2
--- /dev/null
+++ b/spec/factories/background_migration_jobs.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+FactoryBot.define do
+ factory :background_migration_job, class: '::Gitlab::Database::BackgroundMigrationJob' do
+ class_name { 'TestJob' }
+ status { :pending }
+ arguments { [] }
+
+ trait :succeeded do
+ status { :succeeded }
+ end
+ end
+end
diff --git a/spec/factories/ci/builds.rb b/spec/factories/ci/builds.rb
index 9403967aa0a..b3815b53c2b 100644
--- a/spec/factories/ci/builds.rb
+++ b/spec/factories/ci/builds.rb
@@ -302,6 +302,18 @@ FactoryBot.define do
end
end
+ trait :report_results do
+ after(:build) do |build|
+ build.report_results << build(:ci_build_report_result)
+ end
+ end
+
+ trait :codequality_report do
+ after(:build) do |build|
+ build.job_artifacts << create(:ci_job_artifact, :codequality, job: build)
+ end
+ end
+
trait :test_reports do
after(:build) do |build|
build.job_artifacts << create(:ci_job_artifact, :junit, job: build)
diff --git a/spec/factories/ci/pipelines.rb b/spec/factories/ci/pipelines.rb
index 85cdeaca12c..5bd5ab7d67a 100644
--- a/spec/factories/ci/pipelines.rb
+++ b/spec/factories/ci/pipelines.rb
@@ -65,6 +65,22 @@ FactoryBot.define do
add_attribute(:protected) { true }
end
+ trait :with_report_results do
+ status { :success }
+
+ after(:build) do |pipeline, evaluator|
+ pipeline.builds << build(:ci_build, :report_results, pipeline: pipeline, project: pipeline.project)
+ end
+ end
+
+ trait :with_codequality_report do
+ status { :success }
+
+ after(:build) do |pipeline, evaluator|
+ pipeline.builds << build(:ci_build, :codequality_report, pipeline: pipeline, project: pipeline.project)
+ end
+ end
+
trait :with_test_reports do
status { :success }
diff --git a/spec/factories/clusters/applications/helm.rb b/spec/factories/clusters/applications/helm.rb
index c49c26f06e5..46aa640780b 100644
--- a/spec/factories/clusters/applications/helm.rb
+++ b/spec/factories/clusters/applications/helm.rb
@@ -172,5 +172,13 @@ FactoryBot.define do
cluster factory: %i(cluster provided_by_gcp)
end
end
+
+ factory :clusters_applications_cilium, class: 'Clusters::Applications::Cilium' do
+ cluster factory: %i(cluster with_installed_helm provided_by_gcp)
+
+ trait :no_helm_installed do
+ cluster factory: %i(cluster provided_by_gcp)
+ end
+ end
end
end
diff --git a/spec/factories/clusters/clusters.rb b/spec/factories/clusters/clusters.rb
index 7d0aaa45e40..0daf624ae7e 100644
--- a/spec/factories/clusters/clusters.rb
+++ b/spec/factories/clusters/clusters.rb
@@ -101,6 +101,7 @@ FactoryBot.define do
application_knative factory: %i(clusters_applications_knative installed)
application_elastic_stack factory: %i(clusters_applications_elastic_stack installed)
application_fluentd factory: %i(clusters_applications_fluentd installed)
+ application_cilium factory: %i(clusters_applications_cilium installed)
end
trait :with_domain do
diff --git a/spec/factories/custom_emoji.rb b/spec/factories/custom_emoji.rb
new file mode 100644
index 00000000000..2d185794ac9
--- /dev/null
+++ b/spec/factories/custom_emoji.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+FactoryBot.define do
+ factory :custom_emoji, class: 'CustomEmoji' do
+ sequence(:name) { |n| "custom_emoji#{n}" }
+ namespace
+ file { fixture_file_upload(Rails.root.join('spec/fixtures/dk.png')) }
+ end
+end
diff --git a/spec/factories/deployments.rb b/spec/factories/deployments.rb
index 42046464213..2aab9764560 100644
--- a/spec/factories/deployments.rb
+++ b/spec/factories/deployments.rb
@@ -7,7 +7,7 @@ FactoryBot.define do
tag { false }
user { nil }
project { nil }
- deployable { association :ci_build, environment: environment.name, project: environment.project }
+ deployable { association :ci_build, environment: environment.name, pipeline: association(:ci_pipeline, project: environment.project) }
environment factory: :environment
after(:build) do |deployment, evaluator|
diff --git a/spec/factories/events.rb b/spec/factories/events.rb
index 60bb3044191..ecbda5fbfd3 100644
--- a/spec/factories/events.rb
+++ b/spec/factories/events.rb
@@ -21,7 +21,7 @@ FactoryBot.define do
factory :closed_issue_event do
action { :closed }
- target factory: :closed_issue
+ target { association(:closed_issue, project: project) }
end
factory :wiki_page_event do
diff --git a/spec/factories/go_module_commits.rb b/spec/factories/go_module_commits.rb
new file mode 100644
index 00000000000..e42ef6696d1
--- /dev/null
+++ b/spec/factories/go_module_commits.rb
@@ -0,0 +1,82 @@
+# frozen_string_literal: true
+
+FactoryBot.define do
+ factory :go_module_commit, class: 'Commit' do
+ skip_create
+
+ transient do
+ files { { 'foo.txt' => 'content' } }
+ message { 'Message' }
+ project { create(:project, :repository) }
+
+ service do
+ Files::MultiService.new(
+ project,
+ project.owner,
+ commit_message: message,
+ start_branch: project.repository.root_ref || 'master',
+ branch_name: project.repository.root_ref || 'master',
+ actions: files.map do |path, content|
+ { action: :create, file_path: path, content: content }
+ end
+ )
+ end
+
+ tag { nil }
+ tag_message { nil }
+
+ commit do
+ r = service.execute
+
+ raise "operation failed: #{r}" unless r[:status] == :success
+
+ commit = project.repository.commit_by(oid: r[:result])
+
+ if tag
+ r = Tags::CreateService.new(project, project.owner).execute(tag, commit.sha, tag_message)
+
+ raise "operation failed: #{r}" unless r[:status] == :success
+ end
+
+ commit
+ end
+ end
+
+ trait :files do
+ transient do
+ files { raise ArgumentError.new("files is required") }
+ message { 'Add files' }
+ end
+ end
+
+ trait :package do
+ transient do
+ path { raise ArgumentError.new("path is required") }
+ message { 'Add package' }
+ files { { "#{path}/b.go" => "package b\nfunc Bye() { println(\"Goodbye world!\") }\n" } }
+ end
+ end
+
+ trait :module do
+ transient do
+ name { nil }
+ message { 'Add module' }
+ host_prefix { "#{::Gitlab.config.gitlab.host}/#{project.path_with_namespace}" }
+
+ url { name ? "#{host_prefix}/#{name}" : host_prefix }
+ path { name.to_s + '/' }
+
+ files do
+ {
+ "#{path}go.mod" => "module #{url}\n",
+ "#{path}a.go" => "package a\nfunc Hi() { println(\"Hello world!\") }\n"
+ }
+ end
+ end
+ end
+
+ initialize_with do
+ commit
+ end
+ end
+end
diff --git a/spec/factories/go_module_versions.rb b/spec/factories/go_module_versions.rb
new file mode 100644
index 00000000000..b0a96197350
--- /dev/null
+++ b/spec/factories/go_module_versions.rb
@@ -0,0 +1,77 @@
+# frozen_string_literal: true
+
+FactoryBot.define do
+ factory :go_module_version, class: 'Packages::Go::ModuleVersion' do
+ skip_create
+
+ initialize_with do
+ p = attributes[:params]
+ s = Packages::SemVer.parse(p.semver, prefixed: true)
+
+ raise ArgumentError.new("invalid sematic version: '#{p.semver}''") if !s && p.semver
+
+ new(p.mod, p.type, p.commit, name: p.name, semver: s, ref: p.ref)
+ end
+
+ mod { create :go_module }
+ type { :commit }
+ commit { mod.project.repository.head_commit }
+ name { nil }
+ semver { nil }
+ ref { nil }
+
+ params { OpenStruct.new(mod: mod, type: type, commit: commit, name: name, semver: semver, ref: ref) }
+
+ trait :tagged do
+ ref { mod.project.repository.find_tag(name) }
+ commit { ref.dereferenced_target }
+ name do
+ # This provides a sane default value, but in reality the caller should
+ # specify `name:`
+
+ # Find 'latest' semver tag (does not actually use semver precedence rules)
+ mod.project.repository.tags
+ .filter { |t| Packages::SemVer.match?(t.name, prefixed: true) }
+ .map { |t| Packages::SemVer.parse(t.name, prefixed: true) }
+ .max { |a, b| "#{a}" <=> "#{b}" }
+ .to_s
+ end
+
+ params { OpenStruct.new(mod: mod, type: :ref, commit: commit, semver: name, ref: ref) }
+ end
+
+ trait :pseudo do
+ transient do
+ prefix do
+ # This provides a sane default value, but in reality the caller should
+ # specify `prefix:`
+
+ # This does not take into account that `commit` may be before the
+ # latest tag.
+
+ # Find 'latest' semver tag (does not actually use semver precedence rules)
+ v = mod.project.repository.tags
+ .filter { |t| Packages::SemVer.match?(t.name, prefixed: true) }
+ .map { |t| Packages::SemVer.parse(t.name, prefixed: true) }
+ .max { |a, b| "#{a}" <=> "#{b}" }
+
+ # Default if no semver tags exist
+ next 'v0.0.0' unless v
+
+ # Valid pseudo-versions are:
+ # vX.0.0-yyyymmddhhmmss-sha1337beef0, when no earlier tagged commit exists for X
+ # vX.Y.Z-pre.0.yyyymmddhhmmss-sha1337beef0, when most recent prior tag is vX.Y.Z-pre
+ # vX.Y.(Z+1)-0.yyyymmddhhmmss-sha1337beef0, when most recent prior tag is vX.Y.Z
+
+ v = v.with(patch: v.patch + 1) unless v.prerelease
+ "#{v}.0"
+ end
+ end
+
+ type { :pseudo }
+ name { "#{prefix}#{commit.committed_date.strftime('%Y%m%d%H%M%S')}-#{commit.sha[0..11]}" }
+
+ params { OpenStruct.new(mod: mod, type: :pseudo, commit: commit, name: name, semver: name) }
+ end
+ end
+end
diff --git a/spec/factories/go_modules.rb b/spec/factories/go_modules.rb
new file mode 100644
index 00000000000..fdbacf48d3b
--- /dev/null
+++ b/spec/factories/go_modules.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+FactoryBot.define do
+ factory :go_module, class: 'Packages::Go::Module' do
+ initialize_with { new(attributes[:project], attributes[:name], attributes[:path]) }
+ skip_create
+
+ project { create :project, :repository }
+
+ path { '' }
+ name { "#{Settings.build_gitlab_go_url}/#{project.full_path}#{path.empty? ? '' : '/'}#{path}" }
+ end
+end
diff --git a/spec/factories/groups.rb b/spec/factories/groups.rb
index d51c437f83a..60d427dde00 100644
--- a/spec/factories/groups.rb
+++ b/spec/factories/groups.rb
@@ -51,5 +51,13 @@ FactoryBot.define do
trait :owner_subgroup_creation_only do
subgroup_creation_level { ::Gitlab::Access::OWNER_SUBGROUP_ACCESS }
end
+
+ trait :shared_runners_disabled do
+ shared_runners_enabled { false }
+ end
+
+ trait :allow_descendants_override_disabled_shared_runners do
+ allow_descendants_override_disabled_shared_runners { true }
+ end
end
end
diff --git a/spec/factories/namespaces.rb b/spec/factories/namespaces.rb
index 09dbe16ef9e..f4d5848e878 100644
--- a/spec/factories/namespaces.rb
+++ b/spec/factories/namespaces.rb
@@ -29,5 +29,35 @@ FactoryBot.define do
trait :with_root_storage_statistics do
association :root_storage_statistics, factory: :namespace_root_storage_statistics
end
+
+ # Construct a hierarchy underneath the namespace.
+ # Each namespace will have `children` amount of children,
+ # and `depth` levels of descendants.
+ trait :with_hierarchy do
+ transient do
+ children { 4 }
+ depth { 4 }
+ end
+
+ after(:create) do |namespace, evaluator|
+ def create_graph(parent: nil, children: 4, depth: 4)
+ return unless depth > 1
+
+ children.times do
+ factory_name = parent.model_name.singular
+ child = FactoryBot.create(factory_name, parent: parent)
+ create_graph(parent: child, children: children, depth: depth - 1)
+ end
+
+ parent
+ end
+
+ create_graph(
+ parent: namespace,
+ children: evaluator.children,
+ depth: evaluator.depth
+ )
+ end
+ end
end
end
diff --git a/spec/factories/notes.rb b/spec/factories/notes.rb
index 52e91f31ec1..4b1f3194ce5 100644
--- a/spec/factories/notes.rb
+++ b/spec/factories/notes.rb
@@ -159,6 +159,11 @@ FactoryBot.define do
system { true }
end
+ trait :with_system_note_metadata do
+ system
+ system_note_metadata
+ end
+
trait :downvote do
note { "thumbsdown" }
end
diff --git a/spec/factories/packages.rb b/spec/factories/packages.rb
new file mode 100644
index 00000000000..562269a67bc
--- /dev/null
+++ b/spec/factories/packages.rb
@@ -0,0 +1,355 @@
+# frozen_string_literal: true
+FactoryBot.define do
+ factory :package, class: 'Packages::Package' do
+ project
+ name { 'my/company/app/my-app' }
+ sequence(:version) { |n| "1.#{n}-SNAPSHOT" }
+ package_type { :maven }
+
+ factory :maven_package do
+ maven_metadatum
+
+ after :build do |package|
+ package.maven_metadatum.path = "#{package.name}/#{package.version}"
+ end
+
+ after :create do |package|
+ create :package_file, :xml, package: package
+ create :package_file, :jar, package: package
+ create :package_file, :pom, package: package
+ end
+ end
+
+ factory :npm_package do
+ sequence(:name) { |n| "@#{project.root_namespace.path}/package-#{n}"}
+ version { '1.0.0' }
+ package_type { :npm }
+
+ after :create do |package|
+ create :package_file, :npm, package: package
+ end
+
+ trait :with_build do
+ after :create do |package|
+ user = package.project.creator
+ pipeline = create(:ci_pipeline, user: user)
+ create(:ci_build, user: user, pipeline: pipeline)
+ create :package_build_info, package: package, pipeline: pipeline
+ end
+ end
+ end
+
+ factory :nuget_package do
+ sequence(:name) { |n| "NugetPackage#{n}"}
+ sequence(:version) { |n| "1.0.#{n}" }
+ package_type { :nuget }
+
+ after :create do |package|
+ create :package_file, :nuget, package: package, file_name: "#{package.name}.#{package.version}.nupkg"
+ end
+
+ trait(:with_metadatum) do
+ after :build do |pkg|
+ pkg.nuget_metadatum = build(:nuget_metadatum)
+ end
+ end
+ end
+
+ factory :pypi_package do
+ pypi_metadatum
+
+ sequence(:name) { |n| "pypi-package-#{n}"}
+ sequence(:version) { |n| "1.0.#{n}" }
+ package_type { :pypi }
+
+ after :create do |package|
+ create :package_file, :pypi, package: package, file_name: "#{package.name}-#{package.version}.tar.gz"
+ end
+ end
+
+ factory :composer_package do
+ sequence(:name) { |n| "composer-package-#{n}"}
+ sequence(:version) { |n| "1.0.#{n}" }
+ package_type { :composer }
+
+ transient do
+ sha { project.repository.find_branch('master').target }
+ json { { name: name, version: version } }
+ end
+
+ trait(:with_metadatum) do
+ after :create do |package, evaluator|
+ create :composer_metadatum, package: package, target_sha: evaluator.sha, composer_json: evaluator.json
+ end
+ end
+ end
+
+ factory :conan_package do
+ conan_metadatum
+
+ transient do
+ without_package_files { false }
+ end
+
+ after :build do |package|
+ package.conan_metadatum.package_username = Packages::Conan::Metadatum.package_username_from(
+ full_path: package.project.full_path
+ )
+ end
+
+ sequence(:name) { |n| "package-#{n}" }
+ version { '1.0.0' }
+ package_type { :conan }
+
+ after :create do |package, evaluator|
+ unless evaluator.without_package_files
+ create :conan_package_file, :conan_recipe_file, package: package
+ create :conan_package_file, :conan_recipe_manifest, package: package
+ create :conan_package_file, :conan_package_info, package: package
+ create :conan_package_file, :conan_package_manifest, package: package
+ create :conan_package_file, :conan_package, package: package
+ end
+ end
+
+ trait(:without_loaded_metadatum) do
+ conan_metadatum { build(:conan_metadatum, package: nil) }
+ end
+ end
+ end
+
+ factory :composer_metadatum, class: 'Packages::Composer::Metadatum' do
+ package { create(:composer_package) }
+
+ target_sha { '123' }
+ composer_json { { name: 'foo' } }
+ end
+
+ factory :package_build_info, class: 'Packages::BuildInfo' do
+ package
+ end
+
+ factory :package_file, class: 'Packages::PackageFile' do
+ package
+
+ file_name { 'somefile.txt' }
+
+ transient do
+ file_fixture { 'spec/fixtures/packages/conan/recipe_files/conanfile.py' }
+ end
+
+ after(:build) do |package_file, evaluator|
+ package_file.file = fixture_file_upload(evaluator.file_fixture)
+ end
+
+ factory :conan_package_file do
+ package { create(:conan_package, without_package_files: true) }
+
+ transient do
+ without_loaded_metadatum { false }
+ end
+
+ trait(:conan_recipe_file) do
+ after :create do |package_file, evaluator|
+ unless evaluator.without_loaded_metadatum
+ create :conan_file_metadatum, :recipe_file, package_file: package_file
+ end
+ end
+
+ file_fixture { 'spec/fixtures/packages/conan/recipe_files/conanfile.py' }
+ file_name { 'conanfile.py' }
+ file_sha1 { 'be93151dc23ac34a82752444556fe79b32c7a1ad' }
+ file_md5 { '12345abcde' }
+ size { 400.kilobytes }
+ end
+
+ trait(:conan_recipe_manifest) do
+ after :create do |package_file, evaluator|
+ unless evaluator.without_loaded_metadatum
+ create :conan_file_metadatum, :recipe_file, package_file: package_file
+ end
+ end
+
+ file_fixture { 'spec/fixtures/packages/conan/recipe_files/conanmanifest.txt' }
+ file_name { 'conanmanifest.txt' }
+ file_sha1 { 'be93151dc23ac34a82752444556fe79b32c7a1ad' }
+ file_md5 { '12345abcde' }
+ size { 400.kilobytes }
+ end
+
+ trait(:conan_package_manifest) do
+ after :create do |package_file, evaluator|
+ unless evaluator.without_loaded_metadatum
+ create :conan_file_metadatum, :package_file, package_file: package_file
+ end
+ end
+
+ file_fixture { 'spec/fixtures/packages/conan/package_files/conanmanifest.txt' }
+ file_name { 'conanmanifest.txt' }
+ file_sha1 { 'be93151dc23ac34a82752444556fe79b32c7a1ad' }
+ file_md5 { '12345abcde' }
+ size { 400.kilobytes }
+ end
+
+ trait(:conan_package_info) do
+ after :create do |package_file, evaluator|
+ unless evaluator.without_loaded_metadatum
+ create :conan_file_metadatum, :package_file, package_file: package_file
+ end
+ end
+
+ file_fixture { 'spec/fixtures/packages/conan/package_files/conaninfo.txt' }
+ file_name { 'conaninfo.txt' }
+ file_sha1 { 'be93151dc23ac34a82752444556fe79b32c7a1ad' }
+ file_md5 { '12345abcde' }
+ size { 400.kilobytes }
+ end
+
+ trait(:conan_package) do
+ after :create do |package_file, evaluator|
+ unless evaluator.without_loaded_metadatum
+ create :conan_file_metadatum, :package_file, package_file: package_file
+ end
+ end
+
+ file_fixture { 'spec/fixtures/packages/conan/package_files/conan_package.tgz' }
+ file_name { 'conan_package.tgz' }
+ file_sha1 { 'be93151dc23ac34a82752444556fe79b32c7a1ad' }
+ file_md5 { '12345abcde' }
+ size { 400.kilobytes }
+ end
+ end
+
+ trait(:jar) do
+ file_fixture { 'spec/fixtures/packages/maven/my-app-1.0-20180724.124855-1.jar' }
+ file_name { 'my-app-1.0-20180724.124855-1.jar' }
+ file_sha1 { '4f0bfa298744d505383fbb57c554d4f5c12d88b3' }
+ size { 100.kilobytes }
+ end
+
+ trait(:pom) do
+ file_fixture { 'spec/fixtures/packages/maven/my-app-1.0-20180724.124855-1.pom' }
+ file_name { 'my-app-1.0-20180724.124855-1.pom' }
+ file_sha1 { '19c975abd49e5102ca6c74a619f21e0cf0351c57' }
+ size { 200.kilobytes }
+ end
+
+ trait(:xml) do
+ file_fixture { 'spec/fixtures/packages/maven/maven-metadata.xml' }
+ file_name { 'maven-metadata.xml' }
+ file_sha1 { '42b1bdc80de64953b6876f5a8c644f20204011b0' }
+ size { 300.kilobytes }
+ end
+
+ trait(:npm) do
+ file_fixture { 'spec/fixtures/packages/npm/foo-1.0.1.tgz' }
+ file_name { 'foo-1.0.1.tgz' }
+ file_sha1 { 'be93151dc23ac34a82752444556fe79b32c7a1ad' }
+ verified_at { Date.current }
+ verification_checksum { '4437b5775e61455588a7e5187a2e5c58c680694260bbe5501c235ec690d17f83' }
+ size { 400.kilobytes }
+ end
+
+ trait(:nuget) do
+ package
+ file_fixture { 'spec/fixtures/packages/nuget/package.nupkg' }
+ file_name { 'package.nupkg' }
+ file_sha1 { '5fe852b2a6abd96c22c11fa1ff2fb19d9ce58b57' }
+ size { 300.kilobytes }
+ end
+
+ trait(:pypi) do
+ package
+ file_fixture { 'spec/fixtures/packages/pypi/sample-project.tar.gz' }
+ file_name { 'sample-project-1.0.0.tar.gz' }
+ file_sha1 { '2c0cfbed075d3fae226f051f0cc771b533e01aff' }
+ file_md5 { '0a7392d24f42f83068fa3767c5310052' }
+ file_sha256 { '440e5e148a25331bbd7991575f7d54933c0ebf6cc735a18ee5066ac1381bb590' }
+ size { 1149.bytes }
+ end
+
+ trait(:object_storage) do
+ file_store { Packages::PackageFileUploader::Store::REMOTE }
+ end
+
+ trait(:checksummed) do
+ verification_checksum { 'abc' }
+ end
+
+ trait(:checksum_failure) do
+ verification_failure { 'Could not calculate the checksum' }
+ end
+
+ factory :package_file_with_file, traits: [:jar]
+ end
+
+ factory :maven_metadatum, class: 'Packages::Maven::Metadatum' do
+ association :package, package_type: :maven
+ path { 'my/company/app/my-app/1.0-SNAPSHOT' }
+ app_group { 'my.company.app' }
+ app_name { 'my-app' }
+ app_version { '1.0-SNAPSHOT' }
+ end
+
+ factory :conan_metadatum, class: 'Packages::Conan::Metadatum' do
+ association :package, factory: [:conan_package, :without_loaded_metadatum], without_package_files: true
+ package_username { 'username' }
+ package_channel { 'stable' }
+ end
+
+ factory :pypi_metadatum, class: 'Packages::Pypi::Metadatum' do
+ association :package, package_type: :pypi
+ required_python { '>=2.7' }
+ end
+
+ factory :nuget_metadatum, class: 'Packages::Nuget::Metadatum' do
+ package { create(:nuget_package) }
+
+ license_url { 'http://www.gitlab.com' }
+ project_url { 'http://www.gitlab.com' }
+ icon_url { 'http://www.gitlab.com' }
+ end
+
+ factory :conan_file_metadatum, class: 'Packages::Conan::FileMetadatum' do
+ package_file { create(:conan_package_file, :conan_recipe_file, without_loaded_metadatum: true) }
+ recipe_revision { '0' }
+ conan_file_type { 'recipe_file' }
+
+ trait(:recipe_file) do
+ conan_file_type { 'recipe_file' }
+ end
+
+ trait(:package_file) do
+ package_file { create(:conan_package_file, :conan_package, without_loaded_metadatum: true) }
+ conan_file_type { 'package_file' }
+ package_revision { '0' }
+ conan_package_reference { '123456789' }
+ end
+ end
+
+ factory :packages_dependency, class: 'Packages::Dependency' do
+ sequence(:name) { |n| "@test/package-#{n}"}
+ sequence(:version_pattern) { |n| "~6.2.#{n}" }
+ end
+
+ factory :packages_dependency_link, class: 'Packages::DependencyLink' do
+ package { create(:nuget_package) }
+ dependency { create(:packages_dependency) }
+ dependency_type { :dependencies }
+
+ trait(:with_nuget_metadatum) do
+ after :build do |link|
+ link.nuget_metadatum = build(:nuget_dependency_link_metadatum)
+ end
+ end
+ end
+
+ factory :nuget_dependency_link_metadatum, class: 'Packages::Nuget::DependencyLinkMetadatum' do
+ dependency_link { create(:packages_dependency_link) }
+ target_framework { '.NETStandard2.0' }
+ end
+
+ factory :packages_tag, class: 'Packages::Tag' do
+ package
+ sequence(:name) { |n| "tag-#{n}"}
+ end
+end
diff --git a/spec/factories/product_analytics_event.rb b/spec/factories/product_analytics_event.rb
new file mode 100644
index 00000000000..168b255f6ca
--- /dev/null
+++ b/spec/factories/product_analytics_event.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+
+FactoryBot.define do
+ factory :product_analytics_event do
+ project
+ platform { 'web' }
+ collector_tstamp { DateTime.now }
+ dvce_created_tstamp { DateTime.now }
+ event_id { SecureRandom.uuid }
+ name_tracker { 'sp' }
+ v_tracker { 'js-2.14.0' }
+ v_collector { 'GitLab 13.1.0-pre' }
+ v_etl { 'GitLab 13.1.0-pre' }
+ domain_userid { SecureRandom.uuid }
+ domain_sessionidx { 4 }
+ page_url { 'http://localhost:3333/products/123' }
+ br_lang { 'en-US' }
+ br_cookies { true }
+ br_colordepth { '24' }
+ os_timezone { 'America/Los_Angeles' }
+ doc_charset { 'UTF-8' }
+ domain_sessionid { SecureRandom.uuid }
+ end
+end
diff --git a/spec/factories/project_repository_storage_moves.rb b/spec/factories/project_repository_storage_moves.rb
index b35d5e1d535..ea0b34e0338 100644
--- a/spec/factories/project_repository_storage_moves.rb
+++ b/spec/factories/project_repository_storage_moves.rb
@@ -14,5 +14,13 @@ FactoryBot.define do
trait :started do
state { ProjectRepositoryStorageMove.state_machines[:state].states[:started].value }
end
+
+ trait :finished do
+ state { ProjectRepositoryStorageMove.state_machines[:state].states[:finished].value }
+ end
+
+ trait :failed do
+ state { ProjectRepositoryStorageMove.state_machines[:state].states[:failed].value }
+ end
end
end
diff --git a/spec/factories/project_statistics.rb b/spec/factories/project_statistics.rb
index f084d9d5cbf..78e80a92b3a 100644
--- a/spec/factories/project_statistics.rb
+++ b/spec/factories/project_statistics.rb
@@ -21,6 +21,7 @@ FactoryBot.define do
project_statistics.lfs_objects_size = evaluator.size_multiplier * 3
project_statistics.build_artifacts_size = evaluator.size_multiplier * 4
project_statistics.packages_size = evaluator.size_multiplier * 5
+ project_statistics.snippets_size = evaluator.size_multiplier * 6
end
end
end
diff --git a/spec/factories/projects.rb b/spec/factories/projects.rb
index 4affab295b8..e4b53186ea8 100644
--- a/spec/factories/projects.rb
+++ b/spec/factories/projects.rb
@@ -181,6 +181,7 @@ FactoryBot.define do
transient do
create_templates { nil }
+ create_branch { nil }
end
after :create do |project, evaluator|
@@ -206,6 +207,16 @@ FactoryBot.define do
message: 'test 2',
branch_name: 'master')
end
+
+ if evaluator.create_branch
+ project.repository.create_file(
+ project.creator,
+ 'README.md',
+ "README on branch #{evaluator.create_branch}",
+ message: 'Add README.md',
+ branch_name: evaluator.create_branch)
+
+ end
end
end
@@ -305,6 +316,14 @@ FactoryBot.define do
end
end
+ trait :service_desk_disabled do
+ service_desk_enabled { nil }
+ end
+
+ trait(:service_desk_enabled) do
+ service_desk_enabled { true }
+ end
+
# Project with empty repository
#
# This is a case when you just created a project
@@ -363,4 +382,11 @@ FactoryBot.define do
)
end
end
+
+ factory :project_with_design, parent: :project do
+ after(:create) do |project|
+ issue = create(:issue, project: project)
+ create(:design, project: project, issue: issue)
+ end
+ end
end
diff --git a/spec/factories/service_desk_settings.rb b/spec/factories/service_desk_settings.rb
new file mode 100644
index 00000000000..abdbc5f1ea0
--- /dev/null
+++ b/spec/factories/service_desk_settings.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+FactoryBot.define do
+ factory :service_desk_setting do
+ project
+ end
+end
diff --git a/spec/factories/services.rb b/spec/factories/services.rb
index fd97f6abb85..9a521336fee 100644
--- a/spec/factories/services.rb
+++ b/spec/factories/services.rb
@@ -77,18 +77,27 @@ FactoryBot.define do
username { 'jira_username' }
password { 'jira_password' }
jira_issue_transition_id { '56-1' }
+ issues_enabled { false }
+ project_key { nil }
end
after(:build) do |service, evaluator|
if evaluator.create_data
create(:jira_tracker_data, service: service,
url: evaluator.url, api_url: evaluator.api_url, jira_issue_transition_id: evaluator.jira_issue_transition_id,
- username: evaluator.username, password: evaluator.password
+ username: evaluator.username, password: evaluator.password, issues_enabled: evaluator.issues_enabled,
+ project_key: evaluator.project_key
)
end
end
end
+ factory :confluence_service do
+ project
+ active { true }
+ confluence_url { 'https://example.atlassian.net/wiki' }
+ end
+
factory :bugzilla_service do
project
active { true }
diff --git a/spec/factories/snippet_statistics.rb b/spec/factories/snippet_statistics.rb
new file mode 100644
index 00000000000..ab2d9525466
--- /dev/null
+++ b/spec/factories/snippet_statistics.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+FactoryBot.define do
+ factory :snippet_statistics do
+ snippet
+
+ initialize_with do
+ # statistics are automatically created when a snippet is created
+ snippet&.statistics || new
+ end
+
+ transient do
+ with_data { false }
+ size_multiplier { 1 }
+ end
+
+ after(:build) do |snippet_statistics, evaluator|
+ if evaluator.with_data
+ snippet_statistics.repository_size = evaluator.size_multiplier
+ snippet_statistics.commit_count = evaluator.size_multiplier * 2
+ snippet_statistics.file_count = evaluator.size_multiplier * 3
+ end
+ end
+ end
+end
diff --git a/spec/factories/terraform/state.rb b/spec/factories/terraform/state.rb
index 74950ccf93e..46784581180 100644
--- a/spec/factories/terraform/state.rb
+++ b/spec/factories/terraform/state.rb
@@ -9,5 +9,11 @@ FactoryBot.define do
trait :with_file do
file { fixture_file_upload('spec/fixtures/terraform/terraform.tfstate', 'application/json') }
end
+
+ trait :locked do
+ sequence(:lock_xid) { |n| "lock-#{n}" }
+ locked_at { Time.current }
+ locked_by_user { create(:user) }
+ end
end
end
diff --git a/spec/factories/usage_data.rb b/spec/factories/usage_data.rb
index c0c5b1103fe..518b5b5e76a 100644
--- a/spec/factories/usage_data.rb
+++ b/spec/factories/usage_data.rb
@@ -5,7 +5,8 @@ FactoryBot.define do
skip_create # non-model factories (i.e. without #save)
initialize_with do
- projects = create_list(:project, 4)
+ projects = create_list(:project, 3)
+ projects << create(:project, :repository)
create(:board, project: projects[0])
create(:jira_service, project: projects[0])
create(:jira_service, :without_properties_callback, project: projects[1])
@@ -46,7 +47,7 @@ FactoryBot.define do
create(:sentry_issue, issue: projects[0].issues[0])
# Incident Labeled Issues
- incident_label_attrs = IncidentManagement::CreateIssueService::INCIDENT_LABEL
+ incident_label_attrs = IncidentManagement::CreateIncidentLabelService::LABEL_PROPERTIES
incident_label = create(:label, project: projects[0], **incident_label_attrs)
create(:labeled_issue, project: projects[0], labels: [incident_label])
incident_group = create(:group)
@@ -83,12 +84,23 @@ FactoryBot.define do
create(:clusters_applications_knative, :installed, cluster: gcp_cluster)
create(:clusters_applications_elastic_stack, :installed, cluster: gcp_cluster)
create(:clusters_applications_jupyter, :installed, cluster: gcp_cluster)
+ create(:clusters_applications_cilium, :installed, cluster: gcp_cluster)
create(:grafana_integration, project: projects[0], enabled: true)
create(:grafana_integration, project: projects[1], enabled: true)
create(:grafana_integration, project: projects[2], enabled: false)
ProjectFeature.first.update_attribute('repository_access_level', 0)
+
+ # Create fresh & a month (28-days SMAU) old data
+ env = create(:environment, project: projects[3])
+ [2, 29].each do |n|
+ deployment_options = { created_at: n.days.ago, project: env.project, environment: env }
+ create(:deployment, :failed, deployment_options)
+ create(:deployment, :success, deployment_options)
+ create_list(:project_snippet, 2, project: projects[0], created_at: n.days.ago)
+ create(:personal_snippet, created_at: n.days.ago)
+ end
end
end
end
diff --git a/spec/features/admin/admin_sees_project_statistics_spec.rb b/spec/features/admin/admin_sees_project_statistics_spec.rb
index 6e8211a9b4e..d94889b825a 100644
--- a/spec/features/admin/admin_sees_project_statistics_spec.rb
+++ b/spec/features/admin/admin_sees_project_statistics_spec.rb
@@ -15,7 +15,7 @@ RSpec.describe "Admin > Admin sees project statistics" do
let(:project) { create(:project, :repository) }
it "shows project statistics" do
- expect(page).to have_content("Storage: 0 Bytes (Repository: 0 Bytes / Wikis: 0 Bytes / Build Artifacts: 0 Bytes / LFS: 0 Bytes)")
+ expect(page).to have_content("Storage: 0 Bytes (Repository: 0 Bytes / Wikis: 0 Bytes / Build Artifacts: 0 Bytes / LFS: 0 Bytes / Snippets: 0 Bytes)")
end
end
diff --git a/spec/features/admin/admin_users_spec.rb b/spec/features/admin/admin_users_spec.rb
index 369f91c6faa..6cd18f2755c 100644
--- a/spec/features/admin/admin_users_spec.rb
+++ b/spec/features/admin/admin_users_spec.rb
@@ -38,7 +38,7 @@ RSpec.describe "Admin::Users" do
end
describe "view extra user information" do
- it 'shows the user popover on hover', :js, :quarantine do
+ it 'shows the user popover on hover', :js, quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/11290' do
expect(page).not_to have_selector('#__BV_popover_1__')
first_user_link = page.first('.js-user-link')
@@ -512,14 +512,14 @@ RSpec.describe "Admin::Users" do
end
it "lists group projects" do
- within(:css, '.append-bottom-default + .card') do
+ within(:css, '.gl-mb-3 + .card') do
expect(page).to have_content 'Group projects'
expect(page).to have_link group.name, href: admin_group_path(group)
end
end
it 'allows navigation to the group details' do
- within(:css, '.append-bottom-default + .card') do
+ within(:css, '.gl-mb-3 + .card') do
click_link group.name
end
within(:css, 'h3.page-title') do
@@ -529,7 +529,7 @@ RSpec.describe "Admin::Users" do
end
it 'shows the group access level' do
- within(:css, '.append-bottom-default + .card') do
+ within(:css, '.gl-mb-3 + .card') do
expect(page).to have_content 'Developer'
end
end
diff --git a/spec/features/admin/services/admin_visits_service_templates_spec.rb b/spec/features/admin/services/admin_visits_service_templates_spec.rb
new file mode 100644
index 00000000000..8e02538ece0
--- /dev/null
+++ b/spec/features/admin/services/admin_visits_service_templates_spec.rb
@@ -0,0 +1,30 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'Admin visits service templates' do
+ let(:admin) { create(:user, :admin) }
+ let(:slack_service) { Service.templates.find { |s| s.type == 'SlackService' } }
+
+ before do
+ sign_in(admin)
+
+ visit(admin_application_settings_services_path)
+ end
+
+ context 'without instance-level integration' do
+ it 'shows a link to service template' do
+ expect(page).to have_link('Slack', href: edit_admin_application_settings_service_path(slack_service.id))
+ expect(page).not_to have_link('Slack', href: edit_admin_application_settings_integration_path(slack_service))
+ end
+ end
+
+ context 'with instance-level integration' do
+ let_it_be(:slack_instance_integration) { create(:slack_service, instance: true, project: nil) }
+
+ it 'shows a link to instance-level integration' do
+ expect(page).not_to have_link('Slack', href: edit_admin_application_settings_service_path(slack_service.id))
+ expect(page).to have_link('Slack', href: edit_admin_application_settings_integration_path(slack_service))
+ end
+ end
+end
diff --git a/spec/features/clusters/cluster_health_dashboard_spec.rb b/spec/features/clusters/cluster_health_dashboard_spec.rb
new file mode 100644
index 00000000000..e9e3b48e9c0
--- /dev/null
+++ b/spec/features/clusters/cluster_health_dashboard_spec.rb
@@ -0,0 +1,93 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'Cluster Health board', :js, :kubeclient, :use_clean_rails_memory_store_caching, :sidekiq_inline do
+ include KubernetesHelpers
+ include PrometheusHelpers
+
+ let_it_be(:current_user) { create(:user) }
+ let_it_be(:clusterable) { create(:project) }
+ let_it_be(:cluster) { create(:cluster, :provided_by_gcp, :project, projects: [clusterable]) }
+ let_it_be(:cluster_path) { project_cluster_path(clusterable, cluster) }
+
+ before do
+ clusterable.add_maintainer(current_user)
+
+ sign_in(current_user)
+ end
+
+ it 'shows cluster board section within the page' do
+ visit cluster_path
+
+ expect(page).to have_text('Health')
+
+ click_link 'Health'
+
+ expect(page).to have_css('.cluster-health-graphs')
+ end
+
+ context 'no prometheus installed' do
+ it 'shows install prometheus message' do
+ visit cluster_path
+
+ click_link 'Health'
+
+ expect(page).to have_text('you must first install Prometheus in the Applications tab')
+ end
+ end
+
+ context 'when there is cluster with installed prometheus' do
+ before do
+ create(:clusters_applications_prometheus, :installed, cluster: cluster)
+ stub_kubeclient_discover(cluster.platform.api_url)
+ end
+
+ context 'waiting for data' do
+ before do
+ stub_empty_response
+ end
+
+ it 'shows container and waiting for data message' do
+ visit cluster_path
+
+ click_link 'Health'
+
+ wait_for_requests
+
+ expect(page).to have_css('.prometheus-graphs')
+ expect(page).to have_text('Waiting for performance data')
+ end
+ end
+
+ context 'connected, prometheus returns data' do
+ before do
+ stub_connected
+ end
+
+ it 'renders charts' do
+ visit cluster_path
+
+ click_link 'Health'
+
+ wait_for_requests
+
+ expect(page).to have_css('.prometheus-graphs')
+ expect(page).to have_css('.prometheus-graph')
+ expect(page).to have_css('.prometheus-graph-title')
+ expect(page).to have_css('[_echarts_instance_]')
+ expect(page).to have_content('Avg')
+ end
+ end
+
+ def stub_empty_response
+ stub_prometheus_request(/prometheus-prometheus-server/, status: 204, body: {})
+ stub_prometheus_request(/prometheus\/api\/v1/, status: 204, body: {})
+ end
+
+ def stub_connected
+ stub_prometheus_request(/prometheus-prometheus-server/, body: prometheus_values_body)
+ stub_prometheus_request(/prometheus\/api\/v1/, body: prometheus_values_body)
+ end
+ end
+end
diff --git a/spec/features/clusters/installing_applications_shared_examples.rb b/spec/features/clusters/installing_applications_shared_examples.rb
index d2f28f5b219..74150c42519 100644
--- a/spec/features/clusters/installing_applications_shared_examples.rb
+++ b/spec/features/clusters/installing_applications_shared_examples.rb
@@ -2,6 +2,9 @@
RSpec.shared_examples "installing applications for a cluster" do |managed_apps_local_tiller|
before do
+ # Reduce interval from 10 seconds which is too long for an automated test
+ stub_const("#{Clusters::ClustersController}::STATUS_POLLING_INTERVAL", 500)
+
stub_feature_flags(managed_apps_local_tiller: managed_apps_local_tiller)
visit cluster_path
@@ -107,10 +110,6 @@ RSpec.shared_examples "installing applications for a cluster" do |managed_apps_l
end
describe 'when user clicks install button' do
- def domainname_form_value
- page.find('.js-knative-domainname').value
- end
-
before do
allow(ClusterInstallAppWorker).to receive(:perform_async)
allow(ClusterWaitForIngressIpAddressWorker).to receive(:perform_in)
@@ -135,7 +134,7 @@ RSpec.shared_examples "installing applications for a cluster" do |managed_apps_l
it 'shows status transition' do
page.within('.js-cluster-application-row-knative') do
- expect(domainname_form_value).to eq('domain.example.org')
+ expect(page).to have_field('Knative Domain Name:', with: 'domain.example.org')
expect(page).to have_css('.js-cluster-application-uninstall-button', exact_text: 'Uninstall')
end
@@ -147,7 +146,7 @@ RSpec.shared_examples "installing applications for a cluster" do |managed_apps_l
page.within('.js-cluster-application-row-knative') do
expect(ClusterPatchAppWorker).to receive(:perform_async)
- expect(domainname_form_value).to eq('domain.example.org')
+ expect(page).to have_field('Knative Domain Name:', with: 'domain.example.org')
page.find('.js-knative-domainname').set("new.domain.example.org")
@@ -155,7 +154,7 @@ RSpec.shared_examples "installing applications for a cluster" do |managed_apps_l
wait_for_requests
- expect(domainname_form_value).to eq('new.domain.example.org')
+ expect(page).to have_field('Knative Domain Name:', with: 'new.domain.example.org')
end
end
end
@@ -169,34 +168,54 @@ RSpec.shared_examples "installing applications for a cluster" do |managed_apps_l
allow(ClusterWaitForIngressIpAddressWorker).to receive(:perform_async)
create(:clusters_applications_helm, :installed, cluster: cluster) unless managed_apps_local_tiller
+ end
+ it 'shows status transition' do
page.within('.js-cluster-application-row-cert_manager') do
click_button 'Install'
+ wait_for_requests
+
+ expect(page).to have_field('Issuer Email', with: cluster.user.email)
+ expect(page).to have_css('.js-cluster-application-install-button', exact_text: 'Installing')
+
+ Clusters::Cluster.last.application_cert_manager.make_installing!
+
+ expect(page).to have_field('Issuer Email', with: cluster.user.email)
+ expect(page).to have_css('.js-cluster-application-install-button', exact_text: 'Installing')
+
+ Clusters::Cluster.last.application_cert_manager.make_installed!
+
+ expect(page).to have_field('Issuer Email', with: cluster.user.email)
+ expect(page).to have_css('.js-cluster-application-uninstall-button', exact_text: 'Uninstall')
end
+
+ expect(page).to have_content('Cert-Manager was successfully installed on your Kubernetes cluster')
end
- it 'shows status transition' do
- def email_form_value
- page.find('.js-email').value
- end
+ it 'installs with custom email' do
+ custom_email = 'new_email@example.org'
page.within('.js-cluster-application-row-cert_manager') do
- expect(email_form_value).to eq(cluster.user.email)
+ # Wait for the polling to finish
+ wait_for_requests
+
+ page.find('.js-email').set(custom_email)
+ click_button 'Install'
+ wait_for_requests
+
+ expect(page).to have_field('Issuer Email', with: custom_email)
expect(page).to have_css('.js-cluster-application-install-button', exact_text: 'Installing')
- page.find('.js-email').set("new_email@example.org")
Clusters::Cluster.last.application_cert_manager.make_installing!
- expect(email_form_value).to eq('new_email@example.org')
+ expect(page).to have_field('Issuer Email', with: custom_email)
expect(page).to have_css('.js-cluster-application-install-button', exact_text: 'Installing')
Clusters::Cluster.last.application_cert_manager.make_installed!
- expect(email_form_value).to eq('new_email@example.org')
+ expect(page).to have_field('Issuer Email', with: custom_email)
expect(page).to have_css('.js-cluster-application-uninstall-button', exact_text: 'Uninstall')
end
-
- expect(page).to have_content('Cert-Manager was successfully installed on your Kubernetes cluster')
end
end
diff --git a/spec/features/dashboard/projects_spec.rb b/spec/features/dashboard/projects_spec.rb
index e1beaf923e8..f870adbbdb6 100644
--- a/spec/features/dashboard/projects_spec.rb
+++ b/spec/features/dashboard/projects_spec.rb
@@ -205,6 +205,14 @@ RSpec.describe 'Dashboard Projects' do
it_behaves_like 'hidden pipeline status'
end
+
+ context "when last_pipeline is missing" do
+ before do
+ project.last_pipeline.delete
+ end
+
+ it_behaves_like 'hidden pipeline status'
+ end
end
context 'last push widget', :use_clean_rails_memory_store_caching do
diff --git a/spec/features/discussion_comments/commit_spec.rb b/spec/features/discussion_comments/commit_spec.rb
index 5a744e43bb6..32c0ba2a9a7 100644
--- a/spec/features/discussion_comments/commit_spec.rb
+++ b/spec/features/discussion_comments/commit_spec.rb
@@ -24,7 +24,7 @@ RSpec.describe 'Thread Comments Commit', :js do
expect(page).to have_css('.js-note-emoji')
end
- it 'adds award to the correct note', quarantine: 'https://gitlab.com/gitlab-org/gitlab/issues/207973' do
+ it 'adds award to the correct note', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/207973' do
find("#note_#{commit_discussion_note2.id} .js-note-emoji").click
first('.emoji-menu .js-emoji-btn').click
diff --git a/spec/features/groups/container_registry_spec.rb b/spec/features/groups/container_registry_spec.rb
index 87ef2131211..acac8724edf 100644
--- a/spec/features/groups/container_registry_spec.rb
+++ b/spec/features/groups/container_registry_spec.rb
@@ -82,7 +82,7 @@ RSpec.describe 'Container Registry', :js do
expect(service).to receive(:execute).with(container_repository) { { status: :success } }
expect(Projects::ContainerRepository::DeleteTagsService).to receive(:new).with(container_repository.project, user, tags: ['latest']) { service }
- first('[data-testid="singleDeleteButton"]').click
+ first('[data-testid="single-delete-button"]').click
expect(find('.modal .modal-title')).to have_content _('Remove tag')
find('.modal .modal-footer .btn-danger').click
end
diff --git a/spec/features/groups/empty_states_spec.rb b/spec/features/groups/empty_states_spec.rb
index d76cf993004..aaa59108b95 100644
--- a/spec/features/groups/empty_states_spec.rb
+++ b/spec/features/groups/empty_states_spec.rb
@@ -7,6 +7,8 @@ RSpec.describe 'Group empty states' do
let(:user) { create(:group_member, :developer, user: create(:user), group: group ).user }
before do
+ stub_feature_flags(vue_issuables_list: false)
+
sign_in(user)
end
diff --git a/spec/features/groups/import_export/import_file_spec.rb b/spec/features/groups/import_export/import_file_spec.rb
index 577198ef3f1..ee4f2740f9f 100644
--- a/spec/features/groups/import_export/import_file_spec.rb
+++ b/spec/features/groups/import_export/import_file_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Import/Export - Group Import', :js do
+RSpec.describe 'Import/Export - Group Import', :js do
let_it_be(:user) { create(:user) }
let_it_be(:import_path) { "#{Dir.tmpdir}/group_import_spec" }
diff --git a/spec/features/groups/members/manage_members_spec.rb b/spec/features/groups/members/manage_members_spec.rb
index e29d8fd651e..99846ecee27 100644
--- a/spec/features/groups/members/manage_members_spec.rb
+++ b/spec/features/groups/members/manage_members_spec.rb
@@ -68,9 +68,12 @@ RSpec.describe 'Groups > Members > Manage members' do
visit group_group_members_path(group)
- accept_confirm do
- find(:css, '.project-members-page li', text: user2.name).find(:css, 'a.btn-remove').click
- end
+ # Open modal
+ find(:css, '.project-members-page li', text: user2.name).find(:css, 'button.btn-remove').click
+
+ expect(page).to have_unchecked_field 'Also unassign this user from related issues and merge requests'
+
+ click_on('Remove member')
wait_for_requests
diff --git a/spec/features/groups/navbar_spec.rb b/spec/features/groups/navbar_spec.rb
index cfa1f3338a1..06ff33ff0eb 100644
--- a/spec/features/groups/navbar_spec.rb
+++ b/spec/features/groups/navbar_spec.rb
@@ -47,6 +47,7 @@ RSpec.describe 'Group navbar' do
before do
stub_feature_flags(group_push_rules: false)
stub_feature_flags(group_iterations: false)
+ stub_feature_flags(group_wiki: false)
group.add_maintainer(user)
sign_in(user)
end
diff --git a/spec/features/groups_spec.rb b/spec/features/groups_spec.rb
index 78a35fe1d3f..8104ff3f987 100644
--- a/spec/features/groups_spec.rb
+++ b/spec/features/groups_spec.rb
@@ -68,6 +68,35 @@ RSpec.describe 'Group' do
end
end
+ describe 'real-time group url validation', :js do
+ it 'shows a message if group url is available' do
+ fill_in 'group_path', with: 'az'
+ wait_for_requests
+
+ expect(page).to have_content('Group path is available')
+ end
+
+ it 'shows an error if group url is taken' do
+ fill_in 'group_path', with: user.username
+ wait_for_requests
+
+ expect(page).to have_content('Group path is already taken')
+ end
+
+ it 'does not break after an invalid form submit' do
+ fill_in 'group_name', with: 'MyGroup'
+ fill_in 'group_path', with: 'z'
+ click_button 'Create group'
+
+ expect(page).to have_content('Group URL is too short')
+
+ fill_in 'group_path', with: 'az'
+ wait_for_requests
+
+ expect(page).to have_content('Group path is available')
+ end
+ end
+
describe 'Mattermost team creation' do
before do
stub_mattermost_setting(enabled: mattermost_enabled)
diff --git a/spec/features/invites_spec.rb b/spec/features/invites_spec.rb
index f85b4b78e35..d91fae5cdfd 100644
--- a/spec/features/invites_spec.rb
+++ b/spec/features/invites_spec.rb
@@ -2,8 +2,8 @@
require 'spec_helper'
-RSpec.describe 'Invites' do
- let(:user) { create(:user) }
+RSpec.describe 'Invites', :aggregate_failures do
+ let(:user) { create(:user, email: 'user@example.com') }
let(:owner) { create(:user, name: 'John Doe') }
let(:group) { create(:group, name: 'Owned') }
let(:project) { create(:project, :repository, namespace: group) }
@@ -11,7 +11,7 @@ RSpec.describe 'Invites' do
before do
project.add_maintainer(owner)
- group.add_user(owner, Gitlab::Access::OWNER)
+ group.add_owner(owner)
group.add_developer('user@example.com', owner)
group_invite.generate_invite_token!
end
@@ -23,12 +23,12 @@ RSpec.describe 'Invites' do
end
def fill_in_sign_up_form(new_user)
- fill_in 'new_user_name', with: new_user.name
- fill_in 'new_user_username', with: new_user.username
- fill_in 'new_user_email', with: new_user.email
- fill_in 'new_user_email_confirmation', with: new_user.email
- fill_in 'new_user_password', with: new_user.password
- click_button "Register"
+ fill_in 'new_user_name', with: new_user.name
+ fill_in 'new_user_username', with: new_user.username
+ fill_in 'new_user_email', with: new_user.email
+ fill_in 'new_user_email_confirmation', with: new_user.email
+ fill_in 'new_user_password', with: new_user.password
+ click_button 'Register'
end
def fill_in_sign_in_form(user)
@@ -48,19 +48,15 @@ RSpec.describe 'Invites' do
expect(page).to have_content('To accept this invitation, sign in')
end
- it 'sign in and redirects to invitation page' do
+ it 'sign in, grants access and redirects to group page' do
fill_in_sign_in_form(user)
- expect(current_path).to eq(invite_path(group_invite.raw_invite_token))
- expect(page).to have_content(
- 'You have been invited by John Doe to join group Owned as Developer.'
- )
- expect(page).to have_link('Accept invitation')
- expect(page).to have_link('Decline')
+ expect(current_path).to eq(group_path(group))
+ expect(page).to have_content('You have been granted Developer access to group Owned.')
end
end
- context 'when signed in as an exists member' do
+ context 'when signed in as an existing member' do
before do
sign_in(owner)
end
@@ -71,166 +67,166 @@ RSpec.describe 'Invites' do
end
end
- describe 'accepting the invitation' do
- before do
- sign_in(user)
- visit invite_path(group_invite.raw_invite_token)
- end
-
- it 'grants access and redirects to group page' do
- page.click_link 'Accept invitation'
- expect(current_path).to eq(group_path(group))
- expect(page).to have_content(
- 'You have been granted Developer access to group Owned.'
- )
- end
- end
-
- describe 'declining the application' do
- context 'when signed in' do
- before do
- sign_in(user)
- visit invite_path(group_invite.raw_invite_token)
- end
-
- it 'declines application and redirects to dashboard' do
- page.click_link 'Decline'
- expect(current_path).to eq(dashboard_projects_path)
- expect(page).to have_content(
- 'You have declined the invitation to join group Owned.'
- )
- end
- end
-
- context 'when signed out' do
- before do
- visit decline_invite_path(group_invite.raw_invite_token)
- end
-
- it 'declines application and redirects to sign in page' do
- expect(current_path).to eq(new_user_session_path)
- expect(page).to have_content(
- 'You have declined the invitation to join group Owned.'
- )
- end
- end
- end
-
- describe 'invite an user using their email address' do
+ context 'when inviting a user using their email address' do
let(:new_user) { build_stubbed(:user) }
let(:invite_email) { new_user.email }
let(:group_invite) { create(:group_member, :invited, group: group, invite_email: invite_email) }
let!(:project_invite) { create(:project_member, :invited, project: project, invite_email: invite_email) }
- before do
- stub_application_setting(send_user_confirmation_email: send_email_confirmation)
- visit invite_path(group_invite.raw_invite_token)
- end
-
- context 'email confirmation disabled' do
- let(:send_email_confirmation) { false }
-
- it 'signs up and redirects to the dashboard page with all the projects/groups invitations automatically accepted' do
- fill_in_sign_up_form(new_user)
-
- expect(current_path).to eq(dashboard_projects_path)
- expect(page).to have_content(project.full_name)
-
- visit group_path(group)
-
- expect(page).to have_content(group.full_name)
+ context 'when user has not signed in yet' do
+ before do
+ stub_application_setting(send_user_confirmation_email: send_email_confirmation)
+ visit invite_path(group_invite.raw_invite_token)
end
- context 'the user sign-up using a different email address' do
- let(:invite_email) { build_stubbed(:user).email }
+ context 'email confirmation disabled' do
+ let(:send_email_confirmation) { false }
- it 'signs up and redirects to the invitation page' do
+ it 'signs up and redirects to the dashboard page with all the projects/groups invitations automatically accepted' do
fill_in_sign_up_form(new_user)
- expect(current_path).to eq(invite_path(group_invite.raw_invite_token))
- end
- end
- end
-
- context 'email confirmation enabled' do
- let(:send_email_confirmation) { true }
-
- context 'when soft email confirmation is not enabled' do
- before do
- allow(User).to receive(:allow_unconfirmed_access_for).and_return 0
- end
-
- it 'signs up and redirects to root page with all the project/groups invitation automatically accepted' do
- fill_in_sign_up_form(new_user)
- confirm_email(new_user)
- fill_in_sign_in_form(new_user)
-
- expect(current_path).to eq(root_path)
+ expect(current_path).to eq(dashboard_projects_path)
expect(page).to have_content(project.full_name)
visit group_path(group)
expect(page).to have_content(group.full_name)
end
- end
- context 'when soft email confirmation is enabled' do
- before do
- allow(User).to receive(:allow_unconfirmed_access_for).and_return 2.days
- end
+ context 'the user sign-up using a different email address' do
+ let(:invite_email) { build_stubbed(:user).email }
- it 'signs up and redirects to root page with all the project/groups invitation automatically accepted' do
- fill_in_sign_up_form(new_user)
- confirm_email(new_user)
-
- expect(current_path).to eq(root_path)
- expect(page).to have_content(project.full_name)
-
- visit group_path(group)
+ it 'signs up and redirects to the invitation page' do
+ fill_in_sign_up_form(new_user)
- expect(page).to have_content(group.full_name)
+ expect(current_path).to eq(invite_path(group_invite.raw_invite_token))
+ end
end
end
- it "doesn't accept invitations until the user confirms their email" do
- fill_in_sign_up_form(new_user)
- sign_in(owner)
-
- visit project_project_members_path(project)
- expect(page).to have_content 'Invited'
- end
-
- context 'the user sign-up using a different email address' do
- let(:invite_email) { build_stubbed(:user).email }
+ context 'email confirmation enabled' do
+ let(:send_email_confirmation) { true }
context 'when soft email confirmation is not enabled' do
before do
- stub_feature_flags(soft_email_confirmation: false)
allow(User).to receive(:allow_unconfirmed_access_for).and_return 0
end
- it 'signs up and redirects to the invitation page' do
+ it 'signs up and redirects to root page with all the project/groups invitation automatically accepted' do
fill_in_sign_up_form(new_user)
confirm_email(new_user)
fill_in_sign_in_form(new_user)
- expect(current_path).to eq(invite_path(group_invite.raw_invite_token))
+ expect(current_path).to eq(root_path)
+ expect(page).to have_content(project.full_name)
+
+ visit group_path(group)
+
+ expect(page).to have_content(group.full_name)
end
end
context 'when soft email confirmation is enabled' do
before do
- stub_feature_flags(soft_email_confirmation: true)
allow(User).to receive(:allow_unconfirmed_access_for).and_return 2.days
end
- it 'signs up and redirects to the invitation page' do
+ it 'signs up and redirects to root page with all the project/groups invitation automatically accepted' do
fill_in_sign_up_form(new_user)
+ confirm_email(new_user)
- expect(current_path).to eq(invite_path(group_invite.raw_invite_token))
+ expect(current_path).to eq(root_path)
+ expect(page).to have_content(project.full_name)
+
+ visit group_path(group)
+
+ expect(page).to have_content(group.full_name)
+ end
+ end
+
+ it "doesn't accept invitations until the user confirms their email" do
+ fill_in_sign_up_form(new_user)
+ sign_in(owner)
+
+ visit project_project_members_path(project)
+ expect(page).to have_content 'Invited'
+ end
+
+ context 'the user sign-up using a different email address' do
+ let(:invite_email) { build_stubbed(:user).email }
+
+ context 'when soft email confirmation is not enabled' do
+ before do
+ stub_feature_flags(soft_email_confirmation: false)
+ allow(User).to receive(:allow_unconfirmed_access_for).and_return 0
+ end
+
+ it 'signs up and redirects to the invitation page' do
+ fill_in_sign_up_form(new_user)
+ confirm_email(new_user)
+ fill_in_sign_in_form(new_user)
+
+ expect(current_path).to eq(invite_path(group_invite.raw_invite_token))
+ end
+ end
+
+ context 'when soft email confirmation is enabled' do
+ before do
+ stub_feature_flags(soft_email_confirmation: true)
+ allow(User).to receive(:allow_unconfirmed_access_for).and_return 2.days
+ end
+
+ it 'signs up and redirects to the invitation page' do
+ fill_in_sign_up_form(new_user)
+
+ expect(current_path).to eq(invite_path(group_invite.raw_invite_token))
+ end
end
end
end
end
+
+ context 'when declining the invitation' do
+ let(:send_email_confirmation) { true }
+
+ context 'when signed in' do
+ before do
+ sign_in(user)
+ visit invite_path(group_invite.raw_invite_token)
+ end
+
+ it 'declines application and redirects to dashboard' do
+ page.click_link 'Decline'
+ expect(current_path).to eq(dashboard_projects_path)
+ expect(page).to have_content('You have declined the invitation to join group Owned.')
+ end
+ end
+
+ context 'when signed out' do
+ before do
+ visit decline_invite_path(group_invite.raw_invite_token)
+ end
+
+ it 'declines application and redirects to sign in page' do
+ expect(current_path).to eq(new_user_session_path)
+ expect(page).to have_content('You have declined the invitation to join group Owned.')
+ end
+ end
+ end
+
+ context 'when accepting the invitation' do
+ let(:send_email_confirmation) { true }
+
+ before do
+ sign_in(user)
+ visit invite_path(group_invite.raw_invite_token)
+ end
+
+ it 'grants access and redirects to group page' do
+ page.click_link 'Accept invitation'
+ expect(current_path).to eq(group_path(group))
+ expect(page).to have_content('You have been granted Owner access to group Owned.')
+ end
+ end
end
end
diff --git a/spec/features/issuables/close_reopen_report_toggle_spec.rb b/spec/features/issuables/close_reopen_report_toggle_spec.rb
index cf3028ec4c9..f442b25f593 100644
--- a/spec/features/issuables/close_reopen_report_toggle_spec.rb
+++ b/spec/features/issuables/close_reopen_report_toggle_spec.rb
@@ -10,7 +10,7 @@ RSpec.describe 'Issuables Close/Reopen/Report toggle' do
let(:human_model_name) { issuable.model_name.human.downcase }
it 'shows toggle' do
- expect(page).to have_link("Close #{human_model_name}")
+ expect(page).to have_button("Close #{human_model_name}")
expect(page).to have_selector('.issuable-close-dropdown')
end
@@ -63,7 +63,7 @@ RSpec.describe 'Issuables Close/Reopen/Report toggle' do
let(:issuable) { create(:issue, :closed, :locked, project: project) }
it 'hides the reopen button' do
- expect(page).not_to have_link('Reopen issue')
+ expect(page).not_to have_button('Reopen issue')
end
context 'when the issue author is the current user' do
@@ -72,7 +72,7 @@ RSpec.describe 'Issuables Close/Reopen/Report toggle' do
end
it 'hides the reopen button' do
- expect(page).not_to have_link('Reopen issue')
+ expect(page).not_to have_button('Reopen issue')
end
end
end
@@ -91,8 +91,8 @@ RSpec.describe 'Issuables Close/Reopen/Report toggle' do
it 'only shows the `Report abuse` and `New issue` buttons' do
expect(page).to have_link('Report abuse')
expect(page).to have_link('New issue')
- expect(page).not_to have_link('Close issue')
- expect(page).not_to have_link('Reopen issue')
+ expect(page).not_to have_button('Close issue')
+ expect(page).not_to have_button('Reopen issue')
expect(page).not_to have_link('Edit')
end
end
@@ -120,8 +120,8 @@ RSpec.describe 'Issuables Close/Reopen/Report toggle' do
it 'shows only the `Report abuse` and `Edit` button' do
expect(page).to have_link('Report abuse')
expect(page).to have_link('Edit')
- expect(page).not_to have_link('Close merge request')
- expect(page).not_to have_link('Reopen merge request')
+ expect(page).not_to have_button('Close merge request')
+ expect(page).not_to have_button('Reopen merge request')
end
context 'when the merge request author is the current user' do
@@ -130,8 +130,8 @@ RSpec.describe 'Issuables Close/Reopen/Report toggle' do
it 'shows only the `Edit` button' do
expect(page).to have_link('Edit')
expect(page).not_to have_link('Report abuse')
- expect(page).not_to have_link('Close merge request')
- expect(page).not_to have_link('Reopen merge request')
+ expect(page).not_to have_button('Close merge request')
+ expect(page).not_to have_button('Reopen merge request')
end
end
end
@@ -149,8 +149,8 @@ RSpec.describe 'Issuables Close/Reopen/Report toggle' do
it 'only shows a `Report abuse` button' do
expect(page).to have_link('Report abuse')
- expect(page).not_to have_link('Close merge request')
- expect(page).not_to have_link('Reopen merge request')
+ expect(page).not_to have_button('Close merge request')
+ expect(page).not_to have_button('Reopen merge request')
expect(page).not_to have_link('Edit')
end
end
diff --git a/spec/features/issuables/issuable_list_spec.rb b/spec/features/issuables/issuable_list_spec.rb
index 382a7a9321c..259c09b9d11 100644
--- a/spec/features/issuables/issuable_list_spec.rb
+++ b/spec/features/issuables/issuable_list_spec.rb
@@ -2,13 +2,15 @@
require 'spec_helper'
-RSpec.describe 'issuable list' do
+RSpec.describe 'issuable list', :js do
let(:project) { create(:project) }
let(:user) { create(:user) }
issuable_types = [:issue, :merge_request]
before do
+ stub_feature_flags(vue_issuables_list: false)
+ # something is going on
project.add_user(user, :developer)
sign_in(user)
issuable_types.each { |type| create_issuables(type) }
@@ -26,9 +28,9 @@ RSpec.describe 'issuable list' do
it "counts upvotes, downvotes and notes count for each #{issuable_type.to_s.humanize}" do
visit_issuable_list(issuable_type)
- expect(first('.fa-thumbs-up').find(:xpath, '..')).to have_content(1)
- expect(first('.fa-thumbs-down').find(:xpath, '..')).to have_content(1)
- expect(first('.fa-comments').find(:xpath, '..')).to have_content(2)
+ expect(first('.issuable-upvotes')).to have_content(1)
+ expect(first('.issuable-downvotes')).to have_content(1)
+ expect(first('.issuable-comments')).to have_content(2)
end
it 'sorts labels alphabetically' do
diff --git a/spec/features/issuables/sorting_list_spec.rb b/spec/features/issuables/sorting_list_spec.rb
index 59518723740..ff92fe369d4 100644
--- a/spec/features/issuables/sorting_list_spec.rb
+++ b/spec/features/issuables/sorting_list_spec.rb
@@ -10,6 +10,10 @@ RSpec.describe 'Sort Issuable List' do
let(:first_updated_issuable) { issuables.order_updated_asc.first }
let(:last_updated_issuable) { issuables.order_updated_desc.first }
+ before do
+ stub_feature_flags(vue_issuables_list: false)
+ end
+
context 'for merge requests' do
include MergeRequestHelpers
diff --git a/spec/features/issues/bulk_assignment_labels_spec.rb b/spec/features/issues/bulk_assignment_labels_spec.rb
index 84a786e91a7..91f0e983fa8 100644
--- a/spec/features/issues/bulk_assignment_labels_spec.rb
+++ b/spec/features/issues/bulk_assignment_labels_spec.rb
@@ -5,14 +5,18 @@ require 'spec_helper'
RSpec.describe 'Issues > Labels bulk assignment' do
let(:user) { create(:user) }
let!(:project) { create(:project) }
- let!(:issue1) { create(:issue, project: project, title: "Issue 1") }
- let!(:issue2) { create(:issue, project: project, title: "Issue 2") }
let!(:bug) { create(:label, project: project, title: 'bug') }
let!(:feature) { create(:label, project: project, title: 'feature') }
+ let!(:frontend) { create(:label, project: project, title: 'frontend') }
let!(:wontfix) { create(:label, project: project, title: 'wontfix') }
+ let!(:issue1) { create(:issue, project: project, title: "Issue 1", labels: [frontend]) }
+ let!(:issue2) { create(:issue, project: project, title: "Issue 2") }
context 'as an allowed user', :js do
before do
+ # Make sure that issuables list FF is not turned on.
+ stub_feature_flags(vue_issuables_list: false)
+
project.add_maintainer(user)
sign_in user
@@ -48,11 +52,29 @@ RSpec.describe 'Issues > Labels bulk assignment' do
it do
expect(find("#issue_#{issue1.id}")).to have_content 'bug'
+ expect(find("#issue_#{issue1.id}")).to have_content 'frontend'
expect(find("#issue_#{issue2.id}")).to have_content 'bug'
+ expect(find("#issue_#{issue2.id}")).not_to have_content 'frontend'
end
end
- context 'to a issue' do
+ context 'to some issues' do
+ before do
+ check "selected_issue_#{issue1.id}"
+ check "selected_issue_#{issue2.id}"
+ open_labels_dropdown ['bug']
+ update_issues
+ end
+
+ it do
+ expect(find("#issue_#{issue1.id}")).to have_content 'bug'
+ expect(find("#issue_#{issue1.id}")).to have_content 'frontend'
+ expect(find("#issue_#{issue2.id}")).to have_content 'bug'
+ expect(find("#issue_#{issue2.id}")).not_to have_content 'frontend'
+ end
+ end
+
+ context 'to an issue' do
before do
check "selected_issue_#{issue1.id}"
open_labels_dropdown ['bug']
@@ -61,7 +83,24 @@ RSpec.describe 'Issues > Labels bulk assignment' do
it do
expect(find("#issue_#{issue1.id}")).to have_content 'bug'
+ expect(find("#issue_#{issue1.id}")).to have_content 'frontend'
+ expect(find("#issue_#{issue2.id}")).not_to have_content 'bug'
+ expect(find("#issue_#{issue2.id}")).not_to have_content 'frontend'
+ end
+ end
+
+ context 'to an issue by selecting the label first' do
+ before do
+ open_labels_dropdown ['bug']
+ check "selected_issue_#{issue1.id}"
+ update_issues
+ end
+
+ it do
+ expect(find("#issue_#{issue1.id}")).to have_content 'bug'
+ expect(find("#issue_#{issue1.id}")).to have_content 'frontend'
expect(find("#issue_#{issue2.id}")).not_to have_content 'bug'
+ expect(find("#issue_#{issue2.id}")).not_to have_content 'frontend'
end
end
end
diff --git a/spec/features/issues/filtered_search/filter_issues_spec.rb b/spec/features/issues/filtered_search/filter_issues_spec.rb
index 5b5348d4069..080943da185 100644
--- a/spec/features/issues/filtered_search/filter_issues_spec.rb
+++ b/spec/features/issues/filtered_search/filter_issues_spec.rb
@@ -20,9 +20,7 @@ RSpec.describe 'Filter issues', :js do
let!(:milestone) { create(:milestone, title: "8", project: project, start_date: 2.days.ago) }
def expect_no_issues_list
- page.within '.issues-list' do
- expect(page).to have_no_selector('.issue')
- end
+ expect(page).to have_no_selector('.issue')
end
before do
@@ -88,7 +86,7 @@ RSpec.describe 'Filter issues', :js do
end
it 'does not have the != option' do
- input_filtered_search("label:", submit: false)
+ input_filtered_search("label:", submit: false, extra_space: false)
wait_for_requests
within('#js-dropdown-operator') do
@@ -344,7 +342,7 @@ RSpec.describe 'Filter issues', :js do
context 'issue label clicked' do
it 'filters and displays in search bar' do
- find('.issues-list .issue .issuable-main-info .issuable-info a .gl-label-text', text: multiple_words_label.title).click
+ find('[data-qa-selector="issuable-label"]', text: multiple_words_label.title).click
expect_issues_list_count(1)
expect_tokens([label_token("\"#{multiple_words_label.title}\"")])
diff --git a/spec/features/issues/filtered_search/search_bar_spec.rb b/spec/features/issues/filtered_search/search_bar_spec.rb
index 167fecc5ab1..2a094281133 100644
--- a/spec/features/issues/filtered_search/search_bar_spec.rb
+++ b/spec/features/issues/filtered_search/search_bar_spec.rb
@@ -88,7 +88,7 @@ RSpec.describe 'Search bar', :js do
expect(find('#js-dropdown-hint')).to have_selector('.filter-dropdown .filter-dropdown-item', count: original_size)
end
- it 'resets the dropdown filters', :quarantine do
+ it 'resets the dropdown filters', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/9985' do
filtered_search.click
hint_offset = get_left_style(find('#js-dropdown-hint')['style'])
diff --git a/spec/features/issues/gfm_autocomplete_spec.rb b/spec/features/issues/gfm_autocomplete_spec.rb
index 4a7e1ba99e9..0b2e8013304 100644
--- a/spec/features/issues/gfm_autocomplete_spec.rb
+++ b/spec/features/issues/gfm_autocomplete_spec.rb
@@ -3,455 +3,647 @@
require 'spec_helper'
RSpec.describe 'GFM autocomplete', :js do
- let(:issue_xss_title) { 'This will execute alert<img src=x onerror=alert(2)&lt;img src=x onerror=alert(1)&gt;' }
- let(:user_xss_title) { 'eve <img src=x onerror=alert(2)&lt;img src=x onerror=alert(1)&gt;' }
- let(:label_xss_title) { 'alert label &lt;img src=x onerror="alert(\'Hello xss\');" a' }
- let(:milestone_xss_title) { 'alert milestone &lt;img src=x onerror="alert(\'Hello xss\');" a' }
-
- let(:user_xss) { create(:user, name: user_xss_title, username: 'xss.user') }
- let(:user) { create(:user, name: '💃speciąl someone💃', username: 'someone.special') }
- let(:project) { create(:project) }
- let(:label) { create(:label, project: project, title: 'special+') }
+ let_it_be(:user_xss_title) { 'eve <img src=x onerror=alert(2)&lt;img src=x onerror=alert(1)&gt;' }
+ let_it_be(:user_xss) { create(:user, name: user_xss_title, username: 'xss.user') }
+ let_it_be(:user) { create(:user, name: '💃speciąl someone💃', username: 'someone.special') }
+ let_it_be(:project) { create(:project) }
+ let_it_be(:label) { create(:label, project: project, title: 'special+') }
+
let(:issue) { create(:issue, project: project) }
- before do
+ before_all do
project.add_maintainer(user)
project.add_maintainer(user_xss)
+ end
- sign_in(user)
- visit project_issue_path(project, issue)
+ describe 'when tribute_autocomplete feature flag is off' do
+ before do
+ stub_feature_flags(tribute_autocomplete: false)
- wait_for_requests
- end
+ sign_in(user)
+ visit project_issue_path(project, issue)
- it 'updates issue description with GFM reference' do
- find('.js-issuable-edit').click
+ wait_for_requests
+ end
- wait_for_requests
+ it 'updates issue description with GFM reference' do
+ find('.js-issuable-edit').click
- simulate_input('#issue-description', "@#{user.name[0...3]}")
+ wait_for_requests
- wait_for_requests
+ simulate_input('#issue-description', "@#{user.name[0...3]}")
- find('.atwho-view .cur').click
+ wait_for_requests
- click_button 'Save changes'
+ find('.atwho-view .cur').click
- wait_for_requests
+ click_button 'Save changes'
- expect(find('.description')).to have_content(user.to_reference)
- end
+ wait_for_requests
- it 'opens autocomplete menu when field starts with text' do
- page.within '.timeline-content-form' do
- find('#note-body').native.send_keys('@')
+ expect(find('.description')).to have_content(user.to_reference)
end
- expect(page).to have_selector('.atwho-container')
- end
+ it 'opens quick action autocomplete when updating description' do
+ find('.js-issuable-edit').click
- it 'opens autocomplete menu for Issues when field starts with text with item escaping HTML characters' do
- create(:issue, project: project, title: issue_xss_title)
+ find('#issue-description').native.send_keys('/')
- page.within '.timeline-content-form' do
- find('#note-body').native.send_keys('#')
+ expect(page).to have_selector('.atwho-container')
end
- wait_for_requests
-
- expect(page).to have_selector('.atwho-container')
+ it 'opens autocomplete menu when field starts with text' do
+ page.within '.timeline-content-form' do
+ find('#note-body').native.send_keys('@')
+ end
- page.within '.atwho-container #at-view-issues' do
- expect(page.all('li').first.text).to include(issue_xss_title)
+ expect(page).to have_selector('.atwho-container')
end
- end
- it 'opens autocomplete menu for Username when field starts with text with item escaping HTML characters' do
- page.within '.timeline-content-form' do
- find('#note-body').native.send_keys('@ev')
- end
+ it 'opens autocomplete menu for Issues when field starts with text with item escaping HTML characters' do
+ issue_xss_title = 'This will execute alert<img src=x onerror=alert(2)&lt;img src=x onerror=alert(1)&gt;'
+ create(:issue, project: project, title: issue_xss_title)
+
+ page.within '.timeline-content-form' do
+ find('#note-body').native.send_keys('#')
+ end
- wait_for_requests
+ wait_for_requests
- expect(page).to have_selector('.atwho-container')
+ expect(page).to have_selector('.atwho-container')
- page.within '.atwho-container #at-view-users' do
- expect(find('li').text).to have_content(user_xss.username)
+ page.within '.atwho-container #at-view-issues' do
+ expect(page.all('li').first.text).to include(issue_xss_title)
+ end
end
- end
- it 'opens autocomplete menu for Milestone when field starts with text with item escaping HTML characters' do
- create(:milestone, project: project, title: milestone_xss_title)
+ it 'opens autocomplete menu for Username when field starts with text with item escaping HTML characters' do
+ page.within '.timeline-content-form' do
+ find('#note-body').native.send_keys('@ev')
+ end
+
+ wait_for_requests
+
+ expect(page).to have_selector('.atwho-container')
- page.within '.timeline-content-form' do
- find('#note-body').native.send_keys('%')
+ page.within '.atwho-container #at-view-users' do
+ expect(find('li').text).to have_content(user_xss.username)
+ end
end
- wait_for_requests
+ it 'opens autocomplete menu for Milestone when field starts with text with item escaping HTML characters' do
+ milestone_xss_title = 'alert milestone &lt;img src=x onerror="alert(\'Hello xss\');" a'
+ create(:milestone, project: project, title: milestone_xss_title)
+
+ page.within '.timeline-content-form' do
+ find('#note-body').native.send_keys('%')
+ end
+
+ wait_for_requests
- expect(page).to have_selector('.atwho-container')
+ expect(page).to have_selector('.atwho-container')
- page.within '.atwho-container #at-view-milestones' do
- expect(find('li').text).to have_content('alert milestone')
+ page.within '.atwho-container #at-view-milestones' do
+ expect(find('li').text).to have_content('alert milestone')
+ end
end
- end
- it 'doesnt open autocomplete menu character is prefixed with text' do
- page.within '.timeline-content-form' do
- find('#note-body').native.send_keys('testing')
- find('#note-body').native.send_keys('@')
+ it 'doesnt open autocomplete menu character is prefixed with text' do
+ page.within '.timeline-content-form' do
+ find('#note-body').native.send_keys('testing')
+ find('#note-body').native.send_keys('@')
+ end
+
+ expect(page).not_to have_selector('.atwho-view')
end
- expect(page).not_to have_selector('.atwho-view')
- end
+ it 'doesnt select the first item for non-assignee dropdowns' do
+ page.within '.timeline-content-form' do
+ find('#note-body').native.send_keys(':')
+ end
+
+ expect(page).to have_selector('.atwho-container')
- it 'doesnt select the first item for non-assignee dropdowns' do
- page.within '.timeline-content-form' do
- find('#note-body').native.send_keys(':')
+ wait_for_requests
+
+ expect(find('#at-view-58')).not_to have_selector('.cur:first-of-type')
end
- expect(page).to have_selector('.atwho-container')
+ it 'does not open autocomplete menu when ":" is prefixed by a number and letters' do
+ note = find('#note-body')
- wait_for_requests
+ # Number.
+ page.within '.timeline-content-form' do
+ note.native.send_keys('7:')
+ end
- expect(find('#at-view-58')).not_to have_selector('.cur:first-of-type')
- end
+ expect(page).not_to have_selector('.atwho-view')
- it 'does not open autocomplete menu when ":" is prefixed by a number and letters' do
- note = find('#note-body')
+ # ASCII letter.
+ page.within '.timeline-content-form' do
+ note.set('')
+ note.native.send_keys('w:')
+ end
- # Number.
- page.within '.timeline-content-form' do
- note.native.send_keys('7:')
- end
+ expect(page).not_to have_selector('.atwho-view')
- expect(page).not_to have_selector('.atwho-view')
+ # Non-ASCII letter.
+ page.within '.timeline-content-form' do
+ note.set('')
+ note.native.send_keys('Ð:')
+ end
- # ASCII letter.
- page.within '.timeline-content-form' do
- note.set('')
- note.native.send_keys('w:')
+ expect(page).not_to have_selector('.atwho-view')
end
- expect(page).not_to have_selector('.atwho-view')
+ it 'selects the first item for assignee dropdowns' do
+ page.within '.timeline-content-form' do
+ find('#note-body').native.send_keys('@')
+ end
- # Non-ASCII letter.
- page.within '.timeline-content-form' do
- note.set('')
- note.native.send_keys('Ð:')
- end
+ expect(page).to have_selector('.atwho-container')
- expect(page).not_to have_selector('.atwho-view')
- end
+ wait_for_requests
- it 'selects the first item for assignee dropdowns' do
- page.within '.timeline-content-form' do
- find('#note-body').native.send_keys('@')
+ expect(find('#at-view-users')).to have_selector('.cur:first-of-type')
end
- expect(page).to have_selector('.atwho-container')
+ it 'includes items for assignee dropdowns with non-ASCII characters in name' do
+ page.within '.timeline-content-form' do
+ find('#note-body').native.send_keys('')
+ simulate_input('#note-body', "@#{user.name[0...8]}")
+ end
- wait_for_requests
+ expect(page).to have_selector('.atwho-container')
- expect(find('#at-view-users')).to have_selector('.cur:first-of-type')
- end
+ wait_for_requests
- it 'includes items for assignee dropdowns with non-ASCII characters in name' do
- page.within '.timeline-content-form' do
- find('#note-body').native.send_keys('')
- simulate_input('#note-body', "@#{user.name[0...8]}")
+ expect(find('#at-view-users')).to have_content(user.name)
end
- expect(page).to have_selector('.atwho-container')
+ it 'selects the first item for non-assignee dropdowns if a query is entered' do
+ page.within '.timeline-content-form' do
+ find('#note-body').native.send_keys(':1')
+ end
- wait_for_requests
+ expect(page).to have_selector('.atwho-container')
- expect(find('#at-view-users')).to have_content(user.name)
- end
+ wait_for_requests
- it 'selects the first item for non-assignee dropdowns if a query is entered' do
- page.within '.timeline-content-form' do
- find('#note-body').native.send_keys(':1')
+ expect(find('#at-view-58')).to have_selector('.cur:first-of-type')
end
- expect(page).to have_selector('.atwho-container')
+ context 'if a selected value has special characters' do
+ it 'wraps the result in double quotes' do
+ note = find('#note-body')
+ page.within '.timeline-content-form' do
+ find('#note-body').native.send_keys('')
+ simulate_input('#note-body', "~#{label.title[0]}")
+ end
- wait_for_requests
+ label_item = find('.atwho-view li', text: label.title)
- expect(find('#at-view-58')).to have_selector('.cur:first-of-type')
- end
+ expect_to_wrap(true, label_item, note, label.title)
+ end
- context 'if a selected value has special characters' do
- it 'wraps the result in double quotes' do
- note = find('#note-body')
- page.within '.timeline-content-form' do
- find('#note-body').native.send_keys('')
- simulate_input('#note-body', "~#{label.title[0]}")
+ it "shows dropdown after a new line" do
+ note = find('#note-body')
+ page.within '.timeline-content-form' do
+ note.native.send_keys('test')
+ note.native.send_keys(:enter)
+ note.native.send_keys(:enter)
+ note.native.send_keys('@')
+ end
+
+ expect(page).to have_selector('.atwho-container')
end
- label_item = find('.atwho-view li', text: label.title)
+ it "does not show dropdown when preceded with a special character" do
+ note = find('#note-body')
+ page.within '.timeline-content-form' do
+ note.native.send_keys("@")
+ end
- expect_to_wrap(true, label_item, note, label.title)
- end
+ expect(page).to have_selector('.atwho-container')
- it "shows dropdown after a new line" do
- note = find('#note-body')
- page.within '.timeline-content-form' do
- note.native.send_keys('test')
- note.native.send_keys(:enter)
- note.native.send_keys(:enter)
- note.native.send_keys('@')
+ page.within '.timeline-content-form' do
+ note.native.send_keys("@")
+ end
+
+ expect(page).to have_selector('.atwho-container', visible: false)
end
- expect(page).to have_selector('.atwho-container')
- end
+ it "does not throw an error if no labels exist" do
+ note = find('#note-body')
+ page.within '.timeline-content-form' do
+ note.native.send_keys('~')
+ end
- it "does not show dropdown when preceded with a special character" do
- note = find('#note-body')
- page.within '.timeline-content-form' do
- note.native.send_keys("@")
+ expect(page).to have_selector('.atwho-container', visible: false)
end
- expect(page).to have_selector('.atwho-container')
+ it 'doesn\'t wrap for assignee values' do
+ note = find('#note-body')
+ page.within '.timeline-content-form' do
+ note.native.send_keys("@#{user.username[0]}")
+ end
- page.within '.timeline-content-form' do
- note.native.send_keys("@")
+ user_item = find('.atwho-view li', text: user.username)
+
+ expect_to_wrap(false, user_item, note, user.username)
end
- expect(page).to have_selector('.atwho-container', visible: false)
- end
+ it 'doesn\'t wrap for emoji values' do
+ note = find('#note-body')
+ page.within '.timeline-content-form' do
+ note.native.send_keys(":cartwheel_")
+ end
- it "does not throw an error if no labels exist" do
- note = find('#note-body')
- page.within '.timeline-content-form' do
- note.native.send_keys('~')
+ emoji_item = find('.atwho-view li', text: 'cartwheel_tone1')
+
+ expect_to_wrap(false, emoji_item, note, 'cartwheel_tone1')
end
- expect(page).to have_selector('.atwho-container', visible: false)
- end
+ it 'doesn\'t open autocomplete after non-word character' do
+ page.within '.timeline-content-form' do
+ find('#note-body').native.send_keys("@#{user.username[0..2]}!")
+ end
- it 'doesn\'t wrap for assignee values' do
- note = find('#note-body')
- page.within '.timeline-content-form' do
- note.native.send_keys("@#{user.username[0]}")
+ expect(page).not_to have_selector('.atwho-view')
+ end
+
+ it 'doesn\'t open autocomplete if there is no space before' do
+ page.within '.timeline-content-form' do
+ find('#note-body').native.send_keys("hello:#{user.username[0..2]}")
+ end
+
+ expect(page).not_to have_selector('.atwho-view')
end
- user_item = find('.atwho-view li', text: user.username)
+ it 'triggers autocomplete after selecting a quick action' do
+ note = find('#note-body')
+ page.within '.timeline-content-form' do
+ note.native.send_keys('/as')
+ end
+
+ find('.atwho-view li', text: '/assign')
+ note.native.send_keys(:tab)
- expect_to_wrap(false, user_item, note, user.username)
+ user_item = find('.atwho-view li', text: user.username)
+ expect(user_item).to have_content(user.username)
+ end
end
- it 'doesn\'t wrap for emoji values' do
- note = find('#note-body')
- page.within '.timeline-content-form' do
- note.native.send_keys(":cartwheel_")
+ context 'assignees' do
+ let(:issue_assignee) { create(:issue, project: project) }
+ let(:unassigned_user) { create(:user) }
+
+ before do
+ issue_assignee.update(assignees: [user])
+
+ project.add_maintainer(unassigned_user)
end
- emoji_item = find('.atwho-view li', text: 'cartwheel_tone1')
+ it 'lists users who are currently not assigned to the issue when using /assign' do
+ visit project_issue_path(project, issue_assignee)
- expect_to_wrap(false, emoji_item, note, 'cartwheel_tone1')
- end
+ note = find('#note-body')
+ page.within '.timeline-content-form' do
+ note.native.send_keys('/as')
+ end
- it 'doesn\'t open autocomplete after non-word character' do
- page.within '.timeline-content-form' do
- find('#note-body').native.send_keys("@#{user.username[0..2]}!")
+ find('.atwho-view li', text: '/assign')
+ note.native.send_keys(:tab)
+
+ wait_for_requests
+
+ expect(find('#at-view-users .atwho-view-ul')).not_to have_content(user.username)
+ expect(find('#at-view-users .atwho-view-ul')).to have_content(unassigned_user.username)
end
- expect(page).not_to have_selector('.atwho-view')
+ it 'shows dropdown on new issue form' do
+ visit new_project_issue_path(project)
+
+ textarea = find('#issue_description')
+ textarea.native.send_keys('/ass')
+ find('.atwho-view li', text: '/assign')
+ textarea.native.send_keys(:tab)
+
+ expect(find('#at-view-users .atwho-view-ul')).to have_content(unassigned_user.username)
+ expect(find('#at-view-users .atwho-view-ul')).to have_content(user.username)
+ end
end
- it 'doesn\'t open autocomplete if there is no space before' do
- page.within '.timeline-content-form' do
- find('#note-body').native.send_keys("hello:#{user.username[0..2]}")
+ context 'labels' do
+ it 'opens autocomplete menu for Labels when field starts with text with item escaping HTML characters' do
+ label_xss_title = 'alert label &lt;img src=x onerror="alert(\'Hello xss\');" a'
+ create(:label, project: project, title: label_xss_title)
+
+ note = find('#note-body')
+
+ # It should show all the labels on "~".
+ type(note, '~')
+
+ wait_for_requests
+
+ page.within '.atwho-container #at-view-labels' do
+ expect(find('.atwho-view-ul').text).to have_content('alert label')
+ end
end
- expect(page).not_to have_selector('.atwho-view')
- end
+ it 'allows colons when autocompleting scoped labels' do
+ create(:label, project: project, title: 'scoped:label')
- it 'triggers autocomplete after selecting a quick action' do
- note = find('#note-body')
- page.within '.timeline-content-form' do
- note.native.send_keys('/as')
+ note = find('#note-body')
+ type(note, '~scoped:')
+
+ wait_for_requests
+
+ page.within '.atwho-container #at-view-labels' do
+ expect(find('.atwho-view-ul').text).to have_content('scoped:label')
+ end
end
- find('.atwho-view li', text: '/assign')
- note.native.send_keys(:tab)
+ it 'allows colons when autocompleting scoped labels with double colons' do
+ create(:label, project: project, title: 'scoped::label')
- user_item = find('.atwho-view li', text: user.username)
- expect(user_item).to have_content(user.username)
- end
- end
+ note = find('#note-body')
+ type(note, '~scoped::')
- context 'assignees' do
- let(:issue_assignee) { create(:issue, project: project) }
- let(:unassigned_user) { create(:user) }
+ wait_for_requests
- before do
- issue_assignee.update(assignees: [user])
+ page.within '.atwho-container #at-view-labels' do
+ expect(find('.atwho-view-ul').text).to have_content('scoped::label')
+ end
+ end
+
+ it 'allows spaces when autocompleting multi-word labels' do
+ create(:label, project: project, title: 'Accepting merge requests')
- project.add_maintainer(unassigned_user)
+ note = find('#note-body')
+ type(note, '~Accepting merge')
+
+ wait_for_requests
+
+ page.within '.atwho-container #at-view-labels' do
+ expect(find('.atwho-view-ul').text).to have_content('Accepting merge requests')
+ end
+ end
+
+ it 'only autocompletes the latest label' do
+ create(:label, project: project, title: 'Accepting merge requests')
+ create(:label, project: project, title: 'Accepting job applicants')
+
+ note = find('#note-body')
+ type(note, '~Accepting merge requests foo bar ~Accepting job')
+
+ wait_for_requests
+
+ page.within '.atwho-container #at-view-labels' do
+ expect(find('.atwho-view-ul').text).to have_content('Accepting job applicants')
+ end
+ end
+
+ it 'does not autocomplete labels if no tilde is typed' do
+ create(:label, project: project, title: 'Accepting merge requests')
+
+ note = find('#note-body')
+ type(note, 'Accepting merge')
+
+ wait_for_requests
+
+ expect(page).not_to have_css('.atwho-container #at-view-labels')
+ end
end
- it 'lists users who are currently not assigned to the issue when using /assign' do
- visit project_issue_path(project, issue_assignee)
+ shared_examples 'autocomplete suggestions' do
+ it 'suggests objects correctly' do
+ page.within '.timeline-content-form' do
+ find('#note-body').native.send_keys(object.class.reference_prefix)
+ end
- note = find('#note-body')
- page.within '.timeline-content-form' do
- note.native.send_keys('/as')
+ page.within '.atwho-container' do
+ expect(page).to have_content(object.title)
+
+ find('ul li').click
+ end
+
+ expect(find('.new-note #note-body').value).to include(expected_body)
end
+ end
- find('.atwho-view li', text: '/assign')
- note.native.send_keys(:tab)
+ context 'issues' do
+ let(:object) { issue }
+ let(:expected_body) { object.to_reference }
- wait_for_requests
+ it_behaves_like 'autocomplete suggestions'
+ end
+
+ context 'merge requests' do
+ let(:object) { create(:merge_request, source_project: project) }
+ let(:expected_body) { object.to_reference }
- expect(find('#at-view-users .atwho-view-ul')).not_to have_content(user.username)
- expect(find('#at-view-users .atwho-view-ul')).to have_content(unassigned_user.username)
+ it_behaves_like 'autocomplete suggestions'
end
- it 'shows dropdown on new issue form' do
- visit new_project_issue_path(project)
+ context 'project snippets' do
+ let!(:object) { create(:project_snippet, project: project, title: 'code snippet') }
+ let(:expected_body) { object.to_reference }
- textarea = find('#issue_description')
- textarea.native.send_keys('/ass')
- find('.atwho-view li', text: '/assign')
- textarea.native.send_keys(:tab)
+ it_behaves_like 'autocomplete suggestions'
+ end
+
+ context 'label' do
+ let!(:object) { label }
+ let(:expected_body) { object.title }
+
+ it_behaves_like 'autocomplete suggestions'
+ end
+
+ context 'milestone' do
+ let!(:object) { create(:milestone, project: project) }
+ let(:expected_body) { object.to_reference }
- expect(find('#at-view-users .atwho-view-ul')).to have_content(unassigned_user.username)
- expect(find('#at-view-users .atwho-view-ul')).to have_content(user.username)
+ it_behaves_like 'autocomplete suggestions'
end
end
- context 'labels' do
- it 'opens autocomplete menu for Labels when field starts with text with item escaping HTML characters' do
- create(:label, project: project, title: label_xss_title)
+ describe 'when tribute_autocomplete feature flag is on' do
+ before do
+ stub_feature_flags(tribute_autocomplete: true)
+
+ sign_in(user)
+ visit project_issue_path(project, issue)
- note = find('#note-body')
+ wait_for_requests
+ end
- # It should show all the labels on "~".
- type(note, '~')
+ it 'updates issue description with GFM reference' do
+ find('.js-issuable-edit').click
wait_for_requests
- page.within '.atwho-container #at-view-labels' do
- expect(find('.atwho-view-ul').text).to have_content('alert label')
- end
- end
+ simulate_input('#issue-description', "@#{user.name[0...3]}")
+
+ wait_for_requests
- it 'allows colons when autocompleting scoped labels' do
- create(:label, project: project, title: 'scoped:label')
+ find('.tribute-container .highlight').click
- note = find('#note-body')
- type(note, '~scoped:')
+ click_button 'Save changes'
wait_for_requests
- page.within '.atwho-container #at-view-labels' do
- expect(find('.atwho-view-ul').text).to have_content('scoped:label')
- end
+ expect(find('.description')).to have_content(user.to_reference)
end
- it 'allows colons when autocompleting scoped labels with double colons' do
- create(:label, project: project, title: 'scoped::label')
+ it 'opens autocomplete menu when field starts with text' do
+ page.within '.timeline-content-form' do
+ find('#note-body').native.send_keys('@')
+ end
- note = find('#note-body')
- type(note, '~scoped::')
+ expect(page).to have_selector('.tribute-container')
+ end
+
+ it 'opens autocomplete menu for Username when field starts with text with item escaping HTML characters' do
+ page.within '.timeline-content-form' do
+ find('#note-body').native.send_keys('@ev')
+ end
wait_for_requests
- page.within '.atwho-container #at-view-labels' do
- expect(find('.atwho-view-ul').text).to have_content('scoped::label')
+ expect(page).to have_selector('.tribute-container')
+
+ page.within '.tribute-container ul' do
+ expect(find('li').text).to have_content(user_xss.username)
end
end
- it 'allows spaces when autocompleting multi-word labels' do
- create(:label, project: project, title: 'Accepting merge requests')
+ it 'doesnt open autocomplete menu character is prefixed with text' do
+ page.within '.timeline-content-form' do
+ find('#note-body').native.send_keys('testing')
+ find('#note-body').native.send_keys('@')
+ end
- note = find('#note-body')
- type(note, '~Accepting merge')
+ expect(page).not_to have_selector('.tribute-container')
+ end
+
+ it 'selects the first item for assignee dropdowns' do
+ page.within '.timeline-content-form' do
+ find('#note-body').native.send_keys('@')
+ end
+
+ expect(page).to have_selector('.tribute-container')
wait_for_requests
- page.within '.atwho-container #at-view-labels' do
- expect(find('.atwho-view-ul').text).to have_content('Accepting merge requests')
- end
+ expect(find('.tribute-container ul')).to have_selector('.highlight:first-of-type')
end
- it 'only autocompletes the latest label' do
- create(:label, project: project, title: 'Accepting merge requests')
- create(:label, project: project, title: 'Accepting job applicants')
+ it 'includes items for assignee dropdowns with non-ASCII characters in name' do
+ page.within '.timeline-content-form' do
+ find('#note-body').native.send_keys('')
+ simulate_input('#note-body', "@#{user.name[0...8]}")
+ end
- note = find('#note-body')
- type(note, '~Accepting merge requests foo bar ~Accepting job')
+ expect(page).to have_selector('.tribute-container')
wait_for_requests
- page.within '.atwho-container #at-view-labels' do
- expect(find('.atwho-view-ul').text).to have_content('Accepting job applicants')
- end
+ expect(find('.tribute-container')).to have_content(user.name)
end
- it 'does not autocomplete labels if no tilde is typed' do
- create(:label, project: project, title: 'Accepting merge requests')
+ context 'if a selected value has special characters' do
+ it "shows dropdown after a new line" do
+ note = find('#note-body')
+ page.within '.timeline-content-form' do
+ note.native.send_keys('test')
+ note.native.send_keys(:enter)
+ note.native.send_keys(:enter)
+ note.native.send_keys('@')
+ end
- note = find('#note-body')
- type(note, 'Accepting merge')
+ expect(page).to have_selector('.tribute-container')
+ end
- wait_for_requests
+ it "does not show dropdown when preceded with a special character" do
+ note = find('#note-body')
+ page.within '.timeline-content-form' do
+ note.native.send_keys("@")
+ end
- expect(page).not_to have_css('.atwho-container #at-view-labels')
- end
- end
+ expect(page).to have_selector('.tribute-container')
- shared_examples 'autocomplete suggestions' do
- it 'suggests objects correctly' do
- page.within '.timeline-content-form' do
- find('#note-body').native.send_keys(object.class.reference_prefix)
+ page.within '.timeline-content-form' do
+ note.native.send_keys("@")
+ end
+
+ expect(page).to have_selector('.tribute-container', visible: false)
end
- page.within '.atwho-container' do
- expect(page).to have_content(object.title)
+ it 'doesn\'t wrap for assignee values' do
+ note = find('#note-body')
+ page.within '.timeline-content-form' do
+ note.native.send_keys("@#{user.username[0]}")
+ end
- find('ul li').click
+ user_item = find('.tribute-container li', text: user.username)
+
+ expect_to_wrap(false, user_item, note, user.username)
end
- expect(find('.new-note #note-body').value).to include(expected_body)
- end
- end
+ it 'doesn\'t open autocomplete after non-word character' do
+ page.within '.timeline-content-form' do
+ find('#note-body').native.send_keys("@#{user.username[0..2]}!")
+ end
- context 'issues' do
- let(:object) { issue }
- let(:expected_body) { object.to_reference }
+ expect(page).not_to have_selector('.tribute-container')
+ end
- it_behaves_like 'autocomplete suggestions'
- end
+ it 'triggers autocomplete after selecting a quick action' do
+ note = find('#note-body')
+ page.within '.timeline-content-form' do
+ note.native.send_keys('/as')
+ end
- context 'merge requests' do
- let(:object) { create(:merge_request, source_project: project) }
- let(:expected_body) { object.to_reference }
+ find('.atwho-view li', text: '/assign')
+ note.native.send_keys(:tab)
+ note.native.send_keys(:right)
- it_behaves_like 'autocomplete suggestions'
- end
+ wait_for_requests
- context 'project snippets' do
- let!(:object) { create(:project_snippet, project: project, title: 'code snippet') }
- let(:expected_body) { object.to_reference }
+ user_item = find('.tribute-container li', text: user.username)
+ expect(user_item).to have_content(user.username)
+ end
+ end
- it_behaves_like 'autocomplete suggestions'
- end
+ context 'assignees' do
+ let(:issue_assignee) { create(:issue, project: project) }
+ let(:unassigned_user) { create(:user) }
- context 'label' do
- let!(:object) { label }
- let(:expected_body) { object.title }
+ before do
+ issue_assignee.update(assignees: [user])
- it_behaves_like 'autocomplete suggestions'
- end
+ project.add_maintainer(unassigned_user)
+ end
+
+ it 'lists users who are currently not assigned to the issue when using /assign' do
+ visit project_issue_path(project, issue_assignee)
+
+ note = find('#note-body')
+ page.within '.timeline-content-form' do
+ note.native.send_keys('/as')
+ end
- context 'milestone' do
- let!(:object) { create(:milestone, project: project) }
- let(:expected_body) { object.to_reference }
+ find('.atwho-view li', text: '/assign')
+ note.native.send_keys(:tab)
+ note.native.send_keys(:right)
- it_behaves_like 'autocomplete suggestions'
+ wait_for_requests
+
+ expect(find('.tribute-container ul')).not_to have_content(user.username)
+ expect(find('.tribute-container ul')).to have_content(unassigned_user.username)
+ end
+ end
end
private
diff --git a/spec/features/issues/issue_detail_spec.rb b/spec/features/issues/issue_detail_spec.rb
index ab319daec71..9879703c8bf 100644
--- a/spec/features/issues/issue_detail_spec.rb
+++ b/spec/features/issues/issue_detail_spec.rb
@@ -28,7 +28,7 @@ RSpec.describe 'Issue Detail', :js do
visit project_issue_path(project, issue)
end
- it 'encodes the description to prevent xss issues', quarantine: 'https://gitlab.com/gitlab-org/gitlab/issues/207951' do
+ it 'encodes the description to prevent xss issues', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/207951' do
page.within('.issuable-details .detail-page-description') do
image = find('img.js-lazy-loaded')
diff --git a/spec/features/issues/issue_sidebar_spec.rb b/spec/features/issues/issue_sidebar_spec.rb
index 9e4362bf0e5..ecda80f2483 100644
--- a/spec/features/issues/issue_sidebar_spec.rb
+++ b/spec/features/issues/issue_sidebar_spec.rb
@@ -195,7 +195,7 @@ RSpec.describe 'Issue Sidebar' do
end
end
- context 'creating a project label', :js, :quarantine do
+ context 'creating a project label', :js, quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/27992' do
before do
page.within('.block.labels') do
click_link 'Create project'
diff --git a/spec/features/issues/move_spec.rb b/spec/features/issues/move_spec.rb
index f3a6655f397..ee2fbf0865e 100644
--- a/spec/features/issues/move_spec.rb
+++ b/spec/features/issues/move_spec.rb
@@ -97,6 +97,45 @@ RSpec.describe 'issue move to another project' do
end
end
+ context 'service desk issue moved to a project with service desk disabled', :js do
+ let(:project_title) { 'service desk disabled project' }
+ let(:warning_selector) { '.js-alert-moved-from-service-desk-warning' }
+ let(:namespace) { create(:namespace) }
+ let(:regular_project) { create(:project, title: project_title, service_desk_enabled: false) }
+ let(:service_desk_project) { build(:project, :private, namespace: namespace, service_desk_enabled: true) }
+ let(:service_desk_issue) { create(:issue, project: service_desk_project, author: ::User.support_bot) }
+
+ before do
+ allow(Gitlab).to receive(:com?).and_return(true)
+ allow(Gitlab::IncomingEmail).to receive(:enabled?).and_return(true)
+ allow(Gitlab::IncomingEmail).to receive(:supports_wildcard?).and_return(true)
+
+ regular_project.add_reporter(user)
+ service_desk_project.add_reporter(user)
+
+ visit issue_path(service_desk_issue)
+
+ find('.js-move-issue').click
+ wait_for_requests
+ find('.js-move-issue-dropdown-item', text: project_title).click
+ find('.js-move-issue-confirmation-button').click
+ end
+
+ it 'shows an alert after being moved' do
+ expect(page).to have_content('This project does not have Service Desk enabled')
+ end
+
+ it 'does not show an alert after being dismissed' do
+ find("#{warning_selector} .js-close").click
+
+ expect(page).to have_no_selector(warning_selector)
+
+ page.refresh
+
+ expect(page).to have_no_selector(warning_selector)
+ end
+ end
+
def issue_path(issue)
project_issue_path(issue.project, issue)
end
diff --git a/spec/features/issues/service_desk_spec.rb b/spec/features/issues/service_desk_spec.rb
new file mode 100644
index 00000000000..0995aa11654
--- /dev/null
+++ b/spec/features/issues/service_desk_spec.rb
@@ -0,0 +1,163 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'Service Desk Issue Tracker', :js do
+ let(:project) { create(:project, :private, service_desk_enabled: true) }
+ let(:user) { create(:user) }
+
+ before do
+ stub_feature_flags(vue_issuables_list: false)
+
+ allow(Gitlab::IncomingEmail).to receive(:enabled?).and_return(true)
+ allow(Gitlab::IncomingEmail).to receive(:supports_wildcard?).and_return(true)
+
+ project.add_maintainer(user)
+ sign_in(user)
+ end
+
+ describe 'navigation to service desk' do
+ before do
+ visit project_path(project)
+ find('.sidebar-top-level-items .shortcuts-issues').click
+ find('.sidebar-sub-level-items a[title="Service Desk"]').click
+ end
+
+ it 'can navigate to the service desk from link in the sidebar' do
+ expect(page).to have_content('Use Service Desk to connect with your users')
+ end
+ end
+
+ describe 'issues list' do
+ context 'when service desk is misconfigured' do
+ before do
+ allow(Gitlab::ServiceDesk).to receive(:supported?).and_return(false)
+ visit service_desk_project_issues_path(project)
+ end
+
+ it 'shows a message to say the configuration is incomplete' do
+ expect(page).to have_css('.empty-state')
+ expect(page).to have_text('Service Desk is enabled but not yet active')
+ expect(page).to have_text('You must set up incoming email before it becomes active')
+ expect(page).to have_link('More information', href: help_page_path('administration/incoming_email', anchor: 'set-it-up'))
+ end
+ end
+
+ context 'when service desk has not been activated' do
+ let(:project_without_service_desk) { create(:project, :private, service_desk_enabled: false) }
+
+ describe 'service desk info content' do
+ context 'when user has permissions to edit project settings' do
+ before do
+ project_without_service_desk.add_maintainer(user)
+ visit service_desk_project_issues_path(project_without_service_desk)
+ end
+
+ it 'displays the large info box, documentation, and a button to configure' do
+ aggregate_failures do
+ expect(page).to have_css('.empty-state')
+ expect(page).to have_link('Read more', href: help_page_path('user/project/service_desk'))
+ expect(page).to have_link('Turn on Service Desk')
+ end
+ end
+ end
+
+ context 'when user does not have permission to edit project settings' do
+ before do
+ project_without_service_desk.add_guest(user)
+ visit service_desk_project_issues_path(project_without_service_desk)
+ end
+
+ it 'does not show a button configure service desk' do
+ expect(page).not_to have_link('Turn on Service Desk')
+ end
+ end
+ end
+ end
+
+ context 'when service desk has been activated' do
+ context 'when there are no issues' do
+ describe 'service desk info content' do
+ before do
+ visit service_desk_project_issues_path(project)
+ end
+
+ it 'displays the large info box, documentation, and the address' do
+ aggregate_failures do
+ expect(page).to have_css('.empty-state')
+ expect(page).to have_link('Read more', href: help_page_path('user/project/service_desk'))
+ expect(page).not_to have_link('Turn on Service Desk')
+ expect(page).to have_content(project.service_desk_address)
+ end
+ end
+
+ context 'when user does not have permission to edit project settings' do
+ before do
+ user_2 = create(:user)
+
+ project.add_guest(user_2)
+ sign_in(user_2)
+ visit service_desk_project_issues_path(project)
+ end
+
+ it 'displays the large info box and the documentation link' do
+ aggregate_failures do
+ expect(page).to have_css('.empty-state')
+ expect(page).to have_link('Read more', href: help_page_path('user/project/service_desk'))
+ expect(page).not_to have_link('Turn on Service Desk')
+ expect(page).not_to have_content(project.service_desk_address)
+ end
+ end
+ end
+ end
+ end
+
+ context 'when there are issues' do
+ let(:support_bot) { User.support_bot }
+ let(:other_user) { create(:user) }
+ let!(:service_desk_issue) { create(:issue, project: project, author: support_bot) }
+ let!(:other_user_issue) { create(:issue, project: project, author: other_user) }
+
+ describe 'service desk info content' do
+ before do
+ visit service_desk_project_issues_path(project)
+ end
+
+ it 'displays the small info box, documentation, a button to configure service desk, and the address' do
+ aggregate_failures do
+ expect(page).to have_css('.non-empty-state')
+ expect(page).to have_link('Read more', href: help_page_path('user/project/service_desk'))
+ expect(page).not_to have_link('Turn on Service Desk')
+ expect(page).to have_content(project.service_desk_address)
+ end
+ end
+ end
+
+ describe 'issues list' do
+ before do
+ visit service_desk_project_issues_path(project)
+ end
+
+ it 'only displays issues created by support bot' do
+ expect(page).to have_selector('.issues-list .issue', count: 1)
+ end
+ end
+
+ describe 'search box' do
+ before do
+ visit service_desk_project_issues_path(project)
+ end
+
+ it 'adds hidden support bot author token' do
+ expect(page).to have_selector('.filtered-search-token .value', text: 'Support Bot', visible: false)
+ end
+
+ it 'support bot author token cannot be deleted' do
+ find('.input-token .filtered-search').native.send_key(:backspace)
+ expect(page).to have_selector('.js-visual-token', count: 1)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/spec/features/issues/update_issues_spec.rb b/spec/features/issues/update_issues_spec.rb
index f8385f183d2..dfe3a1bf1b3 100644
--- a/spec/features/issues/update_issues_spec.rb
+++ b/spec/features/issues/update_issues_spec.rb
@@ -8,6 +8,7 @@ RSpec.describe 'Multiple issue updating from issues#index', :js do
let!(:user) { create(:user)}
before do
+ stub_feature_flags(vue_issuables_list: false)
project.add_maintainer(user)
sign_in(user)
end
diff --git a/spec/features/issues/user_creates_branch_and_merge_request_spec.rb b/spec/features/issues/user_creates_branch_and_merge_request_spec.rb
index a546fb3e85b..617eac88973 100644
--- a/spec/features/issues/user_creates_branch_and_merge_request_spec.rb
+++ b/spec/features/issues/user_creates_branch_and_merge_request_spec.rb
@@ -31,7 +31,7 @@ RSpec.describe 'User creates branch and merge request on issue page', :js do
end
# In order to improve tests performance, all UI checks are placed in this test.
- it 'shows elements', :quarantine do
+ it 'shows elements', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/27993' do
button_create_merge_request = find('.js-create-merge-request')
button_toggle_dropdown = find('.create-mr-dropdown-wrap .dropdown-toggle')
@@ -71,7 +71,7 @@ RSpec.describe 'User creates branch and merge request on issue page', :js do
perform_enqueued_jobs do
select_dropdown_option('create-mr')
- expect(page).to have_content('WIP: Resolve "Cherry-Coloured Funk"')
+ expect(page).to have_content('Draft: Resolve "Cherry-Coloured Funk"')
expect(current_path).to eq(project_merge_request_path(project, MergeRequest.first))
wait_for_requests
@@ -100,7 +100,7 @@ RSpec.describe 'User creates branch and merge request on issue page', :js do
perform_enqueued_jobs do
select_dropdown_option('create-mr', branch_name)
- expect(page).to have_content('WIP: Resolve "Cherry-Coloured Funk"')
+ expect(page).to have_content('Draft: Resolve "Cherry-Coloured Funk"')
expect(page).to have_content('Request to merge custom-branch-name into')
expect(current_path).to eq(project_merge_request_path(project, MergeRequest.first))
@@ -141,7 +141,7 @@ RSpec.describe 'User creates branch and merge request on issue page', :js do
visit project_issue_path(project, issue)
end
- it 'disables the create branch button', :quarantine do
+ it 'disables the create branch button', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/27985' do
expect(page).to have_css('.create-mr-dropdown-wrap .unavailable:not(.hidden)')
expect(page).to have_css('.create-mr-dropdown-wrap .available.hidden', visible: false)
expect(page).to have_content /Related merge requests/
diff --git a/spec/features/issues/user_filters_issues_spec.rb b/spec/features/issues/user_filters_issues_spec.rb
index 20ad47b111a..54a600910ef 100644
--- a/spec/features/issues/user_filters_issues_spec.rb
+++ b/spec/features/issues/user_filters_issues_spec.rb
@@ -7,6 +7,8 @@ RSpec.describe 'User filters issues' do
let_it_be(:project) { create(:project_empty_repo, :public) }
before do
+ stub_feature_flags(vue_issuables_list: false)
+
%w[foobar barbaz].each do |title|
create(:issue,
author: user,
diff --git a/spec/features/issues/user_interacts_with_awards_spec.rb b/spec/features/issues/user_interacts_with_awards_spec.rb
index 7b7e087a6d6..35d9db68d32 100644
--- a/spec/features/issues/user_interacts_with_awards_spec.rb
+++ b/spec/features/issues/user_interacts_with_awards_spec.rb
@@ -16,7 +16,7 @@ RSpec.describe 'User interacts with awards' do
visit(project_issue_path(project, issue))
end
- it 'toggles the thumbsup award emoji', :quarantine do
+ it 'toggles the thumbsup award emoji', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/27959' do
page.within('.awards') do
thumbsup = page.first('.award-control')
thumbsup.click
@@ -77,7 +77,7 @@ RSpec.describe 'User interacts with awards' do
end
end
- it 'shows the list of award emoji categories', :quarantine do
+ it 'shows the list of award emoji categories', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/27991' do
page.within('.awards') do
page.find('.js-add-award').click
end
diff --git a/spec/features/issues/user_sees_breadcrumb_links_spec.rb b/spec/features/issues/user_sees_breadcrumb_links_spec.rb
index 2660101c330..f5793758a9b 100644
--- a/spec/features/issues/user_sees_breadcrumb_links_spec.rb
+++ b/spec/features/issues/user_sees_breadcrumb_links_spec.rb
@@ -7,6 +7,8 @@ RSpec.describe 'New issue breadcrumb' do
let(:user) { project.creator }
before do
+ stub_feature_flags(vue_issuables_list: false)
+
sign_in(user)
visit(new_project_issue_path(project))
end
diff --git a/spec/features/issues/user_sees_empty_state_spec.rb b/spec/features/issues/user_sees_empty_state_spec.rb
index 047c5ca2189..e39369b0150 100644
--- a/spec/features/issues/user_sees_empty_state_spec.rb
+++ b/spec/features/issues/user_sees_empty_state_spec.rb
@@ -6,6 +6,10 @@ RSpec.describe 'Issues > User sees empty state' do
let_it_be(:project) { create(:project, :public) }
let_it_be(:user) { project.creator }
+ before do
+ stub_feature_flags(vue_issuables_list: false)
+ end
+
shared_examples_for 'empty state with filters' do
it 'user sees empty state with filters' do
create(:issue, author: user, project: project)
diff --git a/spec/features/issues/user_sorts_issues_spec.rb b/spec/features/issues/user_sorts_issues_spec.rb
index ec38bf99035..91c6419b464 100644
--- a/spec/features/issues/user_sorts_issues_spec.rb
+++ b/spec/features/issues/user_sorts_issues_spec.rb
@@ -16,6 +16,8 @@ RSpec.describe "User sorts issues" do
let_it_be(:later_due_milestone) { create(:milestone, project: project, due_date: '2013-12-12') }
before do
+ stub_feature_flags(vue_issuables_list: false)
+
create_list(:award_emoji, 2, :upvote, awardable: issue1)
create_list(:award_emoji, 2, :downvote, awardable: issue2)
create(:award_emoji, :downvote, awardable: issue1)
diff --git a/spec/features/issues/user_views_issues_spec.rb b/spec/features/issues/user_views_issues_spec.rb
index 91de813e414..34cea7f3b0b 100644
--- a/spec/features/issues/user_views_issues_spec.rb
+++ b/spec/features/issues/user_views_issues_spec.rb
@@ -6,9 +6,14 @@ RSpec.describe "User views issues" do
let!(:closed_issue) { create(:closed_issue, project: project) }
let!(:open_issue1) { create(:issue, project: project) }
let!(:open_issue2) { create(:issue, project: project) }
+ let!(:moved_open_issue) { create(:issue, project: project, moved_to: create(:issue)) }
let_it_be(:user) { create(:user) }
+ before do
+ stub_feature_flags(vue_issuables_list: false)
+ end
+
shared_examples "opens issue from list" do
it "opens issue" do
click_link(issue.title)
@@ -32,6 +37,7 @@ RSpec.describe "User views issues" do
.and have_content(open_issue1.title)
.and have_content(open_issue2.title)
.and have_no_content(closed_issue.title)
+ .and have_content(moved_open_issue.title)
.and have_no_selector(".js-new-board-list")
end
@@ -62,6 +68,7 @@ RSpec.describe "User views issues" do
.and have_content(closed_issue.title)
.and have_no_content(open_issue1.title)
.and have_no_content(open_issue2.title)
+ .and have_no_content(moved_open_issue.title)
.and have_no_selector(".js-new-board-list")
end
@@ -82,6 +89,8 @@ RSpec.describe "User views issues" do
.and have_content(closed_issue.title)
.and have_content(open_issue1.title)
.and have_content(open_issue2.title)
+ .and have_content(moved_open_issue.title)
+ .and have_no_content('CLOSED (MOVED)')
.and have_no_selector(".js-new-board-list")
end
@@ -116,4 +125,24 @@ RSpec.describe "User views issues" do
context "when not signed in" do
include_examples "public project"
end
+
+ context 'when vue_issuables_list feature is enabled', :js do
+ before do
+ stub_feature_flags(vue_issuables_list: true)
+ end
+
+ context 'when signed in' do
+ before do
+ project.add_developer(user)
+ sign_in(user)
+ end
+
+ include_examples "public project"
+ include_examples "internal project"
+ end
+
+ context 'when not signed in' do
+ include_examples "public project"
+ end
+ end
end
diff --git a/spec/features/labels_hierarchy_spec.rb b/spec/features/labels_hierarchy_spec.rb
index 3ab7fbea198..1545cb36e9b 100644
--- a/spec/features/labels_hierarchy_spec.rb
+++ b/spec/features/labels_hierarchy_spec.rb
@@ -23,7 +23,7 @@ RSpec.describe 'Labels Hierarchy', :js do
end
shared_examples 'assigning labels from sidebar' do
- it 'can assign all ancestors labels', :quarantine do
+ it 'can assign all ancestors labels', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/27952' do
[grandparent_group_label, parent_group_label, project_label_1].each do |label|
page.within('.block.labels') do
find('.edit-link').click
diff --git a/spec/features/markdown/metrics_spec.rb b/spec/features/markdown/metrics_spec.rb
index 092408c2be0..3e63ae67f19 100644
--- a/spec/features/markdown/metrics_spec.rb
+++ b/spec/features/markdown/metrics_spec.rb
@@ -2,8 +2,9 @@
require 'spec_helper'
-RSpec.describe 'Metrics rendering', :js, :use_clean_rails_memory_store_caching, :sidekiq_inline do
+RSpec.describe 'Metrics rendering', :js, :kubeclient, :use_clean_rails_memory_store_caching, :sidekiq_inline do
include PrometheusHelpers
+ include KubernetesHelpers
include GrafanaApiHelpers
include MetricsDashboardUrlHelpers
@@ -166,6 +167,41 @@ RSpec.describe 'Metrics rendering', :js, :use_clean_rails_memory_store_caching,
end
end
+ context 'for GitLab embedded cluster health metrics' do
+ before do
+ project.add_maintainer(user)
+ import_common_metrics
+ stub_any_prometheus_request_with_response
+
+ allow(Prometheus::ProxyService).to receive(:new).and_call_original
+
+ create(:clusters_applications_prometheus, :installed, cluster: cluster)
+ stub_kubeclient_discover(cluster.platform.api_url)
+ stub_prometheus_request(/prometheus-prometheus-server/, body: prometheus_values_body)
+ stub_prometheus_request(/prometheus\/api\/v1/, body: prometheus_values_body)
+ end
+
+ let_it_be(:cluster) { create(:cluster, :provided_by_gcp, :project, projects: [project], user: user) }
+ let(:params) { [project.namespace.path, project.path, cluster.id] }
+ let(:query_params) { { group: 'Cluster Health', title: 'CPU Usage', y_label: 'CPU (cores)' } }
+ let(:metrics_url) { urls.namespace_project_cluster_url(*params, **query_params) }
+ let(:description) { "# Summary \n[](#{metrics_url})" }
+
+ it 'shows embedded metrics' do
+ visit project_issue_path(project, issue)
+
+ expect(page).to have_css('div.prometheus-graph')
+ expect(page).to have_text(query_params[:title])
+ expect(page).to have_text(query_params[:y_label])
+ expect(page).not_to have_text(metrics_url)
+
+ expect(Prometheus::ProxyService)
+ .to have_received(:new)
+ .with(cluster, 'GET', 'query_range', hash_including('start', 'end', 'step'))
+ .at_least(:once)
+ end
+ end
+
def import_common_metrics
::Gitlab::DatabaseImporters::CommonMetrics::Importer.new.execute
end
diff --git a/spec/features/merge_request/maintainer_edits_fork_spec.rb b/spec/features/merge_request/maintainer_edits_fork_spec.rb
index 7573553b3fb..be12f774c29 100644
--- a/spec/features/merge_request/maintainer_edits_fork_spec.rb
+++ b/spec/features/merge_request/maintainer_edits_fork_spec.rb
@@ -20,7 +20,7 @@ RSpec.describe 'a maintainer edits files on a source-branch of an MR from a fork
end
before do
- stub_feature_flags(web_ide_default: false, single_mr_diff_view: false)
+ stub_feature_flags(single_mr_diff_view: false)
target_project.add_maintainer(user)
sign_in(user)
@@ -37,7 +37,7 @@ RSpec.describe 'a maintainer edits files on a source-branch of an MR from a fork
end
it 'allows committing to the source branch' do
- find('.ace_text-input', visible: false).send_keys('Updated the readme')
+ execute_script("monaco.editor.getModels()[0].setValue('Updated the readme')")
click_button 'Commit changes'
wait_for_requests
diff --git a/spec/features/merge_request/user_approves_spec.rb b/spec/features/merge_request/user_approves_spec.rb
new file mode 100644
index 00000000000..d319fdcb87b
--- /dev/null
+++ b/spec/features/merge_request/user_approves_spec.rb
@@ -0,0 +1,41 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'Merge request > User approves', :js do
+ let(:user) { create(:user) }
+ let(:project) { create(:project, :public, :repository) }
+ let(:merge_request) { create(:merge_request, source_project: project) }
+
+ before do
+ project.add_developer(user)
+
+ sign_in(user)
+
+ visit project_merge_request_path(project, merge_request)
+ end
+
+ it 'approves merge request' do
+ click_approval_button('Approve')
+ expect(page).to have_content('Merge request approved')
+
+ verify_approvals_count_on_index!
+
+ click_approval_button('Revoke approval')
+ expect(page).to have_content('No approval required; you can still approve')
+ end
+
+ def verify_approvals_count_on_index!
+ visit(project_merge_requests_path(project, state: :all))
+ expect(page.all('li').any? { |item| item["title"] == "1 approver (you've approved)"}).to be true
+ visit project_merge_request_path(project, merge_request)
+ end
+
+ def click_approval_button(action)
+ page.within('.mr-state-widget') do
+ click_button(action)
+ end
+
+ wait_for_requests
+ end
+end
diff --git a/spec/features/merge_request/user_closes_merge_request_spec.rb b/spec/features/merge_request/user_closes_merge_request_spec.rb
index 669bd989f4f..e6b6778c76e 100644
--- a/spec/features/merge_request/user_closes_merge_request_spec.rb
+++ b/spec/features/merge_request/user_closes_merge_request_spec.rb
@@ -15,7 +15,7 @@ RSpec.describe 'User closes a merge requests', :js do
end
it 'closes a merge request' do
- click_link('Close merge request', match: :first)
+ click_button('Close merge request', match: :first)
expect(page).to have_content(merge_request.title)
expect(page).to have_content('Closed by')
diff --git a/spec/features/merge_request/user_comments_on_diff_spec.rb b/spec/features/merge_request/user_comments_on_diff_spec.rb
index 9cd3c7eaf76..30bf82e3665 100644
--- a/spec/features/merge_request/user_comments_on_diff_spec.rb
+++ b/spec/features/merge_request/user_comments_on_diff_spec.rb
@@ -117,9 +117,58 @@ RSpec.describe 'User comments on a diff', :js do
context 'when adding multiline comments' do
it 'saves a multiline comment' do
click_diff_line(find("[id='#{sample_commit.line_code}']"))
+ add_comment('-13', '+14')
+ end
+
+ context 'when in side-by-side view' do
+ before do
+ visit(diffs_project_merge_request_path(project, merge_request, view: 'parallel'))
+ end
+
+ # In `files/ruby/popen.rb`
+ it 'allows comments for changes involving both sides' do
+ # click +15, select -13 add and verify comment
+ click_diff_line(find('div[data-path="files/ruby/popen.rb"] .new_line a[data-linenumber="15"]').find(:xpath, '../..'), 'right')
+ add_comment('-13', '+15')
+ end
+
+ it 'allows comments to start above hidden lines and end below' do
+ # click +28, select 21 add and verify comment
+ click_diff_line(find('div[data-path="files/ruby/popen.rb"] .new_line a[data-linenumber="28"]').find(:xpath, '../..'), 'right')
+ add_comment('21', '+28')
+ end
+
+ it 'allows comments on previously hidden lines at the top of a file' do
+ # Click -9, expand up, select 1 add and verify comment
+ page.within('[data-path="files/ruby/popen.rb"]') do
+ all('.js-unfold-all')[0].click
+ end
+ click_diff_line(find('div[data-path="files/ruby/popen.rb"] .old_line a[data-linenumber="9"]').find(:xpath, '../..'), 'left')
+ add_comment('1', '-9')
+ end
+
+ it 'allows comments on previously hidden lines the middle of a file' do
+ # Click 27, expand up, select 18, add and verify comment
+ page.within('[data-path="files/ruby/popen.rb"]') do
+ all('.js-unfold-all')[1].click
+ end
+ click_diff_line(find('div[data-path="files/ruby/popen.rb"] .old_line a[data-linenumber="21"]').find(:xpath, '../..'), 'left')
+ add_comment('18', '21')
+ end
+
+ it 'allows comments on previously hidden lines at the bottom of a file' do
+ # Click +28, expand down, select 37 add and verify comment
+ page.within('[data-path="files/ruby/popen.rb"]') do
+ all('.js-unfold-down')[1].click
+ end
+ click_diff_line(find('div[data-path="files/ruby/popen.rb"] .old_line a[data-linenumber="30"]').find(:xpath, '../..'), 'left')
+ add_comment('+28', '37')
+ end
+ end
+ def add_comment(start_line, end_line)
page.within('.discussion-form') do
- find('#comment-line-start option', text: '-13').select_option
+ find('#comment-line-start option', exact_text: start_line).select_option
end
page.within('.js-discussion-note-form') do
@@ -131,7 +180,7 @@ RSpec.describe 'User comments on a diff', :js do
page.within('.notes_holder') do
expect(page).to have_content('Line is wrong')
- expect(page).to have_content('Comment on lines -13 to +14')
+ expect(page).to have_content("Comment on lines #{start_line} to #{end_line}")
end
visit(merge_request_path(merge_request))
diff --git a/spec/features/merge_request/user_creates_image_diff_notes_spec.rb b/spec/features/merge_request/user_creates_image_diff_notes_spec.rb
index 34eaca24a01..3cd23764382 100644
--- a/spec/features/merge_request/user_creates_image_diff_notes_spec.rb
+++ b/spec/features/merge_request/user_creates_image_diff_notes_spec.rb
@@ -88,7 +88,7 @@ RSpec.describe 'Merge request > User creates image diff notes', :js do
create_image_diff_note
end
- it 'shows indicator and avatar badges, and allows collapsing/expanding the discussion notes', :quarantine do
+ it 'shows indicator and avatar badges, and allows collapsing/expanding the discussion notes', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/27950' do
indicator = find('.js-image-badge', match: :first)
badge = find('.user-avatar-link .badge', match: :first)
diff --git a/spec/features/merge_request/user_edits_merge_request_spec.rb b/spec/features/merge_request/user_edits_merge_request_spec.rb
index 84ecd6537dc..6c5f508c8c6 100644
--- a/spec/features/merge_request/user_edits_merge_request_spec.rb
+++ b/spec/features/merge_request/user_edits_merge_request_spec.rb
@@ -16,6 +16,75 @@ RSpec.describe 'User edits a merge request', :js do
visit(edit_project_merge_request_path(project, merge_request))
end
+ describe 'Squash commits' do
+ it 'override MR setting if "Required" is saved' do
+ merge_request.update!(squash: false)
+
+ project.project_setting.update!(squash_option: 'always')
+ visit(edit_project_merge_request_path(project, merge_request))
+ click_button('Save changes')
+
+ project.project_setting.update!(squash_option: 'default_off')
+ visit(edit_project_merge_request_path(project, merge_request))
+
+ expect(find("#merge_request_squash").selected?).to be(true)
+ end
+
+ it 'recovers MR squash setting if "Required" is not saved' do
+ merge_request.update!(squash: false)
+
+ project.project_setting.update!(squash_option: 'always')
+ visit(edit_project_merge_request_path(project, merge_request))
+
+ project.project_setting.update!(squash_option: 'default_on')
+ visit(edit_project_merge_request_path(project, merge_request))
+
+ expect(find("#merge_request_squash").selected?).to be(false)
+ end
+
+ it 'does not override MR squash setting if "Do not allow" is saved' do
+ merge_request.update!(squash: true)
+
+ project.project_setting.update!(squash_option: 'never')
+ visit(edit_project_merge_request_path(project, merge_request))
+ click_button('Save changes')
+
+ expect(merge_request.squash).to be true
+ end
+
+ it 'displays "Required in this project" for "Required" project setting squash option' do
+ project.project_setting.update!(squash_option: 'always')
+ visit(edit_project_merge_request_path(project, merge_request))
+
+ expect(page).to have_content('Squash commits when merge request is accepted.')
+ expect(page).to have_content("Required in this project")
+ end
+
+ it 'does not display message for "Allow" project setting squash option' do
+ project.project_setting.update!(squash_option: 'default_off')
+ visit(edit_project_merge_request_path(project, merge_request))
+
+ expect(page).to have_content('Squash commits when merge request is accepted.')
+ expect(page).not_to have_content("Required in this project")
+ end
+
+ it 'does not display message for "Encourage" project setting squash option' do
+ project.project_setting.update!(squash_option: 'default_on')
+ visit(edit_project_merge_request_path(project, merge_request))
+
+ expect(page).to have_content('Squash commits when merge request is accepted.')
+ expect(page).not_to have_content("Required in this project")
+ end
+
+ it 'is hidden for "Do not allow" project setting squash option' do
+ project.project_setting.update!(squash_option: 'never')
+ visit(edit_project_merge_request_path(project, merge_request))
+
+ expect(page).not_to have_content('Squash commits when merge request is accepted.')
+ expect(page).not_to have_css('#merge_request_squash')
+ end
+ end
+
it 'changes the target branch' do
expect(page).to have_content('From master into feature')
diff --git a/spec/features/merge_request/user_posts_diff_notes_spec.rb b/spec/features/merge_request/user_posts_diff_notes_spec.rb
index dbad2f191a1..6ecffb05009 100644
--- a/spec/features/merge_request/user_posts_diff_notes_spec.rb
+++ b/spec/features/merge_request/user_posts_diff_notes_spec.rb
@@ -46,7 +46,7 @@ RSpec.describe 'Merge request > User posts diff notes', :js do
end
context 'with an old line on the left and a new line on the right' do
- it 'allows commenting on the left side', quarantine: 'https://gitlab.com/gitlab-org/gitlab/issues/199050' do
+ it 'allows commenting on the left side', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/199050' do
should_allow_commenting(find('[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_9_9"]').find(:xpath, '..'), 'left')
end
@@ -56,7 +56,7 @@ RSpec.describe 'Merge request > User posts diff notes', :js do
end
context 'with an unchanged line on the left and an unchanged line on the right' do
- it 'allows commenting on the left side', quarantine: 'https://gitlab.com/gitlab-org/gitlab/issues/196826' do
+ it 'allows commenting on the left side', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/196826' do
should_allow_commenting(find('[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_7_7"]', match: :first).find(:xpath, '..'), 'left')
end
diff --git a/spec/features/merge_request/user_reopens_merge_request_spec.rb b/spec/features/merge_request/user_reopens_merge_request_spec.rb
index 020929dc416..7866ece84ac 100644
--- a/spec/features/merge_request/user_reopens_merge_request_spec.rb
+++ b/spec/features/merge_request/user_reopens_merge_request_spec.rb
@@ -15,7 +15,7 @@ RSpec.describe 'User reopens a merge requests', :js do
end
it 'reopens a merge request' do
- click_link('Reopen merge request', match: :first)
+ click_button('Reopen merge request', match: :first)
page.within('.status-box') do
expect(page).to have_content('Open')
diff --git a/spec/features/merge_request/user_sees_diff_spec.rb b/spec/features/merge_request/user_sees_diff_spec.rb
index 2229b242d5b..d067fc0ada4 100644
--- a/spec/features/merge_request/user_sees_diff_spec.rb
+++ b/spec/features/merge_request/user_sees_diff_spec.rb
@@ -72,7 +72,7 @@ RSpec.describe 'Merge request > User sees diff', :js do
end
context 'as user who needs to fork' do
- it 'shows fork/cancel confirmation', :sidekiq_might_not_need_inline, quarantine: 'https://gitlab.com/gitlab-org/gitlab/issues/196749' do
+ it 'shows fork/cancel confirmation', :sidekiq_might_not_need_inline, quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/196749' do
sign_in(user)
visit diffs_project_merge_request_path(project, merge_request)
diff --git a/spec/features/merge_request/user_sees_merge_widget_spec.rb b/spec/features/merge_request/user_sees_merge_widget_spec.rb
index bd140a0643d..ce49e9f4141 100644
--- a/spec/features/merge_request/user_sees_merge_widget_spec.rb
+++ b/spec/features/merge_request/user_sees_merge_widget_spec.rb
@@ -268,7 +268,7 @@ RSpec.describe 'Merge request > User sees merge widget', :js do
end
end
- context 'view merge request where project has CI set up but no CI status' do
+ context 'view merge request where there is no pipeline yet' do
before do
pipeline = create(:ci_pipeline, project: project,
sha: merge_request.diff_head_sha,
@@ -278,11 +278,11 @@ RSpec.describe 'Merge request > User sees merge widget', :js do
visit project_merge_request_path(project, merge_request)
end
- it 'has pipeline error text' do
+ it 'has pipeline loading state' do
# Wait for the `ci_status` and `merge_check` requests
wait_for_requests
- expect(page).to have_text("Could not retrieve the pipeline status. For troubleshooting steps, read the documentation.")
+ expect(page).to have_text("Checking pipeline status")
end
end
@@ -889,9 +889,9 @@ RSpec.describe 'Merge request > User sees merge widget', :js do
visit project_merge_request_path(project, merge_request)
end
- it 'renders a CI pipeline error' do
+ it 'renders a CI pipeline loading state' do
within '.ci-widget' do
- expect(page).to have_content('Could not retrieve the pipeline status.')
+ expect(page).to have_content('Checking pipeline status')
end
end
end
diff --git a/spec/features/merge_request/user_sees_pipelines_spec.rb b/spec/features/merge_request/user_sees_pipelines_spec.rb
index 2d125087cb6..d693eec91c8 100644
--- a/spec/features/merge_request/user_sees_pipelines_spec.rb
+++ b/spec/features/merge_request/user_sees_pipelines_spec.rb
@@ -38,14 +38,6 @@ RSpec.describe 'Merge request > User sees pipelines', :js do
expect(page).to have_selector('.stage-cell')
end
- it 'pipeline sha does not equal last commit sha' do
- pipeline.update_attribute(:sha, '19e2e9b4ef76b422ce1154af39a91323ccc57434')
- visit project_merge_request_path(project, merge_request)
- wait_for_requests
-
- expect(page.find('.ci-widget')).to have_text("Could not retrieve the pipeline status. For troubleshooting steps, read the documentation.")
- end
-
context 'with a detached merge request pipeline' do
let(:merge_request) { create(:merge_request, :with_detached_merge_request_pipeline) }
@@ -92,6 +84,111 @@ RSpec.describe 'Merge request > User sees pipelines', :js do
end
end
+ describe 'fork MRs in parent project', :sidekiq_inline do
+ include ProjectForksHelper
+
+ let_it_be(:parent_project) { create(:project, :public, :repository) }
+ let_it_be(:forked_project) { fork_project(parent_project, developer_in_fork, repository: true, target_project: create(:project, :public, :repository)) }
+ let_it_be(:developer_in_parent) { create(:user) }
+ let_it_be(:developer_in_fork) { create(:user) }
+ let_it_be(:reporter_in_parent_and_developer_in_fork) { create(:user) }
+
+ let(:merge_request) do
+ create(:merge_request, :with_detached_merge_request_pipeline,
+ source_project: forked_project, source_branch: 'feature',
+ target_project: parent_project, target_branch: 'master')
+ end
+
+ let(:config) do
+ { test: { script: 'test', rules: [{ if: '$CI_MERGE_REQUEST_ID' }] } }
+ end
+
+ before_all do
+ parent_project.add_developer(developer_in_parent)
+ parent_project.add_reporter(reporter_in_parent_and_developer_in_fork)
+ forked_project.add_developer(developer_in_fork)
+ forked_project.add_developer(reporter_in_parent_and_developer_in_fork)
+ end
+
+ before do
+ stub_ci_pipeline_yaml_file(YAML.dump(config))
+ sign_in(actor)
+ end
+
+ after do
+ parent_project.all_pipelines.delete_all
+ forked_project.all_pipelines.delete_all
+ end
+
+ context 'when actor is a developer in parent project' do
+ let(:actor) { developer_in_parent }
+
+ it 'creates a pipeline in the parent project' do
+ visit project_merge_request_path(parent_project, merge_request)
+
+ create_merge_request_pipeline
+
+ check_pipeline(expected_project: parent_project)
+ check_head_pipeline(expected_project: parent_project)
+ end
+ end
+
+ context 'when actor is a developer in fork project' do
+ let(:actor) { developer_in_fork }
+
+ it 'creates a pipeline in the fork project' do
+ visit project_merge_request_path(parent_project, merge_request)
+
+ create_merge_request_pipeline
+
+ check_pipeline(expected_project: forked_project)
+ check_head_pipeline(expected_project: forked_project)
+ end
+ end
+
+ context 'when actor is a reporter in parent project and a developer in fork project' do
+ let(:actor) { reporter_in_parent_and_developer_in_fork }
+
+ it 'creates a pipeline in the fork project' do
+ visit project_merge_request_path(parent_project, merge_request)
+
+ create_merge_request_pipeline
+
+ check_pipeline(expected_project: forked_project)
+ check_head_pipeline(expected_project: forked_project)
+ end
+ end
+
+ def create_merge_request_pipeline
+ page.within('.merge-request-tabs') { click_link('Pipelines') }
+ click_button('Run Pipeline')
+ end
+
+ def check_pipeline(expected_project:)
+ page.within('.ci-table') do
+ expect(page).to have_selector('.commit', count: 2)
+
+ page.within(first('.commit')) do
+ page.within('.pipeline-tags') do
+ expect(page.find('.js-pipeline-url-link')[:href]).to include(expected_project.full_path)
+ expect(page).to have_content('detached')
+ end
+ page.within('.pipeline-triggerer') do
+ expect(page).to have_link(href: user_path(actor))
+ end
+ end
+ end
+ end
+
+ def check_head_pipeline(expected_project:)
+ page.within('.merge-request-tabs') { click_link('Overview') }
+
+ page.within('.ci-widget-content') do
+ expect(page.find('.pipeline-id')[:href]).to include(expected_project.full_path)
+ end
+ end
+ end
+
describe 'race condition' do
let(:project) { create(:project, :repository) }
let(:user) { create(:user) }
diff --git a/spec/features/merge_request/user_suggests_changes_on_diff_spec.rb b/spec/features/merge_request/user_suggests_changes_on_diff_spec.rb
index b81c0e49538..0506d190487 100644
--- a/spec/features/merge_request/user_suggests_changes_on_diff_spec.rb
+++ b/spec/features/merge_request/user_suggests_changes_on_diff_spec.rb
@@ -155,7 +155,7 @@ RSpec.describe 'User comments on a diff', :js do
end
end
- it 'can apply multiple suggestions as a batch' do
+ it 'can apply multiple suggestions as a batch', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/224100' do
files.each_with_index do |file, index|
page.within("[id='#{file[:hash]}']") do
find("button[title='Show full file']").click
@@ -188,8 +188,7 @@ RSpec.describe 'User comments on a diff', :js do
end
context 'multiple suggestions in expanded lines' do
- # https://gitlab.com/gitlab-org/gitlab/issues/38277
- it 'suggestions are appliable', :quarantine do
+ it 'suggestions are appliable', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/38277' do
diff_file = merge_request.diffs(paths: ['files/ruby/popen.rb']).diff_files.first
hash = Digest::SHA1.hexdigest(diff_file.file_path)
diff --git a/spec/features/merge_request/user_toggles_whitespace_changes_spec.rb b/spec/features/merge_request/user_toggles_whitespace_changes_spec.rb
index fab500f47bf..05f4c16ef60 100644
--- a/spec/features/merge_request/user_toggles_whitespace_changes_spec.rb
+++ b/spec/features/merge_request/user_toggles_whitespace_changes_spec.rb
@@ -27,7 +27,7 @@ RSpec.describe 'Merge request > User toggles whitespace changes', :js do
find('.js-show-diff-settings').click
- expect(find('#show-whitespace')).to be_checked
+ expect(find('#show-whitespace')).not_to be_checked
end
end
end
diff --git a/spec/features/merge_request/user_views_diffs_file_by_file_spec.rb b/spec/features/merge_request/user_views_diffs_file_by_file_spec.rb
new file mode 100644
index 00000000000..c254a142349
--- /dev/null
+++ b/spec/features/merge_request/user_views_diffs_file_by_file_spec.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'User views diffs file-by-file', :js do
+ let(:merge_request) do
+ create(:merge_request_with_diffs, source_project: project, target_project: project, source_branch: 'merge-test')
+ end
+ let(:project) { create(:project, :repository) }
+ let(:user) { create(:user, view_diffs_file_by_file: true) }
+
+ before do
+ project.add_developer(user)
+
+ sign_in(user)
+
+ visit(diffs_project_merge_request_path(project, merge_request))
+
+ wait_for_requests
+ end
+
+ it 'shows diffs file-by-file' do
+ page.within('#diffs') do
+ expect(page).to have_selector('.file-holder', count: 1)
+ expect(page).to have_selector('.diff-file .file-title', text: '.DS_Store')
+
+ click_button 'Next'
+
+ expect(page).to have_selector('.file-holder', count: 1)
+ expect(page).to have_selector('.diff-file .file-title', text: '.gitignore')
+ end
+ end
+end
diff --git a/spec/features/participants_autocomplete_spec.rb b/spec/features/participants_autocomplete_spec.rb
index 5c29ac870c0..d6f23b21d65 100644
--- a/spec/features/participants_autocomplete_spec.rb
+++ b/spec/features/participants_autocomplete_spec.rb
@@ -36,12 +36,35 @@ RSpec.describe 'Member autocomplete', :js do
let(:noteable) { create(:issue, author: author, project: project) }
before do
+ stub_feature_flags(tribute_autocomplete: false)
visit project_issue_path(project, noteable)
end
include_examples "open suggestions when typing @", 'issue'
end
+ describe 'when tribute_autocomplete feature flag is on' do
+ context 'adding a new note on a Issue' do
+ let(:noteable) { create(:issue, author: author, project: project) }
+
+ before do
+ stub_feature_flags(tribute_autocomplete: true)
+ visit project_issue_path(project, noteable)
+
+ page.within('.new-note') do
+ find('#note-body').send_keys('@')
+ end
+ end
+
+ it 'suggests noteable author and note author' do
+ page.within('.tribute-container', visible: true) do
+ expect(page).to have_content(author.username)
+ expect(page).to have_content(note.author.username)
+ end
+ end
+ end
+ end
+
context 'adding a new note on a Merge Request' do
let(:project) { create(:project, :public, :repository) }
let(:noteable) do
diff --git a/spec/features/profiles/user_edit_preferences_spec.rb b/spec/features/profiles/user_edit_preferences_spec.rb
index e1117d2d420..817228edca7 100644
--- a/spec/features/profiles/user_edit_preferences_spec.rb
+++ b/spec/features/profiles/user_edit_preferences_spec.rb
@@ -56,4 +56,17 @@ RSpec.describe 'User edit preferences profile' do
expect(page).to have_content('Failed to save preferences')
end
end
+
+ describe 'User language' do
+ let(:user) { create(:user, preferred_language: :es) }
+
+ it 'shows the user preferred language by default' do
+ expect(page).to have_select(
+ 'user[preferred_language]',
+ selected: 'Spanish - español',
+ options: Gitlab::I18n::AVAILABLE_LANGUAGES.values,
+ visible: :all
+ )
+ end
+ end
end
diff --git a/spec/features/profiles/user_edit_profile_spec.rb b/spec/features/profiles/user_edit_profile_spec.rb
index 2659157d61d..697fccaca34 100644
--- a/spec/features/profiles/user_edit_profile_spec.rb
+++ b/spec/features/profiles/user_edit_profile_spec.rb
@@ -26,7 +26,7 @@ RSpec.describe 'User edit profile' do
fill_in 'user_twitter', with: 'testtwitter'
fill_in 'user_website_url', with: 'testurl'
fill_in 'user_location', with: 'Ukraine'
- fill_in 'user_bio', with: 'I <3 GitLab'
+ fill_in 'user_bio', with: 'I <3 GitLab :tada:'
fill_in 'user_job_title', with: 'Frontend Engineer'
fill_in 'user_organization', with: 'GitLab'
submit_settings
@@ -36,7 +36,8 @@ RSpec.describe 'User edit profile' do
linkedin: 'testlinkedin',
twitter: 'testtwitter',
website_url: 'testurl',
- bio: 'I <3 GitLab',
+ bio: 'I <3 GitLab :tada:',
+ bio_html: '<p data-sourcepos="1:1-1:18" dir="auto">I &lt;3 GitLab <gl-emoji title="party popper" data-name="tada" data-unicode-version="6.0">🎉</gl-emoji></p>',
job_title: 'Frontend Engineer',
organization: 'GitLab'
)
diff --git a/spec/features/profiles/user_visits_profile_preferences_page_spec.rb b/spec/features/profiles/user_visits_profile_preferences_page_spec.rb
index d9421631b32..2747b5894dc 100644
--- a/spec/features/profiles/user_visits_profile_preferences_page_spec.rb
+++ b/spec/features/profiles/user_visits_profile_preferences_page_spec.rb
@@ -65,7 +65,7 @@ RSpec.describe 'User visits the profile preferences page' do
end
describe 'User changes their language', :js do
- it 'creates a flash message', :quarantine do
+ it 'creates a flash message', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/31404' do
select2('en', from: '#user_preferred_language')
click_button 'Save'
diff --git a/spec/features/projects/activity/user_sees_design_activity_spec.rb b/spec/features/projects/activity/user_sees_design_activity_spec.rb
new file mode 100644
index 00000000000..27a52b87178
--- /dev/null
+++ b/spec/features/projects/activity/user_sees_design_activity_spec.rb
@@ -0,0 +1,78 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'Projects > Activity > User sees design Activity', :js do
+ include DesignManagementTestHelpers
+
+ let_it_be(:uploader) { create(:user) }
+ let_it_be(:editor) { create(:user) }
+ let_it_be(:deleter) { create(:user) }
+ let_it_be(:archiver) { create(:user) }
+
+ def design_activity(user, action)
+ [user.name, user.to_reference, action, 'design'].join(' ')
+ end
+
+ shared_examples 'being able to see design activity' do
+ let_it_be(:issue) { create(:issue, project: project) }
+ let_it_be(:design) { create(:design, issue: issue) }
+
+ before_all do
+ project.add_developer(user) # implicitly adds a project join event.
+ common_attrs = { project: project, design: design }
+ create(:design_event, :created, author: uploader, **common_attrs)
+ create(:design_event, :updated, author: editor, **common_attrs)
+ create(:design_event, :destroyed, author: deleter, **common_attrs)
+ create(:design_event, :archived, author: archiver, **common_attrs)
+ end
+
+ before do
+ enable_design_management
+ sign_in(user)
+ end
+
+ it 'shows the design comment action in the activity page' do
+ visit activity_project_path(project)
+
+ expect(page).to have_content('joined project')
+ expect(page).to have_content(design_activity(uploader, 'uploaded'))
+ expect(page).to have_content(design_activity(editor, 'revised'))
+ expect(page).to have_content(design_activity(deleter, 'deleted'))
+ expect(page).to have_content(design_activity(archiver, 'archived'))
+ end
+
+ it 'allows filtering out the design events', :aggregate_failures do
+ visit activity_project_path(project, event_filter: EventFilter::ISSUE)
+
+ expect(page).not_to have_content(design_activity(uploader, 'uploaded'))
+ expect(page).not_to have_content(design_activity(editor, 'revised'))
+ expect(page).not_to have_content(design_activity(deleter, 'deleted'))
+ expect(page).not_to have_content(design_activity(archiver, 'archived'))
+ end
+
+ it 'allows filtering in the design events', :aggregate_failures do
+ visit activity_project_path(project, event_filter: EventFilter::DESIGNS)
+
+ expect(page).not_to have_content('joined project')
+ expect(page).to have_content(design_activity(uploader, 'uploaded'))
+ expect(page).to have_content(design_activity(editor, 'revised'))
+ expect(page).to have_content(design_activity(deleter, 'deleted'))
+ expect(page).to have_content(design_activity(archiver, 'archived'))
+ end
+ end
+
+ context 'the project is public' do
+ let_it_be(:project) { create(:project, :repository, :public) }
+ let_it_be(:user) { create(:user) }
+
+ it_behaves_like 'being able to see design activity'
+ end
+
+ context 'the project is private' do
+ let_it_be(:project) { create(:project, :repository) }
+ let_it_be(:user) { create(:user, developer_projects: [project]) }
+
+ it_behaves_like 'being able to see design activity'
+ end
+end
diff --git a/spec/features/projects/blobs/blob_show_spec.rb b/spec/features/projects/blobs/blob_show_spec.rb
index cce656a1260..3032d115a00 100644
--- a/spec/features/projects/blobs/blob_show_spec.rb
+++ b/spec/features/projects/blobs/blob_show_spec.rb
@@ -589,7 +589,7 @@ RSpec.describe 'File blob', :js do
aggregate_failures do
# shows that dashboard yaml is invalid
expect(page).to have_content('Metrics Dashboard YAML definition is invalid:')
- expect(page).to have_content("panel_groups: can't be blank")
+ expect(page).to have_content("panel_groups: should be an array of panel_groups objects")
# shows a learn more link
expect(page).to have_link('Learn more')
diff --git a/spec/features/projects/blobs/edit_spec.rb b/spec/features/projects/blobs/edit_spec.rb
index a369bacc4dd..5aca994f53e 100644
--- a/spec/features/projects/blobs/edit_spec.rb
+++ b/spec/features/projects/blobs/edit_spec.rb
@@ -11,10 +11,6 @@ RSpec.describe 'Editing file blob', :js do
let(:file_path) { project.repository.ls_files(project.repository.root_ref)[1] }
let(:readme_file_path) { 'README.md' }
- before do
- stub_feature_flags(web_ide_default: false)
- end
-
context 'as a developer' do
let(:user) { create(:user) }
let(:role) { :developer }
@@ -36,8 +32,7 @@ RSpec.describe 'Editing file blob', :js do
def fill_editor(content: 'class NextFeature\\nend\\n')
wait_for_requests
- find('#editor')
- execute_script("ace.edit('editor').setValue('#{content}')")
+ execute_script("monaco.editor.getModels()[0].setValue('#{content}')")
end
context 'from MR diff' do
@@ -67,6 +62,15 @@ RSpec.describe 'Editing file blob', :js do
expect(find_by_id('file_path').value).to eq('ci/.gitlab-ci.yml')
end
+ it 'updating file path updates syntax highlighting' do
+ visit project_edit_blob_path(project, tree_join(branch, readme_file_path))
+ expect(find('#editor')['data-mode-id']).to eq('markdown')
+
+ find('#file_path').send_keys('foo.txt') do
+ expect(find('#editor')['data-mode-id']).to eq('plaintext')
+ end
+ end
+
context 'from blob file path' do
before do
visit project_blob_path(project, tree_join(branch, file_path))
diff --git a/spec/features/projects/blobs/user_creates_new_blob_in_new_project_spec.rb b/spec/features/projects/blobs/user_creates_new_blob_in_new_project_spec.rb
index d250a01c050..a271a4f43a8 100644
--- a/spec/features/projects/blobs/user_creates_new_blob_in_new_project_spec.rb
+++ b/spec/features/projects/blobs/user_creates_new_blob_in_new_project_spec.rb
@@ -15,8 +15,7 @@ RSpec.describe 'User creates blob in new project', :js do
it 'allows the user to add a new file' do
click_link 'New file'
- find('#editor')
- execute_script('ace.edit("editor").setValue("Hello world")')
+ execute_script("monaco.editor.getModels()[0].setValue('Hello world')")
fill_in(:file_name, with: 'dummy-file')
diff --git a/spec/features/projects/blobs/user_follows_pipeline_suggest_nudge_spec.rb b/spec/features/projects/blobs/user_follows_pipeline_suggest_nudge_spec.rb
index 5270774b541..8b43687c71c 100644
--- a/spec/features/projects/blobs/user_follows_pipeline_suggest_nudge_spec.rb
+++ b/spec/features/projects/blobs/user_follows_pipeline_suggest_nudge_spec.rb
@@ -32,6 +32,8 @@ RSpec.describe 'User follows pipeline suggest nudge spec when feature is enabled
end
it 'displays suggest_gitlab_ci_yml popover' do
+ page.find(:css, '.gitlab-ci-yml-selector').click
+
popover_selector = '.suggest-gitlab-ci-yml'
expect(page).to have_css(popover_selector, visible: true)
diff --git a/spec/features/projects/clusters/gcp_spec.rb b/spec/features/projects/clusters/gcp_spec.rb
index 3e1006920e7..2e6a366f77a 100644
--- a/spec/features/projects/clusters/gcp_spec.rb
+++ b/spec/features/projects/clusters/gcp_spec.rb
@@ -139,6 +139,19 @@ RSpec.describe 'Gcp Cluster', :js, :do_not_mock_admin_mode do
end
end
+ context 'when a user adds an existing cluster' do
+ before do
+ visit project_clusters_path(project)
+
+ click_link 'Add Kubernetes cluster'
+ click_link 'Add existing cluster'
+ end
+
+ it 'user sees the "Environment scope" field' do
+ expect(page).to have_css('#cluster_environment_scope')
+ end
+ end
+
context 'when user destroys the cluster' do
before do
click_link 'Advanced Settings'
@@ -155,19 +168,6 @@ RSpec.describe 'Gcp Cluster', :js, :do_not_mock_admin_mode do
end
end
- context 'when a user cannot edit the environment scope' do
- before do
- visit project_clusters_path(project)
-
- click_link 'Add Kubernetes cluster'
- click_link 'Add existing cluster'
- end
-
- it 'user does not see the "Environment scope" field' do
- expect(page).not_to have_css('#cluster_environment_scope')
- end
- end
-
context 'when user has not dismissed GCP signup offer' do
before do
visit project_clusters_path(project)
diff --git a/spec/features/projects/clusters_spec.rb b/spec/features/projects/clusters_spec.rb
index 1cf214a5c4e..c56a1ed1711 100644
--- a/spec/features/projects/clusters_spec.rb
+++ b/spec/features/projects/clusters_spec.rb
@@ -25,6 +25,168 @@ RSpec.describe 'Clusters', :js do
end
end
+ context 'when user has a cluster' do
+ before do
+ allow_any_instance_of(Clusters::Cluster).to receive(:retrieve_connection_status).and_return(:connected)
+ end
+
+ context 'when user adds an existing cluster' do
+ before do
+ create(:cluster, :provided_by_user, name: 'default-cluster', environment_scope: '*', projects: [project])
+ visit project_clusters_path(project)
+ end
+
+ it 'user sees an add cluster button' do
+ expect(page).to have_selector('.js-add-cluster:not(.readonly)')
+ end
+
+ context 'when user filled form with environment scope' do
+ before do
+ click_link 'Add Kubernetes cluster'
+ click_link 'Add existing cluster'
+ fill_in 'cluster_name', with: 'staging-cluster'
+ fill_in 'cluster_environment_scope', with: 'staging/*'
+ click_button 'Add Kubernetes cluster'
+ end
+
+ it 'user sees a cluster details page' do
+ expect(page.find_field('cluster[name]').value).to eq('staging-cluster')
+ expect(page.find_field('cluster[environment_scope]').value).to eq('staging/*')
+ end
+ end
+
+ context 'when user updates environment scope' do
+ before do
+ click_link 'default-cluster'
+ fill_in 'cluster_environment_scope', with: 'production/*'
+ within '.js-cluster-integration-form' do
+ click_button 'Save changes'
+ end
+ end
+
+ it 'updates the environment scope' do
+ expect(page.find_field('cluster[environment_scope]').value).to eq('production/*')
+ end
+ end
+
+ context 'when user updates duplicated environment scope' do
+ before do
+ click_link 'Add Kubernetes cluster'
+ click_link 'Add existing cluster'
+ fill_in 'cluster_name', with: 'staging-cluster'
+ fill_in 'cluster_environment_scope', with: '*'
+ fill_in 'cluster_platform_kubernetes_attributes_api_url', with: 'https://0.0.0.0'
+ fill_in 'cluster_platform_kubernetes_attributes_token', with: 'token'
+
+ click_button 'Add Kubernetes cluster'
+ end
+
+ it 'users sees an environment scope validation error' do
+ expect(page).to have_content('cannot add duplicated environment scope')
+ end
+ end
+ end
+
+ context 'when user adds a Google Kubernetes Engine cluster' do
+ before do
+ allow_any_instance_of(Projects::ClustersController)
+ .to receive(:token_in_session).and_return('token')
+ allow_any_instance_of(Projects::ClustersController)
+ .to receive(:expires_at_in_session).and_return(1.hour.since.to_i.to_s)
+
+ allow_any_instance_of(Projects::ClustersController).to receive(:authorize_google_project_billing)
+ allow_any_instance_of(Projects::ClustersController).to receive(:google_project_billing_status).and_return(true)
+
+ allow_any_instance_of(GoogleApi::CloudPlatform::Client)
+ .to receive(:projects_zones_clusters_create) do
+ OpenStruct.new(
+ self_link: 'projects/gcp-project-12345/zones/us-central1-a/operations/ope-123',
+ status: 'RUNNING'
+ )
+ end
+
+ allow(WaitForClusterCreationWorker).to receive(:perform_in).and_return(nil)
+
+ create(:cluster, :provided_by_gcp, name: 'default-cluster', environment_scope: '*', projects: [project])
+ visit project_clusters_path(project)
+ end
+
+ it 'user sees a add cluster button ' do
+ expect(page).to have_selector('.js-add-cluster:not(.readonly)')
+ end
+
+ context 'when user filled form with environment scope' do
+ before do
+ click_link 'Add Kubernetes cluster'
+ click_link 'Create new cluster'
+ click_link 'Google GKE'
+
+ sleep 2 # wait for ajax
+ execute_script('document.querySelector(".js-gcp-project-id-dropdown input").setAttribute("type", "text")')
+ execute_script('document.querySelector(".js-gcp-zone-dropdown input").setAttribute("type", "text")')
+ execute_script('document.querySelector(".js-gcp-machine-type-dropdown input").setAttribute("type", "text")')
+ execute_script('document.querySelector(".js-gke-cluster-creation-submit").removeAttribute("disabled")')
+
+ fill_in 'cluster_name', with: 'staging-cluster'
+ fill_in 'cluster_environment_scope', with: 'staging/*'
+ fill_in 'cluster[provider_gcp_attributes][gcp_project_id]', with: 'gcp-project-123'
+ fill_in 'cluster[provider_gcp_attributes][zone]', with: 'us-central1-a'
+ fill_in 'cluster[provider_gcp_attributes][machine_type]', with: 'n1-standard-2'
+ click_button 'Create Kubernetes cluster'
+
+ # The frontend won't show the details until the cluster is
+ # created, and we don't want to make calls out to GCP.
+ provider = Clusters::Cluster.last.provider
+ provider.make_created
+ end
+
+ it 'user sees a cluster details page' do
+ expect(page).to have_content('GitLab Integration')
+ expect(page.find_field('cluster[environment_scope]').value).to eq('staging/*')
+ end
+ end
+
+ context 'when user updates environment scope' do
+ before do
+ click_link 'default-cluster'
+ fill_in 'cluster_environment_scope', with: 'production/*'
+ within ".js-cluster-integration-form" do
+ click_button 'Save changes'
+ end
+ end
+
+ it 'updates the environment scope' do
+ expect(page.find_field('cluster[environment_scope]').value).to eq('production/*')
+ end
+ end
+
+ context 'when user updates duplicated environment scope' do
+ before do
+ click_link 'Add Kubernetes cluster'
+ click_link 'Create new cluster'
+ click_link 'Google GKE'
+
+ sleep 2 # wait for ajax
+ execute_script('document.querySelector(".js-gcp-project-id-dropdown input").setAttribute("type", "text")')
+ execute_script('document.querySelector(".js-gcp-zone-dropdown input").setAttribute("type", "text")')
+ execute_script('document.querySelector(".js-gcp-machine-type-dropdown input").setAttribute("type", "text")')
+ execute_script('document.querySelector(".js-gke-cluster-creation-submit").removeAttribute("disabled")')
+
+ fill_in 'cluster_name', with: 'staging-cluster'
+ fill_in 'cluster_environment_scope', with: '*'
+ fill_in 'cluster[provider_gcp_attributes][gcp_project_id]', with: 'gcp-project-123'
+ fill_in 'cluster[provider_gcp_attributes][zone]', with: 'us-central1-a'
+ fill_in 'cluster[provider_gcp_attributes][machine_type]', with: 'n1-standard-2'
+ click_button 'Create Kubernetes cluster'
+ end
+
+ it 'users sees an environment scope validation error' do
+ expect(page).to have_content('cannot add duplicated environment scope')
+ end
+ end
+ end
+ end
+
context 'when user has a cluster and visits cluster index page' do
let!(:cluster) { create(:cluster, :project, :provided_by_gcp) }
let(:project) { cluster.project }
diff --git a/spec/features/projects/commit/builds_spec.rb b/spec/features/projects/commit/builds_spec.rb
index 13f73b8cf44..f97abc5bd8b 100644
--- a/spec/features/projects/commit/builds_spec.rb
+++ b/spec/features/projects/commit/builds_spec.rb
@@ -6,23 +6,32 @@ RSpec.describe 'project commit pipelines', :js do
let(:project) { create(:project, :repository) }
before do
+ create(:ci_pipeline, project: project,
+ sha: project.commit.sha,
+ ref: 'master')
+
user = create(:user)
project.add_maintainer(user)
sign_in(user)
+
+ visit pipelines_project_commit_path(project, project.commit.sha)
end
context 'when no builds triggered yet' do
- before do
- create(:ci_pipeline, project: project,
- sha: project.commit.sha,
- ref: 'master')
+ it 'shows the ID of the first pipeline' do
+ page.within('.table-holder') do
+ expect(page).to have_content project.ci_pipelines[0].id # pipeline ids
+ end
end
+ end
- it 'user views commit pipelines page' do
- visit pipelines_project_commit_path(project, project.commit.sha)
+ context 'with no related merge requests' do
+ it 'shows the correct text for no related MRs' do
+ wait_for_requests
- page.within('.table-holder') do
- expect(page).to have_content project.ci_pipelines[0].id # pipeline ids
+ page.within('.merge-request-info') do
+ expect(page).not_to have_selector '.spinner'
+ expect(page).to have_content 'No related merge requests found'
end
end
end
diff --git a/spec/features/projects/commits/user_browses_commits_spec.rb b/spec/features/projects/commits/user_browses_commits_spec.rb
index 2796156bfbf..dee964005a4 100644
--- a/spec/features/projects/commits/user_browses_commits_spec.rb
+++ b/spec/features/projects/commits/user_browses_commits_spec.rb
@@ -137,6 +137,33 @@ RSpec.describe 'User browses commits' do
.and have_selector('entry summary', text: commit.description[0..10].delete("\r\n"))
end
+ context "when commit has a filename with pathspec characters" do
+ let(:path) { ':wq' }
+ let(:filename) { File.join(path, 'test.txt') }
+ let(:ref) { project.repository.root_ref }
+ let(:newrev) { project.repository.commit('master').sha }
+ let(:short_newrev) { project.repository.commit('master').short_id }
+ let(:message) { 'Glob characters'}
+
+ before do
+ create_file_in_repo(project, ref, ref, filename, 'Test file', commit_message: message)
+ visit project_commits_path(project, "#{ref}/#{path}", limit: 1)
+ wait_for_requests
+ end
+
+ it 'searches commit', :js do
+ expect(page).to have_content(message)
+
+ fill_in 'commits-search', with: 'bogus12345'
+
+ expect(page).to have_content "Your search didn't match any commits"
+
+ fill_in 'commits-search', with: 'Glob'
+
+ expect(page).to have_content message
+ end
+ end
+
context 'when a commit links to a confidential issue' do
let(:confidential_issue) { create(:issue, confidential: true, title: 'Secret issue!', project: project) }
diff --git a/spec/features/projects/confluence/user_views_confluence_page_spec.rb b/spec/features/projects/confluence/user_views_confluence_page_spec.rb
new file mode 100644
index 00000000000..d39c97291db
--- /dev/null
+++ b/spec/features/projects/confluence/user_views_confluence_page_spec.rb
@@ -0,0 +1,28 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'User views the Confluence page' do
+ let_it_be(:user) { create(:user) }
+ let(:project) { create(:project, :public) }
+
+ before do
+ sign_in(user)
+ end
+
+ it 'shows the page when the Confluence integration is enabled' do
+ service = create(:confluence_service, project: project)
+
+ visit project_wikis_confluence_path(project)
+
+ element = page.find('.row.empty-state')
+
+ expect(element).to have_link('Go to Confluence', href: service.confluence_url)
+ end
+
+ it 'does not show the page when the Confluence integration disabled' do
+ visit project_wikis_confluence_path(project)
+
+ expect(page).to have_gitlab_http_status(:not_found)
+ end
+end
diff --git a/spec/features/projects/container_registry_spec.rb b/spec/features/projects/container_registry_spec.rb
index f561149d2ad..7514a26f020 100644
--- a/spec/features/projects/container_registry_spec.rb
+++ b/spec/features/projects/container_registry_spec.rb
@@ -84,7 +84,7 @@ RSpec.describe 'Container Registry', :js do
expect(service).to receive(:execute).with(container_repository) { { status: :success } }
expect(Projects::ContainerRepository::DeleteTagsService).to receive(:new).with(container_repository.project, user, tags: ['1']) { service }
- first('[data-testid="singleDeleteButton"]').click
+ first('[data-testid="single-delete-button"]').click
expect(find('.modal .modal-title')).to have_content _('Remove tag')
find('.modal .modal-footer .btn-danger').click
end
diff --git a/spec/features/projects/environments/environment_metrics_spec.rb b/spec/features/projects/environments/environment_metrics_spec.rb
index a3b979d0f42..c72f88205b5 100644
--- a/spec/features/projects/environments/environment_metrics_spec.rb
+++ b/spec/features/projects/environments/environment_metrics_spec.rb
@@ -28,8 +28,9 @@ RSpec.describe 'Environment > Metrics' do
shared_examples 'has environment selector' do
it 'has a working environment selector', :js do
click_link('See metrics')
-
- expect(page).to have_metrics_path(environment)
+ # TODO: See metrics on the sidebar still points to the old metrics URL
+ # https://gitlab.com/gitlab-org/gitlab/-/issues/229277
+ expect(page).to have_current_path(metrics_project_environment_path(project, id: environment.id))
expect(page).to have_css('[data-qa-selector="environments_dropdown"]')
within('[data-qa-selector="environments_dropdown"]') do
@@ -40,7 +41,7 @@ RSpec.describe 'Environment > Metrics' do
click_on(staging.name)
end
- expect(page).to have_metrics_path(staging)
+ expect(page).to have_current_path(project_metrics_dashboard_path(project, environment: staging.id))
wait_for_requests
end
@@ -67,8 +68,4 @@ RSpec.describe 'Environment > Metrics' do
def visit_environment(environment)
visit project_environment_path(environment.project, environment)
end
-
- def have_metrics_path(environment)
- have_current_path(metrics_project_environment_path(project, id: environment.id))
- end
end
diff --git a/spec/features/projects/environments_pod_logs_spec.rb b/spec/features/projects/environments_pod_logs_spec.rb
index 82dafbc6237..c491cd62d85 100644
--- a/spec/features/projects/environments_pod_logs_spec.rb
+++ b/spec/features/projects/environments_pod_logs_spec.rb
@@ -12,11 +12,12 @@ RSpec.describe 'Environment > Pod Logs', :js, :kubeclient do
let(:service) { create(:cluster_platform_kubernetes, :configured) }
before do
- create(:cluster, :provided_by_gcp, environment_scope: '*', projects: [project])
+ cluster = create(:cluster, :provided_by_gcp, environment_scope: '*', projects: [project])
create(:deployment, :success, environment: environment)
stub_kubeclient_pods(environment.deployment_namespace)
stub_kubeclient_logs(pod_name, environment.deployment_namespace, container: 'container-0')
+ stub_kubeclient_nodes_and_nodes_metrics(cluster.platform.api_url)
sign_in(project.owner)
end
@@ -37,7 +38,7 @@ RSpec.describe 'Environment > Pod Logs', :js, :kubeclient do
dropdown_items = find(".dropdown-menu").all(".dropdown-item")
expect(dropdown_items.first).to have_content(environment.name)
- expect(dropdown_items.size).to eq(2)
+ expect(dropdown_items.size).to eq(3)
end
end
diff --git a/spec/features/projects/files/edit_file_soft_wrap_spec.rb b/spec/features/projects/files/edit_file_soft_wrap_spec.rb
index ede22204dbd..fda024e893d 100644
--- a/spec/features/projects/files/edit_file_soft_wrap_spec.rb
+++ b/spec/features/projects/files/edit_file_soft_wrap_spec.rb
@@ -8,8 +8,9 @@ RSpec.describe 'Projects > Files > User uses soft wrap while editing file', :js
user = project.owner
sign_in user
visit project_new_blob_path(project, 'master', file_name: 'test_file-name')
+
page.within('.file-editor.code') do
- find('.ace_text-input', visible: false).send_keys 'Touch water with paw then recoil in horror chase dog then
+ find('.inputarea', visible: false).send_keys 'Touch water with paw then recoil in horror chase dog then
run away chase the pig around the house eat owner\'s food, and knock
dish off table head butt cant eat out of my own dish. Cat is love, cat
is life rub face on everything poop on grasses so meow. Playing with
@@ -26,17 +27,20 @@ RSpec.describe 'Projects > Files > User uses soft wrap while editing file', :js
it 'user clicks the "Soft wrap" button and then "No wrap" button' do
wrapped_content_width = get_content_width
- toggle_button.click
- expect(toggle_button).to have_content 'No wrap'
- unwrapped_content_width = get_content_width
- expect(unwrapped_content_width).to be < wrapped_content_width
-
- toggle_button.click
- expect(toggle_button).to have_content 'Soft wrap'
- expect(get_content_width).to be > unwrapped_content_width
+
+ toggle_button.click do
+ expect(toggle_button).to have_content 'Soft wrap'
+ unwrapped_content_width = get_content_width
+ expect(unwrapped_content_width).to be > wrapped_content_width
+ end
+
+ toggle_button.click do
+ expect(toggle_button).to have_content 'No wrap'
+ expect(get_content_width).to be < unwrapped_content_width
+ end
end
def get_content_width
- find('.ace_content')[:style].slice!(/width: \d+/).slice!(/\d+/).to_i
+ find('.view-lines', visible: false)[:style].slice!(/width: \d+/).slice!(/\d+/).to_i
end
end
diff --git a/spec/features/projects/files/gitignore_dropdown_spec.rb b/spec/features/projects/files/gitignore_dropdown_spec.rb
index dae1164f7f2..5a39f2bcd98 100644
--- a/spec/features/projects/files/gitignore_dropdown_spec.rb
+++ b/spec/features/projects/files/gitignore_dropdown_spec.rb
@@ -25,6 +25,6 @@ RSpec.describe 'Projects > Files > User wants to add a .gitignore file' do
expect(page).to have_css('.gitignore-selector .dropdown-toggle-text', text: 'Apply a template')
expect(page).to have_content('/.bundle')
- expect(page).to have_content('# Gemfile.lock, .ruby-version, .ruby-gemset')
+ expect(page).to have_content('config/initializers/secret_token.rb')
end
end
diff --git a/spec/features/projects/files/user_browses_files_spec.rb b/spec/features/projects/files/user_browses_files_spec.rb
index 44b5833a8c8..e5259bd88be 100644
--- a/spec/features/projects/files/user_browses_files_spec.rb
+++ b/spec/features/projects/files/user_browses_files_spec.rb
@@ -3,6 +3,8 @@
require "spec_helper"
RSpec.describe "User browses files" do
+ include RepoHelpers
+
let(:fork_message) do
"You're not allowed to make changes to this project directly. "\
"A fork of this project has been created that you can make changes in, so you can submit a merge request."
@@ -339,6 +341,24 @@ RSpec.describe "User browses files" do
end
end
+ context "when browsing a file with pathspec characters" do
+ let(:filename) { ':wq' }
+ let(:newrev) { project.repository.commit('master').sha }
+
+ before do
+ create_file_in_repo(project, 'master', 'master', filename, 'Test file')
+ path = File.join('master', filename)
+
+ visit(project_blob_path(project, path))
+ end
+
+ it "shows a raw file content" do
+ click_link("Open raw")
+
+ expect(source).to eq("") # Body is filled in by gitlab-workhorse
+ end
+ end
+
context "when browsing a raw file" do
before do
path = File.join(RepoHelpers.sample_commit.id, RepoHelpers.sample_blob.path)
diff --git a/spec/features/projects/files/user_creates_files_spec.rb b/spec/features/projects/files/user_creates_files_spec.rb
index cc90f0cf294..5abc048c135 100644
--- a/spec/features/projects/files/user_creates_files_spec.rb
+++ b/spec/features/projects/files/user_creates_files_spec.rb
@@ -14,8 +14,6 @@ RSpec.describe 'Projects > Files > User creates files', :js do
let(:user) { create(:user) }
before do
- stub_feature_flags(web_ide_default: false)
-
project.add_maintainer(user)
sign_in(user)
end
@@ -67,7 +65,7 @@ RSpec.describe 'Projects > Files > User creates files', :js do
file_name = find('#file_name')
file_name.set options[:file_name] || 'README.md'
- find('.ace_text-input', visible: false).send_keys.native.send_keys options[:file_content] || 'Some content'
+ find('.monaco-editor textarea').send_keys.native.send_keys options[:file_content] || 'Some content'
click_button 'Commit changes'
end
@@ -89,7 +87,7 @@ RSpec.describe 'Projects > Files > User creates files', :js do
it 'creates and commit a new file' do
find('#editor')
- execute_script("ace.edit('editor').setValue('*.rbca')")
+ execute_script("monaco.editor.getModels()[0].setValue('*.rbca')")
fill_in(:file_name, with: 'not_a_file.md')
fill_in(:commit_message, with: 'New commit message', visible: true)
click_button('Commit changes')
@@ -105,7 +103,7 @@ RSpec.describe 'Projects > Files > User creates files', :js do
it 'creates and commit a new file with new lines at the end of file' do
find('#editor')
- execute_script('ace.edit("editor").setValue("Sample\n\n\n")')
+ execute_script('monaco.editor.getModels()[0].setValue("Sample\n\n\n")')
fill_in(:file_name, with: 'not_a_file.md')
fill_in(:commit_message, with: 'New commit message', visible: true)
click_button('Commit changes')
@@ -117,7 +115,7 @@ RSpec.describe 'Projects > Files > User creates files', :js do
find('.js-edit-blob').click
find('#editor')
- expect(evaluate_script('ace.edit("editor").getValue()')).to eq("Sample\n\n\n")
+ expect(evaluate_script('monaco.editor.getModels()[0].getValue()')).to eq("Sample\n\n\n")
end
it 'creates and commit a new file with a directory name' do
@@ -126,7 +124,7 @@ RSpec.describe 'Projects > Files > User creates files', :js do
expect(page).to have_selector('.file-editor')
find('#editor')
- execute_script("ace.edit('editor').setValue('*.rbca')")
+ execute_script("monaco.editor.getModels()[0].setValue('*.rbca')")
fill_in(:commit_message, with: 'New commit message', visible: true)
click_button('Commit changes')
@@ -141,7 +139,7 @@ RSpec.describe 'Projects > Files > User creates files', :js do
expect(page).to have_selector('.file-editor')
find('#editor')
- execute_script("ace.edit('editor').setValue('*.rbca')")
+ execute_script("monaco.editor.getModels()[0].setValue('*.rbca')")
fill_in(:file_name, with: 'not_a_file.md')
fill_in(:commit_message, with: 'New commit message', visible: true)
fill_in(:branch_name, with: 'new_branch_name', visible: true)
@@ -176,7 +174,7 @@ RSpec.describe 'Projects > Files > User creates files', :js do
expect(page).to have_selector('.file-editor')
find('#editor')
- execute_script("ace.edit('editor').setValue('*.rbca')")
+ execute_script("monaco.editor.getModels()[0].setValue('*.rbca')")
fill_in(:file_name, with: 'not_a_file.md')
fill_in(:commit_message, with: 'New commit message', visible: true)
diff --git a/spec/features/projects/files/user_edits_files_spec.rb b/spec/features/projects/files/user_edits_files_spec.rb
index 1bb931e35ec..d3e075001c8 100644
--- a/spec/features/projects/files/user_edits_files_spec.rb
+++ b/spec/features/projects/files/user_edits_files_spec.rb
@@ -11,8 +11,6 @@ RSpec.describe 'Projects > Files > User edits files', :js do
let(:user) { create(:user) }
before do
- stub_feature_flags(web_ide_default: false)
-
sign_in(user)
end
@@ -46,9 +44,9 @@ RSpec.describe 'Projects > Files > User edits files', :js do
find('.file-editor', match: :first)
find('#editor')
- execute_script("ace.edit('editor').setValue('*.rbca')")
+ execute_script("monaco.editor.getModels()[0].setValue('*.rbca')")
- expect(evaluate_script('ace.edit("editor").getValue()')).to eq('*.rbca')
+ expect(evaluate_script('monaco.editor.getModels()[0].getValue()')).to eq('*.rbca')
end
it 'does not show the edit link if a file is binary' do
@@ -67,7 +65,7 @@ RSpec.describe 'Projects > Files > User edits files', :js do
find('.file-editor', match: :first)
find('#editor')
- execute_script("ace.edit('editor').setValue('*.rbca')")
+ execute_script("monaco.editor.getModels()[0].setValue('*.rbca')")
fill_in(:commit_message, with: 'New commit message', visible: true)
click_button('Commit changes')
@@ -85,7 +83,7 @@ RSpec.describe 'Projects > Files > User edits files', :js do
find('.file-editor', match: :first)
find('#editor')
- execute_script("ace.edit('editor').setValue('*.rbca')")
+ execute_script("monaco.editor.getModels()[0].setValue('*.rbca')")
fill_in(:commit_message, with: 'New commit message', visible: true)
fill_in(:branch_name, with: 'new_branch_name', visible: true)
click_button('Commit changes')
@@ -103,7 +101,7 @@ RSpec.describe 'Projects > Files > User edits files', :js do
find('.file-editor', match: :first)
find('#editor')
- execute_script("ace.edit('editor').setValue('*.rbca')")
+ execute_script("monaco.editor.getModels()[0].setValue('*.rbca')")
click_link('Preview changes')
expect(page).to have_css('.line_holder.new')
@@ -148,9 +146,9 @@ RSpec.describe 'Projects > Files > User edits files', :js do
find('.file-editor', match: :first)
find('#editor')
- execute_script("ace.edit('editor').setValue('*.rbca')")
+ execute_script("monaco.editor.getModels()[0].setValue('*.rbca')")
- expect(evaluate_script('ace.edit("editor").getValue()')).to eq('*.rbca')
+ expect(evaluate_script('monaco.editor.getModels()[0].getValue()')).to eq('*.rbca')
end
it 'opens the Web IDE in a forked project', :sidekiq_might_not_need_inline do
@@ -178,7 +176,7 @@ RSpec.describe 'Projects > Files > User edits files', :js do
find('.file-editor', match: :first)
find('#editor')
- execute_script("ace.edit('editor').setValue('*.rbca')")
+ execute_script("monaco.editor.getModels()[0].setValue('*.rbca')")
fill_in(:commit_message, with: 'New commit message', visible: true)
click_button('Commit changes')
@@ -207,7 +205,7 @@ RSpec.describe 'Projects > Files > User edits files', :js do
expect(page).not_to have_button('Cancel')
find('#editor')
- execute_script("ace.edit('editor').setValue('*.rbca')")
+ execute_script("monaco.editor.getModels()[0].setValue('*.rbca')")
fill_in(:commit_message, with: 'Another commit', visible: true)
click_button('Commit changes')
diff --git a/spec/features/projects/import_export/test_project_export.tar.gz b/spec/features/projects/import_export/test_project_export.tar.gz
index 730e586b278..b93da033aea 100644
--- a/spec/features/projects/import_export/test_project_export.tar.gz
+++ b/spec/features/projects/import_export/test_project_export.tar.gz
Binary files differ
diff --git a/spec/features/projects/issues/design_management/user_paginates_designs_spec.rb b/spec/features/projects/issues/design_management/user_paginates_designs_spec.rb
index f871ca60596..aff8951d9de 100644
--- a/spec/features/projects/issues/design_management/user_paginates_designs_spec.rb
+++ b/spec/features/projects/issues/design_management/user_paginates_designs_spec.rb
@@ -8,33 +8,57 @@ RSpec.describe 'User paginates issue designs', :js do
let(:project) { create(:project_empty_repo, :public) }
let(:issue) { create(:issue, project: project) }
- before do
- enable_design_management
+ context 'design_management_moved flag disabled' do
+ before do
+ stub_feature_flags(design_management_moved: false)
+ enable_design_management
- create_list(:design, 2, :with_file, issue: issue)
+ create_list(:design, 2, :with_file, issue: issue)
+ visit project_issue_path(project, issue)
+ click_link 'Designs'
+ wait_for_requests
+ find('.js-design-list-item', match: :first).click
+ end
- visit project_issue_path(project, issue)
+ it 'paginates to next design' do
+ expect(find('.js-previous-design')[:disabled]).to eq('true')
- click_link 'Designs'
+ page.within(find('.js-design-header')) do
+ expect(page).to have_content('1 of 2')
+ end
- wait_for_requests
+ find('.js-next-design').click
- find('.js-design-list-item', match: :first).click
- end
+ expect(find('.js-previous-design')[:disabled]).not_to eq('true')
- it 'paginates to next design' do
- expect(find('.js-previous-design')[:disabled]).to eq('true')
+ page.within(find('.js-design-header')) do
+ expect(page).to have_content('2 of 2')
+ end
+ end
+ end
- page.within(find('.js-design-header')) do
- expect(page).to have_content('1 of 2')
+ context 'design_management_moved flag enabled' do
+ before do
+ enable_design_management
+ create_list(:design, 2, :with_file, issue: issue)
+ visit project_issue_path(project, issue)
+ find('.js-design-list-item', match: :first).click
end
- find('.js-next-design').click
+ it 'paginates to next design' do
+ expect(find('.js-previous-design')[:disabled]).to eq('true')
+
+ page.within(find('.js-design-header')) do
+ expect(page).to have_content('1 of 2')
+ end
+
+ find('.js-next-design').click
- expect(find('.js-previous-design')[:disabled]).not_to eq('true')
+ expect(find('.js-previous-design')[:disabled]).not_to eq('true')
- page.within(find('.js-design-header')) do
- expect(page).to have_content('2 of 2')
+ page.within(find('.js-design-header')) do
+ expect(page).to have_content('2 of 2')
+ end
end
end
end
diff --git a/spec/features/projects/issues/design_management/user_permissions_upload_spec.rb b/spec/features/projects/issues/design_management/user_permissions_upload_spec.rb
index 902a84afc83..4e45312eac3 100644
--- a/spec/features/projects/issues/design_management/user_permissions_upload_spec.rb
+++ b/spec/features/projects/issues/design_management/user_permissions_upload_spec.rb
@@ -8,17 +8,32 @@ RSpec.describe 'User design permissions', :js do
let(:project) { create(:project_empty_repo, :public) }
let(:issue) { create(:issue, project: project) }
- before do
- enable_design_management
+ context 'design_management_moved flag disabled' do
+ before do
+ enable_design_management
+ stub_feature_flags(design_management_moved: false)
- visit project_issue_path(project, issue)
+ visit project_issue_path(project, issue)
- click_link 'Designs'
+ click_link 'Designs'
- wait_for_requests
+ wait_for_requests
+ end
+
+ it 'user does not have permissions to upload design' do
+ expect(page).not_to have_field('design_file')
+ end
end
- it 'user does not have permissions to upload design' do
- expect(page).not_to have_field('design_file')
+ context 'design_management_moved flag enabled' do
+ before do
+ enable_design_management
+
+ visit project_issue_path(project, issue)
+ end
+
+ it 'user does not have permissions to upload design' do
+ expect(page).not_to have_field('design_file')
+ end
end
end
diff --git a/spec/features/projects/issues/design_management/user_uploads_designs_spec.rb b/spec/features/projects/issues/design_management/user_uploads_designs_spec.rb
index 66b449a9de5..2381e00972f 100644
--- a/spec/features/projects/issues/design_management/user_uploads_designs_spec.rb
+++ b/spec/features/projects/issues/design_management/user_uploads_designs_spec.rb
@@ -13,10 +13,10 @@ RSpec.describe 'User uploads new design', :js do
sign_in(user)
end
- context "when the feature is available" do
+ context 'design_management_moved flag disabled' do
before do
- enable_design_management
-
+ enable_design_management(feature_enabled)
+ stub_feature_flags(design_management_moved: false)
visit project_issue_path(project, issue)
click_link 'Designs'
@@ -24,32 +24,64 @@ RSpec.describe 'User uploads new design', :js do
wait_for_requests
end
- it 'uploads designs' do
- attach_file(:design_file, logo_fixture, make_visible: true)
+ context "when the feature is available" do
+ let(:feature_enabled) { true }
+
+ it 'uploads designs' do
+ attach_file(:design_file, logo_fixture, make_visible: true)
+
+ expect(page).to have_selector('.js-design-list-item', count: 1)
+
+ within first('#designs-tab .js-design-list-item') do
+ expect(page).to have_content('dk.png')
+ end
- expect(page).to have_selector('.js-design-list-item', count: 1)
+ attach_file(:design_file, gif_fixture, make_visible: true)
- within first('#designs-tab .js-design-list-item') do
- expect(page).to have_content('dk.png')
+ expect(page).to have_selector('.js-design-list-item', count: 2)
end
+ end
- attach_file(:design_file, gif_fixture, make_visible: true)
+ context 'when the feature is not available' do
+ let(:feature_enabled) { false }
- expect(page).to have_selector('.js-design-list-item', count: 2)
+ it 'shows the message about requirements' do
+ expect(page).to have_content("To enable design management, you'll need to meet the requirements.")
+ end
end
end
- context 'when the feature is not available' do
+ context 'design_management_moved flag enabled' do
before do
+ enable_design_management(feature_enabled)
+ stub_feature_flags(design_management_moved: true)
visit project_issue_path(project, issue)
+ end
- click_link 'Designs'
+ context "when the feature is available" do
+ let(:feature_enabled) { true }
- wait_for_requests
+ it 'uploads designs', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/225616' do
+ attach_file(:design_file, logo_fixture, make_visible: true)
+
+ expect(page).to have_selector('.js-design-list-item', count: 1)
+
+ within first('[data-testid="designs-root"] .js-design-list-item') do
+ expect(page).to have_content('dk.png')
+ end
+
+ attach_file(:design_file, gif_fixture, make_visible: true)
+
+ expect(page).to have_selector('.js-design-list-item', count: 2)
+ end
end
- it 'shows the message about requirements' do
- expect(page).to have_content("To enable design management, you'll need to meet the requirements.")
+ context 'when the feature is not available' do
+ let(:feature_enabled) { false }
+
+ it 'shows the message about requirements' do
+ expect(page).to have_content("To enable design management, you'll need to meet the requirements.")
+ end
end
end
diff --git a/spec/features/projects/issues/design_management/user_views_design_spec.rb b/spec/features/projects/issues/design_management/user_views_design_spec.rb
index 527442d5339..49245218e81 100644
--- a/spec/features/projects/issues/design_management/user_views_design_spec.rb
+++ b/spec/features/projects/issues/design_management/user_views_design_spec.rb
@@ -9,21 +9,42 @@ RSpec.describe 'User views issue designs', :js do
let_it_be(:issue) { create(:issue, project: project) }
let_it_be(:design) { create(:design, :with_file, issue: issue) }
- before do
- enable_design_management
+ context 'design_management_moved flag disabled' do
+ before do
+ enable_design_management
+ stub_feature_flags(design_management_moved: false)
- visit project_issue_path(project, issue)
+ visit project_issue_path(project, issue)
- click_link 'Designs'
+ click_link 'Designs'
+ end
+
+ it 'opens design detail' do
+ click_link design.filename
+
+ page.within(find('.js-design-header')) do
+ expect(page).to have_content(design.filename)
+ end
+
+ expect(page).to have_selector('.js-design-image')
+ end
end
- it 'opens design detail' do
- click_link design.filename
+ context 'design_management_moved flag enabled' do
+ before do
+ enable_design_management
- page.within(find('.js-design-header')) do
- expect(page).to have_content(design.filename)
+ visit project_issue_path(project, issue)
end
- expect(page).to have_selector('.js-design-image')
+ it 'opens design detail' do
+ click_link design.filename
+
+ page.within(find('.js-design-header')) do
+ expect(page).to have_content(design.filename)
+ end
+
+ expect(page).to have_selector('.js-design-image')
+ end
end
end
diff --git a/spec/features/projects/issues/design_management/user_views_designs_spec.rb b/spec/features/projects/issues/design_management/user_views_designs_spec.rb
index d371ae1aad7..772a9ffbe6f 100644
--- a/spec/features/projects/issues/design_management/user_views_designs_spec.rb
+++ b/spec/features/projects/issues/design_management/user_views_designs_spec.rb
@@ -9,39 +9,78 @@ RSpec.describe 'User views issue designs', :js do
let_it_be(:issue) { create(:issue, project: project) }
let_it_be(:design) { create(:design, :with_file, issue: issue) }
- before do
- enable_design_management
- end
-
- context 'navigates from the issue view' do
+ context 'design_management_moved flag disabled' do
before do
- visit project_issue_path(project, issue)
- click_link 'Designs'
- wait_for_requests
+ enable_design_management
+ stub_feature_flags(design_management_moved: false)
end
- it 'fetches list of designs' do
- expect(page).to have_selector('.js-design-list-item', count: 1)
+ context 'navigates from the issue view' do
+ before do
+ visit project_issue_path(project, issue)
+ click_link 'Designs'
+ wait_for_requests
+ end
+
+ it 'fetches list of designs' do
+ expect(page).to have_selector('.js-design-list-item', count: 1)
+ end
end
- end
- context 'navigates directly to the design collection view' do
- before do
- visit designs_project_issue_path(project, issue)
+ context 'navigates directly to the design collection view' do
+ before do
+ visit designs_project_issue_path(project, issue)
+ end
+
+ it 'expands the sidebar' do
+ expect(page).to have_selector('.layout-page.right-sidebar-expanded')
+ end
end
- it 'expands the sidebar' do
- expect(page).to have_selector('.layout-page.right-sidebar-expanded')
+ context 'navigates directly to the individual design view' do
+ before do
+ visit designs_project_issue_path(project, issue, vueroute: design.filename)
+ end
+
+ it 'sees the design' do
+ expect(page).to have_selector('.js-design-detail')
+ end
end
end
- context 'navigates directly to the individual design view' do
+ context 'design_management_moved flag enabled' do
before do
- visit designs_project_issue_path(project, issue, vueroute: design.filename)
+ enable_design_management
end
- it 'sees the design' do
- expect(page).to have_selector('.js-design-detail')
+ context 'navigates from the issue view' do
+ before do
+ visit project_issue_path(project, issue)
+ end
+
+ it 'fetches list of designs' do
+ expect(page).to have_selector('.js-design-list-item', count: 1)
+ end
+ end
+
+ context 'navigates directly to the design collection view' do
+ before do
+ visit designs_project_issue_path(project, issue)
+ end
+
+ it 'expands the sidebar' do
+ expect(page).to have_selector('.layout-page.right-sidebar-expanded')
+ end
+ end
+
+ context 'navigates directly to the individual design view' do
+ before do
+ visit designs_project_issue_path(project, issue, vueroute: design.filename)
+ end
+
+ it 'sees the design' do
+ expect(page).to have_selector('.js-design-detail')
+ end
end
end
end
diff --git a/spec/features/projects/issues/design_management/user_views_designs_with_svg_xss_spec.rb b/spec/features/projects/issues/design_management/user_views_designs_with_svg_xss_spec.rb
index 5bc1271309c..0fe84ab47ed 100644
--- a/spec/features/projects/issues/design_management/user_views_designs_with_svg_xss_spec.rb
+++ b/spec/features/projects/issues/design_management/user_views_designs_with_svg_xss_spec.rb
@@ -29,6 +29,7 @@ RSpec.describe 'User views an SVG design that contains XSS', :js do
end
it 'displays the SVG' do
+ find("[data-testid='close-design']").click
expect(page).to have_selector("img.design-img[alt='xss.svg']", count: 1, visible: false)
end
diff --git a/spec/features/projects/jobs_spec.rb b/spec/features/projects/jobs_spec.rb
index e78e8989575..62e8997f6cb 100644
--- a/spec/features/projects/jobs_spec.rb
+++ b/spec/features/projects/jobs_spec.rb
@@ -837,7 +837,7 @@ RSpec.describe 'Jobs', :clean_gitlab_redis_shared_state do
it 'renders empty state' do
expect(page).to have_content(job.detailed_status(user).illustration[:title])
- expect(page).not_to have_selector('.js-build-trace')
+ expect(page).not_to have_selector('.job-log')
expect(page).to have_content('This job has been canceled')
end
end
@@ -852,7 +852,7 @@ RSpec.describe 'Jobs', :clean_gitlab_redis_shared_state do
it 'renders empty state' do
expect(page).to have_content(job.detailed_status(user).illustration[:title])
- expect(page).not_to have_selector('.js-build-trace')
+ expect(page).not_to have_selector('.job-log')
expect(page).to have_content('This job has been skipped')
end
end
diff --git a/spec/features/projects/labels/issues_sorted_by_priority_spec.rb b/spec/features/projects/labels/issues_sorted_by_priority_spec.rb
index 66d61e629df..85a08c441ca 100644
--- a/spec/features/projects/labels/issues_sorted_by_priority_spec.rb
+++ b/spec/features/projects/labels/issues_sorted_by_priority_spec.rb
@@ -14,7 +14,7 @@ RSpec.describe 'Issue prioritization' do
let(:label_5) { create(:label, title: 'label_5', project: project) } # no priority
# According to https://gitlab.com/gitlab-org/gitlab-foss/issues/14189#note_4360653
- context 'when issues have one label' do
+ context 'when issues have one label', :js do
it 'Are sorted properly' do
# Issues
issue_1 = create(:issue, title: 'issue_1', project: project)
@@ -44,7 +44,7 @@ RSpec.describe 'Issue prioritization' do
end
end
- context 'when issues have multiple labels' do
+ context 'when issues have multiple labels', :js do
it 'Are sorted properly' do
# Issues
issue_1 = create(:issue, title: 'issue_1', project: project)
diff --git a/spec/features/projects/members/list_spec.rb b/spec/features/projects/members/list_spec.rb
index f51ebde8f80..56b807e08d7 100644
--- a/spec/features/projects/members/list_spec.rb
+++ b/spec/features/projects/members/list_spec.rb
@@ -64,9 +64,12 @@ RSpec.describe 'Project members list' do
visit_members_page
- accept_confirm do
- find(:css, 'li.project_member', text: other_user.name).find(:css, 'a.btn-remove').click
- end
+ # Open modal
+ find(:css, 'li.project_member', text: other_user.name).find(:css, 'button.btn-remove').click
+
+ expect(page).to have_unchecked_field 'Also unassign this user from related issues and merge requests'
+
+ click_on('Remove member')
wait_for_requests
diff --git a/spec/features/projects/members/member_leaves_project_spec.rb b/spec/features/projects/members/member_leaves_project_spec.rb
index aa7633c3b28..c4bd0b81dc0 100644
--- a/spec/features/projects/members/member_leaves_project_spec.rb
+++ b/spec/features/projects/members/member_leaves_project_spec.rb
@@ -20,7 +20,7 @@ RSpec.describe 'Projects > Members > Member leaves project' do
expect(project.users.exists?(user.id)).to be_falsey
end
- it 'user leaves project by url param', :js, :quarantine do
+ it 'user leaves project by url param', :js, quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/35925' do
visit project_path(project, leave: 1)
page.accept_confirm
diff --git a/spec/features/projects/navbar_spec.rb b/spec/features/projects/navbar_spec.rb
index 94d79d60aeb..22cd832ff06 100644
--- a/spec/features/projects/navbar_spec.rb
+++ b/spec/features/projects/navbar_spec.rb
@@ -12,8 +12,6 @@ RSpec.describe 'Project navbar' do
let_it_be(:project) { create(:project, :repository) }
before do
- stub_licensed_features(service_desk: false)
-
project.add_maintainer(user)
sign_in(user)
end
diff --git a/spec/features/projects/pipeline_schedules_spec.rb b/spec/features/projects/pipeline_schedules_spec.rb
index 921bbbfbe7d..1e2cd3c0a69 100644
--- a/spec/features/projects/pipeline_schedules_spec.rb
+++ b/spec/features/projects/pipeline_schedules_spec.rb
@@ -3,8 +3,6 @@
require 'spec_helper'
RSpec.describe 'Pipeline Schedules', :js do
- include PipelineSchedulesHelper
-
let!(:project) { create(:project, :repository) }
let!(:pipeline_schedule) { create(:ci_pipeline_schedule, :nightly, project: project ) }
let!(:pipeline) { create(:ci_pipeline, pipeline_schedule: pipeline_schedule) }
diff --git a/spec/features/projects/pipelines/pipeline_spec.rb b/spec/features/projects/pipelines/pipeline_spec.rb
index c6a002ad18b..2ca584ab8f6 100644
--- a/spec/features/projects/pipelines/pipeline_spec.rb
+++ b/spec/features/projects/pipelines/pipeline_spec.rb
@@ -111,7 +111,7 @@ RSpec.describe 'Pipeline', :js do
end
context 'when there is one related merge request' do
- before do
+ let!(:merge_request) do
create(:merge_request,
source_project: project,
source_branch: pipeline.ref)
@@ -123,7 +123,7 @@ RSpec.describe 'Pipeline', :js do
within '.related-merge-requests' do
expect(page).to have_content('1 related merge request: ')
expect(page).to have_selector('.js-truncated-mr-list')
- expect(page).to have_link('!1 My title 1')
+ expect(page).to have_link("#{merge_request.to_reference} #{merge_request.title}")
expect(page).not_to have_selector('.js-full-mr-list')
expect(page).not_to have_selector('.text-expander')
@@ -132,9 +132,16 @@ RSpec.describe 'Pipeline', :js do
end
context 'when there are two related merge requests' do
- before do
- create(:merge_request, source_project: project, source_branch: pipeline.ref)
- create(:merge_request, source_project: project, source_branch: pipeline.ref, target_branch: 'fix')
+ let!(:merge_request1) do
+ create(:merge_request,
+ source_project: project,
+ source_branch: pipeline.ref)
+ end
+ let!(:merge_request2) do
+ create(:merge_request,
+ source_project: project,
+ source_branch: pipeline.ref,
+ target_branch: 'fix')
end
it 'links to the most recent related merge request' do
@@ -142,7 +149,7 @@ RSpec.describe 'Pipeline', :js do
within '.related-merge-requests' do
expect(page).to have_content('2 related merge requests: ')
- expect(page).to have_link('!2 My title 3')
+ expect(page).to have_link("#{merge_request2.to_reference} #{merge_request2.title}")
expect(page).to have_selector('.text-expander')
expect(page).to have_selector('.js-full-mr-list', visible: false)
end
@@ -354,37 +361,68 @@ RSpec.describe 'Pipeline', :js do
end
describe 'test tabs' do
- let(:pipeline) { create(:ci_pipeline, :with_test_reports, project: project) }
+ let(:pipeline) { create(:ci_pipeline, :with_test_reports, :with_report_results, project: project) }
- before do
- visit_pipeline
- wait_for_requests
- end
+ context 'with build_report_summary feature flag disabled' do
+ before do
+ stub_feature_flags(build_report_summary: false)
+ visit_pipeline
+ wait_for_requests
+ end
+
+ context 'with test reports' do
+ it 'shows badge counter in Tests tab' do
+ expect(pipeline.test_reports.total_count).to eq(4)
+ expect(page.find('.js-test-report-badge-counter').text).to eq(pipeline.test_reports.total_count.to_s)
+ end
- context 'with test reports' do
- it 'shows badge counter in Tests tab' do
- expect(pipeline.test_reports.total_count).to eq(4)
- expect(page.find('.js-test-report-badge-counter').text).to eq(pipeline.test_reports.total_count.to_s)
+ it 'does not call test_report.json endpoint by default', :js do
+ expect(page).to have_selector('.js-no-tests-to-show', visible: :all)
+ end
+
+ it 'does call test_report.json endpoint when tab is selected', :js do
+ find('.js-tests-tab-link').click
+ wait_for_requests
+
+ expect(page).to have_content('Jobs')
+ expect(page).to have_selector('.js-tests-detail', visible: :all)
+ end
end
- it 'does not call test_report.json endpoint by default', :js do
- expect(page).to have_selector('.js-no-tests-to-show', visible: :all)
+ context 'without test reports' do
+ let(:pipeline) { create(:ci_pipeline, project: project) }
+
+ it 'shows zero' do
+ expect(page.find('.js-test-report-badge-counter', visible: :all).text).to eq("0")
+ end
end
+ end
- it 'does call test_report.json endpoint when tab is selected', :js do
- find('.js-tests-tab-link').click
+ context 'with build_report_summary feature flag enabled' do
+ before do
+ visit_pipeline
wait_for_requests
+ end
+
+ context 'with test reports' do
+ it 'shows badge counter in Tests tab' do
+ expect(page.find('.js-test-report-badge-counter').text).to eq(pipeline.test_report_summary.total_count.to_s)
+ end
- expect(page).to have_content('Test suites')
- expect(page).to have_selector('.js-tests-detail', visible: :all)
+ it 'calls summary.json endpoint', :js do
+ find('.js-tests-tab-link').click
+
+ expect(page).to have_content('Jobs')
+ expect(page).to have_selector('.js-tests-detail', visible: :all)
+ end
end
- end
- context 'without test reports' do
- let(:pipeline) { create(:ci_pipeline, project: project) }
+ context 'without test reports' do
+ let(:pipeline) { create(:ci_pipeline, project: project) }
- it 'shows nothing' do
- expect(page.find('.js-test-report-badge-counter', visible: :all).text).to eq("")
+ it 'shows zero' do
+ expect(page.find('.js-test-report-badge-counter', visible: :all).text).to eq("0")
+ end
end
end
end
diff --git a/spec/features/projects/pipelines/pipelines_spec.rb b/spec/features/projects/pipelines/pipelines_spec.rb
index 0e33204f851..0eb92f3e679 100644
--- a/spec/features/projects/pipelines/pipelines_spec.rb
+++ b/spec/features/projects/pipelines/pipelines_spec.rb
@@ -65,19 +65,8 @@ RSpec.describe 'Pipelines', :js do
expect(page.find('.js-pipelines-tab-all .badge').text).to include('1')
end
- it 'shows a tab for Pending pipelines and count' do
- expect(page.find('.js-pipelines-tab-pending').text).to include('Pending')
- expect(page.find('.js-pipelines-tab-pending .badge').text).to include('0')
- end
-
- it 'shows a tab for Running pipelines and count' do
- expect(page.find('.js-pipelines-tab-running').text).to include('Running')
- expect(page.find('.js-pipelines-tab-running .badge').text).to include('1')
- end
-
it 'shows a tab for Finished pipelines and count' do
expect(page.find('.js-pipelines-tab-finished').text).to include('Finished')
- expect(page.find('.js-pipelines-tab-finished .badge').text).to include('0')
end
it 'shows a tab for Branches' do
@@ -89,9 +78,9 @@ RSpec.describe 'Pipelines', :js do
end
it 'updates content when tab is clicked' do
- page.find('.js-pipelines-tab-pending').click
+ page.find('.js-pipelines-tab-finished').click
wait_for_requests
- expect(page).to have_content('There are currently no pending pipelines.')
+ expect(page).to have_content('There are currently no finished pipelines.')
end
end
@@ -539,7 +528,7 @@ RSpec.describe 'Pipelines', :js do
end
it 'renders a mini pipeline graph' do
- expect(page).to have_selector('.js-mini-pipeline-graph')
+ expect(page).to have_selector('[data-testid="widget-mini-pipeline-graph"]')
expect(page).to have_selector('.js-builds-dropdown-button')
end
diff --git a/spec/features/projects/services/disable_triggers_spec.rb b/spec/features/projects/services/disable_triggers_spec.rb
index 8f87d0e7ff1..b3a3d7f0622 100644
--- a/spec/features/projects/services/disable_triggers_spec.rb
+++ b/spec/features/projects/services/disable_triggers_spec.rb
@@ -5,7 +5,7 @@ require 'spec_helper'
RSpec.describe 'Disable individual triggers', :js do
include_context 'project service activation'
- let(:checkbox_selector) { 'input[type=checkbox][name$="_events]"]' }
+ let(:checkbox_selector) { 'input[name$="_events]"]' }
before do
visit_project_integration(service_name)
@@ -18,7 +18,7 @@ RSpec.describe 'Disable individual triggers', :js do
event_count = HipchatService.supported_events.count
expect(page).to have_content "Trigger"
- expect(page).to have_css(checkbox_selector, count: event_count)
+ expect(page).to have_css(checkbox_selector, visible: :all, count: event_count)
end
end
@@ -27,7 +27,7 @@ RSpec.describe 'Disable individual triggers', :js do
it "doesn't show unnecessary Trigger checkboxes" do
expect(page).not_to have_content "Trigger"
- expect(page).not_to have_css(checkbox_selector)
+ expect(page).not_to have_css(checkbox_selector, visible: :all)
end
end
end
diff --git a/spec/features/projects/services/user_activates_alerts_spec.rb b/spec/features/projects/services/user_activates_alerts_spec.rb
index 95642f49d61..8b0acdf3618 100644
--- a/spec/features/projects/services/user_activates_alerts_spec.rb
+++ b/spec/features/projects/services/user_activates_alerts_spec.rb
@@ -15,35 +15,32 @@ RSpec.describe 'User activates Alerts', :js do
end
context 'when service is deactivated' do
- it 'activates service' do
+ it 'user cannot activate service' do
visit_project_services
expect(page).to have_link(service_title)
click_link(service_title)
+ expect(page).to have_callout_message
expect(page).not_to have_active_service
-
- click_activate_service
- wait_for_requests
-
- expect(page).to have_active_service
+ expect(page).to have_toggle_active_disabled
end
end
context 'when service is activated' do
+ let_it_be(:activated_alerts_service) do
+ create(:alerts_service, :active, project: project)
+ end
+
before do
visit_alerts_service
- click_activate_service
end
- it 're-generates key' do
- expect(reset_key.value).to be_blank
-
- click_reset_key
- click_confirm_reset_key
- wait_for_requests
-
- expect(reset_key.value).to be_present
+ it 'user cannot change settings' do
+ expect(page).to have_callout_message
+ expect(page).to have_active_service
+ expect(page).to have_toggle_active_disabled
+ expect(page).to have_button_reset_key_disabled
end
end
@@ -57,25 +54,21 @@ RSpec.describe 'User activates Alerts', :js do
visit(edit_project_service_path(project, service_name))
end
- def click_activate_service
- find('#activated').click
- end
-
- def click_reset_key
- click_button('Reset key')
+ def have_callout_message
+ within('.gl-alert') do
+ have_content('You can now manage alert endpoint configuration in the Alerts section on the Operations settings page.')
+ end
end
- def click_confirm_reset_key
- within '.modal-content' do
- click_reset_key
- end
+ def have_active_service
+ have_selector('.js-service-active-status[data-value="true"]')
end
- def reset_key
- find_field('Authorization key')
+ def have_toggle_active_disabled
+ have_selector('#activated .project-feature-toggle.is-disabled')
end
- def have_active_service
- have_selector('.js-service-active-status[data-value="true"]')
+ def have_button_reset_key_disabled
+ have_button('Reset key', disabled: true)
end
end
diff --git a/spec/features/projects/services/user_activates_issue_tracker_spec.rb b/spec/features/projects/services/user_activates_issue_tracker_spec.rb
index a2a2604c610..4f25794d058 100644
--- a/spec/features/projects/services/user_activates_issue_tracker_spec.rb
+++ b/spec/features/projects/services/user_activates_issue_tracker_spec.rb
@@ -30,7 +30,7 @@ RSpec.describe 'User activates issue tracker', :js do
it 'activates the service' do
expect(page).to have_content("#{tracker} activated.")
- expect(current_path).to eq(project_settings_integrations_path(project))
+ expect(current_path).to eq(edit_project_service_path(project, tracker.parameterize(separator: '_')))
end
it 'shows the link in the menu' do
@@ -50,7 +50,7 @@ RSpec.describe 'User activates issue tracker', :js do
click_test_then_save_integration
expect(page).to have_content("#{tracker} activated.")
- expect(current_path).to eq(project_settings_integrations_path(project))
+ expect(current_path).to eq(edit_project_service_path(project, tracker.parameterize(separator: '_')))
end
end
end
@@ -65,7 +65,7 @@ RSpec.describe 'User activates issue tracker', :js do
it 'saves but does not activate the service' do
expect(page).to have_content("#{tracker} settings saved, but not activated.")
- expect(current_path).to eq(project_settings_integrations_path(project))
+ expect(current_path).to eq(edit_project_service_path(project, tracker.parameterize(separator: '_')))
end
it 'does not show the external tracker link in the menu' do
diff --git a/spec/features/projects/services/user_activates_jira_spec.rb b/spec/features/projects/services/user_activates_jira_spec.rb
index 1da8a49699b..483671c4b5b 100644
--- a/spec/features/projects/services/user_activates_jira_spec.rb
+++ b/spec/features/projects/services/user_activates_jira_spec.rb
@@ -4,18 +4,7 @@ require 'spec_helper'
RSpec.describe 'User activates Jira', :js do
include_context 'project service activation'
-
- let(:url) { 'http://jira.example.com' }
- let(:test_url) { 'http://jira.example.com/rest/api/2/serverInfo' }
-
- def fill_form(disable: false)
- click_active_toggle if disable
-
- fill_in 'service_url', with: url
- fill_in 'service_username', with: 'username'
- fill_in 'service_password', with: 'password'
- fill_in 'service_jira_issue_transition_id', with: '25'
- end
+ include_context 'project service Jira context'
describe 'user sets and activates Jira Service' do
context 'when Jira connection test succeeds' do
@@ -30,12 +19,17 @@ RSpec.describe 'User activates Jira', :js do
it 'activates the Jira service' do
expect(page).to have_content('Jira activated.')
- expect(current_path).to eq(project_settings_integrations_path(project))
+ expect(current_path).to eq(edit_project_service_path(project, :jira))
end
- it 'shows the Jira link in the menu' do
- page.within('.nav-sidebar') do
- expect(page).to have_link('Jira', href: url)
+ unless Gitlab.ee?
+ it 'adds Jira link to sidebar menu' do
+ page.within('.nav-sidebar') do
+ expect(page).not_to have_link('Jira Issues')
+ expect(page).not_to have_link('Issue List', visible: false)
+ expect(page).not_to have_link('Open Jira', href: url, visible: false)
+ expect(page).to have_link('Jira', href: url)
+ end
end
end
end
@@ -61,7 +55,7 @@ RSpec.describe 'User activates Jira', :js do
click_test_then_save_integration
expect(page).to have_content('Jira activated.')
- expect(current_path).to eq(project_settings_integrations_path(project))
+ expect(current_path).to eq(edit_project_service_path(project, :jira))
end
end
end
@@ -75,7 +69,7 @@ RSpec.describe 'User activates Jira', :js do
it 'saves but does not activate the Jira service' do
expect(page).to have_content('Jira settings saved, but not activated.')
- expect(current_path).to eq(project_settings_integrations_path(project))
+ expect(current_path).to eq(edit_project_service_path(project, :jira))
end
it 'does not show the Jira link in the menu' do
diff --git a/spec/features/projects/services/user_activates_mattermost_slash_command_spec.rb b/spec/features/projects/services/user_activates_mattermost_slash_command_spec.rb
index a6b4aaccfb5..6ddffb710a8 100644
--- a/spec/features/projects/services/user_activates_mattermost_slash_command_spec.rb
+++ b/spec/features/projects/services/user_activates_mattermost_slash_command_spec.rb
@@ -31,7 +31,7 @@ RSpec.describe 'Set up Mattermost slash commands', :js do
click_active_toggle
click_on 'Save changes'
- expect(current_path).to eq(project_settings_integrations_path(project))
+ expect(current_path).to eq(edit_project_service_path(project, :mattermost_slash_commands))
expect(page).to have_content('Mattermost slash commands settings saved, but not activated.')
end
@@ -41,7 +41,7 @@ RSpec.describe 'Set up Mattermost slash commands', :js do
fill_in 'service_token', with: token
click_on 'Save changes'
- expect(current_path).to eq(project_settings_integrations_path(project))
+ expect(current_path).to eq(edit_project_service_path(project, :mattermost_slash_commands))
expect(page).to have_content('Mattermost slash commands activated.')
end
diff --git a/spec/features/projects/services/user_activates_slack_slash_command_spec.rb b/spec/features/projects/services/user_activates_slack_slash_command_spec.rb
index 360e462b935..afe6855d6ad 100644
--- a/spec/features/projects/services/user_activates_slack_slash_command_spec.rb
+++ b/spec/features/projects/services/user_activates_slack_slash_command_spec.rb
@@ -24,7 +24,7 @@ RSpec.describe 'Slack slash commands', :js do
click_active_toggle
click_on 'Save'
- expect(current_path).to eq(project_settings_integrations_path(project))
+ expect(current_path).to eq(edit_project_service_path(project, :slack_slash_commands))
expect(page).to have_content('Slack slash commands settings saved, but not activated.')
end
@@ -32,7 +32,7 @@ RSpec.describe 'Slack slash commands', :js do
fill_in 'Token', with: 'token'
click_on 'Save'
- expect(current_path).to eq(project_settings_integrations_path(project))
+ expect(current_path).to eq(edit_project_service_path(project, :slack_slash_commands))
expect(page).to have_content('Slack slash commands activated.')
end
diff --git a/spec/features/projects/settings/operations_settings_spec.rb b/spec/features/projects/settings/operations_settings_spec.rb
index dfbb6342173..878794bd897 100644
--- a/spec/features/projects/settings/operations_settings_spec.rb
+++ b/spec/features/projects/settings/operations_settings_spec.rb
@@ -45,15 +45,12 @@ RSpec.describe 'Projects > Settings > For a forked project', :js do
it 'updates form values' do
check(create_issue)
- template_select = find_field('Issue template')
- template_select.find(:xpath, 'option[2]').select_option
uncheck(send_email)
save_form
click_expand_incident_management_button
expect(find_field(create_issue)).to be_checked
- expect(page).to have_select('Issue template', selected: 'bug')
expect(find_field(send_email)).not_to be_checked
end
@@ -64,7 +61,7 @@ RSpec.describe 'Projects > Settings > For a forked project', :js do
end
def save_form
- page.within "#edit_project_#{project.id}" do
+ page.within ".qa-incident-management-settings" do
click_on 'Save changes'
end
end
diff --git a/spec/features/projects/settings/registry_settings_spec.rb b/spec/features/projects/settings/registry_settings_spec.rb
index 3dcb7ca54a1..8e2f97fd6a0 100644
--- a/spec/features/projects/settings/registry_settings_spec.rb
+++ b/spec/features/projects/settings/registry_settings_spec.rb
@@ -20,10 +20,10 @@ RSpec.describe 'Project > Settings > CI/CD > Container registry tag expiration p
it 'shows available section' do
settings_block = find('#js-registry-policies')
- expect(settings_block).to have_text 'Container Registry tag expiration policy'
+ expect(settings_block).to have_text 'Cleanup policy for tags'
end
- it 'saves expiration policy submit the form' do
+ it 'saves cleanup policy submit the form' do
within '#js-registry-policies' do
within '.card-body' do
select('7 days until tags are automatically removed', from: 'Expiration interval:')
@@ -36,10 +36,10 @@ RSpec.describe 'Project > Settings > CI/CD > Container registry tag expiration p
submit_button.click
end
toast = find('.gl-toast')
- expect(toast).to have_content('Expiration policy successfully saved.')
+ expect(toast).to have_content('Cleanup policy successfully saved.')
end
- it 'does not save expiration policy submit form with invalid regex' do
+ it 'does not save cleanup policy submit form with invalid regex' do
within '#js-registry-policies' do
within '.card-body' do
fill_in('Tags with names matching this regex pattern will expire:', with: '*-production')
@@ -49,7 +49,7 @@ RSpec.describe 'Project > Settings > CI/CD > Container registry tag expiration p
submit_button.click
end
toast = find('.gl-toast')
- expect(toast).to have_content('Something went wrong while updating the expiration policy.')
+ expect(toast).to have_content('Something went wrong while updating the cleanup policy.')
end
end
diff --git a/spec/features/projects/settings/service_desk_setting_spec.rb b/spec/features/projects/settings/service_desk_setting_spec.rb
new file mode 100644
index 00000000000..7856ab1fb4e
--- /dev/null
+++ b/spec/features/projects/settings/service_desk_setting_spec.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'Service Desk Setting', :js do
+ let(:project) { create(:project_empty_repo, :private, service_desk_enabled: false) }
+ let(:presenter) { project.present(current_user: user) }
+ let(:user) { create(:user) }
+
+ before do
+ project.add_maintainer(user)
+ sign_in(user)
+
+ allow_any_instance_of(Project).to receive(:present).with(current_user: user).and_return(presenter)
+ allow(::Gitlab::IncomingEmail).to receive(:enabled?) { true }
+ allow(::Gitlab::IncomingEmail).to receive(:supports_wildcard?) { true }
+
+ visit edit_project_path(project)
+ end
+
+ it 'shows activation checkbox' do
+ expect(page).to have_selector("#service-desk-checkbox")
+ end
+
+ it 'shows incoming email after activating' do
+ find("#service-desk-checkbox").click
+ wait_for_requests
+ project.reload
+ expect(project.service_desk_enabled).to be_truthy
+ expect(project.service_desk_address).to be_present
+ expect(find('.incoming-email').value).to eq(project.service_desk_address)
+ end
+end
diff --git a/spec/features/projects/settings/user_manages_merge_requests_settings_spec.rb b/spec/features/projects/settings/user_manages_merge_requests_settings_spec.rb
index 3fc1f47d98a..e97e4a2030a 100644
--- a/spec/features/projects/settings/user_manages_merge_requests_settings_spec.rb
+++ b/spec/features/projects/settings/user_manages_merge_requests_settings_spec.rb
@@ -28,6 +28,22 @@ RSpec.describe 'Projects > Settings > User manages merge request settings' do
end
end
+ it 'shows Squash commit options', :aggregate_failures do
+ page.within '#js-merge-request-settings' do
+ expect(page).to have_content 'Do not allow'
+ expect(page).to have_content 'Squashing is never performed and the checkbox is hidden.'
+
+ expect(page).to have_content 'Allow'
+ expect(page).to have_content 'Checkbox is visible and unselected by default.'
+
+ expect(page).to have_content 'Encourage'
+ expect(page).to have_content 'Checkbox is visible and selected by default.'
+
+ expect(page).to have_content 'Require'
+ expect(page).to have_content 'Squashing is always performed. Checkbox is visible and selected, and users cannot change it.'
+ end
+ end
+
context 'when Merge Request and Pipelines are initially enabled', :js do
context 'when Pipelines are initially enabled' do
it 'shows the Merge Requests settings' do
@@ -130,4 +146,56 @@ RSpec.describe 'Projects > Settings > User manages merge request settings' do
expect(project.remove_source_branch_after_merge).to be(false)
end
end
+
+ describe 'Squash commits when merging', :js do
+ it 'initially has :squash_option set to :default_off' do
+ radio = find_field('project_project_setting_attributes_squash_option_default_off')
+ expect(radio).to be_checked
+ end
+
+ it 'allows :squash_option to be set to :default_on' do
+ choose('project_project_setting_attributes_squash_option_default_on')
+
+ within('.merge-request-settings-form') do
+ find('.qa-save-merge-request-changes')
+ click_on('Save changes')
+ end
+
+ find('.flash-notice')
+ radio = find_field('project_project_setting_attributes_squash_option_default_on')
+
+ expect(radio).to be_checked
+ expect(project.reload.project_setting.squash_option).to eq('default_on')
+ end
+
+ it 'allows :squash_option to be set to :always' do
+ choose('project_project_setting_attributes_squash_option_always')
+
+ within('.merge-request-settings-form') do
+ find('.qa-save-merge-request-changes')
+ click_on('Save changes')
+ end
+
+ find('.flash-notice')
+ radio = find_field('project_project_setting_attributes_squash_option_always')
+
+ expect(radio).to be_checked
+ expect(project.reload.project_setting.squash_option).to eq('always')
+ end
+
+ it 'allows :squash_option to be set to :never' do
+ choose('project_project_setting_attributes_squash_option_never')
+
+ within('.merge-request-settings-form') do
+ find('.qa-save-merge-request-changes')
+ click_on('Save changes')
+ end
+
+ find('.flash-notice')
+ radio = find_field('project_project_setting_attributes_squash_option_never')
+
+ expect(radio).to be_checked
+ expect(project.reload.project_setting.squash_option).to eq('never')
+ end
+ end
end
diff --git a/spec/features/projects/settings/user_manages_project_members_spec.rb b/spec/features/projects/settings/user_manages_project_members_spec.rb
index d32f4cb8ec7..3836b95a28a 100644
--- a/spec/features/projects/settings/user_manages_project_members_spec.rb
+++ b/spec/features/projects/settings/user_manages_project_members_spec.rb
@@ -16,15 +16,20 @@ RSpec.describe 'Projects > Settings > User manages project members' do
sign_in(user)
end
- it 'cancels a team member' do
+ it 'cancels a team member', :js do
visit(project_project_members_path(project))
project_member = project.project_members.find_by(user_id: user_dmitriy.id)
page.within("#project_member_#{project_member.id}") do
- click_link('Remove user from project')
+ # Open modal
+ click_on('Remove user from project')
end
+ expect(page).to have_unchecked_field 'Also unassign this user from related issues and merge requests'
+
+ click_on('Remove member')
+
visit(project_project_members_path(project))
expect(page).not_to have_content(user_dmitriy.name)
diff --git a/spec/features/projects/show/user_sees_git_instructions_spec.rb b/spec/features/projects/show/user_sees_git_instructions_spec.rb
index a35f6420bdc..3b77fd7eebf 100644
--- a/spec/features/projects/show/user_sees_git_instructions_spec.rb
+++ b/spec/features/projects/show/user_sees_git_instructions_spec.rb
@@ -18,6 +18,8 @@ RSpec.describe 'Projects > Show > User sees Git instructions' do
page.within '.empty-wrapper' do
expect(page).to have_content('Command line instructions')
end
+
+ expect(page).to have_content("git push -u origin master")
end
end
@@ -59,6 +61,26 @@ RSpec.describe 'Projects > Show > User sees Git instructions' do
include_examples 'shows details of empty project with no repo'
end
+ context ":default_branch_name is specified" do
+ let_it_be(:project) { create(:project, :public) }
+
+ before do
+ expect(Gitlab::CurrentSettings)
+ .to receive(:default_branch_name)
+ .at_least(:once)
+ .and_return('example_branch')
+
+ sign_in(project.owner)
+ visit project_path(project)
+ end
+
+ it "recommends default_branch_name instead of master" do
+ click_link 'Create empty repository'
+
+ expect(page).to have_content("git push -u origin example_branch")
+ end
+ end
+
context 'when project is empty' do
let_it_be(:project) { create(:project_empty_repo, :public) }
diff --git a/spec/features/projects/show/user_sees_setup_shortcut_buttons_spec.rb b/spec/features/projects/show/user_sees_setup_shortcut_buttons_spec.rb
index 0f10b0a4010..afa9de5ce86 100644
--- a/spec/features/projects/show/user_sees_setup_shortcut_buttons_spec.rb
+++ b/spec/features/projects/show/user_sees_setup_shortcut_buttons_spec.rb
@@ -63,6 +63,23 @@ RSpec.describe 'Projects > Show > User sees setup shortcut buttons' do
expect(page).to have_link('Add LICENSE', href: presenter.add_license_path)
end
end
+
+ context 'Gitlab::CurrentSettings.default_branch_name is available' do
+ before do
+ expect(Gitlab::CurrentSettings)
+ .to receive(:default_branch_name)
+ .at_least(:once)
+ .and_return('example_branch')
+
+ visit project_path(project)
+ end
+
+ it '"New file" button linked to new file page' do
+ page.within('.project-buttons') do
+ expect(page).to have_link('New file', href: project_new_blob_path(project, 'example_branch'))
+ end
+ end
+ end
end
end
diff --git a/spec/features/projects/tree/tree_show_spec.rb b/spec/features/projects/tree/tree_show_spec.rb
index 388fa39874d..bd2af66710a 100644
--- a/spec/features/projects/tree/tree_show_spec.rb
+++ b/spec/features/projects/tree/tree_show_spec.rb
@@ -3,6 +3,8 @@
require 'spec_helper'
RSpec.describe 'Projects tree', :js do
+ include RepoHelpers
+
let(:user) { create(:user) }
let(:project) { create(:project, :repository) }
let(:gravatar_enabled) { true }
@@ -47,6 +49,30 @@ RSpec.describe 'Projects tree', :js do
expect(page).not_to have_selector('.flash-alert')
end
+ context "with a tree that contains pathspec characters" do
+ let(:path) { ':wq' }
+ let(:filename) { File.join(path, 'test.txt') }
+ let(:newrev) { project.repository.commit('master').sha }
+ let(:short_newrev) { project.repository.commit('master').short_id }
+ let(:message) { 'Glob characters'}
+
+ before do
+ create_file_in_repo(project, 'master', 'master', filename, 'Test file', commit_message: message)
+ visit project_tree_path(project, File.join('master', path))
+ wait_for_requests
+ end
+
+ it "renders tree table without errors" do
+ expect(page).to have_selector('.tree-item')
+ expect(page).to have_content('test.txt')
+ expect(page).to have_content(message)
+
+ # Check last commit
+ expect(find('.commit-content').text).to include(message)
+ expect(find('.commit-sha-group').text).to eq(short_newrev)
+ end
+ end
+
context 'gravatar disabled' do
let(:gravatar_enabled) { false }
diff --git a/spec/features/projects/user_sees_sidebar_spec.rb b/spec/features/projects/user_sees_sidebar_spec.rb
index cc2a9eacbad..1d443e0b339 100644
--- a/spec/features/projects/user_sees_sidebar_spec.rb
+++ b/spec/features/projects/user_sees_sidebar_spec.rb
@@ -6,6 +6,10 @@ RSpec.describe 'Projects > User sees sidebar' do
let(:user) { create(:user) }
let(:project) { create(:project, :private, public_builds: false, namespace: user.namespace) }
+ before do
+ stub_feature_flags(vue_issuables_list: false)
+ end
+
# NOTE: See documented behaviour https://design.gitlab.com/regions/navigation#contextual-navigation
context 'on different viewports', :js do
include MobileHelpers
diff --git a/spec/features/projects/user_sees_user_popover_spec.rb b/spec/features/projects/user_sees_user_popover_spec.rb
index 851ce79e1c6..9cfc6234969 100644
--- a/spec/features/projects/user_sees_user_popover_spec.rb
+++ b/spec/features/projects/user_sees_user_popover_spec.rb
@@ -19,7 +19,7 @@ RSpec.describe 'User sees user popover', :js do
subject { page }
describe 'hovering over a user link in a merge request' do
- let(:popover_selector) { '.user-popover' }
+ let(:popover_selector) { '[data-testid="user-popover"]' }
before do
visit project_merge_request_path(project, merge_request)
diff --git a/spec/features/projects/wiki/user_views_wiki_empty_spec.rb b/spec/features/projects/wiki/user_views_wiki_empty_spec.rb
index d9f79162c19..0af40a2d760 100644
--- a/spec/features/projects/wiki/user_views_wiki_empty_spec.rb
+++ b/spec/features/projects/wiki/user_views_wiki_empty_spec.rb
@@ -4,18 +4,19 @@ require 'spec_helper'
RSpec.describe 'User views empty wiki' do
let(:user) { create(:user) }
+ let(:confluence_link) { 'Enable the Confluence Wiki integration' }
+ let(:element) { page.find('.row.empty-state') }
shared_examples 'empty wiki and accessible issues' do
it 'show "issue tracker" message' do
visit(project_wikis_path(project))
- element = page.find('.row.empty-state')
-
expect(element).to have_content('This project has no wiki pages')
expect(element).to have_content('You must be a project member')
expect(element).to have_content('improve the wiki for this project')
expect(element).to have_link("issue tracker", href: project_issues_path(project))
expect(element).to have_link("Suggest wiki improvement", href: new_project_issue_path(project))
+ expect(element).to have_no_link(confluence_link)
end
end
@@ -23,11 +24,10 @@ RSpec.describe 'User views empty wiki' do
it 'does not show "issue tracker" message' do
visit(project_wikis_path(project))
- element = page.find('.row.empty-state')
-
expect(element).to have_content('This project has no wiki pages')
expect(element).to have_content('You must be a project member')
expect(element).to have_no_link('Suggest wiki improvement')
+ expect(element).to have_no_link(confluence_link)
end
end
@@ -60,16 +60,15 @@ RSpec.describe 'User views empty wiki' do
end
context 'when user is logged in and a member' do
- let(:project) { create(:project, :public, :wiki_repo) }
+ let(:project) { create(:project, :public) }
before do
sign_in(user)
project.add_developer(user)
end
- it 'show "create first page" message' do
+ it 'shows "create first page" message' do
visit(project_wikis_path(project))
- element = page.find('.row.empty-state')
expect(element).to have_content('your project', count: 2)
@@ -77,5 +76,34 @@ RSpec.describe 'User views empty wiki' do
expect(page).to have_button('Create page')
end
+
+ it 'does not show the "enable confluence" button' do
+ visit(project_wikis_path(project))
+
+ expect(element).to have_no_link(confluence_link)
+ end
+ end
+
+ context 'when user is logged in and an admin' do
+ let(:project) { create(:project, :public, :wiki_repo) }
+
+ before do
+ sign_in(user)
+ project.add_maintainer(user)
+ end
+
+ it 'shows the "enable confluence" button' do
+ visit(project_wikis_path(project))
+
+ expect(element).to have_link(confluence_link)
+ end
+
+ it 'does not show "enable confluence" button if confluence is already enabled' do
+ create(:confluence_service, project: project)
+
+ visit(project_wikis_path(project))
+
+ expect(element).to have_no_link(confluence_link)
+ end
end
end
diff --git a/spec/features/projects/wiki/user_views_wiki_page_spec.rb b/spec/features/projects/wiki/user_views_wiki_page_spec.rb
index 59ccb83a9bb..e93689af0aa 100644
--- a/spec/features/projects/wiki/user_views_wiki_page_spec.rb
+++ b/spec/features/projects/wiki/user_views_wiki_page_spec.rb
@@ -8,9 +8,10 @@ RSpec.describe 'User views a wiki page' do
let(:user) { create(:user) }
let(:project) { create(:project, :wiki_repo, namespace: user.namespace) }
let(:path) { 'image.png' }
+ let(:wiki) { project.wiki }
let(:wiki_page) do
create(:wiki_page,
- wiki: project.wiki,
+ wiki: wiki,
title: 'home', content: "Look at this [image](#{path})\n\n ![alt text](#{path})")
end
@@ -70,11 +71,13 @@ RSpec.describe 'User views a wiki page' do
click_on('Page history')
- page.within(:css, '.nav-text') do
+ within('.nav-text') do
expect(page).to have_content('History')
end
- find('a[href*="?version_id"]')
+ within('.wiki-history') do
+ expect(page).to have_css('a[href*="?version_id"]', count: 4)
+ end
end
end
@@ -92,8 +95,8 @@ RSpec.describe 'User views a wiki page' do
let(:path) { upload_file_to_wiki(project, user, 'dk.png') }
it do
- expect(page).to have_xpath("//img[@data-src='#{project.wiki.wiki_base_path}/#{path}']")
- expect(page).to have_link('image', href: "#{project.wiki.wiki_base_path}/#{path}")
+ expect(page).to have_xpath("//img[@data-src='#{wiki.wiki_base_path}/#{path}']")
+ expect(page).to have_link('image', href: "#{wiki.wiki_base_path}/#{path}")
click_on('image')
@@ -103,7 +106,7 @@ RSpec.describe 'User views a wiki page' do
end
it 'shows the creation page if file does not exist' do
- expect(page).to have_link('image', href: "#{project.wiki.wiki_base_path}/#{path}")
+ expect(page).to have_link('image', href: "#{wiki.wiki_base_path}/#{path}")
click_on('image')
@@ -114,7 +117,7 @@ RSpec.describe 'User views a wiki page' do
context 'when a page has history' do
before do
- wiki_page.update(message: 'updated home', content: 'updated [some link](other-page)')
+ wiki_page.update(message: 'updated home', content: 'updated [some link](other-page)') # rubocop:disable Rails/SaveBang
end
it 'shows the page history' do
@@ -134,13 +137,74 @@ RSpec.describe 'User views a wiki page' do
expect(page).not_to have_selector('a.btn', text: 'Edit')
end
+
+ context 'show the diff' do
+ def expect_diff_links(commit)
+ diff_path = wiki_page_path(wiki, wiki_page, version_id: commit, action: :diff)
+
+ expect(page).to have_link('Hide whitespace changes', href: "#{diff_path}&w=1")
+ expect(page).to have_link('Inline', href: "#{diff_path}&view=inline")
+ expect(page).to have_link('Side-by-side', href: "#{diff_path}&view=parallel")
+ expect(page).to have_link("View page @ #{commit.short_id}", href: wiki_page_path(wiki, wiki_page, version_id: commit))
+ expect(page).to have_css('.diff-file[data-blob-diff-path="%s"]' % diff_path)
+ end
+
+ it 'links to the correct diffs' do
+ visit project_wiki_history_path(project, wiki_page)
+
+ commit1 = wiki.commit('HEAD^')
+ commit2 = wiki.commit
+
+ expect(page).to have_link('created page: home', href: wiki_page_path(wiki, wiki_page, version_id: commit1, action: :diff))
+ expect(page).to have_link('updated home', href: wiki_page_path(wiki, wiki_page, version_id: commit2, action: :diff))
+ end
+
+ it 'between the current and the previous version of a page' do
+ commit = wiki.commit
+ visit wiki_page_path(wiki, wiki_page, version_id: commit, action: :diff)
+
+ expect(page).to have_content('by John Doe')
+ expect(page).to have_content('updated home')
+ expect(page).to have_content('Showing 1 changed file with 1 addition and 3 deletions')
+ expect(page).to have_content('some link')
+
+ expect_diff_links(commit)
+ end
+
+ it 'between two old versions of a page' do
+ wiki_page.update(message: 'latest home change', content: 'updated [another link](other-page)') # rubocop:disable Rails/SaveBang:
+ commit = wiki.commit('HEAD^')
+ visit wiki_page_path(wiki, wiki_page, version_id: commit, action: :diff)
+
+ expect(page).to have_content('by John Doe')
+ expect(page).to have_content('updated home')
+ expect(page).to have_content('Showing 1 changed file with 1 addition and 3 deletions')
+ expect(page).to have_content('some link')
+ expect(page).not_to have_content('latest home change')
+ expect(page).not_to have_content('another link')
+
+ expect_diff_links(commit)
+ end
+
+ it 'for the oldest version of a page' do
+ commit = wiki.commit('HEAD^')
+ visit wiki_page_path(wiki, wiki_page, version_id: commit, action: :diff)
+
+ expect(page).to have_content('by John Doe')
+ expect(page).to have_content('created page: home')
+ expect(page).to have_content('Showing 1 changed file with 4 additions and 0 deletions')
+ expect(page).to have_content('Look at this')
+
+ expect_diff_links(commit)
+ end
+ end
end
context 'when a page has special characters in its title' do
let(:title) { '<foo> !@#$%^&*()[]{}=_+\'"\\|<>? <bar>' }
before do
- wiki_page.update(title: title )
+ wiki_page.update(title: title ) # rubocop:disable Rails/SaveBang
end
it 'preserves the special characters' do
@@ -155,7 +219,7 @@ RSpec.describe 'User views a wiki page' do
let(:title) { '<script>alert("title")<script>' }
before do
- wiki_page.update(title: title, content: 'foo <script>alert("content")</script> bar')
+ wiki_page.update(title: title, content: 'foo <script>alert("content")</script> bar') # rubocop:disable Rails/SaveBang
end
it 'safely displays the page' do
@@ -168,7 +232,7 @@ RSpec.describe 'User views a wiki page' do
context 'when a page has XSS in its message' do
before do
- wiki_page.update(message: '<script>alert(true)<script>', content: 'XSS update')
+ wiki_page.update(message: '<script>alert(true)<script>', content: 'XSS update') # rubocop:disable Rails/SaveBang
end
it 'safely displays the message' do
diff --git a/spec/features/promotion_spec.rb b/spec/features/promotion_spec.rb
new file mode 100644
index 00000000000..9344f9b56b8
--- /dev/null
+++ b/spec/features/promotion_spec.rb
@@ -0,0 +1,53 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'Promotions', :js do
+ let_it_be(:user) { create(:user) }
+ let_it_be(:project) { create(:project_empty_repo) }
+
+ describe 'for service desk', :js do
+ before do
+ project.add_maintainer(user)
+ sign_in(user)
+ end
+
+ context 'when service desk is not supported' do
+ before do
+ allow(::Gitlab::ServiceDesk).to receive(:supported?).and_return(false)
+ end
+
+ it 'appears in project edit page' do
+ visit edit_project_path(project)
+
+ expect(find('#promote_service_desk')).to have_content 'Improve customer support with GitLab Service Desk.'
+ end
+
+ it 'does not show when cookie is set' do
+ visit edit_project_path(project)
+
+ within('#promote_service_desk') do
+ find('.close').click
+ end
+
+ wait_for_requests
+
+ visit edit_project_path(project)
+
+ expect(page).not_to have_selector('#promote_service_desk')
+ end
+ end
+
+ context 'when service desk is supported' do
+ before do
+ allow(::Gitlab::ServiceDesk).to receive(:supported?).and_return(true)
+ end
+
+ it 'does not show promotion' do
+ visit edit_project_path(project)
+
+ expect(page).not_to have_selector('#promote_service_desk')
+ end
+ end
+ end
+end
diff --git a/spec/features/runners_spec.rb b/spec/features/runners_spec.rb
index 8806a363ca4..9b2373bf28b 100644
--- a/spec/features/runners_spec.rb
+++ b/spec/features/runners_spec.rb
@@ -270,7 +270,7 @@ RSpec.describe 'Runners' do
it 'there are no runners displayed' do
visit group_settings_ci_cd_path(group)
- expect(page).to have_content 'This group does not provide any group Runners yet'
+ expect(page).to have_content 'No runners found'
end
it 'user can see a link to install runners on kubernetes clusters' do
@@ -286,26 +286,26 @@ RSpec.describe 'Runners' do
it 'the runner is visible' do
visit group_settings_ci_cd_path(group)
- expect(page).not_to have_content 'This group does not provide any group Runners yet'
- expect(page).to have_content 'Available group Runners: 1'
+ expect(page).not_to have_content 'No runners found'
+ expect(page).to have_content 'Available Runners: 1'
expect(page).to have_content 'group-runner'
end
it 'user can pause and resume the group runner' do
visit group_settings_ci_cd_path(group)
- expect(page).to have_content('Pause')
- expect(page).not_to have_content('Resume')
+ expect(page).to have_link href: pause_group_runner_path(group, runner)
+ expect(page).not_to have_link href: resume_group_runner_path(group, runner)
- click_on 'Pause'
+ click_link href: pause_group_runner_path(group, runner)
- expect(page).not_to have_content('Pause')
- expect(page).to have_content('Resume')
+ expect(page).not_to have_link href: pause_group_runner_path(group, runner)
+ expect(page).to have_link href: resume_group_runner_path(group, runner)
- click_on 'Resume'
+ click_link href: resume_group_runner_path(group, runner)
- expect(page).to have_content('Pause')
- expect(page).not_to have_content('Resume')
+ expect(page).to have_link href: pause_group_runner_path(group, runner)
+ expect(page).not_to have_link href: resume_group_runner_path(group, runner)
end
it 'user can view runner details' do
@@ -321,7 +321,7 @@ RSpec.describe 'Runners' do
it 'user can remove a group runner' do
visit group_settings_ci_cd_path(group)
- click_on 'Remove Runner'
+ all(:link, href: group_runner_path(group, runner))[1].click
expect(page).not_to have_content(runner.display_name)
end
@@ -329,7 +329,7 @@ RSpec.describe 'Runners' do
it 'user edits the runner to be protected' do
visit group_settings_ci_cd_path(group)
- first('.edit-runner > a').click
+ click_link href: edit_group_runner_path(group, runner)
expect(page.find_field('runner[access_level]')).not_to be_checked
@@ -347,7 +347,87 @@ RSpec.describe 'Runners' do
it 'user edits runner not to run untagged jobs' do
visit group_settings_ci_cd_path(group)
- first('.edit-runner > a').click
+ click_link href: edit_group_runner_path(group, runner)
+
+ expect(page.find_field('runner[run_untagged]')).to be_checked
+
+ uncheck 'runner_run_untagged'
+ click_button 'Save changes'
+
+ expect(page).to have_content 'Can run untagged jobs No'
+ end
+ end
+ end
+
+ context 'group with a project runner' do
+ let(:project) { create(:project, group: group) }
+ let!(:runner) { create(:ci_runner, :project, projects: [project], description: 'project-runner') }
+
+ it 'the runner is visible' do
+ visit group_settings_ci_cd_path(group)
+
+ expect(page).not_to have_content 'No runners found'
+ expect(page).to have_content 'Available Runners: 1'
+ expect(page).to have_content 'project-runner'
+ end
+
+ it 'user can pause and resume the project runner' do
+ visit group_settings_ci_cd_path(group)
+
+ expect(page).to have_link href: pause_group_runner_path(group, runner)
+ expect(page).not_to have_link href: resume_group_runner_path(group, runner)
+
+ click_link href: pause_group_runner_path(group, runner)
+
+ expect(page).not_to have_link href: pause_group_runner_path(group, runner)
+ expect(page).to have_link href: resume_group_runner_path(group, runner)
+
+ click_link href: resume_group_runner_path(group, runner)
+
+ expect(page).to have_link href: pause_group_runner_path(group, runner)
+ expect(page).not_to have_link href: resume_group_runner_path(group, runner)
+ end
+
+ it 'user can view runner details' do
+ visit group_settings_ci_cd_path(group)
+
+ expect(page).to have_content(runner.display_name)
+
+ click_on runner.short_sha
+
+ expect(page).to have_content(runner.platform)
+ end
+
+ it 'user can remove a project runner' do
+ visit group_settings_ci_cd_path(group)
+
+ all(:link, href: group_runner_path(group, runner))[1].click
+
+ expect(page).not_to have_content(runner.display_name)
+ end
+
+ it 'user edits the runner to be protected' do
+ visit group_settings_ci_cd_path(group)
+
+ click_link href: edit_group_runner_path(group, runner)
+
+ expect(page.find_field('runner[access_level]')).not_to be_checked
+
+ check 'runner_access_level'
+ click_button 'Save changes'
+
+ expect(page).to have_content 'Protected Yes'
+ end
+
+ context 'when a runner has a tag' do
+ before do
+ runner.update(tag_list: ['tag'])
+ end
+
+ it 'user edits runner not to run untagged jobs' do
+ visit group_settings_ci_cd_path(group)
+
+ click_link href: edit_group_runner_path(group, runner)
expect(page.find_field('runner[run_untagged]')).to be_checked
@@ -358,5 +438,17 @@ RSpec.describe 'Runners' do
end
end
end
+
+ context 'group with a multi-project runner' do
+ let(:project) { create(:project, group: group) }
+ let(:project_2) { create(:project, group: group) }
+ let!(:runner) { create(:ci_runner, :project, projects: [project, project_2], description: 'group-runner') }
+
+ it 'user cannot remove the project runner' do
+ visit group_settings_ci_cd_path(group)
+
+ expect(all(:link, href: group_runner_path(group, runner)).length).to eq(1)
+ end
+ end
end
end
diff --git a/spec/features/security/project/snippet/public_access_spec.rb b/spec/features/security/project/snippet/public_access_spec.rb
index dfe78aa7ebc..20a271f9c0e 100644
--- a/spec/features/security/project/snippet/public_access_spec.rb
+++ b/spec/features/security/project/snippet/public_access_spec.rb
@@ -5,11 +5,10 @@ require 'spec_helper'
RSpec.describe "Public Project Snippets Access" do
include AccessMatchers
- let(:project) { create(:project, :public) }
-
- let(:public_snippet) { create(:project_snippet, :public, project: project, author: project.owner) }
- let(:internal_snippet) { create(:project_snippet, :internal, project: project, author: project.owner) }
- let(:private_snippet) { create(:project_snippet, :private, project: project, author: project.owner) }
+ let_it_be(:project) { create(:project, :public) }
+ let_it_be(:public_snippet) { create(:project_snippet, :public, project: project, author: project.owner) }
+ let_it_be(:internal_snippet) { create(:project_snippet, :internal, project: project, author: project.owner) }
+ let_it_be(:private_snippet) { create(:project_snippet, :private, project: project, author: project.owner) }
describe "GET /:project_path/snippets" do
subject { project_snippets_path(project) }
diff --git a/spec/features/signed_commits_spec.rb b/spec/features/signed_commits_spec.rb
index 04ca8a09ca8..d679e4dbb99 100644
--- a/spec/features/signed_commits_spec.rb
+++ b/spec/features/signed_commits_spec.rb
@@ -72,6 +72,7 @@ RSpec.describe 'GPG signed commits' do
it 'unverified signature' do
visit project_commit_path(project, GpgHelpers::SIGNED_COMMIT_SHA)
+ wait_for_all_requests
page.find('.gpg-status-box', text: 'Unverified').click
@@ -85,6 +86,7 @@ RSpec.describe 'GPG signed commits' do
user_2_key
visit project_commit_path(project, GpgHelpers::DIFFERING_EMAIL_SHA)
+ wait_for_all_requests
page.find('.gpg-status-box', text: 'Unverified').click
@@ -100,6 +102,7 @@ RSpec.describe 'GPG signed commits' do
user_2_key
visit project_commit_path(project, GpgHelpers::SIGNED_COMMIT_SHA)
+ wait_for_all_requests
page.find('.gpg-status-box', text: 'Unverified').click
@@ -115,6 +118,7 @@ RSpec.describe 'GPG signed commits' do
user_1_key
visit project_commit_path(project, GpgHelpers::SIGNED_AND_AUTHORED_SHA)
+ wait_for_all_requests
page.find('.gpg-status-box', text: 'Verified').click
@@ -130,6 +134,7 @@ RSpec.describe 'GPG signed commits' do
user_1_key
visit project_commit_path(project, GpgHelpers::SIGNED_AND_AUTHORED_SHA)
+ wait_for_all_requests
# wait for the signature to get generated
expect(page).to have_selector('.gpg-status-box', text: 'Verified')
@@ -137,6 +142,7 @@ RSpec.describe 'GPG signed commits' do
user_1.destroy!
refresh
+ wait_for_all_requests
page.find('.gpg-status-box', text: 'Verified').click
@@ -153,6 +159,7 @@ RSpec.describe 'GPG signed commits' do
shared_examples 'a commit with a signature' do
before do
visit project_tree_path(project, 'signed-commits')
+ wait_for_all_requests
end
it 'displays commit signature' do
diff --git a/spec/features/task_lists_spec.rb b/spec/features/task_lists_spec.rb
index fa87c4bb1c4..fc3f8a94318 100644
--- a/spec/features/task_lists_spec.rb
+++ b/spec/features/task_lists_spec.rb
@@ -103,7 +103,7 @@ RSpec.describe 'Task Lists' do
wait_for_requests
expect(page).to have_selector(".md .task-list .task-list-item .task-list-item-checkbox")
- expect(page).to have_selector('a.btn-close')
+ expect(page).to have_selector('.btn-close')
end
it 'is only editable by author' do
diff --git a/spec/features/triggers_spec.rb b/spec/features/triggers_spec.rb
index 577134fe722..4be27673adf 100644
--- a/spec/features/triggers_spec.rb
+++ b/spec/features/triggers_spec.rb
@@ -85,7 +85,7 @@ RSpec.describe 'Triggers', :js do
end
expect(page.find('.flash-notice')).to have_content 'Trigger removed'
- expect(page).to have_selector('p.settings-message.text-center.append-bottom-default')
+ expect(page).to have_selector('p.settings-message.text-center.gl-mb-3')
end
end
diff --git a/spec/features/users/login_spec.rb b/spec/features/users/login_spec.rb
index 7ba663d08d4..2d0fcfe84e6 100644
--- a/spec/features/users/login_spec.rb
+++ b/spec/features/users/login_spec.rb
@@ -110,7 +110,7 @@ RSpec.describe 'Login' do
gitlab_sign_in(user)
- expect(page).not_to have_content('You have to confirm your email address before continuing.')
+ expect(page).not_to have_content(I18n.t('devise.failure.unconfirmed'))
expect(page).not_to have_link('Resend confirmation email', href: new_user_confirmation_path)
end
end
@@ -124,7 +124,7 @@ RSpec.describe 'Login' do
gitlab_sign_in(user)
- expect(page).to have_content('You have to confirm your email address before continuing.')
+ expect(page).to have_content(I18n.t('devise.failure.unconfirmed'))
expect(page).to have_link('Resend confirmation email', href: new_user_confirmation_path)
end
end
@@ -820,7 +820,7 @@ RSpec.describe 'Login' do
gitlab_sign_in(user)
expect(current_path).to eq new_user_session_path
- expect(page).to have_content('You have to confirm your email address before continuing.')
+ expect(page).to have_content(I18n.t('devise.failure.unconfirmed'))
end
end
end
diff --git a/spec/features/users/signup_spec.rb b/spec/features/users/signup_spec.rb
index 66a26493339..af2ecfec498 100644
--- a/spec/features/users/signup_spec.rb
+++ b/spec/features/users/signup_spec.rb
@@ -70,6 +70,13 @@ RSpec.shared_examples 'Signup' do
expect(page).to have_content("Username is too long (maximum is 255 characters).")
end
+ it 'shows an error message if the username is less than 2 characters' do
+ fill_in 'new_user_username', with: 'u'
+ wait_for_requests
+
+ expect(page).to have_content("Username is too short (minimum is 2 characters).")
+ end
+
it 'shows an error message on submit if the username contains special characters' do
fill_in 'new_user_username', with: 'new$user!username'
wait_for_requests
@@ -91,7 +98,7 @@ RSpec.shared_examples 'Signup' do
expect(page).to have_content("Invalid input, please avoid emojis")
end
- it 'shows a pending message if the username availability is being fetched', :quarantine do
+ it 'shows a pending message if the username availability is being fetched', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/31484' do
fill_in 'new_user_username', with: 'new-user'
expect(find('.username > .validation-pending')).not_to have_css '.hide'
diff --git a/spec/finders/alert_management/alerts_finder_spec.rb b/spec/finders/alert_management/alerts_finder_spec.rb
index 5920d579ba6..7bf9047704b 100644
--- a/spec/finders/alert_management/alerts_finder_spec.rb
+++ b/spec/finders/alert_management/alerts_finder_spec.rb
@@ -11,7 +11,7 @@ RSpec.describe AlertManagement::AlertsFinder, '#execute' do
let(:params) { {} }
describe '#execute' do
- subject { described_class.new(current_user, project, params).execute }
+ subject(:execute) { described_class.new(current_user, project, params).execute }
context 'user is not a developer or above' do
it { is_expected.to be_empty }
@@ -144,81 +144,55 @@ RSpec.describe AlertManagement::AlertsFinder, '#execute' do
end
context 'when sorting by severity' do
- let_it_be(:alert_critical) { create(:alert_management_alert, project: project, severity: :critical) }
- let_it_be(:alert_high) { create(:alert_management_alert, project: project, severity: :high) }
- let_it_be(:alert_medium) { create(:alert_management_alert, project: project, severity: :medium) }
- let_it_be(:alert_low) { create(:alert_management_alert, project: project, severity: :low) }
- let_it_be(:alert_info) { create(:alert_management_alert, project: project, severity: :info) }
- let_it_be(:alert_unknown) { create(:alert_management_alert, project: project, severity: :unknown) }
-
- context 'sorts alerts ascending' do
+ let_it_be(:alert_critical) { create(:alert_management_alert, :critical, project: project) }
+ let_it_be(:alert_high) { create(:alert_management_alert, :high, project: project) }
+ let_it_be(:alert_medium) { create(:alert_management_alert, :medium, project: project) }
+ let_it_be(:alert_low) { create(:alert_management_alert, :low, project: project) }
+ let_it_be(:alert_info) { create(:alert_management_alert, :info, project: project) }
+ let_it_be(:alert_unknown) { create(:alert_management_alert, :unknown, project: project) }
+
+ context 'with ascending sort order' do
let(:params) { { sort: 'severity_asc' } }
- it do
- is_expected.to eq [
- alert_2,
- alert_critical,
- alert_1,
- alert_high,
- alert_medium,
- alert_low,
- alert_info,
- alert_unknown
- ]
+ it 'sorts alerts by severity from less critical to more critical' do
+ expect(execute.pluck(:severity).uniq).to eq(%w(unknown info low medium high critical))
end
end
- context 'sorts alerts descending' do
+ context 'with descending sort order' do
let(:params) { { sort: 'severity_desc' } }
- it do
- is_expected.to eq [
- alert_unknown,
- alert_info,
- alert_low,
- alert_medium,
- alert_1,
- alert_high,
- alert_critical,
- alert_2
- ]
+ it 'sorts alerts by severity from more critical to less critical' do
+ expect(execute.pluck(:severity).uniq).to eq(%w(critical high medium low info unknown))
end
end
end
context 'when sorting by status' do
+ let(:statuses) { AlertManagement::Alert::STATUSES }
+ let(:triggered) { statuses[:triggered] }
+ let(:acknowledged) { statuses[:acknowledged] }
+ let(:resolved) { statuses[:resolved] }
+ let(:ignored) { statuses[:ignored] }
+
let_it_be(:alert_triggered) { create(:alert_management_alert, project: project) }
let_it_be(:alert_acknowledged) { create(:alert_management_alert, :acknowledged, project: project) }
let_it_be(:alert_resolved) { create(:alert_management_alert, :resolved, project: project) }
let_it_be(:alert_ignored) { create(:alert_management_alert, :ignored, project: project) }
- context 'sorts alerts ascending' do
+ context 'with ascending sort order' do
let(:params) { { sort: 'status_asc' } }
- it do
- is_expected.to eq [
- alert_triggered,
- alert_acknowledged,
- alert_1,
- alert_resolved,
- alert_2,
- alert_ignored
- ]
+ it 'sorts by status: Ignored > Resolved > Acknowledged > Triggered' do
+ expect(execute.map(&:status).uniq).to eq([ignored, resolved, acknowledged, triggered])
end
end
- context 'sorts alerts descending' do
+ context 'with descending sort order' do
let(:params) { { sort: 'status_desc' } }
- it do
- is_expected.to eq [
- alert_2,
- alert_ignored,
- alert_1,
- alert_resolved,
- alert_acknowledged,
- alert_triggered
- ]
+ it 'sorts by status: Triggered > Acknowledged > Resolved > Ignored' do
+ expect(execute.map(&:status).uniq).to eq([triggered, acknowledged, resolved, ignored])
end
end
end
diff --git a/spec/finders/branches_finder_spec.rb b/spec/finders/branches_finder_spec.rb
index 2e52093342d..a62dd3842db 100644
--- a/spec/finders/branches_finder_spec.rb
+++ b/spec/finders/branches_finder_spec.rb
@@ -7,142 +7,255 @@ RSpec.describe BranchesFinder do
let(:project) { create(:project, :repository) }
let(:repository) { project.repository }
+ let(:branch_finder) { described_class.new(repository, params) }
+ let(:params) { {} }
+
describe '#execute' do
+ subject { branch_finder.execute }
+
context 'sort only' do
- it 'sorts by name' do
- branches_finder = described_class.new(repository, {})
+ context 'by name' do
+ let(:params) { {} }
- result = branches_finder.execute
+ it 'sorts' do
+ result = subject
- expect(result.first.name).to eq("'test'")
+ expect(result.first.name).to eq("'test'")
+ end
end
- it 'sorts by recently_updated' do
- branches_finder = described_class.new(repository, { sort: 'updated_desc' })
+ context 'by recently_updated' do
+ let(:params) { { sort: 'updated_desc' } }
- result = branches_finder.execute
+ it 'sorts' do
+ result = subject
- recently_updated_branch = repository.branches.max do |a, b|
- repository.commit(a.dereferenced_target).committed_date <=> repository.commit(b.dereferenced_target).committed_date
- end
+ recently_updated_branch = repository.branches.max do |a, b|
+ repository.commit(a.dereferenced_target).committed_date <=> repository.commit(b.dereferenced_target).committed_date
+ end
- expect(result.first.name).to eq(recently_updated_branch.name)
+ expect(result.first.name).to eq(recently_updated_branch.name)
+ end
end
- it 'sorts by last_updated' do
- branches_finder = described_class.new(repository, { sort: 'updated_asc' })
+ context 'by last_updated' do
+ let(:params) { { sort: 'updated_asc' } }
- result = branches_finder.execute
+ it 'sorts' do
+ result = subject
- expect(result.first.name).to eq('feature')
+ expect(result.first.name).to eq('feature')
+ end
end
end
context 'filter only' do
- it 'filters branches by name' do
- branches_finder = described_class.new(repository, { search: 'fix' })
+ context 'by name' do
+ let(:params) { { search: 'fix' } }
- result = branches_finder.execute
+ it 'filters branches' do
+ result = subject
- expect(result.first.name).to eq('fix')
- expect(result.count).to eq(1)
+ expect(result.first.name).to eq('fix')
+ expect(result.count).to eq(1)
+ end
end
- it 'filters branches by name ignoring letter case' do
- branches_finder = described_class.new(repository, { search: 'FiX' })
+ context 'by name ignoring letter case' do
+ let(:params) { { search: 'FiX' } }
- result = branches_finder.execute
+ it 'filters branches' do
+ result = subject
- expect(result.first.name).to eq('fix')
- expect(result.count).to eq(1)
+ expect(result.first.name).to eq('fix')
+ expect(result.count).to eq(1)
+ end
end
- it 'does not find any branch with that name' do
- branches_finder = described_class.new(repository, { search: 'random' })
+ context 'with an unknown name' do
+ let(:params) { { search: 'random' } }
- result = branches_finder.execute
+ it 'does not find any branch' do
+ result = subject
- expect(result.count).to eq(0)
+ expect(result.count).to eq(0)
+ end
end
- it 'filters branches by provided names' do
- branches_finder = described_class.new(repository, { names: %w[fix csv lfs does-not-exist] })
+ context 'by provided names' do
+ let(:params) { { names: %w[fix csv lfs does-not-exist] } }
- result = branches_finder.execute
+ it 'filters branches' do
+ result = subject
- expect(result.count).to eq(3)
- expect(result.map(&:name)).to eq(%w{csv fix lfs})
+ expect(result.count).to eq(3)
+ expect(result.map(&:name)).to eq(%w{csv fix lfs})
+ end
end
- it 'filters branches by name that begins with' do
- params = { search: '^feature_' }
- branches_finder = described_class.new(repository, params)
+ context 'by name that begins with' do
+ let(:params) { { search: '^feature_' } }
- result = branches_finder.execute
+ it 'filters branches' do
+ result = subject
- expect(result.first.name).to eq('feature_conflict')
- expect(result.count).to eq(1)
+ expect(result.first.name).to eq('feature_conflict')
+ expect(result.count).to eq(1)
+ end
end
- it 'filters branches by name that ends with' do
- params = { search: 'feature$' }
- branches_finder = described_class.new(repository, params)
+ context 'by name that ends with' do
+ let(:params) { { search: 'feature$' } }
- result = branches_finder.execute
+ it 'filters branches' do
+ result = subject
- expect(result.first.name).to eq('feature')
- expect(result.count).to eq(1)
+ expect(result.first.name).to eq('feature')
+ expect(result.count).to eq(1)
+ end
end
- it 'filters branches by nonexistent name that begins with' do
- params = { search: '^nope' }
- branches_finder = described_class.new(repository, params)
+ context 'by nonexistent name that begins with' do
+ let(:params) { { search: '^nope' } }
- result = branches_finder.execute
+ it 'filters branches' do
+ result = subject
- expect(result.count).to eq(0)
+ expect(result.count).to eq(0)
+ end
end
- it 'filters branches by nonexistent name that ends with' do
- params = { search: 'nope$' }
- branches_finder = described_class.new(repository, params)
+ context 'by nonexistent name that ends with' do
+ let(:params) { { search: 'nope$' } }
- result = branches_finder.execute
+ it 'filters branches' do
+ result = subject
- expect(result.count).to eq(0)
+ expect(result.count).to eq(0)
+ end
end
end
context 'filter and sort' do
- it 'filters branches by name and sorts by recently_updated' do
- params = { sort: 'updated_desc', search: 'feat' }
- branches_finder = described_class.new(repository, params)
+ context 'by name and sorts by recently_updated' do
+ let(:params) { { sort: 'updated_desc', search: 'feat' } }
+
+ it 'filters branches' do
+ result = subject
+
+ expect(result.first.name).to eq('feature_conflict')
+ expect(result.count).to eq(2)
+ end
+ end
+
+ context 'by name and sorts by recently_updated, with exact matches first' do
+ let(:params) { { sort: 'updated_desc', search: 'feature' } }
+
+ it 'filters branches' do
+ result = subject
+
+ expect(result.first.name).to eq('feature')
+ expect(result.second.name).to eq('feature_conflict')
+ expect(result.count).to eq(2)
+ end
+ end
+
+ context 'by name and sorts by last_updated' do
+ let(:params) { { sort: 'updated_asc', search: 'feature' } }
+
+ it 'filters branches' do
+ result = subject
+
+ expect(result.first.name).to eq('feature')
+ expect(result.count).to eq(2)
+ end
+ end
+ end
- result = branches_finder.execute
+ context 'with gitaly pagination' do
+ subject { branch_finder.execute(gitaly_pagination: true) }
- expect(result.first.name).to eq('feature_conflict')
- expect(result.count).to eq(2)
+ context 'by page_token and per_page' do
+ let(:params) { { page_token: 'feature', per_page: 2 } }
+
+ it 'filters branches' do
+ result = subject
+
+ expect(result.map(&:name)).to eq(%w(feature_conflict fix))
+ end
end
- it 'filters branches by name and sorts by recently_updated, with exact matches first' do
- params = { sort: 'updated_desc', search: 'feature' }
- branches_finder = described_class.new(repository, params)
+ context 'by next page_token and per_page' do
+ let(:params) { { page_token: 'fix', per_page: 2 } }
- result = branches_finder.execute
+ it 'filters branches' do
+ result = subject
- expect(result.first.name).to eq('feature')
- expect(result.second.name).to eq('feature_conflict')
- expect(result.count).to eq(2)
+ expect(result.map(&:name)).to eq(%w(flatten-dir gitattributes))
+ end
end
- it 'filters branches by name and sorts by last_updated' do
- params = { sort: 'updated_asc', search: 'feature' }
- branches_finder = described_class.new(repository, params)
+ context 'by per_page only' do
+ let(:params) { { per_page: 2 } }
+
+ it 'filters branches' do
+ result = subject
+
+ expect(result.map(&:name)).to eq(["'test'", '2-mb-file'])
+ end
+ end
- result = branches_finder.execute
+ context 'by page_token only' do
+ let(:params) { { page_token: 'feature' } }
- expect(result.first.name).to eq('feature')
- expect(result.count).to eq(2)
+ it 'returns nothing' do
+ result = subject
+
+ expect(result.count).to eq(0)
+ end
+ end
+
+ context 'pagination and sort' do
+ context 'by per_page' do
+ let(:params) { { sort: 'updated_asc', per_page: 5 } }
+
+ it 'filters branches' do
+ result = subject
+
+ expect(result.map(&:name)).to eq(%w(feature improve/awesome merge-test markdown feature_conflict))
+ end
+ end
+
+ context 'by page_token and per_page' do
+ let(:params) { { sort: 'updated_asc', page_token: 'improve/awesome', per_page: 2 } }
+
+ it 'filters branches' do
+ result = subject
+
+ expect(result.map(&:name)).to eq(%w(merge-test markdown))
+ end
+ end
+ end
+
+ context 'pagination and names' do
+ let(:params) { { page_token: 'fix', per_page: 2, names: %w[fix csv lfs does-not-exist] } }
+
+ it 'falls back to default execute and ignore paginations' do
+ result = subject
+
+ expect(result.count).to eq(3)
+ expect(result.map(&:name)).to eq(%w{csv fix lfs})
+ end
+ end
+
+ context 'pagination and search' do
+ let(:params) { { page_token: 'feature', per_page: 2, search: '^f' } }
+
+ it 'falls back to default execute and ignore paginations' do
+ result = subject
+
+ expect(result.map(&:name)).to eq(%w(feature feature_conflict fix flatten-dir))
+ end
end
end
end
diff --git a/spec/finders/ci/pipelines_finder_spec.rb b/spec/finders/ci/pipelines_finder_spec.rb
index 680955ff9f9..a2a714689ba 100644
--- a/spec/finders/ci/pipelines_finder_spec.rb
+++ b/spec/finders/ci/pipelines_finder_spec.rb
@@ -77,13 +77,13 @@ RSpec.describe Ci::PipelinesFinder do
end
end
- HasStatus::AVAILABLE_STATUSES.each do |target|
+ Ci::HasStatus::AVAILABLE_STATUSES.each do |target|
context "when status is #{target}" do
let(:params) { { status: target } }
let!(:pipeline) { create(:ci_pipeline, project: project, status: target) }
before do
- exception_status = HasStatus::AVAILABLE_STATUSES - [target]
+ exception_status = Ci::HasStatus::AVAILABLE_STATUSES - [target]
create(:ci_pipeline, project: project, status: exception_status.first)
end
diff --git a/spec/finders/ci/pipelines_for_merge_request_finder_spec.rb b/spec/finders/ci/pipelines_for_merge_request_finder_spec.rb
index 543c289d366..ca6e0793d55 100644
--- a/spec/finders/ci/pipelines_for_merge_request_finder_spec.rb
+++ b/spec/finders/ci/pipelines_for_merge_request_finder_spec.rb
@@ -3,11 +3,97 @@
require 'spec_helper'
RSpec.describe Ci::PipelinesForMergeRequestFinder do
+ describe '#execute' do
+ include ProjectForksHelper
+
+ subject { finder.execute }
+
+ let_it_be(:developer_in_parent) { create(:user) }
+ let_it_be(:developer_in_fork) { create(:user) }
+ let_it_be(:developer_in_both) { create(:user) }
+ let_it_be(:reporter_in_parent_and_developer_in_fork) { create(:user) }
+ let_it_be(:external_user) { create(:user) }
+ let_it_be(:parent_project) { create(:project, :repository, :private) }
+ let_it_be(:forked_project) { fork_project(parent_project, nil, repository: true, target_project: create(:project, :private, :repository)) }
+
+ let(:merge_request) do
+ create(:merge_request, source_project: forked_project, source_branch: 'feature',
+ target_project: parent_project, target_branch: 'master')
+ end
+
+ let!(:pipeline_in_parent) do
+ create(:ci_pipeline, :merged_result_pipeline, merge_request: merge_request, project: parent_project)
+ end
+
+ let!(:pipeline_in_fork) do
+ create(:ci_pipeline, :merged_result_pipeline, merge_request: merge_request, project: forked_project)
+ end
+
+ let(:finder) { described_class.new(merge_request, actor) }
+
+ before_all do
+ parent_project.add_developer(developer_in_parent)
+ parent_project.add_developer(developer_in_both)
+ parent_project.add_reporter(reporter_in_parent_and_developer_in_fork)
+ forked_project.add_developer(developer_in_fork)
+ forked_project.add_developer(developer_in_both)
+ forked_project.add_developer(reporter_in_parent_and_developer_in_fork)
+ end
+
+ context 'when actor has permission to read pipelines in both parent and forked projects' do
+ let(:actor) { developer_in_both }
+
+ it 'returns all pipelines' do
+ is_expected.to eq([pipeline_in_fork, pipeline_in_parent])
+ end
+ end
+
+ context 'when actor has permission to read pipelines in both parent and forked projects' do
+ let(:actor) { reporter_in_parent_and_developer_in_fork }
+
+ it 'returns all pipelines' do
+ is_expected.to eq([pipeline_in_fork, pipeline_in_parent])
+ end
+ end
+
+ context 'when actor has permission to read pipelines in the parent project only' do
+ let(:actor) { developer_in_parent }
+
+ it 'returns pipelines in parent' do
+ is_expected.to eq([pipeline_in_parent])
+ end
+ end
+
+ context 'when actor has permission to read pipelines in the forked project only' do
+ let(:actor) { developer_in_fork }
+
+ it 'returns pipelines in fork' do
+ is_expected.to eq([pipeline_in_fork])
+ end
+ end
+
+ context 'when actor does not have permission to read pipelines' do
+ let(:actor) { external_user }
+
+ it 'returns nothing' do
+ is_expected.to be_empty
+ end
+ end
+
+ context 'when actor is nil' do
+ let(:actor) { nil }
+
+ it 'returns nothing' do
+ is_expected.to be_empty
+ end
+ end
+ end
+
describe '#all' do
let(:merge_request) { create(:merge_request) }
let(:project) { merge_request.source_project }
- subject { described_class.new(merge_request) }
+ subject { described_class.new(merge_request, nil) }
shared_examples 'returning pipelines with proper ordering' do
let!(:all_pipelines) do
@@ -134,7 +220,7 @@ RSpec.describe Ci::PipelinesForMergeRequestFinder do
branch_pipeline_2,
branch_pipeline])
- expect(described_class.new(merge_request_2).all)
+ expect(described_class.new(merge_request_2, nil).all)
.to eq([detached_merge_request_pipeline_2,
branch_pipeline_2,
branch_pipeline])
diff --git a/spec/finders/ci/runner_jobs_finder_spec.rb b/spec/finders/ci/runner_jobs_finder_spec.rb
index 7c9f762c000..3569582d70f 100644
--- a/spec/finders/ci/runner_jobs_finder_spec.rb
+++ b/spec/finders/ci/runner_jobs_finder_spec.rb
@@ -21,13 +21,13 @@ RSpec.describe Ci::RunnerJobsFinder do
end
context 'when params contains status' do
- HasStatus::AVAILABLE_STATUSES.each do |target_status|
+ Ci::HasStatus::AVAILABLE_STATUSES.each do |target_status|
context "when status is #{target_status}" do
let(:params) { { status: target_status } }
let!(:job) { create(:ci_build, runner: runner, project: project, status: target_status) }
before do
- exception_status = HasStatus::AVAILABLE_STATUSES - [target_status]
+ exception_status = Ci::HasStatus::AVAILABLE_STATUSES - [target_status]
create(:ci_build, runner: runner, project: project, status: exception_status.first)
end
diff --git a/spec/finders/ci/variables_finder_spec.rb b/spec/finders/ci/variables_finder_spec.rb
new file mode 100644
index 00000000000..cd5f950ca8e
--- /dev/null
+++ b/spec/finders/ci/variables_finder_spec.rb
@@ -0,0 +1,44 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Ci::VariablesFinder do
+ let!(:project) { create(:project) }
+ let!(:params) { {} }
+
+ let!(:var1) { create(:ci_variable, project: project, key: 'key1', environment_scope: 'staging') }
+ let!(:var2) { create(:ci_variable, project: project, key: 'key2', environment_scope: 'staging') }
+ let!(:var3) { create(:ci_variable, project: project, key: 'key2', environment_scope: 'production') }
+
+ describe '#initialize' do
+ subject { described_class.new(project, params) }
+
+ context 'without key filter' do
+ let!(:params) { {} }
+
+ it 'raises an error' do
+ expect { subject }.to raise_error(ArgumentError, 'Please provide params[:key]')
+ end
+ end
+ end
+
+ describe '#execute' do
+ subject { described_class.new(project.reload, params).execute }
+
+ context 'with key filter' do
+ let!(:params) { { key: 'key1' } }
+
+ it 'returns var1' do
+ expect(subject).to contain_exactly(var1)
+ end
+ end
+
+ context 'with key and environment_scope filter' do
+ let!(:params) { { key: 'key2', filter: { environment_scope: 'staging' } } }
+
+ it 'returns var2' do
+ expect(subject).to contain_exactly(var2)
+ end
+ end
+ end
+end
diff --git a/spec/finders/events_finder_spec.rb b/spec/finders/events_finder_spec.rb
index b13ef7e94e7..fe2e449f03d 100644
--- a/spec/finders/events_finder_spec.rb
+++ b/spec/finders/events_finder_spec.rb
@@ -66,29 +66,13 @@ RSpec.describe EventsFinder do
end
end
- describe 'wiki events feature flag' do
+ describe 'wiki events' do
let_it_be(:events) { create_list(:wiki_page_event, 3, project: public_project) }
subject(:finder) { described_class.new(source: public_project, target_type: 'wiki', current_user: user) }
- context 'the wiki_events feature flag is disabled' do
- before do
- stub_feature_flags(wiki_events: false)
- end
-
- it 'omits the wiki page events' do
- expect(finder.execute).to be_empty
- end
- end
-
- context 'the wiki_events feature flag is enabled' do
- before do
- stub_feature_flags(wiki_events: true)
- end
-
- it 'can find the wiki events' do
- expect(finder.execute).to match_array(events)
- end
+ it 'can find the wiki events' do
+ expect(finder.execute).to match_array(events)
end
end
diff --git a/spec/finders/group_projects_finder_spec.rb b/spec/finders/group_projects_finder_spec.rb
index 7499461ad8f..14f2bb017c6 100644
--- a/spec/finders/group_projects_finder_spec.rb
+++ b/spec/finders/group_projects_finder_spec.rb
@@ -46,6 +46,18 @@ RSpec.describe GroupProjectsFinder do
context 'without subgroups projects' do
it { is_expected.to match_array([shared_project_3, shared_project_2, shared_project_1, private_project, public_project]) }
end
+
+ context "with min access level" do
+ let!(:shared_project_4) { create(:project, :internal, path: '8') }
+
+ before do
+ shared_project_4.project_group_links.create(group_access: Gitlab::Access::REPORTER, group: group)
+ end
+
+ let(:params) { { min_access_level: Gitlab::Access::MAINTAINER } }
+
+ it { is_expected.to match_array([shared_project_3, shared_project_2, shared_project_1, private_project, public_project]) }
+ end
end
end
@@ -171,6 +183,38 @@ RSpec.describe GroupProjectsFinder do
end
end
+ describe 'feature availability' do
+ let!(:project_with_issues_disabled) { create(:project, :issues_disabled, :internal, path: '9') }
+ let!(:project_with_merge_request_disabled) { create(:project, :merge_requests_disabled, :internal, path: '10') }
+
+ before do
+ project_with_issues_disabled.project_group_links.create!(group_access: Gitlab::Access::REPORTER, group: group)
+ project_with_merge_request_disabled.project_group_links.create!(group_access: Gitlab::Access::REPORTER, group: group)
+ end
+
+ context 'without issues and merge request enabled' do
+ it { is_expected.to match_array([public_project, shared_project_1, shared_project_3, project_with_issues_disabled, project_with_merge_request_disabled]) }
+ end
+
+ context 'with issues enabled' do
+ let(:params) { { with_issues_enabled: true } }
+
+ it { is_expected.to match_array([public_project, shared_project_1, shared_project_3, project_with_merge_request_disabled]) }
+ end
+
+ context 'with merge request enabled' do
+ let(:params) { { with_merge_requests_enabled: true } }
+
+ it { is_expected.to match_array([public_project, shared_project_1, shared_project_3, project_with_issues_disabled]) }
+ end
+
+ context 'with issues and merge request enabled' do
+ let(:params) { { with_merge_requests_enabled: true, with_issues_enabled: true } }
+
+ it { is_expected.to match_array([public_project, shared_project_1, shared_project_3]) }
+ end
+ end
+
describe 'limiting' do
context 'without limiting' do
it 'returns all projects' do
diff --git a/spec/finders/merge_requests_finder_spec.rb b/spec/finders/merge_requests_finder_spec.rb
index f76110e3d85..e3643698012 100644
--- a/spec/finders/merge_requests_finder_spec.rb
+++ b/spec/finders/merge_requests_finder_spec.rb
@@ -53,6 +53,21 @@ RSpec.describe MergeRequestsFinder do
expect(merge_requests).to be_empty
end
+ context 'filtering by not author ID' do
+ let(:params) { { not: { author_id: user2.id } } }
+
+ before do
+ merge_request2.update!(author: user2)
+ merge_request3.update!(author: user2)
+ end
+
+ it 'returns merge requests not created by that user' do
+ merge_requests = described_class.new(user, params).execute
+
+ expect(merge_requests).to contain_exactly(merge_request1, merge_request4, merge_request5)
+ end
+ end
+
it 'filters by projects' do
params = { projects: [project2.id, project3.id] }
@@ -258,6 +273,11 @@ RSpec.describe MergeRequestsFinder do
let(:expected_issuables) { [merge_request1, merge_request2] }
end
+ it_behaves_like 'assignee NOT ID filter' do
+ let(:params) { { not: { assignee_id: user.id } } }
+ let(:expected_issuables) { [merge_request3, merge_request4, merge_request5] }
+ end
+
it_behaves_like 'assignee username filter' do
before do
project2.add_developer(user3)
@@ -269,6 +289,15 @@ RSpec.describe MergeRequestsFinder do
let(:expected_issuables) { [merge_request3] }
end
+ it_behaves_like 'assignee NOT username filter' do
+ before do
+ merge_request2.assignees = [user2]
+ end
+
+ let(:params) { { not: { assignee_username: [user.username, user2.username] } } }
+ let(:expected_issuables) { [merge_request4, merge_request5] }
+ end
+
it_behaves_like 'no assignee filter' do
let_it_be(:user3) { create(:user) }
let(:expected_issuables) { [merge_request4, merge_request5] }
@@ -294,6 +323,16 @@ RSpec.describe MergeRequestsFinder do
expect(merge_requests).to contain_exactly(merge_request2, merge_request3)
end
+
+ context 'using NOT' do
+ let(:params) { { not: { milestone_title: group_milestone.title } } }
+
+ it 'returns MRs not assigned to that group milestone' do
+ merge_requests = described_class.new(user, params).execute
+
+ expect(merge_requests).to contain_exactly(merge_request1, merge_request4, merge_request5)
+ end
+ end
end
end
diff --git a/spec/finders/notes_finder_spec.rb b/spec/finders/notes_finder_spec.rb
index 5610f5889e6..868b126dc28 100644
--- a/spec/finders/notes_finder_spec.rb
+++ b/spec/finders/notes_finder_spec.rb
@@ -123,7 +123,7 @@ RSpec.describe NotesFinder do
let!(:note1) { create :note_on_commit, project: project }
let!(:note2) { create :note_on_commit, project: project }
let(:commit) { note1.noteable }
- let(:params) { { project: project, target_id: commit.id, target_type: 'commit', last_fetched_at: 1.hour.ago.to_i } }
+ let(:params) { { project: project, target_id: commit.id, target_type: 'commit', last_fetched_at: 1.hour.ago } }
it 'finds all notes' do
notes = described_class.new(user, params).execute
@@ -172,7 +172,7 @@ RSpec.describe NotesFinder do
let(:confidential_issue) { create(:issue, :confidential, project: project, author: user) }
let!(:confidential_note) { create(:note, noteable: confidential_issue, project: confidential_issue.project) }
- let(:params) { { project: confidential_issue.project, target_id: confidential_issue.id, target_type: 'issue', last_fetched_at: 1.hour.ago.to_i } }
+ let(:params) { { project: confidential_issue.project, target_id: confidential_issue.id, target_type: 'issue', last_fetched_at: 1.hour.ago } }
it 'returns notes if user can see the issue' do
expect(described_class.new(user, params).execute).to eq([confidential_note])
@@ -204,7 +204,7 @@ RSpec.describe NotesFinder do
end
it 'returns the expected notes when last_fetched_at is given' do
- params = { project: project, target: commit, last_fetched_at: 1.hour.ago.to_i }
+ params = { project: project, target: commit, last_fetched_at: 1.hour.ago }
expect(described_class.new(user, params).execute).to eq([note2])
end
diff --git a/spec/finders/packages/conan/package_file_finder_spec.rb b/spec/finders/packages/conan/package_file_finder_spec.rb
new file mode 100644
index 00000000000..d0c9efa1418
--- /dev/null
+++ b/spec/finders/packages/conan/package_file_finder_spec.rb
@@ -0,0 +1,64 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+RSpec.describe ::Packages::Conan::PackageFileFinder do
+ let_it_be(:package) { create(:conan_package) }
+ let_it_be(:package_file) { package.package_files.first }
+ let(:package_file_name) { package_file.file_name }
+ let(:params) { {} }
+
+ RSpec.shared_examples 'package file finder examples' do
+ it { is_expected.to eq(package_file) }
+
+ context 'with conan_file_type' do
+ # conan packages contain a conanmanifest.txt file for both conan_file_types
+ let(:package_file_name) { 'conanmanifest.txt' }
+ let(:params) { { conan_file_type: :recipe_file } }
+
+ it { expect(subject.conan_file_type).to eq('recipe_file') }
+ end
+
+ context 'with conan_package_reference' do
+ let_it_be(:other_package) { create(:conan_package) }
+ let_it_be(:package_file_name) { 'conan_package.tgz' }
+ let_it_be(:package_file) { package.package_files.find_by(file_name: package_file_name) }
+
+ let(:params) do
+ { conan_package_reference: package_file.conan_file_metadatum.conan_package_reference }
+ end
+
+ it { expect(subject).to eq(package_file) }
+ end
+
+ context 'with file_name_like' do
+ let(:package_file_name) { package_file.file_name.upcase }
+ let(:params) { { with_file_name_like: true } }
+
+ it { is_expected.to eq(package_file) }
+ end
+ end
+
+ describe '#execute' do
+ subject { described_class.new(package, package_file_name, params).execute }
+
+ it_behaves_like 'package file finder examples'
+
+ context 'with unknown file_name' do
+ let(:package_file_name) { 'unknown.jpg' }
+
+ it { expect(subject).to be_nil }
+ end
+ end
+
+ describe '#execute!' do
+ subject { described_class.new(package, package_file_name, params).execute! }
+
+ it_behaves_like 'package file finder examples'
+
+ context 'with unknown file_name' do
+ let(:package_file_name) { 'unknown.jpg' }
+
+ it { expect { subject }.to raise_error(ActiveRecord::RecordNotFound) }
+ end
+ end
+end
diff --git a/spec/finders/packages/conan/package_finder_spec.rb b/spec/finders/packages/conan/package_finder_spec.rb
new file mode 100644
index 00000000000..936a0e5ff4b
--- /dev/null
+++ b/spec/finders/packages/conan/package_finder_spec.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+RSpec.describe ::Packages::Conan::PackageFinder do
+ let_it_be(:user) { create(:user) }
+ let_it_be(:project) { create(:project, :public) }
+
+ describe '#execute' do
+ let!(:conan_package) { create(:conan_package, project: project) }
+ let!(:conan_package2) { create(:conan_package, project: project) }
+
+ subject { described_class.new(user, query: query).execute }
+
+ context 'packages that are not visible to user' do
+ let!(:non_visible_project) { create(:project, :private) }
+ let!(:non_visible_conan_package) { create(:conan_package, project: non_visible_project) }
+ let(:query) { "#{conan_package.name.split('/').first[0, 3]}%" }
+
+ it { is_expected.to eq [conan_package, conan_package2] }
+ end
+ end
+end
diff --git a/spec/finders/packages/go/module_finder_spec.rb b/spec/finders/packages/go/module_finder_spec.rb
new file mode 100644
index 00000000000..e5c8827fc8d
--- /dev/null
+++ b/spec/finders/packages/go/module_finder_spec.rb
@@ -0,0 +1,71 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Packages::Go::ModuleFinder do
+ let_it_be(:project) { create :project }
+ let_it_be(:other_project) { create :project }
+ let(:finder) { described_class.new project, module_name }
+
+ shared_examples 'an invalid path' do
+ describe '#module_name' do
+ it 'returns the expected name' do
+ expect(finder.module_name).to eq(expected_name)
+ end
+ end
+
+ describe '#execute' do
+ it 'returns nil' do
+ expect(finder.execute).to be_nil
+ end
+ end
+ end
+
+ describe '#execute' do
+ context 'with module name equal to project name' do
+ let(:module_name) { base_url(project) }
+
+ it 'returns a module with empty path' do
+ mod = finder.execute
+ expect(mod).not_to be_nil
+ expect(mod.path).to eq('')
+ end
+ end
+
+ context 'with module name starting with project name and slash' do
+ let(:module_name) { base_url(project) + '/mod' }
+
+ it 'returns a module with non-empty path' do
+ mod = finder.execute
+ expect(mod).not_to be_nil
+ expect(mod.path).to eq('mod')
+ end
+ end
+
+ context 'with a module name not equal to and not starting with project name' do
+ let(:module_name) { base_url(other_project) }
+
+ it 'returns nil' do
+ expect(finder.execute).to be_nil
+ end
+ end
+ end
+
+ context 'with relative path component' do
+ it_behaves_like 'an invalid path' do
+ let(:module_name) { base_url(project) + '/../xyz' }
+ let(:expected_name) { base_url(project.namespace) + '/xyz' }
+ end
+ end
+
+ context 'with many relative path components' do
+ it_behaves_like 'an invalid path' do
+ let(:module_name) { base_url(project) + ('/..' * 10) + '/xyz' }
+ let(:expected_name) { ('../' * 7) + 'xyz' }
+ end
+ end
+
+ def base_url(project)
+ "#{Settings.build_gitlab_go_url}/#{project.full_path}"
+ end
+end
diff --git a/spec/finders/packages/go/version_finder_spec.rb b/spec/finders/packages/go/version_finder_spec.rb
new file mode 100644
index 00000000000..b67842d1e05
--- /dev/null
+++ b/spec/finders/packages/go/version_finder_spec.rb
@@ -0,0 +1,160 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Packages::Go::VersionFinder do
+ let_it_be(:user) { create :user }
+ let_it_be(:project) { create :project_empty_repo, creator: user, path: 'my-go-lib' }
+
+ let(:finder) { described_class.new mod }
+
+ before :all do
+ create :go_module_commit, :files, project: project, tag: 'v1.0.0', files: { 'README.md' => 'Hi' }
+ create :go_module_commit, :module, project: project, tag: 'v1.0.1'
+ create :go_module_commit, :package, project: project, tag: 'v1.0.2', path: 'pkg'
+ create :go_module_commit, :module, project: project, tag: 'v1.0.3', name: 'mod'
+ create :go_module_commit, :module, project: project, tag: 'v1.0.4', name: 'bad-mod', url: 'example.com/go-lib'
+ create :go_module_commit, :files, project: project, tag: 'c1', files: { 'y.go' => "package a\n" }
+ create :go_module_commit, :module, project: project, tag: 'c2', name: 'v2'
+ create :go_module_commit, :files, project: project, tag: 'v2.0.0', files: { 'v2/x.go' => "package a\n" }
+ end
+
+ before do
+ stub_feature_flags(go_proxy_disable_gomod_validation: false)
+ end
+
+ shared_examples '#execute' do |*expected|
+ it "returns #{expected.empty? ? 'nothing' : expected.join(', ')}" do
+ actual = finder.execute.map { |x| x.name }
+ expect(actual.to_set).to eq(expected.to_set)
+ end
+ end
+
+ shared_examples '#find with an invalid argument' do |message|
+ it "raises an argument exception: #{message}" do
+ expect { finder.find(target) }.to raise_error(ArgumentError, message)
+ end
+ end
+
+ describe '#execute' do
+ context 'for the root module' do
+ let(:mod) { create :go_module, project: project }
+
+ it_behaves_like '#execute', 'v1.0.1', 'v1.0.2', 'v1.0.3', 'v1.0.4'
+ end
+
+ context 'for the package' do
+ let(:mod) { create :go_module, project: project, path: 'pkg' }
+
+ it_behaves_like '#execute'
+ end
+
+ context 'for the submodule' do
+ let(:mod) { create :go_module, project: project, path: 'mod' }
+
+ it_behaves_like '#execute', 'v1.0.3', 'v1.0.4'
+ end
+
+ context 'for the root module v2' do
+ let(:mod) { create :go_module, project: project, path: 'v2' }
+
+ it_behaves_like '#execute', 'v2.0.0'
+ end
+
+ context 'for the bad module' do
+ let(:mod) { create :go_module, project: project, path: 'bad-mod' }
+
+ context 'with gomod checking enabled' do
+ it_behaves_like '#execute'
+ end
+
+ context 'with gomod checking disabled' do
+ before do
+ stub_feature_flags(go_proxy_disable_gomod_validation: true)
+ end
+
+ it_behaves_like '#execute', 'v1.0.4'
+ end
+ end
+ end
+
+ describe '#find' do
+ let(:mod) { create :go_module, project: project }
+
+ context 'with a ref' do
+ it 'returns a ref version' do
+ ref = project.repository.find_branch 'master'
+ v = finder.find(ref)
+ expect(v.type).to eq(:ref)
+ expect(v.ref).to eq(ref)
+ end
+ end
+
+ context 'with a semver tag' do
+ it 'returns a version with a semver' do
+ v = finder.find(project.repository.find_tag('v1.0.0'))
+ expect(v.major).to eq(1)
+ expect(v.minor).to eq(0)
+ expect(v.patch).to eq(0)
+ expect(v.prerelease).to be_nil
+ expect(v.build).to be_nil
+ end
+ end
+
+ context 'with a semver tag string' do
+ it 'returns a version with a semver' do
+ v = finder.find('v1.0.1')
+ expect(v.major).to eq(1)
+ expect(v.minor).to eq(0)
+ expect(v.patch).to eq(1)
+ expect(v.prerelease).to be_nil
+ expect(v.build).to be_nil
+ end
+ end
+
+ context 'with a commit' do
+ it 'retruns a commit version' do
+ v = finder.find(project.repository.head_commit)
+ expect(v.type).to eq(:commit)
+ end
+ end
+
+ context 'with a pseudo-version' do
+ it 'returns a pseudo version' do
+ commit = project.repository.head_commit
+ pseudo = "v0.0.0-#{commit.committed_date.strftime('%Y%m%d%H%M%S')}-#{commit.sha[0..11]}"
+ v = finder.find(pseudo)
+ expect(v.type).to eq(:pseudo)
+ expect(v.commit).to eq(commit)
+ expect(v.name).to eq(pseudo)
+ end
+ end
+
+ context 'with a string that is not a semantic version' do
+ it 'returns nil' do
+ expect(finder.find('not-a-semver')).to be_nil
+ end
+ end
+
+ context 'with a pseudo-version that does not reference a commit' do
+ it_behaves_like '#find with an invalid argument', 'invalid pseudo-version: unknown commit' do
+ let(:commit) { project.repository.head_commit }
+ let(:target) { "v0.0.0-#{commit.committed_date.strftime('%Y%m%d%H%M%S')}-#{'0' * 12}" }
+ end
+ end
+
+ context 'with a pseudo-version with a short sha' do
+ it_behaves_like '#find with an invalid argument', 'invalid pseudo-version: revision is shorter than canonical' do
+ let(:commit) { project.repository.head_commit }
+ let(:target) { "v0.0.0-#{commit.committed_date.strftime('%Y%m%d%H%M%S')}-#{commit.sha[0..10]}" }
+ end
+ end
+
+ context 'with a pseudo-version with an invalid timestamp' do
+ it_behaves_like '#find with an invalid argument', 'invalid pseudo-version: does not match version-control timestamp' do
+ let(:commit) { project.repository.head_commit }
+ let(:target) { "v0.0.0-#{'0' * 14}-#{commit.sha[0..11]}" }
+ end
+ end
+ end
+end
diff --git a/spec/finders/packages/group_packages_finder_spec.rb b/spec/finders/packages/group_packages_finder_spec.rb
new file mode 100644
index 00000000000..163c920f621
--- /dev/null
+++ b/spec/finders/packages/group_packages_finder_spec.rb
@@ -0,0 +1,156 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+RSpec.describe Packages::GroupPackagesFinder do
+ let_it_be(:user) { create(:user) }
+ let_it_be(:group) { create(:group) }
+ let_it_be(:project) { create(:project, namespace: group) }
+ let(:another_group) { create(:group) }
+
+ before do
+ group.add_developer(user)
+ end
+
+ describe '#execute' do
+ let(:params) { { exclude_subgroups: false } }
+
+ subject { described_class.new(user, group, params).execute }
+
+ shared_examples 'with package type' do |package_type|
+ let(:params) { { exclude_subgroups: false, package_type: package_type } }
+
+ it { is_expected.to match_array([send("package_#{package_type}")]) }
+ end
+
+ def self.package_types
+ @package_types ||= Packages::Package.package_types.keys
+ end
+
+ context 'group has packages' do
+ let!(:package1) { create(:maven_package, project: project) }
+ let!(:package2) { create(:maven_package, project: project) }
+ let!(:package3) { create(:maven_package) }
+
+ it { is_expected.to match_array([package1, package2]) }
+
+ context 'subgroup has packages' do
+ let(:subgroup) { create(:group, parent: group) }
+ let(:subproject) { create(:project, namespace: subgroup) }
+ let!(:package4) { create(:npm_package, project: subproject) }
+
+ it { is_expected.to match_array([package1, package2, package4]) }
+
+ context 'excluding subgroups' do
+ let(:params) { { exclude_subgroups: true } }
+
+ it { is_expected.to match_array([package1, package2]) }
+ end
+ end
+
+ context 'when there are processing packages' do
+ let!(:package4) { create(:nuget_package, project: project, name: Packages::Nuget::CreatePackageService::TEMPORARY_PACKAGE_NAME) }
+
+ it { is_expected.to match_array([package1, package2]) }
+ end
+
+ context 'does not include packages without version number' do
+ let!(:package_without_version) { create(:maven_package, project: project, version: nil) }
+
+ it { is_expected.not_to include(package_without_version) }
+ end
+
+ context 'with package_name' do
+ let_it_be(:named_package) { create(:maven_package, project: project, name: 'maven') }
+ let(:params) { { package_name: package_name } }
+
+ context 'as complete name' do
+ let(:package_name) { 'maven' }
+
+ it { is_expected.to eq([named_package]) }
+ end
+
+ %w[aven mav ave].each do |filter|
+ context "for fuzzy filter #{filter}" do
+ let(:package_name) { filter }
+
+ it { is_expected.to eq([named_package]) }
+ end
+ end
+ end
+ end
+
+ context 'group has package of all types' do
+ package_types.each { |pt| let!("package_#{pt}") { create("#{pt}_package", project: project) } }
+
+ package_types.each do |package_type|
+ it_behaves_like 'with package type', package_type
+ end
+ end
+
+ context 'group has no packages' do
+ it { is_expected.to be_empty }
+ end
+
+ context 'group is nil' do
+ subject { described_class.new(user, nil).execute }
+
+ it { is_expected.to be_empty}
+ end
+
+ context 'package type is nil' do
+ let!(:package1) { create(:maven_package, project: project) }
+
+ subject { described_class.new(user, group, package_type: nil).execute }
+
+ it { is_expected.to match_array([package1])}
+ end
+
+ context 'with invalid package_type' do
+ let(:params) { { package_type: 'invalid_type' } }
+
+ it { expect { subject }.to raise_exception(described_class::InvalidPackageTypeError) }
+ end
+
+ context 'when project is public' do
+ let_it_be(:other_user) { create(:user) }
+ let(:finder) { described_class.new(other_user, group) }
+
+ before do
+ project.update!(visibility_level: ProjectFeature::ENABLED)
+ end
+
+ context 'when packages are public' do
+ before do
+ project.project_feature.update!(
+ builds_access_level: ProjectFeature::PRIVATE,
+ merge_requests_access_level: ProjectFeature::PRIVATE,
+ repository_access_level: ProjectFeature::ENABLED)
+ end
+
+ it 'returns group packages' do
+ package1 = create(:maven_package, project: project)
+ package2 = create(:maven_package, project: project)
+ create(:maven_package)
+
+ expect(finder.execute).to match_array([package1, package2])
+ end
+ end
+
+ context 'packages are members only' do
+ before do
+ project.project_feature.update!(
+ builds_access_level: ProjectFeature::PRIVATE,
+ merge_requests_access_level: ProjectFeature::PRIVATE,
+ repository_access_level: ProjectFeature::PRIVATE)
+
+ create(:maven_package, project: project)
+ create(:maven_package)
+ end
+
+ it 'filters out the project if the user doesn\'t have permission' do
+ expect(finder.execute).to be_empty
+ end
+ end
+ end
+ end
+end
diff --git a/spec/finders/packages/maven/package_finder_spec.rb b/spec/finders/packages/maven/package_finder_spec.rb
new file mode 100644
index 00000000000..239e8c10f52
--- /dev/null
+++ b/spec/finders/packages/maven/package_finder_spec.rb
@@ -0,0 +1,57 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+RSpec.describe ::Packages::Maven::PackageFinder do
+ let(:user) { create(:user) }
+ let(:group) { create(:group) }
+ let(:project) { create(:project, namespace: group) }
+ let(:package) { create(:maven_package, project: project) }
+
+ before do
+ group.add_developer(user)
+ end
+
+ describe '#execute!' do
+ context 'within the project' do
+ it 'returns a package' do
+ finder = described_class.new(package.maven_metadatum.path, user, project: project)
+
+ expect(finder.execute!).to eq(package)
+ end
+
+ it 'raises an error' do
+ finder = described_class.new('com/example/my-app/1.0-SNAPSHOT', user, project: project)
+
+ expect { finder.execute! }.to raise_error(ActiveRecord::RecordNotFound)
+ end
+ end
+
+ context 'across all projects' do
+ it 'returns a package' do
+ finder = described_class.new(package.maven_metadatum.path, user)
+
+ expect(finder.execute!).to eq(package)
+ end
+
+ it 'raises an error' do
+ finder = described_class.new('com/example/my-app/1.0-SNAPSHOT', user)
+
+ expect { finder.execute! }.to raise_error(ActiveRecord::RecordNotFound)
+ end
+ end
+
+ context 'within a group' do
+ it 'returns a package' do
+ finder = described_class.new(package.maven_metadatum.path, user, group: group)
+
+ expect(finder.execute!).to eq(package)
+ end
+
+ it 'raises an error' do
+ finder = described_class.new('com/example/my-app/1.0-SNAPSHOT', user, group: group)
+
+ expect { finder.execute! }.to raise_error(ActiveRecord::RecordNotFound)
+ end
+ end
+ end
+end
diff --git a/spec/finders/packages/npm/package_finder_spec.rb b/spec/finders/packages/npm/package_finder_spec.rb
new file mode 100644
index 00000000000..be54b1f8b18
--- /dev/null
+++ b/spec/finders/packages/npm/package_finder_spec.rb
@@ -0,0 +1,34 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+RSpec.describe ::Packages::Npm::PackageFinder do
+ let(:package) { create(:npm_package) }
+ let(:project) { package.project }
+ let(:package_name) { package.name }
+
+ describe '#execute!' do
+ subject { described_class.new(project, package_name).execute }
+
+ it { is_expected.to eq([package]) }
+
+ context 'with unknown package name' do
+ let(:package_name) { 'baz' }
+
+ it { is_expected.to be_empty }
+ end
+ end
+
+ describe '#find_by_version' do
+ let(:version) { package.version }
+
+ subject { described_class.new(project, package.name).find_by_version(version) }
+
+ it { is_expected.to eq(package) }
+
+ context 'with unknown version' do
+ let(:version) { 'foobar' }
+
+ it { is_expected.to be_nil }
+ end
+ end
+end
diff --git a/spec/finders/packages/nuget/package_finder_spec.rb b/spec/finders/packages/nuget/package_finder_spec.rb
new file mode 100644
index 00000000000..9295d0c7a2f
--- /dev/null
+++ b/spec/finders/packages/nuget/package_finder_spec.rb
@@ -0,0 +1,75 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+RSpec.describe Packages::Nuget::PackageFinder do
+ let_it_be(:package1) { create(:nuget_package) }
+ let_it_be(:project) { package1.project }
+ let_it_be(:package2) { create(:nuget_package, name: package1.name, version: '2.0.0', project: project) }
+ let_it_be(:package3) { create(:nuget_package, name: 'Another.Dummy.Package', project: project) }
+ let(:package_name) { package1.name }
+ let(:package_version) { nil }
+ let(:limit) { 50 }
+
+ describe '#execute!' do
+ subject { described_class.new(project, package_name: package_name, package_version: package_version, limit: limit).execute }
+
+ it { is_expected.to match_array([package1, package2]) }
+
+ context 'with lower case package name' do
+ let(:package_name) { package1.name.downcase }
+
+ it { is_expected.to match_array([package1, package2]) }
+ end
+
+ context 'with unknown package name' do
+ let(:package_name) { 'foobar' }
+
+ it { is_expected.to be_empty }
+ end
+
+ context 'with valid version' do
+ let(:package_version) { '2.0.0' }
+
+ it { is_expected.to match_array([package2]) }
+ end
+
+ context 'with unknown version' do
+ let(:package_version) { 'foobar' }
+
+ it { is_expected.to be_empty }
+ end
+
+ context 'with limit hit' do
+ let_it_be(:package4) { create(:nuget_package, name: package1.name, project: project) }
+ let_it_be(:package5) { create(:nuget_package, name: package1.name, project: project) }
+ let_it_be(:package6) { create(:nuget_package, name: package1.name, project: project) }
+ let(:limit) { 2 }
+
+ it { is_expected.to match_array([package5, package6]) }
+ end
+
+ context 'with downcase package name' do
+ let(:package_name) { package1.name.downcase }
+
+ it { is_expected.to match_array([package1, package2]) }
+ end
+
+ context 'with prefix wildcard' do
+ let(:package_name) { "%#{package1.name[3..-1]}" }
+
+ it { is_expected.to match_array([package1, package2]) }
+ end
+
+ context 'with suffix wildcard' do
+ let(:package_name) { "#{package1.name[0..-3]}%" }
+
+ it { is_expected.to match_array([package1, package2]) }
+ end
+
+ context 'with surrounding wildcards' do
+ let(:package_name) { "%#{package1.name[3..-3]}%" }
+
+ it { is_expected.to match_array([package1, package2]) }
+ end
+ end
+end
diff --git a/spec/finders/packages/package_file_finder_spec.rb b/spec/finders/packages/package_file_finder_spec.rb
new file mode 100644
index 00000000000..ab58f75fcae
--- /dev/null
+++ b/spec/finders/packages/package_file_finder_spec.rb
@@ -0,0 +1,44 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+RSpec.describe Packages::PackageFileFinder do
+ let_it_be(:package) { create(:maven_package) }
+ let_it_be(:package_file) { package.package_files.first }
+ let(:package_file_name) { package_file.file_name }
+ let(:params) { {} }
+
+ RSpec.shared_examples 'package file finder examples' do
+ it { is_expected.to eq(package_file) }
+
+ context 'with file_name_like' do
+ let(:package_file_name) { package_file.file_name.upcase }
+ let(:params) { { with_file_name_like: true } }
+
+ it { is_expected.to eq(package_file) }
+ end
+ end
+
+ describe '#execute' do
+ subject { described_class.new(package, package_file_name, params).execute }
+
+ it_behaves_like 'package file finder examples'
+
+ context 'with unknown file_name' do
+ let(:package_file_name) { 'unknown.jpg' }
+
+ it { expect(subject).to be_nil }
+ end
+ end
+
+ describe '#execute!' do
+ subject { described_class.new(package, package_file_name, params).execute! }
+
+ it_behaves_like 'package file finder examples'
+
+ context 'with unknown file_name' do
+ let(:package_file_name) { 'unknown.jpg' }
+
+ it { expect { subject }.to raise_error(ActiveRecord::RecordNotFound) }
+ end
+ end
+end
diff --git a/spec/finders/packages/package_finder_spec.rb b/spec/finders/packages/package_finder_spec.rb
new file mode 100644
index 00000000000..ef07e7575d1
--- /dev/null
+++ b/spec/finders/packages/package_finder_spec.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe ::Packages::PackageFinder do
+ let_it_be(:project) { create(:project) }
+ let_it_be(:maven_package) { create(:maven_package, project: project) }
+
+ describe '#execute' do
+ let(:package_id) { maven_package.id }
+
+ subject { described_class.new(project, package_id).execute }
+
+ it { is_expected.to eq(maven_package) }
+
+ context 'processing packages' do
+ let_it_be(:nuget_package) { create(:nuget_package, project: project, name: Packages::Nuget::CreatePackageService::TEMPORARY_PACKAGE_NAME) }
+ let(:package_id) { nuget_package.id }
+
+ it 'are not returned' do
+ expect { subject }.to raise_exception(ActiveRecord::RecordNotFound)
+ end
+ end
+ end
+end
diff --git a/spec/finders/packages/packages_finder_spec.rb b/spec/finders/packages/packages_finder_spec.rb
new file mode 100644
index 00000000000..925b003bb8e
--- /dev/null
+++ b/spec/finders/packages/packages_finder_spec.rb
@@ -0,0 +1,90 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe ::Packages::PackagesFinder do
+ let_it_be(:project) { create(:project) }
+ let_it_be(:maven_package) { create(:maven_package, project: project, created_at: 2.days.ago, name: 'maven', version: '2.0.0') }
+ let_it_be(:conan_package) { create(:conan_package, project: project, created_at: 1.day.ago, name: 'conan', version: '1.0.0') }
+
+ describe '#execute' do
+ let(:params) { {} }
+
+ subject { described_class.new(project, params).execute }
+
+ context 'with package_type' do
+ let_it_be(:npm_package1) { create(:npm_package, project: project) }
+ let_it_be(:npm_package2) { create(:npm_package, project: project) }
+
+ context 'conan packages' do
+ let(:params) { { package_type: 'conan' } }
+
+ it { is_expected.to eq([conan_package]) }
+ end
+
+ context 'npm packages' do
+ let(:params) { { package_type: 'npm' } }
+
+ it { is_expected.to match_array([npm_package1, npm_package2]) }
+ end
+ end
+
+ context 'with order_by' do
+ context 'by default is created_at' do
+ it { is_expected.to eq([maven_package, conan_package]) }
+ end
+
+ context 'order by name' do
+ let(:params) { { order_by: 'name' } }
+
+ it { is_expected.to eq([conan_package, maven_package]) }
+ end
+
+ context 'order by version' do
+ let(:params) { { order_by: 'version' } }
+
+ it { is_expected.to eq([conan_package, maven_package]) }
+ end
+
+ context 'order by type' do
+ let(:params) { { order_by: 'type' } }
+
+ it { is_expected.to eq([maven_package, conan_package]) }
+ end
+ end
+
+ context 'with sort' do
+ context 'by default is ascending' do
+ it { is_expected.to eq([maven_package, conan_package]) }
+ end
+
+ context 'can sort descended' do
+ let(:params) { { sort: 'desc' } }
+
+ it { is_expected.to eq([conan_package, maven_package]) }
+ end
+ end
+
+ context 'with package_name' do
+ let(:params) { { package_name: 'maven' } }
+
+ it { is_expected.to eq([maven_package]) }
+ end
+
+ context 'with nil params' do
+ it { is_expected.to match_array([conan_package, maven_package]) }
+ end
+
+ context 'with processing packages' do
+ let_it_be(:nuget_package) { create(:nuget_package, project: project, name: Packages::Nuget::CreatePackageService::TEMPORARY_PACKAGE_NAME) }
+
+ it { is_expected.to match_array([conan_package, maven_package]) }
+ end
+
+ context 'does not include packages without version number' do
+ let_it_be(:package_without_version) { create(:maven_package, project: project, version: nil) }
+
+ it { is_expected.not_to include(package_without_version) }
+ end
+ end
+end
diff --git a/spec/finders/packages/tags_finder_spec.rb b/spec/finders/packages/tags_finder_spec.rb
new file mode 100644
index 00000000000..47e1d25debf
--- /dev/null
+++ b/spec/finders/packages/tags_finder_spec.rb
@@ -0,0 +1,68 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+RSpec.describe Packages::TagsFinder do
+ let(:package) { create(:npm_package) }
+ let(:project) { package.project }
+ let!(:tag1) { create(:packages_tag, package: package) }
+ let!(:tag2) { create(:packages_tag, package: package) }
+ let(:package_name) { package.name }
+ let(:params) { {} }
+
+ describe '#execute' do
+ subject { described_class.new(project, package_name, params).execute }
+
+ it { is_expected.to match_array([tag1, tag2]) }
+
+ context 'with package type' do
+ let(:package_maven) { create(:maven_package, project: project) }
+ let!(:tag_maven) { create(:packages_tag, package: package_maven) }
+ let(:package_name) { package_maven.name }
+ let(:params) { { package_type: package_maven.package_type } }
+
+ it { is_expected.to match_array([tag_maven]) }
+ end
+
+ context 'with blank package type' do
+ let(:params) { { package_type: ' ' } }
+
+ it { is_expected.to match_array([tag1, tag2]) }
+ end
+
+ context 'with nil package type' do
+ let(:params) { { package_type: nil } }
+
+ it { is_expected.to match_array([tag1, tag2]) }
+ end
+
+ context 'with unknown package name' do
+ let(:package_name) { 'foobar' }
+
+ it { is_expected.to be_empty }
+ end
+ end
+
+ describe '#find_by_name' do
+ let(:tag_name) { tag1.name }
+
+ subject { described_class.new(project, package_name, params).execute.find_by_name(tag_name) }
+
+ it { is_expected.to eq(tag1) }
+
+ context 'with package type' do
+ let(:package_maven) { create(:maven_package, project: project) }
+ let!(:tag_maven) { create(:packages_tag, package: package_maven) }
+ let(:package_name) { package_maven.name }
+ let(:params) { { package_type: package_maven.package_type } }
+ let(:tag_name) { tag_maven.name }
+
+ it { is_expected.to eq(tag_maven) }
+ end
+
+ context 'with unknown tag_name' do
+ let(:tag_name) { 'foobar' }
+
+ it { is_expected.to be_nil }
+ end
+ end
+end
diff --git a/spec/finders/personal_access_tokens_finder_spec.rb b/spec/finders/personal_access_tokens_finder_spec.rb
index dde4f010e41..94954f4153b 100644
--- a/spec/finders/personal_access_tokens_finder_spec.rb
+++ b/spec/finders/personal_access_tokens_finder_spec.rb
@@ -218,6 +218,24 @@ RSpec.describe PersonalAccessTokensFinder do
end
end
+ describe 'with active or expired state' do
+ before do
+ params[:state] = 'active_or_expired'
+ end
+
+ it 'includes active tokens' do
+ is_expected.to include(active_personal_access_token, active_impersonation_token)
+ end
+
+ it 'includes expired tokens' do
+ is_expected.to include(expired_personal_access_token, expired_impersonation_token)
+ end
+
+ it 'does not include revoked tokens' do
+ is_expected.not_to include(revoked_personal_access_token, revoked_impersonation_token)
+ end
+ end
+
describe 'with id' do
subject { finder(params).find_by_id(active_personal_access_token.id) }
diff --git a/spec/finders/projects_finder_spec.rb b/spec/finders/projects_finder_spec.rb
index 929927ec1c4..bd71a8186ad 100644
--- a/spec/finders/projects_finder_spec.rb
+++ b/spec/finders/projects_finder_spec.rb
@@ -262,6 +262,17 @@ RSpec.describe ProjectsFinder, :do_not_mock_admin_mode do
it { is_expected.to match_array([public_project]) }
end
+ describe 'filter by repository_storage' do
+ let(:params) { { repository_storage: 'nfs-05' } }
+ let!(:project) { create(:project, :public) }
+
+ before do
+ project.update_columns(repository_storage: 'nfs-05')
+ end
+
+ it { is_expected.to match_array([project]) }
+ end
+
describe 'sorting' do
let(:params) { { sort: 'name_asc' } }
diff --git a/spec/finders/resource_milestone_event_finder_spec.rb b/spec/finders/resource_milestone_event_finder_spec.rb
index ff4508996e2..27e124afe2e 100644
--- a/spec/finders/resource_milestone_event_finder_spec.rb
+++ b/spec/finders/resource_milestone_event_finder_spec.rb
@@ -42,18 +42,6 @@ RSpec.describe ResourceMilestoneEventFinder do
expect(subject).to be_empty
end
- it 'paginates results' do
- milestone = create(:milestone, project: issue_project)
- create_event(milestone)
- create_event(milestone)
- issue_project.add_guest(user)
-
- paginated = described_class.new(user, issue, per_page: 1).execute
-
- expect(subject.count).to eq 2
- expect(paginated.count).to eq 1
- end
-
context 'when multiple events share the same milestone' do
it 'avoids N+1 queries' do
issue_project.add_developer(user)
@@ -71,8 +59,8 @@ RSpec.describe ResourceMilestoneEventFinder do
create_event(milestone2, :add)
create_event(milestone2, :remove)
- # 1 events + 1 milestones + 1 project + 1 user + 4 ability
- expect { described_class.new(user, issue).execute }.not_to exceed_query_limit(control_count + 7)
+ # 1 milestones + 1 project + 1 user + 4 ability
+ expect { described_class.new(user, issue).execute }.not_to exceed_query_limit(control_count + 6)
end
end
diff --git a/spec/finders/resource_state_event_finder_spec.rb b/spec/finders/resource_state_event_finder_spec.rb
new file mode 100644
index 00000000000..139ef549834
--- /dev/null
+++ b/spec/finders/resource_state_event_finder_spec.rb
@@ -0,0 +1,76 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe ResourceStateEventFinder do
+ let_it_be(:user) { create(:user) }
+
+ describe '#execute' do
+ subject { described_class.new(user, issue).execute }
+
+ let(:project) { create(:project) }
+ let(:issue) { create(:issue, project: project) }
+
+ let!(:event) { create(:resource_state_event, issue: issue) }
+
+ it 'returns events accessible by user' do
+ project.add_guest(user)
+
+ expect(subject).to eq [event]
+ end
+
+ context 'when issues are private' do
+ let(:project) { create(:project, :public, :issues_private) }
+
+ it 'does not return any events' do
+ expect(subject).to be_empty
+ end
+ end
+
+ context 'when issue is not accesible to the user' do
+ let(:project) { create(:project, :private) }
+
+ it 'does not return any events' do
+ expect(subject).to be_empty
+ end
+ end
+ end
+
+ describe '#can_read_eventable?' do
+ let(:project) { create(:project, :private) }
+
+ subject { described_class.new(user, eventable).can_read_eventable? }
+
+ context 'when eventable is an Issue' do
+ let(:eventable) { create(:issue, project: project) }
+
+ context 'when issue is readable' do
+ before do
+ project.add_developer(user)
+ end
+
+ it { is_expected.to be_truthy }
+ end
+
+ context 'when issue is not readable' do
+ it { is_expected.to be_falsey }
+ end
+ end
+
+ context 'when eventable is a MergeRequest' do
+ let(:eventable) { create(:merge_request, source_project: project) }
+
+ context 'when merge request is readable' do
+ before do
+ project.add_developer(user)
+ end
+
+ it { is_expected.to be_truthy }
+ end
+
+ context 'when merge request is not readable' do
+ it { is_expected.to be_falsey }
+ end
+ end
+ end
+end
diff --git a/spec/finders/snippets_finder_spec.rb b/spec/finders/snippets_finder_spec.rb
index 0affc832b30..6fc1cbcee0a 100644
--- a/spec/finders/snippets_finder_spec.rb
+++ b/spec/finders/snippets_finder_spec.rb
@@ -283,6 +283,12 @@ RSpec.describe SnippetsFinder do
it 'returns only personal snippets when the user cannot read cross project' do
expect(described_class.new(user).execute).to contain_exactly(private_personal_snippet, internal_personal_snippet, public_personal_snippet)
end
+
+ context 'when only project snippets are required' do
+ it 'returns no records' do
+ expect(described_class.new(user, only_project: true).execute).to be_empty
+ end
+ end
end
context 'when project snippets are disabled' do
@@ -295,6 +301,22 @@ RSpec.describe SnippetsFinder do
expect(finder.execute).to be_empty
end
end
+
+ context 'no sort param is provided' do
+ it 'returns snippets sorted by id' do
+ snippets = described_class.new(admin).execute
+
+ expect(snippets.ids).to eq(Snippet.order_id_desc.ids)
+ end
+ end
+
+ context 'sort param is provided' do
+ it 'returns snippets sorted by sort param' do
+ snippets = described_class.new(admin, sort: 'updated_desc').execute
+
+ expect(snippets.ids).to eq(Snippet.order_updated_desc.ids)
+ end
+ end
end
it_behaves_like 'snippet visibility'
diff --git a/spec/finders/todos_finder_spec.rb b/spec/finders/todos_finder_spec.rb
index 4123783d828..f6796398782 100644
--- a/spec/finders/todos_finder_spec.rb
+++ b/spec/finders/todos_finder_spec.rb
@@ -37,16 +37,63 @@ RSpec.describe TodosFinder do
end
context 'when filtering by type' do
- it 'returns correct todos when filtered by a type' do
+ it 'returns todos by type when filtered by a single type' do
todos = finder.new(user, { type: 'Issue' }).execute
expect(todos).to match_array([todo1])
end
- it 'returns the correct todos when filtering for multiple types' do
+ it 'returns todos by type when filtered by multiple types' do
+ design_todo = create(:todo, user: user, group: group, target: create(:design))
+
todos = finder.new(user, { type: %w[Issue MergeRequest] }).execute
- expect(todos).to match_array([todo1, todo2])
+ expect(todos).to contain_exactly(todo1, todo2)
+ expect(todos).not_to include(design_todo)
+ end
+
+ it 'returns all todos when type is nil' do
+ todos = finder.new(user, { type: nil }).execute
+
+ expect(todos).to contain_exactly(todo1, todo2)
+ end
+
+ it 'returns all todos when type is an empty collection' do
+ todos = finder.new(user, { type: [] }).execute
+
+ expect(todos).to contain_exactly(todo1, todo2)
+ end
+
+ it 'returns all todos when type is blank' do
+ todos = finder.new(user, { type: '' }).execute
+
+ expect(todos).to contain_exactly(todo1, todo2)
+ end
+
+ it 'returns todos by type when blank type is in type collection' do
+ todos = finder.new(user, { type: ['', 'MergeRequest'] }).execute
+
+ expect(todos).to contain_exactly(todo2)
+ end
+
+ it 'returns todos of all types when only blanks are in a collection' do
+ todos = finder.new(user, { type: ['', ''] }).execute
+
+ expect(todos).to contain_exactly(todo1, todo2)
+ end
+
+ it 'returns all todos when no type param' do
+ todos = finder.new(user).execute
+
+ expect(todos).to contain_exactly(todo1, todo2)
+ end
+
+ it 'raises an argument error when invalid type is passed' do
+ create(:todo, user: user, group: group, target: create(:design))
+
+ todos_finder = finder.new(user, { type: %w[Issue MergeRequest NotAValidType] })
+
+ expect { todos_finder.execute }.to raise_error(ArgumentError)
end
end
diff --git a/spec/finders/user_recent_events_finder_spec.rb b/spec/finders/user_recent_events_finder_spec.rb
index 04ba05c68e4..559d1004b4b 100644
--- a/spec/finders/user_recent_events_finder_spec.rb
+++ b/spec/finders/user_recent_events_finder_spec.rb
@@ -3,8 +3,8 @@
require 'spec_helper'
RSpec.describe UserRecentEventsFinder do
- let(:current_user) { create(:user) }
- let(:project_owner) { create(:user) }
+ let_it_be(:project_owner, reload: true) { create(:user) }
+ let_it_be(:current_user, reload: true) { create(:user) }
let(:private_project) { create(:project, :private, creator: project_owner) }
let(:internal_project) { create(:project, :internal, creator: project_owner) }
let(:public_project) { create(:project, :public, creator: project_owner) }
@@ -36,5 +36,17 @@ RSpec.describe UserRecentEventsFinder do
expect(finder.execute).to be_empty
end
+
+ describe 'design activity events' do
+ let_it_be(:event_a) { create(:design_event, author: project_owner) }
+ let_it_be(:event_b) { create(:design_event, author: project_owner) }
+
+ it 'only includes design events', :aggregate_failures do
+ events = finder.execute
+
+ expect(events).to include(event_a)
+ expect(events).to include(event_b)
+ end
+ end
end
end
diff --git a/spec/fixtures/api/graphql/introspection.graphql b/spec/fixtures/api/graphql/introspection.graphql
index 7b712068fcd..6b6de2efbaf 100644
--- a/spec/fixtures/api/graphql/introspection.graphql
+++ b/spec/fixtures/api/graphql/introspection.graphql
@@ -1,9 +1,15 @@
# pulled from GraphiQL query
query IntrospectionQuery {
__schema {
- queryType { name }
- mutationType { name }
- subscriptionType { name }
+ queryType {
+ name
+ }
+ mutationType {
+ name
+ }
+ subscriptionType {
+ name
+ }
types {
...FullType
}
@@ -54,7 +60,9 @@ fragment FullType on __Type {
fragment InputValue on __InputValue {
name
description
- type { ...TypeRef }
+ type {
+ ...TypeRef
+ }
defaultValue
}
diff --git a/spec/fixtures/api/schemas/entities/dag_job.json b/spec/fixtures/api/schemas/entities/dag_job.json
new file mode 100644
index 00000000000..171ac23ca06
--- /dev/null
+++ b/spec/fixtures/api/schemas/entities/dag_job.json
@@ -0,0 +1,10 @@
+{
+ "type": "object",
+ "required": ["name", "scheduling_type"],
+ "properties": {
+ "name": { "type": "string" },
+ "scheduling_type": { "type": ["string", null] },
+ "needs": { "type": "array" }
+ },
+ "additionalProperties": false
+}
diff --git a/spec/fixtures/api/schemas/entities/dag_job_group.json b/spec/fixtures/api/schemas/entities/dag_job_group.json
new file mode 100644
index 00000000000..69a4e69fc63
--- /dev/null
+++ b/spec/fixtures/api/schemas/entities/dag_job_group.json
@@ -0,0 +1,13 @@
+{
+ "type": "object",
+ "required": ["name", "size", "jobs"],
+ "properties": {
+ "name": { "type": "string" },
+ "size": { "type": "integer" },
+ "jobs": {
+ "type": "array",
+ "items": { "$ref": "dag_job.json" }
+ }
+ },
+ "additionalProperties": false
+}
diff --git a/spec/fixtures/api/schemas/entities/dag_pipeline.json b/spec/fixtures/api/schemas/entities/dag_pipeline.json
new file mode 100644
index 00000000000..a661577d040
--- /dev/null
+++ b/spec/fixtures/api/schemas/entities/dag_pipeline.json
@@ -0,0 +1,11 @@
+{
+ "type": "object",
+ "required": ["stages"],
+ "properties": {
+ "stages": {
+ "type": "array",
+ "items": { "$ref": "dag_stage.json" }
+ }
+ },
+ "additionalProperties": false
+}
diff --git a/spec/fixtures/api/schemas/entities/dag_stage.json b/spec/fixtures/api/schemas/entities/dag_stage.json
new file mode 100644
index 00000000000..6a7b1424ec5
--- /dev/null
+++ b/spec/fixtures/api/schemas/entities/dag_stage.json
@@ -0,0 +1,11 @@
+{
+ "type": "object",
+ "required": ["name", "groups"],
+ "properties": {
+ "name": { "type": "string" },
+ "groups": {
+ "type": "array",
+ "items": { "$ref": "dag_job_group.json" }
+ }
+ }
+}
diff --git a/spec/fixtures/api/schemas/evidences/build_artifact.json b/spec/fixtures/api/schemas/evidences/build_artifact.json
new file mode 100644
index 00000000000..850fb8c45a8
--- /dev/null
+++ b/spec/fixtures/api/schemas/evidences/build_artifact.json
@@ -0,0 +1,10 @@
+{
+ "type": "object",
+ "required": [
+ "url"
+ ],
+ "properties": {
+ "url": { "type": "string" }
+ },
+ "additionalProperties": false
+}
diff --git a/spec/fixtures/api/schemas/evidences/release.json b/spec/fixtures/api/schemas/evidences/release.json
index 37eb9a9b5c0..529abfe490e 100644
--- a/spec/fixtures/api/schemas/evidences/release.json
+++ b/spec/fixtures/api/schemas/evidences/release.json
@@ -19,6 +19,10 @@
"milestones": {
"type": "array",
"items": { "$ref": "milestone.json" }
+ },
+ "report_artifacts": {
+ "type": "array",
+ "items": { "$ref": "build_artifact.json" }
}
},
"additionalProperties": false
diff --git a/spec/fixtures/api/schemas/public_api/v4/packages/composer/index.json b/spec/fixtures/api/schemas/public_api/v4/packages/composer/index.json
new file mode 100644
index 00000000000..2245b39cabe
--- /dev/null
+++ b/spec/fixtures/api/schemas/public_api/v4/packages/composer/index.json
@@ -0,0 +1,29 @@
+{
+ "type": "object",
+ "required": ["packages", "provider-includes", "providers-url"],
+ "properties": {
+ "packages": {
+ "type": "array",
+ "items": { "type": "integer" }
+ },
+ "providers-url": {
+ "type": "string"
+ },
+ "provider-includes": {
+ "type": "object",
+ "required": ["p/%hash%.json"],
+ "properties": {
+ "p/%hash%.json": {
+ "type": "object",
+ "required": ["sha256"],
+ "properties": {
+ "sha256": {
+ "type": "string"
+ }
+ }
+ }
+ }
+ }
+ },
+ "additionalProperties": false
+}
diff --git a/spec/fixtures/api/schemas/public_api/v4/packages/composer/package.json b/spec/fixtures/api/schemas/public_api/v4/packages/composer/package.json
new file mode 100644
index 00000000000..324a8a4e00c
--- /dev/null
+++ b/spec/fixtures/api/schemas/public_api/v4/packages/composer/package.json
@@ -0,0 +1,65 @@
+{
+ "type": "object",
+ "required": [
+ "packages"
+ ],
+ "properties": {
+ "packages": {
+ "type": "object",
+ "propertyNames": {
+ "pattern": "^[A-Za-z_]+"
+ },
+ "patternProperties": {
+ "^[A-Za-z_]+": {
+ "type": "object",
+ "propertyNames": {
+ "pattern": "^[A-Za-z_0-9.]+"
+ },
+ "patternProperties": {
+ "^[A-Za-z_0-9.]+": {
+ "type": "object",
+ "required": [
+ "dist",
+ "uid",
+ "version"
+ ],
+ "properties": {
+ "uid": {
+ "type": "integer"
+ },
+ "version": {
+ "type": "string"
+ },
+ "dist": {
+ "type": "object",
+ "required": [
+ "type",
+ "url",
+ "reference",
+ "shasum"
+ ],
+ "properties": {
+ "type": {
+ "type": "string"
+ },
+ "url": {
+ "type": "string"
+ },
+ "reference": {
+ "type": "string"
+ },
+ "shasum": {
+ "type": "string"
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "additionalProperties": false
+ }
+ },
+ "additionalProperties": false
+}
diff --git a/spec/fixtures/api/schemas/public_api/v4/packages/composer/provider.json b/spec/fixtures/api/schemas/public_api/v4/packages/composer/provider.json
new file mode 100644
index 00000000000..5335fa9ad64
--- /dev/null
+++ b/spec/fixtures/api/schemas/public_api/v4/packages/composer/provider.json
@@ -0,0 +1,25 @@
+{
+ "type": "object",
+ "required": ["providers"],
+ "properties": {
+ "providers": {
+ "type": "object",
+ "propertyNames": {
+ "pattern": "^[A-Za-z_]+"
+ },
+ "patternProperties": {
+ "^[A-Za-z_]+": {
+ "type": "object",
+ "required": ["sha256"],
+ "properties": {
+ "sha256": {
+ "type": "string"
+ }
+ }
+ }
+ },
+ "additionalProperties": false
+ }
+ },
+ "additionalProperties": false
+}
diff --git a/spec/fixtures/api/schemas/public_api/v4/packages/group_package.json b/spec/fixtures/api/schemas/public_api/v4/packages/group_package.json
new file mode 100644
index 00000000000..f18e314a287
--- /dev/null
+++ b/spec/fixtures/api/schemas/public_api/v4/packages/group_package.json
@@ -0,0 +1,33 @@
+{
+ "type": "object",
+ "required": ["name", "version", "package_type", "_links", "project_id", "project_path"],
+ "properties": {
+ "name": {
+ "type": "string"
+ },
+ "version": {
+ "type": "string"
+ },
+ "package_type": {
+ "type": "string"
+ },
+ "_links": {
+ "type": "object",
+ "required": ["web_path"],
+ "properties": {
+ "details": {
+ "type": "string"
+ }
+ }
+ },
+ "created_at": {
+ "type": "string"
+ },
+ "project_id": {
+ "type": "integer"
+ },
+ "project_path": {
+ "type": "string"
+ }
+ }
+}
diff --git a/spec/fixtures/api/schemas/public_api/v4/packages/group_packages.json b/spec/fixtures/api/schemas/public_api/v4/packages/group_packages.json
new file mode 100644
index 00000000000..3169bbc8f25
--- /dev/null
+++ b/spec/fixtures/api/schemas/public_api/v4/packages/group_packages.json
@@ -0,0 +1,4 @@
+{
+ "type": "array",
+ "items": { "$ref": "./group_package.json" }
+}
diff --git a/spec/fixtures/api/schemas/public_api/v4/packages/npm_package.json b/spec/fixtures/api/schemas/public_api/v4/packages/npm_package.json
new file mode 100644
index 00000000000..d7e8a872abe
--- /dev/null
+++ b/spec/fixtures/api/schemas/public_api/v4/packages/npm_package.json
@@ -0,0 +1,8 @@
+{
+ "type": "object",
+ "required" : ["name", "versions"],
+ "properties" : {
+ "name": { "type": "string" },
+ "versions": { "type": "object" }
+ }
+}
diff --git a/spec/fixtures/api/schemas/public_api/v4/packages/npm_package_tags.json b/spec/fixtures/api/schemas/public_api/v4/packages/npm_package_tags.json
new file mode 100644
index 00000000000..db64fe1de23
--- /dev/null
+++ b/spec/fixtures/api/schemas/public_api/v4/packages/npm_package_tags.json
@@ -0,0 +1,7 @@
+{
+ "type": "object",
+ "properties" : {
+ "$tag": { "type": "string" },
+ "$version": { "type": "string" }
+ }
+}
diff --git a/spec/fixtures/api/schemas/public_api/v4/packages/npm_package_version.json b/spec/fixtures/api/schemas/public_api/v4/packages/npm_package_version.json
new file mode 100644
index 00000000000..3e74dc0a1c2
--- /dev/null
+++ b/spec/fixtures/api/schemas/public_api/v4/packages/npm_package_version.json
@@ -0,0 +1,46 @@
+{
+ "type": "object",
+ "required": ["name", "version", "dist"],
+ "properties" : {
+ "name": { "type": "string" },
+ "version": { "type": "string" },
+ "dist": {
+ "type": "object",
+ "required": ["shasum", "tarball"],
+ "properties" : {
+ "shasum": { "type": "string" },
+ "tarball": { "type": "string" }
+ }
+ },
+ "dependencies": {
+ "type": "object",
+ "patternProperties": {
+ ".{1,}": { "type": "string" }
+ }
+ },
+ "devDependencies": {
+ "type": "object",
+ "patternProperties": {
+ ".{1,}": { "type": "string" }
+ }
+ },
+ "bundleDependencies": {
+ "type": "object",
+ "patternProperties": {
+ ".{1,}": { "type": "string" }
+ }
+ },
+ "peerDependencies": {
+ "type": "object",
+ "patternProperties": {
+ ".{1,}": { "type": "string" }
+ }
+ },
+ "deprecated": {
+ "type": "object",
+ "patternProperties": {
+ ".{1,}": { "type": "string" }
+ }
+ }
+ }
+}
diff --git a/spec/fixtures/api/schemas/public_api/v4/packages/nuget/dependency_group.json b/spec/fixtures/api/schemas/public_api/v4/packages/nuget/dependency_group.json
new file mode 100644
index 00000000000..87dc2794b61
--- /dev/null
+++ b/spec/fixtures/api/schemas/public_api/v4/packages/nuget/dependency_group.json
@@ -0,0 +1,22 @@
+{
+ "type": "object",
+ "required": ["@id", "@type", "dependencies"],
+ "properties": {
+ "@id": { "type": "string" },
+ "@type": { "const": "PackageDependencyGroup" },
+ "targetFramework": { "type": "string" },
+ "dependencies": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "required": ["@id", "@type", "id", "range"],
+ "properties": {
+ "@id": { "type": "string" },
+ "@type": { "const": "PackageDependency" },
+ "id": { "type": "string" },
+ "range": { "type": "string" }
+ }
+ }
+ }
+ }
+}
diff --git a/spec/fixtures/api/schemas/public_api/v4/packages/nuget/download_versions.json b/spec/fixtures/api/schemas/public_api/v4/packages/nuget/download_versions.json
new file mode 100644
index 00000000000..ab40dfbbc4c
--- /dev/null
+++ b/spec/fixtures/api/schemas/public_api/v4/packages/nuget/download_versions.json
@@ -0,0 +1,10 @@
+{
+ "type": "object",
+ "required": ["versions"],
+ "properties": {
+ "versions": {
+ "type": "array",
+ "items": { "type": "string" }
+ }
+ }
+}
diff --git a/spec/fixtures/api/schemas/public_api/v4/packages/nuget/package_metadata.json b/spec/fixtures/api/schemas/public_api/v4/packages/nuget/package_metadata.json
new file mode 100644
index 00000000000..1244cbe474e
--- /dev/null
+++ b/spec/fixtures/api/schemas/public_api/v4/packages/nuget/package_metadata.json
@@ -0,0 +1,28 @@
+{
+ "type": "object",
+ "required": ["@id", "packageContent", "catalogEntry"],
+ "properties": {
+ "@id": { "type": "string" },
+ "packageContent": { "type": "string" },
+ "catalogEntry": {
+ "type": "object",
+ "required": ["@id", "authors", "dependencyGroups", "id", "packageContent", "summary", "version"],
+ "properties": {
+ "@id": { "type": "string" },
+ "authors": { "const": "" },
+ "id": { "type": "string" },
+ "packageContent": { "type": "string" },
+ "summary": { "const": "" },
+ "tags": { "type": "string" },
+ "projectUrl": { "type": "string" },
+ "licenseUrl": { "type": "string" },
+ "iconUrl": { "type": "string" },
+ "version": { "type": "string" },
+ "dependencyGroups": {
+ "type": "array",
+ "items": { "$ref": "./dependency_group.json" }
+ }
+ }
+ }
+ }
+}
diff --git a/spec/fixtures/api/schemas/public_api/v4/packages/nuget/packages_metadata.json b/spec/fixtures/api/schemas/public_api/v4/packages/nuget/packages_metadata.json
new file mode 100644
index 00000000000..724df5a437d
--- /dev/null
+++ b/spec/fixtures/api/schemas/public_api/v4/packages/nuget/packages_metadata.json
@@ -0,0 +1,54 @@
+{
+ "type": "object",
+ "required": ["count", "items"],
+ "properties": {
+ "count": { "const": 0 },
+ "items": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "required": ["lower", "upper", "count", "items"],
+ "properties": {
+ "lower": { "type": "string" },
+ "upper": { "type": "string" },
+ "count": { "type": "integer" },
+ "items": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "required": ["@id", "packageContent", "catalogEntry"],
+ "properties": {
+ "@id": { "type": "string" },
+ "packageContent": { "type": "string" },
+ "catalogEntry": {
+ "type": "object",
+ "required": ["@id", "authors", "dependencyGroups", "id", "packageContent", "summary", "version"],
+ "properties": {
+ "@id": { "type": "string" },
+ "authors": { "const": "" },
+ "id": { "type": "string" },
+ "packageContent": { "type": "string" },
+ "summary": { "const": "" },
+ "tags": { "type": "string" },
+ "projectUrl": { "type": "string" },
+ "licenseUrl": { "type": "string" },
+ "iconUrl": { "type": "string" },
+ "version": { "type": "string" },
+ "dependencyGroups": {
+ "type": "array",
+ "items": { "$ref": "./dependency_group.json" }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+
+
+
diff --git a/spec/fixtures/api/schemas/public_api/v4/packages/nuget/search.json b/spec/fixtures/api/schemas/public_api/v4/packages/nuget/search.json
new file mode 100644
index 00000000000..73d0927e24c
--- /dev/null
+++ b/spec/fixtures/api/schemas/public_api/v4/packages/nuget/search.json
@@ -0,0 +1,39 @@
+{
+ "type": "object",
+ "required": ["totalHits", "data"],
+ "properties": {
+ "totalHits": { "type": "integer" },
+ "data": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "required": ["@type", "authors", "id", "summary", "title", "totalDownloads", "verified", "versions"],
+ "properties": {
+ "@type": { "const": "Package" },
+ "authors": { "const": "" },
+ "id": { "type": "string" },
+ "summary": { "const": "" },
+ "title": { "type": "string" },
+ "totalDownloads": { "const": 0 },
+ "verified": { "const": true },
+ "tags": { "type": "string" },
+ "projectUrl": { "type": "string" },
+ "licenseUrl": { "type": "string" },
+ "iconUrl": { "type": "string" },
+ "versions": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "required": ["@id", "version", "downloads"],
+ "properties": {
+ "@id": { "type": "string" },
+ "version": { "type": "string" },
+ "downloads": { "const": 0 }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/spec/fixtures/api/schemas/public_api/v4/packages/nuget/service_index.json b/spec/fixtures/api/schemas/public_api/v4/packages/nuget/service_index.json
new file mode 100644
index 00000000000..405018f8c37
--- /dev/null
+++ b/spec/fixtures/api/schemas/public_api/v4/packages/nuget/service_index.json
@@ -0,0 +1,19 @@
+{
+ "type": "object",
+ "required": ["version", "resources"],
+ "properties": {
+ "version": { "const": "3.0.0" },
+ "resources": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "required": ["@id", "@type", "comment"],
+ "properties": {
+ "@id": { "type": "string" },
+ "@type": { "type": "string" },
+ "comment": { "type": "string" }
+ }
+ }
+ }
+ }
+}
diff --git a/spec/fixtures/api/schemas/public_api/v4/packages/package.json b/spec/fixtures/api/schemas/public_api/v4/packages/package.json
new file mode 100644
index 00000000000..757e5fd26b6
--- /dev/null
+++ b/spec/fixtures/api/schemas/public_api/v4/packages/package.json
@@ -0,0 +1,41 @@
+{
+ "type": "object",
+ "required": [
+ "name",
+ "version",
+ "package_type",
+ "_links",
+ "versions"
+ ],
+ "properties": {
+ "name": {
+ "type": "string"
+ },
+ "version": {
+ "type": "string"
+ },
+ "package_type": {
+ "type": "string"
+ },
+ "_links": {
+ "type": "object",
+ "required": [
+ "web_path"
+ ],
+ "properties": {
+ "details": {
+ "type": "string"
+ }
+ }
+ },
+ "created_at": {
+ "type": "string"
+ },
+ "versions": {
+ "type": "array",
+ "items": {
+ "$ref": "package_version.json"
+ }
+ }
+ }
+}
diff --git a/spec/fixtures/api/schemas/public_api/v4/packages/package_files.json b/spec/fixtures/api/schemas/public_api/v4/packages/package_files.json
new file mode 100644
index 00000000000..f057adba65c
--- /dev/null
+++ b/spec/fixtures/api/schemas/public_api/v4/packages/package_files.json
@@ -0,0 +1,13 @@
+{
+ "type": "array",
+ "items": {
+ "type": "object",
+ "required" : ["id", "package_id", "file_name"],
+ "properties" : {
+ "id": { "type": "integer" },
+ "package_id": { "type": "integer" },
+ "file_name": { "type": "string" },
+ "file_sha1": { "type": "string" }
+ }
+ }
+}
diff --git a/spec/fixtures/api/schemas/public_api/v4/packages/package_version.json b/spec/fixtures/api/schemas/public_api/v4/packages/package_version.json
new file mode 100644
index 00000000000..72f2a2121ad
--- /dev/null
+++ b/spec/fixtures/api/schemas/public_api/v4/packages/package_version.json
@@ -0,0 +1,19 @@
+{
+ "type": "object",
+ "required": [
+ "version",
+ "created_at",
+ "pipeline"
+ ],
+ "properties": {
+ "version": {
+ "type": "string"
+ },
+ "created_at": {
+ "type": "string"
+ },
+ "pipeline": {
+ "$ref": "../pipeline.json"
+ }
+ }
+}
diff --git a/spec/fixtures/api/schemas/public_api/v4/packages/package_with_build.json b/spec/fixtures/api/schemas/public_api/v4/packages/package_with_build.json
new file mode 100644
index 00000000000..de3ef94138e
--- /dev/null
+++ b/spec/fixtures/api/schemas/public_api/v4/packages/package_with_build.json
@@ -0,0 +1,10 @@
+{
+ "type": "object",
+ "required": ["name", "version", "package_type", "pipeline"],
+ "properties": {
+ "name": { "type": "string" },
+ "version": { "type": "string" },
+ "package_type": { "type": "string" },
+ "pipeline": { "$ref": "../pipeline.json" }
+ }
+}
diff --git a/spec/fixtures/api/schemas/public_api/v4/packages/packages.json b/spec/fixtures/api/schemas/public_api/v4/packages/packages.json
new file mode 100644
index 00000000000..66364da4fdb
--- /dev/null
+++ b/spec/fixtures/api/schemas/public_api/v4/packages/packages.json
@@ -0,0 +1,4 @@
+{
+ "type": "array",
+ "items": { "$ref": "./package.json" }
+}
diff --git a/spec/fixtures/api/schemas/public_api/v4/pipeline.json b/spec/fixtures/api/schemas/public_api/v4/pipeline.json
new file mode 100644
index 00000000000..f83844a115d
--- /dev/null
+++ b/spec/fixtures/api/schemas/public_api/v4/pipeline.json
@@ -0,0 +1,27 @@
+{
+ "type": "object",
+ "required": ["id", "sha", "ref", "status", "created_at", "updated_at", "web_url"],
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "sha": {
+ "type": "string"
+ },
+ "ref": {
+ "type": "string"
+ },
+ "status": {
+ "type": "string"
+ },
+ "created_at": {
+ "type": "string"
+ },
+ "updated_at": {
+ "type": "string"
+ },
+ "web_url": {
+ "type": "string"
+ }
+ }
+}
diff --git a/spec/fixtures/api/schemas/public_api/v4/snippets.json b/spec/fixtures/api/schemas/public_api/v4/snippets.json
index 7baa24a6f1f..de658e01657 100644
--- a/spec/fixtures/api/schemas/public_api/v4/snippets.json
+++ b/spec/fixtures/api/schemas/public_api/v4/snippets.json
@@ -7,6 +7,16 @@
"project_id": { "type": ["integer", "null"] },
"title": { "type": "string" },
"file_name": { "type": ["string", "null"] },
+ "files" : {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "path": { "type": "string" },
+ "raw_url": { "type": "string" }
+ }
+ }
+ },
"description": { "type": ["string", "null"] },
"visibility": { "type": "string" },
"web_url": { "type": "string" },
diff --git a/spec/fixtures/clusters/ca_certificate.pem b/spec/fixtures/clusters/ca_certificate.pem
new file mode 100644
index 00000000000..9e6810ab70c
--- /dev/null
+++ b/spec/fixtures/clusters/ca_certificate.pem
@@ -0,0 +1,23 @@
+-----BEGIN CERTIFICATE-----
+MIIDxTCCAq2gAwIBAgIQAqxcJmoLQJuPC3nyrkYldzANBgkqhkiG9w0BAQUFADBs
+MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
+d3cuZGlnaWNlcnQuY29tMSswKQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5j
+ZSBFViBSb290IENBMB4XDTA2MTExMDAwMDAwMFoXDTMxMTExMDAwMDAwMFowbDEL
+MAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3
+LmRpZ2ljZXJ0LmNvbTErMCkGA1UEAxMiRGlnaUNlcnQgSGlnaCBBc3N1cmFuY2Ug
+RVYgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMbM5XPm
++9S75S0tMqbf5YE/yc0lSbZxKsPVlDRnogocsF9ppkCxxLeyj9CYpKlBWTrT3JTW
+PNt0OKRKzE0lgvdKpVMSOO7zSW1xkX5jtqumX8OkhPhPYlG++MXs2ziS4wblCJEM
+xChBVfvLWokVfnHoNb9Ncgk9vjo4UFt3MRuNs8ckRZqnrG0AFFoEt7oT61EKmEFB
+Ik5lYYeBQVCmeVyJ3hlKV9Uu5l0cUyx+mM0aBhakaHPQNAQTXKFx01p8VdteZOE3
+hzBWBOURtCmAEvF5OYiiAhF8J2a3iLd48soKqDirCmTCv2ZdlYTBoSUeh10aUAsg
+EsxBu24LUTi4S8sCAwEAAaNjMGEwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQF
+MAMBAf8wHQYDVR0OBBYEFLE+w2kD+L9HAdSYJhoIAu9jZCvDMB8GA1UdIwQYMBaA
+FLE+w2kD+L9HAdSYJhoIAu9jZCvDMA0GCSqGSIb3DQEBBQUAA4IBAQAcGgaX3Nec
+nzyIZgYIVyHbIUf4KmeqvxgydkAQV8GK83rZEWWONfqe/EW1ntlMMUu4kehDLI6z
+eM7b41N5cdblIZQB2lWHmiRk9opmzN6cN82oNLFpmyPInngiK3BD41VHMWEZ71jF
+hS9OMPagMRYjyOfiZRYzy78aG6A9+MpeizGLYAiJLQwGXFK3xPkKmNEVX58Svnw2
+Yzi9RKR/5CYrCsSXaQ3pjOLAEFe4yHYSkVXySGnYvCoCWw9E1CAx2/S6cCZdkGCe
+vEsXCS+0yx5DaMkHJ8HSXPfqIbloEpw8nL+e/IBcm2PN7EeqJSdnoDfzAIJ9VNep
++OkuE6N36B9K
+-----END CERTIFICATE-----
diff --git a/spec/fixtures/clusters/chain_certificates.pem b/spec/fixtures/clusters/chain_certificates.pem
new file mode 100644
index 00000000000..b8e64d58ee7
--- /dev/null
+++ b/spec/fixtures/clusters/chain_certificates.pem
@@ -0,0 +1,100 @@
+-----BEGIN CERTIFICATE-----
+MIIItjCCB56gAwIBAgIQCu5Ga1hR41iahM0SWhyeNjANBgkqhkiG9w0BAQsFADB1
+MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
+d3cuZGlnaWNlcnQuY29tMTQwMgYDVQQDEytEaWdpQ2VydCBTSEEyIEV4dGVuZGVk
+IFZhbGlkYXRpb24gU2VydmVyIENBMB4XDTE5MTIwNDAwMDAwMFoXDTIxMTIwODEy
+MDAwMFowgb0xHTAbBgNVBA8MFFByaXZhdGUgT3JnYW5pemF0aW9uMRMwEQYLKwYB
+BAGCNzwCAQMTAlVTMRUwEwYLKwYBBAGCNzwCAQITBFV0YWgxFTATBgNVBAUTDDUy
+OTk1MzctMDE0MjELMAkGA1UEBhMCVVMxDTALBgNVBAgTBFV0YWgxDTALBgNVBAcT
+BExlaGkxFzAVBgNVBAoTDkRpZ2lDZXJ0LCBJbmMuMRUwEwYDVQQDEwxkaWdpY2Vy
+dC5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDAeRYb/RLbljGZ
+IB//DrEdyKYMQqqaJwBlrr3t2paAWNuDJizvVkTMIzdJesI1pA58Myenxp5Dp8GJ
+u/VhBf//v/HAZHUE4xwu104Fg6A1BwUEKgVKERf+7kTt17Lf9fcMIjMyL+FeyPXb
+DOFbH+ej/nYaneFLch2j2xWZg1+Thk0qBlGE8WWAK+fvbEuM0SOeH9RkYFCNGPRS
+KsLn0GvaCnnD4LfNDyMqYop0IpaqXoREEnkRv1MVSOw+hBj497wnnO+/GZegfzwU
+iS60h+PjlDfmdCP18qOS7tRd0qnfU3N3S+PYEd3R63LMcIfbgXNEEWBNKpiH9+8f
+eXq6bXKPAgMBAAGjggT3MIIE8zAfBgNVHSMEGDAWgBQ901Cl1qCt7vNKYApl0yHU
++PjWDzAdBgNVHQ4EFgQUTx0XO7HqD5DOhwlm2p+70uYPBmgwggGjBgNVHREEggGa
+MIIBloIMZGlnaWNlcnQuY29tggl0aGF3dGUuZGWCC2ZyZWVzc2wuY29tggxyYXBp
+ZHNzbC5jb22CDGdlb3RydXN0LmNvbYIJdGhhd3RlLmZyggp0aGF3dGUuY29tghB3
+d3cucmFwaWRzc2wuY29tghB3d3cuZ2VvdHJ1c3QuY29tgg13d3cudGhhd3RlLmZy
+gg13d3cudGhhd3RlLmRlgg53d3cudGhhd3RlLmNvbYIQd3d3LmRpZ2ljZXJ0LmNv
+bYIYa2ItaW50ZXJuYWwuZGlnaWNlcnQuY29tghprbm93bGVkZ2ViYXNlLmRpZ2lj
+ZXJ0LmNvbYIWa25vd2xlZGdlLmRpZ2ljZXJ0LmNvbYIPa2guZGlnaWNlcnQuY29t
+ghlrbm93bGVkZ2VodWIuZGlnaWNlcnQuY29tghh3ZWJzZWN1cml0eS5kaWdpY2Vy
+dC5jb22CFGNvbnRlbnQuZGlnaWNlcnQuY29tgg93d3cuZnJlZXNzbC5jb22CHHd3
+dy53ZWJzZWN1cml0eS5kaWdpY2VydC5jb20wDgYDVR0PAQH/BAQDAgWgMB0GA1Ud
+JQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjB1BgNVHR8EbjBsMDSgMqAwhi5odHRw
+Oi8vY3JsMy5kaWdpY2VydC5jb20vc2hhMi1ldi1zZXJ2ZXItZzIuY3JsMDSgMqAw
+hi5odHRwOi8vY3JsNC5kaWdpY2VydC5jb20vc2hhMi1ldi1zZXJ2ZXItZzIuY3Js
+MEsGA1UdIAREMEIwNwYJYIZIAYb9bAIBMCowKAYIKwYBBQUHAgEWHGh0dHBzOi8v
+d3d3LmRpZ2ljZXJ0LmNvbS9DUFMwBwYFZ4EMAQEwgYgGCCsGAQUFBwEBBHwwejAk
+BggrBgEFBQcwAYYYaHR0cDovL29jc3AuZGlnaWNlcnQuY29tMFIGCCsGAQUFBzAC
+hkZodHRwOi8vY2FjZXJ0cy5kaWdpY2VydC5jb20vRGlnaUNlcnRTSEEyRXh0ZW5k
+ZWRWYWxpZGF0aW9uU2VydmVyQ0EuY3J0MAwGA1UdEwEB/wQCMAAwggF8BgorBgEE
+AdZ5AgQCBIIBbASCAWgBZgB1AKS5CZC0GFgUh7sTosxncAo8NZgE+RvfuON3zQ7I
+DdwQAAABbtLkOs4AAAQDAEYwRAIgQ7gh393PInhYfPOhg/lF9yZNRdvjBeufFoG8
+VnBuPNMCIBP8YGC83ig5ttw3ipSRjH0bKj4Ak5O4rynoql9Dy8x3AHYAVhQGmi/X
+wuzT9eG9RLI+x0Z2ubyZEVzA75SYVdaJ0N0AAAFu0uQ7VgAABAMARzBFAiEAhzE7
+1c48wn3s/30IB4WgxfpLburH0Ku8cchv8QeqcgACIBrWpUlDD18AOfkPCOcB2kWU
+vRXsdptVm3jPeU5TtDSoAHUAu9nfvB+KcbWTlCOXqpJ7RzhXlQqrUugakJZkNo4e
+0YUAAAFu0uQ60gAABAMARjBEAiBBpH5m7ntGKFTOFgSLcFXRDg66xJqerMy0gOHj
+4TIBYAIgfFABPNy6P61hjiOWwjq73lvoEdAyh18GeFHIp0BgsWEwDQYJKoZIhvcN
+AQELBQADggEBAInaSEqteyQA1zUKiXVqgffhHKZsUq9UnMows6X+UoFPoby9xqm6
+IaY/77zaFZYwXJlP/SvrlbgTLHAdir3y38uhAlfPX4iRuwggOpFFF5hqDckzCm91
+ocGnoG6sUY5mOqKu2vIcZkUQDe+K5gOxI6ME/4YwzWCIcTmBPQ6NQmqiFLPoQty1
+gdbGCcLQNFCuNq4n5OK2NmBjcbtyT4gglat7C4+KV8RkEubZ+MkXzyDkpEXjjzsK
+7iuNB0hRgyyhGzHrlZ/l0OLoT0Cb4I5PzzRSseFEyPKCC1WSF7aE9rFfUqhpqSAT
+7NV7SEijYyFFtuZfz9RGglcqnRlAfgTy+tU=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIEtjCCA56gAwIBAgIQDHmpRLCMEZUgkmFf4msdgzANBgkqhkiG9w0BAQsFADBs
+MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
+d3cuZGlnaWNlcnQuY29tMSswKQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5j
+ZSBFViBSb290IENBMB4XDTEzMTAyMjEyMDAwMFoXDTI4MTAyMjEyMDAwMFowdTEL
+MAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3
+LmRpZ2ljZXJ0LmNvbTE0MDIGA1UEAxMrRGlnaUNlcnQgU0hBMiBFeHRlbmRlZCBW
+YWxpZGF0aW9uIFNlcnZlciBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC
+ggEBANdTpARR+JmmFkhLZyeqk0nQOe0MsLAAh/FnKIaFjI5j2ryxQDji0/XspQUY
+uD0+xZkXMuwYjPrxDKZkIYXLBxA0sFKIKx9om9KxjxKws9LniB8f7zh3VFNfgHk/
+LhqqqB5LKw2rt2O5Nbd9FLxZS99RStKh4gzikIKHaq7q12TWmFXo/a8aUGxUvBHy
+/Urynbt/DvTVvo4WiRJV2MBxNO723C3sxIclho3YIeSwTQyJ3DkmF93215SF2AQh
+cJ1vb/9cuhnhRctWVyh+HA1BV6q3uCe7seT6Ku8hI3UarS2bhjWMnHe1c63YlC3k
+8wyd7sFOYn4XwHGeLN7x+RAoGTMCAwEAAaOCAUkwggFFMBIGA1UdEwEB/wQIMAYB
+Af8CAQAwDgYDVR0PAQH/BAQDAgGGMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEF
+BQcDAjA0BggrBgEFBQcBAQQoMCYwJAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRp
+Z2ljZXJ0LmNvbTBLBgNVHR8ERDBCMECgPqA8hjpodHRwOi8vY3JsNC5kaWdpY2Vy
+dC5jb20vRGlnaUNlcnRIaWdoQXNzdXJhbmNlRVZSb290Q0EuY3JsMD0GA1UdIAQ2
+MDQwMgYEVR0gADAqMCgGCCsGAQUFBwIBFhxodHRwczovL3d3dy5kaWdpY2VydC5j
+b20vQ1BTMB0GA1UdDgQWBBQ901Cl1qCt7vNKYApl0yHU+PjWDzAfBgNVHSMEGDAW
+gBSxPsNpA/i/RwHUmCYaCALvY2QrwzANBgkqhkiG9w0BAQsFAAOCAQEAnbbQkIbh
+hgLtxaDwNBx0wY12zIYKqPBKikLWP8ipTa18CK3mtlC4ohpNiAexKSHc59rGPCHg
+4xFJcKx6HQGkyhE6V6t9VypAdP3THYUYUN9XR3WhfVUgLkc3UHKMf4Ib0mKPLQNa
+2sPIoc4sUqIAY+tzunHISScjl2SFnjgOrWNoPLpSgVh5oywM395t6zHyuqB8bPEs
+1OG9d4Q3A84ytciagRpKkk47RpqF/oOi+Z6Mo8wNXrM9zwR4jxQUezKcxwCmXMS1
+oVWNWlZopCJwqjyBcdmdqEU79OX2olHdx3ti6G8MdOu42vi/hw15UJGQmxg7kVkn
+8TUoE6smftX3eg==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDxTCCAq2gAwIBAgIQAqxcJmoLQJuPC3nyrkYldzANBgkqhkiG9w0BAQUFADBs
+MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
+d3cuZGlnaWNlcnQuY29tMSswKQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5j
+ZSBFViBSb290IENBMB4XDTA2MTExMDAwMDAwMFoXDTMxMTExMDAwMDAwMFowbDEL
+MAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3
+LmRpZ2ljZXJ0LmNvbTErMCkGA1UEAxMiRGlnaUNlcnQgSGlnaCBBc3N1cmFuY2Ug
+RVYgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMbM5XPm
++9S75S0tMqbf5YE/yc0lSbZxKsPVlDRnogocsF9ppkCxxLeyj9CYpKlBWTrT3JTW
+PNt0OKRKzE0lgvdKpVMSOO7zSW1xkX5jtqumX8OkhPhPYlG++MXs2ziS4wblCJEM
+xChBVfvLWokVfnHoNb9Ncgk9vjo4UFt3MRuNs8ckRZqnrG0AFFoEt7oT61EKmEFB
+Ik5lYYeBQVCmeVyJ3hlKV9Uu5l0cUyx+mM0aBhakaHPQNAQTXKFx01p8VdteZOE3
+hzBWBOURtCmAEvF5OYiiAhF8J2a3iLd48soKqDirCmTCv2ZdlYTBoSUeh10aUAsg
+EsxBu24LUTi4S8sCAwEAAaNjMGEwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQF
+MAMBAf8wHQYDVR0OBBYEFLE+w2kD+L9HAdSYJhoIAu9jZCvDMB8GA1UdIwQYMBaA
+FLE+w2kD+L9HAdSYJhoIAu9jZCvDMA0GCSqGSIb3DQEBBQUAA4IBAQAcGgaX3Nec
+nzyIZgYIVyHbIUf4KmeqvxgydkAQV8GK83rZEWWONfqe/EW1ntlMMUu4kehDLI6z
+eM7b41N5cdblIZQB2lWHmiRk9opmzN6cN82oNLFpmyPInngiK3BD41VHMWEZ71jF
+hS9OMPagMRYjyOfiZRYzy78aG6A9+MpeizGLYAiJLQwGXFK3xPkKmNEVX58Svnw2
+Yzi9RKR/5CYrCsSXaQ3pjOLAEFe4yHYSkVXySGnYvCoCWw9E1CAx2/S6cCZdkGCe
+vEsXCS+0yx5DaMkHJ8HSXPfqIbloEpw8nL+e/IBcm2PN7EeqJSdnoDfzAIJ9VNep
++OkuE6N36B9K
+-----END CERTIFICATE-----
diff --git a/spec/fixtures/clusters/intermediate_certificate.pem b/spec/fixtures/clusters/intermediate_certificate.pem
new file mode 100644
index 00000000000..8a81175b746
--- /dev/null
+++ b/spec/fixtures/clusters/intermediate_certificate.pem
@@ -0,0 +1,28 @@
+-----BEGIN CERTIFICATE-----
+MIIEtjCCA56gAwIBAgIQDHmpRLCMEZUgkmFf4msdgzANBgkqhkiG9w0BAQsFADBs
+MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
+d3cuZGlnaWNlcnQuY29tMSswKQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5j
+ZSBFViBSb290IENBMB4XDTEzMTAyMjEyMDAwMFoXDTI4MTAyMjEyMDAwMFowdTEL
+MAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3
+LmRpZ2ljZXJ0LmNvbTE0MDIGA1UEAxMrRGlnaUNlcnQgU0hBMiBFeHRlbmRlZCBW
+YWxpZGF0aW9uIFNlcnZlciBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC
+ggEBANdTpARR+JmmFkhLZyeqk0nQOe0MsLAAh/FnKIaFjI5j2ryxQDji0/XspQUY
+uD0+xZkXMuwYjPrxDKZkIYXLBxA0sFKIKx9om9KxjxKws9LniB8f7zh3VFNfgHk/
+LhqqqB5LKw2rt2O5Nbd9FLxZS99RStKh4gzikIKHaq7q12TWmFXo/a8aUGxUvBHy
+/Urynbt/DvTVvo4WiRJV2MBxNO723C3sxIclho3YIeSwTQyJ3DkmF93215SF2AQh
+cJ1vb/9cuhnhRctWVyh+HA1BV6q3uCe7seT6Ku8hI3UarS2bhjWMnHe1c63YlC3k
+8wyd7sFOYn4XwHGeLN7x+RAoGTMCAwEAAaOCAUkwggFFMBIGA1UdEwEB/wQIMAYB
+Af8CAQAwDgYDVR0PAQH/BAQDAgGGMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEF
+BQcDAjA0BggrBgEFBQcBAQQoMCYwJAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRp
+Z2ljZXJ0LmNvbTBLBgNVHR8ERDBCMECgPqA8hjpodHRwOi8vY3JsNC5kaWdpY2Vy
+dC5jb20vRGlnaUNlcnRIaWdoQXNzdXJhbmNlRVZSb290Q0EuY3JsMD0GA1UdIAQ2
+MDQwMgYEVR0gADAqMCgGCCsGAQUFBwIBFhxodHRwczovL3d3dy5kaWdpY2VydC5j
+b20vQ1BTMB0GA1UdDgQWBBQ901Cl1qCt7vNKYApl0yHU+PjWDzAfBgNVHSMEGDAW
+gBSxPsNpA/i/RwHUmCYaCALvY2QrwzANBgkqhkiG9w0BAQsFAAOCAQEAnbbQkIbh
+hgLtxaDwNBx0wY12zIYKqPBKikLWP8ipTa18CK3mtlC4ohpNiAexKSHc59rGPCHg
+4xFJcKx6HQGkyhE6V6t9VypAdP3THYUYUN9XR3WhfVUgLkc3UHKMf4Ib0mKPLQNa
+2sPIoc4sUqIAY+tzunHISScjl2SFnjgOrWNoPLpSgVh5oywM395t6zHyuqB8bPEs
+1OG9d4Q3A84ytciagRpKkk47RpqF/oOi+Z6Mo8wNXrM9zwR4jxQUezKcxwCmXMS1
+oVWNWlZopCJwqjyBcdmdqEU79OX2olHdx3ti6G8MdOu42vi/hw15UJGQmxg7kVkn
+8TUoE6smftX3eg==
+-----END CERTIFICATE-----
diff --git a/spec/fixtures/clusters/root_certificate.pem b/spec/fixtures/clusters/root_certificate.pem
new file mode 100644
index 00000000000..40107bd837d
--- /dev/null
+++ b/spec/fixtures/clusters/root_certificate.pem
@@ -0,0 +1,49 @@
+-----BEGIN CERTIFICATE-----
+MIIItjCCB56gAwIBAgIQCu5Ga1hR41iahM0SWhyeNjANBgkqhkiG9w0BAQsFADB1
+MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
+d3cuZGlnaWNlcnQuY29tMTQwMgYDVQQDEytEaWdpQ2VydCBTSEEyIEV4dGVuZGVk
+IFZhbGlkYXRpb24gU2VydmVyIENBMB4XDTE5MTIwNDAwMDAwMFoXDTIxMTIwODEy
+MDAwMFowgb0xHTAbBgNVBA8MFFByaXZhdGUgT3JnYW5pemF0aW9uMRMwEQYLKwYB
+BAGCNzwCAQMTAlVTMRUwEwYLKwYBBAGCNzwCAQITBFV0YWgxFTATBgNVBAUTDDUy
+OTk1MzctMDE0MjELMAkGA1UEBhMCVVMxDTALBgNVBAgTBFV0YWgxDTALBgNVBAcT
+BExlaGkxFzAVBgNVBAoTDkRpZ2lDZXJ0LCBJbmMuMRUwEwYDVQQDEwxkaWdpY2Vy
+dC5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDAeRYb/RLbljGZ
+IB//DrEdyKYMQqqaJwBlrr3t2paAWNuDJizvVkTMIzdJesI1pA58Myenxp5Dp8GJ
+u/VhBf//v/HAZHUE4xwu104Fg6A1BwUEKgVKERf+7kTt17Lf9fcMIjMyL+FeyPXb
+DOFbH+ej/nYaneFLch2j2xWZg1+Thk0qBlGE8WWAK+fvbEuM0SOeH9RkYFCNGPRS
+KsLn0GvaCnnD4LfNDyMqYop0IpaqXoREEnkRv1MVSOw+hBj497wnnO+/GZegfzwU
+iS60h+PjlDfmdCP18qOS7tRd0qnfU3N3S+PYEd3R63LMcIfbgXNEEWBNKpiH9+8f
+eXq6bXKPAgMBAAGjggT3MIIE8zAfBgNVHSMEGDAWgBQ901Cl1qCt7vNKYApl0yHU
++PjWDzAdBgNVHQ4EFgQUTx0XO7HqD5DOhwlm2p+70uYPBmgwggGjBgNVHREEggGa
+MIIBloIMZGlnaWNlcnQuY29tggl0aGF3dGUuZGWCC2ZyZWVzc2wuY29tggxyYXBp
+ZHNzbC5jb22CDGdlb3RydXN0LmNvbYIJdGhhd3RlLmZyggp0aGF3dGUuY29tghB3
+d3cucmFwaWRzc2wuY29tghB3d3cuZ2VvdHJ1c3QuY29tgg13d3cudGhhd3RlLmZy
+gg13d3cudGhhd3RlLmRlgg53d3cudGhhd3RlLmNvbYIQd3d3LmRpZ2ljZXJ0LmNv
+bYIYa2ItaW50ZXJuYWwuZGlnaWNlcnQuY29tghprbm93bGVkZ2ViYXNlLmRpZ2lj
+ZXJ0LmNvbYIWa25vd2xlZGdlLmRpZ2ljZXJ0LmNvbYIPa2guZGlnaWNlcnQuY29t
+ghlrbm93bGVkZ2VodWIuZGlnaWNlcnQuY29tghh3ZWJzZWN1cml0eS5kaWdpY2Vy
+dC5jb22CFGNvbnRlbnQuZGlnaWNlcnQuY29tgg93d3cuZnJlZXNzbC5jb22CHHd3
+dy53ZWJzZWN1cml0eS5kaWdpY2VydC5jb20wDgYDVR0PAQH/BAQDAgWgMB0GA1Ud
+JQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjB1BgNVHR8EbjBsMDSgMqAwhi5odHRw
+Oi8vY3JsMy5kaWdpY2VydC5jb20vc2hhMi1ldi1zZXJ2ZXItZzIuY3JsMDSgMqAw
+hi5odHRwOi8vY3JsNC5kaWdpY2VydC5jb20vc2hhMi1ldi1zZXJ2ZXItZzIuY3Js
+MEsGA1UdIAREMEIwNwYJYIZIAYb9bAIBMCowKAYIKwYBBQUHAgEWHGh0dHBzOi8v
+d3d3LmRpZ2ljZXJ0LmNvbS9DUFMwBwYFZ4EMAQEwgYgGCCsGAQUFBwEBBHwwejAk
+BggrBgEFBQcwAYYYaHR0cDovL29jc3AuZGlnaWNlcnQuY29tMFIGCCsGAQUFBzAC
+hkZodHRwOi8vY2FjZXJ0cy5kaWdpY2VydC5jb20vRGlnaUNlcnRTSEEyRXh0ZW5k
+ZWRWYWxpZGF0aW9uU2VydmVyQ0EuY3J0MAwGA1UdEwEB/wQCMAAwggF8BgorBgEE
+AdZ5AgQCBIIBbASCAWgBZgB1AKS5CZC0GFgUh7sTosxncAo8NZgE+RvfuON3zQ7I
+DdwQAAABbtLkOs4AAAQDAEYwRAIgQ7gh393PInhYfPOhg/lF9yZNRdvjBeufFoG8
+VnBuPNMCIBP8YGC83ig5ttw3ipSRjH0bKj4Ak5O4rynoql9Dy8x3AHYAVhQGmi/X
+wuzT9eG9RLI+x0Z2ubyZEVzA75SYVdaJ0N0AAAFu0uQ7VgAABAMARzBFAiEAhzE7
+1c48wn3s/30IB4WgxfpLburH0Ku8cchv8QeqcgACIBrWpUlDD18AOfkPCOcB2kWU
+vRXsdptVm3jPeU5TtDSoAHUAu9nfvB+KcbWTlCOXqpJ7RzhXlQqrUugakJZkNo4e
+0YUAAAFu0uQ60gAABAMARjBEAiBBpH5m7ntGKFTOFgSLcFXRDg66xJqerMy0gOHj
+4TIBYAIgfFABPNy6P61hjiOWwjq73lvoEdAyh18GeFHIp0BgsWEwDQYJKoZIhvcN
+AQELBQADggEBAInaSEqteyQA1zUKiXVqgffhHKZsUq9UnMows6X+UoFPoby9xqm6
+IaY/77zaFZYwXJlP/SvrlbgTLHAdir3y38uhAlfPX4iRuwggOpFFF5hqDckzCm91
+ocGnoG6sUY5mOqKu2vIcZkUQDe+K5gOxI6ME/4YwzWCIcTmBPQ6NQmqiFLPoQty1
+gdbGCcLQNFCuNq4n5OK2NmBjcbtyT4gglat7C4+KV8RkEubZ+MkXzyDkpEXjjzsK
+7iuNB0hRgyyhGzHrlZ/l0OLoT0Cb4I5PzzRSseFEyPKCC1WSF7aE9rFfUqhpqSAT
+7NV7SEijYyFFtuZfz9RGglcqnRlAfgTy+tU=
+-----END CERTIFICATE-----
diff --git a/spec/fixtures/emails/service_desk.eml b/spec/fixtures/emails/service_desk.eml
new file mode 100644
index 00000000000..0db1270bc64
--- /dev/null
+++ b/spec/fixtures/emails/service_desk.eml
@@ -0,0 +1,28 @@
+Return-Path: <jake@adventuretime.ooo>
+Received: from iceking.adventuretime.ooo ([unix socket]) by iceking (Cyrus v2.2.13-Debian-2.2.13-19+squeeze3) with LMTPA; Thu, 13 Jun 2013 17:03:50 -0400
+Received: from mail-ie0-x234.google.com (mail-ie0-x234.google.com [IPv6:2607:f8b0:4001:c03::234]) by iceking.adventuretime.ooo (8.14.3/8.14.3/Debian-9.4) with ESMTP id r5DL3nFJ016967 (version=TLSv1/SSLv3 cipher=RC4-SHA bits=128 verify=NOT) for <incoming+gitlabhq/gitlabhq@appmail.adventuretime.ooo>; Thu, 13 Jun 2013 17:03:50 -0400
+Received: by mail-ie0-f180.google.com with SMTP id f4so21977375iea.25 for <incoming+email-test-project_id-issue-@appmail.adventuretime.ooo>; Thu, 13 Jun 2013 14:03:48 -0700
+Received: by 10.0.0.1 with HTTP; Thu, 13 Jun 2013 14:03:48 -0700
+Date: Thu, 13 Jun 2013 17:03:48 -0400
+From: Jake the Dog <jake@adventuretime.ooo>
+To: incoming+email-test-project_id-issue-@appmail.adventuretime.ooo
+Message-ID: <CADkmRc+rNGAGGbV2iE5p918UVy4UyJqVcXRO2=otppgzduJSg@mail.gmail.com>
+Subject: The message subject! @all
+Mime-Version: 1.0
+Content-Type: text/plain;
+ charset=ISO-8859-1
+Content-Transfer-Encoding: 7bit
+X-Sieve: CMU Sieve 2.2
+X-Received: by 10.0.0.1 with SMTP id n7mr11234144ipb.85.1371157428600; Thu,
+ 13 Jun 2013 14:03:48 -0700 (PDT)
+X-Scanned-By: MIMEDefang 2.69 on IPv6:2001:470:1d:165::1
+
+Service desk stuff!
+
+```
+a = b
+```
+
+/label ~label1
+/assign @user1
+/close
diff --git a/spec/fixtures/emails/service_desk_custom_address.eml b/spec/fixtures/emails/service_desk_custom_address.eml
new file mode 100644
index 00000000000..3293dd48303
--- /dev/null
+++ b/spec/fixtures/emails/service_desk_custom_address.eml
@@ -0,0 +1,27 @@
+Return-Path: <jake@adventuretime.ooo>
+Received: from iceking.adventuretime.ooo ([unix socket]) by iceking (Cyrus v2.2.13-Debian-2.2.13-19+squeeze3) with LMTPA; Thu, 13 Jun 2013 17:03:50 -0400
+Received: by mail-ie0-f180.google.com with SMTP id f4so21977375iea.25 for <support+project_slug-project_key@example.com>; Thu, 13 Jun 2013 14:03:48 -0700
+Received: by 10.0.0.1 with HTTP; Thu, 13 Jun 2013 14:03:48 -0700
+Date: Thu, 13 Jun 2013 17:03:48 -0400
+From: Jake the Dog <jake@adventuretime.ooo>
+To: support+project_slug-project_key@example.com
+Message-ID: <CADkmRc+rNGAGGbV2iE5p918UVy4UyJqVcXRO2=otppgzduJSg@mail.gmail.com>
+Subject: The message subject! @all
+Mime-Version: 1.0
+Content-Type: text/plain;
+ charset=ISO-8859-1
+Content-Transfer-Encoding: 7bit
+X-Sieve: CMU Sieve 2.2
+X-Received: by 10.0.0.1 with SMTP id n7mr11234144ipb.85.1371157428600; Thu,
+ 13 Jun 2013 14:03:48 -0700 (PDT)
+X-Scanned-By: MIMEDefang 2.69 on IPv6:2001:470:1d:165::1
+
+Service desk stuff!
+
+```
+a = b
+```
+
+/label ~label1
+/assign @user1
+/close
diff --git a/spec/fixtures/emails/service_desk_forwarded.eml b/spec/fixtures/emails/service_desk_forwarded.eml
new file mode 100644
index 00000000000..56987972808
--- /dev/null
+++ b/spec/fixtures/emails/service_desk_forwarded.eml
@@ -0,0 +1,30 @@
+Delivered-To: incoming+email-test-project_id-issue-@appmail.adventuretime.ooo
+Return-Path: <jake@adventuretime.ooo>
+Received: from iceking.adventuretime.ooo ([unix socket]) by iceking (Cyrus v2.2.13-Debian-2.2.13-19+squeeze3) with LMTPA; Thu, 13 Jun 2013 17:03:50 -0400
+Received: from mail-ie0-x234.google.com (mail-ie0-x234.google.com [IPv6:2607:f8b0:4001:c03::234]) by iceking.adventuretime.ooo (8.14.3/8.14.3/Debian-9.4) with ESMTP id r5DL3nFJ016967 (version=TLSv1/SSLv3 cipher=RC4-SHA bits=128 verify=NOT) for <incoming+gitlabhq/gitlabhq@appmail.adventuretime.ooo>; Thu, 13 Jun 2013 17:03:50 -0400
+Received: by mail-ie0-f180.google.com with SMTP id f4so21977375iea.25 for <incoming+email-test-project_id-issue-@appmail.adventuretime.ooo>; Thu, 13 Jun 2013 14:03:48 -0700
+Received: by 10.0.0.1 with HTTP; Thu, 13 Jun 2013 14:03:48 -0700
+Date: Thu, 13 Jun 2013 17:03:48 -0400
+From: Jake the Dog <jake@adventuretime.ooo>
+To: support@adventuretime.ooo
+Delivered-To: support@adventuretime.ooo
+Message-ID: <CADkmRc+rNGAGGbV2iE5p918UVy4UyJqVcXRO2=otppgzduJSg@mail.gmail.com>
+Subject: The message subject! @all
+Mime-Version: 1.0
+Content-Type: text/plain;
+ charset=ISO-8859-1
+Content-Transfer-Encoding: 7bit
+X-Sieve: CMU Sieve 2.2
+X-Received: by 10.0.0.1 with SMTP id n7mr11234144ipb.85.1371157428600; Thu,
+ 13 Jun 2013 14:03:48 -0700 (PDT)
+X-Scanned-By: MIMEDefang 2.69 on IPv6:2001:470:1d:165::1
+
+Service desk stuff!
+
+```
+a = b
+```
+
+/label ~label1
+/assign @user1
+/close
diff --git a/spec/fixtures/emails/service_desk_forwarded_new_issue.eml b/spec/fixtures/emails/service_desk_forwarded_new_issue.eml
new file mode 100644
index 00000000000..4eedb24b32d
--- /dev/null
+++ b/spec/fixtures/emails/service_desk_forwarded_new_issue.eml
@@ -0,0 +1,29 @@
+Delivered-To: incoming+email-test-project_id-issue-@appmail.adventuretime.ooo
+Return-Path: <jake@adventuretime.ooo>
+Received: from iceking.adventuretime.ooo ([unix socket]) by iceking (Cyrus v2.2.13-Debian-2.2.13-19+squeeze3) with LMTPA; Thu, 13 Jun 2013 17:03:50 -0400
+Received: from mail-ie0-x234.google.com (mail-ie0-x234.google.com [IPv6:2607:f8b0:4001:c03::234]) by iceking.adventuretime.ooo (8.14.3/8.14.3/Debian-9.4) with ESMTP id r5DL3nFJ016967 (version=TLSv1/SSLv3 cipher=RC4-SHA bits=128 verify=NOT) for <incoming+gitlabhq/gitlabhq@appmail.adventuretime.ooo>; Thu, 13 Jun 2013 17:03:50 -0400
+Received: by mail-ie0-f180.google.com with SMTP id f4so21977375iea.25 for <incoming+email-test-project_id-issue-@appmail.adventuretime.ooo>; Thu, 13 Jun 2013 14:03:48 -0700
+Received: by 10.0.0.1 with HTTP; Thu, 13 Jun 2013 14:03:48 -0700
+Date: Thu, 13 Jun 2013 17:03:48 -0400
+From: Jake the Dog <jake@adventuretime.ooo>
+To: support@adventuretime.ooo
+Delivered-To: support@adventuretime.ooo
+Message-ID: <CADkmRc+rNGAGGbV2iE5p918UVy4UyJqVcXRO2=otppgzduJSg@mail.gmail.com>
+Subject: The message subject! @all
+Mime-Version: 1.0
+Content-Type: text/plain;
+ charset=ISO-8859-1
+Content-Transfer-Encoding: 7bit
+X-Sieve: CMU Sieve 2.2
+X-Received: by 10.0.0.1 with SMTP id n7mr11234144ipb.85.1371157428600; Thu,
+ 13 Jun 2013 14:03:48 -0700 (PDT)
+X-Scanned-By: MIMEDefang 2.69 on IPv6:2001:470:1d:165::1
+
+Service desk stuff!
+
+---------- Forwarded message ---------
+From: Jake the Dog <jake@adventuretime.ooo>
+To: <jake@adventuretime.ooo>
+
+
+forwarded content
diff --git a/spec/fixtures/emails/service_desk_legacy.eml b/spec/fixtures/emails/service_desk_legacy.eml
new file mode 100644
index 00000000000..fc9178c937b
--- /dev/null
+++ b/spec/fixtures/emails/service_desk_legacy.eml
@@ -0,0 +1,28 @@
+Return-Path: <jake@adventuretime.ooo>
+Received: from iceking.adventuretime.ooo ([unix socket]) by iceking (Cyrus v2.2.13-Debian-2.2.13-19+squeeze3) with LMTPA; Thu, 13 Jun 2013 17:03:50 -0400
+Received: from mail-ie0-x234.google.com (mail-ie0-x234.google.com [IPv6:2607:f8b0:4001:c03::234]) by iceking.adventuretime.ooo (8.14.3/8.14.3/Debian-9.4) with ESMTP id r5DL3nFJ016967 (version=TLSv1/SSLv3 cipher=RC4-SHA bits=128 verify=NOT) for <incoming+gitlabhq/gitlabhq@appmail.adventuretime.ooo>; Thu, 13 Jun 2013 17:03:50 -0400
+Received: by mail-ie0-f180.google.com with SMTP id f4so21977375iea.25 for <incoming+gitlabhq/gitlabhq@appmail.adventuretime.ooo>; Thu, 13 Jun 2013 14:03:48 -0700
+Received: by 10.0.0.1 with HTTP; Thu, 13 Jun 2013 14:03:48 -0700
+Date: Thu, 13 Jun 2013 17:03:48 -0400
+From: Jake the Dog <jake@adventuretime.ooo>
+To: incoming+email/test@appmail.adventuretime.ooo
+Message-ID: <CADkmRc+rNGAGGbV2iE5p918UVy4UyJqVcXRO2=otppgzduJSg@mail.gmail.com>
+Subject: The message subject! @all
+Mime-Version: 1.0
+Content-Type: text/plain;
+ charset=ISO-8859-1
+Content-Transfer-Encoding: 7bit
+X-Sieve: CMU Sieve 2.2
+X-Received: by 10.0.0.1 with SMTP id n7mr11234144ipb.85.1371157428600; Thu,
+ 13 Jun 2013 14:03:48 -0700 (PDT)
+X-Scanned-By: MIMEDefang 2.69 on IPv6:2001:470:1d:165::1
+
+Service desk stuff!
+
+```
+a = b
+```
+
+/label ~label1
+/assign @user1
+/close
diff --git a/spec/fixtures/emails/service_desk_sender_and_from.eml b/spec/fixtures/emails/service_desk_sender_and_from.eml
new file mode 100644
index 00000000000..987c24d70bf
--- /dev/null
+++ b/spec/fixtures/emails/service_desk_sender_and_from.eml
@@ -0,0 +1,27 @@
+Delivered-To: incoming+email-test-project_id-issue-@appmail.adventuretime.ooo
+Return-Path: <jake@adventuretime.ooo>
+Received: from iceking.adventuretime.ooo ([unix socket]) by iceking (Cyrus v2.2.13-Debian-2.2.13-19+squeeze3) with LMTPA; Thu, 13 Jun 2013 17:03:50 -0400
+Received: from mail-ie0-x234.google.com (mail-ie0-x234.google.com [IPv6:2607:f8b0:4001:c03::234]) by iceking.adventuretime.ooo (8.14.3/8.14.3/Debian-9.4) with ESMTP id r5DL3nFJ016967 (version=TLSv1/SSLv3 cipher=RC4-SHA bits=128 verify=NOT) for <incoming+gitlabhq/gitlabhq@appmail.adventuretime.ooo>; Thu, 13 Jun 2013 17:03:50 -0400
+Received: by mail-ie0-f180.google.com with SMTP id f4so21977375iea.25 for <incoming+email-test-project_id-issue-@appmail.adventuretime.ooo>; Thu, 13 Jun 2013 14:03:48 -0700
+Received: by 10.0.0.1 with HTTP; Thu, 13 Jun 2013 14:03:48 -0700
+Date: Thu, 13 Jun 2013 17:03:48 -0400
+From: Finn the Human <finn@adventuretime.ooo>
+Sender: Jake the Dog <jake@adventuretime.ooo>
+To: support@adventuretime.ooo
+Delivered-To: support@adventuretime.ooo
+Message-ID: <CADkmRc+rNGAGGbV2iE5p918UVy4UyJqVcXRO2=otppgzduJSg@mail.gmail.com>
+Subject: The message subject! @all
+Mime-Version: 1.0
+Content-Type: text/plain;
+ charset=ISO-8859-1
+Content-Transfer-Encoding: 7bit
+X-Sieve: CMU Sieve 2.2
+X-Received: by 10.0.0.1 with SMTP id n7mr11234144ipb.85.1371157428600; Thu,
+ 13 Jun 2013 14:03:48 -0700 (PDT)
+X-Scanned-By: MIMEDefang 2.69 on IPv6:2001:470:1d:165::1
+
+Service desk stuff!
+
+```
+a = b
+```
diff --git a/spec/fixtures/emails/valid_reply_with_quick_actions.eml b/spec/fixtures/emails/valid_reply_with_quick_actions.eml
new file mode 100644
index 00000000000..cd00b6eb8b8
--- /dev/null
+++ b/spec/fixtures/emails/valid_reply_with_quick_actions.eml
@@ -0,0 +1,45 @@
+Return-Path: <jake@adventuretime.ooo>
+Received: from iceking.adventuretime.ooo ([unix socket]) by iceking (Cyrus v2.2.13-Debian-2.2.13-19+squeeze3) with LMTPA; Thu, 13 Jun 2013 17:03:50 -0400
+Received: from mail-ie0-x234.google.com (mail-ie0-x234.google.com [IPv6:2607:f8b0:4001:c03::234]) by iceking.adventuretime.ooo (8.14.3/8.14.3/Debian-9.4) with ESMTP id r5DL3nFJ016967 (version=TLSv1/SSLv3 cipher=RC4-SHA bits=128 verify=NOT) for <reply+59d8df8370b7e95c5a49fbf86aeb2c93@appmail.adventuretime.ooo>; Thu, 13 Jun 2013 17:03:50 -0400
+Received: by mail-ie0-f180.google.com with SMTP id f4so21977375iea.25 for <reply+59d8df8370b7e95c5a49fbf86aeb2c93@appmail.adventuretime.ooo>; Thu, 13 Jun 2013 14:03:48 -0700
+Received: by 10.0.0.1 with HTTP; Thu, 13 Jun 2013 14:03:48 -0700
+Date: Thu, 13 Jun 2013 17:03:48 -0400
+From: Jake the Dog <jake@adventuretime.ooo>
+To: reply+59d8df8370b7e95c5a49fbf86aeb2c93@appmail.adventuretime.ooo
+Message-ID: <CADkmRc+rNGAGGbV2iE5p918UVy4UyJqVcXRO2=otppgzduJSg@mail.gmail.com>
+In-Reply-To: <issue_1@localhost>
+References: <reply-59d8df8370b7e95c5a49fbf86aeb2c93@localhost> <issue_1@localhost>
+Subject: re: [Discourse Meta] eviltrout posted in 'Adventure Time Sux'
+Mime-Version: 1.0
+Content-Type: text/plain;
+ charset=ISO-8859-1
+Content-Transfer-Encoding: 7bit
+X-Sieve: CMU Sieve 2.2
+X-Received: by 10.0.0.1 with SMTP id n7mr11234144ipb.85.1371157428600; Thu,
+ 13 Jun 2013 14:03:48 -0700 (PDT)
+X-Scanned-By: MIMEDefang 2.69 on IPv6:2001:470:1d:165::1
+
+I could not disagree more. I am obviously biased but adventure time is the
+greatest show ever created. Everyone should watch it.
+
+- Jake out
+
+/close
+/title test
+
+
+On Sun, Jun 9, 2013 at 1:39 PM, eviltrout via Discourse Meta
+<reply+59d8df8370b7e95c5a49fbf86aeb2c93@appmail.adventuretime.ooo> wrote:
+>
+>
+>
+> eviltrout posted in 'Adventure Time Sux' on Discourse Meta:
+>
+> ---
+> hey guys everyone knows adventure time sucks!
+>
+> ---
+> Please visit this link to respond: http://localhost:3000/t/adventure-time-sux/1234/3
+>
+> To unsubscribe from these emails, visit your [user preferences](http://localhost:3000/user_preferences).
+>
diff --git a/spec/fixtures/gitlab/import_export/corrupted_project_export.tar.gz b/spec/fixtures/gitlab/import_export/corrupted_project_export.tar.gz
index cac16cf9cd8..e99136e96b7 100644
--- a/spec/fixtures/gitlab/import_export/corrupted_project_export.tar.gz
+++ b/spec/fixtures/gitlab/import_export/corrupted_project_export.tar.gz
Binary files differ
diff --git a/spec/fixtures/gitlab/import_export/lightweight_project_export.tar.gz b/spec/fixtures/gitlab/import_export/lightweight_project_export.tar.gz
index c01402954dd..e3ec4f603b9 100644
--- a/spec/fixtures/gitlab/import_export/lightweight_project_export.tar.gz
+++ b/spec/fixtures/gitlab/import_export/lightweight_project_export.tar.gz
Binary files differ
diff --git a/spec/fixtures/helm/helm_list_v2_cilium_deployed.json.gz b/spec/fixtures/helm/helm_list_v2_cilium_deployed.json.gz
new file mode 100644
index 00000000000..a343356c95c
--- /dev/null
+++ b/spec/fixtures/helm/helm_list_v2_cilium_deployed.json.gz
Binary files differ
diff --git a/spec/fixtures/helm/helm_list_v2_cilium_failed.json.gz b/spec/fixtures/helm/helm_list_v2_cilium_failed.json.gz
new file mode 100644
index 00000000000..f7faff2ca19
--- /dev/null
+++ b/spec/fixtures/helm/helm_list_v2_cilium_failed.json.gz
Binary files differ
diff --git a/spec/fixtures/helm/helm_list_v2_cilium_missing.json.gz b/spec/fixtures/helm/helm_list_v2_cilium_missing.json.gz
new file mode 100644
index 00000000000..20cac36287b
--- /dev/null
+++ b/spec/fixtures/helm/helm_list_v2_cilium_missing.json.gz
Binary files differ
diff --git a/spec/fixtures/lib/gitlab/import_export/complex/project.json b/spec/fixtures/lib/gitlab/import_export/complex/project.json
index 0785da9c1bf..d88b2ebc83a 100644
--- a/spec/fixtures/lib/gitlab/import_export/complex/project.json
+++ b/spec/fixtures/lib/gitlab/import_export/complex/project.json
@@ -7007,395 +7007,6 @@
"enabled": false
},
"deploy_keys": [],
- "services": [
- {
- "id": 101,
- "title": "YouTrack",
- "project_id": 5,
- "created_at": "2016-06-14T15:01:51.327Z",
- "updated_at": "2016-06-14T15:01:51.327Z",
- "active": false,
- "properties": {},
- "template": false,
- "push_events": true,
- "issues_events": true,
- "merge_requests_events": true,
- "tag_push_events": true,
- "note_events": true,
- "job_events": true,
- "type": "YoutrackService",
- "category": "issue_tracker",
- "default": false,
- "wiki_page_events": true
- },
- {
- "id": 100,
- "title": "JetBrains TeamCity CI",
- "project_id": 5,
- "created_at": "2016-06-14T15:01:51.315Z",
- "updated_at": "2016-06-14T15:01:51.315Z",
- "active": false,
- "properties": {},
- "template": false,
- "push_events": true,
- "issues_events": true,
- "merge_requests_events": true,
- "tag_push_events": true,
- "note_events": true,
- "job_events": true,
- "type": "TeamcityService",
- "category": "ci",
- "default": false,
- "wiki_page_events": true
- },
- {
- "id": 99,
- "title": "Slack",
- "project_id": 5,
- "created_at": "2016-06-14T15:01:51.303Z",
- "updated_at": "2016-06-14T15:01:51.303Z",
- "active": false,
- "properties": {
- "notify_only_broken_pipelines": true
- },
- "template": false,
- "push_events": true,
- "issues_events": true,
- "merge_requests_events": true,
- "tag_push_events": true,
- "note_events": true,
- "pipeline_events": true,
- "type": "SlackService",
- "category": "common",
- "default": false,
- "wiki_page_events": true
- },
- {
- "id": 98,
- "title": "Redmine",
- "project_id": 5,
- "created_at": "2016-06-14T15:01:51.289Z",
- "updated_at": "2016-06-14T15:01:51.289Z",
- "active": false,
- "properties": {},
- "template": false,
- "push_events": true,
- "issues_events": true,
- "merge_requests_events": true,
- "tag_push_events": true,
- "note_events": true,
- "job_events": true,
- "type": "RedmineService",
- "category": "issue_tracker",
- "default": false,
- "wiki_page_events": true
- },
- {
- "id": 97,
- "title": "Pushover",
- "project_id": 5,
- "created_at": "2016-06-14T15:01:51.277Z",
- "updated_at": "2016-06-14T15:01:51.277Z",
- "active": false,
- "properties": {},
- "template": false,
- "push_events": true,
- "issues_events": true,
- "merge_requests_events": true,
- "tag_push_events": true,
- "note_events": true,
- "job_events": true,
- "type": "PushoverService",
- "category": "common",
- "default": false,
- "wiki_page_events": true
- },
- {
- "id": 96,
- "title": "PivotalTracker",
- "project_id": 5,
- "created_at": "2016-06-14T15:01:51.267Z",
- "updated_at": "2016-06-14T15:01:51.267Z",
- "active": false,
- "properties": {},
- "template": false,
- "push_events": true,
- "issues_events": true,
- "merge_requests_events": true,
- "tag_push_events": true,
- "note_events": true,
- "job_events": true,
- "type": "PivotalTrackerService",
- "category": "common",
- "default": false,
- "wiki_page_events": true
- },
- {
- "id": 95,
- "title": "Jira",
- "project_id": 5,
- "created_at": "2016-06-14T15:01:51.255Z",
- "updated_at": "2016-06-14T15:01:51.255Z",
- "active": false,
- "properties": {
- "api_url": "",
- "jira_issue_transition_id": "2"
- },
- "template": false,
- "push_events": true,
- "issues_events": true,
- "merge_requests_events": true,
- "tag_push_events": true,
- "note_events": true,
- "job_events": true,
- "type": "JiraService",
- "category": "issue_tracker",
- "default": false,
- "wiki_page_events": true
- },
- {
- "id": 94,
- "title": "Irker (IRC gateway)",
- "project_id": 5,
- "created_at": "2016-06-14T15:01:51.232Z",
- "updated_at": "2016-06-14T15:01:51.232Z",
- "active": true,
- "properties": {},
- "template": false,
- "push_events": true,
- "issues_events": true,
- "merge_requests_events": true,
- "tag_push_events": true,
- "note_events": true,
- "job_events": true,
- "type": "IrkerService",
- "category": "common",
- "default": false,
- "wiki_page_events": true
- },
- {
- "id": 93,
- "title": "HipChat",
- "project_id": 5,
- "created_at": "2016-06-14T15:01:51.219Z",
- "updated_at": "2016-06-14T15:01:51.219Z",
- "active": false,
- "properties": {
- "notify_only_broken_pipelines": true
- },
- "template": false,
- "push_events": true,
- "issues_events": true,
- "merge_requests_events": true,
- "tag_push_events": true,
- "note_events": true,
- "pipeline_events": true,
- "type": "HipchatService",
- "category": "common",
- "default": false,
- "wiki_page_events": true
- },
- {
- "id": 91,
- "title": "Flowdock",
- "project_id": 5,
- "created_at": "2016-06-14T15:01:51.182Z",
- "updated_at": "2016-06-14T15:01:51.182Z",
- "active": false,
- "properties": {},
- "template": false,
- "push_events": true,
- "issues_events": true,
- "merge_requests_events": true,
- "tag_push_events": true,
- "note_events": true,
- "job_events": true,
- "type": "FlowdockService",
- "category": "common",
- "default": false,
- "wiki_page_events": true
- },
- {
- "id": 90,
- "title": "External Wiki",
- "project_id": 5,
- "created_at": "2016-06-14T15:01:51.166Z",
- "updated_at": "2016-06-14T15:01:51.166Z",
- "active": false,
- "properties": {},
- "template": false,
- "push_events": true,
- "issues_events": true,
- "merge_requests_events": true,
- "tag_push_events": true,
- "note_events": true,
- "job_events": true,
- "type": "ExternalWikiService",
- "category": "common",
- "default": false,
- "wiki_page_events": true
- },
- {
- "id": 89,
- "title": "Emails on push",
- "project_id": 5,
- "created_at": "2016-06-14T15:01:51.153Z",
- "updated_at": "2016-06-14T15:01:51.153Z",
- "active": false,
- "properties": {},
- "template": false,
- "push_events": true,
- "issues_events": true,
- "merge_requests_events": true,
- "tag_push_events": true,
- "note_events": true,
- "job_events": true,
- "type": "EmailsOnPushService",
- "category": "common",
- "default": false,
- "wiki_page_events": true
- },
- {
- "id": 88,
- "title": "Drone CI",
- "project_id": 5,
- "created_at": "2016-06-14T15:01:51.139Z",
- "updated_at": "2016-06-14T15:01:51.139Z",
- "active": false,
- "properties": {},
- "template": false,
- "push_events": true,
- "issues_events": true,
- "merge_requests_events": true,
- "tag_push_events": true,
- "note_events": true,
- "job_events": true,
- "type": "DroneCiService",
- "category": "ci",
- "default": false,
- "wiki_page_events": true
- },
- {
- "id": 87,
- "title": "Custom Issue Tracker",
- "project_id": 5,
- "created_at": "2016-06-14T15:01:51.125Z",
- "updated_at": "2016-06-14T15:01:51.125Z",
- "active": false,
- "properties": {},
- "template": false,
- "push_events": true,
- "issues_events": true,
- "merge_requests_events": true,
- "tag_push_events": true,
- "note_events": true,
- "job_events": true,
- "type": "CustomIssueTrackerService",
- "category": "issue_tracker",
- "default": false,
- "wiki_page_events": true
- },
- {
- "id": 86,
- "title": "Campfire",
- "project_id": 5,
- "created_at": "2016-06-14T15:01:51.113Z",
- "updated_at": "2016-06-14T15:01:51.113Z",
- "active": false,
- "properties": {},
- "template": false,
- "push_events": true,
- "issues_events": true,
- "merge_requests_events": true,
- "tag_push_events": true,
- "note_events": true,
- "job_events": true,
- "type": "CampfireService",
- "category": "common",
- "default": false,
- "wiki_page_events": true
- },
- {
- "id": 84,
- "title": "Buildkite",
- "project_id": 5,
- "created_at": "2016-06-14T15:01:51.080Z",
- "updated_at": "2016-06-14T15:01:51.080Z",
- "active": false,
- "properties": {},
- "template": false,
- "push_events": true,
- "issues_events": true,
- "merge_requests_events": true,
- "tag_push_events": true,
- "note_events": true,
- "job_events": true,
- "type": "BuildkiteService",
- "category": "ci",
- "default": false,
- "wiki_page_events": true
- },
- {
- "id": 83,
- "title": "Atlassian Bamboo CI",
- "project_id": 5,
- "created_at": "2016-06-14T15:01:51.067Z",
- "updated_at": "2016-06-14T15:01:51.067Z",
- "active": false,
- "properties": {},
- "template": false,
- "push_events": true,
- "issues_events": true,
- "merge_requests_events": true,
- "tag_push_events": true,
- "note_events": true,
- "job_events": true,
- "type": "BambooService",
- "category": "ci",
- "default": false,
- "wiki_page_events": true
- },
- {
- "id": 82,
- "title": "Assembla",
- "project_id": 5,
- "created_at": "2016-06-14T15:01:51.047Z",
- "updated_at": "2016-06-14T15:01:51.047Z",
- "active": false,
- "properties": {},
- "template": false,
- "push_events": true,
- "issues_events": true,
- "merge_requests_events": true,
- "tag_push_events": true,
- "note_events": true,
- "job_events": true,
- "type": "AssemblaService",
- "category": "common",
- "default": false,
- "wiki_page_events": true
- },
- {
- "id": 81,
- "title": "Asana",
- "project_id": 5,
- "created_at": "2016-06-14T15:01:51.031Z",
- "updated_at": "2016-06-14T15:01:51.031Z",
- "active": false,
- "properties": {},
- "template": false,
- "push_events": true,
- "issues_events": true,
- "merge_requests_events": true,
- "tag_push_events": true,
- "note_events": true,
- "job_events": true,
- "type": "AsanaService",
- "category": "common",
- "default": false,
- "wiki_page_events": true
- }
- ],
"hooks": [],
"protected_branches": [
{
diff --git a/spec/fixtures/lib/gitlab/import_export/complex/tree/project/services.ndjson b/spec/fixtures/lib/gitlab/import_export/complex/tree/project/services.ndjson
index 6d6afd3af0b..e5d39512255 100644
--- a/spec/fixtures/lib/gitlab/import_export/complex/tree/project/services.ndjson
+++ b/spec/fixtures/lib/gitlab/import_export/complex/tree/project/services.ndjson
@@ -1,19 +1,19 @@
-{"id":101,"title":"YouTrack","project_id":5,"created_at":"2016-06-14T15:01:51.327Z","updated_at":"2016-06-14T15:01:51.327Z","active":false,"properties":{},"template":false,"push_events":true,"issues_events":true,"merge_requests_events":true,"tag_push_events":true,"note_events":true,"job_events":true,"type":"YoutrackService","category":"issue_tracker","default":false,"wiki_page_events":true}
-{"id":100,"title":"JetBrains TeamCity CI","project_id":5,"created_at":"2016-06-14T15:01:51.315Z","updated_at":"2016-06-14T15:01:51.315Z","active":false,"properties":{},"template":false,"push_events":true,"issues_events":true,"merge_requests_events":true,"tag_push_events":true,"note_events":true,"job_events":true,"type":"TeamcityService","category":"ci","default":false,"wiki_page_events":true}
-{"id":99,"title":"Slack","project_id":5,"created_at":"2016-06-14T15:01:51.303Z","updated_at":"2016-06-14T15:01:51.303Z","active":false,"properties":{"notify_only_broken_pipelines":true},"template":false,"push_events":true,"issues_events":true,"merge_requests_events":true,"tag_push_events":true,"note_events":true,"pipeline_events":true,"type":"SlackService","category":"common","default":false,"wiki_page_events":true}
-{"id":98,"title":"Redmine","project_id":5,"created_at":"2016-06-14T15:01:51.289Z","updated_at":"2016-06-14T15:01:51.289Z","active":false,"properties":{},"template":false,"push_events":true,"issues_events":true,"merge_requests_events":true,"tag_push_events":true,"note_events":true,"job_events":true,"type":"RedmineService","category":"issue_tracker","default":false,"wiki_page_events":true}
-{"id":97,"title":"Pushover","project_id":5,"created_at":"2016-06-14T15:01:51.277Z","updated_at":"2016-06-14T15:01:51.277Z","active":false,"properties":{},"template":false,"push_events":true,"issues_events":true,"merge_requests_events":true,"tag_push_events":true,"note_events":true,"job_events":true,"type":"PushoverService","category":"common","default":false,"wiki_page_events":true}
-{"id":96,"title":"PivotalTracker","project_id":5,"created_at":"2016-06-14T15:01:51.267Z","updated_at":"2016-06-14T15:01:51.267Z","active":false,"properties":{},"template":false,"push_events":true,"issues_events":true,"merge_requests_events":true,"tag_push_events":true,"note_events":true,"job_events":true,"type":"PivotalTrackerService","category":"common","default":false,"wiki_page_events":true}
-{"id":95,"title":"Jira","project_id":5,"created_at":"2016-06-14T15:01:51.255Z","updated_at":"2016-06-14T15:01:51.255Z","active":false,"properties":{"api_url":"","jira_issue_transition_id":"2"},"template":false,"push_events":true,"issues_events":true,"merge_requests_events":true,"tag_push_events":true,"note_events":true,"job_events":true,"type":"JiraService","category":"issue_tracker","default":false,"wiki_page_events":true}
-{"id":94,"title":"Irker (IRC gateway)","project_id":5,"created_at":"2016-06-14T15:01:51.232Z","updated_at":"2016-06-14T15:01:51.232Z","active":true,"properties":{},"template":false,"push_events":true,"issues_events":true,"merge_requests_events":true,"tag_push_events":true,"note_events":true,"job_events":true,"type":"IrkerService","category":"common","default":false,"wiki_page_events":true}
-{"id":93,"title":"HipChat","project_id":5,"created_at":"2016-06-14T15:01:51.219Z","updated_at":"2016-06-14T15:01:51.219Z","active":false,"properties":{"notify_only_broken_pipelines":true},"template":false,"push_events":true,"issues_events":true,"merge_requests_events":true,"tag_push_events":true,"note_events":true,"pipeline_events":true,"type":"HipchatService","category":"common","default":false,"wiki_page_events":true}
-{"id":91,"title":"Flowdock","project_id":5,"created_at":"2016-06-14T15:01:51.182Z","updated_at":"2016-06-14T15:01:51.182Z","active":false,"properties":{},"template":false,"push_events":true,"issues_events":true,"merge_requests_events":true,"tag_push_events":true,"note_events":true,"job_events":true,"type":"FlowdockService","category":"common","default":false,"wiki_page_events":true}
-{"id":90,"title":"External Wiki","project_id":5,"created_at":"2016-06-14T15:01:51.166Z","updated_at":"2016-06-14T15:01:51.166Z","active":false,"properties":{},"template":false,"push_events":true,"issues_events":true,"merge_requests_events":true,"tag_push_events":true,"note_events":true,"job_events":true,"type":"ExternalWikiService","category":"common","default":false,"wiki_page_events":true}
-{"id":89,"title":"Emails on push","project_id":5,"created_at":"2016-06-14T15:01:51.153Z","updated_at":"2016-06-14T15:01:51.153Z","active":false,"properties":{},"template":false,"push_events":true,"issues_events":true,"merge_requests_events":true,"tag_push_events":true,"note_events":true,"job_events":true,"type":"EmailsOnPushService","category":"common","default":false,"wiki_page_events":true}
-{"id":88,"title":"Drone CI","project_id":5,"created_at":"2016-06-14T15:01:51.139Z","updated_at":"2016-06-14T15:01:51.139Z","active":false,"properties":{},"template":false,"push_events":true,"issues_events":true,"merge_requests_events":true,"tag_push_events":true,"note_events":true,"job_events":true,"type":"DroneCiService","category":"ci","default":false,"wiki_page_events":true}
-{"id":87,"title":"Custom Issue Tracker","project_id":5,"created_at":"2016-06-14T15:01:51.125Z","updated_at":"2016-06-14T15:01:51.125Z","active":false,"properties":{},"template":false,"push_events":true,"issues_events":true,"merge_requests_events":true,"tag_push_events":true,"note_events":true,"job_events":true,"type":"CustomIssueTrackerService","category":"issue_tracker","default":false,"wiki_page_events":true}
-{"id":86,"title":"Campfire","project_id":5,"created_at":"2016-06-14T15:01:51.113Z","updated_at":"2016-06-14T15:01:51.113Z","active":false,"properties":{},"template":false,"push_events":true,"issues_events":true,"merge_requests_events":true,"tag_push_events":true,"note_events":true,"job_events":true,"type":"CampfireService","category":"common","default":false,"wiki_page_events":true}
-{"id":84,"title":"Buildkite","project_id":5,"created_at":"2016-06-14T15:01:51.080Z","updated_at":"2016-06-14T15:01:51.080Z","active":false,"properties":{},"template":false,"push_events":true,"issues_events":true,"merge_requests_events":true,"tag_push_events":true,"note_events":true,"job_events":true,"type":"BuildkiteService","category":"ci","default":false,"wiki_page_events":true}
-{"id":83,"title":"Atlassian Bamboo CI","project_id":5,"created_at":"2016-06-14T15:01:51.067Z","updated_at":"2016-06-14T15:01:51.067Z","active":false,"properties":{},"template":false,"push_events":true,"issues_events":true,"merge_requests_events":true,"tag_push_events":true,"note_events":true,"job_events":true,"type":"BambooService","category":"ci","default":false,"wiki_page_events":true}
-{"id":82,"title":"Assembla","project_id":5,"created_at":"2016-06-14T15:01:51.047Z","updated_at":"2016-06-14T15:01:51.047Z","active":false,"properties":{},"template":false,"push_events":true,"issues_events":true,"merge_requests_events":true,"tag_push_events":true,"note_events":true,"job_events":true,"type":"AssemblaService","category":"common","default":false,"wiki_page_events":true}
-{"id":81,"title":"Asana","project_id":5,"created_at":"2016-06-14T15:01:51.031Z","updated_at":"2016-06-14T15:01:51.031Z","active":false,"properties":{},"template":false,"push_events":true,"issues_events":true,"merge_requests_events":true,"tag_push_events":true,"note_events":true,"job_events":true,"type":"AsanaService","category":"common","default":false,"wiki_page_events":true}
+{"id":101,"project_id":5,"created_at":"2016-06-14T15:01:51.327Z","updated_at":"2016-06-14T15:01:51.327Z","active":false,"properties":{},"template":false,"push_events":true,"issues_events":true,"merge_requests_events":true,"tag_push_events":true,"note_events":true,"job_events":true,"type":"YoutrackService","category":"issue_tracker","default":false,"wiki_page_events":true}
+{"id":100,"project_id":5,"created_at":"2016-06-14T15:01:51.315Z","updated_at":"2016-06-14T15:01:51.315Z","active":false,"properties":{},"template":false,"push_events":true,"issues_events":true,"merge_requests_events":true,"tag_push_events":true,"note_events":true,"job_events":true,"type":"TeamcityService","category":"ci","default":false,"wiki_page_events":true}
+{"id":99,"project_id":5,"created_at":"2016-06-14T15:01:51.303Z","updated_at":"2016-06-14T15:01:51.303Z","active":false,"properties":{"notify_only_broken_pipelines":true},"template":false,"push_events":true,"issues_events":true,"merge_requests_events":true,"tag_push_events":true,"note_events":true,"pipeline_events":true,"type":"SlackService","category":"common","default":false,"wiki_page_events":true}
+{"id":98,"project_id":5,"created_at":"2016-06-14T15:01:51.289Z","updated_at":"2016-06-14T15:01:51.289Z","active":false,"properties":{},"template":false,"push_events":true,"issues_events":true,"merge_requests_events":true,"tag_push_events":true,"note_events":true,"job_events":true,"type":"RedmineService","category":"issue_tracker","default":false,"wiki_page_events":true}
+{"id":97,"project_id":5,"created_at":"2016-06-14T15:01:51.277Z","updated_at":"2016-06-14T15:01:51.277Z","active":false,"properties":{},"template":false,"push_events":true,"issues_events":true,"merge_requests_events":true,"tag_push_events":true,"note_events":true,"job_events":true,"type":"PushoverService","category":"common","default":false,"wiki_page_events":true}
+{"id":96,"project_id":5,"created_at":"2016-06-14T15:01:51.267Z","updated_at":"2016-06-14T15:01:51.267Z","active":false,"properties":{},"template":false,"push_events":true,"issues_events":true,"merge_requests_events":true,"tag_push_events":true,"note_events":true,"job_events":true,"type":"PivotalTrackerService","category":"common","default":false,"wiki_page_events":true}
+{"id":95,"project_id":5,"created_at":"2016-06-14T15:01:51.255Z","updated_at":"2016-06-14T15:01:51.255Z","active":false,"properties":{"api_url":"","jira_issue_transition_id":"2"},"template":false,"push_events":true,"issues_events":true,"merge_requests_events":true,"tag_push_events":true,"note_events":true,"job_events":true,"type":"JiraService","category":"issue_tracker","default":false,"wiki_page_events":true}
+{"id":94,"project_id":5,"created_at":"2016-06-14T15:01:51.232Z","updated_at":"2016-06-14T15:01:51.232Z","active":true,"properties":{},"template":false,"push_events":true,"issues_events":true,"merge_requests_events":true,"tag_push_events":true,"note_events":true,"job_events":true,"type":"IrkerService","category":"common","default":false,"wiki_page_events":true}
+{"id":93,"project_id":5,"created_at":"2016-06-14T15:01:51.219Z","updated_at":"2016-06-14T15:01:51.219Z","active":false,"properties":{"notify_only_broken_pipelines":true},"template":false,"push_events":true,"issues_events":true,"merge_requests_events":true,"tag_push_events":true,"note_events":true,"pipeline_events":true,"type":"HipchatService","category":"common","default":false,"wiki_page_events":true}
+{"id":91,"project_id":5,"created_at":"2016-06-14T15:01:51.182Z","updated_at":"2016-06-14T15:01:51.182Z","active":false,"properties":{},"template":false,"push_events":true,"issues_events":true,"merge_requests_events":true,"tag_push_events":true,"note_events":true,"job_events":true,"type":"FlowdockService","category":"common","default":false,"wiki_page_events":true}
+{"id":90,"project_id":5,"created_at":"2016-06-14T15:01:51.166Z","updated_at":"2016-06-14T15:01:51.166Z","active":false,"properties":{},"template":false,"push_events":true,"issues_events":true,"merge_requests_events":true,"tag_push_events":true,"note_events":true,"job_events":true,"type":"ExternalWikiService","category":"common","default":false,"wiki_page_events":true}
+{"id":89,"project_id":5,"created_at":"2016-06-14T15:01:51.153Z","updated_at":"2016-06-14T15:01:51.153Z","active":false,"properties":{},"template":false,"push_events":true,"issues_events":true,"merge_requests_events":true,"tag_push_events":true,"note_events":true,"job_events":true,"type":"EmailsOnPushService","category":"common","default":false,"wiki_page_events":true}
+{"id":88,"project_id":5,"created_at":"2016-06-14T15:01:51.139Z","updated_at":"2016-06-14T15:01:51.139Z","active":false,"properties":{},"template":false,"push_events":true,"issues_events":true,"merge_requests_events":true,"tag_push_events":true,"note_events":true,"job_events":true,"type":"DroneCiService","category":"ci","default":false,"wiki_page_events":true}
+{"id":87,"project_id":5,"created_at":"2016-06-14T15:01:51.125Z","updated_at":"2016-06-14T15:01:51.125Z","active":false,"properties":{},"template":false,"push_events":true,"issues_events":true,"merge_requests_events":true,"tag_push_events":true,"note_events":true,"job_events":true,"type":"CustomIssueTrackerService","category":"issue_tracker","default":false,"wiki_page_events":true}
+{"id":86,"project_id":5,"created_at":"2016-06-14T15:01:51.113Z","updated_at":"2016-06-14T15:01:51.113Z","active":false,"properties":{},"template":false,"push_events":true,"issues_events":true,"merge_requests_events":true,"tag_push_events":true,"note_events":true,"job_events":true,"type":"CampfireService","category":"common","default":false,"wiki_page_events":true}
+{"id":84,"project_id":5,"created_at":"2016-06-14T15:01:51.080Z","updated_at":"2016-06-14T15:01:51.080Z","active":false,"properties":{},"template":false,"push_events":true,"issues_events":true,"merge_requests_events":true,"tag_push_events":true,"note_events":true,"job_events":true,"type":"BuildkiteService","category":"ci","default":false,"wiki_page_events":true}
+{"id":83,"project_id":5,"created_at":"2016-06-14T15:01:51.067Z","updated_at":"2016-06-14T15:01:51.067Z","active":false,"properties":{},"template":false,"push_events":true,"issues_events":true,"merge_requests_events":true,"tag_push_events":true,"note_events":true,"job_events":true,"type":"BambooService","category":"ci","default":false,"wiki_page_events":true}
+{"id":82,"project_id":5,"created_at":"2016-06-14T15:01:51.047Z","updated_at":"2016-06-14T15:01:51.047Z","active":false,"properties":{},"template":false,"push_events":true,"issues_events":true,"merge_requests_events":true,"tag_push_events":true,"note_events":true,"job_events":true,"type":"AssemblaService","category":"common","default":false,"wiki_page_events":true}
+{"id":81,"project_id":5,"created_at":"2016-06-14T15:01:51.031Z","updated_at":"2016-06-14T15:01:51.031Z","active":false,"properties":{},"template":false,"push_events":true,"issues_events":true,"merge_requests_events":true,"tag_push_events":true,"note_events":true,"job_events":true,"type":"AsanaService","category":"common","default":false,"wiki_page_events":true}
diff --git a/spec/fixtures/lib/gitlab/import_export/designs/project.json b/spec/fixtures/lib/gitlab/import_export/designs/project.json
index 28eaa38d387..ebc08868d9e 100644
--- a/spec/fixtures/lib/gitlab/import_export/designs/project.json
+++ b/spec/fixtures/lib/gitlab/import_export/designs/project.json
@@ -456,9 +456,6 @@
"pipeline_schedules":[
],
- "services":[
-
- ],
"protected_branches":[
],
diff --git a/spec/fixtures/lib/gitlab/import_export/light/project.json b/spec/fixtures/lib/gitlab/import_export/light/project.json
index 326a2cef9ff..963cdb342b5 100644
--- a/spec/fixtures/lib/gitlab/import_export/light/project.json
+++ b/spec/fixtures/lib/gitlab/import_export/light/project.json
@@ -141,50 +141,6 @@
]
}
],
- "services": [
- {
- "id": 100,
- "title": "JetBrains TeamCity CI",
- "project_id": 5,
- "created_at": "2016-06-14T15:01:51.315Z",
- "updated_at": "2016-06-14T15:01:51.315Z",
- "active": false,
- "properties": {},
- "template": true,
- "instance": false,
- "push_events": true,
- "issues_events": true,
- "merge_requests_events": true,
- "tag_push_events": true,
- "note_events": true,
- "job_events": true,
- "type": "TeamcityService",
- "category": "ci",
- "default": false,
- "wiki_page_events": true
- },
- {
- "id": 101,
- "title": "Jira",
- "project_id": 5,
- "created_at": "2016-06-14T15:01:51.315Z",
- "updated_at": "2016-06-14T15:01:51.315Z",
- "active": false,
- "properties": {},
- "template": false,
- "instance": true,
- "push_events": true,
- "issues_events": true,
- "merge_requests_events": true,
- "tag_push_events": true,
- "note_events": true,
- "job_events": true,
- "type": "JiraService",
- "category": "ci",
- "default": false,
- "wiki_page_events": true
- }
- ],
"snippets": [],
"hooks": [],
"custom_attributes": [
diff --git a/spec/fixtures/lib/gitlab/import_export/light/tree/project/services.ndjson b/spec/fixtures/lib/gitlab/import_export/light/tree/project/services.ndjson
index c5ae6bf4b04..414b68dacd7 100644
--- a/spec/fixtures/lib/gitlab/import_export/light/tree/project/services.ndjson
+++ b/spec/fixtures/lib/gitlab/import_export/light/tree/project/services.ndjson
@@ -1,2 +1,2 @@
-{"id":100,"title":"JetBrains TeamCity CI","project_id":5,"created_at":"2016-06-14T15:01:51.315Z","updated_at":"2016-06-14T15:01:51.315Z","active":false,"properties":{},"template":true,"instance":false,"push_events":true,"issues_events":true,"merge_requests_events":true,"tag_push_events":true,"note_events":true,"job_events":true,"type":"TeamcityService","category":"ci","default":false,"wiki_page_events":true}
-{"id":101,"title":"Jira","project_id":5,"created_at":"2016-06-14T15:01:51.315Z","updated_at":"2016-06-14T15:01:51.315Z","active":false,"properties":{},"template":false,"instance":true,"push_events":true,"issues_events":true,"merge_requests_events":true,"tag_push_events":true,"note_events":true,"job_events":true,"type":"JiraService","category":"ci","default":false,"wiki_page_events":true}
+{"id":100,"project_id":5,"created_at":"2016-06-14T15:01:51.315Z","updated_at":"2016-06-14T15:01:51.315Z","active":false,"properties":{},"template":true,"instance":false,"push_events":true,"issues_events":true,"merge_requests_events":true,"tag_push_events":true,"note_events":true,"job_events":true,"type":"TeamcityService","category":"ci","default":false,"wiki_page_events":true}
+{"id":101,"project_id":5,"created_at":"2016-06-14T15:01:51.315Z","updated_at":"2016-06-14T15:01:51.315Z","active":false,"properties":{},"template":false,"instance":true,"push_events":true,"issues_events":true,"merge_requests_events":true,"tag_push_events":true,"note_events":true,"job_events":true,"type":"JiraService","category":"ci","default":false,"wiki_page_events":true}
diff --git a/spec/fixtures/lib/gitlab/import_export/with_invalid_records/project.json b/spec/fixtures/lib/gitlab/import_export/with_invalid_records/project.json
index a6e6ba43bdc..b9e791ee85a 100644
--- a/spec/fixtures/lib/gitlab/import_export/with_invalid_records/project.json
+++ b/spec/fixtures/lib/gitlab/import_export/with_invalid_records/project.json
@@ -32,7 +32,6 @@
],
"labels": [],
"issues": [],
- "services": [],
"snippets": [],
"hooks": []
}
diff --git a/spec/fixtures/lib/gitlab/metrics/dashboard/sample_dashboard.yml b/spec/fixtures/lib/gitlab/metrics/dashboard/sample_dashboard.yml
index 5177de66fcf..1e41ef669d1 100644
--- a/spec/fixtures/lib/gitlab/metrics/dashboard/sample_dashboard.yml
+++ b/spec/fixtures/lib/gitlab/metrics/dashboard/sample_dashboard.yml
@@ -13,6 +13,23 @@ templating:
options:
default_value: 'default'
text_variable_simple_syntax: 'default value'
+ custom_variable_simple_syntax: ['value1', 'value2', 'value3']
+ custom_variable_full_syntax:
+ label: 'Variable 2'
+ type: custom
+ options:
+ values:
+ - value: 'value option 1'
+ text: 'Option 1'
+ - value: 'value_option_2'
+ text: 'Option 2'
+ default: true
+ metric_label_values_variable:
+ label: 'Variable 3'
+ type: metric_label_values
+ options:
+ series_selector: 'backend:haproxy_backend_availability:ratio{env="{{env}}"}'
+ label: 'backend'
panel_groups:
- group: Group A
priority: 1
diff --git a/spec/fixtures/lib/gitlab/metrics/dashboard/schemas/custom_variable_full_syntax.json b/spec/fixtures/lib/gitlab/metrics/dashboard/schemas/custom_variable_full_syntax.json
new file mode 100644
index 00000000000..e251e59de29
--- /dev/null
+++ b/spec/fixtures/lib/gitlab/metrics/dashboard/schemas/custom_variable_full_syntax.json
@@ -0,0 +1,12 @@
+{
+ "type": "object",
+ "required": [
+ "type", "options"
+ ],
+ "properties": {
+ "type": { "enum": ["custom"] },
+ "label": { "type": "string" },
+ "options": { "$ref": "custom_variable_options.json" }
+ },
+ "additionalProperties": false
+}
diff --git a/spec/fixtures/lib/gitlab/metrics/dashboard/schemas/custom_variable_options.json b/spec/fixtures/lib/gitlab/metrics/dashboard/schemas/custom_variable_options.json
new file mode 100644
index 00000000000..f351d3ba340
--- /dev/null
+++ b/spec/fixtures/lib/gitlab/metrics/dashboard/schemas/custom_variable_options.json
@@ -0,0 +1,11 @@
+{
+ "type": "object",
+ "required": ["values"],
+ "properties": {
+ "values": {
+ "type": "array",
+ "items": { "$ref": "custom_variable_values.json" }
+ }
+ },
+ "additionalProperties": false
+}
diff --git a/spec/fixtures/lib/gitlab/metrics/dashboard/schemas/custom_variable_values.json b/spec/fixtures/lib/gitlab/metrics/dashboard/schemas/custom_variable_values.json
new file mode 100644
index 00000000000..430d66a9691
--- /dev/null
+++ b/spec/fixtures/lib/gitlab/metrics/dashboard/schemas/custom_variable_values.json
@@ -0,0 +1,10 @@
+{
+ "type": "object",
+ "required": ["value"],
+ "properties": {
+ "value": { "type": "string" },
+ "text": { "type": "string" },
+ "default": { "type": "boolean" }
+ },
+ "additionalProperties": false
+}
diff --git a/spec/fixtures/lib/gitlab/metrics/dashboard/schemas/metric_label_values_variable_full_syntax.json b/spec/fixtures/lib/gitlab/metrics/dashboard/schemas/metric_label_values_variable_full_syntax.json
new file mode 100644
index 00000000000..6eb2c0e51e2
--- /dev/null
+++ b/spec/fixtures/lib/gitlab/metrics/dashboard/schemas/metric_label_values_variable_full_syntax.json
@@ -0,0 +1,12 @@
+{
+ "type": "object",
+ "required": [
+ "type", "options"
+ ],
+ "properties": {
+ "type": { "enum": "metric_label_values" },
+ "label": { "type": "string" },
+ "options": { "$ref": "metric_label_values_variable_options.json" }
+ },
+ "additionalProperties": false
+}
diff --git a/spec/fixtures/lib/gitlab/metrics/dashboard/schemas/metric_label_values_variable_options.json b/spec/fixtures/lib/gitlab/metrics/dashboard/schemas/metric_label_values_variable_options.json
new file mode 100644
index 00000000000..304372ed876
--- /dev/null
+++ b/spec/fixtures/lib/gitlab/metrics/dashboard/schemas/metric_label_values_variable_options.json
@@ -0,0 +1,12 @@
+{
+ "type": "object",
+ "required": [
+ "series_selector", "label", "prometheus_endpoint_path"
+ ],
+ "properties": {
+ "series_selector": { "type": "string" },
+ "label": { "type": "string" },
+ "prometheus_endpoint_path": { "type": "string" }
+ },
+ "additionalProperties": false
+}
diff --git a/spec/fixtures/lib/gitlab/metrics/dashboard/schemas/text_variable_full_syntax.json b/spec/fixtures/lib/gitlab/metrics/dashboard/schemas/text_variable_full_syntax.json
index f5090dc8c88..1818b2775f0 100644
--- a/spec/fixtures/lib/gitlab/metrics/dashboard/schemas/text_variable_full_syntax.json
+++ b/spec/fixtures/lib/gitlab/metrics/dashboard/schemas/text_variable_full_syntax.json
@@ -4,7 +4,7 @@
"type", "options"
],
"properties": {
- "type": { "type": "string" },
+ "type": { "enum": ["text"] },
"label": { "type": "string" },
"options": { "$ref": "text_variable_options.json" }
},
diff --git a/spec/fixtures/lib/gitlab/metrics/dashboard/schemas/variables.json b/spec/fixtures/lib/gitlab/metrics/dashboard/schemas/variables.json
index f3e0dd555a6..aec129111e0 100644
--- a/spec/fixtures/lib/gitlab/metrics/dashboard/schemas/variables.json
+++ b/spec/fixtures/lib/gitlab/metrics/dashboard/schemas/variables.json
@@ -4,7 +4,13 @@
"^[a-zA-Z0-9_]*$": {
"anyOf": [
{ "$ref": "text_variable_full_syntax.json" },
- { "type": "string" }
+ { "type": "string" },
+ {
+ "type": "array",
+ "items": { "type": "string" }
+ },
+ { "$ref": "custom_variable_full_syntax.json" },
+ { "$ref": "metric_label_values_variable_full_syntax.json" }
]
}
},
diff --git a/spec/fixtures/lib/kramdown/atlassian_document_format/blockquote.json b/spec/fixtures/lib/kramdown/atlassian_document_format/blockquote.json
new file mode 100644
index 00000000000..5ad414b43b9
--- /dev/null
+++ b/spec/fixtures/lib/kramdown/atlassian_document_format/blockquote.json
@@ -0,0 +1,24 @@
+{
+ "version": 1,
+ "type": "doc",
+ "content": [
+ {
+ "type": "blockquote",
+ "content": [
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "This is a sample quote"
+ },
+ {
+ "type": "text",
+ "text": " with lorem ipsum dolor sit amet..."
+ }
+ ]
+ }
+ ]
+ }
+ ]
+}
diff --git a/spec/fixtures/lib/kramdown/atlassian_document_format/blockquote.md b/spec/fixtures/lib/kramdown/atlassian_document_format/blockquote.md
new file mode 100644
index 00000000000..9564e11fc3b
--- /dev/null
+++ b/spec/fixtures/lib/kramdown/atlassian_document_format/blockquote.md
@@ -0,0 +1,2 @@
+> This is a sample quote with lorem ipsum dolor sit amet...
+
diff --git a/spec/fixtures/lib/kramdown/atlassian_document_format/bullet_list.json b/spec/fixtures/lib/kramdown/atlassian_document_format/bullet_list.json
new file mode 100644
index 00000000000..0e2b34b0790
--- /dev/null
+++ b/spec/fixtures/lib/kramdown/atlassian_document_format/bullet_list.json
@@ -0,0 +1,72 @@
+{
+ "version": 1,
+ "type": "doc",
+ "content": [
+ {
+ "type": "bulletList",
+ "content": [
+ {
+ "type": "listItem",
+ "content": [
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "Item 1"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "type": "listItem",
+ "content": [
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "Item 2",
+ "marks": [
+ {
+ "type": "strong"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "type": "bulletList",
+ "content": [
+ {
+ "type": "listItem",
+ "content": [
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "Sub-item 1"
+ }
+ ]
+ },
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "Sub-item 1 paragraph"
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ]
+}
diff --git a/spec/fixtures/lib/kramdown/atlassian_document_format/bullet_list.md b/spec/fixtures/lib/kramdown/atlassian_document_format/bullet_list.md
new file mode 100644
index 00000000000..b7de76b973f
--- /dev/null
+++ b/spec/fixtures/lib/kramdown/atlassian_document_format/bullet_list.md
@@ -0,0 +1,8 @@
+* Item 1
+
+* **Item 2**
+
+ * Sub-item 1
+
+ Sub-item 1 paragraph
+
diff --git a/spec/fixtures/lib/kramdown/atlassian_document_format/code_block.json b/spec/fixtures/lib/kramdown/atlassian_document_format/code_block.json
new file mode 100644
index 00000000000..007493bdf49
--- /dev/null
+++ b/spec/fixtures/lib/kramdown/atlassian_document_format/code_block.json
@@ -0,0 +1,89 @@
+{
+ "version": 1,
+ "type": "doc",
+ "content": [
+ {
+ "type": "codeBlock",
+ "attrs": {
+ "language": "javascript"
+ },
+ "content": [
+ {
+ "type": "text",
+ "text": "export function makeIssue({ parentIssue, project, users }) {\n\n const issueType = pickRandom(project.issueTypes)\n\n console.log(data)\n\n return data\n}"
+ }
+ ]
+ },
+ {
+ "type": "codeBlock",
+ "attrs": {
+ "language": "css"
+ },
+ "content": [
+ {
+ "type": "text",
+ "text": ".overflow { overflow: hidden; }"
+ }
+ ]
+ },
+ {
+ "type": "bulletList",
+ "content": [
+ {
+ "type": "listItem",
+ "content": [
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "Item 1"
+ }
+ ]
+ },
+ {
+ "type": "codeBlock",
+ "content": [
+ {
+ "type": "text",
+ "text": "public DemoClass()\n{\n // assign default value\n x = 0;\n}"
+ }
+ ]
+ },
+ {
+ "type": "orderedList",
+ "content": [
+ {
+ "type": "listItem",
+ "content": [
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "Number list Item 1"
+ }
+ ]
+ },
+ {
+ "type": "codeBlock",
+ "attrs": {
+ "language": "ruby"
+ },
+ "content": [
+ {
+ "type": "text",
+ "text": "def test\n # assign default value\n x = 0\nend"
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ]
+}
diff --git a/spec/fixtures/lib/kramdown/atlassian_document_format/code_block.md b/spec/fixtures/lib/kramdown/atlassian_document_format/code_block.md
new file mode 100644
index 00000000000..c89710afd36
--- /dev/null
+++ b/spec/fixtures/lib/kramdown/atlassian_document_format/code_block.md
@@ -0,0 +1,38 @@
+```javascript
+export function makeIssue({ parentIssue, project, users }) {
+
+ const issueType = pickRandom(project.issueTypes)
+
+ console.log(data)
+
+ return data
+}
+```
+
+<!-- -->
+
+```css
+.overflow { overflow: hidden; }
+```
+
+
+* Item 1
+
+ ```java
+ public DemoClass()
+ {
+ // assign default value
+ x = 0;
+ }
+ ```
+
+
+ 1. Number list Item 1
+
+ ```ruby
+ def test
+ # assign default value
+ x = 0
+ end
+ ```
+
diff --git a/spec/fixtures/lib/kramdown/atlassian_document_format/code_mark.json b/spec/fixtures/lib/kramdown/atlassian_document_format/code_mark.json
new file mode 100644
index 00000000000..884715709f7
--- /dev/null
+++ b/spec/fixtures/lib/kramdown/atlassian_document_format/code_mark.json
@@ -0,0 +1,24 @@
+{
+ "version": 1,
+ "type": "doc",
+ "content": [
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "This is some "
+ },
+ {
+ "type": "text",
+ "text": "inline code",
+ "marks": [
+ {
+ "type": "code"
+ }
+ ]
+ }
+ ]
+ }
+ ]
+}
diff --git a/spec/fixtures/lib/kramdown/atlassian_document_format/code_mark.md b/spec/fixtures/lib/kramdown/atlassian_document_format/code_mark.md
new file mode 100644
index 00000000000..a52d00bfd38
--- /dev/null
+++ b/spec/fixtures/lib/kramdown/atlassian_document_format/code_mark.md
@@ -0,0 +1,2 @@
+This is some `inline code`
+
diff --git a/spec/fixtures/lib/kramdown/atlassian_document_format/complex_document.json b/spec/fixtures/lib/kramdown/atlassian_document_format/complex_document.json
new file mode 100644
index 00000000000..c69ad6b4260
--- /dev/null
+++ b/spec/fixtures/lib/kramdown/atlassian_document_format/complex_document.json
@@ -0,0 +1,964 @@
+{
+ "version": 1,
+ "type": "doc",
+ "content": [
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "This is a complex issue…and this is normal text"
+ }
+ ]
+ },
+ {
+ "type": "paragraph",
+ "content": []
+ },
+ {
+ "type": "rule"
+ },
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "Color - Dark Gray"
+ }
+ ]
+ },
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "Color - "
+ },
+ {
+ "type": "text",
+ "text": "Light Gray",
+ "marks": [
+ {
+ "type": "textColor",
+ "attrs": {
+ "color": "#97a0af"
+ }
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "Color - "
+ },
+ {
+ "type": "text",
+ "text": "Purple",
+ "marks": [
+ {
+ "type": "textColor",
+ "attrs": {
+ "color": "#6554c0"
+ }
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "Color - "
+ },
+ {
+ "type": "text",
+ "text": "Teal",
+ "marks": [
+ {
+ "type": "textColor",
+ "attrs": {
+ "color": "#00b8d9"
+ }
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "Color - "
+ },
+ {
+ "type": "text",
+ "text": "Green",
+ "marks": [
+ {
+ "type": "textColor",
+ "attrs": {
+ "color": "#36b37e"
+ }
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "Color - "
+ },
+ {
+ "type": "text",
+ "text": "Red",
+ "marks": [
+ {
+ "type": "textColor",
+ "attrs": {
+ "color": "#ff5630"
+ }
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "Color - "
+ },
+ {
+ "type": "text",
+ "text": "Orange",
+ "marks": [
+ {
+ "type": "textColor",
+ "attrs": {
+ "color": "#ff991f"
+ }
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "type": "paragraph",
+ "content": []
+ },
+ {
+ "type": "rule"
+ },
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "inlineCard",
+ "attrs": {
+ "url": "https://gitlab-jira.atlassian.net/browse/DEMO-1"
+ }
+ },
+ {
+ "type": "text",
+ "text": " "
+ }
+ ]
+ },
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "inlineCard",
+ "attrs": {
+ "data": {
+ "@context": "https://json-ld.org/contexts/person.jsonld",
+ "@id": "http://dbpedia.org/resource/John_Lennon",
+ "name": "John Lennon",
+ "born": "1940-10-09",
+ "spouse": "http://dbpedia.org/resource/Cynthia_Lennon"
+ }
+ }
+ },
+ {
+ "type": "text",
+ "text": " "
+ }
+ ]
+ },
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "External Link",
+ "marks": [
+ {
+ "type": "link",
+ "attrs": {
+ "href": "https://gitlab.com/gitlab-org/gitlab/-/merge_requests/25718"
+ }
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "type": "paragraph",
+ "content": []
+ },
+ {
+ "type": "rule"
+ },
+ {
+ "type": "paragraph",
+ "content": []
+ },
+ {
+ "type": "blockquote",
+ "content": [
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "This is a block quote"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "type": "paragraph",
+ "content": []
+ },
+ {
+ "type": "panel",
+ "attrs": {
+ "panelType": "success"
+ },
+ "content": [
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "Success info panel"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "type": "panel",
+ "attrs": {
+ "panelType": "info"
+ },
+ "content": [
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "Info info panel"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "type": "panel",
+ "attrs": {
+ "panelType": "note"
+ },
+ "content": [
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "Note info panel"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "type": "panel",
+ "attrs": {
+ "panelType": "warning"
+ },
+ "content": [
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "Warning info panel"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "type": "panel",
+ "attrs": {
+ "panelType": "error"
+ },
+ "content": [
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "Error info panel"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "type": "paragraph",
+ "content": []
+ },
+ {
+ "type": "paragraph",
+ "content": []
+ },
+ {
+ "type": "rule"
+ },
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "mention",
+ "attrs": {
+ "id": "5e32f803e127810e82875bc1",
+ "text": "jhope"
+ }
+ },
+ {
+ "type": "text",
+ "text": " what up"
+ }
+ ]
+ },
+ {
+ "type": "paragraph",
+ "content": []
+ },
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "emoji",
+ "attrs": {
+ "shortName": ":grinning:",
+ "id": "1f600",
+ "text": "\uD83D\uDE00"
+ }
+ },
+ {
+ "type": "text",
+ "text": " "
+ },
+ {
+ "type": "emoji",
+ "attrs": {
+ "shortName": ":rofl:",
+ "id": "1f923",
+ "text": "\uD83E\uDD23"
+ }
+ },
+ {
+ "type": "text",
+ "text": " "
+ },
+ {
+ "type": "emoji",
+ "attrs": {
+ "shortName": ":partying_face:",
+ "id": "1f973",
+ "text": "\uD83E\uDD73"
+ }
+ },
+ {
+ "type": "text",
+ "text": " "
+ },
+ {
+ "type": "emoji",
+ "attrs": {
+ "shortName": ":heart_eyes:",
+ "id": "1f60d",
+ "text": "\uD83D\uDE0D"
+ }
+ },
+ {
+ "type": "text",
+ "text": " "
+ }
+ ]
+ },
+ {
+ "type": "paragraph",
+ "content": []
+ },
+ {
+ "type": "table",
+ "attrs": {
+ "isNumberColumnEnabled": false,
+ "layout": "default"
+ },
+ "content": [
+ {
+ "type": "tableRow",
+ "content": [
+ {
+ "type": "tableHeader",
+ "attrs": {},
+ "content": [
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "Col 1 Row 1",
+ "marks": [
+ {
+ "type": "strong"
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "type": "tableHeader",
+ "attrs": {},
+ "content": [
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "Col 2 Row 1",
+ "marks": [
+ {
+ "type": "strong"
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "type": "tableHeader",
+ "attrs": {},
+ "content": [
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "Col 3 Row 1",
+ "marks": [
+ {
+ "type": "strong"
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "type": "tableRow",
+ "content": [
+ {
+ "type": "tableCell",
+ "attrs": {},
+ "content": [
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "Col 1 Row 2"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "type": "tableCell",
+ "attrs": {},
+ "content": [
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "Col 2 Row 2"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "type": "tableCell",
+ "attrs": {},
+ "content": [
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "Col 3 Row 2"
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "type": "tableRow",
+ "content": [
+ {
+ "type": "tableCell",
+ "attrs": {},
+ "content": [
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "Col 1 Row 3"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "type": "tableCell",
+ "attrs": {},
+ "content": [
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "Col 2 Row 3"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "type": "tableCell",
+ "attrs": {},
+ "content": [
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "Col 3 Row 3"
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "type": "heading",
+ "attrs": {
+ "level": 1
+ },
+ "content": [
+ {
+ "type": "text",
+ "text": "Header 1"
+ }
+ ]
+ },
+ {
+ "type": "heading",
+ "attrs": {
+ "level": 2
+ },
+ "content": [
+ {
+ "type": "text",
+ "text": "Header 2"
+ }
+ ]
+ },
+ {
+ "type": "heading",
+ "attrs": {
+ "level": 3
+ },
+ "content": [
+ {
+ "type": "text",
+ "text": "Header 3"
+ }
+ ]
+ },
+ {
+ "type": "heading",
+ "attrs": {
+ "level": 4
+ },
+ "content": [
+ {
+ "type": "text",
+ "text": "Header 4"
+ }
+ ]
+ },
+ {
+ "type": "heading",
+ "attrs": {
+ "level": 5
+ },
+ "content": [
+ {
+ "type": "text",
+ "text": "Header 5"
+ }
+ ]
+ },
+ {
+ "type": "heading",
+ "attrs": {
+ "level": 6
+ },
+ "content": [
+ {
+ "type": "text",
+ "text": "Header 6"
+ }
+ ]
+ },
+ {
+ "type": "paragraph",
+ "content": []
+ },
+ {
+ "type": "bulletList",
+ "content": [
+ {
+ "type": "listItem",
+ "content": [
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "Bullet point list item 1"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "type": "listItem",
+ "content": [
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "Bullet point list Item 2"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "type": "listItem",
+ "content": [
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "Bullet point list Item 3"
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "type": "paragraph",
+ "content": []
+ },
+ {
+ "type": "orderedList",
+ "content": [
+ {
+ "type": "listItem",
+ "content": [
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "Number list Item 1"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "type": "listItem",
+ "content": [
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "Number list item 2"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "type": "listItem",
+ "content": [
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "Number list item 3"
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "type": "paragraph",
+ "content": []
+ },
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "Underline",
+ "marks": [
+ {
+ "type": "underline"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "Superscript",
+ "marks": [
+ {
+ "type": "subsup",
+ "attrs": {
+ "type": "sup"
+ }
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "Subscript",
+ "marks": [
+ {
+ "type": "subsup",
+ "attrs": {
+ "type": "sub"
+ }
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "Bold",
+ "marks": [
+ {
+ "type": "strong"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "Italic",
+ "marks": [
+ {
+ "type": "em"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "Strikethrough",
+ "marks": [
+ {
+ "type": "strike"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "type": "codeBlock",
+ "attrs": {
+ "language": "javascript"
+ },
+ "content": [
+ {
+ "type": "text",
+ "text": "export function makeIssue({ parentIssue, project, users }) {\n \n const issueType = pickRandom(project.issueTypes)\n\n let data = {\n fields: {\n summary: faker.lorem.sentence(),\n issuetype: {\n id: issueType.id\n },\n project: {\n id: project.id\n },\n reporter: {\n id: pickRandom(users)\n }\n }\n }\n\n if (issueType.subtask) {\n data = {\n parent: {\n key: parentIssue\n }\n }\n }\n\n console.log(data)\n\n return data\n}"
+ }
+ ]
+ },
+ {
+ "type": "mediaSingle",
+ "attrs": {
+ "layout": "center"
+ },
+ "content": [
+ {
+ "type": "media",
+ "attrs": {
+ "id": "79411c6b-50e0-477f-b4ed-ac3a5887750c",
+ "type": "file",
+ "collection": "jira-10050-field-description",
+ "width": 400,
+ "height": 400
+ }
+ }
+ ]
+ },
+ {
+ "type": "paragraph",
+ "content": []
+ },
+ {
+ "type": "mediaSingle",
+ "attrs": {
+ "layout": "center"
+ },
+ "content": [
+ {
+ "type": "media",
+ "attrs": {
+ "id": "6a5b48c6-70bd-4747-9ac8-a9abc9adb1f4",
+ "type": "file",
+ "collection": "jira-10050-field-description",
+ "width": 1280,
+ "height": 789
+ }
+ }
+ ]
+ },
+ {
+ "type": "mediaSingle",
+ "attrs": {
+ "layout": "center"
+ },
+ "content": [
+ {
+ "type": "media",
+ "attrs": {
+ "id": "e818a88d-9185-4a7f-8882-18339a0f0966",
+ "type": "file",
+ "collection": "jira-10050-field-description",
+ "width": 1280,
+ "height": 598
+ }
+ }
+ ]
+ },
+ {
+ "type": "paragraph",
+ "content": []
+ },
+ {
+ "type": "paragraph",
+ "content": []
+ },
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "blob:"
+ },
+ {
+ "type": "text",
+ "text": "https://gitlab-jira.atlassian.net/5eb8e93b-7b15-446f-82d9-9d82ad7b8ea5#media-blob-url=true&id=572b2c1b-1b38-44ba-904a-649ee1861917&collection=upload-user-collection-426749591&contextId=10042&mimeType=image%2Fpng&name=import-jira-issues.png&size=294408",
+ "marks": [
+ {
+ "type": "link",
+ "attrs": {
+ "href": "https://gitlab-jira.atlassian.net/5eb8e93b-7b15-446f-82d9-9d82ad7b8ea5#media-blob-url=true&id=572b2c1b-1b38-44ba-904a-649ee1861917&collection=upload-user-collection-426749591&contextId=10042&mimeType=image%2Fpng&name=import-jira-issues.png&size=294408"
+ }
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "type": "paragraph",
+ "content": []
+ }
+ ]
+}
diff --git a/spec/fixtures/lib/kramdown/atlassian_document_format/complex_document.md b/spec/fixtures/lib/kramdown/atlassian_document_format/complex_document.md
new file mode 100644
index 00000000000..d4faf84e971
--- /dev/null
+++ b/spec/fixtures/lib/kramdown/atlassian_document_format/complex_document.md
@@ -0,0 +1,223 @@
+This is a complex issue…and this is normal text
+
+
+
+* * *
+
+Color - Dark Gray
+
+Color - <span color="#97a0af">Light Gray</span>
+
+Color - <span color="#6554c0">Purple</span>
+
+Color - <span color="#00b8d9">Teal</span>
+
+Color - <span color="#36b37e">Green</span>
+
+Color - <span color="#ff5630">Red</span>
+
+Color - <span color="#ff991f">Orange</span>
+
+
+
+* * *
+
+[https://gitlab-jira.atlassian.net/browse/DEMO-1][1]
+
+`adf-inlineCard:
+{"@context"=>"https://json-ld.org/contexts/person.jsonld",
+"@id"=>"http://dbpedia.org/resource/John_Lennon", "name"=>"John Lennon",
+"born"=>"1940-10-09",
+"spouse"=>"http://dbpedia.org/resource/Cynthia_Lennon"}`
+
+[External Link][2]
+
+
+
+* * *
+
+
+
+> This is a block quote
+
+
+
+> \:white\_check\_mark: Success info panel
+
+> \:information\_source: Info info panel
+
+> \:notepad\_spiral: Note info panel
+
+> \:warning: Warning info panel
+
+> \:octagonal\_sign: Error info panel
+
+
+
+
+
+* * *
+
+@adf-mention:jhope what up
+
+
+
+😀 🤣 🥳 ðŸ˜
+
+
+
+<table>
+<tbody>
+<tr>
+<th>
+
+**Col 1 Row 1**
+
+</th>
+<th>
+
+**Col 2 Row 1**
+
+</th>
+<th>
+
+**Col 3 Row 1**
+
+</th>
+</tr>
+<tr>
+<td>
+
+Col 1 Row 2
+
+</td>
+<td>
+
+Col 2 Row 2
+
+</td>
+<td>
+
+Col 3 Row 2
+
+</td>
+</tr>
+<tr>
+<td>
+
+Col 1 Row 3
+
+</td>
+<td>
+
+Col 2 Row 3
+
+</td>
+<td>
+
+Col 3 Row 3
+
+</td>
+</tr>
+</tbody>
+</table>
+
+
+# Header 1
+
+## Header 2
+
+### Header 3
+
+#### Header 4
+
+##### Header 5
+
+###### Header 6
+
+
+
+* Bullet point list item 1
+
+* Bullet point list Item 2
+
+* Bullet point list Item 3
+
+
+
+1. Number list Item 1
+
+2. Number list item 2
+
+3. Number list item 3
+
+
+
+<u>Underline</u>
+
+<sup>Superscript</sup>
+
+<sub>Subscript</sub>
+
+**Bold**
+
+*Italic*
+
+<del>Strikethrough</del>
+
+```javascript
+export function makeIssue({ parentIssue, project, users }) {
+
+ const issueType = pickRandom(project.issueTypes)
+
+ let data = {
+ fields: {
+ summary: faker.lorem.sentence(),
+ issuetype: {
+ id: issueType.id
+ },
+ project: {
+ id: project.id
+ },
+ reporter: {
+ id: pickRandom(users)
+ }
+ }
+ }
+
+ if (issueType.subtask) {
+ data = {
+ parent: {
+ key: parentIssue
+ }
+ }
+ }
+
+ console.log(data)
+
+ return data
+}
+```
+
+
+![jira-10050-field-description](adf-media://79411c6b-50e0-477f-b4ed-ac3a5887750c)
+
+
+
+![jira-10050-field-description](adf-media://6a5b48c6-70bd-4747-9ac8-a9abc9adb1f4)
+
+![jira-10050-field-description](adf-media://e818a88d-9185-4a7f-8882-18339a0f0966)
+
+
+
+
+
+blob:[https://gitlab-jira.atlassian.net/5eb8e93b-7b15-446f-82d9-9d82ad7b8ea5#media-blob-url=true&id=572b2c1b-1b38-44ba-904a-649ee1861917&collection=upload-user-collection-426749591&contextId=10042&mimeType=image%2Fpng&name=import-jira-issues.png&size=294408][3]
+
+
+
+
+
+[1]: https://gitlab-jira.atlassian.net/browse/DEMO-1
+[2]: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/25718
+[3]: https://gitlab-jira.atlassian.net/5eb8e93b-7b15-446f-82d9-9d82ad7b8ea5#media-blob-url=true&id=572b2c1b-1b38-44ba-904a-649ee1861917&collection=upload-user-collection-426749591&contextId=10042&mimeType=image%2Fpng&name=import-jira-issues.png&size=294408
diff --git a/spec/fixtures/lib/kramdown/atlassian_document_format/emoji.json b/spec/fixtures/lib/kramdown/atlassian_document_format/emoji.json
new file mode 100644
index 00000000000..3a7944d2509
--- /dev/null
+++ b/spec/fixtures/lib/kramdown/atlassian_document_format/emoji.json
@@ -0,0 +1,66 @@
+{
+ "version": 1,
+ "type": "doc",
+ "content": [
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "Grinning with unicode "
+ },
+ {
+ "type": "emoji",
+ "attrs": {
+ "shortName": ":grinning:",
+ "text": "😀"
+ }
+ },
+ {
+ "type": "text",
+ "text": " and heart eyes with raw unicode "
+ },
+ {
+ "type": "emoji",
+ "attrs": {
+ "shortName": ":heart_eyes:",
+ "id": "1f60d",
+ "text": "\uD83D\uDE0D"
+ }
+ },
+ {
+ "type": "text",
+ "text": ", non-standard Atlassian "
+ },
+ {
+ "type": "emoji",
+ "attrs": {
+ "shortName": ":awthanks:",
+ "id": "atlassian-awthanks",
+ "text": ":awthanks:"
+ }
+ },
+ {
+ "type": "text",
+ "text": ", non-standard customer emoji "
+ },
+ {
+ "type": "emoji",
+ "attrs": {
+ "shortName": ":thumbsup::skin-tone-2:"
+ }
+ },
+ {
+ "type": "text",
+ "text": ", and invalid is ignored "
+ },
+ {
+ "type": "emoji",
+ "attrs": {
+ "id": "1f44d"
+ }
+ }
+ ]
+ }
+ ]
+}
diff --git a/spec/fixtures/lib/kramdown/atlassian_document_format/emoji.md b/spec/fixtures/lib/kramdown/atlassian_document_format/emoji.md
new file mode 100644
index 00000000000..11adf377188
--- /dev/null
+++ b/spec/fixtures/lib/kramdown/atlassian_document_format/emoji.md
@@ -0,0 +1,4 @@
+Grinning with unicode 😀 and heart eyes with raw unicode ðŸ˜, non-standard
+Atlassian :awthanks:, non-standard customer emoji
+:thumbsup::skin-tone-2:, and invalid is ignored
+
diff --git a/spec/fixtures/lib/kramdown/atlassian_document_format/hard_break.json b/spec/fixtures/lib/kramdown/atlassian_document_format/hard_break.json
new file mode 100644
index 00000000000..564570c016e
--- /dev/null
+++ b/spec/fixtures/lib/kramdown/atlassian_document_format/hard_break.json
@@ -0,0 +1,22 @@
+{
+ "version": 1,
+ "type": "doc",
+ "content": [
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "This is a sample paragraph"
+ },
+ {
+ "type": "hardBreak"
+ },
+ {
+ "type": "text",
+ "text": "with lorem ipsum dolor sit amet..."
+ }
+ ]
+ }
+ ]
+}
diff --git a/spec/fixtures/lib/kramdown/atlassian_document_format/hard_break.md b/spec/fixtures/lib/kramdown/atlassian_document_format/hard_break.md
new file mode 100644
index 00000000000..d8c4a8dca53
--- /dev/null
+++ b/spec/fixtures/lib/kramdown/atlassian_document_format/hard_break.md
@@ -0,0 +1,3 @@
+This is a sample paragraph
+with lorem ipsum dolor sit amet...
+
diff --git a/spec/fixtures/lib/kramdown/atlassian_document_format/heading.json b/spec/fixtures/lib/kramdown/atlassian_document_format/heading.json
new file mode 100644
index 00000000000..dbc756f3603
--- /dev/null
+++ b/spec/fixtures/lib/kramdown/atlassian_document_format/heading.json
@@ -0,0 +1,91 @@
+{
+ "version": 1,
+ "type": "doc",
+ "content": [
+ {
+ "type": "heading",
+ "attrs": {
+ "level": 1
+ },
+ "content": [
+ {
+ "type": "text",
+ "text": "Header 1"
+ }
+ ]
+ },
+ {
+ "type": "heading",
+ "attrs": {
+ "level": 2
+ },
+ "content": [
+ {
+ "type": "text",
+ "text": "Header 2",
+ "marks": [
+ {
+ "type": "strong"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "type": "heading",
+ "attrs": {
+ "level": 3
+ },
+ "content": [
+ {
+ "type": "text",
+ "text": "Header 3"
+ }
+ ]
+ },
+ {
+ "type": "heading",
+ "attrs": {
+ "level": 4
+ },
+ "content": [
+ {
+ "type": "text",
+ "text": "Header 4",
+ "marks": [
+ {
+ "type": "textColor",
+ "attrs": {
+ "color": "#ff6347"
+ }
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "type": "heading",
+ "attrs": {
+ "level": 5
+ },
+ "content": [
+ {
+ "type": "text",
+ "text": "Header 5"
+ }
+ ]
+ },
+ {
+ "type": "heading",
+ "attrs": {
+ "level": 6
+ },
+ "content": [
+ {
+ "type": "text",
+ "text": "Header 6"
+ }
+ ]
+ }
+ ]
+}
diff --git a/spec/fixtures/lib/kramdown/atlassian_document_format/heading.md b/spec/fixtures/lib/kramdown/atlassian_document_format/heading.md
new file mode 100644
index 00000000000..073ae1fdef4
--- /dev/null
+++ b/spec/fixtures/lib/kramdown/atlassian_document_format/heading.md
@@ -0,0 +1,12 @@
+# Header 1
+
+## **Header 2**
+
+### Header 3
+
+#### <span color="#ff6347">Header 4</span>
+
+##### Header 5
+
+###### Header 6
+
diff --git a/spec/fixtures/lib/kramdown/atlassian_document_format/inline_card.json b/spec/fixtures/lib/kramdown/atlassian_document_format/inline_card.json
new file mode 100644
index 00000000000..8b7428debc2
--- /dev/null
+++ b/spec/fixtures/lib/kramdown/atlassian_document_format/inline_card.json
@@ -0,0 +1,46 @@
+{
+ "version": 1,
+ "type": "doc",
+ "content": [
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "An inline card (url): "
+ },
+ {
+ "type": "inlineCard",
+ "attrs": {
+ "url": "https://gitlab-jira.atlassian.net/browse/DEMO-1"
+ }
+ }
+ ]
+ },
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "Some prelude text "
+ },
+ {
+ "type": "inlineCard",
+ "attrs": {
+ "data": {
+ "@context": "https://json-ld.org/contexts/person.jsonld",
+ "@id": "http://dbpedia.org/resource/John_Lennon",
+ "name": "John Lennon",
+ "born": "1940-10-09",
+ "spouse": "http://dbpedia.org/resource/Cynthia_Lennon"
+ }
+ }
+ },
+ {
+ "type": "text",
+ "text": " some following text"
+ }
+ ]
+ }
+ ]
+}
diff --git a/spec/fixtures/lib/kramdown/atlassian_document_format/inline_card.md b/spec/fixtures/lib/kramdown/atlassian_document_format/inline_card.md
new file mode 100644
index 00000000000..655102941a7
--- /dev/null
+++ b/spec/fixtures/lib/kramdown/atlassian_document_format/inline_card.md
@@ -0,0 +1,13 @@
+An inline card (url):
+[https://gitlab-jira.atlassian.net/browse/DEMO-1][1]
+
+Some prelude text `adf-inlineCard:
+{"@context"=>"https://json-ld.org/contexts/person.jsonld",
+"@id"=>"http://dbpedia.org/resource/John_Lennon", "name"=>"John Lennon",
+"born"=>"1940-10-09",
+"spouse"=>"http://dbpedia.org/resource/Cynthia_Lennon"}` some following
+text
+
+
+
+[1]: https://gitlab-jira.atlassian.net/browse/DEMO-1
diff --git a/spec/fixtures/lib/kramdown/atlassian_document_format/invalid_json.json b/spec/fixtures/lib/kramdown/atlassian_document_format/invalid_json.json
new file mode 100644
index 00000000000..6305592d175
--- /dev/null
+++ b/spec/fixtures/lib/kramdown/atlassian_document_format/invalid_json.json
@@ -0,0 +1,16 @@
+{
+ "version": 1,
+ "type": "doc",
+ "content": [
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "This is a second paragraph"
+ }
+ ]
+ }
+ ]
+}
+}
diff --git a/spec/fixtures/lib/kramdown/atlassian_document_format/invalid_no_doc.json b/spec/fixtures/lib/kramdown/atlassian_document_format/invalid_no_doc.json
new file mode 100644
index 00000000000..c9f10eeeb79
--- /dev/null
+++ b/spec/fixtures/lib/kramdown/atlassian_document_format/invalid_no_doc.json
@@ -0,0 +1,13 @@
+{
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "This is a sample paragraph"
+ },
+ {
+ "type": "text",
+ "text": " with lorem ipsum dolor sit amet..."
+ }
+ ]
+}
diff --git a/spec/fixtures/lib/kramdown/atlassian_document_format/invalid_node_type.json b/spec/fixtures/lib/kramdown/atlassian_document_format/invalid_node_type.json
new file mode 100644
index 00000000000..ebb74624333
--- /dev/null
+++ b/spec/fixtures/lib/kramdown/atlassian_document_format/invalid_node_type.json
@@ -0,0 +1,28 @@
+{
+ "version": 1,
+ "type": "doc",
+ "content": [
+ {
+ "type": "invalid",
+ "content": [
+ {
+ "type": "text",
+ "text": "This is a sample paragraph"
+ },
+ {
+ "type": "text",
+ "text": " with lorem ipsum dolor sit amet..."
+ }
+ ]
+ },
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "This is a second paragraph"
+ }
+ ]
+ }
+ ]
+}
diff --git a/spec/fixtures/lib/kramdown/atlassian_document_format/link_mark.json b/spec/fixtures/lib/kramdown/atlassian_document_format/link_mark.json
new file mode 100644
index 00000000000..bf6facf1b05
--- /dev/null
+++ b/spec/fixtures/lib/kramdown/atlassian_document_format/link_mark.json
@@ -0,0 +1,44 @@
+{
+ "version": 1,
+ "type": "doc",
+ "content": [
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "This is a "
+ },
+ {
+ "type": "text",
+ "text": "link without title",
+ "marks": [
+ {
+ "type": "link",
+ "attrs": {
+ "href": "http://example.com"
+ }
+ }
+ ]
+ },
+ {
+ "type": "text",
+ "text": " and a "
+ },
+ {
+ "type": "text",
+ "text": "link with title",
+ "marks": [
+ {
+ "type": "link",
+ "attrs": {
+ "href": "http://example.net",
+ "title": "Link Title"
+ }
+ }
+ ]
+ }
+ ]
+ }
+ ]
+}
diff --git a/spec/fixtures/lib/kramdown/atlassian_document_format/link_mark.md b/spec/fixtures/lib/kramdown/atlassian_document_format/link_mark.md
new file mode 100644
index 00000000000..cd42b2297cf
--- /dev/null
+++ b/spec/fixtures/lib/kramdown/atlassian_document_format/link_mark.md
@@ -0,0 +1,6 @@
+This is a [link without title][1] and a [link with title][2]
+
+
+
+[1]: http://example.com
+[2]: http://example.net "Link Title"
diff --git a/spec/fixtures/lib/kramdown/atlassian_document_format/media_group.json b/spec/fixtures/lib/kramdown/atlassian_document_format/media_group.json
new file mode 100644
index 00000000000..a51da964e0e
--- /dev/null
+++ b/spec/fixtures/lib/kramdown/atlassian_document_format/media_group.json
@@ -0,0 +1,59 @@
+{
+ "version": 1,
+ "type": "doc",
+ "content": [
+ {
+ "type": "mediaGroup",
+ "content": [
+ {
+ "type": "media",
+ "attrs": {
+ "id": "79411c6b-50e0-477f-b4ed-ac3a5887750c",
+ "type": "file",
+ "collection": "jira-10050-field-description",
+ "width": 400,
+ "height": 400
+ }
+ },
+ {
+ "type": "media",
+ "attrs": {
+ "id": "79411c6b-50e0-477f-b4ed-abcde",
+ "type": "file",
+ "collection": "jira-10050-field-description",
+ "width": 400,
+ "height": 400
+ }
+ }
+ ]
+ },
+ {
+ "type": "rule"
+ },
+ {
+ "type": "mediaGroup",
+ "content": [
+ {
+ "type": "media",
+ "attrs": {
+ "id": "79411c6b-50e0-477f-b4ed-ac3a5887750c-link1",
+ "type": "link",
+ "collection": "jira-10050-field-description-links",
+ "width": 400,
+ "height": 400
+ }
+ },
+ {
+ "type": "media",
+ "attrs": {
+ "id": "79411c6b-50e0-477f-b4ed-ac3a5887750c-link2",
+ "type": "link",
+ "collection": "jira-10050-field-description-links",
+ "width": 400,
+ "height": 400
+ }
+ }
+ ]
+ }
+ ]
+}
diff --git a/spec/fixtures/lib/kramdown/atlassian_document_format/media_group.md b/spec/fixtures/lib/kramdown/atlassian_document_format/media_group.md
new file mode 100644
index 00000000000..7b602d8fc2b
--- /dev/null
+++ b/spec/fixtures/lib/kramdown/atlassian_document_format/media_group.md
@@ -0,0 +1,10 @@
+* ![jira-10050-field-description](adf-media://79411c6b-50e0-477f-b4ed-ac3a5887750c)
+
+* ![jira-10050-field-description](adf-media://79411c6b-50e0-477f-b4ed-abcde)
+
+* * *
+
+* [adf-media://79411c6b-50e0-477f-b4ed-ac3a5887750c-link1](adf-media://79411c6b-50e0-477f-b4ed-ac3a5887750c-link1)
+
+* [adf-media://79411c6b-50e0-477f-b4ed-ac3a5887750c-link2](adf-media://79411c6b-50e0-477f-b4ed-ac3a5887750c-link2)
+
diff --git a/spec/fixtures/lib/kramdown/atlassian_document_format/media_single.json b/spec/fixtures/lib/kramdown/atlassian_document_format/media_single.json
new file mode 100644
index 00000000000..0ecf2dcf03d
--- /dev/null
+++ b/spec/fixtures/lib/kramdown/atlassian_document_format/media_single.json
@@ -0,0 +1,42 @@
+{
+ "version": 1,
+ "type": "doc",
+ "content": [
+ {
+ "type": "mediaSingle",
+ "attrs": {
+ "layout": "center"
+ },
+ "content": [
+ {
+ "type": "media",
+ "attrs": {
+ "id": "79411c6b-50e0-477f-b4ed-ac3a5887750c",
+ "type": "file",
+ "collection": "jira-10050-field-description",
+ "width": 400,
+ "height": 400
+ }
+ }
+ ]
+ },
+ {
+ "type": "mediaSingle",
+ "attrs": {
+ "layout": "center"
+ },
+ "content": [
+ {
+ "type": "media",
+ "attrs": {
+ "id": "79411c6b-50e0-477f-b4ed-abcd",
+ "type": "file",
+ "collection": "another-jira-10050-field-description",
+ "width": 400,
+ "height": 400
+ }
+ }
+ ]
+ }
+ ]
+}
diff --git a/spec/fixtures/lib/kramdown/atlassian_document_format/media_single.md b/spec/fixtures/lib/kramdown/atlassian_document_format/media_single.md
new file mode 100644
index 00000000000..5ed1bbb55e1
--- /dev/null
+++ b/spec/fixtures/lib/kramdown/atlassian_document_format/media_single.md
@@ -0,0 +1,4 @@
+![jira-10050-field-description](adf-media://79411c6b-50e0-477f-b4ed-ac3a5887750c)
+
+![another-jira-10050-field-description](adf-media://79411c6b-50e0-477f-b4ed-abcd)
+
diff --git a/spec/fixtures/lib/kramdown/atlassian_document_format/mention.json b/spec/fixtures/lib/kramdown/atlassian_document_format/mention.json
new file mode 100644
index 00000000000..81a087c57d9
--- /dev/null
+++ b/spec/fixtures/lib/kramdown/atlassian_document_format/mention.json
@@ -0,0 +1,44 @@
+{
+ "version": 1,
+ "type": "doc",
+ "content": [
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "Mentioning "
+ },
+ {
+ "type": "mention",
+ "attrs": {
+ "id": "ABCDE-ABCDE-ABCDE-ABCDE",
+ "text": "@testuser",
+ "userType": "APP"
+ }
+ }
+ ]
+ },
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "Mentioning "
+ },
+ {
+ "type": "mention",
+ "attrs": {
+ "id": "ABCDE-ABCDE-ABCDE-ABCDE",
+ "text": "@test user",
+ "userType": "APP"
+ }
+ },
+ {
+ "type": "text",
+ "text": " with space in user name"
+ }
+ ]
+ }
+ ]
+}
diff --git a/spec/fixtures/lib/kramdown/atlassian_document_format/mention.md b/spec/fixtures/lib/kramdown/atlassian_document_format/mention.md
new file mode 100644
index 00000000000..f22c6421d8c
--- /dev/null
+++ b/spec/fixtures/lib/kramdown/atlassian_document_format/mention.md
@@ -0,0 +1,4 @@
+Mentioning @adf-mention:testuser
+
+Mentioning @adf-mention:\"test user\" with space in user name
+
diff --git a/spec/fixtures/lib/kramdown/atlassian_document_format/ordered_list.json b/spec/fixtures/lib/kramdown/atlassian_document_format/ordered_list.json
new file mode 100644
index 00000000000..71d638c6a38
--- /dev/null
+++ b/spec/fixtures/lib/kramdown/atlassian_document_format/ordered_list.json
@@ -0,0 +1,170 @@
+{
+ "version": 1,
+ "type": "doc",
+ "content": [
+ {
+ "type": "orderedList",
+ "content": [
+ {
+ "type": "listItem",
+ "content": [
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "Number list Item 1"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "type": "listItem",
+ "content": [
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "Number list item 2"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "type": "listItem",
+ "content": [
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "Number list item 3"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "type": "listItem",
+ "content": [
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "Number list item 4"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "type": "listItem",
+ "content": [
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "Number list item 5"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "type": "listItem",
+ "content": [
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "Number list item 6"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "type": "listItem",
+ "content": [
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "Number list item 7"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "type": "listItem",
+ "content": [
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "Number list item 8"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "type": "listItem",
+ "content": [
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "Number list item 9"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "type": "listItem",
+ "content": [
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "Number list item 10"
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "type": "orderedList",
+ "content": [
+ {
+ "type": "listItem",
+ "content": [
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "Another list"
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ]
+}
diff --git a/spec/fixtures/lib/kramdown/atlassian_document_format/ordered_list.md b/spec/fixtures/lib/kramdown/atlassian_document_format/ordered_list.md
new file mode 100644
index 00000000000..2c1f45cd810
--- /dev/null
+++ b/spec/fixtures/lib/kramdown/atlassian_document_format/ordered_list.md
@@ -0,0 +1,25 @@
+1. Number list Item 1
+
+2. Number list item 2
+
+3. Number list item 3
+
+4. Number list item 4
+
+5. Number list item 5
+
+6. Number list item 6
+
+7. Number list item 7
+
+8. Number list item 8
+
+9. Number list item 9
+
+10. Number list item 10
+<!-- -->
+
+1. Another list
+
+<!-- -->
+
diff --git a/spec/fixtures/lib/kramdown/atlassian_document_format/panel.json b/spec/fixtures/lib/kramdown/atlassian_document_format/panel.json
new file mode 100644
index 00000000000..e3982d23a9e
--- /dev/null
+++ b/spec/fixtures/lib/kramdown/atlassian_document_format/panel.json
@@ -0,0 +1,117 @@
+{
+ "version": 1,
+ "type": "doc",
+ "content": [
+ {
+ "type": "panel",
+ "attrs": {
+ "panelType": "success"
+ },
+ "content": [
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "Success info panel"
+ }
+ ]
+ },
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "Second paragraph"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "type": "panel",
+ "attrs": {
+ "panelType": "info"
+ },
+ "content": [
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "Info info panel"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "type": "panel",
+ "attrs": {
+ "panelType": "note"
+ },
+ "content": [
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "Note info panel"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "type": "panel",
+ "attrs": {
+ "panelType": "warning"
+ },
+ "content": [
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "Warning info panel"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "type": "panel",
+ "attrs": {
+ "panelType": "error"
+ },
+ "content": [
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "Error info panel"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "type": "panel",
+ "attrs": {
+ "panelType": "unknown"
+ },
+ "content": [
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "Unknown panel"
+ }
+ ]
+ }
+ ]
+ }
+ ]
+}
diff --git a/spec/fixtures/lib/kramdown/atlassian_document_format/panel.md b/spec/fixtures/lib/kramdown/atlassian_document_format/panel.md
new file mode 100644
index 00000000000..fe3a10e4878
--- /dev/null
+++ b/spec/fixtures/lib/kramdown/atlassian_document_format/panel.md
@@ -0,0 +1,12 @@
+> \:white\_check\_mark: Success info panel
+>
+> Second paragraph
+
+> \:information\_source: Info info panel
+
+> \:notepad\_spiral: Note info panel
+
+> \:warning: Warning info panel
+
+> \:octagonal\_sign: Error info panel
+
diff --git a/spec/fixtures/lib/kramdown/atlassian_document_format/paragraph.json b/spec/fixtures/lib/kramdown/atlassian_document_format/paragraph.json
new file mode 100644
index 00000000000..e5b99dbd112
--- /dev/null
+++ b/spec/fixtures/lib/kramdown/atlassian_document_format/paragraph.json
@@ -0,0 +1,28 @@
+{
+ "version": 1,
+ "type": "doc",
+ "content": [
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "This is a sample paragraph"
+ },
+ {
+ "type": "text",
+ "text": " with lorem ipsum dolor sit amet..."
+ }
+ ]
+ },
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "This is a second paragraph"
+ }
+ ]
+ }
+ ]
+}
diff --git a/spec/fixtures/lib/kramdown/atlassian_document_format/paragraph.md b/spec/fixtures/lib/kramdown/atlassian_document_format/paragraph.md
new file mode 100644
index 00000000000..661110e0267
--- /dev/null
+++ b/spec/fixtures/lib/kramdown/atlassian_document_format/paragraph.md
@@ -0,0 +1,4 @@
+This is a sample paragraph with lorem ipsum dolor sit amet...
+
+This is a second paragraph
+
diff --git a/spec/fixtures/lib/kramdown/atlassian_document_format/rule.json b/spec/fixtures/lib/kramdown/atlassian_document_format/rule.json
new file mode 100644
index 00000000000..c2566a84713
--- /dev/null
+++ b/spec/fixtures/lib/kramdown/atlassian_document_format/rule.json
@@ -0,0 +1,9 @@
+{
+ "version": 1,
+ "type": "doc",
+ "content": [
+ {
+ "type": "rule"
+ }
+ ]
+}
diff --git a/spec/fixtures/lib/kramdown/atlassian_document_format/rule.md b/spec/fixtures/lib/kramdown/atlassian_document_format/rule.md
new file mode 100644
index 00000000000..8ccef4e8dfb
--- /dev/null
+++ b/spec/fixtures/lib/kramdown/atlassian_document_format/rule.md
@@ -0,0 +1,2 @@
+* * *
+
diff --git a/spec/fixtures/lib/kramdown/atlassian_document_format/strike_sup_sub_mark.json b/spec/fixtures/lib/kramdown/atlassian_document_format/strike_sup_sub_mark.json
new file mode 100644
index 00000000000..21d364d642f
--- /dev/null
+++ b/spec/fixtures/lib/kramdown/atlassian_document_format/strike_sup_sub_mark.json
@@ -0,0 +1,56 @@
+{
+ "version": 1,
+ "type": "doc",
+ "content": [
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "This is "
+ },
+ {
+ "type": "text",
+ "text": "stricken",
+ "marks": [
+ {
+ "type": "strike"
+ }
+ ]
+ },
+ {
+ "type": "text",
+ "text": " and "
+ },
+ {
+ "type": "text",
+ "text": "superscripted",
+ "marks": [
+ {
+ "type": "subsup",
+ "attrs": {
+ "type": "sup"
+ }
+ }
+ ]
+ },
+ {
+ "type": "text",
+ "text": " and "
+ },
+ {
+ "type": "text",
+ "text": "subscripted",
+ "marks": [
+ {
+ "type": "subsup",
+ "attrs": {
+ "type": "sub"
+ }
+ }
+ ]
+ }
+ ]
+ }
+ ]
+}
diff --git a/spec/fixtures/lib/kramdown/atlassian_document_format/strike_sup_sub_mark.md b/spec/fixtures/lib/kramdown/atlassian_document_format/strike_sup_sub_mark.md
new file mode 100644
index 00000000000..e4dda350907
--- /dev/null
+++ b/spec/fixtures/lib/kramdown/atlassian_document_format/strike_sup_sub_mark.md
@@ -0,0 +1,3 @@
+This is <del>stricken</del> and <sup>superscripted</sup> and
+<sub>subscripted</sub>
+
diff --git a/spec/fixtures/lib/kramdown/atlassian_document_format/strong_em_mark.json b/spec/fixtures/lib/kramdown/atlassian_document_format/strong_em_mark.json
new file mode 100644
index 00000000000..7ab1a7c1461
--- /dev/null
+++ b/spec/fixtures/lib/kramdown/atlassian_document_format/strong_em_mark.json
@@ -0,0 +1,53 @@
+{
+ "version": 1,
+ "type": "doc",
+ "content": [
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "Hello "
+ },
+ {
+ "type": "text",
+ "text": "strong",
+ "marks": [
+ {
+ "type": "strong"
+ }
+ ]
+ },
+ {
+ "type": "text",
+ "text": " and "
+ },
+ {
+ "type": "text",
+ "text": "emphasis",
+ "marks": [
+ {
+ "type": "em"
+ }
+ ]
+ },
+ {
+ "type": "text",
+ "text": " and "
+ },
+ {
+ "type": "text",
+ "text": "strongly emphasised",
+ "marks": [
+ {
+ "type": "em"
+ },
+ {
+ "type": "strong"
+ }
+ ]
+ }
+ ]
+ }
+ ]
+}
diff --git a/spec/fixtures/lib/kramdown/atlassian_document_format/strong_em_mark.md b/spec/fixtures/lib/kramdown/atlassian_document_format/strong_em_mark.md
new file mode 100644
index 00000000000..d0dcbb231ae
--- /dev/null
+++ b/spec/fixtures/lib/kramdown/atlassian_document_format/strong_em_mark.md
@@ -0,0 +1,2 @@
+Hello **strong** and *emphasis* and ***strongly emphasised***
+
diff --git a/spec/fixtures/lib/kramdown/atlassian_document_format/table.json b/spec/fixtures/lib/kramdown/atlassian_document_format/table.json
new file mode 100644
index 00000000000..ad12c2875c0
--- /dev/null
+++ b/spec/fixtures/lib/kramdown/atlassian_document_format/table.json
@@ -0,0 +1,55 @@
+{
+ "version": 1,
+ "type": "doc",
+ "content": [
+ {
+ "type": "table",
+ "attrs": {
+ "isNumberColumnEnabled": false,
+ "layout": "default"
+ },
+ "content": [
+ {
+ "type": "tableRow",
+ "content": [
+ {
+ "type": "tableCell",
+ "attrs": {},
+ "content": [
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "Row one, cell one",
+ "marks": [
+ {
+ "type": "strong"
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "type": "tableCell",
+ "attrs": {},
+ "content": [
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "Row one, cell two"
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ]
+}
diff --git a/spec/fixtures/lib/kramdown/atlassian_document_format/table.md b/spec/fixtures/lib/kramdown/atlassian_document_format/table.md
new file mode 100644
index 00000000000..4e0c53338df
--- /dev/null
+++ b/spec/fixtures/lib/kramdown/atlassian_document_format/table.md
@@ -0,0 +1,18 @@
+<table>
+<tbody>
+<tr>
+<td>
+
+**Row one, cell one**
+
+</td>
+<td>
+
+Row one, cell two
+
+</td>
+</tr>
+</tbody>
+</table>
+
+
diff --git a/spec/fixtures/lib/kramdown/atlassian_document_format/underline_text_color_mark.json b/spec/fixtures/lib/kramdown/atlassian_document_format/underline_text_color_mark.json
new file mode 100644
index 00000000000..2690233d4f6
--- /dev/null
+++ b/spec/fixtures/lib/kramdown/atlassian_document_format/underline_text_color_mark.json
@@ -0,0 +1,40 @@
+{
+ "version": 1,
+ "type": "doc",
+ "content": [
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "This is "
+ },
+ {
+ "type": "text",
+ "text": "underlined",
+ "marks": [
+ {
+ "type": "underline"
+ }
+ ]
+ },
+ {
+ "type": "text",
+ "text": " and "
+ },
+ {
+ "type": "text",
+ "text": "red",
+ "marks": [
+ {
+ "type": "textColor",
+ "attrs": {
+ "color": "#ff6347"
+ }
+ }
+ ]
+ }
+ ]
+ }
+ ]
+}
diff --git a/spec/fixtures/lib/kramdown/atlassian_document_format/underline_text_color_mark.md b/spec/fixtures/lib/kramdown/atlassian_document_format/underline_text_color_mark.md
new file mode 100644
index 00000000000..75c9de3b451
--- /dev/null
+++ b/spec/fixtures/lib/kramdown/atlassian_document_format/underline_text_color_mark.md
@@ -0,0 +1,2 @@
+This is <u>underlined</u> and <span color="#ff6347">red</span>
+
diff --git a/spec/fixtures/pager_duty/webhook_incident_trigger.json b/spec/fixtures/pager_duty/webhook_incident_trigger.json
new file mode 100644
index 00000000000..872297adcf6
--- /dev/null
+++ b/spec/fixtures/pager_duty/webhook_incident_trigger.json
@@ -0,0 +1,239 @@
+{
+ "messages": [
+ {
+ "event": "incident.trigger",
+ "log_entries": [
+ {
+ "id": "R2XGXEI3W0FHMSDXHDIBQGBQ5E",
+ "type": "trigger_log_entry",
+ "summary": "Triggered through the website",
+ "self": "https://api.pagerduty.com/log_entries/R2XGXEI3W0FHMSDXHDIBQGBQ5E",
+ "html_url": "https://webdemo.pagerduty.com/incidents/PRORDTY/log_entries/R2XGXEI3W0FHMSDXHDIBQGBQ5E",
+ "created_at": "2017-09-26T15:14:36Z",
+ "agent": {
+ "id": "P553OPV",
+ "type": "user_reference",
+ "summary": "Laura Haley",
+ "self": "https://api.pagerduty.com/users/P553OPV",
+ "html_url": "https://webdemo.pagerduty.com/users/P553OPV"
+ },
+ "channel": {
+ "type": "web_trigger",
+ "summary": "My new incident",
+ "subject": "My new incident",
+ "details": "Oh my gosh",
+ "details_omitted": false
+ },
+ "service": {
+ "id": "PN49J75",
+ "type": "service_reference",
+ "summary": "Production XDB Cluster",
+ "self": "https://api.pagerduty.com/services/PN49J75",
+ "html_url": "https://webdemo.pagerduty.com/services/PN49J75"
+ },
+ "incident": {
+ "id": "PRORDTY",
+ "type": "incident_reference",
+ "summary": "[#33] My new incident",
+ "self": "https://api.pagerduty.com/incidents/PRORDTY",
+ "html_url": "https://webdemo.pagerduty.com/incidents/PRORDTY"
+ },
+ "teams": [
+ {
+ "id": "P4SI59S",
+ "type": "team_reference",
+ "summary": "Engineering",
+ "self": "https://api.pagerduty.com/teams/P4SI59S",
+ "html_url": "https://webdemo.pagerduty.com/teams/P4SI59S"
+ }
+ ],
+ "contexts": [],
+ "event_details": {
+ "description": "My new incident"
+ }
+ }
+ ],
+ "webhook": {
+ "endpoint_url": "https://requestb.in/18ao6fs1",
+ "name": "V2 wabhook",
+ "description": null,
+ "webhook_object": {
+ "id": "PN49J75",
+ "type": "service_reference",
+ "summary": "Production XDB Cluster",
+ "self": "https://api.pagerduty.com/services/PN49J75",
+ "html_url": "https://webdemo.pagerduty.com/services/PN49J75"
+ },
+ "config": {},
+ "outbound_integration": {
+ "id": "PJFWPEP",
+ "type": "outbound_integration_reference",
+ "summary": "Generic V2 Webhook",
+ "self": "https://api.pagerduty.com/outbound_integrations/PJFWPEP",
+ "html_url": null
+ },
+ "accounts_addon": null,
+ "id": "PKT9NNX",
+ "type": "webhook",
+ "summary": "V2 wabhook",
+ "self": "https://api.pagerduty.com/webhooks/PKT9NNX",
+ "html_url": null
+ },
+ "incident": {
+ "incident_number": 33,
+ "title": "My new incident",
+ "description": "My new incident",
+ "created_at": "2017-09-26T15:14:36Z",
+ "status": "triggered",
+ "pending_actions": [
+ {
+ "type": "escalate",
+ "at": "2017-09-26T15:44:36Z"
+ },
+ {
+ "type": "resolve",
+ "at": "2017-09-26T19:14:36Z"
+ }
+ ],
+ "incident_key": null,
+ "service": {
+ "id": "PN49J75",
+ "name": "Production XDB Cluster",
+ "description": "This service was created during onboarding on July 5, 2017.",
+ "auto_resolve_timeout": 14400,
+ "acknowledgement_timeout": 1800,
+ "created_at": "2017-07-05T17:33:09Z",
+ "status": "critical",
+ "last_incident_timestamp": "2017-09-26T15:14:36Z",
+ "teams": [
+ {
+ "id": "P4SI59S",
+ "type": "team_reference",
+ "summary": "Engineering",
+ "self": "https://api.pagerduty.com/teams/P4SI59S",
+ "html_url": "https://webdemo.pagerduty.com/teams/P4SI59S"
+ }
+ ],
+ "incident_urgency_rule": {
+ "type": "constant",
+ "urgency": "high"
+ },
+ "scheduled_actions": [],
+ "support_hours": null,
+ "escalation_policy": {
+ "id": "PINYWEF",
+ "type": "escalation_policy_reference",
+ "summary": "Default",
+ "self": "https://api.pagerduty.com/escalation_policies/PINYWEF",
+ "html_url": "https://webdemo.pagerduty.com/escalation_policies/PINYWEF"
+ },
+ "addons": [],
+ "privilege": null,
+ "alert_creation": "create_alerts_and_incidents",
+ "integrations": [
+ {
+ "id": "PUAYF96",
+ "type": "generic_events_api_inbound_integration_reference",
+ "summary": "API",
+ "self": "https://api.pagerduty.com/services/PN49J75/integrations/PUAYF96",
+ "html_url": "https://webdemo.pagerduty.com/services/PN49J75/integrations/PUAYF96"
+ },
+ {
+ "id": "P90GZUH",
+ "type": "generic_email_inbound_integration_reference",
+ "summary": "Email",
+ "self": "https://api.pagerduty.com/services/PN49J75/integrations/P90GZUH",
+ "html_url": "https://webdemo.pagerduty.com/services/PN49J75/integrations/P90GZUH"
+ }
+ ],
+ "metadata": {},
+ "type": "service",
+ "summary": "Production XDB Cluster",
+ "self": "https://api.pagerduty.com/services/PN49J75",
+ "html_url": "https://webdemo.pagerduty.com/services/PN49J75"
+ },
+ "assignments": [
+ {
+ "at": "2017-09-26T15:14:36Z",
+ "assignee": {
+ "id": "P553OPV",
+ "type": "user_reference",
+ "summary": "Laura Haley",
+ "self": "https://api.pagerduty.com/users/P553OPV",
+ "html_url": "https://webdemo.pagerduty.com/users/P553OPV"
+ }
+ }
+ ],
+ "acknowledgements": [],
+ "last_status_change_at": "2017-09-26T15:14:36Z",
+ "last_status_change_by": {
+ "id": "PN49J75",
+ "type": "service_reference",
+ "summary": "Production XDB Cluster",
+ "self": "https://api.pagerduty.com/services/PN49J75",
+ "html_url": "https://webdemo.pagerduty.com/services/PN49J75"
+ },
+ "first_trigger_log_entry": {
+ "id": "R2XGXEI3W0FHMSDXHDIBQGBQ5E",
+ "type": "trigger_log_entry_reference",
+ "summary": "Triggered through the website",
+ "self": "https://api.pagerduty.com/log_entries/R2XGXEI3W0FHMSDXHDIBQGBQ5E",
+ "html_url": "https://webdemo.pagerduty.com/incidents/PRORDTY/log_entries/R2XGXEI3W0FHMSDXHDIBQGBQ5E"
+ },
+ "escalation_policy": {
+ "id": "PINYWEF",
+ "type": "escalation_policy_reference",
+ "summary": "Default",
+ "self": "https://api.pagerduty.com/escalation_policies/PINYWEF",
+ "html_url": "https://webdemo.pagerduty.com/escalation_policies/PINYWEF"
+ },
+ "privilege": null,
+ "teams": [
+ {
+ "id": "P4SI59S",
+ "type": "team_reference",
+ "summary": "Engineering",
+ "self": "https://api.pagerduty.com/teams/P4SI59S",
+ "html_url": "https://webdemo.pagerduty.com/teams/P4SI59S"
+ }
+ ],
+ "alert_counts": {
+ "all": 0,
+ "triggered": 0,
+ "resolved": 0
+ },
+ "impacted_services": [
+ {
+ "id": "PN49J75",
+ "type": "service_reference",
+ "summary": "Production XDB Cluster",
+ "self": "https://api.pagerduty.com/services/PN49J75",
+ "html_url": "https://webdemo.pagerduty.com/services/PN49J75"
+ }
+ ],
+ "is_mergeable": true,
+ "basic_alert_grouping": null,
+ "alert_grouping": null,
+ "metadata": {},
+ "external_references": [],
+ "importance": null,
+ "incidents_responders": [],
+ "responder_requests": [],
+ "subscriber_requests": [],
+ "urgency": "high",
+ "id": "PRORDTY",
+ "type": "incident",
+ "summary": "[#33] My new incident",
+ "self": "https://api.pagerduty.com/incidents/PRORDTY",
+ "html_url": "https://webdemo.pagerduty.com/incidents/PRORDTY",
+ "alerts": [
+ {
+ "alert_key": "c24117fc42e44b44b4d6876190583378"
+ }
+ ]
+ },
+ "id": "69a7ced0-a2cd-11e7-a799-22000a15839c",
+ "created_on": "2017-09-26T15:14:36Z"
+ }
+ ]
+}
diff --git a/spec/fixtures/product_analytics/event.json b/spec/fixtures/product_analytics/event.json
new file mode 100644
index 00000000000..3100b068a0c
--- /dev/null
+++ b/spec/fixtures/product_analytics/event.json
@@ -0,0 +1,16 @@
+{
+ "aid":"1",
+ "p":"web",
+ "tna":"sp",
+ "tv":"js-2.14.0",
+ "eid":"fbf14096-74ee-47e4-883c-8a0d6cb72e37",
+ "duid":"79543c31-cfc3-4479-a737-fafb9333c8ba",
+ "sid":"54f6d3f3-f4f9-4fdc-87e0-a2c775234c1b",
+ "vid":4,
+ "url":"http://example.com/products/1",
+ "refr":"http://example.com/products/1",
+ "lang":"en-US",
+ "cookie":"1",
+ "tz":"America/Los_Angeles",
+ "cs":"UTF-8"
+}
diff --git a/spec/fixtures/sentry/issue_sample_response.json b/spec/fixtures/sentry/issue_sample_response.json
index 43d55f584b8..82588bd0749 100644
--- a/spec/fixtures/sentry/issue_sample_response.json
+++ b/spec/fixtures/sentry/issue_sample_response.json
@@ -42,7 +42,30 @@
"isBookmarked": false,
"isPublic": false,
"isSubscribed": true,
- "lastRelease": null,
+ "lastRelease": {
+ "dateReleased": null,
+ "commitCount": 1,
+ "url": null,
+ "data": {},
+ "lastDeploy": {},
+ "deployCount": 3,
+ "dateCreated": "2020-06-29T08:10:45.909Z",
+ "lastEvent": "2020-06-30T09:47:19.651Z",
+ "version": "17642328ead24b51867165985996d04b29321448",
+ "firstEvent": "2020-06-29T09:13:36.696Z",
+ "lastCommit": {},
+ "shortVersion": "27de6b42eb4",
+ "authors": [],
+ "owner": null,
+ "newGroups": 208,
+ "ref": null,
+ "projects": [
+ {
+ "name": "Pump Station",
+ "slug": "pump-station"
+ }
+ ]
+ },
"lastSeen": "2018-11-06T21:19:55Z",
"level": "error",
"logger": null,
diff --git a/spec/frontend/__mocks__/@gitlab/ui.js b/spec/frontend/__mocks__/@gitlab/ui.js
index d65fab80d3b..ea36f1dabaf 100644
--- a/spec/frontend/__mocks__/@gitlab/ui.js
+++ b/spec/frontend/__mocks__/@gitlab/ui.js
@@ -7,22 +7,23 @@ export * from '@gitlab/ui';
*
* This mock decouples those tests from the implementation, removing the need to set
* them up specially just for these tooltips.
+ *
+ * Mocking the modules using the full file path allows the mocks to take effect
+ * when the modules are imported in this project (`gitlab`) **and** when they
+ * are imported internally in `@gitlab/ui`.
*/
-export const GlTooltipDirective = {
+
+jest.mock('@gitlab/ui/dist/directives/tooltip.js', () => ({
bind() {},
-};
+}));
-export const GlTooltip = {
+jest.mock('@gitlab/ui/dist/components/base/tooltip/tooltip.js', () => ({
render(h) {
return h('div', this.$attrs, this.$slots.default);
},
-};
-
-export const GlPopoverDirective = {
- bind() {},
-};
+}));
-export const GlPopover = {
+jest.mock('@gitlab/ui/dist/components/base/popover/popover.js', () => ({
props: {
cssClasses: {
type: Array,
@@ -33,4 +34,4 @@ export const GlPopover = {
render(h) {
return h('div', this.$attrs, Object.keys(this.$slots).map(s => this.$slots[s]));
},
-};
+}));
diff --git a/spec/frontend/__mocks__/document-register-element/index.js b/spec/frontend/__mocks__/document-register-element/index.js
new file mode 100644
index 00000000000..2d1ec238274
--- /dev/null
+++ b/spec/frontend/__mocks__/document-register-element/index.js
@@ -0,0 +1 @@
+export default () => {};
diff --git a/spec/frontend/__mocks__/monaco-editor/index.js b/spec/frontend/__mocks__/monaco-editor/index.js
index 7c53cfb5174..b9602d69b74 100644
--- a/spec/frontend/__mocks__/monaco-editor/index.js
+++ b/spec/frontend/__mocks__/monaco-editor/index.js
@@ -8,9 +8,11 @@ import 'monaco-editor/esm/vs/language/css/monaco.contribution';
import 'monaco-editor/esm/vs/language/json/monaco.contribution';
import 'monaco-editor/esm/vs/language/html/monaco.contribution';
import 'monaco-editor/esm/vs/basic-languages/monaco.contribution';
+import 'monaco-yaml/esm/monaco.contribution';
// This language starts trying to spin up web workers which obviously breaks in Jest environment
jest.mock('monaco-editor/esm/vs/language/typescript/tsMode');
+jest.mock('monaco-yaml/esm/yamlMode');
export * from 'monaco-editor/esm/vs/editor/editor.api';
export default global.monaco;
diff --git a/spec/frontend/alert_management/components/alert_management_detail_spec.js b/spec/frontend/alert_management/components/alert_management_detail_spec.js
index 14e45a4f563..daa730d3b9f 100644
--- a/spec/frontend/alert_management/components/alert_management_detail_spec.js
+++ b/spec/frontend/alert_management/components/alert_management_detail_spec.js
@@ -3,7 +3,7 @@ import { GlAlert, GlLoadingIcon, GlTable } from '@gitlab/ui';
import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
import AlertDetails from '~/alert_management/components/alert_details.vue';
-import createIssueQuery from '~/alert_management/graphql/mutations/create_issue_from_alert.graphql';
+import createIssueMutation from '~/alert_management/graphql/mutations/create_issue_from_alert.mutation.graphql';
import { joinPaths } from '~/lib/utils/url_utility';
import {
trackAlertsDetailsViewsOptions,
@@ -19,18 +19,20 @@ describe('AlertDetails', () => {
let mock;
const projectPath = 'root/alerts';
const projectIssuesPath = 'root/alerts/-/issues';
+ const projectId = '1';
const findDetailsTable = () => wrapper.find(GlTable);
function mountComponent({ data, loading = false, mountMethod = shallowMount, stubs = {} } = {}) {
wrapper = mountMethod(AlertDetails, {
- propsData: {
+ provide: {
alertId: 'alertId',
projectPath,
projectIssuesPath,
+ projectId,
},
data() {
- return { alert: { ...mockAlert }, ...data };
+ return { alert: { ...mockAlert }, sidebarStatus: false, ...data };
},
mocks: {
$apollo: {
@@ -39,6 +41,7 @@ describe('AlertDetails', () => {
alert: {
loading,
},
+ sidebarStatus: {},
},
},
},
@@ -52,9 +55,7 @@ describe('AlertDetails', () => {
afterEach(() => {
if (wrapper) {
- if (wrapper) {
- wrapper.destroy();
- }
+ wrapper.destroy();
}
mock.restore();
});
@@ -133,7 +134,7 @@ describe('AlertDetails', () => {
it('should display "View issue" button that links the issue page when issue exists', () => {
const issueIid = '3';
mountComponent({
- data: { alert: { ...mockAlert, issueIid } },
+ data: { alert: { ...mockAlert, issueIid }, sidebarStatus: false },
});
expect(findViewIssueBtn().exists()).toBe(true);
expect(findViewIssueBtn().attributes('href')).toBe(joinPaths(projectIssuesPath, issueIid));
@@ -146,8 +147,11 @@ describe('AlertDetails', () => {
mountMethod: mount,
data: { alert: { ...mockAlert, issueIid } },
});
- expect(findViewIssueBtn().exists()).toBe(false);
- expect(findCreateIssueBtn().exists()).toBe(true);
+
+ return wrapper.vm.$nextTick().then(() => {
+ expect(findViewIssueBtn().exists()).toBe(false);
+ expect(findCreateIssueBtn().exists()).toBe(true);
+ });
});
it('calls `$apollo.mutate` with `createIssueQuery`', () => {
@@ -158,7 +162,7 @@ describe('AlertDetails', () => {
findCreateIssueBtn().trigger('click');
expect(wrapper.vm.$apollo.mutate).toHaveBeenCalledWith({
- mutation: createIssueQuery,
+ mutation: createIssueMutation,
variables: {
iid: mockAlert.iid,
projectPath,
@@ -208,6 +212,13 @@ describe('AlertDetails', () => {
expect(wrapper.find(GlAlert).exists()).toBe(true);
});
+ it('renders html-errors correctly', () => {
+ mountComponent({
+ data: { errored: true, sidebarErrorMessage: '<span data-testid="htmlError" />' },
+ });
+ expect(wrapper.find('[data-testid="htmlError"]').exists()).toBe(true);
+ });
+
it('does not display an error when dismissed', () => {
mountComponent({ data: { errored: true, isErrorDismissed: true } });
expect(wrapper.find(GlAlert).exists()).toBe(false);
diff --git a/spec/frontend/alert_management/components/alert_management_empty_state_spec.js b/spec/frontend/alert_management/components/alert_management_empty_state_spec.js
new file mode 100644
index 00000000000..0d1214211d3
--- /dev/null
+++ b/spec/frontend/alert_management/components/alert_management_empty_state_spec.js
@@ -0,0 +1,54 @@
+import { shallowMount } from '@vue/test-utils';
+import { GlEmptyState } from '@gitlab/ui';
+import AlertManagementEmptyState from '~/alert_management/components/alert_management_empty_state.vue';
+
+describe('AlertManagementEmptyState', () => {
+ let wrapper;
+
+ function mountComponent({
+ props = {
+ alertManagementEnabled: false,
+ userCanEnableAlertManagement: false,
+ },
+ stubs = {},
+ } = {}) {
+ wrapper = shallowMount(AlertManagementEmptyState, {
+ propsData: {
+ enableAlertManagementPath: '/link',
+ emptyAlertSvgPath: 'illustration/path',
+ ...props,
+ },
+ stubs,
+ });
+ }
+
+ beforeEach(() => {
+ mountComponent();
+ });
+
+ afterEach(() => {
+ if (wrapper) {
+ wrapper.destroy();
+ }
+ });
+
+ const EmptyState = () => wrapper.find(GlEmptyState);
+
+ describe('Empty state', () => {
+ it('shows empty state', () => {
+ expect(EmptyState().exists()).toBe(true);
+ });
+
+ it('show OpsGenie integration state when OpsGenie mcv is true', () => {
+ mountComponent({
+ props: {
+ alertManagementEnabled: false,
+ userCanEnableAlertManagement: false,
+ opsgenieMvcEnabled: true,
+ opsgenieMvcTargetUrl: 'https://opsgenie-url.com',
+ },
+ });
+ expect(EmptyState().props('title')).toBe('Opsgenie is enabled');
+ });
+ });
+});
diff --git a/spec/frontend/alert_management/components/alert_management_list_spec.js b/spec/frontend/alert_management/components/alert_management_list_spec.js
deleted file mode 100644
index 0154e5fa112..00000000000
--- a/spec/frontend/alert_management/components/alert_management_list_spec.js
+++ /dev/null
@@ -1,489 +0,0 @@
-import { mount } from '@vue/test-utils';
-import {
- GlEmptyState,
- GlTable,
- GlAlert,
- GlLoadingIcon,
- GlDropdown,
- GlDropdownItem,
- GlIcon,
- GlTabs,
- GlTab,
- GlBadge,
- GlPagination,
-} from '@gitlab/ui';
-import { visitUrl } from '~/lib/utils/url_utility';
-import TimeAgo from '~/vue_shared/components/time_ago_tooltip.vue';
-import createFlash from '~/flash';
-import AlertManagementList from '~/alert_management/components/alert_management_list.vue';
-import {
- ALERTS_STATUS_TABS,
- trackAlertListViewsOptions,
- trackAlertStatusUpdateOptions,
-} from '~/alert_management/constants';
-import updateAlertStatus from '~/alert_management/graphql/mutations/update_alert_status.graphql';
-import mockAlerts from '../mocks/alerts.json';
-import Tracking from '~/tracking';
-
-jest.mock('~/flash');
-
-jest.mock('~/lib/utils/url_utility', () => ({
- visitUrl: jest.fn().mockName('visitUrlMock'),
- joinPaths: jest.requireActual('~/lib/utils/url_utility').joinPaths,
-}));
-
-describe('AlertManagementList', () => {
- let wrapper;
-
- const findAlertsTable = () => wrapper.find(GlTable);
- const findAlerts = () => wrapper.findAll('table tbody tr');
- const findAlert = () => wrapper.find(GlAlert);
- const findLoader = () => wrapper.find(GlLoadingIcon);
- const findStatusDropdown = () => wrapper.find(GlDropdown);
- const findStatusFilterTabs = () => wrapper.findAll(GlTab);
- const findStatusTabs = () => wrapper.find(GlTabs);
- const findStatusFilterBadge = () => wrapper.findAll(GlBadge);
- const findDateFields = () => wrapper.findAll(TimeAgo);
- const findFirstStatusOption = () => findStatusDropdown().find(GlDropdownItem);
- const findAssignees = () => wrapper.findAll('[data-testid="assigneesField"]');
- const findSeverityFields = () => wrapper.findAll('[data-testid="severityField"]');
- const findSeverityColumnHeader = () => wrapper.findAll('th').at(0);
- const findPagination = () => wrapper.find(GlPagination);
- const alertsCount = {
- open: 14,
- triggered: 10,
- acknowledged: 6,
- resolved: 1,
- all: 16,
- };
-
- function mountComponent({
- props = {
- alertManagementEnabled: false,
- userCanEnableAlertManagement: false,
- },
- data = {},
- loading = false,
- stubs = {},
- } = {}) {
- wrapper = mount(AlertManagementList, {
- propsData: {
- projectPath: 'gitlab-org/gitlab',
- enableAlertManagementPath: '/link',
- emptyAlertSvgPath: 'illustration/path',
- ...props,
- },
- data() {
- return data;
- },
- mocks: {
- $apollo: {
- mutate: jest.fn(),
- query: jest.fn(),
- queries: {
- alerts: {
- loading,
- },
- },
- },
- },
- stubs,
- });
- }
-
- beforeEach(() => {
- mountComponent();
- });
-
- afterEach(() => {
- if (wrapper) {
- wrapper.destroy();
- }
- });
-
- describe('Empty state', () => {
- it('shows empty state', () => {
- expect(wrapper.find(GlEmptyState).exists()).toBe(true);
- });
- });
-
- describe('Status Filter Tabs', () => {
- beforeEach(() => {
- mountComponent({
- props: { alertManagementEnabled: true, userCanEnableAlertManagement: true },
- data: { alerts: mockAlerts, alertsCount },
- loading: false,
- stubs: {
- GlTab: true,
- },
- });
- });
-
- it('should display filter tabs with alerts count badge for each status', () => {
- const tabs = findStatusFilterTabs().wrappers;
- const badges = findStatusFilterBadge();
-
- tabs.forEach((tab, i) => {
- const status = ALERTS_STATUS_TABS[i].status.toLowerCase();
- expect(tab.text()).toContain(ALERTS_STATUS_TABS[i].title);
- expect(badges.at(i).text()).toContain(alertsCount[status]);
- });
- });
- });
-
- describe('Alerts table', () => {
- it('loading state', () => {
- mountComponent({
- props: { alertManagementEnabled: true, userCanEnableAlertManagement: true },
- data: { alerts: {}, alertsCount: null },
- loading: true,
- });
- expect(findAlertsTable().exists()).toBe(true);
- expect(findLoader().exists()).toBe(true);
- expect(
- findAlerts()
- .at(0)
- .classes(),
- ).not.toContain('gl-hover-bg-blue-50');
- });
-
- it('error state', () => {
- mountComponent({
- props: { alertManagementEnabled: true, userCanEnableAlertManagement: true },
- data: { alerts: { errors: ['error'] }, alertsCount: null, errored: true },
- loading: false,
- });
- expect(findAlertsTable().exists()).toBe(true);
- expect(findAlertsTable().text()).toContain('No alerts to display');
- expect(findLoader().exists()).toBe(false);
- expect(findAlert().props().variant).toBe('danger');
- expect(
- findAlerts()
- .at(0)
- .classes(),
- ).not.toContain('gl-hover-bg-blue-50');
- });
-
- it('empty state', () => {
- mountComponent({
- props: { alertManagementEnabled: true, userCanEnableAlertManagement: true },
- data: { alerts: { list: [], pageInfo: {} }, alertsCount: { all: 0 }, errored: false },
- loading: false,
- });
- expect(findAlertsTable().exists()).toBe(true);
- expect(findAlertsTable().text()).toContain('No alerts to display');
- expect(findLoader().exists()).toBe(false);
- expect(findAlert().props().variant).toBe('info');
- expect(
- findAlerts()
- .at(0)
- .classes(),
- ).not.toContain('gl-hover-bg-blue-50');
- });
-
- it('has data state', () => {
- mountComponent({
- props: { alertManagementEnabled: true, userCanEnableAlertManagement: true },
- data: { alerts: { list: mockAlerts }, alertsCount, errored: false },
- loading: false,
- });
- expect(findLoader().exists()).toBe(false);
- expect(findAlertsTable().exists()).toBe(true);
- expect(findAlerts()).toHaveLength(mockAlerts.length);
- expect(
- findAlerts()
- .at(0)
- .classes(),
- ).toContain('gl-hover-bg-blue-50');
- });
-
- it('displays status dropdown', () => {
- mountComponent({
- props: { alertManagementEnabled: true, userCanEnableAlertManagement: true },
- data: { alerts: { list: mockAlerts }, alertsCount, errored: false },
- loading: false,
- });
- expect(findStatusDropdown().exists()).toBe(true);
- });
-
- it('shows correct severity icons', () => {
- mountComponent({
- props: { alertManagementEnabled: true, userCanEnableAlertManagement: true },
- data: { alerts: { list: mockAlerts }, alertsCount, errored: false },
- loading: false,
- });
-
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.find(GlTable).exists()).toBe(true);
- expect(
- findAlertsTable()
- .find(GlIcon)
- .classes('icon-critical'),
- ).toBe(true);
- });
- });
-
- it('renders severity text', () => {
- mountComponent({
- props: { alertManagementEnabled: true, userCanEnableAlertManagement: true },
- data: { alerts: { list: mockAlerts }, alertsCount, errored: false },
- loading: false,
- });
-
- expect(
- findSeverityFields()
- .at(0)
- .text(),
- ).toBe('Critical');
- });
-
- it('renders Unassigned when no assignee(s) present', () => {
- mountComponent({
- props: { alertManagementEnabled: true, userCanEnableAlertManagement: true },
- data: { alerts: { list: mockAlerts }, alertsCount, errored: false },
- loading: false,
- });
-
- expect(
- findAssignees()
- .at(0)
- .text(),
- ).toBe('Unassigned');
- });
-
- it('renders username(s) when assignee(s) present', () => {
- mountComponent({
- props: { alertManagementEnabled: true, userCanEnableAlertManagement: true },
- data: { alerts: { list: mockAlerts }, alertsCount, errored: false },
- loading: false,
- });
-
- expect(
- findAssignees()
- .at(1)
- .text(),
- ).toBe(mockAlerts[1].assignees.nodes[0].username);
- });
-
- it('navigates to the detail page when alert row is clicked', () => {
- mountComponent({
- props: { alertManagementEnabled: true, userCanEnableAlertManagement: true },
- data: { alerts: { list: mockAlerts }, alertsCount, errored: false },
- loading: false,
- });
-
- findAlerts()
- .at(0)
- .trigger('click');
- expect(visitUrl).toHaveBeenCalledWith('/1527542/details');
- });
-
- describe('handle date fields', () => {
- it('should display time ago dates when values provided', () => {
- mountComponent({
- props: { alertManagementEnabled: true, userCanEnableAlertManagement: true },
- data: {
- alerts: {
- list: [
- {
- iid: 1,
- status: 'acknowledged',
- startedAt: '2020-03-17T23:18:14.996Z',
- endedAt: '2020-04-17T23:18:14.996Z',
- severity: 'high',
- assignees: { nodes: [] },
- },
- ],
- },
- alertsCount,
- errored: false,
- },
- loading: false,
- });
- expect(findDateFields().length).toBe(2);
- });
-
- it('should not display time ago dates when values not provided', () => {
- mountComponent({
- props: { alertManagementEnabled: true, userCanEnableAlertManagement: true },
- data: {
- alerts: [
- {
- iid: 1,
- status: 'acknowledged',
- startedAt: null,
- endedAt: null,
- severity: 'high',
- },
- ],
- alertsCount,
- errored: false,
- },
- loading: false,
- });
- expect(findDateFields().exists()).toBe(false);
- });
- });
- });
-
- describe('sorting the alert list by column', () => {
- beforeEach(() => {
- mountComponent({
- props: { alertManagementEnabled: true, userCanEnableAlertManagement: true },
- data: {
- alerts: { list: mockAlerts },
- errored: false,
- sort: 'STARTED_AT_DESC',
- alertsCount,
- },
- loading: false,
- stubs: { GlTable },
- });
- });
-
- it('updates sort with new direction and column key', () => {
- findSeverityColumnHeader().trigger('click');
-
- expect(wrapper.vm.$data.sort).toBe('SEVERITY_DESC');
-
- findSeverityColumnHeader().trigger('click');
-
- expect(wrapper.vm.$data.sort).toBe('SEVERITY_ASC');
- });
- });
-
- describe('updating the alert status', () => {
- const iid = '1527542';
- const mockUpdatedMutationResult = {
- data: {
- updateAlertStatus: {
- errors: [],
- alert: {
- iid,
- status: 'acknowledged',
- },
- },
- },
- };
-
- beforeEach(() => {
- mountComponent({
- props: { alertManagementEnabled: true, userCanEnableAlertManagement: true },
- data: { alerts: { list: mockAlerts }, alertsCount, errored: false },
- loading: false,
- });
- });
-
- it('calls `$apollo.mutate` with `updateAlertStatus` mutation and variables containing `iid`, `status`, & `projectPath`', () => {
- jest.spyOn(wrapper.vm.$apollo, 'mutate').mockResolvedValue(mockUpdatedMutationResult);
- findFirstStatusOption().vm.$emit('click');
-
- expect(wrapper.vm.$apollo.mutate).toHaveBeenCalledWith({
- mutation: updateAlertStatus,
- variables: {
- iid,
- status: 'TRIGGERED',
- projectPath: 'gitlab-org/gitlab',
- },
- });
- });
-
- it('calls `createFlash` when request fails', () => {
- jest.spyOn(wrapper.vm.$apollo, 'mutate').mockReturnValue(Promise.reject(new Error()));
- findFirstStatusOption().vm.$emit('click');
-
- setImmediate(() => {
- expect(createFlash).toHaveBeenCalledWith(
- 'There was an error while updating the status of the alert. Please try again.',
- );
- });
- });
- });
-
- describe('Snowplow tracking', () => {
- beforeEach(() => {
- jest.spyOn(Tracking, 'event');
- mountComponent({
- props: { alertManagementEnabled: true, userCanEnableAlertManagement: true },
- data: { alerts: { list: mockAlerts }, alertsCount },
- loading: false,
- });
- });
-
- it('should track alert list page views', () => {
- const { category, action } = trackAlertListViewsOptions;
- expect(Tracking.event).toHaveBeenCalledWith(category, action);
- });
-
- it('should track alert status updates', () => {
- Tracking.event.mockClear();
- jest.spyOn(wrapper.vm.$apollo, 'mutate').mockResolvedValue({});
- findFirstStatusOption().vm.$emit('click');
- const status = findFirstStatusOption().text();
- setImmediate(() => {
- const { category, action, label } = trackAlertStatusUpdateOptions;
- expect(Tracking.event).toHaveBeenCalledWith(category, action, { label, property: status });
- });
- });
- });
-
- describe('Pagination', () => {
- beforeEach(() => {
- mountComponent({
- props: { alertManagementEnabled: true, userCanEnableAlertManagement: true },
- data: { alerts: { list: mockAlerts, pageInfo: {} }, alertsCount, errored: false },
- loading: false,
- });
- });
-
- it('does NOT show pagination control when list is smaller than default page size', () => {
- findStatusTabs().vm.$emit('input', 3);
- wrapper.vm.$nextTick(() => {
- expect(findPagination().exists()).toBe(false);
- });
- });
-
- it('shows pagination control when list is larger than default page size', () => {
- findStatusTabs().vm.$emit('input', 0);
- wrapper.vm.$nextTick(() => {
- expect(findPagination().exists()).toBe(true);
- });
- });
-
- describe('prevPage', () => {
- it('returns prevPage number', () => {
- findPagination().vm.$emit('input', 3);
-
- return wrapper.vm.$nextTick(() => {
- expect(wrapper.vm.prevPage).toBe(2);
- });
- });
-
- it('returns 0 when it is the first page', () => {
- findPagination().vm.$emit('input', 1);
-
- return wrapper.vm.$nextTick(() => {
- expect(wrapper.vm.prevPage).toBe(0);
- });
- });
- });
-
- describe('nextPage', () => {
- it('returns nextPage number', () => {
- findPagination().vm.$emit('input', 1);
-
- return wrapper.vm.$nextTick(() => {
- expect(wrapper.vm.nextPage).toBe(2);
- });
- });
-
- it('returns `null` when currentPage is already last page', () => {
- findStatusTabs().vm.$emit('input', 3);
- findPagination().vm.$emit('input', 1);
- return wrapper.vm.$nextTick(() => {
- expect(wrapper.vm.nextPage).toBeNull();
- });
- });
- });
- });
-});
diff --git a/spec/frontend/alert_management/components/alert_management_list_wrapper_spec.js b/spec/frontend/alert_management/components/alert_management_list_wrapper_spec.js
new file mode 100644
index 00000000000..4644406c037
--- /dev/null
+++ b/spec/frontend/alert_management/components/alert_management_list_wrapper_spec.js
@@ -0,0 +1,57 @@
+import { shallowMount } from '@vue/test-utils';
+import AlertManagementList from '~/alert_management/components/alert_management_list_wrapper.vue';
+import { trackAlertListViewsOptions } from '~/alert_management/constants';
+import mockAlerts from '../mocks/alerts.json';
+import Tracking from '~/tracking';
+
+describe('AlertManagementList', () => {
+ let wrapper;
+
+ function mountComponent({
+ props = {
+ alertManagementEnabled: false,
+ userCanEnableAlertManagement: false,
+ },
+ data = {},
+ stubs = {},
+ } = {}) {
+ wrapper = shallowMount(AlertManagementList, {
+ propsData: {
+ projectPath: 'gitlab-org/gitlab',
+ enableAlertManagementPath: '/link',
+ populatingAlertsHelpUrl: '/help/help-page.md#populating-alert-data',
+ emptyAlertSvgPath: 'illustration/path',
+ ...props,
+ },
+ data() {
+ return data;
+ },
+ stubs,
+ });
+ }
+
+ beforeEach(() => {
+ mountComponent();
+ });
+
+ afterEach(() => {
+ if (wrapper) {
+ wrapper.destroy();
+ }
+ });
+
+ describe('Snowplow tracking', () => {
+ beforeEach(() => {
+ jest.spyOn(Tracking, 'event');
+ mountComponent({
+ props: { alertManagementEnabled: true, userCanEnableAlertManagement: true },
+ data: { alerts: { list: mockAlerts } },
+ });
+ });
+
+ it('should track alert list page views', () => {
+ const { category, action } = trackAlertListViewsOptions;
+ expect(Tracking.event).toHaveBeenCalledWith(category, action);
+ });
+ });
+});
diff --git a/spec/frontend/alert_management/components/alert_management_sidebar_todo_spec.js b/spec/frontend/alert_management/components/alert_management_sidebar_todo_spec.js
new file mode 100644
index 00000000000..fe08cf2c10a
--- /dev/null
+++ b/spec/frontend/alert_management/components/alert_management_sidebar_todo_spec.js
@@ -0,0 +1,76 @@
+import { mount } from '@vue/test-utils';
+import SidebarTodo from '~/alert_management/components/sidebar/sidebar_todo.vue';
+import AlertMarkTodo from '~/alert_management/graphql/mutations/alert_todo_create.graphql';
+import mockAlerts from '../mocks/alerts.json';
+
+const mockAlert = mockAlerts[0];
+
+describe('Alert Details Sidebar To Do', () => {
+ let wrapper;
+
+ function mountComponent({ data, sidebarCollapsed = true, loading = false, stubs = {} } = {}) {
+ wrapper = mount(SidebarTodo, {
+ propsData: {
+ alert: { ...mockAlert },
+ ...data,
+ sidebarCollapsed,
+ projectPath: 'projectPath',
+ },
+ mocks: {
+ $apollo: {
+ mutate: jest.fn(),
+ queries: {
+ alert: {
+ loading,
+ },
+ },
+ },
+ },
+ stubs,
+ });
+ }
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ describe('updating the alert to do', () => {
+ const mockUpdatedMutationResult = {
+ data: {
+ updateAlertTodo: {
+ errors: [],
+ alert: {},
+ },
+ },
+ };
+
+ beforeEach(() => {
+ mountComponent({
+ data: { alert: mockAlert },
+ sidebarCollapsed: false,
+ loading: false,
+ });
+ });
+
+ it('renders a button for adding a To Do', () => {
+ return wrapper.vm.$nextTick().then(() => {
+ expect(wrapper.find('[data-testid="alert-todo-button"]').text()).toBe('Add a To Do');
+ });
+ });
+
+ it('calls `$apollo.mutate` with `AlertMarkTodo` mutation and variables containing `iid`, `todoEvent`, & `projectPath`', () => {
+ jest.spyOn(wrapper.vm.$apollo, 'mutate').mockResolvedValue(mockUpdatedMutationResult);
+
+ return wrapper.vm.$nextTick().then(() => {
+ wrapper.find('button').trigger('click');
+ expect(wrapper.vm.$apollo.mutate).toHaveBeenCalledWith({
+ mutation: AlertMarkTodo,
+ variables: {
+ iid: '1527542',
+ projectPath: 'projectPath',
+ },
+ });
+ });
+ });
+ });
+});
diff --git a/spec/frontend/alert_management/components/alert_management_system_note_spec.js b/spec/frontend/alert_management/components/alert_management_system_note_spec.js
deleted file mode 100644
index 87dc36cc7cb..00000000000
--- a/spec/frontend/alert_management/components/alert_management_system_note_spec.js
+++ /dev/null
@@ -1,34 +0,0 @@
-import { shallowMount } from '@vue/test-utils';
-import SystemNote from '~/alert_management/components/system_notes/system_note.vue';
-import mockAlerts from '../mocks/alerts.json';
-
-const mockAlert = mockAlerts[1];
-
-describe('Alert Details System Note', () => {
- let wrapper;
-
- function mountComponent({ stubs = {} } = {}) {
- wrapper = shallowMount(SystemNote, {
- propsData: {
- note: { ...mockAlert.notes.nodes[0] },
- },
- stubs,
- });
- }
-
- afterEach(() => {
- if (wrapper) {
- wrapper.destroy();
- }
- });
-
- describe('System notes', () => {
- beforeEach(() => {
- mountComponent({});
- });
-
- it('renders the correct system note', () => {
- expect(wrapper.find('.note-wrapper').attributes('id')).toBe('note_1628');
- });
- });
-});
diff --git a/spec/frontend/alert_management/components/alert_management_table_spec.js b/spec/frontend/alert_management/components/alert_management_table_spec.js
new file mode 100644
index 00000000000..f316126432e
--- /dev/null
+++ b/spec/frontend/alert_management/components/alert_management_table_spec.js
@@ -0,0 +1,590 @@
+import { mount } from '@vue/test-utils';
+import {
+ GlTable,
+ GlAlert,
+ GlLoadingIcon,
+ GlDropdown,
+ GlDropdownItem,
+ GlIcon,
+ GlTabs,
+ GlTab,
+ GlBadge,
+ GlPagination,
+ GlSearchBoxByType,
+} from '@gitlab/ui';
+import { visitUrl } from '~/lib/utils/url_utility';
+import TimeAgo from '~/vue_shared/components/time_ago_tooltip.vue';
+import AlertManagementTable from '~/alert_management/components/alert_management_table.vue';
+import { ALERTS_STATUS_TABS, trackAlertStatusUpdateOptions } from '~/alert_management/constants';
+import updateAlertStatus from '~/alert_management/graphql/mutations/update_alert_status.mutation.graphql';
+import mockAlerts from '../mocks/alerts.json';
+import Tracking from '~/tracking';
+
+jest.mock('~/lib/utils/url_utility', () => ({
+ visitUrl: jest.fn().mockName('visitUrlMock'),
+ joinPaths: jest.requireActual('~/lib/utils/url_utility').joinPaths,
+}));
+
+describe('AlertManagementTable', () => {
+ let wrapper;
+
+ const findAlertsTable = () => wrapper.find(GlTable);
+ const findAlerts = () => wrapper.findAll('table tbody tr');
+ const findAlert = () => wrapper.find(GlAlert);
+ const findLoader = () => wrapper.find(GlLoadingIcon);
+ const findStatusDropdown = () => wrapper.find(GlDropdown);
+ const findStatusFilterTabs = () => wrapper.findAll(GlTab);
+ const findStatusTabs = () => wrapper.find(GlTabs);
+ const findStatusFilterBadge = () => wrapper.findAll(GlBadge);
+ const findDateFields = () => wrapper.findAll(TimeAgo);
+ const findFirstStatusOption = () => findStatusDropdown().find(GlDropdownItem);
+ const findAssignees = () => wrapper.findAll('[data-testid="assigneesField"]');
+ const findSeverityFields = () => wrapper.findAll('[data-testid="severityField"]');
+ const findSeverityColumnHeader = () => wrapper.findAll('th').at(0);
+ const findPagination = () => wrapper.find(GlPagination);
+ const findSearch = () => wrapper.find(GlSearchBoxByType);
+ const findIssueFields = () => wrapper.findAll('[data-testid="issueField"]');
+ const alertsCount = {
+ open: 14,
+ triggered: 10,
+ acknowledged: 6,
+ resolved: 1,
+ all: 16,
+ };
+
+ function mountComponent({
+ props = {
+ alertManagementEnabled: false,
+ userCanEnableAlertManagement: false,
+ },
+ data = {},
+ loading = false,
+ stubs = {},
+ } = {}) {
+ wrapper = mount(AlertManagementTable, {
+ propsData: {
+ projectPath: 'gitlab-org/gitlab',
+ populatingAlertsHelpUrl: '/help/help-page.md#populating-alert-data',
+ ...props,
+ },
+ data() {
+ return data;
+ },
+ mocks: {
+ $apollo: {
+ mutate: jest.fn(),
+ query: jest.fn(),
+ queries: {
+ alerts: {
+ loading,
+ },
+ },
+ },
+ },
+ stubs,
+ });
+ }
+
+ beforeEach(() => {
+ mountComponent({ data: { alerts: mockAlerts, alertsCount } });
+ });
+
+ afterEach(() => {
+ if (wrapper) {
+ wrapper.destroy();
+ }
+ });
+
+ describe('Status Filter Tabs', () => {
+ beforeEach(() => {
+ mountComponent({
+ props: { alertManagementEnabled: true, userCanEnableAlertManagement: true },
+ data: { alerts: mockAlerts, alertsCount },
+ loading: false,
+ stubs: {
+ GlTab: true,
+ },
+ });
+ });
+
+ it('should display filter tabs with alerts count badge for each status', () => {
+ const tabs = findStatusFilterTabs().wrappers;
+ const badges = findStatusFilterBadge();
+
+ tabs.forEach((tab, i) => {
+ const status = ALERTS_STATUS_TABS[i].status.toLowerCase();
+ expect(tab.text()).toContain(ALERTS_STATUS_TABS[i].title);
+ expect(badges.at(i).text()).toContain(alertsCount[status]);
+ });
+ });
+ });
+
+ describe('Alerts table', () => {
+ it('loading state', () => {
+ mountComponent({
+ props: { alertManagementEnabled: true, userCanEnableAlertManagement: true },
+ data: { alerts: {}, alertsCount: null },
+ loading: true,
+ });
+ expect(findAlertsTable().exists()).toBe(true);
+ expect(findLoader().exists()).toBe(true);
+ expect(
+ findAlerts()
+ .at(0)
+ .classes(),
+ ).not.toContain('gl-hover-bg-blue-50');
+ });
+
+ it('error state', () => {
+ mountComponent({
+ props: { alertManagementEnabled: true, userCanEnableAlertManagement: true },
+ data: { alerts: { errors: ['error'] }, alertsCount: null, errored: true },
+ loading: false,
+ });
+ expect(findAlertsTable().exists()).toBe(true);
+ expect(findAlertsTable().text()).toContain('No alerts to display');
+ expect(findLoader().exists()).toBe(false);
+ expect(findAlert().props().variant).toBe('danger');
+ expect(
+ findAlerts()
+ .at(0)
+ .classes(),
+ ).not.toContain('gl-hover-bg-blue-50');
+ });
+
+ it('empty state', () => {
+ mountComponent({
+ props: { alertManagementEnabled: true, userCanEnableAlertManagement: true },
+ data: { alerts: { list: [], pageInfo: {} }, alertsCount: { all: 0 }, errored: false },
+ loading: false,
+ });
+ expect(findAlertsTable().exists()).toBe(true);
+ expect(findAlertsTable().text()).toContain('No alerts to display');
+ expect(findLoader().exists()).toBe(false);
+ expect(findAlert().props().variant).toBe('info');
+ expect(
+ findAlerts()
+ .at(0)
+ .classes(),
+ ).not.toContain('gl-hover-bg-blue-50');
+ });
+
+ it('has data state', () => {
+ mountComponent({
+ props: { alertManagementEnabled: true, userCanEnableAlertManagement: true },
+ data: { alerts: { list: mockAlerts }, alertsCount, errored: false },
+ loading: false,
+ });
+ expect(findLoader().exists()).toBe(false);
+ expect(findAlertsTable().exists()).toBe(true);
+ expect(findAlerts()).toHaveLength(mockAlerts.length);
+ expect(
+ findAlerts()
+ .at(0)
+ .classes(),
+ ).toContain('gl-hover-bg-blue-50');
+ });
+
+ it('displays status dropdown', () => {
+ mountComponent({
+ props: { alertManagementEnabled: true, userCanEnableAlertManagement: true },
+ data: { alerts: { list: mockAlerts }, alertsCount, errored: false },
+ loading: false,
+ });
+ expect(findStatusDropdown().exists()).toBe(true);
+ });
+
+ it('does not display a dropdown status header', () => {
+ mountComponent({
+ props: { alertManagementEnabled: true, userCanEnableAlertManagement: true },
+ data: { alerts: { list: mockAlerts }, alertsCount, errored: false },
+ loading: false,
+ });
+ expect(findStatusDropdown().contains('.dropdown-title')).toBe(false);
+ });
+
+ it('shows correct severity icons', () => {
+ mountComponent({
+ props: { alertManagementEnabled: true, userCanEnableAlertManagement: true },
+ data: { alerts: { list: mockAlerts }, alertsCount, errored: false },
+ loading: false,
+ });
+
+ return wrapper.vm.$nextTick().then(() => {
+ expect(wrapper.find(GlTable).exists()).toBe(true);
+ expect(
+ findAlertsTable()
+ .find(GlIcon)
+ .classes('icon-critical'),
+ ).toBe(true);
+ });
+ });
+
+ it('renders severity text', () => {
+ mountComponent({
+ props: { alertManagementEnabled: true, userCanEnableAlertManagement: true },
+ data: { alerts: { list: mockAlerts }, alertsCount, errored: false },
+ loading: false,
+ });
+
+ expect(
+ findSeverityFields()
+ .at(0)
+ .text(),
+ ).toBe('Critical');
+ });
+
+ it('renders Unassigned when no assignee(s) present', () => {
+ mountComponent({
+ props: { alertManagementEnabled: true, userCanEnableAlertManagement: true },
+ data: { alerts: { list: mockAlerts }, alertsCount, errored: false },
+ loading: false,
+ });
+
+ expect(
+ findAssignees()
+ .at(0)
+ .text(),
+ ).toBe('Unassigned');
+ });
+
+ it('renders username(s) when assignee(s) present', () => {
+ mountComponent({
+ props: { alertManagementEnabled: true, userCanEnableAlertManagement: true },
+ data: { alerts: { list: mockAlerts }, alertsCount, errored: false },
+ loading: false,
+ });
+
+ expect(
+ findAssignees()
+ .at(1)
+ .text(),
+ ).toBe(mockAlerts[1].assignees.nodes[0].username);
+ });
+
+ it('navigates to the detail page when alert row is clicked', () => {
+ mountComponent({
+ props: { alertManagementEnabled: true, userCanEnableAlertManagement: true },
+ data: { alerts: { list: mockAlerts }, alertsCount, errored: false },
+ loading: false,
+ });
+
+ findAlerts()
+ .at(0)
+ .trigger('click');
+ expect(visitUrl).toHaveBeenCalledWith('/1527542/details');
+ });
+
+ describe('alert issue links', () => {
+ beforeEach(() => {
+ mountComponent({
+ props: { alertManagementEnabled: true, userCanEnableAlertManagement: true },
+ data: { alerts: { list: mockAlerts }, alertsCount, errored: false },
+ loading: false,
+ });
+ });
+
+ it('shows "None" when no link exists', () => {
+ expect(
+ findIssueFields()
+ .at(0)
+ .text(),
+ ).toBe('None');
+ });
+
+ it('renders a link when one exists', () => {
+ expect(
+ findIssueFields()
+ .at(1)
+ .text(),
+ ).toBe('#1');
+ expect(
+ findIssueFields()
+ .at(1)
+ .attributes('href'),
+ ).toBe('/gitlab-org/gitlab/-/issues/1');
+ });
+ });
+
+ describe('handle date fields', () => {
+ it('should display time ago dates when values provided', () => {
+ mountComponent({
+ props: { alertManagementEnabled: true, userCanEnableAlertManagement: true },
+ data: {
+ alerts: {
+ list: [
+ {
+ iid: 1,
+ status: 'acknowledged',
+ startedAt: '2020-03-17T23:18:14.996Z',
+ severity: 'high',
+ assignees: { nodes: [] },
+ },
+ ],
+ },
+ alertsCount,
+ errored: false,
+ },
+ loading: false,
+ });
+ expect(findDateFields().length).toBe(1);
+ });
+
+ it('should not display time ago dates when values not provided', () => {
+ mountComponent({
+ props: { alertManagementEnabled: true, userCanEnableAlertManagement: true },
+ data: {
+ alerts: [
+ {
+ iid: 1,
+ status: 'acknowledged',
+ startedAt: null,
+ severity: 'high',
+ },
+ ],
+ alertsCount,
+ errored: false,
+ },
+ loading: false,
+ });
+ expect(findDateFields().exists()).toBe(false);
+ });
+
+ describe('New Alert indicator', () => {
+ const oldAlert = mockAlerts[0];
+
+ const newAlert = { ...oldAlert, isNew: true };
+
+ it('should highlight the row when alert is new', () => {
+ mountComponent({
+ props: { alertManagementEnabled: true, userCanEnableAlertManagement: true },
+ data: { alerts: { list: [newAlert] }, alertsCount, errored: false },
+ loading: false,
+ });
+
+ expect(
+ findAlerts()
+ .at(0)
+ .classes(),
+ ).toContain('new-alert');
+ });
+
+ it('should not highlight the row when alert is not new', () => {
+ mountComponent({
+ props: { alertManagementEnabled: true, userCanEnableAlertManagement: true },
+ data: { alerts: { list: [oldAlert] }, alertsCount, errored: false },
+ loading: false,
+ });
+
+ expect(
+ findAlerts()
+ .at(0)
+ .classes(),
+ ).not.toContain('new-alert');
+ });
+ });
+ });
+ });
+
+ describe('sorting the alert list by column', () => {
+ beforeEach(() => {
+ mountComponent({
+ props: { alertManagementEnabled: true, userCanEnableAlertManagement: true },
+ data: {
+ alerts: { list: mockAlerts },
+ errored: false,
+ sort: 'STARTED_AT_DESC',
+ alertsCount,
+ },
+ loading: false,
+ stubs: { GlTable },
+ });
+ });
+
+ it('updates sort with new direction and column key', () => {
+ findSeverityColumnHeader().trigger('click');
+
+ expect(wrapper.vm.$data.sort).toBe('SEVERITY_DESC');
+
+ findSeverityColumnHeader().trigger('click');
+
+ expect(wrapper.vm.$data.sort).toBe('SEVERITY_ASC');
+ });
+ });
+
+ describe('updating the alert status', () => {
+ const iid = '1527542';
+ const mockUpdatedMutationResult = {
+ data: {
+ updateAlertStatus: {
+ errors: [],
+ alert: {
+ iid,
+ status: 'acknowledged',
+ },
+ },
+ },
+ };
+
+ beforeEach(() => {
+ mountComponent({
+ props: { alertManagementEnabled: true, userCanEnableAlertManagement: true },
+ data: { alerts: { list: mockAlerts }, alertsCount, errored: false },
+ loading: false,
+ });
+ });
+
+ it('calls `$apollo.mutate` with `updateAlertStatus` mutation and variables containing `iid`, `status`, & `projectPath`', () => {
+ jest.spyOn(wrapper.vm.$apollo, 'mutate').mockResolvedValue(mockUpdatedMutationResult);
+ findFirstStatusOption().vm.$emit('click');
+
+ expect(wrapper.vm.$apollo.mutate).toHaveBeenCalledWith({
+ mutation: updateAlertStatus,
+ variables: {
+ iid,
+ status: 'TRIGGERED',
+ projectPath: 'gitlab-org/gitlab',
+ },
+ });
+ });
+
+ it('shows an error when request fails', () => {
+ jest.spyOn(wrapper.vm.$apollo, 'mutate').mockReturnValue(Promise.reject(new Error()));
+ findFirstStatusOption().vm.$emit('click');
+ wrapper.setData({
+ errored: true,
+ });
+
+ return wrapper.vm.$nextTick(() => {
+ expect(wrapper.find('[data-testid="alert-error"]').exists()).toBe(true);
+ });
+ });
+
+ it('shows an error when response includes HTML errors', () => {
+ const mockUpdatedMutationErrorResult = {
+ data: {
+ updateAlertStatus: {
+ errors: ['<span data-testid="htmlError" />'],
+ alert: {
+ iid,
+ status: 'acknowledged',
+ },
+ },
+ },
+ };
+
+ jest.spyOn(wrapper.vm.$apollo, 'mutate').mockResolvedValue(mockUpdatedMutationErrorResult);
+ findFirstStatusOption().vm.$emit('click');
+ wrapper.setData({ errored: true });
+
+ return wrapper.vm.$nextTick(() => {
+ expect(wrapper.contains('[data-testid="alert-error"]')).toBe(true);
+ expect(wrapper.contains('[data-testid="htmlError"]')).toBe(true);
+ });
+ });
+ });
+
+ describe('Snowplow tracking', () => {
+ beforeEach(() => {
+ jest.spyOn(Tracking, 'event');
+ mountComponent({
+ props: { alertManagementEnabled: true, userCanEnableAlertManagement: true },
+ data: { alerts: { list: mockAlerts }, alertsCount },
+ loading: false,
+ });
+ });
+
+ it('should track alert status updates', () => {
+ Tracking.event.mockClear();
+ jest.spyOn(wrapper.vm.$apollo, 'mutate').mockResolvedValue({});
+ findFirstStatusOption().vm.$emit('click');
+ const status = findFirstStatusOption().text();
+ setImmediate(() => {
+ const { category, action, label } = trackAlertStatusUpdateOptions;
+ expect(Tracking.event).toHaveBeenCalledWith(category, action, { label, property: status });
+ });
+ });
+ });
+
+ describe('Pagination', () => {
+ beforeEach(() => {
+ mountComponent({
+ props: { alertManagementEnabled: true, userCanEnableAlertManagement: true },
+ data: { alerts: { list: mockAlerts, pageInfo: {} }, alertsCount, errored: false },
+ loading: false,
+ });
+ });
+
+ it('does NOT show pagination control when list is smaller than default page size', () => {
+ findStatusTabs().vm.$emit('input', 3);
+ return wrapper.vm.$nextTick(() => {
+ expect(findPagination().exists()).toBe(false);
+ });
+ });
+
+ it('shows pagination control when list is larger than default page size', () => {
+ findStatusTabs().vm.$emit('input', 0);
+ return wrapper.vm.$nextTick(() => {
+ expect(findPagination().exists()).toBe(true);
+ });
+ });
+
+ describe('prevPage', () => {
+ it('returns prevPage number', () => {
+ findPagination().vm.$emit('input', 3);
+
+ return wrapper.vm.$nextTick(() => {
+ expect(wrapper.vm.prevPage).toBe(2);
+ });
+ });
+
+ it('returns 0 when it is the first page', () => {
+ findPagination().vm.$emit('input', 1);
+
+ return wrapper.vm.$nextTick(() => {
+ expect(wrapper.vm.prevPage).toBe(0);
+ });
+ });
+ });
+
+ describe('nextPage', () => {
+ it('returns nextPage number', () => {
+ findPagination().vm.$emit('input', 1);
+
+ return wrapper.vm.$nextTick(() => {
+ expect(wrapper.vm.nextPage).toBe(2);
+ });
+ });
+
+ it('returns `null` when currentPage is already last page', () => {
+ findStatusTabs().vm.$emit('input', 3);
+ findPagination().vm.$emit('input', 1);
+ return wrapper.vm.$nextTick(() => {
+ expect(wrapper.vm.nextPage).toBeNull();
+ });
+ });
+ });
+ });
+
+ describe('Search', () => {
+ beforeEach(() => {
+ mountComponent({
+ props: { alertManagementEnabled: true, userCanEnableAlertManagement: true },
+ data: { alerts: { list: mockAlerts }, alertsCount, errored: false },
+ loading: false,
+ });
+ });
+
+ it('renders the search component', () => {
+ expect(findSearch().exists()).toBe(true);
+ });
+
+ it('sets the `searchTerm` graphql variable', () => {
+ const SEARCH_TERM = 'Simple Alert';
+
+ findSearch().vm.$emit('input', SEARCH_TERM);
+
+ expect(wrapper.vm.$data.searchTerm).toBe(SEARCH_TERM);
+ });
+ });
+});
diff --git a/spec/frontend/alert_management/components/alert_managment_sidebar_assignees_spec.js b/spec/frontend/alert_management/components/alert_managment_sidebar_assignees_spec.js
deleted file mode 100644
index 5dbd83dbdac..00000000000
--- a/spec/frontend/alert_management/components/alert_managment_sidebar_assignees_spec.js
+++ /dev/null
@@ -1,133 +0,0 @@
-import { shallowMount } from '@vue/test-utils';
-import axios from 'axios';
-import MockAdapter from 'axios-mock-adapter';
-import { GlDropdownItem } from '@gitlab/ui';
-import SidebarAssignee from '~/alert_management/components/sidebar/sidebar_assignee.vue';
-import SidebarAssignees from '~/alert_management/components/sidebar/sidebar_assignees.vue';
-import AlertSetAssignees from '~/alert_management/graphql/mutations/alert_set_assignees.graphql';
-import mockAlerts from '../mocks/alerts.json';
-
-const mockAlert = mockAlerts[0];
-
-describe('Alert Details Sidebar Assignees', () => {
- let wrapper;
- let mock;
-
- function mountComponent({
- data,
- users = [],
- isDropdownSearching = false,
- sidebarCollapsed = true,
- loading = false,
- stubs = {},
- } = {}) {
- wrapper = shallowMount(SidebarAssignees, {
- data() {
- return {
- users,
- isDropdownSearching,
- };
- },
- propsData: {
- alert: { ...mockAlert },
- ...data,
- sidebarCollapsed,
- projectPath: 'projectPath',
- },
- mocks: {
- $apollo: {
- mutate: jest.fn(),
- queries: {
- alert: {
- loading,
- },
- },
- },
- },
- stubs,
- });
- }
-
- afterEach(() => {
- if (wrapper) {
- wrapper.destroy();
- }
- mock.restore();
- });
-
- describe('updating the alert status', () => {
- const mockUpdatedMutationResult = {
- data: {
- updateAlertStatus: {
- errors: [],
- alert: {
- assigneeUsernames: ['root'],
- },
- },
- },
- };
-
- beforeEach(() => {
- mock = new MockAdapter(axios);
- const path = '/autocomplete/users.json';
- const users = [
- {
- avatar_url:
- 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon',
- id: 1,
- name: 'User 1',
- username: 'root',
- },
- {
- avatar_url:
- 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon',
- id: 2,
- name: 'User 2',
- username: 'not-root',
- },
- ];
-
- mock.onGet(path).replyOnce(200, users);
- mountComponent({
- data: { alert: mockAlert },
- sidebarCollapsed: false,
- loading: false,
- users,
- stubs: {
- SidebarAssignee,
- },
- });
- });
-
- it('renders a unassigned option', () => {
- wrapper.setData({ isDropdownSearching: false });
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.find(GlDropdownItem).text()).toBe('Unassigned');
- });
- });
-
- it('calls `$apollo.mutate` with `AlertSetAssignees` mutation and variables containing `iid`, `assigneeUsernames`, & `projectPath`', () => {
- jest.spyOn(wrapper.vm.$apollo, 'mutate').mockResolvedValue(mockUpdatedMutationResult);
- wrapper.setData({ isDropdownSearching: false });
-
- return wrapper.vm.$nextTick().then(() => {
- wrapper.find(SidebarAssignee).vm.$emit('update-alert-assignees', 'root');
-
- expect(wrapper.vm.$apollo.mutate).toHaveBeenCalledWith({
- mutation: AlertSetAssignees,
- variables: {
- iid: '1527542',
- assigneeUsernames: ['root'],
- projectPath: 'projectPath',
- },
- });
- });
- });
-
- it('stops updating and cancels loading when the request fails', () => {
- jest.spyOn(wrapper.vm.$apollo, 'mutate').mockReturnValue(Promise.reject(new Error()));
- wrapper.vm.updateAlertAssignees('root');
- expect(wrapper.find('[data-testid="unassigned-users"]').text()).toBe('assign yourself');
- });
- });
-});
diff --git a/spec/frontend/alert_management/components/alert_metrics_spec.js b/spec/frontend/alert_management/components/alert_metrics_spec.js
new file mode 100644
index 00000000000..c188363ddc2
--- /dev/null
+++ b/spec/frontend/alert_management/components/alert_metrics_spec.js
@@ -0,0 +1,67 @@
+import { shallowMount } from '@vue/test-utils';
+import waitForPromises from 'helpers/wait_for_promises';
+import AlertMetrics from '~/alert_management/components/alert_metrics.vue';
+import MockAdapter from 'axios-mock-adapter';
+import axios from 'axios';
+
+jest.mock('~/monitoring/stores', () => ({
+ monitoringDashboard: {},
+}));
+
+const mockEmbedName = 'MetricsEmbedStub';
+
+jest.mock('~/monitoring/components/embeds/metric_embed.vue', () => ({
+ name: mockEmbedName,
+ render(h) {
+ return h('div');
+ },
+}));
+
+describe('Alert Metrics', () => {
+ let wrapper;
+ const mock = new MockAdapter(axios);
+
+ function mountComponent({ props } = {}) {
+ wrapper = shallowMount(AlertMetrics, {
+ propsData: {
+ ...props,
+ },
+ stubs: {
+ MetricEmbed: true,
+ },
+ });
+ }
+
+ const findChart = () => wrapper.find({ name: mockEmbedName });
+ const findEmptyState = () => wrapper.find({ ref: 'emptyState' });
+
+ afterEach(() => {
+ if (wrapper) {
+ wrapper.destroy();
+ }
+ });
+
+ afterAll(() => {
+ mock.restore();
+ });
+
+ describe('Empty state', () => {
+ it('should display a message when metrics dashboard url is not provided ', () => {
+ mountComponent();
+ expect(findChart().exists()).toBe(false);
+ expect(findEmptyState().text()).toBe("Metrics weren't available in the alerts payload.");
+ });
+ });
+
+ describe('Chart', () => {
+ it('should be rendered when dashboard url is provided', async () => {
+ mountComponent({ props: { dashboardUrl: 'metrics.url' } });
+
+ await waitForPromises();
+ await wrapper.vm.$nextTick();
+
+ expect(findEmptyState().exists()).toBe(false);
+ expect(findChart().exists()).toBe(true);
+ });
+ });
+});
diff --git a/spec/frontend/alert_management/components/alert_sidebar_spec.js b/spec/frontend/alert_management/components/alert_sidebar_spec.js
deleted file mode 100644
index 80c4d9e0650..00000000000
--- a/spec/frontend/alert_management/components/alert_sidebar_spec.js
+++ /dev/null
@@ -1,55 +0,0 @@
-import { shallowMount, mount } from '@vue/test-utils';
-import axios from 'axios';
-import MockAdapter from 'axios-mock-adapter';
-import AlertSidebar from '~/alert_management/components/alert_sidebar.vue';
-import SidebarAssignees from '~/alert_management/components/sidebar/sidebar_assignees.vue';
-import mockAlerts from '../mocks/alerts.json';
-
-const mockAlert = mockAlerts[0];
-
-describe('Alert Details Sidebar', () => {
- let wrapper;
- let mock;
-
- function mountComponent({
- sidebarCollapsed = true,
- mountMethod = shallowMount,
- stubs = {},
- alert = {},
- } = {}) {
- wrapper = mountMethod(AlertSidebar, {
- propsData: {
- alert,
- sidebarCollapsed,
- projectPath: 'projectPath',
- },
- stubs,
- });
- }
-
- afterEach(() => {
- if (wrapper) {
- wrapper.destroy();
- }
- mock.restore();
- });
-
- describe('the sidebar renders', () => {
- beforeEach(() => {
- mock = new MockAdapter(axios);
- mountComponent();
- });
-
- it('open as default', () => {
- expect(wrapper.props('sidebarCollapsed')).toBe(true);
- });
-
- it('should render side bar assignee dropdown', () => {
- mountComponent({
- mountMethod: mount,
- alert: mockAlert,
- });
- expect(wrapper.find(SidebarAssignees).exists()).toBe(true);
- });
- });
-});
diff --git a/spec/frontend/alert_management/components/alert_sidebar_status_spec.js b/spec/frontend/alert_management/components/alert_sidebar_status_spec.js
deleted file mode 100644
index 94643966a43..00000000000
--- a/spec/frontend/alert_management/components/alert_sidebar_status_spec.js
+++ /dev/null
@@ -1,107 +0,0 @@
-import { shallowMount } from '@vue/test-utils';
-import { GlDropdownItem, GlLoadingIcon } from '@gitlab/ui';
-import { trackAlertStatusUpdateOptions } from '~/alert_management/constants';
-import AlertSidebarStatus from '~/alert_management/components/sidebar/sidebar_status.vue';
-import updateAlertStatus from '~/alert_management/graphql/mutations/update_alert_status.graphql';
-import Tracking from '~/tracking';
-import mockAlerts from '../mocks/alerts.json';
-
-const mockAlert = mockAlerts[0];
-
-describe('Alert Details Sidebar Status', () => {
- let wrapper;
- const findStatusDropdownItem = () => wrapper.find(GlDropdownItem);
- const findStatusLoadingIcon = () => wrapper.find(GlLoadingIcon);
-
- function mountComponent({ data, sidebarCollapsed = true, loading = false, stubs = {} } = {}) {
- wrapper = shallowMount(AlertSidebarStatus, {
- propsData: {
- alert: { ...mockAlert },
- ...data,
- sidebarCollapsed,
- projectPath: 'projectPath',
- },
- mocks: {
- $apollo: {
- mutate: jest.fn(),
- queries: {
- alert: {
- loading,
- },
- },
- },
- },
- stubs,
- });
- }
-
- afterEach(() => {
- if (wrapper) {
- wrapper.destroy();
- }
- });
-
- describe('updating the alert status', () => {
- const mockUpdatedMutationResult = {
- data: {
- updateAlertStatus: {
- errors: [],
- alert: {
- status: 'acknowledged',
- },
- },
- },
- };
-
- beforeEach(() => {
- mountComponent({
- data: { alert: mockAlert },
- sidebarCollapsed: false,
- loading: false,
- });
- });
-
- it('calls `$apollo.mutate` with `updateAlertStatus` mutation and variables containing `iid`, `status`, & `projectPath`', () => {
- jest.spyOn(wrapper.vm.$apollo, 'mutate').mockResolvedValue(mockUpdatedMutationResult);
- findStatusDropdownItem().vm.$emit('click');
-
- expect(wrapper.vm.$apollo.mutate).toHaveBeenCalledWith({
- mutation: updateAlertStatus,
- variables: {
- iid: '1527542',
- status: 'TRIGGERED',
- projectPath: 'projectPath',
- },
- });
- });
-
- it('stops updating when the request fails', () => {
- jest.spyOn(wrapper.vm.$apollo, 'mutate').mockReturnValue(Promise.reject(new Error()));
- findStatusDropdownItem().vm.$emit('click');
- expect(findStatusLoadingIcon().exists()).toBe(false);
- expect(wrapper.find('[data-testid="status"]').text()).toBe('Triggered');
- });
- });
-
- describe('Snowplow tracking', () => {
- beforeEach(() => {
- jest.spyOn(Tracking, 'event');
- mountComponent({
- props: { alertManagementEnabled: true, userCanEnableAlertManagement: true },
- data: { alert: mockAlert },
- loading: false,
- });
- });
-
- it('should track alert status updates', () => {
- Tracking.event.mockClear();
- jest.spyOn(wrapper.vm.$apollo, 'mutate').mockResolvedValue({});
- findStatusDropdownItem().vm.$emit('click');
- const status = findStatusDropdownItem().text();
- setImmediate(() => {
- const { category, action, label } = trackAlertStatusUpdateOptions;
- expect(Tracking.event).toHaveBeenCalledWith(category, action, { label, property: status });
- });
- });
- });
-});
diff --git a/spec/frontend/alert_management/components/sidebar/alert_managment_sidebar_assignees_spec.js b/spec/frontend/alert_management/components/sidebar/alert_managment_sidebar_assignees_spec.js
new file mode 100644
index 00000000000..db086782424
--- /dev/null
+++ b/spec/frontend/alert_management/components/sidebar/alert_managment_sidebar_assignees_spec.js
@@ -0,0 +1,154 @@
+import { shallowMount } from '@vue/test-utils';
+import axios from 'axios';
+import MockAdapter from 'axios-mock-adapter';
+import { GlDropdownItem } from '@gitlab/ui';
+import SidebarAssignee from '~/alert_management/components/sidebar/sidebar_assignee.vue';
+import SidebarAssignees from '~/alert_management/components/sidebar/sidebar_assignees.vue';
+import AlertSetAssignees from '~/alert_management/graphql/mutations/alert_set_assignees.mutation.graphql';
+import mockAlerts from '../../mocks/alerts.json';
+
+const mockAlert = mockAlerts[0];
+
+describe('Alert Details Sidebar Assignees', () => {
+ let wrapper;
+ let mock;
+
+ function mountComponent({
+ data,
+ users = [],
+ isDropdownSearching = false,
+ sidebarCollapsed = true,
+ loading = false,
+ stubs = {},
+ } = {}) {
+ wrapper = shallowMount(SidebarAssignees, {
+ data() {
+ return {
+ users,
+ isDropdownSearching,
+ };
+ },
+ propsData: {
+ alert: { ...mockAlert },
+ ...data,
+ sidebarCollapsed,
+ projectPath: 'projectPath',
+ projectId: '1',
+ },
+ mocks: {
+ $apollo: {
+ mutate: jest.fn(),
+ queries: {
+ alert: {
+ loading,
+ },
+ },
+ },
+ },
+ stubs,
+ });
+ }
+
+ afterEach(() => {
+ if (wrapper) {
+ wrapper.destroy();
+ }
+ mock.restore();
+ });
+
+ describe('updating the alert status', () => {
+ const mockUpdatedMutationResult = {
+ data: {
+ alertSetAssignees: {
+ errors: [],
+ alert: {
+ assigneeUsernames: ['root'],
+ },
+ },
+ },
+ };
+
+ beforeEach(() => {
+ mock = new MockAdapter(axios);
+ const path = '/-/autocomplete/users.json';
+ const users = [
+ {
+ avatar_url:
+ 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon',
+ id: 1,
+ name: 'User 1',
+ username: 'root',
+ },
+ {
+ avatar_url:
+ 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon',
+ id: 2,
+ name: 'User 2',
+ username: 'not-root',
+ },
+ ];
+
+ mock.onGet(path).replyOnce(200, users);
+ mountComponent({
+ data: { alert: mockAlert },
+ sidebarCollapsed: false,
+ loading: false,
+ users,
+ stubs: {
+ SidebarAssignee,
+ },
+ });
+ });
+
+ it('renders a unassigned option', () => {
+ wrapper.setData({ isDropdownSearching: false });
+ return wrapper.vm.$nextTick().then(() => {
+ expect(wrapper.find(GlDropdownItem).text()).toBe('Unassigned');
+ });
+ });
+
+ it('calls `$apollo.mutate` with `AlertSetAssignees` mutation and variables containing `iid`, `assigneeUsernames`, & `projectPath`', () => {
+ jest.spyOn(wrapper.vm.$apollo, 'mutate').mockResolvedValue(mockUpdatedMutationResult);
+ wrapper.setData({ isDropdownSearching: false });
+
+ return wrapper.vm.$nextTick().then(() => {
+ wrapper.find(SidebarAssignee).vm.$emit('update-alert-assignees', 'root');
+
+ expect(wrapper.vm.$apollo.mutate).toHaveBeenCalledWith({
+ mutation: AlertSetAssignees,
+ variables: {
+ iid: '1527542',
+ assigneeUsernames: ['root'],
+ projectPath: 'projectPath',
+ },
+ });
+ });
+ });
+
+ it('shows an error when request contains error messages', () => {
+ wrapper.setData({ isDropdownSearching: false });
+ const errorMutationResult = {
+ data: {
+ alertSetAssignees: {
+ errors: ['There was a problem for sure.'],
+ alert: {},
+ },
+ },
+ };
+
+ jest.spyOn(wrapper.vm.$apollo, 'mutate').mockResolvedValue(errorMutationResult);
+
+ return wrapper.vm.$nextTick().then(() => {
+ const SideBarAssigneeItem = wrapper.findAll(SidebarAssignee).at(0);
+ SideBarAssigneeItem.vm.$emit('click');
+ expect(wrapper.emitted('alert-refresh')).toBeUndefined();
+ });
+ });
+
+ it('stops updating and cancels loading when the request fails', () => {
+ jest.spyOn(wrapper.vm.$apollo, 'mutate').mockReturnValue(Promise.reject(new Error()));
+ wrapper.vm.updateAlertAssignees('root');
+ expect(wrapper.find('[data-testid="unassigned-users"]').text()).toBe('assign yourself');
+ });
+ });
+});
diff --git a/spec/frontend/alert_management/components/sidebar/alert_sidebar_spec.js b/spec/frontend/alert_management/components/sidebar/alert_sidebar_spec.js
new file mode 100644
index 00000000000..5235ae63fee
--- /dev/null
+++ b/spec/frontend/alert_management/components/sidebar/alert_sidebar_spec.js
@@ -0,0 +1,64 @@
+import { shallowMount, mount } from '@vue/test-utils';
+import axios from 'axios';
+import MockAdapter from 'axios-mock-adapter';
+import AlertSidebar from '~/alert_management/components/alert_sidebar.vue';
+import SidebarAssignees from '~/alert_management/components/sidebar/sidebar_assignees.vue';
+import mockAlerts from '../../mocks/alerts.json';
+
+const mockAlert = mockAlerts[0];
+
+describe('Alert Details Sidebar', () => {
+ let wrapper;
+ let mock;
+
+ function mountComponent({ mountMethod = shallowMount, stubs = {}, alert = {} } = {}) {
+ wrapper = mountMethod(AlertSidebar, {
+ data() {
+ return {
+ sidebarStatus: false,
+ };
+ },
+ propsData: {
+ alert,
+ },
+ provide: {
+ projectPath: 'projectPath',
+ projectId: '1',
+ },
+ stubs,
+ mocks: {
+ $apollo: {
+ queries: {
+ sidebarStatus: {},
+ },
+ },
+ },
+ });
+ }
+
+ afterEach(() => {
+ if (wrapper) {
+ wrapper.destroy();
+ }
+ mock.restore();
+ });
+
+ describe('the sidebar renders', () => {
+ beforeEach(() => {
+ mock = new MockAdapter(axios);
+ mountComponent();
+ });
+
+ it('open as default', () => {
+ expect(wrapper.classes('right-sidebar-expanded')).toBe(true);
+ });
+
+ it('should render side bar assignee dropdown', () => {
+ mountComponent({
+ mountMethod: mount,
+ alert: mockAlert,
+ });
+ expect(wrapper.find(SidebarAssignees).exists()).toBe(true);
+ });
+ });
+});
diff --git a/spec/frontend/alert_management/components/sidebar/alert_sidebar_status_spec.js b/spec/frontend/alert_management/components/sidebar/alert_sidebar_status_spec.js
new file mode 100644
index 00000000000..c2eaf540e9c
--- /dev/null
+++ b/spec/frontend/alert_management/components/sidebar/alert_sidebar_status_spec.js
@@ -0,0 +1,129 @@
+import { mount } from '@vue/test-utils';
+import { GlDropdown, GlDropdownItem, GlLoadingIcon } from '@gitlab/ui';
+import { trackAlertStatusUpdateOptions } from '~/alert_management/constants';
+import AlertSidebarStatus from '~/alert_management/components/sidebar/sidebar_status.vue';
+import updateAlertStatus from '~/alert_management/graphql/mutations/update_alert_status.mutation.graphql';
+import Tracking from '~/tracking';
+import mockAlerts from '../../mocks/alerts.json';
+
+const mockAlert = mockAlerts[0];
+
+describe('Alert Details Sidebar Status', () => {
+ let wrapper;
+ const findStatusDropdown = () => wrapper.find(GlDropdown);
+ const findStatusDropdownItem = () => wrapper.find(GlDropdownItem);
+ const findStatusLoadingIcon = () => wrapper.find(GlLoadingIcon);
+
+ function mountComponent({ data, sidebarCollapsed = true, loading = false, stubs = {} } = {}) {
+ wrapper = mount(AlertSidebarStatus, {
+ propsData: {
+ alert: { ...mockAlert },
+ ...data,
+ sidebarCollapsed,
+ projectPath: 'projectPath',
+ },
+ mocks: {
+ $apollo: {
+ mutate: jest.fn(),
+ queries: {
+ alert: {
+ loading,
+ },
+ },
+ },
+ },
+ stubs,
+ });
+ }
+
+ afterEach(() => {
+ if (wrapper) {
+ wrapper.destroy();
+ }
+ });
+
+ describe('Alert Sidebar Dropdown Status', () => {
+ beforeEach(() => {
+ mountComponent({
+ data: { alert: mockAlert },
+ sidebarCollapsed: false,
+ loading: false,
+ });
+ });
+
+ it('displays status dropdown', () => {
+ expect(findStatusDropdown().exists()).toBe(true);
+ });
+
+ it('displays the dropdown status header', () => {
+ expect(findStatusDropdown().contains('.dropdown-title')).toBe(true);
+ });
+
+ describe('updating the alert status', () => {
+ const mockUpdatedMutationResult = {
+ data: {
+ updateAlertStatus: {
+ errors: [],
+ alert: {
+ status: 'acknowledged',
+ },
+ },
+ },
+ };
+
+ beforeEach(() => {
+ mountComponent({
+ data: { alert: mockAlert },
+ sidebarCollapsed: false,
+ loading: false,
+ });
+ });
+
+ it('calls `$apollo.mutate` with `updateAlertStatus` mutation and variables containing `iid`, `status`, & `projectPath`', () => {
+ jest.spyOn(wrapper.vm.$apollo, 'mutate').mockResolvedValue(mockUpdatedMutationResult);
+ findStatusDropdownItem().vm.$emit('click');
+
+ expect(wrapper.vm.$apollo.mutate).toHaveBeenCalledWith({
+ mutation: updateAlertStatus,
+ variables: {
+ iid: '1527542',
+ status: 'TRIGGERED',
+ projectPath: 'projectPath',
+ },
+ });
+ });
+
+ it('stops updating when the request fails', () => {
+ jest.spyOn(wrapper.vm.$apollo, 'mutate').mockReturnValue(Promise.reject(new Error()));
+ findStatusDropdownItem().vm.$emit('click');
+ expect(findStatusLoadingIcon().exists()).toBe(false);
+ expect(wrapper.find('[data-testid="status"]').text()).toBe('Triggered');
+ });
+ });
+
+ describe('Snowplow tracking', () => {
+ beforeEach(() => {
+ jest.spyOn(Tracking, 'event');
+ mountComponent({
+ props: { alertManagementEnabled: true, userCanEnableAlertManagement: true },
+ data: { alert: mockAlert },
+ loading: false,
+ });
+ });
+
+ it('should track alert status updates', () => {
+ Tracking.event.mockClear();
+ jest.spyOn(wrapper.vm.$apollo, 'mutate').mockResolvedValue({});
+ findStatusDropdownItem().vm.$emit('click');
+ const status = findStatusDropdownItem().text();
+ setImmediate(() => {
+ const { category, action, label } = trackAlertStatusUpdateOptions;
+ expect(Tracking.event).toHaveBeenCalledWith(category, action, {
+ label,
+ property: status,
+ });
+ });
+ });
+ });
+ });
+});
diff --git a/spec/frontend/alert_management/components/system_notes/alert_management_system_note_spec.js b/spec/frontend/alert_management/components/system_notes/alert_management_system_note_spec.js
new file mode 100644
index 00000000000..8dd663e55d9
--- /dev/null
+++ b/spec/frontend/alert_management/components/system_notes/alert_management_system_note_spec.js
@@ -0,0 +1,38 @@
+import { shallowMount } from '@vue/test-utils';
+import SystemNote from '~/alert_management/components/system_notes/system_note.vue';
+import mockAlerts from '../../mocks/alerts.json';
+
+const mockAlert = mockAlerts[1];
+
+describe('Alert Details System Note', () => {
+ let wrapper;
+
+ function mountComponent({ stubs = {} } = {}) {
+ wrapper = shallowMount(SystemNote, {
+ propsData: {
+ note: { ...mockAlert.notes.nodes[0] },
+ },
+ stubs,
+ });
+ }
+
+ afterEach(() => {
+ if (wrapper) {
+ wrapper.destroy();
+ }
+ });
+
+ describe('System notes', () => {
+ beforeEach(() => {
+ mountComponent({});
+ });
+
+ it('renders the correct system note', () => {
+ const noteId = wrapper.find('.note-wrapper').attributes('id');
+ const iconRoute = wrapper.find('use').attributes('href');
+
+ expect(noteId).toBe('note_1628');
+ expect(iconRoute.includes('user')).toBe(true);
+ });
+ });
+});
diff --git a/spec/frontend/alert_management/mocks/alerts.json b/spec/frontend/alert_management/mocks/alerts.json
index 312d1756790..f63019d1e5c 100644
--- a/spec/frontend/alert_management/mocks/alerts.json
+++ b/spec/frontend/alert_management/mocks/alerts.json
@@ -20,6 +20,7 @@
"endedAt": "2020-04-17T23:18:14.996Z",
"status": "ACKNOWLEDGED",
"assignees": { "nodes": [{ "username": "root" }] },
+ "issueIid": "1",
"notes": {
"nodes": [
{
@@ -32,7 +33,8 @@
"name": "Administrator",
"username": "root",
"webUrl": "http://192.168.1.4:3000/root"
- }
+ },
+ "systemNoteIconName": "user"
}
]
}
diff --git a/spec/frontend/alert_settings/__snapshots__/alert_settings_form_spec.js.snap b/spec/frontend/alert_settings/__snapshots__/alert_settings_form_spec.js.snap
new file mode 100644
index 00000000000..1f5c3a80fbb
--- /dev/null
+++ b/spec/frontend/alert_settings/__snapshots__/alert_settings_form_spec.js.snap
@@ -0,0 +1,48 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`AlertsSettingsForm with default values renders the initial template 1`] = `
+"<div>
+ <!---->
+ <div data-testid=\\"alert-settings-description\\" class=\\"gl-mt-5\\">
+ <p>
+ <gl-sprintf-stub message=\\"You must provide this URL and authorization key to authorize an external service to send alerts to GitLab. You can provide this URL and key to multiple services. After configuring an external service, alerts from your service will display on the GitLab %{linkStart}Alerts%{linkEnd} page.\\"></gl-sprintf-stub>
+ </p>
+ <p>
+ <gl-sprintf-stub message=\\"Review your external service's documentation to learn where to provide this information to your external service, and the %{linkStart}GitLab documentation%{linkEnd} to learn more about configuring your endpoint.\\"></gl-sprintf-stub>
+ </p>
+ </div>
+ <gl-form-stub>
+ <gl-form-group-stub label=\\"Integrations\\" label-for=\\"integrations\\" label-class=\\"label-bold\\">
+ <gl-form-select-stub options=\\"[object Object],[object Object],[object Object]\\" data-testid=\\"alert-settings-select\\" value=\\"generic\\"></gl-form-select-stub> <span class=\\"gl-text-gray-400\\"><gl-sprintf-stub message=\\"Learn more about our %{linkStart}upcoming integrations%{linkEnd}\\"></gl-sprintf-stub></span>
+ </gl-form-group-stub>
+ <gl-form-group-stub label=\\"Active\\" label-for=\\"activated\\" label-class=\\"label-bold\\">
+ <toggle-button-stub id=\\"activated\\"></toggle-button-stub>
+ </gl-form-group-stub>
+ <!---->
+ <gl-form-group-stub label=\\"Webhook URL\\" label-for=\\"url\\" label-class=\\"label-bold\\">
+ <gl-form-input-group-stub value=\\"/alerts/notify.json\\" predefinedoptions=\\"[object Object]\\" id=\\"url\\" readonly=\\"\\"></gl-form-input-group-stub> <span class=\\"gl-text-gray-400\\">
+
+ </span>
+ </gl-form-group-stub>
+ <gl-form-group-stub label=\\"Authorization key\\" label-for=\\"authorization-key\\" label-class=\\"label-bold\\">
+ <gl-form-input-group-stub value=\\"abcedfg123\\" predefinedoptions=\\"[object Object]\\" id=\\"authorization-key\\" readonly=\\"\\" class=\\"gl-mb-2\\"></gl-form-input-group-stub>
+ <gl-button-stub category=\\"tertiary\\" variant=\\"default\\" size=\\"medium\\" icon=\\"\\" disabled=\\"true\\" class=\\"gl-mt-3\\" role=\\"button\\" tabindex=\\"0\\">Reset key</gl-button-stub>
+ <gl-modal-stub modalid=\\"authKeyModal\\" titletag=\\"h4\\" modalclass=\\"\\" size=\\"md\\" title=\\"Reset key\\" ok-title=\\"Reset key\\" ok-variant=\\"danger\\">
+ Resetting the authorization key for this project will require updating the authorization key in every alert source it is enabled in.
+ </gl-modal-stub>
+ </gl-form-group-stub>
+ <gl-form-group-stub label=\\"Alert test payload\\" label-for=\\"alert-json\\" label-class=\\"label-bold\\">
+ <gl-form-textarea-stub noresize=\\"true\\" id=\\"alert-json\\" disabled=\\"true\\" state=\\"true\\" placeholder=\\"Enter test alert JSON....\\" rows=\\"6\\" max-rows=\\"10\\"></gl-form-textarea-stub>
+ </gl-form-group-stub>
+ <gl-button-stub category=\\"tertiary\\" variant=\\"default\\" size=\\"medium\\" icon=\\"\\" disabled=\\"true\\">Test alert payload</gl-button-stub>
+ <div class=\\"footer-block row-content-block gl-display-flex gl-justify-content-space-between\\">
+ <gl-button-stub category=\\"primary\\" variant=\\"success\\" size=\\"medium\\" icon=\\"\\" disabled=\\"true\\">
+ Save changes
+ </gl-button-stub>
+ <gl-button-stub category=\\"primary\\" variant=\\"default\\" size=\\"medium\\" icon=\\"\\" disabled=\\"true\\">
+ Cancel
+ </gl-button-stub>
+ </div>
+ </gl-form-stub>
+</div>"
+`;
diff --git a/spec/frontend/alert_settings/alert_settings_form_spec.js b/spec/frontend/alert_settings/alert_settings_form_spec.js
new file mode 100644
index 00000000000..5a04d768645
--- /dev/null
+++ b/spec/frontend/alert_settings/alert_settings_form_spec.js
@@ -0,0 +1,233 @@
+import axios from 'axios';
+import MockAdapter from 'axios-mock-adapter';
+import { shallowMount } from '@vue/test-utils';
+import { GlModal, GlAlert } from '@gitlab/ui';
+import AlertsSettingsForm from '~/alerts_settings/components/alerts_settings_form.vue';
+import ToggleButton from '~/vue_shared/components/toggle_button.vue';
+
+const PROMETHEUS_URL = '/prometheus/alerts/notify.json';
+const GENERIC_URL = '/alerts/notify.json';
+const KEY = 'abcedfg123';
+const INVALID_URL = 'http://invalid';
+const ACTIVATED = false;
+
+const defaultProps = {
+ generic: {
+ initialAuthorizationKey: KEY,
+ formPath: INVALID_URL,
+ url: GENERIC_URL,
+ alertsSetupUrl: INVALID_URL,
+ alertsUsageUrl: INVALID_URL,
+ activated: ACTIVATED,
+ },
+ prometheus: {
+ prometheusAuthorizationKey: KEY,
+ prometheusFormPath: INVALID_URL,
+ prometheusUrl: PROMETHEUS_URL,
+ activated: ACTIVATED,
+ },
+ opsgenie: {
+ opsgenieMvcIsAvailable: true,
+ formPath: INVALID_URL,
+ activated: ACTIVATED,
+ opsgenieMvcTargetUrl: GENERIC_URL,
+ },
+};
+
+describe('AlertsSettingsForm', () => {
+ let wrapper;
+ let mockAxios;
+
+ const createComponent = (props = defaultProps, { methods } = {}, data) => {
+ wrapper = shallowMount(AlertsSettingsForm, {
+ data() {
+ return { ...data };
+ },
+ propsData: {
+ ...defaultProps,
+ ...props,
+ },
+ methods,
+ });
+ };
+
+ const findSelect = () => wrapper.find('[data-testid="alert-settings-select"]');
+ const findJsonInput = () => wrapper.find('#alert-json');
+ const findUrl = () => wrapper.find('#url');
+ const findAuthorizationKey = () => wrapper.find('#authorization-key');
+ const findApiUrl = () => wrapper.find('#api-url');
+
+ beforeEach(() => {
+ mockAxios = new MockAdapter(axios);
+ setFixtures(`
+ <div>
+ <span class="js-service-active-status fa fa-circle" data-value="true"></span>
+ <span class="js-service-active-status fa fa-power-off" data-value="false"></span>
+ </div>`);
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ mockAxios.restore();
+ });
+
+ describe('with default values', () => {
+ beforeEach(() => {
+ createComponent();
+ });
+
+ it('renders the initial template', () => {
+ expect(wrapper.html()).toMatchSnapshot();
+ });
+ });
+
+ describe('reset key', () => {
+ it('triggers resetKey method', () => {
+ const resetGenericKey = jest.fn();
+ const methods = { resetGenericKey };
+ createComponent(defaultProps, { methods });
+
+ wrapper.find(GlModal).vm.$emit('ok');
+
+ expect(resetGenericKey).toHaveBeenCalled();
+ });
+
+ it('updates the authorization key on success', () => {
+ const formPath = 'some/path';
+ mockAxios.onPut(formPath, { service: { token: '' } }).replyOnce(200, { token: 'newToken' });
+ createComponent({ generic: { ...defaultProps.generic, formPath } });
+
+ return wrapper.vm.resetGenericKey().then(() => {
+ expect(findAuthorizationKey().attributes('value')).toBe('newToken');
+ });
+ });
+
+ it('shows a alert message on error', () => {
+ const formPath = 'some/path';
+ mockAxios.onPut(formPath).replyOnce(404);
+
+ createComponent({ generic: { ...defaultProps.generic, formPath } });
+
+ return wrapper.vm.resetGenericKey().then(() => {
+ expect(wrapper.find(GlAlert).exists()).toBe(true);
+ });
+ });
+ });
+
+ describe('activate toggle', () => {
+ it('triggers toggleActivated method', () => {
+ const toggleService = jest.fn();
+ const methods = { toggleService };
+ createComponent(defaultProps, { methods });
+
+ wrapper.find(ToggleButton).vm.$emit('change', true);
+
+ expect(toggleService).toHaveBeenCalled();
+ });
+
+ describe('error is encountered', () => {
+ beforeEach(() => {
+ const formPath = 'some/path';
+ mockAxios.onPut(formPath).replyOnce(500);
+ });
+
+ it('restores previous value', () => {
+ createComponent({ generic: { ...defaultProps.generic, initialActivated: false } });
+ return wrapper.vm.resetGenericKey().then(() => {
+ expect(wrapper.find(ToggleButton).props('value')).toBe(false);
+ });
+ });
+ });
+ });
+
+ describe('prometheus is active', () => {
+ beforeEach(() => {
+ createComponent(
+ { prometheus: { ...defaultProps.prometheus, prometheusIsActivated: true } },
+ {},
+ {
+ selectedEndpoint: 'prometheus',
+ },
+ );
+ });
+
+ it('renders a valid "select"', () => {
+ expect(findSelect().exists()).toBe(true);
+ });
+
+ it('shows the API URL input', () => {
+ expect(findApiUrl().exists()).toBe(true);
+ });
+
+ it('shows the correct default API URL', () => {
+ expect(findUrl().attributes('value')).toBe(PROMETHEUS_URL);
+ });
+ });
+
+ describe('opsgenie is active', () => {
+ beforeEach(() => {
+ createComponent(
+ { opsgenie: { ...defaultProps.opsgenie, opsgenieMvcActivated: true } },
+ {},
+ {
+ selectedEndpoint: 'opsgenie',
+ },
+ );
+ });
+
+ it('shows a input for the opsgenie target URL', () => {
+ expect(findApiUrl().exists()).toBe(true);
+ expect(findSelect().attributes('value')).toBe('opsgenie');
+ });
+ });
+
+ describe('trigger test alert', () => {
+ beforeEach(() => {
+ createComponent({ generic: { ...defaultProps.generic, initialActivated: true } }, {}, true);
+ });
+
+ it('should enable the JSON input', () => {
+ expect(findJsonInput().exists()).toBe(true);
+ expect(findJsonInput().props('value')).toBe(null);
+ });
+
+ it('should validate JSON input', () => {
+ createComponent({ generic: { ...defaultProps.generic } }, true, {
+ testAlertJson: '{ "value": "test" }',
+ });
+
+ findJsonInput().vm.$emit('change');
+ return wrapper.vm.$nextTick().then(() => {
+ expect(findJsonInput().attributes('state')).toBe('true');
+ });
+ });
+
+ describe('alert service is toggled', () => {
+ it('should show a info alert if successful', () => {
+ const formPath = 'some/path';
+ const toggleService = true;
+ mockAxios.onPut(formPath).replyOnce(200);
+
+ createComponent({ generic: { ...defaultProps.generic, formPath } });
+
+ return wrapper.vm.toggleActivated(toggleService).then(() => {
+ expect(wrapper.find(GlAlert).attributes('variant')).toBe('info');
+ });
+ });
+
+ it('should show a error alert if failed', () => {
+ const formPath = 'some/path';
+ const toggleService = true;
+ mockAxios.onPut(formPath).replyOnce(422, {
+ errors: 'Error message to display',
+ });
+
+ createComponent({ generic: { ...defaultProps.generic, formPath } });
+
+ return wrapper.vm.toggleActivated(toggleService).then(() => {
+ expect(wrapper.find(GlAlert).attributes('variant')).toBe('danger');
+ });
+ });
+ });
+ });
+});
diff --git a/spec/frontend/alerts_service_settings/components/alerts_service_form_spec.js b/spec/frontend/alerts_service_settings/components/alerts_service_form_spec.js
index c7c15c8fd44..610f9d6b9bd 100644
--- a/spec/frontend/alerts_service_settings/components/alerts_service_form_spec.js
+++ b/spec/frontend/alerts_service_settings/components/alerts_service_form_spec.js
@@ -15,6 +15,7 @@ const defaultProps = {
alertsSetupUrl: 'http://invalid',
alertsUsageUrl: 'http://invalid',
initialActivated: false,
+ isDisabled: false,
};
describe('AlertsServiceForm', () => {
@@ -166,4 +167,17 @@ describe('AlertsServiceForm', () => {
});
});
});
+
+ describe('form is disabled', () => {
+ beforeEach(() => {
+ createComponent({ isDisabled: true });
+ });
+
+ it('cannot be toggled', () => {
+ wrapper.find(ToggleButton).vm.$emit('change');
+ return wrapper.vm.$nextTick().then(() => {
+ expect(wrapper.find(ToggleButton).props('disabledInput')).toBe(true);
+ });
+ });
+ });
});
diff --git a/spec/frontend/api_spec.js b/spec/frontend/api_spec.js
index c1a23d441b3..c94637e04af 100644
--- a/spec/frontend/api_spec.js
+++ b/spec/frontend/api_spec.js
@@ -46,6 +46,77 @@ describe('Api', () => {
});
});
+ describe('packages', () => {
+ const projectId = 'project_a';
+ const packageId = 'package_b';
+ const apiResponse = [{ id: 1, name: 'foo' }];
+
+ describe('groupPackages', () => {
+ const groupId = 'group_a';
+
+ it('fetch all group packages', () => {
+ const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/groups/${groupId}/packages`;
+ jest.spyOn(axios, 'get');
+ mock.onGet(expectedUrl).replyOnce(200, apiResponse);
+
+ return Api.groupPackages(groupId).then(({ data }) => {
+ expect(data).toEqual(apiResponse);
+ expect(axios.get).toHaveBeenCalledWith(expectedUrl, {});
+ });
+ });
+ });
+
+ describe('projectPackages', () => {
+ it('fetch all project packages', () => {
+ const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/projects/${projectId}/packages`;
+ jest.spyOn(axios, 'get');
+ mock.onGet(expectedUrl).replyOnce(200, apiResponse);
+
+ return Api.projectPackages(projectId).then(({ data }) => {
+ expect(data).toEqual(apiResponse);
+ expect(axios.get).toHaveBeenCalledWith(expectedUrl, {});
+ });
+ });
+ });
+
+ describe('buildProjectPackageUrl', () => {
+ it('returns the right url', () => {
+ const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/projects/${projectId}/packages/${packageId}`;
+ const url = Api.buildProjectPackageUrl(projectId, packageId);
+ expect(url).toEqual(expectedUrl);
+ });
+ });
+
+ describe('projectPackage', () => {
+ it('fetch package details', () => {
+ const expectedUrl = `foo`;
+ jest.spyOn(Api, 'buildProjectPackageUrl').mockReturnValue(expectedUrl);
+ jest.spyOn(axios, 'get');
+ mock.onGet(expectedUrl).replyOnce(200, apiResponse);
+
+ return Api.projectPackage(projectId, packageId).then(({ data }) => {
+ expect(data).toEqual(apiResponse);
+ expect(axios.get).toHaveBeenCalledWith(expectedUrl);
+ });
+ });
+ });
+
+ describe('deleteProjectPackage', () => {
+ it('delete a package', () => {
+ const expectedUrl = `foo`;
+
+ jest.spyOn(Api, 'buildProjectPackageUrl').mockReturnValue(expectedUrl);
+ jest.spyOn(axios, 'delete');
+ mock.onDelete(expectedUrl).replyOnce(200, true);
+
+ return Api.deleteProjectPackage(projectId, packageId).then(({ data }) => {
+ expect(data).toEqual(true);
+ expect(axios.delete).toHaveBeenCalledWith(expectedUrl);
+ });
+ });
+ });
+ });
+
describe('group', () => {
it('fetches a group', done => {
const groupId = '123456';
@@ -366,6 +437,30 @@ describe('Api', () => {
});
});
+ describe('commit', () => {
+ const projectId = 'user/project';
+ const sha = 'abcd0123';
+ const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/projects/${encodeURIComponent(
+ projectId,
+ )}/repository/commits/${sha}`;
+
+ it('fetches a single commit', () => {
+ mock.onGet(expectedUrl).reply(200, { id: sha });
+
+ return Api.commit(projectId, sha).then(({ data: commit }) => {
+ expect(commit.id).toBe(sha);
+ });
+ });
+
+ it('fetches a single commit without stats', () => {
+ mock.onGet(expectedUrl, { params: { stats: false } }).reply(200, { id: sha });
+
+ return Api.commit(projectId, sha, { stats: false }).then(({ data: commit }) => {
+ expect(commit.id).toBe(sha);
+ });
+ });
+ });
+
describe('issueTemplate', () => {
it('fetches an issue template', done => {
const namespace = 'some namespace';
diff --git a/spec/frontend/awards_handler_spec.js b/spec/frontend/awards_handler_spec.js
index 754f0702b84..6cfbc6024af 100644
--- a/spec/frontend/awards_handler_spec.js
+++ b/spec/frontend/awards_handler_spec.js
@@ -1,63 +1,61 @@
import $ from 'jquery';
import Cookies from 'js-cookie';
+import MockAdapter from 'axios-mock-adapter';
+import axios from '~/lib/utils/axios_utils';
import loadAwardsHandler from '~/awards_handler';
-import '~/lib/utils/common_utils';
-import waitForPromises from './helpers/wait_for_promises';
+import { setTestTimeout } from './helpers/timeout';
+import { EMOJI_VERSION } from '~/emoji';
+import { useFakeRequestAnimationFrame } from 'helpers/fake_request_animation_frame';
window.gl = window.gl || {};
window.gon = window.gon || {};
-let openAndWaitForEmojiMenu;
+let mock;
let awardsHandler = null;
const urlRoot = gon.relative_url_root;
-const lazyAssert = (done, assertFn) => {
- jest.runOnlyPendingTimers();
- waitForPromises()
- .then(() => {
- assertFn();
- done();
- })
- .catch(e => {
- throw e;
- });
-};
-
describe('AwardsHandler', () => {
+ useFakeRequestAnimationFrame();
+
+ const emojiData = getJSONFixture('emojis/emojis.json');
preloadFixtures('snippets/show.html');
- beforeEach(done => {
- loadFixtures('snippets/show.html');
- loadAwardsHandler(true)
- .then(obj => {
- awardsHandler = obj;
- jest.spyOn(awardsHandler, 'postEmoji').mockImplementation((button, url, emoji, cb) => cb());
- done();
- })
- .catch(done.fail);
-
- let isEmojiMenuBuilt = false;
- openAndWaitForEmojiMenu = () => {
- return new Promise(resolve => {
- if (isEmojiMenuBuilt) {
- resolve();
- } else {
- $('.js-add-award')
- .eq(0)
- .click();
- const $menu = $('.emoji-menu');
- $menu.one('build-emoji-menu-finish', () => {
- isEmojiMenuBuilt = true;
- resolve();
- });
- }
+
+ const openAndWaitForEmojiMenu = (sel = '.js-add-award') => {
+ $(sel)
+ .eq(0)
+ .click();
+
+ jest.advanceTimersByTime(200);
+
+ const $menu = $('.emoji-menu');
+
+ return new Promise(resolve => {
+ $menu.one('build-emoji-menu-finish', () => {
+ resolve();
});
- };
+ });
+ };
+
+ beforeEach(async () => {
+ // These tests have had some timeout issues
+ // https://gitlab.com/gitlab-org/gitlab/-/issues/221086
+ setTestTimeout(6000);
+
+ mock = new MockAdapter(axios);
+ mock.onGet(`/-/emojis/${EMOJI_VERSION}/emojis.json`).reply(200, emojiData);
+
+ loadFixtures('snippets/show.html');
+
+ awardsHandler = await loadAwardsHandler(true);
+ jest.spyOn(awardsHandler, 'postEmoji').mockImplementation((button, url, emoji, cb) => cb());
});
afterEach(() => {
// restore original url root value
gon.relative_url_root = urlRoot;
+ mock.restore();
+
// Undo what we did to the shared <body>
$('body').removeAttr('data-page');
@@ -65,55 +63,45 @@ describe('AwardsHandler', () => {
});
describe('::showEmojiMenu', () => {
- it('should show emoji menu when Add emoji button clicked', done => {
- $('.js-add-award')
- .eq(0)
- .click();
- lazyAssert(done, () => {
- const $emojiMenu = $('.emoji-menu');
+ it('should show emoji menu when Add emoji button clicked', async () => {
+ await openAndWaitForEmojiMenu();
- expect($emojiMenu.length).toBe(1);
- expect($emojiMenu.hasClass('is-visible')).toBe(true);
- expect($emojiMenu.find('.js-emoji-menu-search').length).toBe(1);
- expect($('.js-awards-block.current').length).toBe(1);
- });
+ const $emojiMenu = $('.emoji-menu');
+
+ expect($emojiMenu.length).toBe(1);
+ expect($emojiMenu.hasClass('is-visible')).toBe(true);
+ expect($emojiMenu.find('.js-emoji-menu-search').length).toBe(1);
+ expect($('.js-awards-block.current').length).toBe(1);
});
- it('should also show emoji menu for the smiley icon in notes', done => {
- $('.js-add-award.note-action-button').click();
- lazyAssert(done, () => {
- const $emojiMenu = $('.emoji-menu');
+ it('should also show emoji menu for the smiley icon in notes', async () => {
+ await openAndWaitForEmojiMenu('.js-add-award.note-action-button');
- expect($emojiMenu.length).toBe(1);
- });
+ const $emojiMenu = $('.emoji-menu');
+
+ expect($emojiMenu.length).toBe(1);
});
- it('should remove emoji menu when body is clicked', done => {
- $('.js-add-award')
- .eq(0)
- .click();
- lazyAssert(done, () => {
- const $emojiMenu = $('.emoji-menu');
- $('body').click();
+ it('should remove emoji menu when body is clicked', async () => {
+ await openAndWaitForEmojiMenu();
- expect($emojiMenu.length).toBe(1);
- expect($emojiMenu.hasClass('is-visible')).toBe(false);
- expect($('.js-awards-block.current').length).toBe(0);
- });
+ const $emojiMenu = $('.emoji-menu');
+ $('body').click();
+
+ expect($emojiMenu.length).toBe(1);
+ expect($emojiMenu.hasClass('is-visible')).toBe(false);
+ expect($('.js-awards-block.current').length).toBe(0);
});
- it('should not remove emoji menu when search is clicked', done => {
- $('.js-add-award')
- .eq(0)
- .click();
- lazyAssert(done, () => {
- const $emojiMenu = $('.emoji-menu');
- $('.emoji-search').click();
+ it('should not remove emoji menu when search is clicked', async () => {
+ await openAndWaitForEmojiMenu();
- expect($emojiMenu.length).toBe(1);
- expect($emojiMenu.hasClass('is-visible')).toBe(true);
- expect($('.js-awards-block.current').length).toBe(1);
- });
+ const $emojiMenu = $('.emoji-menu');
+ $('.emoji-search').click();
+
+ expect($emojiMenu.length).toBe(1);
+ expect($emojiMenu.hasClass('is-visible')).toBe(true);
+ expect($('.js-awards-block.current').length).toBe(1);
});
});
@@ -261,48 +249,39 @@ describe('AwardsHandler', () => {
});
describe('::searchEmojis', () => {
- it('should filter the emoji', done => {
- openAndWaitForEmojiMenu()
- .then(() => {
- expect($('[data-name=angel]').is(':visible')).toBe(true);
- expect($('[data-name=anger]').is(':visible')).toBe(true);
- awardsHandler.searchEmojis('ali');
-
- expect($('[data-name=angel]').is(':visible')).toBe(false);
- expect($('[data-name=anger]').is(':visible')).toBe(false);
- expect($('[data-name=alien]').is(':visible')).toBe(true);
- expect($('.js-emoji-menu-search').val()).toBe('ali');
- })
- .then(done)
- .catch(err => {
- done.fail(`Failed to open and build emoji menu: ${err.message}`);
- });
+ it('should filter the emoji', async () => {
+ await openAndWaitForEmojiMenu();
+
+ expect($('[data-name=angel]').is(':visible')).toBe(true);
+ expect($('[data-name=anger]').is(':visible')).toBe(true);
+ awardsHandler.searchEmojis('ali');
+
+ expect($('[data-name=angel]').is(':visible')).toBe(false);
+ expect($('[data-name=anger]').is(':visible')).toBe(false);
+ expect($('[data-name=alien]').is(':visible')).toBe(true);
+ expect($('.js-emoji-menu-search').val()).toBe('ali');
});
- it('should clear the search when searching for nothing', done => {
- openAndWaitForEmojiMenu()
- .then(() => {
- awardsHandler.searchEmojis('ali');
-
- expect($('[data-name=angel]').is(':visible')).toBe(false);
- expect($('[data-name=anger]').is(':visible')).toBe(false);
- expect($('[data-name=alien]').is(':visible')).toBe(true);
- awardsHandler.searchEmojis('');
-
- expect($('[data-name=angel]').is(':visible')).toBe(true);
- expect($('[data-name=anger]').is(':visible')).toBe(true);
- expect($('[data-name=alien]').is(':visible')).toBe(true);
- expect($('.js-emoji-menu-search').val()).toBe('');
- })
- .then(done)
- .catch(err => {
- done.fail(`Failed to open and build emoji menu: ${err.message}`);
- });
+ it('should clear the search when searching for nothing', async () => {
+ await openAndWaitForEmojiMenu();
+
+ awardsHandler.searchEmojis('ali');
+
+ expect($('[data-name=angel]').is(':visible')).toBe(false);
+ expect($('[data-name=anger]').is(':visible')).toBe(false);
+ expect($('[data-name=alien]').is(':visible')).toBe(true);
+ awardsHandler.searchEmojis('');
+
+ expect($('[data-name=angel]').is(':visible')).toBe(true);
+ expect($('[data-name=anger]').is(':visible')).toBe(true);
+ expect($('[data-name=alien]').is(':visible')).toBe(true);
+ expect($('.js-emoji-menu-search').val()).toBe('');
});
});
describe('emoji menu', () => {
const emojiSelector = '[data-name="sunglasses"]';
+
const openEmojiMenuAndAddEmoji = () => {
return openAndWaitForEmojiMenu().then(() => {
const $menu = $('.emoji-menu');
@@ -318,32 +297,23 @@ describe('AwardsHandler', () => {
});
};
- it('should add selected emoji to awards block', done => {
- openEmojiMenuAndAddEmoji()
- .then(done)
- .catch(err => {
- done.fail(`Failed to open and build emoji menu: ${err.message}`);
- });
+ it('should add selected emoji to awards block', async () => {
+ await openEmojiMenuAndAddEmoji();
});
- it('should remove already selected emoji', done => {
- openEmojiMenuAndAddEmoji()
- .then(() => {
- $('.js-add-award')
- .eq(0)
- .click();
- const $block = $('.js-awards-block');
- const $emoji = $('.emoji-menu').find(
- `.emoji-menu-list:not(.frequent-emojis) ${emojiSelector}`,
- );
- $emoji.click();
-
- expect($block.find(emojiSelector).length).toBe(0);
- })
- .then(done)
- .catch(err => {
- done.fail(`Failed to open and build emoji menu: ${err.message}`);
- });
+ it('should remove already selected emoji', async () => {
+ await openEmojiMenuAndAddEmoji();
+
+ $('.js-add-award')
+ .eq(0)
+ .click();
+ const $block = $('.js-awards-block');
+ const $emoji = $('.emoji-menu').find(
+ `.emoji-menu-list:not(.frequent-emojis) ${emojiSelector}`,
+ );
+ $emoji.click();
+
+ expect($block.find(emojiSelector).length).toBe(0);
});
});
@@ -353,37 +323,27 @@ describe('AwardsHandler', () => {
Cookies.set('frequently_used_emojis', '');
});
- it('shouldn\'t have any "Frequently used" heading if no frequently used emojis', done => {
- return openAndWaitForEmojiMenu()
- .then(() => {
- const emojiMenu = document.querySelector('.emoji-menu');
- Array.prototype.forEach.call(emojiMenu.querySelectorAll('.emoji-menu-title'), title => {
- expect(title.textContent.trim().toLowerCase()).not.toBe('frequently used');
- });
- })
- .then(done)
- .catch(err => {
- done.fail(`Failed to open and build emoji menu: ${err.message}`);
- });
+ it('shouldn\'t have any "Frequently used" heading if no frequently used emojis', async () => {
+ await openAndWaitForEmojiMenu();
+
+ const emojiMenu = document.querySelector('.emoji-menu');
+ Array.prototype.forEach.call(emojiMenu.querySelectorAll('.emoji-menu-title'), title => {
+ expect(title.textContent.trim().toLowerCase()).not.toBe('frequently used');
+ });
});
- it('should have any frequently used section when there are frequently used emojis', done => {
+ it('should have any frequently used section when there are frequently used emojis', async () => {
awardsHandler.addEmojiToFrequentlyUsedList('8ball');
- return openAndWaitForEmojiMenu()
- .then(() => {
- const emojiMenu = document.querySelector('.emoji-menu');
- const hasFrequentlyUsedHeading = Array.prototype.some.call(
- emojiMenu.querySelectorAll('.emoji-menu-title'),
- title => title.textContent.trim().toLowerCase() === 'frequently used',
- );
-
- expect(hasFrequentlyUsedHeading).toBe(true);
- })
- .then(done)
- .catch(err => {
- done.fail(`Failed to open and build emoji menu: ${err.message}`);
- });
+ await openAndWaitForEmojiMenu();
+
+ const emojiMenu = document.querySelector('.emoji-menu');
+ const hasFrequentlyUsedHeading = Array.prototype.some.call(
+ emojiMenu.querySelectorAll('.emoji-menu-title'),
+ title => title.textContent.trim().toLowerCase() === 'frequently used',
+ );
+
+ expect(hasFrequentlyUsedHeading).toBe(true);
});
it('should disregard invalid frequently used emoji that are being attempted to be added', () => {
diff --git a/spec/frontend/batch_comments/stores/modules/batch_comments/actions_spec.js b/spec/frontend/batch_comments/stores/modules/batch_comments/actions_spec.js
index 2ec114d026a..4bac6d4e3dc 100644
--- a/spec/frontend/batch_comments/stores/modules/batch_comments/actions_spec.js
+++ b/spec/frontend/batch_comments/stores/modules/batch_comments/actions_spec.js
@@ -2,6 +2,7 @@ import MockAdapter from 'axios-mock-adapter';
import testAction from 'helpers/vuex_action_helper';
import * as actions from '~/batch_comments/stores/modules/batch_comments/actions';
import axios from '~/lib/utils/axios_utils';
+import { TEST_HOST } from 'jest/helpers/test_constants';
describe('Batch comments store actions', () => {
let res = {};
@@ -33,7 +34,7 @@ describe('Batch comments store actions', () => {
testAction(
actions.addDraftToDiscussion,
- { endpoint: gl.TEST_HOST, data: 'test' },
+ { endpoint: TEST_HOST, data: 'test' },
null,
[{ type: 'ADD_NEW_DRAFT', payload: res }],
[],
@@ -46,7 +47,7 @@ describe('Batch comments store actions', () => {
testAction(
actions.addDraftToDiscussion,
- { endpoint: gl.TEST_HOST, data: 'test' },
+ { endpoint: TEST_HOST, data: 'test' },
null,
[],
[],
@@ -62,7 +63,7 @@ describe('Batch comments store actions', () => {
testAction(
actions.createNewDraft,
- { endpoint: gl.TEST_HOST, data: 'test' },
+ { endpoint: TEST_HOST, data: 'test' },
null,
[{ type: 'ADD_NEW_DRAFT', payload: res }],
[],
@@ -73,14 +74,7 @@ describe('Batch comments store actions', () => {
it('does not commit ADD_NEW_DRAFT if errors returned', done => {
mock.onAny().reply(500);
- testAction(
- actions.createNewDraft,
- { endpoint: gl.TEST_HOST, data: 'test' },
- null,
- [],
- [],
- done,
- );
+ testAction(actions.createNewDraft, { endpoint: TEST_HOST, data: 'test' }, null, [], [], done);
});
});
@@ -90,7 +84,7 @@ describe('Batch comments store actions', () => {
beforeEach(() => {
getters = {
getNotesData: {
- draftsDiscardPath: gl.TEST_HOST,
+ draftsDiscardPath: TEST_HOST,
},
};
});
@@ -137,7 +131,7 @@ describe('Batch comments store actions', () => {
beforeEach(() => {
getters = {
getNotesData: {
- draftsPath: gl.TEST_HOST,
+ draftsPath: TEST_HOST,
},
};
});
@@ -171,7 +165,7 @@ describe('Batch comments store actions', () => {
dispatch = jest.fn();
commit = jest.fn();
getters = {
- getNotesData: { draftsPublishPath: gl.TEST_HOST, discussionsPath: gl.TEST_HOST },
+ getNotesData: { draftsPublishPath: TEST_HOST, discussionsPath: TEST_HOST },
};
rootGetters = { discussionsStructuredByLineCode: 'discussions' };
});
@@ -208,7 +202,7 @@ describe('Batch comments store actions', () => {
describe('discardReview', () => {
it('commits mutations', done => {
const getters = {
- getNotesData: { draftsDiscardPath: gl.TEST_HOST },
+ getNotesData: { draftsDiscardPath: TEST_HOST },
};
const commit = jest.fn();
mock.onAny().reply(200);
@@ -225,7 +219,7 @@ describe('Batch comments store actions', () => {
it('commits error mutations', done => {
const getters = {
- getNotesData: { draftsDiscardPath: gl.TEST_HOST },
+ getNotesData: { draftsDiscardPath: TEST_HOST },
};
const commit = jest.fn();
mock.onAny().reply(500);
@@ -247,7 +241,7 @@ describe('Batch comments store actions', () => {
beforeEach(() => {
getters = {
getNotesData: {
- draftsPath: gl.TEST_HOST,
+ draftsPath: TEST_HOST,
},
};
});
diff --git a/spec/frontend/behaviors/copy_as_gfm_spec.js b/spec/frontend/behaviors/copy_as_gfm_spec.js
index cf96ac488a8..33af9bc135e 100644
--- a/spec/frontend/behaviors/copy_as_gfm_spec.js
+++ b/spec/frontend/behaviors/copy_as_gfm_spec.js
@@ -1,3 +1,4 @@
+import * as commonUtils from '~/lib/utils/common_utils';
import initCopyAsGFM, { CopyAsGFM } from '~/behaviors/markdown/copy_as_gfm';
describe('CopyAsGFM', () => {
@@ -27,7 +28,7 @@ describe('CopyAsGFM', () => {
}
it('wraps pasted code when not already in code tags', () => {
- jest.spyOn(window.gl.utils, 'insertText').mockImplementation((el, textFunc) => {
+ jest.spyOn(commonUtils, 'insertText').mockImplementation((el, textFunc) => {
const insertedText = textFunc('This is code: ', '');
expect(insertedText).toEqual('`code`');
@@ -37,7 +38,7 @@ describe('CopyAsGFM', () => {
});
it('does not wrap pasted code when already in code tags', () => {
- jest.spyOn(window.gl.utils, 'insertText').mockImplementation((el, textFunc) => {
+ jest.spyOn(commonUtils, 'insertText').mockImplementation((el, textFunc) => {
const insertedText = textFunc('This is code: `', '`');
expect(insertedText).toEqual('code');
diff --git a/spec/frontend/behaviors/gl_emoji_spec.js b/spec/frontend/behaviors/gl_emoji_spec.js
new file mode 100644
index 00000000000..7ea0bafc328
--- /dev/null
+++ b/spec/frontend/behaviors/gl_emoji_spec.js
@@ -0,0 +1,110 @@
+import MockAdapter from 'axios-mock-adapter';
+import axios from '~/lib/utils/axios_utils';
+import { initEmojiMap, EMOJI_VERSION } from '~/emoji';
+import installGlEmojiElement from '~/behaviors/gl_emoji';
+
+import * as EmojiUnicodeSupport from '~/emoji/support';
+import waitForPromises from 'jest/helpers/wait_for_promises';
+
+jest.mock('~/emoji/support');
+
+describe('gl_emoji', () => {
+ let mock;
+ const emojiData = getJSONFixture('emojis/emojis.json');
+
+ beforeAll(() => {
+ jest.spyOn(EmojiUnicodeSupport, 'default').mockReturnValue(true);
+ installGlEmojiElement();
+ });
+
+ function markupToDomElement(markup) {
+ const div = document.createElement('div');
+ div.innerHTML = markup;
+ document.body.appendChild(div);
+
+ return div.firstElementChild;
+ }
+
+ beforeEach(() => {
+ mock = new MockAdapter(axios);
+ mock.onGet(`/-/emojis/${EMOJI_VERSION}/emojis.json`).reply(200, emojiData);
+
+ return initEmojiMap().catch(() => {});
+ });
+
+ afterEach(() => {
+ mock.restore();
+
+ document.body.innerHTML = '';
+ });
+
+ describe.each([
+ [
+ 'bomb emoji just with name attribute',
+ '<gl-emoji data-name="bomb"></gl-emoji>',
+ '<gl-emoji data-name="bomb" data-unicode-version="6.0" title="bomb">💣</gl-emoji>',
+ '<gl-emoji data-name="bomb" data-unicode-version="6.0" title="bomb"><img class="emoji" title=":bomb:" alt=":bomb:" src="/-/emojis/1/bomb.png" width="20" height="20" align="absmiddle"></gl-emoji>',
+ ],
+ [
+ 'bomb emoji with name attribute and unicode version',
+ '<gl-emoji data-name="bomb" data-unicode-version="6.0">💣</gl-emoji>',
+ '<gl-emoji data-name="bomb" data-unicode-version="6.0">💣</gl-emoji>',
+ '<gl-emoji data-name="bomb" data-unicode-version="6.0"><img class="emoji" title=":bomb:" alt=":bomb:" src="/-/emojis/1/bomb.png" width="20" height="20" align="absmiddle"></gl-emoji>',
+ ],
+ [
+ 'bomb emoji with sprite fallback',
+ '<gl-emoji data-fallback-sprite-class="emoji-bomb" data-name="bomb"></gl-emoji>',
+ '<gl-emoji data-fallback-sprite-class="emoji-bomb" data-name="bomb" data-unicode-version="6.0" title="bomb">💣</gl-emoji>',
+ '<gl-emoji data-fallback-sprite-class="emoji-bomb" data-name="bomb" data-unicode-version="6.0" title="bomb" class="emoji-icon emoji-bomb">💣</gl-emoji>',
+ ],
+ [
+ 'bomb emoji with image fallback',
+ '<gl-emoji data-fallback-src="/bomb.png" data-name="bomb"></gl-emoji>',
+ '<gl-emoji data-fallback-src="/bomb.png" data-name="bomb" data-unicode-version="6.0" title="bomb">💣</gl-emoji>',
+ '<gl-emoji data-fallback-src="/bomb.png" data-name="bomb" data-unicode-version="6.0" title="bomb"><img class="emoji" title=":bomb:" alt=":bomb:" src="/bomb.png" width="20" height="20" align="absmiddle"></gl-emoji>',
+ ],
+ [
+ 'invalid emoji',
+ '<gl-emoji data-name="invalid_emoji"></gl-emoji>',
+ '<gl-emoji data-name="grey_question" data-unicode-version="6.0" title="white question mark ornament">â”</gl-emoji>',
+ '<gl-emoji data-name="grey_question" data-unicode-version="6.0" title="white question mark ornament"><img class="emoji" title=":grey_question:" alt=":grey_question:" src="/-/emojis/1/grey_question.png" width="20" height="20" align="absmiddle"></gl-emoji>',
+ ],
+ ])('%s', (name, markup, withEmojiSupport, withoutEmojiSupport) => {
+ it(`renders correctly with emoji support`, async () => {
+ jest.spyOn(EmojiUnicodeSupport, 'default').mockReturnValue(true);
+ const glEmojiElement = markupToDomElement(markup);
+
+ await waitForPromises();
+
+ expect(glEmojiElement.outerHTML).toBe(withEmojiSupport);
+ });
+
+ it(`renders correctly without emoji support`, async () => {
+ jest.spyOn(EmojiUnicodeSupport, 'default').mockReturnValue(false);
+ const glEmojiElement = markupToDomElement(markup);
+
+ await waitForPromises();
+
+ expect(glEmojiElement.outerHTML).toBe(withoutEmojiSupport);
+ });
+ });
+
+ it('Adds sprite CSS if emojis are not supported', async () => {
+ const testPath = '/test-path.css';
+ jest.spyOn(EmojiUnicodeSupport, 'default').mockReturnValue(false);
+ window.gon.emoji_sprites_css_path = testPath;
+
+ expect(document.head.querySelector(`link[href="${testPath}"]`)).toBe(null);
+ expect(window.gon.emoji_sprites_css_added).toBeFalsy();
+
+ markupToDomElement(
+ '<gl-emoji data-fallback-sprite-class="emoji-bomb" data-name="bomb"></gl-emoji>',
+ );
+ await waitForPromises();
+
+ expect(document.head.querySelector(`link[href="${testPath}"]`).outerHTML).toBe(
+ '<link rel="stylesheet" href="/test-path.css">',
+ );
+ expect(window.gon.emoji_sprites_css_added).toBe(true);
+ });
+});
diff --git a/spec/frontend/behaviors/shortcuts/shortcuts_issuable_spec.js b/spec/frontend/behaviors/shortcuts/shortcuts_issuable_spec.js
index 6391a544985..baedbf5771a 100644
--- a/spec/frontend/behaviors/shortcuts/shortcuts_issuable_spec.js
+++ b/spec/frontend/behaviors/shortcuts/shortcuts_issuable_spec.js
@@ -46,7 +46,7 @@ describe('ShortcutsIssuable', () => {
});
describe('replyWithSelectedText', () => {
- // Stub window.gl.utils.getSelectedFragment to return a node with the provided HTML.
+ // Stub getSelectedFragment to return a node with the provided HTML.
const stubSelection = (html, invalidNode) => {
getSelectedFragment.mockImplementation(() => {
const documentFragment = document.createDocumentFragment();
diff --git a/spec/frontend/blob/components/__snapshots__/blob_header_filepath_spec.js.snap b/spec/frontend/blob/components/__snapshots__/blob_header_filepath_spec.js.snap
index 005b2c5da1c..0f5b3cd3f5e 100644
--- a/spec/frontend/blob/components/__snapshots__/blob_header_filepath_spec.js.snap
+++ b/spec/frontend/blob/components/__snapshots__/blob_header_filepath_spec.js.snap
@@ -8,6 +8,7 @@ exports[`Blob Header Filepath rendering matches the snapshot 1`] = `
<file-icon-stub
aria-hidden="true"
cssclasses="mr-2"
+ filemode=""
filename="foo/bar/dummy.md"
size="18"
/>
diff --git a/spec/frontend/blob/components/blob_content_error_spec.js b/spec/frontend/blob/components/blob_content_error_spec.js
index 6eb5cfb71aa..508b1ed7e68 100644
--- a/spec/frontend/blob/components/blob_content_error_spec.js
+++ b/spec/frontend/blob/components/blob_content_error_spec.js
@@ -24,9 +24,9 @@ describe('Blob Content Error component', () => {
describe('collapsed and too large blobs', () => {
it.each`
- error | reason | options
- ${BLOB_RENDER_ERRORS.REASONS.COLLAPSED} | ${'it is larger than 1.00 MiB'} | ${[BLOB_RENDER_ERRORS.OPTIONS.LOAD.text, BLOB_RENDER_ERRORS.OPTIONS.DOWNLOAD.text]}
- ${BLOB_RENDER_ERRORS.REASONS.TOO_LARGE} | ${'it is larger than 100.00 MiB'} | ${[BLOB_RENDER_ERRORS.OPTIONS.DOWNLOAD.text]}
+ error | reason | options
+ ${BLOB_RENDER_ERRORS.REASONS.COLLAPSED} | ${'it is larger than 1.00 MiB'} | ${[BLOB_RENDER_ERRORS.OPTIONS.LOAD.text, BLOB_RENDER_ERRORS.OPTIONS.DOWNLOAD.text]}
+ ${BLOB_RENDER_ERRORS.REASONS.TOO_LARGE} | ${'it is larger than 10.00 MiB'} | ${[BLOB_RENDER_ERRORS.OPTIONS.DOWNLOAD.text]}
`('renders correct reason for $error.id', ({ error, reason, options }) => {
createComponent({
viewerError: error.id,
diff --git a/spec/frontend/blob/components/blob_header_default_actions_spec.js b/spec/frontend/blob/components/blob_header_default_actions_spec.js
index 0247a12d8d3..529e7cc85f5 100644
--- a/spec/frontend/blob/components/blob_header_default_actions_spec.js
+++ b/spec/frontend/blob/components/blob_header_default_actions_spec.js
@@ -13,7 +13,6 @@ describe('Blob Header Default Actions', () => {
let wrapper;
let btnGroup;
let buttons;
- const hrefPrefix = 'http://localhost';
function createComponent(propsData = {}) {
wrapper = mount(BlobHeaderActions, {
@@ -47,11 +46,11 @@ describe('Blob Header Default Actions', () => {
});
it('correct href attribute on RAW button', () => {
- expect(buttons.at(1).vm.$el.href).toBe(`${hrefPrefix}${Blob.rawPath}`);
+ expect(buttons.at(1).attributes('href')).toBe(Blob.rawPath);
});
it('correct href attribute on Download button', () => {
- expect(buttons.at(2).vm.$el.href).toBe(`${hrefPrefix}${Blob.rawPath}?inline=false`);
+ expect(buttons.at(2).attributes('href')).toBe(`${Blob.rawPath}?inline=false`);
});
it('does not render "Copy file contents" button as disables if the viewer is Simple', () => {
diff --git a/spec/frontend/blob/components/blob_header_viewer_switcher_spec.js b/spec/frontend/blob/components/blob_header_viewer_switcher_spec.js
index 0f34d6419d3..f1a7ac8b21a 100644
--- a/spec/frontend/blob/components/blob_header_viewer_switcher_spec.js
+++ b/spec/frontend/blob/components/blob_header_viewer_switcher_spec.js
@@ -6,7 +6,7 @@ import {
SIMPLE_BLOB_VIEWER,
SIMPLE_BLOB_VIEWER_TITLE,
} from '~/blob/components/constants';
-import { GlButtonGroup, GlDeprecatedButton } from '@gitlab/ui';
+import { GlButtonGroup, GlButton } from '@gitlab/ui';
describe('Blob Header Viewer Switcher', () => {
let wrapper;
@@ -35,7 +35,7 @@ describe('Blob Header Viewer Switcher', () => {
beforeEach(() => {
createComponent();
btnGroup = wrapper.find(GlButtonGroup);
- buttons = wrapper.findAll(GlDeprecatedButton);
+ buttons = wrapper.findAll(GlButton);
});
it('renders gl-button-group component', () => {
@@ -57,7 +57,7 @@ describe('Blob Header Viewer Switcher', () => {
function factory(propsData = {}) {
createComponent(propsData);
- buttons = wrapper.findAll(GlDeprecatedButton);
+ buttons = wrapper.findAll(GlButton);
simpleBtn = buttons.at(0);
richBtn = buttons.at(1);
diff --git a/spec/frontend/blob/components/mock_data.js b/spec/frontend/blob/components/mock_data.js
index 0f7193846ff..58aa1dc6dc9 100644
--- a/spec/frontend/blob/components/mock_data.js
+++ b/spec/frontend/blob/components/mock_data.js
@@ -32,6 +32,20 @@ export const Blob = {
},
};
+export const BinaryBlob = {
+ binary: true,
+ name: 'dummy.png',
+ path: 'foo/bar/dummy.png',
+ rawPath: '/flightjs/flight/snippets/51/raw',
+ size: 75,
+ simpleViewer: {
+ ...SimpleViewerMock,
+ },
+ richViewer: {
+ ...RichViewerMock,
+ },
+};
+
export const RichBlobContentMock = {
richData: '<h1>Rich</h1>',
};
diff --git a/spec/frontend/blob_edit/edit_blob_spec.js b/spec/frontend/blob_edit/edit_blob_spec.js
new file mode 100644
index 00000000000..9642b55b9b4
--- /dev/null
+++ b/spec/frontend/blob_edit/edit_blob_spec.js
@@ -0,0 +1,31 @@
+import EditBlob from '~/blob_edit/edit_blob';
+import EditorLite from '~/editor/editor_lite';
+import MarkdownExtension from '~/editor/editor_markdown_ext';
+
+jest.mock('~/editor/editor_lite');
+jest.mock('~/editor/editor_markdown_ext');
+
+describe('Blob Editing', () => {
+ beforeEach(() => {
+ setFixtures(
+ `<div class="js-edit-blob-form"><div id="file_path"></div><div id="iditor"></div><input id="file-content"></div>`,
+ );
+ });
+
+ const initEditor = (isMarkdown = false) => {
+ return new EditBlob({
+ isMarkdown,
+ monacoEnabled: true,
+ });
+ };
+
+ it('does not load MarkdownExtension by default', async () => {
+ await initEditor();
+ expect(EditorLite.prototype.use).not.toHaveBeenCalled();
+ });
+
+ it('loads MarkdownExtension only for the markdown files', async () => {
+ await initEditor(true);
+ expect(EditorLite.prototype.use).toHaveBeenCalledWith(MarkdownExtension);
+ });
+});
diff --git a/spec/frontend/boards/components/board_form_spec.js b/spec/frontend/boards/components/board_form_spec.js
index ee427bc2154..94f607698d7 100644
--- a/spec/frontend/boards/components/board_form_spec.js
+++ b/spec/frontend/boards/components/board_form_spec.js
@@ -3,14 +3,15 @@ import { mount } from '@vue/test-utils';
import boardsStore from '~/boards/stores/boards_store';
import boardForm from '~/boards/components/board_form.vue';
import DeprecatedModal from '~/vue_shared/components/deprecated_modal.vue';
+import { TEST_HOST } from 'jest/helpers/test_constants';
describe('board_form.vue', () => {
let wrapper;
const propsData = {
canAdminBoard: false,
- labelsPath: `${gl.TEST_HOST}/labels/path`,
- milestonePath: `${gl.TEST_HOST}/milestone/path`,
+ labelsPath: `${TEST_HOST}/labels/path`,
+ milestonePath: `${TEST_HOST}/milestone/path`,
};
const findModal = () => wrapper.find(DeprecatedModal);
diff --git a/spec/frontend/boards/issue_card_spec.js b/spec/frontend/boards/issue_card_spec.js
index 09b5c664bee..15750a161ae 100644
--- a/spec/frontend/boards/issue_card_spec.js
+++ b/spec/frontend/boards/issue_card_spec.js
@@ -6,7 +6,7 @@ import '~/boards/models/assignee';
import '~/boards/models/issue';
import '~/boards/models/list';
import IssueCardInner from '~/boards/components/issue_card_inner.vue';
-import { listObj } from '../../javascripts/boards/mock_data';
+import { listObj } from './mock_data';
import store from '~/boards/stores';
import { GlLabel } from '@gitlab/ui';
diff --git a/spec/frontend/ci_variable_list/components/ci_key_field_spec.js b/spec/frontend/ci_variable_list/components/ci_key_field_spec.js
deleted file mode 100644
index bcc29f22dd1..00000000000
--- a/spec/frontend/ci_variable_list/components/ci_key_field_spec.js
+++ /dev/null
@@ -1,244 +0,0 @@
-import { mount } from '@vue/test-utils';
-import { GlButton, GlFormInput } from '@gitlab/ui';
-import { AWS_ACCESS_KEY_ID, AWS_DEFAULT_REGION } from '~/ci_variable_list/constants';
-import CiKeyField from '~/ci_variable_list/components/ci_key_field.vue';
-
-import {
- awsTokens,
- awsTokenList,
-} from '~/ci_variable_list/components/ci_variable_autocomplete_tokens';
-
-const doTimes = (num, fn) => {
- for (let i = 0; i < num; i += 1) {
- fn();
- }
-};
-
-describe('Ci Key field', () => {
- let wrapper;
-
- const createComponent = () => {
- wrapper = mount({
- data() {
- return {
- inputVal: '',
- tokens: awsTokenList,
- };
- },
- components: { CiKeyField },
- template: `
- <div>
- <ci-key-field
- v-model="inputVal"
- :token-list="tokens"
- />
- </div>
- `,
- });
- };
-
- const findDropdown = () => wrapper.find('#ci-variable-dropdown');
- const findDropdownOptions = () => wrapper.findAll(GlButton).wrappers.map(item => item.text());
- const findInput = () => wrapper.find(GlFormInput);
- const findInputValue = () => findInput().element.value;
- const setInput = val => findInput().setValue(val);
- const clickDown = () => findInput().trigger('keydown.down');
-
- afterEach(() => {
- wrapper.destroy();
- });
-
- describe('match and filter functionality', () => {
- beforeEach(() => {
- createComponent();
- });
-
- it('is closed when the input is empty', () => {
- expect(findInput().isVisible()).toBe(true);
- expect(findInputValue()).toBe('');
- expect(findDropdown().isVisible()).toBe(false);
- });
-
- it('is open when the input text matches a token', () => {
- setInput('AWS');
- return wrapper.vm.$nextTick().then(() => {
- expect(findDropdown().isVisible()).toBe(true);
- });
- });
-
- it('shows partial matches at string start', () => {
- setInput('AWS');
- return wrapper.vm.$nextTick().then(() => {
- expect(findDropdown().isVisible()).toBe(true);
- expect(findDropdownOptions()).toEqual(awsTokenList);
- });
- });
-
- it('shows partial matches mid-string', () => {
- setInput('D');
- return wrapper.vm.$nextTick().then(() => {
- expect(findDropdown().isVisible()).toBe(true);
- expect(findDropdownOptions()).toEqual([
- awsTokens[AWS_ACCESS_KEY_ID].name,
- awsTokens[AWS_DEFAULT_REGION].name,
- ]);
- });
- });
-
- it('is closed when the text does not match', () => {
- setInput('elephant');
- return wrapper.vm.$nextTick().then(() => {
- expect(findDropdown().isVisible()).toBe(false);
- });
- });
- });
-
- describe('keyboard navigation in dropdown', () => {
- beforeEach(() => {
- createComponent();
- });
-
- describe('on down arrow + enter', () => {
- it('selects the next item in the list and closes the dropdown', () => {
- setInput('AWS');
- return wrapper.vm
- .$nextTick()
- .then(() => {
- findInput().trigger('keydown.down');
- findInput().trigger('keydown.enter');
- return wrapper.vm.$nextTick();
- })
- .then(() => {
- expect(findInputValue()).toBe(awsTokenList[0]);
- });
- });
-
- it('loops to the top when it reaches the bottom', () => {
- setInput('AWS');
- return wrapper.vm
- .$nextTick()
- .then(() => {
- doTimes(findDropdownOptions().length + 1, clickDown);
- findInput().trigger('keydown.enter');
- return wrapper.vm.$nextTick();
- })
- .then(() => {
- expect(findInputValue()).toBe(awsTokenList[0]);
- });
- });
- });
-
- describe('on up arrow + enter', () => {
- it('selects the previous item in the list and closes the dropdown', () => {
- setInput('AWS');
- return wrapper.vm
- .$nextTick()
- .then(() => {
- doTimes(3, clickDown);
- findInput().trigger('keydown.up');
- findInput().trigger('keydown.enter');
- return wrapper.vm.$nextTick();
- })
- .then(() => {
- expect(findInputValue()).toBe(awsTokenList[1]);
- });
- });
-
- it('loops to the bottom when it reaches the top', () => {
- setInput('AWS');
- return wrapper.vm
- .$nextTick()
- .then(() => {
- findInput().trigger('keydown.down');
- findInput().trigger('keydown.up');
- findInput().trigger('keydown.enter');
- return wrapper.vm.$nextTick();
- })
- .then(() => {
- expect(findInputValue()).toBe(awsTokenList[awsTokenList.length - 1]);
- });
- });
- });
-
- describe('on enter with no item highlighted', () => {
- it('does not select any item and closes the dropdown', () => {
- setInput('AWS');
- return wrapper.vm
- .$nextTick()
- .then(() => {
- findInput().trigger('keydown.enter');
- return wrapper.vm.$nextTick();
- })
- .then(() => {
- expect(findInputValue()).toBe('AWS');
- });
- });
- });
-
- describe('on click', () => {
- it('selects the clicked item regardless of arrow highlight', () => {
- setInput('AWS');
- return wrapper.vm
- .$nextTick()
- .then(() => {
- wrapper.find(GlButton).trigger('click');
- return wrapper.vm.$nextTick();
- })
- .then(() => {
- expect(findInputValue()).toBe(awsTokenList[0]);
- });
- });
- });
-
- describe('on tab', () => {
- it('selects entered text, closes dropdown', () => {
- setInput('AWS');
- return wrapper.vm
- .$nextTick()
- .then(() => {
- findInput().trigger('keydown.tab');
- doTimes(2, clickDown);
- return wrapper.vm.$nextTick();
- })
- .then(() => {
- expect(findInputValue()).toBe('AWS');
- expect(findDropdown().isVisible()).toBe(false);
- });
- });
- });
-
- describe('on esc', () => {
- describe('when dropdown is open', () => {
- it('closes dropdown and does not select anything', () => {
- setInput('AWS');
- return wrapper.vm
- .$nextTick()
- .then(() => {
- findInput().trigger('keydown.esc');
- return wrapper.vm.$nextTick();
- })
- .then(() => {
- expect(findInputValue()).toBe('AWS');
- expect(findDropdown().isVisible()).toBe(false);
- });
- });
- });
-
- describe('when dropdown is closed', () => {
- it('clears the input field', () => {
- setInput('elephant');
- return wrapper.vm
- .$nextTick()
- .then(() => {
- expect(findDropdown().isVisible()).toBe(false);
- findInput().trigger('keydown.esc');
- return wrapper.vm.$nextTick();
- })
- .then(() => {
- expect(findInputValue()).toBe('');
- });
- });
- });
- });
- });
-});
diff --git a/spec/frontend/ci_variable_list/components/ci_variable_modal_spec.js b/spec/frontend/ci_variable_list/components/ci_variable_modal_spec.js
index 094fdcdc185..ad398d6ccd6 100644
--- a/spec/frontend/ci_variable_list/components/ci_variable_modal_spec.js
+++ b/spec/frontend/ci_variable_list/components/ci_variable_modal_spec.js
@@ -1,10 +1,8 @@
import Vuex from 'vuex';
import { createLocalVue, shallowMount, mount } from '@vue/test-utils';
-import { GlDeprecatedButton } from '@gitlab/ui';
+import { GlDeprecatedButton, GlFormCombobox } from '@gitlab/ui';
import { AWS_ACCESS_KEY_ID } from '~/ci_variable_list/constants';
import CiVariableModal from '~/ci_variable_list/components/ci_variable_modal.vue';
-import CiKeyField from '~/ci_variable_list/components/ci_key_field.vue';
-import { awsTokens } from '~/ci_variable_list/components/ci_variable_autocomplete_tokens';
import createStore from '~/ci_variable_list/store';
import mockData from '../services/mock_data';
import ModalStub from '../stubs';
@@ -51,7 +49,7 @@ describe('Ci variable modal', () => {
});
it('does not render the autocomplete dropdown', () => {
- expect(wrapper.contains(CiKeyField)).toBe(false);
+ expect(wrapper.contains(GlFormCombobox)).toBe(false);
});
});
@@ -60,7 +58,7 @@ describe('Ci variable modal', () => {
createComponent(shallowMount);
});
it('renders the autocomplete dropdown', () => {
- expect(wrapper.find(CiKeyField).exists()).toBe(true);
+ expect(wrapper.find(GlFormCombobox).exists()).toBe(true);
});
});
});
@@ -159,10 +157,7 @@ describe('Ci variable modal', () => {
it('Update variable button dispatches updateVariable with correct variable', () => {
addOrUpdateButton(2).vm.$emit('click');
- expect(store.dispatch).toHaveBeenCalledWith(
- 'updateVariable',
- store.state.variableBeingEdited,
- );
+ expect(store.dispatch).toHaveBeenCalledWith('updateVariable');
});
it('Resets the editing state once modal is hidden', () => {
@@ -172,36 +167,13 @@ describe('Ci variable modal', () => {
it('dispatches deleteVariable with correct variable to delete', () => {
deleteVariableButton().vm.$emit('click');
- expect(store.dispatch).toHaveBeenCalledWith('deleteVariable', mockData.mockVariables[0]);
+ expect(store.dispatch).toHaveBeenCalledWith('deleteVariable');
});
});
describe('Validations', () => {
const maskError = 'This variable can not be masked.';
- describe('when the key state is invalid', () => {
- beforeEach(() => {
- const [variable] = mockData.mockVariables;
- const invalidKeyVariable = {
- ...variable,
- key: AWS_ACCESS_KEY_ID,
- value: 'AKIAIOSFODNN7EXAMPLEjdhy',
- secret_value: 'AKIAIOSFODNN7EXAMPLEjdhy',
- };
- createComponent(mount);
- store.state.variable = invalidKeyVariable;
- });
-
- it('disables the submit button', () => {
- expect(addOrUpdateButton(1).attributes('disabled')).toBeTruthy();
- });
-
- it('shows the correct error text', () => {
- const errorText = awsTokens[AWS_ACCESS_KEY_ID].invalidMessage;
- expect(findModal().text()).toContain(errorText);
- });
- });
-
describe('when the mask state is invalid', () => {
beforeEach(() => {
const [variable] = mockData.mockVariables;
@@ -225,39 +197,14 @@ describe('Ci variable modal', () => {
});
});
- describe('when the mask and key states are invalid', () => {
- beforeEach(() => {
- const [variable] = mockData.mockVariables;
- const invalidMaskandKeyVariable = {
- ...variable,
- key: AWS_ACCESS_KEY_ID,
- value: 'AKIAIOSFODNN7EXAMPLEjdhyd:;',
- secret_value: 'AKIAIOSFODNN7EXAMPLEjdhyd:;',
- masked: true,
- };
- createComponent(mount);
- store.state.variable = invalidMaskandKeyVariable;
- });
-
- it('disables the submit button', () => {
- expect(addOrUpdateButton(1).attributes('disabled')).toBeTruthy();
- });
-
- it('shows the correct error text', () => {
- const errorText = awsTokens[AWS_ACCESS_KEY_ID].invalidMessage;
- expect(findModal().text()).toContain(maskError);
- expect(findModal().text()).toContain(errorText);
- });
- });
-
describe('when both states are valid', () => {
beforeEach(() => {
const [variable] = mockData.mockVariables;
const validMaskandKeyVariable = {
...variable,
key: AWS_ACCESS_KEY_ID,
- value: 'AKIAIOSFODNN7EXAMPLE',
- secret_value: 'AKIAIOSFODNN7EXAMPLE',
+ value: '12345678',
+ secret_value: '87654321',
masked: true,
};
createComponent(mount);
@@ -268,12 +215,6 @@ describe('Ci variable modal', () => {
it('does not disable the submit button', () => {
expect(addOrUpdateButton(1).attributes('disabled')).toBeFalsy();
});
-
- it('shows no error text', () => {
- const errorText = awsTokens[AWS_ACCESS_KEY_ID].invalidMessage;
- expect(findModal().text()).not.toContain(maskError);
- expect(findModal().text()).not.toContain(errorText);
- });
});
});
});
diff --git a/spec/frontend/ci_variable_list/services/mock_data.js b/spec/frontend/ci_variable_list/services/mock_data.js
index 7dab33050d9..44f4db93c63 100644
--- a/spec/frontend/ci_variable_list/services/mock_data.js
+++ b/spec/frontend/ci_variable_list/services/mock_data.js
@@ -42,6 +42,7 @@ export default {
key: 'test_var',
masked: false,
protected: false,
+ protected_variable: false,
secret_value: 'test_val',
value: 'test_val',
variable_type: 'Variable',
@@ -52,6 +53,7 @@ export default {
key: 'test_var_2',
masked: false,
protected: false,
+ protected_variable: false,
secret_value: 'test_val_2',
value: 'test_val_2',
variable_type: 'File',
diff --git a/spec/frontend/ci_variable_list/store/actions_spec.js b/spec/frontend/ci_variable_list/store/actions_spec.js
index 12b4311d0f5..eb565d4c979 100644
--- a/spec/frontend/ci_variable_list/store/actions_spec.js
+++ b/spec/frontend/ci_variable_list/store/actions_spec.js
@@ -91,7 +91,7 @@ describe('CI variable list store actions', () => {
testAction(
actions.deleteVariable,
- mockVariable,
+ {},
state,
[],
[
@@ -110,7 +110,7 @@ describe('CI variable list store actions', () => {
testAction(
actions.deleteVariable,
- mockVariable,
+ {},
state,
[],
[
@@ -134,7 +134,7 @@ describe('CI variable list store actions', () => {
testAction(
actions.updateVariable,
- mockVariable,
+ {},
state,
[],
[
@@ -286,4 +286,66 @@ describe('CI variable list store actions', () => {
);
});
});
+
+ describe('Update variable values', () => {
+ it('updateVariableKey', () => {
+ testAction(
+ actions.updateVariableKey,
+ { key: mockVariable.key },
+ {},
+ [
+ {
+ type: types.UPDATE_VARIABLE_KEY,
+ payload: mockVariable.key,
+ },
+ ],
+ [],
+ );
+ });
+
+ it('updateVariableValue', () => {
+ testAction(
+ actions.updateVariableValue,
+ { secret_value: mockVariable.value },
+ {},
+ [
+ {
+ type: types.UPDATE_VARIABLE_VALUE,
+ payload: mockVariable.value,
+ },
+ ],
+ [],
+ );
+ });
+
+ it('updateVariableType', () => {
+ testAction(
+ actions.updateVariableType,
+ { variable_type: mockVariable.variable_type },
+ {},
+ [{ type: types.UPDATE_VARIABLE_TYPE, payload: mockVariable.variable_type }],
+ [],
+ );
+ });
+
+ it('updateVariableProtected', () => {
+ testAction(
+ actions.updateVariableProtected,
+ { protected_variable: mockVariable.protected },
+ {},
+ [{ type: types.UPDATE_VARIABLE_PROTECTED, payload: mockVariable.protected }],
+ [],
+ );
+ });
+
+ it('updateVariableMasked', () => {
+ testAction(
+ actions.updateVariableMasked,
+ { masked: mockVariable.masked },
+ {},
+ [{ type: types.UPDATE_VARIABLE_MASKED, payload: mockVariable.masked }],
+ [],
+ );
+ });
+ });
});
diff --git a/spec/frontend/ci_variable_list/store/mutations_spec.js b/spec/frontend/ci_variable_list/store/mutations_spec.js
index 1934d108957..663b3486a17 100644
--- a/spec/frontend/ci_variable_list/store/mutations_spec.js
+++ b/spec/frontend/ci_variable_list/store/mutations_spec.js
@@ -4,15 +4,6 @@ import * as types from '~/ci_variable_list/store/mutation_types';
describe('CI variable list mutations', () => {
let stateCopy;
- const variableBeingEdited = {
- environment_scope: '*',
- id: 63,
- key: 'test_var',
- masked: false,
- protected: false,
- value: 'test_val',
- variable_type: 'env_var',
- };
beforeEach(() => {
stateCopy = state();
@@ -29,18 +20,18 @@ describe('CI variable list mutations', () => {
});
describe('VARIABLE_BEING_EDITED', () => {
- it('should set variable that is being edited', () => {
- mutations[types.VARIABLE_BEING_EDITED](stateCopy, variableBeingEdited);
+ it('should set the variable that is being edited', () => {
+ mutations[types.VARIABLE_BEING_EDITED](stateCopy);
- expect(stateCopy.variableBeingEdited).toEqual(variableBeingEdited);
+ expect(stateCopy.variableBeingEdited).toBe(true);
});
});
describe('RESET_EDITING', () => {
- it('should reset variableBeingEdited to null', () => {
+ it('should reset variableBeingEdited to false', () => {
mutations[types.RESET_EDITING](stateCopy);
- expect(stateCopy.variableBeingEdited).toEqual(null);
+ expect(stateCopy.variableBeingEdited).toBe(false);
});
});
@@ -50,7 +41,7 @@ describe('CI variable list mutations', () => {
variable_type: 'Variable',
key: '',
secret_value: '',
- protected: false,
+ protected_variable: false,
masked: false,
environment_scope: 'All (default)',
};
@@ -74,15 +65,7 @@ describe('CI variable list mutations', () => {
describe('SET_ENVIRONMENT_SCOPE', () => {
const environment = 'production';
- it('should set scope to variable being updated if updating variable', () => {
- stateCopy.variableBeingEdited = variableBeingEdited;
-
- mutations[types.SET_ENVIRONMENT_SCOPE](stateCopy, environment);
-
- expect(stateCopy.variableBeingEdited.environment_scope).toBe('production');
- });
-
- it('should set scope to variable if adding new variable', () => {
+ it('should set environment scope on variable', () => {
mutations[types.SET_ENVIRONMENT_SCOPE](stateCopy, environment);
expect(stateCopy.variable.environment_scope).toBe('production');
@@ -102,7 +85,52 @@ describe('CI variable list mutations', () => {
it('should set protected value to true', () => {
mutations[types.SET_VARIABLE_PROTECTED](stateCopy);
- expect(stateCopy.variable.protected).toBe(true);
+ expect(stateCopy.variable.protected_variable).toBe(true);
+ });
+ });
+
+ describe('UPDATE_VARIABLE_KEY', () => {
+ it('should update variable key value', () => {
+ const key = 'new_var';
+ mutations[types.UPDATE_VARIABLE_KEY](stateCopy, key);
+
+ expect(stateCopy.variable.key).toBe(key);
+ });
+ });
+
+ describe('UPDATE_VARIABLE_VALUE', () => {
+ it('should update variable value', () => {
+ const value = 'variable_value';
+ mutations[types.UPDATE_VARIABLE_VALUE](stateCopy, value);
+
+ expect(stateCopy.variable.secret_value).toBe(value);
+ });
+ });
+
+ describe('UPDATE_VARIABLE_TYPE', () => {
+ it('should update variable type value', () => {
+ const type = 'File';
+ mutations[types.UPDATE_VARIABLE_TYPE](stateCopy, type);
+
+ expect(stateCopy.variable.variable_type).toBe(type);
+ });
+ });
+
+ describe('UPDATE_VARIABLE_PROTECTED', () => {
+ it('should update variable protected value', () => {
+ const protectedValue = true;
+ mutations[types.UPDATE_VARIABLE_PROTECTED](stateCopy, protectedValue);
+
+ expect(stateCopy.variable.protected_variable).toBe(protectedValue);
+ });
+ });
+
+ describe('UPDATE_VARIABLE_MASKED', () => {
+ it('should update variable masked value', () => {
+ const masked = true;
+ mutations[types.UPDATE_VARIABLE_MASKED](stateCopy, masked);
+
+ expect(stateCopy.variable.masked).toBe(masked);
});
});
});
diff --git a/spec/frontend/close_reopen_report_toggle_spec.js b/spec/frontend/close_reopen_report_toggle_spec.js
index f6b5e4bed87..d2ce6298c5c 100644
--- a/spec/frontend/close_reopen_report_toggle_spec.js
+++ b/spec/frontend/close_reopen_report_toggle_spec.js
@@ -274,12 +274,7 @@ describe('CloseReopenReportToggle', () => {
{
input: button,
valueAttribute: 'data-url',
- inputAttribute: 'href',
- },
- {
- input: button,
- valueAttribute: 'data-method',
- inputAttribute: 'data-method',
+ inputAttribute: 'data-endpoint',
},
],
});
diff --git a/spec/frontend/clusters/components/remove_cluster_confirmation_spec.js b/spec/frontend/clusters/components/remove_cluster_confirmation_spec.js
index 091d4e07987..f448948843a 100644
--- a/spec/frontend/clusters/components/remove_cluster_confirmation_spec.js
+++ b/spec/frontend/clusters/components/remove_cluster_confirmation_spec.js
@@ -28,7 +28,7 @@ describe('Remove cluster confirmation modal', () => {
describe('split button dropdown', () => {
const findModal = () => wrapper.find(GlModal).vm;
- const findSplitButton = () => wrapper.find(SplitButton).vm;
+ const findSplitButton = () => wrapper.find(SplitButton);
beforeEach(() => {
createComponent({ clusterName: 'my-test-cluster' });
@@ -36,7 +36,7 @@ describe('Remove cluster confirmation modal', () => {
});
it('opens modal with "cleanup" option', () => {
- findSplitButton().$emit('remove-cluster-and-cleanup');
+ findSplitButton().vm.$emit('remove-cluster-and-cleanup');
return wrapper.vm.$nextTick().then(() => {
expect(findModal().show).toHaveBeenCalled();
@@ -45,12 +45,23 @@ describe('Remove cluster confirmation modal', () => {
});
it('opens modal without "cleanup" option', () => {
- findSplitButton().$emit('remove-cluster');
+ findSplitButton().vm.$emit('remove-cluster');
return wrapper.vm.$nextTick().then(() => {
expect(findModal().show).toHaveBeenCalled();
expect(wrapper.vm.confirmCleanup).toEqual(false);
});
});
+
+ describe('with cluster management project', () => {
+ beforeEach(() => {
+ createComponent({ hasManagementProject: true });
+ });
+
+ it('renders regular button instead', () => {
+ expect(findSplitButton().exists()).toBe(false);
+ expect(wrapper.find('[data-testid="btnRemove"]').exists()).toBe(true);
+ });
+ });
});
});
diff --git a/spec/frontend/clusters_list/components/ancestor_notice_spec.js b/spec/frontend/clusters_list/components/ancestor_notice_spec.js
new file mode 100644
index 00000000000..c931912eaf9
--- /dev/null
+++ b/spec/frontend/clusters_list/components/ancestor_notice_spec.js
@@ -0,0 +1,51 @@
+import AncestorNotice from '~/clusters_list/components/ancestor_notice.vue';
+import ClusterStore from '~/clusters_list/store';
+import { shallowMount } from '@vue/test-utils';
+import { GlLink, GlSprintf } from '@gitlab/ui';
+
+describe('ClustersAncestorNotice', () => {
+ let store;
+ let wrapper;
+
+ const createWrapper = () => {
+ store = ClusterStore({ ancestorHelperPath: '/some/ancestor/path' });
+ wrapper = shallowMount(AncestorNotice, { store, stubs: { GlSprintf } });
+ return wrapper.vm.$nextTick();
+ };
+
+ beforeEach(() => {
+ return createWrapper();
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ describe('when cluster does not have ancestors', () => {
+ beforeEach(() => {
+ store.state.hasAncestorClusters = false;
+ return wrapper.vm.$nextTick();
+ });
+
+ it('displays no notice', () => {
+ expect(wrapper.isEmpty()).toBe(true);
+ });
+ });
+
+ describe('when cluster has ancestors', () => {
+ beforeEach(() => {
+ store.state.hasAncestorClusters = true;
+ return wrapper.vm.$nextTick();
+ });
+
+ it('displays notice text', () => {
+ expect(wrapper.text()).toContain(
+ 'Clusters are utilized by selecting the nearest ancestor with a matching environment scope. For example, project clusters will override group clusters.',
+ );
+ });
+
+ it('displays link', () => {
+ expect(wrapper.contains(GlLink)).toBe(true);
+ });
+ });
+});
diff --git a/spec/frontend/clusters_list/components/clusters_spec.js b/spec/frontend/clusters_list/components/clusters_spec.js
index 07faee7e50b..deb275a9bb9 100644
--- a/spec/frontend/clusters_list/components/clusters_spec.js
+++ b/spec/frontend/clusters_list/components/clusters_spec.js
@@ -4,7 +4,7 @@ import ClusterStore from '~/clusters_list/store';
import MockAdapter from 'axios-mock-adapter';
import { apiData } from '../mock_data';
import { mount } from '@vue/test-utils';
-import { GlLoadingIcon, GlTable, GlPagination } from '@gitlab/ui';
+import { GlLoadingIcon, GlPagination, GlSkeletonLoading, GlTable } from '@gitlab/ui';
import * as Sentry from '@sentry/browser';
describe('Clusters', () => {
@@ -64,7 +64,7 @@ describe('Clusters', () => {
describe('clusters table', () => {
describe('when data is loading', () => {
beforeEach(() => {
- wrapper.vm.$store.state.loading = true;
+ wrapper.vm.$store.state.loadingClusters = true;
return wrapper.vm.$nextTick();
});
@@ -131,19 +131,48 @@ describe('Clusters', () => {
});
describe('nodes present', () => {
- it.each`
- nodeSize | lineNumber
- ${'Unknown'} | ${0}
- ${'1'} | ${1}
- ${'2'} | ${2}
- ${'1'} | ${3}
- ${'1'} | ${4}
- ${'Unknown'} | ${5}
- `('renders node size for each cluster', ({ nodeSize, lineNumber }) => {
- const sizes = findTable().findAll('td:nth-child(3)');
- const size = sizes.at(lineNumber);
-
- expect(size.text()).toBe(nodeSize);
+ describe('nodes while loading', () => {
+ it.each`
+ nodeSize | lineNumber
+ ${null} | ${0}
+ ${'1'} | ${1}
+ ${'2'} | ${2}
+ ${'1'} | ${3}
+ ${'1'} | ${4}
+ ${null} | ${5}
+ `('renders node size for each cluster', ({ nodeSize, lineNumber }) => {
+ const sizes = findTable().findAll('td:nth-child(3)');
+ const size = sizes.at(lineNumber);
+
+ if (nodeSize) {
+ expect(size.text()).toBe(nodeSize);
+ } else {
+ expect(size.find(GlSkeletonLoading).exists()).toBe(true);
+ }
+ });
+ });
+
+ describe('nodes finish loading', () => {
+ beforeEach(() => {
+ wrapper.vm.$store.state.loadingNodes = false;
+ return wrapper.vm.$nextTick();
+ });
+
+ it.each`
+ nodeSize | lineNumber
+ ${'Unknown'} | ${0}
+ ${'1'} | ${1}
+ ${'2'} | ${2}
+ ${'1'} | ${3}
+ ${'1'} | ${4}
+ ${'Unknown'} | ${5}
+ `('renders node size for each cluster', ({ nodeSize, lineNumber }) => {
+ const sizes = findTable().findAll('td:nth-child(3)');
+ const size = sizes.at(lineNumber);
+
+ expect(size.text()).toBe(nodeSize);
+ expect(size.find(GlSkeletonLoading).exists()).toBe(false);
+ });
});
describe('nodes with unknown quantity', () => {
diff --git a/spec/frontend/clusters_list/store/actions_spec.js b/spec/frontend/clusters_list/store/actions_spec.js
index 74e351a3704..c8556350747 100644
--- a/spec/frontend/clusters_list/store/actions_spec.js
+++ b/spec/frontend/clusters_list/store/actions_spec.js
@@ -13,6 +13,28 @@ import * as Sentry from '@sentry/browser';
jest.mock('~/flash.js');
describe('Clusters store actions', () => {
+ let captureException;
+
+ describe('reportSentryError', () => {
+ beforeEach(() => {
+ captureException = jest.spyOn(Sentry, 'captureException');
+ });
+
+ afterEach(() => {
+ captureException.mockRestore();
+ });
+
+ it('should report sentry error', done => {
+ const sentryError = new Error('New Sentry Error');
+ const tag = 'sentryErrorTag';
+
+ testAction(actions.reportSentryError, { error: sentryError, tag }, {}, [], [], () => {
+ expect(captureException).toHaveBeenCalledWith(sentryError);
+ done();
+ });
+ });
+ });
+
describe('fetchClusters', () => {
let mock;
@@ -48,8 +70,9 @@ describe('Clusters store actions', () => {
{ endpoint: apiData.endpoint },
{},
[
+ { type: types.SET_LOADING_NODES, payload: true },
{ type: types.SET_CLUSTERS_DATA, payload: { data: apiData, paginationInformation } },
- { type: types.SET_LOADING_STATE, payload: false },
+ { type: types.SET_LOADING_CLUSTERS, payload: false },
],
[],
() => done(),
@@ -63,8 +86,20 @@ describe('Clusters store actions', () => {
actions.fetchClusters,
{ endpoint: apiData.endpoint },
{},
- [{ type: types.SET_LOADING_STATE, payload: false }],
- [],
+ [
+ { type: types.SET_LOADING_NODES, payload: true },
+ { type: types.SET_LOADING_CLUSTERS, payload: false },
+ { type: types.SET_LOADING_NODES, payload: false },
+ ],
+ [
+ {
+ type: 'reportSentryError',
+ payload: {
+ error: new Error('Request failed with status code 400'),
+ tag: 'fetchClustersErrorCallback',
+ },
+ },
+ ],
() => {
expect(flashError).toHaveBeenCalledWith(expect.stringMatching('error'));
done();
@@ -73,7 +108,6 @@ describe('Clusters store actions', () => {
});
describe('multiple api requests', () => {
- let captureException;
let pollRequest;
let pollStop;
@@ -81,7 +115,6 @@ describe('Clusters store actions', () => {
const pollHeaders = { 'poll-interval': pollInterval, ...headers };
beforeEach(() => {
- captureException = jest.spyOn(Sentry, 'captureException');
pollRequest = jest.spyOn(Poll.prototype, 'makeRequest');
pollStop = jest.spyOn(Poll.prototype, 'stop');
@@ -89,7 +122,6 @@ describe('Clusters store actions', () => {
});
afterEach(() => {
- captureException.mockRestore();
pollRequest.mockRestore();
pollStop.mockRestore();
});
@@ -100,8 +132,9 @@ describe('Clusters store actions', () => {
{ endpoint: apiData.endpoint },
{},
[
+ { type: types.SET_LOADING_NODES, payload: true },
{ type: types.SET_CLUSTERS_DATA, payload: { data: apiData, paginationInformation } },
- { type: types.SET_LOADING_STATE, payload: false },
+ { type: types.SET_LOADING_CLUSTERS, payload: false },
],
[],
() => {
@@ -149,17 +182,27 @@ describe('Clusters store actions', () => {
{ endpoint: apiData.endpoint },
{},
[
+ { type: types.SET_LOADING_NODES, payload: true },
{
type: types.SET_CLUSTERS_DATA,
payload: { data: badApiResponse, paginationInformation },
},
- { type: types.SET_LOADING_STATE, payload: false },
+ { type: types.SET_LOADING_CLUSTERS, payload: false },
+ { type: types.SET_LOADING_CLUSTERS, payload: false },
+ { type: types.SET_LOADING_NODES, payload: false },
+ ],
+ [
+ {
+ type: 'reportSentryError',
+ payload: {
+ error: new Error('clusters.every is not a function'),
+ tag: 'fetchClustersSuccessCallback',
+ },
+ },
],
- [],
() => {
expect(pollRequest).toHaveBeenCalledTimes(1);
expect(pollStop).toHaveBeenCalledTimes(1);
- expect(captureException).toHaveBeenCalledTimes(1);
done();
},
);
diff --git a/spec/frontend/clusters_list/store/mutations_spec.js b/spec/frontend/clusters_list/store/mutations_spec.js
new file mode 100644
index 00000000000..df0dfe587b6
--- /dev/null
+++ b/spec/frontend/clusters_list/store/mutations_spec.js
@@ -0,0 +1,60 @@
+import * as types from '~/clusters_list/store/mutation_types';
+import { apiData } from '../mock_data';
+import getInitialState from '~/clusters_list/store/state';
+import mutations from '~/clusters_list/store/mutations';
+
+describe('Admin statistics panel mutations', () => {
+ let state;
+
+ const paginationInformation = {
+ nextPage: 1,
+ page: 1,
+ perPage: 20,
+ previousPage: 1,
+ total: apiData.clusters.length,
+ totalPages: 1,
+ };
+
+ beforeEach(() => {
+ state = getInitialState();
+ });
+
+ describe(`${types.SET_CLUSTERS_DATA}`, () => {
+ it('sets clusters and pagination values', () => {
+ mutations[types.SET_CLUSTERS_DATA](state, { data: apiData, paginationInformation });
+
+ expect(state.clusters).toBe(apiData.clusters);
+ expect(state.clustersPerPage).toBe(paginationInformation.perPage);
+ expect(state.hasAncestorClusters).toBe(apiData.has_ancestor_clusters);
+ expect(state.totalCulsters).toBe(paginationInformation.total);
+ });
+ });
+
+ describe(`${types.SET_LOADING_CLUSTERS}`, () => {
+ it('sets value to false', () => {
+ expect(state.loadingClusters).toBe(true);
+
+ mutations[types.SET_LOADING_CLUSTERS](state, false);
+
+ expect(state.loadingClusters).toBe(false);
+ });
+ });
+
+ describe(`${types.SET_LOADING_NODES}`, () => {
+ it('sets value to false', () => {
+ expect(state.loadingNodes).toBe(true);
+
+ mutations[types.SET_LOADING_NODES](state, false);
+
+ expect(state.loadingNodes).toBe(false);
+ });
+ });
+
+ describe(`${types.SET_PAGE}`, () => {
+ it('changes page value', () => {
+ mutations[types.SET_PAGE](state, 123);
+
+ expect(state.page).toBe(123);
+ });
+ });
+});
diff --git a/spec/frontend/code_navigation/components/__snapshots__/popover_spec.js.snap b/spec/frontend/code_navigation/components/__snapshots__/popover_spec.js.snap
index 7079ddfc2ab..161c2bade05 100644
--- a/spec/frontend/code_navigation/components/__snapshots__/popover_spec.js.snap
+++ b/spec/frontend/code_navigation/components/__snapshots__/popover_spec.js.snap
@@ -10,53 +10,81 @@ exports[`Code navigation popover component renders popover 1`] = `
style="left: 0px;"
/>
- <div
- class="border-bottom"
+ <gl-tabs-stub
+ contentclass="gl-py-0"
+ nav-class="gl-hidden"
+ theme="indigo"
>
- <pre
- class="border-0 bg-transparent m-0 code highlight"
+ <gl-tab-stub
+ title="Definition"
>
- <span
- class="line"
- lang="javascript"
+ <div
+ class="overflow-auto code-navigation-popover-container"
>
- <span
- class="k"
+ <div
+ class=""
>
- function
- </span>
- <span>
- main() {
- </span>
- </span>
- <span
- class="line"
- lang="javascript"
+ <pre
+ class="border-0 bg-transparent m-0 code highlight text-wrap"
+ >
+ <span
+ class="line"
+ lang="javascript"
+ >
+ <span
+ class="k"
+ >
+ function
+ </span>
+ <span>
+ main() {
+ </span>
+ </span>
+ <span
+ class="line"
+ lang="javascript"
+ >
+ <span>
+ }
+ </span>
+ </span>
+ </pre>
+ </div>
+ </div>
+
+ <div
+ class="popover-body border-top"
>
- <span>
- }
- </span>
- </span>
- </pre>
- </div>
-
- <div
- class="popover-body"
- >
- <gl-button-stub
- category="tertiary"
- class="w-100"
- data-testid="go-to-definition-btn"
- href="http://gitlab.com/test.js#L20"
- icon=""
- size="medium"
- target="_blank"
- variant="default"
+ <gl-button-stub
+ category="tertiary"
+ class="w-100"
+ data-testid="go-to-definition-btn"
+ href="http://gitlab.com/test.js"
+ icon=""
+ size="medium"
+ target="_blank"
+ variant="default"
+ >
+
+ Go to definition
+
+ </gl-button-stub>
+ </div>
+ </gl-tab-stub>
+
+ <gl-tab-stub
+ class="py-2"
+ data-testid="references-tab"
>
+
+ <p
+ class="gl-my-4 gl-px-4"
+ >
+
+ No references found
- Go to definition
-
- </gl-button-stub>
- </div>
+ </p>
+ </gl-tab-stub>
+ </gl-tabs-stub>
</div>
`;
diff --git a/spec/frontend/code_navigation/components/popover_spec.js b/spec/frontend/code_navigation/components/popover_spec.js
index b3f814f1be4..7b323cfab72 100644
--- a/spec/frontend/code_navigation/components/popover_spec.js
+++ b/spec/frontend/code_navigation/components/popover_spec.js
@@ -26,7 +26,8 @@ const MOCK_CODE_DATA = Object.freeze({
],
},
],
- definition_path: 'test.js#L20',
+ definition_path: 'test.js',
+ definitionLineNumber: 20,
});
const MOCK_DOCS_DATA = Object.freeze({
@@ -39,6 +40,17 @@ const MOCK_DOCS_DATA = Object.freeze({
definition_path: 'test.js#L20',
});
+const MOCK_DATA_WITH_REFERENCES = Object.freeze({
+ hover: [
+ {
+ language: null,
+ value: 'console.log',
+ },
+ ],
+ references: [{ path: 'index.js' }, { path: 'app.js' }],
+ definition_path: 'test.js#L20',
+});
+
let wrapper;
function factory({ position, data, definitionPathPrefix, blobPath = 'index.js' }) {
@@ -63,6 +75,16 @@ describe('Code navigation popover component', () => {
expect(wrapper.element).toMatchSnapshot();
});
+ it('srender references tab with empty text when no references exist', () => {
+ factory({
+ position: { x: 0, y: 0, height: 0 },
+ data: MOCK_CODE_DATA,
+ definitionPathPrefix: DEFINITION_PATH_PREFIX,
+ });
+
+ expect(wrapper.find('[data-testid="references-tab"]').text()).toContain('No references found');
+ });
+
it('renders link with hash to current file', () => {
factory({
position: { x: 0, y: 0, height: 0 },
@@ -74,6 +96,17 @@ describe('Code navigation popover component', () => {
expect(wrapper.find('[data-testid="go-to-definition-btn"]').attributes('href')).toBe('#L20');
});
+ it('renders list of references', () => {
+ factory({
+ position: { x: 0, y: 0, height: 0 },
+ data: MOCK_DATA_WITH_REFERENCES,
+ definitionPathPrefix: DEFINITION_PATH_PREFIX,
+ });
+
+ expect(wrapper.find('[data-testid="references-tab"]').exists()).toBe(true);
+ expect(wrapper.findAll('[data-testid="reference-link"]').length).toBe(2);
+ });
+
describe('code output', () => {
it('renders code output', () => {
factory({
diff --git a/spec/frontend/code_navigation/store/actions_spec.js b/spec/frontend/code_navigation/store/actions_spec.js
index 4cf77ed1be5..fbd93b10a14 100644
--- a/spec/frontend/code_navigation/store/actions_spec.js
+++ b/spec/frontend/code_navigation/store/actions_spec.js
@@ -69,7 +69,12 @@ describe('Code navigation actions', () => {
payload: {
path: 'index.js',
normalizedData: {
- '0:0': { start_line: 0, start_char: 0, hover: { value: '123' } },
+ '0:0': {
+ definitionLineNumber: 0,
+ start_line: 0,
+ start_char: 0,
+ hover: { value: '123' },
+ },
},
},
},
@@ -91,7 +96,12 @@ describe('Code navigation actions', () => {
payload: {
path: 'index.js',
normalizedData: {
- '0:0': { start_line: 0, start_char: 0, hover: { value: '123' } },
+ '0:0': {
+ definitionLineNumber: 0,
+ start_line: 0,
+ start_char: 0,
+ hover: { value: '123' },
+ },
},
},
},
@@ -159,7 +169,9 @@ describe('Code navigation actions', () => {
let target;
beforeEach(() => {
- setFixtures('<div data-path="index.js"><div class="js-test"></div></div>');
+ setFixtures(
+ '<div data-path="index.js"><div class="line"><div class="js-test"></div></div></div>',
+ );
target = document.querySelector('.js-test');
});
@@ -186,7 +198,7 @@ describe('Code navigation actions', () => {
payload: {
blobPath: 'index.js',
definition: { hover: 'test' },
- position: { height: 0, x: 0, y: 0 },
+ position: { height: 0, x: 0, y: 0, lineIndex: 0 },
},
},
],
@@ -210,7 +222,7 @@ describe('Code navigation actions', () => {
payload: {
blobPath: 'index.js',
definition: { hover: 'test' },
- position: { height: 0, x: 0, y: 0 },
+ position: { height: 0, x: 0, y: 0, lineIndex: 0 },
},
},
],
@@ -235,7 +247,7 @@ describe('Code navigation actions', () => {
payload: {
blobPath: 'index.js',
definition: { hover: 'test' },
- position: { height: 0, x: 0, y: 0 },
+ position: { height: 0, x: 0, y: 0, lineIndex: 0 },
},
},
],
diff --git a/spec/frontend/code_navigation/utils/index_spec.js b/spec/frontend/code_navigation/utils/index_spec.js
index 58c48a90075..6a01249d2a3 100644
--- a/spec/frontend/code_navigation/utils/index_spec.js
+++ b/spec/frontend/code_navigation/utils/index_spec.js
@@ -36,7 +36,7 @@ describe('setCurrentHoverElement', () => {
describe('addInteractionClass', () => {
beforeEach(() => {
setFixtures(
- '<div data-path="index.js"><div class="blob-content"><div id="LC1"><span>console</span><span>.</span><span>log</span></div><div id="LC2"><span>function</span></div></div></div>',
+ '<div data-path="index.js"><div class="blob-content"><div id="LC1" class="line"><span>console</span><span>.</span><span>log</span></div><div id="LC2" class="line"><span>function</span></div></div></div>',
);
});
diff --git a/spec/frontend/cycle_analytics/stage_nav_item_spec.js b/spec/frontend/cycle_analytics/stage_nav_item_spec.js
index 480bb756731..1fe80d3b1ce 100644
--- a/spec/frontend/cycle_analytics/stage_nav_item_spec.js
+++ b/spec/frontend/cycle_analytics/stage_nav_item_spec.js
@@ -10,7 +10,6 @@ describe('StageNavItem', () => {
const func = shallow ? shallowMount : mount;
return func(StageNavItem, {
propsData: {
- canEdit: false,
isActive: false,
isUserAllowed: false,
isDefaultStage: true,
@@ -125,7 +124,7 @@ describe('StageNavItem', () => {
describe('User can edit stages', () => {
beforeEach(() => {
- wrapper = createComponent({ canEdit: true, isUserAllowed: true }, false);
+ wrapper = createComponent({ isUserAllowed: true }, false);
});
afterEach(() => {
diff --git a/spec/frontend/design_management/components/design_notes/design_discussion_spec.js b/spec/frontend/design_management/components/design_notes/design_discussion_spec.js
index 557f53e864f..102e8e0664c 100644
--- a/spec/frontend/design_management/components/design_notes/design_discussion_spec.js
+++ b/spec/frontend/design_management/components/design_notes/design_discussion_spec.js
@@ -4,7 +4,7 @@ import notes from '../../mock_data/notes';
import DesignDiscussion from '~/design_management/components/design_notes/design_discussion.vue';
import DesignNote from '~/design_management/components/design_notes/design_note.vue';
import DesignReplyForm from '~/design_management/components/design_notes/design_reply_form.vue';
-import createNoteMutation from '~/design_management/graphql/mutations/createNote.mutation.graphql';
+import createNoteMutation from '~/design_management/graphql/mutations/create_note.mutation.graphql';
import toggleResolveDiscussionMutation from '~/design_management/graphql/mutations/toggle_resolve_discussion.mutation.graphql';
import ReplyPlaceholder from '~/notes/components/discussion_reply_placeholder.vue';
import ToggleRepliesWidget from '~/design_management/components/design_notes/toggle_replies_widget.vue';
diff --git a/spec/frontend/design_management/pages/design/index_spec.js b/spec/frontend/design_management/pages/design/index_spec.js
index 430cf8722fe..82b607eb77d 100644
--- a/spec/frontend/design_management/pages/design/index_spec.js
+++ b/spec/frontend/design_management/pages/design/index_spec.js
@@ -5,8 +5,8 @@ import { ApolloMutation } from 'vue-apollo';
import createFlash from '~/flash';
import DesignIndex from '~/design_management/pages/design/index.vue';
import DesignSidebar from '~/design_management/components/design_sidebar.vue';
-import DesignReplyForm from '~/design_management/components/design_notes/design_reply_form.vue';
-import createImageDiffNoteMutation from '~/design_management/graphql/mutations/createImageDiffNote.mutation.graphql';
+import DesignPresentation from '~/design_management/components/design_presentation.vue';
+import createImageDiffNoteMutation from '~/design_management/graphql/mutations/create_image_diff_note.mutation.graphql';
import design from '../../mock_data/design';
import mockResponseWithDesigns from '../../mock_data/designs';
import mockResponseNoDesigns from '../../mock_data/no_designs';
@@ -26,6 +26,15 @@ jest.mock('mousetrap', () => ({
unbind: jest.fn(),
}));
+const focusInput = jest.fn();
+
+const DesignReplyForm = {
+ template: '<div><textarea ref="textarea"></textarea></div>',
+ methods: {
+ focusInput,
+ },
+};
+
const localVue = createLocalVue();
localVue.use(VueRouter);
@@ -64,6 +73,7 @@ describe('Design management design index page', () => {
const findDiscussionForm = () => wrapper.find(DesignReplyForm);
const findSidebar = () => wrapper.find(DesignSidebar);
+ const findDesignPresentation = () => wrapper.find(DesignPresentation);
function createComponent(loading = false, data = {}) {
const $apollo = {
@@ -83,6 +93,7 @@ describe('Design management design index page', () => {
stubs: {
ApolloMutation,
DesignSidebar,
+ DesignReplyForm,
},
data() {
return {
@@ -153,13 +164,29 @@ describe('Design management design index page', () => {
},
});
- wrapper.vm.openCommentForm({ x: 0, y: 0 });
+ findDesignPresentation().vm.$emit('openCommentForm', { x: 0, y: 0 });
return wrapper.vm.$nextTick().then(() => {
expect(findDiscussionForm().exists()).toBe(true);
});
});
+ it('keeps new discussion form focused', () => {
+ createComponent(false, {
+ design: {
+ ...design,
+ discussions: {
+ nodes: [],
+ },
+ },
+ annotationCoordinates,
+ });
+
+ findDesignPresentation().vm.$emit('openCommentForm', { x: 10, y: 10 });
+
+ expect(focusInput).toHaveBeenCalled();
+ });
+
it('sends a mutation on submitting form and closes form', () => {
createComponent(false, {
design: {
diff --git a/spec/frontend/design_management/pages/index_spec.js b/spec/frontend/design_management/pages/index_spec.js
index d4e9bae3e89..d3761bf09e9 100644
--- a/spec/frontend/design_management/pages/index_spec.js
+++ b/spec/frontend/design_management/pages/index_spec.js
@@ -3,7 +3,7 @@ import { ApolloMutation } from 'vue-apollo';
import VueRouter from 'vue-router';
import { GlEmptyState } from '@gitlab/ui';
import Index from '~/design_management/pages/index.vue';
-import uploadDesignQuery from '~/design_management/graphql/mutations/uploadDesign.mutation.graphql';
+import uploadDesignQuery from '~/design_management/graphql/mutations/upload_design.mutation.graphql';
import DesignDestroyer from '~/design_management/components/design_destroyer.vue';
import DesignDropzone from '~/design_management/components/upload/design_dropzone.vue';
import DeleteButton from '~/design_management/components/delete_button.vue';
diff --git a/spec/frontend/design_management/utils/tracking_spec.js b/spec/frontend/design_management/utils/tracking_spec.js
index 9fa5eae55b3..0549fb44956 100644
--- a/spec/frontend/design_management/utils/tracking_spec.js
+++ b/spec/frontend/design_management/utils/tracking_spec.js
@@ -8,7 +8,7 @@ function getTrackingSpy(key) {
describe('Tracking Events', () => {
describe('trackDesignDetailView', () => {
const eventKey = 'projects:issues:design';
- const eventName = 'design_viewed';
+ const eventName = 'view_design';
it('trackDesignDetailView fires a tracking event when called', () => {
const trackingSpy = getTrackingSpy(eventKey);
@@ -20,11 +20,14 @@ describe('Tracking Events', () => {
eventName,
expect.objectContaining({
label: eventName,
- value: {
- 'internal-object-refrerer': '',
- 'design-collection-owner': '',
- 'design-version-number': 1,
- 'design-is-current-version': false,
+ context: {
+ schema: expect.any(String),
+ data: {
+ 'design-version-number': 1,
+ 'design-is-current-version': false,
+ 'internal-object-referrer': '',
+ 'design-collection-owner': '',
+ },
},
}),
);
@@ -40,11 +43,14 @@ describe('Tracking Events', () => {
eventName,
expect.objectContaining({
label: eventName,
- value: {
- 'internal-object-refrerer': 'from-a-test',
- 'design-collection-owner': 'test',
- 'design-version-number': 100,
- 'design-is-current-version': true,
+ context: {
+ schema: expect.any(String),
+ data: {
+ 'design-version-number': 100,
+ 'design-is-current-version': true,
+ 'internal-object-referrer': 'from-a-test',
+ 'design-collection-owner': 'test',
+ },
},
}),
);
diff --git a/spec/frontend/design_management_new/components/__snapshots__/design_note_pin_spec.js.snap b/spec/frontend/design_management_new/components/__snapshots__/design_note_pin_spec.js.snap
new file mode 100644
index 00000000000..4c848256e5b
--- /dev/null
+++ b/spec/frontend/design_management_new/components/__snapshots__/design_note_pin_spec.js.snap
@@ -0,0 +1,42 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Design note pin component should match the snapshot of note when repositioning 1`] = `
+<button
+ aria-label="Comment form position"
+ class="design-pin gl-absolute gl-display-flex gl-align-items-center gl-justify-content-center btn-transparent comment-indicator"
+ style="left: 10px; top: 10px; cursor: move;"
+ type="button"
+>
+ <icon-stub
+ name="image-comment-dark"
+ size="16"
+ />
+</button>
+`;
+
+exports[`Design note pin component should match the snapshot of note with index 1`] = `
+<button
+ aria-label="Comment '1' position"
+ class="design-pin gl-absolute gl-display-flex gl-align-items-center gl-justify-content-center js-image-badge badge badge-pill"
+ style="left: 10px; top: 10px;"
+ type="button"
+>
+
+ 1
+
+</button>
+`;
+
+exports[`Design note pin component should match the snapshot of note without index 1`] = `
+<button
+ aria-label="Comment form position"
+ class="design-pin gl-absolute gl-display-flex gl-align-items-center gl-justify-content-center btn-transparent comment-indicator"
+ style="left: 10px; top: 10px;"
+ type="button"
+>
+ <icon-stub
+ name="image-comment-dark"
+ size="16"
+ />
+</button>
+`;
diff --git a/spec/frontend/design_management_new/components/__snapshots__/design_presentation_spec.js.snap b/spec/frontend/design_management_new/components/__snapshots__/design_presentation_spec.js.snap
new file mode 100644
index 00000000000..189962c5b2e
--- /dev/null
+++ b/spec/frontend/design_management_new/components/__snapshots__/design_presentation_spec.js.snap
@@ -0,0 +1,104 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Design management design presentation component currentCommentForm is equal to current annotation position when isAnnotating is true 1`] = `
+<div
+ class="h-100 w-100 p-3 overflow-auto position-relative"
+>
+ <div
+ class="h-100 w-100 d-flex align-items-center position-relative"
+ >
+ <design-image-stub
+ image="test.jpg"
+ name="test"
+ scale="1"
+ />
+
+ <design-overlay-stub
+ currentcommentform="[object Object]"
+ dimensions="[object Object]"
+ notes=""
+ position="[object Object]"
+ />
+ </div>
+</div>
+`;
+
+exports[`Design management design presentation component currentCommentForm is null when isAnnotating is false 1`] = `
+<div
+ class="h-100 w-100 p-3 overflow-auto position-relative"
+>
+ <div
+ class="h-100 w-100 d-flex align-items-center position-relative"
+ >
+ <design-image-stub
+ image="test.jpg"
+ name="test"
+ scale="1"
+ />
+
+ <design-overlay-stub
+ dimensions="[object Object]"
+ notes=""
+ position="[object Object]"
+ />
+ </div>
+</div>
+`;
+
+exports[`Design management design presentation component currentCommentForm is null when isAnnotating is true but annotation position is falsey 1`] = `
+<div
+ class="h-100 w-100 p-3 overflow-auto position-relative"
+>
+ <div
+ class="h-100 w-100 d-flex align-items-center position-relative"
+ >
+ <design-image-stub
+ image="test.jpg"
+ name="test"
+ scale="1"
+ />
+
+ <design-overlay-stub
+ dimensions="[object Object]"
+ notes=""
+ position="[object Object]"
+ />
+ </div>
+</div>
+`;
+
+exports[`Design management design presentation component renders empty state when no image provided 1`] = `
+<div
+ class="h-100 w-100 p-3 overflow-auto position-relative"
+>
+ <div
+ class="h-100 w-100 d-flex align-items-center position-relative"
+ >
+ <!---->
+
+ <!---->
+ </div>
+</div>
+`;
+
+exports[`Design management design presentation component renders image and overlay when image provided 1`] = `
+<div
+ class="h-100 w-100 p-3 overflow-auto position-relative"
+>
+ <div
+ class="h-100 w-100 d-flex align-items-center position-relative"
+ >
+ <design-image-stub
+ image="test.jpg"
+ name="test"
+ scale="1"
+ />
+
+ <design-overlay-stub
+ dimensions="[object Object]"
+ notes=""
+ position="[object Object]"
+ />
+ </div>
+</div>
+`;
diff --git a/spec/frontend/design_management_new/components/__snapshots__/design_scaler_spec.js.snap b/spec/frontend/design_management_new/components/__snapshots__/design_scaler_spec.js.snap
new file mode 100644
index 00000000000..cb4575cbd11
--- /dev/null
+++ b/spec/frontend/design_management_new/components/__snapshots__/design_scaler_spec.js.snap
@@ -0,0 +1,115 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Design management design scaler component minus and reset buttons are disabled when scale === 1 1`] = `
+<div
+ class="design-scaler btn-group"
+ role="group"
+>
+ <button
+ class="btn"
+ disabled="disabled"
+ >
+ <span
+ class="d-flex-center gl-icon s16"
+ >
+
+ –
+
+ </span>
+ </button>
+
+ <button
+ class="btn"
+ disabled="disabled"
+ >
+ <gl-icon-stub
+ name="redo"
+ size="16"
+ />
+ </button>
+
+ <button
+ class="btn"
+ >
+ <gl-icon-stub
+ name="plus"
+ size="16"
+ />
+ </button>
+</div>
+`;
+
+exports[`Design management design scaler component minus and reset buttons are enabled when scale > 1 1`] = `
+<div
+ class="design-scaler btn-group"
+ role="group"
+>
+ <button
+ class="btn"
+ >
+ <span
+ class="d-flex-center gl-icon s16"
+ >
+
+ –
+
+ </span>
+ </button>
+
+ <button
+ class="btn"
+ >
+ <gl-icon-stub
+ name="redo"
+ size="16"
+ />
+ </button>
+
+ <button
+ class="btn"
+ >
+ <gl-icon-stub
+ name="plus"
+ size="16"
+ />
+ </button>
+</div>
+`;
+
+exports[`Design management design scaler component plus button is disabled when scale === 2 1`] = `
+<div
+ class="design-scaler btn-group"
+ role="group"
+>
+ <button
+ class="btn"
+ >
+ <span
+ class="d-flex-center gl-icon s16"
+ >
+
+ –
+
+ </span>
+ </button>
+
+ <button
+ class="btn"
+ >
+ <gl-icon-stub
+ name="redo"
+ size="16"
+ />
+ </button>
+
+ <button
+ class="btn"
+ disabled="disabled"
+ >
+ <gl-icon-stub
+ name="plus"
+ size="16"
+ />
+ </button>
+</div>
+`;
diff --git a/spec/frontend/design_management_new/components/__snapshots__/image_spec.js.snap b/spec/frontend/design_management_new/components/__snapshots__/image_spec.js.snap
new file mode 100644
index 00000000000..acaa62b11eb
--- /dev/null
+++ b/spec/frontend/design_management_new/components/__snapshots__/image_spec.js.snap
@@ -0,0 +1,68 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Design management large image component renders image 1`] = `
+<div
+ class="m-auto js-design-image"
+>
+ <!---->
+
+ <img
+ alt="test"
+ class="mh-100 img-fluid"
+ src="test.jpg"
+ />
+</div>
+`;
+
+exports[`Design management large image component renders loading state 1`] = `
+<div
+ class="m-auto js-design-image"
+ isloading="true"
+>
+ <!---->
+
+ <img
+ alt=""
+ class="mh-100 img-fluid"
+ src=""
+ />
+</div>
+`;
+
+exports[`Design management large image component renders media broken icon on error 1`] = `
+<gl-icon-stub
+ class="text-secondary-100"
+ name="media-broken"
+ size="48"
+/>
+`;
+
+exports[`Design management large image component sets correct classes and styles if imageStyle is set 1`] = `
+<div
+ class="m-auto js-design-image"
+>
+ <!---->
+
+ <img
+ alt="test"
+ class="mh-100"
+ src="test.jpg"
+ style="width: 100px; height: 100px;"
+ />
+</div>
+`;
+
+exports[`Design management large image component zoom sets image style when zoomed 1`] = `
+<div
+ class="m-auto js-design-image"
+>
+ <!---->
+
+ <img
+ alt="test"
+ class="mh-100"
+ src="test.jpg"
+ style="width: 200px; height: 200px;"
+ />
+</div>
+`;
diff --git a/spec/frontend/design_management_new/components/delete_button_spec.js b/spec/frontend/design_management_new/components/delete_button_spec.js
new file mode 100644
index 00000000000..218c58847a6
--- /dev/null
+++ b/spec/frontend/design_management_new/components/delete_button_spec.js
@@ -0,0 +1,51 @@
+import { shallowMount } from '@vue/test-utils';
+import { GlButton, GlModal, GlModalDirective } from '@gitlab/ui';
+import BatchDeleteButton from '~/design_management_new/components/delete_button.vue';
+
+describe('Batch delete button component', () => {
+ let wrapper;
+
+ const findButton = () => wrapper.find(GlButton);
+ const findModal = () => wrapper.find(GlModal);
+
+ function createComponent(isDeleting = false) {
+ wrapper = shallowMount(BatchDeleteButton, {
+ propsData: {
+ isDeleting,
+ },
+ directives: {
+ GlModalDirective,
+ },
+ });
+ }
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ it('renders non-disabled button by default', () => {
+ createComponent();
+
+ expect(findButton().exists()).toBe(true);
+ expect(findButton().attributes('disabled')).toBeFalsy();
+ });
+
+ it('renders disabled button when design is deleting', () => {
+ createComponent(true);
+ expect(findButton().attributes('disabled')).toBeTruthy();
+ });
+
+ it('emits `deleteSelectedDesigns` event on modal ok click', () => {
+ createComponent();
+ findButton().vm.$emit('click');
+ return wrapper.vm
+ .$nextTick()
+ .then(() => {
+ findModal().vm.$emit('ok');
+ return wrapper.vm.$nextTick();
+ })
+ .then(() => {
+ expect(wrapper.emitted().deleteSelectedDesigns).toBeTruthy();
+ });
+ });
+});
diff --git a/spec/frontend/design_management_new/components/design_note_pin_spec.js b/spec/frontend/design_management_new/components/design_note_pin_spec.js
new file mode 100644
index 00000000000..8e2caa604f4
--- /dev/null
+++ b/spec/frontend/design_management_new/components/design_note_pin_spec.js
@@ -0,0 +1,49 @@
+import { shallowMount } from '@vue/test-utils';
+import DesignNotePin from '~/design_management_new/components/design_note_pin.vue';
+
+describe('Design note pin component', () => {
+ let wrapper;
+
+ function createComponent(propsData = {}) {
+ wrapper = shallowMount(DesignNotePin, {
+ propsData: {
+ position: {
+ left: '10px',
+ top: '10px',
+ },
+ ...propsData,
+ },
+ });
+ }
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ it('should match the snapshot of note without index', () => {
+ createComponent();
+ expect(wrapper.element).toMatchSnapshot();
+ });
+
+ it('should match the snapshot of note with index', () => {
+ createComponent({ label: 1 });
+ expect(wrapper.element).toMatchSnapshot();
+ });
+
+ it('should match the snapshot of note when repositioning', () => {
+ createComponent({ repositioning: true });
+ expect(wrapper.element).toMatchSnapshot();
+ });
+
+ describe('pinStyle', () => {
+ it('sets cursor to `move` when repositioning = true', () => {
+ createComponent({ repositioning: true });
+ expect(wrapper.vm.pinStyle.cursor).toBe('move');
+ });
+
+ it('does not set cursor when repositioning = false', () => {
+ createComponent();
+ expect(wrapper.vm.pinStyle.cursor).toBe(undefined);
+ });
+ });
+});
diff --git a/spec/frontend/design_management_new/components/design_notes/__snapshots__/design_note_spec.js.snap b/spec/frontend/design_management_new/components/design_notes/__snapshots__/design_note_spec.js.snap
new file mode 100644
index 00000000000..b55bacb6fc5
--- /dev/null
+++ b/spec/frontend/design_management_new/components/design_notes/__snapshots__/design_note_spec.js.snap
@@ -0,0 +1,67 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Design note component should match the snapshot 1`] = `
+<timeline-entry-item-stub
+ class="design-note note-form"
+ id="note_123"
+>
+ <user-avatar-link-stub
+ imgalt=""
+ imgcssclasses=""
+ imgsize="40"
+ imgsrc=""
+ linkhref=""
+ tooltipplacement="top"
+ tooltiptext=""
+ username=""
+ />
+
+ <div
+ class="d-flex justify-content-between"
+ >
+ <div>
+ <a
+ class="js-user-link"
+ data-user-id="author-id"
+ >
+ <span
+ class="note-header-author-name bold"
+ >
+
+ </span>
+
+ <!---->
+
+ <span
+ class="note-headline-light"
+ >
+ @
+ </span>
+ </a>
+
+ <span
+ class="note-headline-light note-headline-meta"
+ >
+ <span
+ class="system-note-message"
+ />
+
+ <!---->
+ </span>
+ </div>
+
+ <div
+ class="gl-display-flex"
+ >
+
+ <!---->
+ </div>
+ </div>
+
+ <div
+ class="note-text js-note-text md"
+ data-qa-selector="note_content"
+ />
+
+</timeline-entry-item-stub>
+`;
diff --git a/spec/frontend/design_management_new/components/design_notes/__snapshots__/design_reply_form_spec.js.snap b/spec/frontend/design_management_new/components/design_notes/__snapshots__/design_reply_form_spec.js.snap
new file mode 100644
index 00000000000..e01c79e3520
--- /dev/null
+++ b/spec/frontend/design_management_new/components/design_notes/__snapshots__/design_reply_form_spec.js.snap
@@ -0,0 +1,15 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Design reply form component renders button text as "Comment" when creating a comment 1`] = `
+"<button data-track-event=\\"click_button\\" data-qa-selector=\\"save_comment_button\\" type=\\"submit\\" disabled=\\"disabled\\" class=\\"btn btn-success btn-md disabled\\">
+ <!---->
+ Comment
+</button>"
+`;
+
+exports[`Design reply form component renders button text as "Save comment" when creating a comment 1`] = `
+"<button data-track-event=\\"click_button\\" data-qa-selector=\\"save_comment_button\\" type=\\"submit\\" disabled=\\"disabled\\" class=\\"btn btn-success btn-md disabled\\">
+ <!---->
+ Save comment
+</button>"
+`;
diff --git a/spec/frontend/design_management_new/components/design_notes/design_discussion_spec.js b/spec/frontend/design_management_new/components/design_notes/design_discussion_spec.js
new file mode 100644
index 00000000000..401ce64e859
--- /dev/null
+++ b/spec/frontend/design_management_new/components/design_notes/design_discussion_spec.js
@@ -0,0 +1,322 @@
+import { mount } from '@vue/test-utils';
+import { GlLoadingIcon } from '@gitlab/ui';
+import notes from '../../mock_data/notes';
+import DesignDiscussion from '~/design_management_new/components/design_notes/design_discussion.vue';
+import DesignNote from '~/design_management_new/components/design_notes/design_note.vue';
+import DesignReplyForm from '~/design_management_new/components/design_notes/design_reply_form.vue';
+import createNoteMutation from '~/design_management_new/graphql/mutations/create_note.mutation.graphql';
+import toggleResolveDiscussionMutation from '~/design_management_new/graphql/mutations/toggle_resolve_discussion.mutation.graphql';
+import ReplyPlaceholder from '~/notes/components/discussion_reply_placeholder.vue';
+import ToggleRepliesWidget from '~/design_management_new/components/design_notes/toggle_replies_widget.vue';
+
+const discussion = {
+ id: '0',
+ resolved: false,
+ resolvable: true,
+ notes,
+};
+
+describe('Design discussions component', () => {
+ let wrapper;
+
+ const findDesignNotes = () => wrapper.findAll(DesignNote);
+ const findReplyPlaceholder = () => wrapper.find(ReplyPlaceholder);
+ const findReplyForm = () => wrapper.find(DesignReplyForm);
+ const findRepliesWidget = () => wrapper.find(ToggleRepliesWidget);
+ const findResolveButton = () => wrapper.find('[data-testid="resolve-button"]');
+ const findResolveIcon = () => wrapper.find('[data-testid="resolve-icon"]');
+ const findResolvedMessage = () => wrapper.find('[data-testid="resolved-message"]');
+ const findResolveLoadingIcon = () => wrapper.find(GlLoadingIcon);
+ const findResolveCheckbox = () => wrapper.find('[data-testid="resolve-checkbox"]');
+
+ const mutationVariables = {
+ mutation: createNoteMutation,
+ update: expect.anything(),
+ variables: {
+ input: {
+ noteableId: 'noteable-id',
+ body: 'test',
+ discussionId: '0',
+ },
+ },
+ };
+ const mutate = jest.fn(() => Promise.resolve());
+ const $apollo = {
+ mutate,
+ };
+
+ function createComponent(props = {}, data = {}) {
+ wrapper = mount(DesignDiscussion, {
+ propsData: {
+ resolvedDiscussionsExpanded: true,
+ discussion,
+ noteableId: 'noteable-id',
+ designId: 'design-id',
+ discussionIndex: 1,
+ discussionWithOpenForm: '',
+ ...props,
+ },
+ data() {
+ return {
+ ...data,
+ };
+ },
+ provide: {
+ projectPath: 'project-path',
+ issueIid: '1',
+ },
+ mocks: {
+ $apollo,
+ $route: {
+ hash: '#note_1',
+ },
+ },
+ });
+ }
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ describe('when discussion is not resolvable', () => {
+ beforeEach(() => {
+ createComponent({
+ discussion: {
+ ...discussion,
+ resolvable: false,
+ },
+ });
+ });
+
+ it('does not render an icon to resolve a thread', () => {
+ expect(findResolveIcon().exists()).toBe(false);
+ });
+
+ it('does not render a checkbox in reply form', () => {
+ findReplyPlaceholder().vm.$emit('onMouseDown');
+
+ return wrapper.vm.$nextTick().then(() => {
+ expect(findResolveCheckbox().exists()).toBe(false);
+ });
+ });
+ });
+
+ describe('when discussion is unresolved', () => {
+ beforeEach(() => {
+ createComponent();
+ });
+
+ it('renders correct amount of discussion notes', () => {
+ expect(findDesignNotes()).toHaveLength(2);
+ expect(findDesignNotes().wrappers.every(w => w.isVisible())).toBe(true);
+ });
+
+ it('renders reply placeholder', () => {
+ expect(findReplyPlaceholder().isVisible()).toBe(true);
+ });
+
+ it('does not render toggle replies widget', () => {
+ expect(findRepliesWidget().exists()).toBe(false);
+ });
+
+ it('renders a correct icon to resolve a thread', () => {
+ expect(findResolveIcon().props('name')).toBe('check-circle');
+ });
+
+ it('renders a checkbox with Resolve thread text in reply form', () => {
+ findReplyPlaceholder().vm.$emit('onClick');
+ wrapper.setProps({ discussionWithOpenForm: discussion.id });
+
+ return wrapper.vm.$nextTick().then(() => {
+ expect(findResolveCheckbox().text()).toBe('Resolve thread');
+ });
+ });
+
+ it('does not render resolved message', () => {
+ expect(findResolvedMessage().exists()).toBe(false);
+ });
+ });
+
+ describe('when discussion is resolved', () => {
+ beforeEach(() => {
+ createComponent({
+ discussion: {
+ ...discussion,
+ resolved: true,
+ resolvedBy: notes[0].author,
+ resolvedAt: '2020-05-08T07:10:45Z',
+ },
+ });
+ });
+
+ it('shows only the first note', () => {
+ expect(
+ findDesignNotes()
+ .at(0)
+ .isVisible(),
+ ).toBe(true);
+ expect(
+ findDesignNotes()
+ .at(1)
+ .isVisible(),
+ ).toBe(false);
+ });
+
+ it('renders resolved message', () => {
+ expect(findResolvedMessage().exists()).toBe(true);
+ });
+
+ it('does not show renders reply placeholder', () => {
+ expect(findReplyPlaceholder().isVisible()).toBe(false);
+ });
+
+ it('renders toggle replies widget with correct props', () => {
+ expect(findRepliesWidget().exists()).toBe(true);
+ expect(findRepliesWidget().props()).toEqual({
+ collapsed: true,
+ replies: notes.slice(1),
+ });
+ });
+
+ it('renders a correct icon to resolve a thread', () => {
+ expect(findResolveIcon().props('name')).toBe('check-circle-filled');
+ });
+
+ describe('when replies are expanded', () => {
+ beforeEach(() => {
+ findRepliesWidget().vm.$emit('toggle');
+ return wrapper.vm.$nextTick();
+ });
+
+ it('renders replies widget with collapsed prop equal to false', () => {
+ expect(findRepliesWidget().props('collapsed')).toBe(false);
+ });
+
+ it('renders the second note', () => {
+ expect(
+ findDesignNotes()
+ .at(1)
+ .isVisible(),
+ ).toBe(true);
+ });
+
+ it('renders a reply placeholder', () => {
+ expect(findReplyPlaceholder().isVisible()).toBe(true);
+ });
+
+ it('renders a checkbox with Unresolve thread text in reply form', () => {
+ findReplyPlaceholder().vm.$emit('onClick');
+ wrapper.setProps({ discussionWithOpenForm: discussion.id });
+
+ return wrapper.vm.$nextTick().then(() => {
+ expect(findResolveCheckbox().text()).toBe('Unresolve thread');
+ });
+ });
+ });
+ });
+
+ it('hides reply placeholder and opens form on placeholder click', () => {
+ createComponent();
+ findReplyPlaceholder().vm.$emit('onClick');
+ wrapper.setProps({ discussionWithOpenForm: discussion.id });
+
+ return wrapper.vm.$nextTick().then(() => {
+ expect(findReplyPlaceholder().exists()).toBe(false);
+ expect(findReplyForm().exists()).toBe(true);
+ });
+ });
+
+ it('calls mutation on submitting form and closes the form', () => {
+ createComponent(
+ { discussionWithOpenForm: discussion.id },
+ { discussionComment: 'test', isFormRendered: true },
+ );
+
+ findReplyForm().vm.$emit('submitForm');
+ expect(mutate).toHaveBeenCalledWith(mutationVariables);
+
+ return mutate()
+ .then(() => {
+ return wrapper.vm.$nextTick();
+ })
+ .then(() => {
+ expect(findReplyForm().exists()).toBe(false);
+ });
+ });
+
+ it('clears the discussion comment on closing comment form', () => {
+ createComponent(
+ { discussionWithOpenForm: discussion.id },
+ { discussionComment: 'test', isFormRendered: true },
+ );
+
+ return wrapper.vm
+ .$nextTick()
+ .then(() => {
+ findReplyForm().vm.$emit('cancelForm');
+
+ expect(wrapper.vm.discussionComment).toBe('');
+ return wrapper.vm.$nextTick();
+ })
+ .then(() => {
+ expect(findReplyForm().exists()).toBe(false);
+ });
+ });
+
+ it('applies correct class to design notes when discussion is highlighted', () => {
+ createComponent(
+ {},
+ {
+ activeDiscussion: {
+ id: notes[0].id,
+ source: 'pin',
+ },
+ },
+ );
+
+ expect(wrapper.findAll(DesignNote).wrappers.every(note => note.classes('gl-bg-blue-50'))).toBe(
+ true,
+ );
+ });
+
+ it('calls toggleResolveDiscussion mutation on resolve thread button click', () => {
+ createComponent();
+ findResolveButton().trigger('click');
+ expect(mutate).toHaveBeenCalledWith({
+ mutation: toggleResolveDiscussionMutation,
+ variables: {
+ id: discussion.id,
+ resolve: true,
+ },
+ });
+ return wrapper.vm.$nextTick(() => {
+ expect(findResolveLoadingIcon().exists()).toBe(true);
+ });
+ });
+
+ it('calls toggleResolveDiscussion mutation after adding a note if checkbox was checked', () => {
+ createComponent(
+ { discussionWithOpenForm: discussion.id },
+ { discussionComment: 'test', isFormRendered: true },
+ );
+ findResolveButton().trigger('click');
+ findReplyForm().vm.$emit('submitForm');
+
+ return mutate().then(() => {
+ expect(mutate).toHaveBeenCalledWith({
+ mutation: toggleResolveDiscussionMutation,
+ variables: {
+ id: discussion.id,
+ resolve: true,
+ },
+ });
+ });
+ });
+
+ it('emits openForm event on opening the form', () => {
+ createComponent();
+ findReplyPlaceholder().vm.$emit('onClick');
+
+ expect(wrapper.emitted('openForm')).toBeTruthy();
+ });
+});
diff --git a/spec/frontend/design_management_new/components/design_notes/design_note_spec.js b/spec/frontend/design_management_new/components/design_notes/design_note_spec.js
new file mode 100644
index 00000000000..b0e3e85b9c6
--- /dev/null
+++ b/spec/frontend/design_management_new/components/design_notes/design_note_spec.js
@@ -0,0 +1,170 @@
+import { shallowMount } from '@vue/test-utils';
+import { ApolloMutation } from 'vue-apollo';
+import DesignNote from '~/design_management_new/components/design_notes/design_note.vue';
+import UserAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link.vue';
+import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
+import DesignReplyForm from '~/design_management_new/components/design_notes/design_reply_form.vue';
+
+const scrollIntoViewMock = jest.fn();
+const note = {
+ id: 'gid://gitlab/DiffNote/123',
+ author: {
+ id: 'author-id',
+ },
+ body: 'test',
+ userPermissions: {
+ adminNote: false,
+ },
+};
+HTMLElement.prototype.scrollIntoView = scrollIntoViewMock;
+
+const $route = {
+ hash: '#note_123',
+};
+
+const mutate = jest.fn().mockResolvedValue({ data: { updateNote: {} } });
+
+describe('Design note component', () => {
+ let wrapper;
+
+ const findUserAvatar = () => wrapper.find(UserAvatarLink);
+ const findUserLink = () => wrapper.find('.js-user-link');
+ const findReplyForm = () => wrapper.find(DesignReplyForm);
+ const findEditButton = () => wrapper.find('.js-note-edit');
+ const findNoteContent = () => wrapper.find('.js-note-text');
+
+ function createComponent(props = {}, data = { isEditing: false }) {
+ wrapper = shallowMount(DesignNote, {
+ propsData: {
+ note: {},
+ ...props,
+ },
+ data() {
+ return {
+ ...data,
+ };
+ },
+ mocks: {
+ $route,
+ $apollo: {
+ mutate,
+ },
+ },
+ stubs: {
+ ApolloMutation,
+ },
+ });
+ }
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ it('should match the snapshot', () => {
+ createComponent({
+ note,
+ });
+
+ expect(wrapper.element).toMatchSnapshot();
+ });
+
+ it('should render an author', () => {
+ createComponent({
+ note,
+ });
+
+ expect(findUserAvatar().exists()).toBe(true);
+ expect(findUserLink().exists()).toBe(true);
+ });
+
+ it('should render a time ago tooltip if note has createdAt property', () => {
+ createComponent({
+ note: {
+ ...note,
+ createdAt: '2019-07-26T15:02:20Z',
+ },
+ });
+
+ expect(wrapper.find(TimeAgoTooltip).exists()).toBe(true);
+ });
+
+ it('should trigger a scrollIntoView method', () => {
+ createComponent({
+ note,
+ });
+
+ expect(scrollIntoViewMock).toHaveBeenCalled();
+ });
+
+ it('should not render edit icon when user does not have a permission', () => {
+ createComponent({
+ note,
+ });
+
+ expect(findEditButton().exists()).toBe(false);
+ });
+
+ describe('when user has a permission to edit note', () => {
+ it('should open an edit form on edit button click', () => {
+ createComponent({
+ note: {
+ ...note,
+ userPermissions: {
+ adminNote: true,
+ },
+ },
+ });
+
+ findEditButton().trigger('click');
+
+ return wrapper.vm.$nextTick().then(() => {
+ expect(findReplyForm().exists()).toBe(true);
+ expect(findNoteContent().exists()).toBe(false);
+ });
+ });
+
+ describe('when edit form is rendered', () => {
+ beforeEach(() => {
+ createComponent(
+ {
+ note: {
+ ...note,
+ userPermissions: {
+ adminNote: true,
+ },
+ },
+ },
+ { isEditing: true },
+ );
+ });
+
+ it('should not render note content and should render reply form', () => {
+ expect(findNoteContent().exists()).toBe(false);
+ expect(findReplyForm().exists()).toBe(true);
+ });
+
+ it('hides the form on hideForm event', () => {
+ findReplyForm().vm.$emit('cancelForm');
+
+ return wrapper.vm.$nextTick().then(() => {
+ expect(findReplyForm().exists()).toBe(false);
+ expect(findNoteContent().exists()).toBe(true);
+ });
+ });
+
+ it('calls a mutation on submitForm event and hides a form', () => {
+ findReplyForm().vm.$emit('submitForm');
+ expect(mutate).toHaveBeenCalled();
+
+ return mutate()
+ .then(() => {
+ return wrapper.vm.$nextTick();
+ })
+ .then(() => {
+ expect(findReplyForm().exists()).toBe(false);
+ expect(findNoteContent().exists()).toBe(true);
+ });
+ });
+ });
+ });
+});
diff --git a/spec/frontend/design_management_new/components/design_notes/design_reply_form_spec.js b/spec/frontend/design_management_new/components/design_notes/design_reply_form_spec.js
new file mode 100644
index 00000000000..9c1d6154516
--- /dev/null
+++ b/spec/frontend/design_management_new/components/design_notes/design_reply_form_spec.js
@@ -0,0 +1,184 @@
+import { mount } from '@vue/test-utils';
+import DesignReplyForm from '~/design_management_new/components/design_notes/design_reply_form.vue';
+
+const showModal = jest.fn();
+
+const GlModal = {
+ template: '<div><slot name="modal-title"></slot><slot></slot><slot name="modal-ok"></slot></div>',
+ methods: {
+ show: showModal,
+ },
+};
+
+describe('Design reply form component', () => {
+ let wrapper;
+
+ const findTextarea = () => wrapper.find('textarea');
+ const findSubmitButton = () => wrapper.find({ ref: 'submitButton' });
+ const findCancelButton = () => wrapper.find({ ref: 'cancelButton' });
+ const findModal = () => wrapper.find({ ref: 'cancelCommentModal' });
+
+ function createComponent(props = {}, mountOptions = {}) {
+ wrapper = mount(DesignReplyForm, {
+ propsData: {
+ value: '',
+ isSaving: false,
+ ...props,
+ },
+ stubs: { GlModal },
+ ...mountOptions,
+ });
+ }
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ it('textarea has focus after component mount', () => {
+ // We need to attach to document, so that `document.activeElement` is properly set in jsdom
+ createComponent({}, { attachToDocument: true });
+
+ expect(findTextarea().element).toEqual(document.activeElement);
+ });
+
+ it('renders button text as "Comment" when creating a comment', () => {
+ createComponent();
+
+ expect(findSubmitButton().html()).toMatchSnapshot();
+ });
+
+ it('renders button text as "Save comment" when creating a comment', () => {
+ createComponent({ isNewComment: false });
+
+ expect(findSubmitButton().html()).toMatchSnapshot();
+ });
+
+ describe('when form has no text', () => {
+ beforeEach(() => {
+ createComponent({
+ value: '',
+ });
+ });
+
+ it('submit button is disabled', () => {
+ expect(findSubmitButton().attributes().disabled).toBeTruthy();
+ });
+
+ it('does not emit submitForm event on textarea ctrl+enter keydown', () => {
+ findTextarea().trigger('keydown.enter', {
+ ctrlKey: true,
+ });
+
+ return wrapper.vm.$nextTick().then(() => {
+ expect(wrapper.emitted('submitForm')).toBeFalsy();
+ });
+ });
+
+ it('does not emit submitForm event on textarea meta+enter keydown', () => {
+ findTextarea().trigger('keydown.enter', {
+ metaKey: true,
+ });
+
+ return wrapper.vm.$nextTick().then(() => {
+ expect(wrapper.emitted('submitForm')).toBeFalsy();
+ });
+ });
+
+ it('emits cancelForm event on pressing escape button on textarea', () => {
+ findTextarea().trigger('keyup.esc');
+
+ expect(wrapper.emitted('cancelForm')).toBeTruthy();
+ });
+
+ it('emits cancelForm event on clicking Cancel button', () => {
+ findCancelButton().vm.$emit('click');
+
+ expect(wrapper.emitted('cancelForm')).toHaveLength(1);
+ });
+ });
+
+ describe('when form has text', () => {
+ beforeEach(() => {
+ createComponent({
+ value: 'test',
+ });
+ });
+
+ it('submit button is enabled', () => {
+ expect(findSubmitButton().attributes().disabled).toBeFalsy();
+ });
+
+ it('emits submitForm event on Comment button click', () => {
+ findSubmitButton().vm.$emit('click');
+
+ return wrapper.vm.$nextTick().then(() => {
+ expect(wrapper.emitted('submitForm')).toBeTruthy();
+ });
+ });
+
+ it('emits submitForm event on textarea ctrl+enter keydown', () => {
+ findTextarea().trigger('keydown.enter', {
+ ctrlKey: true,
+ });
+
+ return wrapper.vm.$nextTick().then(() => {
+ expect(wrapper.emitted('submitForm')).toBeTruthy();
+ });
+ });
+
+ it('emits submitForm event on textarea meta+enter keydown', () => {
+ findTextarea().trigger('keydown.enter', {
+ metaKey: true,
+ });
+
+ return wrapper.vm.$nextTick().then(() => {
+ expect(wrapper.emitted('submitForm')).toBeTruthy();
+ });
+ });
+
+ it('emits input event on changing textarea content', () => {
+ findTextarea().setValue('test2');
+
+ return wrapper.vm.$nextTick().then(() => {
+ expect(wrapper.emitted('input')).toBeTruthy();
+ });
+ });
+
+ it('emits cancelForm event on Escape key if text was not changed', () => {
+ findTextarea().trigger('keyup.esc');
+
+ expect(wrapper.emitted('cancelForm')).toBeTruthy();
+ });
+
+ it('opens confirmation modal on Escape key when text has changed', () => {
+ wrapper.setProps({ value: 'test2' });
+
+ return wrapper.vm.$nextTick().then(() => {
+ findTextarea().trigger('keyup.esc');
+ expect(showModal).toHaveBeenCalled();
+ });
+ });
+
+ it('emits cancelForm event on Cancel button click if text was not changed', () => {
+ findCancelButton().trigger('click');
+
+ expect(wrapper.emitted('cancelForm')).toBeTruthy();
+ });
+
+ it('opens confirmation modal on Cancel button click when text has changed', () => {
+ wrapper.setProps({ value: 'test2' });
+
+ return wrapper.vm.$nextTick().then(() => {
+ findCancelButton().trigger('click');
+ expect(showModal).toHaveBeenCalled();
+ });
+ });
+
+ it('emits cancelForm event on modal Ok button click', () => {
+ findTextarea().trigger('keyup.esc');
+ findModal().vm.$emit('ok');
+
+ expect(wrapper.emitted('cancelForm')).toBeTruthy();
+ });
+ });
+});
diff --git a/spec/frontend/design_management_new/components/design_notes/toggle_replies_widget_spec.js b/spec/frontend/design_management_new/components/design_notes/toggle_replies_widget_spec.js
new file mode 100644
index 00000000000..d3c89075a24
--- /dev/null
+++ b/spec/frontend/design_management_new/components/design_notes/toggle_replies_widget_spec.js
@@ -0,0 +1,98 @@
+import { shallowMount } from '@vue/test-utils';
+import { GlIcon, GlButton, GlLink } from '@gitlab/ui';
+import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
+import ToggleRepliesWidget from '~/design_management_new/components/design_notes/toggle_replies_widget.vue';
+import notes from '../../mock_data/notes';
+
+describe('Toggle replies widget component', () => {
+ let wrapper;
+
+ const findToggleWrapper = () => wrapper.find('[data-testid="toggle-comments-wrapper"]');
+ const findIcon = () => wrapper.find(GlIcon);
+ const findButton = () => wrapper.find(GlButton);
+ const findAuthorLink = () => wrapper.find(GlLink);
+ const findTimeAgo = () => wrapper.find(TimeAgoTooltip);
+
+ function createComponent(props = {}) {
+ wrapper = shallowMount(ToggleRepliesWidget, {
+ propsData: {
+ collapsed: true,
+ replies: notes,
+ ...props,
+ },
+ });
+ }
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ describe('when replies are collapsed', () => {
+ beforeEach(() => {
+ createComponent();
+ });
+
+ it('should not have expanded class', () => {
+ expect(findToggleWrapper().classes()).not.toContain('expanded');
+ });
+
+ it('should render chevron-right icon', () => {
+ expect(findIcon().props('name')).toBe('chevron-right');
+ });
+
+ it('should have replies length on button', () => {
+ expect(findButton().text()).toBe('2 replies');
+ });
+
+ it('should render a link to the last reply author', () => {
+ expect(findAuthorLink().exists()).toBe(true);
+ expect(findAuthorLink().text()).toBe(notes[1].author.name);
+ expect(findAuthorLink().attributes('href')).toBe(notes[1].author.webUrl);
+ });
+
+ it('should render correct time ago tooltip', () => {
+ expect(findTimeAgo().exists()).toBe(true);
+ expect(findTimeAgo().props('time')).toBe(notes[1].createdAt);
+ });
+ });
+
+ describe('when replies are expanded', () => {
+ beforeEach(() => {
+ createComponent({ collapsed: false });
+ });
+
+ it('should have expanded class', () => {
+ expect(findToggleWrapper().classes()).toContain('expanded');
+ });
+
+ it('should render chevron-down icon', () => {
+ expect(findIcon().props('name')).toBe('chevron-down');
+ });
+
+ it('should have Collapse replies text on button', () => {
+ expect(findButton().text()).toBe('Collapse replies');
+ });
+
+ it('should not have a link to the last reply author', () => {
+ expect(findAuthorLink().exists()).toBe(false);
+ });
+
+ it('should not render time ago tooltip', () => {
+ expect(findTimeAgo().exists()).toBe(false);
+ });
+ });
+
+ it('should emit toggle event on icon click', () => {
+ createComponent();
+ findIcon().vm.$emit('click', new MouseEvent('click'));
+
+ expect(wrapper.emitted('toggle')).toHaveLength(1);
+ });
+
+ it('should emit toggle event on button click', () => {
+ createComponent();
+ findButton().vm.$emit('click', new MouseEvent('click'));
+
+ expect(wrapper.emitted('toggle')).toHaveLength(1);
+ });
+});
diff --git a/spec/frontend/design_management_new/components/design_overlay_spec.js b/spec/frontend/design_management_new/components/design_overlay_spec.js
new file mode 100644
index 00000000000..4ca69c143a8
--- /dev/null
+++ b/spec/frontend/design_management_new/components/design_overlay_spec.js
@@ -0,0 +1,410 @@
+import { mount } from '@vue/test-utils';
+import DesignOverlay from '~/design_management_new/components/design_overlay.vue';
+import updateActiveDiscussion from '~/design_management_new/graphql/mutations/update_active_discussion.mutation.graphql';
+import notes from '../mock_data/notes';
+import { ACTIVE_DISCUSSION_SOURCE_TYPES } from '~/design_management_new/constants';
+
+const mutate = jest.fn(() => Promise.resolve());
+
+describe('Design overlay component', () => {
+ let wrapper;
+
+ const mockDimensions = { width: 100, height: 100 };
+
+ const findOverlay = () => wrapper.find('.image-diff-overlay');
+ const findAllNotes = () => wrapper.findAll('.js-image-badge');
+ const findCommentBadge = () => wrapper.find('.comment-indicator');
+ const findFirstBadge = () => findAllNotes().at(0);
+ const findSecondBadge = () => findAllNotes().at(1);
+
+ const clickAndDragBadge = (elem, fromPoint, toPoint) => {
+ elem.trigger('mousedown', { clientX: fromPoint.x, clientY: fromPoint.y });
+ return wrapper.vm.$nextTick().then(() => {
+ elem.trigger('mousemove', { clientX: toPoint.x, clientY: toPoint.y });
+ return wrapper.vm.$nextTick();
+ });
+ };
+
+ function createComponent(props = {}, data = {}) {
+ wrapper = mount(DesignOverlay, {
+ propsData: {
+ dimensions: mockDimensions,
+ position: {
+ top: '0',
+ left: '0',
+ },
+ resolvedDiscussionsExpanded: false,
+ ...props,
+ },
+ data() {
+ return {
+ activeDiscussion: {
+ id: null,
+ source: null,
+ },
+ ...data,
+ };
+ },
+ mocks: {
+ $apollo: {
+ mutate,
+ },
+ },
+ });
+ }
+
+ it('should have correct inline style', () => {
+ createComponent();
+
+ expect(wrapper.find('.image-diff-overlay').attributes().style).toBe(
+ 'width: 100px; height: 100px; top: 0px; left: 0px;',
+ );
+ });
+
+ it('should emit `openCommentForm` when clicking on overlay', () => {
+ createComponent();
+ const newCoordinates = {
+ x: 10,
+ y: 10,
+ };
+
+ wrapper
+ .find('.image-diff-overlay-add-comment')
+ .trigger('mouseup', { offsetX: newCoordinates.x, offsetY: newCoordinates.y });
+ return wrapper.vm.$nextTick().then(() => {
+ expect(wrapper.emitted('openCommentForm')).toEqual([
+ [{ x: newCoordinates.x, y: newCoordinates.y }],
+ ]);
+ });
+ });
+
+ describe('with notes', () => {
+ it('should render only the first note', () => {
+ createComponent({
+ notes,
+ });
+ expect(findAllNotes()).toHaveLength(1);
+ });
+
+ describe('with resolved discussions toggle expanded', () => {
+ beforeEach(() => {
+ createComponent({
+ notes,
+ resolvedDiscussionsExpanded: true,
+ });
+ });
+
+ it('should render all notes', () => {
+ expect(findAllNotes()).toHaveLength(notes.length);
+ });
+
+ it('should have set the correct position for each note badge', () => {
+ expect(findFirstBadge().attributes().style).toBe('left: 10px; top: 15px;');
+ expect(findSecondBadge().attributes().style).toBe('left: 50px; top: 50px;');
+ });
+
+ it('should apply resolved class to the resolved note pin', () => {
+ expect(findSecondBadge().classes()).toContain('resolved');
+ });
+
+ it('when there is an active discussion, should apply inactive class to all pins besides the active one', () => {
+ wrapper.setData({
+ activeDiscussion: {
+ id: notes[0].id,
+ source: 'discussion',
+ },
+ });
+
+ return wrapper.vm.$nextTick().then(() => {
+ expect(findSecondBadge().classes()).toContain('inactive');
+ });
+ });
+ });
+
+ it('should recalculate badges positions on window resize', () => {
+ createComponent({
+ notes,
+ dimensions: {
+ width: 400,
+ height: 400,
+ },
+ });
+
+ expect(findFirstBadge().attributes().style).toBe('left: 40px; top: 60px;');
+
+ wrapper.setProps({
+ dimensions: {
+ width: 200,
+ height: 200,
+ },
+ });
+
+ return wrapper.vm.$nextTick().then(() => {
+ expect(findFirstBadge().attributes().style).toBe('left: 20px; top: 30px;');
+ });
+ });
+
+ it('should call an update active discussion mutation when clicking a note without moving it', () => {
+ const note = notes[0];
+ const { position } = note;
+ const mutationVariables = {
+ mutation: updateActiveDiscussion,
+ variables: {
+ id: note.id,
+ source: ACTIVE_DISCUSSION_SOURCE_TYPES.pin,
+ },
+ };
+
+ findFirstBadge().trigger('mousedown', { clientX: position.x, clientY: position.y });
+
+ return wrapper.vm.$nextTick().then(() => {
+ findFirstBadge().trigger('mouseup', { clientX: position.x, clientY: position.y });
+ expect(mutate).toHaveBeenCalledWith(mutationVariables);
+ });
+ });
+ });
+
+ describe('when moving notes', () => {
+ it('should update badge style when note is being moved', () => {
+ createComponent({
+ notes,
+ });
+
+ const { position } = notes[0];
+
+ return clickAndDragBadge(
+ findFirstBadge(),
+ { x: position.x, y: position.y },
+ { x: 20, y: 20 },
+ ).then(() => {
+ expect(findFirstBadge().attributes().style).toBe('left: 20px; top: 20px; cursor: move;');
+ });
+ });
+
+ it('should emit `moveNote` event when note-moving action ends', () => {
+ createComponent({ notes });
+ const note = notes[0];
+ const { position } = note;
+ const newCoordinates = { x: 20, y: 20 };
+
+ wrapper.setData({
+ movingNoteNewPosition: {
+ ...position,
+ ...newCoordinates,
+ },
+ movingNoteStartPosition: {
+ noteId: notes[0].id,
+ discussionId: notes[0].discussion.id,
+ ...position,
+ },
+ });
+
+ const badge = findFirstBadge();
+ return clickAndDragBadge(badge, { x: position.x, y: position.y }, newCoordinates)
+ .then(() => {
+ badge.trigger('mouseup');
+ return wrapper.vm.$nextTick();
+ })
+ .then(() => {
+ expect(wrapper.emitted('moveNote')).toEqual([
+ [
+ {
+ noteId: notes[0].id,
+ discussionId: notes[0].discussion.id,
+ coordinates: newCoordinates,
+ },
+ ],
+ ]);
+ });
+ });
+
+ describe('without [adminNote] permission', () => {
+ const mockNoteNotAuthorised = {
+ ...notes[0],
+ userPermissions: {
+ adminNote: false,
+ },
+ };
+
+ const mockNoteCoordinates = {
+ x: mockNoteNotAuthorised.position.x,
+ y: mockNoteNotAuthorised.position.y,
+ };
+
+ it('should be unable to move a note', () => {
+ createComponent({
+ dimensions: mockDimensions,
+ notes: [mockNoteNotAuthorised],
+ });
+
+ const badge = findAllNotes().at(0);
+ return clickAndDragBadge(badge, { ...mockNoteCoordinates }, { x: 20, y: 20 }).then(() => {
+ // note position should not change after a click-and-drag attempt
+ expect(findFirstBadge().attributes().style).toContain(
+ `left: ${mockNoteCoordinates.x}px; top: ${mockNoteCoordinates.y}px;`,
+ );
+ });
+ });
+ });
+ });
+
+ describe('with a new form', () => {
+ it('should render a new comment badge', () => {
+ createComponent({
+ currentCommentForm: {
+ ...notes[0].position,
+ },
+ });
+
+ expect(findCommentBadge().exists()).toBe(true);
+ expect(findCommentBadge().attributes().style).toBe('left: 10px; top: 15px;');
+ });
+
+ describe('when moving the comment badge', () => {
+ it('should update badge style to reflect new position', () => {
+ const { position } = notes[0];
+
+ createComponent({
+ currentCommentForm: {
+ ...position,
+ },
+ });
+
+ return clickAndDragBadge(
+ findCommentBadge(),
+ { x: position.x, y: position.y },
+ { x: 20, y: 20 },
+ ).then(() => {
+ expect(findCommentBadge().attributes().style).toBe(
+ 'left: 20px; top: 20px; cursor: move;',
+ );
+ });
+ });
+
+ it('should update badge style when note-moving action ends', () => {
+ const { position } = notes[0];
+ createComponent({
+ currentCommentForm: {
+ ...position,
+ },
+ });
+
+ const commentBadge = findCommentBadge();
+ const toPoint = { x: 20, y: 20 };
+
+ return clickAndDragBadge(commentBadge, { x: position.x, y: position.y }, toPoint)
+ .then(() => {
+ commentBadge.trigger('mouseup');
+ // simulates the currentCommentForm being updated in index.vue component, and
+ // propagated back down to this prop
+ wrapper.setProps({
+ currentCommentForm: { height: position.height, width: position.width, ...toPoint },
+ });
+ return wrapper.vm.$nextTick();
+ })
+ .then(() => {
+ expect(commentBadge.attributes().style).toBe('left: 20px; top: 20px;');
+ });
+ });
+
+ it.each`
+ element | getElementFunc | event
+ ${'overlay'} | ${findOverlay} | ${'mouseleave'}
+ ${'comment badge'} | ${findCommentBadge} | ${'mouseup'}
+ `(
+ 'should emit `openCommentForm` event when $event fired on $element element',
+ ({ getElementFunc, event }) => {
+ createComponent({
+ notes,
+ currentCommentForm: {
+ ...notes[0].position,
+ },
+ });
+
+ const newCoordinates = { x: 20, y: 20 };
+ wrapper.setData({
+ movingNoteStartPosition: {
+ ...notes[0].position,
+ },
+ movingNoteNewPosition: {
+ ...notes[0].position,
+ ...newCoordinates,
+ },
+ });
+
+ getElementFunc().trigger(event);
+ return wrapper.vm.$nextTick().then(() => {
+ expect(wrapper.emitted('openCommentForm')).toEqual([[newCoordinates]]);
+ });
+ },
+ );
+ });
+ });
+
+ describe('getMovingNotePositionDelta', () => {
+ it('should calculate delta correctly from state', () => {
+ createComponent();
+
+ wrapper.setData({
+ movingNoteStartPosition: {
+ clientX: 10,
+ clientY: 20,
+ },
+ });
+
+ const mockMouseEvent = {
+ clientX: 30,
+ clientY: 10,
+ };
+
+ expect(wrapper.vm.getMovingNotePositionDelta(mockMouseEvent)).toEqual({
+ deltaX: 20,
+ deltaY: -10,
+ });
+ });
+ });
+
+ describe('isPositionInOverlay', () => {
+ createComponent({ dimensions: mockDimensions });
+
+ it.each`
+ test | coordinates | expectedResult
+ ${'within overlay bounds'} | ${{ x: 50, y: 50 }} | ${true}
+ ${'outside overlay bounds'} | ${{ x: 101, y: 101 }} | ${false}
+ `('returns [$expectedResult] when position is $test', ({ coordinates, expectedResult }) => {
+ const position = { ...mockDimensions, ...coordinates };
+
+ expect(wrapper.vm.isPositionInOverlay(position)).toBe(expectedResult);
+ });
+ });
+
+ describe('getNoteRelativePosition', () => {
+ it('calculates position correctly', () => {
+ createComponent({ dimensions: mockDimensions });
+ const position = { x: 50, y: 50, width: 200, height: 200 };
+
+ expect(wrapper.vm.getNoteRelativePosition(position)).toEqual({ left: 25, top: 25 });
+ });
+ });
+
+ describe('canMoveNote', () => {
+ it.each`
+ adminNotePermission | canMoveNoteResult
+ ${true} | ${true}
+ ${false} | ${false}
+ ${undefined} | ${false}
+ `(
+ 'returns [$canMoveNoteResult] when [adminNote permission] is [$adminNotePermission]',
+ ({ adminNotePermission, canMoveNoteResult }) => {
+ createComponent();
+
+ const note = {
+ userPermissions: {
+ adminNote: adminNotePermission,
+ },
+ };
+ expect(wrapper.vm.canMoveNote(note)).toBe(canMoveNoteResult);
+ },
+ );
+ });
+});
diff --git a/spec/frontend/design_management_new/components/design_presentation_spec.js b/spec/frontend/design_management_new/components/design_presentation_spec.js
new file mode 100644
index 00000000000..d043a762cd2
--- /dev/null
+++ b/spec/frontend/design_management_new/components/design_presentation_spec.js
@@ -0,0 +1,553 @@
+import { shallowMount } from '@vue/test-utils';
+import DesignPresentation from '~/design_management_new/components/design_presentation.vue';
+import DesignOverlay from '~/design_management_new/components/design_overlay.vue';
+
+const mockOverlayData = {
+ overlayDimensions: {
+ width: 100,
+ height: 100,
+ },
+ overlayPosition: {
+ top: '0',
+ left: '0',
+ },
+};
+
+describe('Design management design presentation component', () => {
+ let wrapper;
+
+ function createComponent(
+ {
+ image,
+ imageName,
+ discussions = [],
+ isAnnotating = false,
+ resolvedDiscussionsExpanded = false,
+ } = {},
+ data = {},
+ stubs = {},
+ ) {
+ wrapper = shallowMount(DesignPresentation, {
+ propsData: {
+ image,
+ imageName,
+ discussions,
+ isAnnotating,
+ resolvedDiscussionsExpanded,
+ },
+ stubs,
+ });
+
+ wrapper.setData(data);
+ wrapper.element.scrollTo = jest.fn();
+ }
+
+ const findOverlayCommentButton = () => wrapper.find('.image-diff-overlay-add-comment');
+
+ /**
+ * Spy on $refs and mock given values
+ * @param {Object} viewportDimensions {width, height}
+ * @param {Object} childDimensions {width, height}
+ * @param {Float} scrollTopPerc 0 < x < 1
+ * @param {Float} scrollLeftPerc 0 < x < 1
+ */
+ function mockRefDimensions(
+ ref,
+ viewportDimensions,
+ childDimensions,
+ scrollTopPerc,
+ scrollLeftPerc,
+ ) {
+ jest.spyOn(ref, 'scrollWidth', 'get').mockReturnValue(childDimensions.width);
+ jest.spyOn(ref, 'scrollHeight', 'get').mockReturnValue(childDimensions.height);
+ jest.spyOn(ref, 'offsetWidth', 'get').mockReturnValue(viewportDimensions.width);
+ jest.spyOn(ref, 'offsetHeight', 'get').mockReturnValue(viewportDimensions.height);
+ jest
+ .spyOn(ref, 'scrollLeft', 'get')
+ .mockReturnValue((childDimensions.width - viewportDimensions.width) * scrollLeftPerc);
+ jest
+ .spyOn(ref, 'scrollTop', 'get')
+ .mockReturnValue((childDimensions.height - viewportDimensions.height) * scrollTopPerc);
+ }
+
+ function clickDragExplore(startCoords, endCoords, { useTouchEvents, mouseup } = {}) {
+ const event = useTouchEvents
+ ? {
+ mousedown: 'touchstart',
+ mousemove: 'touchmove',
+ mouseup: 'touchend',
+ }
+ : {
+ mousedown: 'mousedown',
+ mousemove: 'mousemove',
+ mouseup: 'mouseup',
+ };
+
+ const addCommentOverlay = findOverlayCommentButton();
+
+ // triggering mouse events on this element best simulates
+ // reality, as it is the lowest-level node that needs to
+ // respond to mouse events
+ addCommentOverlay.trigger(event.mousedown, {
+ clientX: startCoords.clientX,
+ clientY: startCoords.clientY,
+ });
+ return wrapper.vm
+ .$nextTick()
+ .then(() => {
+ addCommentOverlay.trigger(event.mousemove, {
+ clientX: endCoords.clientX,
+ clientY: endCoords.clientY,
+ });
+
+ return wrapper.vm.$nextTick();
+ })
+ .then(() => {
+ if (mouseup) {
+ addCommentOverlay.trigger(event.mouseup);
+ return wrapper.vm.$nextTick();
+ }
+
+ return undefined;
+ });
+ }
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ it('renders image and overlay when image provided', () => {
+ createComponent(
+ {
+ image: 'test.jpg',
+ imageName: 'test',
+ },
+ mockOverlayData,
+ );
+
+ return wrapper.vm.$nextTick().then(() => {
+ expect(wrapper.element).toMatchSnapshot();
+ });
+ });
+
+ it('renders empty state when no image provided', () => {
+ createComponent();
+
+ return wrapper.vm.$nextTick().then(() => {
+ expect(wrapper.element).toMatchSnapshot();
+ });
+ });
+
+ it('openCommentForm event emits correct data', () => {
+ createComponent(
+ {
+ image: 'test.jpg',
+ imageName: 'test',
+ },
+ mockOverlayData,
+ );
+
+ wrapper.vm.openCommentForm({ x: 1, y: 1 });
+
+ return wrapper.vm.$nextTick().then(() => {
+ expect(wrapper.emitted('openCommentForm')).toEqual([
+ [{ ...mockOverlayData.overlayDimensions, x: 1, y: 1 }],
+ ]);
+ });
+ });
+
+ describe('currentCommentForm', () => {
+ it('is null when isAnnotating is false', () => {
+ createComponent(
+ {
+ image: 'test.jpg',
+ imageName: 'test',
+ },
+ mockOverlayData,
+ );
+
+ return wrapper.vm.$nextTick().then(() => {
+ expect(wrapper.vm.currentCommentForm).toBeNull();
+ expect(wrapper.element).toMatchSnapshot();
+ });
+ });
+
+ it('is null when isAnnotating is true but annotation position is falsey', () => {
+ createComponent(
+ {
+ image: 'test.jpg',
+ imageName: 'test',
+ isAnnotating: true,
+ },
+ mockOverlayData,
+ );
+
+ return wrapper.vm.$nextTick().then(() => {
+ expect(wrapper.vm.currentCommentForm).toBeNull();
+ expect(wrapper.element).toMatchSnapshot();
+ });
+ });
+
+ it('is equal to current annotation position when isAnnotating is true', () => {
+ createComponent(
+ {
+ image: 'test.jpg',
+ imageName: 'test',
+ isAnnotating: true,
+ },
+ {
+ ...mockOverlayData,
+ currentAnnotationPosition: {
+ x: 1,
+ y: 1,
+ width: 100,
+ height: 100,
+ },
+ },
+ );
+
+ return wrapper.vm.$nextTick().then(() => {
+ expect(wrapper.vm.currentCommentForm).toEqual({
+ x: 1,
+ y: 1,
+ width: 100,
+ height: 100,
+ });
+ expect(wrapper.element).toMatchSnapshot();
+ });
+ });
+ });
+
+ describe('setOverlayPosition', () => {
+ beforeEach(() => {
+ createComponent(
+ {
+ image: 'test.jpg',
+ imageName: 'test',
+ },
+ mockOverlayData,
+ );
+ });
+
+ afterEach(() => {
+ jest.clearAllMocks();
+ });
+
+ it('sets overlay position correctly when overlay is smaller than viewport', () => {
+ jest.spyOn(wrapper.vm.$refs.presentationViewport, 'offsetWidth', 'get').mockReturnValue(200);
+ jest.spyOn(wrapper.vm.$refs.presentationViewport, 'offsetHeight', 'get').mockReturnValue(200);
+
+ wrapper.vm.setOverlayPosition();
+ expect(wrapper.vm.overlayPosition).toEqual({
+ left: `calc(50% - ${mockOverlayData.overlayDimensions.width / 2}px)`,
+ top: `calc(50% - ${mockOverlayData.overlayDimensions.height / 2}px)`,
+ });
+ });
+
+ it('sets overlay position correctly when overlay width is larger than viewports', () => {
+ jest.spyOn(wrapper.vm.$refs.presentationViewport, 'offsetWidth', 'get').mockReturnValue(50);
+ jest.spyOn(wrapper.vm.$refs.presentationViewport, 'offsetHeight', 'get').mockReturnValue(200);
+
+ wrapper.vm.setOverlayPosition();
+ expect(wrapper.vm.overlayPosition).toEqual({
+ left: '0',
+ top: `calc(50% - ${mockOverlayData.overlayDimensions.height / 2}px)`,
+ });
+ });
+
+ it('sets overlay position correctly when overlay height is larger than viewports', () => {
+ jest.spyOn(wrapper.vm.$refs.presentationViewport, 'offsetWidth', 'get').mockReturnValue(200);
+ jest.spyOn(wrapper.vm.$refs.presentationViewport, 'offsetHeight', 'get').mockReturnValue(50);
+
+ wrapper.vm.setOverlayPosition();
+ expect(wrapper.vm.overlayPosition).toEqual({
+ left: `calc(50% - ${mockOverlayData.overlayDimensions.width / 2}px)`,
+ top: '0',
+ });
+ });
+ });
+
+ describe('getViewportCenter', () => {
+ beforeEach(() => {
+ createComponent(
+ {
+ image: 'test.jpg',
+ imageName: 'test',
+ },
+ mockOverlayData,
+ );
+ });
+
+ it('calculate center correctly with no scroll', () => {
+ mockRefDimensions(
+ wrapper.vm.$refs.presentationViewport,
+ { width: 10, height: 10 },
+ { width: 20, height: 20 },
+ 0,
+ 0,
+ );
+
+ expect(wrapper.vm.getViewportCenter()).toEqual({
+ x: 5,
+ y: 5,
+ });
+ });
+
+ it('calculate center correctly with some scroll', () => {
+ mockRefDimensions(
+ wrapper.vm.$refs.presentationViewport,
+ { width: 10, height: 10 },
+ { width: 20, height: 20 },
+ 0.5,
+ 0.5,
+ );
+
+ expect(wrapper.vm.getViewportCenter()).toEqual({
+ x: 10,
+ y: 10,
+ });
+ });
+
+ it('Returns default case if no overflow (scrollWidth==offsetWidth, etc.)', () => {
+ mockRefDimensions(
+ wrapper.vm.$refs.presentationViewport,
+ { width: 20, height: 20 },
+ { width: 20, height: 20 },
+ 0.5,
+ 0.5,
+ );
+
+ expect(wrapper.vm.getViewportCenter()).toEqual({
+ x: 10,
+ y: 10,
+ });
+ });
+ });
+
+ describe('scaleZoomFocalPoint', () => {
+ it('scales focal point correctly when zooming in', () => {
+ createComponent(
+ {
+ image: 'test.jpg',
+ imageName: 'test',
+ },
+ {
+ ...mockOverlayData,
+ zoomFocalPoint: {
+ x: 5,
+ y: 5,
+ width: 50,
+ height: 50,
+ },
+ },
+ );
+
+ wrapper.vm.scaleZoomFocalPoint();
+ expect(wrapper.vm.zoomFocalPoint).toEqual({
+ x: 10,
+ y: 10,
+ width: 100,
+ height: 100,
+ });
+ });
+
+ it('scales focal point correctly when zooming out', () => {
+ createComponent(
+ {
+ image: 'test.jpg',
+ imageName: 'test',
+ },
+ {
+ ...mockOverlayData,
+ zoomFocalPoint: {
+ x: 10,
+ y: 10,
+ width: 200,
+ height: 200,
+ },
+ },
+ );
+
+ wrapper.vm.scaleZoomFocalPoint();
+ expect(wrapper.vm.zoomFocalPoint).toEqual({
+ x: 5,
+ y: 5,
+ width: 100,
+ height: 100,
+ });
+ });
+ });
+
+ describe('onImageResize', () => {
+ it('sets zoom focal point on initial load', () => {
+ createComponent(
+ {
+ image: 'test.jpg',
+ imageName: 'test',
+ },
+ mockOverlayData,
+ );
+
+ wrapper.setMethods({
+ shiftZoomFocalPoint: jest.fn(),
+ scaleZoomFocalPoint: jest.fn(),
+ scrollToFocalPoint: jest.fn(),
+ });
+
+ wrapper.vm.onImageResize({ width: 10, height: 10 });
+ return wrapper.vm.$nextTick().then(() => {
+ expect(wrapper.vm.shiftZoomFocalPoint).toHaveBeenCalled();
+ expect(wrapper.vm.initialLoad).toBe(false);
+ });
+ });
+
+ it('calls scaleZoomFocalPoint and scrollToFocalPoint after initial load', () => {
+ wrapper.vm.onImageResize({ width: 10, height: 10 });
+ return wrapper.vm.$nextTick().then(() => {
+ expect(wrapper.vm.scaleZoomFocalPoint).toHaveBeenCalled();
+ expect(wrapper.vm.scrollToFocalPoint).toHaveBeenCalled();
+ });
+ });
+ });
+
+ describe('onPresentationMousedown', () => {
+ it.each`
+ scenario | width | height
+ ${'width overflows'} | ${101} | ${100}
+ ${'height overflows'} | ${100} | ${101}
+ ${'width and height overflows'} | ${200} | ${200}
+ `('sets lastDragPosition when design $scenario', ({ width, height }) => {
+ createComponent();
+ mockRefDimensions(
+ wrapper.vm.$refs.presentationViewport,
+ { width: 100, height: 100 },
+ { width, height },
+ );
+
+ const newLastDragPosition = { x: 2, y: 2 };
+ wrapper.vm.onPresentationMousedown({
+ clientX: newLastDragPosition.x,
+ clientY: newLastDragPosition.y,
+ });
+
+ expect(wrapper.vm.lastDragPosition).toStrictEqual(newLastDragPosition);
+ });
+
+ it('does not set lastDragPosition if design does not overflow', () => {
+ const lastDragPosition = { x: 1, y: 1 };
+
+ createComponent({}, { lastDragPosition });
+ mockRefDimensions(
+ wrapper.vm.$refs.presentationViewport,
+ { width: 100, height: 100 },
+ { width: 50, height: 50 },
+ );
+
+ wrapper.vm.onPresentationMousedown({ clientX: 2, clientY: 2 });
+
+ // check lastDragPosition is unchanged
+ expect(wrapper.vm.lastDragPosition).toStrictEqual(lastDragPosition);
+ });
+ });
+
+ describe('getAnnotationPositon', () => {
+ it.each`
+ coordinates | overlayDimensions | position
+ ${{ x: 100, y: 100 }} | ${{ width: 50, height: 50 }} | ${{ x: 100, y: 100, width: 50, height: 50 }}
+ ${{ x: 100.2, y: 100.5 }} | ${{ width: 50.6, height: 50.0 }} | ${{ x: 100, y: 101, width: 51, height: 50 }}
+ `('returns correct annotation position', ({ coordinates, overlayDimensions, position }) => {
+ createComponent(undefined, {
+ overlayDimensions: {
+ width: overlayDimensions.width,
+ height: overlayDimensions.height,
+ },
+ });
+
+ expect(wrapper.vm.getAnnotationPositon(coordinates)).toStrictEqual(position);
+ });
+ });
+
+ describe('when design is overflowing', () => {
+ beforeEach(() => {
+ createComponent(
+ {
+ image: 'test.jpg',
+ imageName: 'test',
+ },
+ mockOverlayData,
+ {
+ 'design-overlay': DesignOverlay,
+ },
+ );
+
+ // mock a design that overflows
+ mockRefDimensions(
+ wrapper.vm.$refs.presentationViewport,
+ { width: 10, height: 10 },
+ { width: 20, height: 20 },
+ 0,
+ 0,
+ );
+ });
+
+ it('opens a comment form if design was not dragged', () => {
+ const addCommentOverlay = findOverlayCommentButton();
+ const startCoords = {
+ clientX: 1,
+ clientY: 1,
+ };
+
+ addCommentOverlay.trigger('mousedown', {
+ clientX: startCoords.clientX,
+ clientY: startCoords.clientY,
+ });
+
+ return wrapper.vm
+ .$nextTick()
+ .then(() => {
+ addCommentOverlay.trigger('mouseup');
+ return wrapper.vm.$nextTick();
+ })
+ .then(() => {
+ expect(wrapper.emitted('openCommentForm')).toBeDefined();
+ });
+ });
+
+ describe('when clicking and dragging', () => {
+ it.each`
+ description | useTouchEvents
+ ${'with touch events'} | ${true}
+ ${'without touch events'} | ${false}
+ `('calls scrollTo with correct arguments $description', ({ useTouchEvents }) => {
+ return clickDragExplore(
+ { clientX: 0, clientY: 0 },
+ { clientX: 10, clientY: 10 },
+ { useTouchEvents },
+ ).then(() => {
+ expect(wrapper.element.scrollTo).toHaveBeenCalledTimes(1);
+ expect(wrapper.element.scrollTo).toHaveBeenCalledWith(-10, -10);
+ });
+ });
+
+ it('does not open a comment form when drag position exceeds buffer', () => {
+ return clickDragExplore(
+ { clientX: 0, clientY: 0 },
+ { clientX: 10, clientY: 10 },
+ { mouseup: true },
+ ).then(() => {
+ expect(wrapper.emitted('openCommentForm')).toBeFalsy();
+ });
+ });
+
+ it('opens a comment form when drag position is within buffer', () => {
+ return clickDragExplore(
+ { clientX: 0, clientY: 0 },
+ { clientX: 1, clientY: 0 },
+ { mouseup: true },
+ ).then(() => {
+ expect(wrapper.emitted('openCommentForm')).toBeDefined();
+ });
+ });
+ });
+ });
+});
diff --git a/spec/frontend/design_management_new/components/design_scaler_spec.js b/spec/frontend/design_management_new/components/design_scaler_spec.js
new file mode 100644
index 00000000000..5ff2554cd60
--- /dev/null
+++ b/spec/frontend/design_management_new/components/design_scaler_spec.js
@@ -0,0 +1,67 @@
+import { shallowMount } from '@vue/test-utils';
+import DesignScaler from '~/design_management_new/components/design_scaler.vue';
+
+describe('Design management design scaler component', () => {
+ let wrapper;
+
+ function createComponent(propsData, data = {}) {
+ wrapper = shallowMount(DesignScaler, {
+ propsData,
+ });
+ wrapper.setData(data);
+ }
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ const getButton = type => {
+ const buttonTypeOrder = ['minus', 'reset', 'plus'];
+ const buttons = wrapper.findAll('button');
+ return buttons.at(buttonTypeOrder.indexOf(type));
+ };
+
+ it('emits @scale event when "plus" button clicked', () => {
+ createComponent();
+
+ getButton('plus').trigger('click');
+ expect(wrapper.emitted('scale')).toEqual([[1.2]]);
+ });
+
+ it('emits @scale event when "reset" button clicked (scale > 1)', () => {
+ createComponent({}, { scale: 1.6 });
+ return wrapper.vm.$nextTick().then(() => {
+ getButton('reset').trigger('click');
+ expect(wrapper.emitted('scale')).toEqual([[1]]);
+ });
+ });
+
+ it('emits @scale event when "minus" button clicked (scale > 1)', () => {
+ createComponent({}, { scale: 1.6 });
+
+ return wrapper.vm.$nextTick().then(() => {
+ getButton('minus').trigger('click');
+ expect(wrapper.emitted('scale')).toEqual([[1.4]]);
+ });
+ });
+
+ it('minus and reset buttons are disabled when scale === 1', () => {
+ createComponent();
+
+ expect(wrapper.element).toMatchSnapshot();
+ });
+
+ it('minus and reset buttons are enabled when scale > 1', () => {
+ createComponent({}, { scale: 1.2 });
+ return wrapper.vm.$nextTick().then(() => {
+ expect(wrapper.element).toMatchSnapshot();
+ });
+ });
+
+ it('plus button is disabled when scale === 2', () => {
+ createComponent({}, { scale: 2 });
+ return wrapper.vm.$nextTick().then(() => {
+ expect(wrapper.element).toMatchSnapshot();
+ });
+ });
+});
diff --git a/spec/frontend/design_management_new/components/design_sidebar_spec.js b/spec/frontend/design_management_new/components/design_sidebar_spec.js
new file mode 100644
index 00000000000..f1d442a7b21
--- /dev/null
+++ b/spec/frontend/design_management_new/components/design_sidebar_spec.js
@@ -0,0 +1,236 @@
+import { shallowMount } from '@vue/test-utils';
+import { GlCollapse, GlPopover } from '@gitlab/ui';
+import Cookies from 'js-cookie';
+import DesignSidebar from '~/design_management_new/components/design_sidebar.vue';
+import Participants from '~/sidebar/components/participants/participants.vue';
+import DesignDiscussion from '~/design_management_new/components/design_notes/design_discussion.vue';
+import design from '../mock_data/design';
+import updateActiveDiscussionMutation from '~/design_management_new/graphql/mutations/update_active_discussion.mutation.graphql';
+
+const updateActiveDiscussionMutationVariables = {
+ mutation: updateActiveDiscussionMutation,
+ variables: {
+ id: design.discussions.nodes[0].notes.nodes[0].id,
+ source: 'discussion',
+ },
+};
+
+const $route = {
+ params: {
+ id: '1',
+ },
+};
+
+const cookieKey = 'hide_design_resolved_comments_popover';
+
+const mutate = jest.fn().mockResolvedValue();
+
+describe('Design management design sidebar component', () => {
+ let wrapper;
+
+ const findDiscussions = () => wrapper.findAll(DesignDiscussion);
+ const findFirstDiscussion = () => findDiscussions().at(0);
+ const findUnresolvedDiscussions = () => wrapper.findAll('[data-testid="unresolved-discussion"]');
+ const findResolvedDiscussions = () => wrapper.findAll('[data-testid="resolved-discussion"]');
+ const findParticipants = () => wrapper.find(Participants);
+ const findCollapsible = () => wrapper.find(GlCollapse);
+ const findToggleResolvedCommentsButton = () => wrapper.find('[data-testid="resolved-comments"]');
+ const findPopover = () => wrapper.find(GlPopover);
+ const findNewDiscussionDisclaimer = () =>
+ wrapper.find('[data-testid="new-discussion-disclaimer"]');
+
+ function createComponent(props = {}) {
+ wrapper = shallowMount(DesignSidebar, {
+ propsData: {
+ design,
+ resolvedDiscussionsExpanded: false,
+ markdownPreviewPath: '',
+ ...props,
+ },
+ mocks: {
+ $route,
+ $apollo: {
+ mutate,
+ },
+ },
+ });
+ }
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ it('renders participants', () => {
+ createComponent();
+
+ expect(findParticipants().exists()).toBe(true);
+ });
+
+ it('passes the correct amount of participants to the Participants component', () => {
+ createComponent();
+
+ expect(findParticipants().props('participants')).toHaveLength(1);
+ });
+
+ describe('when has no discussions', () => {
+ beforeEach(() => {
+ createComponent({
+ design: {
+ ...design,
+ discussions: {
+ nodes: [],
+ },
+ },
+ });
+ });
+
+ it('does not render discussions', () => {
+ expect(findDiscussions().exists()).toBe(false);
+ });
+
+ it('renders a message about possibility to create a new discussion', () => {
+ expect(findNewDiscussionDisclaimer().exists()).toBe(true);
+ });
+ });
+
+ describe('when has discussions', () => {
+ beforeEach(() => {
+ Cookies.set(cookieKey, true);
+ createComponent();
+ });
+
+ it('renders correct amount of unresolved discussions', () => {
+ expect(findUnresolvedDiscussions()).toHaveLength(1);
+ });
+
+ it('renders correct amount of resolved discussions', () => {
+ expect(findResolvedDiscussions()).toHaveLength(1);
+ });
+
+ it('has resolved comments collapsible collapsed', () => {
+ expect(findCollapsible().attributes('visible')).toBeUndefined();
+ });
+
+ it('emits toggleResolveComments event on resolve comments button click', () => {
+ findToggleResolvedCommentsButton().vm.$emit('click');
+ expect(wrapper.emitted('toggleResolvedComments')).toHaveLength(1);
+ });
+
+ it('opens a collapsible when resolvedDiscussionsExpanded prop changes to true', () => {
+ expect(findCollapsible().attributes('visible')).toBeUndefined();
+ wrapper.setProps({
+ resolvedDiscussionsExpanded: true,
+ });
+ return wrapper.vm.$nextTick().then(() => {
+ expect(findCollapsible().attributes('visible')).toBe('true');
+ });
+ });
+
+ it('does not popover about resolved comments', () => {
+ expect(findPopover().exists()).toBe(false);
+ });
+
+ it('sends a mutation to set an active discussion when clicking on a discussion', () => {
+ findFirstDiscussion().trigger('click');
+
+ expect(mutate).toHaveBeenCalledWith(updateActiveDiscussionMutationVariables);
+ });
+
+ it('sends a mutation to reset an active discussion when clicking outside of discussion', () => {
+ wrapper.trigger('click');
+
+ expect(mutate).toHaveBeenCalledWith({
+ ...updateActiveDiscussionMutationVariables,
+ variables: { id: undefined, source: 'discussion' },
+ });
+ });
+
+ it('emits correct event on discussion create note error', () => {
+ findFirstDiscussion().vm.$emit('createNoteError', 'payload');
+ expect(wrapper.emitted('onDesignDiscussionError')).toEqual([['payload']]);
+ });
+
+ it('emits correct event on discussion update note error', () => {
+ findFirstDiscussion().vm.$emit('updateNoteError', 'payload');
+ expect(wrapper.emitted('updateNoteError')).toEqual([['payload']]);
+ });
+
+ it('emits correct event on discussion resolve error', () => {
+ findFirstDiscussion().vm.$emit('resolveDiscussionError', 'payload');
+ expect(wrapper.emitted('resolveDiscussionError')).toEqual([['payload']]);
+ });
+
+ it('changes prop correctly on opening discussion form', () => {
+ findFirstDiscussion().vm.$emit('openForm', 'some-id');
+
+ return wrapper.vm.$nextTick().then(() => {
+ expect(findFirstDiscussion().props('discussionWithOpenForm')).toBe('some-id');
+ });
+ });
+ });
+
+ describe('when all discussions are resolved', () => {
+ beforeEach(() => {
+ createComponent({
+ design: {
+ ...design,
+ discussions: {
+ nodes: [
+ {
+ id: 'discussion-id',
+ replyId: 'discussion-reply-id',
+ resolved: true,
+ notes: {
+ nodes: [
+ {
+ id: 'note-id',
+ body: '123',
+ author: {
+ name: 'Administrator',
+ username: 'root',
+ webUrl: 'link-to-author',
+ avatarUrl: 'link-to-avatar',
+ },
+ },
+ ],
+ },
+ },
+ ],
+ },
+ },
+ });
+ });
+
+ it('renders a message about possibility to create a new discussion', () => {
+ expect(findNewDiscussionDisclaimer().exists()).toBe(true);
+ });
+
+ it('does not render unresolved discussions', () => {
+ expect(findUnresolvedDiscussions()).toHaveLength(0);
+ });
+ });
+
+ describe('when showing resolved discussions for the first time', () => {
+ beforeEach(() => {
+ Cookies.set(cookieKey, false);
+ createComponent();
+ });
+
+ it('renders a popover if we show resolved comments collapsible for the first time', () => {
+ expect(findPopover().exists()).toBe(true);
+ });
+
+ it('dismisses a popover on the outside click', () => {
+ wrapper.trigger('click');
+ return wrapper.vm.$nextTick(() => {
+ expect(findPopover().exists()).toBe(false);
+ });
+ });
+
+ it(`sets a ${cookieKey} cookie on clicking outside the popover`, () => {
+ jest.spyOn(Cookies, 'set');
+ wrapper.trigger('click');
+ expect(Cookies.set).toHaveBeenCalledWith(cookieKey, 'true', { expires: 365 * 10 });
+ });
+ });
+});
diff --git a/spec/frontend/design_management_new/components/image_spec.js b/spec/frontend/design_management_new/components/image_spec.js
new file mode 100644
index 00000000000..c1a8a8767df
--- /dev/null
+++ b/spec/frontend/design_management_new/components/image_spec.js
@@ -0,0 +1,133 @@
+import { shallowMount } from '@vue/test-utils';
+import { GlIcon } from '@gitlab/ui';
+import DesignImage from '~/design_management_new/components/image.vue';
+
+describe('Design management large image component', () => {
+ let wrapper;
+
+ function createComponent(propsData, data = {}) {
+ wrapper = shallowMount(DesignImage, {
+ propsData,
+ });
+ wrapper.setData(data);
+ }
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ it('renders loading state', () => {
+ createComponent({
+ isLoading: true,
+ });
+
+ expect(wrapper.element).toMatchSnapshot();
+ });
+
+ it('renders image', () => {
+ createComponent({
+ isLoading: false,
+ image: 'test.jpg',
+ name: 'test',
+ });
+
+ expect(wrapper.element).toMatchSnapshot();
+ });
+
+ it('sets correct classes and styles if imageStyle is set', () => {
+ createComponent(
+ {
+ isLoading: false,
+ image: 'test.jpg',
+ name: 'test',
+ },
+ {
+ imageStyle: {
+ width: '100px',
+ height: '100px',
+ },
+ },
+ );
+ return wrapper.vm.$nextTick().then(() => {
+ expect(wrapper.element).toMatchSnapshot();
+ });
+ });
+
+ it('renders media broken icon on error', () => {
+ createComponent({
+ isLoading: false,
+ image: 'test.jpg',
+ name: 'test',
+ });
+
+ const image = wrapper.find('img');
+ image.trigger('error');
+ return wrapper.vm.$nextTick().then(() => {
+ expect(image.isVisible()).toBe(false);
+ expect(wrapper.find(GlIcon).element).toMatchSnapshot();
+ });
+ });
+
+ describe('zoom', () => {
+ const baseImageWidth = 100;
+ const baseImageHeight = 100;
+
+ beforeEach(() => {
+ createComponent(
+ {
+ isLoading: false,
+ image: 'test.jpg',
+ name: 'test',
+ },
+ {
+ imageStyle: {
+ width: `${baseImageWidth}px`,
+ height: `${baseImageHeight}px`,
+ },
+ baseImageSize: {
+ width: baseImageWidth,
+ height: baseImageHeight,
+ },
+ },
+ );
+
+ jest.spyOn(wrapper.vm.$refs.contentImg, 'offsetWidth', 'get').mockReturnValue(baseImageWidth);
+ jest
+ .spyOn(wrapper.vm.$refs.contentImg, 'offsetHeight', 'get')
+ .mockReturnValue(baseImageHeight);
+ });
+
+ it('emits @resize event on zoom', () => {
+ const zoomAmount = 2;
+ wrapper.vm.zoom(zoomAmount);
+
+ return wrapper.vm.$nextTick().then(() => {
+ expect(wrapper.emitted('resize')).toEqual([
+ [{ width: baseImageWidth * zoomAmount, height: baseImageHeight * zoomAmount }],
+ ]);
+ });
+ });
+
+ it('emits @resize event with base image size when scale=1', () => {
+ wrapper.vm.zoom(1);
+
+ return wrapper.vm.$nextTick().then(() => {
+ expect(wrapper.emitted('resize')).toEqual([
+ [{ width: baseImageWidth, height: baseImageHeight }],
+ ]);
+ });
+ });
+
+ it('sets image style when zoomed', () => {
+ const zoomAmount = 2;
+ wrapper.vm.zoom(zoomAmount);
+ expect(wrapper.vm.imageStyle).toEqual({
+ width: `${baseImageWidth * zoomAmount}px`,
+ height: `${baseImageHeight * zoomAmount}px`,
+ });
+ return wrapper.vm.$nextTick().then(() => {
+ expect(wrapper.element).toMatchSnapshot();
+ });
+ });
+ });
+});
diff --git a/spec/frontend/design_management_new/components/list/__snapshots__/item_spec.js.snap b/spec/frontend/design_management_new/components/list/__snapshots__/item_spec.js.snap
new file mode 100644
index 00000000000..8c6e20cb54c
--- /dev/null
+++ b/spec/frontend/design_management_new/components/list/__snapshots__/item_spec.js.snap
@@ -0,0 +1,472 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Design management list item component when item appears in view after image is loaded renders media broken icon when image onerror triggered 1`] = `
+<gl-icon-stub
+ class="text-secondary"
+ name="media-broken"
+ size="32"
+/>
+`;
+
+exports[`Design management list item component with no notes renders item with correct status icon for creation event 1`] = `
+<router-link-stub
+ class="card cursor-pointer text-plain js-design-list-item design-list-item design-list-item-new"
+ to="[object Object]"
+>
+ <div
+ class="card-body p-0 d-flex-center overflow-hidden position-relative"
+ >
+ <div
+ class="design-event position-absolute"
+ >
+ <span
+ aria-label="Added in this version"
+ title="Added in this version"
+ >
+ <icon-stub
+ class="text-success-500"
+ name="file-addition-solid"
+ size="18"
+ />
+ </span>
+ </div>
+
+ <gl-intersection-observer-stub
+ options="[object Object]"
+ >
+ <!---->
+
+ <img
+ alt="test"
+ class="block mx-auto mw-100 mh-100 design-img"
+ data-qa-selector="design_image"
+ src=""
+ />
+ </gl-intersection-observer-stub>
+ </div>
+
+ <div
+ class="card-footer d-flex w-100"
+ >
+ <div
+ class="d-flex flex-column str-truncated-100"
+ >
+ <span
+ class="bold str-truncated-100"
+ data-qa-selector="design_file_name"
+ >
+ test
+ </span>
+
+ <span
+ class="str-truncated-100"
+ >
+
+ Updated
+ <timeago-stub
+ cssclass=""
+ time="01-01-2019"
+ tooltipplacement="bottom"
+ />
+ </span>
+ </div>
+
+ <!---->
+ </div>
+</router-link-stub>
+`;
+
+exports[`Design management list item component with no notes renders item with correct status icon for deletion event 1`] = `
+<router-link-stub
+ class="card cursor-pointer text-plain js-design-list-item design-list-item design-list-item-new"
+ to="[object Object]"
+>
+ <div
+ class="card-body p-0 d-flex-center overflow-hidden position-relative"
+ >
+ <div
+ class="design-event position-absolute"
+ >
+ <span
+ aria-label="Deleted in this version"
+ title="Deleted in this version"
+ >
+ <icon-stub
+ class="text-danger-500"
+ name="file-deletion-solid"
+ size="18"
+ />
+ </span>
+ </div>
+
+ <gl-intersection-observer-stub
+ options="[object Object]"
+ >
+ <!---->
+
+ <img
+ alt="test"
+ class="block mx-auto mw-100 mh-100 design-img"
+ data-qa-selector="design_image"
+ src=""
+ />
+ </gl-intersection-observer-stub>
+ </div>
+
+ <div
+ class="card-footer d-flex w-100"
+ >
+ <div
+ class="d-flex flex-column str-truncated-100"
+ >
+ <span
+ class="bold str-truncated-100"
+ data-qa-selector="design_file_name"
+ >
+ test
+ </span>
+
+ <span
+ class="str-truncated-100"
+ >
+
+ Updated
+ <timeago-stub
+ cssclass=""
+ time="01-01-2019"
+ tooltipplacement="bottom"
+ />
+ </span>
+ </div>
+
+ <!---->
+ </div>
+</router-link-stub>
+`;
+
+exports[`Design management list item component with no notes renders item with correct status icon for modification event 1`] = `
+<router-link-stub
+ class="card cursor-pointer text-plain js-design-list-item design-list-item design-list-item-new"
+ to="[object Object]"
+>
+ <div
+ class="card-body p-0 d-flex-center overflow-hidden position-relative"
+ >
+ <div
+ class="design-event position-absolute"
+ >
+ <span
+ aria-label="Modified in this version"
+ title="Modified in this version"
+ >
+ <icon-stub
+ class="text-primary-500"
+ name="file-modified-solid"
+ size="18"
+ />
+ </span>
+ </div>
+
+ <gl-intersection-observer-stub
+ options="[object Object]"
+ >
+ <!---->
+
+ <img
+ alt="test"
+ class="block mx-auto mw-100 mh-100 design-img"
+ data-qa-selector="design_image"
+ src=""
+ />
+ </gl-intersection-observer-stub>
+ </div>
+
+ <div
+ class="card-footer d-flex w-100"
+ >
+ <div
+ class="d-flex flex-column str-truncated-100"
+ >
+ <span
+ class="bold str-truncated-100"
+ data-qa-selector="design_file_name"
+ >
+ test
+ </span>
+
+ <span
+ class="str-truncated-100"
+ >
+
+ Updated
+ <timeago-stub
+ cssclass=""
+ time="01-01-2019"
+ tooltipplacement="bottom"
+ />
+ </span>
+ </div>
+
+ <!---->
+ </div>
+</router-link-stub>
+`;
+
+exports[`Design management list item component with no notes renders item with no status icon for none event 1`] = `
+<router-link-stub
+ class="card cursor-pointer text-plain js-design-list-item design-list-item design-list-item-new"
+ to="[object Object]"
+>
+ <div
+ class="card-body p-0 d-flex-center overflow-hidden position-relative"
+ >
+ <!---->
+
+ <gl-intersection-observer-stub
+ options="[object Object]"
+ >
+ <!---->
+
+ <img
+ alt="test"
+ class="block mx-auto mw-100 mh-100 design-img"
+ data-qa-selector="design_image"
+ src=""
+ />
+ </gl-intersection-observer-stub>
+ </div>
+
+ <div
+ class="card-footer d-flex w-100"
+ >
+ <div
+ class="d-flex flex-column str-truncated-100"
+ >
+ <span
+ class="bold str-truncated-100"
+ data-qa-selector="design_file_name"
+ >
+ test
+ </span>
+
+ <span
+ class="str-truncated-100"
+ >
+
+ Updated
+ <timeago-stub
+ cssclass=""
+ time="01-01-2019"
+ tooltipplacement="bottom"
+ />
+ </span>
+ </div>
+
+ <!---->
+ </div>
+</router-link-stub>
+`;
+
+exports[`Design management list item component with no notes renders loading spinner when isUploading is true 1`] = `
+<router-link-stub
+ class="card cursor-pointer text-plain js-design-list-item design-list-item design-list-item-new"
+ to="[object Object]"
+>
+ <div
+ class="card-body p-0 d-flex-center overflow-hidden position-relative"
+ >
+ <!---->
+
+ <gl-intersection-observer-stub
+ options="[object Object]"
+ >
+ <gl-loading-icon-stub
+ color="orange"
+ label="Loading"
+ size="md"
+ />
+
+ <img
+ alt="test"
+ class="block mx-auto mw-100 mh-100 design-img"
+ data-qa-selector="design_image"
+ src=""
+ style="display: none;"
+ />
+ </gl-intersection-observer-stub>
+ </div>
+
+ <div
+ class="card-footer d-flex w-100"
+ >
+ <div
+ class="d-flex flex-column str-truncated-100"
+ >
+ <span
+ class="bold str-truncated-100"
+ data-qa-selector="design_file_name"
+ >
+ test
+ </span>
+
+ <span
+ class="str-truncated-100"
+ >
+
+ Updated
+ <timeago-stub
+ cssclass=""
+ time="01-01-2019"
+ tooltipplacement="bottom"
+ />
+ </span>
+ </div>
+
+ <!---->
+ </div>
+</router-link-stub>
+`;
+
+exports[`Design management list item component with notes renders item with multiple comments 1`] = `
+<router-link-stub
+ class="card cursor-pointer text-plain js-design-list-item design-list-item design-list-item-new"
+ to="[object Object]"
+>
+ <div
+ class="card-body p-0 d-flex-center overflow-hidden position-relative"
+ >
+ <!---->
+
+ <gl-intersection-observer-stub
+ options="[object Object]"
+ >
+ <!---->
+
+ <img
+ alt="test"
+ class="block mx-auto mw-100 mh-100 design-img"
+ data-qa-selector="design_image"
+ src=""
+ />
+ </gl-intersection-observer-stub>
+ </div>
+
+ <div
+ class="card-footer d-flex w-100"
+ >
+ <div
+ class="d-flex flex-column str-truncated-100"
+ >
+ <span
+ class="bold str-truncated-100"
+ data-qa-selector="design_file_name"
+ >
+ test
+ </span>
+
+ <span
+ class="str-truncated-100"
+ >
+
+ Updated
+ <timeago-stub
+ cssclass=""
+ time="01-01-2019"
+ tooltipplacement="bottom"
+ />
+ </span>
+ </div>
+
+ <div
+ class="ml-auto d-flex align-items-center text-secondary"
+ >
+ <icon-stub
+ class="ml-1"
+ name="comments"
+ size="16"
+ />
+
+ <span
+ aria-label="2 comments"
+ class="ml-1"
+ >
+
+ 2
+
+ </span>
+ </div>
+ </div>
+</router-link-stub>
+`;
+
+exports[`Design management list item component with notes renders item with single comment 1`] = `
+<router-link-stub
+ class="card cursor-pointer text-plain js-design-list-item design-list-item design-list-item-new"
+ to="[object Object]"
+>
+ <div
+ class="card-body p-0 d-flex-center overflow-hidden position-relative"
+ >
+ <!---->
+
+ <gl-intersection-observer-stub
+ options="[object Object]"
+ >
+ <!---->
+
+ <img
+ alt="test"
+ class="block mx-auto mw-100 mh-100 design-img"
+ data-qa-selector="design_image"
+ src=""
+ />
+ </gl-intersection-observer-stub>
+ </div>
+
+ <div
+ class="card-footer d-flex w-100"
+ >
+ <div
+ class="d-flex flex-column str-truncated-100"
+ >
+ <span
+ class="bold str-truncated-100"
+ data-qa-selector="design_file_name"
+ >
+ test
+ </span>
+
+ <span
+ class="str-truncated-100"
+ >
+
+ Updated
+ <timeago-stub
+ cssclass=""
+ time="01-01-2019"
+ tooltipplacement="bottom"
+ />
+ </span>
+ </div>
+
+ <div
+ class="ml-auto d-flex align-items-center text-secondary"
+ >
+ <icon-stub
+ class="ml-1"
+ name="comments"
+ size="16"
+ />
+
+ <span
+ aria-label="1 comment"
+ class="ml-1"
+ >
+
+ 1
+
+ </span>
+ </div>
+ </div>
+</router-link-stub>
+`;
diff --git a/spec/frontend/design_management_new/components/list/item_spec.js b/spec/frontend/design_management_new/components/list/item_spec.js
new file mode 100644
index 00000000000..5e3e6832acb
--- /dev/null
+++ b/spec/frontend/design_management_new/components/list/item_spec.js
@@ -0,0 +1,168 @@
+import { createLocalVue, shallowMount } from '@vue/test-utils';
+import { GlIcon, GlLoadingIcon, GlIntersectionObserver } from '@gitlab/ui';
+import VueRouter from 'vue-router';
+import Item from '~/design_management_new/components/list/item.vue';
+
+const localVue = createLocalVue();
+localVue.use(VueRouter);
+const router = new VueRouter();
+
+// Referenced from: doc/api/graphql/reference/gitlab_schema.graphql:DesignVersionEvent
+const DESIGN_VERSION_EVENT = {
+ CREATION: 'CREATION',
+ DELETION: 'DELETION',
+ MODIFICATION: 'MODIFICATION',
+ NO_CHANGE: 'NONE',
+};
+
+describe('Design management list item component', () => {
+ let wrapper;
+
+ function createComponent({
+ notesCount = 0,
+ event = DESIGN_VERSION_EVENT.NO_CHANGE,
+ isUploading = false,
+ imageLoading = false,
+ } = {}) {
+ wrapper = shallowMount(Item, {
+ localVue,
+ router,
+ propsData: {
+ id: 1,
+ filename: 'test',
+ image: 'http://via.placeholder.com/300',
+ isUploading,
+ event,
+ notesCount,
+ updatedAt: '01-01-2019',
+ },
+ data() {
+ return {
+ imageLoading,
+ };
+ },
+ stubs: ['router-link'],
+ });
+ }
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ describe('when item is not in view', () => {
+ it('image is not rendered', () => {
+ createComponent();
+
+ const image = wrapper.find('img');
+ expect(image.attributes('src')).toBe('');
+ });
+ });
+
+ describe('when item appears in view', () => {
+ let image;
+ let glIntersectionObserver;
+
+ beforeEach(() => {
+ createComponent();
+ image = wrapper.find('img');
+ glIntersectionObserver = wrapper.find(GlIntersectionObserver);
+
+ glIntersectionObserver.vm.$emit('appear');
+ return wrapper.vm.$nextTick();
+ });
+
+ describe('before image is loaded', () => {
+ it('renders loading spinner', () => {
+ expect(wrapper.find(GlLoadingIcon)).toExist();
+ });
+ });
+
+ describe('after image is loaded', () => {
+ beforeEach(() => {
+ image.trigger('load');
+ return wrapper.vm.$nextTick();
+ });
+
+ it('renders an image', () => {
+ expect(image.attributes('src')).toBe('http://via.placeholder.com/300');
+ expect(image.isVisible()).toBe(true);
+ });
+
+ it('renders media broken icon when image onerror triggered', () => {
+ image.trigger('error');
+ return wrapper.vm.$nextTick().then(() => {
+ expect(image.isVisible()).toBe(false);
+ expect(wrapper.find(GlIcon).element).toMatchSnapshot();
+ });
+ });
+
+ describe('when imageV432x230 and image provided', () => {
+ it('renders imageV432x230 image', () => {
+ const mockSrc = 'mock-imageV432x230-url';
+ wrapper.setProps({ imageV432x230: mockSrc });
+
+ return wrapper.vm.$nextTick().then(() => {
+ expect(image.attributes('src')).toBe(mockSrc);
+ });
+ });
+ });
+
+ describe('when image disappears from view and then reappears', () => {
+ beforeEach(() => {
+ glIntersectionObserver.vm.$emit('appear');
+ return wrapper.vm.$nextTick();
+ });
+
+ it('renders an image', () => {
+ expect(image.isVisible()).toBe(true);
+ });
+ });
+ });
+ });
+
+ describe('with notes', () => {
+ it('renders item with single comment', () => {
+ createComponent({ notesCount: 1 });
+
+ expect(wrapper.element).toMatchSnapshot();
+ });
+
+ it('renders item with multiple comments', () => {
+ createComponent({ notesCount: 2 });
+
+ expect(wrapper.element).toMatchSnapshot();
+ });
+ });
+
+ describe('with no notes', () => {
+ it('renders item with no status icon for none event', () => {
+ createComponent();
+
+ expect(wrapper.element).toMatchSnapshot();
+ });
+
+ it('renders item with correct status icon for modification event', () => {
+ createComponent({ event: DESIGN_VERSION_EVENT.MODIFICATION });
+
+ expect(wrapper.element).toMatchSnapshot();
+ });
+
+ it('renders item with correct status icon for deletion event', () => {
+ createComponent({ event: DESIGN_VERSION_EVENT.DELETION });
+
+ expect(wrapper.element).toMatchSnapshot();
+ });
+
+ it('renders item with correct status icon for creation event', () => {
+ createComponent({ event: DESIGN_VERSION_EVENT.CREATION });
+
+ expect(wrapper.element).toMatchSnapshot();
+ });
+
+ it('renders loading spinner when isUploading is true', () => {
+ createComponent({ isUploading: true });
+
+ expect(wrapper.element).toMatchSnapshot();
+ });
+ });
+});
diff --git a/spec/frontend/design_management_new/components/toolbar/__snapshots__/index_spec.js.snap b/spec/frontend/design_management_new/components/toolbar/__snapshots__/index_spec.js.snap
new file mode 100644
index 00000000000..f251171ecda
--- /dev/null
+++ b/spec/frontend/design_management_new/components/toolbar/__snapshots__/index_spec.js.snap
@@ -0,0 +1,63 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Design management toolbar component renders design and updated data 1`] = `
+<header
+ class="d-flex p-2 bg-white align-items-center js-design-header"
+>
+ <a
+ aria-label="Go back to designs"
+ class="mr-3 text-plain d-flex justify-content-center align-items-center"
+ data-testid="close-design"
+ >
+ <icon-stub
+ name="close"
+ size="18"
+ />
+ </a>
+
+ <div
+ class="overflow-hidden d-flex align-items-center"
+ >
+ <h2
+ class="m-0 str-truncated-100 gl-font-base"
+ >
+ test.jpg
+ </h2>
+
+ <small
+ class="text-secondary"
+ >
+ Updated 1 hour ago by Test Name
+ </small>
+ </div>
+
+ <pagination-stub
+ class="ml-auto flex-shrink-0"
+ id="1"
+ />
+
+ <gl-deprecated-button-stub
+ class="mr-2"
+ href="/-/designs/306/7f747adcd4693afadbe968d7ba7d983349b9012d"
+ size="md"
+ variant="secondary"
+ >
+ <icon-stub
+ name="download"
+ size="18"
+ />
+ </gl-deprecated-button-stub>
+
+ <delete-button-stub
+ buttonclass=""
+ buttonsize="medium"
+ buttonvariant="danger"
+ hasselecteddesigns="true"
+ >
+ <icon-stub
+ name="remove"
+ size="18"
+ />
+ </delete-button-stub>
+</header>
+`;
diff --git a/spec/frontend/design_management_new/components/toolbar/__snapshots__/pagination_button_spec.js.snap b/spec/frontend/design_management_new/components/toolbar/__snapshots__/pagination_button_spec.js.snap
new file mode 100644
index 00000000000..08662a04f15
--- /dev/null
+++ b/spec/frontend/design_management_new/components/toolbar/__snapshots__/pagination_button_spec.js.snap
@@ -0,0 +1,28 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Design management pagination button component disables button when no design is passed 1`] = `
+<router-link-stub
+ aria-label="Test title"
+ class="btn btn-default disabled"
+ disabled="true"
+ to="[object Object]"
+>
+ <icon-stub
+ name="angle-right"
+ size="16"
+ />
+</router-link-stub>
+`;
+
+exports[`Design management pagination button component renders router-link 1`] = `
+<router-link-stub
+ aria-label="Test title"
+ class="btn btn-default"
+ to="[object Object]"
+>
+ <icon-stub
+ name="angle-right"
+ size="16"
+ />
+</router-link-stub>
+`;
diff --git a/spec/frontend/design_management_new/components/toolbar/__snapshots__/pagination_spec.js.snap b/spec/frontend/design_management_new/components/toolbar/__snapshots__/pagination_spec.js.snap
new file mode 100644
index 00000000000..0197b4bff79
--- /dev/null
+++ b/spec/frontend/design_management_new/components/toolbar/__snapshots__/pagination_spec.js.snap
@@ -0,0 +1,29 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Design management pagination component hides components when designs are empty 1`] = `<!---->`;
+
+exports[`Design management pagination component renders pagination buttons 1`] = `
+<div
+ class="d-flex align-items-center"
+>
+
+ 0 of 2
+
+ <div
+ class="btn-group ml-3 mr-3"
+ >
+ <pagination-button-stub
+ class="js-previous-design"
+ iconname="angle-left"
+ title="Go to previous design"
+ />
+
+ <pagination-button-stub
+ class="js-next-design"
+ design="[object Object]"
+ iconname="angle-right"
+ title="Go to next design"
+ />
+ </div>
+</div>
+`;
diff --git a/spec/frontend/design_management_new/components/toolbar/index_spec.js b/spec/frontend/design_management_new/components/toolbar/index_spec.js
new file mode 100644
index 00000000000..eb5ae15ed58
--- /dev/null
+++ b/spec/frontend/design_management_new/components/toolbar/index_spec.js
@@ -0,0 +1,123 @@
+import { createLocalVue, shallowMount } from '@vue/test-utils';
+import VueRouter from 'vue-router';
+import Toolbar from '~/design_management_new/components/toolbar/index.vue';
+import DeleteButton from '~/design_management_new/components/delete_button.vue';
+import { DESIGNS_ROUTE_NAME } from '~/design_management_new/router/constants';
+import { GlDeprecatedButton } from '@gitlab/ui';
+
+const localVue = createLocalVue();
+localVue.use(VueRouter);
+const router = new VueRouter();
+
+const RouterLinkStub = {
+ props: {
+ to: {
+ type: Object,
+ },
+ },
+ render(createElement) {
+ return createElement('a', {}, this.$slots.default);
+ },
+};
+
+describe('Design management toolbar component', () => {
+ let wrapper;
+
+ function createComponent(isLoading = false, createDesign = true, props) {
+ const updatedAt = new Date();
+ updatedAt.setHours(updatedAt.getHours() - 1);
+
+ wrapper = shallowMount(Toolbar, {
+ localVue,
+ router,
+ propsData: {
+ id: '1',
+ isLatestVersion: true,
+ isLoading,
+ isDeleting: false,
+ filename: 'test.jpg',
+ updatedAt: updatedAt.toString(),
+ updatedBy: {
+ name: 'Test Name',
+ },
+ image: '/-/designs/306/7f747adcd4693afadbe968d7ba7d983349b9012d',
+ ...props,
+ },
+ stubs: {
+ 'router-link': RouterLinkStub,
+ },
+ });
+
+ wrapper.setData({
+ permissions: {
+ createDesign,
+ },
+ });
+ }
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ it('renders design and updated data', () => {
+ createComponent();
+
+ return wrapper.vm.$nextTick().then(() => {
+ expect(wrapper.element).toMatchSnapshot();
+ });
+ });
+
+ it('links back to designs list', () => {
+ createComponent();
+
+ return wrapper.vm.$nextTick().then(() => {
+ const link = wrapper.find('a');
+
+ expect(link.props('to')).toEqual({
+ name: DESIGNS_ROUTE_NAME,
+ query: {
+ version: undefined,
+ },
+ });
+ });
+ });
+
+ it('renders delete button on latest designs version with logged in user', () => {
+ createComponent();
+
+ return wrapper.vm.$nextTick().then(() => {
+ expect(wrapper.find(DeleteButton).exists()).toBe(true);
+ });
+ });
+
+ it('does not render delete button on non-latest version', () => {
+ createComponent(false, true, { isLatestVersion: false });
+
+ return wrapper.vm.$nextTick().then(() => {
+ expect(wrapper.find(DeleteButton).exists()).toBe(false);
+ });
+ });
+
+ it('does not render delete button when user is not logged in', () => {
+ createComponent(false, false);
+
+ return wrapper.vm.$nextTick().then(() => {
+ expect(wrapper.find(DeleteButton).exists()).toBe(false);
+ });
+ });
+
+ it('emits `delete` event on deleteButton `deleteSelectedDesigns` event', () => {
+ createComponent();
+
+ return wrapper.vm.$nextTick().then(() => {
+ wrapper.find(DeleteButton).vm.$emit('deleteSelectedDesigns');
+ expect(wrapper.emitted().delete).toBeTruthy();
+ });
+ });
+
+ it('renders download button with correct link', () => {
+ expect(wrapper.find(GlDeprecatedButton).attributes('href')).toBe(
+ '/-/designs/306/7f747adcd4693afadbe968d7ba7d983349b9012d',
+ );
+ });
+});
diff --git a/spec/frontend/design_management_new/components/toolbar/pagination_button_spec.js b/spec/frontend/design_management_new/components/toolbar/pagination_button_spec.js
new file mode 100644
index 00000000000..5f33d65fc1f
--- /dev/null
+++ b/spec/frontend/design_management_new/components/toolbar/pagination_button_spec.js
@@ -0,0 +1,61 @@
+import { createLocalVue, shallowMount } from '@vue/test-utils';
+import VueRouter from 'vue-router';
+import PaginationButton from '~/design_management_new/components/toolbar/pagination_button.vue';
+import { DESIGN_ROUTE_NAME } from '~/design_management_new/router/constants';
+
+const localVue = createLocalVue();
+localVue.use(VueRouter);
+const router = new VueRouter();
+
+describe('Design management pagination button component', () => {
+ let wrapper;
+
+ function createComponent(design = null) {
+ wrapper = shallowMount(PaginationButton, {
+ localVue,
+ router,
+ propsData: {
+ design,
+ title: 'Test title',
+ iconName: 'angle-right',
+ },
+ stubs: ['router-link'],
+ });
+ }
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ it('disables button when no design is passed', () => {
+ createComponent();
+
+ expect(wrapper.element).toMatchSnapshot();
+ });
+
+ it('renders router-link', () => {
+ createComponent({ id: '2' });
+
+ expect(wrapper.element).toMatchSnapshot();
+ });
+
+ describe('designLink', () => {
+ it('returns empty link when design is null', () => {
+ createComponent();
+
+ expect(wrapper.vm.designLink).toEqual({});
+ });
+
+ it('returns design link', () => {
+ createComponent({ id: '2', filename: 'test' });
+
+ wrapper.vm.$router.replace('/root/test-project/issues/1/designs/test?version=1');
+
+ expect(wrapper.vm.designLink).toEqual({
+ name: DESIGN_ROUTE_NAME,
+ params: { id: 'test' },
+ query: { version: '1' },
+ });
+ });
+ });
+});
diff --git a/spec/frontend/design_management_new/components/toolbar/pagination_spec.js b/spec/frontend/design_management_new/components/toolbar/pagination_spec.js
new file mode 100644
index 00000000000..45dce15e292
--- /dev/null
+++ b/spec/frontend/design_management_new/components/toolbar/pagination_spec.js
@@ -0,0 +1,79 @@
+/* global Mousetrap */
+import 'mousetrap';
+import { shallowMount } from '@vue/test-utils';
+import Pagination from '~/design_management_new/components/toolbar/pagination.vue';
+import { DESIGN_ROUTE_NAME } from '~/design_management_new/router/constants';
+
+const push = jest.fn();
+const $router = {
+ push,
+};
+
+const $route = {
+ path: '/designs/design-2',
+ query: {},
+};
+
+describe('Design management pagination component', () => {
+ let wrapper;
+
+ function createComponent() {
+ wrapper = shallowMount(Pagination, {
+ propsData: {
+ id: '2',
+ },
+ mocks: {
+ $router,
+ $route,
+ },
+ });
+ }
+
+ beforeEach(() => {
+ createComponent();
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ it('hides components when designs are empty', () => {
+ expect(wrapper.element).toMatchSnapshot();
+ });
+
+ it('renders pagination buttons', () => {
+ wrapper.setData({
+ designs: [{ id: '1' }, { id: '2' }],
+ });
+
+ return wrapper.vm.$nextTick().then(() => {
+ expect(wrapper.element).toMatchSnapshot();
+ });
+ });
+
+ describe('keyboard buttons navigation', () => {
+ beforeEach(() => {
+ wrapper.setData({
+ designs: [{ filename: '1' }, { filename: '2' }, { filename: '3' }],
+ });
+ });
+
+ it('routes to previous design on Left button', () => {
+ Mousetrap.trigger('left');
+ expect(push).toHaveBeenCalledWith({
+ name: DESIGN_ROUTE_NAME,
+ params: { id: '1' },
+ query: {},
+ });
+ });
+
+ it('routes to next design on Right button', () => {
+ Mousetrap.trigger('right');
+ expect(push).toHaveBeenCalledWith({
+ name: DESIGN_ROUTE_NAME,
+ params: { id: '3' },
+ query: {},
+ });
+ });
+ });
+});
diff --git a/spec/frontend/design_management_new/components/upload/__snapshots__/button_spec.js.snap b/spec/frontend/design_management_new/components/upload/__snapshots__/button_spec.js.snap
new file mode 100644
index 00000000000..b498becc606
--- /dev/null
+++ b/spec/frontend/design_management_new/components/upload/__snapshots__/button_spec.js.snap
@@ -0,0 +1,85 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Design management upload button component renders inverted upload design button 1`] = `
+<div
+ isinverted="true"
+>
+ <gl-button-stub
+ category="tertiary"
+ icon=""
+ size="small"
+ title="Adding a design with the same filename replaces the file in a new version."
+ variant="success"
+ >
+
+ Upload designs
+
+ <!---->
+ </gl-button-stub>
+
+ <input
+ accept="image/*"
+ class="hide"
+ multiple="multiple"
+ name="design_file"
+ type="file"
+ />
+</div>
+`;
+
+exports[`Design management upload button component renders loading icon 1`] = `
+<div>
+ <gl-button-stub
+ category="tertiary"
+ disabled="true"
+ icon=""
+ size="small"
+ title="Adding a design with the same filename replaces the file in a new version."
+ variant="success"
+ >
+
+ Upload designs
+
+ <gl-loading-icon-stub
+ class="ml-1"
+ color="orange"
+ inline="true"
+ label="Loading"
+ size="sm"
+ />
+ </gl-button-stub>
+
+ <input
+ accept="image/*"
+ class="hide"
+ multiple="multiple"
+ name="design_file"
+ type="file"
+ />
+</div>
+`;
+
+exports[`Design management upload button component renders upload design button 1`] = `
+<div>
+ <gl-button-stub
+ category="tertiary"
+ icon=""
+ size="small"
+ title="Adding a design with the same filename replaces the file in a new version."
+ variant="success"
+ >
+
+ Upload designs
+
+ <!---->
+ </gl-button-stub>
+
+ <input
+ accept="image/*"
+ class="hide"
+ multiple="multiple"
+ name="design_file"
+ type="file"
+ />
+</div>
+`;
diff --git a/spec/frontend/design_management_new/components/upload/__snapshots__/design_dropzone_spec.js.snap b/spec/frontend/design_management_new/components/upload/__snapshots__/design_dropzone_spec.js.snap
new file mode 100644
index 00000000000..c53c6c889b0
--- /dev/null
+++ b/spec/frontend/design_management_new/components/upload/__snapshots__/design_dropzone_spec.js.snap
@@ -0,0 +1,501 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Design management dropzone component when dragging renders correct template when drag event contains files 1`] = `
+<div
+ class="w-100 position-relative"
+>
+ <button
+ class="card design-dropzone-card design-dropzone-border w-100 h-100 gl-align-items-center gl-justify-content-center gl-p-3"
+ >
+ <div
+ class="gl-display-flex gl-align-items-center gl-justify-content-center gl-text-center gl-flex-direction-column"
+ data-testid="dropzone-area"
+ >
+ <gl-icon-stub
+ class="gl-mb-2"
+ name="upload"
+ size="24"
+ />
+
+ <p
+ class="gl-font-weight-bold gl-mb-0"
+ >
+ <gl-sprintf-stub
+ message="Drop or %{linkStart}upload%{linkEnd} Designs to attach"
+ />
+ </p>
+ </div>
+ </button>
+
+ <input
+ accept="image/*"
+ class="hide"
+ multiple="multiple"
+ name="design_file"
+ type="file"
+ />
+
+ <transition-stub
+ name="design-dropzone-fade"
+ >
+ <div
+ class="card design-dropzone-border design-dropzone-overlay w-100 h-100 position-absolute d-flex-center p-3 bg-white"
+ style=""
+ >
+ <div
+ class="mw-50 text-center"
+ style="display: none;"
+ >
+ <h3
+ class=""
+ >
+ Oh no!
+ </h3>
+
+ <span>
+ You are trying to upload something other than an image. Please upload a .png, .jpg, .jpeg, .gif, .bmp, .tiff or .ico.
+ </span>
+ </div>
+
+ <div
+ class="mw-50 text-center"
+ style=""
+ >
+ <h3
+ class=""
+ >
+ Incoming!
+ </h3>
+
+ <span>
+ Drop your designs to start your upload.
+ </span>
+ </div>
+ </div>
+ </transition-stub>
+</div>
+`;
+
+exports[`Design management dropzone component when dragging renders correct template when drag event contains files and text 1`] = `
+<div
+ class="w-100 position-relative"
+>
+ <button
+ class="card design-dropzone-card design-dropzone-border w-100 h-100 gl-align-items-center gl-justify-content-center gl-p-3"
+ >
+ <div
+ class="gl-display-flex gl-align-items-center gl-justify-content-center gl-text-center gl-flex-direction-column"
+ data-testid="dropzone-area"
+ >
+ <gl-icon-stub
+ class="gl-mb-2"
+ name="upload"
+ size="24"
+ />
+
+ <p
+ class="gl-font-weight-bold gl-mb-0"
+ >
+ <gl-sprintf-stub
+ message="Drop or %{linkStart}upload%{linkEnd} Designs to attach"
+ />
+ </p>
+ </div>
+ </button>
+
+ <input
+ accept="image/*"
+ class="hide"
+ multiple="multiple"
+ name="design_file"
+ type="file"
+ />
+
+ <transition-stub
+ name="design-dropzone-fade"
+ >
+ <div
+ class="card design-dropzone-border design-dropzone-overlay w-100 h-100 position-absolute d-flex-center p-3 bg-white"
+ style=""
+ >
+ <div
+ class="mw-50 text-center"
+ style="display: none;"
+ >
+ <h3
+ class=""
+ >
+ Oh no!
+ </h3>
+
+ <span>
+ You are trying to upload something other than an image. Please upload a .png, .jpg, .jpeg, .gif, .bmp, .tiff or .ico.
+ </span>
+ </div>
+
+ <div
+ class="mw-50 text-center"
+ style=""
+ >
+ <h3
+ class=""
+ >
+ Incoming!
+ </h3>
+
+ <span>
+ Drop your designs to start your upload.
+ </span>
+ </div>
+ </div>
+ </transition-stub>
+</div>
+`;
+
+exports[`Design management dropzone component when dragging renders correct template when drag event contains text 1`] = `
+<div
+ class="w-100 position-relative"
+>
+ <button
+ class="card design-dropzone-card design-dropzone-border w-100 h-100 gl-align-items-center gl-justify-content-center gl-p-3"
+ >
+ <div
+ class="gl-display-flex gl-align-items-center gl-justify-content-center gl-text-center gl-flex-direction-column"
+ data-testid="dropzone-area"
+ >
+ <gl-icon-stub
+ class="gl-mb-2"
+ name="upload"
+ size="24"
+ />
+
+ <p
+ class="gl-font-weight-bold gl-mb-0"
+ >
+ <gl-sprintf-stub
+ message="Drop or %{linkStart}upload%{linkEnd} Designs to attach"
+ />
+ </p>
+ </div>
+ </button>
+
+ <input
+ accept="image/*"
+ class="hide"
+ multiple="multiple"
+ name="design_file"
+ type="file"
+ />
+
+ <transition-stub
+ name="design-dropzone-fade"
+ >
+ <div
+ class="card design-dropzone-border design-dropzone-overlay w-100 h-100 position-absolute d-flex-center p-3 bg-white"
+ style=""
+ >
+ <div
+ class="mw-50 text-center"
+ >
+ <h3
+ class=""
+ >
+ Oh no!
+ </h3>
+
+ <span>
+ You are trying to upload something other than an image. Please upload a .png, .jpg, .jpeg, .gif, .bmp, .tiff or .ico.
+ </span>
+ </div>
+
+ <div
+ class="mw-50 text-center"
+ style="display: none;"
+ >
+ <h3
+ class=""
+ >
+ Incoming!
+ </h3>
+
+ <span>
+ Drop your designs to start your upload.
+ </span>
+ </div>
+ </div>
+ </transition-stub>
+</div>
+`;
+
+exports[`Design management dropzone component when dragging renders correct template when drag event is empty 1`] = `
+<div
+ class="w-100 position-relative"
+>
+ <button
+ class="card design-dropzone-card design-dropzone-border w-100 h-100 gl-align-items-center gl-justify-content-center gl-p-3"
+ >
+ <div
+ class="gl-display-flex gl-align-items-center gl-justify-content-center gl-text-center gl-flex-direction-column"
+ data-testid="dropzone-area"
+ >
+ <gl-icon-stub
+ class="gl-mb-2"
+ name="upload"
+ size="24"
+ />
+
+ <p
+ class="gl-font-weight-bold gl-mb-0"
+ >
+ <gl-sprintf-stub
+ message="Drop or %{linkStart}upload%{linkEnd} Designs to attach"
+ />
+ </p>
+ </div>
+ </button>
+
+ <input
+ accept="image/*"
+ class="hide"
+ multiple="multiple"
+ name="design_file"
+ type="file"
+ />
+
+ <transition-stub
+ name="design-dropzone-fade"
+ >
+ <div
+ class="card design-dropzone-border design-dropzone-overlay w-100 h-100 position-absolute d-flex-center p-3 bg-white"
+ style=""
+ >
+ <div
+ class="mw-50 text-center"
+ >
+ <h3
+ class=""
+ >
+ Oh no!
+ </h3>
+
+ <span>
+ You are trying to upload something other than an image. Please upload a .png, .jpg, .jpeg, .gif, .bmp, .tiff or .ico.
+ </span>
+ </div>
+
+ <div
+ class="mw-50 text-center"
+ style="display: none;"
+ >
+ <h3
+ class=""
+ >
+ Incoming!
+ </h3>
+
+ <span>
+ Drop your designs to start your upload.
+ </span>
+ </div>
+ </div>
+ </transition-stub>
+</div>
+`;
+
+exports[`Design management dropzone component when dragging renders correct template when dragging stops 1`] = `
+<div
+ class="w-100 position-relative"
+>
+ <button
+ class="card design-dropzone-card design-dropzone-border w-100 h-100 gl-align-items-center gl-justify-content-center gl-p-3"
+ >
+ <div
+ class="gl-display-flex gl-align-items-center gl-justify-content-center gl-text-center gl-flex-direction-column"
+ data-testid="dropzone-area"
+ >
+ <gl-icon-stub
+ class="gl-mb-2"
+ name="upload"
+ size="24"
+ />
+
+ <p
+ class="gl-font-weight-bold gl-mb-0"
+ >
+ <gl-sprintf-stub
+ message="Drop or %{linkStart}upload%{linkEnd} Designs to attach"
+ />
+ </p>
+ </div>
+ </button>
+
+ <input
+ accept="image/*"
+ class="hide"
+ multiple="multiple"
+ name="design_file"
+ type="file"
+ />
+
+ <transition-stub
+ name="design-dropzone-fade"
+ >
+ <div
+ class="card design-dropzone-border design-dropzone-overlay w-100 h-100 position-absolute d-flex-center p-3 bg-white"
+ style="display: none;"
+ >
+ <div
+ class="mw-50 text-center"
+ >
+ <h3
+ class=""
+ >
+ Oh no!
+ </h3>
+
+ <span>
+ You are trying to upload something other than an image. Please upload a .png, .jpg, .jpeg, .gif, .bmp, .tiff or .ico.
+ </span>
+ </div>
+
+ <div
+ class="mw-50 text-center"
+ style="display: none;"
+ >
+ <h3
+ class=""
+ >
+ Incoming!
+ </h3>
+
+ <span>
+ Drop your designs to start your upload.
+ </span>
+ </div>
+ </div>
+ </transition-stub>
+</div>
+`;
+
+exports[`Design management dropzone component when no slot provided renders default dropzone card 1`] = `
+<div
+ class="w-100 position-relative"
+>
+ <button
+ class="card design-dropzone-card design-dropzone-border w-100 h-100 gl-align-items-center gl-justify-content-center gl-p-3"
+ >
+ <div
+ class="gl-display-flex gl-align-items-center gl-justify-content-center gl-text-center gl-flex-direction-column"
+ data-testid="dropzone-area"
+ >
+ <gl-icon-stub
+ class="gl-mb-2"
+ name="upload"
+ size="24"
+ />
+
+ <p
+ class="gl-font-weight-bold gl-mb-0"
+ >
+ <gl-sprintf-stub
+ message="Drop or %{linkStart}upload%{linkEnd} Designs to attach"
+ />
+ </p>
+ </div>
+ </button>
+
+ <input
+ accept="image/*"
+ class="hide"
+ multiple="multiple"
+ name="design_file"
+ type="file"
+ />
+
+ <transition-stub
+ name="design-dropzone-fade"
+ >
+ <div
+ class="card design-dropzone-border design-dropzone-overlay w-100 h-100 position-absolute d-flex-center p-3 bg-white"
+ style="display: none;"
+ >
+ <div
+ class="mw-50 text-center"
+ >
+ <h3
+ class=""
+ >
+ Oh no!
+ </h3>
+
+ <span>
+ You are trying to upload something other than an image. Please upload a .png, .jpg, .jpeg, .gif, .bmp, .tiff or .ico.
+ </span>
+ </div>
+
+ <div
+ class="mw-50 text-center"
+ style="display: none;"
+ >
+ <h3
+ class=""
+ >
+ Incoming!
+ </h3>
+
+ <span>
+ Drop your designs to start your upload.
+ </span>
+ </div>
+ </div>
+ </transition-stub>
+</div>
+`;
+
+exports[`Design management dropzone component when slot provided renders dropzone with slot content 1`] = `
+<div
+ class="w-100 position-relative"
+>
+ <div>
+ dropzone slot
+ </div>
+
+ <transition-stub
+ name="design-dropzone-fade"
+ >
+ <div
+ class="card design-dropzone-border design-dropzone-overlay w-100 h-100 position-absolute d-flex-center p-3 bg-white"
+ style="display: none;"
+ >
+ <div
+ class="mw-50 text-center"
+ >
+ <h3
+ class=""
+ >
+ Oh no!
+ </h3>
+
+ <span>
+ You are trying to upload something other than an image. Please upload a .png, .jpg, .jpeg, .gif, .bmp, .tiff or .ico.
+ </span>
+ </div>
+
+ <div
+ class="mw-50 text-center"
+ style="display: none;"
+ >
+ <h3
+ class=""
+ >
+ Incoming!
+ </h3>
+
+ <span>
+ Drop your designs to start your upload.
+ </span>
+ </div>
+ </div>
+ </transition-stub>
+</div>
+`;
diff --git a/spec/frontend/design_management_new/components/upload/__snapshots__/design_version_dropdown_spec.js.snap b/spec/frontend/design_management_new/components/upload/__snapshots__/design_version_dropdown_spec.js.snap
new file mode 100644
index 00000000000..0d16acdef54
--- /dev/null
+++ b/spec/frontend/design_management_new/components/upload/__snapshots__/design_version_dropdown_spec.js.snap
@@ -0,0 +1,141 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Design management design version dropdown component renders design version dropdown button 1`] = `
+<gl-new-dropdown-stub
+ category="tertiary"
+ class="design-version-dropdown"
+ headertext=""
+ issueiid=""
+ projectpath=""
+ size="small"
+ text="Showing Latest Version"
+ variant="default"
+>
+ <gl-new-dropdown-item-stub
+ avatarurl=""
+ iconcolor=""
+ iconname=""
+ iconrightname=""
+ secondarytext=""
+ >
+ <router-link-stub
+ class="d-flex js-version-link"
+ to="[object Object]"
+ >
+ <div
+ class="flex-grow-1 ml-2"
+ >
+ <div>
+ <strong>
+ Version 2
+
+ <span>
+ (latest)
+ </span>
+ </strong>
+ </div>
+ </div>
+
+ <i
+ class="fa fa-check pull-right"
+ />
+ </router-link-stub>
+ </gl-new-dropdown-item-stub>
+ <gl-new-dropdown-item-stub
+ avatarurl=""
+ iconcolor=""
+ iconname=""
+ iconrightname=""
+ secondarytext=""
+ >
+ <router-link-stub
+ class="d-flex js-version-link"
+ to="[object Object]"
+ >
+ <div
+ class="flex-grow-1 ml-2"
+ >
+ <div>
+ <strong>
+ Version 1
+
+ <!---->
+ </strong>
+ </div>
+ </div>
+
+ <!---->
+ </router-link-stub>
+ </gl-new-dropdown-item-stub>
+</gl-new-dropdown-stub>
+`;
+
+exports[`Design management design version dropdown component renders design version list 1`] = `
+<gl-new-dropdown-stub
+ category="tertiary"
+ class="design-version-dropdown"
+ headertext=""
+ issueiid=""
+ projectpath=""
+ size="small"
+ text="Showing Latest Version"
+ variant="default"
+>
+ <gl-new-dropdown-item-stub
+ avatarurl=""
+ iconcolor=""
+ iconname=""
+ iconrightname=""
+ secondarytext=""
+ >
+ <router-link-stub
+ class="d-flex js-version-link"
+ to="[object Object]"
+ >
+ <div
+ class="flex-grow-1 ml-2"
+ >
+ <div>
+ <strong>
+ Version 2
+
+ <span>
+ (latest)
+ </span>
+ </strong>
+ </div>
+ </div>
+
+ <i
+ class="fa fa-check pull-right"
+ />
+ </router-link-stub>
+ </gl-new-dropdown-item-stub>
+ <gl-new-dropdown-item-stub
+ avatarurl=""
+ iconcolor=""
+ iconname=""
+ iconrightname=""
+ secondarytext=""
+ >
+ <router-link-stub
+ class="d-flex js-version-link"
+ to="[object Object]"
+ >
+ <div
+ class="flex-grow-1 ml-2"
+ >
+ <div>
+ <strong>
+ Version 1
+
+ <!---->
+ </strong>
+ </div>
+ </div>
+
+ <!---->
+ </router-link-stub>
+ </gl-new-dropdown-item-stub>
+</gl-new-dropdown-stub>
+`;
diff --git a/spec/frontend/design_management_new/components/upload/button_spec.js b/spec/frontend/design_management_new/components/upload/button_spec.js
new file mode 100644
index 00000000000..7f751982491
--- /dev/null
+++ b/spec/frontend/design_management_new/components/upload/button_spec.js
@@ -0,0 +1,59 @@
+import { shallowMount } from '@vue/test-utils';
+import UploadButton from '~/design_management_new/components/upload/button.vue';
+
+describe('Design management upload button component', () => {
+ let wrapper;
+
+ function createComponent(isSaving = false, isInverted = false) {
+ wrapper = shallowMount(UploadButton, {
+ propsData: {
+ isSaving,
+ isInverted,
+ },
+ });
+ }
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ it('renders upload design button', () => {
+ createComponent();
+
+ expect(wrapper.element).toMatchSnapshot();
+ });
+
+ it('renders inverted upload design button', () => {
+ createComponent(false, true);
+
+ expect(wrapper.element).toMatchSnapshot();
+ });
+
+ it('renders loading icon', () => {
+ createComponent(true);
+
+ expect(wrapper.element).toMatchSnapshot();
+ });
+
+ describe('onFileUploadChange', () => {
+ it('emits upload event', () => {
+ createComponent();
+
+ wrapper.vm.onFileUploadChange({ target: { files: 'test' } });
+
+ expect(wrapper.emitted().upload[0]).toEqual(['test']);
+ });
+ });
+
+ describe('openFileUpload', () => {
+ it('triggers click on input', () => {
+ createComponent();
+
+ const clickSpy = jest.spyOn(wrapper.find('input').element, 'click');
+
+ wrapper.vm.openFileUpload();
+
+ expect(clickSpy).toHaveBeenCalled();
+ });
+ });
+});
diff --git a/spec/frontend/design_management_new/components/upload/design_dropzone_spec.js b/spec/frontend/design_management_new/components/upload/design_dropzone_spec.js
new file mode 100644
index 00000000000..c48cbb10172
--- /dev/null
+++ b/spec/frontend/design_management_new/components/upload/design_dropzone_spec.js
@@ -0,0 +1,151 @@
+import { shallowMount } from '@vue/test-utils';
+import DesignDropzone from '~/design_management_new/components/upload/design_dropzone.vue';
+import createFlash from '~/flash';
+import { GlIcon } from '@gitlab/ui';
+
+jest.mock('~/flash');
+
+describe('Design management dropzone component', () => {
+ let wrapper;
+
+ const mockDragEvent = ({ types = ['Files'], files = [] }) => {
+ return { dataTransfer: { types, files } };
+ };
+
+ const findDropzoneCard = () => wrapper.find('.design-dropzone-card');
+ const findDropzoneArea = () => wrapper.find('[data-testid="dropzone-area"]');
+ const findIcon = () => wrapper.find(GlIcon);
+
+ function createComponent({ slots = {}, data = {}, props = {} } = {}) {
+ wrapper = shallowMount(DesignDropzone, {
+ slots,
+ propsData: {
+ hasDesigns: true,
+ ...props,
+ },
+ data() {
+ return data;
+ },
+ });
+ }
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ describe('when slot provided', () => {
+ it('renders dropzone with slot content', () => {
+ createComponent({
+ slots: {
+ default: ['<div>dropzone slot</div>'],
+ },
+ });
+
+ expect(wrapper.element).toMatchSnapshot();
+ });
+ });
+
+ describe('when no slot provided', () => {
+ it('renders default dropzone card', () => {
+ createComponent();
+
+ expect(wrapper.element).toMatchSnapshot();
+ });
+
+ it('triggers click event on file input element when clicked', () => {
+ createComponent();
+ const clickSpy = jest.spyOn(wrapper.find('input').element, 'click');
+
+ findDropzoneCard().trigger('click');
+ expect(clickSpy).toHaveBeenCalled();
+ });
+ });
+
+ describe('when dragging', () => {
+ it.each`
+ description | eventPayload
+ ${'is empty'} | ${{}}
+ ${'contains text'} | ${mockDragEvent({ types: ['text'] })}
+ ${'contains files and text'} | ${mockDragEvent({ types: ['Files', 'text'] })}
+ ${'contains files'} | ${mockDragEvent({ types: ['Files'] })}
+ `('renders correct template when drag event $description', ({ eventPayload }) => {
+ createComponent();
+
+ wrapper.trigger('dragenter', eventPayload);
+ return wrapper.vm.$nextTick().then(() => {
+ expect(wrapper.element).toMatchSnapshot();
+ });
+ });
+
+ it('renders correct template when dragging stops', () => {
+ createComponent();
+
+ wrapper.trigger('dragenter');
+ return wrapper.vm
+ .$nextTick()
+ .then(() => {
+ wrapper.trigger('dragleave');
+ return wrapper.vm.$nextTick();
+ })
+ .then(() => {
+ expect(wrapper.element).toMatchSnapshot();
+ });
+ });
+ });
+
+ describe('when dropping', () => {
+ it('emits upload event', () => {
+ createComponent();
+ const mockFile = { name: 'test', type: 'image/jpg' };
+ const mockEvent = mockDragEvent({ files: [mockFile] });
+
+ wrapper.trigger('dragenter', mockEvent);
+ return wrapper.vm
+ .$nextTick()
+ .then(() => {
+ wrapper.trigger('drop', mockEvent);
+ return wrapper.vm.$nextTick();
+ })
+ .then(() => {
+ expect(wrapper.emitted().change[0]).toEqual([[mockFile]]);
+ });
+ });
+ });
+
+ describe('ondrop', () => {
+ const mockData = { dragCounter: 1, isDragDataValid: true };
+
+ describe('when drag data is valid', () => {
+ it('emits upload event for valid files', () => {
+ createComponent({ data: mockData });
+
+ const mockFile = { type: 'image/jpg' };
+ const mockEvent = mockDragEvent({ files: [mockFile] });
+
+ wrapper.vm.ondrop(mockEvent);
+ expect(wrapper.emitted().change[0]).toEqual([[mockFile]]);
+ });
+
+ it('calls createFlash when files are invalid', () => {
+ createComponent({ data: mockData });
+
+ const mockEvent = mockDragEvent({ files: [{ type: 'audio/midi' }] });
+
+ wrapper.vm.ondrop(mockEvent);
+ expect(createFlash).toHaveBeenCalledTimes(1);
+ });
+ });
+ });
+
+ it('applies correct classes when there are no designs or no design saving loader', () => {
+ createComponent({ props: { hasDesigns: false } });
+ expect(findDropzoneArea().classes()).not.toContain('gl-flex-direction-column');
+ expect(findIcon().classes()).toEqual(['gl-mr-4']);
+ });
+
+ it('applies correct classes when there are designs or design saving loader', () => {
+ createComponent({ props: { hasDesigns: true } });
+ expect(findDropzoneArea().classes()).toContain('gl-flex-direction-column');
+ expect(findIcon().classes()).toEqual(['gl-mb-2']);
+ });
+});
diff --git a/spec/frontend/design_management_new/components/upload/design_version_dropdown_spec.js b/spec/frontend/design_management_new/components/upload/design_version_dropdown_spec.js
new file mode 100644
index 00000000000..74e7f3f88fc
--- /dev/null
+++ b/spec/frontend/design_management_new/components/upload/design_version_dropdown_spec.js
@@ -0,0 +1,114 @@
+import { shallowMount } from '@vue/test-utils';
+import DesignVersionDropdown from '~/design_management_new/components/upload/design_version_dropdown.vue';
+import { GlNewDropdown, GlNewDropdownItem } from '@gitlab/ui';
+import mockAllVersions from './mock_data/all_versions';
+
+const LATEST_VERSION_ID = 3;
+const PREVIOUS_VERSION_ID = 2;
+
+const designRouteFactory = versionId => ({
+ path: `/designs?version=${versionId}`,
+ query: {
+ version: `${versionId}`,
+ },
+});
+
+const MOCK_ROUTE = {
+ path: '/designs',
+ query: {},
+};
+
+describe('Design management design version dropdown component', () => {
+ let wrapper;
+
+ function createComponent({ maxVersions = -1, $route = MOCK_ROUTE } = {}) {
+ wrapper = shallowMount(DesignVersionDropdown, {
+ propsData: {
+ projectPath: '',
+ issueIid: '',
+ },
+ mocks: {
+ $route,
+ },
+ stubs: ['router-link'],
+ });
+
+ wrapper.setData({
+ allVersions: maxVersions > -1 ? mockAllVersions.slice(0, maxVersions) : mockAllVersions,
+ });
+ }
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ const findVersionLink = index => wrapper.findAll('.js-version-link').at(index);
+
+ it('renders design version dropdown button', () => {
+ createComponent();
+
+ return wrapper.vm.$nextTick().then(() => {
+ expect(wrapper.element).toMatchSnapshot();
+ });
+ });
+
+ it('renders design version list', () => {
+ createComponent();
+
+ return wrapper.vm.$nextTick().then(() => {
+ expect(wrapper.element).toMatchSnapshot();
+ });
+ });
+
+ describe('selected version name', () => {
+ it('has "latest" on most recent version item', () => {
+ createComponent();
+
+ return wrapper.vm.$nextTick().then(() => {
+ expect(findVersionLink(0).text()).toContain('latest');
+ });
+ });
+ });
+
+ describe('versions list', () => {
+ it('displays latest version text by default', () => {
+ createComponent();
+
+ return wrapper.vm.$nextTick().then(() => {
+ expect(wrapper.find(GlNewDropdown).attributes('text')).toBe('Showing Latest Version');
+ });
+ });
+
+ it('displays latest version text when only 1 version is present', () => {
+ createComponent({ maxVersions: 1 });
+
+ return wrapper.vm.$nextTick().then(() => {
+ expect(wrapper.find(GlNewDropdown).attributes('text')).toBe('Showing Latest Version');
+ });
+ });
+
+ it('displays version text when the current version is not the latest', () => {
+ createComponent({ $route: designRouteFactory(PREVIOUS_VERSION_ID) });
+
+ return wrapper.vm.$nextTick().then(() => {
+ expect(wrapper.find(GlNewDropdown).attributes('text')).toBe(`Showing Version #1`);
+ });
+ });
+
+ it('displays latest version text when the current version is the latest', () => {
+ createComponent({ $route: designRouteFactory(LATEST_VERSION_ID) });
+
+ return wrapper.vm.$nextTick().then(() => {
+ expect(wrapper.find(GlNewDropdown).attributes('text')).toBe('Showing Latest Version');
+ });
+ });
+
+ it('should have the same length as apollo query', () => {
+ createComponent();
+
+ return wrapper.vm.$nextTick().then(() => {
+ expect(wrapper.findAll(GlNewDropdownItem)).toHaveLength(wrapper.vm.allVersions.length);
+ });
+ });
+ });
+});
diff --git a/spec/frontend/design_management_new/components/upload/mock_data/all_versions.js b/spec/frontend/design_management_new/components/upload/mock_data/all_versions.js
new file mode 100644
index 00000000000..e76bbd261bd
--- /dev/null
+++ b/spec/frontend/design_management_new/components/upload/mock_data/all_versions.js
@@ -0,0 +1,14 @@
+export default [
+ {
+ node: {
+ id: 'gid://gitlab/DesignManagement::Version/3',
+ sha: '0945756378e0b1588b9dd40d5a6b99e8b7198f55',
+ },
+ },
+ {
+ node: {
+ id: 'gid://gitlab/DesignManagement::Version/2',
+ sha: '5b063fef0cd7213b312db65b30e24f057df21b20',
+ },
+ },
+];
diff --git a/spec/frontend/design_management_new/mock_data/all_versions.js b/spec/frontend/design_management_new/mock_data/all_versions.js
new file mode 100644
index 00000000000..c389fdb8747
--- /dev/null
+++ b/spec/frontend/design_management_new/mock_data/all_versions.js
@@ -0,0 +1,8 @@
+export default [
+ {
+ node: {
+ id: 'gid://gitlab/DesignManagement::Version/1',
+ sha: 'b389071a06c153509e11da1f582005b316667001',
+ },
+ },
+];
diff --git a/spec/frontend/design_management_new/mock_data/design.js b/spec/frontend/design_management_new/mock_data/design.js
new file mode 100644
index 00000000000..675198b9408
--- /dev/null
+++ b/spec/frontend/design_management_new/mock_data/design.js
@@ -0,0 +1,74 @@
+export default {
+ id: 'design-id',
+ filename: 'test.jpg',
+ fullPath: 'full-design-path',
+ image: 'test.jpg',
+ updatedAt: '01-01-2019',
+ updatedBy: {
+ name: 'test',
+ },
+ issue: {
+ title: 'My precious issue',
+ webPath: 'full-issue-path',
+ webUrl: 'full-issue-url',
+ participants: {
+ edges: [
+ {
+ node: {
+ name: 'Administrator',
+ username: 'root',
+ webUrl: 'link-to-author',
+ avatarUrl: 'link-to-avatar',
+ },
+ },
+ ],
+ },
+ },
+ discussions: {
+ nodes: [
+ {
+ id: 'discussion-id',
+ replyId: 'discussion-reply-id',
+ resolved: false,
+ notes: {
+ nodes: [
+ {
+ id: 'note-id',
+ body: '123',
+ author: {
+ name: 'Administrator',
+ username: 'root',
+ webUrl: 'link-to-author',
+ avatarUrl: 'link-to-avatar',
+ },
+ },
+ ],
+ },
+ },
+ {
+ id: 'discussion-resolved',
+ replyId: 'discussion-reply-resolved',
+ resolved: true,
+ notes: {
+ nodes: [
+ {
+ id: 'note-resolved',
+ body: '123',
+ author: {
+ name: 'Administrator',
+ username: 'root',
+ webUrl: 'link-to-author',
+ avatarUrl: 'link-to-avatar',
+ },
+ },
+ ],
+ },
+ },
+ ],
+ },
+ diffRefs: {
+ headSha: 'headSha',
+ baseSha: 'baseSha',
+ startSha: 'startSha',
+ },
+};
diff --git a/spec/frontend/design_management_new/mock_data/designs.js b/spec/frontend/design_management_new/mock_data/designs.js
new file mode 100644
index 00000000000..07f5c1b7457
--- /dev/null
+++ b/spec/frontend/design_management_new/mock_data/designs.js
@@ -0,0 +1,17 @@
+import design from './design';
+
+export default {
+ project: {
+ issue: {
+ designCollection: {
+ designs: {
+ edges: [
+ {
+ node: design,
+ },
+ ],
+ },
+ },
+ },
+ },
+};
diff --git a/spec/frontend/design_management_new/mock_data/no_designs.js b/spec/frontend/design_management_new/mock_data/no_designs.js
new file mode 100644
index 00000000000..9db0ffcade2
--- /dev/null
+++ b/spec/frontend/design_management_new/mock_data/no_designs.js
@@ -0,0 +1,11 @@
+export default {
+ project: {
+ issue: {
+ designCollection: {
+ designs: {
+ edges: [],
+ },
+ },
+ },
+ },
+};
diff --git a/spec/frontend/design_management_new/mock_data/notes.js b/spec/frontend/design_management_new/mock_data/notes.js
new file mode 100644
index 00000000000..80cb3944786
--- /dev/null
+++ b/spec/frontend/design_management_new/mock_data/notes.js
@@ -0,0 +1,46 @@
+export default [
+ {
+ id: 'note-id-1',
+ index: 1,
+ position: {
+ height: 100,
+ width: 100,
+ x: 10,
+ y: 15,
+ },
+ author: {
+ name: 'John',
+ webUrl: 'link-to-john-profile',
+ },
+ createdAt: '2020-05-08T07:10:45Z',
+ userPermissions: {
+ adminNote: true,
+ },
+ discussion: {
+ id: 'discussion-id-1',
+ },
+ resolved: false,
+ },
+ {
+ id: 'note-id-2',
+ index: 2,
+ position: {
+ height: 50,
+ width: 50,
+ x: 25,
+ y: 25,
+ },
+ author: {
+ name: 'Mary',
+ webUrl: 'link-to-mary-profile',
+ },
+ createdAt: '2020-05-08T07:10:45Z',
+ userPermissions: {
+ adminNote: true,
+ },
+ discussion: {
+ id: 'discussion-id-2',
+ },
+ resolved: true,
+ },
+];
diff --git a/spec/frontend/design_management_new/pages/__snapshots__/index_spec.js.snap b/spec/frontend/design_management_new/pages/__snapshots__/index_spec.js.snap
new file mode 100644
index 00000000000..3d1fe143ac3
--- /dev/null
+++ b/spec/frontend/design_management_new/pages/__snapshots__/index_spec.js.snap
@@ -0,0 +1,317 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Design management index page designs does not render toolbar when there is no permission 1`] = `
+<div
+ class="gl-mt-5"
+ data-testid="designs-root"
+>
+ <!---->
+
+ <div
+ class="mt-4"
+ >
+ <ol
+ class="list-unstyled row"
+ >
+ <!---->
+
+ <li
+ class="gl-flex-direction-column col-md-6 col-lg-3 gl-mb-3"
+ data-testid="design-dropzone-wrapper"
+ >
+ <design-dropzone-stub
+ class="design-list-item design-list-item-new"
+ hasdesigns="true"
+ />
+ </li>
+
+ <li
+ class="col-md-6 col-lg-3 gl-mb-3"
+ >
+ <design-dropzone-stub
+ hasdesigns="true"
+ >
+ <design-stub
+ event="NONE"
+ filename="design-1-name"
+ id="design-1"
+ image="design-1-image"
+ notescount="0"
+ />
+ </design-dropzone-stub>
+
+ <!---->
+ </li>
+ <li
+ class="col-md-6 col-lg-3 gl-mb-3"
+ >
+ <design-dropzone-stub
+ hasdesigns="true"
+ >
+ <design-stub
+ event="NONE"
+ filename="design-2-name"
+ id="design-2"
+ image="design-2-image"
+ notescount="1"
+ />
+ </design-dropzone-stub>
+
+ <!---->
+ </li>
+ <li
+ class="col-md-6 col-lg-3 gl-mb-3"
+ >
+ <design-dropzone-stub
+ hasdesigns="true"
+ >
+ <design-stub
+ event="NONE"
+ filename="design-3-name"
+ id="design-3"
+ image="design-3-image"
+ notescount="0"
+ />
+ </design-dropzone-stub>
+
+ <!---->
+ </li>
+ </ol>
+ </div>
+
+ <router-view-stub
+ name="default"
+ />
+</div>
+`;
+
+exports[`Design management index page designs renders designs list and header with upload button 1`] = `
+<div
+ class="gl-mt-5"
+ data-testid="designs-root"
+>
+ <header
+ class="row-content-block border-top-0 p-2 d-flex"
+ >
+ <div
+ class="gl-display-flex gl-justify-content-space-between gl-align-items-center gl-w-full"
+ >
+ <div>
+ <span
+ class="gl-font-weight-bold gl-mr-3"
+ >
+ Designs
+ </span>
+
+ <design-version-dropdown-stub />
+ </div>
+
+ <div
+ class="qa-selector-toolbar gl-display-flex"
+ >
+ <gl-button-stub
+ category="tertiary"
+ class="gl-mr-2 js-select-all"
+ icon=""
+ size="small"
+ variant="link"
+ >
+ Select all
+
+ </gl-button-stub>
+
+ <div>
+ <delete-button-stub
+ buttonclass="gl-mr-4"
+ buttonsize="small"
+ buttonvariant="danger"
+ >
+
+ Delete selected
+
+ <!---->
+ </delete-button-stub>
+ </div>
+
+ <upload-button-stub />
+ </div>
+ </div>
+ </header>
+
+ <div
+ class="mt-4"
+ >
+ <ol
+ class="list-unstyled row"
+ >
+ <!---->
+
+ <li
+ class="gl-flex-direction-column col-md-6 col-lg-3 gl-mb-3"
+ data-testid="design-dropzone-wrapper"
+ >
+ <design-dropzone-stub
+ class="design-list-item design-list-item-new"
+ hasdesigns="true"
+ />
+ </li>
+
+ <li
+ class="col-md-6 col-lg-3 gl-mb-3"
+ >
+ <design-dropzone-stub
+ hasdesigns="true"
+ >
+ <design-stub
+ event="NONE"
+ filename="design-1-name"
+ id="design-1"
+ image="design-1-image"
+ notescount="0"
+ />
+ </design-dropzone-stub>
+
+ <input
+ class="design-checkbox"
+ type="checkbox"
+ />
+ </li>
+ <li
+ class="col-md-6 col-lg-3 gl-mb-3"
+ >
+ <design-dropzone-stub
+ hasdesigns="true"
+ >
+ <design-stub
+ event="NONE"
+ filename="design-2-name"
+ id="design-2"
+ image="design-2-image"
+ notescount="1"
+ />
+ </design-dropzone-stub>
+
+ <input
+ class="design-checkbox"
+ type="checkbox"
+ />
+ </li>
+ <li
+ class="col-md-6 col-lg-3 gl-mb-3"
+ >
+ <design-dropzone-stub
+ hasdesigns="true"
+ >
+ <design-stub
+ event="NONE"
+ filename="design-3-name"
+ id="design-3"
+ image="design-3-image"
+ notescount="0"
+ />
+ </design-dropzone-stub>
+
+ <input
+ class="design-checkbox"
+ type="checkbox"
+ />
+ </li>
+ </ol>
+ </div>
+
+ <router-view-stub
+ name="default"
+ />
+</div>
+`;
+
+exports[`Design management index page designs renders error 1`] = `
+<div
+ class="gl-mt-5"
+ data-testid="designs-root"
+>
+ <!---->
+
+ <div
+ class="mt-4"
+ >
+ <gl-alert-stub
+ dismisslabel="Dismiss"
+ primarybuttonlink=""
+ primarybuttontext=""
+ secondarybuttonlink=""
+ secondarybuttontext=""
+ title=""
+ variant="danger"
+ >
+
+ An error occurred while loading designs. Please try again.
+
+ </gl-alert-stub>
+ </div>
+
+ <router-view-stub
+ name="default"
+ />
+</div>
+`;
+
+exports[`Design management index page designs renders loading icon 1`] = `
+<div
+ class="gl-mt-5"
+ data-testid="designs-root"
+>
+ <!---->
+
+ <div
+ class="mt-4"
+ >
+ <gl-loading-icon-stub
+ color="orange"
+ label="Loading"
+ size="md"
+ />
+ </div>
+
+ <router-view-stub
+ name="default"
+ />
+</div>
+`;
+
+exports[`Design management index page when has no designs renders design dropzone 1`] = `
+<div
+ class="gl-mt-5"
+ data-testid="designs-root"
+>
+ <!---->
+
+ <div
+ class="mt-4"
+ >
+ <ol
+ class="list-unstyled row"
+ >
+ <span
+ class="gl-font-weight-bold gl-font-weight-bold gl-ml-5 gl-mb-4"
+ >
+ Designs
+ </span>
+
+ <li
+ class="col-12"
+ data-testid="design-dropzone-wrapper"
+ >
+ <design-dropzone-stub
+ class=""
+ />
+ </li>
+
+ </ol>
+ </div>
+
+ <router-view-stub
+ name="default"
+ />
+</div>
+`;
diff --git a/spec/frontend/design_management_new/pages/design/__snapshots__/index_spec.js.snap b/spec/frontend/design_management_new/pages/design/__snapshots__/index_spec.js.snap
new file mode 100644
index 00000000000..83bcebd513e
--- /dev/null
+++ b/spec/frontend/design_management_new/pages/design/__snapshots__/index_spec.js.snap
@@ -0,0 +1,216 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Design management design index page renders design index 1`] = `
+<div
+ class="design-detail js-design-detail fixed-top w-100 position-bottom-0 d-flex justify-content-center flex-column flex-lg-row"
+>
+ <div
+ class="d-flex overflow-hidden flex-grow-1 flex-column position-relative"
+ >
+ <design-destroyer-stub
+ filenames="test.jpg"
+ iid="1"
+ project-path="project-path"
+ />
+
+ <!---->
+
+ <design-presentation-stub
+ discussions="[object Object],[object Object]"
+ image="test.jpg"
+ imagename="test.jpg"
+ scale="1"
+ />
+
+ <div
+ class="design-scaler-wrapper position-absolute mb-4 d-flex-center"
+ >
+ <design-scaler-stub />
+ </div>
+ </div>
+
+ <div
+ class="image-notes"
+ >
+ <h2
+ class="gl-font-weight-bold gl-mt-0"
+ >
+
+ My precious issue
+
+ </h2>
+
+ <a
+ class="gl-text-gray-600 gl-text-decoration-none gl-mb-6 gl-display-block"
+ href="full-issue-url"
+ >
+ ull-issue-path
+ </a>
+
+ <participants-stub
+ class="gl-mb-4"
+ numberoflessparticipants="7"
+ participants="[object Object]"
+ />
+
+ <!---->
+
+ <design-discussion-stub
+ data-testid="unresolved-discussion"
+ designid="test"
+ discussion="[object Object]"
+ discussionwithopenform=""
+ markdownpreviewpath="/project-path/preview_markdown?target_type=Issue"
+ noteableid="design-id"
+ />
+
+ <gl-button-stub
+ category="tertiary"
+ class="link-inherit-color gl-text-black-normal gl-text-decoration-none gl-font-weight-bold gl-mb-4"
+ data-testid="resolved-comments"
+ icon="chevron-right"
+ id="resolved-comments"
+ size="medium"
+ variant="link"
+ >
+ Resolved Comments (1)
+
+ </gl-button-stub>
+
+ <gl-popover-stub
+ container="popovercontainer"
+ cssclasses=""
+ placement="top"
+ show="true"
+ target="resolved-comments"
+ title="Resolved Comments"
+ >
+ <p>
+
+ Comments you resolve can be viewed and unresolved by going to the "Resolved Comments" section below
+
+ </p>
+
+ <a
+ href="#"
+ rel="noopener noreferrer"
+ target="_blank"
+ >
+ Learn more about resolving comments
+ </a>
+ </gl-popover-stub>
+
+ <gl-collapse-stub
+ class="gl-mt-3"
+ >
+ <design-discussion-stub
+ data-testid="resolved-discussion"
+ designid="test"
+ discussion="[object Object]"
+ discussionwithopenform=""
+ markdownpreviewpath="/project-path/preview_markdown?target_type=Issue"
+ noteableid="design-id"
+ />
+ </gl-collapse-stub>
+
+ </div>
+</div>
+`;
+
+exports[`Design management design index page sets loading state 1`] = `
+<div
+ class="design-detail js-design-detail fixed-top w-100 position-bottom-0 d-flex justify-content-center flex-column flex-lg-row"
+>
+ <gl-loading-icon-stub
+ class="align-self-center"
+ color="orange"
+ label="Loading"
+ size="xl"
+ />
+</div>
+`;
+
+exports[`Design management design index page with error GlAlert is rendered in correct position with correct content 1`] = `
+<div
+ class="design-detail js-design-detail fixed-top w-100 position-bottom-0 d-flex justify-content-center flex-column flex-lg-row"
+>
+ <div
+ class="d-flex overflow-hidden flex-grow-1 flex-column position-relative"
+ >
+ <design-destroyer-stub
+ filenames="test.jpg"
+ iid="1"
+ project-path="project-path"
+ />
+
+ <div
+ class="p-3"
+ >
+ <gl-alert-stub
+ dismissible="true"
+ dismisslabel="Dismiss"
+ primarybuttonlink=""
+ primarybuttontext=""
+ secondarybuttonlink=""
+ secondarybuttontext=""
+ title=""
+ variant="danger"
+ >
+
+ woops
+
+ </gl-alert-stub>
+ </div>
+
+ <design-presentation-stub
+ discussions=""
+ image="test.jpg"
+ imagename="test.jpg"
+ scale="1"
+ />
+
+ <div
+ class="design-scaler-wrapper position-absolute mb-4 d-flex-center"
+ >
+ <design-scaler-stub />
+ </div>
+ </div>
+
+ <div
+ class="image-notes"
+ >
+ <h2
+ class="gl-font-weight-bold gl-mt-0"
+ >
+
+ My precious issue
+
+ </h2>
+
+ <a
+ class="gl-text-gray-600 gl-text-decoration-none gl-mb-6 gl-display-block"
+ href="full-issue-url"
+ >
+ ull-issue-path
+ </a>
+
+ <participants-stub
+ class="gl-mb-4"
+ numberoflessparticipants="7"
+ participants="[object Object]"
+ />
+
+ <h2
+ class="new-discussion-disclaimer gl-font-base gl-m-0 gl-mb-4"
+ data-testid="new-discussion-disclaimer"
+ >
+
+ Click the image where you'd like to start a new discussion
+
+ </h2>
+
+ <!---->
+
+ </div>
+</div>
+`;
diff --git a/spec/frontend/design_management_new/pages/design/index_spec.js b/spec/frontend/design_management_new/pages/design/index_spec.js
new file mode 100644
index 00000000000..3822b0b3b71
--- /dev/null
+++ b/spec/frontend/design_management_new/pages/design/index_spec.js
@@ -0,0 +1,294 @@
+import { shallowMount, createLocalVue } from '@vue/test-utils';
+import VueRouter from 'vue-router';
+import { GlAlert } from '@gitlab/ui';
+import { ApolloMutation } from 'vue-apollo';
+import createFlash from '~/flash';
+import DesignIndex from '~/design_management_new/pages/design/index.vue';
+import DesignSidebar from '~/design_management_new/components/design_sidebar.vue';
+import DesignPresentation from '~/design_management_new/components/design_presentation.vue';
+import createImageDiffNoteMutation from '~/design_management_new/graphql/mutations/create_image_diff_note.mutation.graphql';
+import design from '../../mock_data/design';
+import mockResponseWithDesigns from '../../mock_data/designs';
+import mockResponseNoDesigns from '../../mock_data/no_designs';
+import mockAllVersions from '../../mock_data/all_versions';
+import {
+ DESIGN_NOT_FOUND_ERROR,
+ DESIGN_VERSION_NOT_EXIST_ERROR,
+} from '~/design_management_new/utils/error_messages';
+import { DESIGNS_ROUTE_NAME } from '~/design_management_new/router/constants';
+import createRouter from '~/design_management_new/router';
+import * as utils from '~/design_management_new/utils/design_management_utils';
+import { DESIGN_DETAIL_LAYOUT_CLASSLIST } from '~/design_management_new/constants';
+
+jest.mock('~/flash');
+jest.mock('mousetrap', () => ({
+ bind: jest.fn(),
+ unbind: jest.fn(),
+}));
+
+const focusInput = jest.fn();
+
+const DesignReplyForm = {
+ template: '<div><textarea ref="textarea"></textarea></div>',
+ methods: {
+ focusInput,
+ },
+};
+
+const localVue = createLocalVue();
+localVue.use(VueRouter);
+
+describe('Design management design index page', () => {
+ let wrapper;
+ let router;
+
+ const newComment = 'new comment';
+ const annotationCoordinates = {
+ x: 10,
+ y: 10,
+ width: 100,
+ height: 100,
+ };
+ const createDiscussionMutationVariables = {
+ mutation: createImageDiffNoteMutation,
+ update: expect.anything(),
+ variables: {
+ input: {
+ body: newComment,
+ noteableId: design.id,
+ position: {
+ headSha: 'headSha',
+ baseSha: 'baseSha',
+ startSha: 'startSha',
+ paths: {
+ newPath: 'full-design-path',
+ },
+ ...annotationCoordinates,
+ },
+ },
+ },
+ };
+
+ const mutate = jest.fn().mockResolvedValue();
+
+ const findDiscussionForm = () => wrapper.find(DesignReplyForm);
+ const findSidebar = () => wrapper.find(DesignSidebar);
+ const findDesignPresentation = () => wrapper.find(DesignPresentation);
+
+ function createComponent(loading = false, data = {}) {
+ const $apollo = {
+ queries: {
+ design: {
+ loading,
+ },
+ },
+ mutate,
+ };
+
+ router = createRouter();
+
+ wrapper = shallowMount(DesignIndex, {
+ propsData: { id: '1' },
+ mocks: { $apollo },
+ stubs: {
+ ApolloMutation,
+ DesignSidebar,
+ DesignReplyForm,
+ },
+ provide: {
+ issueIid: '1',
+ projectPath: 'project-path',
+ },
+ data() {
+ return {
+ activeDiscussion: {
+ id: null,
+ source: null,
+ },
+ ...data,
+ };
+ },
+ localVue,
+ router,
+ });
+ }
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ describe('when navigating', () => {
+ it('applies fullscreen layout', () => {
+ const mockEl = {
+ classList: {
+ add: jest.fn(),
+ remove: jest.fn(),
+ },
+ };
+ jest.spyOn(utils, 'getPageLayoutElement').mockReturnValue(mockEl);
+ createComponent(true);
+
+ wrapper.vm.$router.push('/designs/test');
+ expect(mockEl.classList.add).toHaveBeenCalledTimes(1);
+ expect(mockEl.classList.add).toHaveBeenCalledWith(...DESIGN_DETAIL_LAYOUT_CLASSLIST);
+ });
+ });
+
+ it('sets loading state', () => {
+ createComponent(true);
+
+ expect(wrapper.element).toMatchSnapshot();
+ });
+
+ it('renders design index', () => {
+ createComponent(false, { design });
+
+ expect(wrapper.element).toMatchSnapshot();
+ expect(wrapper.find(GlAlert).exists()).toBe(false);
+ });
+
+ it('passes correct props to sidebar component', () => {
+ createComponent(false, { design });
+
+ expect(findSidebar().props()).toEqual({
+ design,
+ markdownPreviewPath: '/project-path/preview_markdown?target_type=Issue',
+ resolvedDiscussionsExpanded: false,
+ });
+ });
+
+ it('opens a new discussion form', () => {
+ createComponent(false, {
+ design: {
+ ...design,
+ discussions: {
+ nodes: [],
+ },
+ },
+ });
+
+ findDesignPresentation().vm.$emit('openCommentForm', { x: 0, y: 0 });
+
+ return wrapper.vm.$nextTick().then(() => {
+ expect(findDiscussionForm().exists()).toBe(true);
+ });
+ });
+
+ it('keeps new discussion form focused', () => {
+ createComponent(false, {
+ design: {
+ ...design,
+ discussions: {
+ nodes: [],
+ },
+ },
+ annotationCoordinates,
+ });
+
+ findDesignPresentation().vm.$emit('openCommentForm', { x: 10, y: 10 });
+
+ expect(focusInput).toHaveBeenCalled();
+ });
+
+ it('sends a mutation on submitting form and closes form', () => {
+ createComponent(false, {
+ design: {
+ ...design,
+ discussions: {
+ nodes: [],
+ },
+ },
+ annotationCoordinates,
+ comment: newComment,
+ });
+
+ findDiscussionForm().vm.$emit('submitForm');
+ expect(mutate).toHaveBeenCalledWith(createDiscussionMutationVariables);
+
+ return wrapper.vm
+ .$nextTick()
+ .then(() => {
+ return mutate({ variables: createDiscussionMutationVariables });
+ })
+ .then(() => {
+ expect(findDiscussionForm().exists()).toBe(false);
+ });
+ });
+
+ it('closes the form and clears the comment on canceling form', () => {
+ createComponent(false, {
+ design: {
+ ...design,
+ discussions: {
+ nodes: [],
+ },
+ },
+ annotationCoordinates,
+ comment: newComment,
+ });
+
+ findDiscussionForm().vm.$emit('cancelForm');
+
+ expect(wrapper.vm.comment).toBe('');
+
+ return wrapper.vm.$nextTick().then(() => {
+ expect(findDiscussionForm().exists()).toBe(false);
+ });
+ });
+
+ describe('with error', () => {
+ beforeEach(() => {
+ createComponent(false, {
+ design: {
+ ...design,
+ discussions: {
+ nodes: [],
+ },
+ },
+ errorMessage: 'woops',
+ });
+ });
+
+ it('GlAlert is rendered in correct position with correct content', () => {
+ expect(wrapper.element).toMatchSnapshot();
+ });
+ });
+
+ describe('onDesignQueryResult', () => {
+ describe('with no designs', () => {
+ it('redirects to /designs', () => {
+ createComponent(true);
+ router.push = jest.fn();
+
+ wrapper.vm.onDesignQueryResult({ data: mockResponseNoDesigns, loading: false });
+ return wrapper.vm.$nextTick().then(() => {
+ expect(createFlash).toHaveBeenCalledTimes(1);
+ expect(createFlash).toHaveBeenCalledWith(DESIGN_NOT_FOUND_ERROR);
+ expect(router.push).toHaveBeenCalledTimes(1);
+ expect(router.push).toHaveBeenCalledWith({ name: DESIGNS_ROUTE_NAME });
+ });
+ });
+ });
+
+ describe('when no design exists for given version', () => {
+ it('redirects to /designs', () => {
+ createComponent(true);
+ wrapper.setData({
+ allVersions: mockAllVersions,
+ });
+
+ // attempt to query for a version of the design that doesn't exist
+ router.push({ query: { version: '999' } });
+ router.push = jest.fn();
+
+ wrapper.vm.onDesignQueryResult({ data: mockResponseWithDesigns, loading: false });
+ return wrapper.vm.$nextTick().then(() => {
+ expect(createFlash).toHaveBeenCalledTimes(1);
+ expect(createFlash).toHaveBeenCalledWith(DESIGN_VERSION_NOT_EXIST_ERROR);
+ expect(router.push).toHaveBeenCalledTimes(1);
+ expect(router.push).toHaveBeenCalledWith({ name: DESIGNS_ROUTE_NAME });
+ });
+ });
+ });
+ });
+});
diff --git a/spec/frontend/design_management_new/pages/index_spec.js b/spec/frontend/design_management_new/pages/index_spec.js
new file mode 100644
index 00000000000..40a462eabb8
--- /dev/null
+++ b/spec/frontend/design_management_new/pages/index_spec.js
@@ -0,0 +1,571 @@
+import { createLocalVue, shallowMount } from '@vue/test-utils';
+import { ApolloMutation } from 'vue-apollo';
+import VueRouter from 'vue-router';
+import { GlEmptyState } from '@gitlab/ui';
+import Index from '~/design_management_new/pages/index.vue';
+import uploadDesignQuery from '~/design_management_new/graphql/mutations/upload_design.mutation.graphql';
+import DesignDestroyer from '~/design_management_new/components/design_destroyer.vue';
+import DesignDropzone from '~/design_management_new/components/upload/design_dropzone.vue';
+import DeleteButton from '~/design_management_new/components/delete_button.vue';
+import { DESIGNS_ROUTE_NAME } from '~/design_management_new/router/constants';
+import {
+ EXISTING_DESIGN_DROP_MANY_FILES_MESSAGE,
+ EXISTING_DESIGN_DROP_INVALID_FILENAME_MESSAGE,
+} from '~/design_management_new/utils/error_messages';
+import createFlash from '~/flash';
+import createRouter from '~/design_management_new/router';
+import * as utils from '~/design_management_new/utils/design_management_utils';
+import { DESIGN_DETAIL_LAYOUT_CLASSLIST } from '~/design_management_new/constants';
+
+jest.mock('~/flash.js');
+const mockPageEl = {
+ classList: {
+ remove: jest.fn(),
+ },
+};
+jest.spyOn(utils, 'getPageLayoutElement').mockReturnValue(mockPageEl);
+
+const localVue = createLocalVue();
+const router = createRouter();
+localVue.use(VueRouter);
+
+const mockDesigns = [
+ {
+ id: 'design-1',
+ image: 'design-1-image',
+ filename: 'design-1-name',
+ event: 'NONE',
+ notesCount: 0,
+ },
+ {
+ id: 'design-2',
+ image: 'design-2-image',
+ filename: 'design-2-name',
+ event: 'NONE',
+ notesCount: 1,
+ },
+ {
+ id: 'design-3',
+ image: 'design-3-image',
+ filename: 'design-3-name',
+ event: 'NONE',
+ notesCount: 0,
+ },
+];
+
+const mockVersion = {
+ node: {
+ id: 'gid://gitlab/DesignManagement::Version/1',
+ },
+};
+
+describe('Design management index page', () => {
+ let mutate;
+ let wrapper;
+
+ const findDesignCheckboxes = () => wrapper.findAll('.design-checkbox');
+ const findSelectAllButton = () => wrapper.find('.js-select-all');
+ const findToolbar = () => wrapper.find('.qa-selector-toolbar');
+ const findDeleteButton = () => wrapper.find(DeleteButton);
+ const findDropzone = () => wrapper.findAll(DesignDropzone).at(0);
+ const dropzoneClasses = () => findDropzone().classes();
+ const findDropzoneWrapper = () => wrapper.find('[data-testid="design-dropzone-wrapper"]');
+ const findFirstDropzoneWithDesign = () => wrapper.findAll(DesignDropzone).at(1);
+
+ function createComponent({
+ loading = false,
+ designs = [],
+ allVersions = [],
+ createDesign = true,
+ stubs = {},
+ mockMutate = jest.fn().mockResolvedValue(),
+ } = {}) {
+ mutate = mockMutate;
+ const $apollo = {
+ queries: {
+ designs: {
+ loading,
+ },
+ permissions: {
+ loading,
+ },
+ },
+ mutate,
+ };
+
+ wrapper = shallowMount(Index, {
+ data() {
+ return {
+ designs,
+ allVersions,
+ permissions: {
+ createDesign,
+ },
+ };
+ },
+ mocks: { $apollo },
+ localVue,
+ router,
+ stubs: { DesignDestroyer, ApolloMutation, ...stubs },
+ attachToDocument: true,
+ provide: {
+ projectPath: 'project-path',
+ issueIid: '1',
+ },
+ });
+ }
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ describe('designs', () => {
+ it('renders loading icon', () => {
+ createComponent({ loading: true });
+
+ expect(wrapper.element).toMatchSnapshot();
+ });
+
+ it('renders error', () => {
+ createComponent();
+
+ wrapper.setData({ error: true });
+
+ return wrapper.vm.$nextTick().then(() => {
+ expect(wrapper.element).toMatchSnapshot();
+ });
+ });
+
+ it('renders a toolbar with buttons when there are designs', () => {
+ createComponent({ designs: mockDesigns, allVersions: [mockVersion] });
+
+ expect(findToolbar().exists()).toBe(true);
+ });
+
+ it('renders designs list and header with upload button', () => {
+ createComponent({ designs: mockDesigns, allVersions: [mockVersion] });
+
+ expect(wrapper.element).toMatchSnapshot();
+ });
+
+ it('does not render toolbar when there is no permission', () => {
+ createComponent({ designs: mockDesigns, allVersions: [mockVersion], createDesign: false });
+
+ expect(wrapper.element).toMatchSnapshot();
+ });
+
+ it('has correct classes applied to design dropzone', () => {
+ createComponent({ designs: mockDesigns, allVersions: [mockVersion] });
+ expect(dropzoneClasses()).toContain('design-list-item');
+ expect(dropzoneClasses()).toContain('design-list-item-new');
+ });
+
+ it('has correct classes applied to dropzone wrapper', () => {
+ createComponent({ designs: mockDesigns, allVersions: [mockVersion] });
+ expect(findDropzoneWrapper().classes()).toEqual([
+ 'gl-flex-direction-column',
+ 'col-md-6',
+ 'col-lg-3',
+ 'gl-mb-3',
+ ]);
+ });
+ });
+
+ describe('when has no designs', () => {
+ beforeEach(() => {
+ createComponent();
+ });
+
+ it('renders design dropzone', () =>
+ wrapper.vm.$nextTick().then(() => {
+ expect(wrapper.element).toMatchSnapshot();
+ }));
+
+ it('has correct classes applied to design dropzone', () => {
+ expect(dropzoneClasses()).not.toContain('design-list-item');
+ expect(dropzoneClasses()).not.toContain('design-list-item-new');
+ });
+
+ it('has correct classes applied to dropzone wrapper', () => {
+ expect(findDropzoneWrapper().classes()).toEqual(['col-12']);
+ });
+
+ it('does not render a toolbar with buttons', () =>
+ wrapper.vm.$nextTick().then(() => {
+ expect(findToolbar().exists()).toBe(false);
+ }));
+ });
+
+ describe('uploading designs', () => {
+ it('calls mutation on upload', () => {
+ createComponent({ stubs: { GlEmptyState } });
+
+ const mutationVariables = {
+ update: expect.anything(),
+ context: {
+ hasUpload: true,
+ },
+ mutation: uploadDesignQuery,
+ variables: {
+ files: [{ name: 'test' }],
+ projectPath: 'project-path',
+ iid: '1',
+ },
+ optimisticResponse: {
+ __typename: 'Mutation',
+ designManagementUpload: {
+ __typename: 'DesignManagementUploadPayload',
+ designs: [
+ {
+ __typename: 'Design',
+ id: expect.anything(),
+ image: '',
+ imageV432x230: '',
+ filename: 'test',
+ fullPath: '',
+ event: 'NONE',
+ notesCount: 0,
+ diffRefs: {
+ __typename: 'DiffRefs',
+ baseSha: '',
+ startSha: '',
+ headSha: '',
+ },
+ discussions: {
+ __typename: 'DesignDiscussion',
+ nodes: [],
+ },
+ versions: {
+ __typename: 'DesignVersionConnection',
+ edges: {
+ __typename: 'DesignVersionEdge',
+ node: {
+ __typename: 'DesignVersion',
+ id: expect.anything(),
+ sha: expect.anything(),
+ },
+ },
+ },
+ },
+ ],
+ skippedDesigns: [],
+ errors: [],
+ },
+ },
+ };
+
+ return wrapper.vm
+ .$nextTick()
+ .then(() => {
+ findDropzone().vm.$emit('change', [{ name: 'test' }]);
+ expect(mutate).toHaveBeenCalledWith(mutationVariables);
+ expect(wrapper.vm.filesToBeSaved).toEqual([{ name: 'test' }]);
+ expect(wrapper.vm.isSaving).toBeTruthy();
+ })
+ .then(() => {
+ expect(dropzoneClasses()).toContain('design-list-item');
+ expect(dropzoneClasses()).toContain('design-list-item-new');
+ });
+ });
+
+ it('sets isSaving', () => {
+ createComponent();
+
+ const uploadDesign = wrapper.vm.onUploadDesign([
+ {
+ name: 'test',
+ },
+ ]);
+
+ expect(wrapper.vm.isSaving).toBe(true);
+
+ return uploadDesign.then(() => {
+ expect(wrapper.vm.isSaving).toBe(false);
+ });
+ });
+
+ it('updates state appropriately after upload complete', () => {
+ createComponent({ stubs: { GlEmptyState } });
+ wrapper.setData({ filesToBeSaved: [{ name: 'test' }] });
+
+ wrapper.vm.onUploadDesignDone();
+ return wrapper.vm.$nextTick().then(() => {
+ expect(wrapper.vm.filesToBeSaved).toEqual([]);
+ expect(wrapper.vm.isSaving).toBeFalsy();
+ expect(wrapper.vm.isLatestVersion).toBe(true);
+ });
+ });
+
+ it('updates state appropriately after upload error', () => {
+ createComponent({ stubs: { GlEmptyState } });
+ wrapper.setData({ filesToBeSaved: [{ name: 'test' }] });
+
+ wrapper.vm.onUploadDesignError();
+ return wrapper.vm.$nextTick().then(() => {
+ expect(wrapper.vm.filesToBeSaved).toEqual([]);
+ expect(wrapper.vm.isSaving).toBeFalsy();
+ expect(createFlash).toHaveBeenCalled();
+
+ createFlash.mockReset();
+ });
+ });
+
+ it('does not call mutation if createDesign is false', () => {
+ createComponent({ createDesign: false });
+
+ wrapper.vm.onUploadDesign([]);
+
+ expect(mutate).not.toHaveBeenCalled();
+ });
+
+ describe('upload count limit', () => {
+ const MAXIMUM_FILE_UPLOAD_LIMIT = 10;
+
+ afterEach(() => {
+ createFlash.mockReset();
+ });
+
+ it('does not warn when the max files are uploaded', () => {
+ createComponent();
+
+ wrapper.vm.onUploadDesign(new Array(MAXIMUM_FILE_UPLOAD_LIMIT).fill(mockDesigns[0]));
+
+ expect(createFlash).not.toHaveBeenCalled();
+ });
+
+ it('warns when too many files are uploaded', () => {
+ createComponent();
+
+ wrapper.vm.onUploadDesign(new Array(MAXIMUM_FILE_UPLOAD_LIMIT + 1).fill(mockDesigns[0]));
+
+ expect(createFlash).toHaveBeenCalled();
+ });
+ });
+
+ it('flashes warning if designs are skipped', () => {
+ createComponent({
+ mockMutate: () =>
+ Promise.resolve({
+ data: { designManagementUpload: { skippedDesigns: [{ filename: 'test.jpg' }] } },
+ }),
+ });
+
+ const uploadDesign = wrapper.vm.onUploadDesign([
+ {
+ name: 'test',
+ },
+ ]);
+
+ return uploadDesign.then(() => {
+ expect(createFlash).toHaveBeenCalledTimes(1);
+ expect(createFlash).toHaveBeenCalledWith(
+ 'Upload skipped. test.jpg did not change.',
+ 'warning',
+ );
+ });
+ });
+
+ describe('dragging onto an existing design', () => {
+ beforeEach(() => {
+ createComponent({ designs: mockDesigns, allVersions: [mockVersion] });
+ });
+
+ it('calls onUploadDesign with valid upload', () => {
+ wrapper.setMethods({
+ onUploadDesign: jest.fn(),
+ });
+
+ const mockUploadPayload = [
+ {
+ name: mockDesigns[0].filename,
+ },
+ ];
+
+ const designDropzone = findFirstDropzoneWithDesign();
+ designDropzone.vm.$emit('change', mockUploadPayload);
+
+ expect(wrapper.vm.onUploadDesign).toHaveBeenCalledTimes(1);
+ expect(wrapper.vm.onUploadDesign).toHaveBeenCalledWith(mockUploadPayload);
+ });
+
+ it.each`
+ description | eventPayload | message
+ ${'> 1 file'} | ${[{ name: 'test' }, { name: 'test-2' }]} | ${EXISTING_DESIGN_DROP_MANY_FILES_MESSAGE}
+ ${'different filename'} | ${[{ name: 'wrong-name' }]} | ${EXISTING_DESIGN_DROP_INVALID_FILENAME_MESSAGE}
+ `('calls createFlash when upload has $description', ({ eventPayload, message }) => {
+ const designDropzone = findFirstDropzoneWithDesign();
+ designDropzone.vm.$emit('change', eventPayload);
+
+ expect(createFlash).toHaveBeenCalledTimes(1);
+ expect(createFlash).toHaveBeenCalledWith(message);
+ });
+ });
+ });
+
+ describe('on latest version when has designs', () => {
+ beforeEach(() => {
+ createComponent({ designs: mockDesigns, allVersions: [mockVersion] });
+ });
+
+ it('renders design checkboxes', () => {
+ expect(findDesignCheckboxes()).toHaveLength(mockDesigns.length);
+ });
+
+ it('renders toolbar buttons', () => {
+ expect(findToolbar().exists()).toBe(true);
+ expect(findToolbar().isVisible()).toBe(true);
+ });
+
+ it('adds two designs to selected designs when their checkboxes are checked', () => {
+ findDesignCheckboxes()
+ .at(0)
+ .trigger('click');
+
+ return wrapper.vm
+ .$nextTick()
+ .then(() => {
+ findDesignCheckboxes()
+ .at(1)
+ .trigger('click');
+
+ return wrapper.vm.$nextTick();
+ })
+ .then(() => {
+ expect(findDeleteButton().exists()).toBe(true);
+ expect(findSelectAllButton().text()).toBe('Deselect all');
+ findDeleteButton().vm.$emit('deleteSelectedDesigns');
+ const [{ variables }] = mutate.mock.calls[0];
+ expect(variables.filenames).toStrictEqual([
+ mockDesigns[0].filename,
+ mockDesigns[1].filename,
+ ]);
+ });
+ });
+
+ it('adds all designs to selected designs when Select All button is clicked', () => {
+ findSelectAllButton().vm.$emit('click');
+
+ return wrapper.vm.$nextTick().then(() => {
+ expect(findDeleteButton().props().hasSelectedDesigns).toBe(true);
+ expect(findSelectAllButton().text()).toBe('Deselect all');
+ expect(wrapper.vm.selectedDesigns).toEqual(mockDesigns.map(design => design.filename));
+ });
+ });
+
+ it('removes all designs from selected designs when at least one design was selected', () => {
+ findDesignCheckboxes()
+ .at(0)
+ .trigger('click');
+
+ return wrapper.vm
+ .$nextTick()
+ .then(() => {
+ findSelectAllButton().vm.$emit('click');
+ })
+ .then(() => {
+ expect(findDeleteButton().props().hasSelectedDesigns).toBe(false);
+ expect(findSelectAllButton().text()).toBe('Select all');
+ expect(wrapper.vm.selectedDesigns).toEqual([]);
+ });
+ });
+ });
+
+ it('on latest version when has no designs toolbar buttons are invisible', () => {
+ createComponent({ designs: [], allVersions: [mockVersion] });
+ expect(findToolbar().isVisible()).toBe(false);
+ });
+
+ describe('on non-latest version', () => {
+ beforeEach(() => {
+ createComponent({ designs: mockDesigns, allVersions: [mockVersion] });
+
+ router.replace({
+ name: DESIGNS_ROUTE_NAME,
+ query: {
+ version: '2',
+ },
+ });
+ });
+
+ it('does not render design checkboxes', () => {
+ expect(findDesignCheckboxes()).toHaveLength(0);
+ });
+
+ it('does not render Delete selected button', () => {
+ expect(findDeleteButton().exists()).toBe(false);
+ });
+
+ it('does not render Select All button', () => {
+ expect(findSelectAllButton().exists()).toBe(false);
+ });
+ });
+
+ describe('pasting a design', () => {
+ let event;
+ beforeEach(() => {
+ createComponent({ designs: mockDesigns, allVersions: [mockVersion] });
+
+ wrapper.setMethods({
+ onUploadDesign: jest.fn(),
+ });
+
+ event = new Event('paste');
+
+ router.replace({
+ name: DESIGNS_ROUTE_NAME,
+ query: {
+ version: '2',
+ },
+ });
+ });
+
+ it('calls onUploadDesign with valid paste', () => {
+ event.clipboardData = {
+ files: [{ name: 'image.png', type: 'image/png' }],
+ getData: () => 'test.png',
+ };
+
+ document.dispatchEvent(event);
+
+ expect(wrapper.vm.onUploadDesign).toHaveBeenCalledTimes(1);
+ expect(wrapper.vm.onUploadDesign).toHaveBeenCalledWith([
+ new File([{ name: 'image.png' }], 'test.png'),
+ ]);
+ });
+
+ it('renames a design if it has an image.png filename', () => {
+ event.clipboardData = {
+ files: [{ name: 'image.png', type: 'image/png' }],
+ getData: () => 'image.png',
+ };
+
+ document.dispatchEvent(event);
+
+ expect(wrapper.vm.onUploadDesign).toHaveBeenCalledTimes(1);
+ expect(wrapper.vm.onUploadDesign).toHaveBeenCalledWith([
+ new File([{ name: 'image.png' }], `design_${Date.now()}.png`),
+ ]);
+ });
+
+ it('does not call onUploadDesign with invalid paste', () => {
+ event.clipboardData = {
+ items: [{ type: 'text/plain' }, { type: 'text' }],
+ files: [],
+ };
+
+ document.dispatchEvent(event);
+
+ expect(wrapper.vm.onUploadDesign).not.toHaveBeenCalled();
+ });
+ });
+
+ describe('when navigating', () => {
+ it('ensures fullscreen layout is not applied', () => {
+ createComponent(true);
+
+ wrapper.vm.$router.push('/');
+ expect(mockPageEl.classList.remove).toHaveBeenCalledTimes(1);
+ expect(mockPageEl.classList.remove).toHaveBeenCalledWith(...DESIGN_DETAIL_LAYOUT_CLASSLIST);
+ });
+ });
+});
diff --git a/spec/frontend/design_management_new/router_spec.js b/spec/frontend/design_management_new/router_spec.js
new file mode 100644
index 00000000000..4d63e622724
--- /dev/null
+++ b/spec/frontend/design_management_new/router_spec.js
@@ -0,0 +1,70 @@
+import { mount, createLocalVue } from '@vue/test-utils';
+import { nextTick } from 'vue';
+import VueRouter from 'vue-router';
+import App from '~/design_management_new/components/app.vue';
+import Designs from '~/design_management_new/pages/index.vue';
+import DesignDetail from '~/design_management_new/pages/design/index.vue';
+import createRouter from '~/design_management_new/router';
+import { DESIGNS_ROUTE_NAME, DESIGN_ROUTE_NAME } from '~/design_management_new/router/constants';
+import '~/commons/bootstrap';
+
+function factory(routeArg) {
+ const localVue = createLocalVue();
+ localVue.use(VueRouter);
+
+ window.gon = { sprite_icons: '' };
+
+ const router = createRouter('/');
+ if (routeArg !== undefined) {
+ router.push(routeArg);
+ }
+
+ return mount(App, {
+ localVue,
+ router,
+ mocks: {
+ $apollo: {
+ queries: {
+ designs: { loading: true },
+ design: { loading: true },
+ permissions: { loading: true },
+ },
+ mutate: jest.fn(),
+ },
+ },
+ });
+}
+
+jest.mock('mousetrap', () => ({
+ bind: jest.fn(),
+ unbind: jest.fn(),
+}));
+
+describe('Design management router', () => {
+ afterEach(() => {
+ window.location.hash = '';
+ });
+
+ describe.each([['/'], [{ name: DESIGNS_ROUTE_NAME }]])('root route', routeArg => {
+ it('pushes home component', () => {
+ const wrapper = factory(routeArg);
+
+ expect(wrapper.find(Designs).exists()).toBe(true);
+ });
+ });
+
+ describe.each([['/designs/1'], [{ name: DESIGN_ROUTE_NAME, params: { id: '1' } }]])(
+ 'designs detail route',
+ routeArg => {
+ it('pushes designs detail component', () => {
+ const wrapper = factory(routeArg);
+
+ return nextTick().then(() => {
+ const detail = wrapper.find(DesignDetail);
+ expect(detail.exists()).toBe(true);
+ expect(detail.props('id')).toEqual('1');
+ });
+ });
+ },
+ );
+});
diff --git a/spec/frontend/design_management_new/utils/cache_update_spec.js b/spec/frontend/design_management_new/utils/cache_update_spec.js
new file mode 100644
index 00000000000..611716d5aa7
--- /dev/null
+++ b/spec/frontend/design_management_new/utils/cache_update_spec.js
@@ -0,0 +1,44 @@
+import { InMemoryCache } from 'apollo-cache-inmemory';
+import {
+ updateStoreAfterDesignsDelete,
+ updateStoreAfterAddDiscussionComment,
+ updateStoreAfterAddImageDiffNote,
+ updateStoreAfterUploadDesign,
+ updateStoreAfterUpdateImageDiffNote,
+} from '~/design_management_new/utils/cache_update';
+import {
+ designDeletionError,
+ ADD_DISCUSSION_COMMENT_ERROR,
+ ADD_IMAGE_DIFF_NOTE_ERROR,
+ UPDATE_IMAGE_DIFF_NOTE_ERROR,
+} from '~/design_management_new/utils/error_messages';
+import design from '../mock_data/design';
+import createFlash from '~/flash';
+
+jest.mock('~/flash.js');
+
+describe('Design Management cache update', () => {
+ const mockErrors = ['code red!'];
+
+ let mockStore;
+
+ beforeEach(() => {
+ mockStore = new InMemoryCache();
+ });
+
+ describe('error handling', () => {
+ it.each`
+ fnName | subject | errorMessage | extraArgs
+ ${'updateStoreAfterDesignsDelete'} | ${updateStoreAfterDesignsDelete} | ${designDeletionError({ singular: true })} | ${[[design]]}
+ ${'updateStoreAfterAddDiscussionComment'} | ${updateStoreAfterAddDiscussionComment} | ${ADD_DISCUSSION_COMMENT_ERROR} | ${[]}
+ ${'updateStoreAfterAddImageDiffNote'} | ${updateStoreAfterAddImageDiffNote} | ${ADD_IMAGE_DIFF_NOTE_ERROR} | ${[]}
+ ${'updateStoreAfterUploadDesign'} | ${updateStoreAfterUploadDesign} | ${mockErrors[0]} | ${[]}
+ ${'updateStoreAfterUpdateImageDiffNote'} | ${updateStoreAfterUpdateImageDiffNote} | ${UPDATE_IMAGE_DIFF_NOTE_ERROR} | ${[]}
+ `('$fnName handles errors in response', ({ subject, extraArgs, errorMessage }) => {
+ expect(createFlash).not.toHaveBeenCalled();
+ expect(() => subject(mockStore, { errors: mockErrors }, {}, ...extraArgs)).toThrow();
+ expect(createFlash).toHaveBeenCalledTimes(1);
+ expect(createFlash).toHaveBeenCalledWith(errorMessage);
+ });
+ });
+});
diff --git a/spec/frontend/design_management_new/utils/design_management_utils_spec.js b/spec/frontend/design_management_new/utils/design_management_utils_spec.js
new file mode 100644
index 00000000000..8bc33e214be
--- /dev/null
+++ b/spec/frontend/design_management_new/utils/design_management_utils_spec.js
@@ -0,0 +1,176 @@
+import {
+ extractCurrentDiscussion,
+ extractDiscussions,
+ findVersionId,
+ designUploadOptimisticResponse,
+ updateImageDiffNoteOptimisticResponse,
+ isValidDesignFile,
+ extractDesign,
+} from '~/design_management_new/utils/design_management_utils';
+import mockResponseNoDesigns from '../mock_data/no_designs';
+import mockResponseWithDesigns from '../mock_data/designs';
+import mockDesign from '../mock_data/design';
+
+jest.mock('lodash/uniqueId', () => () => 1);
+
+describe('extractCurrentDiscussion', () => {
+ let discussions;
+
+ beforeEach(() => {
+ discussions = {
+ nodes: [
+ { id: 101, payload: 'w' },
+ { id: 102, payload: 'x' },
+ { id: 103, payload: 'y' },
+ { id: 104, payload: 'z' },
+ ],
+ };
+ });
+
+ it('finds the relevant discussion if it exists', () => {
+ const id = 103;
+ expect(extractCurrentDiscussion(discussions, id)).toEqual({ id, payload: 'y' });
+ });
+
+ it('returns null if the relevant discussion does not exist', () => {
+ expect(extractCurrentDiscussion(discussions, 0)).not.toBeDefined();
+ });
+});
+
+describe('extractDiscussions', () => {
+ let discussions;
+
+ beforeEach(() => {
+ discussions = {
+ nodes: [
+ { id: 1, notes: { nodes: ['a'] } },
+ { id: 2, notes: { nodes: ['b'] } },
+ { id: 3, notes: { nodes: ['c'] } },
+ { id: 4, notes: { nodes: ['d'] } },
+ ],
+ };
+ });
+
+ it('discards the edges.node artifacts of GraphQL', () => {
+ expect(extractDiscussions(discussions)).toEqual([
+ { id: 1, notes: ['a'], index: 1 },
+ { id: 2, notes: ['b'], index: 2 },
+ { id: 3, notes: ['c'], index: 3 },
+ { id: 4, notes: ['d'], index: 4 },
+ ]);
+ });
+});
+
+describe('version parser', () => {
+ it('correctly extracts version ID from a valid version string', () => {
+ const testVersionId = '123';
+ const testVersionString = `gid://gitlab/DesignManagement::Version/${testVersionId}`;
+
+ expect(findVersionId(testVersionString)).toEqual(testVersionId);
+ });
+
+ it('fails to extract version ID from an invalid version string', () => {
+ const testInvalidVersionString = `gid://gitlab/DesignManagement::Version`;
+
+ expect(findVersionId(testInvalidVersionString)).toBeUndefined();
+ });
+});
+
+describe('optimistic responses', () => {
+ it('correctly generated for designManagementUpload', () => {
+ const expectedResponse = {
+ __typename: 'Mutation',
+ designManagementUpload: {
+ __typename: 'DesignManagementUploadPayload',
+ designs: [
+ {
+ __typename: 'Design',
+ id: -1,
+ image: '',
+ imageV432x230: '',
+ filename: 'test',
+ fullPath: '',
+ notesCount: 0,
+ event: 'NONE',
+ diffRefs: { __typename: 'DiffRefs', baseSha: '', startSha: '', headSha: '' },
+ discussions: { __typename: 'DesignDiscussion', nodes: [] },
+ versions: {
+ __typename: 'DesignVersionConnection',
+ edges: {
+ __typename: 'DesignVersionEdge',
+ node: { __typename: 'DesignVersion', id: -1, sha: -1 },
+ },
+ },
+ },
+ ],
+ errors: [],
+ skippedDesigns: [],
+ },
+ };
+ expect(designUploadOptimisticResponse([{ name: 'test' }])).toEqual(expectedResponse);
+ });
+
+ it('correctly generated for updateImageDiffNoteOptimisticResponse', () => {
+ const mockNote = {
+ id: 'test-note-id',
+ };
+
+ const mockPosition = {
+ x: 10,
+ y: 10,
+ width: 10,
+ height: 10,
+ };
+
+ const expectedResponse = {
+ __typename: 'Mutation',
+ updateImageDiffNote: {
+ __typename: 'UpdateImageDiffNotePayload',
+ note: {
+ ...mockNote,
+ position: mockPosition,
+ },
+ errors: [],
+ },
+ };
+ expect(updateImageDiffNoteOptimisticResponse(mockNote, { position: mockPosition })).toEqual(
+ expectedResponse,
+ );
+ });
+});
+
+describe('isValidDesignFile', () => {
+ // test every filetype that Design Management supports
+ // https://docs.gitlab.com/ee/user/project/issues/design_management.html#limitations
+ it.each`
+ mimetype | isValid
+ ${'image/svg'} | ${true}
+ ${'image/png'} | ${true}
+ ${'image/jpg'} | ${true}
+ ${'image/jpeg'} | ${true}
+ ${'image/gif'} | ${true}
+ ${'image/bmp'} | ${true}
+ ${'image/tiff'} | ${true}
+ ${'image/ico'} | ${true}
+ ${'image/svg'} | ${true}
+ ${'video/mpeg'} | ${false}
+ ${'audio/midi'} | ${false}
+ ${'application/octet-stream'} | ${false}
+ `('returns $isValid for file type $mimetype', ({ mimetype, isValid }) => {
+ expect(isValidDesignFile({ type: mimetype })).toBe(isValid);
+ });
+});
+
+describe('extractDesign', () => {
+ describe('with no designs', () => {
+ it('returns undefined', () => {
+ expect(extractDesign(mockResponseNoDesigns)).toBeUndefined();
+ });
+ });
+
+ describe('with designs', () => {
+ it('returns the first design available', () => {
+ expect(extractDesign(mockResponseWithDesigns)).toEqual(mockDesign);
+ });
+ });
+});
diff --git a/spec/frontend/design_management_new/utils/error_messages_spec.js b/spec/frontend/design_management_new/utils/error_messages_spec.js
new file mode 100644
index 00000000000..eb5dc0fad20
--- /dev/null
+++ b/spec/frontend/design_management_new/utils/error_messages_spec.js
@@ -0,0 +1,62 @@
+import {
+ designDeletionError,
+ designUploadSkippedWarning,
+} from '~/design_management_new/utils/error_messages';
+
+const mockFilenames = n =>
+ Array(n)
+ .fill(0)
+ .map((_, i) => ({ filename: `${i + 1}.jpg` }));
+
+describe('Error message', () => {
+ describe('designDeletionError', () => {
+ const singularMsg = 'Could not delete a design. Please try again.';
+ const pluralMsg = 'Could not delete designs. Please try again.';
+
+ describe('when [singular=true]', () => {
+ it.each([[undefined], [true]])('uses singular grammar', singularOption => {
+ expect(designDeletionError({ singular: singularOption })).toEqual(singularMsg);
+ });
+ });
+
+ describe('when [singular=false]', () => {
+ it('uses plural grammar', () => {
+ expect(designDeletionError({ singular: false })).toEqual(pluralMsg);
+ });
+ });
+ });
+
+ describe.each([
+ [[], [], null],
+ [mockFilenames(1), mockFilenames(1), 'Upload skipped. 1.jpg did not change.'],
+ [
+ mockFilenames(2),
+ mockFilenames(2),
+ 'Upload skipped. The designs you tried uploading did not change.',
+ ],
+ [
+ mockFilenames(2),
+ mockFilenames(1),
+ 'Upload skipped. Some of the designs you tried uploading did not change: 1.jpg.',
+ ],
+ [
+ mockFilenames(6),
+ mockFilenames(5),
+ 'Upload skipped. Some of the designs you tried uploading did not change: 1.jpg, 2.jpg, 3.jpg, 4.jpg, 5.jpg.',
+ ],
+ [
+ mockFilenames(7),
+ mockFilenames(6),
+ 'Upload skipped. Some of the designs you tried uploading did not change: 1.jpg, 2.jpg, 3.jpg, 4.jpg, 5.jpg, and 1 more.',
+ ],
+ [
+ mockFilenames(8),
+ mockFilenames(7),
+ 'Upload skipped. Some of the designs you tried uploading did not change: 1.jpg, 2.jpg, 3.jpg, 4.jpg, 5.jpg, and 2 more.',
+ ],
+ ])('designUploadSkippedWarning', (uploadedFiles, skippedFiles, expected) => {
+ test('returns expected warning message', () => {
+ expect(designUploadSkippedWarning(uploadedFiles, skippedFiles)).toBe(expected);
+ });
+ });
+});
diff --git a/spec/frontend/design_management_new/utils/tracking_spec.js b/spec/frontend/design_management_new/utils/tracking_spec.js
new file mode 100644
index 00000000000..ac7267642cb
--- /dev/null
+++ b/spec/frontend/design_management_new/utils/tracking_spec.js
@@ -0,0 +1,59 @@
+import { mockTracking } from 'helpers/tracking_helper';
+import { trackDesignDetailView } from '~/design_management_new/utils/tracking';
+
+function getTrackingSpy(key) {
+ return mockTracking(key, undefined, jest.spyOn);
+}
+
+describe('Tracking Events', () => {
+ describe('trackDesignDetailView', () => {
+ const eventKey = 'projects:issues:design';
+ const eventName = 'view_design';
+
+ it('trackDesignDetailView fires a tracking event when called', () => {
+ const trackingSpy = getTrackingSpy(eventKey);
+
+ trackDesignDetailView();
+
+ expect(trackingSpy).toHaveBeenCalledWith(
+ eventKey,
+ eventName,
+ expect.objectContaining({
+ label: eventName,
+ context: {
+ schema: expect.any(String),
+ data: {
+ 'design-version-number': 1,
+ 'design-is-current-version': false,
+ 'internal-object-referrer': '',
+ 'design-collection-owner': '',
+ },
+ },
+ }),
+ );
+ });
+
+ it('trackDesignDetailView allows to customize the value payload', () => {
+ const trackingSpy = getTrackingSpy(eventKey);
+
+ trackDesignDetailView('from-a-test', 'test', 100, true);
+
+ expect(trackingSpy).toHaveBeenCalledWith(
+ eventKey,
+ eventName,
+ expect.objectContaining({
+ label: eventName,
+ context: {
+ schema: expect.any(String),
+ data: {
+ 'design-version-number': 100,
+ 'design-is-current-version': true,
+ 'internal-object-referrer': 'from-a-test',
+ 'design-collection-owner': 'test',
+ },
+ },
+ }),
+ );
+ });
+ });
+});
diff --git a/spec/frontend/diffs/components/app_spec.js b/spec/frontend/diffs/components/app_spec.js
index 57e3a93c6f4..b7f03f35dfb 100644
--- a/spec/frontend/diffs/components/app_spec.js
+++ b/spec/frontend/diffs/components/app_spec.js
@@ -56,6 +56,7 @@ describe('diffs/components/app', () => {
changesEmptyStateIllustration: '',
dismissEndpoint: '',
showSuggestPopover: true,
+ viewDiffsFileByFile: false,
...props,
},
provide,
@@ -829,4 +830,58 @@ describe('diffs/components/app', () => {
expect(toggleShowTreeList).not.toHaveBeenCalled();
});
});
+
+ describe('file-by-file', () => {
+ it('renders a single diff', () => {
+ createComponent({ viewDiffsFileByFile: true }, ({ state }) => {
+ state.diffs.diffFiles.push({ file_hash: '123' });
+ state.diffs.diffFiles.push({ file_hash: '312' });
+ });
+
+ expect(wrapper.findAll(DiffFile).length).toBe(1);
+ });
+
+ describe('pagination', () => {
+ it('sets previous button as disabled', () => {
+ createComponent({ viewDiffsFileByFile: true }, ({ state }) => {
+ state.diffs.diffFiles.push({ file_hash: '123' }, { file_hash: '312' });
+ });
+
+ expect(wrapper.find('[data-testid="singleFilePrevious"]').props('disabled')).toBe(true);
+ expect(wrapper.find('[data-testid="singleFileNext"]').props('disabled')).toBe(false);
+ });
+
+ it('sets next button as disabled', () => {
+ createComponent({ viewDiffsFileByFile: true }, ({ state }) => {
+ state.diffs.diffFiles.push({ file_hash: '123' }, { file_hash: '312' });
+ state.diffs.currentDiffFileId = '312';
+ });
+
+ expect(wrapper.find('[data-testid="singleFilePrevious"]').props('disabled')).toBe(false);
+ expect(wrapper.find('[data-testid="singleFileNext"]').props('disabled')).toBe(true);
+ });
+
+ it.each`
+ currentDiffFileId | button | index
+ ${'123'} | ${'singleFileNext'} | ${1}
+ ${'312'} | ${'singleFilePrevious'} | ${0}
+ `(
+ 'it calls navigateToDiffFileIndex with $index when $button is clicked',
+ ({ currentDiffFileId, button, index }) => {
+ createComponent({ viewDiffsFileByFile: true }, ({ state }) => {
+ state.diffs.diffFiles.push({ file_hash: '123' }, { file_hash: '312' });
+ state.diffs.currentDiffFileId = currentDiffFileId;
+ });
+
+ jest.spyOn(wrapper.vm, 'navigateToDiffFileIndex');
+
+ wrapper.find(`[data-testid="${button}"]`).vm.$emit('click');
+
+ return wrapper.vm.$nextTick().then(() => {
+ expect(wrapper.vm.navigateToDiffFileIndex).toHaveBeenCalledWith(index);
+ });
+ },
+ );
+ });
+ });
});
diff --git a/spec/frontend/diffs/components/diff_expansion_cell_spec.js b/spec/frontend/diffs/components/diff_expansion_cell_spec.js
index 0504f3933e0..ef2e0dfe59b 100644
--- a/spec/frontend/diffs/components/diff_expansion_cell_spec.js
+++ b/spec/frontend/diffs/components/diff_expansion_cell_spec.js
@@ -6,10 +6,10 @@ import DiffExpansionCell from '~/diffs/components/diff_expansion_cell.vue';
import { getPreviousLineIndex } from '~/diffs/store/utils';
import { INLINE_DIFF_VIEW_TYPE, PARALLEL_DIFF_VIEW_TYPE } from '~/diffs/constants';
import diffFileMockData from '../mock_data/diff_file';
+import { getByText } from '@testing-library/dom';
const EXPAND_UP_CLASS = '.js-unfold';
const EXPAND_DOWN_CLASS = '.js-unfold-down';
-const EXPAND_ALL_CLASS = '.js-unfold-all';
const LINE_TO_USE = 5;
const lineSources = {
[INLINE_DIFF_VIEW_TYPE]: 'highlighted_diff_lines',
@@ -88,7 +88,7 @@ describe('DiffExpansionCell', () => {
const findExpandUp = () => vm.$el.querySelector(EXPAND_UP_CLASS);
const findExpandDown = () => vm.$el.querySelector(EXPAND_DOWN_CLASS);
- const findExpandAll = () => vm.$el.querySelector(EXPAND_ALL_CLASS);
+ const findExpandAll = () => getByText(vm.$el, 'Show unchanged lines');
describe('top row', () => {
it('should have "expand up" and "show all" option', () => {
diff --git a/spec/frontend/diffs/components/diff_file_header_spec.js b/spec/frontend/diffs/components/diff_file_header_spec.js
index e0b7e0bc0f3..671dced080c 100644
--- a/spec/frontend/diffs/components/diff_file_header_spec.js
+++ b/spec/frontend/diffs/components/diff_file_header_spec.js
@@ -87,6 +87,7 @@ describe('DiffFileHeader component', () => {
propsData: {
diffFile,
canCurrentUserFork: false,
+ viewDiffsFileByFile: false,
...props,
},
localVue,
diff --git a/spec/frontend/diffs/components/diff_file_row_spec.js b/spec/frontend/diffs/components/diff_file_row_spec.js
index 856622b89cb..afdd4bfb335 100644
--- a/spec/frontend/diffs/components/diff_file_row_spec.js
+++ b/spec/frontend/diffs/components/diff_file_row_spec.js
@@ -73,4 +73,15 @@ describe('Diff File Row component', () => {
expect(wrapper.find(FileRowStats).exists()).toEqual(value);
});
});
+
+ it('adds is-active class when currentDiffFileId matches file_hash', () => {
+ createComponent({
+ level: 0,
+ currentDiffFileId: '123',
+ file: { fileHash: '123' },
+ hideFileStats: false,
+ });
+
+ expect(wrapper.classes('is-active')).toBe(true);
+ });
});
diff --git a/spec/frontend/diffs/components/diff_file_spec.js b/spec/frontend/diffs/components/diff_file_spec.js
index 71e975f2409..7e154d76f45 100644
--- a/spec/frontend/diffs/components/diff_file_spec.js
+++ b/spec/frontend/diffs/components/diff_file_spec.js
@@ -15,6 +15,7 @@ describe('DiffFile', () => {
vm = createComponentWithStore(Vue.extend(DiffFileComponent), createStore(), {
file: JSON.parse(JSON.stringify(diffFileMockDataReadable)),
canCurrentUserFork: false,
+ viewDiffsFileByFile: false,
}).$mount();
trackingSpy = mockTracking('_category_', vm.$el, jest.spyOn);
});
@@ -113,6 +114,7 @@ describe('DiffFile', () => {
vm = createComponentWithStore(Vue.extend(DiffFileComponent), createStore(), {
file: JSON.parse(JSON.stringify(diffFileMockDataUnreadable)),
canCurrentUserFork: false,
+ viewDiffsFileByFile: false,
}).$mount();
vm.renderIt = false;
@@ -235,6 +237,7 @@ describe('DiffFile', () => {
vm = createComponentWithStore(Vue.extend(DiffFileComponent), createStore(), {
file: JSON.parse(JSON.stringify(diffFileMockDataUnreadable)),
canCurrentUserFork: false,
+ viewDiffsFileByFile: false,
}).$mount();
jest.spyOn(vm, 'handleLoadCollapsedDiff').mockImplementation(() => {});
diff --git a/spec/frontend/diffs/components/diff_gutter_avatars_spec.js b/spec/frontend/diffs/components/diff_gutter_avatars_spec.js
index da18d8e7894..61e110b345a 100644
--- a/spec/frontend/diffs/components/diff_gutter_avatars_spec.js
+++ b/spec/frontend/diffs/components/diff_gutter_avatars_spec.js
@@ -110,7 +110,7 @@ describe('DiffGutterAvatars', () => {
it('returns truncated version of comment if it is longer than max length', () => {
const note = wrapper.vm.discussions[0].notes[1];
- expect(wrapper.vm.getTooltipText(note)).toEqual('Fatih Acet: comment 2 is r...');
+ expect(wrapper.vm.getTooltipText(note)).toEqual('Fatih Acet: comment 2 is rea…');
});
});
});
diff --git a/spec/frontend/diffs/components/diff_line_note_form_spec.js b/spec/frontend/diffs/components/diff_line_note_form_spec.js
index 623df8bd55e..75ec5c202af 100644
--- a/spec/frontend/diffs/components/diff_line_note_form_spec.js
+++ b/spec/frontend/diffs/components/diff_line_note_form_spec.js
@@ -78,10 +78,18 @@ describe('DiffLineNoteForm', () => {
.mockReturnValue(Promise.resolve());
const lineRange = {
- start_line_code: wrapper.vm.commentLineStart.lineCode,
- start_line_type: wrapper.vm.commentLineStart.type,
- end_line_code: wrapper.vm.line.line_code,
- end_line_type: wrapper.vm.line.type,
+ start: {
+ line_code: wrapper.vm.commentLineStart.line_code,
+ type: wrapper.vm.commentLineStart.type,
+ new_line: 1,
+ old_line: null,
+ },
+ end: {
+ line_code: wrapper.vm.line.line_code,
+ type: wrapper.vm.line.type,
+ new_line: 1,
+ old_line: null,
+ },
};
const formData = {
diff --git a/spec/frontend/diffs/components/diff_table_cell_spec.js b/spec/frontend/diffs/components/diff_table_cell_spec.js
index e871d86d901..9693fe68b57 100644
--- a/spec/frontend/diffs/components/diff_table_cell_spec.js
+++ b/spec/frontend/diffs/components/diff_table_cell_spec.js
@@ -100,7 +100,11 @@ describe('DiffTableCell', () => {
setWindowLocation({ href: `${TEST_HOST}?${query}` });
createComponent({ showCommentButton });
- expect(findNoteButton().exists()).toBe(expectation);
+ wrapper.setData({ isCommentButtonRendered: showCommentButton });
+
+ return wrapper.vm.$nextTick().then(() => {
+ expect(findNoteButton().exists()).toBe(expectation);
+ });
},
);
@@ -108,7 +112,6 @@ describe('DiffTableCell', () => {
isHover | otherProps | discussions | expectation
${true} | ${{}} | ${[]} | ${true}
${false} | ${{}} | ${[]} | ${false}
- ${true} | ${{ line: { ...line, type: 'match' } }} | ${[]} | ${false}
${true} | ${{ line: { ...line, type: 'context' } }} | ${[]} | ${false}
${true} | ${{ line: { ...line, type: 'old-nonewline' } }} | ${[]} | ${false}
${true} | ${{}} | ${[{}]} | ${false}
@@ -122,7 +125,13 @@ describe('DiffTableCell', () => {
...otherProps,
});
- expect(findNoteButton().isVisible()).toBe(expectation);
+ wrapper.setData({
+ isCommentButtonRendered: true,
+ });
+
+ return wrapper.vm.$nextTick().then(() => {
+ expect(findNoteButton().isVisible()).toBe(expectation);
+ });
},
);
});
diff --git a/spec/frontend/diffs/components/inline_diff_table_row_spec.js b/spec/frontend/diffs/components/inline_diff_table_row_spec.js
index 66349727b11..f929f97b598 100644
--- a/spec/frontend/diffs/components/inline_diff_table_row_spec.js
+++ b/spec/frontend/diffs/components/inline_diff_table_row_spec.js
@@ -1,27 +1,31 @@
-import Vue from 'vue';
-import { createComponentWithStore } from 'helpers/vue_mount_component_helper';
+import { shallowMount } from '@vue/test-utils';
import { createStore } from '~/mr_notes/stores';
import InlineDiffTableRow from '~/diffs/components/inline_diff_table_row.vue';
import diffFileMockData from '../mock_data/diff_file';
describe('InlineDiffTableRow', () => {
+ let wrapper;
let vm;
const thisLine = diffFileMockData.highlighted_diff_lines[0];
beforeEach(() => {
- vm = createComponentWithStore(Vue.extend(InlineDiffTableRow), createStore(), {
- line: thisLine,
- fileHash: diffFileMockData.file_hash,
- filePath: diffFileMockData.file_path,
- contextLinesPath: 'contextLinesPath',
- isHighlighted: false,
- }).$mount();
+ wrapper = shallowMount(InlineDiffTableRow, {
+ store: createStore(),
+ propsData: {
+ line: thisLine,
+ fileHash: diffFileMockData.file_hash,
+ filePath: diffFileMockData.file_path,
+ contextLinesPath: 'contextLinesPath',
+ isHighlighted: false,
+ },
+ });
+ vm = wrapper.vm;
});
it('does not add hll class to line content when line does not match highlighted row', done => {
vm.$nextTick()
.then(() => {
- expect(vm.$el.querySelector('.line_content').classList).not.toContain('hll');
+ expect(wrapper.find('.line_content').classes('hll')).toBe(false);
})
.then(done)
.catch(done.fail);
@@ -35,12 +39,19 @@ describe('InlineDiffTableRow', () => {
return vm.$nextTick();
})
.then(() => {
- expect(vm.$el.querySelector('.line_content').classList).toContain('hll');
+ expect(wrapper.find('.line_content').classes('hll')).toBe(true);
})
.then(done)
.catch(done.fail);
});
+ it('adds hll class to lineContent when line is part of a multiline comment', () => {
+ wrapper.setProps({ isCommented: true });
+ return vm.$nextTick().then(() => {
+ expect(wrapper.find('.line_content').classes('hll')).toBe(true);
+ });
+ });
+
describe('sets coverage title and class', () => {
it('for lines with coverage', done => {
vm.$nextTick()
@@ -53,10 +64,10 @@ describe('InlineDiffTableRow', () => {
return vm.$nextTick();
})
.then(() => {
- const coverage = vm.$el.querySelector('.line-coverage');
+ const coverage = wrapper.find('.line-coverage');
- expect(coverage.title).toContain('Test coverage: 5 hits');
- expect(coverage.classList).toContain('coverage');
+ expect(coverage.attributes('title')).toContain('Test coverage: 5 hits');
+ expect(coverage.classes('coverage')).toBe(true);
})
.then(done)
.catch(done.fail);
@@ -73,10 +84,10 @@ describe('InlineDiffTableRow', () => {
return vm.$nextTick();
})
.then(() => {
- const coverage = vm.$el.querySelector('.line-coverage');
+ const coverage = wrapper.find('.line-coverage');
- expect(coverage.title).toContain('No test coverage');
- expect(coverage.classList).toContain('no-coverage');
+ expect(coverage.attributes('title')).toContain('No test coverage');
+ expect(coverage.classes('no-coverage')).toBe(true);
})
.then(done)
.catch(done.fail);
@@ -90,11 +101,11 @@ describe('InlineDiffTableRow', () => {
return vm.$nextTick();
})
.then(() => {
- const coverage = vm.$el.querySelector('.line-coverage');
+ const coverage = wrapper.find('.line-coverage');
- expect(coverage.title).not.toContain('Coverage');
- expect(coverage.classList).not.toContain('coverage');
- expect(coverage.classList).not.toContain('no-coverage');
+ expect(coverage.attributes('title')).toBeUndefined();
+ expect(coverage.classes('coverage')).toBe(false);
+ expect(coverage.classes('no-coverage')).toBe(false);
})
.then(done)
.catch(done.fail);
diff --git a/spec/frontend/diffs/components/no_changes_spec.js b/spec/frontend/diffs/components/no_changes_spec.js
index 245651af61c..2eca97a47fd 100644
--- a/spec/frontend/diffs/components/no_changes_spec.js
+++ b/spec/frontend/diffs/components/no_changes_spec.js
@@ -2,6 +2,7 @@ import { createLocalVue, shallowMount } from '@vue/test-utils';
import Vuex from 'vuex';
import { createStore } from '~/mr_notes/stores';
import NoChanges from '~/diffs/components/no_changes.vue';
+import { GlButton } from '@gitlab/ui';
describe('Diff no changes empty state', () => {
let vm;
@@ -37,4 +38,11 @@ describe('Diff no changes empty state', () => {
expect(vm.contains('script')).toBe(false);
});
+
+ describe('Renders', () => {
+ it('Show create commit button', () => {
+ createComponent();
+ expect(vm.find(GlButton).exists()).toBe(true);
+ });
+ });
});
diff --git a/spec/frontend/diffs/components/parallel_diff_table_row_spec.js b/spec/frontend/diffs/components/parallel_diff_table_row_spec.js
index 6b92d448cf5..339352943a9 100644
--- a/spec/frontend/diffs/components/parallel_diff_table_row_spec.js
+++ b/spec/frontend/diffs/components/parallel_diff_table_row_spec.js
@@ -1,4 +1,5 @@
import Vue from 'vue';
+import { shallowMount } from '@vue/test-utils';
import { createComponentWithStore } from 'helpers/vue_mount_component_helper';
import { createStore } from '~/mr_notes/stores';
import ParallelDiffTableRow from '~/diffs/components/parallel_diff_table_row.vue';
@@ -6,18 +7,24 @@ import diffFileMockData from '../mock_data/diff_file';
describe('ParallelDiffTableRow', () => {
describe('when one side is empty', () => {
+ let wrapper;
let vm;
const thisLine = diffFileMockData.parallel_diff_lines[0];
const rightLine = diffFileMockData.parallel_diff_lines[0].right;
beforeEach(() => {
- vm = createComponentWithStore(Vue.extend(ParallelDiffTableRow), createStore(), {
- line: thisLine,
- fileHash: diffFileMockData.file_hash,
- filePath: diffFileMockData.file_path,
- contextLinesPath: 'contextLinesPath',
- isHighlighted: false,
- }).$mount();
+ wrapper = shallowMount(ParallelDiffTableRow, {
+ store: createStore(),
+ propsData: {
+ line: thisLine,
+ fileHash: diffFileMockData.file_hash,
+ filePath: diffFileMockData.file_path,
+ contextLinesPath: 'contextLinesPath',
+ isHighlighted: false,
+ },
+ });
+
+ vm = wrapper.vm;
});
it('does not highlight non empty line content when line does not match highlighted row', done => {
@@ -42,6 +49,13 @@ describe('ParallelDiffTableRow', () => {
.then(done)
.catch(done.fail);
});
+
+ it('highlights nonempty line content when line is part of a multiline comment', () => {
+ wrapper.setProps({ isCommented: true });
+ return vm.$nextTick().then(() => {
+ expect(vm.$el.querySelector('.line_content.right-side').classList).toContain('hll');
+ });
+ });
});
describe('when both sides have content', () => {
diff --git a/spec/frontend/diffs/store/actions_spec.js b/spec/frontend/diffs/store/actions_spec.js
index 7d79dcfbfe3..ec6ad031813 100644
--- a/spec/frontend/diffs/store/actions_spec.js
+++ b/spec/frontend/diffs/store/actions_spec.js
@@ -6,6 +6,8 @@ import {
INLINE_DIFF_VIEW_TYPE,
PARALLEL_DIFF_VIEW_TYPE,
DIFFS_PER_PAGE,
+ DIFF_WHITESPACE_COOKIE_NAME,
+ SHOW_WHITESPACE,
} from '~/diffs/constants';
import {
setBaseConfig,
@@ -44,6 +46,8 @@ import {
setSuggestPopoverDismissed,
changeCurrentCommit,
moveToNeighboringCommit,
+ setCurrentDiffFileIdFromNote,
+ navigateToDiffFileIndex,
} from '~/diffs/store/actions';
import eventHub from '~/notes/event_hub';
import * as types from '~/diffs/store/mutation_types';
@@ -55,6 +59,7 @@ import { mergeUrlParams } from '~/lib/utils/url_utility';
import { useLocalStorageSpy } from 'helpers/local_storage_helper';
import { diffMetadata } from '../mock_data/diff_metadata';
import createFlash from '~/flash';
+import { TEST_HOST } from 'jest/helpers/test_constants';
jest.mock('~/flash', () => jest.fn());
@@ -187,8 +192,8 @@ describe('DiffsStoreActions', () => {
it('should fetch batch diff files', done => {
const endpointBatch = '/fetch/diffs_batch';
- const res1 = { diff_files: [], pagination: { next_page: 2 } };
- const res2 = { diff_files: [], pagination: {} };
+ const res1 = { diff_files: [{ file_hash: 'test' }], pagination: { next_page: 2 } };
+ const res2 = { diff_files: [{ file_hash: 'test2' }], pagination: {} };
mock
.onGet(
mergeUrlParams(
@@ -224,8 +229,10 @@ describe('DiffsStoreActions', () => {
{ type: types.SET_RETRIEVING_BATCHES, payload: true },
{ type: types.SET_DIFF_DATA_BATCH, payload: { diff_files: res1.diff_files } },
{ type: types.SET_BATCH_LOADING, payload: false },
- { type: types.SET_DIFF_DATA_BATCH, payload: { diff_files: [] } },
+ { type: types.UPDATE_CURRENT_DIFF_FILE_ID, payload: 'test' },
+ { type: types.SET_DIFF_DATA_BATCH, payload: { diff_files: res2.diff_files } },
{ type: types.SET_BATCH_LOADING, payload: false },
+ { type: types.UPDATE_CURRENT_DIFF_FILE_ID, payload: 'test2' },
{ type: types.SET_RETRIEVING_BATCHES, payload: false },
],
[],
@@ -309,6 +316,7 @@ describe('DiffsStoreActions', () => {
showWhitespace: false,
diffViewType: 'inline',
useSingleDiffStyle: false,
+ currentDiffFileId: null,
},
[
{ type: types.SET_LOADING, payload: true },
@@ -345,8 +353,8 @@ describe('DiffsStoreActions', () => {
it('should fetch batch diff files', done => {
const endpointBatch = '/fetch/diffs_batch';
- const res1 = { diff_files: [], pagination: { next_page: 2 } };
- const res2 = { diff_files: [], pagination: {} };
+ const res1 = { diff_files: [{ file_hash: 'test' }], pagination: { next_page: 2 } };
+ const res2 = { diff_files: [{ file_hash: 'test2' }], pagination: {} };
mock
.onGet(mergeUrlParams({ per_page: DIFFS_PER_PAGE, w: '1', page: 1 }, endpointBatch))
.reply(200, res1)
@@ -356,14 +364,16 @@ describe('DiffsStoreActions', () => {
testAction(
fetchDiffFilesBatch,
{},
- { endpointBatch, useSingleDiffStyle: false },
+ { endpointBatch, useSingleDiffStyle: false, currentDiffFileId: null },
[
{ type: types.SET_BATCH_LOADING, payload: true },
{ type: types.SET_RETRIEVING_BATCHES, payload: true },
{ type: types.SET_DIFF_DATA_BATCH, payload: { diff_files: res1.diff_files } },
{ type: types.SET_BATCH_LOADING, payload: false },
- { type: types.SET_DIFF_DATA_BATCH, payload: { diff_files: [] } },
+ { type: types.UPDATE_CURRENT_DIFF_FILE_ID, payload: 'test' },
+ { type: types.SET_DIFF_DATA_BATCH, payload: { diff_files: res2.diff_files } },
{ type: types.SET_BATCH_LOADING, payload: false },
+ { type: types.UPDATE_CURRENT_DIFF_FILE_ID, payload: 'test2' },
{ type: types.SET_RETRIEVING_BATCHES, payload: false },
],
[],
@@ -475,6 +485,10 @@ describe('DiffsStoreActions', () => {
});
describe('assignDiscussionsToDiff', () => {
+ afterEach(() => {
+ window.location.hash = '';
+ });
+
it('should merge discussions into diffs', done => {
window.location.hash = 'ABC_123';
@@ -568,6 +582,19 @@ describe('DiffsStoreActions', () => {
done,
);
});
+
+ it('dispatches setCurrentDiffFileIdFromNote with note ID', done => {
+ window.location.hash = 'note_123';
+
+ testAction(
+ assignDiscussionsToDiff,
+ [],
+ { diffFiles: [], useSingleDiffStyle: true },
+ [],
+ [{ type: 'setCurrentDiffFileIdFromNote', payload: '123' }],
+ done,
+ );
+ });
});
describe('removeDiscussionsFromDiff', () => {
@@ -1187,10 +1214,10 @@ describe('DiffsStoreActions', () => {
);
});
- it('sets localStorage', () => {
+ it('sets cookie', () => {
setShowWhitespace({ commit() {} }, { showWhitespace: true });
- expect(localStorage.setItem).toHaveBeenCalledWith('mr_show_whitespace', true);
+ expect(Cookies.get(DIFF_WHITESPACE_COOKIE_NAME)).toEqual(SHOW_WHITESPACE);
});
it('calls history pushState', () => {
@@ -1250,12 +1277,12 @@ describe('DiffsStoreActions', () => {
describe('success', () => {
beforeEach(() => {
- mock.onGet(`${gl.TEST_HOST}/context`).replyOnce(200, ['test']);
+ mock.onGet(`${TEST_HOST}/context`).replyOnce(200, ['test']);
});
it('commits the success and dispatches an action to expand the new lines', done => {
const file = {
- context_lines_path: `${gl.TEST_HOST}/context`,
+ context_lines_path: `${TEST_HOST}/context`,
file_path: 'test',
file_hash: 'test',
};
@@ -1272,13 +1299,13 @@ describe('DiffsStoreActions', () => {
describe('error', () => {
beforeEach(() => {
- mock.onGet(`${gl.TEST_HOST}/context`).replyOnce(500);
+ mock.onGet(`${TEST_HOST}/context`).replyOnce(500);
});
it('dispatches receiveFullDiffError', done => {
testAction(
fetchFullDiff,
- { context_lines_path: `${gl.TEST_HOST}/context`, file_path: 'test', file_hash: 'test' },
+ { context_lines_path: `${TEST_HOST}/context`, file_path: 'test', file_hash: 'test' },
null,
[],
[{ type: 'receiveFullDiffError', payload: 'test' }],
@@ -1442,7 +1469,7 @@ describe('DiffsStoreActions', () => {
describe('setSuggestPopoverDismissed', () => {
it('commits SET_SHOW_SUGGEST_POPOVER', done => {
- const state = { dismissEndpoint: `${gl.TEST_HOST}/-/user_callouts` };
+ const state = { dismissEndpoint: `${TEST_HOST}/-/user_callouts` };
const mock = new MockAdapter(axios);
mock.onPost(state.dismissEndpoint).reply(200, {});
@@ -1563,4 +1590,31 @@ describe('DiffsStoreActions', () => {
},
);
});
+
+ describe('setCurrentDiffFileIdFromNote', () => {
+ it('commits UPDATE_CURRENT_DIFF_FILE_ID', () => {
+ const commit = jest.fn();
+ const rootGetters = {
+ getDiscussion: () => ({ diff_file: { file_hash: '123' } }),
+ notesById: { '1': { discussion_id: '2' } },
+ };
+
+ setCurrentDiffFileIdFromNote({ commit, rootGetters }, '1');
+
+ expect(commit).toHaveBeenCalledWith(types.UPDATE_CURRENT_DIFF_FILE_ID, '123');
+ });
+ });
+
+ describe('navigateToDiffFileIndex', () => {
+ it('commits UPDATE_CURRENT_DIFF_FILE_ID', done => {
+ testAction(
+ navigateToDiffFileIndex,
+ 0,
+ { diffFiles: [{ file_hash: '123' }] },
+ [{ type: types.UPDATE_CURRENT_DIFF_FILE_ID, payload: '123' }],
+ [],
+ done,
+ );
+ });
+ });
});
diff --git a/spec/frontend/diffs/store/utils_spec.js b/spec/frontend/diffs/store/utils_spec.js
index 891de45e268..d87619e1e3c 100644
--- a/spec/frontend/diffs/store/utils_spec.js
+++ b/spec/frontend/diffs/store/utils_spec.js
@@ -1090,4 +1090,26 @@ describe('DiffsStoreUtils', () => {
]);
});
});
+
+ describe('getDefaultWhitespace', () => {
+ it('defaults to true if querystring and cookie are undefined', () => {
+ expect(utils.getDefaultWhitespace()).toBe(true);
+ });
+
+ it('returns false if querystring is `1`', () => {
+ expect(utils.getDefaultWhitespace('1', '0')).toBe(false);
+ });
+
+ it('returns true if querystring is `0`', () => {
+ expect(utils.getDefaultWhitespace('0', undefined)).toBe(true);
+ });
+
+ it('returns false if cookie is `1`', () => {
+ expect(utils.getDefaultWhitespace(undefined, '1')).toBe(false);
+ });
+
+ it('returns true if cookie is `0`', () => {
+ expect(utils.getDefaultWhitespace(undefined, '0')).toBe(true);
+ });
+ });
});
diff --git a/spec/frontend/editor/editor_lite_spec.js b/spec/frontend/editor/editor_lite_spec.js
index cb07bcf8f28..92a136835bf 100644
--- a/spec/frontend/editor/editor_lite_spec.js
+++ b/spec/frontend/editor/editor_lite_spec.js
@@ -115,6 +115,76 @@ describe('Base editor', () => {
});
});
+ describe('extensions', () => {
+ const foo1 = jest.fn();
+ const foo2 = jest.fn();
+ const bar = jest.fn();
+ const MyExt1 = {
+ foo: foo1,
+ };
+ const MyExt2 = {
+ bar,
+ };
+ const MyExt3 = {
+ foo: foo2,
+ };
+ beforeEach(() => {
+ editor.createInstance({ el: editorEl, blobPath, blobContent });
+ });
+
+ afterEach(() => {
+ editor.model.dispose();
+ });
+
+ it('is extensible with the extensions', () => {
+ expect(editor.foo).toBeUndefined();
+
+ editor.use(MyExt1);
+ expect(editor.foo).toEqual(foo1);
+ });
+
+ it('does not fail if no extensions supplied', () => {
+ const spy = jest.spyOn(global.console, 'error');
+ editor.use();
+
+ expect(spy).not.toHaveBeenCalled();
+ });
+
+ it('is extensible with multiple extensions', () => {
+ expect(editor.foo).toBeUndefined();
+ expect(editor.bar).toBeUndefined();
+
+ editor.use([MyExt1, MyExt2]);
+
+ expect(editor.foo).toEqual(foo1);
+ expect(editor.bar).toEqual(bar);
+ });
+
+ it('uses the last definition of a method in case of an overlap', () => {
+ editor.use([MyExt1, MyExt2, MyExt3]);
+ expect(editor).toEqual(
+ expect.objectContaining({
+ foo: foo2,
+ bar,
+ }),
+ );
+ });
+
+ it('correctly resolves references withing extensions', () => {
+ const FunctionExt = {
+ inst() {
+ return this.instance;
+ },
+ mod() {
+ return this.model;
+ },
+ };
+ editor.use(FunctionExt);
+ expect(editor.inst()).toEqual(editor.instance);
+ expect(editor.mod()).toEqual(editor.model);
+ });
+ });
+
describe('languages', () => {
it('registers custom languages defined with Monaco', () => {
expect(monacoLanguages.getLanguages()).toEqual(
diff --git a/spec/frontend/editor/editor_markdown_ext_spec.js b/spec/frontend/editor/editor_markdown_ext_spec.js
new file mode 100644
index 00000000000..aad2400c0f0
--- /dev/null
+++ b/spec/frontend/editor/editor_markdown_ext_spec.js
@@ -0,0 +1,204 @@
+import EditorLite from '~/editor/editor_lite';
+import { Range, Position } from 'monaco-editor';
+import EditorMarkdownExtension from '~/editor/editor_markdown_ext';
+
+describe('Markdown Extension for Editor Lite', () => {
+ let editor;
+ let editorEl;
+ const firstLine = 'This is a';
+ const secondLine = 'multiline';
+ const thirdLine = 'string with some **markup**';
+ const text = `${firstLine}\n${secondLine}\n${thirdLine}`;
+ const filePath = 'foo.md';
+
+ const setSelection = (startLineNumber = 1, startColumn = 1, endLineNumber = 1, endColumn = 1) => {
+ const selection = new Range(startLineNumber, startColumn, endLineNumber, endColumn);
+ editor.instance.setSelection(selection);
+ };
+ const selectSecondString = () => setSelection(2, 1, 2, secondLine.length + 1); // select the whole second line
+ const selectSecondAndThirdLines = () => setSelection(2, 1, 3, thirdLine.length + 1); // select second and third lines
+
+ const selectionToString = () => editor.instance.getSelection().toString();
+ const positionToString = () => editor.instance.getPosition().toString();
+
+ beforeEach(() => {
+ setFixtures('<div id="editor" data-editor-loading></div>');
+ editorEl = document.getElementById('editor');
+ editor = new EditorLite();
+ editor.createInstance({
+ el: editorEl,
+ blobPath: filePath,
+ blobContent: text,
+ });
+ editor.use(EditorMarkdownExtension);
+ });
+
+ afterEach(() => {
+ editor.instance.dispose();
+ editor.model.dispose();
+ editorEl.remove();
+ });
+
+ describe('getSelectedText', () => {
+ it('does not fail if there is no selection and returns the empty string', () => {
+ jest.spyOn(editor.instance, 'getSelection');
+ const resText = editor.getSelectedText();
+
+ expect(editor.instance.getSelection).toHaveBeenCalled();
+ expect(resText).toBe('');
+ });
+
+ it.each`
+ description | selection | expectedString
+ ${'same-line'} | ${[1, 1, 1, firstLine.length + 1]} | ${firstLine}
+ ${'two-lines'} | ${[1, 1, 2, secondLine.length + 1]} | ${`${firstLine}\n${secondLine}`}
+ ${'multi-lines'} | ${[1, 1, 3, thirdLine.length + 1]} | ${text}
+ `('correctly returns selected text for $description', ({ selection, expectedString }) => {
+ setSelection(...selection);
+
+ const resText = editor.getSelectedText();
+
+ expect(resText).toBe(expectedString);
+ });
+
+ it('accepts selection object that serves as a source instead of current selection', () => {
+ selectSecondString();
+ const firstLineSelection = new Range(1, 1, 1, firstLine.length + 1);
+
+ const resText = editor.getSelectedText(firstLineSelection);
+
+ expect(resText).toBe(firstLine);
+ });
+ });
+
+ describe('replaceSelectedText', () => {
+ const expectedStr = 'foo';
+
+ it('replaces selected text with the supplied one', () => {
+ selectSecondString();
+ editor.replaceSelectedText(expectedStr);
+
+ expect(editor.getValue()).toBe(`${firstLine}\n${expectedStr}\n${thirdLine}`);
+ });
+
+ it('prepends the supplied text if no text is selected', () => {
+ editor.replaceSelectedText(expectedStr);
+ expect(editor.getValue()).toBe(`${expectedStr}${firstLine}\n${secondLine}\n${thirdLine}`);
+ });
+
+ it('replaces selection with empty string if no text is supplied', () => {
+ selectSecondString();
+ editor.replaceSelectedText();
+ expect(editor.getValue()).toBe(`${firstLine}\n\n${thirdLine}`);
+ });
+
+ it('puts cursor at the end of the new string and collapses selection by default', () => {
+ selectSecondString();
+ editor.replaceSelectedText(expectedStr);
+
+ expect(positionToString()).toBe(`(2,${expectedStr.length + 1})`);
+ expect(selectionToString()).toBe(
+ `[2,${expectedStr.length + 1} -> 2,${expectedStr.length + 1}]`,
+ );
+ });
+
+ it('puts cursor at the end of the new string and keeps selection if "select" is supplied', () => {
+ const select = 'url';
+ const complexReplacementString = `[${secondLine}](${select})`;
+ selectSecondString();
+ editor.replaceSelectedText(complexReplacementString, select);
+
+ expect(positionToString()).toBe(`(2,${complexReplacementString.length + 1})`);
+ expect(selectionToString()).toBe(`[2,1 -> 2,${complexReplacementString.length + 1}]`);
+ });
+ });
+
+ describe('moveCursor', () => {
+ const setPosition = endCol => {
+ const currentPos = new Position(2, endCol);
+ editor.instance.setPosition(currentPos);
+ };
+
+ it.each`
+ direction | condition | startColumn | shift | endPosition
+ ${'left'} | ${'negative'} | ${secondLine.length + 1} | ${-1} | ${`(2,${secondLine.length})`}
+ ${'left'} | ${'negative'} | ${secondLine.length} | ${secondLine.length * -1} | ${'(2,1)'}
+ ${'right'} | ${'positive'} | ${1} | ${1} | ${'(2,2)'}
+ ${'right'} | ${'positive'} | ${2} | ${secondLine.length} | ${`(2,${secondLine.length + 1})`}
+ ${'up'} | ${'positive'} | ${1} | ${[0, -1]} | ${'(1,1)'}
+ ${'top of file'} | ${'positive'} | ${1} | ${[0, -100]} | ${'(1,1)'}
+ ${'down'} | ${'negative'} | ${1} | ${[0, 1]} | ${'(3,1)'}
+ ${'end of file'} | ${'negative'} | ${1} | ${[0, 100]} | ${`(3,${thirdLine.length + 1})`}
+ ${'end of line'} | ${'too large'} | ${1} | ${secondLine.length + 100} | ${`(2,${secondLine.length + 1})`}
+ ${'start of line'} | ${'too low'} | ${1} | ${-100} | ${'(2,1)'}
+ `(
+ 'moves cursor to the $direction if $condition supplied',
+ ({ startColumn, shift, endPosition }) => {
+ setPosition(startColumn);
+ if (Array.isArray(shift)) {
+ editor.moveCursor(...shift);
+ } else {
+ editor.moveCursor(shift);
+ }
+ expect(positionToString()).toBe(endPosition);
+ },
+ );
+ });
+
+ describe('selectWithinSelection', () => {
+ it('scopes down current selection to supplied text', () => {
+ const selectedText = `${secondLine}\n${thirdLine}`;
+ const toSelect = 'string';
+ selectSecondAndThirdLines();
+
+ expect(selectionToString()).toBe(`[2,1 -> 3,${thirdLine.length + 1}]`);
+
+ editor.selectWithinSelection(toSelect, selectedText);
+ expect(selectionToString()).toBe(`[3,1 -> 3,${toSelect.length + 1}]`);
+ });
+
+ it('does not fail when only `toSelect` is supplied and fetches the text from selection', () => {
+ jest.spyOn(editor, 'getSelectedText');
+ const toSelect = 'string';
+ selectSecondAndThirdLines();
+
+ editor.selectWithinSelection(toSelect);
+
+ expect(editor.getSelectedText).toHaveBeenCalled();
+ expect(selectionToString()).toBe(`[3,1 -> 3,${toSelect.length + 1}]`);
+ });
+
+ it('does nothing if no `toSelect` is supplied', () => {
+ selectSecondAndThirdLines();
+ const expectedPos = `(3,${thirdLine.length + 1})`;
+ const expectedSelection = `[2,1 -> 3,${thirdLine.length + 1}]`;
+
+ expect(positionToString()).toBe(expectedPos);
+ expect(selectionToString()).toBe(expectedSelection);
+
+ editor.selectWithinSelection();
+
+ expect(positionToString()).toBe(expectedPos);
+ expect(selectionToString()).toBe(expectedSelection);
+ });
+
+ it('does nothing if no selection is set in the editor', () => {
+ const expectedPos = '(1,1)';
+ const expectedSelection = '[1,1 -> 1,1]';
+ const toSelect = 'string';
+
+ expect(positionToString()).toBe(expectedPos);
+ expect(selectionToString()).toBe(expectedSelection);
+
+ editor.selectWithinSelection(toSelect);
+
+ expect(positionToString()).toBe(expectedPos);
+ expect(selectionToString()).toBe(expectedSelection);
+
+ editor.selectWithinSelection();
+
+ expect(positionToString()).toBe(expectedPos);
+ expect(selectionToString()).toBe(expectedSelection);
+ });
+ });
+});
diff --git a/spec/frontend/emoji/emoji_spec.js b/spec/frontend/emoji/emoji_spec.js
new file mode 100644
index 00000000000..c6a15d5976a
--- /dev/null
+++ b/spec/frontend/emoji/emoji_spec.js
@@ -0,0 +1,382 @@
+import MockAdapter from 'axios-mock-adapter';
+import axios from '~/lib/utils/axios_utils';
+import { initEmojiMap, glEmojiTag, EMOJI_VERSION } from '~/emoji';
+import isEmojiUnicodeSupported, {
+ isFlagEmoji,
+ isRainbowFlagEmoji,
+ isKeycapEmoji,
+ isSkinToneComboEmoji,
+ isHorceRacingSkinToneComboEmoji,
+ isPersonZwjEmoji,
+} from '~/emoji/support/is_emoji_unicode_supported';
+import { trimText } from 'helpers/text_helper';
+
+const emptySupportMap = {
+ personZwj: false,
+ horseRacing: false,
+ flag: false,
+ skinToneModifier: false,
+ '9.0': false,
+ '8.0': false,
+ '7.0': false,
+ 6.1: false,
+ '6.0': false,
+ 5.2: false,
+ 5.1: false,
+ 4.1: false,
+ '4.0': false,
+ 3.2: false,
+ '3.0': false,
+ 1.1: false,
+};
+
+const emojiFixtureMap = {
+ bomb: {
+ name: 'bomb',
+ moji: '💣',
+ unicodeVersion: '6.0',
+ },
+ construction_worker_tone5: {
+ name: 'construction_worker_tone5',
+ moji: '👷ðŸ¿',
+ unicodeVersion: '8.0',
+ },
+ five: {
+ name: 'five',
+ moji: '5ï¸âƒ£',
+ unicodeVersion: '3.0',
+ },
+ grey_question: {
+ name: 'grey_question',
+ moji: 'â”',
+ unicodeVersion: '6.0',
+ },
+};
+
+describe('gl_emoji', () => {
+ let mock;
+ const emojiData = getJSONFixture('emojis/emojis.json');
+
+ beforeEach(() => {
+ mock = new MockAdapter(axios);
+ mock.onGet(`/-/emojis/${EMOJI_VERSION}/emojis.json`).reply(200, emojiData);
+
+ return initEmojiMap().catch(() => {});
+ });
+
+ afterEach(() => {
+ mock.restore();
+ });
+
+ describe('glEmojiTag', () => {
+ it('bomb emoji', () => {
+ const emojiKey = 'bomb';
+ const markup = glEmojiTag(emojiFixtureMap[emojiKey].name);
+
+ expect(trimText(markup)).toMatchInlineSnapshot(
+ `"<gl-emoji data-name=\\"bomb\\"></gl-emoji>"`,
+ );
+ });
+
+ it('bomb emoji with sprite fallback readiness', () => {
+ const emojiKey = 'bomb';
+ const markup = glEmojiTag(emojiFixtureMap[emojiKey].name, {
+ sprite: true,
+ });
+ expect(trimText(markup)).toMatchInlineSnapshot(
+ `"<gl-emoji data-fallback-sprite-class=\\"emoji-bomb\\" data-name=\\"bomb\\"></gl-emoji>"`,
+ );
+ });
+ });
+
+ describe('isFlagEmoji', () => {
+ it('should gracefully handle empty string', () => {
+ expect(isFlagEmoji('')).toBeFalsy();
+ });
+
+ it('should detect flag_ac', () => {
+ expect(isFlagEmoji('🇦🇨')).toBeTruthy();
+ });
+
+ it('should detect flag_us', () => {
+ expect(isFlagEmoji('🇺🇸')).toBeTruthy();
+ });
+
+ it('should detect flag_zw', () => {
+ expect(isFlagEmoji('🇿🇼')).toBeTruthy();
+ });
+
+ it('should not detect flags', () => {
+ expect(isFlagEmoji('ðŸŽ')).toBeFalsy();
+ });
+
+ it('should not detect triangular_flag_on_post', () => {
+ expect(isFlagEmoji('🚩')).toBeFalsy();
+ });
+
+ it('should not detect single letter', () => {
+ expect(isFlagEmoji('🇦')).toBeFalsy();
+ });
+
+ it('should not detect >2 letters', () => {
+ expect(isFlagEmoji('🇦🇧🇨')).toBeFalsy();
+ });
+ });
+
+ describe('isRainbowFlagEmoji', () => {
+ it('should gracefully handle empty string', () => {
+ expect(isRainbowFlagEmoji('')).toBeFalsy();
+ });
+
+ it('should detect rainbow_flag', () => {
+ expect(isRainbowFlagEmoji('ðŸ³ðŸŒˆ')).toBeTruthy();
+ });
+
+ it("should not detect flag_white on its' own", () => {
+ expect(isRainbowFlagEmoji('ðŸ³')).toBeFalsy();
+ });
+
+ it("should not detect rainbow on its' own", () => {
+ expect(isRainbowFlagEmoji('🌈')).toBeFalsy();
+ });
+
+ it('should not detect flag_white with something else', () => {
+ expect(isRainbowFlagEmoji('ðŸ³ðŸ”µ')).toBeFalsy();
+ });
+ });
+
+ describe('isKeycapEmoji', () => {
+ it('should gracefully handle empty string', () => {
+ expect(isKeycapEmoji('')).toBeFalsy();
+ });
+
+ it('should detect one(keycap)', () => {
+ expect(isKeycapEmoji('1ï¸âƒ£')).toBeTruthy();
+ });
+
+ it('should detect nine(keycap)', () => {
+ expect(isKeycapEmoji('9ï¸âƒ£')).toBeTruthy();
+ });
+
+ it('should not detect ten(keycap)', () => {
+ expect(isKeycapEmoji('🔟')).toBeFalsy();
+ });
+
+ it('should not detect hash(keycap)', () => {
+ expect(isKeycapEmoji('#⃣')).toBeFalsy();
+ });
+ });
+
+ describe('isSkinToneComboEmoji', () => {
+ it('should gracefully handle empty string', () => {
+ expect(isSkinToneComboEmoji('')).toBeFalsy();
+ });
+
+ it('should detect hand_splayed_tone5', () => {
+ expect(isSkinToneComboEmoji('ðŸ–ðŸ¿')).toBeTruthy();
+ });
+
+ it('should not detect hand_splayed', () => {
+ expect(isSkinToneComboEmoji('ðŸ–')).toBeFalsy();
+ });
+
+ it('should detect lifter_tone1', () => {
+ expect(isSkinToneComboEmoji('ðŸ‹ðŸ»')).toBeTruthy();
+ });
+
+ it('should not detect lifter', () => {
+ expect(isSkinToneComboEmoji('ðŸ‹')).toBeFalsy();
+ });
+
+ it('should detect rowboat_tone4', () => {
+ expect(isSkinToneComboEmoji('🚣ðŸ¾')).toBeTruthy();
+ });
+
+ it('should not detect rowboat', () => {
+ expect(isSkinToneComboEmoji('🚣')).toBeFalsy();
+ });
+
+ it('should not detect individual tone emoji', () => {
+ expect(isSkinToneComboEmoji('ðŸ»')).toBeFalsy();
+ });
+ });
+
+ describe('isHorceRacingSkinToneComboEmoji', () => {
+ it('should gracefully handle empty string', () => {
+ expect(isHorceRacingSkinToneComboEmoji('')).toBeFalsy();
+ });
+
+ it('should detect horse_racing_tone2', () => {
+ expect(isHorceRacingSkinToneComboEmoji('ðŸ‡ðŸ¼')).toBeTruthy();
+ });
+
+ it('should not detect horse_racing', () => {
+ expect(isHorceRacingSkinToneComboEmoji('ðŸ‡')).toBeFalsy();
+ });
+ });
+
+ describe('isPersonZwjEmoji', () => {
+ it('should gracefully handle empty string', () => {
+ expect(isPersonZwjEmoji('')).toBeFalsy();
+ });
+
+ it('should detect couple_mm', () => {
+ expect(isPersonZwjEmoji('👨â€â¤ï¸â€ðŸ‘¨')).toBeTruthy();
+ });
+
+ it('should not detect couple_with_heart', () => {
+ expect(isPersonZwjEmoji('💑')).toBeFalsy();
+ });
+
+ it('should not detect couplekiss', () => {
+ expect(isPersonZwjEmoji('ðŸ’')).toBeFalsy();
+ });
+
+ it('should detect family_mmb', () => {
+ expect(isPersonZwjEmoji('👨â€ðŸ‘¨â€ðŸ‘¦')).toBeTruthy();
+ });
+
+ it('should detect family_mwgb', () => {
+ expect(isPersonZwjEmoji('👨â€ðŸ‘©â€ðŸ‘§â€ðŸ‘¦')).toBeTruthy();
+ });
+
+ it('should not detect family', () => {
+ expect(isPersonZwjEmoji('👪')).toBeFalsy();
+ });
+
+ it('should detect kiss_ww', () => {
+ expect(isPersonZwjEmoji('👩â€â¤ï¸â€ðŸ’‹â€ðŸ‘©')).toBeTruthy();
+ });
+
+ it('should not detect girl', () => {
+ expect(isPersonZwjEmoji('👧')).toBeFalsy();
+ });
+
+ it('should not detect girl_tone5', () => {
+ expect(isPersonZwjEmoji('👧ðŸ¿')).toBeFalsy();
+ });
+
+ it('should not detect man', () => {
+ expect(isPersonZwjEmoji('👨')).toBeFalsy();
+ });
+
+ it('should not detect woman', () => {
+ expect(isPersonZwjEmoji('👩')).toBeFalsy();
+ });
+ });
+
+ describe('isEmojiUnicodeSupported', () => {
+ it('should gracefully handle empty string with unicode support', () => {
+ const isSupported = isEmojiUnicodeSupported({ '1.0': true }, '', '1.0');
+
+ expect(isSupported).toBeTruthy();
+ });
+
+ it('should gracefully handle empty string without unicode support', () => {
+ const isSupported = isEmojiUnicodeSupported({}, '', '1.0');
+
+ expect(isSupported).toBeFalsy();
+ });
+
+ it('bomb(6.0) with 6.0 support', () => {
+ const emojiKey = 'bomb';
+ const unicodeSupportMap = { ...emptySupportMap, '6.0': true };
+ const isSupported = isEmojiUnicodeSupported(
+ unicodeSupportMap,
+ emojiFixtureMap[emojiKey].moji,
+ emojiFixtureMap[emojiKey].unicodeVersion,
+ );
+
+ expect(isSupported).toBeTruthy();
+ });
+
+ it('bomb(6.0) without 6.0 support', () => {
+ const emojiKey = 'bomb';
+ const unicodeSupportMap = emptySupportMap;
+ const isSupported = isEmojiUnicodeSupported(
+ unicodeSupportMap,
+ emojiFixtureMap[emojiKey].moji,
+ emojiFixtureMap[emojiKey].unicodeVersion,
+ );
+
+ expect(isSupported).toBeFalsy();
+ });
+
+ it('bomb(6.0) without 6.0 but with 9.0 support', () => {
+ const emojiKey = 'bomb';
+ const unicodeSupportMap = { ...emptySupportMap, '9.0': true };
+ const isSupported = isEmojiUnicodeSupported(
+ unicodeSupportMap,
+ emojiFixtureMap[emojiKey].moji,
+ emojiFixtureMap[emojiKey].unicodeVersion,
+ );
+
+ expect(isSupported).toBeFalsy();
+ });
+
+ it('construction_worker_tone5(8.0) without skin tone modifier support', () => {
+ const emojiKey = 'construction_worker_tone5';
+ const unicodeSupportMap = {
+ ...emptySupportMap,
+ skinToneModifier: false,
+ '9.0': true,
+ '8.0': true,
+ '7.0': true,
+ 6.1: true,
+ '6.0': true,
+ 5.2: true,
+ 5.1: true,
+ 4.1: true,
+ '4.0': true,
+ 3.2: true,
+ '3.0': true,
+ 1.1: true,
+ };
+ const isSupported = isEmojiUnicodeSupported(
+ unicodeSupportMap,
+ emojiFixtureMap[emojiKey].moji,
+ emojiFixtureMap[emojiKey].unicodeVersion,
+ );
+
+ expect(isSupported).toBeFalsy();
+ });
+
+ it('use native keycap on >=57 chrome', () => {
+ const emojiKey = 'five';
+ const unicodeSupportMap = {
+ ...emptySupportMap,
+ '3.0': true,
+ meta: {
+ isChrome: true,
+ chromeVersion: 57,
+ },
+ };
+ const isSupported = isEmojiUnicodeSupported(
+ unicodeSupportMap,
+ emojiFixtureMap[emojiKey].moji,
+ emojiFixtureMap[emojiKey].unicodeVersion,
+ );
+
+ expect(isSupported).toBeTruthy();
+ });
+
+ it('fallback keycap on <57 chrome', () => {
+ const emojiKey = 'five';
+ const unicodeSupportMap = {
+ ...emptySupportMap,
+ '3.0': true,
+ meta: {
+ isChrome: true,
+ chromeVersion: 50,
+ },
+ };
+ const isSupported = isEmojiUnicodeSupported(
+ unicodeSupportMap,
+ emojiFixtureMap[emojiKey].moji,
+ emojiFixtureMap[emojiKey].unicodeVersion,
+ );
+
+ expect(isSupported).toBeFalsy();
+ });
+ });
+});
diff --git a/spec/frontend/behaviors/gl_emoji/unicode_support_map_spec.js b/spec/frontend/emoji/support/unicode_support_map_spec.js
index aaee9c30cac..aaee9c30cac 100644
--- a/spec/frontend/behaviors/gl_emoji/unicode_support_map_spec.js
+++ b/spec/frontend/emoji/support/unicode_support_map_spec.js
diff --git a/spec/frontend/emoji_spec.js b/spec/frontend/emoji_spec.js
deleted file mode 100644
index 25bc95e0dd6..00000000000
--- a/spec/frontend/emoji_spec.js
+++ /dev/null
@@ -1,485 +0,0 @@
-import { glEmojiTag } from '~/emoji';
-import isEmojiUnicodeSupported, {
- isFlagEmoji,
- isRainbowFlagEmoji,
- isKeycapEmoji,
- isSkinToneComboEmoji,
- isHorceRacingSkinToneComboEmoji,
- isPersonZwjEmoji,
-} from '~/emoji/support/is_emoji_unicode_supported';
-
-const emptySupportMap = {
- personZwj: false,
- horseRacing: false,
- flag: false,
- skinToneModifier: false,
- '9.0': false,
- '8.0': false,
- '7.0': false,
- 6.1: false,
- '6.0': false,
- 5.2: false,
- 5.1: false,
- 4.1: false,
- '4.0': false,
- 3.2: false,
- '3.0': false,
- 1.1: false,
-};
-
-const emojiFixtureMap = {
- bomb: {
- name: 'bomb',
- moji: '💣',
- unicodeVersion: '6.0',
- },
- construction_worker_tone5: {
- name: 'construction_worker_tone5',
- moji: '👷ðŸ¿',
- unicodeVersion: '8.0',
- },
- five: {
- name: 'five',
- moji: '5ï¸âƒ£',
- unicodeVersion: '3.0',
- },
- grey_question: {
- name: 'grey_question',
- moji: 'â”',
- unicodeVersion: '6.0',
- },
-};
-
-function markupToDomElement(markup) {
- const div = document.createElement('div');
- div.innerHTML = markup;
- return div.firstElementChild;
-}
-
-function testGlEmojiImageFallback(element, name, src) {
- expect(element.tagName.toLowerCase()).toBe('img');
- expect(element.getAttribute('src')).toBe(src);
- expect(element.getAttribute('title')).toBe(`:${name}:`);
- expect(element.getAttribute('alt')).toBe(`:${name}:`);
-}
-
-const defaults = {
- forceFallback: false,
- sprite: false,
-};
-
-function testGlEmojiElement(element, name, unicodeVersion, unicodeMoji, options = {}) {
- const opts = { ...defaults, ...options };
- expect(element.tagName.toLowerCase()).toBe('gl-emoji');
- expect(element.dataset.name).toBe(name);
- expect(element.dataset.fallbackSrc.length).toBeGreaterThan(0);
- expect(element.dataset.unicodeVersion).toBe(unicodeVersion);
-
- const fallbackSpriteClass = `emoji-${name}`;
- if (opts.sprite) {
- expect(element.dataset.fallbackSpriteClass).toBe(fallbackSpriteClass);
- }
-
- if (opts.forceFallback && opts.sprite) {
- expect(element.getAttribute('class')).toBe(`emoji-icon ${fallbackSpriteClass}`);
- }
-
- if (opts.forceFallback && !opts.sprite) {
- // Check for image fallback
- testGlEmojiImageFallback(element.firstElementChild, name, element.dataset.fallbackSrc);
- } else {
- // Otherwise make sure things are still unicode text
- expect(element.textContent.trim()).toBe(unicodeMoji);
- }
-}
-
-describe('gl_emoji', () => {
- describe('glEmojiTag', () => {
- it('bomb emoji', () => {
- const emojiKey = 'bomb';
- const markup = glEmojiTag(emojiFixtureMap[emojiKey].name);
- const glEmojiElement = markupToDomElement(markup);
- testGlEmojiElement(
- glEmojiElement,
- emojiFixtureMap[emojiKey].name,
- emojiFixtureMap[emojiKey].unicodeVersion,
- emojiFixtureMap[emojiKey].moji,
- );
- });
-
- it('bomb emoji with image fallback', () => {
- const emojiKey = 'bomb';
- const markup = glEmojiTag(emojiFixtureMap[emojiKey].name, {
- forceFallback: true,
- });
- const glEmojiElement = markupToDomElement(markup);
- testGlEmojiElement(
- glEmojiElement,
- emojiFixtureMap[emojiKey].name,
- emojiFixtureMap[emojiKey].unicodeVersion,
- emojiFixtureMap[emojiKey].moji,
- {
- forceFallback: true,
- },
- );
- });
-
- it('bomb emoji with sprite fallback readiness', () => {
- const emojiKey = 'bomb';
- const markup = glEmojiTag(emojiFixtureMap[emojiKey].name, {
- sprite: true,
- });
- const glEmojiElement = markupToDomElement(markup);
- testGlEmojiElement(
- glEmojiElement,
- emojiFixtureMap[emojiKey].name,
- emojiFixtureMap[emojiKey].unicodeVersion,
- emojiFixtureMap[emojiKey].moji,
- {
- sprite: true,
- },
- );
- });
-
- it('bomb emoji with sprite fallback', () => {
- const emojiKey = 'bomb';
- const markup = glEmojiTag(emojiFixtureMap[emojiKey].name, {
- forceFallback: true,
- sprite: true,
- });
- const glEmojiElement = markupToDomElement(markup);
- testGlEmojiElement(
- glEmojiElement,
- emojiFixtureMap[emojiKey].name,
- emojiFixtureMap[emojiKey].unicodeVersion,
- emojiFixtureMap[emojiKey].moji,
- {
- forceFallback: true,
- sprite: true,
- },
- );
- });
-
- it('question mark when invalid emoji name given', () => {
- const name = 'invalid_emoji';
- const emojiKey = 'grey_question';
- const markup = glEmojiTag(name);
- const glEmojiElement = markupToDomElement(markup);
- testGlEmojiElement(
- glEmojiElement,
- emojiFixtureMap[emojiKey].name,
- emojiFixtureMap[emojiKey].unicodeVersion,
- emojiFixtureMap[emojiKey].moji,
- );
- });
-
- it('question mark with image fallback when invalid emoji name given', () => {
- const name = 'invalid_emoji';
- const emojiKey = 'grey_question';
- const markup = glEmojiTag(name, {
- forceFallback: true,
- });
- const glEmojiElement = markupToDomElement(markup);
- testGlEmojiElement(
- glEmojiElement,
- emojiFixtureMap[emojiKey].name,
- emojiFixtureMap[emojiKey].unicodeVersion,
- emojiFixtureMap[emojiKey].moji,
- {
- forceFallback: true,
- },
- );
- });
- });
-
- describe('isFlagEmoji', () => {
- it('should gracefully handle empty string', () => {
- expect(isFlagEmoji('')).toBeFalsy();
- });
-
- it('should detect flag_ac', () => {
- expect(isFlagEmoji('🇦🇨')).toBeTruthy();
- });
-
- it('should detect flag_us', () => {
- expect(isFlagEmoji('🇺🇸')).toBeTruthy();
- });
-
- it('should detect flag_zw', () => {
- expect(isFlagEmoji('🇿🇼')).toBeTruthy();
- });
-
- it('should not detect flags', () => {
- expect(isFlagEmoji('ðŸŽ')).toBeFalsy();
- });
-
- it('should not detect triangular_flag_on_post', () => {
- expect(isFlagEmoji('🚩')).toBeFalsy();
- });
-
- it('should not detect single letter', () => {
- expect(isFlagEmoji('🇦')).toBeFalsy();
- });
-
- it('should not detect >2 letters', () => {
- expect(isFlagEmoji('🇦🇧🇨')).toBeFalsy();
- });
- });
-
- describe('isRainbowFlagEmoji', () => {
- it('should gracefully handle empty string', () => {
- expect(isRainbowFlagEmoji('')).toBeFalsy();
- });
-
- it('should detect rainbow_flag', () => {
- expect(isRainbowFlagEmoji('ðŸ³ðŸŒˆ')).toBeTruthy();
- });
-
- it("should not detect flag_white on its' own", () => {
- expect(isRainbowFlagEmoji('ðŸ³')).toBeFalsy();
- });
-
- it("should not detect rainbow on its' own", () => {
- expect(isRainbowFlagEmoji('🌈')).toBeFalsy();
- });
-
- it('should not detect flag_white with something else', () => {
- expect(isRainbowFlagEmoji('ðŸ³ðŸ”µ')).toBeFalsy();
- });
- });
-
- describe('isKeycapEmoji', () => {
- it('should gracefully handle empty string', () => {
- expect(isKeycapEmoji('')).toBeFalsy();
- });
-
- it('should detect one(keycap)', () => {
- expect(isKeycapEmoji('1ï¸âƒ£')).toBeTruthy();
- });
-
- it('should detect nine(keycap)', () => {
- expect(isKeycapEmoji('9ï¸âƒ£')).toBeTruthy();
- });
-
- it('should not detect ten(keycap)', () => {
- expect(isKeycapEmoji('🔟')).toBeFalsy();
- });
-
- it('should not detect hash(keycap)', () => {
- expect(isKeycapEmoji('#⃣')).toBeFalsy();
- });
- });
-
- describe('isSkinToneComboEmoji', () => {
- it('should gracefully handle empty string', () => {
- expect(isSkinToneComboEmoji('')).toBeFalsy();
- });
-
- it('should detect hand_splayed_tone5', () => {
- expect(isSkinToneComboEmoji('ðŸ–ðŸ¿')).toBeTruthy();
- });
-
- it('should not detect hand_splayed', () => {
- expect(isSkinToneComboEmoji('ðŸ–')).toBeFalsy();
- });
-
- it('should detect lifter_tone1', () => {
- expect(isSkinToneComboEmoji('ðŸ‹ðŸ»')).toBeTruthy();
- });
-
- it('should not detect lifter', () => {
- expect(isSkinToneComboEmoji('ðŸ‹')).toBeFalsy();
- });
-
- it('should detect rowboat_tone4', () => {
- expect(isSkinToneComboEmoji('🚣ðŸ¾')).toBeTruthy();
- });
-
- it('should not detect rowboat', () => {
- expect(isSkinToneComboEmoji('🚣')).toBeFalsy();
- });
-
- it('should not detect individual tone emoji', () => {
- expect(isSkinToneComboEmoji('ðŸ»')).toBeFalsy();
- });
- });
-
- describe('isHorceRacingSkinToneComboEmoji', () => {
- it('should gracefully handle empty string', () => {
- expect(isHorceRacingSkinToneComboEmoji('')).toBeFalsy();
- });
-
- it('should detect horse_racing_tone2', () => {
- expect(isHorceRacingSkinToneComboEmoji('ðŸ‡ðŸ¼')).toBeTruthy();
- });
-
- it('should not detect horse_racing', () => {
- expect(isHorceRacingSkinToneComboEmoji('ðŸ‡')).toBeFalsy();
- });
- });
-
- describe('isPersonZwjEmoji', () => {
- it('should gracefully handle empty string', () => {
- expect(isPersonZwjEmoji('')).toBeFalsy();
- });
-
- it('should detect couple_mm', () => {
- expect(isPersonZwjEmoji('👨â€â¤ï¸â€ðŸ‘¨')).toBeTruthy();
- });
-
- it('should not detect couple_with_heart', () => {
- expect(isPersonZwjEmoji('💑')).toBeFalsy();
- });
-
- it('should not detect couplekiss', () => {
- expect(isPersonZwjEmoji('ðŸ’')).toBeFalsy();
- });
-
- it('should detect family_mmb', () => {
- expect(isPersonZwjEmoji('👨â€ðŸ‘¨â€ðŸ‘¦')).toBeTruthy();
- });
-
- it('should detect family_mwgb', () => {
- expect(isPersonZwjEmoji('👨â€ðŸ‘©â€ðŸ‘§â€ðŸ‘¦')).toBeTruthy();
- });
-
- it('should not detect family', () => {
- expect(isPersonZwjEmoji('👪')).toBeFalsy();
- });
-
- it('should detect kiss_ww', () => {
- expect(isPersonZwjEmoji('👩â€â¤ï¸â€ðŸ’‹â€ðŸ‘©')).toBeTruthy();
- });
-
- it('should not detect girl', () => {
- expect(isPersonZwjEmoji('👧')).toBeFalsy();
- });
-
- it('should not detect girl_tone5', () => {
- expect(isPersonZwjEmoji('👧ðŸ¿')).toBeFalsy();
- });
-
- it('should not detect man', () => {
- expect(isPersonZwjEmoji('👨')).toBeFalsy();
- });
-
- it('should not detect woman', () => {
- expect(isPersonZwjEmoji('👩')).toBeFalsy();
- });
- });
-
- describe('isEmojiUnicodeSupported', () => {
- it('should gracefully handle empty string with unicode support', () => {
- const isSupported = isEmojiUnicodeSupported({ '1.0': true }, '', '1.0');
-
- expect(isSupported).toBeTruthy();
- });
-
- it('should gracefully handle empty string without unicode support', () => {
- const isSupported = isEmojiUnicodeSupported({}, '', '1.0');
-
- expect(isSupported).toBeFalsy();
- });
-
- it('bomb(6.0) with 6.0 support', () => {
- const emojiKey = 'bomb';
- const unicodeSupportMap = { ...emptySupportMap, '6.0': true };
- const isSupported = isEmojiUnicodeSupported(
- unicodeSupportMap,
- emojiFixtureMap[emojiKey].moji,
- emojiFixtureMap[emojiKey].unicodeVersion,
- );
-
- expect(isSupported).toBeTruthy();
- });
-
- it('bomb(6.0) without 6.0 support', () => {
- const emojiKey = 'bomb';
- const unicodeSupportMap = emptySupportMap;
- const isSupported = isEmojiUnicodeSupported(
- unicodeSupportMap,
- emojiFixtureMap[emojiKey].moji,
- emojiFixtureMap[emojiKey].unicodeVersion,
- );
-
- expect(isSupported).toBeFalsy();
- });
-
- it('bomb(6.0) without 6.0 but with 9.0 support', () => {
- const emojiKey = 'bomb';
- const unicodeSupportMap = { ...emptySupportMap, '9.0': true };
- const isSupported = isEmojiUnicodeSupported(
- unicodeSupportMap,
- emojiFixtureMap[emojiKey].moji,
- emojiFixtureMap[emojiKey].unicodeVersion,
- );
-
- expect(isSupported).toBeFalsy();
- });
-
- it('construction_worker_tone5(8.0) without skin tone modifier support', () => {
- const emojiKey = 'construction_worker_tone5';
- const unicodeSupportMap = {
- ...emptySupportMap,
- skinToneModifier: false,
- '9.0': true,
- '8.0': true,
- '7.0': true,
- 6.1: true,
- '6.0': true,
- 5.2: true,
- 5.1: true,
- 4.1: true,
- '4.0': true,
- 3.2: true,
- '3.0': true,
- 1.1: true,
- };
- const isSupported = isEmojiUnicodeSupported(
- unicodeSupportMap,
- emojiFixtureMap[emojiKey].moji,
- emojiFixtureMap[emojiKey].unicodeVersion,
- );
-
- expect(isSupported).toBeFalsy();
- });
-
- it('use native keycap on >=57 chrome', () => {
- const emojiKey = 'five';
- const unicodeSupportMap = {
- ...emptySupportMap,
- '3.0': true,
- meta: {
- isChrome: true,
- chromeVersion: 57,
- },
- };
- const isSupported = isEmojiUnicodeSupported(
- unicodeSupportMap,
- emojiFixtureMap[emojiKey].moji,
- emojiFixtureMap[emojiKey].unicodeVersion,
- );
-
- expect(isSupported).toBeTruthy();
- });
-
- it('fallback keycap on <57 chrome', () => {
- const emojiKey = 'five';
- const unicodeSupportMap = {
- ...emptySupportMap,
- '3.0': true,
- meta: {
- isChrome: true,
- chromeVersion: 50,
- },
- };
- const isSupported = isEmojiUnicodeSupported(
- unicodeSupportMap,
- emojiFixtureMap[emojiKey].moji,
- emojiFixtureMap[emojiKey].unicodeVersion,
- );
-
- expect(isSupported).toBeFalsy();
- });
- });
-});
diff --git a/spec/frontend/environment.js b/spec/frontend/environment.js
index 08da34aa27a..c9d77a34595 100644
--- a/spec/frontend/environment.js
+++ b/spec/frontend/environment.js
@@ -3,12 +3,14 @@
const path = require('path');
const { ErrorWithStack } = require('jest-util');
const JSDOMEnvironment = require('jest-environment-jsdom-sixteen');
+const { TEST_HOST } = require('./helpers/test_constants');
const ROOT_PATH = path.resolve(__dirname, '../..');
class CustomEnvironment extends JSDOMEnvironment {
constructor(config, context) {
- super(config, context);
+ // Setup testURL so that window.location is setup properly
+ super({ ...config, testURL: TEST_HOST }, context);
Object.assign(context.console, {
error(...args) {
@@ -57,6 +59,9 @@ class CustomEnvironment extends JSDOMEnvironment {
ownerDocument: this.global.document,
},
});
+
+ // Expose the jsdom (created in super class) to the global so that we can call reconfigure({ url: '' }) to properly set `window.location`
+ this.global.dom = this.dom;
}
async teardown() {
diff --git a/spec/frontend/environments/emtpy_state_spec.js b/spec/frontend/environments/emtpy_state_spec.js
index ed90c13f1e1..862d90e50dc 100644
--- a/spec/frontend/environments/emtpy_state_spec.js
+++ b/spec/frontend/environments/emtpy_state_spec.js
@@ -7,8 +7,6 @@ describe('environments empty state', () => {
beforeEach(() => {
vm = shallowMount(EmptyState, {
propsData: {
- newPath: 'foo',
- canCreateEnvironment: true,
helpPath: 'bar',
},
});
@@ -23,18 +21,4 @@ describe('environments empty state', () => {
"You don't have any environments right now",
);
});
-
- it('renders the new environment button', () => {
- expect(vm.find('.js-new-environment-button').attributes('href')).toEqual('foo');
- });
-
- describe('without permission', () => {
- beforeEach(() => {
- vm.setProps({ canCreateEnvironment: false });
- });
-
- it('does not render the new environment button', () => {
- expect(vm.find('.js-new-environment-button').exists()).toBe(false);
- });
- });
});
diff --git a/spec/frontend/error_tracking/components/error_details_spec.js b/spec/frontend/error_tracking/components/error_details_spec.js
index fd2164d05fc..6124602e038 100644
--- a/spec/frontend/error_tracking/components/error_details_spec.js
+++ b/spec/frontend/error_tracking/components/error_details_spec.js
@@ -3,7 +3,7 @@ import Vuex from 'vuex';
import { __ } from '~/locale';
import createFlash from '~/flash';
import {
- GlDeprecatedButton,
+ GlButton,
GlLoadingIcon,
GlLink,
GlBadge,
@@ -48,10 +48,11 @@ describe('ErrorDetails', () => {
const findUpdateResolveStatusButton = () =>
wrapper.find('[data-testid="update-resolve-status-btn"]');
const findExternalUrl = () => wrapper.find('[data-testid="external-url-link"]');
+ const findAlert = () => wrapper.find(GlAlert);
function mountComponent() {
wrapper = shallowMount(ErrorDetails, {
- stubs: { GlDeprecatedButton, GlSprintf },
+ stubs: { GlButton, GlSprintf },
localVue,
store,
mocks,
@@ -194,7 +195,7 @@ describe('ErrorDetails', () => {
expect(wrapper.find(GlLoadingIcon).exists()).toBe(true);
expect(wrapper.find(Stacktrace).exists()).toBe(false);
expect(wrapper.find(GlBadge).exists()).toBe(false);
- expect(wrapper.findAll(GlDeprecatedButton).length).toBe(3);
+ expect(wrapper.findAll(GlButton)).toHaveLength(3);
});
describe('unsafe chars for culprit field', () => {
@@ -278,15 +279,17 @@ describe('ErrorDetails', () => {
return wrapper.vm.$nextTick().then(() => {
expect(wrapper.find(GlLoadingIcon).exists()).toBe(false);
expect(wrapper.find(Stacktrace).exists()).toBe(true);
+ expect(findAlert().exists()).toBe(false);
});
});
- it('should NOT show stacktrace if no entries', () => {
+ it('should NOT show stacktrace if no entries and show Alert message', () => {
store.state.details.loadingStacktrace = false;
store.getters = { 'details/sentryUrl': () => 'sentry.io', 'details/stacktrace': () => [] };
return wrapper.vm.$nextTick().then(() => {
expect(wrapper.find(GlLoadingIcon).exists()).toBe(false);
expect(wrapper.find(Stacktrace).exists()).toBe(false);
+ expect(findAlert().text()).toBe('No stack trace for this error');
});
});
});
@@ -404,7 +407,6 @@ describe('ErrorDetails', () => {
});
it('should show alert with closed issueId', () => {
- const findAlert = () => wrapper.find(GlAlert);
const closedIssueId = 123;
wrapper.setData({
isAlertVisible: true,
diff --git a/spec/frontend/filtered_search/dropdown_user_spec.js b/spec/frontend/filtered_search/dropdown_user_spec.js
index 8eef10290bf..c1c09ea5d3e 100644
--- a/spec/frontend/filtered_search/dropdown_user_spec.js
+++ b/spec/frontend/filtered_search/dropdown_user_spec.js
@@ -48,13 +48,13 @@ describe('Dropdown User', () => {
};
const dropdown = new DropdownUser();
- expect(dropdown.config.AjaxFilter.endpoint).toBe('/autocomplete/users.json');
+ expect(dropdown.config.AjaxFilter.endpoint).toBe('/-/autocomplete/users.json');
});
it('should return endpoint when relative_url_root is undefined', () => {
const dropdown = new DropdownUser();
- expect(dropdown.config.AjaxFilter.endpoint).toBe('/autocomplete/users.json');
+ expect(dropdown.config.AjaxFilter.endpoint).toBe('/-/autocomplete/users.json');
});
it('should return endpoint with relative url when available', () => {
@@ -63,7 +63,9 @@ describe('Dropdown User', () => {
};
const dropdown = new DropdownUser();
- expect(dropdown.config.AjaxFilter.endpoint).toBe('/gitlab_directory/autocomplete/users.json');
+ expect(dropdown.config.AjaxFilter.endpoint).toBe(
+ '/gitlab_directory/-/autocomplete/users.json',
+ );
});
afterEach(() => {
diff --git a/spec/frontend/filtered_search/filtered_search_manager_spec.js b/spec/frontend/filtered_search/filtered_search_manager_spec.js
index ef87662a1ef..70e8b339d4b 100644
--- a/spec/frontend/filtered_search/filtered_search_manager_spec.js
+++ b/spec/frontend/filtered_search/filtered_search_manager_spec.js
@@ -2,7 +2,6 @@ import RecentSearchesService from '~/filtered_search/services/recent_searches_se
import RecentSearchesServiceError from '~/filtered_search/services/recent_searches_service_error';
import RecentSearchesRoot from '~/filtered_search/recent_searches_root';
import IssuableFilteredSearchTokenKeys from '~/filtered_search/issuable_filtered_search_token_keys';
-import '~/lib/utils/common_utils';
import DropdownUtils from '~/filtered_search/dropdown_utils';
import FilteredSearchVisualTokens from '~/filtered_search/filtered_search_visual_tokens';
import FilteredSearchDropdownManager from '~/filtered_search/filtered_search_dropdown_manager';
@@ -10,6 +9,7 @@ import FilteredSearchManager from '~/filtered_search/filtered_search_manager';
import FilteredSearchSpecHelper from '../helpers/filtered_search_spec_helper';
import { BACKSPACE_KEY_CODE, DELETE_KEY_CODE } from '~/lib/utils/keycodes';
import { visitUrl } from '~/lib/utils/url_utility';
+import * as commonUtils from '~/lib/utils/common_utils';
jest.mock('~/lib/utils/url_utility', () => ({
...jest.requireActual('~/lib/utils/url_utility'),
@@ -83,7 +83,7 @@ describe('Filtered Search Manager', () => {
jest
.spyOn(FilteredSearchDropdownManager.prototype, 'updateDropdownOffset')
.mockImplementation();
- jest.spyOn(gl.utils, 'getParameterByName').mockReturnValue(null);
+ jest.spyOn(commonUtils, 'getParameterByName').mockReturnValue(null);
jest.spyOn(FilteredSearchVisualTokens, 'unselectTokens');
input = document.querySelector('.filtered-search');
diff --git a/spec/frontend/filtered_search/stores/recent_searches_store_spec.js b/spec/frontend/filtered_search/stores/recent_searches_store_spec.js
index 56bb82ae941..320aaa99bcc 100644
--- a/spec/frontend/filtered_search/stores/recent_searches_store_spec.js
+++ b/spec/frontend/filtered_search/stores/recent_searches_store_spec.js
@@ -44,6 +44,15 @@ describe('RecentSearchesStore', () => {
expect(store.state.recentSearches).toEqual(['baz', 'qux']);
});
+ it('handles non-string values', () => {
+ store.setRecentSearches(['foo ', { foo: 'bar' }, { foo: 'bar' }, ['foobar']]);
+
+ // 1. String values will be trimmed of leading/trailing spaces
+ // 2. Comparison will account for objects to remove duplicates
+ // 3. Old behaviour of handling string values stays as it is.
+ expect(store.state.recentSearches).toEqual(['foo', { foo: 'bar' }, ['foobar']]);
+ });
+
it('only keeps track of 5 items', () => {
store.setRecentSearches(['1', '2', '3', '4', '5', '6', '7']);
diff --git a/spec/frontend/filtered_search/visual_token_value_spec.js b/spec/frontend/filtered_search/visual_token_value_spec.js
index ea501423403..3a64b688c7a 100644
--- a/spec/frontend/filtered_search/visual_token_value_spec.js
+++ b/spec/frontend/filtered_search/visual_token_value_spec.js
@@ -4,6 +4,7 @@ import AjaxCache from '~/lib/utils/ajax_cache';
import UsersCache from '~/lib/utils/users_cache';
import DropdownUtils from '~/filtered_search//dropdown_utils';
import FilteredSearchSpecHelper from '../helpers/filtered_search_spec_helper';
+import { TEST_HOST } from 'jest/helpers/test_constants';
describe('Filtered Search Visual Tokens', () => {
const findElements = tokenElement => {
@@ -106,7 +107,7 @@ describe('Filtered Search Visual Tokens', () => {
it('escapes user name when creating token', done => {
const dummyUser = {
name: '<script>',
- avatar_url: `${gl.TEST_HOST}/mypics/avatar.png`,
+ avatar_url: `${TEST_HOST}/mypics/avatar.png`,
};
const { subject, tokenValueContainer, tokenValueElement } = findElements(authorToken);
const tokenValue = tokenValueElement.innerText;
diff --git a/spec/frontend/fixtures/branches.rb b/spec/frontend/fixtures/branches.rb
index 4667dfb69f8..df2d1af7ecf 100644
--- a/spec/frontend/fixtures/branches.rb
+++ b/spec/frontend/fixtures/branches.rb
@@ -2,33 +2,51 @@
require 'spec_helper'
-RSpec.describe Projects::BranchesController, '(JavaScript fixtures)', type: :controller do
+RSpec.describe 'Branches (JavaScript fixtures)' do
include JavaScriptFixturesHelpers
- let(:admin) { create(:admin) }
- let(:namespace) { create(:namespace, name: 'frontend-fixtures' )}
- let(:project) { create(:project, :repository, namespace: namespace, path: 'branches-project') }
-
- render_views
+ let_it_be(:admin) { create(:admin) }
+ let_it_be(:namespace) { create(:namespace, name: 'frontend-fixtures' )}
+ let_it_be(:project) { create(:project, :repository, namespace: namespace, path: 'branches-project') }
before(:all) do
clean_frontend_fixtures('branches/')
+ clean_frontend_fixtures('api/branches/')
end
- before do
- sign_in(admin)
+ after(:all) do
+ remove_repository(project)
end
- after do
- remove_repository(project)
+ describe Projects::BranchesController, '(JavaScript fixtures)', type: :controller do
+ render_views
+
+ before do
+ sign_in(admin)
+ end
+
+ it 'branches/new_branch.html' do
+ get :new, params: {
+ namespace_id: project.namespace.to_param,
+ project_id: project
+ }
+
+ expect(response).to be_successful
+ end
end
- it 'branches/new_branch.html' do
- get :new, params: {
- namespace_id: project.namespace.to_param,
- project_id: project
- }
+ describe API::Branches, '(JavaScript fixtures)', type: :request do
+ include ApiHelpers
+
+ it 'api/branches/branches.json' do
+ # The search query "ma" matches a few branch names in the test
+ # repository with a variety of different properties, including:
+ # - "master": default, protected
+ # - "markdown": non-default, protected
+ # - "many_files": non-default, not protected
+ get api("/projects/#{project.id}/repository/branches?search=ma", admin)
- expect(response).to be_successful
+ expect(response).to be_successful
+ end
end
end
diff --git a/spec/frontend/fixtures/commit.rb b/spec/frontend/fixtures/commit.rb
index c5c00afd4ca..9175a757b73 100644
--- a/spec/frontend/fixtures/commit.rb
+++ b/spec/frontend/fixtures/commit.rb
@@ -2,34 +2,55 @@
require 'spec_helper'
-RSpec.describe Projects::CommitController, '(JavaScript fixtures)', type: :controller do
+RSpec.describe 'Commit (JavaScript fixtures)' do
include JavaScriptFixturesHelpers
let_it_be(:project) { create(:project, :repository) }
let_it_be(:user) { create(:user) }
- let(:commit) { project.commit("master") }
-
- render_views
+ let_it_be(:commit) { project.commit("master") }
before(:all) do
clean_frontend_fixtures('commit/')
+ clean_frontend_fixtures('api/commits/')
+
+ project.add_maintainer(user)
end
before do
- project.add_maintainer(user)
- sign_in(user)
allow(SecureRandom).to receive(:hex).and_return('securerandomhex:thereisnospoon')
end
- it 'commit/show.html' do
- params = {
- namespace_id: project.namespace,
- project_id: project,
- id: commit.id
- }
+ after(:all) do
+ remove_repository(project)
+ end
+
+ describe Projects::CommitController, '(JavaScript fixtures)', type: :controller do
+ render_views
+
+ before do
+ sign_in(user)
+ end
+
+ it 'commit/show.html' do
+ params = {
+ namespace_id: project.namespace,
+ project_id: project,
+ id: commit.id
+ }
+
+ get :show, params: params
+
+ expect(response).to be_successful
+ end
+ end
+
+ describe API::Commits, '(JavaScript fixtures)', type: :request do
+ include ApiHelpers
- get :show, params: params
+ it 'api/commits/commit.json' do
+ get api("/projects/#{project.id}/repository/commits/#{commit.id}", user)
- expect(response).to be_successful
+ expect(response).to be_successful
+ end
end
end
diff --git a/spec/frontend/fixtures/emojis.rb b/spec/frontend/fixtures/emojis.rb
new file mode 100644
index 00000000000..b95c7632917
--- /dev/null
+++ b/spec/frontend/fixtures/emojis.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'Emojis (JavaScript fixtures)', type: :request do
+ include JavaScriptFixturesHelpers
+
+ before(:all) do
+ clean_frontend_fixtures('emojis/')
+ end
+
+ it 'emojis/emojis.json' do |example|
+ get '/-/emojis/1/emojis.json'
+
+ expect(response).to be_successful
+ end
+end
diff --git a/spec/frontend/fixtures/metrics_dashboard.rb b/spec/frontend/fixtures/metrics_dashboard.rb
index b5dee7525f6..6ee730f5c3d 100644
--- a/spec/frontend/fixtures/metrics_dashboard.rb
+++ b/spec/frontend/fixtures/metrics_dashboard.rb
@@ -6,10 +6,11 @@ RSpec.describe MetricsDashboard, '(JavaScript fixtures)', type: :controller do
include JavaScriptFixturesHelpers
include MetricsDashboardHelpers
- let(:user) { create(:user) }
- let(:project) { project_with_dashboard('.gitlab/dashboards/test.yml') }
- let(:environment) { create(:environment, project: project) }
- let(:params) { { environment: environment } }
+ let_it_be(:user) { create(:user) }
+ let_it_be(:namespace) { create(:namespace, name: 'monitoring' )}
+ let_it_be(:project) { project_with_dashboard_namespace('.gitlab/dashboards/test.yml', namespace: namespace) }
+ let_it_be(:environment) { create(:environment, id: 1, project: project) }
+ let_it_be(:params) { { environment: environment } }
before(:all) do
clean_frontend_fixtures('metrics_dashboard/')
@@ -24,6 +25,7 @@ RSpec.describe MetricsDashboard, '(JavaScript fixtures)', type: :controller do
project.add_maintainer(user)
allow(controller).to receive(:project).and_return(project)
+ allow(controller).to receive(:environment).and_return(environment)
allow(controller)
.to receive(:metrics_dashboard_params)
.and_return(params)
@@ -35,7 +37,9 @@ RSpec.describe MetricsDashboard, '(JavaScript fixtures)', type: :controller do
it 'metrics_dashboard/environment_metrics_dashboard.json' do
routes.draw { get "metrics_dashboard" => "anonymous#metrics_dashboard" }
+
response = get :metrics_dashboard, format: :json
+
expect(response).to be_successful
end
end
diff --git a/spec/frontend/fixtures/services.rb b/spec/frontend/fixtures/services.rb
index 0877998cc9d..43230301296 100644
--- a/spec/frontend/fixtures/services.rb
+++ b/spec/frontend/fixtures/services.rb
@@ -8,7 +8,7 @@ RSpec.describe Projects::ServicesController, '(JavaScript fixtures)', type: :con
let(:admin) { create(:admin) }
let(:namespace) { create(:namespace, name: 'frontend-fixtures' )}
let(:project) { create(:project_empty_repo, namespace: namespace, path: 'services-project') }
- let!(:service) { create(:custom_issue_tracker_service, project: project, title: 'Custom Issue Tracker') }
+ let!(:service) { create(:custom_issue_tracker_service, project: project) }
render_views
diff --git a/spec/frontend/fixtures/static/mini_dropdown_graph.html b/spec/frontend/fixtures/static/mini_dropdown_graph.html
index cd0b8dec3fc..cb55698b709 100644
--- a/spec/frontend/fixtures/static/mini_dropdown_graph.html
+++ b/spec/frontend/fixtures/static/mini_dropdown_graph.html
@@ -1,13 +1,13 @@
-<div class="js-builds-dropdown-tests dropdown dropdown js-mini-pipeline-graph">
-<button class="js-builds-dropdown-button" data-toggle="dropdown" data-stage-endpoint="foobar">
-Dropdown
-</button>
-<ul class="dropdown-menu mini-pipeline-graph-dropdown-menu js-builds-dropdown-container">
-<li class="js-builds-dropdown-list scrollable-menu">
-<ul></ul>
-</li>
-<li class="js-builds-dropdown-loading hidden">
-<span class="fa fa-spinner"></span>
-</li>
-</ul>
+<div class="js-builds-dropdown-tests dropdown dropdown" data-testid="widget-mini-pipeline-graph">
+ <button class="js-builds-dropdown-button" data-toggle="dropdown" data-stage-endpoint="foobar">
+ Dropdown
+ </button>
+ <ul class="dropdown-menu mini-pipeline-graph-dropdown-menu js-builds-dropdown-container">
+ <li class="js-builds-dropdown-list scrollable-menu">
+ <ul></ul>
+ </li>
+ <li class="js-builds-dropdown-loading hidden">
+ <span class="fa fa-spinner"></span>
+ </li>
+ </ul>
</div>
diff --git a/spec/frontend/fixtures/static/global_search_input.html b/spec/frontend/fixtures/static/search_autocomplete.html
index 29db9020424..29db9020424 100644
--- a/spec/frontend/fixtures/static/global_search_input.html
+++ b/spec/frontend/fixtures/static/search_autocomplete.html
diff --git a/spec/frontend/fixtures/tags.rb b/spec/frontend/fixtures/tags.rb
new file mode 100644
index 00000000000..b2a5429fac8
--- /dev/null
+++ b/spec/frontend/fixtures/tags.rb
@@ -0,0 +1,28 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'Tags (JavaScript fixtures)' do
+ include JavaScriptFixturesHelpers
+
+ let_it_be(:admin) { create(:admin) }
+ let_it_be(:project) { create(:project, :repository, path: 'tags-project') }
+
+ before(:all) do
+ clean_frontend_fixtures('api/tags/')
+ end
+
+ after(:all) do
+ remove_repository(project)
+ end
+
+ describe API::Tags, '(JavaScript fixtures)', type: :request do
+ include ApiHelpers
+
+ it 'api/tags/tags.json' do
+ get api("/projects/#{project.id}/repository/tags", admin)
+
+ expect(response).to be_successful
+ end
+ end
+end
diff --git a/spec/frontend/gfm_auto_complete_spec.js b/spec/frontend/gfm_auto_complete_spec.js
index f58615000ee..869347128e5 100644
--- a/spec/frontend/gfm_auto_complete_spec.js
+++ b/spec/frontend/gfm_auto_complete_spec.js
@@ -312,7 +312,7 @@ describe('GfmAutoComplete', () => {
title: 'My Group',
search: 'my-group My Group',
icon:
- '<svg class="s16 vertical-align-middle prepend-left-5"><use xlink:href="undefined#notifications-off" /></svg>',
+ '<svg class="s16 vertical-align-middle gl-ml-2"><use xlink:href="undefined#notifications-off" /></svg>',
},
]);
});
diff --git a/spec/frontend/gl_form_spec.js b/spec/frontend/gl_form_spec.js
index 150d8a053d5..52e1693f8a6 100644
--- a/spec/frontend/gl_form_spec.js
+++ b/spec/frontend/gl_form_spec.js
@@ -9,6 +9,8 @@ describe('GLForm', () => {
describe('when instantiated', () => {
beforeEach(done => {
+ window.gl = window.gl || {};
+
testContext.form = $('<form class="gfm-form"><textarea class="js-gfm-input"></form>');
testContext.textarea = testContext.form.find('textarea');
jest.spyOn($.prototype, 'off').mockReturnValue(testContext.textarea);
@@ -111,5 +113,21 @@ describe('GLForm', () => {
expect(autosize.destroy).not.toHaveBeenCalled();
});
});
+
+ describe('supportsQuickActions', () => {
+ it('should return false if textarea does not support quick actions', () => {
+ const glForm = new GLForm(testContext.form, false);
+
+ expect(glForm.supportsQuickActions).toEqual(false);
+ });
+
+ it('should return true if textarea supports quick actions', () => {
+ testContext.textarea.attr('data-supports-quick-actions', true);
+
+ const glForm = new GLForm(testContext.form, false);
+
+ expect(glForm.supportsQuickActions).toEqual(true);
+ });
+ });
});
});
diff --git a/spec/frontend/global_search_input_spec.js b/spec/frontend/global_search_input_spec.js
deleted file mode 100644
index 8c00ea5f193..00000000000
--- a/spec/frontend/global_search_input_spec.js
+++ /dev/null
@@ -1,215 +0,0 @@
-/* eslint-disable no-unused-expressions, consistent-return, no-param-reassign, default-case, no-return-assign */
-
-import $ from 'jquery';
-import '~/gl_dropdown';
-import initGlobalSearchInput from '~/global_search_input';
-import '~/lib/utils/common_utils';
-
-describe('Global search input dropdown', () => {
- let widget = null;
-
- const userName = 'root';
-
- const userId = 1;
-
- const dashboardIssuesPath = '/dashboard/issues';
-
- const dashboardMRsPath = '/dashboard/merge_requests';
-
- const projectIssuesPath = '/gitlab-org/gitlab-foss/issues';
-
- const projectMRsPath = '/gitlab-org/gitlab-foss/-/merge_requests';
-
- const groupIssuesPath = '/groups/gitlab-org/-/issues';
-
- const groupMRsPath = '/groups/gitlab-org/-/merge_requests';
-
- const projectName = 'GitLab Community Edition';
-
- const groupName = 'Gitlab Org';
-
- const removeBodyAttributes = () => {
- const $body = $('body');
-
- $body.removeAttr('data-page');
- $body.removeAttr('data-project');
- $body.removeAttr('data-group');
- };
-
- // Add required attributes to body before starting the test.
- // section would be dashboard|group|project
- const addBodyAttributes = section => {
- if (section == null) {
- section = 'dashboard';
- }
-
- const $body = $('body');
- removeBodyAttributes();
- switch (section) {
- case 'dashboard':
- return $body.attr('data-page', 'root:index');
- case 'group':
- $body.attr('data-page', 'groups:show');
- return $body.data('group', 'gitlab-org');
- case 'project':
- $body.attr('data-page', 'projects:show');
- return $body.data('project', 'gitlab-ce');
- }
- };
-
- const disableProjectIssues = () => {
- document.querySelector('.js-search-project-options').setAttribute('data-issues-disabled', true);
- };
-
- // Mock `gl` object in window for dashboard specific page. App code will need it.
- const mockDashboardOptions = () => {
- window.gl || (window.gl = {});
- return (window.gl.dashboardOptions = {
- issuesPath: dashboardIssuesPath,
- mrPath: dashboardMRsPath,
- });
- };
-
- // Mock `gl` object in window for project specific page. App code will need it.
- const mockProjectOptions = () => {
- window.gl || (window.gl = {});
- return (window.gl.projectOptions = {
- 'gitlab-ce': {
- issuesPath: projectIssuesPath,
- mrPath: projectMRsPath,
- projectName,
- },
- });
- };
-
- const mockGroupOptions = () => {
- window.gl || (window.gl = {});
- return (window.gl.groupOptions = {
- 'gitlab-org': {
- issuesPath: groupIssuesPath,
- mrPath: groupMRsPath,
- projectName: groupName,
- },
- });
- };
-
- const assertLinks = (list, issuesPath, mrsPath) => {
- if (issuesPath) {
- const issuesAssignedToMeLink = `a[href="${issuesPath}/?assignee_username=${userName}"]`;
- const issuesIHaveCreatedLink = `a[href="${issuesPath}/?author_username=${userName}"]`;
-
- expect(list.find(issuesAssignedToMeLink).length).toBe(1);
- expect(list.find(issuesAssignedToMeLink).text()).toBe('Issues assigned to me');
- expect(list.find(issuesIHaveCreatedLink).length).toBe(1);
- expect(list.find(issuesIHaveCreatedLink).text()).toBe("Issues I've created");
- }
- const mrsAssignedToMeLink = `a[href="${mrsPath}/?assignee_username=${userName}"]`;
- const mrsIHaveCreatedLink = `a[href="${mrsPath}/?author_username=${userName}"]`;
-
- expect(list.find(mrsAssignedToMeLink).length).toBe(1);
- expect(list.find(mrsAssignedToMeLink).text()).toBe('Merge requests assigned to me');
- expect(list.find(mrsIHaveCreatedLink).length).toBe(1);
- expect(list.find(mrsIHaveCreatedLink).text()).toBe("Merge requests I've created");
- };
-
- preloadFixtures('static/global_search_input.html');
- beforeEach(() => {
- loadFixtures('static/global_search_input.html');
-
- window.gon = {};
- window.gon.current_user_id = userId;
- window.gon.current_username = userName;
-
- return (widget = initGlobalSearchInput());
- });
-
- afterEach(() => {
- // Undo what we did to the shared <body>
- removeBodyAttributes();
- window.gon = {};
- });
-
- it('should show Dashboard specific dropdown menu', () => {
- addBodyAttributes();
- mockDashboardOptions();
- widget.searchInput.triggerHandler('focus');
- const list = widget.wrap.find('.dropdown-menu').find('ul');
- return assertLinks(list, dashboardIssuesPath, dashboardMRsPath);
- });
-
- it('should show Group specific dropdown menu', () => {
- addBodyAttributes('group');
- mockGroupOptions();
- widget.searchInput.triggerHandler('focus');
- const list = widget.wrap.find('.dropdown-menu').find('ul');
- return assertLinks(list, groupIssuesPath, groupMRsPath);
- });
-
- it('should show Project specific dropdown menu', () => {
- addBodyAttributes('project');
- mockProjectOptions();
- widget.searchInput.triggerHandler('focus');
- const list = widget.wrap.find('.dropdown-menu').find('ul');
- return assertLinks(list, projectIssuesPath, projectMRsPath);
- });
-
- it('should show only Project mergeRequest dropdown menu items when project issues are disabled', () => {
- addBodyAttributes('project');
- disableProjectIssues();
- mockProjectOptions();
- widget.searchInput.triggerHandler('focus');
- const list = widget.wrap.find('.dropdown-menu').find('ul');
- assertLinks(list, null, projectMRsPath);
- });
-
- it('should not show category related menu if there is text in the input', () => {
- addBodyAttributes('project');
- mockProjectOptions();
- widget.searchInput.val('help');
- widget.searchInput.triggerHandler('focus');
- const list = widget.wrap.find('.dropdown-menu').find('ul');
- const link = `a[href='${projectIssuesPath}/?assignee_username=${userName}']`;
-
- expect(list.find(link).length).toBe(0);
- });
-
- it('should not submit the search form when selecting an autocomplete row with the keyboard', () => {
- const ENTER = 13;
- const DOWN = 40;
- addBodyAttributes();
- mockDashboardOptions(true);
- const submitSpy = jest.spyOn(document.querySelector('form'), 'submit');
- widget.searchInput.triggerHandler('focus');
- widget.wrap.trigger($.Event('keydown', { which: DOWN }));
- const enterKeyEvent = $.Event('keydown', { which: ENTER });
- widget.searchInput.trigger(enterKeyEvent);
- // This does not currently catch failing behavior. For security reasons,
- // browsers will not trigger default behavior (form submit, in this
- // example) on JavaScript-created keypresses.
- expect(submitSpy).not.toHaveBeenCalled();
- });
-
- describe('disableDropdown', () => {
- beforeEach(() => {
- widget.enableDropdown();
- });
-
- it('should close the Dropdown', () => {
- const toggleSpy = jest.spyOn(widget.dropdownToggle, 'dropdown');
-
- widget.dropdown.addClass('show');
- widget.disableDropdown();
-
- expect(toggleSpy).toHaveBeenCalledWith('toggle');
- });
- });
-
- describe('enableDropdown', () => {
- it('should open the Dropdown', () => {
- const toggleSpy = jest.spyOn(widget.dropdownToggle, 'dropdown');
- widget.enableDropdown();
-
- expect(toggleSpy).toHaveBeenCalledWith('toggle');
- });
- });
-});
diff --git a/spec/frontend/helpers/event_hub_factory_spec.js b/spec/frontend/helpers/event_hub_factory_spec.js
index dcfec6b836a..c4f63ff6049 100644
--- a/spec/frontend/helpers/event_hub_factory_spec.js
+++ b/spec/frontend/helpers/event_hub_factory_spec.js
@@ -1,77 +1,72 @@
import createEventHub from '~/helpers/event_hub_factory';
+const TEST_EVENT = 'foobar';
+const TEST_EVENT_2 = 'testevent';
+
describe('event bus factory', () => {
let eventBus;
+ let handler;
+ let otherHandlers;
beforeEach(() => {
eventBus = createEventHub();
+ handler = jest.fn();
+ otherHandlers = [jest.fn(), jest.fn()];
});
afterEach(() => {
+ eventBus.dispose();
eventBus = null;
});
- describe('underlying module', () => {
- let mitt;
+ describe('instance', () => {
+ it.each`
+ method
+ ${'$on'}
+ ${'$once'}
+ ${'$off'}
+ ${'$emit'}
+ `('has $method method', ({ method }) => {
+ expect(eventBus[method]).toEqual(expect.any(Function));
+ });
+ });
+ describe('$on', () => {
beforeEach(() => {
- jest.resetModules();
- jest.mock('mitt');
-
- // eslint-disable-next-line global-require
- mitt = require('mitt');
- mitt.mockReturnValue(() => ({}));
-
- const createEventHubActual = jest.requireActual('~/helpers/event_hub_factory').default;
- eventBus = createEventHubActual();
+ eventBus.$on(TEST_EVENT, handler);
});
- it('creates an emitter', () => {
- expect(mitt).toHaveBeenCalled();
+ it('calls handler when event is emitted', () => {
+ eventBus.$emit(TEST_EVENT);
+ expect(handler).toHaveBeenCalledWith();
});
- });
- describe('instance', () => {
- it.each`
- method
- ${'on'}
- ${'once'}
- ${'off'}
- ${'emit'}
- `('binds $$method to $method ', ({ method }) => {
- expect(typeof eventBus[method]).toBe('function');
- expect(eventBus[method]).toBe(eventBus[`$${method}`]);
+ it('calls handler with multiple args', () => {
+ eventBus.$emit(TEST_EVENT, 'arg1', 'arg2', 'arg3');
+ expect(handler).toHaveBeenCalledWith('arg1', 'arg2', 'arg3');
});
- });
- describe('once', () => {
- const event = 'foobar';
- let handler;
+ it('calls handler multiple times', () => {
+ eventBus.$emit(TEST_EVENT, 'arg1', 'arg2', 'arg3');
+ eventBus.$emit(TEST_EVENT, 'arg1', 'arg2', 'arg3');
- beforeEach(() => {
- jest.spyOn(eventBus, 'on');
- jest.spyOn(eventBus, 'off');
- handler = jest.fn();
- eventBus.once(event, handler);
+ expect(handler).toHaveBeenCalledTimes(2);
});
+ });
- it('calls on internally', () => {
- expect(eventBus.on).toHaveBeenCalled();
+ describe('$once', () => {
+ beforeEach(() => {
+ eventBus.$once(TEST_EVENT, handler);
});
it('calls handler when event is emitted', () => {
- eventBus.emit(event);
+ eventBus.$emit(TEST_EVENT);
expect(handler).toHaveBeenCalled();
});
- it('calls off when event is emitted', () => {
- eventBus.emit(event);
- expect(eventBus.off).toHaveBeenCalled();
- });
-
it('calls the handler only once when event is emitted multiple times', () => {
- eventBus.emit(event);
- eventBus.emit(event);
+ eventBus.$emit(TEST_EVENT);
+ eventBus.$emit(TEST_EVENT);
expect(handler).toHaveBeenCalledTimes(1);
});
@@ -80,15 +75,70 @@ describe('event bus factory', () => {
handler = jest.fn().mockImplementation(() => {
throw new Error();
});
- eventBus.once(event, handler);
+ eventBus.$once(TEST_EVENT, handler);
});
it('calls off when event is emitted', () => {
expect(() => {
- eventBus.emit(event);
+ eventBus.$emit(TEST_EVENT);
}).toThrow();
- expect(eventBus.off).toHaveBeenCalled();
+ expect(() => {
+ eventBus.$emit(TEST_EVENT);
+ }).not.toThrow();
+
+ expect(handler).toHaveBeenCalledTimes(1);
});
});
});
+
+ describe('$off', () => {
+ beforeEach(() => {
+ otherHandlers.forEach(x => eventBus.$on(TEST_EVENT, x));
+ eventBus.$on(TEST_EVENT, handler);
+ });
+
+ it('can be called on event with no handlers', () => {
+ expect(() => {
+ eventBus.$off(TEST_EVENT_2);
+ }).not.toThrow();
+ });
+
+ it('can be called on event with no handlers, with a handler', () => {
+ expect(() => {
+ eventBus.$off(TEST_EVENT_2, handler);
+ }).not.toThrow();
+ });
+
+ it('with a handler, will no longer call that handler', () => {
+ eventBus.$off(TEST_EVENT, handler);
+
+ eventBus.$emit(TEST_EVENT);
+
+ expect(handler).not.toHaveBeenCalled();
+ expect(otherHandlers.map(x => x.mock.calls.length)).toEqual(otherHandlers.map(() => 1));
+ });
+
+ it('without a handler, will no longer call any handlers', () => {
+ eventBus.$off(TEST_EVENT);
+
+ eventBus.$emit(TEST_EVENT);
+
+ expect(handler).not.toHaveBeenCalled();
+ expect(otherHandlers.map(x => x.mock.calls.length)).toEqual(otherHandlers.map(() => 0));
+ });
+ });
+
+ describe('$emit', () => {
+ beforeEach(() => {
+ otherHandlers.forEach(x => eventBus.$on(TEST_EVENT_2, x));
+ eventBus.$on(TEST_EVENT, handler);
+ });
+
+ it('only calls handlers for given type', () => {
+ eventBus.$emit(TEST_EVENT, 'arg1');
+
+ expect(handler).toHaveBeenCalledWith('arg1');
+ expect(otherHandlers.map(x => x.mock.calls.length)).toEqual(otherHandlers.map(() => 0));
+ });
+ });
});
diff --git a/spec/frontend/helpers/fake_request_animation_frame.js b/spec/frontend/helpers/fake_request_animation_frame.js
new file mode 100644
index 00000000000..b01ae5b7c5f
--- /dev/null
+++ b/spec/frontend/helpers/fake_request_animation_frame.js
@@ -0,0 +1,13 @@
+// eslint-disable-next-line import/prefer-default-export
+export const useFakeRequestAnimationFrame = () => {
+ let orig;
+
+ beforeEach(() => {
+ orig = global.requestAnimationFrame;
+ global.requestAnimationFrame = cb => cb();
+ });
+
+ afterEach(() => {
+ global.requestAnimationFrame = orig;
+ });
+};
diff --git a/spec/frontend/helpers/init_vue_mr_page_helper.js b/spec/frontend/helpers/init_vue_mr_page_helper.js
new file mode 100644
index 00000000000..c1d608cc5a0
--- /dev/null
+++ b/spec/frontend/helpers/init_vue_mr_page_helper.js
@@ -0,0 +1,46 @@
+import MockAdapter from 'axios-mock-adapter';
+import initMRPage from '~/mr_notes';
+import axios from '~/lib/utils/axios_utils';
+import { userDataMock, notesDataMock, noteableDataMock } from '../notes/mock_data';
+import diffFileMockData from '../diffs/mock_data/diff_file';
+
+export default function initVueMRPage() {
+ const mrTestEl = document.createElement('div');
+ mrTestEl.className = 'js-merge-request-test';
+ document.body.appendChild(mrTestEl);
+
+ const diffsAppEndpoint = '/diffs/app/endpoint';
+ const diffsAppProjectPath = 'testproject';
+ const mrEl = document.createElement('div');
+ mrEl.className = 'merge-request fixture-mr';
+ mrEl.setAttribute('data-mr-action', 'diffs');
+ mrTestEl.appendChild(mrEl);
+
+ const mrDiscussionsEl = document.createElement('div');
+ mrDiscussionsEl.id = 'js-vue-mr-discussions';
+ mrDiscussionsEl.setAttribute('data-current-user-data', JSON.stringify(userDataMock));
+ mrDiscussionsEl.setAttribute('data-noteable-data', JSON.stringify(noteableDataMock));
+ mrDiscussionsEl.setAttribute('data-notes-data', JSON.stringify(notesDataMock));
+ mrDiscussionsEl.setAttribute('data-noteable-type', 'merge-request');
+ mrTestEl.appendChild(mrDiscussionsEl);
+
+ const discussionCounterEl = document.createElement('div');
+ discussionCounterEl.id = 'js-vue-discussion-counter';
+ mrTestEl.appendChild(discussionCounterEl);
+
+ const diffsAppEl = document.createElement('div');
+ diffsAppEl.id = 'js-diffs-app';
+ diffsAppEl.setAttribute('data-endpoint', diffsAppEndpoint);
+ diffsAppEl.setAttribute('data-project-path', diffsAppProjectPath);
+ diffsAppEl.setAttribute('data-current-user-data', JSON.stringify(userDataMock));
+ mrTestEl.appendChild(diffsAppEl);
+
+ const mock = new MockAdapter(axios);
+ mock.onGet(diffsAppEndpoint).reply(200, {
+ branch_name: 'foo',
+ diff_files: [diffFileMockData],
+ });
+
+ initMRPage();
+ return mock;
+}
diff --git a/spec/frontend/helpers/monitor_helper_spec.js b/spec/frontend/helpers/monitor_helper_spec.js
index f7163d496d2..083b6404125 100644
--- a/spec/frontend/helpers/monitor_helper_spec.js
+++ b/spec/frontend/helpers/monitor_helper_spec.js
@@ -33,15 +33,6 @@ describe('monitor helper', () => {
]);
});
- it('excludes NaN values', () => {
- expect(
- monitorHelper.makeDataSeries(
- data({ metric: {}, values: [[1, 1], [2, NaN]] }),
- defaultConfig,
- ),
- ).toEqual([{ ...expectedDataSeries[0], data: [[1, 1]] }]);
- });
-
it('updates series name from templates', () => {
const config = {
...defaultConfig,
diff --git a/spec/frontend/helpers/test_constants.js b/spec/frontend/helpers/test_constants.js
index c97d47a6406..69b78f556aa 100644
--- a/spec/frontend/helpers/test_constants.js
+++ b/spec/frontend/helpers/test_constants.js
@@ -1,7 +1,19 @@
-export const FIXTURES_PATH = `/fixtures`;
-export const TEST_HOST = 'http://test.host';
+const FIXTURES_PATH = `/fixtures`;
+const TEST_HOST = 'http://test.host';
-export const DUMMY_IMAGE_URL = `${FIXTURES_PATH}/static/images/one_white_pixel.png`;
+const DUMMY_IMAGE_URL = `${FIXTURES_PATH}/static/images/one_white_pixel.png`;
-export const GREEN_BOX_IMAGE_URL = `${FIXTURES_PATH}/static/images/green_box.png`;
-export const RED_BOX_IMAGE_URL = `${FIXTURES_PATH}/static/images/red_box.png`;
+const GREEN_BOX_IMAGE_URL = `${FIXTURES_PATH}/static/images/green_box.png`;
+const RED_BOX_IMAGE_URL = `${FIXTURES_PATH}/static/images/red_box.png`;
+
+// NOTE: module.exports is needed so that this file can be used
+// by environment.js
+//
+// eslint-disable-next-line import/no-commonjs
+module.exports = {
+ FIXTURES_PATH,
+ TEST_HOST,
+ DUMMY_IMAGE_URL,
+ GREEN_BOX_IMAGE_URL,
+ RED_BOX_IMAGE_URL,
+};
diff --git a/spec/frontend/helpers/vue_mock_directive.js b/spec/frontend/helpers/vue_mock_directive.js
index 699fe3eab26..28d4708835d 100644
--- a/spec/frontend/helpers/vue_mock_directive.js
+++ b/spec/frontend/helpers/vue_mock_directive.js
@@ -2,13 +2,21 @@ export const getKey = name => `$_gl_jest_${name}`;
export const getBinding = (el, name) => el[getKey(name)];
+const writeBindingToElement = (el, { name, value, arg, modifiers }) => {
+ el[getKey(name)] = {
+ value,
+ arg,
+ modifiers,
+ };
+};
+
export const createMockDirective = () => ({
- bind(el, { name, value, arg, modifiers }) {
- el[getKey(name)] = {
- value,
- arg,
- modifiers,
- };
+ bind(el, binding) {
+ writeBindingToElement(el, binding);
+ },
+
+ update(el, binding) {
+ writeBindingToElement(el, binding);
},
unbind(el, { name }) {
diff --git a/spec/frontend/helpers/wait_using_real_timer.js b/spec/frontend/helpers/wait_using_real_timer.js
new file mode 100644
index 00000000000..ddf23cd97b4
--- /dev/null
+++ b/spec/frontend/helpers/wait_using_real_timer.js
@@ -0,0 +1,7 @@
+/* useful for timing promises when jest fakeTimers are not reliable enough */
+export default timeout =>
+ new Promise(resolve => {
+ jest.useRealTimers();
+ setTimeout(resolve, timeout);
+ jest.useFakeTimers();
+ });
diff --git a/spec/frontend/ide/commit_icon_spec.js b/spec/frontend/ide/commit_icon_spec.js
index 90b8e34497c..e4a7394b089 100644
--- a/spec/frontend/ide/commit_icon_spec.js
+++ b/spec/frontend/ide/commit_icon_spec.js
@@ -11,7 +11,6 @@ const createFile = (name = 'name', id = name, type = '', parent = null) =>
name,
path: parent ? `${parent.path}/${name}` : name,
parentPath: parent ? parent.path : '',
- lastCommit: {},
});
describe('getCommitIconMap', () => {
diff --git a/spec/frontend/ide/components/ide_status_list_spec.js b/spec/frontend/ide/components/ide_status_list_spec.js
index 847464ed806..fed61233e55 100644
--- a/spec/frontend/ide/components/ide_status_list_spec.js
+++ b/spec/frontend/ide/components/ide_status_list_spec.js
@@ -1,5 +1,6 @@
import Vuex from 'vuex';
import { createLocalVue, shallowMount } from '@vue/test-utils';
+import { GlLink } from '@gitlab/ui';
import IdeStatusList from '~/ide/components/ide_status_list.vue';
import TerminalSyncStatusSafe from '~/ide/components/terminal_sync/terminal_sync_status_safe.vue';
@@ -9,6 +10,7 @@ const TEST_FILE = {
editorColumn: 23,
fileLanguage: 'markdown',
content: 'abc\nndef',
+ permalink: '/lorem.md',
};
const localVue = createLocalVue();
@@ -19,6 +21,7 @@ describe('ide/components/ide_status_list', () => {
let store;
let wrapper;
+ const findLink = () => wrapper.find(GlLink);
const createComponent = (options = {}) => {
store = new Vuex.Store({
getters: {
@@ -51,8 +54,9 @@ describe('ide/components/ide_status_list', () => {
createComponent();
});
- it('shows file name', () => {
- expect(wrapper.text()).toContain(TEST_FILE.name);
+ it('shows a link to the file that contains the file name', () => {
+ expect(findLink().attributes('href')).toBe(TEST_FILE.permalink);
+ expect(findLink().text()).toBe(TEST_FILE.name);
});
it('shows file eol', () => {
diff --git a/spec/frontend/ide/components/jobs/__snapshots__/stage_spec.js.snap b/spec/frontend/ide/components/jobs/__snapshots__/stage_spec.js.snap
index bdd3d439fd4..dbfacb98813 100644
--- a/spec/frontend/ide/components/jobs/__snapshots__/stage_spec.js.snap
+++ b/spec/frontend/ide/components/jobs/__snapshots__/stage_spec.js.snap
@@ -2,7 +2,7 @@
exports[`IDE pipeline stage renders stage details & icon 1`] = `
<div
- class="ide-stage card prepend-top-default"
+ class="ide-stage card gl-mt-3"
>
<div
class="card-header"
diff --git a/spec/frontend/ide/components/repo_editor_spec.js b/spec/frontend/ide/components/repo_editor_spec.js
index 4967434dfd7..a4336b8f2eb 100644
--- a/spec/frontend/ide/components/repo_editor_spec.js
+++ b/spec/frontend/ide/components/repo_editor_spec.js
@@ -4,19 +4,25 @@ import MockAdapter from 'axios-mock-adapter';
import '~/behaviors/markdown/render_gfm';
import { Range } from 'monaco-editor';
import axios from '~/lib/utils/axios_utils';
+import service from '~/ide/services';
import { createStoreOptions } from '~/ide/stores';
import RepoEditor from '~/ide/components/repo_editor.vue';
import Editor from '~/ide/lib/editor';
-import { leftSidebarViews, FILE_VIEW_MODE_EDITOR, FILE_VIEW_MODE_PREVIEW } from '~/ide/constants';
+import {
+ leftSidebarViews,
+ FILE_VIEW_MODE_EDITOR,
+ FILE_VIEW_MODE_PREVIEW,
+ viewerTypes,
+} from '~/ide/constants';
import { createComponentWithStore } from '../../helpers/vue_mount_component_helper';
import waitForPromises from 'helpers/wait_for_promises';
import { file } from '../helpers';
import { exampleConfigs, exampleFiles } from '../lib/editorconfig/mock_data';
+import waitUsingRealTimer from 'helpers/wait_using_real_timer';
describe('RepoEditor', () => {
let vm;
let store;
- let mockActions;
const waitForEditorSetup = () =>
new Promise(resolve => {
@@ -30,6 +36,10 @@ describe('RepoEditor', () => {
vm = createComponentWithStore(Vue.extend(RepoEditor), store, {
file: store.state.openFiles[0],
});
+
+ jest.spyOn(vm, 'getFileData').mockResolvedValue();
+ jest.spyOn(vm, 'getRawFileData').mockResolvedValue();
+
vm.$mount();
};
@@ -43,21 +53,12 @@ describe('RepoEditor', () => {
};
beforeEach(() => {
- mockActions = {
- getFileData: jest.fn().mockResolvedValue(),
- getRawFileData: jest.fn().mockResolvedValue(),
- };
-
const f = {
...file(),
viewMode: FILE_VIEW_MODE_EDITOR,
};
const storeOptions = createStoreOptions();
- storeOptions.actions = {
- ...storeOptions.actions,
- ...mockActions,
- };
store = new Vuex.Store(storeOptions);
f.active = true;
@@ -438,7 +439,7 @@ describe('RepoEditor', () => {
vm.initEditor();
vm.$nextTick()
.then(() => {
- expect(mockActions.getFileData).not.toHaveBeenCalled();
+ expect(vm.getFileData).not.toHaveBeenCalled();
})
.then(done)
.catch(done.fail);
@@ -449,10 +450,11 @@ describe('RepoEditor', () => {
vm.file.raw = '';
vm.initEditor();
+
vm.$nextTick()
.then(() => {
- expect(mockActions.getFileData).toHaveBeenCalled();
- expect(mockActions.getRawFileData).toHaveBeenCalled();
+ expect(vm.getFileData).toHaveBeenCalled();
+ expect(vm.getRawFileData).toHaveBeenCalled();
})
.then(done)
.catch(done.fail);
@@ -464,8 +466,8 @@ describe('RepoEditor', () => {
vm.initEditor();
vm.$nextTick()
.then(() => {
- expect(mockActions.getFileData).not.toHaveBeenCalled();
- expect(mockActions.getRawFileData).not.toHaveBeenCalled();
+ expect(vm.getFileData).not.toHaveBeenCalled();
+ expect(vm.getRawFileData).not.toHaveBeenCalled();
expect(vm.editor.createInstance).not.toHaveBeenCalled();
})
.then(done)
@@ -526,6 +528,63 @@ describe('RepoEditor', () => {
});
});
+ describe('populates editor with the fetched content', () => {
+ beforeEach(() => {
+ vm.getRawFileData.mockRestore();
+ });
+
+ const createRemoteFile = name => ({
+ ...file(name),
+ tmpFile: false,
+ });
+
+ it('after switching viewer from edit to diff', async () => {
+ jest.spyOn(service, 'getRawFileData').mockImplementation(async () => {
+ expect(vm.file.loading).toBe(true);
+
+ // switching from edit to diff mode usually triggers editor initialization
+ store.state.viewer = viewerTypes.diff;
+
+ // we delay returning the file to make sure editor doesn't initialize before we fetch file content
+ await waitUsingRealTimer(30);
+ return 'rawFileData123\n';
+ });
+
+ const f = createRemoteFile('newFile');
+ Vue.set(store.state.entries, f.path, f);
+
+ vm.file = f;
+
+ await waitForEditorSetup();
+ expect(vm.model.getModel().getValue()).toBe('rawFileData123\n');
+ });
+
+ it('after opening multiple files at the same time', async () => {
+ const fileA = createRemoteFile('fileA');
+ const fileB = createRemoteFile('fileB');
+ Vue.set(store.state.entries, fileA.path, fileA);
+ Vue.set(store.state.entries, fileB.path, fileB);
+
+ jest
+ .spyOn(service, 'getRawFileData')
+ .mockImplementationOnce(async () => {
+ // opening fileB while the content of fileA is still being fetched
+ vm.file = fileB;
+ return 'fileA-rawContent\n';
+ })
+ .mockImplementationOnce(async () => {
+ // we delay returning fileB content to make sure the editor doesn't initialize prematurely
+ await waitUsingRealTimer(30);
+ return 'fileB-rawContent\n';
+ });
+
+ vm.file = fileA;
+
+ await waitForEditorSetup();
+ expect(vm.model.getModel().getValue()).toBe('fileB-rawContent\n');
+ });
+ });
+
describe('onPaste', () => {
const setFileName = name => {
Vue.set(vm, 'file', {
@@ -557,6 +616,11 @@ describe('RepoEditor', () => {
});
});
+ // Pasting an image does a lot of things like using the FileReader API,
+ // so, waitForPromises isn't very reliable (and causes a flaky spec)
+ // Read more about state.watch: https://vuex.vuejs.org/api/#watch
+ const waitForFileContentChange = () => watchState(s => s.entries['foo/bar.md'].content);
+
beforeEach(() => {
setFileName('bar.md');
@@ -573,13 +637,15 @@ describe('RepoEditor', () => {
// set cursor to line 2, column 1
vm.editor.instance.setSelection(new Range(2, 1, 2, 1));
vm.editor.instance.focus();
+
+ jest.spyOn(vm.editor.instance, 'hasTextFocus').mockReturnValue(true);
});
});
it('adds an image entry to the same folder for a pasted image in a markdown file', () => {
pasteImage();
- return waitForPromises().then(() => {
+ return waitForFileContentChange().then(() => {
expect(vm.$store.state.entries['foo/foo.png']).toMatchObject({
path: 'foo/foo.png',
type: 'blob',
@@ -593,10 +659,7 @@ describe('RepoEditor', () => {
it("adds a markdown image tag to the file's contents", () => {
pasteImage();
- // Pasting an image does a lot of things like using the FileReader API,
- // so, waitForPromises isn't very reliable (and causes a flaky spec)
- // Read more about state.watch: https://vuex.vuejs.org/api/#watch
- return watchState(s => s.entries['foo/bar.md'].content).then(() => {
+ return waitForFileContentChange().then(() => {
expect(vm.file.content).toBe('hello world\n![foo.png](./foo.png)');
});
});
@@ -629,8 +692,8 @@ describe('RepoEditor', () => {
return waitForEditorSetup().then(() => {
expect(vm.rules).toEqual(monacoRules);
expect(vm.model.options).toMatchObject(monacoRules);
- expect(mockActions.getFileData).not.toHaveBeenCalled();
- expect(mockActions.getRawFileData).not.toHaveBeenCalled();
+ expect(vm.getFileData).not.toHaveBeenCalled();
+ expect(vm.getRawFileData).not.toHaveBeenCalled();
});
},
);
@@ -646,13 +709,13 @@ describe('RepoEditor', () => {
createComponent();
return waitForEditorSetup().then(() => {
- expect(mockActions.getFileData.mock.calls.map(([, args]) => args)).toEqual([
+ expect(vm.getFileData.mock.calls.map(([args]) => args)).toEqual([
{ makeFileActive: false, path: 'foo/bar/baz/.editorconfig' },
{ makeFileActive: false, path: 'foo/bar/.editorconfig' },
{ makeFileActive: false, path: 'foo/.editorconfig' },
{ makeFileActive: false, path: '.editorconfig' },
]);
- expect(mockActions.getRawFileData.mock.calls.map(([, args]) => args)).toEqual([
+ expect(vm.getRawFileData.mock.calls.map(([args]) => args)).toEqual([
{ path: 'foo/bar/baz/.editorconfig' },
{ path: 'foo/bar/.editorconfig' },
{ path: 'foo/.editorconfig' },
diff --git a/spec/frontend/ide/helpers.js b/spec/frontend/ide/helpers.js
index de839fa99ca..a9620d26313 100644
--- a/spec/frontend/ide/helpers.js
+++ b/spec/frontend/ide/helpers.js
@@ -30,7 +30,6 @@ export const file = (name = 'name', id = name, type = '', parent = null) =>
name,
path: parent ? `${parent.path}/${name}` : name,
parentPath: parent ? parent.path : '',
- lastCommit: {},
});
export const createEntriesFromPaths = paths =>
diff --git a/spec/frontend/ide/lib/editor_spec.js b/spec/frontend/ide/lib/editor_spec.js
index f5815771cdf..5f28309422d 100644
--- a/spec/frontend/ide/lib/editor_spec.js
+++ b/spec/frontend/ide/lib/editor_spec.js
@@ -199,6 +199,28 @@ describe('Multi-file editor library', () => {
});
});
+ describe('schemas', () => {
+ let originalGon;
+
+ beforeEach(() => {
+ originalGon = window.gon;
+ window.gon = { features: { schemaLinting: true } };
+
+ delete Editor.editorInstance;
+ instance = Editor.create();
+ });
+
+ afterEach(() => {
+ window.gon = originalGon;
+ });
+
+ it('registers custom schemas defined with Monaco', () => {
+ expect(monacoLanguages.yaml.yamlDefaults.diagnosticsOptions).toMatchObject({
+ schemas: [{ fileMatch: ['*.gitlab-ci.yml'] }],
+ });
+ });
+ });
+
describe('replaceSelectedText', () => {
let model;
let editor;
diff --git a/spec/frontend/ide/services/index_spec.js b/spec/frontend/ide/services/index_spec.js
index 3cb6e064aa2..bc3f86702cf 100644
--- a/spec/frontend/ide/services/index_spec.js
+++ b/spec/frontend/ide/services/index_spec.js
@@ -2,7 +2,7 @@ import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
import services from '~/ide/services';
import Api from '~/api';
-import gqClient from '~/ide/services/gql';
+import { query } from '~/ide/services/gql';
import { escapeFileUrl } from '~/lib/utils/url_utility';
import getUserPermissions from '~/ide/queries/getUserPermissions.query.graphql';
import { projectData } from '../mock_data';
@@ -207,12 +207,12 @@ describe('IDE services', () => {
},
};
Api.project.mockReturnValue(Promise.resolve({ data: { ...projectData } }));
- gqClient.query.mockReturnValue(Promise.resolve({ data: { project: gqlProjectData } }));
+ query.mockReturnValue(Promise.resolve({ data: { project: gqlProjectData } }));
return services.getProjectData(TEST_NAMESPACE, TEST_PROJECT).then(response => {
expect(response).toEqual({ data: { ...projectData, ...gqlProjectData } });
expect(Api.project).toHaveBeenCalledWith(TEST_PROJECT_ID);
- expect(gqClient.query).toHaveBeenCalledWith({
+ expect(query).toHaveBeenCalledWith({
query: getUserPermissions,
variables: {
projectPath: TEST_PROJECT_ID,
diff --git a/spec/frontend/ide/stores/actions/file_spec.js b/spec/frontend/ide/stores/actions/file_spec.js
index e2dc7626c67..88e7a9fff36 100644
--- a/spec/frontend/ide/stores/actions/file_spec.js
+++ b/spec/frontend/ide/stores/actions/file_spec.js
@@ -51,35 +51,27 @@ describe('IDE store file actions', () => {
store.state.entries[localFile.path] = localFile;
});
- it('closes open files', done => {
- store
- .dispatch('closeFile', localFile)
- .then(() => {
- expect(localFile.opened).toBeFalsy();
- expect(localFile.active).toBeFalsy();
- expect(store.state.openFiles.length).toBe(0);
-
- done();
- })
- .catch(done.fail);
+ it('closes open files', () => {
+ return store.dispatch('closeFile', localFile).then(() => {
+ expect(localFile.opened).toBeFalsy();
+ expect(localFile.active).toBeFalsy();
+ expect(store.state.openFiles.length).toBe(0);
+ });
});
- it('closes file even if file has changes', done => {
+ it('closes file even if file has changes', () => {
store.state.changedFiles.push(localFile);
- store
+ return store
.dispatch('closeFile', localFile)
.then(Vue.nextTick)
.then(() => {
expect(store.state.openFiles.length).toBe(0);
expect(store.state.changedFiles.length).toBe(1);
-
- done();
- })
- .catch(done.fail);
+ });
});
- it('closes file & opens next available file', done => {
+ it('closes file & opens next available file', () => {
const f = {
...file('newOpenFile'),
url: '/newOpenFile',
@@ -88,31 +80,23 @@ describe('IDE store file actions', () => {
store.state.openFiles.push(f);
store.state.entries[f.path] = f;
- store
+ return store
.dispatch('closeFile', localFile)
.then(Vue.nextTick)
.then(() => {
expect(router.push).toHaveBeenCalledWith(`/project${f.url}`);
-
- done();
- })
- .catch(done.fail);
+ });
});
- it('removes file if it pending', done => {
+ it('removes file if it pending', () => {
store.state.openFiles.push({
...localFile,
pending: true,
});
- store
- .dispatch('closeFile', localFile)
- .then(() => {
- expect(store.state.openFiles.length).toBe(0);
-
- done();
- })
- .catch(done.fail);
+ return store.dispatch('closeFile', localFile).then(() => {
+ expect(store.state.openFiles.length).toBe(0);
+ });
});
});
@@ -264,61 +248,48 @@ describe('IDE store file actions', () => {
);
});
- it('calls the service', done => {
- store
- .dispatch('getFileData', { path: localFile.path })
- .then(() => {
- expect(service.getFileData).toHaveBeenCalledWith(
- `${RELATIVE_URL_ROOT}/test/test/-/7297abc/${localFile.path}`,
- );
-
- done();
- })
- .catch(done.fail);
+ it('calls the service', () => {
+ return store.dispatch('getFileData', { path: localFile.path }).then(() => {
+ expect(service.getFileData).toHaveBeenCalledWith(
+ `${RELATIVE_URL_ROOT}/test/test/-/7297abc/${localFile.path}`,
+ );
+ });
});
- it('sets document title with the branchId', done => {
- store
- .dispatch('getFileData', { path: localFile.path })
- .then(() => {
- expect(document.title).toBe(`${localFile.path} · master · test/test · GitLab`);
- done();
- })
- .catch(done.fail);
+ it('sets document title with the branchId', () => {
+ return store.dispatch('getFileData', { path: localFile.path }).then(() => {
+ expect(document.title).toBe(`${localFile.path} · master · test/test · GitLab`);
+ });
});
- it('sets the file as active', done => {
- store
- .dispatch('getFileData', { path: localFile.path })
- .then(() => {
- expect(localFile.active).toBeTruthy();
-
- done();
- })
- .catch(done.fail);
+ it('sets the file as active', () => {
+ return store.dispatch('getFileData', { path: localFile.path }).then(() => {
+ expect(localFile.active).toBeTruthy();
+ });
});
- it('sets the file not as active if we pass makeFileActive false', done => {
- store
+ it('sets the file not as active if we pass makeFileActive false', () => {
+ return store
.dispatch('getFileData', { path: localFile.path, makeFileActive: false })
.then(() => {
expect(localFile.active).toBeFalsy();
-
- done();
- })
- .catch(done.fail);
+ });
});
- it('adds the file to open files', done => {
- store
- .dispatch('getFileData', { path: localFile.path })
+ it('does not update the page title with the path of the file if makeFileActive is false', () => {
+ document.title = 'dummy title';
+ return store
+ .dispatch('getFileData', { path: localFile.path, makeFileActive: false })
.then(() => {
- expect(store.state.openFiles.length).toBe(1);
- expect(store.state.openFiles[0].name).toBe(localFile.name);
+ expect(document.title).toBe(`dummy title`);
+ });
+ });
- done();
- })
- .catch(done.fail);
+ it('adds the file to open files', () => {
+ return store.dispatch('getFileData', { path: localFile.path }).then(() => {
+ expect(store.state.openFiles.length).toBe(1);
+ expect(store.state.openFiles[0].name).toBe(localFile.name);
+ });
});
});
@@ -342,15 +313,10 @@ describe('IDE store file actions', () => {
);
});
- it('sets document title considering `prevPath` on a file', done => {
- store
- .dispatch('getFileData', { path: localFile.path })
- .then(() => {
- expect(document.title).toBe(`new-shiny-file · master · test/test · GitLab`);
-
- done();
- })
- .catch(done.fail);
+ it('sets document title considering `prevPath` on a file', () => {
+ return store.dispatch('getFileData', { path: localFile.path }).then(() => {
+ expect(document.title).toBe(`new-shiny-file · master · test/test · GitLab`);
+ });
});
});
@@ -397,29 +363,19 @@ describe('IDE store file actions', () => {
mock.onGet(/(.*)/).replyOnce(200, 'raw');
});
- it('calls getRawFileData service method', done => {
- store
- .dispatch('getRawFileData', { path: tmpFile.path })
- .then(() => {
- expect(service.getRawFileData).toHaveBeenCalledWith(tmpFile);
-
- done();
- })
- .catch(done.fail);
+ it('calls getRawFileData service method', () => {
+ return store.dispatch('getRawFileData', { path: tmpFile.path }).then(() => {
+ expect(service.getRawFileData).toHaveBeenCalledWith(tmpFile);
+ });
});
- it('updates file raw data', done => {
- store
- .dispatch('getRawFileData', { path: tmpFile.path })
- .then(() => {
- expect(tmpFile.raw).toBe('raw');
-
- done();
- })
- .catch(done.fail);
+ it('updates file raw data', () => {
+ return store.dispatch('getRawFileData', { path: tmpFile.path }).then(() => {
+ expect(tmpFile.raw).toBe('raw');
+ });
});
- it('calls also getBaseRawFileData service method', done => {
+ it('calls also getBaseRawFileData service method', () => {
jest.spyOn(service, 'getBaseRawFileData').mockReturnValue(Promise.resolve('baseraw'));
store.state.currentProjectId = 'gitlab-org/gitlab-ce';
@@ -436,15 +392,58 @@ describe('IDE store file actions', () => {
tmpFile.mrChange = { new_file: false };
- store
- .dispatch('getRawFileData', { path: tmpFile.path })
- .then(() => {
- expect(service.getBaseRawFileData).toHaveBeenCalledWith(tmpFile, 'SHA');
- expect(tmpFile.baseRaw).toBe('baseraw');
+ return store.dispatch('getRawFileData', { path: tmpFile.path }).then(() => {
+ expect(service.getBaseRawFileData).toHaveBeenCalledWith(tmpFile, 'SHA');
+ expect(tmpFile.baseRaw).toBe('baseraw');
+ });
+ });
+
+ describe('sets file loading to true', () => {
+ let loadingWhenGettingRawData;
+ let loadingWhenGettingBaseRawData;
+
+ beforeEach(() => {
+ loadingWhenGettingRawData = undefined;
+ loadingWhenGettingBaseRawData = undefined;
+
+ jest.spyOn(service, 'getRawFileData').mockImplementation(f => {
+ loadingWhenGettingRawData = f.loading;
+ return Promise.resolve('raw');
+ });
+ jest.spyOn(service, 'getBaseRawFileData').mockImplementation(f => {
+ loadingWhenGettingBaseRawData = f.loading;
+ return Promise.resolve('rawBase');
+ });
+ });
- done();
- })
- .catch(done.fail);
+ it('when getting raw file data', async () => {
+ expect(tmpFile.loading).toBe(false);
+
+ await store.dispatch('getRawFileData', { path: tmpFile.path });
+
+ expect(loadingWhenGettingRawData).toBe(true);
+ expect(tmpFile.loading).toBe(false);
+ });
+
+ it('when getting base raw file data', async () => {
+ tmpFile.mrChange = { new_file: false };
+
+ expect(tmpFile.loading).toBe(false);
+
+ await store.dispatch('getRawFileData', { path: tmpFile.path });
+
+ expect(loadingWhenGettingBaseRawData).toBe(true);
+ expect(tmpFile.loading).toBe(false);
+ });
+
+ it('when file was already loading', async () => {
+ tmpFile.loading = true;
+
+ await store.dispatch('getRawFileData', { path: tmpFile.path });
+
+ expect(loadingWhenGettingRawData).toBe(true);
+ expect(tmpFile.loading).toBe(false);
+ });
});
});
@@ -453,15 +452,10 @@ describe('IDE store file actions', () => {
mock.onGet(/(.*)/).replyOnce(200, JSON.stringify({ test: '123' }));
});
- it('does not parse returned JSON', done => {
- store
- .dispatch('getRawFileData', { path: tmpFile.path })
- .then(() => {
- expect(tmpFile.raw).toEqual('{"test":"123"}');
-
- done();
- })
- .catch(done.fail);
+ it('does not parse returned JSON', () => {
+ return store.dispatch('getRawFileData', { path: tmpFile.path }).then(() => {
+ expect(tmpFile.raw).toEqual('{"test":"123"}');
+ });
});
});
@@ -489,6 +483,12 @@ describe('IDE store file actions', () => {
});
});
});
+
+ it('toggles loading off after error', async () => {
+ await expect(store.dispatch('getRawFileData', { path: tmpFile.path })).rejects.toThrow();
+
+ expect(tmpFile.loading).toBe(false);
+ });
});
});
@@ -504,32 +504,25 @@ describe('IDE store file actions', () => {
store.state.entries[tmpFile.path] = tmpFile;
});
- it('updates file content', done => {
- callAction()
- .then(() => {
- expect(tmpFile.content).toBe('content\n');
-
- done();
- })
- .catch(done.fail);
+ it('updates file content', () => {
+ return callAction().then(() => {
+ expect(tmpFile.content).toBe('content\n');
+ });
});
- it('adds file into stagedFiles array', done => {
- store
+ it('adds file into stagedFiles array', () => {
+ return store
.dispatch('changeFileContent', {
path: tmpFile.path,
content: 'content',
})
.then(() => {
expect(store.state.stagedFiles.length).toBe(1);
-
- done();
- })
- .catch(done.fail);
+ });
});
- it('adds file not more than once into stagedFiles array', done => {
- store
+ it('adds file not more than once into stagedFiles array', () => {
+ return store
.dispatch('changeFileContent', {
path: tmpFile.path,
content: 'content',
@@ -542,14 +535,11 @@ describe('IDE store file actions', () => {
)
.then(() => {
expect(store.state.stagedFiles.length).toBe(1);
-
- done();
- })
- .catch(done.fail);
+ });
});
- it('removes file from changedFiles array if not changed', done => {
- store
+ it('removes file from changedFiles array if not changed', () => {
+ return store
.dispatch('changeFileContent', {
path: tmpFile.path,
content: 'content\n',
@@ -562,10 +552,7 @@ describe('IDE store file actions', () => {
)
.then(() => {
expect(store.state.changedFiles.length).toBe(0);
-
- done();
- })
- .catch(done.fail);
+ });
});
});
@@ -723,52 +710,36 @@ describe('IDE store file actions', () => {
store.state.entries[f.path] = f;
});
- it('makes file pending in openFiles', done => {
- store
- .dispatch('openPendingTab', { file: f, keyPrefix: 'pending' })
- .then(() => {
- expect(store.state.openFiles[0].pending).toBe(true);
- })
- .then(done)
- .catch(done.fail);
+ it('makes file pending in openFiles', () => {
+ return store.dispatch('openPendingTab', { file: f, keyPrefix: 'pending' }).then(() => {
+ expect(store.state.openFiles[0].pending).toBe(true);
+ });
});
- it('returns true when opened', done => {
- store
- .dispatch('openPendingTab', { file: f, keyPrefix: 'pending' })
- .then(added => {
- expect(added).toBe(true);
- })
- .then(done)
- .catch(done.fail);
+ it('returns true when opened', () => {
+ return store.dispatch('openPendingTab', { file: f, keyPrefix: 'pending' }).then(added => {
+ expect(added).toBe(true);
+ });
});
- it('returns false when already opened', done => {
+ it('returns false when already opened', () => {
store.state.openFiles.push({
...f,
active: true,
key: `pending-${f.key}`,
});
- store
- .dispatch('openPendingTab', { file: f, keyPrefix: 'pending' })
- .then(added => {
- expect(added).toBe(false);
- })
- .then(done)
- .catch(done.fail);
+ return store.dispatch('openPendingTab', { file: f, keyPrefix: 'pending' }).then(added => {
+ expect(added).toBe(false);
+ });
});
- it('pushes router URL when added', done => {
+ it('pushes router URL when added', () => {
store.state.currentBranchId = 'master';
- store
- .dispatch('openPendingTab', { file: f, keyPrefix: 'pending' })
- .then(() => {
- expect(router.push).toHaveBeenCalledWith('/project/123/tree/master/');
- })
- .then(done)
- .catch(done.fail);
+ return store.dispatch('openPendingTab', { file: f, keyPrefix: 'pending' }).then(() => {
+ expect(router.push).toHaveBeenCalledWith('/project/123/tree/master/');
+ });
});
});
@@ -784,26 +755,18 @@ describe('IDE store file actions', () => {
};
});
- it('removes pending file from open files', done => {
+ it('removes pending file from open files', () => {
store.state.openFiles.push(f);
- store
- .dispatch('removePendingTab', f)
- .then(() => {
- expect(store.state.openFiles.length).toBe(0);
- })
- .then(done)
- .catch(done.fail);
+ return store.dispatch('removePendingTab', f).then(() => {
+ expect(store.state.openFiles.length).toBe(0);
+ });
});
- it('emits event to dispose model', done => {
- store
- .dispatch('removePendingTab', f)
- .then(() => {
- expect(eventHub.$emit).toHaveBeenCalledWith(`editor.update.model.dispose.${f.key}`);
- })
- .then(done)
- .catch(done.fail);
+ it('emits event to dispose model', () => {
+ return store.dispatch('removePendingTab', f).then(() => {
+ expect(eventHub.$emit).toHaveBeenCalledWith(`editor.update.model.dispose.${f.key}`);
+ });
});
});
@@ -812,14 +775,10 @@ describe('IDE store file actions', () => {
jest.spyOn(eventHub, '$emit').mockImplementation(() => {});
});
- it('emits event that files have changed', done => {
- store
- .dispatch('triggerFilesChange')
- .then(() => {
- expect(eventHub.$emit).toHaveBeenCalledWith('ide.files.change');
- })
- .then(done)
- .catch(done.fail);
+ it('emits event that files have changed', () => {
+ return store.dispatch('triggerFilesChange').then(() => {
+ expect(eventHub.$emit).toHaveBeenCalledWith('ide.files.change');
+ });
});
});
});
diff --git a/spec/frontend/ide/stores/actions/merge_request_spec.js b/spec/frontend/ide/stores/actions/merge_request_spec.js
index cb4eebd97d9..e5c4f346459 100644
--- a/spec/frontend/ide/stores/actions/merge_request_spec.js
+++ b/spec/frontend/ide/stores/actions/merge_request_spec.js
@@ -55,6 +55,7 @@ describe('IDE store merge request actions', () => {
expect(service.getProjectMergeRequests).toHaveBeenCalledWith(TEST_PROJECT, {
source_branch: 'bar',
source_project_id: TEST_PROJECT_ID,
+ state: 'opened',
order_by: 'created_at',
per_page: 1,
});
diff --git a/spec/frontend/ide/stores/actions/tree_spec.js b/spec/frontend/ide/stores/actions/tree_spec.js
index 44e2fcab436..c20941843c4 100644
--- a/spec/frontend/ide/stores/actions/tree_spec.js
+++ b/spec/frontend/ide/stores/actions/tree_spec.js
@@ -7,6 +7,7 @@ import { createStore } from '~/ide/stores';
import service from '~/ide/services';
import { createRouter } from '~/ide/ide_router';
import { file, createEntriesFromPaths } from '../../helpers';
+import { TEST_HOST } from 'jest/helpers/test_constants';
describe('Multi-file store tree actions', () => {
let projectTree;
@@ -97,7 +98,7 @@ describe('Multi-file store tree actions', () => {
store.state.projects = {
'abc/def': {
- web_url: `${gl.TEST_HOST}/files`,
+ web_url: `${TEST_HOST}/files`,
branches: {
'master-testing': {
commit: {
diff --git a/spec/frontend/ide/utils_spec.js b/spec/frontend/ide/utils_spec.js
index 15baeca7f36..b6de576a0a4 100644
--- a/spec/frontend/ide/utils_spec.js
+++ b/spec/frontend/ide/utils_spec.js
@@ -1,6 +1,7 @@
import {
isTextFile,
registerLanguages,
+ registerSchemas,
trimPathComponents,
insertFinalNewline,
trimTrailingWhitespace,
@@ -158,6 +159,57 @@ describe('WebIDE utils', () => {
});
});
+ describe('registerSchemas', () => {
+ let options;
+
+ beforeEach(() => {
+ options = {
+ validate: true,
+ enableSchemaRequest: true,
+ hover: true,
+ completion: true,
+ schemas: [
+ {
+ uri: 'http://myserver/foo-schema.json',
+ fileMatch: ['*'],
+ schema: {
+ id: 'http://myserver/foo-schema.json',
+ type: 'object',
+ properties: {
+ p1: { enum: ['v1', 'v2'] },
+ p2: { $ref: 'http://myserver/bar-schema.json' },
+ },
+ },
+ },
+ {
+ uri: 'http://myserver/bar-schema.json',
+ schema: {
+ id: 'http://myserver/bar-schema.json',
+ type: 'object',
+ properties: { q1: { enum: ['x1', 'x2'] } },
+ },
+ },
+ ],
+ };
+
+ jest.spyOn(languages.json.jsonDefaults, 'setDiagnosticsOptions');
+ jest.spyOn(languages.yaml.yamlDefaults, 'setDiagnosticsOptions');
+ });
+
+ it.each`
+ language | defaultsObj
+ ${'json'} | ${languages.json.jsonDefaults}
+ ${'yaml'} | ${languages.yaml.yamlDefaults}
+ `(
+ 'registers the given schemas with monaco for lang: $language',
+ ({ language, defaultsObj }) => {
+ registerSchemas({ language, options });
+
+ expect(defaultsObj.setDiagnosticsOptions).toHaveBeenCalledWith(options);
+ },
+ );
+ });
+
describe('trimTrailingWhitespace', () => {
it.each`
input | output
diff --git a/spec/frontend/image_diff/helpers/comment_indicator_helper_spec.js b/spec/frontend/image_diff/helpers/comment_indicator_helper_spec.js
index 395bb7de362..2deb4be2b91 100644
--- a/spec/frontend/image_diff/helpers/comment_indicator_helper_spec.js
+++ b/spec/frontend/image_diff/helpers/comment_indicator_helper_spec.js
@@ -1,5 +1,6 @@
import * as commentIndicatorHelper from '~/image_diff/helpers/comment_indicator_helper';
import * as mockData from '../mock_data';
+import { TEST_HOST } from 'jest/helpers/test_constants';
describe('commentIndicatorHelper', () => {
const { coordinate } = mockData;
@@ -52,7 +53,7 @@ describe('commentIndicatorHelper', () => {
beforeEach(() => {
containerEl.innerHTML = `
<div class="comment-indicator" style="left:${coordinate.x}px; top: ${coordinate.y}px;">
- <img src="${gl.TEST_HOST}/image.png">
+ <img src="${TEST_HOST}/image.png">
</div>
`;
result = commentIndicatorHelper.removeCommentIndicator(containerEl);
diff --git a/spec/frontend/image_diff/helpers/utils_helper_spec.js b/spec/frontend/image_diff/helpers/utils_helper_spec.js
index 3b6378be883..a47c681e775 100644
--- a/spec/frontend/image_diff/helpers/utils_helper_spec.js
+++ b/spec/frontend/image_diff/helpers/utils_helper_spec.js
@@ -1,6 +1,7 @@
import * as utilsHelper from '~/image_diff/helpers/utils_helper';
import ImageBadge from '~/image_diff/image_badge';
import * as mockData from '../mock_data';
+import { TEST_HOST } from 'jest/helpers/test_constants';
describe('utilsHelper', () => {
const { noteId, discussionId, image, imageProperties, imageMeta } = mockData;
@@ -36,7 +37,7 @@ describe('utilsHelper', () => {
beforeEach(() => {
const imageFrameEl = document.createElement('div');
imageFrameEl.innerHTML = `
- <img src="${gl.TEST_HOST}/image.png">
+ <img src="${TEST_HOST}/image.png">
`;
discussionEl = document.createElement('div');
discussionEl.dataset.discussionId = discussionId;
diff --git a/spec/frontend/image_diff/image_diff_spec.js b/spec/frontend/image_diff/image_diff_spec.js
index c15718b5106..2b29a522193 100644
--- a/spec/frontend/image_diff/image_diff_spec.js
+++ b/spec/frontend/image_diff/image_diff_spec.js
@@ -2,6 +2,7 @@ import ImageDiff from '~/image_diff/image_diff';
import * as imageUtility from '~/lib/utils/image_utility';
import imageDiffHelper from '~/image_diff/helpers/index';
import * as mockData from './mock_data';
+import { TEST_HOST } from 'jest/helpers/test_constants';
describe('ImageDiff', () => {
let element;
@@ -12,7 +13,7 @@ describe('ImageDiff', () => {
<div id="element">
<div class="diff-file">
<div class="js-image-frame">
- <img src="${gl.TEST_HOST}/image.png">
+ <img src="${TEST_HOST}/image.png">
<div class="comment-indicator"></div>
<div id="badge-1" class="badge">1</div>
<div id="badge-2" class="badge">2</div>
diff --git a/spec/frontend/image_diff/replaced_image_diff_spec.js b/spec/frontend/image_diff/replaced_image_diff_spec.js
index f2a7b7f8406..38a43bfa858 100644
--- a/spec/frontend/image_diff/replaced_image_diff_spec.js
+++ b/spec/frontend/image_diff/replaced_image_diff_spec.js
@@ -2,6 +2,7 @@ import ReplacedImageDiff from '~/image_diff/replaced_image_diff';
import ImageDiff from '~/image_diff/image_diff';
import { viewTypes } from '~/image_diff/view_types';
import imageDiffHelper from '~/image_diff/helpers/index';
+import { TEST_HOST } from 'jest/helpers/test_constants';
describe('ReplacedImageDiff', () => {
let element;
@@ -12,17 +13,17 @@ describe('ReplacedImageDiff', () => {
<div id="element">
<div class="two-up">
<div class="js-image-frame">
- <img src="${gl.TEST_HOST}/image.png">
+ <img src="${TEST_HOST}/image.png">
</div>
</div>
<div class="swipe">
<div class="js-image-frame">
- <img src="${gl.TEST_HOST}/image.png">
+ <img src="${TEST_HOST}/image.png">
</div>
</div>
<div class="onion-skin">
<div class="js-image-frame">
- <img src="${gl.TEST_HOST}/image.png">
+ <img src="${TEST_HOST}/image.png">
</div>
</div>
<div class="view-modes-menu">
diff --git a/spec/frontend/import_projects/store/actions_spec.js b/spec/frontend/import_projects/store/actions_spec.js
index 1f2882a2532..fd6fbcbfce0 100644
--- a/spec/frontend/import_projects/store/actions_spec.js
+++ b/spec/frontend/import_projects/store/actions_spec.js
@@ -1,4 +1,5 @@
import MockAdapter from 'axios-mock-adapter';
+import createFlash from '~/flash';
import testAction from 'helpers/vuex_action_helper';
import { TEST_HOST } from 'helpers/test_constants';
import axios from '~/lib/utils/axios_utils';
@@ -22,6 +23,8 @@ import {
} from '~/import_projects/store/actions';
import state from '~/import_projects/store/state';
+jest.mock('~/flash');
+
describe('import_projects store actions', () => {
let localState;
const repos = [{ id: 1 }, { id: 2 }];
@@ -130,10 +133,28 @@ describe('import_projects store actions', () => {
);
});
- it('commits REQUEST_IMPORT and RECEIVE_IMPORT_ERROR on an unsuccessful request', () => {
+ it('commits REQUEST_IMPORT and RECEIVE_IMPORT_ERROR and shows generic error message on an unsuccessful request', async () => {
mock.onPost(`${TEST_HOST}/endpoint.json`).reply(500);
- return testAction(
+ await testAction(
+ fetchImport,
+ importPayload,
+ localState,
+ [
+ { type: REQUEST_IMPORT, payload: importPayload.repo.id },
+ { type: RECEIVE_IMPORT_ERROR, payload: importPayload.repo.id },
+ ],
+ [],
+ );
+
+ expect(createFlash).toHaveBeenCalledWith('Importing the project failed');
+ });
+
+ it('commits REQUEST_IMPORT and RECEIVE_IMPORT_ERROR and shows detailed error message on an unsuccessful request with errors fields in response', async () => {
+ const ERROR_MESSAGE = 'dummy';
+ mock.onPost(`${TEST_HOST}/endpoint.json`).reply(500, { errors: ERROR_MESSAGE });
+
+ await testAction(
fetchImport,
importPayload,
localState,
@@ -143,6 +164,8 @@ describe('import_projects store actions', () => {
],
[],
);
+
+ expect(createFlash).toHaveBeenCalledWith(`Importing the project failed: ${ERROR_MESSAGE}`);
});
});
diff --git a/spec/frontend/incidents_settings/components/__snapshots__/alerts_form_spec.js.snap b/spec/frontend/incidents_settings/components/__snapshots__/alerts_form_spec.js.snap
new file mode 100644
index 00000000000..dd3589e2951
--- /dev/null
+++ b/spec/frontend/incidents_settings/components/__snapshots__/alerts_form_spec.js.snap
@@ -0,0 +1,99 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Alert integration settings form default state should match the default snapshot 1`] = `
+<div>
+ <p>
+ <gl-sprintf-stub
+ message="Action to take when receiving an alert. %{docsLink}"
+ />
+ </p>
+
+ <form>
+ <gl-form-group-stub
+ class="gl-pl-0"
+ >
+ <gl-form-checkbox-stub
+ checked="true"
+ data-qa-selector="create_issue_checkbox"
+ >
+ <span>
+ Create an issue. Issues are created for each alert triggered.
+ </span>
+ </gl-form-checkbox-stub>
+ </gl-form-group-stub>
+
+ <gl-form-group-stub
+ class="col-8 col-md-9 gl-px-6"
+ label-for="alert-integration-settings-issue-template"
+ label-size="sm"
+ >
+ <label
+ class="gl-display-inline-flex"
+ for="alert-integration-settings-issue-template"
+ >
+
+ Issue template (optional)
+
+ <gl-link-stub
+ href="/help/user/project/description_templates#creating-issue-templates"
+ target="_blank"
+ >
+ <gl-icon-stub
+ name="question"
+ size="12"
+ />
+ </gl-link-stub>
+ </label>
+
+ <gl-new-dropdown-stub
+ block="true"
+ category="tertiary"
+ data-qa-selector="incident_templates_dropdown"
+ headertext=""
+ id="alert-integration-settings-issue-template"
+ size="medium"
+ text="selecte_tmpl"
+ variant="default"
+ >
+ <gl-new-dropdown-item-stub
+ avatarurl=""
+ data-qa-selector="incident_templates_item"
+ iconcolor=""
+ iconname=""
+ iconrightname=""
+ ischeckitem="true"
+ secondarytext=""
+ >
+
+ No template selected
+
+ </gl-new-dropdown-item-stub>
+ </gl-new-dropdown-stub>
+ </gl-form-group-stub>
+
+ <gl-form-group-stub
+ class="gl-pl-0 gl-mb-5"
+ >
+ <gl-form-checkbox-stub>
+ <span>
+ Send a separate email notification to Developers.
+ </span>
+ </gl-form-checkbox-stub>
+ </gl-form-group-stub>
+
+ <gl-button-stub
+ category="tertiary"
+ class="js-no-auto-disable"
+ data-qa-selector="save_changes_button"
+ icon=""
+ size="medium"
+ type="submit"
+ variant="success"
+ >
+
+ Save changes
+
+ </gl-button-stub>
+ </form>
+</div>
+`;
diff --git a/spec/frontend/incidents_settings/components/__snapshots__/incidents_settings_tabs_spec.js.snap b/spec/frontend/incidents_settings/components/__snapshots__/incidents_settings_tabs_spec.js.snap
new file mode 100644
index 00000000000..5f355ee8261
--- /dev/null
+++ b/spec/frontend/incidents_settings/components/__snapshots__/incidents_settings_tabs_spec.js.snap
@@ -0,0 +1,63 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`IncidentsSettingTabs should render the component 1`] = `
+<section
+ class="settings no-animate qa-incident-management-settings"
+ data-qa-selector="incidents_settings_content"
+ id="incident-management-settings"
+>
+ <div
+ class="settings-header"
+ >
+ <h3
+ class="h4"
+ >
+
+ Incidents
+
+ </h3>
+
+ <gl-button-stub
+ category="tertiary"
+ class="js-settings-toggle"
+ icon=""
+ size="medium"
+ variant="default"
+ >
+ Expand
+ </gl-button-stub>
+
+ <p>
+
+ Set up integrations with external tools to help better manage incidents.
+
+ </p>
+ </div>
+
+ <div
+ class="settings-content"
+ >
+ <gl-tabs-stub
+ theme="indigo"
+ >
+ <gl-tab-stub
+ title="Alert integration"
+ >
+ <alertssettingsform-stub
+ class="gl-pt-3"
+ data-testid="AlertsSettingsForm-tab"
+ />
+ </gl-tab-stub>
+ <gl-tab-stub
+ title="PagerDuty integration"
+ >
+ <pagerdutysettingsform-stub
+ class="gl-pt-3"
+ data-testid="PagerDutySettingsForm-tab"
+ />
+ </gl-tab-stub>
+ <!---->
+ </gl-tabs-stub>
+ </div>
+</section>
+`;
diff --git a/spec/frontend/incidents_settings/components/__snapshots__/pagerduty_form_spec.js.snap b/spec/frontend/incidents_settings/components/__snapshots__/pagerduty_form_spec.js.snap
new file mode 100644
index 00000000000..17ada722034
--- /dev/null
+++ b/spec/frontend/incidents_settings/components/__snapshots__/pagerduty_form_spec.js.snap
@@ -0,0 +1,89 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Alert integration settings form should match the default snapshot 1`] = `
+<div>
+ <!---->
+
+ <p>
+ Setting up a webhook with PagerDuty will automatically create a GitLab issue for each PagerDuty incident.
+ </p>
+
+ <form>
+ <gl-form-group-stub
+ class="col-8 col-md-9 gl-p-0"
+ >
+ <gl-toggle-stub
+ id="active"
+ label="Active"
+ labelposition="top"
+ value="true"
+ />
+ </gl-form-group-stub>
+
+ <gl-form-group-stub
+ class="col-8 col-md-9 gl-p-0"
+ label="Webhook URL"
+ label-class="label-bold"
+ label-for="url"
+ >
+ <gl-form-input-group-stub
+ data-testid="webhook-url"
+ id="url"
+ predefinedoptions="[object Object]"
+ readonly=""
+ value="pagerduty.webhook.com"
+ />
+
+ <div
+ class="gl-text-gray-400 gl-pt-2"
+ >
+ <gl-sprintf-stub
+ message="Create a GitLab issue for each PagerDuty incident by %{docsLink}"
+ />
+ </div>
+
+ <gl-button-stub
+ category="tertiary"
+ class="gl-mt-3"
+ data-testid="webhook-reset-btn"
+ icon=""
+ role="button"
+ size="medium"
+ tabindex="0"
+ variant="default"
+ >
+
+ Reset webhook URL
+
+ </gl-button-stub>
+
+ <gl-modal-stub
+ modalclass=""
+ modalid="resetWebhookModal"
+ ok-title="Reset webhook URL"
+ ok-variant="danger"
+ size="md"
+ title="Reset webhook URL"
+ titletag="h4"
+ >
+
+ Resetting the webhook URL for this project will require updating this integration's settings in PagerDuty.
+
+ </gl-modal-stub>
+ </gl-form-group-stub>
+
+ <gl-button-stub
+ category="tertiary"
+ class="js-no-auto-disable"
+ icon=""
+ size="medium"
+ type="submit"
+ variant="success"
+ >
+
+ Save changes
+
+ </gl-button-stub>
+ </form>
+</div>
+`;
diff --git a/spec/frontend/incidents_settings/components/alerts_form_spec.js b/spec/frontend/incidents_settings/components/alerts_form_spec.js
new file mode 100644
index 00000000000..04832f31e58
--- /dev/null
+++ b/spec/frontend/incidents_settings/components/alerts_form_spec.js
@@ -0,0 +1,49 @@
+import { shallowMount } from '@vue/test-utils';
+import AlertsSettingsForm from '~/incidents_settings/components/alerts_form.vue';
+
+describe('Alert integration settings form', () => {
+ let wrapper;
+ const service = { updateSettings: jest.fn().mockResolvedValue() };
+
+ const findForm = () => wrapper.find({ ref: 'settingsForm' });
+
+ beforeEach(() => {
+ wrapper = shallowMount(AlertsSettingsForm, {
+ provide: {
+ service,
+ alertSettings: {
+ issueTemplateKey: 'selecte_tmpl',
+ createIssue: true,
+ sendEmail: false,
+ templates: [],
+ },
+ },
+ });
+ });
+
+ afterEach(() => {
+ if (wrapper) {
+ wrapper.destroy();
+ wrapper = null;
+ }
+ });
+
+ describe('default state', () => {
+ it('should match the default snapshot', () => {
+ expect(wrapper.element).toMatchSnapshot();
+ });
+ });
+
+ describe('form', () => {
+ it('should call service `updateSettings` on submit', () => {
+ findForm().trigger('submit');
+ expect(service.updateSettings).toHaveBeenCalledWith(
+ expect.objectContaining({
+ create_issue: wrapper.vm.createIssueEnabled,
+ issue_template_key: wrapper.vm.issueTemplate,
+ send_email: wrapper.vm.sendEmailEnabled,
+ }),
+ );
+ });
+ });
+});
diff --git a/spec/frontend/incidents_settings/components/incidents_settings_service_spec.js b/spec/frontend/incidents_settings/components/incidents_settings_service_spec.js
new file mode 100644
index 00000000000..58f9a318808
--- /dev/null
+++ b/spec/frontend/incidents_settings/components/incidents_settings_service_spec.js
@@ -0,0 +1,55 @@
+import axios from '~/lib/utils/axios_utils';
+import AxiosMockAdapter from 'axios-mock-adapter';
+import httpStatusCodes from '~/lib/utils/http_status';
+import IncidentsSettingsService from '~/incidents_settings/incidents_settings_service';
+import { ERROR_MSG } from '~/incidents_settings/constants';
+import createFlash from '~/flash';
+import { refreshCurrentPage } from '~/lib/utils/url_utility';
+
+jest.mock('~/flash');
+jest.mock('~/lib/utils/url_utility');
+
+describe('IncidentsSettingsService', () => {
+ const settingsEndpoint = 'operations/settings';
+ const webhookUpdateEndpoint = 'webhook/update';
+ let mock;
+ let service;
+
+ beforeEach(() => {
+ mock = new AxiosMockAdapter(axios);
+ service = new IncidentsSettingsService(settingsEndpoint, webhookUpdateEndpoint);
+ });
+
+ afterEach(() => {
+ mock.restore();
+ });
+
+ describe('updateSettings', () => {
+ it('should refresh the page on successful update', () => {
+ mock.onPatch().reply(httpStatusCodes.OK);
+
+ return service.updateSettings({}).then(() => {
+ expect(refreshCurrentPage).toHaveBeenCalled();
+ });
+ });
+
+ it('should display a flash message on update error', () => {
+ mock.onPatch().reply(httpStatusCodes.BAD_REQUEST);
+
+ return service.updateSettings({}).then(() => {
+ expect(createFlash).toHaveBeenCalledWith(expect.stringContaining(ERROR_MSG), 'alert');
+ });
+ });
+ });
+
+ describe('resetWebhookUrl', () => {
+ it('should make a call for webhook update', () => {
+ jest.spyOn(axios, 'post');
+ mock.onPost().reply(httpStatusCodes.OK);
+
+ return service.resetWebhookUrl().then(() => {
+ expect(axios.post).toHaveBeenCalledWith(webhookUpdateEndpoint);
+ });
+ });
+ });
+});
diff --git a/spec/frontend/incidents_settings/components/incidents_settings_tabs_spec.js b/spec/frontend/incidents_settings/components/incidents_settings_tabs_spec.js
new file mode 100644
index 00000000000..47e2aecc108
--- /dev/null
+++ b/spec/frontend/incidents_settings/components/incidents_settings_tabs_spec.js
@@ -0,0 +1,55 @@
+import { shallowMount } from '@vue/test-utils';
+import { GlTab } from '@gitlab/ui';
+import IncidentsSettingTabs from '~/incidents_settings/components/incidents_settings_tabs.vue';
+
+describe('IncidentsSettingTabs', () => {
+ let wrapper;
+
+ beforeEach(() => {
+ wrapper = shallowMount(IncidentsSettingTabs, {
+ provide: { glFeatures: { pagerdutyWebhook: true } },
+ });
+ });
+
+ afterEach(() => {
+ if (wrapper) {
+ wrapper.destroy();
+ }
+ });
+
+ const findToggleButton = () => wrapper.find({ ref: 'toggleBtn' });
+ const findSectionHeader = () => wrapper.find({ ref: 'sectionHeader' });
+
+ const findIntegrationTabs = () => wrapper.findAll(GlTab);
+ it('renders header text', () => {
+ expect(findSectionHeader().text()).toBe('Incidents');
+ });
+
+ describe('expand/collapse button', () => {
+ it('renders as an expand button by default', () => {
+ expect(findToggleButton().text()).toBe('Expand');
+ });
+ });
+
+ it('should render the component', () => {
+ expect(wrapper.element).toMatchSnapshot();
+ });
+
+ it('should render the tab for each active integration', () => {
+ const activeTabs = wrapper.vm.$options.tabs.filter(tab => tab.active);
+ expect(findIntegrationTabs().length).toBe(activeTabs.length);
+ activeTabs.forEach((tab, index) => {
+ expect(
+ findIntegrationTabs()
+ .at(index)
+ .attributes('title'),
+ ).toBe(tab.title);
+ expect(
+ findIntegrationTabs()
+ .at(index)
+ .find(`[data-testid="${tab.component}-tab"]`)
+ .exists(),
+ ).toBe(true);
+ });
+ });
+});
diff --git a/spec/frontend/incidents_settings/components/pagerduty_form_spec.js b/spec/frontend/incidents_settings/components/pagerduty_form_spec.js
new file mode 100644
index 00000000000..521094ad54c
--- /dev/null
+++ b/spec/frontend/incidents_settings/components/pagerduty_form_spec.js
@@ -0,0 +1,67 @@
+import { shallowMount } from '@vue/test-utils';
+import waitForPromises from 'helpers/wait_for_promises';
+import PagerDutySettingsForm from '~/incidents_settings/components/pagerduty_form.vue';
+import { GlAlert, GlModal } from '@gitlab/ui';
+
+describe('Alert integration settings form', () => {
+ let wrapper;
+ const resetWebhookUrl = jest.fn();
+ const service = { updateSettings: jest.fn().mockResolvedValue(), resetWebhookUrl };
+
+ const findForm = () => wrapper.find({ ref: 'settingsForm' });
+ const findWebhookInput = () => wrapper.find('[data-testid="webhook-url"]');
+ const findModal = () => wrapper.find(GlModal);
+ const findAlert = () => wrapper.find(GlAlert);
+
+ beforeEach(() => {
+ wrapper = shallowMount(PagerDutySettingsForm, {
+ provide: {
+ service,
+ pagerDutySettings: {
+ active: true,
+ webhookUrl: 'pagerduty.webhook.com',
+ webhookUpdateEndpoint: 'webhook/update',
+ },
+ },
+ });
+ });
+
+ afterEach(() => {
+ if (wrapper) {
+ wrapper.destroy();
+ wrapper = null;
+ }
+ });
+
+ it('should match the default snapshot', () => {
+ expect(wrapper.element).toMatchSnapshot();
+ });
+
+ it('should call service `updateSettings` on form submit', () => {
+ findForm().trigger('submit');
+ expect(service.updateSettings).toHaveBeenCalledWith(
+ expect.objectContaining({ pagerduty_active: wrapper.vm.active }),
+ );
+ });
+
+ describe('Webhook reset', () => {
+ it('should make a call for webhook reset and reset form values', async () => {
+ const newWebhookUrl = 'new.webhook.url?token=token';
+ resetWebhookUrl.mockResolvedValueOnce({
+ data: { pagerduty_webhook_url: newWebhookUrl },
+ });
+ findModal().vm.$emit('ok');
+ await waitForPromises();
+ expect(resetWebhookUrl).toHaveBeenCalled();
+ expect(findWebhookInput().attributes('value')).toBe(newWebhookUrl);
+ expect(findAlert().attributes('variant')).toBe('success');
+ });
+
+ it('should show error message and NOT reset webhook url', async () => {
+ resetWebhookUrl.mockRejectedValueOnce();
+ findModal().vm.$emit('ok');
+ await waitForPromises();
+ expect(findAlert().attributes('variant')).toBe('danger');
+ });
+ });
+});
diff --git a/spec/frontend/integrations/edit/components/active_toggle_spec.js b/spec/frontend/integrations/edit/components/active_toggle_spec.js
index 5469b45f708..228d8f5fc30 100644
--- a/spec/frontend/integrations/edit/components/active_toggle_spec.js
+++ b/spec/frontend/integrations/edit/components/active_toggle_spec.js
@@ -1,8 +1,10 @@
import { mount } from '@vue/test-utils';
-import ActiveToggle from '~/integrations/edit/components/active_toggle.vue';
import { GlToggle } from '@gitlab/ui';
+import ActiveToggle from '~/integrations/edit/components/active_toggle.vue';
+
const GL_TOGGLE_ACTIVE_CLASS = 'is-checked';
+const GL_TOGGLE_DISABLED_CLASS = 'is-disabled';
describe('ActiveToggle', () => {
let wrapper;
@@ -11,9 +13,12 @@ describe('ActiveToggle', () => {
initialActivated: true,
};
- const createComponent = props => {
+ const createComponent = (props = {}, isInheriting = false) => {
wrapper = mount(ActiveToggle, {
propsData: { ...defaultProps, ...props },
+ computed: {
+ isInheriting: () => isInheriting,
+ },
});
};
@@ -29,6 +34,15 @@ describe('ActiveToggle', () => {
const findInputInToggle = () => findGlToggle().find('input');
describe('template', () => {
+ describe('is inheriting adminSettings', () => {
+ it('renders GlToggle as disabled', () => {
+ createComponent({}, true);
+
+ expect(findGlToggle().exists()).toBe(true);
+ expect(findButtonInToggle().classes()).toContain(GL_TOGGLE_DISABLED_CLASS);
+ });
+ });
+
describe('initialActivated is false', () => {
it('renders GlToggle as inactive', () => {
createComponent({
diff --git a/spec/frontend/integrations/edit/components/dynamic_field_spec.js b/spec/frontend/integrations/edit/components/dynamic_field_spec.js
index e5710641f81..3a7a0efcab7 100644
--- a/spec/frontend/integrations/edit/components/dynamic_field_spec.js
+++ b/spec/frontend/integrations/edit/components/dynamic_field_spec.js
@@ -14,9 +14,12 @@ describe('DynamicField', () => {
value: '1',
};
- const createComponent = props => {
+ const createComponent = (props, isInheriting = false) => {
wrapper = mount(DynamicField, {
propsData: { ...defaultProps, ...props },
+ computed: {
+ isInheriting: () => isInheriting,
+ },
});
};
@@ -34,108 +37,143 @@ describe('DynamicField', () => {
const findGlFormTextarea = () => wrapper.find(GlFormTextarea);
describe('template', () => {
- describe('dynamic field', () => {
- describe('type is checkbox', () => {
- beforeEach(() => {
- createComponent({
- type: 'checkbox',
+ describe.each([[true, 'disabled', 'readonly'], [false, undefined, undefined]])(
+ 'dynamic field, when isInheriting = `%p`',
+ (isInheriting, disabled, readonly) => {
+ describe('type is checkbox', () => {
+ beforeEach(() => {
+ createComponent(
+ {
+ type: 'checkbox',
+ },
+ isInheriting,
+ );
});
- });
- it('renders GlFormCheckbox', () => {
- expect(findGlFormCheckbox().exists()).toBe(true);
- });
+ it(`renders GlFormCheckbox, which ${isInheriting ? 'is' : 'is not'} disabled`, () => {
+ expect(findGlFormCheckbox().exists()).toBe(true);
+ expect(
+ findGlFormCheckbox()
+ .find('[type=checkbox]')
+ .attributes('disabled'),
+ ).toBe(disabled);
+ });
- it('does not render other types of input', () => {
- expect(findGlFormSelect().exists()).toBe(false);
- expect(findGlFormTextarea().exists()).toBe(false);
- expect(findGlFormInput().exists()).toBe(false);
+ it('does not render other types of input', () => {
+ expect(findGlFormSelect().exists()).toBe(false);
+ expect(findGlFormTextarea().exists()).toBe(false);
+ expect(findGlFormInput().exists()).toBe(false);
+ });
});
- });
- describe('type is select', () => {
- beforeEach(() => {
- createComponent({
- type: 'select',
- choices: [['all', 'All details'], ['standard', 'Standard']],
+ describe('type is select', () => {
+ beforeEach(() => {
+ createComponent(
+ {
+ type: 'select',
+ choices: [['all', 'All details'], ['standard', 'Standard']],
+ },
+ isInheriting,
+ );
});
- });
- it('renders findGlFormSelect', () => {
- expect(findGlFormSelect().exists()).toBe(true);
- expect(findGlFormSelect().findAll('option')).toHaveLength(2);
- });
+ it(`renders GlFormSelect, which ${isInheriting ? 'is' : 'is not'} disabled`, () => {
+ expect(findGlFormSelect().exists()).toBe(true);
+ expect(findGlFormSelect().findAll('option')).toHaveLength(2);
+ expect(
+ findGlFormSelect()
+ .find('select')
+ .attributes('disabled'),
+ ).toBe(disabled);
+ });
- it('does not render other types of input', () => {
- expect(findGlFormCheckbox().exists()).toBe(false);
- expect(findGlFormTextarea().exists()).toBe(false);
- expect(findGlFormInput().exists()).toBe(false);
+ it('does not render other types of input', () => {
+ expect(findGlFormCheckbox().exists()).toBe(false);
+ expect(findGlFormTextarea().exists()).toBe(false);
+ expect(findGlFormInput().exists()).toBe(false);
+ });
});
- });
- describe('type is textarea', () => {
- beforeEach(() => {
- createComponent({
- type: 'textarea',
+ describe('type is textarea', () => {
+ beforeEach(() => {
+ createComponent(
+ {
+ type: 'textarea',
+ },
+ isInheriting,
+ );
});
- });
- it('renders findGlFormTextarea', () => {
- expect(findGlFormTextarea().exists()).toBe(true);
- });
+ it(`renders GlFormTextarea, which ${isInheriting ? 'is' : 'is not'} readonly`, () => {
+ expect(findGlFormTextarea().exists()).toBe(true);
+ expect(
+ findGlFormTextarea()
+ .find('textarea')
+ .attributes('readonly'),
+ ).toBe(readonly);
+ });
- it('does not render other types of input', () => {
- expect(findGlFormCheckbox().exists()).toBe(false);
- expect(findGlFormSelect().exists()).toBe(false);
- expect(findGlFormInput().exists()).toBe(false);
+ it('does not render other types of input', () => {
+ expect(findGlFormCheckbox().exists()).toBe(false);
+ expect(findGlFormSelect().exists()).toBe(false);
+ expect(findGlFormInput().exists()).toBe(false);
+ });
});
- });
- describe('type is password', () => {
- beforeEach(() => {
- createComponent({
- type: 'password',
+ describe('type is password', () => {
+ beforeEach(() => {
+ createComponent(
+ {
+ type: 'password',
+ },
+ isInheriting,
+ );
});
- });
- it('renders GlFormInput', () => {
- expect(findGlFormInput().exists()).toBe(true);
- expect(findGlFormInput().attributes('type')).toBe('password');
- });
+ it(`renders GlFormInput, which ${isInheriting ? 'is' : 'is not'} readonly`, () => {
+ expect(findGlFormInput().exists()).toBe(true);
+ expect(findGlFormInput().attributes('type')).toBe('password');
+ expect(findGlFormInput().attributes('readonly')).toBe(readonly);
+ });
- it('does not render other types of input', () => {
- expect(findGlFormCheckbox().exists()).toBe(false);
- expect(findGlFormSelect().exists()).toBe(false);
- expect(findGlFormTextarea().exists()).toBe(false);
+ it('does not render other types of input', () => {
+ expect(findGlFormCheckbox().exists()).toBe(false);
+ expect(findGlFormSelect().exists()).toBe(false);
+ expect(findGlFormTextarea().exists()).toBe(false);
+ });
});
- });
- describe('type is text', () => {
- beforeEach(() => {
- createComponent({
- type: 'text',
- required: true,
+ describe('type is text', () => {
+ beforeEach(() => {
+ createComponent(
+ {
+ type: 'text',
+ required: true,
+ },
+ isInheriting,
+ );
});
- });
- it('renders GlFormInput', () => {
- expect(findGlFormInput().exists()).toBe(true);
- expect(findGlFormInput().attributes()).toMatchObject({
- type: 'text',
- id: 'service_project_url',
- name: 'service[project_url]',
- placeholder: defaultProps.placeholder,
- required: 'required',
+ it(`renders GlFormInput, which ${isInheriting ? 'is' : 'is not'} readonly`, () => {
+ expect(findGlFormInput().exists()).toBe(true);
+ expect(findGlFormInput().attributes()).toMatchObject({
+ type: 'text',
+ id: 'service_project_url',
+ name: 'service[project_url]',
+ placeholder: defaultProps.placeholder,
+ required: 'required',
+ });
+ expect(findGlFormInput().attributes('readonly')).toBe(readonly);
});
- });
- it('does not render other types of input', () => {
- expect(findGlFormCheckbox().exists()).toBe(false);
- expect(findGlFormSelect().exists()).toBe(false);
- expect(findGlFormTextarea().exists()).toBe(false);
+ it('does not render other types of input', () => {
+ expect(findGlFormCheckbox().exists()).toBe(false);
+ expect(findGlFormSelect().exists()).toBe(false);
+ expect(findGlFormTextarea().exists()).toBe(false);
+ });
});
- });
- });
+ },
+ );
describe('help text', () => {
it('renders description with help text', () => {
@@ -147,6 +185,20 @@ describe('DynamicField', () => {
.text(),
).toBe(defaultProps.help);
});
+
+ it('renders description with help text as HTML', () => {
+ const helpHTML = 'The <strong>URL</strong> of the project';
+
+ createComponent({
+ help: helpHTML,
+ });
+
+ expect(
+ findGlFormGroup()
+ .find('small')
+ .html(),
+ ).toContain(helpHTML);
+ });
});
describe('label text', () => {
@@ -175,5 +227,39 @@ describe('DynamicField', () => {
});
});
});
+
+ describe('validations', () => {
+ describe('password field', () => {
+ beforeEach(() => {
+ createComponent({
+ type: 'password',
+ required: true,
+ value: null,
+ });
+
+ wrapper.vm.validated = true;
+ });
+
+ describe('without value', () => {
+ it('requires validation', () => {
+ expect(wrapper.vm.valid).toBe(false);
+ expect(findGlFormGroup().classes('is-invalid')).toBe(true);
+ expect(findGlFormInput().classes('is-invalid')).toBe(true);
+ });
+ });
+
+ describe('with value', () => {
+ beforeEach(() => {
+ wrapper.setProps({ value: 'true' });
+ });
+
+ it('does not require validation', () => {
+ expect(wrapper.vm.valid).toBe(true);
+ expect(findGlFormGroup().classes('is-valid')).toBe(true);
+ expect(findGlFormInput().classes('is-valid')).toBe(true);
+ });
+ });
+ });
+ });
});
});
diff --git a/spec/frontend/integrations/edit/components/integration_form_spec.js b/spec/frontend/integrations/edit/components/integration_form_spec.js
index b598a71cea8..482c6a439f2 100644
--- a/spec/frontend/integrations/edit/components/integration_form_spec.js
+++ b/spec/frontend/integrations/edit/components/integration_form_spec.js
@@ -1,32 +1,32 @@
import { shallowMount } from '@vue/test-utils';
+import { createStore } from '~/integrations/edit/store';
import IntegrationForm from '~/integrations/edit/components/integration_form.vue';
+import OverrideDropdown from '~/integrations/edit/components/override_dropdown.vue';
import ActiveToggle from '~/integrations/edit/components/active_toggle.vue';
import JiraTriggerFields from '~/integrations/edit/components/jira_trigger_fields.vue';
+import JiraIssuesFields from '~/integrations/edit/components/jira_issues_fields.vue';
import TriggerFields from '~/integrations/edit/components/trigger_fields.vue';
import DynamicField from '~/integrations/edit/components/dynamic_field.vue';
+import { mockIntegrationProps } from 'jest/integrations/edit/mock_data';
describe('IntegrationForm', () => {
let wrapper;
- const defaultProps = {
- activeToggleProps: {
- initialActivated: true,
- },
- showActive: true,
- triggerFieldsProps: {
- initialTriggerCommit: false,
- initialTriggerMergeRequest: false,
- initialEnableComments: false,
- },
- type: '',
- };
-
- const createComponent = props => {
+ const createComponent = (customStateProps = {}, featureFlags = {}, initialState = {}) => {
wrapper = shallowMount(IntegrationForm, {
- propsData: { ...defaultProps, ...props },
+ propsData: {},
+ store: createStore({
+ customState: { ...mockIntegrationProps, ...customStateProps },
+ ...initialState,
+ }),
stubs: {
+ OverrideDropdown,
ActiveToggle,
JiraTriggerFields,
+ TriggerFields,
+ },
+ provide: {
+ glFeatures: featureFlags,
},
});
};
@@ -38,8 +38,10 @@ describe('IntegrationForm', () => {
}
});
+ const findOverrideDropdown = () => wrapper.find(OverrideDropdown);
const findActiveToggle = () => wrapper.find(ActiveToggle);
const findJiraTriggerFields = () => wrapper.find(JiraTriggerFields);
+ const findJiraIssuesFields = () => wrapper.find(JiraIssuesFields);
const findTriggerFields = () => wrapper.find(TriggerFields);
describe('template', () => {
@@ -62,23 +64,41 @@ describe('IntegrationForm', () => {
});
describe('type is "slack"', () => {
- it('does not render JiraTriggerFields', () => {
- createComponent({
- type: 'slack',
- });
+ beforeEach(() => {
+ createComponent({ type: 'slack' });
+ });
+ it('does not render JiraTriggerFields', () => {
expect(findJiraTriggerFields().exists()).toBe(false);
});
+
+ it('does not render JiraIssuesFields', () => {
+ expect(findJiraIssuesFields().exists()).toBe(false);
+ });
});
describe('type is "jira"', () => {
it('renders JiraTriggerFields', () => {
- createComponent({
- type: 'jira',
- });
+ createComponent({ type: 'jira' });
expect(findJiraTriggerFields().exists()).toBe(true);
});
+
+ describe('featureFlag jiraIssuesIntegration is false', () => {
+ it('does not render JiraIssuesFields', () => {
+ createComponent({ type: 'jira' }, { jiraIssuesIntegration: false });
+
+ expect(findJiraIssuesFields().exists()).toBe(false);
+ });
+ });
+
+ describe('featureFlag jiraIssuesIntegration is true', () => {
+ it('renders JiraIssuesFields', () => {
+ createComponent({ type: 'jira' }, { jiraIssuesIntegration: true });
+
+ expect(findJiraIssuesFields().exists()).toBe(true);
+ });
+ });
});
describe('triggerEvents is present', () => {
@@ -116,5 +136,35 @@ describe('IntegrationForm', () => {
});
});
});
+
+ describe('adminState state is null', () => {
+ it('does not render OverrideDropdown', () => {
+ createComponent(
+ {},
+ {},
+ {
+ adminState: null,
+ },
+ );
+
+ expect(findOverrideDropdown().exists()).toBe(false);
+ });
+ });
+
+ describe('adminState state is an object', () => {
+ it('renders OverrideDropdown', () => {
+ createComponent(
+ {},
+ {},
+ {
+ adminState: {
+ ...mockIntegrationProps,
+ },
+ },
+ );
+
+ expect(findOverrideDropdown().exists()).toBe(true);
+ });
+ });
});
});
diff --git a/spec/frontend/integrations/edit/components/jira_issues_fields_spec.js b/spec/frontend/integrations/edit/components/jira_issues_fields_spec.js
new file mode 100644
index 00000000000..f58825f6297
--- /dev/null
+++ b/spec/frontend/integrations/edit/components/jira_issues_fields_spec.js
@@ -0,0 +1,96 @@
+import { mount } from '@vue/test-utils';
+
+import { GlFormCheckbox, GlFormInput } from '@gitlab/ui';
+
+import JiraIssuesFields from '~/integrations/edit/components/jira_issues_fields.vue';
+
+describe('JiraIssuesFields', () => {
+ let wrapper;
+
+ const defaultProps = {
+ showJiraIssuesIntegration: true,
+ editProjectPath: '/edit',
+ };
+
+ const createComponent = props => {
+ wrapper = mount(JiraIssuesFields, {
+ propsData: { ...defaultProps, ...props },
+ });
+ };
+
+ afterEach(() => {
+ if (wrapper) {
+ wrapper.destroy();
+ wrapper = null;
+ }
+ });
+
+ const findEnableCheckbox = () => wrapper.find(GlFormCheckbox);
+ const findProjectKey = () => wrapper.find(GlFormInput);
+ const expectedBannerText = 'This is a Premium feature';
+
+ describe('template', () => {
+ describe('upgrade banner for non-Premium user', () => {
+ beforeEach(() => {
+ createComponent({ initialProjectKey: '', showJiraIssuesIntegration: false });
+ });
+
+ it('shows upgrade banner', () => {
+ expect(wrapper.text()).toContain(expectedBannerText);
+ });
+
+ it('does not show checkbox and input field', () => {
+ expect(findEnableCheckbox().exists()).toBe(false);
+ expect(findProjectKey().exists()).toBe(false);
+ });
+ });
+
+ describe('Enable Jira issues checkbox', () => {
+ beforeEach(() => {
+ createComponent({ initialProjectKey: '' });
+ });
+
+ it('does not show upgrade banner', () => {
+ expect(wrapper.text()).not.toContain(expectedBannerText);
+ });
+
+ // As per https://vuejs.org/v2/guide/forms.html#Checkbox-1,
+ // browsers don't include unchecked boxes in form submissions.
+ it('includes issues_enabled as false even if unchecked', () => {
+ expect(wrapper.contains('input[name="service[issues_enabled]"]')).toBe(true);
+ });
+
+ it('disables project_key input', () => {
+ expect(findProjectKey().attributes('disabled')).toBe('disabled');
+ });
+
+ it('does not require project_key', () => {
+ expect(findProjectKey().attributes('required')).toBeUndefined();
+ });
+
+ describe('on enable issues', () => {
+ it('enables project_key input', () => {
+ findEnableCheckbox().vm.$emit('input', true);
+
+ return wrapper.vm.$nextTick().then(() => {
+ expect(findProjectKey().attributes('disabled')).toBeUndefined();
+ });
+ });
+
+ it('requires project_key input', () => {
+ findEnableCheckbox().vm.$emit('input', true);
+
+ return wrapper.vm.$nextTick().then(() => {
+ expect(findProjectKey().attributes('required')).toBe('required');
+ });
+ });
+ });
+ });
+
+ it('contains link to editProjectPath', () => {
+ createComponent();
+
+ expect(wrapper.contains(`a[href="${defaultProps.editProjectPath}"]`)).toBe(true);
+ });
+ });
+});
diff --git a/spec/frontend/integrations/edit/components/jira_trigger_fields_spec.js b/spec/frontend/integrations/edit/components/jira_trigger_fields_spec.js
index e4c2a0be6a3..782930eb6a2 100644
--- a/spec/frontend/integrations/edit/components/jira_trigger_fields_spec.js
+++ b/spec/frontend/integrations/edit/components/jira_trigger_fields_spec.js
@@ -1,6 +1,6 @@
import { mount } from '@vue/test-utils';
-import JiraTriggerFields from '~/integrations/edit/components/jira_trigger_fields.vue';
import { GlFormCheckbox } from '@gitlab/ui';
+import JiraTriggerFields from '~/integrations/edit/components/jira_trigger_fields.vue';
describe('JiraTriggerFields', () => {
let wrapper;
@@ -11,9 +11,12 @@ describe('JiraTriggerFields', () => {
initialEnableComments: false,
};
- const createComponent = props => {
+ const createComponent = (props, isInheriting = false) => {
wrapper = mount(JiraTriggerFields, {
propsData: { ...defaultProps, ...props },
+ computed: {
+ isInheriting: () => isInheriting,
+ },
});
};
@@ -93,5 +96,23 @@ describe('JiraTriggerFields', () => {
expect(findCommentDetail().isVisible()).toBe(true);
});
});
+
+ it('disables checkboxes and radios if inheriting', () => {
+ createComponent(
+ {
+ initialTriggerCommit: true,
+ initialEnableComments: true,
+ },
+ true,
+ );
+
+ wrapper.findAll('[type=checkbox]').wrappers.forEach(checkbox => {
+ expect(checkbox.attributes('disabled')).toBe('disabled');
+ });
+
+ wrapper.findAll('[type=radio]').wrappers.forEach(radio => {
+ expect(radio.attributes('disabled')).toBe('disabled');
+ });
+ });
});
});
diff --git a/spec/frontend/integrations/edit/components/trigger_fields_spec.js b/spec/frontend/integrations/edit/components/trigger_fields_spec.js
index 337876c6d16..41bccb8ada0 100644
--- a/spec/frontend/integrations/edit/components/trigger_fields_spec.js
+++ b/spec/frontend/integrations/edit/components/trigger_fields_spec.js
@@ -9,9 +9,12 @@ describe('TriggerFields', () => {
type: 'slack',
};
- const createComponent = props => {
+ const createComponent = (props, isInheriting = false) => {
wrapper = mount(TriggerFields, {
propsData: { ...defaultProps, ...props },
+ computed: {
+ isInheriting: () => isInheriting,
+ },
});
};
@@ -22,10 +25,11 @@ describe('TriggerFields', () => {
}
});
+ const findAllGlFormGroups = () => wrapper.find('#trigger-fields').findAll(GlFormGroup);
const findAllGlFormCheckboxes = () => wrapper.findAll(GlFormCheckbox);
const findAllGlFormInputs = () => wrapper.findAll(GlFormInput);
- describe('template', () => {
+ describe.each([true, false])('template, isInheriting = `%p`', isInheriting => {
it('renders a label with text "Trigger"', () => {
createComponent();
@@ -51,9 +55,12 @@ describe('TriggerFields', () => {
];
beforeEach(() => {
- createComponent({
- events,
- });
+ createComponent(
+ {
+ events,
+ },
+ isInheriting,
+ );
});
it('does not render GlFormInput for each event', () => {
@@ -69,8 +76,10 @@ describe('TriggerFields', () => {
});
});
- it('renders GlFormCheckbox for each event', () => {
- const checkboxes = findAllGlFormCheckboxes();
+ it(`renders GlFormCheckbox and corresponding hidden input for each event, which ${
+ isInheriting ? 'is' : 'is not'
+ } disabled`, () => {
+ const checkboxes = findAllGlFormGroups();
const expectedResults = [
{ labelText: 'Push', inputName: 'service[push_event]' },
{ labelText: 'Merge Request', inputName: 'service[merge_requests_event]' },
@@ -78,14 +87,22 @@ describe('TriggerFields', () => {
expect(checkboxes).toHaveLength(2);
checkboxes.wrappers.forEach((checkbox, index) => {
+ const checkBox = checkbox.find(GlFormCheckbox);
+
expect(checkbox.find('label').text()).toBe(expectedResults[index].labelText);
- expect(checkbox.find('input').attributes('name')).toBe(expectedResults[index].inputName);
- expect(checkbox.vm.$attrs.checked).toBe(events[index].value);
+ expect(checkbox.find('[type=hidden]').attributes('name')).toBe(
+ expectedResults[index].inputName,
+ );
+ expect(checkbox.find('[type=hidden]').attributes('value')).toBe(
+ events[index].value.toString(),
+ );
+ expect(checkBox.vm.$attrs.disabled).toBe(isInheriting);
+ expect(checkBox.vm.$attrs.checked).toBe(events[index].value);
});
});
});
- describe('events with field property', () => {
+ describe('events with field property, isInheriting = `%p`', () => {
const events = [
{
field: {
@@ -102,16 +119,21 @@ describe('TriggerFields', () => {
];
beforeEach(() => {
- createComponent({
- events,
- });
+ createComponent(
+ {
+ events,
+ },
+ isInheriting,
+ );
});
it('renders GlFormCheckbox for each event', () => {
expect(findAllGlFormCheckboxes()).toHaveLength(2);
});
- it('renders GlFormInput for each event', () => {
+ it(`renders GlFormInput for each event, which ${
+ isInheriting ? 'is' : 'is not'
+ } readonly`, () => {
const fields = findAllGlFormInputs();
const expectedResults = [
{
@@ -128,6 +150,7 @@ describe('TriggerFields', () => {
fields.wrappers.forEach((field, index) => {
expect(field.attributes()).toMatchObject(expectedResults[index]);
+ expect(field.vm.$attrs.readonly).toBe(isInheriting);
expect(field.vm.$attrs.value).toBe(events[index].field.value);
});
});
diff --git a/spec/frontend/integrations/edit/mock_data.js b/spec/frontend/integrations/edit/mock_data.js
new file mode 100644
index 00000000000..da2758ec15c
--- /dev/null
+++ b/spec/frontend/integrations/edit/mock_data.js
@@ -0,0 +1,18 @@
+// eslint-disable-next-line import/prefer-default-export
+export const mockIntegrationProps = {
+ id: 25,
+ activeToggleProps: {
+ initialActivated: true,
+ },
+ showActive: true,
+ triggerFieldsProps: {
+ initialTriggerCommit: false,
+ initialTriggerMergeRequest: false,
+ initialEnableComments: false,
+ },
+ jiraIssuesProps: {},
+ triggerEvents: [],
+ fields: [],
+ type: '',
+ inheritFromId: 25,
+};
diff --git a/spec/frontend/integrations/edit/store/actions_spec.js b/spec/frontend/integrations/edit/store/actions_spec.js
new file mode 100644
index 00000000000..c3ce6e51a3d
--- /dev/null
+++ b/spec/frontend/integrations/edit/store/actions_spec.js
@@ -0,0 +1,19 @@
+import createState from '~/integrations/edit/store/state';
+import { setOverride } from '~/integrations/edit/store/actions';
+import * as types from '~/integrations/edit/store/mutation_types';
+
+import testAction from 'helpers/vuex_action_helper';
+
+describe('Integration form store actions', () => {
+ let state;
+
+ beforeEach(() => {
+ state = createState();
+ });
+
+ describe('setOverride', () => {
+ it('should commit override mutation', () => {
+ return testAction(setOverride, true, state, [{ type: types.SET_OVERRIDE, payload: true }]);
+ });
+ });
+});
diff --git a/spec/frontend/integrations/edit/store/getters_spec.js b/spec/frontend/integrations/edit/store/getters_spec.js
new file mode 100644
index 00000000000..700d36edaad
--- /dev/null
+++ b/spec/frontend/integrations/edit/store/getters_spec.js
@@ -0,0 +1,71 @@
+import { currentKey, isInheriting, propsSource } from '~/integrations/edit/store/getters';
+import createState from '~/integrations/edit/store/state';
+import { mockIntegrationProps } from '../mock_data';
+
+describe('Integration form store getters', () => {
+ let state;
+ const customState = { ...mockIntegrationProps, type: 'CustomState' };
+ const adminState = { ...mockIntegrationProps, type: 'AdminState' };
+
+ beforeEach(() => {
+ state = createState({ customState });
+ });
+
+ describe('isInheriting', () => {
+ describe('when adminState is null', () => {
+ it('returns false', () => {
+ expect(isInheriting(state)).toBe(false);
+ });
+ });
+
+ describe('when adminState is an object', () => {
+ beforeEach(() => {
+ state.adminState = adminState;
+ });
+
+ describe('when override is false', () => {
+ beforeEach(() => {
+ state.override = false;
+ });
+
+ it('returns false', () => {
+ expect(isInheriting(state)).toBe(true);
+ });
+ });
+
+ describe('when override is true', () => {
+ beforeEach(() => {
+ state.override = true;
+ });
+
+ it('returns true', () => {
+ expect(isInheriting(state)).toBe(false);
+ });
+ });
+ });
+ });
+
+ describe('propsSource', () => {
+ beforeEach(() => {
+ state.adminState = adminState;
+ });
+
+ it('equals adminState if inheriting', () => {
+ expect(propsSource(state, { isInheriting: true })).toEqual(adminState);
+ });
+
+ it('equals customState if not inheriting', () => {
+ expect(propsSource(state, { isInheriting: false })).toEqual(customState);
+ });
+ });
+
+ describe('currentKey', () => {
+ it('equals `admin` if inheriting', () => {
+ expect(currentKey(state, { isInheriting: true })).toEqual('admin');
+ });
+
+ it('equals `custom` if not inheriting', () => {
+ expect(currentKey(state, { isInheriting: false })).toEqual('custom');
+ });
+ });
+});
diff --git a/spec/frontend/integrations/edit/store/mutations_spec.js b/spec/frontend/integrations/edit/store/mutations_spec.js
new file mode 100644
index 00000000000..4b733726d44
--- /dev/null
+++ b/spec/frontend/integrations/edit/store/mutations_spec.js
@@ -0,0 +1,19 @@
+import mutations from '~/integrations/edit/store/mutations';
+import createState from '~/integrations/edit/store/state';
+import * as types from '~/integrations/edit/store/mutation_types';
+
+describe('Integration form store mutations', () => {
+ let state;
+
+ beforeEach(() => {
+ state = createState();
+ });
+
+ describe(`${types.SET_OVERRIDE}`, () => {
+ it('sets override', () => {
+ mutations[types.SET_OVERRIDE](state, true);
+
+ expect(state.override).toBe(true);
+ });
+ });
+});
diff --git a/spec/frontend/integrations/edit/store/state_spec.js b/spec/frontend/integrations/edit/store/state_spec.js
new file mode 100644
index 00000000000..a8b431aa310
--- /dev/null
+++ b/spec/frontend/integrations/edit/store/state_spec.js
@@ -0,0 +1,26 @@
+import createState from '~/integrations/edit/store/state';
+
+describe('Integration form state factory', () => {
+ it('states default to null', () => {
+ expect(createState()).toEqual({
+ adminState: null,
+ customState: {},
+ override: false,
+ });
+ });
+
+ describe('override is initialized correctly', () => {
+ it.each([
+ [{ id: 25 }, { inheritFromId: null }, true],
+ [{ id: 25 }, { inheritFromId: 27 }, true],
+ [{ id: 25 }, { inheritFromId: 25 }, false],
+ [null, { inheritFromId: null }, false],
+ [null, { inheritFromId: 25 }, false],
+ ])(
+ 'for adminState: %p, customState: %p: override = `%p`',
+ (adminState, customState, expected) => {
+ expect(createState({ adminState, customState }).override).toEqual(expected);
+ },
+ );
+ });
+});
diff --git a/spec/frontend/issuable_suggestions/components/app_spec.js b/spec/frontend/issuable_suggestions/components/app_spec.js
index 20930be8667..d51c89807be 100644
--- a/spec/frontend/issuable_suggestions/components/app_spec.js
+++ b/spec/frontend/issuable_suggestions/components/app_spec.js
@@ -89,7 +89,7 @@ describe('Issuable suggestions app component', () => {
wrapper
.findAll('li')
.at(0)
- .is('.append-bottom-default'),
+ .is('.gl-mb-3'),
).toBe(true);
});
});
@@ -102,7 +102,7 @@ describe('Issuable suggestions app component', () => {
wrapper
.findAll('li')
.at(1)
- .is('.append-bottom-default'),
+ .is('.gl-mb-3'),
).toBe(false);
});
});
diff --git a/spec/frontend/issuable_suggestions/components/item_spec.js b/spec/frontend/issuable_suggestions/components/item_spec.js
index 6c3c30fcbb0..36799f4ee9f 100644
--- a/spec/frontend/issuable_suggestions/components/item_spec.js
+++ b/spec/frontend/issuable_suggestions/components/item_spec.js
@@ -4,6 +4,7 @@ import Icon from '~/vue_shared/components/icon.vue';
import UserAvatarImage from '~/vue_shared/components/user_avatar/user_avatar_image.vue';
import Suggestion from '~/issuable_suggestions/components/item.vue';
import mockData from '../mock_data';
+import { TEST_HOST } from 'jest/helpers/test_constants';
describe('Issuable suggestions suggestion component', () => {
let vm;
@@ -34,7 +35,7 @@ describe('Issuable suggestions suggestion component', () => {
const link = vm.find(GlLink);
- expect(link.attributes('href')).toBe(`${gl.TEST_HOST}/test/issue/1`);
+ expect(link.attributes('href')).toBe(`${TEST_HOST}/test/issue/1`);
});
it('renders IID', () => {
@@ -100,7 +101,7 @@ describe('Issuable suggestions suggestion component', () => {
const image = vm.find(UserAvatarImage);
- expect(image.props('imgSrc')).toBe(`${gl.TEST_HOST}/avatar`);
+ expect(image.props('imgSrc')).toBe(`${TEST_HOST}/avatar`);
});
});
diff --git a/spec/frontend/issuable_suggestions/mock_data.js b/spec/frontend/issuable_suggestions/mock_data.js
index 4f0f9ef8d62..4ce35ad5196 100644
--- a/spec/frontend/issuable_suggestions/mock_data.js
+++ b/spec/frontend/issuable_suggestions/mock_data.js
@@ -1,3 +1,5 @@
+import { TEST_HOST } from 'jest/helpers/test_constants';
+
function getDate(daysMinus) {
const today = new Date();
today.setDate(today.getDate() - daysMinus);
@@ -15,12 +17,12 @@ export default () => ({
createdAt: getDate(3),
updatedAt: getDate(2),
confidential: false,
- webUrl: `${gl.TEST_HOST}/test/issue/1`,
+ webUrl: `${TEST_HOST}/test/issue/1`,
title: 'Test issue',
author: {
- avatarUrl: `${gl.TEST_HOST}/avatar`,
+ avatarUrl: `${TEST_HOST}/avatar`,
name: 'Author Name',
username: 'author.username',
- webUrl: `${gl.TEST_HOST}/author`,
+ webUrl: `${TEST_HOST}/author`,
},
});
diff --git a/spec/frontend/issuables_list/components/issuable_list_root_app_spec.js b/spec/frontend/issuables_list/components/issuable_list_root_app_spec.js
index 899010bdb0f..aee49076b5d 100644
--- a/spec/frontend/issuables_list/components/issuable_list_root_app_spec.js
+++ b/spec/frontend/issuables_list/components/issuable_list_root_app_spec.js
@@ -16,10 +16,8 @@ describe('IssuableListRootApp', () => {
const findAlertLabel = () => wrapper.find(GlAlert).find(GlLabel);
const mountComponent = ({
- isFinishedAlertShowing = false,
- isInProgressAlertShowing = false,
- isInProgress = false,
- isFinished = false,
+ shouldShowFinishedAlert = false,
+ shouldShowInProgressAlert = false,
} = {}) =>
shallowMount(IssuableListRootApp, {
propsData: {
@@ -30,12 +28,11 @@ describe('IssuableListRootApp', () => {
},
data() {
return {
- isFinishedAlertShowing,
- isInProgressAlertShowing,
jiraImport: {
- isInProgress,
- isFinished,
+ importedIssuesCount: 1,
label,
+ shouldShowFinishedAlert,
+ shouldShowInProgressAlert,
},
};
},
@@ -57,8 +54,7 @@ describe('IssuableListRootApp', () => {
describe('when Jira import is in progress', () => {
it('shows an alert that tells the user a Jira import is in progress', () => {
wrapper = mountComponent({
- isInProgressAlertShowing: true,
- isInProgress: true,
+ shouldShowInProgressAlert: true,
});
expect(findAlert().text()).toBe(
@@ -70,14 +66,13 @@ describe('IssuableListRootApp', () => {
describe('when Jira import has finished', () => {
beforeEach(() => {
wrapper = mountComponent({
- isFinishedAlertShowing: true,
- isFinished: true,
+ shouldShowFinishedAlert: true,
});
});
describe('shows an alert', () => {
it('tells the user the Jira import has finished', () => {
- expect(findAlert().text()).toBe('Issues successfully imported with the label');
+ expect(findAlert().text()).toBe('1 issue successfully imported with the label');
});
it('contains the label title associated with the Jira import', () => {
@@ -105,8 +100,7 @@ describe('IssuableListRootApp', () => {
describe('alert message', () => {
it('is hidden when dismissed', () => {
wrapper = mountComponent({
- isInProgressAlertShowing: true,
- isInProgress: true,
+ shouldShowInProgressAlert: true,
});
expect(wrapper.contains(GlAlert)).toBe(true);
diff --git a/spec/frontend/issuables_list/components/issuable_spec.js b/spec/frontend/issuables_list/components/issuable_spec.js
index 834d18246a5..87868b7eeff 100644
--- a/spec/frontend/issuables_list/components/issuable_spec.js
+++ b/spec/frontend/issuables_list/components/issuable_spec.js
@@ -1,5 +1,5 @@
import { shallowMount } from '@vue/test-utils';
-import { GlSprintf } from '@gitlab/ui';
+import { GlSprintf, GlLabel, GlIcon } from '@gitlab/ui';
import { TEST_HOST } from 'helpers/test_constants';
import { trimText } from 'helpers/text_helper';
import initUserPopovers from '~/user_popovers';
@@ -8,6 +8,7 @@ import { mergeUrlParams } from '~/lib/utils/url_utility';
import Issuable from '~/issuables_list/components/issuable.vue';
import IssueAssignees from '~/vue_shared/components/issue/issue_assignees.vue';
import { simpleIssue, testAssignees, testLabels } from '../issuable_list_test_data';
+import { isScopedLabel } from '~/lib/utils/common_utils';
jest.mock('~/user_popovers');
@@ -37,13 +38,18 @@ describe('Issuable component', () => {
let DateOrig;
let wrapper;
- const factory = (props = {}) => {
+ const factory = (props = {}, scopedLabels = false) => {
wrapper = shallowMount(Issuable, {
propsData: {
issuable: simpleIssue,
baseUrl: TEST_BASE_URL,
...props,
},
+ provide: {
+ glFeatures: {
+ scopedLabels,
+ },
+ },
stubs: {
'gl-sprintf': GlSprintf,
'gl-link': '<a><slot></slot></a>',
@@ -57,6 +63,7 @@ describe('Issuable component', () => {
afterEach(() => {
wrapper.destroy();
+ wrapper = null;
});
beforeAll(() => {
@@ -68,14 +75,16 @@ describe('Issuable component', () => {
window.Date = DateOrig;
});
- const findConfidentialIcon = () => wrapper.find('.fa-eye-slash');
+ const checkExists = findFn => () => findFn().exists();
+ const hasConfidentialIcon = () =>
+ wrapper.findAll(GlIcon).wrappers.some(iconWrapper => iconWrapper.props('name') === 'eye-slash');
const findTaskStatus = () => wrapper.find('.task-status');
const findOpenedAgoContainer = () => wrapper.find('[data-testid="openedByMessage"]');
+ const findAuthor = () => wrapper.find({ ref: 'openedAgoByContainer' });
const findMilestone = () => wrapper.find('.js-milestone');
const findMilestoneTooltip = () => findMilestone().attributes('title');
const findDueDate = () => wrapper.find('.js-due-date');
- const findLabelContainer = () => wrapper.find('.js-labels');
- const findLabelLinks = () => findLabelContainer().findAll('a');
+ const findLabels = () => wrapper.findAll(GlLabel);
const findWeight = () => wrapper.find('.js-weight');
const findAssignees = () => wrapper.find(IssueAssignees);
const findMergeRequestsCount = () => wrapper.find('.js-merge-requests');
@@ -83,6 +92,11 @@ describe('Issuable component', () => {
const findDownvotes = () => wrapper.find('.js-downvotes');
const findNotes = () => wrapper.find('.js-notes');
const findBulkCheckbox = () => wrapper.find('input.selected-issuable');
+ const findScopedLabels = () => findLabels().filter(w => isScopedLabel({ title: w.text() }));
+ const findUnscopedLabels = () => findLabels().filter(w => !isScopedLabel({ title: w.text() }));
+ const findIssuableTitle = () => wrapper.find('[data-testid="issuable-title"]');
+ const findIssuableStatus = () => wrapper.find('[data-testid="issuable-status"]');
+ const containsJiraLogo = () => wrapper.contains('[data-testid="jira-logo"]');
describe('when mounted', () => {
it('initializes user popovers', () => {
@@ -94,6 +108,54 @@ describe('Issuable component', () => {
});
});
+ describe('when scopedLabels feature is available', () => {
+ beforeEach(() => {
+ issuable.labels = [...testLabels];
+
+ factory({ issuable }, true);
+ });
+
+ describe('when label is scoped', () => {
+ it('returns label with correct props', () => {
+ const scopedLabel = findScopedLabels().at(0);
+
+ expect(scopedLabel.props('scoped')).toBe(true);
+ });
+ });
+
+ describe('when label is not scoped', () => {
+ it('returns label with correct props', () => {
+ const notScopedLabel = findUnscopedLabels().at(0);
+
+ expect(notScopedLabel.props('scoped')).toBe(false);
+ });
+ });
+ });
+
+ describe('when scopedLabels feature is not available', () => {
+ beforeEach(() => {
+ issuable.labels = [...testLabels];
+
+ factory({ issuable });
+ });
+
+ describe('when label is scoped', () => {
+ it('label scoped props is false', () => {
+ const scopedLabel = findScopedLabels().at(0);
+
+ expect(scopedLabel.props('scoped')).toBe(false);
+ });
+ });
+
+ describe('when label is not scoped', () => {
+ it('label scoped props is false', () => {
+ const notScopedLabel = findUnscopedLabels().at(0);
+
+ expect(notScopedLabel.props('scoped')).toBe(false);
+ });
+ });
+ });
+
describe('with simple issuable', () => {
beforeEach(() => {
Object.assign(issuable, {
@@ -111,19 +173,19 @@ describe('Issuable component', () => {
});
it.each`
- desc | finder
- ${'bulk editing checkbox'} | ${findBulkCheckbox}
- ${'confidential icon'} | ${findConfidentialIcon}
- ${'task status'} | ${findTaskStatus}
- ${'milestone'} | ${findMilestone}
- ${'due date'} | ${findDueDate}
- ${'labels'} | ${findLabelContainer}
- ${'weight'} | ${findWeight}
- ${'merge request count'} | ${findMergeRequestsCount}
- ${'upvotes'} | ${findUpvotes}
- ${'downvotes'} | ${findDownvotes}
- `('does not render $desc', ({ finder }) => {
- expect(finder().exists()).toBe(false);
+ desc | check
+ ${'bulk editing checkbox'} | ${checkExists(findBulkCheckbox)}
+ ${'confidential icon'} | ${hasConfidentialIcon}
+ ${'task status'} | ${checkExists(findTaskStatus)}
+ ${'milestone'} | ${checkExists(findMilestone)}
+ ${'due date'} | ${checkExists(findDueDate)}
+ ${'labels'} | ${checkExists(findLabels)}
+ ${'weight'} | ${checkExists(findWeight)}
+ ${'merge request count'} | ${checkExists(findMergeRequestsCount)}
+ ${'upvotes'} | ${checkExists(findUpvotes)}
+ ${'downvotes'} | ${checkExists(findDownvotes)}
+ `('does not render $desc', ({ check }) => {
+ expect(check()).toBe(false);
});
it('show relative reference path', () => {
@@ -157,7 +219,41 @@ describe('Issuable component', () => {
});
it('renders the confidential icon', () => {
- expect(findConfidentialIcon().exists()).toBe(true);
+ expect(hasConfidentialIcon()).toBe(true);
+ });
+ });
+
+ describe('with Jira issuable', () => {
+ beforeEach(() => {
+ issuable.external_tracker = 'jira';
+
+ factory({ issuable });
+ });
+
+ it('renders the Jira icon', () => {
+ expect(containsJiraLogo()).toBe(true);
+ });
+
+ it('opens issuable in a new tab', () => {
+ expect(findIssuableTitle().props('target')).toBe('_blank');
+ });
+
+ it('opens author in a new tab', () => {
+ expect(findAuthor().props('target')).toBe('_blank');
+ });
+
+ describe('with Jira status', () => {
+ const expectedStatus = 'In Progress';
+
+ beforeEach(() => {
+ issuable.status = expectedStatus;
+
+ factory({ issuable });
+ });
+
+ it('renders the Jira status', () => {
+ expect(findIssuableStatus().text()).toBe(expectedStatus);
+ });
});
});
@@ -243,10 +339,10 @@ describe('Issuable component', () => {
it('renders labels', () => {
factory({ issuable });
- const labels = findLabelLinks().wrappers.map(label => ({
- href: label.attributes('href'),
+ const labels = findLabels().wrappers.map(label => ({
+ href: label.props('target'),
text: label.text(),
- tooltip: label.find('span').attributes('title'),
+ tooltip: label.attributes('description'),
}));
const expected = testLabels.map(label => ({
@@ -259,6 +355,33 @@ describe('Issuable component', () => {
});
});
+ describe('with labels for Jira issuable', () => {
+ beforeEach(() => {
+ issuable.labels = [...testLabels];
+ issuable.external_tracker = 'jira';
+
+ factory({ issuable });
+ });
+
+ it('renders labels', () => {
+ factory({ issuable });
+
+ const labels = findLabels().wrappers.map(label => ({
+ href: label.props('target'),
+ text: label.text(),
+ tooltip: label.attributes('description'),
+ }));
+
+ const expected = testLabels.map(label => ({
+ href: mergeUrlParams({ 'labels[]': label.name }, TEST_BASE_URL),
+ text: label.name,
+ tooltip: label.description,
+ }));
+
+ expect(labels).toEqual(expected);
+ });
+ });
+
describe.each`
weight
${0}
diff --git a/spec/frontend/issuables_list/components/issuables_list_app_spec.js b/spec/frontend/issuables_list/components/issuables_list_app_spec.js
index 6b680af354e..9f4995a54ee 100644
--- a/spec/frontend/issuables_list/components/issuables_list_app_spec.js
+++ b/spec/frontend/issuables_list/components/issuables_list_app_spec.js
@@ -7,6 +7,7 @@ import { TEST_HOST } from 'helpers/test_constants';
import flash from '~/flash';
import IssuablesListApp from '~/issuables_list/components/issuables_list_app.vue';
import Issuable from '~/issuables_list/components/issuable.vue';
+import FilteredSearchBar from '~/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue';
import issueablesEventBus from '~/issuables_list/eventhub';
import { PAGE_SIZE, PAGE_SIZE_MANUAL, RELATIVE_POSITION } from '~/issuables_list/constants';
@@ -59,6 +60,7 @@ describe('Issuables list component', () => {
const findLoading = () => wrapper.find(GlSkeletonLoading);
const findIssuables = () => wrapper.findAll(Issuable);
+ const findFilteredSearchBar = () => wrapper.find(FilteredSearchBar);
const findFirstIssuable = () => findIssuables().wrappers[0];
const findEmptyState = () => wrapper.find(GlEmptyState);
@@ -75,6 +77,7 @@ describe('Issuables list component', () => {
afterEach(() => {
wrapper.destroy();
+ wrapper = null;
mockAxios.restore();
window.location = oldLocation;
});
@@ -131,6 +134,7 @@ describe('Issuables list component', () => {
});
it('does not call API until mounted', () => {
+ factory();
expect(apiSpy).not.toHaveBeenCalled();
});
@@ -173,6 +177,12 @@ describe('Issuables list component', () => {
expect(wrapper.find(GlPagination).exists()).toBe(true);
});
});
+
+ it('does not render FilteredSearchBar', () => {
+ factory();
+
+ expect(findFilteredSearchBar().exists()).toBe(false);
+ });
});
describe('with bulk editing enabled', () => {
@@ -293,7 +303,7 @@ describe('Issuables list component', () => {
describe('when page is not present in params', () => {
const query =
- '?assignee_username=root&author_username=root&confidential=yes&label_name%5B%5D=Aquapod&label_name%5B%5D=Astro&milestone_title=v3.0&my_reaction_emoji=airplane&scope=all&sort=priority&state=opened&utf8=%E2%9C%93&weight=0';
+ '?assignee_username=root&author_username=root&confidential=yes&label_name%5B%5D=Aquapod&label_name%5B%5D=Astro&milestone_title=v3.0&my_reaction_emoji=airplane&scope=all&sort=priority&state=opened&utf8=%E2%9C%93&weight=0&not[label_name][]=Afterpod&not[milestone_title][]=13';
beforeEach(() => {
setUrl(query);
@@ -310,7 +320,11 @@ describe('Issuables list component', () => {
it('applies filters and sorts', () => {
expect(wrapper.vm.hasFilters).toBe(true);
- expect(wrapper.vm.filters).toEqual(expectedFilters);
+ expect(wrapper.vm.filters).toEqual({
+ ...expectedFilters,
+ 'not[milestone]': ['13'],
+ 'not[labels]': ['Afterpod'],
+ });
expect(apiSpy).toHaveBeenCalledWith(
expect.objectContaining({
@@ -319,6 +333,8 @@ describe('Issuables list component', () => {
with_labels_details: true,
page: 1,
per_page: PAGE_SIZE,
+ 'not[milestone]': ['13'],
+ 'not[labels]': ['Afterpod'],
},
}),
);
@@ -454,43 +470,117 @@ describe('Issuables list component', () => {
describe('when paginates', () => {
const newPage = 3;
- beforeEach(() => {
- window.history.pushState = jest.fn();
- setupApiMock(() => [
- 200,
- MOCK_ISSUES.slice(0, PAGE_SIZE),
- {
- 'x-total': 100,
- 'x-page': 2,
- },
- ]);
+ describe('when total-items is defined in response headers', () => {
+ beforeEach(() => {
+ window.history.pushState = jest.fn();
+ setupApiMock(() => [
+ 200,
+ MOCK_ISSUES.slice(0, PAGE_SIZE),
+ {
+ 'x-total': 100,
+ 'x-page': 2,
+ },
+ ]);
- factory();
+ factory();
- return waitForPromises();
+ return waitForPromises();
+ });
+
+ afterEach(() => {
+ // reset to original value
+ window.history.pushState.mockRestore();
+ });
+
+ it('calls window.history.pushState one time', () => {
+ // Trigger pagination
+ wrapper.find(GlPagination).vm.$emit('input', newPage);
+
+ expect(window.history.pushState).toHaveBeenCalledTimes(1);
+ });
+
+ it('sets params in the url', () => {
+ // Trigger pagination
+ wrapper.find(GlPagination).vm.$emit('input', newPage);
+
+ expect(window.history.pushState).toHaveBeenCalledWith(
+ {},
+ '',
+ `${TEST_LOCATION}?state=opened&order_by=priority&sort=asc&page=${newPage}`,
+ );
+ });
});
- afterEach(() => {
- // reset to original value
- window.history.pushState.mockRestore();
+ describe('when total-items is not defined in the headers', () => {
+ const page = 2;
+ const prevPage = page - 1;
+ const nextPage = page + 1;
+
+ beforeEach(() => {
+ setupApiMock(() => [
+ 200,
+ MOCK_ISSUES.slice(0, PAGE_SIZE),
+ {
+ 'x-page': page,
+ },
+ ]);
+
+ factory();
+
+ return waitForPromises();
+ });
+
+ it('finds the correct props applied to GlPagination', () => {
+ expect(wrapper.find(GlPagination).props()).toMatchObject({
+ nextPage,
+ prevPage,
+ value: page,
+ });
+ });
});
+ });
- it('calls window.history.pushState one time', () => {
- // Trigger pagination
- wrapper.find(GlPagination).vm.$emit('input', newPage);
+ describe('when type is "jira"', () => {
+ it('renders FilteredSearchBar', () => {
+ factory({ type: 'jira' });
- expect(window.history.pushState).toHaveBeenCalledTimes(1);
+ expect(findFilteredSearchBar().exists()).toBe(true);
});
- it('sets params in the url', () => {
- // Trigger pagination
- wrapper.find(GlPagination).vm.$emit('input', newPage);
+ describe('initialSortBy', () => {
+ const query = '?sort=updated_asc';
- expect(window.history.pushState).toHaveBeenCalledWith(
- {},
- '',
- `${TEST_LOCATION}?state=opened&order_by=priority&sort=asc&page=${newPage}`,
- );
+ it('sets default value', () => {
+ factory({ type: 'jira' });
+
+ expect(findFilteredSearchBar().props('initialSortBy')).toBe('created_desc');
+ });
+
+ it('sets value according to query', () => {
+ setUrl(query);
+
+ factory({ type: 'jira' });
+
+ expect(findFilteredSearchBar().props('initialSortBy')).toBe('updated_asc');
+ });
+ });
+
+ describe('initialFilterValue', () => {
+ it('does not set value when no query', () => {
+ factory({ type: 'jira' });
+
+ expect(findFilteredSearchBar().props('initialFilterValue')).toEqual([]);
+ });
+
+ it('sets value according to query', () => {
+ const query = '?search=free+text';
+
+ setUrl(query);
+
+ factory({ type: 'jira' });
+
+ expect(findFilteredSearchBar().props('initialFilterValue')).toEqual(['free text']);
+ });
});
});
});
diff --git a/spec/frontend/issue_show/components/issuable_header_warnings_spec.js b/spec/frontend/issue_show/components/issuable_header_warnings_spec.js
new file mode 100644
index 00000000000..5a166812d84
--- /dev/null
+++ b/spec/frontend/issue_show/components/issuable_header_warnings_spec.js
@@ -0,0 +1,79 @@
+import { shallowMount, createLocalVue } from '@vue/test-utils';
+import Vuex from 'vuex';
+import IssuableHeaderWarnings from '~/issue_show/components/issuable_header_warnings.vue';
+import createStore from '~/notes/stores';
+
+const localVue = createLocalVue();
+localVue.use(Vuex);
+
+describe('IssuableHeaderWarnings', () => {
+ let wrapper;
+ let store;
+
+ const findConfidential = () => wrapper.find('[data-testid="confidential"]');
+ const findLocked = () => wrapper.find('[data-testid="locked"]');
+ const confidentialIconName = () => findConfidential().attributes('name');
+ const lockedIconName = () => findLocked().attributes('name');
+
+ const createComponent = () => {
+ wrapper = shallowMount(IssuableHeaderWarnings, { store, localVue });
+ };
+
+ beforeEach(() => {
+ store = createStore();
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ wrapper = null;
+ store = null;
+ });
+
+ describe('when confidential is on', () => {
+ beforeEach(() => {
+ store.state.noteableData.confidential = true;
+
+ createComponent();
+ });
+
+ it('renders the confidential icon', () => {
+ expect(confidentialIconName()).toBe('eye-slash');
+ });
+ });
+
+ describe('when confidential is off', () => {
+ beforeEach(() => {
+ store.state.noteableData.confidential = false;
+
+ createComponent();
+ });
+
+ it('does not find the component', () => {
+ expect(findConfidential().exists()).toBe(false);
+ });
+ });
+
+ describe('when discussion locked is on', () => {
+ beforeEach(() => {
+ store.state.noteableData.discussion_locked = true;
+
+ createComponent();
+ });
+
+ it('renders the locked icon', () => {
+ expect(lockedIconName()).toBe('lock');
+ });
+ });
+
+ describe('when discussion locked is off', () => {
+ beforeEach(() => {
+ store.state.noteableData.discussion_locked = false;
+
+ createComponent();
+ });
+
+ it('does not find the component', () => {
+ expect(findLocked().exists()).toBe(false);
+ });
+ });
+});
diff --git a/spec/frontend/issue_show/components/pinned_links_spec.js b/spec/frontend/issue_show/components/pinned_links_spec.js
index 007ad4c9a1b..bb67feee601 100644
--- a/spec/frontend/issue_show/components/pinned_links_spec.js
+++ b/spec/frontend/issue_show/components/pinned_links_spec.js
@@ -1,6 +1,7 @@
import { shallowMount } from '@vue/test-utils';
-import { GlLink } from '@gitlab/ui';
+import { GlButton } from '@gitlab/ui';
import PinnedLinks from '~/issue_show/components/pinned_links.vue';
+import { STATUS_PAGE_PUBLISHED, JOIN_ZOOM_MEETING } from '~/issue_show/constants';
const plainZoomUrl = 'https://zoom.us/j/123456789';
const plainStatusUrl = 'https://status.com';
@@ -8,7 +9,7 @@ const plainStatusUrl = 'https://status.com';
describe('PinnedLinks', () => {
let wrapper;
- const findLinks = () => wrapper.findAll(GlLink);
+ const findButtons = () => wrapper.findAll(GlButton);
const createComponent = props => {
wrapper = shallowMount(PinnedLinks, {
@@ -26,10 +27,10 @@ describe('PinnedLinks', () => {
});
expect(
- findLinks()
+ findButtons()
.at(0)
.text(),
- ).toBe('Join Zoom meeting');
+ ).toBe(JOIN_ZOOM_MEETING);
});
it('displays Status link', () => {
@@ -38,10 +39,10 @@ describe('PinnedLinks', () => {
});
expect(
- findLinks()
+ findButtons()
.at(0)
.text(),
- ).toBe('Published on status page');
+ ).toBe(STATUS_PAGE_PUBLISHED);
});
it('does not render if there are no links', () => {
@@ -50,6 +51,6 @@ describe('PinnedLinks', () => {
publishedIncidentUrl: '',
});
- expect(wrapper.find(GlLink).exists()).toBe(false);
+ expect(findButtons()).toHaveLength(0);
});
});
diff --git a/spec/frontend/jira_import/components/__snapshots__/jira_import_form_spec.js.snap b/spec/frontend/jira_import/components/__snapshots__/jira_import_form_spec.js.snap
new file mode 100644
index 00000000000..975c31bb59c
--- /dev/null
+++ b/spec/frontend/jira_import/components/__snapshots__/jira_import_form_spec.js.snap
@@ -0,0 +1,277 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`JiraImportForm table body shows correct information in each cell 1`] = `
+<table
+ aria-busy="false"
+ aria-colcount="3"
+ class="table b-table gl-table b-table-fixed"
+ role="table"
+>
+ <!---->
+ <!---->
+ <thead
+ class=""
+ role="rowgroup"
+ >
+ <!---->
+ <tr
+ class=""
+ role="row"
+ >
+ <th
+ aria-colindex="1"
+ class=""
+ role="columnheader"
+ scope="col"
+ >
+ Jira display name
+ </th>
+ <th
+ aria-colindex="2"
+ aria-label="Arrow"
+ class=""
+ role="columnheader"
+ scope="col"
+ />
+ <th
+ aria-colindex="3"
+ class=""
+ role="columnheader"
+ scope="col"
+ >
+ GitLab username
+ </th>
+ </tr>
+ </thead>
+ <tbody
+ role="rowgroup"
+ >
+ <!---->
+ <tr
+ class=""
+ role="row"
+ >
+ <td
+ aria-colindex="1"
+ class=""
+ role="cell"
+ >
+ Jane Doe
+ </td>
+ <td
+ aria-colindex="2"
+ class=""
+ role="cell"
+ >
+ <svg
+ aria-label="Will be mapped to"
+ class="gl-icon s16"
+ data-testid="arrow-right-icon"
+ >
+ <use
+ href="#arrow-right"
+ />
+ </svg>
+ </td>
+ <td
+ aria-colindex="3"
+ class=""
+ role="cell"
+ >
+ <div
+ aria-label="The GitLab user to which the Jira user Jane Doe will be mapped"
+ class="dropdown b-dropdown gl-new-dropdown w-100 btn-group"
+ >
+ <!---->
+ <button
+ aria-expanded="false"
+ aria-haspopup="true"
+ class="btn dropdown-toggle btn-default btn-md gl-button gl-dropdown-toggle"
+ type="button"
+ >
+ <!---->
+
+ <span
+ class="gl-new-dropdown-button-text"
+ >
+ janedoe
+ </span>
+
+ <svg
+ class="dropdown-chevron gl-icon s16"
+ data-testid="chevron-down-icon"
+ >
+ <use
+ href="#chevron-down"
+ />
+ </svg>
+ </button>
+ <ul
+ class="dropdown-menu"
+ role="menu"
+ tabindex="-1"
+ >
+ <!---->
+
+ <div
+ class="gl-search-box-by-type m-2"
+ >
+ <svg
+ class="gl-search-box-by-type-search-icon gl-icon s16"
+ data-testid="search-icon"
+ >
+ <use
+ href="#search"
+ />
+ </svg>
+
+ <input
+ aria-label="Search"
+ class="gl-form-input gl-search-box-by-type-input form-control"
+ placeholder="Search"
+ type="text"
+ />
+
+ <div
+ class="gl-search-box-by-type-right-icons"
+ >
+ <!---->
+
+ <!---->
+ </div>
+ </div>
+
+ <li
+ class="gl-new-dropdown-text text-secondary"
+ role="presentation"
+ >
+ <p
+ class="b-dropdown-text"
+ >
+
+ No matches found
+
+ </p>
+ </li>
+
+ </ul>
+ </div>
+ </td>
+ </tr>
+ <tr
+ class=""
+ role="row"
+ >
+ <td
+ aria-colindex="1"
+ class=""
+ role="cell"
+ >
+ Fred Chopin
+ </td>
+ <td
+ aria-colindex="2"
+ class=""
+ role="cell"
+ >
+ <svg
+ aria-label="Will be mapped to"
+ class="gl-icon s16"
+ data-testid="arrow-right-icon"
+ >
+ <use
+ href="#arrow-right"
+ />
+ </svg>
+ </td>
+ <td
+ aria-colindex="3"
+ class=""
+ role="cell"
+ >
+ <div
+ aria-label="The GitLab user to which the Jira user Fred Chopin will be mapped"
+ class="dropdown b-dropdown gl-new-dropdown w-100 btn-group"
+ >
+ <!---->
+ <button
+ aria-expanded="false"
+ aria-haspopup="true"
+ class="btn dropdown-toggle btn-default btn-md gl-button gl-dropdown-toggle"
+ type="button"
+ >
+ <!---->
+
+ <span
+ class="gl-new-dropdown-button-text"
+ >
+ mrgitlab
+ </span>
+
+ <svg
+ class="dropdown-chevron gl-icon s16"
+ data-testid="chevron-down-icon"
+ >
+ <use
+ href="#chevron-down"
+ />
+ </svg>
+ </button>
+ <ul
+ class="dropdown-menu"
+ role="menu"
+ tabindex="-1"
+ >
+ <!---->
+
+ <div
+ class="gl-search-box-by-type m-2"
+ >
+ <svg
+ class="gl-search-box-by-type-search-icon gl-icon s16"
+ data-testid="search-icon"
+ >
+ <use
+ href="#search"
+ />
+ </svg>
+
+ <input
+ aria-label="Search"
+ class="gl-form-input gl-search-box-by-type-input form-control"
+ placeholder="Search"
+ type="text"
+ />
+
+ <div
+ class="gl-search-box-by-type-right-icons"
+ >
+ <!---->
+
+ <!---->
+ </div>
+ </div>
+
+ <li
+ class="gl-new-dropdown-text text-secondary"
+ role="presentation"
+ >
+ <p
+ class="b-dropdown-text"
+ >
+
+ No matches found
+
+ </p>
+ </li>
+
+ </ul>
+ </div>
+ </td>
+ </tr>
+ <!---->
+ <!---->
+ </tbody>
+ <!---->
+</table>
+`;
diff --git a/spec/frontend/jira_import/components/jira_import_app_spec.js b/spec/frontend/jira_import/components/jira_import_app_spec.js
index a21b89f6517..074f9842512 100644
--- a/spec/frontend/jira_import/components/jira_import_app_spec.js
+++ b/spec/frontend/jira_import/components/jira_import_app_spec.js
@@ -1,88 +1,19 @@
import { GlAlert, GlLoadingIcon } from '@gitlab/ui';
import { mount, shallowMount } from '@vue/test-utils';
+import AxiosMockAdapter from 'axios-mock-adapter';
import Vue from 'vue';
+import axios from '~/lib/utils/axios_utils';
import JiraImportApp from '~/jira_import/components/jira_import_app.vue';
import JiraImportForm from '~/jira_import/components/jira_import_form.vue';
import JiraImportProgress from '~/jira_import/components/jira_import_progress.vue';
import JiraImportSetup from '~/jira_import/components/jira_import_setup.vue';
import initiateJiraImportMutation from '~/jira_import/queries/initiate_jira_import.mutation.graphql';
-
-const mountComponent = ({
- isJiraConfigured = true,
- errorMessage = '',
- selectedProject = 'MTG',
- showAlert = false,
- isInProgress = false,
- loading = false,
- mutate = jest.fn(() => Promise.resolve()),
- mountType,
-} = {}) => {
- const mountFunction = mountType === 'mount' ? mount : shallowMount;
-
- return mountFunction(JiraImportApp, {
- propsData: {
- inProgressIllustration: 'in-progress-illustration.svg',
- isJiraConfigured,
- issuesPath: 'gitlab-org/gitlab-test/-/issues',
- jiraIntegrationPath: 'gitlab-org/gitlab-test/-/services/jira/edit',
- projectPath: 'gitlab-org/gitlab-test',
- setupIllustration: 'setup-illustration.svg',
- },
- data() {
- return {
- errorMessage,
- showAlert,
- selectedProject,
- jiraImportDetails: {
- isInProgress,
- imports: [
- {
- jiraProjectKey: 'MTG',
- scheduledAt: '2020-04-08T10:11:12+00:00',
- scheduledBy: {
- name: 'John Doe',
- },
- },
- {
- jiraProjectKey: 'MSJP',
- scheduledAt: '2020-04-09T13:14:15+00:00',
- scheduledBy: {
- name: 'Jimmy Doe',
- },
- },
- {
- jiraProjectKey: 'MTG',
- scheduledAt: '2020-04-09T16:17:18+00:00',
- scheduledBy: {
- name: 'Jane Doe',
- },
- },
- ],
- mostRecentImport: {
- jiraProjectKey: 'MTG',
- scheduledAt: '2020-04-09T16:17:18+00:00',
- scheduledBy: {
- name: 'Jane Doe',
- },
- },
- projects: [
- { text: 'My Jira Project (MJP)', value: 'MJP' },
- { text: 'My Second Jira Project (MSJP)', value: 'MSJP' },
- { text: 'Migrate to GitLab (MTG)', value: 'MTG' },
- ],
- },
- };
- },
- mocks: {
- $apollo: {
- loading,
- mutate,
- },
- },
- });
-};
+import getJiraUserMappingMutation from '~/jira_import/queries/get_jira_user_mapping.mutation.graphql';
+import { imports, issuesPath, jiraIntegrationPath, jiraProjects, userMappings } from '../mock_data';
describe('JiraImportApp', () => {
+ let axiosMock;
+ let mutateSpy;
let wrapper;
const getFormComponent = () => wrapper.find(JiraImportForm);
@@ -95,7 +26,64 @@ describe('JiraImportApp', () => {
const getLoadingIcon = () => wrapper.find(GlLoadingIcon);
+ const mountComponent = ({
+ isJiraConfigured = true,
+ errorMessage = '',
+ selectedProject = 'MTG',
+ showAlert = false,
+ isInProgress = false,
+ loading = false,
+ mutate = mutateSpy,
+ mountFunction = shallowMount,
+ } = {}) =>
+ mountFunction(JiraImportApp, {
+ propsData: {
+ inProgressIllustration: 'in-progress-illustration.svg',
+ isJiraConfigured,
+ issuesPath,
+ jiraIntegrationPath,
+ projectId: '5',
+ projectPath: 'gitlab-org/gitlab-test',
+ setupIllustration: 'setup-illustration.svg',
+ },
+ data() {
+ return {
+ isSubmitting: false,
+ selectedProject,
+ userMappings,
+ errorMessage,
+ showAlert,
+ jiraImportDetails: {
+ isInProgress,
+ imports,
+ mostRecentImport: imports[imports.length - 1],
+ projects: jiraProjects,
+ },
+ };
+ },
+ mocks: {
+ $apollo: {
+ loading,
+ mutate,
+ },
+ },
+ });
+
+ beforeEach(() => {
+ axiosMock = new AxiosMockAdapter(axios);
+ mutateSpy = jest.fn(() =>
+ Promise.resolve({
+ data: {
+ jiraImportStart: { errors: [] },
+ jiraImportUsers: { jiraUsers: [], errors: [] },
+ },
+ }),
+ );
+ });
+
afterEach(() => {
+ axiosMock.restore();
+ mutateSpy.mockRestore();
wrapper.destroy();
wrapper = null;
});
@@ -223,7 +211,7 @@ describe('JiraImportApp', () => {
});
it('shows warning alert to explain project MTG has been imported 2 times before', () => {
- wrapper = mountComponent({ mountType: 'mount' });
+ wrapper = mountComponent({ mountFunction: mount });
expect(getAlert().text()).toBe(
'You have imported from this project 2 times before. Each new import will create duplicate issues.',
@@ -248,9 +236,7 @@ describe('JiraImportApp', () => {
describe('initiating a Jira import', () => {
it('calls the mutation with the expected arguments', () => {
- const mutate = jest.fn(() => Promise.resolve());
-
- wrapper = mountComponent({ mutate });
+ wrapper = mountComponent();
const mutationArguments = {
mutation: initiateJiraImportMutation,
@@ -258,13 +244,23 @@ describe('JiraImportApp', () => {
input: {
jiraProjectKey: 'MTG',
projectPath: 'gitlab-org/gitlab-test',
+ usersMapping: [
+ {
+ jiraAccountId: 'aei23f98f-q23fj98qfj',
+ gitlabId: 15,
+ },
+ {
+ jiraAccountId: 'fu39y8t34w-rq3u289t3h4i',
+ gitlabId: undefined,
+ },
+ ],
},
},
};
getFormComponent().vm.$emit('initiateJiraImport', 'MTG');
- expect(mutate).toHaveBeenCalledWith(expect.objectContaining(mutationArguments));
+ expect(mutateSpy).toHaveBeenCalledWith(expect.objectContaining(mutationArguments));
});
it('shows alert message with error message on error', () => {
@@ -283,19 +279,53 @@ describe('JiraImportApp', () => {
});
});
- it('can dismiss alert message', () => {
- wrapper = mountComponent({
- errorMessage: 'There was an error importing the Jira project.',
- showAlert: true,
- selectedProject: null,
+ describe('alert', () => {
+ it('can be dismissed', () => {
+ wrapper = mountComponent({
+ errorMessage: 'There was an error importing the Jira project.',
+ showAlert: true,
+ selectedProject: null,
+ });
+
+ expect(getAlert().exists()).toBe(true);
+
+ getAlert().vm.$emit('dismiss');
+
+ return Vue.nextTick().then(() => {
+ expect(getAlert().exists()).toBe(false);
+ });
});
+ });
- expect(getAlert().exists()).toBe(true);
+ describe('on mount', () => {
+ it('makes a GraphQL mutation call to get user mappings', () => {
+ wrapper = mountComponent();
- getAlert().vm.$emit('dismiss');
+ const mutationArguments = {
+ mutation: getJiraUserMappingMutation,
+ variables: {
+ input: {
+ projectPath: 'gitlab-org/gitlab-test',
+ startAt: 1,
+ },
+ },
+ };
+
+ expect(mutateSpy).toHaveBeenCalledWith(expect.objectContaining(mutationArguments));
+ });
+
+ it('does not make a GraphQL mutation call to get user mappings when Jira is not configured', () => {
+ wrapper = mountComponent({ isJiraConfigured: false });
+
+ expect(mutateSpy).not.toHaveBeenCalled();
+ });
+
+ it('shows error message when there is an error with the GraphQL mutation call', () => {
+ const mutate = jest.fn(() => Promise.reject());
+
+ wrapper = mountComponent({ mutate });
- return Vue.nextTick().then(() => {
- expect(getAlert().exists()).toBe(false);
+ expect(getAlert().exists()).toBe(true);
});
});
});
diff --git a/spec/frontend/jira_import/components/jira_import_form_spec.js b/spec/frontend/jira_import/components/jira_import_form_spec.js
index dea94e7bf1f..685b0288e92 100644
--- a/spec/frontend/jira_import/components/jira_import_form_spec.js
+++ b/spec/frontend/jira_import/components/jira_import_form_spec.js
@@ -1,44 +1,51 @@
-import { GlAvatar, GlButton, GlFormSelect, GlLabel } from '@gitlab/ui';
+import { GlButton, GlFormSelect, GlLabel, GlTable } from '@gitlab/ui';
+import { getByRole } from '@testing-library/dom';
import { mount, shallowMount } from '@vue/test-utils';
+import AxiosMockAdapter from 'axios-mock-adapter';
+import axios from '~/lib/utils/axios_utils';
import JiraImportForm from '~/jira_import/components/jira_import_form.vue';
-
-const importLabel = 'jira-import::MTG-1';
-const value = 'MTG';
-
-const mountComponent = ({ mountType } = {}) => {
- const mountFunction = mountType === 'mount' ? mount : shallowMount;
-
- return mountFunction(JiraImportForm, {
- propsData: {
- importLabel,
- issuesPath: 'gitlab-org/gitlab-test/-/issues',
- jiraProjects: [
- {
- text: 'My Jira Project',
- value: 'MJP',
- },
- {
- text: 'My Second Jira Project',
- value: 'MSJP',
- },
- {
- text: 'Migrate to GitLab',
- value: 'MTG',
- },
- ],
- value,
- },
- });
-};
+import { issuesPath, jiraProjects, userMappings } from '../mock_data';
describe('JiraImportForm', () => {
+ let axiosMock;
let wrapper;
+ const currentUsername = 'mrgitlab';
+ const importLabel = 'jira-import::MTG-1';
+ const value = 'MTG';
+
const getSelectDropdown = () => wrapper.find(GlFormSelect);
const getCancelButton = () => wrapper.findAll(GlButton).at(1);
+ const getHeader = name => getByRole(wrapper.element, 'columnheader', { name });
+
+ const mountComponent = ({ isSubmitting = false, mountFunction = shallowMount } = {}) =>
+ mountFunction(JiraImportForm, {
+ propsData: {
+ importLabel,
+ isSubmitting,
+ issuesPath,
+ jiraProjects,
+ projectId: '5',
+ userMappings,
+ value,
+ },
+ data: () => ({
+ isFetching: false,
+ searchTerm: '',
+ selectState: null,
+ users: [],
+ }),
+ currentUsername,
+ });
+
+ beforeEach(() => {
+ axiosMock = new AxiosMockAdapter(axios);
+ });
+
afterEach(() => {
+ axiosMock.restore();
wrapper.destroy();
wrapper = null;
});
@@ -51,16 +58,22 @@ describe('JiraImportForm', () => {
});
it('contains a list of Jira projects to select from', () => {
- wrapper = mountComponent({ mountType: 'mount' });
-
- const optionItems = ['My Jira Project', 'My Second Jira Project', 'Migrate to GitLab'];
+ wrapper = mountComponent({ mountFunction: mount });
getSelectDropdown()
.findAll('option')
.wrappers.forEach((optionEl, index) => {
- expect(optionEl.text()).toBe(optionItems[index]);
+ expect(optionEl.text()).toBe(jiraProjects[index].text);
});
});
+
+ it('emits an "input" event when the input select value changes', () => {
+ wrapper = mountComponent();
+
+ getSelectDropdown().vm.$emit('change', value);
+
+ expect(wrapper.emitted('input')[0]).toEqual([value]);
+ });
});
describe('form information', () => {
@@ -72,64 +85,90 @@ describe('JiraImportForm', () => {
expect(wrapper.find(GlLabel).props('title')).toBe(importLabel);
});
+ it('shows a heading for the user mapping section', () => {
+ expect(
+ getByRole(wrapper.element, 'heading', { name: 'Jira-GitLab user mapping template' }),
+ ).toBeTruthy();
+ });
+
it('shows information to the user', () => {
expect(wrapper.find('p').text()).toBe(
- "For each Jira issue successfully imported, we'll create a new GitLab issue with the following data:",
+ 'Jira users have been matched with similar GitLab users. This can be overwritten by selecting a GitLab user from the dropdown in the "GitLab username" column. If it wasn\'t possible to match a Jira user with a GitLab user, the dropdown defaults to the user conducting the import.',
);
});
+ });
- it('shows jira.issue.summary for the Title', () => {
- expect(wrapper.find('[id="jira-project-title"]').text()).toBe('jira.issue.summary');
- });
+ describe('table', () => {
+ describe('headers', () => {
+ beforeEach(() => {
+ wrapper = mountComponent({ mountFunction: mount });
+ });
- it('shows an avatar for the Reporter', () => {
- expect(wrapper.contains(GlAvatar)).toBe(true);
- });
+ it('has a "Jira display name" column', () => {
+ expect(getHeader('Jira display name')).toBeTruthy();
+ });
- it('shows jira.issue.description.content for the Description', () => {
- expect(wrapper.find('[id="jira-project-description"]').text()).toBe(
- 'jira.issue.description.content',
- );
- });
- });
+ it('has an "arrow" column', () => {
+ expect(getHeader('Arrow')).toBeTruthy();
+ });
- describe('Next button', () => {
- beforeEach(() => {
- wrapper = mountComponent();
+ it('has a "GitLab username" column', () => {
+ expect(getHeader('GitLab username')).toBeTruthy();
+ });
});
- it('is shown', () => {
- expect(wrapper.find(GlButton).text()).toBe('Next');
+ describe('body', () => {
+ it('shows all user mappings', () => {
+ wrapper = mountComponent({ mountFunction: mount });
+
+ expect(wrapper.find(GlTable).findAll('tbody tr').length).toBe(userMappings.length);
+ });
+
+ it('shows correct information in each cell', () => {
+ wrapper = mountComponent({ mountFunction: mount });
+
+ expect(wrapper.find(GlTable).element).toMatchSnapshot();
+ });
});
});
- describe('Cancel button', () => {
- beforeEach(() => {
- wrapper = mountComponent();
- });
+ describe('buttons', () => {
+ describe('"Continue" button', () => {
+ it('is shown', () => {
+ wrapper = mountComponent();
- it('is shown', () => {
- expect(getCancelButton().text()).toBe('Cancel');
- });
+ expect(wrapper.find(GlButton).text()).toBe('Continue');
+ });
+
+ it('is in loading state when the form is submitting', async () => {
+ wrapper = mountComponent({ isSubmitting: true });
- it('links to the Issues page', () => {
- expect(getCancelButton().attributes('href')).toBe('gitlab-org/gitlab-test/-/issues');
+ expect(wrapper.find(GlButton).props('loading')).toBe(true);
+ });
});
- });
- it('emits an "input" event when the input select value changes', () => {
- wrapper = mountComponent({ mountType: 'mount' });
+ describe('"Cancel" button', () => {
+ beforeEach(() => {
+ wrapper = mountComponent();
+ });
- getSelectDropdown().vm.$emit('change', value);
+ it('is shown', () => {
+ expect(getCancelButton().text()).toBe('Cancel');
+ });
- expect(wrapper.emitted('input')[0]).toEqual([value]);
+ it('links to the Issues page', () => {
+ expect(getCancelButton().attributes('href')).toBe(issuesPath);
+ });
+ });
});
- it('emits an "initiateJiraImport" event with the selected dropdown value when submitted', () => {
- wrapper = mountComponent();
+ describe('form', () => {
+ it('emits an "initiateJiraImport" event with the selected dropdown value when submitted', () => {
+ wrapper = mountComponent();
- wrapper.find('form').trigger('submit');
+ wrapper.find('form').trigger('submit');
- expect(wrapper.emitted('initiateJiraImport')[0]).toEqual([value]);
+ expect(wrapper.emitted('initiateJiraImport')[0]).toEqual([value]);
+ });
});
});
diff --git a/spec/frontend/jira_import/components/jira_import_progress_spec.js b/spec/frontend/jira_import/components/jira_import_progress_spec.js
index 3ccf14554e1..ed7e1824fa3 100644
--- a/spec/frontend/jira_import/components/jira_import_progress_spec.js
+++ b/spec/frontend/jira_import/components/jira_import_progress_spec.js
@@ -1,14 +1,13 @@
import { GlEmptyState } from '@gitlab/ui';
import { mount, shallowMount } from '@vue/test-utils';
import JiraImportProgress from '~/jira_import/components/jira_import_progress.vue';
-
-const illustration = 'illustration.svg';
-const importProject = 'JIRAPROJECT';
-const issuesPath = 'gitlab-org/gitlab-test/-/issues';
+import { illustration, issuesPath } from '../mock_data';
describe('JiraImportProgress', () => {
let wrapper;
+ const importProject = 'JIRAPROJECT';
+
const getGlEmptyStateProp = attribute => wrapper.find(GlEmptyState).props(attribute);
const getParagraphText = () => wrapper.find('p').text();
diff --git a/spec/frontend/jira_import/components/jira_import_setup_spec.js b/spec/frontend/jira_import/components/jira_import_setup_spec.js
index aa94dc4f503..7c84d4a166a 100644
--- a/spec/frontend/jira_import/components/jira_import_setup_spec.js
+++ b/spec/frontend/jira_import/components/jira_import_setup_spec.js
@@ -1,9 +1,7 @@
import { GlEmptyState } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import JiraImportSetup from '~/jira_import/components/jira_import_setup.vue';
-
-const illustration = 'illustration.svg';
-const jiraIntegrationPath = 'gitlab-org/gitlab-test/-/services/jira/edit';
+import { illustration, jiraIntegrationPath } from '../mock_data';
describe('JiraImportSetup', () => {
let wrapper;
diff --git a/spec/frontend/jira_import/mock_data.js b/spec/frontend/jira_import/mock_data.js
index e82ab53cb6f..a7447221b15 100644
--- a/spec/frontend/jira_import/mock_data.js
+++ b/spec/frontend/jira_import/mock_data.js
@@ -70,3 +70,56 @@ export const jiraImportMutationResponse = {
__typename: 'JiraImportStartPayload',
},
};
+
+export const issuesPath = 'gitlab-org/gitlab-test/-/issues';
+
+export const jiraIntegrationPath = 'gitlab-org/gitlab-test/-/services/jira/edit';
+
+export const illustration = 'illustration.svg';
+
+export const jiraProjects = [
+ { text: 'My Jira Project (MJP)', value: 'MJP' },
+ { text: 'My Second Jira Project (MSJP)', value: 'MSJP' },
+ { text: 'Migrate to GitLab (MTG)', value: 'MTG' },
+];
+
+export const imports = [
+ {
+ jiraProjectKey: 'MTG',
+ scheduledAt: '2020-04-08T10:11:12+00:00',
+ scheduledBy: {
+ name: 'John Doe',
+ },
+ },
+ {
+ jiraProjectKey: 'MSJP',
+ scheduledAt: '2020-04-09T13:14:15+00:00',
+ scheduledBy: {
+ name: 'Jimmy Doe',
+ },
+ },
+ {
+ jiraProjectKey: 'MTG',
+ scheduledAt: '2020-04-09T16:17:18+00:00',
+ scheduledBy: {
+ name: 'Jane Doe',
+ },
+ },
+];
+
+export const userMappings = [
+ {
+ jiraAccountId: 'aei23f98f-q23fj98qfj',
+ jiraDisplayName: 'Jane Doe',
+ jiraEmail: 'janedoe@example.com',
+ gitlabId: 15,
+ gitlabUsername: 'janedoe',
+ },
+ {
+ jiraAccountId: 'fu39y8t34w-rq3u289t3h4i',
+ jiraDisplayName: 'Fred Chopin',
+ jiraEmail: 'fredchopin@example.com',
+ gitlabId: undefined,
+ gitlabUsername: undefined,
+ },
+];
diff --git a/spec/frontend/jira_import/utils/jira_import_utils_spec.js b/spec/frontend/jira_import/utils/jira_import_utils_spec.js
index 504d399217a..8ae1fc3535a 100644
--- a/spec/frontend/jira_import/utils/jira_import_utils_spec.js
+++ b/spec/frontend/jira_import/utils/jira_import_utils_spec.js
@@ -1,10 +1,16 @@
+import { useLocalStorageSpy } from 'helpers/local_storage_helper';
import {
calculateJiraImportLabel,
extractJiraProjectsOptions,
IMPORT_STATE,
isFinished,
isInProgress,
+ setFinishedAlertHideMap,
+ shouldShowFinishedAlert,
} from '~/jira_import/utils/jira_import_utils';
+import { JIRA_IMPORT_SUCCESS_ALERT_HIDE_MAP_KEY } from '~/issuables_list/constants';
+
+useLocalStorageSpy();
describe('isInProgress', () => {
it.each`
@@ -89,3 +95,56 @@ describe('calculateJiraImportLabel', () => {
expect(label.color).toBe('#333');
});
});
+
+describe('shouldShowFinishedAlert', () => {
+ const labelTitle = 'jira-import::JCP-1';
+
+ afterEach(() => {
+ localStorage.clear();
+ });
+
+ it('checks localStorage value', () => {
+ jest.spyOn(localStorage, 'getItem').mockReturnValue(JSON.stringify({}));
+
+ shouldShowFinishedAlert(labelTitle, IMPORT_STATE.FINISHED);
+
+ expect(localStorage.getItem).toHaveBeenCalledWith(JIRA_IMPORT_SUCCESS_ALERT_HIDE_MAP_KEY);
+ });
+
+ it('returns true when an import has finished', () => {
+ jest.spyOn(localStorage, 'getItem').mockReturnValue(JSON.stringify({}));
+
+ expect(shouldShowFinishedAlert(labelTitle, IMPORT_STATE.FINISHED)).toBe(true);
+ });
+
+ it('returns false when an import has finished but the user chose to hide the alert', () => {
+ jest.spyOn(localStorage, 'getItem').mockReturnValue(JSON.stringify({ [labelTitle]: true }));
+
+ expect(shouldShowFinishedAlert(labelTitle, IMPORT_STATE.FINISHED)).toBe(false);
+ });
+
+ it('returns false when an import has not finished', () => {
+ jest.spyOn(localStorage, 'getItem').mockReturnValue(JSON.stringify({}));
+
+ expect(shouldShowFinishedAlert(labelTitle, IMPORT_STATE.SCHEDULED)).toBe(false);
+ });
+});
+
+describe('setFinishedAlertHideMap', () => {
+ const labelTitle = 'jira-import::ABC-1';
+ const newLabelTitle = 'jira-import::JCP-1';
+
+ it('sets item to localStorage correctly', () => {
+ jest.spyOn(localStorage, 'getItem').mockReturnValue(JSON.stringify({ [labelTitle]: true }));
+
+ setFinishedAlertHideMap(newLabelTitle);
+
+ expect(localStorage.setItem).toHaveBeenCalledWith(
+ JIRA_IMPORT_SUCCESS_ALERT_HIDE_MAP_KEY,
+ JSON.stringify({
+ [labelTitle]: true,
+ [newLabelTitle]: true,
+ }),
+ );
+ });
+});
diff --git a/spec/frontend/jobs/components/job_app_spec.js b/spec/frontend/jobs/components/job_app_spec.js
index 8fa289bbe4d..d0b3d4f6247 100644
--- a/spec/frontend/jobs/components/job_app_spec.js
+++ b/spec/frontend/jobs/components/job_app_spec.js
@@ -6,6 +6,7 @@ import axios from '~/lib/utils/axios_utils';
import JobApp from '~/jobs/components/job_app.vue';
import createStore from '~/jobs/store';
import job from '../mock_data';
+import { TEST_HOST } from 'jest/helpers/test_constants';
describe('Job App', () => {
const localVue = createLocalVue();
@@ -18,8 +19,8 @@ describe('Job App', () => {
let mock;
const initSettings = {
- endpoint: `${gl.TEST_HOST}jobs/123.json`,
- pagePath: `${gl.TEST_HOST}jobs/123`,
+ endpoint: `${TEST_HOST}jobs/123.json`,
+ pagePath: `${TEST_HOST}jobs/123`,
logState:
'eyJvZmZzZXQiOjE3NDUxLCJuX29wZW5fdGFncyI6MCwiZmdfY29sb3IiOm51bGwsImJnX2NvbG9yIjpudWxsLCJzdHlsZV9tYXNrIjowfQ%3D%3D',
};
@@ -397,132 +398,31 @@ describe('Job App', () => {
});
});
- describe('trace output', () => {
- describe('with append flag', () => {
- it('appends the log content to the existing one', () =>
- setupAndMount({
- traceData: {
- html: '<span>More<span>',
- status: 'running',
- state: 'newstate',
- append: true,
- complete: true,
- },
- })
- .then(() => {
- store.state.trace = 'Update';
-
- return wrapper.vm.$nextTick();
- })
- .then(() => {
- expect(
- wrapper
- .find('.js-build-trace')
- .text()
- .trim(),
- ).toEqual('Update');
- }));
+ describe('trace controls', () => {
+ beforeEach(() =>
+ setupAndMount({
+ traceData: {
+ html: '<span>Update</span>',
+ status: 'success',
+ append: false,
+ size: 50,
+ total: 100,
+ complete: true,
+ },
+ }),
+ );
+
+ it('should render scroll buttons', () => {
+ expect(wrapper.find('.js-scroll-top').exists()).toBe(true);
+ expect(wrapper.find('.js-scroll-bottom').exists()).toBe(true);
});
- describe('without append flag', () => {
- it('replaces the trace', () =>
- setupAndMount({
- traceData: {
- html: '<span>Different<span>',
- status: 'running',
- append: false,
- complete: true,
- },
- }).then(() => {
- expect(
- wrapper
- .find('.js-build-trace')
- .text()
- .trim(),
- ).toEqual('Different');
- }));
- });
-
- describe('truncated information', () => {
- describe('when size is less than total', () => {
- it('shows information about truncated log', () => {
- mock.onGet(`${props.pagePath}/trace.json`).reply(200, {
- html: '<span>Update</span>',
- status: 'success',
- append: false,
- size: 50,
- total: 100,
- complete: true,
- });
-
- return setupAndMount({
- traceData: {
- html: '<span>Update</span>',
- status: 'success',
- append: false,
- size: 50,
- total: 100,
- complete: true,
- },
- }).then(() => {
- expect(
- wrapper
- .find('.js-truncated-info')
- .text()
- .trim(),
- ).toContain('Showing last 50 bytes');
- });
- });
- });
-
- describe('when size is equal than total', () => {
- it('does not show the truncated information', () =>
- setupAndMount({
- traceData: {
- html: '<span>Update</span>',
- status: 'success',
- append: false,
- size: 100,
- total: 100,
- complete: true,
- },
- }).then(() => {
- expect(
- wrapper
- .find('.js-truncated-info')
- .text()
- .trim(),
- ).toEqual('');
- }));
- });
+ it('should render link to raw ouput', () => {
+ expect(wrapper.find('.js-raw-link-controller').exists()).toBe(true);
});
- describe('trace controls', () => {
- beforeEach(() =>
- setupAndMount({
- traceData: {
- html: '<span>Update</span>',
- status: 'success',
- append: false,
- size: 50,
- total: 100,
- complete: true,
- },
- }),
- );
-
- it('should render scroll buttons', () => {
- expect(wrapper.find('.js-scroll-top').exists()).toBe(true);
- expect(wrapper.find('.js-scroll-bottom').exists()).toBe(true);
- });
-
- it('should render link to raw ouput', () => {
- expect(wrapper.find('.js-raw-link-controller').exists()).toBe(true);
- });
-
- it('should render link to erase job', () => {
- expect(wrapper.find('.js-erase-link').exists()).toBe(true);
- });
+ it('should render link to erase job', () => {
+ expect(wrapper.find('.js-erase-link').exists()).toBe(true);
});
});
});
diff --git a/spec/frontend/jobs/components/job_log_spec.js b/spec/frontend/jobs/components/job_log_spec.js
deleted file mode 100644
index a167fe8a134..00000000000
--- a/spec/frontend/jobs/components/job_log_spec.js
+++ /dev/null
@@ -1,65 +0,0 @@
-import Vue from 'vue';
-import { mountComponentWithStore } from 'helpers/vue_mount_component_helper';
-import component from '~/jobs/components/job_log.vue';
-import createStore from '~/jobs/store';
-import { resetStore } from '../store/helpers';
-
-describe('Job Log', () => {
- const Component = Vue.extend(component);
- let store;
- let vm;
-
- const trace =
- '<span>Running with gitlab-runner 12.1.0 (de7731dd)<br/></span><span> on docker-auto-scale-com d5ae8d25<br/></span><div class="gl-mr-3" data-timestamp="1565502765" data-section="prepare-executor" role="button"></div><span class="section section-header js-s-prepare-executor">Using Docker executor with image ruby:2.6 ...<br/></span>';
-
- beforeEach(() => {
- store = createStore();
- });
-
- afterEach(() => {
- resetStore(store);
- vm.$destroy();
- });
-
- it('renders provided trace', () => {
- vm = mountComponentWithStore(Component, {
- props: {
- trace,
- isComplete: true,
- },
- store,
- });
-
- expect(vm.$el.querySelector('code').textContent).toContain(
- 'Running with gitlab-runner 12.1.0 (de7731dd)',
- );
- });
-
- describe('while receiving trace', () => {
- it('renders animation', () => {
- vm = mountComponentWithStore(Component, {
- props: {
- trace,
- isComplete: false,
- },
- store,
- });
-
- expect(vm.$el.querySelector('.js-log-animation')).not.toBeNull();
- });
- });
-
- describe('when build trace has finishes', () => {
- it('does not render animation', () => {
- vm = mountComponentWithStore(Component, {
- props: {
- trace,
- isComplete: true,
- },
- store,
- });
-
- expect(vm.$el.querySelector('.js-log-animation')).toBeNull();
- });
- });
-});
diff --git a/spec/frontend/jobs/components/log/collapsible_section_spec.js b/spec/frontend/jobs/components/log/collapsible_section_spec.js
index 3a16521a986..bf2f8c05806 100644
--- a/spec/frontend/jobs/components/log/collapsible_section_spec.js
+++ b/spec/frontend/jobs/components/log/collapsible_section_spec.js
@@ -1,5 +1,5 @@
import { mount } from '@vue/test-utils';
-import CollpasibleSection from '~/jobs/components/log/collapsible_section.vue';
+import CollapsibleSection from '~/jobs/components/log/collapsible_section.vue';
import { collapsibleSectionClosed, collapsibleSectionOpened } from './mock_data';
describe('Job Log Collapsible Section', () => {
@@ -11,7 +11,7 @@ describe('Job Log Collapsible Section', () => {
const findCollapsibleLineSvg = () => wrapper.find('.collapsible-line svg');
const createComponent = (props = {}) => {
- wrapper = mount(CollpasibleSection, {
+ wrapper = mount(CollapsibleSection, {
propsData: {
...props,
},
diff --git a/spec/frontend/jobs/store/mutations_spec.js b/spec/frontend/jobs/store/mutations_spec.js
index 3557d3b94b6..608abc8f7c4 100644
--- a/spec/frontend/jobs/store/mutations_spec.js
+++ b/spec/frontend/jobs/store/mutations_spec.js
@@ -76,28 +76,15 @@ describe('Jobs Store Mutations', () => {
lines: [],
});
- expect(stateCopy.trace).toEqual(html);
expect(stateCopy.traceSize).toEqual(511846);
expect(stateCopy.isTraceComplete).toEqual(true);
});
describe('with new job log', () => {
- let stateWithNewLog;
- beforeEach(() => {
- gon.features = gon.features || {};
- gon.features.jobLogJson = true;
-
- stateWithNewLog = state();
- });
-
- afterEach(() => {
- gon.features.jobLogJson = false;
- });
-
describe('log.lines', () => {
describe('when append is true', () => {
it('sets the parsed log ', () => {
- mutations[types.RECEIVE_TRACE_SUCCESS](stateWithNewLog, {
+ mutations[types.RECEIVE_TRACE_SUCCESS](stateCopy, {
append: true,
size: 511846,
complete: true,
@@ -109,7 +96,7 @@ describe('Jobs Store Mutations', () => {
],
});
- expect(stateWithNewLog.trace).toEqual([
+ expect(stateCopy.trace).toEqual([
{
offset: 1,
content: [{ text: 'Running with gitlab-runner 11.12.1 (5a147c92)' }],
@@ -121,7 +108,7 @@ describe('Jobs Store Mutations', () => {
describe('when it is defined', () => {
it('sets the parsed log ', () => {
- mutations[types.RECEIVE_TRACE_SUCCESS](stateWithNewLog, {
+ mutations[types.RECEIVE_TRACE_SUCCESS](stateCopy, {
append: false,
size: 511846,
complete: true,
@@ -130,7 +117,7 @@ describe('Jobs Store Mutations', () => {
],
});
- expect(stateWithNewLog.trace).toEqual([
+ expect(stateCopy.trace).toEqual([
{
offset: 0,
content: [{ text: 'Running with gitlab-runner 11.11.1 (5a147c92)' }],
@@ -142,7 +129,7 @@ describe('Jobs Store Mutations', () => {
describe('when it is null', () => {
it('sets the default value', () => {
- mutations[types.RECEIVE_TRACE_SUCCESS](stateWithNewLog, {
+ mutations[types.RECEIVE_TRACE_SUCCESS](stateCopy, {
append: true,
html,
size: 511846,
@@ -150,7 +137,7 @@ describe('Jobs Store Mutations', () => {
lines: null,
});
- expect(stateWithNewLog.trace).toEqual([]);
+ expect(stateCopy.trace).toEqual([]);
});
});
});
diff --git a/spec/frontend/jobs/store/utils_spec.js b/spec/frontend/jobs/store/utils_spec.js
index 8819f39dee0..294f88bbc74 100644
--- a/spec/frontend/jobs/store/utils_spec.js
+++ b/spec/frontend/jobs/store/utils_spec.js
@@ -181,7 +181,7 @@ describe('Jobs Store Utils', () => {
});
});
- describe('collpasible section', () => {
+ describe('collapsible section', () => {
it('adds a `isClosed` property', () => {
expect(result[1].isClosed).toEqual(false);
});
@@ -190,7 +190,7 @@ describe('Jobs Store Utils', () => {
expect(result[1].isHeader).toEqual(true);
});
- it('creates a lines array property with the content of the collpasible section', () => {
+ it('creates a lines array property with the content of the collapsible section', () => {
expect(result[1].lines.length).toEqual(2);
expect(result[1].lines[0].content).toEqual(utilsMockData[2].content);
expect(result[1].lines[1].content).toEqual(utilsMockData[3].content);
diff --git a/spec/frontend/lib/utils/common_utils_spec.js b/spec/frontend/lib/utils/common_utils_spec.js
index f597255538c..585f0de9cc3 100644
--- a/spec/frontend/lib/utils/common_utils_spec.js
+++ b/spec/frontend/lib/utils/common_utils_spec.js
@@ -330,32 +330,6 @@ describe('common_utils', () => {
});
});
- describe('normalizeCRLFHeaders', () => {
- const testContext = {};
- beforeEach(() => {
- testContext.CLRFHeaders =
- 'a-header: a-value\nAnother-Header: ANOTHER-VALUE\nLaSt-HeAdEr: last-VALUE';
- jest.spyOn(String.prototype, 'split');
- testContext.normalizeCRLFHeaders = commonUtils.normalizeCRLFHeaders(testContext.CLRFHeaders);
- });
-
- it('should split by newline', () => {
- expect(String.prototype.split).toHaveBeenCalledWith('\n');
- });
-
- it('should split by colon+space for each header', () => {
- expect(String.prototype.split.mock.calls.filter(args => args[0] === ': ').length).toBe(3);
- });
-
- it('should return a normalized headers object', () => {
- expect(testContext.normalizeCRLFHeaders).toEqual({
- 'A-HEADER': 'a-value',
- 'ANOTHER-HEADER': 'ANOTHER-VALUE',
- 'LAST-HEADER': 'last-VALUE',
- });
- });
- });
-
describe('parseIntPagination', () => {
it('should parse to integers all string values and return pagination object', () => {
const pagination = {
@@ -510,27 +484,6 @@ describe('common_utils', () => {
});
});
- describe('setFavicon', () => {
- beforeEach(() => {
- const favicon = document.createElement('link');
- favicon.setAttribute('id', 'favicon');
- favicon.setAttribute('href', 'default/favicon');
- favicon.setAttribute('data-default-href', 'default/favicon');
- document.body.appendChild(favicon);
- });
-
- afterEach(() => {
- document.body.removeChild(document.getElementById('favicon'));
- });
-
- it('should set page favicon to provided favicon', () => {
- const faviconPath = '//custom_favicon';
- commonUtils.setFavicon(faviconPath);
-
- expect(document.getElementById('favicon').getAttribute('href')).toEqual(faviconPath);
- });
- });
-
describe('resetFavicon', () => {
beforeEach(() => {
const favicon = document.createElement('link');
diff --git a/spec/frontend/lib/utils/datetime_utility_spec.js b/spec/frontend/lib/utils/datetime_utility_spec.js
index f6878c7c920..adf5c312149 100644
--- a/spec/frontend/lib/utils/datetime_utility_spec.js
+++ b/spec/frontend/lib/utils/datetime_utility_spec.js
@@ -1,5 +1,6 @@
import { __, s__ } from '~/locale';
import $ from 'jquery';
+import timezoneMock from 'timezone-mock';
import '~/commons/bootstrap';
import * as datetimeUtility from '~/lib/utils/datetime_utility';
@@ -86,6 +87,31 @@ describe('Date time utils', () => {
datetimeUtility.formatDate('2016-07-23 00:00:00 UTC');
}).toThrow(new Error('Invalid date'));
});
+
+ describe('convert local timezone to UTC with utc parameter', () => {
+ const midnightUTC = '2020-07-09';
+ const format = 'mmm d, yyyy';
+
+ beforeEach(() => {
+ timezoneMock.register('US/Pacific');
+ });
+
+ afterEach(() => {
+ timezoneMock.unregister();
+ });
+
+ it('defaults to false', () => {
+ const formattedDate = datetimeUtility.formatDate(midnightUTC, format);
+
+ expect(formattedDate).toBe('Jul 8, 2020');
+ });
+
+ it('converts local time to UTC if utc flag is true', () => {
+ const formattedDate = datetimeUtility.formatDate(midnightUTC, format, true);
+
+ expect(formattedDate).toBe('Jul 9, 2020');
+ });
+ });
});
describe('get day difference', () => {
diff --git a/spec/frontend/lib/utils/dom_utils_spec.js b/spec/frontend/lib/utils/dom_utils_spec.js
index 10b4a10a8ff..d918016a5f4 100644
--- a/spec/frontend/lib/utils/dom_utils_spec.js
+++ b/spec/frontend/lib/utils/dom_utils_spec.js
@@ -1,4 +1,9 @@
-import { addClassIfElementExists, canScrollUp, canScrollDown } from '~/lib/utils/dom_utils';
+import {
+ addClassIfElementExists,
+ canScrollUp,
+ canScrollDown,
+ parseBooleanDataAttributes,
+} from '~/lib/utils/dom_utils';
const TEST_MARGIN = 5;
@@ -112,4 +117,47 @@ describe('DOM Utils', () => {
expect(canScrollDown(element, TEST_MARGIN)).toBe(false);
});
});
+
+ describe('parseBooleanDataAttributes', () => {
+ let element;
+
+ beforeEach(() => {
+ setFixtures('<div data-foo-bar data-baz data-qux="">');
+ element = document.querySelector('[data-foo-bar]');
+ });
+
+ it('throws if not given an element', () => {
+ expect(() => parseBooleanDataAttributes(null, ['baz'])).toThrow();
+ });
+
+ it('throws if not given an array of dataset names', () => {
+ expect(() => parseBooleanDataAttributes(element)).toThrow();
+ });
+
+ it('returns an empty object if given an empty array of names', () => {
+ expect(parseBooleanDataAttributes(element, [])).toEqual({});
+ });
+
+ it('correctly parses boolean-like data attributes', () => {
+ expect(
+ parseBooleanDataAttributes(element, [
+ 'fooBar',
+ 'foobar',
+ 'baz',
+ 'qux',
+ 'doesNotExist',
+ 'toString',
+ ]),
+ ).toEqual({
+ fooBar: true,
+ foobar: false,
+ baz: true,
+ qux: true,
+ doesNotExist: false,
+
+ // Ensure prototype properties aren't false positives
+ toString: false,
+ });
+ });
+ });
});
diff --git a/spec/frontend/lib/utils/grammar_spec.js b/spec/frontend/lib/utils/grammar_spec.js
index 377b2ffb48c..7f2431af7ed 100644
--- a/spec/frontend/lib/utils/grammar_spec.js
+++ b/spec/frontend/lib/utils/grammar_spec.js
@@ -7,27 +7,27 @@ describe('utils/grammar', () => {
});
it('with single item returns item', () => {
- const items = ['Lorem Ipsum'];
+ const items = ['Lorem & Ipsum'];
expect(grammar.toNounSeriesText(items)).toBe(items[0]);
});
it('with 2 items returns item1 and item2', () => {
- const items = ['Dolar', 'Sit Amit'];
+ const items = ['Dolar', 'Sit & Amit'];
expect(grammar.toNounSeriesText(items)).toBe(`${items[0]} and ${items[1]}`);
});
it('with 3 items returns comma separated series', () => {
- const items = ['Lorem', 'Ipsum', 'dolar'];
- const expected = 'Lorem, Ipsum, and dolar';
+ const items = ['Lorem', 'Ipsum', 'Sit & Amit'];
+ const expected = 'Lorem, Ipsum, and Sit & Amit';
expect(grammar.toNounSeriesText(items)).toBe(expected);
});
it('with 6 items returns comma separated series', () => {
- const items = ['Lorem', 'ipsum', 'dolar', 'sit', 'amit', 'consectetur'];
- const expected = 'Lorem, ipsum, dolar, sit, amit, and consectetur';
+ const items = ['Lorem', 'ipsum', 'dolar', 'sit', 'amit', 'consectetur & adipiscing'];
+ const expected = 'Lorem, ipsum, dolar, sit, amit, and consectetur & adipiscing';
expect(grammar.toNounSeriesText(items)).toBe(expected);
});
diff --git a/spec/frontend/lib/utils/text_markdown_spec.js b/spec/frontend/lib/utils/text_markdown_spec.js
index aca299aea0f..2e52958a828 100644
--- a/spec/frontend/lib/utils/text_markdown_spec.js
+++ b/spec/frontend/lib/utils/text_markdown_spec.js
@@ -232,19 +232,17 @@ describe('init markdown', () => {
beforeEach(() => {
editor = {
- getSelectionRange: () => ({
+ getSelectionRange: jest.fn().mockReturnValue({
start: 0,
end: 0,
}),
- getValue: () => 'this is text \n in two lines',
- insert: () => {},
- navigateLeft: () => {},
+ getValue: jest.fn().mockReturnValue('this is text \n in two lines'),
+ insert: jest.fn(),
+ navigateLeft: jest.fn(),
};
});
it('uses ace editor insert text when editor is passed in', () => {
- jest.spyOn(editor, 'insert').mockReturnValue();
-
insertMarkdownText({
text: editor.getValue,
tag: '*',
@@ -258,8 +256,6 @@ describe('init markdown', () => {
});
it('adds block tags on line above and below selection', () => {
- jest.spyOn(editor, 'insert').mockReturnValue();
-
const selected = 'this text \n is multiple \n lines';
const text = `before \n ${selected} \n after`;
@@ -276,8 +272,6 @@ describe('init markdown', () => {
});
it('uses ace editor to navigate back tag length when nothing is selected', () => {
- jest.spyOn(editor, 'navigateLeft').mockReturnValue();
-
insertMarkdownText({
text: editor.getValue,
tag: '*',
@@ -291,8 +285,6 @@ describe('init markdown', () => {
});
it('ace editor does not navigate back when there is selected text', () => {
- jest.spyOn(editor, 'navigateLeft').mockReturnValue();
-
insertMarkdownText({
text: editor.getValue,
tag: '*',
@@ -305,4 +297,96 @@ describe('init markdown', () => {
expect(editor.navigateLeft).not.toHaveBeenCalled();
});
});
+
+ describe('Editor Lite', () => {
+ let editor;
+ let origGon;
+
+ beforeEach(() => {
+ origGon = window.gon;
+ window.gon = {
+ features: {
+ monacoBlobs: true,
+ },
+ };
+ editor = {
+ getSelection: jest.fn().mockReturnValue({
+ startLineNumber: 1,
+ startColumn: 1,
+ endLineNumber: 2,
+ endColumn: 2,
+ }),
+ getValue: jest.fn().mockReturnValue('this is text \n in two lines'),
+ selectWithinSelection: jest.fn(),
+ replaceSelectedText: jest.fn(),
+ moveCursor: jest.fn(),
+ };
+ });
+
+ afterEach(() => {
+ window.gon = origGon;
+ });
+
+ it('replaces selected text', () => {
+ insertMarkdownText({
+ text: editor.getValue,
+ tag: '*',
+ blockTag: null,
+ selected: '',
+ wrap: false,
+ editor,
+ });
+
+ expect(editor.replaceSelectedText).toHaveBeenCalled();
+ });
+
+ it('adds block tags on line above and below selection', () => {
+ const selected = 'this text \n is multiple \n lines';
+ const text = `before \n ${selected} \n after`;
+
+ insertMarkdownText({
+ text,
+ tag: '',
+ blockTag: '***',
+ selected,
+ wrap: true,
+ editor,
+ });
+
+ expect(editor.replaceSelectedText).toHaveBeenCalledWith(`***\n${selected}\n***\n`, undefined);
+ });
+
+ it('uses ace editor to navigate back tag length when nothing is selected', () => {
+ editor.getSelection = jest.fn().mockReturnValue({
+ startLineNumber: 1,
+ startColumn: 1,
+ endLineNumber: 1,
+ endColumn: 1,
+ });
+
+ insertMarkdownText({
+ text: editor.getValue,
+ tag: '*',
+ blockTag: null,
+ selected: '',
+ wrap: true,
+ editor,
+ });
+
+ expect(editor.moveCursor).toHaveBeenCalledWith(-1);
+ });
+
+ it('ace editor does not navigate back when there is selected text', () => {
+ insertMarkdownText({
+ text: editor.getValue,
+ tag: '*',
+ blockTag: null,
+ selected: 'foobar',
+ wrap: true,
+ editor,
+ });
+
+ expect(editor.selectWithinSelection).not.toHaveBeenCalled();
+ });
+ });
});
diff --git a/spec/frontend/lib/utils/text_utility_spec.js b/spec/frontend/lib/utils/text_utility_spec.js
index 76e0e435860..285f7d04c3b 100644
--- a/spec/frontend/lib/utils/text_utility_spec.js
+++ b/spec/frontend/lib/utils/text_utility_spec.js
@@ -145,6 +145,56 @@ describe('text_utility', () => {
});
});
+ describe('truncate', () => {
+ it('returns the original string when str length is less than maxLength', () => {
+ const str = 'less than 20 chars';
+ expect(textUtils.truncate(str, 20)).toEqual(str);
+ });
+
+ it('returns truncated string when str length is more than maxLength', () => {
+ const str = 'more than 10 chars';
+ expect(textUtils.truncate(str, 10)).toEqual(`${str.substring(0, 10 - 1)}…`);
+ });
+
+ it('returns the original string when rendered width is exactly equal to maxWidth', () => {
+ const str = 'Exactly 16 chars';
+ expect(textUtils.truncate(str, 16)).toEqual(str);
+ });
+ });
+
+ describe('truncateWidth', () => {
+ const clientWidthDescriptor = Object.getOwnPropertyDescriptor(Element.prototype, 'clientWidth');
+
+ beforeAll(() => {
+ // Mock measured width of ' ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
+ Object.defineProperty(Element.prototype, 'clientWidth', {
+ value: 431,
+ writable: false,
+ });
+ });
+
+ afterAll(() => {
+ Object.defineProperty(Element.prototype, 'clientWidth', clientWidthDescriptor);
+ });
+
+ it('returns the original string when rendered width is less than maxWidth', () => {
+ const str = '< 80px';
+ expect(textUtils.truncateWidth(str)).toEqual(str);
+ });
+
+ it('returns truncated string when rendered width is more than maxWidth', () => {
+ const str = 'This is wider than 80px';
+ expect(textUtils.truncateWidth(str)).toEqual(`${str.substring(0, 10)}…`);
+ });
+
+ it('returns the original string when rendered width is exactly equal to maxWidth', () => {
+ const str = 'Exactly 159.62962962962965px';
+ expect(textUtils.truncateWidth(str, { maxWidth: 159.62962962962965, fontSize: 10 })).toEqual(
+ str,
+ );
+ });
+ });
+
describe('truncateSha', () => {
it('shortens SHAs to 8 characters', () => {
expect(textUtils.truncateSha('verylongsha')).toBe('verylong');
diff --git a/spec/frontend/lib/utils/url_utility_spec.js b/spec/frontend/lib/utils/url_utility_spec.js
index 85e680fe216..e769580b587 100644
--- a/spec/frontend/lib/utils/url_utility_spec.js
+++ b/spec/frontend/lib/utils/url_utility_spec.js
@@ -595,6 +595,14 @@ describe('URL utility', () => {
);
});
+ it('handles arrays properly when railsArraySyntax=true', () => {
+ const url = 'https://gitlab.com/test';
+
+ expect(urlUtils.setUrlParams({ labels: ['foo', 'bar'] }, url, false, true)).toEqual(
+ 'https://gitlab.com/test?labels%5B%5D=foo&labels%5B%5D=bar',
+ );
+ });
+
it('removes all existing URL params and sets a new param when cleanParams=true', () => {
const url = 'https://gitlab.com/test?group_id=gitlab-org&project_id=my-project';
diff --git a/spec/frontend/logs/components/environment_logs_spec.js b/spec/frontend/logs/components/environment_logs_spec.js
index 62f3e8a755d..dee62709d81 100644
--- a/spec/frontend/logs/components/environment_logs_spec.js
+++ b/spec/frontend/logs/components/environment_logs_spec.js
@@ -12,6 +12,7 @@ import {
mockTrace,
mockEnvironmentsEndpoint,
mockDocumentationPath,
+ mockManagedAppsEndpoint,
} from '../mock_data';
jest.mock('~/lib/utils/scroll_utils');
@@ -34,6 +35,7 @@ describe('EnvironmentLogs', () => {
environmentName: mockEnvName,
environmentsPath: mockEnvironmentsEndpoint,
clusterApplicationsDocumentationPath: mockDocumentationPath,
+ clustersPath: mockManagedAppsEndpoint,
};
const updateControlBtnsMock = jest.fn();
diff --git a/spec/frontend/logs/components/log_control_buttons_spec.js b/spec/frontend/logs/components/log_control_buttons_spec.js
index 85fc5a040d6..38e568f569f 100644
--- a/spec/frontend/logs/components/log_control_buttons_spec.js
+++ b/spec/frontend/logs/components/log_control_buttons_spec.js
@@ -1,5 +1,5 @@
import { shallowMount } from '@vue/test-utils';
-import { GlDeprecatedButton } from '@gitlab/ui';
+import { GlButton } from '@gitlab/ui';
import LogControlButtons from '~/logs/components/log_control_buttons.vue';
describe('LogControlButtons', () => {
@@ -31,9 +31,9 @@ describe('LogControlButtons', () => {
expect(wrapper.isVueInstance()).toBe(true);
expect(wrapper.isEmpty()).toBe(false);
- expect(findScrollToTop().is(GlDeprecatedButton)).toBe(true);
- expect(findScrollToBottom().is(GlDeprecatedButton)).toBe(true);
- expect(findRefreshBtn().is(GlDeprecatedButton)).toBe(true);
+ expect(findScrollToTop().is(GlButton)).toBe(true);
+ expect(findScrollToBottom().is(GlButton)).toBe(true);
+ expect(findRefreshBtn().is(GlButton)).toBe(true);
});
it('emits a `refresh` event on click on `refresh` button', () => {
diff --git a/spec/frontend/logs/mock_data.js b/spec/frontend/logs/mock_data.js
index 14c8f7a2ba2..f9b3508e01c 100644
--- a/spec/frontend/logs/mock_data.js
+++ b/spec/frontend/logs/mock_data.js
@@ -7,6 +7,8 @@ export const mockDocumentationPath = '/documentation.md';
export const mockLogsEndpoint = '/dummy_logs_path.json';
export const mockCursor = 'MOCK_CURSOR';
export const mockNextCursor = 'MOCK_NEXT_CURSOR';
+export const mockManagedAppName = 'kubernetes-cluster-1';
+export const mockManagedAppsEndpoint = `${mockProjectPath}/clusters.json`;
const makeMockEnvironment = (id, name, advancedQuerying) => ({
id,
@@ -23,6 +25,19 @@ export const mockEnvironments = [
makeMockEnvironment(102, 'review/a-feature', false),
];
+export const mockManagedApps = [
+ {
+ cluster_type: 'project_type',
+ enabled: true,
+ environment_scope: '*',
+ name: 'kubernetes-cluster-1',
+ provider_type: 'user',
+ status: 'connected',
+ path: '/root/autodevops-deploy/-/clusters/15',
+ gitlab_managed_apps_logs_path: '/root/autodevops-deploy/-/logs?cluster_id=15',
+ },
+];
+
export const mockPodName = 'production-764c58d697-aaaaa';
export const mockPods = [
mockPodName,
diff --git a/spec/frontend/logs/stores/actions_spec.js b/spec/frontend/logs/stores/actions_spec.js
index e2e3c3d23c6..acd9536a682 100644
--- a/spec/frontend/logs/stores/actions_spec.js
+++ b/spec/frontend/logs/stores/actions_spec.js
@@ -11,6 +11,7 @@ import {
fetchEnvironments,
fetchLogs,
fetchMoreLogsPrepend,
+ fetchManagedApps,
} from '~/logs/stores/actions';
import { defaultTimeRange } from '~/vue_shared/constants';
@@ -30,6 +31,8 @@ import {
mockResponse,
mockCursor,
mockNextCursor,
+ mockManagedApps,
+ mockManagedAppsEndpoint,
} from '../mock_data';
import { TOKEN_TYPE_POD_NAME } from '~/logs/constants';
@@ -217,6 +220,30 @@ describe('Logs Store actions', () => {
});
});
+ describe('fetchManagedApps', () => {
+ beforeEach(() => {
+ mock = new MockAdapter(axios);
+ });
+
+ it('should commit RECEIVE_MANAGED_APPS_DATA_SUCCESS mutation on succesful fetch', () => {
+ mock.onGet(mockManagedAppsEndpoint).replyOnce(200, { clusters: mockManagedApps });
+ return testAction(fetchManagedApps, mockManagedAppsEndpoint, state, [
+ { type: types.RECEIVE_MANAGED_APPS_DATA_SUCCESS, payload: mockManagedApps },
+ ]);
+ });
+
+ it('should commit RECEIVE_MANAGED_APPS_DATA_ERROR on wrong data', () => {
+ mock.onGet(mockManagedAppsEndpoint).replyOnce(500);
+ return testAction(
+ fetchManagedApps,
+ mockManagedAppsEndpoint,
+ state,
+ [{ type: types.RECEIVE_MANAGED_APPS_DATA_ERROR }],
+ [],
+ );
+ });
+ });
+
describe('when the backend responds succesfully', () => {
let expectedMutations;
let expectedActions;
diff --git a/spec/frontend/logs/stores/mutations_spec.js b/spec/frontend/logs/stores/mutations_spec.js
index 46561055a4a..137533f02d7 100644
--- a/spec/frontend/logs/stores/mutations_spec.js
+++ b/spec/frontend/logs/stores/mutations_spec.js
@@ -11,6 +11,8 @@ import {
mockSearch,
mockCursor,
mockNextCursor,
+ mockManagedApps,
+ mockManagedAppName,
} from '../mock_data';
describe('Logs Store Mutations', () => {
@@ -30,6 +32,15 @@ describe('Logs Store Mutations', () => {
it('sets the environment', () => {
mutations[types.SET_PROJECT_ENVIRONMENT](state, mockEnvName);
expect(state.environments.current).toEqual(mockEnvName);
+ expect(state.managedApps.current).toBe(null);
+ });
+ });
+
+ describe('SET_MANAGED_APP', () => {
+ it('sets the managed app', () => {
+ mutations[types.SET_MANAGED_APP](state, mockManagedAppName);
+ expect(state.managedApps.current).toBe(mockManagedAppName);
+ expect(state.environments.current).toBe(null);
});
});
@@ -254,4 +265,28 @@ describe('Logs Store Mutations', () => {
);
});
});
+
+ describe('RECEIVE_MANAGED_APPS_DATA_SUCCESS', () => {
+ it('receives managed apps data success', () => {
+ expect(state.managedApps.options).toEqual([]);
+
+ mutations[types.RECEIVE_MANAGED_APPS_DATA_SUCCESS](state, mockManagedApps);
+
+ expect(state.managedApps.options).toEqual(mockManagedApps);
+ expect(state.managedApps.isLoading).toBe(false);
+ });
+ });
+
+ describe('RECEIVE_MANAGED_APPS_DATA_ERROR', () => {
+ it('received managed apps data error', () => {
+ mutations[types.RECEIVE_MANAGED_APPS_DATA_ERROR](state);
+
+ expect(state.managedApps).toEqual({
+ options: [],
+ isLoading: false,
+ current: null,
+ fetchError: true,
+ });
+ });
+ });
});
diff --git a/spec/frontend/merge_request_tabs_spec.js b/spec/frontend/merge_request_tabs_spec.js
index 3d3be647d12..ad373d04ec0 100644
--- a/spec/frontend/merge_request_tabs_spec.js
+++ b/spec/frontend/merge_request_tabs_spec.js
@@ -5,7 +5,7 @@ import MergeRequestTabs from '~/merge_request_tabs';
import '~/commit/pipelines/pipelines_bundle';
import '~/lib/utils/common_utils';
import 'vendor/jquery.scrollTo';
-import initMrPage from '../javascripts/helpers/init_vue_mr_page_helper';
+import initMrPage from 'helpers/init_vue_mr_page_helper';
jest.mock('~/lib/utils/webpack', () => ({
resetServiceWorkersPublicPath: jest.fn(),
diff --git a/spec/frontend/monitoring/components/__snapshots__/dashboard_template_spec.js.snap b/spec/frontend/monitoring/components/__snapshots__/dashboard_template_spec.js.snap
index 4b08163f30a..e7c51d82cd2 100644
--- a/spec/frontend/monitoring/components/__snapshots__/dashboard_template_spec.js.snap
+++ b/spec/frontend/monitoring/components/__snapshots__/dashboard_template_spec.js.snap
@@ -4,22 +4,32 @@ exports[`Dashboard template matches the default snapshot 1`] = `
<div
class="prometheus-graphs"
data-qa-selector="prometheus_graphs"
+ environmentstate="available"
+ metricsdashboardbasepath="/monitoring/monitor-project/-/environments/1/metrics"
+ metricsendpoint="/monitoring/monitor-project/-/environments/1/additional_metrics.json"
+ prometheusstatus=""
>
<div
class="prometheus-graphs-header d-sm-flex flex-sm-wrap pt-2 pr-1 pb-0 pl-2 border-bottom bg-gray-light"
>
<div
- class="mb-2 pr-2 d-flex d-sm-block"
+ class="mb-2 mr-2 d-flex d-sm-block"
>
<dashboards-dropdown-stub
class="flex-grow-1"
data-qa-selector="dashboards_filter_dropdown"
defaultbranch="master"
id="monitor-dashboards-dropdown"
+ modalid="duplicateDashboard"
toggle-class="dropdown-menu-toggle"
/>
</div>
+ <span
+ aria-hidden="true"
+ class="gl-pl-3 border-left gl-mb-3 d-none d-sm-block"
+ />
+
<div
class="mb-2 pr-2 d-flex d-sm-block"
>
@@ -80,17 +90,7 @@ exports[`Dashboard template matches the default snapshot 1`] = `
<div
class="mb-2 pr-2 d-flex d-sm-block"
>
- <gl-deprecated-button-stub
- class="flex-grow-1"
- size="md"
- title="Refresh dashboard"
- variant="default"
- >
- <icon-stub
- name="retry"
- size="16"
- />
- </gl-deprecated-button-stub>
+ <refresh-button-stub />
</div>
<div
@@ -127,23 +127,30 @@ exports[`Dashboard template matches the default snapshot 1`] = `
<!---->
<!---->
+
+ <!---->
+
+ <!---->
+
+ <!---->
</div>
+
+ <duplicate-dashboard-modal-stub
+ defaultbranch="master"
+ modalid="duplicateDashboard"
+ />
</div>
- <!---->
-
- <!---->
-
<empty-state-stub
- clusterspath="/path/to/clusters"
- documentationpath="/path/to/docs"
- emptygettingstartedsvgpath="/path/to/getting-started.svg"
- emptyloadingsvgpath="/path/to/loading.svg"
- emptynodatasmallsvgpath="/path/to/no-data-small.svg"
- emptynodatasvgpath="/path/to/no-data.svg"
- emptyunabletoconnectsvgpath="/path/to/unable-to-connect.svg"
+ clusterspath="/monitoring/monitor-project/-/clusters"
+ documentationpath="/help/administration/monitoring/prometheus/index.md"
+ emptygettingstartedsvgpath="/images/illustrations/monitoring/getting_started.svg"
+ emptyloadingsvgpath="/images/illustrations/monitoring/loading.svg"
+ emptynodatasmallsvgpath="/images/illustrations/chart-empty-state-small.svg"
+ emptynodatasvgpath="/images/illustrations/monitoring/no_data.svg"
+ emptyunabletoconnectsvgpath="/images/illustrations/monitoring/unable_to_connect.svg"
selectedstate="gettingStarted"
- settingspath="/path/to/settings"
+ settingspath="/monitoring/monitor-project/-/services/prometheus/edit"
/>
</div>
`;
diff --git a/spec/frontend/monitoring/components/__snapshots__/empty_state_spec.js.snap b/spec/frontend/monitoring/components/__snapshots__/empty_state_spec.js.snap
index 31b3ad1bd76..4f8a82692b8 100644
--- a/spec/frontend/monitoring/components/__snapshots__/empty_state_spec.js.snap
+++ b/spec/frontend/monitoring/components/__snapshots__/empty_state_spec.js.snap
@@ -1,37 +1,49 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`EmptyState shows gettingStarted state 1`] = `
-<gl-empty-state-stub
- description="Stay updated about the performance and health of your environment by configuring Prometheus to monitor your deployments."
- primarybuttonlink="/clustersPath"
- primarybuttontext="Install on clusters"
- secondarybuttonlink="/settingsPath"
- secondarybuttontext="Configure existing installation"
- svgpath="/path/to/getting-started.svg"
- title="Get started with performance monitoring"
-/>
+<div>
+ <!---->
+
+ <gl-empty-state-stub
+ description="Stay updated about the performance and health of your environment by configuring Prometheus to monitor your deployments."
+ primarybuttonlink="/clustersPath"
+ primarybuttontext="Install on clusters"
+ secondarybuttonlink="/settingsPath"
+ secondarybuttontext="Configure existing installation"
+ svgpath="/path/to/getting-started.svg"
+ title="Get started with performance monitoring"
+ />
+</div>
`;
-exports[`EmptyState shows loading state 1`] = `
-<gl-empty-state-stub
- description="Creating graphs uses the data from the Prometheus server. If this takes a long time, ensure that data is available."
- primarybuttonlink="/documentationPath"
- primarybuttontext="View documentation"
- secondarybuttonlink=""
- secondarybuttontext=""
- svgpath="/path/to/loading.svg"
- title="Waiting for performance data"
-/>
+exports[`EmptyState shows noData state 1`] = `
+<div>
+ <!---->
+
+ <gl-empty-state-stub
+ description="You are connected to the Prometheus server, but there is currently no data to display."
+ primarybuttonlink="/settingsPath"
+ primarybuttontext="Configure Prometheus"
+ secondarybuttonlink=""
+ secondarybuttontext=""
+ svgpath="/path/to/no-data.svg"
+ title="No data found"
+ />
+</div>
`;
exports[`EmptyState shows unableToConnect state 1`] = `
-<gl-empty-state-stub
- description="Ensure connectivity is available from the GitLab server to the Prometheus server"
- primarybuttonlink="/documentationPath"
- primarybuttontext="View documentation"
- secondarybuttonlink="/settingsPath"
- secondarybuttontext="Configure Prometheus"
- svgpath="/path/to/unable-to-connect.svg"
- title="Unable to connect to Prometheus server"
-/>
+<div>
+ <!---->
+
+ <gl-empty-state-stub
+ description="Ensure connectivity is available from the GitLab server to the Prometheus server"
+ primarybuttonlink="/documentationPath"
+ primarybuttontext="View documentation"
+ secondarybuttonlink="/settingsPath"
+ secondarybuttontext="Configure Prometheus"
+ svgpath="/path/to/unable-to-connect.svg"
+ title="Unable to connect to Prometheus server"
+ />
+</div>
`;
diff --git a/spec/frontend/monitoring/components/charts/anomaly_spec.js b/spec/frontend/monitoring/components/charts/anomaly_spec.js
index 4178d3f0d2d..15a52d03bcd 100644
--- a/spec/frontend/monitoring/components/charts/anomaly_spec.js
+++ b/spec/frontend/monitoring/components/charts/anomaly_spec.js
@@ -3,28 +3,14 @@ import { TEST_HOST } from 'helpers/test_constants';
import Anomaly from '~/monitoring/components/charts/anomaly.vue';
import { colorValues } from '~/monitoring/constants';
-import {
- anomalyDeploymentData,
- mockProjectDir,
- anomalyMockGraphData,
- anomalyMockResultValues,
-} from '../../mock_data';
+import { anomalyDeploymentData, mockProjectDir } from '../../mock_data';
+import { anomalyGraphData } from '../../graph_data';
import MonitorTimeSeriesChart from '~/monitoring/components/charts/time_series.vue';
const mockProjectPath = `${TEST_HOST}${mockProjectDir}`;
-const makeAnomalyGraphData = (datasetName, template = anomalyMockGraphData) => {
- const metrics = anomalyMockResultValues[datasetName].map((values, index) => ({
- ...template.metrics[index],
- result: [
- {
- metrics: {},
- values,
- },
- ],
- }));
- return { ...template, metrics };
-};
+const TEST_UPPER = 11;
+const TEST_LOWER = 9;
describe('Anomaly chart component', () => {
let wrapper;
@@ -38,13 +24,22 @@ describe('Anomaly chart component', () => {
const getTimeSeriesProps = () => findTimeSeries().props();
describe('wrapped monitor-time-series-chart component', () => {
- const dataSetName = 'noAnomaly';
- const dataSet = anomalyMockResultValues[dataSetName];
+ const mockValues = ['10', '10', '10'];
+
+ const mockGraphData = anomalyGraphData(
+ {},
+ {
+ upper: mockValues.map(() => String(TEST_UPPER)),
+ values: mockValues,
+ lower: mockValues.map(() => String(TEST_LOWER)),
+ },
+ );
+
const inputThresholds = ['some threshold'];
beforeEach(() => {
setupAnomalyChart({
- graphData: makeAnomalyGraphData(dataSetName),
+ graphData: mockGraphData,
deploymentData: anomalyDeploymentData,
thresholds: inputThresholds,
projectPath: mockProjectPath,
@@ -65,21 +60,21 @@ describe('Anomaly chart component', () => {
it('receives "metric" with all data', () => {
const { graphData } = getTimeSeriesProps();
- const query = graphData.metrics[0];
- const expectedQuery = makeAnomalyGraphData(dataSetName).metrics[0];
- expect(query).toEqual(expectedQuery);
+ const metric = graphData.metrics[0];
+ const expectedMetric = mockGraphData.metrics[0];
+ expect(metric).toEqual(expectedMetric);
});
it('receives the "metric" results', () => {
const { graphData } = getTimeSeriesProps();
const { result } = graphData.metrics[0];
const { values } = result[0];
- const [metricDataset] = dataSet;
- expect(values).toEqual(expect.any(Array));
- values.forEach(([, y], index) => {
- expect(y).toBeCloseTo(metricDataset[index][1]);
- });
+ expect(values).toEqual([
+ [expect.any(String), 10],
+ [expect.any(String), 10],
+ [expect.any(String), 10],
+ ]);
});
});
@@ -108,14 +103,13 @@ describe('Anomaly chart component', () => {
it('upper boundary values are stacked on top of lower boundary', () => {
const [lowerSeries, upperSeries] = series;
- const [, upperDataset, lowerDataset] = dataSet;
- lowerSeries.data.forEach(([, y], i) => {
- expect(y).toBeCloseTo(lowerDataset[i][1]);
+ lowerSeries.data.forEach(([, y]) => {
+ expect(y).toBeCloseTo(TEST_LOWER);
});
- upperSeries.data.forEach(([, y], i) => {
- expect(y).toBeCloseTo(upperDataset[i][1] - lowerDataset[i][1]);
+ upperSeries.data.forEach(([, y]) => {
+ expect(y).toBeCloseTo(TEST_UPPER - TEST_LOWER);
});
});
});
@@ -140,11 +134,10 @@ describe('Anomaly chart component', () => {
}),
);
});
+
it('does not display anomalies', () => {
const { symbolSize, itemStyle } = seriesConfig;
- const [metricDataset] = dataSet;
-
- metricDataset.forEach((v, dataIndex) => {
+ mockValues.forEach((v, dataIndex) => {
const size = symbolSize(null, { dataIndex });
const color = itemStyle.color({ dataIndex });
@@ -155,9 +148,10 @@ describe('Anomaly chart component', () => {
});
it('can format y values (to use in tooltips)', () => {
- expect(parseFloat(wrapper.vm.yValueFormatted(0, 0))).toEqual(dataSet[0][0][1]);
- expect(parseFloat(wrapper.vm.yValueFormatted(1, 0))).toEqual(dataSet[1][0][1]);
- expect(parseFloat(wrapper.vm.yValueFormatted(2, 0))).toEqual(dataSet[2][0][1]);
+ mockValues.forEach((v, dataIndex) => {
+ const formatted = wrapper.vm.yValueFormatted(0, dataIndex);
+ expect(parseFloat(formatted)).toEqual(parseFloat(v));
+ });
});
});
@@ -179,12 +173,18 @@ describe('Anomaly chart component', () => {
});
describe('with no boundary data', () => {
- const dataSetName = 'noBoundary';
- const dataSet = anomalyMockResultValues[dataSetName];
+ const noBoundaryData = anomalyGraphData(
+ {},
+ {
+ upper: [],
+ values: ['10', '10', '10'],
+ lower: [],
+ },
+ );
beforeEach(() => {
setupAnomalyChart({
- graphData: makeAnomalyGraphData(dataSetName),
+ graphData: noBoundaryData,
deploymentData: anomalyDeploymentData,
});
});
@@ -204,7 +204,7 @@ describe('Anomaly chart component', () => {
});
it('can format y values (to use in tooltips)', () => {
- expect(parseFloat(wrapper.vm.yValueFormatted(0, 0))).toEqual(dataSet[0][0][1]);
+ expect(parseFloat(wrapper.vm.yValueFormatted(0, 0))).toEqual(10);
expect(wrapper.vm.yValueFormatted(1, 0)).toBe(''); // missing boundary
expect(wrapper.vm.yValueFormatted(2, 0)).toBe(''); // missing boundary
});
@@ -212,12 +212,20 @@ describe('Anomaly chart component', () => {
});
describe('with one anomaly', () => {
- const dataSetName = 'oneAnomaly';
- const dataSet = anomalyMockResultValues[dataSetName];
+ const mockValues = ['10', '20', '10'];
+
+ const oneAnomalyData = anomalyGraphData(
+ {},
+ {
+ upper: mockValues.map(() => TEST_UPPER),
+ values: mockValues,
+ lower: mockValues.map(() => TEST_LOWER),
+ },
+ );
beforeEach(() => {
setupAnomalyChart({
- graphData: makeAnomalyGraphData(dataSetName),
+ graphData: oneAnomalyData,
deploymentData: anomalyDeploymentData,
});
});
@@ -226,13 +234,12 @@ describe('Anomaly chart component', () => {
it('displays one anomaly', () => {
const { seriesConfig } = getTimeSeriesProps();
const { symbolSize, itemStyle } = seriesConfig;
- const [metricDataset] = dataSet;
- const bigDots = metricDataset.filter((v, dataIndex) => {
+ const bigDots = mockValues.filter((v, dataIndex) => {
const size = symbolSize(null, { dataIndex });
return size > 0.1;
});
- const redDots = metricDataset.filter((v, dataIndex) => {
+ const redDots = mockValues.filter((v, dataIndex) => {
const color = itemStyle.color({ dataIndex });
return color === colorValues.anomalySymbol;
});
@@ -244,13 +251,21 @@ describe('Anomaly chart component', () => {
});
describe('with offset', () => {
- const dataSetName = 'negativeBoundary';
- const dataSet = anomalyMockResultValues[dataSetName];
- const expectedOffset = 4; // Lowst point in mock data is -3.70, it gets rounded
+ const mockValues = ['10', '11', '12'];
+ const mockUpper = ['20', '20', '20'];
+ const mockLower = ['-1', '-2', '-3.70'];
+ const expectedOffset = 4; // Lowest point in mock data is -3.70, it gets rounded
beforeEach(() => {
setupAnomalyChart({
- graphData: makeAnomalyGraphData(dataSetName),
+ graphData: anomalyGraphData(
+ {},
+ {
+ upper: mockUpper,
+ values: mockValues,
+ lower: mockLower,
+ },
+ ),
deploymentData: anomalyDeploymentData,
});
});
@@ -266,11 +281,11 @@ describe('Anomaly chart component', () => {
const { graphData } = getTimeSeriesProps();
const { result } = graphData.metrics[0];
const { values } = result[0];
- const [metricDataset] = dataSet;
+
expect(values).toEqual(expect.any(Array));
values.forEach(([, y], index) => {
- expect(y).toBeCloseTo(metricDataset[index][1] + expectedOffset);
+ expect(y).toBeCloseTo(parseFloat(mockValues[index]) + expectedOffset);
});
});
});
@@ -281,14 +296,12 @@ describe('Anomaly chart component', () => {
const { option } = getTimeSeriesProps();
const { series } = option;
const [lowerSeries, upperSeries] = series;
- const [, upperDataset, lowerDataset] = dataSet;
-
lowerSeries.data.forEach(([, y], i) => {
- expect(y).toBeCloseTo(lowerDataset[i][1] + expectedOffset);
+ expect(y).toBeCloseTo(parseFloat(mockLower[i]) + expectedOffset);
});
upperSeries.data.forEach(([, y], i) => {
- expect(y).toBeCloseTo(upperDataset[i][1] - lowerDataset[i][1]);
+ expect(y).toBeCloseTo(parseFloat(mockUpper[i] - mockLower[i]));
});
});
});
diff --git a/spec/frontend/monitoring/components/charts/column_spec.js b/spec/frontend/monitoring/components/charts/column_spec.js
index 89739a7485d..a2056d96dcf 100644
--- a/spec/frontend/monitoring/components/charts/column_spec.js
+++ b/spec/frontend/monitoring/components/charts/column_spec.js
@@ -63,8 +63,8 @@ describe('Column component', () => {
return formatter(date);
};
- it('x-axis is formatted correctly in AM/PM format', () => {
- expect(useXAxisFormatter(mockDate)).toEqual('8:00 PM');
+ it('x-axis is formatted correctly in m/d h:MM TT format', () => {
+ expect(useXAxisFormatter(mockDate)).toEqual('5/26 8:00 PM');
});
describe('when in PT timezone', () => {
@@ -78,17 +78,17 @@ describe('Column component', () => {
it('by default, values are formatted in PT', () => {
createWrapper();
- expect(useXAxisFormatter(mockDate)).toEqual('1:00 PM');
+ expect(useXAxisFormatter(mockDate)).toEqual('5/26 1:00 PM');
});
it('when the chart uses local timezone, y-axis is formatted in PT', () => {
createWrapper({ timezone: 'LOCAL' });
- expect(useXAxisFormatter(mockDate)).toEqual('1:00 PM');
+ expect(useXAxisFormatter(mockDate)).toEqual('5/26 1:00 PM');
});
it('when the chart uses UTC, y-axis is formatted in UTC', () => {
createWrapper({ timezone: 'UTC' });
- expect(useXAxisFormatter(mockDate)).toEqual('8:00 PM');
+ expect(useXAxisFormatter(mockDate)).toEqual('5/26 8:00 PM');
});
});
});
diff --git a/spec/frontend/monitoring/components/charts/single_stat_spec.js b/spec/frontend/monitoring/components/charts/single_stat_spec.js
index 9cc5970da82..3783b1eebd2 100644
--- a/spec/frontend/monitoring/components/charts/single_stat_spec.js
+++ b/spec/frontend/monitoring/components/charts/single_stat_spec.js
@@ -1,6 +1,6 @@
import { shallowMount } from '@vue/test-utils';
import SingleStatChart from '~/monitoring/components/charts/single_stat.vue';
-import { singleStatMetricsResult } from '../../mock_data';
+import { singleStatGraphData } from '../../graph_data';
describe('Single Stat Chart component', () => {
let singleStatChart;
@@ -8,7 +8,7 @@ describe('Single Stat Chart component', () => {
beforeEach(() => {
singleStatChart = shallowMount(SingleStatChart, {
propsData: {
- graphData: singleStatMetricsResult,
+ graphData: singleStatGraphData({}, { unit: 'MB' }),
},
});
});
@@ -20,15 +20,12 @@ describe('Single Stat Chart component', () => {
describe('computed', () => {
describe('statValue', () => {
it('should interpolate the value and unit props', () => {
- expect(singleStatChart.vm.statValue).toBe('91.00MB');
+ expect(singleStatChart.vm.statValue).toBe('1.00MB');
});
it('should change the value representation to a percentile one', () => {
singleStatChart.setProps({
- graphData: {
- ...singleStatMetricsResult,
- maxValue: 120,
- },
+ graphData: singleStatGraphData({ max_value: 120 }, { value: 91 }),
});
expect(singleStatChart.vm.statValue).toContain('75.83%');
@@ -36,10 +33,7 @@ describe('Single Stat Chart component', () => {
it('should display NaN for non numeric maxValue values', () => {
singleStatChart.setProps({
- graphData: {
- ...singleStatMetricsResult,
- maxValue: 'not a number',
- },
+ graphData: singleStatGraphData({ max_value: 'not a number' }),
});
expect(singleStatChart.vm.statValue).toContain('NaN');
@@ -47,25 +41,33 @@ describe('Single Stat Chart component', () => {
it('should display NaN for missing query values', () => {
singleStatChart.setProps({
- graphData: {
- ...singleStatMetricsResult,
- metrics: [
- {
- ...singleStatMetricsResult.metrics[0],
- result: [
- {
- ...singleStatMetricsResult.metrics[0].result[0],
- value: [''],
- },
- ],
- },
- ],
- maxValue: 120,
- },
+ graphData: singleStatGraphData({ max_value: 120 }, { value: 'NaN' }),
});
expect(singleStatChart.vm.statValue).toContain('NaN');
});
+
+ describe('field attribute', () => {
+ it('displays a label value instead of metric value when field attribute is used', () => {
+ singleStatChart.setProps({
+ graphData: singleStatGraphData({ field: 'job' }, { isVector: true }),
+ });
+
+ return singleStatChart.vm.$nextTick(() => {
+ expect(singleStatChart.vm.statValue).toContain('prometheus');
+ });
+ });
+
+ it('displays No data to display if field attribute is not present', () => {
+ singleStatChart.setProps({
+ graphData: singleStatGraphData({ field: 'this-does-not-exist' }),
+ });
+
+ return singleStatChart.vm.$nextTick(() => {
+ expect(singleStatChart.vm.statValue).toContain('No data to display');
+ });
+ });
+ });
});
});
});
diff --git a/spec/frontend/monitoring/components/charts/time_series_spec.js b/spec/frontend/monitoring/components/charts/time_series_spec.js
index 50d2c9c80b2..97386be9e32 100644
--- a/spec/frontend/monitoring/components/charts/time_series_spec.js
+++ b/spec/frontend/monitoring/components/charts/time_series_spec.js
@@ -9,18 +9,12 @@ import {
GlChartSeriesLabel,
GlChartLegend,
} from '@gitlab/ui/dist/charts';
-import { cloneDeep } from 'lodash';
import { shallowWrapperContainsSlotText } from 'helpers/vue_test_utils_helper';
-import { createStore } from '~/monitoring/stores';
import { panelTypes, chartHeight } from '~/monitoring/constants';
import TimeSeries from '~/monitoring/components/charts/time_series.vue';
-import * as types from '~/monitoring/stores/mutation_types';
import { deploymentData, mockProjectDir, annotationsData } from '../../mock_data';
-import {
- metricsDashboardPayload,
- metricsDashboardViewModel,
- metricResultStatus,
-} from '../../fixture_data';
+
+import { timeSeriesGraphData } from '../../graph_data';
jest.mock('lodash/throttle', () =>
// this throttle mock executes immediately
@@ -35,23 +29,21 @@ jest.mock('~/lib/utils/icon_utils', () => ({
}));
describe('Time series component', () => {
- let mockGraphData;
- let store;
+ const defaultGraphData = timeSeriesGraphData();
let wrapper;
const createWrapper = (
- { graphData = mockGraphData, ...props } = {},
+ { graphData = defaultGraphData, ...props } = {},
mountingMethod = shallowMount,
) => {
wrapper = mountingMethod(TimeSeries, {
propsData: {
graphData,
- deploymentData: store.state.monitoringDashboard.deploymentData,
- annotations: store.state.monitoringDashboard.annotations,
+ deploymentData,
+ annotations: annotationsData,
projectPath: `${TEST_HOST}${mockProjectDir}`,
...props,
},
- store,
stubs: {
GlPopover: true,
},
@@ -59,27 +51,15 @@ describe('Time series component', () => {
});
};
- describe('With a single time series', () => {
- beforeEach(() => {
- setTestTimeout(1000);
-
- store = createStore();
-
- store.commit(
- `monitoringDashboard/${types.RECEIVE_METRICS_DASHBOARD_SUCCESS}`,
- metricsDashboardPayload,
- );
-
- store.commit(`monitoringDashboard/${types.RECEIVE_DEPLOYMENTS_DATA_SUCCESS}`, deploymentData);
+ beforeEach(() => {
+ setTestTimeout(1000);
+ });
- store.commit(
- `monitoringDashboard/${types.RECEIVE_METRIC_RESULT_SUCCESS}`,
- metricResultStatus,
- );
- // dashboard is a dynamically generated fixture and stored at environment_metrics_dashboard.json
- [mockGraphData] = store.state.monitoringDashboard.dashboard.panelGroups[1].panels;
- });
+ afterEach(() => {
+ wrapper.destroy();
+ });
+ describe('With a single time series', () => {
describe('general functions', () => {
const findChart = () => wrapper.find({ ref: 'chart' });
@@ -88,10 +68,6 @@ describe('Time series component', () => {
return wrapper.vm.$nextTick();
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('allows user to override legend label texts using props', () => {
const legendRelatedProps = {
legendMinText: 'legendMinText',
@@ -231,19 +207,20 @@ describe('Time series component', () => {
});
it('formats tooltip content', () => {
- const name = 'Status Code';
+ const name = 'Metric 1';
const value = '5.556';
const dataIndex = 0;
const seriesLabel = wrapper.find(GlChartSeriesLabel);
expect(seriesLabel.vm.color).toBe('');
+
expect(shallowWrapperContainsSlotText(seriesLabel, 'default', name)).toBe(true);
expect(wrapper.vm.tooltip.content).toEqual([
{ name, value, dataIndex, color: undefined },
]);
expect(
- shallowWrapperContainsSlotText(wrapper.find(GlAreaChart), 'tooltipContent', value),
+ shallowWrapperContainsSlotText(wrapper.find(GlLineChart), 'tooltipContent', value),
).toBe(true);
});
@@ -385,10 +362,8 @@ describe('Time series component', () => {
});
it('utilizes all data points', () => {
- const { values } = mockGraphData.metrics[0].result[0];
-
expect(chartData.length).toBe(1);
- expect(seriesData().data.length).toBe(values.length);
+ expect(seriesData().data.length).toBe(3);
});
it('creates valid data', () => {
@@ -552,8 +527,8 @@ describe('Time series component', () => {
return formatter(date);
};
- it('x-axis is formatted correctly in AM/PM format', () => {
- expect(useXAxisFormatter(mockDate)).toEqual('8:00 PM');
+ it('x-axis is formatted correctly in m/d h:MM TT format', () => {
+ expect(useXAxisFormatter(mockDate)).toEqual('5/26 8:00 PM');
});
describe('when in PT timezone', () => {
@@ -567,17 +542,17 @@ describe('Time series component', () => {
it('by default, values are formatted in PT', () => {
createWrapper();
- expect(useXAxisFormatter(mockDate)).toEqual('1:00 PM');
+ expect(useXAxisFormatter(mockDate)).toEqual('5/26 1:00 PM');
});
it('when the chart uses local timezone, y-axis is formatted in PT', () => {
createWrapper({ timezone: 'LOCAL' });
- expect(useXAxisFormatter(mockDate)).toEqual('1:00 PM');
+ expect(useXAxisFormatter(mockDate)).toEqual('5/26 1:00 PM');
});
it('when the chart uses UTC, y-axis is formatted in UTC', () => {
createWrapper({ timezone: 'UTC' });
- expect(useXAxisFormatter(mockDate)).toEqual('8:00 PM');
+ expect(useXAxisFormatter(mockDate)).toEqual('5/26 8:00 PM');
});
});
});
@@ -602,14 +577,10 @@ describe('Time series component', () => {
it('constructs a label for the chart y-axis', () => {
const { yAxis } = getChartOptions();
- expect(yAxis[0].name).toBe('Requests / Sec');
+ expect(yAxis[0].name).toBe('Y Axis');
});
});
});
-
- afterEach(() => {
- wrapper.destroy();
- });
});
describe('wrapped components', () => {
@@ -630,7 +601,7 @@ describe('Time series component', () => {
beforeEach(() => {
createWrapper(
- { graphData: { ...mockGraphData, type: dynamicComponent.chartType } },
+ { graphData: timeSeriesGraphData({ type: dynamicComponent.chartType }) },
mount,
);
return wrapper.vm.$nextTick();
@@ -700,20 +671,12 @@ describe('Time series component', () => {
describe('with multiple time series', () => {
describe('General functions', () => {
beforeEach(() => {
- store = createStore();
- const graphData = cloneDeep(metricsDashboardViewModel.panelGroups[0].panels[3]);
- graphData.metrics.forEach(metric =>
- Object.assign(metric, { result: metricResultStatus.result }),
- );
+ const graphData = timeSeriesGraphData({ type: panelTypes.AREA_CHART, multiMetric: true });
- createWrapper({ graphData: { ...graphData, type: 'area-chart' } }, mount);
+ createWrapper({ graphData }, mount);
return wrapper.vm.$nextTick();
});
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('Color match', () => {
let lineColors;
@@ -754,14 +717,10 @@ describe('Time series component', () => {
const findLegend = () => wrapper.find(GlChartLegend);
beforeEach(() => {
- createWrapper(mockGraphData, mount);
+ createWrapper({}, mount);
return wrapper.vm.$nextTick();
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('should render a tabular legend layout by default', () => {
expect(findLegend().props('layout')).toBe('table');
});
diff --git a/spec/frontend/monitoring/components/create_dashboard_modal_spec.js b/spec/frontend/monitoring/components/create_dashboard_modal_spec.js
new file mode 100644
index 00000000000..d1028445638
--- /dev/null
+++ b/spec/frontend/monitoring/components/create_dashboard_modal_spec.js
@@ -0,0 +1,48 @@
+import { shallowMount } from '@vue/test-utils';
+import { GlModal } from '@gitlab/ui';
+import CreateDashboardModal from '~/monitoring/components/create_dashboard_modal.vue';
+
+describe('Create dashboard modal', () => {
+ let wrapper;
+
+ const defaultProps = {
+ modalId: 'id',
+ projectPath: 'https://localhost/',
+ addDashboardDocumentationPath: 'https://link/to/docs',
+ };
+
+ const findDocsButton = () => wrapper.find('[data-testid="create-dashboard-modal-docs-button"]');
+ const findRepoButton = () => wrapper.find('[data-testid="create-dashboard-modal-repo-button"]');
+
+ const createWrapper = (props = {}, options = {}) => {
+ wrapper = shallowMount(CreateDashboardModal, {
+ propsData: { ...defaultProps, ...props },
+ stubs: {
+ GlModal,
+ },
+ ...options,
+ });
+ };
+
+ beforeEach(() => {
+ createWrapper();
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ it('has button that links to the project url', () => {
+ findRepoButton().trigger('click');
+
+ return wrapper.vm.$nextTick().then(() => {
+ expect(findRepoButton().exists()).toBe(true);
+ expect(findRepoButton().attributes('href')).toBe(defaultProps.projectPath);
+ });
+ });
+
+ it('has button that links to the docs', () => {
+ expect(findDocsButton().exists()).toBe(true);
+ expect(findDocsButton().attributes('href')).toBe(defaultProps.addDashboardDocumentationPath);
+ });
+});
diff --git a/spec/frontend/monitoring/components/dashboard_header_spec.js b/spec/frontend/monitoring/components/dashboard_header_spec.js
new file mode 100644
index 00000000000..5a1a615c703
--- /dev/null
+++ b/spec/frontend/monitoring/components/dashboard_header_spec.js
@@ -0,0 +1,232 @@
+import { shallowMount } from '@vue/test-utils';
+import { createStore } from '~/monitoring/stores';
+import DashboardHeader from '~/monitoring/components/dashboard_header.vue';
+import DuplicateDashboardModal from '~/monitoring/components/duplicate_dashboard_modal.vue';
+import CreateDashboardModal from '~/monitoring/components/create_dashboard_modal.vue';
+import { setupAllDashboards } from '../store_utils';
+import {
+ dashboardGitResponse,
+ selfMonitoringDashboardGitResponse,
+ dashboardHeaderProps,
+} from '../mock_data';
+import { redirectTo } from '~/lib/utils/url_utility';
+
+jest.mock('~/lib/utils/url_utility', () => ({
+ redirectTo: jest.fn(),
+ queryToObject: jest.fn(),
+ mergeUrlParams: jest.requireActual('~/lib/utils/url_utility').mergeUrlParams,
+}));
+
+describe('Dashboard header', () => {
+ let store;
+ let wrapper;
+
+ const findActionsMenu = () => wrapper.find('[data-testid="actions-menu"]');
+ const findCreateDashboardMenuItem = () =>
+ findActionsMenu().find('[data-testid="action-create-dashboard"]');
+ const findCreateDashboardDuplicateItem = () =>
+ findActionsMenu().find('[data-testid="action-duplicate-dashboard"]');
+ const findDuplicateDashboardModal = () => wrapper.find(DuplicateDashboardModal);
+ const findCreateDashboardModal = () => wrapper.find('[data-testid="create-dashboard-modal"]');
+
+ const createShallowWrapper = (props = {}, options = {}) => {
+ wrapper = shallowMount(DashboardHeader, {
+ propsData: { ...dashboardHeaderProps, ...props },
+ store,
+ ...options,
+ });
+ };
+
+ beforeEach(() => {
+ store = createStore();
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ describe('when a dashboard has been duplicated in the duplicate dashboard modal', () => {
+ beforeEach(() => {
+ store.state.monitoringDashboard.projectPath = 'root/sandbox';
+ });
+ /**
+ * The duplicate dashboard modal gets called both by a menu item from the
+ * dashboards dropdown and by an item from the actions menu.
+ *
+ * This spec is context agnostic, so it addresses all cases where the
+ * duplicate dashboard modal gets called.
+ */
+ it('redirects to the newly created dashboard', () => {
+ delete window.location;
+ window.location = new URL('https://localhost');
+
+ const newDashboard = dashboardGitResponse[1];
+
+ createShallowWrapper();
+
+ const newDashboardUrl = 'root/sandbox/-/metrics/dashboard.yml';
+ findDuplicateDashboardModal().vm.$emit('dashboardDuplicated', newDashboard);
+
+ return wrapper.vm.$nextTick().then(() => {
+ expect(redirectTo).toHaveBeenCalled();
+ expect(redirectTo).toHaveBeenCalledWith(newDashboardUrl);
+ });
+ });
+ });
+
+ describe('actions menu', () => {
+ beforeEach(() => {
+ store.state.monitoringDashboard.projectPath = '';
+ createShallowWrapper();
+ });
+
+ it('is rendered if projectPath is set in store', () => {
+ store.state.monitoringDashboard.projectPath = 'https://path/to/project';
+
+ return wrapper.vm.$nextTick().then(() => {
+ expect(findActionsMenu().exists()).toBe(true);
+ });
+ });
+
+ it('is not rendered if projectPath is not set in store', () => {
+ expect(findActionsMenu().exists()).toBe(false);
+ });
+
+ it('contains a modal', () => {
+ store.state.monitoringDashboard.projectPath = 'https://path/to/project';
+
+ return wrapper.vm.$nextTick().then(() => {
+ expect(findActionsMenu().contains(CreateDashboardModal)).toBe(true);
+ });
+ });
+
+ const duplicableCases = [
+ null, // When no path is specified, it uses the default dashboard path.
+ dashboardGitResponse[0].path,
+ dashboardGitResponse[2].path,
+ selfMonitoringDashboardGitResponse[0].path,
+ ];
+
+ describe.each(duplicableCases)(
+ 'when the selected dashboard can be duplicated',
+ dashboardPath => {
+ it('contains a "Create New" menu item and a "Duplicate Dashboard" menu item', () => {
+ store.state.monitoringDashboard.projectPath = 'https://path/to/project';
+ setupAllDashboards(store, dashboardPath);
+
+ return wrapper.vm.$nextTick().then(() => {
+ expect(findCreateDashboardMenuItem().exists()).toBe(true);
+ expect(findCreateDashboardDuplicateItem().exists()).toBe(true);
+ });
+ });
+ },
+ );
+
+ const nonDuplicableCases = [
+ dashboardGitResponse[1].path,
+ selfMonitoringDashboardGitResponse[1].path,
+ ];
+
+ describe.each(nonDuplicableCases)(
+ 'when the selected dashboard cannot be duplicated',
+ dashboardPath => {
+ it('contains a "Create New" menu item and no "Duplicate Dashboard" menu item', () => {
+ store.state.monitoringDashboard.projectPath = 'https://path/to/project';
+ setupAllDashboards(store, dashboardPath);
+
+ return wrapper.vm.$nextTick().then(() => {
+ expect(findCreateDashboardMenuItem().exists()).toBe(true);
+ expect(findCreateDashboardDuplicateItem().exists()).toBe(false);
+ });
+ });
+ },
+ );
+ });
+
+ describe('actions menu modals', () => {
+ const url = 'https://path/to/project';
+
+ beforeEach(() => {
+ store.state.monitoringDashboard.projectPath = url;
+ setupAllDashboards(store);
+
+ createShallowWrapper();
+ });
+
+ it('Clicking on "Create New" opens up a modal', () => {
+ const modalId = 'createDashboard';
+ const modalTrigger = findCreateDashboardMenuItem();
+ const rootEmit = jest.spyOn(wrapper.vm.$root, '$emit');
+
+ modalTrigger.trigger('click');
+
+ return wrapper.vm.$nextTick().then(() => {
+ expect(rootEmit.mock.calls[0]).toContainEqual(modalId);
+ });
+ });
+
+ it('"Create new dashboard" modal contains correct buttons', () => {
+ expect(findCreateDashboardModal().props('projectPath')).toBe(url);
+ });
+
+ it('"Duplicate Dashboard" opens up a modal', () => {
+ const modalId = 'duplicateDashboard';
+ const modalTrigger = findCreateDashboardDuplicateItem();
+ const rootEmit = jest.spyOn(wrapper.vm.$root, '$emit');
+
+ modalTrigger.trigger('click');
+
+ return wrapper.vm.$nextTick().then(() => {
+ expect(rootEmit.mock.calls[0]).toContainEqual(modalId);
+ });
+ });
+ });
+
+ describe('metrics settings button', () => {
+ const findSettingsButton = () => wrapper.find('[data-testid="metrics-settings-button"]');
+ const url = 'https://path/to/project/settings';
+
+ beforeEach(() => {
+ createShallowWrapper();
+
+ store.state.monitoringDashboard.canAccessOperationsSettings = false;
+ store.state.monitoringDashboard.operationsSettingsPath = '';
+ });
+
+ it('is rendered when the user can access the project settings and path to settings is available', () => {
+ store.state.monitoringDashboard.canAccessOperationsSettings = true;
+ store.state.monitoringDashboard.operationsSettingsPath = url;
+
+ return wrapper.vm.$nextTick(() => {
+ expect(findSettingsButton().exists()).toBe(true);
+ });
+ });
+
+ it('is not rendered when the user can not access the project settings', () => {
+ store.state.monitoringDashboard.canAccessOperationsSettings = false;
+ store.state.monitoringDashboard.operationsSettingsPath = url;
+
+ return wrapper.vm.$nextTick(() => {
+ expect(findSettingsButton().exists()).toBe(false);
+ });
+ });
+
+ it('is not rendered when the path to settings is unavailable', () => {
+ store.state.monitoringDashboard.canAccessOperationsSettings = false;
+ store.state.monitoringDashboard.operationsSettingsPath = '';
+
+ return wrapper.vm.$nextTick(() => {
+ expect(findSettingsButton().exists()).toBe(false);
+ });
+ });
+
+ it('leads to the project settings page', () => {
+ store.state.monitoringDashboard.canAccessOperationsSettings = true;
+ store.state.monitoringDashboard.operationsSettingsPath = url;
+
+ return wrapper.vm.$nextTick(() => {
+ expect(findSettingsButton().attributes('href')).toBe(url);
+ });
+ });
+ });
+});
diff --git a/spec/frontend/monitoring/components/dashboard_panel_spec.js b/spec/frontend/monitoring/components/dashboard_panel_spec.js
index 0ad6e04588f..693818aa55a 100644
--- a/spec/frontend/monitoring/components/dashboard_panel_spec.js
+++ b/spec/frontend/monitoring/components/dashboard_panel_spec.js
@@ -9,17 +9,16 @@ import AlertWidget from '~/monitoring/components/alert_widget.vue';
import DashboardPanel from '~/monitoring/components/dashboard_panel.vue';
import {
- anomalyMockGraphData,
mockLogsHref,
mockLogsPath,
mockNamespace,
mockNamespacedData,
mockTimeRange,
- singleStatMetricsResult,
graphDataPrometheusQueryRangeMultiTrack,
barMockData,
- propsData,
} from '../mock_data';
+import { dashboardProps, graphData, graphDataEmpty } from '../fixture_data';
+import { anomalyGraphData, singleStatGraphData } from '../graph_data';
import { panelTypes } from '~/monitoring/constants';
@@ -32,7 +31,6 @@ import MonitorColumnChart from '~/monitoring/components/charts/column.vue';
import MonitorBarChart from '~/monitoring/components/charts/bar.vue';
import MonitorStackedColumnChart from '~/monitoring/components/charts/stacked_column.vue';
-import { graphData, graphDataEmpty } from '../fixture_data';
import { createStore, monitoringDashboard } from '~/monitoring/stores';
import { createStore as createEmbedGroupStore } from '~/monitoring/stores/embed_group';
@@ -63,7 +61,7 @@ describe('Dashboard Panel', () => {
wrapper = shallowMount(DashboardPanel, {
propsData: {
graphData,
- settingsPath: propsData.settingsPath,
+ settingsPath: dashboardProps.settingsPath,
...props,
},
store,
@@ -137,10 +135,6 @@ describe('Dashboard Panel', () => {
expect(wrapper.find(MonitorEmptyChart).exists()).toBe(true);
expect(wrapper.find(MonitorEmptyChart).isVueInstance()).toBe(true);
});
-
- it('does not contain a tabindex attribute', () => {
- expect(wrapper.find(MonitorEmptyChart).contains('[tabindex]')).toBe(false);
- });
});
describe('When graphData is null', () => {
@@ -233,23 +227,32 @@ describe('Dashboard Panel', () => {
expect(wrapper.find(MonitorTimeSeriesChart).isVueInstance()).toBe(true);
});
- it.each`
- data | component
- ${dataWithType(panelTypes.AREA_CHART)} | ${MonitorTimeSeriesChart}
- ${dataWithType(panelTypes.LINE_CHART)} | ${MonitorTimeSeriesChart}
- ${anomalyMockGraphData} | ${MonitorAnomalyChart}
- ${dataWithType(panelTypes.COLUMN)} | ${MonitorColumnChart}
- ${dataWithType(panelTypes.STACKED_COLUMN)} | ${MonitorStackedColumnChart}
- ${singleStatMetricsResult} | ${MonitorSingleStatChart}
- ${graphDataPrometheusQueryRangeMultiTrack} | ${MonitorHeatmapChart}
- ${barMockData} | ${MonitorBarChart}
- `('wrapps a $data.type component binding attributes', ({ data, component }) => {
+ describe.each`
+ data | component | hasCtxMenu
+ ${dataWithType(panelTypes.AREA_CHART)} | ${MonitorTimeSeriesChart} | ${true}
+ ${dataWithType(panelTypes.LINE_CHART)} | ${MonitorTimeSeriesChart} | ${true}
+ ${singleStatGraphData()} | ${MonitorSingleStatChart} | ${true}
+ ${anomalyGraphData()} | ${MonitorAnomalyChart} | ${false}
+ ${dataWithType(panelTypes.COLUMN)} | ${MonitorColumnChart} | ${false}
+ ${dataWithType(panelTypes.STACKED_COLUMN)} | ${MonitorStackedColumnChart} | ${false}
+ ${graphDataPrometheusQueryRangeMultiTrack} | ${MonitorHeatmapChart} | ${false}
+ ${barMockData} | ${MonitorBarChart} | ${false}
+ `('when $data.type data is provided', ({ data, component, hasCtxMenu }) => {
const attrs = { attr1: 'attr1Value', attr2: 'attr2Value' };
- createWrapper({ graphData: data }, { attrs });
- expect(wrapper.find(component).exists()).toBe(true);
- expect(wrapper.find(component).isVueInstance()).toBe(true);
- expect(wrapper.find(component).attributes()).toMatchObject(attrs);
+ beforeEach(() => {
+ createWrapper({ graphData: data }, { attrs });
+ });
+
+ it(`renders the chart component and binds attributes`, () => {
+ expect(wrapper.find(component).exists()).toBe(true);
+ expect(wrapper.find(component).isVueInstance()).toBe(true);
+ expect(wrapper.find(component).attributes()).toMatchObject(attrs);
+ });
+
+ it(`contextual menu is ${hasCtxMenu ? '' : 'not '}shown`, () => {
+ expect(findCtxMenu().exists()).toBe(hasCtxMenu);
+ });
});
});
});
@@ -307,7 +310,7 @@ describe('Dashboard Panel', () => {
return wrapper.vm.$nextTick(() => {
expect(findEditCustomMetricLink().text()).toBe('Edit metrics');
- expect(findEditCustomMetricLink().attributes('href')).toBe(propsData.settingsPath);
+ expect(findEditCustomMetricLink().attributes('href')).toBe(dashboardProps.settingsPath);
});
});
});
@@ -361,7 +364,7 @@ describe('Dashboard Panel', () => {
});
});
- it('it is overriden when a datazoom event is received', () => {
+ it('it is overridden when a datazoom event is received', () => {
state.logsPath = mockLogsPath;
state.timeRange = mockTimeRange;
@@ -424,7 +427,7 @@ describe('Dashboard Panel', () => {
wrapper = shallowMount(DashboardPanel, {
propsData: {
clipboardText: exampleText,
- settingsPath: propsData.settingsPath,
+ settingsPath: dashboardProps.settingsPath,
graphData: {
y_label: 'metric',
...graphData,
@@ -474,7 +477,7 @@ describe('Dashboard Panel', () => {
wrapper = shallowMount(DashboardPanel, {
propsData: {
graphData,
- settingsPath: propsData.settingsPath,
+ settingsPath: dashboardProps.settingsPath,
namespace: mockNamespace,
},
store,
diff --git a/spec/frontend/monitoring/components/dashboard_spec.js b/spec/frontend/monitoring/components/dashboard_spec.js
index 7bb4c68b4cd..4b7f7a9ddb3 100644
--- a/spec/frontend/monitoring/components/dashboard_spec.js
+++ b/spec/frontend/monitoring/components/dashboard_spec.js
@@ -6,16 +6,18 @@ import { objectToQuery } from '~/lib/utils/url_utility';
import VueDraggable from 'vuedraggable';
import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
-import { metricStates } from '~/monitoring/constants';
+import { dashboardEmptyStates, metricStates } from '~/monitoring/constants';
import Dashboard from '~/monitoring/components/dashboard.vue';
import DashboardHeader from '~/monitoring/components/dashboard_header.vue';
+import RefreshButton from '~/monitoring/components/refresh_button.vue';
import DateTimePicker from '~/vue_shared/components/date_time_picker/date_time_picker.vue';
import CustomMetricsFormFields from '~/custom_metrics/components/custom_metrics_form_fields.vue';
import DashboardsDropdown from '~/monitoring/components/dashboards_dropdown.vue';
import EmptyState from '~/monitoring/components/empty_state.vue';
import GroupEmptyState from '~/monitoring/components/group_empty_state.vue';
import DashboardPanel from '~/monitoring/components/dashboard_panel.vue';
+import GraphGroup from '~/monitoring/components/graph_group.vue';
import LinksSection from '~/monitoring/components/links_section.vue';
import { createStore } from '~/monitoring/stores';
import * as types from '~/monitoring/stores/mutation_types';
@@ -24,12 +26,17 @@ import {
setupStoreWithDashboard,
setMetricResult,
setupStoreWithData,
- setupStoreWithVariable,
+ setupStoreWithDataForPanelCount,
setupStoreWithLinks,
} from '../store_utils';
-import { environmentData, dashboardGitResponse, propsData } from '../mock_data';
-import { metricsDashboardViewModel, metricsDashboardPanelCount } from '../fixture_data';
+import { environmentData, dashboardGitResponse, storeVariables } from '../mock_data';
+import {
+ metricsDashboardViewModel,
+ metricsDashboardPanelCount,
+ dashboardProps,
+} from '../fixture_data';
import createFlash from '~/flash';
+import { TEST_HOST } from 'helpers/test_constants';
jest.mock('~/flash');
@@ -48,7 +55,7 @@ describe('Dashboard', () => {
const createShallowWrapper = (props = {}, options = {}) => {
wrapper = shallowMount(Dashboard, {
- propsData: { ...propsData, ...props },
+ propsData: { ...dashboardProps, ...props },
store,
stubs: {
DashboardHeader,
@@ -59,7 +66,7 @@ describe('Dashboard', () => {
const createMountedWrapper = (props = {}, options = {}) => {
wrapper = mount(Dashboard, {
- propsData: { ...propsData, ...props },
+ propsData: { ...dashboardProps, ...props },
store,
stubs: {
'graph-group': true,
@@ -120,13 +127,13 @@ describe('Dashboard', () => {
});
it('shows up a loading state', () => {
- store.state.monitoringDashboard.emptyState = 'loading';
+ store.state.monitoringDashboard.emptyState = dashboardEmptyStates.LOADING;
createShallowWrapper({ hasMetrics: true });
return wrapper.vm.$nextTick().then(() => {
expect(wrapper.find(EmptyState).exists()).toBe(true);
- expect(wrapper.find(EmptyState).props('selectedState')).toBe('loading');
+ expect(wrapper.find(EmptyState).props('selectedState')).toBe(dashboardEmptyStates.LOADING);
});
});
@@ -136,7 +143,7 @@ describe('Dashboard', () => {
setupStoreWithData(store);
return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.vm.showEmptyState).toEqual(false);
+ expect(wrapper.vm.emptyState).toBeNull();
expect(wrapper.findAll('.prometheus-panel')).toHaveLength(0);
});
});
@@ -157,6 +164,103 @@ describe('Dashboard', () => {
});
});
+ describe('panel containers layout', () => {
+ const findPanelLayoutWrapperAt = index => {
+ return wrapper
+ .find(GraphGroup)
+ .findAll('[data-testid="dashboard-panel-layout-wrapper"]')
+ .at(index);
+ };
+
+ beforeEach(() => {
+ createMountedWrapper({ hasMetrics: true });
+
+ return wrapper.vm.$nextTick();
+ });
+
+ describe('when the graph group has an even number of panels', () => {
+ it('2 panels - all panel wrappers take half width of their parent', () => {
+ setupStoreWithDataForPanelCount(store, 2);
+
+ wrapper.vm.$nextTick(() => {
+ expect(findPanelLayoutWrapperAt(0).classes('col-lg-6')).toBe(true);
+ expect(findPanelLayoutWrapperAt(1).classes('col-lg-6')).toBe(true);
+ });
+ });
+
+ it('4 panels - all panel wrappers take half width of their parent', () => {
+ setupStoreWithDataForPanelCount(store, 4);
+
+ wrapper.vm.$nextTick(() => {
+ expect(findPanelLayoutWrapperAt(0).classes('col-lg-6')).toBe(true);
+ expect(findPanelLayoutWrapperAt(1).classes('col-lg-6')).toBe(true);
+ expect(findPanelLayoutWrapperAt(2).classes('col-lg-6')).toBe(true);
+ expect(findPanelLayoutWrapperAt(3).classes('col-lg-6')).toBe(true);
+ });
+ });
+ });
+
+ describe('when the graph group has an odd number of panels', () => {
+ it('1 panel - panel wrapper does not take half width of its parent', () => {
+ setupStoreWithDataForPanelCount(store, 1);
+
+ wrapper.vm.$nextTick(() => {
+ expect(findPanelLayoutWrapperAt(0).classes('col-lg-6')).toBe(false);
+ });
+ });
+
+ it('3 panels - all panels but last take half width of their parents', () => {
+ setupStoreWithDataForPanelCount(store, 3);
+
+ wrapper.vm.$nextTick(() => {
+ expect(findPanelLayoutWrapperAt(0).classes('col-lg-6')).toBe(true);
+ expect(findPanelLayoutWrapperAt(1).classes('col-lg-6')).toBe(true);
+ expect(findPanelLayoutWrapperAt(2).classes('col-lg-6')).toBe(false);
+ });
+ });
+
+ it('5 panels - all panels but last take half width of their parents', () => {
+ setupStoreWithDataForPanelCount(store, 5);
+
+ wrapper.vm.$nextTick(() => {
+ expect(findPanelLayoutWrapperAt(0).classes('col-lg-6')).toBe(true);
+ expect(findPanelLayoutWrapperAt(1).classes('col-lg-6')).toBe(true);
+ expect(findPanelLayoutWrapperAt(2).classes('col-lg-6')).toBe(true);
+ expect(findPanelLayoutWrapperAt(3).classes('col-lg-6')).toBe(true);
+ expect(findPanelLayoutWrapperAt(4).classes('col-lg-6')).toBe(false);
+ });
+ });
+ });
+ });
+
+ describe('dashboard validation warning', () => {
+ it('displays a warning if there are validation warnings', () => {
+ createMountedWrapper({ hasMetrics: true });
+
+ store.commit(
+ `monitoringDashboard/${types.RECEIVE_DASHBOARD_VALIDATION_WARNINGS_SUCCESS}`,
+ true,
+ );
+
+ return wrapper.vm.$nextTick().then(() => {
+ expect(createFlash).toHaveBeenCalled();
+ });
+ });
+
+ it('does not display a warning if there are no validation warnings', () => {
+ createMountedWrapper({ hasMetrics: true });
+
+ store.commit(
+ `monitoringDashboard/${types.RECEIVE_DASHBOARD_VALIDATION_WARNINGS_SUCCESS}`,
+ false,
+ );
+
+ return wrapper.vm.$nextTick().then(() => {
+ expect(createFlash).not.toHaveBeenCalled();
+ });
+ });
+ });
+
describe('when the URL contains a reference to a panel', () => {
let location;
@@ -323,12 +427,72 @@ describe('Dashboard', () => {
);
});
});
+
+ describe('when custom dashboard is selected', () => {
+ const windowLocation = window.location;
+ const findDashboardDropdown = () => wrapper.find(DashboardHeader).find(DashboardsDropdown);
+
+ beforeEach(() => {
+ store.commit(`monitoringDashboard/${types.SET_INITIAL_STATE}`, {
+ projectPath: TEST_HOST,
+ });
+
+ delete window.location;
+ window.location = { ...windowLocation, assign: jest.fn() };
+ createMountedWrapper();
+
+ return wrapper.vm.$nextTick();
+ });
+
+ afterEach(() => {
+ window.location = windowLocation;
+ });
+
+ it('encodes dashboard param', () => {
+ findDashboardDropdown().vm.$emit('selectDashboard', {
+ path: '.gitlab/dashboards/dashboard&copy.yml',
+ display_name: 'dashboard&copy.yml',
+ });
+ expect(window.location.assign).toHaveBeenCalledWith(
+ `${TEST_HOST}/-/metrics/dashboard%26copy.yml`,
+ );
+ });
+ });
+ });
+
+ describe('when all panels in the first group are loading', () => {
+ const findGroupAt = i => wrapper.findAll(GraphGroup).at(i);
+
+ beforeEach(() => {
+ setupStoreWithDashboard(store);
+
+ const { panels } = store.state.monitoringDashboard.dashboard.panelGroups[0];
+ panels.forEach(({ metrics }) => {
+ store.commit(`monitoringDashboard/${types.REQUEST_METRIC_RESULT}`, {
+ metricId: metrics[0].metricId,
+ });
+ });
+
+ createShallowWrapper();
+
+ return wrapper.vm.$nextTick();
+ });
+
+ it('a loading icon appears in the first group', () => {
+ expect(findGroupAt(0).props('isLoading')).toBe(true);
+ });
+
+ it('a loading icon does not appear in the second group', () => {
+ expect(findGroupAt(1).props('isLoading')).toBe(false);
+ });
});
describe('when all requests have been commited by the store', () => {
beforeEach(() => {
store.commit(`monitoringDashboard/${types.SET_INITIAL_STATE}`, {
currentEnvironmentName: 'production',
+ currentDashboard: dashboardGitResponse[0].path,
+ projectPath: TEST_HOST,
});
createMountedWrapper({ hasMetrics: true });
setupStoreWithData(store);
@@ -341,13 +505,26 @@ describe('Dashboard', () => {
findAllEnvironmentsDropdownItems().wrappers.forEach((itemWrapper, index) => {
const anchorEl = itemWrapper.find('a');
- if (anchorEl.exists() && environmentData[index].metrics_path) {
+ if (anchorEl.exists()) {
const href = anchorEl.attributes('href');
- expect(href).toBe(environmentData[index].metrics_path);
+ const currentDashboard = encodeURIComponent(dashboardGitResponse[0].path);
+ const environmentId = encodeURIComponent(environmentData[index].id);
+ const url = `${TEST_HOST}/-/metrics/${currentDashboard}?environment=${environmentId}`;
+ expect(href).toBe(url);
}
});
});
+ it('it does not show loading icons in any group', () => {
+ setupStoreWithData(store);
+
+ wrapper.vm.$nextTick(() => {
+ wrapper.findAll(GraphGroup).wrappers.forEach(groupWrapper => {
+ expect(groupWrapper.props('isLoading')).toBe(false);
+ });
+ });
+ });
+
// Note: This test is not working, .active does not show the active environment
// eslint-disable-next-line jest/no-disabled-tests
it.skip('renders the environments dropdown with a single active element', () => {
@@ -464,10 +641,9 @@ describe('Dashboard', () => {
setupStoreWithData(store);
return wrapper.vm.$nextTick().then(() => {
- const refreshBtn = wrapper.find(DashboardHeader).findAll({ ref: 'refreshDashboardBtn' });
+ const refreshBtn = wrapper.find(DashboardHeader).find(RefreshButton);
- expect(refreshBtn).toHaveLength(1);
- expect(refreshBtn.is(GlDeprecatedButton)).toBe(true);
+ expect(refreshBtn.exists()).toBe(true);
});
});
@@ -475,8 +651,7 @@ describe('Dashboard', () => {
beforeEach(() => {
createShallowWrapper({ hasMetrics: true });
setupStoreWithData(store);
- setupStoreWithVariable(store);
-
+ store.state.monitoringDashboard.variables = storeVariables;
return wrapper.vm.$nextTick();
});
@@ -1041,6 +1216,34 @@ describe('Dashboard', () => {
});
});
+ describe('keyboard shortcuts', () => {
+ const currentDashboard = dashboardGitResponse[1].path;
+ const panelRef = 'dashboard-panel-response-metrics-aws-elb-4-1'; // skip expanded panel
+
+ // While the recommendation in the documentation is to test
+ // with a data-testid attribute, I want to make sure that
+ // the dashboard panels have a ref attribute set.
+ const getDashboardPanel = () => wrapper.find({ ref: panelRef });
+
+ beforeEach(() => {
+ setupStoreWithData(store);
+ store.commit(`monitoringDashboard/${types.SET_INITIAL_STATE}`, {
+ currentDashboard,
+ });
+ createShallowWrapper({ hasMetrics: true });
+
+ wrapper.setData({ hoveredPanel: panelRef });
+
+ return wrapper.vm.$nextTick();
+ });
+
+ it('contains a ref attribute inside a DashboardPanel component', () => {
+ const dashboardPanel = getDashboardPanel();
+
+ expect(dashboardPanel.exists()).toBe(true);
+ });
+ });
+
describe('add custom metrics', () => {
const findAddMetricButton = () => wrapper.find(DashboardHeader).find({ ref: 'addMetricBtn' });
@@ -1082,7 +1285,7 @@ describe('Dashboard', () => {
it('uses modal for custom metrics form', () => {
expect(wrapper.find(GlModal).exists()).toBe(true);
- expect(wrapper.find(GlModal).attributes().modalid).toBe('add-metric');
+ expect(wrapper.find(GlModal).attributes().modalid).toBe('addMetric');
});
it('adding new metric is tracked', done => {
const submitButton = wrapper
diff --git a/spec/frontend/monitoring/components/dashboard_template_spec.js b/spec/frontend/monitoring/components/dashboard_template_spec.js
index a1a450d4abe..8941e57c4ce 100644
--- a/spec/frontend/monitoring/components/dashboard_template_spec.js
+++ b/spec/frontend/monitoring/components/dashboard_template_spec.js
@@ -5,7 +5,7 @@ import Dashboard from '~/monitoring/components/dashboard.vue';
import DashboardHeader from '~/monitoring/components/dashboard_header.vue';
import { createStore } from '~/monitoring/stores';
import { setupAllDashboards } from '../store_utils';
-import { propsData } from '../mock_data';
+import { dashboardProps } from '../fixture_data';
jest.mock('~/lib/utils/url_utility');
@@ -29,7 +29,7 @@ describe('Dashboard template', () => {
it('matches the default snapshot', () => {
wrapper = shallowMount(Dashboard, {
- propsData: { ...propsData },
+ propsData: { ...dashboardProps },
store,
stubs: {
DashboardHeader,
diff --git a/spec/frontend/monitoring/components/dashboard_url_time_spec.js b/spec/frontend/monitoring/components/dashboard_url_time_spec.js
index a74c621db9b..276e20bae6a 100644
--- a/spec/frontend/monitoring/components/dashboard_url_time_spec.js
+++ b/spec/frontend/monitoring/components/dashboard_url_time_spec.js
@@ -9,7 +9,8 @@ import {
updateHistory,
} from '~/lib/utils/url_utility';
import axios from '~/lib/utils/axios_utils';
-import { mockProjectDir, propsData } from '../mock_data';
+import { mockProjectDir } from '../mock_data';
+import { dashboardProps } from '../fixture_data';
import Dashboard from '~/monitoring/components/dashboard.vue';
import DashboardHeader from '~/monitoring/components/dashboard_header.vue';
@@ -26,7 +27,7 @@ describe('dashboard invalid url parameters', () => {
const createMountedWrapper = (props = { hasMetrics: true }, options = {}) => {
wrapper = mount(Dashboard, {
- propsData: { ...propsData, ...props },
+ propsData: { ...dashboardProps, ...props },
store,
stubs: { 'graph-group': true, 'dashboard-panel': true, 'dashboard-header': DashboardHeader },
...options,
diff --git a/spec/frontend/monitoring/components/dashboards_dropdown_spec.js b/spec/frontend/monitoring/components/dashboards_dropdown_spec.js
index b29d86cbc5b..d09fcc92ee7 100644
--- a/spec/frontend/monitoring/components/dashboards_dropdown_spec.js
+++ b/spec/frontend/monitoring/components/dashboards_dropdown_spec.js
@@ -1,14 +1,12 @@
import { shallowMount } from '@vue/test-utils';
-import { GlDropdownItem, GlModal, GlLoadingIcon, GlAlert, GlIcon } from '@gitlab/ui';
-import waitForPromises from 'helpers/wait_for_promises';
+import { GlDropdownItem, GlIcon } from '@gitlab/ui';
import DashboardsDropdown from '~/monitoring/components/dashboards_dropdown.vue';
-import DuplicateDashboardForm from '~/monitoring/components/duplicate_dashboard_form.vue';
-import { dashboardGitResponse } from '../mock_data';
+import { dashboardGitResponse, selfMonitoringDashboardGitResponse } from '../mock_data';
const defaultBranch = 'master';
-
+const modalId = 'duplicateDashboardModalId';
const starredDashboards = dashboardGitResponse.filter(({ starred }) => starred);
const notStarredDashboards = dashboardGitResponse.filter(({ starred }) => !starred);
@@ -32,6 +30,7 @@ describe('DashboardsDropdown', () => {
propsData: {
...props,
defaultBranch,
+ modalId,
},
sync: false,
...storeOpts,
@@ -82,7 +81,7 @@ describe('DashboardsDropdown', () => {
const searchTerm = 'Default';
setSearchTerm(searchTerm);
- return wrapper.vm.$nextTick(() => {
+ return wrapper.vm.$nextTick().then(() => {
expect(findItems()).toHaveLength(1);
});
});
@@ -91,7 +90,7 @@ describe('DashboardsDropdown', () => {
const searchTerm = 'does-not-exist';
setSearchTerm(searchTerm);
- return wrapper.vm.$nextTick(() => {
+ return wrapper.vm.$nextTick().then(() => {
expect(findNoItemsMsg().isVisible()).toBe(true);
});
});
@@ -151,12 +150,18 @@ describe('DashboardsDropdown', () => {
});
});
- describe('when a system dashboard is selected', () => {
+ const duplicableCases = [
+ dashboardGitResponse[0],
+ dashboardGitResponse[2],
+ selfMonitoringDashboardGitResponse[0],
+ ];
+
+ describe.each(duplicableCases)('when the selected dashboard can be duplicated', dashboard => {
let duplicateDashboardAction;
let modalDirective;
beforeEach(() => {
- [mockSelectedDashboard] = dashboardGitResponse;
+ mockSelectedDashboard = dashboard;
modalDirective = jest.fn();
duplicateDashboardAction = jest.fn().mockResolvedValue();
@@ -172,152 +177,59 @@ describe('DashboardsDropdown', () => {
},
},
);
-
- wrapper.vm.$refs.duplicateDashboardModal.hide = jest.fn();
});
- it('displays an item for each dashboard plus a "duplicate dashboard" item', () => {
- const item = wrapper.findAll({ ref: 'duplicateDashboardItem' });
-
+ it('displays a dropdown item for each dashboard', () => {
expect(findItems().length).toEqual(dashboardGitResponse.length + 1);
- expect(item.length).toBe(1);
});
- describe('modal form', () => {
- let okEvent;
-
- const findModal = () => wrapper.find(GlModal);
- const findAlert = () => wrapper.find(GlAlert);
-
- beforeEach(() => {
- okEvent = {
- preventDefault: jest.fn(),
- };
- });
-
- it('exists and contains a form to duplicate a dashboard', () => {
- expect(findModal().exists()).toBe(true);
- expect(findModal().contains(DuplicateDashboardForm)).toBe(true);
- });
-
- it('saves a new dashboard', () => {
- findModal().vm.$emit('ok', okEvent);
-
- return waitForPromises().then(() => {
- expect(okEvent.preventDefault).toHaveBeenCalled();
-
- expect(wrapper.find(GlLoadingIcon).exists()).toBe(false);
- expect(wrapper.vm.$refs.duplicateDashboardModal.hide).toHaveBeenCalled();
- expect(wrapper.emitted().selectDashboard).toBeTruthy();
- expect(findAlert().exists()).toBe(false);
- });
- });
-
- describe('when a new dashboard is saved succesfully', () => {
- const newDashboard = {
- can_edit: true,
- default: false,
- display_name: 'A new dashboard',
- system_dashboard: false,
- };
-
- const submitForm = formVals => {
- duplicateDashboardAction.mockResolvedValueOnce(newDashboard);
- findModal()
- .find(DuplicateDashboardForm)
- .vm.$emit('change', {
- dashboard: 'common_metrics.yml',
- commitMessage: 'A commit message',
- ...formVals,
- });
- findModal().vm.$emit('ok', okEvent);
- };
-
- it('to the default branch, redirects to the new dashboard', () => {
- submitForm({
- branch: defaultBranch,
- });
-
- return waitForPromises().then(() => {
- expect(wrapper.emitted().selectDashboard[0][0]).toEqual(newDashboard);
- });
- });
-
- it('to a new branch refreshes in the current dashboard', () => {
- submitForm({
- branch: 'another-branch',
- });
-
- return waitForPromises().then(() => {
- expect(wrapper.emitted().selectDashboard[0][0]).toEqual(dashboardGitResponse[0]);
- });
- });
- });
-
- it('handles error when a new dashboard is not saved', () => {
- const errMsg = 'An error occurred';
-
- duplicateDashboardAction.mockRejectedValueOnce(errMsg);
- findModal().vm.$emit('ok', okEvent);
+ it('displays one "duplicate dashboard" dropdown item with a directive attached', () => {
+ const item = wrapper.findAll('[data-testid="duplicateDashboardItem"]');
- return waitForPromises().then(() => {
- expect(okEvent.preventDefault).toHaveBeenCalled();
+ expect(item.length).toBe(1);
+ });
- expect(findAlert().exists()).toBe(true);
- expect(findAlert().text()).toBe(errMsg);
+ it('"duplicate dashboard" dropdown item directive works', () => {
+ const item = wrapper.find('[data-testid="duplicateDashboardItem"]');
- expect(wrapper.find(GlLoadingIcon).exists()).toBe(false);
- expect(wrapper.vm.$refs.duplicateDashboardModal.hide).not.toHaveBeenCalled();
- });
- });
+ item.trigger('click');
- it('id is correct, as the value of modal directive binding matches modal id', () => {
- expect(modalDirective).toHaveBeenCalledTimes(1);
-
- // Binding's second argument contains the modal id
- expect(modalDirective.mock.calls[0][1]).toEqual(
- expect.objectContaining({
- value: findModal().props('modalId'),
- }),
- );
+ return wrapper.vm.$nextTick().then(() => {
+ expect(modalDirective).toHaveBeenCalled();
});
+ });
- it('updates the form on changes', () => {
- const formVals = {
- dashboard: 'common_metrics.yml',
- commitMessage: 'A commit message',
- };
-
- findModal()
- .find(DuplicateDashboardForm)
- .vm.$emit('change', formVals);
+ it('id is correct, as the value of modal directive binding matches modal id', () => {
+ expect(modalDirective).toHaveBeenCalledTimes(1);
- // Binding's second argument contains the modal id
- expect(wrapper.vm.form).toEqual(formVals);
- });
+ // Binding's second argument contains the modal id
+ expect(modalDirective.mock.calls[0][1]).toEqual(
+ expect.objectContaining({
+ value: modalId,
+ }),
+ );
});
});
- describe('when a custom dashboard is selected', () => {
- const findModal = () => wrapper.find(GlModal);
+ const nonDuplicableCases = [dashboardGitResponse[1], selfMonitoringDashboardGitResponse[1]];
- beforeEach(() => {
- wrapper = createComponent({
- selectedDashboard: dashboardGitResponse[1],
+ describe.each(nonDuplicableCases)(
+ 'when the selected dashboard can not be duplicated',
+ dashboard => {
+ beforeEach(() => {
+ mockSelectedDashboard = dashboard;
+
+ wrapper = createComponent();
});
- });
- it('displays an item for each dashboard', () => {
- const item = wrapper.findAll({ ref: 'duplicateDashboardItem' });
+ it('displays a dropdown list item for each dashboard, but no list item for "duplicate dashboard"', () => {
+ const item = wrapper.findAll('[data-testid="duplicateDashboardItem"]');
- expect(findItems()).toHaveLength(dashboardGitResponse.length);
- expect(item.length).toBe(0);
- });
-
- it('modal form does not exist and contains a form to duplicate a dashboard', () => {
- expect(findModal().exists()).toBe(false);
- });
- });
+ expect(findItems()).toHaveLength(dashboardGitResponse.length);
+ expect(item.length).toBe(0);
+ });
+ },
+ );
describe('when a dashboard gets selected by the user', () => {
beforeEach(() => {
diff --git a/spec/frontend/monitoring/components/duplicate_dashboard_modal_spec.js b/spec/frontend/monitoring/components/duplicate_dashboard_modal_spec.js
new file mode 100644
index 00000000000..d8ffb4443ac
--- /dev/null
+++ b/spec/frontend/monitoring/components/duplicate_dashboard_modal_spec.js
@@ -0,0 +1,111 @@
+import { shallowMount } from '@vue/test-utils';
+import { GlAlert, GlLoadingIcon, GlModal } from '@gitlab/ui';
+
+import waitForPromises from 'helpers/wait_for_promises';
+
+import DuplicateDashboardModal from '~/monitoring/components/duplicate_dashboard_modal.vue';
+import DuplicateDashboardForm from '~/monitoring/components/duplicate_dashboard_form.vue';
+
+import { dashboardGitResponse } from '../mock_data';
+
+describe('duplicate dashboard modal', () => {
+ let wrapper;
+ let mockDashboards;
+ let mockSelectedDashboard;
+ let duplicateDashboardAction;
+ let okEvent;
+
+ function createComponent(opts = {}) {
+ const storeOpts = {
+ methods: {
+ duplicateSystemDashboard: jest.fn(),
+ },
+ computed: {
+ allDashboards: () => mockDashboards,
+ selectedDashboard: () => mockSelectedDashboard,
+ },
+ };
+
+ return shallowMount(DuplicateDashboardModal, {
+ propsData: {
+ defaultBranch: 'master',
+ modalId: 'id',
+ },
+ sync: false,
+ ...storeOpts,
+ ...opts,
+ });
+ }
+
+ const findAlert = () => wrapper.find(GlAlert);
+ const findModal = () => wrapper.find(GlModal);
+ const findDuplicateDashboardForm = () => wrapper.find(DuplicateDashboardForm);
+
+ beforeEach(() => {
+ mockDashboards = dashboardGitResponse;
+ [mockSelectedDashboard] = dashboardGitResponse;
+
+ duplicateDashboardAction = jest.fn().mockResolvedValue();
+
+ okEvent = {
+ preventDefault: jest.fn(),
+ };
+
+ wrapper = createComponent({
+ methods: {
+ // Mock vuex actions
+ duplicateSystemDashboard: duplicateDashboardAction,
+ },
+ });
+
+ wrapper.vm.$refs.duplicateDashboardModal.hide = jest.fn();
+ });
+
+ it('contains a form to duplicate a dashboard', () => {
+ expect(findDuplicateDashboardForm().exists()).toBe(true);
+ });
+
+ it('saves a new dashboard', () => {
+ findModal().vm.$emit('ok', okEvent);
+
+ return waitForPromises().then(() => {
+ expect(okEvent.preventDefault).toHaveBeenCalled();
+ expect(wrapper.emitted().dashboardDuplicated).toBeTruthy();
+ expect(wrapper.emitted().dashboardDuplicated[0]).toEqual([dashboardGitResponse[0]]);
+ expect(wrapper.find(GlLoadingIcon).exists()).toBe(false);
+ expect(wrapper.vm.$refs.duplicateDashboardModal.hide).toHaveBeenCalled();
+ expect(findAlert().exists()).toBe(false);
+ });
+ });
+
+ it('handles error when a new dashboard is not saved', () => {
+ const errMsg = 'An error occurred';
+
+ duplicateDashboardAction.mockRejectedValueOnce(errMsg);
+ findModal().vm.$emit('ok', okEvent);
+
+ return waitForPromises().then(() => {
+ expect(okEvent.preventDefault).toHaveBeenCalled();
+
+ expect(findAlert().exists()).toBe(true);
+ expect(findAlert().text()).toBe(errMsg);
+
+ expect(wrapper.find(GlLoadingIcon).exists()).toBe(false);
+ expect(wrapper.vm.$refs.duplicateDashboardModal.hide).not.toHaveBeenCalled();
+ });
+ });
+
+ it('updates the form on changes', () => {
+ const formVals = {
+ dashboard: 'common_metrics.yml',
+ commitMessage: 'A commit message',
+ };
+
+ findModal()
+ .find(DuplicateDashboardForm)
+ .vm.$emit('change', formVals);
+
+ // Binding's second argument contains the modal id
+ expect(wrapper.vm.form).toEqual(formVals);
+ });
+});
diff --git a/spec/frontend/monitoring/components/empty_state_spec.js b/spec/frontend/monitoring/components/empty_state_spec.js
index e985e5fb443..abb8b21e9f4 100644
--- a/spec/frontend/monitoring/components/empty_state_spec.js
+++ b/spec/frontend/monitoring/components/empty_state_spec.js
@@ -1,10 +1,11 @@
import { shallowMount } from '@vue/test-utils';
+import { GlLoadingIcon, GlEmptyState } from '@gitlab/ui';
+import { dashboardEmptyStates } from '~/monitoring/constants';
import EmptyState from '~/monitoring/components/empty_state.vue';
function createComponent(props) {
return shallowMount(EmptyState, {
propsData: {
- ...props,
settingsPath: '/settingsPath',
clustersPath: '/clustersPath',
documentationPath: '/documentationPath',
@@ -13,30 +14,40 @@ function createComponent(props) {
emptyNoDataSvgPath: '/path/to/no-data.svg',
emptyNoDataSmallSvgPath: '/path/to/no-data-small.svg',
emptyUnableToConnectSvgPath: '/path/to/unable-to-connect.svg',
+ ...props,
},
});
}
describe('EmptyState', () => {
+ it('shows loading state with a loading icon', () => {
+ const wrapper = createComponent({
+ selectedState: dashboardEmptyStates.LOADING,
+ });
+
+ expect(wrapper.find(GlLoadingIcon).exists()).toBe(true);
+ expect(wrapper.find(GlEmptyState).exists()).toBe(false);
+ });
+
it('shows gettingStarted state', () => {
const wrapper = createComponent({
- selectedState: 'gettingStarted',
+ selectedState: dashboardEmptyStates.GETTING_STARTED,
});
expect(wrapper.element).toMatchSnapshot();
});
- it('shows loading state', () => {
+ it('shows unableToConnect state', () => {
const wrapper = createComponent({
- selectedState: 'loading',
+ selectedState: dashboardEmptyStates.UNABLE_TO_CONNECT,
});
expect(wrapper.element).toMatchSnapshot();
});
- it('shows unableToConnect state', () => {
+ it('shows noData state', () => {
const wrapper = createComponent({
- selectedState: 'unableToConnect',
+ selectedState: dashboardEmptyStates.NO_DATA,
});
expect(wrapper.element).toMatchSnapshot();
diff --git a/spec/frontend/monitoring/components/graph_group_spec.js b/spec/frontend/monitoring/components/graph_group_spec.js
index 92829135c0f..81f5d90c310 100644
--- a/spec/frontend/monitoring/components/graph_group_spec.js
+++ b/spec/frontend/monitoring/components/graph_group_spec.js
@@ -1,13 +1,14 @@
import { shallowMount } from '@vue/test-utils';
import GraphGroup from '~/monitoring/components/graph_group.vue';
-import Icon from '~/vue_shared/components/icon.vue';
+import { GlLoadingIcon, GlIcon } from '@gitlab/ui';
describe('Graph group component', () => {
let wrapper;
const findGroup = () => wrapper.find({ ref: 'graph-group' });
const findContent = () => wrapper.find({ ref: 'graph-group-content' });
- const findCaretIcon = () => wrapper.find(Icon);
+ const findLoadingIcon = () => wrapper.find(GlLoadingIcon);
+ const findCaretIcon = () => wrapper.find(GlIcon);
const findToggleButton = () => wrapper.find('[data-testid="group-toggle-button"]');
const createComponent = propsData => {
@@ -28,28 +29,28 @@ describe('Graph group component', () => {
});
});
+ it('should not show a loading icon', () => {
+ expect(findLoadingIcon().exists()).toBe(false);
+ });
+
it('should show the angle-down caret icon', () => {
expect(findContent().isVisible()).toBe(true);
expect(findCaretIcon().props('name')).toBe('angle-down');
});
it('should show the angle-right caret icon when the user collapses the group', () => {
- wrapper.vm.collapse();
+ findToggleButton().trigger('click');
- return wrapper.vm.$nextTick(() => {
+ return wrapper.vm.$nextTick().then(() => {
expect(findContent().isVisible()).toBe(false);
expect(findCaretIcon().props('name')).toBe('angle-right');
});
});
- it('should contain a tabindex', () => {
- expect(findGroup().contains('[tabindex]')).toBe(true);
- });
-
it('should contain a tab index for the collapse button', () => {
const groupToggle = findToggleButton();
- expect(groupToggle.contains('[tabindex]')).toBe(true);
+ expect(groupToggle.is('[tabindex]')).toBe(true);
});
it('should show the open the group when collapseGroup is set to true', () => {
@@ -57,77 +58,94 @@ describe('Graph group component', () => {
collapseGroup: true,
});
- return wrapper.vm.$nextTick(() => {
+ return wrapper.vm.$nextTick().then(() => {
expect(findContent().isVisible()).toBe(true);
expect(findCaretIcon().props('name')).toBe('angle-down');
});
});
+ });
- describe('When group is collapsed', () => {
- beforeEach(() => {
- createComponent({
- name: 'panel',
- collapseGroup: true,
- });
+ describe('When group is collapsed', () => {
+ beforeEach(() => {
+ createComponent({
+ name: 'panel',
+ collapseGroup: true,
});
+ });
- it('should show the angle-down caret icon when collapseGroup is true', () => {
- expect(wrapper.vm.caretIcon).toBe('angle-right');
- });
+ it('should show the angle-down caret icon when collapseGroup is true', () => {
+ expect(findCaretIcon().props('name')).toBe('angle-right');
+ });
- it('should show the angle-right caret icon when collapseGroup is false', () => {
- wrapper.vm.collapse();
+ it('should show the angle-right caret icon when collapseGroup is false', () => {
+ findToggleButton().trigger('click');
- expect(wrapper.vm.caretIcon).toBe('angle-down');
+ return wrapper.vm.$nextTick().then(() => {
+ expect(findCaretIcon().props('name')).toBe('angle-down');
});
+ });
- it('should call collapse the graph group content when enter is pressed on the caret icon', () => {
- const graphGroupContent = findContent();
- const button = findToggleButton();
+ it('should call collapse the graph group content when enter is pressed on the caret icon', () => {
+ const graphGroupContent = findContent();
+ const button = findToggleButton();
- button.trigger('keyup.enter');
+ button.trigger('keyup.enter');
+
+ expect(graphGroupContent.isVisible()).toBe(false);
+ });
+ });
- expect(graphGroupContent.isVisible()).toBe(false);
+ describe('When groups can not be collapsed', () => {
+ beforeEach(() => {
+ createComponent({
+ name: 'panel',
+ showPanels: false,
+ collapseGroup: false,
});
});
- describe('When groups can not be collapsed', () => {
- beforeEach(() => {
- createComponent({
- name: 'panel',
- showPanels: false,
- collapseGroup: false,
- });
+ it('should not have a container when showPanels is false', () => {
+ expect(findGroup().exists()).toBe(false);
+ expect(findContent().exists()).toBe(true);
+ });
+ });
+
+ describe('When group is loading', () => {
+ beforeEach(() => {
+ createComponent({
+ name: 'panel',
+ isLoading: true,
});
+ });
- it('should not have a container when showPanels is false', () => {
- expect(findGroup().exists()).toBe(false);
- expect(findContent().exists()).toBe(true);
+ it('should show a loading icon', () => {
+ expect(findLoadingIcon().exists()).toBe(true);
+ });
+ });
+
+ describe('When group does not show a panel heading', () => {
+ beforeEach(() => {
+ createComponent({
+ name: 'panel',
+ showPanels: false,
+ collapseGroup: false,
});
});
- describe('When group does not show a panel heading', () => {
- beforeEach(() => {
- createComponent({
- name: 'panel',
- showPanels: false,
- collapseGroup: false,
- });
+ it('should collapse the panel content', () => {
+ expect(findContent().isVisible()).toBe(true);
+ expect(findCaretIcon().exists()).toBe(false);
+ });
+
+ it('should show the panel content when collapse is set to false', () => {
+ wrapper.setProps({
+ collapseGroup: false,
});
- it('should collapse the panel content', () => {
+ return wrapper.vm.$nextTick().then(() => {
expect(findContent().isVisible()).toBe(true);
expect(findCaretIcon().exists()).toBe(false);
});
-
- it('should show the panel content when clicked', () => {
- wrapper.vm.collapse();
-
- return wrapper.vm.$nextTick(() => {
- expect(findContent().isVisible()).toBe(true);
- expect(findCaretIcon().exists()).toBe(false);
- });
- });
});
});
});
diff --git a/spec/frontend/monitoring/components/links_section_spec.js b/spec/frontend/monitoring/components/links_section_spec.js
index 3b5b72d84ee..b771d63d51f 100644
--- a/spec/frontend/monitoring/components/links_section_spec.js
+++ b/spec/frontend/monitoring/components/links_section_spec.js
@@ -15,7 +15,7 @@ describe('Links Section component', () => {
const setState = links => {
store.state.monitoringDashboard = {
...store.state.monitoringDashboard,
- showEmptyState: false,
+ emptyState: null,
links,
};
};
diff --git a/spec/frontend/monitoring/components/refresh_button_spec.js b/spec/frontend/monitoring/components/refresh_button_spec.js
new file mode 100644
index 00000000000..29615638453
--- /dev/null
+++ b/spec/frontend/monitoring/components/refresh_button_spec.js
@@ -0,0 +1,143 @@
+import { shallowMount } from '@vue/test-utils';
+import { createStore } from '~/monitoring/stores';
+import { GlNewDropdown, GlNewDropdownItem, GlButton } from '@gitlab/ui';
+
+import RefreshButton from '~/monitoring/components/refresh_button.vue';
+
+describe('RefreshButton', () => {
+ let wrapper;
+ let store;
+ let dispatch;
+ let documentHidden;
+
+ const createWrapper = () => {
+ wrapper = shallowMount(RefreshButton, { store });
+ };
+
+ const findRefreshBtn = () => wrapper.find(GlButton);
+ const findDropdown = () => wrapper.find(GlNewDropdown);
+ const findOptions = () => findDropdown().findAll(GlNewDropdownItem);
+ const findOptionAt = index => findOptions().at(index);
+
+ const expectFetchDataToHaveBeenCalledTimes = times => {
+ const refreshCalls = dispatch.mock.calls.filter(([action, payload]) => {
+ return action === 'monitoringDashboard/fetchDashboardData' && payload === undefined;
+ });
+ expect(refreshCalls).toHaveLength(times);
+ };
+
+ beforeEach(() => {
+ store = createStore();
+ jest.spyOn(store, 'dispatch').mockResolvedValue();
+ dispatch = store.dispatch;
+
+ // Document can be mock hidden by overriding the `hidden` property
+ documentHidden = false;
+ Object.defineProperty(document, 'hidden', {
+ configurable: true,
+ get() {
+ return documentHidden;
+ },
+ });
+
+ createWrapper();
+ });
+
+ afterEach(() => {
+ dispatch.mockReset();
+ wrapper.destroy();
+ });
+
+ it('refreshes data when "refresh" is clicked', () => {
+ findRefreshBtn().vm.$emit('click');
+ expectFetchDataToHaveBeenCalledTimes(1);
+ });
+
+ it('refresh rate is "Off" in the dropdown', () => {
+ expect(findDropdown().props('text')).toBe('Off');
+ });
+
+ describe('refresh rate options', () => {
+ it('presents multiple options', () => {
+ expect(findOptions().length).toBeGreaterThan(1);
+ });
+
+ it('presents an "Off" option as the default option', () => {
+ expect(findOptionAt(0).text()).toBe('Off');
+ expect(findOptionAt(0).props('isChecked')).toBe(true);
+ });
+ });
+
+ describe('when a refresh rate is chosen', () => {
+ const optIndex = 2; // Other option than "Off"
+
+ beforeEach(() => {
+ findOptionAt(optIndex).vm.$emit('click');
+ return wrapper.vm.$nextTick;
+ });
+
+ it('refresh rate appears in the dropdown', () => {
+ expect(findDropdown().props('text')).toBe('10s');
+ });
+
+ it('refresh rate option is checked', () => {
+ expect(findOptionAt(0).props('isChecked')).toBe(false);
+ expect(findOptionAt(optIndex).props('isChecked')).toBe(true);
+ });
+
+ it('refreshes data when a new refresh rate is chosen', () => {
+ expectFetchDataToHaveBeenCalledTimes(1);
+ });
+
+ it('refreshes data after two intervals of time have passed', async () => {
+ jest.runOnlyPendingTimers();
+ expectFetchDataToHaveBeenCalledTimes(2);
+
+ await wrapper.vm.$nextTick();
+
+ jest.runOnlyPendingTimers();
+ expectFetchDataToHaveBeenCalledTimes(3);
+ });
+
+ it('does not refresh data if the document is hidden', async () => {
+ documentHidden = true;
+
+ jest.runOnlyPendingTimers();
+ expectFetchDataToHaveBeenCalledTimes(1);
+
+ await wrapper.vm.$nextTick();
+
+ jest.runOnlyPendingTimers();
+ expectFetchDataToHaveBeenCalledTimes(1);
+ });
+
+ it('data is not refreshed anymore after component is destroyed', () => {
+ expect(jest.getTimerCount()).toBe(1);
+
+ wrapper.destroy();
+
+ expect(jest.getTimerCount()).toBe(0);
+ });
+
+ describe('when "Off" refresh rate is chosen', () => {
+ beforeEach(() => {
+ findOptionAt(0).vm.$emit('click');
+ return wrapper.vm.$nextTick;
+ });
+
+ it('refresh rate is "Off" in the dropdown', () => {
+ expect(findDropdown().props('text')).toBe('Off');
+ });
+
+ it('refresh rate option is appears selected', () => {
+ expect(findOptionAt(0).props('isChecked')).toBe(true);
+ expect(findOptionAt(optIndex).props('isChecked')).toBe(false);
+ });
+
+ it('stops refreshing data', () => {
+ jest.runOnlyPendingTimers();
+ expectFetchDataToHaveBeenCalledTimes(1);
+ });
+ });
+ });
+});
diff --git a/spec/frontend/monitoring/components/variables/custom_variable_spec.js b/spec/frontend/monitoring/components/variables/custom_variable_spec.js
deleted file mode 100644
index 5a2b26219b6..00000000000
--- a/spec/frontend/monitoring/components/variables/custom_variable_spec.js
+++ /dev/null
@@ -1,52 +0,0 @@
-import { shallowMount } from '@vue/test-utils';
-import { GlDropdown, GlDropdownItem } from '@gitlab/ui';
-import CustomVariable from '~/monitoring/components/variables/custom_variable.vue';
-
-describe('Custom variable component', () => {
- let wrapper;
- const propsData = {
- name: 'env',
- label: 'Select environment',
- value: 'Production',
- options: [{ text: 'Production', value: 'prod' }, { text: 'Canary', value: 'canary' }],
- };
- const createShallowWrapper = () => {
- wrapper = shallowMount(CustomVariable, {
- propsData,
- });
- };
-
- const findDropdown = () => wrapper.find(GlDropdown);
- const findDropdownItems = () => wrapper.findAll(GlDropdownItem);
-
- it('renders dropdown element when all necessary props are passed', () => {
- createShallowWrapper();
-
- expect(findDropdown()).toExist();
- });
-
- it('renders dropdown element with a text', () => {
- createShallowWrapper();
-
- expect(findDropdown().attributes('text')).toBe(propsData.value);
- });
-
- it('renders all the dropdown items', () => {
- createShallowWrapper();
-
- expect(findDropdownItems()).toHaveLength(propsData.options.length);
- });
-
- it('changing dropdown items triggers update', () => {
- createShallowWrapper();
- jest.spyOn(wrapper.vm, '$emit');
-
- findDropdownItems()
- .at(1)
- .vm.$emit('click');
-
- return wrapper.vm.$nextTick(() => {
- expect(wrapper.vm.$emit).toHaveBeenCalledWith('onUpdate', 'env', 'canary');
- });
- });
-});
diff --git a/spec/frontend/monitoring/components/variables/dropdown_field_spec.js b/spec/frontend/monitoring/components/variables/dropdown_field_spec.js
new file mode 100644
index 00000000000..cc384aef231
--- /dev/null
+++ b/spec/frontend/monitoring/components/variables/dropdown_field_spec.js
@@ -0,0 +1,65 @@
+import { shallowMount } from '@vue/test-utils';
+import { GlDropdown, GlDropdownItem } from '@gitlab/ui';
+import DropdownField from '~/monitoring/components/variables/dropdown_field.vue';
+
+describe('Custom variable component', () => {
+ let wrapper;
+
+ const defaultProps = {
+ name: 'env',
+ label: 'Select environment',
+ value: 'Production',
+ options: {
+ values: [{ text: 'Production', value: 'prod' }, { text: 'Canary', value: 'canary' }],
+ },
+ };
+
+ const createShallowWrapper = props => {
+ wrapper = shallowMount(DropdownField, {
+ propsData: {
+ ...defaultProps,
+ ...props,
+ },
+ });
+ };
+
+ const findDropdown = () => wrapper.find(GlDropdown);
+ const findDropdownItems = () => wrapper.findAll(GlDropdownItem);
+
+ it('renders dropdown element when all necessary props are passed', () => {
+ createShallowWrapper();
+
+ expect(findDropdown().exists()).toBe(true);
+ });
+
+ it('renders dropdown element with a text', () => {
+ createShallowWrapper();
+
+ expect(findDropdown().attributes('text')).toBe(defaultProps.value);
+ });
+
+ it('renders all the dropdown items', () => {
+ createShallowWrapper();
+
+ expect(findDropdownItems()).toHaveLength(defaultProps.options.values.length);
+ });
+
+ it('renders dropdown when values are missing', () => {
+ createShallowWrapper({ options: {} });
+
+ expect(findDropdown().exists()).toBe(true);
+ });
+
+ it('changing dropdown items triggers update', () => {
+ createShallowWrapper();
+ jest.spyOn(wrapper.vm, '$emit');
+
+ findDropdownItems()
+ .at(1)
+ .vm.$emit('click');
+
+ return wrapper.vm.$nextTick(() => {
+ expect(wrapper.vm.$emit).toHaveBeenCalledWith('input', 'canary');
+ });
+ });
+});
diff --git a/spec/frontend/monitoring/components/variables/text_field_spec.js b/spec/frontend/monitoring/components/variables/text_field_spec.js
new file mode 100644
index 00000000000..99c6facac38
--- /dev/null
+++ b/spec/frontend/monitoring/components/variables/text_field_spec.js
@@ -0,0 +1,59 @@
+import { shallowMount } from '@vue/test-utils';
+import { GlFormInput } from '@gitlab/ui';
+import TextField from '~/monitoring/components/variables/text_field.vue';
+
+describe('Text variable component', () => {
+ let wrapper;
+ const propsData = {
+ name: 'pod',
+ label: 'Select pod',
+ value: 'test-pod',
+ };
+ const createShallowWrapper = () => {
+ wrapper = shallowMount(TextField, {
+ propsData,
+ });
+ };
+
+ const findInput = () => wrapper.find(GlFormInput);
+
+ it('renders a text input when all props are passed', () => {
+ createShallowWrapper();
+
+ expect(findInput()).toExist();
+ });
+
+ it('always has a default value', () => {
+ createShallowWrapper();
+
+ return wrapper.vm.$nextTick(() => {
+ expect(findInput().attributes('value')).toBe(propsData.value);
+ });
+ });
+
+ it('triggers keyup enter', () => {
+ createShallowWrapper();
+ jest.spyOn(wrapper.vm, '$emit');
+
+ findInput().element.value = 'prod-pod';
+ findInput().trigger('input');
+ findInput().trigger('keyup.enter');
+
+ return wrapper.vm.$nextTick(() => {
+ expect(wrapper.vm.$emit).toHaveBeenCalledWith('input', 'prod-pod');
+ });
+ });
+
+ it('triggers blur enter', () => {
+ createShallowWrapper();
+ jest.spyOn(wrapper.vm, '$emit');
+
+ findInput().element.value = 'canary-pod';
+ findInput().trigger('input');
+ findInput().trigger('blur');
+
+ return wrapper.vm.$nextTick(() => {
+ expect(wrapper.vm.$emit).toHaveBeenCalledWith('input', 'canary-pod');
+ });
+ });
+});
diff --git a/spec/frontend/monitoring/components/variables/text_variable_spec.js b/spec/frontend/monitoring/components/variables/text_variable_spec.js
deleted file mode 100644
index f01584ae8bc..00000000000
--- a/spec/frontend/monitoring/components/variables/text_variable_spec.js
+++ /dev/null
@@ -1,59 +0,0 @@
-import { shallowMount } from '@vue/test-utils';
-import { GlFormInput } from '@gitlab/ui';
-import TextVariable from '~/monitoring/components/variables/text_variable.vue';
-
-describe('Text variable component', () => {
- let wrapper;
- const propsData = {
- name: 'pod',
- label: 'Select pod',
- value: 'test-pod',
- };
- const createShallowWrapper = () => {
- wrapper = shallowMount(TextVariable, {
- propsData,
- });
- };
-
- const findInput = () => wrapper.find(GlFormInput);
-
- it('renders a text input when all props are passed', () => {
- createShallowWrapper();
-
- expect(findInput()).toExist();
- });
-
- it('always has a default value', () => {
- createShallowWrapper();
-
- return wrapper.vm.$nextTick(() => {
- expect(findInput().attributes('value')).toBe(propsData.value);
- });
- });
-
- it('triggers keyup enter', () => {
- createShallowWrapper();
- jest.spyOn(wrapper.vm, '$emit');
-
- findInput().element.value = 'prod-pod';
- findInput().trigger('input');
- findInput().trigger('keyup.enter');
-
- return wrapper.vm.$nextTick(() => {
- expect(wrapper.vm.$emit).toHaveBeenCalledWith('onUpdate', 'pod', 'prod-pod');
- });
- });
-
- it('triggers blur enter', () => {
- createShallowWrapper();
- jest.spyOn(wrapper.vm, '$emit');
-
- findInput().element.value = 'canary-pod';
- findInput().trigger('input');
- findInput().trigger('blur');
-
- return wrapper.vm.$nextTick(() => {
- expect(wrapper.vm.$emit).toHaveBeenCalledWith('onUpdate', 'pod', 'canary-pod');
- });
- });
-});
diff --git a/spec/frontend/monitoring/components/variables_section_spec.js b/spec/frontend/monitoring/components/variables_section_spec.js
index fd814e81c8f..3097906ee68 100644
--- a/spec/frontend/monitoring/components/variables_section_spec.js
+++ b/spec/frontend/monitoring/components/variables_section_spec.js
@@ -1,13 +1,12 @@
import { shallowMount } from '@vue/test-utils';
import Vuex from 'vuex';
import VariablesSection from '~/monitoring/components/variables_section.vue';
-import CustomVariable from '~/monitoring/components/variables/custom_variable.vue';
-import TextVariable from '~/monitoring/components/variables/text_variable.vue';
+import DropdownField from '~/monitoring/components/variables/dropdown_field.vue';
+import TextField from '~/monitoring/components/variables/text_field.vue';
import { updateHistory, mergeUrlParams } from '~/lib/utils/url_utility';
import { createStore } from '~/monitoring/stores';
import { convertVariablesForURL } from '~/monitoring/utils';
-import * as types from '~/monitoring/stores/mutation_types';
-import { mockTemplatingDataResponses } from '../mock_data';
+import { storeVariables } from '../mock_data';
jest.mock('~/lib/utils/url_utility', () => ({
updateHistory: jest.fn(),
@@ -17,11 +16,6 @@ jest.mock('~/lib/utils/url_utility', () => ({
describe('Metrics dashboard/variables section component', () => {
let store;
let wrapper;
- const sampleVariables = {
- label1: mockTemplatingDataResponses.simpleText.simpleText,
- label2: mockTemplatingDataResponses.advText.advText,
- label3: mockTemplatingDataResponses.simpleCustom.simpleCustom,
- };
const createShallowWrapper = () => {
wrapper = shallowMount(VariablesSection, {
@@ -29,30 +23,41 @@ describe('Metrics dashboard/variables section component', () => {
});
};
- const findTextInput = () => wrapper.findAll(TextVariable);
- const findCustomInput = () => wrapper.findAll(CustomVariable);
+ const findTextInputs = () => wrapper.findAll(TextField);
+ const findCustomInputs = () => wrapper.findAll(DropdownField);
beforeEach(() => {
store = createStore();
- store.state.monitoringDashboard.showEmptyState = false;
+ store.state.monitoringDashboard.emptyState = null;
});
it('does not show the variables section', () => {
createShallowWrapper();
- const allInputs = findTextInput().length + findCustomInput().length;
+ const allInputs = findTextInputs().length + findCustomInputs().length;
expect(allInputs).toBe(0);
});
- it('shows the variables section', () => {
- createShallowWrapper();
- store.commit(`monitoringDashboard/${types.SET_VARIABLES}`, sampleVariables);
+ describe('when variables are set', () => {
+ beforeEach(() => {
+ store.state.monitoringDashboard.variables = storeVariables;
+ createShallowWrapper();
+
+ return wrapper.vm.$nextTick;
+ });
+
+ it('shows the variables section', () => {
+ const allInputs = findTextInputs().length + findCustomInputs().length;
+
+ expect(allInputs).toBe(storeVariables.length);
+ });
- return wrapper.vm.$nextTick(() => {
- const allInputs = findTextInput().length + findCustomInput().length;
+ it('shows the right custom variable inputs', () => {
+ const customInputs = findCustomInputs();
- expect(allInputs).toBe(Object.keys(sampleVariables).length);
+ expect(customInputs.at(0).props('name')).toBe('customSimple');
+ expect(customInputs.at(1).props('name')).toBe('customAdvanced');
});
});
@@ -65,8 +70,8 @@ describe('Metrics dashboard/variables section component', () => {
monitoringDashboard: {
namespaced: true,
state: {
- showEmptyState: false,
- variables: sampleVariables,
+ emptyState: null,
+ variables: storeVariables,
},
actions: {
updateVariablesAndFetchData,
@@ -79,14 +84,14 @@ describe('Metrics dashboard/variables section component', () => {
});
it('merges the url params and refreshes the dashboard when a text-based variables inputs are updated', () => {
- const firstInput = findTextInput().at(0);
+ const firstInput = findTextInputs().at(0);
- firstInput.vm.$emit('onUpdate', 'label1', 'test');
+ firstInput.vm.$emit('input', 'test');
return wrapper.vm.$nextTick(() => {
expect(updateVariablesAndFetchData).toHaveBeenCalled();
expect(mergeUrlParams).toHaveBeenCalledWith(
- convertVariablesForURL(sampleVariables),
+ convertVariablesForURL(storeVariables),
window.location.href,
);
expect(updateHistory).toHaveBeenCalled();
@@ -94,14 +99,14 @@ describe('Metrics dashboard/variables section component', () => {
});
it('merges the url params and refreshes the dashboard when a custom-based variables inputs are updated', () => {
- const firstInput = findCustomInput().at(0);
+ const firstInput = findCustomInputs().at(0);
- firstInput.vm.$emit('onUpdate', 'label1', 'test');
+ firstInput.vm.$emit('input', 'test');
return wrapper.vm.$nextTick(() => {
expect(updateVariablesAndFetchData).toHaveBeenCalled();
expect(mergeUrlParams).toHaveBeenCalledWith(
- convertVariablesForURL(sampleVariables),
+ convertVariablesForURL(storeVariables),
window.location.href,
);
expect(updateHistory).toHaveBeenCalled();
@@ -109,9 +114,9 @@ describe('Metrics dashboard/variables section component', () => {
});
it('does not merge the url params and refreshes the dashboard if the value entered is not different that is what currently stored', () => {
- const firstInput = findTextInput().at(0);
+ const firstInput = findTextInputs().at(0);
- firstInput.vm.$emit('onUpdate', 'label1', 'Simple text');
+ firstInput.vm.$emit('input', 'My default value');
expect(updateVariablesAndFetchData).not.toHaveBeenCalled();
expect(mergeUrlParams).not.toHaveBeenCalled();
diff --git a/spec/frontend/monitoring/fixture_data.js b/spec/frontend/monitoring/fixture_data.js
index b7b72a15992..97edf7bda74 100644
--- a/spec/frontend/monitoring/fixture_data.js
+++ b/spec/frontend/monitoring/fixture_data.js
@@ -1,5 +1,8 @@
+import { stateAndPropsFromDataset } from '~/monitoring/utils';
import { mapToDashboardViewModel } from '~/monitoring/stores/utils';
import { metricStates } from '~/monitoring/constants';
+import { convertObjectProps } from '~/lib/utils/common_utils';
+import { convertToCamelCase } from '~/lib/utils/text_utility';
import { metricsResult } from './mock_data';
@@ -7,23 +10,54 @@ import { metricsResult } from './mock_data';
export const metricsDashboardResponse = getJSONFixture(
'metrics_dashboard/environment_metrics_dashboard.json',
);
+
export const metricsDashboardPayload = metricsDashboardResponse.dashboard;
+
+const datasetState = stateAndPropsFromDataset(
+ // It's preferable to have props in snake_case, this will be addressed at:
+ // https://gitlab.com/gitlab-org/gitlab/-/merge_requests/33574
+ convertObjectProps(
+ // Some props use kebab-case, convert to snake_case first
+ key => convertToCamelCase(key.replace(/-/g, '_')),
+ metricsDashboardResponse.metrics_data,
+ ),
+);
+
+// new properties like addDashboardDocumentationPath prop and alertsEndpoint
+// was recently added to dashboard.vue component this needs to be
+// added to fixtures data
+// https://gitlab.com/gitlab-org/gitlab/-/issues/229256
+export const dashboardProps = {
+ ...datasetState.dataProps,
+ addDashboardDocumentationPath: 'https://path/to/docs',
+ alertsEndpoint: null,
+};
+
export const metricsDashboardViewModel = mapToDashboardViewModel(metricsDashboardPayload);
export const metricsDashboardPanelCount = 22;
export const metricResultStatus = {
// First metric in fixture `metrics_dashboard/environment_metrics_dashboard.json`
metricId: 'NO_DB_response_metrics_nginx_ingress_throughput_status_code',
- result: metricsResult,
+ data: {
+ resultType: 'matrix',
+ result: metricsResult,
+ },
};
export const metricResultPods = {
// Second metric in fixture `metrics_dashboard/environment_metrics_dashboard.json`
metricId: 'NO_DB_response_metrics_nginx_ingress_latency_pod_average',
- result: metricsResult,
+ data: {
+ resultType: 'matrix',
+ result: metricsResult,
+ },
};
export const metricResultEmpty = {
metricId: 'NO_DB_response_metrics_nginx_ingress_16_throughput_status_code',
- result: [],
+ data: {
+ resultType: 'matrix',
+ result: [],
+ },
};
// Graph data
diff --git a/spec/frontend/monitoring/graph_data.js b/spec/frontend/monitoring/graph_data.js
new file mode 100644
index 00000000000..e1b95723f3d
--- /dev/null
+++ b/spec/frontend/monitoring/graph_data.js
@@ -0,0 +1,164 @@
+import { mapPanelToViewModel, normalizeQueryResponseData } from '~/monitoring/stores/utils';
+import { panelTypes, metricStates } from '~/monitoring/constants';
+
+const initTime = 1435781451.781;
+
+const makeValue = val => [initTime, val];
+const makeValues = vals => vals.map((val, i) => [initTime + 15 * i, val]);
+
+// Normalized Prometheus Responses
+
+const scalarResult = ({ value = '1' } = {}) =>
+ normalizeQueryResponseData({
+ resultType: 'scalar',
+ result: makeValue(value),
+ });
+
+const vectorResult = ({ value1 = '1', value2 = '2' } = {}) =>
+ normalizeQueryResponseData({
+ resultType: 'vector',
+ result: [
+ {
+ metric: {
+ __name__: 'up',
+ job: 'prometheus',
+ instance: 'localhost:9090',
+ },
+ value: makeValue(value1),
+ },
+ {
+ metric: {
+ __name__: 'up',
+ job: 'node',
+ instance: 'localhost:9100',
+ },
+ value: makeValue(value2),
+ },
+ ],
+ });
+
+const matrixSingleResult = ({ values = ['1', '2', '3'] } = {}) =>
+ normalizeQueryResponseData({
+ resultType: 'matrix',
+ result: [
+ {
+ metric: {},
+ values: makeValues(values),
+ },
+ ],
+ });
+
+const matrixMultiResult = ({ values1 = ['1', '2', '3'], values2 = ['4', '5', '6'] } = {}) =>
+ normalizeQueryResponseData({
+ resultType: 'matrix',
+ result: [
+ {
+ metric: {
+ __name__: 'up',
+ job: 'prometheus',
+ instance: 'localhost:9090',
+ },
+ values: makeValues(values1),
+ },
+ {
+ metric: {
+ __name__: 'up',
+ job: 'node',
+ instance: 'localhost:9091',
+ },
+ values: makeValues(values2),
+ },
+ ],
+ });
+
+// GraphData factory
+
+/**
+ * Generate mock graph data according to options
+ *
+ * @param {Object} panelOptions - Panel options as in YML.
+ * @param {Object} dataOptions
+ * @param {Object} dataOptions.metricCount
+ * @param {Object} dataOptions.isMultiSeries
+ */
+export const timeSeriesGraphData = (panelOptions = {}, dataOptions = {}) => {
+ const { metricCount = 1, isMultiSeries = false } = dataOptions;
+
+ return mapPanelToViewModel({
+ title: 'Time Series Panel',
+ type: panelTypes.LINE_CHART,
+ x_label: 'X Axis',
+ y_label: 'Y Axis',
+ metrics: Array.from(Array(metricCount), (_, i) => ({
+ label: `Metric ${i + 1}`,
+ state: metricStates.OK,
+ result: isMultiSeries ? matrixMultiResult() : matrixSingleResult(),
+ })),
+ ...panelOptions,
+ });
+};
+
+/**
+ * Generate mock graph data according to options
+ *
+ * @param {Object} panelOptions - Panel options as in YML.
+ * @param {Object} dataOptions
+ * @param {Object} dataOptions.unit
+ * @param {Object} dataOptions.value
+ * @param {Object} dataOptions.isVector
+ */
+export const singleStatGraphData = (panelOptions = {}, dataOptions = {}) => {
+ const { unit, value = '1', isVector = false } = dataOptions;
+
+ return mapPanelToViewModel({
+ title: 'Single Stat Panel',
+ type: panelTypes.SINGLE_STAT,
+ metrics: [
+ {
+ label: 'Metric Label',
+ state: metricStates.OK,
+ result: isVector ? vectorResult({ value }) : scalarResult({ value }),
+ unit,
+ },
+ ],
+ ...panelOptions,
+ });
+};
+
+/**
+ * Generate mock graph data according to options
+ *
+ * @param {Object} panelOptions - Panel options as in YML.
+ * @param {Object} dataOptions
+ * @param {Array} dataOptions.values - Metric values
+ * @param {Array} dataOptions.upper - Upper boundary values
+ * @param {Array} dataOptions.lower - Lower boundary values
+ */
+export const anomalyGraphData = (panelOptions = {}, dataOptions = {}) => {
+ const { values, upper, lower } = dataOptions;
+
+ return mapPanelToViewModel({
+ title: 'Anomaly Panel',
+ type: panelTypes.ANOMALY_CHART,
+ x_label: 'X Axis',
+ y_label: 'Y Axis',
+ metrics: [
+ {
+ label: `Metric`,
+ state: metricStates.OK,
+ result: matrixSingleResult({ values }),
+ },
+ {
+ label: `Upper boundary`,
+ state: metricStates.OK,
+ result: matrixSingleResult({ values: upper }),
+ },
+ {
+ label: `Lower boundary`,
+ state: metricStates.OK,
+ result: matrixSingleResult({ values: lower }),
+ },
+ ],
+ ...panelOptions,
+ });
+};
diff --git a/spec/frontend/monitoring/mock_data.js b/spec/frontend/monitoring/mock_data.js
index 05b29e78ecd..49ad33402c6 100644
--- a/spec/frontend/monitoring/mock_data.js
+++ b/spec/frontend/monitoring/mock_data.js
@@ -5,28 +5,14 @@ import { TEST_HOST } from '../helpers/test_constants';
export const mockProjectDir = '/frontend-fixtures/environments-project';
export const mockApiEndpoint = `${TEST_HOST}/monitoring/mock`;
-export const propsData = {
- hasMetrics: false,
- documentationPath: '/path/to/docs',
- settingsPath: '/path/to/settings',
- clustersPath: '/path/to/clusters',
- tagsPath: '/path/to/tags',
- defaultBranch: 'master',
- emptyGettingStartedSvgPath: '/path/to/getting-started.svg',
- emptyLoadingSvgPath: '/path/to/loading.svg',
- emptyNoDataSvgPath: '/path/to/no-data.svg',
- emptyNoDataSmallSvgPath: '/path/to/no-data-small.svg',
- emptyUnableToConnectSvgPath: '/path/to/unable-to-connect.svg',
- customMetricsAvailable: false,
- customMetricsPath: '',
- validateQueryPath: '',
-};
+export const customDashboardBasePath = '.gitlab/dashboards';
const customDashboardsData = new Array(30).fill(null).map((_, idx) => ({
default: false,
display_name: `Custom Dashboard ${idx}`,
can_edit: true,
system_dashboard: false,
+ out_of_the_box_dashboard: false,
project_blob_path: `${mockProjectDir}/blob/master/dashboards/.gitlab/dashboards/dashboard_${idx}.yml`,
path: `.gitlab/dashboards/dashboard_${idx}.yml`,
starred: false,
@@ -65,136 +51,6 @@ export const anomalyDeploymentData = [
},
];
-export const anomalyMockResultValues = {
- noAnomaly: [
- [
- ['2019-08-19T19:00:00.000Z', 1.25],
- ['2019-08-19T20:00:00.000Z', 1.45],
- ['2019-08-19T21:00:00.000Z', 1.55],
- ['2019-08-19T22:00:00.000Z', 1.48],
- ],
- [
- // upper boundary
- ['2019-08-19T19:00:00.000Z', 2],
- ['2019-08-19T20:00:00.000Z', 2.55],
- ['2019-08-19T21:00:00.000Z', 2.65],
- ['2019-08-19T22:00:00.000Z', 3.0],
- ],
- [
- // lower boundary
- ['2019-08-19T19:00:00.000Z', 0.45],
- ['2019-08-19T20:00:00.000Z', 0.65],
- ['2019-08-19T21:00:00.000Z', 0.7],
- ['2019-08-19T22:00:00.000Z', 0.8],
- ],
- ],
- noBoundary: [
- [
- ['2019-08-19T19:00:00.000Z', 1.25],
- ['2019-08-19T20:00:00.000Z', 1.45],
- ['2019-08-19T21:00:00.000Z', 1.55],
- ['2019-08-19T22:00:00.000Z', 1.48],
- ],
- [
- // empty upper boundary
- ],
- [
- // empty lower boundary
- ],
- ],
- oneAnomaly: [
- [
- ['2019-08-19T19:00:00.000Z', 1.25],
- ['2019-08-19T20:00:00.000Z', 3.45], // anomaly
- ['2019-08-19T21:00:00.000Z', 1.55],
- ],
- [
- // upper boundary
- ['2019-08-19T19:00:00.000Z', 2],
- ['2019-08-19T20:00:00.000Z', 2.55],
- ['2019-08-19T21:00:00.000Z', 2.65],
- ],
- [
- // lower boundary
- ['2019-08-19T19:00:00.000Z', 0.45],
- ['2019-08-19T20:00:00.000Z', 0.65],
- ['2019-08-19T21:00:00.000Z', 0.7],
- ],
- ],
- negativeBoundary: [
- [
- ['2019-08-19T19:00:00.000Z', 1.25],
- ['2019-08-19T20:00:00.000Z', 3.45], // anomaly
- ['2019-08-19T21:00:00.000Z', 1.55],
- ],
- [
- // upper boundary
- ['2019-08-19T19:00:00.000Z', 2],
- ['2019-08-19T20:00:00.000Z', 2.55],
- ['2019-08-19T21:00:00.000Z', 2.65],
- ],
- [
- // lower boundary
- ['2019-08-19T19:00:00.000Z', -1.25],
- ['2019-08-19T20:00:00.000Z', -2.65],
- ['2019-08-19T21:00:00.000Z', -3.7], // lowest point
- ],
- ],
-};
-
-export const anomalyMockGraphData = {
- title: 'Requests Per Second Mock Data',
- type: 'anomaly-chart',
- weight: 3,
- metrics: [
- {
- metricId: '90',
- id: 'metric',
- query_range: 'MOCK_PROMETHEUS_METRIC_QUERY_RANGE',
- unit: 'RPS',
- label: 'Metrics RPS',
- metric_id: 90,
- prometheus_endpoint_path: 'MOCK_METRIC_PEP',
- result: [
- {
- metric: {},
- values: [['2019-08-19T19:00:00.000Z', 0]],
- },
- ],
- },
- {
- metricId: '91',
- id: 'upper',
- query_range: '...',
- unit: 'RPS',
- label: 'Upper Limit Metrics RPS',
- metric_id: 91,
- prometheus_endpoint_path: 'MOCK_UPPER_PEP',
- result: [
- {
- metric: {},
- values: [['2019-08-19T19:00:00.000Z', 0]],
- },
- ],
- },
- {
- metricId: '92',
- id: 'lower',
- query_range: '...',
- unit: 'RPS',
- label: 'Lower Limit Metrics RPS',
- metric_id: 92,
- prometheus_endpoint_path: 'MOCK_LOWER_PEP',
- result: [
- {
- metric: {},
- values: [['2019-08-19T19:00:00.000Z', 0]],
- },
- ],
- },
- ],
-};
-
export const deploymentData = [
{
id: 111,
@@ -317,6 +173,7 @@ export const dashboardGitResponse = [
display_name: 'Default',
can_edit: false,
system_dashboard: true,
+ out_of_the_box_dashboard: true,
project_blob_path: null,
path: 'config/prometheus/common_metrics.yml',
starred: false,
@@ -327,6 +184,44 @@ export const dashboardGitResponse = [
display_name: 'dashboard.yml',
can_edit: true,
system_dashboard: false,
+ out_of_the_box_dashboard: false,
+ project_blob_path: `${mockProjectDir}/-/blob/master/.gitlab/dashboards/dashboard.yml`,
+ path: '.gitlab/dashboards/dashboard.yml',
+ starred: true,
+ user_starred_path: `${mockProjectDir}/metrics_user_starred_dashboards?dashboard_path=.gitlab/dashboards/dashboard.yml`,
+ },
+ {
+ default: false,
+ display_name: 'Pod Health',
+ can_edit: false,
+ system_dashboard: false,
+ out_of_the_box_dashboard: true,
+ project_blob_path: null,
+ path: 'config/prometheus/pod_metrics.yml',
+ starred: false,
+ user_starred_path: `${mockProjectDir}/metrics_user_starred_dashboards?dashboard_path=config/prometheus/pod_metrics.yml`,
+ },
+ ...customDashboardsData,
+];
+
+export const selfMonitoringDashboardGitResponse = [
+ {
+ default: true,
+ display_name: 'Default',
+ can_edit: false,
+ system_dashboard: false,
+ out_of_the_box_dashboard: true,
+ project_blob_path: null,
+ path: 'config/prometheus/self_monitoring_default.yml',
+ starred: false,
+ user_starred_path: `${mockProjectDir}/metrics_user_starred_dashboards?dashboard_path=config/prometheus/self_monitoring_default.yml`,
+ },
+ {
+ default: false,
+ display_name: 'dashboard.yml',
+ can_edit: true,
+ system_dashboard: false,
+ out_of_the_box_dashboard: false,
project_blob_path: `${mockProjectDir}/-/blob/master/.gitlab/dashboards/dashboard.yml`,
path: '.gitlab/dashboards/dashboard.yml',
starred: true,
@@ -349,30 +244,6 @@ export const metricsResult = [
},
];
-export const singleStatMetricsResult = {
- title: 'Super Chart A2',
- type: 'single-stat',
- weight: 2,
- metrics: [
- {
- id: 'metric_a1',
- metricId: '2',
- query: 'max(go_memstats_alloc_bytes{job="prometheus"}) by (job) /1024/1024',
- unit: 'MB',
- label: 'Total Consumption',
- metric_id: 2,
- prometheus_endpoint_path:
- '/root/kubernetes-gke-project/environments/35/prometheus/api/v1/query?query=max%28go_memstats_alloc_bytes%7Bjob%3D%22prometheus%22%7D%29+by+%28job%29+%2F1024%2F1024',
- result: [
- {
- metric: { job: 'prometheus' },
- value: ['2019-06-26T21:03:20.881Z', 91],
- },
- ],
- },
- ],
-};
-
export const graphDataPrometheusQueryRangeMultiTrack = {
title: 'Super Chart A3',
type: 'heatmap',
@@ -641,253 +512,186 @@ export const mockLinks = [
},
];
-const templatingVariableTypes = {
+export const templatingVariablesExamples = {
text: {
- simple: 'Simple text',
- advanced: {
- label: 'Variable 4',
+ textSimple: 'My default value',
+ textAdvanced: {
+ label: 'Advanced text variable',
type: 'text',
options: {
- default_value: 'default',
+ default_value: 'A default value',
},
},
},
custom: {
- simple: ['value1', 'value2', 'value3'],
- advanced: {
- normal: {
- label: 'Advanced Var',
- type: 'custom',
- options: {
- values: [
- { value: 'value1', text: 'Var 1 Option 1' },
- {
- value: 'value2',
- text: 'Var 1 Option 2',
- default: true,
- },
- ],
- },
- },
- withoutOpts: {
- type: 'custom',
- options: {},
+ customSimple: ['value1', 'value2', 'value3'],
+ customAdvanced: {
+ label: 'Advanced Var',
+ type: 'custom',
+ options: {
+ values: [
+ { value: 'value1', text: 'Var 1 Option 1' },
+ {
+ value: 'value2',
+ text: 'Var 1 Option 2',
+ default: true,
+ },
+ ],
},
- withoutLabel: {
- type: 'custom',
- options: {
- values: [
- { value: 'value1', text: 'Var 1 Option 1' },
- {
- value: 'value2',
- text: 'Var 1 Option 2',
- default: true,
- },
- ],
- },
+ },
+ customAdvancedWithoutOpts: {
+ type: 'custom',
+ options: {},
+ },
+ customAdvancedWithoutLabel: {
+ type: 'custom',
+ options: {
+ values: [
+ { value: 'value1', text: 'Var 1 Option 1' },
+ {
+ value: 'value2',
+ text: 'Var 1 Option 2',
+ default: true,
+ },
+ ],
},
- withoutType: {
- label: 'Variable 2',
- options: {
- values: [
- { value: 'value1', text: 'Var 1 Option 1' },
- {
- value: 'value2',
- text: 'Var 1 Option 2',
- default: true,
- },
- ],
- },
+ },
+ customAdvancedWithoutType: {
+ label: 'Variable 2',
+ options: {
+ values: [
+ { value: 'value1', text: 'Var 1 Option 1' },
+ {
+ value: 'value2',
+ text: 'Var 1 Option 2',
+ default: true,
+ },
+ ],
},
- withoutOptText: {
- label: 'Options without text',
- type: 'custom',
- options: {
- values: [
- { value: 'value1' },
- {
- value: 'value2',
- default: true,
- },
- ],
- },
+ },
+ customAdvancedWithoutOptText: {
+ label: 'Options without text',
+ type: 'custom',
+ options: {
+ values: [
+ { value: 'value1' },
+ {
+ value: 'value2',
+ default: true,
+ },
+ ],
},
},
},
-};
-
-const generateMockTemplatingData = data => {
- const vars = data
- ? {
- variables: {
- ...data,
- },
- }
- : {};
- return {
- dashboard: {
- templating: vars,
+ metricLabelValues: {
+ metricLabelValuesSimple: {
+ label: 'Metric Label Values',
+ type: 'metric_label_values',
+ options: {
+ prometheus_endpoint_path: '/series',
+ series_selector: 'backend:haproxy_backend_availability:ratio{env="{{env}}"}',
+ label: 'backend',
+ },
},
- };
+ },
};
-const responseForSimpleTextVariable = {
- simpleText: {
- label: 'simpleText',
+export const storeTextVariables = [
+ {
type: 'text',
- value: 'Simple text',
+ name: 'textSimple',
+ label: 'textSimple',
+ value: 'My default value',
},
-};
-
-const responseForAdvTextVariable = {
- advText: {
- label: 'Variable 4',
+ {
type: 'text',
- value: 'default',
+ name: 'textAdvanced',
+ label: 'Advanced text variable',
+ value: 'A default value',
},
-};
+];
-const responseForSimpleCustomVariable = {
- simpleCustom: {
- label: 'simpleCustom',
+export const storeCustomVariables = [
+ {
+ type: 'custom',
+ name: 'customSimple',
+ label: 'customSimple',
+ options: {
+ values: [
+ { default: false, text: 'value1', value: 'value1' },
+ { default: false, text: 'value2', value: 'value2' },
+ { default: false, text: 'value3', value: 'value3' },
+ ],
+ },
value: 'value1',
- options: [
- {
- default: false,
- text: 'value1',
- value: 'value1',
- },
- {
- default: false,
- text: 'value2',
- value: 'value2',
- },
- {
- default: false,
- text: 'value3',
- value: 'value3',
- },
- ],
+ },
+ {
type: 'custom',
+ name: 'customAdvanced',
+ label: 'Advanced Var',
+ options: {
+ values: [
+ { default: false, text: 'Var 1 Option 1', value: 'value1' },
+ { default: true, text: 'Var 1 Option 2', value: 'value2' },
+ ],
+ },
+ value: 'value2',
},
-};
-
-const responseForAdvancedCustomVariableWithoutOptions = {
- advCustomWithoutOpts: {
- label: 'advCustomWithoutOpts',
- options: [],
+ {
type: 'custom',
+ name: 'customAdvancedWithoutOpts',
+ label: 'customAdvancedWithoutOpts',
+ options: { values: [] },
+ value: null,
},
-};
-
-const responseForAdvancedCustomVariableWithoutLabel = {
- advCustomWithoutLabel: {
- label: 'advCustomWithoutLabel',
- value: 'value2',
- options: [
- {
- default: false,
- text: 'Var 1 Option 1',
- value: 'value1',
- },
- {
- default: true,
- text: 'Var 1 Option 2',
- value: 'value2',
- },
- ],
+ {
type: 'custom',
+ name: 'customAdvancedWithoutLabel',
+ label: 'customAdvancedWithoutLabel',
+ value: 'value2',
+ options: {
+ values: [
+ { default: false, text: 'Var 1 Option 1', value: 'value1' },
+ { default: true, text: 'Var 1 Option 2', value: 'value2' },
+ ],
+ },
},
-};
-
-const responseForAdvancedCustomVariableWithoutOptText = {
- advCustomWithoutOptText: {
+ {
+ type: 'custom',
+ name: 'customAdvancedWithoutOptText',
label: 'Options without text',
+ options: {
+ values: [
+ { default: false, text: 'value1', value: 'value1' },
+ { default: true, text: 'value2', value: 'value2' },
+ ],
+ },
value: 'value2',
- options: [
- {
- default: false,
- text: 'value1',
- value: 'value1',
- },
- {
- default: true,
- text: 'value2',
- value: 'value2',
- },
- ],
- type: 'custom',
},
-};
+];
-const responseForAdvancedCustomVariable = {
- ...responseForSimpleCustomVariable,
- advCustomNormal: {
- label: 'Advanced Var',
- value: 'value2',
- options: [
- {
- default: false,
- text: 'Var 1 Option 1',
- value: 'value1',
- },
- {
- default: true,
- text: 'Var 1 Option 2',
- value: 'value2',
- },
- ],
- type: 'custom',
+export const storeMetricLabelValuesVariables = [
+ {
+ type: 'metric_label_values',
+ name: 'metricLabelValuesSimple',
+ label: 'Metric Label Values',
+ options: { prometheusEndpointPath: '/series', label: 'backend', values: [] },
+ value: null,
},
-};
-
-const responsesForAllVariableTypes = {
- ...responseForSimpleTextVariable,
- ...responseForAdvTextVariable,
- ...responseForSimpleCustomVariable,
- ...responseForAdvancedCustomVariable,
-};
+];
-export const mockTemplatingData = {
- emptyTemplatingProp: generateMockTemplatingData(),
- emptyVariablesProp: generateMockTemplatingData({}),
- simpleText: generateMockTemplatingData({ simpleText: templatingVariableTypes.text.simple }),
- advText: generateMockTemplatingData({ advText: templatingVariableTypes.text.advanced }),
- simpleCustom: generateMockTemplatingData({ simpleCustom: templatingVariableTypes.custom.simple }),
- advCustomWithoutOpts: generateMockTemplatingData({
- advCustomWithoutOpts: templatingVariableTypes.custom.advanced.withoutOpts,
- }),
- advCustomWithoutType: generateMockTemplatingData({
- advCustomWithoutType: templatingVariableTypes.custom.advanced.withoutType,
- }),
- advCustomWithoutLabel: generateMockTemplatingData({
- advCustomWithoutLabel: templatingVariableTypes.custom.advanced.withoutLabel,
- }),
- advCustomWithoutOptText: generateMockTemplatingData({
- advCustomWithoutOptText: templatingVariableTypes.custom.advanced.withoutOptText,
- }),
- simpleAndAdv: generateMockTemplatingData({
- simpleCustom: templatingVariableTypes.custom.simple,
- advCustomNormal: templatingVariableTypes.custom.advanced.normal,
- }),
- allVariableTypes: generateMockTemplatingData({
- simpleText: templatingVariableTypes.text.simple,
- advText: templatingVariableTypes.text.advanced,
- simpleCustom: templatingVariableTypes.custom.simple,
- advCustomNormal: templatingVariableTypes.custom.advanced.normal,
- }),
-};
+export const storeVariables = [
+ ...storeTextVariables,
+ ...storeCustomVariables,
+ ...storeMetricLabelValuesVariables,
+];
-export const mockTemplatingDataResponses = {
- emptyTemplatingProp: {},
- emptyVariablesProp: {},
- simpleText: responseForSimpleTextVariable,
- advText: responseForAdvTextVariable,
- simpleCustom: responseForSimpleCustomVariable,
- advCustomWithoutOpts: responseForAdvancedCustomVariableWithoutOptions,
- advCustomWithoutType: {},
- advCustomWithoutLabel: responseForAdvancedCustomVariableWithoutLabel,
- advCustomWithoutOptText: responseForAdvancedCustomVariableWithoutOptText,
- simpleAndAdv: responseForAdvancedCustomVariable,
- allVariableTypes: responsesForAllVariableTypes,
+export const dashboardHeaderProps = {
+ defaultBranch: 'master',
+ addDashboardDocumentationPath: 'https://path/to/docs',
+ isRearrangingPanels: false,
+ selectedTimeRange: {
+ start: '2020-01-01T00:00:00.000Z',
+ end: '2020-01-01T01:00:00.000Z',
+ },
};
diff --git a/spec/frontend/monitoring/pages/dashboard_page_spec.js b/spec/frontend/monitoring/pages/dashboard_page_spec.js
index e3c56ef4cbf..675165e9e56 100644
--- a/spec/frontend/monitoring/pages/dashboard_page_spec.js
+++ b/spec/frontend/monitoring/pages/dashboard_page_spec.js
@@ -1,21 +1,42 @@
import { shallowMount } from '@vue/test-utils';
+import { createStore } from '~/monitoring/stores';
import DashboardPage from '~/monitoring/pages/dashboard_page.vue';
import Dashboard from '~/monitoring/components/dashboard.vue';
-import { propsData } from '../mock_data';
+import { dashboardProps } from '../fixture_data';
describe('monitoring/pages/dashboard_page', () => {
let wrapper;
+ let store;
+ let $route;
+
+ const buildRouter = () => {
+ const dashboard = {};
+ $route = {
+ params: { dashboard },
+ query: { dashboard },
+ };
+ };
const buildWrapper = (props = {}) => {
wrapper = shallowMount(DashboardPage, {
+ store,
propsData: {
...props,
},
+ mocks: {
+ $route,
+ },
});
};
const findDashboardComponent = () => wrapper.find(Dashboard);
+ beforeEach(() => {
+ buildRouter();
+ store = createStore();
+ jest.spyOn(store, 'dispatch').mockResolvedValue();
+ });
+
afterEach(() => {
if (wrapper) {
wrapper.destroy();
@@ -28,9 +49,18 @@ describe('monitoring/pages/dashboard_page', () => {
});
it('renders the dashboard page with dashboard component', () => {
- buildWrapper({ dashboardProps: propsData });
+ buildWrapper({ dashboardProps });
+
+ const allProps = {
+ ...dashboardProps,
+ // default props values
+ rearrangePanelsAvailable: false,
+ showHeader: true,
+ showPanels: true,
+ smallEmptyState: false,
+ };
- expect(findDashboardComponent().props()).toMatchObject(propsData);
expect(findDashboardComponent()).toExist();
+ expect(allProps).toMatchObject(findDashboardComponent().props());
});
});
diff --git a/spec/frontend/monitoring/router_spec.js b/spec/frontend/monitoring/router_spec.js
new file mode 100644
index 00000000000..5b8f4b3c83e
--- /dev/null
+++ b/spec/frontend/monitoring/router_spec.js
@@ -0,0 +1,81 @@
+import { mount, createLocalVue } from '@vue/test-utils';
+import VueRouter from 'vue-router';
+import DashboardPage from '~/monitoring/pages/dashboard_page.vue';
+import Dashboard from '~/monitoring/components/dashboard.vue';
+import { createStore } from '~/monitoring/stores';
+import createRouter from '~/monitoring/router';
+import { dashboardProps } from './fixture_data';
+import { dashboardHeaderProps } from './mock_data';
+
+describe('Monitoring router', () => {
+ let router;
+ let store;
+ const propsData = { dashboardProps: { ...dashboardProps, ...dashboardHeaderProps } };
+ const NEW_BASE_PATH = '/project/my-group/test-project/-/metrics';
+ const OLD_BASE_PATH = '/project/my-group/test-project/-/environments/71146/metrics';
+
+ const createWrapper = (basePath, routeArg) => {
+ const localVue = createLocalVue();
+ localVue.use(VueRouter);
+
+ router = createRouter(basePath);
+ if (routeArg !== undefined) {
+ router.push(routeArg);
+ }
+
+ return mount(DashboardPage, {
+ localVue,
+ store,
+ router,
+ propsData,
+ });
+ };
+
+ beforeEach(() => {
+ store = createStore();
+ jest.spyOn(store, 'dispatch').mockResolvedValue();
+ });
+
+ afterEach(() => {
+ window.location.hash = '';
+ });
+
+ describe('support old URL with full dashboard path', () => {
+ it.each`
+ route | currentDashboard
+ ${'/dashboard.yml'} | ${'dashboard.yml'}
+ ${'/folder1/dashboard.yml'} | ${'folder1/dashboard.yml'}
+ ${'/?dashboard=dashboard.yml'} | ${'dashboard.yml'}
+ `('sets component as $componentName for path "$route"', ({ route, currentDashboard }) => {
+ const wrapper = createWrapper(OLD_BASE_PATH, route);
+
+ expect(store.dispatch).toHaveBeenCalledWith('monitoringDashboard/setCurrentDashboard', {
+ currentDashboard,
+ });
+
+ expect(wrapper.find(Dashboard)).toExist();
+ });
+ });
+
+ describe('supports new URL with short dashboard path', () => {
+ it.each`
+ route | currentDashboard
+ ${'/'} | ${null}
+ ${'/dashboard.yml'} | ${'dashboard.yml'}
+ ${'/folder1/dashboard.yml'} | ${'folder1/dashboard.yml'}
+ ${'/folder1%2Fdashboard.yml'} | ${'folder1/dashboard.yml'}
+ ${'/dashboard.yml'} | ${'dashboard.yml'}
+ ${'/config/prometheus/common_metrics.yml'} | ${'config/prometheus/common_metrics.yml'}
+ ${'/config/prometheus/pod_metrics.yml'} | ${'config/prometheus/pod_metrics.yml'}
+ ${'/config%2Fprometheus%2Fpod_metrics.yml'} | ${'config/prometheus/pod_metrics.yml'}
+ `('sets component as $componentName for path "$route"', ({ route, currentDashboard }) => {
+ const wrapper = createWrapper(NEW_BASE_PATH, route);
+
+ expect(store.dispatch).toHaveBeenCalledWith('monitoringDashboard/setCurrentDashboard', {
+ currentDashboard,
+ });
+
+ expect(wrapper.find(Dashboard)).toExist();
+ });
+ });
+});
diff --git a/spec/frontend/monitoring/store/actions_spec.js b/spec/frontend/monitoring/store/actions_spec.js
index d0290386f12..22f2b2e3c77 100644
--- a/spec/frontend/monitoring/store/actions_spec.js
+++ b/spec/frontend/monitoring/store/actions_spec.js
@@ -6,27 +6,30 @@ import statusCodes from '~/lib/utils/http_status';
import * as commonUtils from '~/lib/utils/common_utils';
import createFlash from '~/flash';
import { defaultTimeRange } from '~/vue_shared/constants';
+import * as getters from '~/monitoring/stores/getters';
import { ENVIRONMENT_AVAILABLE_STATE } from '~/monitoring/constants';
import { createStore } from '~/monitoring/stores';
import * as types from '~/monitoring/stores/mutation_types';
import {
+ setGettingStartedEmptyState,
+ setInitialState,
+ setExpandedPanel,
+ clearExpandedPanel,
+ filterEnvironments,
fetchData,
fetchDashboard,
receiveMetricsDashboardSuccess,
+ fetchDashboardData,
+ fetchPrometheusMetric,
fetchDeploymentsData,
fetchEnvironmentsData,
- fetchDashboardData,
fetchAnnotations,
+ fetchDashboardValidationWarnings,
toggleStarredValue,
- fetchPrometheusMetric,
- setInitialState,
- filterEnvironments,
- setExpandedPanel,
- clearExpandedPanel,
- setGettingStartedEmptyState,
duplicateSystemDashboard,
updateVariablesAndFetchData,
+ fetchVariableMetricLabelValues,
} from '~/monitoring/stores/actions';
import {
gqClient,
@@ -35,12 +38,12 @@ import {
} from '~/monitoring/stores/utils';
import getEnvironments from '~/monitoring/queries/getEnvironments.query.graphql';
import getAnnotations from '~/monitoring/queries/getAnnotations.query.graphql';
+import getDashboardValidationWarnings from '~/monitoring/queries/getDashboardValidationWarnings.query.graphql';
import storeState from '~/monitoring/stores/state';
import {
deploymentData,
environmentData,
annotationsData,
- mockTemplatingData,
dashboardGitResponse,
mockDashboardsErrorResponse,
} from '../mock_data';
@@ -59,11 +62,17 @@ describe('Monitoring store actions', () => {
let store;
let state;
+ let dispatch;
+ let commit;
+
beforeEach(() => {
- store = createStore();
+ store = createStore({ getters });
state = store.state.monitoringDashboard;
mock = new MockAdapter(axios);
+ commit = jest.fn();
+ dispatch = jest.fn();
+
jest.spyOn(commonUtils, 'backOff').mockImplementation(callback => {
const q = new Promise((resolve, reject) => {
const stop = arg => (arg instanceof Error ? reject(arg) : resolve(arg));
@@ -78,6 +87,7 @@ describe('Monitoring store actions', () => {
return q;
});
});
+
afterEach(() => {
mock.reset();
@@ -85,377 +95,122 @@ describe('Monitoring store actions', () => {
createFlash.mockReset();
});
- describe('fetchData', () => {
- it('dispatches fetchEnvironmentsData and fetchEnvironmentsData', () => {
- return testAction(
- fetchData,
- null,
- state,
- [],
- [
- { type: 'fetchEnvironmentsData' },
- { type: 'fetchDashboard' },
- { type: 'fetchAnnotations' },
- ],
- );
- });
+ // Setup
- it('dispatches when feature metricsDashboardAnnotations is on', () => {
- const origGon = window.gon;
- window.gon = { features: { metricsDashboardAnnotations: true } };
-
- return testAction(
- fetchData,
+ describe('setGettingStartedEmptyState', () => {
+ it('should commit SET_GETTING_STARTED_EMPTY_STATE mutation', done => {
+ testAction(
+ setGettingStartedEmptyState,
null,
state,
- [],
[
- { type: 'fetchEnvironmentsData' },
- { type: 'fetchDashboard' },
- { type: 'fetchAnnotations' },
+ {
+ type: types.SET_GETTING_STARTED_EMPTY_STATE,
+ },
],
- ).then(() => {
- window.gon = origGon;
- });
- });
- });
-
- describe('fetchDeploymentsData', () => {
- it('dispatches receiveDeploymentsDataSuccess on success', () => {
- state.deploymentsEndpoint = '/success';
- mock.onGet(state.deploymentsEndpoint).reply(200, {
- deployments: deploymentData,
- });
-
- return testAction(
- fetchDeploymentsData,
- null,
- state,
- [],
- [{ type: 'receiveDeploymentsDataSuccess', payload: deploymentData }],
- );
- });
- it('dispatches receiveDeploymentsDataFailure on error', () => {
- state.deploymentsEndpoint = '/error';
- mock.onGet(state.deploymentsEndpoint).reply(500);
-
- return testAction(
- fetchDeploymentsData,
- null,
- state,
[],
- [{ type: 'receiveDeploymentsDataFailure' }],
- () => {
- expect(createFlash).toHaveBeenCalled();
- },
+ done,
);
});
});
- describe('fetchEnvironmentsData', () => {
- beforeEach(() => {
- state.projectPath = 'gitlab-org/gitlab-test';
- });
-
- it('setting SET_ENVIRONMENTS_FILTER should dispatch fetchEnvironmentsData', () => {
- jest.spyOn(gqClient, 'mutate').mockReturnValue({
- data: {
- project: {
- data: {
- environments: [],
- },
- },
+ describe('setInitialState', () => {
+ it('should commit SET_INITIAL_STATE mutation', done => {
+ testAction(
+ setInitialState,
+ {
+ currentDashboard: '.gitlab/dashboards/dashboard.yml',
+ deploymentsEndpoint: 'deployments.json',
},
- });
-
- return testAction(
- filterEnvironments,
- {},
state,
[
{
- type: 'SET_ENVIRONMENTS_FILTER',
- payload: {},
- },
- ],
- [
- {
- type: 'fetchEnvironmentsData',
+ type: types.SET_INITIAL_STATE,
+ payload: {
+ currentDashboard: '.gitlab/dashboards/dashboard.yml',
+ deploymentsEndpoint: 'deployments.json',
+ },
},
],
- );
- });
-
- it('fetch environments data call takes in search param', () => {
- const mockMutate = jest.spyOn(gqClient, 'mutate');
- const searchTerm = 'Something';
- const mutationVariables = {
- mutation: getEnvironments,
- variables: {
- projectPath: state.projectPath,
- search: searchTerm,
- states: [ENVIRONMENT_AVAILABLE_STATE],
- },
- };
- state.environmentsSearchTerm = searchTerm;
- mockMutate.mockResolvedValue({});
-
- return testAction(
- fetchEnvironmentsData,
- null,
- state,
[],
- [
- { type: 'requestEnvironmentsData' },
- { type: 'receiveEnvironmentsDataSuccess', payload: [] },
- ],
- () => {
- expect(mockMutate).toHaveBeenCalledWith(mutationVariables);
- },
+ done,
);
});
+ });
- it('dispatches receiveEnvironmentsDataSuccess on success', () => {
- jest.spyOn(gqClient, 'mutate').mockResolvedValue({
- data: {
- project: {
- data: {
- environments: environmentData,
- },
- },
- },
- });
+ describe('setExpandedPanel', () => {
+ it('Sets a panel as expanded', () => {
+ const group = 'group_1';
+ const panel = { title: 'A Panel' };
return testAction(
- fetchEnvironmentsData,
- null,
+ setExpandedPanel,
+ { group, panel },
state,
+ [{ type: types.SET_EXPANDED_PANEL, payload: { group, panel } }],
[],
- [
- { type: 'requestEnvironmentsData' },
- {
- type: 'receiveEnvironmentsDataSuccess',
- payload: parseEnvironmentsResponse(environmentData, state.projectPath),
- },
- ],
);
});
+ });
- it('dispatches receiveEnvironmentsDataFailure on error', () => {
- jest.spyOn(gqClient, 'mutate').mockRejectedValue({});
-
+ describe('clearExpandedPanel', () => {
+ it('Clears a panel as expanded', () => {
return testAction(
- fetchEnvironmentsData,
- null,
+ clearExpandedPanel,
+ undefined,
state,
+ [{ type: types.SET_EXPANDED_PANEL, payload: { group: null, panel: null } }],
[],
- [{ type: 'requestEnvironmentsData' }, { type: 'receiveEnvironmentsDataFailure' }],
);
});
});
- describe('fetchAnnotations', () => {
- beforeEach(() => {
- state.timeRange = {
- start: '2020-04-15T12:54:32.137Z',
- end: '2020-08-15T12:54:32.137Z',
- };
- state.projectPath = 'gitlab-org/gitlab-test';
- state.currentEnvironmentName = 'production';
- state.currentDashboard = '.gitlab/dashboards/custom_dashboard.yml';
- });
-
- it('fetches annotations data and dispatches receiveAnnotationsSuccess', () => {
- const mockMutate = jest.spyOn(gqClient, 'mutate');
- const mutationVariables = {
- mutation: getAnnotations,
- variables: {
- projectPath: state.projectPath,
- environmentName: state.currentEnvironmentName,
- dashboardPath: state.currentDashboard,
- startingFrom: state.timeRange.start,
- },
- };
- const parsedResponse = parseAnnotationsResponse(annotationsData);
-
- mockMutate.mockResolvedValue({
- data: {
- project: {
- environments: {
- nodes: [
- {
- metricsDashboard: {
- annotations: {
- nodes: parsedResponse,
- },
- },
- },
- ],
- },
- },
- },
- });
+ // All Data
+ describe('fetchData', () => {
+ it('dispatches fetchEnvironmentsData and fetchEnvironmentsData', () => {
return testAction(
- fetchAnnotations,
+ fetchData,
null,
state,
[],
- [{ type: 'receiveAnnotationsSuccess', payload: parsedResponse }],
- () => {
- expect(mockMutate).toHaveBeenCalledWith(mutationVariables);
- },
+ [
+ { type: 'fetchEnvironmentsData' },
+ { type: 'fetchDashboard' },
+ { type: 'fetchAnnotations' },
+ ],
);
});
- it('dispatches receiveAnnotationsFailure if the annotations API call fails', () => {
- const mockMutate = jest.spyOn(gqClient, 'mutate');
- const mutationVariables = {
- mutation: getAnnotations,
- variables: {
- projectPath: state.projectPath,
- environmentName: state.currentEnvironmentName,
- dashboardPath: state.currentDashboard,
- startingFrom: state.timeRange.start,
- },
- };
-
- mockMutate.mockRejectedValue({});
+ it('dispatches when feature metricsDashboardAnnotations is on', () => {
+ const origGon = window.gon;
+ window.gon = { features: { metricsDashboardAnnotations: true } };
return testAction(
- fetchAnnotations,
+ fetchData,
null,
state,
[],
- [{ type: 'receiveAnnotationsFailure' }],
- () => {
- expect(mockMutate).toHaveBeenCalledWith(mutationVariables);
- },
- );
- });
- });
-
- describe('Toggles starred value of current dashboard', () => {
- let unstarredDashboard;
- let starredDashboard;
-
- beforeEach(() => {
- state.isUpdatingStarredValue = false;
- [unstarredDashboard, starredDashboard] = dashboardGitResponse;
- });
-
- describe('toggleStarredValue', () => {
- it('performs no changes if no dashboard is selected', () => {
- return testAction(toggleStarredValue, null, state, [], []);
- });
-
- it('performs no changes if already changing starred value', () => {
- state.selectedDashboard = unstarredDashboard;
- state.isUpdatingStarredValue = true;
- return testAction(toggleStarredValue, null, state, [], []);
- });
-
- it('stars dashboard if it is not starred', () => {
- state.selectedDashboard = unstarredDashboard;
- mock.onPost(unstarredDashboard.user_starred_path).reply(200);
-
- return testAction(toggleStarredValue, null, state, [
- { type: types.REQUEST_DASHBOARD_STARRING },
- {
- type: types.RECEIVE_DASHBOARD_STARRING_SUCCESS,
- payload: {
- newStarredValue: true,
- selectedDashboard: unstarredDashboard,
- },
- },
- ]);
- });
-
- it('unstars dashboard if it is starred', () => {
- state.selectedDashboard = starredDashboard;
- mock.onPost(starredDashboard.user_starred_path).reply(200);
-
- return testAction(toggleStarredValue, null, state, [
- { type: types.REQUEST_DASHBOARD_STARRING },
- { type: types.RECEIVE_DASHBOARD_STARRING_FAILURE },
- ]);
- });
- });
- });
-
- describe('Set initial state', () => {
- it('should commit SET_INITIAL_STATE mutation', done => {
- testAction(
- setInitialState,
- {
- currentDashboard: '.gitlab/dashboards/dashboard.yml',
- deploymentsEndpoint: 'deployments.json',
- },
- state,
- [
- {
- type: types.SET_INITIAL_STATE,
- payload: {
- currentDashboard: '.gitlab/dashboards/dashboard.yml',
- deploymentsEndpoint: 'deployments.json',
- },
- },
- ],
- [],
- done,
- );
- });
- });
- describe('Set empty states', () => {
- it('should commit SET_METRICS_ENDPOINT mutation', done => {
- testAction(
- setGettingStartedEmptyState,
- null,
- state,
[
- {
- type: types.SET_GETTING_STARTED_EMPTY_STATE,
- },
+ { type: 'fetchEnvironmentsData' },
+ { type: 'fetchDashboard' },
+ { type: 'fetchAnnotations' },
],
- [],
- done,
- );
+ ).then(() => {
+ window.gon = origGon;
+ });
});
});
- describe('updateVariablesAndFetchData', () => {
- it('should commit UPDATE_VARIABLES mutation and fetch data', done => {
- testAction(
- updateVariablesAndFetchData,
- { pod: 'POD' },
- state,
- [
- {
- type: types.UPDATE_VARIABLES,
- payload: { pod: 'POD' },
- },
- ],
- [
- {
- type: 'fetchDashboardData',
- },
- ],
- done,
- );
- });
- });
+ // Metrics dashboard
describe('fetchDashboard', () => {
- let dispatch;
- let commit;
const response = metricsDashboardResponse;
beforeEach(() => {
- dispatch = jest.fn();
- commit = jest.fn();
state.dashboardEndpoint = '/dashboard';
});
- it('on success, dispatches receive and success actions', () => {
+ it('on success, dispatches receive and success actions, then fetches dashboard warnings', () => {
document.body.dataset.page = 'projects:environments:metrics';
mock.onGet(state.dashboardEndpoint).reply(200, response);
@@ -470,6 +225,7 @@ describe('Monitoring store actions', () => {
type: 'receiveMetricsDashboardSuccess',
payload: { response },
},
+ { type: 'fetchDashboardValidationWarnings' },
],
);
});
@@ -478,9 +234,12 @@ describe('Monitoring store actions', () => {
let result;
beforeEach(() => {
const params = {};
+ const localGetters = {
+ fullDashboardPath: store.getters['monitoringDashboard/fullDashboardPath'],
+ };
result = () => {
mock.onGet(state.dashboardEndpoint).replyOnce(500, mockDashboardsErrorResponse);
- return fetchDashboard({ state, commit, dispatch }, params);
+ return fetchDashboard({ state, commit, dispatch, getters: localGetters }, params);
};
});
@@ -532,15 +291,8 @@ describe('Monitoring store actions', () => {
});
});
});
- describe('receiveMetricsDashboardSuccess', () => {
- let commit;
- let dispatch;
-
- beforeEach(() => {
- commit = jest.fn();
- dispatch = jest.fn();
- });
+ describe('receiveMetricsDashboardSuccess', () => {
it('stores groups', () => {
const response = metricsDashboardResponse;
receiveMetricsDashboardSuccess({ state, commit, dispatch }, { response });
@@ -552,32 +304,6 @@ describe('Monitoring store actions', () => {
expect(dispatch).toHaveBeenCalledWith('fetchDashboardData');
});
- it('stores templating variables', () => {
- const response = {
- ...metricsDashboardResponse.dashboard,
- ...mockTemplatingData.allVariableTypes.dashboard,
- };
-
- receiveMetricsDashboardSuccess(
- { state, commit, dispatch },
- {
- response: {
- ...metricsDashboardResponse,
- dashboard: {
- ...metricsDashboardResponse.dashboard,
- ...mockTemplatingData.allVariableTypes.dashboard,
- },
- },
- },
- );
-
- expect(commit).toHaveBeenCalledWith(
- types.RECEIVE_METRICS_DASHBOARD_SUCCESS,
-
- response,
- );
- });
-
it('sets the dashboards loaded from the repository', () => {
const params = {};
const response = metricsDashboardResponse;
@@ -596,23 +322,21 @@ describe('Monitoring store actions', () => {
expect(commit).toHaveBeenCalledWith(types.SET_ALL_DASHBOARDS, dashboardGitResponse);
});
});
- describe('fetchDashboardData', () => {
- let commit;
- let dispatch;
+ // Metrics
+
+ describe('fetchDashboardData', () => {
beforeEach(() => {
jest.spyOn(Tracking, 'event');
- commit = jest.fn();
- dispatch = jest.fn();
state.timeRange = defaultTimeRange;
});
it('commits empty state when state.groups is empty', done => {
- const getters = {
+ const localGetters = {
metricsWithData: () => [],
};
- fetchDashboardData({ state, commit, dispatch, getters })
+ fetchDashboardData({ state, commit, dispatch, getters: localGetters })
.then(() => {
expect(Tracking.event).toHaveBeenCalledWith(
document.body.dataset.page,
@@ -623,25 +347,33 @@ describe('Monitoring store actions', () => {
value: 0,
},
);
- expect(dispatch).toHaveBeenCalledTimes(1);
+ expect(dispatch).toHaveBeenCalledTimes(2);
expect(dispatch).toHaveBeenCalledWith('fetchDeploymentsData');
+ expect(dispatch).toHaveBeenCalledWith('fetchVariableMetricLabelValues', {
+ defaultQueryParams: {
+ start_time: expect.any(String),
+ end_time: expect.any(String),
+ step: expect.any(Number),
+ },
+ });
expect(createFlash).not.toHaveBeenCalled();
done();
})
.catch(done.fail);
});
+
it('dispatches fetchPrometheusMetric for each panel query', done => {
state.dashboard.panelGroups = convertObjectPropsToCamelCase(
metricsDashboardResponse.dashboard.panel_groups,
);
const [metric] = state.dashboard.panelGroups[0].panels[0].metrics;
- const getters = {
+ const localGetters = {
metricsWithData: () => [metric.id],
};
- fetchDashboardData({ state, commit, dispatch, getters })
+ fetchDashboardData({ state, commit, dispatch, getters: localGetters })
.then(() => {
expect(dispatch).toHaveBeenCalledWith('fetchPrometheusMetric', {
metric,
@@ -673,21 +405,27 @@ describe('Monitoring store actions', () => {
const metric = state.dashboard.panelGroups[0].panels[0].metrics[0];
dispatch.mockResolvedValueOnce(); // fetchDeploymentsData
+ dispatch.mockResolvedValueOnce(); // fetchVariableMetricLabelValues
// Mock having one out of four metrics failing
dispatch.mockRejectedValueOnce(new Error('Error fetching this metric'));
dispatch.mockResolvedValue();
fetchDashboardData({ state, commit, dispatch })
.then(() => {
- expect(dispatch).toHaveBeenCalledTimes(metricsDashboardPanelCount + 1); // plus 1 for deployments
+ const defaultQueryParams = {
+ start_time: expect.any(String),
+ end_time: expect.any(String),
+ step: expect.any(Number),
+ };
+
+ expect(dispatch).toHaveBeenCalledTimes(metricsDashboardPanelCount + 2); // plus 1 for deployments
expect(dispatch).toHaveBeenCalledWith('fetchDeploymentsData');
+ expect(dispatch).toHaveBeenCalledWith('fetchVariableMetricLabelValues', {
+ defaultQueryParams,
+ });
expect(dispatch).toHaveBeenCalledWith('fetchPrometheusMetric', {
metric,
- defaultQueryParams: {
- start_time: expect.any(String),
- end_time: expect.any(String),
- step: expect.any(Number),
- },
+ defaultQueryParams,
});
expect(createFlash).toHaveBeenCalledTimes(1);
@@ -698,6 +436,7 @@ describe('Monitoring store actions', () => {
done();
});
});
+
describe('fetchPrometheusMetric', () => {
const defaultQueryParams = {
start_time: '2019-08-06T12:40:02.184Z',
@@ -738,7 +477,7 @@ describe('Monitoring store actions', () => {
type: types.RECEIVE_METRIC_RESULT_SUCCESS,
payload: {
metricId: metric.metricId,
- result: data.result,
+ data,
},
},
],
@@ -775,7 +514,7 @@ describe('Monitoring store actions', () => {
type: types.RECEIVE_METRIC_RESULT_SUCCESS,
payload: {
metricId: metric.metricId,
- result: data.result,
+ data,
},
},
],
@@ -817,7 +556,7 @@ describe('Monitoring store actions', () => {
type: types.RECEIVE_METRIC_RESULT_SUCCESS,
payload: {
metricId: metric.metricId,
- result: data.result,
+ data,
},
},
],
@@ -852,7 +591,7 @@ describe('Monitoring store actions', () => {
type: types.RECEIVE_METRIC_RESULT_SUCCESS,
payload: {
metricId: metric.metricId,
- result: data.result,
+ data,
},
},
],
@@ -901,6 +640,402 @@ describe('Monitoring store actions', () => {
});
});
+ // Deployments
+
+ describe('fetchDeploymentsData', () => {
+ it('dispatches receiveDeploymentsDataSuccess on success', () => {
+ state.deploymentsEndpoint = '/success';
+ mock.onGet(state.deploymentsEndpoint).reply(200, {
+ deployments: deploymentData,
+ });
+
+ return testAction(
+ fetchDeploymentsData,
+ null,
+ state,
+ [],
+ [{ type: 'receiveDeploymentsDataSuccess', payload: deploymentData }],
+ );
+ });
+ it('dispatches receiveDeploymentsDataFailure on error', () => {
+ state.deploymentsEndpoint = '/error';
+ mock.onGet(state.deploymentsEndpoint).reply(500);
+
+ return testAction(
+ fetchDeploymentsData,
+ null,
+ state,
+ [],
+ [{ type: 'receiveDeploymentsDataFailure' }],
+ () => {
+ expect(createFlash).toHaveBeenCalled();
+ },
+ );
+ });
+ });
+
+ // Environments
+
+ describe('fetchEnvironmentsData', () => {
+ beforeEach(() => {
+ state.projectPath = 'gitlab-org/gitlab-test';
+ });
+
+ it('setting SET_ENVIRONMENTS_FILTER should dispatch fetchEnvironmentsData', () => {
+ jest.spyOn(gqClient, 'mutate').mockReturnValue({
+ data: {
+ project: {
+ data: {
+ environments: [],
+ },
+ },
+ },
+ });
+
+ return testAction(
+ filterEnvironments,
+ {},
+ state,
+ [
+ {
+ type: 'SET_ENVIRONMENTS_FILTER',
+ payload: {},
+ },
+ ],
+ [
+ {
+ type: 'fetchEnvironmentsData',
+ },
+ ],
+ );
+ });
+
+ it('fetch environments data call takes in search param', () => {
+ const mockMutate = jest.spyOn(gqClient, 'mutate');
+ const searchTerm = 'Something';
+ const mutationVariables = {
+ mutation: getEnvironments,
+ variables: {
+ projectPath: state.projectPath,
+ search: searchTerm,
+ states: [ENVIRONMENT_AVAILABLE_STATE],
+ },
+ };
+ state.environmentsSearchTerm = searchTerm;
+ mockMutate.mockResolvedValue({});
+
+ return testAction(
+ fetchEnvironmentsData,
+ null,
+ state,
+ [],
+ [
+ { type: 'requestEnvironmentsData' },
+ { type: 'receiveEnvironmentsDataSuccess', payload: [] },
+ ],
+ () => {
+ expect(mockMutate).toHaveBeenCalledWith(mutationVariables);
+ },
+ );
+ });
+
+ it('dispatches receiveEnvironmentsDataSuccess on success', () => {
+ jest.spyOn(gqClient, 'mutate').mockResolvedValue({
+ data: {
+ project: {
+ data: {
+ environments: environmentData,
+ },
+ },
+ },
+ });
+
+ return testAction(
+ fetchEnvironmentsData,
+ null,
+ state,
+ [],
+ [
+ { type: 'requestEnvironmentsData' },
+ {
+ type: 'receiveEnvironmentsDataSuccess',
+ payload: parseEnvironmentsResponse(environmentData, state.projectPath),
+ },
+ ],
+ );
+ });
+
+ it('dispatches receiveEnvironmentsDataFailure on error', () => {
+ jest.spyOn(gqClient, 'mutate').mockRejectedValue({});
+
+ return testAction(
+ fetchEnvironmentsData,
+ null,
+ state,
+ [],
+ [{ type: 'requestEnvironmentsData' }, { type: 'receiveEnvironmentsDataFailure' }],
+ );
+ });
+ });
+
+ describe('fetchAnnotations', () => {
+ beforeEach(() => {
+ state.timeRange = {
+ start: '2020-04-15T12:54:32.137Z',
+ end: '2020-08-15T12:54:32.137Z',
+ };
+ state.projectPath = 'gitlab-org/gitlab-test';
+ state.currentEnvironmentName = 'production';
+ state.currentDashboard = '.gitlab/dashboards/custom_dashboard.yml';
+ // testAction doesn't have access to getters. The state is passed in as getters
+ // instead of the actual getters inside the testAction method implementation.
+ // All methods downstream that needs access to getters will throw and error.
+ // For that reason, the result of the getter is set as a state variable.
+ state.fullDashboardPath = store.getters['monitoringDashboard/fullDashboardPath'];
+ });
+
+ it('fetches annotations data and dispatches receiveAnnotationsSuccess', () => {
+ const mockMutate = jest.spyOn(gqClient, 'mutate');
+ const mutationVariables = {
+ mutation: getAnnotations,
+ variables: {
+ projectPath: state.projectPath,
+ environmentName: state.currentEnvironmentName,
+ dashboardPath: state.currentDashboard,
+ startingFrom: state.timeRange.start,
+ },
+ };
+ const parsedResponse = parseAnnotationsResponse(annotationsData);
+
+ mockMutate.mockResolvedValue({
+ data: {
+ project: {
+ environments: {
+ nodes: [
+ {
+ metricsDashboard: {
+ annotations: {
+ nodes: parsedResponse,
+ },
+ },
+ },
+ ],
+ },
+ },
+ },
+ });
+
+ return testAction(
+ fetchAnnotations,
+ null,
+ state,
+ [],
+ [{ type: 'receiveAnnotationsSuccess', payload: parsedResponse }],
+ () => {
+ expect(mockMutate).toHaveBeenCalledWith(mutationVariables);
+ },
+ );
+ });
+
+ it('dispatches receiveAnnotationsFailure if the annotations API call fails', () => {
+ const mockMutate = jest.spyOn(gqClient, 'mutate');
+ const mutationVariables = {
+ mutation: getAnnotations,
+ variables: {
+ projectPath: state.projectPath,
+ environmentName: state.currentEnvironmentName,
+ dashboardPath: state.currentDashboard,
+ startingFrom: state.timeRange.start,
+ },
+ };
+
+ mockMutate.mockRejectedValue({});
+
+ return testAction(
+ fetchAnnotations,
+ null,
+ state,
+ [],
+ [{ type: 'receiveAnnotationsFailure' }],
+ () => {
+ expect(mockMutate).toHaveBeenCalledWith(mutationVariables);
+ },
+ );
+ });
+ });
+
+ describe('fetchDashboardValidationWarnings', () => {
+ let mockMutate;
+ let mutationVariables;
+
+ beforeEach(() => {
+ state.projectPath = 'gitlab-org/gitlab-test';
+ state.currentEnvironmentName = 'production';
+ state.currentDashboard = '.gitlab/dashboards/dashboard_with_warnings.yml';
+ // testAction doesn't have access to getters. The state is passed in as getters
+ // instead of the actual getters inside the testAction method implementation.
+ // All methods downstream that needs access to getters will throw and error.
+ // For that reason, the result of the getter is set as a state variable.
+ state.fullDashboardPath = store.getters['monitoringDashboard/fullDashboardPath'];
+
+ mockMutate = jest.spyOn(gqClient, 'mutate');
+ mutationVariables = {
+ mutation: getDashboardValidationWarnings,
+ variables: {
+ projectPath: state.projectPath,
+ environmentName: state.currentEnvironmentName,
+ dashboardPath: state.fullDashboardPath,
+ },
+ };
+ });
+
+ it('dispatches receiveDashboardValidationWarningsSuccess with true payload when there are warnings', () => {
+ mockMutate.mockResolvedValue({
+ data: {
+ project: {
+ id: 'gid://gitlab/Project/29',
+ environments: {
+ nodes: [
+ {
+ name: 'production',
+ metricsDashboard: {
+ path: '.gitlab/dashboards/dashboard_errors_test.yml',
+ schemaValidationWarnings: ["unit: can't be blank"],
+ },
+ },
+ ],
+ },
+ },
+ },
+ });
+
+ return testAction(
+ fetchDashboardValidationWarnings,
+ null,
+ state,
+ [],
+ [{ type: 'receiveDashboardValidationWarningsSuccess', payload: true }],
+ () => {
+ expect(mockMutate).toHaveBeenCalledWith(mutationVariables);
+ },
+ );
+ });
+
+ it('dispatches receiveDashboardValidationWarningsSuccess with false payload when there are no warnings', () => {
+ mockMutate.mockResolvedValue({
+ data: {
+ project: {
+ id: 'gid://gitlab/Project/29',
+ environments: {
+ nodes: [
+ {
+ name: 'production',
+ metricsDashboard: {
+ path: '.gitlab/dashboards/dashboard_errors_test.yml',
+ schemaValidationWarnings: [],
+ },
+ },
+ ],
+ },
+ },
+ },
+ });
+
+ return testAction(
+ fetchDashboardValidationWarnings,
+ null,
+ state,
+ [],
+ [{ type: 'receiveDashboardValidationWarningsSuccess', payload: false }],
+ () => {
+ expect(mockMutate).toHaveBeenCalledWith(mutationVariables);
+ },
+ );
+ });
+
+ it('dispatches receiveDashboardValidationWarningsSuccess with false payload when the response is empty ', () => {
+ mockMutate.mockResolvedValue({
+ data: {
+ project: null,
+ },
+ });
+
+ return testAction(
+ fetchDashboardValidationWarnings,
+ null,
+ state,
+ [],
+ [{ type: 'receiveDashboardValidationWarningsSuccess', payload: false }],
+ () => {
+ expect(mockMutate).toHaveBeenCalledWith(mutationVariables);
+ },
+ );
+ });
+
+ it('dispatches receiveDashboardValidationWarningsFailure if the warnings API call fails', () => {
+ mockMutate.mockRejectedValue({});
+
+ return testAction(
+ fetchDashboardValidationWarnings,
+ null,
+ state,
+ [],
+ [{ type: 'receiveDashboardValidationWarningsFailure' }],
+ () => {
+ expect(mockMutate).toHaveBeenCalledWith(mutationVariables);
+ },
+ );
+ });
+ });
+
+ // Dashboard manipulation
+
+ describe('toggleStarredValue', () => {
+ let unstarredDashboard;
+ let starredDashboard;
+
+ beforeEach(() => {
+ state.isUpdatingStarredValue = false;
+ [unstarredDashboard, starredDashboard] = dashboardGitResponse;
+ });
+
+ it('performs no changes if no dashboard is selected', () => {
+ return testAction(toggleStarredValue, null, state, [], []);
+ });
+
+ it('performs no changes if already changing starred value', () => {
+ state.selectedDashboard = unstarredDashboard;
+ state.isUpdatingStarredValue = true;
+ return testAction(toggleStarredValue, null, state, [], []);
+ });
+
+ it('stars dashboard if it is not starred', () => {
+ state.selectedDashboard = unstarredDashboard;
+ mock.onPost(unstarredDashboard.user_starred_path).reply(200);
+
+ return testAction(toggleStarredValue, null, state, [
+ { type: types.REQUEST_DASHBOARD_STARRING },
+ {
+ type: types.RECEIVE_DASHBOARD_STARRING_SUCCESS,
+ payload: {
+ newStarredValue: true,
+ selectedDashboard: unstarredDashboard,
+ },
+ },
+ ]);
+ });
+
+ it('unstars dashboard if it is starred', () => {
+ state.selectedDashboard = starredDashboard;
+ mock.onPost(starredDashboard.user_starred_path).reply(200);
+
+ return testAction(toggleStarredValue, null, state, [
+ { type: types.REQUEST_DASHBOARD_STARRING },
+ { type: types.RECEIVE_DASHBOARD_STARRING_FAILURE },
+ ]);
+ });
+ });
+
describe('duplicateSystemDashboard', () => {
beforeEach(() => {
state.dashboardsEndpoint = '/dashboards.json';
@@ -979,30 +1114,95 @@ describe('Monitoring store actions', () => {
});
});
- describe('setExpandedPanel', () => {
- it('Sets a panel as expanded', () => {
- const group = 'group_1';
- const panel = { title: 'A Panel' };
+ // Variables manipulation
- return testAction(
- setExpandedPanel,
- { group, panel },
+ describe('updateVariablesAndFetchData', () => {
+ it('should commit UPDATE_VARIABLE_VALUE mutation and fetch data', done => {
+ testAction(
+ updateVariablesAndFetchData,
+ { pod: 'POD' },
state,
- [{ type: types.SET_EXPANDED_PANEL, payload: { group, panel } }],
- [],
+ [
+ {
+ type: types.UPDATE_VARIABLE_VALUE,
+ payload: { pod: 'POD' },
+ },
+ ],
+ [
+ {
+ type: 'fetchDashboardData',
+ },
+ ],
+ done,
);
});
});
- describe('clearExpandedPanel', () => {
- it('Clears a panel as expanded', () => {
+ describe('fetchVariableMetricLabelValues', () => {
+ const variable = {
+ type: 'metric_label_values',
+ name: 'label1',
+ options: {
+ prometheusEndpointPath: '/series?match[]=metric_name',
+ label: 'job',
+ },
+ };
+
+ const defaultQueryParams = {
+ start_time: '2019-08-06T12:40:02.184Z',
+ end_time: '2019-08-06T20:40:02.184Z',
+ };
+
+ beforeEach(() => {
+ state = {
+ ...state,
+ timeRange: defaultTimeRange,
+ variables: [variable],
+ };
+ });
+
+ it('should commit UPDATE_VARIABLE_METRIC_LABEL_VALUES mutation and fetch data', () => {
+ const data = [
+ {
+ __name__: 'up',
+ job: 'prometheus',
+ },
+ {
+ __name__: 'up',
+ job: 'POD',
+ },
+ ];
+
+ mock.onGet('/series?match[]=metric_name').reply(200, {
+ status: 'success',
+ data,
+ });
+
return testAction(
- clearExpandedPanel,
- undefined,
+ fetchVariableMetricLabelValues,
+ { defaultQueryParams },
state,
- [{ type: types.SET_EXPANDED_PANEL, payload: { group: null, panel: null } }],
+ [
+ {
+ type: types.UPDATE_VARIABLE_METRIC_LABEL_VALUES,
+ payload: { variable, label: 'job', data },
+ },
+ ],
[],
);
});
+
+ it('should notify the user that dynamic options were not loaded', () => {
+ mock.onGet('/series?match[]=metric_name').reply(500);
+
+ return testAction(fetchVariableMetricLabelValues, { defaultQueryParams }, state, [], []).then(
+ () => {
+ expect(createFlash).toHaveBeenCalledTimes(1);
+ expect(createFlash).toHaveBeenCalledWith(
+ expect.stringContaining('error getting options for variable "label1"'),
+ );
+ },
+ );
+ });
});
});
diff --git a/spec/frontend/monitoring/store/getters_spec.js b/spec/frontend/monitoring/store/getters_spec.js
index 933ccb1e46c..a69f5265ea7 100644
--- a/spec/frontend/monitoring/store/getters_spec.js
+++ b/spec/frontend/monitoring/store/getters_spec.js
@@ -4,10 +4,11 @@ import mutations from '~/monitoring/stores/mutations';
import * as types from '~/monitoring/stores/mutation_types';
import { metricStates } from '~/monitoring/constants';
import {
+ customDashboardBasePath,
environmentData,
metricsResult,
dashboardGitResponse,
- mockTemplatingDataResponses,
+ storeVariables,
mockLinks,
} from '../mock_data';
import {
@@ -27,7 +28,10 @@ describe('Monitoring store Getters', () => {
const { metricId } = state.dashboard.panelGroups[group].panels[panel].metrics[metric];
mutations[types.RECEIVE_METRIC_RESULT_SUCCESS](state, {
metricId,
- result,
+ data: {
+ resultType: 'matrix',
+ result,
+ },
});
};
@@ -340,19 +344,21 @@ describe('Monitoring store Getters', () => {
});
it('transforms the variables object to an array in the [variable, variable_value] format for all variable types', () => {
- mutations[types.SET_VARIABLES](state, mockTemplatingDataResponses.allVariableTypes);
+ state.variables = storeVariables;
const variablesArray = getters.getCustomVariablesParams(state);
expect(variablesArray).toEqual({
- 'variables[advCustomNormal]': 'value2',
- 'variables[advText]': 'default',
- 'variables[simpleCustom]': 'value1',
- 'variables[simpleText]': 'Simple text',
+ 'variables[textSimple]': 'My default value',
+ 'variables[textAdvanced]': 'A default value',
+ 'variables[customSimple]': 'value1',
+ 'variables[customAdvanced]': 'value2',
+ 'variables[customAdvancedWithoutLabel]': 'value2',
+ 'variables[customAdvancedWithoutOptText]': 'value2',
});
});
it('transforms the variables object to an empty array when no keys are present', () => {
- mutations[types.SET_VARIABLES](state, {});
+ state.variables = [];
const variablesArray = getters.getCustomVariablesParams(state);
expect(variablesArray).toEqual({});
@@ -361,45 +367,53 @@ describe('Monitoring store Getters', () => {
describe('selectedDashboard', () => {
const { selectedDashboard } = getters;
+ const localGetters = state => ({
+ fullDashboardPath: getters.fullDashboardPath(state),
+ });
it('returns a dashboard', () => {
const state = {
allDashboards: dashboardGitResponse,
currentDashboard: dashboardGitResponse[0].path,
+ customDashboardBasePath,
};
- expect(selectedDashboard(state)).toEqual(dashboardGitResponse[0]);
+ expect(selectedDashboard(state, localGetters(state))).toEqual(dashboardGitResponse[0]);
});
it('returns a non-default dashboard', () => {
const state = {
allDashboards: dashboardGitResponse,
currentDashboard: dashboardGitResponse[1].path,
+ customDashboardBasePath,
};
- expect(selectedDashboard(state)).toEqual(dashboardGitResponse[1]);
+ expect(selectedDashboard(state, localGetters(state))).toEqual(dashboardGitResponse[1]);
});
it('returns a default dashboard when no dashboard is selected', () => {
const state = {
allDashboards: dashboardGitResponse,
currentDashboard: null,
+ customDashboardBasePath,
};
- expect(selectedDashboard(state)).toEqual(dashboardGitResponse[0]);
+ expect(selectedDashboard(state, localGetters(state))).toEqual(dashboardGitResponse[0]);
});
it('returns a default dashboard when dashboard cannot be found', () => {
const state = {
allDashboards: dashboardGitResponse,
currentDashboard: 'wrong_path',
+ customDashboardBasePath,
};
- expect(selectedDashboard(state)).toEqual(dashboardGitResponse[0]);
+ expect(selectedDashboard(state, localGetters(state))).toEqual(dashboardGitResponse[0]);
});
it('returns null when no dashboards are present', () => {
const state = {
allDashboards: [],
currentDashboard: dashboardGitResponse[0].path,
+ customDashboardBasePath,
};
- expect(selectedDashboard(state)).toEqual(null);
+ expect(selectedDashboard(state, localGetters(state))).toEqual(null);
});
});
diff --git a/spec/frontend/monitoring/store/mutations_spec.js b/spec/frontend/monitoring/store/mutations_spec.js
index 0283f1a86a4..14b38d79aa2 100644
--- a/spec/frontend/monitoring/store/mutations_spec.js
+++ b/spec/frontend/monitoring/store/mutations_spec.js
@@ -3,9 +3,9 @@ import httpStatusCodes from '~/lib/utils/http_status';
import mutations from '~/monitoring/stores/mutations';
import * as types from '~/monitoring/stores/mutation_types';
import state from '~/monitoring/stores/state';
-import { metricStates } from '~/monitoring/constants';
+import { dashboardEmptyStates, metricStates } from '~/monitoring/constants';
-import { deploymentData, dashboardGitResponse } from '../mock_data';
+import { deploymentData, dashboardGitResponse, storeTextVariables } from '../mock_data';
import { metricsDashboardPayload } from '../fixture_data';
describe('Monitoring mutations', () => {
@@ -15,6 +15,14 @@ describe('Monitoring mutations', () => {
stateCopy = state();
});
+ describe('REQUEST_METRICS_DASHBOARD', () => {
+ it('sets an empty loading state', () => {
+ mutations[types.REQUEST_METRICS_DASHBOARD](stateCopy);
+
+ expect(stateCopy.emptyState).toBe(dashboardEmptyStates.LOADING);
+ });
+ });
+
describe('RECEIVE_METRICS_DASHBOARD_SUCCESS', () => {
let payload;
const getGroups = () => stateCopy.dashboard.panelGroups;
@@ -23,6 +31,18 @@ describe('Monitoring mutations', () => {
stateCopy.dashboard.panelGroups = [];
payload = metricsDashboardPayload;
});
+ it('sets an empty noData state when the dashboard is empty', () => {
+ const emptyDashboardPayload = {
+ ...payload,
+ panel_groups: [],
+ };
+
+ mutations[types.RECEIVE_METRICS_DASHBOARD_SUCCESS](stateCopy, emptyDashboardPayload);
+ const groups = getGroups();
+
+ expect(groups).toEqual([]);
+ expect(stateCopy.emptyState).toBe(dashboardEmptyStates.NO_DATA);
+ });
it('adds a key to the group', () => {
mutations[types.RECEIVE_METRICS_DASHBOARD_SUCCESS](stateCopy, payload);
const groups = getGroups();
@@ -72,6 +92,20 @@ describe('Monitoring mutations', () => {
});
});
+ describe('RECEIVE_METRICS_DASHBOARD_FAILURE', () => {
+ it('sets an empty noData state when an empty error occurs', () => {
+ mutations[types.RECEIVE_METRICS_DASHBOARD_FAILURE](stateCopy);
+
+ expect(stateCopy.emptyState).toBe(dashboardEmptyStates.NO_DATA);
+ });
+
+ it('sets an empty unableToConnect state when an error occurs', () => {
+ mutations[types.RECEIVE_METRICS_DASHBOARD_FAILURE](stateCopy, 'myerror');
+
+ expect(stateCopy.emptyState).toBe(dashboardEmptyStates.UNABLE_TO_CONNECT);
+ });
+ });
+
describe('Dashboard starring mutations', () => {
it('REQUEST_DASHBOARD_STARRING', () => {
stateCopy = { isUpdatingStarredValue: false };
@@ -225,11 +259,28 @@ describe('Monitoring mutations', () => {
describe('Individual panel/metric results', () => {
const metricId = 'NO_DB_response_metrics_nginx_ingress_throughput_status_code';
- const result = [
- {
- values: [[0, 1], [1, 1], [1, 3]],
- },
- ];
+ const data = {
+ resultType: 'matrix',
+ result: [
+ {
+ metric: {
+ __name__: 'up',
+ job: 'prometheus',
+ instance: 'localhost:9090',
+ },
+ values: [[1435781430.781, '1'], [1435781445.781, '1'], [1435781460.781, '1']],
+ },
+ {
+ metric: {
+ __name__: 'up',
+ job: 'node',
+ instance: 'localhost:9091',
+ },
+ values: [[1435781430.781, '0'], [1435781445.781, '0'], [1435781460.781, '1']],
+ },
+ ],
+ };
+
const dashboard = metricsDashboardPayload;
const getMetric = () => stateCopy.dashboard.panelGroups[1].panels[0].metrics[0];
@@ -238,13 +289,10 @@ describe('Monitoring mutations', () => {
mutations[types.RECEIVE_METRICS_DASHBOARD_SUCCESS](stateCopy, dashboard);
});
it('stores a loading state on a metric', () => {
- expect(stateCopy.showEmptyState).toBe(true);
-
mutations[types.REQUEST_METRIC_RESULT](stateCopy, {
metricId,
});
- expect(stateCopy.showEmptyState).toBe(true);
expect(getMetric()).toEqual(
expect.objectContaining({
loading: true,
@@ -257,26 +305,16 @@ describe('Monitoring mutations', () => {
beforeEach(() => {
mutations[types.RECEIVE_METRICS_DASHBOARD_SUCCESS](stateCopy, dashboard);
});
- it('clears empty state', () => {
- expect(stateCopy.showEmptyState).toBe(true);
-
- mutations[types.RECEIVE_METRIC_RESULT_SUCCESS](stateCopy, {
- metricId,
- result,
- });
-
- expect(stateCopy.showEmptyState).toBe(false);
- });
it('adds results to the store', () => {
expect(getMetric().result).toBe(null);
mutations[types.RECEIVE_METRIC_RESULT_SUCCESS](stateCopy, {
metricId,
- result,
+ data,
});
- expect(getMetric().result).toHaveLength(result.length);
+ expect(getMetric().result).toHaveLength(data.result.length);
expect(getMetric()).toEqual(
expect.objectContaining({
loading: false,
@@ -290,16 +328,6 @@ describe('Monitoring mutations', () => {
beforeEach(() => {
mutations[types.RECEIVE_METRICS_DASHBOARD_SUCCESS](stateCopy, dashboard);
});
- it('maintains the loading state when a metric fails', () => {
- expect(stateCopy.showEmptyState).toBe(true);
-
- mutations[types.RECEIVE_METRIC_RESULT_FAILURE](stateCopy, {
- metricId,
- error: 'an error',
- });
-
- expect(stateCopy.showEmptyState).toBe(true);
- });
it('stores a timeout error in a metric', () => {
mutations[types.RECEIVE_METRIC_RESULT_FAILURE](stateCopy, {
@@ -369,6 +397,7 @@ describe('Monitoring mutations', () => {
});
});
});
+
describe('SET_ALL_DASHBOARDS', () => {
it('stores `undefined` dashboards as an empty array', () => {
mutations[types.SET_ALL_DASHBOARDS](stateCopy, undefined);
@@ -410,30 +439,53 @@ describe('Monitoring mutations', () => {
});
});
- describe('SET_VARIABLES', () => {
- it('stores an empty variables array when no custom variables are given', () => {
- mutations[types.SET_VARIABLES](stateCopy, {});
-
- expect(stateCopy.variables).toEqual({});
- });
-
- it('stores variables in the key key_value format in the array', () => {
- mutations[types.SET_VARIABLES](stateCopy, { pod: 'POD', stage: 'main ops' });
+ describe('UPDATE_VARIABLE_VALUE', () => {
+ it('updates only the value of the variable in variables', () => {
+ stateCopy.variables = storeTextVariables;
+ mutations[types.UPDATE_VARIABLE_VALUE](stateCopy, { name: 'textSimple', value: 'New Value' });
- expect(stateCopy.variables).toEqual({ pod: 'POD', stage: 'main ops' });
+ expect(stateCopy.variables[0].value).toEqual('New Value');
});
});
- describe('UPDATE_VARIABLES', () => {
- afterEach(() => {
- mutations[types.SET_VARIABLES](stateCopy, {});
- });
-
- it('updates only the value of the variable in variables', () => {
- mutations[types.SET_VARIABLES](stateCopy, { environment: { value: 'prod', type: 'text' } });
- mutations[types.UPDATE_VARIABLES](stateCopy, { key: 'environment', value: 'new prod' });
+ describe('UPDATE_VARIABLE_METRIC_LABEL_VALUES', () => {
+ it('updates options in a variable', () => {
+ const data = [
+ {
+ __name__: 'up',
+ job: 'prometheus',
+ env: 'prd',
+ },
+ {
+ __name__: 'up',
+ job: 'prometheus',
+ env: 'stg',
+ },
+ {
+ __name__: 'up',
+ job: 'node',
+ env: 'prod',
+ },
+ {
+ __name__: 'up',
+ job: 'node',
+ env: 'stg',
+ },
+ ];
+
+ const variable = {
+ options: {},
+ };
+
+ mutations[types.UPDATE_VARIABLE_METRIC_LABEL_VALUES](stateCopy, {
+ variable,
+ label: 'job',
+ data,
+ });
- expect(stateCopy.variables).toEqual({ environment: { value: 'new prod', type: 'text' } });
+ expect(variable.options).toEqual({
+ values: [{ text: 'prometheus', value: 'prometheus' }, { text: 'node', value: 'node' }],
+ });
});
});
});
diff --git a/spec/frontend/monitoring/store/utils_spec.js b/spec/frontend/monitoring/store/utils_spec.js
index 2dea40585f1..b97948fa1bf 100644
--- a/spec/frontend/monitoring/store/utils_spec.js
+++ b/spec/frontend/monitoring/store/utils_spec.js
@@ -5,9 +5,10 @@ import {
parseAnnotationsResponse,
removeLeadingSlash,
mapToDashboardViewModel,
- normalizeQueryResult,
+ normalizeQueryResponseData,
convertToGrafanaTimeRange,
addDashboardMetaDataToLink,
+ normalizeCustomDashboardPath,
} from '~/monitoring/stores/utils';
import * as urlUtils from '~/lib/utils/url_utility';
import { annotationsData } from '../mock_data';
@@ -21,7 +22,7 @@ describe('mapToDashboardViewModel', () => {
dashboard: '',
panelGroups: [],
links: [],
- variables: {},
+ variables: [],
});
});
@@ -51,7 +52,7 @@ describe('mapToDashboardViewModel', () => {
expect(mapToDashboardViewModel(response)).toEqual({
dashboard: 'Dashboard Name',
links: [],
- variables: {},
+ variables: [],
panelGroups: [
{
group: 'Group 1',
@@ -423,22 +424,20 @@ describe('mapToDashboardViewModel', () => {
urlUtils.queryToObject.mockReturnValueOnce();
- expect(mapToDashboardViewModel(response)).toMatchObject({
- dashboard: 'Dashboard Name',
- links: [],
- variables: {
- pod: {
- label: 'pod',
- type: 'text',
- value: 'kubernetes',
- },
- pod_2: {
- label: 'pod_2',
- type: 'text',
- value: 'kubernetes-2',
- },
+ expect(mapToDashboardViewModel(response).variables).toEqual([
+ {
+ name: 'pod',
+ label: 'pod',
+ type: 'text',
+ value: 'kubernetes',
},
- });
+ {
+ name: 'pod_2',
+ label: 'pod_2',
+ type: 'text',
+ value: 'kubernetes-2',
+ },
+ ]);
});
it('sets variables as-is from yml file if URL has no matching variables', () => {
@@ -457,22 +456,20 @@ describe('mapToDashboardViewModel', () => {
'var-environment': 'POD',
});
- expect(mapToDashboardViewModel(response)).toMatchObject({
- dashboard: 'Dashboard Name',
- links: [],
- variables: {
- pod: {
- label: 'pod',
- type: 'text',
- value: 'kubernetes',
- },
- pod_2: {
- label: 'pod_2',
- type: 'text',
- value: 'kubernetes-2',
- },
+ expect(mapToDashboardViewModel(response).variables).toEqual([
+ {
+ label: 'pod',
+ name: 'pod',
+ type: 'text',
+ value: 'kubernetes',
},
- });
+ {
+ label: 'pod_2',
+ name: 'pod_2',
+ type: 'text',
+ value: 'kubernetes-2',
+ },
+ ]);
});
it('merges variables from URL with the ones from yml file', () => {
@@ -493,44 +490,20 @@ describe('mapToDashboardViewModel', () => {
'var-pod_2': 'POD2',
});
- expect(mapToDashboardViewModel(response)).toMatchObject({
- dashboard: 'Dashboard Name',
- links: [],
- variables: {
- pod: {
- label: 'pod',
- type: 'text',
- value: 'POD1',
- },
- pod_2: {
- label: 'pod_2',
- type: 'text',
- value: 'POD2',
- },
+ expect(mapToDashboardViewModel(response).variables).toEqual([
+ {
+ label: 'pod',
+ name: 'pod',
+ type: 'text',
+ value: 'POD1',
},
- });
- });
- });
-});
-
-describe('normalizeQueryResult', () => {
- const testData = {
- metric: {
- __name__: 'up',
- job: 'prometheus',
- instance: 'localhost:9090',
- },
- values: [[1435781430.781, '1'], [1435781445.781, '1'], [1435781460.781, '1']],
- };
-
- it('processes a simple matrix result', () => {
- expect(normalizeQueryResult(testData)).toEqual({
- metric: { __name__: 'up', job: 'prometheus', instance: 'localhost:9090' },
- values: [
- ['2015-07-01T20:10:30.781Z', 1],
- ['2015-07-01T20:10:45.781Z', 1],
- ['2015-07-01T20:11:00.781Z', 1],
- ],
+ {
+ label: 'pod_2',
+ name: 'pod_2',
+ type: 'text',
+ value: 'POD2',
+ },
+ ]);
});
});
});
@@ -720,3 +693,187 @@ describe('user-defined links utils', () => {
});
});
});
+
+describe('normalizeQueryResponseData', () => {
+ // Data examples from
+ // https://prometheus.io/docs/prometheus/latest/querying/api/#expression-queries
+
+ it('processes a string result', () => {
+ const mockScalar = {
+ resultType: 'string',
+ result: [1435781451.781, '1'],
+ };
+
+ expect(normalizeQueryResponseData(mockScalar)).toEqual([
+ {
+ metric: {},
+ value: ['2015-07-01T20:10:51.781Z', '1'],
+ values: [['2015-07-01T20:10:51.781Z', '1']],
+ },
+ ]);
+ });
+
+ it('processes a scalar result', () => {
+ const mockScalar = {
+ resultType: 'scalar',
+ result: [1435781451.781, '1'],
+ };
+
+ expect(normalizeQueryResponseData(mockScalar)).toEqual([
+ {
+ metric: {},
+ value: ['2015-07-01T20:10:51.781Z', 1],
+ values: [['2015-07-01T20:10:51.781Z', 1]],
+ },
+ ]);
+ });
+
+ it('processes a vector result', () => {
+ const mockVector = {
+ resultType: 'vector',
+ result: [
+ {
+ metric: {
+ __name__: 'up',
+ job: 'prometheus',
+ instance: 'localhost:9090',
+ },
+ value: [1435781451.781, '1'],
+ },
+ {
+ metric: {
+ __name__: 'up',
+ job: 'node',
+ instance: 'localhost:9100',
+ },
+ value: [1435781451.781, '0'],
+ },
+ ],
+ };
+
+ expect(normalizeQueryResponseData(mockVector)).toEqual([
+ {
+ metric: { __name__: 'up', job: 'prometheus', instance: 'localhost:9090' },
+ value: ['2015-07-01T20:10:51.781Z', 1],
+ values: [['2015-07-01T20:10:51.781Z', 1]],
+ },
+ {
+ metric: { __name__: 'up', job: 'node', instance: 'localhost:9100' },
+ value: ['2015-07-01T20:10:51.781Z', 0],
+ values: [['2015-07-01T20:10:51.781Z', 0]],
+ },
+ ]);
+ });
+
+ it('processes a matrix result', () => {
+ const mockMatrix = {
+ resultType: 'matrix',
+ result: [
+ {
+ metric: {
+ __name__: 'up',
+ job: 'prometheus',
+ instance: 'localhost:9090',
+ },
+ values: [[1435781430.781, '1'], [1435781445.781, '2'], [1435781460.781, '3']],
+ },
+ {
+ metric: {
+ __name__: 'up',
+ job: 'node',
+ instance: 'localhost:9091',
+ },
+ values: [[1435781430.781, '4'], [1435781445.781, '5'], [1435781460.781, '6']],
+ },
+ ],
+ };
+
+ expect(normalizeQueryResponseData(mockMatrix)).toEqual([
+ {
+ metric: { __name__: 'up', instance: 'localhost:9090', job: 'prometheus' },
+ value: ['2015-07-01T20:11:00.781Z', 3],
+ values: [
+ ['2015-07-01T20:10:30.781Z', 1],
+ ['2015-07-01T20:10:45.781Z', 2],
+ ['2015-07-01T20:11:00.781Z', 3],
+ ],
+ },
+ {
+ metric: { __name__: 'up', instance: 'localhost:9091', job: 'node' },
+ value: ['2015-07-01T20:11:00.781Z', 6],
+ values: [
+ ['2015-07-01T20:10:30.781Z', 4],
+ ['2015-07-01T20:10:45.781Z', 5],
+ ['2015-07-01T20:11:00.781Z', 6],
+ ],
+ },
+ ]);
+ });
+
+ it('processes a scalar result with a NaN result', () => {
+ // Queries may return "NaN" string values.
+ // e.g. when Prometheus cannot find a metric the query
+ // `scalar(does_not_exist)` will return a "NaN" value.
+
+ const mockScalar = {
+ resultType: 'scalar',
+ result: [1435781451.781, 'NaN'],
+ };
+
+ expect(normalizeQueryResponseData(mockScalar)).toEqual([
+ {
+ metric: {},
+ value: ['2015-07-01T20:10:51.781Z', NaN],
+ values: [['2015-07-01T20:10:51.781Z', NaN]],
+ },
+ ]);
+ });
+
+ it('processes a matrix result with a "NaN" value', () => {
+ // Queries may return "NaN" string values.
+ const mockMatrix = {
+ resultType: 'matrix',
+ result: [
+ {
+ metric: {
+ __name__: 'up',
+ job: 'prometheus',
+ instance: 'localhost:9090',
+ },
+ values: [[1435781430.781, '1'], [1435781460.781, 'NaN']],
+ },
+ ],
+ };
+
+ expect(normalizeQueryResponseData(mockMatrix)).toEqual([
+ {
+ metric: { __name__: 'up', instance: 'localhost:9090', job: 'prometheus' },
+ value: ['2015-07-01T20:11:00.781Z', NaN],
+ values: [['2015-07-01T20:10:30.781Z', 1], ['2015-07-01T20:11:00.781Z', NaN]],
+ },
+ ]);
+ });
+});
+
+describe('normalizeCustomDashboardPath', () => {
+ it.each`
+ input | expected
+ ${[undefined]} | ${''}
+ ${[null]} | ${''}
+ ${[]} | ${''}
+ ${['links.yml']} | ${'links.yml'}
+ ${['links.yml', '.gitlab/dashboards']} | ${'.gitlab/dashboards/links.yml'}
+ ${['config/prometheus/common_metrics.yml']} | ${'config/prometheus/common_metrics.yml'}
+ ${['config/prometheus/common_metrics.yml', '.gitlab/dashboards']} | ${'config/prometheus/common_metrics.yml'}
+ ${['dir1/links.yml', '.gitlab/dashboards']} | ${'.gitlab/dashboards/dir1/links.yml'}
+ ${['dir1/dir2/links.yml', '.gitlab/dashboards']} | ${'.gitlab/dashboards/dir1/dir2/links.yml'}
+ ${['.gitlab/dashboards/links.yml']} | ${'.gitlab/dashboards/links.yml'}
+ ${['.gitlab/dashboards/links.yml', '.gitlab/dashboards']} | ${'.gitlab/dashboards/links.yml'}
+ ${['.gitlab/dashboards/dir1/links.yml', '.gitlab/dashboards']} | ${'.gitlab/dashboards/dir1/links.yml'}
+ ${['.gitlab/dashboards/dir1/dir2/links.yml', '.gitlab/dashboards']} | ${'.gitlab/dashboards/dir1/dir2/links.yml'}
+ ${['config/prometheus/pod_metrics.yml', '.gitlab/dashboards']} | ${'config/prometheus/pod_metrics.yml'}
+ ${['config/prometheus/pod_metrics.yml']} | ${'config/prometheus/pod_metrics.yml'}
+ `(`normalizeCustomDashboardPath returns $expected for $input`, ({ input, expected }) => {
+ expect(normalizeCustomDashboardPath(...input)).toEqual(expected);
+ });
+});
diff --git a/spec/frontend/monitoring/store/variable_mapping_spec.js b/spec/frontend/monitoring/store/variable_mapping_spec.js
index 5164ed1b54b..de124b0313c 100644
--- a/spec/frontend/monitoring/store/variable_mapping_spec.js
+++ b/spec/frontend/monitoring/store/variable_mapping_spec.js
@@ -1,94 +1,209 @@
-import { parseTemplatingVariables, mergeURLVariables } from '~/monitoring/stores/variable_mapping';
+import {
+ parseTemplatingVariables,
+ mergeURLVariables,
+ optionsFromSeriesData,
+} from '~/monitoring/stores/variable_mapping';
+import {
+ templatingVariablesExamples,
+ storeTextVariables,
+ storeCustomVariables,
+ storeMetricLabelValuesVariables,
+} from '../mock_data';
import * as urlUtils from '~/lib/utils/url_utility';
-import { mockTemplatingData, mockTemplatingDataResponses } from '../mock_data';
-
-describe('parseTemplatingVariables', () => {
- it.each`
- case | input | expected
- ${'Returns empty object for no dashboard input'} | ${{}} | ${{}}
- ${'Returns empty object for empty dashboard input'} | ${{ dashboard: {} }} | ${{}}
- ${'Returns empty object for empty templating prop'} | ${mockTemplatingData.emptyTemplatingProp} | ${{}}
- ${'Returns empty object for empty variables prop'} | ${mockTemplatingData.emptyVariablesProp} | ${{}}
- ${'Returns parsed object for simple text variable'} | ${mockTemplatingData.simpleText} | ${mockTemplatingDataResponses.simpleText}
- ${'Returns parsed object for advanced text variable'} | ${mockTemplatingData.advText} | ${mockTemplatingDataResponses.advText}
- ${'Returns parsed object for simple custom variable'} | ${mockTemplatingData.simpleCustom} | ${mockTemplatingDataResponses.simpleCustom}
- ${'Returns parsed object for advanced custom variable without options'} | ${mockTemplatingData.advCustomWithoutOpts} | ${mockTemplatingDataResponses.advCustomWithoutOpts}
- ${'Returns parsed object for advanced custom variable for option without text'} | ${mockTemplatingData.advCustomWithoutOptText} | ${mockTemplatingDataResponses.advCustomWithoutOptText}
- ${'Returns parsed object for advanced custom variable without type'} | ${mockTemplatingData.advCustomWithoutType} | ${{}}
- ${'Returns parsed object for advanced custom variable without label'} | ${mockTemplatingData.advCustomWithoutLabel} | ${mockTemplatingDataResponses.advCustomWithoutLabel}
- ${'Returns parsed object for simple and advanced custom variables'} | ${mockTemplatingData.simpleAndAdv} | ${mockTemplatingDataResponses.simpleAndAdv}
- ${'Returns parsed object for all variable types'} | ${mockTemplatingData.allVariableTypes} | ${mockTemplatingDataResponses.allVariableTypes}
- `('$case', ({ input, expected }) => {
- expect(parseTemplatingVariables(input?.dashboard?.templating)).toEqual(expected);
- });
-});
-describe('mergeURLVariables', () => {
- beforeEach(() => {
- jest.spyOn(urlUtils, 'queryToObject');
- });
+describe('Monitoring variable mapping', () => {
+ describe('parseTemplatingVariables', () => {
+ it.each`
+ case | input
+ ${'For undefined templating object'} | ${undefined}
+ ${'For empty templating object'} | ${{}}
+ `('$case, returns an empty array', ({ input }) => {
+ expect(parseTemplatingVariables(input)).toEqual([]);
+ });
- afterEach(() => {
- urlUtils.queryToObject.mockRestore();
+ it.each`
+ case | input | output
+ ${'Returns parsed object for text variables'} | ${templatingVariablesExamples.text} | ${storeTextVariables}
+ ${'Returns parsed object for custom variables'} | ${templatingVariablesExamples.custom} | ${storeCustomVariables}
+ ${'Returns parsed object for metric label value variables'} | ${templatingVariablesExamples.metricLabelValues} | ${storeMetricLabelValuesVariables}
+ `('$case, returns an empty array', ({ input, output }) => {
+ expect(parseTemplatingVariables(input)).toEqual(output);
+ });
});
- it('returns empty object if variables are not defined in yml or URL', () => {
- urlUtils.queryToObject.mockReturnValueOnce({});
+ describe('mergeURLVariables', () => {
+ beforeEach(() => {
+ jest.spyOn(urlUtils, 'queryToObject');
+ });
- expect(mergeURLVariables({})).toEqual({});
- });
+ afterEach(() => {
+ urlUtils.queryToObject.mockRestore();
+ });
- it('returns empty object if variables are defined in URL but not in yml', () => {
- urlUtils.queryToObject.mockReturnValueOnce({
- 'var-env': 'one',
- 'var-instance': 'localhost',
+ it('returns empty object if variables are not defined in yml or URL', () => {
+ urlUtils.queryToObject.mockReturnValueOnce({});
+
+ expect(mergeURLVariables([])).toEqual([]);
});
- expect(mergeURLVariables({})).toEqual({});
- });
+ it('returns empty object if variables are defined in URL but not in yml', () => {
+ urlUtils.queryToObject.mockReturnValueOnce({
+ 'var-env': 'one',
+ 'var-instance': 'localhost',
+ });
- it('returns yml variables if variables defined in yml but not in the URL', () => {
- urlUtils.queryToObject.mockReturnValueOnce({});
+ expect(mergeURLVariables([])).toEqual([]);
+ });
- const params = {
- env: 'one',
- instance: 'localhost',
- };
+ it('returns yml variables if variables defined in yml but not in the URL', () => {
+ urlUtils.queryToObject.mockReturnValueOnce({});
+
+ const variables = [
+ {
+ name: 'env',
+ value: 'one',
+ },
+ {
+ name: 'instance',
+ value: 'localhost',
+ },
+ ];
+
+ expect(mergeURLVariables(variables)).toEqual(variables);
+ });
- expect(mergeURLVariables(params)).toEqual(params);
- });
+ it('returns yml variables if variables defined in URL do not match with yml variables', () => {
+ const urlParams = {
+ 'var-env': 'one',
+ 'var-instance': 'localhost',
+ };
+ const variables = [
+ {
+ name: 'env',
+ value: 'one',
+ },
+ {
+ name: 'service',
+ value: 'database',
+ },
+ ];
+ urlUtils.queryToObject.mockReturnValueOnce(urlParams);
+
+ expect(mergeURLVariables(variables)).toEqual(variables);
+ });
- it('returns yml variables if variables defined in URL do not match with yml variables', () => {
- const urlParams = {
- 'var-env': 'one',
- 'var-instance': 'localhost',
- };
- const ymlParams = {
- pod: { value: 'one' },
- service: { value: 'database' },
- };
- urlUtils.queryToObject.mockReturnValueOnce(urlParams);
-
- expect(mergeURLVariables(ymlParams)).toEqual(ymlParams);
+ it('returns merged yml and URL variables if there is some match', () => {
+ const urlParams = {
+ 'var-env': 'one',
+ 'var-instance': 'localhost:8080',
+ };
+ const variables = [
+ {
+ name: 'instance',
+ value: 'localhost',
+ },
+ {
+ name: 'service',
+ value: 'database',
+ },
+ ];
+
+ urlUtils.queryToObject.mockReturnValueOnce(urlParams);
+
+ expect(mergeURLVariables(variables)).toEqual([
+ {
+ name: 'instance',
+ value: 'localhost:8080',
+ },
+ {
+ name: 'service',
+ value: 'database',
+ },
+ ]);
+ });
});
- it('returns merged yml and URL variables if there is some match', () => {
- const urlParams = {
- 'var-env': 'one',
- 'var-instance': 'localhost:8080',
- };
- const ymlParams = {
- instance: { value: 'localhost' },
- service: { value: 'database' },
- };
+ describe('optionsFromSeriesData', () => {
+ it('fetches the label values from missing data', () => {
+ expect(optionsFromSeriesData({ label: 'job' })).toEqual([]);
+ });
- const merged = {
- instance: { value: 'localhost:8080' },
- service: { value: 'database' },
- };
+ it('fetches the label values from a simple series', () => {
+ const data = [
+ {
+ __name__: 'up',
+ job: 'job1',
+ },
+ {
+ __name__: 'up',
+ job: 'job2',
+ },
+ ];
+
+ expect(optionsFromSeriesData({ label: 'job', data })).toEqual([
+ { text: 'job1', value: 'job1' },
+ { text: 'job2', value: 'job2' },
+ ]);
+ });
- urlUtils.queryToObject.mockReturnValueOnce(urlParams);
+ it('fetches the label values from multiple series', () => {
+ const data = [
+ {
+ __name__: 'up',
+ job: 'job1',
+ instance: 'host1',
+ },
+ {
+ __name__: 'up',
+ job: 'job2',
+ instance: 'host1',
+ },
+ {
+ __name__: 'up',
+ job: 'job1',
+ instance: 'host2',
+ },
+ {
+ __name__: 'up',
+ job: 'job2',
+ instance: 'host2',
+ },
+ ];
+
+ expect(optionsFromSeriesData({ label: '__name__', data })).toEqual([
+ { text: 'up', value: 'up' },
+ ]);
+
+ expect(optionsFromSeriesData({ label: 'job', data })).toEqual([
+ { text: 'job1', value: 'job1' },
+ { text: 'job2', value: 'job2' },
+ ]);
+
+ expect(optionsFromSeriesData({ label: 'instance', data })).toEqual([
+ { text: 'host1', value: 'host1' },
+ { text: 'host2', value: 'host2' },
+ ]);
+ });
- expect(mergeURLVariables(ymlParams)).toEqual(merged);
+ it('fetches the label values from a series with missing values', () => {
+ const data = [
+ {
+ __name__: 'up',
+ job: 'job1',
+ },
+ {
+ __name__: 'up',
+ job: 'job2',
+ },
+ {
+ __name__: 'up',
+ },
+ ];
+
+ expect(optionsFromSeriesData({ label: 'job', data })).toEqual([
+ { text: 'job1', value: 'job1' },
+ { text: 'job2', value: 'job2' },
+ ]);
+ });
});
});
diff --git a/spec/frontend/monitoring/store_utils.js b/spec/frontend/monitoring/store_utils.js
index eb2578aa9db..6c8267e6a3c 100644
--- a/spec/frontend/monitoring/store_utils.js
+++ b/spec/frontend/monitoring/store_utils.js
@@ -8,7 +8,10 @@ export const setMetricResult = ({ store, result, group = 0, panel = 0, metric =
store.commit(`monitoringDashboard/${types.RECEIVE_METRIC_RESULT_SUCCESS}`, {
metricId,
- result,
+ data: {
+ resultType: 'matrix',
+ result,
+ },
});
};
@@ -32,12 +35,6 @@ export const setupStoreWithDashboard = store => {
);
};
-export const setupStoreWithVariable = store => {
- store.commit(`monitoringDashboard/${types.SET_VARIABLES}`, {
- label1: 'pod',
- });
-};
-
export const setupStoreWithLinks = store => {
store.commit(`monitoringDashboard/${types.RECEIVE_METRICS_DASHBOARD_SUCCESS}`, {
...metricsDashboardPayload,
@@ -60,3 +57,24 @@ export const setupStoreWithData = store => {
setEnvironmentData(store);
};
+
+export const setupStoreWithDataForPanelCount = (store, panelCount) => {
+ const payloadPanelGroup = metricsDashboardPayload.panel_groups[0];
+
+ const panelGroupCustom = {
+ ...payloadPanelGroup,
+ panels: payloadPanelGroup.panels.slice(0, panelCount),
+ };
+
+ const metricsDashboardPayloadCustom = {
+ ...metricsDashboardPayload,
+ panel_groups: [panelGroupCustom],
+ };
+
+ store.commit(
+ `monitoringDashboard/${types.RECEIVE_METRICS_DASHBOARD_SUCCESS}`,
+ metricsDashboardPayloadCustom,
+ );
+
+ setMetricResult({ store, result: metricsResult, panel: 0 });
+};
diff --git a/spec/frontend/monitoring/utils_spec.js b/spec/frontend/monitoring/utils_spec.js
index 039cf275eea..35ca6ba9b52 100644
--- a/spec/frontend/monitoring/utils_spec.js
+++ b/spec/frontend/monitoring/utils_spec.js
@@ -1,12 +1,8 @@
import * as monitoringUtils from '~/monitoring/utils';
import * as urlUtils from '~/lib/utils/url_utility';
import { TEST_HOST } from 'jest/helpers/test_constants';
-import {
- mockProjectDir,
- singleStatMetricsResult,
- anomalyMockGraphData,
- barMockData,
-} from './mock_data';
+import { mockProjectDir, barMockData } from './mock_data';
+import { singleStatGraphData, anomalyGraphData } from './graph_data';
import { metricsDashboardViewModel, graphData } from './fixture_data';
const mockPath = `${TEST_HOST}${mockProjectDir}/-/environments/29/metrics`;
@@ -82,7 +78,7 @@ describe('monitoring/utils', () => {
it('validates data with the query format', () => {
const validGraphData = monitoringUtils.graphDataValidatorForValues(
true,
- singleStatMetricsResult,
+ singleStatGraphData(),
);
expect(validGraphData).toBe(true);
@@ -105,13 +101,13 @@ describe('monitoring/utils', () => {
let threeMetrics;
let fourMetrics;
beforeEach(() => {
- oneMetric = singleStatMetricsResult;
- threeMetrics = anomalyMockGraphData;
+ oneMetric = singleStatGraphData();
+ threeMetrics = anomalyGraphData();
const metrics = [...threeMetrics.metrics];
metrics.push(threeMetrics.metrics[0]);
fourMetrics = {
- ...anomalyMockGraphData,
+ ...anomalyGraphData(),
metrics,
};
});
@@ -429,14 +425,41 @@ describe('monitoring/utils', () => {
describe('convertVariablesForURL', () => {
it.each`
- input | expected
- ${undefined} | ${{}}
- ${null} | ${{}}
- ${{}} | ${{}}
- ${{ env: { value: 'prod' } }} | ${{ 'var-env': 'prod' }}
- ${{ 'var-env': { value: 'prod' } }} | ${{ 'var-var-env': 'prod' }}
+ input | expected
+ ${[]} | ${{}}
+ ${[{ name: 'env', value: 'prod' }]} | ${{ 'var-env': 'prod' }}
+ ${[{ name: 'env1', value: 'prod' }, { name: 'env2', value: null }]} | ${{ 'var-env1': 'prod' }}
+ ${[{ name: 'var-env', value: 'prod' }]} | ${{ 'var-var-env': 'prod' }}
`('convertVariablesForURL returns $expected with input $input', ({ input, expected }) => {
expect(monitoringUtils.convertVariablesForURL(input)).toEqual(expected);
});
});
+
+ describe('setCustomVariablesFromUrl', () => {
+ beforeEach(() => {
+ jest.spyOn(urlUtils, 'updateHistory');
+ });
+
+ afterEach(() => {
+ urlUtils.updateHistory.mockRestore();
+ });
+
+ it.each`
+ input | urlParams
+ ${[]} | ${''}
+ ${[{ name: 'env', value: 'prod' }]} | ${'?var-env=prod'}
+ ${[{ name: 'env1', value: 'prod' }, { name: 'env2', value: null }]} | ${'?var-env=prod&var-env1=prod'}
+ `(
+ 'setCustomVariablesFromUrl updates history with query "$urlParams" with input $input',
+ ({ input, urlParams }) => {
+ monitoringUtils.setCustomVariablesFromUrl(input);
+
+ expect(urlUtils.updateHistory).toHaveBeenCalledTimes(1);
+ expect(urlUtils.updateHistory).toHaveBeenCalledWith({
+ url: `${TEST_HOST}/${urlParams}`,
+ title: '',
+ });
+ },
+ );
+ });
});
diff --git a/spec/frontend/namespace_storage_limit_alert_spec.js b/spec/frontend/namespace_storage_limit_alert_spec.js
deleted file mode 100644
index ef398b12e1f..00000000000
--- a/spec/frontend/namespace_storage_limit_alert_spec.js
+++ /dev/null
@@ -1,36 +0,0 @@
-import Cookies from 'js-cookie';
-import initNamespaceStorageLimitAlert from '~/namespace_storage_limit_alert';
-
-describe('broadcast message on dismiss', () => {
- const dismiss = () => {
- const button = document.querySelector('.js-namespace-storage-alert-dismiss');
- button.click();
- };
-
- beforeEach(() => {
- setFixtures(`
- <div class="js-namespace-storage-alert">
- <button class="js-namespace-storage-alert-dismiss" data-id="1" data-level="info"></button>
- </div>
- `);
-
- initNamespaceStorageLimitAlert();
- });
-
- it('removes alert', () => {
- expect(document.querySelector('.js-namespace-storage-alert')).toBeTruthy();
-
- dismiss();
-
- expect(document.querySelector('.js-namespace-storage-alert')).toBeNull();
- });
-
- it('calls Cookies.set', () => {
- jest.spyOn(Cookies, 'set');
- dismiss();
-
- expect(Cookies.set).toHaveBeenCalledWith('hide_storage_limit_alert_1_info', true, {
- expires: 365,
- });
- });
-});
diff --git a/spec/frontend/notes/components/multiline_comment_utils_spec.js b/spec/frontend/notes/components/multiline_comment_utils_spec.js
index 261bfb106e7..af4394cc648 100644
--- a/spec/frontend/notes/components/multiline_comment_utils_spec.js
+++ b/spec/frontend/notes/components/multiline_comment_utils_spec.js
@@ -2,35 +2,23 @@ import {
getSymbol,
getStartLineNumber,
getEndLineNumber,
+ getCommentedLines,
} from '~/notes/components/multiline_comment_utils';
describe('Multiline comment utilities', () => {
- describe('getStartLineNumber', () => {
+ describe('get start & end line numbers', () => {
+ const lineRanges = ['old', 'new', null].map(type => ({
+ start: { new_line: 1, old_line: 1, type },
+ end: { new_line: 2, old_line: 2, type },
+ }));
it.each`
- lineCode | type | result
- ${'abcdef_1_1'} | ${'old'} | ${'-1'}
- ${'abcdef_1_1'} | ${'new'} | ${'+1'}
- ${'abcdef_1_1'} | ${null} | ${'1'}
- ${'abcdef'} | ${'new'} | ${''}
- ${'abcdef'} | ${'old'} | ${''}
- ${'abcdef'} | ${null} | ${''}
- `('returns line number', ({ lineCode, type, result }) => {
- expect(getStartLineNumber({ start_line_code: lineCode, start_line_type: type })).toEqual(
- result,
- );
- });
- });
- describe('getEndLineNumber', () => {
- it.each`
- lineCode | type | result
- ${'abcdef_1_1'} | ${'old'} | ${'-1'}
- ${'abcdef_1_1'} | ${'new'} | ${'+1'}
- ${'abcdef_1_1'} | ${null} | ${'1'}
- ${'abcdef'} | ${'new'} | ${''}
- ${'abcdef'} | ${'old'} | ${''}
- ${'abcdef'} | ${null} | ${''}
- `('returns line number', ({ lineCode, type, result }) => {
- expect(getEndLineNumber({ end_line_code: lineCode, end_line_type: type })).toEqual(result);
+ lineRange | start | end
+ ${lineRanges[0]} | ${'-1'} | ${'-2'}
+ ${lineRanges[1]} | ${'+1'} | ${'+2'}
+ ${lineRanges[2]} | ${'1'} | ${'2'}
+ `('returns line numbers `$start` & `$end`', ({ lineRange, start, end }) => {
+ expect(getStartLineNumber(lineRange)).toEqual(start);
+ expect(getEndLineNumber(lineRange)).toEqual(end);
});
});
describe('getSymbol', () => {
@@ -46,4 +34,30 @@ describe('Multiline comment utilities', () => {
expect(getSymbol(type)).toEqual(result);
});
});
+ describe('getCommentedLines', () => {
+ const diffLines = [{ line_code: '1' }, { line_code: '2' }, { line_code: '3' }];
+ it('returns a default object when `selectedCommentPosition` is not provided', () => {
+ expect(getCommentedLines(undefined, diffLines)).toEqual({ startLine: 4, endLine: 4 });
+ });
+ it('returns an object with startLine and endLine equal to 0', () => {
+ const selectedCommentPosition = {
+ start: { line_code: '1' },
+ end: { line_code: '1' },
+ };
+ expect(getCommentedLines(selectedCommentPosition, diffLines)).toEqual({
+ startLine: 0,
+ endLine: 0,
+ });
+ });
+ it('returns an object with startLine and endLine equal to 0 and 1', () => {
+ const selectedCommentPosition = {
+ start: { line_code: '1' },
+ end: { line_code: '2' },
+ };
+ expect(getCommentedLines(selectedCommentPosition, diffLines)).toEqual({
+ startLine: 0,
+ endLine: 1,
+ });
+ });
+ });
});
diff --git a/spec/frontend/notes/components/note_actions_spec.js b/spec/frontend/notes/components/note_actions_spec.js
index 220ac22d8eb..5cc56cdefae 100644
--- a/spec/frontend/notes/components/note_actions_spec.js
+++ b/spec/frontend/notes/components/note_actions_spec.js
@@ -127,25 +127,63 @@ describe('noteActions', () => {
.catch(done.fail);
});
- it('should be possible to assign or unassign the comment author', () => {
- wrapper = shallowMountNoteActions(props, {
- targetType: () => 'issue',
- });
-
+ it('should not be possible to assign or unassign the comment author in a merge request', () => {
const assignUserButton = wrapper.find('[data-testid="assign-user"]');
- expect(assignUserButton.exists()).toBe(true);
+ expect(assignUserButton.exists()).toBe(false);
+ });
+ });
+ });
- assignUserButton.trigger('click');
- axiosMock.onPut(`${TEST_HOST}/api/v4/projects/group/project/issues/1`).reply(() => {
- expect(actions.updateAssignees).toHaveBeenCalled();
- });
+ describe('when a user has access to edit an issue', () => {
+ const testButtonClickTriggersAction = () => {
+ axiosMock.onPut(`${TEST_HOST}/api/v4/projects/group/project/issues/1`).reply(() => {
+ expect(actions.updateAssignees).toHaveBeenCalled();
});
- it('should not be possible to assign or unassign the comment author in a merge request', () => {
- const assignUserButton = wrapper.find('[data-testid="assign-user"]');
- expect(assignUserButton.exists()).toBe(false);
+ const assignUserButton = wrapper.find('[data-testid="assign-user"]');
+ expect(assignUserButton.exists()).toBe(true);
+ assignUserButton.trigger('click');
+ };
+
+ beforeEach(() => {
+ wrapper = shallowMountNoteActions(props, {
+ targetType: () => 'issue',
});
+ store.state.noteableData = {
+ current_user: {
+ can_update: true,
+ },
+ };
+ store.state.userData = userDataMock;
});
+
+ afterEach(() => {
+ wrapper.destroy();
+ axiosMock.restore();
+ });
+
+ it('should be possible to assign the comment author', testButtonClickTriggersAction);
+ it('should be possible to unassign the comment author', testButtonClickTriggersAction);
+ });
+
+ describe('when a user does not have access to edit an issue', () => {
+ const testButtonDoesNotRender = () => {
+ const assignUserButton = wrapper.find('[data-testid="assign-user"]');
+ expect(assignUserButton.exists()).toBe(false);
+ };
+
+ beforeEach(() => {
+ wrapper = shallowMountNoteActions(props, {
+ targetType: () => 'issue',
+ });
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ it('should not be possible to assign the comment author', testButtonDoesNotRender);
+ it('should not be possible to unassign the comment author', testButtonDoesNotRender);
});
describe('user is not logged in', () => {
diff --git a/spec/frontend/notes/components/note_form_spec.js b/spec/frontend/notes/components/note_form_spec.js
index 15802841c57..a5b5204509e 100644
--- a/spec/frontend/notes/components/note_form_spec.js
+++ b/spec/frontend/notes/components/note_form_spec.js
@@ -245,6 +245,24 @@ describe('issue_note_form component', () => {
expect(updateDraft).toHaveBeenCalledWith(dummyAutosaveKey, dummyContent);
});
+
+ it('does not save draft when ctrl+enter is pressed', () => {
+ const options = {
+ noteBody: '',
+ autosaveKey: dummyAutosaveKey,
+ };
+
+ props = { ...props, ...options };
+ wrapper = createComponentWrapper();
+
+ wrapper.setData({ isSubmittingWithKeydown: true });
+
+ const textarea = wrapper.find('textarea');
+ textarea.setValue('some content');
+ textarea.trigger('keydown.enter', { metaKey: true });
+
+ expect(updateDraft).not.toHaveBeenCalled();
+ });
});
describe('with batch comments', () => {
diff --git a/spec/frontend/notes/components/noteable_note_spec.js b/spec/frontend/notes/components/noteable_note_spec.js
index aa3eaa97e20..fc238feb974 100644
--- a/spec/frontend/notes/components/noteable_note_spec.js
+++ b/spec/frontend/notes/components/noteable_note_spec.js
@@ -34,7 +34,13 @@ describe('issue_note', () => {
note,
},
localVue,
- stubs: ['note-header', 'user-avatar-link', 'note-actions', 'note-body'],
+ stubs: [
+ 'note-header',
+ 'user-avatar-link',
+ 'note-actions',
+ 'note-body',
+ 'multiline-comment-form',
+ ],
});
});
@@ -46,12 +52,30 @@ describe('issue_note', () => {
it('should render if has multiline comment', () => {
const position = {
line_range: {
- start_line_code: 'abc_1_1',
- end_line_code: 'abc_2_2',
+ start: {
+ line_code: 'abc_1_1',
+ type: null,
+ old_line: '1',
+ new_line: '1',
+ },
+ end: {
+ line_code: 'abc_2_2',
+ type: null,
+ old_line: '2',
+ new_line: '2',
+ },
},
};
+ const line = {
+ line_code: 'abc_1_1',
+ type: null,
+ old_line: '1',
+ new_line: '1',
+ };
wrapper.setProps({
note: { ...note, position },
+ discussionRoot: true,
+ line,
});
return wrapper.vm.$nextTick().then(() => {
@@ -59,15 +83,51 @@ describe('issue_note', () => {
});
});
+ it('should render multiline comment if editing discussion root', () => {
+ wrapper.setProps({ discussionRoot: true });
+ wrapper.vm.isEditing = true;
+
+ return wrapper.vm.$nextTick().then(() => {
+ expect(findMultilineComment().exists()).toBe(true);
+ });
+ });
+
+ it('should not render multiline comment form unless it is the discussion root', () => {
+ wrapper.setProps({ discussionRoot: false });
+ wrapper.vm.isEditing = true;
+
+ return wrapper.vm.$nextTick().then(() => {
+ expect(findMultilineComment().exists()).toBe(false);
+ });
+ });
+
it('should not render if has single line comment', () => {
const position = {
line_range: {
- start_line_code: 'abc_1_1',
- end_line_code: 'abc_1_1',
+ start: {
+ line_code: 'abc_1_1',
+ type: null,
+ old_line: '1',
+ new_line: '1',
+ },
+ end: {
+ line_code: 'abc_1_1',
+ type: null,
+ old_line: '1',
+ new_line: '1',
+ },
},
};
+ const line = {
+ line_code: 'abc_1_1',
+ type: null,
+ old_line: '1',
+ new_line: '1',
+ };
wrapper.setProps({
note: { ...note, position },
+ discussionRoot: true,
+ line,
});
return wrapper.vm.$nextTick().then(() => {
@@ -139,6 +199,7 @@ describe('issue_note', () => {
store.hotUpdate({
actions: {
updateNote() {},
+ setSelectedCommentPositionHover() {},
},
});
const noteBodyComponent = wrapper.find(NoteBody);
diff --git a/spec/frontend/notes/mixins/discussion_navigation_spec.js b/spec/frontend/notes/mixins/discussion_navigation_spec.js
index ae30a36fc81..ecff95b6fe0 100644
--- a/spec/frontend/notes/mixins/discussion_navigation_spec.js
+++ b/spec/frontend/notes/mixins/discussion_navigation_spec.js
@@ -91,6 +91,8 @@ describe('Discussion navigation mixin', () => {
beforeEach(() => {
window.mrTabs.currentAction = 'show';
wrapper.vm[fn](...args);
+
+ return wrapper.vm.$nextTick();
});
it('sets current discussion', () => {
@@ -112,6 +114,8 @@ describe('Discussion navigation mixin', () => {
beforeEach(() => {
window.mrTabs.currentAction = 'diffs';
wrapper.vm[fn](...args);
+
+ return wrapper.vm.$nextTick();
});
it('sets current discussion', () => {
@@ -137,6 +141,8 @@ describe('Discussion navigation mixin', () => {
beforeEach(() => {
window.mrTabs.currentAction = 'other';
wrapper.vm[fn](...args);
+
+ return wrapper.vm.$nextTick();
});
it('sets current discussion', () => {
diff --git a/spec/frontend/notes/old_notes_spec.js b/spec/frontend/notes/old_notes_spec.js
index cb1d563ece7..dee4f93f0ce 100644
--- a/spec/frontend/notes/old_notes_spec.js
+++ b/spec/frontend/notes/old_notes_spec.js
@@ -624,7 +624,7 @@ describe.skip('Old Notes (~/notes.js)', () => {
});
});
- describe('postComment with Slash commands', () => {
+ describe('postComment with quick actions', () => {
const sampleComment = '/assign @root\n/award :100:';
const note = {
commands_changes: {
@@ -640,6 +640,7 @@ describe.skip('Old Notes (~/notes.js)', () => {
let $notesContainer;
beforeEach(() => {
+ loadFixtures('commit/show.html');
mockAxios.onPost(NOTES_POST_PATH).reply(200, note);
new Notes('', []);
@@ -659,14 +660,49 @@ describe.skip('Old Notes (~/notes.js)', () => {
$form.find('textarea.js-note-text').val(sampleComment);
});
- it('should remove slash command placeholder when comment with slash commands is done posting', done => {
+ it('should remove quick action placeholder when comment with quick actions is done posting', done => {
jest.spyOn(gl.awardsHandler, 'addAwardToEmojiBar');
$('.js-comment-button').click();
- expect($notesContainer.find('.system-note.being-posted').length).toEqual(1); // Placeholder shown
+ expect($notesContainer.find('.note.being-posted').length).toEqual(1); // Placeholder shown
setImmediate(() => {
- expect($notesContainer.find('.system-note.being-posted').length).toEqual(0); // Placeholder removed
+ expect($notesContainer.find('.note.being-posted').length).toEqual(0); // Placeholder removed
+ done();
+ });
+ });
+ });
+
+ describe('postComment with slash when quick actions are not supported', () => {
+ const sampleComment = '/assign @root';
+ let $form;
+ let $notesContainer;
+
+ beforeEach(() => {
+ const note = {
+ id: 1234,
+ html: `<li class="note note-row-1234 timeline-entry" id="note_1234">
+ <div class="note-text">${sampleComment}</div>
+ </li>`,
+ note: sampleComment,
+ valid: true,
+ };
+ mockAxios.onPost(NOTES_POST_PATH).reply(200, note);
+
+ new Notes('', []);
+ $form = $('form.js-main-target-form');
+ $notesContainer = $('ul.main-notes-list');
+ $form.find('textarea.js-note-text').val(sampleComment);
+ });
+
+ it('should show message placeholder including lines starting with slash', done => {
+ $('.js-comment-button').click();
+
+ expect($notesContainer.find('.note.being-posted').length).toEqual(1); // Placeholder shown
+ expect($notesContainer.find('.note-body p').text()).toEqual(sampleComment); // No quick action processing
+
+ setImmediate(() => {
+ expect($notesContainer.find('.note.being-posted').length).toEqual(0); // Placeholder removed
done();
});
});
diff --git a/spec/frontend/notes/stores/actions_spec.js b/spec/frontend/notes/stores/actions_spec.js
index ef87cb3bee7..909a4a797ae 100644
--- a/spec/frontend/notes/stores/actions_spec.js
+++ b/spec/frontend/notes/stores/actions_spec.js
@@ -18,6 +18,8 @@ import {
batchSuggestionsInfoMock,
} from '../mock_data';
import axios from '~/lib/utils/axios_utils';
+import * as utils from '~/notes/stores/utils';
+import updateIssueConfidentialMutation from '~/sidebar/components/confidential/queries/update_issue_confidential.mutation.graphql';
const TEST_ERROR_MESSAGE = 'Test error message';
jest.mock('~/flash');
@@ -272,9 +274,54 @@ describe('Actions Notes Store', () => {
});
});
+ describe('fetchData', () => {
+ describe('given there are no notes', () => {
+ const lastFetchedAt = '13579';
+
+ beforeEach(() => {
+ axiosMock
+ .onGet(notesDataMock.notesPath)
+ .replyOnce(200, { notes: [], last_fetched_at: lastFetchedAt });
+ });
+
+ it('should commit SET_LAST_FETCHED_AT', () =>
+ testAction(
+ actions.fetchData,
+ undefined,
+ { notesData: notesDataMock },
+ [{ type: 'SET_LAST_FETCHED_AT', payload: lastFetchedAt }],
+ [],
+ ));
+ });
+
+ describe('given there are notes', () => {
+ const lastFetchedAt = '12358';
+
+ beforeEach(() => {
+ axiosMock
+ .onGet(notesDataMock.notesPath)
+ .replyOnce(200, { notes: discussionMock.notes, last_fetched_at: lastFetchedAt });
+ });
+
+ it('should dispatch updateOrCreateNotes, startTaskList and commit SET_LAST_FETCHED_AT', () =>
+ testAction(
+ actions.fetchData,
+ undefined,
+ { notesData: notesDataMock },
+ [{ type: 'SET_LAST_FETCHED_AT', payload: lastFetchedAt }],
+ [
+ { type: 'updateOrCreateNotes', payload: discussionMock.notes },
+ { type: 'startTaskList' },
+ ],
+ ));
+ });
+ });
+
describe('poll', () => {
beforeEach(done => {
- jest.spyOn(axios, 'get');
+ axiosMock
+ .onGet(notesDataMock.notesPath)
+ .reply(200, { notes: [], last_fetched_at: '123456' }, { 'poll-interval': '1000' });
store
.dispatch('setNotesData', notesDataMock)
@@ -283,15 +330,10 @@ describe('Actions Notes Store', () => {
});
it('calls service with last fetched state', done => {
- axiosMock
- .onAny()
- .reply(200, { notes: [], last_fetched_at: '123456' }, { 'poll-interval': '1000' });
-
store
.dispatch('poll')
.then(() => new Promise(resolve => requestAnimationFrame(resolve)))
.then(() => {
- expect(axios.get).toHaveBeenCalled();
expect(store.state.lastFetchedAt).toBe('123456');
jest.advanceTimersByTime(1500);
@@ -303,8 +345,9 @@ describe('Actions Notes Store', () => {
}),
)
.then(() => {
- expect(axios.get.mock.calls.length).toBe(2);
- expect(axios.get.mock.calls[axios.get.mock.calls.length - 1][1].headers).toEqual({
+ const expectedGetRequests = 2;
+ expect(axiosMock.history.get.length).toBe(expectedGetRequests);
+ expect(axiosMock.history.get[expectedGetRequests - 1].headers).toMatchObject({
'X-Last-Fetched-At': '123456',
});
})
@@ -449,7 +492,7 @@ describe('Actions Notes Store', () => {
it('commits ADD_NEW_NOTE and dispatches updateMergeRequestWidget', done => {
testAction(
actions.createNewNote,
- { endpoint: `${gl.TEST_HOST}`, data: {} },
+ { endpoint: `${TEST_HOST}`, data: {} },
store.state,
[
{
@@ -485,7 +528,7 @@ describe('Actions Notes Store', () => {
it('does not commit ADD_NEW_NOTE or dispatch updateMergeRequestWidget', done => {
testAction(
actions.createNewNote,
- { endpoint: `${gl.TEST_HOST}`, data: {} },
+ { endpoint: `${TEST_HOST}`, data: {} },
store.state,
[],
[],
@@ -508,7 +551,7 @@ describe('Actions Notes Store', () => {
it('commits UPDATE_NOTE and dispatches updateMergeRequestWidget', done => {
testAction(
actions.toggleResolveNote,
- { endpoint: `${gl.TEST_HOST}`, isResolved: true, discussion: false },
+ { endpoint: `${TEST_HOST}`, isResolved: true, discussion: false },
store.state,
[
{
@@ -533,7 +576,7 @@ describe('Actions Notes Store', () => {
it('commits UPDATE_DISCUSSION and dispatches updateMergeRequestWidget', done => {
testAction(
actions.toggleResolveNote,
- { endpoint: `${gl.TEST_HOST}`, isResolved: true, discussion: true },
+ { endpoint: `${TEST_HOST}`, isResolved: true, discussion: true },
store.state,
[
{
@@ -1084,6 +1127,19 @@ describe('Actions Notes Store', () => {
});
});
+ describe('setSelectedCommentPosition', () => {
+ it('calls the correct mutation with the correct args', done => {
+ testAction(
+ actions.setSelectedCommentPosition,
+ {},
+ {},
+ [{ type: mutationTypes.SET_SELECTED_COMMENT_POSITION, payload: {} }],
+ [],
+ done,
+ );
+ });
+ });
+
describe('softDeleteDescriptionVersion', () => {
const endpoint = '/path/to/diff/1';
const payload = {
@@ -1142,6 +1198,14 @@ describe('Actions Notes Store', () => {
});
});
+ describe('setConfidentiality', () => {
+ it('calls the correct mutation with the correct args', () => {
+ testAction(actions.setConfidentiality, true, { noteableData: { confidential: false } }, [
+ { type: mutationTypes.SET_ISSUE_CONFIDENTIAL, payload: true },
+ ]);
+ });
+ });
+
describe('updateAssignees', () => {
it('update the assignees state', done => {
testAction(
@@ -1154,4 +1218,49 @@ describe('Actions Notes Store', () => {
);
});
});
+
+ describe('updateConfidentialityOnIssue', () => {
+ state = { noteableData: { confidential: false } };
+ const iid = '1';
+ const projectPath = 'full/path';
+ const getters = { getNoteableData: { iid } };
+ const actionArgs = { fullPath: projectPath, confidential: true };
+ const confidential = true;
+
+ beforeEach(() => {
+ jest
+ .spyOn(utils.gqClient, 'mutate')
+ .mockResolvedValue({ data: { issueSetConfidential: { issue: { confidential } } } });
+ });
+
+ it('calls gqClient mutation one time', () => {
+ actions.updateConfidentialityOnIssue({ commit: () => {}, state, getters }, actionArgs);
+
+ expect(utils.gqClient.mutate).toHaveBeenCalledTimes(1);
+ });
+
+ it('calls gqClient mutation with the correct values', () => {
+ actions.updateConfidentialityOnIssue({ commit: () => {}, state, getters }, actionArgs);
+
+ expect(utils.gqClient.mutate).toHaveBeenCalledWith({
+ mutation: updateIssueConfidentialMutation,
+ variables: { input: { iid, projectPath, confidential } },
+ });
+ });
+
+ describe('on success of mutation', () => {
+ it('calls commit with the correct values', () => {
+ const commitSpy = jest.fn();
+
+ return actions
+ .updateConfidentialityOnIssue({ commit: commitSpy, state, getters }, actionArgs)
+ .then(() => {
+ expect(commitSpy).toHaveBeenCalledWith(
+ mutationTypes.SET_ISSUE_CONFIDENTIAL,
+ confidential,
+ );
+ });
+ });
+ });
+ });
});
diff --git a/spec/frontend/notes/stores/mutation_spec.js b/spec/frontend/notes/stores/mutation_spec.js
index 75ef007b78d..0ad18ba9b6a 100644
--- a/spec/frontend/notes/stores/mutation_spec.js
+++ b/spec/frontend/notes/stores/mutation_spec.js
@@ -524,6 +524,26 @@ describe('Notes Store mutations', () => {
});
});
+ describe('SET_SELECTED_COMMENT_POSITION', () => {
+ it('should set comment position state', () => {
+ const state = {};
+
+ mutations.SET_SELECTED_COMMENT_POSITION(state, {});
+
+ expect(state.selectedCommentPosition).toEqual({});
+ });
+ });
+
+ describe('SET_SELECTED_COMMENT_POSITION_HOVER', () => {
+ it('should set comment hover position state', () => {
+ const state = {};
+
+ mutations.SET_SELECTED_COMMENT_POSITION_HOVER(state, {});
+
+ expect(state.selectedCommentPositionHover).toEqual({});
+ });
+ });
+
describe('DISABLE_COMMENTS', () => {
it('should set comments disabled state', () => {
const state = {};
@@ -806,6 +826,20 @@ describe('Notes Store mutations', () => {
});
});
+ describe('SET_ISSUE_CONFIDENTIAL', () => {
+ let state;
+
+ beforeEach(() => {
+ state = { noteableData: { confidential: false } };
+ });
+
+ it('sets sort order', () => {
+ mutations.SET_ISSUE_CONFIDENTIAL(state, true);
+
+ expect(state.noteableData.confidential).toBe(true);
+ });
+ });
+
describe('UPDATE_ASSIGNEES', () => {
it('should update assignees', () => {
const state = {
diff --git a/spec/frontend/pager_spec.js b/spec/frontend/pager_spec.js
index d7177a32cde..47056c2804c 100644
--- a/spec/frontend/pager_spec.js
+++ b/spec/frontend/pager_spec.js
@@ -3,6 +3,7 @@ import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
import Pager from '~/pager';
import { removeParams } from '~/lib/utils/url_utility';
+import { TEST_HOST } from 'jest/helpers/test_constants';
jest.mock('~/lib/utils/url_utility', () => ({
removeParams: jest.fn().mockName('removeParams'),
@@ -32,7 +33,7 @@ describe('pager', () => {
});
it('should use data-href attribute from list element', () => {
- const href = `${gl.TEST_HOST}/some_list.json`;
+ const href = `${TEST_HOST}/some_list.json`;
setFixtures(`<div class="content_list" data-href="${href}"></div>`);
Pager.init();
@@ -40,7 +41,7 @@ describe('pager', () => {
});
it('should use current url if data-href attribute not provided', () => {
- const href = `${gl.TEST_HOST}/some_list`;
+ const href = `${TEST_HOST}/some_list`;
removeParams.mockReturnValue(href);
Pager.init();
@@ -56,7 +57,7 @@ describe('pager', () => {
it('keeps extra query parameters from url', () => {
window.history.replaceState({}, null, '?filter=test&offset=100');
- const href = `${gl.TEST_HOST}/some_list?filter=test`;
+ const href = `${TEST_HOST}/some_list?filter=test`;
removeParams.mockReturnValue(href);
Pager.init();
diff --git a/spec/frontend/pages/admin/jobs/index/components/stop_jobs_modal_spec.js b/spec/frontend/pages/admin/jobs/index/components/stop_jobs_modal_spec.js
index fe17c03389c..fb7a07b7bc7 100644
--- a/spec/frontend/pages/admin/jobs/index/components/stop_jobs_modal_spec.js
+++ b/spec/frontend/pages/admin/jobs/index/components/stop_jobs_modal_spec.js
@@ -3,6 +3,7 @@ import { redirectTo } from '~/lib/utils/url_utility';
import mountComponent from 'helpers/vue_mount_component_helper';
import axios from '~/lib/utils/axios_utils';
import stopJobsModal from '~/pages/admin/jobs/index/components/stop_jobs_modal.vue';
+import { TEST_HOST } from 'jest/helpers/test_constants';
jest.mock('~/lib/utils/url_utility', () => ({
...jest.requireActual('~/lib/utils/url_utility'),
@@ -11,7 +12,7 @@ jest.mock('~/lib/utils/url_utility', () => ({
describe('stop_jobs_modal.vue', () => {
const props = {
- url: `${gl.TEST_HOST}/stop_jobs_modal.vue/stopAll`,
+ url: `${TEST_HOST}/stop_jobs_modal.vue/stopAll`,
};
let vm;
@@ -26,7 +27,7 @@ describe('stop_jobs_modal.vue', () => {
describe('onSubmit', () => {
it('stops jobs and redirects to overview page', done => {
- const responseURL = `${gl.TEST_HOST}/stop_jobs_modal.vue/jobs`;
+ const responseURL = `${TEST_HOST}/stop_jobs_modal.vue/jobs`;
jest.spyOn(axios, 'post').mockImplementation(url => {
expect(url).toBe(props.url);
return Promise.resolve({
diff --git a/spec/frontend/pages/labels/components/promote_label_modal_spec.js b/spec/frontend/pages/labels/components/promote_label_modal_spec.js
index 9d5beca70b5..d4aabcc02f4 100644
--- a/spec/frontend/pages/labels/components/promote_label_modal_spec.js
+++ b/spec/frontend/pages/labels/components/promote_label_modal_spec.js
@@ -3,6 +3,7 @@ import mountComponent from 'helpers/vue_mount_component_helper';
import promoteLabelModal from '~/pages/projects/labels/components/promote_label_modal.vue';
import eventHub from '~/pages/projects/labels/event_hub';
import axios from '~/lib/utils/axios_utils';
+import { TEST_HOST } from 'jest/helpers/test_constants';
describe('Promote label modal', () => {
let vm;
@@ -11,7 +12,7 @@ describe('Promote label modal', () => {
labelTitle: 'Documentation',
labelColor: '#5cb85c',
labelTextColor: '#ffffff',
- url: `${gl.TEST_HOST}/dummy/promote/labels`,
+ url: `${TEST_HOST}/dummy/promote/labels`,
groupName: 'group',
};
@@ -51,7 +52,7 @@ describe('Promote label modal', () => {
});
it('redirects when a label is promoted', done => {
- const responseURL = `${gl.TEST_HOST}/dummy/endpoint`;
+ const responseURL = `${TEST_HOST}/dummy/endpoint`;
jest.spyOn(axios, 'post').mockImplementation(url => {
expect(url).toBe(labelMockData.url);
expect(eventHub.$emit).toHaveBeenCalledWith(
diff --git a/spec/frontend/pages/milestones/shared/components/delete_milestone_modal_spec.js b/spec/frontend/pages/milestones/shared/components/delete_milestone_modal_spec.js
index ff5dc6d8988..c376cf02594 100644
--- a/spec/frontend/pages/milestones/shared/components/delete_milestone_modal_spec.js
+++ b/spec/frontend/pages/milestones/shared/components/delete_milestone_modal_spec.js
@@ -4,6 +4,7 @@ import mountComponent from 'helpers/vue_mount_component_helper';
import axios from '~/lib/utils/axios_utils';
import deleteMilestoneModal from '~/pages/milestones/shared/components/delete_milestone_modal.vue';
import eventHub from '~/pages/milestones/shared/event_hub';
+import { TEST_HOST } from 'jest/helpers/test_constants';
jest.mock('~/lib/utils/url_utility', () => ({
...jest.requireActual('~/lib/utils/url_utility'),
@@ -17,7 +18,7 @@ describe('delete_milestone_modal.vue', () => {
mergeRequestCount: 2,
milestoneId: 3,
milestoneTitle: 'my milestone title',
- milestoneUrl: `${gl.TEST_HOST}/delete_milestone_modal.vue/milestone`,
+ milestoneUrl: `${TEST_HOST}/delete_milestone_modal.vue/milestone`,
};
let vm;
@@ -32,7 +33,7 @@ describe('delete_milestone_modal.vue', () => {
});
it('deletes milestone and redirects to overview page', done => {
- const responseURL = `${gl.TEST_HOST}/delete_milestone_modal.vue/milestoneOverview`;
+ const responseURL = `${TEST_HOST}/delete_milestone_modal.vue/milestoneOverview`;
jest.spyOn(axios, 'delete').mockImplementation(url => {
expect(url).toBe(props.milestoneUrl);
expect(eventHub.$emit).toHaveBeenCalledWith(
diff --git a/spec/frontend/pages/milestones/shared/components/promote_milestone_modal_spec.js b/spec/frontend/pages/milestones/shared/components/promote_milestone_modal_spec.js
index ff896354d96..87d32a67d47 100644
--- a/spec/frontend/pages/milestones/shared/components/promote_milestone_modal_spec.js
+++ b/spec/frontend/pages/milestones/shared/components/promote_milestone_modal_spec.js
@@ -3,13 +3,14 @@ import mountComponent from 'helpers/vue_mount_component_helper';
import promoteMilestoneModal from '~/pages/milestones/shared/components/promote_milestone_modal.vue';
import eventHub from '~/pages/milestones/shared/event_hub';
import axios from '~/lib/utils/axios_utils';
+import { TEST_HOST } from 'jest/helpers/test_constants';
describe('Promote milestone modal', () => {
let vm;
const Component = Vue.extend(promoteMilestoneModal);
const milestoneMockData = {
milestoneTitle: 'v1.0',
- url: `${gl.TEST_HOST}/dummy/promote/milestones`,
+ url: `${TEST_HOST}/dummy/promote/milestones`,
groupName: 'group',
};
@@ -46,7 +47,7 @@ describe('Promote milestone modal', () => {
});
it('redirects when a milestone is promoted', done => {
- const responseURL = `${gl.TEST_HOST}/dummy/endpoint`;
+ const responseURL = `${TEST_HOST}/dummy/endpoint`;
jest.spyOn(axios, 'post').mockImplementation(url => {
expect(url).toBe(milestoneMockData.url);
expect(eventHub.$emit).toHaveBeenCalledWith(
diff --git a/spec/frontend/pages/projects/forks/new/components/fork_groups_list_item_spec.js b/spec/frontend/pages/projects/forks/new/components/fork_groups_list_item_spec.js
new file mode 100644
index 00000000000..73e3c385d33
--- /dev/null
+++ b/spec/frontend/pages/projects/forks/new/components/fork_groups_list_item_spec.js
@@ -0,0 +1,78 @@
+import { shallowMount } from '@vue/test-utils';
+import { GlBadge, GlButton, GlLink } from '@gitlab/ui';
+import ForkGroupsListItem from '~/pages/projects/forks/new/components/fork_groups_list_item.vue';
+
+describe('Fork groups list item component', () => {
+ let wrapper;
+
+ const DEFAULT_PROPS = {
+ hasReachedProjectLimit: false,
+ };
+
+ const DEFAULT_GROUP_DATA = {
+ id: 22,
+ name: 'Gitlab Org',
+ description: 'Ad et ipsam earum id aut nobis.',
+ visibility: 'public',
+ full_name: 'Gitlab Org',
+ created_at: '2020-06-22T03:32:05.664Z',
+ updated_at: '2020-06-22T03:32:05.664Z',
+ avatar_url: null,
+ fork_path: '/twitter/typeahead-js/-/forks?namespace_key=22',
+ forked_project_path: null,
+ permission: 'Owner',
+ relative_path: '/gitlab-org',
+ markdown_description:
+ '<p data-sourcepos="1:1-1:31" dir="auto">Ad et ipsam earum id aut nobis.</p>',
+ can_create_project: true,
+ marked_for_deletion: false,
+ };
+
+ const DUMMY_PATH = '/dummy/path';
+
+ const createWrapper = propsData => {
+ wrapper = shallowMount(ForkGroupsListItem, {
+ propsData: {
+ ...DEFAULT_PROPS,
+ ...propsData,
+ },
+ });
+ };
+
+ it('renders pending removal badge if applicable', () => {
+ createWrapper({ group: { ...DEFAULT_GROUP_DATA, marked_for_deletion: true } });
+
+ expect(wrapper.find(GlBadge).text()).toBe('pending removal');
+ });
+
+ it('renders go to fork button if has forked project', () => {
+ createWrapper({ group: { ...DEFAULT_GROUP_DATA, forked_project_path: DUMMY_PATH } });
+
+ expect(wrapper.find(GlButton).text()).toBe('Go to fork');
+ expect(wrapper.find(GlButton).attributes().href).toBe(DUMMY_PATH);
+ });
+
+ it('renders select button if has no forked project', () => {
+ createWrapper({
+ group: { ...DEFAULT_GROUP_DATA, forked_project_path: null, fork_path: DUMMY_PATH },
+ });
+
+ expect(wrapper.find(GlButton).text()).toBe('Select');
+ expect(wrapper.find('form').attributes().action).toBe(DUMMY_PATH);
+ });
+
+ it('renders link to current group', () => {
+ const DUMMY_FULL_NAME = 'dummy';
+ createWrapper({
+ group: { ...DEFAULT_GROUP_DATA, relative_path: DUMMY_PATH, full_name: DUMMY_FULL_NAME },
+ });
+
+ expect(
+ wrapper
+ .findAll(GlLink)
+ .filter(w => w.text() === DUMMY_FULL_NAME)
+ .at(0)
+ .attributes().href,
+ ).toBe(DUMMY_PATH);
+ });
+});
diff --git a/spec/frontend/pages/projects/forks/new/components/fork_groups_list_spec.js b/spec/frontend/pages/projects/forks/new/components/fork_groups_list_spec.js
new file mode 100644
index 00000000000..979dff78eba
--- /dev/null
+++ b/spec/frontend/pages/projects/forks/new/components/fork_groups_list_spec.js
@@ -0,0 +1,133 @@
+import AxiosMockAdapter from 'axios-mock-adapter';
+import axios from '~/lib/utils/axios_utils';
+import { shallowMount } from '@vue/test-utils';
+import { GlLoadingIcon, GlSearchBoxByType } from '@gitlab/ui';
+import { nextTick } from 'vue';
+import createFlash from '~/flash';
+import ForkGroupsList from '~/pages/projects/forks/new/components/fork_groups_list.vue';
+import ForkGroupsListItem from '~/pages/projects/forks/new/components/fork_groups_list_item.vue';
+import waitForPromises from 'helpers/wait_for_promises';
+
+jest.mock('~/flash', () => jest.fn());
+
+describe('Fork groups list component', () => {
+ let wrapper;
+ let axiosMock;
+
+ const DEFAULT_PROPS = {
+ endpoint: '/dummy',
+ hasReachedProjectLimit: false,
+ };
+
+ const replyWith = (...args) => axiosMock.onGet(DEFAULT_PROPS.endpoint).reply(...args);
+
+ const createWrapper = propsData => {
+ wrapper = shallowMount(ForkGroupsList, {
+ propsData: {
+ ...DEFAULT_PROPS,
+ ...propsData,
+ },
+ stubs: {
+ GlTabs: {
+ template: '<div><slot></slot><slot name="tabs-end"></slot></div>',
+ },
+ },
+ });
+ };
+
+ beforeEach(() => {
+ axiosMock = new AxiosMockAdapter(axios);
+ });
+
+ afterEach(() => {
+ axiosMock.reset();
+
+ if (wrapper) {
+ wrapper.destroy();
+ wrapper = null;
+ }
+ });
+
+ it('fires load groups request on mount', async () => {
+ replyWith(200, { namespaces: [] });
+ createWrapper();
+
+ await waitForPromises();
+
+ expect(axiosMock.history.get[0].url).toBe(DEFAULT_PROPS.endpoint);
+ });
+
+ it('displays flash if loading groups fails', async () => {
+ replyWith(500);
+ createWrapper();
+
+ await waitForPromises();
+
+ expect(createFlash).toHaveBeenCalled();
+ });
+
+ it('displays loading indicator while loading groups', () => {
+ replyWith(() => new Promise(() => {}));
+ createWrapper();
+
+ expect(wrapper.contains(GlLoadingIcon)).toBe(true);
+ });
+
+ it('displays empty text if no groups are available', async () => {
+ const EMPTY_TEXT = 'No available groups to fork the project.';
+ replyWith(200, { namespaces: [] });
+ createWrapper();
+
+ await waitForPromises();
+
+ expect(wrapper.text()).toContain(EMPTY_TEXT);
+ });
+
+ it('displays filter field when groups are available', async () => {
+ replyWith(200, { namespaces: [{ name: 'dummy1' }, { name: 'dummy2' }] });
+ createWrapper();
+
+ await waitForPromises();
+
+ expect(wrapper.contains(GlSearchBoxByType)).toBe(true);
+ });
+
+ it('renders list items for each available group', async () => {
+ const namespaces = [{ name: 'dummy1' }, { name: 'dummy2' }, { name: 'otherdummy' }];
+ const hasReachedProjectLimit = true;
+
+ replyWith(200, { namespaces });
+ createWrapper({ hasReachedProjectLimit });
+
+ await waitForPromises();
+
+ expect(wrapper.findAll(ForkGroupsListItem)).toHaveLength(namespaces.length);
+
+ namespaces.forEach((namespace, idx) => {
+ expect(
+ wrapper
+ .findAll(ForkGroupsListItem)
+ .at(idx)
+ .props(),
+ ).toStrictEqual({ group: namespace, hasReachedProjectLimit });
+ });
+ });
+
+ it('filters repositories on the fly', async () => {
+ replyWith(200, {
+ namespaces: [{ name: 'dummy1' }, { name: 'dummy2' }, { name: 'otherdummy' }],
+ });
+ createWrapper();
+ await waitForPromises();
+ wrapper.find(GlSearchBoxByType).vm.$emit('input', 'other');
+ await nextTick();
+
+ expect(wrapper.findAll(ForkGroupsListItem)).toHaveLength(1);
+ expect(
+ wrapper
+ .findAll(ForkGroupsListItem)
+ .at(0)
+ .props().group.name,
+ ).toBe('otherdummy');
+ });
+});
diff --git a/spec/frontend/pages/projects/graphs/code_coverage_spec.js b/spec/frontend/pages/projects/graphs/code_coverage_spec.js
index 4990985b076..30c7ff78c6e 100644
--- a/spec/frontend/pages/projects/graphs/code_coverage_spec.js
+++ b/spec/frontend/pages/projects/graphs/code_coverage_spec.js
@@ -5,7 +5,7 @@ import { GlAreaChart } from '@gitlab/ui/dist/charts';
import axios from '~/lib/utils/axios_utils';
import CodeCoverage from '~/pages/projects/graphs/components/code_coverage.vue';
-import codeCoverageMockData from './mock_data';
+import { codeCoverageMockData, sortedDataByDates } from './mock_data';
import waitForPromises from 'helpers/wait_for_promises';
import httpStatusCodes from '~/lib/utils/http_status';
@@ -52,6 +52,10 @@ describe('Code Coverage', () => {
expect(findAreaChart().exists()).toBe(true);
});
+ it('sorts the dates in ascending order', () => {
+ expect(wrapper.vm.sortedData).toEqual(sortedDataByDates);
+ });
+
it('matches the snapshot', () => {
expect(wrapper.element).toMatchSnapshot();
});
diff --git a/spec/frontend/pages/projects/graphs/mock_data.js b/spec/frontend/pages/projects/graphs/mock_data.js
index a15f861ee7a..28d97b9d3f0 100644
--- a/spec/frontend/pages/projects/graphs/mock_data.js
+++ b/spec/frontend/pages/projects/graphs/mock_data.js
@@ -1,60 +1,69 @@
-export default [
+export const codeCoverageMockData = [
{
group_name: 'rspec',
data: [
- { date: '2020-04-30', coverage: 40.0 },
- { date: '2020-05-01', coverage: 80.0 },
- { date: '2020-05-02', coverage: 99.0 },
- { date: '2020-05-10', coverage: 80.0 },
- { date: '2020-05-15', coverage: 70.0 },
{ date: '2020-05-20', coverage: 69.0 },
+ { date: '2020-05-15', coverage: 70.0 },
+ { date: '2020-05-10', coverage: 80.0 },
+ { date: '2020-05-02', coverage: 99.0 },
+ { date: '2020-05-01', coverage: 80.0 },
+ { date: '2020-04-30', coverage: 40.0 },
],
},
{
group_name: 'cypress',
data: [
- { date: '2022-07-30', coverage: 1.0 },
- { date: '2022-08-01', coverage: 2.4 },
- { date: '2022-08-02', coverage: 5.0 },
- { date: '2022-08-10', coverage: 15.0 },
- { date: '2022-08-15', coverage: 30.0 },
{ date: '2022-08-20', coverage: 40.0 },
+ { date: '2022-08-15', coverage: 30.0 },
+ { date: '2022-08-10', coverage: 15.0 },
+ { date: '2022-08-02', coverage: 5.0 },
+ { date: '2022-08-01', coverage: 2.4 },
+ { date: '2022-07-30', coverage: 1.0 },
],
},
{
group_name: 'karma',
data: [
- { date: '2020-05-01', coverage: 94.0 },
- { date: '2020-05-02', coverage: 94.0 },
- { date: '2020-05-03', coverage: 94.0 },
- { date: '2020-05-04', coverage: 94.0 },
- { date: '2020-05-05', coverage: 92.0 },
- { date: '2020-05-06', coverage: 91.0 },
- { date: '2020-05-07', coverage: 78.0 },
- { date: '2020-05-08', coverage: 94.0 },
- { date: '2020-05-09', coverage: 94.0 },
- { date: '2020-05-10', coverage: 94.0 },
- { date: '2020-05-11', coverage: 94.0 },
- { date: '2020-05-12', coverage: 94.0 },
- { date: '2020-05-13', coverage: 92.0 },
- { date: '2020-05-14', coverage: 91.0 },
- { date: '2020-05-15', coverage: 78.0 },
- { date: '2020-05-16', coverage: 94.0 },
- { date: '2020-05-17', coverage: 94.0 },
- { date: '2020-05-18', coverage: 93.0 },
- { date: '2020-05-19', coverage: 92.0 },
- { date: '2020-05-20', coverage: 91.0 },
- { date: '2020-05-21', coverage: 90.0 },
- { date: '2020-05-22', coverage: 91.0 },
- { date: '2020-05-23', coverage: 92.0 },
- { date: '2020-05-24', coverage: 75.0 },
- { date: '2020-05-25', coverage: 74.0 },
- { date: '2020-05-26', coverage: 74.0 },
- { date: '2020-05-27', coverage: 74.0 },
- { date: '2020-05-28', coverage: 80.0 },
- { date: '2020-05-29', coverage: 85.0 },
- { date: '2020-05-30', coverage: 92.0 },
{ date: '2020-05-31', coverage: 91.0 },
+ { date: '2020-05-30', coverage: 94.0 },
+ { date: '2020-05-29', coverage: 94.0 },
+ { date: '2020-05-28', coverage: 92.0 },
+ { date: '2020-05-27', coverage: 91.0 },
+ { date: '2020-05-26', coverage: 78.0 },
+ { date: '2020-05-25', coverage: 94.0 },
+ { date: '2020-05-24', coverage: 94.0 },
+ { date: '2020-05-23', coverage: 94.0 },
+ { date: '2020-05-22', coverage: 94.0 },
+ { date: '2020-05-21', coverage: 94.0 },
+ { date: '2020-05-20', coverage: 92.0 },
+ { date: '2020-05-19', coverage: 91.0 },
+ { date: '2020-05-18', coverage: 78.0 },
+ { date: '2020-05-17', coverage: 94.0 },
+ { date: '2020-05-16', coverage: 94.0 },
+ { date: '2020-05-15', coverage: 93.0 },
+ { date: '2020-05-14', coverage: 92.0 },
+ { date: '2020-05-13', coverage: 91.0 },
+ { date: '2020-05-12', coverage: 90.0 },
+ { date: '2020-05-11', coverage: 91.0 },
+ { date: '2020-05-10', coverage: 92.0 },
+ { date: '2020-05-09', coverage: 75.0 },
+ { date: '2020-05-08', coverage: 74.0 },
+ { date: '2020-05-07', coverage: 74.0 },
+ { date: '2020-05-06', coverage: 74.0 },
+ { date: '2020-05-05', coverage: 80.0 },
+ { date: '2020-05-04', coverage: 85.0 },
+ { date: '2020-05-03', coverage: 92.0 },
+ { date: '2020-05-02', coverage: 94.0 },
+ { date: '2020-05-01', coverage: 94.0 },
],
},
];
+
+export const sortedDataByDates = [
+ { date: '2020-04-30', coverage: 40.0 },
+ { date: '2020-05-01', coverage: 80.0 },
+ { date: '2020-05-02', coverage: 99.0 },
+ { date: '2020-05-10', coverage: 80.0 },
+ { date: '2020-05-15', coverage: 70.0 },
+ { date: '2020-05-20', coverage: 69.0 },
+];
diff --git a/spec/frontend/pages/projects/pipeline_schedules/shared/components/interval_pattern_input_spec.js b/spec/frontend/pages/projects/pipeline_schedules/shared/components/interval_pattern_input_spec.js
index 9cc1d6eeb5a..9a119377542 100644
--- a/spec/frontend/pages/projects/pipeline_schedules/shared/components/interval_pattern_input_spec.js
+++ b/spec/frontend/pages/projects/pipeline_schedules/shared/components/interval_pattern_input_spec.js
@@ -1,4 +1,5 @@
-import { shallowMount } from '@vue/test-utils';
+import { mount } from '@vue/test-utils';
+import { trimText } from 'helpers/text_helper';
import IntervalPatternInput from '~/pages/projects/pipeline_schedules/shared/components/interval_pattern_input.vue';
describe('Interval Pattern Input Component', () => {
@@ -14,15 +15,22 @@ describe('Interval Pattern Input Component', () => {
everyWeek: `0 ${mockHour} * * ${mockWeekDayIndex}`,
everyMonth: `0 ${mockHour} ${mockDay} * *`,
};
-
- const findEveryDayRadio = () => wrapper.find('#every-day');
- const findEveryWeekRadio = () => wrapper.find('#every-week');
- const findEveryMonthRadio = () => wrapper.find('#every-month');
- const findCustomRadio = () => wrapper.find('#custom');
+ const customKey = 'custom';
+ const everyDayKey = 'everyDay';
+ const cronIntervalNotInPreset = `0 12 * * *`;
+
+ const findEveryDayRadio = () => wrapper.find(`[data-testid=${everyDayKey}]`);
+ const findEveryWeekRadio = () => wrapper.find('[data-testid="everyWeek"]');
+ const findEveryMonthRadio = () => wrapper.find('[data-testid="everyMonth"]');
+ const findCustomRadio = () => wrapper.find(`[data-testid="${customKey}"]`);
const findCustomInput = () => wrapper.find('#schedule_cron');
- const selectEveryDayRadio = () => findEveryDayRadio().setChecked();
- const selectEveryWeekRadio = () => findEveryWeekRadio().setChecked();
- const selectEveryMonthRadio = () => findEveryMonthRadio().setChecked();
+ const findAllLabels = () => wrapper.findAll('label');
+ const findSelectedRadio = () =>
+ wrapper.findAll('input[type="radio"]').wrappers.find(x => x.element.checked);
+ const findSelectedRadioKey = () => findSelectedRadio()?.attributes('data-testid');
+ const selectEveryDayRadio = () => findEveryDayRadio().trigger('click');
+ const selectEveryWeekRadio = () => findEveryWeekRadio().trigger('click');
+ const selectEveryMonthRadio = () => findEveryMonthRadio().trigger('click');
const selectCustomRadio = () => findCustomRadio().trigger('click');
const createWrapper = (props = {}, data = {}) => {
@@ -30,7 +38,7 @@ describe('Interval Pattern Input Component', () => {
throw new Error('A wrapper already exists');
}
- wrapper = shallowMount(IntervalPatternInput, {
+ wrapper = mount(IntervalPatternInput, {
propsData: { ...props },
data() {
return {
@@ -63,8 +71,8 @@ describe('Interval Pattern Input Component', () => {
createWrapper();
});
- it('to a non empty string when no initial value is not passed', () => {
- expect(findCustomInput()).not.toBe('');
+ it('defaults to every day value when no `initialCronInterval` is passed', () => {
+ expect(findCustomInput().element.value).toBe(cronIntervalPresets.everyDay);
});
});
@@ -85,20 +93,20 @@ describe('Interval Pattern Input Component', () => {
createWrapper();
});
- it('when a default option is selected', () => {
+ it('when a default option is selected', async () => {
selectEveryDayRadio();
- return wrapper.vm.$nextTick().then(() => {
- expect(findCustomInput().attributes('disabled')).toBeUndefined();
- });
+ await wrapper.vm.$nextTick();
+
+ expect(findCustomInput().attributes('disabled')).toBeUndefined();
});
- it('when the custom option is selected', () => {
+ it('when the custom option is selected', async () => {
selectCustomRadio();
- return wrapper.vm.$nextTick().then(() => {
- expect(findCustomInput().attributes('disabled')).toBeUndefined();
- });
+ await wrapper.vm.$nextTick();
+
+ expect(findCustomInput().attributes('disabled')).toBeUndefined();
});
});
@@ -115,40 +123,83 @@ describe('Interval Pattern Input Component', () => {
});
});
+ describe('Time strings', () => {
+ beforeEach(() => {
+ createWrapper();
+ });
+
+ it('renders each label for radio options properly', () => {
+ const labels = findAllLabels().wrappers.map(el => trimText(el.text()));
+
+ expect(labels).toEqual([
+ 'Every day (at 4:00am)',
+ 'Every week (Monday at 4:00am)',
+ 'Every month (Day 1 at 4:00am)',
+ 'Custom ( Cron syntax )',
+ ]);
+ });
+ });
+
describe('User Actions with radio buttons', () => {
- it.each`
- desc | initialCronInterval | act | expectedValue
- ${'when everyday is selected, update value'} | ${'1 2 3 4 5'} | ${selectEveryDayRadio} | ${cronIntervalPresets.everyDay}
- ${'when everyweek is selected, update value'} | ${'1 2 3 4 5'} | ${selectEveryWeekRadio} | ${cronIntervalPresets.everyWeek}
- ${'when everymonth is selected, update value'} | ${'1 2 3 4 5'} | ${selectEveryMonthRadio} | ${cronIntervalPresets.everyMonth}
- ${'when custom is selected, add space to value'} | ${cronIntervalPresets.everyMonth} | ${selectCustomRadio} | ${`${cronIntervalPresets.everyMonth} `}
- `('$desc', ({ initialCronInterval, act, expectedValue }) => {
- createWrapper({ initialCronInterval });
+ describe('Default option', () => {
+ beforeEach(() => {
+ createWrapper();
+ });
+
+ it('when everyday is selected, update value', async () => {
+ selectEveryWeekRadio();
+ await wrapper.vm.$nextTick();
+ expect(findCustomInput().element.value).toBe(cronIntervalPresets.everyWeek);
+
+ selectEveryDayRadio();
+ await wrapper.vm.$nextTick();
+ expect(findCustomInput().element.value).toBe(cronIntervalPresets.everyDay);
+ });
+ });
+
+ describe('Other options', () => {
+ it.each`
+ desc | initialCronInterval | act | expectedValue
+ ${'when everyweek is selected, update value'} | ${'1 2 3 4 5'} | ${selectEveryWeekRadio} | ${cronIntervalPresets.everyWeek}
+ ${'when everymonth is selected, update value'} | ${'1 2 3 4 5'} | ${selectEveryMonthRadio} | ${cronIntervalPresets.everyMonth}
+ ${'when custom is selected, value remains the same'} | ${cronIntervalPresets.everyMonth} | ${selectCustomRadio} | ${cronIntervalPresets.everyMonth}
+ `('$desc', async ({ initialCronInterval, act, expectedValue }) => {
+ createWrapper({ initialCronInterval });
+
+ act();
- act();
+ await wrapper.vm.$nextTick();
- return wrapper.vm.$nextTick().then(() => {
expect(findCustomInput().element.value).toBe(expectedValue);
});
});
});
+
describe('User actions with input field for Cron syntax', () => {
beforeEach(() => {
createWrapper();
});
- it('when editing the cron input it selects the custom radio button', () => {
+ it('when editing the cron input it selects the custom radio button', async () => {
const newValue = '0 * * * *';
+ expect(findSelectedRadioKey()).toBe(everyDayKey);
+
findCustomInput().setValue(newValue);
- expect(wrapper.vm.cronInterval).toBe(newValue);
+ await wrapper.vm.$nextTick;
+
+ expect(findSelectedRadioKey()).toBe(customKey);
});
+ });
- it('when value of input is one of the defaults, it selects the corresponding radio button', () => {
- findCustomInput().setValue(cronIntervalPresets.everyWeek);
+ describe('Edit form field', () => {
+ beforeEach(() => {
+ createWrapper({ initialCronInterval: cronIntervalNotInPreset });
+ });
- expect(wrapper.vm.cronInterval).toBe(cronIntervalPresets.everyWeek);
+ it('loads with the custom option being selected', () => {
+ expect(findSelectedRadioKey()).toBe(customKey);
});
});
});
diff --git a/spec/frontend/persistent_user_callout_spec.js b/spec/frontend/persistent_user_callout_spec.js
index db324990e71..97985ba3a07 100644
--- a/spec/frontend/persistent_user_callout_spec.js
+++ b/spec/frontend/persistent_user_callout_spec.js
@@ -43,6 +43,23 @@ describe('PersistentUserCallout', () => {
return fixture;
}
+ function createFollowLinkFixture() {
+ const fixture = document.createElement('div');
+ fixture.innerHTML = `
+ <ul>
+ <li
+ class="container"
+ data-dismiss-endpoint="${dismissEndpoint}"
+ data-feature-id="${featureName}"
+ >
+ <a class="js-follow-link" href="/somewhere-pleasant">A Link</a>
+ </li>
+ </ul>
+ `;
+
+ return fixture;
+ }
+
describe('dismiss', () => {
let button;
let mockAxios;
@@ -144,6 +161,55 @@ describe('PersistentUserCallout', () => {
});
});
+ describe('follow links', () => {
+ let link;
+ let mockAxios;
+ let persistentUserCallout;
+
+ beforeEach(() => {
+ const fixture = createFollowLinkFixture();
+ const container = fixture.querySelector('.container');
+ link = fixture.querySelector('.js-follow-link');
+ mockAxios = new MockAdapter(axios);
+
+ persistentUserCallout = new PersistentUserCallout(container);
+ jest.spyOn(persistentUserCallout.container, 'remove').mockImplementation(() => {});
+
+ delete window.location;
+ window.location = { assign: jest.fn() };
+ });
+
+ afterEach(() => {
+ mockAxios.restore();
+ });
+
+ it('uses a link to trigger callout and defers following until callout is finished', () => {
+ const { href } = link;
+ mockAxios.onPost(dismissEndpoint).replyOnce(200);
+
+ link.click();
+
+ return waitForPromises().then(() => {
+ expect(window.location.assign).toBeCalledWith(href);
+ expect(mockAxios.history.post[0].data).toBe(JSON.stringify({ feature_name: featureName }));
+ expect(persistentUserCallout.container.remove).not.toHaveBeenCalled();
+ });
+ });
+
+ it('invokes Flash when the dismiss request fails', () => {
+ mockAxios.onPost(dismissEndpoint).replyOnce(500);
+
+ link.click();
+
+ return waitForPromises().then(() => {
+ expect(window.location.assign).not.toHaveBeenCalled();
+ expect(Flash).toHaveBeenCalledWith(
+ 'An error occurred while acknowledging the notification. Refresh the page and try again.',
+ );
+ });
+ });
+ });
+
describe('factory', () => {
it('returns an instance of PersistentUserCallout with the provided container property', () => {
const fixture = createFixture();
diff --git a/spec/frontend/pipelines/blank_state_spec.js b/spec/frontend/pipelines/blank_state_spec.js
index 033bd5ccb73..bb069fdc2c8 100644
--- a/spec/frontend/pipelines/blank_state_spec.js
+++ b/spec/frontend/pipelines/blank_state_spec.js
@@ -1,5 +1,5 @@
import Vue from 'vue';
-import component from '~/pipelines/components/blank_state.vue';
+import component from '~/pipelines/components/pipelines_list/blank_state.vue';
import mountComponent from '../helpers/vue_mount_component_helper';
describe('Pipelines Blank State', () => {
diff --git a/spec/frontend/pipelines/components/dag/__snapshots__/dag_graph_spec.js.snap b/spec/frontend/pipelines/components/dag/__snapshots__/dag_graph_spec.js.snap
index 629efc6d3fa..cb5f6ff5307 100644
--- a/spec/frontend/pipelines/components/dag/__snapshots__/dag_graph_spec.js.snap
+++ b/spec/frontend/pipelines/components/dag/__snapshots__/dag_graph_spec.js.snap
@@ -3,7 +3,7 @@
exports[`The DAG graph in the basic case renders the graph svg 1`] = `
"<svg viewBox=\\"0,0,1000,540\\" width=\\"1000\\" height=\\"540\\">
<g fill=\\"none\\" stroke-opacity=\\"0.8\\">
- <g id=\\"dag-link43\\" class=\\"dag-link gl-cursor-pointer\\">
+ <g id=\\"dag-link43\\" class=\\"dag-link gl-transition-property-stroke-opacity gl-cursor-pointer gl-transition-duration-slow gl-transition-timing-function-ease\\">
<linearGradient id=\\"dag-grad53\\" gradientUnits=\\"userSpaceOnUse\\" x1=\\"116\\" x2=\\"361.3333333333333\\">
<stop offset=\\"0%\\" stop-color=\\"#e17223\\"></stop>
<stop offset=\\"100%\\" stop-color=\\"#83ab4a\\"></stop>
@@ -20,7 +20,7 @@ exports[`The DAG graph in the basic case renders the graph svg 1`] = `
</clipPath>
<path d=\\"M108,129L190,129L190,129L369.3333333333333,129\\" stroke=\\"url(#dag-grad53)\\" style=\\"stroke-linejoin: round;\\" stroke-width=\\"56\\" clip-path=\\"url(#dag-clip63)\\"></path>
</g>
- <g id=\\"dag-link44\\" class=\\"dag-link gl-cursor-pointer\\">
+ <g id=\\"dag-link44\\" class=\\"dag-link gl-transition-property-stroke-opacity gl-cursor-pointer gl-transition-duration-slow gl-transition-timing-function-ease\\">
<linearGradient id=\\"dag-grad54\\" gradientUnits=\\"userSpaceOnUse\\" x1=\\"377.3333333333333\\" x2=\\"622.6666666666666\\">
<stop offset=\\"0%\\" stop-color=\\"#83ab4a\\"></stop>
<stop offset=\\"100%\\" stop-color=\\"#6f3500\\"></stop>
@@ -37,7 +37,7 @@ exports[`The DAG graph in the basic case renders the graph svg 1`] = `
</clipPath>
<path d=\\"M369.3333333333333,129L509.3333333333333,129L509.3333333333333,129.0000000000002L630.6666666666666,129.0000000000002\\" stroke=\\"url(#dag-grad54)\\" style=\\"stroke-linejoin: round;\\" stroke-width=\\"56\\" clip-path=\\"url(#dag-clip64)\\"></path>
</g>
- <g id=\\"dag-link45\\" class=\\"dag-link gl-cursor-pointer\\">
+ <g id=\\"dag-link45\\" class=\\"dag-link gl-transition-property-stroke-opacity gl-cursor-pointer gl-transition-duration-slow gl-transition-timing-function-ease\\">
<linearGradient id=\\"dag-grad55\\" gradientUnits=\\"userSpaceOnUse\\" x1=\\"116\\" x2=\\"622.6666666666666\\">
<stop offset=\\"0%\\" stop-color=\\"#5772ff\\"></stop>
<stop offset=\\"100%\\" stop-color=\\"#6f3500\\"></stop>
@@ -54,7 +54,7 @@ exports[`The DAG graph in the basic case renders the graph svg 1`] = `
</clipPath>
<path d=\\"M108,212.00000000000003L306,212.00000000000003L306,187.0000000000002L630.6666666666666,187.0000000000002\\" stroke=\\"url(#dag-grad55)\\" style=\\"stroke-linejoin: round;\\" stroke-width=\\"56\\" clip-path=\\"url(#dag-clip65)\\"></path>
</g>
- <g id=\\"dag-link46\\" class=\\"dag-link gl-cursor-pointer\\">
+ <g id=\\"dag-link46\\" class=\\"dag-link gl-transition-property-stroke-opacity gl-cursor-pointer gl-transition-duration-slow gl-transition-timing-function-ease\\">
<linearGradient id=\\"dag-grad56\\" gradientUnits=\\"userSpaceOnUse\\" x1=\\"116\\" x2=\\"361.3333333333333\\">
<stop offset=\\"0%\\" stop-color=\\"#b24800\\"></stop>
<stop offset=\\"100%\\" stop-color=\\"#006887\\"></stop>
@@ -71,7 +71,7 @@ exports[`The DAG graph in the basic case renders the graph svg 1`] = `
</clipPath>
<path d=\\"M108,295L338.93333333333334,295L338.93333333333334,269.9999999999998L369.3333333333333,269.9999999999998\\" stroke=\\"url(#dag-grad56)\\" style=\\"stroke-linejoin: round;\\" stroke-width=\\"56\\" clip-path=\\"url(#dag-clip66)\\"></path>
</g>
- <g id=\\"dag-link47\\" class=\\"dag-link gl-cursor-pointer\\">
+ <g id=\\"dag-link47\\" class=\\"dag-link gl-transition-property-stroke-opacity gl-cursor-pointer gl-transition-duration-slow gl-transition-timing-function-ease\\">
<linearGradient id=\\"dag-grad57\\" gradientUnits=\\"userSpaceOnUse\\" x1=\\"116\\" x2=\\"361.3333333333333\\">
<stop offset=\\"0%\\" stop-color=\\"#25d2d2\\"></stop>
<stop offset=\\"100%\\" stop-color=\\"#487900\\"></stop>
@@ -88,7 +88,7 @@ exports[`The DAG graph in the basic case renders the graph svg 1`] = `
</clipPath>
<path d=\\"M108,378.00000000000006L144.66666666666669,378.00000000000006L144.66666666666669,352.99999999999994L369.3333333333333,352.99999999999994\\" stroke=\\"url(#dag-grad57)\\" style=\\"stroke-linejoin: round;\\" stroke-width=\\"56\\" clip-path=\\"url(#dag-clip67)\\"></path>
</g>
- <g id=\\"dag-link48\\" class=\\"dag-link gl-cursor-pointer\\">
+ <g id=\\"dag-link48\\" class=\\"dag-link gl-transition-property-stroke-opacity gl-cursor-pointer gl-transition-duration-slow gl-transition-timing-function-ease\\">
<linearGradient id=\\"dag-grad58\\" gradientUnits=\\"userSpaceOnUse\\" x1=\\"377.3333333333333\\" x2=\\"622.6666666666666\\">
<stop offset=\\"0%\\" stop-color=\\"#006887\\"></stop>
<stop offset=\\"100%\\" stop-color=\\"#d84280\\"></stop>
@@ -105,7 +105,7 @@ exports[`The DAG graph in the basic case renders the graph svg 1`] = `
</clipPath>
<path d=\\"M369.3333333333333,269.9999999999998L464,269.9999999999998L464,270.0000000000001L630.6666666666666,270.0000000000001\\" stroke=\\"url(#dag-grad58)\\" style=\\"stroke-linejoin: round;\\" stroke-width=\\"56\\" clip-path=\\"url(#dag-clip68)\\"></path>
</g>
- <g id=\\"dag-link49\\" class=\\"dag-link gl-cursor-pointer\\">
+ <g id=\\"dag-link49\\" class=\\"dag-link gl-transition-property-stroke-opacity gl-cursor-pointer gl-transition-duration-slow gl-transition-timing-function-ease\\">
<linearGradient id=\\"dag-grad59\\" gradientUnits=\\"userSpaceOnUse\\" x1=\\"377.3333333333333\\" x2=\\"622.6666666666666\\">
<stop offset=\\"0%\\" stop-color=\\"#487900\\"></stop>
<stop offset=\\"100%\\" stop-color=\\"#d84280\\"></stop>
@@ -122,7 +122,7 @@ exports[`The DAG graph in the basic case renders the graph svg 1`] = `
</clipPath>
<path d=\\"M369.3333333333333,352.99999999999994L522,352.99999999999994L522,328.0000000000001L630.6666666666666,328.0000000000001\\" stroke=\\"url(#dag-grad59)\\" style=\\"stroke-linejoin: round;\\" stroke-width=\\"56\\" clip-path=\\"url(#dag-clip69)\\"></path>
</g>
- <g id=\\"dag-link50\\" class=\\"dag-link gl-cursor-pointer\\">
+ <g id=\\"dag-link50\\" class=\\"dag-link gl-transition-property-stroke-opacity gl-cursor-pointer gl-transition-duration-slow gl-transition-timing-function-ease\\">
<linearGradient id=\\"dag-grad60\\" gradientUnits=\\"userSpaceOnUse\\" x1=\\"377.3333333333333\\" x2=\\"622.6666666666666\\">
<stop offset=\\"0%\\" stop-color=\\"#487900\\"></stop>
<stop offset=\\"100%\\" stop-color=\\"#3547de\\"></stop>
@@ -139,7 +139,7 @@ exports[`The DAG graph in the basic case renders the graph svg 1`] = `
</clipPath>
<path d=\\"M369.3333333333333,410.99999999999994L580,410.99999999999994L580,411L630.6666666666666,411\\" stroke=\\"url(#dag-grad60)\\" style=\\"stroke-linejoin: round;\\" stroke-width=\\"56\\" clip-path=\\"url(#dag-clip70)\\"></path>
</g>
- <g id=\\"dag-link51\\" class=\\"dag-link gl-cursor-pointer\\">
+ <g id=\\"dag-link51\\" class=\\"dag-link gl-transition-property-stroke-opacity gl-cursor-pointer gl-transition-duration-slow gl-transition-timing-function-ease\\">
<linearGradient id=\\"dag-grad61\\" gradientUnits=\\"userSpaceOnUse\\" x1=\\"638.6666666666666\\" x2=\\"884\\">
<stop offset=\\"0%\\" stop-color=\\"#d84280\\"></stop>
<stop offset=\\"100%\\" stop-color=\\"#006887\\"></stop>
@@ -156,7 +156,7 @@ exports[`The DAG graph in the basic case renders the graph svg 1`] = `
</clipPath>
<path d=\\"M630.6666666666666,270.0000000000001L861.6,270.0000000000001L861.6,270.1890725105691L892,270.1890725105691\\" stroke=\\"url(#dag-grad61)\\" style=\\"stroke-linejoin: round;\\" stroke-width=\\"56\\" clip-path=\\"url(#dag-clip71)\\"></path>
</g>
- <g id=\\"dag-link52\\" class=\\"dag-link gl-cursor-pointer\\">
+ <g id=\\"dag-link52\\" class=\\"dag-link gl-transition-property-stroke-opacity gl-cursor-pointer gl-transition-duration-slow gl-transition-timing-function-ease\\">
<linearGradient id=\\"dag-grad62\\" gradientUnits=\\"userSpaceOnUse\\" x1=\\"638.6666666666666\\" x2=\\"884\\">
<stop offset=\\"0%\\" stop-color=\\"#3547de\\"></stop>
<stop offset=\\"100%\\" stop-color=\\"#275600\\"></stop>
@@ -175,18 +175,18 @@ exports[`The DAG graph in the basic case renders the graph svg 1`] = `
</g>
</g>
<g>
- <line class=\\"dag-node gl-cursor-pointer\\" id=\\"dag-node73\\" stroke=\\"#e17223\\" stroke-width=\\"16\\" stroke-linecap=\\"round\\" x1=\\"108\\" x2=\\"108\\" y1=\\"104\\" y2=\\"154.00000000000003\\"></line>
- <line class=\\"dag-node gl-cursor-pointer\\" id=\\"dag-node74\\" stroke=\\"#83ab4a\\" stroke-width=\\"16\\" stroke-linecap=\\"round\\" x1=\\"369\\" x2=\\"369\\" y1=\\"104\\" y2=\\"154\\"></line>
- <line class=\\"dag-node gl-cursor-pointer\\" id=\\"dag-node75\\" stroke=\\"#5772ff\\" stroke-width=\\"16\\" stroke-linecap=\\"round\\" x1=\\"108\\" x2=\\"108\\" y1=\\"187.00000000000003\\" y2=\\"237.00000000000003\\"></line>
- <line class=\\"dag-node gl-cursor-pointer\\" id=\\"dag-node76\\" stroke=\\"#b24800\\" stroke-width=\\"16\\" stroke-linecap=\\"round\\" x1=\\"108\\" x2=\\"108\\" y1=\\"270\\" y2=\\"320.00000000000006\\"></line>
- <line class=\\"dag-node gl-cursor-pointer\\" id=\\"dag-node77\\" stroke=\\"#25d2d2\\" stroke-width=\\"16\\" stroke-linecap=\\"round\\" x1=\\"108\\" x2=\\"108\\" y1=\\"353.00000000000006\\" y2=\\"403.0000000000001\\"></line>
- <line class=\\"dag-node gl-cursor-pointer\\" id=\\"dag-node78\\" stroke=\\"#6f3500\\" stroke-width=\\"16\\" stroke-linecap=\\"round\\" x1=\\"630\\" x2=\\"630\\" y1=\\"104.0000000000002\\" y2=\\"212.00000000000009\\"></line>
- <line class=\\"dag-node gl-cursor-pointer\\" id=\\"dag-node79\\" stroke=\\"#006887\\" stroke-width=\\"16\\" stroke-linecap=\\"round\\" x1=\\"369\\" x2=\\"369\\" y1=\\"244.99999999999977\\" y2=\\"294.99999999999994\\"></line>
- <line class=\\"dag-node gl-cursor-pointer\\" id=\\"dag-node80\\" stroke=\\"#487900\\" stroke-width=\\"16\\" stroke-linecap=\\"round\\" x1=\\"369\\" x2=\\"369\\" y1=\\"327.99999999999994\\" y2=\\"436\\"></line>
- <line class=\\"dag-node gl-cursor-pointer\\" id=\\"dag-node81\\" stroke=\\"#d84280\\" stroke-width=\\"16\\" stroke-linecap=\\"round\\" x1=\\"630\\" x2=\\"630\\" y1=\\"245.00000000000009\\" y2=\\"353\\"></line>
- <line class=\\"dag-node gl-cursor-pointer\\" id=\\"dag-node82\\" stroke=\\"#3547de\\" stroke-width=\\"16\\" stroke-linecap=\\"round\\" x1=\\"630\\" x2=\\"630\\" y1=\\"386\\" y2=\\"436\\"></line>
- <line class=\\"dag-node gl-cursor-pointer\\" id=\\"dag-node83\\" stroke=\\"#006887\\" stroke-width=\\"16\\" stroke-linecap=\\"round\\" x1=\\"892\\" x2=\\"892\\" y1=\\"245.18907251056908\\" y2=\\"295.1890725105691\\"></line>
- <line class=\\"dag-node gl-cursor-pointer\\" id=\\"dag-node84\\" stroke=\\"#275600\\" stroke-width=\\"16\\" stroke-linecap=\\"round\\" x1=\\"892\\" x2=\\"892\\" y1=\\"386\\" y2=\\"436\\"></line>
+ <line class=\\"dag-node gl-transition-property-stroke gl-cursor-pointer gl-transition-duration-slow gl-transition-timing-function-ease\\" id=\\"dag-node73\\" stroke=\\"#e17223\\" stroke-width=\\"16\\" stroke-linecap=\\"round\\" x1=\\"108\\" x2=\\"108\\" y1=\\"104\\" y2=\\"154.00000000000003\\"></line>
+ <line class=\\"dag-node gl-transition-property-stroke gl-cursor-pointer gl-transition-duration-slow gl-transition-timing-function-ease\\" id=\\"dag-node74\\" stroke=\\"#83ab4a\\" stroke-width=\\"16\\" stroke-linecap=\\"round\\" x1=\\"369\\" x2=\\"369\\" y1=\\"104\\" y2=\\"154\\"></line>
+ <line class=\\"dag-node gl-transition-property-stroke gl-cursor-pointer gl-transition-duration-slow gl-transition-timing-function-ease\\" id=\\"dag-node75\\" stroke=\\"#5772ff\\" stroke-width=\\"16\\" stroke-linecap=\\"round\\" x1=\\"108\\" x2=\\"108\\" y1=\\"187.00000000000003\\" y2=\\"237.00000000000003\\"></line>
+ <line class=\\"dag-node gl-transition-property-stroke gl-cursor-pointer gl-transition-duration-slow gl-transition-timing-function-ease\\" id=\\"dag-node76\\" stroke=\\"#b24800\\" stroke-width=\\"16\\" stroke-linecap=\\"round\\" x1=\\"108\\" x2=\\"108\\" y1=\\"270\\" y2=\\"320.00000000000006\\"></line>
+ <line class=\\"dag-node gl-transition-property-stroke gl-cursor-pointer gl-transition-duration-slow gl-transition-timing-function-ease\\" id=\\"dag-node77\\" stroke=\\"#25d2d2\\" stroke-width=\\"16\\" stroke-linecap=\\"round\\" x1=\\"108\\" x2=\\"108\\" y1=\\"353.00000000000006\\" y2=\\"403.0000000000001\\"></line>
+ <line class=\\"dag-node gl-transition-property-stroke gl-cursor-pointer gl-transition-duration-slow gl-transition-timing-function-ease\\" id=\\"dag-node78\\" stroke=\\"#6f3500\\" stroke-width=\\"16\\" stroke-linecap=\\"round\\" x1=\\"630\\" x2=\\"630\\" y1=\\"104.0000000000002\\" y2=\\"212.00000000000009\\"></line>
+ <line class=\\"dag-node gl-transition-property-stroke gl-cursor-pointer gl-transition-duration-slow gl-transition-timing-function-ease\\" id=\\"dag-node79\\" stroke=\\"#006887\\" stroke-width=\\"16\\" stroke-linecap=\\"round\\" x1=\\"369\\" x2=\\"369\\" y1=\\"244.99999999999977\\" y2=\\"294.99999999999994\\"></line>
+ <line class=\\"dag-node gl-transition-property-stroke gl-cursor-pointer gl-transition-duration-slow gl-transition-timing-function-ease\\" id=\\"dag-node80\\" stroke=\\"#487900\\" stroke-width=\\"16\\" stroke-linecap=\\"round\\" x1=\\"369\\" x2=\\"369\\" y1=\\"327.99999999999994\\" y2=\\"436\\"></line>
+ <line class=\\"dag-node gl-transition-property-stroke gl-cursor-pointer gl-transition-duration-slow gl-transition-timing-function-ease\\" id=\\"dag-node81\\" stroke=\\"#d84280\\" stroke-width=\\"16\\" stroke-linecap=\\"round\\" x1=\\"630\\" x2=\\"630\\" y1=\\"245.00000000000009\\" y2=\\"353\\"></line>
+ <line class=\\"dag-node gl-transition-property-stroke gl-cursor-pointer gl-transition-duration-slow gl-transition-timing-function-ease\\" id=\\"dag-node82\\" stroke=\\"#3547de\\" stroke-width=\\"16\\" stroke-linecap=\\"round\\" x1=\\"630\\" x2=\\"630\\" y1=\\"386\\" y2=\\"436\\"></line>
+ <line class=\\"dag-node gl-transition-property-stroke gl-cursor-pointer gl-transition-duration-slow gl-transition-timing-function-ease\\" id=\\"dag-node83\\" stroke=\\"#006887\\" stroke-width=\\"16\\" stroke-linecap=\\"round\\" x1=\\"892\\" x2=\\"892\\" y1=\\"245.18907251056908\\" y2=\\"295.1890725105691\\"></line>
+ <line class=\\"dag-node gl-transition-property-stroke gl-cursor-pointer gl-transition-duration-slow gl-transition-timing-function-ease\\" id=\\"dag-node84\\" stroke=\\"#275600\\" stroke-width=\\"16\\" stroke-linecap=\\"round\\" x1=\\"892\\" x2=\\"892\\" y1=\\"386\\" y2=\\"436\\"></line>
</g>
<g class=\\"gl-font-sm\\">
<foreignObject requiredFeatures=\\"http://www.w3.org/TR/SVG11/feature#Extensibility\\" height=\\"58.00000000000003px\\" width=\\"84\\" x=\\"8\\" y=\\"100\\" class=\\"gl-overflow-visible\\">
diff --git a/spec/frontend/pipelines/components/dag/dag_annotations_spec.js b/spec/frontend/pipelines/components/dag/dag_annotations_spec.js
new file mode 100644
index 00000000000..5747c91bee8
--- /dev/null
+++ b/spec/frontend/pipelines/components/dag/dag_annotations_spec.js
@@ -0,0 +1,112 @@
+import { shallowMount, mount } from '@vue/test-utils';
+import { GlButton } from '@gitlab/ui';
+import DagAnnotations from '~/pipelines/components/dag/dag_annotations.vue';
+import { singleNote, multiNote } from './mock_data';
+
+describe('The DAG annotations', () => {
+ let wrapper;
+
+ const getColorBlock = () => wrapper.find('[data-testid="dag-color-block"]');
+ const getAllColorBlocks = () => wrapper.findAll('[data-testid="dag-color-block"]');
+ const getTextBlock = () => wrapper.find('[data-testid="dag-note-text"]');
+ const getAllTextBlocks = () => wrapper.findAll('[data-testid="dag-note-text"]');
+ const getToggleButton = () => wrapper.find(GlButton);
+
+ const createComponent = (propsData = {}, method = shallowMount) => {
+ if (wrapper?.destroy) {
+ wrapper.destroy();
+ }
+
+ wrapper = method(DagAnnotations, {
+ propsData,
+ data() {
+ return {
+ showList: true,
+ };
+ },
+ });
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ wrapper = null;
+ });
+
+ describe('when there is one annotation', () => {
+ const currentNote = singleNote['dag-link103'];
+
+ beforeEach(() => {
+ createComponent({ annotations: singleNote });
+ });
+
+ it('displays the color block', () => {
+ expect(getColorBlock().exists()).toBe(true);
+ });
+
+ it('displays the text block', () => {
+ expect(getTextBlock().exists()).toBe(true);
+ expect(getTextBlock().text()).toBe(`${currentNote.source.name} → ${currentNote.target.name}`);
+ });
+
+ it('does not display the list toggle link', () => {
+ expect(getToggleButton().exists()).toBe(false);
+ });
+ });
+
+ describe('when there are multiple annoataions', () => {
+ beforeEach(() => {
+ createComponent({ annotations: multiNote });
+ });
+
+ it('displays a color block for each link', () => {
+ expect(getAllColorBlocks().length).toBe(Object.keys(multiNote).length);
+ });
+
+ it('displays a text block for each link', () => {
+ expect(getAllTextBlocks().length).toBe(Object.keys(multiNote).length);
+
+ Object.values(multiNote).forEach((item, idx) => {
+ expect(
+ getAllTextBlocks()
+ .at(idx)
+ .text(),
+ ).toBe(`${item.source.name} → ${item.target.name}`);
+ });
+ });
+
+ it('displays the list toggle link', () => {
+ expect(getToggleButton().exists()).toBe(true);
+ expect(getToggleButton().text()).toBe('Hide list');
+ });
+ });
+
+ describe('the list toggle', () => {
+ beforeEach(() => {
+ createComponent({ annotations: multiNote }, mount);
+ });
+
+ describe('clicking hide', () => {
+ it('hides listed items and changes text to show', () => {
+ expect(getAllTextBlocks().length).toBe(Object.keys(multiNote).length);
+ expect(getToggleButton().text()).toBe('Hide list');
+ getToggleButton().trigger('click');
+ return wrapper.vm.$nextTick().then(() => {
+ expect(getAllTextBlocks().length).toBe(0);
+ expect(getToggleButton().text()).toBe('Show list');
+ });
+ });
+ });
+
+ describe('clicking show', () => {
+ it('shows listed items and changes text to hide', () => {
+ getToggleButton().trigger('click');
+ getToggleButton().trigger('click');
+
+ return wrapper.vm.$nextTick().then(() => {
+ expect(getAllTextBlocks().length).toBe(Object.keys(multiNote).length);
+ expect(getToggleButton().text()).toBe('Hide list');
+ });
+ });
+ });
+ });
+});
diff --git a/spec/frontend/pipelines/components/dag/dag_graph_spec.js b/spec/frontend/pipelines/components/dag/dag_graph_spec.js
index 017461dfb84..e312791b01f 100644
--- a/spec/frontend/pipelines/components/dag/dag_graph_spec.js
+++ b/spec/frontend/pipelines/components/dag/dag_graph_spec.js
@@ -1,4 +1,4 @@
-import { mount } from '@vue/test-utils';
+import { shallowMount } from '@vue/test-utils';
import DagGraph from '~/pipelines/components/dag/dag_graph.vue';
import { IS_HIGHLIGHTED, LINK_SELECTOR, NODE_SELECTOR } from '~/pipelines/components/dag/constants';
import { highlightIn, highlightOut } from '~/pipelines/components/dag/interactions';
@@ -19,7 +19,7 @@ describe('The DAG graph', () => {
wrapper.destroy();
}
- wrapper = mount(DagGraph, {
+ wrapper = shallowMount(DagGraph, {
attachToDocument: true,
propsData,
data() {
diff --git a/spec/frontend/pipelines/components/dag/dag_spec.js b/spec/frontend/pipelines/components/dag/dag_spec.js
index 666b4cfaa2f..7dea6d819b9 100644
--- a/spec/frontend/pipelines/components/dag/dag_spec.js
+++ b/spec/frontend/pipelines/components/dag/dag_spec.js
@@ -2,17 +2,28 @@ import { mount, shallowMount } from '@vue/test-utils';
import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
import waitForPromises from 'helpers/wait_for_promises';
-import { GlAlert } from '@gitlab/ui';
+import { GlAlert, GlEmptyState } from '@gitlab/ui';
import Dag from '~/pipelines/components/dag/dag.vue';
import DagGraph from '~/pipelines/components/dag/dag_graph.vue';
+import DagAnnotations from '~/pipelines/components/dag/dag_annotations.vue';
import {
+ ADD_NOTE,
+ REMOVE_NOTE,
+ REPLACE_NOTES,
DEFAULT,
PARSE_FAILURE,
LOAD_FAILURE,
UNSUPPORTED_DATA,
} from '~/pipelines/components/dag//constants';
-import { mockBaseData, tooSmallGraph, unparseableGraph } from './mock_data';
+import {
+ mockBaseData,
+ tooSmallGraph,
+ unparseableGraph,
+ graphWithoutDependencies,
+ singleNote,
+ multiNote,
+} from './mock_data';
describe('Pipeline DAG graph wrapper', () => {
let wrapper;
@@ -20,7 +31,9 @@ describe('Pipeline DAG graph wrapper', () => {
const getAlert = () => wrapper.find(GlAlert);
const getAllAlerts = () => wrapper.findAll(GlAlert);
const getGraph = () => wrapper.find(DagGraph);
+ const getNotes = () => wrapper.find(DagAnnotations);
const getErrorText = type => wrapper.vm.$options.errorTexts[type];
+ const getEmptyState = () => wrapper.find(GlEmptyState);
const dataPath = '/root/test/pipelines/90/dag.json';
@@ -30,7 +43,11 @@ describe('Pipeline DAG graph wrapper', () => {
}
wrapper = method(Dag, {
- propsData,
+ propsData: {
+ emptySvgPath: '/my-svg',
+ dagDocPath: '/my-doc',
+ ...propsData,
+ },
data() {
return {
showFailureAlert: false,
@@ -59,79 +76,153 @@ describe('Pipeline DAG graph wrapper', () => {
expect(getAlert().text()).toBe(getErrorText(DEFAULT));
expect(getGraph().exists()).toBe(false);
});
+
+ it('does not render the empty state', () => {
+ expect(getEmptyState().exists()).toBe(false);
+ });
});
describe('when there is a dataUrl', () => {
describe('but the data fetch fails', () => {
- beforeEach(() => {
+ beforeEach(async () => {
mock.onGet(dataPath).replyOnce(500);
createComponent({ graphUrl: dataPath });
+
+ await wrapper.vm.$nextTick();
+
+ return waitForPromises();
});
it('shows the LOAD_FAILURE alert and not the graph', () => {
- return wrapper.vm
- .$nextTick()
- .then(waitForPromises)
- .then(() => {
- expect(getAlert().exists()).toBe(true);
- expect(getAlert().text()).toBe(getErrorText(LOAD_FAILURE));
- expect(getGraph().exists()).toBe(false);
- });
+ expect(getAlert().exists()).toBe(true);
+ expect(getAlert().text()).toBe(getErrorText(LOAD_FAILURE));
+ expect(getGraph().exists()).toBe(false);
+ });
+
+ it('does not render the empty state', () => {
+ expect(getEmptyState().exists()).toBe(false);
});
});
describe('the data fetch succeeds but the parse fails', () => {
- beforeEach(() => {
+ beforeEach(async () => {
mock.onGet(dataPath).replyOnce(200, unparseableGraph);
createComponent({ graphUrl: dataPath });
+
+ await wrapper.vm.$nextTick();
+
+ return waitForPromises();
});
it('shows the PARSE_FAILURE alert and not the graph', () => {
- return wrapper.vm
- .$nextTick()
- .then(waitForPromises)
- .then(() => {
- expect(getAlert().exists()).toBe(true);
- expect(getAlert().text()).toBe(getErrorText(PARSE_FAILURE));
- expect(getGraph().exists()).toBe(false);
- });
+ expect(getAlert().exists()).toBe(true);
+ expect(getAlert().text()).toBe(getErrorText(PARSE_FAILURE));
+ expect(getGraph().exists()).toBe(false);
+ });
+
+ it('does not render the empty state', () => {
+ expect(getEmptyState().exists()).toBe(false);
});
});
describe('and the data fetch and parse succeeds', () => {
- beforeEach(() => {
+ beforeEach(async () => {
mock.onGet(dataPath).replyOnce(200, mockBaseData);
createComponent({ graphUrl: dataPath }, mount);
+
+ await wrapper.vm.$nextTick();
+
+ return waitForPromises();
});
- it('shows the graph and not the beta alert', () => {
- return wrapper.vm
- .$nextTick()
- .then(waitForPromises)
- .then(() => {
- expect(getAllAlerts().length).toBe(1);
- expect(getAlert().text()).toContain('This feature is currently in beta.');
- expect(getGraph().exists()).toBe(true);
- });
+ it('shows the graph and the beta alert', () => {
+ expect(getAllAlerts().length).toBe(1);
+ expect(getAlert().text()).toContain('This feature is currently in beta.');
+ expect(getGraph().exists()).toBe(true);
+ });
+
+ it('does not render the empty state', () => {
+ expect(getEmptyState().exists()).toBe(false);
});
});
describe('the data fetch and parse succeeds, but the resulting graph is too small', () => {
- beforeEach(() => {
+ beforeEach(async () => {
mock.onGet(dataPath).replyOnce(200, tooSmallGraph);
createComponent({ graphUrl: dataPath });
+
+ await wrapper.vm.$nextTick();
+
+ return waitForPromises();
});
it('shows the UNSUPPORTED_DATA alert and not the graph', () => {
- return wrapper.vm
- .$nextTick()
- .then(waitForPromises)
- .then(() => {
- expect(getAlert().exists()).toBe(true);
- expect(getAlert().text()).toBe(getErrorText(UNSUPPORTED_DATA));
- expect(getGraph().exists()).toBe(false);
- });
+ expect(getAlert().exists()).toBe(true);
+ expect(getAlert().text()).toBe(getErrorText(UNSUPPORTED_DATA));
+ expect(getGraph().exists()).toBe(false);
+ });
+
+ it('does not show the empty dag graph state', () => {
+ expect(getEmptyState().exists()).toBe(false);
+ });
+ });
+
+ describe('the data fetch succeeds but the returned data is empty', () => {
+ beforeEach(async () => {
+ mock.onGet(dataPath).replyOnce(200, graphWithoutDependencies);
+ createComponent({ graphUrl: dataPath }, mount);
+
+ await wrapper.vm.$nextTick();
+
+ return waitForPromises();
+ });
+
+ it('does not render an error alert or the graph', () => {
+ expect(getAllAlerts().length).toBe(1);
+ expect(getAlert().text()).toContain('This feature is currently in beta.');
+ expect(getGraph().exists()).toBe(false);
});
+
+ it('shows the empty dag graph state', () => {
+ expect(getEmptyState().exists()).toBe(true);
+ });
+ });
+ });
+
+ describe('annotations', () => {
+ beforeEach(async () => {
+ mock.onGet(dataPath).replyOnce(200, mockBaseData);
+ createComponent({ graphUrl: dataPath }, mount);
+
+ await wrapper.vm.$nextTick();
+
+ return waitForPromises();
+ });
+
+ it('toggles on link mouseover and mouseout', async () => {
+ const currentNote = singleNote['dag-link103'];
+
+ expect(getNotes().exists()).toBe(false);
+
+ getGraph().vm.$emit('update-annotation', { type: ADD_NOTE, data: currentNote });
+ await wrapper.vm.$nextTick();
+ expect(getNotes().exists()).toBe(true);
+
+ getGraph().vm.$emit('update-annotation', { type: REMOVE_NOTE, data: currentNote });
+ await wrapper.vm.$nextTick();
+ expect(getNotes().exists()).toBe(false);
+ });
+
+ it('toggles on node and link click', async () => {
+ expect(getNotes().exists()).toBe(false);
+
+ getGraph().vm.$emit('update-annotation', { type: REPLACE_NOTES, data: multiNote });
+ await wrapper.vm.$nextTick();
+ expect(getNotes().exists()).toBe(true);
+
+ getGraph().vm.$emit('update-annotation', { type: REPLACE_NOTES, data: {} });
+ await wrapper.vm.$nextTick();
+ expect(getNotes().exists()).toBe(false);
});
});
});
diff --git a/spec/frontend/pipelines/components/dag/mock_data.js b/spec/frontend/pipelines/components/dag/mock_data.js
index 5de8697170a..3b39b9cd21c 100644
--- a/spec/frontend/pipelines/components/dag/mock_data.js
+++ b/spec/frontend/pipelines/components/dag/mock_data.js
@@ -83,6 +83,46 @@ export const tooSmallGraph = {
],
};
+export const graphWithoutDependencies = {
+ stages: [
+ {
+ name: 'test',
+ groups: [
+ {
+ name: 'jest',
+ size: 2,
+ jobs: [{ name: 'jest 1/2' }, { name: 'jest 2/2' }],
+ },
+ {
+ name: 'rspec',
+ size: 1,
+ jobs: [{ name: 'rspec' }],
+ },
+ ],
+ },
+ {
+ name: 'fixtures',
+ groups: [
+ {
+ name: 'frontend fixtures',
+ size: 1,
+ jobs: [{ name: 'frontend fixtures' }],
+ },
+ ],
+ },
+ {
+ name: 'un-needed',
+ groups: [
+ {
+ name: 'un-needed',
+ size: 1,
+ jobs: [{ name: 'un-needed' }],
+ },
+ ],
+ },
+ ],
+};
+
export const unparseableGraph = [
{
name: 'test',
@@ -388,3 +428,43 @@ export const parsedData = {
},
],
};
+
+export const singleNote = {
+ 'dag-link103': {
+ uid: 'dag-link103',
+ source: {
+ name: 'canary_a',
+ color: '#b31756',
+ },
+ target: {
+ name: 'production_a',
+ color: '#b24800',
+ },
+ },
+};
+
+export const multiNote = {
+ ...singleNote,
+ 'dag-link104': {
+ uid: 'dag-link104',
+ source: {
+ name: 'build_a',
+ color: '#e17223',
+ },
+ target: {
+ name: 'test_c',
+ color: '#006887',
+ },
+ },
+ 'dag-link105': {
+ uid: 'dag-link105',
+ source: {
+ name: 'test_c',
+ color: '#006887',
+ },
+ target: {
+ name: 'post_test_c',
+ color: '#3547de',
+ },
+ },
+};
diff --git a/spec/frontend/pipelines/components/pipelines_filtered_search_spec.js b/spec/frontend/pipelines/components/pipelines_filtered_search_spec.js
index bdc807fcbfe..add7b56845e 100644
--- a/spec/frontend/pipelines/components/pipelines_filtered_search_spec.js
+++ b/spec/frontend/pipelines/components/pipelines_filtered_search_spec.js
@@ -2,7 +2,7 @@ import Api from '~/api';
import { mount } from '@vue/test-utils';
import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
-import PipelinesFilteredSearch from '~/pipelines/components/pipelines_filtered_search.vue';
+import PipelinesFilteredSearch from '~/pipelines/components/pipelines_list/pipelines_filtered_search.vue';
import { users, mockSearch, branches, tags } from '../mock_data';
import { GlFilteredSearch } from '@gitlab/ui';
diff --git a/spec/frontend/pipelines/empty_state_spec.js b/spec/frontend/pipelines/empty_state_spec.js
index f12950b8fce..79356664834 100644
--- a/spec/frontend/pipelines/empty_state_spec.js
+++ b/spec/frontend/pipelines/empty_state_spec.js
@@ -1,5 +1,5 @@
import Vue from 'vue';
-import emptyStateComp from '~/pipelines/components/empty_state.vue';
+import emptyStateComp from '~/pipelines/components/pipelines_list/empty_state.vue';
import mountComponent from '../helpers/vue_mount_component_helper';
describe('Pipelines Empty State', () => {
diff --git a/spec/frontend/pipelines/graph/job_item_spec.js b/spec/frontend/pipelines/graph/job_item_spec.js
index da777466e3e..2c5e7a1f6e9 100644
--- a/spec/frontend/pipelines/graph/job_item_spec.js
+++ b/spec/frontend/pipelines/graph/job_item_spec.js
@@ -5,6 +5,8 @@ import JobItem from '~/pipelines/components/graph/job_item.vue';
describe('pipeline graph job item', () => {
let wrapper;
+ const findJobWithoutLink = () => wrapper.find('[data-testid="job-without-link"]');
+
const createWrapper = propsData => {
wrapper = mount(JobItem, {
propsData,
@@ -57,7 +59,7 @@ describe('pipeline graph job item', () => {
});
describe('name without link', () => {
- it('it should render status and name', () => {
+ beforeEach(() => {
createWrapper({
job: {
id: 4257,
@@ -71,13 +73,22 @@ describe('pipeline graph job item', () => {
has_details: false,
},
},
+ cssClassJobName: 'css-class-job-name',
+ jobHovered: 'test',
});
+ });
+ it('it should render status and name', () => {
expect(wrapper.find('.ci-status-icon-success').exists()).toBe(true);
expect(wrapper.find('a').exists()).toBe(false);
expect(trimText(wrapper.find('.ci-status-text').text())).toEqual(mockJob.name);
});
+
+ it('should apply hover class and provided class name', () => {
+ expect(findJobWithoutLink().classes()).toContain('gl-inset-border-1-blue-500');
+ expect(findJobWithoutLink().classes()).toContain('css-class-job-name');
+ });
});
describe('action icon', () => {
diff --git a/spec/frontend/pipelines/graph/linked_pipeline_spec.js b/spec/frontend/pipelines/graph/linked_pipeline_spec.js
index cf78aa3ef71..133d5695afb 100644
--- a/spec/frontend/pipelines/graph/linked_pipeline_spec.js
+++ b/spec/frontend/pipelines/graph/linked_pipeline_spec.js
@@ -11,7 +11,10 @@ const invalidTriggeredPipelineId = mockPipeline.project.id + 5;
describe('Linked pipeline', () => {
let wrapper;
+
const findButton = () => wrapper.find('button');
+ const findPipelineLabel = () => wrapper.find('[data-testid="downstream-pipeline-label"]');
+ const findLinkedPipeline = () => wrapper.find({ ref: 'linkedPipeline' });
const createWrapper = propsData => {
wrapper = mount(LinkedPipelineComponent, {
@@ -69,6 +72,8 @@ describe('Linked pipeline', () => {
it('should correctly compute the tooltip text', () => {
expect(wrapper.vm.tooltipText).toContain(mockPipeline.project.name);
expect(wrapper.vm.tooltipText).toContain(mockPipeline.details.status.label);
+ expect(wrapper.vm.tooltipText).toContain(mockPipeline.source_job.name);
+ expect(wrapper.vm.tooltipText).toContain(mockPipeline.id);
});
it('should render the tooltip text as the title attribute', () => {
@@ -83,9 +88,8 @@ describe('Linked pipeline', () => {
expect(wrapper.find('.js-linked-pipeline-loading').exists()).toBe(false);
});
- it('should not display child label when pipeline project id is not the same as triggered pipeline project id', () => {
- const labelContainer = wrapper.find('.parent-child-label-container');
- expect(labelContainer.exists()).toBe(false);
+ it('should display multi-project label when pipeline project id is not the same as triggered pipeline project id', () => {
+ expect(findPipelineLabel().text()).toBe('Multi-project');
});
});
@@ -103,17 +107,17 @@ describe('Linked pipeline', () => {
it('parent/child label container should exist', () => {
createWrapper(downstreamProps);
- expect(wrapper.find('.parent-child-label-container').exists()).toBe(true);
+ expect(findPipelineLabel().exists()).toBe(true);
});
it('should display child label when pipeline project id is the same as triggered pipeline project id', () => {
createWrapper(downstreamProps);
- expect(wrapper.find('.parent-child-label-container').text()).toContain('Child');
+ expect(findPipelineLabel().exists()).toBe(true);
});
it('should display parent label when pipeline project id is the same as triggered_by pipeline project id', () => {
createWrapper(upstreamProps);
- expect(wrapper.find('.parent-child-label-container').text()).toContain('Parent');
+ expect(findPipelineLabel().exists()).toBe(true);
});
});
@@ -133,7 +137,7 @@ describe('Linked pipeline', () => {
});
});
- describe('on click', () => {
+ describe('on click/hover', () => {
const props = {
pipeline: mockPipeline,
projectId: validTriggeredPipelineId,
@@ -160,5 +164,15 @@ describe('Linked pipeline', () => {
'js-linked-pipeline-34993051',
]);
});
+
+ it('should emit downstreamHovered with job name on mouseover', () => {
+ findLinkedPipeline().trigger('mouseover');
+ expect(wrapper.emitted().downstreamHovered).toStrictEqual([['trigger_job']]);
+ });
+
+ it('should emit downstreamHovered with empty string on mouseleave', () => {
+ findLinkedPipeline().trigger('mouseleave');
+ expect(wrapper.emitted().downstreamHovered).toStrictEqual([['']]);
+ });
});
});
diff --git a/spec/frontend/pipelines/graph/linked_pipelines_mock_data.js b/spec/frontend/pipelines/graph/linked_pipelines_mock_data.js
index 3e9c0814403..5756a666ff3 100644
--- a/spec/frontend/pipelines/graph/linked_pipelines_mock_data.js
+++ b/spec/frontend/pipelines/graph/linked_pipelines_mock_data.js
@@ -14,6 +14,9 @@ export default {
active: false,
coverage: null,
source: 'push',
+ source_job: {
+ name: 'trigger_job',
+ },
created_at: '2018-06-05T11:31:30.452Z',
updated_at: '2018-10-31T16:35:31.305Z',
path: '/gitlab-org/gitlab-runner/pipelines/23211253',
@@ -381,6 +384,9 @@ export default {
active: false,
coverage: null,
source: 'pipeline',
+ source_job: {
+ name: 'trigger_job',
+ },
path: '/gitlab-com/gitlab-docs/pipelines/34993051',
details: {
status: {
@@ -889,6 +895,9 @@ export default {
active: false,
coverage: null,
source: 'pipeline',
+ source_job: {
+ name: 'trigger_job',
+ },
path: '/gitlab-com/gitlab-docs/pipelines/34993051',
details: {
status: {
@@ -1402,6 +1411,9 @@ export default {
active: false,
coverage: null,
source: 'pipeline',
+ source_job: {
+ name: 'trigger_job',
+ },
path: '/gitlab-com/gitlab-docs/pipelines/34993051',
details: {
status: {
@@ -1912,6 +1924,9 @@ export default {
active: false,
coverage: null,
source: 'pipeline',
+ source_job: {
+ name: 'trigger_job',
+ },
path: '/gitlab-com/gitlab-docs/pipelines/34993051',
details: {
status: {
@@ -2412,6 +2427,9 @@ export default {
active: false,
coverage: null,
source: 'push',
+ source_job: {
+ name: 'trigger_job',
+ },
created_at: '2019-01-06T17:48:37.599Z',
updated_at: '2019-01-06T17:48:38.371Z',
path: '/h5bp/html5-boilerplate/pipelines/26',
@@ -3743,6 +3761,9 @@ export default {
active: false,
coverage: null,
source: 'push',
+ source_job: {
+ name: 'trigger_job',
+ },
path: '/gitlab-org/gitlab-test/pipelines/4',
details: {
status: {
diff --git a/spec/frontend/pipelines/nav_controls_spec.js b/spec/frontend/pipelines/nav_controls_spec.js
index 6d28da0ea2a..139d53881c8 100644
--- a/spec/frontend/pipelines/nav_controls_spec.js
+++ b/spec/frontend/pipelines/nav_controls_spec.js
@@ -1,5 +1,5 @@
import Vue from 'vue';
-import navControlsComp from '~/pipelines/components/nav_controls.vue';
+import navControlsComp from '~/pipelines/components/pipelines_list/nav_controls.vue';
import mountComponent from '../helpers/vue_mount_component_helper';
describe('Pipelines Nav Controls', () => {
diff --git a/spec/frontend/pipelines/pipeline_triggerer_spec.js b/spec/frontend/pipelines/pipeline_triggerer_spec.js
index a8eec274487..6fd9a143d82 100644
--- a/spec/frontend/pipelines/pipeline_triggerer_spec.js
+++ b/spec/frontend/pipelines/pipeline_triggerer_spec.js
@@ -1,6 +1,6 @@
import { shallowMount } from '@vue/test-utils';
import UserAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link.vue';
-import pipelineTriggerer from '~/pipelines/components/pipeline_triggerer.vue';
+import pipelineTriggerer from '~/pipelines/components/pipelines_list/pipeline_triggerer.vue';
describe('Pipelines Triggerer', () => {
let wrapper;
diff --git a/spec/frontend/pipelines/pipeline_url_spec.js b/spec/frontend/pipelines/pipeline_url_spec.js
index 70b94f2c8e1..0bcc3f96f7c 100644
--- a/spec/frontend/pipelines/pipeline_url_spec.js
+++ b/spec/frontend/pipelines/pipeline_url_spec.js
@@ -1,108 +1,140 @@
import $ from 'jquery';
import { trimText } from 'helpers/text_helper';
import { shallowMount } from '@vue/test-utils';
-import PipelineUrlComponent from '~/pipelines/components/pipeline_url.vue';
+import PipelineUrlComponent from '~/pipelines/components/pipelines_list/pipeline_url.vue';
$.fn.popover = () => {};
describe('Pipeline Url Component', () => {
let wrapper;
+ const findPipelineUrlLink = () => wrapper.find('[data-testid="pipeline-url-link"]');
+ const findScheduledTag = () => wrapper.find('[data-testid="pipeline-url-scheduled"]');
+ const findLatestTag = () => wrapper.find('[data-testid="pipeline-url-latest"]');
+ const findYamlTag = () => wrapper.find('[data-testid="pipeline-url-yaml"]');
+ const findFailureTag = () => wrapper.find('[data-testid="pipeline-url-failure"]');
+ const findAutoDevopsTag = () => wrapper.find('[data-testid="pipeline-url-autodevops"]');
+ const findStuckTag = () => wrapper.find('[data-testid="pipeline-url-stuck"]');
+ const findDetachedTag = () => wrapper.find('[data-testid="pipeline-url-detached"]');
+
+ const defaultProps = {
+ pipeline: {
+ id: 1,
+ path: 'foo',
+ flags: {},
+ },
+ autoDevopsHelpPath: 'foo',
+ pipelineScheduleUrl: 'foo',
+ };
+
const createComponent = props => {
wrapper = shallowMount(PipelineUrlComponent, {
- propsData: props,
+ propsData: { ...defaultProps, ...props },
});
};
afterEach(() => {
wrapper.destroy();
+ wrapper = null;
});
it('should render a table cell', () => {
+ createComponent();
+
+ expect(wrapper.attributes('class')).toContain('table-section');
+ });
+
+ it('should render a link the provided path and id', () => {
+ createComponent();
+
+ expect(findPipelineUrlLink().attributes('href')).toBe('foo');
+
+ expect(findPipelineUrlLink().text()).toBe('#1');
+ });
+
+ it('should render the stuck tag when flag is provided', () => {
createComponent({
pipeline: {
- id: 1,
- path: 'foo',
- flags: {},
+ flags: {
+ stuck: true,
+ },
},
- autoDevopsHelpPath: 'foo',
});
- expect(wrapper.attributes('class')).toContain('table-section');
+ expect(findStuckTag().text()).toContain('stuck');
});
- it('should render a link the provided path and id', () => {
+ it('should render latest tag when flag is provided', () => {
createComponent({
pipeline: {
- id: 1,
- path: 'foo',
- flags: {},
+ flags: {
+ latest: true,
+ },
},
- autoDevopsHelpPath: 'foo',
});
- expect(wrapper.find('.js-pipeline-url-link').attributes('href')).toBe('foo');
-
- expect(wrapper.find('.js-pipeline-url-link span').text()).toBe('#1');
+ expect(findLatestTag().text()).toContain('latest');
});
- it('should render latest, yaml invalid, merge request, and stuck flags when provided', () => {
+ it('should render a yaml badge when it is invalid', () => {
createComponent({
pipeline: {
- id: 1,
- path: 'foo',
flags: {
- latest: true,
yaml_errors: true,
- stuck: true,
- merge_request_pipeline: true,
- detached_merge_request_pipeline: true,
},
},
- autoDevopsHelpPath: 'foo',
});
- expect(wrapper.find('.js-pipeline-url-latest').text()).toContain('latest');
-
- expect(wrapper.find('.js-pipeline-url-yaml').text()).toContain('yaml invalid');
+ expect(findYamlTag().text()).toContain('yaml invalid');
+ });
- expect(wrapper.find('.js-pipeline-url-stuck').text()).toContain('stuck');
+ it('should render an autodevops badge when flag is provided', () => {
+ createComponent({
+ pipeline: {
+ flags: {
+ auto_devops: true,
+ },
+ },
+ });
- expect(wrapper.find('.js-pipeline-url-detached').text()).toContain('detached');
+ expect(trimText(findAutoDevopsTag().text())).toBe('Auto DevOps');
});
- it('should render a badge for autodevops', () => {
+ it('should render a detached badge when flag is provided', () => {
createComponent({
pipeline: {
- id: 1,
- path: 'foo',
flags: {
- latest: true,
- yaml_errors: true,
- stuck: true,
- auto_devops: true,
+ detached_merge_request_pipeline: true,
},
},
- autoDevopsHelpPath: 'foo',
});
- expect(trimText(wrapper.find('.js-pipeline-url-autodevops').text())).toEqual('Auto DevOps');
+ expect(findDetachedTag().text()).toContain('detached');
});
it('should render error badge when pipeline has a failure reason set', () => {
createComponent({
pipeline: {
- id: 1,
- path: 'foo',
flags: {
failure_reason: true,
},
failure_reason: 'some reason',
},
- autoDevopsHelpPath: 'foo',
});
- expect(wrapper.find('.js-pipeline-url-failure').text()).toContain('error');
- expect(wrapper.find('.js-pipeline-url-failure').attributes('title')).toContain('some reason');
+ expect(findFailureTag().text()).toContain('error');
+ expect(findFailureTag().attributes('title')).toContain('some reason');
+ });
+
+ it('should render scheduled badge when pipeline was triggered by a schedule', () => {
+ createComponent({
+ pipeline: {
+ flags: {},
+ source: 'schedule',
+ },
+ });
+
+ expect(findScheduledTag().exists()).toBe(true);
+ expect(findScheduledTag().text()).toContain('Scheduled');
});
});
diff --git a/spec/frontend/pipelines/pipelines_actions_spec.js b/spec/frontend/pipelines/pipelines_actions_spec.js
index 5e8d21660de..aef54d94974 100644
--- a/spec/frontend/pipelines/pipelines_actions_spec.js
+++ b/spec/frontend/pipelines/pipelines_actions_spec.js
@@ -2,7 +2,7 @@ import { shallowMount } from '@vue/test-utils';
import MockAdapter from 'axios-mock-adapter';
import { TEST_HOST } from 'spec/test_constants';
import axios from '~/lib/utils/axios_utils';
-import PipelinesActions from '~/pipelines/components/pipelines_actions.vue';
+import PipelinesActions from '~/pipelines/components/pipelines_list/pipelines_actions.vue';
import { GlDeprecatedButton } from '@gitlab/ui';
import GlCountdown from '~/vue_shared/components/gl_countdown.vue';
import waitForPromises from 'helpers/wait_for_promises';
diff --git a/spec/frontend/pipelines/pipelines_artifacts_spec.js b/spec/frontend/pipelines/pipelines_artifacts_spec.js
index a93cc8a62ab..512205c3fc3 100644
--- a/spec/frontend/pipelines/pipelines_artifacts_spec.js
+++ b/spec/frontend/pipelines/pipelines_artifacts_spec.js
@@ -1,5 +1,5 @@
import { shallowMount } from '@vue/test-utils';
-import PipelineArtifacts from '~/pipelines/components/pipelines_artifacts.vue';
+import PipelineArtifacts from '~/pipelines/components/pipelines_list/pipelines_artifacts.vue';
import { GlLink } from '@gitlab/ui';
describe('Pipelines Artifacts dropdown', () => {
diff --git a/spec/frontend/pipelines/pipelines_spec.js b/spec/frontend/pipelines/pipelines_spec.js
index 0eeaef01a2d..66446b9aa1d 100644
--- a/spec/frontend/pipelines/pipelines_spec.js
+++ b/spec/frontend/pipelines/pipelines_spec.js
@@ -3,7 +3,7 @@ import { mount } from '@vue/test-utils';
import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
import waitForPromises from 'helpers/wait_for_promises';
-import PipelinesComponent from '~/pipelines/components/pipelines.vue';
+import PipelinesComponent from '~/pipelines/components/pipelines_list/pipelines.vue';
import Store from '~/pipelines/stores/pipelines_store';
import { pipelineWithStages, stageReply, users, mockSearch, branches } from './mock_data';
import { RAW_TEXT_WARNING } from '~/pipelines/constants';
@@ -343,12 +343,8 @@ describe('Pipelines', () => {
});
it('should render navigation tabs', () => {
- expect(wrapper.find('.js-pipelines-tab-pending').text()).toContain('Pending');
-
expect(wrapper.find('.js-pipelines-tab-all').text()).toContain('All');
- expect(wrapper.find('.js-pipelines-tab-running').text()).toContain('Running');
-
expect(wrapper.find('.js-pipelines-tab-finished').text()).toContain('Finished');
expect(wrapper.find('.js-pipelines-tab-branches').text()).toContain('Branches');
@@ -452,8 +448,6 @@ describe('Pipelines', () => {
it('returns default tabs', () => {
expect(wrapper.vm.tabs).toEqual([
{ name: 'All', scope: 'all', count: undefined, isActive: true },
- { name: 'Pending', scope: 'pending', count: undefined, isActive: false },
- { name: 'Running', scope: 'running', count: undefined, isActive: false },
{ name: 'Finished', scope: 'finished', count: undefined, isActive: false },
{ name: 'Branches', scope: 'branches', isActive: false },
{ name: 'Tags', scope: 'tags', isActive: false },
@@ -462,11 +456,11 @@ describe('Pipelines', () => {
});
describe('emptyTabMessage', () => {
- it('returns message with scope', () => {
- wrapper.vm.scope = 'pending';
+ it('returns message with finished scope', () => {
+ wrapper.vm.scope = 'finished';
return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.vm.emptyTabMessage).toEqual('There are currently no pending pipelines.');
+ expect(wrapper.vm.emptyTabMessage).toEqual('There are currently no finished pipelines.');
});
});
diff --git a/spec/frontend/pipelines/pipelines_table_row_spec.js b/spec/frontend/pipelines/pipelines_table_row_spec.js
index 3d564c8758c..9901f476f1b 100644
--- a/spec/frontend/pipelines/pipelines_table_row_spec.js
+++ b/spec/frontend/pipelines/pipelines_table_row_spec.js
@@ -1,5 +1,5 @@
import { mount } from '@vue/test-utils';
-import PipelinesTableRowComponent from '~/pipelines/components/pipelines_table_row.vue';
+import PipelinesTableRowComponent from '~/pipelines/components/pipelines_list/pipelines_table_row.vue';
import eventHub from '~/pipelines/event_hub';
describe('Pipelines Table Row', () => {
diff --git a/spec/frontend/pipelines/pipelines_table_spec.js b/spec/frontend/pipelines/pipelines_table_spec.js
index b0ab250dd16..c7d104bbde8 100644
--- a/spec/frontend/pipelines/pipelines_table_spec.js
+++ b/spec/frontend/pipelines/pipelines_table_spec.js
@@ -1,5 +1,5 @@
import { mount } from '@vue/test-utils';
-import PipelinesTable from '~/pipelines/components/pipelines_table.vue';
+import PipelinesTable from '~/pipelines/components/pipelines_list/pipelines_table.vue';
describe('Pipelines Table', () => {
let pipeline;
diff --git a/spec/frontend/pipelines/stage_spec.js b/spec/frontend/pipelines/stage_spec.js
index 6aa041bcb7f..547f8994ca5 100644
--- a/spec/frontend/pipelines/stage_spec.js
+++ b/spec/frontend/pipelines/stage_spec.js
@@ -1,7 +1,7 @@
import { mount } from '@vue/test-utils';
import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
-import StageComponent from '~/pipelines/components/stage.vue';
+import StageComponent from '~/pipelines/components/pipelines_list/stage.vue';
import eventHub from '~/pipelines/event_hub';
import { stageReply } from './mock_data';
import waitForPromises from 'helpers/wait_for_promises';
diff --git a/spec/frontend/pipelines/test_reports/stores/actions_spec.js b/spec/frontend/pipelines/test_reports/stores/actions_spec.js
index 56148361e0a..d4647c55a53 100644
--- a/spec/frontend/pipelines/test_reports/stores/actions_spec.js
+++ b/spec/frontend/pipelines/test_reports/stores/actions_spec.js
@@ -14,31 +14,100 @@ describe('Actions TestReports Store', () => {
let state;
const testReports = getJSONFixture('pipelines/test_report.json');
+ const summary = { total_count: 1 };
- const endpoint = `${TEST_HOST}/test_reports.json`;
+ const fullReportEndpoint = `${TEST_HOST}/test_reports.json`;
+ const summaryEndpoint = `${TEST_HOST}/test_reports/summary.json`;
const defaultState = {
- endpoint,
+ fullReportEndpoint,
+ summaryEndpoint,
testReports: {},
- selectedSuite: {},
+ selectedSuite: null,
+ useBuildSummaryReport: false,
};
beforeEach(() => {
mock = new MockAdapter(axios);
- state = defaultState;
+ state = { ...defaultState };
});
afterEach(() => {
mock.restore();
});
- describe('fetch reports', () => {
+ describe('fetch report summary', () => {
beforeEach(() => {
- mock.onGet(`${TEST_HOST}/test_reports.json`).replyOnce(200, testReports, {});
+ mock.onGet(summaryEndpoint).replyOnce(200, summary, {});
+ });
+
+ describe('when useBuildSummaryReport in state is true', () => {
+ it('sets testReports and shows tests', done => {
+ testAction(
+ actions.fetchSummary,
+ null,
+ { ...state, useBuildSummaryReport: true },
+ [{ type: types.SET_SUMMARY, payload: summary }],
+ [{ type: 'toggleLoading' }, { type: 'toggleLoading' }],
+ done,
+ );
+ });
+
+ it('should create flash on API error', done => {
+ testAction(
+ actions.fetchSummary,
+ null,
+ {
+ summaryEndpoint: null,
+ useBuildSummaryReport: true,
+ },
+ [],
+ [{ type: 'toggleLoading' }, { type: 'toggleLoading' }],
+ () => {
+ expect(createFlash).toHaveBeenCalled();
+ done();
+ },
+ );
+ });
+ });
+
+ describe('when useBuildSummaryReport in state is false', () => {
+ it('sets testReports and shows tests', done => {
+ testAction(
+ actions.fetchSummary,
+ null,
+ state,
+ [{ type: types.SET_SUMMARY, payload: summary }],
+ [],
+ done,
+ );
+ });
+
+ it('should create flash on API error', done => {
+ testAction(
+ actions.fetchSummary,
+ null,
+ {
+ summaryEndpoint: null,
+ },
+ [],
+ [],
+ () => {
+ expect(createFlash).toHaveBeenCalled();
+ done();
+ },
+ );
+ });
+ });
+ });
+
+ describe('fetch full report', () => {
+ beforeEach(() => {
+ mock.onGet(fullReportEndpoint).replyOnce(200, testReports, {});
});
it('sets testReports and shows tests', done => {
testAction(
- actions.fetchReports,
+ actions.fetchFullReport,
null,
state,
[{ type: types.SET_REPORTS, payload: testReports }],
@@ -49,10 +118,10 @@ describe('Actions TestReports Store', () => {
it('should create flash on API error', done => {
testAction(
- actions.fetchReports,
+ actions.fetchFullReport,
null,
{
- endpoint: null,
+ fullReportEndpoint: null,
},
[],
[{ type: 'toggleLoading' }, { type: 'toggleLoading' }],
@@ -64,28 +133,28 @@ describe('Actions TestReports Store', () => {
});
});
- describe('set selected suite', () => {
- const selectedSuite = testReports.test_suites[0];
+ describe('set selected suite index', () => {
+ it('sets selectedSuiteIndex', done => {
+ const selectedSuiteIndex = 0;
- it('sets selectedSuite', done => {
testAction(
- actions.setSelectedSuite,
- selectedSuite,
- state,
- [{ type: types.SET_SELECTED_SUITE, payload: selectedSuite }],
+ actions.setSelectedSuiteIndex,
+ selectedSuiteIndex,
+ { ...state, hasFullReport: true },
+ [{ type: types.SET_SELECTED_SUITE_INDEX, payload: selectedSuiteIndex }],
[],
done,
);
});
});
- describe('remove selected suite', () => {
- it('sets selectedSuite to {}', done => {
+ describe('remove selected suite index', () => {
+ it('sets selectedSuiteIndex to null', done => {
testAction(
- actions.removeSelectedSuite,
+ actions.removeSelectedSuiteIndex,
{},
state,
- [{ type: types.SET_SELECTED_SUITE, payload: {} }],
+ [{ type: types.SET_SELECTED_SUITE_INDEX, payload: null }],
[],
done,
);
diff --git a/spec/frontend/pipelines/test_reports/stores/getters_spec.js b/spec/frontend/pipelines/test_reports/stores/getters_spec.js
index 011a7e68908..ca9ebb54138 100644
--- a/spec/frontend/pipelines/test_reports/stores/getters_spec.js
+++ b/spec/frontend/pipelines/test_reports/stores/getters_spec.js
@@ -9,12 +9,12 @@ describe('Getters TestReports Store', () => {
const defaultState = {
testReports,
- selectedSuite: testReports.test_suites[0],
+ selectedSuiteIndex: 0,
};
const emptyState = {
testReports: {},
- selectedSuite: {},
+ selectedSuite: null,
};
beforeEach(() => {
@@ -47,6 +47,17 @@ describe('Getters TestReports Store', () => {
});
});
+ describe('getSelectedSuite', () => {
+ it('should return the selected suite', () => {
+ setupState();
+
+ const selectedSuite = getters.getSelectedSuite(state);
+ const expected = testReports.test_suites[state.selectedSuiteIndex];
+
+ expect(selectedSuite).toEqual(expected);
+ });
+ });
+
describe('getSuiteTests', () => {
it('should return the test cases inside the suite', () => {
setupState();
diff --git a/spec/frontend/pipelines/test_reports/stores/mutations_spec.js b/spec/frontend/pipelines/test_reports/stores/mutations_spec.js
index a0eb93c4e6b..f4cc5c4bc5d 100644
--- a/spec/frontend/pipelines/test_reports/stores/mutations_spec.js
+++ b/spec/frontend/pipelines/test_reports/stores/mutations_spec.js
@@ -10,21 +10,13 @@ describe('Mutations TestReports Store', () => {
const defaultState = {
endpoint: '',
testReports: {},
- selectedSuite: {},
+ selectedSuite: null,
isLoading: false,
+ hasFullReport: false,
};
beforeEach(() => {
- mockState = defaultState;
- });
-
- describe('set endpoint', () => {
- it('should set endpoint', () => {
- const expectedState = { ...mockState, endpoint: 'foo' };
- mutations[types.SET_ENDPOINT](mockState, 'foo');
-
- expect(mockState.endpoint).toEqual(expectedState.endpoint);
- });
+ mockState = { ...defaultState };
});
describe('set reports', () => {
@@ -33,15 +25,25 @@ describe('Mutations TestReports Store', () => {
mutations[types.SET_REPORTS](mockState, testReports);
expect(mockState.testReports).toEqual(expectedState.testReports);
+ expect(mockState.hasFullReport).toBe(true);
+ });
+ });
+
+ describe('set selected suite index', () => {
+ it('should set selectedSuiteIndex', () => {
+ const selectedSuiteIndex = 0;
+ mutations[types.SET_SELECTED_SUITE_INDEX](mockState, selectedSuiteIndex);
+
+ expect(mockState.selectedSuiteIndex).toEqual(selectedSuiteIndex);
});
});
- describe('set selected suite', () => {
- it('should set selectedSuite', () => {
- const selectedSuite = testReports.test_suites[0];
- mutations[types.SET_SELECTED_SUITE](mockState, selectedSuite);
+ describe('set summary', () => {
+ it('should set summary', () => {
+ const summary = { total_count: 1 };
+ mutations[types.SET_SUMMARY](mockState, summary);
- expect(mockState.selectedSuite).toEqual(selectedSuite);
+ expect(mockState.testReports).toEqual(summary);
});
});
diff --git a/spec/frontend/pipelines/test_reports/test_reports_spec.js b/spec/frontend/pipelines/test_reports/test_reports_spec.js
index cc86ba6d46d..ef0bcffabe3 100644
--- a/spec/frontend/pipelines/test_reports/test_reports_spec.js
+++ b/spec/frontend/pipelines/test_reports/test_reports_spec.js
@@ -1,8 +1,13 @@
import Vuex from 'vuex';
-import { shallowMount } from '@vue/test-utils';
+import { shallowMount, createLocalVue } from '@vue/test-utils';
import { getJSONFixture } from 'helpers/fixtures';
import TestReports from '~/pipelines/components/test_reports/test_reports.vue';
-import * as actions from '~/pipelines/stores/test_reports/actions';
+import TestSummary from '~/pipelines/components/test_reports/test_summary.vue';
+import TestSummaryTable from '~/pipelines/components/test_reports/test_summary_table.vue';
+import * as getters from '~/pipelines/stores/test_reports/getters';
+
+const localVue = createLocalVue();
+localVue.use(Vuex);
describe('Test reports app', () => {
let wrapper;
@@ -13,20 +18,31 @@ describe('Test reports app', () => {
const loadingSpinner = () => wrapper.find('.js-loading-spinner');
const testsDetail = () => wrapper.find('.js-tests-detail');
const noTestsToShow = () => wrapper.find('.js-no-tests-to-show');
+ const testSummary = () => wrapper.find(TestSummary);
+ const testSummaryTable = () => wrapper.find(TestSummaryTable);
+
+ const actionSpies = {
+ fetchFullReport: jest.fn(),
+ fetchSummary: jest.fn(),
+ setSelectedSuiteIndex: jest.fn(),
+ removeSelectedSuiteIndex: jest.fn(),
+ };
const createComponent = (state = {}) => {
store = new Vuex.Store({
state: {
isLoading: false,
- selectedSuite: {},
+ selectedSuiteIndex: null,
testReports,
...state,
},
- actions,
+ actions: actionSpies,
+ getters,
});
wrapper = shallowMount(TestReports, {
store,
+ localVue,
});
};
@@ -34,6 +50,16 @@ describe('Test reports app', () => {
wrapper.destroy();
});
+ describe('when component is created', () => {
+ beforeEach(() => {
+ createComponent();
+ });
+
+ it('should call fetchSummary', () => {
+ expect(actionSpies.fetchSummary).toHaveBeenCalled();
+ });
+ });
+
describe('when loading', () => {
beforeEach(() => createComponent({ isLoading: true }));
@@ -63,4 +89,41 @@ describe('Test reports app', () => {
expect(wrapper.vm.showTests).toBeTruthy();
});
});
+
+ describe('when a suite is clicked', () => {
+ describe('when the full test report has already been received', () => {
+ beforeEach(() => {
+ createComponent({ hasFullReport: true });
+ testSummaryTable().vm.$emit('row-click', 0);
+ });
+
+ it('should only call setSelectedSuiteIndex', () => {
+ expect(actionSpies.setSelectedSuiteIndex).toHaveBeenCalled();
+ expect(actionSpies.fetchFullReport).not.toHaveBeenCalled();
+ });
+ });
+
+ describe('when the full test report has not been received', () => {
+ beforeEach(() => {
+ createComponent({ hasFullReport: false });
+ testSummaryTable().vm.$emit('row-click', 0);
+ });
+
+ it('should call setSelectedSuiteIndex and fetchFullReport', () => {
+ expect(actionSpies.setSelectedSuiteIndex).toHaveBeenCalled();
+ expect(actionSpies.fetchFullReport).toHaveBeenCalled();
+ });
+ });
+ });
+
+ describe('when clicking back to summary', () => {
+ beforeEach(() => {
+ createComponent({ selectedSuiteIndex: 0 });
+ testSummary().vm.$emit('on-back-click');
+ });
+
+ it('should call removeSelectedSuiteIndex', () => {
+ expect(actionSpies.removeSelectedSuiteIndex).toHaveBeenCalled();
+ });
+ });
});
diff --git a/spec/frontend/pipelines/test_reports/test_suite_table_spec.js b/spec/frontend/pipelines/test_reports/test_suite_table_spec.js
index a5b093cf769..65bffe7039a 100644
--- a/spec/frontend/pipelines/test_reports/test_suite_table_spec.js
+++ b/spec/frontend/pipelines/test_reports/test_suite_table_spec.js
@@ -1,11 +1,14 @@
import Vuex from 'vuex';
-import { shallowMount } from '@vue/test-utils';
+import { shallowMount, createLocalVue } from '@vue/test-utils';
import { getJSONFixture } from 'helpers/fixtures';
import SuiteTable from '~/pipelines/components/test_reports/test_suite_table.vue';
import * as getters from '~/pipelines/stores/test_reports/getters';
import { TestStatus } from '~/pipelines/constants';
import skippedTestCases from './mock_data';
+const localVue = createLocalVue();
+localVue.use(Vuex);
+
describe('Test reports suite table', () => {
let wrapper;
let store;
@@ -25,13 +28,17 @@ describe('Test reports suite table', () => {
const createComponent = (suite = testSuite) => {
store = new Vuex.Store({
state: {
- selectedSuite: suite,
+ testReports: {
+ test_suites: [suite],
+ },
+ selectedSuiteIndex: 0,
},
getters,
});
wrapper = shallowMount(SuiteTable, {
store,
+ localVue,
});
};
diff --git a/spec/frontend/pipelines/test_reports/test_summary_spec.js b/spec/frontend/pipelines/test_reports/test_summary_spec.js
index 8f041e46472..79be6c168cf 100644
--- a/spec/frontend/pipelines/test_reports/test_summary_spec.js
+++ b/spec/frontend/pipelines/test_reports/test_summary_spec.js
@@ -60,7 +60,7 @@ describe('Test reports summary', () => {
});
it('displays the correct total', () => {
- expect(totalTests().text()).toBe('4 jobs');
+ expect(totalTests().text()).toBe('4 tests');
});
it('displays the correct failure count', () => {
diff --git a/spec/frontend/pipelines/time_ago_spec.js b/spec/frontend/pipelines/time_ago_spec.js
index 1bd16182d47..04934fb93b0 100644
--- a/spec/frontend/pipelines/time_ago_spec.js
+++ b/spec/frontend/pipelines/time_ago_spec.js
@@ -1,5 +1,5 @@
import { shallowMount } from '@vue/test-utils';
-import TimeAgo from '~/pipelines/components/time_ago.vue';
+import TimeAgo from '~/pipelines/components/pipelines_list/time_ago.vue';
describe('Timeago component', () => {
let wrapper;
diff --git a/spec/frontend/pipelines/tokens/pipeline_branch_name_token_spec.js b/spec/frontend/pipelines/tokens/pipeline_branch_name_token_spec.js
index 1a85221581e..650dd8a1def 100644
--- a/spec/frontend/pipelines/tokens/pipeline_branch_name_token_spec.js
+++ b/spec/frontend/pipelines/tokens/pipeline_branch_name_token_spec.js
@@ -1,7 +1,7 @@
import Api from '~/api';
import { GlFilteredSearchToken, GlFilteredSearchSuggestion, GlLoadingIcon } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
-import PipelineBranchNameToken from '~/pipelines/components/tokens/pipeline_branch_name_token.vue';
+import PipelineBranchNameToken from '~/pipelines/components/pipelines_list/tokens/pipeline_branch_name_token.vue';
import { branches, mockBranchesAfterMap } from '../mock_data';
describe('Pipeline Branch Name Token', () => {
diff --git a/spec/frontend/pipelines/tokens/pipeline_status_token_spec.js b/spec/frontend/pipelines/tokens/pipeline_status_token_spec.js
index ee3694868a5..096e4cd97f6 100644
--- a/spec/frontend/pipelines/tokens/pipeline_status_token_spec.js
+++ b/spec/frontend/pipelines/tokens/pipeline_status_token_spec.js
@@ -1,6 +1,6 @@
import { GlFilteredSearchToken, GlFilteredSearchSuggestion, GlIcon } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
-import PipelineStatusToken from '~/pipelines/components/tokens/pipeline_status_token.vue';
+import PipelineStatusToken from '~/pipelines/components/pipelines_list/tokens/pipeline_status_token.vue';
describe('Pipeline Status Token', () => {
let wrapper;
diff --git a/spec/frontend/pipelines/tokens/pipeline_tag_name_token_spec.js b/spec/frontend/pipelines/tokens/pipeline_tag_name_token_spec.js
index 9fecc9412b7..15b283dc2ff 100644
--- a/spec/frontend/pipelines/tokens/pipeline_tag_name_token_spec.js
+++ b/spec/frontend/pipelines/tokens/pipeline_tag_name_token_spec.js
@@ -1,7 +1,7 @@
import Api from '~/api';
import { GlFilteredSearchToken, GlFilteredSearchSuggestion, GlLoadingIcon } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
-import PipelineTagNameToken from '~/pipelines/components/tokens/pipeline_tag_name_token.vue';
+import PipelineTagNameToken from '~/pipelines/components/pipelines_list/tokens/pipeline_tag_name_token.vue';
import { tags, mockTagsAfterMap } from '../mock_data';
describe('Pipeline Branch Name Token', () => {
diff --git a/spec/frontend/pipelines/tokens/pipeline_trigger_author_token_spec.js b/spec/frontend/pipelines/tokens/pipeline_trigger_author_token_spec.js
index 98de4f40c51..0b5cf2e202b 100644
--- a/spec/frontend/pipelines/tokens/pipeline_trigger_author_token_spec.js
+++ b/spec/frontend/pipelines/tokens/pipeline_trigger_author_token_spec.js
@@ -1,7 +1,7 @@
import Api from '~/api';
import { GlFilteredSearchToken, GlFilteredSearchSuggestion, GlLoadingIcon } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
-import PipelineTriggerAuthorToken from '~/pipelines/components/tokens/pipeline_trigger_author_token.vue';
+import PipelineTriggerAuthorToken from '~/pipelines/components/pipelines_list/tokens/pipeline_trigger_author_token.vue';
import { users } from '../mock_data';
describe('Pipeline Trigger Author Token', () => {
diff --git a/spec/frontend/polyfills/element_spec.js b/spec/frontend/polyfills/element_spec.js
deleted file mode 100644
index 64ce248ca44..00000000000
--- a/spec/frontend/polyfills/element_spec.js
+++ /dev/null
@@ -1,46 +0,0 @@
-import '~/commons/polyfills/element';
-
-describe('Element polyfills', () => {
- let testContext;
-
- beforeEach(() => {
- testContext = {};
- });
-
- beforeEach(() => {
- testContext.element = document.createElement('ul');
- });
-
- describe('matches', () => {
- it('returns true if element matches the selector', () => {
- expect(testContext.element.matches('ul')).toBeTruthy();
- });
-
- it("returns false if element doesn't match the selector", () => {
- expect(testContext.element.matches('.not-an-element')).toBeFalsy();
- });
- });
-
- describe('closest', () => {
- beforeEach(() => {
- testContext.childElement = document.createElement('li');
- testContext.element.appendChild(testContext.childElement);
- });
-
- it('returns the closest parent that matches the selector', () => {
- expect(testContext.childElement.closest('ul').toString()).toBe(
- testContext.element.toString(),
- );
- });
-
- it('returns itself if it matches the selector', () => {
- expect(testContext.childElement.closest('li').toString()).toBe(
- testContext.childElement.toString(),
- );
- });
-
- it('returns undefined if nothing matches the selector', () => {
- expect(testContext.childElement.closest('.no-an-element')).toBeFalsy();
- });
- });
-});
diff --git a/spec/frontend/projects/commits/store/actions_spec.js b/spec/frontend/projects/commits/store/actions_spec.js
index c9945e1cc27..886224252ad 100644
--- a/spec/frontend/projects/commits/store/actions_spec.js
+++ b/spec/frontend/projects/commits/store/actions_spec.js
@@ -45,7 +45,7 @@ describe('Project commits actions', () => {
describe('fetchAuthors', () => {
it('dispatches request/receive', () => {
- const path = '/autocomplete/users.json';
+ const path = '/-/autocomplete/users.json';
state.projectId = '8';
const data = [{ id: 1 }];
@@ -60,7 +60,7 @@ describe('Project commits actions', () => {
});
it('dispatches request/receive on error', () => {
- const path = '/autocomplete/users.json';
+ const path = '/-/autocomplete/users.json';
mock.onGet(path).replyOnce(500);
testAction(actions.fetchAuthors, null, state, [], [{ type: 'receiveAuthorsError' }]);
diff --git a/spec/frontend/projects/components/__snapshots__/remove_modal_spec.js.snap b/spec/frontend/projects/components/__snapshots__/remove_modal_spec.js.snap
new file mode 100644
index 00000000000..4d5b6c56a34
--- /dev/null
+++ b/spec/frontend/projects/components/__snapshots__/remove_modal_spec.js.snap
@@ -0,0 +1,126 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Project remove modal initialized matches the snapshot 1`] = `
+<form
+ action="some/path"
+ method="post"
+>
+ <input
+ name="_method"
+ type="hidden"
+ value="delete"
+ />
+
+ <input
+ name="authenticity_token"
+ type="hidden"
+ />
+
+ <b-button-stub
+ class="[object Object]"
+ event="click"
+ role="button"
+ routertag="a"
+ size="md"
+ tabindex="0"
+ tag="button"
+ type="button"
+ variant="danger"
+ >
+ <!---->
+
+ <!---->
+
+ <span
+ class="gl-button-text"
+ >
+ Remove project
+ </span>
+ </b-button-stub>
+
+ <b-modal-stub
+ canceltitle="Cancel"
+ cancelvariant="secondary"
+ footerclass="bg-gray-light gl-p-5"
+ headerclosecontent="&times;"
+ headercloselabel="Close"
+ id="remove-project-modal"
+ ignoreenforcefocusselector=""
+ lazy="true"
+ modalclass="gl-modal,"
+ oktitle="OK"
+ okvariant="danger"
+ size="sm"
+ title=""
+ titletag="h4"
+ >
+
+ <div>
+ <p
+ class="gl-text-red-500 gl-font-weight-bold"
+ >
+ This can lead to data loss.
+ </p>
+
+ <p
+ class="gl-mb-0"
+ >
+ This action can lead to data loss. To prevent accidental actions we ask you to confirm your intention.
+ </p>
+
+ <p>
+ <gl-sprintf-stub
+ message="Please type %{phrase_code} to proceed or close this modal to cancel."
+ />
+ </p>
+
+ <gl-form-input-stub
+ id="confirm_name_input"
+ name="confirm_name_input"
+ type="text"
+ />
+ </div>
+
+ <template />
+
+ <template>
+ Confirmation required
+ </template>
+
+ <template />
+
+ <template />
+
+ <template />
+
+ <template>
+ <div
+ class="gl-w-full gl-display-flex gl-just-content-start gl-m-0"
+ >
+ <b-button-stub
+ class="[object Object]"
+ disabled="true"
+ event="click"
+ routertag="a"
+ size="md"
+ tag="button"
+ type="button"
+ variant="danger"
+ >
+ <!---->
+
+ <!---->
+
+ <span
+ class="gl-button-text"
+ >
+
+ Confirm
+
+ </span>
+ </b-button-stub>
+ </div>
+ </template>
+ </b-modal-stub>
+</form>
+`;
diff --git a/spec/frontend/projects/components/remove_modal_spec.js b/spec/frontend/projects/components/remove_modal_spec.js
new file mode 100644
index 00000000000..339aee65b99
--- /dev/null
+++ b/spec/frontend/projects/components/remove_modal_spec.js
@@ -0,0 +1,62 @@
+import { shallowMount } from '@vue/test-utils';
+import { GlButton, GlModal } from '@gitlab/ui';
+import ProjectRemoveModal from '~/projects/components/remove_modal.vue';
+
+describe('Project remove modal', () => {
+ let wrapper;
+
+ const findFormElement = () => wrapper.find('form').element;
+ const findConfirmButton = () => wrapper.find(GlModal).find(GlButton);
+
+ const defaultProps = {
+ formPath: 'some/path',
+ confirmPhrase: 'foo',
+ warningMessage: 'This can lead to data loss.',
+ };
+
+ const createComponent = (data = {}) => {
+ wrapper = shallowMount(ProjectRemoveModal, {
+ propsData: defaultProps,
+ data: () => data,
+ stubs: {
+ GlButton,
+ GlModal,
+ },
+ });
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ wrapper = null;
+ });
+
+ describe('initialized', () => {
+ beforeEach(() => {
+ createComponent();
+ });
+
+ it('matches the snapshot', () => {
+ expect(wrapper.element).toMatchSnapshot();
+ });
+ });
+
+ describe('user input matches the confirmPhrase', () => {
+ beforeEach(() => {
+ createComponent({ userInput: defaultProps.confirmPhrase });
+ });
+
+ it('the confirm button is not dislabled', () => {
+ expect(findConfirmButton().attributes('disabled')).toBe(undefined);
+ });
+
+ describe('and when the confirmation button is clicked', () => {
+ beforeEach(() => {
+ findConfirmButton().vm.$emit('click');
+ });
+
+ it('submits the form element', () => {
+ expect(findFormElement().submit).toHaveBeenCalled();
+ });
+ });
+ });
+});
diff --git a/spec/frontend/projects/pipelines/charts/components/__snapshots__/pipelines_area_chart_spec.js.snap b/spec/frontend/projects/pipelines/charts/components/__snapshots__/pipelines_area_chart_spec.js.snap
index f280ecaa0bc..d68e009f46e 100644
--- a/spec/frontend/projects/pipelines/charts/components/__snapshots__/pipelines_area_chart_spec.js.snap
+++ b/spec/frontend/projects/pipelines/charts/components/__snapshots__/pipelines_area_chart_spec.js.snap
@@ -2,7 +2,7 @@
exports[`PipelinesAreaChart matches the snapshot 1`] = `
<div
- class="prepend-top-default"
+ class="gl-mt-3"
>
<p>
Some title
diff --git a/spec/frontend/projects/project_new_spec.js b/spec/frontend/projects/project_new_spec.js
index 7c6ff90aff6..7aafbd33fc8 100644
--- a/spec/frontend/projects/project_new_spec.js
+++ b/spec/frontend/projects/project_new_spec.js
@@ -1,5 +1,6 @@
import $ from 'jquery';
import projectNew from '~/projects/project_new';
+import { TEST_HOST } from 'jest/helpers/test_constants';
describe('New Project', () => {
let $projectImportUrl;
@@ -33,7 +34,7 @@ describe('New Project', () => {
});
describe('deriveProjectPathFromUrl', () => {
- const dummyImportUrl = `${gl.TEST_HOST}/dummy/import/url.git`;
+ const dummyImportUrl = `${TEST_HOST}/dummy/import/url.git`;
beforeEach(() => {
projectNew.bindEvents();
diff --git a/spec/frontend/projects/settings_service_desk/components/service_desk_root_spec.js b/spec/frontend/projects/settings_service_desk/components/service_desk_root_spec.js
new file mode 100644
index 00000000000..4c873bdfd60
--- /dev/null
+++ b/spec/frontend/projects/settings_service_desk/components/service_desk_root_spec.js
@@ -0,0 +1,226 @@
+import { shallowMount, mount } from '@vue/test-utils';
+import AxiosMockAdapter from 'axios-mock-adapter';
+import waitForPromises from 'helpers/wait_for_promises';
+import ServiceDeskRoot from '~/projects/settings_service_desk/components/service_desk_root.vue';
+import axios from '~/lib/utils/axios_utils';
+import httpStatusCodes from '~/lib/utils/http_status';
+
+describe('ServiceDeskRoot', () => {
+ const endpoint = '/gitlab-org/gitlab-test/service_desk';
+ const initialIncomingEmail = 'servicedeskaddress@example.com';
+ let axiosMock;
+ let wrapper;
+ let spy;
+
+ beforeEach(() => {
+ axiosMock = new AxiosMockAdapter(axios);
+ });
+
+ afterEach(() => {
+ axiosMock.restore();
+ wrapper.destroy();
+ if (spy) {
+ spy.mockRestore();
+ }
+ });
+
+ it('fetches incoming email when there is no incoming email provided', () => {
+ axiosMock.onGet(endpoint).replyOnce(httpStatusCodes.OK);
+
+ wrapper = shallowMount(ServiceDeskRoot, {
+ propsData: {
+ initialIsEnabled: true,
+ initialIncomingEmail: '',
+ endpoint,
+ },
+ });
+
+ return wrapper.vm
+ .$nextTick()
+ .then(waitForPromises)
+ .then(() => {
+ expect(axiosMock.history.get).toHaveLength(1);
+ });
+ });
+
+ it('does not fetch incoming email when there is an incoming email provided', () => {
+ axiosMock.onGet(endpoint).replyOnce(httpStatusCodes.OK);
+
+ wrapper = shallowMount(ServiceDeskRoot, {
+ propsData: {
+ initialIsEnabled: true,
+ initialIncomingEmail,
+ endpoint,
+ },
+ });
+
+ return wrapper.vm
+ .$nextTick()
+ .then(waitForPromises)
+ .then(() => {
+ expect(axiosMock.history.get).toHaveLength(0);
+ });
+ });
+
+ it('shows an error message when incoming email is not fetched correctly', () => {
+ axiosMock.onGet(endpoint).networkError();
+
+ wrapper = shallowMount(ServiceDeskRoot, {
+ propsData: {
+ initialIsEnabled: true,
+ initialIncomingEmail: '',
+ endpoint,
+ },
+ });
+
+ return wrapper.vm
+ .$nextTick()
+ .then(waitForPromises)
+ .then(() => {
+ expect(wrapper.html()).toContain(
+ 'An error occurred while fetching the Service Desk address.',
+ );
+ });
+ });
+
+ it('sends a request to toggle service desk off when the toggle is clicked from the on state', () => {
+ axiosMock.onPut(endpoint).replyOnce(httpStatusCodes.OK);
+
+ spy = jest.spyOn(axios, 'put');
+
+ wrapper = mount(ServiceDeskRoot, {
+ propsData: {
+ initialIsEnabled: true,
+ initialIncomingEmail,
+ endpoint,
+ },
+ });
+
+ wrapper.find('button.gl-toggle').trigger('click');
+
+ return wrapper.vm
+ .$nextTick()
+ .then(waitForPromises)
+ .then(() => {
+ expect(spy).toHaveBeenCalledWith(endpoint, { service_desk_enabled: false });
+ });
+ });
+
+ it('sends a request to toggle service desk on when the toggle is clicked from the off state', () => {
+ axiosMock.onPut(endpoint).replyOnce(httpStatusCodes.OK);
+
+ spy = jest.spyOn(axios, 'put');
+
+ wrapper = mount(ServiceDeskRoot, {
+ propsData: {
+ initialIsEnabled: false,
+ initialIncomingEmail: '',
+ endpoint,
+ },
+ });
+
+ wrapper.find('button.gl-toggle').trigger('click');
+
+ return wrapper.vm.$nextTick(() => {
+ expect(spy).toHaveBeenCalledWith(endpoint, { service_desk_enabled: true });
+ });
+ });
+
+ it('shows an error message when there is an issue toggling service desk on', () => {
+ axiosMock.onPut(endpoint).networkError();
+
+ wrapper = mount(ServiceDeskRoot, {
+ propsData: {
+ initialIsEnabled: false,
+ initialIncomingEmail: '',
+ endpoint,
+ },
+ });
+
+ wrapper.find('button.gl-toggle').trigger('click');
+
+ return wrapper.vm
+ .$nextTick()
+ .then(waitForPromises)
+ .then(() => {
+ expect(wrapper.html()).toContain('An error occurred while enabling Service Desk.');
+ });
+ });
+
+ it('sends a request to update template when the "Save template" button is clicked', () => {
+ axiosMock.onPut(endpoint).replyOnce(httpStatusCodes.OK);
+
+ spy = jest.spyOn(axios, 'put');
+
+ wrapper = mount(ServiceDeskRoot, {
+ propsData: {
+ initialIsEnabled: true,
+ endpoint,
+ initialIncomingEmail,
+ selectedTemplate: 'Bug',
+ outgoingName: 'GitLab Support Bot',
+ templates: ['Bug', 'Documentation'],
+ projectKey: 'key',
+ },
+ });
+
+ wrapper.find('button.btn-success').trigger('click');
+
+ return wrapper.vm.$nextTick(() => {
+ expect(spy).toHaveBeenCalledWith(endpoint, {
+ issue_template_key: 'Bug',
+ outgoing_name: 'GitLab Support Bot',
+ project_key: 'key',
+ service_desk_enabled: true,
+ });
+ });
+ });
+
+ it('saves the template when the "Save template" button is clicked', () => {
+ axiosMock.onPut(endpoint).replyOnce(httpStatusCodes.OK);
+
+ wrapper = mount(ServiceDeskRoot, {
+ propsData: {
+ initialIsEnabled: true,
+ endpoint,
+ initialIncomingEmail,
+ selectedTemplate: 'Bug',
+ templates: ['Bug', 'Documentation'],
+ },
+ });
+
+ wrapper.find('button.btn-success').trigger('click');
+
+ return wrapper.vm
+ .$nextTick()
+ .then(waitForPromises)
+ .then(() => {
+ expect(wrapper.html()).toContain('Template was successfully saved.');
+ });
+ });
+
+ it('shows an error message when there is an issue saving the template', () => {
+ axiosMock.onPut(endpoint).networkError();
+
+ wrapper = mount(ServiceDeskRoot, {
+ propsData: {
+ initialIsEnabled: true,
+ endpoint,
+ initialIncomingEmail,
+ selectedTemplate: 'Bug',
+ templates: ['Bug', 'Documentation'],
+ },
+ });
+
+ wrapper.find('button.btn-success').trigger('click');
+
+ return wrapper.vm
+ .$nextTick()
+ .then(waitForPromises)
+ .then(() => {
+ expect(wrapper.html()).toContain(
+ 'An error occurred while saving the template. Please check if the template exists.',
+ );
+ });
+ });
+});
diff --git a/spec/frontend/projects/settings_service_desk/components/service_desk_setting_spec.js b/spec/frontend/projects/settings_service_desk/components/service_desk_setting_spec.js
new file mode 100644
index 00000000000..7fe310aa400
--- /dev/null
+++ b/spec/frontend/projects/settings_service_desk/components/service_desk_setting_spec.js
@@ -0,0 +1,234 @@
+import { shallowMount, mount } from '@vue/test-utils';
+import { GlLoadingIcon } from '@gitlab/ui';
+import eventHub from '~/projects/settings_service_desk/event_hub';
+import ServiceDeskSetting from '~/projects/settings_service_desk/components/service_desk_setting.vue';
+
+describe('ServiceDeskSetting', () => {
+ let wrapper;
+
+ afterEach(() => {
+ if (wrapper) {
+ wrapper.destroy();
+ }
+ });
+
+ const findTemplateDropdown = () => wrapper.find('#service-desk-template-select');
+
+ describe('when isEnabled=true', () => {
+ describe('only isEnabled', () => {
+ describe('as project admin', () => {
+ beforeEach(() => {
+ wrapper = shallowMount(ServiceDeskSetting, {
+ propsData: {
+ isEnabled: true,
+ },
+ });
+ });
+
+ it('should see activation checkbox', () => {
+ expect(wrapper.contains('#service-desk-checkbox')).toBe(true);
+ });
+
+ it('should see main panel with the email info', () => {
+ expect(wrapper.contains('#incoming-email-describer')).toBe(true);
+ });
+
+ it('should see loading spinner and not the incoming email', () => {
+ expect(wrapper.find(GlLoadingIcon).exists()).toBe(true);
+ expect(wrapper.contains('.incoming-email')).toBe(false);
+ });
+ });
+ });
+
+ describe('service desk toggle', () => {
+ it('emits an event to turn on Service Desk when clicked', () => {
+ const eventSpy = jest.fn();
+ eventHub.$on('serviceDeskEnabledCheckboxToggled', eventSpy);
+
+ wrapper = mount(ServiceDeskSetting, {
+ propsData: {
+ isEnabled: false,
+ },
+ });
+
+ wrapper.find('#service-desk-checkbox').trigger('click');
+
+ expect(eventSpy).toHaveBeenCalledWith(true);
+
+ eventHub.$off('serviceDeskEnabledCheckboxToggled', eventSpy);
+ eventSpy.mockRestore();
+ });
+ });
+
+ describe('with incomingEmail', () => {
+ const incomingEmail = 'foo@bar.com';
+
+ beforeEach(() => {
+ wrapper = mount(ServiceDeskSetting, {
+ propsData: {
+ isEnabled: true,
+ incomingEmail,
+ },
+ });
+ });
+
+ it('should see email and not the loading spinner', () => {
+ expect(wrapper.find('.incoming-email').element.value).toEqual(incomingEmail);
+ expect(wrapper.find(GlLoadingIcon).exists()).toBe(false);
+ });
+
+ it('renders a copy to clipboard button', () => {
+ expect(wrapper.contains('.qa-clipboard-button')).toBe(true);
+ expect(wrapper.find('.qa-clipboard-button').element.dataset.clipboardText).toBe(
+ incomingEmail,
+ );
+ });
+ });
+
+ describe('templates dropdown', () => {
+ it('renders a dropdown to choose a template', () => {
+ wrapper = shallowMount(ServiceDeskSetting, {
+ propsData: {
+ isEnabled: true,
+ },
+ });
+
+ expect(wrapper.contains('#service-desk-template-select')).toBe(true);
+ });
+
+ it('renders a dropdown with a default value of ""', () => {
+ wrapper = mount(ServiceDeskSetting, {
+ propsData: {
+ isEnabled: true,
+ },
+ });
+
+ expect(findTemplateDropdown().element.value).toEqual('');
+ });
+
+ it('renders a dropdown with a value of "Bug" when it is the initial value', () => {
+ const templates = ['Bug', 'Documentation', 'Security release'];
+
+ wrapper = mount(ServiceDeskSetting, {
+ propsData: {
+ isEnabled: true,
+ initialSelectedTemplate: 'Bug',
+ templates,
+ },
+ });
+
+ expect(findTemplateDropdown().element.value).toEqual('Bug');
+ });
+
+ it('renders a dropdown with no options when the project has no templates', () => {
+ wrapper = mount(ServiceDeskSetting, {
+ propsData: {
+ isEnabled: true,
+ templates: [],
+ },
+ });
+
+ // The dropdown by default has one empty option
+ expect(findTemplateDropdown().element.children).toHaveLength(1);
+ });
+
+ it('renders a dropdown with options when the project has templates', () => {
+ const templates = ['Bug', 'Documentation', 'Security release'];
+ wrapper = mount(ServiceDeskSetting, {
+ propsData: {
+ isEnabled: true,
+ templates,
+ },
+ });
+
+ // An empty-named template is prepended so the user can select no template
+ const expectedTemplates = [''].concat(templates);
+
+ const dropdown = findTemplateDropdown();
+ const dropdownList = Array.from(dropdown.element.children).map(option => option.innerText);
+
+ expect(dropdown.element.children).toHaveLength(expectedTemplates.length);
+ expect(dropdownList.includes('Bug')).toEqual(true);
+ expect(dropdownList.includes('Documentation')).toEqual(true);
+ expect(dropdownList.includes('Security release')).toEqual(true);
+ });
+ });
+ });
+
+ describe('save button', () => {
+ it('renders a save button to save a template', () => {
+ wrapper = mount(ServiceDeskSetting, {
+ propsData: {
+ isEnabled: true,
+ },
+ });
+
+ expect(wrapper.find('button.btn-success').text()).toContain('Save template');
+ });
+
+ it('emits a save event with the chosen template when the save button is clicked', () => {
+ const eventSpy = jest.fn();
+ eventHub.$on('serviceDeskTemplateSave', eventSpy);
+
+ wrapper = mount(ServiceDeskSetting, {
+ propsData: {
+ isEnabled: true,
+ initialSelectedTemplate: 'Bug',
+ initialOutgoingName: 'GitLab Support Bot',
+ initialProjectKey: 'key',
+ },
+ });
+
+ wrapper.find('button.btn-success').trigger('click');
+
+ expect(eventSpy).toHaveBeenCalledWith({
+ selectedTemplate: 'Bug',
+ outgoingName: 'GitLab Support Bot',
+ projectKey: 'key',
+ });
+
+ eventHub.$off('serviceDeskTemplateSave', eventSpy);
+ eventSpy.mockRestore();
+ });
+ });
+
+ describe('when isEnabled=false', () => {
+ beforeEach(() => {
+ wrapper = shallowMount(ServiceDeskSetting, {
+ propsData: {
+ isEnabled: false,
+ },
+ });
+ });
+
+ it('does not render email panel', () => {
+ expect(wrapper.contains('#incoming-email-describer')).toBe(false);
+ });
+
+ it('does not render template dropdown', () => {
+ expect(wrapper.contains('#service-desk-template-select')).toBe(false);
+ });
+
+ it('does not render template save button', () => {
+ expect(wrapper.contains('button.btn-success')).toBe(false);
+ });
+
+ it('emits an event to turn on Service Desk when the toggle is clicked', () => {
+ const eventSpy = jest.fn();
+ eventHub.$on('serviceDeskEnabledCheckboxToggled', eventSpy);
+
+ wrapper = mount(ServiceDeskSetting, {
+ propsData: {
+ isEnabled: true,
+ },
+ });
+
+ wrapper.find('#service-desk-checkbox').trigger('click');
+
+ expect(eventSpy).toHaveBeenCalledWith(false);
+
+ eventHub.$off('serviceDeskEnabledCheckboxToggled', eventSpy);
+ eventSpy.mockRestore();
+ });
+ });
+});
diff --git a/spec/frontend/projects/settings_service_desk/services/service_desk_service_spec.js b/spec/frontend/projects/settings_service_desk/services/service_desk_service_spec.js
new file mode 100644
index 00000000000..f9e4d55245a
--- /dev/null
+++ b/spec/frontend/projects/settings_service_desk/services/service_desk_service_spec.js
@@ -0,0 +1,129 @@
+import AxiosMockAdapter from 'axios-mock-adapter';
+import ServiceDeskService from '~/projects/settings_service_desk/services/service_desk_service';
+import axios from '~/lib/utils/axios_utils';
+import httpStatusCodes from '~/lib/utils/http_status';
+
+describe('ServiceDeskService', () => {
+ const endpoint = `/gitlab-org/gitlab-test/service_desk`;
+ const dummyResponse = { message: 'Dummy response' };
+ const errorMessage = 'Network Error';
+ let axiosMock;
+ let service;
+
+ beforeEach(() => {
+ axiosMock = new AxiosMockAdapter(axios);
+ service = new ServiceDeskService(endpoint);
+ });
+
+ afterEach(() => {
+ axiosMock.restore();
+ });
+
+ describe('fetchIncomingEmail', () => {
+ it('makes a request to fetch incoming email', () => {
+ axiosMock.onGet(endpoint).replyOnce(httpStatusCodes.OK, dummyResponse);
+
+ return service.fetchIncomingEmail().then(response => {
+ expect(response.data).toEqual(dummyResponse);
+ });
+ });
+
+ it('fails on error response', () => {
+ axiosMock.onGet(endpoint).networkError();
+
+ return service.fetchIncomingEmail().catch(error => {
+ expect(error.message).toBe(errorMessage);
+ });
+ });
+ });
+
+ describe('toggleServiceDesk', () => {
+ it('makes a request to set service desk', () => {
+ axiosMock.onPut(endpoint).replyOnce(httpStatusCodes.OK, dummyResponse);
+
+ return service.toggleServiceDesk(true).then(response => {
+ expect(response.data).toEqual(dummyResponse);
+ });
+ });
+
+ it('fails on error response', () => {
+ axiosMock.onPut(endpoint).networkError();
+
+ return service.toggleServiceDesk(true).catch(error => {
+ expect(error.message).toBe(errorMessage);
+ });
+ });
+
+ it('makes a request with the expected body', () => {
+ axiosMock.onPut(endpoint).replyOnce(httpStatusCodes.OK, dummyResponse);
+
+ const spy = jest.spyOn(axios, 'put');
+
+ service.toggleServiceDesk(true);
+
+ expect(spy).toHaveBeenCalledWith(endpoint, {
+ service_desk_enabled: true,
+ });
+
+ spy.mockRestore();
+ });
+ });
+
+ describe('updateTemplate', () => {
+ it('makes a request to update template', () => {
+ axiosMock.onPut(endpoint).replyOnce(httpStatusCodes.OK, dummyResponse);
+
+ return service
+ .updateTemplate(
+ {
+ selectedTemplate: 'Bug',
+ outgoingName: 'GitLab Support Bot',
+ },
+ true,
+ )
+ .then(response => {
+ expect(response.data).toEqual(dummyResponse);
+ });
+ });
+
+ it('fails on error response', () => {
+ axiosMock.onPut(endpoint).networkError();
+
+ return service
+ .updateTemplate(
+ {
+ selectedTemplate: 'Bug',
+ outgoingName: 'GitLab Support Bot',
+ },
+ true,
+ )
+ .catch(error => {
+ expect(error.message).toBe(errorMessage);
+ });
+ });
+
+ it('makes a request with the expected body', () => {
+ axiosMock.onPut(endpoint).replyOnce(httpStatusCodes.OK, dummyResponse);
+
+ const spy = jest.spyOn(axios, 'put');
+
+ service.updateTemplate(
+ {
+ selectedTemplate: 'Bug',
+ outgoingName: 'GitLab Support Bot',
+ projectKey: 'key',
+ },
+ true,
+ );
+
+ expect(spy).toHaveBeenCalledWith(endpoint, {
+ issue_template_key: 'Bug',
+ outgoing_name: 'GitLab Support Bot',
+ project_key: 'key',
+ service_desk_enabled: true,
+ });
+
+ spy.mockRestore();
+ });
+ });
+});
diff --git a/spec/frontend/ref/components/ref_selector_spec.js b/spec/frontend/ref/components/ref_selector_spec.js
new file mode 100644
index 00000000000..2688e4b3428
--- /dev/null
+++ b/spec/frontend/ref/components/ref_selector_spec.js
@@ -0,0 +1,532 @@
+import Vuex from 'vuex';
+import { mount, createLocalVue } from '@vue/test-utils';
+import axios from 'axios';
+import MockAdapter from 'axios-mock-adapter';
+import { GlLoadingIcon, GlSearchBoxByType, GlNewDropdownItem, GlIcon } from '@gitlab/ui';
+import { trimText } from 'helpers/text_helper';
+import { sprintf } from '~/locale';
+import RefSelector from '~/ref/components/ref_selector.vue';
+import { X_TOTAL_HEADER, DEFAULT_I18N } from '~/ref/constants';
+import createStore from '~/ref/stores/';
+
+const localVue = createLocalVue();
+localVue.use(Vuex);
+
+describe('Ref selector component', () => {
+ const fixtures = {
+ branches: getJSONFixture('api/branches/branches.json'),
+ tags: getJSONFixture('api/tags/tags.json'),
+ commit: getJSONFixture('api/commits/commit.json'),
+ };
+
+ const projectId = '8';
+
+ let wrapper;
+ let branchesApiCallSpy;
+ let tagsApiCallSpy;
+ let commitApiCallSpy;
+
+ const createComponent = () => {
+ wrapper = mount(RefSelector, {
+ propsData: {
+ projectId,
+ value: '',
+ },
+ listeners: {
+ // simulate a parent component v-model binding
+ input: selectedRef => {
+ wrapper.setProps({ value: selectedRef });
+ },
+ },
+ stubs: {
+ GlSearchBoxByType: true,
+ },
+ localVue,
+ store: createStore(),
+ });
+ };
+
+ beforeEach(() => {
+ const mock = new MockAdapter(axios);
+ gon.api_version = 'v4';
+
+ branchesApiCallSpy = jest
+ .fn()
+ .mockReturnValue([200, fixtures.branches, { [X_TOTAL_HEADER]: '123' }]);
+ tagsApiCallSpy = jest.fn().mockReturnValue([200, fixtures.tags, { [X_TOTAL_HEADER]: '456' }]);
+ commitApiCallSpy = jest.fn().mockReturnValue([200, fixtures.commit]);
+
+ mock
+ .onGet(`/api/v4/projects/${projectId}/repository/branches`)
+ .reply(config => branchesApiCallSpy(config));
+ mock
+ .onGet(`/api/v4/projects/${projectId}/repository/tags`)
+ .reply(config => tagsApiCallSpy(config));
+ mock
+ .onGet(new RegExp(`/api/v4/projects/${projectId}/repository/commits/.*`))
+ .reply(config => commitApiCallSpy(config));
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ wrapper = null;
+ });
+
+ //
+ // Finders
+ //
+ const findButtonContent = () => wrapper.find('[data-testid="button-content"]');
+
+ const findNoResults = () => wrapper.find('[data-testid="no-results"]');
+
+ const findLoadingIcon = () => wrapper.find(GlLoadingIcon);
+
+ const findBranchesSection = () => wrapper.find('[data-testid="branches-section"]');
+ const findBranchDropdownItems = () => findBranchesSection().findAll(GlNewDropdownItem);
+ const findFirstBranchDropdownItem = () => findBranchDropdownItems().at(0);
+
+ const findTagsSection = () => wrapper.find('[data-testid="tags-section"]');
+ const findTagDropdownItems = () => findTagsSection().findAll(GlNewDropdownItem);
+ const findFirstTagDropdownItem = () => findTagDropdownItems().at(0);
+
+ const findCommitsSection = () => wrapper.find('[data-testid="commits-section"]');
+ const findCommitDropdownItems = () => findCommitsSection().findAll(GlNewDropdownItem);
+ const findFirstCommitDropdownItem = () => findCommitDropdownItems().at(0);
+
+ //
+ // Expecters
+ //
+ const branchesSectionContainsErrorMessage = () => {
+ const branchesSection = findBranchesSection();
+
+ return branchesSection.text().includes(DEFAULT_I18N.branchesErrorMessage);
+ };
+
+ const tagsSectionContainsErrorMessage = () => {
+ const tagsSection = findTagsSection();
+
+ return tagsSection.text().includes(DEFAULT_I18N.tagsErrorMessage);
+ };
+
+ const commitsSectionContainsErrorMessage = () => {
+ const commitsSection = findCommitsSection();
+
+ return commitsSection.text().includes(DEFAULT_I18N.commitsErrorMessage);
+ };
+
+ //
+ // Convenience methods
+ //
+ const updateQuery = newQuery => {
+ wrapper.find(GlSearchBoxByType).vm.$emit('input', newQuery);
+ };
+
+ const selectFirstBranch = () => {
+ findFirstBranchDropdownItem().vm.$emit('click');
+ };
+
+ const selectFirstTag = () => {
+ findFirstTagDropdownItem().vm.$emit('click');
+ };
+
+ const selectFirstCommit = () => {
+ findFirstCommitDropdownItem().vm.$emit('click');
+ };
+
+ const waitForRequests = ({ andClearMocks } = { andClearMocks: false }) =>
+ axios.waitForAll().then(() => {
+ if (andClearMocks) {
+ branchesApiCallSpy.mockClear();
+ tagsApiCallSpy.mockClear();
+ commitApiCallSpy.mockClear();
+ }
+ });
+
+ describe('initialization behavior', () => {
+ beforeEach(createComponent);
+
+ it('initializes the dropdown with branches and tags when mounted', () => {
+ return waitForRequests().then(() => {
+ expect(branchesApiCallSpy).toHaveBeenCalledTimes(1);
+ expect(tagsApiCallSpy).toHaveBeenCalledTimes(1);
+ expect(commitApiCallSpy).not.toHaveBeenCalled();
+ });
+ });
+
+ it('shows a spinner while network requests are in progress', () => {
+ expect(findLoadingIcon().exists()).toBe(true);
+
+ return waitForRequests().then(() => {
+ expect(findLoadingIcon().exists()).toBe(false);
+ });
+ });
+ });
+
+ describe('post-initialization behavior', () => {
+ describe('when the search query is updated', () => {
+ beforeEach(() => {
+ createComponent();
+
+ return waitForRequests({ andClearMocks: true });
+ });
+
+ it('requeries the endpoints when the search query is updated', () => {
+ updateQuery('v1.2.3');
+
+ return waitForRequests().then(() => {
+ expect(branchesApiCallSpy).toHaveBeenCalledTimes(1);
+ expect(tagsApiCallSpy).toHaveBeenCalledTimes(1);
+ });
+ });
+
+ it("does not make a call to the commit endpoint if the query doesn't look like a SHA", () => {
+ updateQuery('not a sha');
+
+ return waitForRequests().then(() => {
+ expect(commitApiCallSpy).not.toHaveBeenCalled();
+ });
+ });
+
+ it('searches for a commit if the query could potentially be a SHA', () => {
+ updateQuery('abcdef');
+
+ return waitForRequests().then(() => {
+ expect(commitApiCallSpy).toHaveBeenCalled();
+ });
+ });
+ });
+
+ describe('when no results are found', () => {
+ beforeEach(() => {
+ branchesApiCallSpy = jest.fn().mockReturnValue([200, [], { [X_TOTAL_HEADER]: '0' }]);
+ tagsApiCallSpy = jest.fn().mockReturnValue([200, [], { [X_TOTAL_HEADER]: '0' }]);
+ commitApiCallSpy = jest.fn().mockReturnValue([404]);
+
+ createComponent();
+
+ return waitForRequests();
+ });
+
+ describe('when the search query is empty', () => {
+ it('renders a "no results" message', () => {
+ expect(findNoResults().text()).toBe(DEFAULT_I18N.noResults);
+ });
+ });
+
+ describe('when the search query is not empty', () => {
+ const query = 'hello';
+
+ beforeEach(() => {
+ updateQuery(query);
+
+ return waitForRequests();
+ });
+
+ it('renders a "no results" message that includes the search query', () => {
+ expect(findNoResults().text()).toBe(sprintf(DEFAULT_I18N.noResultsWithQuery, { query }));
+ });
+ });
+ });
+
+ describe('branches', () => {
+ describe('when the branches search returns results', () => {
+ beforeEach(() => {
+ createComponent();
+
+ return waitForRequests();
+ });
+
+ it('renders the branches section in the dropdown', () => {
+ expect(findBranchesSection().exists()).toBe(true);
+ });
+
+ it('renders the "Branches" heading with a total number indicator', () => {
+ expect(
+ findBranchesSection()
+ .find('[data-testid="section-header"]')
+ .text(),
+ ).toBe('Branches 123');
+ });
+
+ it("does not render an error message in the branches section's body", () => {
+ expect(branchesSectionContainsErrorMessage()).toBe(false);
+ });
+
+ it('renders each non-default branch as a selectable item', () => {
+ const dropdownItems = findBranchDropdownItems();
+
+ fixtures.branches.forEach((b, i) => {
+ if (!b.default) {
+ expect(dropdownItems.at(i).text()).toBe(b.name);
+ }
+ });
+ });
+
+ it('renders the default branch as a selectable item with a "default" badge', () => {
+ const dropdownItems = findBranchDropdownItems();
+
+ const defaultBranch = fixtures.branches.find(b => b.default);
+ const defaultBranchIndex = fixtures.branches.indexOf(defaultBranch);
+
+ expect(trimText(dropdownItems.at(defaultBranchIndex).text())).toBe(
+ `${defaultBranch.name} default`,
+ );
+ });
+ });
+
+ describe('when the branches search returns no results', () => {
+ beforeEach(() => {
+ branchesApiCallSpy = jest.fn().mockReturnValue([200, [], { [X_TOTAL_HEADER]: '0' }]);
+
+ createComponent();
+
+ return waitForRequests();
+ });
+
+ it('does not render the branches section in the dropdown', () => {
+ expect(findBranchesSection().exists()).toBe(false);
+ });
+ });
+
+ describe('when the branches search returns an error', () => {
+ beforeEach(() => {
+ branchesApiCallSpy = jest.fn().mockReturnValue([500]);
+
+ createComponent();
+
+ return waitForRequests();
+ });
+
+ it('renders the branches section in the dropdown', () => {
+ expect(findBranchesSection().exists()).toBe(true);
+ });
+
+ it("renders an error message in the branches section's body", () => {
+ expect(branchesSectionContainsErrorMessage()).toBe(true);
+ });
+ });
+ });
+
+ describe('tags', () => {
+ describe('when the tags search returns results', () => {
+ beforeEach(() => {
+ createComponent();
+
+ return waitForRequests();
+ });
+
+ it('renders the tags section in the dropdown', () => {
+ expect(findTagsSection().exists()).toBe(true);
+ });
+
+ it('renders the "Tags" heading with a total number indicator', () => {
+ expect(
+ findTagsSection()
+ .find('[data-testid="section-header"]')
+ .text(),
+ ).toBe('Tags 456');
+ });
+
+ it("does not render an error message in the tags section's body", () => {
+ expect(tagsSectionContainsErrorMessage()).toBe(false);
+ });
+
+ it('renders each tag as a selectable item', () => {
+ const dropdownItems = findTagDropdownItems();
+
+ fixtures.tags.forEach((t, i) => {
+ expect(dropdownItems.at(i).text()).toBe(t.name);
+ });
+ });
+ });
+
+ describe('when the tags search returns no results', () => {
+ beforeEach(() => {
+ tagsApiCallSpy = jest.fn().mockReturnValue([200, [], { [X_TOTAL_HEADER]: '0' }]);
+
+ createComponent();
+
+ return waitForRequests();
+ });
+
+ it('does not render the tags section in the dropdown', () => {
+ expect(findTagsSection().exists()).toBe(false);
+ });
+ });
+
+ describe('when the tags search returns an error', () => {
+ beforeEach(() => {
+ tagsApiCallSpy = jest.fn().mockReturnValue([500]);
+
+ createComponent();
+
+ return waitForRequests();
+ });
+
+ it('renders the tags section in the dropdown', () => {
+ expect(findTagsSection().exists()).toBe(true);
+ });
+
+ it("renders an error message in the tags section's body", () => {
+ expect(tagsSectionContainsErrorMessage()).toBe(true);
+ });
+ });
+ });
+
+ describe('commits', () => {
+ describe('when the commit search returns results', () => {
+ beforeEach(() => {
+ createComponent();
+
+ updateQuery('abcd1234');
+
+ return waitForRequests();
+ });
+
+ it('renders the commit section in the dropdown', () => {
+ expect(findCommitsSection().exists()).toBe(true);
+ });
+
+ it('renders the "Commits" heading with a total number indicator', () => {
+ expect(
+ findCommitsSection()
+ .find('[data-testid="section-header"]')
+ .text(),
+ ).toBe('Commits 1');
+ });
+
+ it("does not render an error message in the comits section's body", () => {
+ expect(commitsSectionContainsErrorMessage()).toBe(false);
+ });
+
+ it('renders each commit as a selectable item with the short SHA and commit title', () => {
+ const dropdownItems = findCommitDropdownItems();
+
+ const { commit } = fixtures;
+
+ expect(dropdownItems.at(0).text()).toBe(`${commit.short_id} ${commit.title}`);
+ });
+ });
+
+ describe('when the commit search returns no results (i.e. a 404)', () => {
+ beforeEach(() => {
+ commitApiCallSpy = jest.fn().mockReturnValue([404]);
+
+ createComponent();
+
+ updateQuery('abcd1234');
+
+ return waitForRequests();
+ });
+
+ it('does not render the commits section in the dropdown', () => {
+ expect(findCommitsSection().exists()).toBe(false);
+ });
+ });
+
+ describe('when the commit search returns an error (other than a 404)', () => {
+ beforeEach(() => {
+ commitApiCallSpy = jest.fn().mockReturnValue([500]);
+
+ createComponent();
+
+ updateQuery('abcd1234');
+
+ return waitForRequests();
+ });
+
+ it('renders the commits section in the dropdown', () => {
+ expect(findCommitsSection().exists()).toBe(true);
+ });
+
+ it("renders an error message in the commits section's body", () => {
+ expect(commitsSectionContainsErrorMessage()).toBe(true);
+ });
+ });
+ });
+
+ describe('selection', () => {
+ beforeEach(() => {
+ createComponent();
+
+ updateQuery(fixtures.commit.short_id);
+
+ return waitForRequests();
+ });
+
+ it('renders a checkmark by the selected item', () => {
+ expect(findFirstBranchDropdownItem().find(GlIcon).element).toHaveClass(
+ 'gl-visibility-hidden',
+ );
+
+ selectFirstBranch();
+
+ return localVue.nextTick().then(() => {
+ expect(findFirstBranchDropdownItem().find(GlIcon).element).not.toHaveClass(
+ 'gl-visibility-hidden',
+ );
+ });
+ });
+
+ describe('when a branch is seleceted', () => {
+ it("displays the branch name in the dropdown's button", () => {
+ expect(findButtonContent().text()).toBe(DEFAULT_I18N.noRefSelected);
+
+ selectFirstBranch();
+
+ return localVue.nextTick().then(() => {
+ expect(findButtonContent().text()).toBe(fixtures.branches[0].name);
+ });
+ });
+
+ it("updates the v-model binding with the branch's name", () => {
+ expect(wrapper.vm.value).toEqual('');
+
+ selectFirstBranch();
+
+ expect(wrapper.vm.value).toEqual(fixtures.branches[0].name);
+ });
+ });
+
+ describe('when a tag is seleceted', () => {
+ it("displays the tag name in the dropdown's button", () => {
+ expect(findButtonContent().text()).toBe(DEFAULT_I18N.noRefSelected);
+
+ selectFirstTag();
+
+ return localVue.nextTick().then(() => {
+ expect(findButtonContent().text()).toBe(fixtures.tags[0].name);
+ });
+ });
+
+ it("updates the v-model binding with the tag's name", () => {
+ expect(wrapper.vm.value).toEqual('');
+
+ selectFirstTag();
+
+ expect(wrapper.vm.value).toEqual(fixtures.tags[0].name);
+ });
+ });
+
+ describe('when a commit is selected', () => {
+ it("displays the full SHA in the dropdown's button", () => {
+ expect(findButtonContent().text()).toBe(DEFAULT_I18N.noRefSelected);
+
+ selectFirstCommit();
+
+ return localVue.nextTick().then(() => {
+ expect(findButtonContent().text()).toBe(fixtures.commit.id);
+ });
+ });
+
+ it("updates the v-model binding with the commit's full SHA", () => {
+ expect(wrapper.vm.value).toEqual('');
+
+ selectFirstCommit();
+
+ expect(wrapper.vm.value).toEqual(fixtures.commit.id);
+ });
+ });
+ });
+ });
+});
diff --git a/spec/frontend/ref/stores/actions_spec.js b/spec/frontend/ref/stores/actions_spec.js
new file mode 100644
index 00000000000..32966354c95
--- /dev/null
+++ b/spec/frontend/ref/stores/actions_spec.js
@@ -0,0 +1,180 @@
+import testAction from 'helpers/vuex_action_helper';
+import createState from '~/ref/stores/state';
+import * as actions from '~/ref/stores/actions';
+import * as types from '~/ref/stores/mutation_types';
+
+let mockBranchesReturnValue;
+let mockTagsReturnValue;
+let mockCommitReturnValue;
+
+jest.mock('~/api', () => ({
+ // `__esModule: true` is required when mocking modules with default exports:
+ // https://jestjs.io/docs/en/jest-object#jestmockmodulename-factory-options
+ __esModule: true,
+ default: {
+ branches: () => mockBranchesReturnValue,
+ tags: () => mockTagsReturnValue,
+ commit: () => mockCommitReturnValue,
+ },
+}));
+
+describe('Ref selector Vuex store actions', () => {
+ let state;
+
+ beforeEach(() => {
+ state = createState();
+ });
+
+ describe('setProjectId', () => {
+ it(`commits ${types.SET_PROJECT_ID} with the new project ID`, () => {
+ const projectId = '4';
+ testAction(actions.setProjectId, projectId, state, [
+ { type: types.SET_PROJECT_ID, payload: projectId },
+ ]);
+ });
+ });
+
+ describe('setSelectedRef', () => {
+ it(`commits ${types.SET_SELECTED_REF} with the new selected ref name`, () => {
+ const selectedRef = 'v1.2.3';
+ testAction(actions.setSelectedRef, selectedRef, state, [
+ { type: types.SET_SELECTED_REF, payload: selectedRef },
+ ]);
+ });
+ });
+
+ describe('search', () => {
+ it(`commits ${types.SET_QUERY} with the new search query`, () => {
+ const query = 'hello';
+ testAction(
+ actions.search,
+ query,
+ state,
+ [{ type: types.SET_QUERY, payload: query }],
+ [{ type: 'searchBranches' }, { type: 'searchTags' }, { type: 'searchCommits' }],
+ );
+ });
+ });
+
+ describe('searchBranches', () => {
+ describe('when the search is successful', () => {
+ const branchesApiResponse = { data: [{ name: 'my-feature-branch' }] };
+
+ beforeEach(() => {
+ mockBranchesReturnValue = Promise.resolve(branchesApiResponse);
+ });
+
+ it(`commits ${types.REQUEST_START}, ${types.RECEIVE_BRANCHES_SUCCESS} with the response from the API, and ${types.REQUEST_FINISH}`, () => {
+ return testAction(actions.searchBranches, undefined, state, [
+ { type: types.REQUEST_START },
+ { type: types.RECEIVE_BRANCHES_SUCCESS, payload: branchesApiResponse },
+ { type: types.REQUEST_FINISH },
+ ]);
+ });
+ });
+
+ describe('when the search fails', () => {
+ const error = new Error('Something went wrong!');
+
+ beforeEach(() => {
+ mockBranchesReturnValue = Promise.reject(error);
+ });
+
+ it(`commits ${types.REQUEST_START}, ${types.RECEIVE_BRANCHES_ERROR} with the error object, and ${types.REQUEST_FINISH}`, () => {
+ return testAction(actions.searchBranches, undefined, state, [
+ { type: types.REQUEST_START },
+ { type: types.RECEIVE_BRANCHES_ERROR, payload: error },
+ { type: types.REQUEST_FINISH },
+ ]);
+ });
+ });
+ });
+
+ describe('searchTags', () => {
+ describe('when the search is successful', () => {
+ const tagsApiResponse = { data: [{ name: 'v1.2.3' }] };
+
+ beforeEach(() => {
+ mockTagsReturnValue = Promise.resolve(tagsApiResponse);
+ });
+
+ it(`commits ${types.REQUEST_START}, ${types.RECEIVE_TAGS_SUCCESS} with the response from the API, and ${types.REQUEST_FINISH}`, () => {
+ return testAction(actions.searchTags, undefined, state, [
+ { type: types.REQUEST_START },
+ { type: types.RECEIVE_TAGS_SUCCESS, payload: tagsApiResponse },
+ { type: types.REQUEST_FINISH },
+ ]);
+ });
+ });
+
+ describe('when the search fails', () => {
+ const error = new Error('Something went wrong!');
+
+ beforeEach(() => {
+ mockTagsReturnValue = Promise.reject(error);
+ });
+
+ it(`commits ${types.REQUEST_START}, ${types.RECEIVE_TAGS_ERROR} with the error object, and ${types.REQUEST_FINISH}`, () => {
+ return testAction(actions.searchTags, undefined, state, [
+ { type: types.REQUEST_START },
+ { type: types.RECEIVE_TAGS_ERROR, payload: error },
+ { type: types.REQUEST_FINISH },
+ ]);
+ });
+ });
+ });
+
+ describe('searchCommits', () => {
+ describe('when the search query potentially matches a commit SHA', () => {
+ beforeEach(() => {
+ state.isQueryPossiblyASha = true;
+ });
+
+ describe('when the search is successful', () => {
+ const commitApiResponse = { data: [{ id: 'abcd1234' }] };
+
+ beforeEach(() => {
+ mockCommitReturnValue = Promise.resolve(commitApiResponse);
+ });
+
+ it(`commits ${types.REQUEST_START}, ${types.RECEIVE_COMMITS_SUCCESS} with the response from the API, and ${types.REQUEST_FINISH}`, () => {
+ return testAction(actions.searchCommits, undefined, state, [
+ { type: types.REQUEST_START },
+ { type: types.RECEIVE_COMMITS_SUCCESS, payload: commitApiResponse },
+ { type: types.REQUEST_FINISH },
+ ]);
+ });
+ });
+
+ describe('when the search fails', () => {
+ const error = new Error('Something went wrong!');
+
+ beforeEach(() => {
+ mockCommitReturnValue = Promise.reject(error);
+ });
+
+ describe('when the search query might match a commit SHA', () => {
+ it(`commits ${types.REQUEST_START}, ${types.RECEIVE_COMMITS_ERROR} with the error object, and ${types.REQUEST_FINISH}`, () => {
+ return testAction(actions.searchCommits, undefined, state, [
+ { type: types.REQUEST_START },
+ { type: types.RECEIVE_COMMITS_ERROR, payload: error },
+ { type: types.REQUEST_FINISH },
+ ]);
+ });
+ });
+ });
+ });
+
+ describe('when the search query will not match a commit SHA', () => {
+ beforeEach(() => {
+ state.isQueryPossiblyASha = false;
+ });
+
+ it(`commits ${types.RESET_COMMIT_MATCHES}`, () => {
+ return testAction(actions.searchCommits, undefined, state, [
+ { type: types.RESET_COMMIT_MATCHES },
+ ]);
+ });
+ });
+ });
+});
diff --git a/spec/frontend/ref/stores/getters_spec.js b/spec/frontend/ref/stores/getters_spec.js
new file mode 100644
index 00000000000..49d74e5b9e4
--- /dev/null
+++ b/spec/frontend/ref/stores/getters_spec.js
@@ -0,0 +1,36 @@
+import * as getters from '~/ref/stores/getters';
+
+describe('Ref selector Vuex store getters', () => {
+ describe('isQueryPossiblyASha', () => {
+ it.each`
+ query | isPossiblyASha
+ ${'abcd'} | ${true}
+ ${'ABCD'} | ${true}
+ ${'0123456789abcdef0123456789abcdef01234567'} | ${true}
+ ${'0123456789abcdef0123456789abcdef012345678'} | ${false}
+ ${'abc'} | ${false}
+ ${'ghij'} | ${false}
+ ${' abcd'} | ${false}
+ ${''} | ${false}
+ ${null} | ${false}
+ ${undefined} | ${false}
+ `(
+ 'returns true when the query potentially refers to a commit SHA',
+ ({ query, isPossiblyASha }) => {
+ expect(getters.isQueryPossiblyASha({ query })).toBe(isPossiblyASha);
+ },
+ );
+ });
+
+ describe('isLoading', () => {
+ it.each`
+ requestCount | isLoading
+ ${2} | ${true}
+ ${1} | ${true}
+ ${0} | ${false}
+ ${-1} | ${false}
+ `('returns true when at least one request is in progress', ({ requestCount, isLoading }) => {
+ expect(getters.isLoading({ requestCount })).toBe(isLoading);
+ });
+ });
+});
diff --git a/spec/frontend/ref/stores/mutations_spec.js b/spec/frontend/ref/stores/mutations_spec.js
new file mode 100644
index 00000000000..78117436c33
--- /dev/null
+++ b/spec/frontend/ref/stores/mutations_spec.js
@@ -0,0 +1,274 @@
+import createState from '~/ref/stores/state';
+import mutations from '~/ref/stores/mutations';
+import * as types from '~/ref/stores/mutation_types';
+import { X_TOTAL_HEADER } from '~/ref/constants';
+
+describe('Ref selector Vuex store mutations', () => {
+ let state;
+
+ beforeEach(() => {
+ state = createState();
+ });
+
+ describe('initial state', () => {
+ it('is created with the correct structure and initial values', () => {
+ expect(state).toEqual({
+ projectId: null,
+
+ query: '',
+ matches: {
+ branches: {
+ list: [],
+ totalCount: 0,
+ error: null,
+ },
+ tags: {
+ list: [],
+ totalCount: 0,
+ error: null,
+ },
+ commits: {
+ list: [],
+ totalCount: 0,
+ error: null,
+ },
+ },
+ selectedRef: null,
+ requestCount: 0,
+ });
+ });
+ });
+
+ describe(`${types.SET_PROJECT_ID}`, () => {
+ it('updates the project ID', () => {
+ const newProjectId = '4';
+ mutations[types.SET_PROJECT_ID](state, newProjectId);
+
+ expect(state.projectId).toBe(newProjectId);
+ });
+ });
+
+ describe(`${types.SET_SELECTED_REF}`, () => {
+ it('updates the selected ref', () => {
+ const newSelectedRef = 'my-feature-branch';
+ mutations[types.SET_SELECTED_REF](state, newSelectedRef);
+
+ expect(state.selectedRef).toBe(newSelectedRef);
+ });
+ });
+
+ describe(`${types.SET_QUERY}`, () => {
+ it('updates the search query', () => {
+ const newQuery = 'hello';
+ mutations[types.SET_QUERY](state, newQuery);
+
+ expect(state.query).toBe(newQuery);
+ });
+ });
+
+ describe(`${types.REQUEST_START}`, () => {
+ it('increments requestCount by 1', () => {
+ mutations[types.REQUEST_START](state);
+ expect(state.requestCount).toBe(1);
+
+ mutations[types.REQUEST_START](state);
+ expect(state.requestCount).toBe(2);
+
+ mutations[types.REQUEST_START](state);
+ expect(state.requestCount).toBe(3);
+ });
+ });
+
+ describe(`${types.REQUEST_FINISH}`, () => {
+ it('decrements requestCount by 1', () => {
+ state.requestCount = 3;
+
+ mutations[types.REQUEST_FINISH](state);
+ expect(state.requestCount).toBe(2);
+
+ mutations[types.REQUEST_FINISH](state);
+ expect(state.requestCount).toBe(1);
+
+ mutations[types.REQUEST_FINISH](state);
+ expect(state.requestCount).toBe(0);
+ });
+ });
+
+ describe(`${types.RECEIVE_BRANCHES_SUCCESS}`, () => {
+ it('updates state.matches.branches based on the provided API response', () => {
+ const response = {
+ data: [
+ {
+ name: 'master',
+ default: true,
+
+ // everything except "name" and "default" should be stripped
+ merged: false,
+ protected: true,
+ },
+ {
+ name: 'my-feature-branch',
+ default: false,
+ },
+ ],
+ headers: {
+ [X_TOTAL_HEADER]: 37,
+ },
+ };
+
+ mutations[types.RECEIVE_BRANCHES_SUCCESS](state, response);
+
+ expect(state.matches.branches).toEqual({
+ list: [
+ {
+ name: 'master',
+ default: true,
+ },
+ {
+ name: 'my-feature-branch',
+ default: false,
+ },
+ ],
+ totalCount: 37,
+ error: null,
+ });
+ });
+ });
+
+ describe(`${types.RECEIVE_BRANCHES_ERROR}`, () => {
+ it('updates state.matches.branches to an empty state with the error object', () => {
+ const error = new Error('Something went wrong!');
+
+ state.matches.branches = {
+ list: [{ name: 'my-feature-branch' }],
+ totalCount: 1,
+ error: null,
+ };
+
+ mutations[types.RECEIVE_BRANCHES_ERROR](state, error);
+
+ expect(state.matches.branches).toEqual({
+ list: [],
+ totalCount: 0,
+ error,
+ });
+ });
+ });
+
+ describe(`${types.RECEIVE_REQUEST_TAGS_SUCCESS}`, () => {
+ it('updates state.matches.tags based on the provided API response', () => {
+ const response = {
+ data: [
+ {
+ name: 'v1.2',
+
+ // everything except "name" should be stripped
+ target: '2695effb5807a22ff3d138d593fd856244e155e7',
+ },
+ ],
+ headers: {
+ [X_TOTAL_HEADER]: 23,
+ },
+ };
+
+ mutations[types.RECEIVE_TAGS_SUCCESS](state, response);
+
+ expect(state.matches.tags).toEqual({
+ list: [
+ {
+ name: 'v1.2',
+ },
+ ],
+ totalCount: 23,
+ error: null,
+ });
+ });
+ });
+
+ describe(`${types.RECEIVE_TAGS_ERROR}`, () => {
+ it('updates state.matches.tags to an empty state with the error object', () => {
+ const error = new Error('Something went wrong!');
+
+ state.matches.tags = {
+ list: [{ name: 'v1.2' }],
+ totalCount: 1,
+ error: null,
+ };
+
+ mutations[types.RECEIVE_TAGS_ERROR](state, error);
+
+ expect(state.matches.tags).toEqual({
+ list: [],
+ totalCount: 0,
+ error,
+ });
+ });
+ });
+
+ describe(`${types.RECEIVE_COMMITS_SUCCESS}`, () => {
+ it('updates state.matches.commits based on the provided API response', () => {
+ const response = {
+ data: {
+ id: '2695effb5807a22ff3d138d593fd856244e155e7',
+ short_id: '2695effb580',
+ title: 'Initial commit',
+
+ // everything except "id", "short_id", and "title" should be stripped
+ author_name: 'Example User',
+ },
+ };
+
+ mutations[types.RECEIVE_COMMITS_SUCCESS](state, response);
+
+ expect(state.matches.commits).toEqual({
+ list: [
+ {
+ name: '2695effb580',
+ value: '2695effb5807a22ff3d138d593fd856244e155e7',
+ subtitle: 'Initial commit',
+ },
+ ],
+ totalCount: 1,
+ error: null,
+ });
+ });
+ });
+
+ describe(`${types.RECEIVE_COMMITS_ERROR}`, () => {
+ it('updates state.matches.commits to an empty state with the error object', () => {
+ const error = new Error('Something went wrong!');
+
+ state.matches.commits = {
+ list: [{ name: 'abcd0123' }],
+ totalCount: 1,
+ error: null,
+ };
+
+ mutations[types.RECEIVE_COMMITS_ERROR](state, error);
+
+ expect(state.matches.commits).toEqual({
+ list: [],
+ totalCount: 0,
+ error,
+ });
+ });
+ });
+
+ describe(`${types.RESET_COMMIT_MATCHES}`, () => {
+ it('resets the commit results back to their original (empty) state', () => {
+ state.matches.commits = {
+ list: [{ name: 'abcd0123' }],
+ totalCount: 1,
+ error: null,
+ };
+
+ mutations[types.RESET_COMMIT_MATCHES](state);
+
+ expect(state.matches.commits).toEqual({
+ list: [],
+ totalCount: 0,
+ error: null,
+ });
+ });
+ });
+});
diff --git a/spec/frontend/registry/explorer/components/delete_button_spec.js b/spec/frontend/registry/explorer/components/delete_button_spec.js
new file mode 100644
index 00000000000..bb0fe81117a
--- /dev/null
+++ b/spec/frontend/registry/explorer/components/delete_button_spec.js
@@ -0,0 +1,73 @@
+import { shallowMount } from '@vue/test-utils';
+import { GlButton } from '@gitlab/ui';
+import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
+import component from '~/registry/explorer/components/delete_button.vue';
+
+describe('delete_button', () => {
+ let wrapper;
+
+ const defaultProps = {
+ title: 'Foo title',
+ tooltipTitle: 'Bar tooltipTitle',
+ };
+
+ const findButton = () => wrapper.find(GlButton);
+
+ const mountComponent = props => {
+ wrapper = shallowMount(component, {
+ propsData: {
+ ...defaultProps,
+ ...props,
+ },
+ directives: {
+ GlTooltip: createMockDirective(),
+ },
+ });
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ wrapper = null;
+ });
+
+ describe('tooltip', () => {
+ it('the title is controlled by tooltipTitle prop', () => {
+ mountComponent();
+ const tooltip = getBinding(wrapper.element, 'gl-tooltip');
+ expect(tooltip).toBeDefined();
+ expect(tooltip.value.title).toBe(defaultProps.tooltipTitle);
+ });
+
+ it('is disabled when tooltipTitle is disabled', () => {
+ mountComponent({ tooltipDisabled: true });
+ const tooltip = getBinding(wrapper.element, 'gl-tooltip');
+ expect(tooltip.value.disabled).toBe(true);
+ });
+
+ describe('button', () => {
+ it('exists', () => {
+ mountComponent();
+ expect(findButton().exists()).toBe(true);
+ });
+
+ it('has the correct props/attributes bound', () => {
+ mountComponent({ disabled: true });
+ expect(findButton().attributes()).toMatchObject({
+ 'aria-label': 'Foo title',
+ category: 'secondary',
+ icon: 'remove',
+ title: 'Foo title',
+ variant: 'danger',
+ disabled: 'true',
+ });
+ });
+
+ it('emits a delete event', () => {
+ mountComponent();
+ expect(wrapper.emitted('delete')).toEqual(undefined);
+ findButton().vm.$emit('click');
+ expect(wrapper.emitted('delete')).toEqual([[]]);
+ });
+ });
+ });
+});
diff --git a/spec/frontend/registry/explorer/components/details_page/details_row_spec.js b/spec/frontend/registry/explorer/components/details_page/details_row_spec.js
new file mode 100644
index 00000000000..95b8e18d677
--- /dev/null
+++ b/spec/frontend/registry/explorer/components/details_page/details_row_spec.js
@@ -0,0 +1,43 @@
+import { shallowMount } from '@vue/test-utils';
+import { GlIcon } from '@gitlab/ui';
+import component from '~/registry/explorer/components/details_page/details_row.vue';
+
+describe('DetailsRow', () => {
+ let wrapper;
+
+ const findIcon = () => wrapper.find(GlIcon);
+ const findDefaultSlot = () => wrapper.find('[data-testid="default-slot"]');
+
+ const mountComponent = () => {
+ wrapper = shallowMount(component, {
+ propsData: {
+ icon: 'clock',
+ },
+ slots: {
+ default: '<div data-testid="default-slot"></div>',
+ },
+ });
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ wrapper = null;
+ });
+
+ it('contains an icon', () => {
+ mountComponent();
+ expect(findIcon().exists()).toBe(true);
+ });
+
+ it('icon has the correct props', () => {
+ mountComponent();
+ expect(findIcon().props()).toMatchObject({
+ name: 'clock',
+ });
+ });
+
+ it('has a default slot', () => {
+ mountComponent();
+ expect(findDefaultSlot().exists()).toBe(true);
+ });
+});
diff --git a/spec/frontend/registry/explorer/components/details_page/empty_tags_state.js b/spec/frontend/registry/explorer/components/details_page/empty_tags_state.js
deleted file mode 100644
index da80c75a26a..00000000000
--- a/spec/frontend/registry/explorer/components/details_page/empty_tags_state.js
+++ /dev/null
@@ -1,43 +0,0 @@
-import { shallowMount } from '@vue/test-utils';
-import { GlEmptyState } from '@gitlab/ui';
-import component from '~/registry/explorer/components/details_page/empty_tags_state.vue';
-import {
- EMPTY_IMAGE_REPOSITORY_TITLE,
- EMPTY_IMAGE_REPOSITORY_MESSAGE,
-} from '~/registry/explorer/constants';
-
-describe('EmptyTagsState component', () => {
- let wrapper;
-
- const findEmptyState = () => wrapper.find(GlEmptyState);
-
- const mountComponent = () => {
- wrapper = shallowMount(component, {
- stubs: {
- GlEmptyState,
- },
- propsData: {
- noContainersImage: 'foo',
- },
- });
- };
-
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
- it('contains gl-empty-state', () => {
- mountComponent();
- expect(findEmptyState().exist()).toBe(true);
- });
-
- it('has the correct props', () => {
- mountComponent();
- expect(findEmptyState().props()).toMatchObject({
- title: EMPTY_IMAGE_REPOSITORY_TITLE,
- description: EMPTY_IMAGE_REPOSITORY_MESSAGE,
- svgPath: 'foo',
- });
- });
-});
diff --git a/spec/frontend/registry/explorer/components/details_page/empty_tags_state_spec.js b/spec/frontend/registry/explorer/components/details_page/empty_tags_state_spec.js
new file mode 100644
index 00000000000..09afd9d2d84
--- /dev/null
+++ b/spec/frontend/registry/explorer/components/details_page/empty_tags_state_spec.js
@@ -0,0 +1,43 @@
+import { shallowMount } from '@vue/test-utils';
+import { GlEmptyState } from '@gitlab/ui';
+import component from '~/registry/explorer/components/details_page/empty_tags_state.vue';
+import {
+ EMPTY_IMAGE_REPOSITORY_TITLE,
+ EMPTY_IMAGE_REPOSITORY_MESSAGE,
+} from '~/registry/explorer/constants';
+
+describe('EmptyTagsState component', () => {
+ let wrapper;
+
+ const findEmptyState = () => wrapper.find(GlEmptyState);
+
+ const mountComponent = () => {
+ wrapper = shallowMount(component, {
+ stubs: {
+ GlEmptyState,
+ },
+ propsData: {
+ noContainersImage: 'foo',
+ },
+ });
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ wrapper = null;
+ });
+
+ it('contains gl-empty-state', () => {
+ mountComponent();
+ expect(findEmptyState().exists()).toBe(true);
+ });
+
+ it('has the correct props', () => {
+ mountComponent();
+ expect(findEmptyState().props()).toMatchObject({
+ title: EMPTY_IMAGE_REPOSITORY_TITLE,
+ description: EMPTY_IMAGE_REPOSITORY_MESSAGE,
+ svgPath: 'foo',
+ });
+ });
+});
diff --git a/spec/frontend/registry/explorer/components/details_page/tags_list_row_spec.js b/spec/frontend/registry/explorer/components/details_page/tags_list_row_spec.js
new file mode 100644
index 00000000000..9e876d6d8a3
--- /dev/null
+++ b/spec/frontend/registry/explorer/components/details_page/tags_list_row_spec.js
@@ -0,0 +1,330 @@
+import { shallowMount } from '@vue/test-utils';
+import { GlFormCheckbox, GlSprintf, GlIcon } from '@gitlab/ui';
+
+import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
+import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
+import component from '~/registry/explorer/components/details_page/tags_list_row.vue';
+import DeleteButton from '~/registry/explorer/components/delete_button.vue';
+import DetailsRow from '~/registry/explorer/components/details_page/details_row.vue';
+import {
+ REMOVE_TAG_BUTTON_TITLE,
+ REMOVE_TAG_BUTTON_DISABLE_TOOLTIP,
+ MISSING_MANIFEST_WARNING_TOOLTIP,
+ NOT_AVAILABLE_TEXT,
+ NOT_AVAILABLE_SIZE,
+} from '~/registry/explorer/constants/index';
+import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
+
+import { tagsListResponse } from '../../mock_data';
+import { ListItem } from '../../stubs';
+
+describe('tags list row', () => {
+ let wrapper;
+ const [tag] = [...tagsListResponse.data];
+
+ const defaultProps = { tag, isDesktop: true, index: 0 };
+
+ const findCheckbox = () => wrapper.find(GlFormCheckbox);
+ const findName = () => wrapper.find('[data-testid="name"]');
+ const findSize = () => wrapper.find('[data-testid="size"]');
+ const findTime = () => wrapper.find('[data-testid="time"]');
+ const findShortRevision = () => wrapper.find('[data-testid="digest"]');
+ const findClipboardButton = () => wrapper.find(ClipboardButton);
+ const findDeleteButton = () => wrapper.find(DeleteButton);
+ const findTimeAgoTooltip = () => wrapper.find(TimeAgoTooltip);
+ const findDetailsRows = () => wrapper.findAll(DetailsRow);
+ const findPublishedDateDetail = () => wrapper.find('[data-testid="published-date-detail"]');
+ const findManifestDetail = () => wrapper.find('[data-testid="manifest-detail"]');
+ const findConfigurationDetail = () => wrapper.find('[data-testid="configuration-detail"]');
+ const findWarningIcon = () => wrapper.find(GlIcon);
+
+ const mountComponent = (propsData = defaultProps) => {
+ wrapper = shallowMount(component, {
+ stubs: {
+ GlSprintf,
+ ListItem,
+ DetailsRow,
+ },
+ propsData,
+ directives: {
+ GlTooltip: createMockDirective(),
+ },
+ });
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ wrapper = null;
+ });
+
+ describe('checkbox', () => {
+ it('exists', () => {
+ mountComponent();
+
+ expect(findCheckbox().exists()).toBe(true);
+ });
+
+ it("does not exist when the row can't be deleted", () => {
+ const customTag = { ...tag, destroy_path: '' };
+
+ mountComponent({ ...defaultProps, tag: customTag });
+
+ expect(findCheckbox().exists()).toBe(false);
+ });
+
+ it('is disabled when the digest is missing', () => {
+ mountComponent({ tag: { ...tag, digest: null } });
+ expect(findCheckbox().attributes('disabled')).toBe('true');
+ });
+
+ it('is wired to the selected prop', () => {
+ mountComponent({ ...defaultProps, selected: true });
+
+ expect(findCheckbox().attributes('checked')).toBe('true');
+ });
+
+ it('when changed emit a select event', () => {
+ mountComponent();
+
+ findCheckbox().vm.$emit('change');
+
+ expect(wrapper.emitted('select')).toEqual([[]]);
+ });
+ });
+
+ describe('tag name', () => {
+ it('exists', () => {
+ mountComponent();
+
+ expect(findName().exists()).toBe(true);
+ });
+
+ it('has the correct text', () => {
+ mountComponent();
+
+ expect(findName().text()).toBe(tag.name);
+ });
+
+ it('has a tooltip', () => {
+ mountComponent();
+
+ const tooltip = getBinding(findName().element, 'gl-tooltip');
+
+ expect(tooltip.value.title).toBe(tag.name);
+ });
+
+ it('on mobile has mw-s class', () => {
+ mountComponent({ ...defaultProps, isDesktop: false });
+
+ expect(findName().classes('mw-s')).toBe(true);
+ });
+ });
+
+ describe('clipboard button', () => {
+ it('exist if tag.location exist', () => {
+ mountComponent();
+
+ expect(findClipboardButton().exists()).toBe(true);
+ });
+
+ it('is hidden if tag does not have a location', () => {
+ mountComponent({ ...defaultProps, tag: { ...tag, location: null } });
+
+ expect(findClipboardButton().exists()).toBe(false);
+ });
+
+ it('has the correct props/attributes', () => {
+ mountComponent();
+
+ expect(findClipboardButton().attributes()).toMatchObject({
+ text: 'location',
+ title: 'location',
+ });
+ });
+ });
+
+ describe('warning icon', () => {
+ it('is normally hidden', () => {
+ mountComponent();
+
+ expect(findWarningIcon().exists()).toBe(false);
+ });
+
+ it('is shown when the tag is broken', () => {
+ mountComponent({ tag: { ...tag, digest: null } });
+
+ expect(findWarningIcon().exists()).toBe(true);
+ });
+
+ it('has an appropriate tooltip', () => {
+ mountComponent({ tag: { ...tag, digest: null } });
+
+ const tooltip = getBinding(findWarningIcon().element, 'gl-tooltip');
+ expect(tooltip.value.title).toBe(MISSING_MANIFEST_WARNING_TOOLTIP);
+ });
+ });
+
+ describe('size', () => {
+ it('exists', () => {
+ mountComponent();
+
+ expect(findSize().exists()).toBe(true);
+ });
+
+ it('contains the total_size and layers', () => {
+ mountComponent({ ...defaultProps, tag: { ...tag, total_size: 1024 } });
+
+ expect(findSize().text()).toMatchInterpolatedText('1.00 KiB · 10 layers');
+ });
+
+ it('when total_size is missing', () => {
+ mountComponent();
+
+ expect(findSize().text()).toMatchInterpolatedText(`${NOT_AVAILABLE_SIZE} · 10 layers`);
+ });
+
+ it('when layers are missing', () => {
+ mountComponent({ ...defaultProps, tag: { ...tag, total_size: 1024, layers: null } });
+
+ expect(findSize().text()).toMatchInterpolatedText('1.00 KiB');
+ });
+
+ it('when there is 1 layer', () => {
+ mountComponent({ ...defaultProps, tag: { ...tag, layers: 1 } });
+
+ expect(findSize().text()).toMatchInterpolatedText(`${NOT_AVAILABLE_SIZE} · 1 layer`);
+ });
+ });
+
+ describe('time', () => {
+ it('exists', () => {
+ mountComponent();
+
+ expect(findTime().exists()).toBe(true);
+ });
+
+ it('has the correct text', () => {
+ mountComponent();
+
+ expect(findTime().text()).toBe('Published');
+ });
+
+ it('contains time_ago_tooltip component', () => {
+ mountComponent();
+
+ expect(findTimeAgoTooltip().exists()).toBe(true);
+ });
+
+ it('pass the correct props to time ago tooltip', () => {
+ mountComponent();
+
+ expect(findTimeAgoTooltip().attributes()).toMatchObject({ time: tag.created_at });
+ });
+ });
+
+ describe('digest', () => {
+ it('exists', () => {
+ mountComponent();
+
+ expect(findShortRevision().exists()).toBe(true);
+ });
+
+ it('has the correct text', () => {
+ mountComponent();
+
+ expect(findShortRevision().text()).toMatchInterpolatedText('Digest: 1ab51d5');
+ });
+
+ it(`displays ${NOT_AVAILABLE_TEXT} when digest is missing`, () => {
+ mountComponent({ tag: { ...tag, digest: null } });
+
+ expect(findShortRevision().text()).toMatchInterpolatedText(`Digest: ${NOT_AVAILABLE_TEXT}`);
+ });
+ });
+
+ describe('delete button', () => {
+ it('exists', () => {
+ mountComponent();
+
+ expect(findDeleteButton().exists()).toBe(true);
+ });
+
+ it('has the correct props/attributes', () => {
+ mountComponent();
+
+ expect(findDeleteButton().attributes()).toMatchObject({
+ title: REMOVE_TAG_BUTTON_TITLE,
+ tooltiptitle: REMOVE_TAG_BUTTON_DISABLE_TOOLTIP,
+ tooltipdisabled: 'true',
+ });
+ });
+
+ it.each`
+ destroy_path | digest
+ ${'foo'} | ${null}
+ ${null} | ${'foo'}
+ ${null} | ${null}
+ `(
+ 'is disabled when destroy_path is $destroy_path and digest is $digest',
+ ({ destroy_path, digest }) => {
+ mountComponent({ ...defaultProps, tag: { ...tag, destroy_path, digest } });
+
+ expect(findDeleteButton().attributes('disabled')).toBe('true');
+ },
+ );
+
+ it('delete event emits delete', () => {
+ mountComponent();
+
+ findDeleteButton().vm.$emit('delete');
+
+ expect(wrapper.emitted('delete')).toEqual([[]]);
+ });
+ });
+
+ describe('details rows', () => {
+ describe('when the tag has a digest', () => {
+ beforeEach(() => {
+ mountComponent();
+
+ return wrapper.vm.$nextTick();
+ });
+
+ it('has 3 details rows', () => {
+ expect(findDetailsRows().length).toBe(3);
+ });
+
+ describe.each`
+ name | finderFunction | text | icon | clipboard
+ ${'published date detail'} | ${findPublishedDateDetail} | ${'Published to the bar image repository at 10:23 GMT+0000 on 2020-06-29'} | ${'clock'} | ${false}
+ ${'manifest detail'} | ${findManifestDetail} | ${'Manifest digest: sha256:1ab51d519f574b636ae7788051c60239334ae8622a9fd82a0cf7bae7786dfd5c'} | ${'log'} | ${true}
+ ${'configuration detail'} | ${findConfigurationDetail} | ${'Configuration digest: sha256:b118ab5b0e90b7cb5127db31d5321ac14961d097516a8e0e72084b6cdc783b43'} | ${'cloud-gear'} | ${true}
+ `('$name details row', ({ finderFunction, text, icon, clipboard }) => {
+ it(`has ${text} as text`, () => {
+ expect(finderFunction().text()).toMatchInterpolatedText(text);
+ });
+
+ it(`has the ${icon} icon`, () => {
+ expect(finderFunction().props('icon')).toBe(icon);
+ });
+
+ it(`is ${clipboard} that clipboard button exist`, () => {
+ expect(
+ finderFunction()
+ .find(ClipboardButton)
+ .exists(),
+ ).toBe(clipboard);
+ });
+ });
+ });
+
+ describe('when the tag does not have a digest', () => {
+ it('hides the details rows', async () => {
+ mountComponent({ tag: { ...tag, digest: null } });
+
+ await wrapper.vm.$nextTick();
+ expect(findDetailsRows().length).toBe(0);
+ });
+ });
+ });
+});
diff --git a/spec/frontend/registry/explorer/components/details_page/tags_list_spec.js b/spec/frontend/registry/explorer/components/details_page/tags_list_spec.js
new file mode 100644
index 00000000000..1f560753476
--- /dev/null
+++ b/spec/frontend/registry/explorer/components/details_page/tags_list_spec.js
@@ -0,0 +1,146 @@
+import { shallowMount } from '@vue/test-utils';
+import { GlButton } from '@gitlab/ui';
+import component from '~/registry/explorer/components/details_page/tags_list.vue';
+import TagsListRow from '~/registry/explorer/components/details_page/tags_list_row.vue';
+import { TAGS_LIST_TITLE, REMOVE_TAGS_BUTTON_TITLE } from '~/registry/explorer/constants/index';
+import { tagsListResponse } from '../../mock_data';
+
+describe('Tags List', () => {
+ let wrapper;
+ const tags = [...tagsListResponse.data];
+ const readOnlyTags = tags.map(t => ({ ...t, destroy_path: undefined }));
+
+ const findTagsListRow = () => wrapper.findAll(TagsListRow);
+ const findDeleteButton = () => wrapper.find(GlButton);
+ const findListTitle = () => wrapper.find('[data-testid="list-title"]');
+
+ const mountComponent = (propsData = { tags, isDesktop: true }) => {
+ wrapper = shallowMount(component, {
+ propsData,
+ });
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ wrapper = null;
+ });
+
+ describe('List title', () => {
+ it('exists', () => {
+ mountComponent();
+
+ expect(findListTitle().exists()).toBe(true);
+ });
+
+ it('has the correct text', () => {
+ mountComponent();
+
+ expect(findListTitle().text()).toBe(TAGS_LIST_TITLE);
+ });
+ });
+
+ describe('delete button', () => {
+ it.each`
+ inputTags | isDesktop | isVisible
+ ${tags} | ${true} | ${true}
+ ${tags} | ${false} | ${false}
+ ${readOnlyTags} | ${true} | ${false}
+ ${readOnlyTags} | ${false} | ${false}
+ `(
+ 'is $isVisible that delete button exists when tags is $inputTags and isDesktop is $isDesktop',
+ ({ inputTags, isDesktop, isVisible }) => {
+ mountComponent({ tags: inputTags, isDesktop });
+
+ expect(findDeleteButton().exists()).toBe(isVisible);
+ },
+ );
+
+ it('has the correct text', () => {
+ mountComponent();
+
+ expect(findDeleteButton().text()).toBe(REMOVE_TAGS_BUTTON_TITLE);
+ });
+
+ it('has the correct props', () => {
+ mountComponent();
+
+ expect(findDeleteButton().attributes()).toMatchObject({
+ category: 'secondary',
+ variant: 'danger',
+ });
+ });
+
+ it('is disabled when no item is selected', () => {
+ mountComponent();
+
+ expect(findDeleteButton().attributes('disabled')).toBe('true');
+ });
+
+ it('is enabled when at least one item is selected', async () => {
+ mountComponent();
+ findTagsListRow()
+ .at(0)
+ .vm.$emit('select');
+ await wrapper.vm.$nextTick();
+ expect(findDeleteButton().attributes('disabled')).toBe(undefined);
+ });
+
+ it('click event emits a deleted event with selected items', () => {
+ mountComponent();
+ findTagsListRow()
+ .at(0)
+ .vm.$emit('select');
+
+ findDeleteButton().vm.$emit('click');
+ expect(wrapper.emitted('delete')).toEqual([[{ centos6: true }]]);
+ });
+ });
+
+ describe('list rows', () => {
+ it('one row exist for each tag', () => {
+ mountComponent();
+
+ expect(findTagsListRow()).toHaveLength(tags.length);
+ });
+
+ it('the correct props are bound to it', () => {
+ mountComponent();
+
+ const rows = findTagsListRow();
+
+ expect(rows.at(0).attributes()).toMatchObject({
+ first: 'true',
+ isdesktop: 'true',
+ });
+
+ // The list has only two tags and for some reasons .at(-1) does not work
+ expect(rows.at(1).attributes()).toMatchObject({
+ last: 'true',
+ isdesktop: 'true',
+ });
+ });
+
+ describe('events', () => {
+ it('select event update the selected items', async () => {
+ mountComponent();
+ findTagsListRow()
+ .at(0)
+ .vm.$emit('select');
+ await wrapper.vm.$nextTick();
+ expect(
+ findTagsListRow()
+ .at(0)
+ .attributes('selected'),
+ ).toBe('true');
+ });
+
+ it('delete event emit a delete event', () => {
+ mountComponent();
+ findTagsListRow()
+ .at(0)
+ .vm.$emit('delete');
+ expect(wrapper.emitted('delete')).toEqual([[{ centos6: true }]]);
+ });
+ });
+ });
+});
diff --git a/spec/frontend/registry/explorer/components/details_page/tags_table_spec.js b/spec/frontend/registry/explorer/components/details_page/tags_table_spec.js
deleted file mode 100644
index a60a362dcfe..00000000000
--- a/spec/frontend/registry/explorer/components/details_page/tags_table_spec.js
+++ /dev/null
@@ -1,286 +0,0 @@
-import { mount } from '@vue/test-utils';
-import stubChildren from 'helpers/stub_children';
-import component from '~/registry/explorer/components/details_page/tags_table.vue';
-import { tagsListResponse } from '../../mock_data';
-
-describe('tags_table', () => {
- let wrapper;
- const tags = [...tagsListResponse.data];
-
- const findMainCheckbox = () => wrapper.find('[data-testid="mainCheckbox"]');
- const findFirstRowItem = testid => wrapper.find(`[data-testid="${testid}"]`);
- const findBulkDeleteButton = () => wrapper.find('[data-testid="bulkDeleteButton"]');
- const findAllDeleteButtons = () => wrapper.findAll('[data-testid="singleDeleteButton"]');
- const findAllCheckboxes = () => wrapper.findAll('[data-testid="rowCheckbox"]');
- const findCheckedCheckboxes = () => findAllCheckboxes().filter(c => c.attributes('checked'));
- const findFirsTagColumn = () => wrapper.find('.js-tag-column');
- const findFirstTagNameText = () => wrapper.find('[data-testid="rowNameText"]');
-
- const findLoaderSlot = () => wrapper.find('[data-testid="loaderSlot"]');
- const findEmptySlot = () => wrapper.find('[data-testid="emptySlot"]');
-
- const mountComponent = (propsData = { tags, isDesktop: true }) => {
- wrapper = mount(component, {
- stubs: {
- ...stubChildren(component),
- GlTable: false,
- },
- propsData,
- slots: {
- loader: '<div data-testid="loaderSlot"></div>',
- empty: '<div data-testid="emptySlot"></div>',
- },
- });
- };
-
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
- it.each([
- 'rowCheckbox',
- 'rowName',
- 'rowShortRevision',
- 'rowSize',
- 'rowTime',
- 'singleDeleteButton',
- ])('%s exist in the table', element => {
- mountComponent();
-
- expect(findFirstRowItem(element).exists()).toBe(true);
- });
-
- describe('header checkbox', () => {
- it('exists', () => {
- mountComponent();
- expect(findMainCheckbox().exists()).toBe(true);
- });
-
- it('if selected selects all the rows', () => {
- mountComponent();
- findMainCheckbox().vm.$emit('change');
- return wrapper.vm.$nextTick().then(() => {
- expect(findMainCheckbox().attributes('checked')).toBeTruthy();
- expect(findCheckedCheckboxes()).toHaveLength(tags.length);
- });
- });
-
- it('if deselect deselects all the row', () => {
- mountComponent();
- findMainCheckbox().vm.$emit('change');
- return wrapper.vm
- .$nextTick()
- .then(() => {
- expect(findMainCheckbox().attributes('checked')).toBeTruthy();
- findMainCheckbox().vm.$emit('change');
- return wrapper.vm.$nextTick();
- })
- .then(() => {
- expect(findMainCheckbox().attributes('checked')).toBe(undefined);
- expect(findCheckedCheckboxes()).toHaveLength(0);
- });
- });
- });
-
- describe('row checkbox', () => {
- beforeEach(() => {
- mountComponent();
- });
-
- it('selecting and deselecting the checkbox works as intended', () => {
- findFirstRowItem('rowCheckbox').vm.$emit('change');
- return wrapper.vm
- .$nextTick()
- .then(() => {
- expect(wrapper.vm.selectedItems).toEqual([tags[0].name]);
- expect(findFirstRowItem('rowCheckbox').attributes('checked')).toBeTruthy();
- findFirstRowItem('rowCheckbox').vm.$emit('change');
- return wrapper.vm.$nextTick();
- })
- .then(() => {
- expect(wrapper.vm.selectedItems.length).toBe(0);
- expect(findFirstRowItem('rowCheckbox').attributes('checked')).toBe(undefined);
- });
- });
- });
-
- describe('header delete button', () => {
- beforeEach(() => {
- mountComponent();
- });
-
- it('exists', () => {
- expect(findBulkDeleteButton().exists()).toBe(true);
- });
-
- it('is disabled if no item is selected', () => {
- expect(findBulkDeleteButton().attributes('disabled')).toBe('true');
- });
-
- it('is enabled if at least one item is selected', () => {
- expect(findBulkDeleteButton().attributes('disabled')).toBe('true');
- findFirstRowItem('rowCheckbox').vm.$emit('change');
- return wrapper.vm.$nextTick().then(() => {
- expect(findBulkDeleteButton().attributes('disabled')).toBeFalsy();
- });
- });
-
- describe('on click', () => {
- it('when one item is selected', () => {
- findFirstRowItem('rowCheckbox').vm.$emit('change');
- findBulkDeleteButton().vm.$emit('click');
- expect(wrapper.emitted('delete')).toEqual([[['centos6']]]);
- });
-
- it('when multiple items are selected', () => {
- findMainCheckbox().vm.$emit('change');
- findBulkDeleteButton().vm.$emit('click');
-
- expect(wrapper.emitted('delete')).toEqual([[tags.map(t => t.name)]]);
- });
- });
- });
-
- describe('row delete button', () => {
- beforeEach(() => {
- mountComponent();
- });
-
- it('exists', () => {
- expect(
- findAllDeleteButtons()
- .at(0)
- .exists(),
- ).toBe(true);
- });
-
- it('is disabled if the item has no destroy_path', () => {
- expect(
- findAllDeleteButtons()
- .at(1)
- .attributes('disabled'),
- ).toBe('true');
- });
-
- it('on click', () => {
- findAllDeleteButtons()
- .at(0)
- .vm.$emit('click');
-
- expect(wrapper.emitted('delete')).toEqual([[['centos6']]]);
- });
- });
-
- describe('name cell', () => {
- it('tag column has a tooltip with the tag name', () => {
- mountComponent();
- expect(findFirstTagNameText().attributes('title')).toBe(tagsListResponse.data[0].name);
- });
-
- describe('on desktop viewport', () => {
- beforeEach(() => {
- mountComponent();
- });
-
- it('table header has class w-25', () => {
- expect(findFirsTagColumn().classes()).toContain('w-25');
- });
-
- it('tag column has the mw-m class', () => {
- expect(findFirstRowItem('rowName').classes()).toContain('mw-m');
- });
- });
-
- describe('on mobile viewport', () => {
- beforeEach(() => {
- mountComponent({ tags, isDesktop: false });
- });
-
- it('table header does not have class w-25', () => {
- expect(findFirsTagColumn().classes()).not.toContain('w-25');
- });
-
- it('tag column has the gl-justify-content-end class', () => {
- expect(findFirstRowItem('rowName').classes()).toContain('gl-justify-content-end');
- });
- });
- });
-
- describe('last updated cell', () => {
- let timeCell;
-
- beforeEach(() => {
- mountComponent();
- timeCell = findFirstRowItem('rowTime');
- });
-
- it('displays the time in string format', () => {
- expect(timeCell.text()).toBe('2 years ago');
- });
-
- it('has a tooltip timestamp', () => {
- expect(timeCell.attributes('title')).toBe('Sep 19, 2017 1:45pm GMT+0000');
- });
- });
-
- describe('empty state slot', () => {
- describe('when the table is empty', () => {
- beforeEach(() => {
- mountComponent({ tags: [], isDesktop: true });
- });
-
- it('does not show table rows', () => {
- expect(findFirstTagNameText().exists()).toBe(false);
- });
-
- it('has the empty state slot', () => {
- expect(findEmptySlot().exists()).toBe(true);
- });
- });
-
- describe('when the table is not empty', () => {
- beforeEach(() => {
- mountComponent({ tags, isDesktop: true });
- });
-
- it('does show table rows', () => {
- expect(findFirstTagNameText().exists()).toBe(true);
- });
-
- it('does not show the empty state', () => {
- expect(findEmptySlot().exists()).toBe(false);
- });
- });
- });
-
- describe('loader slot', () => {
- describe('when the data is loading', () => {
- beforeEach(() => {
- mountComponent({ isLoading: true, tags });
- });
-
- it('show the loader', () => {
- expect(findLoaderSlot().exists()).toBe(true);
- });
-
- it('does not show the table rows', () => {
- expect(findFirstTagNameText().exists()).toBe(false);
- });
- });
-
- describe('when the data is not loading', () => {
- beforeEach(() => {
- mountComponent({ isLoading: false, tags });
- });
-
- it('does not show the loader', () => {
- expect(findLoaderSlot().exists()).toBe(false);
- });
-
- it('shows the table rows', () => {
- expect(findFirstTagNameText().exists()).toBe(true);
- });
- });
- });
-});
diff --git a/spec/frontend/registry/explorer/components/list_item_spec.js b/spec/frontend/registry/explorer/components/list_item_spec.js
new file mode 100644
index 00000000000..f244627a8c3
--- /dev/null
+++ b/spec/frontend/registry/explorer/components/list_item_spec.js
@@ -0,0 +1,156 @@
+import { GlButton } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
+import component from '~/registry/explorer/components/list_item.vue';
+
+describe('list item', () => {
+ let wrapper;
+
+ const findLeftActionSlot = () => wrapper.find('[data-testid="left-action"]');
+ const findLeftPrimarySlot = () => wrapper.find('[data-testid="left-primary"]');
+ const findLeftSecondarySlot = () => wrapper.find('[data-testid="left-secondary"]');
+ const findRightPrimarySlot = () => wrapper.find('[data-testid="right-primary"]');
+ const findRightSecondarySlot = () => wrapper.find('[data-testid="right-secondary"]');
+ const findRightActionSlot = () => wrapper.find('[data-testid="right-action"]');
+ const findDetailsSlot = name => wrapper.find(`[data-testid="${name}"]`);
+ const findToggleDetailsButton = () => wrapper.find(GlButton);
+
+ const mountComponent = (propsData, slots) => {
+ wrapper = shallowMount(component, {
+ propsData,
+ slots: {
+ 'left-action': '<div data-testid="left-action" />',
+ 'left-primary': '<div data-testid="left-primary" />',
+ 'left-secondary': '<div data-testid="left-secondary" />',
+ 'right-primary': '<div data-testid="right-primary" />',
+ 'right-secondary': '<div data-testid="right-secondary" />',
+ 'right-action': '<div data-testid="right-action" />',
+ ...slots,
+ },
+ });
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ wrapper = null;
+ });
+
+ it.each`
+ slotName | finderFunction
+ ${'left-primary'} | ${findLeftPrimarySlot}
+ ${'left-secondary'} | ${findLeftSecondarySlot}
+ ${'right-primary'} | ${findRightPrimarySlot}
+ ${'right-secondary'} | ${findRightSecondarySlot}
+ ${'left-action'} | ${findLeftActionSlot}
+ ${'right-action'} | ${findRightActionSlot}
+ `('has a $slotName slot', ({ finderFunction }) => {
+ mountComponent();
+
+ expect(finderFunction().exists()).toBe(true);
+ });
+
+ describe.each`
+ slotNames
+ ${['details_foo']}
+ ${['details_foo', 'details_bar']}
+ ${['details_foo', 'details_bar', 'details_baz']}
+ `('$slotNames details slots', ({ slotNames }) => {
+ const slotMocks = slotNames.reduce((acc, current) => {
+ acc[current] = `<div data-testid="${current}" />`;
+ return acc;
+ }, {});
+
+ it('are visible when details is shown', async () => {
+ mountComponent({}, slotMocks);
+
+ await wrapper.vm.$nextTick();
+ findToggleDetailsButton().vm.$emit('click');
+
+ await wrapper.vm.$nextTick();
+ slotNames.forEach(name => {
+ expect(findDetailsSlot(name).exists()).toBe(true);
+ });
+ });
+ it('are not visible when details are not shown', () => {
+ mountComponent({}, slotMocks);
+
+ slotNames.forEach(name => {
+ expect(findDetailsSlot(name).exists()).toBe(false);
+ });
+ });
+ });
+
+ describe('details toggle button', () => {
+ it('is visible when at least one details slot exists', async () => {
+ mountComponent({}, { details_foo: '<span></span>' });
+ await wrapper.vm.$nextTick();
+ expect(findToggleDetailsButton().exists()).toBe(true);
+ });
+
+ it('is hidden without details slot', () => {
+ mountComponent();
+ expect(findToggleDetailsButton().exists()).toBe(false);
+ });
+ });
+
+ describe('disabled prop', () => {
+ it('when true applies disabled-content class', () => {
+ mountComponent({ disabled: true });
+
+ expect(wrapper.classes('disabled-content')).toBe(true);
+ });
+
+ it('when false does not apply disabled-content class', () => {
+ mountComponent({ disabled: false });
+
+ expect(wrapper.classes('disabled-content')).toBe(false);
+ });
+ });
+
+ describe('first prop', () => {
+ it('when is true displays a double top border', () => {
+ mountComponent({ first: true });
+
+ expect(wrapper.classes('gl-border-t-2')).toBe(true);
+ });
+
+ it('when is false display a single top border', () => {
+ mountComponent({ first: false });
+
+ expect(wrapper.classes('gl-border-t-1')).toBe(true);
+ });
+ });
+
+ describe('last prop', () => {
+ it('when is true displays a double bottom border', () => {
+ mountComponent({ last: true });
+
+ expect(wrapper.classes('gl-border-b-2')).toBe(true);
+ });
+
+ it('when is false display a single bottom border', () => {
+ mountComponent({ last: false });
+
+ expect(wrapper.classes('gl-border-b-1')).toBe(true);
+ });
+ });
+
+ describe('selected prop', () => {
+ it('when true applies the selected border and background', () => {
+ mountComponent({ selected: true });
+
+ expect(wrapper.classes()).toEqual(
+ expect.arrayContaining(['gl-bg-blue-50', 'gl-border-blue-200']),
+ );
+ expect(wrapper.classes()).toEqual(expect.not.arrayContaining(['gl-border-gray-100']));
+ });
+
+ it('when false applies the default border', () => {
+ mountComponent({ selected: false });
+
+ expect(wrapper.classes()).toEqual(
+ expect.not.arrayContaining(['gl-bg-blue-50', 'gl-border-blue-200']),
+ );
+ expect(wrapper.classes()).toEqual(expect.arrayContaining(['gl-border-gray-100']));
+ });
+ });
+});
diff --git a/spec/frontend/registry/explorer/components/list_page/__snapshots__/group_empty_state_spec.js.snap b/spec/frontend/registry/explorer/components/list_page/__snapshots__/group_empty_state_spec.js.snap
index 3761369c944..a8412e2bde9 100644
--- a/spec/frontend/registry/explorer/components/list_page/__snapshots__/group_empty_state_spec.js.snap
+++ b/spec/frontend/registry/explorer/components/list_page/__snapshots__/group_empty_state_spec.js.snap
@@ -2,13 +2,10 @@
exports[`Registry Group Empty state to match the default snapshot 1`] = `
<div
- class="container-message"
svg-path="foo"
title="There are no container images available in this group"
>
- <p
- class="js-no-container-images-text"
- >
+ <p>
With the Container Registry, every project can have its own space to store its Docker images. Push at least one Docker image in one of this group's projects in order to show up here.
<gl-link-stub
href="baz"
diff --git a/spec/frontend/registry/explorer/components/list_page/__snapshots__/project_empty_state_spec.js.snap b/spec/frontend/registry/explorer/components/list_page/__snapshots__/project_empty_state_spec.js.snap
index d8ec9c3ca4d..8413e17c7b2 100644
--- a/spec/frontend/registry/explorer/components/list_page/__snapshots__/project_empty_state_spec.js.snap
+++ b/spec/frontend/registry/explorer/components/list_page/__snapshots__/project_empty_state_spec.js.snap
@@ -2,13 +2,10 @@
exports[`Registry Project Empty state to match the default snapshot 1`] = `
<div
- class="container-message"
svg-path="bazFoo"
title="There are no container images stored for this project"
>
- <p
- class="js-no-container-images-text"
- >
+ <p>
With the Container Registry, every project can have its own space to store its Docker images.
<gl-link-stub
href="baz"
@@ -22,9 +19,7 @@ exports[`Registry Project Empty state to match the default snapshot 1`] = `
CLI Commands
</h5>
- <p
- class="js-not-logged-in-to-registry-text"
- >
+ <p>
If you are not already logged in, you need to authenticate to the Container Registry by using your GitLab username and password. If you have
<gl-link-stub
href="barBaz"
@@ -42,78 +37,50 @@ exports[`Registry Project Empty state to match the default snapshot 1`] = `
instead of a password.
</p>
- <div
- class="input-group append-bottom-10"
+ <gl-form-input-group-stub
+ class="gl-mb-4"
+ predefinedoptions="[object Object]"
+ value=""
>
- <input
- class="form-control monospace"
- readonly="readonly"
+ <gl-form-input-stub
+ class="gl-font-monospace!"
+ readonly=""
type="text"
+ value="docker login bar"
/>
-
- <span
- class="input-group-append"
- >
- <clipboard-button-stub
- class="input-group-text"
- cssclass="btn-default"
- text="docker login bar"
- title="Copy login command"
- tooltipplacement="top"
- />
- </span>
- </div>
+ </gl-form-input-group-stub>
- <p />
-
- <p>
+ <p
+ class="gl-mb-4"
+ >
You can add an image to this registry with the following commands:
</p>
- <div
- class="input-group append-bottom-10"
+ <gl-form-input-group-stub
+ class="gl-mb-4 "
+ predefinedoptions="[object Object]"
+ value=""
>
- <input
- class="form-control monospace"
- readonly="readonly"
+ <gl-form-input-stub
+ class="gl-font-monospace!"
+ readonly=""
type="text"
+ value="docker build -t foo ."
/>
-
- <span
- class="input-group-append"
- >
- <clipboard-button-stub
- class="input-group-text"
- cssclass="btn-default"
- text="docker build -t foo ."
- title="Copy build command"
- tooltipplacement="top"
- />
- </span>
- </div>
+ </gl-form-input-group-stub>
- <div
- class="input-group"
+ <gl-form-input-group-stub
+ predefinedoptions="[object Object]"
+ value=""
>
- <input
- class="form-control monospace"
- readonly="readonly"
+ <gl-form-input-stub
+ class="gl-font-monospace!"
+ readonly=""
type="text"
+ value="docker push foo"
/>
-
- <span
- class="input-group-append"
- >
- <clipboard-button-stub
- class="input-group-text"
- cssclass="btn-default"
- text="docker push foo"
- title="Copy push command"
- tooltipplacement="top"
- />
- </span>
- </div>
+ </gl-form-input-group-stub>
</div>
`;
diff --git a/spec/frontend/registry/explorer/components/list_page/image_list_row_spec.js b/spec/frontend/registry/explorer/components/list_page/image_list_row_spec.js
index 78de35ae1dc..aaeaaf00748 100644
--- a/spec/frontend/registry/explorer/components/list_page/image_list_row_spec.js
+++ b/spec/frontend/registry/explorer/components/list_page/image_list_row_spec.js
@@ -1,11 +1,14 @@
import { shallowMount } from '@vue/test-utils';
import { GlIcon, GlSprintf } from '@gitlab/ui';
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
-import Component from '~/registry/explorer/components/list_page/image_list_row.vue';
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
+import Component from '~/registry/explorer/components/list_page/image_list_row.vue';
+import ListItem from '~/registry/explorer/components/list_item.vue';
+import DeleteButton from '~/registry/explorer/components/delete_button.vue';
import {
ROW_SCHEDULED_FOR_DELETION,
LIST_DELETE_BUTTON_DISABLED,
+ REMOVE_REPOSITORY_LABEL,
} from '~/registry/explorer/constants';
import { RouterLink } from '../../stubs';
import { imagesListResponse } from '../../mock_data';
@@ -13,10 +16,10 @@ import { imagesListResponse } from '../../mock_data';
describe('Image List Row', () => {
let wrapper;
const item = imagesListResponse.data[0];
- const findDeleteBtn = () => wrapper.find('[data-testid="deleteImageButton"]');
+
const findDetailsLink = () => wrapper.find('[data-testid="detailsLink"]');
const findTagsCount = () => wrapper.find('[data-testid="tagsCount"]');
- const findDeleteButtonWrapper = () => wrapper.find('[data-testid="deleteButtonWrapper"]');
+ const findDeleteBtn = () => wrapper.find(DeleteButton);
const findClipboardButton = () => wrapper.find(ClipboardButton);
const mountComponent = props => {
@@ -24,6 +27,7 @@ describe('Image List Row', () => {
stubs: {
RouterLink,
GlSprintf,
+ ListItem,
},
propsData: {
item,
@@ -72,29 +76,24 @@ describe('Image List Row', () => {
});
});
- describe('delete button wrapper', () => {
- it('has a tooltip', () => {
- mountComponent();
- const tooltip = getBinding(findDeleteButtonWrapper().element, 'gl-tooltip');
- expect(tooltip).toBeDefined();
- expect(tooltip.value.title).toBe(LIST_DELETE_BUTTON_DISABLED);
- });
- it('tooltip is enabled when destroy_path is falsy', () => {
- mountComponent({ item: { ...item, destroy_path: null } });
- const tooltip = getBinding(findDeleteButtonWrapper().element, 'gl-tooltip');
- expect(tooltip.value.disabled).toBeFalsy();
- });
- });
-
describe('delete button', () => {
it('exists', () => {
mountComponent();
expect(findDeleteBtn().exists()).toBe(true);
});
+ it('has the correct props', () => {
+ mountComponent();
+ expect(findDeleteBtn().attributes()).toMatchObject({
+ title: REMOVE_REPOSITORY_LABEL,
+ tooltipdisabled: `${Boolean(item.destroy_path)}`,
+ tooltiptitle: LIST_DELETE_BUTTON_DISABLED,
+ });
+ });
+
it('emits a delete event', () => {
mountComponent();
- findDeleteBtn().vm.$emit('click');
+ findDeleteBtn().vm.$emit('delete');
expect(wrapper.emitted('delete')).toEqual([[item]]);
});
diff --git a/spec/frontend/registry/explorer/mock_data.js b/spec/frontend/registry/explorer/mock_data.js
index e2b33826503..a7ffed4c9fd 100644
--- a/spec/frontend/registry/explorer/mock_data.js
+++ b/spec/frontend/registry/explorer/mock_data.js
@@ -70,9 +70,10 @@ export const tagsListResponse = {
size: 19,
layers: 10,
location: 'location',
- path: 'bar',
- created_at: 1505828744434,
+ path: 'bar:centos6',
+ created_at: '2020-06-29T10:23:51.766+00:00',
destroy_path: 'path',
+ digest: 'sha256:1ab51d519f574b636ae7788051c60239334ae8622a9fd82a0cf7bae7786dfd5c',
},
{
name: 'test-tag',
@@ -80,9 +81,10 @@ export const tagsListResponse = {
short_revision: 'b969de599',
size: 19,
layers: 10,
- path: 'foo',
+ path: 'foo:test-tag',
location: 'location-2',
- created_at: 1505828744434,
+ created_at: '2020-06-29T10:23:51.766+00:00',
+ digest: 'sha256:1ab51d519f574b636ae7788051c60239334ae8622a9fd82a0cf7bae7736dfd5c',
},
],
headers,
diff --git a/spec/frontend/registry/explorer/pages/details_spec.js b/spec/frontend/registry/explorer/pages/details_spec.js
index b7e01cad9bc..9bc0bae5c23 100644
--- a/spec/frontend/registry/explorer/pages/details_spec.js
+++ b/spec/frontend/registry/explorer/pages/details_spec.js
@@ -5,6 +5,7 @@ import component from '~/registry/explorer/pages/details.vue';
import DeleteAlert from '~/registry/explorer/components/details_page/delete_alert.vue';
import DetailsHeader from '~/registry/explorer/components/details_page/details_header.vue';
import TagsLoader from '~/registry/explorer/components/details_page/tags_loader.vue';
+import TagsList from '~/registry/explorer/components/details_page/tags_list.vue';
import EmptyTagsState from '~/registry/explorer/components/details_page/empty_tags_state.vue';
import { createStore } from '~/registry/explorer/stores/';
import {
@@ -15,7 +16,7 @@ import {
} from '~/registry/explorer/stores/mutation_types/';
import { tagsListResponse } from '../mock_data';
-import { TagsTable, DeleteModal } from '../stubs';
+import { DeleteModal } from '../stubs';
describe('Details Page', () => {
let wrapper;
@@ -25,18 +26,23 @@ describe('Details Page', () => {
const findDeleteModal = () => wrapper.find(DeleteModal);
const findPagination = () => wrapper.find(GlPagination);
const findTagsLoader = () => wrapper.find(TagsLoader);
- const findTagsTable = () => wrapper.find(TagsTable);
+ const findTagsList = () => wrapper.find(TagsList);
const findDeleteAlert = () => wrapper.find(DeleteAlert);
const findDetailsHeader = () => wrapper.find(DetailsHeader);
const findEmptyTagsState = () => wrapper.find(EmptyTagsState);
const routeId = window.btoa(JSON.stringify({ name: 'foo', tags_path: 'bar' }));
+ const tagsArrayToSelectedTags = tags =>
+ tags.reduce((acc, c) => {
+ acc[c.name] = true;
+ return acc;
+ }, {});
+
const mountComponent = options => {
wrapper = shallowMount(component, {
store,
stubs: {
- TagsTable,
DeleteModal,
},
mocks: {
@@ -66,15 +72,18 @@ describe('Details Page', () => {
describe('when isLoading is true', () => {
beforeEach(() => {
- mountComponent();
store.commit(SET_MAIN_LOADING, true);
- return wrapper.vm.$nextTick();
+ mountComponent();
});
afterEach(() => store.commit(SET_MAIN_LOADING, false));
- it('binds isLoading to tags-table', () => {
- expect(findTagsTable().props('isLoading')).toBe(true);
+ it('shows the loader', () => {
+ expect(findTagsLoader().exists()).toBe(true);
+ });
+
+ it('does not show the list', () => {
+ expect(findTagsList().exists()).toBe(false);
});
it('does not show pagination', () => {
@@ -82,8 +91,9 @@ describe('Details Page', () => {
});
});
- describe('table slots', () => {
+ describe('when the list of tags is empty', () => {
beforeEach(() => {
+ store.commit(SET_TAGS_LIST_SUCCESS, []);
mountComponent();
});
@@ -91,32 +101,37 @@ describe('Details Page', () => {
expect(findEmptyTagsState().exists()).toBe(true);
});
- it('has a skeleton loader', () => {
- expect(findTagsLoader().exists()).toBe(true);
+ it('does not show the loader', () => {
+ expect(findTagsLoader().exists()).toBe(false);
+ });
+
+ it('does not show the list', () => {
+ expect(findTagsList().exists()).toBe(false);
});
});
- describe('table', () => {
+ describe('list', () => {
beforeEach(() => {
mountComponent();
});
it('exists', () => {
- expect(findTagsTable().exists()).toBe(true);
+ expect(findTagsList().exists()).toBe(true);
});
it('has the correct props bound', () => {
- expect(findTagsTable().props()).toMatchObject({
+ expect(findTagsList().props()).toMatchObject({
isDesktop: true,
- isLoading: false,
tags: store.state.tags,
});
});
describe('deleteEvent', () => {
describe('single item', () => {
+ let tagToBeDeleted;
beforeEach(() => {
- findTagsTable().vm.$emit('delete', [store.state.tags[0].name]);
+ [tagToBeDeleted] = store.state.tags;
+ findTagsList().vm.$emit('delete', { [tagToBeDeleted.name]: true });
});
it('open the modal', () => {
@@ -124,7 +139,7 @@ describe('Details Page', () => {
});
it('maps the selection to itemToBeDeleted', () => {
- expect(wrapper.vm.itemsToBeDeleted).toEqual([store.state.tags[0]]);
+ expect(wrapper.vm.itemsToBeDeleted).toEqual([tagToBeDeleted]);
});
it('tracks a single delete event', () => {
@@ -136,7 +151,7 @@ describe('Details Page', () => {
describe('multiple items', () => {
beforeEach(() => {
- findTagsTable().vm.$emit('delete', store.state.tags.map(t => t.name));
+ findTagsList().vm.$emit('delete', tagsArrayToSelectedTags(store.state.tags));
});
it('open the modal', () => {
@@ -202,7 +217,7 @@ describe('Details Page', () => {
describe('when one item is selected to be deleted', () => {
beforeEach(() => {
mountComponent();
- findTagsTable().vm.$emit('delete', [store.state.tags[0].name]);
+ findTagsList().vm.$emit('delete', { [store.state.tags[0].name]: true });
});
it('dispatch requestDeleteTag with the right parameters', () => {
@@ -217,7 +232,7 @@ describe('Details Page', () => {
describe('when more than one item is selected to be deleted', () => {
beforeEach(() => {
mountComponent();
- findTagsTable().vm.$emit('delete', store.state.tags.map(t => t.name));
+ findTagsList().vm.$emit('delete', tagsArrayToSelectedTags(store.state.tags));
});
it('dispatch requestDeleteTags with the right parameters', () => {
diff --git a/spec/frontend/registry/explorer/stubs.js b/spec/frontend/registry/explorer/stubs.js
index d3518c36c82..8f95fce2867 100644
--- a/spec/frontend/registry/explorer/stubs.js
+++ b/spec/frontend/registry/explorer/stubs.js
@@ -1,5 +1,5 @@
-import RealTagsTable from '~/registry/explorer/components/details_page/tags_table.vue';
import RealDeleteModal from '~/registry/explorer/components/details_page/delete_modal.vue';
+import RealListItem from '~/registry/explorer/components/list_item.vue';
export const GlModal = {
template: '<div><slot name="modal-title"></slot><slot></slot><slot name="modal-ok"></slot></div>',
@@ -18,11 +18,6 @@ export const RouterLink = {
props: ['to'],
};
-export const TagsTable = {
- props: RealTagsTable.props,
- template: `<div><slot name="empty"></slot><slot name="loader"></slot></div>`,
-};
-
export const DeleteModal = {
template: '<div></div>',
methods: {
@@ -35,3 +30,13 @@ export const GlSkeletonLoader = {
template: `<div><slot></slot></div>`,
props: ['width', 'height'],
};
+
+export const ListItem = {
+ ...RealListItem,
+ data() {
+ return {
+ detailsSlots: [],
+ isDetailsShown: true,
+ };
+ },
+};
diff --git a/spec/frontend/registry/settings/components/__snapshots__/registry_settings_app_spec.js.snap b/spec/frontend/registry/settings/components/__snapshots__/registry_settings_app_spec.js.snap
index 966acdf52be..11393c89d06 100644
--- a/spec/frontend/registry/settings/components/__snapshots__/registry_settings_app_spec.js.snap
+++ b/spec/frontend/registry/settings/components/__snapshots__/registry_settings_app_spec.js.snap
@@ -2,24 +2,6 @@
exports[`Registry Settings App renders 1`] = `
<div>
- <p>
-
- Tag expiration policy is designed to:
-
- </p>
-
- <ul>
- <li>
- Keep and protect the images that matter most.
- </li>
-
- <li>
-
- Automatically remove extra images that aren't designed to be kept.
-
- </li>
- </ul>
-
<settings-form-stub />
</div>
`;
diff --git a/spec/frontend/registry/settings/components/registry_settings_app_spec.js b/spec/frontend/registry/settings/components/registry_settings_app_spec.js
index 95f784c9727..9551ee72e51 100644
--- a/spec/frontend/registry/settings/components/registry_settings_app_spec.js
+++ b/spec/frontend/registry/settings/components/registry_settings_app_spec.js
@@ -5,6 +5,11 @@ import SettingsForm from '~/registry/settings/components/settings_form.vue';
import { createStore } from '~/registry/settings/store/';
import { SET_SETTINGS, SET_INITIAL_STATE } from '~/registry/settings/store/mutation_types';
import { FETCH_SETTINGS_ERROR_MESSAGE } from '~/registry/shared/constants';
+import {
+ UNAVAILABLE_FEATURE_INTRO_TEXT,
+ UNAVAILABLE_USER_FEATURE_TEXT,
+} from '~/registry/settings/constants';
+
import { stringifiedFormOptions } from '../../shared/mock_data';
describe('Registry Settings App', () => {
@@ -68,10 +73,8 @@ describe('Registry Settings App', () => {
it('shows an alert', () => {
const text = findAlert().text();
- expect(text).toContain(
- 'The Container Registry tag expiration and retention policies for this project have not been enabled.',
- );
- expect(text).toContain('Please contact your administrator.');
+ expect(text).toContain(UNAVAILABLE_FEATURE_INTRO_TEXT);
+ expect(text).toContain(UNAVAILABLE_USER_FEATURE_TEXT);
});
describe('an admin is visiting the page', () => {
diff --git a/spec/frontend/registry/settings/components/settings_form_spec.js b/spec/frontend/registry/settings/components/settings_form_spec.js
index 2b3e529b283..9b9ca92270c 100644
--- a/spec/frontend/registry/settings/components/settings_form_spec.js
+++ b/spec/frontend/registry/settings/components/settings_form_spec.js
@@ -7,6 +7,7 @@ import {
UPDATE_SETTINGS_ERROR_MESSAGE,
UPDATE_SETTINGS_SUCCESS_MESSAGE,
} from '~/registry/shared/constants';
+import waitForPromises from 'helpers/wait_for_promises';
import { stringifiedFormOptions } from '../../shared/mock_data';
describe('Settings Form', () => {
@@ -36,12 +37,17 @@ describe('Settings Form', () => {
const findSaveButton = () => wrapper.find({ ref: 'save-button' });
const findLoadingIcon = (parent = wrapper) => parent.find(GlLoadingIcon);
- const mountComponent = () => {
+ const mountComponent = (data = {}) => {
wrapper = shallowMount(component, {
stubs: {
GlCard,
GlLoadingIcon,
},
+ data() {
+ return {
+ ...data,
+ };
+ },
mocks: {
$toast: {
show: jest.fn(),
@@ -55,7 +61,6 @@ describe('Settings Form', () => {
store = createStore();
store.dispatch('setInitialState', stringifiedFormOptions);
dispatchSpy = jest.spyOn(store, 'dispatch');
- mountComponent();
jest.spyOn(Tracking, 'event');
});
@@ -63,20 +68,30 @@ describe('Settings Form', () => {
wrapper.destroy();
});
+ describe('data binding', () => {
+ it('v-model change update the settings property', () => {
+ mountComponent();
+ findFields().vm.$emit('input', { newValue: 'foo' });
+ expect(dispatchSpy).toHaveBeenCalledWith('updateSettings', { settings: 'foo' });
+ });
+
+ it('v-model change update the api error property', () => {
+ const apiErrors = { baz: 'bar' };
+ mountComponent({ apiErrors });
+ expect(findFields().props('apiErrors')).toEqual(apiErrors);
+ findFields().vm.$emit('input', { newValue: 'foo', modified: 'baz' });
+ expect(findFields().props('apiErrors')).toEqual({});
+ });
+ });
+
describe('form', () => {
let form;
beforeEach(() => {
+ mountComponent();
form = findForm();
dispatchSpy.mockReturnValue();
});
- describe('data binding', () => {
- it('v-model change update the settings property', () => {
- findFields().vm.$emit('input', 'foo');
- expect(dispatchSpy).toHaveBeenCalledWith('updateSettings', { settings: 'foo' });
- });
- });
-
describe('form reset event', () => {
beforeEach(() => {
form.trigger('reset');
@@ -108,24 +123,40 @@ describe('Settings Form', () => {
expect(Tracking.event).toHaveBeenCalledWith(undefined, 'submit_form', trackingPayload);
});
- it('show a success toast when submit succeed', () => {
+ it('show a success toast when submit succeed', async () => {
dispatchSpy.mockResolvedValue();
form.trigger('submit');
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.vm.$toast.show).toHaveBeenCalledWith(UPDATE_SETTINGS_SUCCESS_MESSAGE, {
- type: 'success',
- });
+ await waitForPromises();
+ expect(wrapper.vm.$toast.show).toHaveBeenCalledWith(UPDATE_SETTINGS_SUCCESS_MESSAGE, {
+ type: 'success',
});
});
- it('show an error toast when submit fails', () => {
- dispatchSpy.mockRejectedValue();
- form.trigger('submit');
- return wrapper.vm.$nextTick().then(() => {
+ describe('when submit fails', () => {
+ it('shows an error', async () => {
+ dispatchSpy.mockRejectedValue({ response: {} });
+ form.trigger('submit');
+ await waitForPromises();
expect(wrapper.vm.$toast.show).toHaveBeenCalledWith(UPDATE_SETTINGS_ERROR_MESSAGE, {
type: 'error',
});
});
+
+ it('parses the error messages', async () => {
+ dispatchSpy.mockRejectedValue({
+ response: {
+ data: {
+ message: {
+ foo: 'bar',
+ 'container_expiration_policy.name': ['baz'],
+ },
+ },
+ },
+ });
+ form.trigger('submit');
+ await waitForPromises();
+ expect(findFields().props('apiErrors')).toEqual({ name: 'baz' });
+ });
});
});
});
@@ -134,6 +165,7 @@ describe('Settings Form', () => {
describe('cancel button', () => {
beforeEach(() => {
store.commit('SET_SETTINGS', { foo: 'bar' });
+ mountComponent();
});
it('has type reset', () => {
@@ -165,6 +197,7 @@ describe('Settings Form', () => {
describe('when isLoading is true', () => {
beforeEach(() => {
store.commit('TOGGLE_LOADING');
+ mountComponent();
});
afterEach(() => {
store.commit('TOGGLE_LOADING');
diff --git a/spec/frontend/registry/shared/components/__snapshots__/expiration_policy_fields_spec.js.snap b/spec/frontend/registry/shared/components/__snapshots__/expiration_policy_fields_spec.js.snap
index a9034b81d2f..69953fb5e03 100644
--- a/spec/frontend/registry/shared/components/__snapshots__/expiration_policy_fields_spec.js.snap
+++ b/spec/frontend/registry/shared/components/__snapshots__/expiration_policy_fields_spec.js.snap
@@ -2,32 +2,30 @@
exports[`Expiration Policy Form renders 1`] = `
<div
- class="lh-2"
+ class="gl-line-height-20"
>
<gl-form-group-stub
id="expiration-policy-toggle-group"
- label="Expiration policy:"
+ label="Cleanup policy:"
label-align="right"
label-cols="3"
label-for="expiration-policy-toggle"
>
<div
- class="d-flex align-items-start"
+ class="gl-display-flex"
>
<gl-toggle-stub
id="expiration-policy-toggle"
- labeloff="Toggle Status: OFF"
- labelon="Toggle Status: ON"
- labelposition="hidden"
+ labelposition="top"
/>
<span
- class="mb-2 ml-1 lh-2"
+ class="gl-mb-3 gl-ml-3 gl-line-height-20"
>
- Docker tag expiration policy is
<strong>
- disabled
+ Disabled
</strong>
+ - Tags matching the patterns defined below will be scheduled for deletion
</span>
</div>
</gl-form-group-stub>
@@ -116,7 +114,6 @@ exports[`Expiration Policy Form renders 1`] = `
<gl-form-group-stub
id="expiration-policy-name-matching-group"
- invalid-feedback="The value of this input should be less than 255 characters"
label-align="right"
label-cols="3"
label-for="expiration-policy-name-matching"
@@ -125,6 +122,7 @@ exports[`Expiration Policy Form renders 1`] = `
<gl-form-textarea-stub
disabled="true"
id="expiration-policy-name-matching"
+ noresize="true"
placeholder=".*"
trim=""
value=""
@@ -132,7 +130,6 @@ exports[`Expiration Policy Form renders 1`] = `
</gl-form-group-stub>
<gl-form-group-stub
id="expiration-policy-keep-name-group"
- invalid-feedback="The value of this input should be less than 255 characters"
label-align="right"
label-cols="3"
label-for="expiration-policy-keep-name"
@@ -141,6 +138,7 @@ exports[`Expiration Policy Form renders 1`] = `
<gl-form-textarea-stub
disabled="true"
id="expiration-policy-keep-name"
+ noresize="true"
placeholder=""
trim=""
value=""
diff --git a/spec/frontend/registry/shared/components/expiration_policy_fields_spec.js b/spec/frontend/registry/shared/components/expiration_policy_fields_spec.js
index 4825351a6d3..ee765ffd1c0 100644
--- a/spec/frontend/registry/shared/components/expiration_policy_fields_spec.js
+++ b/spec/frontend/registry/shared/components/expiration_policy_fields_spec.js
@@ -2,7 +2,7 @@ import { shallowMount } from '@vue/test-utils';
import { GlSprintf } from '@gitlab/ui';
import component from '~/registry/shared/components/expiration_policy_fields.vue';
-import { NAME_REGEX_LENGTH } from '~/registry/shared/constants';
+import { NAME_REGEX_LENGTH, ENABLED_TEXT, DISABLED_TEXT } from '~/registry/shared/constants';
import { formOptions } from '../mock_data';
describe('Expiration Policy Form', () => {
@@ -94,7 +94,9 @@ describe('Expiration Policy Form', () => {
: 'input';
element.vm.$emit(modelUpdateEvent, value);
return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.emitted('input')).toEqual([[{ [modelName]: value }]]);
+ expect(wrapper.emitted('input')).toEqual([
+ [{ newValue: { [modelName]: value }, modified: modelName }],
+ ]);
});
});
@@ -126,42 +128,61 @@ describe('Expiration Policy Form', () => {
});
describe.each`
- modelName | elementName | stateVariable
- ${'name_regex'} | ${'name-matching'} | ${'nameRegexState'}
- ${'name_regex_keep'} | ${'keep-name'} | ${'nameKeepRegexState'}
- `('regex textarea validation', ({ modelName, elementName, stateVariable }) => {
- describe(`when name regex is longer than ${NAME_REGEX_LENGTH}`, () => {
- const invalidString = new Array(NAME_REGEX_LENGTH + 2).join(',');
-
- beforeEach(() => {
- mountComponent({ value: { [modelName]: invalidString } });
+ modelName | elementName
+ ${'name_regex'} | ${'name-matching'}
+ ${'name_regex_keep'} | ${'keep-name'}
+ `('regex textarea validation', ({ modelName, elementName }) => {
+ const invalidString = new Array(NAME_REGEX_LENGTH + 2).join(',');
+
+ describe('when apiError contains an error message', () => {
+ const errorMessage = 'something went wrong';
+
+ it('shows the error message on the relevant field', () => {
+ mountComponent({ apiErrors: { [modelName]: errorMessage } });
+ expect(findFormGroup(elementName).attributes('invalid-feedback')).toBe(errorMessage);
});
- it(`${stateVariable} is false`, () => {
- expect(wrapper.vm.textAreaState[stateVariable]).toBe(false);
- });
-
- it('emit the @invalidated event', () => {
- expect(wrapper.emitted('invalidated')).toBeTruthy();
+ it('gives precedence to API errors compared to local ones', () => {
+ mountComponent({
+ apiErrors: { [modelName]: errorMessage },
+ value: { [modelName]: invalidString },
+ });
+ expect(findFormGroup(elementName).attributes('invalid-feedback')).toBe(errorMessage);
});
});
- it('if the user did not type validation is null', () => {
- mountComponent({ value: { [modelName]: '' } });
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.vm.textAreaState[stateVariable]).toBe(null);
+ describe('when apiErrors is empty', () => {
+ it('if the user did not type validation is null', async () => {
+ mountComponent({ value: { [modelName]: '' } });
+ expect(findFormGroup(elementName).attributes('state')).toBeUndefined();
expect(wrapper.emitted('validated')).toBeTruthy();
});
- });
- it(`if the user typed and is less than ${NAME_REGEX_LENGTH} state is true`, () => {
- mountComponent({ value: { [modelName]: 'foo' } });
- return wrapper.vm.$nextTick().then(() => {
+ it(`if the user typed and is less than ${NAME_REGEX_LENGTH} state is true`, () => {
+ mountComponent({ value: { [modelName]: 'foo' } });
+
const formGroup = findFormGroup(elementName);
const formElement = findFormElements(elementName, formGroup);
expect(formGroup.attributes('state')).toBeTruthy();
expect(formElement.attributes('state')).toBeTruthy();
});
+
+ describe(`when name regex is longer than ${NAME_REGEX_LENGTH}`, () => {
+ beforeEach(() => {
+ mountComponent({ value: { [modelName]: invalidString } });
+ });
+
+ it('textAreaValidation state is false', () => {
+ expect(findFormGroup(elementName).attributes('state')).toBeUndefined();
+ // we are forced to check the model attribute because falsy attrs are all casted to undefined in attrs
+ // while in this case false shows an error and null instead shows nothing.
+ expect(wrapper.vm.textAreaValidation[modelName].state).toBe(false);
+ });
+
+ it('emit the @invalidated event', () => {
+ expect(wrapper.emitted('invalidated')).toBeTruthy();
+ });
+ });
});
});
@@ -169,13 +190,13 @@ describe('Expiration Policy Form', () => {
it('toggleDescriptionText show disabled when settings.enabled is false', () => {
mountComponent();
const toggleHelpText = findFormGroup('toggle').find('span');
- expect(toggleHelpText.html()).toContain('disabled');
+ expect(toggleHelpText.html()).toContain(DISABLED_TEXT);
});
it('toggleDescriptionText show enabled when settings.enabled is true', () => {
mountComponent({ value: { enabled: true } });
const toggleHelpText = findFormGroup('toggle').find('span');
- expect(toggleHelpText.html()).toContain('enabled');
+ expect(toggleHelpText.html()).toContain(ENABLED_TEXT);
});
});
});
diff --git a/spec/frontend/releases/components/app_new_spec.js b/spec/frontend/releases/components/app_new_spec.js
new file mode 100644
index 00000000000..0d5664766e5
--- /dev/null
+++ b/spec/frontend/releases/components/app_new_spec.js
@@ -0,0 +1,26 @@
+import Vue from 'vue';
+import Vuex from 'vuex';
+import { mount } from '@vue/test-utils';
+import ReleaseNewApp from '~/releases/components/app_new.vue';
+
+Vue.use(Vuex);
+
+describe('Release new component', () => {
+ let wrapper;
+
+ const factory = () => {
+ const store = new Vuex.Store();
+ wrapper = mount(ReleaseNewApp, { store });
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ wrapper = null;
+ });
+
+ it('renders the app', () => {
+ factory();
+
+ expect(wrapper.exists()).toBe(true);
+ });
+});
diff --git a/spec/frontend/releases/components/release_block_assets_spec.js b/spec/frontend/releases/components/release_block_assets_spec.js
index 44b190b0d19..a85532a8118 100644
--- a/spec/frontend/releases/components/release_block_assets_spec.js
+++ b/spec/frontend/releases/components/release_block_assets_spec.js
@@ -4,6 +4,7 @@ import ReleaseBlockAssets from '~/releases/components/release_block_assets.vue';
import { ASSET_LINK_TYPE } from '~/releases/constants';
import { trimText } from 'helpers/text_helper';
import { assets } from '../mock_data';
+import { cloneDeep } from 'lodash';
describe('Release block assets', () => {
let wrapper;
@@ -30,7 +31,7 @@ describe('Release block assets', () => {
wrapper.findAll('h5').filter(h5 => h5.text() === sections[type]);
beforeEach(() => {
- defaultProps = { assets };
+ defaultProps = { assets: cloneDeep(assets) };
});
describe('with default props', () => {
@@ -96,6 +97,35 @@ describe('Release block assets', () => {
});
});
+ describe('sources', () => {
+ const testSources = ({ shouldSourcesBeRendered }) => {
+ assets.sources.forEach(s => {
+ expect(wrapper.find(`a[href="${s.url}"]`).exists()).toBe(shouldSourcesBeRendered);
+ });
+ };
+
+ describe('when the release has sources', () => {
+ beforeEach(() => {
+ createComponent(defaultProps);
+ });
+
+ it('renders sources', () => {
+ testSources({ shouldSourcesBeRendered: true });
+ });
+ });
+
+ describe('when the release does not have sources', () => {
+ beforeEach(() => {
+ delete defaultProps.assets.sources;
+ createComponent(defaultProps);
+ });
+
+ it('does not render any sources', () => {
+ testSources({ shouldSourcesBeRendered: false });
+ });
+ });
+ });
+
describe('external vs internal links', () => {
const containsExternalSourceIndicator = () =>
wrapper.contains('[data-testid="external-link-indicator"]');
diff --git a/spec/frontend/reports/codequality_report/components/codequality_issue_body_spec.js b/spec/frontend/reports/codequality_report/components/codequality_issue_body_spec.js
new file mode 100644
index 00000000000..3e11af9c9df
--- /dev/null
+++ b/spec/frontend/reports/codequality_report/components/codequality_issue_body_spec.js
@@ -0,0 +1,62 @@
+import { shallowMount } from '@vue/test-utils';
+import component from '~/reports/codequality_report/components/codequality_issue_body.vue';
+import { STATUS_FAILED, STATUS_NEUTRAL, STATUS_SUCCESS } from '~/reports/constants';
+
+describe('code quality issue body issue body', () => {
+ let wrapper;
+
+ const codequalityIssue = {
+ name:
+ 'rubygem-rest-client: session fixation vulnerability via Set-Cookie headers in 30x redirection responses',
+ path: 'Gemfile.lock',
+ severity: 'normal',
+ type: 'Issue',
+ urlPath: '/Gemfile.lock#L22',
+ };
+
+ const mountWithStatus = initialStatus => {
+ wrapper = shallowMount(component, {
+ propsData: {
+ issue: codequalityIssue,
+ status: initialStatus,
+ },
+ });
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ wrapper = null;
+ });
+
+ describe('with success', () => {
+ it('renders fixed label', () => {
+ mountWithStatus(STATUS_SUCCESS);
+
+ expect(wrapper.text()).toContain('Fixed');
+ });
+ });
+
+ describe('without success', () => {
+ it('renders fixed label', () => {
+ mountWithStatus(STATUS_FAILED);
+
+ expect(wrapper.text()).not.toContain('Fixed');
+ });
+ });
+
+ describe('name', () => {
+ it('renders name', () => {
+ mountWithStatus(STATUS_NEUTRAL);
+
+ expect(wrapper.text()).toContain(codequalityIssue.name);
+ });
+ });
+
+ describe('path', () => {
+ it('renders the report-link path using the correct code quality issue', () => {
+ mountWithStatus(STATUS_NEUTRAL);
+
+ expect(wrapper.find('report-link-stub').props('issue')).toBe(codequalityIssue);
+ });
+ });
+});
diff --git a/spec/frontend/reports/codequality_report/grouped_codequality_reports_app_spec.js b/spec/frontend/reports/codequality_report/grouped_codequality_reports_app_spec.js
new file mode 100644
index 00000000000..1905ca0d5e1
--- /dev/null
+++ b/spec/frontend/reports/codequality_report/grouped_codequality_reports_app_spec.js
@@ -0,0 +1,146 @@
+import { mount, createLocalVue } from '@vue/test-utils';
+import Vuex from 'vuex';
+import GroupedCodequalityReportsApp from '~/reports/codequality_report/grouped_codequality_reports_app.vue';
+import CodequalityIssueBody from '~/reports/codequality_report/components/codequality_issue_body.vue';
+import store from '~/reports/codequality_report/store';
+import { mockParsedHeadIssues, mockParsedBaseIssues } from './mock_data';
+
+const localVue = createLocalVue();
+localVue.use(Vuex);
+
+describe('Grouped code quality reports app', () => {
+ const Component = localVue.extend(GroupedCodequalityReportsApp);
+ let wrapper;
+ let mockStore;
+
+ const mountComponent = (props = {}) => {
+ wrapper = mount(Component, {
+ store: mockStore,
+ localVue,
+ propsData: {
+ basePath: 'base.json',
+ headPath: 'head.json',
+ baseBlobPath: 'base/blob/path/',
+ headBlobPath: 'head/blob/path/',
+ codequalityHelpPath: 'codequality_help.html',
+ ...props,
+ },
+ methods: {
+ fetchReports: () => {},
+ },
+ });
+ };
+
+ const findWidget = () => wrapper.find('.js-codequality-widget');
+ const findIssueBody = () => wrapper.find(CodequalityIssueBody);
+
+ beforeEach(() => {
+ mockStore = store();
+ mountComponent();
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ describe('when it is loading reports', () => {
+ beforeEach(() => {
+ mockStore.state.isLoading = true;
+ });
+
+ it('should render loading text', () => {
+ expect(findWidget().text()).toEqual('Loading codeclimate report');
+ });
+ });
+
+ describe('when base and head reports are loaded and compared', () => {
+ describe('with no issues', () => {
+ beforeEach(() => {
+ mockStore.state.newIssues = [];
+ mockStore.state.resolvedIssues = [];
+ });
+
+ it('renders no changes text', () => {
+ expect(findWidget().text()).toEqual('No changes to code quality');
+ });
+ });
+
+ describe('with issues', () => {
+ describe('with new issues', () => {
+ beforeEach(() => {
+ mockStore.state.newIssues = [mockParsedHeadIssues[0]];
+ mockStore.state.resolvedIssues = [];
+ });
+
+ it('renders summary text', () => {
+ expect(findWidget().text()).toContain('Code quality degraded on 1 point');
+ });
+
+ it('renders custom codequality issue body', () => {
+ expect(findIssueBody().props('issue')).toEqual(mockParsedHeadIssues[0]);
+ });
+ });
+
+ describe('with resolved issues', () => {
+ beforeEach(() => {
+ mockStore.state.newIssues = [];
+ mockStore.state.resolvedIssues = [mockParsedBaseIssues[0]];
+ });
+
+ it('renders summary text', () => {
+ expect(findWidget().text()).toContain('Code quality improved on 1 point');
+ });
+
+ it('renders custom codequality issue body', () => {
+ expect(findIssueBody().props('issue')).toEqual(mockParsedBaseIssues[0]);
+ });
+ });
+
+ describe('with new and resolved issues', () => {
+ beforeEach(() => {
+ mockStore.state.newIssues = [mockParsedHeadIssues[0]];
+ mockStore.state.resolvedIssues = [mockParsedBaseIssues[0]];
+ });
+
+ it('renders summary text', () => {
+ expect(findWidget().text()).toContain(
+ 'Code quality improved on 1 point and degraded on 1 point',
+ );
+ });
+
+ it('renders custom codequality issue body', () => {
+ expect(findIssueBody().props('issue')).toEqual(mockParsedHeadIssues[0]);
+ });
+ });
+ });
+ });
+
+ describe('when there is a head report but no base report', () => {
+ beforeEach(() => {
+ mockStore.state.basePath = null;
+ mockStore.state.hasError = true;
+ });
+
+ it('renders error text', () => {
+ expect(findWidget().text()).toEqual('Failed to load codeclimate report');
+ });
+
+ it('renders a help icon with more information', () => {
+ expect(findWidget().html()).toContain('ic-question');
+ });
+ });
+
+ describe('on error', () => {
+ beforeEach(() => {
+ mockStore.state.hasError = true;
+ });
+
+ it('renders error text', () => {
+ expect(findWidget().text()).toContain('Failed to load codeclimate report');
+ });
+
+ it('does not render a help icon', () => {
+ expect(findWidget().html()).not.toContain('ic-question');
+ });
+ });
+});
diff --git a/spec/frontend/reports/codequality_report/mock_data.js b/spec/frontend/reports/codequality_report/mock_data.js
new file mode 100644
index 00000000000..9bd61527d3f
--- /dev/null
+++ b/spec/frontend/reports/codequality_report/mock_data.js
@@ -0,0 +1,90 @@
+export const headIssues = [
+ {
+ check_name: 'Rubocop/Lint/UselessAssignment',
+ description: 'Insecure Dependency',
+ location: {
+ path: 'lib/six.rb',
+ lines: {
+ begin: 6,
+ end: 7,
+ },
+ },
+ fingerprint: 'e879dd9bbc0953cad5037cde7ff0f627',
+ },
+ {
+ categories: ['Security'],
+ check_name: 'Insecure Dependency',
+ description: 'Insecure Dependency',
+ location: {
+ path: 'Gemfile.lock',
+ lines: {
+ begin: 22,
+ end: 22,
+ },
+ },
+ fingerprint: 'ca2e59451e98ae60ba2f54e3857c50e5',
+ },
+];
+
+export const mockParsedHeadIssues = [
+ {
+ ...headIssues[1],
+ name: 'Insecure Dependency',
+ path: 'lib/six.rb',
+ urlPath: 'headPath/lib/six.rb#L6',
+ line: 6,
+ },
+];
+
+export const baseIssues = [
+ {
+ categories: ['Security'],
+ check_name: 'Insecure Dependency',
+ description: 'Insecure Dependency',
+ location: {
+ path: 'Gemfile.lock',
+ lines: {
+ begin: 22,
+ end: 22,
+ },
+ },
+ fingerprint: 'ca2e59451e98ae60ba2f54e3857c50e5',
+ },
+ {
+ categories: ['Security'],
+ check_name: 'Insecure Dependency',
+ description: 'Insecure Dependency',
+ location: {
+ path: 'Gemfile.lock',
+ lines: {
+ begin: 21,
+ end: 21,
+ },
+ },
+ fingerprint: 'ca2354534dee94ae60ba2f54e3857c50e5',
+ },
+];
+
+export const mockParsedBaseIssues = [
+ {
+ ...baseIssues[1],
+ name: 'Insecure Dependency',
+ path: 'Gemfile.lock',
+ line: 21,
+ urlPath: 'basePath/Gemfile.lock#L21',
+ },
+];
+
+export const issueDiff = [
+ {
+ categories: ['Security'],
+ check_name: 'Insecure Dependency',
+ description: 'Insecure Dependency',
+ fingerprint: 'ca2e59451e98ae60ba2f54e3857c50e5',
+ line: 6,
+ location: { lines: { begin: 22, end: 22 }, path: 'Gemfile.lock' },
+ name: 'Insecure Dependency',
+ path: 'lib/six.rb',
+ urlPath: 'headPath/lib/six.rb#L6',
+ },
+];
diff --git a/spec/frontend/reports/codequality_report/store/actions_spec.js b/spec/frontend/reports/codequality_report/store/actions_spec.js
new file mode 100644
index 00000000000..6c30fdb7871
--- /dev/null
+++ b/spec/frontend/reports/codequality_report/store/actions_spec.js
@@ -0,0 +1,151 @@
+import axios from '~/lib/utils/axios_utils';
+import MockAdapter from 'axios-mock-adapter';
+import * as actions from '~/reports/codequality_report/store/actions';
+import * as types from '~/reports/codequality_report/store/mutation_types';
+import createStore from '~/reports/codequality_report/store';
+import { TEST_HOST } from 'spec/test_constants';
+import testAction from 'helpers/vuex_action_helper';
+import { headIssues, baseIssues, mockParsedHeadIssues, mockParsedBaseIssues } from '../mock_data';
+
+// mock codequality comparison worker
+jest.mock('~/reports/codequality_report/workers/codequality_comparison_worker', () =>
+ jest.fn().mockImplementation(() => {
+ return {
+ addEventListener: (eventName, callback) => {
+ callback({
+ data: {
+ newIssues: [mockParsedHeadIssues[0]],
+ resolvedIssues: [mockParsedBaseIssues[0]],
+ },
+ });
+ },
+ };
+ }),
+);
+
+describe('Codequality Reports actions', () => {
+ let localState;
+ let localStore;
+
+ beforeEach(() => {
+ localStore = createStore();
+ localState = localStore.state;
+ });
+
+ describe('setPaths', () => {
+ it('should commit SET_PATHS mutation', done => {
+ const paths = {
+ basePath: 'basePath',
+ headPath: 'headPath',
+ baseBlobPath: 'baseBlobPath',
+ headBlobPath: 'headBlobPath',
+ helpPath: 'codequalityHelpPath',
+ };
+
+ testAction(
+ actions.setPaths,
+ paths,
+ localState,
+ [{ type: types.SET_PATHS, payload: paths }],
+ [],
+ done,
+ );
+ });
+ });
+
+ describe('fetchReports', () => {
+ let mock;
+
+ beforeEach(() => {
+ localState.headPath = `${TEST_HOST}/head.json`;
+ localState.basePath = `${TEST_HOST}/base.json`;
+ mock = new MockAdapter(axios);
+ });
+
+ afterEach(() => {
+ mock.restore();
+ });
+
+ describe('on success', () => {
+ it('commits REQUEST_REPORTS and dispatches receiveReportsSuccess', done => {
+ mock.onGet(`${TEST_HOST}/head.json`).reply(200, headIssues);
+ mock.onGet(`${TEST_HOST}/base.json`).reply(200, baseIssues);
+
+ testAction(
+ actions.fetchReports,
+ null,
+ localState,
+ [{ type: types.REQUEST_REPORTS }],
+ [
+ {
+ payload: {
+ newIssues: [mockParsedHeadIssues[0]],
+ resolvedIssues: [mockParsedBaseIssues[0]],
+ },
+ type: 'receiveReportsSuccess',
+ },
+ ],
+ done,
+ );
+ });
+ });
+
+ describe('on error', () => {
+ it('commits REQUEST_REPORTS and dispatches receiveReportsError', done => {
+ mock.onGet(`${TEST_HOST}/head.json`).reply(500);
+
+ testAction(
+ actions.fetchReports,
+ null,
+ localState,
+ [{ type: types.REQUEST_REPORTS }],
+ [{ type: 'receiveReportsError' }],
+ done,
+ );
+ });
+ });
+
+ describe('with no base path', () => {
+ it('commits REQUEST_REPORTS and dispatches receiveReportsError', done => {
+ localState.basePath = null;
+
+ testAction(
+ actions.fetchReports,
+ null,
+ localState,
+ [{ type: types.REQUEST_REPORTS }],
+ [{ type: 'receiveReportsError' }],
+ done,
+ );
+ });
+ });
+ });
+
+ describe('receiveReportsSuccess', () => {
+ it('commits RECEIVE_REPORTS_SUCCESS', done => {
+ const data = { issues: [] };
+
+ testAction(
+ actions.receiveReportsSuccess,
+ data,
+ localState,
+ [{ type: types.RECEIVE_REPORTS_SUCCESS, payload: data }],
+ [],
+ done,
+ );
+ });
+ });
+
+ describe('receiveReportsError', () => {
+ it('commits RECEIVE_REPORTS_ERROR', done => {
+ testAction(
+ actions.receiveReportsError,
+ null,
+ localState,
+ [{ type: types.RECEIVE_REPORTS_ERROR }],
+ [],
+ done,
+ );
+ });
+ });
+});
diff --git a/spec/frontend/reports/codequality_report/store/getters_spec.js b/spec/frontend/reports/codequality_report/store/getters_spec.js
new file mode 100644
index 00000000000..a641e2fe74f
--- /dev/null
+++ b/spec/frontend/reports/codequality_report/store/getters_spec.js
@@ -0,0 +1,95 @@
+import * as getters from '~/reports/codequality_report/store/getters';
+import createStore from '~/reports/codequality_report/store';
+import { LOADING, ERROR, SUCCESS } from '~/reports/constants';
+
+describe('Codequality reports store getters', () => {
+ let localState;
+ let localStore;
+
+ beforeEach(() => {
+ localStore = createStore();
+ localState = localStore.state;
+ });
+
+ describe('hasCodequalityIssues', () => {
+ describe('when there are issues', () => {
+ it('returns true', () => {
+ localState.newIssues = [{ reason: 'repetitive code' }];
+ localState.resolvedIssues = [];
+
+ expect(getters.hasCodequalityIssues(localState)).toEqual(true);
+
+ localState.newIssues = [];
+ localState.resolvedIssues = [{ reason: 'repetitive code' }];
+
+ expect(getters.hasCodequalityIssues(localState)).toEqual(true);
+ });
+ });
+
+ describe('when there are no issues', () => {
+ it('returns false when there are no issues', () => {
+ expect(getters.hasCodequalityIssues(localState)).toEqual(false);
+ });
+ });
+ });
+
+ describe('codequalityStatus', () => {
+ describe('when loading', () => {
+ it('returns loading status', () => {
+ localState.isLoading = true;
+
+ expect(getters.codequalityStatus(localState)).toEqual(LOADING);
+ });
+ });
+
+ describe('on error', () => {
+ it('returns error status', () => {
+ localState.hasError = true;
+
+ expect(getters.codequalityStatus(localState)).toEqual(ERROR);
+ });
+ });
+
+ describe('when successfully loaded', () => {
+ it('returns error status', () => {
+ expect(getters.codequalityStatus(localState)).toEqual(SUCCESS);
+ });
+ });
+ });
+
+ describe('codequalityText', () => {
+ it.each`
+ resolvedIssues | newIssues | expectedText
+ ${0} | ${0} | ${'No changes to code quality'}
+ ${0} | ${1} | ${'Code quality degraded on 1 point'}
+ ${2} | ${0} | ${'Code quality improved on 2 points'}
+ ${1} | ${2} | ${'Code quality improved on 1 point and degraded on 2 points'}
+ `(
+ 'returns a summary containing $resolvedIssues resolved issues and $newIssues new issues',
+ ({ newIssues, resolvedIssues, expectedText }) => {
+ localState.newIssues = new Array(newIssues).fill({ reason: 'Repetitive code' });
+ localState.resolvedIssues = new Array(resolvedIssues).fill({ reason: 'Repetitive code' });
+
+ expect(getters.codequalityText(localState)).toEqual(expectedText);
+ },
+ );
+ });
+
+ describe('codequalityPopover', () => {
+ describe('when head report is available but base report is not', () => {
+ it('returns a popover with a documentation link', () => {
+ localState.headPath = 'head.json';
+ localState.basePath = undefined;
+ localState.helpPath = 'codequality_help.html';
+
+ expect(getters.codequalityPopover(localState).title).toEqual(
+ 'Base pipeline codequality artifact not found',
+ );
+ expect(getters.codequalityPopover(localState).content).toContain(
+ 'Learn more about codequality reports',
+ 'href="codequality_help.html"',
+ );
+ });
+ });
+ });
+});
diff --git a/spec/frontend/reports/codequality_report/store/mutations_spec.js b/spec/frontend/reports/codequality_report/store/mutations_spec.js
new file mode 100644
index 00000000000..658abf3088c
--- /dev/null
+++ b/spec/frontend/reports/codequality_report/store/mutations_spec.js
@@ -0,0 +1,80 @@
+import mutations from '~/reports/codequality_report/store/mutations';
+import createStore from '~/reports/codequality_report/store';
+
+describe('Codequality Reports mutations', () => {
+ let localState;
+ let localStore;
+
+ beforeEach(() => {
+ localStore = createStore();
+ localState = localStore.state;
+ });
+
+ describe('SET_PATHS', () => {
+ it('sets paths to given values', () => {
+ const basePath = 'base.json';
+ const headPath = 'head.json';
+ const baseBlobPath = 'base/blob/path/';
+ const headBlobPath = 'head/blob/path/';
+ const helpPath = 'help.html';
+
+ mutations.SET_PATHS(localState, {
+ basePath,
+ headPath,
+ baseBlobPath,
+ headBlobPath,
+ helpPath,
+ });
+
+ expect(localState.basePath).toEqual(basePath);
+ expect(localState.headPath).toEqual(headPath);
+ expect(localState.baseBlobPath).toEqual(baseBlobPath);
+ expect(localState.headBlobPath).toEqual(headBlobPath);
+ expect(localState.helpPath).toEqual(helpPath);
+ });
+ });
+
+ describe('REQUEST_REPORTS', () => {
+ it('sets isLoading to true', () => {
+ mutations.REQUEST_REPORTS(localState);
+
+ expect(localState.isLoading).toEqual(true);
+ });
+ });
+
+ describe('RECEIVE_REPORTS_SUCCESS', () => {
+ it('sets isLoading to false', () => {
+ mutations.RECEIVE_REPORTS_SUCCESS(localState, {});
+
+ expect(localState.isLoading).toEqual(false);
+ });
+
+ it('sets hasError to false', () => {
+ mutations.RECEIVE_REPORTS_SUCCESS(localState, {});
+
+ expect(localState.hasError).toEqual(false);
+ });
+
+ it('sets newIssues and resolvedIssues from response data', () => {
+ const data = { newIssues: [{ id: 1 }], resolvedIssues: [{ id: 2 }] };
+ mutations.RECEIVE_REPORTS_SUCCESS(localState, data);
+
+ expect(localState.newIssues).toEqual(data.newIssues);
+ expect(localState.resolvedIssues).toEqual(data.resolvedIssues);
+ });
+ });
+
+ describe('RECEIVE_REPORTS_ERROR', () => {
+ it('sets isLoading to false', () => {
+ mutations.RECEIVE_REPORTS_ERROR(localState);
+
+ expect(localState.isLoading).toEqual(false);
+ });
+
+ it('sets hasError to true', () => {
+ mutations.RECEIVE_REPORTS_ERROR(localState);
+
+ expect(localState.hasError).toEqual(true);
+ });
+ });
+});
diff --git a/spec/frontend/reports/codequality_report/store/utils/codequality_comparison_spec.js b/spec/frontend/reports/codequality_report/store/utils/codequality_comparison_spec.js
new file mode 100644
index 00000000000..5dd69d3c4d4
--- /dev/null
+++ b/spec/frontend/reports/codequality_report/store/utils/codequality_comparison_spec.js
@@ -0,0 +1,139 @@
+import {
+ parseCodeclimateMetrics,
+ doCodeClimateComparison,
+} from '~/reports/codequality_report/store/utils/codequality_comparison';
+import { baseIssues, mockParsedHeadIssues, mockParsedBaseIssues } from '../../mock_data';
+
+jest.mock('~/reports/codequality_report/workers/codequality_comparison_worker', () => {
+ let mockPostMessageCallback;
+ return jest.fn().mockImplementation(() => {
+ return {
+ addEventListener: (_, callback) => {
+ mockPostMessageCallback = callback;
+ },
+ postMessage: data => {
+ if (!data.headIssues) return mockPostMessageCallback({ data: {} });
+ if (!data.baseIssues) throw new Error();
+ const key = 'fingerprint';
+ return mockPostMessageCallback({
+ data: {
+ newIssues: data.headIssues.filter(
+ item => !data.baseIssues.find(el => el[key] === item[key]),
+ ),
+ resolvedIssues: data.baseIssues.filter(
+ item => !data.headIssues.find(el => el[key] === item[key]),
+ ),
+ },
+ });
+ },
+ };
+ });
+});
+
+describe('Codequality report store utils', () => {
+ let result;
+
+ describe('parseCodeclimateMetrics', () => {
+ it('should parse the received issues', () => {
+ [result] = parseCodeclimateMetrics(baseIssues, 'path');
+
+ expect(result.name).toEqual(baseIssues[0].check_name);
+ expect(result.path).toEqual(baseIssues[0].location.path);
+ expect(result.line).toEqual(baseIssues[0].location.lines.begin);
+ });
+
+ describe('when an issue has no location or path', () => {
+ const issue = { description: 'Insecure Dependency' };
+
+ beforeEach(() => {
+ [result] = parseCodeclimateMetrics([issue], 'path');
+ });
+
+ it('is parsed', () => {
+ expect(result.name).toEqual(issue.description);
+ });
+ });
+
+ describe('when an issue has a path but no line', () => {
+ const issue = { description: 'Insecure Dependency', location: { path: 'Gemfile.lock' } };
+
+ beforeEach(() => {
+ [result] = parseCodeclimateMetrics([issue], 'path');
+ });
+
+ it('is parsed', () => {
+ expect(result.name).toEqual(issue.description);
+ expect(result.path).toEqual(issue.location.path);
+ expect(result.urlPath).toEqual(`path/${issue.location.path}`);
+ });
+ });
+
+ describe('when an issue has a line nested in positions', () => {
+ const issue = {
+ description: 'Insecure Dependency',
+ location: {
+ path: 'Gemfile.lock',
+ positions: { begin: { line: 84 } },
+ },
+ };
+
+ beforeEach(() => {
+ [result] = parseCodeclimateMetrics([issue], 'path');
+ });
+
+ it('is parsed', () => {
+ expect(result.name).toEqual(issue.description);
+ expect(result.path).toEqual(issue.location.path);
+ expect(result.urlPath).toEqual(
+ `path/${issue.location.path}#L${issue.location.positions.begin.line}`,
+ );
+ });
+ });
+
+ describe('with an empty issue array', () => {
+ beforeEach(() => {
+ result = parseCodeclimateMetrics([], 'path');
+ });
+
+ it('returns an empty array', () => {
+ expect(result).toEqual([]);
+ });
+ });
+ });
+
+ describe('doCodeClimateComparison', () => {
+ describe('when the comparison worker finds changed issues', () => {
+ beforeEach(async () => {
+ result = await doCodeClimateComparison(mockParsedHeadIssues, mockParsedBaseIssues);
+ });
+
+ it('returns the new and resolved issues', () => {
+ expect(result.resolvedIssues[0]).toEqual(mockParsedBaseIssues[0]);
+ expect(result.newIssues[0]).toEqual(mockParsedHeadIssues[0]);
+ });
+ });
+
+ describe('when the comparison worker finds no changed issues', () => {
+ beforeEach(async () => {
+ result = await doCodeClimateComparison([], []);
+ });
+
+ it('returns the empty issue arrays', () => {
+ expect(result.newIssues).toEqual([]);
+ expect(result.resolvedIssues).toEqual([]);
+ });
+ });
+
+ describe('when the comparison worker is given malformed data', () => {
+ it('rejects the promise', () => {
+ return expect(doCodeClimateComparison(null)).rejects.toEqual({});
+ });
+ });
+
+ describe('when the comparison worker encounters an error', () => {
+ it('rejects the promise and throws an error', () => {
+ return expect(doCodeClimateComparison([], null)).rejects.toThrow();
+ });
+ });
+ });
+});
diff --git a/spec/frontend/reports/components/grouped_test_reports_app_spec.js b/spec/frontend/reports/components/grouped_test_reports_app_spec.js
index 6a402277f52..017e0335569 100644
--- a/spec/frontend/reports/components/grouped_test_reports_app_spec.js
+++ b/spec/frontend/reports/components/grouped_test_reports_app_spec.js
@@ -15,20 +15,29 @@ localVue.use(Vuex);
describe('Grouped test reports app', () => {
const endpoint = 'endpoint.json';
+ const pipelinePath = '/path/to/pipeline';
const Component = localVue.extend(GroupedTestReportsApp);
let wrapper;
let mockStore;
- const mountComponent = () => {
+ const mountComponent = ({
+ glFeatures = { junitPipelineView: false },
+ props = { pipelinePath },
+ } = {}) => {
wrapper = mount(Component, {
store: mockStore,
localVue,
propsData: {
endpoint,
+ pipelinePath,
+ ...props,
},
methods: {
fetchReports: () => {},
},
+ provide: {
+ glFeatures,
+ },
});
};
@@ -39,6 +48,7 @@ describe('Grouped test reports app', () => {
};
const findHeader = () => wrapper.find('[data-testid="report-section-code-text"]');
+ const findFullTestReportLink = () => wrapper.find('[data-testid="group-test-reports-full-link"]');
const findSummaryDescription = () => wrapper.find('[data-testid="test-summary-row-description"]');
const findIssueDescription = () => wrapper.find('[data-testid="test-issue-body-description"]');
const findAllIssueDescriptions = () =>
@@ -67,6 +77,39 @@ describe('Grouped test reports app', () => {
});
});
+ describe('`View full report` button', () => {
+ it('should not render the full test report link', () => {
+ expect(findFullTestReportLink().exists()).toBe(false);
+ });
+
+ describe('With junitPipelineView feature flag enabled', () => {
+ beforeEach(() => {
+ mountComponent({ glFeatures: { junitPipelineView: true } });
+ });
+
+ it('should render the full test report link', () => {
+ const fullTestReportLink = findFullTestReportLink();
+
+ expect(fullTestReportLink.exists()).toBe(true);
+ expect(pipelinePath).not.toBe('');
+ expect(fullTestReportLink.attributes('href')).toBe(`${pipelinePath}/test_report`);
+ });
+ });
+
+ describe('Without a pipelinePath', () => {
+ beforeEach(() => {
+ mountComponent({
+ glFeatures: { junitPipelineView: true },
+ props: { pipelinePath: '' },
+ });
+ });
+
+ it('should not render the full test report link', () => {
+ expect(findFullTestReportLink().exists()).toBe(false);
+ });
+ });
+ });
+
describe('with new failed result', () => {
beforeEach(() => {
setReports(newFailedTestReports);
diff --git a/spec/frontend/reports/components/report_section_spec.js b/spec/frontend/reports/components/report_section_spec.js
index eaeb074acaf..a620b5d9afc 100644
--- a/spec/frontend/reports/components/report_section_spec.js
+++ b/spec/frontend/reports/components/report_section_spec.js
@@ -1,9 +1,11 @@
import Vue from 'vue';
+import { shallowMount } from '@vue/test-utils';
import mountComponent, { mountComponentWithSlots } from 'helpers/vue_mount_component_helper';
import reportSection from '~/reports/components/report_section.vue';
describe('Report section', () => {
let vm;
+ let wrapper;
const ReportSection = Vue.extend(reportSection);
const resolvedIssues = [
@@ -16,22 +18,41 @@ describe('Report section', () => {
},
];
+ const defaultProps = {
+ component: '',
+ status: 'SUCCESS',
+ loadingText: 'Loading codeclimate report',
+ errorText: 'foo',
+ successText: 'Code quality improved on 1 point and degraded on 1 point',
+ resolvedIssues,
+ hasIssues: false,
+ alwaysOpen: false,
+ };
+
+ const createComponent = props => {
+ wrapper = shallowMount(reportSection, {
+ propsData: {
+ ...defaultProps,
+ ...props,
+ },
+ });
+ return wrapper;
+ };
+
afterEach(() => {
- vm.$destroy();
+ if (vm) {
+ vm.$destroy();
+ vm = null;
+ }
+ if (wrapper) {
+ wrapper.destroy();
+ wrapper = null;
+ }
});
describe('computed', () => {
beforeEach(() => {
- vm = mountComponent(ReportSection, {
- component: '',
- status: 'SUCCESS',
- loadingText: 'Loading codeclimate report',
- errorText: 'foo',
- successText: 'Code quality improved on 1 point and degraded on 1 point',
- resolvedIssues,
- hasIssues: false,
- alwaysOpen: false,
- });
+ vm = mountComponent(ReportSection, defaultProps);
});
describe('isCollapsible', () => {
@@ -105,12 +126,7 @@ describe('Report section', () => {
describe('with success status', () => {
beforeEach(() => {
vm = mountComponent(ReportSection, {
- component: '',
- status: 'SUCCESS',
- loadingText: 'Loading codeclimate report',
- errorText: 'foo',
- successText: 'Code quality improved on 1 point and degraded on 1 point',
- resolvedIssues,
+ ...defaultProps,
hasIssues: true,
});
});
@@ -160,6 +176,50 @@ describe('Report section', () => {
});
});
+ describe('snowplow events', () => {
+ it('does emit an event on issue toggle if the shouldEmitToggleEvent prop does exist', done => {
+ createComponent({ hasIssues: true, shouldEmitToggleEvent: true });
+
+ expect(wrapper.emitted().toggleEvent).toBeUndefined();
+
+ wrapper.vm.$el.querySelector('button').click();
+ return wrapper.vm
+ .$nextTick()
+ .then(() => {
+ expect(wrapper.emitted().toggleEvent).toHaveLength(1);
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+
+ it('does not emit an event on issue toggle if the shouldEmitToggleEvent prop does not exist', done => {
+ createComponent({ hasIssues: true });
+
+ expect(wrapper.emitted().toggleEvent).toBeUndefined();
+
+ wrapper.vm.$el.querySelector('button').click();
+ return wrapper.vm
+ .$nextTick()
+ .then(() => {
+ expect(wrapper.emitted().toggleEvent).toBeUndefined();
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+
+ it('does not emit an event if always-open is set to true', done => {
+ createComponent({ alwaysOpen: true, hasIssues: true, shouldEmitToggleEvent: true });
+
+ wrapper.vm
+ .$nextTick()
+ .then(() => {
+ expect(wrapper.emitted().toggleEvent).toBeUndefined();
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+ });
+
describe('with failed request', () => {
it('should render error indicator', () => {
vm = mountComponent(ReportSection, {
@@ -199,7 +259,7 @@ describe('Report section', () => {
});
describe('Success and Error slots', () => {
- const createComponent = status => {
+ const createComponentWithSlots = status => {
vm = mountComponentWithSlots(ReportSection, {
props: {
status,
@@ -214,7 +274,7 @@ describe('Report section', () => {
};
it('only renders success slot when status is "SUCCESS"', () => {
- createComponent('SUCCESS');
+ createComponentWithSlots('SUCCESS');
expect(vm.$el.textContent.trim()).toContain('This is a success');
expect(vm.$el.textContent.trim()).not.toContain('This is an error');
@@ -222,7 +282,7 @@ describe('Report section', () => {
});
it('only renders error slot when status is "ERROR"', () => {
- createComponent('ERROR');
+ createComponentWithSlots('ERROR');
expect(vm.$el.textContent.trim()).toContain('This is an error');
expect(vm.$el.textContent.trim()).not.toContain('This is a success');
@@ -230,7 +290,7 @@ describe('Report section', () => {
});
it('only renders loading slot when status is "LOADING"', () => {
- createComponent('LOADING');
+ createComponentWithSlots('LOADING');
expect(vm.$el.textContent.trim()).toContain('This is loading');
expect(vm.$el.textContent.trim()).not.toContain('This is an error');
diff --git a/spec/frontend/reports/components/summary_row_spec.js b/spec/frontend/reports/components/summary_row_spec.js
index cb0cc025e80..85c68ed069b 100644
--- a/spec/frontend/reports/components/summary_row_spec.js
+++ b/spec/frontend/reports/components/summary_row_spec.js
@@ -1,10 +1,8 @@
-import Vue from 'vue';
-import mountComponent from 'helpers/vue_mount_component_helper';
-import component from '~/reports/components/summary_row.vue';
+import { mount } from '@vue/test-utils';
+import SummaryRow from '~/reports/components/summary_row.vue';
describe('Summary row', () => {
- const Component = Vue.extend(component);
- let vm;
+ let wrapper;
const props = {
summary: 'SAST detected 1 new vulnerability and 1 fixed vulnerability',
@@ -15,23 +13,42 @@ describe('Summary row', () => {
statusIcon: 'warning',
};
- beforeEach(() => {
- vm = mountComponent(Component, props);
- });
+ const createComponent = ({ propsData = {}, slots = {} } = {}) => {
+ wrapper = mount(SummaryRow, {
+ propsData: {
+ ...props,
+ ...propsData,
+ },
+ slots,
+ });
+ };
+
+ const findSummary = () => wrapper.find('.report-block-list-issue-description-text');
afterEach(() => {
- vm.$destroy();
+ wrapper.destroy();
+ wrapper = null;
});
it('renders provided summary', () => {
- expect(
- vm.$el.querySelector('.report-block-list-issue-description-text').textContent.trim(),
- ).toEqual(props.summary);
+ createComponent();
+ expect(findSummary().text()).toEqual(props.summary);
});
it('renders provided icon', () => {
- expect(vm.$el.querySelector('.report-block-list-icon span').classList).toContain(
+ createComponent();
+ expect(wrapper.find('.report-block-list-icon span').classes()).toContain(
'js-ci-status-icon-warning',
);
});
+
+ describe('summary slot', () => {
+ it('replaces the summary prop', () => {
+ const summarySlotContent = 'Summary slot content';
+ createComponent({ slots: { summary: summarySlotContent } });
+
+ expect(wrapper.text()).not.toContain(props.summary);
+ expect(findSummary().text()).toEqual(summarySlotContent);
+ });
+ });
});
diff --git a/spec/frontend/repository/components/table/__snapshots__/row_spec.js.snap b/spec/frontend/repository/components/table/__snapshots__/row_spec.js.snap
index 97597ed8063..ac60fc4917d 100644
--- a/spec/frontend/repository/components/table/__snapshots__/row_spec.js.snap
+++ b/spec/frontend/repository/components/table/__snapshots__/row_spec.js.snap
@@ -1,5 +1,58 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
+exports[`Repository table row component renders a symlink table row 1`] = `
+<tr
+ class="tree-item"
+>
+ <td
+ class="tree-item-file-name cursor-default position-relative"
+ >
+ <a
+ class="tree-item-link str-truncated"
+ data-qa-selector="file_name_link"
+ href="https://test.com"
+ >
+ <file-icon-stub
+ class="mr-1 position-relative text-secondary"
+ cssclasses="position-relative file-icon"
+ filemode="120000"
+ filename="test"
+ size="16"
+ />
+ <span
+ class="position-relative"
+ >
+ test
+ </span>
+ </a>
+
+ <!---->
+
+ <!---->
+
+ <!---->
+ </td>
+
+ <td
+ class="d-none d-sm-table-cell tree-commit cursor-default"
+ >
+ <gl-skeleton-loading-stub
+ class="h-auto"
+ lines="1"
+ />
+ </td>
+
+ <td
+ class="tree-time-ago text-right cursor-default"
+ >
+ <gl-skeleton-loading-stub
+ class="ml-auto h-auto w-50"
+ lines="1"
+ />
+ </td>
+</tr>
+`;
+
exports[`Repository table row component renders table row 1`] = `
<tr
class="tree-item"
@@ -15,6 +68,7 @@ exports[`Repository table row component renders table row 1`] = `
<file-icon-stub
class="mr-1 position-relative text-secondary"
cssclasses="position-relative file-icon"
+ filemode=""
filename="test"
size="16"
/>
@@ -67,6 +121,7 @@ exports[`Repository table row component renders table row for path with special
<file-icon-stub
class="mr-1 position-relative text-secondary"
cssclasses="position-relative file-icon"
+ filemode=""
filename="test"
size="16"
/>
diff --git a/spec/frontend/repository/components/table/index_spec.js b/spec/frontend/repository/components/table/index_spec.js
index 9db90839b29..ed50f292b8c 100644
--- a/spec/frontend/repository/components/table/index_spec.js
+++ b/spec/frontend/repository/components/table/index_spec.js
@@ -23,6 +23,15 @@ const MOCK_BLOBS = [
type: 'blob',
webUrl: 'http://test.com',
},
+ {
+ id: '125abc',
+ sha: '125abc',
+ flatPath: 'blob3',
+ name: 'blob3.md',
+ type: 'blob',
+ webUrl: 'http://test.com',
+ mode: '120000',
+ },
];
function factory({ path, isLoading = false, entries = {} }) {
@@ -74,7 +83,9 @@ describe('Repository table component', () => {
},
});
- expect(vm.find(TableRow).exists()).toBe(true);
- expect(vm.findAll(TableRow).length).toBe(2);
+ const rows = vm.findAll(TableRow);
+
+ expect(rows.length).toEqual(3);
+ expect(rows.at(2).attributes().mode).toEqual('120000');
});
});
diff --git a/spec/frontend/repository/components/table/row_spec.js b/spec/frontend/repository/components/table/row_spec.js
index 800a7e586a8..767b117c798 100644
--- a/spec/frontend/repository/components/table/row_spec.js
+++ b/spec/frontend/repository/components/table/row_spec.js
@@ -2,6 +2,7 @@ import { shallowMount, RouterLinkStub } from '@vue/test-utils';
import { GlBadge, GlLink, GlIcon } from '@gitlab/ui';
import TableRow from '~/repository/components/table/row.vue';
import FileIcon from '~/vue_shared/components/file_icon.vue';
+import { FILE_SYMLINK_MODE } from '~/vue_shared/constants';
let vm;
let $router;
@@ -48,6 +49,21 @@ describe('Repository table row component', () => {
});
});
+ it('renders a symlink table row', () => {
+ factory({
+ id: '1',
+ sha: '123',
+ path: 'test',
+ type: 'blob',
+ currentPath: '/',
+ mode: FILE_SYMLINK_MODE,
+ });
+
+ return vm.vm.$nextTick().then(() => {
+ expect(vm.element).toMatchSnapshot();
+ });
+ });
+
it('renders table row for path with special character', () => {
factory({
id: '1',
diff --git a/spec/frontend/repository/components/web_ide_link_spec.js b/spec/frontend/repository/components/web_ide_link_spec.js
new file mode 100644
index 00000000000..59e1a4fd719
--- /dev/null
+++ b/spec/frontend/repository/components/web_ide_link_spec.js
@@ -0,0 +1,51 @@
+import WebIdeLink from '~/repository/components/web_ide_link.vue';
+import { mount } from '@vue/test-utils';
+
+describe('Web IDE link component', () => {
+ let wrapper;
+
+ function createComponent(props) {
+ wrapper = mount(WebIdeLink, {
+ propsData: { ...props },
+ mocks: {
+ $route: {
+ params: {},
+ },
+ },
+ });
+ }
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ it('renders link to the Web IDE for a project if only projectPath is given', () => {
+ createComponent({ projectPath: 'gitlab-org/gitlab', refSha: 'master' });
+
+ expect(wrapper.attributes('href')).toBe('/-/ide/project/gitlab-org/gitlab/edit/master/-/');
+ expect(wrapper.text()).toBe('Web IDE');
+ });
+
+ it('renders link to the Web IDE for a project even if both projectPath and forkPath are given', () => {
+ createComponent({
+ projectPath: 'gitlab-org/gitlab',
+ refSha: 'master',
+ forkPath: 'my-namespace/gitlab',
+ });
+
+ expect(wrapper.attributes('href')).toBe('/-/ide/project/gitlab-org/gitlab/edit/master/-/');
+ expect(wrapper.text()).toBe('Web IDE');
+ });
+
+ it('renders link to the forked project if it exists and cannot write to the repo', () => {
+ createComponent({
+ projectPath: 'gitlab-org/gitlab',
+ refSha: 'master',
+ forkPath: 'my-namespace/gitlab',
+ canPushCode: false,
+ });
+
+ expect(wrapper.attributes('href')).toBe('/-/ide/project/my-namespace/gitlab/edit/master/-/');
+ expect(wrapper.text()).toBe('Edit fork in Web IDE');
+ });
+});
diff --git a/spec/frontend/repository/utils/dom_spec.js b/spec/frontend/repository/utils/dom_spec.js
index 0b61161c9d0..e8b0565868e 100644
--- a/spec/frontend/repository/utils/dom_spec.js
+++ b/spec/frontend/repository/utils/dom_spec.js
@@ -1,5 +1,6 @@
import { setHTMLFixture } from '../../helpers/fixtures';
import { updateElementsVisibility, updateFormAction } from '~/repository/utils/dom';
+import { TEST_HOST } from 'helpers/test_constants';
describe('updateElementsVisibility', () => {
it('adds hidden class', () => {
@@ -31,7 +32,7 @@ describe('updateFormAction', () => {
updateFormAction('.js-test', '/gitlab/create', path);
expect(document.querySelector('.js-test').action).toBe(
- `http://localhost/gitlab/create/${path.replace(/^\//, '')}`,
+ `${TEST_HOST}/gitlab/create/${path.replace(/^\//, '')}`,
);
});
});
diff --git a/spec/frontend/search_autocomplete_spec.js b/spec/frontend/search_autocomplete_spec.js
new file mode 100644
index 00000000000..05b36474548
--- /dev/null
+++ b/spec/frontend/search_autocomplete_spec.js
@@ -0,0 +1,284 @@
+/* eslint-disable no-unused-expressions, consistent-return, no-param-reassign, default-case, no-return-assign */
+
+import $ from 'jquery';
+import '~/gl_dropdown';
+import initSearchAutocomplete from '~/search_autocomplete';
+import '~/lib/utils/common_utils';
+import axios from '~/lib/utils/axios_utils';
+import AxiosMockAdapter from 'axios-mock-adapter';
+
+describe('Search autocomplete dropdown', () => {
+ let widget = null;
+
+ const userName = 'root';
+ const userId = 1;
+ const dashboardIssuesPath = '/dashboard/issues';
+ const dashboardMRsPath = '/dashboard/merge_requests';
+ const projectIssuesPath = '/gitlab-org/gitlab-foss/issues';
+ const projectMRsPath = '/gitlab-org/gitlab-foss/-/merge_requests';
+ const groupIssuesPath = '/groups/gitlab-org/-/issues';
+ const groupMRsPath = '/groups/gitlab-org/-/merge_requests';
+ const autocompletePath = '/search/autocomplete';
+ const projectName = 'GitLab Community Edition';
+ const groupName = 'Gitlab Org';
+
+ const removeBodyAttributes = () => {
+ const $body = $('body');
+
+ $body.removeAttr('data-page');
+ $body.removeAttr('data-project');
+ $body.removeAttr('data-group');
+ };
+
+ // Add required attributes to body before starting the test.
+ // section would be dashboard|group|project
+ const addBodyAttributes = section => {
+ if (section == null) {
+ section = 'dashboard';
+ }
+
+ const $body = $('body');
+ removeBodyAttributes();
+ switch (section) {
+ case 'dashboard':
+ return $body.attr('data-page', 'root:index');
+ case 'group':
+ $body.attr('data-page', 'groups:show');
+ return $body.data('group', 'gitlab-org');
+ case 'project':
+ $body.attr('data-page', 'projects:show');
+ return $body.data('project', 'gitlab-ce');
+ }
+ };
+
+ const disableProjectIssues = () => {
+ document.querySelector('.js-search-project-options').setAttribute('data-issues-disabled', true);
+ };
+
+ // Mock `gl` object in window for dashboard specific page. App code will need it.
+ const mockDashboardOptions = () => {
+ window.gl || (window.gl = {});
+ return (window.gl.dashboardOptions = {
+ issuesPath: dashboardIssuesPath,
+ mrPath: dashboardMRsPath,
+ });
+ };
+
+ // Mock `gl` object in window for project specific page. App code will need it.
+ const mockProjectOptions = () => {
+ window.gl || (window.gl = {});
+ return (window.gl.projectOptions = {
+ 'gitlab-ce': {
+ issuesPath: projectIssuesPath,
+ mrPath: projectMRsPath,
+ projectName,
+ },
+ });
+ };
+
+ const mockGroupOptions = () => {
+ window.gl || (window.gl = {});
+ return (window.gl.groupOptions = {
+ 'gitlab-org': {
+ issuesPath: groupIssuesPath,
+ mrPath: groupMRsPath,
+ projectName: groupName,
+ },
+ });
+ };
+
+ const assertLinks = (list, issuesPath, mrsPath) => {
+ if (issuesPath) {
+ const issuesAssignedToMeLink = `a[href="${issuesPath}/?assignee_username=${userName}"]`;
+ const issuesIHaveCreatedLink = `a[href="${issuesPath}/?author_username=${userName}"]`;
+
+ expect(list.find(issuesAssignedToMeLink).length).toBe(1);
+ expect(list.find(issuesAssignedToMeLink).text()).toBe('Issues assigned to me');
+ expect(list.find(issuesIHaveCreatedLink).length).toBe(1);
+ expect(list.find(issuesIHaveCreatedLink).text()).toBe("Issues I've created");
+ }
+ const mrsAssignedToMeLink = `a[href="${mrsPath}/?assignee_username=${userName}"]`;
+ const mrsIHaveCreatedLink = `a[href="${mrsPath}/?author_username=${userName}"]`;
+
+ expect(list.find(mrsAssignedToMeLink).length).toBe(1);
+ expect(list.find(mrsAssignedToMeLink).text()).toBe('Merge requests assigned to me');
+ expect(list.find(mrsIHaveCreatedLink).length).toBe(1);
+ expect(list.find(mrsIHaveCreatedLink).text()).toBe("Merge requests I've created");
+ };
+
+ preloadFixtures('static/search_autocomplete.html');
+ beforeEach(() => {
+ loadFixtures('static/search_autocomplete.html');
+
+ window.gon = {};
+ window.gon.current_user_id = userId;
+ window.gon.current_username = userName;
+ window.gl = window.gl || (window.gl = {});
+
+ return (widget = initSearchAutocomplete({ autocompletePath }));
+ });
+
+ afterEach(() => {
+ // Undo what we did to the shared <body>
+ removeBodyAttributes();
+ window.gon = {};
+ });
+
+ it('should show Dashboard specific dropdown menu', () => {
+ addBodyAttributes();
+ mockDashboardOptions();
+ widget.searchInput.triggerHandler('focus');
+ const list = widget.wrap.find('.dropdown-menu').find('ul');
+ return assertLinks(list, dashboardIssuesPath, dashboardMRsPath);
+ });
+
+ it('should show Group specific dropdown menu', () => {
+ addBodyAttributes('group');
+ mockGroupOptions();
+ widget.searchInput.triggerHandler('focus');
+ const list = widget.wrap.find('.dropdown-menu').find('ul');
+ return assertLinks(list, groupIssuesPath, groupMRsPath);
+ });
+
+ it('should show Project specific dropdown menu', () => {
+ addBodyAttributes('project');
+ mockProjectOptions();
+ widget.searchInput.triggerHandler('focus');
+ const list = widget.wrap.find('.dropdown-menu').find('ul');
+ return assertLinks(list, projectIssuesPath, projectMRsPath);
+ });
+
+ it('should show only Project mergeRequest dropdown menu items when project issues are disabled', () => {
+ addBodyAttributes('project');
+ disableProjectIssues();
+ mockProjectOptions();
+ widget.searchInput.triggerHandler('focus');
+ const list = widget.wrap.find('.dropdown-menu').find('ul');
+ assertLinks(list, null, projectMRsPath);
+ });
+
+ it('should not show category related menu if there is text in the input', () => {
+ addBodyAttributes('project');
+ mockProjectOptions();
+ widget.searchInput.val('help');
+ widget.searchInput.triggerHandler('focus');
+ const list = widget.wrap.find('.dropdown-menu').find('ul');
+ const link = `a[href='${projectIssuesPath}/?assignee_username=${userName}']`;
+
+ expect(list.find(link).length).toBe(0);
+ });
+
+ it('should not submit the search form when selecting an autocomplete row with the keyboard', () => {
+ const ENTER = 13;
+ const DOWN = 40;
+ addBodyAttributes();
+ mockDashboardOptions(true);
+ const submitSpy = jest.spyOn(document.querySelector('form'), 'submit');
+ widget.searchInput.triggerHandler('focus');
+ widget.wrap.trigger($.Event('keydown', { which: DOWN }));
+ const enterKeyEvent = $.Event('keydown', { which: ENTER });
+ widget.searchInput.trigger(enterKeyEvent);
+
+ // This does not currently catch failing behavior. For security reasons,
+ // browsers will not trigger default behavior (form submit, in this
+ // example) on JavaScript-created keypresses.
+ expect(submitSpy).not.toHaveBeenCalled();
+ });
+
+ describe('show autocomplete results', () => {
+ beforeEach(() => {
+ widget.enableAutocomplete();
+
+ const axiosMock = new AxiosMockAdapter(axios);
+ const autocompleteUrl = new RegExp(autocompletePath);
+
+ axiosMock.onGet(autocompleteUrl).reply(200, [
+ {
+ category: 'Projects',
+ id: 1,
+ value: 'Gitlab Test',
+ label: 'Gitlab Org / Gitlab Test',
+ url: '/gitlab-org/gitlab-test',
+ avatar_url: '',
+ },
+ {
+ category: 'Groups',
+ id: 1,
+ value: 'Gitlab Org',
+ label: 'Gitlab Org',
+ url: '/gitlab-org',
+ avatar_url: '',
+ },
+ ]);
+ });
+
+ function triggerAutocomplete() {
+ return new Promise(resolve => {
+ const dropdown = widget.searchInput.data('glDropdown');
+ const filterCallback = dropdown.filter.options.callback;
+ dropdown.filter.options.callback = jest.fn(data => {
+ filterCallback(data);
+
+ resolve();
+ });
+
+ widget.searchInput.val('Gitlab');
+ widget.searchInput.triggerHandler('input');
+ });
+ }
+
+ it('suggest Projects', done => {
+ // eslint-disable-next-line promise/catch-or-return
+ triggerAutocomplete().finally(() => {
+ const list = widget.wrap.find('.dropdown-menu').find('ul');
+ const link = "a[href$='/gitlab-org/gitlab-test']";
+
+ expect(list.find(link).length).toBe(1);
+
+ done();
+ });
+
+ // Make sure jest properly acknowledge the `done` invocation
+ jest.runOnlyPendingTimers();
+ });
+
+ it('suggest Groups', done => {
+ // eslint-disable-next-line promise/catch-or-return
+ triggerAutocomplete().finally(() => {
+ const list = widget.wrap.find('.dropdown-menu').find('ul');
+ const link = "a[href$='/gitlab-org']";
+
+ expect(list.find(link).length).toBe(1);
+
+ done();
+ });
+
+ // Make sure jest properly acknowledge the `done` invocation
+ jest.runOnlyPendingTimers();
+ });
+ });
+
+ describe('disableAutocomplete', () => {
+ beforeEach(() => {
+ widget.enableAutocomplete();
+ });
+
+ it('should close the Dropdown', () => {
+ const toggleSpy = jest.spyOn(widget.dropdownToggle, 'dropdown');
+
+ widget.dropdown.addClass('show');
+ widget.disableAutocomplete();
+
+ expect(toggleSpy).toHaveBeenCalledWith('toggle');
+ });
+ });
+
+ describe('enableAutocomplete', () => {
+ it('should open the Dropdown', () => {
+ const toggleSpy = jest.spyOn(widget.dropdownToggle, 'dropdown');
+ widget.enableAutocomplete();
+
+ expect(toggleSpy).toHaveBeenCalledWith('toggle');
+ });
+ });
+});
diff --git a/spec/frontend/self_monitor/components/__snapshots__/self_monitor_form_spec.js.snap b/spec/frontend/self_monitor/components/__snapshots__/self_monitor_form_spec.js.snap
index f7a9827a9ad..f4ac2f57261 100644
--- a/spec/frontend/self_monitor/components/__snapshots__/self_monitor_form_spec.js.snap
+++ b/spec/frontend/self_monitor/components/__snapshots__/self_monitor_form_spec.js.snap
@@ -47,9 +47,7 @@ exports[`self monitor component When the self monitor project has not been creat
label-for="self-monitor-toggle"
>
<gl-toggle-stub
- labeloff="Toggle Status: OFF"
- labelon="Toggle Status: ON"
- labelposition="hidden"
+ labelposition="top"
name="self-monitor-toggle"
/>
</gl-form-group-stub>
diff --git a/spec/frontend/self_monitor/components/self_monitor_form_spec.js b/spec/frontend/self_monitor/components/self_monitor_form_spec.js
index 0e6abba08f3..aa6f71b6412 100644
--- a/spec/frontend/self_monitor/components/self_monitor_form_spec.js
+++ b/spec/frontend/self_monitor/components/self_monitor_form_spec.js
@@ -2,6 +2,7 @@ import { shallowMount } from '@vue/test-utils';
import { GlDeprecatedButton } from '@gitlab/ui';
import SelfMonitor from '~/self_monitor/components/self_monitor_form.vue';
import { createStore } from '~/self_monitor/store';
+import { TEST_HOST } from 'helpers/test_constants';
describe('self monitor component', () => {
let wrapper;
@@ -82,7 +83,7 @@ describe('self monitor component', () => {
.find({ ref: 'selfMonitoringFormText' })
.find('a')
.attributes('href'),
- ).toEqual('http://localhost/instance-administrators-random/gitlab-self-monitoring');
+ ).toEqual(`${TEST_HOST}/instance-administrators-random/gitlab-self-monitoring`);
});
});
});
diff --git a/spec/frontend/sidebar/__snapshots__/confidential_issue_sidebar_spec.js.snap b/spec/frontend/sidebar/__snapshots__/confidential_issue_sidebar_spec.js.snap
index cf7832f3948..da571af3a0d 100644
--- a/spec/frontend/sidebar/__snapshots__/confidential_issue_sidebar_spec.js.snap
+++ b/spec/frontend/sidebar/__snapshots__/confidential_issue_sidebar_spec.js.snap
@@ -3,6 +3,7 @@
exports[`Confidential Issue Sidebar Block renders for confidential = false and isEditable = false 1`] = `
<div
class="block issuable-sidebar-item confidentiality"
+ iid=""
>
<div
class="sidebar-collapsed-icon"
@@ -35,6 +36,7 @@ exports[`Confidential Issue Sidebar Block renders for confidential = false and i
<div
class="no-value sidebar-item-value"
+ data-testid="not-confidential"
>
<icon-stub
aria-hidden="true"
@@ -55,6 +57,7 @@ exports[`Confidential Issue Sidebar Block renders for confidential = false and i
exports[`Confidential Issue Sidebar Block renders for confidential = false and isEditable = true 1`] = `
<div
class="block issuable-sidebar-item confidentiality"
+ iid=""
>
<div
class="sidebar-collapsed-icon"
@@ -95,6 +98,7 @@ exports[`Confidential Issue Sidebar Block renders for confidential = false and i
<div
class="no-value sidebar-item-value"
+ data-testid="not-confidential"
>
<icon-stub
aria-hidden="true"
@@ -115,6 +119,7 @@ exports[`Confidential Issue Sidebar Block renders for confidential = false and i
exports[`Confidential Issue Sidebar Block renders for confidential = true and isEditable = false 1`] = `
<div
class="block issuable-sidebar-item confidentiality"
+ iid=""
>
<div
class="sidebar-collapsed-icon"
@@ -167,6 +172,7 @@ exports[`Confidential Issue Sidebar Block renders for confidential = true and is
exports[`Confidential Issue Sidebar Block renders for confidential = true and isEditable = true 1`] = `
<div
class="block issuable-sidebar-item confidentiality"
+ iid=""
>
<div
class="sidebar-collapsed-icon"
diff --git a/spec/frontend/sidebar/confidential/edit_form_buttons_spec.js b/spec/frontend/sidebar/confidential/edit_form_buttons_spec.js
index acdfb5139bf..15493d3087f 100644
--- a/spec/frontend/sidebar/confidential/edit_form_buttons_spec.js
+++ b/spec/frontend/sidebar/confidential/edit_form_buttons_spec.js
@@ -1,16 +1,49 @@
import { shallowMount } from '@vue/test-utils';
+import { GlLoadingIcon } from '@gitlab/ui';
import EditFormButtons from '~/sidebar/components/confidential/edit_form_buttons.vue';
+import eventHub from '~/sidebar/event_hub';
+import createStore from '~/notes/stores';
+import waitForPromises from 'helpers/wait_for_promises';
+import flash from '~/flash';
+
+jest.mock('~/sidebar/event_hub', () => ({ $emit: jest.fn() }));
+jest.mock('~/flash');
describe('Edit Form Buttons', () => {
let wrapper;
+ let store;
const findConfidentialToggle = () => wrapper.find('[data-testid="confidential-toggle"]');
- const createComponent = props => {
+ const createComponent = ({
+ props = {},
+ data = {},
+ confidentialApolloSidebar = false,
+ resolved = true,
+ }) => {
+ store = createStore();
+ if (resolved) {
+ jest.spyOn(store, 'dispatch').mockResolvedValue();
+ } else {
+ jest.spyOn(store, 'dispatch').mockRejectedValue();
+ }
+
wrapper = shallowMount(EditFormButtons, {
propsData: {
- updateConfidentialAttribute: () => {},
+ fullPath: '',
...props,
},
+ data() {
+ return {
+ isLoading: true,
+ ...data,
+ };
+ },
+ provide: {
+ glFeatures: {
+ confidentialApolloSidebar,
+ },
+ },
+ store,
});
};
@@ -19,10 +52,32 @@ describe('Edit Form Buttons', () => {
wrapper = null;
});
+ describe('when isLoading', () => {
+ beforeEach(() => {
+ createComponent({});
+
+ wrapper.vm.$store.state.noteableData.confidential = false;
+ });
+
+ it('renders "Applying" in the toggle button', () => {
+ expect(findConfidentialToggle().text()).toBe('Applying');
+ });
+
+ it('disables the toggle button', () => {
+ expect(findConfidentialToggle().attributes('disabled')).toBe('disabled');
+ });
+
+ it('finds the GlLoadingIcon', () => {
+ expect(wrapper.find(GlLoadingIcon).exists()).toBe(true);
+ });
+ });
+
describe('when not confidential', () => {
- it('renders Turn On in the ', () => {
+ it('renders Turn On in the toggle button', () => {
createComponent({
- isConfidential: false,
+ data: {
+ isLoading: false,
+ },
});
expect(findConfidentialToggle().text()).toBe('Turn On');
@@ -30,12 +85,75 @@ describe('Edit Form Buttons', () => {
});
describe('when confidential', () => {
- it('renders on or off text based on confidentiality', () => {
+ beforeEach(() => {
createComponent({
- isConfidential: true,
+ data: {
+ isLoading: false,
+ },
});
+ wrapper.vm.$store.state.noteableData.confidential = true;
+ });
+
+ it('renders on or off text based on confidentiality', () => {
expect(findConfidentialToggle().text()).toBe('Turn Off');
});
+
+ describe('when clicking on the confidential toggle', () => {
+ it('emits updateConfidentialAttribute', () => {
+ findConfidentialToggle().trigger('click');
+
+ expect(eventHub.$emit).toHaveBeenCalledWith('updateConfidentialAttribute');
+ });
+ });
+ });
+
+ describe('when confidentialApolloSidebar is turned on', () => {
+ const isConfidential = true;
+
+ describe('when succeeds', () => {
+ beforeEach(() => {
+ createComponent({ data: { isLoading: false }, confidentialApolloSidebar: true });
+ wrapper.vm.$store.state.noteableData.confidential = isConfidential;
+ findConfidentialToggle().trigger('click');
+ });
+
+ it('dispatches the correct action', () => {
+ expect(store.dispatch).toHaveBeenCalledWith('updateConfidentialityOnIssue', {
+ confidential: !isConfidential,
+ fullPath: '',
+ });
+ });
+
+ it('resets loading', () => {
+ return waitForPromises().then(() => {
+ expect(wrapper.find(GlLoadingIcon).exists()).toBe(false);
+ });
+ });
+
+ it('emits close form', () => {
+ return waitForPromises().then(() => {
+ expect(eventHub.$emit).toHaveBeenCalledWith('closeConfidentialityForm');
+ });
+ });
+ });
+
+ describe('when fails', () => {
+ beforeEach(() => {
+ createComponent({
+ data: { isLoading: false },
+ confidentialApolloSidebar: true,
+ resolved: false,
+ });
+ wrapper.vm.$store.state.noteableData.confidential = isConfidential;
+ findConfidentialToggle().trigger('click');
+ });
+
+ it('calls flash with the correct message', () => {
+ expect(flash).toHaveBeenCalledWith(
+ 'Something went wrong trying to change the confidentiality of this issue',
+ );
+ });
+ });
});
});
diff --git a/spec/frontend/sidebar/confidential/edit_form_spec.js b/spec/frontend/sidebar/confidential/edit_form_spec.js
index 137019a1e1b..a22bbe5ae0d 100644
--- a/spec/frontend/sidebar/confidential/edit_form_spec.js
+++ b/spec/frontend/sidebar/confidential/edit_form_spec.js
@@ -10,6 +10,8 @@ describe('Edit Form Dropdown', () => {
wrapper = shallowMount(EditForm, {
propsData: {
...props,
+ isLoading: false,
+ fullPath: '',
},
});
};
diff --git a/spec/frontend/sidebar/confidential_issue_sidebar_spec.js b/spec/frontend/sidebar/confidential_issue_sidebar_spec.js
index fe7c3aadeeb..06cf1e6166c 100644
--- a/spec/frontend/sidebar/confidential_issue_sidebar_spec.js
+++ b/spec/frontend/sidebar/confidential_issue_sidebar_spec.js
@@ -7,6 +7,7 @@ import createFlash from '~/flash';
import RecaptchaModal from '~/vue_shared/components/recaptcha_modal.vue';
import createStore from '~/notes/stores';
import { useMockLocationHelper } from 'helpers/mock_window_location_helper';
+import eventHub from '~/sidebar/event_hub';
jest.mock('~/flash');
jest.mock('~/sidebar/services/sidebar_service');
@@ -15,6 +16,9 @@ describe('Confidential Issue Sidebar Block', () => {
useMockLocationHelper();
let wrapper;
+ const mutate = jest
+ .fn()
+ .mockResolvedValue({ data: { issueSetConfidential: { issue: { confidential: true } } } });
const findRecaptchaModal = () => wrapper.find(RecaptchaModal);
@@ -25,24 +29,32 @@ describe('Confidential Issue Sidebar Block', () => {
wrapper.vm
.$nextTick()
.then(() => {
- const editForm = wrapper.find(EditForm);
- const { updateConfidentialAttribute } = editForm.props();
- updateConfidentialAttribute();
+ eventHub.$emit('updateConfidentialAttribute');
})
// wait for reCAPTCHA modal to render
.then(() => wrapper.vm.$nextTick())
);
};
- const createComponent = propsData => {
+ const createComponent = ({ propsData, data = {} }) => {
const store = createStore();
const service = new SidebarService();
wrapper = shallowMount(ConfidentialIssueSidebar, {
store,
+ data() {
+ return data;
+ },
propsData: {
service,
+ iid: '',
+ fullPath: '',
...propsData,
},
+ mocks: {
+ $apollo: {
+ mutate,
+ },
+ },
});
};
@@ -60,7 +72,9 @@ describe('Confidential Issue Sidebar Block', () => {
'renders for confidential = $confidential and isEditable = $isEditable',
({ confidential, isEditable }) => {
createComponent({
- isEditable,
+ propsData: {
+ isEditable,
+ },
});
wrapper.vm.$store.state.noteableData.confidential = confidential;
@@ -73,7 +87,9 @@ describe('Confidential Issue Sidebar Block', () => {
describe('if editable', () => {
beforeEach(() => {
createComponent({
- isEditable: true,
+ propsData: {
+ isEditable: true,
+ },
});
wrapper.vm.$store.state.noteableData.confidential = true;
});
diff --git a/spec/frontend/snippets/components/edit_spec.js b/spec/frontend/snippets/components/edit_spec.js
index 83f46dd347f..d2265dfd506 100644
--- a/spec/frontend/snippets/components/edit_spec.js
+++ b/spec/frontend/snippets/components/edit_spec.js
@@ -1,9 +1,8 @@
import { shallowMount } from '@vue/test-utils';
-import axios from '~/lib/utils/axios_utils';
import Flash from '~/flash';
import { GlLoadingIcon } from '@gitlab/ui';
-import { joinPaths, redirectTo } from '~/lib/utils/url_utility';
+import { redirectTo } from '~/lib/utils/url_utility';
import SnippetEditApp from '~/snippets/components/edit.vue';
import SnippetDescriptionEdit from '~/snippets/components/snippet_description_edit.vue';
@@ -16,25 +15,17 @@ import { SNIPPET_CREATE_MUTATION_ERROR, SNIPPET_UPDATE_MUTATION_ERROR } from '~/
import UpdateSnippetMutation from '~/snippets/mutations/updateSnippet.mutation.graphql';
import CreateSnippetMutation from '~/snippets/mutations/createSnippet.mutation.graphql';
-import AxiosMockAdapter from 'axios-mock-adapter';
import waitForPromises from 'helpers/wait_for_promises';
import { ApolloMutation } from 'vue-apollo';
jest.mock('~/lib/utils/url_utility', () => ({
- getBaseURL: jest.fn().mockReturnValue('foo/'),
redirectTo: jest.fn().mockName('redirectTo'),
- joinPaths: jest
- .fn()
- .mockName('joinPaths')
- .mockReturnValue('contentApiURL'),
}));
jest.mock('~/flash');
let flashSpy;
-const contentMock = 'Foo Bar';
-const rawPathMock = '/foo/bar';
const rawProjectPathMock = '/project/path';
const newlyEditedSnippetUrl = 'http://foo.bar';
const apiError = { message: 'Ufff' };
@@ -43,15 +34,27 @@ const mutationError = 'Bummer';
const attachedFilePath1 = 'foo/bar';
const attachedFilePath2 = 'alpha/beta';
+const actionWithContent = {
+ content: 'Foo Bar',
+};
+const actionWithoutContent = {
+ content: '',
+};
+
const defaultProps = {
snippetGid: 'gid://gitlab/PersonalSnippet/42',
markdownPreviewPath: 'http://preview.foo.bar',
markdownDocsPath: 'http://docs.foo.bar',
};
+const defaultData = {
+ blobsActions: {
+ ...actionWithContent,
+ action: '',
+ },
+};
describe('Snippet Edit app', () => {
let wrapper;
- let axiosMock;
const resolveMutate = jest.fn().mockResolvedValue({
data: {
@@ -156,18 +159,21 @@ describe('Snippet Edit app', () => {
});
it.each`
- title | content | expectation
- ${''} | ${''} | ${true}
- ${'foo'} | ${''} | ${true}
- ${''} | ${'foo'} | ${true}
- ${'foo'} | ${'bar'} | ${false}
+ title | blobsActions | expectation
+ ${''} | ${{}} | ${true}
+ ${''} | ${{ actionWithContent }} | ${true}
+ ${''} | ${{ actionWithoutContent }} | ${true}
+ ${'foo'} | ${{}} | ${true}
+ ${'foo'} | ${{ actionWithoutContent }} | ${true}
+ ${'foo'} | ${{ actionWithoutContent, actionWithContent }} | ${true}
+ ${'foo'} | ${{ actionWithContent }} | ${false}
`(
- 'disables submit button unless both title and content are present',
- ({ title, content, expectation }) => {
+ 'disables submit button unless both title and content for all blobs are present',
+ ({ title, blobsActions, expectation }) => {
createComponent({
data: {
snippet: { title },
- content,
+ blobsActions,
},
});
const isBtnDisabled = Boolean(findSubmitButton().attributes('disabled'));
@@ -192,83 +198,31 @@ describe('Snippet Edit app', () => {
});
describe('functionality', () => {
- describe('handling of the data from GraphQL response', () => {
- const snippet = {
- blob: {
- rawPath: rawPathMock,
- },
- };
- const getResSchema = newSnippet => {
- return {
- data: {
- snippets: {
- edges: newSnippet ? [] : [snippet],
- },
- },
+ describe('form submission handling', () => {
+ it('does not submit unchanged blobs', () => {
+ const foo = {
+ action: '',
+ };
+ const bar = {
+ action: 'update',
};
- };
-
- const bootstrapForExistingSnippet = resp => {
createComponent({
data: {
- snippet,
+ blobsActions: {
+ foo,
+ bar,
+ },
},
});
-
- if (resp === 500) {
- axiosMock.onGet('contentApiURL').reply(500);
- } else {
- axiosMock.onGet('contentApiURL').reply(200, contentMock);
- }
- wrapper.vm.onSnippetFetch(getResSchema());
- };
-
- const bootstrapForNewSnippet = () => {
- createComponent();
- wrapper.vm.onSnippetFetch(getResSchema(true));
- };
-
- beforeEach(() => {
- axiosMock = new AxiosMockAdapter(axios);
- });
-
- afterEach(() => {
- axiosMock.restore();
- });
-
- it('fetches blob content with the additional query', () => {
- bootstrapForExistingSnippet();
-
- return waitForPromises().then(() => {
- expect(joinPaths).toHaveBeenCalledWith('foo/', rawPathMock);
- expect(wrapper.vm.newSnippet).toBe(false);
- expect(wrapper.vm.content).toBe(contentMock);
- });
- });
-
- it('flashes the error message if fetching content fails', () => {
- bootstrapForExistingSnippet(500);
-
- return waitForPromises().then(() => {
- expect(flashSpy).toHaveBeenCalled();
- expect(wrapper.vm.content).toBe('');
- });
- });
-
- it('does not fetch content for new snippet', () => {
- bootstrapForNewSnippet();
+ clickSubmitBtn();
return waitForPromises().then(() => {
- // we keep using waitForPromises to make sure we do not run failed test
- expect(wrapper.vm.newSnippet).toBe(true);
- expect(wrapper.vm.content).toBe('');
- expect(joinPaths).not.toHaveBeenCalled();
- expect(wrapper.vm.snippet).toEqual(wrapper.vm.$options.newSnippetSchema);
+ expect(resolveMutate).toHaveBeenCalledWith(
+ expect.objectContaining({ variables: { input: { files: [bar] } } }),
+ );
});
});
- });
- describe('form submission handling', () => {
it.each`
newSnippet | projectPath | mutation | mutationName
${true} | ${rawProjectPathMock} | ${CreateSnippetMutation} | ${'CreateSnippetMutation with projectPath'}
@@ -279,6 +233,7 @@ describe('Snippet Edit app', () => {
createComponent({
data: {
newSnippet,
+ ...defaultData,
},
props: {
...defaultProps,
@@ -419,5 +374,57 @@ describe('Snippet Edit app', () => {
expect(resolveMutate).toHaveBeenCalledWith(updateMutationPayload());
});
});
+
+ describe('on before unload', () => {
+ let event;
+ let returnValueSetter;
+
+ const bootstrap = data => {
+ createComponent({
+ data,
+ });
+
+ event = new Event('beforeunload');
+ returnValueSetter = jest.spyOn(event, 'returnValue', 'set');
+ };
+
+ it('does not prevent page navigation if there are no blobs', () => {
+ bootstrap();
+ window.dispatchEvent(event);
+
+ expect(returnValueSetter).not.toHaveBeenCalled();
+ });
+
+ it('does not prevent page navigation if there are no changes to the blobs content', () => {
+ bootstrap({
+ blobsActions: {
+ foo: {
+ ...actionWithContent,
+ action: '',
+ },
+ },
+ });
+ window.dispatchEvent(event);
+
+ expect(returnValueSetter).not.toHaveBeenCalled();
+ });
+
+ it('prevents page navigation if there are some changes in the snippet content', () => {
+ bootstrap({
+ blobsActions: {
+ foo: {
+ ...actionWithContent,
+ action: 'update',
+ },
+ },
+ });
+
+ window.dispatchEvent(event);
+
+ expect(returnValueSetter).toHaveBeenCalledWith(
+ 'Are you sure you want to lose unsaved changes?',
+ );
+ });
+ });
});
});
diff --git a/spec/frontend/snippets/components/show_spec.js b/spec/frontend/snippets/components/show_spec.js
index 33608df8cf2..b5446e70028 100644
--- a/spec/frontend/snippets/components/show_spec.js
+++ b/spec/frontend/snippets/components/show_spec.js
@@ -1,10 +1,13 @@
import SnippetApp from '~/snippets/components/show.vue';
+import BlobEmbeddable from '~/blob/components/blob_embeddable.vue';
import SnippetHeader from '~/snippets/components/snippet_header.vue';
import SnippetTitle from '~/snippets/components/snippet_title.vue';
import SnippetBlob from '~/snippets/components/snippet_blob_view.vue';
import { GlLoadingIcon } from '@gitlab/ui';
+import { Blob, BinaryBlob } from 'jest/blob/components/mock_data';
import { shallowMount } from '@vue/test-utils';
+import { SNIPPET_VISIBILITY_PUBLIC } from '~/snippets/constants';
describe('Snippet view app', () => {
let wrapper;
@@ -12,7 +15,7 @@ describe('Snippet view app', () => {
snippetGid: 'gid://gitlab/PersonalSnippet/42',
};
- function createComponent({ props = defaultProps, loading = false } = {}) {
+ function createComponent({ props = defaultProps, data = {}, loading = false } = {}) {
const $apollo = {
queries: {
snippet: {
@@ -26,6 +29,9 @@ describe('Snippet view app', () => {
propsData: {
...props,
},
+ data() {
+ return data;
+ },
});
}
afterEach(() => {
@@ -37,10 +43,33 @@ describe('Snippet view app', () => {
expect(wrapper.find(GlLoadingIcon).exists()).toBe(true);
});
- it('renders all components after the query is finished', () => {
+ it('renders all simple components after the query is finished', () => {
createComponent();
expect(wrapper.find(SnippetHeader).exists()).toBe(true);
expect(wrapper.find(SnippetTitle).exists()).toBe(true);
- expect(wrapper.find(SnippetBlob).exists()).toBe(true);
+ });
+
+ it('renders embeddable component if visibility allows', () => {
+ createComponent({
+ data: {
+ snippet: {
+ visibilityLevel: SNIPPET_VISIBILITY_PUBLIC,
+ webUrl: 'http://foo.bar',
+ },
+ },
+ });
+ expect(wrapper.contains(BlobEmbeddable)).toBe(true);
+ });
+
+ it('renders correct snippet-blob components', () => {
+ createComponent({
+ data: {
+ blobs: [Blob, BinaryBlob],
+ },
+ });
+ const blobs = wrapper.findAll(SnippetBlob);
+ expect(blobs.length).toBe(2);
+ expect(blobs.at(0).props('blob')).toEqual(Blob);
+ expect(blobs.at(1).props('blob')).toEqual(BinaryBlob);
});
});
diff --git a/spec/frontend/snippets/components/snippet_blob_edit_spec.js b/spec/frontend/snippets/components/snippet_blob_edit_spec.js
index 75688e61892..009074b4558 100644
--- a/spec/frontend/snippets/components/snippet_blob_edit_spec.js
+++ b/spec/frontend/snippets/components/snippet_blob_edit_spec.js
@@ -4,78 +4,161 @@ import BlobContentEdit from '~/blob/components/blob_edit_content.vue';
import { GlLoadingIcon } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import { nextTick } from 'vue';
+import AxiosMockAdapter from 'axios-mock-adapter';
+import axios from '~/lib/utils/axios_utils';
+import { joinPaths } from '~/lib/utils/url_utility';
+import waitForPromises from 'helpers/wait_for_promises';
jest.mock('~/blob/utils', () => jest.fn());
+jest.mock('~/lib/utils/url_utility', () => ({
+ getBaseURL: jest.fn().mockReturnValue('foo/'),
+ joinPaths: jest
+ .fn()
+ .mockName('joinPaths')
+ .mockReturnValue('contentApiURL'),
+}));
+
+jest.mock('~/flash');
+
+let flashSpy;
+
describe('Snippet Blob Edit component', () => {
let wrapper;
- const value = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.';
- const fileName = 'lorem.txt';
- const findHeader = () => wrapper.find(BlobHeaderEdit);
- const findContent = () => wrapper.find(BlobContentEdit);
+ let axiosMock;
+ const contentMock = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.';
+ const pathMock = 'lorem.txt';
+ const rawPathMock = 'foo/bar';
+ const blob = {
+ path: pathMock,
+ content: contentMock,
+ rawPath: rawPathMock,
+ };
+ const findComponent = component => wrapper.find(component);
- function createComponent(props = {}) {
+ function createComponent(props = {}, data = { isContentLoading: false }) {
wrapper = shallowMount(SnippetBlobEdit, {
propsData: {
- value,
- fileName,
- isLoading: false,
...props,
},
+ data() {
+ return {
+ ...data,
+ };
+ },
});
+ flashSpy = jest.spyOn(wrapper.vm, 'flashAPIFailure');
}
beforeEach(() => {
+ axiosMock = new AxiosMockAdapter(axios);
createComponent();
});
afterEach(() => {
+ axiosMock.restore();
wrapper.destroy();
});
describe('rendering', () => {
it('matches the snapshot', () => {
+ createComponent({ blob });
expect(wrapper.element).toMatchSnapshot();
});
it('renders required components', () => {
- expect(findHeader().exists()).toBe(true);
- expect(findContent().exists()).toBe(true);
+ expect(findComponent(BlobHeaderEdit).exists()).toBe(true);
+ expect(findComponent(BlobContentEdit).exists()).toBe(true);
});
- it('renders loader if isLoading equals true', () => {
- createComponent({ isLoading: true });
+ it('renders loader if existing blob is supplied but no content is fetched yet', () => {
+ createComponent({ blob }, { isContentLoading: true });
expect(wrapper.contains(GlLoadingIcon)).toBe(true);
- expect(findContent().exists()).toBe(false);
+ expect(findComponent(BlobContentEdit).exists()).toBe(false);
+ });
+
+ it('does not render loader if when blob is not supplied', () => {
+ createComponent();
+ expect(wrapper.contains(GlLoadingIcon)).toBe(false);
+ expect(findComponent(BlobContentEdit).exists()).toBe(true);
});
});
describe('functionality', () => {
- it('does not fail without content', () => {
+ it('does not fail without blob', () => {
const spy = jest.spyOn(global.console, 'error');
- createComponent({ value: undefined });
+ createComponent({ blob: undefined });
expect(spy).not.toHaveBeenCalled();
- expect(findContent().exists()).toBe(true);
+ expect(findComponent(BlobContentEdit).exists()).toBe(true);
});
- it('emits "name-change" event when the file name gets changed', () => {
- expect(wrapper.emitted('name-change')).toBeUndefined();
- const newFilename = 'foo.bar';
- findHeader().vm.$emit('input', newFilename);
+ it.each`
+ emitter | prop
+ ${BlobHeaderEdit} | ${'filePath'}
+ ${BlobContentEdit} | ${'content'}
+ `('emits "blob-updated" event when the $prop gets changed', ({ emitter, prop }) => {
+ expect(wrapper.emitted('blob-updated')).toBeUndefined();
+ const newValue = 'foo.bar';
+ findComponent(emitter).vm.$emit('input', newValue);
return nextTick().then(() => {
- expect(wrapper.emitted('name-change')[0]).toEqual([newFilename]);
+ expect(wrapper.emitted('blob-updated')[0]).toEqual([
+ expect.objectContaining({
+ [prop]: newValue,
+ }),
+ ]);
});
});
- it('emits "input" event when the file content gets changed', () => {
- expect(wrapper.emitted('input')).toBeUndefined();
- const newValue = 'foo.bar';
- findContent().vm.$emit('input', newValue);
+ describe('fetching blob content', () => {
+ const bootstrapForExistingSnippet = resp => {
+ createComponent({
+ blob: {
+ ...blob,
+ content: '',
+ },
+ });
- return nextTick().then(() => {
- expect(wrapper.emitted('input')[0]).toEqual([newValue]);
+ if (resp === 500) {
+ axiosMock.onGet('contentApiURL').reply(500);
+ } else {
+ axiosMock.onGet('contentApiURL').reply(200, contentMock);
+ }
+ };
+
+ const bootstrapForNewSnippet = () => {
+ createComponent();
+ };
+
+ it('fetches blob content with the additional query', () => {
+ bootstrapForExistingSnippet();
+
+ return waitForPromises().then(() => {
+ expect(joinPaths).toHaveBeenCalledWith('foo/', rawPathMock);
+ expect(findComponent(BlobHeaderEdit).props('value')).toBe(pathMock);
+ expect(findComponent(BlobContentEdit).props('value')).toBe(contentMock);
+ });
+ });
+
+ it('flashes the error message if fetching content fails', () => {
+ bootstrapForExistingSnippet(500);
+
+ return waitForPromises().then(() => {
+ expect(flashSpy).toHaveBeenCalled();
+ expect(findComponent(BlobContentEdit).props('value')).toBe('');
+ });
+ });
+
+ it('does not fetch content for new snippet', () => {
+ bootstrapForNewSnippet();
+
+ return waitForPromises().then(() => {
+ // we keep using waitForPromises to make sure we do not run failed test
+ expect(findComponent(BlobHeaderEdit).props('value')).toBe('');
+ expect(findComponent(BlobContentEdit).props('value')).toBe('');
+ expect(joinPaths).not.toHaveBeenCalled();
+ });
});
});
});
diff --git a/spec/frontend/snippets/components/snippet_blob_view_spec.js b/spec/frontend/snippets/components/snippet_blob_view_spec.js
index e4d8ee9b7df..c8f1c8fc8a9 100644
--- a/spec/frontend/snippets/components/snippet_blob_view_spec.js
+++ b/spec/frontend/snippets/components/snippet_blob_view_spec.js
@@ -23,13 +23,17 @@ describe('Blob Embeddable', () => {
id: 'gid://foo.bar/snippet',
webUrl: 'https://foo.bar',
visibilityLevel: SNIPPET_VISIBILITY_PUBLIC,
- blob: BlobMock,
};
const dataMock = {
activeViewerType: SimpleViewerMock.type,
};
- function createComponent(props = {}, data = dataMock, contentLoading = false) {
+ function createComponent({
+ snippetProps = {},
+ data = dataMock,
+ blob = BlobMock,
+ contentLoading = false,
+ } = {}) {
const $apollo = {
queries: {
blobContent: {
@@ -44,8 +48,9 @@ describe('Blob Embeddable', () => {
propsData: {
snippet: {
...snippet,
- ...props,
+ ...snippetProps,
},
+ blob,
},
data() {
return {
@@ -63,7 +68,6 @@ describe('Blob Embeddable', () => {
describe('rendering', () => {
it('renders correct components', () => {
createComponent();
- expect(wrapper.find(BlobEmbeddable).exists()).toBe(true);
expect(wrapper.find(BlobHeader).exists()).toBe(true);
expect(wrapper.find(BlobContent).exists()).toBe(true);
});
@@ -72,19 +76,14 @@ describe('Blob Embeddable', () => {
'does not render blob-embeddable by default',
visibilityLevel => {
createComponent({
- visibilityLevel,
+ snippetProps: {
+ visibilityLevel,
+ },
});
expect(wrapper.find(BlobEmbeddable).exists()).toBe(false);
},
);
- it('does render blob-embeddable for public snippet', () => {
- createComponent({
- visibilityLevel: SNIPPET_VISIBILITY_PUBLIC,
- });
- expect(wrapper.find(BlobEmbeddable).exists()).toBe(true);
- });
-
it('sets simple viewer correctly', () => {
createComponent();
expect(wrapper.find(SimpleViewer).exists()).toBe(true);
@@ -92,7 +91,9 @@ describe('Blob Embeddable', () => {
it('sets rich viewer correctly', () => {
const data = { ...dataMock, activeViewerType: RichViewerMock.type };
- createComponent({}, data);
+ createComponent({
+ data,
+ });
expect(wrapper.find(RichViewer).exists()).toBe(true);
});
@@ -137,7 +138,9 @@ describe('Blob Embeddable', () => {
});
it('renders simple viewer by default if URL contains hash', () => {
- createComponent({}, {});
+ createComponent({
+ data: {},
+ });
expect(wrapper.vm.activeViewerType).toBe(SimpleViewerMock.type);
expect(wrapper.find(SimpleViewer).exists()).toBe(true);
@@ -183,12 +186,11 @@ describe('Blob Embeddable', () => {
});
it(`sets '${SimpleViewerMock.type}' as active on ${BLOB_RENDER_EVENT_SHOW_SOURCE} event`, () => {
- createComponent(
- {},
- {
+ createComponent({
+ data: {
activeViewerType: RichViewerMock.type,
},
- );
+ });
findContentEl().vm.$emit(BLOB_RENDER_EVENT_SHOW_SOURCE);
expect(wrapper.vm.activeViewerType).toEqual(SimpleViewerMock.type);
diff --git a/spec/frontend/snippets/components/snippet_header_spec.js b/spec/frontend/snippets/components/snippet_header_spec.js
index 5230910b6f5..0825da92118 100644
--- a/spec/frontend/snippets/components/snippet_header_spec.js
+++ b/spec/frontend/snippets/components/snippet_header_spec.js
@@ -3,6 +3,7 @@ import DeleteSnippetMutation from '~/snippets/mutations/deleteSnippet.mutation.g
import { ApolloMutation } from 'vue-apollo';
import { GlButton, GlModal } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
+import { Blob, BinaryBlob } from 'jest/blob/components/mock_data';
describe('Snippet header component', () => {
let wrapper;
@@ -20,9 +21,7 @@ describe('Snippet header component', () => {
author: {
name: 'Thor Odinson',
},
- blob: {
- binary: false,
- },
+ blobs: [Blob],
};
const mutationVariables = {
mutation: DeleteSnippetMutation,
@@ -49,7 +48,6 @@ describe('Snippet header component', () => {
mutationRes = mutationTypes.RESOLVE,
snippetProps = {},
} = {}) {
- // const defaultProps = Object.assign({}, snippet, snippetProps);
const defaultProps = Object.assign(snippet, snippetProps);
if (permissions) {
Object.assign(defaultProps.userPermissions, {
@@ -131,15 +129,18 @@ describe('Snippet header component', () => {
expect(wrapper.find(GlModal).exists()).toBe(true);
});
- it('renders Edit button as disabled for binary snippets', () => {
+ it.each`
+ blobs | isDisabled | condition
+ ${[Blob]} | ${false} | ${'no binary'}
+ ${[Blob, BinaryBlob]} | ${true} | ${'several blobs. incl. a binary'}
+ ${[BinaryBlob]} | ${true} | ${'binary'}
+ `('renders Edit button when snippet contains $condition file', ({ blobs, isDisabled }) => {
createComponent({
snippetProps: {
- blob: {
- binary: true,
- },
+ blobs,
},
});
- expect(wrapper.find('[href*="edit"]').props('disabled')).toBe(true);
+ expect(wrapper.find('[href*="edit"]').props('disabled')).toBe(isDisabled);
});
describe('Delete mutation', () => {
diff --git a/spec/frontend/static_site_editor/components/edit_area_spec.js b/spec/frontend/static_site_editor/components/edit_area_spec.js
index d7c798e6620..11c5abf1b08 100644
--- a/spec/frontend/static_site_editor/components/edit_area_spec.js
+++ b/spec/frontend/static_site_editor/components/edit_area_spec.js
@@ -1,6 +1,7 @@
import { shallowMount } from '@vue/test-utils';
import RichContentEditor from '~/vue_shared/components/rich_content_editor/rich_content_editor.vue';
+import { EDITOR_TYPES } from '~/vue_shared/components/rich_content_editor/constants';
import EditArea from '~/static_site_editor/components/edit_area.vue';
import PublishToolbar from '~/static_site_editor/components/publish_toolbar.vue';
@@ -51,7 +52,7 @@ describe('~/static_site_editor/components/edit_area.vue', () => {
it('renders rich content editor', () => {
expect(findRichContentEditor().exists()).toBe(true);
- expect(findRichContentEditor().props('value')).toBe(body);
+ expect(findRichContentEditor().props('content')).toBe(body);
});
it('renders publish toolbar', () => {
@@ -75,6 +76,15 @@ describe('~/static_site_editor/components/edit_area.vue', () => {
return wrapper.vm.$nextTick();
});
+ it('updates parsedSource with new content', () => {
+ const newContent = 'New content';
+ const spySyncParsedSource = jest.spyOn(wrapper.vm.parsedSource, 'sync');
+
+ findRichContentEditor().vm.$emit('input', newContent);
+
+ expect(spySyncParsedSource).toHaveBeenCalledWith(newContent, true);
+ });
+
it('sets publish toolbar as saveable', () => {
expect(findPublishToolbar().props('saveable')).toBe(true);
});
@@ -91,4 +101,33 @@ describe('~/static_site_editor/components/edit_area.vue', () => {
});
});
});
+
+ describe('when the mode changes', () => {
+ const setInitialMode = mode => {
+ wrapper.setData({ editorMode: mode });
+ };
+
+ afterEach(() => {
+ setInitialMode(EDITOR_TYPES.wysiwyg);
+ });
+
+ it.each`
+ initialMode | targetMode | resetValue
+ ${EDITOR_TYPES.wysiwyg} | ${EDITOR_TYPES.markdown} | ${content}
+ ${EDITOR_TYPES.markdown} | ${EDITOR_TYPES.wysiwyg} | ${body}
+ `(
+ 'sets editorMode from $initialMode to $targetMode',
+ ({ initialMode, targetMode, resetValue }) => {
+ setInitialMode(initialMode);
+
+ const resetInitialValue = jest.fn();
+
+ findRichContentEditor().setMethods({ resetInitialValue });
+ findRichContentEditor().vm.$emit('modeChange', targetMode);
+
+ expect(resetInitialValue).toHaveBeenCalledWith(resetValue);
+ expect(wrapper.vm.editorMode).toBe(targetMode);
+ },
+ );
+ });
});
diff --git a/spec/frontend/static_site_editor/mock_data.js b/spec/frontend/static_site_editor/mock_data.js
index 422048a5f69..96de9b73af0 100644
--- a/spec/frontend/static_site_editor/mock_data.js
+++ b/spec/frontend/static_site_editor/mock_data.js
@@ -10,6 +10,8 @@ export const sourceContentBody = `## On this page
- TOC
{:toc .hidden-md .hidden-lg}
+
+![image](path/to/image1.png)
`;
export const sourceContent = `${sourceContentHeader}${sourceContentSpacing}${sourceContentBody}`;
export const sourceContentTitle = 'Handbook';
@@ -48,3 +50,8 @@ export const createMergeRequestResponse = {
};
export const trackingCategory = 'projects:static_site_editor:show';
+
+export const images = new Map([
+ ['path/to/image1.png', 'image1-content'],
+ ['path/to/image2.png', 'image2-content'],
+]);
diff --git a/spec/frontend/static_site_editor/services/parse_source_file_spec.js b/spec/frontend/static_site_editor/services/parse_source_file_spec.js
index fe99c4f5334..4588548e614 100644
--- a/spec/frontend/static_site_editor/services/parse_source_file_spec.js
+++ b/spec/frontend/static_site_editor/services/parse_source_file_spec.js
@@ -1,64 +1,58 @@
-import {
- sourceContent as content,
- sourceContentHeader as header,
- sourceContentSpacing as spacing,
- sourceContentBody as body,
-} from '../mock_data';
+import { sourceContent as content, sourceContentBody as body } from '../mock_data';
import parseSourceFile from '~/static_site_editor/services/parse_source_file';
describe('parseSourceFile', () => {
- const contentSimple = content;
const contentComplex = [content, content, content].join('');
+ const complexBody = [body, content, content].join('');
+ const edit = 'and more';
+ const newContent = `${content} ${edit}`;
+ const newContentComplex = `${contentComplex} ${edit}`;
- describe('the editable shape and its expected values', () => {
+ describe('unmodified content', () => {
it.each`
- sourceContent | sourceHeader | sourceSpacing | sourceBody | desc
- ${contentSimple} | ${header} | ${spacing} | ${body} | ${'extracts header'}
- ${contentComplex} | ${header} | ${spacing} | ${[body, content, content].join('')} | ${'extracts body'}
- `('$desc', ({ sourceContent, sourceHeader, sourceSpacing, sourceBody }) => {
- const { editable } = parseSourceFile(sourceContent);
-
- expect(editable).toMatchObject({
- raw: sourceContent,
- header: sourceHeader,
- spacing: sourceSpacing,
- body: sourceBody,
- });
+ parsedSource
+ ${parseSourceFile(content)}
+ ${parseSourceFile(contentComplex)}
+ `('returns false by default', ({ parsedSource }) => {
+ expect(parsedSource.isModified()).toBe(false);
});
- it('returns the same front matter regardless of front matter duplication', () => {
- const parsedSourceSimple = parseSourceFile(contentSimple);
- const parsedSourceComplex = parseSourceFile(contentComplex);
-
- expect(parsedSourceSimple.editable.header).toBe(parsedSourceComplex.editable.header);
- });
- });
-
- describe('editable body to raw content default and changes', () => {
it.each`
- sourceContent | desc
- ${contentSimple} | ${'returns false by default for both raw and body'}
- ${contentComplex} | ${'returns false by default for both raw and body'}
- `('$desc', ({ sourceContent }) => {
- const parsedSource = parseSourceFile(sourceContent);
+ parsedSource | isBody | target
+ ${parseSourceFile(content)} | ${undefined} | ${content}
+ ${parseSourceFile(content)} | ${false} | ${content}
+ ${parseSourceFile(content)} | ${true} | ${body}
+ ${parseSourceFile(contentComplex)} | ${undefined} | ${contentComplex}
+ ${parseSourceFile(contentComplex)} | ${false} | ${contentComplex}
+ ${parseSourceFile(contentComplex)} | ${true} | ${complexBody}
+ `(
+ 'returns only the $target content when the `isBody` parameter argument is $isBody',
+ ({ parsedSource, isBody, target }) => {
+ expect(parsedSource.content(isBody)).toBe(target);
+ },
+ );
+ });
- expect(parsedSource.isModifiedRaw()).toBe(false);
- expect(parsedSource.isModifiedBody()).toBe(false);
- });
+ describe('modified content', () => {
+ const newBody = `${body} ${edit}`;
+ const newComplexBody = `${complexBody} ${edit}`;
it.each`
- sourceContent | editableKey | syncKey | isModifiedKey | desc
- ${contentSimple} | ${'body'} | ${'syncRaw'} | ${'isModifiedRaw'} | ${'returns true after modification and sync'}
- ${contentSimple} | ${'raw'} | ${'syncBody'} | ${'isModifiedBody'} | ${'returns true after modification and sync'}
- ${contentComplex} | ${'body'} | ${'syncRaw'} | ${'isModifiedRaw'} | ${'returns true after modification and sync'}
- ${contentComplex} | ${'raw'} | ${'syncBody'} | ${'isModifiedBody'} | ${'returns true after modification and sync'}
- `('$desc', ({ sourceContent, editableKey, syncKey, isModifiedKey }) => {
- const parsedSource = parseSourceFile(sourceContent);
- parsedSource.editable[editableKey] += 'Added content';
- parsedSource[syncKey]();
-
- expect(parsedSource[isModifiedKey]()).toBe(true);
- });
+ parsedSource | isModified | targetRaw | targetBody
+ ${parseSourceFile(content)} | ${false} | ${content} | ${body}
+ ${parseSourceFile(content)} | ${true} | ${newContent} | ${newBody}
+ ${parseSourceFile(contentComplex)} | ${false} | ${contentComplex} | ${complexBody}
+ ${parseSourceFile(contentComplex)} | ${true} | ${newContentComplex} | ${newComplexBody}
+ `(
+ 'returns $isModified after a $targetRaw sync',
+ ({ parsedSource, isModified, targetRaw, targetBody }) => {
+ parsedSource.sync(targetRaw);
+
+ expect(parsedSource.isModified()).toBe(isModified);
+ expect(parsedSource.content()).toBe(targetRaw);
+ expect(parsedSource.content(true)).toBe(targetBody);
+ },
+ );
});
});
diff --git a/spec/frontend/static_site_editor/services/submit_content_changes_spec.js b/spec/frontend/static_site_editor/services/submit_content_changes_spec.js
index 3636de3fe70..a9169eb3e16 100644
--- a/spec/frontend/static_site_editor/services/submit_content_changes_spec.js
+++ b/spec/frontend/static_site_editor/services/submit_content_changes_spec.js
@@ -22,6 +22,7 @@ import {
sourcePath,
sourceContent as content,
trackingCategory,
+ images,
} from '../mock_data';
jest.mock('~/static_site_editor/services/generate_branch_name');
@@ -69,7 +70,7 @@ describe('submitContentChanges', () => {
});
it('commits the content changes to the branch when creating branch succeeds', () => {
- return submitContentChanges({ username, projectId, sourcePath, content }).then(() => {
+ return submitContentChanges({ username, projectId, sourcePath, content, images }).then(() => {
expect(Api.commitMultiple).toHaveBeenCalledWith(projectId, {
branch,
commit_message: mergeRequestTitle,
@@ -79,6 +80,35 @@ describe('submitContentChanges', () => {
file_path: sourcePath,
content,
},
+ {
+ action: 'create',
+ content: 'image1-content',
+ encoding: 'base64',
+ file_path: 'path/to/image1.png',
+ },
+ ],
+ });
+ });
+ });
+
+ it('does not commit an image if it has been removed from the content', () => {
+ const contentWithoutImages = '## Content without images';
+ return submitContentChanges({
+ username,
+ projectId,
+ sourcePath,
+ content: contentWithoutImages,
+ images,
+ }).then(() => {
+ expect(Api.commitMultiple).toHaveBeenCalledWith(projectId, {
+ branch,
+ commit_message: mergeRequestTitle,
+ actions: [
+ {
+ action: 'update',
+ file_path: sourcePath,
+ content: contentWithoutImages,
+ },
],
});
});
@@ -87,13 +117,13 @@ describe('submitContentChanges', () => {
it('notifies error when content could not be committed', () => {
Api.commitMultiple.mockRejectedValueOnce();
- return expect(submitContentChanges({ username, projectId })).rejects.toThrow(
+ return expect(submitContentChanges({ username, projectId, images })).rejects.toThrow(
SUBMIT_CHANGES_COMMIT_ERROR,
);
});
it('creates a merge request when commiting changes succeeds', () => {
- return submitContentChanges({ username, projectId, sourcePath, content }).then(() => {
+ return submitContentChanges({ username, projectId, sourcePath, content, images }).then(() => {
expect(Api.createProjectMergeRequest).toHaveBeenCalledWith(
projectId,
convertObjectPropsToSnakeCase({
@@ -108,7 +138,7 @@ describe('submitContentChanges', () => {
it('notifies error when merge request could not be created', () => {
Api.createProjectMergeRequest.mockRejectedValueOnce();
- return expect(submitContentChanges({ username, projectId })).rejects.toThrow(
+ return expect(submitContentChanges({ username, projectId, images })).rejects.toThrow(
SUBMIT_CHANGES_MERGE_REQUEST_ERROR,
);
});
@@ -117,9 +147,11 @@ describe('submitContentChanges', () => {
let result;
beforeEach(() => {
- return submitContentChanges({ username, projectId, sourcePath, content }).then(_result => {
- result = _result;
- });
+ return submitContentChanges({ username, projectId, sourcePath, content, images }).then(
+ _result => {
+ result = _result;
+ },
+ );
});
it('returns the branch name', () => {
@@ -147,7 +179,7 @@ describe('submitContentChanges', () => {
describe('sends the correct tracking event', () => {
beforeEach(() => {
- return submitContentChanges({ username, projectId, sourcePath, content });
+ return submitContentChanges({ username, projectId, sourcePath, content, images });
});
it('for committing changes', () => {
diff --git a/spec/frontend/vue_mr_widget/components/approvals/approvals_spec.js b/spec/frontend/vue_mr_widget/components/approvals/approvals_spec.js
new file mode 100644
index 00000000000..e39f66d3f30
--- /dev/null
+++ b/spec/frontend/vue_mr_widget/components/approvals/approvals_spec.js
@@ -0,0 +1,391 @@
+import { shallowMount } from '@vue/test-utils';
+import { GlButton } from '@gitlab/ui';
+import Approvals from '~/vue_merge_request_widget/components/approvals/approvals.vue';
+import ApprovalsSummary from '~/vue_merge_request_widget/components/approvals/approvals_summary.vue';
+import ApprovalsSummaryOptional from '~/vue_merge_request_widget/components/approvals/approvals_summary_optional.vue';
+import createFlash from '~/flash';
+import {
+ FETCH_LOADING,
+ FETCH_ERROR,
+ APPROVE_ERROR,
+ UNAPPROVE_ERROR,
+} from '~/vue_merge_request_widget/components/approvals/messages';
+import eventHub from '~/vue_merge_request_widget/event_hub';
+
+jest.mock('~/flash');
+
+const TEST_HELP_PATH = 'help/path';
+const testApprovedBy = () => [1, 7, 10].map(id => ({ id }));
+const testApprovals = () => ({
+ approved: false,
+ approved_by: testApprovedBy().map(user => ({ user })),
+ approval_rules_left: [],
+ approvals_left: 4,
+ suggested_approvers: [],
+ user_can_approve: true,
+ user_has_approved: true,
+ require_password_to_approve: false,
+});
+const testApprovalRulesResponse = () => ({ rules: [{ id: 2 }] });
+
+// For some reason, the `Promise.resolve()` needs to be deferred
+// or the timing doesn't work.
+const tick = () => Promise.resolve();
+const waitForTick = done =>
+ tick()
+ .then(done)
+ .catch(done.fail);
+
+describe('MRWidget approvals', () => {
+ let wrapper;
+ let service;
+ let mr;
+
+ const createComponent = (props = {}) => {
+ wrapper = shallowMount(Approvals, {
+ propsData: {
+ mr,
+ service,
+ ...props,
+ },
+ });
+ };
+
+ const findAction = () => wrapper.find(GlButton);
+ const findActionData = () => {
+ const action = findAction();
+
+ return !action.exists()
+ ? null
+ : {
+ variant: action.props('variant'),
+ category: action.props('category'),
+ text: action.text(),
+ };
+ };
+ const findSummary = () => wrapper.find(ApprovalsSummary);
+ const findOptionalSummary = () => wrapper.find(ApprovalsSummaryOptional);
+
+ beforeEach(() => {
+ service = {
+ ...{
+ fetchApprovals: jest.fn().mockReturnValue(Promise.resolve(testApprovals())),
+ fetchApprovalSettings: jest
+ .fn()
+ .mockReturnValue(Promise.resolve(testApprovalRulesResponse())),
+ approveMergeRequest: jest.fn().mockReturnValue(Promise.resolve(testApprovals())),
+ unapproveMergeRequest: jest.fn().mockReturnValue(Promise.resolve(testApprovals())),
+ approveMergeRequestWithAuth: jest.fn().mockReturnValue(Promise.resolve(testApprovals())),
+ },
+ };
+ mr = {
+ ...{
+ setApprovals: jest.fn(),
+ setApprovalRules: jest.fn(),
+ },
+ approvalsHelpPath: TEST_HELP_PATH,
+ approvals: testApprovals(),
+ approvalRules: [],
+ isOpen: true,
+ state: 'open',
+ };
+
+ jest.spyOn(eventHub, '$emit').mockImplementation(() => {});
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ wrapper = null;
+ });
+
+ describe('when created', () => {
+ beforeEach(() => {
+ createComponent();
+ });
+
+ it('shows loading message', () => {
+ wrapper.setData({ fetchingApprovals: true });
+
+ return tick().then(() => {
+ expect(wrapper.text()).toContain(FETCH_LOADING);
+ });
+ });
+
+ it('fetches approvals', () => {
+ expect(service.fetchApprovals).toHaveBeenCalled();
+ });
+ });
+
+ describe('when fetch approvals error', () => {
+ beforeEach(done => {
+ jest.spyOn(service, 'fetchApprovals').mockReturnValue(Promise.reject());
+ createComponent();
+ waitForTick(done);
+ });
+
+ it('still shows loading message', () => {
+ expect(wrapper.text()).toContain(FETCH_LOADING);
+ });
+
+ it('flashes error', () => {
+ expect(createFlash).toHaveBeenCalledWith(FETCH_ERROR);
+ });
+ });
+
+ describe('action button', () => {
+ describe('when mr is closed', () => {
+ beforeEach(done => {
+ mr.isOpen = false;
+ mr.approvals.user_has_approved = false;
+ mr.approvals.user_can_approve = true;
+
+ createComponent();
+ waitForTick(done);
+ });
+
+ it('action is not rendered', () => {
+ expect(findActionData()).toBe(null);
+ });
+ });
+
+ describe('when user cannot approve', () => {
+ beforeEach(done => {
+ mr.approvals.user_has_approved = false;
+ mr.approvals.user_can_approve = false;
+
+ createComponent();
+ waitForTick(done);
+ });
+
+ it('action is not rendered', () => {
+ expect(findActionData()).toBe(null);
+ });
+ });
+
+ describe('when user can approve', () => {
+ beforeEach(() => {
+ mr.approvals.user_has_approved = false;
+ mr.approvals.user_can_approve = true;
+ });
+
+ describe('and MR is unapproved', () => {
+ beforeEach(done => {
+ createComponent();
+ waitForTick(done);
+ });
+
+ it('approve action is rendered', () => {
+ expect(findActionData()).toEqual({
+ variant: 'info',
+ text: 'Approve',
+ category: 'primary',
+ });
+ });
+ });
+
+ describe('and MR is approved', () => {
+ beforeEach(() => {
+ mr.approvals.approved = true;
+ });
+
+ describe('with no approvers', () => {
+ beforeEach(done => {
+ mr.approvals.approved_by = [];
+ createComponent();
+ waitForTick(done);
+ });
+
+ it('approve action (with inverted style) is rendered', () => {
+ expect(findActionData()).toEqual({
+ variant: 'info',
+ text: 'Approve',
+ category: 'secondary',
+ });
+ });
+ });
+
+ describe('with approvers', () => {
+ beforeEach(done => {
+ mr.approvals.approved_by = [{ user: { id: 7 } }];
+ createComponent();
+ waitForTick(done);
+ });
+
+ it('approve additionally action is rendered', () => {
+ expect(findActionData()).toEqual({
+ variant: 'info',
+ text: 'Approve additionally',
+ category: 'secondary',
+ });
+ });
+ });
+ });
+
+ describe('when approve action is clicked', () => {
+ beforeEach(done => {
+ createComponent();
+ waitForTick(done);
+ });
+
+ it('shows loading icon', () => {
+ jest.spyOn(service, 'approveMergeRequest').mockReturnValue(new Promise(() => {}));
+ const action = findAction();
+
+ expect(action.props('loading')).toBe(false);
+
+ action.vm.$emit('click');
+
+ return tick().then(() => {
+ expect(action.props('loading')).toBe(true);
+ });
+ });
+
+ describe('and after loading', () => {
+ beforeEach(done => {
+ findAction().vm.$emit('click');
+ waitForTick(done);
+ });
+
+ it('calls service approve', () => {
+ expect(service.approveMergeRequest).toHaveBeenCalled();
+ });
+
+ it('emits to eventHub', () => {
+ expect(eventHub.$emit).toHaveBeenCalledWith('MRWidgetUpdateRequested');
+ });
+
+ it('calls store setApprovals', () => {
+ expect(mr.setApprovals).toHaveBeenCalledWith(testApprovals());
+ });
+ });
+
+ describe('and error', () => {
+ beforeEach(done => {
+ jest.spyOn(service, 'approveMergeRequest').mockReturnValue(Promise.reject());
+ findAction().vm.$emit('click');
+ waitForTick(done);
+ });
+
+ it('flashes error message', () => {
+ expect(createFlash).toHaveBeenCalledWith(APPROVE_ERROR);
+ });
+ });
+ });
+ });
+
+ describe('when user has approved', () => {
+ beforeEach(done => {
+ mr.approvals.user_has_approved = true;
+ mr.approvals.user_can_approve = false;
+
+ createComponent();
+ waitForTick(done);
+ });
+
+ it('revoke action is rendered', () => {
+ expect(findActionData()).toEqual({
+ variant: 'warning',
+ text: 'Revoke approval',
+ category: 'secondary',
+ });
+ });
+
+ describe('when revoke action is clicked', () => {
+ describe('and successful', () => {
+ beforeEach(done => {
+ findAction().vm.$emit('click');
+ waitForTick(done);
+ });
+
+ it('calls service unapprove', () => {
+ expect(service.unapproveMergeRequest).toHaveBeenCalled();
+ });
+
+ it('emits to eventHub', () => {
+ expect(eventHub.$emit).toHaveBeenCalledWith('MRWidgetUpdateRequested');
+ });
+
+ it('calls store setApprovals', () => {
+ expect(mr.setApprovals).toHaveBeenCalledWith(testApprovals());
+ });
+ });
+
+ describe('and error', () => {
+ beforeEach(done => {
+ jest.spyOn(service, 'unapproveMergeRequest').mockReturnValue(Promise.reject());
+ findAction().vm.$emit('click');
+ waitForTick(done);
+ });
+
+ it('flashes error message', () => {
+ expect(createFlash).toHaveBeenCalledWith(UNAPPROVE_ERROR);
+ });
+ });
+ });
+ });
+ });
+
+ describe('approvals optional summary', () => {
+ describe('when no approvals required and no approvers', () => {
+ beforeEach(() => {
+ mr.approvals.approved_by = [];
+ mr.approvals.approvals_required = 0;
+ mr.approvals.user_has_approved = false;
+ });
+
+ describe('and can approve', () => {
+ beforeEach(done => {
+ mr.approvals.user_can_approve = true;
+
+ createComponent();
+ waitForTick(done);
+ });
+
+ it('is shown', () => {
+ expect(findSummary().exists()).toBe(false);
+ expect(findOptionalSummary().props()).toEqual({
+ canApprove: true,
+ helpPath: TEST_HELP_PATH,
+ });
+ });
+ });
+
+ describe('and cannot approve', () => {
+ beforeEach(done => {
+ mr.approvals.user_can_approve = false;
+
+ createComponent();
+ waitForTick(done);
+ });
+
+ it('is shown', () => {
+ expect(findSummary().exists()).toBe(false);
+ expect(findOptionalSummary().props()).toEqual({
+ canApprove: false,
+ helpPath: TEST_HELP_PATH,
+ });
+ });
+ });
+ });
+ });
+
+ describe('approvals summary', () => {
+ beforeEach(done => {
+ createComponent();
+ waitForTick(done);
+ });
+
+ it('is rendered with props', () => {
+ const expected = testApprovals();
+ const summary = findSummary();
+
+ expect(findOptionalSummary().exists()).toBe(false);
+ expect(summary.exists()).toBe(true);
+ expect(summary.props()).toMatchObject({
+ approvalsLeft: expected.approvals_left,
+ rulesLeft: expected.approval_rules_left,
+ approvers: testApprovedBy(),
+ });
+ });
+ });
+});
diff --git a/spec/frontend/vue_mr_widget/components/approvals/approvals_summary_optional_spec.js b/spec/frontend/vue_mr_widget/components/approvals/approvals_summary_optional_spec.js
new file mode 100644
index 00000000000..77fad7f51ab
--- /dev/null
+++ b/spec/frontend/vue_mr_widget/components/approvals/approvals_summary_optional_spec.js
@@ -0,0 +1,57 @@
+import { shallowMount } from '@vue/test-utils';
+import { GlLink } from '@gitlab/ui';
+import {
+ OPTIONAL,
+ OPTIONAL_CAN_APPROVE,
+} from '~/vue_merge_request_widget/components/approvals/messages';
+import ApprovalsSummaryOptional from '~/vue_merge_request_widget/components/approvals/approvals_summary_optional.vue';
+
+const TEST_HELP_PATH = 'help/path';
+
+describe('MRWidget approvals summary optional', () => {
+ let wrapper;
+
+ const createComponent = (props = {}) => {
+ wrapper = shallowMount(ApprovalsSummaryOptional, {
+ propsData: props,
+ });
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ wrapper = null;
+ });
+
+ const findHelpLink = () => wrapper.find(GlLink);
+
+ describe('when can approve', () => {
+ beforeEach(() => {
+ createComponent({ canApprove: true, helpPath: TEST_HELP_PATH });
+ });
+
+ it('shows optional can approve message', () => {
+ expect(wrapper.text()).toEqual(OPTIONAL_CAN_APPROVE);
+ });
+
+ it('shows help link', () => {
+ const link = findHelpLink();
+
+ expect(link.exists()).toBe(true);
+ expect(link.attributes('href')).toBe(TEST_HELP_PATH);
+ });
+ });
+
+ describe('when cannot approve', () => {
+ beforeEach(() => {
+ createComponent({ canApprove: false, helpPath: TEST_HELP_PATH });
+ });
+
+ it('shows optional message', () => {
+ expect(wrapper.text()).toEqual(OPTIONAL);
+ });
+
+ it('does not show help link', () => {
+ expect(findHelpLink().exists()).toBe(false);
+ });
+ });
+});
diff --git a/spec/frontend/vue_mr_widget/components/approvals/approvals_summary_spec.js b/spec/frontend/vue_mr_widget/components/approvals/approvals_summary_spec.js
new file mode 100644
index 00000000000..822d075f28f
--- /dev/null
+++ b/spec/frontend/vue_mr_widget/components/approvals/approvals_summary_spec.js
@@ -0,0 +1,93 @@
+import { shallowMount } from '@vue/test-utils';
+import { APPROVED_MESSAGE } from '~/vue_merge_request_widget/components/approvals/messages';
+import ApprovalsSummary from '~/vue_merge_request_widget/components/approvals/approvals_summary.vue';
+import { toNounSeriesText } from '~/lib/utils/grammar';
+import UserAvatarList from '~/vue_shared/components/user_avatar/user_avatar_list.vue';
+
+const testApprovers = () => Array.from({ length: 5 }, (_, i) => i).map(id => ({ id }));
+const testRulesLeft = () => ['Lorem', 'Ipsum', 'dolar & sit'];
+const TEST_APPROVALS_LEFT = 3;
+
+describe('MRWidget approvals summary', () => {
+ let wrapper;
+
+ const createComponent = (props = {}) => {
+ wrapper = shallowMount(ApprovalsSummary, {
+ propsData: {
+ approved: false,
+ approvers: testApprovers(),
+ approvalsLeft: TEST_APPROVALS_LEFT,
+ rulesLeft: testRulesLeft(),
+ ...props,
+ },
+ });
+ };
+
+ const findAvatars = () => wrapper.find(UserAvatarList);
+
+ afterEach(() => {
+ wrapper.destroy();
+ wrapper = null;
+ });
+
+ describe('when approved', () => {
+ beforeEach(() => {
+ createComponent({
+ approved: true,
+ });
+ });
+
+ it('shows approved message', () => {
+ expect(wrapper.text()).toContain(APPROVED_MESSAGE);
+ });
+
+ it('renders avatar list for approvers', () => {
+ const avatars = findAvatars();
+
+ expect(avatars.exists()).toBe(true);
+ expect(avatars.props()).toEqual(
+ expect.objectContaining({
+ items: testApprovers(),
+ }),
+ );
+ });
+ });
+
+ describe('when not approved', () => {
+ beforeEach(() => {
+ createComponent();
+ });
+
+ it('render message', () => {
+ const names = toNounSeriesText(testRulesLeft());
+
+ expect(wrapper.text()).toContain(
+ `Requires ${TEST_APPROVALS_LEFT} more approvals from ${names}.`,
+ );
+ });
+ });
+
+ describe('when no rulesLeft', () => {
+ beforeEach(() => {
+ createComponent({
+ rulesLeft: [],
+ });
+ });
+
+ it('renders message', () => {
+ expect(wrapper.text()).toContain(`Requires ${TEST_APPROVALS_LEFT} more approvals.`);
+ });
+ });
+
+ describe('when no approvers', () => {
+ beforeEach(() => {
+ createComponent({
+ approvers: [],
+ });
+ });
+
+ it('does not render avatar list', () => {
+ expect(wrapper.find(UserAvatarList).exists()).toBe(false);
+ });
+ });
+});
diff --git a/spec/frontend/vue_mr_widget/components/mr_widget_author_spec.js b/spec/frontend/vue_mr_widget/components/mr_widget_author_spec.js
index 05690aa1248..e7c10ab4c2d 100644
--- a/spec/frontend/vue_mr_widget/components/mr_widget_author_spec.js
+++ b/spec/frontend/vue_mr_widget/components/mr_widget_author_spec.js
@@ -1,39 +1,61 @@
-import Vue from 'vue';
-import mountComponent from 'helpers/vue_mount_component_helper';
+import { shallowMount } from '@vue/test-utils';
import MrWidgetAuthor from '~/vue_merge_request_widget/components/mr_widget_author.vue';
+window.gl = window.gl || {};
+
describe('MrWidgetAuthor', () => {
- let vm;
+ let wrapper;
+ let oldWindowGl;
+ const mockAuthor = {
+ name: 'Administrator',
+ username: 'root',
+ webUrl: 'http://localhost:3000/root',
+ avatarUrl: 'http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon',
+ };
beforeEach(() => {
- const Component = Vue.extend(MrWidgetAuthor);
-
- vm = mountComponent(Component, {
- author: {
- name: 'Administrator',
- username: 'root',
- webUrl: 'http://localhost:3000/root',
- avatarUrl:
- 'http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon',
+ oldWindowGl = window.gl;
+ window.gl = {
+ mrWidgetData: {
+ defaultAvatarUrl: 'no_avatar.png',
+ },
+ };
+ wrapper = shallowMount(MrWidgetAuthor, {
+ propsData: {
+ author: mockAuthor,
},
});
});
afterEach(() => {
- vm.$destroy();
+ wrapper.destroy();
+ window.gl = oldWindowGl;
});
it('renders link with the author web url', () => {
- expect(vm.$el.getAttribute('href')).toEqual('http://localhost:3000/root');
+ expect(wrapper.attributes('href')).toBe('http://localhost:3000/root');
});
it('renders image with avatar url', () => {
- expect(vm.$el.querySelector('img').getAttribute('src')).toEqual(
+ expect(wrapper.find('img').attributes('src')).toBe(
'http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon',
);
});
+ it('renders image with default avatar url when no avatarUrl is present in author', async () => {
+ wrapper.setProps({
+ author: {
+ ...mockAuthor,
+ avatarUrl: null,
+ },
+ });
+
+ await wrapper.vm.$nextTick();
+
+ expect(wrapper.find('img').attributes('src')).toBe('no_avatar.png');
+ });
+
it('renders author name', () => {
- expect(vm.$el.textContent.trim()).toEqual('Administrator');
+ expect(wrapper.find('span').text()).toBe('Administrator');
});
});
diff --git a/spec/frontend/vue_mr_widget/components/mr_widget_expandable_section_spec.js b/spec/frontend/vue_mr_widget/components/mr_widget_expandable_section_spec.js
new file mode 100644
index 00000000000..69a50899d4d
--- /dev/null
+++ b/spec/frontend/vue_mr_widget/components/mr_widget_expandable_section_spec.js
@@ -0,0 +1,65 @@
+import { GlButton, GlCollapse, GlIcon } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
+import MrCollapsibleSection from '~/vue_merge_request_widget/components/mr_widget_expandable_section.vue';
+
+describe('MrWidgetExpanableSection', () => {
+ let wrapper;
+
+ const findButton = () => wrapper.find(GlButton);
+ const findCollapse = () => wrapper.find(GlCollapse);
+
+ beforeEach(() => {
+ wrapper = shallowMount(MrCollapsibleSection, {
+ slots: {
+ content: '<span>Collapsable Content</span>',
+ header: '<span>Header Content</span>',
+ },
+ });
+ });
+
+ it('renders Icon', () => {
+ expect(wrapper.contains(GlIcon)).toBe(true);
+ });
+
+ it('renders header slot', () => {
+ expect(wrapper.text()).toContain('Header Content');
+ });
+
+ it('renders content slot', () => {
+ expect(wrapper.text()).toContain('Collapsable Content');
+ });
+
+ describe('when collapse section is closed', () => {
+ it('renders button with expand text', () => {
+ expect(findButton().text()).toBe('Expand');
+ });
+
+ it('renders a collpased section with no visibility', () => {
+ const collapse = findCollapse();
+
+ expect(collapse.exists()).toBe(true);
+ expect(collapse.attributes('visible')).toBeUndefined();
+ });
+ });
+
+ describe('when collapse section is open', () => {
+ beforeEach(() => {
+ findButton().vm.$emit('click');
+ return wrapper.vm.$nextTick();
+ });
+
+ it('renders button with collapse text', () => {
+ const button = findButton();
+
+ expect(button.exists()).toBe(true);
+ expect(button.text()).toBe('Collapse');
+ });
+
+ it('renders a collpased section with visible content', () => {
+ const collapse = findCollapse();
+
+ expect(collapse.exists()).toBe(true);
+ expect(collapse.attributes('visible')).toBe('true');
+ });
+ });
+});
diff --git a/spec/frontend/vue_mr_widget/components/mr_widget_header_spec.js b/spec/frontend/vue_mr_widget/components/mr_widget_header_spec.js
index b492a69fb3d..21058005d29 100644
--- a/spec/frontend/vue_mr_widget/components/mr_widget_header_spec.js
+++ b/spec/frontend/vue_mr_widget/components/mr_widget_header_spec.js
@@ -1,7 +1,13 @@
import Vue from 'vue';
+import Mousetrap from 'mousetrap';
import mountComponent from 'helpers/vue_mount_component_helper';
import headerComponent from '~/vue_merge_request_widget/components/mr_widget_header.vue';
+jest.mock('mousetrap', () => ({
+ bind: jest.fn(),
+ unbind: jest.fn(),
+}));
+
describe('MRWidgetHeader', () => {
let vm;
let Component;
@@ -126,6 +132,35 @@ describe('MRWidgetHeader', () => {
it('renders target branch', () => {
expect(vm.$el.querySelector('.js-target-branch').textContent.trim()).toEqual('master');
});
+
+ describe('keyboard shortcuts', () => {
+ it('binds a keyboard shortcut handler to the "b" key', () => {
+ expect(Mousetrap.bind).toHaveBeenCalledWith('b', expect.any(Function));
+ });
+
+ it('triggers a click on the "copy to clipboard" button when the handler is executed', () => {
+ const testClickHandler = jest.fn();
+ vm.$refs.copyBranchNameButton.$el.addEventListener('click', testClickHandler);
+
+ // Get a reference to the function that was assigned to the "b" shortcut key.
+ const shortcutHandler = Mousetrap.bind.mock.calls[0][1];
+
+ expect(testClickHandler).not.toHaveBeenCalled();
+
+ // Simulate Mousetrap calling the function.
+ shortcutHandler();
+
+ expect(testClickHandler).toHaveBeenCalledTimes(1);
+ });
+
+ it('unbinds the keyboard shortcut when the component is destroyed', () => {
+ expect(Mousetrap.unbind).not.toHaveBeenCalled();
+
+ vm.$destroy();
+
+ expect(Mousetrap.unbind).toHaveBeenCalledWith('b');
+ });
+ });
});
describe('with an open merge request', () => {
diff --git a/spec/frontend/vue_mr_widget/components/mr_widget_pipeline_spec.js b/spec/frontend/vue_mr_widget/components/mr_widget_pipeline_spec.js
index 309aec179d9..6486826c3ec 100644
--- a/spec/frontend/vue_mr_widget/components/mr_widget_pipeline_spec.js
+++ b/spec/frontend/vue_mr_widget/components/mr_widget_pipeline_spec.js
@@ -1,189 +1,182 @@
-import Vue from 'vue';
-import mountComponent from 'helpers/vue_mount_component_helper';
+import { shallowMount, mount } from '@vue/test-utils';
+import { GlLoadingIcon } from '@gitlab/ui';
import { trimText } from 'helpers/text_helper';
-import pipelineComponent from '~/vue_merge_request_widget/components/mr_widget_pipeline.vue';
+import { SUCCESS } from '~/vue_merge_request_widget/constants';
+import PipelineComponent from '~/vue_merge_request_widget/components/mr_widget_pipeline.vue';
+import PipelineStage from '~/pipelines/components/pipelines_list/stage.vue';
import mockData from '../mock_data';
describe('MRWidgetPipeline', () => {
- let vm;
- let Component;
-
- beforeEach(() => {
- Component = Vue.extend(pipelineComponent);
- });
+ let wrapper;
+
+ const defaultProps = {
+ pipeline: mockData.pipeline,
+ ciStatus: SUCCESS,
+ hasCi: true,
+ mrTroubleshootingDocsPath: 'help',
+ ciTroubleshootingDocsPath: 'ci-help',
+ };
+
+ const ciErrorMessage =
+ 'Could not retrieve the pipeline status. For troubleshooting steps, read the documentation.';
+ const monitoringMessage = 'Checking pipeline status.';
+
+ const findCIErrorMessage = () => wrapper.find('[data-testid="ci-error-message"]');
+ const findPipelineID = () => wrapper.find('[data-testid="pipeline-id"]');
+ const findPipelineInfoContainer = () => wrapper.find('[data-testid="pipeline-info-container"]');
+ const findCommitLink = () => wrapper.find('[data-testid="commit-link"]');
+ const findPipelineGraph = () => wrapper.find('[data-testid="widget-mini-pipeline-graph"]');
+ const findAllPipelineStages = () => wrapper.findAll(PipelineStage);
+ const findPipelineCoverage = () => wrapper.find('[data-testid="pipeline-coverage"]');
+ const findPipelineCoverageDelta = () => wrapper.find('[data-testid="pipeline-coverage-delta"]');
+ const findMonitoringPipelineMessage = () =>
+ wrapper.find('[data-testid="monitoring-pipeline-message"]');
+ const findLoadingIcon = () => wrapper.find(GlLoadingIcon);
+
+ const createWrapper = (props, mountFn = shallowMount) => {
+ wrapper = mountFn(PipelineComponent, {
+ propsData: {
+ ...defaultProps,
+ ...props,
+ },
+ });
+ };
afterEach(() => {
- vm.$destroy();
+ if (wrapper?.destroy) {
+ wrapper.destroy();
+ wrapper = null;
+ }
});
describe('computed', () => {
describe('hasPipeline', () => {
- it('should return true when there is a pipeline', () => {
- vm = mountComponent(Component, {
- pipeline: mockData.pipeline,
- ciStatus: 'success',
- hasCi: true,
- troubleshootingDocsPath: 'help',
- });
+ beforeEach(() => {
+ createWrapper();
+ });
- expect(vm.hasPipeline).toEqual(true);
+ it('should return true when there is a pipeline', () => {
+ expect(wrapper.vm.hasPipeline).toBe(true);
});
- it('should return false when there is no pipeline', () => {
- vm = mountComponent(Component, {
- pipeline: {},
- troubleshootingDocsPath: 'help',
- });
+ it('should return false when there is no pipeline', async () => {
+ wrapper.setProps({ pipeline: {} });
- expect(vm.hasPipeline).toEqual(false);
+ await wrapper.vm.$nextTick();
+
+ expect(wrapper.vm.hasPipeline).toBe(false);
});
});
describe('hasCIError', () => {
- it('should return false when there is no CI error', () => {
- vm = mountComponent(Component, {
- pipeline: mockData.pipeline,
- hasCi: true,
- ciStatus: 'success',
- troubleshootingDocsPath: 'help',
- });
+ beforeEach(() => {
+ createWrapper();
+ });
- expect(vm.hasCIError).toEqual(false);
+ it('should return false when there is no CI error', () => {
+ expect(wrapper.vm.hasCIError).toBe(false);
});
- it('should return true when there is a CI error', () => {
- vm = mountComponent(Component, {
- pipeline: mockData.pipeline,
- hasCi: true,
- ciStatus: null,
- troubleshootingDocsPath: 'help',
- });
+ it('should return true when there is a pipeline, but no ci status', async () => {
+ wrapper.setProps({ ciStatus: null });
- expect(vm.hasCIError).toEqual(true);
+ await wrapper.vm.$nextTick();
+
+ expect(wrapper.vm.hasCIError).toBe(true);
});
});
describe('coverageDeltaClass', () => {
- it('should return no class if there is no coverage change', () => {
- vm = mountComponent(Component, {
- pipeline: mockData.pipeline,
- pipelineCoverageDelta: '0',
- troubleshootingDocsPath: 'help',
- });
+ beforeEach(() => {
+ createWrapper({ pipelineCoverageDelta: '0' });
+ });
- expect(vm.coverageDeltaClass).toEqual('');
+ it('should return no class if there is no coverage change', async () => {
+ expect(wrapper.vm.coverageDeltaClass).toBe('');
});
- it('should return text-success if the coverage increased', () => {
- vm = mountComponent(Component, {
- pipeline: mockData.pipeline,
- pipelineCoverageDelta: '10',
- troubleshootingDocsPath: 'help',
- });
+ it('should return text-success if the coverage increased', async () => {
+ wrapper.setProps({ pipelineCoverageDelta: '10' });
- expect(vm.coverageDeltaClass).toEqual('text-success');
+ await wrapper.vm.$nextTick();
+
+ expect(wrapper.vm.coverageDeltaClass).toBe('text-success');
});
- it('should return text-danger if the coverage decreased', () => {
- vm = mountComponent(Component, {
- pipeline: mockData.pipeline,
- pipelineCoverageDelta: '-12',
- troubleshootingDocsPath: 'help',
- });
+ it('should return text-danger if the coverage decreased', async () => {
+ wrapper.setProps({ pipelineCoverageDelta: '-12' });
- expect(vm.coverageDeltaClass).toEqual('text-danger');
+ await wrapper.vm.$nextTick();
+
+ expect(wrapper.vm.coverageDeltaClass).toBe('text-danger');
});
});
});
describe('rendered output', () => {
- it('should render CI error', () => {
- vm = mountComponent(Component, {
- pipeline: mockData.pipeline,
- hasCi: true,
- troubleshootingDocsPath: 'help',
- });
-
- expect(vm.$el.querySelector('.media-body').textContent.trim()).toContain(
- 'Could not retrieve the pipeline status. For troubleshooting steps, read the documentation.',
- );
+ beforeEach(() => {
+ createWrapper({ ciStatus: null }, mount);
});
- it('should render CI error when no pipeline is provided', () => {
- vm = mountComponent(Component, {
- pipeline: {},
- hasCi: true,
- ciStatus: 'success',
- troubleshootingDocsPath: 'help',
- });
-
- expect(vm.$el.querySelector('.media-body').textContent.trim()).toContain(
- 'Could not retrieve the pipeline status. For troubleshooting steps, read the documentation.',
- );
+ it('should render CI error if there is a pipeline, but no status', async () => {
+ expect(findCIErrorMessage().text()).toBe(ciErrorMessage);
});
- it('should render CI error when no CI is provided and pipeline must succeed is turned on', () => {
- vm = mountComponent(Component, {
+ it('should render a loading state when no pipeline is found', async () => {
+ wrapper.setProps({
pipeline: {},
hasCi: false,
pipelineMustSucceed: true,
- troubleshootingDocsPath: 'help',
});
- expect(vm.$el.querySelector('.media-body').textContent.trim()).toContain(
- 'No pipeline has been run for this commit.',
- );
+ await wrapper.vm.$nextTick();
+
+ expect(findMonitoringPipelineMessage().text()).toBe(monitoringMessage);
+ expect(findLoadingIcon().exists()).toBe(true);
});
describe('with a pipeline', () => {
beforeEach(() => {
- vm = mountComponent(Component, {
- pipeline: mockData.pipeline,
- hasCi: true,
- ciStatus: 'success',
- pipelineCoverageDelta: mockData.pipelineCoverageDelta,
- troubleshootingDocsPath: 'help',
- });
+ createWrapper(
+ {
+ pipelineCoverageDelta: mockData.pipelineCoverageDelta,
+ },
+ mount,
+ );
});
it('should render pipeline ID', () => {
- expect(vm.$el.querySelector('.pipeline-id').textContent.trim()).toEqual(
- `#${mockData.pipeline.id}`,
- );
+ expect(
+ findPipelineID()
+ .text()
+ .trim(),
+ ).toBe(`#${mockData.pipeline.id}`);
});
it('should render pipeline status and commit id', () => {
- expect(vm.$el.querySelector('.media-body').textContent.trim()).toContain(
- mockData.pipeline.details.status.label,
- );
+ expect(findPipelineInfoContainer().text()).toMatch(mockData.pipeline.details.status.label);
- expect(vm.$el.querySelector('.js-commit-link').textContent.trim()).toEqual(
- mockData.pipeline.commit.short_id,
- );
+ expect(
+ findCommitLink()
+ .text()
+ .trim(),
+ ).toBe(mockData.pipeline.commit.short_id);
- expect(vm.$el.querySelector('.js-commit-link').getAttribute('href')).toEqual(
- mockData.pipeline.commit.commit_path,
- );
+ expect(findCommitLink().attributes('href')).toBe(mockData.pipeline.commit.commit_path);
});
it('should render pipeline graph', () => {
- expect(vm.$el.querySelector('.mr-widget-pipeline-graph')).toBeDefined();
- expect(vm.$el.querySelectorAll('.stage-container').length).toEqual(
- mockData.pipeline.details.stages.length,
- );
+ expect(findPipelineGraph().exists()).toBe(true);
+ expect(findAllPipelineStages().length).toBe(mockData.pipeline.details.stages.length);
});
it('should render coverage information', () => {
- expect(vm.$el.querySelector('.media-body').textContent).toContain(
- `Coverage ${mockData.pipeline.coverage}`,
- );
+ expect(findPipelineCoverage().text()).toMatch(`Coverage ${mockData.pipeline.coverage}%`);
});
it('should render pipeline coverage delta information', () => {
- expect(vm.$el.querySelector('.js-pipeline-coverage-delta.text-danger')).toBeDefined();
- expect(vm.$el.querySelector('.js-pipeline-coverage-delta').textContent).toContain(
- `(${mockData.pipelineCoverageDelta}%)`,
- );
+ expect(findPipelineCoverageDelta().exists()).toBe(true);
+ expect(findPipelineCoverageDelta().text()).toBe(`(${mockData.pipelineCoverageDelta}%)`);
});
});
@@ -192,71 +185,61 @@ describe('MRWidgetPipeline', () => {
const mockCopy = JSON.parse(JSON.stringify(mockData));
delete mockCopy.pipeline.commit;
- vm = mountComponent(Component, {
- pipeline: mockCopy.pipeline,
- hasCi: true,
- ciStatus: 'success',
- troubleshootingDocsPath: 'help',
- });
+ createWrapper({}, mount);
});
it('should render pipeline ID', () => {
- expect(vm.$el.querySelector('.pipeline-id').textContent.trim()).toEqual(
- `#${mockData.pipeline.id}`,
- );
+ expect(
+ findPipelineID()
+ .text()
+ .trim(),
+ ).toBe(`#${mockData.pipeline.id}`);
});
it('should render pipeline status', () => {
- expect(vm.$el.querySelector('.media-body').textContent.trim()).toContain(
- mockData.pipeline.details.status.label,
- );
-
- expect(vm.$el.querySelector('.js-commit-link')).toBeNull();
+ expect(findPipelineInfoContainer().text()).toMatch(mockData.pipeline.details.status.label);
});
it('should render pipeline graph', () => {
- expect(vm.$el.querySelector('.mr-widget-pipeline-graph')).toBeDefined();
- expect(vm.$el.querySelectorAll('.stage-container').length).toEqual(
- mockData.pipeline.details.stages.length,
- );
+ expect(findPipelineGraph().exists()).toBe(true);
+ expect(findAllPipelineStages().length).toBe(mockData.pipeline.details.stages.length);
});
it('should render coverage information', () => {
- expect(vm.$el.querySelector('.media-body').textContent).toContain(
- `Coverage ${mockData.pipeline.coverage}`,
- );
+ expect(findPipelineCoverage().text()).toMatch(`Coverage ${mockData.pipeline.coverage}%`);
});
});
describe('without coverage', () => {
- it('should not render a coverage', () => {
+ beforeEach(() => {
const mockCopy = JSON.parse(JSON.stringify(mockData));
delete mockCopy.pipeline.coverage;
- vm = mountComponent(Component, {
- pipeline: mockCopy.pipeline,
- hasCi: true,
- ciStatus: 'success',
- troubleshootingDocsPath: 'help',
- });
+ createWrapper(
+ {
+ pipeline: mockCopy.pipeline,
+ },
+ mount,
+ );
+ });
- expect(vm.$el.querySelector('.media-body').textContent).not.toContain('Coverage');
+ it('should not render a coverage component', () => {
+ expect(findPipelineCoverage().exists()).toBe(false);
});
});
describe('without a pipeline graph', () => {
- it('should not render a pipeline graph', () => {
+ beforeEach(() => {
const mockCopy = JSON.parse(JSON.stringify(mockData));
delete mockCopy.pipeline.details.stages;
- vm = mountComponent(Component, {
+ createWrapper({
pipeline: mockCopy.pipeline,
- hasCi: true,
- ciStatus: 'success',
- troubleshootingDocsPath: 'help',
});
+ });
- expect(vm.$el.querySelector('.js-mini-pipeline-graph')).toEqual(null);
+ it('should not render a pipeline graph', () => {
+ expect(findPipelineGraph().exists()).toBe(false);
});
});
@@ -273,11 +256,8 @@ describe('MRWidgetPipeline', () => {
});
const factory = () => {
- vm = mountComponent(Component, {
+ createWrapper({
pipeline,
- hasCi: true,
- ciStatus: 'success',
- troubleshootingDocsPath: 'help',
sourceBranchLink: mockData.source_branch_link,
});
};
@@ -289,7 +269,7 @@ describe('MRWidgetPipeline', () => {
factory();
const expected = `Pipeline #${pipeline.id} ${pipeline.details.status.label} for ${pipeline.commit.short_id} on ${mockData.source_branch_link}`;
- const actual = trimText(vm.$el.querySelector('.js-pipeline-info-container').innerText);
+ const actual = trimText(findPipelineInfoContainer().text());
expect(actual).toBe(expected);
});
@@ -302,7 +282,7 @@ describe('MRWidgetPipeline', () => {
factory();
const expected = `Pipeline #${pipeline.id} ${pipeline.details.status.label} for ${pipeline.commit.short_id}`;
- const actual = trimText(vm.$el.querySelector('.js-pipeline-info-container').innerText);
+ const actual = trimText(findPipelineInfoContainer().text());
expect(actual).toBe(expected);
});
@@ -316,7 +296,7 @@ describe('MRWidgetPipeline', () => {
factory();
const expected = `Detached merge request pipeline #${pipeline.id} ${pipeline.details.status.label} for ${pipeline.commit.short_id}`;
- const actual = trimText(vm.$el.querySelector('.js-pipeline-info-container').innerText);
+ const actual = trimText(findPipelineInfoContainer().text());
expect(actual).toBe(expected);
});
diff --git a/spec/frontend/vue_mr_widget/components/mr_widget_suggest_pipeline_spec.js b/spec/frontend/vue_mr_widget/components/mr_widget_suggest_pipeline_spec.js
index 8b0253dc01a..d6c996f7501 100644
--- a/spec/frontend/vue_mr_widget/components/mr_widget_suggest_pipeline_spec.js
+++ b/spec/frontend/vue_mr_widget/components/mr_widget_suggest_pipeline_spec.js
@@ -1,37 +1,44 @@
import { mount } from '@vue/test-utils';
-import { GlLink } from '@gitlab/ui';
+import { GlLink, GlSprintf } from '@gitlab/ui';
import suggestPipelineComponent from '~/vue_merge_request_widget/components/mr_widget_suggest_pipeline.vue';
-import stubChildren from 'helpers/stub_children';
-import PipelineTourState from '~/vue_merge_request_widget/components/states/mr_widget_pipeline_tour.vue';
import MrWidgetIcon from '~/vue_merge_request_widget/components/mr_widget_icon.vue';
import { mockTracking, triggerEvent, unmockTracking } from 'helpers/tracking_helper';
+import { popoverProps, iconName } from './pipeline_tour_mock_data';
-describe('MRWidgetHeader', () => {
+describe('MRWidgetSuggestPipeline', () => {
let wrapper;
- const pipelinePath = '/foo/bar/add/pipeline/path';
- const pipelineSvgPath = '/foo/bar/pipeline/svg/path';
- const humanAccess = 'maintainer';
- const iconName = 'status_notfound';
+ let trackingSpy;
+
+ const mockTrackingOnWrapper = () => {
+ unmockTracking();
+ trackingSpy = mockTracking('_category_', wrapper.element, jest.spyOn);
+ };
beforeEach(() => {
+ document.body.dataset.page = 'projects:merge_requests:show';
+ trackingSpy = mockTracking('_category_', undefined, jest.spyOn);
+
wrapper = mount(suggestPipelineComponent, {
- propsData: { pipelinePath, pipelineSvgPath, humanAccess },
+ propsData: popoverProps,
stubs: {
- ...stubChildren(PipelineTourState),
+ GlSprintf,
},
});
});
afterEach(() => {
wrapper.destroy();
+ unmockTracking();
});
describe('template', () => {
+ const findOkBtn = () => wrapper.find('[data-testid="ok"]');
+
it('renders add pipeline file link', () => {
const link = wrapper.find(GlLink);
expect(link.exists()).toBe(true);
- expect(link.attributes().href).toBe(pipelinePath);
+ expect(link.attributes().href).toBe(popoverProps.pipelinePath);
});
it('renders the expected text', () => {
@@ -51,25 +58,60 @@ describe('MRWidgetHeader', () => {
);
});
+ it('renders the show me how button', () => {
+ const button = findOkBtn();
+
+ expect(button.exists()).toBe(true);
+ expect(button.classes('btn-info')).toEqual(true);
+ expect(button.attributes('href')).toBe(popoverProps.pipelinePath);
+ });
+
+ it('renders the help link', () => {
+ const link = wrapper.find('[data-testid="help"]');
+
+ expect(link.exists()).toBe(true);
+ expect(link.attributes('href')).toBe(wrapper.vm.$options.helpURL);
+ });
+
+ it('renders the empty pipelines image', () => {
+ const image = wrapper.find('[data-testid="pipeline-image"]');
+
+ expect(image.exists()).toBe(true);
+ expect(image.attributes().src).toBe(popoverProps.pipelineSvgPath);
+ });
+
describe('tracking', () => {
- let spy;
+ it('send event for basic view of the suggest pipeline widget', () => {
+ const expectedCategory = undefined;
+ const expectedAction = undefined;
- beforeEach(() => {
- spy = mockTracking('_category_', wrapper.element, jest.spyOn);
+ expect(trackingSpy).toHaveBeenCalledWith(expectedCategory, expectedAction, {
+ label: wrapper.vm.$options.trackLabel,
+ property: popoverProps.humanAccess,
+ });
});
- afterEach(() => {
- unmockTracking();
+ it('send an event when add pipeline link is clicked', () => {
+ mockTrackingOnWrapper();
+ const link = wrapper.find('[data-testid="add-pipeline-link"]');
+ triggerEvent(link.element);
+
+ expect(trackingSpy).toHaveBeenCalledWith('_category_', 'click_link', {
+ label: wrapper.vm.$options.trackLabel,
+ property: popoverProps.humanAccess,
+ value: '30',
+ });
});
it('send an event when ok button is clicked', () => {
- const link = wrapper.find(GlLink);
- triggerEvent(link.element);
+ mockTrackingOnWrapper();
+ const okBtn = findOkBtn();
+ triggerEvent(okBtn.element);
- expect(spy).toHaveBeenCalledWith('_category_', 'click_link', {
- label: 'no_pipeline_noticed',
- property: humanAccess,
- value: '30',
+ expect(trackingSpy).toHaveBeenCalledWith('_category_', 'click_button', {
+ label: wrapper.vm.$options.trackLabel,
+ property: popoverProps.humanAccess,
+ value: '10',
});
});
});
diff --git a/spec/frontend/vue_mr_widget/components/mr_widget_terraform_plan_spec.js b/spec/frontend/vue_mr_widget/components/mr_widget_terraform_plan_spec.js
deleted file mode 100644
index 62c5c8e8531..00000000000
--- a/spec/frontend/vue_mr_widget/components/mr_widget_terraform_plan_spec.js
+++ /dev/null
@@ -1,107 +0,0 @@
-import { GlLink, GlLoadingIcon, GlSprintf } from '@gitlab/ui';
-import { shallowMount } from '@vue/test-utils';
-import axios from '~/lib/utils/axios_utils';
-import MockAdapter from 'axios-mock-adapter';
-import MrWidgetTerraformPlan from '~/vue_merge_request_widget/components/mr_widget_terraform_plan.vue';
-import Poll from '~/lib/utils/poll';
-
-const plan = {
- create: 10,
- update: 20,
- delete: 30,
- job_path: '/path/to/ci/logs',
-};
-
-describe('MrWidgetTerraformPlan', () => {
- let mock;
- let wrapper;
-
- const propsData = { endpoint: '/path/to/terraform/report.json' };
-
- const mockPollingApi = (response, body, header) => {
- mock.onGet(propsData.endpoint).reply(response, body, header);
- };
-
- const mountWrapper = () => {
- wrapper = shallowMount(MrWidgetTerraformPlan, { propsData });
- return axios.waitForAll();
- };
-
- beforeEach(() => {
- mock = new MockAdapter(axios);
- });
-
- afterEach(() => {
- wrapper.destroy();
- mock.restore();
- });
-
- describe('loading poll', () => {
- beforeEach(() => {
- mockPollingApi(200, { '123': plan }, {});
-
- return mountWrapper().then(() => {
- wrapper.setData({ loading: true });
- return wrapper.vm.$nextTick();
- });
- });
-
- it('Diplays loading icon when loading is true', () => {
- expect(wrapper.find(GlLoadingIcon).exists()).toBe(true);
-
- expect(wrapper.find(GlSprintf).exists()).toBe(false);
-
- expect(wrapper.text()).not.toContain(
- 'A terraform report was generated in your pipelines. Changes are unknown',
- );
- });
- });
-
- describe('successful poll', () => {
- let pollRequest;
- let pollStop;
-
- beforeEach(() => {
- pollRequest = jest.spyOn(Poll.prototype, 'makeRequest');
- pollStop = jest.spyOn(Poll.prototype, 'stop');
-
- mockPollingApi(200, { '123': plan }, {});
-
- return mountWrapper();
- });
-
- afterEach(() => {
- pollRequest.mockRestore();
- pollStop.mockRestore();
- });
-
- it('content change text', () => {
- expect(wrapper.find(GlSprintf).exists()).toBe(true);
- });
-
- it('renders button when url is found', () => {
- expect(wrapper.find(GlLink).exists()).toBe(true);
- });
-
- it('does not make additional requests after poll is successful', () => {
- expect(pollRequest).toHaveBeenCalledTimes(1);
- expect(pollStop).toHaveBeenCalledTimes(1);
- });
- });
-
- describe('polling fails', () => {
- beforeEach(() => {
- mockPollingApi(500, null, {});
- return mountWrapper();
- });
-
- it('does not display changes text when api fails', () => {
- expect(wrapper.text()).toContain(
- 'A terraform report was generated in your pipelines. Changes are unknown',
- );
-
- expect(wrapper.find('.js-terraform-report-link').exists()).toBe(false);
- expect(wrapper.find(GlLink).exists()).toBe(false);
- });
- });
-});
diff --git a/spec/frontend/vue_mr_widget/components/pipeline_tour_mock_data.js b/spec/frontend/vue_mr_widget/components/pipeline_tour_mock_data.js
new file mode 100644
index 00000000000..c749c434079
--- /dev/null
+++ b/spec/frontend/vue_mr_widget/components/pipeline_tour_mock_data.js
@@ -0,0 +1,7 @@
+export const popoverProps = {
+ pipelinePath: '/foo/bar/add/pipeline/path',
+ pipelineSvgPath: 'assets/illustrations/something.svg',
+ humanAccess: 'maintainer',
+};
+
+export const iconName = 'status_notfound';
diff --git a/spec/frontend/vue_mr_widget/components/states/mr_widget_auto_merge_enabled_spec.js b/spec/frontend/vue_mr_widget/components/states/mr_widget_auto_merge_enabled_spec.js
index e2caa6e8092..ae0f605c419 100644
--- a/spec/frontend/vue_mr_widget/components/states/mr_widget_auto_merge_enabled_spec.js
+++ b/spec/frontend/vue_mr_widget/components/states/mr_widget_auto_merge_enabled_spec.js
@@ -8,6 +8,7 @@ import { MWPS_MERGE_STRATEGY } from '~/vue_merge_request_widget/constants';
describe('MRWidgetAutoMergeEnabled', () => {
let vm;
+ let oldWindowGl;
const targetBranchPath = '/foo/bar';
const targetBranch = 'foo';
const sha = '1EA2EZ34';
@@ -16,6 +17,13 @@ describe('MRWidgetAutoMergeEnabled', () => {
const Component = Vue.extend(autoMergeEnabledComponent);
jest.spyOn(eventHub, '$emit').mockImplementation(() => {});
+ oldWindowGl = window.gl;
+ window.gl = {
+ mrWidgetData: {
+ defaultAvatarUrl: 'no_avatar.png',
+ },
+ };
+
vm = mountComponent(Component, {
mr: {
shouldRemoveSourceBranch: false,
@@ -35,6 +43,7 @@ describe('MRWidgetAutoMergeEnabled', () => {
afterEach(() => {
vm.$destroy();
+ window.gl = oldWindowGl;
});
describe('computed', () => {
diff --git a/spec/frontend/vue_mr_widget/components/states/mr_widget_checking_spec.js b/spec/frontend/vue_mr_widget/components/states/mr_widget_checking_spec.js
index 56d55c9afac..afe6bd0e767 100644
--- a/spec/frontend/vue_mr_widget/components/states/mr_widget_checking_spec.js
+++ b/spec/frontend/vue_mr_widget/components/states/mr_widget_checking_spec.js
@@ -25,7 +25,7 @@ describe('MRWidgetChecking', () => {
it('renders information about merging', () => {
expect(vm.$el.querySelector('.media-body').textContent.trim()).toEqual(
- 'Checking ability to merge automatically…',
+ 'Checking if merge request can be merged…',
);
});
});
diff --git a/spec/frontend/vue_mr_widget/components/states/mr_widget_pipeline_tour_spec.js b/spec/frontend/vue_mr_widget/components/states/mr_widget_pipeline_tour_spec.js
deleted file mode 100644
index e8f95e099cc..00000000000
--- a/spec/frontend/vue_mr_widget/components/states/mr_widget_pipeline_tour_spec.js
+++ /dev/null
@@ -1,143 +0,0 @@
-import { shallowMount } from '@vue/test-utils';
-import { GlPopover } from '@gitlab/ui';
-import Cookies from 'js-cookie';
-import { mockTracking, triggerEvent, unmockTracking } from 'helpers/tracking_helper';
-import pipelineTourState from '~/vue_merge_request_widget/components/states/mr_widget_pipeline_tour.vue';
-import { popoverProps, cookieKey } from './pipeline_tour_mock_data';
-
-describe('MRWidgetPipelineTour', () => {
- let wrapper;
-
- afterEach(() => {
- wrapper.destroy();
- });
-
- describe('template', () => {
- describe(`when ${cookieKey} cookie is set`, () => {
- beforeEach(() => {
- Cookies.set(cookieKey, true);
- wrapper = shallowMount(pipelineTourState, {
- propsData: popoverProps,
- });
- });
-
- it('does not render the popover', () => {
- const popover = wrapper.find(GlPopover);
-
- expect(popover.exists()).toBe(false);
- });
-
- describe('tracking', () => {
- let trackingSpy;
-
- beforeEach(() => {
- trackingSpy = mockTracking('_category_', wrapper.element, jest.spyOn);
- });
-
- afterEach(() => {
- unmockTracking();
- });
- it('does not call tracking', () => {
- expect(trackingSpy).not.toHaveBeenCalled();
- });
- });
- });
-
- describe(`when ${cookieKey} cookie is not set`, () => {
- const findOkBtn = () => wrapper.find({ ref: 'ok' });
- const findDismissBtn = () => wrapper.find({ ref: 'no-thanks' });
-
- beforeEach(() => {
- Cookies.remove(cookieKey);
- wrapper = shallowMount(pipelineTourState, {
- propsData: popoverProps,
- });
- });
-
- it('renders the popover', () => {
- const popover = wrapper.find(GlPopover);
-
- expect(popover.exists()).toBe(true);
- });
-
- it('renders the show me how button', () => {
- const button = findOkBtn();
-
- expect(button.exists()).toBe(true);
- expect(button.attributes().category).toBe('primary');
- });
-
- it('renders the dismiss button', () => {
- const button = findDismissBtn();
-
- expect(button.exists()).toBe(true);
- expect(button.attributes().category).toBe('secondary');
- });
-
- it('renders the empty pipelines image', () => {
- const image = wrapper.find('img');
-
- expect(image.exists()).toBe(true);
- expect(image.attributes().src).toBe(popoverProps.pipelineSvgPath);
- });
-
- describe('tracking', () => {
- let trackingSpy;
-
- beforeEach(() => {
- trackingSpy = mockTracking('_category_', wrapper.element, jest.spyOn);
- });
-
- afterEach(() => {
- unmockTracking();
- });
-
- it('send event for basic view of popover', () => {
- document.body.dataset.page = 'projects:merge_requests:show';
-
- wrapper.vm.trackOnShow();
-
- expect(trackingSpy).toHaveBeenCalledWith(undefined, undefined, {
- label: popoverProps.trackLabel,
- property: popoverProps.humanAccess,
- });
- });
-
- it('send an event when ok button is clicked', () => {
- const okBtn = findOkBtn();
- triggerEvent(okBtn.element);
-
- expect(trackingSpy).toHaveBeenCalledWith('_category_', 'click_button', {
- label: popoverProps.trackLabel,
- property: popoverProps.humanAccess,
- value: '10',
- });
- });
-
- it('send an event when dismiss button is clicked', () => {
- const dismissBtn = findDismissBtn();
- triggerEvent(dismissBtn.element);
-
- expect(trackingSpy).toHaveBeenCalledWith('_category_', 'click_button', {
- label: popoverProps.trackLabel,
- property: popoverProps.humanAccess,
- value: '20',
- });
- });
- });
-
- describe('dismissPopover', () => {
- it('updates popoverDismissed', () => {
- const button = findDismissBtn();
- const popover = wrapper.find(GlPopover);
- button.vm.$emit('click');
-
- return wrapper.vm.$nextTick().then(() => {
- expect(Cookies.get(cookieKey)).toBe('true');
- expect(popover.exists()).toBe(false);
- });
- });
- });
- });
- });
-});
diff --git a/spec/frontend/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js b/spec/frontend/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js
index 1f0d6a7378c..5eb24315ca6 100644
--- a/spec/frontend/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js
+++ b/spec/frontend/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js
@@ -34,6 +34,9 @@ const createTestMr = customConfig => {
ciStatus: null,
sha: '12345678',
squash: false,
+ squashIsEnabledByDefault: false,
+ squashIsReadonly: false,
+ squashIsSelected: false,
commitMessage,
squashCommitMessage,
commitMessageWithDescription,
@@ -694,6 +697,37 @@ describe('ReadyToMerge', () => {
expect(findCheckboxElement().exists()).toBeFalsy();
});
+
+ describe('squash options', () => {
+ it.each`
+ squashState | state | prop | expectation
+ ${'squashIsReadonly'} | ${'enabled'} | ${'isDisabled'} | ${false}
+ ${'squashIsSelected'} | ${'selected'} | ${'value'} | ${false}
+ ${'squashIsSelected'} | ${'unselected'} | ${'value'} | ${false}
+ `(
+ 'is $state when squashIsReadonly returns $expectation ',
+ ({ squashState, prop, expectation }) => {
+ createLocalComponent({
+ mr: { commitsCount: 2, enableSquashBeforeMerge: true, [squashState]: expectation },
+ });
+
+ expect(findCheckboxElement().props(prop)).toBe(expectation);
+ },
+ );
+
+ it('is not rendered for "Do not allow" option', () => {
+ createLocalComponent({
+ mr: {
+ commitsCount: 2,
+ enableSquashBeforeMerge: true,
+ squashIsReadonly: true,
+ squashIsSelected: false,
+ },
+ });
+
+ expect(findCheckboxElement().exists()).toBe(false);
+ });
+ });
});
describe('commits count collapsible header', () => {
@@ -709,7 +743,7 @@ describe('ReadyToMerge', () => {
mr: {
ffOnlyEnabled: true,
enableSquashBeforeMerge: true,
- squash: true,
+ squashIsSelected: true,
commitsCount: 2,
},
});
@@ -803,7 +837,7 @@ describe('ReadyToMerge', () => {
createLocalComponent({
mr: {
ffOnlyEnabled: true,
- squash: true,
+ squashIsSelected: true,
enableSquashBeforeMerge: true,
commitsCount: 2,
},
@@ -824,7 +858,7 @@ describe('ReadyToMerge', () => {
createLocalComponent({
mr: {
commitsCount: 2,
- squash: true,
+ squashIsSelected: true,
enableSquashBeforeMerge: true,
},
});
@@ -854,7 +888,7 @@ describe('ReadyToMerge', () => {
createLocalComponent({
mr: {
commitsCount: 2,
- squash: true,
+ squashIsSelected: true,
enableSquashBeforeMerge: true,
},
});
@@ -872,7 +906,7 @@ describe('ReadyToMerge', () => {
it('should be rendered if squash is enabled and there is more than 1 commit', () => {
createLocalComponent({
- mr: { enableSquashBeforeMerge: true, squash: true, commitsCount: 2 },
+ mr: { enableSquashBeforeMerge: true, squashIsSelected: true, commitsCount: 2 },
});
expect(findCommitDropdownElement().exists()).toBeTruthy();
diff --git a/spec/frontend/vue_mr_widget/components/states/mr_widget_squash_before_merge_spec.js b/spec/frontend/vue_mr_widget/components/states/mr_widget_squash_before_merge_spec.js
index b70d580ed04..1542b0939aa 100644
--- a/spec/frontend/vue_mr_widget/components/states/mr_widget_squash_before_merge_spec.js
+++ b/spec/frontend/vue_mr_widget/components/states/mr_widget_squash_before_merge_spec.js
@@ -19,6 +19,8 @@ describe('Squash before merge component', () => {
wrapper.destroy();
});
+ const findLabel = () => wrapper.find('[data-testid="squashLabel"]');
+
describe('checkbox', () => {
const findCheckbox = () => wrapper.find('.js-squash-checkbox');
@@ -63,6 +65,46 @@ describe('Squash before merge component', () => {
});
});
+ describe('label', () => {
+ describe.each`
+ isDisabled | expectation
+ ${true} | ${'grays out text if it is true'}
+ ${false} | ${'does not gray out text if it is false'}
+ `('isDisabled prop', ({ isDisabled, expectation }) => {
+ beforeEach(() => {
+ createComponent({
+ value: false,
+ isDisabled,
+ });
+ });
+
+ it(expectation, () => {
+ expect(findLabel().classes('gl-text-gray-600')).toBe(isDisabled);
+ });
+ });
+ });
+
+ describe('tooltip', () => {
+ const tooltipTitle = () => findLabel().element.dataset.title;
+
+ it('does not render when isDisabled is false', () => {
+ createComponent({
+ value: true,
+ isDisabled: false,
+ });
+ expect(tooltipTitle()).toBeUndefined();
+ });
+
+ it('display message when when isDisabled is true', () => {
+ createComponent({
+ value: true,
+ isDisabled: true,
+ });
+
+ expect(tooltipTitle()).toBe('Required in this project.');
+ });
+ });
+
describe('about link', () => {
it('is not rendered if no help path is passed', () => {
createComponent({
diff --git a/spec/frontend/vue_mr_widget/components/states/pipeline_tour_mock_data.js b/spec/frontend/vue_mr_widget/components/states/pipeline_tour_mock_data.js
deleted file mode 100644
index 39bc89e459c..00000000000
--- a/spec/frontend/vue_mr_widget/components/states/pipeline_tour_mock_data.js
+++ /dev/null
@@ -1,10 +0,0 @@
-export const popoverProps = {
- pipelinePath: '/foo/bar/add/pipeline/path',
- pipelineSvgPath: 'assets/illustrations/something.svg',
- humanAccess: 'maintainer',
- popoverTarget: 'suggest-popover',
- popoverContainer: 'suggest-pipeline',
- trackLabel: 'some_tracking_label',
-};
-
-export const cookieKey = 'suggest_pipeline_dismissed';
diff --git a/spec/frontend/vue_mr_widget/components/terraform/mock_data.js b/spec/frontend/vue_mr_widget/components/terraform/mock_data.js
new file mode 100644
index 00000000000..ae280146c22
--- /dev/null
+++ b/spec/frontend/vue_mr_widget/components/terraform/mock_data.js
@@ -0,0 +1,31 @@
+export const invalidPlanWithName = {
+ job_name: 'Invalid Plan',
+ job_path: '/path/to/ci/logs/1',
+ tf_report_error: 'api_error',
+};
+
+export const invalidPlanWithoutName = {
+ tf_report_error: 'invalid_json_format',
+};
+
+export const validPlanWithName = {
+ create: 10,
+ update: 20,
+ delete: 30,
+ job_name: 'Valid Plan',
+ job_path: '/path/to/ci/logs/1',
+};
+
+export const validPlanWithoutName = {
+ create: 10,
+ update: 20,
+ delete: 30,
+ job_path: '/path/to/ci/logs/1',
+};
+
+export const plans = {
+ invalid_plan_one: invalidPlanWithName,
+ invalid_plan_two: invalidPlanWithName,
+ valid_plan_one: validPlanWithName,
+ valid_plan_two: validPlanWithoutName,
+};
diff --git a/spec/frontend/vue_mr_widget/components/terraform/mr_widget_terraform_container_spec.js b/spec/frontend/vue_mr_widget/components/terraform/mr_widget_terraform_container_spec.js
new file mode 100644
index 00000000000..be43f10c03e
--- /dev/null
+++ b/spec/frontend/vue_mr_widget/components/terraform/mr_widget_terraform_container_spec.js
@@ -0,0 +1,172 @@
+import { GlSkeletonLoading, GlSprintf } from '@gitlab/ui';
+import { invalidPlanWithName, plans, validPlanWithName } from './mock_data';
+import { shallowMount } from '@vue/test-utils';
+import axios from '~/lib/utils/axios_utils';
+import MockAdapter from 'axios-mock-adapter';
+import MrWidgetExpanableSection from '~/vue_merge_request_widget/components/mr_widget_expandable_section.vue';
+import MrWidgetTerraformContainer from '~/vue_merge_request_widget/components/terraform/mr_widget_terraform_container.vue';
+import Poll from '~/lib/utils/poll';
+import TerraformPlan from '~/vue_merge_request_widget/components/terraform/terraform_plan.vue';
+
+describe('MrWidgetTerraformConainer', () => {
+ let mock;
+ let wrapper;
+
+ const propsData = { endpoint: '/path/to/terraform/report.json' };
+
+ const findHeader = () => wrapper.find('[data-testid="terraform-header-text"]');
+ const findPlans = () => wrapper.findAll(TerraformPlan).wrappers.map(x => x.props('plan'));
+
+ const mockPollingApi = (response, body, header) => {
+ mock.onGet(propsData.endpoint).reply(response, body, header);
+ };
+
+ const mountWrapper = () => {
+ wrapper = shallowMount(MrWidgetTerraformContainer, {
+ propsData,
+ stubs: { MrWidgetExpanableSection, GlSprintf },
+ });
+ return axios.waitForAll();
+ };
+
+ beforeEach(() => {
+ mock = new MockAdapter(axios);
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ mock.restore();
+ });
+
+ describe('when data is loading', () => {
+ beforeEach(() => {
+ mockPollingApi(200, plans, {});
+
+ return mountWrapper().then(() => {
+ wrapper.setData({ loading: true });
+ return wrapper.vm.$nextTick();
+ });
+ });
+
+ it('diplays loading skeleton', () => {
+ expect(wrapper.contains(GlSkeletonLoading)).toBe(true);
+ expect(wrapper.contains(MrWidgetExpanableSection)).toBe(false);
+ });
+ });
+
+ describe('when data has finished loading', () => {
+ beforeEach(() => {
+ mockPollingApi(200, plans, {});
+ return mountWrapper();
+ });
+
+ it('displays terraform content', () => {
+ expect(wrapper.contains(GlSkeletonLoading)).toBe(false);
+ expect(wrapper.contains(MrWidgetExpanableSection)).toBe(true);
+ expect(findPlans()).toEqual(Object.values(plans));
+ });
+
+ describe('when data includes one invalid plan', () => {
+ beforeEach(() => {
+ const invalidPlanGroup = { bad_plan: invalidPlanWithName };
+ mockPollingApi(200, invalidPlanGroup, {});
+ return mountWrapper();
+ });
+
+ it('displays header text for one invalid plan', () => {
+ expect(findHeader().text()).toBe('1 Terraform report failed to generate');
+ });
+ });
+
+ describe('when data includes multiple invalid plans', () => {
+ beforeEach(() => {
+ const invalidPlanGroup = {
+ bad_plan_one: invalidPlanWithName,
+ bad_plan_two: invalidPlanWithName,
+ };
+
+ mockPollingApi(200, invalidPlanGroup, {});
+ return mountWrapper();
+ });
+
+ it('displays header text for multiple invalid plans', () => {
+ expect(findHeader().text()).toBe('2 Terraform reports failed to generate');
+ });
+ });
+
+ describe('when data includes one valid plan', () => {
+ beforeEach(() => {
+ const validPlanGroup = { valid_plan: validPlanWithName };
+ mockPollingApi(200, validPlanGroup, {});
+ return mountWrapper();
+ });
+
+ it('displays header text for one valid plans', () => {
+ expect(findHeader().text()).toBe('1 Terraform report was generated in your pipelines');
+ });
+ });
+
+ describe('when data includes multiple valid plans', () => {
+ beforeEach(() => {
+ const validPlanGroup = {
+ valid_plan_one: validPlanWithName,
+ valid_plan_two: validPlanWithName,
+ };
+ mockPollingApi(200, validPlanGroup, {});
+ return mountWrapper();
+ });
+
+ it('displays header text for multiple valid plans', () => {
+ expect(findHeader().text()).toBe('2 Terraform reports were generated in your pipelines');
+ });
+ });
+ });
+
+ describe('polling', () => {
+ let pollRequest;
+ let pollStop;
+
+ beforeEach(() => {
+ pollRequest = jest.spyOn(Poll.prototype, 'makeRequest');
+ pollStop = jest.spyOn(Poll.prototype, 'stop');
+ });
+
+ afterEach(() => {
+ pollRequest.mockRestore();
+ pollStop.mockRestore();
+ });
+
+ describe('successful poll', () => {
+ beforeEach(() => {
+ mockPollingApi(200, plans, {});
+
+ return mountWrapper();
+ });
+
+ it('does not make additional requests after poll is successful', () => {
+ expect(pollRequest).toHaveBeenCalledTimes(1);
+ expect(pollStop).toHaveBeenCalledTimes(1);
+ });
+ });
+
+ describe('polling fails', () => {
+ beforeEach(() => {
+ mockPollingApi(500, null, {});
+ return mountWrapper();
+ });
+
+ it('stops loading', () => {
+ expect(wrapper.contains(GlSkeletonLoading)).toBe(false);
+ });
+
+ it('generates one broken plan', () => {
+ expect(findPlans()).toEqual([{ tf_report_error: 'api_error' }]);
+ });
+
+ it('does not make additional requests after poll is unsuccessful', () => {
+ expect(pollRequest).toHaveBeenCalledTimes(1);
+ expect(pollStop).toHaveBeenCalledTimes(1);
+ });
+ });
+ });
+});
diff --git a/spec/frontend/vue_mr_widget/components/terraform/terraform_plan_spec.js b/spec/frontend/vue_mr_widget/components/terraform/terraform_plan_spec.js
new file mode 100644
index 00000000000..cc68ba0d9df
--- /dev/null
+++ b/spec/frontend/vue_mr_widget/components/terraform/terraform_plan_spec.js
@@ -0,0 +1,95 @@
+import { GlLink, GlSprintf } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
+import TerraformPlan from '~/vue_merge_request_widget/components/terraform/terraform_plan.vue';
+import {
+ invalidPlanWithName,
+ invalidPlanWithoutName,
+ validPlanWithName,
+ validPlanWithoutName,
+} from './mock_data';
+
+describe('TerraformPlan', () => {
+ let wrapper;
+
+ const findIcon = () => wrapper.find('[data-testid="change-type-icon"]');
+ const findLogButton = () => wrapper.find('[data-testid="terraform-report-link"]');
+
+ const mountWrapper = propsData => {
+ wrapper = shallowMount(TerraformPlan, { stubs: { GlLink, GlSprintf }, propsData });
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ describe('valid plan with job_name', () => {
+ beforeEach(() => {
+ mountWrapper({ plan: validPlanWithName });
+ });
+
+ it('displays a document icon', () => {
+ expect(findIcon().attributes('name')).toBe('doc-changes');
+ });
+
+ it('diplays the header text with a name', () => {
+ expect(wrapper.text()).toContain(
+ `The Terraform report ${validPlanWithName.job_name} was generated in your pipelines.`,
+ );
+ });
+
+ it('diplays the reported changes', () => {
+ expect(wrapper.text()).toContain(
+ `Reported Resource Changes: ${validPlanWithName.create} to add, ${validPlanWithName.update} to change, ${validPlanWithName.delete} to delete`,
+ );
+ });
+
+ it('renders button when url is found', () => {
+ expect(findLogButton().exists()).toBe(true);
+ expect(findLogButton().text()).toEqual('View full log');
+ });
+ });
+
+ describe('valid plan without job_name', () => {
+ beforeEach(() => {
+ mountWrapper({ plan: validPlanWithoutName });
+ });
+
+ it('diplays the header text without a name', () => {
+ expect(wrapper.text()).toContain('A Terraform report was generated in your pipelines.');
+ });
+ });
+
+ describe('invalid plan with job_name', () => {
+ beforeEach(() => {
+ mountWrapper({ plan: invalidPlanWithName });
+ });
+
+ it('displays a warning icon', () => {
+ expect(findIcon().attributes('name')).toBe('warning');
+ });
+
+ it('diplays the header text with a name', () => {
+ expect(wrapper.text()).toContain(
+ `The Terraform report ${invalidPlanWithName.job_name} failed to generate.`,
+ );
+ });
+
+ it('diplays generic error since report values are missing', () => {
+ expect(wrapper.text()).toContain('Generating the report caused an error.');
+ });
+ });
+
+ describe('invalid plan with out job_name', () => {
+ beforeEach(() => {
+ mountWrapper({ plan: invalidPlanWithoutName });
+ });
+
+ it('diplays the header text without a name', () => {
+ expect(wrapper.text()).toContain('A Terraform report failed to generate.');
+ });
+
+ it('does not render button because url is missing', () => {
+ expect(findLogButton().exists()).toBe(false);
+ });
+ });
+});
diff --git a/spec/frontend/vue_mr_widget/mock_data.js b/spec/frontend/vue_mr_widget/mock_data.js
index 8ed153658fd..e00456a78b5 100644
--- a/spec/frontend/vue_mr_widget/mock_data.js
+++ b/spec/frontend/vue_mr_widget/mock_data.js
@@ -211,6 +211,15 @@ export default {
can_revert_on_current_merge_request: true,
can_cherry_pick_on_current_merge_request: true,
},
+ codeclimate: {
+ head_path: 'head.json',
+ base_path: 'base.json',
+ },
+ blob_path: {
+ base_path: 'blob_path',
+ head_path: 'blob_path',
+ },
+ codequality_help_path: 'code_quality.html',
target_branch_path: '/root/acets-app/branches/master',
source_branch_path: '/root/acets-app/branches/daaaa',
conflict_resolution_ui_path: '/root/acets-app/-/merge_requests/22/conflicts',
@@ -239,7 +248,8 @@ export default {
commit_change_content_path: '/root/acets-app/-/merge_requests/22/commit_change_content',
merge_commit_path:
'http://localhost:3000/root/acets-app/commit/53027d060246c8f47e4a9310fb332aa52f221775',
- troubleshooting_docs_path: 'help',
+ mr_troubleshooting_docs_path: 'help',
+ ci_troubleshooting_docs_path: 'help2',
merge_request_pipelines_docs_path: '/help/ci/merge_request_pipelines/index.md',
merge_train_when_pipeline_succeeds_docs_path:
'/help/ci/merge_request_pipelines/pipelines_for_merged_results/merge_trains/#startadd-to-merge-train-when-pipeline-succeeds',
@@ -312,7 +322,8 @@ export const mockStore = {
{ id: 0, name: 'prod', status: SUCCESS },
{ id: 1, name: 'prod-docs', status: SUCCESS },
],
- troubleshootingDocsPath: 'troubleshooting-docs-path',
+ mrTroubleshootingDocsPath: 'mr-troubleshooting-docs-path',
+ ciTroubleshootingDocsPath: 'ci-troubleshooting-docs-path',
ciStatus: 'ci-status',
hasCI: true,
exposedArtifactsPath: 'exposed_artifacts.json',
diff --git a/spec/frontend/vue_mr_widget/mr_widget_options_spec.js b/spec/frontend/vue_mr_widget/mr_widget_options_spec.js
index e022f68fdec..93659fa54fb 100644
--- a/spec/frontend/vue_mr_widget/mr_widget_options_spec.js
+++ b/spec/frontend/vue_mr_widget/mr_widget_options_spec.js
@@ -609,6 +609,12 @@ describe('mrWidgetOptions', () => {
});
});
+ describe('code quality widget', () => {
+ it('renders the component', () => {
+ expect(vm.$el.querySelector('.js-codequality-widget')).toExist();
+ });
+ });
+
describe('pipeline for target branch after merge', () => {
describe('with information for target branch pipeline', () => {
beforeEach(done => {
diff --git a/spec/frontend/vue_mr_widget/stores/get_state_key_spec.js b/spec/frontend/vue_mr_widget/stores/get_state_key_spec.js
index e54cd345a37..1cb2c6c669b 100644
--- a/spec/frontend/vue_mr_widget/stores/get_state_key_spec.js
+++ b/spec/frontend/vue_mr_widget/stores/get_state_key_spec.js
@@ -49,14 +49,18 @@ describe('getStateKey', () => {
expect(bound()).toEqual('unresolvedDiscussions');
+ data.work_in_progress = true;
+
+ expect(bound()).toEqual('workInProgress');
+
context.onlyAllowMergeIfPipelineSucceeds = true;
context.isPipelineFailed = true;
expect(bound()).toEqual('pipelineFailed');
- data.work_in_progress = true;
+ context.shouldBeRebased = true;
- expect(bound()).toEqual('workInProgress');
+ expect(bound()).toEqual('rebase');
data.has_conflicts = true;
diff --git a/spec/frontend/vue_shared/components/__snapshots__/awards_list_spec.js.snap b/spec/frontend/vue_shared/components/__snapshots__/awards_list_spec.js.snap
index df4b30f1cb8..19671d425a9 100644
--- a/spec/frontend/vue_shared/components/__snapshots__/awards_list_spec.js.snap
+++ b/spec/frontend/vue_shared/components/__snapshots__/awards_list_spec.js.snap
@@ -18,15 +18,8 @@ exports[`vue_shared/components/awards_list default matches snapshot 1`] = `
<gl-emoji
- data-fallback-src="/assets/emoji/thumbsup-59ec2457ab33e8897261d01a495f6cf5c668d0004807dc541c3b1be5294b1e61.png"
data-name="thumbsup"
- data-unicode-version="6.0"
- title="thumbs up sign"
- >
-
- ðŸ‘
-
- </gl-emoji>
+ />
</span>
@@ -51,15 +44,8 @@ exports[`vue_shared/components/awards_list default matches snapshot 1`] = `
<gl-emoji
- data-fallback-src="/assets/emoji/thumbsdown-5954334e2dae5357312b3d629f10a496c728029e02216f8c8b887f9b51561c61.png"
data-name="thumbsdown"
- data-unicode-version="6.0"
- title="thumbs down sign"
- >
-
- 👎
-
- </gl-emoji>
+ />
</span>
@@ -84,15 +70,8 @@ exports[`vue_shared/components/awards_list default matches snapshot 1`] = `
<gl-emoji
- data-fallback-src="/assets/emoji/smile-14905c372d5bf7719bd727c9efae31a03291acec79801652a23710c6848c5d14.png"
data-name="smile"
- data-unicode-version="6.0"
- title="smiling face with open mouth and smiling eyes"
- >
-
- 😄
-
- </gl-emoji>
+ />
</span>
@@ -117,15 +96,8 @@ exports[`vue_shared/components/awards_list default matches snapshot 1`] = `
<gl-emoji
- data-fallback-src="/assets/emoji/ok_hand-d63002dce3cc3655b67b8765b7c28d370edba0e3758b2329b60e0e61c4d8e78d.png"
data-name="ok_hand"
- data-unicode-version="6.0"
- title="ok hand sign"
- >
-
- 👌
-
- </gl-emoji>
+ />
</span>
@@ -150,15 +122,8 @@ exports[`vue_shared/components/awards_list default matches snapshot 1`] = `
<gl-emoji
- data-fallback-src="/assets/emoji/cactus-2c5c4c35f26c7046fdc002b337e0d939729b33a26980e675950f9934c91e40fd.png"
data-name="cactus"
- data-unicode-version="6.0"
- title="cactus"
- >
-
- 🌵
-
- </gl-emoji>
+ />
</span>
@@ -183,15 +148,8 @@ exports[`vue_shared/components/awards_list default matches snapshot 1`] = `
<gl-emoji
- data-fallback-src="/assets/emoji/a-bddbb39e8a1d35d42b7c08e7d47f63988cb4d8614b79f74e70b9c67c221896cc.png"
data-name="a"
- data-unicode-version="6.0"
- title="negative squared latin capital letter a"
- >
-
- 🅰
-
- </gl-emoji>
+ />
</span>
@@ -216,15 +174,8 @@ exports[`vue_shared/components/awards_list default matches snapshot 1`] = `
<gl-emoji
- data-fallback-src="/assets/emoji/b-722f9db9442e7c0fc0d0ac0f5291fbf47c6a0ac4d8abd42e97957da705fb82bf.png"
data-name="b"
- data-unicode-version="6.0"
- title="negative squared latin capital letter b"
- >
-
- 🅱
-
- </gl-emoji>
+ />
</span>
diff --git a/spec/frontend/vue_shared/components/file_icon_spec.js b/spec/frontend/vue_shared/components/file_icon_spec.js
index 5a385eee60c..adf0da21f9f 100644
--- a/spec/frontend/vue_shared/components/file_icon_spec.js
+++ b/spec/frontend/vue_shared/components/file_icon_spec.js
@@ -1,12 +1,14 @@
import { shallowMount } from '@vue/test-utils';
import { GlLoadingIcon, GlIcon } from '@gitlab/ui';
import FileIcon from '~/vue_shared/components/file_icon.vue';
+import { FILE_SYMLINK_MODE } from '~/vue_shared/constants';
describe('File Icon component', () => {
let wrapper;
- const findIcon = () => wrapper.find('svg');
+ const findSvgIcon = () => wrapper.find('svg');
+ const findGlIcon = () => wrapper.find(GlIcon);
const getIconName = () =>
- findIcon()
+ findSvgIcon()
.find('use')
.element.getAttribute('xlink:href')
.replace(`${gon.sprite_file_icons}#`, '');
@@ -27,7 +29,7 @@ describe('File Icon component', () => {
});
expect(wrapper.element.tagName).toEqual('SPAN');
- expect(findIcon().exists()).toBeDefined();
+ expect(findSvgIcon().exists()).toBeDefined();
});
it.each`
@@ -46,8 +48,8 @@ describe('File Icon component', () => {
folder: true,
});
- expect(findIcon().exists()).toBe(false);
- expect(wrapper.find(GlIcon).classes()).toContain('folder-icon');
+ expect(findSvgIcon().exists()).toBe(false);
+ expect(findGlIcon().classes()).toContain('folder-icon');
});
it('should render a loading icon', () => {
@@ -66,8 +68,19 @@ describe('File Icon component', () => {
cssClasses: 'extraclasses',
size,
});
+ const classes = findSvgIcon().classes();
- expect(findIcon().classes()).toContain(`s${size}`);
- expect(findIcon().classes()).toContain('extraclasses');
+ expect(classes).toContain(`s${size}`);
+ expect(classes).toContain('extraclasses');
+ });
+
+ it('should render a symlink icon', () => {
+ createComponent({
+ fileName: 'anything',
+ fileMode: FILE_SYMLINK_MODE,
+ });
+
+ expect(findSvgIcon().exists()).toBe(false);
+ expect(findGlIcon().attributes('name')).toBe('symlink');
});
});
diff --git a/spec/frontend/vue_shared/components/filtered_search_bar/filtered_search_bar_root_spec.js b/spec/frontend/vue_shared/components/filtered_search_bar/filtered_search_bar_root_spec.js
index eded5b87abc..05508d14209 100644
--- a/spec/frontend/vue_shared/components/filtered_search_bar/filtered_search_bar_root_spec.js
+++ b/spec/frontend/vue_shared/components/filtered_search_bar/filtered_search_bar_root_spec.js
@@ -13,7 +13,7 @@ import { SortDirection } from '~/vue_shared/components/filtered_search_bar/const
import RecentSearchesStore from '~/filtered_search/stores/recent_searches_store';
import RecentSearchesService from '~/filtered_search/services/recent_searches_service';
-import { mockAvailableTokens, mockSortOptions } from './mock_data';
+import { mockAvailableTokens, mockSortOptions, mockHistoryItems } from './mock_data';
const createComponent = ({
namespace = 'gitlab-org/gitlab-test',
@@ -53,11 +53,17 @@ describe('FilteredSearchBarRoot', () => {
describe('computed', () => {
describe('tokenSymbols', () => {
- it('returns array of map containing type and symbols from `tokens` prop', () => {
+ it('returns a map containing type and symbols from `tokens` prop', () => {
expect(wrapper.vm.tokenSymbols).toEqual({ author_username: '@' });
});
});
+ describe('tokenTitles', () => {
+ it('returns a map containing type and title from `tokens` prop', () => {
+ expect(wrapper.vm.tokenTitles).toEqual({ author_username: 'Author' });
+ });
+ });
+
describe('sortDirectionIcon', () => {
it('returns string "sort-lowest" when `selectedSortDirection` is "ascending"', () => {
wrapper.setData({
@@ -133,14 +139,6 @@ describe('FilteredSearchBarRoot', () => {
});
});
- describe('getRecentSearches', () => {
- it('returns array of strings representing recent searches', () => {
- wrapper.vm.recentSearchesStore.setRecentSearches(['foo']);
-
- expect(wrapper.vm.getRecentSearches()).toEqual(['foo']);
- });
- });
-
describe('handleSortOptionClick', () => {
it('emits component event `onSort` with selected sort by value', () => {
wrapper.vm.handleSortOptionClick(mockSortOptions[1]);
@@ -172,6 +170,27 @@ describe('FilteredSearchBarRoot', () => {
});
});
+ describe('handleHistoryItemSelected', () => {
+ it('emits `onFilter` event with provided filters param', () => {
+ wrapper.vm.handleHistoryItemSelected(mockHistoryItems[0]);
+
+ expect(wrapper.emitted('onFilter')[0]).toEqual([mockHistoryItems[0]]);
+ });
+ });
+
+ describe('handleClearHistory', () => {
+ it('clears search history from recent searches store', () => {
+ jest.spyOn(wrapper.vm.recentSearchesStore, 'setRecentSearches').mockReturnValue([]);
+ jest.spyOn(wrapper.vm.recentSearchesService, 'save');
+
+ wrapper.vm.handleClearHistory();
+
+ expect(wrapper.vm.recentSearchesStore.setRecentSearches).toHaveBeenCalledWith([]);
+ expect(wrapper.vm.recentSearchesService.save).toHaveBeenCalledWith([]);
+ expect(wrapper.vm.recentSearches).toEqual([]);
+ });
+ });
+
describe('handleFilterSubmit', () => {
const mockFilters = [
{
@@ -186,14 +205,11 @@ describe('FilteredSearchBarRoot', () => {
it('calls `recentSearchesStore.addRecentSearch` with serialized value of provided `filters` param', () => {
jest.spyOn(wrapper.vm.recentSearchesStore, 'addRecentSearch');
- // jest.spyOn(wrapper.vm.recentSearchesService, 'save');
wrapper.vm.handleFilterSubmit(mockFilters);
return wrapper.vm.recentSearchesPromise.then(() => {
- expect(wrapper.vm.recentSearchesStore.addRecentSearch).toHaveBeenCalledWith(
- 'author_username:=@root foo',
- );
+ expect(wrapper.vm.recentSearchesStore.addRecentSearch).toHaveBeenCalledWith(mockFilters);
});
});
@@ -203,9 +219,17 @@ describe('FilteredSearchBarRoot', () => {
wrapper.vm.handleFilterSubmit(mockFilters);
return wrapper.vm.recentSearchesPromise.then(() => {
- expect(wrapper.vm.recentSearchesService.save).toHaveBeenCalledWith([
- 'author_username:=@root foo',
- ]);
+ expect(wrapper.vm.recentSearchesService.save).toHaveBeenCalledWith([mockFilters]);
+ });
+ });
+
+ it('sets `recentSearches` data prop with array of searches', () => {
+ jest.spyOn(wrapper.vm.recentSearchesService, 'save');
+
+ wrapper.vm.handleFilterSubmit(mockFilters);
+
+ return wrapper.vm.recentSearchesPromise.then(() => {
+ expect(wrapper.vm.recentSearches).toEqual([mockFilters]);
});
});
@@ -222,6 +246,7 @@ describe('FilteredSearchBarRoot', () => {
wrapper.setData({
selectedSortOption: mockSortOptions[0],
selectedSortDirection: SortDirection.descending,
+ recentSearches: mockHistoryItems,
});
return wrapper.vm.$nextTick();
@@ -232,6 +257,7 @@ describe('FilteredSearchBarRoot', () => {
expect(glFilteredSearchEl.props('placeholder')).toBe('Filter requirements');
expect(glFilteredSearchEl.props('availableTokens')).toEqual(mockAvailableTokens);
+ expect(glFilteredSearchEl.props('historyItems')).toEqual(mockHistoryItems);
});
it('renders sort dropdown component', () => {
diff --git a/spec/frontend/vue_shared/components/filtered_search_bar/mock_data.js b/spec/frontend/vue_shared/components/filtered_search_bar/mock_data.js
index edc0f119262..7e28c4e11e1 100644
--- a/spec/frontend/vue_shared/components/filtered_search_bar/mock_data.js
+++ b/spec/frontend/vue_shared/components/filtered_search_bar/mock_data.js
@@ -44,6 +44,29 @@ export const mockAuthorToken = {
export const mockAvailableTokens = [mockAuthorToken];
+export const mockHistoryItems = [
+ [
+ {
+ type: 'author_username',
+ value: {
+ data: 'toby',
+ operator: '=',
+ },
+ },
+ 'duo',
+ ],
+ [
+ {
+ type: 'author_username',
+ value: {
+ data: 'root',
+ operator: '=',
+ },
+ },
+ 'si',
+ ],
+];
+
export const mockSortOptions = [
{
id: 1,
diff --git a/spec/frontend/vue_shared/components/filtered_search_bar/tokens/author_token_spec.js b/spec/frontend/vue_shared/components/filtered_search_bar/tokens/author_token_spec.js
index 3650ef79136..45294096eda 100644
--- a/spec/frontend/vue_shared/components/filtered_search_bar/tokens/author_token_spec.js
+++ b/spec/frontend/vue_shared/components/filtered_search_bar/tokens/author_token_spec.js
@@ -11,11 +11,12 @@ import { mockAuthorToken, mockAuthors } from '../mock_data';
jest.mock('~/flash');
-const createComponent = ({ config = mockAuthorToken, value = { data: '' } } = {}) =>
+const createComponent = ({ config = mockAuthorToken, value = { data: '' }, active = false } = {}) =>
mount(AuthorToken, {
propsData: {
config,
value,
+ active,
},
provide: {
portalName: 'fake target',
@@ -51,29 +52,23 @@ describe('AuthorToken', () => {
describe('computed', () => {
describe('currentValue', () => {
it('returns lowercase string for `value.data`', () => {
- wrapper.setProps({
- value: { data: 'FOO' },
- });
+ wrapper = createComponent({ value: { data: 'FOO' } });
- return wrapper.vm.$nextTick(() => {
- expect(wrapper.vm.currentValue).toBe('foo');
- });
+ expect(wrapper.vm.currentValue).toBe('foo');
});
});
describe('activeAuthor', () => {
- it('returns object for currently present `value.data`', () => {
+ it('returns object for currently present `value.data`', async () => {
+ wrapper = createComponent({ value: { data: mockAuthors[0].username } });
+
wrapper.setData({
authors: mockAuthors,
});
- wrapper.setProps({
- value: { data: mockAuthors[0].username },
- });
+ await wrapper.vm.$nextTick();
- return wrapper.vm.$nextTick(() => {
- expect(wrapper.vm.activeAuthor).toEqual(mockAuthors[0]);
- });
+ expect(wrapper.vm.activeAuthor).toEqual(mockAuthors[0]);
});
});
});
diff --git a/spec/frontend/vue_shared/components/gl_modal_vuex_spec.js b/spec/frontend/vue_shared/components/gl_modal_vuex_spec.js
index 8437e68d73c..93f4db5df18 100644
--- a/spec/frontend/vue_shared/components/gl_modal_vuex_spec.js
+++ b/spec/frontend/vue_shared/components/gl_modal_vuex_spec.js
@@ -38,6 +38,9 @@ describe('GlModalVuex', () => {
localVue,
store,
propsData,
+ stubs: {
+ GlModal,
+ },
});
};
@@ -148,4 +151,29 @@ describe('GlModalVuex', () => {
.then(done)
.catch(done.fail);
});
+
+ it.each(['ok', 'cancel'])(
+ 'passes an "%s" handler to the "modal-footer" slot scope',
+ handlerName => {
+ state.isVisible = true;
+
+ const modalFooterSlotContent = jest.fn();
+
+ factory({
+ scopedSlots: {
+ 'modal-footer': modalFooterSlotContent,
+ },
+ });
+
+ const handler = modalFooterSlotContent.mock.calls[0][0][handlerName];
+
+ expect(wrapper.emitted(handlerName)).toBeFalsy();
+ expect(actions.hide).not.toHaveBeenCalled();
+
+ handler();
+
+ expect(actions.hide).toHaveBeenCalledTimes(1);
+ expect(wrapper.emitted(handlerName)).toBeTruthy();
+ },
+ );
});
diff --git a/spec/frontend/vue_shared/components/issue/__snapshots__/issue_warning_spec.js.snap b/spec/frontend/vue_shared/components/issue/__snapshots__/issue_warning_spec.js.snap
deleted file mode 100644
index 49b18d3e106..00000000000
--- a/spec/frontend/vue_shared/components/issue/__snapshots__/issue_warning_spec.js.snap
+++ /dev/null
@@ -1,62 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`Issue Warning Component when issue is confidential but not locked renders information about confidential issue 1`] = `
-<span>
-
- This is a confidential issue.
- People without permission will never get a notification.
-
- <gl-link-stub
- href="confidential-path"
- target="_blank"
- >
-
- Learn more
-
- </gl-link-stub>
-</span>
-`;
-
-exports[`Issue Warning Component when issue is locked and confidential renders information about locked and confidential issue 1`] = `
-<span>
- <span>
- This issue is
- <a
- href=""
- rel="noopener noreferrer"
- target="_blank"
- >
- confidential
- </a>
- and
- <a
- href=""
- rel="noopener noreferrer"
- target="_blank"
- >
- locked
- </a>
- .
- </span>
-
- People without permission will never get a notification and won't be able to comment.
-
-</span>
-`;
-
-exports[`Issue Warning Component when issue is locked but not confidential renders information about locked issue 1`] = `
-<span>
-
- This issue is locked.
- Only project members can comment.
-
- <gl-link-stub
- href="locked-path"
- target="_blank"
- >
-
- Learn more
-
- </gl-link-stub>
-</span>
-`;
diff --git a/spec/frontend/vue_shared/components/issue/issue_assignees_spec.js b/spec/frontend/vue_shared/components/issue/issue_assignees_spec.js
index ca75c55df26..548d4476c0f 100644
--- a/spec/frontend/vue_shared/components/issue/issue_assignees_spec.js
+++ b/spec/frontend/vue_shared/components/issue/issue_assignees_spec.js
@@ -1,7 +1,7 @@
import { shallowMount } from '@vue/test-utils';
import UserAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link.vue';
import IssueAssignees from '~/vue_shared/components/issue/issue_assignees.vue';
-import { mockAssigneesList } from '../../../../javascripts/boards/mock_data';
+import { mockAssigneesList } from 'jest/boards/mock_data';
const TEST_CSS_CLASSES = 'test-classes';
const TEST_MAX_VISIBLE = 4;
diff --git a/spec/frontend/vue_shared/components/issue/issue_milestone_spec.js b/spec/frontend/vue_shared/components/issue/issue_milestone_spec.js
index 90c3fe54901..69d8c1a5918 100644
--- a/spec/frontend/vue_shared/components/issue/issue_milestone_spec.js
+++ b/spec/frontend/vue_shared/components/issue/issue_milestone_spec.js
@@ -4,7 +4,7 @@ import { shallowMount } from '@vue/test-utils';
import IssueMilestone from '~/vue_shared/components/issue/issue_milestone.vue';
import Icon from '~/vue_shared/components/icon.vue';
-import { mockMilestone } from '../../../../javascripts/boards/mock_data';
+import { mockMilestone } from 'jest/boards/mock_data';
const createComponent = (milestone = mockMilestone) => {
const Component = Vue.extend(IssueMilestone);
diff --git a/spec/frontend/vue_shared/components/issue/issue_warning_spec.js b/spec/frontend/vue_shared/components/issue/issue_warning_spec.js
deleted file mode 100644
index 891c70bcb5c..00000000000
--- a/spec/frontend/vue_shared/components/issue/issue_warning_spec.js
+++ /dev/null
@@ -1,105 +0,0 @@
-import { shallowMount } from '@vue/test-utils';
-import IssueWarning from '~/vue_shared/components/issue/issue_warning.vue';
-import Icon from '~/vue_shared/components/icon.vue';
-
-describe('Issue Warning Component', () => {
- let wrapper;
-
- const findIcon = () => wrapper.find(Icon);
- const findLockedBlock = () => wrapper.find({ ref: 'locked' });
- const findConfidentialBlock = () => wrapper.find({ ref: 'confidential' });
- const findLockedAndConfidentialBlock = () => wrapper.find({ ref: 'lockedAndConfidential' });
-
- const createComponent = props => {
- wrapper = shallowMount(IssueWarning, {
- propsData: {
- ...props,
- },
- });
- };
-
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
- describe('when issue is locked but not confidential', () => {
- beforeEach(() => {
- createComponent({
- isLocked: true,
- lockedIssueDocsPath: 'locked-path',
- isConfidential: false,
- });
- });
-
- it('renders information about locked issue', () => {
- expect(findLockedBlock().exists()).toBe(true);
- expect(findLockedBlock().element).toMatchSnapshot();
- });
-
- it('renders warning icon', () => {
- expect(findIcon().exists()).toBe(true);
- });
-
- it('does not render information about locked and confidential issue', () => {
- expect(findLockedAndConfidentialBlock().exists()).toBe(false);
- });
-
- it('does not render information about confidential issue', () => {
- expect(findConfidentialBlock().exists()).toBe(false);
- });
- });
-
- describe('when issue is confidential but not locked', () => {
- beforeEach(() => {
- createComponent({
- isLocked: false,
- isConfidential: true,
- confidentialIssueDocsPath: 'confidential-path',
- });
- });
-
- it('renders information about confidential issue', () => {
- expect(findConfidentialBlock().exists()).toBe(true);
- expect(findConfidentialBlock().element).toMatchSnapshot();
- });
-
- it('renders warning icon', () => {
- expect(wrapper.find(Icon).exists()).toBe(true);
- });
-
- it('does not render information about locked issue', () => {
- expect(findLockedBlock().exists()).toBe(false);
- });
-
- it('does not render information about locked and confidential issue', () => {
- expect(findLockedAndConfidentialBlock().exists()).toBe(false);
- });
- });
-
- describe('when issue is locked and confidential', () => {
- beforeEach(() => {
- createComponent({
- isLocked: true,
- isConfidential: true,
- });
- });
-
- it('renders information about locked and confidential issue', () => {
- expect(findLockedAndConfidentialBlock().exists()).toBe(true);
- expect(findLockedAndConfidentialBlock().element).toMatchSnapshot();
- });
-
- it('does not render warning icon', () => {
- expect(wrapper.find(Icon).exists()).toBe(false);
- });
-
- it('does not render information about locked issue', () => {
- expect(findLockedBlock().exists()).toBe(false);
- });
-
- it('does not render information about confidential issue', () => {
- expect(findConfidentialBlock().exists()).toBe(false);
- });
- });
-});
diff --git a/spec/frontend/vue_shared/components/issue/related_issuable_item_spec.js b/spec/frontend/vue_shared/components/issue/related_issuable_item_spec.js
index 9be0a67e4fa..fe9a5156539 100644
--- a/spec/frontend/vue_shared/components/issue/related_issuable_item_spec.js
+++ b/spec/frontend/vue_shared/components/issue/related_issuable_item_spec.js
@@ -3,6 +3,7 @@ import { mount } from '@vue/test-utils';
import { formatDate } from '~/lib/utils/datetime_utility';
import RelatedIssuableItem from '~/vue_shared/components/issue/related_issuable_item.vue';
import { defaultAssignees, defaultMilestone } from './related_issuable_mock_data';
+import { TEST_HOST } from 'jest/helpers/test_constants';
describe('RelatedIssuableItem', () => {
let wrapper;
@@ -19,7 +20,7 @@ describe('RelatedIssuableItem', () => {
idKey: 1,
displayReference: 'gitlab-org/gitlab-test#1',
pathIdSeparator: '#',
- path: `${gl.TEST_HOST}/path`,
+ path: `${TEST_HOST}/path`,
title: 'title',
confidential: true,
dueDate: '1990-12-31',
diff --git a/spec/frontend/vue_shared/components/issue/related_issuable_mock_data.js b/spec/frontend/vue_shared/components/issue/related_issuable_mock_data.js
index 5f69d761fdf..17813f2833d 100644
--- a/spec/frontend/vue_shared/components/issue/related_issuable_mock_data.js
+++ b/spec/frontend/vue_shared/components/issue/related_issuable_mock_data.js
@@ -1,3 +1,5 @@
+import { TEST_HOST } from 'jest/helpers/test_constants';
+
export const defaultProps = {
endpoint: '/foo/bar/issues/1/related_issues',
currentNamespacePath: 'foo',
@@ -83,8 +85,8 @@ export const defaultAssignees = [
name: 'Administrator',
username: 'root',
state: 'active',
- avatar_url: `${gl.TEST_HOST}`,
- web_url: `${gl.TEST_HOST}/root`,
+ avatar_url: `${TEST_HOST}`,
+ web_url: `${TEST_HOST}/root`,
status_tooltip_html: null,
path: '/root',
},
@@ -93,8 +95,8 @@ export const defaultAssignees = [
name: 'Brooks Beatty',
username: 'brynn_champlin',
state: 'active',
- avatar_url: `${gl.TEST_HOST}`,
- web_url: `${gl.TEST_HOST}/brynn_champlin`,
+ avatar_url: `${TEST_HOST}`,
+ web_url: `${TEST_HOST}/brynn_champlin`,
status_tooltip_html: null,
path: '/brynn_champlin',
},
@@ -103,8 +105,8 @@ export const defaultAssignees = [
name: 'Bryce Turcotte',
username: 'melynda',
state: 'active',
- avatar_url: `${gl.TEST_HOST}`,
- web_url: `${gl.TEST_HOST}/melynda`,
+ avatar_url: `${TEST_HOST}`,
+ web_url: `${TEST_HOST}/melynda`,
status_tooltip_html: null,
path: '/melynda',
},
@@ -113,8 +115,8 @@ export const defaultAssignees = [
name: 'Conchita Eichmann',
username: 'juliana_gulgowski',
state: 'active',
- avatar_url: `${gl.TEST_HOST}`,
- web_url: `${gl.TEST_HOST}/juliana_gulgowski`,
+ avatar_url: `${TEST_HOST}`,
+ web_url: `${TEST_HOST}/juliana_gulgowski`,
status_tooltip_html: null,
path: '/juliana_gulgowski',
},
diff --git a/spec/frontend/vue_shared/components/markdown/suggestion_diff_header_spec.js b/spec/frontend/vue_shared/components/markdown/suggestion_diff_header_spec.js
index 9a5b95b555f..c6e147899e4 100644
--- a/spec/frontend/vue_shared/components/markdown/suggestion_diff_header_spec.js
+++ b/spec/frontend/vue_shared/components/markdown/suggestion_diff_header_spec.js
@@ -69,11 +69,6 @@ describe('Suggestion Diff component', () => {
expect(addToBatchBtn.html().includes('Add suggestion to batch')).toBe(true);
});
- it('renders correct tooltip message for apply button', () => {
- createComponent();
- expect(wrapper.vm.tooltipMessage).toBe('This also resolves the discussion');
- });
-
describe('when apply suggestion is clicked', () => {
beforeEach(() => {
createComponent();
@@ -227,17 +222,23 @@ describe('Suggestion Diff component', () => {
createComponent({ canApply: false });
});
- it('disables apply suggestion and add to batch buttons', () => {
+ it('disables apply suggestion and hides add to batch button', () => {
expect(findApplyButton().exists()).toBe(true);
- expect(findAddToBatchButton().exists()).toBe(true);
+ expect(findAddToBatchButton().exists()).toBe(false);
expect(findApplyButton().attributes('disabled')).toBe('true');
- expect(findAddToBatchButton().attributes('disabled')).toBe('true');
+ });
+ });
+
+ describe('tooltip message for apply button', () => {
+ it('renders correct tooltip message when button is applicable', () => {
+ createComponent();
+ expect(wrapper.vm.tooltipMessage).toBe('This also resolves this thread');
});
- it('renders correct tooltip message for apply button', () => {
- expect(wrapper.vm.tooltipMessage).toBe(
- "Can't apply as this line has changed or the suggestion already matches its content.",
- );
+ it('renders the inapplicable reason in the tooltip when button is not applicable', () => {
+ const inapplicableReason = 'lorem';
+ createComponent({ canApply: false, inapplicableReason });
+ expect(wrapper.vm.tooltipMessage).toBe(inapplicableReason);
});
});
});
diff --git a/spec/frontend/vue_shared/components/notes/__snapshots__/noteable_warning_spec.js.snap b/spec/frontend/vue_shared/components/notes/__snapshots__/noteable_warning_spec.js.snap
new file mode 100644
index 00000000000..573bc9abe4d
--- /dev/null
+++ b/spec/frontend/vue_shared/components/notes/__snapshots__/noteable_warning_spec.js.snap
@@ -0,0 +1,58 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Issue Warning Component when issue is locked but not confidential renders information about locked issue 1`] = `
+<span>
+
+ This issue is locked.
+ Only project members can comment.
+
+ <gl-link-stub
+ href="locked-path"
+ target="_blank"
+ >
+ Learn more
+ </gl-link-stub>
+</span>
+`;
+
+exports[`Issue Warning Component when noteable is confidential but not locked renders information about confidential issue 1`] = `
+<span>
+
+ This is a confidential issue.
+ People without permission will never get a notification.
+
+ <gl-link-stub
+ href="confidential-path"
+ target="_blank"
+ >
+ Learn more
+ </gl-link-stub>
+</span>
+`;
+
+exports[`Issue Warning Component when noteable is locked and confidential renders information about locked and confidential noteable 1`] = `
+<span>
+ <span>
+ This issue is
+ <a
+ href=""
+ rel="noopener noreferrer"
+ target="_blank"
+ >
+ confidential
+ </a>
+ and
+ <a
+ href=""
+ rel="noopener noreferrer"
+ target="_blank"
+ >
+ locked
+ </a>
+ .
+ </span>
+
+ People without permission will never get a notification and won't be able to comment.
+
+</span>
+`;
diff --git a/spec/frontend/vue_shared/components/notes/noteable_warning_spec.js b/spec/frontend/vue_shared/components/notes/noteable_warning_spec.js
new file mode 100644
index 00000000000..ae8c9a0928e
--- /dev/null
+++ b/spec/frontend/vue_shared/components/notes/noteable_warning_spec.js
@@ -0,0 +1,196 @@
+import { shallowMount } from '@vue/test-utils';
+import NoteableWarning from '~/vue_shared/components/notes/noteable_warning.vue';
+import Icon from '~/vue_shared/components/icon.vue';
+
+describe('Issue Warning Component', () => {
+ let wrapper;
+
+ const findIcon = (w = wrapper) => w.find(Icon);
+ const findLockedBlock = (w = wrapper) => w.find({ ref: 'locked' });
+ const findConfidentialBlock = (w = wrapper) => w.find({ ref: 'confidential' });
+ const findLockedAndConfidentialBlock = (w = wrapper) => w.find({ ref: 'lockedAndConfidential' });
+
+ const createComponent = props =>
+ shallowMount(NoteableWarning, {
+ propsData: {
+ ...props,
+ },
+ });
+
+ afterEach(() => {
+ if (wrapper) {
+ wrapper.destroy();
+ wrapper = null;
+ }
+ });
+
+ describe('when issue is locked but not confidential', () => {
+ beforeEach(() => {
+ wrapper = createComponent({
+ isLocked: true,
+ lockedNoteableDocsPath: 'locked-path',
+ isConfidential: false,
+ });
+ });
+
+ it('renders information about locked issue', () => {
+ expect(findLockedBlock().exists()).toBe(true);
+ expect(findLockedBlock().element).toMatchSnapshot();
+ });
+
+ it('renders warning icon', () => {
+ expect(findIcon().exists()).toBe(true);
+ });
+
+ it('does not render information about locked and confidential issue', () => {
+ expect(findLockedAndConfidentialBlock().exists()).toBe(false);
+ });
+
+ it('does not render information about confidential issue', () => {
+ expect(findConfidentialBlock().exists()).toBe(false);
+ });
+ });
+
+ describe('when noteable is confidential but not locked', () => {
+ beforeEach(() => {
+ wrapper = createComponent({
+ isLocked: false,
+ isConfidential: true,
+ confidentialNoteableDocsPath: 'confidential-path',
+ });
+ });
+
+ it('renders information about confidential issue', async () => {
+ expect(findConfidentialBlock().exists()).toBe(true);
+ expect(findConfidentialBlock().element).toMatchSnapshot();
+
+ await wrapper.vm.$nextTick();
+ expect(findConfidentialBlock(wrapper).text()).toContain('This is a confidential issue.');
+ });
+
+ it('renders warning icon', () => {
+ expect(wrapper.find(Icon).exists()).toBe(true);
+ });
+
+ it('does not render information about locked noteable', () => {
+ expect(findLockedBlock().exists()).toBe(false);
+ });
+
+ it('does not render information about locked and confidential noteable', () => {
+ expect(findLockedAndConfidentialBlock().exists()).toBe(false);
+ });
+ });
+
+ describe('when noteable is locked and confidential', () => {
+ beforeEach(() => {
+ wrapper = createComponent({
+ isLocked: true,
+ isConfidential: true,
+ });
+ });
+
+ it('renders information about locked and confidential noteable', () => {
+ expect(findLockedAndConfidentialBlock().exists()).toBe(true);
+ expect(findLockedAndConfidentialBlock().element).toMatchSnapshot();
+ });
+
+ it('does not render warning icon', () => {
+ expect(wrapper.find(Icon).exists()).toBe(false);
+ });
+
+ it('does not render information about locked noteable', () => {
+ expect(findLockedBlock().exists()).toBe(false);
+ });
+
+ it('does not render information about confidential noteable', () => {
+ expect(findConfidentialBlock().exists()).toBe(false);
+ });
+ });
+
+ describe('when noteableType prop is defined', () => {
+ let wrapperLocked;
+ let wrapperConfidential;
+ let wrapperLockedAndConfidential;
+
+ beforeEach(() => {
+ wrapperLocked = createComponent({
+ isLocked: true,
+ isConfidential: false,
+ });
+ wrapperConfidential = createComponent({
+ isLocked: false,
+ isConfidential: true,
+ });
+ wrapperLockedAndConfidential = createComponent({
+ isLocked: true,
+ isConfidential: true,
+ });
+ });
+
+ afterEach(() => {
+ wrapperLocked.destroy();
+ wrapperConfidential.destroy();
+ wrapperLockedAndConfidential.destroy();
+ });
+
+ it('renders confidential & locked messages with noteable "issue"', () => {
+ expect(findLockedBlock(wrapperLocked).text()).toContain('This issue is locked.');
+ expect(findConfidentialBlock(wrapperConfidential).text()).toContain(
+ 'This is a confidential issue.',
+ );
+ expect(findLockedAndConfidentialBlock(wrapperLockedAndConfidential).text()).toContain(
+ 'This issue is confidential and locked.',
+ );
+ });
+
+ it('renders confidential & locked messages with noteable "epic"', async () => {
+ wrapperLocked.setProps({
+ noteableType: 'Epic',
+ });
+ wrapperConfidential.setProps({
+ noteableType: 'Epic',
+ });
+ wrapperLockedAndConfidential.setProps({
+ noteableType: 'Epic',
+ });
+
+ await wrapperLocked.vm.$nextTick();
+ expect(findLockedBlock(wrapperLocked).text()).toContain('This epic is locked.');
+
+ await wrapperConfidential.vm.$nextTick();
+ expect(findConfidentialBlock(wrapperConfidential).text()).toContain(
+ 'This is a confidential epic.',
+ );
+
+ await wrapperLockedAndConfidential.vm.$nextTick();
+ expect(findLockedAndConfidentialBlock(wrapperLockedAndConfidential).text()).toContain(
+ 'This epic is confidential and locked.',
+ );
+ });
+
+ it('renders confidential & locked messages with noteable "merge request"', async () => {
+ wrapperLocked.setProps({
+ noteableType: 'MergeRequest',
+ });
+ wrapperConfidential.setProps({
+ noteableType: 'MergeRequest',
+ });
+ wrapperLockedAndConfidential.setProps({
+ noteableType: 'MergeRequest',
+ });
+
+ await wrapperLocked.vm.$nextTick();
+ expect(findLockedBlock(wrapperLocked).text()).toContain('This merge request is locked.');
+
+ await wrapperConfidential.vm.$nextTick();
+ expect(findConfidentialBlock(wrapperConfidential).text()).toContain(
+ 'This is a confidential merge request.',
+ );
+
+ await wrapperLockedAndConfidential.vm.$nextTick();
+ expect(findLockedAndConfidentialBlock(wrapperLockedAndConfidential).text()).toContain(
+ 'This merge request is confidential and locked.',
+ );
+ });
+ });
+});
diff --git a/spec/frontend/vue_shared/components/project_selector/project_list_item_spec.js b/spec/frontend/vue_shared/components/project_selector/project_list_item_spec.js
index eb1d9e93634..385134c4a3f 100644
--- a/spec/frontend/vue_shared/components/project_selector/project_list_item_spec.js
+++ b/spec/frontend/vue_shared/components/project_selector/project_list_item_spec.js
@@ -74,6 +74,16 @@ describe('ProjectListItem component', () => {
expect(renderedNamespace).toBe('a / ... / e /');
});
+ it(`renders a simple namespace name of a GraphQL project`, () => {
+ options.propsData.project.name_with_namespace = undefined;
+ options.propsData.project.nameWithNamespace = 'test';
+
+ wrapper = shallowMount(Component, options);
+ const renderedNamespace = trimText(wrapper.find('.js-project-namespace').text());
+
+ expect(renderedNamespace).toBe('test /');
+ });
+
it(`renders the project name`, () => {
options.propsData.project.name = 'my-test-project';
diff --git a/spec/frontend/vue_shared/components/remove_member_modal_spec.js b/spec/frontend/vue_shared/components/remove_member_modal_spec.js
new file mode 100644
index 00000000000..2d380b25a0a
--- /dev/null
+++ b/spec/frontend/vue_shared/components/remove_member_modal_spec.js
@@ -0,0 +1,65 @@
+import { GlFormCheckbox, GlModal } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
+import RemoveMemberModal from '~/vue_shared/components/remove_member_modal.vue';
+
+describe('RemoveMemberModal', () => {
+ const memberPath = '/gitlab-org/gitlab-test/-/project_members/90';
+ let wrapper;
+
+ const findForm = () => wrapper.find({ ref: 'form' });
+ const findGlModal = () => wrapper.find(GlModal);
+
+ afterEach(() => {
+ wrapper.destroy();
+ wrapper = null;
+ });
+
+ describe.each`
+ state | isAccessRequest | actionText | checkboxTestDescription | checkboxExpected | message
+ ${'removing a member'} | ${'false'} | ${'Remove member'} | ${'shows a checkbox to allow removal from related issues and MRs'} | ${true} | ${'Are you sure you want to remove Jane Doe from the Gitlab Org / Gitlab Test project?'}
+ ${'denying an access request'} | ${'true'} | ${'Deny access request'} | ${'does not show a checkbox'} | ${false} | ${"Are you sure you want to deny Jane Doe's request to join the Gitlab Org / Gitlab Test project?"}
+ `(
+ 'when $state',
+ ({ actionText, isAccessRequest, message, checkboxTestDescription, checkboxExpected }) => {
+ beforeEach(() => {
+ wrapper = shallowMount(RemoveMemberModal, {
+ data() {
+ return {
+ modalData: {
+ isAccessRequest,
+ message,
+ memberPath,
+ },
+ };
+ },
+ });
+ });
+
+ it(`has the title ${actionText}`, () => {
+ expect(findGlModal().attributes('title')).toBe(actionText);
+ });
+
+ it('contains a form action', () => {
+ expect(findForm().attributes('action')).toBe(memberPath);
+ });
+
+ it('displays a message to the user', () => {
+ expect(wrapper.find('[data-testid=modal-message]').text()).toBe(message);
+ });
+
+ it(`${checkboxTestDescription}`, () => {
+ expect(wrapper.contains(GlFormCheckbox)).toBe(checkboxExpected);
+ });
+
+ it('submits the form when the modal is submitted', () => {
+ const spy = jest.spyOn(findForm().element, 'submit');
+
+ findGlModal().vm.$emit('primary');
+
+ expect(spy).toHaveBeenCalled();
+
+ spy.mockRestore();
+ });
+ },
+ );
+});
diff --git a/spec/frontend/vue_shared/components/__snapshots__/resizable_chart_container_spec.js.snap b/spec/frontend/vue_shared/components/resizable_chart/__snapshots__/resizable_chart_container_spec.js.snap
index add0c36a120..add0c36a120 100644
--- a/spec/frontend/vue_shared/components/__snapshots__/resizable_chart_container_spec.js.snap
+++ b/spec/frontend/vue_shared/components/resizable_chart/__snapshots__/resizable_chart_container_spec.js.snap
diff --git a/spec/frontend/vue_shared/components/resizable_chart/__snapshots__/skeleton_loader_spec.js.snap b/spec/frontend/vue_shared/components/resizable_chart/__snapshots__/skeleton_loader_spec.js.snap
new file mode 100644
index 00000000000..103b53cb280
--- /dev/null
+++ b/spec/frontend/vue_shared/components/resizable_chart/__snapshots__/skeleton_loader_spec.js.snap
@@ -0,0 +1,324 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Resizable Skeleton Loader default setup renders the bars, labels, and grid with correct position, size, and rx percentages 1`] = `
+<gl-skeleton-loader-stub
+ baseurl=""
+ height="130"
+ preserveaspectratio="xMidYMid meet"
+ width="400"
+>
+ <rect
+ data-testid="skeleton-chart-grid"
+ height="1px"
+ width="100%"
+ x="0"
+ y="30%"
+ />
+ <rect
+ data-testid="skeleton-chart-grid"
+ height="1px"
+ width="100%"
+ x="0"
+ y="60%"
+ />
+ <rect
+ data-testid="skeleton-chart-grid"
+ height="1px"
+ width="100%"
+ x="0"
+ y="90%"
+ />
+
+ <rect
+ data-testid="skeleton-chart-bar"
+ height="5%"
+ rx="0.4%"
+ width="6%"
+ x="5.875%"
+ y="85%"
+ />
+ <rect
+ data-testid="skeleton-chart-bar"
+ height="7%"
+ rx="0.4%"
+ width="6%"
+ x="17.625%"
+ y="83%"
+ />
+ <rect
+ data-testid="skeleton-chart-bar"
+ height="9%"
+ rx="0.4%"
+ width="6%"
+ x="29.375%"
+ y="81%"
+ />
+ <rect
+ data-testid="skeleton-chart-bar"
+ height="14%"
+ rx="0.4%"
+ width="6%"
+ x="41.125%"
+ y="76%"
+ />
+ <rect
+ data-testid="skeleton-chart-bar"
+ height="21%"
+ rx="0.4%"
+ width="6%"
+ x="52.875%"
+ y="69%"
+ />
+ <rect
+ data-testid="skeleton-chart-bar"
+ height="35%"
+ rx="0.4%"
+ width="6%"
+ x="64.625%"
+ y="55%"
+ />
+ <rect
+ data-testid="skeleton-chart-bar"
+ height="50%"
+ rx="0.4%"
+ width="6%"
+ x="76.375%"
+ y="40%"
+ />
+ <rect
+ data-testid="skeleton-chart-bar"
+ height="80%"
+ rx="0.4%"
+ width="6%"
+ x="88.125%"
+ y="10%"
+ />
+
+ <rect
+ data-testid="skeleton-chart-label"
+ height="5%"
+ rx="0.4%"
+ width="4%"
+ x="6.875%"
+ y="95%"
+ />
+ <rect
+ data-testid="skeleton-chart-label"
+ height="5%"
+ rx="0.4%"
+ width="4%"
+ x="18.625%"
+ y="95%"
+ />
+ <rect
+ data-testid="skeleton-chart-label"
+ height="5%"
+ rx="0.4%"
+ width="4%"
+ x="30.375%"
+ y="95%"
+ />
+ <rect
+ data-testid="skeleton-chart-label"
+ height="5%"
+ rx="0.4%"
+ width="4%"
+ x="42.125%"
+ y="95%"
+ />
+ <rect
+ data-testid="skeleton-chart-label"
+ height="5%"
+ rx="0.4%"
+ width="4%"
+ x="53.875%"
+ y="95%"
+ />
+ <rect
+ data-testid="skeleton-chart-label"
+ height="5%"
+ rx="0.4%"
+ width="4%"
+ x="65.625%"
+ y="95%"
+ />
+ <rect
+ data-testid="skeleton-chart-label"
+ height="5%"
+ rx="0.4%"
+ width="4%"
+ x="77.375%"
+ y="95%"
+ />
+ <rect
+ data-testid="skeleton-chart-label"
+ height="5%"
+ rx="0.4%"
+ width="4%"
+ x="89.125%"
+ y="95%"
+ />
+</gl-skeleton-loader-stub>
+`;
+
+exports[`Resizable Skeleton Loader with custom settings renders the correct position, and size percentages for bars and labels with different settings 1`] = `
+<gl-skeleton-loader-stub
+ baseurl=""
+ height="130"
+ preserveaspectratio="xMidYMid meet"
+ uniquekey=""
+ width="400"
+>
+ <rect
+ data-testid="skeleton-chart-grid"
+ height="1px"
+ width="100%"
+ x="0"
+ y="30%"
+ />
+ <rect
+ data-testid="skeleton-chart-grid"
+ height="1px"
+ width="100%"
+ x="0"
+ y="60%"
+ />
+ <rect
+ data-testid="skeleton-chart-grid"
+ height="1px"
+ width="100%"
+ x="0"
+ y="90%"
+ />
+
+ <rect
+ data-testid="skeleton-chart-bar"
+ height="5%"
+ rx="0.6%"
+ width="3%"
+ x="6.0625%"
+ y="85%"
+ />
+ <rect
+ data-testid="skeleton-chart-bar"
+ height="7%"
+ rx="0.6%"
+ width="3%"
+ x="18.1875%"
+ y="83%"
+ />
+ <rect
+ data-testid="skeleton-chart-bar"
+ height="9%"
+ rx="0.6%"
+ width="3%"
+ x="30.3125%"
+ y="81%"
+ />
+ <rect
+ data-testid="skeleton-chart-bar"
+ height="14%"
+ rx="0.6%"
+ width="3%"
+ x="42.4375%"
+ y="76%"
+ />
+ <rect
+ data-testid="skeleton-chart-bar"
+ height="21%"
+ rx="0.6%"
+ width="3%"
+ x="54.5625%"
+ y="69%"
+ />
+ <rect
+ data-testid="skeleton-chart-bar"
+ height="35%"
+ rx="0.6%"
+ width="3%"
+ x="66.6875%"
+ y="55%"
+ />
+ <rect
+ data-testid="skeleton-chart-bar"
+ height="50%"
+ rx="0.6%"
+ width="3%"
+ x="78.8125%"
+ y="40%"
+ />
+ <rect
+ data-testid="skeleton-chart-bar"
+ height="80%"
+ rx="0.6%"
+ width="3%"
+ x="90.9375%"
+ y="10%"
+ />
+
+ <rect
+ data-testid="skeleton-chart-label"
+ height="2%"
+ rx="0.6%"
+ width="7%"
+ x="4.0625%"
+ y="98%"
+ />
+ <rect
+ data-testid="skeleton-chart-label"
+ height="2%"
+ rx="0.6%"
+ width="7%"
+ x="16.1875%"
+ y="98%"
+ />
+ <rect
+ data-testid="skeleton-chart-label"
+ height="2%"
+ rx="0.6%"
+ width="7%"
+ x="28.3125%"
+ y="98%"
+ />
+ <rect
+ data-testid="skeleton-chart-label"
+ height="2%"
+ rx="0.6%"
+ width="7%"
+ x="40.4375%"
+ y="98%"
+ />
+ <rect
+ data-testid="skeleton-chart-label"
+ height="2%"
+ rx="0.6%"
+ width="7%"
+ x="52.5625%"
+ y="98%"
+ />
+ <rect
+ data-testid="skeleton-chart-label"
+ height="2%"
+ rx="0.6%"
+ width="7%"
+ x="64.6875%"
+ y="98%"
+ />
+ <rect
+ data-testid="skeleton-chart-label"
+ height="2%"
+ rx="0.6%"
+ width="7%"
+ x="76.8125%"
+ y="98%"
+ />
+ <rect
+ data-testid="skeleton-chart-label"
+ height="2%"
+ rx="0.6%"
+ width="7%"
+ x="88.9375%"
+ y="98%"
+ />
+</gl-skeleton-loader-stub>
+`;
diff --git a/spec/frontend/vue_shared/components/resizable_chart_container_spec.js b/spec/frontend/vue_shared/components/resizable_chart/resizable_chart_container_spec.js
index 3a5514ef318..3a5514ef318 100644
--- a/spec/frontend/vue_shared/components/resizable_chart_container_spec.js
+++ b/spec/frontend/vue_shared/components/resizable_chart/resizable_chart_container_spec.js
diff --git a/spec/frontend/vue_shared/components/resizable_chart/skeleton_loader_spec.js b/spec/frontend/vue_shared/components/resizable_chart/skeleton_loader_spec.js
new file mode 100644
index 00000000000..7facd02e596
--- /dev/null
+++ b/spec/frontend/vue_shared/components/resizable_chart/skeleton_loader_spec.js
@@ -0,0 +1,55 @@
+import { shallowMount } from '@vue/test-utils';
+import ChartSkeletonLoader from '~/vue_shared/components/resizable_chart/skeleton_loader.vue';
+
+describe('Resizable Skeleton Loader', () => {
+ let wrapper;
+
+ const createComponent = (propsData = {}) => {
+ wrapper = shallowMount(ChartSkeletonLoader, {
+ propsData,
+ });
+ };
+
+ const verifyElementsPresence = () => {
+ const gridItems = wrapper.findAll('[data-testid="skeleton-chart-grid"]').wrappers;
+ const barItems = wrapper.findAll('[data-testid="skeleton-chart-bar"]').wrappers;
+ const labelItems = wrapper.findAll('[data-testid="skeleton-chart-label"]').wrappers;
+ expect(gridItems.length).toBe(3);
+ expect(barItems.length).toBe(8);
+ expect(labelItems.length).toBe(8);
+ };
+
+ afterEach(() => {
+ if (wrapper?.destroy) {
+ wrapper.destroy();
+ }
+ });
+
+ describe('default setup', () => {
+ beforeEach(() => {
+ createComponent({ uniqueKey: null });
+ });
+
+ it('renders the bars, labels, and grid with correct position, size, and rx percentages', () => {
+ expect(wrapper.element).toMatchSnapshot();
+ });
+
+ it('renders the correct number of grid items, bars, and labels', () => {
+ verifyElementsPresence();
+ });
+ });
+
+ describe('with custom settings', () => {
+ beforeEach(() => {
+ createComponent({ uniqueKey: '', rx: 0.6, barWidth: 3, labelWidth: 7, labelHeight: 2 });
+ });
+
+ it('renders the correct position, and size percentages for bars and labels with different settings', () => {
+ expect(wrapper.element).toMatchSnapshot();
+ });
+
+ it('renders the correct number of grid items, bars, and labels', () => {
+ verifyElementsPresence();
+ });
+ });
+});
diff --git a/spec/frontend/vue_shared/components/rich_content_editor/editor_service_spec.js b/spec/frontend/vue_shared/components/rich_content_editor/editor_service_spec.js
index faa32131fab..78f27c9948b 100644
--- a/spec/frontend/vue_shared/components/rich_content_editor/editor_service_spec.js
+++ b/spec/frontend/vue_shared/components/rich_content_editor/editor_service_spec.js
@@ -2,18 +2,35 @@ import {
generateToolbarItem,
addCustomEventListener,
removeCustomEventListener,
+ registerHTMLToMarkdownRenderer,
addImage,
getMarkdown,
-} from '~/vue_shared/components/rich_content_editor/editor_service';
+} from '~/vue_shared/components/rich_content_editor/services/editor_service';
+import buildHTMLToMarkdownRenderer from '~/vue_shared/components/rich_content_editor/services/build_html_to_markdown_renderer';
+
+jest.mock('~/vue_shared/components/rich_content_editor/services/build_html_to_markdown_renderer');
describe('Editor Service', () => {
- const mockInstance = {
- eventManager: { addEventType: jest.fn(), removeEventHandler: jest.fn(), listen: jest.fn() },
- editor: { exec: jest.fn() },
- invoke: jest.fn(),
- };
- const event = 'someCustomEvent';
- const handler = jest.fn();
+ let mockInstance;
+ let event;
+ let handler;
+
+ beforeEach(() => {
+ mockInstance = {
+ eventManager: { addEventType: jest.fn(), removeEventHandler: jest.fn(), listen: jest.fn() },
+ editor: { exec: jest.fn() },
+ invoke: jest.fn(),
+ toMarkOptions: {
+ renderer: {
+ constructor: {
+ factory: jest.fn(),
+ },
+ },
+ },
+ };
+ event = 'someCustomEvent';
+ handler = jest.fn();
+ });
describe('generateToolbarItem', () => {
const config = {
@@ -74,4 +91,33 @@ describe('Editor Service', () => {
expect(mockInstance.invoke).toHaveBeenCalledWith('getMarkdown');
});
});
+
+ describe('registerHTMLToMarkdownRenderer', () => {
+ let baseRenderer;
+ const htmlToMarkdownRenderer = {};
+ const extendedRenderer = {};
+
+ beforeEach(() => {
+ baseRenderer = mockInstance.toMarkOptions.renderer;
+ buildHTMLToMarkdownRenderer.mockReturnValueOnce(htmlToMarkdownRenderer);
+ baseRenderer.constructor.factory.mockReturnValueOnce(extendedRenderer);
+
+ registerHTMLToMarkdownRenderer(mockInstance);
+ });
+
+ it('builds a new instance of the HTML to Markdown renderer', () => {
+ expect(buildHTMLToMarkdownRenderer).toHaveBeenCalledWith(baseRenderer);
+ });
+
+ it('extends base renderer with the HTML to Markdown renderer', () => {
+ expect(baseRenderer.constructor.factory).toHaveBeenCalledWith(
+ baseRenderer,
+ htmlToMarkdownRenderer,
+ );
+ });
+
+ it('replaces the default renderer with extended renderer', () => {
+ expect(mockInstance.toMarkOptions.renderer).toBe(extendedRenderer);
+ });
+ });
});
diff --git a/spec/frontend/vue_shared/components/rich_content_editor/modals/add_image/add_image_modal_spec.js b/spec/frontend/vue_shared/components/rich_content_editor/modals/add_image/add_image_modal_spec.js
new file mode 100644
index 00000000000..0c2ac53aa52
--- /dev/null
+++ b/spec/frontend/vue_shared/components/rich_content_editor/modals/add_image/add_image_modal_spec.js
@@ -0,0 +1,76 @@
+import { shallowMount } from '@vue/test-utils';
+import { GlModal, GlTabs } from '@gitlab/ui';
+import AddImageModal from '~/vue_shared/components/rich_content_editor/modals/add_image/add_image_modal.vue';
+import UploadImageTab from '~/vue_shared/components/rich_content_editor/modals/add_image/upload_image_tab.vue';
+import { IMAGE_TABS } from '~/vue_shared/components/rich_content_editor/constants';
+
+describe('Add Image Modal', () => {
+ let wrapper;
+ const propsData = { imageRoot: 'path/to/root/' };
+
+ const findModal = () => wrapper.find(GlModal);
+ const findTabs = () => wrapper.find(GlTabs);
+ const findUploadImageTab = () => wrapper.find(UploadImageTab);
+ const findUrlInput = () => wrapper.find({ ref: 'urlInput' });
+ const findDescriptionInput = () => wrapper.find({ ref: 'descriptionInput' });
+
+ beforeEach(() => {
+ wrapper = shallowMount(AddImageModal, {
+ provide: { glFeatures: { sseImageUploads: true } },
+ propsData,
+ });
+ });
+
+ describe('when content is loaded', () => {
+ it('renders a modal component', () => {
+ expect(findModal().exists()).toBe(true);
+ });
+
+ it('renders a Tabs component', () => {
+ expect(findTabs().exists()).toBe(true);
+ });
+
+ it('renders an upload image tab', () => {
+ expect(findUploadImageTab().exists()).toBe(true);
+ });
+
+ it('renders an input to add an image URL', () => {
+ expect(findUrlInput().exists()).toBe(true);
+ });
+
+ it('renders an input to add an image description', () => {
+ expect(findDescriptionInput().exists()).toBe(true);
+ });
+ });
+
+ describe('add image', () => {
+ describe('Upload', () => {
+ it('validates the file', () => {
+ const preventDefault = jest.fn();
+ const description = 'some description';
+ const file = { name: 'some_file.png' };
+
+ wrapper.vm.$refs.uploadImageTab = { validateFile: jest.fn() };
+ wrapper.setData({ file, description, tabIndex: IMAGE_TABS.UPLOAD_TAB });
+
+ findModal().vm.$emit('ok', { preventDefault });
+
+ expect(wrapper.vm.$refs.uploadImageTab.validateFile).toHaveBeenCalled();
+ });
+ });
+
+ describe('URL', () => {
+ it('emits an addImage event when a valid URL is specified', () => {
+ const preventDefault = jest.fn();
+ const mockImage = { imageUrl: '/some/valid/url.png', description: 'some description' };
+ wrapper.setData({ ...mockImage, tabIndex: IMAGE_TABS.URL_TAB });
+
+ findModal().vm.$emit('ok', { preventDefault });
+ expect(preventDefault).not.toHaveBeenCalled();
+ expect(wrapper.emitted('addImage')).toEqual([
+ [{ imageUrl: mockImage.imageUrl, altText: mockImage.description }],
+ ]);
+ });
+ });
+ });
+});
diff --git a/spec/frontend/vue_shared/components/rich_content_editor/modals/add_image/upload_image_tab_spec.js b/spec/frontend/vue_shared/components/rich_content_editor/modals/add_image/upload_image_tab_spec.js
new file mode 100644
index 00000000000..ded490b2568
--- /dev/null
+++ b/spec/frontend/vue_shared/components/rich_content_editor/modals/add_image/upload_image_tab_spec.js
@@ -0,0 +1,41 @@
+import { shallowMount } from '@vue/test-utils';
+import UploadImageTab from '~/vue_shared/components/rich_content_editor/modals/add_image/upload_image_tab.vue';
+
+describe('Upload Image Tab', () => {
+ let wrapper;
+
+ beforeEach(() => {
+ wrapper = shallowMount(UploadImageTab);
+ });
+
+ afterEach(() => wrapper.destroy());
+
+ const triggerInputEvent = size => {
+ const file = { size, name: 'file-name.png' };
+ const mockEvent = new Event('input');
+
+ Object.defineProperty(mockEvent, 'target', { value: { files: [file] } });
+
+ wrapper.find({ ref: 'fileInput' }).element.dispatchEvent(mockEvent);
+
+ return file;
+ };
+
+ describe('onInput', () => {
+ it.each`
+ size | fileError
+ ${2000000000} | ${'Maximum file size is 2MB. Please select a smaller file.'}
+ ${200} | ${null}
+ `('validates the file correctly', ({ size, fileError }) => {
+ triggerInputEvent(size);
+
+ expect(wrapper.vm.fileError).toBe(fileError);
+ });
+ });
+
+ it('emits input event when file is valid', () => {
+ const file = triggerInputEvent(200);
+
+ expect(wrapper.emitted('input')).toEqual([[file]]);
+ });
+});
diff --git a/spec/frontend/vue_shared/components/rich_content_editor/modals/add_image_modal_spec.js b/spec/frontend/vue_shared/components/rich_content_editor/modals/add_image_modal_spec.js
deleted file mode 100644
index 4889bc8538d..00000000000
--- a/spec/frontend/vue_shared/components/rich_content_editor/modals/add_image_modal_spec.js
+++ /dev/null
@@ -1,41 +0,0 @@
-import { shallowMount } from '@vue/test-utils';
-import { GlModal } from '@gitlab/ui';
-import AddImageModal from '~/vue_shared/components/rich_content_editor/modals/add_image_modal.vue';
-
-describe('Add Image Modal', () => {
- let wrapper;
-
- const findModal = () => wrapper.find(GlModal);
- const findUrlInput = () => wrapper.find({ ref: 'urlInput' });
- const findDescriptionInput = () => wrapper.find({ ref: 'descriptionInput' });
-
- beforeEach(() => {
- wrapper = shallowMount(AddImageModal);
- });
-
- describe('when content is loaded', () => {
- it('renders a modal component', () => {
- expect(findModal().exists()).toBe(true);
- });
-
- it('renders an input to add an image URL', () => {
- expect(findUrlInput().exists()).toBe(true);
- });
-
- it('renders an input to add an image description', () => {
- expect(findDescriptionInput().exists()).toBe(true);
- });
- });
-
- describe('add image', () => {
- it('emits an addImage event when a valid URL is specified', () => {
- const preventDefault = jest.fn();
- const mockImage = { imageUrl: '/some/valid/url.png', altText: 'some description' };
- wrapper.setData({ ...mockImage });
-
- findModal().vm.$emit('ok', { preventDefault });
- expect(preventDefault).not.toHaveBeenCalled();
- expect(wrapper.emitted('addImage')).toEqual([[mockImage]]);
- });
- });
-});
diff --git a/spec/frontend/vue_shared/components/rich_content_editor/rich_content_editor_spec.js b/spec/frontend/vue_shared/components/rich_content_editor/rich_content_editor_spec.js
index 0db10389df4..b6ff6aa767c 100644
--- a/spec/frontend/vue_shared/components/rich_content_editor/rich_content_editor_spec.js
+++ b/spec/frontend/vue_shared/components/rich_content_editor/rich_content_editor_spec.js
@@ -1,6 +1,6 @@
import { shallowMount } from '@vue/test-utils';
import RichContentEditor from '~/vue_shared/components/rich_content_editor/rich_content_editor.vue';
-import AddImageModal from '~/vue_shared/components/rich_content_editor/modals/add_image_modal.vue';
+import AddImageModal from '~/vue_shared/components/rich_content_editor/modals/add_image/add_image_modal.vue';
import {
EDITOR_OPTIONS,
EDITOR_TYPES,
@@ -13,25 +13,28 @@ import {
addCustomEventListener,
removeCustomEventListener,
addImage,
-} from '~/vue_shared/components/rich_content_editor/editor_service';
+ registerHTMLToMarkdownRenderer,
+} from '~/vue_shared/components/rich_content_editor/services/editor_service';
-jest.mock('~/vue_shared/components/rich_content_editor/editor_service', () => ({
- ...jest.requireActual('~/vue_shared/components/rich_content_editor/editor_service'),
+jest.mock('~/vue_shared/components/rich_content_editor/services/editor_service', () => ({
+ ...jest.requireActual('~/vue_shared/components/rich_content_editor/services/editor_service'),
addCustomEventListener: jest.fn(),
removeCustomEventListener: jest.fn(),
addImage: jest.fn(),
+ registerHTMLToMarkdownRenderer: jest.fn(),
}));
describe('Rich Content Editor', () => {
let wrapper;
- const value = '## Some Markdown';
+ const content = '## Some Markdown';
+ const imageRoot = 'path/to/root/';
const findEditor = () => wrapper.find({ ref: 'editor' });
const findAddImageModal = () => wrapper.find(AddImageModal);
beforeEach(() => {
wrapper = shallowMount(RichContentEditor, {
- propsData: { value },
+ propsData: { content, imageRoot },
});
});
@@ -41,7 +44,7 @@ describe('Rich Content Editor', () => {
});
it('renders the correct content', () => {
- expect(findEditor().props().initialValue).toBe(value);
+ expect(findEditor().props().initialValue).toBe(content);
});
it('provides the correct editor options', () => {
@@ -73,17 +76,37 @@ describe('Rich Content Editor', () => {
});
});
+ describe('when content is reset', () => {
+ it('should reset the content via setMarkdown', () => {
+ const newContent = 'Just the body content excluding the front matter for example';
+ const mockInstance = { invoke: jest.fn() };
+ wrapper.vm.$refs.editor = mockInstance;
+
+ wrapper.vm.resetInitialValue(newContent);
+
+ expect(mockInstance.invoke).toHaveBeenCalledWith('setMarkdown', newContent);
+ });
+ });
+
describe('when editor is loaded', () => {
- it('adds the CUSTOM_EVENTS.openAddImageModal custom event listener', () => {
- const mockEditorApi = { eventManager: { addEventType: jest.fn(), listen: jest.fn() } };
+ let mockEditorApi;
+
+ beforeEach(() => {
+ mockEditorApi = { eventManager: { addEventType: jest.fn(), listen: jest.fn() } };
findEditor().vm.$emit('load', mockEditorApi);
+ });
+ it('adds the CUSTOM_EVENTS.openAddImageModal custom event listener', () => {
expect(addCustomEventListener).toHaveBeenCalledWith(
mockEditorApi,
CUSTOM_EVENTS.openAddImageModal,
wrapper.vm.onOpenAddImageModal,
);
});
+
+ it('registers HTML to markdown renderer', () => {
+ expect(registerHTMLToMarkdownRenderer).toHaveBeenCalledWith(mockEditorApi);
+ });
});
describe('when editor is destroyed', () => {
@@ -107,7 +130,7 @@ describe('Rich Content Editor', () => {
});
it('calls the onAddImage method when the addImage event is emitted', () => {
- const mockImage = { imageUrl: 'some/url.png', description: 'some description' };
+ const mockImage = { imageUrl: 'some/url.png', altText: 'some description' };
const mockInstance = { exec: jest.fn() };
wrapper.vm.$refs.editor = mockInstance;
diff --git a/spec/frontend/vue_shared/components/rich_content_editor/services/build_custom_renderer_spec.js b/spec/frontend/vue_shared/components/rich_content_editor/services/build_custom_renderer_spec.js
new file mode 100644
index 00000000000..cafe53e6bb2
--- /dev/null
+++ b/spec/frontend/vue_shared/components/rich_content_editor/services/build_custom_renderer_spec.js
@@ -0,0 +1,29 @@
+import buildCustomHTMLRenderer from '~/vue_shared/components/rich_content_editor/services/build_custom_renderer';
+
+describe('Build Custom Renderer Service', () => {
+ describe('buildCustomHTMLRenderer', () => {
+ it('should return an object with the default renderer functions when lacking arguments', () => {
+ expect(buildCustomHTMLRenderer()).toEqual(
+ expect.objectContaining({
+ list: expect.any(Function),
+ text: expect.any(Function),
+ }),
+ );
+ });
+
+ it('should return an object with both custom and default renderer functions when passed customRenderers', () => {
+ const mockHtmlCustomRenderer = jest.fn();
+ const customRenderers = {
+ html: [mockHtmlCustomRenderer],
+ };
+
+ expect(buildCustomHTMLRenderer(customRenderers)).toEqual(
+ expect.objectContaining({
+ html: expect.any(Function),
+ list: expect.any(Function),
+ text: expect.any(Function),
+ }),
+ );
+ });
+ });
+});
diff --git a/spec/frontend/vue_shared/components/rich_content_editor/services/build_html_to_markdown_renderer_spec.js b/spec/frontend/vue_shared/components/rich_content_editor/services/build_html_to_markdown_renderer_spec.js
new file mode 100644
index 00000000000..0e8610a22f5
--- /dev/null
+++ b/spec/frontend/vue_shared/components/rich_content_editor/services/build_html_to_markdown_renderer_spec.js
@@ -0,0 +1,50 @@
+import buildHTMLToMarkdownRenderer from '~/vue_shared/components/rich_content_editor/services/build_html_to_markdown_renderer';
+
+describe('HTMLToMarkdownRenderer', () => {
+ let baseRenderer;
+ let htmlToMarkdownRenderer;
+ const NODE = { nodeValue: 'mock_node' };
+
+ beforeEach(() => {
+ baseRenderer = {
+ trim: jest.fn(input => `trimmed ${input}`),
+ getSpaceCollapsedText: jest.fn(input => `space collapsed ${input}`),
+ getSpaceControlled: jest.fn(input => `space controlled ${input}`),
+ convert: jest.fn(),
+ };
+ });
+
+ describe('TEXT_NODE visitor', () => {
+ it('composes getSpaceControlled, getSpaceCollapsedText, and trim services', () => {
+ htmlToMarkdownRenderer = buildHTMLToMarkdownRenderer(baseRenderer);
+
+ expect(htmlToMarkdownRenderer.TEXT_NODE(NODE)).toBe(
+ `space controlled trimmed space collapsed ${NODE.nodeValue}`,
+ );
+ });
+ });
+
+ describe('LI OL, LI UL visitor', () => {
+ const oneLevelNestedList = '\n * List item 1\n * List item 2';
+ const twoLevelNestedList = '\n * List item 1\n * List item 2';
+ const spaceInContentList = '\n * List item 1\n * List item 2';
+
+ it.each`
+ list | indentSpaces | result
+ ${oneLevelNestedList} | ${2} | ${'\n * List item 1\n * List item 2'}
+ ${oneLevelNestedList} | ${3} | ${'\n * List item 1\n * List item 2'}
+ ${oneLevelNestedList} | ${6} | ${'\n * List item 1\n * List item 2'}
+ ${twoLevelNestedList} | ${4} | ${'\n * List item 1\n * List item 2'}
+ ${spaceInContentList} | ${1} | ${'\n * List item 1\n * List item 2'}
+ `('changes the list indentation to $indentSpaces spaces', ({ list, indentSpaces, result }) => {
+ htmlToMarkdownRenderer = buildHTMLToMarkdownRenderer(baseRenderer, {
+ subListIndentSpaces: indentSpaces,
+ });
+
+ baseRenderer.convert.mockReturnValueOnce(list);
+
+ expect(htmlToMarkdownRenderer['LI OL, LI UL'](NODE, list)).toBe(result);
+ expect(baseRenderer.convert).toHaveBeenCalledWith(NODE, list);
+ });
+ });
+});
diff --git a/spec/frontend/vue_shared/components/rich_content_editor/services/renderers/build_uneditable_token_spec.js b/spec/frontend/vue_shared/components/rich_content_editor/services/renderers/build_uneditable_token_spec.js
new file mode 100644
index 00000000000..18dff0a39bb
--- /dev/null
+++ b/spec/frontend/vue_shared/components/rich_content_editor/services/renderers/build_uneditable_token_spec.js
@@ -0,0 +1,88 @@
+import {
+ buildTextToken,
+ buildUneditableOpenTokens,
+ buildUneditableCloseToken,
+ buildUneditableCloseTokens,
+ buildUneditableTokens,
+ buildUneditableInlineTokens,
+ buildUneditableHtmlAsTextTokens,
+} from '~/vue_shared/components/rich_content_editor/services/renderers/build_uneditable_token';
+
+import {
+ originInlineToken,
+ originToken,
+ uneditableOpenTokens,
+ uneditableCloseToken,
+ uneditableCloseTokens,
+ uneditableBlockTokens,
+ uneditableInlineTokens,
+ uneditableTokens,
+} from './mock_data';
+
+describe('Build Uneditable Token renderer helper', () => {
+ describe('buildTextToken', () => {
+ it('returns an object literal representing a text token', () => {
+ const text = originToken.content;
+ expect(buildTextToken(text)).toStrictEqual(originToken);
+ });
+ });
+
+ describe('buildUneditableOpenTokens', () => {
+ it('returns a 2-item array of tokens with the originToken appended to an open token', () => {
+ const result = buildUneditableOpenTokens(originToken);
+
+ expect(result).toHaveLength(2);
+ expect(result).toStrictEqual(uneditableOpenTokens);
+ });
+ });
+
+ describe('buildUneditableCloseToken', () => {
+ it('returns an object literal representing the uneditable close token', () => {
+ expect(buildUneditableCloseToken()).toStrictEqual(uneditableCloseToken);
+ });
+ });
+
+ describe('buildUneditableCloseTokens', () => {
+ it('returns a 2-item array of tokens with the originToken prepended to a close token', () => {
+ const result = buildUneditableCloseTokens(originToken);
+
+ expect(result).toHaveLength(2);
+ expect(result).toStrictEqual(uneditableCloseTokens);
+ });
+ });
+
+ describe('buildUneditableTokens', () => {
+ it('returns a 3-item array of tokens with the originToken wrapped in the middle of block tokens', () => {
+ const result = buildUneditableTokens(originToken);
+
+ expect(result).toHaveLength(3);
+ expect(result).toStrictEqual(uneditableTokens);
+ });
+ });
+
+ describe('buildUneditableInlineTokens', () => {
+ it('returns a 3-item array of tokens with the originInlineToken wrapped in the middle of inline tokens', () => {
+ const result = buildUneditableInlineTokens(originInlineToken);
+
+ expect(result).toHaveLength(3);
+ expect(result).toStrictEqual(uneditableInlineTokens);
+ });
+ });
+
+ describe('buildUneditableHtmlAsTextTokens', () => {
+ it('returns a 3-item array of tokens with the htmlBlockNode wrapped as a text token in the middle of block tokens', () => {
+ const htmlBlockNode = {
+ type: 'htmlBlock',
+ literal: '<div data-tomark-pass ><h1>Some header</h1><p>Some paragraph</p></div>',
+ };
+ const result = buildUneditableHtmlAsTextTokens(htmlBlockNode);
+ const { type, content } = result[1];
+
+ expect(type).toBe('text');
+ expect(content).not.toMatch(/ data-tomark-pass /);
+
+ expect(result).toHaveLength(3);
+ expect(result).toStrictEqual(uneditableBlockTokens);
+ });
+ });
+});
diff --git a/spec/frontend/vue_shared/components/rich_content_editor/services/renderers/mock_data.js b/spec/frontend/vue_shared/components/rich_content_editor/services/renderers/mock_data.js
new file mode 100644
index 00000000000..660c21281fd
--- /dev/null
+++ b/spec/frontend/vue_shared/components/rich_content_editor/services/renderers/mock_data.js
@@ -0,0 +1,58 @@
+// Node spec helpers
+
+export const buildMockTextNode = literal => {
+ return {
+ firstChild: null,
+ literal,
+ type: 'text',
+ };
+};
+
+export const normalTextNode = buildMockTextNode('This is just normal text.');
+
+// Token spec helpers
+
+const buildMockUneditableOpenToken = type => {
+ return {
+ type: 'openTag',
+ tagName: type,
+ attributes: { contenteditable: false },
+ classNames: [
+ 'gl-px-4 gl-py-2 gl-my-5 gl-opacity-5 gl-bg-gray-100 gl-user-select-none gl-cursor-not-allowed',
+ ],
+ };
+};
+
+const buildMockUneditableCloseToken = type => {
+ return { type: 'closeTag', tagName: type };
+};
+
+export const originToken = {
+ type: 'text',
+ tagName: null,
+ content: '{:.no_toc .hidden-md .hidden-lg}',
+};
+export const uneditableCloseToken = buildMockUneditableCloseToken('div');
+export const uneditableOpenTokens = [buildMockUneditableOpenToken('div'), originToken];
+export const uneditableCloseTokens = [originToken, uneditableCloseToken];
+export const uneditableTokens = [...uneditableOpenTokens, uneditableCloseToken];
+
+export const originInlineToken = {
+ type: 'text',
+ content: '<i>Inline</i> content',
+};
+export const uneditableInlineTokens = [
+ buildMockUneditableOpenToken('a'),
+ originInlineToken,
+ buildMockUneditableCloseToken('a'),
+];
+
+export const uneditableBlockTokens = [
+ buildMockUneditableOpenToken('div'),
+ {
+ type: 'text',
+ tagName: null,
+ content: '<div><h1>Some header</h1><p>Some paragraph</p></div>',
+ },
+ buildMockUneditableCloseToken('div'),
+];
diff --git a/spec/frontend/vue_shared/components/rich_content_editor/services/renderers/render_embedded_ruby_spec.js b/spec/frontend/vue_shared/components/rich_content_editor/services/renderers/render_embedded_ruby_spec.js
new file mode 100644
index 00000000000..b723ee8c8a0
--- /dev/null
+++ b/spec/frontend/vue_shared/components/rich_content_editor/services/renderers/render_embedded_ruby_spec.js
@@ -0,0 +1,30 @@
+import renderer from '~/vue_shared/components/rich_content_editor/services/renderers/render_embedded_ruby_text';
+import { buildUneditableTokens } from '~/vue_shared/components/rich_content_editor/services/renderers/build_uneditable_token';
+
+import { buildMockTextNode, normalTextNode } from './mock_data';
+
+const embeddedRubyTextNode = buildMockTextNode('<%= partial("some/path") %>');
+
+describe('Render Embedded Ruby Text renderer', () => {
+ describe('canRender', () => {
+ it('should return true when the argument `literal` has embedded ruby syntax', () => {
+ expect(renderer.canRender(embeddedRubyTextNode)).toBe(true);
+ });
+
+ it('should return false when the argument `literal` lacks embedded ruby syntax', () => {
+ expect(renderer.canRender(normalTextNode)).toBe(false);
+ });
+ });
+
+ describe('render', () => {
+ const origin = jest.fn();
+
+ it('should return uneditable tokens', () => {
+ const context = { origin };
+
+ expect(renderer.render(embeddedRubyTextNode, context)).toStrictEqual(
+ buildUneditableTokens(origin()),
+ );
+ });
+ });
+});
diff --git a/spec/frontend/vue_shared/components/rich_content_editor/services/renderers/render_font_awesome_html_inline_spec.js b/spec/frontend/vue_shared/components/rich_content_editor/services/renderers/render_font_awesome_html_inline_spec.js
new file mode 100644
index 00000000000..d6bb01259bb
--- /dev/null
+++ b/spec/frontend/vue_shared/components/rich_content_editor/services/renderers/render_font_awesome_html_inline_spec.js
@@ -0,0 +1,33 @@
+import renderer from '~/vue_shared/components/rich_content_editor/services/renderers/render_font_awesome_html_inline';
+import { buildUneditableInlineTokens } from '~/vue_shared/components/rich_content_editor/services/renderers/build_uneditable_token';
+
+import { normalTextNode } from './mock_data';
+
+const fontAwesomeInlineHtmlNode = {
+ firstChild: null,
+ literal: '<i class="far fa-paper-plane" id="biz-tech-icons">',
+ type: 'html',
+};
+
+describe('Render Font Awesome Inline HTML renderer', () => {
+ describe('canRender', () => {
+ it('should return true when the argument `literal` has font awesome inline html syntax', () => {
+ expect(renderer.canRender(fontAwesomeInlineHtmlNode)).toBe(true);
+ });
+
+ it('should return false when the argument `literal` lacks font awesome inline html syntax', () => {
+ expect(renderer.canRender(normalTextNode)).toBe(false);
+ });
+ });
+
+ describe('render', () => {
+ it('should return uneditable inline tokens', () => {
+ const token = { type: 'text', tagName: null, content: fontAwesomeInlineHtmlNode.literal };
+ const context = { origin: () => token };
+
+ expect(renderer.render(fontAwesomeInlineHtmlNode, context)).toStrictEqual(
+ buildUneditableInlineTokens(token),
+ );
+ });
+ });
+});
diff --git a/spec/frontend/vue_shared/components/rich_content_editor/services/renderers/render_html_block_spec.js b/spec/frontend/vue_shared/components/rich_content_editor/services/renderers/render_html_block_spec.js
new file mode 100644
index 00000000000..a6c712eeb31
--- /dev/null
+++ b/spec/frontend/vue_shared/components/rich_content_editor/services/renderers/render_html_block_spec.js
@@ -0,0 +1,38 @@
+import renderer from '~/vue_shared/components/rich_content_editor/services/renderers/render_html_block';
+import { buildUneditableHtmlAsTextTokens } from '~/vue_shared/components/rich_content_editor/services/renderers/build_uneditable_token';
+
+import { normalTextNode } from './mock_data';
+
+const htmlBlockNode = {
+ firstChild: null,
+ literal: '<div><h1>Heading</h1><p>Paragraph.</p></div>',
+ type: 'htmlBlock',
+};
+
+describe('Render HTML renderer', () => {
+ describe('canRender', () => {
+ it('should return true when the argument is an html block', () => {
+ expect(renderer.canRender(htmlBlockNode)).toBe(true);
+ });
+
+ it('should return false when the argument is not an html block', () => {
+ expect(renderer.canRender(normalTextNode)).toBe(false);
+ });
+ });
+
+ describe('render', () => {
+ const htmlBlockNodeToMark = {
+ firstChild: null,
+ literal: '<div data-to-mark ></div>',
+ type: 'htmlBlock',
+ };
+
+ it.each`
+ node
+ ${htmlBlockNode}
+ ${htmlBlockNodeToMark}
+ `('should return uneditable tokens wrapping the $node as a token', ({ node }) => {
+ expect(renderer.render(node)).toStrictEqual(buildUneditableHtmlAsTextTokens(node));
+ });
+ });
+});
diff --git a/spec/frontend/vue_shared/components/rich_content_editor/services/renderers/render_identifier_instance_text_spec.js b/spec/frontend/vue_shared/components/rich_content_editor/services/renderers/render_identifier_instance_text_spec.js
new file mode 100644
index 00000000000..2897929f1bf
--- /dev/null
+++ b/spec/frontend/vue_shared/components/rich_content_editor/services/renderers/render_identifier_instance_text_spec.js
@@ -0,0 +1,55 @@
+import renderer from '~/vue_shared/components/rich_content_editor/services/renderers/render_identifier_instance_text';
+import { buildUneditableInlineTokens } from '~/vue_shared/components/rich_content_editor/services/renderers/build_uneditable_token';
+
+import { buildMockTextNode, normalTextNode } from './mock_data';
+
+const mockTextStart = 'Majority example ';
+const mockTextMiddle = '[environment terraform plans][terraform]';
+const mockTextEnd = '.';
+const identifierInstanceStartTextNode = buildMockTextNode(mockTextStart);
+const identifierInstanceEndTextNode = buildMockTextNode(mockTextEnd);
+
+describe('Render Identifier Instance Text renderer', () => {
+ describe('canRender', () => {
+ it.each`
+ node | target
+ ${normalTextNode} | ${false}
+ ${identifierInstanceStartTextNode} | ${false}
+ ${identifierInstanceEndTextNode} | ${false}
+ ${buildMockTextNode(mockTextMiddle)} | ${true}
+ ${buildMockTextNode('Minority example [environment terraform plans][]')} | ${true}
+ ${buildMockTextNode('Minority example [environment terraform plans]')} | ${true}
+ `(
+ 'should return $target when the $node validates against identifier instance syntax',
+ ({ node, target }) => {
+ expect(renderer.canRender(node)).toBe(target);
+ },
+ );
+ });
+
+ describe('render', () => {
+ it.each`
+ start | middle | end
+ ${mockTextStart} | ${mockTextMiddle} | ${mockTextEnd}
+ ${mockTextStart} | ${'[environment terraform plans][]'} | ${mockTextEnd}
+ ${mockTextStart} | ${'[environment terraform plans]'} | ${mockTextEnd}
+ `(
+ 'should return inline editable, uneditable, and editable tokens in sequence',
+ ({ start, middle, end }) => {
+ const buildMockTextToken = content => ({ type: 'text', tagName: null, content });
+
+ const startToken = buildMockTextToken(start);
+ const middleToken = buildMockTextToken(middle);
+ const endToken = buildMockTextToken(end);
+
+ const content = `${start}${middle}${end}`;
+ const contentToken = buildMockTextToken(content);
+ const contentNode = buildMockTextNode(content);
+ const context = { origin: jest.fn().mockReturnValueOnce(contentToken) };
+ expect(renderer.render(contentNode, context)).toStrictEqual(
+ [startToken, buildUneditableInlineTokens(middleToken), endToken].flat(),
+ );
+ },
+ );
+ });
+});
diff --git a/spec/frontend/vue_shared/components/rich_content_editor/services/renderers/render_identifier_paragraph_spec.js b/spec/frontend/vue_shared/components/rich_content_editor/services/renderers/render_identifier_paragraph_spec.js
new file mode 100644
index 00000000000..320589e4de3
--- /dev/null
+++ b/spec/frontend/vue_shared/components/rich_content_editor/services/renderers/render_identifier_paragraph_spec.js
@@ -0,0 +1,65 @@
+import renderer from '~/vue_shared/components/rich_content_editor/services/renderers/render_identifier_paragraph';
+import {
+ buildUneditableOpenTokens,
+ buildUneditableCloseToken,
+} from '~/vue_shared/components/rich_content_editor/services/renderers/build_uneditable_token';
+
+import { buildMockTextNode } from './mock_data';
+
+const buildMockParagraphNode = literal => {
+ return {
+ firstChild: buildMockTextNode(literal),
+ type: 'paragraph',
+ };
+};
+
+const normalParagraphNode = buildMockParagraphNode(
+ 'This is just normal paragraph. It has multiple sentences.',
+);
+const identifierParagraphNode = buildMockParagraphNode(
+ `[another-identifier]: https://example.com "This example has a title" [identifier]: http://example1.com [this link]: http://example2.com`,
+);
+
+describe('Render Identifier Paragraph renderer', () => {
+ describe('canRender', () => {
+ it.each`
+ node | paragraph | target
+ ${identifierParagraphNode} | ${'[Some text]: https://link.com'} | ${true}
+ ${normalParagraphNode} | ${'Normal non-identifier text. Another sentence.'} | ${false}
+ `(
+ 'should return $target when the $node matches $paragraph syntax',
+ ({ node, paragraph, target }) => {
+ const context = {
+ entering: true,
+ getChildrenText: jest.fn().mockReturnValueOnce(paragraph),
+ };
+
+ expect(renderer.canRender(node, context)).toBe(target);
+ },
+ );
+ });
+
+ describe('render', () => {
+ let origin;
+
+ beforeEach(() => {
+ origin = jest.fn();
+ });
+
+ it('should return uneditable open tokens when entering', () => {
+ const context = { entering: true, origin };
+
+ expect(renderer.render(identifierParagraphNode, context)).toStrictEqual(
+ buildUneditableOpenTokens(origin()),
+ );
+ });
+
+ it('should return an uneditable close tokens when exiting', () => {
+ const context = { entering: false, origin };
+
+ expect(renderer.render(identifierParagraphNode, context)).toStrictEqual(
+ buildUneditableCloseToken(origin()),
+ );
+ });
+ });
+});
diff --git a/spec/frontend/vue_shared/components/rich_content_editor/services/renderers/render_kramdown_list_spec.js b/spec/frontend/vue_shared/components/rich_content_editor/services/renderers/render_kramdown_list_spec.js
new file mode 100644
index 00000000000..e60bf1c8c92
--- /dev/null
+++ b/spec/frontend/vue_shared/components/rich_content_editor/services/renderers/render_kramdown_list_spec.js
@@ -0,0 +1,55 @@
+import renderer from '~/vue_shared/components/rich_content_editor/services/renderers/render_kramdown_list';
+import {
+ buildUneditableOpenTokens,
+ buildUneditableCloseToken,
+} from '~/vue_shared/components/rich_content_editor/services/renderers/build_uneditable_token';
+
+import { buildMockTextNode } from './mock_data';
+
+const buildMockListNode = literal => {
+ return {
+ firstChild: {
+ firstChild: {
+ firstChild: buildMockTextNode(literal),
+ type: 'paragraph',
+ },
+ type: 'item',
+ },
+ type: 'list',
+ };
+};
+
+const normalListNode = buildMockListNode('Just another bullet point');
+const kramdownListNode = buildMockListNode('TOC');
+
+describe('Render Kramdown List renderer', () => {
+ describe('canRender', () => {
+ it('should return true when the argument is a special kramdown TOC ordered/unordered list', () => {
+ expect(renderer.canRender(kramdownListNode)).toBe(true);
+ });
+
+ it('should return false when the argument is a normal ordered/unordered list', () => {
+ expect(renderer.canRender(normalListNode)).toBe(false);
+ });
+ });
+
+ describe('render', () => {
+ const origin = jest.fn();
+
+ it('should return uneditable open tokens when entering', () => {
+ const context = { entering: true, origin };
+
+ expect(renderer.render(kramdownListNode, context)).toStrictEqual(
+ buildUneditableOpenTokens(origin()),
+ );
+ });
+
+ it('should return an uneditable close tokens when exiting', () => {
+ const context = { entering: false, origin };
+
+ expect(renderer.render(kramdownListNode, context)).toStrictEqual(
+ buildUneditableCloseToken(origin()),
+ );
+ });
+ });
+});
diff --git a/spec/frontend/vue_shared/components/rich_content_editor/services/renderers/render_kramdown_text_spec.js b/spec/frontend/vue_shared/components/rich_content_editor/services/renderers/render_kramdown_text_spec.js
new file mode 100644
index 00000000000..97ff9794e69
--- /dev/null
+++ b/spec/frontend/vue_shared/components/rich_content_editor/services/renderers/render_kramdown_text_spec.js
@@ -0,0 +1,30 @@
+import renderer from '~/vue_shared/components/rich_content_editor/services/renderers/render_kramdown_text';
+import { buildUneditableTokens } from '~/vue_shared/components/rich_content_editor/services/renderers/build_uneditable_token';
+
+import { buildMockTextNode, normalTextNode } from './mock_data';
+
+const kramdownTextNode = buildMockTextNode('{:toc}');
+
+describe('Render Kramdown Text renderer', () => {
+ describe('canRender', () => {
+ it('should return true when the argument `literal` has kramdown syntax', () => {
+ expect(renderer.canRender(kramdownTextNode)).toBe(true);
+ });
+
+ it('should return false when the argument `literal` lacks kramdown syntax', () => {
+ expect(renderer.canRender(normalTextNode)).toBe(false);
+ });
+ });
+
+ describe('render', () => {
+ const origin = jest.fn();
+
+ it('should return uneditable tokens', () => {
+ const context = { origin };
+
+ expect(renderer.render(kramdownTextNode, context)).toStrictEqual(
+ buildUneditableTokens(origin()),
+ );
+ });
+ });
+});
diff --git a/spec/frontend/vue_shared/components/sidebar/labels_select/dropdown_button_spec.js b/spec/frontend/vue_shared/components/sidebar/labels_select/dropdown_button_spec.js
index d02d924bd2b..79851e5db05 100644
--- a/spec/frontend/vue_shared/components/sidebar/labels_select/dropdown_button_spec.js
+++ b/spec/frontend/vue_shared/components/sidebar/labels_select/dropdown_button_spec.js
@@ -82,10 +82,9 @@ describe('DropdownButtonComponent', () => {
});
it('renders dropdown button icon', () => {
- const dropdownIconEl = vm.$el.querySelector('i.fa');
+ const dropdownIconEl = vm.$el.querySelector('.dropdown-menu-toggle .gl-icon');
expect(dropdownIconEl).not.toBeNull();
- expect(dropdownIconEl.classList.contains('fa-chevron-down')).toBe(true);
});
});
});
diff --git a/spec/frontend/vue_shared/components/sidebar/labels_select/dropdown_search_input_spec.js b/spec/frontend/vue_shared/components/sidebar/labels_select/dropdown_search_input_spec.js
index 035af946d75..510e537b1cd 100644
--- a/spec/frontend/vue_shared/components/sidebar/labels_select/dropdown_search_input_spec.js
+++ b/spec/frontend/vue_shared/components/sidebar/labels_select/dropdown_search_input_spec.js
@@ -29,13 +29,11 @@ describe('DropdownSearchInputComponent', () => {
});
it('renders search icon element', () => {
- expect(vm.$el.querySelector('.fa-search.dropdown-input-search')).not.toBeNull();
+ expect(vm.$el.querySelector('.dropdown-input-search')).not.toBeNull();
});
it('renders clear search icon element', () => {
- expect(
- vm.$el.querySelector('.fa-times.dropdown-input-clear.js-dropdown-input-clear'),
- ).not.toBeNull();
+ expect(vm.$el.querySelector('.dropdown-input-clear.js-dropdown-input-clear')).not.toBeNull();
});
});
});
diff --git a/spec/frontend/vue_shared/components/sidebar/labels_select_vue/dropdown_button_spec.js b/spec/frontend/vue_shared/components/sidebar/labels_select_vue/dropdown_button_spec.js
index 214eb239432..68c9d26bb1a 100644
--- a/spec/frontend/vue_shared/components/sidebar/labels_select_vue/dropdown_button_spec.js
+++ b/spec/frontend/vue_shared/components/sidebar/labels_select_vue/dropdown_button_spec.js
@@ -1,18 +1,19 @@
import Vuex from 'vuex';
import { shallowMount, createLocalVue } from '@vue/test-utils';
-import { GlIcon } from '@gitlab/ui';
+import { GlIcon, GlButton } from '@gitlab/ui';
import DropdownButton from '~/vue_shared/components/sidebar/labels_select_vue/dropdown_button.vue';
import labelSelectModule from '~/vue_shared/components/sidebar/labels_select_vue/store';
import { mockConfig } from './mock_data';
+let store;
const localVue = createLocalVue();
localVue.use(Vuex);
const createComponent = (initialState = mockConfig) => {
- const store = new Vuex.Store(labelSelectModule());
+ store = new Vuex.Store(labelSelectModule());
store.dispatch('setInitialState', initialState);
@@ -33,26 +34,32 @@ describe('DropdownButton', () => {
wrapper.destroy();
});
+ const findDropdownButton = () => wrapper.find(GlButton);
+ const findDropdownText = () => wrapper.find('.dropdown-toggle-text');
+ const findDropdownIcon = () => wrapper.find(GlIcon);
+
describe('methods', () => {
describe('handleButtonClick', () => {
- it('calls action `toggleDropdownContents` and stops event propagation when `state.variant` is "standalone"', () => {
- const event = {
- stopPropagation: jest.fn(),
- };
- wrapper = createComponent({
- ...mockConfig,
- variant: 'standalone',
- });
-
- jest.spyOn(wrapper.vm, 'toggleDropdownContents');
-
- wrapper.vm.handleButtonClick(event);
-
- expect(wrapper.vm.toggleDropdownContents).toHaveBeenCalled();
- expect(event.stopPropagation).toHaveBeenCalled();
-
- wrapper.destroy();
- });
+ it.each`
+ variant
+ ${'standalone'}
+ ${'embedded'}
+ `(
+ 'toggles dropdown content and stops event propagation when `state.variant` is "$variant"',
+ ({ variant }) => {
+ const event = { stopPropagation: jest.fn() };
+
+ wrapper = createComponent({
+ ...mockConfig,
+ variant,
+ });
+
+ findDropdownButton().vm.$emit('click', event);
+
+ expect(store.state.showDropdownContents).toBe(true);
+ expect(event.stopPropagation).toHaveBeenCalled();
+ },
+ );
});
});
@@ -61,15 +68,24 @@ describe('DropdownButton', () => {
expect(wrapper.is('gl-button-stub')).toBe(true);
});
- it('renders button text element', () => {
- const dropdownTextEl = wrapper.find('.dropdown-toggle-text');
+ it('renders default button text element', () => {
+ const dropdownTextEl = findDropdownText();
expect(dropdownTextEl.exists()).toBe(true);
expect(dropdownTextEl.text()).toBe('Label');
});
+ it('renders provided button text element', () => {
+ store.state.dropdownButtonText = 'Custom label';
+ const dropdownTextEl = findDropdownText();
+
+ return wrapper.vm.$nextTick().then(() => {
+ expect(dropdownTextEl.text()).toBe('Custom label');
+ });
+ });
+
it('renders chevron icon element', () => {
- const iconEl = wrapper.find(GlIcon);
+ const iconEl = findDropdownIcon();
expect(iconEl.exists()).toBe(true);
expect(iconEl.props('name')).toBe('chevron-down');
diff --git a/spec/frontend/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_labels_view_spec.js b/spec/frontend/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_labels_view_spec.js
index 1504e1521d3..9b01e0b9637 100644
--- a/spec/frontend/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_labels_view_spec.js
+++ b/spec/frontend/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_labels_view_spec.js
@@ -44,6 +44,7 @@ const createComponent = (initialState = mockConfig) => {
describe('DropdownContentsLabelsView', () => {
let wrapper;
let wrapperStandalone;
+ let wrapperEmbedded;
beforeEach(() => {
wrapper = createComponent();
@@ -51,11 +52,16 @@ describe('DropdownContentsLabelsView', () => {
...mockConfig,
variant: 'standalone',
});
+ wrapperEmbedded = createComponent({
+ ...mockConfig,
+ variant: 'embedded',
+ });
});
afterEach(() => {
wrapper.destroy();
wrapperStandalone.destroy();
+ wrapperEmbedded.destroy();
});
describe('computed', () => {
@@ -211,6 +217,10 @@ describe('DropdownContentsLabelsView', () => {
expect(wrapperStandalone.find('.dropdown-title').exists()).toBe(false);
});
+ it('renders dropdown title element when `state.variant` is "embedded"', () => {
+ expect(wrapperEmbedded.find('.dropdown-title').exists()).toBe(true);
+ });
+
it('renders dropdown close button element', () => {
const closeButtonEl = wrapper.find('.dropdown-title').find(GlButton);
@@ -291,5 +301,9 @@ describe('DropdownContentsLabelsView', () => {
it('does not render footer list items when `state.variant` is "standalone"', () => {
expect(wrapperStandalone.find('.dropdown-footer').exists()).toBe(false);
});
+
+ it('renders footer list items when `state.variant` is "embedded"', () => {
+ expect(wrapperEmbedded.find('.dropdown-footer').exists()).toBe(true);
+ });
});
});
diff --git a/spec/frontend/vue_shared/components/sidebar/labels_select_vue/labels_select_root_spec.js b/spec/frontend/vue_shared/components/sidebar/labels_select_vue/labels_select_root_spec.js
index ee4e9090e5d..6e97b046be2 100644
--- a/spec/frontend/vue_shared/components/sidebar/labels_select_vue/labels_select_root_spec.js
+++ b/spec/frontend/vue_shared/components/sidebar/labels_select_vue/labels_select_root_spec.js
@@ -89,18 +89,23 @@ describe('LabelsSelectRoot', () => {
expect(wrapper.attributes('class')).toContain('labels-select-wrapper position-relative');
});
- it('renders component root element with CSS class `is-standalone` when `state.variant` is "standalone"', () => {
- const wrapperStandalone = createComponent({
- ...mockConfig,
- variant: 'standalone',
- });
-
- return wrapperStandalone.vm.$nextTick(() => {
- expect(wrapperStandalone.classes()).toContain('is-standalone');
-
- wrapperStandalone.destroy();
- });
- });
+ it.each`
+ variant | cssClass
+ ${'standalone'} | ${'is-standalone'}
+ ${'embedded'} | ${'is-embedded'}
+ `(
+ 'renders component root element with CSS class `$cssClass` when `state.variant` is "$variant"',
+ ({ variant, cssClass }) => {
+ wrapper = createComponent({
+ ...mockConfig,
+ variant,
+ });
+
+ return wrapper.vm.$nextTick(() => {
+ expect(wrapper.classes()).toContain(cssClass);
+ });
+ },
+ );
it('renders `dropdown-value-collapsed` component when `allowLabelCreate` prop is `true`', () => {
expect(wrapper.find(DropdownValueCollapsed).exists()).toBe(true);
diff --git a/spec/frontend/vue_shared/components/sidebar/labels_select_vue/store/getters_spec.js b/spec/frontend/vue_shared/components/sidebar/labels_select_vue/store/getters_spec.js
index b866117efcf..52116f757c5 100644
--- a/spec/frontend/vue_shared/components/sidebar/labels_select_vue/store/getters_spec.js
+++ b/spec/frontend/vue_shared/components/sidebar/labels_select_vue/store/getters_spec.js
@@ -2,13 +2,20 @@ import * as getters from '~/vue_shared/components/sidebar/labels_select_vue/stor
describe('LabelsSelect Getters', () => {
describe('dropdownButtonText', () => {
- it('returns string "Label" when state.labels has no selected labels', () => {
- const labels = [{ id: 1 }, { id: 2 }, { id: 3 }, { id: 4 }];
-
- expect(getters.dropdownButtonText({ labels }, { isDropdownVariantSidebar: true })).toBe(
- 'Label',
- );
- });
+ it.each`
+ labelType | dropdownButtonText | expected
+ ${'default'} | ${''} | ${'Label'}
+ ${'custom'} | ${'Custom label'} | ${'Custom label'}
+ `(
+ 'returns $labelType text when state.labels has no selected labels',
+ ({ dropdownButtonText, expected }) => {
+ const labels = [{ id: 1 }, { id: 2 }, { id: 3 }, { id: 4 }];
+ const selectedLabels = [];
+ const state = { labels, selectedLabels, dropdownButtonText };
+
+ expect(getters.dropdownButtonText(state, {})).toBe(expected);
+ },
+ );
it('returns label title when state.labels has only 1 label', () => {
const labels = [{ id: 1, title: 'Foobar', set: true }];
diff --git a/spec/frontend/vue_shared/components/user_popover/user_popover_spec.js b/spec/frontend/vue_shared/components/user_popover/user_popover_spec.js
index 2c7fce714f0..a4ff6ac0c16 100644
--- a/spec/frontend/vue_shared/components/user_popover/user_popover_spec.js
+++ b/spec/frontend/vue_shared/components/user_popover/user_popover_spec.js
@@ -4,7 +4,6 @@ import UserPopover from '~/vue_shared/components/user_popover/user_popover.vue';
import Icon from '~/vue_shared/components/icon.vue';
const DEFAULT_PROPS = {
- loaded: true,
user: {
username: 'root',
name: 'Administrator',
@@ -12,6 +11,7 @@ const DEFAULT_PROPS = {
bio: null,
workInformation: null,
status: null,
+ loaded: true,
},
};
@@ -46,28 +46,21 @@ describe('User Popover Component', () => {
});
};
- describe('Empty', () => {
- beforeEach(() => {
- createWrapper(
- {},
- {
- propsData: {
- target: findTarget(),
- user: {
- name: null,
- username: null,
- location: null,
- bio: null,
- workInformation: null,
- status: null,
- },
- },
+ describe('when user is loading', () => {
+ it('displays skeleton loaders', () => {
+ createWrapper({
+ user: {
+ name: null,
+ username: null,
+ location: null,
+ bio: null,
+ workInformation: null,
+ status: null,
+ loaded: false,
},
- );
- });
+ });
- it('should return skeleton loaders', () => {
- expect(wrapper.find(GlSkeletonLoading).exists()).toBe(true);
+ expect(wrapper.findAll(GlSkeletonLoading)).toHaveLength(4);
});
});
@@ -90,9 +83,10 @@ describe('User Popover Component', () => {
describe('job data', () => {
const findWorkInformation = () => wrapper.find({ ref: 'workInformation' });
const findBio = () => wrapper.find({ ref: 'bio' });
+ const bio = 'My super interesting bio';
it('should show only bio if work information is not available', () => {
- const user = { ...DEFAULT_PROPS.user, bio: 'My super interesting bio' };
+ const user = { ...DEFAULT_PROPS.user, bio, bioHtml: bio };
createWrapper({ user });
@@ -114,7 +108,8 @@ describe('User Popover Component', () => {
it('should display bio and work information in separate lines', () => {
const user = {
...DEFAULT_PROPS.user,
- bio: 'My super interesting bio',
+ bio,
+ bioHtml: bio,
workInformation: 'Frontend Engineer at GitLab',
};
@@ -127,12 +122,13 @@ describe('User Popover Component', () => {
it('should not encode special characters in bio', () => {
const user = {
...DEFAULT_PROPS.user,
- bio: 'I like <html> & CSS',
+ bio: 'I like CSS',
+ bioHtml: 'I like <b>CSS</b>',
};
createWrapper({ user });
- expect(findBio().text()).toBe('I like <html> & CSS');
+ expect(findBio().html()).toContain('I like <b>CSS</b>');
});
it('shows icon for bio', () => {
diff --git a/spec/frontend/wikis_spec.js b/spec/frontend/wikis_spec.js
index 8c68edafd16..3469be4da1c 100644
--- a/spec/frontend/wikis_spec.js
+++ b/spec/frontend/wikis_spec.js
@@ -1,4 +1,6 @@
+import { escape } from 'lodash';
import Wikis from '~/pages/shared/wikis/wikis';
+import Tracking from '~/tracking';
import { setHTMLFixture } from './helpers/fixtures';
describe('Wikis', () => {
@@ -122,4 +124,32 @@ describe('Wikis', () => {
});
});
});
+
+ describe('trackPageView', () => {
+ const trackingPage = 'projects:wikis:show';
+ const trackingContext = { foo: 'bar' };
+ const showPageHtmlFixture = `
+ <div class="js-wiki-page-content" data-tracking-context="${escape(
+ JSON.stringify(trackingContext),
+ )}"></div>
+ `;
+
+ beforeEach(() => {
+ setHTMLFixture(showPageHtmlFixture);
+ document.body.dataset.page = trackingPage;
+ jest.spyOn(Tracking, 'event').mockImplementation();
+
+ Wikis.trackPageView();
+ });
+
+ it('sends the tracking event and context', () => {
+ expect(Tracking.event).toHaveBeenCalledWith(trackingPage, 'view_wiki_page', {
+ label: 'view_wiki_page',
+ context: {
+ schema: 'iglu:com.gitlab/wiki_page_context/jsonschema/1-0-0',
+ data: trackingContext,
+ },
+ });
+ });
+ });
});
diff --git a/spec/frontend_integration/ide/__snapshots__/ide_integration_spec.js.snap b/spec/frontend_integration/ide/__snapshots__/ide_integration_spec.js.snap
index fe714924c2b..6beb5eab6db 100644
--- a/spec/frontend_integration/ide/__snapshots__/ide_integration_spec.js.snap
+++ b/spec/frontend_integration/ide/__snapshots__/ide_integration_spec.js.snap
@@ -112,7 +112,6 @@ exports[`WebIDE runs 1`] = `
class="gl-spinner-container"
>
<span
- aria-hidden="true"
aria-label="Loading"
class="align-text-bottom gl-spinner gl-spinner-orange gl-spinner-md"
/>
diff --git a/spec/graphql/mutations/alert_management/alerts/set_assignees_spec.rb b/spec/graphql/mutations/alert_management/alerts/set_assignees_spec.rb
index a025b3d344a..f8b61c5064a 100644
--- a/spec/graphql/mutations/alert_management/alerts/set_assignees_spec.rb
+++ b/spec/graphql/mutations/alert_management/alerts/set_assignees_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Mutations::AlertManagement::Alerts::SetAssignees do
+RSpec.describe Mutations::AlertManagement::Alerts::SetAssignees do
let_it_be(:starting_assignee) { create(:user) }
let_it_be(:unassigned_user) { create(:user) }
let_it_be(:alert) { create(:alert_management_alert, assignees: [starting_assignee]) }
diff --git a/spec/graphql/mutations/alert_management/alerts/todo/create_spec.rb b/spec/graphql/mutations/alert_management/alerts/todo/create_spec.rb
new file mode 100644
index 00000000000..11ee40a4c7e
--- /dev/null
+++ b/spec/graphql/mutations/alert_management/alerts/todo/create_spec.rb
@@ -0,0 +1,58 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Mutations::AlertManagement::Alerts::Todo::Create do
+ subject(:mutation) { described_class.new(object: project, context: { current_user: current_user }, field: nil) }
+
+ let_it_be(:alert) { create(:alert_management_alert) }
+ let_it_be(:project) { alert.project }
+ let(:current_user) { project.owner }
+
+ let(:args) { { project_path: project.full_path, iid: alert.iid } }
+
+ specify { expect(described_class).to require_graphql_authorizations(:update_alert_management_alert) }
+
+ describe '#resolve' do
+ subject(:resolve) { mutation.resolve(args) }
+
+ context 'when user does not have permissions' do
+ let(:current_user) { nil }
+
+ specify { expect { resolve }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable) }
+ end
+
+ context 'when project is invalid' do
+ let(:args) { { project_path: 'bunk/path', iid: alert.iid } }
+
+ specify { expect { resolve }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable) }
+ end
+
+ context 'when alert is invalid' do
+ let(:args) { { project_path: project.full_path, iid: "-1" } }
+
+ specify { expect { resolve }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable) }
+ end
+
+ context 'when the create service yields errors' do
+ let(:error_response) { double(error?: true, message: 'error', payload: { alert: {} }) }
+
+ before do
+ allow_next_instance_of(::AlertManagement::Alerts::Todo::CreateService) do |service|
+ allow(service).to receive(:execute).and_return(error_response)
+ end
+ end
+
+ specify { expect { resolve }.not_to change(Todo, :count) }
+ specify { expect(resolve[:errors]).to eq([error_response.message]) }
+ end
+
+ context 'with valid inputs' do
+ it 'creates a new todo' do
+ expect { resolve }.to change { Todo.where(user: current_user, action: Todo::MARKED).count }.by(1)
+ end
+
+ it { is_expected.to eq(alert: alert, todo: Todo.last, errors: []) }
+ end
+ end
+end
diff --git a/spec/graphql/mutations/alert_management/update_alert_status_spec.rb b/spec/graphql/mutations/alert_management/update_alert_status_spec.rb
index 68513c02040..a224b564de9 100644
--- a/spec/graphql/mutations/alert_management/update_alert_status_spec.rb
+++ b/spec/graphql/mutations/alert_management/update_alert_status_spec.rb
@@ -39,7 +39,7 @@ RSpec.describe Mutations::AlertManagement::UpdateAlertStatus do
allow(alert).to receive(:save).and_return(false)
allow(alert).to receive(:errors).and_return(
- double(full_messages: %w(foo bar))
+ double(full_messages: %w(foo bar), :[] => nil)
)
expect(resolve).to eq(
alert: alert,
diff --git a/spec/graphql/mutations/concerns/mutations/resolves_issuable_spec.rb b/spec/graphql/mutations/concerns/mutations/resolves_issuable_spec.rb
index 706a54931ea..8af3868ba98 100644
--- a/spec/graphql/mutations/concerns/mutations/resolves_issuable_spec.rb
+++ b/spec/graphql/mutations/concerns/mutations/resolves_issuable_spec.rb
@@ -3,26 +3,31 @@
require 'spec_helper'
RSpec.describe Mutations::ResolvesIssuable do
+ include GraphqlHelpers
+
let_it_be(:mutation_class) do
Class.new(Mutations::BaseMutation) do
include Mutations::ResolvesIssuable
end
end
- let_it_be(:project) { create(:project) }
+ let_it_be(:project) { create(:project, :empty_repo) }
let_it_be(:user) { create(:user) }
let_it_be(:context) { { current_user: user } }
- let_it_be(:mutation) { mutation_class.new(object: nil, context: context, field: nil) }
+ let(:mutation) { mutation_class.new(object: nil, context: context, field: nil) }
let(:parent) { issuable.project }
+ let_it_be(:issue) { create(:issue, project: project) }
+ let_it_be(:merge_request) { create(:merge_request, source_project: project) }
+
context 'with issues' do
- let(:issuable) { create(:issue, project: project) }
+ let(:issuable) { issue }
it_behaves_like 'resolving an issuable in GraphQL', :issue
end
context 'with merge requests' do
- let(:issuable) { create(:merge_request, source_project: project) }
+ let(:issuable) { merge_request }
it_behaves_like 'resolving an issuable in GraphQL', :merge_request
end
diff --git a/spec/graphql/mutations/container_expiration_policies/update_spec.rb b/spec/graphql/mutations/container_expiration_policies/update_spec.rb
index fc90f437576..6aedaab3b53 100644
--- a/spec/graphql/mutations/container_expiration_policies/update_spec.rb
+++ b/spec/graphql/mutations/container_expiration_policies/update_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Mutations::ContainerExpirationPolicies::Update do
+RSpec.describe Mutations::ContainerExpirationPolicies::Update do
using RSpec::Parameterized::TableSyntax
let_it_be(:project, reload: true) { create(:project) }
diff --git a/spec/graphql/mutations/issues/set_locked_spec.rb b/spec/graphql/mutations/issues/set_locked_spec.rb
new file mode 100644
index 00000000000..10438226c17
--- /dev/null
+++ b/spec/graphql/mutations/issues/set_locked_spec.rb
@@ -0,0 +1,46 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Mutations::Issues::SetLocked do
+ let_it_be(:issue) { create(:issue) }
+ let_it_be(:user) { create(:user) }
+
+ subject(:mutation) { described_class.new(object: nil, context: { current_user: user }, field: nil) }
+
+ specify { expect(described_class).to require_graphql_authorizations(:update_issue) }
+
+ describe '#resolve' do
+ let(:locked) { true }
+
+ subject { mutation.resolve(project_path: issue.project.full_path, iid: issue.iid, locked: locked) }
+
+ it 'raises an error if the resource is not accessible to the user' do
+ expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
+ end
+
+ context 'when the user can update the issue' do
+ let(:mutated_issue) { subject[:issue] }
+
+ before do
+ issue.project.add_developer(user)
+ end
+
+ it 'returns the issue as discussion locked' do
+ expect(mutated_issue).to eq(issue)
+ expect(mutated_issue).to be_discussion_locked
+ expect(subject[:errors]).to be_empty
+ end
+
+ context 'when passing locked as false' do
+ let(:locked) { false }
+
+ it 'unlocks the discussion' do
+ issue.update!(discussion_locked: true)
+
+ expect(mutated_issue).not_to be_discussion_locked
+ end
+ end
+ end
+ end
+end
diff --git a/spec/graphql/mutations/issues/update_spec.rb b/spec/graphql/mutations/issues/update_spec.rb
index 8c3d01918fd..9a847476e2e 100644
--- a/spec/graphql/mutations/issues/update_spec.rb
+++ b/spec/graphql/mutations/issues/update_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Mutations::Issues::Update do
+RSpec.describe Mutations::Issues::Update do
let(:issue) { create(:issue) }
let(:user) { create(:user) }
let(:expected_attributes) do
diff --git a/spec/graphql/mutations/merge_requests/create_spec.rb b/spec/graphql/mutations/merge_requests/create_spec.rb
index 88acd3ed5b6..ae31790f1f9 100644
--- a/spec/graphql/mutations/merge_requests/create_spec.rb
+++ b/spec/graphql/mutations/merge_requests/create_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Mutations::MergeRequests::Create do
+RSpec.describe Mutations::MergeRequests::Create do
subject(:mutation) { described_class.new(object: nil, context: context, field: nil) }
let_it_be(:project) { create(:project, :public, :repository) }
diff --git a/spec/graphql/mutations/merge_requests/set_assignees_spec.rb b/spec/graphql/mutations/merge_requests/set_assignees_spec.rb
index d88c5db05c9..0e7abb849c4 100644
--- a/spec/graphql/mutations/merge_requests/set_assignees_spec.rb
+++ b/spec/graphql/mutations/merge_requests/set_assignees_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Mutations::MergeRequests::SetAssignees do
+RSpec.describe Mutations::MergeRequests::SetAssignees do
let(:merge_request) { create(:merge_request) }
let(:user) { create(:user) }
diff --git a/spec/graphql/mutations/merge_requests/set_labels_spec.rb b/spec/graphql/mutations/merge_requests/set_labels_spec.rb
index 0fd2c20a5c8..62a7f650f84 100644
--- a/spec/graphql/mutations/merge_requests/set_labels_spec.rb
+++ b/spec/graphql/mutations/merge_requests/set_labels_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Mutations::MergeRequests::SetLabels do
+RSpec.describe Mutations::MergeRequests::SetLabels do
let(:merge_request) { create(:merge_request) }
let(:user) { create(:user) }
diff --git a/spec/graphql/mutations/merge_requests/set_locked_spec.rb b/spec/graphql/mutations/merge_requests/set_locked_spec.rb
index d5219c781fd..aca7df5445f 100644
--- a/spec/graphql/mutations/merge_requests/set_locked_spec.rb
+++ b/spec/graphql/mutations/merge_requests/set_locked_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Mutations::MergeRequests::SetLocked do
+RSpec.describe Mutations::MergeRequests::SetLocked do
let(:merge_request) { create(:merge_request) }
let(:user) { create(:user) }
diff --git a/spec/graphql/mutations/merge_requests/set_milestone_spec.rb b/spec/graphql/mutations/merge_requests/set_milestone_spec.rb
index d77ec4de4d0..1c0d655ee83 100644
--- a/spec/graphql/mutations/merge_requests/set_milestone_spec.rb
+++ b/spec/graphql/mutations/merge_requests/set_milestone_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Mutations::MergeRequests::SetMilestone do
+RSpec.describe Mutations::MergeRequests::SetMilestone do
let(:merge_request) { create(:merge_request) }
let(:user) { create(:user) }
diff --git a/spec/graphql/mutations/merge_requests/set_subscription_spec.rb b/spec/graphql/mutations/merge_requests/set_subscription_spec.rb
index cf569a74aa9..20cfed9dd3d 100644
--- a/spec/graphql/mutations/merge_requests/set_subscription_spec.rb
+++ b/spec/graphql/mutations/merge_requests/set_subscription_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Mutations::MergeRequests::SetSubscription do
+RSpec.describe Mutations::MergeRequests::SetSubscription do
let(:merge_request) { create(:merge_request) }
let(:project) { merge_request.project }
let(:user) { create(:user) }
diff --git a/spec/graphql/mutations/merge_requests/set_wip_spec.rb b/spec/graphql/mutations/merge_requests/set_wip_spec.rb
index 7255d0fe7d7..b6cb49724fa 100644
--- a/spec/graphql/mutations/merge_requests/set_wip_spec.rb
+++ b/spec/graphql/mutations/merge_requests/set_wip_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Mutations::MergeRequests::SetWip do
+RSpec.describe Mutations::MergeRequests::SetWip do
let(:merge_request) { create(:merge_request) }
let(:user) { create(:user) }
diff --git a/spec/graphql/mutations/merge_requests/update_spec.rb b/spec/graphql/mutations/merge_requests/update_spec.rb
new file mode 100644
index 00000000000..4a1fdf6e74b
--- /dev/null
+++ b/spec/graphql/mutations/merge_requests/update_spec.rb
@@ -0,0 +1,68 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Mutations::MergeRequests::Update do
+ let(:merge_request) { create(:merge_request) }
+ let(:user) { create(:user) }
+
+ subject(:mutation) { described_class.new(object: nil, context: { current_user: user }, field: nil) }
+
+ specify { expect(described_class).to require_graphql_authorizations(:update_merge_request) }
+
+ describe '#resolve' do
+ let(:attributes) { { title: 'new title', description: 'new description', target_branch: 'new-branch' } }
+ let(:mutated_merge_request) { subject[:merge_request] }
+
+ subject do
+ mutation.resolve(project_path: merge_request.project.full_path, iid: merge_request.iid, **attributes)
+ end
+
+ it 'raises an error if the resource is not accessible to the user' do
+ expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
+ end
+
+ context 'when the user can update the merge request' do
+ before do
+ merge_request.project.add_developer(user)
+ end
+
+ it 'applies all attributes' do
+ expect(mutated_merge_request).to eq(merge_request)
+ expect(mutated_merge_request).to have_attributes(attributes)
+ expect(subject[:errors]).to be_empty
+ end
+
+ context 'the merge request is invalid' do
+ before do
+ merge_request.allow_broken = true
+ merge_request.update!(source_project: nil)
+ end
+
+ it 'returns error information, and changes were not applied' do
+ expect(mutated_merge_request).not_to have_attributes(attributes)
+ expect(subject[:errors]).not_to be_empty
+ end
+ end
+
+ context 'our change is invalid' do
+ let(:attributes) { { target_branch: 'this is not a branch' } }
+
+ it 'returns error information, and changes were not applied' do
+ expect(mutated_merge_request).not_to have_attributes(attributes)
+ expect(subject[:errors]).not_to be_empty
+ end
+ end
+
+ context 'when passing subset of attributes' do
+ let(:attributes) { { title: 'no, this title' } }
+
+ it 'only changes the mentioned attributes' do
+ expect { subject }.not_to change { merge_request.reset.description }
+
+ expect(mutated_merge_request).to have_attributes(attributes)
+ end
+ end
+ end
+ end
+end
diff --git a/spec/graphql/mutations/todos/mark_all_done_spec.rb b/spec/graphql/mutations/todos/mark_all_done_spec.rb
index 4af00307969..2f167164050 100644
--- a/spec/graphql/mutations/todos/mark_all_done_spec.rb
+++ b/spec/graphql/mutations/todos/mark_all_done_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Mutations::Todos::MarkAllDone do
+RSpec.describe Mutations::Todos::MarkAllDone do
include GraphqlHelpers
let_it_be(:current_user) { create(:user) }
@@ -21,7 +21,7 @@ describe Mutations::Todos::MarkAllDone do
describe '#resolve' do
it 'marks all pending todos as done' do
- updated_todo_ids = mutation_for(current_user).resolve.dig(:updated_ids)
+ updated_todo_ids, todos = mutation_for(current_user).resolve.values_at(:updated_ids, :todos)
expect(todo1.reload.state).to eq('done')
expect(todo2.reload.state).to eq('done')
@@ -29,6 +29,7 @@ describe Mutations::Todos::MarkAllDone do
expect(other_user_todo.reload.state).to eq('pending')
expect(updated_todo_ids).to contain_exactly(global_id_of(todo1), global_id_of(todo3))
+ expect(todos).to contain_exactly(todo1, todo3)
end
it 'behaves as expected if there are no todos for the requesting user' do
diff --git a/spec/graphql/mutations/todos/mark_done_spec.rb b/spec/graphql/mutations/todos/mark_done_spec.rb
index 44065f83f74..51ad3e1a5d7 100644
--- a/spec/graphql/mutations/todos/mark_done_spec.rb
+++ b/spec/graphql/mutations/todos/mark_done_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Mutations::Todos::MarkDone do
+RSpec.describe Mutations::Todos::MarkDone do
include GraphqlHelpers
let_it_be(:current_user) { create(:user) }
diff --git a/spec/graphql/mutations/todos/restore_many_spec.rb b/spec/graphql/mutations/todos/restore_many_spec.rb
index 8f4a8985f9e..b3b3e057745 100644
--- a/spec/graphql/mutations/todos/restore_many_spec.rb
+++ b/spec/graphql/mutations/todos/restore_many_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Mutations::Todos::RestoreMany do
+RSpec.describe Mutations::Todos::RestoreMany do
let_it_be(:current_user) { create(:user) }
let_it_be(:author) { create(:user) }
let_it_be(:other_user) { create(:user) }
@@ -25,6 +25,8 @@ describe Mutations::Todos::RestoreMany do
todo_ids = result[:updated_ids]
expect(todo_ids.size).to eq(1)
expect(todo_ids.first).to eq(todo1.to_global_id.to_s)
+
+ expect(result[:todos]).to contain_exactly(todo1)
end
it 'handles a todo which is already pending as expected' do
@@ -33,6 +35,7 @@ describe Mutations::Todos::RestoreMany do
expect_states_were_not_changed
expect(result[:updated_ids]).to eq([])
+ expect(result[:todos]).to be_empty
end
it 'ignores requests for todos which do not belong to the current user' do
@@ -56,6 +59,7 @@ describe Mutations::Todos::RestoreMany do
returned_todo_ids = result[:updated_ids]
expect(returned_todo_ids).to contain_exactly(todo1.to_global_id.to_s, todo4.to_global_id.to_s)
+ expect(result[:todos]).to contain_exactly(todo1, todo4)
expect(todo1.reload.state).to eq('pending')
expect(todo2.reload.state).to eq('pending')
diff --git a/spec/graphql/mutations/todos/restore_spec.rb b/spec/graphql/mutations/todos/restore_spec.rb
index 949ab6a164b..9043d7a44a8 100644
--- a/spec/graphql/mutations/todos/restore_spec.rb
+++ b/spec/graphql/mutations/todos/restore_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Mutations::Todos::Restore do
+RSpec.describe Mutations::Todos::Restore do
let_it_be(:current_user) { create(:user) }
let_it_be(:author) { create(:user) }
let_it_be(:other_user) { create(:user) }
diff --git a/spec/graphql/resolvers/alert_management/alert_resolver_spec.rb b/spec/graphql/resolvers/alert_management/alert_resolver_spec.rb
index 6c12f765e69..0c1ba5aab2c 100644
--- a/spec/graphql/resolvers/alert_management/alert_resolver_spec.rb
+++ b/spec/graphql/resolvers/alert_management/alert_resolver_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Resolvers::AlertManagement::AlertResolver do
+RSpec.describe Resolvers::AlertManagement::AlertResolver do
include GraphqlHelpers
let_it_be(:current_user) { create(:user) }
diff --git a/spec/graphql/resolvers/alert_management/alert_status_counts_resolver_spec.rb b/spec/graphql/resolvers/alert_management/alert_status_counts_resolver_spec.rb
index 8eb28c8c945..b72e692f2e8 100644
--- a/spec/graphql/resolvers/alert_management/alert_status_counts_resolver_spec.rb
+++ b/spec/graphql/resolvers/alert_management/alert_status_counts_resolver_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Resolvers::AlertManagement::AlertStatusCountsResolver do
+RSpec.describe Resolvers::AlertManagement::AlertStatusCountsResolver do
include GraphqlHelpers
describe '#resolve' do
diff --git a/spec/graphql/resolvers/base_resolver_spec.rb b/spec/graphql/resolvers/base_resolver_spec.rb
index 6c384349577..40dc2370052 100644
--- a/spec/graphql/resolvers/base_resolver_spec.rb
+++ b/spec/graphql/resolvers/base_resolver_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Resolvers::BaseResolver do
+RSpec.describe Resolvers::BaseResolver do
include GraphqlHelpers
let(:resolver) do
diff --git a/spec/graphql/resolvers/board_lists_resolver_spec.rb b/spec/graphql/resolvers/board_lists_resolver_spec.rb
index 5f6c440a8ed..f662e9a0f62 100644
--- a/spec/graphql/resolvers/board_lists_resolver_spec.rb
+++ b/spec/graphql/resolvers/board_lists_resolver_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Resolvers::BoardListsResolver do
+RSpec.describe Resolvers::BoardListsResolver do
include GraphqlHelpers
let_it_be(:user) { create(:user) }
diff --git a/spec/graphql/resolvers/boards_resolver_spec.rb b/spec/graphql/resolvers/boards_resolver_spec.rb
index 02d6f808118..f121e8a4083 100644
--- a/spec/graphql/resolvers/boards_resolver_spec.rb
+++ b/spec/graphql/resolvers/boards_resolver_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Resolvers::BoardsResolver do
+RSpec.describe Resolvers::BoardsResolver do
include GraphqlHelpers
let_it_be(:user) { create(:user) }
diff --git a/spec/graphql/resolvers/branch_commit_resolver_spec.rb b/spec/graphql/resolvers/branch_commit_resolver_spec.rb
index 22e1de8f375..78d4959c3f9 100644
--- a/spec/graphql/resolvers/branch_commit_resolver_spec.rb
+++ b/spec/graphql/resolvers/branch_commit_resolver_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Resolvers::BranchCommitResolver do
+RSpec.describe Resolvers::BranchCommitResolver do
include GraphqlHelpers
subject(:commit) { resolve(described_class, obj: branch) }
diff --git a/spec/graphql/resolvers/ci_configuration/sast_resolver_spec.rb b/spec/graphql/resolvers/ci_configuration/sast_resolver_spec.rb
new file mode 100644
index 00000000000..de69ad5d450
--- /dev/null
+++ b/spec/graphql/resolvers/ci_configuration/sast_resolver_spec.rb
@@ -0,0 +1,28 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Resolvers::CiConfiguration::SastResolver do
+ include GraphqlHelpers
+
+ let_it_be(:user) { create(:user) }
+ let_it_be(:project) { create(:project) }
+
+ describe '#resolve' do
+ subject(:sast_config) { resolve(described_class, ctx: { current_user: user }, obj: project) }
+
+ it 'returns global variable informations related to SAST' do
+ expect(sast_config['global'].first['field']).to eql("SECURE_ANALYZERS_PREFIX")
+ expect(sast_config['global'].first['label']).to eql("Image prefix")
+ expect(sast_config['global'].first['type']).to eql("string")
+
+ expect(sast_config['pipeline'].first['field']).to eql("stage")
+ expect(sast_config['pipeline'].first['label']).to eql("Stage")
+ expect(sast_config['pipeline'].first['type']).to eql("dropdown")
+
+ expect(sast_config['analyzers'].first['name']).to eql("brakeman")
+ expect(sast_config['analyzers'].first['label']).to eql("Brakeman")
+ expect(sast_config['analyzers'].first['enabled']).to be true
+ end
+ end
+end
diff --git a/spec/graphql/resolvers/commit_pipelines_resolver_spec.rb b/spec/graphql/resolvers/commit_pipelines_resolver_spec.rb
index 93da877d714..20a0cb842a4 100644
--- a/spec/graphql/resolvers/commit_pipelines_resolver_spec.rb
+++ b/spec/graphql/resolvers/commit_pipelines_resolver_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Resolvers::CommitPipelinesResolver do
+RSpec.describe Resolvers::CommitPipelinesResolver do
include GraphqlHelpers
let_it_be(:project) { create(:project) }
diff --git a/spec/graphql/resolvers/concerns/looks_ahead_spec.rb b/spec/graphql/resolvers/concerns/looks_ahead_spec.rb
index 8b83f887846..f13823085b8 100644
--- a/spec/graphql/resolvers/concerns/looks_ahead_spec.rb
+++ b/spec/graphql/resolvers/concerns/looks_ahead_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe LooksAhead do
+RSpec.describe LooksAhead do
include GraphqlHelpers
let_it_be(:the_user) { create(:user) }
diff --git a/spec/graphql/resolvers/concerns/resolves_pipelines_spec.rb b/spec/graphql/resolvers/concerns/resolves_pipelines_spec.rb
index 03ff1e11d85..3dffda75e08 100644
--- a/spec/graphql/resolvers/concerns/resolves_pipelines_spec.rb
+++ b/spec/graphql/resolvers/concerns/resolves_pipelines_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ResolvesPipelines do
+RSpec.describe ResolvesPipelines do
include GraphqlHelpers
subject(:resolver) do
diff --git a/spec/graphql/resolvers/concerns/resolves_project_spec.rb b/spec/graphql/resolvers/concerns/resolves_project_spec.rb
index f29f54483d6..1748d8a81a3 100644
--- a/spec/graphql/resolvers/concerns/resolves_project_spec.rb
+++ b/spec/graphql/resolvers/concerns/resolves_project_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ResolvesProject do
+RSpec.describe ResolvesProject do
include GraphqlHelpers
let(:implementing_class) do
diff --git a/spec/graphql/resolvers/design_management/design_at_version_resolver_spec.rb b/spec/graphql/resolvers/design_management/design_at_version_resolver_spec.rb
index a5054ae3ebf..4bdef49499c 100644
--- a/spec/graphql/resolvers/design_management/design_at_version_resolver_spec.rb
+++ b/spec/graphql/resolvers/design_management/design_at_version_resolver_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Resolvers::DesignManagement::DesignAtVersionResolver do
+RSpec.describe Resolvers::DesignManagement::DesignAtVersionResolver do
include GraphqlHelpers
include DesignManagementTestHelpers
diff --git a/spec/graphql/resolvers/design_management/design_resolver_spec.rb b/spec/graphql/resolvers/design_management/design_resolver_spec.rb
index 857acc3d371..02d7f94612c 100644
--- a/spec/graphql/resolvers/design_management/design_resolver_spec.rb
+++ b/spec/graphql/resolvers/design_management/design_resolver_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Resolvers::DesignManagement::DesignResolver do
+RSpec.describe Resolvers::DesignManagement::DesignResolver do
include GraphqlHelpers
include DesignManagementTestHelpers
diff --git a/spec/graphql/resolvers/design_management/designs_resolver_spec.rb b/spec/graphql/resolvers/design_management/designs_resolver_spec.rb
index 28fc9e2151d..cfa37d34fd9 100644
--- a/spec/graphql/resolvers/design_management/designs_resolver_spec.rb
+++ b/spec/graphql/resolvers/design_management/designs_resolver_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Resolvers::DesignManagement::DesignsResolver do
+RSpec.describe Resolvers::DesignManagement::DesignsResolver do
include GraphqlHelpers
include DesignManagementTestHelpers
diff --git a/spec/graphql/resolvers/design_management/version/design_at_version_resolver_spec.rb b/spec/graphql/resolvers/design_management/version/design_at_version_resolver_spec.rb
index cc9c0436885..850b9f8cc87 100644
--- a/spec/graphql/resolvers/design_management/version/design_at_version_resolver_spec.rb
+++ b/spec/graphql/resolvers/design_management/version/design_at_version_resolver_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Resolvers::DesignManagement::Version::DesignAtVersionResolver do
+RSpec.describe Resolvers::DesignManagement::Version::DesignAtVersionResolver do
include GraphqlHelpers
include_context 'four designs in three versions'
diff --git a/spec/graphql/resolvers/design_management/version/designs_at_version_resolver_spec.rb b/spec/graphql/resolvers/design_management/version/designs_at_version_resolver_spec.rb
index 123b26862d0..c038216ce0b 100644
--- a/spec/graphql/resolvers/design_management/version/designs_at_version_resolver_spec.rb
+++ b/spec/graphql/resolvers/design_management/version/designs_at_version_resolver_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Resolvers::DesignManagement::Version::DesignsAtVersionResolver do
+RSpec.describe Resolvers::DesignManagement::Version::DesignsAtVersionResolver do
include GraphqlHelpers
include_context 'four designs in three versions'
diff --git a/spec/graphql/resolvers/design_management/version_in_collection_resolver_spec.rb b/spec/graphql/resolvers/design_management/version_in_collection_resolver_spec.rb
index ef50598d241..8ad928e9854 100644
--- a/spec/graphql/resolvers/design_management/version_in_collection_resolver_spec.rb
+++ b/spec/graphql/resolvers/design_management/version_in_collection_resolver_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Resolvers::DesignManagement::VersionInCollectionResolver do
+RSpec.describe Resolvers::DesignManagement::VersionInCollectionResolver do
include GraphqlHelpers
include DesignManagementTestHelpers
diff --git a/spec/graphql/resolvers/design_management/version_resolver_spec.rb b/spec/graphql/resolvers/design_management/version_resolver_spec.rb
index e7c09351204..af1e6a73d09 100644
--- a/spec/graphql/resolvers/design_management/version_resolver_spec.rb
+++ b/spec/graphql/resolvers/design_management/version_resolver_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Resolvers::DesignManagement::VersionResolver do
+RSpec.describe Resolvers::DesignManagement::VersionResolver do
include GraphqlHelpers
include DesignManagementTestHelpers
diff --git a/spec/graphql/resolvers/design_management/versions_resolver_spec.rb b/spec/graphql/resolvers/design_management/versions_resolver_spec.rb
index d5bab025e45..5bc1c555e9a 100644
--- a/spec/graphql/resolvers/design_management/versions_resolver_spec.rb
+++ b/spec/graphql/resolvers/design_management/versions_resolver_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Resolvers::DesignManagement::VersionsResolver do
+RSpec.describe Resolvers::DesignManagement::VersionsResolver do
include GraphqlHelpers
include DesignManagementTestHelpers
diff --git a/spec/graphql/resolvers/echo_resolver_spec.rb b/spec/graphql/resolvers/echo_resolver_spec.rb
index 466501a4227..2182ac221f6 100644
--- a/spec/graphql/resolvers/echo_resolver_spec.rb
+++ b/spec/graphql/resolvers/echo_resolver_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Resolvers::EchoResolver do
+RSpec.describe Resolvers::EchoResolver do
include GraphqlHelpers
let(:current_user) { create(:user) }
diff --git a/spec/graphql/resolvers/environments_resolver_spec.rb b/spec/graphql/resolvers/environments_resolver_spec.rb
index 75fd7aff39c..6c999e5d0e7 100644
--- a/spec/graphql/resolvers/environments_resolver_spec.rb
+++ b/spec/graphql/resolvers/environments_resolver_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Resolvers::EnvironmentsResolver do
+RSpec.describe Resolvers::EnvironmentsResolver do
include GraphqlHelpers
let_it_be(:current_user) { create(:user) }
diff --git a/spec/graphql/resolvers/error_tracking/sentry_detailed_error_resolver_spec.rb b/spec/graphql/resolvers/error_tracking/sentry_detailed_error_resolver_spec.rb
index 8b2e33cdfda..7e531910184 100644
--- a/spec/graphql/resolvers/error_tracking/sentry_detailed_error_resolver_spec.rb
+++ b/spec/graphql/resolvers/error_tracking/sentry_detailed_error_resolver_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Resolvers::ErrorTracking::SentryDetailedErrorResolver do
+RSpec.describe Resolvers::ErrorTracking::SentryDetailedErrorResolver do
include GraphqlHelpers
let_it_be(:project) { create(:project) }
diff --git a/spec/graphql/resolvers/error_tracking/sentry_error_collection_resolver_spec.rb b/spec/graphql/resolvers/error_tracking/sentry_error_collection_resolver_spec.rb
index 3bb8a5c389d..02e0420be2a 100644
--- a/spec/graphql/resolvers/error_tracking/sentry_error_collection_resolver_spec.rb
+++ b/spec/graphql/resolvers/error_tracking/sentry_error_collection_resolver_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Resolvers::ErrorTracking::SentryErrorCollectionResolver do
+RSpec.describe Resolvers::ErrorTracking::SentryErrorCollectionResolver do
include GraphqlHelpers
let_it_be(:project) { create(:project) }
diff --git a/spec/graphql/resolvers/error_tracking/sentry_errors_resolver_spec.rb b/spec/graphql/resolvers/error_tracking/sentry_errors_resolver_spec.rb
index 93f89d077d7..554873a6e21 100644
--- a/spec/graphql/resolvers/error_tracking/sentry_errors_resolver_spec.rb
+++ b/spec/graphql/resolvers/error_tracking/sentry_errors_resolver_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Resolvers::ErrorTracking::SentryErrorsResolver do
+RSpec.describe Resolvers::ErrorTracking::SentryErrorsResolver do
include GraphqlHelpers
let_it_be(:project) { create(:project) }
diff --git a/spec/graphql/resolvers/group_resolver_spec.rb b/spec/graphql/resolvers/group_resolver_spec.rb
index 70b1102d363..a03e7854177 100644
--- a/spec/graphql/resolvers/group_resolver_spec.rb
+++ b/spec/graphql/resolvers/group_resolver_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Resolvers::GroupResolver do
+RSpec.describe Resolvers::GroupResolver do
include GraphqlHelpers
let_it_be(:group1) { create(:group) }
diff --git a/spec/graphql/resolvers/issues_resolver_spec.rb b/spec/graphql/resolvers/issues_resolver_spec.rb
index b7cc9bc6d71..eb17e94a450 100644
--- a/spec/graphql/resolvers/issues_resolver_spec.rb
+++ b/spec/graphql/resolvers/issues_resolver_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Resolvers::IssuesResolver do
+RSpec.describe Resolvers::IssuesResolver do
include GraphqlHelpers
let(:current_user) { create(:user) }
@@ -101,12 +101,10 @@ describe Resolvers::IssuesResolver do
end
it 'uses project search optimization' do
- expected_arguments = {
+ expected_arguments = a_hash_including(
search: 'foo',
- attempt_project_search_optimizations: true,
- iids: [],
- project_id: project.id
- }
+ attempt_project_search_optimizations: true
+ )
expect(IssuesFinder).to receive(:new).with(anything, expected_arguments).and_call_original
resolve_issues(search: 'foo')
@@ -217,16 +215,32 @@ describe Resolvers::IssuesResolver do
expect(resolve_issues).to contain_exactly(issue1, issue2)
end
- it 'finds a specific issue with iid' do
- expect(resolve_issues(iid: issue1.iid)).to contain_exactly(issue1)
+ it 'finds a specific issue with iid', :request_store do
+ result = batch_sync(max_queries: 2) { resolve_issues(iid: issue1.iid) }
+
+ expect(result).to contain_exactly(issue1)
+ end
+
+ it 'batches queries that only include IIDs', :request_store do
+ result = batch_sync(max_queries: 2) do
+ resolve_issues(iid: issue1.iid) + resolve_issues(iids: issue2.iid)
+ end
+
+ expect(result).to contain_exactly(issue1, issue2)
end
- it 'finds a specific issue with iids' do
- expect(resolve_issues(iids: issue1.iid)).to contain_exactly(issue1)
+ it 'finds a specific issue with iids', :request_store do
+ result = batch_sync(max_queries: 2) do
+ resolve_issues(iids: [issue1.iid])
+ end
+
+ expect(result).to contain_exactly(issue1)
end
it 'finds multiple issues with iids' do
- expect(resolve_issues(iids: [issue1.iid, issue2.iid]))
+ create(:issue, project: project, author: current_user)
+
+ expect(batch_sync { resolve_issues(iids: [issue1.iid, issue2.iid]) })
.to contain_exactly(issue1, issue2)
end
@@ -238,7 +252,7 @@ describe Resolvers::IssuesResolver do
create(:issue, project: another_project, iid: iid)
end
- expect(resolve_issues(iids: iids)).to contain_exactly(issue1, issue2)
+ expect(batch_sync { resolve_issues(iids: iids) }).to contain_exactly(issue1, issue2)
end
end
end
diff --git a/spec/graphql/resolvers/last_commit_resolver_spec.rb b/spec/graphql/resolvers/last_commit_resolver_spec.rb
index 15b09b77a10..5ac6ad59864 100644
--- a/spec/graphql/resolvers/last_commit_resolver_spec.rb
+++ b/spec/graphql/resolvers/last_commit_resolver_spec.rb
@@ -2,10 +2,12 @@
require 'spec_helper'
-describe Resolvers::LastCommitResolver do
+RSpec.describe Resolvers::LastCommitResolver do
include GraphqlHelpers
+ include RepoHelpers
- let(:repository) { create(:project, :repository).repository }
+ let(:project) { create(:project, :repository) }
+ let(:repository) { project.repository }
let(:tree) { repository.tree(ref, path) }
let(:commit) { resolve(described_class, obj: tree) }
@@ -29,6 +31,28 @@ describe Resolvers::LastCommitResolver do
end
end
+ context 'last commit for a wildcard pathspec' do
+ let(:ref) { 'fix' }
+ let(:path) { 'files/*' }
+
+ it 'returns nil' do
+ expect(commit).to be_nil
+ end
+ end
+
+ context 'last commit with pathspec characters' do
+ let(:ref) { 'fix' }
+ let(:path) { ':wq' }
+
+ before do
+ create_file_in_repo(project, ref, ref, path, 'Test file')
+ end
+
+ it 'resolves commit' do
+ expect(commit).to eq(repository.commits(ref, path: path, limit: 1).last)
+ end
+ end
+
context 'last commit does not exist' do
let(:ref) { 'master' }
let(:path) { 'does-not-exist' }
diff --git a/spec/graphql/resolvers/merge_request_pipelines_resolver_spec.rb b/spec/graphql/resolvers/merge_request_pipelines_resolver_spec.rb
index b894dce3e17..2fe3e86ec14 100644
--- a/spec/graphql/resolvers/merge_request_pipelines_resolver_spec.rb
+++ b/spec/graphql/resolvers/merge_request_pipelines_resolver_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Resolvers::MergeRequestPipelinesResolver do
+RSpec.describe Resolvers::MergeRequestPipelinesResolver do
include GraphqlHelpers
let_it_be(:merge_request) { create(:merge_request) }
diff --git a/spec/graphql/resolvers/merge_requests_resolver_spec.rb b/spec/graphql/resolvers/merge_requests_resolver_spec.rb
index 6ff7e1ecac6..0a8fd82613a 100644
--- a/spec/graphql/resolvers/merge_requests_resolver_spec.rb
+++ b/spec/graphql/resolvers/merge_requests_resolver_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Resolvers::MergeRequestsResolver do
+RSpec.describe Resolvers::MergeRequestsResolver do
include GraphqlHelpers
let_it_be(:project) { create(:project, :repository) }
@@ -22,9 +22,12 @@ describe Resolvers::MergeRequestsResolver do
before do
project.add_developer(current_user)
+ other_project.add_developer(current_user)
end
describe '#resolve' do
+ let(:queries_per_project) { 3 }
+
context 'no arguments' do
it 'returns all merge requests' do
result = resolve_mr(project, {})
@@ -40,24 +43,34 @@ describe Resolvers::MergeRequestsResolver do
end
context 'by iid alone' do
- it 'batch-resolves by target project full path and individual IID' do
- result = batch_sync(max_queries: 2) do
+ it 'batch-resolves by target project full path and individual IID', :request_store do
+ # 1 query for project_authorizations, and 1 for merge_requests
+ result = batch_sync(max_queries: queries_per_project) do
[iid_1, iid_2].map { |iid| resolve_mr_single(project, iid) }
end
expect(result).to contain_exactly(merge_request_1, merge_request_2)
end
- it 'batch-resolves by target project full path and IIDS' do
- result = batch_sync(max_queries: 2) do
+ it 'batch-resolves by target project full path and IIDS', :request_store do
+ result = batch_sync(max_queries: queries_per_project) do
resolve_mr(project, iids: [iid_1, iid_2])
end
expect(result).to contain_exactly(merge_request_1, merge_request_2)
end
+ it 'batch-resolves by target project full path and IIDS, single or plural', :request_store do
+ result = batch_sync(max_queries: queries_per_project) do
+ [resolve_mr_single(project, merge_request_3.iid), *resolve_mr(project, iids: [iid_1, iid_2])]
+ end
+
+ expect(result).to contain_exactly(merge_request_1, merge_request_2, merge_request_3)
+ end
+
it 'can batch-resolve merge requests from different projects' do
- result = batch_sync(max_queries: 3) do
+ # 2 queries for project_authorizations, and 2 for merge_requests
+ result = batch_sync(max_queries: queries_per_project * 2) do
resolve_mr(project, iids: iid_1) +
resolve_mr(project, iids: iid_2) +
resolve_mr(other_project, iids: other_iid)
diff --git a/spec/graphql/resolvers/metadata_resolver_spec.rb b/spec/graphql/resolvers/metadata_resolver_spec.rb
index afff9eabb97..20556941de4 100644
--- a/spec/graphql/resolvers/metadata_resolver_spec.rb
+++ b/spec/graphql/resolvers/metadata_resolver_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Resolvers::MetadataResolver do
+RSpec.describe Resolvers::MetadataResolver do
include GraphqlHelpers
describe '#resolve' do
diff --git a/spec/graphql/resolvers/metrics/dashboard_resolver_spec.rb b/spec/graphql/resolvers/metrics/dashboard_resolver_spec.rb
index 6a8eb8a65af..4112e3d4fe6 100644
--- a/spec/graphql/resolvers/metrics/dashboard_resolver_spec.rb
+++ b/spec/graphql/resolvers/metrics/dashboard_resolver_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Resolvers::Metrics::DashboardResolver do
+RSpec.describe Resolvers::Metrics::DashboardResolver do
include GraphqlHelpers
let_it_be(:current_user) { create(:user) }
diff --git a/spec/graphql/resolvers/metrics/dashboards/annotation_resolver_spec.rb b/spec/graphql/resolvers/metrics/dashboards/annotation_resolver_spec.rb
index c06fbef53b6..f90869c52bc 100644
--- a/spec/graphql/resolvers/metrics/dashboards/annotation_resolver_spec.rb
+++ b/spec/graphql/resolvers/metrics/dashboards/annotation_resolver_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Resolvers::Metrics::Dashboards::AnnotationResolver do
+RSpec.describe Resolvers::Metrics::Dashboards::AnnotationResolver do
include GraphqlHelpers
describe '#resolve' do
diff --git a/spec/graphql/resolvers/milestone_resolver_spec.rb b/spec/graphql/resolvers/milestone_resolver_spec.rb
index 8e2c67fdc03..36dd5ef03e2 100644
--- a/spec/graphql/resolvers/milestone_resolver_spec.rb
+++ b/spec/graphql/resolvers/milestone_resolver_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Resolvers::MilestoneResolver do
+RSpec.describe Resolvers::MilestoneResolver do
include GraphqlHelpers
describe '#resolve' do
diff --git a/spec/graphql/resolvers/namespace_projects_resolver_spec.rb b/spec/graphql/resolvers/namespace_projects_resolver_spec.rb
index 639cc69650b..699269b47e0 100644
--- a/spec/graphql/resolvers/namespace_projects_resolver_spec.rb
+++ b/spec/graphql/resolvers/namespace_projects_resolver_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Resolvers::NamespaceProjectsResolver do
+RSpec.describe Resolvers::NamespaceProjectsResolver do
include GraphqlHelpers
let(:current_user) { create(:user) }
diff --git a/spec/graphql/resolvers/packages_resolver_spec.rb b/spec/graphql/resolvers/packages_resolver_spec.rb
new file mode 100644
index 00000000000..9aec2c7e036
--- /dev/null
+++ b/spec/graphql/resolvers/packages_resolver_spec.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Resolvers::PackagesResolver do
+ include GraphqlHelpers
+
+ let_it_be(:user) { create(:user) }
+ let_it_be(:project) { create(:project) }
+ let_it_be(:package) { create(:package, project: project) }
+
+ describe '#resolve' do
+ subject(:packages) { resolve(described_class, ctx: { current_user: user }, obj: project) }
+
+ it { is_expected.to contain_exactly(package) }
+ end
+end
diff --git a/spec/graphql/resolvers/project_members_resolver_spec.rb b/spec/graphql/resolvers/project_members_resolver_spec.rb
index 3209838850b..602225cf632 100644
--- a/spec/graphql/resolvers/project_members_resolver_spec.rb
+++ b/spec/graphql/resolvers/project_members_resolver_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Resolvers::ProjectMembersResolver do
+RSpec.describe Resolvers::ProjectMembersResolver do
include GraphqlHelpers
context "with a group" do
diff --git a/spec/graphql/resolvers/project_pipeline_resolver_spec.rb b/spec/graphql/resolvers/project_pipeline_resolver_spec.rb
index 72049f16d7d..a659b3bdb6e 100644
--- a/spec/graphql/resolvers/project_pipeline_resolver_spec.rb
+++ b/spec/graphql/resolvers/project_pipeline_resolver_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Resolvers::ProjectPipelineResolver do
+RSpec.describe Resolvers::ProjectPipelineResolver do
include GraphqlHelpers
let_it_be(:project) { create(:project) }
diff --git a/spec/graphql/resolvers/project_pipelines_resolver_spec.rb b/spec/graphql/resolvers/project_pipelines_resolver_spec.rb
index 2a14796fdfa..b2e8fed2441 100644
--- a/spec/graphql/resolvers/project_pipelines_resolver_spec.rb
+++ b/spec/graphql/resolvers/project_pipelines_resolver_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Resolvers::ProjectPipelinesResolver do
+RSpec.describe Resolvers::ProjectPipelinesResolver do
include GraphqlHelpers
let_it_be(:project) { create(:project) }
diff --git a/spec/graphql/resolvers/project_resolver_spec.rb b/spec/graphql/resolvers/project_resolver_spec.rb
index e9e38353156..72a01b1c574 100644
--- a/spec/graphql/resolvers/project_resolver_spec.rb
+++ b/spec/graphql/resolvers/project_resolver_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Resolvers::ProjectResolver do
+RSpec.describe Resolvers::ProjectResolver do
include GraphqlHelpers
let_it_be(:project1) { create(:project) }
diff --git a/spec/graphql/resolvers/projects/grafana_integration_resolver_spec.rb b/spec/graphql/resolvers/projects/grafana_integration_resolver_spec.rb
index 416a90a841f..854e763fbdd 100644
--- a/spec/graphql/resolvers/projects/grafana_integration_resolver_spec.rb
+++ b/spec/graphql/resolvers/projects/grafana_integration_resolver_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Resolvers::Projects::GrafanaIntegrationResolver do
+RSpec.describe Resolvers::Projects::GrafanaIntegrationResolver do
include GraphqlHelpers
let_it_be(:project) { create(:project) }
diff --git a/spec/graphql/resolvers/projects/jira_imports_resolver_spec.rb b/spec/graphql/resolvers/projects/jira_imports_resolver_spec.rb
index 9811075a613..0775c1c31d1 100644
--- a/spec/graphql/resolvers/projects/jira_imports_resolver_spec.rb
+++ b/spec/graphql/resolvers/projects/jira_imports_resolver_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Resolvers::Projects::JiraImportsResolver do
+RSpec.describe Resolvers::Projects::JiraImportsResolver do
include GraphqlHelpers
describe '#resolve' do
diff --git a/spec/graphql/resolvers/projects/jira_projects_resolver_spec.rb b/spec/graphql/resolvers/projects/jira_projects_resolver_spec.rb
index 364e2aa6ca8..4038bcb3e5d 100644
--- a/spec/graphql/resolvers/projects/jira_projects_resolver_spec.rb
+++ b/spec/graphql/resolvers/projects/jira_projects_resolver_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Resolvers::Projects::JiraProjectsResolver do
+RSpec.describe Resolvers::Projects::JiraProjectsResolver do
include GraphqlHelpers
describe '#resolve' do
@@ -63,7 +63,7 @@ describe Resolvers::Projects::JiraProjectsResolver do
context 'when Jira connection is not valid' do
before do
- WebMock.stub_request(:get, 'https://jira.example.com/rest/api/2/project/search?maxResults=50&query=&startAt=0')
+ WebMock.stub_request(:get, 'https://jira.example.com/rest/api/2/project')
.to_raise(JIRA::HTTPError.new(double(message: 'Some failure.')))
end
diff --git a/spec/graphql/resolvers/projects/services_resolver_spec.rb b/spec/graphql/resolvers/projects/services_resolver_spec.rb
index 00045442ea0..8b6eff9e8b6 100644
--- a/spec/graphql/resolvers/projects/services_resolver_spec.rb
+++ b/spec/graphql/resolvers/projects/services_resolver_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Resolvers::Projects::ServicesResolver do
+RSpec.describe Resolvers::Projects::ServicesResolver do
include GraphqlHelpers
describe '#resolve' do
diff --git a/spec/graphql/resolvers/projects/snippets_resolver_spec.rb b/spec/graphql/resolvers/projects/snippets_resolver_spec.rb
index 6d301b1c742..b4a5eb8ddb0 100644
--- a/spec/graphql/resolvers/projects/snippets_resolver_spec.rb
+++ b/spec/graphql/resolvers/projects/snippets_resolver_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Resolvers::Projects::SnippetsResolver do
+RSpec.describe Resolvers::Projects::SnippetsResolver do
include GraphqlHelpers
describe '#resolve' do
diff --git a/spec/graphql/resolvers/projects_resolver_spec.rb b/spec/graphql/resolvers/projects_resolver_spec.rb
index 73ff99a2520..db7c9225c84 100644
--- a/spec/graphql/resolvers/projects_resolver_spec.rb
+++ b/spec/graphql/resolvers/projects_resolver_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Resolvers::ProjectsResolver do
+RSpec.describe Resolvers::ProjectsResolver do
include GraphqlHelpers
describe '#resolve' do
diff --git a/spec/graphql/resolvers/release_resolver_spec.rb b/spec/graphql/resolvers/release_resolver_spec.rb
index 71aa4bbb439..666d54fbc3c 100644
--- a/spec/graphql/resolvers/release_resolver_spec.rb
+++ b/spec/graphql/resolvers/release_resolver_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Resolvers::ReleaseResolver do
+RSpec.describe Resolvers::ReleaseResolver do
include GraphqlHelpers
let_it_be(:project) { create(:project, :private) }
diff --git a/spec/graphql/resolvers/releases_resolver_spec.rb b/spec/graphql/resolvers/releases_resolver_spec.rb
index 9de539b417a..ee8b33fc748 100644
--- a/spec/graphql/resolvers/releases_resolver_spec.rb
+++ b/spec/graphql/resolvers/releases_resolver_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Resolvers::ReleasesResolver do
+RSpec.describe Resolvers::ReleasesResolver do
include GraphqlHelpers
let_it_be(:project) { create(:project, :private) }
diff --git a/spec/graphql/resolvers/snippets_resolver_spec.rb b/spec/graphql/resolvers/snippets_resolver_spec.rb
index 89c350020f0..180be8e8624 100644
--- a/spec/graphql/resolvers/snippets_resolver_spec.rb
+++ b/spec/graphql/resolvers/snippets_resolver_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Resolvers::SnippetsResolver do
+RSpec.describe Resolvers::SnippetsResolver do
include GraphqlHelpers
describe '#resolve' do
diff --git a/spec/graphql/resolvers/todo_resolver_spec.rb b/spec/graphql/resolvers/todo_resolver_spec.rb
index 5a09ec40e64..0775cb8dae7 100644
--- a/spec/graphql/resolvers/todo_resolver_spec.rb
+++ b/spec/graphql/resolvers/todo_resolver_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Resolvers::TodoResolver do
+RSpec.describe Resolvers::TodoResolver do
include GraphqlHelpers
describe '#resolve' do
@@ -10,9 +10,9 @@ describe Resolvers::TodoResolver do
let_it_be(:author1) { create(:user) }
let_it_be(:author2) { create(:user) }
- let_it_be(:todo1) { create(:todo, user: current_user, target_type: 'MergeRequest', state: :pending, action: Todo::MENTIONED, author: author1) }
- let_it_be(:todo2) { create(:todo, user: current_user, state: :done, action: Todo::ASSIGNED, author: author2) }
- let_it_be(:todo3) { create(:todo, user: current_user, state: :pending, action: Todo::ASSIGNED, author: author1) }
+ let_it_be(:merge_request_todo_pending) { create(:todo, user: current_user, target_type: 'MergeRequest', state: :pending, action: Todo::MENTIONED, author: author1) }
+ let_it_be(:issue_todo_done) { create(:todo, user: current_user, state: :done, action: Todo::ASSIGNED, author: author2) }
+ let_it_be(:issue_todo_pending) { create(:todo, user: current_user, state: :pending, action: Todo::ASSIGNED, author: author1) }
it 'calls TodosFinder' do
expect_next_instance_of(TodosFinder) do |finder|
@@ -23,22 +23,30 @@ describe Resolvers::TodoResolver do
end
context 'when using no filter' do
- it 'returns expected todos' do
- expect(resolve_todos).to contain_exactly(todo1, todo3)
+ it 'returns pending todos' do
+ expect(resolve_todos).to contain_exactly(merge_request_todo_pending, issue_todo_pending)
end
end
context 'when using filters' do
it 'returns the todos for multiple states' do
- todos = resolve_todos({ state: [:done, :pending] })
+ todos = resolve_todos(state: [:done, :pending])
- expect(todos).to contain_exactly(todo1, todo2, todo3)
+ expect(todos).to contain_exactly(merge_request_todo_pending, issue_todo_done, issue_todo_pending)
end
- it 'returns the todos for multiple types' do
- todos = resolve_todos({ type: %w[Issue MergeRequest] })
+ it 'returns the todos for multiple filters' do
+ design_todo_pending = create(:todo, target_type: 'DesignManagement::Design', user: current_user, state: :pending, action: Todo::ASSIGNED, author: author1)
- expect(todos).to contain_exactly(todo1, todo3)
+ todos = resolve_todos(type: ['MergeRequest', 'DesignManagement::Design'])
+
+ expect(todos).to contain_exactly(merge_request_todo_pending, design_todo_pending)
+ end
+
+ it 'returns the todos for single filter' do
+ todos = resolve_todos(type: 'MergeRequest')
+
+ expect(todos).to contain_exactly(merge_request_todo_pending)
end
it 'returns the todos for multiple groups' do
@@ -53,7 +61,7 @@ describe Resolvers::TodoResolver do
todo5 = create(:todo, group: group2, user: current_user, state: :pending, action: Todo::ASSIGNED, author: author1)
create(:todo, group: group3, user: current_user, state: :pending, action: Todo::ASSIGNED, author: author1)
- todos = resolve_todos({ group_id: [group2.id, group1.id] })
+ todos = resolve_todos(group_id: [group2.id, group1.id])
expect(todos).to contain_exactly(todo4, todo5)
end
@@ -61,20 +69,19 @@ describe Resolvers::TodoResolver do
it 'returns the todos for multiple authors' do
author3 = create(:user)
- todo4 = create(:todo, user: current_user, state: :pending, action: Todo::ASSIGNED, author: author2)
create(:todo, user: current_user, state: :pending, action: Todo::ASSIGNED, author: author3)
- todos = resolve_todos({ author_id: [author2.id, author1.id] })
+ todos = resolve_todos(author_id: [author2.id, author1.id])
- expect(todos).to contain_exactly(todo1, todo3, todo4)
+ expect(todos).to contain_exactly(merge_request_todo_pending, issue_todo_pending)
end
it 'returns the todos for multiple actions' do
create(:todo, user: current_user, state: :pending, action: Todo::DIRECTLY_ADDRESSED, author: author1)
- todos = resolve_todos({ action: [Todo::MENTIONED, Todo::ASSIGNED] })
+ todos = resolve_todos(action: [Todo::MENTIONED, Todo::ASSIGNED])
- expect(todos).to contain_exactly(todo1, todo3)
+ expect(todos).to contain_exactly(merge_request_todo_pending, issue_todo_pending)
end
it 'returns the todos for multiple projects' do
@@ -86,7 +93,7 @@ describe Resolvers::TodoResolver do
todo5 = create(:todo, project: project2, user: current_user, state: :pending, action: Todo::ASSIGNED, author: author1)
create(:todo, project: project3, user: current_user, state: :pending, action: Todo::ASSIGNED, author: author1)
- todos = resolve_todos({ project_id: [project2.id, project1.id] })
+ todos = resolve_todos(project_id: [project2.id, project1.id])
expect(todos).to contain_exactly(todo4, todo5)
end
diff --git a/spec/graphql/resolvers/tree_resolver_spec.rb b/spec/graphql/resolvers/tree_resolver_spec.rb
index 0ea4e6eeaad..7818c25fe47 100644
--- a/spec/graphql/resolvers/tree_resolver_spec.rb
+++ b/spec/graphql/resolvers/tree_resolver_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Resolvers::TreeResolver do
+RSpec.describe Resolvers::TreeResolver do
include GraphqlHelpers
let(:repository) { create(:project, :repository).repository }
diff --git a/spec/graphql/resolvers/user_resolver_spec.rb b/spec/graphql/resolvers/user_resolver_spec.rb
index 45a8816bf26..3ee9f63d832 100644
--- a/spec/graphql/resolvers/user_resolver_spec.rb
+++ b/spec/graphql/resolvers/user_resolver_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Resolvers::UserResolver do
+RSpec.describe Resolvers::UserResolver do
include GraphqlHelpers
describe '#resolve' do
diff --git a/spec/graphql/resolvers/users/snippets_resolver_spec.rb b/spec/graphql/resolvers/users/snippets_resolver_spec.rb
index 6412d77e02b..497b6b11b46 100644
--- a/spec/graphql/resolvers/users/snippets_resolver_spec.rb
+++ b/spec/graphql/resolvers/users/snippets_resolver_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Resolvers::Users::SnippetsResolver do
+RSpec.describe Resolvers::Users::SnippetsResolver do
include GraphqlHelpers
describe '#resolve' do
diff --git a/spec/graphql/resolvers/users_resolver_spec.rb b/spec/graphql/resolvers/users_resolver_spec.rb
index e752500d52f..e3d595e0790 100644
--- a/spec/graphql/resolvers/users_resolver_spec.rb
+++ b/spec/graphql/resolvers/users_resolver_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Resolvers::UsersResolver do
+RSpec.describe Resolvers::UsersResolver do
include GraphqlHelpers
let_it_be(:user1) { create(:user) }
diff --git a/spec/graphql/types/access_level_enum_spec.rb b/spec/graphql/types/access_level_enum_spec.rb
index 05a6d6d5545..eeb10a50b7e 100644
--- a/spec/graphql/types/access_level_enum_spec.rb
+++ b/spec/graphql/types/access_level_enum_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GitlabSchema.types['AccessLevelEnum'] do
+RSpec.describe GitlabSchema.types['AccessLevelEnum'] do
specify { expect(described_class.graphql_name).to eq('AccessLevelEnum') }
it 'exposes all the existing access levels' do
diff --git a/spec/graphql/types/access_level_type_spec.rb b/spec/graphql/types/access_level_type_spec.rb
index b9711a9aa4b..25707ae160d 100644
--- a/spec/graphql/types/access_level_type_spec.rb
+++ b/spec/graphql/types/access_level_type_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe GitlabSchema.types['AccessLevel'] do
+RSpec.describe GitlabSchema.types['AccessLevel'] do
specify { expect(described_class.graphql_name).to eq('AccessLevel') }
specify { expect(described_class).to require_graphql_authorizations(nil) }
diff --git a/spec/graphql/types/alert_management/alert_status_count_type_spec.rb b/spec/graphql/types/alert_management/alert_status_count_type_spec.rb
index 1c56028425e..6372d5dd915 100644
--- a/spec/graphql/types/alert_management/alert_status_count_type_spec.rb
+++ b/spec/graphql/types/alert_management/alert_status_count_type_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GitlabSchema.types['AlertManagementAlertStatusCountsType'] do
+RSpec.describe GitlabSchema.types['AlertManagementAlertStatusCountsType'] do
specify { expect(described_class.graphql_name).to eq('AlertManagementAlertStatusCountsType') }
it 'exposes the expected fields' do
diff --git a/spec/graphql/types/alert_management/alert_type_spec.rb b/spec/graphql/types/alert_management/alert_type_spec.rb
index 5acbf8ebb7a..45ac673986d 100644
--- a/spec/graphql/types/alert_management/alert_type_spec.rb
+++ b/spec/graphql/types/alert_management/alert_type_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GitlabSchema.types['AlertManagementAlert'] do
+RSpec.describe GitlabSchema.types['AlertManagementAlert'] do
specify { expect(described_class.graphql_name).to eq('AlertManagementAlert') }
specify { expect(described_class).to require_graphql_authorizations(:read_alert_management_alert) }
@@ -27,6 +27,7 @@ describe GitlabSchema.types['AlertManagementAlert'] do
assignees
notes
discussions
+ metrics_dashboard_url
]
expect(described_class).to have_graphql_fields(*expected_fields)
diff --git a/spec/graphql/types/alert_management/severity_enum_spec.rb b/spec/graphql/types/alert_management/severity_enum_spec.rb
index ca5aa826fe5..6c3c962b71d 100644
--- a/spec/graphql/types/alert_management/severity_enum_spec.rb
+++ b/spec/graphql/types/alert_management/severity_enum_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GitlabSchema.types['AlertManagementSeverity'] do
+RSpec.describe GitlabSchema.types['AlertManagementSeverity'] do
specify { expect(described_class.graphql_name).to eq('AlertManagementSeverity') }
it 'exposes all the severity values' do
diff --git a/spec/graphql/types/alert_management/status_enum_spec.rb b/spec/graphql/types/alert_management/status_enum_spec.rb
index 240d8863c97..ac7a8eb53f6 100644
--- a/spec/graphql/types/alert_management/status_enum_spec.rb
+++ b/spec/graphql/types/alert_management/status_enum_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GitlabSchema.types['AlertManagementStatus'] do
+RSpec.describe GitlabSchema.types['AlertManagementStatus'] do
specify { expect(described_class.graphql_name).to eq('AlertManagementStatus') }
describe 'statuses' do
diff --git a/spec/graphql/types/award_emojis/award_emoji_type_spec.rb b/spec/graphql/types/award_emojis/award_emoji_type_spec.rb
index 4e06329506d..3c43c5f8e42 100644
--- a/spec/graphql/types/award_emojis/award_emoji_type_spec.rb
+++ b/spec/graphql/types/award_emojis/award_emoji_type_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GitlabSchema.types['AwardEmoji'] do
+RSpec.describe GitlabSchema.types['AwardEmoji'] do
specify { expect(described_class.graphql_name).to eq('AwardEmoji') }
specify { expect(described_class).to require_graphql_authorizations(:read_emoji) }
diff --git a/spec/graphql/types/base_enum_spec.rb b/spec/graphql/types/base_enum_spec.rb
index 3eadb492cf5..0d0f6346f2d 100644
--- a/spec/graphql/types/base_enum_spec.rb
+++ b/spec/graphql/types/base_enum_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Types::BaseEnum do
+RSpec.describe Types::BaseEnum do
describe '#enum' do
let(:enum) do
Class.new(described_class) do
diff --git a/spec/graphql/types/base_field_spec.rb b/spec/graphql/types/base_field_spec.rb
index 3ec33c75803..73bb54e7ad0 100644
--- a/spec/graphql/types/base_field_spec.rb
+++ b/spec/graphql/types/base_field_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Types::BaseField do
+RSpec.describe Types::BaseField do
context 'when considering complexity' do
let(:resolver) do
Class.new(described_class) do
diff --git a/spec/graphql/types/blob_viewers/type_enum_spec.rb b/spec/graphql/types/blob_viewers/type_enum_spec.rb
index 09664382af9..57f052a4fd8 100644
--- a/spec/graphql/types/blob_viewers/type_enum_spec.rb
+++ b/spec/graphql/types/blob_viewers/type_enum_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Types::BlobViewers::TypeEnum do
+RSpec.describe Types::BlobViewers::TypeEnum do
specify { expect(described_class.graphql_name).to eq('BlobViewersType') }
it 'exposes all tree entry types' do
diff --git a/spec/graphql/types/board_list_type_spec.rb b/spec/graphql/types/board_list_type_spec.rb
index 69597fc9617..046d1e92bfa 100644
--- a/spec/graphql/types/board_list_type_spec.rb
+++ b/spec/graphql/types/board_list_type_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GitlabSchema.types['BoardList'] do
+RSpec.describe GitlabSchema.types['BoardList'] do
specify { expect(described_class.graphql_name).to eq('BoardList') }
it 'has specific fields' do
diff --git a/spec/graphql/types/board_type_spec.rb b/spec/graphql/types/board_type_spec.rb
index 5d87a1757b5..b02b342390d 100644
--- a/spec/graphql/types/board_type_spec.rb
+++ b/spec/graphql/types/board_type_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GitlabSchema.types['Board'] do
+RSpec.describe GitlabSchema.types['Board'] do
specify { expect(described_class.graphql_name).to eq('Board') }
specify { expect(described_class).to require_graphql_authorizations(:read_board) }
diff --git a/spec/graphql/types/branch_type_spec.rb b/spec/graphql/types/branch_type_spec.rb
index f58b514116d..a5838739a80 100644
--- a/spec/graphql/types/branch_type_spec.rb
+++ b/spec/graphql/types/branch_type_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GitlabSchema.types['Branch'] do
+RSpec.describe GitlabSchema.types['Branch'] do
it { expect(described_class.graphql_name).to eq('Branch') }
it { expect(described_class).to have_graphql_fields(:name, :commit) }
diff --git a/spec/graphql/types/ci/detailed_status_type_spec.rb b/spec/graphql/types/ci/detailed_status_type_spec.rb
index c62c8f23728..67199848df0 100644
--- a/spec/graphql/types/ci/detailed_status_type_spec.rb
+++ b/spec/graphql/types/ci/detailed_status_type_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Types::Ci::DetailedStatusType do
+RSpec.describe Types::Ci::DetailedStatusType do
specify { expect(described_class.graphql_name).to eq('DetailedStatus') }
it "has all fields" do
diff --git a/spec/graphql/types/ci/pipeline_type_spec.rb b/spec/graphql/types/ci/pipeline_type_spec.rb
index d56cff12105..f13f1c9afb2 100644
--- a/spec/graphql/types/ci/pipeline_type_spec.rb
+++ b/spec/graphql/types/ci/pipeline_type_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Types::Ci::PipelineType do
+RSpec.describe Types::Ci::PipelineType do
specify { expect(described_class.graphql_name).to eq('Pipeline') }
specify { expect(described_class).to expose_permissions_using(Types::PermissionTypes::Ci::Pipeline) }
diff --git a/spec/graphql/types/ci_configuration/sast/analyzers_entity_type_spec.rb b/spec/graphql/types/ci_configuration/sast/analyzers_entity_type_spec.rb
new file mode 100644
index 00000000000..34a22feeaf5
--- /dev/null
+++ b/spec/graphql/types/ci_configuration/sast/analyzers_entity_type_spec.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe GitlabSchema.types['SastCiConfigurationAnalyzersEntity'] do
+ let(:fields) { %i[name label enabled description] }
+
+ it { expect(described_class.graphql_name).to eq('SastCiConfigurationAnalyzersEntity') }
+
+ it { expect(described_class).to have_graphql_fields(fields) }
+end
diff --git a/spec/graphql/types/ci_configuration/sast/entity_type_spec.rb b/spec/graphql/types/ci_configuration/sast/entity_type_spec.rb
new file mode 100644
index 00000000000..7c6ad013d4a
--- /dev/null
+++ b/spec/graphql/types/ci_configuration/sast/entity_type_spec.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe GitlabSchema.types['SastCiConfigurationEntity'] do
+ let(:fields) { %i[field label description type options default_value value] }
+
+ it { expect(described_class.graphql_name).to eq('SastCiConfigurationEntity') }
+
+ it { expect(described_class).to have_graphql_fields(fields) }
+end
diff --git a/spec/graphql/types/ci_configuration/sast/options_entity_spec.rb b/spec/graphql/types/ci_configuration/sast/options_entity_spec.rb
new file mode 100644
index 00000000000..c60c8b9c84a
--- /dev/null
+++ b/spec/graphql/types/ci_configuration/sast/options_entity_spec.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe GitlabSchema.types['SastCiConfigurationOptionsEntity'] do
+ let(:fields) { %i[label value] }
+
+ it { expect(described_class.graphql_name).to eq('SastCiConfigurationOptionsEntity') }
+
+ it { expect(described_class).to have_graphql_fields(fields) }
+end
diff --git a/spec/graphql/types/ci_configuration/sast/type_spec.rb b/spec/graphql/types/ci_configuration/sast/type_spec.rb
new file mode 100644
index 00000000000..e7a8cd436e4
--- /dev/null
+++ b/spec/graphql/types/ci_configuration/sast/type_spec.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe GitlabSchema.types['SastCiConfiguration'] do
+ let(:fields) { %i[global pipeline analyzers] }
+
+ it { expect(described_class.graphql_name).to eq('SastCiConfiguration') }
+
+ it { expect(described_class).to have_graphql_fields(fields) }
+end
diff --git a/spec/graphql/types/commit_action_mode_enum_spec.rb b/spec/graphql/types/commit_action_mode_enum_spec.rb
index 9e1a27ea254..cedd466e292 100644
--- a/spec/graphql/types/commit_action_mode_enum_spec.rb
+++ b/spec/graphql/types/commit_action_mode_enum_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GitlabSchema.types['CommitActionMode'] do
+RSpec.describe GitlabSchema.types['CommitActionMode'] do
it { expect(described_class.graphql_name).to eq('CommitActionMode') }
it 'exposes all the existing commit actions' do
diff --git a/spec/graphql/types/commit_encoding_enum_spec.rb b/spec/graphql/types/commit_encoding_enum_spec.rb
index 30686a0c712..7acfb73c06d 100644
--- a/spec/graphql/types/commit_encoding_enum_spec.rb
+++ b/spec/graphql/types/commit_encoding_enum_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GitlabSchema.types['CommitEncoding'] do
+RSpec.describe GitlabSchema.types['CommitEncoding'] do
it { expect(described_class.graphql_name).to eq('CommitEncoding') }
it 'exposes all the existing encoding option' do
diff --git a/spec/graphql/types/commit_type_spec.rb b/spec/graphql/types/commit_type_spec.rb
index 88b450e3924..75984786972 100644
--- a/spec/graphql/types/commit_type_spec.rb
+++ b/spec/graphql/types/commit_type_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GitlabSchema.types['Commit'] do
+RSpec.describe GitlabSchema.types['Commit'] do
specify { expect(described_class.graphql_name).to eq('Commit') }
specify { expect(described_class).to require_graphql_authorizations(:download_code) }
diff --git a/spec/graphql/types/container_expiration_policy_cadence_enum_spec.rb b/spec/graphql/types/container_expiration_policy_cadence_enum_spec.rb
index 08c777cd365..9bd1fe3ea76 100644
--- a/spec/graphql/types/container_expiration_policy_cadence_enum_spec.rb
+++ b/spec/graphql/types/container_expiration_policy_cadence_enum_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GitlabSchema.types['ContainerExpirationPolicyCadenceEnum'] do
+RSpec.describe GitlabSchema.types['ContainerExpirationPolicyCadenceEnum'] do
let_it_be(:expected_values) { %w[EVERY_DAY EVERY_WEEK EVERY_TWO_WEEKS EVERY_MONTH EVERY_THREE_MONTHS] }
it_behaves_like 'exposing container expiration policy option', :cadence
diff --git a/spec/graphql/types/container_expiration_policy_keep_enum_spec.rb b/spec/graphql/types/container_expiration_policy_keep_enum_spec.rb
index 1a5b4bdd3bb..35385545fe3 100644
--- a/spec/graphql/types/container_expiration_policy_keep_enum_spec.rb
+++ b/spec/graphql/types/container_expiration_policy_keep_enum_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GitlabSchema.types['ContainerExpirationPolicyKeepEnum'] do
+RSpec.describe GitlabSchema.types['ContainerExpirationPolicyKeepEnum'] do
let_it_be(:expected_values) { %w[ONE_TAG FIVE_TAGS TEN_TAGS TWENTY_FIVE_TAGS FIFTY_TAGS ONE_HUNDRED_TAGS] }
it_behaves_like 'exposing container expiration policy option', :keep_n
diff --git a/spec/graphql/types/container_expiration_policy_older_than_enum_spec.rb b/spec/graphql/types/container_expiration_policy_older_than_enum_spec.rb
index 47f0ca22522..72ab605f2e6 100644
--- a/spec/graphql/types/container_expiration_policy_older_than_enum_spec.rb
+++ b/spec/graphql/types/container_expiration_policy_older_than_enum_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GitlabSchema.types['ContainerExpirationPolicyOlderThanEnum'] do
+RSpec.describe GitlabSchema.types['ContainerExpirationPolicyOlderThanEnum'] do
let_it_be(:expected_values) { %w[SEVEN_DAYS FOURTEEN_DAYS THIRTY_DAYS NINETY_DAYS] }
it_behaves_like 'exposing container expiration policy option', :older_than
diff --git a/spec/graphql/types/container_expiration_policy_type_spec.rb b/spec/graphql/types/container_expiration_policy_type_spec.rb
index 8924ab67847..9e9ddaf1cb0 100644
--- a/spec/graphql/types/container_expiration_policy_type_spec.rb
+++ b/spec/graphql/types/container_expiration_policy_type_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GitlabSchema.types['ContainerExpirationPolicy'] do
+RSpec.describe GitlabSchema.types['ContainerExpirationPolicy'] do
specify { expect(described_class.graphql_name).to eq('ContainerExpirationPolicy') }
specify { expect(described_class.description).to eq('A tag expiration policy designed to keep only the images that matter most') }
@@ -24,4 +24,20 @@ describe GitlabSchema.types['ContainerExpirationPolicy'] do
is_expected.to have_graphql_type(Types::ContainerExpirationPolicyKeepEnum)
end
end
+
+ describe 'name_regex field' do
+ subject { described_class.fields['nameRegex'] }
+
+ it 'returns untrusted regexp type' do
+ is_expected.to have_graphql_type(Types::UntrustedRegexp)
+ end
+ end
+
+ describe 'name_regex_keep field' do
+ subject { described_class.fields['nameRegexKeep'] }
+
+ it 'returns untrusted regexp type' do
+ is_expected.to have_graphql_type(Types::UntrustedRegexp)
+ end
+ end
end
diff --git a/spec/graphql/types/design_management/design_at_version_type_spec.rb b/spec/graphql/types/design_management/design_at_version_type_spec.rb
index 1453d73d59c..5a6292c924a 100644
--- a/spec/graphql/types/design_management/design_at_version_type_spec.rb
+++ b/spec/graphql/types/design_management/design_at_version_type_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GitlabSchema.types['DesignAtVersion'] do
+RSpec.describe GitlabSchema.types['DesignAtVersion'] do
it_behaves_like 'a GraphQL type with design fields' do
let(:extra_design_fields) { %i[version design] }
let_it_be(:design) { create(:design, :with_versions) }
diff --git a/spec/graphql/types/design_management/design_collection_type_spec.rb b/spec/graphql/types/design_management/design_collection_type_spec.rb
index 65150f0971d..6b1d3a87c2d 100644
--- a/spec/graphql/types/design_management/design_collection_type_spec.rb
+++ b/spec/graphql/types/design_management/design_collection_type_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GitlabSchema.types['DesignCollection'] do
+RSpec.describe GitlabSchema.types['DesignCollection'] do
it { expect(described_class).to require_graphql_authorizations(:read_design) }
it 'has the expected fields' do
diff --git a/spec/graphql/types/design_management/design_type_spec.rb b/spec/graphql/types/design_management/design_type_spec.rb
index 75b4cd66d5e..7a38b397965 100644
--- a/spec/graphql/types/design_management/design_type_spec.rb
+++ b/spec/graphql/types/design_management/design_type_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GitlabSchema.types['Design'] do
+RSpec.describe GitlabSchema.types['Design'] do
it_behaves_like 'a GraphQL type with design fields' do
let(:extra_design_fields) { %i[notes discussions versions] }
let_it_be(:design) { create(:design, :with_versions) }
diff --git a/spec/graphql/types/design_management/design_version_event_enum_spec.rb b/spec/graphql/types/design_management/design_version_event_enum_spec.rb
index a65f1bb5990..06576336231 100644
--- a/spec/graphql/types/design_management/design_version_event_enum_spec.rb
+++ b/spec/graphql/types/design_management/design_version_event_enum_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GitlabSchema.types['DesignVersionEvent'] do
+RSpec.describe GitlabSchema.types['DesignVersionEvent'] do
it { expect(described_class.graphql_name).to eq('DesignVersionEvent') }
it 'exposes the correct event states' do
diff --git a/spec/graphql/types/design_management/version_type_spec.rb b/spec/graphql/types/design_management/version_type_spec.rb
index 3317c4c6571..017cc1775a1 100644
--- a/spec/graphql/types/design_management/version_type_spec.rb
+++ b/spec/graphql/types/design_management/version_type_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GitlabSchema.types['DesignVersion'] do
+RSpec.describe GitlabSchema.types['DesignVersion'] do
it { expect(described_class).to require_graphql_authorizations(:read_design) }
it 'has the expected fields' do
diff --git a/spec/graphql/types/design_management_type_spec.rb b/spec/graphql/types/design_management_type_spec.rb
index a6204f20f23..e9162feec74 100644
--- a/spec/graphql/types/design_management_type_spec.rb
+++ b/spec/graphql/types/design_management_type_spec.rb
@@ -2,6 +2,6 @@
require 'spec_helper'
-describe GitlabSchema.types['DesignManagement'] do
+RSpec.describe GitlabSchema.types['DesignManagement'] do
it { expect(described_class).to have_graphql_fields(:version, :design_at_version) }
end
diff --git a/spec/graphql/types/diff_refs_type_spec.rb b/spec/graphql/types/diff_refs_type_spec.rb
index 3165e642452..894191c55ba 100644
--- a/spec/graphql/types/diff_refs_type_spec.rb
+++ b/spec/graphql/types/diff_refs_type_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GitlabSchema.types['DiffRefs'] do
+RSpec.describe GitlabSchema.types['DiffRefs'] do
specify { expect(described_class.graphql_name).to eq('DiffRefs') }
specify { expect(described_class).to have_graphql_fields(:head_sha, :base_sha, :start_sha).only }
diff --git a/spec/graphql/types/environment_type_spec.rb b/spec/graphql/types/environment_type_spec.rb
index 0e5cbac05df..f7522cb3e2c 100644
--- a/spec/graphql/types/environment_type_spec.rb
+++ b/spec/graphql/types/environment_type_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GitlabSchema.types['Environment'] do
+RSpec.describe GitlabSchema.types['Environment'] do
specify { expect(described_class.graphql_name).to eq('Environment') }
it 'has the expected fields' do
diff --git a/spec/graphql/types/error_tracking/sentry_detailed_error_type_spec.rb b/spec/graphql/types/error_tracking/sentry_detailed_error_type_spec.rb
index 0a094e9e188..8723c212486 100644
--- a/spec/graphql/types/error_tracking/sentry_detailed_error_type_spec.rb
+++ b/spec/graphql/types/error_tracking/sentry_detailed_error_type_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GitlabSchema.types['SentryDetailedError'] do
+RSpec.describe GitlabSchema.types['SentryDetailedError'] do
specify { expect(described_class.graphql_name).to eq('SentryDetailedError') }
specify { expect(described_class).to require_graphql_authorizations(:read_sentry_issue) }
@@ -31,6 +31,8 @@ describe GitlabSchema.types['SentryDetailedError'] do
lastReleaseLastCommit
firstReleaseShortVersion
lastReleaseShortVersion
+ firstReleaseVersion
+ lastReleaseVersion
gitlabIssuePath
gitlabCommit
gitlabCommitPath
diff --git a/spec/graphql/types/error_tracking/sentry_error_collection_type_spec.rb b/spec/graphql/types/error_tracking/sentry_error_collection_type_spec.rb
index 793da2db960..3a8ff7a73be 100644
--- a/spec/graphql/types/error_tracking/sentry_error_collection_type_spec.rb
+++ b/spec/graphql/types/error_tracking/sentry_error_collection_type_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GitlabSchema.types['SentryErrorCollection'] do
+RSpec.describe GitlabSchema.types['SentryErrorCollection'] do
specify { expect(described_class.graphql_name).to eq('SentryErrorCollection') }
specify { expect(described_class).to require_graphql_authorizations(:read_sentry_issue) }
diff --git a/spec/graphql/types/error_tracking/sentry_error_stack_trace_entry_type_spec.rb b/spec/graphql/types/error_tracking/sentry_error_stack_trace_entry_type_spec.rb
index b65398fccc9..54d0d8edbc6 100644
--- a/spec/graphql/types/error_tracking/sentry_error_stack_trace_entry_type_spec.rb
+++ b/spec/graphql/types/error_tracking/sentry_error_stack_trace_entry_type_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GitlabSchema.types['SentryErrorStackTraceEntry'] do
+RSpec.describe GitlabSchema.types['SentryErrorStackTraceEntry'] do
specify { expect(described_class.graphql_name).to eq('SentryErrorStackTraceEntry') }
it 'exposes the expected fields' do
diff --git a/spec/graphql/types/error_tracking/sentry_error_stack_trace_type_spec.rb b/spec/graphql/types/error_tracking/sentry_error_stack_trace_type_spec.rb
index 2cec8865764..07ddbd83d0a 100644
--- a/spec/graphql/types/error_tracking/sentry_error_stack_trace_type_spec.rb
+++ b/spec/graphql/types/error_tracking/sentry_error_stack_trace_type_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GitlabSchema.types['SentryErrorStackTrace'] do
+RSpec.describe GitlabSchema.types['SentryErrorStackTrace'] do
specify { expect(described_class.graphql_name).to eq('SentryErrorStackTrace') }
specify { expect(described_class).to require_graphql_authorizations(:read_sentry_issue) }
diff --git a/spec/graphql/types/error_tracking/sentry_error_type_spec.rb b/spec/graphql/types/error_tracking/sentry_error_type_spec.rb
index f8cc801e35e..6cbcc8888c6 100644
--- a/spec/graphql/types/error_tracking/sentry_error_type_spec.rb
+++ b/spec/graphql/types/error_tracking/sentry_error_type_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GitlabSchema.types['SentryError'] do
+RSpec.describe GitlabSchema.types['SentryError'] do
specify { expect(described_class.graphql_name).to eq('SentryError') }
it 'exposes the expected fields' do
diff --git a/spec/graphql/types/evidence_type_spec.rb b/spec/graphql/types/evidence_type_spec.rb
index 4a11f7bcda9..92134e74d51 100644
--- a/spec/graphql/types/evidence_type_spec.rb
+++ b/spec/graphql/types/evidence_type_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GitlabSchema.types['ReleaseEvidence'] do
+RSpec.describe GitlabSchema.types['ReleaseEvidence'] do
it { expect(described_class).to require_graphql_authorizations(:download_code) }
it 'has the expected fields' do
diff --git a/spec/graphql/types/global_id_type_spec.rb b/spec/graphql/types/global_id_type_spec.rb
new file mode 100644
index 00000000000..2a7b26f66b0
--- /dev/null
+++ b/spec/graphql/types/global_id_type_spec.rb
@@ -0,0 +1,215 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Types::GlobalIDType do
+ let_it_be(:project) { create(:project) }
+ let(:gid) { project.to_global_id }
+ let(:foreign_gid) { GlobalID.new(::URI::GID.build(app: 'otherapp', model_name: 'Project', model_id: project.id, params: nil)) }
+
+ it 'is has the correct name' do
+ expect(described_class.to_graphql.name).to eq('GlobalID')
+ end
+
+ describe '.coerce_result' do
+ it 'can coerce results' do
+ expect(described_class.coerce_isolated_result(gid)).to eq(gid.to_s)
+ end
+
+ it 'rejects integer IDs' do
+ expect { described_class.coerce_isolated_result(project.id) }
+ .to raise_error(ArgumentError)
+ end
+
+ it 'rejects strings' do
+ expect { described_class.coerce_isolated_result('not a GID') }
+ .to raise_error(ArgumentError)
+ end
+ end
+
+ describe '.coerce_input' do
+ it 'can coerce valid input' do
+ coerced = described_class.coerce_isolated_input(gid.to_s)
+
+ expect(coerced).to eq(gid)
+ end
+
+ it 'handles all valid application GIDs' do
+ expect { described_class.coerce_isolated_input(build_stubbed(:user).to_global_id.to_s) }
+ .not_to raise_error
+ end
+
+ it 'rejects invalid input' do
+ expect { described_class.coerce_isolated_input('not valid') }
+ .to raise_error(GraphQL::CoercionError)
+ end
+
+ it 'rejects nil' do
+ expect { described_class.coerce_isolated_input(nil) }
+ .to raise_error(GraphQL::CoercionError)
+ end
+
+ it 'rejects gids from different apps' do
+ expect { described_class.coerce_isolated_input(foreign_gid) }
+ .to raise_error(GraphQL::CoercionError)
+ end
+ end
+
+ describe 'a parameterized type' do
+ let(:type) { ::Types::GlobalIDType[::Project] }
+
+ it 'is has the correct name' do
+ expect(type.to_graphql.name).to eq('ProjectID')
+ end
+
+ context 'the GID is appropriate' do
+ it 'can coerce results' do
+ expect(type.coerce_isolated_result(gid)).to eq(gid.to_s)
+ end
+
+ it 'can coerce IDs to a GlobalIDType' do
+ expect(type.coerce_isolated_result(project.id)).to eq(gid.to_s)
+ end
+
+ it 'can coerce valid input' do
+ expect(type.coerce_isolated_input(gid.to_s)).to eq(gid)
+ end
+ end
+
+ context 'the GID is not for an appropriate type' do
+ let(:gid) { build_stubbed(:user).to_global_id }
+
+ it 'raises errors when coercing results' do
+ expect { type.coerce_isolated_result(gid) }.to raise_error(GraphQL::CoercionError)
+ end
+
+ it 'will not coerce invalid input, even if its a valid GID' do
+ expect { type.coerce_isolated_input(gid.to_s) }
+ .to raise_error(GraphQL::CoercionError)
+ end
+ end
+ end
+
+ describe 'a parameterized type with a namespace' do
+ let(:type) { ::Types::GlobalIDType[::Ci::Build] }
+
+ it 'is has a valid GraphQL identifier for a name' do
+ expect(type.to_graphql.name).to eq('CiBuildID')
+ end
+ end
+
+ describe 'compatibility' do
+ # Simplified schema to test compatibility
+
+ def query(doc, vars)
+ GraphQL::Query.new(schema, document: doc, context: {}, variables: vars)
+ end
+
+ def run_query(gql_query, vars)
+ query(GraphQL.parse(gql_query), vars).result
+ end
+
+ all_types = [::GraphQL::ID_TYPE, ::Types::GlobalIDType, ::Types::GlobalIDType[::Project]]
+
+ shared_examples 'a working query' do
+ let!(:schema) do
+ # capture values so they can be closed over
+ arg_type = argument_type
+ res_type = result_type
+
+ project = Class.new(GraphQL::Schema::Object) do
+ graphql_name 'Project'
+ field :name, String, null: false
+ field :id, res_type, null: false, resolver_method: :global_id
+
+ def global_id
+ object.to_global_id
+ end
+ end
+
+ Class.new(GraphQL::Schema) do
+ query(Class.new(GraphQL::Schema::Object) do
+ graphql_name 'Query'
+
+ field :project_by_id, project, null: true do
+ argument :id, arg_type, required: true
+ end
+
+ def project_by_id(id:)
+ gid = ::Types::GlobalIDType[::Project].coerce_isolated_input(id)
+ gid.model_class.find(gid.model_id)
+ end
+ end)
+ end
+ end
+
+ it 'works' do
+ res = run_query(document, 'projectId' => project.to_global_id.to_s)
+
+ expect(res['errors']).to be_blank
+ expect(res.dig('data', 'project', 'name')).to eq(project.name)
+ expect(res.dig('data', 'project', 'id')).to eq(project.to_global_id.to_s)
+ end
+ end
+
+ context 'when the argument is declared as ID' do
+ let(:document) do
+ <<-GRAPHQL
+ query($projectId: ID!){
+ project: projectById(id: $projectId) {
+ name, id
+ }
+ }
+ GRAPHQL
+ end
+
+ let(:argument_type) { ::GraphQL::ID_TYPE }
+
+ where(:result_type) { all_types }
+
+ with_them do
+ it_behaves_like 'a working query'
+ end
+ end
+
+ context 'when the argument is declared as GlobalID' do
+ let(:document) do
+ <<-GRAPHQL
+ query($projectId: GlobalID!) {
+ project: projectById(id: $projectId) {
+ name, id
+ }
+ }
+ GRAPHQL
+ end
+
+ let(:argument_type) { ::Types::GlobalIDType }
+
+ where(:result_type) { all_types }
+
+ with_them do
+ it_behaves_like 'a working query'
+ end
+ end
+
+ context 'when the argument is declared as ProjectID' do
+ let(:document) do
+ <<-GRAPHQL
+ query($projectId: ProjectID!) {
+ project: projectById(id: $projectId) {
+ name, id
+ }
+ }
+ GRAPHQL
+ end
+
+ let(:argument_type) { ::Types::GlobalIDType[::Project] }
+
+ where(:result_type) { all_types }
+
+ with_them do
+ it_behaves_like 'a working query'
+ end
+ end
+ end
+end
diff --git a/spec/graphql/types/grafana_integration_type_spec.rb b/spec/graphql/types/grafana_integration_type_spec.rb
index 429b5bdffe6..b4658db08d7 100644
--- a/spec/graphql/types/grafana_integration_type_spec.rb
+++ b/spec/graphql/types/grafana_integration_type_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GitlabSchema.types['GrafanaIntegration'] do
+RSpec.describe GitlabSchema.types['GrafanaIntegration'] do
let(:expected_fields) do
%i[
id
diff --git a/spec/graphql/types/group_member_type_spec.rb b/spec/graphql/types/group_member_type_spec.rb
index 5d09e60d21c..b1cb8e572ad 100644
--- a/spec/graphql/types/group_member_type_spec.rb
+++ b/spec/graphql/types/group_member_type_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Types::GroupMemberType do
+RSpec.describe Types::GroupMemberType do
specify { expect(described_class).to expose_permissions_using(Types::PermissionTypes::Group) }
specify { expect(described_class.graphql_name).to eq('GroupMember') }
diff --git a/spec/graphql/types/group_type_spec.rb b/spec/graphql/types/group_type_spec.rb
index c56cd40ef12..fb79e9bb85b 100644
--- a/spec/graphql/types/group_type_spec.rb
+++ b/spec/graphql/types/group_type_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GitlabSchema.types['Group'] do
+RSpec.describe GitlabSchema.types['Group'] do
specify { expect(described_class).to expose_permissions_using(Types::PermissionTypes::Group) }
specify { expect(described_class.graphql_name).to eq('Group') }
diff --git a/spec/graphql/types/issuable_sort_enum_spec.rb b/spec/graphql/types/issuable_sort_enum_spec.rb
index 35c42d8194c..e5237256a2b 100644
--- a/spec/graphql/types/issuable_sort_enum_spec.rb
+++ b/spec/graphql/types/issuable_sort_enum_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Types::IssuableSortEnum do
+RSpec.describe Types::IssuableSortEnum do
specify { expect(described_class.graphql_name).to eq('IssuableSort') }
it 'exposes all the existing issuable sort values' do
diff --git a/spec/graphql/types/issuable_state_enum_spec.rb b/spec/graphql/types/issuable_state_enum_spec.rb
index f974ed5f5fb..a63b8cddb14 100644
--- a/spec/graphql/types/issuable_state_enum_spec.rb
+++ b/spec/graphql/types/issuable_state_enum_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GitlabSchema.types['IssuableState'] do
+RSpec.describe GitlabSchema.types['IssuableState'] do
specify { expect(described_class.graphql_name).to eq('IssuableState') }
it_behaves_like 'issuable state'
diff --git a/spec/graphql/types/issue_connection_type_spec.rb b/spec/graphql/types/issue_connection_type_spec.rb
new file mode 100644
index 00000000000..af34611ecfe
--- /dev/null
+++ b/spec/graphql/types/issue_connection_type_spec.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe GitlabSchema.types['IssueConnection'] do
+ it 'has the expected fields' do
+ expected_fields = %i[count page_info edges nodes]
+
+ expect(described_class).to have_graphql_fields(*expected_fields)
+ end
+end
diff --git a/spec/graphql/types/issue_sort_enum_spec.rb b/spec/graphql/types/issue_sort_enum_spec.rb
index c496b897cdb..9313d3aee84 100644
--- a/spec/graphql/types/issue_sort_enum_spec.rb
+++ b/spec/graphql/types/issue_sort_enum_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GitlabSchema.types['IssueSort'] do
+RSpec.describe GitlabSchema.types['IssueSort'] do
specify { expect(described_class.graphql_name).to eq('IssueSort') }
it_behaves_like 'common sort values'
diff --git a/spec/graphql/types/issue_state_enum_spec.rb b/spec/graphql/types/issue_state_enum_spec.rb
index a18c5f5d317..a9dd287e196 100644
--- a/spec/graphql/types/issue_state_enum_spec.rb
+++ b/spec/graphql/types/issue_state_enum_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GitlabSchema.types['IssueState'] do
+RSpec.describe GitlabSchema.types['IssueState'] do
specify { expect(described_class.graphql_name).to eq('IssueState') }
it_behaves_like 'issuable state'
diff --git a/spec/graphql/types/issue_type_spec.rb b/spec/graphql/types/issue_type_spec.rb
index a8f7edcfe8e..4a86b07ab1c 100644
--- a/spec/graphql/types/issue_type_spec.rb
+++ b/spec/graphql/types/issue_type_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GitlabSchema.types['Issue'] do
+RSpec.describe GitlabSchema.types['Issue'] do
specify { expect(described_class).to expose_permissions_using(Types::PermissionTypes::Issue) }
specify { expect(described_class.graphql_name).to eq('Issue') }
@@ -12,7 +12,7 @@ describe GitlabSchema.types['Issue'] do
specify { expect(described_class.interfaces).to include(Types::Notes::NoteableType) }
it 'has specific fields' do
- fields = %i[iid title description state reference author assignees participants labels milestone due_date
+ fields = %i[id iid title description state reference author assignees participants labels milestone due_date
confidential discussion_locked upvotes downvotes user_notes_count web_path web_url relative_position
subscribed time_estimate total_time_spent closed_at created_at updated_at task_completion_status
designs design_collection]
@@ -22,6 +22,104 @@ describe GitlabSchema.types['Issue'] do
end
end
+ describe 'pagination and count' do
+ let_it_be(:user) { create(:user) }
+ let_it_be(:project) { create(:project, :public) }
+ let_it_be(:now) { Time.now.change(usec: 0) }
+ let_it_be(:issues) { create_list(:issue, 10, project: project, created_at: now) }
+
+ let(:count_path) { %w(data project issues count) }
+ let(:page_size) { 3 }
+ let(:query) do
+ <<~GRAPHQL
+ query project($fullPath: ID!, $first: Int, $after: String) {
+ project(fullPath: $fullPath) {
+ issues(first: $first, after: $after) {
+ count
+ edges {
+ node {
+ iid
+ }
+ }
+ pageInfo {
+ endCursor
+ hasNextPage
+ }
+ }
+ }
+ }
+ GRAPHQL
+ end
+
+ subject do
+ GitlabSchema.execute(
+ query,
+ context: { current_user: user },
+ variables: {
+ fullPath: project.full_path,
+ first: page_size
+ }
+ ).to_h
+ end
+
+ context 'when user does not have the permission' do
+ it 'returns no data' do
+ allow(Ability).to receive(:allowed?).with(user, :read_project, project).and_return(false)
+
+ expect(subject.dig(:data, :project)).to eq(nil)
+ end
+ end
+
+ context 'count' do
+ let(:end_cursor) { %w(data project issues pageInfo endCursor) }
+ let(:issues_edges) { %w(data project issues edges) }
+
+ it 'returns total count' do
+ expect(subject.dig(*count_path)).to eq(issues.count)
+ end
+
+ it 'total count does not change between pages' do
+ old_count = subject.dig(*count_path)
+ new_cursor = subject.dig(*end_cursor)
+
+ new_page = GitlabSchema.execute(
+ query,
+ context: { current_user: user },
+ variables: {
+ fullPath: project.full_path,
+ first: page_size,
+ after: new_cursor
+ }
+ ).to_h
+
+ new_count = new_page.dig(*count_path)
+ expect(old_count).to eq(new_count)
+ end
+
+ context 'pagination' do
+ let(:page_size) { 9 }
+
+ it 'returns new ids during pagination' do
+ old_edges = subject.dig(*issues_edges)
+ new_cursor = subject.dig(*end_cursor)
+
+ new_edges = GitlabSchema.execute(
+ query,
+ context: { current_user: user },
+ variables: {
+ fullPath: project.full_path,
+ first: page_size,
+ after: new_cursor
+ }
+ ).to_h.dig(*issues_edges)
+
+ expect(old_edges.count).to eq(9)
+ expect(new_edges.count).to eq(1)
+ end
+ end
+ end
+ end
+
describe "issue notes" do
let(:user) { create(:user) }
let(:project) { create(:project, :public) }
diff --git a/spec/graphql/types/jira_import_type_spec.rb b/spec/graphql/types/jira_import_type_spec.rb
index fa1152aec41..b44525d3304 100644
--- a/spec/graphql/types/jira_import_type_spec.rb
+++ b/spec/graphql/types/jira_import_type_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GitlabSchema.types['JiraImport'] do
+RSpec.describe GitlabSchema.types['JiraImport'] do
specify { expect(described_class.graphql_name).to eq('JiraImport') }
it 'has the expected fields' do
diff --git a/spec/graphql/types/jira_user_type_spec.rb b/spec/graphql/types/jira_user_type_spec.rb
new file mode 100644
index 00000000000..6e55efb42f4
--- /dev/null
+++ b/spec/graphql/types/jira_user_type_spec.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe GitlabSchema.types['JiraUser'] do
+ specify { expect(described_class.graphql_name).to eq('JiraUser') }
+
+ it 'has the expected fields' do
+ expect(described_class).to have_graphql_fields(
+ :jira_account_id, :jira_display_name, :jira_email, :gitlab_id, :gitlab_username, :gitlab_name
+ )
+ end
+end
diff --git a/spec/graphql/types/label_type_spec.rb b/spec/graphql/types/label_type_spec.rb
index 026c63906ef..6a999a2e925 100644
--- a/spec/graphql/types/label_type_spec.rb
+++ b/spec/graphql/types/label_type_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe GitlabSchema.types['Label'] do
+RSpec.describe GitlabSchema.types['Label'] do
it 'has the correct fields' do
expected_fields = [:id, :description, :description_html, :title, :color, :text_color]
diff --git a/spec/graphql/types/merge_request_state_enum_spec.rb b/spec/graphql/types/merge_request_state_enum_spec.rb
index 2abc7b298b1..6fc5803a5d0 100644
--- a/spec/graphql/types/merge_request_state_enum_spec.rb
+++ b/spec/graphql/types/merge_request_state_enum_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GitlabSchema.types['MergeRequestState'] do
+RSpec.describe GitlabSchema.types['MergeRequestState'] do
specify { expect(described_class.graphql_name).to eq('MergeRequestState') }
it_behaves_like 'issuable state'
diff --git a/spec/graphql/types/merge_request_type_spec.rb b/spec/graphql/types/merge_request_type_spec.rb
index 0f48264c99f..b3dccde8ce3 100644
--- a/spec/graphql/types/merge_request_type_spec.rb
+++ b/spec/graphql/types/merge_request_type_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GitlabSchema.types['MergeRequest'] do
+RSpec.describe GitlabSchema.types['MergeRequest'] do
specify { expect(described_class).to expose_permissions_using(Types::PermissionTypes::MergeRequest) }
specify { expect(described_class).to require_graphql_authorizations(:read_merge_request) }
@@ -15,7 +15,8 @@ describe GitlabSchema.types['MergeRequest'] do
description_html state created_at updated_at source_project target_project
project project_id source_project_id target_project_id source_branch
target_branch work_in_progress merge_when_pipeline_succeeds diff_head_sha
- merge_commit_sha user_notes_count should_remove_source_branch diff_refs
+ merge_commit_sha user_notes_count should_remove_source_branch
+ diff_refs diff_stats diff_stats_summary
force_remove_source_branch merge_status in_progress_merge_commit_sha
merge_error allow_collaboration should_be_rebased rebase_commit_sha
rebase_in_progress merge_commit_message default_merge_commit_message
diff --git a/spec/graphql/types/metadata_type_spec.rb b/spec/graphql/types/metadata_type_spec.rb
index 75369ec9c3c..a1efb0a03d5 100644
--- a/spec/graphql/types/metadata_type_spec.rb
+++ b/spec/graphql/types/metadata_type_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GitlabSchema.types['Metadata'] do
+RSpec.describe GitlabSchema.types['Metadata'] do
specify { expect(described_class.graphql_name).to eq('Metadata') }
specify { expect(described_class).to require_graphql_authorizations(:read_instance_metadata) }
end
diff --git a/spec/graphql/types/metrics/dashboard_type_spec.rb b/spec/graphql/types/metrics/dashboard_type_spec.rb
index 0dbd0d8b38d..30dccc7c0be 100644
--- a/spec/graphql/types/metrics/dashboard_type_spec.rb
+++ b/spec/graphql/types/metrics/dashboard_type_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GitlabSchema.types['MetricsDashboard'] do
+RSpec.describe GitlabSchema.types['MetricsDashboard'] do
specify { expect(described_class.graphql_name).to eq('MetricsDashboard') }
it 'has the expected fields' do
diff --git a/spec/graphql/types/metrics/dashboards/annotation_type_spec.rb b/spec/graphql/types/metrics/dashboards/annotation_type_spec.rb
index dbb8b04dbd7..12c5eec937c 100644
--- a/spec/graphql/types/metrics/dashboards/annotation_type_spec.rb
+++ b/spec/graphql/types/metrics/dashboards/annotation_type_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GitlabSchema.types['MetricsDashboardAnnotation'] do
+RSpec.describe GitlabSchema.types['MetricsDashboardAnnotation'] do
specify { expect(described_class.graphql_name).to eq('MetricsDashboardAnnotation') }
it 'has the expected fields' do
diff --git a/spec/graphql/types/milestone_stats_type_spec.rb b/spec/graphql/types/milestone_stats_type_spec.rb
new file mode 100644
index 00000000000..1ccb275c922
--- /dev/null
+++ b/spec/graphql/types/milestone_stats_type_spec.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe GitlabSchema.types['MilestoneStats'] do
+ it { expect(described_class).to require_graphql_authorizations(:read_milestone) }
+
+ it 'has the expected fields' do
+ expected_fields = %w[
+ total_issues_count closed_issues_count
+ ]
+
+ expect(described_class).to include_graphql_fields(*expected_fields)
+ end
+end
diff --git a/spec/graphql/types/milestone_type_spec.rb b/spec/graphql/types/milestone_type_spec.rb
index 4c3d9f50a64..2315c10433b 100644
--- a/spec/graphql/types/milestone_type_spec.rb
+++ b/spec/graphql/types/milestone_type_spec.rb
@@ -2,8 +2,25 @@
require 'spec_helper'
-describe GitlabSchema.types['Milestone'] do
+RSpec.describe GitlabSchema.types['Milestone'] do
specify { expect(described_class.graphql_name).to eq('Milestone') }
specify { expect(described_class).to require_graphql_authorizations(:read_milestone) }
+
+ it 'has the expected fields' do
+ expected_fields = %w[
+ id title description state web_path
+ due_date start_date created_at updated_at
+ project_milestone group_milestone subgroup_milestone
+ stats
+ ]
+
+ expect(described_class).to have_graphql_fields(*expected_fields)
+ end
+
+ describe 'stats field' do
+ subject { described_class.fields['stats'] }
+
+ it { is_expected.to have_graphql_type(Types::MilestoneStatsType) }
+ end
end
diff --git a/spec/graphql/types/mutation_type_spec.rb b/spec/graphql/types/mutation_type_spec.rb
index a67d83b1edf..41993327577 100644
--- a/spec/graphql/types/mutation_type_spec.rb
+++ b/spec/graphql/types/mutation_type_spec.rb
@@ -2,8 +2,34 @@
require 'spec_helper'
-describe Types::MutationType do
+RSpec.describe Types::MutationType do
it 'is expected to have the MergeRequestSetWip' do
expect(described_class).to have_graphql_mutation(Mutations::MergeRequests::SetWip)
end
+
+ describe 'deprecated and aliased mutations' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:alias_name, :canonical_name) do
+ 'AddAwardEmoji' | 'AwardEmojiAdd'
+ 'RemoveAwardEmoji' | 'AwardEmojiRemove'
+ 'ToggleAwardEmoji' | 'AwardEmojiToggle'
+ end
+
+ with_them do
+ let(:alias_field) { get_field(alias_name) }
+ let(:canonical_field) { get_field(canonical_name) }
+
+ it { expect(alias_field).to be_present }
+ it { expect(canonical_field).to be_present }
+ it { expect(alias_field.deprecation_reason).to be_present }
+ it { expect(canonical_field.deprecation_reason).not_to be_present }
+ it { expect(alias_field.resolver.fields).to eq(canonical_field.resolver.fields) }
+ it { expect(alias_field.resolver.arguments).to eq(canonical_field.resolver.arguments) }
+ end
+
+ def get_field(name)
+ described_class.fields[GraphqlHelpers.fieldnamerize(name)]
+ end
+ end
end
diff --git a/spec/graphql/types/namespace_type_spec.rb b/spec/graphql/types/namespace_type_spec.rb
index 741698021e7..2ed1ee3e8c4 100644
--- a/spec/graphql/types/namespace_type_spec.rb
+++ b/spec/graphql/types/namespace_type_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GitlabSchema.types['Namespace'] do
+RSpec.describe GitlabSchema.types['Namespace'] do
specify { expect(described_class.graphql_name).to eq('Namespace') }
it 'has the expected fields' do
@@ -11,7 +11,7 @@ describe GitlabSchema.types['Namespace'] do
lfs_enabled request_access_enabled projects root_storage_statistics
]
- expect(described_class).to have_graphql_fields(*expected_fields)
+ expect(described_class).to include_graphql_fields(*expected_fields)
end
specify { expect(described_class).to require_graphql_authorizations(:read_namespace) }
diff --git a/spec/graphql/types/notes/diff_position_type_spec.rb b/spec/graphql/types/notes/diff_position_type_spec.rb
index 87f3810d55c..1515c2f66d7 100644
--- a/spec/graphql/types/notes/diff_position_type_spec.rb
+++ b/spec/graphql/types/notes/diff_position_type_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GitlabSchema.types['DiffPosition'] do
+RSpec.describe GitlabSchema.types['DiffPosition'] do
it 'exposes the expected fields' do
expected_fields = %i[
diff_refs
diff --git a/spec/graphql/types/notes/discussion_type_spec.rb b/spec/graphql/types/notes/discussion_type_spec.rb
index 177000b01b2..37ed861d069 100644
--- a/spec/graphql/types/notes/discussion_type_spec.rb
+++ b/spec/graphql/types/notes/discussion_type_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GitlabSchema.types['Discussion'] do
+RSpec.describe GitlabSchema.types['Discussion'] do
it 'exposes the expected fields' do
expected_fields = %i[
created_at
diff --git a/spec/graphql/types/notes/note_type_spec.rb b/spec/graphql/types/notes/note_type_spec.rb
index d6cd0800234..180d13d35d2 100644
--- a/spec/graphql/types/notes/note_type_spec.rb
+++ b/spec/graphql/types/notes/note_type_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GitlabSchema.types['Note'] do
+RSpec.describe GitlabSchema.types['Note'] do
it 'exposes the expected fields' do
expected_fields = %i[
author
@@ -19,6 +19,7 @@ describe GitlabSchema.types['Note'] do
resolved_at
resolved_by
system
+ system_note_icon_name
updated_at
user_permissions
]
diff --git a/spec/graphql/types/notes/noteable_type_spec.rb b/spec/graphql/types/notes/noteable_type_spec.rb
index 88d8eae56d1..fad24c6fed4 100644
--- a/spec/graphql/types/notes/noteable_type_spec.rb
+++ b/spec/graphql/types/notes/noteable_type_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Types::Notes::NoteableType do
+RSpec.describe Types::Notes::NoteableType do
it 'exposes the expected fields' do
expected_fields = %i[
discussions
diff --git a/spec/graphql/types/package_type_enum_spec.rb b/spec/graphql/types/package_type_enum_spec.rb
new file mode 100644
index 00000000000..fadec9744ed
--- /dev/null
+++ b/spec/graphql/types/package_type_enum_spec.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe GitlabSchema.types['PackageTypeEnum'] do
+ it 'exposes all package types' do
+ expect(described_class.values.keys).to contain_exactly(*%w[MAVEN NPM CONAN NUGET PYPI COMPOSER])
+ end
+end
diff --git a/spec/graphql/types/package_type_spec.rb b/spec/graphql/types/package_type_spec.rb
new file mode 100644
index 00000000000..22048e7a693
--- /dev/null
+++ b/spec/graphql/types/package_type_spec.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe GitlabSchema.types['Package'] do
+ it { expect(described_class.graphql_name).to eq('Package') }
+
+ it 'includes all the package fields' do
+ expected_fields = %w[
+ id name version created_at updated_at package_type
+ ]
+
+ expect(described_class).to include_graphql_fields(*expected_fields)
+ end
+end
diff --git a/spec/graphql/types/permission_types/base_permission_type_spec.rb b/spec/graphql/types/permission_types/base_permission_type_spec.rb
index 4c6d5fd369a..2ce02f1520c 100644
--- a/spec/graphql/types/permission_types/base_permission_type_spec.rb
+++ b/spec/graphql/types/permission_types/base_permission_type_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Types::PermissionTypes::BasePermissionType do
+RSpec.describe Types::PermissionTypes::BasePermissionType do
let(:permitable) { double('permittable') }
let(:current_user) { build(:user) }
let(:context) { { current_user: current_user } }
diff --git a/spec/graphql/types/permission_types/issue_spec.rb b/spec/graphql/types/permission_types/issue_spec.rb
index a7a3dd00f11..58c5808cbcc 100644
--- a/spec/graphql/types/permission_types/issue_spec.rb
+++ b/spec/graphql/types/permission_types/issue_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Types::PermissionTypes::Issue do
+RSpec.describe Types::PermissionTypes::Issue do
it do
expected_permissions = [
:read_issue, :admin_issue, :update_issue, :reopen_issue,
diff --git a/spec/graphql/types/permission_types/merge_request_spec.rb b/spec/graphql/types/permission_types/merge_request_spec.rb
index e0f8bdd4712..73a178540a6 100644
--- a/spec/graphql/types/permission_types/merge_request_spec.rb
+++ b/spec/graphql/types/permission_types/merge_request_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Types::PermissionTypes::MergeRequest do
+RSpec.describe Types::PermissionTypes::MergeRequest do
it do
expected_permissions = [
:read_merge_request, :admin_merge_request, :update_merge_request,
diff --git a/spec/graphql/types/permission_types/merge_request_type_spec.rb b/spec/graphql/types/permission_types/merge_request_type_spec.rb
index 7e9752cdc46..7dd1d3bca06 100644
--- a/spec/graphql/types/permission_types/merge_request_type_spec.rb
+++ b/spec/graphql/types/permission_types/merge_request_type_spec.rb
@@ -2,6 +2,6 @@
require 'spec_helper'
-describe Types::MergeRequestType do
+RSpec.describe Types::MergeRequestType do
specify { expect(described_class).to expose_permissions_using(Types::PermissionTypes::MergeRequest) }
end
diff --git a/spec/graphql/types/permission_types/note_spec.rb b/spec/graphql/types/permission_types/note_spec.rb
index a2becb6892b..9769c7b3aa3 100644
--- a/spec/graphql/types/permission_types/note_spec.rb
+++ b/spec/graphql/types/permission_types/note_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GitlabSchema.types['NotePermissions'] do
+RSpec.describe GitlabSchema.types['NotePermissions'] do
it 'has the expected fields' do
expected_permissions = [
:read_note, :create_note, :admin_note, :resolve_note, :award_emoji
diff --git a/spec/graphql/types/permission_types/project_spec.rb b/spec/graphql/types/permission_types/project_spec.rb
index 2789464d29c..c6853a0eadc 100644
--- a/spec/graphql/types/permission_types/project_spec.rb
+++ b/spec/graphql/types/permission_types/project_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Types::PermissionTypes::Project do
+RSpec.describe Types::PermissionTypes::Project do
it do
expected_permissions = [
:change_namespace, :change_visibility_level, :rename_project, :remove_project, :archive_project,
diff --git a/spec/graphql/types/permission_types/snippet_spec.rb b/spec/graphql/types/permission_types/snippet_spec.rb
index 66e9fa6dfdb..e2caa1c52c2 100644
--- a/spec/graphql/types/permission_types/snippet_spec.rb
+++ b/spec/graphql/types/permission_types/snippet_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Types::PermissionTypes::Snippet do
+RSpec.describe Types::PermissionTypes::Snippet do
it 'returns the snippets permissions' do
expected_permissions = [
:create_note, :award_emoji, :read_snippet, :update_snippet, :admin_snippet, :report_snippet
diff --git a/spec/graphql/types/permission_types/user_spec.rb b/spec/graphql/types/permission_types/user_spec.rb
index 1e8201db568..6f0d380f763 100644
--- a/spec/graphql/types/permission_types/user_spec.rb
+++ b/spec/graphql/types/permission_types/user_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Types::PermissionTypes::User do
+RSpec.describe Types::PermissionTypes::User do
it 'returns user permissions' do
expected_permissions = [
:create_snippet
diff --git a/spec/graphql/types/project_member_type_spec.rb b/spec/graphql/types/project_member_type_spec.rb
index 1b1f6c24a32..65c89557ec1 100644
--- a/spec/graphql/types/project_member_type_spec.rb
+++ b/spec/graphql/types/project_member_type_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Types::ProjectMemberType do
+RSpec.describe Types::ProjectMemberType do
specify { expect(described_class).to expose_permissions_using(Types::PermissionTypes::Project) }
specify { expect(described_class.graphql_name).to eq('ProjectMember') }
diff --git a/spec/graphql/types/project_statistics_type_spec.rb b/spec/graphql/types/project_statistics_type_spec.rb
index fbea780494b..e6cffd407de 100644
--- a/spec/graphql/types/project_statistics_type_spec.rb
+++ b/spec/graphql/types/project_statistics_type_spec.rb
@@ -2,10 +2,10 @@
require 'spec_helper'
-describe GitlabSchema.types['ProjectStatistics'] do
- it "has all the required fields" do
+RSpec.describe GitlabSchema.types['ProjectStatistics'] do
+ it 'has all the required fields' do
expect(described_class).to have_graphql_fields(:storage_size, :repository_size, :lfs_objects_size,
:build_artifacts_size, :packages_size, :commit_count,
- :wiki_size)
+ :wiki_size, :snippets_size)
end
end
diff --git a/spec/graphql/types/project_type_spec.rb b/spec/graphql/types/project_type_spec.rb
index 8ee9aa9cf3a..ea88ed6a3f5 100644
--- a/spec/graphql/types/project_type_spec.rb
+++ b/spec/graphql/types/project_type_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GitlabSchema.types['Project'] do
+RSpec.describe GitlabSchema.types['Project'] do
specify { expect(described_class).to expose_permissions_using(Types::PermissionTypes::Project) }
specify { expect(described_class.graphql_name).to eq('Project') }
@@ -26,7 +26,7 @@ describe GitlabSchema.types['Project'] do
grafanaIntegration autocloseReferencedIssues suggestion_commit_message environments
boards jira_import_status jira_imports services releases release
alert_management_alerts alert_management_alert alert_management_alert_status_counts
- container_expiration_policy
+ container_expiration_policy sast_ci_configuration service_desk_enabled service_desk_address
]
expect(described_class).to include_graphql_fields(*expected_fields)
@@ -140,5 +140,93 @@ describe GitlabSchema.types['Project'] do
it { is_expected.to have_graphql_type(Types::ContainerExpirationPolicyType) }
end
+ describe 'sast_ci_configuration' do
+ let_it_be(:project) { create(:project) }
+ let_it_be(:user) { create(:user) }
+ let_it_be(:query) do
+ %(
+ query {
+ project(fullPath: "#{project.full_path}") {
+ sastCiConfiguration {
+ global {
+ nodes {
+ type
+ options {
+ nodes {
+ label
+ value
+ }
+ }
+ field
+ label
+ defaultValue
+ value
+ }
+ }
+ pipeline {
+ nodes {
+ type
+ options {
+ nodes {
+ label
+ value
+ }
+ }
+ field
+ label
+ defaultValue
+ value
+ }
+ }
+ analyzers {
+ nodes {
+ name
+ label
+ enabled
+ }
+ }
+ }
+ }
+ }
+ )
+ end
+
+ subject { GitlabSchema.execute(query, context: { current_user: user }).as_json }
+
+ before do
+ project.add_developer(user)
+ end
+
+ it "returns the project's sast configuration for global variables" do
+ query_result = subject.dig('data', 'project', 'sastCiConfiguration', 'global', 'nodes')
+ first_config = query_result.first
+ fourth_config = query_result[3]
+ expect(first_config['type']).to eq('string')
+ expect(first_config['field']).to eq('SECURE_ANALYZERS_PREFIX')
+ expect(first_config['label']).to eq('Image prefix')
+ expect(first_config['defaultValue']).to eq('registry.gitlab.com/gitlab-org/security-products/analyzers')
+ expect(first_config['value']).to eq('')
+ expect(first_config['options']).to be_nil
+ expect(fourth_config['options']['nodes']).to match([{ "value" => "true", "label" => "true (disables SAST)" },
+ { "value" => "false", "label" => "false (enables SAST)" }])
+ end
+
+ it "returns the project's sast configuration for pipeline variables" do
+ configuration = subject.dig('data', 'project', 'sastCiConfiguration', 'pipeline', 'nodes').first
+ expect(configuration['type']).to eq('dropdown')
+ expect(configuration['field']).to eq('stage')
+ expect(configuration['label']).to eq('Stage')
+ expect(configuration['defaultValue']).to eq('test')
+ expect(configuration['value']).to eq('')
+ end
+
+ it "returns the project's sast configuration for analyzer variables" do
+ configuration = subject.dig('data', 'project', 'sastCiConfiguration', 'analyzers', 'nodes').first
+ expect(configuration['name']).to eq('brakeman')
+ expect(configuration['label']).to eq('Brakeman')
+ expect(configuration['enabled']).to eq(true)
+ end
+ end
+
it_behaves_like 'a GraphQL type with labels'
end
diff --git a/spec/graphql/types/projects/base_service_type_spec.rb b/spec/graphql/types/projects/base_service_type_spec.rb
index 4fcb9fe1a73..423cea860d7 100644
--- a/spec/graphql/types/projects/base_service_type_spec.rb
+++ b/spec/graphql/types/projects/base_service_type_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GitlabSchema.types['BaseService'] do
+RSpec.describe GitlabSchema.types['BaseService'] do
specify { expect(described_class.graphql_name).to eq('BaseService') }
it 'has basic expected fields' do
diff --git a/spec/graphql/types/projects/jira_project_type_spec.rb b/spec/graphql/types/projects/jira_project_type_spec.rb
index cbb01117717..60b4c5c5276 100644
--- a/spec/graphql/types/projects/jira_project_type_spec.rb
+++ b/spec/graphql/types/projects/jira_project_type_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GitlabSchema.types['JiraProject'] do
+RSpec.describe GitlabSchema.types['JiraProject'] do
it { expect(described_class.graphql_name).to eq('JiraProject') }
it 'has basic expected fields' do
diff --git a/spec/graphql/types/projects/jira_service_type_spec.rb b/spec/graphql/types/projects/jira_service_type_spec.rb
index fad0c91caab..9db580ac963 100644
--- a/spec/graphql/types/projects/jira_service_type_spec.rb
+++ b/spec/graphql/types/projects/jira_service_type_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GitlabSchema.types['JiraService'] do
+RSpec.describe GitlabSchema.types['JiraService'] do
specify { expect(described_class.graphql_name).to eq('JiraService') }
it 'has basic expected fields' do
diff --git a/spec/graphql/types/projects/service_type_spec.rb b/spec/graphql/types/projects/service_type_spec.rb
index f6758d17d18..f110322ac89 100644
--- a/spec/graphql/types/projects/service_type_spec.rb
+++ b/spec/graphql/types/projects/service_type_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Types::Projects::ServiceType do
+RSpec.describe Types::Projects::ServiceType do
specify { expect(described_class).to have_graphql_fields(:type, :active) }
describe ".resolve_type" do
diff --git a/spec/graphql/types/projects/services_enum_spec.rb b/spec/graphql/types/projects/services_enum_spec.rb
index 91e398e8d81..dac1213daf3 100644
--- a/spec/graphql/types/projects/services_enum_spec.rb
+++ b/spec/graphql/types/projects/services_enum_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GitlabSchema.types['ServiceType'] do
+RSpec.describe GitlabSchema.types['ServiceType'] do
specify { expect(described_class.graphql_name).to eq('ServiceType') }
it 'exposes all the existing project services' do
diff --git a/spec/graphql/types/query_type_spec.rb b/spec/graphql/types/query_type_spec.rb
index 1194391c26a..081f99a8307 100644
--- a/spec/graphql/types/query_type_spec.rb
+++ b/spec/graphql/types/query_type_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GitlabSchema.types['Query'] do
+RSpec.describe GitlabSchema.types['Query'] do
it 'is called Query' do
expect(described_class.graphql_name).to eq('Query')
end
diff --git a/spec/graphql/types/release_asset_link_type_spec.rb b/spec/graphql/types/release_asset_link_type_spec.rb
new file mode 100644
index 00000000000..679431012cf
--- /dev/null
+++ b/spec/graphql/types/release_asset_link_type_spec.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe GitlabSchema.types['ReleaseAssetLink'] do
+ it { expect(described_class).to require_graphql_authorizations(:read_release) }
+
+ it 'has the expected fields' do
+ expected_fields = %w[
+ id name url external link_type
+ ]
+
+ expect(described_class).to include_graphql_fields(*expected_fields)
+ end
+end
diff --git a/spec/graphql/types/release_assets_type_spec.rb b/spec/graphql/types/release_assets_type_spec.rb
index 58f0f7ee697..0166f87bf47 100644
--- a/spec/graphql/types/release_assets_type_spec.rb
+++ b/spec/graphql/types/release_assets_type_spec.rb
@@ -2,12 +2,12 @@
require 'spec_helper'
-describe GitlabSchema.types['ReleaseAssets'] do
+RSpec.describe GitlabSchema.types['ReleaseAssets'] do
it { expect(described_class).to require_graphql_authorizations(:read_release) }
it 'has the expected fields' do
expected_fields = %w[
- assets_count links sources
+ count links sources
]
expect(described_class).to include_graphql_fields(*expected_fields)
@@ -16,7 +16,7 @@ describe GitlabSchema.types['ReleaseAssets'] do
describe 'links field' do
subject { described_class.fields['links'] }
- it { is_expected.to have_graphql_type(Types::ReleaseLinkType.connection_type) }
+ it { is_expected.to have_graphql_type(Types::ReleaseAssetLinkType.connection_type) }
end
describe 'sources field' do
diff --git a/spec/graphql/types/release_links_type_spec.rb b/spec/graphql/types/release_links_type_spec.rb
index 49e04e120f4..d505f0a4b5c 100644
--- a/spec/graphql/types/release_links_type_spec.rb
+++ b/spec/graphql/types/release_links_type_spec.rb
@@ -2,12 +2,15 @@
require 'spec_helper'
-describe GitlabSchema.types['ReleaseLink'] do
- it { expect(described_class).to require_graphql_authorizations(:read_release) }
+RSpec.describe GitlabSchema.types['ReleaseLinks'] do
+ it { expect(described_class).to require_graphql_authorizations(:download_code) }
it 'has the expected fields' do
expected_fields = %w[
- id name url external link_type
+ selfUrl
+ mergeRequestsUrl
+ issuesUrl
+ editUrl
]
expect(described_class).to include_graphql_fields(*expected_fields)
diff --git a/spec/graphql/types/release_source_type_spec.rb b/spec/graphql/types/release_source_type_spec.rb
index e471ac1a5ac..69a1ca30dbc 100644
--- a/spec/graphql/types/release_source_type_spec.rb
+++ b/spec/graphql/types/release_source_type_spec.rb
@@ -2,8 +2,8 @@
require 'spec_helper'
-describe GitlabSchema.types['ReleaseSource'] do
- it { expect(described_class).to require_graphql_authorizations(:read_release_sources) }
+RSpec.describe GitlabSchema.types['ReleaseSource'] do
+ it { expect(described_class).to require_graphql_authorizations(:download_code) }
it 'has the expected fields' do
expected_fields = %w[
diff --git a/spec/graphql/types/release_type_spec.rb b/spec/graphql/types/release_type_spec.rb
index feafe5ed519..0c05a68c5a6 100644
--- a/spec/graphql/types/release_type_spec.rb
+++ b/spec/graphql/types/release_type_spec.rb
@@ -2,14 +2,15 @@
require 'spec_helper'
-describe GitlabSchema.types['Release'] do
+RSpec.describe GitlabSchema.types['Release'] do
it { expect(described_class).to require_graphql_authorizations(:read_release) }
it 'has the expected fields' do
expected_fields = %w[
tag_name tag_path
description description_html
- name assets milestones evidences author commit
+ name milestones evidences author commit
+ assets links
created_at released_at
]
@@ -22,6 +23,12 @@ describe GitlabSchema.types['Release'] do
it { is_expected.to have_graphql_type(Types::ReleaseAssetsType) }
end
+ describe 'links field' do
+ subject { described_class.fields['links'] }
+
+ it { is_expected.to have_graphql_type(Types::ReleaseLinksType) }
+ end
+
describe 'milestones field' do
subject { described_class.fields['milestones'] }
@@ -44,6 +51,5 @@ describe GitlabSchema.types['Release'] do
subject { described_class.fields['commit'] }
it { is_expected.to have_graphql_type(Types::CommitType) }
- it { is_expected.to require_graphql_authorizations(:reporter_access) }
end
end
diff --git a/spec/graphql/types/repository_type_spec.rb b/spec/graphql/types/repository_type_spec.rb
index fb52839c712..27780476421 100644
--- a/spec/graphql/types/repository_type_spec.rb
+++ b/spec/graphql/types/repository_type_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GitlabSchema.types['Repository'] do
+RSpec.describe GitlabSchema.types['Repository'] do
specify { expect(described_class.graphql_name).to eq('Repository') }
specify { expect(described_class).to require_graphql_authorizations(:download_code) }
diff --git a/spec/graphql/types/resolvable_interface_spec.rb b/spec/graphql/types/resolvable_interface_spec.rb
index 231287f9969..25370f8d527 100644
--- a/spec/graphql/types/resolvable_interface_spec.rb
+++ b/spec/graphql/types/resolvable_interface_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Types::ResolvableInterface do
+RSpec.describe Types::ResolvableInterface do
it 'exposes the expected fields' do
expected_fields = %i[
resolvable
diff --git a/spec/graphql/types/root_storage_statistics_type_spec.rb b/spec/graphql/types/root_storage_statistics_type_spec.rb
index ebaa5a18623..f01c55cbccb 100644
--- a/spec/graphql/types/root_storage_statistics_type_spec.rb
+++ b/spec/graphql/types/root_storage_statistics_type_spec.rb
@@ -2,12 +2,12 @@
require 'spec_helper'
-describe GitlabSchema.types['RootStorageStatistics'] do
+RSpec.describe GitlabSchema.types['RootStorageStatistics'] do
specify { expect(described_class.graphql_name).to eq('RootStorageStatistics') }
it 'has all the required fields' do
expect(described_class).to have_graphql_fields(:storage_size, :repository_size, :lfs_objects_size,
- :build_artifacts_size, :packages_size, :wiki_size)
+ :build_artifacts_size, :packages_size, :wiki_size, :snippets_size)
end
specify { expect(described_class).to require_graphql_authorizations(:read_statistics) }
diff --git a/spec/graphql/types/snippet_type_spec.rb b/spec/graphql/types/snippet_type_spec.rb
index f24419ce9cc..0341ca2c733 100644
--- a/spec/graphql/types/snippet_type_spec.rb
+++ b/spec/graphql/types/snippet_type_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GitlabSchema.types['Snippet'] do
+RSpec.describe GitlabSchema.types['Snippet'] do
let_it_be(:user) { create(:user) }
it 'has the correct fields' do
diff --git a/spec/graphql/types/snippets/blob_type_spec.rb b/spec/graphql/types/snippets/blob_type_spec.rb
index fb8c6896732..bfac08f40d3 100644
--- a/spec/graphql/types/snippets/blob_type_spec.rb
+++ b/spec/graphql/types/snippets/blob_type_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GitlabSchema.types['SnippetBlob'] do
+RSpec.describe GitlabSchema.types['SnippetBlob'] do
it 'has the correct fields' do
expected_fields = [:rich_data, :plain_data,
:raw_path, :size, :binary, :name, :path,
diff --git a/spec/graphql/types/snippets/blob_viewer_type_spec.rb b/spec/graphql/types/snippets/blob_viewer_type_spec.rb
index 841e22451db..8210eb9a95c 100644
--- a/spec/graphql/types/snippets/blob_viewer_type_spec.rb
+++ b/spec/graphql/types/snippets/blob_viewer_type_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GitlabSchema.types['SnippetBlobViewer'] do
+RSpec.describe GitlabSchema.types['SnippetBlobViewer'] do
let_it_be(:snippet) { create(:personal_snippet, :repository) }
let_it_be(:blob) { snippet.repository.blob_at('HEAD', 'files/images/6049019_460s.jpg') }
diff --git a/spec/graphql/types/snippets/file_input_action_enum_spec.rb b/spec/graphql/types/snippets/file_input_action_enum_spec.rb
index 2ccc8b04b8f..ff9b706240b 100644
--- a/spec/graphql/types/snippets/file_input_action_enum_spec.rb
+++ b/spec/graphql/types/snippets/file_input_action_enum_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Types::Snippets::FileInputActionEnum do
+RSpec.describe Types::Snippets::FileInputActionEnum do
specify { expect(described_class.graphql_name).to eq('SnippetFileInputActionEnum') }
it 'exposes all file input action types' do
diff --git a/spec/graphql/types/snippets/file_input_type_spec.rb b/spec/graphql/types/snippets/file_input_type_spec.rb
index 62e5caf20b7..c7d4909b542 100644
--- a/spec/graphql/types/snippets/file_input_type_spec.rb
+++ b/spec/graphql/types/snippets/file_input_type_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Types::Snippets::FileInputType do
+RSpec.describe Types::Snippets::FileInputType do
specify { expect(described_class.graphql_name).to eq('SnippetFileInputType') }
it 'has the correct arguments' do
diff --git a/spec/graphql/types/time_type_spec.rb b/spec/graphql/types/time_type_spec.rb
index 3c6e191e2fb..68d346766c2 100644
--- a/spec/graphql/types/time_type_spec.rb
+++ b/spec/graphql/types/time_type_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GitlabSchema.types['Time'] do
+RSpec.describe GitlabSchema.types['Time'] do
let(:iso) { "2018-06-04T15:23:50+02:00" }
let(:time) { Time.parse(iso) }
diff --git a/spec/graphql/types/todo_type_spec.rb b/spec/graphql/types/todo_type_spec.rb
index 87a5405f0e2..15b6195ec5c 100644
--- a/spec/graphql/types/todo_type_spec.rb
+++ b/spec/graphql/types/todo_type_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GitlabSchema.types['Todo'] do
+RSpec.describe GitlabSchema.types['Todo'] do
it 'has the correct fields' do
expected_fields = [:id, :project, :group, :author, :action, :target_type, :body, :state, :created_at]
diff --git a/spec/graphql/types/tree/blob_type_spec.rb b/spec/graphql/types/tree/blob_type_spec.rb
index 547a03b5edf..73d61d4860c 100644
--- a/spec/graphql/types/tree/blob_type_spec.rb
+++ b/spec/graphql/types/tree/blob_type_spec.rb
@@ -2,8 +2,8 @@
require 'spec_helper'
-describe Types::Tree::BlobType do
+RSpec.describe Types::Tree::BlobType do
specify { expect(described_class.graphql_name).to eq('Blob') }
- specify { expect(described_class).to have_graphql_fields(:id, :sha, :name, :type, :path, :flat_path, :web_url, :lfs_oid) }
+ specify { expect(described_class).to have_graphql_fields(:id, :sha, :name, :type, :path, :flat_path, :web_url, :lfs_oid, :mode) }
end
diff --git a/spec/graphql/types/tree/submodule_type_spec.rb b/spec/graphql/types/tree/submodule_type_spec.rb
index b5cfe8eb812..ba2b06f1907 100644
--- a/spec/graphql/types/tree/submodule_type_spec.rb
+++ b/spec/graphql/types/tree/submodule_type_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Types::Tree::SubmoduleType do
+RSpec.describe Types::Tree::SubmoduleType do
specify { expect(described_class.graphql_name).to eq('Submodule') }
specify { expect(described_class).to have_graphql_fields(:id, :sha, :name, :type, :path, :flat_path, :web_url, :tree_url) }
diff --git a/spec/graphql/types/tree/tree_entry_type_spec.rb b/spec/graphql/types/tree/tree_entry_type_spec.rb
index 14826d06645..0e5caf66854 100644
--- a/spec/graphql/types/tree/tree_entry_type_spec.rb
+++ b/spec/graphql/types/tree/tree_entry_type_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Types::Tree::TreeEntryType do
+RSpec.describe Types::Tree::TreeEntryType do
specify { expect(described_class.graphql_name).to eq('TreeEntry') }
specify { expect(described_class).to have_graphql_fields(:id, :sha, :name, :type, :path, :flat_path, :web_url) }
diff --git a/spec/graphql/types/tree/tree_type_spec.rb b/spec/graphql/types/tree/tree_type_spec.rb
index 93faebd3602..362ecdfca91 100644
--- a/spec/graphql/types/tree/tree_type_spec.rb
+++ b/spec/graphql/types/tree/tree_type_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Types::Tree::TreeType do
+RSpec.describe Types::Tree::TreeType do
specify { expect(described_class.graphql_name).to eq('Tree') }
specify { expect(described_class).to have_graphql_fields(:trees, :submodules, :blobs, :last_commit) }
diff --git a/spec/graphql/types/tree/type_enum_spec.rb b/spec/graphql/types/tree/type_enum_spec.rb
index dcacd6073f9..f751bcf2a13 100644
--- a/spec/graphql/types/tree/type_enum_spec.rb
+++ b/spec/graphql/types/tree/type_enum_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Types::Tree::TypeEnum do
+RSpec.describe Types::Tree::TypeEnum do
specify { expect(described_class.graphql_name).to eq('EntryType') }
it 'exposes all tree entry types' do
diff --git a/spec/graphql/types/untrusted_regexp_spec.rb b/spec/graphql/types/untrusted_regexp_spec.rb
new file mode 100644
index 00000000000..c801c62fbdc
--- /dev/null
+++ b/spec/graphql/types/untrusted_regexp_spec.rb
@@ -0,0 +1,52 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe GitlabSchema.types['UntrustedRegexp'] do
+ using RSpec::Parameterized::TableSyntax
+
+ specify { expect(described_class.graphql_name).to eq('UntrustedRegexp') }
+
+ specify { expect(described_class.description).to eq('A regexp containing patterns sourced from user input') }
+
+ describe '.coerce_input' do
+ subject { described_class.coerce_input(input, nil) }
+
+ where(:input, :expected_result) do
+ '.*' | '.*'
+ '(.*)' | '(.*)'
+ '[test*]+' | '[test*]+'
+ '*v1' | :raise_error
+ '[test*' | :raise_error
+ 'test*+' | :raise_error
+ end
+
+ with_them do
+ context "with input #{params[:input]}" do
+ if params[:expected_result] == :raise_error
+ it 'raises a coercion error' do
+ expect { subject }.to raise_error(GraphQL::CoercionError, /#{Regexp.quote(input)} is an invalid regexp/)
+ end
+ else
+ it { expect(subject).to eq(expected_result) }
+ end
+ end
+ end
+ end
+
+ describe '.coerce_result' do
+ subject { described_class.coerce_result(input, nil) }
+
+ where(:input, :expected_result) do
+ '1' | '1'
+ 1 | '1'
+ true | 'true'
+ end
+
+ with_them do
+ context "with input #{params[:input]}" do
+ it { expect(subject).to eq(expected_result) }
+ end
+ end
+ end
+end
diff --git a/spec/graphql/types/user_type_spec.rb b/spec/graphql/types/user_type_spec.rb
index 7b34588b0ff..6cc3f7bcaa1 100644
--- a/spec/graphql/types/user_type_spec.rb
+++ b/spec/graphql/types/user_type_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GitlabSchema.types['User'] do
+RSpec.describe GitlabSchema.types['User'] do
specify { expect(described_class.graphql_name).to eq('User') }
specify { expect(described_class).to require_graphql_authorizations(:read_user) }
diff --git a/spec/haml_lint/linter/documentation_links_spec.rb b/spec/haml_lint/linter/documentation_links_spec.rb
new file mode 100644
index 00000000000..68de8317b82
--- /dev/null
+++ b/spec/haml_lint/linter/documentation_links_spec.rb
@@ -0,0 +1,82 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require 'haml_lint'
+require 'haml_lint/spec'
+require Rails.root.join('haml_lint/linter/documentation_links')
+
+RSpec.describe HamlLint::Linter::DocumentationLinks do
+ include_context 'linter'
+
+ context 'when link_to points to the existing file path' do
+ let(:haml) { "= link_to 'Description', help_page_path('README.md')" }
+
+ it { is_expected.not_to report_lint }
+ end
+
+ context 'when link_to points to the existing file with valid anchor' do
+ let(:haml) { "= link_to 'Description', help_page_path('README.md', anchor: 'overview'), target: '_blank'" }
+
+ it { is_expected.not_to report_lint }
+ end
+
+ context 'when link_to points to the existing file path without .md extension' do
+ let(:haml) { "= link_to 'Description', help_page_path('README')" }
+
+ it { is_expected.not_to report_lint }
+ end
+
+ context 'when anchor is not correct' do
+ let(:haml) { "= link_to 'Description', help_page_path('README.md', anchor: 'wrong')" }
+
+ it { is_expected.to report_lint }
+
+ context 'when help_page_path has multiple options' do
+ let(:haml) { "= link_to 'Description', help_page_path('README.md', key: :value, anchor: 'wrong')" }
+
+ it { is_expected.to report_lint }
+ end
+ end
+
+ context 'when file path is wrong' do
+ let(:haml) { "= link_to 'Description', help_page_path('wrong.md'), target: '_blank'" }
+
+ it { is_expected.to report_lint }
+ end
+
+ context 'when link with wrong file path is assigned to a variable' do
+ let(:haml) { "- my_link = link_to 'Description', help_page_path('wrong.md')" }
+
+ it { is_expected.to report_lint }
+ end
+
+ context 'when it is a broken code' do
+ let(:haml) { "= I am broken! ]]]]" }
+
+ it { is_expected.not_to report_lint }
+ end
+
+ context 'when anchor belongs to a different element' do
+ let(:haml) { "= link_to 'Description', help_page_path('README.md'), target: (anchor: 'blank')" }
+
+ it { is_expected.not_to report_lint }
+ end
+
+ context 'when a simple help_page_path' do
+ let(:haml) { "- url = help_page_path('wrong.md')" }
+
+ it { is_expected.to report_lint }
+ end
+
+ context 'when link is not a string' do
+ let(:haml) { "- url = help_page_path(help_url)" }
+
+ it { is_expected.not_to report_lint }
+ end
+
+ context 'when link is a part of the tag' do
+ let(:haml) { ".data-form{ data: { url: help_page_path('wrong.md') } }" }
+
+ it { is_expected.to report_lint }
+ end
+end
diff --git a/spec/haml_lint/linter/no_plain_nodes_spec.rb b/spec/haml_lint/linter/no_plain_nodes_spec.rb
index dc647467db6..08f7e6131cc 100644
--- a/spec/haml_lint/linter/no_plain_nodes_spec.rb
+++ b/spec/haml_lint/linter/no_plain_nodes_spec.rb
@@ -5,7 +5,7 @@ require 'haml_lint'
require 'haml_lint/spec'
require Rails.root.join('haml_lint/linter/no_plain_nodes')
-describe HamlLint::Linter::NoPlainNodes do
+RSpec.describe HamlLint::Linter::NoPlainNodes do
include_context 'linter'
context 'reports when a tag has an inline plain node' do
diff --git a/spec/helpers/access_tokens_helper_spec.rb b/spec/helpers/access_tokens_helper_spec.rb
index 1d246d3f236..28041203447 100644
--- a/spec/helpers/access_tokens_helper_spec.rb
+++ b/spec/helpers/access_tokens_helper_spec.rb
@@ -2,7 +2,7 @@
require "spec_helper"
-describe AccessTokensHelper do
+RSpec.describe AccessTokensHelper do
describe "#scope_description" do
using RSpec::Parameterized::TableSyntax
diff --git a/spec/helpers/analytics/unique_visits_helper_spec.rb b/spec/helpers/analytics/unique_visits_helper_spec.rb
new file mode 100644
index 00000000000..ff9769078c4
--- /dev/null
+++ b/spec/helpers/analytics/unique_visits_helper_spec.rb
@@ -0,0 +1,56 @@
+# frozen_string_literal: true
+
+require "spec_helper"
+
+RSpec.describe Analytics::UniqueVisitsHelper do
+ include Devise::Test::ControllerHelpers
+
+ describe '#track_visit' do
+ let(:target_id) { 'p_analytics_valuestream' }
+ let(:current_user) { create(:user) }
+
+ before do
+ stub_feature_flags(track_unique_visits: true)
+ end
+
+ it 'does not track visits if feature flag disabled' do
+ stub_feature_flags(track_unique_visits: false)
+ sign_in(current_user)
+
+ expect_any_instance_of(Gitlab::Analytics::UniqueVisits).not_to receive(:track_visit)
+
+ helper.track_visit(target_id)
+ end
+
+ it 'does not track visits if usage ping is disabled' do
+ sign_in(current_user)
+ expect(Gitlab::CurrentSettings).to receive(:usage_ping_enabled?).and_return(false)
+
+ expect_any_instance_of(Gitlab::Analytics::UniqueVisits).not_to receive(:track_visit)
+
+ helper.track_visit(target_id)
+ end
+
+ it 'does not track visit if user is not logged in' do
+ expect_any_instance_of(Gitlab::Analytics::UniqueVisits).not_to receive(:track_visit)
+
+ helper.track_visit(target_id)
+ end
+
+ it 'tracks visit if user is logged in' do
+ sign_in(current_user)
+
+ expect_any_instance_of(Gitlab::Analytics::UniqueVisits).to receive(:track_visit)
+
+ helper.track_visit(target_id)
+ end
+
+ it 'tracks visit if user is not logged in, but has the cookie already' do
+ helper.request.cookies[:visitor_id] = { value: SecureRandom.uuid, expires: 24.months }
+
+ expect_any_instance_of(Gitlab::Analytics::UniqueVisits).to receive(:track_visit)
+
+ helper.track_visit(target_id)
+ end
+ end
+end
diff --git a/spec/helpers/appearances_helper_spec.rb b/spec/helpers/appearances_helper_spec.rb
index ed3e31b3c53..179c69b2a67 100644
--- a/spec/helpers/appearances_helper_spec.rb
+++ b/spec/helpers/appearances_helper_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe AppearancesHelper do
+RSpec.describe AppearancesHelper do
before do
user = create(:user)
allow(helper).to receive(:current_user).and_return(user)
diff --git a/spec/helpers/application_helper_spec.rb b/spec/helpers/application_helper_spec.rb
index 75377356445..08107b841d7 100644
--- a/spec/helpers/application_helper_spec.rb
+++ b/spec/helpers/application_helper_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ApplicationHelper do
+RSpec.describe ApplicationHelper do
describe 'current_controller?' do
before do
stub_controller_name('foo')
@@ -209,6 +209,16 @@ describe ApplicationHelper do
end
end
+ describe '#page_startup_api_calls' do
+ it 'returns map containing JS Page Startup Calls' do
+ helper.add_page_startup_api_call("testURL")
+
+ startup_calls = helper.page_startup_api_calls
+
+ expect(startup_calls["testURL"]).to eq({})
+ end
+ end
+
describe '#autocomplete_data_sources' do
let(:project) { create(:project) }
let(:noteable_type) { Issue }
diff --git a/spec/helpers/application_settings_helper_spec.rb b/spec/helpers/application_settings_helper_spec.rb
index 3fb754f1090..c5fd88ada8f 100644
--- a/spec/helpers/application_settings_helper_spec.rb
+++ b/spec/helpers/application_settings_helper_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ApplicationSettingsHelper do
+RSpec.describe ApplicationSettingsHelper do
context 'when all protocols in use' do
before do
stub_application_setting(enabled_git_access_protocol: '')
diff --git a/spec/helpers/auth_helper_spec.rb b/spec/helpers/auth_helper_spec.rb
index 23f3449d9a7..1e843ee221b 100644
--- a/spec/helpers/auth_helper_spec.rb
+++ b/spec/helpers/auth_helper_spec.rb
@@ -2,7 +2,7 @@
require "spec_helper"
-describe AuthHelper do
+RSpec.describe AuthHelper do
describe "button_based_providers" do
it 'returns all enabled providers from devise' do
allow(helper).to receive(:auth_providers) { [:twitter, :github] }
diff --git a/spec/helpers/auto_devops_helper_spec.rb b/spec/helpers/auto_devops_helper_spec.rb
index e0fecb0c159..ad705dc5a7b 100644
--- a/spec/helpers/auto_devops_helper_spec.rb
+++ b/spec/helpers/auto_devops_helper_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe AutoDevopsHelper do
+RSpec.describe AutoDevopsHelper do
let_it_be(:project, reload: true) { create(:project) }
let_it_be(:user) { create(:user) }
@@ -93,6 +93,12 @@ describe AutoDevopsHelper do
end
end
+ describe '#auto_devops_settings_path' do
+ it 'returns auto devops settings path' do
+ expect(helper.auto_devops_settings_path(project)).to eql(project_settings_ci_cd_path(project, anchor: 'autodevops-settings'))
+ end
+ end
+
describe '#badge_for_auto_devops_scope' do
subject { helper.badge_for_auto_devops_scope(receiver) }
diff --git a/spec/helpers/avatars_helper_spec.rb b/spec/helpers/avatars_helper_spec.rb
index 2a030742cb9..9e18ab34c1f 100644
--- a/spec/helpers/avatars_helper_spec.rb
+++ b/spec/helpers/avatars_helper_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe AvatarsHelper do
+RSpec.describe AvatarsHelper do
include UploadHelpers
let(:user) { create(:user) }
diff --git a/spec/helpers/award_emoji_helper_spec.rb b/spec/helpers/award_emoji_helper_spec.rb
index 3dee466a80c..51e0a1b9721 100644
--- a/spec/helpers/award_emoji_helper_spec.rb
+++ b/spec/helpers/award_emoji_helper_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe AwardEmojiHelper do
+RSpec.describe AwardEmojiHelper do
describe '.toggle_award_url' do
subject { helper.toggle_award_url(awardable) }
diff --git a/spec/helpers/blame_helper_spec.rb b/spec/helpers/blame_helper_spec.rb
index 8b5de040508..6371c2b63ce 100644
--- a/spec/helpers/blame_helper_spec.rb
+++ b/spec/helpers/blame_helper_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe BlameHelper do
+RSpec.describe BlameHelper do
describe '#get_age_map_start_date' do
let(:dates) do
[Time.zone.local(2014, 3, 17, 0, 0, 0),
diff --git a/spec/helpers/blob_helper_spec.rb b/spec/helpers/blob_helper_spec.rb
index 2631c219222..fe975aa7723 100644
--- a/spec/helpers/blob_helper_spec.rb
+++ b/spec/helpers/blob_helper_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe BlobHelper do
+RSpec.describe BlobHelper do
include TreeHelper
describe '#highlight' do
@@ -53,20 +53,12 @@ describe BlobHelper do
end
it 'returns a link with the proper route' do
- stub_feature_flags(web_ide_default: false)
link = helper.edit_blob_button(project, 'master', 'README.md')
expect(Capybara.string(link).find_link('Edit')[:href]).to eq("/#{project.full_path}/-/edit/master/README.md")
end
- it 'returns a link with a Web IDE route' do
- link = helper.edit_blob_button(project, 'master', 'README.md')
-
- expect(Capybara.string(link).find_link('Edit')[:href]).to eq("/-/ide/project/#{project.full_path}/edit/master/-/README.md")
- end
-
it 'returns a link with the passed link_opts on the expected route' do
- stub_feature_flags(web_ide_default: false)
link = helper.edit_blob_button(project, 'master', 'README.md', link_opts: { mr_id: 10 })
expect(Capybara.string(link).find_link('Edit')[:href]).to eq("/#{project.full_path}/-/edit/master/README.md?mr_id=10")
diff --git a/spec/helpers/boards_helper_spec.rb b/spec/helpers/boards_helper_spec.rb
index cb9be9d5fb4..a805b96a8cc 100644
--- a/spec/helpers/boards_helper_spec.rb
+++ b/spec/helpers/boards_helper_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe BoardsHelper do
+RSpec.describe BoardsHelper do
let_it_be(:project) { create(:project) }
describe '#build_issue_link_base' do
diff --git a/spec/helpers/broadcast_messages_helper_spec.rb b/spec/helpers/broadcast_messages_helper_spec.rb
index 58cc03a9446..21fde35954e 100644
--- a/spec/helpers/broadcast_messages_helper_spec.rb
+++ b/spec/helpers/broadcast_messages_helper_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe BroadcastMessagesHelper do
+RSpec.describe BroadcastMessagesHelper do
describe 'current_broadcast_notification_message' do
subject { helper.current_broadcast_notification_message }
diff --git a/spec/helpers/button_helper_spec.rb b/spec/helpers/button_helper_spec.rb
index cf8887f9731..6a5cb73281e 100644
--- a/spec/helpers/button_helper_spec.rb
+++ b/spec/helpers/button_helper_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ButtonHelper do
+RSpec.describe ButtonHelper do
describe 'http_clone_button' do
let(:user) { create(:user) }
let(:project) { build_stubbed(:project) }
diff --git a/spec/helpers/calendar_helper_spec.rb b/spec/helpers/calendar_helper_spec.rb
index 8dba6815e8d..ceed4191ef4 100644
--- a/spec/helpers/calendar_helper_spec.rb
+++ b/spec/helpers/calendar_helper_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe CalendarHelper do
+RSpec.describe CalendarHelper do
describe '#calendar_url_options' do
context 'when signed in' do
it "includes the current_user's feed_token" do
diff --git a/spec/helpers/ci/builds_helper_spec.rb b/spec/helpers/ci/builds_helper_spec.rb
new file mode 100644
index 00000000000..143d96cf632
--- /dev/null
+++ b/spec/helpers/ci/builds_helper_spec.rb
@@ -0,0 +1,111 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Ci::BuildsHelper do
+ describe '#build_summary' do
+ subject { helper.build_summary(build, skip: skip) }
+
+ context 'when build has no trace' do
+ let(:build) { instance_double(Ci::Build, has_trace?: false) }
+
+ context 'when skip is false' do
+ let(:skip) { false }
+
+ it 'returns no job log' do
+ expect(subject).to eq('No job log')
+ end
+ end
+
+ context 'when skip is true' do
+ let(:skip) { true }
+
+ it 'returns no job log' do
+ expect(subject).to eq('No job log')
+ end
+ end
+ end
+
+ context 'when build has trace' do
+ let(:build) { create(:ci_build, :trace_live) }
+
+ context 'when skip is true' do
+ let(:skip) { true }
+
+ it 'returns link to logs' do
+ expect(subject).to include('View job log')
+ expect(subject).to include(pipeline_job_url(build.pipeline, build))
+ end
+ end
+
+ context 'when skip is false' do
+ let(:skip) { false }
+
+ it 'returns log lines' do
+ expect(subject).to include(build.trace.html(last_lines: 10).html_safe)
+ end
+ end
+ end
+ end
+
+ describe '#sidebar_build_class' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:build_id, :current_build_id, :retried, :expected_result) do
+ 1 | 1 | true | 'active retried'
+ 1 | 1 | false | 'active'
+ 1 | 2 | false | ''
+ 1 | 2 | true | 'retried'
+ end
+
+ let(:build) { instance_double(Ci::Build, retried?: retried, id: build_id) }
+ let(:current_build) { instance_double(Ci::Build, retried?: true, id: current_build_id ) }
+
+ subject { helper.sidebar_build_class(build, current_build) }
+
+ with_them do
+ it 'builds sidebar html class' do
+ expect(subject).to eq(expected_result)
+ end
+ end
+ end
+
+ describe '#javascript_build_options' do
+ subject { helper.javascript_build_options }
+
+ it 'returns build options' do
+ project = assign_project
+ ci_build = assign_build
+
+ expect(subject).to eq({
+ page_path: project_job_path(project, ci_build),
+ build_status: ci_build.status,
+ build_stage: ci_build.stage,
+ log_state: ''
+ })
+ end
+ end
+
+ describe '#build_failed_issue_options' do
+ subject { helper.build_failed_issue_options }
+
+ it 'returns failed title and description' do
+ project = assign_project
+ ci_build = assign_build
+
+ expect(subject).to eq(title: "Job Failed \##{ci_build.id}", description: project_job_url(project, ci_build))
+ end
+ end
+
+ def assign_project
+ build(:project).tap do |project|
+ assign(:project, project)
+ end
+ end
+
+ def assign_build
+ create(:ci_build).tap do |ci_build|
+ assign(:build, ci_build)
+ end
+ end
+end
diff --git a/spec/helpers/ci/pipeline_schedules_helper_spec.rb b/spec/helpers/ci/pipeline_schedules_helper_spec.rb
new file mode 100644
index 00000000000..2a81c2a44a0
--- /dev/null
+++ b/spec/helpers/ci/pipeline_schedules_helper_spec.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Ci::PipelineSchedulesHelper, :aggregate_failures do
+ describe '#timezone_data' do
+ subject { helper.timezone_data }
+
+ it 'matches schema' do
+ expect(subject).not_to be_empty
+ subject.each_with_index do |timzone_hash, i|
+ expect(timzone_hash.keys).to contain_exactly(:name, :offset, :identifier), "Failed at index #{i}"
+ end
+ end
+
+ it 'formats for display' do
+ first_timezone = ActiveSupport::TimeZone.all[0]
+
+ expect(subject[0][:name]).to eq(first_timezone.name)
+ expect(subject[0][:offset]).to eq(first_timezone.now.utc_offset)
+ expect(subject[0][:identifier]).to eq(first_timezone.tzinfo.identifier)
+ end
+ end
+end
diff --git a/spec/helpers/ci/runners_helper_spec.rb b/spec/helpers/ci/runners_helper_spec.rb
new file mode 100644
index 00000000000..a006933a2a5
--- /dev/null
+++ b/spec/helpers/ci/runners_helper_spec.rb
@@ -0,0 +1,56 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Ci::RunnersHelper do
+ it "returns - not contacted yet" do
+ runner = FactoryBot.build :ci_runner
+ expect(runner_status_icon(runner)).to include("not connected yet")
+ end
+
+ it "returns offline text" do
+ runner = FactoryBot.build(:ci_runner, contacted_at: 1.day.ago, active: true)
+ expect(runner_status_icon(runner)).to include("Runner is offline")
+ end
+
+ it "returns online text" do
+ runner = FactoryBot.build(:ci_runner, contacted_at: 1.second.ago, active: true)
+ expect(runner_status_icon(runner)).to include("Runner is online")
+ end
+
+ describe '#runner_contacted_at' do
+ let(:contacted_at_stored) { 1.hour.ago.change(usec: 0) }
+ let(:contacted_at_cached) { 1.second.ago.change(usec: 0) }
+ let(:runner) { create(:ci_runner, contacted_at: contacted_at_stored) }
+
+ before do
+ runner.cache_attributes(contacted_at: contacted_at_cached)
+ end
+
+ context 'without sorting' do
+ it 'returns cached value' do
+ expect(runner_contacted_at(runner)).to eq(contacted_at_cached)
+ end
+ end
+
+ context 'with sorting set to created_date' do
+ before do
+ controller.params[:sort] = 'created_date'
+ end
+
+ it 'returns cached value' do
+ expect(runner_contacted_at(runner)).to eq(contacted_at_cached)
+ end
+ end
+
+ context 'with sorting set to contacted_asc' do
+ before do
+ controller.params[:sort] = 'contacted_asc'
+ end
+
+ it 'returns stored value' do
+ expect(runner_contacted_at(runner)).to eq(contacted_at_stored)
+ end
+ end
+ end
+end
diff --git a/spec/helpers/ci/status_helper_spec.rb b/spec/helpers/ci/status_helper_spec.rb
new file mode 100644
index 00000000000..12a6acb1ecc
--- /dev/null
+++ b/spec/helpers/ci/status_helper_spec.rb
@@ -0,0 +1,134 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Ci::StatusHelper do
+ include IconsHelper
+
+ let(:success_commit) { double("Ci::Pipeline", status: 'success') }
+ let(:failed_commit) { double("Ci::Pipeline", status: 'failed') }
+
+ describe '#ci_icon_for_status' do
+ it 'renders to correct svg on success' do
+ expect(helper.ci_icon_for_status('success').to_s)
+ .to include 'status_success'
+ end
+
+ it 'renders the correct svg on failure' do
+ expect(helper.ci_icon_for_status('failed').to_s)
+ .to include 'status_failed'
+ end
+ end
+
+ describe '#ci_text_for_status' do
+ context 'when status is manual' do
+ it 'changes the status to blocked' do
+ expect(helper.ci_text_for_status('manual'))
+ .to eq 'blocked'
+ end
+ end
+
+ context 'when status is success' do
+ it 'changes the status to passed' do
+ expect(helper.ci_text_for_status('success'))
+ .to eq 'passed'
+ end
+ end
+
+ context 'when status is something else' do
+ it 'returns status unchanged' do
+ expect(helper.ci_text_for_status('some-status'))
+ .to eq 'some-status'
+ end
+ end
+ end
+
+ describe "#pipeline_status_cache_key" do
+ it "builds a cache key for pipeline status" do
+ pipeline_status = Gitlab::Cache::Ci::ProjectPipelineStatus.new(
+ build_stubbed(:project),
+ pipeline_info: {
+ sha: "123abc",
+ status: "success"
+ }
+ )
+ expect(helper.pipeline_status_cache_key(pipeline_status)).to eq("pipeline-status/123abc-success")
+ end
+ end
+
+ describe "#render_status_with_link" do
+ subject { helper.render_status_with_link("success") }
+
+ it "renders a passed status icon" do
+ is_expected.to include("<span class=\"ci-status-link ci-status-icon-success d-inline-flex")
+ end
+
+ it "has 'Pipeline' as the status type in the title" do
+ is_expected.to include("title=\"Pipeline: passed\"")
+ end
+
+ it "has the success status icon" do
+ is_expected.to include("ci-status-icon-success")
+ end
+
+ context "when pipeline has commit path" do
+ subject { helper.render_status_with_link("success", "/commit-path") }
+
+ it "links to commit" do
+ is_expected.to include("href=\"/commit-path\"")
+ end
+
+ it "does not contain a span element" do
+ is_expected.not_to include("<span")
+ end
+
+ it "has 'Pipeline' as the status type in the title" do
+ is_expected.to include("title=\"Pipeline: passed\"")
+ end
+
+ it "has the correct status icon" do
+ is_expected.to include("ci-status-icon-success")
+ end
+ end
+
+ context "when different type than pipeline is provided" do
+ subject { helper.render_status_with_link("success", type: "commit") }
+
+ it "has the provided type in the title" do
+ is_expected.to include("title=\"Commit: passed\"")
+ end
+ end
+
+ context "when tooltip_placement is provided" do
+ subject { helper.render_status_with_link("success", tooltip_placement: "right") }
+
+ it "has the provided tooltip placement" do
+ is_expected.to include("data-placement=\"right\"")
+ end
+ end
+
+ context "when additional CSS classes are provided" do
+ subject { helper.render_status_with_link("success", cssclass: "extra-class") }
+
+ it "has appended extra class to icon classes" do
+ is_expected.to include("class=\"ci-status-link ci-status-icon-success d-inline-flex extra-class\"")
+ end
+ end
+
+ context "when container is provided" do
+ subject { helper.render_status_with_link("success", container: "my-container") }
+
+ it "has the provided container in data" do
+ is_expected.to include("data-container=\"my-container\"")
+ end
+ end
+
+ context "when icon_size is provided" do
+ subject { helper.render_status_with_link("success", icon_size: 24) }
+
+ it "has the svg class to change size" do
+ is_expected.to include("<svg class=\"s24\">")
+ end
+ end
+ end
+end
diff --git a/spec/helpers/ci_status_helper_spec.rb b/spec/helpers/ci_status_helper_spec.rb
deleted file mode 100644
index 80be119b069..00000000000
--- a/spec/helpers/ci_status_helper_spec.rb
+++ /dev/null
@@ -1,134 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-describe CiStatusHelper do
- include IconsHelper
-
- let(:success_commit) { double("Ci::Pipeline", status: 'success') }
- let(:failed_commit) { double("Ci::Pipeline", status: 'failed') }
-
- describe '#ci_icon_for_status' do
- it 'renders to correct svg on success' do
- expect(helper.ci_icon_for_status('success').to_s)
- .to include 'status_success'
- end
-
- it 'renders the correct svg on failure' do
- expect(helper.ci_icon_for_status('failed').to_s)
- .to include 'status_failed'
- end
- end
-
- describe '#ci_text_for_status' do
- context 'when status is manual' do
- it 'changes the status to blocked' do
- expect(helper.ci_text_for_status('manual'))
- .to eq 'blocked'
- end
- end
-
- context 'when status is success' do
- it 'changes the status to passed' do
- expect(helper.ci_text_for_status('success'))
- .to eq 'passed'
- end
- end
-
- context 'when status is something else' do
- it 'returns status unchanged' do
- expect(helper.ci_text_for_status('some-status'))
- .to eq 'some-status'
- end
- end
- end
-
- describe "#pipeline_status_cache_key" do
- it "builds a cache key for pipeline status" do
- pipeline_status = Gitlab::Cache::Ci::ProjectPipelineStatus.new(
- build_stubbed(:project),
- pipeline_info: {
- sha: "123abc",
- status: "success"
- }
- )
- expect(helper.pipeline_status_cache_key(pipeline_status)).to eq("pipeline-status/123abc-success")
- end
- end
-
- describe "#render_status_with_link" do
- subject { helper.render_status_with_link("success") }
-
- it "renders a passed status icon" do
- is_expected.to include("<span class=\"ci-status-link ci-status-icon-success d-inline-flex")
- end
-
- it "has 'Pipeline' as the status type in the title" do
- is_expected.to include("title=\"Pipeline: passed\"")
- end
-
- it "has the success status icon" do
- is_expected.to include("ci-status-icon-success")
- end
-
- context "when pipeline has commit path" do
- subject { helper.render_status_with_link("success", "/commit-path") }
-
- it "links to commit" do
- is_expected.to include("href=\"/commit-path\"")
- end
-
- it "does not contain a span element" do
- is_expected.not_to include("<span")
- end
-
- it "has 'Pipeline' as the status type in the title" do
- is_expected.to include("title=\"Pipeline: passed\"")
- end
-
- it "has the correct status icon" do
- is_expected.to include("ci-status-icon-success")
- end
- end
-
- context "when different type than pipeline is provided" do
- subject { helper.render_status_with_link("success", type: "commit") }
-
- it "has the provided type in the title" do
- is_expected.to include("title=\"Commit: passed\"")
- end
- end
-
- context "when tooltip_placement is provided" do
- subject { helper.render_status_with_link("success", tooltip_placement: "right") }
-
- it "has the provided tooltip placement" do
- is_expected.to include("data-placement=\"right\"")
- end
- end
-
- context "when additional CSS classes are provided" do
- subject { helper.render_status_with_link("success", cssclass: "extra-class") }
-
- it "has appended extra class to icon classes" do
- is_expected.to include("class=\"ci-status-link ci-status-icon-success d-inline-flex extra-class\"")
- end
- end
-
- context "when container is provided" do
- subject { helper.render_status_with_link("success", container: "my-container") }
-
- it "has the provided container in data" do
- is_expected.to include("data-container=\"my-container\"")
- end
- end
-
- context "when icon_size is provided" do
- subject { helper.render_status_with_link("success", icon_size: 24) }
-
- it "has the svg class to change size" do
- is_expected.to include("<svg class=\"s24\">")
- end
- end
- end
-end
diff --git a/spec/helpers/clusters_helper_spec.rb b/spec/helpers/clusters_helper_spec.rb
index c41d4f0ede7..dff83005c89 100644
--- a/spec/helpers/clusters_helper_spec.rb
+++ b/spec/helpers/clusters_helper_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ClustersHelper do
+RSpec.describe ClustersHelper do
describe '#has_rbac_enabled?' do
context 'when kubernetes platform has been created' do
let(:platform_kubernetes) { build_stubbed(:cluster_platform_kubernetes) }
@@ -60,18 +60,24 @@ describe ClustersHelper do
end
describe '#js_clusters_list_data' do
- it 'displays endpoint path and images' do
- js_data = helper.js_clusters_list_data('/path')
+ subject { helper.js_clusters_list_data('/path') }
- expect(js_data[:endpoint]).to eq('/path')
+ it 'displays endpoint path' do
+ expect(subject[:endpoint]).to eq('/path')
+ end
+
+ it 'generates svg image data', :aggregate_failures do
+ expect(subject.dig(:img_tags, :aws, :path)).to match(%r(/illustrations/logos/amazon_eks|svg))
+ expect(subject.dig(:img_tags, :default, :path)).to match(%r(/illustrations/logos/kubernetes|svg))
+ expect(subject.dig(:img_tags, :gcp, :path)).to match(%r(/illustrations/logos/google_gke|svg))
- expect(js_data.dig(:img_tags, :aws, :path)).to match(%r(/illustrations/logos/amazon_eks|svg))
- expect(js_data.dig(:img_tags, :default, :path)).to match(%r(/illustrations/logos/kubernetes|svg))
- expect(js_data.dig(:img_tags, :gcp, :path)).to match(%r(/illustrations/logos/google_gke|svg))
+ expect(subject.dig(:img_tags, :aws, :text)).to eq('Amazon EKS')
+ expect(subject.dig(:img_tags, :default, :text)).to eq('Kubernetes Cluster')
+ expect(subject.dig(:img_tags, :gcp, :text)).to eq('Google GKE')
+ end
- expect(js_data.dig(:img_tags, :aws, :text)).to eq('Amazon EKS')
- expect(js_data.dig(:img_tags, :default, :text)).to eq('Kubernetes Cluster')
- expect(js_data.dig(:img_tags, :gcp, :text)).to eq('Google GKE')
+ it 'displays and ancestor_help_path' do
+ expect(subject[:ancestor_help_path]).to eq('/help/user/group/clusters/index#cluster-precedence')
end
end
diff --git a/spec/helpers/commits_helper_spec.rb b/spec/helpers/commits_helper_spec.rb
index e036e97f745..cc318a21fc9 100644
--- a/spec/helpers/commits_helper_spec.rb
+++ b/spec/helpers/commits_helper_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe CommitsHelper do
+RSpec.describe CommitsHelper do
describe 'commit_author_link' do
it 'escapes the author email' do
commit = double(
@@ -51,6 +51,20 @@ describe CommitsHelper do
end
end
+ describe '#view_file_button' do
+ let(:project) { build(:project) }
+ let(:path) { 'path/to/file' }
+ let(:sha) { '1234567890' }
+
+ subject do
+ helper.view_file_button(sha, path, project)
+ end
+
+ it 'links to project files' do
+ expect(subject).to have_link('1234567', href: helper.project_blob_path(project, "#{sha}/#{path}"))
+ end
+ end
+
describe '#view_on_environment_button' do
let(:project) { create(:project) }
let(:environment) { create(:environment, external_url: 'http://example.com') }
diff --git a/spec/helpers/components_helper_spec.rb b/spec/helpers/components_helper_spec.rb
index 703bee0ca92..4fa6d27993f 100644
--- a/spec/helpers/components_helper_spec.rb
+++ b/spec/helpers/components_helper_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ComponentsHelper do
+RSpec.describe ComponentsHelper do
describe '#gitlab_workhorse_version' do
context 'without a Gitlab-Workhorse header' do
it 'shows the version from Gitlab::Workhorse.version' do
diff --git a/spec/helpers/container_expiration_policies_helper_spec.rb b/spec/helpers/container_expiration_policies_helper_spec.rb
index 6dcbadd89cb..b2a03f8d90f 100644
--- a/spec/helpers/container_expiration_policies_helper_spec.rb
+++ b/spec/helpers/container_expiration_policies_helper_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ContainerExpirationPoliciesHelper do
+RSpec.describe ContainerExpirationPoliciesHelper do
describe '#keep_n_options' do
it 'returns keep_n options formatted for dropdown usage' do
expected_result = [
diff --git a/spec/helpers/cookies_helper_spec.rb b/spec/helpers/cookies_helper_spec.rb
new file mode 100644
index 00000000000..c73e7d64987
--- /dev/null
+++ b/spec/helpers/cookies_helper_spec.rb
@@ -0,0 +1,42 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe CookiesHelper do
+ describe '#set_secure_cookie' do
+ it 'creates an encrypted cookie with expected attributes' do
+ stub_config_setting(https: true)
+ expiration = 1.month.from_now
+ key = :secure_cookie
+ value = 'secure value'
+
+ expect_next_instance_of(ActionDispatch::Cookies::EncryptedKeyRotatingCookieJar) do |instance|
+ expect(instance).to receive(:[]=).with(key, httponly: true, secure: true, expires: expiration, value: value)
+ end
+
+ helper.set_secure_cookie(key, value, httponly: true, expires: expiration, type: CookiesHelper::COOKIE_TYPE_ENCRYPTED)
+ end
+
+ it 'creates a permanent cookie with expected attributes' do
+ key = :permanent_cookie
+ value = 'permanent value'
+
+ expect_next_instance_of(ActionDispatch::Cookies::PermanentCookieJar) do |instance|
+ expect(instance).to receive(:[]=).with(key, httponly: false, secure: false, expires: nil, value: value)
+ end
+
+ helper.set_secure_cookie(key, value, type: CookiesHelper::COOKIE_TYPE_PERMANENT)
+ end
+
+ it 'creates a regular cookie with expected attributes' do
+ key = :regular_cookie
+ value = 'regular value'
+
+ expect_next_instance_of(ActionDispatch::Cookies::CookieJar) do |instance|
+ expect(instance).to receive(:[]=).with(key, httponly: false, secure: false, expires: nil, value: value)
+ end
+
+ helper.set_secure_cookie(key, value)
+ end
+ end
+end
diff --git a/spec/helpers/dashboard_helper_spec.rb b/spec/helpers/dashboard_helper_spec.rb
index 8a4ea33ac7c..65182dcb729 100644
--- a/spec/helpers/dashboard_helper_spec.rb
+++ b/spec/helpers/dashboard_helper_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe DashboardHelper do
+RSpec.describe DashboardHelper do
let(:user) { build(:user) }
before do
diff --git a/spec/helpers/defer_script_tag_helper_spec.rb b/spec/helpers/defer_script_tag_helper_spec.rb
index 440904188ca..14317e353ab 100644
--- a/spec/helpers/defer_script_tag_helper_spec.rb
+++ b/spec/helpers/defer_script_tag_helper_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe DeferScriptTagHelper do
+RSpec.describe DeferScriptTagHelper do
describe 'script tag' do
script_url = 'test.js'
diff --git a/spec/helpers/diff_helper_spec.rb b/spec/helpers/diff_helper_spec.rb
index 63aa41bbad5..ef1f0940074 100644
--- a/spec/helpers/diff_helper_spec.rb
+++ b/spec/helpers/diff_helper_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe DiffHelper do
+RSpec.describe DiffHelper do
include RepoHelpers
let(:project) { create(:project, :repository) }
@@ -303,6 +303,20 @@ describe DiffHelper do
end
end
+ describe '#diff_file_html_data' do
+ let(:project) { build(:project) }
+ let(:path) { 'path/to/file' }
+ let(:sha) { '1234567890' }
+
+ subject do
+ helper.diff_file_html_data(project, path, sha)
+ end
+
+ it 'returns data for project files' do
+ expect(subject).to include(blob_diff_path: helper.project_blob_diff_path(project, "#{sha}/#{path}"))
+ end
+ end
+
describe '#diff_file_path_text' do
it 'returns full path by default' do
expect(diff_file_path_text(diff_file)).to eq(diff_file.new_path)
diff --git a/spec/helpers/emails_helper_spec.rb b/spec/helpers/emails_helper_spec.rb
index 0ff9080ef94..bc5fe05ab52 100644
--- a/spec/helpers/emails_helper_spec.rb
+++ b/spec/helpers/emails_helper_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe EmailsHelper do
+RSpec.describe EmailsHelper do
describe 'closure_reason_text' do
context 'when given a MergeRequest' do
let(:merge_request) { create(:merge_request) }
diff --git a/spec/helpers/emoji_helper_spec.rb b/spec/helpers/emoji_helper_spec.rb
index 1b73e956f7a..15e4ce03960 100644
--- a/spec/helpers/emoji_helper_spec.rb
+++ b/spec/helpers/emoji_helper_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe EmojiHelper do
+RSpec.describe EmojiHelper do
describe '#emoji_icon' do
let(:options) { {} }
let(:emoji_text) { 'rocket' }
diff --git a/spec/helpers/environment_helper_spec.rb b/spec/helpers/environment_helper_spec.rb
index 53953d72b06..8c542ca01f4 100644
--- a/spec/helpers/environment_helper_spec.rb
+++ b/spec/helpers/environment_helper_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe EnvironmentHelper do
+RSpec.describe EnvironmentHelper do
describe '#render_deployment_status' do
context 'when using a manual deployment' do
it 'renders a span tag' do
diff --git a/spec/helpers/environments_helper_spec.rb b/spec/helpers/environments_helper_spec.rb
index 48104dfc5a6..90d6096654e 100644
--- a/spec/helpers/environments_helper_spec.rb
+++ b/spec/helpers/environments_helper_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe EnvironmentsHelper do
+RSpec.describe EnvironmentsHelper do
let_it_be(:user) { create(:user) }
let_it_be(:project, reload: true) { create(:project, :repository) }
let_it_be(:environment) { create(:environment, project: project) }
@@ -23,6 +23,7 @@ describe EnvironmentsHelper do
'metrics-dashboard-base-path' => environment_metrics_path(environment),
'current-environment-name' => environment.name,
'documentation-path' => help_page_path('administration/monitoring/prometheus/index.md'),
+ 'add-dashboard-documentation-path' => help_page_path('user/project/integrations/prometheus.md', anchor: 'adding-a-new-dashboard-to-your-project'),
'empty-getting-started-svg-path' => match_asset_path('/assets/illustrations/monitoring/getting_started.svg'),
'empty-loading-svg-path' => match_asset_path('/assets/illustrations/monitoring/loading.svg'),
'empty-no-data-svg-path' => match_asset_path('/assets/illustrations/monitoring/no_data.svg'),
@@ -41,10 +42,26 @@ describe EnvironmentsHelper do
'custom-metrics-available' => 'true',
'alerts-endpoint' => project_prometheus_alerts_path(project, environment_id: environment.id, format: :json),
'prometheus-alerts-available' => 'true',
- 'custom-dashboard-base-path' => Metrics::Dashboard::CustomDashboardService::DASHBOARD_ROOT
+ 'custom-dashboard-base-path' => Metrics::Dashboard::CustomDashboardService::DASHBOARD_ROOT,
+ 'operations-settings-path' => project_settings_operations_path(project),
+ 'can-access-operations-settings' => 'true'
)
end
+ context 'without admin_operations permission' do
+ before do
+ allow(helper).to receive(:can?)
+ .with(user, :admin_operations, project)
+ .and_return(false)
+ end
+
+ specify do
+ expect(metrics_data).to include(
+ 'can-access-operations-settings' => 'false'
+ )
+ end
+ end
+
context 'without read_prometheus_alerts permission' do
before do
allow(helper).to receive(:can?)
@@ -78,6 +95,30 @@ describe EnvironmentsHelper do
it { is_expected.to include('environment-state' => 'stopped') }
end
+
+ context 'when request is from project scoped metrics path' do
+ let(:request) { double('request', path: path) }
+
+ before do
+ allow(helper).to receive(:request).and_return(request)
+ end
+
+ context '/:namespace/:project/-/metrics' do
+ let(:path) { project_metrics_dashboard_path(project) }
+
+ it 'uses correct path for metrics-dashboard-base-path' do
+ expect(metrics_data['metrics-dashboard-base-path']).to eq(project_metrics_dashboard_path(project))
+ end
+ end
+
+ context '/:namespace/:project/-/metrics/some_custom_dashboard.yml' do
+ let(:path) { "#{project_metrics_dashboard_path(project)}/some_custom_dashboard.yml" }
+
+ it 'uses correct path for metrics-dashboard-base-path' do
+ expect(metrics_data['metrics-dashboard-base-path']).to eq(project_metrics_dashboard_path(project))
+ end
+ end
+ end
end
describe '#custom_metrics_available?' do
@@ -97,4 +138,18 @@ describe EnvironmentsHelper do
expect(subject).to eq(true)
end
end
+
+ describe '#environment_logs_data' do
+ it 'returns logs data' do
+ expected_data = {
+ "environment-name": environment.name,
+ "environments-path": project_environments_path(project, format: :json),
+ "environment-id": environment.id,
+ "cluster-applications-documentation-path" => help_page_path('user/clusters/applications.md', anchor: 'elastic-stack'),
+ "clusters-path": project_clusters_path(project, format: :json)
+ }
+
+ expect(helper.environment_logs_data(project, environment)).to eq(expected_data)
+ end
+ end
end
diff --git a/spec/helpers/events_helper_spec.rb b/spec/helpers/events_helper_spec.rb
index 6f24308757d..4ca31405c1e 100644
--- a/spec/helpers/events_helper_spec.rb
+++ b/spec/helpers/events_helper_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe EventsHelper do
+RSpec.describe EventsHelper do
include Gitlab::Routing
describe '#event_commit_title' do
@@ -174,8 +174,8 @@ describe EventsHelper do
url = helper.event_wiki_page_target_url(event)
title = event.target_title
html = [
- "<span class=\"event-target-type append-right-4\">wiki page</span>",
- "<a title=\"#{title}\" class=\"has-tooltip event-target-link append-right-4\" href=\"#{url}\">",
+ "<span class=\"event-target-type gl-mr-2\">wiki page</span>",
+ "<a title=\"#{title}\" class=\"has-tooltip event-target-link gl-mr-2\" href=\"#{url}\">",
title,
"</a>"
].join
@@ -227,4 +227,101 @@ describe EventsHelper do
end
end
end
+
+ describe '#event_filter_visible' do
+ include DesignManagementTestHelpers
+
+ let_it_be(:project) { create(:project) }
+ let_it_be(:current_user) { create(:user) }
+
+ subject { helper.event_filter_visible(key) }
+
+ before do
+ enable_design_management
+ project.add_reporter(current_user)
+ allow(helper).to receive(:current_user).and_return(current_user)
+ end
+
+ def disable_read_design_activity(object)
+ allow(Ability).to receive(:allowed?)
+ .with(current_user, :read_design_activity, eq(object))
+ .and_return(false)
+ end
+
+ context 'for :designs' do
+ let(:key) { :designs }
+
+ context 'there is no relevant instance variable' do
+ it { is_expected.to be(true) }
+ end
+
+ context 'a project has been assigned' do
+ before do
+ assign(:project, project)
+ end
+
+ it { is_expected.to be(true) }
+
+ context 'the current user cannot read design activity' do
+ before do
+ disable_read_design_activity(project)
+ end
+
+ it { is_expected.to be(false) }
+ end
+ end
+
+ context 'projects have been assigned' do
+ before do
+ assign(:projects, Project.where(id: project.id))
+ end
+
+ it { is_expected.to be(true) }
+
+ context 'the collection is empty' do
+ before do
+ assign(:projects, Project.none)
+ end
+
+ it { is_expected.to be(false) }
+ end
+
+ context 'the current user cannot read design activity' do
+ before do
+ disable_read_design_activity(project)
+ end
+
+ it { is_expected.to be(false) }
+ end
+ end
+
+ context 'a group has been assigned' do
+ let_it_be(:group) { create(:group) }
+
+ before do
+ assign(:group, group)
+ end
+
+ context 'there are no projects in the group' do
+ it { is_expected.to be(false) }
+ end
+
+ context 'the group has at least one project' do
+ before do
+ create(:project_group_link, project: project, group: group)
+ end
+
+ it { is_expected.to be(true) }
+
+ context 'the current user cannot read design activity' do
+ before do
+ disable_read_design_activity(group)
+ end
+
+ it { is_expected.to be(false) }
+ end
+ end
+ end
+ end
+ end
end
diff --git a/spec/helpers/explore_helper_spec.rb b/spec/helpers/explore_helper_spec.rb
index 1a6af3be055..d843a9d3ce5 100644
--- a/spec/helpers/explore_helper_spec.rb
+++ b/spec/helpers/explore_helper_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ExploreHelper do
+RSpec.describe ExploreHelper do
let(:user) { build(:user) }
before do
diff --git a/spec/helpers/export_helper_spec.rb b/spec/helpers/export_helper_spec.rb
index 3fbda441b5d..7ab9560ca6a 100644
--- a/spec/helpers/export_helper_spec.rb
+++ b/spec/helpers/export_helper_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ExportHelper do
+RSpec.describe ExportHelper do
describe '#project_export_descriptions' do
it 'includes design management' do
expect(project_export_descriptions).to include('Design Management files and data')
diff --git a/spec/helpers/external_link_helper_spec.rb b/spec/helpers/external_link_helper_spec.rb
index 7fc4ef18731..b1a1884d887 100644
--- a/spec/helpers/external_link_helper_spec.rb
+++ b/spec/helpers/external_link_helper_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ExternalLinkHelper do
+RSpec.describe ExternalLinkHelper do
include IconsHelper
it 'returns external link with icon' do
diff --git a/spec/helpers/form_helper_spec.rb b/spec/helpers/form_helper_spec.rb
index 6698d8970e7..79c96e65a0e 100644
--- a/spec/helpers/form_helper_spec.rb
+++ b/spec/helpers/form_helper_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe FormHelper do
+RSpec.describe FormHelper do
describe 'form_errors' do
it 'returns nil when model has no errors' do
model = double(errors: [])
diff --git a/spec/helpers/git_helper_spec.rb b/spec/helpers/git_helper_spec.rb
index 6dfd8d2cc76..0dd9eecb7f0 100644
--- a/spec/helpers/git_helper_spec.rb
+++ b/spec/helpers/git_helper_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GitHelper do
+RSpec.describe GitHelper do
describe '#short_sha' do
let(:short_sha) { helper.short_sha('d4e043f6c20749a3ab3f4b8e23f2a8979f4b9100') }
diff --git a/spec/helpers/gitlab_routing_helper_spec.rb b/spec/helpers/gitlab_routing_helper_spec.rb
index 4def04f4284..bd48fc7cee2 100644
--- a/spec/helpers/gitlab_routing_helper_spec.rb
+++ b/spec/helpers/gitlab_routing_helper_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GitlabRoutingHelper do
+RSpec.describe GitlabRoutingHelper do
let(:project) { build_stubbed(:project) }
let(:group) { build_stubbed(:group) }
@@ -147,8 +147,8 @@ describe GitlabRoutingHelper do
end
context 'snippets' do
- let_it_be(:personal_snippet) { create(:personal_snippet) }
- let_it_be(:project_snippet) { create(:project_snippet) }
+ let_it_be(:personal_snippet) { create(:personal_snippet, :repository) }
+ let_it_be(:project_snippet) { create(:project_snippet, :repository) }
let_it_be(:note) { create(:note_on_personal_snippet, noteable: personal_snippet) }
describe '#gitlab_snippet_path' do
@@ -181,6 +181,23 @@ describe GitlabRoutingHelper do
end
end
+ describe '#gitlab_raw_snippet_blob_path' do
+ let(:ref) { 'test-ref' }
+
+ it_behaves_like 'snippet blob raw path' do
+ subject { gitlab_raw_snippet_blob_path(blob, ref) }
+ end
+
+ context 'without a ref' do
+ let(:blob) { personal_snippet.blobs.first }
+ let(:ref) { blob.repository.root_ref }
+
+ it 'uses the root ref' do
+ expect(gitlab_raw_snippet_blob_path(blob)).to eq("/-/snippets/#{personal_snippet.id}/raw/#{ref}/#{blob.path}")
+ end
+ end
+ end
+
describe '#gitlab_raw_snippet_url' do
it 'returns the raw personal snippet url' do
expect(gitlab_raw_snippet_url(personal_snippet)).to eq("http://test.host/snippets/#{personal_snippet.id}/raw")
@@ -191,6 +208,32 @@ describe GitlabRoutingHelper do
end
end
+ describe '#gitlab_raw_snippet_blob_url' do
+ let(:blob) { snippet.blobs.first }
+ let(:ref) { 'snippet-test-ref' }
+
+ context 'for a PersonalSnippet' do
+ let(:snippet) { personal_snippet }
+
+ it { expect(gitlab_raw_snippet_blob_url(snippet, blob.path, ref)).to eq("http://test.host/-/snippets/#{snippet.id}/raw/#{ref}/#{blob.path}") }
+ end
+
+ context 'for a ProjectSnippet' do
+ let(:snippet) { project_snippet }
+
+ it { expect(gitlab_raw_snippet_blob_url(snippet, blob.path, ref)).to eq("http://test.host/#{snippet.project.full_path}/-/snippets/#{snippet.id}/raw/#{ref}/#{blob.path}") }
+ end
+
+ context 'without a ref' do
+ let(:snippet) { personal_snippet }
+ let(:ref) { snippet.repository.root_ref }
+
+ it 'uses the root ref' do
+ expect(gitlab_raw_snippet_blob_url(snippet, blob.path)).to eq("http://test.host/-/snippets/#{snippet.id}/raw/#{ref}/#{blob.path}")
+ end
+ end
+ end
+
describe '#gitlab_snippet_notes_path' do
it 'returns the notes path for the personal snippet' do
expect(gitlab_snippet_notes_path(personal_snippet)).to eq("/snippets/#{personal_snippet.id}/notes")
diff --git a/spec/helpers/graph_helper_spec.rb b/spec/helpers/graph_helper_spec.rb
index dc389c09e60..3c7e4f970c3 100644
--- a/spec/helpers/graph_helper_spec.rb
+++ b/spec/helpers/graph_helper_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GraphHelper do
+RSpec.describe GraphHelper do
describe '#get_refs' do
let(:project) { create(:project, :repository) }
let(:commit) { project.commit("master") }
diff --git a/spec/helpers/groups/group_members_helper_spec.rb b/spec/helpers/groups/group_members_helper_spec.rb
index 898c330c498..90792331d9b 100644
--- a/spec/helpers/groups/group_members_helper_spec.rb
+++ b/spec/helpers/groups/group_members_helper_spec.rb
@@ -2,7 +2,7 @@
require "spec_helper"
-describe Groups::GroupMembersHelper do
+RSpec.describe Groups::GroupMembersHelper do
describe '.group_member_select_options' do
let(:group) { create(:group) }
diff --git a/spec/helpers/groups_helper_spec.rb b/spec/helpers/groups_helper_spec.rb
index 5be247c5b49..a739c16f3b1 100644
--- a/spec/helpers/groups_helper_spec.rb
+++ b/spec/helpers/groups_helper_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GroupsHelper do
+RSpec.describe GroupsHelper do
include ApplicationHelper
describe 'group_icon_url' do
diff --git a/spec/helpers/hooks_helper_spec.rb b/spec/helpers/hooks_helper_spec.rb
index 4352089c1c0..92e082c4974 100644
--- a/spec/helpers/hooks_helper_spec.rb
+++ b/spec/helpers/hooks_helper_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe HooksHelper do
+RSpec.describe HooksHelper do
let(:project) { create(:project) }
let(:project_hook) { create(:project_hook, project: project) }
let(:system_hook) { create(:system_hook) }
diff --git a/spec/helpers/icons_helper_spec.rb b/spec/helpers/icons_helper_spec.rb
index 5c26db028b7..c47bba42ae2 100644
--- a/spec/helpers/icons_helper_spec.rb
+++ b/spec/helpers/icons_helper_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe IconsHelper do
+RSpec.describe IconsHelper do
let(:icons_path) { ActionController::Base.helpers.image_path("icons.svg") }
describe 'icon' do
@@ -22,8 +22,13 @@ describe IconsHelper do
describe 'sprite_icon_path' do
it 'returns relative path' do
- expect(sprite_icon_path)
- .to eq icons_path
+ expect(sprite_icon_path).to eq(icons_path)
+ end
+
+ it 'only calls image_path once when called multiple times' do
+ expect(ActionController::Base.helpers).to receive(:image_path).once.and_call_original
+
+ 2.times { sprite_icon_path }
end
context 'when an asset_host is set in the config it will return an absolute local URL' do
@@ -235,4 +240,25 @@ describe IconsHelper do
.to eq("<span class=\"gl-snippet-icon gl-snippet-icon-download\"></span>")
end
end
+
+ describe 'loading_icon' do
+ it 'returns span with gl-spinner class and default configuration' do
+ expect(loading_icon.to_s)
+ .to eq '<span class="gl-spinner gl-spinner-orange gl-spinner-sm" aria-label="Loading"></span>'
+ end
+
+ context 'when css_class is provided' do
+ it 'appends css_class to gl-spinner element' do
+ expect(loading_icon(css_class: 'gl-mr-2').to_s)
+ .to eq '<span class="gl-spinner gl-spinner-orange gl-spinner-sm gl-mr-2" aria-label="Loading"></span>'
+ end
+ end
+
+ context 'when container is true' do
+ it 'creates a container that has the gl-spinner-container class selector' do
+ expect(loading_icon(container: true).to_s)
+ .to eq '<div class="gl-spinner-container"><span class="gl-spinner gl-spinner-orange gl-spinner-sm" aria-label="Loading"></span></div>'
+ end
+ end
+ end
end
diff --git a/spec/helpers/import_helper_spec.rb b/spec/helpers/import_helper_spec.rb
index a6b283e49dc..18cbbdfd804 100644
--- a/spec/helpers/import_helper_spec.rb
+++ b/spec/helpers/import_helper_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ImportHelper do
+RSpec.describe ImportHelper do
describe '#sanitize_project_name' do
it 'removes leading tildes' do
expect(helper.sanitize_project_name('~~root')).to eq('root')
diff --git a/spec/helpers/instance_configuration_helper_spec.rb b/spec/helpers/instance_configuration_helper_spec.rb
index 31a6c7bc839..1ba06b97088 100644
--- a/spec/helpers/instance_configuration_helper_spec.rb
+++ b/spec/helpers/instance_configuration_helper_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe InstanceConfigurationHelper do
+RSpec.describe InstanceConfigurationHelper do
describe '#instance_configuration_cell_html' do
describe 'if not block is passed' do
it 'returns the parameter if present' do
diff --git a/spec/helpers/issuables_helper_spec.rb b/spec/helpers/issuables_helper_spec.rb
index 38ad11846d2..4c93a8387a9 100644
--- a/spec/helpers/issuables_helper_spec.rb
+++ b/spec/helpers/issuables_helper_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe IssuablesHelper do
+RSpec.describe IssuablesHelper do
let(:label) { build_stubbed(:label) }
let(:label2) { build_stubbed(:label) }
@@ -303,4 +303,28 @@ describe IssuablesHelper do
end
end
end
+
+ describe '#issuable_squash_option?' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:issuable_persisted, :squash, :squash_enabled_by_default, :expectation) do
+ true | true | true | true
+ true | false | true | false
+ false | false | false | false
+ false | false | true | true
+ false | true | false | false
+ false | true | true | true
+ end
+
+ with_them do
+ it 'returns the correct value' do
+ project = double(
+ squash_enabled_by_default?: squash_enabled_by_default
+ )
+ issuable = double(persisted?: issuable_persisted, squash: squash)
+
+ expect(helper.issuable_squash_option?(issuable, project)).to eq(expectation)
+ end
+ end
+ end
end
diff --git a/spec/helpers/issues_helper_spec.rb b/spec/helpers/issues_helper_spec.rb
index 3ef6745958c..f2757f0e3ed 100644
--- a/spec/helpers/issues_helper_spec.rb
+++ b/spec/helpers/issues_helper_spec.rb
@@ -2,7 +2,7 @@
require "spec_helper"
-describe IssuesHelper do
+RSpec.describe IssuesHelper do
let(:project) { create(:project) }
let(:issue) { create :issue, project: project }
let(:ext_project) { create :redmine_project }
@@ -209,4 +209,28 @@ describe IssuesHelper do
it_behaves_like 'does not display link'
end
end
+
+ describe '#show_moved_service_desk_issue_warning?' do
+ let(:project1) { create(:project, service_desk_enabled: true) }
+ let(:project2) { create(:project, service_desk_enabled: true) }
+ let!(:old_issue) { create(:issue, author: User.support_bot, project: project1) }
+ let!(:new_issue) { create(:issue, author: User.support_bot, project: project2) }
+
+ before do
+ allow(Gitlab::IncomingEmail).to receive(:enabled?) { true }
+ allow(Gitlab::IncomingEmail).to receive(:supports_wildcard?) { true }
+
+ old_issue.update(moved_to: new_issue)
+ end
+
+ it 'is true when moved issue project has service desk disabled' do
+ project2.update!(service_desk_enabled: false)
+
+ expect(helper.show_moved_service_desk_issue_warning?(new_issue)).to be(true)
+ end
+
+ it 'is false when moved issue project has service desk enabled' do
+ expect(helper.show_moved_service_desk_issue_warning?(new_issue)).to be(false)
+ end
+ end
end
diff --git a/spec/helpers/labels_helper_spec.rb b/spec/helpers/labels_helper_spec.rb
index ec70041d51f..77e1d10354c 100644
--- a/spec/helpers/labels_helper_spec.rb
+++ b/spec/helpers/labels_helper_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe LabelsHelper do
+RSpec.describe LabelsHelper do
describe '#show_label_issuables_link?' do
shared_examples 'a valid response to show_label_issuables_link?' do |issuables_type, when_enabled = true, when_disabled = false|
context "when asking for a #{issuables_type} link" do
diff --git a/spec/helpers/markup_helper_spec.rb b/spec/helpers/markup_helper_spec.rb
index 1fc79a9762a..302ab0cc137 100644
--- a/spec/helpers/markup_helper_spec.rb
+++ b/spec/helpers/markup_helper_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe MarkupHelper do
+RSpec.describe MarkupHelper do
let_it_be(:project) { create(:project, :repository) }
let_it_be(:user) do
user = create(:user, username: 'gfm')
diff --git a/spec/helpers/members_helper_spec.rb b/spec/helpers/members_helper_spec.rb
index 946ffcddae7..99e8696e960 100644
--- a/spec/helpers/members_helper_spec.rb
+++ b/spec/helpers/members_helper_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe MembersHelper do
+RSpec.describe MembersHelper do
describe '#remove_member_message' do
let(:requester) { create(:user) }
let(:project) { create(:project, :public) }
diff --git a/spec/helpers/merge_requests_helper_spec.rb b/spec/helpers/merge_requests_helper_spec.rb
index 8db8c37038e..fcb9efa39d5 100644
--- a/spec/helpers/merge_requests_helper_spec.rb
+++ b/spec/helpers/merge_requests_helper_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe MergeRequestsHelper do
+RSpec.describe MergeRequestsHelper do
include ActionView::Helpers::UrlHelper
include ProjectForksHelper
diff --git a/spec/helpers/namespaces_helper_spec.rb b/spec/helpers/namespaces_helper_spec.rb
index ebce296d7c2..1313a5c9352 100644
--- a/spec/helpers/namespaces_helper_spec.rb
+++ b/spec/helpers/namespaces_helper_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe NamespacesHelper do
+RSpec.describe NamespacesHelper do
let!(:admin) { create(:admin) }
let!(:admin_project_creation_level) { nil }
let!(:admin_group) do
@@ -174,96 +174,4 @@ describe NamespacesHelper do
end
end
end
-
- describe '#namespace_storage_alert' do
- subject { helper.namespace_storage_alert(namespace) }
-
- let(:namespace) { build(:namespace) }
-
- let(:payload) do
- {
- alert_level: :info,
- usage_message: "Usage",
- explanation_message: "Explanation",
- root_namespace: namespace
- }
- end
-
- before do
- allow(helper).to receive(:current_user).and_return(admin)
- allow_next_instance_of(Namespaces::CheckStorageSizeService, namespace, admin) do |check_storage_size_service|
- expect(check_storage_size_service).to receive(:execute).and_return(ServiceResponse.success(payload: payload))
- end
- end
-
- context 'when payload is not empty and no cookie is set' do
- it { is_expected.to eq(payload) }
- end
-
- context 'when there is no current_user' do
- before do
- allow(helper).to receive(:current_user).and_return(nil)
- end
-
- it { is_expected.to eq({}) }
- end
-
- context 'when payload is empty' do
- let(:payload) { {} }
-
- it { is_expected.to eq({}) }
- end
-
- context 'when cookie is set' do
- before do
- helper.request.cookies["hide_storage_limit_alert_#{namespace.id}_info"] = 'true'
- end
-
- it { is_expected.to eq({}) }
- end
-
- context 'when payload is empty and cookie is set' do
- let(:payload) { {} }
-
- before do
- helper.request.cookies["hide_storage_limit_alert_#{namespace.id}_info"] = 'true'
- end
-
- it { is_expected.to eq({}) }
- end
- end
-
- describe '#namespace_storage_alert_style' do
- using RSpec::Parameterized::TableSyntax
-
- subject { helper.namespace_storage_alert_style(alert_level) }
-
- where(:alert_level, :result) do
- :info | 'info'
- :warning | 'warning'
- :error | 'danger'
- :alert | 'danger'
- end
-
- with_them do
- it { is_expected.to eq(result) }
- end
- end
-
- describe '#namespace_storage_alert_icon' do
- using RSpec::Parameterized::TableSyntax
-
- subject { helper.namespace_storage_alert_icon(alert_level) }
-
- where(:alert_level, :result) do
- :info | 'information-o'
- :warning | 'warning'
- :error | 'error'
- :alert | 'error'
- end
-
- with_them do
- it { is_expected.to eq(result) }
- end
- end
end
diff --git a/spec/helpers/nav_helper_spec.rb b/spec/helpers/nav_helper_spec.rb
index ac1c6c62433..b1c9485e5a1 100644
--- a/spec/helpers/nav_helper_spec.rb
+++ b/spec/helpers/nav_helper_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe NavHelper, :do_not_mock_admin_mode do
+RSpec.describe NavHelper, :do_not_mock_admin_mode do
describe '#header_links' do
include_context 'custom session'
diff --git a/spec/helpers/notes_helper_spec.rb b/spec/helpers/notes_helper_spec.rb
index 543a9081779..f29f947ba46 100644
--- a/spec/helpers/notes_helper_spec.rb
+++ b/spec/helpers/notes_helper_spec.rb
@@ -2,7 +2,7 @@
require "spec_helper"
-describe NotesHelper do
+RSpec.describe NotesHelper do
include RepoHelpers
let(:owner) { create(:owner) }
diff --git a/spec/helpers/notifications_helper_spec.rb b/spec/helpers/notifications_helper_spec.rb
index d8dcce203fe..319c85c19f9 100644
--- a/spec/helpers/notifications_helper_spec.rb
+++ b/spec/helpers/notifications_helper_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe NotificationsHelper do
+RSpec.describe NotificationsHelper do
describe 'notification_icon' do
it { expect(notification_icon(:disabled)).to match('class="fa fa-microphone-slash fa-fw"') }
it { expect(notification_icon(:owner_disabled)).to match('class="fa fa-microphone-slash fa-fw"') }
diff --git a/spec/helpers/notify_helper_spec.rb b/spec/helpers/notify_helper_spec.rb
new file mode 100644
index 00000000000..9c9d745cb53
--- /dev/null
+++ b/spec/helpers/notify_helper_spec.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe NotifyHelper do
+ include ActionView::Helpers::UrlHelper
+
+ describe 'merge_request_reference_link' do
+ let(:project) { create(:project) }
+ let(:merge_request) { create(:merge_request, source_project: project) }
+
+ it 'returns link to merge request with the text reference' do
+ url = "http://test.host/#{project.full_path}/-/merge_requests/#{merge_request.iid}"
+
+ expect(merge_request_reference_link(merge_request)).to eq(reference_link(merge_request, url))
+ end
+ end
+
+ describe 'issue_reference_link' do
+ let(:project) { create(:project) }
+ let(:issue) { create(:issue, project: project) }
+
+ it 'returns link to issue with the text reference' do
+ url = "http://test.host/#{project.full_path}/-/issues/#{issue.iid}"
+
+ expect(issue_reference_link(issue)).to eq(reference_link(issue, url))
+ end
+ end
+
+ def reference_link(entity, url)
+ "<a href=\"#{url}\">#{entity.to_reference}</a>"
+ end
+end
diff --git a/spec/helpers/onboarding_experiment_helper_spec.rb b/spec/helpers/onboarding_experiment_helper_spec.rb
deleted file mode 100644
index cada91bff3c..00000000000
--- a/spec/helpers/onboarding_experiment_helper_spec.rb
+++ /dev/null
@@ -1,38 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-describe OnboardingExperimentHelper, type: :helper do
- describe '.allow_access_to_onboarding?' do
- context "when we're not gitlab.com or dev env" do
- it 'returns false' do
- allow(::Gitlab).to receive(:dev_env_or_com?).and_return(false)
-
- expect(helper.allow_access_to_onboarding?).to be(false)
- end
- end
-
- context "when we're gitlab.com or dev env" do
- before do
- allow(::Gitlab).to receive(:dev_env_or_com?).and_return(true)
- end
-
- context 'and the :user_onboarding feature is not enabled' do
- it 'returns false' do
- stub_feature_flags(user_onboarding: false)
-
- expect(helper.allow_access_to_onboarding?).to be(false)
- end
- end
-
- context 'and the :user_onboarding feature is enabled' do
- it 'returns true' do
- stub_feature_flags(user_onboarding: true)
- allow(helper).to receive(:current_user).and_return(create(:user))
-
- expect(helper.allow_access_to_onboarding?).to be(true)
- end
- end
- end
- end
-end
diff --git a/spec/helpers/operations_helper_spec.rb b/spec/helpers/operations_helper_spec.rb
new file mode 100644
index 00000000000..73deb2249bc
--- /dev/null
+++ b/spec/helpers/operations_helper_spec.rb
@@ -0,0 +1,160 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe OperationsHelper do
+ include Gitlab::Routing
+
+ let_it_be(:user) { create(:user) }
+ let_it_be(:project, reload: true) { create(:project) }
+
+ before do
+ helper.instance_variable_set(:@project, project)
+ allow(helper).to receive(:current_user) { user }
+ end
+
+ describe '#alerts_settings_data' do
+ subject { helper.alerts_settings_data }
+
+ before do
+ allow(helper).to receive(:can?).with(user, :admin_operations, project) { true }
+ end
+
+ context 'initial service configuration' do
+ let_it_be(:alerts_service) { AlertsService.new(project: project) }
+ let_it_be(:prometheus_service) { PrometheusService.new(project: project) }
+
+ before do
+ allow(project).to receive(:find_or_initialize_service).with('alerts').and_return(alerts_service)
+ allow(project).to receive(:find_or_initialize_service).with('prometheus').and_return(prometheus_service)
+ end
+
+ it 'returns the correct values' do
+ expect(subject).to eq(
+ 'activated' => 'false',
+ 'url' => alerts_service.url,
+ 'authorization_key' => nil,
+ 'form_path' => project_service_path(project, alerts_service),
+ 'alerts_setup_url' => help_page_path('user/project/integrations/generic_alerts.md', anchor: 'setting-up-generic-alerts'),
+ 'alerts_usage_url' => project_alert_management_index_path(project),
+ 'prometheus_form_path' => project_service_path(project, prometheus_service),
+ 'prometheus_reset_key_path' => reset_alerting_token_project_settings_operations_path(project),
+ 'prometheus_authorization_key' => nil,
+ 'prometheus_api_url' => nil,
+ 'prometheus_activated' => 'false',
+ 'prometheus_url' => notify_project_prometheus_alerts_url(project, format: :json),
+ 'disabled' => 'false'
+ )
+ end
+ end
+
+ context 'with external Prometheus configured' do
+ let_it_be(:prometheus_service, reload: true) do
+ create(:prometheus_service, project: project)
+ end
+
+ context 'with external Prometheus enabled' do
+ it 'returns the correct values' do
+ expect(subject).to include(
+ 'prometheus_activated' => 'true',
+ 'prometheus_api_url' => prometheus_service.api_url
+ )
+ end
+ end
+
+ context 'with external Prometheus disabled' do
+ shared_examples 'Prometheus is disabled' do
+ it 'returns the correct values' do
+ expect(subject).to include(
+ 'prometheus_activated' => 'false',
+ 'prometheus_api_url' => prometheus_service.api_url
+ )
+ end
+ end
+
+ let(:cluster_managed) { false }
+
+ before do
+ allow(prometheus_service)
+ .to receive(:prometheus_available?)
+ .and_return(cluster_managed)
+
+ prometheus_service.update!(manual_configuration: false)
+ end
+
+ include_examples 'Prometheus is disabled'
+
+ context 'when cluster managed' do
+ let(:cluster_managed) { true }
+
+ include_examples 'Prometheus is disabled'
+ end
+ end
+
+ context 'with project alert setting' do
+ let_it_be(:project_alerting_setting) { create(:project_alerting_setting, project: project) }
+
+ it 'returns the correct values' do
+ expect(subject).to include(
+ 'prometheus_authorization_key' => project_alerting_setting.token,
+ 'prometheus_api_url' => prometheus_service.api_url
+ )
+ end
+ end
+ end
+
+ context 'with generic alerts service configured' do
+ let_it_be(:alerts_service) { create(:alerts_service, project: project) }
+
+ context 'with generic alerts enabled' do
+ it 'returns the correct values' do
+ expect(subject).to include(
+ 'activated' => 'true',
+ 'authorization_key' => alerts_service.token,
+ 'url' => alerts_service.url
+ )
+ end
+ end
+
+ context 'with generic alerts disabled' do
+ before do
+ alerts_service.update!(active: false)
+ end
+
+ it 'returns the correct values' do
+ expect(subject).to include(
+ 'activated' => 'false',
+ 'authorization_key' => alerts_service.token
+ )
+ end
+ end
+ end
+ end
+
+ describe '#operations_settings_data' do
+ let_it_be(:operations_settings) do
+ create(
+ :project_incident_management_setting,
+ project: project,
+ issue_template_key: 'template-key',
+ pagerduty_active: true
+ )
+ end
+
+ subject { helper.operations_settings_data }
+
+ it 'returns the correct set of data' do
+ is_expected.to eq(
+ operations_settings_endpoint: project_settings_operations_path(project),
+ templates: '[]',
+ create_issue: 'false',
+ issue_template_key: 'template-key',
+ send_email: 'false',
+ pagerduty_active: 'true',
+ pagerduty_token: operations_settings.pagerduty_token,
+ pagerduty_webhook_url: project_incidents_pagerduty_url(project, token: operations_settings.pagerduty_token),
+ pagerduty_reset_key_path: reset_pagerduty_token_project_settings_operations_path(project)
+ )
+ end
+ end
+end
diff --git a/spec/helpers/page_layout_helper_spec.rb b/spec/helpers/page_layout_helper_spec.rb
index 55f743ac683..e8a5c4613fe 100644
--- a/spec/helpers/page_layout_helper_spec.rb
+++ b/spec/helpers/page_layout_helper_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe PageLayoutHelper do
+RSpec.describe PageLayoutHelper do
describe 'page_description' do
it 'defaults to nil' do
expect(helper.page_description).to eq nil
diff --git a/spec/helpers/pagination_helper_spec.rb b/spec/helpers/pagination_helper_spec.rb
index 9fb51249edc..4cd6b6f3922 100644
--- a/spec/helpers/pagination_helper_spec.rb
+++ b/spec/helpers/pagination_helper_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe PaginationHelper do
+RSpec.describe PaginationHelper do
describe '#paginate_collection' do
let(:collection) { User.all.page(1) }
diff --git a/spec/helpers/preferences_helper_spec.rb b/spec/helpers/preferences_helper_spec.rb
index 7969cfd97b5..be0ad5e1a3f 100644
--- a/spec/helpers/preferences_helper_spec.rb
+++ b/spec/helpers/preferences_helper_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe PreferencesHelper do
+RSpec.describe PreferencesHelper do
describe '#dashboard_choices' do
let(:user) { build(:user) }
@@ -120,13 +120,6 @@ describe PreferencesHelper do
end
end
- describe '#language_choices' do
- it 'returns an array of all available languages' do
- expect(helper.language_choices).to be_an(Array)
- expect(helper.language_choices.map(&:first)).to eq(Gitlab::I18n::AVAILABLE_LANGUAGES.values.sort)
- end
- end
-
def stub_user(messages = {})
if messages.empty?
allow(helper).to receive(:current_user).and_return(nil)
diff --git a/spec/helpers/profiles_helper_spec.rb b/spec/helpers/profiles_helper_spec.rb
index fc282eee26d..4a8ba2b7113 100644
--- a/spec/helpers/profiles_helper_spec.rb
+++ b/spec/helpers/profiles_helper_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ProfilesHelper do
+RSpec.describe ProfilesHelper do
describe '#commit_email_select_options' do
it 'returns an array with private commit email along with all the verified emails' do
user = create(:user)
diff --git a/spec/helpers/projects/alert_management_helper_spec.rb b/spec/helpers/projects/alert_management_helper_spec.rb
index 49b64b316e7..859c08b194a 100644
--- a/spec/helpers/projects/alert_management_helper_spec.rb
+++ b/spec/helpers/projects/alert_management_helper_spec.rb
@@ -2,23 +2,24 @@
require 'spec_helper'
-describe Projects::AlertManagementHelper do
+RSpec.describe Projects::AlertManagementHelper do
include Gitlab::Routing.url_helpers
let_it_be(:project, reload: true) { create(:project) }
let_it_be(:current_user) { create(:user) }
- let_it_be(:project_path) { project.full_path }
+ let(:project_path) { project.full_path }
+ let(:project_id) { project.id }
describe '#alert_management_data' do
let(:user_can_enable_alert_management) { true }
- let(:setting_path) { edit_project_service_path(project, AlertsService) }
+ let(:setting_path) { project_settings_operations_path(project, anchor: 'js-alert-management-settings') }
subject(:data) { helper.alert_management_data(current_user, project) }
before do
allow(helper)
.to receive(:can?)
- .with(current_user, :admin_project, project)
+ .with(current_user, :admin_operations, project)
.and_return(user_can_enable_alert_management)
end
@@ -27,6 +28,7 @@ describe Projects::AlertManagementHelper do
expect(helper.alert_management_data(current_user, project)).to match(
'project-path' => project_path,
'enable-alert-management-path' => setting_path,
+ 'populating-alerts-help-url' => 'http://test.host/help/user/project/operations/alert_management.html#enable-alert-management',
'empty-alert-svg-path' => match_asset_path('/assets/illustrations/alert-management-empty-state.svg'),
'user-can-enable-alert-management' => 'true',
'alert-management-enabled' => 'false'
@@ -56,6 +58,28 @@ describe Projects::AlertManagementHelper do
end
end
+ context 'with prometheus service' do
+ let_it_be(:prometheus_service) { create(:prometheus_service, project: project) }
+
+ context 'when prometheus service is active' do
+ it 'enables alert management' do
+ expect(data).to include(
+ 'alert-management-enabled' => 'true'
+ )
+ end
+ end
+
+ context 'when prometheus service is inactive' do
+ it 'disables alert management' do
+ prometheus_service.update!(manual_configuration: false)
+
+ expect(data).to include(
+ 'alert-management-enabled' => 'false'
+ )
+ end
+ end
+ end
+
context 'when user does not have requisite enablement permissions' do
let(:user_can_enable_alert_management) { false }
@@ -75,6 +99,7 @@ describe Projects::AlertManagementHelper do
expect(helper.alert_management_detail_data(project, alert_id)).to eq(
'alert-id' => alert_id,
'project-path' => project_path,
+ 'project-id' => project_id,
'project-issues-path' => issues_path
)
end
diff --git a/spec/helpers/projects/error_tracking_helper_spec.rb b/spec/helpers/projects/error_tracking_helper_spec.rb
index 008d749a002..882031a9c86 100644
--- a/spec/helpers/projects/error_tracking_helper_spec.rb
+++ b/spec/helpers/projects/error_tracking_helper_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Projects::ErrorTrackingHelper do
+RSpec.describe Projects::ErrorTrackingHelper do
include Gitlab::Routing.url_helpers
let_it_be(:project, reload: true) { create(:project) }
diff --git a/spec/helpers/projects_helper_spec.rb b/spec/helpers/projects_helper_spec.rb
index 4e072f02ae0..a3d0673f1b3 100644
--- a/spec/helpers/projects_helper_spec.rb
+++ b/spec/helpers/projects_helper_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ProjectsHelper do
+RSpec.describe ProjectsHelper do
include ProjectForksHelper
let_it_be(:project) { create(:project) }
@@ -444,8 +444,8 @@ describe ProjectsHelper do
end
describe '#get_project_nav_tabs' do
+ let_it_be(:user) { create(:user) }
let(:project) { create(:project) }
- let(:user) { create(:user) }
before do
allow(helper).to receive(:can?) { true }
@@ -501,6 +501,20 @@ describe ProjectsHelper do
is_expected.not_to include(:external_wiki)
end
end
+
+ context 'when project has confluence enabled' do
+ before do
+ allow(project).to receive(:has_confluence?).and_return(true)
+ end
+
+ it { is_expected.to include(:confluence) }
+ it { is_expected.not_to include(:wiki) }
+ end
+
+ context 'when project does not have confluence enabled' do
+ it { is_expected.not_to include(:confluence) }
+ it { is_expected.to include(:wiki) }
+ end
end
describe '#can_view_operations_tab?' do
@@ -977,19 +991,32 @@ describe ProjectsHelper do
end
end
- describe '#project_license_name(project)' do
+ describe '#project_license_name(project)', :request_store do
let_it_be(:project) { create(:project) }
let_it_be(:repository) { project.repository }
subject { project_license_name(project) }
+ def license_name
+ project_license_name(project)
+ end
+
context 'gitaly is working appropriately' do
- it 'returns the license name' do
- license = Licensee::License.new('mit')
- allow(repository).to receive(:license).and_return(license)
+ let(:license) { Licensee::License.new('mit') }
+ before do
+ expect(repository).to receive(:license).and_return(license)
+ end
+
+ it 'returns the license name' do
expect(subject).to eq(license.name)
end
+
+ it 'memoizes the value' do
+ expect do
+ 2.times { expect(license_name).to eq(license.name) }
+ end.to change { Gitlab::GitalyClient.get_request_count }.by_at_most(1)
+ end
end
context 'gitaly is unreachable' do
@@ -1003,10 +1030,16 @@ describe ProjectsHelper do
subject
end
+
+ it 'memoizes the nil value' do
+ expect do
+ 2.times { expect(license_name).to be_nil }
+ end.to change { Gitlab::GitalyClient.get_request_count }.by_at_most(1)
+ end
end
before do
- allow(repository).to receive(:license).and_raise(exception)
+ expect(repository).to receive(:license).and_raise(exception)
end
context "Gitlab::Git::CommandError" do
diff --git a/spec/helpers/recaptcha_experiment_helper_spec.rb b/spec/helpers/recaptcha_experiment_helper_spec.rb
index a5b233e28a0..e677164c950 100644
--- a/spec/helpers/recaptcha_experiment_helper_spec.rb
+++ b/spec/helpers/recaptcha_experiment_helper_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe RecaptchaExperimentHelper, type: :helper do
+RSpec.describe RecaptchaExperimentHelper, type: :helper do
let(:session) { {} }
before do
diff --git a/spec/helpers/releases_helper_spec.rb b/spec/helpers/releases_helper_spec.rb
index de4086e48db..82fc799f9b0 100644
--- a/spec/helpers/releases_helper_spec.rb
+++ b/spec/helpers/releases_helper_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ReleasesHelper do
+RSpec.describe ReleasesHelper do
describe '#illustration' do
it 'returns the correct image path' do
expect(helper.illustration).to match(/illustrations\/releases-(\w+)\.svg/)
@@ -22,6 +22,7 @@ describe ReleasesHelper do
let(:can_user_create_release) { false }
let(:common_keys) { [:project_id, :illustration_path, :documentation_path] }
+ # rubocop: disable CodeReuse/ActiveRecord
before do
helper.instance_variable_set(:@project, project)
helper.instance_variable_set(:@release, release)
@@ -30,6 +31,7 @@ describe ReleasesHelper do
.with(user, :create_release, project)
.and_return(can_user_create_release)
end
+ # rubocop: enable CodeReuse/ActiveRecord
describe '#data_for_releases_page' do
it 'includes the required data for displaying release blocks' do
@@ -41,7 +43,20 @@ describe ReleasesHelper do
it 'includes new_release_path' do
expect(helper.data_for_releases_page.keys).to contain_exactly(*common_keys, :new_release_path)
- expect(helper.data_for_releases_page[:new_release_path]).to eq(new_project_tag_path(project))
+ end
+
+ it 'points new_release_path to the "New Release" page' do
+ expect(helper.data_for_releases_page[:new_release_path]).to eq(new_project_release_path(project))
+ end
+
+ context 'when the "new_release_page" feature flag is disabled' do
+ before do
+ stub_feature_flags(new_release_page: false)
+ end
+
+ it 'points new_release_path to the "New Tag" page' do
+ expect(helper.data_for_releases_page[:new_release_path]).to eq(new_project_tag_path(project))
+ end
end
end
end
@@ -57,7 +72,23 @@ describe ReleasesHelper do
release_assets_docs_path
manage_milestones_path
new_milestone_path)
- expect(helper.data_for_edit_release_page.keys).to eq(keys)
+
+ expect(helper.data_for_edit_release_page.keys).to match_array(keys)
+ end
+ end
+
+ describe '#data_for_new_release_page' do
+ it 'has the needed data to display the "new release" page' do
+ keys = %i(project_id
+ markdown_preview_path
+ markdown_docs_path
+ update_release_api_docs_path
+ release_assets_docs_path
+ manage_milestones_path
+ new_milestone_path
+ default_branch)
+
+ expect(helper.data_for_new_release_page.keys).to match_array(keys)
end
end
end
diff --git a/spec/helpers/rss_helper_spec.rb b/spec/helpers/rss_helper_spec.rb
index 657f5fb42bc..c7eb33dc6f7 100644
--- a/spec/helpers/rss_helper_spec.rb
+++ b/spec/helpers/rss_helper_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe RssHelper do
+RSpec.describe RssHelper do
describe '#rss_url_options' do
context 'when signed in' do
it "includes the current_user's feed_token" do
diff --git a/spec/helpers/runners_helper_spec.rb b/spec/helpers/runners_helper_spec.rb
deleted file mode 100644
index 042714d002e..00000000000
--- a/spec/helpers/runners_helper_spec.rb
+++ /dev/null
@@ -1,56 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-describe RunnersHelper do
- it "returns - not contacted yet" do
- runner = FactoryBot.build :ci_runner
- expect(runner_status_icon(runner)).to include("not connected yet")
- end
-
- it "returns offline text" do
- runner = FactoryBot.build(:ci_runner, contacted_at: 1.day.ago, active: true)
- expect(runner_status_icon(runner)).to include("Runner is offline")
- end
-
- it "returns online text" do
- runner = FactoryBot.build(:ci_runner, contacted_at: 1.second.ago, active: true)
- expect(runner_status_icon(runner)).to include("Runner is online")
- end
-
- describe '#runner_contacted_at' do
- let(:contacted_at_stored) { 1.hour.ago.change(usec: 0) }
- let(:contacted_at_cached) { 1.second.ago.change(usec: 0) }
- let(:runner) { create(:ci_runner, contacted_at: contacted_at_stored) }
-
- before do
- runner.cache_attributes(contacted_at: contacted_at_cached)
- end
-
- context 'without sorting' do
- it 'returns cached value' do
- expect(runner_contacted_at(runner)).to eq(contacted_at_cached)
- end
- end
-
- context 'with sorting set to created_date' do
- before do
- controller.params[:sort] = 'created_date'
- end
-
- it 'returns cached value' do
- expect(runner_contacted_at(runner)).to eq(contacted_at_cached)
- end
- end
-
- context 'with sorting set to contacted_asc' do
- before do
- controller.params[:sort] = 'contacted_asc'
- end
-
- it 'returns stored value' do
- expect(runner_contacted_at(runner)).to eq(contacted_at_stored)
- end
- end
- end
-end
diff --git a/spec/helpers/search_helper_spec.rb b/spec/helpers/search_helper_spec.rb
index b209ed869bf..699232e67b1 100644
--- a/spec/helpers/search_helper_spec.rb
+++ b/spec/helpers/search_helper_spec.rb
@@ -2,12 +2,105 @@
require 'spec_helper'
-describe SearchHelper do
+RSpec.describe SearchHelper do
# Override simple_sanitize for our testing purposes
def simple_sanitize(str)
str
end
+ describe 'search_autocomplete_opts' do
+ context "with no current user" do
+ before do
+ allow(self).to receive(:current_user).and_return(nil)
+ end
+
+ it "returns nil" do
+ expect(search_autocomplete_opts("q")).to be_nil
+ end
+ end
+
+ context "with a standard user" do
+ let(:user) { create(:user) }
+
+ before do
+ allow(self).to receive(:current_user).and_return(user)
+ end
+
+ it "includes Help sections" do
+ expect(search_autocomplete_opts("hel").size).to eq(9)
+ end
+
+ it "includes default sections" do
+ expect(search_autocomplete_opts("dash").size).to eq(1)
+ end
+
+ it "does not include admin sections" do
+ expect(search_autocomplete_opts("admin").size).to eq(0)
+ end
+
+ it "does not allow regular expression in search term" do
+ expect(search_autocomplete_opts("(webhooks|api)").size).to eq(0)
+ end
+
+ it "includes the user's groups" do
+ create(:group).add_owner(user)
+ expect(search_autocomplete_opts("gro").size).to eq(1)
+ end
+
+ it "includes nested group" do
+ create(:group, :nested, name: 'foo').add_owner(user)
+ expect(search_autocomplete_opts('foo').size).to eq(1)
+ end
+
+ it "includes the user's projects" do
+ project = create(:project, namespace: create(:namespace, owner: user))
+ expect(search_autocomplete_opts(project.name).size).to eq(1)
+ end
+
+ it "includes the required project attrs" do
+ project = create(:project, namespace: create(:namespace, owner: user))
+ result = search_autocomplete_opts(project.name).first
+
+ expect(result.keys).to match_array(%i[category id value label url avatar_url])
+ end
+
+ it "includes the required group attrs" do
+ create(:group).add_owner(user)
+ result = search_autocomplete_opts("gro").first
+
+ expect(result.keys).to match_array(%i[category id label url avatar_url])
+ end
+
+ it "does not include the public group" do
+ group = create(:group)
+ expect(search_autocomplete_opts(group.name).size).to eq(0)
+ end
+
+ context "with a current project" do
+ before do
+ @project = create(:project, :repository)
+ end
+
+ it "includes project-specific sections" do
+ expect(search_autocomplete_opts("Files").size).to eq(1)
+ expect(search_autocomplete_opts("Commits").size).to eq(1)
+ end
+ end
+ end
+
+ context 'with an admin user' do
+ let(:admin) { create(:admin) }
+
+ before do
+ allow(self).to receive(:current_user).and_return(admin)
+ end
+
+ it "includes admin sections" do
+ expect(search_autocomplete_opts("admin").size).to eq(1)
+ end
+ end
+ end
+
describe 'search_entries_info' do
using RSpec::Parameterized::TableSyntax
diff --git a/spec/helpers/services_helper_spec.rb b/spec/helpers/services_helper_spec.rb
index edc14f86a50..10d6ec7b6a7 100644
--- a/spec/helpers/services_helper_spec.rb
+++ b/spec/helpers/services_helper_spec.rb
@@ -2,14 +2,33 @@
require 'spec_helper'
-describe ServicesHelper do
+RSpec.describe ServicesHelper do
describe 'event_action_title' do
it { expect(event_action_title('comment')).to eq 'Comment' }
it { expect(event_action_title('something')).to eq 'Something' }
end
- describe 'event_action_description' do
- it { expect(event_action_description('comment')).to eq 'Comment will be posted on each event' }
- it { expect(event_action_description('something')).to eq nil }
+ describe '#integration_form_data' do
+ subject { helper.integration_form_data(integration) }
+
+ context 'Jira service' do
+ let(:integration) { build(:jira_service) }
+
+ it 'includes Jira specific fields' do
+ is_expected.to include(
+ :id,
+ :show_active,
+ :activated,
+ :type,
+ :merge_request_events,
+ :commit_events,
+ :enable_comments,
+ :comment_detail,
+ :trigger_events,
+ :fields,
+ :inherit_from_id
+ )
+ end
+ end
end
end
diff --git a/spec/helpers/sessions_helper_spec.rb b/spec/helpers/sessions_helper_spec.rb
index 647771ace92..027943aecee 100644
--- a/spec/helpers/sessions_helper_spec.rb
+++ b/spec/helpers/sessions_helper_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe SessionsHelper do
+RSpec.describe SessionsHelper do
describe '#unconfirmed_email?' do
it 'returns true when the flash alert contains a devise failure unconfirmed message' do
flash[:alert] = t(:unconfirmed, scope: [:devise, :failure])
diff --git a/spec/helpers/sidekiq_helper_spec.rb b/spec/helpers/sidekiq_helper_spec.rb
index 86e52419f9c..6a0a92bafd8 100644
--- a/spec/helpers/sidekiq_helper_spec.rb
+++ b/spec/helpers/sidekiq_helper_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe SidekiqHelper do
+RSpec.describe SidekiqHelper do
describe 'parse_sidekiq_ps' do
it 'parses line with time' do
line = '55137 10,0 2,1 S+ 2:30pm sidekiq 4.1.4 gitlab [0 of 25 busy] '
diff --git a/spec/helpers/snippets_helper_spec.rb b/spec/helpers/snippets_helper_spec.rb
index 6fdf4f5cfb4..8fc54f17e71 100644
--- a/spec/helpers/snippets_helper_spec.rb
+++ b/spec/helpers/snippets_helper_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe SnippetsHelper do
+RSpec.describe SnippetsHelper do
include Gitlab::Routing
include IconsHelper
diff --git a/spec/helpers/sorting_helper_spec.rb b/spec/helpers/sorting_helper_spec.rb
index 5397a47b3dd..6c52016139b 100644
--- a/spec/helpers/sorting_helper_spec.rb
+++ b/spec/helpers/sorting_helper_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe SortingHelper do
+RSpec.describe SortingHelper do
include ApplicationHelper
include IconsHelper
include ExploreHelper
diff --git a/spec/helpers/sourcegraph_helper_spec.rb b/spec/helpers/sourcegraph_helper_spec.rb
index 3e8486a5632..6a95c8e4a43 100644
--- a/spec/helpers/sourcegraph_helper_spec.rb
+++ b/spec/helpers/sourcegraph_helper_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe SourcegraphHelper do
+RSpec.describe SourcegraphHelper do
describe '#sourcegraph_url_message' do
let(:sourcegraph_url) { 'http://sourcegraph.example.com' }
diff --git a/spec/helpers/storage_helper_spec.rb b/spec/helpers/storage_helper_spec.rb
index 577e6e5caf0..eca42c8ce06 100644
--- a/spec/helpers/storage_helper_spec.rb
+++ b/spec/helpers/storage_helper_spec.rb
@@ -2,7 +2,7 @@
require "spec_helper"
-describe StorageHelper do
+RSpec.describe StorageHelper do
describe "#storage_counter" do
it "formats bytes to one decimal place" do
expect(helper.storage_counter(1.23.megabytes)).to eq("1.2 MB")
@@ -30,10 +30,11 @@ describe StorageHelper do
repository_size: 10.kilobytes,
wiki_size: 10.bytes,
lfs_objects_size: 20.gigabytes,
- build_artifacts_size: 30.megabytes))
+ build_artifacts_size: 30.megabytes,
+ snippets_size: 40.megabytes))
end
- let(:message) { 'Repository: 10 KB / Wikis: 10 Bytes / Build Artifacts: 30 MB / LFS: 20 GB' }
+ let(:message) { 'Repository: 10 KB / Wikis: 10 Bytes / Build Artifacts: 30 MB / LFS: 20 GB / Snippets: 40 MB' }
it 'works on ProjectStatistics' do
expect(helper.storage_counters_details(project.statistics)).to eq(message)
diff --git a/spec/helpers/submodule_helper_spec.rb b/spec/helpers/submodule_helper_spec.rb
index db0836c8550..426bca2ced2 100644
--- a/spec/helpers/submodule_helper_spec.rb
+++ b/spec/helpers/submodule_helper_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe SubmoduleHelper do
+RSpec.describe SubmoduleHelper do
include RepoHelpers
let(:submodule_item) { double(id: 'hash', path: 'rack') }
diff --git a/spec/helpers/subscribable_banner_helper_spec.rb b/spec/helpers/subscribable_banner_helper_spec.rb
index 75f2e32d7d8..3f9ea989fb0 100644
--- a/spec/helpers/subscribable_banner_helper_spec.rb
+++ b/spec/helpers/subscribable_banner_helper_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe SubscribableBannerHelper do
+RSpec.describe SubscribableBannerHelper do
describe '#display_subscription_banner!' do
it 'is over-written in EE' do
expect { helper.display_subscription_banner! }.not_to raise_error
diff --git a/spec/helpers/tab_helper_spec.rb b/spec/helpers/tab_helper_spec.rb
index 3a3935a2130..f89d0ac0f5a 100644
--- a/spec/helpers/tab_helper_spec.rb
+++ b/spec/helpers/tab_helper_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe TabHelper do
+RSpec.describe TabHelper do
include ApplicationHelper
describe 'nav_link' do
diff --git a/spec/helpers/time_helper_spec.rb b/spec/helpers/time_helper_spec.rb
index 858d6d341f4..6663a5c81c8 100644
--- a/spec/helpers/time_helper_spec.rb
+++ b/spec/helpers/time_helper_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe TimeHelper do
+RSpec.describe TimeHelper do
describe "#time_interval_in_words" do
it "returns minutes and seconds" do
intervals_in_words = {
diff --git a/spec/helpers/timeboxes_helper_spec.rb b/spec/helpers/timeboxes_helper_spec.rb
index 6fe738914ce..94e997f7a65 100644
--- a/spec/helpers/timeboxes_helper_spec.rb
+++ b/spec/helpers/timeboxes_helper_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe TimeboxesHelper do
+RSpec.describe TimeboxesHelper do
describe '#milestones_filter_dropdown_path' do
let(:project) { create(:project) }
let(:project2) { create(:project) }
diff --git a/spec/helpers/timeboxes_routing_helper_spec.rb b/spec/helpers/timeboxes_routing_helper_spec.rb
index 1b0eb2c0ad2..952194b6704 100644
--- a/spec/helpers/timeboxes_routing_helper_spec.rb
+++ b/spec/helpers/timeboxes_routing_helper_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe TimeboxesRoutingHelper do
+RSpec.describe TimeboxesRoutingHelper do
let(:project) { build_stubbed(:project) }
let(:group) { build_stubbed(:group) }
diff --git a/spec/helpers/todos_helper_spec.rb b/spec/helpers/todos_helper_spec.rb
index 0811c2af891..6b658a475b1 100644
--- a/spec/helpers/todos_helper_spec.rb
+++ b/spec/helpers/todos_helper_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe TodosHelper do
+RSpec.describe TodosHelper do
let_it_be(:user) { create(:user) }
let_it_be(:author) { create(:user) }
let_it_be(:issue) { create(:issue, title: 'Issue 1') }
@@ -163,4 +163,83 @@ describe TodosHelper do
expect(design_option).to include(text: 'Design')
end
end
+
+ describe '#todo_target_state_pill' do
+ subject { helper.todo_target_state_pill(todo) }
+
+ shared_examples 'a rendered state pill' do |attr|
+ it 'returns expected html' do
+ aggregate_failures do
+ expect(subject).to have_css(".status-box-#{attr[:type]}-#{attr[:state].dasherize}")
+ expect(subject).to have_content(attr[:state].capitalize)
+ end
+ end
+ end
+
+ shared_examples 'no state pill' do
+ specify { expect(subject).to eq(nil) }
+ end
+
+ context 'merge request todo' do
+ let(:todo) { create(:todo, target: create(:merge_request)) }
+
+ it_behaves_like 'no state pill'
+
+ context 'merged MR' do
+ before do
+ todo.target.update!(state: 'merged')
+ end
+
+ it_behaves_like 'a rendered state pill', type: 'mr', state: 'merged'
+ end
+ end
+
+ context 'issue todo' do
+ let(:todo) { create(:todo, target: issue) }
+
+ it_behaves_like 'no state pill'
+
+ context 'closed issue' do
+ before do
+ todo.target.update!(state: 'closed')
+ end
+
+ it_behaves_like 'a rendered state pill', type: 'issue', state: 'closed'
+ end
+ end
+
+ context 'alert todo' do
+ let(:todo) { alert_todo }
+
+ it_behaves_like 'no state pill'
+
+ context 'resolved alert' do
+ before do
+ todo.target.resolve!
+ end
+
+ it_behaves_like 'a rendered state pill', type: 'alert', state: 'resolved'
+ end
+ end
+ end
+
+ describe '#todo_author_display?' do
+ using RSpec::Parameterized::TableSyntax
+
+ subject { helper.todo_author_display?(alert_todo) }
+
+ where(:action, :result) do
+ Todo::BUILD_FAILED | false
+ Todo::UNMERGEABLE | false
+ Todo::ASSIGNED | true
+ end
+
+ with_them do
+ before do
+ alert_todo.action = action
+ end
+
+ it { is_expected.to eq(result) }
+ end
+ end
end
diff --git a/spec/helpers/tracking_helper_spec.rb b/spec/helpers/tracking_helper_spec.rb
index b0c98be4130..47b344cfc25 100644
--- a/spec/helpers/tracking_helper_spec.rb
+++ b/spec/helpers/tracking_helper_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe TrackingHelper do
+RSpec.describe TrackingHelper do
describe '#tracking_attrs' do
using RSpec::Parameterized::TableSyntax
diff --git a/spec/helpers/tree_helper_spec.rb b/spec/helpers/tree_helper_spec.rb
index c7cdb4ae45c..307479744ef 100644
--- a/spec/helpers/tree_helper_spec.rb
+++ b/spec/helpers/tree_helper_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe TreeHelper do
+RSpec.describe TreeHelper do
let(:project) { create(:project, :repository) }
let(:repository) { project.repository }
let(:sha) { 'c1c67abbaf91f624347bb3ae96eabe3a1b742478' }
@@ -154,4 +154,58 @@ describe TreeHelper do
expect(helper.commit_in_single_accessible_branch).to include(escaped_branch_name)
end
end
+
+ describe '#vue_file_list_data' do
+ before do
+ allow(helper).to receive(:current_user).and_return(nil)
+ end
+
+ it 'returns a list of attributes related to the project' do
+ expect(helper.vue_file_list_data(project, sha)).to include(
+ can_push_code: nil,
+ fork_path: nil,
+ escaped_ref: sha,
+ ref: sha,
+ project_path: project.full_path,
+ project_short_path: project.path,
+ full_name: project.name_with_namespace
+ )
+ end
+
+ context 'user does not have write access but a personal fork exists' do
+ include ProjectForksHelper
+
+ let_it_be(:user) { create(:user) }
+ let!(:forked_project) { create(:project, :repository, namespace: user.namespace) }
+
+ before do
+ project.add_guest(user)
+ fork_project(project, nil, target_project: forked_project)
+
+ allow(helper).to receive(:current_user).and_return(user)
+ end
+
+ it 'includes fork_path too' do
+ expect(helper.vue_file_list_data(project, sha)).to include(
+ fork_path: forked_project.full_path
+ )
+ end
+ end
+
+ context 'user has write access' do
+ let_it_be(:user) { create(:user) }
+
+ before do
+ project.add_developer(user)
+
+ allow(helper).to receive(:current_user).and_return(user)
+ end
+
+ it 'includes can_push_code: true' do
+ expect(helper.vue_file_list_data(project, sha)).to include(
+ can_push_code: "true"
+ )
+ end
+ end
+ end
end
diff --git a/spec/helpers/user_callouts_helper_spec.rb b/spec/helpers/user_callouts_helper_spec.rb
index b123b11d278..60238053aa2 100644
--- a/spec/helpers/user_callouts_helper_spec.rb
+++ b/spec/helpers/user_callouts_helper_spec.rb
@@ -2,15 +2,15 @@
require "spec_helper"
-describe UserCalloutsHelper do
- let(:user) { create(:user) }
+RSpec.describe UserCalloutsHelper do
+ let_it_be(:user) { create(:user) }
before do
allow(helper).to receive(:current_user).and_return(user)
end
describe '.show_gke_cluster_integration_callout?' do
- let(:project) { create(:project) }
+ let_it_be(:project) { create(:project) }
subject { helper.show_gke_cluster_integration_callout?(project) }
diff --git a/spec/helpers/users_helper_spec.rb b/spec/helpers/users_helper_spec.rb
index e3e599007a4..8dfdb23c64b 100644
--- a/spec/helpers/users_helper_spec.rb
+++ b/spec/helpers/users_helper_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe UsersHelper do
+RSpec.describe UsersHelper do
include TermsHelper
let(:user) { create(:user) }
diff --git a/spec/helpers/version_check_helper_spec.rb b/spec/helpers/version_check_helper_spec.rb
index 421ff21bfdb..6d849d0720e 100644
--- a/spec/helpers/version_check_helper_spec.rb
+++ b/spec/helpers/version_check_helper_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe VersionCheckHelper do
+RSpec.describe VersionCheckHelper do
describe '#version_status_badge' do
it 'returns nil if not dev environment and not enabled' do
stub_rails_env('development')
diff --git a/spec/helpers/visibility_level_helper_spec.rb b/spec/helpers/visibility_level_helper_spec.rb
index 3b4d82c65ac..9cbace3cfd0 100644
--- a/spec/helpers/visibility_level_helper_spec.rb
+++ b/spec/helpers/visibility_level_helper_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe VisibilityLevelHelper do
+RSpec.describe VisibilityLevelHelper do
include ProjectForksHelper
let(:project) { build(:project) }
diff --git a/spec/helpers/wiki_helper_spec.rb b/spec/helpers/wiki_helper_spec.rb
index 4b53823aaed..040368b5ebd 100644
--- a/spec/helpers/wiki_helper_spec.rb
+++ b/spec/helpers/wiki_helper_spec.rb
@@ -2,7 +2,39 @@
require 'spec_helper'
-describe WikiHelper do
+RSpec.describe WikiHelper do
+ describe '#wiki_page_title' do
+ let_it_be(:page) { create(:wiki_page) }
+
+ it 'sets the title for the show action' do
+ expect(helper).to receive(:breadcrumb_title).with(page.human_title)
+ expect(helper).to receive(:wiki_breadcrumb_dropdown_links).with(page.slug)
+ expect(helper).to receive(:page_title).with(page.human_title, 'Wiki')
+ expect(helper).to receive(:add_to_breadcrumbs).with('Wiki', helper.wiki_path(page.wiki))
+
+ helper.wiki_page_title(page)
+ end
+
+ it 'sets the title for a custom action' do
+ expect(helper).to receive(:breadcrumb_title).with(page.human_title)
+ expect(helper).to receive(:wiki_breadcrumb_dropdown_links).with(page.slug)
+ expect(helper).to receive(:page_title).with('Edit', page.human_title, 'Wiki')
+ expect(helper).to receive(:add_to_breadcrumbs).with('Wiki', helper.wiki_path(page.wiki))
+
+ helper.wiki_page_title(page, 'Edit')
+ end
+
+ it 'sets the title for an unsaved page' do
+ expect(page).to receive(:persisted?).and_return(false)
+ expect(helper).not_to receive(:breadcrumb_title)
+ expect(helper).not_to receive(:wiki_breadcrumb_dropdown_links)
+ expect(helper).to receive(:page_title).with('Wiki')
+ expect(helper).to receive(:add_to_breadcrumbs).with('Wiki', helper.wiki_path(page.wiki))
+
+ helper.wiki_page_title(page)
+ end
+ end
+
describe '#breadcrumb' do
context 'when the page is at the root level' do
it 'returns the capitalized page name' do
@@ -72,4 +104,24 @@ describe WikiHelper do
expect(helper.wiki_sort_title('unknown')).to eq('Title')
end
end
+
+ describe '#wiki_page_tracking_context' do
+ let_it_be(:page) { create(:wiki_page, title: 'path/to/page 💩', content: '💩', format: :markdown) }
+
+ subject { helper.wiki_page_tracking_context(page) }
+
+ it 'returns the tracking context' do
+ expect(subject).to eq(
+ 'wiki-format' => :markdown,
+ 'wiki-title-size' => 9,
+ 'wiki-content-size' => 4,
+ 'wiki-directory-nest-level' => 2
+ )
+ end
+
+ it 'returns a nest level of zero for toplevel files' do
+ expect(page).to receive(:path).and_return('page')
+ expect(subject).to include('wiki-directory-nest-level' => 0)
+ end
+ end
end
diff --git a/spec/helpers/x509_helper_spec.rb b/spec/helpers/x509_helper_spec.rb
index db3f6158195..4e3e8c8d3f6 100644
--- a/spec/helpers/x509_helper_spec.rb
+++ b/spec/helpers/x509_helper_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe X509Helper do
+RSpec.describe X509Helper do
describe '#x509_subject' do
let(:search_uppercase) { %w[CN OU O] }
let(:search_lowercase) { %w[cn ou o] }
diff --git a/spec/initializers/100_patch_omniauth_saml_spec.rb b/spec/initializers/100_patch_omniauth_saml_spec.rb
index c4d20f79af0..3496eb4d680 100644
--- a/spec/initializers/100_patch_omniauth_saml_spec.rb
+++ b/spec/initializers/100_patch_omniauth_saml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'OmniAuth::Strategies::SAML', type: :strategy do
+RSpec.describe 'OmniAuth::Strategies::SAML', type: :strategy do
let(:idp_sso_target_url) { 'https://login.example.com/idp' }
let(:strategy) { [OmniAuth::Strategies::SAML, { idp_sso_target_url: idp_sso_target_url }] }
diff --git a/spec/initializers/6_validations_spec.rb b/spec/initializers/6_validations_spec.rb
index 248f967311b..b909fc9db0a 100644
--- a/spec/initializers/6_validations_spec.rb
+++ b/spec/initializers/6_validations_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require_relative '../../config/initializers/6_validations.rb'
-describe '6_validations' do
+RSpec.describe '6_validations' do
describe 'validate_storages_config' do
context 'with correct settings' do
before do
diff --git a/spec/initializers/action_mailer_hooks_spec.rb b/spec/initializers/action_mailer_hooks_spec.rb
index 03eee09f737..f04fe804d62 100644
--- a/spec/initializers/action_mailer_hooks_spec.rb
+++ b/spec/initializers/action_mailer_hooks_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'ActionMailer hooks' do
+RSpec.describe 'ActionMailer hooks' do
describe 'smime signature interceptor' do
before do
class_spy(ActionMailer::Base).as_stubbed_const
diff --git a/spec/initializers/actionpack_generate_old_csrf_token_spec.rb b/spec/initializers/actionpack_generate_old_csrf_token_spec.rb
deleted file mode 100644
index 036f52398bb..00000000000
--- a/spec/initializers/actionpack_generate_old_csrf_token_spec.rb
+++ /dev/null
@@ -1,47 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-describe ActionController::Base, 'CSRF token generation patch', type: :controller do # rubocop:disable RSpec/FilePath
- let(:fixed_seed) { SecureRandom.random_bytes(described_class::AUTHENTICITY_TOKEN_LENGTH) }
-
- context 'global_csrf_token feature flag is enabled' do
- it 'generates 6.0.3.1 style CSRF token', :aggregate_failures do
- generated_token = controller.send(:form_authenticity_token)
-
- expect(valid_authenticity_token?(generated_token)).to be_truthy
- expect(compare_with_real_token(generated_token)).to be_falsey
- expect(compare_with_global_token(generated_token)).to be_truthy
- end
- end
-
- context 'global_csrf_token feature flag is disabled' do
- before do
- stub_feature_flags(global_csrf_token: false)
- end
-
- it 'generates 6.0.3 style CSRF token', :aggregate_failures do
- generated_token = controller.send(:form_authenticity_token)
-
- expect(valid_authenticity_token?(generated_token)).to be_truthy
- expect(compare_with_real_token(generated_token)).to be_truthy
- expect(compare_with_global_token(generated_token)).to be_falsey
- end
- end
-
- def compare_with_global_token(token)
- unmasked_token = controller.send :unmask_token, Base64.strict_decode64(token)
-
- controller.send(:compare_with_global_token, unmasked_token, session)
- end
-
- def compare_with_real_token(token)
- unmasked_token = controller.send :unmask_token, Base64.strict_decode64(token)
-
- controller.send(:compare_with_real_token, unmasked_token, session)
- end
-
- def valid_authenticity_token?(token)
- controller.send(:valid_authenticity_token?, session, token)
- end
-end
diff --git a/spec/initializers/active_record_locking_spec.rb b/spec/initializers/active_record_locking_spec.rb
index 5a16aef78e6..e979fa0b793 100644
--- a/spec/initializers/active_record_locking_spec.rb
+++ b/spec/initializers/active_record_locking_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'ActiveRecord locking' do
+RSpec.describe 'ActiveRecord locking' do
let(:issue) { create(:issue) }
shared_examples 'locked model' do
diff --git a/spec/initializers/asset_proxy_setting_spec.rb b/spec/initializers/asset_proxy_setting_spec.rb
index 7eab5de155b..62bb31b2be9 100644
--- a/spec/initializers/asset_proxy_setting_spec.rb
+++ b/spec/initializers/asset_proxy_setting_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Asset proxy settings initialization' do
+RSpec.describe 'Asset proxy settings initialization' do
describe '#asset_proxy' do
it 'defaults to disabled' do
expect(Banzai::Filter::AssetProxyFilter).to receive(:initialize_settings)
diff --git a/spec/initializers/attr_encrypted_no_db_connection_spec.rb b/spec/initializers/attr_encrypted_no_db_connection_spec.rb
index 14e0e1f2167..ad3d14ed7d4 100644
--- a/spec/initializers/attr_encrypted_no_db_connection_spec.rb
+++ b/spec/initializers/attr_encrypted_no_db_connection_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'GitLab monkey-patches to AttrEncrypted' do
+RSpec.describe 'GitLab monkey-patches to AttrEncrypted' do
describe '#attribute_instance_methods_as_symbols_available?' do
it 'returns false' do
expect(ActiveRecord::Base.__send__(:attribute_instance_methods_as_symbols_available?)).to be_falsy
diff --git a/spec/initializers/attr_encrypted_thread_safe_spec.rb b/spec/initializers/attr_encrypted_thread_safe_spec.rb
index 096b8b196b4..e79b7c716ec 100644
--- a/spec/initializers/attr_encrypted_thread_safe_spec.rb
+++ b/spec/initializers/attr_encrypted_thread_safe_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe AttrEncrypted do
+RSpec.describe AttrEncrypted do
describe '#encrypted_attributes' do
subject do
Class.new(ActiveRecord::Base) do
diff --git a/spec/initializers/database_config_spec.rb b/spec/initializers/database_config_spec.rb
index 7c0b280fdaf..29d499efcd3 100644
--- a/spec/initializers/database_config_spec.rb
+++ b/spec/initializers/database_config_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Database config initializer' do
+RSpec.describe 'Database config initializer' do
subject do
load Rails.root.join('config/initializers/database_config.rb')
end
diff --git a/spec/initializers/direct_upload_support_spec.rb b/spec/initializers/direct_upload_support_spec.rb
index 7db40f4b5ab..aa77c0905c9 100644
--- a/spec/initializers/direct_upload_support_spec.rb
+++ b/spec/initializers/direct_upload_support_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Direct upload support' do
+RSpec.describe 'Direct upload support' do
subject do
load Rails.root.join('config/initializers/direct_upload_support.rb')
end
diff --git a/spec/initializers/doorkeeper_spec.rb b/spec/initializers/doorkeeper_spec.rb
index 47c196cb3a3..164225a00b2 100644
--- a/spec/initializers/doorkeeper_spec.rb
+++ b/spec/initializers/doorkeeper_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require_relative '../../config/initializers/doorkeeper'
-describe Doorkeeper.configuration do
+RSpec.describe Doorkeeper.configuration do
describe '#default_scopes' do
it 'matches Gitlab::Auth::DEFAULT_SCOPES' do
expect(subject.default_scopes).to eq Gitlab::Auth::DEFAULT_SCOPES
diff --git a/spec/initializers/fog_google_https_private_urls_spec.rb b/spec/initializers/fog_google_https_private_urls_spec.rb
index 8a0d7ad8f15..4825525a3d8 100644
--- a/spec/initializers/fog_google_https_private_urls_spec.rb
+++ b/spec/initializers/fog_google_https_private_urls_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Fog::Storage::GoogleXML::File', :fog_requests do
+RSpec.describe 'Fog::Storage::GoogleXML::File', :fog_requests do
let(:storage) do
Fog.mock!
Fog::Storage.new(
diff --git a/spec/initializers/hangouts_chat_http_override_spec.rb b/spec/initializers/hangouts_chat_http_override_spec.rb
index 0eee891799f..42236c8c853 100644
--- a/spec/initializers/hangouts_chat_http_override_spec.rb
+++ b/spec/initializers/hangouts_chat_http_override_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'HangoutsChat::Sender Gitlab::HTTP override' do
+RSpec.describe 'HangoutsChat::Sender Gitlab::HTTP override' do
describe 'HangoutsChat::Sender::HTTP#post' do
it 'calls Gitlab::HTTP.post with default protection settings' do
webhook_url = 'https://example.gitlab.com'
diff --git a/spec/initializers/lograge_spec.rb b/spec/initializers/lograge_spec.rb
index 9e5eab4fc6b..de722764bf4 100644
--- a/spec/initializers/lograge_spec.rb
+++ b/spec/initializers/lograge_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'lograge', type: :request do
+RSpec.describe 'lograge', type: :request do
let(:headers) { { 'X-Request-ID' => 'new-correlation-id' } }
let(:large_params) do
@@ -152,5 +152,35 @@ describe 'lograge', type: :request do
expect(log_data['etag_route']).to eq(etag_route)
end
end
+
+ context 'with transaction' do
+ let(:transaction) { Gitlab::Metrics::WebTransaction.new({}) }
+
+ before do
+ allow(Gitlab::Metrics::Transaction).to receive(:current).and_return(transaction)
+ end
+
+ context 'when RequestStore is enabled', :request_store do
+ context 'with db payload' do
+ it 'includes db counters', :request_store do
+ ActiveRecord::Base.connection.execute('SELECT pg_sleep(0.1);')
+ subscriber.process_action(event)
+
+ expect(log_data).to include("db_count" => 1, "db_write_count" => 0, "db_cached_count" => 0)
+ end
+ end
+ end
+
+ context 'when RequestStore is disabled' do
+ context 'with db payload' do
+ it 'does not include db counters' do
+ ActiveRecord::Base.connection.execute('SELECT pg_sleep(0.1);')
+ subscriber.process_action(event)
+
+ expect(log_data).not_to include("db_count" => 1, "db_write_count" => 0, "db_cached_count" => 0)
+ end
+ end
+ end
+ end
end
end
diff --git a/spec/initializers/mail_encoding_patch_spec.rb b/spec/initializers/mail_encoding_patch_spec.rb
index 41074af3503..efacaf6b1b4 100644
--- a/spec/initializers/mail_encoding_patch_spec.rb
+++ b/spec/initializers/mail_encoding_patch_spec.rb
@@ -5,7 +5,7 @@ require 'fast_spec_helper'
require 'mail'
require_relative '../../config/initializers/mail_encoding_patch.rb'
-describe 'Mail quoted-printable transfer encoding patch and Unicode characters' do
+RSpec.describe 'Mail quoted-printable transfer encoding patch and Unicode characters' do
shared_examples 'email encoding' do |email|
it 'enclosing in a new object does not change the encoded original' do
new_email = Mail.new(email)
diff --git a/spec/initializers/rest-client-hostname_override_spec.rb b/spec/initializers/rest-client-hostname_override_spec.rb
index 7e36656ba1c..187f18a6b1d 100644
--- a/spec/initializers/rest-client-hostname_override_spec.rb
+++ b/spec/initializers/rest-client-hostname_override_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'rest-client dns rebinding protection' do
+RSpec.describe 'rest-client dns rebinding protection' do
it_behaves_like 'a request using Gitlab::UrlBlocker' do
let(:http_method) { :get }
let(:url_blocked_error_class) { ArgumentError }
diff --git a/spec/initializers/secret_token_spec.rb b/spec/initializers/secret_token_spec.rb
index b7979144c72..362371e0962 100644
--- a/spec/initializers/secret_token_spec.rb
+++ b/spec/initializers/secret_token_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require_relative '../../config/initializers/01_secret_token'
-describe 'create_tokens' do
+RSpec.describe 'create_tokens' do
include StubENV
let(:secrets) { ActiveSupport::OrderedOptions.new }
@@ -19,6 +19,30 @@ describe 'create_tokens' do
allow(self).to receive(:exit)
end
+ describe 'ensure acknowledged secrets in any installations' do
+ let(:acknowledged_secrets) do
+ %w[secret_key_base otp_key_base db_key_base openid_connect_signing_key]
+ end
+
+ it 'does not allow to add a new secret without a proper handling' do
+ create_tokens
+
+ secrets_hash = YAML.load_file(Rails.root.join('config/secrets.yml'))
+
+ secrets_hash.each do |environment, secrets|
+ new_secrets = secrets.keys - acknowledged_secrets
+
+ expect(new_secrets).to be_empty,
+ <<~EOS
+ CAUTION:
+ It looks like you have just added new secret(s) #{new_secrets.inspect} to the secrets.yml.
+ Please read the development guide for GitLab secrets at doc/development/application_secrets.md before you proceed this change.
+ If you're absolutely sure that the change is safe, please add the new secrets to the 'acknowledged_secrets' in order to silence this warning.
+ EOS
+ end
+ end
+ end
+
context 'setting secret keys' do
context 'when none of the secrets exist' do
before do
diff --git a/spec/initializers/settings_spec.rb b/spec/initializers/settings_spec.rb
index 6cb45b4c86b..71ea12a41aa 100644
--- a/spec/initializers/settings_spec.rb
+++ b/spec/initializers/settings_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require_relative '../../config/initializers/1_settings' unless defined?(Settings)
-describe Settings do
+RSpec.describe Settings do
describe '#ldap' do
it 'can be accessed with dot syntax all the way down' do
expect(Gitlab.config.ldap.servers.main.label).to eq('ldap')
diff --git a/spec/initializers/trusted_proxies_spec.rb b/spec/initializers/trusted_proxies_spec.rb
index a2bd0ff9f1c..2786f034969 100644
--- a/spec/initializers/trusted_proxies_spec.rb
+++ b/spec/initializers/trusted_proxies_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'trusted_proxies' do
+RSpec.describe 'trusted_proxies' do
context 'with default config' do
before do
set_trusted_proxies([])
diff --git a/spec/initializers/zz_metrics_spec.rb b/spec/initializers/zz_metrics_spec.rb
index f41a807f1eb..ad93c30ee22 100644
--- a/spec/initializers/zz_metrics_spec.rb
+++ b/spec/initializers/zz_metrics_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'instrument_classes' do
+RSpec.describe 'instrument_classes' do
let(:config) { double(:config) }
before do
diff --git a/spec/javascripts/boards/components/board_spec.js b/spec/javascripts/boards/components/board_spec.js
deleted file mode 100644
index 86a2a10b7a0..00000000000
--- a/spec/javascripts/boards/components/board_spec.js
+++ /dev/null
@@ -1,251 +0,0 @@
-import Vue from 'vue';
-import Board from '~/boards/components/board';
-import List from '~/boards/models/list';
-
-describe('Board component', () => {
- let vm;
-
- const createComponent = ({ gon = {}, collapsed = false, listType = 'backlog' } = {}) => {
- if (Object.prototype.hasOwnProperty.call(gon, 'current_user_id')) {
- window.gon = gon;
- } else {
- window.gon = {};
- }
- const el = document.createElement('div');
- document.body.appendChild(el);
-
- vm = new Board({
- propsData: {
- boardId: '1',
- disabled: false,
- issueLinkBase: '/',
- rootPath: '/',
- list: new List({
- id: 1,
- position: 0,
- title: 'test',
- list_type: listType,
- collapsed,
- }),
- },
- }).$mount(el);
- };
-
- const setUpTests = (done, opts = {}) => {
- loadFixtures('boards/show.html');
-
- createComponent(opts);
-
- Vue.nextTick(done);
- };
-
- const cleanUpTests = spy => {
- if (spy) {
- spy.calls.reset();
- }
-
- vm.$destroy();
-
- // remove the component from the DOM
- document.querySelector('.board').remove();
-
- localStorage.removeItem(`${vm.uniqueKey}.expanded`);
- };
-
- describe('List', () => {
- it('board is expandable when list type is closed', () => {
- expect(new List({ id: 1, list_type: 'closed' }).isExpandable).toBe(true);
- });
-
- it('board is expandable when list type is label', () => {
- expect(new List({ id: 1, list_type: 'closed' }).isExpandable).toBe(true);
- });
-
- it('board is not expandable when list type is blank', () => {
- expect(new List({ id: 1, list_type: 'blank' }).isExpandable).toBe(false);
- });
- });
-
- describe('when clicking the header', () => {
- beforeEach(done => {
- setUpTests(done);
- });
-
- afterEach(() => {
- cleanUpTests();
- });
-
- it('does not collapse', done => {
- vm.list.isExpanded = true;
- vm.$el.querySelector('.board-header').click();
-
- Vue.nextTick()
- .then(() => {
- expect(vm.$el.classList.contains('is-collapsed')).toBe(false);
- })
- .then(done)
- .catch(done.fail);
- });
- });
-
- describe('when clicking the collapse icon', () => {
- beforeEach(done => {
- setUpTests(done);
- });
-
- afterEach(() => {
- cleanUpTests();
- });
-
- it('collapses', done => {
- Vue.nextTick()
- .then(() => {
- vm.$el.querySelector('.board-title-caret').click();
- })
- .then(() => {
- expect(vm.$el.classList.contains('is-collapsed')).toBe(true);
- })
- .then(done)
- .catch(done.fail);
- });
- });
-
- describe('when clicking the expand icon', () => {
- beforeEach(done => {
- setUpTests(done);
- });
-
- afterEach(() => {
- cleanUpTests();
- });
-
- it('expands', done => {
- vm.list.isExpanded = false;
-
- Vue.nextTick()
- .then(() => {
- vm.$el.querySelector('.board-title-caret').click();
- })
- .then(() => {
- expect(vm.$el.classList.contains('is-collapsed')).toBe(false);
- })
- .then(done)
- .catch(done.fail);
- });
- });
-
- describe('when collapsed is false', () => {
- beforeEach(done => {
- setUpTests(done);
- });
-
- afterEach(() => {
- cleanUpTests();
- });
-
- it('is expanded when collapsed is false', () => {
- expect(vm.list.isExpanded).toBe(true);
- expect(vm.$el.classList.contains('is-collapsed')).toBe(false);
- });
- });
-
- describe('when list type is blank', () => {
- beforeEach(done => {
- setUpTests(done, { listType: 'blank' });
- });
-
- afterEach(() => {
- cleanUpTests();
- });
-
- it('does not render add issue button when list type is blank', done => {
- Vue.nextTick(() => {
- expect(vm.$el.querySelector('.issue-count-badge-add-button')).toBeNull();
-
- done();
- });
- });
- });
-
- describe('when list type is backlog', () => {
- beforeEach(done => {
- setUpTests(done);
- });
-
- afterEach(() => {
- cleanUpTests();
- });
-
- it('board is expandable', () => {
- expect(vm.$el.classList.contains('is-expandable')).toBe(true);
- });
- });
-
- describe('when logged in', () => {
- let spy;
-
- beforeEach(done => {
- spy = spyOn(List.prototype, 'update');
- setUpTests(done, { gon: { current_user_id: 1 } });
- });
-
- afterEach(() => {
- cleanUpTests(spy);
- });
-
- it('calls list update', done => {
- Vue.nextTick()
- .then(() => {
- vm.$el.querySelector('.board-title-caret').click();
- })
- .then(() => {
- expect(vm.list.update).toHaveBeenCalledTimes(1);
- })
- .then(done)
- .catch(done.fail);
- });
- });
-
- describe('when logged out', () => {
- let spy;
- beforeEach(done => {
- spy = spyOn(List.prototype, 'update');
- setUpTests(done, { collapsed: false });
- });
-
- afterEach(() => {
- cleanUpTests(spy);
- });
-
- // can only be one or the other cant toggle window.gon.current_user_id states.
- it('clicking on the caret does not call list update', done => {
- Vue.nextTick()
- .then(() => {
- vm.$el.querySelector('.board-title-caret').click();
- })
- .then(() => {
- expect(vm.list.update).toHaveBeenCalledTimes(0);
- })
- .then(done)
- .catch(done.fail);
- });
-
- it('sets expanded to be the opposite of its value when toggleExpanded is called', done => {
- const expanded = true;
- vm.list.isExpanded = expanded;
- vm.toggleExpanded();
-
- Vue.nextTick()
- .then(() => {
- expect(vm.list.isExpanded).toBe(!expanded);
- expect(localStorage.getItem(`${vm.uniqueKey}.expanded`)).toBe(String(!expanded));
- })
- .then(done)
- .catch(done.fail);
- });
-
- it('does render add issue button', () => {
- expect(vm.$el.querySelector('.issue-count-badge-add-button')).not.toBeNull();
- });
- });
-});
diff --git a/spec/javascripts/boards/mock_data.js b/spec/javascripts/boards/mock_data.js
deleted file mode 100644
index 8b39ad1abb4..00000000000
--- a/spec/javascripts/boards/mock_data.js
+++ /dev/null
@@ -1 +0,0 @@
-export * from '../../frontend/boards/mock_data';
diff --git a/spec/javascripts/fly_out_nav_browser_spec.js b/spec/javascripts/fly_out_nav_browser_spec.js
new file mode 100644
index 00000000000..f84cee72042
--- /dev/null
+++ b/spec/javascripts/fly_out_nav_browser_spec.js
@@ -0,0 +1,333 @@
+// this file can't be migrated to jest because it relies on the browser to perform integration tests:
+// (specifically getClientBoundingRect and mouse movements)
+// see: https://gitlab.com/groups/gitlab-org/-/epics/895#what-if-theres-a-karma-spec-which-is-simply-unmovable-to-jest-ie-it-is-dependent-on-a-running-browser-environment
+
+import { GlBreakpointInstance } from '@gitlab/ui/dist/utils';
+import {
+ calculateTop,
+ showSubLevelItems,
+ canShowSubItems,
+ canShowActiveSubItems,
+ mouseEnterTopItems,
+ mouseLeaveTopItem,
+ getOpenMenu,
+ setOpenMenu,
+ mousePos,
+ getHideSubItemsInterval,
+ documentMouseMove,
+ getHeaderHeight,
+ setSidebar,
+ subItemsMouseLeave,
+} from '~/fly_out_nav';
+import { SIDEBAR_COLLAPSED_CLASS } from '~/contextual_sidebar';
+
+describe('Fly out sidebar navigation', () => {
+ let el;
+ let breakpointSize = 'lg';
+
+ beforeEach(() => {
+ el = document.createElement('div');
+ el.style.position = 'relative';
+ document.body.appendChild(el);
+
+ spyOn(GlBreakpointInstance, 'getBreakpointSize').and.callFake(() => breakpointSize);
+
+ setOpenMenu(null);
+ });
+
+ afterEach(() => {
+ document.body.innerHTML = '';
+ breakpointSize = 'lg';
+ mousePos.length = 0;
+
+ setSidebar(null);
+ });
+
+ describe('calculateTop', () => {
+ it('returns boundingRect top', () => {
+ const boundingRect = {
+ top: 100,
+ height: 100,
+ };
+
+ expect(calculateTop(boundingRect, 100)).toBe(100);
+ });
+
+ it('returns boundingRect - bottomOverflow', () => {
+ const boundingRect = {
+ top: window.innerHeight - 50,
+ height: 100,
+ };
+
+ expect(calculateTop(boundingRect, 100)).toBe(window.innerHeight - 50);
+ });
+ });
+
+ describe('getHideSubItemsInterval', () => {
+ beforeEach(() => {
+ el.innerHTML =
+ '<div class="sidebar-sub-level-items" style="position: fixed; top: 0; left: 100px; height: 150px;"></div>';
+ });
+
+ it('returns 0 if currentOpenMenu is nil', () => {
+ expect(getHideSubItemsInterval()).toBe(0);
+ });
+
+ it('returns 0 if mousePos is empty', () => {
+ expect(getHideSubItemsInterval()).toBe(0);
+ });
+
+ it('returns 0 when mouse above sub-items', () => {
+ showSubLevelItems(el);
+ documentMouseMove({
+ clientX: el.getBoundingClientRect().left,
+ clientY: el.getBoundingClientRect().top,
+ });
+ documentMouseMove({
+ clientX: el.getBoundingClientRect().left,
+ clientY: el.getBoundingClientRect().top - 50,
+ });
+
+ expect(getHideSubItemsInterval()).toBe(0);
+ });
+
+ it('returns 0 when mouse is below sub-items', () => {
+ const subItems = el.querySelector('.sidebar-sub-level-items');
+
+ showSubLevelItems(el);
+ documentMouseMove({
+ clientX: el.getBoundingClientRect().left,
+ clientY: el.getBoundingClientRect().top,
+ });
+ documentMouseMove({
+ clientX: el.getBoundingClientRect().left,
+ clientY: el.getBoundingClientRect().top - subItems.getBoundingClientRect().height + 50,
+ });
+
+ expect(getHideSubItemsInterval()).toBe(0);
+ });
+
+ it('returns 300 when mouse is moved towards sub-items', () => {
+ documentMouseMove({
+ clientX: el.getBoundingClientRect().left,
+ clientY: el.getBoundingClientRect().top,
+ });
+ showSubLevelItems(el);
+ documentMouseMove({
+ clientX: el.getBoundingClientRect().left + 20,
+ clientY: el.getBoundingClientRect().top + 10,
+ });
+
+ expect(getHideSubItemsInterval()).toBe(300);
+ });
+ });
+
+ describe('mouseLeaveTopItem', () => {
+ beforeEach(() => {
+ spyOn(el.classList, 'remove');
+ });
+
+ it('removes is-over class if currentOpenMenu is null', () => {
+ mouseLeaveTopItem(el);
+
+ expect(el.classList.remove).toHaveBeenCalledWith('is-over');
+ });
+
+ it('removes is-over class if currentOpenMenu is null & there are sub-items', () => {
+ el.innerHTML = '<div class="sidebar-sub-level-items" style="position: absolute;"></div>';
+
+ mouseLeaveTopItem(el);
+
+ expect(el.classList.remove).toHaveBeenCalledWith('is-over');
+ });
+
+ it('does not remove is-over class if currentOpenMenu is the passed in sub-items', () => {
+ el.innerHTML = '<div class="sidebar-sub-level-items" style="position: absolute;"></div>';
+
+ setOpenMenu(el.querySelector('.sidebar-sub-level-items'));
+ mouseLeaveTopItem(el);
+
+ expect(el.classList.remove).not.toHaveBeenCalled();
+ });
+ });
+
+ describe('mouseEnterTopItems', () => {
+ beforeEach(() => {
+ el.innerHTML =
+ '<div class="sidebar-sub-level-items" style="position: absolute; top: 0; left: 100px; height: 200px;"></div>';
+ });
+
+ it('shows sub-items after 0ms if no menu is open', done => {
+ mouseEnterTopItems(el);
+
+ expect(getHideSubItemsInterval()).toBe(0);
+
+ setTimeout(() => {
+ expect(el.querySelector('.sidebar-sub-level-items').style.display).toBe('block');
+
+ done();
+ });
+ });
+
+ it('shows sub-items after 300ms if a menu is currently open', done => {
+ documentMouseMove({
+ clientX: el.getBoundingClientRect().left,
+ clientY: el.getBoundingClientRect().top,
+ });
+
+ setOpenMenu(el.querySelector('.sidebar-sub-level-items'));
+
+ documentMouseMove({
+ clientX: el.getBoundingClientRect().left + 20,
+ clientY: el.getBoundingClientRect().top + 10,
+ });
+
+ mouseEnterTopItems(el, 0);
+
+ expect(getHideSubItemsInterval()).toBe(300);
+
+ setTimeout(() => {
+ expect(el.querySelector('.sidebar-sub-level-items').style.display).toBe('block');
+
+ done();
+ });
+ });
+ });
+
+ describe('showSubLevelItems', () => {
+ beforeEach(() => {
+ el.innerHTML = '<div class="sidebar-sub-level-items" style="position: absolute;"></div>';
+ });
+
+ it('adds is-over class to el', () => {
+ spyOn(el.classList, 'add');
+
+ showSubLevelItems(el);
+
+ expect(el.classList.add).toHaveBeenCalledWith('is-over');
+ });
+
+ it('does not show sub-items on mobile', () => {
+ breakpointSize = 'xs';
+
+ showSubLevelItems(el);
+
+ expect(el.querySelector('.sidebar-sub-level-items').style.display).not.toBe('block');
+ });
+
+ it('shows sub-items', () => {
+ showSubLevelItems(el);
+
+ expect(el.querySelector('.sidebar-sub-level-items').style.display).toBe('block');
+ });
+
+ it('shows collapsed only sub-items if icon only sidebar', () => {
+ const subItems = el.querySelector('.sidebar-sub-level-items');
+ const sidebar = document.createElement('div');
+ sidebar.classList.add(SIDEBAR_COLLAPSED_CLASS);
+ subItems.classList.add('is-fly-out-only');
+
+ setSidebar(sidebar);
+
+ showSubLevelItems(el);
+
+ expect(el.querySelector('.sidebar-sub-level-items').style.display).toBe('block');
+ });
+
+ it('does not show collapsed only sub-items if icon only sidebar', () => {
+ const subItems = el.querySelector('.sidebar-sub-level-items');
+ subItems.classList.add('is-fly-out-only');
+
+ showSubLevelItems(el);
+
+ expect(subItems.style.display).not.toBe('block');
+ });
+
+ it('sets transform of sub-items', () => {
+ const sidebar = document.createElement('div');
+ const subItems = el.querySelector('.sidebar-sub-level-items');
+
+ sidebar.style.width = '200px';
+
+ document.body.appendChild(sidebar);
+
+ setSidebar(sidebar);
+ showSubLevelItems(el);
+
+ expect(subItems.style.transform).toBe(
+ `translate3d(200px, ${Math.floor(el.getBoundingClientRect().top) -
+ getHeaderHeight()}px, 0px)`,
+ );
+ });
+
+ it('sets is-above when element is above', () => {
+ const subItems = el.querySelector('.sidebar-sub-level-items');
+ subItems.style.height = `${window.innerHeight + el.offsetHeight}px`;
+ el.style.top = `${window.innerHeight - el.offsetHeight}px`;
+
+ spyOn(subItems.classList, 'add');
+
+ showSubLevelItems(el);
+
+ expect(subItems.classList.add).toHaveBeenCalledWith('is-above');
+ });
+ });
+
+ describe('canShowSubItems', () => {
+ it('returns true if on desktop size', () => {
+ expect(canShowSubItems()).toBeTruthy();
+ });
+
+ it('returns false if on mobile size', () => {
+ breakpointSize = 'xs';
+
+ expect(canShowSubItems()).toBeFalsy();
+ });
+ });
+
+ describe('canShowActiveSubItems', () => {
+ it('returns true by default', () => {
+ expect(canShowActiveSubItems(el)).toBeTruthy();
+ });
+
+ it('returns false when active & expanded sidebar', () => {
+ const sidebar = document.createElement('div');
+ el.classList.add('active');
+
+ setSidebar(sidebar);
+
+ expect(canShowActiveSubItems(el)).toBeFalsy();
+ });
+
+ it('returns true when active & collapsed sidebar', () => {
+ const sidebar = document.createElement('div');
+ sidebar.classList.add(SIDEBAR_COLLAPSED_CLASS);
+ el.classList.add('active');
+
+ setSidebar(sidebar);
+
+ expect(canShowActiveSubItems(el)).toBeTruthy();
+ });
+ });
+
+ describe('subItemsMouseLeave', () => {
+ beforeEach(() => {
+ el.innerHTML = '<div class="sidebar-sub-level-items" style="position: absolute;"></div>';
+
+ setOpenMenu(el.querySelector('.sidebar-sub-level-items'));
+ });
+
+ it('hides subMenu if element is not hovered', () => {
+ subItemsMouseLeave(el);
+
+ expect(getOpenMenu()).toBeNull();
+ });
+
+ it('does not hide subMenu if element is hovered', () => {
+ el.classList.add('is-over');
+ subItemsMouseLeave(el);
+
+ expect(getOpenMenu()).not.toBeNull();
+ });
+ });
+});
diff --git a/spec/javascripts/fly_out_nav_spec.js b/spec/javascripts/fly_out_nav_spec.js
deleted file mode 100644
index afcf132bea3..00000000000
--- a/spec/javascripts/fly_out_nav_spec.js
+++ /dev/null
@@ -1,329 +0,0 @@
-import { GlBreakpointInstance } from '@gitlab/ui/dist/utils';
-import {
- calculateTop,
- showSubLevelItems,
- canShowSubItems,
- canShowActiveSubItems,
- mouseEnterTopItems,
- mouseLeaveTopItem,
- getOpenMenu,
- setOpenMenu,
- mousePos,
- getHideSubItemsInterval,
- documentMouseMove,
- getHeaderHeight,
- setSidebar,
- subItemsMouseLeave,
-} from '~/fly_out_nav';
-import { SIDEBAR_COLLAPSED_CLASS } from '~/contextual_sidebar';
-
-describe('Fly out sidebar navigation', () => {
- let el;
- let breakpointSize = 'lg';
-
- beforeEach(() => {
- el = document.createElement('div');
- el.style.position = 'relative';
- document.body.appendChild(el);
-
- spyOn(GlBreakpointInstance, 'getBreakpointSize').and.callFake(() => breakpointSize);
-
- setOpenMenu(null);
- });
-
- afterEach(() => {
- document.body.innerHTML = '';
- breakpointSize = 'lg';
- mousePos.length = 0;
-
- setSidebar(null);
- });
-
- describe('calculateTop', () => {
- it('returns boundingRect top', () => {
- const boundingRect = {
- top: 100,
- height: 100,
- };
-
- expect(calculateTop(boundingRect, 100)).toBe(100);
- });
-
- it('returns boundingRect - bottomOverflow', () => {
- const boundingRect = {
- top: window.innerHeight - 50,
- height: 100,
- };
-
- expect(calculateTop(boundingRect, 100)).toBe(window.innerHeight - 50);
- });
- });
-
- describe('getHideSubItemsInterval', () => {
- beforeEach(() => {
- el.innerHTML =
- '<div class="sidebar-sub-level-items" style="position: fixed; top: 0; left: 100px; height: 150px;"></div>';
- });
-
- it('returns 0 if currentOpenMenu is nil', () => {
- expect(getHideSubItemsInterval()).toBe(0);
- });
-
- it('returns 0 if mousePos is empty', () => {
- expect(getHideSubItemsInterval()).toBe(0);
- });
-
- it('returns 0 when mouse above sub-items', () => {
- showSubLevelItems(el);
- documentMouseMove({
- clientX: el.getBoundingClientRect().left,
- clientY: el.getBoundingClientRect().top,
- });
- documentMouseMove({
- clientX: el.getBoundingClientRect().left,
- clientY: el.getBoundingClientRect().top - 50,
- });
-
- expect(getHideSubItemsInterval()).toBe(0);
- });
-
- it('returns 0 when mouse is below sub-items', () => {
- const subItems = el.querySelector('.sidebar-sub-level-items');
-
- showSubLevelItems(el);
- documentMouseMove({
- clientX: el.getBoundingClientRect().left,
- clientY: el.getBoundingClientRect().top,
- });
- documentMouseMove({
- clientX: el.getBoundingClientRect().left,
- clientY: el.getBoundingClientRect().top - subItems.getBoundingClientRect().height + 50,
- });
-
- expect(getHideSubItemsInterval()).toBe(0);
- });
-
- it('returns 300 when mouse is moved towards sub-items', () => {
- documentMouseMove({
- clientX: el.getBoundingClientRect().left,
- clientY: el.getBoundingClientRect().top,
- });
- showSubLevelItems(el);
- documentMouseMove({
- clientX: el.getBoundingClientRect().left + 20,
- clientY: el.getBoundingClientRect().top + 10,
- });
-
- expect(getHideSubItemsInterval()).toBe(300);
- });
- });
-
- describe('mouseLeaveTopItem', () => {
- beforeEach(() => {
- spyOn(el.classList, 'remove');
- });
-
- it('removes is-over class if currentOpenMenu is null', () => {
- mouseLeaveTopItem(el);
-
- expect(el.classList.remove).toHaveBeenCalledWith('is-over');
- });
-
- it('removes is-over class if currentOpenMenu is null & there are sub-items', () => {
- el.innerHTML = '<div class="sidebar-sub-level-items" style="position: absolute;"></div>';
-
- mouseLeaveTopItem(el);
-
- expect(el.classList.remove).toHaveBeenCalledWith('is-over');
- });
-
- it('does not remove is-over class if currentOpenMenu is the passed in sub-items', () => {
- el.innerHTML = '<div class="sidebar-sub-level-items" style="position: absolute;"></div>';
-
- setOpenMenu(el.querySelector('.sidebar-sub-level-items'));
- mouseLeaveTopItem(el);
-
- expect(el.classList.remove).not.toHaveBeenCalled();
- });
- });
-
- describe('mouseEnterTopItems', () => {
- beforeEach(() => {
- el.innerHTML =
- '<div class="sidebar-sub-level-items" style="position: absolute; top: 0; left: 100px; height: 200px;"></div>';
- });
-
- it('shows sub-items after 0ms if no menu is open', done => {
- mouseEnterTopItems(el);
-
- expect(getHideSubItemsInterval()).toBe(0);
-
- setTimeout(() => {
- expect(el.querySelector('.sidebar-sub-level-items').style.display).toBe('block');
-
- done();
- });
- });
-
- it('shows sub-items after 300ms if a menu is currently open', done => {
- documentMouseMove({
- clientX: el.getBoundingClientRect().left,
- clientY: el.getBoundingClientRect().top,
- });
-
- setOpenMenu(el.querySelector('.sidebar-sub-level-items'));
-
- documentMouseMove({
- clientX: el.getBoundingClientRect().left + 20,
- clientY: el.getBoundingClientRect().top + 10,
- });
-
- mouseEnterTopItems(el, 0);
-
- expect(getHideSubItemsInterval()).toBe(300);
-
- setTimeout(() => {
- expect(el.querySelector('.sidebar-sub-level-items').style.display).toBe('block');
-
- done();
- });
- });
- });
-
- describe('showSubLevelItems', () => {
- beforeEach(() => {
- el.innerHTML = '<div class="sidebar-sub-level-items" style="position: absolute;"></div>';
- });
-
- it('adds is-over class to el', () => {
- spyOn(el.classList, 'add');
-
- showSubLevelItems(el);
-
- expect(el.classList.add).toHaveBeenCalledWith('is-over');
- });
-
- it('does not show sub-items on mobile', () => {
- breakpointSize = 'xs';
-
- showSubLevelItems(el);
-
- expect(el.querySelector('.sidebar-sub-level-items').style.display).not.toBe('block');
- });
-
- it('shows sub-items', () => {
- showSubLevelItems(el);
-
- expect(el.querySelector('.sidebar-sub-level-items').style.display).toBe('block');
- });
-
- it('shows collapsed only sub-items if icon only sidebar', () => {
- const subItems = el.querySelector('.sidebar-sub-level-items');
- const sidebar = document.createElement('div');
- sidebar.classList.add(SIDEBAR_COLLAPSED_CLASS);
- subItems.classList.add('is-fly-out-only');
-
- setSidebar(sidebar);
-
- showSubLevelItems(el);
-
- expect(el.querySelector('.sidebar-sub-level-items').style.display).toBe('block');
- });
-
- it('does not show collapsed only sub-items if icon only sidebar', () => {
- const subItems = el.querySelector('.sidebar-sub-level-items');
- subItems.classList.add('is-fly-out-only');
-
- showSubLevelItems(el);
-
- expect(subItems.style.display).not.toBe('block');
- });
-
- it('sets transform of sub-items', () => {
- const sidebar = document.createElement('div');
- const subItems = el.querySelector('.sidebar-sub-level-items');
-
- sidebar.style.width = '200px';
-
- document.body.appendChild(sidebar);
-
- setSidebar(sidebar);
- showSubLevelItems(el);
-
- expect(subItems.style.transform).toBe(
- `translate3d(200px, ${Math.floor(el.getBoundingClientRect().top) -
- getHeaderHeight()}px, 0px)`,
- );
- });
-
- it('sets is-above when element is above', () => {
- const subItems = el.querySelector('.sidebar-sub-level-items');
- subItems.style.height = `${window.innerHeight + el.offsetHeight}px`;
- el.style.top = `${window.innerHeight - el.offsetHeight}px`;
-
- spyOn(subItems.classList, 'add');
-
- showSubLevelItems(el);
-
- expect(subItems.classList.add).toHaveBeenCalledWith('is-above');
- });
- });
-
- describe('canShowSubItems', () => {
- it('returns true if on desktop size', () => {
- expect(canShowSubItems()).toBeTruthy();
- });
-
- it('returns false if on mobile size', () => {
- breakpointSize = 'xs';
-
- expect(canShowSubItems()).toBeFalsy();
- });
- });
-
- describe('canShowActiveSubItems', () => {
- it('returns true by default', () => {
- expect(canShowActiveSubItems(el)).toBeTruthy();
- });
-
- it('returns false when active & expanded sidebar', () => {
- const sidebar = document.createElement('div');
- el.classList.add('active');
-
- setSidebar(sidebar);
-
- expect(canShowActiveSubItems(el)).toBeFalsy();
- });
-
- it('returns true when active & collapsed sidebar', () => {
- const sidebar = document.createElement('div');
- sidebar.classList.add(SIDEBAR_COLLAPSED_CLASS);
- el.classList.add('active');
-
- setSidebar(sidebar);
-
- expect(canShowActiveSubItems(el)).toBeTruthy();
- });
- });
-
- describe('subItemsMouseLeave', () => {
- beforeEach(() => {
- el.innerHTML = '<div class="sidebar-sub-level-items" style="position: absolute;"></div>';
-
- setOpenMenu(el.querySelector('.sidebar-sub-level-items'));
- });
-
- it('hides subMenu if element is not hovered', () => {
- subItemsMouseLeave(el);
-
- expect(getOpenMenu()).toBeNull();
- });
-
- it('does not hide subMenu if element is hovered', () => {
- el.classList.add('is-over');
- subItemsMouseLeave(el);
-
- expect(getOpenMenu()).not.toBeNull();
- });
- });
-});
diff --git a/spec/javascripts/helpers/class_spec_helper.js b/spec/javascripts/helpers/class_spec_helper.js
deleted file mode 100644
index 7a60d33b471..00000000000
--- a/spec/javascripts/helpers/class_spec_helper.js
+++ /dev/null
@@ -1,9 +0,0 @@
-export default class ClassSpecHelper {
- static itShouldBeAStaticMethod(base, method) {
- return it('should be a static method', () => {
- expect(Object.prototype.hasOwnProperty.call(base, method)).toBeTruthy();
- });
- }
-}
-
-window.ClassSpecHelper = ClassSpecHelper;
diff --git a/spec/javascripts/helpers/filtered_search_spec_helper.js b/spec/javascripts/helpers/filtered_search_spec_helper.js
deleted file mode 100644
index de17518ea51..00000000000
--- a/spec/javascripts/helpers/filtered_search_spec_helper.js
+++ /dev/null
@@ -1 +0,0 @@
-export { default } from '../../frontend/helpers/filtered_search_spec_helper';
diff --git a/spec/javascripts/helpers/index.js b/spec/javascripts/helpers/index.js
deleted file mode 100644
index d2c5caf0bdb..00000000000
--- a/spec/javascripts/helpers/index.js
+++ /dev/null
@@ -1,3 +0,0 @@
-import mountComponent, { mountComponentWithStore } from './vue_mount_component_helper';
-
-export { mountComponent, mountComponentWithStore };
diff --git a/spec/javascripts/helpers/init_vue_mr_page_helper.js b/spec/javascripts/helpers/init_vue_mr_page_helper.js
deleted file mode 100644
index 1ba08199764..00000000000
--- a/spec/javascripts/helpers/init_vue_mr_page_helper.js
+++ /dev/null
@@ -1,46 +0,0 @@
-import MockAdapter from 'axios-mock-adapter';
-import initMRPage from '~/mr_notes/index';
-import axios from '~/lib/utils/axios_utils';
-import { userDataMock, notesDataMock, noteableDataMock } from '../../frontend/notes/mock_data';
-import diffFileMockData from '../../frontend/diffs/mock_data/diff_file';
-
-export default function initVueMRPage() {
- const mrTestEl = document.createElement('div');
- mrTestEl.className = 'js-merge-request-test';
- document.body.appendChild(mrTestEl);
-
- const diffsAppEndpoint = '/diffs/app/endpoint';
- const diffsAppProjectPath = 'testproject';
- const mrEl = document.createElement('div');
- mrEl.className = 'merge-request fixture-mr';
- mrEl.setAttribute('data-mr-action', 'diffs');
- mrTestEl.appendChild(mrEl);
-
- const mrDiscussionsEl = document.createElement('div');
- mrDiscussionsEl.id = 'js-vue-mr-discussions';
- mrDiscussionsEl.setAttribute('data-current-user-data', JSON.stringify(userDataMock));
- mrDiscussionsEl.setAttribute('data-noteable-data', JSON.stringify(noteableDataMock));
- mrDiscussionsEl.setAttribute('data-notes-data', JSON.stringify(notesDataMock));
- mrDiscussionsEl.setAttribute('data-noteable-type', 'merge-request');
- mrTestEl.appendChild(mrDiscussionsEl);
-
- const discussionCounterEl = document.createElement('div');
- discussionCounterEl.id = 'js-vue-discussion-counter';
- mrTestEl.appendChild(discussionCounterEl);
-
- const diffsAppEl = document.createElement('div');
- diffsAppEl.id = 'js-diffs-app';
- diffsAppEl.setAttribute('data-endpoint', diffsAppEndpoint);
- diffsAppEl.setAttribute('data-project-path', diffsAppProjectPath);
- diffsAppEl.setAttribute('data-current-user-data', JSON.stringify(userDataMock));
- mrTestEl.appendChild(diffsAppEl);
-
- const mock = new MockAdapter(axios);
- mock.onGet(diffsAppEndpoint).reply(200, {
- branch_name: 'foo',
- diff_files: [diffFileMockData],
- });
-
- initMRPage();
- return mock;
-}
diff --git a/spec/javascripts/helpers/locale_helper.js b/spec/javascripts/helpers/locale_helper.js
deleted file mode 100644
index 80047b06003..00000000000
--- a/spec/javascripts/helpers/locale_helper.js
+++ /dev/null
@@ -1,11 +0,0 @@
-/* eslint-disable import/prefer-default-export */
-
-export const setLanguage = languageCode => {
- const htmlElement = document.querySelector('html');
-
- if (languageCode) {
- htmlElement.setAttribute('lang', languageCode);
- } else {
- htmlElement.removeAttribute('lang');
- }
-};
diff --git a/spec/javascripts/helpers/set_timeout_promise_helper.js b/spec/javascripts/helpers/set_timeout_promise_helper.js
deleted file mode 100644
index 47087619187..00000000000
--- a/spec/javascripts/helpers/set_timeout_promise_helper.js
+++ /dev/null
@@ -1,4 +0,0 @@
-export default (time = 0) =>
- new Promise(resolve => {
- setTimeout(resolve, time);
- });
diff --git a/spec/javascripts/helpers/text_helper.js b/spec/javascripts/helpers/text_helper.js
deleted file mode 100644
index e0fe18e5560..00000000000
--- a/spec/javascripts/helpers/text_helper.js
+++ /dev/null
@@ -1,18 +0,0 @@
-/**
- * Replaces line break with an empty space
- * @param {*} data
- */
-export const removeBreakLine = data => data.replace(/\r?\n|\r/g, ' ');
-
-/**
- * Removes line breaks, spaces and trims the given text
- * @param {String} str
- * @returns {String}
- */
-export const trimText = str =>
- str
- .replace(/\r?\n|\r/g, '')
- .replace(/\s\s+/g, ' ')
- .trim();
-
-export const removeWhitespace = str => str.replace(/\s\s+/g, ' ');
diff --git a/spec/javascripts/helpers/tracking_helper.js b/spec/javascripts/helpers/tracking_helper.js
deleted file mode 100644
index ea322de46f4..00000000000
--- a/spec/javascripts/helpers/tracking_helper.js
+++ /dev/null
@@ -1,5 +0,0 @@
-// No new code should be added to this file. Instead, modify the
-// file this one re-exports from. For more detail about why, see:
-// https://gitlab.com/gitlab-org/gitlab-foss/merge_requests/31349
-
-export * from '../../frontend/helpers/tracking_helper';
diff --git a/spec/javascripts/helpers/user_mock_data_helper.js b/spec/javascripts/helpers/user_mock_data_helper.js
deleted file mode 100644
index 6999fa1f8a1..00000000000
--- a/spec/javascripts/helpers/user_mock_data_helper.js
+++ /dev/null
@@ -1,14 +0,0 @@
-export default {
- createNumberRandomUsers(numberUsers) {
- const users = [];
- for (let i = 0; i < numberUsers; i += 1) {
- users.push({
- avatar: 'https://gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon',
- id: i + 1,
- name: `GitLab User ${i}`,
- username: `gitlab${i}`,
- });
- }
- return users;
- },
-};
diff --git a/spec/javascripts/helpers/vue_mount_component_helper.js b/spec/javascripts/helpers/vue_mount_component_helper.js
deleted file mode 100644
index c1857115b61..00000000000
--- a/spec/javascripts/helpers/vue_mount_component_helper.js
+++ /dev/null
@@ -1,2 +0,0 @@
-export { default } from '../../frontend/helpers/vue_mount_component_helper';
-export * from '../../frontend/helpers/vue_mount_component_helper';
diff --git a/spec/javascripts/helpers/vue_test_utils_helper.js b/spec/javascripts/helpers/vue_test_utils_helper.js
deleted file mode 100644
index 1f5d8716dd3..00000000000
--- a/spec/javascripts/helpers/vue_test_utils_helper.js
+++ /dev/null
@@ -1,5 +0,0 @@
-// No new code should be added to this file. Instead, modify the
-// file this one re-exports from. For more detail about why, see:
-// https://gitlab.com/gitlab-org/gitlab-foss/merge_requests/31349
-
-export * from '../../frontend/helpers/vue_test_utils_helper';
diff --git a/spec/javascripts/helpers/vuex_action_helper.js b/spec/javascripts/helpers/vuex_action_helper.js
deleted file mode 100644
index c5de31a4138..00000000000
--- a/spec/javascripts/helpers/vuex_action_helper.js
+++ /dev/null
@@ -1,102 +0,0 @@
-const noop = () => {};
-
-/**
- * Helper for testing action with expected mutations inspired in
- * https://vuex.vuejs.org/en/testing.html
- *
- * @param {Function} action to be tested
- * @param {Object} payload will be provided to the action
- * @param {Object} state will be provided to the action
- * @param {Array} [expectedMutations=[]] mutations expected to be committed
- * @param {Array} [expectedActions=[]] actions expected to be dispatched
- * @param {Function} [done=noop] to be executed after the tests
- * @return {Promise}
- *
- * @example
- * testAction(
- * actions.actionName, // action
- * { }, // mocked payload
- * state, //state
- * // expected mutations
- * [
- * { type: types.MUTATION}
- * { type: types.MUTATION_1, payload: jasmine.any(Number)}
- * ],
- * // expected actions
- * [
- * { type: 'actionName', payload: {param: 'foobar'}},
- * { type: 'actionName1'}
- * ]
- * done,
- * );
- *
- * @example
- * testAction(
- * actions.actionName, // action
- * { }, // mocked payload
- * state, //state
- * [ { type: types.MUTATION} ], // expected mutations
- * [], // expected actions
- * ).then(done)
- * .catch(done.fail);
- */
-export default (
- action,
- payload,
- state,
- expectedMutations = [],
- expectedActions = [],
- done = noop,
-) => {
- const mutations = [];
- const actions = [];
-
- // mock commit
- const commit = (type, mutationPayload) => {
- const mutation = { type };
-
- if (typeof mutationPayload !== 'undefined') {
- mutation.payload = mutationPayload;
- }
-
- mutations.push(mutation);
- };
-
- // mock dispatch
- const dispatch = (type, actionPayload) => {
- const dispatchedAction = { type };
-
- if (typeof actionPayload !== 'undefined') {
- dispatchedAction.payload = actionPayload;
- }
-
- actions.push(dispatchedAction);
- };
-
- const validateResults = () => {
- expect({
- mutations,
- actions,
- }).toEqual({
- mutations: expectedMutations,
- actions: expectedActions,
- });
- done();
- };
-
- const result = action(
- { commit, state, dispatch, rootState: state, rootGetters: state, getters: state },
- payload,
- );
-
- return new Promise(setImmediate)
- .then(() => result)
- .catch(error => {
- validateResults();
- throw error;
- })
- .then(data => {
- validateResults();
- return data;
- });
-};
diff --git a/spec/javascripts/helpers/wait_for_promises.js b/spec/javascripts/helpers/wait_for_promises.js
deleted file mode 100644
index 1d2b53fc770..00000000000
--- a/spec/javascripts/helpers/wait_for_promises.js
+++ /dev/null
@@ -1 +0,0 @@
-export default () => new Promise(resolve => requestAnimationFrame(resolve));
diff --git a/spec/javascripts/jobs/mock_data.js b/spec/javascripts/jobs/mock_data.js
deleted file mode 100644
index f0ba46c058a..00000000000
--- a/spec/javascripts/jobs/mock_data.js
+++ /dev/null
@@ -1,2 +0,0 @@
-export { default } from '../../frontend/jobs/mock_data';
-export * from '../../frontend/jobs/mock_data';
diff --git a/spec/javascripts/matchers.js b/spec/javascripts/matchers.js
index 7d1921cabcf..5b7b7dc78b1 100644
--- a/spec/javascripts/matchers.js
+++ b/spec/javascripts/matchers.js
@@ -1,61 +1,6 @@
import pixelmatch from 'pixelmatch';
export default {
- toContainText: () => ({
- compare(vm, text) {
- if (!(vm.$el instanceof HTMLElement)) {
- throw new Error('vm.$el is not a DOM element!');
- }
-
- const result = {
- pass: vm.$el.innerText.includes(text),
- };
- return result;
- },
- }),
- toHaveSpriteIcon: () => ({
- compare(element, iconName) {
- if (!iconName) {
- throw new Error('toHaveSpriteIcon is missing iconName argument!');
- }
-
- if (!(element instanceof HTMLElement)) {
- throw new Error(`${element} is not a DOM element!`);
- }
-
- const iconReferences = [].slice.apply(element.querySelectorAll('svg use'));
- const matchingIcon = iconReferences.find(reference =>
- reference.getAttribute('xlink:href').endsWith(`#${iconName}`),
- );
- const result = {
- pass: Boolean(matchingIcon),
- };
-
- if (result.pass) {
- result.message = `${element.outerHTML} contains the sprite icon "${iconName}"!`;
- } else {
- result.message = `${element.outerHTML} does not contain the sprite icon "${iconName}"!`;
-
- const existingIcons = iconReferences.map(reference => {
- const iconUrl = reference.getAttribute('xlink:href');
- return `"${iconUrl.replace(/^.+#/, '')}"`;
- });
- if (existingIcons.length > 0) {
- result.message += ` (only found ${existingIcons.join(',')})`;
- }
- }
-
- return result;
- },
- }),
- toRender: () => ({
- compare(vm) {
- const result = {
- pass: vm.$el.nodeType !== Node.COMMENT_NODE,
- };
- return result;
- },
- }),
toImageDiffEqual: () => {
const getImageData = img => {
const canvas = document.createElement('canvas');
diff --git a/spec/javascripts/monitoring/components/dashboard_resize_browser_spec.js b/spec/javascripts/monitoring/components/dashboard_resize_browser_spec.js
index 4416dbd014a..bbcdc0b879f 100644
--- a/spec/javascripts/monitoring/components/dashboard_resize_browser_spec.js
+++ b/spec/javascripts/monitoring/components/dashboard_resize_browser_spec.js
@@ -11,8 +11,8 @@ import MockAdapter from 'axios-mock-adapter';
import Dashboard from '~/monitoring/components/dashboard.vue';
import { createStore } from '~/monitoring/stores';
import axios from '~/lib/utils/axios_utils';
-import { mockApiEndpoint, propsData } from '../mock_data';
-import { metricsDashboardPayload } from '../fixture_data';
+import { mockApiEndpoint } from '../mock_data';
+import { metricsDashboardPayload, dashboardProps } from '../fixture_data';
import { setupStoreWithData } from '../store_utils';
const localVue = createLocalVue();
@@ -56,7 +56,7 @@ describe('Dashboard', () => {
component = new DashboardComponent({
el: document.querySelector('.prometheus-graphs'),
propsData: {
- ...propsData,
+ ...dashboardProps,
hasMetrics: true,
showPanels: true,
},
diff --git a/spec/javascripts/vue_shared/components/tooltip_on_truncate_browser_spec.js b/spec/javascripts/vue_shared/components/tooltip_on_truncate_browser_spec.js
new file mode 100644
index 00000000000..da964a2d5b9
--- /dev/null
+++ b/spec/javascripts/vue_shared/components/tooltip_on_truncate_browser_spec.js
@@ -0,0 +1,240 @@
+// this file can't be migrated to jest because it relies on the browser to perform integration tests:
+// (specifically testing around css properties `overflow` and `white-space`)
+// see: https://gitlab.com/groups/gitlab-org/-/epics/895#what-if-theres-a-karma-spec-which-is-simply-unmovable-to-jest-ie-it-is-dependent-on-a-running-browser-environment
+
+import { mount, shallowMount } from '@vue/test-utils';
+import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate.vue';
+
+const TEXT_SHORT = 'lorem';
+const TEXT_LONG = 'lorem-ipsum-dolar-sit-amit-consectur-adipiscing-elit-sed-do';
+
+const TEXT_TRUNCATE = 'white-space: nowrap; overflow:hidden;';
+const STYLE_NORMAL = `${TEXT_TRUNCATE} display: inline-block; max-width: 1000px;`; // does not overflows
+const STYLE_OVERFLOWED = `${TEXT_TRUNCATE} display: inline-block; max-width: 50px;`; // overflowed when text is long
+
+const createElementWithStyle = (style, content) => `<a href="#" style="${style}">${content}</a>`;
+
+describe('TooltipOnTruncate component', () => {
+ let wrapper;
+ let parent;
+
+ const createComponent = ({ propsData, ...options } = {}) => {
+ wrapper = shallowMount(TooltipOnTruncate, {
+ attachToDocument: true,
+ propsData: {
+ ...propsData,
+ },
+ attrs: {
+ style: STYLE_OVERFLOWED,
+ },
+ ...options,
+ });
+ };
+
+ const createWrappedComponent = ({ propsData, ...options }) => {
+ // set a parent around the tested component
+ parent = mount(
+ {
+ props: {
+ title: { default: '' },
+ },
+ template: `
+ <TooltipOnTruncate :title="title" truncate-target="child" style="${STYLE_OVERFLOWED}">
+ <div>{{title}}</div>
+ </TooltipOnTruncate>
+ `,
+ components: {
+ TooltipOnTruncate,
+ },
+ },
+ {
+ propsData: { ...propsData },
+ attachToDocument: true,
+ ...options,
+ },
+ );
+
+ wrapper = parent.find(TooltipOnTruncate);
+ };
+
+ const hasTooltip = () => wrapper.classes('js-show-tooltip');
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ describe('with default target', () => {
+ it('renders tooltip if truncated', () => {
+ createComponent({
+ propsData: {
+ title: TEXT_LONG,
+ },
+ slots: {
+ default: [TEXT_LONG],
+ },
+ });
+
+ return wrapper.vm.$nextTick().then(() => {
+ expect(hasTooltip()).toBe(true);
+ expect(wrapper.attributes('data-original-title')).toEqual(TEXT_LONG);
+ expect(wrapper.attributes('data-placement')).toEqual('top');
+ });
+ });
+
+ it('does not render tooltip if normal', () => {
+ createComponent({
+ propsData: {
+ title: TEXT_SHORT,
+ },
+ slots: {
+ default: [TEXT_SHORT],
+ },
+ });
+
+ return wrapper.vm.$nextTick().then(() => {
+ expect(hasTooltip()).toBe(false);
+ });
+ });
+ });
+
+ describe('with child target', () => {
+ it('renders tooltip if truncated', () => {
+ createComponent({
+ attrs: {
+ style: STYLE_NORMAL,
+ },
+ propsData: {
+ title: TEXT_LONG,
+ truncateTarget: 'child',
+ },
+ slots: {
+ default: createElementWithStyle(STYLE_OVERFLOWED, TEXT_LONG),
+ },
+ });
+
+ return wrapper.vm.$nextTick().then(() => {
+ expect(hasTooltip()).toBe(true);
+ });
+ });
+
+ it('does not render tooltip if normal', () => {
+ createComponent({
+ propsData: {
+ truncateTarget: 'child',
+ },
+ slots: {
+ default: createElementWithStyle(STYLE_NORMAL, TEXT_LONG),
+ },
+ });
+
+ return wrapper.vm.$nextTick().then(() => {
+ expect(hasTooltip()).toBe(false);
+ });
+ });
+ });
+
+ describe('with fn target', () => {
+ it('renders tooltip if truncated', () => {
+ createComponent({
+ attrs: {
+ style: STYLE_NORMAL,
+ },
+ propsData: {
+ title: TEXT_LONG,
+ truncateTarget: el => el.childNodes[1],
+ },
+ slots: {
+ default: [
+ createElementWithStyle('', TEXT_LONG),
+ createElementWithStyle(STYLE_OVERFLOWED, TEXT_LONG),
+ ],
+ },
+ });
+
+ return wrapper.vm.$nextTick().then(() => {
+ expect(hasTooltip()).toBe(true);
+ });
+ });
+ });
+
+ describe('placement', () => {
+ it('sets data-placement when tooltip is rendered', () => {
+ const placement = 'bottom';
+
+ createComponent({
+ propsData: {
+ placement,
+ },
+ attrs: {
+ style: STYLE_OVERFLOWED,
+ },
+ slots: {
+ default: TEXT_LONG,
+ },
+ });
+
+ return wrapper.vm.$nextTick().then(() => {
+ expect(hasTooltip()).toBe(true);
+ expect(wrapper.attributes('data-placement')).toEqual(placement);
+ });
+ });
+ });
+
+ describe('updates when title and slot content changes', () => {
+ describe('is initialized with a long text', () => {
+ beforeEach(() => {
+ createWrappedComponent({
+ propsData: { title: TEXT_LONG },
+ });
+ return parent.vm.$nextTick();
+ });
+
+ it('renders tooltip', () => {
+ expect(hasTooltip()).toBe(true);
+ expect(wrapper.attributes('data-original-title')).toEqual(TEXT_LONG);
+ expect(wrapper.attributes('data-placement')).toEqual('top');
+ });
+
+ it('does not render tooltip after updated to a short text', () => {
+ parent.setProps({
+ title: TEXT_SHORT,
+ });
+
+ return wrapper.vm
+ .$nextTick()
+ .then(() => wrapper.vm.$nextTick()) // wait 2 times to get an updated slot
+ .then(() => {
+ expect(hasTooltip()).toBe(false);
+ });
+ });
+ });
+
+ describe('is initialized with a short text', () => {
+ beforeEach(() => {
+ createWrappedComponent({
+ propsData: { title: TEXT_SHORT },
+ });
+ return wrapper.vm.$nextTick();
+ });
+
+ it('does not render tooltip', () => {
+ expect(hasTooltip()).toBe(false);
+ });
+
+ it('renders tooltip after updated to a long text', () => {
+ parent.setProps({
+ title: TEXT_LONG,
+ });
+
+ return wrapper.vm
+ .$nextTick()
+ .then(() => wrapper.vm.$nextTick()) // wait 2 times to get an updated slot
+ .then(() => {
+ expect(hasTooltip()).toBe(true);
+ expect(wrapper.attributes('data-original-title')).toEqual(TEXT_LONG);
+ expect(wrapper.attributes('data-placement')).toEqual('top');
+ });
+ });
+ });
+ });
+});
diff --git a/spec/javascripts/vue_shared/components/tooltip_on_truncate_spec.js b/spec/javascripts/vue_shared/components/tooltip_on_truncate_spec.js
deleted file mode 100644
index 5f432f2a1b5..00000000000
--- a/spec/javascripts/vue_shared/components/tooltip_on_truncate_spec.js
+++ /dev/null
@@ -1,236 +0,0 @@
-import { mount, shallowMount } from '@vue/test-utils';
-import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate.vue';
-
-const TEXT_SHORT = 'lorem';
-const TEXT_LONG = 'lorem-ipsum-dolar-sit-amit-consectur-adipiscing-elit-sed-do';
-
-const TEXT_TRUNCATE = 'white-space: nowrap; overflow:hidden;';
-const STYLE_NORMAL = `${TEXT_TRUNCATE} display: inline-block; max-width: 1000px;`; // does not overflows
-const STYLE_OVERFLOWED = `${TEXT_TRUNCATE} display: inline-block; max-width: 50px;`; // overflowed when text is long
-
-const createElementWithStyle = (style, content) => `<a href="#" style="${style}">${content}</a>`;
-
-describe('TooltipOnTruncate component', () => {
- let wrapper;
- let parent;
-
- const createComponent = ({ propsData, ...options } = {}) => {
- wrapper = shallowMount(TooltipOnTruncate, {
- attachToDocument: true,
- propsData: {
- ...propsData,
- },
- attrs: {
- style: STYLE_OVERFLOWED,
- },
- ...options,
- });
- };
-
- const createWrappedComponent = ({ propsData, ...options }) => {
- // set a parent around the tested component
- parent = mount(
- {
- props: {
- title: { default: '' },
- },
- template: `
- <TooltipOnTruncate :title="title" truncate-target="child" style="${STYLE_OVERFLOWED}">
- <div>{{title}}</div>
- </TooltipOnTruncate>
- `,
- components: {
- TooltipOnTruncate,
- },
- },
- {
- propsData: { ...propsData },
- attachToDocument: true,
- ...options,
- },
- );
-
- wrapper = parent.find(TooltipOnTruncate);
- };
-
- const hasTooltip = () => wrapper.classes('js-show-tooltip');
-
- afterEach(() => {
- wrapper.destroy();
- });
-
- describe('with default target', () => {
- it('renders tooltip if truncated', () => {
- createComponent({
- propsData: {
- title: TEXT_LONG,
- },
- slots: {
- default: [TEXT_LONG],
- },
- });
-
- return wrapper.vm.$nextTick().then(() => {
- expect(hasTooltip()).toBe(true);
- expect(wrapper.attributes('data-original-title')).toEqual(TEXT_LONG);
- expect(wrapper.attributes('data-placement')).toEqual('top');
- });
- });
-
- it('does not render tooltip if normal', () => {
- createComponent({
- propsData: {
- title: TEXT_SHORT,
- },
- slots: {
- default: [TEXT_SHORT],
- },
- });
-
- return wrapper.vm.$nextTick().then(() => {
- expect(hasTooltip()).toBe(false);
- });
- });
- });
-
- describe('with child target', () => {
- it('renders tooltip if truncated', () => {
- createComponent({
- attrs: {
- style: STYLE_NORMAL,
- },
- propsData: {
- title: TEXT_LONG,
- truncateTarget: 'child',
- },
- slots: {
- default: createElementWithStyle(STYLE_OVERFLOWED, TEXT_LONG),
- },
- });
-
- return wrapper.vm.$nextTick().then(() => {
- expect(hasTooltip()).toBe(true);
- });
- });
-
- it('does not render tooltip if normal', () => {
- createComponent({
- propsData: {
- truncateTarget: 'child',
- },
- slots: {
- default: createElementWithStyle(STYLE_NORMAL, TEXT_LONG),
- },
- });
-
- return wrapper.vm.$nextTick().then(() => {
- expect(hasTooltip()).toBe(false);
- });
- });
- });
-
- describe('with fn target', () => {
- it('renders tooltip if truncated', () => {
- createComponent({
- attrs: {
- style: STYLE_NORMAL,
- },
- propsData: {
- title: TEXT_LONG,
- truncateTarget: el => el.childNodes[1],
- },
- slots: {
- default: [
- createElementWithStyle('', TEXT_LONG),
- createElementWithStyle(STYLE_OVERFLOWED, TEXT_LONG),
- ],
- },
- });
-
- return wrapper.vm.$nextTick().then(() => {
- expect(hasTooltip()).toBe(true);
- });
- });
- });
-
- describe('placement', () => {
- it('sets data-placement when tooltip is rendered', () => {
- const placement = 'bottom';
-
- createComponent({
- propsData: {
- placement,
- },
- attrs: {
- style: STYLE_OVERFLOWED,
- },
- slots: {
- default: TEXT_LONG,
- },
- });
-
- return wrapper.vm.$nextTick().then(() => {
- expect(hasTooltip()).toBe(true);
- expect(wrapper.attributes('data-placement')).toEqual(placement);
- });
- });
- });
-
- describe('updates when title and slot content changes', () => {
- describe('is initialized with a long text', () => {
- beforeEach(() => {
- createWrappedComponent({
- propsData: { title: TEXT_LONG },
- });
- return parent.vm.$nextTick();
- });
-
- it('renders tooltip', () => {
- expect(hasTooltip()).toBe(true);
- expect(wrapper.attributes('data-original-title')).toEqual(TEXT_LONG);
- expect(wrapper.attributes('data-placement')).toEqual('top');
- });
-
- it('does not render tooltip after updated to a short text', () => {
- parent.setProps({
- title: TEXT_SHORT,
- });
-
- return wrapper.vm
- .$nextTick()
- .then(() => wrapper.vm.$nextTick()) // wait 2 times to get an updated slot
- .then(() => {
- expect(hasTooltip()).toBe(false);
- });
- });
- });
-
- describe('is initialized with a short text', () => {
- beforeEach(() => {
- createWrappedComponent({
- propsData: { title: TEXT_SHORT },
- });
- return wrapper.vm.$nextTick();
- });
-
- it('does not render tooltip', () => {
- expect(hasTooltip()).toBe(false);
- });
-
- it('renders tooltip after updated to a long text', () => {
- parent.setProps({
- title: TEXT_LONG,
- });
-
- return wrapper.vm
- .$nextTick()
- .then(() => wrapper.vm.$nextTick()) // wait 2 times to get an updated slot
- .then(() => {
- expect(hasTooltip()).toBe(true);
- expect(wrapper.attributes('data-original-title')).toEqual(TEXT_LONG);
- expect(wrapper.attributes('data-placement')).toEqual('top');
- });
- });
- });
- });
-});
diff --git a/spec/lib/after_commit_queue_spec.rb b/spec/lib/after_commit_queue_spec.rb
index 8e9dfd90338..ca383808bfc 100644
--- a/spec/lib/after_commit_queue_spec.rb
+++ b/spec/lib/after_commit_queue_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe AfterCommitQueue do
+RSpec.describe AfterCommitQueue do
it 'runs after transaction is committed' do
called = false
test_proc = proc { called = true }
diff --git a/spec/lib/api/api_spec.rb b/spec/lib/api/api_spec.rb
index c83d068ca50..cf08eaa7653 100644
--- a/spec/lib/api/api_spec.rb
+++ b/spec/lib/api/api_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::API do
+RSpec.describe API::API do
describe '.prefix' do
it 'has a prefix defined' do
expect(described_class.prefix).to eq :api
diff --git a/spec/lib/api/entities/branch_spec.rb b/spec/lib/api/entities/branch_spec.rb
index 604f56c0cb2..e07b431964c 100644
--- a/spec/lib/api/entities/branch_spec.rb
+++ b/spec/lib/api/entities/branch_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Entities::Branch do
+RSpec.describe API::Entities::Branch do
describe '#as_json' do
subject { entity.as_json }
diff --git a/spec/lib/api/entities/deploy_key_spec.rb b/spec/lib/api/entities/deploy_key_spec.rb
index 704dabae63b..6427d6eac8f 100644
--- a/spec/lib/api/entities/deploy_key_spec.rb
+++ b/spec/lib/api/entities/deploy_key_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Entities::DeployKey do
+RSpec.describe API::Entities::DeployKey do
describe '#as_json' do
subject { entity.as_json }
diff --git a/spec/lib/api/entities/deploy_keys_project_spec.rb b/spec/lib/api/entities/deploy_keys_project_spec.rb
index a357467d7ce..57ffdc55736 100644
--- a/spec/lib/api/entities/deploy_keys_project_spec.rb
+++ b/spec/lib/api/entities/deploy_keys_project_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Entities::DeployKeysProject do
+RSpec.describe API::Entities::DeployKeysProject do
describe '#as_json' do
subject { entity.as_json }
diff --git a/spec/lib/api/entities/design_management/design_spec.rb b/spec/lib/api/entities/design_management/design_spec.rb
index 50ca3b43c6a..fe449e3e9bc 100644
--- a/spec/lib/api/entities/design_management/design_spec.rb
+++ b/spec/lib/api/entities/design_management/design_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Entities::DesignManagement::Design do
+RSpec.describe API::Entities::DesignManagement::Design do
let_it_be(:design) { create(:design) }
let(:entity) { described_class.new(design, request: double) }
diff --git a/spec/lib/api/entities/job_request/image_spec.rb b/spec/lib/api/entities/job_request/image_spec.rb
index 092c181ae9c..f13eab6a752 100644
--- a/spec/lib/api/entities/job_request/image_spec.rb
+++ b/spec/lib/api/entities/job_request/image_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Entities::JobRequest::Image do
+RSpec.describe API::Entities::JobRequest::Image do
let(:ports) { [{ number: 80, protocol: 'http', name: 'name' }]}
let(:image) { double(name: 'image_name', entrypoint: ['foo'], ports: ports)}
let(:entity) { described_class.new(image) }
diff --git a/spec/lib/api/entities/job_request/port_spec.rb b/spec/lib/api/entities/job_request/port_spec.rb
index 40ab4cd6231..4820c4a691b 100644
--- a/spec/lib/api/entities/job_request/port_spec.rb
+++ b/spec/lib/api/entities/job_request/port_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ::API::Entities::JobRequest::Port do
+RSpec.describe ::API::Entities::JobRequest::Port do
let(:port) { double(number: 80, protocol: 'http', name: 'name')}
let(:entity) { described_class.new(port) }
diff --git a/spec/lib/api/entities/merge_request_approvals_spec.rb b/spec/lib/api/entities/merge_request_approvals_spec.rb
new file mode 100644
index 00000000000..cbbb037100a
--- /dev/null
+++ b/spec/lib/api/entities/merge_request_approvals_spec.rb
@@ -0,0 +1,36 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe API::Entities::MergeRequestApprovals do
+ let(:user) { create(:user) }
+ let(:merge_request) { create(:merge_request) }
+
+ subject { described_class.new(merge_request, current_user: user).as_json }
+
+ before do
+ merge_request.project.add_developer(user)
+ end
+
+ it 'serializes an approved merge request' do
+ create(:approval, merge_request: merge_request, user: user)
+
+ is_expected.to eq({
+ user_has_approved: true,
+ user_can_approve: false,
+ approved: true,
+ approved_by: [{
+ user: API::Entities::UserBasic.new(user).as_json
+ }]
+ })
+ end
+
+ it 'serializes a merge request that is not approved' do
+ is_expected.to eq({
+ user_has_approved: false,
+ user_can_approve: true,
+ approved: false,
+ approved_by: []
+ })
+ end
+end
diff --git a/spec/lib/api/entities/merge_request_basic_spec.rb b/spec/lib/api/entities/merge_request_basic_spec.rb
new file mode 100644
index 00000000000..715fcf4bcdb
--- /dev/null
+++ b/spec/lib/api/entities/merge_request_basic_spec.rb
@@ -0,0 +1,43 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe ::API::Entities::MergeRequestBasic do
+ let_it_be(:user) { create(:user) }
+ let_it_be(:project) { create(:project, :public) }
+ let_it_be(:merge_request) { create(:merge_request) }
+ let_it_be(:labels) { create_list(:label, 3) }
+ let_it_be(:merge_requests) { create_list(:labeled_merge_request, 10, :unique_branches, :with_diffs, labels: labels) }
+
+ # This mimics the behavior of the `Grape::Entity` serializer
+ def present(obj)
+ described_class.new(obj).presented
+ end
+
+ context "with :with_api_entity_associations scope" do
+ let(:scope) { MergeRequest.with_api_entity_associations }
+
+ it "avoids N+1 queries" do
+ query = scope.find(merge_request.id)
+
+ control = ActiveRecord::QueryRecorder.new do
+ present(query).to_json
+ end
+
+ # stub the `head_commit_sha` as it will trigger a
+ # backward compatibility query that is out-of-scope
+ # for this test whenever it is `nil`
+ allow_any_instance_of(MergeRequestDiff).to receive(:head_commit_sha).and_return(Gitlab::Git::BLANK_SHA)
+
+ query = scope.all
+ batch = ActiveRecord::QueryRecorder.new do
+ entities = query.map(&method(:present))
+
+ entities.to_json
+ end
+
+ # The current threshold is 3 query per entity maximum.
+ expect(batch.count).to be_within(3 * query.count).of(control.count)
+ end
+ end
+end
diff --git a/spec/lib/api/entities/nuget/dependency_group_spec.rb b/spec/lib/api/entities/nuget/dependency_group_spec.rb
new file mode 100644
index 00000000000..5a649be846b
--- /dev/null
+++ b/spec/lib/api/entities/nuget/dependency_group_spec.rb
@@ -0,0 +1,50 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe API::Entities::Nuget::DependencyGroup do
+ let(:dependency_group) do
+ {
+ id: 'http://gitlab.com/Sandbox.App/1.0.0.json#dependencygroup',
+ type: 'PackageDependencyGroup',
+ target_framework: 'fwk test',
+ dependencies: [
+ {
+ id: 'http://gitlab.com/Sandbox.App/1.0.0.json#dependency',
+ type: 'PackageDependency',
+ name: 'Dependency',
+ range: '2.0.0'
+ }
+ ]
+ }
+ end
+
+ let(:expected) do
+ {
+ '@id': 'http://gitlab.com/Sandbox.App/1.0.0.json#dependencygroup',
+ '@type': 'PackageDependencyGroup',
+ 'targetFramework': 'fwk test',
+ 'dependencies': [
+ {
+ '@id': 'http://gitlab.com/Sandbox.App/1.0.0.json#dependency',
+ '@type': 'PackageDependency',
+ 'id': 'Dependency',
+ 'range': '2.0.0'
+ }
+ ]
+ }
+ end
+ let(:entity) { described_class.new(dependency_group) }
+
+ subject { entity.as_json }
+
+ it { is_expected.to eq(expected) }
+
+ context 'dependency group without target framework' do
+ let(:dependency_group_with_no_target_framework) { dependency_group.tap { |dg| dg[:target_framework] = nil } }
+ let(:expected_no_target_framework) { expected.except(:targetFramework) }
+ let(:entity) { described_class.new(dependency_group_with_no_target_framework) }
+
+ it { is_expected.to eq(expected_no_target_framework) }
+ end
+end
diff --git a/spec/lib/api/entities/nuget/dependency_spec.rb b/spec/lib/api/entities/nuget/dependency_spec.rb
new file mode 100644
index 00000000000..13897cc91f0
--- /dev/null
+++ b/spec/lib/api/entities/nuget/dependency_spec.rb
@@ -0,0 +1,28 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe API::Entities::Nuget::Dependency do
+ let(:dependency) do
+ {
+ id: 'http://gitlab.com/Sandbox.App/1.0.0.json#dependency',
+ type: 'PackageDependency',
+ name: 'Dependency',
+ range: '2.0.0'
+ }
+ end
+
+ let(:expected) do
+ {
+ '@id': 'http://gitlab.com/Sandbox.App/1.0.0.json#dependency',
+ '@type': 'PackageDependency',
+ 'id': 'Dependency',
+ 'range': '2.0.0'
+ }
+ end
+ let(:entity) { described_class.new(dependency) }
+
+ subject { entity.as_json }
+
+ it { is_expected.to eq(expected) }
+end
diff --git a/spec/lib/api/entities/nuget/metadatum_spec.rb b/spec/lib/api/entities/nuget/metadatum_spec.rb
new file mode 100644
index 00000000000..fe94ea3a69a
--- /dev/null
+++ b/spec/lib/api/entities/nuget/metadatum_spec.rb
@@ -0,0 +1,35 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe API::Entities::Nuget::Metadatum do
+ let(:metadatum) do
+ {
+ project_url: 'http://sandbox.com/project',
+ license_url: 'http://sandbox.com/license',
+ icon_url: 'http://sandbox.com/icon'
+ }
+ end
+ let(:expected) do
+ {
+ 'projectUrl': 'http://sandbox.com/project',
+ 'licenseUrl': 'http://sandbox.com/license',
+ 'iconUrl': 'http://sandbox.com/icon'
+ }
+ end
+ let(:entity) { described_class.new(metadatum) }
+
+ subject { entity.as_json }
+
+ it { is_expected.to eq(expected) }
+
+ %i[project_url license_url icon_url].each do |optional_field|
+ context "metadatum without #{optional_field}" do
+ let(:metadatum_without_a_field) { metadatum.except(optional_field) }
+ let(:expected_without_a_field) { expected.except(optional_field.to_s.camelize(:lower).to_sym) }
+ let(:entity) { described_class.new(metadatum_without_a_field) }
+
+ it { is_expected.to eq(expected_without_a_field) }
+ end
+ end
+end
diff --git a/spec/lib/api/entities/nuget/package_metadata_catalog_entry_spec.rb b/spec/lib/api/entities/nuget/package_metadata_catalog_entry_spec.rb
new file mode 100644
index 00000000000..c422b51bf3b
--- /dev/null
+++ b/spec/lib/api/entities/nuget/package_metadata_catalog_entry_spec.rb
@@ -0,0 +1,43 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe API::Entities::Nuget::PackageMetadataCatalogEntry do
+ let(:entry) do
+ {
+ json_url: 'http://sandbox.com/json/package',
+ authors: 'Authors',
+ dependency_groups: [],
+ package_name: 'PackageTest',
+ package_version: '1.2.3',
+ tags: 'tag1 tag2 tag3',
+ archive_url: 'http://sandbox.com/archive/package',
+ summary: 'Summary',
+ metadatum: {
+ project_url: 'http://sandbox.com/project',
+ license_url: 'http://sandbox.com/license',
+ icon_url: 'http://sandbox.com/icon'
+ }
+ }
+ end
+
+ let(:expected) do
+ {
+ '@id': 'http://sandbox.com/json/package',
+ 'id': 'PackageTest',
+ 'version': '1.2.3',
+ 'authors': 'Authors',
+ 'dependencyGroups': [],
+ 'tags': 'tag1 tag2 tag3',
+ 'packageContent': 'http://sandbox.com/archive/package',
+ 'summary': 'Summary',
+ 'projectUrl': 'http://sandbox.com/project',
+ 'licenseUrl': 'http://sandbox.com/license',
+ 'iconUrl': 'http://sandbox.com/icon'
+ }
+ end
+
+ subject { described_class.new(entry).as_json }
+
+ it { is_expected.to eq(expected) }
+end
diff --git a/spec/lib/api/entities/nuget/search_result_spec.rb b/spec/lib/api/entities/nuget/search_result_spec.rb
new file mode 100644
index 00000000000..2a760c70224
--- /dev/null
+++ b/spec/lib/api/entities/nuget/search_result_spec.rb
@@ -0,0 +1,57 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe API::Entities::Nuget::SearchResult do
+ let(:search_result) do
+ {
+ type: 'Package',
+ authors: 'Author',
+ name: 'PackageTest',
+ version: '1.2.3',
+ versions: [
+ {
+ json_url: 'http://sandbox.com/json/package',
+ downloads: 100,
+ version: '1.2.3'
+ }
+ ],
+ summary: 'Summary',
+ total_downloads: 100,
+ verified: true,
+ tags: 'tag1 tag2 tag3',
+ metadatum: {
+ project_url: 'http://sandbox.com/project',
+ license_url: 'http://sandbox.com/license',
+ icon_url: 'http://sandbox.com/icon'
+ }
+ }
+ end
+ let(:expected) do
+ {
+ '@type': 'Package',
+ 'authors': 'Author',
+ 'id': 'PackageTest',
+ 'title': 'PackageTest',
+ 'summary': 'Summary',
+ 'totalDownloads': 100,
+ 'verified': true,
+ 'version': '1.2.3',
+ 'tags': 'tag1 tag2 tag3',
+ 'projectUrl': 'http://sandbox.com/project',
+ 'licenseUrl': 'http://sandbox.com/license',
+ 'iconUrl': 'http://sandbox.com/icon',
+ 'versions': [
+ {
+ '@id': 'http://sandbox.com/json/package',
+ 'downloads': 100,
+ 'version': '1.2.3'
+ }
+ ]
+ }
+ end
+
+ subject { described_class.new(search_result).as_json }
+
+ it { is_expected.to eq(expected) }
+end
diff --git a/spec/lib/api/entities/project_import_failed_relation_spec.rb b/spec/lib/api/entities/project_import_failed_relation_spec.rb
index f8330713480..51a684c4564 100644
--- a/spec/lib/api/entities/project_import_failed_relation_spec.rb
+++ b/spec/lib/api/entities/project_import_failed_relation_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Entities::ProjectImportFailedRelation do
+RSpec.describe API::Entities::ProjectImportFailedRelation do
describe '#as_json' do
subject { entity.as_json }
diff --git a/spec/lib/api/entities/project_import_status_spec.rb b/spec/lib/api/entities/project_import_status_spec.rb
index a800d703496..5eda613a6a6 100644
--- a/spec/lib/api/entities/project_import_status_spec.rb
+++ b/spec/lib/api/entities/project_import_status_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Entities::ProjectImportStatus do
+RSpec.describe API::Entities::ProjectImportStatus do
describe '#as_json' do
subject { entity.as_json }
diff --git a/spec/lib/api/entities/project_repository_storage_move_spec.rb b/spec/lib/api/entities/project_repository_storage_move_spec.rb
index 1c38c8231d4..b0102dc376a 100644
--- a/spec/lib/api/entities/project_repository_storage_move_spec.rb
+++ b/spec/lib/api/entities/project_repository_storage_move_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Entities::ProjectRepositoryStorageMove do
+RSpec.describe API::Entities::ProjectRepositoryStorageMove do
describe '#as_json' do
subject { entity.as_json }
diff --git a/spec/lib/api/entities/release_spec.rb b/spec/lib/api/entities/release_spec.rb
index fa9e1e74f9b..d57c283c1f4 100644
--- a/spec/lib/api/entities/release_spec.rb
+++ b/spec/lib/api/entities/release_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Entities::Release do
+RSpec.describe API::Entities::Release do
let_it_be(:project) { create(:project) }
let(:release) { create(:release, project: project) }
let(:evidence) { release.evidences.first }
diff --git a/spec/lib/api/entities/snippet_spec.rb b/spec/lib/api/entities/snippet_spec.rb
index dada0942e49..bcb8c364392 100644
--- a/spec/lib/api/entities/snippet_spec.rb
+++ b/spec/lib/api/entities/snippet_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ::API::Entities::Snippet do
+RSpec.describe ::API::Entities::Snippet do
let_it_be(:user) { create(:user) }
let_it_be(:personal_snippet) { create(:personal_snippet, :repository, author: user ) }
let_it_be(:project_snippet) { create(:project_snippet, :repository, author: user) }
@@ -21,6 +21,16 @@ describe ::API::Entities::Snippet do
it { expect(subject[:visibility]).to eq snippet.visibility }
it { expect(subject).to include(:author) }
+ context 'with snippet_multiple_files feature disabled' do
+ before do
+ stub_feature_flags(snippet_multiple_files: false)
+ end
+
+ it 'does not return files' do
+ expect(subject).not_to include(:files)
+ end
+ end
+
describe 'file_name' do
it 'returns attribute from repository' do
expect(subject[:file_name]).to eq snippet.blobs.first.path
@@ -62,6 +72,49 @@ describe ::API::Entities::Snippet do
end
end
end
+
+ describe 'files' do
+ let(:blob) { snippet.blobs.first }
+ let(:ref) { blob.repository.root_ref }
+
+ context 'when repository does not exist' do
+ it 'does not include the files attribute' do
+ allow(snippet).to receive(:repository_exists?).and_return(false)
+
+ expect(subject).not_to include(:files)
+ end
+ end
+
+ shared_examples 'snippet files' do
+ let(:file) { subject[:files].first }
+
+ it 'returns all snippet files' do
+ expect(subject[:files].count).to eq snippet.blobs.count
+ end
+
+ it 'has the file path' do
+ expect(file[:path]).to eq blob.path
+ end
+
+ it 'has the raw url' do
+ expect(file[:raw_url]).to match(raw_url)
+ end
+ end
+
+ context 'with PersonalSnippet' do
+ it_behaves_like 'snippet files' do
+ let(:snippet) { personal_snippet }
+ let(:raw_url) { "/-/snippets/#{snippet.id}/raw/#{ref}/#{blob.path}" }
+ end
+ end
+
+ context 'with ProjectSnippet' do
+ it_behaves_like 'snippet files' do
+ let(:snippet) { project_snippet }
+ let(:raw_url) { "#{snippet.project.full_path}/-/snippets/#{snippet.id}/raw/#{ref}/#{blob.path}" }
+ end
+ end
+ end
end
context 'with PersonalSnippet' do
diff --git a/spec/lib/api/entities/ssh_key_spec.rb b/spec/lib/api/entities/ssh_key_spec.rb
index 25a0fecfb75..768ad416fbe 100644
--- a/spec/lib/api/entities/ssh_key_spec.rb
+++ b/spec/lib/api/entities/ssh_key_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Entities::SSHKey do
+RSpec.describe API::Entities::SSHKey do
describe '#as_json' do
subject { entity.as_json }
diff --git a/spec/lib/api/entities/user_spec.rb b/spec/lib/api/entities/user_spec.rb
index 20524b197e0..99ffe0eb925 100644
--- a/spec/lib/api/entities/user_spec.rb
+++ b/spec/lib/api/entities/user_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Entities::User do
+RSpec.describe API::Entities::User do
let(:user) { create(:user) }
let(:current_user) { create(:user) }
diff --git a/spec/lib/api/helpers/common_helpers_spec.rb b/spec/lib/api/helpers/common_helpers_spec.rb
new file mode 100644
index 00000000000..5162d2f1000
--- /dev/null
+++ b/spec/lib/api/helpers/common_helpers_spec.rb
@@ -0,0 +1,51 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe API::Helpers::CommonHelpers do
+ include Rack::Test::Methods
+
+ subject do
+ Class.new(Grape::API) do
+ helpers API::Helpers::CommonHelpers
+
+ before do
+ coerce_nil_params_to_array!
+ end
+
+ params do
+ requires :id, type: String
+ optional :array, type: Array, coerce_with: ::API::Validations::Types::CommaSeparatedToArray.coerce
+ optional :array_of_strings, type: Array[String], coerce_with: ::API::Validations::Types::CommaSeparatedToArray.coerce
+ optional :array_of_ints, type: Array[Integer], coerce_with: ::API::Validations::Types::CommaSeparatedToIntegerArray.coerce
+ end
+ get ":id" do
+ params.to_json
+ end
+ end
+ end
+
+ def app
+ subject
+ end
+
+ describe '.coerce_nil_params_to_array!' do
+ let(:json_response) { Gitlab::Json.parse(last_response.body) }
+
+ it 'converts all nil parameters to empty arrays' do
+ get '/test?array=&array_of_strings=&array_of_ints='
+
+ expect(json_response['array']).to eq([])
+ expect(json_response['array_of_strings']).to eq([])
+ expect(json_response['array_of_ints']).to eq([])
+ end
+
+ it 'leaves non-nil parameters alone' do
+ get '/test?array=&array_of_strings=test,me&array_of_ints=1,2'
+
+ expect(json_response['array']).to eq([])
+ expect(json_response['array_of_strings']).to eq(%w(test me))
+ expect(json_response['array_of_ints']).to eq([1, 2])
+ end
+ end
+end
diff --git a/spec/lib/api/helpers/graphql_helpers_spec.rb b/spec/lib/api/helpers/graphql_helpers_spec.rb
index c775ba6d5e8..678f4f8a3e3 100644
--- a/spec/lib/api/helpers/graphql_helpers_spec.rb
+++ b/spec/lib/api/helpers/graphql_helpers_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Helpers::GraphqlHelpers do
+RSpec.describe API::Helpers::GraphqlHelpers do
describe 'run_graphql!' do
let(:query) { '{ metadata { version } }' }
diff --git a/spec/lib/api/helpers/label_helpers_spec.rb b/spec/lib/api/helpers/label_helpers_spec.rb
index 138e9a22d70..007cb3248e2 100644
--- a/spec/lib/api/helpers/label_helpers_spec.rb
+++ b/spec/lib/api/helpers/label_helpers_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Helpers::LabelHelpers do
+RSpec.describe API::Helpers::LabelHelpers do
describe 'create_service_params' do
let(:label_helper) do
Class.new do
diff --git a/spec/lib/api/helpers/packages/dependency_proxy_helpers_spec.rb b/spec/lib/api/helpers/packages/dependency_proxy_helpers_spec.rb
new file mode 100644
index 00000000000..ccf96bcbad6
--- /dev/null
+++ b/spec/lib/api/helpers/packages/dependency_proxy_helpers_spec.rb
@@ -0,0 +1,72 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe API::Helpers::Packages::DependencyProxyHelpers do
+ let_it_be(:helper) { Class.new.include(described_class).new }
+
+ describe 'redirect_registry_request' do
+ using RSpec::Parameterized::TableSyntax
+
+ let(:options) { {} }
+
+ subject { helper.redirect_registry_request(forward_to_registry, package_type, options) { helper.fallback } }
+
+ shared_examples 'executing fallback' do
+ it 'redirects to package registry' do
+ expect(helper).to receive(:registry_url).never
+ expect(helper).to receive(:redirect).never
+ expect(helper).to receive(:fallback).once
+
+ subject
+ end
+ end
+
+ shared_examples 'executing redirect' do
+ it 'redirects to package registry' do
+ expect(helper).to receive(:registry_url).once
+ expect(helper).to receive(:redirect).once
+ expect(helper).to receive(:fallback).never
+
+ subject
+ end
+ end
+
+ context 'with npm packages' do
+ let(:package_type) { :npm }
+
+ where(:application_setting, :forward_to_registry, :example_name) do
+ true | true | 'executing redirect'
+ true | false | 'executing fallback'
+ false | true | 'executing fallback'
+ false | false | 'executing fallback'
+ end
+
+ with_them do
+ before do
+ stub_application_setting(npm_package_requests_forwarding: application_setting)
+ end
+
+ it_behaves_like params[:example_name]
+ end
+ end
+
+ context 'with non-forwardable packages' do
+ let(:forward_to_registry) { true }
+
+ before do
+ stub_application_setting(npm_package_requests_forwarding: true)
+ end
+
+ Packages::Package.package_types.keys.without('npm').each do |pkg_type|
+ context "#{pkg_type}" do
+ let(:package_type) { pkg_type }
+
+ it 'raises an error' do
+ expect { subject }.to raise_error(ArgumentError, "Can't build registry_url for package_type #{package_type}")
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/spec/lib/api/helpers/packages_helpers_spec.rb b/spec/lib/api/helpers/packages_helpers_spec.rb
new file mode 100644
index 00000000000..0c51e25bad9
--- /dev/null
+++ b/spec/lib/api/helpers/packages_helpers_spec.rb
@@ -0,0 +1,104 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe API::Helpers::PackagesHelpers do
+ let_it_be(:helper) { Class.new.include(described_class).new }
+ let_it_be(:project) { create(:project) }
+
+ describe 'authorize_packages_access!' do
+ subject { helper.authorize_packages_access!(project) }
+
+ it 'authorizes packages access' do
+ expect(helper).to receive(:require_packages_enabled!)
+ expect(helper).to receive(:authorize_read_package!).with(project)
+
+ expect(subject).to eq nil
+ end
+ end
+
+ %i[read_package create_package destroy_package].each do |action|
+ describe "authorize_#{action}!" do
+ subject { helper.send("authorize_#{action}!", project) }
+
+ it 'calls authorize!' do
+ expect(helper).to receive(:authorize!).with(action, project)
+
+ expect(subject).to eq nil
+ end
+ end
+ end
+
+ describe 'require_packages_enabled!' do
+ let(:packages_enabled) { true }
+
+ subject { helper.require_packages_enabled! }
+
+ before do
+ allow(::Gitlab.config.packages).to receive(:enabled).and_return(packages_enabled)
+ end
+
+ context 'with packages enabled' do
+ it "doesn't call not_found!" do
+ expect(helper).to receive(:not_found!).never
+
+ expect(subject).to eq nil
+ end
+ end
+
+ context 'with package disabled' do
+ let(:packages_enabled) { false }
+
+ it 'calls not_found!' do
+ expect(helper).to receive(:not_found!).once
+
+ subject
+ end
+ end
+ end
+
+ describe '#authorize_workhorse!' do
+ let_it_be(:headers) { {} }
+
+ subject { helper.authorize_workhorse!(subject: project) }
+
+ before do
+ allow(helper).to receive(:headers).and_return(headers)
+ end
+
+ it 'authorizes workhorse' do
+ expect(helper).to receive(:authorize_upload!).with(project)
+ expect(helper).to receive(:status).with(200)
+ expect(helper).to receive(:content_type).with(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE)
+ expect(Gitlab::Workhorse).to receive(:verify_api_request!).with(headers)
+ expect(::Packages::PackageFileUploader).to receive(:workhorse_authorize).with(has_length: true)
+
+ expect(subject).to eq nil
+ end
+
+ context 'without length' do
+ subject { helper.authorize_workhorse!(subject: project, has_length: false) }
+
+ it 'authorizes workhorse' do
+ expect(helper).to receive(:authorize_upload!).with(project)
+ expect(helper).to receive(:status).with(200)
+ expect(helper).to receive(:content_type).with(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE)
+ expect(Gitlab::Workhorse).to receive(:verify_api_request!).with(headers)
+ expect(::Packages::PackageFileUploader).to receive(:workhorse_authorize).with(has_length: false, maximum_size: ::API::Helpers::PackagesHelpers::MAX_PACKAGE_FILE_SIZE)
+
+ expect(subject).to eq nil
+ end
+ end
+ end
+
+ describe '#authorize_upload!' do
+ subject { helper.authorize_upload!(project) }
+
+ it 'authorizes the upload' do
+ expect(helper).to receive(:authorize_create_package!).with(project)
+ expect(helper).to receive(:require_gitlab_workhorse!)
+
+ expect(subject).to eq nil
+ end
+ end
+end
diff --git a/spec/lib/api/helpers/packages_manager_clients_helpers_spec.rb b/spec/lib/api/helpers/packages_manager_clients_helpers_spec.rb
new file mode 100644
index 00000000000..80be5f7d10a
--- /dev/null
+++ b/spec/lib/api/helpers/packages_manager_clients_helpers_spec.rb
@@ -0,0 +1,154 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe API::Helpers::PackagesManagerClientsHelpers do
+ let_it_be(:personal_access_token) { create(:personal_access_token) }
+ let_it_be(:username) { personal_access_token.user.username }
+ let_it_be(:helper) { Class.new.include(described_class).new }
+ let(:password) { personal_access_token.token }
+
+ describe '#find_personal_access_token_from_http_basic_auth' do
+ let(:headers) { { Authorization: basic_http_auth(username, password) } }
+
+ subject { helper.find_personal_access_token_from_http_basic_auth }
+
+ before do
+ allow(helper).to receive(:headers).and_return(headers&.with_indifferent_access)
+ end
+
+ context 'with a valid Authorization header' do
+ it { is_expected.to eq personal_access_token }
+ end
+
+ context 'with an invalid Authorization header' do
+ where(:headers) do
+ [
+ [{ Authorization: 'Invalid' }],
+ [{}],
+ [nil]
+ ]
+ end
+
+ with_them do
+ it { is_expected.to be nil }
+ end
+ end
+
+ context 'with an unknown Authorization header' do
+ let(:password) { 'Unknown' }
+
+ it { is_expected.to be nil }
+ end
+ end
+
+ describe '#find_job_from_http_basic_auth' do
+ let_it_be(:user) { personal_access_token.user }
+
+ let(:job) { create(:ci_build, user: user) }
+ let(:password) { job.token }
+ let(:headers) { { Authorization: basic_http_auth(username, password) } }
+
+ subject { helper.find_job_from_http_basic_auth }
+
+ before do
+ allow(helper).to receive(:headers).and_return(headers&.with_indifferent_access)
+ end
+
+ context 'with a valid Authorization header' do
+ it { is_expected.to eq job }
+ end
+
+ context 'with an invalid Authorization header' do
+ where(:headers) do
+ [
+ [{ Authorization: 'Invalid' }],
+ [{}],
+ [nil]
+ ]
+ end
+
+ with_them do
+ it { is_expected.to be nil }
+ end
+ end
+
+ context 'with an unknown Authorization header' do
+ let(:password) { 'Unknown' }
+
+ it { is_expected.to be nil }
+ end
+ end
+
+ describe '#find_deploy_token_from_http_basic_auth' do
+ let_it_be(:deploy_token) { create(:deploy_token) }
+ let(:token) { deploy_token.token }
+ let(:headers) { { Authorization: basic_http_auth(deploy_token.username, token) } }
+
+ subject { helper.find_deploy_token_from_http_basic_auth }
+
+ before do
+ allow(helper).to receive(:headers).and_return(headers&.with_indifferent_access)
+ end
+
+ context 'with a valid Authorization header' do
+ it { is_expected.to eq deploy_token }
+ end
+
+ context 'with an invalid Authorization header' do
+ where(:headers) do
+ [
+ [{ Authorization: 'Invalid' }],
+ [{}],
+ [nil]
+ ]
+ end
+
+ with_them do
+ it { is_expected.to be nil }
+ end
+ end
+
+ context 'with an invalid token' do
+ let(:token) { 'Unknown' }
+
+ it { is_expected.to be nil }
+ end
+ end
+
+ describe '#uploaded_package_file' do
+ let_it_be(:params) { {} }
+
+ subject { helper.uploaded_package_file }
+
+ before do
+ allow(helper).to receive(:params).and_return(params)
+ end
+
+ context 'with valid uploaded package file' do
+ let_it_be(:uploaded_file) { Object.new }
+
+ before do
+ allow(UploadedFile).to receive(:from_params).and_return(uploaded_file)
+ end
+
+ it { is_expected.to be uploaded_file }
+ end
+
+ context 'with invalid uploaded package file' do
+ before do
+ allow(UploadedFile).to receive(:from_params).and_return(nil)
+ end
+
+ it 'fails with bad_request!' do
+ expect(helper).to receive(:bad_request!)
+
+ expect(subject).to be nil
+ end
+ end
+ end
+
+ def basic_http_auth(username, password)
+ ActionController::HttpAuthentication::Basic.encode_credentials(username, password)
+ end
+end
diff --git a/spec/lib/api/helpers/pagination_spec.rb b/spec/lib/api/helpers/pagination_spec.rb
index 796c753d6c4..a008c1adeac 100644
--- a/spec/lib/api/helpers/pagination_spec.rb
+++ b/spec/lib/api/helpers/pagination_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Helpers::Pagination do
+RSpec.describe API::Helpers::Pagination do
subject { Class.new.include(described_class).new }
let(:paginator) { double('paginator') }
diff --git a/spec/lib/api/helpers/pagination_strategies_spec.rb b/spec/lib/api/helpers/pagination_strategies_spec.rb
index eaa71159714..e8a4243b407 100644
--- a/spec/lib/api/helpers/pagination_strategies_spec.rb
+++ b/spec/lib/api/helpers/pagination_strategies_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Helpers::PaginationStrategies do
+RSpec.describe API::Helpers::PaginationStrategies do
subject { Class.new.include(described_class).new }
let(:expected_result) { double("result") }
diff --git a/spec/lib/api/helpers/related_resources_helpers_spec.rb b/spec/lib/api/helpers/related_resources_helpers_spec.rb
index eeeb22abd10..a0dc69536b4 100644
--- a/spec/lib/api/helpers/related_resources_helpers_spec.rb
+++ b/spec/lib/api/helpers/related_resources_helpers_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Helpers::RelatedResourcesHelpers do
+RSpec.describe API::Helpers::RelatedResourcesHelpers do
subject(:helpers) do
Class.new.include(described_class).new
end
diff --git a/spec/lib/api/helpers/version_spec.rb b/spec/lib/api/helpers/version_spec.rb
index a9f33962537..a87a3c5a026 100644
--- a/spec/lib/api/helpers/version_spec.rb
+++ b/spec/lib/api/helpers/version_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Helpers::Version do
+RSpec.describe API::Helpers::Version do
describe '.new' do
it 'is possible to initialize it with existing API version' do
expect(described_class.new('v4').to_s).to eq 'v4'
diff --git a/spec/lib/api/helpers_spec.rb b/spec/lib/api/helpers_spec.rb
index 3595d06a184..8cba1e0794a 100644
--- a/spec/lib/api/helpers_spec.rb
+++ b/spec/lib/api/helpers_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Helpers do
+RSpec.describe API::Helpers do
subject { Class.new.include(described_class).new }
describe '#find_project' do
diff --git a/spec/lib/api/support/git_access_actor_spec.rb b/spec/lib/api/support/git_access_actor_spec.rb
index 69637947c79..70753856419 100644
--- a/spec/lib/api/support/git_access_actor_spec.rb
+++ b/spec/lib/api/support/git_access_actor_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Support::GitAccessActor do
+RSpec.describe API::Support::GitAccessActor do
let(:user) { nil }
let(:key) { nil }
diff --git a/spec/lib/api/validations/validators/absence_spec.rb b/spec/lib/api/validations/validators/absence_spec.rb
index 31120979d4f..bfecaf4e243 100644
--- a/spec/lib/api/validations/validators/absence_spec.rb
+++ b/spec/lib/api/validations/validators/absence_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Validations::Validators::Absence do
+RSpec.describe API::Validations::Validators::Absence do
include ApiValidatorsHelpers
subject do
diff --git a/spec/lib/api/validations/validators/array_none_any_spec.rb b/spec/lib/api/validations/validators/array_none_any_spec.rb
index 03f1c63b117..833adea6554 100644
--- a/spec/lib/api/validations/validators/array_none_any_spec.rb
+++ b/spec/lib/api/validations/validators/array_none_any_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Validations::Validators::ArrayNoneAny do
+RSpec.describe API::Validations::Validators::ArrayNoneAny do
include ApiValidatorsHelpers
subject do
diff --git a/spec/lib/api/validations/validators/file_path_spec.rb b/spec/lib/api/validations/validators/file_path_spec.rb
index 8679f102d23..2c79260b8d5 100644
--- a/spec/lib/api/validations/validators/file_path_spec.rb
+++ b/spec/lib/api/validations/validators/file_path_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Validations::Validators::FilePath do
+RSpec.describe API::Validations::Validators::FilePath do
include ApiValidatorsHelpers
subject do
diff --git a/spec/lib/api/validations/validators/git_ref_spec.rb b/spec/lib/api/validations/validators/git_ref_spec.rb
index 84de6272fe1..0d2d9e8f39a 100644
--- a/spec/lib/api/validations/validators/git_ref_spec.rb
+++ b/spec/lib/api/validations/validators/git_ref_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Validations::Validators::GitRef do
+RSpec.describe API::Validations::Validators::GitRef do
include ApiValidatorsHelpers
subject do
diff --git a/spec/lib/api/validations/validators/git_sha_spec.rb b/spec/lib/api/validations/validators/git_sha_spec.rb
index 39c2fe1dcf9..ae6be52a4c7 100644
--- a/spec/lib/api/validations/validators/git_sha_spec.rb
+++ b/spec/lib/api/validations/validators/git_sha_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Validations::Validators::GitSha do
+RSpec.describe API::Validations::Validators::GitSha do
include ApiValidatorsHelpers
let(:sha) { RepoHelpers.sample_commit.id }
diff --git a/spec/lib/api/validations/validators/integer_none_any_spec.rb b/spec/lib/api/validations/validators/integer_none_any_spec.rb
index a42f69fd96e..33fa7688d18 100644
--- a/spec/lib/api/validations/validators/integer_none_any_spec.rb
+++ b/spec/lib/api/validations/validators/integer_none_any_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Validations::Validators::IntegerNoneAny do
+RSpec.describe API::Validations::Validators::IntegerNoneAny do
include ApiValidatorsHelpers
subject do
diff --git a/spec/lib/api/validations/validators/limit_spec.rb b/spec/lib/api/validations/validators/limit_spec.rb
index 600f74e1fb2..d71dde470cc 100644
--- a/spec/lib/api/validations/validators/limit_spec.rb
+++ b/spec/lib/api/validations/validators/limit_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Validations::Validators::Limit do
+RSpec.describe API::Validations::Validators::Limit do
include ApiValidatorsHelpers
subject do
diff --git a/spec/lib/api/validations/validators/untrusted_regexp_spec.rb b/spec/lib/api/validations/validators/untrusted_regexp_spec.rb
index 491bf94fd79..def67c94de0 100644
--- a/spec/lib/api/validations/validators/untrusted_regexp_spec.rb
+++ b/spec/lib/api/validations/validators/untrusted_regexp_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Validations::Validators::UntrustedRegexp do
+RSpec.describe API::Validations::Validators::UntrustedRegexp do
include ApiValidatorsHelpers
subject do
diff --git a/spec/lib/backup/files_spec.rb b/spec/lib/backup/files_spec.rb
index b75f3bafeef..a7374b82ce0 100644
--- a/spec/lib/backup/files_spec.rb
+++ b/spec/lib/backup/files_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Backup::Files do
+RSpec.describe Backup::Files do
let(:progress) { StringIO.new }
let!(:project) { create(:project) }
diff --git a/spec/lib/backup/manager_spec.rb b/spec/lib/backup/manager_spec.rb
index b86e92d5969..38a5c30506b 100644
--- a/spec/lib/backup/manager_spec.rb
+++ b/spec/lib/backup/manager_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Backup::Manager do
+RSpec.describe Backup::Manager do
include StubENV
let(:progress) { StringIO.new }
diff --git a/spec/lib/backup/repository_spec.rb b/spec/lib/backup/repository_spec.rb
index e0afa256581..c073a45bf68 100644
--- a/spec/lib/backup/repository_spec.rb
+++ b/spec/lib/backup/repository_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Backup::Repository do
+RSpec.describe Backup::Repository do
let(:progress) { StringIO.new }
let!(:project) { create(:project, :wiki_repo) }
diff --git a/spec/lib/backup/uploads_spec.rb b/spec/lib/backup/uploads_spec.rb
index 1f49baeff69..7c2d715b580 100644
--- a/spec/lib/backup/uploads_spec.rb
+++ b/spec/lib/backup/uploads_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Backup::Uploads do
+RSpec.describe Backup::Uploads do
let(:progress) { StringIO.new }
subject(:backup) { described_class.new(progress) }
diff --git a/spec/lib/banzai/color_parser_spec.rb b/spec/lib/banzai/color_parser_spec.rb
index d9202ce77db..95b3955d8fe 100644
--- a/spec/lib/banzai/color_parser_spec.rb
+++ b/spec/lib/banzai/color_parser_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Banzai::ColorParser do
+RSpec.describe Banzai::ColorParser do
describe '.parse' do
context 'HEX format' do
[
diff --git a/spec/lib/banzai/commit_renderer_spec.rb b/spec/lib/banzai/commit_renderer_spec.rb
index e5a16b167be..a10dd6eb3a2 100644
--- a/spec/lib/banzai/commit_renderer_spec.rb
+++ b/spec/lib/banzai/commit_renderer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Banzai::CommitRenderer do
+RSpec.describe Banzai::CommitRenderer do
describe '.render', :clean_gitlab_redis_cache do
it 'renders a commit description and title' do
user = build(:user)
diff --git a/spec/lib/banzai/cross_project_reference_spec.rb b/spec/lib/banzai/cross_project_reference_spec.rb
index cf41af7e7a1..95b78ceb5d5 100644
--- a/spec/lib/banzai/cross_project_reference_spec.rb
+++ b/spec/lib/banzai/cross_project_reference_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Banzai::CrossProjectReference do
+RSpec.describe Banzai::CrossProjectReference do
let(:including_class) { Class.new.include(described_class).new }
before do
diff --git a/spec/lib/banzai/filter/absolute_link_filter_spec.rb b/spec/lib/banzai/filter/absolute_link_filter_spec.rb
index b61bd496dba..2cb70850dca 100644
--- a/spec/lib/banzai/filter/absolute_link_filter_spec.rb
+++ b/spec/lib/banzai/filter/absolute_link_filter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Banzai::Filter::AbsoluteLinkFilter do
+RSpec.describe Banzai::Filter::AbsoluteLinkFilter do
def filter(doc, context = {})
described_class.call(doc, context)
end
diff --git a/spec/lib/banzai/filter/abstract_reference_filter_spec.rb b/spec/lib/banzai/filter/abstract_reference_filter_spec.rb
index 6890a70518b..797f1c8d52f 100644
--- a/spec/lib/banzai/filter/abstract_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/abstract_reference_filter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Banzai::Filter::AbstractReferenceFilter do
+RSpec.describe Banzai::Filter::AbstractReferenceFilter do
let_it_be(:project) { create(:project) }
let(:doc) { Nokogiri::HTML.fragment('') }
diff --git a/spec/lib/banzai/filter/ascii_doc_post_processing_filter_spec.rb b/spec/lib/banzai/filter/ascii_doc_post_processing_filter_spec.rb
index bd06dae26ba..334d5c59828 100644
--- a/spec/lib/banzai/filter/ascii_doc_post_processing_filter_spec.rb
+++ b/spec/lib/banzai/filter/ascii_doc_post_processing_filter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Banzai::Filter::AsciiDocPostProcessingFilter do
+RSpec.describe Banzai::Filter::AsciiDocPostProcessingFilter do
include FilterSpecHelper
it "adds class for elements with data-math-style" do
diff --git a/spec/lib/banzai/filter/asset_proxy_filter_spec.rb b/spec/lib/banzai/filter/asset_proxy_filter_spec.rb
index ff2346fe1ba..2a4ee28130b 100644
--- a/spec/lib/banzai/filter/asset_proxy_filter_spec.rb
+++ b/spec/lib/banzai/filter/asset_proxy_filter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Banzai::Filter::AssetProxyFilter do
+RSpec.describe Banzai::Filter::AssetProxyFilter do
include FilterSpecHelper
def image(path)
diff --git a/spec/lib/banzai/filter/audio_link_filter_spec.rb b/spec/lib/banzai/filter/audio_link_filter_spec.rb
index a8459137169..4198a50e980 100644
--- a/spec/lib/banzai/filter/audio_link_filter_spec.rb
+++ b/spec/lib/banzai/filter/audio_link_filter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Banzai::Filter::AudioLinkFilter do
+RSpec.describe Banzai::Filter::AudioLinkFilter do
def filter(doc, contexts = {})
contexts.reverse_merge!({
project: project
diff --git a/spec/lib/banzai/filter/autolink_filter_spec.rb b/spec/lib/banzai/filter/autolink_filter_spec.rb
index be6192f9ead..ba15860f3c9 100644
--- a/spec/lib/banzai/filter/autolink_filter_spec.rb
+++ b/spec/lib/banzai/filter/autolink_filter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Banzai::Filter::AutolinkFilter do
+RSpec.describe Banzai::Filter::AutolinkFilter do
include FilterSpecHelper
let(:link) { 'http://about.gitlab.com/' }
diff --git a/spec/lib/banzai/filter/blockquote_fence_filter_spec.rb b/spec/lib/banzai/filter/blockquote_fence_filter_spec.rb
index 8e955ec3b6b..e736943914b 100644
--- a/spec/lib/banzai/filter/blockquote_fence_filter_spec.rb
+++ b/spec/lib/banzai/filter/blockquote_fence_filter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Banzai::Filter::BlockquoteFenceFilter do
+RSpec.describe Banzai::Filter::BlockquoteFenceFilter do
include FilterSpecHelper
it 'converts blockquote fences to blockquote lines' do
diff --git a/spec/lib/banzai/filter/broadcast_message_placeholders_filter_spec.rb b/spec/lib/banzai/filter/broadcast_message_placeholders_filter_spec.rb
index 1a90abc12d9..a2d35eaa6b6 100644
--- a/spec/lib/banzai/filter/broadcast_message_placeholders_filter_spec.rb
+++ b/spec/lib/banzai/filter/broadcast_message_placeholders_filter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Banzai::Filter::BroadcastMessagePlaceholdersFilter do
+RSpec.describe Banzai::Filter::BroadcastMessagePlaceholdersFilter do
include FilterSpecHelper
subject { filter(text, current_user: user, broadcast_message_placeholders: true).to_html }
diff --git a/spec/lib/banzai/filter/broadcast_message_sanitization_filter_spec.rb b/spec/lib/banzai/filter/broadcast_message_sanitization_filter_spec.rb
index 317ac7ef854..1f65268bd3c 100644
--- a/spec/lib/banzai/filter/broadcast_message_sanitization_filter_spec.rb
+++ b/spec/lib/banzai/filter/broadcast_message_sanitization_filter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Banzai::Filter::BroadcastMessageSanitizationFilter do
+RSpec.describe Banzai::Filter::BroadcastMessageSanitizationFilter do
include FilterSpecHelper
it_behaves_like 'default whitelist'
diff --git a/spec/lib/banzai/filter/color_filter_spec.rb b/spec/lib/banzai/filter/color_filter_spec.rb
index f8931d37b99..dced3671323 100644
--- a/spec/lib/banzai/filter/color_filter_spec.rb
+++ b/spec/lib/banzai/filter/color_filter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Banzai::Filter::ColorFilter, lib: true do
+RSpec.describe Banzai::Filter::ColorFilter, lib: true do
include FilterSpecHelper
let(:color) { '#F00' }
diff --git a/spec/lib/banzai/filter/commit_range_reference_filter_spec.rb b/spec/lib/banzai/filter/commit_range_reference_filter_spec.rb
index 5cfb0e6e6f7..f04d3212437 100644
--- a/spec/lib/banzai/filter/commit_range_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/commit_range_reference_filter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Banzai::Filter::CommitRangeReferenceFilter do
+RSpec.describe Banzai::Filter::CommitRangeReferenceFilter do
include FilterSpecHelper
let(:project) { create(:project, :public, :repository) }
diff --git a/spec/lib/banzai/filter/commit_reference_filter_spec.rb b/spec/lib/banzai/filter/commit_reference_filter_spec.rb
index 63ec597a0ba..925fd031d95 100644
--- a/spec/lib/banzai/filter/commit_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/commit_reference_filter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Banzai::Filter::CommitReferenceFilter do
+RSpec.describe Banzai::Filter::CommitReferenceFilter do
include FilterSpecHelper
let(:project) { create(:project, :public, :repository) }
diff --git a/spec/lib/banzai/filter/commit_trailers_filter_spec.rb b/spec/lib/banzai/filter/commit_trailers_filter_spec.rb
index 192d00805e0..03a6cc34962 100644
--- a/spec/lib/banzai/filter/commit_trailers_filter_spec.rb
+++ b/spec/lib/banzai/filter/commit_trailers_filter_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require 'ffaker'
-describe Banzai::Filter::CommitTrailersFilter do
+RSpec.describe Banzai::Filter::CommitTrailersFilter do
include FilterSpecHelper
include CommitTrailersSpecHelper
diff --git a/spec/lib/banzai/filter/design_reference_filter_spec.rb b/spec/lib/banzai/filter/design_reference_filter_spec.rb
index 8a6c2e3b3f9..1b558754932 100644
--- a/spec/lib/banzai/filter/design_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/design_reference_filter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Banzai::Filter::DesignReferenceFilter do
+RSpec.describe Banzai::Filter::DesignReferenceFilter do
include FilterSpecHelper
include DesignManagementTestHelpers
diff --git a/spec/lib/banzai/filter/emoji_filter_spec.rb b/spec/lib/banzai/filter/emoji_filter_spec.rb
index 4e163668a28..d78763b6939 100644
--- a/spec/lib/banzai/filter/emoji_filter_spec.rb
+++ b/spec/lib/banzai/filter/emoji_filter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Banzai::Filter::EmojiFilter do
+RSpec.describe Banzai::Filter::EmojiFilter do
include FilterSpecHelper
it 'replaces supported name emoji' do
diff --git a/spec/lib/banzai/filter/external_issue_reference_filter_spec.rb b/spec/lib/banzai/filter/external_issue_reference_filter_spec.rb
index a70c820f97a..7d8fb183dbb 100644
--- a/spec/lib/banzai/filter/external_issue_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/external_issue_reference_filter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Banzai::Filter::ExternalIssueReferenceFilter do
+RSpec.describe Banzai::Filter::ExternalIssueReferenceFilter do
include FilterSpecHelper
shared_examples_for "external issue tracker" do
diff --git a/spec/lib/banzai/filter/external_link_filter_spec.rb b/spec/lib/banzai/filter/external_link_filter_spec.rb
index 4b2500b31f7..630730dfc1a 100644
--- a/spec/lib/banzai/filter/external_link_filter_spec.rb
+++ b/spec/lib/banzai/filter/external_link_filter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-shared_examples 'an external link with rel attribute' do
+RSpec.shared_examples 'an external link with rel attribute' do
it 'adds rel="nofollow" to external links' do
expect(doc.at_css('a')).to have_attribute('rel')
expect(doc.at_css('a')['rel']).to include 'nofollow'
@@ -19,7 +19,7 @@ shared_examples 'an external link with rel attribute' do
end
end
-describe Banzai::Filter::ExternalLinkFilter do
+RSpec.describe Banzai::Filter::ExternalLinkFilter do
include FilterSpecHelper
it 'ignores elements without an href attribute' do
diff --git a/spec/lib/banzai/filter/footnote_filter_spec.rb b/spec/lib/banzai/filter/footnote_filter_spec.rb
index c6dcb4e46fd..01b7319fab1 100644
--- a/spec/lib/banzai/filter/footnote_filter_spec.rb
+++ b/spec/lib/banzai/filter/footnote_filter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Banzai::Filter::FootnoteFilter do
+RSpec.describe Banzai::Filter::FootnoteFilter do
include FilterSpecHelper
# first[^1] and second[^second]
diff --git a/spec/lib/banzai/filter/front_matter_filter_spec.rb b/spec/lib/banzai/filter/front_matter_filter_spec.rb
index 1b5b1770615..3f966c94dd3 100644
--- a/spec/lib/banzai/filter/front_matter_filter_spec.rb
+++ b/spec/lib/banzai/filter/front_matter_filter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Banzai::Filter::FrontMatterFilter do
+RSpec.describe Banzai::Filter::FrontMatterFilter do
include FilterSpecHelper
it 'allows for `encoding:` before the front matter' do
diff --git a/spec/lib/banzai/filter/gollum_tags_filter_spec.rb b/spec/lib/banzai/filter/gollum_tags_filter_spec.rb
index 00d8b871224..2576dd1bf07 100644
--- a/spec/lib/banzai/filter/gollum_tags_filter_spec.rb
+++ b/spec/lib/banzai/filter/gollum_tags_filter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Banzai::Filter::GollumTagsFilter do
+RSpec.describe Banzai::Filter::GollumTagsFilter do
include FilterSpecHelper
let(:project) { create(:project) }
diff --git a/spec/lib/banzai/filter/html_entity_filter_spec.rb b/spec/lib/banzai/filter/html_entity_filter_spec.rb
index 6017380725d..d88fa21cde7 100644
--- a/spec/lib/banzai/filter/html_entity_filter_spec.rb
+++ b/spec/lib/banzai/filter/html_entity_filter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Banzai::Filter::HtmlEntityFilter do
+RSpec.describe Banzai::Filter::HtmlEntityFilter do
include FilterSpecHelper
let(:unescaped) { 'foo <strike attr="foo">&&amp;&</strike>' }
diff --git a/spec/lib/banzai/filter/image_lazy_load_filter_spec.rb b/spec/lib/banzai/filter/image_lazy_load_filter_spec.rb
index 6475fd14ce4..9f5aa558f24 100644
--- a/spec/lib/banzai/filter/image_lazy_load_filter_spec.rb
+++ b/spec/lib/banzai/filter/image_lazy_load_filter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Banzai::Filter::ImageLazyLoadFilter do
+RSpec.describe Banzai::Filter::ImageLazyLoadFilter do
include FilterSpecHelper
def image(path)
diff --git a/spec/lib/banzai/filter/image_link_filter_spec.rb b/spec/lib/banzai/filter/image_link_filter_spec.rb
index 011e3a1e2da..5c04f6b2b3e 100644
--- a/spec/lib/banzai/filter/image_link_filter_spec.rb
+++ b/spec/lib/banzai/filter/image_link_filter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Banzai::Filter::ImageLinkFilter do
+RSpec.describe Banzai::Filter::ImageLinkFilter do
include FilterSpecHelper
def image(path)
diff --git a/spec/lib/banzai/filter/inline_cluster_metrics_filter_spec.rb b/spec/lib/banzai/filter/inline_cluster_metrics_filter_spec.rb
new file mode 100644
index 00000000000..fe048daa601
--- /dev/null
+++ b/spec/lib/banzai/filter/inline_cluster_metrics_filter_spec.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Banzai::Filter::InlineClusterMetricsFilter do
+ include FilterSpecHelper
+
+ let!(:cluster) { create(:cluster) }
+ let!(:project) { create(:project) }
+ let(:params) { [project.namespace.path, project.path, cluster.id] }
+ let(:query_params) { { group: 'Food metrics', title: 'Pizza Consumption', y_label: 'Slice Count' } }
+ let(:trigger_url) { urls.namespace_project_cluster_url(*params, **query_params) }
+ let(:dashboard_url) do
+ urls.metrics_dashboard_namespace_project_cluster_url(
+ *params,
+ **{
+ embedded: 'true',
+ cluster_type: 'project',
+ format: :json
+ }.merge(query_params)
+ )
+ end
+
+ it_behaves_like 'a metrics embed filter'
+end
diff --git a/spec/lib/banzai/filter/inline_diff_filter_spec.rb b/spec/lib/banzai/filter/inline_diff_filter_spec.rb
index c09065fb746..1ef00139db2 100644
--- a/spec/lib/banzai/filter/inline_diff_filter_spec.rb
+++ b/spec/lib/banzai/filter/inline_diff_filter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Banzai::Filter::InlineDiffFilter do
+RSpec.describe Banzai::Filter::InlineDiffFilter do
include FilterSpecHelper
it 'adds inline diff span tags for deletions when using square brackets' do
diff --git a/spec/lib/banzai/filter/inline_grafana_metrics_filter_spec.rb b/spec/lib/banzai/filter/inline_grafana_metrics_filter_spec.rb
index 28bf5bd3e92..8bdb24ab08c 100644
--- a/spec/lib/banzai/filter/inline_grafana_metrics_filter_spec.rb
+++ b/spec/lib/banzai/filter/inline_grafana_metrics_filter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Banzai::Filter::InlineGrafanaMetricsFilter do
+RSpec.describe Banzai::Filter::InlineGrafanaMetricsFilter do
include FilterSpecHelper
let_it_be(:project) { create(:project) }
diff --git a/spec/lib/banzai/filter/inline_metrics_filter_spec.rb b/spec/lib/banzai/filter/inline_metrics_filter_spec.rb
index 1546a5e88ed..9b0b95b9da2 100644
--- a/spec/lib/banzai/filter/inline_metrics_filter_spec.rb
+++ b/spec/lib/banzai/filter/inline_metrics_filter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Banzai::Filter::InlineMetricsFilter do
+RSpec.describe Banzai::Filter::InlineMetricsFilter do
include FilterSpecHelper
let(:params) { ['foo', 'bar', 12] }
diff --git a/spec/lib/banzai/filter/inline_metrics_redactor_filter_spec.rb b/spec/lib/banzai/filter/inline_metrics_redactor_filter_spec.rb
index 9d8cd729958..cafcaef8ae2 100644
--- a/spec/lib/banzai/filter/inline_metrics_redactor_filter_spec.rb
+++ b/spec/lib/banzai/filter/inline_metrics_redactor_filter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Banzai::Filter::InlineMetricsRedactorFilter do
+RSpec.describe Banzai::Filter::InlineMetricsRedactorFilter do
include FilterSpecHelper
let_it_be(:project) { create(:project) }
@@ -29,6 +29,26 @@ describe Banzai::Filter::InlineMetricsRedactorFilter do
it_behaves_like 'retains the embed placeholder when applicable'
end
+ context 'for a cluster metric embed' do
+ let_it_be(:cluster) { create(:cluster, :provided_by_gcp, :project, projects: [project]) }
+ let(:params) { [project.namespace.path, project.path, cluster.id] }
+ let(:query_params) { { group: 'Cluster Health', title: 'CPU Usage', y_label: 'CPU (cores)' } }
+ let(:url) { urls.metrics_dashboard_namespace_project_cluster_url(*params, **query_params) }
+
+ context 'with user who can read cluster' do
+ it_behaves_like 'redacts the embed placeholder'
+ it_behaves_like 'retains the embed placeholder when applicable'
+ end
+
+ context 'without user who can read cluster' do
+ let(:doc) { filter(input, current_user: create(:user)) }
+
+ it 'redacts the embed placeholder' do
+ expect(doc.to_s).to be_empty
+ end
+ end
+ end
+
context 'the user has requisite permissions' do
let(:user) { create(:user) }
let(:doc) { filter(input, current_user: user) }
diff --git a/spec/lib/banzai/filter/issuable_state_filter_spec.rb b/spec/lib/banzai/filter/issuable_state_filter_spec.rb
index 5950b6878ef..a3851fd7cca 100644
--- a/spec/lib/banzai/filter/issuable_state_filter_spec.rb
+++ b/spec/lib/banzai/filter/issuable_state_filter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Banzai::Filter::IssuableStateFilter do
+RSpec.describe Banzai::Filter::IssuableStateFilter do
include ActionView::Helpers::UrlHelper
include FilterSpecHelper
diff --git a/spec/lib/banzai/filter/issue_reference_filter_spec.rb b/spec/lib/banzai/filter/issue_reference_filter_spec.rb
index 603da2b4421..98955d5cde9 100644
--- a/spec/lib/banzai/filter/issue_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/issue_reference_filter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Banzai::Filter::IssueReferenceFilter do
+RSpec.describe Banzai::Filter::IssueReferenceFilter do
include FilterSpecHelper
include DesignManagementTestHelpers
diff --git a/spec/lib/banzai/filter/jira_import/adf_to_commonmark_filter_spec.rb b/spec/lib/banzai/filter/jira_import/adf_to_commonmark_filter_spec.rb
new file mode 100644
index 00000000000..287b5774048
--- /dev/null
+++ b/spec/lib/banzai/filter/jira_import/adf_to_commonmark_filter_spec.rb
@@ -0,0 +1,28 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Banzai::Filter::JiraImport::AdfToCommonmarkFilter do
+ include FilterSpecHelper
+
+ let_it_be(:fixtures_path) { 'lib/kramdown/atlassian_document_format' }
+
+ it 'renders a complex document' do
+ source = fixture_file(File.join(fixtures_path, 'complex_document.json'))
+ target = fixture_file(File.join(fixtures_path, 'complex_document.md'))
+
+ expect(filter(source)).to eq target
+ end
+
+ it 'renders original source when it is invalid JSON' do
+ source = fixture_file(File.join(fixtures_path, 'invalid_json.json'))
+
+ expect(filter(source)).to eq "Invalid Atlassian Document Format JSON\n\n#{source}"
+ end
+
+ it 'renders original source when missing document node' do
+ source = fixture_file(File.join(fixtures_path, 'invalid_no_doc.json'))
+
+ expect(filter(source)).to eq "Invalid Atlassian Document Format JSON\n\n#{source}"
+ end
+end
diff --git a/spec/lib/banzai/filter/label_reference_filter_spec.rb b/spec/lib/banzai/filter/label_reference_filter_spec.rb
index 0b697ab2040..dadf98d9b76 100644
--- a/spec/lib/banzai/filter/label_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/label_reference_filter_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require 'html/pipeline'
-describe Banzai::Filter::LabelReferenceFilter do
+RSpec.describe Banzai::Filter::LabelReferenceFilter do
include FilterSpecHelper
let(:project) { create(:project, :public, name: 'sample-project') }
diff --git a/spec/lib/banzai/filter/markdown_filter_spec.rb b/spec/lib/banzai/filter/markdown_filter_spec.rb
index d0a43564903..8d01a651651 100644
--- a/spec/lib/banzai/filter/markdown_filter_spec.rb
+++ b/spec/lib/banzai/filter/markdown_filter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Banzai::Filter::MarkdownFilter do
+RSpec.describe Banzai::Filter::MarkdownFilter do
include FilterSpecHelper
describe 'markdown engine from context' do
diff --git a/spec/lib/banzai/filter/math_filter_spec.rb b/spec/lib/banzai/filter/math_filter_spec.rb
index c8fd92edcdf..9f6688f4f7d 100644
--- a/spec/lib/banzai/filter/math_filter_spec.rb
+++ b/spec/lib/banzai/filter/math_filter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Banzai::Filter::MathFilter do
+RSpec.describe Banzai::Filter::MathFilter do
include FilterSpecHelper
it 'leaves regular inline code unchanged' do
diff --git a/spec/lib/banzai/filter/merge_request_reference_filter_spec.rb b/spec/lib/banzai/filter/merge_request_reference_filter_spec.rb
index 12ee952b10e..f24fcf98b1f 100644
--- a/spec/lib/banzai/filter/merge_request_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/merge_request_reference_filter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Banzai::Filter::MergeRequestReferenceFilter do
+RSpec.describe Banzai::Filter::MergeRequestReferenceFilter do
include FilterSpecHelper
let(:project) { create(:project, :public) }
diff --git a/spec/lib/banzai/filter/mermaid_filter_spec.rb b/spec/lib/banzai/filter/mermaid_filter_spec.rb
index ae6725cc14c..c9bfcffe98f 100644
--- a/spec/lib/banzai/filter/mermaid_filter_spec.rb
+++ b/spec/lib/banzai/filter/mermaid_filter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Banzai::Filter::MermaidFilter do
+RSpec.describe Banzai::Filter::MermaidFilter do
include FilterSpecHelper
it 'adds `js-render-mermaid` class to the `code` tag' do
diff --git a/spec/lib/banzai/filter/milestone_reference_filter_spec.rb b/spec/lib/banzai/filter/milestone_reference_filter_spec.rb
index 0c8413adcba..62b1711ee57 100644
--- a/spec/lib/banzai/filter/milestone_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/milestone_reference_filter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Banzai::Filter::MilestoneReferenceFilter do
+RSpec.describe Banzai::Filter::MilestoneReferenceFilter do
include FilterSpecHelper
let(:parent_group) { create(:group, :public) }
diff --git a/spec/lib/banzai/filter/output_safety_spec.rb b/spec/lib/banzai/filter/output_safety_spec.rb
index 5ffe591c9a4..5b7b7298411 100644
--- a/spec/lib/banzai/filter/output_safety_spec.rb
+++ b/spec/lib/banzai/filter/output_safety_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Banzai::Filter::OutputSafety do
+RSpec.describe Banzai::Filter::OutputSafety do
subject do
Class.new do
include Banzai::Filter::OutputSafety
diff --git a/spec/lib/banzai/filter/plantuml_filter_spec.rb b/spec/lib/banzai/filter/plantuml_filter_spec.rb
index abe525ac47a..5ad94c74514 100644
--- a/spec/lib/banzai/filter/plantuml_filter_spec.rb
+++ b/spec/lib/banzai/filter/plantuml_filter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Banzai::Filter::PlantumlFilter do
+RSpec.describe Banzai::Filter::PlantumlFilter do
include FilterSpecHelper
it 'replaces plantuml pre tag with img tag' do
diff --git a/spec/lib/banzai/filter/project_reference_filter_spec.rb b/spec/lib/banzai/filter/project_reference_filter_spec.rb
index a054b79ec03..ac7a90a5893 100644
--- a/spec/lib/banzai/filter/project_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/project_reference_filter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Banzai::Filter::ProjectReferenceFilter do
+RSpec.describe Banzai::Filter::ProjectReferenceFilter do
include FilterSpecHelper
def invalidate_reference(reference)
diff --git a/spec/lib/banzai/filter/reference_filter_spec.rb b/spec/lib/banzai/filter/reference_filter_spec.rb
index d889b0b832d..d5978db13c0 100644
--- a/spec/lib/banzai/filter/reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/reference_filter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Banzai::Filter::ReferenceFilter do
+RSpec.describe Banzai::Filter::ReferenceFilter do
let(:project) { build_stubbed(:project) }
describe '#each_node' do
@@ -44,4 +44,249 @@ describe Banzai::Filter::ReferenceFilter do
expect(filter.nodes).to eq([document.children[0]])
end
end
+
+ RSpec.shared_context 'document nodes' do
+ let(:document) { Nokogiri::HTML.fragment('<p data-sourcepos="1:1-1:18"></p>') }
+ let(:nodes) { [] }
+ let(:filter) { described_class.new(document, project: project) }
+ let(:ref_pattern) { nil }
+ let(:href_link) { nil }
+
+ before do
+ nodes.each do |node|
+ document.children.first.add_child(node)
+ end
+ end
+ end
+
+ RSpec.shared_context 'new nodes' do
+ let(:nodes) { [{ value: "1" }, { value: "2" }, { value: "3" }] }
+ let(:expected_nodes) { [{ value: "1.1" }, { value: "1.2" }, { value: "1.3" }, { value: "2.1" }, { value: "2.2" }, { value: "2.3" }, { value: "3.1" }, { value: "3.2" }, { value: "3.3" }] }
+ let(:new_nodes) do
+ {
+ 0 => [{ value: "1.1" }, { value: "1.2" }, { value: "1.3" }],
+ 2 => [{ value: "3.1" }, { value: "3.2" }, { value: "3.3" }],
+ 1 => [{ value: "2.1" }, { value: "2.2" }, { value: "2.3" }]
+ }
+ end
+ end
+
+ RSpec.shared_examples 'replaces text' do |method_name, index|
+ let(:args) { [filter.nodes[index], index, ref_pattern || href_link].compact }
+
+ context 'when content didnt change' do
+ it 'does not replace link node with html' do
+ filter.send(method_name, *args) do
+ existing_content
+ end
+
+ expect(filter).not_to receive(:replace_text_with_html)
+ end
+ end
+
+ context 'when link node has changed' do
+ let(:html) { %(text <a href="reference_url" class="gfm gfm-user" title="reference">Reference</a>) }
+
+ it 'replaces reference node' do
+ filter.send(method_name, *args) do
+ html
+ end
+
+ expect(document.css('a').length).to eq 1
+ end
+
+ it 'calls replace_and_update_new_nodes' do
+ expect(filter).to receive(:replace_and_update_new_nodes).with(filter.nodes[index], index, html)
+
+ filter.send(method_name, *args) do
+ html
+ end
+ end
+
+ it 'stores filtered new nodes' do
+ filter.send(method_name, *args) do
+ html
+ end
+
+ expect(filter.instance_variable_get(:@new_nodes)).to eq({ index => [filter.each_node.to_a[index]] })
+ end
+
+ context "with update_nodes_for_banzai_reference_filter feature flag disabled" do
+ before do
+ stub_feature_flags(update_nodes_for_banzai_reference_filter: false)
+ end
+
+ it 'does not call replace_and_update_new_nodes' do
+ expect(filter).not_to receive(:replace_and_update_new_nodes).with(filter.nodes[index], index, html)
+
+ filter.send(method_name, *args) do
+ html
+ end
+ end
+ end
+ end
+ end
+
+ RSpec.shared_examples 'replaces document node' do |method_name|
+ context 'when parent has only one node' do
+ let(:nodes) { [node] }
+
+ it_behaves_like 'replaces text', method_name, 0
+ end
+
+ context 'when parent has multiple nodes' do
+ let(:node1) { Nokogiri::HTML.fragment('<span>span text</span>') }
+ let(:node2) { Nokogiri::HTML.fragment('<span>text</span>') }
+
+ context 'when pattern matches in the first node' do
+ let(:nodes) { [node, node1, node2] }
+
+ it_behaves_like 'replaces text', method_name, 0
+ end
+
+ context 'when pattern matches in the middle node' do
+ let(:nodes) { [node1, node, node2] }
+
+ it_behaves_like 'replaces text', method_name, 1
+ end
+
+ context 'when pattern matches in the last node' do
+ let(:nodes) { [node1, node2, node] }
+
+ it_behaves_like 'replaces text', method_name, 2
+ end
+ end
+ end
+
+ describe '#replace_text_when_pattern_matches' do
+ include_context 'document nodes'
+ let(:node) { Nokogiri::HTML.fragment('text @reference') }
+
+ let(:ref_pattern) { %r{(?<!\w)@(?<user>[a-zA-Z0-9_\-\.]*)}x }
+
+ context 'when node has no reference pattern' do
+ let(:node) { Nokogiri::HTML.fragment('random text') }
+ let(:nodes) { [node] }
+
+ it 'skips node' do
+ expect { |b| filter.replace_text_when_pattern_matches(filter.nodes[0], 0, ref_pattern, &b) }.not_to yield_control
+ end
+ end
+
+ it_behaves_like 'replaces document node', :replace_text_when_pattern_matches do
+ let(:existing_content) { node.to_html }
+ end
+ end
+
+ describe '#replace_link_node_with_text' do
+ include_context 'document nodes'
+ let(:node) { Nokogiri::HTML.fragment('<a>end text</a>') }
+
+ it_behaves_like 'replaces document node', :replace_link_node_with_text do
+ let(:existing_content) { node.text }
+ end
+ end
+
+ describe '#replace_link_node_with_href' do
+ include_context 'document nodes'
+ let(:node) { Nokogiri::HTML.fragment('<a href="link">end text</a>') }
+ let(:href_link) { CGI.unescape(node.attr('href').to_s) }
+
+ it_behaves_like 'replaces document node', :replace_link_node_with_href do
+ let(:existing_content) { href_link }
+ end
+ end
+
+ describe "#call_and_update_nodes" do
+ context "with update_nodes_for_banzai_reference_filter feature flag enabled" do
+ include_context 'new nodes'
+ let(:document) { Nokogiri::HTML.fragment('<a href="foo">foo</a>') }
+ let(:filter) { described_class.new(document, project: project) }
+
+ before do
+ stub_feature_flags(update_nodes_for_banzai_reference_filter: true)
+ end
+
+ it "updates all new nodes", :aggregate_failures do
+ filter.instance_variable_set('@nodes', nodes)
+
+ expect(filter).to receive(:call) { filter.instance_variable_set('@new_nodes', new_nodes) }
+ expect(filter).to receive(:with_update_nodes).and_call_original
+ expect(filter).to receive(:update_nodes!).and_call_original
+
+ filter.call_and_update_nodes
+
+ expect(filter.result[:reference_filter_nodes]).to eq(expected_nodes)
+ end
+ end
+
+ context "with update_nodes_for_banzai_reference_filter feature flag disabled" do
+ include_context 'new nodes'
+
+ before do
+ stub_feature_flags(update_nodes_for_banzai_reference_filter: false)
+ end
+
+ it "does not change nodes", :aggregate_failures do
+ document = Nokogiri::HTML.fragment('<a href="foo">foo</a>')
+ filter = described_class.new(document, project: project)
+ filter.instance_variable_set('@nodes', nodes)
+
+ expect(filter).to receive(:call) { filter.instance_variable_set('@new_nodes', new_nodes) }
+ expect(filter).not_to receive(:with_update_nodes)
+ expect(filter).not_to receive(:update_nodes!)
+
+ filter.call_and_update_nodes
+
+ expect(filter.nodes).to eq(nodes)
+ expect(filter.result[:reference_filter_nodes]).to be nil
+ end
+ end
+ end
+
+ describe ".call" do
+ include_context 'new nodes'
+
+ let(:document) { Nokogiri::HTML.fragment('<a href="foo">foo</a>') }
+
+ let(:result) { { reference_filter_nodes: nodes } }
+
+ before do
+ stub_feature_flags(update_nodes_for_banzai_reference_filter: true)
+ end
+
+ it "updates all nodes", :aggregate_failures do
+ expect_next_instance_of(described_class) do |filter|
+ expect(filter).to receive(:call_and_update_nodes).and_call_original
+ expect(filter).to receive(:with_update_nodes).and_call_original
+ expect(filter).to receive(:call) { filter.instance_variable_set('@new_nodes', new_nodes) }
+ expect(filter).to receive(:update_nodes!).and_call_original
+ end
+
+ described_class.call(document, { project: project }, result)
+
+ expect(result[:reference_filter_nodes]).to eq(expected_nodes)
+ end
+
+ context "with update_nodes_for_banzai_reference_filter feature flag disabled" do
+ let(:result) { {} }
+
+ before do
+ stub_feature_flags(update_nodes_for_banzai_reference_filter: false)
+ end
+
+ it "updates all nodes", :aggregate_failures do
+ expect_next_instance_of(described_class) do |filter|
+ expect(filter).to receive(:call_and_update_nodes).and_call_original
+ expect(filter).not_to receive(:with_update_nodes)
+ expect(filter).to receive(:call) { filter.instance_variable_set('@new_nodes', new_nodes) }
+ expect(filter).not_to receive(:update_nodes!)
+ end
+
+ described_class.call(document, { project: project }, result)
+
+ expect(result[:reference_filter_nodes]).to be nil
+ end
+ end
+ end
end
diff --git a/spec/lib/banzai/filter/reference_redactor_filter_spec.rb b/spec/lib/banzai/filter/reference_redactor_filter_spec.rb
index 956bc85e53f..ac1cabb34cc 100644
--- a/spec/lib/banzai/filter/reference_redactor_filter_spec.rb
+++ b/spec/lib/banzai/filter/reference_redactor_filter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Banzai::Filter::ReferenceRedactorFilter do
+RSpec.describe Banzai::Filter::ReferenceRedactorFilter do
include ActionView::Helpers::UrlHelper
include FilterSpecHelper
diff --git a/spec/lib/banzai/filter/repository_link_filter_spec.rb b/spec/lib/banzai/filter/repository_link_filter_spec.rb
index 81f93f885f7..815053aac2f 100644
--- a/spec/lib/banzai/filter/repository_link_filter_spec.rb
+++ b/spec/lib/banzai/filter/repository_link_filter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Banzai::Filter::RepositoryLinkFilter do
+RSpec.describe Banzai::Filter::RepositoryLinkFilter do
include GitHelpers
include RepoHelpers
diff --git a/spec/lib/banzai/filter/sanitization_filter_spec.rb b/spec/lib/banzai/filter/sanitization_filter_spec.rb
index 607dc3fda47..09dcd5518ff 100644
--- a/spec/lib/banzai/filter/sanitization_filter_spec.rb
+++ b/spec/lib/banzai/filter/sanitization_filter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Banzai::Filter::SanitizationFilter do
+RSpec.describe Banzai::Filter::SanitizationFilter do
include FilterSpecHelper
it_behaves_like 'default whitelist'
diff --git a/spec/lib/banzai/filter/snippet_reference_filter_spec.rb b/spec/lib/banzai/filter/snippet_reference_filter_spec.rb
index 62ce12406a2..f23fbc5be88 100644
--- a/spec/lib/banzai/filter/snippet_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/snippet_reference_filter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Banzai::Filter::SnippetReferenceFilter do
+RSpec.describe Banzai::Filter::SnippetReferenceFilter do
include FilterSpecHelper
let(:project) { create(:project, :public) }
diff --git a/spec/lib/banzai/filter/spaced_link_filter_spec.rb b/spec/lib/banzai/filter/spaced_link_filter_spec.rb
index 98c38813144..2c64657d69d 100644
--- a/spec/lib/banzai/filter/spaced_link_filter_spec.rb
+++ b/spec/lib/banzai/filter/spaced_link_filter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Banzai::Filter::SpacedLinkFilter do
+RSpec.describe Banzai::Filter::SpacedLinkFilter do
include FilterSpecHelper
let(:link) { '[example](page slug)' }
diff --git a/spec/lib/banzai/filter/suggestion_filter_spec.rb b/spec/lib/banzai/filter/suggestion_filter_spec.rb
index 9c4650b73de..7d6092e21e9 100644
--- a/spec/lib/banzai/filter/suggestion_filter_spec.rb
+++ b/spec/lib/banzai/filter/suggestion_filter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Banzai::Filter::SuggestionFilter do
+RSpec.describe Banzai::Filter::SuggestionFilter do
include FilterSpecHelper
let(:input) { %(<pre class="code highlight js-syntax-highlight suggestion"><code>foo\n</code></pre>) }
diff --git a/spec/lib/banzai/filter/syntax_highlight_filter_spec.rb b/spec/lib/banzai/filter/syntax_highlight_filter_spec.rb
index 5a844fb61e3..a2875fad421 100644
--- a/spec/lib/banzai/filter/syntax_highlight_filter_spec.rb
+++ b/spec/lib/banzai/filter/syntax_highlight_filter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Banzai::Filter::SyntaxHighlightFilter do
+RSpec.describe Banzai::Filter::SyntaxHighlightFilter do
include FilterSpecHelper
shared_examples "XSS prevention" do |lang|
diff --git a/spec/lib/banzai/filter/table_of_contents_filter_spec.rb b/spec/lib/banzai/filter/table_of_contents_filter_spec.rb
index 05ef77c811a..2d17855707f 100644
--- a/spec/lib/banzai/filter/table_of_contents_filter_spec.rb
+++ b/spec/lib/banzai/filter/table_of_contents_filter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Banzai::Filter::TableOfContentsFilter do
+RSpec.describe Banzai::Filter::TableOfContentsFilter do
include FilterSpecHelper
def header(level, text)
diff --git a/spec/lib/banzai/filter/table_of_contents_tag_filter_spec.rb b/spec/lib/banzai/filter/table_of_contents_tag_filter_spec.rb
index 20f32d7347d..56f36af5066 100644
--- a/spec/lib/banzai/filter/table_of_contents_tag_filter_spec.rb
+++ b/spec/lib/banzai/filter/table_of_contents_tag_filter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Banzai::Filter::TableOfContentsTagFilter do
+RSpec.describe Banzai::Filter::TableOfContentsTagFilter do
include FilterSpecHelper
context 'table of contents' do
diff --git a/spec/lib/banzai/filter/upload_link_filter_spec.rb b/spec/lib/banzai/filter/upload_link_filter_spec.rb
index 8844ad78306..0f8c773c68d 100644
--- a/spec/lib/banzai/filter/upload_link_filter_spec.rb
+++ b/spec/lib/banzai/filter/upload_link_filter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Banzai::Filter::UploadLinkFilter do
+RSpec.describe Banzai::Filter::UploadLinkFilter do
def filter(doc, contexts = {})
contexts.reverse_merge!(
project: project,
diff --git a/spec/lib/banzai/filter/user_reference_filter_spec.rb b/spec/lib/banzai/filter/user_reference_filter_spec.rb
index cd6b68343b5..d8de3e5cc11 100644
--- a/spec/lib/banzai/filter/user_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/user_reference_filter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Banzai::Filter::UserReferenceFilter do
+RSpec.describe Banzai::Filter::UserReferenceFilter do
include FilterSpecHelper
def get_reference(user)
diff --git a/spec/lib/banzai/filter/video_link_filter_spec.rb b/spec/lib/banzai/filter/video_link_filter_spec.rb
index c324c36fe4d..32fbc6b687f 100644
--- a/spec/lib/banzai/filter/video_link_filter_spec.rb
+++ b/spec/lib/banzai/filter/video_link_filter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Banzai::Filter::VideoLinkFilter do
+RSpec.describe Banzai::Filter::VideoLinkFilter do
def filter(doc, contexts = {})
contexts.reverse_merge!({
project: project
diff --git a/spec/lib/banzai/filter/wiki_link_filter_spec.rb b/spec/lib/banzai/filter/wiki_link_filter_spec.rb
index 827f38ef717..7a4464a2604 100644
--- a/spec/lib/banzai/filter/wiki_link_filter_spec.rb
+++ b/spec/lib/banzai/filter/wiki_link_filter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Banzai::Filter::WikiLinkFilter do
+RSpec.describe Banzai::Filter::WikiLinkFilter do
include FilterSpecHelper
let(:namespace) { build_stubbed(:namespace, name: "wiki_link_ns") }
diff --git a/spec/lib/banzai/filter_array_spec.rb b/spec/lib/banzai/filter_array_spec.rb
index bed41a80d29..47bc5633300 100644
--- a/spec/lib/banzai/filter_array_spec.rb
+++ b/spec/lib/banzai/filter_array_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Banzai::FilterArray do
+RSpec.describe Banzai::FilterArray do
describe '#insert_after' do
it 'inserts an element after a provided element' do
filters = described_class.new(%w(a b c))
diff --git a/spec/lib/banzai/issuable_extractor_spec.rb b/spec/lib/banzai/issuable_extractor_spec.rb
index 7fa6048c1c6..c4ee7160e12 100644
--- a/spec/lib/banzai/issuable_extractor_spec.rb
+++ b/spec/lib/banzai/issuable_extractor_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Banzai::IssuableExtractor do
+RSpec.describe Banzai::IssuableExtractor do
let(:project) { create(:project) }
let(:user) { create(:user) }
let(:extractor) { described_class.new(Banzai::RenderContext.new(project, user)) }
diff --git a/spec/lib/banzai/object_renderer_spec.rb b/spec/lib/banzai/object_renderer_spec.rb
index aef11775e60..f8d7acd3148 100644
--- a/spec/lib/banzai/object_renderer_spec.rb
+++ b/spec/lib/banzai/object_renderer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Banzai::ObjectRenderer do
+RSpec.describe Banzai::ObjectRenderer do
let(:project) { create(:project, :repository) }
let(:user) { project.owner }
let(:renderer) do
diff --git a/spec/lib/banzai/pipeline/broadcast_message_pipeline_spec.rb b/spec/lib/banzai/pipeline/broadcast_message_pipeline_spec.rb
index 9832b132b58..41a91c56f3b 100644
--- a/spec/lib/banzai/pipeline/broadcast_message_pipeline_spec.rb
+++ b/spec/lib/banzai/pipeline/broadcast_message_pipeline_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Banzai::Pipeline::BroadcastMessagePipeline do
+RSpec.describe Banzai::Pipeline::BroadcastMessagePipeline do
before do
stub_commonmark_sourcepos_disabled
end
diff --git a/spec/lib/banzai/pipeline/description_pipeline_spec.rb b/spec/lib/banzai/pipeline/description_pipeline_spec.rb
index 6778a273bba..82d4f883e0d 100644
--- a/spec/lib/banzai/pipeline/description_pipeline_spec.rb
+++ b/spec/lib/banzai/pipeline/description_pipeline_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Banzai::Pipeline::DescriptionPipeline do
+RSpec.describe Banzai::Pipeline::DescriptionPipeline do
let_it_be(:project) { create(:project) }
def parse(html)
diff --git a/spec/lib/banzai/pipeline/email_pipeline_spec.rb b/spec/lib/banzai/pipeline/email_pipeline_spec.rb
index 77186861225..c7a0b9fefa1 100644
--- a/spec/lib/banzai/pipeline/email_pipeline_spec.rb
+++ b/spec/lib/banzai/pipeline/email_pipeline_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Banzai::Pipeline::EmailPipeline do
+RSpec.describe Banzai::Pipeline::EmailPipeline do
describe '.filters' do
it 'returns the expected type' do
expect(described_class.filters).to be_kind_of(Banzai::FilterArray)
diff --git a/spec/lib/banzai/pipeline/emoji_pipeline_spec.rb b/spec/lib/banzai/pipeline/emoji_pipeline_spec.rb
index 744df3e0b96..6de9d65f1b2 100644
--- a/spec/lib/banzai/pipeline/emoji_pipeline_spec.rb
+++ b/spec/lib/banzai/pipeline/emoji_pipeline_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Banzai::Pipeline::EmojiPipeline do
+RSpec.describe Banzai::Pipeline::EmojiPipeline do
def parse(text)
described_class.to_html(text, {})
end
diff --git a/spec/lib/banzai/pipeline/full_pipeline_spec.rb b/spec/lib/banzai/pipeline/full_pipeline_spec.rb
index b4047e369fb..0127ac11c81 100644
--- a/spec/lib/banzai/pipeline/full_pipeline_spec.rb
+++ b/spec/lib/banzai/pipeline/full_pipeline_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Banzai::Pipeline::FullPipeline do
+RSpec.describe Banzai::Pipeline::FullPipeline do
describe 'References' do
let(:project) { create(:project, :public) }
let(:issue) { create(:issue, project: project) }
diff --git a/spec/lib/banzai/pipeline/gfm_pipeline_spec.rb b/spec/lib/banzai/pipeline/gfm_pipeline_spec.rb
index 448422b15e3..beb760637b0 100644
--- a/spec/lib/banzai/pipeline/gfm_pipeline_spec.rb
+++ b/spec/lib/banzai/pipeline/gfm_pipeline_spec.rb
@@ -2,12 +2,63 @@
require 'spec_helper'
-describe Banzai::Pipeline::GfmPipeline do
+RSpec.describe Banzai::Pipeline::GfmPipeline do
describe 'integration between parsing regular and external issue references' do
let(:project) { create(:redmine_project, :public) }
context 'when internal issue tracker is enabled' do
context 'when shorthand pattern #ISSUE_ID is used' do
+ it 'links an internal issues and keep updated nodes in result[:reference_filter_nodes]', :aggregate_failures do
+ issue = create(:issue, project: project)
+ markdown = "text #{issue.to_reference(project, full: true)}"
+
+ result = described_class.call(markdown, project: project)
+ link = result[:output].css('a').first
+ text = result[:output].children.first
+
+ expect(link['href']).to eq(Gitlab::Routing.url_helpers.project_issue_path(project, issue))
+ expect(result[:reference_filter_nodes]).to eq([text])
+ end
+ end
+
+ it 'executes :each_node only once for first reference filter', :aggregate_failures do
+ issue = create(:issue, project: project)
+ markdown = "text #{issue.to_reference(project, full: true)}"
+
+ expect_any_instance_of(Banzai::Filter::ReferenceFilter).to receive(:each_node).once
+
+ described_class.call(markdown, project: project)
+ end
+
+ context "with update_nodes_for_banzai_reference_filter feature flag disabled" do
+ before do
+ stub_feature_flags(update_nodes_for_banzai_reference_filter: false)
+ end
+
+ context 'when shorthand pattern #ISSUE_ID is used' do
+ it 'links an internal issues and doesnt store nodes in result[:reference_filter_nodes]', :aggregate_failures do
+ issue = create(:issue, project: project)
+ markdown = "text #{issue.to_reference(project, full: true)}"
+ result = described_class.call(markdown, project: project)
+ link = result[:output].css('a').first
+
+ expect(link['href']).to eq(Gitlab::Routing.url_helpers.project_issue_path(project, issue))
+ expect(result[:reference_filter_nodes]).to eq nil
+ end
+ end
+
+ it 'execute :each_node for each reference_filter', :aggregate_failures do
+ issue = create(:issue, project: project)
+ markdown = "text #{issue.to_reference(project, full: true)}"
+ described_class.reference_filters do |reference_filter|
+ expect_any_instance_of(reference_filter).to receive(:each_node).once
+ end
+
+ described_class.call(markdown, project: project)
+ end
+ end
+
+ context 'when shorthand pattern #ISSUE_ID is used' do
it 'links an internal issue if it exists' do
issue = create(:issue, project: project)
markdown = issue.to_reference(project, full: true)
diff --git a/spec/lib/banzai/pipeline/jira_import/adf_commonmark_pipeline_spec.rb b/spec/lib/banzai/pipeline/jira_import/adf_commonmark_pipeline_spec.rb
new file mode 100644
index 00000000000..d8841a9753e
--- /dev/null
+++ b/spec/lib/banzai/pipeline/jira_import/adf_commonmark_pipeline_spec.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Banzai::Pipeline::JiraImport::AdfCommonmarkPipeline do
+ let_it_be(:fixtures_path) { 'lib/kramdown/atlassian_document_format' }
+
+ it 'converts text in Atlassian Document Format ' do
+ source = fixture_file(File.join(fixtures_path, 'paragraph.json'))
+ target = fixture_file(File.join(fixtures_path, 'paragraph.md'))
+ output = described_class.call(source, {})[:output]
+
+ expect(output).to eq target
+ end
+end
diff --git a/spec/lib/banzai/pipeline/post_process_pipeline_spec.rb b/spec/lib/banzai/pipeline/post_process_pipeline_spec.rb
index ab72354edcf..d9f45769550 100644
--- a/spec/lib/banzai/pipeline/post_process_pipeline_spec.rb
+++ b/spec/lib/banzai/pipeline/post_process_pipeline_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Banzai::Pipeline::PostProcessPipeline do
+RSpec.describe Banzai::Pipeline::PostProcessPipeline do
context 'when a document only has upload links' do
it 'does not make any Gitaly calls', :request_store do
markdown = <<-MARKDOWN.strip_heredoc
diff --git a/spec/lib/banzai/pipeline/wiki_pipeline_spec.rb b/spec/lib/banzai/pipeline/wiki_pipeline_spec.rb
index b2c24284eb9..4af782c7d73 100644
--- a/spec/lib/banzai/pipeline/wiki_pipeline_spec.rb
+++ b/spec/lib/banzai/pipeline/wiki_pipeline_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Banzai::Pipeline::WikiPipeline do
+RSpec.describe Banzai::Pipeline::WikiPipeline do
let_it_be(:namespace) { create(:namespace, name: "wiki_link_ns") }
let_it_be(:project) { create(:project, :public, name: "wiki_link_project", namespace: namespace) }
let_it_be(:wiki) { ProjectWiki.new(project, double(:user)) }
diff --git a/spec/lib/banzai/pipeline_spec.rb b/spec/lib/banzai/pipeline_spec.rb
index eeff7287ff5..7d4df2ca5ce 100644
--- a/spec/lib/banzai/pipeline_spec.rb
+++ b/spec/lib/banzai/pipeline_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Banzai::Pipeline do
+RSpec.describe Banzai::Pipeline do
describe '.[]' do
subject { described_class[name] }
diff --git a/spec/lib/banzai/querying_spec.rb b/spec/lib/banzai/querying_spec.rb
index b7a235b0558..b76f6ec533c 100644
--- a/spec/lib/banzai/querying_spec.rb
+++ b/spec/lib/banzai/querying_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Banzai::Querying do
+RSpec.describe Banzai::Querying do
describe '.css' do
it 'optimizes queries for elements with classes' do
document = double(:document)
diff --git a/spec/lib/banzai/reference_parser/base_parser_spec.rb b/spec/lib/banzai/reference_parser/base_parser_spec.rb
index b1002c1db25..0eea51262ba 100644
--- a/spec/lib/banzai/reference_parser/base_parser_spec.rb
+++ b/spec/lib/banzai/reference_parser/base_parser_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Banzai::ReferenceParser::BaseParser do
+RSpec.describe Banzai::ReferenceParser::BaseParser do
include ReferenceParserHelpers
let(:user) { create(:user) }
diff --git a/spec/lib/banzai/reference_parser/commit_parser_spec.rb b/spec/lib/banzai/reference_parser/commit_parser_spec.rb
index 7f7c750fe74..612ce6b93f1 100644
--- a/spec/lib/banzai/reference_parser/commit_parser_spec.rb
+++ b/spec/lib/banzai/reference_parser/commit_parser_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Banzai::ReferenceParser::CommitParser do
+RSpec.describe Banzai::ReferenceParser::CommitParser do
include ReferenceParserHelpers
let(:project) { create(:project, :public) }
diff --git a/spec/lib/banzai/reference_parser/commit_range_parser_spec.rb b/spec/lib/banzai/reference_parser/commit_range_parser_spec.rb
index 78b337466aa..2f64aef4fb7 100644
--- a/spec/lib/banzai/reference_parser/commit_range_parser_spec.rb
+++ b/spec/lib/banzai/reference_parser/commit_range_parser_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Banzai::ReferenceParser::CommitRangeParser do
+RSpec.describe Banzai::ReferenceParser::CommitRangeParser do
include ReferenceParserHelpers
let(:project) { create(:project, :public) }
diff --git a/spec/lib/banzai/reference_parser/design_parser_spec.rb b/spec/lib/banzai/reference_parser/design_parser_spec.rb
index 76708acf887..92d3a4aaad2 100644
--- a/spec/lib/banzai/reference_parser/design_parser_spec.rb
+++ b/spec/lib/banzai/reference_parser/design_parser_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Banzai::ReferenceParser::DesignParser do
+RSpec.describe Banzai::ReferenceParser::DesignParser do
include ReferenceParserHelpers
include DesignManagementTestHelpers
diff --git a/spec/lib/banzai/reference_parser/external_issue_parser_spec.rb b/spec/lib/banzai/reference_parser/external_issue_parser_spec.rb
index 9343d52e44b..5f92eb42e74 100644
--- a/spec/lib/banzai/reference_parser/external_issue_parser_spec.rb
+++ b/spec/lib/banzai/reference_parser/external_issue_parser_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Banzai::ReferenceParser::ExternalIssueParser do
+RSpec.describe Banzai::ReferenceParser::ExternalIssueParser do
include ReferenceParserHelpers
let(:project) { create(:project, :public) }
diff --git a/spec/lib/banzai/reference_parser/issue_parser_spec.rb b/spec/lib/banzai/reference_parser/issue_parser_spec.rb
index ac321aca5e9..76f13e7b3aa 100644
--- a/spec/lib/banzai/reference_parser/issue_parser_spec.rb
+++ b/spec/lib/banzai/reference_parser/issue_parser_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Banzai::ReferenceParser::IssueParser do
+RSpec.describe Banzai::ReferenceParser::IssueParser do
include ReferenceParserHelpers
let(:project) { create(:project, :public) }
diff --git a/spec/lib/banzai/reference_parser/label_parser_spec.rb b/spec/lib/banzai/reference_parser/label_parser_spec.rb
index 8b66a891e69..8f287e15b37 100644
--- a/spec/lib/banzai/reference_parser/label_parser_spec.rb
+++ b/spec/lib/banzai/reference_parser/label_parser_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Banzai::ReferenceParser::LabelParser do
+RSpec.describe Banzai::ReferenceParser::LabelParser do
include ReferenceParserHelpers
let(:project) { create(:project, :public) }
diff --git a/spec/lib/banzai/reference_parser/mentioned_group_parser_spec.rb b/spec/lib/banzai/reference_parser/mentioned_group_parser_spec.rb
index 8346ba93f88..4610da7cbe6 100644
--- a/spec/lib/banzai/reference_parser/mentioned_group_parser_spec.rb
+++ b/spec/lib/banzai/reference_parser/mentioned_group_parser_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Banzai::ReferenceParser::MentionedGroupParser do
+RSpec.describe Banzai::ReferenceParser::MentionedGroupParser do
include ReferenceParserHelpers
let(:group) { create(:group, :private) }
diff --git a/spec/lib/banzai/reference_parser/mentioned_project_parser_spec.rb b/spec/lib/banzai/reference_parser/mentioned_project_parser_spec.rb
index b99c02351d0..7eb58ee40d3 100644
--- a/spec/lib/banzai/reference_parser/mentioned_project_parser_spec.rb
+++ b/spec/lib/banzai/reference_parser/mentioned_project_parser_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Banzai::ReferenceParser::MentionedProjectParser do
+RSpec.describe Banzai::ReferenceParser::MentionedProjectParser do
include ReferenceParserHelpers
let(:group) { create(:group, :private) }
diff --git a/spec/lib/banzai/reference_parser/mentioned_user_parser_spec.rb b/spec/lib/banzai/reference_parser/mentioned_user_parser_spec.rb
index b10e5d19828..4be07866db1 100644
--- a/spec/lib/banzai/reference_parser/mentioned_user_parser_spec.rb
+++ b/spec/lib/banzai/reference_parser/mentioned_user_parser_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Banzai::ReferenceParser::MentionedUserParser do
+RSpec.describe Banzai::ReferenceParser::MentionedUserParser do
include ReferenceParserHelpers
let(:group) { create(:group, :private) }
diff --git a/spec/lib/banzai/reference_parser/merge_request_parser_spec.rb b/spec/lib/banzai/reference_parser/merge_request_parser_spec.rb
index cb65893aea0..32a9f09c3f6 100644
--- a/spec/lib/banzai/reference_parser/merge_request_parser_spec.rb
+++ b/spec/lib/banzai/reference_parser/merge_request_parser_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Banzai::ReferenceParser::MergeRequestParser do
+RSpec.describe Banzai::ReferenceParser::MergeRequestParser do
include ReferenceParserHelpers
let(:user) { create(:user) }
diff --git a/spec/lib/banzai/reference_parser/milestone_parser_spec.rb b/spec/lib/banzai/reference_parser/milestone_parser_spec.rb
index 25ba41dd8a0..95f71154e38 100644
--- a/spec/lib/banzai/reference_parser/milestone_parser_spec.rb
+++ b/spec/lib/banzai/reference_parser/milestone_parser_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Banzai::ReferenceParser::MilestoneParser do
+RSpec.describe Banzai::ReferenceParser::MilestoneParser do
include ReferenceParserHelpers
let(:project) { create(:project, :public) }
diff --git a/spec/lib/banzai/reference_parser/project_parser_spec.rb b/spec/lib/banzai/reference_parser/project_parser_spec.rb
index e87fa3e8767..6358a04f12a 100644
--- a/spec/lib/banzai/reference_parser/project_parser_spec.rb
+++ b/spec/lib/banzai/reference_parser/project_parser_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Banzai::ReferenceParser::ProjectParser do
+RSpec.describe Banzai::ReferenceParser::ProjectParser do
include ReferenceParserHelpers
let(:project) { create(:project, :public) }
diff --git a/spec/lib/banzai/reference_parser/snippet_parser_spec.rb b/spec/lib/banzai/reference_parser/snippet_parser_spec.rb
index 6581ed0d7c3..cdc660b4f4a 100644
--- a/spec/lib/banzai/reference_parser/snippet_parser_spec.rb
+++ b/spec/lib/banzai/reference_parser/snippet_parser_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Banzai::ReferenceParser::SnippetParser do
+RSpec.describe Banzai::ReferenceParser::SnippetParser do
include ReferenceParserHelpers
let(:project) { create(:project, :public) }
diff --git a/spec/lib/banzai/reference_parser/user_parser_spec.rb b/spec/lib/banzai/reference_parser/user_parser_spec.rb
index 71d2e1de3b6..d4f4339cf17 100644
--- a/spec/lib/banzai/reference_parser/user_parser_spec.rb
+++ b/spec/lib/banzai/reference_parser/user_parser_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Banzai::ReferenceParser::UserParser do
+RSpec.describe Banzai::ReferenceParser::UserParser do
include ReferenceParserHelpers
let(:group) { create(:group) }
diff --git a/spec/lib/banzai/reference_redactor_spec.rb b/spec/lib/banzai/reference_redactor_spec.rb
index 0dec6395fb3..de774267b81 100644
--- a/spec/lib/banzai/reference_redactor_spec.rb
+++ b/spec/lib/banzai/reference_redactor_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Banzai::ReferenceRedactor do
+RSpec.describe Banzai::ReferenceRedactor do
let(:user) { create(:user) }
let(:project) { build(:project) }
let(:redactor) { described_class.new(Banzai::RenderContext.new(project, user)) }
diff --git a/spec/lib/banzai/render_context_spec.rb b/spec/lib/banzai/render_context_spec.rb
index ad17db11613..c4b609b936e 100644
--- a/spec/lib/banzai/render_context_spec.rb
+++ b/spec/lib/banzai/render_context_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Banzai::RenderContext do
+RSpec.describe Banzai::RenderContext do
let(:document) { Nokogiri::HTML.fragment('<p>hello</p>') }
describe '#project_for_node' do
diff --git a/spec/lib/banzai/renderer_spec.rb b/spec/lib/banzai/renderer_spec.rb
index b540a76face..52bf3087875 100644
--- a/spec/lib/banzai/renderer_spec.rb
+++ b/spec/lib/banzai/renderer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Banzai::Renderer do
+RSpec.describe Banzai::Renderer do
let(:renderer) { described_class }
def fake_object(fresh:)
diff --git a/spec/lib/bitbucket/collection_spec.rb b/spec/lib/bitbucket/collection_spec.rb
index 5946be71565..349274585c4 100644
--- a/spec/lib/bitbucket/collection_spec.rb
+++ b/spec/lib/bitbucket/collection_spec.rb
@@ -17,7 +17,7 @@ class TestPaginator
end
end
-describe Bitbucket::Collection do
+RSpec.describe Bitbucket::Collection do
it "iterates paginator" do
collection = described_class.new(TestPaginator.new)
diff --git a/spec/lib/bitbucket/connection_spec.rb b/spec/lib/bitbucket/connection_spec.rb
index 5aca93767dc..bed44b94f44 100644
--- a/spec/lib/bitbucket/connection_spec.rb
+++ b/spec/lib/bitbucket/connection_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Bitbucket::Connection do
+RSpec.describe Bitbucket::Connection do
before do
allow_next_instance_of(described_class) do |instance|
allow(instance).to receive(:provider).and_return(double(app_id: '', app_secret: ''))
diff --git a/spec/lib/bitbucket/page_spec.rb b/spec/lib/bitbucket/page_spec.rb
index 6301dd56faf..1d599007d9e 100644
--- a/spec/lib/bitbucket/page_spec.rb
+++ b/spec/lib/bitbucket/page_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Bitbucket::Page do
+RSpec.describe Bitbucket::Page do
let(:response) { { 'values' => [{ 'username' => 'Ben' }], 'pagelen' => 2, 'next' => '' } }
before do
diff --git a/spec/lib/bitbucket/paginator_spec.rb b/spec/lib/bitbucket/paginator_spec.rb
index a1effa14000..e74af8a264b 100644
--- a/spec/lib/bitbucket/paginator_spec.rb
+++ b/spec/lib/bitbucket/paginator_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Bitbucket::Paginator do
+RSpec.describe Bitbucket::Paginator do
let(:last_page) { double(:page, next?: false, items: ['item_2']) }
let(:first_page) { double(:page, next?: true, next: last_page, items: ['item_1']) }
diff --git a/spec/lib/bitbucket/representation/comment_spec.rb b/spec/lib/bitbucket/representation/comment_spec.rb
index 1874296df8c..f6766ab685b 100644
--- a/spec/lib/bitbucket/representation/comment_spec.rb
+++ b/spec/lib/bitbucket/representation/comment_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Bitbucket::Representation::Comment do
+RSpec.describe Bitbucket::Representation::Comment do
describe '#author' do
it { expect(described_class.new('user' => { 'nickname' => 'Ben' }).author).to eq('Ben') }
it { expect(described_class.new({}).author).to be_nil }
diff --git a/spec/lib/bitbucket/representation/issue_spec.rb b/spec/lib/bitbucket/representation/issue_spec.rb
index 655b9b78b47..8c27086546f 100644
--- a/spec/lib/bitbucket/representation/issue_spec.rb
+++ b/spec/lib/bitbucket/representation/issue_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Bitbucket::Representation::Issue do
+RSpec.describe Bitbucket::Representation::Issue do
describe '#iid' do
it { expect(described_class.new('id' => 1).iid).to eq(1) }
end
diff --git a/spec/lib/bitbucket/representation/pull_request_comment_spec.rb b/spec/lib/bitbucket/representation/pull_request_comment_spec.rb
index 151055f510f..cdab683492f 100644
--- a/spec/lib/bitbucket/representation/pull_request_comment_spec.rb
+++ b/spec/lib/bitbucket/representation/pull_request_comment_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Bitbucket::Representation::PullRequestComment do
+RSpec.describe Bitbucket::Representation::PullRequestComment do
describe '#iid' do
it { expect(described_class.new('id' => 1).iid).to eq(1) }
end
diff --git a/spec/lib/bitbucket/representation/pull_request_spec.rb b/spec/lib/bitbucket/representation/pull_request_spec.rb
index 6a9df0e5099..6f05d03aa0a 100644
--- a/spec/lib/bitbucket/representation/pull_request_spec.rb
+++ b/spec/lib/bitbucket/representation/pull_request_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Bitbucket::Representation::PullRequest do
+RSpec.describe Bitbucket::Representation::PullRequest do
describe '#iid' do
it { expect(described_class.new('id' => 1).iid).to eq(1) }
end
diff --git a/spec/lib/bitbucket/representation/repo_spec.rb b/spec/lib/bitbucket/representation/repo_spec.rb
index a272695e681..a779a153f25 100644
--- a/spec/lib/bitbucket/representation/repo_spec.rb
+++ b/spec/lib/bitbucket/representation/repo_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Bitbucket::Representation::Repo do
+RSpec.describe Bitbucket::Representation::Repo do
describe '#has_wiki?' do
it { expect(described_class.new({ 'has_wiki' => false }).has_wiki?).to be_falsey }
it { expect(described_class.new({ 'has_wiki' => true }).has_wiki?).to be_truthy }
diff --git a/spec/lib/bitbucket/representation/user_spec.rb b/spec/lib/bitbucket/representation/user_spec.rb
index 0169887a24c..e1f6c724da8 100644
--- a/spec/lib/bitbucket/representation/user_spec.rb
+++ b/spec/lib/bitbucket/representation/user_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Bitbucket::Representation::User do
+RSpec.describe Bitbucket::Representation::User do
describe '#username' do
it 'returns correct value' do
user = described_class.new('username' => 'Ben')
diff --git a/spec/lib/bitbucket_server/client_spec.rb b/spec/lib/bitbucket_server/client_spec.rb
index aa0217856ee..9dcd1500aab 100644
--- a/spec/lib/bitbucket_server/client_spec.rb
+++ b/spec/lib/bitbucket_server/client_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe BitbucketServer::Client do
+RSpec.describe BitbucketServer::Client do
let(:base_uri) { 'https://test:7990/stash/' }
let(:options) { { base_uri: base_uri, user: 'bitbucket', password: 'mypassword' } }
let(:project) { 'SOME-PROJECT' }
diff --git a/spec/lib/bitbucket_server/collection_spec.rb b/spec/lib/bitbucket_server/collection_spec.rb
index ddd02bac88a..1a47a30c74b 100644
--- a/spec/lib/bitbucket_server/collection_spec.rb
+++ b/spec/lib/bitbucket_server/collection_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe BitbucketServer::Collection do
+RSpec.describe BitbucketServer::Collection do
let(:connection) { instance_double(BitbucketServer::Connection) }
let(:page) { 1 }
let(:paginator) { BitbucketServer::Paginator.new(connection, 'http://more-data', :pull_request, page_offset: page) }
diff --git a/spec/lib/bitbucket_server/connection_spec.rb b/spec/lib/bitbucket_server/connection_spec.rb
index 3a7fe4e7321..873eded58d7 100644
--- a/spec/lib/bitbucket_server/connection_spec.rb
+++ b/spec/lib/bitbucket_server/connection_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe BitbucketServer::Connection do
+RSpec.describe BitbucketServer::Connection do
let(:options) { { base_uri: 'https://test:7990', user: 'bitbucket', password: 'mypassword' } }
let(:payload) { { 'test' => 1 } }
let(:headers) { { "Content-Type" => "application/json" } }
diff --git a/spec/lib/bitbucket_server/page_spec.rb b/spec/lib/bitbucket_server/page_spec.rb
index 2da1d0995ca..2d4e946e590 100644
--- a/spec/lib/bitbucket_server/page_spec.rb
+++ b/spec/lib/bitbucket_server/page_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe BitbucketServer::Page do
+RSpec.describe BitbucketServer::Page do
let(:response) { { 'values' => [{ 'description' => 'Test' }], 'isLastPage' => false, 'nextPageStart' => 2 } }
before do
diff --git a/spec/lib/bitbucket_server/paginator_spec.rb b/spec/lib/bitbucket_server/paginator_spec.rb
index e01cbeb4270..66bf5bf3f02 100644
--- a/spec/lib/bitbucket_server/paginator_spec.rb
+++ b/spec/lib/bitbucket_server/paginator_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe BitbucketServer::Paginator do
+RSpec.describe BitbucketServer::Paginator do
let(:last_page) { double(:page, next?: false, items: ['item_2']) }
let(:first_page) { double(:page, next?: true, next: last_page, items: ['item_1']) }
let(:connection) { instance_double(BitbucketServer::Connection) }
diff --git a/spec/lib/bitbucket_server/representation/activity_spec.rb b/spec/lib/bitbucket_server/representation/activity_spec.rb
index 6988e77ad25..0b7e4542cbe 100644
--- a/spec/lib/bitbucket_server/representation/activity_spec.rb
+++ b/spec/lib/bitbucket_server/representation/activity_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe BitbucketServer::Representation::Activity do
+RSpec.describe BitbucketServer::Representation::Activity do
let(:activities) { Gitlab::Json.parse(fixture_file('importers/bitbucket_server/activities.json'))['values'] }
let(:inline_comment) { activities.first }
let(:comment) { activities[3] }
diff --git a/spec/lib/bitbucket_server/representation/comment_spec.rb b/spec/lib/bitbucket_server/representation/comment_spec.rb
index ecaf6a843ae..b568789bd97 100644
--- a/spec/lib/bitbucket_server/representation/comment_spec.rb
+++ b/spec/lib/bitbucket_server/representation/comment_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe BitbucketServer::Representation::Comment do
+RSpec.describe BitbucketServer::Representation::Comment do
let(:activities) { Gitlab::Json.parse(fixture_file('importers/bitbucket_server/activities.json'))['values'] }
let(:comment) { activities.first }
diff --git a/spec/lib/bitbucket_server/representation/pull_request_comment_spec.rb b/spec/lib/bitbucket_server/representation/pull_request_comment_spec.rb
index aa3eddf305a..01e56263a5e 100644
--- a/spec/lib/bitbucket_server/representation/pull_request_comment_spec.rb
+++ b/spec/lib/bitbucket_server/representation/pull_request_comment_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe BitbucketServer::Representation::PullRequestComment do
+RSpec.describe BitbucketServer::Representation::PullRequestComment do
let(:activities) { Gitlab::Json.parse(fixture_file('importers/bitbucket_server/activities.json'))['values'] }
let(:comment) { activities.second }
diff --git a/spec/lib/bitbucket_server/representation/pull_request_spec.rb b/spec/lib/bitbucket_server/representation/pull_request_spec.rb
index 7e72da05cb1..a05d98f0d4a 100644
--- a/spec/lib/bitbucket_server/representation/pull_request_spec.rb
+++ b/spec/lib/bitbucket_server/representation/pull_request_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe BitbucketServer::Representation::PullRequest do
+RSpec.describe BitbucketServer::Representation::PullRequest do
let(:sample_data) { Gitlab::Json.parse(fixture_file('importers/bitbucket_server/pull_request.json')) }
subject { described_class.new(sample_data) }
diff --git a/spec/lib/bitbucket_server/representation/repo_spec.rb b/spec/lib/bitbucket_server/representation/repo_spec.rb
index 429b6d36c59..7a773f47ca5 100644
--- a/spec/lib/bitbucket_server/representation/repo_spec.rb
+++ b/spec/lib/bitbucket_server/representation/repo_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe BitbucketServer::Representation::Repo do
+RSpec.describe BitbucketServer::Representation::Repo do
let(:sample_data) do
<<~DATA
{
diff --git a/spec/lib/constraints/admin_constrainer_spec.rb b/spec/lib/constraints/admin_constrainer_spec.rb
index da178f9e71a..3efe683177c 100644
--- a/spec/lib/constraints/admin_constrainer_spec.rb
+++ b/spec/lib/constraints/admin_constrainer_spec.rb
@@ -2,7 +2,7 @@
#
require 'spec_helper'
-describe Constraints::AdminConstrainer, :do_not_mock_admin_mode do
+RSpec.describe Constraints::AdminConstrainer, :do_not_mock_admin_mode do
let(:user) { create(:user) }
let(:session) { {} }
diff --git a/spec/lib/constraints/feature_constrainer_spec.rb b/spec/lib/constraints/feature_constrainer_spec.rb
index 7665d5b3547..c98dc694186 100644
--- a/spec/lib/constraints/feature_constrainer_spec.rb
+++ b/spec/lib/constraints/feature_constrainer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Constraints::FeatureConstrainer do
+RSpec.describe Constraints::FeatureConstrainer do
describe '#matches' do
it 'calls Feature.enabled? with the correct arguments' do
gate = stub_feature_flag_gate("an object")
diff --git a/spec/lib/constraints/group_url_constrainer_spec.rb b/spec/lib/constraints/group_url_constrainer_spec.rb
index 573de331898..45a5b3afd81 100644
--- a/spec/lib/constraints/group_url_constrainer_spec.rb
+++ b/spec/lib/constraints/group_url_constrainer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Constraints::GroupUrlConstrainer do
+RSpec.describe Constraints::GroupUrlConstrainer do
let!(:group) { create(:group, path: 'gitlab') }
describe '#matches?' do
diff --git a/spec/lib/constraints/project_url_constrainer_spec.rb b/spec/lib/constraints/project_url_constrainer_spec.rb
index 963e1d5b8e0..1e8aac8479d 100644
--- a/spec/lib/constraints/project_url_constrainer_spec.rb
+++ b/spec/lib/constraints/project_url_constrainer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Constraints::ProjectUrlConstrainer do
+RSpec.describe Constraints::ProjectUrlConstrainer do
let!(:project) { create(:project) }
let!(:namespace) { project.namespace }
diff --git a/spec/lib/constraints/user_url_constrainer_spec.rb b/spec/lib/constraints/user_url_constrainer_spec.rb
index 4f665def3bf..1b68e966585 100644
--- a/spec/lib/constraints/user_url_constrainer_spec.rb
+++ b/spec/lib/constraints/user_url_constrainer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Constraints::UserUrlConstrainer do
+RSpec.describe Constraints::UserUrlConstrainer do
let!(:user) { create(:user, username: 'dz') }
describe '#matches?' do
diff --git a/spec/lib/container_registry/blob_spec.rb b/spec/lib/container_registry/blob_spec.rb
index be7be2f3719..676adc74be2 100644
--- a/spec/lib/container_registry/blob_spec.rb
+++ b/spec/lib/container_registry/blob_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ContainerRegistry::Blob do
+RSpec.describe ContainerRegistry::Blob do
let(:group) { create(:group, name: 'group') }
let(:project) { create(:project, path: 'test', group: group) }
diff --git a/spec/lib/container_registry/client_spec.rb b/spec/lib/container_registry/client_spec.rb
index 18bcff65f41..de92ca5eeec 100644
--- a/spec/lib/container_registry/client_spec.rb
+++ b/spec/lib/container_registry/client_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ContainerRegistry::Client do
+RSpec.describe ContainerRegistry::Client do
let(:token) { '12345' }
let(:options) { { token: token } }
let(:client) { described_class.new("http://container-registry", options) }
diff --git a/spec/lib/container_registry/path_spec.rb b/spec/lib/container_registry/path_spec.rb
index 8c671b4d56d..aa6876225b5 100644
--- a/spec/lib/container_registry/path_spec.rb
+++ b/spec/lib/container_registry/path_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ContainerRegistry::Path do
+RSpec.describe ContainerRegistry::Path do
subject { described_class.new(path) }
describe '#components' do
diff --git a/spec/lib/container_registry/registry_spec.rb b/spec/lib/container_registry/registry_spec.rb
index e509566fae8..d6e2b17f53b 100644
--- a/spec/lib/container_registry/registry_spec.rb
+++ b/spec/lib/container_registry/registry_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ContainerRegistry::Registry do
+RSpec.describe ContainerRegistry::Registry do
let(:path) { nil }
let(:registry) { described_class.new('http://example.com', path: path) }
diff --git a/spec/lib/container_registry/tag_spec.rb b/spec/lib/container_registry/tag_spec.rb
index 085c73caa97..d696b61ac9d 100644
--- a/spec/lib/container_registry/tag_spec.rb
+++ b/spec/lib/container_registry/tag_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ContainerRegistry::Tag do
+RSpec.describe ContainerRegistry::Tag do
let(:group) { create(:group, name: 'group') }
let(:project) { create(:project, path: 'test', group: group) }
diff --git a/spec/lib/csv_builder_spec.rb b/spec/lib/csv_builder_spec.rb
index 0d5e2b81b16..546be3ba6f7 100644
--- a/spec/lib/csv_builder_spec.rb
+++ b/spec/lib/csv_builder_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe CsvBuilder do
+RSpec.describe CsvBuilder do
let(:object) { double(question: :answer) }
let(:fake_relation) { FakeRelation.new([object]) }
let(:subject) { described_class.new(fake_relation, 'Q & A' => :question, 'Reversed' => -> (o) { o.question.to_s.reverse }) }
diff --git a/spec/lib/declarative_policy/overrides_spec.rb b/spec/lib/declarative_policy/overrides_spec.rb
new file mode 100644
index 00000000000..84dc8f7ac71
--- /dev/null
+++ b/spec/lib/declarative_policy/overrides_spec.rb
@@ -0,0 +1,82 @@
+# frozen_string_literal: true
+
+require 'fast_spec_helper'
+require_dependency 'rspec-parameterized'
+
+RSpec.describe 'DeclarativePolicy overrides' do
+ let(:foo_policy) do
+ Class.new(DeclarativePolicy::Base) do
+ condition(:foo_prop_cond) { @subject.foo_prop }
+
+ rule { foo_prop_cond }.policy do
+ enable :common_ability
+ enable :foo_prop_ability
+ end
+ end
+ end
+
+ let(:bar_policy) do
+ Class.new(DeclarativePolicy::Base) do
+ delegate { @subject.foo }
+
+ overrides :common_ability
+
+ condition(:bar_prop_cond) { @subject.bar_prop }
+
+ rule { bar_prop_cond }.policy do
+ enable :common_ability
+ enable :bar_prop_ability
+ end
+
+ rule { bar_prop_cond & can?(:foo_prop_ability) }.policy do
+ enable :combined_ability
+ end
+ end
+ end
+
+ before do
+ stub_const('Foo', Struct.new(:foo_prop))
+ stub_const('FooPolicy', foo_policy)
+ stub_const('Bar', Struct.new(:foo, :bar_prop))
+ stub_const('BarPolicy', bar_policy)
+ end
+
+ where(:foo_prop, :bar_prop) do
+ [
+ [true, true],
+ [true, false],
+ [false, true],
+ [false, false]
+ ]
+ end
+
+ with_them do
+ let(:foo) { Foo.new(foo_prop) }
+ let(:bar) { Bar.new(foo, bar_prop) }
+
+ it 'determines the correct bar_prop_ability (non-delegated) permissions for bar' do
+ policy = DeclarativePolicy.policy_for(nil, bar)
+ expect(policy.allowed?(:bar_prop_ability)).to eq(bar_prop)
+ end
+
+ it 'determines the correct foo_prop (non-overridden) permissions for bar' do
+ policy = DeclarativePolicy.policy_for(nil, bar)
+ expect(policy.allowed?(:foo_prop_ability)).to eq(foo_prop)
+ end
+
+ it 'determines the correct common_ability (overridden) permissions for bar' do
+ policy = DeclarativePolicy.policy_for(nil, bar)
+ expect(policy.allowed?(:common_ability)).to eq(bar_prop)
+ end
+
+ it 'determines the correct common_ability permissions for foo' do
+ policy = DeclarativePolicy.policy_for(nil, foo)
+ expect(policy.allowed?(:common_ability)).to eq(foo_prop)
+ end
+
+ it 'allows combinations of overridden and inherited values' do
+ policy = DeclarativePolicy.policy_for(nil, bar)
+ expect(policy.allowed?(:combined_ability)).to eq(foo_prop && bar_prop)
+ end
+ end
+end
diff --git a/spec/lib/declarative_policy_spec.rb b/spec/lib/declarative_policy_spec.rb
index 5fdb3c27738..fc21bd43f48 100644
--- a/spec/lib/declarative_policy_spec.rb
+++ b/spec/lib/declarative_policy_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe DeclarativePolicy do
+RSpec.describe DeclarativePolicy do
describe '.class_for' do
it 'uses declarative_policy_class if present' do
instance = Gitlab::ErrorTracking::ErrorEvent.new
diff --git a/spec/lib/event_filter_spec.rb b/spec/lib/event_filter_spec.rb
index da6e1f9458f..bab48796b8c 100644
--- a/spec/lib/event_filter_spec.rb
+++ b/spec/lib/event_filter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe EventFilter do
+RSpec.describe EventFilter do
describe '#filter' do
it 'returns "all" if given filter is nil' do
expect(described_class.new(nil).filter).to eq(described_class::ALL)
@@ -30,6 +30,7 @@ describe EventFilter do
let_it_be(:left_event) { create(:event, :left, project: public_project, target: public_project) }
let_it_be(:wiki_page_event) { create(:wiki_page_event) }
let_it_be(:wiki_page_update_event) { create(:wiki_page_event, :updated) }
+ let_it_be(:design_event) { create(:design_event) }
let(:filtered_events) { described_class.new(filter).apply_filter(Event.all) }
@@ -79,15 +80,13 @@ describe EventFilter do
it 'returns all events' do
expect(filtered_events).to eq(Event.all)
end
+ end
- context 'the :wiki_events filter is disabled' do
- before do
- stub_feature_flags(wiki_events: false)
- end
+ context 'with the "design" filter' do
+ let(:filter) { described_class::DESIGNS }
- it 'does not return wiki events' do
- expect(filtered_events).to eq(Event.not_wiki_page)
- end
+ it 'returns only design events' do
+ expect(filtered_events).to contain_exactly(design_event)
end
end
@@ -97,16 +96,6 @@ describe EventFilter do
it 'returns only wiki page events' do
expect(filtered_events).to contain_exactly(wiki_page_event, wiki_page_update_event)
end
-
- context 'the :wiki_events filter is disabled' do
- before do
- stub_feature_flags(wiki_events: false)
- end
-
- it 'does not return wiki events' do
- expect(filtered_events).not_to include(wiki_page_event, wiki_page_update_event)
- end
- end
end
context 'with an unknown filter' do
@@ -115,16 +104,6 @@ describe EventFilter do
it 'returns all events' do
expect(filtered_events).to eq(Event.all)
end
-
- context 'the :wiki_events filter is disabled' do
- before do
- stub_feature_flags(wiki_events: false)
- end
-
- it 'does not return wiki events' do
- expect(filtered_events).to eq(Event.not_wiki_page)
- end
- end
end
context 'with a nil filter' do
@@ -133,16 +112,6 @@ describe EventFilter do
it 'returns all events' do
expect(filtered_events).to eq(Event.all)
end
-
- context 'the :wiki_events filter is disabled' do
- before do
- stub_feature_flags(wiki_events: false)
- end
-
- it 'does not return wiki events' do
- expect(filtered_events).to eq(Event.not_wiki_page)
- end
- end
end
end
diff --git a/spec/lib/expand_variables_spec.rb b/spec/lib/expand_variables_spec.rb
index 1b8ec2b1979..4a5b70ff248 100644
--- a/spec/lib/expand_variables_spec.rb
+++ b/spec/lib/expand_variables_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ExpandVariables do
+RSpec.describe ExpandVariables do
describe '#expand' do
context 'table tests' do
using RSpec::Parameterized::TableSyntax
diff --git a/spec/lib/extracts_path_spec.rb b/spec/lib/extracts_path_spec.rb
index a56768a1a88..dbb3aa8797e 100644
--- a/spec/lib/extracts_path_spec.rb
+++ b/spec/lib/extracts_path_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ExtractsPath do
+RSpec.describe ExtractsPath do
include described_class
include RepoHelpers
include Gitlab::Routing
diff --git a/spec/lib/extracts_ref_spec.rb b/spec/lib/extracts_ref_spec.rb
index 1867f639711..ca2f1fd7dc1 100644
--- a/spec/lib/extracts_ref_spec.rb
+++ b/spec/lib/extracts_ref_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ExtractsRef do
+RSpec.describe ExtractsRef do
include described_class
include RepoHelpers
diff --git a/spec/lib/feature/definition_spec.rb b/spec/lib/feature/definition_spec.rb
new file mode 100644
index 00000000000..49224cf4279
--- /dev/null
+++ b/spec/lib/feature/definition_spec.rb
@@ -0,0 +1,209 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Feature::Definition do
+ let(:attributes) do
+ { name: 'feature_flag',
+ type: 'development',
+ default_enabled: true }
+ end
+
+ let(:path) { File.join('development', 'feature_flag.yml') }
+ let(:definition) { described_class.new(path, attributes) }
+ let(:yaml_content) { attributes.deep_stringify_keys.to_yaml }
+
+ describe '#key' do
+ subject { definition.key }
+
+ it 'returns a symbol from name' do
+ is_expected.to eq(:feature_flag)
+ end
+ end
+
+ describe '#validate!' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:param, :value, :result) do
+ :name | nil | /Feature flag is missing name/
+ :path | nil | /Feature flag 'feature_flag' is missing path/
+ :type | nil | /Feature flag 'feature_flag' is missing type/
+ :type | 'invalid' | /Feature flag 'feature_flag' type 'invalid' is invalid/
+ :path | 'development/invalid.yml' | /Feature flag 'feature_flag' has an invalid path/
+ :path | 'invalid/feature_flag.yml' | /Feature flag 'feature_flag' has an invalid type/
+ :default_enabled | nil | /Feature flag 'feature_flag' is missing default_enabled/
+ end
+
+ with_them do
+ let(:params) { attributes.merge(path: path) }
+
+ before do
+ params[param] = value
+ end
+
+ it do
+ expect do
+ described_class.new(
+ params[:path], params.except(:path)
+ ).validate!
+ end.to raise_error(result)
+ end
+ end
+ end
+
+ describe '#valid_usage!' do
+ context 'validates type' do
+ it 'raises exception for invalid type' do
+ expect { definition.valid_usage!(type_in_code: :invalid, default_enabled_in_code: false) }
+ .to raise_error(/The `type:` of `feature_flag` is not equal to config/)
+ end
+ end
+
+ context 'validates default enabled' do
+ it 'raises exception for different value' do
+ expect { definition.valid_usage!(type_in_code: :development, default_enabled_in_code: false) }
+ .to raise_error(/The `default_enabled:` of `feature_flag` is not equal to config/)
+ end
+ end
+ end
+
+ describe '.paths' do
+ it 'returns at least one path' do
+ expect(described_class.paths).not_to be_empty
+ end
+ end
+
+ describe '.load_from_file' do
+ it 'properly loads a definition from file' do
+ expect(File).to receive(:read).with(path) { yaml_content }
+
+ expect(described_class.send(:load_from_file, path).attributes)
+ .to eq(definition.attributes)
+ end
+
+ context 'for missing file' do
+ let(:path) { 'missing/feature-flag/file.yml' }
+
+ it 'raises exception' do
+ expect do
+ described_class.send(:load_from_file, path)
+ end.to raise_error(/Invalid definition for/)
+ end
+ end
+
+ context 'for invalid definition' do
+ it 'raises exception' do
+ expect(File).to receive(:read).with(path) { '{}' }
+
+ expect do
+ described_class.send(:load_from_file, path)
+ end.to raise_error(/Feature flag is missing name/)
+ end
+ end
+ end
+
+ describe '.load_all!' do
+ let(:store1) { Dir.mktmpdir('path1') }
+ let(:store2) { Dir.mktmpdir('path2') }
+
+ before do
+ allow(described_class).to receive(:paths).and_return(
+ [
+ File.join(store1, '**', '*.yml'),
+ File.join(store2, '**', '*.yml')
+ ]
+ )
+ end
+
+ it "when there's no feature flags a list of definitions is empty" do
+ expect(described_class.load_all!).to be_empty
+ end
+
+ it "when there's a single feature flag it properly loads them" do
+ write_feature_flag(store1, path, yaml_content)
+
+ expect(described_class.load_all!).to be_one
+ end
+
+ it "when the same feature flag is stored multiple times raises exception" do
+ write_feature_flag(store1, path, yaml_content)
+ write_feature_flag(store2, path, yaml_content)
+
+ expect { described_class.load_all! }
+ .to raise_error(/Feature flag 'feature_flag' is already defined/)
+ end
+
+ it "when one of the YAMLs is invalid it does raise exception" do
+ write_feature_flag(store1, path, '{}')
+
+ expect { described_class.load_all! }
+ .to raise_error(/Feature flag is missing name/)
+ end
+
+ after do
+ FileUtils.rm_rf(store1)
+ FileUtils.rm_rf(store2)
+ end
+
+ def write_feature_flag(store, path, content)
+ path = File.join(store, path)
+ dir = File.dirname(path)
+ FileUtils.mkdir_p(dir)
+ File.write(path, content)
+ end
+ end
+
+ describe '.valid_usage!' do
+ before do
+ allow(described_class).to receive(:definitions) do
+ { definition.key => definition }
+ end
+ end
+
+ context 'when a known feature flag is used' do
+ it 'validates it usage' do
+ expect(definition).to receive(:valid_usage!)
+
+ described_class.valid_usage!(:feature_flag, type: :development, default_enabled: false)
+ end
+ end
+
+ context 'when an unknown feature flag is used' do
+ context 'for a type that is required to have all feature flags registered' do
+ before do
+ stub_const('Feature::Shared::TYPES', {
+ development: { optional: false }
+ })
+ end
+
+ it 'raises exception' do
+ expect do
+ described_class.valid_usage!(:unknown_feature_flag, type: :development, default_enabled: false)
+ end.to raise_error(/Missing feature definition for `unknown_feature_flag`/)
+ end
+ end
+
+ context 'for a type that is optional' do
+ before do
+ stub_const('Feature::Shared::TYPES', {
+ development: { optional: true }
+ })
+ end
+
+ it 'does not raise exception' do
+ expect do
+ described_class.valid_usage!(:unknown_feature_flag, type: :development, default_enabled: false)
+ end.not_to raise_error
+ end
+ end
+
+ context 'for an unknown type' do
+ it 'raises exception' do
+ expect do
+ described_class.valid_usage!(:unknown_feature_flag, type: :unknown_type, default_enabled: false)
+ end.to raise_error(/Unknown feature flag type used: `unknown_type`/)
+ end
+ end
+ end
+ end
+end
diff --git a/spec/lib/feature/gitaly_spec.rb b/spec/lib/feature/gitaly_spec.rb
index 6654b7627cd..a2181a63335 100644
--- a/spec/lib/feature/gitaly_spec.rb
+++ b/spec/lib/feature/gitaly_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Feature::Gitaly do
+RSpec.describe Feature::Gitaly do
let(:feature_flag) { "mep_mep" }
describe ".enabled?" do
diff --git a/spec/lib/feature_spec.rb b/spec/lib/feature_spec.rb
index 37f8d3ad47d..acd7d97ac85 100644
--- a/spec/lib/feature_spec.rb
+++ b/spec/lib/feature_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Feature, stub_feature_flags: false do
+RSpec.describe Feature, stub_feature_flags: false do
before do
# reset Flipper AR-engine
Feature.reset
@@ -21,66 +21,29 @@ describe Feature, stub_feature_flags: false do
end
describe '.persisted_names' do
- context 'when FF_LEGACY_PERSISTED_NAMES=false' do
- before do
- stub_env('FF_LEGACY_PERSISTED_NAMES', 'false')
- end
-
- it 'returns the names of the persisted features' do
- Feature.enable('foo')
-
- expect(described_class.persisted_names).to contain_exactly('foo')
- end
-
- it 'returns an empty Array when no features are presisted' do
- expect(described_class.persisted_names).to be_empty
- end
-
- it 'caches the feature names when request store is active',
- :request_store, :use_clean_rails_memory_store_caching do
- Feature.enable('foo')
+ it 'returns the names of the persisted features' do
+ Feature.enable('foo')
- expect(Gitlab::ProcessMemoryCache.cache_backend)
- .to receive(:fetch)
- .once
- .with('flipper/v1/features', expires_in: 1.minute)
- .and_call_original
+ expect(described_class.persisted_names).to contain_exactly('foo')
+ end
- 2.times do
- expect(described_class.persisted_names).to contain_exactly('foo')
- end
- end
+ it 'returns an empty Array when no features are presisted' do
+ expect(described_class.persisted_names).to be_empty
end
- context 'when FF_LEGACY_PERSISTED_NAMES=true' do
- before do
- stub_env('FF_LEGACY_PERSISTED_NAMES', 'true')
- end
+ it 'caches the feature names when request store is active',
+ :request_store, :use_clean_rails_memory_store_caching do
+ Feature.enable('foo')
- it 'returns the names of the persisted features' do
- Feature.enable('foo')
+ expect(Gitlab::ProcessMemoryCache.cache_backend)
+ .to receive(:fetch)
+ .once
+ .with('flipper/v1/features', expires_in: 1.minute)
+ .and_call_original
+ 2.times do
expect(described_class.persisted_names).to contain_exactly('foo')
end
-
- it 'returns an empty Array when no features are presisted' do
- expect(described_class.persisted_names).to be_empty
- end
-
- it 'caches the feature names when request store is active',
- :request_store, :use_clean_rails_memory_store_caching do
- Feature.enable('foo')
-
- expect(Gitlab::ProcessMemoryCache.cache_backend)
- .to receive(:fetch)
- .once
- .with('flipper:persisted_names', expires_in: 1.minute)
- .and_call_original
-
- 2.times do
- expect(described_class.persisted_names).to contain_exactly('foo')
- end
- end
end
it 'fetches all flags once in a single query', :request_store do
@@ -279,6 +242,36 @@ describe Feature, stub_feature_flags: false do
end
end
end
+
+ context 'validates usage of feature flag with YAML definition' do
+ let(:definition) do
+ Feature::Definition.new('development/my_feature_flag.yml',
+ name: 'my_feature_flag',
+ type: 'development',
+ default_enabled: false
+ ).tap(&:validate!)
+ end
+
+ before do
+ allow(Feature::Definition).to receive(:definitions) do
+ { definition.key => definition }
+ end
+ end
+
+ it 'when usage is correct' do
+ expect { described_class.enabled?(:my_feature_flag) }.not_to raise_error
+ end
+
+ it 'when invalid type is used' do
+ expect { described_class.enabled?(:my_feature_flag, type: :licensed) }
+ .to raise_error(/The `type:` of/)
+ end
+
+ it 'when invalid default_enabled is used' do
+ expect { described_class.enabled?(:my_feature_flag, default_enabled: true) }
+ .to raise_error(/The `default_enabled:` of/)
+ end
+ end
end
describe '.disable?' do
diff --git a/spec/lib/file_size_validator_spec.rb b/spec/lib/file_size_validator_spec.rb
index 87376a98c60..c5cb7d6eb30 100644
--- a/spec/lib/file_size_validator_spec.rb
+++ b/spec/lib/file_size_validator_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe FileSizeValidator do
+RSpec.describe FileSizeValidator do
let(:validator) { described_class.new(options) }
let(:note) { create(:note) }
let(:attachment) { AttachmentUploader.new(note) }
diff --git a/spec/lib/forever_spec.rb b/spec/lib/forever_spec.rb
index 9f17308241b..6f6b3055df5 100644
--- a/spec/lib/forever_spec.rb
+++ b/spec/lib/forever_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Forever do
+RSpec.describe Forever do
describe '.date' do
subject { described_class.date }
diff --git a/spec/lib/gitaly/server_spec.rb b/spec/lib/gitaly/server_spec.rb
index 390855b30ad..83df4b28757 100644
--- a/spec/lib/gitaly/server_spec.rb
+++ b/spec/lib/gitaly/server_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitaly::Server do
+RSpec.describe Gitaly::Server do
let(:server) { described_class.new('default') }
describe '.all' do
diff --git a/spec/lib/gitlab/access/branch_protection_spec.rb b/spec/lib/gitlab/access/branch_protection_spec.rb
index e4b763357c4..9b736a30c7e 100644
--- a/spec/lib/gitlab/access/branch_protection_spec.rb
+++ b/spec/lib/gitlab/access/branch_protection_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Access::BranchProtection do
+RSpec.describe Gitlab::Access::BranchProtection do
describe '#any?' do
using RSpec::Parameterized::TableSyntax
diff --git a/spec/lib/gitlab/alert_management/alert_params_spec.rb b/spec/lib/gitlab/alert_management/alert_params_spec.rb
index 284af421f05..393838ab042 100644
--- a/spec/lib/gitlab/alert_management/alert_params_spec.rb
+++ b/spec/lib/gitlab/alert_management/alert_params_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::AlertManagement::AlertParams do
+RSpec.describe Gitlab::AlertManagement::AlertParams do
let_it_be(:project) { create(:project, :repository, :private) }
describe '.from_generic_alert' do
@@ -88,7 +88,9 @@ describe Gitlab::AlertManagement::AlertParams do
payload: payload,
started_at: parsed_alert.starts_at,
ended_at: parsed_alert.ends_at,
- fingerprint: parsed_alert.gitlab_fingerprint
+ fingerprint: parsed_alert.gitlab_fingerprint,
+ environment: parsed_alert.environment,
+ prometheus_alert: parsed_alert.gitlab_alert
)
end
end
diff --git a/spec/lib/gitlab/alert_management/alert_status_counts_spec.rb b/spec/lib/gitlab/alert_management/alert_status_counts_spec.rb
index 728cbf11cda..4e471a8eac0 100644
--- a/spec/lib/gitlab/alert_management/alert_status_counts_spec.rb
+++ b/spec/lib/gitlab/alert_management/alert_status_counts_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::AlertManagement::AlertStatusCounts do
+RSpec.describe Gitlab::AlertManagement::AlertStatusCounts do
let_it_be(:current_user) { create(:user) }
let_it_be(:project) { create(:project) }
let_it_be(:alert_1) { create(:alert_management_alert, :resolved, project: project) }
diff --git a/spec/lib/gitlab/alert_management/fingerprint_spec.rb b/spec/lib/gitlab/alert_management/fingerprint_spec.rb
index 7865d667f71..8a933963f6a 100644
--- a/spec/lib/gitlab/alert_management/fingerprint_spec.rb
+++ b/spec/lib/gitlab/alert_management/fingerprint_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::AlertManagement::Fingerprint do
+RSpec.describe Gitlab::AlertManagement::Fingerprint do
using RSpec::Parameterized::TableSyntax
let_it_be(:alert) { create(:alert_management_alert) }
@@ -13,34 +13,62 @@ describe Gitlab::AlertManagement::Fingerprint do
context 'when data is an array' do
let(:data) { [1, 'fingerprint', 'given'] }
- it 'flattens the array' do
- expect_next_instance_of(described_class) do |obj|
- expect(obj).to receive(:flatten_array)
- end
-
- subject
- end
-
it 'returns the hashed fingerprint' do
expected_fingerprint = Digest::SHA1.hexdigest(data.flatten.map!(&:to_s).join)
expect(subject).to eq(expected_fingerprint)
end
- end
- context 'when data is a non-array type' do
- where(:data) do
- [
- 111,
- 'fingerprint',
- :fingerprint,
- true,
- { test: true }
- ]
+ context 'with a variety of data' do
+ where(:data) do
+ [
+ 111,
+ 'fingerprint',
+ :fingerprint,
+ true
+ ]
+ end
+
+ with_them do
+ it 'performs like a hashed fingerprint' do
+ expect(subject).to eq(Digest::SHA1.hexdigest(data.to_s))
+ end
+ end
end
+ end
- with_them do
+ context 'when data is a hash' do
+ let(:data) { { test: true } }
+
+ shared_examples 'fingerprinted Hash' do
it 'performs like a hashed fingerprint' do
- expect(subject).to eq(Digest::SHA1.hexdigest(data.to_s))
+ flattened_hash = Gitlab::Utils::SafeInlineHash.merge_keys!(data).sort.to_s
+ expect(subject).to eq(Digest::SHA1.hexdigest(flattened_hash))
+ end
+ end
+
+ it_behaves_like 'fingerprinted Hash'
+
+ context 'hashes with different order' do
+ it 'calculates the same result' do
+ data = { test: true, another_test: 1 }
+ data_hash = described_class.generate(data)
+
+ reverse_data = { another_test: 1, test: true }
+ reverse_data_hash = described_class.generate(reverse_data)
+
+ expect(data_hash).to eq(reverse_data_hash)
+ end
+ end
+
+ context 'hash is too large' do
+ before do
+ expect_next_instance_of(Gitlab::Utils::SafeInlineHash) do |obj|
+ expect(obj).to receive(:valid?).and_return(false)
+ end
+ end
+
+ it 'raises an error' do
+ expect { subject }.to raise_error(ArgumentError)
end
end
end
diff --git a/spec/lib/gitlab/alerting/alert_spec.rb b/spec/lib/gitlab/alerting/alert_spec.rb
index d582ff6f32a..9663e6af0d2 100644
--- a/spec/lib/gitlab/alerting/alert_spec.rb
+++ b/spec/lib/gitlab/alerting/alert_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Alerting::Alert do
+RSpec.describe Gitlab::Alerting::Alert do
let_it_be(:project) { create(:project) }
let(:alert) { build(:alerting_alert, project: project, payload: payload) }
diff --git a/spec/lib/gitlab/alerting/notification_payload_parser_spec.rb b/spec/lib/gitlab/alerting/notification_payload_parser_spec.rb
index 889efae9585..0489108b159 100644
--- a/spec/lib/gitlab/alerting/notification_payload_parser_spec.rb
+++ b/spec/lib/gitlab/alerting/notification_payload_parser_spec.rb
@@ -2,7 +2,9 @@
require 'spec_helper'
-describe Gitlab::Alerting::NotificationPayloadParser do
+RSpec.describe Gitlab::Alerting::NotificationPayloadParser do
+ let_it_be(:project) { build(:project) }
+
describe '.call' do
let(:starts_at) { Time.current.change(usec: 0) }
let(:payload) do
@@ -17,7 +19,7 @@ describe Gitlab::Alerting::NotificationPayloadParser do
}
end
- subject { described_class.call(payload) }
+ subject { described_class.call(payload, project) }
it 'returns Prometheus-like payload' do
is_expected.to eq(
diff --git a/spec/lib/gitlab/allowable_spec.rb b/spec/lib/gitlab/allowable_spec.rb
index 4905cc4c3db..0535384be6e 100644
--- a/spec/lib/gitlab/allowable_spec.rb
+++ b/spec/lib/gitlab/allowable_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Allowable do
+RSpec.describe Gitlab::Allowable do
subject do
Class.new.include(described_class).new
end
diff --git a/spec/lib/gitlab/analytics/cycle_analytics/base_query_builder_spec.rb b/spec/lib/gitlab/analytics/cycle_analytics/base_query_builder_spec.rb
index 250e2f16aec..80d3f82b404 100644
--- a/spec/lib/gitlab/analytics/cycle_analytics/base_query_builder_spec.rb
+++ b/spec/lib/gitlab/analytics/cycle_analytics/base_query_builder_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Analytics::CycleAnalytics::BaseQueryBuilder do
+RSpec.describe Gitlab::Analytics::CycleAnalytics::BaseQueryBuilder do
let_it_be(:project) { create(:project, :empty_repo) }
let_it_be(:mr1) { create(:merge_request, target_project: project, source_project: project, allow_broken: true, created_at: 3.months.ago) }
let_it_be(:mr2) { create(:merge_request, target_project: project, source_project: project, allow_broken: true, created_at: 1.month.ago) }
diff --git a/spec/lib/gitlab/analytics/cycle_analytics/median_spec.rb b/spec/lib/gitlab/analytics/cycle_analytics/median_spec.rb
index 92ecec350ae..c1ea000eb7b 100644
--- a/spec/lib/gitlab/analytics/cycle_analytics/median_spec.rb
+++ b/spec/lib/gitlab/analytics/cycle_analytics/median_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Analytics::CycleAnalytics::Median do
+RSpec.describe Gitlab::Analytics::CycleAnalytics::Median do
let_it_be(:project) { create(:project, :repository) }
let(:query) { Project.joins(merge_requests: :metrics) }
diff --git a/spec/lib/gitlab/analytics/cycle_analytics/records_fetcher_spec.rb b/spec/lib/gitlab/analytics/cycle_analytics/records_fetcher_spec.rb
index e3429b0ca57..b8f9dde4291 100644
--- a/spec/lib/gitlab/analytics/cycle_analytics/records_fetcher_spec.rb
+++ b/spec/lib/gitlab/analytics/cycle_analytics/records_fetcher_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Analytics::CycleAnalytics::RecordsFetcher do
+RSpec.describe Gitlab::Analytics::CycleAnalytics::RecordsFetcher do
around do |example|
Timecop.freeze { example.run }
end
diff --git a/spec/lib/gitlab/analytics/cycle_analytics/stage_events/code_stage_start_spec.rb b/spec/lib/gitlab/analytics/cycle_analytics/stage_events/code_stage_start_spec.rb
index 29c8d548754..fe390289ef6 100644
--- a/spec/lib/gitlab/analytics/cycle_analytics/stage_events/code_stage_start_spec.rb
+++ b/spec/lib/gitlab/analytics/cycle_analytics/stage_events/code_stage_start_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Analytics::CycleAnalytics::StageEvents::CodeStageStart do
+RSpec.describe Gitlab::Analytics::CycleAnalytics::StageEvents::CodeStageStart do
let(:subject) { described_class.new({}) }
let(:project) { create(:project) }
diff --git a/spec/lib/gitlab/analytics/cycle_analytics/stage_events/issue_created_spec.rb b/spec/lib/gitlab/analytics/cycle_analytics/stage_events/issue_created_spec.rb
index efdef91c5a2..5cc6b05407f 100644
--- a/spec/lib/gitlab/analytics/cycle_analytics/stage_events/issue_created_spec.rb
+++ b/spec/lib/gitlab/analytics/cycle_analytics/stage_events/issue_created_spec.rb
@@ -2,6 +2,6 @@
require 'spec_helper'
-describe Gitlab::Analytics::CycleAnalytics::StageEvents::IssueCreated do
+RSpec.describe Gitlab::Analytics::CycleAnalytics::StageEvents::IssueCreated do
it_behaves_like 'cycle analytics event'
end
diff --git a/spec/lib/gitlab/analytics/cycle_analytics/stage_events/issue_first_mentioned_in_commit_spec.rb b/spec/lib/gitlab/analytics/cycle_analytics/stage_events/issue_first_mentioned_in_commit_spec.rb
index 50883e1c1e2..715ad5a8e7d 100644
--- a/spec/lib/gitlab/analytics/cycle_analytics/stage_events/issue_first_mentioned_in_commit_spec.rb
+++ b/spec/lib/gitlab/analytics/cycle_analytics/stage_events/issue_first_mentioned_in_commit_spec.rb
@@ -2,6 +2,6 @@
require 'spec_helper'
-describe Gitlab::Analytics::CycleAnalytics::StageEvents::IssueFirstMentionedInCommit do
+RSpec.describe Gitlab::Analytics::CycleAnalytics::StageEvents::IssueFirstMentionedInCommit do
it_behaves_like 'cycle analytics event'
end
diff --git a/spec/lib/gitlab/analytics/cycle_analytics/stage_events/issue_stage_end_spec.rb b/spec/lib/gitlab/analytics/cycle_analytics/stage_events/issue_stage_end_spec.rb
index 85062db370a..56241194f36 100644
--- a/spec/lib/gitlab/analytics/cycle_analytics/stage_events/issue_stage_end_spec.rb
+++ b/spec/lib/gitlab/analytics/cycle_analytics/stage_events/issue_stage_end_spec.rb
@@ -2,6 +2,6 @@
require 'spec_helper'
-describe Gitlab::Analytics::CycleAnalytics::StageEvents::IssueStageEnd do
+RSpec.describe Gitlab::Analytics::CycleAnalytics::StageEvents::IssueStageEnd do
it_behaves_like 'cycle analytics event'
end
diff --git a/spec/lib/gitlab/analytics/cycle_analytics/stage_events/merge_request_created_spec.rb b/spec/lib/gitlab/analytics/cycle_analytics/stage_events/merge_request_created_spec.rb
index 7858b810661..f3202eab5bb 100644
--- a/spec/lib/gitlab/analytics/cycle_analytics/stage_events/merge_request_created_spec.rb
+++ b/spec/lib/gitlab/analytics/cycle_analytics/stage_events/merge_request_created_spec.rb
@@ -2,6 +2,6 @@
require 'spec_helper'
-describe Gitlab::Analytics::CycleAnalytics::StageEvents::MergeRequestCreated do
+RSpec.describe Gitlab::Analytics::CycleAnalytics::StageEvents::MergeRequestCreated do
it_behaves_like 'cycle analytics event'
end
diff --git a/spec/lib/gitlab/analytics/cycle_analytics/stage_events/merge_request_first_deployed_to_production_spec.rb b/spec/lib/gitlab/analytics/cycle_analytics/stage_events/merge_request_first_deployed_to_production_spec.rb
index ba9d8be5a2c..03b0ccfae43 100644
--- a/spec/lib/gitlab/analytics/cycle_analytics/stage_events/merge_request_first_deployed_to_production_spec.rb
+++ b/spec/lib/gitlab/analytics/cycle_analytics/stage_events/merge_request_first_deployed_to_production_spec.rb
@@ -2,6 +2,6 @@
require 'spec_helper'
-describe Gitlab::Analytics::CycleAnalytics::StageEvents::MergeRequestFirstDeployedToProduction do
+RSpec.describe Gitlab::Analytics::CycleAnalytics::StageEvents::MergeRequestFirstDeployedToProduction do
it_behaves_like 'cycle analytics event'
end
diff --git a/spec/lib/gitlab/analytics/cycle_analytics/stage_events/merge_request_last_build_finished_spec.rb b/spec/lib/gitlab/analytics/cycle_analytics/stage_events/merge_request_last_build_finished_spec.rb
index 8e83e10ef96..b0c003e6f2a 100644
--- a/spec/lib/gitlab/analytics/cycle_analytics/stage_events/merge_request_last_build_finished_spec.rb
+++ b/spec/lib/gitlab/analytics/cycle_analytics/stage_events/merge_request_last_build_finished_spec.rb
@@ -2,6 +2,6 @@
require 'spec_helper'
-describe Gitlab::Analytics::CycleAnalytics::StageEvents::MergeRequestLastBuildFinished do
+RSpec.describe Gitlab::Analytics::CycleAnalytics::StageEvents::MergeRequestLastBuildFinished do
it_behaves_like 'cycle analytics event'
end
diff --git a/spec/lib/gitlab/analytics/cycle_analytics/stage_events/merge_request_last_build_started_spec.rb b/spec/lib/gitlab/analytics/cycle_analytics/stage_events/merge_request_last_build_started_spec.rb
index 9f6b430a320..8f9aaf6f463 100644
--- a/spec/lib/gitlab/analytics/cycle_analytics/stage_events/merge_request_last_build_started_spec.rb
+++ b/spec/lib/gitlab/analytics/cycle_analytics/stage_events/merge_request_last_build_started_spec.rb
@@ -2,6 +2,6 @@
require 'spec_helper'
-describe Gitlab::Analytics::CycleAnalytics::StageEvents::MergeRequestLastBuildStarted do
+RSpec.describe Gitlab::Analytics::CycleAnalytics::StageEvents::MergeRequestLastBuildStarted do
it_behaves_like 'cycle analytics event'
end
diff --git a/spec/lib/gitlab/analytics/cycle_analytics/stage_events/merge_request_merged_spec.rb b/spec/lib/gitlab/analytics/cycle_analytics/stage_events/merge_request_merged_spec.rb
index ce2aa0a60db..f1d2ca9f36e 100644
--- a/spec/lib/gitlab/analytics/cycle_analytics/stage_events/merge_request_merged_spec.rb
+++ b/spec/lib/gitlab/analytics/cycle_analytics/stage_events/merge_request_merged_spec.rb
@@ -2,6 +2,6 @@
require 'spec_helper'
-describe Gitlab::Analytics::CycleAnalytics::StageEvents::MergeRequestMerged do
+RSpec.describe Gitlab::Analytics::CycleAnalytics::StageEvents::MergeRequestMerged do
it_behaves_like 'cycle analytics event'
end
diff --git a/spec/lib/gitlab/analytics/cycle_analytics/stage_events/plan_stage_start_spec.rb b/spec/lib/gitlab/analytics/cycle_analytics/stage_events/plan_stage_start_spec.rb
index cb63139f0a8..3248af524bd 100644
--- a/spec/lib/gitlab/analytics/cycle_analytics/stage_events/plan_stage_start_spec.rb
+++ b/spec/lib/gitlab/analytics/cycle_analytics/stage_events/plan_stage_start_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Analytics::CycleAnalytics::StageEvents::PlanStageStart do
+RSpec.describe Gitlab::Analytics::CycleAnalytics::StageEvents::PlanStageStart do
let(:subject) { described_class.new({}) }
let(:project) { create(:project) }
diff --git a/spec/lib/gitlab/analytics/cycle_analytics/stage_events/stage_event_spec.rb b/spec/lib/gitlab/analytics/cycle_analytics/stage_events/stage_event_spec.rb
index b05faf5d813..6fc658ecade 100644
--- a/spec/lib/gitlab/analytics/cycle_analytics/stage_events/stage_event_spec.rb
+++ b/spec/lib/gitlab/analytics/cycle_analytics/stage_events/stage_event_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Analytics::CycleAnalytics::StageEvents::StageEvent do
+RSpec.describe Gitlab::Analytics::CycleAnalytics::StageEvents::StageEvent do
let(:instance) { described_class.new({}) }
it { expect(described_class).to respond_to(:name) }
diff --git a/spec/lib/gitlab/analytics/unique_visits_spec.rb b/spec/lib/gitlab/analytics/unique_visits_spec.rb
new file mode 100644
index 00000000000..ff3623a3a71
--- /dev/null
+++ b/spec/lib/gitlab/analytics/unique_visits_spec.rb
@@ -0,0 +1,62 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Analytics::UniqueVisits, :clean_gitlab_redis_shared_state do
+ let(:unique_visits) { Gitlab::Analytics::UniqueVisits.new }
+ let(:target1_id) { 'g_analytics_contribution' }
+ let(:target2_id) { 'g_analytics_insights' }
+ let(:target3_id) { 'g_analytics_issues' }
+ let(:visitor1_id) { 'dfb9d2d2-f56c-4c77-8aeb-6cddc4a1f857' }
+ let(:visitor2_id) { '1dd9afb2-a3ee-4de1-8ae3-a405579c8584' }
+
+ around do |example|
+ # We need to freeze to a reference time
+ # because visits are grouped by the week number in the year
+ # Without freezing the time, the test may behave inconsistently
+ # depending on which day of the week test is run.
+ reference_time = Time.utc(2020, 6, 1)
+ Timecop.freeze(reference_time) { example.run }
+ end
+
+ describe '#track_visit' do
+ it 'tracks the unique weekly visits for targets' do
+ unique_visits.track_visit(visitor1_id, target1_id, 7.days.ago)
+ unique_visits.track_visit(visitor1_id, target1_id, 7.days.ago)
+ unique_visits.track_visit(visitor2_id, target1_id, 7.days.ago)
+
+ unique_visits.track_visit(visitor2_id, target2_id, 7.days.ago)
+ unique_visits.track_visit(visitor1_id, target2_id, 8.days.ago)
+ unique_visits.track_visit(visitor1_id, target2_id, 15.days.ago)
+
+ expect(unique_visits.weekly_unique_visits_for_target(target1_id)).to eq(2)
+ expect(unique_visits.weekly_unique_visits_for_target(target2_id)).to eq(1)
+
+ expect(unique_visits.weekly_unique_visits_for_target(target2_id, week_of: 15.days.ago)).to eq(1)
+
+ expect(unique_visits.weekly_unique_visits_for_target(target3_id)).to eq(0)
+
+ expect(unique_visits.weekly_unique_visits_for_any_target).to eq(2)
+ expect(unique_visits.weekly_unique_visits_for_any_target(week_of: 15.days.ago)).to eq(1)
+ expect(unique_visits.weekly_unique_visits_for_any_target(week_of: 30.days.ago)).to eq(0)
+ end
+
+ it 'sets the keys in Redis to expire automatically after 28 days' do
+ unique_visits.track_visit(visitor1_id, target1_id)
+
+ Gitlab::Redis::SharedState.with do |redis|
+ redis.scan_each(match: "#{target1_id}-*").each do |key|
+ expect(redis.ttl(key)).to be_within(5.seconds).of(28.days)
+ end
+ end
+ end
+
+ it 'raises an error if an invalid target id is given' do
+ invalid_target_id = "x_invalid"
+
+ expect do
+ unique_visits.track_visit(visitor1_id, invalid_target_id)
+ end.to raise_error("Invalid target id #{invalid_target_id}")
+ end
+ end
+end
diff --git a/spec/lib/gitlab/anonymous_session_spec.rb b/spec/lib/gitlab/anonymous_session_spec.rb
index 94daa0f2470..0f0795cd9fc 100644
--- a/spec/lib/gitlab/anonymous_session_spec.rb
+++ b/spec/lib/gitlab/anonymous_session_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::AnonymousSession, :clean_gitlab_redis_shared_state do
+RSpec.describe Gitlab::AnonymousSession, :clean_gitlab_redis_shared_state do
let(:default_session_id) { '6919a6f1bb119dd7396fadc38fd18d0d' }
let(:additional_session_id) { '7919a6f1bb119dd7396fadc38fd18d0d' }
diff --git a/spec/lib/gitlab/app_json_logger_spec.rb b/spec/lib/gitlab/app_json_logger_spec.rb
index d11456236cc..89dce969522 100644
--- a/spec/lib/gitlab/app_json_logger_spec.rb
+++ b/spec/lib/gitlab/app_json_logger_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::AppJsonLogger do
+RSpec.describe Gitlab::AppJsonLogger do
subject { described_class.new('/dev/null') }
let(:hash_message) { { 'message' => 'Message', 'project_id' => '123' } }
diff --git a/spec/lib/gitlab/app_logger_spec.rb b/spec/lib/gitlab/app_logger_spec.rb
index 132a10b9409..166b1fda268 100644
--- a/spec/lib/gitlab/app_logger_spec.rb
+++ b/spec/lib/gitlab/app_logger_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::AppLogger do
+RSpec.describe Gitlab::AppLogger do
subject { described_class }
it 'builds a Gitlab::Logger object twice' do
diff --git a/spec/lib/gitlab/app_text_logger_spec.rb b/spec/lib/gitlab/app_text_logger_spec.rb
index c84b986ce40..04c2e946640 100644
--- a/spec/lib/gitlab/app_text_logger_spec.rb
+++ b/spec/lib/gitlab/app_text_logger_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::AppTextLogger do
+RSpec.describe Gitlab::AppTextLogger do
subject { described_class.new('/dev/null') }
let(:hash_message) { { message: 'Message', project_id: 123 } }
diff --git a/spec/lib/gitlab/application_context_spec.rb b/spec/lib/gitlab/application_context_spec.rb
index 3be967ac8a4..88f865adea7 100644
--- a/spec/lib/gitlab/application_context_spec.rb
+++ b/spec/lib/gitlab/application_context_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::ApplicationContext do
+RSpec.describe Gitlab::ApplicationContext do
describe '.with_context' do
it 'yields the block' do
expect { |b| described_class.with_context({}, &b) }.to yield_control
diff --git a/spec/lib/gitlab/application_rate_limiter_spec.rb b/spec/lib/gitlab/application_rate_limiter_spec.rb
index f1a0163d91c..14a7e25a2e8 100644
--- a/spec/lib/gitlab/application_rate_limiter_spec.rb
+++ b/spec/lib/gitlab/application_rate_limiter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::ApplicationRateLimiter, :clean_gitlab_redis_cache do
+RSpec.describe Gitlab::ApplicationRateLimiter, :clean_gitlab_redis_cache do
let(:redis) { double('redis') }
let(:user) { create(:user) }
let(:project) { create(:project) }
diff --git a/spec/lib/gitlab/asciidoc/include_processor_spec.rb b/spec/lib/gitlab/asciidoc/include_processor_spec.rb
index 2781319567c..067dcefb525 100644
--- a/spec/lib/gitlab/asciidoc/include_processor_spec.rb
+++ b/spec/lib/gitlab/asciidoc/include_processor_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require 'nokogiri'
-describe Gitlab::Asciidoc::IncludeProcessor do
+RSpec.describe Gitlab::Asciidoc::IncludeProcessor do
let_it_be(:project) { create(:project, :repository) }
let(:processor_context) do
diff --git a/spec/lib/gitlab/asciidoc_spec.rb b/spec/lib/gitlab/asciidoc_spec.rb
index 24528d79fa8..40a4ab3e173 100644
--- a/spec/lib/gitlab/asciidoc_spec.rb
+++ b/spec/lib/gitlab/asciidoc_spec.rb
@@ -4,7 +4,7 @@ require 'spec_helper'
require 'nokogiri'
module Gitlab
- describe Asciidoc do
+ RSpec.describe Asciidoc do
include FakeBlobHelpers
before do
diff --git a/spec/lib/gitlab/asset_proxy_spec.rb b/spec/lib/gitlab/asset_proxy_spec.rb
index e406917a5a4..73b101c0dd8 100644
--- a/spec/lib/gitlab/asset_proxy_spec.rb
+++ b/spec/lib/gitlab/asset_proxy_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::AssetProxy do
+RSpec.describe Gitlab::AssetProxy do
context 'when asset proxy is disabled' do
before do
stub_asset_proxy_setting(enabled: false)
diff --git a/spec/lib/gitlab/auth/activity_spec.rb b/spec/lib/gitlab/auth/activity_spec.rb
index e03fafe3826..cbc42c46470 100644
--- a/spec/lib/gitlab/auth/activity_spec.rb
+++ b/spec/lib/gitlab/auth/activity_spec.rb
@@ -2,7 +2,7 @@
require 'fast_spec_helper'
-describe Gitlab::Auth::Activity do
+RSpec.describe Gitlab::Auth::Activity do
describe '.each_counter' do
it 'has all static counters defined' do
described_class.each_counter do |counter|
diff --git a/spec/lib/gitlab/auth/auth_finders_spec.rb b/spec/lib/gitlab/auth/auth_finders_spec.rb
index 2aef206c7fd..d0f5d0a9b35 100644
--- a/spec/lib/gitlab/auth/auth_finders_spec.rb
+++ b/spec/lib/gitlab/auth/auth_finders_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Auth::AuthFinders do
+RSpec.describe Gitlab::Auth::AuthFinders do
include described_class
include HttpBasicAuthHelpers
@@ -26,6 +26,63 @@ describe Gitlab::Auth::AuthFinders do
env.merge!(basic_auth_header(username, password))
end
+ shared_examples 'find user from job token' do
+ context 'when route is allowed to be authenticated' do
+ let(:route_authentication_setting) { { job_token_allowed: true } }
+
+ it "returns an Unauthorized exception for an invalid token" do
+ set_token('invalid token')
+
+ expect { subject }.to raise_error(Gitlab::Auth::UnauthorizedError)
+ end
+
+ it "return user if token is valid" do
+ set_token(job.token)
+
+ expect(subject).to eq(user)
+ expect(@current_authenticated_job).to eq job
+ end
+ end
+ end
+
+ describe '#find_user_from_bearer_token' do
+ let(:job) { create(:ci_build, user: user) }
+
+ subject { find_user_from_bearer_token }
+
+ context 'when the token is passed as an oauth token' do
+ def set_token(token)
+ env['HTTP_AUTHORIZATION'] = "Bearer #{token}"
+ end
+
+ context 'with a job token' do
+ it_behaves_like 'find user from job token'
+ end
+
+ context 'with oauth token' do
+ let(:application) { Doorkeeper::Application.create!(name: 'MyApp', redirect_uri: 'https://app.com', owner: user) }
+ let(:token) { Doorkeeper::AccessToken.create!(application_id: application.id, resource_owner_id: user.id, scopes: 'api').token }
+
+ before do
+ set_token(token)
+ end
+
+ it { is_expected.to eq user }
+ end
+ end
+
+ context 'with a personal access token' do
+ let(:pat) { create(:personal_access_token, user: user) }
+ let(:token) { pat.token }
+
+ before do
+ env[described_class::PRIVATE_TOKEN_HEADER] = pat.token
+ end
+
+ it { is_expected.to eq user }
+ end
+ end
+
describe '#find_user_from_warden' do
context 'with CSRF token' do
before do
@@ -522,8 +579,24 @@ describe Gitlab::Auth::AuthFinders do
end
describe '#validate_access_token!' do
+ subject { validate_access_token! }
+
let(:personal_access_token) { create(:personal_access_token, user: user) }
+ context 'with a job token' do
+ let(:route_authentication_setting) { { job_token_allowed: true } }
+ let(:job) { create(:ci_build, user: user) }
+
+ before do
+ env['HTTP_AUTHORIZATION'] = "Bearer #{job.token}"
+ find_user_from_bearer_token
+ end
+
+ it 'does not raise an error' do
+ expect { subject }.not_to raise_error
+ end
+ end
+
it 'returns nil if no access_token present' do
expect(validate_access_token!).to be_nil
end
diff --git a/spec/lib/gitlab/auth/blocked_user_tracker_spec.rb b/spec/lib/gitlab/auth/blocked_user_tracker_spec.rb
index 52849f8c172..76775db3a4a 100644
--- a/spec/lib/gitlab/auth/blocked_user_tracker_spec.rb
+++ b/spec/lib/gitlab/auth/blocked_user_tracker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Auth::BlockedUserTracker do
+RSpec.describe Gitlab::Auth::BlockedUserTracker do
describe '#log_blocked_user_activity!' do
context 'when user is not blocked' do
it 'does not log blocked user activity' do
diff --git a/spec/lib/gitlab/auth/current_user_mode_spec.rb b/spec/lib/gitlab/auth/current_user_mode_spec.rb
index 26e44fa7cc8..60b403780c0 100644
--- a/spec/lib/gitlab/auth/current_user_mode_spec.rb
+++ b/spec/lib/gitlab/auth/current_user_mode_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Auth::CurrentUserMode, :do_not_mock_admin_mode, :request_store do
+RSpec.describe Gitlab::Auth::CurrentUserMode, :do_not_mock_admin_mode, :request_store do
let(:user) { build_stubbed(:user) }
subject { described_class.new(user) }
diff --git a/spec/lib/gitlab/auth/ip_rate_limiter_spec.rb b/spec/lib/gitlab/auth/ip_rate_limiter_spec.rb
index aea1b2921b6..3d782272d7e 100644
--- a/spec/lib/gitlab/auth/ip_rate_limiter_spec.rb
+++ b/spec/lib/gitlab/auth/ip_rate_limiter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Auth::IpRateLimiter, :use_clean_rails_memory_store_caching do
+RSpec.describe Gitlab::Auth::IpRateLimiter, :use_clean_rails_memory_store_caching do
let(:ip) { '10.2.2.3' }
let(:whitelist) { ['127.0.0.1'] }
let(:options) do
diff --git a/spec/lib/gitlab/auth/key_status_checker_spec.rb b/spec/lib/gitlab/auth/key_status_checker_spec.rb
index b1a540eae81..e8ac0d7c394 100644
--- a/spec/lib/gitlab/auth/key_status_checker_spec.rb
+++ b/spec/lib/gitlab/auth/key_status_checker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Auth::KeyStatusChecker do
+RSpec.describe Gitlab::Auth::KeyStatusChecker do
let_it_be(:never_expires_key) { build(:personal_key, expires_at: nil) }
let_it_be(:expired_key) { build(:personal_key, expires_at: 3.days.ago) }
let_it_be(:expiring_soon_key) { build(:personal_key, expires_at: 3.days.from_now) }
diff --git a/spec/lib/gitlab/auth/ldap/access_spec.rb b/spec/lib/gitlab/auth/ldap/access_spec.rb
index 2f691429541..9e269f84b7e 100644
--- a/spec/lib/gitlab/auth/ldap/access_spec.rb
+++ b/spec/lib/gitlab/auth/ldap/access_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Auth::Ldap::Access do
+RSpec.describe Gitlab::Auth::Ldap::Access do
include LdapHelpers
let(:user) { create(:omniauth_user) }
diff --git a/spec/lib/gitlab/auth/ldap/adapter_spec.rb b/spec/lib/gitlab/auth/ldap/adapter_spec.rb
index 34853acdd0f..78970378b7f 100644
--- a/spec/lib/gitlab/auth/ldap/adapter_spec.rb
+++ b/spec/lib/gitlab/auth/ldap/adapter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Auth::Ldap::Adapter do
+RSpec.describe Gitlab::Auth::Ldap::Adapter do
include LdapHelpers
let(:ldap) { double(:ldap) }
diff --git a/spec/lib/gitlab/auth/ldap/auth_hash_spec.rb b/spec/lib/gitlab/auth/ldap/auth_hash_spec.rb
index 7bc92d0abea..9dff7f7b3dc 100644
--- a/spec/lib/gitlab/auth/ldap/auth_hash_spec.rb
+++ b/spec/lib/gitlab/auth/ldap/auth_hash_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Auth::Ldap::AuthHash do
+RSpec.describe Gitlab::Auth::Ldap::AuthHash do
include LdapHelpers
let(:auth_hash) do
diff --git a/spec/lib/gitlab/auth/ldap/authentication_spec.rb b/spec/lib/gitlab/auth/ldap/authentication_spec.rb
index 1f8b1474539..42a893417d8 100644
--- a/spec/lib/gitlab/auth/ldap/authentication_spec.rb
+++ b/spec/lib/gitlab/auth/ldap/authentication_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Auth::Ldap::Authentication do
+RSpec.describe Gitlab::Auth::Ldap::Authentication do
let(:dn) { 'uid=John Smith, ou=People, dc=example, dc=com' }
let(:user) { create(:omniauth_user, extern_uid: Gitlab::Auth::Ldap::Person.normalize_dn(dn)) }
let(:login) { 'john' }
diff --git a/spec/lib/gitlab/auth/ldap/config_spec.rb b/spec/lib/gitlab/auth/ldap/config_spec.rb
index 124f072ebe6..4287596af8f 100644
--- a/spec/lib/gitlab/auth/ldap/config_spec.rb
+++ b/spec/lib/gitlab/auth/ldap/config_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Auth::Ldap::Config do
+RSpec.describe Gitlab::Auth::Ldap::Config do
include LdapHelpers
let(:config) { described_class.new('ldapmain') }
diff --git a/spec/lib/gitlab/auth/ldap/dn_spec.rb b/spec/lib/gitlab/auth/ldap/dn_spec.rb
index 7aaffa52ae4..e89f764b040 100644
--- a/spec/lib/gitlab/auth/ldap/dn_spec.rb
+++ b/spec/lib/gitlab/auth/ldap/dn_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Auth::Ldap::DN do
+RSpec.describe Gitlab::Auth::Ldap::DN do
using RSpec::Parameterized::TableSyntax
describe '#normalize_value' do
diff --git a/spec/lib/gitlab/auth/ldap/person_spec.rb b/spec/lib/gitlab/auth/ldap/person_spec.rb
index 403a48d40ef..6857b561370 100644
--- a/spec/lib/gitlab/auth/ldap/person_spec.rb
+++ b/spec/lib/gitlab/auth/ldap/person_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Auth::Ldap::Person do
+RSpec.describe Gitlab::Auth::Ldap::Person do
include LdapHelpers
let(:entry) { ldap_user_entry('john.doe') }
diff --git a/spec/lib/gitlab/auth/ldap/user_spec.rb b/spec/lib/gitlab/auth/ldap/user_spec.rb
index 867633e54df..7ca2878e583 100644
--- a/spec/lib/gitlab/auth/ldap/user_spec.rb
+++ b/spec/lib/gitlab/auth/ldap/user_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Auth::Ldap::User do
+RSpec.describe Gitlab::Auth::Ldap::User do
include LdapHelpers
let(:ldap_user) { described_class.new(auth_hash) }
diff --git a/spec/lib/gitlab/auth/o_auth/auth_hash_spec.rb b/spec/lib/gitlab/auth/o_auth/auth_hash_spec.rb
index a2d9e27ea5b..7a60acca95b 100644
--- a/spec/lib/gitlab/auth/o_auth/auth_hash_spec.rb
+++ b/spec/lib/gitlab/auth/o_auth/auth_hash_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Auth::OAuth::AuthHash do
+RSpec.describe Gitlab::Auth::OAuth::AuthHash do
let(:provider) { 'ldap'.freeze }
let(:auth_hash) do
described_class.new(
diff --git a/spec/lib/gitlab/auth/o_auth/identity_linker_spec.rb b/spec/lib/gitlab/auth/o_auth/identity_linker_spec.rb
index 45c1baa4089..8014fbe1687 100644
--- a/spec/lib/gitlab/auth/o_auth/identity_linker_spec.rb
+++ b/spec/lib/gitlab/auth/o_auth/identity_linker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Auth::OAuth::IdentityLinker do
+RSpec.describe Gitlab::Auth::OAuth::IdentityLinker do
let(:user) { create(:user) }
let(:provider) { 'twitter' }
let(:uid) { user.email }
diff --git a/spec/lib/gitlab/auth/o_auth/provider_spec.rb b/spec/lib/gitlab/auth/o_auth/provider_spec.rb
index 8b0d4d786cd..658a9976cc2 100644
--- a/spec/lib/gitlab/auth/o_auth/provider_spec.rb
+++ b/spec/lib/gitlab/auth/o_auth/provider_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Auth::OAuth::Provider do
+RSpec.describe Gitlab::Auth::OAuth::Provider do
describe '.enabled?' do
before do
allow(described_class).to receive(:providers).and_return([:ldapmain, :google_oauth2])
diff --git a/spec/lib/gitlab/auth/o_auth/user_spec.rb b/spec/lib/gitlab/auth/o_auth/user_spec.rb
index 62b83ff8b88..ad04fddc675 100644
--- a/spec/lib/gitlab/auth/o_auth/user_spec.rb
+++ b/spec/lib/gitlab/auth/o_auth/user_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Auth::OAuth::User do
+RSpec.describe Gitlab::Auth::OAuth::User do
include LdapHelpers
let(:oauth_user) { described_class.new(auth_hash) }
diff --git a/spec/lib/gitlab/auth/request_authenticator_spec.rb b/spec/lib/gitlab/auth/request_authenticator_spec.rb
index 87c96803c3a..32d64519e2c 100644
--- a/spec/lib/gitlab/auth/request_authenticator_spec.rb
+++ b/spec/lib/gitlab/auth/request_authenticator_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Auth::RequestAuthenticator do
+RSpec.describe Gitlab::Auth::RequestAuthenticator do
let(:env) do
{
'rack.input' => '',
diff --git a/spec/lib/gitlab/auth/saml/auth_hash_spec.rb b/spec/lib/gitlab/auth/saml/auth_hash_spec.rb
index 8b88c16f317..f1fad946f35 100644
--- a/spec/lib/gitlab/auth/saml/auth_hash_spec.rb
+++ b/spec/lib/gitlab/auth/saml/auth_hash_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Auth::Saml::AuthHash do
+RSpec.describe Gitlab::Auth::Saml::AuthHash do
include LoginHelpers
let(:raw_info_attr) { { 'groups' => %w(Developers Freelancers) } }
diff --git a/spec/lib/gitlab/auth/saml/identity_linker_spec.rb b/spec/lib/gitlab/auth/saml/identity_linker_spec.rb
index 7912c8fb4b1..743163ad315 100644
--- a/spec/lib/gitlab/auth/saml/identity_linker_spec.rb
+++ b/spec/lib/gitlab/auth/saml/identity_linker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Auth::Saml::IdentityLinker do
+RSpec.describe Gitlab::Auth::Saml::IdentityLinker do
let(:user) { create(:user) }
let(:provider) { 'saml' }
let(:uid) { user.email }
diff --git a/spec/lib/gitlab/auth/saml/origin_validator_spec.rb b/spec/lib/gitlab/auth/saml/origin_validator_spec.rb
index ae120b328ab..f13140cdcba 100644
--- a/spec/lib/gitlab/auth/saml/origin_validator_spec.rb
+++ b/spec/lib/gitlab/auth/saml/origin_validator_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Auth::Saml::OriginValidator do
+RSpec.describe Gitlab::Auth::Saml::OriginValidator do
let(:session) { instance_double(ActionDispatch::Request::Session) }
subject { described_class.new(session) }
diff --git a/spec/lib/gitlab/auth/saml/user_spec.rb b/spec/lib/gitlab/auth/saml/user_spec.rb
index 55d2f22b923..7f8346f0486 100644
--- a/spec/lib/gitlab/auth/saml/user_spec.rb
+++ b/spec/lib/gitlab/auth/saml/user_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Auth::Saml::User do
+RSpec.describe Gitlab::Auth::Saml::User do
include LdapHelpers
include LoginHelpers
diff --git a/spec/lib/gitlab/auth/unique_ips_limiter_spec.rb b/spec/lib/gitlab/auth/unique_ips_limiter_spec.rb
index ebf7de9c701..a08055ab852 100644
--- a/spec/lib/gitlab/auth/unique_ips_limiter_spec.rb
+++ b/spec/lib/gitlab/auth/unique_ips_limiter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Auth::UniqueIpsLimiter, :clean_gitlab_redis_shared_state do
+RSpec.describe Gitlab::Auth::UniqueIpsLimiter, :clean_gitlab_redis_shared_state do
include_context 'unique ips sign in limit'
let(:user) { create(:user) }
diff --git a/spec/lib/gitlab/auth/user_access_denied_reason_spec.rb b/spec/lib/gitlab/auth/user_access_denied_reason_spec.rb
index 7045105a2c7..a2a0eb5428a 100644
--- a/spec/lib/gitlab/auth/user_access_denied_reason_spec.rb
+++ b/spec/lib/gitlab/auth/user_access_denied_reason_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Auth::UserAccessDeniedReason do
+RSpec.describe Gitlab::Auth::UserAccessDeniedReason do
include TermsHelper
let(:user) { build(:user) }
diff --git a/spec/lib/gitlab/auth_spec.rb b/spec/lib/gitlab/auth_spec.rb
index 870f02b6933..0b391c8cba9 100644
--- a/spec/lib/gitlab/auth_spec.rb
+++ b/spec/lib/gitlab/auth_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Auth, :use_clean_rails_memory_store_caching do
+RSpec.describe Gitlab::Auth, :use_clean_rails_memory_store_caching do
let_it_be(:project) { create(:project) }
let(:gl_auth) { described_class }
@@ -172,7 +172,7 @@ describe Gitlab::Auth, :use_clean_rails_memory_store_caching do
end
end
- (HasStatus::AVAILABLE_STATUSES - ['running']).each do |build_status|
+ (Ci::HasStatus::AVAILABLE_STATUSES - ['running']).each do |build_status|
context "for #{build_status} build" do
let!(:build) { create(:ci_build, status: build_status) }
let(:project) { build.project }
diff --git a/spec/lib/gitlab/authorized_keys_spec.rb b/spec/lib/gitlab/authorized_keys_spec.rb
index d89eb9ef114..1053ae2e325 100644
--- a/spec/lib/gitlab/authorized_keys_spec.rb
+++ b/spec/lib/gitlab/authorized_keys_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::AuthorizedKeys do
+RSpec.describe Gitlab::AuthorizedKeys do
let(:logger) { double('logger').as_null_object }
subject(:authorized_keys) { described_class.new(logger) }
diff --git a/spec/lib/gitlab/background_migration/add_merge_request_diff_commits_count_spec.rb b/spec/lib/gitlab/background_migration/add_merge_request_diff_commits_count_spec.rb
index b77c67b120f..1e72b249c19 100644
--- a/spec/lib/gitlab/background_migration/add_merge_request_diff_commits_count_spec.rb
+++ b/spec/lib/gitlab/background_migration/add_merge_request_diff_commits_count_spec.rb
@@ -2,13 +2,15 @@
require 'spec_helper'
-describe Gitlab::BackgroundMigration::AddMergeRequestDiffCommitsCount, schema: 20180105212544 do
+RSpec.describe Gitlab::BackgroundMigration::AddMergeRequestDiffCommitsCount do
+ let(:namespaces_table) { table(:namespaces) }
let(:projects_table) { table(:projects) }
let(:merge_requests_table) { table(:merge_requests) }
let(:merge_request_diffs_table) { table(:merge_request_diffs) }
let(:merge_request_diff_commits_table) { table(:merge_request_diff_commits) }
- let(:project) { projects_table.create!(name: 'gitlab', path: 'gitlab-org/gitlab-ce') }
+ let(:namespace) { namespaces_table.create!(name: 'gitlab-org', path: 'gitlab-org') }
+ let(:project) { projects_table.create!(name: 'gitlab', path: 'gitlab-org/gitlab-ce', namespace_id: namespace.id) }
let(:merge_request) do
merge_requests_table.create!(target_project_id: project.id,
target_branch: 'master',
diff --git a/spec/lib/gitlab/background_migration/archive_legacy_traces_spec.rb b/spec/lib/gitlab/background_migration/archive_legacy_traces_spec.rb
index 5c8dcb38511..7991ad69007 100644
--- a/spec/lib/gitlab/background_migration/archive_legacy_traces_spec.rb
+++ b/spec/lib/gitlab/background_migration/archive_legacy_traces_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::BackgroundMigration::ArchiveLegacyTraces, schema: 20180529152628 do
+RSpec.describe Gitlab::BackgroundMigration::ArchiveLegacyTraces do
include TraceHelpers
let(:namespaces) { table(:namespaces) }
diff --git a/spec/lib/gitlab/background_migration/backfill_deployment_clusters_from_deployments_spec.rb b/spec/lib/gitlab/background_migration/backfill_deployment_clusters_from_deployments_spec.rb
index fdabc8e8f7c..2be9c03e5bd 100644
--- a/spec/lib/gitlab/background_migration/backfill_deployment_clusters_from_deployments_spec.rb
+++ b/spec/lib/gitlab/background_migration/backfill_deployment_clusters_from_deployments_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::BackgroundMigration::BackfillDeploymentClustersFromDeployments, :migration, schema: 20200227140242 do
+RSpec.describe Gitlab::BackgroundMigration::BackfillDeploymentClustersFromDeployments, :migration, schema: 20200227140242 do
subject { described_class.new }
describe '#perform' do
diff --git a/spec/lib/gitlab/background_migration/backfill_environment_id_deployment_merge_requests_spec.rb b/spec/lib/gitlab/background_migration/backfill_environment_id_deployment_merge_requests_spec.rb
index 34ac70071bb..550bdc484c9 100644
--- a/spec/lib/gitlab/background_migration/backfill_environment_id_deployment_merge_requests_spec.rb
+++ b/spec/lib/gitlab/background_migration/backfill_environment_id_deployment_merge_requests_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::BackgroundMigration::BackfillEnvironmentIdDeploymentMergeRequests, schema: 20200312134637 do
+RSpec.describe Gitlab::BackgroundMigration::BackfillEnvironmentIdDeploymentMergeRequests, schema: 20200312134637 do
let(:environments) { table(:environments) }
let(:merge_requests) { table(:merge_requests) }
let(:deployments) { table(:deployments) }
diff --git a/spec/lib/gitlab/background_migration/backfill_hashed_project_repositories_spec.rb b/spec/lib/gitlab/background_migration/backfill_hashed_project_repositories_spec.rb
index f64c3ccc058..79b344ea6fa 100644
--- a/spec/lib/gitlab/background_migration/backfill_hashed_project_repositories_spec.rb
+++ b/spec/lib/gitlab/background_migration/backfill_hashed_project_repositories_spec.rb
@@ -2,6 +2,6 @@
require 'spec_helper'
-describe Gitlab::BackgroundMigration::BackfillHashedProjectRepositories, schema: 20181130102132 do
+RSpec.describe Gitlab::BackgroundMigration::BackfillHashedProjectRepositories do
it_behaves_like 'backfill migration for project repositories', :hashed
end
diff --git a/spec/lib/gitlab/background_migration/backfill_legacy_project_repositories_spec.rb b/spec/lib/gitlab/background_migration/backfill_legacy_project_repositories_spec.rb
index 806d044ab40..c4013d002b2 100644
--- a/spec/lib/gitlab/background_migration/backfill_legacy_project_repositories_spec.rb
+++ b/spec/lib/gitlab/background_migration/backfill_legacy_project_repositories_spec.rb
@@ -2,6 +2,6 @@
require 'spec_helper'
-describe Gitlab::BackgroundMigration::BackfillLegacyProjectRepositories, schema: 20181212171634 do
+RSpec.describe Gitlab::BackgroundMigration::BackfillLegacyProjectRepositories do
it_behaves_like 'backfill migration for project repositories', :legacy
end
diff --git a/spec/lib/gitlab/background_migration/backfill_namespace_settings_spec.rb b/spec/lib/gitlab/background_migration/backfill_namespace_settings_spec.rb
new file mode 100644
index 00000000000..43e76a2952e
--- /dev/null
+++ b/spec/lib/gitlab/background_migration/backfill_namespace_settings_spec.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::BackgroundMigration::BackfillNamespaceSettings, schema: 20200703125016 do
+ let(:namespaces) { table(:namespaces) }
+ let(:namespace_settings) { table(:namespace_settings) }
+ let(:namespace) { table(:namespaces).create!(name: 'user', path: 'user') }
+
+ subject { described_class.new }
+
+ describe '#perform' do
+ it 'creates settings for all projects in range' do
+ namespaces.create!(id: 5, name: 'test1', path: 'test1')
+ namespaces.create!(id: 7, name: 'test2', path: 'test2')
+ namespaces.create!(id: 8, name: 'test3', path: 'test3')
+
+ subject.perform(5, 7)
+
+ expect(namespace_settings.all.pluck(:namespace_id)).to contain_exactly(5, 7)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/background_migration/backfill_project_fullpath_in_repo_config_spec.rb b/spec/lib/gitlab/background_migration/backfill_project_fullpath_in_repo_config_spec.rb
index 44f5c3380a1..1b2e1ed0c1a 100644
--- a/spec/lib/gitlab/background_migration/backfill_project_fullpath_in_repo_config_spec.rb
+++ b/spec/lib/gitlab/background_migration/backfill_project_fullpath_in_repo_config_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::BackgroundMigration::BackfillProjectFullpathInRepoConfig, schema: 20181010133639 do
+RSpec.describe Gitlab::BackgroundMigration::BackfillProjectFullpathInRepoConfig do
let(:namespaces) { table(:namespaces) }
let(:projects) { table(:projects) }
let(:group) { namespaces.create!(name: 'foo', path: 'foo') }
diff --git a/spec/lib/gitlab/background_migration/backfill_project_repositories_spec.rb b/spec/lib/gitlab/background_migration/backfill_project_repositories_spec.rb
index cfaef1578a9..8a8edc1af29 100644
--- a/spec/lib/gitlab/background_migration/backfill_project_repositories_spec.rb
+++ b/spec/lib/gitlab/background_migration/backfill_project_repositories_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
# rubocop:disable RSpec/FactoriesInMigrationSpecs
-describe Gitlab::BackgroundMigration::BackfillProjectRepositories do
+RSpec.describe Gitlab::BackgroundMigration::BackfillProjectRepositories do
let(:group) { create(:group, name: 'foo', path: 'foo') }
describe described_class::ShardFinder do
diff --git a/spec/lib/gitlab/background_migration/backfill_project_settings_spec.rb b/spec/lib/gitlab/background_migration/backfill_project_settings_spec.rb
index a2b4e003d82..4e7a3a33f7e 100644
--- a/spec/lib/gitlab/background_migration/backfill_project_settings_spec.rb
+++ b/spec/lib/gitlab/background_migration/backfill_project_settings_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::BackgroundMigration::BackfillProjectSettings, schema: 20200114113341 do
+RSpec.describe Gitlab::BackgroundMigration::BackfillProjectSettings, schema: 20200114113341 do
let(:projects) { table(:projects) }
let(:project_settings) { table(:project_settings) }
let(:namespace) { table(:namespaces).create(name: 'user', path: 'user') }
diff --git a/spec/lib/gitlab/background_migration/backfill_push_rules_id_in_projects_spec.rb b/spec/lib/gitlab/background_migration/backfill_push_rules_id_in_projects_spec.rb
index f150ed4bd2e..39b49d008d4 100644
--- a/spec/lib/gitlab/background_migration/backfill_push_rules_id_in_projects_spec.rb
+++ b/spec/lib/gitlab/background_migration/backfill_push_rules_id_in_projects_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::BackgroundMigration::BackfillPushRulesIdInProjects, :migration, schema: 2020_03_25_162730 do
+RSpec.describe Gitlab::BackgroundMigration::BackfillPushRulesIdInProjects, :migration, schema: 2020_03_25_162730 do
let(:push_rules) { table(:push_rules) }
let(:projects) { table(:projects) }
let(:project_settings) { table(:project_settings) }
diff --git a/spec/lib/gitlab/background_migration/backfill_snippet_repositories_spec.rb b/spec/lib/gitlab/background_migration/backfill_snippet_repositories_spec.rb
index 27ae60eb278..ec2fd3cc4e0 100644
--- a/spec/lib/gitlab/background_migration/backfill_snippet_repositories_spec.rb
+++ b/spec/lib/gitlab/background_migration/backfill_snippet_repositories_spec.rb
@@ -2,14 +2,13 @@
require 'spec_helper'
-describe Gitlab::BackgroundMigration::BackfillSnippetRepositories, :migration, schema: 2020_04_20_094444 do
+RSpec.describe Gitlab::BackgroundMigration::BackfillSnippetRepositories, :migration, schema: 2020_04_20_094444 do
let(:gitlab_shell) { Gitlab::Shell.new }
let(:users) { table(:users) }
let(:snippets) { table(:snippets) }
let(:snippet_repositories) { table(:snippet_repositories) }
let(:user_state) { 'active' }
- let(:ghost) { false }
let(:user_type) { nil }
let(:user_name) { 'Test' }
@@ -20,13 +19,20 @@ describe Gitlab::BackgroundMigration::BackfillSnippetRepositories, :migration, s
username: 'test',
name: user_name,
state: user_state,
- ghost: ghost,
last_activity_on: 1.minute.ago,
user_type: user_type,
confirmed_at: 1.day.ago)
end
- let(:migration_bot) { User.migration_bot }
+ let!(:migration_bot) do
+ users.create(id: 100,
+ email: "noreply+gitlab-migration-bot%s@#{Settings.gitlab.host}",
+ user_type: HasUserType::USER_TYPES[:migration_bot],
+ name: 'GitLab Migration Bot',
+ projects_limit: 10,
+ username: 'bot')
+ end
+
let!(:snippet_with_repo) { snippets.create(id: 1, type: 'PersonalSnippet', author_id: user.id, file_name: file_name, content: content) }
let!(:snippet_with_empty_repo) { snippets.create(id: 2, type: 'PersonalSnippet', author_id: user.id, file_name: file_name, content: content) }
let!(:snippet_without_repo) { snippets.create(id: 3, type: 'PersonalSnippet', author_id: user.id, file_name: file_name, content: content) }
@@ -113,8 +119,7 @@ describe Gitlab::BackgroundMigration::BackfillSnippetRepositories, :migration, s
end
context 'when user is a ghost' do
- let(:ghost) { true }
- let(:user_type) { 'ghost' }
+ let(:user_type) { HasUserType::USER_TYPES[:ghost] }
it_behaves_like 'migration_bot user commits files'
end
@@ -255,7 +260,6 @@ describe Gitlab::BackgroundMigration::BackfillSnippetRepositories, :migration, s
username: 'test2',
name: 'Test2',
state: user_state,
- ghost: ghost,
last_activity_on: 1.minute.ago,
user_type: user_type,
confirmed_at: 1.day.ago)
diff --git a/spec/lib/gitlab/background_migration/cleanup_concurrent_schema_change_spec.rb b/spec/lib/gitlab/background_migration/cleanup_concurrent_schema_change_spec.rb
new file mode 100644
index 00000000000..2931b5e6dd3
--- /dev/null
+++ b/spec/lib/gitlab/background_migration/cleanup_concurrent_schema_change_spec.rb
@@ -0,0 +1,28 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+RSpec.describe Gitlab::BackgroundMigration::CleanupConcurrentSchemaChange do
+ describe '#perform' do
+ it 'new column does not exist' do
+ expect(subject).to receive(:column_exists?).with(:issues, :closed_at_timestamp).and_return(false)
+ expect(subject).not_to receive(:column_exists?).with(:issues, :closed_at)
+ expect(subject).not_to receive(:define_model_for)
+
+ expect(subject.perform(:issues, :closed_at, :closed_at_timestamp)).to be_nil
+ end
+
+ it 'old column does not exist' do
+ expect(subject).to receive(:column_exists?).with(:issues, :closed_at_timestamp).and_return(true)
+ expect(subject).to receive(:column_exists?).with(:issues, :closed_at).and_return(false)
+ expect(subject).not_to receive(:define_model_for)
+
+ expect(subject.perform(:issues, :closed_at, :closed_at_timestamp)).to be_nil
+ end
+
+ it 'has both old and new columns' do
+ expect(subject).to receive(:column_exists?).twice.and_return(true)
+
+ expect { subject.perform('issues', :closed_at, :created_at) }.to raise_error(NotImplementedError)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/background_migration/digest_column_spec.rb b/spec/lib/gitlab/background_migration/digest_column_spec.rb
deleted file mode 100644
index 0c76ebe9c66..00000000000
--- a/spec/lib/gitlab/background_migration/digest_column_spec.rb
+++ /dev/null
@@ -1,46 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-describe Gitlab::BackgroundMigration::DigestColumn, schema: 20180913142237 do
- let(:personal_access_tokens) { table(:personal_access_tokens) }
- let(:users) { table(:users) }
-
- subject { described_class.new }
-
- describe '#perform' do
- context 'token is not yet hashed' do
- before do
- users.create(id: 1, email: 'user@example.com', projects_limit: 10)
- personal_access_tokens.create!(id: 1, user_id: 1, name: 'pat-01', token: 'token-01')
- end
-
- it 'saves token digest' do
- expect { subject.perform(PersonalAccessToken, :token, :token_digest, 1, 2) }.to(
- change { PersonalAccessToken.find(1).token_digest }.from(nil).to(Gitlab::CryptoHelper.sha256('token-01')))
- end
-
- it 'erases token' do
- expect { subject.perform(PersonalAccessToken, :token, :token_digest, 1, 2) }.to(
- change { PersonalAccessToken.find(1).read_attribute(:token) }.from('token-01').to(nil))
- end
- end
-
- context 'token is already hashed' do
- before do
- users.create(id: 1, email: 'user@example.com', projects_limit: 10)
- personal_access_tokens.create!(id: 1, user_id: 1, name: 'pat-01', token_digest: 'token-digest-01')
- end
-
- it 'does not change existing token digest' do
- expect { subject.perform(PersonalAccessToken, :token, :token_digest, 1, 2) }.not_to(
- change { PersonalAccessToken.find(1).token_digest })
- end
-
- it 'leaves token empty' do
- expect { subject.perform(PersonalAccessToken, :token, :token_digest, 1, 2) }.not_to(
- change { PersonalAccessToken.find(1).read_attribute(:token) }.from(nil))
- end
- end
- end
-end
diff --git a/spec/lib/gitlab/background_migration/encrypt_columns_spec.rb b/spec/lib/gitlab/background_migration/encrypt_columns_spec.rb
deleted file mode 100644
index 6d3ccde7df2..00000000000
--- a/spec/lib/gitlab/background_migration/encrypt_columns_spec.rb
+++ /dev/null
@@ -1,96 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-describe Gitlab::BackgroundMigration::EncryptColumns, schema: 20180910115836 do
- let(:model) { Gitlab::BackgroundMigration::Models::EncryptColumns::WebHook }
- let(:web_hooks) { table(:web_hooks) }
-
- let(:plaintext_attrs) do
- {
- 'encrypted_token' => nil,
- 'encrypted_url' => nil,
- 'token' => 'secret',
- 'url' => 'http://example.com?access_token=secret'
- }
- end
-
- let(:encrypted_attrs) do
- {
- 'encrypted_token' => be_present,
- 'encrypted_url' => be_present,
- 'token' => nil,
- 'url' => nil
- }
- end
-
- describe '#perform' do
- it 'encrypts columns for the specified range' do
- hooks = web_hooks.create([plaintext_attrs] * 5).sort_by(&:id)
-
- # Encrypt all but the first and last rows
- subject.perform(model, [:token, :url], hooks[1].id, hooks[3].id)
-
- hooks = web_hooks.where(id: hooks.map(&:id)).order(:id)
-
- aggregate_failures do
- expect(hooks[0]).to have_attributes(plaintext_attrs)
- expect(hooks[1]).to have_attributes(encrypted_attrs)
- expect(hooks[2]).to have_attributes(encrypted_attrs)
- expect(hooks[3]).to have_attributes(encrypted_attrs)
- expect(hooks[4]).to have_attributes(plaintext_attrs)
- end
- end
-
- it 'acquires an exclusive lock for the update' do
- relation = double('relation', each: nil)
-
- expect(model).to receive(:where) { relation }
- expect(relation).to receive(:lock) { relation }
-
- subject.perform(model, [:token, :url], 1, 1)
- end
-
- it 'skips already-encrypted columns' do
- values = {
- 'encrypted_token' => 'known encrypted token',
- 'encrypted_url' => 'known encrypted url',
- 'token' => 'token',
- 'url' => 'url'
- }
-
- hook = web_hooks.create(values)
-
- subject.perform(model, [:token, :url], hook.id, hook.id)
-
- hook.reload
-
- expect(hook).to have_attributes(values)
- end
-
- it 'reloads the model column information' do
- expect(model).to receive(:reset_column_information).and_call_original
- expect(model).to receive(:define_attribute_methods).and_call_original
-
- subject.perform(model, [:token, :url], 1, 1)
- end
-
- it 'fails if a source column is not present' do
- columns = model.columns.reject { |c| c.name == 'url' }
- allow(model).to receive(:columns) { columns }
-
- expect do
- subject.perform(model, [:token, :url], 1, 1)
- end.to raise_error(/source column: url is missing/)
- end
-
- it 'fails if a destination column is not present' do
- columns = model.columns.reject { |c| c.name == 'encrypted_url' }
- allow(model).to receive(:columns) { columns }
-
- expect do
- subject.perform(model, [:token, :url], 1, 1)
- end.to raise_error(/destination column: encrypted_url is missing/)
- end
- end
-end
diff --git a/spec/lib/gitlab/background_migration/encrypt_runners_tokens_spec.rb b/spec/lib/gitlab/background_migration/encrypt_runners_tokens_spec.rb
deleted file mode 100644
index 89262788d9b..00000000000
--- a/spec/lib/gitlab/background_migration/encrypt_runners_tokens_spec.rb
+++ /dev/null
@@ -1,79 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-describe Gitlab::BackgroundMigration::EncryptRunnersTokens, schema: 20181121111200 do
- let(:settings) { table(:application_settings) }
- let(:namespaces) { table(:namespaces) }
- let(:projects) { table(:projects) }
- let(:runners) { table(:ci_runners) }
-
- context 'when migrating application settings' do
- before do
- settings.create!(id: 1, runners_registration_token: 'plain-text-token1')
- end
-
- it 'migrates runners registration tokens' do
- migrate!(:settings, 1, 1)
-
- encrypted_token = settings.first.runners_registration_token_encrypted
- decrypted_token = ::Gitlab::CryptoHelper.aes256_gcm_decrypt(encrypted_token)
-
- expect(decrypted_token).to eq 'plain-text-token1'
- expect(settings.first.runners_registration_token).to eq 'plain-text-token1'
- end
- end
-
- context 'when migrating namespaces' do
- before do
- namespaces.create!(id: 11, name: 'gitlab', path: 'gitlab-org', runners_token: 'my-token1')
- namespaces.create!(id: 12, name: 'gitlab', path: 'gitlab-org', runners_token: 'my-token2')
- namespaces.create!(id: 22, name: 'gitlab', path: 'gitlab-org', runners_token: 'my-token3')
- end
-
- it 'migrates runners registration tokens' do
- migrate!(:namespace, 11, 22)
-
- expect(namespaces.all.reload).to all(
- have_attributes(runners_token: be_a(String), runners_token_encrypted: be_a(String))
- )
- end
- end
-
- context 'when migrating projects' do
- before do
- namespaces.create!(id: 11, name: 'gitlab', path: 'gitlab-org')
- projects.create!(id: 111, namespace_id: 11, name: 'gitlab', path: 'gitlab-ce', runners_token: 'my-token1')
- projects.create!(id: 114, namespace_id: 11, name: 'gitlab', path: 'gitlab-ce', runners_token: 'my-token2')
- projects.create!(id: 116, namespace_id: 11, name: 'gitlab', path: 'gitlab-ce', runners_token: 'my-token3')
- end
-
- it 'migrates runners registration tokens' do
- migrate!(:project, 111, 116)
-
- expect(projects.all.reload).to all(
- have_attributes(runners_token: be_a(String), runners_token_encrypted: be_a(String))
- )
- end
- end
-
- context 'when migrating runners' do
- before do
- runners.create!(id: 201, runner_type: 1, token: 'plain-text-token1')
- runners.create!(id: 202, runner_type: 1, token: 'plain-text-token2')
- runners.create!(id: 203, runner_type: 1, token: 'plain-text-token3')
- end
-
- it 'migrates runners communication tokens' do
- migrate!(:runner, 201, 203)
-
- expect(runners.all.reload).to all(
- have_attributes(token: be_a(String), token_encrypted: be_a(String))
- )
- end
- end
-
- def migrate!(model, from, to)
- subject.perform(model, from, to)
- end
-end
diff --git a/spec/lib/gitlab/background_migration/fix_cross_project_label_links_spec.rb b/spec/lib/gitlab/background_migration/fix_cross_project_label_links_spec.rb
index cc4ce023f04..8e3ace083fc 100644
--- a/spec/lib/gitlab/background_migration/fix_cross_project_label_links_spec.rb
+++ b/spec/lib/gitlab/background_migration/fix_cross_project_label_links_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::BackgroundMigration::FixCrossProjectLabelLinks, schema: 20180702120647 do
+RSpec.describe Gitlab::BackgroundMigration::FixCrossProjectLabelLinks do
let(:namespaces_table) { table(:namespaces) }
let(:projects_table) { table(:projects) }
let(:issues_table) { table(:issues) }
diff --git a/spec/lib/gitlab/background_migration/fix_projects_without_project_feature_spec.rb b/spec/lib/gitlab/background_migration/fix_projects_without_project_feature_spec.rb
index 056ddd7adf9..e2175c41513 100644
--- a/spec/lib/gitlab/background_migration/fix_projects_without_project_feature_spec.rb
+++ b/spec/lib/gitlab/background_migration/fix_projects_without_project_feature_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::BackgroundMigration::FixProjectsWithoutProjectFeature, schema: 2020_01_27_111840 do
+RSpec.describe Gitlab::BackgroundMigration::FixProjectsWithoutProjectFeature, schema: 2020_01_27_111840 do
let(:namespaces) { table(:namespaces) }
let(:projects) { table(:projects) }
let(:project_features) { table(:project_features) }
diff --git a/spec/lib/gitlab/background_migration/fix_projects_without_prometheus_service_spec.rb b/spec/lib/gitlab/background_migration/fix_projects_without_prometheus_service_spec.rb
index 3c3e37df200..fe2b206ea74 100644
--- a/spec/lib/gitlab/background_migration/fix_projects_without_prometheus_service_spec.rb
+++ b/spec/lib/gitlab/background_migration/fix_projects_without_prometheus_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::BackgroundMigration::FixProjectsWithoutPrometheusService, :migration, schema: 2020_02_20_115023 do
+RSpec.describe Gitlab::BackgroundMigration::FixProjectsWithoutPrometheusService, :migration, schema: 2020_02_20_115023 do
def service_params_for(project_id, params = {})
{
project_id: project_id,
diff --git a/spec/lib/gitlab/background_migration/fix_promoted_epics_discussion_ids_spec.rb b/spec/lib/gitlab/background_migration/fix_promoted_epics_discussion_ids_spec.rb
index 141a0af6c29..452fc962c7b 100644
--- a/spec/lib/gitlab/background_migration/fix_promoted_epics_discussion_ids_spec.rb
+++ b/spec/lib/gitlab/background_migration/fix_promoted_epics_discussion_ids_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::BackgroundMigration::FixPromotedEpicsDiscussionIds, schema: 20190715193142 do
+RSpec.describe Gitlab::BackgroundMigration::FixPromotedEpicsDiscussionIds, schema: 20190715193142 do
let(:namespaces) { table(:namespaces) }
let(:users) { table(:users) }
let(:epics) { table(:epics) }
diff --git a/spec/lib/gitlab/background_migration/fix_user_namespace_names_spec.rb b/spec/lib/gitlab/background_migration/fix_user_namespace_names_spec.rb
index 52760cdd115..7768411828c 100644
--- a/spec/lib/gitlab/background_migration/fix_user_namespace_names_spec.rb
+++ b/spec/lib/gitlab/background_migration/fix_user_namespace_names_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::BackgroundMigration::FixUserNamespaceNames, schema: 20190620112608 do
+RSpec.describe Gitlab::BackgroundMigration::FixUserNamespaceNames, schema: 20190620112608 do
let(:namespaces) { table(:namespaces) }
let(:users) { table(:users) }
let(:user) { users.create(name: "The user's full name", projects_limit: 10, username: 'not-null', email: '1') }
diff --git a/spec/lib/gitlab/background_migration/fix_user_project_route_names_spec.rb b/spec/lib/gitlab/background_migration/fix_user_project_route_names_spec.rb
index 0fb7eea2bd7..4c04043ebd0 100644
--- a/spec/lib/gitlab/background_migration/fix_user_project_route_names_spec.rb
+++ b/spec/lib/gitlab/background_migration/fix_user_project_route_names_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::BackgroundMigration::FixUserProjectRouteNames, schema: 20190620112608 do
+RSpec.describe Gitlab::BackgroundMigration::FixUserProjectRouteNames, schema: 20190620112608 do
let(:namespaces) { table(:namespaces) }
let(:users) { table(:users) }
let(:routes) { table(:routes) }
diff --git a/spec/lib/gitlab/background_migration/legacy_upload_mover_spec.rb b/spec/lib/gitlab/background_migration/legacy_upload_mover_spec.rb
index 850ef48d44a..bf793e7c537 100644
--- a/spec/lib/gitlab/background_migration/legacy_upload_mover_spec.rb
+++ b/spec/lib/gitlab/background_migration/legacy_upload_mover_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
# rubocop: disable RSpec/FactoriesInMigrationSpecs
-describe Gitlab::BackgroundMigration::LegacyUploadMover do
+RSpec.describe Gitlab::BackgroundMigration::LegacyUploadMover do
let(:test_dir) { FileUploader.options['storage_path'] }
let(:filename) { 'image.png' }
diff --git a/spec/lib/gitlab/background_migration/legacy_uploads_migrator_spec.rb b/spec/lib/gitlab/background_migration/legacy_uploads_migrator_spec.rb
index 85187d039c1..66a1787b2cb 100644
--- a/spec/lib/gitlab/background_migration/legacy_uploads_migrator_spec.rb
+++ b/spec/lib/gitlab/background_migration/legacy_uploads_migrator_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
# rubocop: disable RSpec/FactoriesInMigrationSpecs
-describe Gitlab::BackgroundMigration::LegacyUploadsMigrator do
+RSpec.describe Gitlab::BackgroundMigration::LegacyUploadsMigrator do
let(:test_dir) { FileUploader.options['storage_path'] }
let!(:hashed_project) { create(:project) }
diff --git a/spec/lib/gitlab/background_migration/link_lfs_objects_projects_spec.rb b/spec/lib/gitlab/background_migration/link_lfs_objects_projects_spec.rb
index 5700cac2e0f..dda4f5a3a36 100644
--- a/spec/lib/gitlab/background_migration/link_lfs_objects_projects_spec.rb
+++ b/spec/lib/gitlab/background_migration/link_lfs_objects_projects_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::BackgroundMigration::LinkLfsObjectsProjects, :migration, schema: 2020_03_10_075115 do
+RSpec.describe Gitlab::BackgroundMigration::LinkLfsObjectsProjects, :migration, schema: 2020_03_10_075115 do
let(:namespaces) { table(:namespaces) }
let(:projects) { table(:projects) }
let(:fork_networks) { table(:fork_networks) }
diff --git a/spec/lib/gitlab/background_migration/mailers/unconfirm_mailer_spec.rb b/spec/lib/gitlab/background_migration/mailers/unconfirm_mailer_spec.rb
new file mode 100644
index 00000000000..f430009989b
--- /dev/null
+++ b/spec/lib/gitlab/background_migration/mailers/unconfirm_mailer_spec.rb
@@ -0,0 +1,12 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::BackgroundMigration::Mailers::UnconfirmMailer do
+ let(:user) { User.new(id: 1111) }
+ let(:subject) { described_class.unconfirm_notification_email(user) }
+
+ it 'contains abuse report url' do
+ expect(subject.body.encoded).to include(Rails.application.routes.url_helpers.new_abuse_report_url(user_id: user.id))
+ end
+end
diff --git a/spec/lib/gitlab/background_migration/merge_request_assignees_migration_progress_check_spec.rb b/spec/lib/gitlab/background_migration/merge_request_assignees_migration_progress_check_spec.rb
index 9dd97b58014..a3840e3a22e 100644
--- a/spec/lib/gitlab/background_migration/merge_request_assignees_migration_progress_check_spec.rb
+++ b/spec/lib/gitlab/background_migration/merge_request_assignees_migration_progress_check_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::BackgroundMigration::MergeRequestAssigneesMigrationProgressCheck do
+RSpec.describe Gitlab::BackgroundMigration::MergeRequestAssigneesMigrationProgressCheck do
context 'rescheduling' do
context 'when there are ongoing and no dead jobs' do
it 'reschedules check' do
diff --git a/spec/lib/gitlab/background_migration/migrate_build_stage_spec.rb b/spec/lib/gitlab/background_migration/migrate_build_stage_spec.rb
index adf358f5320..65d45ec694f 100644
--- a/spec/lib/gitlab/background_migration/migrate_build_stage_spec.rb
+++ b/spec/lib/gitlab/background_migration/migrate_build_stage_spec.rb
@@ -2,7 +2,8 @@
require 'spec_helper'
-describe Gitlab::BackgroundMigration::MigrateBuildStage, schema: 20180212101928 do
+RSpec.describe Gitlab::BackgroundMigration::MigrateBuildStage do
+ let(:namespaces) { table(:namespaces) }
let(:projects) { table(:projects) }
let(:pipelines) { table(:ci_pipelines) }
let(:stages) { table(:ci_stages) }
@@ -22,7 +23,8 @@ describe Gitlab::BackgroundMigration::MigrateBuildStage, schema: 20180212101928
end
before do
- projects.create!(id: 123, name: 'gitlab', path: 'gitlab-ce')
+ namespace = namespaces.create!(name: 'gitlab-org', path: 'gitlab-org')
+ projects.create!(id: 123, name: 'gitlab', path: 'gitlab-ce', namespace_id: namespace.id)
pipelines.create!(id: 1, project_id: 123, ref: 'master', sha: 'adf43c3a')
jobs.create!(id: 1, commit_id: 1, project_id: 123,
@@ -53,7 +55,7 @@ describe Gitlab::BackgroundMigration::MigrateBuildStage, schema: 20180212101928
statuses[:pending]]
end
- it 'recovers from unique constraint violation only twice', :quarantine do
+ it 'recovers from unique constraint violation only twice', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/28128' do
allow(described_class::Migratable::Stage)
.to receive(:find_by).and_return(nil)
diff --git a/spec/lib/gitlab/background_migration/migrate_fingerprint_sha256_within_keys_spec.rb b/spec/lib/gitlab/background_migration/migrate_fingerprint_sha256_within_keys_spec.rb
index 79a8cd926a7..c58b2d609e9 100644
--- a/spec/lib/gitlab/background_migration/migrate_fingerprint_sha256_within_keys_spec.rb
+++ b/spec/lib/gitlab/background_migration/migrate_fingerprint_sha256_within_keys_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::BackgroundMigration::MigrateFingerprintSha256WithinKeys, schema: 20200106071113 do
+RSpec.describe Gitlab::BackgroundMigration::MigrateFingerprintSha256WithinKeys, schema: 20200106071113 do
subject(:fingerprint_migrator) { described_class.new }
let(:key_table) { table(:keys) }
diff --git a/spec/lib/gitlab/background_migration/migrate_issue_trackers_sensitive_data_spec.rb b/spec/lib/gitlab/background_migration/migrate_issue_trackers_sensitive_data_spec.rb
index 4411dca3fd9..d53f79c61c1 100644
--- a/spec/lib/gitlab/background_migration/migrate_issue_trackers_sensitive_data_spec.rb
+++ b/spec/lib/gitlab/background_migration/migrate_issue_trackers_sensitive_data_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe Gitlab::BackgroundMigration::MigrateIssueTrackersSensitiveData, schema: 20200130145430 do
+RSpec.describe Gitlab::BackgroundMigration::MigrateIssueTrackersSensitiveData, schema: 20200130145430 do
let(:services) { table(:services) }
before do
diff --git a/spec/lib/gitlab/background_migration/migrate_legacy_artifacts_spec.rb b/spec/lib/gitlab/background_migration/migrate_legacy_artifacts_spec.rb
index 5f2a27acd9b..08f2b2a043e 100644
--- a/spec/lib/gitlab/background_migration/migrate_legacy_artifacts_spec.rb
+++ b/spec/lib/gitlab/background_migration/migrate_legacy_artifacts_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::BackgroundMigration::MigrateLegacyArtifacts, schema: 20180816161409 do
+RSpec.describe Gitlab::BackgroundMigration::MigrateLegacyArtifacts do
let(:namespaces) { table(:namespaces) }
let(:projects) { table(:projects) }
let(:pipelines) { table(:ci_pipelines) }
diff --git a/spec/lib/gitlab/background_migration/migrate_null_private_profile_to_false_spec.rb b/spec/lib/gitlab/background_migration/migrate_null_private_profile_to_false_spec.rb
index ff88d2a5d00..6ff1157cb86 100644
--- a/spec/lib/gitlab/background_migration/migrate_null_private_profile_to_false_spec.rb
+++ b/spec/lib/gitlab/background_migration/migrate_null_private_profile_to_false_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::BackgroundMigration::MigrateNullPrivateProfileToFalse, schema: 20190620105427 do
+RSpec.describe Gitlab::BackgroundMigration::MigrateNullPrivateProfileToFalse, schema: 20190620105427 do
let(:users) { table(:users) }
it 'correctly migrates nil private_profile to false' do
diff --git a/spec/lib/gitlab/background_migration/migrate_pages_metadata_spec.rb b/spec/lib/gitlab/background_migration/migrate_pages_metadata_spec.rb
index 10a1d4ee1b9..906a6a747c9 100644
--- a/spec/lib/gitlab/background_migration/migrate_pages_metadata_spec.rb
+++ b/spec/lib/gitlab/background_migration/migrate_pages_metadata_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::BackgroundMigration::MigratePagesMetadata, schema: 20190919040324 do
+RSpec.describe Gitlab::BackgroundMigration::MigratePagesMetadata, schema: 20190919040324 do
let(:projects) { table(:projects) }
subject(:migrate_pages_metadata) { described_class.new }
diff --git a/spec/lib/gitlab/background_migration/migrate_stage_index_spec.rb b/spec/lib/gitlab/background_migration/migrate_stage_index_spec.rb
index 437be125cf0..81874ff7982 100644
--- a/spec/lib/gitlab/background_migration/migrate_stage_index_spec.rb
+++ b/spec/lib/gitlab/background_migration/migrate_stage_index_spec.rb
@@ -2,35 +2,33 @@
require 'spec_helper'
-describe Gitlab::BackgroundMigration::MigrateStageIndex, schema: 20180420080616 do
+RSpec.describe Gitlab::BackgroundMigration::MigrateStageIndex do
let(:namespaces) { table(:namespaces) }
let(:projects) { table(:projects) }
let(:pipelines) { table(:ci_pipelines) }
let(:stages) { table(:ci_stages) }
let(:jobs) { table(:ci_builds) }
+ let(:namespace) { namespaces.create(name: 'gitlab-org', path: 'gitlab-org') }
+ let(:project) { projects.create!(namespace_id: namespace.id, name: 'gitlab', path: 'gitlab') }
+ let(:pipeline) { pipelines.create!(project_id: project.id, ref: 'master', sha: 'adf43c3a') }
+ let(:stage1) { stages.create(project_id: project.id, pipeline_id: pipeline.id, name: 'build') }
+ let(:stage2) { stages.create(project_id: project.id, pipeline_id: pipeline.id, name: 'test') }
before do
- namespaces.create(id: 10, name: 'gitlab-org', path: 'gitlab-org')
- projects.create!(id: 11, namespace_id: 10, name: 'gitlab', path: 'gitlab')
- pipelines.create!(id: 12, project_id: 11, ref: 'master', sha: 'adf43c3a')
-
- stages.create(id: 100, project_id: 11, pipeline_id: 12, name: 'build')
- stages.create(id: 101, project_id: 11, pipeline_id: 12, name: 'test')
-
- jobs.create!(id: 121, commit_id: 12, project_id: 11,
- stage_idx: 2, stage_id: 100)
- jobs.create!(id: 122, commit_id: 12, project_id: 11,
- stage_idx: 2, stage_id: 100)
- jobs.create!(id: 123, commit_id: 12, project_id: 11,
- stage_idx: 10, stage_id: 100)
- jobs.create!(id: 124, commit_id: 12, project_id: 11,
- stage_idx: 3, stage_id: 101)
+ jobs.create!(commit_id: pipeline.id, project_id: project.id,
+ stage_idx: 2, stage_id: stage1.id)
+ jobs.create!(commit_id: pipeline.id, project_id: project.id,
+ stage_idx: 2, stage_id: stage1.id)
+ jobs.create!(commit_id: pipeline.id, project_id: project.id,
+ stage_idx: 10, stage_id: stage1.id)
+ jobs.create!(commit_id: pipeline.id, project_id: project.id,
+ stage_idx: 3, stage_id: stage2.id)
end
it 'correctly migrates stages indices' do
expect(stages.all.pluck(:position)).to all(be_nil)
- described_class.new.perform(100, 101)
+ described_class.new.perform(stage1.id, stage2.id)
expect(stages.all.order(:id).pluck(:position)).to eq [2, 3]
end
diff --git a/spec/lib/gitlab/background_migration/migrate_users_bio_to_user_details_spec.rb b/spec/lib/gitlab/background_migration/migrate_users_bio_to_user_details_spec.rb
index 8603eb73bd5..db3cbe7ccdc 100644
--- a/spec/lib/gitlab/background_migration/migrate_users_bio_to_user_details_spec.rb
+++ b/spec/lib/gitlab/background_migration/migrate_users_bio_to_user_details_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::BackgroundMigration::MigrateUsersBioToUserDetails, :migration, schema: 20200323074147 do
+RSpec.describe Gitlab::BackgroundMigration::MigrateUsersBioToUserDetails, :migration, schema: 20200323074147 do
let(:users) { table(:users) }
let(:user_details) do
diff --git a/spec/lib/gitlab/background_migration/populate_canonical_emails_spec.rb b/spec/lib/gitlab/background_migration/populate_canonical_emails_spec.rb
index 37ddb8b569d..ee0024e8526 100644
--- a/spec/lib/gitlab/background_migration/populate_canonical_emails_spec.rb
+++ b/spec/lib/gitlab/background_migration/populate_canonical_emails_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::BackgroundMigration::PopulateCanonicalEmails, :migration, schema: 20200312053852 do
+RSpec.describe Gitlab::BackgroundMigration::PopulateCanonicalEmails, :migration, schema: 20200312053852 do
let(:migration) { described_class.new }
let_it_be(:users_table) { table(:users) }
diff --git a/spec/lib/gitlab/background_migration/populate_cluster_kubernetes_namespace_table_spec.rb b/spec/lib/gitlab/background_migration/populate_cluster_kubernetes_namespace_table_spec.rb
index d445858b8e8..73faca54b52 100644
--- a/spec/lib/gitlab/background_migration/populate_cluster_kubernetes_namespace_table_spec.rb
+++ b/spec/lib/gitlab/background_migration/populate_cluster_kubernetes_namespace_table_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::BackgroundMigration::PopulateClusterKubernetesNamespaceTable, schema: 20181022173835 do
+RSpec.describe Gitlab::BackgroundMigration::PopulateClusterKubernetesNamespaceTable do
include MigrationHelpers::ClusterHelpers
let(:migration) { described_class.new }
diff --git a/spec/lib/gitlab/background_migration/populate_merge_request_assignees_table_spec.rb b/spec/lib/gitlab/background_migration/populate_merge_request_assignees_table_spec.rb
index e65b3549de0..1e5773ee16b 100644
--- a/spec/lib/gitlab/background_migration/populate_merge_request_assignees_table_spec.rb
+++ b/spec/lib/gitlab/background_migration/populate_merge_request_assignees_table_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::BackgroundMigration::PopulateMergeRequestAssigneesTable, schema: 20190315191339 do
+RSpec.describe Gitlab::BackgroundMigration::PopulateMergeRequestAssigneesTable, schema: 20190315191339 do
let(:namespaces) { table(:namespaces) }
let(:projects) { table(:projects) }
let(:users) { table(:users) }
diff --git a/spec/lib/gitlab/background_migration/populate_project_snippet_statistics_spec.rb b/spec/lib/gitlab/background_migration/populate_project_snippet_statistics_spec.rb
new file mode 100644
index 00000000000..897f5e81372
--- /dev/null
+++ b/spec/lib/gitlab/background_migration/populate_project_snippet_statistics_spec.rb
@@ -0,0 +1,224 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::BackgroundMigration::PopulateProjectSnippetStatistics do
+ let(:file_name) { 'file_name.rb' }
+ let(:content) { 'content' }
+ let(:snippets) { table(:snippets) }
+ let(:snippet_repositories) { table(:snippet_repositories) }
+ let(:users) { table(:users) }
+ let(:namespaces) { table(:namespaces) }
+ let(:snippet_statistics) { table(:snippet_statistics) }
+ let(:project_statistics) { table(:project_statistics) }
+ let(:projects) { table(:projects) }
+ let(:namespace_statistics) { table(:namespace_root_storage_statistics) }
+ let(:routes) { table(:routes) }
+ let(:repo_size) { 123456 }
+ let(:expected_repo_size) { repo_size.megabytes }
+
+ let(:user) { users.create!(id: 1, email: 'test@example.com', projects_limit: 100, username: 'test') }
+ let(:group) { namespaces.create!(id: 10, type: 'Group', name: 'group1', path: 'group1') }
+ let(:user_namespace) { namespaces.create!(id: 20, name: 'user', path: 'user', owner_id: user.id) }
+
+ let(:project1) { create_project(1, 'test', group) }
+ let(:project2) { create_project(2, 'test1', user_namespace) }
+ let(:project3) { create_project(3, 'test2', group) }
+
+ let!(:project_stats1) { create_project_statistics(project1) }
+ let!(:project_stats2) { create_project_statistics(project2) }
+ let!(:project_stats3) { create_project_statistics(project3) }
+
+ let(:ids) { snippets.pluck(:id) }
+ let(:migration) { described_class.new }
+
+ subject do
+ migration.perform(ids)
+
+ project_stats1.reload if project_stats1.persisted?
+ project_stats2.reload if project_stats2.persisted?
+ project_stats3.reload if project_stats3.persisted?
+ end
+
+ before do
+ allow_any_instance_of(Repository).to receive(:size).and_return(repo_size)
+ end
+
+ after do
+ snippets.all.each { |s| raw_repository(s).remove }
+ end
+
+ context 'with existing user and group snippets' do
+ let!(:snippet1) { create_snippet(1, project1) }
+ let!(:snippet2) { create_snippet(2, project1) }
+ let!(:snippet3) { create_snippet(3, project2) }
+ let!(:snippet4) { create_snippet(4, project2) }
+ let!(:snippet5) { create_snippet(5, project3) }
+
+ before do
+ create_snippet_statistics(2, 0)
+ create_snippet_statistics(4, 123)
+ end
+
+ it 'creates/updates all snippet_statistics' do
+ expect(snippet_statistics.count).to eq 2
+
+ subject
+
+ expect(snippet_statistics.count).to eq 5
+
+ snippet_statistics.all.each do |stat|
+ expect(stat.repository_size).to eq expected_repo_size
+ end
+ end
+
+ it 'updates associated snippet project statistics' do
+ expect(project_stats1.snippets_size).to be_nil
+ expect(project_stats2.snippets_size).to be_nil
+
+ subject
+
+ snippets_size = snippet_statistics.where(snippet_id: [snippet1.id, snippet2.id]).sum(:repository_size)
+ expect(project_stats1.snippets_size).to eq snippets_size
+
+ snippets_size = snippet_statistics.where(snippet_id: [snippet3.id, snippet4.id]).sum(:repository_size)
+ expect(project_stats2.snippets_size).to eq snippets_size
+
+ snippets_size = snippet_statistics.where(snippet_id: snippet5.id).sum(:repository_size)
+ expect(project_stats3.snippets_size).to eq snippets_size
+ end
+
+ it 'forces the project statistics refresh' do
+ expect(migration).to receive(:update_project_statistics).exactly(3).times
+
+ subject
+ end
+
+ it 'creates/updates the associated namespace statistics' do
+ expect(migration).to receive(:update_namespace_statistics).twice.and_call_original
+
+ subject
+
+ expect(namespace_statistics.find_by(namespace_id: group.id).snippets_size).to eq project_stats1.snippets_size + project_stats3.snippets_size
+ expect(namespace_statistics.find_by(namespace_id: user_namespace.id).snippets_size).to eq project_stats2.snippets_size
+ end
+
+ context 'when the project statistics does not exists' do
+ it 'does not raise any error' do
+ project_stats3.delete
+
+ subject
+
+ expect(namespace_statistics.find_by(namespace_id: group.id).snippets_size).to eq project_stats1.snippets_size
+ expect(namespace_statistics.find_by(namespace_id: user_namespace.id).snippets_size).to eq project_stats2.snippets_size
+ end
+ end
+
+ context 'when an error is raised when updating a project statistics' do
+ it 'logs the error and continue execution' do
+ expect(migration).to receive(:update_project_statistics).with(Project.find(project1.id)).and_raise('Error')
+ expect(migration).to receive(:update_project_statistics).with(Project.find(project2.id)).and_call_original
+ expect(migration).to receive(:update_project_statistics).with(Project.find(project3.id)).and_call_original
+
+ expect_next_instance_of(Gitlab::BackgroundMigration::Logger) do |instance|
+ expect(instance).to receive(:error).with(message: /Error updating statistics for project #{project1.id}/).once
+ end
+
+ subject
+
+ expect(project_stats2.snippets_size).not_to be_nil
+ expect(project_stats3.snippets_size).not_to be_nil
+ end
+ end
+
+ context 'when an error is raised when updating a namespace statistics' do
+ it 'logs the error and continue execution' do
+ expect(migration).to receive(:update_namespace_statistics).with(Group.find(group.id)).and_raise('Error')
+ expect(migration).to receive(:update_namespace_statistics).with(Namespace.find(user_namespace.id)).and_call_original
+
+ expect_next_instance_of(Gitlab::BackgroundMigration::Logger) do |instance|
+ expect(instance).to receive(:error).with(message: /Error updating statistics for namespace/).once
+ end
+
+ subject
+
+ expect(namespace_statistics.find_by(namespace_id: user_namespace.id).snippets_size).to eq project_stats2.snippets_size
+ end
+ end
+ end
+
+ context 'when project snippet is in a subgroup' do
+ let(:subgroup) { namespaces.create!(id: 30, type: 'Group', name: 'subgroup', path: 'subgroup', parent_id: group.id) }
+ let(:project1) { create_project(1, 'test', subgroup, "#{group.path}/#{subgroup.path}/test") }
+ let!(:snippet1) { create_snippet(1, project1) }
+
+ it 'updates the root namespace statistics' do
+ subject
+
+ expect(snippet_statistics.count).to eq 1
+ expect(project_stats1.snippets_size).to eq snippet_statistics.first.repository_size
+ expect(namespace_statistics.find_by(namespace_id: subgroup.id)).to be_nil
+ expect(namespace_statistics.find_by(namespace_id: group.id).snippets_size).to eq project_stats1.snippets_size
+ end
+ end
+
+ context 'when a snippet repository is empty' do
+ let!(:snippet1) { create_snippet(1, project1, with_repo: false) }
+ let!(:snippet2) { create_snippet(2, project1) }
+
+ it 'logs error and continues execution' do
+ expect_next_instance_of(Gitlab::BackgroundMigration::Logger) do |instance|
+ expect(instance).to receive(:error).with(message: /Invalid snippet repository/).once
+ end
+
+ subject
+
+ expect(snippet_statistics.find_by(snippet_id: snippet1.id)).to be_nil
+ expect(project_stats1.snippets_size).to eq snippet_statistics.find(snippet2.id).repository_size
+ end
+ end
+
+ def create_snippet(id, project, with_repo: true)
+ snippets.create!(id: id, type: 'ProjectSnippet', project_id: project.id, author_id: user.id, file_name: file_name, content: content).tap do |snippet|
+ if with_repo
+ allow(snippet).to receive(:disk_path).and_return(disk_path(snippet))
+
+ TestEnv.copy_repo(snippet,
+ bare_repo: TestEnv.factory_repo_path_bare,
+ refs: TestEnv::BRANCH_SHA)
+
+ raw_repository(snippet).create_repository
+ end
+ end
+ end
+
+ def create_project(id, name, namespace, path = nil)
+ projects.create!(id: id, name: name, path: name.downcase.gsub(/\s/, '_'), namespace_id: namespace.id).tap do |project|
+ path ||= "#{namespace.path}/#{project.path}"
+ routes.create!(id: id, source_type: 'Project', source_id: project.id, path: path)
+ end
+ end
+
+ def create_snippet_statistics(snippet_id, repository_size = 0)
+ snippet_statistics.create!(snippet_id: snippet_id, repository_size: repository_size)
+ end
+
+ def create_project_statistics(project, snippets_size = nil)
+ project_statistics.create!(id: project.id, project_id: project.id, namespace_id: project.namespace_id, snippets_size: snippets_size)
+ end
+
+ def raw_repository(snippet)
+ Gitlab::Git::Repository.new('default',
+ "#{disk_path(snippet)}.git",
+ Gitlab::GlRepository::SNIPPET.identifier_for_container(snippet),
+ "@snippets/#{snippet.id}")
+ end
+
+ def hashed_repository(snippet)
+ Storage::Hashed.new(snippet, prefix: '@snippets')
+ end
+
+ def disk_path(snippet)
+ hashed_repository(snippet).disk_path
+ end
+end
diff --git a/spec/lib/gitlab/background_migration/populate_untracked_uploads_dependencies/untracked_file_spec.rb b/spec/lib/gitlab/background_migration/populate_untracked_uploads_dependencies/untracked_file_spec.rb
index 0250ebd7759..6a25e8e2784 100644
--- a/spec/lib/gitlab/background_migration/populate_untracked_uploads_dependencies/untracked_file_spec.rb
+++ b/spec/lib/gitlab/background_migration/populate_untracked_uploads_dependencies/untracked_file_spec.rb
@@ -2,8 +2,7 @@
require 'spec_helper'
-# Rollback DB to 10.5 (later than this was originally written for) because it still needs to work.
-describe Gitlab::BackgroundMigration::PopulateUntrackedUploadsDependencies::UntrackedFile, schema: 20180208183958 do
+RSpec.describe Gitlab::BackgroundMigration::PopulateUntrackedUploadsDependencies::UntrackedFile do
include MigrationsHelpers::TrackUntrackedUploadsHelpers
let!(:appearances) { table(:appearances) }
diff --git a/spec/lib/gitlab/background_migration/populate_untracked_uploads_spec.rb b/spec/lib/gitlab/background_migration/populate_untracked_uploads_spec.rb
index 44cec112bfd..787cc54e79a 100644
--- a/spec/lib/gitlab/background_migration/populate_untracked_uploads_spec.rb
+++ b/spec/lib/gitlab/background_migration/populate_untracked_uploads_spec.rb
@@ -2,8 +2,7 @@
require 'spec_helper'
-# Rollback DB to 10.5 (later than this was originally written for) because it still needs to work.
-describe Gitlab::BackgroundMigration::PopulateUntrackedUploads, schema: 20180208183958 do
+RSpec.describe Gitlab::BackgroundMigration::PopulateUntrackedUploads do
include MigrationsHelpers::TrackUntrackedUploadsHelpers
subject { described_class.new }
diff --git a/spec/lib/gitlab/background_migration/populate_user_highest_roles_table_spec.rb b/spec/lib/gitlab/background_migration/populate_user_highest_roles_table_spec.rb
index be661d5b83e..f0b0f77280e 100644
--- a/spec/lib/gitlab/background_migration/populate_user_highest_roles_table_spec.rb
+++ b/spec/lib/gitlab/background_migration/populate_user_highest_roles_table_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::BackgroundMigration::PopulateUserHighestRolesTable, schema: 20200311130802 do
+RSpec.describe Gitlab::BackgroundMigration::PopulateUserHighestRolesTable, schema: 20200311130802 do
let(:members) { table(:members) }
let(:users) { table(:users) }
let(:user_highest_roles) { table(:user_highest_roles) }
diff --git a/spec/lib/gitlab/background_migration/prepare_untracked_uploads_spec.rb b/spec/lib/gitlab/background_migration/prepare_untracked_uploads_spec.rb
index 9daf35d0311..9b01407dc8b 100644
--- a/spec/lib/gitlab/background_migration/prepare_untracked_uploads_spec.rb
+++ b/spec/lib/gitlab/background_migration/prepare_untracked_uploads_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
# Rollback DB to 10.5 (later than this was originally written for) because it still needs to work.
-describe Gitlab::BackgroundMigration::PrepareUntrackedUploads, schema: 20180208183958 do
+RSpec.describe Gitlab::BackgroundMigration::PrepareUntrackedUploads do
include MigrationsHelpers::TrackUntrackedUploadsHelpers
let!(:untracked_files_for_uploads) { table(:untracked_files_for_uploads) }
diff --git a/spec/lib/gitlab/background_migration/recalculate_project_authorizations_spec.rb b/spec/lib/gitlab/background_migration/recalculate_project_authorizations_spec.rb
index ba87312e2bf..33e1f31d1f1 100644
--- a/spec/lib/gitlab/background_migration/recalculate_project_authorizations_spec.rb
+++ b/spec/lib/gitlab/background_migration/recalculate_project_authorizations_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::BackgroundMigration::RecalculateProjectAuthorizations, schema: 20200204113223 do
+RSpec.describe Gitlab::BackgroundMigration::RecalculateProjectAuthorizations, schema: 20200204113223 do
let(:users_table) { table(:users) }
let(:namespaces_table) { table(:namespaces) }
let(:projects_table) { table(:projects) }
diff --git a/spec/lib/gitlab/background_migration/recalculate_project_authorizations_with_min_max_user_id_spec.rb b/spec/lib/gitlab/background_migration/recalculate_project_authorizations_with_min_max_user_id_spec.rb
index edb46efad7c..c1ba1607b89 100644
--- a/spec/lib/gitlab/background_migration/recalculate_project_authorizations_with_min_max_user_id_spec.rb
+++ b/spec/lib/gitlab/background_migration/recalculate_project_authorizations_with_min_max_user_id_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::BackgroundMigration::RecalculateProjectAuthorizationsWithMinMaxUserId, schema: 20200204113224 do
+RSpec.describe Gitlab::BackgroundMigration::RecalculateProjectAuthorizationsWithMinMaxUserId, schema: 20200204113224 do
let(:users_table) { table(:users) }
let(:min) { 1 }
let(:max) { 5 }
diff --git a/spec/lib/gitlab/background_migration/remove_restricted_todos_spec.rb b/spec/lib/gitlab/background_migration/remove_restricted_todos_spec.rb
index 3de24f577ab..7019d5d4212 100644
--- a/spec/lib/gitlab/background_migration/remove_restricted_todos_spec.rb
+++ b/spec/lib/gitlab/background_migration/remove_restricted_todos_spec.rb
@@ -2,7 +2,8 @@
require 'spec_helper'
-describe Gitlab::BackgroundMigration::RemoveRestrictedTodos, schema: 20180704204006 do
+RSpec.describe Gitlab::BackgroundMigration::RemoveRestrictedTodos do
+ let(:namespaces) { table(:namespaces) }
let(:projects) { table(:projects) }
let(:users) { table(:users) }
let(:todos) { table(:todos) }
@@ -18,8 +19,9 @@ describe Gitlab::BackgroundMigration::RemoveRestrictedTodos, schema: 20180704204
users.create(id: 2, email: 'reporter@example.com', projects_limit: 10)
users.create(id: 3, email: 'guest@example.com', projects_limit: 10)
- projects.create!(id: 1, name: 'project-1', path: 'project-1', visibility_level: 0, namespace_id: 1)
- projects.create!(id: 2, name: 'project-2', path: 'project-2', visibility_level: 0, namespace_id: 1)
+ namespace = namespaces.create(name: 'gitlab-org', path: 'gitlab-org')
+ projects.create!(id: 1, name: 'project-1', path: 'project-1', visibility_level: 0, namespace_id: namespace.id)
+ projects.create!(id: 2, name: 'project-2', path: 'project-2', visibility_level: 0, namespace_id: namespace.id)
issues.create(id: 1, project_id: 1)
issues.create(id: 2, project_id: 2)
@@ -92,7 +94,7 @@ describe Gitlab::BackgroundMigration::RemoveRestrictedTodos, schema: 20180704204
context 'when issues are restricted to project members' do
before do
- project_features.create(issues_access_level: 10, project_id: 2)
+ project_features.create(issues_access_level: 10, pages_access_level: 10, project_id: 2)
end
it 'removes non members issue todos' do
@@ -102,7 +104,7 @@ describe Gitlab::BackgroundMigration::RemoveRestrictedTodos, schema: 20180704204
context 'when merge requests are restricted to project members' do
before do
- project_features.create(merge_requests_access_level: 10, project_id: 2)
+ project_features.create(merge_requests_access_level: 10, pages_access_level: 10, project_id: 2)
end
it 'removes non members issue todos' do
@@ -112,7 +114,7 @@ describe Gitlab::BackgroundMigration::RemoveRestrictedTodos, schema: 20180704204
context 'when repository and merge requests are restricted to project members' do
before do
- project_features.create(repository_access_level: 10, merge_requests_access_level: 10, project_id: 2)
+ project_features.create(repository_access_level: 10, merge_requests_access_level: 10, pages_access_level: 10, project_id: 2)
end
it 'removes non members commit and merge requests todos' do
diff --git a/spec/lib/gitlab/background_migration/reset_merge_status_spec.rb b/spec/lib/gitlab/background_migration/reset_merge_status_spec.rb
index e057aea6bb3..43fc0fb3691 100644
--- a/spec/lib/gitlab/background_migration/reset_merge_status_spec.rb
+++ b/spec/lib/gitlab/background_migration/reset_merge_status_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::BackgroundMigration::ResetMergeStatus do
+RSpec.describe Gitlab::BackgroundMigration::ResetMergeStatus do
let(:namespaces) { table(:namespaces) }
let(:projects) { table(:projects) }
let(:namespace) { namespaces.create(name: 'gitlab', path: 'gitlab-org') }
diff --git a/spec/lib/gitlab/background_migration/schedule_calculate_wiki_sizes_spec.rb b/spec/lib/gitlab/background_migration/schedule_calculate_wiki_sizes_spec.rb
deleted file mode 100644
index 387e3343ede..00000000000
--- a/spec/lib/gitlab/background_migration/schedule_calculate_wiki_sizes_spec.rb
+++ /dev/null
@@ -1,64 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-require Rails.root.join('db', 'post_migrate', '20190527194900_schedule_calculate_wiki_sizes.rb')
-
-describe ScheduleCalculateWikiSizes do
- let(:migration_class) { Gitlab::BackgroundMigration::CalculateWikiSizes }
- let(:migration_name) { migration_class.to_s.demodulize }
-
- let(:namespaces) { table(:namespaces) }
- let(:projects) { table(:projects) }
- let(:project_statistics) { table(:project_statistics) }
-
- context 'when missing wiki sizes exist' do
- before do
- namespaces.create!(id: 1, name: 'wiki-migration', path: 'wiki-migration')
- projects.create!(id: 1, name: 'wiki-project-1', path: 'wiki-project-1', namespace_id: 1)
- projects.create!(id: 2, name: 'wiki-project-2', path: 'wiki-project-2', namespace_id: 1)
- projects.create!(id: 3, name: 'wiki-project-3', path: 'wiki-project-3', namespace_id: 1)
- project_statistics.create!(id: 1, project_id: 1, namespace_id: 1, wiki_size: 1000)
- project_statistics.create!(id: 2, project_id: 2, namespace_id: 1, wiki_size: nil)
- project_statistics.create!(id: 3, project_id: 3, namespace_id: 1, wiki_size: nil)
- end
-
- it 'schedules a background migration' do
- Sidekiq::Testing.fake! do
- Timecop.freeze do
- migrate!
-
- expect(migration_name).to be_scheduled_delayed_migration(5.minutes, 2, 3)
- expect(BackgroundMigrationWorker.jobs.size).to eq 1
- end
- end
- end
-
- it 'calculates missing wiki sizes', :sidekiq_might_not_need_inline do
- expect(project_statistics.find_by(id: 2).wiki_size).to be_nil
- expect(project_statistics.find_by(id: 3).wiki_size).to be_nil
-
- migrate!
-
- expect(project_statistics.find_by(id: 2).wiki_size).not_to be_nil
- expect(project_statistics.find_by(id: 3).wiki_size).not_to be_nil
- end
- end
-
- context 'when missing wiki sizes do not exist' do
- before do
- namespaces.create!(id: 1, name: 'wiki-migration', path: 'wiki-migration')
- projects.create!(id: 1, name: 'wiki-project-1', path: 'wiki-project-1', namespace_id: 1)
- project_statistics.create!(id: 1, project_id: 1, namespace_id: 1, wiki_size: 1000)
- end
-
- it 'does not schedule a background migration' do
- Sidekiq::Testing.fake! do
- Timecop.freeze do
- migrate!
-
- expect(BackgroundMigrationWorker.jobs.size).to eq 0
- end
- end
- end
- end
-end
diff --git a/spec/lib/gitlab/background_migration/set_confidential_note_events_on_services_spec.rb b/spec/lib/gitlab/background_migration/set_confidential_note_events_on_services_spec.rb
index 5ce4a322e51..364edf3ed2a 100644
--- a/spec/lib/gitlab/background_migration/set_confidential_note_events_on_services_spec.rb
+++ b/spec/lib/gitlab/background_migration/set_confidential_note_events_on_services_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::BackgroundMigration::SetConfidentialNoteEventsOnServices, schema: 20180122154930 do
+RSpec.describe Gitlab::BackgroundMigration::SetConfidentialNoteEventsOnServices do
let(:services) { table(:services) }
describe '#perform' do
diff --git a/spec/lib/gitlab/background_migration/set_confidential_note_events_on_webhooks_spec.rb b/spec/lib/gitlab/background_migration/set_confidential_note_events_on_webhooks_spec.rb
index 08f1f543f5d..28b06ac3ba3 100644
--- a/spec/lib/gitlab/background_migration/set_confidential_note_events_on_webhooks_spec.rb
+++ b/spec/lib/gitlab/background_migration/set_confidential_note_events_on_webhooks_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::BackgroundMigration::SetConfidentialNoteEventsOnWebhooks, schema: 20180104131052 do
+RSpec.describe Gitlab::BackgroundMigration::SetConfidentialNoteEventsOnWebhooks do
let(:web_hooks) { table(:web_hooks) }
describe '#perform' do
diff --git a/spec/lib/gitlab/background_migration/update_existing_subgroup_to_match_visibility_level_of_parent_spec.rb b/spec/lib/gitlab/background_migration/update_existing_subgroup_to_match_visibility_level_of_parent_spec.rb
index 70397ae1e30..6c0a1d3a5b0 100644
--- a/spec/lib/gitlab/background_migration/update_existing_subgroup_to_match_visibility_level_of_parent_spec.rb
+++ b/spec/lib/gitlab/background_migration/update_existing_subgroup_to_match_visibility_level_of_parent_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::BackgroundMigration::UpdateExistingSubgroupToMatchVisibilityLevelOfParent, schema: 2020_01_10_121314 do
+RSpec.describe Gitlab::BackgroundMigration::UpdateExistingSubgroupToMatchVisibilityLevelOfParent, schema: 2020_01_10_121314 do
include MigrationHelpers::NamespacesHelpers
context 'private visibility level' do
diff --git a/spec/lib/gitlab/background_migration/user_mentions/create_resource_user_mention_spec.rb b/spec/lib/gitlab/background_migration/user_mentions/create_resource_user_mention_spec.rb
index d4f52a11ce7..08a4bbe38ac 100644
--- a/spec/lib/gitlab/background_migration/user_mentions/create_resource_user_mention_spec.rb
+++ b/spec/lib/gitlab/background_migration/user_mentions/create_resource_user_mention_spec.rb
@@ -4,7 +4,7 @@ require 'spec_helper'
require './db/post_migrate/20200128134110_migrate_commit_notes_mentions_to_db'
require './db/post_migrate/20200211155539_migrate_merge_request_mentions_to_db'
-describe Gitlab::BackgroundMigration::UserMentions::CreateResourceUserMention, schema: 20200211155539 do
+RSpec.describe Gitlab::BackgroundMigration::UserMentions::CreateResourceUserMention, schema: 20200211155539 do
include MigrationsHelpers
context 'when migrating data' do
diff --git a/spec/lib/gitlab/background_migration/wrongfully_confirmed_email_unconfirmer_spec.rb b/spec/lib/gitlab/background_migration/wrongfully_confirmed_email_unconfirmer_spec.rb
new file mode 100644
index 00000000000..c9928edb8e8
--- /dev/null
+++ b/spec/lib/gitlab/background_migration/wrongfully_confirmed_email_unconfirmer_spec.rb
@@ -0,0 +1,123 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::BackgroundMigration::WrongfullyConfirmedEmailUnconfirmer, schema: 20200615111857 do
+ let(:users) { table(:users) }
+ let(:emails) { table(:emails) }
+ let(:confirmed_at_2_days_ago) { 2.days.ago }
+ let(:confirmed_at_3_days_ago) { 3.days.ago }
+ let(:one_year_ago) { 1.year.ago }
+
+ let!(:user_needs_migration_1) { users.create!(name: 'user1', email: 'test1@test.com', state: 'active', projects_limit: 1, confirmed_at: confirmed_at_2_days_ago, confirmation_sent_at: one_year_ago) }
+ let!(:user_needs_migration_2) { users.create!(name: 'user2', email: 'test2@test.com', unconfirmed_email: 'unconfirmed@test.com', state: 'active', projects_limit: 1, confirmed_at: confirmed_at_3_days_ago, confirmation_sent_at: one_year_ago) }
+ let!(:user_does_not_need_migration) { users.create!(name: 'user3', email: 'test3@test.com', state: 'active', projects_limit: 1) }
+ let!(:inactive_user) { users.create!(name: 'user4', email: 'test4@test.com', state: 'blocked', projects_limit: 1, confirmed_at: confirmed_at_3_days_ago, confirmation_sent_at: one_year_ago) }
+ let!(:alert_bot_user) { users.create!(name: 'user5', email: 'test5@test.com', state: 'active', user_type: 2, projects_limit: 1, confirmed_at: confirmed_at_3_days_ago, confirmation_sent_at: one_year_ago) }
+
+ let!(:bad_email_1) { emails.create!(user_id: user_needs_migration_1.id, email: 'other1@test.com', confirmed_at: confirmed_at_2_days_ago, confirmation_sent_at: one_year_ago) }
+ let!(:bad_email_2) { emails.create!(user_id: user_needs_migration_2.id, email: 'other2@test.com', confirmed_at: confirmed_at_3_days_ago, confirmation_sent_at: one_year_ago) }
+ let!(:bad_email_3_inactive_user) { emails.create!(user_id: inactive_user.id, email: 'other-inactive@test.com', confirmed_at: confirmed_at_3_days_ago, confirmation_sent_at: one_year_ago) }
+ let!(:bad_email_4_bot_user) { emails.create!(user_id: alert_bot_user.id, email: 'other-bot@test.com', confirmed_at: confirmed_at_3_days_ago, confirmation_sent_at: one_year_ago) }
+
+ let!(:good_email_1) { emails.create!(user_id: user_needs_migration_2.id, email: 'other3@test.com', confirmed_at: confirmed_at_2_days_ago, confirmation_sent_at: one_year_ago) }
+ let!(:good_email_2) { emails.create!(user_id: user_needs_migration_2.id, email: 'other4@test.com', confirmed_at: nil) }
+ let!(:good_email_3) { emails.create!(user_id: user_does_not_need_migration.id, email: 'other5@test.com', confirmed_at: confirmed_at_2_days_ago, confirmation_sent_at: one_year_ago) }
+
+ subject do
+ email_ids = [bad_email_1, bad_email_2, good_email_1, good_email_2, good_email_3].map(&:id)
+
+ described_class.new.perform(email_ids.min, email_ids.max)
+ end
+
+ it 'does not change irrelevant email records' do
+ subject
+
+ expect(good_email_1.reload.confirmed_at).to be_within(1.second).of(confirmed_at_2_days_ago)
+ expect(good_email_2.reload.confirmed_at).to be_nil
+ expect(good_email_3.reload.confirmed_at).to be_within(1.second).of(confirmed_at_2_days_ago)
+
+ expect(bad_email_3_inactive_user.reload.confirmed_at).to be_within(1.second).of(confirmed_at_3_days_ago)
+ expect(bad_email_4_bot_user.reload.confirmed_at).to be_within(1.second).of(confirmed_at_3_days_ago)
+
+ expect(good_email_1.reload.confirmation_sent_at).to be_within(1.second).of(one_year_ago)
+ expect(good_email_2.reload.confirmation_sent_at).to be_nil
+ expect(good_email_3.reload.confirmation_sent_at).to be_within(1.second).of(one_year_ago)
+
+ expect(bad_email_3_inactive_user.reload.confirmation_sent_at).to be_within(1.second).of(one_year_ago)
+ expect(bad_email_4_bot_user.reload.confirmation_sent_at).to be_within(1.second).of(one_year_ago)
+ end
+
+ it 'clears the `unconfirmed_email` field' do
+ subject
+
+ user_needs_migration_2.reload
+ expect(user_needs_migration_2.unconfirmed_email).to be_nil
+ end
+
+ it 'does not change irrelevant user records' do
+ subject
+
+ expect(user_does_not_need_migration.reload.confirmed_at).to be_nil
+ expect(inactive_user.reload.confirmed_at).to be_within(1.second).of(confirmed_at_3_days_ago)
+ expect(alert_bot_user.reload.confirmed_at).to be_within(1.second).of(confirmed_at_3_days_ago)
+
+ expect(user_does_not_need_migration.reload.confirmation_sent_at).to be_nil
+ expect(inactive_user.reload.confirmation_sent_at).to be_within(1.second).of(one_year_ago)
+ expect(alert_bot_user.reload.confirmation_sent_at).to be_within(1.second).of(one_year_ago)
+ end
+
+ it 'updates confirmation_sent_at column' do
+ subject
+
+ expect(user_needs_migration_1.reload.confirmation_sent_at).to be_within(1.minute).of(Time.now)
+ expect(user_needs_migration_2.reload.confirmation_sent_at).to be_within(1.minute).of(Time.now)
+
+ expect(bad_email_1.reload.confirmation_sent_at).to be_within(1.minute).of(Time.now)
+ expect(bad_email_2.reload.confirmation_sent_at).to be_within(1.minute).of(Time.now)
+ end
+
+ it 'unconfirms bad email records' do
+ subject
+
+ expect(bad_email_1.reload.confirmed_at).to be_nil
+ expect(bad_email_2.reload.confirmed_at).to be_nil
+
+ expect(bad_email_1.reload.confirmation_token).not_to be_nil
+ expect(bad_email_2.reload.confirmation_token).not_to be_nil
+ end
+
+ it 'unconfirms user records' do
+ subject
+
+ expect(user_needs_migration_1.reload.confirmed_at).to be_nil
+ expect(user_needs_migration_2.reload.confirmed_at).to be_nil
+
+ expect(user_needs_migration_1.reload.confirmation_token).not_to be_nil
+ expect(user_needs_migration_2.reload.confirmation_token).not_to be_nil
+ end
+
+ context 'enqueued jobs' do
+ let(:user_1) { User.find(user_needs_migration_1.id) }
+ let(:user_2) { User.find(user_needs_migration_2.id) }
+
+ let(:email_1) { Email.find(bad_email_1.id) }
+ let(:email_2) { Email.find(bad_email_2.id) }
+
+ it 'enqueues the email confirmation and the unconfirm notification mailer jobs' do
+ allow(DeviseMailer).to receive(:confirmation_instructions).and_call_original
+ allow(Gitlab::BackgroundMigration::Mailers::UnconfirmMailer).to receive(:unconfirm_notification_email).and_call_original
+
+ subject
+
+ expect(DeviseMailer).to have_received(:confirmation_instructions).with(email_1, email_1.confirmation_token)
+ expect(DeviseMailer).to have_received(:confirmation_instructions).with(email_2, email_2.confirmation_token)
+
+ expect(Gitlab::BackgroundMigration::Mailers::UnconfirmMailer).to have_received(:unconfirm_notification_email).with(user_1)
+ expect(DeviseMailer).to have_received(:confirmation_instructions).with(user_1, user_1.confirmation_token)
+
+ expect(Gitlab::BackgroundMigration::Mailers::UnconfirmMailer).to have_received(:unconfirm_notification_email).with(user_2)
+ expect(DeviseMailer).to have_received(:confirmation_instructions).with(user_2, user_2.confirmation_token)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/background_migration_spec.rb b/spec/lib/gitlab/background_migration_spec.rb
index 71959f54b38..b110fa484ff 100644
--- a/spec/lib/gitlab/background_migration_spec.rb
+++ b/spec/lib/gitlab/background_migration_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::BackgroundMigration do
+RSpec.describe Gitlab::BackgroundMigration do
describe '.queue' do
it 'returns background migration worker queue' do
expect(described_class.queue)
@@ -47,6 +47,25 @@ describe Gitlab::BackgroundMigration do
described_class.steal('Bar')
end
+
+ context 'when a custom predicate is given' do
+ it 'steals jobs that match the predicate' do
+ expect(queue[0]).to receive(:delete).and_return(true)
+
+ expect(described_class).to receive(:perform)
+ .with('Foo', [10, 20])
+
+ described_class.steal('Foo') { |(arg1, arg2)| arg1 == 10 && arg2 == 20 }
+ end
+
+ it 'does not steal jobs that do not match the predicate' do
+ expect(described_class).not_to receive(:perform)
+
+ expect(queue[0]).not_to receive(:delete)
+
+ described_class.steal('Foo') { |(arg1, _)| arg1 == 5 }
+ end
+ end
end
context 'when one of the jobs raises an error' do
diff --git a/spec/lib/gitlab/backtrace_cleaner_spec.rb b/spec/lib/gitlab/backtrace_cleaner_spec.rb
index f3aded9faad..51d99bf5f74 100644
--- a/spec/lib/gitlab/backtrace_cleaner_spec.rb
+++ b/spec/lib/gitlab/backtrace_cleaner_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::BacktraceCleaner do
+RSpec.describe Gitlab::BacktraceCleaner do
describe '.clean_backtrace' do
it 'uses the Rails backtrace cleaner' do
backtrace = []
diff --git a/spec/lib/gitlab/badge/coverage/metadata_spec.rb b/spec/lib/gitlab/badge/coverage/metadata_spec.rb
index 2b87508bdef..725ae03ad74 100644
--- a/spec/lib/gitlab/badge/coverage/metadata_spec.rb
+++ b/spec/lib/gitlab/badge/coverage/metadata_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require 'lib/gitlab/badge/shared/metadata'
-describe Gitlab::Badge::Coverage::Metadata do
+RSpec.describe Gitlab::Badge::Coverage::Metadata do
let(:badge) do
double(project: create(:project), ref: 'feature', job: 'test')
end
diff --git a/spec/lib/gitlab/badge/coverage/report_spec.rb b/spec/lib/gitlab/badge/coverage/report_spec.rb
index 284ca53a996..9c4dfcbfd54 100644
--- a/spec/lib/gitlab/badge/coverage/report_spec.rb
+++ b/spec/lib/gitlab/badge/coverage/report_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Badge::Coverage::Report do
+RSpec.describe Gitlab::Badge::Coverage::Report do
let(:project) { create(:project, :repository) }
let(:job_name) { nil }
diff --git a/spec/lib/gitlab/badge/coverage/template_spec.rb b/spec/lib/gitlab/badge/coverage/template_spec.rb
index 3940b37830e..5a0adfd8e59 100644
--- a/spec/lib/gitlab/badge/coverage/template_spec.rb
+++ b/spec/lib/gitlab/badge/coverage/template_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Badge::Coverage::Template do
+RSpec.describe Gitlab::Badge::Coverage::Template do
let(:badge) { double(entity: 'coverage', status: 90.00, customization: {}) }
let(:template) { described_class.new(badge) }
diff --git a/spec/lib/gitlab/badge/pipeline/metadata_spec.rb b/spec/lib/gitlab/badge/pipeline/metadata_spec.rb
index b096803f921..c8ed0c8ea29 100644
--- a/spec/lib/gitlab/badge/pipeline/metadata_spec.rb
+++ b/spec/lib/gitlab/badge/pipeline/metadata_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require 'lib/gitlab/badge/shared/metadata'
-describe Gitlab::Badge::Pipeline::Metadata do
+RSpec.describe Gitlab::Badge::Pipeline::Metadata do
let(:badge) { double(project: create(:project), ref: 'feature') }
let(:metadata) { described_class.new(badge) }
diff --git a/spec/lib/gitlab/badge/pipeline/status_spec.rb b/spec/lib/gitlab/badge/pipeline/status_spec.rb
index ab8d1f0ec5b..fcc0d4030fd 100644
--- a/spec/lib/gitlab/badge/pipeline/status_spec.rb
+++ b/spec/lib/gitlab/badge/pipeline/status_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Badge::Pipeline::Status do
+RSpec.describe Gitlab::Badge::Pipeline::Status do
let(:project) { create(:project, :repository) }
let(:sha) { project.commit.sha }
let(:branch) { 'master' }
diff --git a/spec/lib/gitlab/badge/pipeline/template_spec.rb b/spec/lib/gitlab/badge/pipeline/template_spec.rb
index 751a5d6645e..2f0d0782369 100644
--- a/spec/lib/gitlab/badge/pipeline/template_spec.rb
+++ b/spec/lib/gitlab/badge/pipeline/template_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Badge::Pipeline::Template do
+RSpec.describe Gitlab::Badge::Pipeline::Template do
let(:badge) { double(entity: 'pipeline', status: 'success', customization: {}) }
let(:template) { described_class.new(badge) }
diff --git a/spec/lib/gitlab/badge/shared/metadata.rb b/spec/lib/gitlab/badge/shared/metadata.rb
index 809fa54db02..c99a65bb2f4 100644
--- a/spec/lib/gitlab/badge/shared/metadata.rb
+++ b/spec/lib/gitlab/badge/shared/metadata.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-shared_examples 'badge metadata' do
+RSpec.shared_examples 'badge metadata' do
describe '#to_html' do
let(:html) { Nokogiri::HTML.parse(metadata.to_html) }
let(:a_href) { html.at('a') }
diff --git a/spec/lib/gitlab/bare_repository_import/importer_spec.rb b/spec/lib/gitlab/bare_repository_import/importer_spec.rb
index 75a23d4f49e..e09430a858c 100644
--- a/spec/lib/gitlab/bare_repository_import/importer_spec.rb
+++ b/spec/lib/gitlab/bare_repository_import/importer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::BareRepositoryImport::Importer, :seed_helper do
+RSpec.describe Gitlab::BareRepositoryImport::Importer, :seed_helper do
let!(:admin) { create(:admin) }
let!(:base_dir) { Dir.mktmpdir + '/' }
let(:bare_repository) { Gitlab::BareRepositoryImport::Repository.new(base_dir, File.join(base_dir, "#{project_path}.git")) }
diff --git a/spec/lib/gitlab/bare_repository_import/repository_spec.rb b/spec/lib/gitlab/bare_repository_import/repository_spec.rb
index d2ecb1869fc..bf115046744 100644
--- a/spec/lib/gitlab/bare_repository_import/repository_spec.rb
+++ b/spec/lib/gitlab/bare_repository_import/repository_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ::Gitlab::BareRepositoryImport::Repository do
+RSpec.describe ::Gitlab::BareRepositoryImport::Repository do
context 'legacy storage' do
subject { described_class.new('/full/path/', '/full/path/to/repo.git') }
diff --git a/spec/lib/gitlab/batch_pop_queueing_spec.rb b/spec/lib/gitlab/batch_pop_queueing_spec.rb
index 28984d52024..41efc5417e4 100644
--- a/spec/lib/gitlab/batch_pop_queueing_spec.rb
+++ b/spec/lib/gitlab/batch_pop_queueing_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::BatchPopQueueing do
+RSpec.describe Gitlab::BatchPopQueueing do
include ExclusiveLeaseHelpers
using RSpec::Parameterized::TableSyntax
diff --git a/spec/lib/gitlab/batch_worker_context_spec.rb b/spec/lib/gitlab/batch_worker_context_spec.rb
index 0ba30287ae5..31641f7449e 100644
--- a/spec/lib/gitlab/batch_worker_context_spec.rb
+++ b/spec/lib/gitlab/batch_worker_context_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::BatchWorkerContext do
+RSpec.describe Gitlab::BatchWorkerContext do
subject(:batch_context) do
described_class.new(
%w(hello world),
diff --git a/spec/lib/gitlab/bitbucket_import/importer_spec.rb b/spec/lib/gitlab/bitbucket_import/importer_spec.rb
index 137d0fd4f9e..08b7bafddf0 100644
--- a/spec/lib/gitlab/bitbucket_import/importer_spec.rb
+++ b/spec/lib/gitlab/bitbucket_import/importer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::BitbucketImport::Importer do
+RSpec.describe Gitlab::BitbucketImport::Importer do
include ImportSpecHelper
before do
@@ -226,8 +226,8 @@ describe Gitlab::BitbucketImport::Importer do
it 'counts imported pull requests' do
expect(Gitlab::Metrics).to receive(:counter).with(
- :bitbucket_importer_imported_pull_requests,
- 'The number of imported Bitbucket pull requests'
+ :bitbucket_importer_imported_merge_requests_total,
+ 'The number of imported merge (pull) requests'
)
expect(counter).to receive(:increment)
@@ -369,8 +369,8 @@ describe Gitlab::BitbucketImport::Importer do
it 'counts imported issues' do
expect(Gitlab::Metrics).to receive(:counter).with(
- :bitbucket_importer_imported_issues,
- 'The number of imported Bitbucket issues'
+ :bitbucket_importer_imported_issues_total,
+ 'The number of imported issues'
)
expect(counter).to receive(:increment)
@@ -389,23 +389,27 @@ describe Gitlab::BitbucketImport::Importer do
allow(subject).to receive(:import_issues)
allow(subject).to receive(:import_pull_requests)
- allow(Gitlab::Metrics).to receive(:counter) { counter }
- allow(Gitlab::Metrics).to receive(:histogram) { histogram }
+ allow(Gitlab::Metrics).to receive(:counter).and_return(counter)
+ allow(Gitlab::Metrics).to receive(:histogram).and_return(histogram)
+ allow(histogram).to receive(:observe)
+ allow(counter).to receive(:increment)
end
it 'counts and measures duration of imported projects' do
expect(Gitlab::Metrics).to receive(:counter).with(
- :bitbucket_importer_imported_projects,
- 'The number of imported Bitbucket projects'
+ :bitbucket_importer_imported_projects_total,
+ 'The number of imported projects'
)
expect(Gitlab::Metrics).to receive(:histogram).with(
:bitbucket_importer_total_duration_seconds,
- 'Total time spent importing Bitbucket projects, in seconds'
+ 'Total time spent importing projects, in seconds',
+ {},
+ Gitlab::Import::Metrics::IMPORT_DURATION_BUCKETS
)
expect(counter).to receive(:increment)
- expect(histogram).to receive(:observe).with({ importer: described_class::IMPORTER }, anything)
+ expect(histogram).to receive(:observe).with({ importer: :bitbucket_importer }, anything)
subject.execute
end
diff --git a/spec/lib/gitlab/bitbucket_import/project_creator_spec.rb b/spec/lib/gitlab/bitbucket_import/project_creator_spec.rb
index 0dd8547a925..236e04a041b 100644
--- a/spec/lib/gitlab/bitbucket_import/project_creator_spec.rb
+++ b/spec/lib/gitlab/bitbucket_import/project_creator_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::BitbucketImport::ProjectCreator do
+RSpec.describe Gitlab::BitbucketImport::ProjectCreator do
let(:user) { create(:user) }
let(:repo) do
diff --git a/spec/lib/gitlab/bitbucket_import/wiki_formatter_spec.rb b/spec/lib/gitlab/bitbucket_import/wiki_formatter_spec.rb
index 7b5c7847f2d..4c285d31979 100644
--- a/spec/lib/gitlab/bitbucket_import/wiki_formatter_spec.rb
+++ b/spec/lib/gitlab/bitbucket_import/wiki_formatter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::BitbucketImport::WikiFormatter do
+RSpec.describe Gitlab::BitbucketImport::WikiFormatter do
let(:project) do
create(:project,
namespace: create(:namespace, path: 'gitlabhq'),
diff --git a/spec/lib/gitlab/bitbucket_server_import/importer_spec.rb b/spec/lib/gitlab/bitbucket_server_import/importer_spec.rb
index cf39d2cb753..5eb27c51f9e 100644
--- a/spec/lib/gitlab/bitbucket_server_import/importer_spec.rb
+++ b/spec/lib/gitlab/bitbucket_server_import/importer_spec.rb
@@ -2,10 +2,13 @@
require 'spec_helper'
-describe Gitlab::BitbucketServerImport::Importer do
+RSpec.describe Gitlab::BitbucketServerImport::Importer do
include ImportSpecHelper
- let(:project) { create(:project, :repository, import_url: 'http://my-bitbucket') }
+ let(:import_url) { 'http://my-bitbucket' }
+ let(:user) { 'bitbucket' }
+ let(:password) { 'test' }
+ let(:project) { create(:project, :repository, import_url: import_url) }
let(:now) { Time.now.utc.change(usec: 0) }
let(:project_key) { 'TEST' }
let(:repo_slug) { 'rouge' }
@@ -16,7 +19,7 @@ describe Gitlab::BitbucketServerImport::Importer do
before do
data = project.create_or_update_import_data(
data: { project_key: project_key, repo_slug: repo_slug },
- credentials: { base_uri: 'http://my-bitbucket', user: 'bitbucket', password: 'test' }
+ credentials: { base_uri: import_url, user: user, password: password }
)
data.save
project.save
@@ -125,6 +128,48 @@ describe Gitlab::BitbucketServerImport::Importer do
expect(note.updated_at).to eq(@pr_note.created_at)
end
+ context 'metrics' do
+ let(:histogram) { double(:histogram) }
+ let(:counter) { double('counter', increment: true) }
+
+ before do
+ allow(Gitlab::Metrics).to receive(:counter) { counter }
+ allow(Gitlab::Metrics).to receive(:histogram) { histogram }
+ allow(subject.client).to receive(:activities).and_return([@merge_event])
+ end
+
+ it 'counts and measures duration of imported projects' do
+ expect(Gitlab::Metrics).to receive(:counter).with(
+ :bitbucket_server_importer_imported_projects_total,
+ 'The number of imported projects'
+ )
+
+ expect(Gitlab::Metrics).to receive(:histogram).with(
+ :bitbucket_server_importer_total_duration_seconds,
+ 'Total time spent importing projects, in seconds',
+ {},
+ Gitlab::Import::Metrics::IMPORT_DURATION_BUCKETS
+ )
+
+ expect(counter).to receive(:increment)
+ expect(histogram).to receive(:observe).with({ importer: :bitbucket_server_importer }, anything)
+
+ subject.execute
+ end
+
+ it 'counts imported pull requests' do
+ expect(Gitlab::Metrics).to receive(:counter).with(
+ :bitbucket_server_importer_imported_merge_requests_total,
+ 'The number of imported merge (pull) requests'
+ )
+
+ expect(counter).to receive(:increment)
+ allow(histogram).to receive(:observe).with({ importer: :bitbucket_server_importer }, anything)
+
+ subject.execute
+ end
+ end
+
it 'imports threaded discussions' do
reply = instance_double(
BitbucketServer::Representation::PullRequestComment,
diff --git a/spec/lib/gitlab/blame_spec.rb b/spec/lib/gitlab/blame_spec.rb
index e1afd5b25bb..e22399723ac 100644
--- a/spec/lib/gitlab/blame_spec.rb
+++ b/spec/lib/gitlab/blame_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Blame do
+RSpec.describe Gitlab::Blame do
let(:project) { create(:project, :repository) }
let(:path) { 'files/ruby/popen.rb' }
let(:commit) { project.commit('master') }
diff --git a/spec/lib/gitlab/blob_helper_spec.rb b/spec/lib/gitlab/blob_helper_spec.rb
index e057385b35f..65fa5bf0120 100644
--- a/spec/lib/gitlab/blob_helper_spec.rb
+++ b/spec/lib/gitlab/blob_helper_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::BlobHelper do
+RSpec.describe Gitlab::BlobHelper do
include FakeBlobHelpers
let(:project) { create(:project) }
diff --git a/spec/lib/gitlab/branch_push_merge_commit_analyzer_spec.rb b/spec/lib/gitlab/branch_push_merge_commit_analyzer_spec.rb
index a27f14cd621..f09f38a6127 100644
--- a/spec/lib/gitlab/branch_push_merge_commit_analyzer_spec.rb
+++ b/spec/lib/gitlab/branch_push_merge_commit_analyzer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::BranchPushMergeCommitAnalyzer do
+RSpec.describe Gitlab::BranchPushMergeCommitAnalyzer do
let(:project) { create(:project, :repository) }
let(:oldrev) { 'merge-commit-analyze-before' }
let(:newrev) { 'merge-commit-analyze-after' }
diff --git a/spec/lib/gitlab/build_access_spec.rb b/spec/lib/gitlab/build_access_spec.rb
index b7af8ace5b5..c6248f94772 100644
--- a/spec/lib/gitlab/build_access_spec.rb
+++ b/spec/lib/gitlab/build_access_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::BuildAccess do
+RSpec.describe Gitlab::BuildAccess do
let(:user) { create(:user) }
let(:project) { create(:project) }
diff --git a/spec/lib/gitlab/cache/ci/project_pipeline_status_spec.rb b/spec/lib/gitlab/cache/ci/project_pipeline_status_spec.rb
index fc9266f75fb..8d625cab1d8 100644
--- a/spec/lib/gitlab/cache/ci/project_pipeline_status_spec.rb
+++ b/spec/lib/gitlab/cache/ci/project_pipeline_status_spec.rb
@@ -2,8 +2,8 @@
require 'spec_helper'
-describe Gitlab::Cache::Ci::ProjectPipelineStatus, :clean_gitlab_redis_cache do
- let!(:project) { create(:project, :repository) }
+RSpec.describe Gitlab::Cache::Ci::ProjectPipelineStatus, :clean_gitlab_redis_cache do
+ let_it_be(:project) { create(:project, :repository) }
let(:pipeline_status) { described_class.new(project) }
let(:cache_key) { pipeline_status.cache_key }
@@ -77,6 +77,62 @@ describe Gitlab::Cache::Ci::ProjectPipelineStatus, :clean_gitlab_redis_cache do
end
describe '#load_status' do
+ describe 'gitaly call counts', :request_store do
+ context 'not cached' do
+ before do
+ expect(pipeline_status).not_to be_has_cache
+ end
+
+ context 'ci_pipeline_status_omit_commit_sha_in_cache_key is enabled' do
+ before do
+ stub_feature_flags(ci_pipeline_status_omit_commit_sha_in_cache_key: project)
+ end
+
+ it 'makes a Gitaly call' do
+ expect { pipeline_status.load_status }.to change { Gitlab::GitalyClient.get_request_count }.by(1)
+ end
+ end
+
+ context 'ci_pipeline_status_omit_commit_sha_in_cache_key is disabled' do
+ before do
+ stub_feature_flags(ci_pipeline_status_omit_commit_sha_in_cache_key: false)
+ end
+
+ it 'makes a Gitaly calls' do
+ expect { pipeline_status.load_status }.to change { Gitlab::GitalyClient.get_request_count }.by(1)
+ end
+ end
+ end
+
+ context 'cached' do
+ before do
+ described_class.load_in_batch_for_projects([project])
+
+ expect(pipeline_status).to be_has_cache
+ end
+
+ context 'ci_pipeline_status_omit_commit_sha_in_cache_key is enabled' do
+ before do
+ stub_feature_flags(ci_pipeline_status_omit_commit_sha_in_cache_key: project)
+ end
+
+ it 'makes no Gitaly calls' do
+ expect { pipeline_status.load_status }.to change { Gitlab::GitalyClient.get_request_count }.by(0)
+ end
+ end
+
+ context 'ci_pipeline_status_omit_commit_sha_in_cache_key is disabled' do
+ before do
+ stub_feature_flags(ci_pipeline_status_omit_commit_sha_in_cache_key: false)
+ end
+
+ it 'makes a Gitaly calls' do
+ expect { pipeline_status.load_status }.to change { Gitlab::GitalyClient.get_request_count }.by(1)
+ end
+ end
+ end
+ end
+
it 'loads the status from the cache when there is one' do
expect(pipeline_status).to receive(:has_cache?).and_return(true)
expect(pipeline_status).to receive(:load_from_cache)
diff --git a/spec/lib/gitlab/cache/import/caching_spec.rb b/spec/lib/gitlab/cache/import/caching_spec.rb
index 7b4308d32ae..d6911dad9d4 100644
--- a/spec/lib/gitlab/cache/import/caching_spec.rb
+++ b/spec/lib/gitlab/cache/import/caching_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Cache::Import::Caching, :clean_gitlab_redis_cache do
+RSpec.describe Gitlab::Cache::Import::Caching, :clean_gitlab_redis_cache do
describe '.read' do
it 'reads a value from the cache' do
described_class.write('foo', 'bar')
diff --git a/spec/lib/gitlab/cache/request_cache_spec.rb b/spec/lib/gitlab/cache/request_cache_spec.rb
index 70a7f090d0a..57aee525ddd 100644
--- a/spec/lib/gitlab/cache/request_cache_spec.rb
+++ b/spec/lib/gitlab/cache/request_cache_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Cache::RequestCache do
+RSpec.describe Gitlab::Cache::RequestCache do
let(:klass) do
Class.new do
extend Gitlab::Cache::RequestCache
diff --git a/spec/lib/gitlab/changes_list_spec.rb b/spec/lib/gitlab/changes_list_spec.rb
index 911450f3a8b..8292764f561 100644
--- a/spec/lib/gitlab/changes_list_spec.rb
+++ b/spec/lib/gitlab/changes_list_spec.rb
@@ -2,7 +2,7 @@
require "spec_helper"
-describe Gitlab::ChangesList do
+RSpec.describe Gitlab::ChangesList do
let(:valid_changes_string) { "\n000000 570e7b2 refs/heads/my_branch\nd14d6c 6fd24d refs/heads/master" }
let(:invalid_changes) { 1 }
diff --git a/spec/lib/gitlab/chat/command_spec.rb b/spec/lib/gitlab/chat/command_spec.rb
index f7f344bf786..89c693daaa0 100644
--- a/spec/lib/gitlab/chat/command_spec.rb
+++ b/spec/lib/gitlab/chat/command_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Chat::Command do
+RSpec.describe Gitlab::Chat::Command do
let(:chat_name) { create(:chat_name) }
let(:command) do
diff --git a/spec/lib/gitlab/chat/output_spec.rb b/spec/lib/gitlab/chat/output_spec.rb
index b179f9e9d0a..38e17c39fad 100644
--- a/spec/lib/gitlab/chat/output_spec.rb
+++ b/spec/lib/gitlab/chat/output_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Chat::Output do
+RSpec.describe Gitlab::Chat::Output do
let(:build) do
create(:ci_build, pipeline: create(:ci_pipeline, source: :chat))
end
diff --git a/spec/lib/gitlab/chat/responder/base_spec.rb b/spec/lib/gitlab/chat/responder/base_spec.rb
index 7fa9bad9d38..667228cbab4 100644
--- a/spec/lib/gitlab/chat/responder/base_spec.rb
+++ b/spec/lib/gitlab/chat/responder/base_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Chat::Responder::Base do
+RSpec.describe Gitlab::Chat::Responder::Base do
let(:project) { double(:project) }
let(:pipeline) { double(:pipeline, project: project) }
let(:build) { double(:build, pipeline: pipeline) }
diff --git a/spec/lib/gitlab/chat/responder/mattermost_spec.rb b/spec/lib/gitlab/chat/responder/mattermost_spec.rb
index f3480dfef06..ca5f83d760a 100644
--- a/spec/lib/gitlab/chat/responder/mattermost_spec.rb
+++ b/spec/lib/gitlab/chat/responder/mattermost_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Chat::Responder::Mattermost do
+RSpec.describe Gitlab::Chat::Responder::Mattermost do
let(:chat_name) { create(:chat_name, chat_id: 'U123') }
let(:pipeline) do
diff --git a/spec/lib/gitlab/chat/responder/slack_spec.rb b/spec/lib/gitlab/chat/responder/slack_spec.rb
index a1553232b32..4801a1c6b66 100644
--- a/spec/lib/gitlab/chat/responder/slack_spec.rb
+++ b/spec/lib/gitlab/chat/responder/slack_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Chat::Responder::Slack do
+RSpec.describe Gitlab::Chat::Responder::Slack do
let(:chat_name) { create(:chat_name, chat_id: 'U123') }
let(:pipeline) do
diff --git a/spec/lib/gitlab/chat/responder_spec.rb b/spec/lib/gitlab/chat/responder_spec.rb
index 9893689cba9..6603dbe8d52 100644
--- a/spec/lib/gitlab/chat/responder_spec.rb
+++ b/spec/lib/gitlab/chat/responder_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Chat::Responder do
+RSpec.describe Gitlab::Chat::Responder do
describe '.responder_for' do
context 'using a regular build' do
it 'returns nil' do
diff --git a/spec/lib/gitlab/chat_name_token_spec.rb b/spec/lib/gitlab/chat_name_token_spec.rb
index b2d4a466021..906c02d54db 100644
--- a/spec/lib/gitlab/chat_name_token_spec.rb
+++ b/spec/lib/gitlab/chat_name_token_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::ChatNameToken do
+RSpec.describe Gitlab::ChatNameToken do
context 'when using unknown token' do
let(:token) { }
diff --git a/spec/lib/gitlab/chat_spec.rb b/spec/lib/gitlab/chat_spec.rb
index be606fe6db1..a9df35ace98 100644
--- a/spec/lib/gitlab/chat_spec.rb
+++ b/spec/lib/gitlab/chat_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Chat, :use_clean_rails_memory_store_caching do
+RSpec.describe Gitlab::Chat, :use_clean_rails_memory_store_caching do
describe '.available?' do
it 'returns true when the chatops feature is available' do
stub_feature_flags(chatops: true)
diff --git a/spec/lib/gitlab/checks/branch_check_spec.rb b/spec/lib/gitlab/checks/branch_check_spec.rb
index fd7eaa1603f..92452727017 100644
--- a/spec/lib/gitlab/checks/branch_check_spec.rb
+++ b/spec/lib/gitlab/checks/branch_check_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Checks::BranchCheck do
+RSpec.describe Gitlab::Checks::BranchCheck do
include_context 'change access checks context'
describe '#validate!' do
diff --git a/spec/lib/gitlab/checks/change_access_spec.rb b/spec/lib/gitlab/checks/change_access_spec.rb
index dfc8c59fd74..87936d19239 100644
--- a/spec/lib/gitlab/checks/change_access_spec.rb
+++ b/spec/lib/gitlab/checks/change_access_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Checks::ChangeAccess do
+RSpec.describe Gitlab::Checks::ChangeAccess do
describe '#exec' do
include_context 'change access checks context'
diff --git a/spec/lib/gitlab/checks/diff_check_spec.rb b/spec/lib/gitlab/checks/diff_check_spec.rb
index 467b4ed3a21..2cca0aed9c6 100644
--- a/spec/lib/gitlab/checks/diff_check_spec.rb
+++ b/spec/lib/gitlab/checks/diff_check_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Checks::DiffCheck do
+RSpec.describe Gitlab::Checks::DiffCheck do
include_context 'change access checks context'
describe '#validate!' do
diff --git a/spec/lib/gitlab/checks/force_push_spec.rb b/spec/lib/gitlab/checks/force_push_spec.rb
index 334dd8635a3..49e02fe5cec 100644
--- a/spec/lib/gitlab/checks/force_push_spec.rb
+++ b/spec/lib/gitlab/checks/force_push_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Checks::ForcePush do
+RSpec.describe Gitlab::Checks::ForcePush do
let_it_be(:project) { create(:project, :repository) }
describe '.force_push?' do
diff --git a/spec/lib/gitlab/checks/lfs_check_spec.rb b/spec/lib/gitlab/checks/lfs_check_spec.rb
index c86481d1abe..713858e0e35 100644
--- a/spec/lib/gitlab/checks/lfs_check_spec.rb
+++ b/spec/lib/gitlab/checks/lfs_check_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Checks::LfsCheck do
+RSpec.describe Gitlab::Checks::LfsCheck do
include_context 'change access checks context'
let(:blob_object) { project.repository.blob_at_branch('lfs', 'files/lfs/lfs_object.iso') }
diff --git a/spec/lib/gitlab/checks/lfs_integrity_spec.rb b/spec/lib/gitlab/checks/lfs_integrity_spec.rb
index 505f117034e..8fec702790c 100644
--- a/spec/lib/gitlab/checks/lfs_integrity_spec.rb
+++ b/spec/lib/gitlab/checks/lfs_integrity_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Checks::LfsIntegrity do
+RSpec.describe Gitlab::Checks::LfsIntegrity do
include ProjectForksHelper
let!(:time_left) { 50 }
diff --git a/spec/lib/gitlab/checks/project_created_spec.rb b/spec/lib/gitlab/checks/project_created_spec.rb
index bbc97155374..f099f19b061 100644
--- a/spec/lib/gitlab/checks/project_created_spec.rb
+++ b/spec/lib/gitlab/checks/project_created_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Checks::ProjectCreated, :clean_gitlab_redis_shared_state do
+RSpec.describe Gitlab::Checks::ProjectCreated, :clean_gitlab_redis_shared_state do
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project, :repository, namespace: user.namespace) }
let(:protocol) { 'http' }
diff --git a/spec/lib/gitlab/checks/project_moved_spec.rb b/spec/lib/gitlab/checks/project_moved_spec.rb
index 1d1d6211088..e15fa90443b 100644
--- a/spec/lib/gitlab/checks/project_moved_spec.rb
+++ b/spec/lib/gitlab/checks/project_moved_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Checks::ProjectMoved, :clean_gitlab_redis_shared_state do
+RSpec.describe Gitlab::Checks::ProjectMoved, :clean_gitlab_redis_shared_state do
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project, :repository, :wiki_repo, namespace: user.namespace) }
let(:repository) { project.repository }
diff --git a/spec/lib/gitlab/checks/push_check_spec.rb b/spec/lib/gitlab/checks/push_check_spec.rb
index 857d71732fe..45ab13cf0cf 100644
--- a/spec/lib/gitlab/checks/push_check_spec.rb
+++ b/spec/lib/gitlab/checks/push_check_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Checks::PushCheck do
+RSpec.describe Gitlab::Checks::PushCheck do
include_context 'change access checks context'
describe '#validate!' do
diff --git a/spec/lib/gitlab/checks/push_file_count_check_spec.rb b/spec/lib/gitlab/checks/push_file_count_check_spec.rb
index e05102a9ce8..e05070e8f35 100644
--- a/spec/lib/gitlab/checks/push_file_count_check_spec.rb
+++ b/spec/lib/gitlab/checks/push_file_count_check_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Checks::PushFileCountCheck do
+RSpec.describe Gitlab::Checks::PushFileCountCheck do
let(:snippet) { create(:personal_snippet, :repository) }
let(:changes) { { oldrev: oldrev, newrev: newrev, ref: ref } }
let(:timeout) { Gitlab::GitAccess::INTERNAL_TIMEOUT }
diff --git a/spec/lib/gitlab/checks/snippet_check_spec.rb b/spec/lib/gitlab/checks/snippet_check_spec.rb
index 3eee5ccfc0a..2c027486bc9 100644
--- a/spec/lib/gitlab/checks/snippet_check_spec.rb
+++ b/spec/lib/gitlab/checks/snippet_check_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Checks::SnippetCheck do
+RSpec.describe Gitlab::Checks::SnippetCheck do
include_context 'change access checks context'
let(:snippet) { create(:personal_snippet, :repository) }
diff --git a/spec/lib/gitlab/checks/tag_check_spec.rb b/spec/lib/gitlab/checks/tag_check_spec.rb
index 0c94171646e..e2e7d9c9648 100644
--- a/spec/lib/gitlab/checks/tag_check_spec.rb
+++ b/spec/lib/gitlab/checks/tag_check_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Checks::TagCheck do
+RSpec.describe Gitlab::Checks::TagCheck do
include_context 'change access checks context'
describe '#validate!' do
diff --git a/spec/lib/gitlab/checks/timed_logger_spec.rb b/spec/lib/gitlab/checks/timed_logger_spec.rb
index 0ed3940c038..6c488212eca 100644
--- a/spec/lib/gitlab/checks/timed_logger_spec.rb
+++ b/spec/lib/gitlab/checks/timed_logger_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Checks::TimedLogger do
+RSpec.describe Gitlab::Checks::TimedLogger do
let!(:timeout) { 50.seconds }
let!(:start) { Time.now }
let!(:ref) { "bar" }
diff --git a/spec/lib/gitlab/ci/ansi2html_spec.rb b/spec/lib/gitlab/ci/ansi2html_spec.rb
index ee789995bc2..f29a39e4e66 100644
--- a/spec/lib/gitlab/ci/ansi2html_spec.rb
+++ b/spec/lib/gitlab/ci/ansi2html_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Ansi2html do
+RSpec.describe Gitlab::Ci::Ansi2html do
subject { described_class }
it "prints non-ansi as-is" do
diff --git a/spec/lib/gitlab/ci/ansi2json/line_spec.rb b/spec/lib/gitlab/ci/ansi2json/line_spec.rb
index 4b5c3f9489e..8b1cd812a70 100644
--- a/spec/lib/gitlab/ci/ansi2json/line_spec.rb
+++ b/spec/lib/gitlab/ci/ansi2json/line_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Ansi2json::Line do
+RSpec.describe Gitlab::Ci::Ansi2json::Line do
let(:offset) { 0 }
let(:style) { Gitlab::Ci::Ansi2json::Style.new }
diff --git a/spec/lib/gitlab/ci/ansi2json/parser_spec.rb b/spec/lib/gitlab/ci/ansi2json/parser_spec.rb
index e161e74c1ff..cf93ebe0721 100644
--- a/spec/lib/gitlab/ci/ansi2json/parser_spec.rb
+++ b/spec/lib/gitlab/ci/ansi2json/parser_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
# The rest of the specs for this class are covered in style_spec.rb
-describe Gitlab::Ci::Ansi2json::Parser do
+RSpec.describe Gitlab::Ci::Ansi2json::Parser do
subject { described_class }
describe 'bold?' do
diff --git a/spec/lib/gitlab/ci/ansi2json/result_spec.rb b/spec/lib/gitlab/ci/ansi2json/result_spec.rb
index 5b7b5481400..31c0da95f0a 100644
--- a/spec/lib/gitlab/ci/ansi2json/result_spec.rb
+++ b/spec/lib/gitlab/ci/ansi2json/result_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Ansi2json::Result do
+RSpec.describe Gitlab::Ci::Ansi2json::Result do
let(:stream) { StringIO.new('hello') }
let(:state) { Gitlab::Ci::Ansi2json::State.new(nil, stream.size) }
let(:offset) { 0 }
diff --git a/spec/lib/gitlab/ci/ansi2json/style_spec.rb b/spec/lib/gitlab/ci/ansi2json/style_spec.rb
index ad05aa03e83..d27a642ecf3 100644
--- a/spec/lib/gitlab/ci/ansi2json/style_spec.rb
+++ b/spec/lib/gitlab/ci/ansi2json/style_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Ansi2json::Style do
+RSpec.describe Gitlab::Ci::Ansi2json::Style do
describe '#set?' do
subject { described_class.new(params).set? }
diff --git a/spec/lib/gitlab/ci/ansi2json_spec.rb b/spec/lib/gitlab/ci/ansi2json_spec.rb
index 124379fa321..cb6949fddc2 100644
--- a/spec/lib/gitlab/ci/ansi2json_spec.rb
+++ b/spec/lib/gitlab/ci/ansi2json_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Ansi2json do
+RSpec.describe Gitlab::Ci::Ansi2json do
subject { described_class }
describe 'lines' do
diff --git a/spec/lib/gitlab/ci/artifact_file_reader_spec.rb b/spec/lib/gitlab/ci/artifact_file_reader_spec.rb
index 04017b9ae3e..e982f0eb015 100644
--- a/spec/lib/gitlab/ci/artifact_file_reader_spec.rb
+++ b/spec/lib/gitlab/ci/artifact_file_reader_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::ArtifactFileReader do
+RSpec.describe Gitlab::Ci::ArtifactFileReader do
let(:job) { create(:ci_build) }
let(:path) { 'generated.yml' } # included in the ci_build_artifacts.zip
diff --git a/spec/lib/gitlab/ci/build/artifacts/adapters/gzip_stream_spec.rb b/spec/lib/gitlab/ci/build/artifacts/adapters/gzip_stream_spec.rb
index cec3e70bb9f..98f909a0ed0 100644
--- a/spec/lib/gitlab/ci/build/artifacts/adapters/gzip_stream_spec.rb
+++ b/spec/lib/gitlab/ci/build/artifacts/adapters/gzip_stream_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Build::Artifacts::Adapters::GzipStream do
+RSpec.describe Gitlab::Ci::Build::Artifacts::Adapters::GzipStream do
describe '#initialize' do
context 'when stream is passed' do
let(:stream) { File.open(expand_fixture_path('junit/junit.xml.gz'), 'rb') }
diff --git a/spec/lib/gitlab/ci/build/artifacts/adapters/raw_stream_spec.rb b/spec/lib/gitlab/ci/build/artifacts/adapters/raw_stream_spec.rb
index 66a234232e1..badff972e60 100644
--- a/spec/lib/gitlab/ci/build/artifacts/adapters/raw_stream_spec.rb
+++ b/spec/lib/gitlab/ci/build/artifacts/adapters/raw_stream_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Build::Artifacts::Adapters::RawStream do
+RSpec.describe Gitlab::Ci::Build::Artifacts::Adapters::RawStream do
describe '#initialize' do
context 'when stream is passed' do
let(:stream) { File.open(expand_fixture_path('junit/junit.xml'), 'rb') }
diff --git a/spec/lib/gitlab/ci/build/artifacts/metadata/entry_spec.rb b/spec/lib/gitlab/ci/build/artifacts/metadata/entry_spec.rb
index 7a413a7aeac..c8ace28108b 100644
--- a/spec/lib/gitlab/ci/build/artifacts/metadata/entry_spec.rb
+++ b/spec/lib/gitlab/ci/build/artifacts/metadata/entry_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Build::Artifacts::Metadata::Entry do
+RSpec.describe Gitlab::Ci::Build::Artifacts::Metadata::Entry do
let(:entries) do
{ 'path/' => {},
'path/dir_1/' => {},
diff --git a/spec/lib/gitlab/ci/build/artifacts/metadata_spec.rb b/spec/lib/gitlab/ci/build/artifacts/metadata_spec.rb
index bfa65c66b33..77b8aa1d591 100644
--- a/spec/lib/gitlab/ci/build/artifacts/metadata_spec.rb
+++ b/spec/lib/gitlab/ci/build/artifacts/metadata_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Build::Artifacts::Metadata do
+RSpec.describe Gitlab::Ci::Build::Artifacts::Metadata do
def metadata(path = '', **opts)
described_class.new(metadata_file_stream, path, **opts)
end
diff --git a/spec/lib/gitlab/ci/build/artifacts/path_spec.rb b/spec/lib/gitlab/ci/build/artifacts/path_spec.rb
index 7bbef0f5197..27b7dac2ae4 100644
--- a/spec/lib/gitlab/ci/build/artifacts/path_spec.rb
+++ b/spec/lib/gitlab/ci/build/artifacts/path_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Build::Artifacts::Path do
+RSpec.describe Gitlab::Ci::Build::Artifacts::Path do
describe '#valid?' do
context 'when path contains a zero character' do
it 'is not valid' do
diff --git a/spec/lib/gitlab/ci/build/context/build_spec.rb b/spec/lib/gitlab/ci/build/context/build_spec.rb
index 1b73b9a083d..61ca8e759b5 100644
--- a/spec/lib/gitlab/ci/build/context/build_spec.rb
+++ b/spec/lib/gitlab/ci/build/context/build_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Build::Context::Build do
+RSpec.describe Gitlab::Ci::Build::Context::Build do
let(:pipeline) { create(:ci_pipeline) }
let(:seed_attributes) { { 'name' => 'some-job' } }
diff --git a/spec/lib/gitlab/ci/build/context/global_spec.rb b/spec/lib/gitlab/ci/build/context/global_spec.rb
index 65cc41ed3f9..7394708f9b6 100644
--- a/spec/lib/gitlab/ci/build/context/global_spec.rb
+++ b/spec/lib/gitlab/ci/build/context/global_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Build::Context::Global do
+RSpec.describe Gitlab::Ci::Build::Context::Global do
let(:pipeline) { create(:ci_pipeline) }
let(:yaml_variables) { {} }
diff --git a/spec/lib/gitlab/ci/build/credentials/factory_spec.rb b/spec/lib/gitlab/ci/build/credentials/factory_spec.rb
index 159f89f4985..613d360f78d 100644
--- a/spec/lib/gitlab/ci/build/credentials/factory_spec.rb
+++ b/spec/lib/gitlab/ci/build/credentials/factory_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Build::Credentials::Factory do
+RSpec.describe Gitlab::Ci::Build::Credentials::Factory do
let(:build) { create(:ci_build, name: 'spinach', stage: 'test', stage_idx: 0) }
subject { described_class.new(build).create! }
diff --git a/spec/lib/gitlab/ci/build/credentials/registry_spec.rb b/spec/lib/gitlab/ci/build/credentials/registry_spec.rb
index 552580dcbbe..c0a76973f60 100644
--- a/spec/lib/gitlab/ci/build/credentials/registry_spec.rb
+++ b/spec/lib/gitlab/ci/build/credentials/registry_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Build::Credentials::Registry do
+RSpec.describe Gitlab::Ci::Build::Credentials::Registry do
let(:build) { create(:ci_build, name: 'spinach', stage: 'test', stage_idx: 0) }
let(:registry_url) { 'registry.example.com:5005' }
diff --git a/spec/lib/gitlab/ci/build/image_spec.rb b/spec/lib/gitlab/ci/build/image_spec.rb
index 4f7cfc9783a..71cd57d317c 100644
--- a/spec/lib/gitlab/ci/build/image_spec.rb
+++ b/spec/lib/gitlab/ci/build/image_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Build::Image do
+RSpec.describe Gitlab::Ci::Build::Image do
let(:job) { create(:ci_build, :no_options) }
describe '#from_image' do
diff --git a/spec/lib/gitlab/ci/build/policy/changes_spec.rb b/spec/lib/gitlab/ci/build/policy/changes_spec.rb
index 07f52605929..016730e01cd 100644
--- a/spec/lib/gitlab/ci/build/policy/changes_spec.rb
+++ b/spec/lib/gitlab/ci/build/policy/changes_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Build::Policy::Changes do
+RSpec.describe Gitlab::Ci::Build::Policy::Changes do
let_it_be(:project) { create(:project) }
describe '#satisfied_by?' do
diff --git a/spec/lib/gitlab/ci/build/policy/kubernetes_spec.rb b/spec/lib/gitlab/ci/build/policy/kubernetes_spec.rb
index 6d96bdc30c7..81c0b76b453 100644
--- a/spec/lib/gitlab/ci/build/policy/kubernetes_spec.rb
+++ b/spec/lib/gitlab/ci/build/policy/kubernetes_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Build::Policy::Kubernetes do
+RSpec.describe Gitlab::Ci::Build::Policy::Kubernetes do
let(:pipeline) { create(:ci_pipeline, project: project) }
context 'when kubernetes service is active' do
diff --git a/spec/lib/gitlab/ci/build/policy/refs_spec.rb b/spec/lib/gitlab/ci/build/policy/refs_spec.rb
index c32fdc5c72e..7fd51102d71 100644
--- a/spec/lib/gitlab/ci/build/policy/refs_spec.rb
+++ b/spec/lib/gitlab/ci/build/policy/refs_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Build::Policy::Refs do
+RSpec.describe Gitlab::Ci::Build::Policy::Refs do
describe '#satisfied_by?' do
context 'when matching ref' do
let(:pipeline) { build_stubbed(:ci_pipeline, ref: 'master') }
diff --git a/spec/lib/gitlab/ci/build/policy/variables_spec.rb b/spec/lib/gitlab/ci/build/policy/variables_spec.rb
index 0e75726b81c..f692aa6146e 100644
--- a/spec/lib/gitlab/ci/build/policy/variables_spec.rb
+++ b/spec/lib/gitlab/ci/build/policy/variables_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Build::Policy::Variables do
+RSpec.describe Gitlab::Ci::Build::Policy::Variables do
let_it_be(:project) { create(:project) }
let(:pipeline) do
diff --git a/spec/lib/gitlab/ci/build/policy_spec.rb b/spec/lib/gitlab/ci/build/policy_spec.rb
index 80d7b2e9dc8..b85b093fd03 100644
--- a/spec/lib/gitlab/ci/build/policy_spec.rb
+++ b/spec/lib/gitlab/ci/build/policy_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Build::Policy do
+RSpec.describe Gitlab::Ci::Build::Policy do
let(:policy) { spy('policy specification') }
before do
diff --git a/spec/lib/gitlab/ci/build/port_spec.rb b/spec/lib/gitlab/ci/build/port_spec.rb
index 1413780dfa6..480418e0851 100644
--- a/spec/lib/gitlab/ci/build/port_spec.rb
+++ b/spec/lib/gitlab/ci/build/port_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Build::Port do
+RSpec.describe Gitlab::Ci::Build::Port do
subject { described_class.new(port) }
context 'when port is defined as an integer' do
diff --git a/spec/lib/gitlab/ci/build/prerequisite/factory_spec.rb b/spec/lib/gitlab/ci/build/prerequisite/factory_spec.rb
index 5187f99a441..a38ade4bfa5 100644
--- a/spec/lib/gitlab/ci/build/prerequisite/factory_spec.rb
+++ b/spec/lib/gitlab/ci/build/prerequisite/factory_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Build::Prerequisite::Factory do
+RSpec.describe Gitlab::Ci::Build::Prerequisite::Factory do
let(:build) { create(:ci_build) }
describe '.for_build' do
diff --git a/spec/lib/gitlab/ci/build/prerequisite/kubernetes_namespace_spec.rb b/spec/lib/gitlab/ci/build/prerequisite/kubernetes_namespace_spec.rb
index 66240380edd..94c14cfa479 100644
--- a/spec/lib/gitlab/ci/build/prerequisite/kubernetes_namespace_spec.rb
+++ b/spec/lib/gitlab/ci/build/prerequisite/kubernetes_namespace_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Build::Prerequisite::KubernetesNamespace do
+RSpec.describe Gitlab::Ci::Build::Prerequisite::KubernetesNamespace do
describe '#unmet?' do
let(:build) { create(:ci_build) }
diff --git a/spec/lib/gitlab/ci/build/releaser_spec.rb b/spec/lib/gitlab/ci/build/releaser_spec.rb
index 2f7bca777dd..fa5e90674a6 100644
--- a/spec/lib/gitlab/ci/build/releaser_spec.rb
+++ b/spec/lib/gitlab/ci/build/releaser_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Build::Releaser do
+RSpec.describe Gitlab::Ci::Build::Releaser do
subject { described_class.new(config: config[:release]).script }
describe '#script' do
@@ -13,13 +13,15 @@ describe Gitlab::Ci::Build::Releaser do
name: 'Release $CI_COMMIT_SHA',
description: 'Created using the release-cli $EXTRA_DESCRIPTION',
tag_name: 'release-$CI_COMMIT_SHA',
- ref: '$CI_COMMIT_SHA'
+ ref: '$CI_COMMIT_SHA',
+ milestones: %w[m1 m2 m3],
+ released_at: '2020-07-15T08:00:00Z'
}
}
end
it 'generates the script' do
- expect(subject).to eq('release-cli create --name "Release $CI_COMMIT_SHA" --description "Created using the release-cli $EXTRA_DESCRIPTION" --tag-name "release-$CI_COMMIT_SHA" --ref "$CI_COMMIT_SHA"')
+ expect(subject).to eq(['release-cli create --name "Release $CI_COMMIT_SHA" --description "Created using the release-cli $EXTRA_DESCRIPTION" --tag-name "release-$CI_COMMIT_SHA" --ref "$CI_COMMIT_SHA" --released-at "2020-07-15T08:00:00Z" --milestone "m1" --milestone "m2" --milestone "m3"'])
end
end
@@ -27,10 +29,12 @@ describe Gitlab::Ci::Build::Releaser do
using RSpec::Parameterized::TableSyntax
where(:node_name, :node_value, :result) do
- 'name' | 'Release $CI_COMMIT_SHA' | 'release-cli create --name "Release $CI_COMMIT_SHA"'
- 'description' | 'Release-cli $EXTRA_DESCRIPTION' | 'release-cli create --description "Release-cli $EXTRA_DESCRIPTION"'
- 'tag_name' | 'release-$CI_COMMIT_SHA' | 'release-cli create --tag-name "release-$CI_COMMIT_SHA"'
- 'ref' | '$CI_COMMIT_SHA' | 'release-cli create --ref "$CI_COMMIT_SHA"'
+ :name | 'Release $CI_COMMIT_SHA' | 'release-cli create --name "Release $CI_COMMIT_SHA"'
+ :description | 'Release-cli $EXTRA_DESCRIPTION' | 'release-cli create --description "Release-cli $EXTRA_DESCRIPTION"'
+ :tag_name | 'release-$CI_COMMIT_SHA' | 'release-cli create --tag-name "release-$CI_COMMIT_SHA"'
+ :ref | '$CI_COMMIT_SHA' | 'release-cli create --ref "$CI_COMMIT_SHA"'
+ :milestones | %w[m1 m2 m3] | 'release-cli create --milestone "m1" --milestone "m2" --milestone "m3"'
+ :released_at | '2020-07-15T08:00:00Z' | 'release-cli create --released-at "2020-07-15T08:00:00Z"'
end
with_them do
@@ -43,7 +47,7 @@ describe Gitlab::Ci::Build::Releaser do
end
it 'generates the script' do
- expect(subject).to eq(result)
+ expect(subject).to eq([result])
end
end
end
diff --git a/spec/lib/gitlab/ci/build/rules/rule/clause/changes_spec.rb b/spec/lib/gitlab/ci/build/rules/rule/clause/changes_spec.rb
index 076de3646b0..cf52f601006 100644
--- a/spec/lib/gitlab/ci/build/rules/rule/clause/changes_spec.rb
+++ b/spec/lib/gitlab/ci/build/rules/rule/clause/changes_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Build::Rules::Rule::Clause::Changes do
+RSpec.describe Gitlab::Ci::Build::Rules::Rule::Clause::Changes do
describe '#satisfied_by?' do
it_behaves_like 'a glob matching rule' do
let(:pipeline) { build(:ci_pipeline) }
diff --git a/spec/lib/gitlab/ci/build/rules/rule/clause/exists_spec.rb b/spec/lib/gitlab/ci/build/rules/rule/clause/exists_spec.rb
index 10843a1435a..86dd5569a96 100644
--- a/spec/lib/gitlab/ci/build/rules/rule/clause/exists_spec.rb
+++ b/spec/lib/gitlab/ci/build/rules/rule/clause/exists_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Build::Rules::Rule::Clause::Exists do
+RSpec.describe Gitlab::Ci::Build::Rules::Rule::Clause::Exists do
describe '#satisfied_by?' do
let(:pipeline) { build(:ci_pipeline, project: project, sha: project.repository.head_commit.sha) }
diff --git a/spec/lib/gitlab/ci/build/rules/rule_spec.rb b/spec/lib/gitlab/ci/build/rules/rule_spec.rb
index 04cdaa9d0ae..5694cd5d0a0 100644
--- a/spec/lib/gitlab/ci/build/rules/rule_spec.rb
+++ b/spec/lib/gitlab/ci/build/rules/rule_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Build::Rules::Rule do
+RSpec.describe Gitlab::Ci::Build::Rules::Rule do
let(:seed) do
double('build seed',
to_resource: ci_build,
diff --git a/spec/lib/gitlab/ci/build/rules_spec.rb b/spec/lib/gitlab/ci/build/rules_spec.rb
index 31a9fa055e1..cbeae33fbcf 100644
--- a/spec/lib/gitlab/ci/build/rules_spec.rb
+++ b/spec/lib/gitlab/ci/build/rules_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Build::Rules do
+RSpec.describe Gitlab::Ci::Build::Rules do
let(:pipeline) { create(:ci_pipeline) }
let(:ci_build) { build(:ci_build, pipeline: pipeline) }
diff --git a/spec/lib/gitlab/ci/build/step_spec.rb b/spec/lib/gitlab/ci/build/step_spec.rb
index 1cebda2cc7e..4b8f68b9fa8 100644
--- a/spec/lib/gitlab/ci/build/step_spec.rb
+++ b/spec/lib/gitlab/ci/build/step_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Build::Step do
+RSpec.describe Gitlab::Ci::Build::Step do
describe '#from_commands' do
subject { described_class.from_commands(job) }
@@ -62,7 +62,7 @@ describe Gitlab::Ci::Build::Step do
let(:job) { create(:ci_build, :release_options) }
it 'returns the release-cli command line' do
- expect(subject.script).to eq("release-cli create --name \"Release $CI_COMMIT_SHA\" --description \"Created using the release-cli $EXTRA_DESCRIPTION\" --tag-name \"release-$CI_COMMIT_SHA\" --ref \"$CI_COMMIT_SHA\"")
+ expect(subject.script).to eq(["release-cli create --name \"Release $CI_COMMIT_SHA\" --description \"Created using the release-cli $EXTRA_DESCRIPTION\" --tag-name \"release-$CI_COMMIT_SHA\" --ref \"$CI_COMMIT_SHA\""])
end
end
diff --git a/spec/lib/gitlab/ci/charts_spec.rb b/spec/lib/gitlab/ci/charts_spec.rb
index cfb7a3f72fa..e00e5ed3920 100644
--- a/spec/lib/gitlab/ci/charts_spec.rb
+++ b/spec/lib/gitlab/ci/charts_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Charts do
+RSpec.describe Gitlab::Ci::Charts do
context "yearchart" do
let(:project) { create(:project) }
let(:chart) { Gitlab::Ci::Charts::YearChart.new(project) }
diff --git a/spec/lib/gitlab/ci/config/edge_stages_injector_spec.rb b/spec/lib/gitlab/ci/config/edge_stages_injector_spec.rb
index 042f9b591b6..52ea925c1c4 100644
--- a/spec/lib/gitlab/ci/config/edge_stages_injector_spec.rb
+++ b/spec/lib/gitlab/ci/config/edge_stages_injector_spec.rb
@@ -2,7 +2,7 @@
require 'fast_spec_helper'
-describe Gitlab::Ci::Config::EdgeStagesInjector do
+RSpec.describe Gitlab::Ci::Config::EdgeStagesInjector do
describe '#call' do
subject { described_class.new(config).to_hash }
diff --git a/spec/lib/gitlab/ci/config/entry/artifacts_spec.rb b/spec/lib/gitlab/ci/config/entry/artifacts_spec.rb
index 8cfd07df777..028dcd3e1e6 100644
--- a/spec/lib/gitlab/ci/config/entry/artifacts_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/artifacts_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Config::Entry::Artifacts do
+RSpec.describe Gitlab::Ci::Config::Entry::Artifacts do
let(:entry) { described_class.new(config) }
describe 'validation' do
diff --git a/spec/lib/gitlab/ci/config/entry/bridge_spec.rb b/spec/lib/gitlab/ci/config/entry/bridge_spec.rb
index d08ce30618d..f33176c3da3 100644
--- a/spec/lib/gitlab/ci/config/entry/bridge_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/bridge_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Config::Entry::Bridge do
+RSpec.describe Gitlab::Ci::Config::Entry::Bridge do
subject { described_class.new(config, name: :my_bridge) }
it_behaves_like 'with inheritable CI config' do
diff --git a/spec/lib/gitlab/ci/config/entry/cache_spec.rb b/spec/lib/gitlab/ci/config/entry/cache_spec.rb
index f7b14360af3..3501812b76e 100644
--- a/spec/lib/gitlab/ci/config/entry/cache_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/cache_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Config::Entry::Cache do
+RSpec.describe Gitlab::Ci::Config::Entry::Cache do
subject(:entry) { described_class.new(config) }
describe 'validations' do
diff --git a/spec/lib/gitlab/ci/config/entry/commands_spec.rb b/spec/lib/gitlab/ci/config/entry/commands_spec.rb
index 8e7f9ab9706..439799fe973 100644
--- a/spec/lib/gitlab/ci/config/entry/commands_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/commands_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Config::Entry::Commands do
+RSpec.describe Gitlab::Ci::Config::Entry::Commands do
let(:entry) { described_class.new(config) }
context 'when entry config value is an array of strings' do
diff --git a/spec/lib/gitlab/ci/config/entry/coverage_spec.rb b/spec/lib/gitlab/ci/config/entry/coverage_spec.rb
index 877e3ec6216..c4353e822ae 100644
--- a/spec/lib/gitlab/ci/config/entry/coverage_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/coverage_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Config::Entry::Coverage do
+RSpec.describe Gitlab::Ci::Config::Entry::Coverage do
let(:entry) { described_class.new(config) }
describe 'validations' do
diff --git a/spec/lib/gitlab/ci/config/entry/default_spec.rb b/spec/lib/gitlab/ci/config/entry/default_spec.rb
index 23c62bbf92a..6e46d02a96e 100644
--- a/spec/lib/gitlab/ci/config/entry/default_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/default_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Config::Entry::Default do
+RSpec.describe Gitlab::Ci::Config::Entry::Default do
let(:entry) { described_class.new(config) }
it_behaves_like 'with inheritable CI config' do
diff --git a/spec/lib/gitlab/ci/config/entry/environment_spec.rb b/spec/lib/gitlab/ci/config/entry/environment_spec.rb
index 8c21d5342cc..0c18a7fb71e 100644
--- a/spec/lib/gitlab/ci/config/entry/environment_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/environment_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Config::Entry::Environment do
+RSpec.describe Gitlab::Ci::Config::Entry::Environment do
let(:entry) { described_class.new(config) }
before do
@@ -102,6 +102,17 @@ describe Gitlab::Ci::Config::Entry::Environment do
end
end
+ context 'when prepare action is used' do
+ let(:config) do
+ { name: 'production',
+ action: 'prepare' }
+ end
+
+ it 'is valid' do
+ expect(entry).to be_valid
+ end
+ end
+
context 'when wrong action type is used' do
let(:config) do
{ name: 'production',
@@ -137,7 +148,7 @@ describe Gitlab::Ci::Config::Entry::Environment do
describe '#errors' do
it 'contains error about invalid action' do
expect(entry.errors)
- .to include 'environment action should be start or stop'
+ .to include 'environment action should be start, stop or prepare'
end
end
end
diff --git a/spec/lib/gitlab/ci/config/entry/files_spec.rb b/spec/lib/gitlab/ci/config/entry/files_spec.rb
index 2bebbd7b198..6a101d80c7d 100644
--- a/spec/lib/gitlab/ci/config/entry/files_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/files_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Config::Entry::Files do
+RSpec.describe Gitlab::Ci::Config::Entry::Files do
let(:entry) { described_class.new(config) }
describe 'validations' do
diff --git a/spec/lib/gitlab/ci/config/entry/hidden_spec.rb b/spec/lib/gitlab/ci/config/entry/hidden_spec.rb
index 40b73352676..090ef67f39d 100644
--- a/spec/lib/gitlab/ci/config/entry/hidden_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/hidden_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Config::Entry::Hidden do
+RSpec.describe Gitlab::Ci::Config::Entry::Hidden do
describe '.matching?' do
subject { described_class.matching?(name, {}) }
diff --git a/spec/lib/gitlab/ci/config/entry/image_spec.rb b/spec/lib/gitlab/ci/config/entry/image_spec.rb
index de3e887a6ed..c3d91057328 100644
--- a/spec/lib/gitlab/ci/config/entry/image_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/image_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Config::Entry::Image do
+RSpec.describe Gitlab::Ci::Config::Entry::Image do
let(:entry) { described_class.new(config) }
context 'when configuration is a string' do
diff --git a/spec/lib/gitlab/ci/config/entry/include_spec.rb b/spec/lib/gitlab/ci/config/entry/include_spec.rb
index bab11f26fa1..3e816f70c03 100644
--- a/spec/lib/gitlab/ci/config/entry/include_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/include_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ::Gitlab::Ci::Config::Entry::Include do
+RSpec.describe ::Gitlab::Ci::Config::Entry::Include do
subject(:include_entry) { described_class.new(config) }
describe 'validations' do
diff --git a/spec/lib/gitlab/ci/config/entry/inherit/default_spec.rb b/spec/lib/gitlab/ci/config/entry/inherit/default_spec.rb
index 073f93ce542..7cd9b0acb99 100644
--- a/spec/lib/gitlab/ci/config/entry/inherit/default_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/inherit/default_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ::Gitlab::Ci::Config::Entry::Inherit::Default do
+RSpec.describe ::Gitlab::Ci::Config::Entry::Inherit::Default do
using RSpec::Parameterized::TableSyntax
subject { described_class.new(config) }
diff --git a/spec/lib/gitlab/ci/config/entry/inherit/variables_spec.rb b/spec/lib/gitlab/ci/config/entry/inherit/variables_spec.rb
index 06deed11c15..b1a8fbcdbe0 100644
--- a/spec/lib/gitlab/ci/config/entry/inherit/variables_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/inherit/variables_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ::Gitlab::Ci::Config::Entry::Inherit::Variables do
+RSpec.describe ::Gitlab::Ci::Config::Entry::Inherit::Variables do
using RSpec::Parameterized::TableSyntax
subject { described_class.new(config) }
diff --git a/spec/lib/gitlab/ci/config/entry/job_spec.rb b/spec/lib/gitlab/ci/config/entry/job_spec.rb
index b6279485426..180c52ee1ab 100644
--- a/spec/lib/gitlab/ci/config/entry/job_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/job_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Config::Entry::Job do
+RSpec.describe Gitlab::Ci::Config::Entry::Job do
let(:entry) { described_class.new(config, name: :rspec) }
it_behaves_like 'with inheritable CI config' do
@@ -33,7 +33,7 @@ describe Gitlab::Ci::Config::Entry::Job do
inherit]
end
- it { is_expected.to match_array result }
+ it { is_expected.to include(*result) }
end
end
diff --git a/spec/lib/gitlab/ci/config/entry/jobs_spec.rb b/spec/lib/gitlab/ci/config/entry/jobs_spec.rb
index 203342ab620..8561bd330b7 100644
--- a/spec/lib/gitlab/ci/config/entry/jobs_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/jobs_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Config::Entry::Jobs do
+RSpec.describe Gitlab::Ci::Config::Entry::Jobs do
let(:entry) { described_class.new(config) }
let(:config) do
diff --git a/spec/lib/gitlab/ci/config/entry/key_spec.rb b/spec/lib/gitlab/ci/config/entry/key_spec.rb
index 327607e2266..4ee1ffb29ad 100644
--- a/spec/lib/gitlab/ci/config/entry/key_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/key_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Config::Entry::Key do
+RSpec.describe Gitlab::Ci::Config::Entry::Key do
let(:entry) { described_class.new(config) }
describe 'validations' do
diff --git a/spec/lib/gitlab/ci/config/entry/kubernetes_spec.rb b/spec/lib/gitlab/ci/config/entry/kubernetes_spec.rb
index 468e83ec506..53809d2d549 100644
--- a/spec/lib/gitlab/ci/config/entry/kubernetes_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/kubernetes_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Config::Entry::Kubernetes do
+RSpec.describe Gitlab::Ci::Config::Entry::Kubernetes do
subject { described_class.new(config) }
describe 'attributes' do
diff --git a/spec/lib/gitlab/ci/config/entry/need_spec.rb b/spec/lib/gitlab/ci/config/entry/need_spec.rb
index 92b71c5f6cc..5a826bf8282 100644
--- a/spec/lib/gitlab/ci/config/entry/need_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/need_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ::Gitlab::Ci::Config::Entry::Need do
+RSpec.describe ::Gitlab::Ci::Config::Entry::Need do
subject(:need) { described_class.new(config) }
shared_examples 'job type' do
diff --git a/spec/lib/gitlab/ci/config/entry/needs_spec.rb b/spec/lib/gitlab/ci/config/entry/needs_spec.rb
index b8b84b5efd2..f3b9d0c3c84 100644
--- a/spec/lib/gitlab/ci/config/entry/needs_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/needs_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ::Gitlab::Ci::Config::Entry::Needs do
+RSpec.describe ::Gitlab::Ci::Config::Entry::Needs do
subject(:needs) { described_class.new(config) }
before do
diff --git a/spec/lib/gitlab/ci/config/entry/paths_spec.rb b/spec/lib/gitlab/ci/config/entry/paths_spec.rb
index 221d5ae5863..dd4e7befc46 100644
--- a/spec/lib/gitlab/ci/config/entry/paths_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/paths_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Config::Entry::Paths do
+RSpec.describe Gitlab::Ci::Config::Entry::Paths do
let(:entry) { described_class.new(config) }
describe 'validations' do
diff --git a/spec/lib/gitlab/ci/config/entry/policy_spec.rb b/spec/lib/gitlab/ci/config/entry/policy_spec.rb
index a606eb303e7..46800055dd9 100644
--- a/spec/lib/gitlab/ci/config/entry/policy_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/policy_spec.rb
@@ -4,7 +4,7 @@ require 'fast_spec_helper'
require 'support/helpers/stub_feature_flags'
require_dependency 'active_model'
-describe Gitlab::Ci::Config::Entry::Policy do
+RSpec.describe Gitlab::Ci::Config::Entry::Policy do
let(:entry) { described_class.new(config) }
context 'when using simplified policy' do
diff --git a/spec/lib/gitlab/ci/config/entry/port_spec.rb b/spec/lib/gitlab/ci/config/entry/port_spec.rb
index 5f8f294334e..e2840c07f6b 100644
--- a/spec/lib/gitlab/ci/config/entry/port_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/port_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Config::Entry::Port do
+RSpec.describe Gitlab::Ci::Config::Entry::Port do
let(:entry) { described_class.new(config) }
before do
diff --git a/spec/lib/gitlab/ci/config/entry/ports_spec.rb b/spec/lib/gitlab/ci/config/entry/ports_spec.rb
index 2063bd1d86c..f738c4ee875 100644
--- a/spec/lib/gitlab/ci/config/entry/ports_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/ports_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Config::Entry::Ports do
+RSpec.describe Gitlab::Ci::Config::Entry::Ports do
let(:entry) { described_class.new(config) }
before do
diff --git a/spec/lib/gitlab/ci/config/entry/prefix_spec.rb b/spec/lib/gitlab/ci/config/entry/prefix_spec.rb
index 8132a674488..b337747f5d6 100644
--- a/spec/lib/gitlab/ci/config/entry/prefix_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/prefix_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Config::Entry::Prefix do
+RSpec.describe Gitlab::Ci::Config::Entry::Prefix do
let(:entry) { described_class.new(config) }
describe 'validations' do
diff --git a/spec/lib/gitlab/ci/config/entry/processable_spec.rb b/spec/lib/gitlab/ci/config/entry/processable_spec.rb
index 8447a29c772..fdf6008f89f 100644
--- a/spec/lib/gitlab/ci/config/entry/processable_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/processable_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Config::Entry::Processable do
+RSpec.describe Gitlab::Ci::Config::Entry::Processable do
let(:node_class) do
Class.new(::Gitlab::Config::Entry::Node) do
include Gitlab::Ci::Config::Entry::Processable
@@ -231,6 +231,12 @@ describe Gitlab::Ci::Config::Entry::Processable do
end
context 'when workflow rules is used' do
+ let(:workflow) { double('workflow', 'has_rules?' => true) }
+
+ before do
+ entry.compose!(deps)
+ end
+
context 'when rules are used' do
let(:config) { { script: 'ls', cache: { key: 'test' }, rules: [] } }
@@ -239,11 +245,11 @@ describe Gitlab::Ci::Config::Entry::Processable do
end
end
- context 'when rules are not used' do
+ context 'when rules are not used and only is defined' do
let(:config) { { script: 'ls', cache: { key: 'test' }, only: [] } }
- it 'does not define only' do
- expect(entry).not_to be_only_defined
+ it 'keeps only entry' do
+ expect(entry).to be_only_defined
end
end
end
diff --git a/spec/lib/gitlab/ci/config/entry/release/assets/link_spec.rb b/spec/lib/gitlab/ci/config/entry/release/assets/link_spec.rb
index 0e346de3d9e..bc320258412 100644
--- a/spec/lib/gitlab/ci/config/entry/release/assets/link_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/release/assets/link_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Config::Entry::Release::Assets::Link do
+RSpec.describe Gitlab::Ci::Config::Entry::Release::Assets::Link do
let(:entry) { described_class.new(config) }
describe 'validation' do
diff --git a/spec/lib/gitlab/ci/config/entry/release/assets/links_spec.rb b/spec/lib/gitlab/ci/config/entry/release/assets/links_spec.rb
index d12e8d966ab..440745d9eb3 100644
--- a/spec/lib/gitlab/ci/config/entry/release/assets/links_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/release/assets/links_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Config::Entry::Release::Assets::Links do
+RSpec.describe Gitlab::Ci::Config::Entry::Release::Assets::Links do
let(:entry) { described_class.new(config) }
describe 'validation' do
diff --git a/spec/lib/gitlab/ci/config/entry/release/assets_spec.rb b/spec/lib/gitlab/ci/config/entry/release/assets_spec.rb
index 08ad5764eaa..3d44d7d9903 100644
--- a/spec/lib/gitlab/ci/config/entry/release/assets_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/release/assets_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Config::Entry::Release::Assets do
+RSpec.describe Gitlab::Ci::Config::Entry::Release::Assets do
let(:entry) { described_class.new(config) }
describe 'validation' do
diff --git a/spec/lib/gitlab/ci/config/entry/release_spec.rb b/spec/lib/gitlab/ci/config/entry/release_spec.rb
index 500897569e9..e5155f91be4 100644
--- a/spec/lib/gitlab/ci/config/entry/release_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/release_spec.rb
@@ -2,24 +2,35 @@
require 'spec_helper'
-describe Gitlab::Ci::Config::Entry::Release do
+RSpec.describe Gitlab::Ci::Config::Entry::Release do
let(:entry) { described_class.new(config) }
+ shared_examples_for 'a valid entry' do
+ describe '#value' do
+ it 'returns release configuration' do
+ expect(entry.value).to eq config
+ end
+ end
+
+ describe '#valid?' do
+ it 'is valid' do
+ expect(entry).to be_valid
+ end
+ end
+ end
+
+ shared_examples_for 'reports error' do |message|
+ it 'reports error' do
+ expect(entry.errors)
+ .to include message
+ end
+ end
+
describe 'validation' do
context 'when entry config value is correct' do
let(:config) { { tag_name: 'v0.06', description: "./release_changelog.txt" } }
- describe '#value' do
- it 'returns release configuration' do
- expect(entry.value).to eq config
- end
- end
-
- describe '#valid?' do
- it 'is valid' do
- expect(entry).to be_valid
- end
- end
+ it_behaves_like 'a valid entry'
end
context "when value includes 'assets' keyword" do
@@ -36,38 +47,144 @@ describe Gitlab::Ci::Config::Entry::Release do
}
end
- describe '#value' do
- it 'returns release configuration' do
- expect(entry.value).to eq config
- end
+ it_behaves_like 'a valid entry'
+ end
+
+ context "when value includes 'name' keyword" do
+ let(:config) do
+ {
+ tag_name: 'v0.06',
+ description: "./release_changelog.txt",
+ name: "Release $CI_TAG_NAME"
+ }
end
- describe '#valid?' do
- it 'is valid' do
- expect(entry).to be_valid
- end
+ it_behaves_like 'a valid entry'
+ end
+
+ context "when value includes 'ref' keyword" do
+ let(:config) do
+ {
+ tag_name: 'v0.06',
+ description: "./release_changelog.txt",
+ name: "Release $CI_TAG_NAME",
+ ref: 'b3235930aa443112e639f941c69c578912189bdd'
+ }
end
+
+ it_behaves_like 'a valid entry'
end
- context "when value includes 'name' keyword" do
+ context "when value includes 'released_at' keyword" do
let(:config) do
{
tag_name: 'v0.06',
description: "./release_changelog.txt",
- name: "Release $CI_TAG_NAME"
+ name: "Release $CI_TAG_NAME",
+ released_at: '2019-03-15T08:00:00Z'
}
end
- describe '#value' do
- it 'returns release configuration' do
- expect(entry.value).to eq config
- end
+ it_behaves_like 'a valid entry'
+ end
+
+ context "when value includes 'milestones' keyword" do
+ let(:config) do
+ {
+ tag_name: 'v0.06',
+ description: "./release_changelog.txt",
+ name: "Release $CI_TAG_NAME",
+ milestones: milestones
+ }
end
- describe '#valid?' do
- it 'is valid' do
- expect(entry).to be_valid
- end
+ context 'for an array of milestones' do
+ let(:milestones) { %w[m1 m2 m3] }
+
+ it_behaves_like 'a valid entry'
+ end
+
+ context 'for a single milestone' do
+ let(:milestones) { 'm1' }
+
+ it_behaves_like 'a valid entry'
+ end
+ end
+
+ context "when value includes 'ref' keyword" do
+ let(:config) do
+ {
+ tag_name: 'v0.06',
+ description: "./release_changelog.txt",
+ name: "Release $CI_TAG_NAME",
+ ref: ref
+ }
+ end
+
+ context "when 'ref' is a full commit SHA" do
+ let(:ref) { 'b3235930aa443112e639f941c69c578912189bdd' }
+
+ it_behaves_like 'a valid entry'
+ end
+
+ context "when 'ref' is a short commit SHA" do
+ let(:ref) { 'b3235930'}
+
+ it_behaves_like 'a valid entry'
+ end
+
+ context "when 'ref' is a branch name" do
+ let(:ref) { 'fix/123-branch-name'}
+
+ it_behaves_like 'a valid entry'
+ end
+
+ context "when 'ref' is a semantic versioning tag" do
+ let(:ref) { 'v1.2.3'}
+
+ it_behaves_like 'a valid entry'
+ end
+
+ context "when 'ref' is a semantic versioning tag rc" do
+ let(:ref) { 'v1.2.3-rc'}
+
+ it_behaves_like 'a valid entry'
+ end
+ end
+
+ context "when value includes 'released_at' keyword" do
+ let(:config) do
+ {
+ tag_name: 'v0.06',
+ description: "./release_changelog.txt",
+ name: "Release $CI_TAG_NAME",
+ released_at: '2019-03-15T08:00:00Z'
+ }
+ end
+
+ it_behaves_like 'a valid entry'
+ end
+
+ context "when value includes 'milestones' keyword" do
+ let(:config) do
+ {
+ tag_name: 'v0.06',
+ description: "./release_changelog.txt",
+ name: "Release $CI_TAG_NAME",
+ milestones: milestones
+ }
+ end
+
+ context 'for an array of milestones' do
+ let(:milestones) { %w[m1 m2 m3] }
+
+ it_behaves_like 'a valid entry'
+ end
+
+ context 'for a single milestone' do
+ let(:milestones) { 'm1' }
+
+ it_behaves_like 'a valid entry'
end
end
@@ -76,37 +193,43 @@ describe Gitlab::Ci::Config::Entry::Release do
context 'when value of attribute is invalid' do
let(:config) { { description: 10 } }
- it 'reports error' do
- expect(entry.errors)
- .to include 'release description should be a string'
- end
+ it_behaves_like 'reports error', 'release description should be a string'
end
context 'when release description is missing' do
let(:config) { { tag_name: 'v0.06' } }
- it 'reports error' do
- expect(entry.errors)
- .to include "release description can't be blank"
- end
+ it_behaves_like 'reports error', "release description can't be blank"
end
context 'when release tag_name is missing' do
let(:config) { { description: "./release_changelog.txt" } }
- it 'reports error' do
- expect(entry.errors)
- .to include "release tag name can't be blank"
- end
+ it_behaves_like 'reports error', "release tag name can't be blank"
end
context 'when there is an unknown key present' do
let(:config) { { test: 100 } }
- it 'reports error' do
- expect(entry.errors)
- .to include 'release config contains unknown keys: test'
- end
+ it_behaves_like 'reports error', 'release config contains unknown keys: test'
+ end
+
+ context 'when `released_at` is not a valid date' do
+ let(:config) { { released_at: 'ABC123' } }
+
+ it_behaves_like 'reports error', 'release released at must be a valid datetime'
+ end
+
+ context 'when `ref` is not valid' do
+ let(:config) { { ref: 'invalid\branch' } }
+
+ it_behaves_like 'reports error', 'release ref must be a valid ref'
+ end
+
+ context 'when `milestones` is not an array of strings' do
+ let(:config) { { milestones: [1, 2, 3] } }
+
+ it_behaves_like 'reports error', 'release milestones should be an array of strings or a string'
end
end
end
diff --git a/spec/lib/gitlab/ci/config/entry/reports_spec.rb b/spec/lib/gitlab/ci/config/entry/reports_spec.rb
index 2c12a88dedb..98105ebcd55 100644
--- a/spec/lib/gitlab/ci/config/entry/reports_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/reports_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Config::Entry::Reports do
+RSpec.describe Gitlab::Ci::Config::Entry::Reports do
let(:entry) { described_class.new(config) }
describe 'validates ALLOWED_KEYS' do
@@ -44,6 +44,9 @@ describe Gitlab::Ci::Config::Entry::Reports do
:license_management | 'gl-license-management-report.json'
:license_scanning | 'gl-license-scanning-report.json'
:performance | 'performance.json'
+ :browser_performance | 'browser-performance.json'
+ :browser_performance | 'performance.json'
+ :load_performance | 'load-performance.json'
:lsif | 'lsif.json'
:dotenv | 'build.dotenv'
:cobertura | 'cobertura-coverage.xml'
diff --git a/spec/lib/gitlab/ci/config/entry/retry_spec.rb b/spec/lib/gitlab/ci/config/entry/retry_spec.rb
index 67253c71f6b..b38387a437e 100644
--- a/spec/lib/gitlab/ci/config/entry/retry_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/retry_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Config::Entry::Retry do
+RSpec.describe Gitlab::Ci::Config::Entry::Retry do
let(:entry) { described_class.new(config) }
shared_context 'when retry value is a numeric', :numeric do
diff --git a/spec/lib/gitlab/ci/config/entry/root_spec.rb b/spec/lib/gitlab/ci/config/entry/root_spec.rb
index ba2dbf72fba..140b3c4f55b 100644
--- a/spec/lib/gitlab/ci/config/entry/root_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/root_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Config::Entry::Root do
+RSpec.describe Gitlab::Ci::Config::Entry::Root do
let(:root) { described_class.new(hash) }
describe '.nodes' do
diff --git a/spec/lib/gitlab/ci/config/entry/rules/rule_spec.rb b/spec/lib/gitlab/ci/config/entry/rules/rule_spec.rb
index 20db5f02fc7..4a43e6c9a86 100644
--- a/spec/lib/gitlab/ci/config/entry/rules/rule_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/rules/rule_spec.rb
@@ -5,7 +5,7 @@ require 'gitlab_chronic_duration'
require 'support/helpers/stub_feature_flags'
require_dependency 'active_model'
-describe Gitlab::Ci::Config::Entry::Rules::Rule do
+RSpec.describe Gitlab::Ci::Config::Entry::Rules::Rule do
let(:factory) do
Gitlab::Config::Entry::Factory.new(described_class)
.metadata(metadata)
diff --git a/spec/lib/gitlab/ci/config/entry/rules_spec.rb b/spec/lib/gitlab/ci/config/entry/rules_spec.rb
index 3c050801023..7d26365e7b3 100644
--- a/spec/lib/gitlab/ci/config/entry/rules_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/rules_spec.rb
@@ -4,7 +4,7 @@ require 'fast_spec_helper'
require 'support/helpers/stub_feature_flags'
require_dependency 'active_model'
-describe Gitlab::Ci::Config::Entry::Rules do
+RSpec.describe Gitlab::Ci::Config::Entry::Rules do
let(:factory) do
Gitlab::Config::Entry::Factory.new(described_class)
.metadata(metadata)
diff --git a/spec/lib/gitlab/ci/config/entry/script_spec.rb b/spec/lib/gitlab/ci/config/entry/script_spec.rb
index 57dc20ea628..1ddf7881e81 100644
--- a/spec/lib/gitlab/ci/config/entry/script_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/script_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Config::Entry::Script do
+RSpec.describe Gitlab::Ci::Config::Entry::Script do
let(:entry) { described_class.new(config) }
describe 'validations' do
diff --git a/spec/lib/gitlab/ci/config/entry/service_spec.rb b/spec/lib/gitlab/ci/config/entry/service_spec.rb
index 66cca100688..9fbc14c19b9 100644
--- a/spec/lib/gitlab/ci/config/entry/service_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Config::Entry::Service do
+RSpec.describe Gitlab::Ci::Config::Entry::Service do
let(:entry) { described_class.new(config) }
before do
diff --git a/spec/lib/gitlab/ci/config/entry/services_spec.rb b/spec/lib/gitlab/ci/config/entry/services_spec.rb
index 764f783b083..e4f8a348d21 100644
--- a/spec/lib/gitlab/ci/config/entry/services_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/services_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Config::Entry::Services do
+RSpec.describe Gitlab::Ci::Config::Entry::Services do
let(:entry) { described_class.new(config) }
before do
diff --git a/spec/lib/gitlab/ci/config/entry/stage_spec.rb b/spec/lib/gitlab/ci/config/entry/stage_spec.rb
index 574fa00575a..2a105fb978e 100644
--- a/spec/lib/gitlab/ci/config/entry/stage_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/stage_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Config::Entry::Stage do
+RSpec.describe Gitlab::Ci::Config::Entry::Stage do
let(:stage) { described_class.new(config) }
describe 'validations' do
diff --git a/spec/lib/gitlab/ci/config/entry/stages_spec.rb b/spec/lib/gitlab/ci/config/entry/stages_spec.rb
index 3e6ff8eca28..1dd11bb03f8 100644
--- a/spec/lib/gitlab/ci/config/entry/stages_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/stages_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Config::Entry::Stages do
+RSpec.describe Gitlab::Ci::Config::Entry::Stages do
let(:entry) { described_class.new(config) }
describe 'validations' do
diff --git a/spec/lib/gitlab/ci/config/entry/trigger_spec.rb b/spec/lib/gitlab/ci/config/entry/trigger_spec.rb
index dfd9807583c..5b4289741f3 100644
--- a/spec/lib/gitlab/ci/config/entry/trigger_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/trigger_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Config::Entry::Trigger do
+RSpec.describe Gitlab::Ci::Config::Entry::Trigger do
subject { described_class.new(config) }
context 'when trigger config is a non-empty string' do
diff --git a/spec/lib/gitlab/ci/config/entry/variables_spec.rb b/spec/lib/gitlab/ci/config/entry/variables_spec.rb
index 1320b366367..d6391092f63 100644
--- a/spec/lib/gitlab/ci/config/entry/variables_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/variables_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Config::Entry::Variables do
+RSpec.describe Gitlab::Ci::Config::Entry::Variables do
let(:entry) { described_class.new(config) }
describe 'validations' do
diff --git a/spec/lib/gitlab/ci/config/entry/workflow_spec.rb b/spec/lib/gitlab/ci/config/entry/workflow_spec.rb
index f2832b94bf0..3d19832e13d 100644
--- a/spec/lib/gitlab/ci/config/entry/workflow_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/workflow_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Config::Entry::Workflow do
+RSpec.describe Gitlab::Ci::Config::Entry::Workflow do
let(:factory) { Gitlab::Config::Entry::Factory.new(described_class).value(rules_hash) }
let(:config) { factory.create! }
diff --git a/spec/lib/gitlab/ci/config/extendable/entry_spec.rb b/spec/lib/gitlab/ci/config/extendable/entry_spec.rb
index e00104e3c68..69aa3bab77a 100644
--- a/spec/lib/gitlab/ci/config/extendable/entry_spec.rb
+++ b/spec/lib/gitlab/ci/config/extendable/entry_spec.rb
@@ -2,7 +2,7 @@
require 'fast_spec_helper'
-describe Gitlab::Ci::Config::Extendable::Entry do
+RSpec.describe Gitlab::Ci::Config::Extendable::Entry do
describe '.new' do
context 'when entry key is not included in the context hash' do
it 'raises error' do
diff --git a/spec/lib/gitlab/ci/config/extendable_spec.rb b/spec/lib/gitlab/ci/config/extendable_spec.rb
index 874b224067b..481f55d790e 100644
--- a/spec/lib/gitlab/ci/config/extendable_spec.rb
+++ b/spec/lib/gitlab/ci/config/extendable_spec.rb
@@ -2,7 +2,7 @@
require 'fast_spec_helper'
-describe Gitlab::Ci::Config::Extendable do
+RSpec.describe Gitlab::Ci::Config::Extendable do
subject { described_class.new(hash) }
describe '#each' do
diff --git a/spec/lib/gitlab/ci/config/external/context_spec.rb b/spec/lib/gitlab/ci/config/external/context_spec.rb
index 610646ca85a..4b9adf7e87b 100644
--- a/spec/lib/gitlab/ci/config/external/context_spec.rb
+++ b/spec/lib/gitlab/ci/config/external/context_spec.rb
@@ -2,7 +2,7 @@
require 'fast_spec_helper'
-describe Gitlab::Ci::Config::External::Context do
+RSpec.describe Gitlab::Ci::Config::External::Context do
let(:project) { double('Project') }
let(:user) { double('User') }
let(:sha) { '12345' }
diff --git a/spec/lib/gitlab/ci/config/external/file/artifact_spec.rb b/spec/lib/gitlab/ci/config/external/file/artifact_spec.rb
index a8eb13c47bc..8dd92c5b5fd 100644
--- a/spec/lib/gitlab/ci/config/external/file/artifact_spec.rb
+++ b/spec/lib/gitlab/ci/config/external/file/artifact_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Config::External::File::Artifact do
+RSpec.describe Gitlab::Ci::Config::External::File::Artifact do
let(:parent_pipeline) { create(:ci_pipeline) }
let(:context) do
Gitlab::Ci::Config::External::Context.new(parent_pipeline: parent_pipeline)
diff --git a/spec/lib/gitlab/ci/config/external/file/base_spec.rb b/spec/lib/gitlab/ci/config/external/file/base_spec.rb
index d472d6527e2..445edb253fd 100644
--- a/spec/lib/gitlab/ci/config/external/file/base_spec.rb
+++ b/spec/lib/gitlab/ci/config/external/file/base_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Config::External::File::Base do
+RSpec.describe Gitlab::Ci::Config::External::File::Base do
let(:context_params) { { sha: 'HEAD' } }
let(:context) { Gitlab::Ci::Config::External::Context.new(**context_params) }
diff --git a/spec/lib/gitlab/ci/config/external/file/local_spec.rb b/spec/lib/gitlab/ci/config/external/file/local_spec.rb
index c9851239859..993a07568de 100644
--- a/spec/lib/gitlab/ci/config/external/file/local_spec.rb
+++ b/spec/lib/gitlab/ci/config/external/file/local_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Config::External::File::Local do
+RSpec.describe Gitlab::Ci::Config::External::File::Local do
let_it_be(:project) { create(:project, :repository) }
let_it_be(:user) { create(:user) }
let(:sha) { '12345' }
diff --git a/spec/lib/gitlab/ci/config/external/file/project_spec.rb b/spec/lib/gitlab/ci/config/external/file/project_spec.rb
index 4d8f0dbc861..a5e4e27df6f 100644
--- a/spec/lib/gitlab/ci/config/external/file/project_spec.rb
+++ b/spec/lib/gitlab/ci/config/external/file/project_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Config::External::File::Project do
+RSpec.describe Gitlab::Ci::Config::External::File::Project do
let_it_be(:context_project) { create(:project) }
let_it_be(:project) { create(:project, :repository) }
let_it_be(:user) { create(:user) }
diff --git a/spec/lib/gitlab/ci/config/external/file/remote_spec.rb b/spec/lib/gitlab/ci/config/external/file/remote_spec.rb
index a23cce9b757..ab60ac215ba 100644
--- a/spec/lib/gitlab/ci/config/external/file/remote_spec.rb
+++ b/spec/lib/gitlab/ci/config/external/file/remote_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Config::External::File::Remote do
+RSpec.describe Gitlab::Ci::Config::External::File::Remote do
include StubRequests
let(:context_params) { { sha: '12345' } }
diff --git a/spec/lib/gitlab/ci/config/external/file/template_spec.rb b/spec/lib/gitlab/ci/config/external/file/template_spec.rb
index 7ea5aadac52..ad1d93a64a1 100644
--- a/spec/lib/gitlab/ci/config/external/file/template_spec.rb
+++ b/spec/lib/gitlab/ci/config/external/file/template_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Config::External::File::Template do
+RSpec.describe Gitlab::Ci::Config::External::File::Template do
let_it_be(:project) { create(:project) }
let_it_be(:user) { create(:user) }
let(:context_params) { { project: project, sha: '12345', user: user } }
diff --git a/spec/lib/gitlab/ci/config/external/mapper_spec.rb b/spec/lib/gitlab/ci/config/external/mapper_spec.rb
index fa358f36527..bf14d8d6b34 100644
--- a/spec/lib/gitlab/ci/config/external/mapper_spec.rb
+++ b/spec/lib/gitlab/ci/config/external/mapper_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Config::External::Mapper do
+RSpec.describe Gitlab::Ci::Config::External::Mapper do
include StubRequests
let_it_be(:project) { create(:project, :repository) }
diff --git a/spec/lib/gitlab/ci/config/external/processor_spec.rb b/spec/lib/gitlab/ci/config/external/processor_spec.rb
index 45f646660a7..b2cf36b2597 100644
--- a/spec/lib/gitlab/ci/config/external/processor_spec.rb
+++ b/spec/lib/gitlab/ci/config/external/processor_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Config::External::Processor do
+RSpec.describe Gitlab::Ci::Config::External::Processor do
include StubRequests
let_it_be(:project) { create(:project, :repository) }
diff --git a/spec/lib/gitlab/ci/config/normalizer_spec.rb b/spec/lib/gitlab/ci/config/normalizer_spec.rb
index db62fb7524d..d3d165ba00f 100644
--- a/spec/lib/gitlab/ci/config/normalizer_spec.rb
+++ b/spec/lib/gitlab/ci/config/normalizer_spec.rb
@@ -2,7 +2,7 @@
require 'fast_spec_helper'
-describe Gitlab::Ci::Config::Normalizer do
+RSpec.describe Gitlab::Ci::Config::Normalizer do
let(:job_name) { :rspec }
let(:job_config) { { script: 'rspec', parallel: 5, name: 'rspec' } }
let(:config) { { job_name => job_config } }
diff --git a/spec/lib/gitlab/ci/config_spec.rb b/spec/lib/gitlab/ci/config_spec.rb
index 3b65dbe11ec..18be9558829 100644
--- a/spec/lib/gitlab/ci/config_spec.rb
+++ b/spec/lib/gitlab/ci/config_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Config do
+RSpec.describe Gitlab::Ci::Config do
include StubRequests
let_it_be(:user) { create(:user) }
diff --git a/spec/lib/gitlab/ci/cron_parser_spec.rb b/spec/lib/gitlab/ci/cron_parser_spec.rb
index 8f9f3d7fa37..f724825a9cc 100644
--- a/spec/lib/gitlab/ci/cron_parser_spec.rb
+++ b/spec/lib/gitlab/ci/cron_parser_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::CronParser do
+RSpec.describe Gitlab::Ci::CronParser do
shared_examples_for "returns time in the future" do
it { is_expected.to be > Time.now }
end
diff --git a/spec/lib/gitlab/ci/jwt_spec.rb b/spec/lib/gitlab/ci/jwt_spec.rb
index f2897708b08..a15f3310dab 100644
--- a/spec/lib/gitlab/ci/jwt_spec.rb
+++ b/spec/lib/gitlab/ci/jwt_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Jwt do
+RSpec.describe Gitlab::Ci::Jwt do
let(:namespace) { build_stubbed(:namespace) }
let(:project) { build_stubbed(:project, namespace: namespace) }
let(:user) { build_stubbed(:user) }
diff --git a/spec/lib/gitlab/ci/mask_secret_spec.rb b/spec/lib/gitlab/ci/mask_secret_spec.rb
index 6607aaae399..7b2d6b58518 100644
--- a/spec/lib/gitlab/ci/mask_secret_spec.rb
+++ b/spec/lib/gitlab/ci/mask_secret_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::MaskSecret do
+RSpec.describe Gitlab::Ci::MaskSecret do
subject { described_class }
describe '#mask' do
diff --git a/spec/lib/gitlab/ci/parsers/accessibility/pa11y_spec.rb b/spec/lib/gitlab/ci/parsers/accessibility/pa11y_spec.rb
index 4d87e3b201a..b3edf452f36 100644
--- a/spec/lib/gitlab/ci/parsers/accessibility/pa11y_spec.rb
+++ b/spec/lib/gitlab/ci/parsers/accessibility/pa11y_spec.rb
@@ -1,8 +1,8 @@
# frozen_string_literal: true
-require 'fast_spec_helper'
+require 'spec_helper'
-describe Gitlab::Ci::Parsers::Accessibility::Pa11y do
+RSpec.describe Gitlab::Ci::Parsers::Accessibility::Pa11y do
describe '#parse!' do
subject { described_class.new.parse!(pa11y, accessibility_report) }
@@ -108,7 +108,7 @@ describe Gitlab::Ci::Parsers::Accessibility::Pa11y do
it "sets error_message" do
expect { subject }.not_to raise_error
- expect(accessibility_report.error_message).to include('Pa11y parsing failed')
+ expect(accessibility_report.error_message).to include('JSON parsing failed')
expect(accessibility_report.errors_count).to eq(0)
expect(accessibility_report.passes_count).to eq(0)
expect(accessibility_report.scans_count).to eq(0)
diff --git a/spec/lib/gitlab/ci/parsers/coverage/cobertura_spec.rb b/spec/lib/gitlab/ci/parsers/coverage/cobertura_spec.rb
index e97544683db..08a3fbd7867 100644
--- a/spec/lib/gitlab/ci/parsers/coverage/cobertura_spec.rb
+++ b/spec/lib/gitlab/ci/parsers/coverage/cobertura_spec.rb
@@ -2,7 +2,7 @@
require 'fast_spec_helper'
-describe Gitlab::Ci::Parsers::Coverage::Cobertura do
+RSpec.describe Gitlab::Ci::Parsers::Coverage::Cobertura do
describe '#parse!' do
subject { described_class.new.parse!(cobertura, coverage_report) }
diff --git a/spec/lib/gitlab/ci/parsers/terraform/tfplan_spec.rb b/spec/lib/gitlab/ci/parsers/terraform/tfplan_spec.rb
index fec27c0f31a..f487fccdab7 100644
--- a/spec/lib/gitlab/ci/parsers/terraform/tfplan_spec.rb
+++ b/spec/lib/gitlab/ci/parsers/terraform/tfplan_spec.rb
@@ -2,39 +2,86 @@
require 'spec_helper'
-describe Gitlab::Ci::Parsers::Terraform::Tfplan do
+RSpec.describe Gitlab::Ci::Parsers::Terraform::Tfplan do
describe '#parse!' do
- let_it_be(:artifact) { create(:ci_job_artifact, :terraform) }
+ let(:artifact) { create(:ci_job_artifact, :terraform) }
let(:reports) { Gitlab::Ci::Reports::TerraformReports.new }
context 'when data is invalid' do
- context 'when there is no data' do
- it 'raises an error' do
- plan = '{}'
+ context 'when data is not a JSON file' do
+ it 'reports an invalid_json_format error' do
+ plan = 'Not a JSON file'
+
+ expect { subject.parse!(plan, reports, artifact: artifact) }.not_to raise_error
+
+ reports.plans.each do |key, hash_value|
+ expect(hash_value.keys).to match_array(%w[job_id job_name job_path tf_report_error])
+ end
- expect { subject.parse!(plan, reports, artifact: artifact) }.to raise_error(
- described_class::TfplanParserError
+ expect(reports.plans).to match(
+ a_hash_including(
+ artifact.job.id.to_s => a_hash_including(
+ 'tf_report_error' => :invalid_json_format
+ )
+ )
)
end
end
- context 'when data is not a JSON file' do
- it 'raises an error' do
- plan = { 'create' => 0, 'update' => 1, 'delete' => 0 }.to_s
+ context 'when JSON is missing a required key' do
+ it 'reports an invalid_json_keys error' do
+ plan = '{ "wrong_key": 1 }'
+
+ expect { subject.parse!(plan, reports, artifact: artifact) }.not_to raise_error
+
+ reports.plans.each do |key, hash_value|
+ expect(hash_value.keys).to match_array(%w[job_id job_name job_path tf_report_error])
+ end
- expect { subject.parse!(plan, reports, artifact: artifact) }.to raise_error(
- described_class::TfplanParserError
+ expect(reports.plans).to match(
+ a_hash_including(
+ artifact.job.id.to_s => a_hash_including(
+ 'tf_report_error' => :missing_json_keys
+ )
+ )
)
end
end
- context 'when JSON is missing a required key' do
- it 'raises an error' do
- plan = '{ "wrong_key": 1 }'
+ context 'when artifact is invalid' do
+ it 'reports an :unknown_error' do
+ expect { subject.parse!('{}', reports, artifact: nil) }.not_to raise_error
+
+ reports.plans.each do |key, hash_value|
+ expect(hash_value.keys).to match_array(%w[tf_report_error])
+ end
+
+ expect(reports.plans).to match(
+ a_hash_including(
+ 'failed_tf_plan' => a_hash_including(
+ 'tf_report_error' => :unknown_error
+ )
+ )
+ )
+ end
+ end
+
+ context 'when job is invalid' do
+ it 'reports an :unknown_error' do
+ artifact.job_id = nil
+ expect { subject.parse!('{}', reports, artifact: artifact) }.not_to raise_error
- expect { subject.parse!(plan, reports, artifact: artifact) }.to raise_error(
- described_class::TfplanParserError
+ reports.plans.each do |key, hash_value|
+ expect(hash_value.keys).to match_array(%w[tf_report_error])
+ end
+
+ expect(reports.plans).to match(
+ a_hash_including(
+ 'failed_tf_plan' => a_hash_including(
+ 'tf_report_error' => :unknown_error
+ )
+ )
)
end
end
@@ -47,7 +94,7 @@ describe Gitlab::Ci::Parsers::Terraform::Tfplan do
expect { subject.parse!(plan, reports, artifact: artifact) }.not_to raise_error
reports.plans.each do |key, hash_value|
- expect(hash_value.keys).to match_array(%w[create delete job_name job_path update])
+ expect(hash_value.keys).to match_array(%w[create delete job_id job_name job_path update])
end
expect(reports.plans).to match(
@@ -68,7 +115,7 @@ describe Gitlab::Ci::Parsers::Terraform::Tfplan do
expect { subject.parse!(plan, reports, artifact: artifact) }.not_to raise_error
reports.plans.each do |key, hash_value|
- expect(hash_value.keys).to match_array(%w[create delete job_name job_path update])
+ expect(hash_value.keys).to match_array(%w[create delete job_id job_name job_path update])
end
expect(reports.plans).to match(
diff --git a/spec/lib/gitlab/ci/parsers/test/junit_spec.rb b/spec/lib/gitlab/ci/parsers/test/junit_spec.rb
index 7b7ace02bba..1f497dea2bf 100644
--- a/spec/lib/gitlab/ci/parsers/test/junit_spec.rb
+++ b/spec/lib/gitlab/ci/parsers/test/junit_spec.rb
@@ -2,7 +2,7 @@
require 'fast_spec_helper'
-describe Gitlab::Ci::Parsers::Test::Junit do
+RSpec.describe Gitlab::Ci::Parsers::Test::Junit do
describe '#parse!' do
subject { described_class.new.parse!(junit, test_suite, args) }
diff --git a/spec/lib/gitlab/ci/parsers_spec.rb b/spec/lib/gitlab/ci/parsers_spec.rb
index 0a266e7a206..db9a5775d9f 100644
--- a/spec/lib/gitlab/ci/parsers_spec.rb
+++ b/spec/lib/gitlab/ci/parsers_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Parsers do
+RSpec.describe Gitlab::Ci::Parsers do
describe '.fabricate!' do
subject { described_class.fabricate!(file_type) }
diff --git a/spec/lib/gitlab/ci/pipeline/chain/build/associations_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/build/associations_spec.rb
index 542a2462b59..5fa414f5bd1 100644
--- a/spec/lib/gitlab/ci/pipeline/chain/build/associations_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/chain/build/associations_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Pipeline::Chain::Build::Associations do
+RSpec.describe Gitlab::Ci::Pipeline::Chain::Build::Associations do
let(:project) { create(:project, :repository) }
let(:user) { create(:user, developer_projects: [project]) }
let(:pipeline) { Ci::Pipeline.new }
diff --git a/spec/lib/gitlab/ci/pipeline/chain/build_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/build_spec.rb
index 094563bd979..6da565a2bf6 100644
--- a/spec/lib/gitlab/ci/pipeline/chain/build_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/chain/build_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Pipeline::Chain::Build do
+RSpec.describe Gitlab::Ci::Pipeline::Chain::Build do
let_it_be(:project) { create(:project, :repository) }
let_it_be(:user) { create(:user, developer_projects: [project]) }
let(:pipeline) { Ci::Pipeline.new }
diff --git a/spec/lib/gitlab/ci/pipeline/chain/command_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/command_spec.rb
index 56707f4e6e4..bc2012e83bd 100644
--- a/spec/lib/gitlab/ci/pipeline/chain/command_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/chain/command_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Pipeline::Chain::Command do
+RSpec.describe Gitlab::Ci::Pipeline::Chain::Command do
let_it_be(:project) { create(:project, :repository) }
describe '#initialize' do
@@ -270,4 +270,29 @@ describe Gitlab::Ci::Pipeline::Chain::Command do
it { is_expected. to eq(true) }
end
end
+
+ describe '#dangling_build?' do
+ let(:project) { create(:project, :repository) }
+ let(:command) { described_class.new(project: project, source: source) }
+
+ subject { command.dangling_build? }
+
+ context 'when source is :webide' do
+ let(:source) { :webide }
+
+ it { is_expected.to eq(true) }
+ end
+
+ context 'when source is :ondemand_dast_scan' do
+ let(:source) { :ondemand_dast_scan }
+
+ it { is_expected.to eq(true) }
+ end
+
+ context 'when source something else' do
+ let(:source) { :web }
+
+ it { is_expected.to eq(false) }
+ end
+ end
end
diff --git a/spec/lib/gitlab/ci/pipeline/chain/config/content_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/config/content_spec.rb
index fc95bb602c2..42ec9ab6f5d 100644
--- a/spec/lib/gitlab/ci/pipeline/chain/config/content_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/chain/config/content_spec.rb
@@ -2,10 +2,12 @@
require 'spec_helper'
-describe Gitlab::Ci::Pipeline::Chain::Config::Content do
+RSpec.describe Gitlab::Ci::Pipeline::Chain::Config::Content do
let(:project) { create(:project, ci_config_path: ci_config_path) }
let(:pipeline) { build(:ci_pipeline, project: project) }
- let(:command) { Gitlab::Ci::Pipeline::Chain::Command.new(project: project) }
+ let(:content) { nil }
+ let(:source) { :push }
+ let(:command) { Gitlab::Ci::Pipeline::Chain::Command.new(project: project, content: content, source: source) }
subject { described_class.new(pipeline, command) }
@@ -141,6 +143,26 @@ describe Gitlab::Ci::Pipeline::Chain::Config::Content do
end
end
+ context 'when config is passed as a parameter' do
+ let(:source) { :ondemand_dast_scan }
+ let(:ci_config_path) { nil }
+ let(:content) do
+ <<~EOY
+ ---
+ stages:
+ - dast
+ EOY
+ end
+
+ it 'uses the parameter content' do
+ subject.perform!
+
+ expect(pipeline.config_source).to eq 'parameter_source'
+ expect(pipeline.pipeline_config.content).to eq(content)
+ expect(command.config_content).to eq(content)
+ end
+ end
+
context 'when config is not defined anywhere' do
let(:ci_config_path) { nil }
diff --git a/spec/lib/gitlab/ci/pipeline/chain/create_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/create_spec.rb
index f18ad05a704..d60ecc80a6e 100644
--- a/spec/lib/gitlab/ci/pipeline/chain/create_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/chain/create_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Pipeline::Chain::Create do
+RSpec.describe Gitlab::Ci::Pipeline::Chain::Create do
let_it_be(:project) { create(:project) }
let_it_be(:user) { create(:user) }
diff --git a/spec/lib/gitlab/ci/pipeline/chain/evaluate_workflow_rules_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/evaluate_workflow_rules_spec.rb
index 7b76adaf683..4ae51ac8bf9 100644
--- a/spec/lib/gitlab/ci/pipeline/chain/evaluate_workflow_rules_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/chain/evaluate_workflow_rules_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Pipeline::Chain::EvaluateWorkflowRules do
+RSpec.describe Gitlab::Ci::Pipeline::Chain::EvaluateWorkflowRules do
let(:project) { create(:project) }
let(:user) { create(:user) }
let(:pipeline) { build(:ci_pipeline, project: project) }
diff --git a/spec/lib/gitlab/ci/pipeline/chain/populate_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/populate_spec.rb
index 6dab5679e60..8c02121857a 100644
--- a/spec/lib/gitlab/ci/pipeline/chain/populate_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/chain/populate_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Pipeline::Chain::Populate do
+RSpec.describe Gitlab::Ci::Pipeline::Chain::Populate do
let_it_be(:project) { create(:project, :repository) }
let_it_be(:user) { create(:user) }
diff --git a/spec/lib/gitlab/ci/pipeline/chain/remove_unwanted_chat_jobs_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/remove_unwanted_chat_jobs_spec.rb
index 92eadf5548c..8b9de16ce5f 100644
--- a/spec/lib/gitlab/ci/pipeline/chain/remove_unwanted_chat_jobs_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/chain/remove_unwanted_chat_jobs_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ::Gitlab::Ci::Pipeline::Chain::RemoveUnwantedChatJobs do
+RSpec.describe ::Gitlab::Ci::Pipeline::Chain::RemoveUnwantedChatJobs do
let(:project) { create(:project) }
let(:pipeline) do
diff --git a/spec/lib/gitlab/ci/pipeline/chain/seed_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/seed_spec.rb
index 1e1d5c2a724..f83cd49d780 100644
--- a/spec/lib/gitlab/ci/pipeline/chain/seed_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/chain/seed_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Pipeline::Chain::Seed do
+RSpec.describe Gitlab::Ci::Pipeline::Chain::Seed do
let(:project) { create(:project, :repository) }
let(:user) { create(:user, developer_projects: [project]) }
diff --git a/spec/lib/gitlab/ci/pipeline/chain/sequence_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/sequence_spec.rb
index ea04862ed74..5d20b1b8fda 100644
--- a/spec/lib/gitlab/ci/pipeline/chain/sequence_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/chain/sequence_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Pipeline::Chain::Sequence do
+RSpec.describe Gitlab::Ci::Pipeline::Chain::Sequence do
let_it_be(:project) { create(:project) }
let_it_be(:user) { create(:user) }
diff --git a/spec/lib/gitlab/ci/pipeline/chain/skip_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/skip_spec.rb
index 55d6d35340e..e4768f2ef0d 100644
--- a/spec/lib/gitlab/ci/pipeline/chain/skip_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/chain/skip_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Pipeline::Chain::Skip do
+RSpec.describe Gitlab::Ci::Pipeline::Chain::Skip do
let_it_be(:project, reload: true) { create(:project) }
let_it_be(:user) { create(:user) }
let_it_be(:pipeline, reload: true) { create(:ci_pipeline, project: project) }
diff --git a/spec/lib/gitlab/ci/pipeline/chain/validate/abilities_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/validate/abilities_spec.rb
index 8bf8bdf08ff..ae3270cb9b2 100644
--- a/spec/lib/gitlab/ci/pipeline/chain/validate/abilities_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/chain/validate/abilities_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Pipeline::Chain::Validate::Abilities do
+RSpec.describe Gitlab::Ci::Pipeline::Chain::Validate::Abilities do
let_it_be(:project, reload: true) { create(:project, :repository) }
let_it_be(:user) { create(:user) }
diff --git a/spec/lib/gitlab/ci/pipeline/chain/validate/external_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/validate/external_spec.rb
index 9a2cf014007..931c62701ce 100644
--- a/spec/lib/gitlab/ci/pipeline/chain/validate/external_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/chain/validate/external_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Pipeline::Chain::Validate::External do
+RSpec.describe Gitlab::Ci::Pipeline::Chain::Validate::External do
let(:project) { create(:project) }
let(:user) { create(:user) }
let(:pipeline) { build(:ci_empty_pipeline, user: user, project: project) }
diff --git a/spec/lib/gitlab/ci/pipeline/chain/validate/repository_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/validate/repository_spec.rb
index 83271007ab0..1dc2e0a1822 100644
--- a/spec/lib/gitlab/ci/pipeline/chain/validate/repository_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/chain/validate/repository_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Pipeline::Chain::Validate::Repository do
+RSpec.describe Gitlab::Ci::Pipeline::Chain::Validate::Repository do
let_it_be(:project) { create(:project, :repository) }
let_it_be(:user) { create(:user) }
let(:pipeline) { build_stubbed(:ci_pipeline) }
diff --git a/spec/lib/gitlab/ci/pipeline/duration_spec.rb b/spec/lib/gitlab/ci/pipeline/duration_spec.rb
index a4984092f35..e0b4928d7f7 100644
--- a/spec/lib/gitlab/ci/pipeline/duration_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/duration_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Pipeline::Duration do
+RSpec.describe Gitlab::Ci::Pipeline::Duration do
let(:calculated_duration) { calculate(data) }
shared_examples 'calculating duration' do
diff --git a/spec/lib/gitlab/ci/pipeline/expression/lexeme/and_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/lexeme/and_spec.rb
index 847d613dba3..6601537a2d3 100644
--- a/spec/lib/gitlab/ci/pipeline/expression/lexeme/and_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/expression/lexeme/and_spec.rb
@@ -3,7 +3,7 @@
require 'fast_spec_helper'
require 'rspec-parameterized'
-describe Gitlab::Ci::Pipeline::Expression::Lexeme::And do
+RSpec.describe Gitlab::Ci::Pipeline::Expression::Lexeme::And do
let(:left) { double('left', evaluate: nil) }
let(:right) { double('right', evaluate: nil) }
diff --git a/spec/lib/gitlab/ci/pipeline/expression/lexeme/equals_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/lexeme/equals_spec.rb
index 0e13681a4cf..2bed47f0a87 100644
--- a/spec/lib/gitlab/ci/pipeline/expression/lexeme/equals_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/expression/lexeme/equals_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Pipeline::Expression::Lexeme::Equals do
+RSpec.describe Gitlab::Ci::Pipeline::Expression::Lexeme::Equals do
let(:left) { double('left') }
let(:right) { double('right') }
diff --git a/spec/lib/gitlab/ci/pipeline/expression/lexeme/matches_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/lexeme/matches_spec.rb
index a527783ffac..efcea0b0e09 100644
--- a/spec/lib/gitlab/ci/pipeline/expression/lexeme/matches_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/expression/lexeme/matches_spec.rb
@@ -3,7 +3,7 @@
require 'fast_spec_helper'
require_dependency 're2'
-describe Gitlab::Ci::Pipeline::Expression::Lexeme::Matches do
+RSpec.describe Gitlab::Ci::Pipeline::Expression::Lexeme::Matches do
let(:left) { double('left') }
let(:right) { double('right') }
diff --git a/spec/lib/gitlab/ci/pipeline/expression/lexeme/not_equals_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/lexeme/not_equals_spec.rb
index a3a48f83b27..a81e1713ef0 100644
--- a/spec/lib/gitlab/ci/pipeline/expression/lexeme/not_equals_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/expression/lexeme/not_equals_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Pipeline::Expression::Lexeme::NotEquals do
+RSpec.describe Gitlab::Ci::Pipeline::Expression::Lexeme::NotEquals do
let(:left) { double('left') }
let(:right) { double('right') }
diff --git a/spec/lib/gitlab/ci/pipeline/expression/lexeme/not_matches_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/lexeme/not_matches_spec.rb
index fb4238ecaf3..f44fe19f86d 100644
--- a/spec/lib/gitlab/ci/pipeline/expression/lexeme/not_matches_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/expression/lexeme/not_matches_spec.rb
@@ -3,7 +3,7 @@
require 'fast_spec_helper'
require_dependency 're2'
-describe Gitlab::Ci::Pipeline::Expression::Lexeme::NotMatches do
+RSpec.describe Gitlab::Ci::Pipeline::Expression::Lexeme::NotMatches do
let(:left) { double('left') }
let(:right) { double('right') }
diff --git a/spec/lib/gitlab/ci/pipeline/expression/lexeme/null_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/lexeme/null_spec.rb
index 7013c6bacbb..49686d1a9bd 100644
--- a/spec/lib/gitlab/ci/pipeline/expression/lexeme/null_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/expression/lexeme/null_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Pipeline::Expression::Lexeme::Null do
+RSpec.describe Gitlab::Ci::Pipeline::Expression::Lexeme::Null do
describe '.build' do
it 'creates a new instance of the token' do
expect(described_class.build('null'))
diff --git a/spec/lib/gitlab/ci/pipeline/expression/lexeme/or_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/lexeme/or_spec.rb
index 15505ebc82b..7fe445975eb 100644
--- a/spec/lib/gitlab/ci/pipeline/expression/lexeme/or_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/expression/lexeme/or_spec.rb
@@ -3,7 +3,7 @@
require 'fast_spec_helper'
require 'rspec-parameterized'
-describe Gitlab::Ci::Pipeline::Expression::Lexeme::Or do
+RSpec.describe Gitlab::Ci::Pipeline::Expression::Lexeme::Or do
let(:left) { double('left', evaluate: nil) }
let(:right) { double('right', evaluate: nil) }
diff --git a/spec/lib/gitlab/ci/pipeline/expression/lexeme/pattern_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/lexeme/pattern_spec.rb
index 2cc25a07417..1a56a91c471 100644
--- a/spec/lib/gitlab/ci/pipeline/expression/lexeme/pattern_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/expression/lexeme/pattern_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Pipeline::Expression::Lexeme::Pattern do
+RSpec.describe Gitlab::Ci::Pipeline::Expression::Lexeme::Pattern do
describe '.build' do
it 'creates a new instance of the token' do
expect(described_class.build('/.*/'))
diff --git a/spec/lib/gitlab/ci/pipeline/expression/lexeme/string_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/lexeme/string_spec.rb
index 2a6b90d127f..c6d0d2534a5 100644
--- a/spec/lib/gitlab/ci/pipeline/expression/lexeme/string_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/expression/lexeme/string_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Pipeline::Expression::Lexeme::String do
+RSpec.describe Gitlab::Ci::Pipeline::Expression::Lexeme::String do
describe '.build' do
it 'creates a new instance of the token' do
expect(described_class.build('"my string"'))
diff --git a/spec/lib/gitlab/ci/pipeline/expression/lexeme/variable_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/lexeme/variable_spec.rb
index 29e26930249..115674edc48 100644
--- a/spec/lib/gitlab/ci/pipeline/expression/lexeme/variable_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/expression/lexeme/variable_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Pipeline::Expression::Lexeme::Variable do
+RSpec.describe Gitlab::Ci::Pipeline::Expression::Lexeme::Variable do
describe '.build' do
it 'creates a new instance of the token' do
expect(described_class.build('$VARIABLE'))
diff --git a/spec/lib/gitlab/ci/pipeline/expression/lexer_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/lexer_spec.rb
index 2b0cee2d6f2..61c6ced4dac 100644
--- a/spec/lib/gitlab/ci/pipeline/expression/lexer_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/expression/lexer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Pipeline::Expression::Lexer do
+RSpec.describe Gitlab::Ci::Pipeline::Expression::Lexer do
let(:token_class) do
Gitlab::Ci::Pipeline::Expression::Token
end
diff --git a/spec/lib/gitlab/ci/pipeline/expression/parser_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/parser_spec.rb
index 10adfa18af6..1704cabfd2e 100644
--- a/spec/lib/gitlab/ci/pipeline/expression/parser_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/expression/parser_spec.rb
@@ -2,7 +2,7 @@
require 'fast_spec_helper'
-describe Gitlab::Ci::Pipeline::Expression::Parser do
+RSpec.describe Gitlab::Ci::Pipeline::Expression::Parser do
describe '#tree' do
context 'when using two operators' do
it 'returns a reverse descent parse tree' do
diff --git a/spec/lib/gitlab/ci/pipeline/expression/statement_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/statement_spec.rb
index 79ee4d07e3a..642d6816030 100644
--- a/spec/lib/gitlab/ci/pipeline/expression/statement_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/expression/statement_spec.rb
@@ -3,7 +3,7 @@
require 'fast_spec_helper'
require 'rspec-parameterized'
-describe Gitlab::Ci::Pipeline::Expression::Statement do
+RSpec.describe Gitlab::Ci::Pipeline::Expression::Statement do
subject do
described_class.new(text, variables)
end
diff --git a/spec/lib/gitlab/ci/pipeline/expression/token_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/token_spec.rb
index aa807cecb72..137e38d2ead 100644
--- a/spec/lib/gitlab/ci/pipeline/expression/token_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/expression/token_spec.rb
@@ -2,7 +2,7 @@
require 'fast_spec_helper'
-describe Gitlab::Ci::Pipeline::Expression::Token do
+RSpec.describe Gitlab::Ci::Pipeline::Expression::Token do
let(:value) { '$VARIABLE' }
let(:lexeme) { Gitlab::Ci::Pipeline::Expression::Lexeme::Variable }
diff --git a/spec/lib/gitlab/ci/pipeline/preloader_spec.rb b/spec/lib/gitlab/ci/pipeline/preloader_spec.rb
index 40dfd893465..ae423fa04f9 100644
--- a/spec/lib/gitlab/ci/pipeline/preloader_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/preloader_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Pipeline::Preloader do
+RSpec.describe Gitlab::Ci::Pipeline::Preloader do
let(:stage) { double(:stage) }
let(:commit) { double(:commit) }
@@ -28,8 +28,9 @@ describe Gitlab::Ci::Pipeline::Preloader do
end
end
- it 'preloads commit authors and number of warnings' do
+ it 'preloads commit authors, number of warnings and ref commits' do
expect(commit).to receive(:lazy_author)
+ expect(pipeline).to receive(:lazy_ref_commit)
expect(pipeline).to receive(:number_of_warnings)
expect(stage).to receive(:number_of_warnings)
@@ -38,6 +39,7 @@ describe Gitlab::Ci::Pipeline::Preloader do
it 'returns original collection' do
allow(commit).to receive(:lazy_author)
+ allow(pipeline).to receive(:lazy_ref_commit)
allow(pipeline).to receive(:number_of_warnings)
allow(stage).to receive(:number_of_warnings)
diff --git a/spec/lib/gitlab/ci/pipeline/seed/build/cache_spec.rb b/spec/lib/gitlab/ci/pipeline/seed/build/cache_spec.rb
index f5b43b5aeab..74c014b6408 100644
--- a/spec/lib/gitlab/ci/pipeline/seed/build/cache_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/seed/build/cache_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Pipeline::Seed::Build::Cache do
+RSpec.describe Gitlab::Ci::Pipeline::Seed::Build::Cache do
let_it_be(:project) { create(:project, :repository) }
let_it_be(:head_sha) { project.repository.head_commit.id }
let_it_be(:pipeline) { create(:ci_pipeline, project: project, sha: head_sha) }
@@ -134,7 +134,7 @@ describe Gitlab::Ci::Pipeline::Seed::Build::Cache do
it_behaves_like 'foo/bar directory key'
end
- context 'with directories ending in slash star', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/222356' do
+ context 'with directories ending in slash star' do
let(:files) { ['foo/bar/*'] }
it_behaves_like 'foo/bar directory key'
diff --git a/spec/lib/gitlab/ci/pipeline/seed/build/resource_group_spec.rb b/spec/lib/gitlab/ci/pipeline/seed/build/resource_group_spec.rb
index bf6985156d3..8fcc242ba5f 100644
--- a/spec/lib/gitlab/ci/pipeline/seed/build/resource_group_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/seed/build/resource_group_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Pipeline::Seed::Build::ResourceGroup do
+RSpec.describe Gitlab::Ci::Pipeline::Seed::Build::ResourceGroup do
let_it_be(:project) { create(:project) }
let(:job) { build(:ci_build, project: project) }
let(:seed) { described_class.new(job, resource_group_key) }
diff --git a/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb b/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb
index 01f65939da7..2dea554fe56 100644
--- a/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Pipeline::Seed::Build do
+RSpec.describe Gitlab::Ci::Pipeline::Seed::Build do
let(:project) { create(:project, :repository) }
let(:head_sha) { project.repository.head_commit.id }
let(:pipeline) { create(:ci_empty_pipeline, project: project, sha: head_sha) }
diff --git a/spec/lib/gitlab/ci/pipeline/seed/deployment_spec.rb b/spec/lib/gitlab/ci/pipeline/seed/deployment_spec.rb
index ceb3cb28bc9..1f38c7aec63 100644
--- a/spec/lib/gitlab/ci/pipeline/seed/deployment_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/seed/deployment_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Pipeline::Seed::Deployment do
+RSpec.describe Gitlab::Ci::Pipeline::Seed::Deployment do
let_it_be(:project, refind: true) { create(:project, :repository) }
let(:pipeline) do
create(:ci_pipeline, project: project,
@@ -102,6 +102,19 @@ describe Gitlab::Ci::Pipeline::Seed::Deployment do
end
end
+ context 'when job has environment attribute with prepare action' do
+ let(:attributes) do
+ {
+ environment: 'production',
+ options: { environment: { name: 'production', action: 'prepare' } }
+ }
+ end
+
+ it 'returns nothing' do
+ is_expected.to be_nil
+ end
+ end
+
context 'when job does not have environment attribute' do
let(:attributes) { { name: 'test' } }
diff --git a/spec/lib/gitlab/ci/pipeline/seed/environment_spec.rb b/spec/lib/gitlab/ci/pipeline/seed/environment_spec.rb
index 4c0464e5e7c..0c8a0de2f34 100644
--- a/spec/lib/gitlab/ci/pipeline/seed/environment_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/seed/environment_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Pipeline::Seed::Environment do
+RSpec.describe Gitlab::Ci::Pipeline::Seed::Environment do
let_it_be(:project) { create(:project) }
let(:job) { build(:ci_build, project: project) }
let(:seed) { described_class.new(job) }
diff --git a/spec/lib/gitlab/ci/pipeline/seed/stage_spec.rb b/spec/lib/gitlab/ci/pipeline/seed/stage_spec.rb
index 875fd457bd0..4b9db9fa6c6 100644
--- a/spec/lib/gitlab/ci/pipeline/seed/stage_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/seed/stage_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Pipeline::Seed::Stage do
+RSpec.describe Gitlab::Ci::Pipeline::Seed::Stage do
let(:project) { create(:project, :repository) }
let(:pipeline) { create(:ci_empty_pipeline, project: project) }
let(:previous_stages) { [] }
diff --git a/spec/lib/gitlab/ci/reports/accessibility_reports_comparer_spec.rb b/spec/lib/gitlab/ci/reports/accessibility_reports_comparer_spec.rb
index 31a330f46b1..240ede790e0 100644
--- a/spec/lib/gitlab/ci/reports/accessibility_reports_comparer_spec.rb
+++ b/spec/lib/gitlab/ci/reports/accessibility_reports_comparer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Reports::AccessibilityReportsComparer do
+RSpec.describe Gitlab::Ci::Reports::AccessibilityReportsComparer do
let(:comparer) { described_class.new(base_reports, head_reports) }
let(:base_reports) { Gitlab::Ci::Reports::AccessibilityReports.new }
let(:head_reports) { Gitlab::Ci::Reports::AccessibilityReports.new }
diff --git a/spec/lib/gitlab/ci/reports/accessibility_reports_spec.rb b/spec/lib/gitlab/ci/reports/accessibility_reports_spec.rb
index 0dc13b464b1..8c35b2a34cf 100644
--- a/spec/lib/gitlab/ci/reports/accessibility_reports_spec.rb
+++ b/spec/lib/gitlab/ci/reports/accessibility_reports_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Reports::AccessibilityReports do
+RSpec.describe Gitlab::Ci::Reports::AccessibilityReports do
let(:accessibility_report) { described_class.new }
let(:url) { 'https://gitlab.com' }
let(:data) do
diff --git a/spec/lib/gitlab/ci/reports/coverage_reports_spec.rb b/spec/lib/gitlab/ci/reports/coverage_reports_spec.rb
index 7cf43ceab32..41ebae863ee 100644
--- a/spec/lib/gitlab/ci/reports/coverage_reports_spec.rb
+++ b/spec/lib/gitlab/ci/reports/coverage_reports_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Reports::CoverageReports do
+RSpec.describe Gitlab::Ci::Reports::CoverageReports do
let(:coverage_report) { described_class.new }
it { expect(coverage_report.files).to eq({}) }
diff --git a/spec/lib/gitlab/ci/reports/terraform_reports_spec.rb b/spec/lib/gitlab/ci/reports/terraform_reports_spec.rb
index bfab30543ed..5e94fe2bb3d 100644
--- a/spec/lib/gitlab/ci/reports/terraform_reports_spec.rb
+++ b/spec/lib/gitlab/ci/reports/terraform_reports_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Reports::TerraformReports do
+RSpec.describe Gitlab::Ci::Reports::TerraformReports do
it 'initializes plans with and empty hash' do
expect(subject.plans).to eq({})
end
diff --git a/spec/lib/gitlab/ci/reports/test_case_spec.rb b/spec/lib/gitlab/ci/reports/test_case_spec.rb
index b5883867983..8882defbd9e 100644
--- a/spec/lib/gitlab/ci/reports/test_case_spec.rb
+++ b/spec/lib/gitlab/ci/reports/test_case_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Reports::TestCase do
+RSpec.describe Gitlab::Ci::Reports::TestCase do
describe '#initialize' do
let(:test_case) { described_class.new(params)}
diff --git a/spec/lib/gitlab/ci/reports/test_report_summary_spec.rb b/spec/lib/gitlab/ci/reports/test_report_summary_spec.rb
new file mode 100644
index 00000000000..70d82851125
--- /dev/null
+++ b/spec/lib/gitlab/ci/reports/test_report_summary_spec.rb
@@ -0,0 +1,90 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Ci::Reports::TestReportSummary do
+ let(:build_report_result_1) { build(:ci_build_report_result) }
+ let(:build_report_result_2) { build(:ci_build_report_result, :with_junit_success) }
+ let(:test_report_summary) { described_class.new([build_report_result_1, build_report_result_2]) }
+
+ describe '#total' do
+ subject { test_report_summary.total }
+
+ context 'when test report summary has several build report results' do
+ it 'returns test suite summary object' do
+ expect(subject).to be_a_kind_of(Gitlab::Ci::Reports::TestSuiteSummary)
+ end
+ end
+ end
+
+ describe '#total_time' do
+ subject { test_report_summary.total_time }
+
+ context 'when test report summary has several build report results' do
+ it 'returns the total' do
+ expect(subject).to eq(0.84)
+ end
+ end
+ end
+
+ describe '#total_count' do
+ subject { test_report_summary.total_count }
+
+ context 'when test report summary has several build report results' do
+ it 'returns the total count' do
+ expect(subject).to eq(4)
+ end
+ end
+ end
+
+ describe '#success_count' do
+ subject { test_report_summary.success_count }
+
+ context 'when test suite summary has several build report results' do
+ it 'returns the total success' do
+ expect(subject).to eq(2)
+ end
+ end
+ end
+
+ describe '#failed_count' do
+ subject { test_report_summary.failed_count }
+
+ context 'when test suite summary has several build report results' do
+ it 'returns the total failed' do
+ expect(subject).to eq(0)
+ end
+ end
+ end
+
+ describe '#error_count' do
+ subject { test_report_summary.error_count }
+
+ context 'when test suite summary has several build report results' do
+ it 'returns the total errored' do
+ expect(subject).to eq(2)
+ end
+ end
+ end
+
+ describe '#skipped_count' do
+ subject { test_report_summary.skipped_count }
+
+ context 'when test suite summary has several build report results' do
+ it 'returns the total skipped' do
+ expect(subject).to eq(0)
+ end
+ end
+ end
+
+ describe '#test_suites' do
+ subject { test_report_summary.test_suites }
+
+ context 'when test report summary has several build report results' do
+ it 'returns test suites grouped by name' do
+ expect(subject.keys).to eq(["rspec"])
+ expect(subject.keys.size).to eq(1)
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/reports/test_reports_comparer_spec.rb b/spec/lib/gitlab/ci/reports/test_reports_comparer_spec.rb
index d731afe1fff..3483dddca3a 100644
--- a/spec/lib/gitlab/ci/reports/test_reports_comparer_spec.rb
+++ b/spec/lib/gitlab/ci/reports/test_reports_comparer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Reports::TestReportsComparer do
+RSpec.describe Gitlab::Ci::Reports::TestReportsComparer do
include TestReportsHelper
let(:comparer) { described_class.new(base_reports, head_reports) }
diff --git a/spec/lib/gitlab/ci/reports/test_reports_spec.rb b/spec/lib/gitlab/ci/reports/test_reports_spec.rb
index e51728496e1..502859852f2 100644
--- a/spec/lib/gitlab/ci/reports/test_reports_spec.rb
+++ b/spec/lib/gitlab/ci/reports/test_reports_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Reports::TestReports do
+RSpec.describe Gitlab::Ci::Reports::TestReports do
include TestReportsHelper
let(:test_reports) { described_class.new }
diff --git a/spec/lib/gitlab/ci/reports/test_suite_comparer_spec.rb b/spec/lib/gitlab/ci/reports/test_suite_comparer_spec.rb
index 2d2179a690b..6bb6771678a 100644
--- a/spec/lib/gitlab/ci/reports/test_suite_comparer_spec.rb
+++ b/spec/lib/gitlab/ci/reports/test_suite_comparer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Reports::TestSuiteComparer do
+RSpec.describe Gitlab::Ci::Reports::TestSuiteComparer do
include TestReportsHelper
let(:comparer) { described_class.new(name, base_suite, head_suite) }
diff --git a/spec/lib/gitlab/ci/reports/test_suite_spec.rb b/spec/lib/gitlab/ci/reports/test_suite_spec.rb
index e0b2593353a..c4c4d2c3704 100644
--- a/spec/lib/gitlab/ci/reports/test_suite_spec.rb
+++ b/spec/lib/gitlab/ci/reports/test_suite_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Reports::TestSuite do
+RSpec.describe Gitlab::Ci::Reports::TestSuite do
include TestReportsHelper
let(:test_suite) { described_class.new('Rspec') }
@@ -139,6 +139,41 @@ describe Gitlab::Ci::Reports::TestSuite do
end
end
+ describe '#+' do
+ let(:test_suite_2) { described_class.new('Rspec') }
+
+ subject { test_suite + test_suite_2 }
+
+ context 'when adding multiple suites together' do
+ before do
+ test_suite.add_test_case(test_case_success)
+ test_suite.add_test_case(test_case_failed)
+ end
+
+ it 'returns a new test suite' do
+ expect(subject).to be_an_instance_of(described_class)
+ end
+
+ it 'returns the suite name' do
+ expect(subject.name).to eq('Rspec')
+ end
+
+ it 'returns the sum for total_time' do
+ expect(subject.total_time).to eq(3.33)
+ end
+
+ it 'merges tests cases hash', :aggregate_failures do
+ test_suite_2.add_test_case(create_test_case_java_success)
+
+ failed_keys = test_suite.test_cases['failed'].keys
+ success_keys = test_suite.test_cases['success'].keys + test_suite_2.test_cases['success'].keys
+
+ expect(subject.test_cases['failed'].keys).to contain_exactly(*failed_keys)
+ expect(subject.test_cases['success'].keys).to contain_exactly(*success_keys)
+ end
+ end
+ end
+
Gitlab::Ci::Reports::TestCase::STATUS_TYPES.each do |status_type|
describe "##{status_type}" do
subject { test_suite.public_send("#{status_type}") }
diff --git a/spec/lib/gitlab/ci/reports/test_suite_summary_spec.rb b/spec/lib/gitlab/ci/reports/test_suite_summary_spec.rb
new file mode 100644
index 00000000000..12c96acdcf3
--- /dev/null
+++ b/spec/lib/gitlab/ci/reports/test_suite_summary_spec.rb
@@ -0,0 +1,89 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Ci::Reports::TestSuiteSummary do
+ let(:build_report_result_1) { build(:ci_build_report_result) }
+ let(:build_report_result_2) { build(:ci_build_report_result, :with_junit_success) }
+ let(:test_suite_summary) { described_class.new([build_report_result_1, build_report_result_2]) }
+
+ describe '#name' do
+ subject { test_suite_summary.name }
+
+ context 'when test suite summary has several build report results' do
+ it 'returns the suite name' do
+ expect(subject).to eq("rspec")
+ end
+ end
+ end
+
+ describe '#build_ids' do
+ subject { test_suite_summary.build_ids }
+
+ context 'when test suite summary has several build report results' do
+ it 'returns the build ids' do
+ expect(subject).to contain_exactly(build_report_result_1.build_id, build_report_result_2.build_id)
+ end
+ end
+ end
+
+ describe '#total_time' do
+ subject { test_suite_summary.total_time }
+
+ context 'when test suite summary has several build report results' do
+ it 'returns the total time' do
+ expect(subject).to eq(0.84)
+ end
+ end
+ end
+
+ describe '#success_count' do
+ subject { test_suite_summary.success_count }
+
+ context 'when test suite summary has several build report results' do
+ it 'returns the total success' do
+ expect(subject).to eq(2)
+ end
+ end
+ end
+
+ describe '#failed_count' do
+ subject { test_suite_summary.failed_count }
+
+ context 'when test suite summary has several build report results' do
+ it 'returns the total failed' do
+ expect(subject).to eq(0)
+ end
+ end
+ end
+
+ describe '#error_count' do
+ subject { test_suite_summary.error_count }
+
+ context 'when test suite summary has several build report results' do
+ it 'returns the total errored' do
+ expect(subject).to eq(2)
+ end
+ end
+ end
+
+ describe '#skipped_count' do
+ subject { test_suite_summary.skipped_count }
+
+ context 'when test suite summary has several build report results' do
+ it 'returns the total skipped' do
+ expect(subject).to eq(0)
+ end
+ end
+ end
+
+ describe '#total_count' do
+ subject { test_suite_summary.total_count }
+
+ context 'when test suite summary has several build report results' do
+ it 'returns the total count' do
+ expect(subject).to eq(4)
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/status/bridge/factory_spec.rb b/spec/lib/gitlab/ci/status/bridge/factory_spec.rb
index 6c67864855d..021b777a0ff 100644
--- a/spec/lib/gitlab/ci/status/bridge/factory_spec.rb
+++ b/spec/lib/gitlab/ci/status/bridge/factory_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Status::Bridge::Factory do
+RSpec.describe Gitlab::Ci::Status::Bridge::Factory do
let(:user) { create(:user) }
let(:project) { bridge.project }
let(:status) { factory.fabricate! }
diff --git a/spec/lib/gitlab/ci/status/build/action_spec.rb b/spec/lib/gitlab/ci/status/build/action_spec.rb
index 3aae7e18d6d..de237183feb 100644
--- a/spec/lib/gitlab/ci/status/build/action_spec.rb
+++ b/spec/lib/gitlab/ci/status/build/action_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Status::Build::Action do
+RSpec.describe Gitlab::Ci::Status::Build::Action do
let(:status) { double('core status') }
let(:user) { double('user') }
diff --git a/spec/lib/gitlab/ci/status/build/cancelable_spec.rb b/spec/lib/gitlab/ci/status/build/cancelable_spec.rb
index 3841dae91c7..95920518793 100644
--- a/spec/lib/gitlab/ci/status/build/cancelable_spec.rb
+++ b/spec/lib/gitlab/ci/status/build/cancelable_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Status::Build::Cancelable do
+RSpec.describe Gitlab::Ci::Status::Build::Cancelable do
let(:status) { double('core status') }
let(:user) { double('user') }
diff --git a/spec/lib/gitlab/ci/status/build/canceled_spec.rb b/spec/lib/gitlab/ci/status/build/canceled_spec.rb
index 4b43c78f1a7..e30a2211c8f 100644
--- a/spec/lib/gitlab/ci/status/build/canceled_spec.rb
+++ b/spec/lib/gitlab/ci/status/build/canceled_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Status::Build::Canceled do
+RSpec.describe Gitlab::Ci::Status::Build::Canceled do
let(:user) { create(:user) }
subject do
diff --git a/spec/lib/gitlab/ci/status/build/common_spec.rb b/spec/lib/gitlab/ci/status/build/common_spec.rb
index 5114540708f..924ee5ee1a4 100644
--- a/spec/lib/gitlab/ci/status/build/common_spec.rb
+++ b/spec/lib/gitlab/ci/status/build/common_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Status::Build::Common do
+RSpec.describe Gitlab::Ci::Status::Build::Common do
let(:user) { create(:user) }
let(:build) { create(:ci_build) }
let(:project) { build.project }
diff --git a/spec/lib/gitlab/ci/status/build/created_spec.rb b/spec/lib/gitlab/ci/status/build/created_spec.rb
index 6e3aa442810..49468674140 100644
--- a/spec/lib/gitlab/ci/status/build/created_spec.rb
+++ b/spec/lib/gitlab/ci/status/build/created_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Status::Build::Created do
+RSpec.describe Gitlab::Ci::Status::Build::Created do
let(:user) { create(:user) }
subject do
diff --git a/spec/lib/gitlab/ci/status/build/erased_spec.rb b/spec/lib/gitlab/ci/status/build/erased_spec.rb
index af9c296da0c..75467cc6b31 100644
--- a/spec/lib/gitlab/ci/status/build/erased_spec.rb
+++ b/spec/lib/gitlab/ci/status/build/erased_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Status::Build::Erased do
+RSpec.describe Gitlab::Ci::Status::Build::Erased do
let(:user) { create(:user) }
subject do
diff --git a/spec/lib/gitlab/ci/status/build/factory_spec.rb b/spec/lib/gitlab/ci/status/build/factory_spec.rb
index 11be17bfc53..21eca97331e 100644
--- a/spec/lib/gitlab/ci/status/build/factory_spec.rb
+++ b/spec/lib/gitlab/ci/status/build/factory_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Status::Build::Factory do
+RSpec.describe Gitlab::Ci::Status::Build::Factory do
let(:user) { create(:user) }
let(:project) { build.project }
let(:status) { factory.fabricate! }
diff --git a/spec/lib/gitlab/ci/status/build/failed_allowed_spec.rb b/spec/lib/gitlab/ci/status/build/failed_allowed_spec.rb
index 01500689619..0ff7c3796c0 100644
--- a/spec/lib/gitlab/ci/status/build/failed_allowed_spec.rb
+++ b/spec/lib/gitlab/ci/status/build/failed_allowed_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Status::Build::FailedAllowed do
+RSpec.describe Gitlab::Ci::Status::Build::FailedAllowed do
let(:status) { double('core status') }
let(:user) { double('user') }
let(:build) { create(:ci_build, :failed, :allowed_to_fail) }
diff --git a/spec/lib/gitlab/ci/status/build/failed_spec.rb b/spec/lib/gitlab/ci/status/build/failed_spec.rb
index 78f5214ca81..1d5069fe180 100644
--- a/spec/lib/gitlab/ci/status/build/failed_spec.rb
+++ b/spec/lib/gitlab/ci/status/build/failed_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Status::Build::Failed do
+RSpec.describe Gitlab::Ci::Status::Build::Failed do
let(:build) { create(:ci_build, :script_failure) }
let(:status) { double('core status') }
let(:user) { double('user') }
diff --git a/spec/lib/gitlab/ci/status/build/manual_spec.rb b/spec/lib/gitlab/ci/status/build/manual_spec.rb
index bffe2c10d12..78193055139 100644
--- a/spec/lib/gitlab/ci/status/build/manual_spec.rb
+++ b/spec/lib/gitlab/ci/status/build/manual_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Status::Build::Manual do
+RSpec.describe Gitlab::Ci::Status::Build::Manual do
let(:user) { create(:user) }
subject do
diff --git a/spec/lib/gitlab/ci/status/build/pending_spec.rb b/spec/lib/gitlab/ci/status/build/pending_spec.rb
index 64d57954c15..7b695d33877 100644
--- a/spec/lib/gitlab/ci/status/build/pending_spec.rb
+++ b/spec/lib/gitlab/ci/status/build/pending_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Status::Build::Pending do
+RSpec.describe Gitlab::Ci::Status::Build::Pending do
let(:user) { create(:user) }
subject do
diff --git a/spec/lib/gitlab/ci/status/build/play_spec.rb b/spec/lib/gitlab/ci/status/build/play_spec.rb
index bb12a900b55..bb406623d2f 100644
--- a/spec/lib/gitlab/ci/status/build/play_spec.rb
+++ b/spec/lib/gitlab/ci/status/build/play_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Status::Build::Play do
+RSpec.describe Gitlab::Ci::Status::Build::Play do
let(:user) { create(:user) }
let(:project) { create(:project, :stubbed_repository) }
let(:build) { create(:ci_build, :manual, project: project) }
diff --git a/spec/lib/gitlab/ci/status/build/preparing_spec.rb b/spec/lib/gitlab/ci/status/build/preparing_spec.rb
index 4d8945845ba..5bb18ac3799 100644
--- a/spec/lib/gitlab/ci/status/build/preparing_spec.rb
+++ b/spec/lib/gitlab/ci/status/build/preparing_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Status::Build::Preparing do
+RSpec.describe Gitlab::Ci::Status::Build::Preparing do
subject do
described_class.new(double('subject'))
end
diff --git a/spec/lib/gitlab/ci/status/build/retried_spec.rb b/spec/lib/gitlab/ci/status/build/retried_spec.rb
index fce497d40a1..522228f0a1d 100644
--- a/spec/lib/gitlab/ci/status/build/retried_spec.rb
+++ b/spec/lib/gitlab/ci/status/build/retried_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Status::Build::Retried do
+RSpec.describe Gitlab::Ci::Status::Build::Retried do
let(:build) { create(:ci_build, :retried) }
let(:status) { double('core status') }
let(:user) { double('user') }
diff --git a/spec/lib/gitlab/ci/status/build/retryable_spec.rb b/spec/lib/gitlab/ci/status/build/retryable_spec.rb
index 5b0ae315927..cb1cf85169d 100644
--- a/spec/lib/gitlab/ci/status/build/retryable_spec.rb
+++ b/spec/lib/gitlab/ci/status/build/retryable_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Status::Build::Retryable do
+RSpec.describe Gitlab::Ci::Status::Build::Retryable do
let(:status) { double('core status') }
let(:user) { double('user') }
diff --git a/spec/lib/gitlab/ci/status/build/scheduled_spec.rb b/spec/lib/gitlab/ci/status/build/scheduled_spec.rb
index 8f87da10815..b0cd1ac4dc5 100644
--- a/spec/lib/gitlab/ci/status/build/scheduled_spec.rb
+++ b/spec/lib/gitlab/ci/status/build/scheduled_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Status::Build::Scheduled do
+RSpec.describe Gitlab::Ci::Status::Build::Scheduled do
let(:user) { create(:user) }
let(:project) { create(:project, :stubbed_repository) }
let(:build) { create(:ci_build, :scheduled, project: project) }
diff --git a/spec/lib/gitlab/ci/status/build/skipped_spec.rb b/spec/lib/gitlab/ci/status/build/skipped_spec.rb
index 7ce5142da78..0b998a52a57 100644
--- a/spec/lib/gitlab/ci/status/build/skipped_spec.rb
+++ b/spec/lib/gitlab/ci/status/build/skipped_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Status::Build::Skipped do
+RSpec.describe Gitlab::Ci::Status::Build::Skipped do
let(:user) { create(:user) }
subject do
diff --git a/spec/lib/gitlab/ci/status/build/stop_spec.rb b/spec/lib/gitlab/ci/status/build/stop_spec.rb
index d3e98400a53..7376afe0e83 100644
--- a/spec/lib/gitlab/ci/status/build/stop_spec.rb
+++ b/spec/lib/gitlab/ci/status/build/stop_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Status::Build::Stop do
+RSpec.describe Gitlab::Ci::Status::Build::Stop do
let(:status) { double('core status') }
let(:user) { double('user') }
diff --git a/spec/lib/gitlab/ci/status/build/unschedule_spec.rb b/spec/lib/gitlab/ci/status/build/unschedule_spec.rb
index c18fc3252b4..74002c7d0d7 100644
--- a/spec/lib/gitlab/ci/status/build/unschedule_spec.rb
+++ b/spec/lib/gitlab/ci/status/build/unschedule_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Status::Build::Unschedule do
+RSpec.describe Gitlab::Ci::Status::Build::Unschedule do
let(:status) { double('core status') }
let(:user) { double('user') }
diff --git a/spec/lib/gitlab/ci/status/canceled_spec.rb b/spec/lib/gitlab/ci/status/canceled_spec.rb
index 6cfcea4fdde..a35efae5c57 100644
--- a/spec/lib/gitlab/ci/status/canceled_spec.rb
+++ b/spec/lib/gitlab/ci/status/canceled_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Status::Canceled do
+RSpec.describe Gitlab::Ci::Status::Canceled do
subject do
described_class.new(double('subject'), double('user'))
end
diff --git a/spec/lib/gitlab/ci/status/composite_spec.rb b/spec/lib/gitlab/ci/status/composite_spec.rb
index 8a226b382b0..47bbc4169b6 100644
--- a/spec/lib/gitlab/ci/status/composite_spec.rb
+++ b/spec/lib/gitlab/ci/status/composite_spec.rb
@@ -2,15 +2,15 @@
require 'spec_helper'
-describe Gitlab::Ci::Status::Composite do
+RSpec.describe Gitlab::Ci::Status::Composite do
let_it_be(:pipeline) { create(:ci_pipeline) }
before_all do
- @statuses = HasStatus::STATUSES_ENUM.map do |status, idx|
+ @statuses = Ci::HasStatus::STATUSES_ENUM.map do |status, idx|
[status, create(:ci_build, pipeline: pipeline, status: status, importing: true)]
end.to_h
- @statuses_with_allow_failure = HasStatus::STATUSES_ENUM.map do |status, idx|
+ @statuses_with_allow_failure = Ci::HasStatus::STATUSES_ENUM.map do |status, idx|
[status, create(:ci_build, pipeline: pipeline, status: status, allow_failure: true, importing: true)]
end.to_h
end
@@ -26,7 +26,7 @@ describe Gitlab::Ci::Status::Composite do
end
shared_examples 'validate all combinations' do |perms|
- HasStatus::STATUSES_ENUM.keys.combination(perms).each do |statuses|
+ Ci::HasStatus::STATUSES_ENUM.keys.combination(perms).each do |statuses|
context "with #{statuses.join(",")}" do
it_behaves_like 'compares composite with SQL status' do
let(:all_statuses) do
@@ -38,7 +38,7 @@ describe Gitlab::Ci::Status::Composite do
end
end
- HasStatus::STATUSES_ENUM.each do |allow_failure_status, _|
+ Ci::HasStatus::STATUSES_ENUM.each do |allow_failure_status, _|
context "and allow_failure #{allow_failure_status}" do
it_behaves_like 'compares composite with SQL status' do
let(:all_statuses) do
diff --git a/spec/lib/gitlab/ci/status/created_spec.rb b/spec/lib/gitlab/ci/status/created_spec.rb
index aeb41e9cfc3..1ddced923f6 100644
--- a/spec/lib/gitlab/ci/status/created_spec.rb
+++ b/spec/lib/gitlab/ci/status/created_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Status::Created do
+RSpec.describe Gitlab::Ci::Status::Created do
subject do
described_class.new(double('subject'), double('user'))
end
diff --git a/spec/lib/gitlab/ci/status/extended_spec.rb b/spec/lib/gitlab/ci/status/extended_spec.rb
index 8accfc4a2f9..3e1004754ba 100644
--- a/spec/lib/gitlab/ci/status/extended_spec.rb
+++ b/spec/lib/gitlab/ci/status/extended_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Status::Extended do
+RSpec.describe Gitlab::Ci::Status::Extended do
it 'requires subclass to implement matcher' do
expect { described_class.matches?(double, double) }
.to raise_error(NotImplementedError)
diff --git a/spec/lib/gitlab/ci/status/external/common_spec.rb b/spec/lib/gitlab/ci/status/external/common_spec.rb
index 983522fa2d6..6de08ce9281 100644
--- a/spec/lib/gitlab/ci/status/external/common_spec.rb
+++ b/spec/lib/gitlab/ci/status/external/common_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Status::External::Common do
+RSpec.describe Gitlab::Ci::Status::External::Common do
let(:user) { create(:user) }
let(:project) { external_status.project }
let(:external_target_url) { 'http://example.gitlab.com/status' }
diff --git a/spec/lib/gitlab/ci/status/external/factory_spec.rb b/spec/lib/gitlab/ci/status/external/factory_spec.rb
index 9c11e42fc5a..a31a11cd14a 100644
--- a/spec/lib/gitlab/ci/status/external/factory_spec.rb
+++ b/spec/lib/gitlab/ci/status/external/factory_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Status::External::Factory do
+RSpec.describe Gitlab::Ci::Status::External::Factory do
let(:user) { create(:user) }
let(:project) { resource.project }
let(:status) { factory.fabricate! }
@@ -14,7 +14,7 @@ describe Gitlab::Ci::Status::External::Factory do
end
context 'when external status has a simple core status' do
- HasStatus::AVAILABLE_STATUSES.each do |simple_status|
+ Ci::HasStatus::AVAILABLE_STATUSES.each do |simple_status|
context "when core status is #{simple_status}" do
let(:resource) do
create(:generic_commit_status, status: simple_status,
diff --git a/spec/lib/gitlab/ci/status/factory_spec.rb b/spec/lib/gitlab/ci/status/factory_spec.rb
index 219eb53d9df..641cb0183d3 100644
--- a/spec/lib/gitlab/ci/status/factory_spec.rb
+++ b/spec/lib/gitlab/ci/status/factory_spec.rb
@@ -2,13 +2,13 @@
require 'spec_helper'
-describe Gitlab::Ci::Status::Factory do
+RSpec.describe Gitlab::Ci::Status::Factory do
let(:user) { create(:user) }
let(:fabricated_status) { factory.fabricate! }
let(:factory) { described_class.new(resource, user) }
context 'when object has a core status' do
- HasStatus::AVAILABLE_STATUSES.each do |simple_status|
+ Ci::HasStatus::AVAILABLE_STATUSES.each do |simple_status|
context "when simple core status is #{simple_status}" do
let(:resource) { double('resource', status: simple_status) }
diff --git a/spec/lib/gitlab/ci/status/failed_spec.rb b/spec/lib/gitlab/ci/status/failed_spec.rb
index 5c7393fc8cf..e8bd728b740 100644
--- a/spec/lib/gitlab/ci/status/failed_spec.rb
+++ b/spec/lib/gitlab/ci/status/failed_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Status::Failed do
+RSpec.describe Gitlab::Ci::Status::Failed do
subject do
described_class.new(double('subject'), double('user'))
end
diff --git a/spec/lib/gitlab/ci/status/group/common_spec.rb b/spec/lib/gitlab/ci/status/group/common_spec.rb
index 35fff30ea9d..2bfd816e45d 100644
--- a/spec/lib/gitlab/ci/status/group/common_spec.rb
+++ b/spec/lib/gitlab/ci/status/group/common_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Status::Group::Common do
+RSpec.describe Gitlab::Ci::Status::Group::Common do
subject do
Gitlab::Ci::Status::Core.new(double, double)
.extend(described_class)
diff --git a/spec/lib/gitlab/ci/status/group/factory_spec.rb b/spec/lib/gitlab/ci/status/group/factory_spec.rb
index be76a1d5a65..6267b26aa78 100644
--- a/spec/lib/gitlab/ci/status/group/factory_spec.rb
+++ b/spec/lib/gitlab/ci/status/group/factory_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Status::Group::Factory do
+RSpec.describe Gitlab::Ci::Status::Group::Factory do
it 'inherits from the core factory' do
expect(described_class)
.to be < Gitlab::Ci::Status::Factory
diff --git a/spec/lib/gitlab/ci/status/manual_spec.rb b/spec/lib/gitlab/ci/status/manual_spec.rb
index 0839452ec22..a9203438898 100644
--- a/spec/lib/gitlab/ci/status/manual_spec.rb
+++ b/spec/lib/gitlab/ci/status/manual_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Status::Manual do
+RSpec.describe Gitlab::Ci::Status::Manual do
subject do
described_class.new(double('subject'), double('user'))
end
diff --git a/spec/lib/gitlab/ci/status/pending_spec.rb b/spec/lib/gitlab/ci/status/pending_spec.rb
index 5f830e5bb56..0e47b19d9c1 100644
--- a/spec/lib/gitlab/ci/status/pending_spec.rb
+++ b/spec/lib/gitlab/ci/status/pending_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Status::Pending do
+RSpec.describe Gitlab::Ci::Status::Pending do
subject do
described_class.new(double('subject'), double('user'))
end
diff --git a/spec/lib/gitlab/ci/status/pipeline/blocked_spec.rb b/spec/lib/gitlab/ci/status/pipeline/blocked_spec.rb
index 664915ba552..8fd974972e4 100644
--- a/spec/lib/gitlab/ci/status/pipeline/blocked_spec.rb
+++ b/spec/lib/gitlab/ci/status/pipeline/blocked_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Status::Pipeline::Blocked do
+RSpec.describe Gitlab::Ci::Status::Pipeline::Blocked do
let(:pipeline) { double('pipeline') }
subject do
diff --git a/spec/lib/gitlab/ci/status/pipeline/common_spec.rb b/spec/lib/gitlab/ci/status/pipeline/common_spec.rb
index d3251d138b8..575acc48a61 100644
--- a/spec/lib/gitlab/ci/status/pipeline/common_spec.rb
+++ b/spec/lib/gitlab/ci/status/pipeline/common_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Status::Pipeline::Common do
+RSpec.describe Gitlab::Ci::Status::Pipeline::Common do
let(:user) { create(:user) }
let(:project) { create(:project, :private) }
let(:pipeline) { create(:ci_pipeline, project: project) }
diff --git a/spec/lib/gitlab/ci/status/pipeline/delayed_spec.rb b/spec/lib/gitlab/ci/status/pipeline/delayed_spec.rb
index aba403de712..1302c2069ff 100644
--- a/spec/lib/gitlab/ci/status/pipeline/delayed_spec.rb
+++ b/spec/lib/gitlab/ci/status/pipeline/delayed_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Status::Pipeline::Delayed do
+RSpec.describe Gitlab::Ci::Status::Pipeline::Delayed do
let(:pipeline) { double('pipeline') }
subject do
diff --git a/spec/lib/gitlab/ci/status/pipeline/factory_spec.rb b/spec/lib/gitlab/ci/status/pipeline/factory_spec.rb
index 838154759cb..a6f23180860 100644
--- a/spec/lib/gitlab/ci/status/pipeline/factory_spec.rb
+++ b/spec/lib/gitlab/ci/status/pipeline/factory_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Status::Pipeline::Factory do
+RSpec.describe Gitlab::Ci::Status::Pipeline::Factory do
let(:user) { create(:user) }
let(:project) { pipeline.project }
let(:status) { factory.fabricate! }
@@ -13,7 +13,7 @@ describe Gitlab::Ci::Status::Pipeline::Factory do
end
context 'when pipeline has a core status' do
- (HasStatus::AVAILABLE_STATUSES - HasStatus::BLOCKED_STATUS).each do |simple_status|
+ (Ci::HasStatus::AVAILABLE_STATUSES - Ci::HasStatus::BLOCKED_STATUS).each do |simple_status|
context "when core status is #{simple_status}" do
let(:pipeline) { create(:ci_pipeline, status: simple_status) }
diff --git a/spec/lib/gitlab/ci/status/preparing_spec.rb b/spec/lib/gitlab/ci/status/preparing_spec.rb
index 33f6bab8d65..6d33eb77560 100644
--- a/spec/lib/gitlab/ci/status/preparing_spec.rb
+++ b/spec/lib/gitlab/ci/status/preparing_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Status::Preparing do
+RSpec.describe Gitlab::Ci::Status::Preparing do
subject do
described_class.new(double('subject'), nil)
end
diff --git a/spec/lib/gitlab/ci/status/running_spec.rb b/spec/lib/gitlab/ci/status/running_spec.rb
index 75ff58c5c98..fbc7bfd81b3 100644
--- a/spec/lib/gitlab/ci/status/running_spec.rb
+++ b/spec/lib/gitlab/ci/status/running_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Status::Running do
+RSpec.describe Gitlab::Ci::Status::Running do
subject do
described_class.new(double('subject'), double('user'))
end
diff --git a/spec/lib/gitlab/ci/status/scheduled_spec.rb b/spec/lib/gitlab/ci/status/scheduled_spec.rb
index a0374e1a87b..4a1dae937ca 100644
--- a/spec/lib/gitlab/ci/status/scheduled_spec.rb
+++ b/spec/lib/gitlab/ci/status/scheduled_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Status::Scheduled do
+RSpec.describe Gitlab::Ci::Status::Scheduled do
subject do
described_class.new(double('subject'), double('user'))
end
diff --git a/spec/lib/gitlab/ci/status/skipped_spec.rb b/spec/lib/gitlab/ci/status/skipped_spec.rb
index 7f68d4a2fa9..f402bbe5221 100644
--- a/spec/lib/gitlab/ci/status/skipped_spec.rb
+++ b/spec/lib/gitlab/ci/status/skipped_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Status::Skipped do
+RSpec.describe Gitlab::Ci::Status::Skipped do
subject do
described_class.new(double('subject'), double('user'))
end
diff --git a/spec/lib/gitlab/ci/status/stage/common_spec.rb b/spec/lib/gitlab/ci/status/stage/common_spec.rb
index 26ff0e901fd..bbd2ce6c83b 100644
--- a/spec/lib/gitlab/ci/status/stage/common_spec.rb
+++ b/spec/lib/gitlab/ci/status/stage/common_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Status::Stage::Common do
+RSpec.describe Gitlab::Ci::Status::Stage::Common do
let(:user) { create(:user) }
let(:project) { create(:project) }
let(:pipeline) { create(:ci_empty_pipeline, project: project) }
diff --git a/spec/lib/gitlab/ci/status/stage/factory_spec.rb b/spec/lib/gitlab/ci/status/stage/factory_spec.rb
index 317756ea13c..e0f5531f370 100644
--- a/spec/lib/gitlab/ci/status/stage/factory_spec.rb
+++ b/spec/lib/gitlab/ci/status/stage/factory_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Status::Stage::Factory do
+RSpec.describe Gitlab::Ci::Status::Stage::Factory do
let(:user) { create(:user) }
let(:project) { create(:project) }
let(:pipeline) { create(:ci_empty_pipeline, project: project) }
@@ -24,7 +24,7 @@ describe Gitlab::Ci::Status::Stage::Factory do
end
context 'when stage has a core status' do
- (HasStatus::AVAILABLE_STATUSES - %w(manual skipped scheduled)).each do |core_status|
+ (Ci::HasStatus::AVAILABLE_STATUSES - %w(manual skipped scheduled)).each do |core_status|
context "when core status is #{core_status}" do
before do
create(:ci_build, pipeline: pipeline, stage: 'test', status: core_status)
@@ -68,7 +68,7 @@ describe Gitlab::Ci::Status::Stage::Factory do
end
context 'when stage has manual builds' do
- (HasStatus::BLOCKED_STATUS + ['skipped']).each do |core_status|
+ (Ci::HasStatus::BLOCKED_STATUS + ['skipped']).each do |core_status|
context "when status is #{core_status}" do
before do
create(:ci_build, pipeline: pipeline, stage: 'test', status: core_status)
diff --git a/spec/lib/gitlab/ci/status/stage/play_manual_spec.rb b/spec/lib/gitlab/ci/status/stage/play_manual_spec.rb
index bdcbfed918f..25b79ff2099 100644
--- a/spec/lib/gitlab/ci/status/stage/play_manual_spec.rb
+++ b/spec/lib/gitlab/ci/status/stage/play_manual_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Status::Stage::PlayManual do
+RSpec.describe Gitlab::Ci::Status::Stage::PlayManual do
let(:stage) { double('stage') }
let(:play_manual) { described_class.new(stage) }
diff --git a/spec/lib/gitlab/ci/status/success_spec.rb b/spec/lib/gitlab/ci/status/success_spec.rb
index d4b3a9f12cc..2d1c50448d4 100644
--- a/spec/lib/gitlab/ci/status/success_spec.rb
+++ b/spec/lib/gitlab/ci/status/success_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Status::Success do
+RSpec.describe Gitlab::Ci::Status::Success do
subject do
described_class.new(double('subject'), double('user'))
end
diff --git a/spec/lib/gitlab/ci/status/success_warning_spec.rb b/spec/lib/gitlab/ci/status/success_warning_spec.rb
index af952011e21..86b826ad272 100644
--- a/spec/lib/gitlab/ci/status/success_warning_spec.rb
+++ b/spec/lib/gitlab/ci/status/success_warning_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Status::SuccessWarning do
+RSpec.describe Gitlab::Ci::Status::SuccessWarning do
let(:status) { double('status') }
subject do
diff --git a/spec/lib/gitlab/ci/status/waiting_for_resource_spec.rb b/spec/lib/gitlab/ci/status/waiting_for_resource_spec.rb
index ed00dac8560..de18198c6c2 100644
--- a/spec/lib/gitlab/ci/status/waiting_for_resource_spec.rb
+++ b/spec/lib/gitlab/ci/status/waiting_for_resource_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Status::WaitingForResource do
+RSpec.describe Gitlab::Ci::Status::WaitingForResource do
subject do
described_class.new(double('subject'), double('user'))
end
diff --git a/spec/lib/gitlab/ci/templates/Jobs/browser_performance_testing_gitlab_ci_yaml_spec.rb b/spec/lib/gitlab/ci/templates/Jobs/browser_performance_testing_gitlab_ci_yaml_spec.rb
deleted file mode 100644
index 54c3500b0a0..00000000000
--- a/spec/lib/gitlab/ci/templates/Jobs/browser_performance_testing_gitlab_ci_yaml_spec.rb
+++ /dev/null
@@ -1,85 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-describe 'Jobs/Browser-Performance-Testing.gitlab-ci.yml' do
- subject(:template) do
- <<~YAML
- stages:
- - test
- - performance
-
- include:
- - template: 'Jobs/Browser-Performance-Testing.gitlab-ci.yml'
-
- placeholder:
- script:
- - keep pipeline validator happy by having a job when stages are intentionally empty
- YAML
- end
-
- describe 'the created pipeline' do
- let(:user) { create(:admin) }
- let(:project) do
- create(:project, :repository, variables: [
- build(:ci_variable, key: 'CI_KUBERNETES_ACTIVE', value: 'true')
- ])
- end
-
- let(:default_branch) { 'master' }
- let(:pipeline_ref) { default_branch }
- let(:service) { Ci::CreatePipelineService.new(project, user, ref: pipeline_ref) }
- let(:pipeline) { service.execute!(:push) }
- let(:build_names) { pipeline.builds.pluck(:name) }
-
- before do
- stub_ci_pipeline_yaml_file(template)
-
- allow_any_instance_of(Ci::BuildScheduleWorker).to receive(:perform).and_return(true)
- allow(project).to receive(:default_branch).and_return(default_branch)
- end
-
- it 'has no errors' do
- expect(pipeline.errors).to be_empty
- end
-
- shared_examples_for 'performance job on tag or branch' do
- it 'by default' do
- expect(build_names).to include('performance')
- end
-
- it 'when PERFORMANCE_DISABLED' do
- create(:ci_variable, project: project, key: 'PERFORMANCE_DISABLED', value: '1')
-
- expect(build_names).not_to include('performance')
- end
- end
-
- context 'on master' do
- it_behaves_like 'performance job on tag or branch'
- end
-
- context 'on another branch' do
- let(:pipeline_ref) { 'feature' }
-
- it_behaves_like 'performance job on tag or branch'
- end
-
- context 'on tag' do
- let(:pipeline_ref) { 'v1.0.0' }
-
- it_behaves_like 'performance job on tag or branch'
- end
-
- context 'on merge request' do
- let(:service) { MergeRequests::CreatePipelineService.new(project, user) }
- let(:merge_request) { create(:merge_request, :simple, source_project: project) }
- let(:pipeline) { service.execute(merge_request) }
-
- it 'has no jobs' do
- expect(pipeline).to be_merge_request_event
- expect(build_names).to be_empty
- end
- end
- end
-end
diff --git a/spec/lib/gitlab/ci/templates/Jobs/build_gitlab_ci_yaml_spec.rb b/spec/lib/gitlab/ci/templates/Jobs/build_gitlab_ci_yaml_spec.rb
index b2a9e3f5cf4..4f8faa5ddb1 100644
--- a/spec/lib/gitlab/ci/templates/Jobs/build_gitlab_ci_yaml_spec.rb
+++ b/spec/lib/gitlab/ci/templates/Jobs/build_gitlab_ci_yaml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Jobs/Build.gitlab-ci.yml' do
+RSpec.describe 'Jobs/Build.gitlab-ci.yml' do
subject(:template) { Gitlab::Template::GitlabCiYmlTemplate.find('Jobs/Build') }
describe 'the created pipeline' do
diff --git a/spec/lib/gitlab/ci/templates/Jobs/code_quality_gitlab_ci_yaml_spec.rb b/spec/lib/gitlab/ci/templates/Jobs/code_quality_gitlab_ci_yaml_spec.rb
index 9c5b2fd5099..e685ad3b46e 100644
--- a/spec/lib/gitlab/ci/templates/Jobs/code_quality_gitlab_ci_yaml_spec.rb
+++ b/spec/lib/gitlab/ci/templates/Jobs/code_quality_gitlab_ci_yaml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Jobs/Code-Quality.gitlab-ci.yml' do
+RSpec.describe 'Jobs/Code-Quality.gitlab-ci.yml' do
subject(:template) { Gitlab::Template::GitlabCiYmlTemplate.find('Jobs/Code-Quality') }
describe 'the created pipeline' do
diff --git a/spec/lib/gitlab/ci/templates/Jobs/deploy_gitlab_ci_yaml_spec.rb b/spec/lib/gitlab/ci/templates/Jobs/deploy_gitlab_ci_yaml_spec.rb
index a6ae23c85d3..ea9bd5bd02c 100644
--- a/spec/lib/gitlab/ci/templates/Jobs/deploy_gitlab_ci_yaml_spec.rb
+++ b/spec/lib/gitlab/ci/templates/Jobs/deploy_gitlab_ci_yaml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Jobs/Deploy.gitlab-ci.yml' do
+RSpec.describe 'Jobs/Deploy.gitlab-ci.yml' do
subject(:template) do
<<~YAML
stages:
diff --git a/spec/lib/gitlab/ci/templates/Jobs/test_gitlab_ci_yaml_spec.rb b/spec/lib/gitlab/ci/templates/Jobs/test_gitlab_ci_yaml_spec.rb
index 2186bf038eb..f475785be98 100644
--- a/spec/lib/gitlab/ci/templates/Jobs/test_gitlab_ci_yaml_spec.rb
+++ b/spec/lib/gitlab/ci/templates/Jobs/test_gitlab_ci_yaml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Jobs/Test.gitlab-ci.yml' do
+RSpec.describe 'Jobs/Test.gitlab-ci.yml' do
subject(:template) { Gitlab::Template::GitlabCiYmlTemplate.find('Jobs/Test') }
describe 'the created pipeline' do
diff --git a/spec/lib/gitlab/ci/templates/Verify/load_performance_testing_gitlab_ci_yaml_spec.rb b/spec/lib/gitlab/ci/templates/Verify/load_performance_testing_gitlab_ci_yaml_spec.rb
new file mode 100644
index 00000000000..9711df55226
--- /dev/null
+++ b/spec/lib/gitlab/ci/templates/Verify/load_performance_testing_gitlab_ci_yaml_spec.rb
@@ -0,0 +1,75 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'Verify/Load-Performance-Testing.gitlab-ci.yml' do
+ subject(:template) do
+ <<~YAML
+ stages:
+ - test
+ - performance
+
+ include:
+ - template: 'Verify/Load-Performance-Testing.gitlab-ci.yml'
+
+ placeholder:
+ script:
+ - keep pipeline validator happy by having a job when stages are intentionally empty
+ YAML
+ end
+
+ describe 'the created pipeline' do
+ let(:user) { create(:admin) }
+ let(:project) { create(:project, :repository) }
+
+ let(:default_branch) { 'master' }
+ let(:pipeline_ref) { default_branch }
+ let(:service) { Ci::CreatePipelineService.new(project, user, ref: pipeline_ref) }
+ let(:pipeline) { service.execute!(:push) }
+ let(:build_names) { pipeline.builds.pluck(:name) }
+
+ before do
+ stub_ci_pipeline_yaml_file(template)
+
+ allow_any_instance_of(Ci::BuildScheduleWorker).to receive(:perform).and_return(true)
+ allow(project).to receive(:default_branch).and_return(default_branch)
+ end
+
+ it 'has no errors' do
+ expect(pipeline.errors).to be_empty
+ end
+
+ shared_examples_for 'load_performance job on tag or branch' do
+ it 'by default' do
+ expect(build_names).to include('load_performance')
+ end
+ end
+
+ context 'on master' do
+ it_behaves_like 'load_performance job on tag or branch'
+ end
+
+ context 'on another branch' do
+ let(:pipeline_ref) { 'feature' }
+
+ it_behaves_like 'load_performance job on tag or branch'
+ end
+
+ context 'on tag' do
+ let(:pipeline_ref) { 'v1.0.0' }
+
+ it_behaves_like 'load_performance job on tag or branch'
+ end
+
+ context 'on merge request' do
+ let(:service) { MergeRequests::CreatePipelineService.new(project, user) }
+ let(:merge_request) { create(:merge_request, :simple, source_project: project) }
+ let(:pipeline) { service.execute(merge_request) }
+
+ it 'has no jobs' do
+ expect(pipeline).to be_merge_request_event
+ expect(build_names).to be_empty
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/templates/auto_devops_gitlab_ci_yaml_spec.rb b/spec/lib/gitlab/ci/templates/auto_devops_gitlab_ci_yaml_spec.rb
index af6ec25b9d6..4d90e7ca9e6 100644
--- a/spec/lib/gitlab/ci/templates/auto_devops_gitlab_ci_yaml_spec.rb
+++ b/spec/lib/gitlab/ci/templates/auto_devops_gitlab_ci_yaml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Auto-DevOps.gitlab-ci.yml' do
+RSpec.describe 'Auto-DevOps.gitlab-ci.yml' do
subject(:template) { Gitlab::Template::GitlabCiYmlTemplate.find('Auto-DevOps') }
describe 'the created pipeline' do
@@ -37,6 +37,7 @@ describe 'Auto-DevOps.gitlab-ci.yml' do
context 'when the project is set for deployment to AWS' do
let(:platform_value) { 'ECS' }
+ let(:review_prod_build_names) { build_names.select {|n| n.include?('review') || n.include?('production')} }
before do
create(:ci_variable, project: project, key: 'AUTO_DEVOPS_PLATFORM_TARGET', value: platform_value)
@@ -67,8 +68,15 @@ describe 'Auto-DevOps.gitlab-ci.yml' do
end
it 'creates an ECS deployment job for production only' do
- expect(build_names).not_to include('review_ecs')
- expect(build_names).to include('production_ecs')
+ expect(review_prod_build_names).to contain_exactly('production_ecs')
+ end
+
+ context 'with FARGATE as a launch type' do
+ let(:platform_value) { 'FARGATE' }
+
+ it 'creates a FARGATE deployment job for production only' do
+ expect(review_prod_build_names).to contain_exactly('production_fargate')
+ end
end
context 'and we are not on the default branch' do
@@ -79,15 +87,22 @@ describe 'Auto-DevOps.gitlab-ci.yml' do
project.repository.create_branch(pipeline_branch)
end
- it_behaves_like 'no ECS job when AUTO_DEVOPS_PLATFORM_TARGET is not present' do
- let(:job_name) { 'review_ecs' }
+ %w(review_ecs review_fargate).each do |job|
+ it_behaves_like 'no ECS job when AUTO_DEVOPS_PLATFORM_TARGET is not present' do
+ let(:job_name) { job }
+ end
end
it 'creates an ECS deployment job for review only' do
- expect(build_names).to include('review_ecs')
- expect(build_names).not_to include('production_ecs')
- expect(build_names).not_to include('review')
- expect(build_names).not_to include('production')
+ expect(review_prod_build_names).to contain_exactly('review_ecs')
+ end
+
+ context 'with FARGATE as a launch type' do
+ let(:platform_value) { 'FARGATE' }
+
+ it 'creates an FARGATE deployment job for review only' do
+ expect(review_prod_build_names).to contain_exactly('review_fargate')
+ end
end
end
@@ -190,6 +205,7 @@ describe 'Auto-DevOps.gitlab-ci.yml' do
'Buildpack' | { 'README.md' => '' } | { 'BUILDPACK_URL' => 'http://example.com' } | %w(build test) | %w()
'Explicit set' | { 'README.md' => '' } | { 'AUTO_DEVOPS_EXPLICITLY_ENABLED' => '1' } | %w(build test) | %w()
'Explicit unset' | { 'README.md' => '' } | { 'AUTO_DEVOPS_EXPLICITLY_ENABLED' => '0' } | %w() | %w(build test)
+ 'DOCKERFILE_PATH' | { 'README.md' => '' } | { 'DOCKERFILE_PATH' => 'Docker.file' } | %w(build test) | %w()
'Dockerfile' | { 'Dockerfile' => '' } | {} | %w(build test) | %w()
'Clojure' | { 'project.clj' => '' } | {} | %w(build test) | %w()
'Go modules' | { 'go.mod' => '' } | {} | %w(build test) | %w()
diff --git a/spec/lib/gitlab/ci/templates/managed_cluster_applications_gitlab_ci_yaml_spec.rb b/spec/lib/gitlab/ci/templates/managed_cluster_applications_gitlab_ci_yaml_spec.rb
index 2a6314755ef..0e458e01a2c 100644
--- a/spec/lib/gitlab/ci/templates/managed_cluster_applications_gitlab_ci_yaml_spec.rb
+++ b/spec/lib/gitlab/ci/templates/managed_cluster_applications_gitlab_ci_yaml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Managed-Cluster-Applications.gitlab-ci.yml' do
+RSpec.describe 'Managed-Cluster-Applications.gitlab-ci.yml' do
subject(:template) { Gitlab::Template::GitlabCiYmlTemplate.find('Managed-Cluster-Applications') }
describe 'the created pipeline' do
diff --git a/spec/lib/gitlab/ci/templates/templates_spec.rb b/spec/lib/gitlab/ci/templates/templates_spec.rb
index bc3d5b89220..def4d1b3bf6 100644
--- a/spec/lib/gitlab/ci/templates/templates_spec.rb
+++ b/spec/lib/gitlab/ci/templates/templates_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'CI YML Templates' do
+RSpec.describe 'CI YML Templates' do
subject { Gitlab::Ci::YamlProcessor.new(content) }
let(:all_templates) { Gitlab::Template::GitlabCiYmlTemplate.all.map(&:full_name) }
diff --git a/spec/lib/gitlab/ci/trace/chunked_io_spec.rb b/spec/lib/gitlab/ci/trace/chunked_io_spec.rb
index 92b3e5562a9..a2903391c6f 100644
--- a/spec/lib/gitlab/ci/trace/chunked_io_spec.rb
+++ b/spec/lib/gitlab/ci/trace/chunked_io_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Trace::ChunkedIO, :clean_gitlab_redis_cache do
+RSpec.describe Gitlab::Ci::Trace::ChunkedIO, :clean_gitlab_redis_cache do
include ChunkedIOHelpers
let_it_be(:build) { create(:ci_build, :running) }
diff --git a/spec/lib/gitlab/ci/trace/section_parser_spec.rb b/spec/lib/gitlab/ci/trace/section_parser_spec.rb
index 24ce4d34411..b6bb52a5c06 100644
--- a/spec/lib/gitlab/ci/trace/section_parser_spec.rb
+++ b/spec/lib/gitlab/ci/trace/section_parser_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Trace::SectionParser do
+RSpec.describe Gitlab::Ci::Trace::SectionParser do
def lines_with_pos(text)
pos = 0
StringIO.new(text).each_line do |line|
diff --git a/spec/lib/gitlab/ci/trace/stream_spec.rb b/spec/lib/gitlab/ci/trace/stream_spec.rb
index ea79073840d..568c10bbac2 100644
--- a/spec/lib/gitlab/ci/trace/stream_spec.rb
+++ b/spec/lib/gitlab/ci/trace/stream_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Trace::Stream, :clean_gitlab_redis_cache do
+RSpec.describe Gitlab::Ci::Trace::Stream, :clean_gitlab_redis_cache do
let_it_be(:build) { create(:ci_build, :running) }
before do
diff --git a/spec/lib/gitlab/ci/trace_spec.rb b/spec/lib/gitlab/ci/trace_spec.rb
index 574c2b73722..85edf27d3e7 100644
--- a/spec/lib/gitlab/ci/trace_spec.rb
+++ b/spec/lib/gitlab/ci/trace_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Trace, :clean_gitlab_redis_shared_state do
+RSpec.describe Gitlab::Ci::Trace, :clean_gitlab_redis_shared_state do
let(:build) { create(:ci_build) }
let(:trace) { described_class.new(build) }
diff --git a/spec/lib/gitlab/ci/variables/collection/item_spec.rb b/spec/lib/gitlab/ci/variables/collection/item_spec.rb
index 1bdca753cd3..eba2f29836d 100644
--- a/spec/lib/gitlab/ci/variables/collection/item_spec.rb
+++ b/spec/lib/gitlab/ci/variables/collection/item_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Variables::Collection::Item do
+RSpec.describe Gitlab::Ci::Variables::Collection::Item do
let(:variable_key) { 'VAR' }
let(:variable_value) { 'something' }
let(:expected_value) { variable_value }
diff --git a/spec/lib/gitlab/ci/variables/collection_spec.rb b/spec/lib/gitlab/ci/variables/collection_spec.rb
index 59b9f7d4fb9..ac84313ad9f 100644
--- a/spec/lib/gitlab/ci/variables/collection_spec.rb
+++ b/spec/lib/gitlab/ci/variables/collection_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Variables::Collection do
+RSpec.describe Gitlab::Ci::Variables::Collection do
describe '.new' do
it 'can be initialized with an array' do
variable = { key: 'VAR', value: 'value', public: true, masked: false }
diff --git a/spec/lib/gitlab/ci/yaml_processor_spec.rb b/spec/lib/gitlab/ci/yaml_processor_spec.rb
index 1668149d8f5..5c6d748d66c 100644
--- a/spec/lib/gitlab/ci/yaml_processor_spec.rb
+++ b/spec/lib/gitlab/ci/yaml_processor_spec.rb
@@ -4,7 +4,7 @@ require 'spec_helper'
module Gitlab
module Ci
- describe YamlProcessor do
+ RSpec.describe YamlProcessor do
include StubRequests
subject { described_class.new(config, user: nil) }
@@ -435,6 +435,153 @@ module Gitlab
end
end
+ describe '#warnings' do
+ before do
+ stub_feature_flags(ci_raise_job_rules_without_workflow_rules_warning: true)
+ end
+
+ context 'when a warning is raised in a given entry' do
+ let(:config) do
+ <<-EOYML
+ rspec:
+ script: rspec
+ rules:
+ - if: '$VAR == "value"'
+ EOYML
+ end
+
+ it 'is propagated all the way up to the processor' do
+ expect(subject.warnings).to contain_exactly('jobs:rspec uses `rules` without defining `workflow:rules`')
+ end
+ end
+
+ context 'when a warning is raised together with errors' do
+ let(:config) do
+ <<-EOYML
+ rspec:
+ script: rspec
+ rules:
+ - if: '$VAR == "value"'
+ invalid:
+ script: echo
+ artifacts:
+ - wrong_key: value
+ EOYML
+ end
+
+ it 'is propagated all the way up into the raised exception' do
+ expect { subject }.to raise_error do |error|
+ expect(error).to be_a(described_class::ValidationError)
+ expect(error.message).to eq('jobs:invalid:artifacts config should be a hash')
+ expect(error.warnings).to contain_exactly('jobs:rspec uses `rules` without defining `workflow:rules`')
+ end
+ end
+ end
+
+ context 'when error is raised before composing the config' do
+ let(:config) do
+ <<-EOYML
+ include: unknown/file.yml
+ rspec:
+ script: rspec
+ rules:
+ - if: '$VAR == "value"'
+ EOYML
+ end
+
+ it 'raises an exception with empty warnings array' do
+ expect { subject }.to raise_error do |error|
+ expect(error).to be_a(described_class::ValidationError)
+ expect(error.message).to eq('Local file `unknown/file.yml` does not have project!')
+ expect(error.warnings).to be_empty
+ end
+ end
+ end
+
+ context 'when error is raised after composing the config with warnings' do
+ shared_examples 'has warnings and expected error' do |error_message|
+ it 'raises an exception including warnings' do
+ expect { subject }.to raise_error do |error|
+ expect(error).to be_a(described_class::ValidationError)
+ expect(error.message).to match(error_message)
+ expect(error.warnings).to be_present
+ end
+ end
+ end
+
+ context 'when stage does not exist' do
+ let(:config) do
+ <<-EOYML
+ rspec:
+ stage: custom_stage
+ script: rspec
+ rules:
+ - if: '$VAR == "value"'
+ EOYML
+ end
+
+ it_behaves_like 'has warnings and expected error', /rspec job: chosen stage does not exist/
+ end
+
+ context 'job dependency does not exist' do
+ let(:config) do
+ <<-EOYML
+ build:
+ stage: build
+ script: echo
+ rules:
+ - if: '$VAR == "value"'
+ test:
+ stage: test
+ script: echo
+ needs: [unknown_job]
+ EOYML
+ end
+
+ it_behaves_like 'has warnings and expected error', /test job: undefined need: unknown_job/
+ end
+
+ context 'job dependency defined in later stage' do
+ let(:config) do
+ <<-EOYML
+ build:
+ stage: build
+ script: echo
+ needs: [test]
+ rules:
+ - if: '$VAR == "value"'
+ test:
+ stage: test
+ script: echo
+ EOYML
+ end
+
+ it_behaves_like 'has warnings and expected error', /build job: need test is not defined in prior stages/
+ end
+ end
+
+ context 'when feature flag is disabled' do
+ before do
+ stub_feature_flags(ci_raise_job_rules_without_workflow_rules_warning: false)
+ end
+
+ context 'job rules used without workflow rules' do
+ let(:config) do
+ <<-EOYML
+ rspec:
+ script: rspec
+ rules:
+ - if: '$VAR == "value"'
+ EOYML
+ end
+
+ it 'does not raise the warning' do
+ expect(subject.warnings).to be_empty
+ end
+ end
+ end
+ end
+
describe 'only / except policies validations' do
context 'when `only` has an invalid value' do
let(:config) { { rspec: { script: "rspec", type: "test", only: only } } }
@@ -1397,6 +1544,9 @@ module Gitlab
tag_name: "$CI_COMMIT_TAG",
name: "Release $CI_TAG_NAME",
description: "./release_changelog.txt",
+ ref: 'b3235930aa443112e639f941c69c578912189bdd',
+ released_at: '2019-03-15T08:00:00Z',
+ milestones: %w[m1 m2 m3],
assets: {
links: [
{
@@ -2514,7 +2664,7 @@ module Gitlab
it 'returns errors and empty configuration' do
expect(subject.valid?).to eq(false)
expect(subject.errors).to eq(['Invalid configuration format'])
- expect(subject.content).to be_blank
+ expect(subject.config).to be_blank
end
end
@@ -2524,7 +2674,7 @@ module Gitlab
it 'returns errors and empty configuration' do
expect(subject.valid?).to eq(false)
expect(subject.errors).to eq(['jobs:rspec:tags config should be an array of strings'])
- expect(subject.content).to be_blank
+ expect(subject.config).to be_blank
end
end
@@ -2536,7 +2686,7 @@ module Gitlab
expect(subject.errors).to contain_exactly(
'jobs:rspec config contains unknown keys: bad_tags',
'jobs:rspec rules should be an array of hashes')
- expect(subject.content).to be_blank
+ expect(subject.config).to be_blank
end
end
@@ -2546,7 +2696,7 @@ module Gitlab
it 'returns errors and empty configuration' do
expect(subject.valid?).to eq(false)
expect(subject.errors).to eq(['Please provide content of .gitlab-ci.yml'])
- expect(subject.content).to be_blank
+ expect(subject.config).to be_blank
end
end
@@ -2556,7 +2706,7 @@ module Gitlab
it 'returns errors and empty configuration' do
expect(subject.valid?).to eq(false)
expect(subject.errors).to eq(['Unknown alias: bad_alias'])
- expect(subject.content).to be_blank
+ expect(subject.config).to be_blank
end
end
@@ -2566,7 +2716,7 @@ module Gitlab
it 'returns errors and empty configuration' do
expect(subject.valid?).to eq(true)
expect(subject.errors).to be_empty
- expect(subject.content).to be_present
+ expect(subject.config).to be_present
end
end
end
diff --git a/spec/lib/gitlab/ci_access_spec.rb b/spec/lib/gitlab/ci_access_spec.rb
index 3c68d209eb6..9b573c6eb7a 100644
--- a/spec/lib/gitlab/ci_access_spec.rb
+++ b/spec/lib/gitlab/ci_access_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::CiAccess do
+RSpec.describe Gitlab::CiAccess do
let(:access) { described_class.new }
describe '#can_do_action?' do
diff --git a/spec/lib/gitlab/class_attributes_spec.rb b/spec/lib/gitlab/class_attributes_spec.rb
new file mode 100644
index 00000000000..f8766f20495
--- /dev/null
+++ b/spec/lib/gitlab/class_attributes_spec.rb
@@ -0,0 +1,41 @@
+# frozen_string_literal: true
+require 'fast_spec_helper'
+
+RSpec.describe Gitlab::ClassAttributes do
+ let(:klass) do
+ Class.new do
+ include Gitlab::ClassAttributes
+
+ def self.get_attribute(name)
+ get_class_attribute(name)
+ end
+
+ def self.set_attribute(name, value)
+ class_attributes[name] = value
+ end
+ end
+ end
+
+ let(:subclass) { Class.new(klass) }
+
+ describe ".get_class_attribute" do
+ it "returns values set on the class" do
+ klass.set_attribute(:foo, :bar)
+
+ expect(klass.get_attribute(:foo)).to eq(:bar)
+ end
+
+ it "returns values set on a superclass" do
+ klass.set_attribute(:foo, :bar)
+
+ expect(subclass.get_attribute(:foo)).to eq(:bar)
+ end
+
+ it "returns values from the subclass over attributes from a superclass" do
+ klass.set_attribute(:foo, :baz)
+ subclass.set_attribute(:foo, :bar)
+
+ expect(subclass.get_attribute(:foo)).to eq(:bar)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/cleanup/orphan_job_artifact_files_batch_spec.rb b/spec/lib/gitlab/cleanup/orphan_job_artifact_files_batch_spec.rb
index 4d8edfeac80..d03d4f64a0f 100644
--- a/spec/lib/gitlab/cleanup/orphan_job_artifact_files_batch_spec.rb
+++ b/spec/lib/gitlab/cleanup/orphan_job_artifact_files_batch_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Cleanup::OrphanJobArtifactFilesBatch do
+RSpec.describe Gitlab::Cleanup::OrphanJobArtifactFilesBatch do
let(:batch_size) { 10 }
let(:dry_run) { true }
diff --git a/spec/lib/gitlab/cleanup/orphan_job_artifact_files_spec.rb b/spec/lib/gitlab/cleanup/orphan_job_artifact_files_spec.rb
index 966c4c8b2fb..8a7425a4156 100644
--- a/spec/lib/gitlab/cleanup/orphan_job_artifact_files_spec.rb
+++ b/spec/lib/gitlab/cleanup/orphan_job_artifact_files_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Cleanup::OrphanJobArtifactFiles do
+RSpec.describe Gitlab::Cleanup::OrphanJobArtifactFiles do
let(:null_logger) { Logger.new('/dev/null') }
subject(:cleanup) { described_class.new(logger: null_logger) }
diff --git a/spec/lib/gitlab/cleanup/orphan_lfs_file_references_spec.rb b/spec/lib/gitlab/cleanup/orphan_lfs_file_references_spec.rb
index 01cc0b30784..47b2cf5dc4a 100644
--- a/spec/lib/gitlab/cleanup/orphan_lfs_file_references_spec.rb
+++ b/spec/lib/gitlab/cleanup/orphan_lfs_file_references_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Cleanup::OrphanLfsFileReferences do
+RSpec.describe Gitlab::Cleanup::OrphanLfsFileReferences do
let(:null_logger) { Logger.new('/dev/null') }
let(:project) { create(:project, :repository, lfs_enabled: true) }
let(:lfs_object) { create(:lfs_object) }
diff --git a/spec/lib/gitlab/cleanup/project_uploads_spec.rb b/spec/lib/gitlab/cleanup/project_uploads_spec.rb
index d1e3a73686e..05d744d95e2 100644
--- a/spec/lib/gitlab/cleanup/project_uploads_spec.rb
+++ b/spec/lib/gitlab/cleanup/project_uploads_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Cleanup::ProjectUploads do
+RSpec.describe Gitlab::Cleanup::ProjectUploads do
subject { described_class.new(logger: logger) }
let(:logger) { double(:logger) }
diff --git a/spec/lib/gitlab/cleanup/remote_uploads_spec.rb b/spec/lib/gitlab/cleanup/remote_uploads_spec.rb
index 35642cd6e50..1752608f844 100644
--- a/spec/lib/gitlab/cleanup/remote_uploads_spec.rb
+++ b/spec/lib/gitlab/cleanup/remote_uploads_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe Gitlab::Cleanup::RemoteUploads do
+RSpec.describe Gitlab::Cleanup::RemoteUploads do
context 'when object_storage is enabled' do
let(:connection) { double }
let(:directory) { double }
diff --git a/spec/lib/gitlab/closing_issue_extractor_spec.rb b/spec/lib/gitlab/closing_issue_extractor_spec.rb
index 4e1bf2840dc..f2bc6390032 100644
--- a/spec/lib/gitlab/closing_issue_extractor_spec.rb
+++ b/spec/lib/gitlab/closing_issue_extractor_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::ClosingIssueExtractor do
+RSpec.describe Gitlab::ClosingIssueExtractor do
let(:project) { create(:project) }
let(:project2) { create(:project) }
let(:forked_project) { Projects::ForkService.new(project, project2.creator).execute }
diff --git a/spec/lib/gitlab/cluster/mixins/puma_cluster_spec.rb b/spec/lib/gitlab/cluster/mixins/puma_cluster_spec.rb
index 864529a6bf6..5b69b34d04b 100644
--- a/spec/lib/gitlab/cluster/mixins/puma_cluster_spec.rb
+++ b/spec/lib/gitlab/cluster/mixins/puma_cluster_spec.rb
@@ -4,7 +4,7 @@ require 'spec_helper'
# For easier debugging set `PUMA_DEBUG=1`
-describe Gitlab::Cluster::Mixins::PumaCluster do
+RSpec.describe Gitlab::Cluster::Mixins::PumaCluster do
before do
stub_const('PUMA_STARTUP_TIMEOUT', 30)
end
diff --git a/spec/lib/gitlab/cluster/mixins/unicorn_http_server_spec.rb b/spec/lib/gitlab/cluster/mixins/unicorn_http_server_spec.rb
index 3965eb722a0..0aaca0a79c2 100644
--- a/spec/lib/gitlab/cluster/mixins/unicorn_http_server_spec.rb
+++ b/spec/lib/gitlab/cluster/mixins/unicorn_http_server_spec.rb
@@ -4,7 +4,7 @@ require 'spec_helper'
# For easier debugging set `UNICORN_DEBUG=1`
-describe Gitlab::Cluster::Mixins::UnicornHttpServer do
+RSpec.describe Gitlab::Cluster::Mixins::UnicornHttpServer do
before do
stub_const('UNICORN_STARTUP_TIMEOUT', 30)
end
diff --git a/spec/lib/gitlab/cluster/puma_worker_killer_observer_spec.rb b/spec/lib/gitlab/cluster/puma_worker_killer_observer_spec.rb
index 6ed9dda08d7..948de161235 100644
--- a/spec/lib/gitlab/cluster/puma_worker_killer_observer_spec.rb
+++ b/spec/lib/gitlab/cluster/puma_worker_killer_observer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Cluster::PumaWorkerKillerObserver do
+RSpec.describe Gitlab::Cluster::PumaWorkerKillerObserver do
let(:counter) { Gitlab::Metrics::NullMetric.instance }
before do
diff --git a/spec/lib/gitlab/cluster/rack_timeout_observer_spec.rb b/spec/lib/gitlab/cluster/rack_timeout_observer_spec.rb
index 68e5435450c..05df4089075 100644
--- a/spec/lib/gitlab/cluster/rack_timeout_observer_spec.rb
+++ b/spec/lib/gitlab/cluster/rack_timeout_observer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Cluster::RackTimeoutObserver do
+RSpec.describe Gitlab::Cluster::RackTimeoutObserver do
let(:counter) { Gitlab::Metrics::NullMetric.instance }
before do
diff --git a/spec/lib/gitlab/code_navigation_path_spec.rb b/spec/lib/gitlab/code_navigation_path_spec.rb
index 07d4dfba622..4dc864b158d 100644
--- a/spec/lib/gitlab/code_navigation_path_spec.rb
+++ b/spec/lib/gitlab/code_navigation_path_spec.rb
@@ -2,10 +2,10 @@
require 'spec_helper'
-describe Gitlab::CodeNavigationPath do
+RSpec.describe Gitlab::CodeNavigationPath do
context 'when there is an artifact with code navigation data' do
let_it_be(:project) { create(:project, :repository) }
- let_it_be(:sha) { project.repository.commits('master', limit: 5).last.id }
+ let_it_be(:sha) { project.repository.commits('master', limit: Gitlab::CodeNavigationPath::LATEST_COMMITS_LIMIT).last.id }
let_it_be(:pipeline) { create(:ci_pipeline, project: project, sha: sha) }
let_it_be(:job) { create(:ci_build, pipeline: pipeline) }
let_it_be(:artifact) { create(:ci_job_artifact, :lsif, job: job) }
diff --git a/spec/lib/gitlab/color_schemes_spec.rb b/spec/lib/gitlab/color_schemes_spec.rb
index ba5573f6901..fd9fccc2bf7 100644
--- a/spec/lib/gitlab/color_schemes_spec.rb
+++ b/spec/lib/gitlab/color_schemes_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::ColorSchemes do
+RSpec.describe Gitlab::ColorSchemes do
describe '.body_classes' do
it 'returns a space-separated list of class names' do
css = described_class.body_classes
diff --git a/spec/lib/gitlab/conan_token_spec.rb b/spec/lib/gitlab/conan_token_spec.rb
new file mode 100644
index 00000000000..b17f2eaa8d8
--- /dev/null
+++ b/spec/lib/gitlab/conan_token_spec.rb
@@ -0,0 +1,97 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+RSpec.describe Gitlab::ConanToken do
+ let(:base_secret) { SecureRandom.base64(64) }
+
+ let(:jwt_secret) do
+ OpenSSL::HMAC.hexdigest(
+ OpenSSL::Digest::SHA256.new,
+ base_secret,
+ described_class::HMAC_KEY
+ )
+ end
+
+ before do
+ allow(Settings).to receive(:attr_encrypted_db_key_base).and_return(base_secret)
+ end
+
+ def build_jwt(access_token_id:, user_id:, expire_time: nil)
+ JSONWebToken::HMACToken.new(jwt_secret).tap do |jwt|
+ jwt['access_token'] = access_token_id
+ jwt['user_id'] = user_id || user_id
+ jwt.expire_time = expire_time || jwt.issued_at + 1.hour
+ end
+ end
+
+ describe '.from_personal_access_token' do
+ it 'sets access token id and user id' do
+ access_token = double(id: 123, user_id: 456)
+
+ token = described_class.from_personal_access_token(access_token)
+
+ expect(token.access_token_id).to eq(123)
+ expect(token.user_id).to eq(456)
+ end
+ end
+
+ describe '.from_job' do
+ it 'sets access token id and user id' do
+ user = double(id: 456)
+ job = double(token: 123, user: user)
+
+ token = described_class.from_job(job)
+
+ expect(token.access_token_id).to eq(123)
+ expect(token.user_id).to eq(456)
+ end
+ end
+
+ describe '.from_deploy_token' do
+ it 'sets access token id and user id' do
+ deploy_token = double(token: '123', username: 'bob')
+
+ token = described_class.from_deploy_token(deploy_token)
+
+ expect(token.access_token_id).to eq('123')
+ expect(token.user_id).to eq('bob')
+ end
+ end
+
+ describe '.decode' do
+ it 'sets access token id and user id' do
+ jwt = build_jwt(access_token_id: 123, user_id: 456)
+
+ token = described_class.decode(jwt.encoded)
+
+ expect(token.access_token_id).to eq(123)
+ expect(token.user_id).to eq(456)
+ end
+
+ it 'returns nil for invalid JWT' do
+ expect(described_class.decode('invalid-jwt')).to be_nil
+ end
+
+ it 'returns nil for expired JWT' do
+ jwt = build_jwt(access_token_id: 123,
+ user_id: 456,
+ expire_time: Time.zone.now - 2.hours)
+
+ expect(described_class.decode(jwt.encoded)).to be_nil
+ end
+ end
+
+ describe '#to_jwt' do
+ it 'returns the encoded JWT' do
+ allow(SecureRandom).to receive(:uuid).and_return('u-u-i-d')
+
+ Timecop.freeze do
+ jwt = build_jwt(access_token_id: 123, user_id: 456)
+
+ token = described_class.new(access_token_id: 123, user_id: 456)
+
+ expect(token.to_jwt).to eq(jwt.encoded)
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/config/entry/attributable_spec.rb b/spec/lib/gitlab/config/entry/attributable_spec.rb
index 64a4670f483..1e7880ed898 100644
--- a/spec/lib/gitlab/config/entry/attributable_spec.rb
+++ b/spec/lib/gitlab/config/entry/attributable_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Config::Entry::Attributable do
+RSpec.describe Gitlab::Config::Entry::Attributable do
let(:node) do
Class.new do
include Gitlab::Config::Entry::Attributable
diff --git a/spec/lib/gitlab/config/entry/boolean_spec.rb b/spec/lib/gitlab/config/entry/boolean_spec.rb
index 0b8b720dd80..86cd92e22d7 100644
--- a/spec/lib/gitlab/config/entry/boolean_spec.rb
+++ b/spec/lib/gitlab/config/entry/boolean_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Config::Entry::Boolean do
+RSpec.describe Gitlab::Config::Entry::Boolean do
let(:entry) { described_class.new(config) }
describe 'validations' do
diff --git a/spec/lib/gitlab/config/entry/configurable_spec.rb b/spec/lib/gitlab/config/entry/configurable_spec.rb
index 8c3a4490d08..c72efa66024 100644
--- a/spec/lib/gitlab/config/entry/configurable_spec.rb
+++ b/spec/lib/gitlab/config/entry/configurable_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Config::Entry::Configurable do
+RSpec.describe Gitlab::Config::Entry::Configurable do
let(:entry) do
Class.new(Gitlab::Config::Entry::Node) do
include Gitlab::Config::Entry::Configurable
diff --git a/spec/lib/gitlab/config/entry/factory_spec.rb b/spec/lib/gitlab/config/entry/factory_spec.rb
index 81ca5f2cba1..a00c45169ef 100644
--- a/spec/lib/gitlab/config/entry/factory_spec.rb
+++ b/spec/lib/gitlab/config/entry/factory_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Config::Entry::Factory do
+RSpec.describe Gitlab::Config::Entry::Factory do
describe '#create!' do
before do
stub_const('Script', Class.new(Gitlab::Config::Entry::Node))
diff --git a/spec/lib/gitlab/config/entry/simplifiable_spec.rb b/spec/lib/gitlab/config/entry/simplifiable_spec.rb
index 5c208cab449..2011587a342 100644
--- a/spec/lib/gitlab/config/entry/simplifiable_spec.rb
+++ b/spec/lib/gitlab/config/entry/simplifiable_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Config::Entry::Simplifiable do
+RSpec.describe Gitlab::Config::Entry::Simplifiable do
describe '.strategy' do
let(:entry) do
Class.new(described_class) do
diff --git a/spec/lib/gitlab/config/entry/undefined_spec.rb b/spec/lib/gitlab/config/entry/undefined_spec.rb
index 83c3a6aec72..36faabd8e31 100644
--- a/spec/lib/gitlab/config/entry/undefined_spec.rb
+++ b/spec/lib/gitlab/config/entry/undefined_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Config::Entry::Undefined do
+RSpec.describe Gitlab::Config::Entry::Undefined do
let(:entry) { described_class.new }
describe '#leaf?' do
diff --git a/spec/lib/gitlab/config/entry/unspecified_spec.rb b/spec/lib/gitlab/config/entry/unspecified_spec.rb
index 32c52594ecf..35ba992f62a 100644
--- a/spec/lib/gitlab/config/entry/unspecified_spec.rb
+++ b/spec/lib/gitlab/config/entry/unspecified_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Config::Entry::Unspecified do
+RSpec.describe Gitlab::Config::Entry::Unspecified do
let(:unspecified) { described_class.new(entry) }
let(:entry) { spy('Entry') }
diff --git a/spec/lib/gitlab/config/entry/validatable_spec.rb b/spec/lib/gitlab/config/entry/validatable_spec.rb
index 925db3594ba..5e66de60367 100644
--- a/spec/lib/gitlab/config/entry/validatable_spec.rb
+++ b/spec/lib/gitlab/config/entry/validatable_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Config::Entry::Validatable do
+RSpec.describe Gitlab::Config::Entry::Validatable do
let(:entry) do
Class.new(Gitlab::Config::Entry::Node) do
include Gitlab::Config::Entry::Validatable
diff --git a/spec/lib/gitlab/config/entry/validator_spec.rb b/spec/lib/gitlab/config/entry/validator_spec.rb
index 7bf350912df..d41f7f794ec 100644
--- a/spec/lib/gitlab/config/entry/validator_spec.rb
+++ b/spec/lib/gitlab/config/entry/validator_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Config::Entry::Validator do
+RSpec.describe Gitlab::Config::Entry::Validator do
let(:validator) { Class.new(described_class) }
let(:validator_instance) { validator.new(node) }
let(:node) { spy('node') }
diff --git a/spec/lib/gitlab/config/loader/yaml_spec.rb b/spec/lib/gitlab/config/loader/yaml_spec.rb
index 623fe927233..731ee12d7f4 100644
--- a/spec/lib/gitlab/config/loader/yaml_spec.rb
+++ b/spec/lib/gitlab/config/loader/yaml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Config::Loader::Yaml do
+RSpec.describe Gitlab::Config::Loader::Yaml do
let(:loader) { described_class.new(yml) }
let(:yml) do
diff --git a/spec/lib/gitlab/config_checker/external_database_checker_spec.rb b/spec/lib/gitlab/config_checker/external_database_checker_spec.rb
index d86d132c237..316696bc584 100644
--- a/spec/lib/gitlab/config_checker/external_database_checker_spec.rb
+++ b/spec/lib/gitlab/config_checker/external_database_checker_spec.rb
@@ -2,55 +2,58 @@
require 'spec_helper'
-describe Gitlab::ConfigChecker::ExternalDatabaseChecker do
+RSpec.describe Gitlab::ConfigChecker::ExternalDatabaseChecker do
describe '#check' do
subject { described_class.check }
- context 'database version is not deprecated' do
+ let_it_be(:deprecation_warning) { "Please upgrade" }
+ let_it_be(:upcoming_deprecation_warning) { "Please consider upgrading" }
+
+ context 'when database meets minimum version and there is no upcoming deprecation' do
before do
- allow(described_class).to receive(:db_version_deprecated?).and_return(false)
+ allow(Gitlab::Database).to receive(:postgresql_minimum_supported_version?).and_return(true)
+ allow(Gitlab::Database).to receive(:postgresql_upcoming_deprecation?).and_return(false)
end
it { is_expected.to be_empty }
end
- context 'database version is deprecated' do
+ context 'when database does not meet minimum version and there is no upcoming deprecation' do
before do
- allow(described_class).to receive(:db_version_deprecated?).and_return(true)
- end
-
- let(:notice_deprecated_database) do
- {
- type: 'warning',
- message: _('Note that PostgreSQL 11 will become the minimum required PostgreSQL version in GitLab 13.0 (May 2020). '\
- 'PostgreSQL 9.6 and PostgreSQL 10 will no longer be supported in GitLab 13.0. '\
- 'Please consider upgrading your PostgreSQL version (%{db_version}) soon.') % { db_version: Gitlab::Database.version.to_s }
- }
+ allow(Gitlab::Database).to receive(:postgresql_minimum_supported_version?).and_return(false)
+ allow(Gitlab::Database).to receive(:postgresql_upcoming_deprecation?).and_return(false)
end
- it 'reports deprecated database notices' do
- is_expected.to contain_exactly(notice_deprecated_database)
+ it 'only returns notice about deprecated database version' do
+ is_expected.to include(a_hash_including(message: include(deprecation_warning)))
+ is_expected.not_to include(a_hash_including(message: include(upcoming_deprecation_warning)))
end
end
- end
- describe '#db_version_deprecated' do
- subject { described_class.db_version_deprecated? }
-
- context 'database version is not deprecated' do
+ context 'when database meets minimum version and there is an upcoming deprecation' do
before do
- allow(Gitlab::Database).to receive(:version).and_return(11)
+ allow(Gitlab::Database).to receive(:postgresql_minimum_supported_version?).and_return(true)
+ allow(Gitlab::Database).to receive(:postgresql_upcoming_deprecation?).and_return(true)
end
- it { is_expected.to be false }
+ it 'only returns notice about an upcoming deprecation' do
+ is_expected.to include(a_hash_including(message: include(upcoming_deprecation_warning)))
+ is_expected.not_to include(a_hash_including(message: include(deprecation_warning)))
+ end
end
- context 'database version is deprecated' do
+ context 'when database does not meet minimum version and there is an upcoming deprecation' do
before do
- allow(Gitlab::Database).to receive(:version).and_return(10)
+ allow(Gitlab::Database).to receive(:postgresql_minimum_supported_version?).and_return(false)
+ allow(Gitlab::Database).to receive(:postgresql_upcoming_deprecation?).and_return(true)
end
- it { is_expected.to be true }
+ it 'returns notice about deprecated database version and an upcoming deprecation' do
+ is_expected.to include(
+ a_hash_including(message: include(deprecation_warning)),
+ a_hash_including(message: include(upcoming_deprecation_warning))
+ )
+ end
end
end
end
diff --git a/spec/lib/gitlab/config_checker/puma_rugged_checker_spec.rb b/spec/lib/gitlab/config_checker/puma_rugged_checker_spec.rb
index badfd56d571..afee3c5536e 100644
--- a/spec/lib/gitlab/config_checker/puma_rugged_checker_spec.rb
+++ b/spec/lib/gitlab/config_checker/puma_rugged_checker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::ConfigChecker::PumaRuggedChecker do
+RSpec.describe Gitlab::ConfigChecker::PumaRuggedChecker do
describe '#check' do
subject { described_class.check }
diff --git a/spec/lib/gitlab/conflict/file_collection_spec.rb b/spec/lib/gitlab/conflict/file_collection_spec.rb
index f3cdb1a9e59..6da7f8d530d 100644
--- a/spec/lib/gitlab/conflict/file_collection_spec.rb
+++ b/spec/lib/gitlab/conflict/file_collection_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Conflict::FileCollection do
+RSpec.describe Gitlab::Conflict::FileCollection do
let(:merge_request) { create(:merge_request, source_branch: 'conflict-resolvable', target_branch: 'conflict-start') }
let(:file_collection) { described_class.new(merge_request) }
diff --git a/spec/lib/gitlab/conflict/file_spec.rb b/spec/lib/gitlab/conflict/file_spec.rb
index 966648bcc5a..b54fe40bb5f 100644
--- a/spec/lib/gitlab/conflict/file_spec.rb
+++ b/spec/lib/gitlab/conflict/file_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Conflict::File do
+RSpec.describe Gitlab::Conflict::File do
include GitHelpers
let(:project) { create(:project, :repository) }
diff --git a/spec/lib/gitlab/content_security_policy/config_loader_spec.rb b/spec/lib/gitlab/content_security_policy/config_loader_spec.rb
index bbbbf91bd44..a94fd6acd32 100644
--- a/spec/lib/gitlab/content_security_policy/config_loader_spec.rb
+++ b/spec/lib/gitlab/content_security_policy/config_loader_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::ContentSecurityPolicy::ConfigLoader do
+RSpec.describe Gitlab::ContentSecurityPolicy::ConfigLoader do
let(:policy) { ActionDispatch::ContentSecurityPolicy.new }
let(:csp_config) do
{
diff --git a/spec/lib/gitlab/contributions_calendar_spec.rb b/spec/lib/gitlab/contributions_calendar_spec.rb
index 97742a3e815..67b2ea7a1d4 100644
--- a/spec/lib/gitlab/contributions_calendar_spec.rb
+++ b/spec/lib/gitlab/contributions_calendar_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::ContributionsCalendar do
+RSpec.describe Gitlab::ContributionsCalendar do
let(:contributor) { create(:user) }
let(:user) { create(:user) }
diff --git a/spec/lib/gitlab/cross_project_access/check_collection_spec.rb b/spec/lib/gitlab/cross_project_access/check_collection_spec.rb
index 1aa5480b670..178188f5555 100644
--- a/spec/lib/gitlab/cross_project_access/check_collection_spec.rb
+++ b/spec/lib/gitlab/cross_project_access/check_collection_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::CrossProjectAccess::CheckCollection do
+RSpec.describe Gitlab::CrossProjectAccess::CheckCollection do
subject(:collection) { described_class.new }
describe '#add_collection' do
diff --git a/spec/lib/gitlab/cross_project_access/check_info_spec.rb b/spec/lib/gitlab/cross_project_access/check_info_spec.rb
index 7d2471b6327..5327030daf0 100644
--- a/spec/lib/gitlab/cross_project_access/check_info_spec.rb
+++ b/spec/lib/gitlab/cross_project_access/check_info_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::CrossProjectAccess::CheckInfo do
+RSpec.describe Gitlab::CrossProjectAccess::CheckInfo do
let(:dummy_controller) { double }
before do
diff --git a/spec/lib/gitlab/cross_project_access/class_methods_spec.rb b/spec/lib/gitlab/cross_project_access/class_methods_spec.rb
index 17d265542dd..cc2c431fc07 100644
--- a/spec/lib/gitlab/cross_project_access/class_methods_spec.rb
+++ b/spec/lib/gitlab/cross_project_access/class_methods_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::CrossProjectAccess::ClassMethods do
+RSpec.describe Gitlab::CrossProjectAccess::ClassMethods do
let(:dummy_class) do
Class.new do
extend Gitlab::CrossProjectAccess::ClassMethods
diff --git a/spec/lib/gitlab/cross_project_access_spec.rb b/spec/lib/gitlab/cross_project_access_spec.rb
index ce18d207413..fb72b85f161 100644
--- a/spec/lib/gitlab/cross_project_access_spec.rb
+++ b/spec/lib/gitlab/cross_project_access_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::CrossProjectAccess do
+RSpec.describe Gitlab::CrossProjectAccess do
let(:super_class) { Class.new }
let(:descendant_class) { Class.new(super_class) }
let(:current_instance) { described_class.new }
diff --git a/spec/lib/gitlab/crypto_helper_spec.rb b/spec/lib/gitlab/crypto_helper_spec.rb
index 71bbeccb17b..c07089d8ef0 100644
--- a/spec/lib/gitlab/crypto_helper_spec.rb
+++ b/spec/lib/gitlab/crypto_helper_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::CryptoHelper do
+RSpec.describe Gitlab::CryptoHelper do
describe '.sha256' do
it 'generates SHA256 digest Base46 encoded' do
digest = described_class.sha256('some-value')
diff --git a/spec/lib/gitlab/current_settings_spec.rb b/spec/lib/gitlab/current_settings_spec.rb
index bfd9980ee9c..fd4e8bc1cd0 100644
--- a/spec/lib/gitlab/current_settings_spec.rb
+++ b/spec/lib/gitlab/current_settings_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::CurrentSettings do
+RSpec.describe Gitlab::CurrentSettings do
before do
stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false')
end
diff --git a/spec/lib/gitlab/cycle_analytics/base_event_fetcher_spec.rb b/spec/lib/gitlab/cycle_analytics/base_event_fetcher_spec.rb
index 3eea791d61a..056c1b5bc9f 100644
--- a/spec/lib/gitlab/cycle_analytics/base_event_fetcher_spec.rb
+++ b/spec/lib/gitlab/cycle_analytics/base_event_fetcher_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::CycleAnalytics::BaseEventFetcher do
+RSpec.describe Gitlab::CycleAnalytics::BaseEventFetcher do
let(:max_events) { 2 }
let(:project) { create(:project, :repository) }
let(:user) { create(:user, :admin) }
diff --git a/spec/lib/gitlab/cycle_analytics/code_event_fetcher_spec.rb b/spec/lib/gitlab/cycle_analytics/code_event_fetcher_spec.rb
index 326a41a3af7..a1a173abe57 100644
--- a/spec/lib/gitlab/cycle_analytics/code_event_fetcher_spec.rb
+++ b/spec/lib/gitlab/cycle_analytics/code_event_fetcher_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::CycleAnalytics::CodeEventFetcher do
+RSpec.describe Gitlab::CycleAnalytics::CodeEventFetcher do
let(:stage_name) { :code }
it_behaves_like 'default query config' do
diff --git a/spec/lib/gitlab/cycle_analytics/code_stage_spec.rb b/spec/lib/gitlab/cycle_analytics/code_stage_spec.rb
index 9a4193b09f5..afab19de2ab 100644
--- a/spec/lib/gitlab/cycle_analytics/code_stage_spec.rb
+++ b/spec/lib/gitlab/cycle_analytics/code_stage_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::CycleAnalytics::CodeStage do
+RSpec.describe Gitlab::CycleAnalytics::CodeStage do
let(:stage_name) { :code }
let(:project) { create(:project) }
diff --git a/spec/lib/gitlab/cycle_analytics/events_spec.rb b/spec/lib/gitlab/cycle_analytics/events_spec.rb
index 9eee7e89062..246003cde84 100644
--- a/spec/lib/gitlab/cycle_analytics/events_spec.rb
+++ b/spec/lib/gitlab/cycle_analytics/events_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'cycle analytics events' do
+RSpec.describe 'cycle analytics events' do
let(:project) { create(:project, :repository) }
let(:from_date) { 10.days.ago }
let(:user) { create(:user, :admin) }
diff --git a/spec/lib/gitlab/cycle_analytics/issue_event_fetcher_spec.rb b/spec/lib/gitlab/cycle_analytics/issue_event_fetcher_spec.rb
index a72e2952782..7a49ee53e8f 100644
--- a/spec/lib/gitlab/cycle_analytics/issue_event_fetcher_spec.rb
+++ b/spec/lib/gitlab/cycle_analytics/issue_event_fetcher_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::CycleAnalytics::IssueEventFetcher do
+RSpec.describe Gitlab::CycleAnalytics::IssueEventFetcher do
let(:stage_name) { :issue }
it_behaves_like 'default query config'
diff --git a/spec/lib/gitlab/cycle_analytics/issue_stage_spec.rb b/spec/lib/gitlab/cycle_analytics/issue_stage_spec.rb
index 021d31bf160..9ec71e6ed72 100644
--- a/spec/lib/gitlab/cycle_analytics/issue_stage_spec.rb
+++ b/spec/lib/gitlab/cycle_analytics/issue_stage_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::CycleAnalytics::IssueStage do
+RSpec.describe Gitlab::CycleAnalytics::IssueStage do
let(:stage_name) { :issue }
let(:project) { create(:project) }
let(:issue_1) { create(:issue, project: project, created_at: 90.minutes.ago) }
diff --git a/spec/lib/gitlab/cycle_analytics/permissions_spec.rb b/spec/lib/gitlab/cycle_analytics/permissions_spec.rb
index 2896e973a43..3fd48993e5f 100644
--- a/spec/lib/gitlab/cycle_analytics/permissions_spec.rb
+++ b/spec/lib/gitlab/cycle_analytics/permissions_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::CycleAnalytics::Permissions do
+RSpec.describe Gitlab::CycleAnalytics::Permissions do
let(:project) { create(:project, public_builds: false) }
let(:user) { create(:user) }
diff --git a/spec/lib/gitlab/cycle_analytics/plan_event_fetcher_spec.rb b/spec/lib/gitlab/cycle_analytics/plan_event_fetcher_spec.rb
index 587f185b970..bc14a772d34 100644
--- a/spec/lib/gitlab/cycle_analytics/plan_event_fetcher_spec.rb
+++ b/spec/lib/gitlab/cycle_analytics/plan_event_fetcher_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::CycleAnalytics::PlanEventFetcher do
+RSpec.describe Gitlab::CycleAnalytics::PlanEventFetcher do
let(:stage_name) { :plan }
it_behaves_like 'default query config' do
diff --git a/spec/lib/gitlab/cycle_analytics/plan_stage_spec.rb b/spec/lib/gitlab/cycle_analytics/plan_stage_spec.rb
index e391fa6b999..66d00edacb7 100644
--- a/spec/lib/gitlab/cycle_analytics/plan_stage_spec.rb
+++ b/spec/lib/gitlab/cycle_analytics/plan_stage_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::CycleAnalytics::PlanStage do
+RSpec.describe Gitlab::CycleAnalytics::PlanStage do
let(:stage_name) { :plan }
let(:project) { create(:project) }
let!(:issue_1) { create(:issue, project: project, created_at: 90.minutes.ago) }
diff --git a/spec/lib/gitlab/cycle_analytics/production_event_fetcher_spec.rb b/spec/lib/gitlab/cycle_analytics/production_event_fetcher_spec.rb
index aeca72e8c91..86b07a95cbb 100644
--- a/spec/lib/gitlab/cycle_analytics/production_event_fetcher_spec.rb
+++ b/spec/lib/gitlab/cycle_analytics/production_event_fetcher_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::CycleAnalytics::ProductionEventFetcher do
+RSpec.describe Gitlab::CycleAnalytics::ProductionEventFetcher do
let(:stage_name) { :production }
it_behaves_like 'default query config'
diff --git a/spec/lib/gitlab/cycle_analytics/production_stage_spec.rb b/spec/lib/gitlab/cycle_analytics/production_stage_spec.rb
index aeeae291e2e..73b17194f72 100644
--- a/spec/lib/gitlab/cycle_analytics/production_stage_spec.rb
+++ b/spec/lib/gitlab/cycle_analytics/production_stage_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::CycleAnalytics::ProductionStage do
+RSpec.describe Gitlab::CycleAnalytics::ProductionStage do
let(:stage_name) { 'Total' }
it_behaves_like 'base stage'
diff --git a/spec/lib/gitlab/cycle_analytics/review_event_fetcher_spec.rb b/spec/lib/gitlab/cycle_analytics/review_event_fetcher_spec.rb
index 3eb62b45e6f..fe13cc6b065 100644
--- a/spec/lib/gitlab/cycle_analytics/review_event_fetcher_spec.rb
+++ b/spec/lib/gitlab/cycle_analytics/review_event_fetcher_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::CycleAnalytics::ReviewEventFetcher do
+RSpec.describe Gitlab::CycleAnalytics::ReviewEventFetcher do
let(:stage_name) { :review }
it_behaves_like 'default query config'
diff --git a/spec/lib/gitlab/cycle_analytics/review_stage_spec.rb b/spec/lib/gitlab/cycle_analytics/review_stage_spec.rb
index 14100ee6f73..cdd1cca6837 100644
--- a/spec/lib/gitlab/cycle_analytics/review_stage_spec.rb
+++ b/spec/lib/gitlab/cycle_analytics/review_stage_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::CycleAnalytics::ReviewStage do
+RSpec.describe Gitlab::CycleAnalytics::ReviewStage do
let(:stage_name) { :review }
let(:project) { create(:project) }
let(:issue_1) { create(:issue, project: project, created_at: 90.minutes.ago) }
diff --git a/spec/lib/gitlab/cycle_analytics/stage_summary_spec.rb b/spec/lib/gitlab/cycle_analytics/stage_summary_spec.rb
index ccc99017e37..9ece24074e7 100644
--- a/spec/lib/gitlab/cycle_analytics/stage_summary_spec.rb
+++ b/spec/lib/gitlab/cycle_analytics/stage_summary_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::CycleAnalytics::StageSummary do
+RSpec.describe Gitlab::CycleAnalytics::StageSummary do
let(:project) { create(:project, :repository) }
let(:options) { { from: 1.day.ago, current_user: user } }
let(:user) { create(:user, :admin) }
diff --git a/spec/lib/gitlab/cycle_analytics/staging_event_fetcher_spec.rb b/spec/lib/gitlab/cycle_analytics/staging_event_fetcher_spec.rb
index 525f1608a70..bdf1b99c4c9 100644
--- a/spec/lib/gitlab/cycle_analytics/staging_event_fetcher_spec.rb
+++ b/spec/lib/gitlab/cycle_analytics/staging_event_fetcher_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::CycleAnalytics::StagingEventFetcher do
+RSpec.describe Gitlab::CycleAnalytics::StagingEventFetcher do
let(:stage_name) { :staging }
it_behaves_like 'default query config' do
diff --git a/spec/lib/gitlab/cycle_analytics/staging_stage_spec.rb b/spec/lib/gitlab/cycle_analytics/staging_stage_spec.rb
index 930892edd31..69e42adb139 100644
--- a/spec/lib/gitlab/cycle_analytics/staging_stage_spec.rb
+++ b/spec/lib/gitlab/cycle_analytics/staging_stage_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::CycleAnalytics::StagingStage do
+RSpec.describe Gitlab::CycleAnalytics::StagingStage do
let(:stage_name) { :staging }
let(:project) { create(:project) }
diff --git a/spec/lib/gitlab/cycle_analytics/summary/value_spec.rb b/spec/lib/gitlab/cycle_analytics/summary/value_spec.rb
index d9bdfa92a04..c955b288500 100644
--- a/spec/lib/gitlab/cycle_analytics/summary/value_spec.rb
+++ b/spec/lib/gitlab/cycle_analytics/summary/value_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::CycleAnalytics::Summary::Value do
+RSpec.describe Gitlab::CycleAnalytics::Summary::Value do
describe Gitlab::CycleAnalytics::Summary::Value::None do
it 'returns `-`' do
expect(described_class.new.to_s).to eq('-')
diff --git a/spec/lib/gitlab/cycle_analytics/test_event_fetcher_spec.rb b/spec/lib/gitlab/cycle_analytics/test_event_fetcher_spec.rb
index d550f083600..1277385d0b4 100644
--- a/spec/lib/gitlab/cycle_analytics/test_event_fetcher_spec.rb
+++ b/spec/lib/gitlab/cycle_analytics/test_event_fetcher_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::CycleAnalytics::TestEventFetcher do
+RSpec.describe Gitlab::CycleAnalytics::TestEventFetcher do
let(:stage_name) { :test }
it_behaves_like 'default query config' do
diff --git a/spec/lib/gitlab/cycle_analytics/test_stage_spec.rb b/spec/lib/gitlab/cycle_analytics/test_stage_spec.rb
index 56e90520e72..9a207d32167 100644
--- a/spec/lib/gitlab/cycle_analytics/test_stage_spec.rb
+++ b/spec/lib/gitlab/cycle_analytics/test_stage_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::CycleAnalytics::TestStage do
+RSpec.describe Gitlab::CycleAnalytics::TestStage do
let(:stage_name) { :test }
let(:project) { create(:project) }
let(:stage_options) { { from: 2.days.ago, current_user: project.creator, project: project } }
diff --git a/spec/lib/gitlab/cycle_analytics/updater_spec.rb b/spec/lib/gitlab/cycle_analytics/updater_spec.rb
index 67f386f9144..f7095cffc19 100644
--- a/spec/lib/gitlab/cycle_analytics/updater_spec.rb
+++ b/spec/lib/gitlab/cycle_analytics/updater_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::CycleAnalytics::Updater do
+RSpec.describe Gitlab::CycleAnalytics::Updater do
describe 'updates authors' do
let(:user) { create(:user) }
let(:events) { [{ 'author_id' => user.id }] }
diff --git a/spec/lib/gitlab/cycle_analytics/usage_data_spec.rb b/spec/lib/gitlab/cycle_analytics/usage_data_spec.rb
index 41ce9355708..9ebdacb16de 100644
--- a/spec/lib/gitlab/cycle_analytics/usage_data_spec.rb
+++ b/spec/lib/gitlab/cycle_analytics/usage_data_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::CycleAnalytics::UsageData do
+RSpec.describe Gitlab::CycleAnalytics::UsageData do
describe '#to_json' do
before do
# Since git commits only have second precision, round up to the
diff --git a/spec/lib/gitlab/daemon_spec.rb b/spec/lib/gitlab/daemon_spec.rb
index cf1f089c577..075a1e414c7 100644
--- a/spec/lib/gitlab/daemon_spec.rb
+++ b/spec/lib/gitlab/daemon_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Daemon do
+RSpec.describe Gitlab::Daemon do
subject { described_class.new }
before do
diff --git a/spec/lib/gitlab/danger/changelog_spec.rb b/spec/lib/gitlab/danger/changelog_spec.rb
index 130a4708cec..f5954cd8c1e 100644
--- a/spec/lib/gitlab/danger/changelog_spec.rb
+++ b/spec/lib/gitlab/danger/changelog_spec.rb
@@ -1,13 +1,11 @@
# frozen_string_literal: true
require 'fast_spec_helper'
-require 'rspec-parameterized'
require_relative 'danger_spec_helper'
require 'gitlab/danger/changelog'
-describe Gitlab::Danger::Changelog do
- using RSpec::Parameterized::TableSyntax
+RSpec.describe Gitlab::Danger::Changelog do
include DangerSpecHelper
let(:added_files) { nil }
@@ -26,34 +24,36 @@ describe Gitlab::Danger::Changelog do
subject(:changelog) { fake_danger.new(git: fake_git, gitlab: fake_gitlab, helper: fake_helper) }
describe '#needed?' do
- subject { changelog.needed? }
+ let(:category_with_changelog) { :backend }
+ let(:label_with_changelog) { 'frontend' }
+ let(:category_without_changelog) { Gitlab::Danger::Changelog::NO_CHANGELOG_CATEGORIES.first }
+ let(:label_without_changelog) { Gitlab::Danger::Changelog::NO_CHANGELOG_LABELS.first }
- where(:categories, :labels) do
- { backend: nil } | %w[backend backstage]
- { frontend: nil, docs: nil } | ['ci-build']
- { engineering_productivity: nil, none: nil } | ['meta']
- end
+ subject { changelog.needed? }
- with_them do
- let(:changes_by_category) { categories }
- let(:mr_labels) { labels }
+ context 'when MR contains only categories requiring no changelog' do
+ let(:changes_by_category) { { category_without_changelog => nil } }
+ let(:mr_labels) { [] }
- it "is falsy when categories and labels require no changelog" do
+ it 'is falsey' do
is_expected.to be_falsy
end
end
- where(:categories, :labels) do
- { frontend: nil, docs: nil } | ['database::review pending', 'feature']
- { backend: nil } | ['backend', 'technical debt']
- { engineering_productivity: nil, none: nil } | ['frontend']
+ context 'when MR contains a label that require no changelog' do
+ let(:changes_by_category) { { category_with_changelog => nil } }
+ let(:mr_labels) { [label_with_changelog, label_without_changelog] }
+
+ it 'is falsey' do
+ is_expected.to be_falsy
+ end
end
- with_them do
- let(:changes_by_category) { categories }
- let(:mr_labels) { labels }
+ context 'when MR contains a category that require changelog and a category that require no changelog' do
+ let(:changes_by_category) { { category_with_changelog => nil, category_without_changelog => nil } }
+ let(:mr_labels) { [] }
- it "is truthy when categories and labels require a changelog" do
+ it 'is truthy' do
is_expected.to be_truthy
end
end
diff --git a/spec/lib/gitlab/danger/commit_linter_spec.rb b/spec/lib/gitlab/danger/commit_linter_spec.rb
index e57ccd12fa5..06bec6f793d 100644
--- a/spec/lib/gitlab/danger/commit_linter_spec.rb
+++ b/spec/lib/gitlab/danger/commit_linter_spec.rb
@@ -6,7 +6,7 @@ require_relative 'danger_spec_helper'
require 'gitlab/danger/commit_linter'
-describe Gitlab::Danger::CommitLinter do
+RSpec.describe Gitlab::Danger::CommitLinter do
using RSpec::Parameterized::TableSyntax
let(:total_files_changed) { 2 }
@@ -156,7 +156,7 @@ describe Gitlab::Danger::CommitLinter do
context 'when subject is a WIP' do
let(:final_message) { 'A B C' }
# commit message with prefix will be over max length. commit message without prefix will be of maximum size
- let(:commit_message) { described_class::WIP_PREFIX + final_message + 'D' * (described_class::WARN_SUBJECT_LENGTH - final_message.size) }
+ let(:commit_message) { described_class::WIP_PREFIX + final_message + 'D' * (described_class::MAX_LINE_LENGTH - final_message.size) }
it 'does not have any problems' do
commit_linter.lint
@@ -176,16 +176,6 @@ describe Gitlab::Danger::CommitLinter do
end
end
- context 'when subject is above warning' do
- let(:commit_message) { 'A B ' + 'C' * described_class::WARN_SUBJECT_LENGTH }
-
- it 'adds a problem' do
- expect(commit_linter).to receive(:add_problem).with(:subject_above_warning, described_class::DEFAULT_SUBJECT_DESCRIPTION)
-
- commit_linter.lint
- end
- end
-
context 'when subject starts with lowercase' do
let(:commit_message) { 'a B C' }
diff --git a/spec/lib/gitlab/danger/emoji_checker_spec.rb b/spec/lib/gitlab/danger/emoji_checker_spec.rb
index 0cdc18ce626..6092c751e1c 100644
--- a/spec/lib/gitlab/danger/emoji_checker_spec.rb
+++ b/spec/lib/gitlab/danger/emoji_checker_spec.rb
@@ -5,7 +5,7 @@ require 'rspec-parameterized'
require 'gitlab/danger/emoji_checker'
-describe Gitlab::Danger::EmojiChecker do
+RSpec.describe Gitlab::Danger::EmojiChecker do
using RSpec::Parameterized::TableSyntax
describe '#includes_text_emoji?' do
diff --git a/spec/lib/gitlab/danger/helper_spec.rb b/spec/lib/gitlab/danger/helper_spec.rb
index 809064a540c..e73742b5911 100644
--- a/spec/lib/gitlab/danger/helper_spec.rb
+++ b/spec/lib/gitlab/danger/helper_spec.rb
@@ -6,7 +6,7 @@ require_relative 'danger_spec_helper'
require 'gitlab/danger/helper'
-describe Gitlab::Danger::Helper do
+RSpec.describe Gitlab::Danger::Helper do
using RSpec::Parameterized::TableSyntax
include DangerSpecHelper
@@ -165,125 +165,152 @@ describe Gitlab::Danger::Helper do
end
end
- describe '#category_for_file' do
- where(:path, :expected_category) do
- 'doc/foo' | :none
- 'CONTRIBUTING.md' | :none
- 'LICENSE' | :none
- 'MAINTENANCE.md' | :none
- 'PHILOSOPHY.md' | :none
- 'PROCESS.md' | :none
- 'README.md' | :none
-
- 'ee/doc/foo' | :unknown
- 'ee/README' | :unknown
-
- 'app/assets/foo' | :frontend
- 'app/views/foo' | :frontend
- 'public/foo' | :frontend
- 'scripts/frontend/foo' | :frontend
- 'spec/javascripts/foo' | :frontend
- 'spec/frontend/bar' | :frontend
- 'vendor/assets/foo' | :frontend
- 'babel.config.js' | :frontend
- 'jest.config.js' | :frontend
- 'package.json' | :frontend
- 'yarn.lock' | :frontend
- 'config/foo.js' | :frontend
- 'config/deep/foo.js' | :frontend
-
- 'ee/app/assets/foo' | :frontend
- 'ee/app/views/foo' | :frontend
- 'ee/spec/javascripts/foo' | :frontend
- 'ee/spec/frontend/bar' | :frontend
-
- 'app/models/foo' | :backend
- 'bin/foo' | :backend
- 'config/foo' | :backend
- 'lib/foo' | :backend
- 'rubocop/foo' | :backend
- 'spec/foo' | :backend
- 'spec/foo/bar' | :backend
-
- 'ee/app/foo' | :backend
- 'ee/bin/foo' | :backend
- 'ee/spec/foo' | :backend
- 'ee/spec/foo/bar' | :backend
-
- 'generator_templates/foo' | :backend
- 'vendor/languages.yml' | :backend
- 'vendor/licenses.csv' | :backend
- 'file_hooks/examples/' | :backend
-
- 'Gemfile' | :backend
- 'Gemfile.lock' | :backend
- 'Rakefile' | :backend
- 'FOO_VERSION' | :backend
-
- 'Dangerfile' | :engineering_productivity
- 'danger/commit_messages/Dangerfile' | :engineering_productivity
- 'ee/danger/commit_messages/Dangerfile' | :engineering_productivity
- 'danger/commit_messages/' | :engineering_productivity
- 'ee/danger/commit_messages/' | :engineering_productivity
- '.gitlab-ci.yml' | :engineering_productivity
- '.gitlab/ci/cng.gitlab-ci.yml' | :engineering_productivity
- '.gitlab/ci/ee-specific-checks.gitlab-ci.yml' | :engineering_productivity
- 'scripts/foo' | :engineering_productivity
- 'lib/gitlab/danger/foo' | :engineering_productivity
- 'ee/lib/gitlab/danger/foo' | :engineering_productivity
- '.overcommit.yml.example' | :engineering_productivity
- '.editorconfig' | :engineering_productivity
- 'tooling/overcommit/foo' | :engineering_productivity
- '.codeclimate.yml' | :engineering_productivity
-
- 'lib/gitlab/ci/templates/Security/SAST.gitlab-ci.yml' | :backend
-
- 'ee/FOO_VERSION' | :unknown
-
- 'db/schema.rb' | :database
- 'db/structure.sql' | :database
- 'db/migrate/foo' | :database
- 'db/post_migrate/foo' | :database
- 'ee/db/migrate/foo' | :database
- 'ee/db/post_migrate/foo' | :database
- 'ee/db/geo/migrate/foo' | :database
- 'ee/db/geo/post_migrate/foo' | :database
- 'app/models/project_authorization.rb' | :database
- 'app/services/users/refresh_authorized_projects_service.rb' | :database
- 'lib/gitlab/background_migration.rb' | :database
- 'lib/gitlab/background_migration/foo' | :database
- 'ee/lib/gitlab/background_migration/foo' | :database
- 'lib/gitlab/database.rb' | :database
- 'lib/gitlab/database/foo' | :database
- 'ee/lib/gitlab/database/foo' | :database
- 'lib/gitlab/github_import.rb' | :database
- 'lib/gitlab/github_import/foo' | :database
- 'lib/gitlab/sql/foo' | :database
- 'rubocop/cop/migration/foo' | :database
-
- 'db/fixtures/foo.rb' | :backend
- 'ee/db/fixtures/foo.rb' | :backend
-
- 'qa/foo' | :qa
- 'ee/qa/foo' | :qa
-
- 'changelogs/foo' | :none
- 'ee/changelogs/foo' | :none
- 'locale/gitlab.pot' | :none
-
- 'FOO' | :unknown
- 'foo' | :unknown
-
- 'foo/bar.rb' | :backend
- 'foo/bar.js' | :frontend
- 'foo/bar.txt' | :none
- 'foo/bar.md' | :none
+ describe '#categories_for_file' do
+ before do
+ allow(fake_git).to receive(:diff_for_file).with('usage_data.rb') { double(:diff, patch: "+ count(User.active)") }
+ end
+
+ where(:path, :expected_categories) do
+ 'usage_data.rb' | [:database, :backend]
+ 'doc/foo.md' | [:docs]
+ 'CONTRIBUTING.md' | [:docs]
+ 'LICENSE' | [:docs]
+ 'MAINTENANCE.md' | [:docs]
+ 'PHILOSOPHY.md' | [:docs]
+ 'PROCESS.md' | [:docs]
+ 'README.md' | [:docs]
+
+ 'ee/doc/foo' | [:unknown]
+ 'ee/README' | [:unknown]
+
+ 'app/assets/foo' | [:frontend]
+ 'app/views/foo' | [:frontend]
+ 'public/foo' | [:frontend]
+ 'scripts/frontend/foo' | [:frontend]
+ 'spec/javascripts/foo' | [:frontend]
+ 'spec/frontend/bar' | [:frontend]
+ 'vendor/assets/foo' | [:frontend]
+ 'babel.config.js' | [:frontend]
+ 'jest.config.js' | [:frontend]
+ 'package.json' | [:frontend]
+ 'yarn.lock' | [:frontend]
+ 'config/foo.js' | [:frontend]
+ 'config/deep/foo.js' | [:frontend]
+
+ 'ee/app/assets/foo' | [:frontend]
+ 'ee/app/views/foo' | [:frontend]
+ 'ee/spec/javascripts/foo' | [:frontend]
+ 'ee/spec/frontend/bar' | [:frontend]
+
+ '.gitlab/ci/frontend.gitlab-ci.yml' | %i[frontend engineering_productivity]
+
+ 'app/models/foo' | [:backend]
+ 'bin/foo' | [:backend]
+ 'config/foo' | [:backend]
+ 'lib/foo' | [:backend]
+ 'rubocop/foo' | [:backend]
+ 'spec/foo' | [:backend]
+ 'spec/foo/bar' | [:backend]
+
+ 'ee/app/foo' | [:backend]
+ 'ee/bin/foo' | [:backend]
+ 'ee/spec/foo' | [:backend]
+ 'ee/spec/foo/bar' | [:backend]
+
+ 'generator_templates/foo' | [:backend]
+ 'vendor/languages.yml' | [:backend]
+ 'vendor/licenses.csv' | [:backend]
+ 'file_hooks/examples/' | [:backend]
+
+ 'Gemfile' | [:backend]
+ 'Gemfile.lock' | [:backend]
+ 'Rakefile' | [:backend]
+ 'FOO_VERSION' | [:backend]
+
+ 'Dangerfile' | [:engineering_productivity]
+ 'danger/commit_messages/Dangerfile' | [:engineering_productivity]
+ 'ee/danger/commit_messages/Dangerfile' | [:engineering_productivity]
+ 'danger/commit_messages/' | [:engineering_productivity]
+ 'ee/danger/commit_messages/' | [:engineering_productivity]
+ '.gitlab-ci.yml' | [:engineering_productivity]
+ '.gitlab/ci/cng.gitlab-ci.yml' | [:engineering_productivity]
+ '.gitlab/ci/ee-specific-checks.gitlab-ci.yml' | [:engineering_productivity]
+ 'scripts/foo' | [:engineering_productivity]
+ 'lib/gitlab/danger/foo' | [:engineering_productivity]
+ 'ee/lib/gitlab/danger/foo' | [:engineering_productivity]
+ '.overcommit.yml.example' | [:engineering_productivity]
+ '.editorconfig' | [:engineering_productivity]
+ 'tooling/overcommit/foo' | [:engineering_productivity]
+ '.codeclimate.yml' | [:engineering_productivity]
+
+ 'lib/gitlab/ci/templates/Security/SAST.gitlab-ci.yml' | [:backend]
+
+ 'ee/FOO_VERSION' | [:unknown]
+
+ 'db/schema.rb' | [:database]
+ 'db/structure.sql' | [:database]
+ 'db/migrate/foo' | [:database]
+ 'db/post_migrate/foo' | [:database]
+ 'ee/db/migrate/foo' | [:database]
+ 'ee/db/post_migrate/foo' | [:database]
+ 'ee/db/geo/migrate/foo' | [:database]
+ 'ee/db/geo/post_migrate/foo' | [:database]
+ 'app/models/project_authorization.rb' | [:database]
+ 'app/services/users/refresh_authorized_projects_service.rb' | [:database]
+ 'lib/gitlab/background_migration.rb' | [:database]
+ 'lib/gitlab/background_migration/foo' | [:database]
+ 'ee/lib/gitlab/background_migration/foo' | [:database]
+ 'lib/gitlab/database.rb' | [:database]
+ 'lib/gitlab/database/foo' | [:database]
+ 'ee/lib/gitlab/database/foo' | [:database]
+ 'lib/gitlab/github_import.rb' | [:database]
+ 'lib/gitlab/github_import/foo' | [:database]
+ 'lib/gitlab/sql/foo' | [:database]
+ 'rubocop/cop/migration/foo' | [:database]
+
+ 'db/fixtures/foo.rb' | [:backend]
+ 'ee/db/fixtures/foo.rb' | [:backend]
+
+ 'qa/foo' | [:qa]
+ 'ee/qa/foo' | [:qa]
+
+ 'changelogs/foo' | [:none]
+ 'ee/changelogs/foo' | [:none]
+ 'locale/gitlab.pot' | [:none]
+
+ 'FOO' | [:unknown]
+ 'foo' | [:unknown]
+
+ 'foo/bar.rb' | [:backend]
+ 'foo/bar.js' | [:frontend]
+ 'foo/bar.txt' | [:none]
+ 'foo/bar.md' | [:none]
end
with_them do
- subject { helper.category_for_file(path) }
+ subject { helper.categories_for_file(path) }
- it { is_expected.to eq(expected_category) }
+ it { is_expected.to eq(expected_categories) }
+ end
+
+ context 'having specific changes' do
+ it 'has database and backend categories' do
+ allow(fake_git).to receive(:diff_for_file).with('usage_data.rb') { double(:diff, patch: "+ count(User.active)") }
+
+ expect(helper.categories_for_file('usage_data.rb')).to eq([:database, :backend])
+ end
+
+ it 'has backend category' do
+ allow(fake_git).to receive(:diff_for_file).with('usage_data.rb') { double(:diff, patch: "+ alt_usage_data(User.active)") }
+
+ expect(helper.categories_for_file('usage_data.rb')).to eq([:backend])
+ end
+
+ it 'has backend category for changes outside usage_data files' do
+ allow(fake_git).to receive(:diff_for_file).with('user.rb') { double(:diff, patch: "+ count(User.active)") }
+
+ expect(helper.categories_for_file('user.rb')).to eq([:backend])
+ end
end
end
@@ -296,6 +323,7 @@ describe Gitlab::Danger::Helper do
:frontend | '~frontend'
:none | ''
:qa | '~QA'
+ :engineering_productivity | '~"Engineering Productivity" for CI, Danger'
end
with_them do
@@ -335,6 +363,11 @@ describe Gitlab::Danger::Helper do
where(:mr_title, :expected_mr_title) do
'My MR title' | 'My MR title'
'WIP: My MR title' | 'My MR title'
+ 'Draft: My MR title' | 'My MR title'
+ '(Draft) My MR title' | 'My MR title'
+ '[Draft] My MR title' | 'My MR title'
+ '[DRAFT] My MR title' | 'My MR title'
+ 'DRAFT: My MR title' | 'My MR title'
end
with_them do
@@ -366,6 +399,69 @@ describe Gitlab::Danger::Helper do
end
end
+ describe '#cherry_pick_mr?' do
+ it 'returns false when `gitlab_helper` is unavailable' do
+ expect(helper).to receive(:gitlab_helper).and_return(nil)
+
+ expect(helper).not_to be_cherry_pick_mr
+ end
+
+ context 'when MR title does not mention a cherry-pick' do
+ it 'returns false' do
+ expect(fake_gitlab).to receive(:mr_json)
+ .and_return('title' => 'Add feature xyz')
+
+ expect(helper).not_to be_cherry_pick_mr
+ end
+ end
+
+ context 'when MR title mentions a cherry-pick' do
+ [
+ 'Cherry Pick !1234',
+ 'cherry-pick !1234',
+ 'CherryPick !1234'
+ ].each do |mr_title|
+ it 'returns true' do
+ expect(fake_gitlab).to receive(:mr_json)
+ .and_return('title' => mr_title)
+
+ expect(helper).to be_cherry_pick_mr
+ end
+ end
+ end
+ end
+
+ describe '#stable_branch?' do
+ it 'returns false when `gitlab_helper` is unavailable' do
+ expect(helper).to receive(:gitlab_helper).and_return(nil)
+
+ expect(helper).not_to be_stable_branch
+ end
+
+ context 'when MR target branch is not a stable branch' do
+ it 'returns false' do
+ expect(fake_gitlab).to receive(:mr_json)
+ .and_return('target_branch' => 'my-feature-branch')
+
+ expect(helper).not_to be_stable_branch
+ end
+ end
+
+ context 'when MR target branch is a stable branch' do
+ %w[
+ 13-1-stable-ee
+ 13-1-stable-ee-patch-1
+ ].each do |target_branch|
+ it 'returns true' do
+ expect(fake_gitlab).to receive(:mr_json)
+ .and_return('target_branch' => target_branch)
+
+ expect(helper).to be_stable_branch
+ end
+ end
+ end
+ end
+
describe '#mr_has_label?' do
it 'returns false when `gitlab_helper` is unavailable' do
expect(helper).to receive(:gitlab_helper).and_return(nil)
diff --git a/spec/lib/gitlab/danger/roulette_spec.rb b/spec/lib/gitlab/danger/roulette_spec.rb
index b6148cd1407..676edca2459 100644
--- a/spec/lib/gitlab/danger/roulette_spec.rb
+++ b/spec/lib/gitlab/danger/roulette_spec.rb
@@ -2,16 +2,23 @@
require 'fast_spec_helper'
require 'webmock/rspec'
+require 'timecop'
require 'gitlab/danger/roulette'
-describe Gitlab::Danger::Roulette do
+RSpec.describe Gitlab::Danger::Roulette do
+ around do |example|
+ Timecop.freeze(Time.utc(2020, 06, 22, 10)) { example.run }
+ end
+
let(:backend_maintainer) do
{
username: 'backend-maintainer',
name: 'Backend maintainer',
role: 'Backend engineer',
- projects: { 'gitlab' => 'maintainer backend' }
+ projects: { 'gitlab' => 'maintainer backend' },
+ available: true,
+ tz_offset_hours: 2.0
}
end
let(:frontend_reviewer) do
@@ -19,7 +26,9 @@ describe Gitlab::Danger::Roulette do
username: 'frontend-reviewer',
name: 'Frontend reviewer',
role: 'Frontend engineer',
- projects: { 'gitlab' => 'reviewer frontend' }
+ projects: { 'gitlab' => 'reviewer frontend' },
+ available: true,
+ tz_offset_hours: 2.0
}
end
let(:frontend_maintainer) do
@@ -27,7 +36,9 @@ describe Gitlab::Danger::Roulette do
username: 'frontend-maintainer',
name: 'Frontend maintainer',
role: 'Frontend engineer',
- projects: { 'gitlab' => "maintainer frontend" }
+ projects: { 'gitlab' => "maintainer frontend" },
+ available: true,
+ tz_offset_hours: 2.0
}
end
let(:software_engineer_in_test) do
@@ -38,7 +49,9 @@ describe Gitlab::Danger::Roulette do
projects: {
'gitlab' => 'reviewer qa',
'gitlab-qa' => 'maintainer'
- }
+ },
+ available: true,
+ tz_offset_hours: 2.0
}
end
let(:engineering_productivity_reviewer) do
@@ -46,7 +59,9 @@ describe Gitlab::Danger::Roulette do
username: 'eng-prod-reviewer',
name: 'EP engineer',
role: 'Engineering Productivity',
- projects: { 'gitlab' => 'reviewer backend' }
+ projects: { 'gitlab' => 'reviewer backend' },
+ available: true,
+ tz_offset_hours: 2.0
}
end
@@ -73,10 +88,17 @@ describe Gitlab::Danger::Roulette do
def matching_spin(category, reviewer: { username: nil }, maintainer: { username: nil }, optional: nil)
satisfy do |spin|
- spin.category == category &&
- spin.reviewer&.username == reviewer[:username] &&
- spin.maintainer&.username == maintainer[:username] &&
- spin.optional_role == optional
+ bool = spin.category == category
+ bool &&= spin.reviewer&.username == reviewer[:username]
+
+ bool &&=
+ if maintainer
+ spin.maintainer&.username == maintainer[:username]
+ else
+ spin.maintainer.nil?
+ end
+
+ bool && spin.optional_role == optional
end
end
@@ -85,67 +107,114 @@ describe Gitlab::Danger::Roulette do
let!(:branch_name) { 'a-branch' }
let!(:mr_labels) { ['backend', 'devops::create'] }
let!(:author) { Gitlab::Danger::Teammate.new('username' => 'filipa') }
-
- before do
- [
- backend_maintainer,
- frontend_reviewer,
- frontend_maintainer,
- software_engineer_in_test,
- engineering_productivity_reviewer
- ].each do |person|
- stub_person_status(instance_double(Gitlab::Danger::Teammate, username: person[:username]), message: 'making GitLab magic')
- end
-
+ let(:timezone_experiment) { false }
+ let(:spins) do
+ # Stub the request at the latest time so that we can modify the raw data, e.g. available fields.
WebMock
.stub_request(:get, described_class::ROULETTE_DATA_URL)
.to_return(body: teammate_json)
+
+ subject.spin(project, categories, branch_name, timezone_experiment: timezone_experiment)
+ end
+
+ before do
allow(subject).to receive_message_chain(:gitlab, :mr_author).and_return(author.username)
allow(subject).to receive_message_chain(:gitlab, :mr_labels).and_return(mr_labels)
end
- context 'when change contains backend category' do
- it 'assigns backend reviewer and maintainer' do
- categories = [:backend]
- spins = subject.spin(project, categories, branch_name)
+ context 'when timezone_experiment == false' do
+ context 'when change contains backend category' do
+ let(:categories) { [:backend] }
- expect(spins).to contain_exactly(matching_spin(:backend, reviewer: engineering_productivity_reviewer, maintainer: backend_maintainer))
+ it 'assigns backend reviewer and maintainer' do
+ expect(spins).to contain_exactly(matching_spin(:backend, reviewer: engineering_productivity_reviewer, maintainer: backend_maintainer))
+ end
+
+ context 'when teammate is not available' do
+ before do
+ backend_maintainer[:available] = false
+ end
+
+ it 'assigns backend reviewer and no maintainer' do
+ expect(spins).to contain_exactly(matching_spin(:backend, reviewer: engineering_productivity_reviewer, maintainer: nil))
+ end
+ end
end
- end
- context 'when change contains frontend category' do
- it 'assigns frontend reviewer and maintainer' do
- categories = [:frontend]
- spins = subject.spin(project, categories, branch_name)
+ context 'when change contains frontend category' do
+ let(:categories) { [:frontend] }
- expect(spins).to contain_exactly(matching_spin(:frontend, reviewer: frontend_reviewer, maintainer: frontend_maintainer))
+ it 'assigns frontend reviewer and maintainer' do
+ expect(spins).to contain_exactly(matching_spin(:frontend, reviewer: frontend_reviewer, maintainer: frontend_maintainer))
+ end
end
- end
- context 'when change contains QA category' do
- it 'assigns QA reviewer and sets optional QA maintainer' do
- categories = [:qa]
- spins = subject.spin(project, categories, branch_name)
+ context 'when change contains QA category' do
+ let(:categories) { [:qa] }
- expect(spins).to contain_exactly(matching_spin(:qa, reviewer: software_engineer_in_test, optional: :maintainer))
+ it 'assigns QA reviewer' do
+ expect(spins).to contain_exactly(matching_spin(:qa, reviewer: software_engineer_in_test))
+ end
end
- end
- context 'when change contains Engineering Productivity category' do
- it 'assigns Engineering Productivity reviewer and fallback to backend maintainer' do
- categories = [:engineering_productivity]
- spins = subject.spin(project, categories, branch_name)
+ context 'when change contains Engineering Productivity category' do
+ let(:categories) { [:engineering_productivity] }
- expect(spins).to contain_exactly(matching_spin(:engineering_productivity, reviewer: engineering_productivity_reviewer, maintainer: backend_maintainer))
+ it 'assigns Engineering Productivity reviewer and fallback to backend maintainer' do
+ expect(spins).to contain_exactly(matching_spin(:engineering_productivity, reviewer: engineering_productivity_reviewer, maintainer: backend_maintainer))
+ end
+ end
+
+ context 'when change contains test category' do
+ let(:categories) { [:test] }
+
+ it 'assigns corresponding SET' do
+ expect(spins).to contain_exactly(matching_spin(:test, reviewer: software_engineer_in_test))
+ end
end
end
- context 'when change contains test category' do
- it 'assigns corresponding SET and sets optional test maintainer' do
- categories = [:test]
- spins = subject.spin(project, categories, branch_name)
+ context 'when timezone_experiment == true' do
+ let(:timezone_experiment) { true }
+
+ context 'when change contains backend category' do
+ let(:categories) { [:backend] }
+
+ it 'assigns backend reviewer and maintainer' do
+ expect(spins).to contain_exactly(matching_spin(:backend, reviewer: engineering_productivity_reviewer, maintainer: backend_maintainer))
+ end
+
+ context 'when teammate is not in a good timezone' do
+ before do
+ backend_maintainer[:tz_offset_hours] = 5.0
+ end
- expect(spins).to contain_exactly(matching_spin(:test, reviewer: software_engineer_in_test, optional: :maintainer))
+ it 'assigns backend reviewer and no maintainer' do
+ expect(spins).to contain_exactly(matching_spin(:backend, reviewer: engineering_productivity_reviewer, maintainer: nil))
+ end
+ end
+ end
+
+ context 'when change includes a category with timezone disabled' do
+ let(:categories) { [:backend] }
+
+ before do
+ stub_const("#{described_class}::INCLUDE_TIMEZONE_FOR_CATEGORY", backend: false)
+ end
+
+ it 'assigns backend reviewer and maintainer' do
+ expect(spins).to contain_exactly(matching_spin(:backend, reviewer: engineering_productivity_reviewer, maintainer: backend_maintainer))
+ end
+
+ context 'when teammate is not in a good timezone' do
+ before do
+ backend_maintainer[:tz_offset_hours] = 5.0
+ end
+
+ it 'assigns backend reviewer and maintainer' do
+ expect(spins).to contain_exactly(matching_spin(:backend, reviewer: engineering_productivity_reviewer, maintainer: backend_maintainer))
+ end
+ end
end
end
end
@@ -217,51 +286,83 @@ describe Gitlab::Danger::Roulette do
end
describe '#spin_for_person' do
- let(:person1) { Gitlab::Danger::Teammate.new('username' => 'rymai') }
- let(:person2) { Gitlab::Danger::Teammate.new('username' => 'godfat') }
- let(:author) { Gitlab::Danger::Teammate.new('username' => 'filipa') }
- let(:ooo) { Gitlab::Danger::Teammate.new('username' => 'jacopo-beschi') }
- let(:no_capacity) { Gitlab::Danger::Teammate.new('username' => 'uncharged') }
+ let(:person_tz_offset_hours) { 0.0 }
+ let(:person1) do
+ Gitlab::Danger::Teammate.new(
+ 'username' => 'rymai',
+ 'available' => true,
+ 'tz_offset_hours' => person_tz_offset_hours
+ )
+ end
+ let(:person2) do
+ Gitlab::Danger::Teammate.new(
+ 'username' => 'godfat',
+ 'available' => true,
+ 'tz_offset_hours' => person_tz_offset_hours)
+ end
+ let(:author) do
+ Gitlab::Danger::Teammate.new(
+ 'username' => 'filipa',
+ 'available' => true,
+ 'tz_offset_hours' => 0.0)
+ end
+ let(:unavailable) do
+ Gitlab::Danger::Teammate.new(
+ 'username' => 'jacopo-beschi',
+ 'available' => false,
+ 'tz_offset_hours' => 0.0)
+ end
before do
- stub_person_status(person1, message: 'making GitLab magic')
- stub_person_status(person2, message: 'making GitLab magic')
- stub_person_status(ooo, message: 'OOO till 15th')
- stub_person_status(no_capacity, message: 'At capacity for the next few days', emoji: 'red_circle')
- # we don't stub Filipa, as she is the author and
- # we should not fire request checking for her
-
allow(subject).to receive_message_chain(:gitlab, :mr_author).and_return(author.username)
end
- it 'returns a random person' do
- persons = [person1, person2]
+ (-4..4).each do |utc_offset|
+ context "when local hour for person is #{10 + utc_offset} (offset: #{utc_offset})" do
+ let(:person_tz_offset_hours) { utc_offset }
- selected = subject.spin_for_person(persons, random: Random.new)
+ [false, true].each do |timezone_experiment|
+ context "with timezone_experiment == #{timezone_experiment}" do
+ it 'returns a random person' do
+ persons = [person1, person2]
- expect(selected.username).to be_in(persons.map(&:username))
- end
+ selected = subject.spin_for_person(persons, random: Random.new, timezone_experiment: timezone_experiment)
- it 'excludes OOO persons' do
- expect(subject.spin_for_person([ooo], random: Random.new)).to be_nil
+ expect(selected.username).to be_in(persons.map(&:username))
+ end
+ end
+ end
+ end
end
- it 'excludes mr.author' do
- expect(subject.spin_for_person([author], random: Random.new)).to be_nil
+ ((-12..-5).to_a + (5..12).to_a).each do |utc_offset|
+ context "when local hour for person is #{10 + utc_offset} (offset: #{utc_offset})" do
+ let(:person_tz_offset_hours) { utc_offset }
+
+ [false, true].each do |timezone_experiment|
+ context "with timezone_experiment == #{timezone_experiment}" do
+ it 'returns a random person or nil' do
+ persons = [person1, person2]
+
+ selected = subject.spin_for_person(persons, random: Random.new, timezone_experiment: timezone_experiment)
+
+ if timezone_experiment
+ expect(selected).to be_nil
+ else
+ expect(selected.username).to be_in(persons.map(&:username))
+ end
+ end
+ end
+ end
+ end
end
- it 'excludes person with no capacity' do
- expect(subject.spin_for_person([no_capacity], random: Random.new)).to be_nil
+ it 'excludes unavailable persons' do
+ expect(subject.spin_for_person([unavailable], random: Random.new)).to be_nil
end
- end
- private
-
- def stub_person_status(person, message: 'dummy message', emoji: 'unicorn')
- body = { message: message, emoji: emoji }.to_json
-
- WebMock
- .stub_request(:get, "https://gitlab.com/api/v4/users/#{person.username}/status")
- .to_return(body: body)
+ it 'excludes mr.author' do
+ expect(subject.spin_for_person([author], random: Random.new)).to be_nil
+ end
end
end
diff --git a/spec/lib/gitlab/danger/sidekiq_queues_spec.rb b/spec/lib/gitlab/danger/sidekiq_queues_spec.rb
new file mode 100644
index 00000000000..7dd1a2e6924
--- /dev/null
+++ b/spec/lib/gitlab/danger/sidekiq_queues_spec.rb
@@ -0,0 +1,82 @@
+# frozen_string_literal: true
+
+require 'fast_spec_helper'
+require 'rspec-parameterized'
+require_relative 'danger_spec_helper'
+
+require 'gitlab/danger/sidekiq_queues'
+
+RSpec.describe Gitlab::Danger::SidekiqQueues do
+ using RSpec::Parameterized::TableSyntax
+ include DangerSpecHelper
+
+ let(:fake_git) { double('fake-git') }
+ let(:fake_danger) { new_fake_danger.include(described_class) }
+
+ subject(:sidekiq_queues) { fake_danger.new(git: fake_git) }
+
+ describe '#changed_queue_files' do
+ where(:modified_files, :changed_queue_files) do
+ %w(app/workers/all_queues.yml ee/app/workers/all_queues.yml foo) | %w(app/workers/all_queues.yml ee/app/workers/all_queues.yml)
+ %w(app/workers/all_queues.yml ee/app/workers/all_queues.yml) | %w(app/workers/all_queues.yml ee/app/workers/all_queues.yml)
+ %w(app/workers/all_queues.yml foo) | %w(app/workers/all_queues.yml)
+ %w(ee/app/workers/all_queues.yml foo) | %w(ee/app/workers/all_queues.yml)
+ %w(foo) | %w()
+ %w() | %w()
+ end
+
+ with_them do
+ it do
+ allow(fake_git).to receive(:modified_files).and_return(modified_files)
+
+ expect(sidekiq_queues.changed_queue_files).to match_array(changed_queue_files)
+ end
+ end
+ end
+
+ describe '#added_queue_names' do
+ it 'returns queue names added by this change' do
+ old_queues = { post_receive: nil }
+
+ allow(sidekiq_queues).to receive(:old_queues).and_return(old_queues)
+ allow(sidekiq_queues).to receive(:new_queues).and_return(old_queues.merge(merge: nil, process_commit: nil))
+
+ expect(sidekiq_queues.added_queue_names).to contain_exactly(:merge, :process_commit)
+ end
+ end
+
+ describe '#changed_queue_names' do
+ it 'returns names for queues whose attributes were changed' do
+ old_queues = {
+ merge: { name: :merge, urgency: :low },
+ post_receive: { name: :post_receive, urgency: :high },
+ process_commit: { name: :process_commit, urgency: :high }
+ }
+
+ new_queues = old_queues.merge(mailers: { name: :mailers, urgency: :high },
+ post_receive: { name: :post_receive, urgency: :low },
+ process_commit: { name: :process_commit, urgency: :low })
+
+ allow(sidekiq_queues).to receive(:old_queues).and_return(old_queues)
+ allow(sidekiq_queues).to receive(:new_queues).and_return(new_queues)
+
+ expect(sidekiq_queues.changed_queue_names).to contain_exactly(:post_receive, :process_commit)
+ end
+
+ it 'ignores removed queues' do
+ old_queues = {
+ merge: { name: :merge, urgency: :low },
+ post_receive: { name: :post_receive, urgency: :high }
+ }
+
+ new_queues = {
+ post_receive: { name: :post_receive, urgency: :low }
+ }
+
+ allow(sidekiq_queues).to receive(:old_queues).and_return(old_queues)
+ allow(sidekiq_queues).to receive(:new_queues).and_return(new_queues)
+
+ expect(sidekiq_queues.changed_queue_names).to contain_exactly(:post_receive)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/danger/teammate_spec.rb b/spec/lib/gitlab/danger/teammate_spec.rb
index ea5aecbc597..a0540a9fbf5 100644
--- a/spec/lib/gitlab/danger/teammate_spec.rb
+++ b/spec/lib/gitlab/danger/teammate_spec.rb
@@ -2,14 +2,27 @@
require 'fast_spec_helper'
+require 'timecop'
require 'rspec-parameterized'
require 'gitlab/danger/teammate'
-describe Gitlab::Danger::Teammate do
+RSpec.describe Gitlab::Danger::Teammate do
+ using RSpec::Parameterized::TableSyntax
+
subject { described_class.new(options.stringify_keys) }
- let(:options) { { username: 'luigi', projects: projects, role: role } }
+ let(:tz_offset_hours) { 2.0 }
+ let(:options) do
+ {
+ username: 'luigi',
+ projects: projects,
+ role: role,
+ markdown_name: '[Luigi](https://gitlab.com/luigi) (`@luigi`)',
+ tz_offset_hours: tz_offset_hours
+ }
+ end
+ let(:capabilities) { ['reviewer backend'] }
let(:projects) { { project => capabilities } }
let(:role) { 'Engineer, Manage' }
let(:labels) { [] }
@@ -115,78 +128,72 @@ describe Gitlab::Danger::Teammate do
end
end
- describe '#status' do
- let(:capabilities) { ['dish washing'] }
-
- context 'with empty cache' do
- context 'for successful request' do
- it 'returns the response' do
- mock_status = double(does_not: 'matter')
- expect(Gitlab::Danger::RequestHelper).to receive(:http_get_json)
- .and_return(mock_status)
+ describe '#local_hour' do
+ around do |example|
+ Timecop.freeze(Time.utc(2020, 6, 23, 10)) { example.run }
+ end
- expect(subject.status).to be mock_status
- end
+ context 'when author is given' do
+ where(:tz_offset_hours, :expected_local_hour) do
+ -12 | 22
+ -10 | 0
+ 2 | 12
+ 4 | 14
+ 12 | 22
end
- context 'for failing request' do
- it 'returns nil' do
- expect(Gitlab::Danger::RequestHelper).to receive(:http_get_json)
- .and_raise(Gitlab::Danger::RequestHelper::HTTPError.new)
-
- expect(subject.status).to be nil
+ with_them do
+ it 'returns the correct local_hour' do
+ expect(subject.local_hour).to eq(expected_local_hour)
end
end
end
+ end
- context 'with filled cache' do
- it 'returns the cached response' do
- mock_status = double(does_not: 'matter')
- expect(Gitlab::Danger::RequestHelper).to receive(:http_get_json)
- .and_return(mock_status)
- subject.status
-
- expect(Gitlab::Danger::RequestHelper).not_to receive(:http_get_json)
- expect(subject.status).to be mock_status
+ describe '#markdown_name' do
+ context 'when timezone_experiment == false' do
+ it 'returns markdown name as-is' do
+ expect(subject.markdown_name).to eq(options[:markdown_name])
+ expect(subject.markdown_name(timezone_experiment: false)).to eq(options[:markdown_name])
end
end
- end
- describe '#available?' do
- using RSpec::Parameterized::TableSyntax
-
- let(:capabilities) { ['dry head'] }
-
- where(:status, :result) do
- {} | true
- { message: 'dear reader' } | true
- { message: 'OOO: massage' } | false
- { message: 'love it SOOO much' } | false
- { emoji: 'red_circle' } | false
- { emoji: 'palm_tree' } | false
- { emoji: 'beach' } | false
- { emoji: 'beach_umbrella' } | false
- { emoji: 'beach_with_umbrella' } | false
- { emoji: nil } | true
- { emoji: '' } | true
- { emoji: 'dancer' } | true
- end
+ context 'when timezone_experiment == true' do
+ it 'returns markdown name with timezone info' do
+ expect(subject.markdown_name(timezone_experiment: true)).to eq("#{options[:markdown_name]} (UTC+2)")
+ end
+
+ context 'when offset is 1.5' do
+ let(:tz_offset_hours) { 1.5 }
- with_them do
- before do
- expect(Gitlab::Danger::RequestHelper).to receive(:http_get_json)
- .and_return(status&.stringify_keys)
+ it 'returns markdown name with timezone info, not truncated' do
+ expect(subject.markdown_name(timezone_experiment: true)).to eq("#{options[:markdown_name]} (UTC+1.5)")
+ end
end
- it { expect(subject.available?).to be result }
- end
+ context 'when author is given' do
+ where(:tz_offset_hours, :author_offset, :diff_text) do
+ -12 | -10 | "2 hours behind `@mario`"
+ -10 | -12 | "2 hours ahead `@mario`"
+ -10 | 2 | "12 hours behind `@mario`"
+ 2 | 4 | "2 hours behind `@mario`"
+ 4 | 2 | "2 hours ahead `@mario`"
+ 2 | 3 | "1 hour behind `@mario`"
+ 3 | 2 | "1 hour ahead `@mario`"
+ 2 | 2 | "same timezone as `@mario`"
+ end
- it 'returns true if request fails' do
- expect(Gitlab::Danger::RequestHelper)
- .to receive(:http_get_json)
- .and_raise(Gitlab::Danger::RequestHelper::HTTPError.new)
+ with_them do
+ it 'returns markdown name with timezone info' do
+ author = described_class.new(options.merge(username: 'mario', tz_offset_hours: author_offset).stringify_keys)
- expect(subject.available?).to be true
+ floored_offset_hours = subject.__send__(:floored_offset_hours)
+ utc_offset = floored_offset_hours >= 0 ? "+#{floored_offset_hours}" : floored_offset_hours
+
+ expect(subject.markdown_name(timezone_experiment: true, author: author)).to eq("#{options[:markdown_name]} (UTC#{utc_offset}, #{diff_text})")
+ end
+ end
+ end
end
end
end
diff --git a/spec/lib/gitlab/data_builder/alert_spec.rb b/spec/lib/gitlab/data_builder/alert_spec.rb
index b881fb8139b..6c3dc3cd8b4 100644
--- a/spec/lib/gitlab/data_builder/alert_spec.rb
+++ b/spec/lib/gitlab/data_builder/alert_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::DataBuilder::Alert do
+RSpec.describe Gitlab::DataBuilder::Alert do
let_it_be(:project) { create(:project) }
let_it_be(:alert) { create(:alert_management_alert, project: project) }
diff --git a/spec/lib/gitlab/data_builder/build_spec.rb b/spec/lib/gitlab/data_builder/build_spec.rb
index da27125c9a6..cfaaf849b09 100644
--- a/spec/lib/gitlab/data_builder/build_spec.rb
+++ b/spec/lib/gitlab/data_builder/build_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::DataBuilder::Build do
+RSpec.describe Gitlab::DataBuilder::Build do
let(:runner) { create(:ci_runner, :instance) }
let(:user) { create(:user) }
let(:build) { create(:ci_build, :running, runner: runner, user: user) }
diff --git a/spec/lib/gitlab/data_builder/deployment_spec.rb b/spec/lib/gitlab/data_builder/deployment_spec.rb
index 42d7329494d..57bde6262a9 100644
--- a/spec/lib/gitlab/data_builder/deployment_spec.rb
+++ b/spec/lib/gitlab/data_builder/deployment_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::DataBuilder::Deployment do
+RSpec.describe Gitlab::DataBuilder::Deployment do
describe '.build' do
it 'returns the object kind for a deployment' do
deployment = build(:deployment)
diff --git a/spec/lib/gitlab/data_builder/note_spec.rb b/spec/lib/gitlab/data_builder/note_spec.rb
index 4b799c23de8..90ca5430526 100644
--- a/spec/lib/gitlab/data_builder/note_spec.rb
+++ b/spec/lib/gitlab/data_builder/note_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::DataBuilder::Note do
+RSpec.describe Gitlab::DataBuilder::Note do
let(:project) { create(:project, :repository) }
let(:user) { create(:user) }
let(:data) { described_class.build(note, user) }
diff --git a/spec/lib/gitlab/data_builder/pipeline_spec.rb b/spec/lib/gitlab/data_builder/pipeline_spec.rb
index 519f5873d75..4e0cc8a1fa9 100644
--- a/spec/lib/gitlab/data_builder/pipeline_spec.rb
+++ b/spec/lib/gitlab/data_builder/pipeline_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::DataBuilder::Pipeline do
+RSpec.describe Gitlab::DataBuilder::Pipeline do
let(:user) { create(:user) }
let(:project) { create(:project, :repository) }
diff --git a/spec/lib/gitlab/data_builder/push_spec.rb b/spec/lib/gitlab/data_builder/push_spec.rb
index cbc03fc38eb..7eb81a880bf 100644
--- a/spec/lib/gitlab/data_builder/push_spec.rb
+++ b/spec/lib/gitlab/data_builder/push_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::DataBuilder::Push do
+RSpec.describe Gitlab::DataBuilder::Push do
include RepoHelpers
let(:project) { create(:project, :repository) }
diff --git a/spec/lib/gitlab/data_builder/wiki_page_spec.rb b/spec/lib/gitlab/data_builder/wiki_page_spec.rb
index ae338e30a69..ec768cf9719 100644
--- a/spec/lib/gitlab/data_builder/wiki_page_spec.rb
+++ b/spec/lib/gitlab/data_builder/wiki_page_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::DataBuilder::WikiPage do
+RSpec.describe Gitlab::DataBuilder::WikiPage do
let_it_be(:project) { create(:project, :repository, :wiki_repo) }
let(:wiki_page) { create(:wiki_page, wiki: project.wiki) }
let(:user) { create(:user) }
diff --git a/spec/lib/gitlab/database/background_migration_job_spec.rb b/spec/lib/gitlab/database/background_migration_job_spec.rb
new file mode 100644
index 00000000000..40f47325be3
--- /dev/null
+++ b/spec/lib/gitlab/database/background_migration_job_spec.rb
@@ -0,0 +1,125 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Database::BackgroundMigrationJob do
+ it_behaves_like 'having unique enum values'
+
+ describe '.for_migration_execution' do
+ let!(:job1) { create(:background_migration_job) }
+ let!(:job2) { create(:background_migration_job, arguments: ['hi', 2]) }
+ let!(:job3) { create(:background_migration_job, class_name: 'OtherJob', arguments: ['hi', 2]) }
+
+ it 'returns jobs matching class_name and arguments' do
+ relation = described_class.for_migration_execution('TestJob', ['hi', 2])
+
+ expect(relation.count).to eq(1)
+ expect(relation.first).to have_attributes(class_name: 'TestJob', arguments: ['hi', 2])
+ end
+
+ it 'normalizes class names by removing leading ::' do
+ relation = described_class.for_migration_execution('::TestJob', ['hi', 2])
+
+ expect(relation.count).to eq(1)
+ expect(relation.first).to have_attributes(class_name: 'TestJob', arguments: ['hi', 2])
+ end
+ end
+
+ describe '.for_partitioning_migration' do
+ let!(:job1) { create(:background_migration_job, arguments: [1, 100, 'other_table']) }
+ let!(:job2) { create(:background_migration_job, arguments: [1, 100, 'audit_events']) }
+ let!(:job3) { create(:background_migration_job, class_name: 'OtherJob', arguments: [1, 100, 'audit_events']) }
+
+ it 'returns jobs matching class_name and the table_name job argument' do
+ relation = described_class.for_partitioning_migration('TestJob', 'audit_events')
+
+ expect(relation.count).to eq(1)
+ expect(relation.first).to have_attributes(class_name: 'TestJob', arguments: [1, 100, 'audit_events'])
+ end
+
+ it 'normalizes class names by removing leading ::' do
+ relation = described_class.for_partitioning_migration('::TestJob', 'audit_events')
+
+ expect(relation.count).to eq(1)
+ expect(relation.first).to have_attributes(class_name: 'TestJob', arguments: [1, 100, 'audit_events'])
+ end
+ end
+
+ describe '.mark_all_as_succeeded' do
+ let!(:job1) { create(:background_migration_job, arguments: [1, 100]) }
+ let!(:job2) { create(:background_migration_job, arguments: [1, 100]) }
+ let!(:job3) { create(:background_migration_job, arguments: [101, 200]) }
+ let!(:job4) { create(:background_migration_job, class_name: 'OtherJob', arguments: [1, 100]) }
+
+ it 'marks all matching jobs as succeeded' do
+ expect { described_class.mark_all_as_succeeded('TestJob', [1, 100]) }
+ .to change { described_class.succeeded.count }.from(0).to(2)
+
+ expect(job1.reload).to be_succeeded
+ expect(job2.reload).to be_succeeded
+ expect(job3.reload).to be_pending
+ expect(job4.reload).to be_pending
+ end
+
+ it 'normalizes class_names by removing leading ::' do
+ expect { described_class.mark_all_as_succeeded('::TestJob', [1, 100]) }
+ .to change { described_class.succeeded.count }.from(0).to(2)
+
+ expect(job1.reload).to be_succeeded
+ expect(job2.reload).to be_succeeded
+ expect(job3.reload).to be_pending
+ expect(job4.reload).to be_pending
+ end
+
+ context 'when previous matching jobs have already succeeded' do
+ let(:initial_time) { Time.now.round }
+ let!(:job1) { create(:background_migration_job, :succeeded, created_at: initial_time, updated_at: initial_time) }
+
+ it 'does not update non-pending jobs' do
+ Timecop.freeze(initial_time + 1.day) do
+ expect { described_class.mark_all_as_succeeded('TestJob', [1, 100]) }
+ .to change { described_class.succeeded.count }.from(1).to(2)
+ end
+
+ expect(job1.reload.updated_at).to eq(initial_time)
+ expect(job2.reload).to be_succeeded
+ expect(job3.reload).to be_pending
+ expect(job4.reload).to be_pending
+ end
+ end
+ end
+
+ describe '#class_name=' do
+ context 'when the class_name is given without the leading ::' do
+ it 'sets the class_name to the given value' do
+ job = described_class.new(class_name: 'TestJob')
+
+ expect(job.class_name).to eq('TestJob')
+ end
+ end
+
+ context 'when the class_name is given with the leading ::' do
+ it 'removes the leading :: when setting the class_name' do
+ job = described_class.new(class_name: '::TestJob')
+
+ expect(job.class_name).to eq('TestJob')
+ end
+ end
+
+ context 'when the value is nil' do
+ it 'sets the class_name to nil' do
+ job = described_class.new(class_name: nil)
+
+ expect(job.class_name).to be_nil
+ end
+ end
+
+ context 'when the values is blank' do
+ it 'sets the class_name to the given value' do
+ job = described_class.new(class_name: '')
+
+ expect(job.class_name).to eq('')
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/database/batch_count_spec.rb b/spec/lib/gitlab/database/batch_count_spec.rb
index e7cb53f2dbd..656501dbf56 100644
--- a/spec/lib/gitlab/database/batch_count_spec.rb
+++ b/spec/lib/gitlab/database/batch_count_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Database::BatchCount do
+RSpec.describe Gitlab::Database::BatchCount do
let_it_be(:fallback) { ::Gitlab::Database::BatchCounter::FALLBACK }
let_it_be(:small_batch_size) { ::Gitlab::Database::BatchCounter::MIN_REQUIRED_BATCH_SIZE - 1 }
let(:model) { Issue }
diff --git a/spec/lib/gitlab/database/connection_timer_spec.rb b/spec/lib/gitlab/database/connection_timer_spec.rb
index c9e9d770343..2b6746bae07 100644
--- a/spec/lib/gitlab/database/connection_timer_spec.rb
+++ b/spec/lib/gitlab/database/connection_timer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Database::ConnectionTimer do
+RSpec.describe Gitlab::Database::ConnectionTimer do
let(:current_clock_value) { 1234.56 }
before do
diff --git a/spec/lib/gitlab/database/count/exact_count_strategy_spec.rb b/spec/lib/gitlab/database/count/exact_count_strategy_spec.rb
index 111833a506a..390620379d6 100644
--- a/spec/lib/gitlab/database/count/exact_count_strategy_spec.rb
+++ b/spec/lib/gitlab/database/count/exact_count_strategy_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Database::Count::ExactCountStrategy do
+RSpec.describe Gitlab::Database::Count::ExactCountStrategy do
before do
create_list(:project, 3)
create(:identity)
diff --git a/spec/lib/gitlab/database/count/reltuples_count_strategy_spec.rb b/spec/lib/gitlab/database/count/reltuples_count_strategy_spec.rb
index 08032d19d14..324ed498abc 100644
--- a/spec/lib/gitlab/database/count/reltuples_count_strategy_spec.rb
+++ b/spec/lib/gitlab/database/count/reltuples_count_strategy_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Database::Count::ReltuplesCountStrategy do
+RSpec.describe Gitlab::Database::Count::ReltuplesCountStrategy do
before do
create_list(:project, 3)
create(:identity)
diff --git a/spec/lib/gitlab/database/count/tablesample_count_strategy_spec.rb b/spec/lib/gitlab/database/count/tablesample_count_strategy_spec.rb
index 0c480709c22..e488bf5ee4c 100644
--- a/spec/lib/gitlab/database/count/tablesample_count_strategy_spec.rb
+++ b/spec/lib/gitlab/database/count/tablesample_count_strategy_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Database::Count::TablesampleCountStrategy do
+RSpec.describe Gitlab::Database::Count::TablesampleCountStrategy do
before do
create_list(:project, 3)
create(:identity)
diff --git a/spec/lib/gitlab/database/count_spec.rb b/spec/lib/gitlab/database/count_spec.rb
index 2469ce482e7..d65413c2a00 100644
--- a/spec/lib/gitlab/database/count_spec.rb
+++ b/spec/lib/gitlab/database/count_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Database::Count do
+RSpec.describe Gitlab::Database::Count do
before do
create_list(:project, 3)
create(:identity)
diff --git a/spec/lib/gitlab/database/custom_structure_spec.rb b/spec/lib/gitlab/database/custom_structure_spec.rb
index f03b5ed0a7f..beda9df3684 100644
--- a/spec/lib/gitlab/database/custom_structure_spec.rb
+++ b/spec/lib/gitlab/database/custom_structure_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Database::CustomStructure do
+RSpec.describe Gitlab::Database::CustomStructure do
let_it_be(:structure) { described_class.new }
let_it_be(:filepath) { Rails.root.join(described_class::CUSTOM_DUMP_FILE) }
let_it_be(:file_header) do
diff --git a/spec/lib/gitlab/database/dynamic_model_helpers_spec.rb b/spec/lib/gitlab/database/dynamic_model_helpers_spec.rb
new file mode 100644
index 00000000000..23ad621d0ee
--- /dev/null
+++ b/spec/lib/gitlab/database/dynamic_model_helpers_spec.rb
@@ -0,0 +1,28 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Database::DynamicModelHelpers do
+ describe '#define_batchable_model' do
+ subject { including_class.new.define_batchable_model(table_name) }
+
+ let(:including_class) { Class.new.include(described_class) }
+ let(:table_name) { 'projects' }
+
+ it 'is an ActiveRecord model' do
+ expect(subject.ancestors).to include(ActiveRecord::Base)
+ end
+
+ it 'includes EachBatch' do
+ expect(subject.included_modules).to include(EachBatch)
+ end
+
+ it 'has the correct table name' do
+ expect(subject.table_name).to eq(table_name)
+ end
+
+ it 'has the inheritance type column disable' do
+ expect(subject.inheritance_column).to eq('_type_disabled')
+ end
+ end
+end
diff --git a/spec/lib/gitlab/database/grant_spec.rb b/spec/lib/gitlab/database/grant_spec.rb
index 02697eb2a16..23aec85d1d0 100644
--- a/spec/lib/gitlab/database/grant_spec.rb
+++ b/spec/lib/gitlab/database/grant_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Database::Grant do
+RSpec.describe Gitlab::Database::Grant do
describe '.create_and_execute_trigger' do
it 'returns true when the user can create and execute a trigger' do
# We assume the DB/user is set up correctly so that triggers can be
diff --git a/spec/lib/gitlab/database/migration_helpers_spec.rb b/spec/lib/gitlab/database/migration_helpers_spec.rb
index bed444ee7c7..48e1c97e97f 100644
--- a/spec/lib/gitlab/database/migration_helpers_spec.rb
+++ b/spec/lib/gitlab/database/migration_helpers_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Database::MigrationHelpers do
+RSpec.describe Gitlab::Database::MigrationHelpers do
let(:model) do
ActiveRecord::Migration.new.extend(described_class)
end
@@ -178,6 +178,19 @@ describe Gitlab::Database::MigrationHelpers do
model.remove_concurrent_index_by_name(:users, "index_x_by_y")
end
+
+ it 'removes the index with keyword arguments' do
+ expect(model).to receive(:remove_index)
+ .with(:users, { algorithm: :concurrently, name: "index_x_by_y" })
+
+ model.remove_concurrent_index_by_name(:users, name: "index_x_by_y")
+ end
+
+ it 'raises an error if the index is blank' do
+ expect do
+ model.remove_concurrent_index_by_name(:users, wrong_key: "index_x_by_y")
+ end.to raise_error 'remove_concurrent_index_by_name must get an index name as the second argument'
+ end
end
end
end
@@ -690,12 +703,28 @@ describe Gitlab::Database::MigrationHelpers do
model.rename_column_concurrently(:users, :old, :new)
end
+ context 'with existing records and type casting' do
+ let(:trigger_name) { model.rename_trigger_name(:users, :id, :new) }
+ let(:user) { create(:user) }
+
+ it 'copies the value to the new column using the type_cast_function', :aggregate_failures do
+ expect(model).to receive(:copy_indexes).with(:users, :id, :new)
+ expect(model).to receive(:add_not_null_constraint).with(:users, :new)
+ expect(model).to receive(:execute).with("UPDATE \"users\" SET \"new\" = cast_to_jsonb_with_default(\"users\".\"id\") WHERE \"users\".\"id\" >= #{user.id}")
+ expect(model).to receive(:execute).with("DROP TRIGGER IF EXISTS #{trigger_name}\nON \"users\"\n")
+ expect(model).to receive(:execute).with("CREATE TRIGGER #{trigger_name}\nBEFORE INSERT OR UPDATE\nON \"users\"\nFOR EACH ROW\nEXECUTE PROCEDURE #{trigger_name}()\n")
+ expect(model).to receive(:execute).with("CREATE OR REPLACE FUNCTION #{trigger_name}()\nRETURNS trigger AS\n$BODY$\nBEGIN\n NEW.\"new\" := NEW.\"id\";\n RETURN NEW;\nEND;\n$BODY$\nLANGUAGE 'plpgsql'\nVOLATILE\n")
+
+ model.rename_column_concurrently(:users, :id, :new, type_cast_function: 'cast_to_jsonb_with_default')
+ end
+ end
+
it 'passes the batch_column_name' do
expect(model).to receive(:column_exists?).with(:users, :other_batch_column).and_return(true)
expect(model).to receive(:check_trigger_permissions!).and_return(true)
expect(model).to receive(:create_column_from).with(
- :users, :old, :new, type: nil, batch_column_name: :other_batch_column
+ :users, :old, :new, type: nil, batch_column_name: :other_batch_column, type_cast_function: nil
).and_return(true)
expect(model).to receive(:install_rename_triggers).and_return(true)
@@ -703,6 +732,14 @@ describe Gitlab::Database::MigrationHelpers do
model.rename_column_concurrently(:users, :old, :new, batch_column_name: :other_batch_column)
end
+ it 'passes the type_cast_function' do
+ expect(model).to receive(:create_column_from).with(
+ :users, :old, :new, type: nil, batch_column_name: :id, type_cast_function: 'JSON'
+ ).and_return(true)
+
+ model.rename_column_concurrently(:users, :old, :new, type_cast_function: 'JSON')
+ end
+
it 'raises an error with invalid batch_column_name' do
expect do
model.rename_column_concurrently(:users, :old, :new, batch_column_name: :invalid)
@@ -866,10 +903,19 @@ describe Gitlab::Database::MigrationHelpers do
describe '#change_column_type_concurrently' do
it 'changes the column type' do
expect(model).to receive(:rename_column_concurrently)
- .with('users', 'username', 'username_for_type_change', type: :text)
+ .with('users', 'username', 'username_for_type_change', type: :text, type_cast_function: nil)
model.change_column_type_concurrently('users', 'username', :text)
end
+
+ context 'with type cast' do
+ it 'changes the column type with casting the value to the new type' do
+ expect(model).to receive(:rename_column_concurrently)
+ .with('users', 'username', 'username_for_type_change', type: :text, type_cast_function: 'JSON')
+
+ model.change_column_type_concurrently('users', 'username', :text, type_cast_function: 'JSON')
+ end
+ end
end
describe '#cleanup_concurrent_column_type_change' do
@@ -1215,166 +1261,6 @@ describe Gitlab::Database::MigrationHelpers do
end
end
- describe '#bulk_queue_background_migration_jobs_by_range' do
- context 'when the model has an ID column' do
- let!(:id1) { create(:user).id }
- let!(:id2) { create(:user).id }
- let!(:id3) { create(:user).id }
-
- before do
- User.class_eval do
- include EachBatch
- end
- end
-
- context 'with enough rows to bulk queue jobs more than once' do
- before do
- stub_const('Gitlab::Database::MigrationHelpers::BACKGROUND_MIGRATION_JOB_BUFFER_SIZE', 1)
- end
-
- it 'queues jobs correctly' do
- Sidekiq::Testing.fake! do
- model.bulk_queue_background_migration_jobs_by_range(User, 'FooJob', batch_size: 2)
-
- expect(BackgroundMigrationWorker.jobs[0]['args']).to eq(['FooJob', [id1, id2]])
- expect(BackgroundMigrationWorker.jobs[1]['args']).to eq(['FooJob', [id3, id3]])
- end
- end
-
- it 'queues jobs in groups of buffer size 1' do
- expect(BackgroundMigrationWorker).to receive(:bulk_perform_async).with([['FooJob', [id1, id2]]])
- expect(BackgroundMigrationWorker).to receive(:bulk_perform_async).with([['FooJob', [id3, id3]]])
-
- model.bulk_queue_background_migration_jobs_by_range(User, 'FooJob', batch_size: 2)
- end
- end
-
- context 'with not enough rows to bulk queue jobs more than once' do
- it 'queues jobs correctly' do
- Sidekiq::Testing.fake! do
- model.bulk_queue_background_migration_jobs_by_range(User, 'FooJob', batch_size: 2)
-
- expect(BackgroundMigrationWorker.jobs[0]['args']).to eq(['FooJob', [id1, id2]])
- expect(BackgroundMigrationWorker.jobs[1]['args']).to eq(['FooJob', [id3, id3]])
- end
- end
-
- it 'queues jobs in bulk all at once (big buffer size)' do
- expect(BackgroundMigrationWorker).to receive(:bulk_perform_async).with([['FooJob', [id1, id2]],
- ['FooJob', [id3, id3]]])
-
- model.bulk_queue_background_migration_jobs_by_range(User, 'FooJob', batch_size: 2)
- end
- end
-
- context 'without specifying batch_size' do
- it 'queues jobs correctly' do
- Sidekiq::Testing.fake! do
- model.bulk_queue_background_migration_jobs_by_range(User, 'FooJob')
-
- expect(BackgroundMigrationWorker.jobs[0]['args']).to eq(['FooJob', [id1, id3]])
- end
- end
- end
- end
-
- context "when the model doesn't have an ID column" do
- it 'raises error (for now)' do
- expect do
- model.bulk_queue_background_migration_jobs_by_range(ProjectAuthorization, 'FooJob')
- end.to raise_error(StandardError, /does not have an ID/)
- end
- end
- end
-
- describe '#queue_background_migration_jobs_by_range_at_intervals' do
- context 'when the model has an ID column' do
- let!(:id1) { create(:user).id }
- let!(:id2) { create(:user).id }
- let!(:id3) { create(:user).id }
-
- around do |example|
- Timecop.freeze { example.run }
- end
-
- before do
- User.class_eval do
- include EachBatch
- end
- end
-
- it 'returns the final expected delay' do
- Sidekiq::Testing.fake! do
- final_delay = model.queue_background_migration_jobs_by_range_at_intervals(User, 'FooJob', 10.minutes, batch_size: 2)
-
- expect(final_delay.to_f).to eq(20.minutes.to_f)
- end
- end
-
- it 'returns zero when nothing gets queued' do
- Sidekiq::Testing.fake! do
- final_delay = model.queue_background_migration_jobs_by_range_at_intervals(User.none, 'FooJob', 10.minutes)
-
- expect(final_delay).to eq(0)
- end
- end
-
- context 'with batch_size option' do
- it 'queues jobs correctly' do
- Sidekiq::Testing.fake! do
- model.queue_background_migration_jobs_by_range_at_intervals(User, 'FooJob', 10.minutes, batch_size: 2)
-
- expect(BackgroundMigrationWorker.jobs[0]['args']).to eq(['FooJob', [id1, id2]])
- expect(BackgroundMigrationWorker.jobs[0]['at']).to eq(10.minutes.from_now.to_f)
- expect(BackgroundMigrationWorker.jobs[1]['args']).to eq(['FooJob', [id3, id3]])
- expect(BackgroundMigrationWorker.jobs[1]['at']).to eq(20.minutes.from_now.to_f)
- end
- end
- end
-
- context 'without batch_size option' do
- it 'queues jobs correctly' do
- Sidekiq::Testing.fake! do
- model.queue_background_migration_jobs_by_range_at_intervals(User, 'FooJob', 10.minutes)
-
- expect(BackgroundMigrationWorker.jobs[0]['args']).to eq(['FooJob', [id1, id3]])
- expect(BackgroundMigrationWorker.jobs[0]['at']).to eq(10.minutes.from_now.to_f)
- end
- end
- end
-
- context 'with other_job_arguments option' do
- it 'queues jobs correctly' do
- Sidekiq::Testing.fake! do
- model.queue_background_migration_jobs_by_range_at_intervals(User, 'FooJob', 10.minutes, other_job_arguments: [1, 2])
-
- expect(BackgroundMigrationWorker.jobs[0]['args']).to eq(['FooJob', [id1, id3, 1, 2]])
- expect(BackgroundMigrationWorker.jobs[0]['at']).to eq(10.minutes.from_now.to_f)
- end
- end
- end
-
- context 'with initial_delay option' do
- it 'queues jobs correctly' do
- Sidekiq::Testing.fake! do
- model.queue_background_migration_jobs_by_range_at_intervals(User, 'FooJob', 10.minutes, other_job_arguments: [1, 2], initial_delay: 10.minutes)
-
- expect(BackgroundMigrationWorker.jobs[0]['args']).to eq(['FooJob', [id1, id3, 1, 2]])
- expect(BackgroundMigrationWorker.jobs[0]['at']).to eq(20.minutes.from_now.to_f)
- end
- end
- end
- end
-
- context "when the model doesn't have an ID column" do
- it 'raises error (for now)' do
- expect do
- model.queue_background_migration_jobs_by_range_at_intervals(ProjectAuthorization, 'FooJob', 10.seconds)
- end.to raise_error(StandardError, /does not have an ID/)
- end
- end
- end
-
describe '#change_column_type_using_background_migration' do
let!(:issue) { create(:issue, :closed, closed_at: Time.zone.now) }
@@ -1485,26 +1371,6 @@ describe Gitlab::Database::MigrationHelpers do
end
end
- describe '#perform_background_migration_inline?' do
- it 'returns true in a test environment' do
- stub_rails_env('test')
-
- expect(model.perform_background_migration_inline?).to eq(true)
- end
-
- it 'returns true in a development environment' do
- stub_rails_env('development')
-
- expect(model.perform_background_migration_inline?).to eq(true)
- end
-
- it 'returns false in a production environment' do
- stub_rails_env('production')
-
- expect(model.perform_background_migration_inline?).to eq(false)
- end
- end
-
describe '#index_exists_by_name?' do
it 'returns true if an index exists' do
ActiveRecord::Base.connection.execute(
@@ -1973,62 +1839,6 @@ describe Gitlab::Database::MigrationHelpers do
end
end
- describe '#migrate_async' do
- it 'calls BackgroundMigrationWorker.perform_async' do
- expect(BackgroundMigrationWorker).to receive(:perform_async).with("Class", "hello", "world")
-
- model.migrate_async("Class", "hello", "world")
- end
-
- it 'pushes a context with the current class name as caller_id' do
- expect(Gitlab::ApplicationContext).to receive(:with_context).with(caller_id: model.class.to_s)
-
- model.migrate_async('Class', 'hello', 'world')
- end
- end
-
- describe '#migrate_in' do
- it 'calls BackgroundMigrationWorker.perform_in' do
- expect(BackgroundMigrationWorker).to receive(:perform_in).with(10.minutes, 'Class', 'Hello', 'World')
-
- model.migrate_in(10.minutes, 'Class', 'Hello', 'World')
- end
-
- it 'pushes a context with the current class name as caller_id' do
- expect(Gitlab::ApplicationContext).to receive(:with_context).with(caller_id: model.class.to_s)
-
- model.migrate_in(10.minutes, 'Class', 'Hello', 'World')
- end
- end
-
- describe '#bulk_migrate_async' do
- it 'calls BackgroundMigrationWorker.bulk_perform_async' do
- expect(BackgroundMigrationWorker).to receive(:bulk_perform_async).with([%w(Class hello world)])
-
- model.bulk_migrate_async([%w(Class hello world)])
- end
-
- it 'pushes a context with the current class name as caller_id' do
- expect(Gitlab::ApplicationContext).to receive(:with_context).with(caller_id: model.class.to_s)
-
- model.bulk_migrate_async([%w(Class hello world)])
- end
- end
-
- describe '#bulk_migrate_in' do
- it 'calls BackgroundMigrationWorker.bulk_perform_in_' do
- expect(BackgroundMigrationWorker).to receive(:bulk_perform_in).with(10.minutes, [%w(Class hello world)])
-
- model.bulk_migrate_in(10.minutes, [%w(Class hello world)])
- end
-
- it 'pushes a context with the current class name as caller_id' do
- expect(Gitlab::ApplicationContext).to receive(:with_context).with(caller_id: model.class.to_s)
-
- model.bulk_migrate_in(10.minutes, [%w(Class hello world)])
- end
- end
-
describe '#check_constraint_name' do
it 'returns a valid constraint name' do
name = model.check_constraint_name(:this_is_a_very_long_table_name,
diff --git a/spec/lib/gitlab/database/migrations/background_migration_helpers_spec.rb b/spec/lib/gitlab/database/migrations/background_migration_helpers_spec.rb
new file mode 100644
index 00000000000..042ac498373
--- /dev/null
+++ b/spec/lib/gitlab/database/migrations/background_migration_helpers_spec.rb
@@ -0,0 +1,276 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Database::Migrations::BackgroundMigrationHelpers do
+ let(:model) do
+ ActiveRecord::Migration.new.extend(described_class)
+ end
+
+ describe '#bulk_queue_background_migration_jobs_by_range' do
+ context 'when the model has an ID column' do
+ let!(:id1) { create(:user).id }
+ let!(:id2) { create(:user).id }
+ let!(:id3) { create(:user).id }
+
+ before do
+ User.class_eval do
+ include EachBatch
+ end
+ end
+
+ context 'with enough rows to bulk queue jobs more than once' do
+ before do
+ stub_const('Gitlab::Database::Migrations::BackgroundMigrationHelpers::BACKGROUND_MIGRATION_JOB_BUFFER_SIZE', 1)
+ end
+
+ it 'queues jobs correctly' do
+ Sidekiq::Testing.fake! do
+ model.bulk_queue_background_migration_jobs_by_range(User, 'FooJob', batch_size: 2)
+
+ expect(BackgroundMigrationWorker.jobs[0]['args']).to eq(['FooJob', [id1, id2]])
+ expect(BackgroundMigrationWorker.jobs[1]['args']).to eq(['FooJob', [id3, id3]])
+ end
+ end
+
+ it 'queues jobs in groups of buffer size 1' do
+ expect(BackgroundMigrationWorker).to receive(:bulk_perform_async).with([['FooJob', [id1, id2]]])
+ expect(BackgroundMigrationWorker).to receive(:bulk_perform_async).with([['FooJob', [id3, id3]]])
+
+ model.bulk_queue_background_migration_jobs_by_range(User, 'FooJob', batch_size: 2)
+ end
+ end
+
+ context 'with not enough rows to bulk queue jobs more than once' do
+ it 'queues jobs correctly' do
+ Sidekiq::Testing.fake! do
+ model.bulk_queue_background_migration_jobs_by_range(User, 'FooJob', batch_size: 2)
+
+ expect(BackgroundMigrationWorker.jobs[0]['args']).to eq(['FooJob', [id1, id2]])
+ expect(BackgroundMigrationWorker.jobs[1]['args']).to eq(['FooJob', [id3, id3]])
+ end
+ end
+
+ it 'queues jobs in bulk all at once (big buffer size)' do
+ expect(BackgroundMigrationWorker).to receive(:bulk_perform_async).with([['FooJob', [id1, id2]],
+ ['FooJob', [id3, id3]]])
+
+ model.bulk_queue_background_migration_jobs_by_range(User, 'FooJob', batch_size: 2)
+ end
+ end
+
+ context 'without specifying batch_size' do
+ it 'queues jobs correctly' do
+ Sidekiq::Testing.fake! do
+ model.bulk_queue_background_migration_jobs_by_range(User, 'FooJob')
+
+ expect(BackgroundMigrationWorker.jobs[0]['args']).to eq(['FooJob', [id1, id3]])
+ end
+ end
+ end
+ end
+
+ context "when the model doesn't have an ID column" do
+ it 'raises error (for now)' do
+ expect do
+ model.bulk_queue_background_migration_jobs_by_range(ProjectAuthorization, 'FooJob')
+ end.to raise_error(StandardError, /does not have an ID/)
+ end
+ end
+ end
+
+ describe '#queue_background_migration_jobs_by_range_at_intervals' do
+ context 'when the model has an ID column' do
+ let!(:id1) { create(:user).id }
+ let!(:id2) { create(:user).id }
+ let!(:id3) { create(:user).id }
+
+ around do |example|
+ Timecop.freeze { example.run }
+ end
+
+ before do
+ User.class_eval do
+ include EachBatch
+ end
+ end
+
+ it 'returns the final expected delay' do
+ Sidekiq::Testing.fake! do
+ final_delay = model.queue_background_migration_jobs_by_range_at_intervals(User, 'FooJob', 10.minutes, batch_size: 2)
+
+ expect(final_delay.to_f).to eq(20.minutes.to_f)
+ end
+ end
+
+ it 'returns zero when nothing gets queued' do
+ Sidekiq::Testing.fake! do
+ final_delay = model.queue_background_migration_jobs_by_range_at_intervals(User.none, 'FooJob', 10.minutes)
+
+ expect(final_delay).to eq(0)
+ end
+ end
+
+ context 'with batch_size option' do
+ it 'queues jobs correctly' do
+ Sidekiq::Testing.fake! do
+ model.queue_background_migration_jobs_by_range_at_intervals(User, 'FooJob', 10.minutes, batch_size: 2)
+
+ expect(BackgroundMigrationWorker.jobs[0]['args']).to eq(['FooJob', [id1, id2]])
+ expect(BackgroundMigrationWorker.jobs[0]['at']).to eq(10.minutes.from_now.to_f)
+ expect(BackgroundMigrationWorker.jobs[1]['args']).to eq(['FooJob', [id3, id3]])
+ expect(BackgroundMigrationWorker.jobs[1]['at']).to eq(20.minutes.from_now.to_f)
+ end
+ end
+ end
+
+ context 'without batch_size option' do
+ it 'queues jobs correctly' do
+ Sidekiq::Testing.fake! do
+ model.queue_background_migration_jobs_by_range_at_intervals(User, 'FooJob', 10.minutes)
+
+ expect(BackgroundMigrationWorker.jobs[0]['args']).to eq(['FooJob', [id1, id3]])
+ expect(BackgroundMigrationWorker.jobs[0]['at']).to eq(10.minutes.from_now.to_f)
+ end
+ end
+ end
+
+ context 'with other_job_arguments option' do
+ it 'queues jobs correctly' do
+ Sidekiq::Testing.fake! do
+ model.queue_background_migration_jobs_by_range_at_intervals(User, 'FooJob', 10.minutes, other_job_arguments: [1, 2])
+
+ expect(BackgroundMigrationWorker.jobs[0]['args']).to eq(['FooJob', [id1, id3, 1, 2]])
+ expect(BackgroundMigrationWorker.jobs[0]['at']).to eq(10.minutes.from_now.to_f)
+ end
+ end
+ end
+
+ context 'with initial_delay option' do
+ it 'queues jobs correctly' do
+ Sidekiq::Testing.fake! do
+ model.queue_background_migration_jobs_by_range_at_intervals(User, 'FooJob', 10.minutes, other_job_arguments: [1, 2], initial_delay: 10.minutes)
+
+ expect(BackgroundMigrationWorker.jobs[0]['args']).to eq(['FooJob', [id1, id3, 1, 2]])
+ expect(BackgroundMigrationWorker.jobs[0]['at']).to eq(20.minutes.from_now.to_f)
+ end
+ end
+ end
+
+ context 'with track_jobs option' do
+ it 'creates a record for each job in the database' do
+ Sidekiq::Testing.fake! do
+ expect do
+ model.queue_background_migration_jobs_by_range_at_intervals(User, '::FooJob', 10.minutes,
+ other_job_arguments: [1, 2], track_jobs: true)
+ end.to change { Gitlab::Database::BackgroundMigrationJob.count }.from(0).to(1)
+
+ expect(BackgroundMigrationWorker.jobs.size).to eq(1)
+
+ tracked_job = Gitlab::Database::BackgroundMigrationJob.first
+
+ expect(tracked_job.class_name).to eq('FooJob')
+ expect(tracked_job.arguments).to eq([id1, id3, 1, 2])
+ expect(tracked_job).to be_pending
+ end
+ end
+ end
+
+ context 'without track_jobs option' do
+ it 'does not create records in the database' do
+ Sidekiq::Testing.fake! do
+ expect do
+ model.queue_background_migration_jobs_by_range_at_intervals(User, 'FooJob', 10.minutes, other_job_arguments: [1, 2])
+ end.not_to change { Gitlab::Database::BackgroundMigrationJob.count }
+
+ expect(BackgroundMigrationWorker.jobs.size).to eq(1)
+ end
+ end
+ end
+ end
+
+ context "when the model doesn't have an ID column" do
+ it 'raises error (for now)' do
+ expect do
+ model.queue_background_migration_jobs_by_range_at_intervals(ProjectAuthorization, 'FooJob', 10.seconds)
+ end.to raise_error(StandardError, /does not have an ID/)
+ end
+ end
+ end
+
+ describe '#perform_background_migration_inline?' do
+ it 'returns true in a test environment' do
+ stub_rails_env('test')
+
+ expect(model.perform_background_migration_inline?).to eq(true)
+ end
+
+ it 'returns true in a development environment' do
+ stub_rails_env('development')
+
+ expect(model.perform_background_migration_inline?).to eq(true)
+ end
+
+ it 'returns false in a production environment' do
+ stub_rails_env('production')
+
+ expect(model.perform_background_migration_inline?).to eq(false)
+ end
+ end
+
+ describe '#migrate_async' do
+ it 'calls BackgroundMigrationWorker.perform_async' do
+ expect(BackgroundMigrationWorker).to receive(:perform_async).with("Class", "hello", "world")
+
+ model.migrate_async("Class", "hello", "world")
+ end
+
+ it 'pushes a context with the current class name as caller_id' do
+ expect(Gitlab::ApplicationContext).to receive(:with_context).with(caller_id: model.class.to_s)
+
+ model.migrate_async('Class', 'hello', 'world')
+ end
+ end
+
+ describe '#migrate_in' do
+ it 'calls BackgroundMigrationWorker.perform_in' do
+ expect(BackgroundMigrationWorker).to receive(:perform_in).with(10.minutes, 'Class', 'Hello', 'World')
+
+ model.migrate_in(10.minutes, 'Class', 'Hello', 'World')
+ end
+
+ it 'pushes a context with the current class name as caller_id' do
+ expect(Gitlab::ApplicationContext).to receive(:with_context).with(caller_id: model.class.to_s)
+
+ model.migrate_in(10.minutes, 'Class', 'Hello', 'World')
+ end
+ end
+
+ describe '#bulk_migrate_async' do
+ it 'calls BackgroundMigrationWorker.bulk_perform_async' do
+ expect(BackgroundMigrationWorker).to receive(:bulk_perform_async).with([%w(Class hello world)])
+
+ model.bulk_migrate_async([%w(Class hello world)])
+ end
+
+ it 'pushes a context with the current class name as caller_id' do
+ expect(Gitlab::ApplicationContext).to receive(:with_context).with(caller_id: model.class.to_s)
+
+ model.bulk_migrate_async([%w(Class hello world)])
+ end
+ end
+
+ describe '#bulk_migrate_in' do
+ it 'calls BackgroundMigrationWorker.bulk_perform_in_' do
+ expect(BackgroundMigrationWorker).to receive(:bulk_perform_in).with(10.minutes, [%w(Class hello world)])
+
+ model.bulk_migrate_in(10.minutes, [%w(Class hello world)])
+ end
+
+ it 'pushes a context with the current class name as caller_id' do
+ expect(Gitlab::ApplicationContext).to receive(:with_context).with(caller_id: model.class.to_s)
+
+ model.bulk_migrate_in(10.minutes, [%w(Class hello world)])
+ end
+ end
+end
diff --git a/spec/lib/gitlab/database/multi_threaded_migration_spec.rb b/spec/lib/gitlab/database/multi_threaded_migration_spec.rb
index 53c001fbc1b..78dd9e88064 100644
--- a/spec/lib/gitlab/database/multi_threaded_migration_spec.rb
+++ b/spec/lib/gitlab/database/multi_threaded_migration_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Database::MultiThreadedMigration do
+RSpec.describe Gitlab::Database::MultiThreadedMigration do
let(:migration) do
Class.new { include Gitlab::Database::MultiThreadedMigration }.new
end
diff --git a/spec/lib/gitlab/database/obsolete_ignored_columns_spec.rb b/spec/lib/gitlab/database/obsolete_ignored_columns_spec.rb
index dee1d7df1a9..034bf966db7 100644
--- a/spec/lib/gitlab/database/obsolete_ignored_columns_spec.rb
+++ b/spec/lib/gitlab/database/obsolete_ignored_columns_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Database::ObsoleteIgnoredColumns do
+RSpec.describe Gitlab::Database::ObsoleteIgnoredColumns do
before do
stub_const('Testing', Module.new)
stub_const('Testing::MyBase', Class.new(ActiveRecord::Base))
diff --git a/spec/lib/gitlab/database/partitioning/monthly_strategy_spec.rb b/spec/lib/gitlab/database/partitioning/monthly_strategy_spec.rb
new file mode 100644
index 00000000000..334cac653cf
--- /dev/null
+++ b/spec/lib/gitlab/database/partitioning/monthly_strategy_spec.rb
@@ -0,0 +1,153 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Database::Partitioning::MonthlyStrategy do
+ describe '#current_partitions' do
+ subject { described_class.new(model, partitioning_key).current_partitions }
+
+ let(:model) { double('model', table_name: table_name) }
+ let(:partitioning_key) { double }
+ let(:table_name) { :partitioned_test }
+
+ before do
+ ActiveRecord::Base.connection.execute(<<~SQL)
+ CREATE TABLE #{table_name}
+ (id serial not null, created_at timestamptz not null, PRIMARY KEY (id, created_at))
+ PARTITION BY RANGE (created_at);
+
+ CREATE TABLE #{Gitlab::Database::DYNAMIC_PARTITIONS_SCHEMA}.partitioned_test_000000
+ PARTITION OF #{table_name}
+ FOR VALUES FROM (MINVALUE) TO ('2020-05-01');
+
+ CREATE TABLE #{Gitlab::Database::DYNAMIC_PARTITIONS_SCHEMA}.partitioned_test_202005
+ PARTITION OF #{table_name}
+ FOR VALUES FROM ('2020-05-01') TO ('2020-06-01');
+ SQL
+ end
+
+ it 'detects both partitions' do
+ expect(subject).to eq([
+ Gitlab::Database::Partitioning::TimePartition.new(table_name, nil, '2020-05-01', partition_name: 'partitioned_test_000000'),
+ Gitlab::Database::Partitioning::TimePartition.new(table_name, '2020-05-01', '2020-06-01', partition_name: 'partitioned_test_202005')
+ ])
+ end
+ end
+
+ describe '#missing_partitions' do
+ subject { described_class.new(model, partitioning_key).missing_partitions }
+
+ let(:model) do
+ Class.new(ActiveRecord::Base) do
+ self.table_name = 'partitioned_test'
+ self.primary_key = :id
+ end
+ end
+
+ let(:partitioning_key) { :created_at }
+
+ around do |example|
+ Timecop.freeze(Date.parse('2020-08-22')) { example.run }
+ end
+
+ context 'with existing partitions' do
+ before do
+ ActiveRecord::Base.connection.execute(<<~SQL)
+ CREATE TABLE #{model.table_name}
+ (id serial not null, created_at timestamptz not null, PRIMARY KEY (id, created_at))
+ PARTITION BY RANGE (created_at);
+
+ CREATE TABLE #{Gitlab::Database::DYNAMIC_PARTITIONS_SCHEMA}.partitioned_test_000000
+ PARTITION OF #{model.table_name}
+ FOR VALUES FROM (MINVALUE) TO ('2020-05-01');
+
+ CREATE TABLE #{Gitlab::Database::DYNAMIC_PARTITIONS_SCHEMA}.partitioned_test_202006
+ PARTITION OF #{model.table_name}
+ FOR VALUES FROM ('2020-06-01') TO ('2020-07-01');
+ SQL
+
+ # Insert some data, it doesn't make a difference
+ model.create!(created_at: Date.parse('2020-04-20'))
+ model.create!(created_at: Date.parse('2020-06-15'))
+ end
+
+ it 'detects the gap and the missing partition in May 2020' do
+ expect(subject).to include(Gitlab::Database::Partitioning::TimePartition.new(model.table_name, '2020-05-01', '2020-06-01'))
+ end
+
+ it 'detects the missing partitions at the end of the range and expects a partition for July 2020' do
+ expect(subject).to include(Gitlab::Database::Partitioning::TimePartition.new(model.table_name, '2020-07-01', '2020-08-01'))
+ end
+
+ it 'detects the missing partitions at the end of the range and expects a partition for August 2020' do
+ expect(subject).to include(Gitlab::Database::Partitioning::TimePartition.new(model.table_name, '2020-08-01', '2020-09-01'))
+ end
+
+ it 'creates partitions 6 months out from now (Sep 2020 through Feb 2021)' do
+ expect(subject).to include(
+ Gitlab::Database::Partitioning::TimePartition.new(model.table_name, '2020-09-01', '2020-10-01'),
+ Gitlab::Database::Partitioning::TimePartition.new(model.table_name, '2020-10-01', '2020-11-01'),
+ Gitlab::Database::Partitioning::TimePartition.new(model.table_name, '2020-11-01', '2020-12-01'),
+ Gitlab::Database::Partitioning::TimePartition.new(model.table_name, '2020-12-01', '2021-01-01'),
+ Gitlab::Database::Partitioning::TimePartition.new(model.table_name, '2021-01-01', '2021-02-01'),
+ Gitlab::Database::Partitioning::TimePartition.new(model.table_name, '2021-02-01', '2021-03-01')
+ )
+ end
+
+ it 'detects all missing partitions' do
+ expect(subject.size).to eq(9)
+ end
+ end
+
+ context 'without existing partitions' do
+ before do
+ ActiveRecord::Base.connection.execute(<<~SQL)
+ CREATE TABLE #{model.table_name}
+ (id serial not null, created_at timestamptz not null, PRIMARY KEY (id, created_at))
+ PARTITION BY RANGE (created_at);
+ SQL
+ end
+
+ it 'detects the missing catch-all partition at the beginning' do
+ expect(subject).to include(Gitlab::Database::Partitioning::TimePartition.new(model.table_name, nil, '2020-08-01'))
+ end
+
+ it 'detects the missing partition for today and expects a partition for August 2020' do
+ expect(subject).to include(Gitlab::Database::Partitioning::TimePartition.new(model.table_name, '2020-08-01', '2020-09-01'))
+ end
+
+ it 'creates partitions 6 months out from now (Sep 2020 through Feb 2021' do
+ expect(subject).to include(
+ Gitlab::Database::Partitioning::TimePartition.new(model.table_name, '2020-09-01', '2020-10-01'),
+ Gitlab::Database::Partitioning::TimePartition.new(model.table_name, '2020-10-01', '2020-11-01'),
+ Gitlab::Database::Partitioning::TimePartition.new(model.table_name, '2020-11-01', '2020-12-01'),
+ Gitlab::Database::Partitioning::TimePartition.new(model.table_name, '2020-12-01', '2021-01-01'),
+ Gitlab::Database::Partitioning::TimePartition.new(model.table_name, '2021-01-01', '2021-02-01'),
+ Gitlab::Database::Partitioning::TimePartition.new(model.table_name, '2021-02-01', '2021-03-01')
+ )
+ end
+
+ it 'detects all missing partitions' do
+ expect(subject.size).to eq(8)
+ end
+ end
+
+ context 'with a regular partition but no catchall (MINVALUE, to) partition' do
+ before do
+ ActiveRecord::Base.connection.execute(<<~SQL)
+ CREATE TABLE #{model.table_name}
+ (id serial not null, created_at timestamptz not null, PRIMARY KEY (id, created_at))
+ PARTITION BY RANGE (created_at);
+
+ CREATE TABLE #{Gitlab::Database::DYNAMIC_PARTITIONS_SCHEMA}.partitioned_test_202006
+ PARTITION OF #{model.table_name}
+ FOR VALUES FROM ('2020-06-01') TO ('2020-07-01');
+ SQL
+ end
+
+ it 'detects a missing catch-all partition to add before the existing partition' do
+ expect(subject).to include(Gitlab::Database::Partitioning::TimePartition.new(model.table_name, nil, '2020-06-01'))
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/database/partitioning/partition_creator_spec.rb b/spec/lib/gitlab/database/partitioning/partition_creator_spec.rb
new file mode 100644
index 00000000000..56399941662
--- /dev/null
+++ b/spec/lib/gitlab/database/partitioning/partition_creator_spec.rb
@@ -0,0 +1,96 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Database::Partitioning::PartitionCreator do
+ include PartitioningHelpers
+ include ExclusiveLeaseHelpers
+
+ describe '.register' do
+ let(:model) { double(partitioning_strategy: nil) }
+
+ it 'remembers registered models' do
+ expect { described_class.register(model) }.to change { described_class.models }.to include(model)
+ end
+ end
+
+ describe '#create_partitions (mocked)' do
+ subject { described_class.new(models).create_partitions }
+
+ let(:models) { [model] }
+ let(:model) { double(partitioning_strategy: partitioning_strategy, table_name: table) }
+ let(:partitioning_strategy) { double(missing_partitions: partitions) }
+ let(:table) { "some_table" }
+
+ before do
+ allow(ActiveRecord::Base.connection).to receive(:table_exists?).and_call_original
+ allow(ActiveRecord::Base.connection).to receive(:table_exists?).with(table).and_return(true)
+ allow(ActiveRecord::Base.connection).to receive(:execute).and_call_original
+
+ stub_exclusive_lease(described_class::LEASE_KEY % table, timeout: described_class::LEASE_TIMEOUT)
+ end
+
+ let(:partitions) do
+ [
+ instance_double(Gitlab::Database::Partitioning::TimePartition, table: 'bar', partition_name: 'foo', to_sql: "SELECT 1"),
+ instance_double(Gitlab::Database::Partitioning::TimePartition, table: 'bar', partition_name: 'foo2', to_sql: "SELECT 2")
+ ]
+ end
+
+ it 'creates the partition' do
+ expect(ActiveRecord::Base.connection).to receive(:execute).with(partitions.first.to_sql)
+ expect(ActiveRecord::Base.connection).to receive(:execute).with(partitions.second.to_sql)
+
+ subject
+ end
+
+ context 'error handling with 2 models' do
+ let(:models) do
+ [
+ double(partitioning_strategy: strategy1, table_name: table),
+ double(partitioning_strategy: strategy2, table_name: table)
+ ]
+ end
+
+ let(:strategy1) { double('strategy1', missing_partitions: nil) }
+ let(:strategy2) { double('strategy2', missing_partitions: partitions) }
+
+ it 'still creates partitions for the second table' do
+ expect(strategy1).to receive(:missing_partitions).and_raise('this should never happen (tm)')
+ expect(ActiveRecord::Base.connection).to receive(:execute).with(partitions.first.to_sql)
+ expect(ActiveRecord::Base.connection).to receive(:execute).with(partitions.second.to_sql)
+
+ subject
+ end
+ end
+ end
+
+ describe '#create_partitions' do
+ subject { described_class.new([my_model]).create_partitions }
+
+ let(:connection) { ActiveRecord::Base.connection }
+ let(:my_model) do
+ Class.new(ApplicationRecord) do
+ include PartitionedTable
+
+ self.table_name = 'my_model_example_table'
+
+ partitioned_by :created_at, strategy: :monthly
+ end
+ end
+
+ before do
+ connection.execute(<<~SQL)
+ CREATE TABLE my_model_example_table
+ (id serial not null, created_at timestamptz not null, primary key (id, created_at))
+ PARTITION BY RANGE (created_at);
+ SQL
+ end
+
+ it 'creates partitions' do
+ expect { subject }.to change { find_partitions(my_model.table_name, schema: Gitlab::Database::DYNAMIC_PARTITIONS_SCHEMA).size }.from(0)
+
+ subject
+ end
+ end
+end
diff --git a/spec/lib/gitlab/database/partitioning/time_partition_spec.rb b/spec/lib/gitlab/database/partitioning/time_partition_spec.rb
new file mode 100644
index 00000000000..700202d81c5
--- /dev/null
+++ b/spec/lib/gitlab/database/partitioning/time_partition_spec.rb
@@ -0,0 +1,174 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Database::Partitioning::TimePartition do
+ describe '.from_sql' do
+ subject { described_class.from_sql(table, partition_name, definition) }
+
+ let(:table) { 'foo' }
+ let(:partition_name) { 'foo_bar' }
+ let(:definition) { 'FOR VALUES FROM (\'2020-04-01 00:00:00\') TO (\'2020-05-01 00:00:00\')' }
+
+ it 'uses specified table name' do
+ expect(subject.table).to eq(table)
+ end
+
+ it 'uses specified partition name' do
+ expect(subject.partition_name).to eq(partition_name)
+ end
+
+ it 'parses start date' do
+ expect(subject.from).to eq(Date.parse('2020-04-01'))
+ end
+
+ it 'parses end date' do
+ expect(subject.to).to eq(Date.parse('2020-05-01'))
+ end
+
+ context 'with MINVALUE as a start date' do
+ let(:definition) { 'FOR VALUES FROM (MINVALUE) TO (\'2020-05-01\')' }
+
+ it 'sets from to nil' do
+ expect(subject.from).to be_nil
+ end
+ end
+
+ context 'with MAXVALUE as an end date' do
+ let(:definition) { 'FOR VALUES FROM (\'2020-04-01\') TO (MAXVALUE)' }
+
+ it 'raises a NotImplementedError' do
+ expect { subject }.to raise_error(NotImplementedError)
+ end
+ end
+ end
+
+ describe '#partition_name' do
+ subject { described_class.new(table, from, to, partition_name: partition_name).partition_name }
+
+ let(:table) { 'foo' }
+ let(:from) { '2020-04-01 00:00:00' }
+ let(:to) { '2020-05-01 00:00:00' }
+ let(:partition_name) { nil }
+
+ it 'uses table as prefix' do
+ expect(subject).to start_with(table)
+ end
+
+ it 'uses Year-Month (from) as suffix' do
+ expect(subject).to end_with("_202004")
+ end
+
+ context 'without from date' do
+ let(:from) { nil }
+
+ it 'uses 000000 as suffix for first partition' do
+ expect(subject).to end_with("_000000")
+ end
+ end
+
+ context 'with partition name explicitly given' do
+ let(:partition_name) { "foo_bar" }
+
+ it 'uses given partition name' do
+ expect(subject).to eq(partition_name)
+ end
+ end
+ end
+
+ describe '#to_sql' do
+ subject { described_class.new(table, from, to).to_sql }
+
+ let(:table) { 'foo' }
+ let(:from) { '2020-04-01 00:00:00' }
+ let(:to) { '2020-05-01 00:00:00' }
+
+ it 'transforms to a CREATE TABLE statement' do
+ expect(subject).to eq(<<~SQL)
+ CREATE TABLE IF NOT EXISTS "#{Gitlab::Database::DYNAMIC_PARTITIONS_SCHEMA}"."foo_202004"
+ PARTITION OF "foo"
+ FOR VALUES FROM ('2020-04-01') TO ('2020-05-01')
+ SQL
+ end
+
+ context 'without from date' do
+ let(:from) { nil }
+
+ it 'uses MINVALUE instead' do
+ expect(subject).to eq(<<~SQL)
+ CREATE TABLE IF NOT EXISTS "#{Gitlab::Database::DYNAMIC_PARTITIONS_SCHEMA}"."foo_000000"
+ PARTITION OF "foo"
+ FOR VALUES FROM (MINVALUE) TO ('2020-05-01')
+ SQL
+ end
+ end
+ end
+
+ describe 'object equality - #eql' do
+ def expect_inequality(actual, other)
+ expect(actual.eql?(other)).to be_falsey
+ expect(actual).not_to eq(other)
+ end
+
+ def expect_equality(actual, other)
+ expect(actual).to eq(other)
+ expect(actual.eql?(other)).to be_truthy
+ expect(actual.hash).to eq(other.hash)
+ end
+
+ def make_new(table: 'foo', from: '2020-04-01 00:00:00', to: '2020-05-01 00:00:00', partition_name: 'foo_202004')
+ described_class.new(table, from, to, partition_name: partition_name)
+ end
+
+ it 'treats objects identical with identical attributes' do
+ expect_equality(make_new, make_new)
+ end
+
+ it 'different table leads to in-equality' do
+ expect_inequality(make_new, make_new(table: 'bar'))
+ end
+
+ it 'different from leads to in-equality' do
+ expect_inequality(make_new, make_new(from: '2020-05-01 00:00:00'))
+ end
+
+ it 'different to leads to in-equality' do
+ expect_inequality(make_new, make_new(to: '2020-06-01 00:00:00'))
+ end
+
+ it 'different partition_name leads to in-equality' do
+ expect_inequality(make_new, make_new(partition_name: 'different'))
+ end
+
+ it 'nil partition_name is ignored if auto-generated matches' do
+ expect_equality(make_new, make_new(partition_name: nil))
+ end
+ end
+
+ describe 'Comparable, #<=>' do
+ let(:table) { 'foo' }
+
+ it 'sorts by partition name, i.e. by month - MINVALUE partition first' do
+ partitions = [
+ described_class.new(table, '2020-04-01', '2020-05-01'),
+ described_class.new(table, '2020-02-01', '2020-03-01'),
+ described_class.new(table, nil, '2020-02-01'),
+ described_class.new(table, '2020-03-01', '2020-04-01')
+ ]
+
+ expect(partitions.sort).to eq([
+ described_class.new(table, nil, '2020-02-01'),
+ described_class.new(table, '2020-02-01', '2020-03-01'),
+ described_class.new(table, '2020-03-01', '2020-04-01'),
+ described_class.new(table, '2020-04-01', '2020-05-01')
+ ])
+ end
+
+ it 'returns nil for partitions of different tables' do
+ one = described_class.new('foo', '2020-02-01', '2020-03-01')
+ two = described_class.new('bar', '2020-02-01', '2020-03-01')
+
+ expect(one.<=>(two)).to be_nil
+ end
+ end
+end
diff --git a/spec/lib/gitlab/database/partitioning_migration_helpers/backfill_partitioned_table_spec.rb b/spec/lib/gitlab/database/partitioning_migration_helpers/backfill_partitioned_table_spec.rb
new file mode 100644
index 00000000000..49f3f87fe61
--- /dev/null
+++ b/spec/lib/gitlab/database/partitioning_migration_helpers/backfill_partitioned_table_spec.rb
@@ -0,0 +1,175 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Database::PartitioningMigrationHelpers::BackfillPartitionedTable, '#perform' do
+ subject { described_class.new }
+
+ let(:source_table) { '_test_partitioning_backfills' }
+ let(:destination_table) { "#{source_table}_part" }
+ let(:unique_key) { 'id' }
+
+ before do
+ allow(subject).to receive(:transaction_open?).and_return(false)
+ end
+
+ context 'when the destination table exists' do
+ before do
+ connection.execute(<<~SQL)
+ CREATE TABLE #{source_table} (
+ id serial NOT NULL PRIMARY KEY,
+ col1 int NOT NULL,
+ col2 text NOT NULL,
+ created_at timestamptz NOT NULL
+ )
+ SQL
+
+ connection.execute(<<~SQL)
+ CREATE TABLE #{destination_table} (
+ id serial NOT NULL,
+ col1 int NOT NULL,
+ col2 text NOT NULL,
+ created_at timestamptz NOT NULL,
+ PRIMARY KEY (id, created_at)
+ ) PARTITION BY RANGE (created_at)
+ SQL
+
+ connection.execute(<<~SQL)
+ CREATE TABLE #{destination_table}_202001 PARTITION OF #{destination_table}
+ FOR VALUES FROM ('2020-01-01') TO ('2020-02-01')
+ SQL
+
+ connection.execute(<<~SQL)
+ CREATE TABLE #{destination_table}_202002 PARTITION OF #{destination_table}
+ FOR VALUES FROM ('2020-02-01') TO ('2020-03-01')
+ SQL
+
+ source_model.table_name = source_table
+ destination_model.table_name = destination_table
+
+ stub_const("#{described_class}::SUB_BATCH_SIZE", 2)
+ stub_const("#{described_class}::PAUSE_SECONDS", pause_seconds)
+
+ allow(subject).to receive(:sleep)
+ end
+
+ let(:connection) { ActiveRecord::Base.connection }
+ let(:source_model) { Class.new(ActiveRecord::Base) }
+ let(:destination_model) { Class.new(ActiveRecord::Base) }
+ let(:timestamp) { Time.utc(2020, 1, 2).round }
+ let(:pause_seconds) { 1 }
+
+ let!(:source1) { create_source_record(timestamp) }
+ let!(:source2) { create_source_record(timestamp + 1.day) }
+ let!(:source3) { create_source_record(timestamp + 1.month) }
+
+ it 'copies data into the destination table idempotently' do
+ expect(destination_model.count).to eq(0)
+
+ subject.perform(source1.id, source3.id, source_table, destination_table, unique_key)
+
+ expect(destination_model.count).to eq(3)
+
+ source_model.find_each do |source_record|
+ destination_record = destination_model.find_by_id(source_record.id)
+
+ expect(destination_record.attributes).to eq(source_record.attributes)
+ end
+
+ subject.perform(source1.id, source3.id, source_table, destination_table, unique_key)
+
+ expect(destination_model.count).to eq(3)
+ end
+
+ it 'breaks the assigned batch into smaller batches' do
+ expect_next_instance_of(described_class::BulkCopy) do |bulk_copy|
+ expect(bulk_copy).to receive(:copy_between).with(source1.id, source2.id)
+ expect(bulk_copy).to receive(:copy_between).with(source3.id, source3.id)
+ end
+
+ subject.perform(source1.id, source3.id, source_table, destination_table, unique_key)
+ end
+
+ it 'pauses after copying each sub-batch' do
+ expect(subject).to receive(:sleep).with(pause_seconds).twice
+
+ subject.perform(source1.id, source3.id, source_table, destination_table, unique_key)
+ end
+
+ it 'marks each job record as succeeded after processing' do
+ create(:background_migration_job, class_name: "::#{described_class.name}",
+ arguments: [source1.id, source3.id, source_table, destination_table, unique_key])
+
+ expect(::Gitlab::Database::BackgroundMigrationJob).to receive(:mark_all_as_succeeded).and_call_original
+
+ expect do
+ subject.perform(source1.id, source3.id, source_table, destination_table, unique_key)
+ end.to change { ::Gitlab::Database::BackgroundMigrationJob.succeeded.count }.from(0).to(1)
+ end
+
+ context 'when the feature flag is disabled' do
+ let(:mock_connection) { double('connection') }
+
+ before do
+ allow(subject).to receive(:connection).and_return(mock_connection)
+ stub_feature_flags(backfill_partitioned_audit_events: false)
+ end
+
+ it 'exits without attempting to copy data' do
+ expect(mock_connection).not_to receive(:execute)
+
+ subject.perform(1, 100, source_table, destination_table, unique_key)
+
+ expect(destination_model.count).to eq(0)
+ end
+ end
+
+ context 'when the job is run within an explicit transaction block' do
+ let(:mock_connection) { double('connection') }
+
+ before do
+ allow(subject).to receive(:connection).and_return(mock_connection)
+ allow(subject).to receive(:transaction_open?).and_return(true)
+ end
+
+ it 'raises an error before copying data' do
+ expect(mock_connection).not_to receive(:execute)
+
+ expect do
+ subject.perform(1, 100, source_table, destination_table, unique_key)
+ end.to raise_error(/Aborting job to backfill partitioned #{source_table}/)
+
+ expect(destination_model.count).to eq(0)
+ end
+ end
+ end
+
+ context 'when the destination table does not exist' do
+ let(:mock_connection) { double('connection') }
+ let(:mock_logger) { double('logger') }
+
+ before do
+ allow(subject).to receive(:connection).and_return(mock_connection)
+ allow(subject).to receive(:logger).and_return(mock_logger)
+
+ expect(mock_connection).to receive(:table_exists?).with(destination_table).and_return(false)
+ allow(mock_logger).to receive(:warn)
+ end
+
+ it 'exits without attempting to copy data' do
+ expect(mock_connection).not_to receive(:execute)
+
+ subject.perform(1, 100, source_table, destination_table, unique_key)
+ end
+
+ it 'logs a warning message that the job was skipped' do
+ expect(mock_logger).to receive(:warn).with(/#{destination_table} does not exist/)
+
+ subject.perform(1, 100, source_table, destination_table, unique_key)
+ end
+ end
+
+ def create_source_record(timestamp)
+ source_model.create!(col1: 123, col2: 'original value', created_at: timestamp)
+ end
+end
diff --git a/spec/lib/gitlab/database/partitioning_migration_helpers/foreign_key_helpers_spec.rb b/spec/lib/gitlab/database/partitioning_migration_helpers/foreign_key_helpers_spec.rb
index 9cec77b434d..efa9c83b2d2 100644
--- a/spec/lib/gitlab/database/partitioning_migration_helpers/foreign_key_helpers_spec.rb
+++ b/spec/lib/gitlab/database/partitioning_migration_helpers/foreign_key_helpers_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Database::PartitioningMigrationHelpers::ForeignKeyHelpers do
+RSpec.describe Gitlab::Database::PartitioningMigrationHelpers::ForeignKeyHelpers do
include TriggerHelpers
let(:model) do
diff --git a/spec/lib/gitlab/database/partitioning_migration_helpers/partitioned_foreign_key_spec.rb b/spec/lib/gitlab/database/partitioning_migration_helpers/partitioned_foreign_key_spec.rb
index 77f71676252..a58c37f111d 100644
--- a/spec/lib/gitlab/database/partitioning_migration_helpers/partitioned_foreign_key_spec.rb
+++ b/spec/lib/gitlab/database/partitioning_migration_helpers/partitioned_foreign_key_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Database::PartitioningMigrationHelpers::PartitionedForeignKey do
+RSpec.describe Gitlab::Database::PartitioningMigrationHelpers::PartitionedForeignKey do
let(:foreign_key) do
described_class.new(
to_table: 'issues',
diff --git a/spec/lib/gitlab/database/partitioning_migration_helpers/table_management_helpers_spec.rb b/spec/lib/gitlab/database/partitioning_migration_helpers/table_management_helpers_spec.rb
index 586b57d2002..9b24ab7cad4 100644
--- a/spec/lib/gitlab/database/partitioning_migration_helpers/table_management_helpers_spec.rb
+++ b/spec/lib/gitlab/database/partitioning_migration_helpers/table_management_helpers_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Database::PartitioningMigrationHelpers::TableManagementHelpers do
+RSpec.describe Gitlab::Database::PartitioningMigrationHelpers::TableManagementHelpers do
include PartitioningHelpers
include TriggerHelpers
@@ -11,7 +11,7 @@ describe Gitlab::Database::PartitioningMigrationHelpers::TableManagementHelpers
end
let_it_be(:connection) { ActiveRecord::Base.connection }
- let(:template_table) { :audit_events }
+ let(:source_table) { :audit_events }
let(:partitioned_table) { '_test_migration_partitioned_table' }
let(:function_name) { '_test_migration_function_name' }
let(:trigger_name) { '_test_migration_trigger_name' }
@@ -22,10 +22,10 @@ describe Gitlab::Database::PartitioningMigrationHelpers::TableManagementHelpers
before do
allow(migration).to receive(:puts)
allow(migration).to receive(:transaction_open?).and_return(false)
- allow(migration).to receive(:partitioned_table_name).and_return(partitioned_table)
- allow(migration).to receive(:sync_function_name).and_return(function_name)
- allow(migration).to receive(:sync_trigger_name).and_return(trigger_name)
- allow(migration).to receive(:assert_table_is_whitelisted)
+ allow(migration).to receive(:make_partitioned_table_name).and_return(partitioned_table)
+ allow(migration).to receive(:make_sync_function_name).and_return(function_name)
+ allow(migration).to receive(:make_sync_trigger_name).and_return(trigger_name)
+ allow(migration).to receive(:assert_table_is_allowed)
end
describe '#partition_table_by_date' do
@@ -33,15 +33,19 @@ describe Gitlab::Database::PartitioningMigrationHelpers::TableManagementHelpers
let(:old_primary_key) { 'id' }
let(:new_primary_key) { [old_primary_key, partition_column] }
- context 'when the table is not whitelisted' do
- let(:template_table) { :this_table_is_not_whitelisted }
+ before do
+ allow(migration).to receive(:queue_background_migration_jobs_by_range_at_intervals)
+ end
+
+ context 'when the table is not allowed' do
+ let(:source_table) { :this_table_is_not_allowed }
it 'raises an error' do
- expect(migration).to receive(:assert_table_is_whitelisted).with(template_table).and_call_original
+ expect(migration).to receive(:assert_table_is_allowed).with(source_table).and_call_original
expect do
- migration.partition_table_by_date template_table, partition_column, min_date: min_date, max_date: max_date
- end.to raise_error(/#{template_table} is not whitelisted for use/)
+ migration.partition_table_by_date source_table, partition_column, min_date: min_date, max_date: max_date
+ end.to raise_error(/#{source_table} is not allowed for use/)
end
end
@@ -50,7 +54,7 @@ describe Gitlab::Database::PartitioningMigrationHelpers::TableManagementHelpers
expect(migration).to receive(:transaction_open?).and_return(true)
expect do
- migration.partition_table_by_date template_table, partition_column, min_date: min_date, max_date: max_date
+ migration.partition_table_by_date source_table, partition_column, min_date: min_date, max_date: max_date
end.to raise_error(/can not be run inside a transaction/)
end
end
@@ -60,7 +64,7 @@ describe Gitlab::Database::PartitioningMigrationHelpers::TableManagementHelpers
it 'raises an error' do
expect do
- migration.partition_table_by_date template_table, partition_column, min_date: min_date, max_date: max_date
+ migration.partition_table_by_date source_table, partition_column, min_date: min_date, max_date: max_date
end.to raise_error(/max_date #{max_date} must be greater than min_date #{min_date}/)
end
end
@@ -70,24 +74,24 @@ describe Gitlab::Database::PartitioningMigrationHelpers::TableManagementHelpers
it 'raises an error' do
expect do
- migration.partition_table_by_date template_table, partition_column, min_date: min_date, max_date: max_date
+ migration.partition_table_by_date source_table, partition_column, min_date: min_date, max_date: max_date
end.to raise_error(/max_date #{max_date} must be greater than min_date #{min_date}/)
end
end
context 'when the given table does not have a primary key' do
- let(:template_table) { :_partitioning_migration_helper_test_table }
+ let(:source_table) { :_partitioning_migration_helper_test_table }
let(:partition_column) { :some_field }
it 'raises an error' do
- migration.create_table template_table, id: false do |t|
+ migration.create_table source_table, id: false do |t|
t.integer :id
t.datetime partition_column
end
expect do
- migration.partition_table_by_date template_table, partition_column, min_date: min_date, max_date: max_date
- end.to raise_error(/primary key not defined for #{template_table}/)
+ migration.partition_table_by_date source_table, partition_column, min_date: min_date, max_date: max_date
+ end.to raise_error(/primary key not defined for #{source_table}/)
end
end
@@ -96,14 +100,14 @@ describe Gitlab::Database::PartitioningMigrationHelpers::TableManagementHelpers
it 'raises an error' do
expect do
- migration.partition_table_by_date template_table, partition_column, min_date: min_date, max_date: max_date
+ migration.partition_table_by_date source_table, partition_column, min_date: min_date, max_date: max_date
end.to raise_error(/partition column #{partition_column} does not exist/)
end
end
describe 'constructing the partitioned table' do
it 'creates a table partitioned by the proper column' do
- migration.partition_table_by_date template_table, partition_column, min_date: min_date, max_date: max_date
+ migration.partition_table_by_date source_table, partition_column, min_date: min_date, max_date: max_date
expect(connection.table_exists?(partitioned_table)).to be(true)
expect(connection.primary_key(partitioned_table)).to eq(new_primary_key)
@@ -112,7 +116,7 @@ describe Gitlab::Database::PartitioningMigrationHelpers::TableManagementHelpers
end
it 'changes the primary key datatype to bigint' do
- migration.partition_table_by_date template_table, partition_column, min_date: min_date, max_date: max_date
+ migration.partition_table_by_date source_table, partition_column, min_date: min_date, max_date: max_date
pk_column = connection.columns(partitioned_table).find { |c| c.name == old_primary_key }
@@ -127,13 +131,13 @@ describe Gitlab::Database::PartitioningMigrationHelpers::TableManagementHelpers
end
end
- let(:template_table) { :another_example }
+ let(:source_table) { :another_example }
let(:old_primary_key) { 'identifier' }
it 'does not change the primary key datatype' do
- migration.partition_table_by_date template_table, partition_column, min_date: min_date, max_date: max_date
+ migration.partition_table_by_date source_table, partition_column, min_date: min_date, max_date: max_date
- original_pk_column = connection.columns(template_table).find { |c| c.name == old_primary_key }
+ original_pk_column = connection.columns(source_table).find { |c| c.name == old_primary_key }
pk_column = connection.columns(partitioned_table).find { |c| c.name == old_primary_key }
expect(pk_column).not_to be_nil
@@ -142,7 +146,7 @@ describe Gitlab::Database::PartitioningMigrationHelpers::TableManagementHelpers
end
it 'removes the default from the primary key column' do
- migration.partition_table_by_date template_table, partition_column, min_date: min_date, max_date: max_date
+ migration.partition_table_by_date source_table, partition_column, min_date: min_date, max_date: max_date
pk_column = connection.columns(partitioned_table).find { |c| c.name == old_primary_key }
@@ -150,26 +154,100 @@ describe Gitlab::Database::PartitioningMigrationHelpers::TableManagementHelpers
end
it 'creates the partitioned table with the same non-key columns' do
- migration.partition_table_by_date template_table, partition_column, min_date: min_date, max_date: max_date
+ migration.partition_table_by_date source_table, partition_column, min_date: min_date, max_date: max_date
copied_columns = filter_columns_by_name(connection.columns(partitioned_table), new_primary_key)
- original_columns = filter_columns_by_name(connection.columns(template_table), new_primary_key)
+ original_columns = filter_columns_by_name(connection.columns(source_table), new_primary_key)
expect(copied_columns).to match_array(original_columns)
end
it 'creates a partition spanning over each month in the range given' do
- migration.partition_table_by_date template_table, partition_column, min_date: min_date, max_date: max_date
+ migration.partition_table_by_date source_table, partition_column, min_date: min_date, max_date: max_date
+
+ expect_range_partitions_for(partitioned_table, {
+ '000000' => ['MINVALUE', "'2019-12-01 00:00:00'"],
+ '201912' => ["'2019-12-01 00:00:00'", "'2020-01-01 00:00:00'"],
+ '202001' => ["'2020-01-01 00:00:00'", "'2020-02-01 00:00:00'"],
+ '202002' => ["'2020-02-01 00:00:00'", "'2020-03-01 00:00:00'"],
+ '202003' => ["'2020-03-01 00:00:00'", "'2020-04-01 00:00:00'"]
+ })
+ end
+
+ context 'when min_date is not given' do
+ let(:source_table) { :todos }
+
+ context 'with records present already' do
+ before do
+ create(:todo, created_at: Date.parse('2019-11-05'))
+ end
+
+ it 'creates a partition spanning over each month from the first record' do
+ migration.partition_table_by_date source_table, partition_column, max_date: max_date
+
+ expect_range_partitions_for(partitioned_table, {
+ '000000' => ['MINVALUE', "'2019-11-01 00:00:00'"],
+ '201911' => ["'2019-11-01 00:00:00'", "'2019-12-01 00:00:00'"],
+ '201912' => ["'2019-12-01 00:00:00'", "'2020-01-01 00:00:00'"],
+ '202001' => ["'2020-01-01 00:00:00'", "'2020-02-01 00:00:00'"],
+ '202002' => ["'2020-02-01 00:00:00'", "'2020-03-01 00:00:00'"],
+ '202003' => ["'2020-03-01 00:00:00'", "'2020-04-01 00:00:00'"]
+ })
+ end
+ end
+
+ context 'without data' do
+ it 'creates the catchall partition plus two actual partition' do
+ migration.partition_table_by_date source_table, partition_column, max_date: max_date
+
+ expect_range_partitions_for(partitioned_table, {
+ '000000' => ['MINVALUE', "'2020-02-01 00:00:00'"],
+ '202002' => ["'2020-02-01 00:00:00'", "'2020-03-01 00:00:00'"],
+ '202003' => ["'2020-03-01 00:00:00'", "'2020-04-01 00:00:00'"]
+ })
+ end
+ end
+ end
- expect_range_partition_of("#{partitioned_table}_000000", partitioned_table, 'MINVALUE', "'2019-12-01 00:00:00'")
- expect_range_partition_of("#{partitioned_table}_201912", partitioned_table, "'2019-12-01 00:00:00'", "'2020-01-01 00:00:00'")
- expect_range_partition_of("#{partitioned_table}_202001", partitioned_table, "'2020-01-01 00:00:00'", "'2020-02-01 00:00:00'")
- expect_range_partition_of("#{partitioned_table}_202002", partitioned_table, "'2020-02-01 00:00:00'", "'2020-03-01 00:00:00'")
+ context 'when max_date is not given' do
+ it 'creates partitions including the next month from today' do
+ today = Date.new(2020, 5, 8)
+
+ Timecop.freeze(today) do
+ migration.partition_table_by_date source_table, partition_column, min_date: min_date
+
+ expect_range_partitions_for(partitioned_table, {
+ '000000' => ['MINVALUE', "'2019-12-01 00:00:00'"],
+ '201912' => ["'2019-12-01 00:00:00'", "'2020-01-01 00:00:00'"],
+ '202001' => ["'2020-01-01 00:00:00'", "'2020-02-01 00:00:00'"],
+ '202002' => ["'2020-02-01 00:00:00'", "'2020-03-01 00:00:00'"],
+ '202003' => ["'2020-03-01 00:00:00'", "'2020-04-01 00:00:00'"],
+ '202004' => ["'2020-04-01 00:00:00'", "'2020-05-01 00:00:00'"],
+ '202005' => ["'2020-05-01 00:00:00'", "'2020-06-01 00:00:00'"],
+ '202006' => ["'2020-06-01 00:00:00'", "'2020-07-01 00:00:00'"]
+ })
+ end
+ end
+ end
+
+ context 'without min_date, max_date' do
+ it 'creates partitions for the current and next month' do
+ current_date = Date.new(2020, 05, 22)
+ Timecop.freeze(current_date.to_time) do
+ migration.partition_table_by_date source_table, partition_column
+
+ expect_range_partitions_for(partitioned_table, {
+ '000000' => ['MINVALUE', "'2020-05-01 00:00:00'"],
+ '202005' => ["'2020-05-01 00:00:00'", "'2020-06-01 00:00:00'"],
+ '202006' => ["'2020-06-01 00:00:00'", "'2020-07-01 00:00:00'"]
+ })
+ end
+ end
end
end
describe 'keeping data in sync with the partitioned table' do
- let(:template_table) { :todos }
+ let(:source_table) { :todos }
let(:model) { Class.new(ActiveRecord::Base) }
let(:timestamp) { Time.utc(2019, 12, 1, 12).round }
@@ -180,16 +258,16 @@ describe Gitlab::Database::PartitioningMigrationHelpers::TableManagementHelpers
it 'creates a trigger function on the original table' do
expect_function_not_to_exist(function_name)
- expect_trigger_not_to_exist(template_table, trigger_name)
+ expect_trigger_not_to_exist(source_table, trigger_name)
- migration.partition_table_by_date template_table, partition_column, min_date: min_date, max_date: max_date
+ migration.partition_table_by_date source_table, partition_column, min_date: min_date, max_date: max_date
expect_function_to_exist(function_name)
- expect_valid_function_trigger(template_table, trigger_name, function_name, after: %w[delete insert update])
+ expect_valid_function_trigger(source_table, trigger_name, function_name, after: %w[delete insert update])
end
it 'syncs inserts to the partitioned tables' do
- migration.partition_table_by_date template_table, partition_column, min_date: min_date, max_date: max_date
+ migration.partition_table_by_date source_table, partition_column, min_date: min_date, max_date: max_date
expect(model.count).to eq(0)
@@ -202,7 +280,7 @@ describe Gitlab::Database::PartitioningMigrationHelpers::TableManagementHelpers
end
it 'syncs updates to the partitioned tables' do
- migration.partition_table_by_date template_table, partition_column, min_date: min_date, max_date: max_date
+ migration.partition_table_by_date source_table, partition_column, min_date: min_date, max_date: max_date
first_todo = create(:todo, :pending, commit_id: nil, created_at: timestamp, updated_at: timestamp)
second_todo = create(:todo, created_at: timestamp, updated_at: timestamp)
@@ -223,7 +301,7 @@ describe Gitlab::Database::PartitioningMigrationHelpers::TableManagementHelpers
end
it 'syncs deletes to the partitioned tables' do
- migration.partition_table_by_date template_table, partition_column, min_date: min_date, max_date: max_date
+ migration.partition_table_by_date source_table, partition_column, min_date: min_date, max_date: max_date
first_todo = create(:todo, created_at: timestamp, updated_at: timestamp)
second_todo = create(:todo, created_at: timestamp, updated_at: timestamp)
@@ -237,50 +315,129 @@ describe Gitlab::Database::PartitioningMigrationHelpers::TableManagementHelpers
expect(model.find(second_todo.id).attributes).to eq(second_todo.attributes)
end
end
+
+ describe 'copying historic data to the partitioned table' do
+ let(:source_table) { 'todos' }
+ let(:migration_class) { '::Gitlab::Database::PartitioningMigrationHelpers::BackfillPartitionedTable' }
+ let(:sub_batch_size) { described_class::SUB_BATCH_SIZE }
+ let(:pause_seconds) { described_class::PAUSE_SECONDS }
+ let!(:first_id) { create(:todo).id }
+ let!(:second_id) { create(:todo).id }
+ let!(:third_id) { create(:todo).id }
+
+ before do
+ stub_const("#{described_class.name}::BATCH_SIZE", 2)
+
+ expect(migration).to receive(:queue_background_migration_jobs_by_range_at_intervals).and_call_original
+ end
+
+ it 'enqueues jobs to copy each batch of data' do
+ Sidekiq::Testing.fake! do
+ migration.partition_table_by_date source_table, partition_column, min_date: min_date, max_date: max_date
+
+ expect(BackgroundMigrationWorker.jobs.size).to eq(2)
+
+ first_job_arguments = [first_id, second_id, source_table, partitioned_table, 'id']
+ expect(BackgroundMigrationWorker.jobs[0]['args']).to eq([migration_class, first_job_arguments])
+
+ second_job_arguments = [third_id, third_id, source_table, partitioned_table, 'id']
+ expect(BackgroundMigrationWorker.jobs[1]['args']).to eq([migration_class, second_job_arguments])
+ end
+ end
+ end
end
describe '#drop_partitioned_table_for' do
let(:expected_tables) do
- %w[000000 201912 202001 202002].map { |suffix| "#{partitioned_table}_#{suffix}" }.unshift(partitioned_table)
+ %w[000000 201912 202001 202002].map { |suffix| "#{Gitlab::Database::DYNAMIC_PARTITIONS_SCHEMA}.#{partitioned_table}_#{suffix}" }.unshift(partitioned_table)
end
+ let(:migration_class) { 'Gitlab::Database::PartitioningMigrationHelpers::BackfillPartitionedTable' }
- context 'when the table is not whitelisted' do
- let(:template_table) { :this_table_is_not_whitelisted }
+ context 'when the table is not allowed' do
+ let(:source_table) { :this_table_is_not_allowed }
it 'raises an error' do
- expect(migration).to receive(:assert_table_is_whitelisted).with(template_table).and_call_original
+ expect(migration).to receive(:assert_table_is_allowed).with(source_table).and_call_original
expect do
- migration.drop_partitioned_table_for template_table
- end.to raise_error(/#{template_table} is not whitelisted for use/)
+ migration.drop_partitioned_table_for source_table
+ end.to raise_error(/#{source_table} is not allowed for use/)
end
end
it 'drops the trigger syncing to the partitioned table' do
- migration.partition_table_by_date template_table, partition_column, min_date: min_date, max_date: max_date
+ migration.partition_table_by_date source_table, partition_column, min_date: min_date, max_date: max_date
expect_function_to_exist(function_name)
- expect_valid_function_trigger(template_table, trigger_name, function_name, after: %w[delete insert update])
+ expect_valid_function_trigger(source_table, trigger_name, function_name, after: %w[delete insert update])
- migration.drop_partitioned_table_for template_table
+ migration.drop_partitioned_table_for source_table
expect_function_not_to_exist(function_name)
- expect_trigger_not_to_exist(template_table, trigger_name)
+ expect_trigger_not_to_exist(source_table, trigger_name)
end
it 'drops the partitioned copy and all partitions' do
- migration.partition_table_by_date template_table, partition_column, min_date: min_date, max_date: max_date
+ migration.partition_table_by_date source_table, partition_column, min_date: min_date, max_date: max_date
expected_tables.each do |table|
expect(connection.table_exists?(table)).to be(true)
end
- migration.drop_partitioned_table_for template_table
+ migration.drop_partitioned_table_for source_table
expected_tables.each do |table|
expect(connection.table_exists?(table)).to be(false)
end
end
+
+ context 'cleaning up background migration tracking records' do
+ let!(:job1) { create(:background_migration_job, class_name: migration_class, arguments: [1, 10, source_table]) }
+ let!(:job2) { create(:background_migration_job, class_name: migration_class, arguments: [11, 20, source_table]) }
+ let!(:job3) { create(:background_migration_job, class_name: migration_class, arguments: [1, 10, 'other_table']) }
+
+ it 'deletes any tracking records from the background_migration_jobs table' do
+ migration.partition_table_by_date source_table, partition_column, min_date: min_date, max_date: max_date
+
+ expect { migration.drop_partitioned_table_for(source_table) }
+ .to change { ::Gitlab::Database::BackgroundMigrationJob.count }.from(3).to(1)
+
+ remaining_record = ::Gitlab::Database::BackgroundMigrationJob.first
+ expect(remaining_record).to have_attributes(class_name: migration_class, arguments: [1, 10, 'other_table'])
+ end
+ end
+ end
+
+ describe '#create_hash_partitions' do
+ before do
+ connection.execute(<<~SQL)
+ CREATE TABLE #{partitioned_table}
+ (id serial not null, some_id integer not null, PRIMARY KEY (id, some_id))
+ PARTITION BY HASH (some_id);
+ SQL
+ end
+
+ it 'creates partitions for the full hash space (8 partitions)' do
+ partitions = 8
+
+ migration.create_hash_partitions(partitioned_table, partitions)
+
+ (0..partitions - 1).each do |partition|
+ partition_name = "#{partitioned_table}_#{"%01d" % partition}"
+ expect_hash_partition_of(partition_name, partitioned_table, partitions, partition)
+ end
+ end
+
+ it 'creates partitions for the full hash space (16 partitions)' do
+ partitions = 16
+
+ migration.create_hash_partitions(partitioned_table, partitions)
+
+ (0..partitions - 1).each do |partition|
+ partition_name = "#{partitioned_table}_#{"%02d" % partition}"
+ expect_hash_partition_of(partition_name, partitioned_table, partitions, partition)
+ end
+ end
end
def filter_columns_by_name(columns, names)
diff --git a/spec/lib/gitlab/database/postgresql_adapter/force_disconnectable_mixin_spec.rb b/spec/lib/gitlab/database/postgresql_adapter/force_disconnectable_mixin_spec.rb
index 0523066b593..8b3a0ceb804 100644
--- a/spec/lib/gitlab/database/postgresql_adapter/force_disconnectable_mixin_spec.rb
+++ b/spec/lib/gitlab/database/postgresql_adapter/force_disconnectable_mixin_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Database::PostgresqlAdapter::ForceDisconnectableMixin do
+RSpec.describe Gitlab::Database::PostgresqlAdapter::ForceDisconnectableMixin do
describe 'checking in a connection to the pool' do
let(:model) do
Class.new(ActiveRecord::Base) do
diff --git a/spec/lib/gitlab/database/postgresql_adapter/schema_versions_copy_mixin_spec.rb b/spec/lib/gitlab/database/postgresql_adapter/schema_versions_copy_mixin_spec.rb
index 968dfc1ea43..c6333e4a4dc 100644
--- a/spec/lib/gitlab/database/postgresql_adapter/schema_versions_copy_mixin_spec.rb
+++ b/spec/lib/gitlab/database/postgresql_adapter/schema_versions_copy_mixin_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Database::PostgresqlAdapter::SchemaVersionsCopyMixin do
+RSpec.describe Gitlab::Database::PostgresqlAdapter::SchemaVersionsCopyMixin do
let(:schema_migration) { double('schem_migration', table_name: table_name, all_versions: versions) }
let(:versions) { %w(5 2 1000 200 4 93 2) }
let(:table_name) { "schema_migrations" }
diff --git a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base_spec.rb b/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base_spec.rb
index fae57996fb6..76b1be1e497 100644
--- a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base_spec.rb
+++ b/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameBase, :delete do
+RSpec.describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameBase, :delete do
let(:migration) { FakeRenameReservedPathMigrationV1.new }
let(:subject) { described_class.new(['the-path'], migration) }
diff --git a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_namespaces_spec.rb b/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_namespaces_spec.rb
index 46fc48ab3fc..e222a29c6a1 100644
--- a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_namespaces_spec.rb
+++ b/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_namespaces_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameNamespaces, :delete do
+RSpec.describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameNamespaces, :delete do
let(:migration) { FakeRenameReservedPathMigrationV1.new }
let(:subject) { described_class.new(['the-path'], migration) }
let(:namespace) { create(:group, name: 'the-path') }
diff --git a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_projects_spec.rb b/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_projects_spec.rb
index 1ccdb1d9447..3799fe3c316 100644
--- a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_projects_spec.rb
+++ b/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_projects_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameProjects, :delete do
+RSpec.describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameProjects, :delete do
let(:migration) { FakeRenameReservedPathMigrationV1.new }
let(:subject) { described_class.new(['the-path'], migration) }
let(:project) do
diff --git a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1_spec.rb b/spec/lib/gitlab/database/rename_reserved_paths_migration/v1_spec.rb
index 56767c21ab7..3b2d3ab1354 100644
--- a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1_spec.rb
+++ b/spec/lib/gitlab/database/rename_reserved_paths_migration/v1_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-shared_examples 'renames child namespaces' do |type|
+RSpec.shared_examples 'renames child namespaces' do |type|
it 'renames namespaces' do
rename_namespaces = double
expect(described_class::RenameNamespaces)
@@ -15,7 +15,7 @@ shared_examples 'renames child namespaces' do |type|
end
end
-describe Gitlab::Database::RenameReservedPathsMigration::V1, :delete do
+RSpec.describe Gitlab::Database::RenameReservedPathsMigration::V1, :delete do
let(:subject) { FakeRenameReservedPathMigrationV1.new }
before do
diff --git a/spec/lib/gitlab/database/schema_cleaner_spec.rb b/spec/lib/gitlab/database/schema_cleaner_spec.rb
index adaeb85d52d..1303ad7a311 100644
--- a/spec/lib/gitlab/database/schema_cleaner_spec.rb
+++ b/spec/lib/gitlab/database/schema_cleaner_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Database::SchemaCleaner do
+RSpec.describe Gitlab::Database::SchemaCleaner do
let(:example_schema) { fixture_file(File.join('gitlab', 'database', 'structure_example.sql')) }
let(:io) { StringIO.new }
diff --git a/spec/lib/gitlab/database/sha_attribute_spec.rb b/spec/lib/gitlab/database/sha_attribute_spec.rb
index 15695bc8069..1b855625a6c 100644
--- a/spec/lib/gitlab/database/sha_attribute_spec.rb
+++ b/spec/lib/gitlab/database/sha_attribute_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Database::ShaAttribute do
+RSpec.describe Gitlab::Database::ShaAttribute do
let(:sha) do
'9a573a369a5bfbb9a4a36e98852c21af8a44ea8b'
end
diff --git a/spec/lib/gitlab/database/with_lock_retries_spec.rb b/spec/lib/gitlab/database/with_lock_retries_spec.rb
index d7eee594631..70cbddbb7b7 100644
--- a/spec/lib/gitlab/database/with_lock_retries_spec.rb
+++ b/spec/lib/gitlab/database/with_lock_retries_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Database::WithLockRetries do
+RSpec.describe Gitlab::Database::WithLockRetries do
let(:env) { {} }
let(:logger) { Gitlab::Database::WithLockRetries::NULL_LOGGER }
let(:subject) { described_class.new(env: env, logger: logger, timing_configuration: timing_configuration) }
diff --git a/spec/lib/gitlab/database_importers/common_metrics/importer_spec.rb b/spec/lib/gitlab/database_importers/common_metrics/importer_spec.rb
index e6321d48e11..fdf16069381 100644
--- a/spec/lib/gitlab/database_importers/common_metrics/importer_spec.rb
+++ b/spec/lib/gitlab/database_importers/common_metrics/importer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::DatabaseImporters::CommonMetrics::Importer do
+RSpec.describe Gitlab::DatabaseImporters::CommonMetrics::Importer do
subject { described_class.new }
context "does import common_metrics.yml" do
diff --git a/spec/lib/gitlab/database_importers/common_metrics/prometheus_metric_spec.rb b/spec/lib/gitlab/database_importers/common_metrics/prometheus_metric_spec.rb
index e4e8a85e7bc..67da59d6477 100644
--- a/spec/lib/gitlab/database_importers/common_metrics/prometheus_metric_spec.rb
+++ b/spec/lib/gitlab/database_importers/common_metrics/prometheus_metric_spec.rb
@@ -2,14 +2,14 @@
require 'spec_helper'
-describe Gitlab::DatabaseImporters::CommonMetrics::PrometheusMetric do
+RSpec.describe Gitlab::DatabaseImporters::CommonMetrics::PrometheusMetric do
it 'group enum equals ::PrometheusMetric' do
expect(described_class.groups).to eq(::PrometheusMetric.groups)
end
it '.group_titles equals ::PrometheusMetric' do
- existing_group_titles = ::PrometheusMetricEnums.group_details.each_with_object({}) do |(key, value), memo|
- memo[key] = value[:group_title]
+ existing_group_titles = ::PrometheusMetricEnums.group_details.transform_values do |value|
+ value[:group_title]
end
expect(Gitlab::DatabaseImporters::CommonMetrics::PrometheusMetricEnums.group_titles).to eq(existing_group_titles)
end
diff --git a/spec/lib/gitlab/database_importers/instance_administrators/create_group_spec.rb b/spec/lib/gitlab/database_importers/instance_administrators/create_group_spec.rb
index 97f4a7eec75..a3661bbe49a 100644
--- a/spec/lib/gitlab/database_importers/instance_administrators/create_group_spec.rb
+++ b/spec/lib/gitlab/database_importers/instance_administrators/create_group_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::DatabaseImporters::InstanceAdministrators::CreateGroup do
+RSpec.describe Gitlab::DatabaseImporters::InstanceAdministrators::CreateGroup do
describe '#execute' do
let(:result) { subject.execute }
diff --git a/spec/lib/gitlab/database_importers/self_monitoring/project/create_service_spec.rb b/spec/lib/gitlab/database_importers/self_monitoring/project/create_service_spec.rb
index 923f620a81d..ca9f9ab915f 100644
--- a/spec/lib/gitlab/database_importers/self_monitoring/project/create_service_spec.rb
+++ b/spec/lib/gitlab/database_importers/self_monitoring/project/create_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::DatabaseImporters::SelfMonitoring::Project::CreateService do
+RSpec.describe Gitlab::DatabaseImporters::SelfMonitoring::Project::CreateService do
describe '#execute' do
let(:result) { subject.execute }
diff --git a/spec/lib/gitlab/database_importers/self_monitoring/project/delete_service_spec.rb b/spec/lib/gitlab/database_importers/self_monitoring/project/delete_service_spec.rb
index 89d816c0cf7..9d514bcc661 100644
--- a/spec/lib/gitlab/database_importers/self_monitoring/project/delete_service_spec.rb
+++ b/spec/lib/gitlab/database_importers/self_monitoring/project/delete_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::DatabaseImporters::SelfMonitoring::Project::DeleteService do
+RSpec.describe Gitlab::DatabaseImporters::SelfMonitoring::Project::DeleteService do
describe '#execute' do
let!(:application_setting) { create(:application_setting) }
let(:result) { subject.execute }
diff --git a/spec/lib/gitlab/database_spec.rb b/spec/lib/gitlab/database_spec.rb
index b99f311de29..cd009f955af 100644
--- a/spec/lib/gitlab/database_spec.rb
+++ b/spec/lib/gitlab/database_spec.rb
@@ -2,11 +2,19 @@
require 'spec_helper'
-describe Gitlab::Database do
+RSpec.describe Gitlab::Database do
before do
stub_const('MigrationTest', Class.new { include Gitlab::Database })
end
+ describe 'EXTRA_SCHEMAS' do
+ it 'contains only schemas starting with gitlab_ prefix' do
+ described_class::EXTRA_SCHEMAS.each do |schema|
+ expect(schema.to_s).to start_with('gitlab_')
+ end
+ end
+ end
+
describe '.config' do
it 'returns a Hash' do
expect(described_class.config).to be_an_instance_of(Hash)
@@ -82,36 +90,83 @@ describe Gitlab::Database do
end
describe '.postgresql_minimum_supported_version?' do
- it 'returns false when using PostgreSQL 9.5' do
- allow(described_class).to receive(:version).and_return('9.5')
+ it 'returns false when using PostgreSQL 10' do
+ allow(described_class).to receive(:version).and_return('10')
expect(described_class.postgresql_minimum_supported_version?).to eq(false)
end
- it 'returns true when using PostgreSQL 9.6' do
- allow(described_class).to receive(:version).and_return('9.6')
+ it 'returns true when using PostgreSQL 11' do
+ allow(described_class).to receive(:version).and_return('11')
expect(described_class.postgresql_minimum_supported_version?).to eq(true)
end
- it 'returns true when using PostgreSQL 10 or newer' do
- allow(described_class).to receive(:version).and_return('10')
+ it 'returns true when using PostgreSQL 12' do
+ allow(described_class).to receive(:version).and_return('12')
expect(described_class.postgresql_minimum_supported_version?).to eq(true)
end
end
- describe '.replication_slots_supported?' do
- it 'returns false when using PostgreSQL 9.3' do
- allow(described_class).to receive(:version).and_return('9.3.1')
+ describe '.postgresql_upcoming_deprecation?' do
+ it 'returns true when database version is lower than the upcoming minimum' do
+ allow(described_class).to receive(:version).and_return('11')
+
+ expect(described_class.postgresql_upcoming_deprecation?).to eq(true)
+ end
+
+ it 'returns false when database version equals the upcoming minimum' do
+ allow(described_class).to receive(:version).and_return('12')
+
+ expect(described_class.postgresql_upcoming_deprecation?).to eq(false)
+ end
+
+ it 'returns false when database version is greater the upcoming minimum' do
+ allow(described_class).to receive(:version).and_return('13')
+
+ expect(described_class.postgresql_upcoming_deprecation?).to eq(false)
+ end
+ end
+
+ describe '.check_postgres_version_and_print_warning' do
+ subject { described_class.check_postgres_version_and_print_warning }
+
+ it 'prints a warning if not compliant with minimum postgres version' do
+ allow(described_class).to receive(:postgresql_minimum_supported_version?).and_return(false)
+
+ expect(Kernel).to receive(:warn).with(/You are using PostgreSQL/)
- expect(described_class.replication_slots_supported?).to eq(false)
+ subject
end
- it 'returns true when using PostgreSQL 9.4.0 or newer' do
- allow(described_class).to receive(:version).and_return('9.4.0')
+ it 'doesnt print a warning if compliant with minimum postgres version' do
+ allow(described_class).to receive(:postgresql_minimum_supported_version?).and_return(true)
- expect(described_class.replication_slots_supported?).to eq(true)
+ expect(Kernel).not_to receive(:warn).with(/You are using PostgreSQL/)
+
+ subject
+ end
+
+ it 'doesnt print a warning in Rails runner environment' do
+ allow(described_class).to receive(:postgresql_minimum_supported_version?).and_return(false)
+ allow(Gitlab::Runtime).to receive(:rails_runner?).and_return(true)
+
+ expect(Kernel).not_to receive(:warn).with(/You are using PostgreSQL/)
+
+ subject
+ end
+
+ it 'ignores ActiveRecord errors' do
+ allow(described_class).to receive(:postgresql_minimum_supported_version?).and_raise(ActiveRecord::ActiveRecordError)
+
+ expect { subject }.not_to raise_error
+ end
+
+ it 'ignores Postgres errors' do
+ allow(described_class).to receive(:postgresql_minimum_supported_version?).and_raise(PG::Error)
+
+ expect { subject }.not_to raise_error
end
end
@@ -228,7 +283,6 @@ describe Gitlab::Database do
describe '.bulk_insert' do
before do
allow(described_class).to receive(:connection).and_return(connection)
- allow(described_class).to receive(:version).and_return(version)
allow(connection).to receive(:quote_column_name, &:itself)
allow(connection).to receive(:quote, &:itself)
allow(connection).to receive(:execute)
@@ -243,8 +297,6 @@ describe Gitlab::Database do
]
end
- let_it_be(:version) { 9.6 }
-
it 'does nothing with empty rows' do
expect(connection).not_to receive(:execute)
@@ -311,28 +363,13 @@ describe Gitlab::Database do
expect(ids).to eq([10])
end
- context 'with version >= 9.5' do
- it 'allows setting the upsert to do nothing' do
- expect(connection)
- .to receive(:execute)
- .with(/ON CONFLICT DO NOTHING/)
-
- described_class
- .bulk_insert('test', [{ number: 10 }], on_conflict: :do_nothing)
- end
- end
-
- context 'with version < 9.5' do
- let(:version) { 9.4 }
-
- it 'refuses setting the upsert' do
- expect(connection)
- .not_to receive(:execute)
- .with(/ON CONFLICT/)
+ it 'allows setting the upsert to do nothing' do
+ expect(connection)
+ .to receive(:execute)
+ .with(/ON CONFLICT DO NOTHING/)
- described_class
- .bulk_insert('test', [{ number: 10 }], on_conflict: :do_nothing)
- end
+ described_class
+ .bulk_insert('test', [{ number: 10 }], on_conflict: :do_nothing)
end
end
end
diff --git a/spec/lib/gitlab/dependency_linker/base_linker_spec.rb b/spec/lib/gitlab/dependency_linker/base_linker_spec.rb
index 1466ce2dfcc..678d4a90e8d 100644
--- a/spec/lib/gitlab/dependency_linker/base_linker_spec.rb
+++ b/spec/lib/gitlab/dependency_linker/base_linker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::DependencyLinker::BaseLinker do
+RSpec.describe Gitlab::DependencyLinker::BaseLinker do
let(:linker_class) do
Class.new(described_class) do
def link_dependencies
diff --git a/spec/lib/gitlab/dependency_linker/cargo_toml_linker_spec.rb b/spec/lib/gitlab/dependency_linker/cargo_toml_linker_spec.rb
index 86d5bc93bf7..8068fa30367 100644
--- a/spec/lib/gitlab/dependency_linker/cargo_toml_linker_spec.rb
+++ b/spec/lib/gitlab/dependency_linker/cargo_toml_linker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::DependencyLinker::CargoTomlLinker do
+RSpec.describe Gitlab::DependencyLinker::CargoTomlLinker do
describe '.support?' do
it 'supports Cargo.toml' do
expect(described_class.support?('Cargo.toml')).to be_truthy
diff --git a/spec/lib/gitlab/dependency_linker/cartfile_linker_spec.rb b/spec/lib/gitlab/dependency_linker/cartfile_linker_spec.rb
index 6d61edaa870..52ddba24458 100644
--- a/spec/lib/gitlab/dependency_linker/cartfile_linker_spec.rb
+++ b/spec/lib/gitlab/dependency_linker/cartfile_linker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::DependencyLinker::CartfileLinker do
+RSpec.describe Gitlab::DependencyLinker::CartfileLinker do
describe '.support?' do
it 'supports Cartfile' do
expect(described_class.support?('Cartfile')).to be_truthy
diff --git a/spec/lib/gitlab/dependency_linker/composer_json_linker_spec.rb b/spec/lib/gitlab/dependency_linker/composer_json_linker_spec.rb
index cc1f09628ef..c24d6a44d9b 100644
--- a/spec/lib/gitlab/dependency_linker/composer_json_linker_spec.rb
+++ b/spec/lib/gitlab/dependency_linker/composer_json_linker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::DependencyLinker::ComposerJsonLinker do
+RSpec.describe Gitlab::DependencyLinker::ComposerJsonLinker do
describe '.support?' do
it 'supports composer.json' do
expect(described_class.support?('composer.json')).to be_truthy
diff --git a/spec/lib/gitlab/dependency_linker/gemfile_linker_spec.rb b/spec/lib/gitlab/dependency_linker/gemfile_linker_spec.rb
index c6b6dfa77cb..00e95dea224 100644
--- a/spec/lib/gitlab/dependency_linker/gemfile_linker_spec.rb
+++ b/spec/lib/gitlab/dependency_linker/gemfile_linker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::DependencyLinker::GemfileLinker do
+RSpec.describe Gitlab::DependencyLinker::GemfileLinker do
describe '.support?' do
it 'supports Gemfile' do
expect(described_class.support?('Gemfile')).to be_truthy
diff --git a/spec/lib/gitlab/dependency_linker/gemspec_linker_spec.rb b/spec/lib/gitlab/dependency_linker/gemspec_linker_spec.rb
index c1cbfa31684..ae82dd51c95 100644
--- a/spec/lib/gitlab/dependency_linker/gemspec_linker_spec.rb
+++ b/spec/lib/gitlab/dependency_linker/gemspec_linker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::DependencyLinker::GemspecLinker do
+RSpec.describe Gitlab::DependencyLinker::GemspecLinker do
describe '.support?' do
it 'supports *.gemspec' do
expect(described_class.support?('gitlab_git.gemspec')).to be_truthy
diff --git a/spec/lib/gitlab/dependency_linker/go_mod_linker_spec.rb b/spec/lib/gitlab/dependency_linker/go_mod_linker_spec.rb
index 769daa0b3a6..605b14bc923 100644
--- a/spec/lib/gitlab/dependency_linker/go_mod_linker_spec.rb
+++ b/spec/lib/gitlab/dependency_linker/go_mod_linker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::DependencyLinker::GoModLinker do
+RSpec.describe Gitlab::DependencyLinker::GoModLinker do
let(:file_name) { 'go.mod' }
let(:file_content) do
<<-CONTENT.strip_heredoc
diff --git a/spec/lib/gitlab/dependency_linker/go_sum_linker_spec.rb b/spec/lib/gitlab/dependency_linker/go_sum_linker_spec.rb
index f5cb7809ad3..2836c0e9f29 100644
--- a/spec/lib/gitlab/dependency_linker/go_sum_linker_spec.rb
+++ b/spec/lib/gitlab/dependency_linker/go_sum_linker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::DependencyLinker::GoSumLinker do
+RSpec.describe Gitlab::DependencyLinker::GoSumLinker do
let(:file_name) { 'go.sum' }
let(:file_content) do
<<-CONTENT.strip_heredoc
diff --git a/spec/lib/gitlab/dependency_linker/godeps_json_linker_spec.rb b/spec/lib/gitlab/dependency_linker/godeps_json_linker_spec.rb
index 7128689e362..c1ed030c548 100644
--- a/spec/lib/gitlab/dependency_linker/godeps_json_linker_spec.rb
+++ b/spec/lib/gitlab/dependency_linker/godeps_json_linker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::DependencyLinker::GodepsJsonLinker do
+RSpec.describe Gitlab::DependencyLinker::GodepsJsonLinker do
describe '.support?' do
it 'supports Godeps.json' do
expect(described_class.support?('Godeps.json')).to be_truthy
diff --git a/spec/lib/gitlab/dependency_linker/package_json_linker_spec.rb b/spec/lib/gitlab/dependency_linker/package_json_linker_spec.rb
index eb0c5e0675a..cdfc0e89bc7 100644
--- a/spec/lib/gitlab/dependency_linker/package_json_linker_spec.rb
+++ b/spec/lib/gitlab/dependency_linker/package_json_linker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::DependencyLinker::PackageJsonLinker do
+RSpec.describe Gitlab::DependencyLinker::PackageJsonLinker do
describe '.support?' do
it 'supports package.json' do
expect(described_class.support?('package.json')).to be_truthy
diff --git a/spec/lib/gitlab/dependency_linker/parser/gemfile_spec.rb b/spec/lib/gitlab/dependency_linker/parser/gemfile_spec.rb
index 5b69ef5af24..15f580a3a60 100644
--- a/spec/lib/gitlab/dependency_linker/parser/gemfile_spec.rb
+++ b/spec/lib/gitlab/dependency_linker/parser/gemfile_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::DependencyLinker::Parser::Gemfile do
+RSpec.describe Gitlab::DependencyLinker::Parser::Gemfile do
describe '#parse' do
let(:file_content) do
<<-CONTENT.strip_heredoc
diff --git a/spec/lib/gitlab/dependency_linker/podfile_linker_spec.rb b/spec/lib/gitlab/dependency_linker/podfile_linker_spec.rb
index 77326e73505..8e536c00ea6 100644
--- a/spec/lib/gitlab/dependency_linker/podfile_linker_spec.rb
+++ b/spec/lib/gitlab/dependency_linker/podfile_linker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::DependencyLinker::PodfileLinker do
+RSpec.describe Gitlab::DependencyLinker::PodfileLinker do
describe '.support?' do
it 'supports Podfile' do
expect(described_class.support?('Podfile')).to be_truthy
diff --git a/spec/lib/gitlab/dependency_linker/podspec_json_linker_spec.rb b/spec/lib/gitlab/dependency_linker/podspec_json_linker_spec.rb
index d522a08cdd9..1f81049a41e 100644
--- a/spec/lib/gitlab/dependency_linker/podspec_json_linker_spec.rb
+++ b/spec/lib/gitlab/dependency_linker/podspec_json_linker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::DependencyLinker::PodspecJsonLinker do
+RSpec.describe Gitlab::DependencyLinker::PodspecJsonLinker do
describe '.support?' do
it 'supports *.podspec.json' do
expect(described_class.support?('Reachability.podspec.json')).to be_truthy
diff --git a/spec/lib/gitlab/dependency_linker/podspec_linker_spec.rb b/spec/lib/gitlab/dependency_linker/podspec_linker_spec.rb
index baabd0c0460..132b5b21d85 100644
--- a/spec/lib/gitlab/dependency_linker/podspec_linker_spec.rb
+++ b/spec/lib/gitlab/dependency_linker/podspec_linker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::DependencyLinker::PodspecLinker do
+RSpec.describe Gitlab::DependencyLinker::PodspecLinker do
describe '.support?' do
it 'supports *.podspec' do
expect(described_class.support?('Reachability.podspec')).to be_truthy
diff --git a/spec/lib/gitlab/dependency_linker/requirements_txt_linker_spec.rb b/spec/lib/gitlab/dependency_linker/requirements_txt_linker_spec.rb
index 04ac5f10479..e59756cb7bc 100644
--- a/spec/lib/gitlab/dependency_linker/requirements_txt_linker_spec.rb
+++ b/spec/lib/gitlab/dependency_linker/requirements_txt_linker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::DependencyLinker::RequirementsTxtLinker do
+RSpec.describe Gitlab::DependencyLinker::RequirementsTxtLinker do
describe '.support?' do
it 'supports requirements.txt' do
expect(described_class.support?('requirements.txt')).to be_truthy
diff --git a/spec/lib/gitlab/dependency_linker_spec.rb b/spec/lib/gitlab/dependency_linker_spec.rb
index acd4376615c..2daa8df815d 100644
--- a/spec/lib/gitlab/dependency_linker_spec.rb
+++ b/spec/lib/gitlab/dependency_linker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::DependencyLinker do
+RSpec.describe Gitlab::DependencyLinker do
describe '.link' do
it 'links using GemfileLinker' do
blob_name = 'Gemfile'
diff --git a/spec/lib/gitlab/devise_failure_spec.rb b/spec/lib/gitlab/devise_failure_spec.rb
index eee05c7befd..a452de59795 100644
--- a/spec/lib/gitlab/devise_failure_spec.rb
+++ b/spec/lib/gitlab/devise_failure_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::DeviseFailure do
+RSpec.describe Gitlab::DeviseFailure do
let(:env) do
{
'REQUEST_URI' => 'http://test.host/',
diff --git a/spec/lib/gitlab/diff/diff_refs_spec.rb b/spec/lib/gitlab/diff/diff_refs_spec.rb
index 33a7cf5ae12..c1ee34ba874 100644
--- a/spec/lib/gitlab/diff/diff_refs_spec.rb
+++ b/spec/lib/gitlab/diff/diff_refs_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Diff::DiffRefs do
+RSpec.describe Gitlab::Diff::DiffRefs do
let(:project) { create(:project, :repository) }
describe '#==' do
diff --git a/spec/lib/gitlab/diff/file_collection/commit_spec.rb b/spec/lib/gitlab/diff/file_collection/commit_spec.rb
index 34ed22b8941..6c109e96a53 100644
--- a/spec/lib/gitlab/diff/file_collection/commit_spec.rb
+++ b/spec/lib/gitlab/diff/file_collection/commit_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Diff::FileCollection::Commit do
+RSpec.describe Gitlab::Diff::FileCollection::Commit do
let(:project) { create(:project, :repository) }
it_behaves_like 'diff statistics' do
diff --git a/spec/lib/gitlab/diff/file_collection/compare_spec.rb b/spec/lib/gitlab/diff/file_collection/compare_spec.rb
index f330f299ac1..168d58e584e 100644
--- a/spec/lib/gitlab/diff/file_collection/compare_spec.rb
+++ b/spec/lib/gitlab/diff/file_collection/compare_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Diff::FileCollection::Compare do
+RSpec.describe Gitlab::Diff::FileCollection::Compare do
include RepoHelpers
let(:project) { create(:project, :repository) }
diff --git a/spec/lib/gitlab/diff/file_collection/merge_request_diff_batch_spec.rb b/spec/lib/gitlab/diff/file_collection/merge_request_diff_batch_spec.rb
index 7e945d1d140..bd60c24859c 100644
--- a/spec/lib/gitlab/diff/file_collection/merge_request_diff_batch_spec.rb
+++ b/spec/lib/gitlab/diff/file_collection/merge_request_diff_batch_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Diff::FileCollection::MergeRequestDiffBatch do
+RSpec.describe Gitlab::Diff::FileCollection::MergeRequestDiffBatch do
let(:merge_request) { create(:merge_request) }
let(:batch_page) { 1 }
let(:batch_size) { 10 }
diff --git a/spec/lib/gitlab/diff/file_collection/merge_request_diff_spec.rb b/spec/lib/gitlab/diff/file_collection/merge_request_diff_spec.rb
index c2b6ca4164c..a5e714c90fc 100644
--- a/spec/lib/gitlab/diff/file_collection/merge_request_diff_spec.rb
+++ b/spec/lib/gitlab/diff/file_collection/merge_request_diff_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Diff::FileCollection::MergeRequestDiff do
+RSpec.describe Gitlab::Diff::FileCollection::MergeRequestDiff do
let(:merge_request) { create(:merge_request) }
let(:diffable) { merge_request.merge_request_diff }
let(:subject) { described_class.new(diffable, diff_options: nil) }
diff --git a/spec/lib/gitlab/diff/file_spec.rb b/spec/lib/gitlab/diff/file_spec.rb
index 8dbedcf26b9..94abfcf079a 100644
--- a/spec/lib/gitlab/diff/file_spec.rb
+++ b/spec/lib/gitlab/diff/file_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Diff::File do
+RSpec.describe Gitlab::Diff::File do
include RepoHelpers
let(:project) { create(:project, :repository) }
diff --git a/spec/lib/gitlab/diff/formatters/image_formatter_spec.rb b/spec/lib/gitlab/diff/formatters/image_formatter_spec.rb
index 3f88f39ba92..579776d44aa 100644
--- a/spec/lib/gitlab/diff/formatters/image_formatter_spec.rb
+++ b/spec/lib/gitlab/diff/formatters/image_formatter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Diff::Formatters::ImageFormatter do
+RSpec.describe Gitlab::Diff::Formatters::ImageFormatter do
let(:base_attrs) do
{
base_sha: 123,
diff --git a/spec/lib/gitlab/diff/formatters/text_formatter_spec.rb b/spec/lib/gitlab/diff/formatters/text_formatter_spec.rb
index 50dd597c5a7..41877a16ebf 100644
--- a/spec/lib/gitlab/diff/formatters/text_formatter_spec.rb
+++ b/spec/lib/gitlab/diff/formatters/text_formatter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Diff::Formatters::TextFormatter do
+RSpec.describe Gitlab::Diff::Formatters::TextFormatter do
let!(:base) do
{
base_sha: 123,
diff --git a/spec/lib/gitlab/diff/highlight_cache_spec.rb b/spec/lib/gitlab/diff/highlight_cache_spec.rb
index 3c128aad976..80cc10051c4 100644
--- a/spec/lib/gitlab/diff/highlight_cache_spec.rb
+++ b/spec/lib/gitlab/diff/highlight_cache_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Diff::HighlightCache, :clean_gitlab_redis_cache do
+RSpec.describe Gitlab::Diff::HighlightCache, :clean_gitlab_redis_cache do
let(:merge_request) { create(:merge_request_with_diffs) }
let(:diff_hash) do
{ ".gitignore-false-false-false" =>
diff --git a/spec/lib/gitlab/diff/highlight_spec.rb b/spec/lib/gitlab/diff/highlight_spec.rb
index ff4ec75358e..283437e7fbd 100644
--- a/spec/lib/gitlab/diff/highlight_spec.rb
+++ b/spec/lib/gitlab/diff/highlight_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Diff::Highlight do
+RSpec.describe Gitlab::Diff::Highlight do
include RepoHelpers
let(:project) { create(:project, :repository) }
diff --git a/spec/lib/gitlab/diff/inline_diff_markdown_marker_spec.rb b/spec/lib/gitlab/diff/inline_diff_markdown_marker_spec.rb
index a668bb464a4..60f7f3a103f 100644
--- a/spec/lib/gitlab/diff/inline_diff_markdown_marker_spec.rb
+++ b/spec/lib/gitlab/diff/inline_diff_markdown_marker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Diff::InlineDiffMarkdownMarker do
+RSpec.describe Gitlab::Diff::InlineDiffMarkdownMarker do
describe '#mark' do
let(:raw) { "abc 'def'" }
let(:inline_diffs) { [2..5] }
diff --git a/spec/lib/gitlab/diff/inline_diff_marker_spec.rb b/spec/lib/gitlab/diff/inline_diff_marker_spec.rb
index 26b99870b31..6820a7df95e 100644
--- a/spec/lib/gitlab/diff/inline_diff_marker_spec.rb
+++ b/spec/lib/gitlab/diff/inline_diff_marker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Diff::InlineDiffMarker do
+RSpec.describe Gitlab::Diff::InlineDiffMarker do
describe '#mark' do
let(:inline_diffs) { [2..5] }
let(:raw) { "abc 'def'" }
diff --git a/spec/lib/gitlab/diff/inline_diff_spec.rb b/spec/lib/gitlab/diff/inline_diff_spec.rb
index fdbee3b4230..35284e952f7 100644
--- a/spec/lib/gitlab/diff/inline_diff_spec.rb
+++ b/spec/lib/gitlab/diff/inline_diff_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Diff::InlineDiff do
+RSpec.describe Gitlab::Diff::InlineDiff do
describe '.for_lines' do
let(:diff) do
<<-EOF.strip_heredoc
diff --git a/spec/lib/gitlab/diff/line_mapper_spec.rb b/spec/lib/gitlab/diff/line_mapper_spec.rb
index 6a86f885c3b..1c1f8201a81 100644
--- a/spec/lib/gitlab/diff/line_mapper_spec.rb
+++ b/spec/lib/gitlab/diff/line_mapper_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Diff::LineMapper do
+RSpec.describe Gitlab::Diff::LineMapper do
include RepoHelpers
let(:project) { create(:project, :repository) }
diff --git a/spec/lib/gitlab/diff/line_spec.rb b/spec/lib/gitlab/diff/line_spec.rb
index 7961bec9d57..e10a50afde9 100644
--- a/spec/lib/gitlab/diff/line_spec.rb
+++ b/spec/lib/gitlab/diff/line_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Diff::Line do
+RSpec.describe Gitlab::Diff::Line do
shared_examples 'line object initialized by hash' do
it 'round-trips correctly with to_hash' do
expect(described_class.safe_init_from_hash(line.to_hash).to_hash)
diff --git a/spec/lib/gitlab/diff/lines_unfolder_spec.rb b/spec/lib/gitlab/diff/lines_unfolder_spec.rb
index ebcbe1fb1a0..b891f9e8285 100644
--- a/spec/lib/gitlab/diff/lines_unfolder_spec.rb
+++ b/spec/lib/gitlab/diff/lines_unfolder_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Diff::LinesUnfolder do
+RSpec.describe Gitlab::Diff::LinesUnfolder do
let(:raw_diff) do
<<-DIFF.strip_heredoc
@@ -7,9 +7,6 @@
diff --git a/spec/lib/gitlab/diff/parallel_diff_spec.rb b/spec/lib/gitlab/diff/parallel_diff_spec.rb
index d275bf2c223..f574d7ec707 100644
--- a/spec/lib/gitlab/diff/parallel_diff_spec.rb
+++ b/spec/lib/gitlab/diff/parallel_diff_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Diff::ParallelDiff do
+RSpec.describe Gitlab::Diff::ParallelDiff do
include RepoHelpers
let(:project) { create(:project, :repository) }
diff --git a/spec/lib/gitlab/diff/parser_spec.rb b/spec/lib/gitlab/diff/parser_spec.rb
index 00a446c4e20..7448ae0b2ea 100644
--- a/spec/lib/gitlab/diff/parser_spec.rb
+++ b/spec/lib/gitlab/diff/parser_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Diff::Parser do
+RSpec.describe Gitlab::Diff::Parser do
include RepoHelpers
let(:project) { create(:project, :repository) }
diff --git a/spec/lib/gitlab/diff/position_collection_spec.rb b/spec/lib/gitlab/diff/position_collection_spec.rb
index dd8434ab10b..b1478c774f1 100644
--- a/spec/lib/gitlab/diff/position_collection_spec.rb
+++ b/spec/lib/gitlab/diff/position_collection_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Diff::PositionCollection do
+RSpec.describe Gitlab::Diff::PositionCollection do
let(:merge_request) { build(:merge_request) }
let(:text_position) do
diff --git a/spec/lib/gitlab/diff/position_spec.rb b/spec/lib/gitlab/diff/position_spec.rb
index b32a2c59bb9..a7f6ea0cbfb 100644
--- a/spec/lib/gitlab/diff/position_spec.rb
+++ b/spec/lib/gitlab/diff/position_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Diff::Position do
+RSpec.describe Gitlab::Diff::Position do
include RepoHelpers
let(:project) { create(:project, :repository) }
diff --git a/spec/lib/gitlab/diff/position_tracer/image_strategy_spec.rb b/spec/lib/gitlab/diff/position_tracer/image_strategy_spec.rb
index 900816af53a..7dceb64b776 100644
--- a/spec/lib/gitlab/diff/position_tracer/image_strategy_spec.rb
+++ b/spec/lib/gitlab/diff/position_tracer/image_strategy_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Diff::PositionTracer::ImageStrategy do
+RSpec.describe Gitlab::Diff::PositionTracer::ImageStrategy do
include PositionTracerHelpers
let(:project) { create(:project, :repository) }
@@ -234,5 +234,118 @@ describe Gitlab::Diff::PositionTracer::ImageStrategy do
end
end
end
+
+ describe 'symlink scenarios' do
+ let(:new_file) { old_file_status == :new }
+ let(:deleted_file) { old_file_status == :deleted }
+ let(:renamed_file) { old_file_status == :renamed }
+
+ let(:file_identifier) { "#{file_name}-#{new_file}-#{deleted_file}-#{renamed_file}" }
+ let(:file_identifier_hash) { Digest::SHA1.hexdigest(file_identifier) }
+ let(:old_position) { position(old_path: file_name, new_path: file_name, position_type: 'image', file_identifier_hash: file_identifier_hash) }
+
+ let(:update_file_commit) do
+ initial_commit
+
+ update_file(
+ branch_name,
+ file_name,
+ Base64.encode64('morecontent')
+ )
+ end
+
+ let(:delete_file_commit) do
+ initial_commit
+
+ delete_file(branch_name, file_name)
+ end
+
+ let(:create_second_file_commit) do
+ initial_commit
+
+ create_file(
+ branch_name,
+ second_file_name,
+ Base64.encode64('morecontent')
+ )
+ end
+
+ before do
+ stub_feature_flags(file_identifier_hash: true)
+ end
+
+ describe 'from symlink to image' do
+ let(:initial_commit) { project.commit('a19c7f9a147e35e535c797cf148d29c24dac5544') }
+ let(:symlink_to_image_commit) { project.commit('8cfca8420812e5bd7479aa32cf33e0c95a3ca576') }
+ let(:branch_name) { 'diff-files-symlink-to-image' }
+ let(:file_name) { 'symlink-to-image.png' }
+
+ context "when the old position is on the new image file" do
+ let(:old_file_status) { :new }
+
+ context "when the image file's content was unchanged between the old and the new diff" do
+ let(:old_diff_refs) { diff_refs(initial_commit, symlink_to_image_commit) }
+ let(:new_diff_refs) { diff_refs(initial_commit, create_second_file_commit) }
+
+ it "returns the new position" do
+ expect_new_position(
+ old_path: file_name,
+ new_path: file_name
+ )
+ end
+ end
+
+ context "when the image file's content was changed between the old and the new diff" do
+ let(:old_diff_refs) { diff_refs(initial_commit, symlink_to_image_commit) }
+ let(:new_diff_refs) { diff_refs(initial_commit, update_file_commit) }
+ let(:change_diff_refs) { diff_refs(symlink_to_image_commit, update_file_commit) }
+
+ it "returns the position of the change" do
+ expect_change_position(
+ old_path: file_name,
+ new_path: file_name
+ )
+ end
+ end
+
+ context "when the image file was removed between the old and the new diff" do
+ let(:old_diff_refs) { diff_refs(initial_commit, symlink_to_image_commit) }
+ let(:new_diff_refs) { diff_refs(initial_commit, delete_file_commit) }
+ let(:change_diff_refs) { diff_refs(symlink_to_image_commit, delete_file_commit) }
+
+ it "returns the position of the change" do
+ expect_change_position(
+ old_path: file_name,
+ new_path: file_name
+ )
+ end
+ end
+ end
+ end
+
+ describe 'from image to symlink' do
+ let(:initial_commit) { project.commit('d10dcdfbbb2b59a959a5f5d66a4adf28f0ea4008') }
+ let(:image_to_symlink_commit) { project.commit('3e94fdaa60da8aed38401b91bc56be70d54ca424') }
+ let(:branch_name) { 'diff-files-image-to-symlink' }
+ let(:file_name) { 'image-to-symlink.png' }
+
+ context "when the old position is on the added image file" do
+ let(:old_file_status) { :new }
+
+ context "when the image file gets changed to a symlink between the old and the new diff" do
+ let(:old_diff_refs) { diff_refs(initial_commit.parent, initial_commit) }
+ let(:new_diff_refs) { diff_refs(initial_commit.parent, image_to_symlink_commit) }
+ let(:change_diff_refs) { diff_refs(initial_commit, image_to_symlink_commit) }
+
+ it "returns the position of the change" do
+ expect_change_position(
+ old_path: file_name,
+ new_path: file_name
+ )
+ end
+ end
+ end
+ end
+ end
end
end
diff --git a/spec/lib/gitlab/diff/position_tracer/line_strategy_spec.rb b/spec/lib/gitlab/diff/position_tracer/line_strategy_spec.rb
index 7f4902c5b86..d9f384fb47f 100644
--- a/spec/lib/gitlab/diff/position_tracer/line_strategy_spec.rb
+++ b/spec/lib/gitlab/diff/position_tracer/line_strategy_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Diff::PositionTracer::LineStrategy do
+RSpec.describe Gitlab::Diff::PositionTracer::LineStrategy do
# Douwe's diary New York City, 2016-06-28
# --------------------------------------------------------------------------
#
@@ -1801,5 +1801,143 @@ describe Gitlab::Diff::PositionTracer::LineStrategy do
end
end
end
+
+ describe 'symlink scenarios' do
+ let(:new_file) { old_file_status == :new }
+ let(:deleted_file) { old_file_status == :deleted }
+ let(:renamed_file) { old_file_status == :renamed }
+
+ let(:file_identifier) { "#{file_name}-#{new_file}-#{deleted_file}-#{renamed_file}" }
+ let(:file_identifier_hash) { Digest::SHA1.hexdigest(file_identifier) }
+
+ let(:update_line_commit) do
+ update_file(
+ branch_name,
+ file_name,
+ <<-CONTENT.strip_heredoc
+ A
+ BB
+ C
+ CONTENT
+ )
+ end
+
+ let(:delete_file_commit) do
+ delete_file(branch_name, file_name)
+ end
+
+ let(:create_second_file_commit) do
+ create_file(
+ branch_name,
+ second_file_name,
+ <<-CONTENT.strip_heredoc
+ D
+ E
+ CONTENT
+ )
+ end
+
+ before do
+ stub_feature_flags(file_identifier_hash: true)
+ end
+
+ describe 'from symlink to text' do
+ let(:initial_commit) { project.commit('0e5b363105e9176a77bac94d7ff6d8c4fb35c3eb') }
+ let(:symlink_to_text_commit) { project.commit('689815e617abc6889f1fded4834d2dd7d942a58e') }
+ let(:branch_name) { 'diff-files-symlink-to-text' }
+ let(:file_name) { 'symlink-to-text.txt' }
+ let(:old_position) { position(old_path: file_name, new_path: file_name, new_line: 3, file_identifier_hash: file_identifier_hash) }
+
+ before do
+ create_branch('diff-files-symlink-to-text-test', branch_name)
+ end
+
+ context "when the old position is on the new text file" do
+ let(:old_file_status) { :new }
+
+ context "when the text file's content was unchanged between the old and the new diff" do
+ let(:old_diff_refs) { diff_refs(initial_commit, symlink_to_text_commit) }
+ let(:new_diff_refs) { diff_refs(initial_commit, create_second_file_commit) }
+
+ it "returns the new position" do
+ expect_new_position(
+ new_path: old_position.new_path,
+ new_line: old_position.new_line
+ )
+ end
+ end
+
+ context "when the text file's content has change, but the line was unchanged between the old and the new diff" do
+ let(:old_diff_refs) { diff_refs(initial_commit, symlink_to_text_commit) }
+ let(:new_diff_refs) { diff_refs(initial_commit, update_line_commit) }
+
+ it "returns the new position" do
+ expect_new_position(
+ new_path: old_position.new_path,
+ new_line: old_position.new_line
+ )
+ end
+ end
+
+ context "when the text file's line was changed between the old and the new diff" do
+ let(:old_position) { position(old_path: file_name, new_path: file_name, new_line: 2, file_identifier_hash: file_identifier_hash) }
+
+ let(:old_diff_refs) { diff_refs(initial_commit, symlink_to_text_commit) }
+ let(:new_diff_refs) { diff_refs(initial_commit, update_line_commit) }
+ let(:change_diff_refs) { diff_refs(symlink_to_text_commit, update_line_commit) }
+
+ it "returns the position of the change" do
+ expect_change_position(
+ old_path: file_name,
+ new_path: file_name,
+ old_line: 2,
+ new_line: nil
+ )
+ end
+ end
+
+ context "when the text file was removed between the old and the new diff" do
+ let(:old_diff_refs) { diff_refs(initial_commit, symlink_to_text_commit) }
+ let(:new_diff_refs) { diff_refs(initial_commit, delete_file_commit) }
+ let(:change_diff_refs) { diff_refs(symlink_to_text_commit, delete_file_commit) }
+
+ it "returns the position of the change" do
+ expect_change_position(
+ old_path: file_name,
+ new_path: file_name,
+ old_line: 3,
+ new_line: nil
+ )
+ end
+ end
+ end
+
+ describe 'from text to symlink' do
+ let(:initial_commit) { project.commit('3db7bd90bab8ce8f02c9818590b84739a2e97230') }
+ let(:text_to_symlink_commit) { project.commit('5e2c2708c2e403dece5dd25759369150aac51644') }
+ let(:branch_name) { 'diff-files-text-to-symlink' }
+ let(:file_name) { 'text-to-symlink.txt' }
+
+ context "when the position is on the added text file" do
+ let(:old_file_status) { :new }
+
+ context "when the text file gets changed to a symlink between the old and the new diff" do
+ let(:old_diff_refs) { diff_refs(initial_commit.parent, initial_commit) }
+ let(:new_diff_refs) { diff_refs(initial_commit.parent, text_to_symlink_commit) }
+ let(:change_diff_refs) { diff_refs(initial_commit, text_to_symlink_commit) }
+
+ it "returns the position of the change" do
+ expect_change_position(
+ old_path: file_name,
+ new_path: file_name,
+ old_line: 3,
+ new_line: nil
+ )
+ end
+ end
+ end
+ end
+ end
+ end
end
end
diff --git a/spec/lib/gitlab/diff/position_tracer_spec.rb b/spec/lib/gitlab/diff/position_tracer_spec.rb
index 47d78e0b18c..fc649812b0a 100644
--- a/spec/lib/gitlab/diff/position_tracer_spec.rb
+++ b/spec/lib/gitlab/diff/position_tracer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Diff::PositionTracer do
+RSpec.describe Gitlab::Diff::PositionTracer do
include PositionTracerHelpers
subject do
diff --git a/spec/lib/gitlab/diff/stats_cache_spec.rb b/spec/lib/gitlab/diff/stats_cache_spec.rb
new file mode 100644
index 00000000000..8bf510c0bdd
--- /dev/null
+++ b/spec/lib/gitlab/diff/stats_cache_spec.rb
@@ -0,0 +1,84 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Diff::StatsCache, :use_clean_rails_memory_store_caching do
+ subject(:stats_cache) { described_class.new(cachable_key: cachable_key) }
+
+ let(:key) { ['diff_stats', cachable_key, described_class::VERSION].join(":") }
+ let(:cachable_key) { 'cachecachecache' }
+ let(:stat) { Gitaly::DiffStats.new(path: 'temp', additions: 10, deletions: 15) }
+ let(:stats) { Gitlab::Git::DiffStatsCollection.new([stat]) }
+ let(:cache) { Rails.cache }
+
+ describe '#read' do
+ before do
+ stats_cache.write_if_empty(stats)
+ end
+
+ it 'returns the expected stats' do
+ expect(stats_cache.read.to_json).to eq(stats.to_json)
+ end
+ end
+
+ describe '#write_if_empty' do
+ context 'when the cache already exists' do
+ before do
+ Rails.cache.write(key, true)
+ end
+
+ it 'does not write the stats' do
+ expect(cache).not_to receive(:write)
+
+ stats_cache.write_if_empty(stats)
+ end
+ end
+
+ context 'when the cache does not exist' do
+ it 'writes the stats' do
+ expect(cache)
+ .to receive(:write)
+ .with(key, stats.as_json, expires_in: described_class::EXPIRATION)
+ .and_call_original
+
+ stats_cache.write_if_empty(stats)
+
+ expect(stats_cache.read.to_a).to eq(stats.to_a)
+ end
+
+ context 'when given non utf-8 characters' do
+ let(:non_utf8_path) { '你好'.b }
+ let(:stat) { Gitaly::DiffStats.new(path: non_utf8_path, additions: 10, deletions: 15) }
+
+ it 'writes the stats' do
+ expect(cache)
+ .to receive(:write)
+ .with(key, stats.as_json, expires_in: described_class::EXPIRATION)
+ .and_call_original
+
+ stats_cache.write_if_empty(stats)
+
+ expect(stats_cache.read.to_a).to eq(stats.to_a)
+ end
+ end
+
+ context 'when given empty stats' do
+ let(:stats) { nil }
+
+ it 'does not write the stats' do
+ expect(cache).not_to receive(:write)
+
+ stats_cache.write_if_empty(stats)
+ end
+ end
+ end
+ end
+
+ describe '#clear' do
+ it 'clears cache' do
+ expect(cache).to receive(:delete).with(key)
+
+ stats_cache.clear
+ end
+ end
+end
diff --git a/spec/lib/gitlab/diff/suggestion_diff_spec.rb b/spec/lib/gitlab/diff/suggestion_diff_spec.rb
index 0d4fe33bc47..9546c581112 100644
--- a/spec/lib/gitlab/diff/suggestion_diff_spec.rb
+++ b/spec/lib/gitlab/diff/suggestion_diff_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Diff::SuggestionDiff do
+RSpec.describe Gitlab::Diff::SuggestionDiff do
describe '#diff_lines' do
let(:from_content) do
<<-BLOB.strip_heredoc
diff --git a/spec/lib/gitlab/diff/suggestion_spec.rb b/spec/lib/gitlab/diff/suggestion_spec.rb
index d7ca0e0a522..5a5c5555818 100644
--- a/spec/lib/gitlab/diff/suggestion_spec.rb
+++ b/spec/lib/gitlab/diff/suggestion_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Diff::Suggestion do
+RSpec.describe Gitlab::Diff::Suggestion do
shared_examples 'correct suggestion raw content' do
it 'returns correct raw data' do
expect(suggestion.to_hash).to include(from_content: expected_lines.join,
diff --git a/spec/lib/gitlab/diff/suggestions_parser_spec.rb b/spec/lib/gitlab/diff/suggestions_parser_spec.rb
index 1f2af42f6e7..5efce414dc8 100644
--- a/spec/lib/gitlab/diff/suggestions_parser_spec.rb
+++ b/spec/lib/gitlab/diff/suggestions_parser_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Diff::SuggestionsParser do
+RSpec.describe Gitlab::Diff::SuggestionsParser do
describe '.parse' do
let(:merge_request) { create(:merge_request) }
let(:project) { merge_request.project }
diff --git a/spec/lib/gitlab/discussions_diff/file_collection_spec.rb b/spec/lib/gitlab/discussions_diff/file_collection_spec.rb
index a13727b62ea..f85a68ada15 100644
--- a/spec/lib/gitlab/discussions_diff/file_collection_spec.rb
+++ b/spec/lib/gitlab/discussions_diff/file_collection_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::DiscussionsDiff::FileCollection do
+RSpec.describe Gitlab::DiscussionsDiff::FileCollection do
let(:merge_request) { create(:merge_request) }
let!(:diff_note_a) { create(:diff_note_on_merge_request, project: merge_request.project, noteable: merge_request) }
let!(:diff_note_b) { create(:diff_note_on_merge_request, project: merge_request.project, noteable: merge_request) }
diff --git a/spec/lib/gitlab/discussions_diff/highlight_cache_spec.rb b/spec/lib/gitlab/discussions_diff/highlight_cache_spec.rb
index 97d3a49ea90..9f10811d765 100644
--- a/spec/lib/gitlab/discussions_diff/highlight_cache_spec.rb
+++ b/spec/lib/gitlab/discussions_diff/highlight_cache_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::DiscussionsDiff::HighlightCache, :clean_gitlab_redis_cache do
+RSpec.describe Gitlab::DiscussionsDiff::HighlightCache, :clean_gitlab_redis_cache do
def fake_file(offset)
{
text: 'foo',
diff --git a/spec/lib/gitlab/doctor/secrets_spec.rb b/spec/lib/gitlab/doctor/secrets_spec.rb
index f118519fd9f..b9e054ce14f 100644
--- a/spec/lib/gitlab/doctor/secrets_spec.rb
+++ b/spec/lib/gitlab/doctor/secrets_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Doctor::Secrets do
+RSpec.describe Gitlab::Doctor::Secrets do
let!(:user) { create(:user, otp_secret: "test") }
let!(:group) { create(:group, runners_token: "test") }
let(:logger) { double(:logger).as_null_object }
diff --git a/spec/lib/gitlab/downtime_check/message_spec.rb b/spec/lib/gitlab/downtime_check/message_spec.rb
index 2beb5a19a32..2d82836db33 100644
--- a/spec/lib/gitlab/downtime_check/message_spec.rb
+++ b/spec/lib/gitlab/downtime_check/message_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::DowntimeCheck::Message do
+RSpec.describe Gitlab::DowntimeCheck::Message do
describe '#to_s' do
it 'returns an ANSI formatted String for an offline migration' do
message = described_class.new('foo.rb', true, 'hello')
diff --git a/spec/lib/gitlab/downtime_check_spec.rb b/spec/lib/gitlab/downtime_check_spec.rb
index 5a5e34961a4..761519425f6 100644
--- a/spec/lib/gitlab/downtime_check_spec.rb
+++ b/spec/lib/gitlab/downtime_check_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::DowntimeCheck do
+RSpec.describe Gitlab::DowntimeCheck do
subject { described_class.new }
let(:path) { 'foo.rb' }
diff --git a/spec/lib/gitlab/elasticsearch/logs/lines_spec.rb b/spec/lib/gitlab/elasticsearch/logs/lines_spec.rb
index 45a262c0e77..f93c1aa1974 100644
--- a/spec/lib/gitlab/elasticsearch/logs/lines_spec.rb
+++ b/spec/lib/gitlab/elasticsearch/logs/lines_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Elasticsearch::Logs::Lines do
+RSpec.describe Gitlab::Elasticsearch::Logs::Lines do
let(:client) { Elasticsearch::Transport::Client }
let(:es_message_1) { { timestamp: "2019-12-13T14:35:34.034Z", pod: "production-6866bc8974-m4sk4", message: "10.8.2.1 - - [25/Oct/2019:08:03:22 UTC] \"GET / HTTP/1.1\" 200 13" } }
diff --git a/spec/lib/gitlab/elasticsearch/logs/pods_spec.rb b/spec/lib/gitlab/elasticsearch/logs/pods_spec.rb
index c2c3074e965..07fa0980d36 100644
--- a/spec/lib/gitlab/elasticsearch/logs/pods_spec.rb
+++ b/spec/lib/gitlab/elasticsearch/logs/pods_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Elasticsearch::Logs::Pods do
+RSpec.describe Gitlab::Elasticsearch::Logs::Pods do
let(:client) { Elasticsearch::Transport::Client }
let(:es_query) { Gitlab::Json.parse(fixture_file('lib/elasticsearch/pods_query.json'), symbolize_names: true) }
diff --git a/spec/lib/gitlab/email/attachment_uploader_spec.rb b/spec/lib/gitlab/email/attachment_uploader_spec.rb
index 5104e0a3f8f..6beadb6e1a5 100644
--- a/spec/lib/gitlab/email/attachment_uploader_spec.rb
+++ b/spec/lib/gitlab/email/attachment_uploader_spec.rb
@@ -2,7 +2,7 @@
require "spec_helper"
-describe Gitlab::Email::AttachmentUploader do
+RSpec.describe Gitlab::Email::AttachmentUploader do
describe "#execute" do
let(:project) { create(:project) }
let(:message_raw) { fixture_file("emails/attachment.eml") }
diff --git a/spec/lib/gitlab/email/handler/create_issue_handler_spec.rb b/spec/lib/gitlab/email/handler/create_issue_handler_spec.rb
index 7833b9f387d..ee2173a9c8d 100644
--- a/spec/lib/gitlab/email/handler/create_issue_handler_spec.rb
+++ b/spec/lib/gitlab/email/handler/create_issue_handler_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Email::Handler::CreateIssueHandler do
+RSpec.describe Gitlab::Email::Handler::CreateIssueHandler do
include_context :email_shared_context
it_behaves_like :reply_processing_shared_examples
diff --git a/spec/lib/gitlab/email/handler/create_merge_request_handler_spec.rb b/spec/lib/gitlab/email/handler/create_merge_request_handler_spec.rb
index 9f5413f9607..75d5fc040cb 100644
--- a/spec/lib/gitlab/email/handler/create_merge_request_handler_spec.rb
+++ b/spec/lib/gitlab/email/handler/create_merge_request_handler_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Email::Handler::CreateMergeRequestHandler do
+RSpec.describe Gitlab::Email::Handler::CreateMergeRequestHandler do
include_context :email_shared_context
it_behaves_like :reply_processing_shared_examples
diff --git a/spec/lib/gitlab/email/handler/create_note_handler_spec.rb b/spec/lib/gitlab/email/handler/create_note_handler_spec.rb
index af963e1b695..e5598bbd10f 100644
--- a/spec/lib/gitlab/email/handler/create_note_handler_spec.rb
+++ b/spec/lib/gitlab/email/handler/create_note_handler_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Email::Handler::CreateNoteHandler do
+RSpec.describe Gitlab::Email::Handler::CreateNoteHandler do
include_context :email_shared_context
it_behaves_like :reply_processing_shared_examples
@@ -242,4 +242,70 @@ describe Gitlab::Email::Handler::CreateNoteHandler do
it_behaves_like 'a reply to existing comment'
end
+
+ context 'when the service desk' do
+ let(:project) { create(:project, :public, service_desk_enabled: true) }
+ let(:support_bot) { User.support_bot }
+ let(:noteable) { create(:issue, project: project, author: support_bot, title: 'service desk issue') }
+ let(:note) { create(:note, project: project, noteable: noteable) }
+ let(:email_raw) { fixture_file('emails/valid_reply_with_quick_actions.eml') }
+
+ let!(:sent_notification) do
+ SentNotification.record_note(note, support_bot.id, mail_key)
+ end
+
+ context 'is enabled' do
+ before do
+ allow(Gitlab::ServiceDesk).to receive(:enabled?).with(project: project).and_return(true)
+ project.project_feature.update!(issues_access_level: issues_access_level)
+ end
+
+ context 'when issues are enabled for everyone' do
+ let(:issues_access_level) { ProjectFeature::ENABLED }
+
+ it 'creates a comment' do
+ expect { receiver.execute }.to change { noteable.notes.count }.by(1)
+ end
+
+ context 'when quick actions are present' do
+ it 'encloses quick actions with code span markdown' do
+ receiver.execute
+ noteable.reload
+
+ note = Note.last
+ expect(note.note).to include("Jake out\n\n`/close`\n`/title test`")
+ expect(noteable.title).to eq('service desk issue')
+ expect(noteable).to be_opened
+ end
+ end
+ end
+
+ context 'when issues are protected members only' do
+ let(:issues_access_level) { ProjectFeature::PRIVATE }
+
+ it 'creates a comment' do
+ expect { receiver.execute }.to change { noteable.notes.count }.by(1)
+ end
+ end
+
+ context 'when issues are disabled' do
+ let(:issues_access_level) { ProjectFeature::DISABLED }
+
+ it 'does not create a comment' do
+ expect { receiver.execute }.to raise_error(Gitlab::Email::UserNotAuthorizedError)
+ end
+ end
+ end
+
+ context 'is disabled' do
+ before do
+ allow(Gitlab::ServiceDesk).to receive(:enabled?).and_return(false)
+ allow(Gitlab::ServiceDesk).to receive(:enabled?).with(project: project).and_return(false)
+ end
+
+ it 'does not create a comment' do
+ expect { receiver.execute }.to raise_error(Gitlab::Email::ProjectNotFound)
+ end
+ end
+ end
end
diff --git a/spec/lib/gitlab/email/handler/service_desk_handler_spec.rb b/spec/lib/gitlab/email/handler/service_desk_handler_spec.rb
new file mode 100644
index 00000000000..a38fe2c51ca
--- /dev/null
+++ b/spec/lib/gitlab/email/handler/service_desk_handler_spec.rb
@@ -0,0 +1,311 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Email::Handler::ServiceDeskHandler do
+ include_context :email_shared_context
+
+ before do
+ stub_incoming_email_setting(enabled: true, address: "incoming+%{key}@appmail.adventuretime.ooo")
+ stub_config_setting(host: 'localhost')
+ end
+
+ let(:email_raw) { email_fixture('emails/service_desk.eml') }
+ let_it_be(:namespace) { create(:namespace, name: "email") }
+ let(:expected_description) do
+ "Service desk stuff!\n\n```\na = b\n```\n\n`/label ~label1`\n`/assign @user1`\n`/close`\n![image](uploads/image.png)"
+ end
+
+ context 'service desk is enabled for the project' do
+ let_it_be(:project) { create(:project, :repository, :public, namespace: namespace, path: 'test', service_desk_enabled: true) }
+
+ before do
+ allow(Gitlab::ServiceDesk).to receive(:supported?).and_return(true)
+ end
+
+ shared_examples 'a new issue request' do
+ before do
+ setup_attachment
+ end
+
+ it 'creates a new issue' do
+ expect { receiver.execute }.to change { Issue.count }.by(1)
+
+ new_issue = Issue.last
+
+ expect(new_issue.author).to eql(User.support_bot)
+ expect(new_issue.confidential?).to be true
+ expect(new_issue.all_references.all).to be_empty
+ expect(new_issue.title).to eq("Service Desk (from jake@adventuretime.ooo): The message subject! @all")
+ expect(new_issue.description).to eq(expected_description.strip)
+ end
+
+ it 'sends thank you email' do
+ expect { receiver.execute }.to have_enqueued_job.on_queue('mailers')
+ end
+ end
+
+ context 'when everything is fine' do
+ it_behaves_like 'a new issue request'
+
+ context 'with legacy incoming email address' do
+ let(:email_raw) { fixture_file('emails/service_desk_legacy.eml') }
+
+ it_behaves_like 'a new issue request'
+ end
+
+ context 'when using issue templates' do
+ let_it_be(:user) { create(:user) }
+
+ before do
+ setup_attachment
+ end
+
+ context 'and template is present' do
+ let_it_be(:settings) { create(:service_desk_setting, project: project) }
+
+ def set_template_file(file_name, content)
+ file_path = ".gitlab/issue_templates/#{file_name}.md"
+ project.repository.create_file(user, file_path, content, message: 'message', branch_name: 'master')
+ settings.update!(issue_template_key: file_name)
+ end
+
+ it 'appends template text to issue description' do
+ set_template_file('service_desk', 'text from template')
+
+ receiver.execute
+
+ issue_description = Issue.last.description
+ expect(issue_description).to include(expected_description)
+ expect(issue_description.lines.last).to eq('text from template')
+ end
+
+ context 'when quick actions are present' do
+ let(:label) { create(:label, project: project, title: 'label1') }
+ let(:milestone) { create(:milestone, project: project) }
+ let!(:user) { create(:user, username: 'user1') }
+
+ before do
+ project.add_developer(user)
+ end
+
+ it 'applies quick action commands present on templates' do
+ file_content = %(Text from template \n/label ~#{label.title} \n/milestone %"#{milestone.name}"")
+ set_template_file('with_slash_commands', file_content)
+
+ receiver.execute
+
+ issue = Issue.last
+ expect(issue.description).to include('Text from template')
+ expect(issue.label_ids).to include(label.id)
+ expect(issue.milestone).to eq(milestone)
+ end
+
+ it 'redacts quick actions present on user email body' do
+ set_template_file('service_desk1', 'text from template')
+
+ receiver.execute
+
+ issue = Issue.last
+ expect(issue).to be_opened
+ expect(issue.description).to include('`/label ~label1`')
+ expect(issue.description).to include('`/assign @user1`')
+ expect(issue.description).to include('`/close`')
+ expect(issue.assignees).to be_empty
+ expect(issue.milestone).to be_nil
+ end
+ end
+ end
+
+ context 'and template cannot be found' do
+ before do
+ service = ServiceDeskSetting.new(project_id: project.id, issue_template_key: 'unknown')
+ service.save!(validate: false)
+ end
+
+ it 'does not append template text to issue description' do
+ receiver.execute
+
+ new_issue = Issue.last
+
+ expect(new_issue.description).to eq(expected_description.strip)
+ end
+
+ it 'creates support bot note on issue' do
+ receiver.execute
+
+ note = Note.last
+
+ expect(note.note).to include("WARNING: The template file unknown.md used for service desk issues is empty or could not be found.")
+ expect(note.author).to eq(User.support_bot)
+ end
+
+ it 'does not send warning note email' do
+ ActionMailer::Base.deliveries = []
+
+ perform_enqueued_jobs do
+ expect { receiver.execute }.to change { ActionMailer::Base.deliveries.size }.by(1)
+ end
+
+ # Only sends created issue email
+ expect(ActionMailer::Base.deliveries.last.text_part.body).to include("Thank you for your support request!")
+ end
+ end
+ end
+
+ context 'when using service desk key' do
+ let_it_be(:service_desk_settings) { create(:service_desk_setting, project: project, project_key: 'mykey') }
+ let(:email_raw) { service_desk_fixture('emails/service_desk_custom_address.eml') }
+ let(:receiver) { Gitlab::Email::ServiceDeskReceiver.new(email_raw) }
+
+ before do
+ stub_service_desk_email_setting(enabled: true, address: 'support+%{key}@example.com')
+ end
+
+ it_behaves_like 'a new issue request'
+
+ context 'when there is no project with the key' do
+ let(:email_raw) { service_desk_fixture('emails/service_desk_custom_address.eml', key: 'some_key') }
+
+ it 'bounces the email' do
+ expect { receiver.execute }.to raise_error(Gitlab::Email::ProjectNotFound)
+ end
+ end
+
+ context 'when the project slug does not match' do
+ let(:email_raw) { service_desk_fixture('emails/service_desk_custom_address.eml', slug: 'some-slug') }
+
+ it 'bounces the email' do
+ expect { receiver.execute }.to raise_error(Gitlab::Email::ProjectNotFound)
+ end
+ end
+
+ context 'when service_desk_custom_address feature is disabled' do
+ before do
+ stub_feature_flags(service_desk_custom_address: false)
+ end
+
+ it 'bounces the email' do
+ expect { receiver.execute }.to raise_error(Gitlab::Email::ProjectNotFound)
+ end
+ end
+ end
+ end
+
+ describe '#can_handle?' do
+ let(:mail) { Mail::Message.new(email_raw) }
+
+ it 'handles the new email key format' do
+ handler = described_class.new(mail, "h5bp-html5-boilerplate-#{project.project_id}-issue-")
+
+ expect(handler.instance_variable_get(:@project_id).to_i).to eq project.project_id
+ expect(handler.can_handle?).to be_truthy
+ end
+
+ it 'handles the legacy email key format' do
+ handler = described_class.new(mail, "h5bp/html5-boilerplate")
+
+ expect(handler.instance_variable_get(:@project_path)).to eq 'h5bp/html5-boilerplate'
+ expect(handler.can_handle?).to be_truthy
+ end
+
+ it "doesn't handle invalid email key" do
+ handler = described_class.new(mail, "h5bp-html5-boilerplate-invalid")
+
+ expect(handler.can_handle?).to be_falsey
+ end
+ end
+
+ context 'when there is no from address' do
+ before do
+ allow_next_instance_of(described_class) do |instance|
+ allow(instance).to receive(:from_address).and_return(nil)
+ end
+ end
+
+ it "creates a new issue" do
+ expect { receiver.execute }.to change { Issue.count }.by(1)
+ end
+
+ it 'does not send thank you email' do
+ expect { receiver.execute }.not_to have_enqueued_job.on_queue('mailers')
+ end
+ end
+
+ context 'when there is a sender address and a from address' do
+ let(:email_raw) { email_fixture('emails/service_desk_sender_and_from.eml') }
+
+ it 'prefers the from address' do
+ setup_attachment
+
+ expect { receiver.execute }.to change { Issue.count }.by(1)
+
+ new_issue = Issue.last
+
+ expect(new_issue.service_desk_reply_to).to eq('finn@adventuretime.ooo')
+ end
+ end
+
+ context 'when service desk is not enabled for project' do
+ before do
+ allow(Gitlab::ServiceDesk).to receive(:enabled?).and_return(false)
+ end
+
+ it 'does not create an issue' do
+ expect { receiver.execute rescue nil }.not_to change { Issue.count }
+ end
+
+ it 'does not send thank you email' do
+ expect { receiver.execute rescue nil }.not_to have_enqueued_job.on_queue('mailers')
+ end
+ end
+
+ context 'when the email is forwarded through an alias' do
+ let(:email_raw) { email_fixture('emails/service_desk_forwarded.eml') }
+
+ it_behaves_like 'a new issue request'
+ end
+
+ context 'when the email is forwarded' do
+ let(:email_raw) { email_fixture('emails/service_desk_forwarded_new_issue.eml') }
+
+ it_behaves_like 'a new issue request' do
+ let(:expected_description) do
+ <<~EOF
+ Service desk stuff!
+
+ ---------- Forwarded message ---------
+ From: Jake the Dog <jake@adventuretime.ooo>
+ To: <jake@adventuretime.ooo>
+
+
+ forwarded content
+
+ ![image](uploads/image.png)
+ EOF
+ end
+ end
+ end
+ end
+
+ context 'service desk is disabled for the project' do
+ let(:project) { create(:project, :public, namespace: namespace, path: 'test', service_desk_enabled: false) }
+
+ it 'bounces the email' do
+ expect { receiver.execute }.to raise_error(Gitlab::Email::ProcessingError)
+ end
+
+ it "doesn't create an issue" do
+ expect { receiver.execute rescue nil }.not_to change { Issue.count }
+ end
+ end
+
+ def email_fixture(path)
+ fixture_file(path).gsub('project_id', project.project_id.to_s)
+ end
+
+ def service_desk_fixture(path, slug: nil, key: 'mykey')
+ slug ||= project.full_path_slug.to_s
+ fixture_file(path).gsub('project_slug', slug).gsub('project_key', key)
+ end
+end
diff --git a/spec/lib/gitlab/email/handler/unsubscribe_handler_spec.rb b/spec/lib/gitlab/email/handler/unsubscribe_handler_spec.rb
index dcddd00df59..13ad9ddd8ef 100644
--- a/spec/lib/gitlab/email/handler/unsubscribe_handler_spec.rb
+++ b/spec/lib/gitlab/email/handler/unsubscribe_handler_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Email::Handler::UnsubscribeHandler do
+RSpec.describe Gitlab::Email::Handler::UnsubscribeHandler do
include_context :email_shared_context
before do
diff --git a/spec/lib/gitlab/email/handler_spec.rb b/spec/lib/gitlab/email/handler_spec.rb
index 6dbf069f07c..2cd8c31e6b2 100644
--- a/spec/lib/gitlab/email/handler_spec.rb
+++ b/spec/lib/gitlab/email/handler_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Email::Handler do
+RSpec.describe Gitlab::Email::Handler do
let(:email) { Mail.new { body 'email' } }
describe '.for' do
@@ -33,12 +33,40 @@ describe Gitlab::Email::Handler do
it 'returns nil if provided email is nil' do
expect(described_class.for(nil, '')).to be_nil
end
+
+ context 'new issue email' do
+ def handler_for(fixture, mail_key)
+ described_class.for(fixture_file(fixture), mail_key)
+ end
+
+ before do
+ stub_incoming_email_setting(enabled: true, address: "incoming+%{key}@appmail.adventuretime.ooo")
+ stub_config_setting(host: 'localhost')
+ end
+
+ let!(:user) { create(:user, email: 'jake@adventuretime.ooo', incoming_email_token: 'auth_token') }
+
+ context 'a Service Desk email' do
+ it 'uses the Service Desk handler' do
+ expect(handler_for('emails/service_desk.eml', 'some/project')).to be_instance_of(Gitlab::Email::Handler::ServiceDeskHandler)
+ end
+ end
+
+ it 'return new issue handler' do
+ expect(handler_for('emails/valid_new_issue.eml', 'some/project+auth_token')).to be_instance_of(Gitlab::Email::Handler::CreateIssueHandler)
+ end
+ end
end
describe 'regexps are set properly' do
let(:addresses) do
- %W(sent_notification_key#{Gitlab::IncomingEmail::UNSUBSCRIBE_SUFFIX} sent_notification_key path-to-project-123-user_email_token-merge-request path-to-project-123-user_email_token-issue) +
- %W(sent_notification_key#{Gitlab::IncomingEmail::UNSUBSCRIBE_SUFFIX_LEGACY} sent_notification_key path/to/project+merge-request+user_email_token path/to/project+user_email_token)
+ %W(sent_notification_key#{Gitlab::IncomingEmail::UNSUBSCRIBE_SUFFIX} sent_notification_key path-to-project-123-user_email_token-merge-request) +
+ %W(sent_notification_key#{Gitlab::IncomingEmail::UNSUBSCRIBE_SUFFIX_LEGACY} sent_notification_key path-to-project-123-user_email_token-issue) +
+ %w(path/to/project+user_email_token path/to/project+merge-request+user_email_token some/project)
+ end
+
+ before do
+ allow(Gitlab::ServiceDesk).to receive(:supported?).and_return(true)
end
it 'picks each handler at least once' do
@@ -46,12 +74,12 @@ describe Gitlab::Email::Handler do
described_class.for(email, address).class
end
- expect(matched_handlers.uniq).to match_array(ce_handlers)
+ expect(matched_handlers.uniq).to match_array(Gitlab::Email::Handler.handlers)
end
it 'can pick exactly one handler for each address' do
addresses.each do |address|
- matched_handlers = ce_handlers.select do |handler|
+ matched_handlers = Gitlab::Email::Handler.handlers.select do |handler|
handler.new(email, address).can_handle?
end
@@ -59,10 +87,4 @@ describe Gitlab::Email::Handler do
end
end
end
-
- def ce_handlers
- @ce_handlers ||= Gitlab::Email::Handler.handlers.reject do |handler|
- handler.name.start_with?('Gitlab::Email::Handler::EE::')
- end
- end
end
diff --git a/spec/lib/gitlab/email/hook/additional_headers_interceptor_spec.rb b/spec/lib/gitlab/email/hook/additional_headers_interceptor_spec.rb
index 65e4e27d56f..deaa086d689 100644
--- a/spec/lib/gitlab/email/hook/additional_headers_interceptor_spec.rb
+++ b/spec/lib/gitlab/email/hook/additional_headers_interceptor_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Email::Hook::AdditionalHeadersInterceptor do
+RSpec.describe Gitlab::Email::Hook::AdditionalHeadersInterceptor do
let(:mail) do
ActionMailer::Base.mail(to: 'test@mail.com', from: 'info@mail.com', body: 'hello')
end
diff --git a/spec/lib/gitlab/email/hook/delivery_metrics_observer_spec.rb b/spec/lib/gitlab/email/hook/delivery_metrics_observer_spec.rb
index 24da47c42ac..73a0573be26 100644
--- a/spec/lib/gitlab/email/hook/delivery_metrics_observer_spec.rb
+++ b/spec/lib/gitlab/email/hook/delivery_metrics_observer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Email::Hook::DeliveryMetricsObserver do
+RSpec.describe Gitlab::Email::Hook::DeliveryMetricsObserver do
let(:email) do
ActionMailer::Base.mail(to: 'test@example.com',
from: 'info@example.com',
diff --git a/spec/lib/gitlab/email/hook/disable_email_interceptor_spec.rb b/spec/lib/gitlab/email/hook/disable_email_interceptor_spec.rb
index c8ed12523d0..47f6015c6f8 100644
--- a/spec/lib/gitlab/email/hook/disable_email_interceptor_spec.rb
+++ b/spec/lib/gitlab/email/hook/disable_email_interceptor_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Email::Hook::DisableEmailInterceptor do
+RSpec.describe Gitlab::Email::Hook::DisableEmailInterceptor do
before do
Mail.register_interceptor(described_class)
end
diff --git a/spec/lib/gitlab/email/hook/smime_signature_interceptor_spec.rb b/spec/lib/gitlab/email/hook/smime_signature_interceptor_spec.rb
index 31ba48e9df1..56cf58dcf92 100644
--- a/spec/lib/gitlab/email/hook/smime_signature_interceptor_spec.rb
+++ b/spec/lib/gitlab/email/hook/smime_signature_interceptor_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Email::Hook::SmimeSignatureInterceptor do
+RSpec.describe Gitlab::Email::Hook::SmimeSignatureInterceptor do
include SmimeHelper
# certs generation is an expensive operation and they are used read-only,
diff --git a/spec/lib/gitlab/email/message/repository_push_spec.rb b/spec/lib/gitlab/email/message/repository_push_spec.rb
index 9e95d31f41c..10586527239 100644
--- a/spec/lib/gitlab/email/message/repository_push_spec.rb
+++ b/spec/lib/gitlab/email/message/repository_push_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Email::Message::RepositoryPush do
+RSpec.describe Gitlab::Email::Message::RepositoryPush do
include RepoHelpers
let!(:group) { create(:group, name: 'my_group') }
diff --git a/spec/lib/gitlab/email/receiver_spec.rb b/spec/lib/gitlab/email/receiver_spec.rb
index d860968ab98..592d3f3f0e4 100644
--- a/spec/lib/gitlab/email/receiver_spec.rb
+++ b/spec/lib/gitlab/email/receiver_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Email::Receiver do
+RSpec.describe Gitlab::Email::Receiver do
include_context :email_shared_context
shared_examples 'correctly finds the mail key' do
diff --git a/spec/lib/gitlab/email/reply_parser_spec.rb b/spec/lib/gitlab/email/reply_parser_spec.rb
index 646575b2edd..575ff7f357b 100644
--- a/spec/lib/gitlab/email/reply_parser_spec.rb
+++ b/spec/lib/gitlab/email/reply_parser_spec.rb
@@ -3,7 +3,7 @@
require "spec_helper"
# Inspired in great part by Discourse's Email::Receiver
-describe Gitlab::Email::ReplyParser do
+RSpec.describe Gitlab::Email::ReplyParser do
describe '#execute' do
def test_parse_body(mail_string, params = {})
described_class.new(Mail::Message.new(mail_string), params).execute
diff --git a/spec/lib/gitlab/email/service_desk_receiver_spec.rb b/spec/lib/gitlab/email/service_desk_receiver_spec.rb
new file mode 100644
index 00000000000..6ba58ad5e93
--- /dev/null
+++ b/spec/lib/gitlab/email/service_desk_receiver_spec.rb
@@ -0,0 +1,37 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Email::ServiceDeskReceiver do
+ let(:email) { fixture_file('emails/service_desk_custom_address.eml') }
+ let(:receiver) { described_class.new(email) }
+
+ context 'when the email contains a valid email address' do
+ before do
+ stub_service_desk_email_setting(enabled: true, address: 'support+%{key}@example.com')
+ end
+
+ it 'finds the service desk key' do
+ handler = double(execute: true, metrics_event: true, metrics_params: true)
+ expected_params = [
+ an_instance_of(Mail::Message), nil,
+ { service_desk_key: 'project_slug-project_key' }
+ ]
+
+ expect(Gitlab::Email::Handler::ServiceDeskHandler)
+ .to receive(:new).with(*expected_params).and_return(handler)
+
+ receiver.execute
+ end
+ end
+
+ context 'when the email does not contain a valid email address' do
+ before do
+ stub_service_desk_email_setting(enabled: true, address: 'other_support+%{key}@example.com')
+ end
+
+ it 'raises an error' do
+ expect { receiver.execute }.to raise_error(Gitlab::Email::UnknownIncomingEmail)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/email/smime/certificate_spec.rb b/spec/lib/gitlab/email/smime/certificate_spec.rb
index 07b8c1e4de1..e4a085d971b 100644
--- a/spec/lib/gitlab/email/smime/certificate_spec.rb
+++ b/spec/lib/gitlab/email/smime/certificate_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Email::Smime::Certificate do
+RSpec.describe Gitlab::Email::Smime::Certificate do
include SmimeHelper
# cert generation is an expensive operation and they are used read-only,
diff --git a/spec/lib/gitlab/email/smime/signer_spec.rb b/spec/lib/gitlab/email/smime/signer_spec.rb
index d891b86da08..217c0d62950 100644
--- a/spec/lib/gitlab/email/smime/signer_spec.rb
+++ b/spec/lib/gitlab/email/smime/signer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Email::Smime::Signer do
+RSpec.describe Gitlab::Email::Smime::Signer do
include SmimeHelper
let_it_be(:root_ca) { generate_root }
diff --git a/spec/lib/gitlab/emoji_spec.rb b/spec/lib/gitlab/emoji_spec.rb
new file mode 100644
index 00000000000..ada37f25d1e
--- /dev/null
+++ b/spec/lib/gitlab/emoji_spec.rb
@@ -0,0 +1,125 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Emoji do
+ let_it_be(:emojis) { Gemojione.index.instance_variable_get(:@emoji_by_name) }
+ let_it_be(:emojis_by_moji) { Gemojione.index.instance_variable_get(:@emoji_by_moji) }
+ let_it_be(:emoji_unicode_versions_by_name) { Gitlab::Json.parse(File.read(Rails.root.join('fixtures', 'emojis', 'emoji-unicode-version-map.json'))) }
+ let_it_be(:emojis_aliases) { Gitlab::Json.parse(File.read(Rails.root.join('fixtures', 'emojis', 'aliases.json'))) }
+
+ describe '.emojis' do
+ it 'returns emojis' do
+ current_emojis = described_class.emojis
+
+ expect(current_emojis).to eq(emojis)
+ end
+ end
+
+ describe '.emojis_by_moji' do
+ it 'return emojis by moji' do
+ current_emojis_by_moji = described_class.emojis_by_moji
+
+ expect(current_emojis_by_moji).to eq(emojis_by_moji)
+ end
+ end
+
+ describe '.emojis_unicodes' do
+ it 'returns emoji unicodes' do
+ emoji_keys = described_class.emojis_unicodes
+
+ expect(emoji_keys).to eq(emojis_by_moji.keys)
+ end
+ end
+
+ describe '.emojis_names' do
+ it 'returns emoji names' do
+ emoji_names = described_class.emojis_names
+
+ expect(emoji_names).to eq(emojis.keys)
+ end
+ end
+
+ describe '.emojis_aliases' do
+ it 'returns emoji aliases' do
+ emoji_aliases = described_class.emojis_aliases
+
+ expect(emoji_aliases).to eq(emojis_aliases)
+ end
+ end
+
+ describe '.emoji_filename' do
+ it 'returns emoji filename' do
+ # "100" => {"unicode"=>"1F4AF"...}
+ emoji_filename = described_class.emoji_filename('100')
+
+ expect(emoji_filename).to eq(emojis['100']['unicode'])
+ end
+ end
+
+ describe '.emoji_unicode_filename' do
+ it 'returns emoji unicode filename' do
+ emoji_unicode_filename = described_class.emoji_unicode_filename('💯')
+
+ expect(emoji_unicode_filename).to eq(emojis_by_moji['💯']['unicode'])
+ end
+ end
+
+ describe '.emoji_unicode_version' do
+ it 'returns emoji unicode version by name' do
+ emoji_unicode_version = described_class.emoji_unicode_version('100')
+
+ expect(emoji_unicode_version).to eq(emoji_unicode_versions_by_name['100'])
+ end
+ end
+
+ describe '.normalize_emoji_name' do
+ it 'returns same name if not found in aliases' do
+ emoji_name = described_class.normalize_emoji_name('random')
+
+ expect(emoji_name).to eq('random')
+ end
+
+ it 'returns name if name found in aliases' do
+ emoji_name = described_class.normalize_emoji_name('small_airplane')
+
+ expect(emoji_name).to eq(emojis_aliases['small_airplane'])
+ end
+ end
+
+ describe '.emoji_image_tag' do
+ it 'returns emoji image tag' do
+ emoji_image = described_class.emoji_image_tag('emoji_one', 'src_url')
+
+ expect(emoji_image).to eq( "<img class='emoji' title=':emoji_one:' alt=':emoji_one:' src='src_url' height='20' width='20' align='absmiddle' />")
+ end
+ end
+
+ describe '.emoji_exists?' do
+ it 'returns true if the name exists' do
+ emoji_exists = described_class.emoji_exists?('100')
+
+ expect(emoji_exists).to be_truthy
+ end
+
+ it 'returns false if the name does not exist' do
+ emoji_exists = described_class.emoji_exists?('random')
+
+ expect(emoji_exists).to be_falsey
+ end
+ end
+
+ describe '.gl_emoji_tag' do
+ it 'returns gl emoji tag if emoji is found' do
+ gl_tag = described_class.gl_emoji_tag('small_airplane')
+
+ expect(gl_tag).to eq('<gl-emoji title="small airplane" data-name="airplane_small" data-unicode-version="7.0">🛩</gl-emoji>')
+ end
+
+ it 'returns nil if emoji name is not found' do
+ gl_tag = described_class.gl_emoji_tag('random')
+
+ expect(gl_tag).to be_nil
+ end
+ end
+end
diff --git a/spec/lib/gitlab/encoding_helper_spec.rb b/spec/lib/gitlab/encoding_helper_spec.rb
index e6dfd8728aa..5394c04c6ba 100644
--- a/spec/lib/gitlab/encoding_helper_spec.rb
+++ b/spec/lib/gitlab/encoding_helper_spec.rb
@@ -2,7 +2,7 @@
require "spec_helper"
-describe Gitlab::EncodingHelper do
+RSpec.describe Gitlab::EncodingHelper do
let(:ext_class) { Class.new { extend Gitlab::EncodingHelper } }
let(:binary_string) { File.read(Rails.root + "spec/fixtures/dk.png") }
diff --git a/spec/lib/gitlab/error_tracking/stack_trace_highlight_decorator_spec.rb b/spec/lib/gitlab/error_tracking/stack_trace_highlight_decorator_spec.rb
index d553fb4848b..5c496d653b2 100644
--- a/spec/lib/gitlab/error_tracking/stack_trace_highlight_decorator_spec.rb
+++ b/spec/lib/gitlab/error_tracking/stack_trace_highlight_decorator_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::ErrorTracking::StackTraceHighlightDecorator do
+RSpec.describe Gitlab::ErrorTracking::StackTraceHighlightDecorator do
let(:error_event) { build(:error_tracking_error_event) }
describe '.decorate' do
diff --git a/spec/lib/gitlab/error_tracking_spec.rb b/spec/lib/gitlab/error_tracking_spec.rb
index c40369f5965..2cc9ff36c99 100644
--- a/spec/lib/gitlab/error_tracking_spec.rb
+++ b/spec/lib/gitlab/error_tracking_spec.rb
@@ -4,7 +4,7 @@ require 'spec_helper'
require 'raven/transports/dummy'
-describe Gitlab::ErrorTracking do
+RSpec.describe Gitlab::ErrorTracking do
let(:exception) { RuntimeError.new('boom') }
let(:issue_url) { 'http://gitlab.com/gitlab-org/gitlab-foss/issues/1' }
@@ -18,6 +18,8 @@ describe Gitlab::ErrorTracking do
]
end
+ let(:sentry_event) { Gitlab::Json.parse(Raven.client.transport.events.last[1]) }
+
before do
stub_sentry_settings
@@ -29,6 +31,86 @@ describe Gitlab::ErrorTracking do
end
end
+ describe '.configure' do
+ context 'default tags from GITLAB_SENTRY_EXTRA_TAGS' do
+ context 'when the value is a JSON hash' do
+ it 'includes those tags in all events' do
+ stub_env('GITLAB_SENTRY_EXTRA_TAGS', { foo: 'bar', baz: 'quux' }.to_json)
+
+ described_class.configure do |config|
+ config.encoding = 'json'
+ end
+
+ described_class.track_exception(StandardError.new)
+
+ expect(sentry_event['tags'].except('correlation_id', 'locale', 'program'))
+ .to eq('foo' => 'bar', 'baz' => 'quux')
+ end
+ end
+
+ context 'when the value is not set' do
+ before do
+ stub_env('GITLAB_SENTRY_EXTRA_TAGS', nil)
+ end
+
+ it 'does not log an error' do
+ expect(Gitlab::AppLogger).not_to receive(:debug)
+
+ described_class.configure do |config|
+ config.encoding = 'json'
+ end
+ end
+
+ it 'does not send any extra tags' do
+ described_class.configure do |config|
+ config.encoding = 'json'
+ end
+
+ described_class.track_exception(StandardError.new)
+
+ expect(sentry_event['tags'].keys).to contain_exactly('correlation_id', 'locale', 'program')
+ end
+ end
+
+ context 'when the value is not a JSON hash' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:env_var, :error) do
+ { foo: 'bar', baz: 'quux' }.inspect | 'JSON::ParserError'
+ [].to_json | 'NoMethodError'
+ [%w[foo bar]].to_json | 'NoMethodError'
+ %w[foo bar].to_json | 'NoMethodError'
+ '"string"' | 'NoMethodError'
+ end
+
+ with_them do
+ before do
+ stub_env('GITLAB_SENTRY_EXTRA_TAGS', env_var)
+ end
+
+ it 'does not include any extra tags' do
+ described_class.configure do |config|
+ config.encoding = 'json'
+ end
+
+ described_class.track_exception(StandardError.new)
+
+ expect(sentry_event['tags'].except('correlation_id', 'locale', 'program'))
+ .to be_empty
+ end
+
+ it 'logs the error class' do
+ expect(Gitlab::AppLogger).to receive(:debug).with(a_string_matching(error))
+
+ described_class.configure do |config|
+ config.encoding = 'json'
+ end
+ end
+ end
+ end
+ end
+ end
+
describe '.with_context' do
it 'adds the expected tags' do
described_class.with_context {}
@@ -202,8 +284,6 @@ describe Gitlab::ErrorTracking do
described_class.track_exception(exception, extra)
- sentry_event = Gitlab::Json.parse(Raven.client.transport.events.last[1])
-
expect(sentry_event.dig('extra', 'sidekiq', 'args')).to eq(['[FILTERED]', 1, 2])
end
end
diff --git a/spec/lib/gitlab/etag_caching/middleware_spec.rb b/spec/lib/gitlab/etag_caching/middleware_spec.rb
index 5e9df555241..361b2329e15 100644
--- a/spec/lib/gitlab/etag_caching/middleware_spec.rb
+++ b/spec/lib/gitlab/etag_caching/middleware_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::EtagCaching::Middleware do
+RSpec.describe Gitlab::EtagCaching::Middleware do
let(:app) { double(:app) }
let(:middleware) { described_class.new(app) }
let(:app_status_code) { 200 }
diff --git a/spec/lib/gitlab/etag_caching/router_spec.rb b/spec/lib/gitlab/etag_caching/router_spec.rb
index d9eeb5b9a2b..3e939e588ad 100644
--- a/spec/lib/gitlab/etag_caching/router_spec.rb
+++ b/spec/lib/gitlab/etag_caching/router_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::EtagCaching::Router do
+RSpec.describe Gitlab::EtagCaching::Router do
it 'matches issue notes endpoint' do
result = described_class.match(
'/my-group/and-subgroup/here-comes-the-project/noteable/issue/1/notes'
diff --git a/spec/lib/gitlab/exclusive_lease_helpers/sleeping_lock_spec.rb b/spec/lib/gitlab/exclusive_lease_helpers/sleeping_lock_spec.rb
index 8917eeec56f..f74fbf1206f 100644
--- a/spec/lib/gitlab/exclusive_lease_helpers/sleeping_lock_spec.rb
+++ b/spec/lib/gitlab/exclusive_lease_helpers/sleeping_lock_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::ExclusiveLeaseHelpers::SleepingLock, :clean_gitlab_redis_shared_state do
+RSpec.describe Gitlab::ExclusiveLeaseHelpers::SleepingLock, :clean_gitlab_redis_shared_state do
include ::ExclusiveLeaseHelpers
let(:timeout) { 1.second }
diff --git a/spec/lib/gitlab/exclusive_lease_helpers_spec.rb b/spec/lib/gitlab/exclusive_lease_helpers_spec.rb
index 9914518cda5..01e2fe8ce17 100644
--- a/spec/lib/gitlab/exclusive_lease_helpers_spec.rb
+++ b/spec/lib/gitlab/exclusive_lease_helpers_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::ExclusiveLeaseHelpers, :clean_gitlab_redis_shared_state do
+RSpec.describe Gitlab::ExclusiveLeaseHelpers, :clean_gitlab_redis_shared_state do
include ::ExclusiveLeaseHelpers
let(:class_instance) { (Class.new { include ::Gitlab::ExclusiveLeaseHelpers }).new }
diff --git a/spec/lib/gitlab/exclusive_lease_spec.rb b/spec/lib/gitlab/exclusive_lease_spec.rb
index 2c0bb23a0b6..e730ddd6577 100644
--- a/spec/lib/gitlab/exclusive_lease_spec.rb
+++ b/spec/lib/gitlab/exclusive_lease_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::ExclusiveLease, :clean_gitlab_redis_shared_state do
+RSpec.describe Gitlab::ExclusiveLease, :clean_gitlab_redis_shared_state do
let(:unique_key) { SecureRandom.hex(10) }
describe '#try_obtain' do
diff --git a/spec/lib/gitlab/experimentation_spec.rb b/spec/lib/gitlab/experimentation_spec.rb
index f6e6c031624..a6408aeae8b 100644
--- a/spec/lib/gitlab/experimentation_spec.rb
+++ b/spec/lib/gitlab/experimentation_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Experimentation do
+RSpec.describe Gitlab::Experimentation do
before do
stub_const('Gitlab::Experimentation::EXPERIMENTS', {
test_experiment: {
diff --git a/spec/lib/gitlab/external_authorization/access_spec.rb b/spec/lib/gitlab/external_authorization/access_spec.rb
index 8a08b2a6275..4bb81230ac0 100644
--- a/spec/lib/gitlab/external_authorization/access_spec.rb
+++ b/spec/lib/gitlab/external_authorization/access_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::ExternalAuthorization::Access, :clean_gitlab_redis_cache do
+RSpec.describe Gitlab::ExternalAuthorization::Access, :clean_gitlab_redis_cache do
subject(:access) { described_class.new(build(:user), 'dummy_label') }
describe '#loaded?' do
diff --git a/spec/lib/gitlab/external_authorization/cache_spec.rb b/spec/lib/gitlab/external_authorization/cache_spec.rb
index 1f217249f97..9037c04cf2b 100644
--- a/spec/lib/gitlab/external_authorization/cache_spec.rb
+++ b/spec/lib/gitlab/external_authorization/cache_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::ExternalAuthorization::Cache, :clean_gitlab_redis_cache do
+RSpec.describe Gitlab::ExternalAuthorization::Cache, :clean_gitlab_redis_cache do
let(:user) { build_stubbed(:user) }
let(:cache_key) { "external_authorization:user-#{user.id}:label-dummy_label" }
diff --git a/spec/lib/gitlab/external_authorization/client_spec.rb b/spec/lib/gitlab/external_authorization/client_spec.rb
index e28a155a47f..473b57441fa 100644
--- a/spec/lib/gitlab/external_authorization/client_spec.rb
+++ b/spec/lib/gitlab/external_authorization/client_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::ExternalAuthorization::Client do
+RSpec.describe Gitlab::ExternalAuthorization::Client do
let(:user) { build(:user, email: 'dummy_user@example.com') }
let(:dummy_url) { 'https://dummy.net/' }
diff --git a/spec/lib/gitlab/external_authorization/logger_spec.rb b/spec/lib/gitlab/external_authorization/logger_spec.rb
index 380e765309c..167ce500911 100644
--- a/spec/lib/gitlab/external_authorization/logger_spec.rb
+++ b/spec/lib/gitlab/external_authorization/logger_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::ExternalAuthorization::Logger do
+RSpec.describe Gitlab::ExternalAuthorization::Logger do
let(:request_time) { Time.parse('2018-03-26 20:22:15') }
def fake_access(has_access, user, load_type = :request)
diff --git a/spec/lib/gitlab/external_authorization/response_spec.rb b/spec/lib/gitlab/external_authorization/response_spec.rb
index 5ce3325ef77..11f83feb76f 100644
--- a/spec/lib/gitlab/external_authorization/response_spec.rb
+++ b/spec/lib/gitlab/external_authorization/response_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::ExternalAuthorization::Response do
+RSpec.describe Gitlab::ExternalAuthorization::Response do
let(:excon_response) { double }
subject(:response) { described_class.new(excon_response) }
diff --git a/spec/lib/gitlab/external_authorization_spec.rb b/spec/lib/gitlab/external_authorization_spec.rb
index 97055e7b3f9..76025d70e9a 100644
--- a/spec/lib/gitlab/external_authorization_spec.rb
+++ b/spec/lib/gitlab/external_authorization_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::ExternalAuthorization, :request_store do
+RSpec.describe Gitlab::ExternalAuthorization, :request_store do
include ExternalAuthorizationServiceHelpers
let(:user) { build(:user) }
diff --git a/spec/lib/gitlab/fake_application_settings_spec.rb b/spec/lib/gitlab/fake_application_settings_spec.rb
index 6a872185713..ec32afcfb7b 100644
--- a/spec/lib/gitlab/fake_application_settings_spec.rb
+++ b/spec/lib/gitlab/fake_application_settings_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::FakeApplicationSettings do
+RSpec.describe Gitlab::FakeApplicationSettings do
let(:defaults) do
described_class.defaults.merge(
foobar: 'asdf',
diff --git a/spec/lib/gitlab/file_detector_spec.rb b/spec/lib/gitlab/file_detector_spec.rb
index 5bf70ef898a..8c0c56ea2c3 100644
--- a/spec/lib/gitlab/file_detector_spec.rb
+++ b/spec/lib/gitlab/file_detector_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::FileDetector do
+RSpec.describe Gitlab::FileDetector do
describe '.types_in_paths' do
it 'returns the file types for the given paths' do
expect(described_class.types_in_paths(%w(README.md CHANGELOG VERSION VERSION)))
diff --git a/spec/lib/gitlab/file_finder_spec.rb b/spec/lib/gitlab/file_finder_spec.rb
index 90aa759671a..36fb4c48fb2 100644
--- a/spec/lib/gitlab/file_finder_spec.rb
+++ b/spec/lib/gitlab/file_finder_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::FileFinder do
+RSpec.describe Gitlab::FileFinder do
describe '#find' do
let(:project) { create(:project, :public, :repository) }
diff --git a/spec/lib/gitlab/file_hook_spec.rb b/spec/lib/gitlab/file_hook_spec.rb
index fda3583289b..7f40d9ae772 100644
--- a/spec/lib/gitlab/file_hook_spec.rb
+++ b/spec/lib/gitlab/file_hook_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::FileHook do
+RSpec.describe Gitlab::FileHook do
let(:file_hook) { Rails.root.join('file_hooks', 'test.rb') }
let(:tmp_file) { Tempfile.new('file_hook-dump') }
diff --git a/spec/lib/gitlab/file_markdown_link_builder_spec.rb b/spec/lib/gitlab/file_markdown_link_builder_spec.rb
index de0ac9733e6..ea21bda12d3 100644
--- a/spec/lib/gitlab/file_markdown_link_builder_spec.rb
+++ b/spec/lib/gitlab/file_markdown_link_builder_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe Gitlab::FileMarkdownLinkBuilder do
+RSpec.describe Gitlab::FileMarkdownLinkBuilder do
let(:custom_class) do
Class.new do
include Gitlab::FileMarkdownLinkBuilder
diff --git a/spec/lib/gitlab/file_type_detection_spec.rb b/spec/lib/gitlab/file_type_detection_spec.rb
index 2f1fc57c559..ba5e7cfabf2 100644
--- a/spec/lib/gitlab/file_type_detection_spec.rb
+++ b/spec/lib/gitlab/file_type_detection_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe Gitlab::FileTypeDetection do
+RSpec.describe Gitlab::FileTypeDetection do
describe '.extension_match?' do
let(:extensions) { %w[foo bar] }
diff --git a/spec/lib/gitlab/fogbugz_import/client_spec.rb b/spec/lib/gitlab/fogbugz_import/client_spec.rb
index ca6f374476c..560cb98ccfd 100644
--- a/spec/lib/gitlab/fogbugz_import/client_spec.rb
+++ b/spec/lib/gitlab/fogbugz_import/client_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::FogbugzImport::Client do
+RSpec.describe Gitlab::FogbugzImport::Client do
let(:client) { described_class.new(uri: '', token: '') }
let(:one_user) { { 'people' => { 'person' => { "ixPerson" => "2", "sFullName" => "James" } } } }
let(:two_users) { { 'people' => { 'person' => [one_user, { "ixPerson" => "3" }] } } }
diff --git a/spec/lib/gitlab/fogbugz_import/importer_spec.rb b/spec/lib/gitlab/fogbugz_import/importer_spec.rb
index 9e67047eeda..d2be3e3f6b1 100644
--- a/spec/lib/gitlab/fogbugz_import/importer_spec.rb
+++ b/spec/lib/gitlab/fogbugz_import/importer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::FogbugzImport::Importer do
+RSpec.describe Gitlab::FogbugzImport::Importer do
let(:project) { create(:project_empty_repo) }
let(:importer) { described_class.new(project) }
let(:repo) do
diff --git a/spec/lib/gitlab/fogbugz_import/project_creator_spec.rb b/spec/lib/gitlab/fogbugz_import/project_creator_spec.rb
index 503fe897e29..6b8bb2229a9 100644
--- a/spec/lib/gitlab/fogbugz_import/project_creator_spec.rb
+++ b/spec/lib/gitlab/fogbugz_import/project_creator_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::FogbugzImport::ProjectCreator do
+RSpec.describe Gitlab::FogbugzImport::ProjectCreator do
let(:user) { create(:user) }
let(:repo) do
diff --git a/spec/lib/gitlab/gfm/reference_rewriter_spec.rb b/spec/lib/gitlab/gfm/reference_rewriter_spec.rb
index 335135696ef..1c9004262c5 100644
--- a/spec/lib/gitlab/gfm/reference_rewriter_spec.rb
+++ b/spec/lib/gitlab/gfm/reference_rewriter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Gfm::ReferenceRewriter do
+RSpec.describe Gitlab::Gfm::ReferenceRewriter do
let_it_be(:group) { create(:group) }
let_it_be(:user) { create(:user) }
diff --git a/spec/lib/gitlab/gfm/uploads_rewriter_spec.rb b/spec/lib/gitlab/gfm/uploads_rewriter_spec.rb
index 7279399d1b8..5b78acc3b1d 100644
--- a/spec/lib/gitlab/gfm/uploads_rewriter_spec.rb
+++ b/spec/lib/gitlab/gfm/uploads_rewriter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Gfm::UploadsRewriter do
+RSpec.describe Gitlab::Gfm::UploadsRewriter do
let(:user) { create(:user) }
let(:old_project) { create(:project) }
let(:new_project) { create(:project) }
diff --git a/spec/lib/gitlab/git/attributes_at_ref_parser_spec.rb b/spec/lib/gitlab/git/attributes_at_ref_parser_spec.rb
index 6c4f650fa83..96cd70b4ff1 100644
--- a/spec/lib/gitlab/git/attributes_at_ref_parser_spec.rb
+++ b/spec/lib/gitlab/git/attributes_at_ref_parser_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Git::AttributesAtRefParser, :seed_helper do
+RSpec.describe Gitlab::Git::AttributesAtRefParser, :seed_helper do
let(:project) { create(:project, :repository) }
let(:repository) { project.repository }
diff --git a/spec/lib/gitlab/git/attributes_parser_spec.rb b/spec/lib/gitlab/git/attributes_parser_spec.rb
index 45db4acd3ac..4bc39921e85 100644
--- a/spec/lib/gitlab/git/attributes_parser_spec.rb
+++ b/spec/lib/gitlab/git/attributes_parser_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Git::AttributesParser, :seed_helper do
+RSpec.describe Gitlab::Git::AttributesParser, :seed_helper do
let(:attributes_path) { File.join(SEED_STORAGE_PATH, 'with-git-attributes.git', 'info', 'attributes') }
let(:data) { File.read(attributes_path) }
diff --git a/spec/lib/gitlab/git/blame_spec.rb b/spec/lib/gitlab/git/blame_spec.rb
index 9b2d6fa3bcb..67d7b37dd45 100644
--- a/spec/lib/gitlab/git/blame_spec.rb
+++ b/spec/lib/gitlab/git/blame_spec.rb
@@ -2,7 +2,7 @@
require "spec_helper"
-describe Gitlab::Git::Blame, :seed_helper do
+RSpec.describe Gitlab::Git::Blame, :seed_helper do
let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH, '', 'group/project') }
let(:blame) do
Gitlab::Git::Blame.new(repository, SeedRepo::Commit::ID, "CONTRIBUTING.md")
diff --git a/spec/lib/gitlab/git/blob_spec.rb b/spec/lib/gitlab/git/blob_spec.rb
index 46d9b78c14b..fb4510a78de 100644
--- a/spec/lib/gitlab/git/blob_spec.rb
+++ b/spec/lib/gitlab/git/blob_spec.rb
@@ -2,7 +2,7 @@
require "spec_helper"
-describe Gitlab::Git::Blob, :seed_helper do
+RSpec.describe Gitlab::Git::Blob, :seed_helper do
let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH, '', 'group/project') }
let(:rugged) do
Rugged::Repository.new(File.join(TestEnv.repos_path, TEST_REPO_PATH))
diff --git a/spec/lib/gitlab/git/branch_spec.rb b/spec/lib/gitlab/git/branch_spec.rb
index cb3f4df2dbd..bac1b4c57f9 100644
--- a/spec/lib/gitlab/git/branch_spec.rb
+++ b/spec/lib/gitlab/git/branch_spec.rb
@@ -2,7 +2,7 @@
require "spec_helper"
-describe Gitlab::Git::Branch, :seed_helper do
+RSpec.describe Gitlab::Git::Branch, :seed_helper do
let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH, '', 'group/project') }
let(:rugged) do
Rugged::Repository.new(File.join(TestEnv.repos_path, repository.relative_path))
diff --git a/spec/lib/gitlab/git/bundle_file_spec.rb b/spec/lib/gitlab/git/bundle_file_spec.rb
index e88e163a03f..701febadc1b 100644
--- a/spec/lib/gitlab/git/bundle_file_spec.rb
+++ b/spec/lib/gitlab/git/bundle_file_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Git::BundleFile do
+RSpec.describe Gitlab::Git::BundleFile do
describe '.check!' do
let(:valid_bundle) { Tempfile.new }
let(:valid_bundle_path) { valid_bundle.path }
diff --git a/spec/lib/gitlab/git/changes_spec.rb b/spec/lib/gitlab/git/changes_spec.rb
index 7f56d30cb48..310be7a3731 100644
--- a/spec/lib/gitlab/git/changes_spec.rb
+++ b/spec/lib/gitlab/git/changes_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Git::Changes do
+RSpec.describe Gitlab::Git::Changes do
let(:changes) { described_class.new }
describe '#includes_branches?' do
diff --git a/spec/lib/gitlab/git/commit_spec.rb b/spec/lib/gitlab/git/commit_spec.rb
index edd367673fb..666b49f27f7 100644
--- a/spec/lib/gitlab/git/commit_spec.rb
+++ b/spec/lib/gitlab/git/commit_spec.rb
@@ -2,7 +2,7 @@
require "spec_helper"
-describe Gitlab::Git::Commit, :seed_helper do
+RSpec.describe Gitlab::Git::Commit, :seed_helper do
include GitHelpers
let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH, '', 'group/project') }
@@ -227,6 +227,34 @@ describe Gitlab::Git::Commit, :seed_helper do
end
end
+ context 'pathspec' do
+ let(:pathspec) { 'files/ruby/*' }
+
+ context 'with default literal_pathspec value' do
+ it 'finds the seed commit' do
+ commit = described_class.last_for_path(repository, 'master', pathspec)
+
+ expect(commit.id).to eq(SeedRepo::Commit::ID)
+ end
+ end
+
+ context 'with literal_pathspec set to false' do
+ it 'finds the seed commit' do
+ commit = described_class.last_for_path(repository, 'master', pathspec, literal_pathspec: false)
+
+ expect(commit.id).to eq(SeedRepo::Commit::ID)
+ end
+ end
+
+ context 'with literal_pathspec set to true' do
+ it 'does not find the seed commit' do
+ commit = described_class.last_for_path(repository, 'master', pathspec, literal_pathspec: true)
+
+ expect(commit).to be_nil
+ end
+ end
+ end
+
context 'ref + path' do
subject { described_class.last_for_path(repository, SeedRepo::Commit::ID, 'encoding') }
@@ -560,7 +588,7 @@ describe Gitlab::Git::Commit, :seed_helper do
end
skip 'move this test to gitaly-ruby' do
- describe '#init_from_rugged' do
+ RSpec.describe '#init_from_rugged' do
let(:gitlab_commit) { described_class.new(repository, rugged_commit) }
subject { gitlab_commit }
diff --git a/spec/lib/gitlab/git/compare_spec.rb b/spec/lib/gitlab/git/compare_spec.rb
index 6136df57acb..51043355ede 100644
--- a/spec/lib/gitlab/git/compare_spec.rb
+++ b/spec/lib/gitlab/git/compare_spec.rb
@@ -2,7 +2,7 @@
require "spec_helper"
-describe Gitlab::Git::Compare, :seed_helper do
+RSpec.describe Gitlab::Git::Compare, :seed_helper do
let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH, '', 'group/project') }
let(:compare) { Gitlab::Git::Compare.new(repository, SeedRepo::BigCommit::ID, SeedRepo::Commit::ID, straight: false) }
let(:compare_straight) { Gitlab::Git::Compare.new(repository, SeedRepo::BigCommit::ID, SeedRepo::Commit::ID, straight: true) }
diff --git a/spec/lib/gitlab/git/conflict/file_spec.rb b/spec/lib/gitlab/git/conflict/file_spec.rb
index 0ee9ff93e87..454a48a1d3a 100644
--- a/spec/lib/gitlab/git/conflict/file_spec.rb
+++ b/spec/lib/gitlab/git/conflict/file_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Git::Conflict::File do
+RSpec.describe Gitlab::Git::Conflict::File do
let(:conflict) { { theirs: { path: 'foo', mode: 33188 }, ours: { path: 'foo', mode: 33188 } } }
let(:invalid_content) { described_class.new(nil, nil, conflict, (+"a\xC4\xFC").force_encoding(Encoding::ASCII_8BIT)) }
let(:valid_content) { described_class.new(nil, nil, conflict, (+"Espa\xC3\xB1a").force_encoding(Encoding::ASCII_8BIT)) }
diff --git a/spec/lib/gitlab/git/conflict/parser_spec.rb b/spec/lib/gitlab/git/conflict/parser_spec.rb
index 92ea44394b1..67bd48256ce 100644
--- a/spec/lib/gitlab/git/conflict/parser_spec.rb
+++ b/spec/lib/gitlab/git/conflict/parser_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Git::Conflict::Parser do
+RSpec.describe Gitlab::Git::Conflict::Parser do
describe '.parse' do
def parse_text(text)
described_class.parse(text, our_path: 'README.md', their_path: 'README.md')
diff --git a/spec/lib/gitlab/git/cross_repo_comparer_spec.rb b/spec/lib/gitlab/git/cross_repo_comparer_spec.rb
index 8b37b6d1667..1c49486b7b1 100644
--- a/spec/lib/gitlab/git/cross_repo_comparer_spec.rb
+++ b/spec/lib/gitlab/git/cross_repo_comparer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Git::CrossRepoComparer do
+RSpec.describe Gitlab::Git::CrossRepoComparer do
let(:source_project) { create(:project, :repository) }
let(:target_project) { create(:project, :repository) }
diff --git a/spec/lib/gitlab/git/diff_collection_spec.rb b/spec/lib/gitlab/git/diff_collection_spec.rb
index 6aa4f884d20..6da07ce84a1 100644
--- a/spec/lib/gitlab/git/diff_collection_spec.rb
+++ b/spec/lib/gitlab/git/diff_collection_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Git::DiffCollection, :seed_helper do
+RSpec.describe Gitlab::Git::DiffCollection, :seed_helper do
before do
stub_const('MutatingConstantIterator', Class.new)
diff --git a/spec/lib/gitlab/git/diff_spec.rb b/spec/lib/gitlab/git/diff_spec.rb
index ff54d7fbcd3..91688c31f5e 100644
--- a/spec/lib/gitlab/git/diff_spec.rb
+++ b/spec/lib/gitlab/git/diff_spec.rb
@@ -2,7 +2,7 @@
require "spec_helper"
-describe Gitlab::Git::Diff, :seed_helper do
+RSpec.describe Gitlab::Git::Diff, :seed_helper do
let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH, '', 'group/project') }
let(:gitaly_diff) do
Gitlab::GitalyClient::Diff.new(
diff --git a/spec/lib/gitlab/git/diff_stats_collection_spec.rb b/spec/lib/gitlab/git/diff_stats_collection_spec.rb
index 82d15a49062..f2fe03829be 100644
--- a/spec/lib/gitlab/git/diff_stats_collection_spec.rb
+++ b/spec/lib/gitlab/git/diff_stats_collection_spec.rb
@@ -2,13 +2,13 @@
require "spec_helper"
-describe Gitlab::Git::DiffStatsCollection do
+RSpec.describe Gitlab::Git::DiffStatsCollection do
let(:stats_a) do
- double(Gitaly::DiffStats, additions: 10, deletions: 15, path: 'foo')
+ Gitaly::DiffStats.new(additions: 10, deletions: 15, path: 'foo')
end
let(:stats_b) do
- double(Gitaly::DiffStats, additions: 5, deletions: 1, path: 'bar')
+ Gitaly::DiffStats.new(additions: 5, deletions: 1, path: 'bar')
end
let(:diff_stats) { [stats_a, stats_b] }
diff --git a/spec/lib/gitlab/git/gitmodules_parser_spec.rb b/spec/lib/gitlab/git/gitmodules_parser_spec.rb
index 58d1d2c71da..0e386c7f3d1 100644
--- a/spec/lib/gitlab/git/gitmodules_parser_spec.rb
+++ b/spec/lib/gitlab/git/gitmodules_parser_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Git::GitmodulesParser do
+RSpec.describe Gitlab::Git::GitmodulesParser do
it 'parses a .gitmodules file correctly' do
data = <<~GITMODULES
[submodule "vendor/libgit2"]
diff --git a/spec/lib/gitlab/git/hook_env_spec.rb b/spec/lib/gitlab/git/hook_env_spec.rb
index 22b016cee3e..c8f9218916e 100644
--- a/spec/lib/gitlab/git/hook_env_spec.rb
+++ b/spec/lib/gitlab/git/hook_env_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Git::HookEnv do
+RSpec.describe Gitlab::Git::HookEnv do
let(:gl_repository) { 'project-123' }
describe ".set" do
diff --git a/spec/lib/gitlab/git/keep_around_spec.rb b/spec/lib/gitlab/git/keep_around_spec.rb
index 04ccf86cd28..44c3caf3f8d 100644
--- a/spec/lib/gitlab/git/keep_around_spec.rb
+++ b/spec/lib/gitlab/git/keep_around_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Git::KeepAround do
+RSpec.describe Gitlab::Git::KeepAround do
include RepoHelpers
let(:repository) { create(:project, :repository).repository }
diff --git a/spec/lib/gitlab/git/lfs_changes_spec.rb b/spec/lib/gitlab/git/lfs_changes_spec.rb
index adc63401b89..286c5b98771 100644
--- a/spec/lib/gitlab/git/lfs_changes_spec.rb
+++ b/spec/lib/gitlab/git/lfs_changes_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Git::LfsChanges do
+RSpec.describe Gitlab::Git::LfsChanges do
let_it_be(:project) { create(:project, :repository) }
let(:newrev) { '54fcc214b94e78d7a41a9a8fe6d87a5e59500e51' }
let(:blob_object_id) { '0c304a93cb8430108629bbbcaa27db3343299bc0' }
diff --git a/spec/lib/gitlab/git/lfs_pointer_file_spec.rb b/spec/lib/gitlab/git/lfs_pointer_file_spec.rb
index 8bb26ed4854..f45c7cccca0 100644
--- a/spec/lib/gitlab/git/lfs_pointer_file_spec.rb
+++ b/spec/lib/gitlab/git/lfs_pointer_file_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Git::LfsPointerFile do
+RSpec.describe Gitlab::Git::LfsPointerFile do
let(:data) { "1234\n" }
subject { described_class.new(data) }
diff --git a/spec/lib/gitlab/git/merge_base_spec.rb b/spec/lib/gitlab/git/merge_base_spec.rb
index d92b13c5023..1410e44a220 100644
--- a/spec/lib/gitlab/git/merge_base_spec.rb
+++ b/spec/lib/gitlab/git/merge_base_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Git::MergeBase do
+RSpec.describe Gitlab::Git::MergeBase do
let_it_be(:project) { create(:project, :repository) }
let(:repository) { project.repository }
diff --git a/spec/lib/gitlab/git/object_pool_spec.rb b/spec/lib/gitlab/git/object_pool_spec.rb
index ebeb7b7b633..c8fbc674c73 100644
--- a/spec/lib/gitlab/git/object_pool_spec.rb
+++ b/spec/lib/gitlab/git/object_pool_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Git::ObjectPool do
+RSpec.describe Gitlab::Git::ObjectPool do
include RepoHelpers
let(:pool_repository) { create(:pool_repository) }
diff --git a/spec/lib/gitlab/git/patches/collection_spec.rb b/spec/lib/gitlab/git/patches/collection_spec.rb
index 080be141c59..eb92f4663c8 100644
--- a/spec/lib/gitlab/git/patches/collection_spec.rb
+++ b/spec/lib/gitlab/git/patches/collection_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe Gitlab::Git::Patches::Collection do
+RSpec.describe Gitlab::Git::Patches::Collection do
let(:patches_folder) { Rails.root.join('spec/fixtures/patchfiles') }
let(:patch_content1) do
File.read(File.join(patches_folder, "0001-This-does-not-apply-to-the-feature-branch.patch"))
diff --git a/spec/lib/gitlab/git/patches/commit_patches_spec.rb b/spec/lib/gitlab/git/patches/commit_patches_spec.rb
index 760112155ce..cd1e03a6de0 100644
--- a/spec/lib/gitlab/git/patches/commit_patches_spec.rb
+++ b/spec/lib/gitlab/git/patches/commit_patches_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe Gitlab::Git::Patches::CommitPatches do
+RSpec.describe Gitlab::Git::Patches::CommitPatches do
describe '#commit' do
let(:patches) do
patches_folder = Rails.root.join('spec/fixtures/patchfiles')
diff --git a/spec/lib/gitlab/git/patches/patch_spec.rb b/spec/lib/gitlab/git/patches/patch_spec.rb
index 7466e853b65..629f43d3636 100644
--- a/spec/lib/gitlab/git/patches/patch_spec.rb
+++ b/spec/lib/gitlab/git/patches/patch_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe Gitlab::Git::Patches::Patch do
+RSpec.describe Gitlab::Git::Patches::Patch do
let(:patches_folder) { Rails.root.join('spec/fixtures/patchfiles') }
let(:patch_content) do
File.read(File.join(patches_folder, "0001-This-does-not-apply-to-the-feature-branch.patch"))
diff --git a/spec/lib/gitlab/git/pre_receive_error_spec.rb b/spec/lib/gitlab/git/pre_receive_error_spec.rb
index cb539261671..bf4530c8945 100644
--- a/spec/lib/gitlab/git/pre_receive_error_spec.rb
+++ b/spec/lib/gitlab/git/pre_receive_error_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Git::PreReceiveError do
+RSpec.describe Gitlab::Git::PreReceiveError do
Gitlab::Git::PreReceiveError::SAFE_MESSAGE_PREFIXES.each do |prefix|
context "error messages prefixed with #{prefix}" do
it 'accepts only errors lines with the prefix' do
diff --git a/spec/lib/gitlab/git/push_spec.rb b/spec/lib/gitlab/git/push_spec.rb
index c09e8a085df..0f52f10c0a6 100644
--- a/spec/lib/gitlab/git/push_spec.rb
+++ b/spec/lib/gitlab/git/push_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Git::Push do
+RSpec.describe Gitlab::Git::Push do
let_it_be(:project) { create(:project, :repository) }
let(:oldrev) { project.commit('HEAD~2').id }
let(:newrev) { project.commit.id }
diff --git a/spec/lib/gitlab/git/raw_diff_change_spec.rb b/spec/lib/gitlab/git/raw_diff_change_spec.rb
index 79b2fc21011..f894ae1d98b 100644
--- a/spec/lib/gitlab/git/raw_diff_change_spec.rb
+++ b/spec/lib/gitlab/git/raw_diff_change_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Git::RawDiffChange do
+RSpec.describe Gitlab::Git::RawDiffChange do
let(:raw_change) { }
let(:change) { described_class.new(raw_change) }
diff --git a/spec/lib/gitlab/git/remote_mirror_spec.rb b/spec/lib/gitlab/git/remote_mirror_spec.rb
index edef91b8bc6..423c4aa9620 100644
--- a/spec/lib/gitlab/git/remote_mirror_spec.rb
+++ b/spec/lib/gitlab/git/remote_mirror_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Git::RemoteMirror do
+RSpec.describe Gitlab::Git::RemoteMirror do
describe '#update' do
let(:project) { create(:project, :repository) }
let(:repository) { project.repository }
diff --git a/spec/lib/gitlab/git/remote_repository_spec.rb b/spec/lib/gitlab/git/remote_repository_spec.rb
index b53eee293f0..84c17234ae4 100644
--- a/spec/lib/gitlab/git/remote_repository_spec.rb
+++ b/spec/lib/gitlab/git/remote_repository_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Git::RemoteRepository, :seed_helper do
+RSpec.describe Gitlab::Git::RemoteRepository, :seed_helper do
let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH, '', 'group/project') }
subject { described_class.new(repository) }
diff --git a/spec/lib/gitlab/git/repository_cleaner_spec.rb b/spec/lib/gitlab/git/repository_cleaner_spec.rb
index b387d1033d3..9f1bf9e48ee 100644
--- a/spec/lib/gitlab/git/repository_cleaner_spec.rb
+++ b/spec/lib/gitlab/git/repository_cleaner_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Git::RepositoryCleaner do
+RSpec.describe Gitlab::Git::RepositoryCleaner do
include HttpIOHelpers
let(:project) { create(:project, :repository) }
diff --git a/spec/lib/gitlab/git/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb
index 183e6e8d044..e7f4573c95f 100644
--- a/spec/lib/gitlab/git/repository_spec.rb
+++ b/spec/lib/gitlab/git/repository_spec.rb
@@ -2,7 +2,7 @@
require "spec_helper"
-describe Gitlab::Git::Repository, :seed_helper do
+RSpec.describe Gitlab::Git::Repository, :seed_helper do
include Gitlab::EncodingHelper
include RepoHelpers
using RSpec::Parameterized::TableSyntax
@@ -2187,34 +2187,47 @@ describe Gitlab::Git::Repository, :seed_helper do
'gitaly_address' => Gitlab.config.repositories.storages.default.gitaly_address,
'path' => TestEnv::SECOND_STORAGE_PATH
})
- new_repository.create_repository
end
after do
new_repository.remove
end
- it 'mirrors the source repository' do
- subject
+ context 'destination does not exist' do
+ it 'mirrors the source repository' do
+ subject
- expect(refs(new_repository_path)).to eq(refs(repository_path))
+ expect(refs(new_repository_path)).to eq(refs(repository_path))
+ end
end
- context 'with keep-around refs' do
- let(:sha) { SeedRepo::Commit::ID }
- let(:keep_around_ref) { "refs/keep-around/#{sha}" }
- let(:tmp_ref) { "refs/tmp/#{SecureRandom.hex}" }
-
+ context 'destination exists' do
before do
- repository.write_ref(keep_around_ref, sha)
- repository.write_ref(tmp_ref, sha)
+ new_repository.create_repository
end
- it 'includes the temporary and keep-around refs' do
+ it 'mirrors the source repository' do
subject
- expect(refs(new_repository_path)).to include(keep_around_ref)
- expect(refs(new_repository_path)).to include(tmp_ref)
+ expect(refs(new_repository_path)).to eq(refs(repository_path))
+ end
+
+ context 'with keep-around refs' do
+ let(:sha) { SeedRepo::Commit::ID }
+ let(:keep_around_ref) { "refs/keep-around/#{sha}" }
+ let(:tmp_ref) { "refs/tmp/#{SecureRandom.hex}" }
+
+ before do
+ repository.write_ref(keep_around_ref, sha)
+ repository.write_ref(tmp_ref, sha)
+ end
+
+ it 'includes the temporary and keep-around refs' do
+ subject
+
+ expect(refs(new_repository_path)).to include(keep_around_ref)
+ expect(refs(new_repository_path)).to include(tmp_ref)
+ end
end
end
end
diff --git a/spec/lib/gitlab/git/rugged_impl/use_rugged_spec.rb b/spec/lib/gitlab/git/rugged_impl/use_rugged_spec.rb
index 8339006fe9f..4f6a3fb823e 100644
--- a/spec/lib/gitlab/git/rugged_impl/use_rugged_spec.rb
+++ b/spec/lib/gitlab/git/rugged_impl/use_rugged_spec.rb
@@ -4,7 +4,7 @@ require 'spec_helper'
require 'json'
require 'tempfile'
-describe Gitlab::Git::RuggedImpl::UseRugged, :seed_helper do
+RSpec.describe Gitlab::Git::RuggedImpl::UseRugged, :seed_helper do
let(:project) { create(:project, :repository) }
let(:repository) { project.repository }
let(:feature_flag_name) { 'feature-flag-name' }
diff --git a/spec/lib/gitlab/git/tag_spec.rb b/spec/lib/gitlab/git/tag_spec.rb
index 6d3b239c38f..f83ccc6cae0 100644
--- a/spec/lib/gitlab/git/tag_spec.rb
+++ b/spec/lib/gitlab/git/tag_spec.rb
@@ -2,7 +2,7 @@
require "spec_helper"
-describe Gitlab::Git::Tag, :seed_helper do
+RSpec.describe Gitlab::Git::Tag, :seed_helper do
let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH, '', 'group/project') }
describe '#tags' do
diff --git a/spec/lib/gitlab/git/tree_spec.rb b/spec/lib/gitlab/git/tree_spec.rb
index b254dd3f036..5ef964ac3c1 100644
--- a/spec/lib/gitlab/git/tree_spec.rb
+++ b/spec/lib/gitlab/git/tree_spec.rb
@@ -2,7 +2,7 @@
require "spec_helper"
-describe Gitlab::Git::Tree, :seed_helper do
+RSpec.describe Gitlab::Git::Tree, :seed_helper do
let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH, '', 'group/project') }
shared_examples :repo do
diff --git a/spec/lib/gitlab/git/user_spec.rb b/spec/lib/gitlab/git/user_spec.rb
index 6761413320a..4414195ebf4 100644
--- a/spec/lib/gitlab/git/user_spec.rb
+++ b/spec/lib/gitlab/git/user_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Git::User do
+RSpec.describe Gitlab::Git::User do
let(:username) { 'janedoe' }
let(:name) { 'Jane Doé' }
let(:email) { 'janedoé@example.com' }
diff --git a/spec/lib/gitlab/git/util_spec.rb b/spec/lib/gitlab/git/util_spec.rb
index 81918f036f9..a0237c821b5 100644
--- a/spec/lib/gitlab/git/util_spec.rb
+++ b/spec/lib/gitlab/git/util_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Git::Util do
+RSpec.describe Gitlab::Git::Util do
describe '#count_lines' do
[
["", 0],
diff --git a/spec/lib/gitlab/git/wiki_spec.rb b/spec/lib/gitlab/git/wiki_spec.rb
index 8bae2e8125e..a88097705f6 100644
--- a/spec/lib/gitlab/git/wiki_spec.rb
+++ b/spec/lib/gitlab/git/wiki_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Git::Wiki do
+RSpec.describe Gitlab::Git::Wiki do
using RSpec::Parameterized::TableSyntax
let(:project) { create(:project) }
diff --git a/spec/lib/gitlab/git/wraps_gitaly_errors_spec.rb b/spec/lib/gitlab/git/wraps_gitaly_errors_spec.rb
index a4489cca443..e448277b307 100644
--- a/spec/lib/gitlab/git/wraps_gitaly_errors_spec.rb
+++ b/spec/lib/gitlab/git/wraps_gitaly_errors_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Git::WrapsGitalyErrors do
+RSpec.describe Gitlab::Git::WrapsGitalyErrors do
subject(:wrapper) do
klazz = Class.new { include Gitlab::Git::WrapsGitalyErrors }
klazz.new
diff --git a/spec/lib/gitlab/git_access_design_spec.rb b/spec/lib/gitlab/git_access_design_spec.rb
index d816608f7e5..ee25f6c2979 100644
--- a/spec/lib/gitlab/git_access_design_spec.rb
+++ b/spec/lib/gitlab/git_access_design_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe Gitlab::GitAccessDesign do
+RSpec.describe Gitlab::GitAccessDesign do
include DesignManagementTestHelpers
let_it_be(:project) { create(:project) }
diff --git a/spec/lib/gitlab/git_access_project_spec.rb b/spec/lib/gitlab/git_access_project_spec.rb
index f7f7976ccb8..520300363c9 100644
--- a/spec/lib/gitlab/git_access_project_spec.rb
+++ b/spec/lib/gitlab/git_access_project_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GitAccessProject do
+RSpec.describe Gitlab::GitAccessProject do
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project, :repository) }
let(:actor) { user }
diff --git a/spec/lib/gitlab/git_access_snippet_spec.rb b/spec/lib/gitlab/git_access_snippet_spec.rb
index 48b425a8ec5..3b8b5fd82c6 100644
--- a/spec/lib/gitlab/git_access_snippet_spec.rb
+++ b/spec/lib/gitlab/git_access_snippet_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GitAccessSnippet do
+RSpec.describe Gitlab::GitAccessSnippet do
include ProjectHelpers
include TermsHelper
include_context 'ProjectPolicyTable context'
diff --git a/spec/lib/gitlab/git_access_spec.rb b/spec/lib/gitlab/git_access_spec.rb
index 7c09fc5cc79..01691f87092 100644
--- a/spec/lib/gitlab/git_access_spec.rb
+++ b/spec/lib/gitlab/git_access_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GitAccess do
+RSpec.describe Gitlab::GitAccess do
include TermsHelper
include GitHelpers
diff --git a/spec/lib/gitlab/git_access_wiki_spec.rb b/spec/lib/gitlab/git_access_wiki_spec.rb
index e42570804a8..738269e4a14 100644
--- a/spec/lib/gitlab/git_access_wiki_spec.rb
+++ b/spec/lib/gitlab/git_access_wiki_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GitAccessWiki do
+RSpec.describe Gitlab::GitAccessWiki do
let(:access) { described_class.new(user, project, 'web', authentication_abilities: authentication_abilities, redirected_path: redirected_path) }
let(:project) { create(:project, :wiki_repo) }
let(:user) { create(:user) }
diff --git a/spec/lib/gitlab/git_post_receive_spec.rb b/spec/lib/gitlab/git_post_receive_spec.rb
index 0e25a616810..45d536a437f 100644
--- a/spec/lib/gitlab/git_post_receive_spec.rb
+++ b/spec/lib/gitlab/git_post_receive_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ::Gitlab::GitPostReceive do
+RSpec.describe ::Gitlab::GitPostReceive do
let_it_be(:project) { create(:project, :repository) }
subject { described_class.new(project, "project-#{project.id}", changes.dup, {}) }
diff --git a/spec/lib/gitlab/git_ref_validator_spec.rb b/spec/lib/gitlab/git_ref_validator_spec.rb
index 28cc13f02de..6938ad51189 100644
--- a/spec/lib/gitlab/git_ref_validator_spec.rb
+++ b/spec/lib/gitlab/git_ref_validator_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GitRefValidator do
+RSpec.describe Gitlab::GitRefValidator do
using RSpec::Parameterized::TableSyntax
describe '.validate' do
diff --git a/spec/lib/gitlab/git_spec.rb b/spec/lib/gitlab/git_spec.rb
index d6d12b84724..784d25f55c1 100644
--- a/spec/lib/gitlab/git_spec.rb
+++ b/spec/lib/gitlab/git_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Git do
+RSpec.describe Gitlab::Git do
let(:committer_email) { 'user@example.org' }
let(:committer_name) { 'John Doe' }
diff --git a/spec/lib/gitlab/gitaly_client/blob_service_spec.rb b/spec/lib/gitlab/gitaly_client/blob_service_spec.rb
index e609acc8fb0..037734f1b13 100644
--- a/spec/lib/gitlab/gitaly_client/blob_service_spec.rb
+++ b/spec/lib/gitlab/gitaly_client/blob_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GitalyClient::BlobService do
+RSpec.describe Gitlab::GitalyClient::BlobService do
let(:project) { create(:project, :repository) }
let(:storage_name) { project.repository_storage }
let(:relative_path) { project.disk_path + '.git' }
diff --git a/spec/lib/gitlab/gitaly_client/blobs_stitcher_spec.rb b/spec/lib/gitlab/gitaly_client/blobs_stitcher_spec.rb
index e88b86c71f2..e0c3e8d4b40 100644
--- a/spec/lib/gitlab/gitaly_client/blobs_stitcher_spec.rb
+++ b/spec/lib/gitlab/gitaly_client/blobs_stitcher_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GitalyClient::BlobsStitcher do
+RSpec.describe Gitlab::GitalyClient::BlobsStitcher do
describe 'enumeration' do
it 'combines segregated blob messages together' do
messages = [
diff --git a/spec/lib/gitlab/gitaly_client/call_spec.rb b/spec/lib/gitlab/gitaly_client/call_spec.rb
new file mode 100644
index 00000000000..5c33ac40460
--- /dev/null
+++ b/spec/lib/gitlab/gitaly_client/call_spec.rb
@@ -0,0 +1,122 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::GitalyClient::Call do
+ describe '#call', :request_store do
+ let(:client) { Gitlab::GitalyClient }
+ let(:storage) { 'default' }
+ let(:remote_storage) { nil }
+ let(:request) { Gitaly::FindLocalBranchesRequest.new }
+ let(:rpc) { :find_local_branches }
+ let(:service) { :ref_service }
+ let(:timeout) { client.long_timeout }
+
+ subject do
+ described_class.new(storage, service, rpc, request, remote_storage, timeout).call
+ end
+
+ before do
+ allow(client).to receive(:execute) { response }
+ allow(Gitlab::PerformanceBar).to receive(:enabled_for_request?) { true }
+ end
+
+ def expect_call_details_to_match(duration_higher_than: 0)
+ expect(client.list_call_details.size).to eq(1)
+ expect(client.list_call_details.first)
+ .to match a_hash_including(feature: "#{service}##{rpc}",
+ duration: a_value > duration_higher_than,
+ request: an_instance_of(Hash),
+ rpc: rpc,
+ backtrace: an_instance_of(Array))
+ end
+
+ context 'when the response is not an enumerator' do
+ let(:response) do
+ Gitaly::FindLocalBranchesResponse.new
+ end
+
+ it 'returns the response' do
+ expect(subject).to eq(response)
+ end
+
+ it 'stores timings and call details' do
+ subject
+
+ expect(client.query_time).to be > 0
+ expect_call_details_to_match
+ end
+
+ context 'when err' do
+ before do
+ allow(client).to receive(:execute).and_raise(StandardError)
+ end
+
+ it 'stores timings and call details' do
+ expect { subject }.to raise_error(StandardError)
+
+ expect(client.query_time).to be > 0
+ expect_call_details_to_match
+ end
+ end
+ end
+
+ context 'when the response is an enumerator' do
+ let(:response) do
+ Enumerator.new do |yielder|
+ yielder << 1
+ yielder << 2
+ end
+ end
+
+ it 'returns a consumable enumerator' do
+ instrumented_response = subject
+
+ expect(instrumented_response).to be_a(Enumerator)
+ expect(instrumented_response.to_a).to eq([1, 2])
+ end
+
+ context 'time measurements' do
+ let(:response) do
+ Enumerator.new do |yielder|
+ sleep 0.1
+ yielder << 1
+ sleep 0.2
+ yielder << 2
+ end
+ end
+
+ it 'records full rpc stream consumption' do
+ subject.to_a
+
+ expect(client.query_time).to be > 0.3
+ expect_call_details_to_match(duration_higher_than: 0.3)
+ end
+
+ it 'records partial rpc stream consumption' do
+ subject.first
+
+ expect(client.query_time).to be > 0.1
+ expect_call_details_to_match(duration_higher_than: 0.1)
+ end
+
+ context 'when err' do
+ let(:response) do
+ Enumerator.new do |yielder|
+ sleep 0.2
+ yielder << 1
+ raise StandardError
+ end
+ end
+
+ it 'records partial rpc stream consumption' do
+ expect { subject.to_a }.to raise_error(StandardError)
+
+ expect(client.query_time).to be > 0.2
+ expect_call_details_to_match(duration_higher_than: 0.2)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/gitaly_client/cleanup_service_spec.rb b/spec/lib/gitlab/gitaly_client/cleanup_service_spec.rb
index 07f53797b2a..3381c69ea0d 100644
--- a/spec/lib/gitlab/gitaly_client/cleanup_service_spec.rb
+++ b/spec/lib/gitlab/gitaly_client/cleanup_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GitalyClient::CleanupService do
+RSpec.describe Gitlab::GitalyClient::CleanupService do
let(:project) { create(:project) }
let(:storage_name) { project.repository_storage }
let(:relative_path) { project.disk_path + '.git' }
diff --git a/spec/lib/gitlab/gitaly_client/commit_service_spec.rb b/spec/lib/gitlab/gitaly_client/commit_service_spec.rb
index 29a5ef0d2fc..45a25ccfc88 100644
--- a/spec/lib/gitlab/gitaly_client/commit_service_spec.rb
+++ b/spec/lib/gitlab/gitaly_client/commit_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GitalyClient::CommitService do
+RSpec.describe Gitlab::GitalyClient::CommitService do
let(:project) { create(:project, :repository) }
let(:storage_name) { project.repository_storage }
let(:relative_path) { project.disk_path + '.git' }
@@ -290,7 +290,8 @@ describe Gitlab::GitalyClient::CommitService do
request = Gitaly::FindCommitsRequest.new(
repository: repository_message,
disable_walk: true,
- order: 'NONE'
+ order: 'NONE',
+ global_options: Gitaly::GlobalOptions.new(literal_pathspecs: false)
)
expect_any_instance_of(Gitaly::CommitService::Stub).to receive(:find_commits)
@@ -303,7 +304,8 @@ describe Gitlab::GitalyClient::CommitService do
request = Gitaly::FindCommitsRequest.new(
repository: repository_message,
disable_walk: true,
- order: 'TOPO'
+ order: 'TOPO',
+ global_options: Gitaly::GlobalOptions.new(literal_pathspecs: false)
)
expect_any_instance_of(Gitaly::CommitService::Stub).to receive(:find_commits)
@@ -317,7 +319,8 @@ describe Gitlab::GitalyClient::CommitService do
repository: repository_message,
disable_walk: true,
order: 'NONE',
- author: "Billy Baggins <bilbo@shire.com>"
+ author: "Billy Baggins <bilbo@shire.com>",
+ global_options: Gitaly::GlobalOptions.new(literal_pathspecs: false)
)
expect_any_instance_of(Gitaly::CommitService::Stub).to receive(:find_commits)
@@ -338,7 +341,8 @@ describe Gitlab::GitalyClient::CommitService do
revision: (options[:revision] || '').dup.force_encoding(Encoding::ASCII_8BIT),
path: (options[:path] || '').dup.force_encoding(Encoding::ASCII_8BIT),
limit: (options[:limit] || 1000).to_i,
- offset: (options[:offset] || 0).to_i
+ offset: (options[:offset] || 0).to_i,
+ global_options: Gitaly::GlobalOptions.new(literal_pathspecs: true)
)
allow_any_instance_of(Gitaly::CommitService::Stub)
@@ -381,4 +385,19 @@ describe Gitlab::GitalyClient::CommitService do
commits.map { |commit| Gitlab::Git::Commit.new(repository, commit) }
end
end
+
+ describe '#list_commits_by_ref_name' do
+ let(:project) { create(:project, :repository, create_branch: 'ü/unicode/multi-byte') }
+
+ it 'lists latest commits grouped by a ref name' do
+ response = client.list_commits_by_ref_name(%w[master feature v1.0.0 nonexistent ü/unicode/multi-byte])
+
+ expect(response.keys.count).to eq 4
+ expect(response.fetch('master').id).to eq 'b83d6e391c22777fca1ed3012fce84f633d7fed0'
+ expect(response.fetch('feature').id).to eq '0b4bc9a49b562e85de7cc9e834518ea6828729b9'
+ expect(response.fetch('v1.0.0').id).to eq '6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9'
+ expect(response.fetch('ü/unicode/multi-byte')).to be_present
+ expect(response).not_to have_key 'nonexistent'
+ end
+ end
end
diff --git a/spec/lib/gitlab/gitaly_client/conflict_files_stitcher_spec.rb b/spec/lib/gitlab/gitaly_client/conflict_files_stitcher_spec.rb
index db734b1c129..0bb8628af6c 100644
--- a/spec/lib/gitlab/gitaly_client/conflict_files_stitcher_spec.rb
+++ b/spec/lib/gitlab/gitaly_client/conflict_files_stitcher_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GitalyClient::ConflictFilesStitcher do
+RSpec.describe Gitlab::GitalyClient::ConflictFilesStitcher do
describe 'enumeration' do
it 'combines segregated ConflictFile messages together' do
target_project = create(:project, :repository)
diff --git a/spec/lib/gitlab/gitaly_client/conflicts_service_spec.rb b/spec/lib/gitlab/gitaly_client/conflicts_service_spec.rb
index f19bcae2470..b016e8bdf5a 100644
--- a/spec/lib/gitlab/gitaly_client/conflicts_service_spec.rb
+++ b/spec/lib/gitlab/gitaly_client/conflicts_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GitalyClient::ConflictsService do
+RSpec.describe Gitlab::GitalyClient::ConflictsService do
let(:project) { create(:project, :repository) }
let(:target_project) { create(:project, :repository) }
let(:source_repository) { project.repository.raw }
diff --git a/spec/lib/gitlab/gitaly_client/diff_spec.rb b/spec/lib/gitlab/gitaly_client/diff_spec.rb
index d86497da7f5..230322faecd 100644
--- a/spec/lib/gitlab/gitaly_client/diff_spec.rb
+++ b/spec/lib/gitlab/gitaly_client/diff_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GitalyClient::Diff do
+RSpec.describe Gitlab::GitalyClient::Diff do
let(:diff_fields) do
{
to_path: ".gitmodules",
diff --git a/spec/lib/gitlab/gitaly_client/diff_stitcher_spec.rb b/spec/lib/gitlab/gitaly_client/diff_stitcher_spec.rb
index c9d42ad32cf..113c47b4f2c 100644
--- a/spec/lib/gitlab/gitaly_client/diff_stitcher_spec.rb
+++ b/spec/lib/gitlab/gitaly_client/diff_stitcher_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GitalyClient::DiffStitcher do
+RSpec.describe Gitlab::GitalyClient::DiffStitcher do
describe 'enumeration' do
it 'combines segregated diff messages together' do
diff_1 = OpenStruct.new(
diff --git a/spec/lib/gitlab/gitaly_client/health_check_service_spec.rb b/spec/lib/gitlab/gitaly_client/health_check_service_spec.rb
index 615bc80fff2..0855544d11e 100644
--- a/spec/lib/gitlab/gitaly_client/health_check_service_spec.rb
+++ b/spec/lib/gitlab/gitaly_client/health_check_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GitalyClient::HealthCheckService do
+RSpec.describe Gitlab::GitalyClient::HealthCheckService do
let(:project) { create(:project) }
let(:storage_name) { project.repository_storage }
diff --git a/spec/lib/gitlab/gitaly_client/object_pool_service_spec.rb b/spec/lib/gitlab/gitaly_client/object_pool_service_spec.rb
index 0e0c3d329b5..15eebf62a39 100644
--- a/spec/lib/gitlab/gitaly_client/object_pool_service_spec.rb
+++ b/spec/lib/gitlab/gitaly_client/object_pool_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GitalyClient::ObjectPoolService do
+RSpec.describe Gitlab::GitalyClient::ObjectPoolService do
let(:pool_repository) { create(:pool_repository) }
let(:project) { create(:project, :repository) }
let(:raw_repository) { project.repository.raw }
diff --git a/spec/lib/gitlab/gitaly_client/operation_service_spec.rb b/spec/lib/gitlab/gitaly_client/operation_service_spec.rb
index 45701b501bb..4e16f760235 100644
--- a/spec/lib/gitlab/gitaly_client/operation_service_spec.rb
+++ b/spec/lib/gitlab/gitaly_client/operation_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GitalyClient::OperationService do
+RSpec.describe Gitlab::GitalyClient::OperationService do
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project, :repository) }
let(:repository) { project.repository.raw }
@@ -191,6 +191,20 @@ describe Gitlab::GitalyClient::OperationService do
it { expect(subject).to be_nil }
end
+
+ context "when the pre-receive hook fails" do
+ let(:response) do
+ Gitaly::UserFFBranchResponse.new(
+ branch_update: nil,
+ pre_receive_error: "pre-receive hook error message\n"
+ )
+ end
+
+ it "raises the error" do
+ # the PreReceiveError class strips the GL-HOOK-ERR prefix from this error
+ expect { subject }.to raise_error(Gitlab::Git::PreReceiveError, "pre-receive hook failed.")
+ end
+ end
end
shared_examples 'cherry pick and revert errors' do
diff --git a/spec/lib/gitlab/gitaly_client/praefect_info_service_spec.rb b/spec/lib/gitlab/gitaly_client/praefect_info_service_spec.rb
index 9b5c751e0ec..5f90745f4e0 100644
--- a/spec/lib/gitlab/gitaly_client/praefect_info_service_spec.rb
+++ b/spec/lib/gitlab/gitaly_client/praefect_info_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GitalyClient::PraefectInfoService do
+RSpec.describe Gitlab::GitalyClient::PraefectInfoService do
let(:project) { create(:project, :repository) }
let(:repository) { project.repository }
let(:gitaly_repository) { repository.gitaly_repository }
diff --git a/spec/lib/gitlab/gitaly_client/ref_service_spec.rb b/spec/lib/gitlab/gitaly_client/ref_service_spec.rb
index d4a7f6e6df9..a4c6e30bba8 100644
--- a/spec/lib/gitlab/gitaly_client/ref_service_spec.rb
+++ b/spec/lib/gitlab/gitaly_client/ref_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GitalyClient::RefService do
+RSpec.describe Gitlab::GitalyClient::RefService do
let(:project) { create(:project, :repository) }
let(:storage_name) { project.repository_storage }
let(:relative_path) { project.disk_path + '.git' }
@@ -34,7 +34,7 @@ describe Gitlab::GitalyClient::RefService do
subject
end
- it 'concantes and returns the response branches as Gitlab::Git::Branch objects' do
+ it 'concatenates and returns the response branches as Gitlab::Git::Branch objects' do
target_commits = create_list(:gitaly_commit, 4)
response_branches = target_commits.each_with_index.map do |gitaly_commit, i|
Gitaly::Branch.new(name: "#{remote_name}/#{i}", target_commit: gitaly_commit)
@@ -59,6 +59,17 @@ describe Gitlab::GitalyClient::RefService do
end
end
+ describe '#merged_branches' do
+ it 'sends a find_all_branches message' do
+ expect_any_instance_of(Gitaly::RefService::Stub)
+ .to receive(:find_all_branches)
+ .with(gitaly_request_with_params(merged_only: true, merged_branches: ['test']), kind_of(Hash))
+ .and_return([])
+
+ client.merged_branches(%w(test))
+ end
+ end
+
describe '#branch_names' do
it 'sends a find_all_branch_names message' do
expect_any_instance_of(Gitaly::RefService::Stub)
@@ -135,6 +146,38 @@ describe Gitlab::GitalyClient::RefService do
end
end
+ describe '#tags' do
+ it 'sends a find_all_tags message' do
+ expect_any_instance_of(Gitaly::RefService::Stub)
+ .to receive(:find_all_tags)
+ .and_return([])
+
+ client.tags
+ end
+ end
+
+ describe '#branch_names_contains_sha' do
+ it 'sends a list_branch_names_containing_commit message' do
+ expect_any_instance_of(Gitaly::RefService::Stub)
+ .to receive(:list_branch_names_containing_commit)
+ .with(gitaly_request_with_params(commit_id: '123', limit: 0), kind_of(Hash))
+ .and_return([])
+
+ client.branch_names_contains_sha('123')
+ end
+ end
+
+ describe '#get_tag_messages' do
+ it 'sends a get_tag_messages message' do
+ expect_any_instance_of(Gitaly::RefService::Stub)
+ .to receive(:get_tag_messages)
+ .with(gitaly_request_with_params(tag_ids: ['some_tag_id']), kind_of(Hash))
+ .and_return([])
+
+ client.get_tag_messages(['some_tag_id'])
+ end
+ end
+
describe '#find_ref_name', :seed_helper do
subject { client.find_ref_name(SeedRepo::Commit::ID, 'refs/heads/master') }
diff --git a/spec/lib/gitlab/gitaly_client/remote_service_spec.rb b/spec/lib/gitlab/gitaly_client/remote_service_spec.rb
index 2bddec739fc..b9ef76e1f41 100644
--- a/spec/lib/gitlab/gitaly_client/remote_service_spec.rb
+++ b/spec/lib/gitlab/gitaly_client/remote_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GitalyClient::RemoteService do
+RSpec.describe Gitlab::GitalyClient::RemoteService do
let(:project) { create(:project) }
let(:storage_name) { project.repository_storage }
let(:relative_path) { project.disk_path + '.git' }
@@ -73,8 +73,11 @@ describe Gitlab::GitalyClient::RemoteService do
describe '.exists?' do
context "when the remote doesn't exist" do
let(:url) { 'https://gitlab.com/gitlab-org/ik-besta-niet-of-ik-word-geplaagd.git' }
+ let(:storage_name) { 'default' }
it 'returns false' do
+ expect(Gitaly::FindRemoteRepositoryRequest).to receive(:new).with(remote: url, storage_name: storage_name).and_call_original
+
expect(described_class.exists?(url)).to be(false)
end
end
diff --git a/spec/lib/gitlab/gitaly_client/repository_service_spec.rb b/spec/lib/gitlab/gitaly_client/repository_service_spec.rb
index 5f4147f6ff6..c7ea0a95596 100644
--- a/spec/lib/gitlab/gitaly_client/repository_service_spec.rb
+++ b/spec/lib/gitlab/gitaly_client/repository_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GitalyClient::RepositoryService do
+RSpec.describe Gitlab::GitalyClient::RepositoryService do
using RSpec::Parameterized::TableSyntax
let(:project) { create(:project) }
diff --git a/spec/lib/gitlab/gitaly_client/storage_settings_spec.rb b/spec/lib/gitlab/gitaly_client/storage_settings_spec.rb
index a6b29489df3..0c4c8de52ae 100644
--- a/spec/lib/gitlab/gitaly_client/storage_settings_spec.rb
+++ b/spec/lib/gitlab/gitaly_client/storage_settings_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GitalyClient::StorageSettings do
+RSpec.describe Gitlab::GitalyClient::StorageSettings do
describe "#initialize" do
context 'when the storage contains no path' do
it 'raises an error' do
diff --git a/spec/lib/gitlab/gitaly_client/util_spec.rb b/spec/lib/gitlab/gitaly_client/util_spec.rb
index f31b7c349ff..b6589a08f7d 100644
--- a/spec/lib/gitlab/gitaly_client/util_spec.rb
+++ b/spec/lib/gitlab/gitaly_client/util_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GitalyClient::Util do
+RSpec.describe Gitlab::GitalyClient::Util do
describe '.repository' do
let(:repository_storage) { 'default' }
let(:relative_path) { 'my/repo.git' }
diff --git a/spec/lib/gitlab/gitaly_client/wiki_service_spec.rb b/spec/lib/gitlab/gitaly_client/wiki_service_spec.rb
index cb04f9a1637..a06f8459963 100644
--- a/spec/lib/gitlab/gitaly_client/wiki_service_spec.rb
+++ b/spec/lib/gitlab/gitaly_client/wiki_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GitalyClient::WikiService do
+RSpec.describe Gitlab::GitalyClient::WikiService do
let(:project) { create(:project) }
let(:storage_name) { project.repository_storage }
let(:relative_path) { project.disk_path + '.git' }
diff --git a/spec/lib/gitlab/gitaly_client_spec.rb b/spec/lib/gitlab/gitaly_client_spec.rb
index c2b989c2fdc..16dd2bbee6d 100644
--- a/spec/lib/gitlab/gitaly_client_spec.rb
+++ b/spec/lib/gitlab/gitaly_client_spec.rb
@@ -4,7 +4,7 @@ require 'spec_helper'
# We stub Gitaly in `spec/support/gitaly.rb` for other tests. We don't want
# those stubs while testing the GitalyClient itself.
-describe Gitlab::GitalyClient do
+RSpec.describe Gitlab::GitalyClient do
let(:sample_cert) { Rails.root.join('spec/fixtures/clusters/sample_cert.pem').to_s }
before do
@@ -521,8 +521,6 @@ describe Gitlab::GitalyClient do
context 'when the request store is active', :request_store do
it 'records call details if a RPC is called' do
- expect(described_class).to receive(:measure_timings).and_call_original
-
gitaly_server.server_version
expect(described_class.list_call_details).not_to be_empty
diff --git a/spec/lib/gitlab/github_import/bulk_importing_spec.rb b/spec/lib/gitlab/github_import/bulk_importing_spec.rb
index 3266ec4ab50..63dce51c5da 100644
--- a/spec/lib/gitlab/github_import/bulk_importing_spec.rb
+++ b/spec/lib/gitlab/github_import/bulk_importing_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GithubImport::BulkImporting do
+RSpec.describe Gitlab::GithubImport::BulkImporting do
let(:importer) do
Class.new { include(Gitlab::GithubImport::BulkImporting) }.new
end
diff --git a/spec/lib/gitlab/github_import/client_spec.rb b/spec/lib/gitlab/github_import/client_spec.rb
index 3b269d64b07..7cff6ed1388 100644
--- a/spec/lib/gitlab/github_import/client_spec.rb
+++ b/spec/lib/gitlab/github_import/client_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GithubImport::Client do
+RSpec.describe Gitlab::GithubImport::Client do
describe '#parallel?' do
it 'returns true when the client is running in parallel mode' do
client = described_class.new('foo', parallel: true)
diff --git a/spec/lib/gitlab/github_import/importer/diff_note_importer_spec.rb b/spec/lib/gitlab/github_import/importer/diff_note_importer_spec.rb
index 66909976b43..9eea85526f5 100644
--- a/spec/lib/gitlab/github_import/importer/diff_note_importer_spec.rb
+++ b/spec/lib/gitlab/github_import/importer/diff_note_importer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GithubImport::Importer::DiffNoteImporter do
+RSpec.describe Gitlab::GithubImport::Importer::DiffNoteImporter do
let(:project) { create(:project) }
let(:client) { double(:client) }
let(:user) { create(:user) }
diff --git a/spec/lib/gitlab/github_import/importer/diff_notes_importer_spec.rb b/spec/lib/gitlab/github_import/importer/diff_notes_importer_spec.rb
index 23ed21294e3..7750e508713 100644
--- a/spec/lib/gitlab/github_import/importer/diff_notes_importer_spec.rb
+++ b/spec/lib/gitlab/github_import/importer/diff_notes_importer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GithubImport::Importer::DiffNotesImporter do
+RSpec.describe Gitlab::GithubImport::Importer::DiffNotesImporter do
let(:project) { double(:project, id: 4, import_source: 'foo/bar') }
let(:client) { double(:client) }
diff --git a/spec/lib/gitlab/github_import/importer/issue_and_label_links_importer_spec.rb b/spec/lib/gitlab/github_import/importer/issue_and_label_links_importer_spec.rb
index 399e2d9a563..49a76fb5e6b 100644
--- a/spec/lib/gitlab/github_import/importer/issue_and_label_links_importer_spec.rb
+++ b/spec/lib/gitlab/github_import/importer/issue_and_label_links_importer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GithubImport::Importer::IssueAndLabelLinksImporter do
+RSpec.describe Gitlab::GithubImport::Importer::IssueAndLabelLinksImporter do
describe '#execute' do
it 'imports an issue and its labels' do
issue = double(:issue)
diff --git a/spec/lib/gitlab/github_import/importer/issue_importer_spec.rb b/spec/lib/gitlab/github_import/importer/issue_importer_spec.rb
index 0f2ba99f816..fb826c987e1 100644
--- a/spec/lib/gitlab/github_import/importer/issue_importer_spec.rb
+++ b/spec/lib/gitlab/github_import/importer/issue_importer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GithubImport::Importer::IssueImporter, :clean_gitlab_redis_cache do
+RSpec.describe Gitlab::GithubImport::Importer::IssueImporter, :clean_gitlab_redis_cache do
let(:project) { create(:project) }
let(:client) { double(:client) }
let(:user) { create(:user) }
diff --git a/spec/lib/gitlab/github_import/importer/issues_importer_spec.rb b/spec/lib/gitlab/github_import/importer/issues_importer_spec.rb
index 8920ef9fedb..2c2b6a2aff0 100644
--- a/spec/lib/gitlab/github_import/importer/issues_importer_spec.rb
+++ b/spec/lib/gitlab/github_import/importer/issues_importer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GithubImport::Importer::IssuesImporter do
+RSpec.describe Gitlab::GithubImport::Importer::IssuesImporter do
let(:project) { double(:project, id: 4, import_source: 'foo/bar') }
let(:client) { double(:client) }
let(:created_at) { Time.new(2017, 1, 1, 12, 00) }
diff --git a/spec/lib/gitlab/github_import/importer/label_links_importer_spec.rb b/spec/lib/gitlab/github_import/importer/label_links_importer_spec.rb
index 9c02b0e280f..4d3245fc988 100644
--- a/spec/lib/gitlab/github_import/importer/label_links_importer_spec.rb
+++ b/spec/lib/gitlab/github_import/importer/label_links_importer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GithubImport::Importer::LabelLinksImporter do
+RSpec.describe Gitlab::GithubImport::Importer::LabelLinksImporter do
let(:project) { create(:project) }
let(:client) { double(:client) }
let(:issue) do
diff --git a/spec/lib/gitlab/github_import/importer/labels_importer_spec.rb b/spec/lib/gitlab/github_import/importer/labels_importer_spec.rb
index 16326da9ca4..0010b959a49 100644
--- a/spec/lib/gitlab/github_import/importer/labels_importer_spec.rb
+++ b/spec/lib/gitlab/github_import/importer/labels_importer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GithubImport::Importer::LabelsImporter, :clean_gitlab_redis_cache do
+RSpec.describe Gitlab::GithubImport::Importer::LabelsImporter, :clean_gitlab_redis_cache do
let(:project) { create(:project, import_source: 'foo/bar') }
let(:client) { double(:client) }
let(:importer) { described_class.new(project, client) }
diff --git a/spec/lib/gitlab/github_import/importer/lfs_object_importer_spec.rb b/spec/lib/gitlab/github_import/importer/lfs_object_importer_spec.rb
index a02b620f131..add554992f1 100644
--- a/spec/lib/gitlab/github_import/importer/lfs_object_importer_spec.rb
+++ b/spec/lib/gitlab/github_import/importer/lfs_object_importer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GithubImport::Importer::LfsObjectImporter do
+RSpec.describe Gitlab::GithubImport::Importer::LfsObjectImporter do
let(:project) { create(:project) }
let(:lfs_attributes) do
{
diff --git a/spec/lib/gitlab/github_import/importer/lfs_objects_importer_spec.rb b/spec/lib/gitlab/github_import/importer/lfs_objects_importer_spec.rb
index bec039a48eb..1f7b14661c2 100644
--- a/spec/lib/gitlab/github_import/importer/lfs_objects_importer_spec.rb
+++ b/spec/lib/gitlab/github_import/importer/lfs_objects_importer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GithubImport::Importer::LfsObjectsImporter do
+RSpec.describe Gitlab::GithubImport::Importer::LfsObjectsImporter do
let(:project) { double(:project, id: 4, import_source: 'foo/bar') }
let(:client) { double(:client) }
let(:download_link) { "http://www.gitlab.com/lfs_objects/oid" }
diff --git a/spec/lib/gitlab/github_import/importer/milestones_importer_spec.rb b/spec/lib/gitlab/github_import/importer/milestones_importer_spec.rb
index 294599c02f4..dad1efc5a8d 100644
--- a/spec/lib/gitlab/github_import/importer/milestones_importer_spec.rb
+++ b/spec/lib/gitlab/github_import/importer/milestones_importer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GithubImport::Importer::MilestonesImporter, :clean_gitlab_redis_cache do
+RSpec.describe Gitlab::GithubImport::Importer::MilestonesImporter, :clean_gitlab_redis_cache do
let(:project) { create(:project, import_source: 'foo/bar') }
let(:client) { double(:client) }
let(:importer) { described_class.new(project, client) }
diff --git a/spec/lib/gitlab/github_import/importer/note_importer_spec.rb b/spec/lib/gitlab/github_import/importer/note_importer_spec.rb
index 816041b771b..3bb57e152fe 100644
--- a/spec/lib/gitlab/github_import/importer/note_importer_spec.rb
+++ b/spec/lib/gitlab/github_import/importer/note_importer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GithubImport::Importer::NoteImporter do
+RSpec.describe Gitlab::GithubImport::Importer::NoteImporter do
let(:client) { double(:client) }
let(:project) { create(:project) }
let(:user) { create(:user) }
diff --git a/spec/lib/gitlab/github_import/importer/notes_importer_spec.rb b/spec/lib/gitlab/github_import/importer/notes_importer_spec.rb
index 128f8f95fa0..3782dab5ee3 100644
--- a/spec/lib/gitlab/github_import/importer/notes_importer_spec.rb
+++ b/spec/lib/gitlab/github_import/importer/notes_importer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GithubImport::Importer::NotesImporter do
+RSpec.describe Gitlab::GithubImport::Importer::NotesImporter do
let(:project) { double(:project, id: 4, import_source: 'foo/bar') }
let(:client) { double(:client) }
diff --git a/spec/lib/gitlab/github_import/importer/pull_request_importer_spec.rb b/spec/lib/gitlab/github_import/importer/pull_request_importer_spec.rb
index bffae9e2ba0..46850618945 100644
--- a/spec/lib/gitlab/github_import/importer/pull_request_importer_spec.rb
+++ b/spec/lib/gitlab/github_import/importer/pull_request_importer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GithubImport::Importer::PullRequestImporter, :clean_gitlab_redis_cache do
+RSpec.describe Gitlab::GithubImport::Importer::PullRequestImporter, :clean_gitlab_redis_cache do
let(:project) { create(:project, :repository) }
let(:client) { double(:client) }
let(:user) { create(:user) }
diff --git a/spec/lib/gitlab/github_import/importer/pull_requests_importer_spec.rb b/spec/lib/gitlab/github_import/importer/pull_requests_importer_spec.rb
index 526a5589743..05ac0248ec9 100644
--- a/spec/lib/gitlab/github_import/importer/pull_requests_importer_spec.rb
+++ b/spec/lib/gitlab/github_import/importer/pull_requests_importer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GithubImport::Importer::PullRequestsImporter do
+RSpec.describe Gitlab::GithubImport::Importer::PullRequestsImporter do
let(:project) { create(:project, import_source: 'foo/bar') }
let(:client) { double(:client) }
diff --git a/spec/lib/gitlab/github_import/importer/releases_importer_spec.rb b/spec/lib/gitlab/github_import/importer/releases_importer_spec.rb
index f8d53208619..1a25824bc8a 100644
--- a/spec/lib/gitlab/github_import/importer/releases_importer_spec.rb
+++ b/spec/lib/gitlab/github_import/importer/releases_importer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GithubImport::Importer::ReleasesImporter do
+RSpec.describe Gitlab::GithubImport::Importer::ReleasesImporter do
let(:project) { create(:project) }
let(:client) { double(:client) }
let(:importer) { described_class.new(project, client) }
diff --git a/spec/lib/gitlab/github_import/importer/repository_importer_spec.rb b/spec/lib/gitlab/github_import/importer/repository_importer_spec.rb
index e26ac7bf81e..65dba2711b9 100644
--- a/spec/lib/gitlab/github_import/importer/repository_importer_spec.rb
+++ b/spec/lib/gitlab/github_import/importer/repository_importer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GithubImport::Importer::RepositoryImporter do
+RSpec.describe Gitlab::GithubImport::Importer::RepositoryImporter do
let(:repository) { double(:repository) }
let(:import_state) { double(:import_state) }
let(:client) { double(:client) }
diff --git a/spec/lib/gitlab/github_import/issuable_finder_spec.rb b/spec/lib/gitlab/github_import/issuable_finder_spec.rb
index 55add863d43..f009b61ad89 100644
--- a/spec/lib/gitlab/github_import/issuable_finder_spec.rb
+++ b/spec/lib/gitlab/github_import/issuable_finder_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GithubImport::IssuableFinder, :clean_gitlab_redis_cache do
+RSpec.describe Gitlab::GithubImport::IssuableFinder, :clean_gitlab_redis_cache do
let(:project) { double(:project, id: 4) }
let(:issue) do
double(:issue, issuable_type: MergeRequest, iid: 1)
diff --git a/spec/lib/gitlab/github_import/label_finder_spec.rb b/spec/lib/gitlab/github_import/label_finder_spec.rb
index bb946a15a2d..452f3c896a4 100644
--- a/spec/lib/gitlab/github_import/label_finder_spec.rb
+++ b/spec/lib/gitlab/github_import/label_finder_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GithubImport::LabelFinder, :clean_gitlab_redis_cache do
+RSpec.describe Gitlab::GithubImport::LabelFinder, :clean_gitlab_redis_cache do
let(:project) { create(:project) }
let(:finder) { described_class.new(project) }
let!(:bug) { create(:label, project: project, name: 'Bug') }
diff --git a/spec/lib/gitlab/github_import/markdown_text_spec.rb b/spec/lib/gitlab/github_import/markdown_text_spec.rb
index a1216db7aac..4995caa0733 100644
--- a/spec/lib/gitlab/github_import/markdown_text_spec.rb
+++ b/spec/lib/gitlab/github_import/markdown_text_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GithubImport::MarkdownText do
+RSpec.describe Gitlab::GithubImport::MarkdownText do
describe '.format' do
it 'formats the text' do
author = double(:author, login: 'Alice')
diff --git a/spec/lib/gitlab/github_import/milestone_finder_spec.rb b/spec/lib/gitlab/github_import/milestone_finder_spec.rb
index ecb533b7e39..419184d6115 100644
--- a/spec/lib/gitlab/github_import/milestone_finder_spec.rb
+++ b/spec/lib/gitlab/github_import/milestone_finder_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GithubImport::MilestoneFinder, :clean_gitlab_redis_cache do
+RSpec.describe Gitlab::GithubImport::MilestoneFinder, :clean_gitlab_redis_cache do
let!(:project) { create(:project) }
let!(:milestone) { create(:milestone, project: project) }
let(:finder) { described_class.new(project) }
diff --git a/spec/lib/gitlab/github_import/page_counter_spec.rb b/spec/lib/gitlab/github_import/page_counter_spec.rb
index 95125c9c22f..a1305b714b5 100644
--- a/spec/lib/gitlab/github_import/page_counter_spec.rb
+++ b/spec/lib/gitlab/github_import/page_counter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GithubImport::PageCounter, :clean_gitlab_redis_cache do
+RSpec.describe Gitlab::GithubImport::PageCounter, :clean_gitlab_redis_cache do
let(:project) { double(:project, id: 1) }
let(:counter) { described_class.new(project, :issues) }
diff --git a/spec/lib/gitlab/github_import/parallel_importer_spec.rb b/spec/lib/gitlab/github_import/parallel_importer_spec.rb
index a8d5cc58bd5..06304bf84ca 100644
--- a/spec/lib/gitlab/github_import/parallel_importer_spec.rb
+++ b/spec/lib/gitlab/github_import/parallel_importer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GithubImport::ParallelImporter do
+RSpec.describe Gitlab::GithubImport::ParallelImporter do
describe '.async?' do
it 'returns true' do
expect(described_class).to be_async
diff --git a/spec/lib/gitlab/github_import/parallel_scheduling_spec.rb b/spec/lib/gitlab/github_import/parallel_scheduling_spec.rb
index a6ae99b395c..578743be96b 100644
--- a/spec/lib/gitlab/github_import/parallel_scheduling_spec.rb
+++ b/spec/lib/gitlab/github_import/parallel_scheduling_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GithubImport::ParallelScheduling do
+RSpec.describe Gitlab::GithubImport::ParallelScheduling do
let(:importer_class) do
Class.new do
include(Gitlab::GithubImport::ParallelScheduling)
diff --git a/spec/lib/gitlab/github_import/representation/diff_note_spec.rb b/spec/lib/gitlab/github_import/representation/diff_note_spec.rb
index e743a87cdd1..7e540674258 100644
--- a/spec/lib/gitlab/github_import/representation/diff_note_spec.rb
+++ b/spec/lib/gitlab/github_import/representation/diff_note_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GithubImport::Representation::DiffNote do
+RSpec.describe Gitlab::GithubImport::Representation::DiffNote do
let(:hunk) do
'@@ -1 +1 @@
-Hello
diff --git a/spec/lib/gitlab/github_import/representation/expose_attribute_spec.rb b/spec/lib/gitlab/github_import/representation/expose_attribute_spec.rb
index e3b48df4ae9..d40be0e841c 100644
--- a/spec/lib/gitlab/github_import/representation/expose_attribute_spec.rb
+++ b/spec/lib/gitlab/github_import/representation/expose_attribute_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GithubImport::Representation::ExposeAttribute do
+RSpec.describe Gitlab::GithubImport::Representation::ExposeAttribute do
it 'defines a getter method that returns an attribute value' do
klass = Class.new do
include Gitlab::GithubImport::Representation::ExposeAttribute
diff --git a/spec/lib/gitlab/github_import/representation/issue_spec.rb b/spec/lib/gitlab/github_import/representation/issue_spec.rb
index 741a912e53b..3d306a4a3a3 100644
--- a/spec/lib/gitlab/github_import/representation/issue_spec.rb
+++ b/spec/lib/gitlab/github_import/representation/issue_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GithubImport::Representation::Issue do
+RSpec.describe Gitlab::GithubImport::Representation::Issue do
let(:created_at) { Time.new(2017, 1, 1, 12, 00) }
let(:updated_at) { Time.new(2017, 1, 1, 12, 15) }
diff --git a/spec/lib/gitlab/github_import/representation/note_spec.rb b/spec/lib/gitlab/github_import/representation/note_spec.rb
index a171a38bc9e..112bb7eb908 100644
--- a/spec/lib/gitlab/github_import/representation/note_spec.rb
+++ b/spec/lib/gitlab/github_import/representation/note_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GithubImport::Representation::Note do
+RSpec.describe Gitlab::GithubImport::Representation::Note do
let(:created_at) { Time.new(2017, 1, 1, 12, 00) }
let(:updated_at) { Time.new(2017, 1, 1, 12, 15) }
diff --git a/spec/lib/gitlab/github_import/representation/pull_request_spec.rb b/spec/lib/gitlab/github_import/representation/pull_request_spec.rb
index b6dcd098c9c..370eac1d993 100644
--- a/spec/lib/gitlab/github_import/representation/pull_request_spec.rb
+++ b/spec/lib/gitlab/github_import/representation/pull_request_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GithubImport::Representation::PullRequest do
+RSpec.describe Gitlab::GithubImport::Representation::PullRequest do
let(:created_at) { Time.new(2017, 1, 1, 12, 00) }
let(:updated_at) { Time.new(2017, 1, 1, 12, 15) }
let(:merged_at) { Time.new(2017, 1, 1, 12, 17) }
diff --git a/spec/lib/gitlab/github_import/representation/to_hash_spec.rb b/spec/lib/gitlab/github_import/representation/to_hash_spec.rb
index 9c47349b376..2770e5c5397 100644
--- a/spec/lib/gitlab/github_import/representation/to_hash_spec.rb
+++ b/spec/lib/gitlab/github_import/representation/to_hash_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GithubImport::Representation::ToHash do
+RSpec.describe Gitlab::GithubImport::Representation::ToHash do
describe '#to_hash' do
let(:user) { double(:user, attributes: { login: 'alice' }) }
diff --git a/spec/lib/gitlab/github_import/representation/user_spec.rb b/spec/lib/gitlab/github_import/representation/user_spec.rb
index a7ad6bda3ad..14204886e9b 100644
--- a/spec/lib/gitlab/github_import/representation/user_spec.rb
+++ b/spec/lib/gitlab/github_import/representation/user_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GithubImport::Representation::User do
+RSpec.describe Gitlab::GithubImport::Representation::User do
shared_examples 'a User' do
it 'returns an instance of User' do
expect(user).to be_an_instance_of(described_class)
diff --git a/spec/lib/gitlab/github_import/representation_spec.rb b/spec/lib/gitlab/github_import/representation_spec.rb
index 76753a0ff21..58c10c4a775 100644
--- a/spec/lib/gitlab/github_import/representation_spec.rb
+++ b/spec/lib/gitlab/github_import/representation_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GithubImport::Representation do
+RSpec.describe Gitlab::GithubImport::Representation do
describe '.symbolize_hash' do
it 'returns a Hash with the keys as Symbols' do
hash = described_class.symbolize_hash('number' => 10)
diff --git a/spec/lib/gitlab/github_import/sequential_importer_spec.rb b/spec/lib/gitlab/github_import/sequential_importer_spec.rb
index 256155dea03..fe13fcd2568 100644
--- a/spec/lib/gitlab/github_import/sequential_importer_spec.rb
+++ b/spec/lib/gitlab/github_import/sequential_importer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GithubImport::SequentialImporter do
+RSpec.describe Gitlab::GithubImport::SequentialImporter do
describe '#execute' do
it 'imports a project in sequence' do
repository = double(:repository)
diff --git a/spec/lib/gitlab/github_import/user_finder_spec.rb b/spec/lib/gitlab/github_import/user_finder_spec.rb
index 8764ebef32b..0dd2bd4df45 100644
--- a/spec/lib/gitlab/github_import/user_finder_spec.rb
+++ b/spec/lib/gitlab/github_import/user_finder_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GithubImport::UserFinder, :clean_gitlab_redis_cache do
+RSpec.describe Gitlab::GithubImport::UserFinder, :clean_gitlab_redis_cache do
let(:project) { create(:project) }
let(:client) { double(:client) }
let(:finder) { described_class.new(project, client) }
diff --git a/spec/lib/gitlab/github_import_spec.rb b/spec/lib/gitlab/github_import_spec.rb
index 290d66243aa..1a690b81d2b 100644
--- a/spec/lib/gitlab/github_import_spec.rb
+++ b/spec/lib/gitlab/github_import_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GithubImport do
+RSpec.describe Gitlab::GithubImport do
let(:project) { double(:project) }
describe '.new_client_for' do
diff --git a/spec/lib/gitlab/gitlab_import/client_spec.rb b/spec/lib/gitlab/gitlab_import/client_spec.rb
index 6e4e88093bb..7f57d5fbf1b 100644
--- a/spec/lib/gitlab/gitlab_import/client_spec.rb
+++ b/spec/lib/gitlab/gitlab_import/client_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GitlabImport::Client do
+RSpec.describe Gitlab::GitlabImport::Client do
include ImportSpecHelper
let(:token) { '123456' }
diff --git a/spec/lib/gitlab/gitlab_import/importer_spec.rb b/spec/lib/gitlab/gitlab_import/importer_spec.rb
index 2db1ddcfd0a..eb4c404e454 100644
--- a/spec/lib/gitlab/gitlab_import/importer_spec.rb
+++ b/spec/lib/gitlab/gitlab_import/importer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GitlabImport::Importer do
+RSpec.describe Gitlab::GitlabImport::Importer do
include ImportSpecHelper
describe '#execute' do
diff --git a/spec/lib/gitlab/gitlab_import/project_creator_spec.rb b/spec/lib/gitlab/gitlab_import/project_creator_spec.rb
index c7ef978df37..44bcfb93c51 100644
--- a/spec/lib/gitlab/gitlab_import/project_creator_spec.rb
+++ b/spec/lib/gitlab/gitlab_import/project_creator_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GitlabImport::ProjectCreator do
+RSpec.describe Gitlab::GitlabImport::ProjectCreator do
let(:user) { create(:user) }
let(:repo) do
{
diff --git a/spec/lib/gitlab/gl_repository/identifier_spec.rb b/spec/lib/gitlab/gl_repository/identifier_spec.rb
index c36f296702e..e95aaaa6690 100644
--- a/spec/lib/gitlab/gl_repository/identifier_spec.rb
+++ b/spec/lib/gitlab/gl_repository/identifier_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GlRepository::Identifier do
+RSpec.describe Gitlab::GlRepository::Identifier do
let_it_be(:project) { create(:project) }
let_it_be(:personal_snippet) { create(:personal_snippet, author: project.owner) }
let_it_be(:project_snippet) { create(:project_snippet, project: project, author: project.owner) }
@@ -14,6 +14,21 @@ describe Gitlab::GlRepository::Identifier do
let(:expected_container) { project }
let(:expected_type) { Gitlab::GlRepository::PROJECT }
end
+
+ pending 'https://gitlab.com/gitlab-org/gitlab/-/issues/219192' do
+ it_behaves_like 'parsing gl_repository identifier' do
+ let(:record_id) { project.id }
+ let(:identifier) { "project-#{record_id}-code" }
+ let(:expected_container) { project }
+ let(:expected_type) { Gitlab::GlRepository::PROJECT }
+ end
+ end
+
+ it_behaves_like 'parsing gl_repository identifier' do
+ let(:identifier) { "project-1000000" }
+ let(:expected_container) { nil }
+ let(:expected_type) { Gitlab::GlRepository::PROJECT }
+ end
end
describe 'wiki' do
@@ -23,6 +38,13 @@ describe Gitlab::GlRepository::Identifier do
let(:expected_container) { project }
let(:expected_type) { Gitlab::GlRepository::WIKI }
end
+
+ it_behaves_like 'parsing gl_repository identifier' do
+ let(:record_id) { project.id }
+ let(:identifier) { "project-#{record_id}-wiki" }
+ let(:expected_container) { project }
+ let(:expected_type) { Gitlab::GlRepository::WIKI }
+ end
end
describe 'snippet' do
@@ -54,29 +76,30 @@ describe Gitlab::GlRepository::Identifier do
end
end
- describe 'incorrect format' do
- def expect_error_raised_for(identifier)
- expect { described_class.new(identifier) }.to raise_error(ArgumentError)
- end
-
- it 'raises error for incorrect id' do
- expect_error_raised_for('wiki-noid')
+ context 'when the format is incorrect' do
+ where(:identifier) do
+ [
+ 'wiki-noid',
+ 'foo-2',
+ 'project-0',
+ '2-project',
+ 'snippet-2-wiki',
+ 'project-wibble-wiki',
+ 'wiki-1-project',
+ 'snippet',
+ 'project-1-wiki-bar'
+ ]
end
- it 'raises error for incorrect type' do
- expect_error_raised_for('foo-2')
- end
-
- it 'raises error for incorrect three-segment container' do
- expect_error_raised_for('snippet-2-wiki')
- end
-
- it 'raises error for one segment' do
- expect_error_raised_for('snippet')
+ with_them do
+ it 'raises InvalidIdentifier' do
+ expect { described_class.parse(identifier) }.to raise_error(described_class::InvalidIdentifier)
+ end
end
- it 'raises error for more than three segments' do
- expect_error_raised_for('project-1-wiki-bar')
+ it 'raises InvalidIdentifier on project-1-project' do
+ pending 'https://gitlab.com/gitlab-org/gitlab/-/issues/219192'
+ expect { described_class.parse('project-1-project') }.to raise_error(described_class::InvalidIdentifier)
end
end
end
diff --git a/spec/lib/gitlab/gl_repository/repo_type_spec.rb b/spec/lib/gitlab/gl_repository/repo_type_spec.rb
index f5270104d2f..e920fc7cd3b 100644
--- a/spec/lib/gitlab/gl_repository/repo_type_spec.rb
+++ b/spec/lib/gitlab/gl_repository/repo_type_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe Gitlab::GlRepository::RepoType do
+RSpec.describe Gitlab::GlRepository::RepoType do
let_it_be(:project) { create(:project) }
let_it_be(:personal_snippet) { create(:personal_snippet, author: project.owner) }
let_it_be(:project_snippet) { create(:project_snippet, project: project, author: project.owner) }
diff --git a/spec/lib/gitlab/gl_repository_spec.rb b/spec/lib/gitlab/gl_repository_spec.rb
index 413540b4db8..f90103ee6f7 100644
--- a/spec/lib/gitlab/gl_repository_spec.rb
+++ b/spec/lib/gitlab/gl_repository_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ::Gitlab::GlRepository do
+RSpec.describe ::Gitlab::GlRepository do
describe '.parse' do
let_it_be(:project) { create(:project, :repository) }
let_it_be(:snippet) { create(:personal_snippet) }
diff --git a/spec/lib/gitlab/global_id_spec.rb b/spec/lib/gitlab/global_id_spec.rb
index 719743ed5dc..3fc18858eee 100644
--- a/spec/lib/gitlab/global_id_spec.rb
+++ b/spec/lib/gitlab/global_id_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GlobalId do
+RSpec.describe Gitlab::GlobalId do
describe '.build' do
let_it_be(:object) { create(:issue) }
@@ -34,4 +34,37 @@ describe Gitlab::GlobalId do
expect { described_class.build }.to raise_error(URI::InvalidComponentError)
end
end
+
+ describe '.as_global_id' do
+ let(:project) { build_stubbed(:project) }
+
+ it 'is the identify function on GlobalID instances' do
+ gid = project.to_global_id
+
+ expect(described_class.as_global_id(gid)).to eq(gid)
+ end
+
+ it 'wraps URI::GID in GlobalID' do
+ uri = described_class.build(model_name: 'Foo', id: 1)
+
+ expect(described_class.as_global_id(uri)).to eq(GlobalID.new(uri))
+ end
+
+ it 'cannot coerce Integers without a model name' do
+ expect { described_class.as_global_id(1) }
+ .to raise_error(described_class::CoerceError, 'Cannot coerce Integer')
+ end
+
+ it 'can coerce Integers with a model name' do
+ uri = described_class.build(model_name: 'Foo', id: 1)
+
+ expect(described_class.as_global_id(1, model_name: 'Foo')).to eq(GlobalID.new(uri))
+ end
+
+ it 'rejects any other value' do
+ [:symbol, 'string', nil, [], {}, project].each do |value|
+ expect { described_class.as_global_id(value) }.to raise_error(described_class::CoerceError)
+ end
+ end
+ end
end
diff --git a/spec/lib/gitlab/gon_helper_spec.rb b/spec/lib/gitlab/gon_helper_spec.rb
index 7b0e0d01257..95db6b2b4e0 100644
--- a/spec/lib/gitlab/gon_helper_spec.rb
+++ b/spec/lib/gitlab/gon_helper_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GonHelper do
+RSpec.describe Gitlab::GonHelper do
let(:helper) do
Class.new do
include Gitlab::GonHelper
diff --git a/spec/lib/gitlab/google_code_import/client_spec.rb b/spec/lib/gitlab/google_code_import/client_spec.rb
index fb1c7085017..402d2169432 100644
--- a/spec/lib/gitlab/google_code_import/client_spec.rb
+++ b/spec/lib/gitlab/google_code_import/client_spec.rb
@@ -2,7 +2,7 @@
require "spec_helper"
-describe Gitlab::GoogleCodeImport::Client do
+RSpec.describe Gitlab::GoogleCodeImport::Client do
let(:raw_data) { Gitlab::Json.parse(fixture_file("GoogleCodeProjectHosting.json")) }
subject { described_class.new(raw_data) }
diff --git a/spec/lib/gitlab/google_code_import/importer_spec.rb b/spec/lib/gitlab/google_code_import/importer_spec.rb
index 3118671bb5e..f681e3c9f31 100644
--- a/spec/lib/gitlab/google_code_import/importer_spec.rb
+++ b/spec/lib/gitlab/google_code_import/importer_spec.rb
@@ -2,7 +2,7 @@
require "spec_helper"
-describe Gitlab::GoogleCodeImport::Importer do
+RSpec.describe Gitlab::GoogleCodeImport::Importer do
let(:mapped_user) { create(:user, username: "thilo123") }
let(:raw_data) { Gitlab::Json.parse(fixture_file("GoogleCodeProjectHosting.json")) }
let(:client) { Gitlab::GoogleCodeImport::Client.new(raw_data) }
diff --git a/spec/lib/gitlab/google_code_import/project_creator_spec.rb b/spec/lib/gitlab/google_code_import/project_creator_spec.rb
index 2353c24f77b..4be2e16c116 100644
--- a/spec/lib/gitlab/google_code_import/project_creator_spec.rb
+++ b/spec/lib/gitlab/google_code_import/project_creator_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GoogleCodeImport::ProjectCreator do
+RSpec.describe Gitlab::GoogleCodeImport::ProjectCreator do
let(:user) { create(:user) }
let(:repo) do
Gitlab::GoogleCodeImport::Repository.new(
diff --git a/spec/lib/gitlab/gpg/commit_spec.rb b/spec/lib/gitlab/gpg/commit_spec.rb
index ea0a6e1b967..55102554508 100644
--- a/spec/lib/gitlab/gpg/commit_spec.rb
+++ b/spec/lib/gitlab/gpg/commit_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Gpg::Commit do
+RSpec.describe Gitlab::Gpg::Commit do
describe '#signature' do
shared_examples 'returns the cached signature on second call' do
it 'returns the cached signature on second call' do
diff --git a/spec/lib/gitlab/gpg_spec.rb b/spec/lib/gitlab/gpg_spec.rb
index c7b9775f642..72c6c8efb5e 100644
--- a/spec/lib/gitlab/gpg_spec.rb
+++ b/spec/lib/gitlab/gpg_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Gpg do
+RSpec.describe Gitlab::Gpg do
describe '.fingerprints_from_key' do
before do
# make sure that each method is using the temporary keychain
@@ -244,7 +244,7 @@ describe Gitlab::Gpg do
end
end
-describe Gitlab::Gpg::CurrentKeyChain do
+RSpec.describe Gitlab::Gpg::CurrentKeyChain do
around do |example|
Gitlab::Gpg.using_tmp_keychain do
example.run
diff --git a/spec/lib/gitlab/grape_logging/formatters/lograge_with_timestamp_spec.rb b/spec/lib/gitlab/grape_logging/formatters/lograge_with_timestamp_spec.rb
index 84f23bb2ad9..e5d0adcfd5f 100644
--- a/spec/lib/gitlab/grape_logging/formatters/lograge_with_timestamp_spec.rb
+++ b/spec/lib/gitlab/grape_logging/formatters/lograge_with_timestamp_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GrapeLogging::Formatters::LogrageWithTimestamp do
+RSpec.describe Gitlab::GrapeLogging::Formatters::LogrageWithTimestamp do
let(:log_entry) do
{
status: 200,
diff --git a/spec/lib/gitlab/grape_logging/loggers/cloudflare_logger_spec.rb b/spec/lib/gitlab/grape_logging/loggers/cloudflare_logger_spec.rb
index 922a433d7ac..f683f98f019 100644
--- a/spec/lib/gitlab/grape_logging/loggers/cloudflare_logger_spec.rb
+++ b/spec/lib/gitlab/grape_logging/loggers/cloudflare_logger_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GrapeLogging::Loggers::CloudflareLogger do
+RSpec.describe Gitlab::GrapeLogging::Loggers::CloudflareLogger do
subject { described_class.new }
describe "#parameters" do
diff --git a/spec/lib/gitlab/grape_logging/loggers/exception_logger_spec.rb b/spec/lib/gitlab/grape_logging/loggers/exception_logger_spec.rb
index cc9535d4d2c..bcb1f6c5af7 100644
--- a/spec/lib/gitlab/grape_logging/loggers/exception_logger_spec.rb
+++ b/spec/lib/gitlab/grape_logging/loggers/exception_logger_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GrapeLogging::Loggers::ExceptionLogger do
+RSpec.describe Gitlab::GrapeLogging::Loggers::ExceptionLogger do
let(:mock_request) { OpenStruct.new(env: {}) }
let(:response_body) { nil }
diff --git a/spec/lib/gitlab/grape_logging/loggers/perf_logger_spec.rb b/spec/lib/gitlab/grape_logging/loggers/perf_logger_spec.rb
index 09ba4b89a1a..95e3af34174 100644
--- a/spec/lib/gitlab/grape_logging/loggers/perf_logger_spec.rb
+++ b/spec/lib/gitlab/grape_logging/loggers/perf_logger_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GrapeLogging::Loggers::PerfLogger do
+RSpec.describe Gitlab::GrapeLogging::Loggers::PerfLogger do
subject { described_class.new }
describe ".parameters" do
diff --git a/spec/lib/gitlab/grape_logging/loggers/queue_duration_logger_spec.rb b/spec/lib/gitlab/grape_logging/loggers/queue_duration_logger_spec.rb
index 17c0659327d..e68c1446502 100644
--- a/spec/lib/gitlab/grape_logging/loggers/queue_duration_logger_spec.rb
+++ b/spec/lib/gitlab/grape_logging/loggers/queue_duration_logger_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GrapeLogging::Loggers::QueueDurationLogger do
+RSpec.describe Gitlab::GrapeLogging::Loggers::QueueDurationLogger do
subject { described_class.new }
describe ".parameters" do
diff --git a/spec/lib/gitlab/graphql/authorize/authorize_field_service_spec.rb b/spec/lib/gitlab/graphql/authorize/authorize_field_service_spec.rb
index c1dab5feb91..efe6c27c463 100644
--- a/spec/lib/gitlab/graphql/authorize/authorize_field_service_spec.rb
+++ b/spec/lib/gitlab/graphql/authorize/authorize_field_service_spec.rb
@@ -4,7 +4,7 @@ require 'spec_helper'
# Also see spec/graphql/features/authorization_spec.rb for
# integration tests of AuthorizeFieldService
-describe Gitlab::Graphql::Authorize::AuthorizeFieldService do
+RSpec.describe Gitlab::Graphql::Authorize::AuthorizeFieldService do
def type(type_authorizations = [])
Class.new(Types::BaseObject) do
graphql_name 'TestType'
diff --git a/spec/lib/gitlab/graphql/authorize/authorize_resource_spec.rb b/spec/lib/gitlab/graphql/authorize/authorize_resource_spec.rb
index 67cb064b966..83873081a98 100644
--- a/spec/lib/gitlab/graphql/authorize/authorize_resource_spec.rb
+++ b/spec/lib/gitlab/graphql/authorize/authorize_resource_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Graphql::Authorize::AuthorizeResource do
+RSpec.describe Gitlab::Graphql::Authorize::AuthorizeResource do
let(:fake_class) do
Class.new do
include Gitlab::Graphql::Authorize::AuthorizeResource
diff --git a/spec/lib/gitlab/graphql/calls_gitaly/instrumentation_spec.rb b/spec/lib/gitlab/graphql/calls_gitaly/instrumentation_spec.rb
index d93ce464a92..f16767f7d14 100644
--- a/spec/lib/gitlab/graphql/calls_gitaly/instrumentation_spec.rb
+++ b/spec/lib/gitlab/graphql/calls_gitaly/instrumentation_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe Gitlab::Graphql::CallsGitaly::Instrumentation do
+RSpec.describe Gitlab::Graphql::CallsGitaly::Instrumentation do
subject { described_class.new }
describe '#calls_gitaly_check' do
diff --git a/spec/lib/gitlab/graphql/copy_field_description_spec.rb b/spec/lib/gitlab/graphql/copy_field_description_spec.rb
index e7462c5b954..310b4046b56 100644
--- a/spec/lib/gitlab/graphql/copy_field_description_spec.rb
+++ b/spec/lib/gitlab/graphql/copy_field_description_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Graphql::CopyFieldDescription do
+RSpec.describe Gitlab::Graphql::CopyFieldDescription do
subject { Class.new.include(described_class) }
describe '.copy_field_description' do
diff --git a/spec/lib/gitlab/graphql/docs/renderer_spec.rb b/spec/lib/gitlab/graphql/docs/renderer_spec.rb
index 87079a1786c..81ef7fcda97 100644
--- a/spec/lib/gitlab/graphql/docs/renderer_spec.rb
+++ b/spec/lib/gitlab/graphql/docs/renderer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Graphql::Docs::Renderer do
+RSpec.describe Gitlab::Graphql::Docs::Renderer do
describe '#contents' do
# Returns a Schema that uses the given `type`
def mock_schema(type)
diff --git a/spec/lib/gitlab/graphql/find_argument_in_parent_spec.rb b/spec/lib/gitlab/graphql/find_argument_in_parent_spec.rb
index 91e90315b3e..68b24a60a99 100644
--- a/spec/lib/gitlab/graphql/find_argument_in_parent_spec.rb
+++ b/spec/lib/gitlab/graphql/find_argument_in_parent_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Graphql::FindArgumentInParent do
+RSpec.describe Gitlab::Graphql::FindArgumentInParent do
describe '#find' do
def build_node(parent = nil, args: {})
props = { irep_node: double(arguments: args) }
diff --git a/spec/lib/gitlab/graphql/generic_tracing_spec.rb b/spec/lib/gitlab/graphql/generic_tracing_spec.rb
index ae92dcc40af..cd116225ecd 100644
--- a/spec/lib/gitlab/graphql/generic_tracing_spec.rb
+++ b/spec/lib/gitlab/graphql/generic_tracing_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Graphql::GenericTracing do
+RSpec.describe Gitlab::Graphql::GenericTracing do
let(:graphql_duration_seconds_histogram) { double('Gitlab::Metrics::NullMetric') }
it 'updates graphql histogram with expected labels' do
diff --git a/spec/lib/gitlab/graphql/loaders/batch_lfs_oid_loader_spec.rb b/spec/lib/gitlab/graphql/loaders/batch_lfs_oid_loader_spec.rb
index b3d57c899d5..ae5d9686c54 100644
--- a/spec/lib/gitlab/graphql/loaders/batch_lfs_oid_loader_spec.rb
+++ b/spec/lib/gitlab/graphql/loaders/batch_lfs_oid_loader_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Graphql::Loaders::BatchLfsOidLoader do
+RSpec.describe Gitlab::Graphql::Loaders::BatchLfsOidLoader do
include GraphqlHelpers
let_it_be(:project) { create(:project, :repository) }
diff --git a/spec/lib/gitlab/graphql/loaders/batch_model_loader_spec.rb b/spec/lib/gitlab/graphql/loaders/batch_model_loader_spec.rb
index 79f9ecb39cf..cf1f00bc176 100644
--- a/spec/lib/gitlab/graphql/loaders/batch_model_loader_spec.rb
+++ b/spec/lib/gitlab/graphql/loaders/batch_model_loader_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Graphql::Loaders::BatchModelLoader do
+RSpec.describe Gitlab::Graphql::Loaders::BatchModelLoader do
describe '#find' do
let(:issue) { create(:issue) }
let(:user) { create(:user) }
diff --git a/spec/lib/gitlab/graphql/loaders/batch_project_statistics_loader_spec.rb b/spec/lib/gitlab/graphql/loaders/batch_project_statistics_loader_spec.rb
index ec2fcad31e5..7ae28fdcf0f 100644
--- a/spec/lib/gitlab/graphql/loaders/batch_project_statistics_loader_spec.rb
+++ b/spec/lib/gitlab/graphql/loaders/batch_project_statistics_loader_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Graphql::Loaders::BatchProjectStatisticsLoader do
+RSpec.describe Gitlab::Graphql::Loaders::BatchProjectStatisticsLoader do
describe '#find' do
it 'only queries once for project statistics' do
stats = create_list(:project_statistics, 2)
diff --git a/spec/lib/gitlab/graphql/loaders/batch_root_storage_statistics_loader_spec.rb b/spec/lib/gitlab/graphql/loaders/batch_root_storage_statistics_loader_spec.rb
index 38931f7ab5e..f73eace274d 100644
--- a/spec/lib/gitlab/graphql/loaders/batch_root_storage_statistics_loader_spec.rb
+++ b/spec/lib/gitlab/graphql/loaders/batch_root_storage_statistics_loader_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Graphql::Loaders::BatchRootStorageStatisticsLoader do
+RSpec.describe Gitlab::Graphql::Loaders::BatchRootStorageStatisticsLoader do
describe '#find' do
it 'only queries once for project statistics' do
stats = create_list(:namespace_root_storage_statistics, 2)
diff --git a/spec/lib/gitlab/graphql/loaders/issuable_loader_spec.rb b/spec/lib/gitlab/graphql/loaders/issuable_loader_spec.rb
new file mode 100644
index 00000000000..180966de895
--- /dev/null
+++ b/spec/lib/gitlab/graphql/loaders/issuable_loader_spec.rb
@@ -0,0 +1,96 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Graphql::Loaders::IssuableLoader do
+ subject { described_class.new(parent, finder) }
+
+ let(:params) { HashWithIndifferentAccess.new }
+
+ describe '#find_all' do
+ let(:finder) { double(:finder, params: params, execute: %i[x y z]) }
+
+ where(:factory, :param_name) do
+ %i[project group].map { |thing| [thing, :"#{thing}_id"] }
+ end
+
+ with_them do
+ let(:parent) { build_stubbed(factory) }
+
+ it 'assignes the parent parameter, and batching_find_alls the finder' do
+ expect(subject.find_all).to contain_exactly(:x, :y, :z)
+ expect(params).to include(param_name => parent)
+ end
+ end
+
+ context 'the parent is of an unexpected type' do
+ let(:parent) { build(:merge_request) }
+
+ it 'raises an error if we pass an unexpected parent' do
+ expect { subject.find_all }.to raise_error(/Unexpected parent/)
+ end
+ end
+ end
+
+ describe '#batching_find_all' do
+ context 'the finder params are anything other than [iids]' do
+ let(:finder) { double(:finder, params: params, execute: [:foo]) }
+ let(:parent) { build_stubbed(:project) }
+
+ it 'batching_find_alls the finder, setting the correct parent parameter' do
+ expect(subject.batching_find_all).to eq([:foo])
+ expect(params[:project_id]).to eq(parent)
+ end
+
+ it 'allows a post-process block' do
+ expect(subject.batching_find_all(&:first)).to eq(:foo)
+ end
+ end
+
+ context 'the finder params are exactly [iids]' do
+ # Dumb finder class, that only implements what we need, and has
+ # predictable query counts.
+ let(:finder_class) do
+ Class.new do
+ attr_reader :current_user, :params
+
+ def initialize(user, args)
+ @current_user = user
+ @params = HashWithIndifferentAccess.new(args.to_h)
+ end
+
+ def execute
+ params[:project_id].issues.where(iid: params[:iids])
+ end
+ end
+ end
+
+ it 'batches requests' do
+ issue_a = create(:issue)
+ issue_b = create(:issue)
+ issue_c = create(:issue, project: issue_a.project)
+ proj_1 = issue_a.project
+ proj_2 = issue_b.project
+ user = create(:user, developer_projects: [proj_1, proj_2])
+
+ finder_a = finder_class.new(user, iids: [issue_a.iid])
+ finder_b = finder_class.new(user, iids: [issue_b.iid])
+ finder_c = finder_class.new(user, iids: [issue_c.iid])
+
+ results = []
+
+ expect do
+ results.concat(described_class.new(proj_1, finder_a).batching_find_all)
+ results.concat(described_class.new(proj_2, finder_b).batching_find_all)
+ results.concat(described_class.new(proj_1, finder_c).batching_find_all)
+ end.not_to exceed_query_limit(0)
+
+ expect do
+ results = results.map(&:sync)
+ end.not_to exceed_query_limit(2)
+
+ expect(results).to contain_exactly(issue_a, issue_b, issue_c)
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/graphql/markdown_field/resolver_spec.rb b/spec/lib/gitlab/graphql/markdown_field/resolver_spec.rb
index b95bcdef188..af604e1c7d5 100644
--- a/spec/lib/gitlab/graphql/markdown_field/resolver_spec.rb
+++ b/spec/lib/gitlab/graphql/markdown_field/resolver_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe Gitlab::Graphql::MarkdownField::Resolver do
+RSpec.describe Gitlab::Graphql::MarkdownField::Resolver do
include Gitlab::Routing
let(:resolver) { described_class.new(:note) }
diff --git a/spec/lib/gitlab/graphql/markdown_field_spec.rb b/spec/lib/gitlab/graphql/markdown_field_spec.rb
index 866a20801d3..e3da925376e 100644
--- a/spec/lib/gitlab/graphql/markdown_field_spec.rb
+++ b/spec/lib/gitlab/graphql/markdown_field_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe Gitlab::Graphql::MarkdownField do
+RSpec.describe Gitlab::Graphql::MarkdownField do
describe '.markdown_field' do
it 'creates the field with some default attributes' do
field = class_with_markdown_field(:test_html, null: true, method: :hello).fields['testHtml']
diff --git a/spec/lib/gitlab/graphql/mount_mutation_spec.rb b/spec/lib/gitlab/graphql/mount_mutation_spec.rb
new file mode 100644
index 00000000000..d6b932e08d2
--- /dev/null
+++ b/spec/lib/gitlab/graphql/mount_mutation_spec.rb
@@ -0,0 +1,63 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+RSpec.describe Gitlab::Graphql::MountMutation do
+ let_it_be(:mutation) do
+ Class.new(Mutations::BaseMutation) do
+ graphql_name 'TestMutation'
+
+ argument :foo, GraphQL::STRING_TYPE, required: false
+ field :bar, GraphQL::STRING_TYPE, null: true
+ end
+ end
+
+ describe '.mount_mutation' do
+ subject(:field) do
+ mutation_type = mutation_type_factory do |f|
+ f.mount_mutation(mutation)
+ end
+
+ mutation_type.get_field('testMutation').to_graphql
+ end
+
+ it 'mounts a mutation' do
+ expect(field.mutation).to be_present
+ end
+ end
+
+ describe '.mount_aliased_mutation' do
+ subject(:field) do
+ mutation_type = mutation_type_factory do |f|
+ f.mount_aliased_mutation('MyAlias', mutation)
+ end
+
+ mutation_type.get_field('myAlias').to_graphql
+ end
+
+ it 'mounts a mutation' do
+ expect(field.mutation).to be_present
+ end
+
+ it 'has a correct `graphql_name`' do
+ expect(field.mutation.graphql_name).to eq('MyAlias')
+ end
+
+ it 'has a correct type' do
+ expect(field.type.name).to eq('MyAliasPayload')
+ end
+
+ it 'has a correct input argument' do
+ expect(field.arguments['input'].type.unwrap.name).to eq('MyAliasInput')
+ end
+ end
+
+ def mutation_type_factory
+ Class.new(GraphQL::Schema::Object) do
+ include Gitlab::Graphql::MountMutation
+
+ graphql_name 'MutationType'
+
+ yield(self) if block_given?
+ end
+ end
+end
diff --git a/spec/lib/gitlab/graphql/pagination/externally_paginated_array_connection_spec.rb b/spec/lib/gitlab/graphql/pagination/externally_paginated_array_connection_spec.rb
index 11cf14523c2..932bcd8cd92 100644
--- a/spec/lib/gitlab/graphql/pagination/externally_paginated_array_connection_spec.rb
+++ b/spec/lib/gitlab/graphql/pagination/externally_paginated_array_connection_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Graphql::Pagination::ExternallyPaginatedArrayConnection do
+RSpec.describe Gitlab::Graphql::Pagination::ExternallyPaginatedArrayConnection do
let(:prev_cursor) { 1 }
let(:next_cursor) { 6 }
let(:values) { [2, 3, 4, 5] }
diff --git a/spec/lib/gitlab/graphql/pagination/keyset/conditions/not_null_condition_spec.rb b/spec/lib/gitlab/graphql/pagination/keyset/conditions/not_null_condition_spec.rb
index 33dc6bdea97..eecdaa3409f 100644
--- a/spec/lib/gitlab/graphql/pagination/keyset/conditions/not_null_condition_spec.rb
+++ b/spec/lib/gitlab/graphql/pagination/keyset/conditions/not_null_condition_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Graphql::Pagination::Keyset::Conditions::NotNullCondition do
+RSpec.describe Gitlab::Graphql::Pagination::Keyset::Conditions::NotNullCondition do
describe '#build' do
let(:operators) { ['>', '>'] }
let(:before_or_after) { :after }
diff --git a/spec/lib/gitlab/graphql/pagination/keyset/conditions/null_condition_spec.rb b/spec/lib/gitlab/graphql/pagination/keyset/conditions/null_condition_spec.rb
index 1c74f2fb0ab..582f96299ec 100644
--- a/spec/lib/gitlab/graphql/pagination/keyset/conditions/null_condition_spec.rb
+++ b/spec/lib/gitlab/graphql/pagination/keyset/conditions/null_condition_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Graphql::Pagination::Keyset::Conditions::NullCondition do
+RSpec.describe Gitlab::Graphql::Pagination::Keyset::Conditions::NullCondition do
describe '#build' do
let(:values) { [nil, 500] }
let(:operators) { [nil, '>'] }
diff --git a/spec/lib/gitlab/graphql/pagination/keyset/connection_spec.rb b/spec/lib/gitlab/graphql/pagination/keyset/connection_spec.rb
index ed728444b17..65698caac34 100644
--- a/spec/lib/gitlab/graphql/pagination/keyset/connection_spec.rb
+++ b/spec/lib/gitlab/graphql/pagination/keyset/connection_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Graphql::Pagination::Keyset::Connection do
+RSpec.describe Gitlab::Graphql::Pagination::Keyset::Connection do
let(:nodes) { Project.all.order(id: :asc) }
let(:arguments) { {} }
let(:query_type) { GraphQL::ObjectType.new }
diff --git a/spec/lib/gitlab/graphql/pagination/keyset/order_info_spec.rb b/spec/lib/gitlab/graphql/pagination/keyset/order_info_spec.rb
index cb5656d7c00..9f310f30253 100644
--- a/spec/lib/gitlab/graphql/pagination/keyset/order_info_spec.rb
+++ b/spec/lib/gitlab/graphql/pagination/keyset/order_info_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Graphql::Pagination::Keyset::OrderInfo do
+RSpec.describe Gitlab::Graphql::Pagination::Keyset::OrderInfo do
describe '#build_order_list' do
let(:order_list) { described_class.build_order_list(relation) }
diff --git a/spec/lib/gitlab/graphql/pagination/keyset/query_builder_spec.rb b/spec/lib/gitlab/graphql/pagination/keyset/query_builder_spec.rb
index 7fd5b889cf9..31c02fd43e8 100644
--- a/spec/lib/gitlab/graphql/pagination/keyset/query_builder_spec.rb
+++ b/spec/lib/gitlab/graphql/pagination/keyset/query_builder_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Graphql::Pagination::Keyset::QueryBuilder do
+RSpec.describe Gitlab::Graphql::Pagination::Keyset::QueryBuilder do
context 'when number of ordering fields is 0' do
it 'raises an error' do
expect { described_class.new(Issue.arel_table, [], {}, :after) }
diff --git a/spec/lib/gitlab/graphql/pagination/offset_active_record_relation_connection_spec.rb b/spec/lib/gitlab/graphql/pagination/offset_active_record_relation_connection_spec.rb
index 931b1e708de..86f35de94ed 100644
--- a/spec/lib/gitlab/graphql/pagination/offset_active_record_relation_connection_spec.rb
+++ b/spec/lib/gitlab/graphql/pagination/offset_active_record_relation_connection_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Graphql::Pagination::OffsetActiveRecordRelationConnection do
+RSpec.describe Gitlab::Graphql::Pagination::OffsetActiveRecordRelationConnection do
it 'subclasses from GraphQL::Relay::RelationConnection' do
expect(described_class.superclass).to eq GraphQL::Pagination::ActiveRecordRelationConnection
end
diff --git a/spec/lib/gitlab/graphql/query_analyzers/logger_analyzer_spec.rb b/spec/lib/gitlab/graphql/query_analyzers/logger_analyzer_spec.rb
index 66033736e01..89d2ab8bb87 100644
--- a/spec/lib/gitlab/graphql/query_analyzers/logger_analyzer_spec.rb
+++ b/spec/lib/gitlab/graphql/query_analyzers/logger_analyzer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Graphql::QueryAnalyzers::LoggerAnalyzer do
+RSpec.describe Gitlab::Graphql::QueryAnalyzers::LoggerAnalyzer do
subject { described_class.new }
describe '#analyze?' do
@@ -17,9 +17,27 @@ describe Gitlab::Graphql::QueryAnalyzers::LoggerAnalyzer do
end
context 'feature flag enabled by default' do
+ let(:monotonic_time_before) { 42 }
+ let(:monotonic_time_after) { 500 }
+ let(:monotonic_time_duration) { monotonic_time_after - monotonic_time_before }
+
it 'enables the analyzer' do
expect(subject.analyze?(anything)).to be_truthy
end
+
+ it 'returns a duration in seconds' do
+ allow(GraphQL::Analysis).to receive(:analyze_query).and_return([4, 2])
+ allow(Gitlab::Metrics::System).to receive(:monotonic_time).and_return(monotonic_time_before, monotonic_time_after)
+ allow(Gitlab::GraphqlLogger).to receive(:info)
+
+ expected_duration = monotonic_time_duration
+ memo = subject.initial_value(spy('query'))
+
+ subject.final_value(memo)
+
+ expect(memo).to have_key(:duration_s)
+ expect(memo[:duration_s]).to eq(expected_duration)
+ end
end
end
end
diff --git a/spec/lib/gitlab/graphql/representation/submodule_tree_entry_spec.rb b/spec/lib/gitlab/graphql/representation/submodule_tree_entry_spec.rb
index ed092a846ae..eb56f1f9671 100644
--- a/spec/lib/gitlab/graphql/representation/submodule_tree_entry_spec.rb
+++ b/spec/lib/gitlab/graphql/representation/submodule_tree_entry_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Graphql::Representation::SubmoduleTreeEntry do
+RSpec.describe Gitlab::Graphql::Representation::SubmoduleTreeEntry do
let(:project) { create(:project, :repository) }
let(:repository) { project.repository }
diff --git a/spec/lib/gitlab/graphql/representation/tree_entry_spec.rb b/spec/lib/gitlab/graphql/representation/tree_entry_spec.rb
index d45e690160c..f4eb7b37774 100644
--- a/spec/lib/gitlab/graphql/representation/tree_entry_spec.rb
+++ b/spec/lib/gitlab/graphql/representation/tree_entry_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Graphql::Representation::TreeEntry do
+RSpec.describe Gitlab::Graphql::Representation::TreeEntry do
let(:project) { create(:project, :repository) }
let(:repository) { project.repository }
diff --git a/spec/lib/gitlab/graphql/timeout_spec.rb b/spec/lib/gitlab/graphql/timeout_spec.rb
index 8e04586d0ec..3669a89ba7c 100644
--- a/spec/lib/gitlab/graphql/timeout_spec.rb
+++ b/spec/lib/gitlab/graphql/timeout_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Graphql::Timeout do
+RSpec.describe Gitlab::Graphql::Timeout do
it 'inherits from ' do
expect(described_class.superclass).to eq GraphQL::Schema::Timeout
end
diff --git a/spec/lib/gitlab/graphql_logger_spec.rb b/spec/lib/gitlab/graphql_logger_spec.rb
index 12cb56c78c1..c92666e0663 100644
--- a/spec/lib/gitlab/graphql_logger_spec.rb
+++ b/spec/lib/gitlab/graphql_logger_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GraphqlLogger do
+RSpec.describe Gitlab::GraphqlLogger do
subject { described_class.new('/dev/null') }
let(:now) { Time.now }
diff --git a/spec/lib/gitlab/graphs/commits_spec.rb b/spec/lib/gitlab/graphs/commits_spec.rb
index f92c7fb11a1..79cec2d8705 100644
--- a/spec/lib/gitlab/graphs/commits_spec.rb
+++ b/spec/lib/gitlab/graphs/commits_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Graphs::Commits do
+RSpec.describe Gitlab::Graphs::Commits do
let!(:project) { create(:project, :public) }
let!(:commit1) { create(:commit, git_commit: RepoHelpers.sample_commit, project: project, committed_date: Time.now) }
diff --git a/spec/lib/gitlab/group_search_results_spec.rb b/spec/lib/gitlab/group_search_results_spec.rb
index 746f505c877..b6a3c8b5e76 100644
--- a/spec/lib/gitlab/group_search_results_spec.rb
+++ b/spec/lib/gitlab/group_search_results_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GroupSearchResults do
+RSpec.describe Gitlab::GroupSearchResults do
let(:user) { create(:user) }
describe 'user search' do
diff --git a/spec/lib/gitlab/hashed_storage/migrator_spec.rb b/spec/lib/gitlab/hashed_storage/migrator_spec.rb
index c59b152a982..0549b3128c7 100644
--- a/spec/lib/gitlab/hashed_storage/migrator_spec.rb
+++ b/spec/lib/gitlab/hashed_storage/migrator_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::HashedStorage::Migrator, :redis do
+RSpec.describe Gitlab::HashedStorage::Migrator, :redis do
describe '#bulk_schedule_migration' do
it 'schedules job to HashedStorage::MigratorWorker' do
Sidekiq::Testing.fake! do
diff --git a/spec/lib/gitlab/health_checks/db_check_spec.rb b/spec/lib/gitlab/health_checks/db_check_spec.rb
index 3c1c1e3818d..60ebc596a0f 100644
--- a/spec/lib/gitlab/health_checks/db_check_spec.rb
+++ b/spec/lib/gitlab/health_checks/db_check_spec.rb
@@ -3,6 +3,6 @@
require 'spec_helper'
require_relative './simple_check_shared'
-describe Gitlab::HealthChecks::DbCheck do
+RSpec.describe Gitlab::HealthChecks::DbCheck do
include_examples 'simple_check', 'db_ping', 'Db', '1'
end
diff --git a/spec/lib/gitlab/health_checks/gitaly_check_spec.rb b/spec/lib/gitlab/health_checks/gitaly_check_spec.rb
index d4ce16ce6fc..7c346e3eb69 100644
--- a/spec/lib/gitlab/health_checks/gitaly_check_spec.rb
+++ b/spec/lib/gitlab/health_checks/gitaly_check_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::HealthChecks::GitalyCheck do
+RSpec.describe Gitlab::HealthChecks::GitalyCheck do
let(:result_class) { Gitlab::HealthChecks::Result }
let(:repository_storages) { ['default'] }
diff --git a/spec/lib/gitlab/health_checks/master_check_spec.rb b/spec/lib/gitlab/health_checks/master_check_spec.rb
index dcfc733d5ad..1c1efe178e2 100644
--- a/spec/lib/gitlab/health_checks/master_check_spec.rb
+++ b/spec/lib/gitlab/health_checks/master_check_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require_relative './simple_check_shared'
-describe Gitlab::HealthChecks::MasterCheck do
+RSpec.describe Gitlab::HealthChecks::MasterCheck do
let(:result_class) { Gitlab::HealthChecks::Result }
before do
diff --git a/spec/lib/gitlab/health_checks/probes/collection_spec.rb b/spec/lib/gitlab/health_checks/probes/collection_spec.rb
index d8c411fa27b..03138e936aa 100644
--- a/spec/lib/gitlab/health_checks/probes/collection_spec.rb
+++ b/spec/lib/gitlab/health_checks/probes/collection_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::HealthChecks::Probes::Collection do
+RSpec.describe Gitlab::HealthChecks::Probes::Collection do
let(:readiness) { described_class.new(*checks) }
describe '#execute' do
@@ -47,6 +47,20 @@ describe Gitlab::HealthChecks::Probes::Collection do
status: 'failed', message: 'check error')
end
end
+
+ context 'when check raises exception not handled inside the check' do
+ before do
+ expect(Gitlab::HealthChecks::Redis::RedisCheck).to receive(:readiness).and_raise(
+ ::Redis::CannotConnectError, 'Redis down')
+ end
+
+ it 'responds with failure including the exception info' do
+ expect(subject.http_status).to eq(500)
+
+ expect(subject.json[:status]).to eq('failed')
+ expect(subject.json[:message]).to eq('Redis::CannotConnectError : Redis down')
+ end
+ end
end
context 'without checks' do
diff --git a/spec/lib/gitlab/health_checks/puma_check_spec.rb b/spec/lib/gitlab/health_checks/puma_check_spec.rb
index 93ef81978a8..0bd0965518f 100644
--- a/spec/lib/gitlab/health_checks/puma_check_spec.rb
+++ b/spec/lib/gitlab/health_checks/puma_check_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::HealthChecks::PumaCheck do
+RSpec.describe Gitlab::HealthChecks::PumaCheck do
let(:result_class) { Gitlab::HealthChecks::Result }
let(:readiness) { described_class.readiness }
let(:metrics) { described_class.metrics }
diff --git a/spec/lib/gitlab/health_checks/redis/cache_check_spec.rb b/spec/lib/gitlab/health_checks/redis/cache_check_spec.rb
index aaf474d7eeb..c44bd2ed585 100644
--- a/spec/lib/gitlab/health_checks/redis/cache_check_spec.rb
+++ b/spec/lib/gitlab/health_checks/redis/cache_check_spec.rb
@@ -3,6 +3,6 @@
require 'spec_helper'
require_relative '../simple_check_shared'
-describe Gitlab::HealthChecks::Redis::CacheCheck do
+RSpec.describe Gitlab::HealthChecks::Redis::CacheCheck do
include_examples 'simple_check', 'redis_cache_ping', 'RedisCache', 'PONG'
end
diff --git a/spec/lib/gitlab/health_checks/redis/queues_check_spec.rb b/spec/lib/gitlab/health_checks/redis/queues_check_spec.rb
index f4b5e18da2a..3882e7db9d9 100644
--- a/spec/lib/gitlab/health_checks/redis/queues_check_spec.rb
+++ b/spec/lib/gitlab/health_checks/redis/queues_check_spec.rb
@@ -3,6 +3,6 @@
require 'spec_helper'
require_relative '../simple_check_shared'
-describe Gitlab::HealthChecks::Redis::QueuesCheck do
+RSpec.describe Gitlab::HealthChecks::Redis::QueuesCheck do
include_examples 'simple_check', 'redis_queues_ping', 'RedisQueues', 'PONG'
end
diff --git a/spec/lib/gitlab/health_checks/redis/redis_check_spec.rb b/spec/lib/gitlab/health_checks/redis/redis_check_spec.rb
index ae7ee0d0859..43e890a6c4f 100644
--- a/spec/lib/gitlab/health_checks/redis/redis_check_spec.rb
+++ b/spec/lib/gitlab/health_checks/redis/redis_check_spec.rb
@@ -3,6 +3,6 @@
require 'spec_helper'
require_relative '../simple_check_shared'
-describe Gitlab::HealthChecks::Redis::RedisCheck do
+RSpec.describe Gitlab::HealthChecks::Redis::RedisCheck do
include_examples 'simple_check', 'redis_ping', 'Redis', 'PONG'
end
diff --git a/spec/lib/gitlab/health_checks/redis/shared_state_check_spec.rb b/spec/lib/gitlab/health_checks/redis/shared_state_check_spec.rb
index 3e92b072254..25917741a1c 100644
--- a/spec/lib/gitlab/health_checks/redis/shared_state_check_spec.rb
+++ b/spec/lib/gitlab/health_checks/redis/shared_state_check_spec.rb
@@ -3,6 +3,6 @@
require 'spec_helper'
require_relative '../simple_check_shared'
-describe Gitlab::HealthChecks::Redis::SharedStateCheck do
+RSpec.describe Gitlab::HealthChecks::Redis::SharedStateCheck do
include_examples 'simple_check', 'redis_shared_state_ping', 'RedisSharedState', 'PONG'
end
diff --git a/spec/lib/gitlab/health_checks/simple_check_shared.rb b/spec/lib/gitlab/health_checks/simple_check_shared.rb
index 3d0f9b3cf7a..591a11d5ab6 100644
--- a/spec/lib/gitlab/health_checks/simple_check_shared.rb
+++ b/spec/lib/gitlab/health_checks/simple_check_shared.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-shared_context 'simple_check' do |metrics_prefix, check_name, success_result|
+RSpec.shared_context 'simple_check' do |metrics_prefix, check_name, success_result|
describe '#metrics' do
subject { described_class.metrics }
diff --git a/spec/lib/gitlab/health_checks/unicorn_check_spec.rb b/spec/lib/gitlab/health_checks/unicorn_check_spec.rb
index 7c57b6f1ca5..1cc44016002 100644
--- a/spec/lib/gitlab/health_checks/unicorn_check_spec.rb
+++ b/spec/lib/gitlab/health_checks/unicorn_check_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::HealthChecks::UnicornCheck do
+RSpec.describe Gitlab::HealthChecks::UnicornCheck do
let(:result_class) { Gitlab::HealthChecks::Result }
let(:readiness) { described_class.readiness }
let(:metrics) { described_class.metrics }
diff --git a/spec/lib/gitlab/highlight_spec.rb b/spec/lib/gitlab/highlight_spec.rb
index 2140cbae488..9271b868e36 100644
--- a/spec/lib/gitlab/highlight_spec.rb
+++ b/spec/lib/gitlab/highlight_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Highlight do
+RSpec.describe Gitlab::Highlight do
include RepoHelpers
let(:project) { create(:project, :repository) }
diff --git a/spec/lib/gitlab/hook_data/base_builder_spec.rb b/spec/lib/gitlab/hook_data/base_builder_spec.rb
index 4c3fd854c09..5d9d9fbffe2 100644
--- a/spec/lib/gitlab/hook_data/base_builder_spec.rb
+++ b/spec/lib/gitlab/hook_data/base_builder_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::HookData::BaseBuilder do
+RSpec.describe Gitlab::HookData::BaseBuilder do
describe '#absolute_image_urls' do
let(:subclass) do
Class.new(described_class) do
diff --git a/spec/lib/gitlab/hook_data/issuable_builder_spec.rb b/spec/lib/gitlab/hook_data/issuable_builder_spec.rb
index afbc48e9ca2..50f3a4776be 100644
--- a/spec/lib/gitlab/hook_data/issuable_builder_spec.rb
+++ b/spec/lib/gitlab/hook_data/issuable_builder_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::HookData::IssuableBuilder do
+RSpec.describe Gitlab::HookData::IssuableBuilder do
let_it_be(:user) { create(:user) }
# This shared example requires a `builder` and `user` variable
diff --git a/spec/lib/gitlab/hook_data/issue_builder_spec.rb b/spec/lib/gitlab/hook_data/issue_builder_spec.rb
index 1f681faa8f7..8a2395d70b2 100644
--- a/spec/lib/gitlab/hook_data/issue_builder_spec.rb
+++ b/spec/lib/gitlab/hook_data/issue_builder_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::HookData::IssueBuilder do
+RSpec.describe Gitlab::HookData::IssueBuilder do
let_it_be(:label) { create(:label) }
let_it_be(:issue) { create(:labeled_issue, labels: [label], project: label.project) }
let(:builder) { described_class.new(issue) }
diff --git a/spec/lib/gitlab/hook_data/merge_request_builder_spec.rb b/spec/lib/gitlab/hook_data/merge_request_builder_spec.rb
index 67fa0a7426a..fede7f273f1 100644
--- a/spec/lib/gitlab/hook_data/merge_request_builder_spec.rb
+++ b/spec/lib/gitlab/hook_data/merge_request_builder_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::HookData::MergeRequestBuilder do
+RSpec.describe Gitlab::HookData::MergeRequestBuilder do
let_it_be(:merge_request) { create(:merge_request) }
let(:builder) { described_class.new(merge_request) }
diff --git a/spec/lib/gitlab/http_connection_adapter_spec.rb b/spec/lib/gitlab/http_connection_adapter_spec.rb
index 1532fd1103e..389bc1a85f4 100644
--- a/spec/lib/gitlab/http_connection_adapter_spec.rb
+++ b/spec/lib/gitlab/http_connection_adapter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::HTTPConnectionAdapter do
+RSpec.describe Gitlab::HTTPConnectionAdapter do
include StubRequests
describe '#connection' do
diff --git a/spec/lib/gitlab/http_io_spec.rb b/spec/lib/gitlab/http_io_spec.rb
index e53c361e169..5ba0cb5e686 100644
--- a/spec/lib/gitlab/http_io_spec.rb
+++ b/spec/lib/gitlab/http_io_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::HttpIO do
+RSpec.describe Gitlab::HttpIO do
include HttpIOHelpers
let(:http_io) { described_class.new(url, size) }
diff --git a/spec/lib/gitlab/http_spec.rb b/spec/lib/gitlab/http_spec.rb
index 85cfc8e2852..09da94e7559 100644
--- a/spec/lib/gitlab/http_spec.rb
+++ b/spec/lib/gitlab/http_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::HTTP do
+RSpec.describe Gitlab::HTTP do
include StubRequests
context 'when allow_local_requests' do
diff --git a/spec/lib/gitlab/i18n/metadata_entry_spec.rb b/spec/lib/gitlab/i18n/metadata_entry_spec.rb
index 2d8bb538681..2f8816e62cc 100644
--- a/spec/lib/gitlab/i18n/metadata_entry_spec.rb
+++ b/spec/lib/gitlab/i18n/metadata_entry_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::I18n::MetadataEntry do
+RSpec.describe Gitlab::I18n::MetadataEntry do
describe '#expected_forms' do
it 'returns the number of plurals' do
data = {
diff --git a/spec/lib/gitlab/i18n/po_linter_spec.rb b/spec/lib/gitlab/i18n/po_linter_spec.rb
index 2ab363ee45c..5dfc9d3613c 100644
--- a/spec/lib/gitlab/i18n/po_linter_spec.rb
+++ b/spec/lib/gitlab/i18n/po_linter_spec.rb
@@ -5,7 +5,7 @@ require 'simple_po_parser'
# Disabling this cop to allow for multi-language examples in comments
# rubocop:disable Style/AsciiComments
-describe Gitlab::I18n::PoLinter do
+RSpec.describe Gitlab::I18n::PoLinter do
let(:linter) { described_class.new(po_path) }
let(:po_path) { 'spec/fixtures/valid.po' }
diff --git a/spec/lib/gitlab/i18n/translation_entry_spec.rb b/spec/lib/gitlab/i18n/translation_entry_spec.rb
index 880da38052e..76879f75bec 100644
--- a/spec/lib/gitlab/i18n/translation_entry_spec.rb
+++ b/spec/lib/gitlab/i18n/translation_entry_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::I18n::TranslationEntry do
+RSpec.describe Gitlab::I18n::TranslationEntry do
describe '#singular_translation' do
it 'returns the normal `msgstr` for translations without plural' do
data = { msgid: 'Hello world', msgstr: 'Bonjour monde' }
diff --git a/spec/lib/gitlab/i18n_spec.rb b/spec/lib/gitlab/i18n_spec.rb
index 2664423af88..592adadc362 100644
--- a/spec/lib/gitlab/i18n_spec.rb
+++ b/spec/lib/gitlab/i18n_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::I18n do
+RSpec.describe Gitlab::I18n do
let(:user) { create(:user, preferred_language: 'es') }
describe '.locale=' do
diff --git a/spec/lib/gitlab/identifier_spec.rb b/spec/lib/gitlab/identifier_spec.rb
index 9c7972d4bde..cc8d68e5c18 100644
--- a/spec/lib/gitlab/identifier_spec.rb
+++ b/spec/lib/gitlab/identifier_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Identifier do
+RSpec.describe Gitlab::Identifier do
let(:identifier) do
Class.new { include Gitlab::Identifier }.new
end
diff --git a/spec/lib/gitlab/import/database_helpers_spec.rb b/spec/lib/gitlab/import/database_helpers_spec.rb
index 3ac34455177..d56e05df5d7 100644
--- a/spec/lib/gitlab/import/database_helpers_spec.rb
+++ b/spec/lib/gitlab/import/database_helpers_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Import::DatabaseHelpers do
+RSpec.describe Gitlab::Import::DatabaseHelpers do
let(:database_helper) do
Class.new do
include Gitlab::Import::DatabaseHelpers
diff --git a/spec/lib/gitlab/import/merge_request_creator_spec.rb b/spec/lib/gitlab/import/merge_request_creator_spec.rb
index 48a57f9b251..9aedca40f1b 100644
--- a/spec/lib/gitlab/import/merge_request_creator_spec.rb
+++ b/spec/lib/gitlab/import/merge_request_creator_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Import::MergeRequestCreator do
+RSpec.describe Gitlab::Import::MergeRequestCreator do
let(:project) { create(:project, :repository) }
subject { described_class.new(project) }
diff --git a/spec/lib/gitlab/import/merge_request_helpers_spec.rb b/spec/lib/gitlab/import/merge_request_helpers_spec.rb
index d81251c4a43..f858ab934bb 100644
--- a/spec/lib/gitlab/import/merge_request_helpers_spec.rb
+++ b/spec/lib/gitlab/import/merge_request_helpers_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Import::MergeRequestHelpers, type: :helper do
+RSpec.describe Gitlab::Import::MergeRequestHelpers, type: :helper do
let_it_be(:project) { create(:project, :repository) }
let_it_be(:user) { create(:user) }
diff --git a/spec/lib/gitlab/import/metrics_spec.rb b/spec/lib/gitlab/import/metrics_spec.rb
index 0799d19fcef..0a912427014 100644
--- a/spec/lib/gitlab/import/metrics_spec.rb
+++ b/spec/lib/gitlab/import/metrics_spec.rb
@@ -2,55 +2,39 @@
require 'spec_helper'
-describe Gitlab::Import::Metrics do
- let(:importer_stub) do
- Class.new do
- prepend Gitlab::Import::Metrics
-
- Gitlab::Import::Metrics.measure :execute, metrics: {
- importer_counter: {
- type: :counter,
- description: 'description'
- },
- importer_histogram: {
- type: :histogram,
- labels: { importer: 'importer' },
- description: 'description'
- }
- }
-
- def execute
- true
- end
+RSpec.describe Gitlab::Import::Metrics do
+ let(:importer) { :test_importer }
+ let(:project) { create(:project) }
+ let(:histogram) { double(:histogram) }
+ let(:counter) { double(:counter) }
+
+ subject { described_class.new(importer, project) }
+
+ describe '#report_import_time' do
+ before do
+ allow(Gitlab::Metrics).to receive(:counter) { counter }
+ allow(Gitlab::Metrics).to receive(:histogram) { histogram }
+ allow(counter).to receive(:increment)
+ allow(counter).to receive(:observe)
end
- end
-
- subject { importer_stub.new.execute }
- describe '#execute' do
- let(:counter) { double(:counter) }
- let(:histogram) { double(:histogram) }
+ it 'emits importer metrics' do
+ expect(Gitlab::Metrics).to receive(:counter).with(
+ :test_importer_imported_projects_total,
+ 'The number of imported projects'
+ )
- it 'increments counter metric' do
- expect(Gitlab::Metrics)
- .to receive(:counter)
- .with(:importer_counter, 'description')
- .and_return(counter)
+ expect(Gitlab::Metrics).to receive(:histogram).with(
+ :test_importer_total_duration_seconds,
+ 'Total time spent importing projects, in seconds',
+ {},
+ described_class::IMPORT_DURATION_BUCKETS
+ )
expect(counter).to receive(:increment)
+ expect(histogram).to receive(:observe).with({ importer: :test_importer }, anything)
- subject
- end
-
- it 'measures method duration and reports histogram metric' do
- expect(Gitlab::Metrics)
- .to receive(:histogram)
- .with(:importer_histogram, 'description')
- .and_return(histogram)
-
- expect(histogram).to receive(:observe).with({ importer: 'importer' }, anything)
-
- subject
+ subject.track_finished_import
end
end
end
diff --git a/spec/lib/gitlab/import/set_async_jid_spec.rb b/spec/lib/gitlab/import/set_async_jid_spec.rb
index d2933cfd371..6931a7a953d 100644
--- a/spec/lib/gitlab/import/set_async_jid_spec.rb
+++ b/spec/lib/gitlab/import/set_async_jid_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe Gitlab::Import::SetAsyncJid do
+RSpec.describe Gitlab::Import::SetAsyncJid do
describe '.set_jid', :clean_gitlab_redis_shared_state do
let(:project) { create(:project, :import_scheduled) }
diff --git a/spec/lib/gitlab/import_export/after_export_strategies/base_after_export_strategy_spec.rb b/spec/lib/gitlab/import_export/after_export_strategies/base_after_export_strategy_spec.rb
index 1631de393b5..fc794f11499 100644
--- a/spec/lib/gitlab/import_export/after_export_strategies/base_after_export_strategy_spec.rb
+++ b/spec/lib/gitlab/import_export/after_export_strategies/base_after_export_strategy_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::ImportExport::AfterExportStrategies::BaseAfterExportStrategy do
+RSpec.describe Gitlab::ImportExport::AfterExportStrategies::BaseAfterExportStrategy do
before do
allow_next_instance_of(ProjectExportWorker) do |job|
allow(job).to receive(:jid).and_return(SecureRandom.hex(8))
diff --git a/spec/lib/gitlab/import_export/after_export_strategies/web_upload_strategy_spec.rb b/spec/lib/gitlab/import_export/after_export_strategies/web_upload_strategy_spec.rb
index 7792daed99c..38f1d48798b 100644
--- a/spec/lib/gitlab/import_export/after_export_strategies/web_upload_strategy_spec.rb
+++ b/spec/lib/gitlab/import_export/after_export_strategies/web_upload_strategy_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::ImportExport::AfterExportStrategies::WebUploadStrategy do
+RSpec.describe Gitlab::ImportExport::AfterExportStrategies::WebUploadStrategy do
include StubRequests
before do
diff --git a/spec/lib/gitlab/import_export/after_export_strategy_builder_spec.rb b/spec/lib/gitlab/import_export/after_export_strategy_builder_spec.rb
index 9fe9e2eb73d..91a3dce0b4e 100644
--- a/spec/lib/gitlab/import_export/after_export_strategy_builder_spec.rb
+++ b/spec/lib/gitlab/import_export/after_export_strategy_builder_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::ImportExport::AfterExportStrategyBuilder do
+RSpec.describe Gitlab::ImportExport::AfterExportStrategyBuilder do
let!(:strategies_namespace) { 'Gitlab::ImportExport::AfterExportStrategies' }
describe '.build!' do
diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml
index ef9321dc1fc..02500778426 100644
--- a/spec/lib/gitlab/import_export/all_models.yml
+++ b/spec/lib/gitlab/import_export/all_models.yml
@@ -31,6 +31,8 @@ issues:
- closed_by
- epic_issue
- epic
+- feature_flag_issues
+- feature_flags
- designs
- design_versions
- description_versions
@@ -95,6 +97,7 @@ snippets:
- user_agent_detail
- user_mentions
- snippet_repository
+- statistics
releases:
- author
- project
@@ -217,13 +220,15 @@ ci_pipelines:
- parent_pipeline
- downstream_bridges
- job_artifacts
-- vulnerabilities_occurrence_pipelines
+- vulnerabilities_finding_pipelines
- vulnerability_findings
- pipeline_config
- security_scans
- daily_build_group_report_results
- latest_builds
- daily_report_results
+- latest_builds_report_results
+- messages
ci_refs:
- project
- ci_pipelines
@@ -317,6 +322,7 @@ project:
- last_event
- services
- campfire_service
+- confluence_service
- discord_service
- drone_ci_service
- emails_on_push_service
@@ -458,6 +464,8 @@ project:
- vulnerability_feedback
- vulnerability_identifiers
- vulnerability_scanners
+- dast_site_profiles
+- dast_sites
- operations_feature_flags
- operations_feature_flags_client
- operations_feature_flags_user_lists
@@ -465,6 +473,7 @@ project:
- prometheus_alert_events
- self_managed_prometheus_alert_events
- software_license_policies
+- software_licenses
- project_registry
- packages
- package_files
@@ -505,6 +514,7 @@ project:
- freeze_periods
- webex_teams_service
- build_report_results
+- vulnerability_statistic
award_emoji:
- awardable
- user
@@ -569,6 +579,9 @@ self_managed_prometheus_alert_events:
epic_issues:
- issue
- epic
+feature_flag_issues:
+- issue
+- feature_flag
tracing_setting:
- project
reviews:
diff --git a/spec/lib/gitlab/import_export/attribute_cleaner_spec.rb b/spec/lib/gitlab/import_export/attribute_cleaner_spec.rb
index 65e99c0c3b8..733be7fc226 100644
--- a/spec/lib/gitlab/import_export/attribute_cleaner_spec.rb
+++ b/spec/lib/gitlab/import_export/attribute_cleaner_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::ImportExport::AttributeCleaner do
+RSpec.describe Gitlab::ImportExport::AttributeCleaner do
let(:relation_class) { double('relation_class').as_null_object }
let(:unsafe_hash) do
{
diff --git a/spec/lib/gitlab/import_export/attribute_configuration_spec.rb b/spec/lib/gitlab/import_export/attribute_configuration_spec.rb
index f97dafc6bf9..0581f07dd3f 100644
--- a/spec/lib/gitlab/import_export/attribute_configuration_spec.rb
+++ b/spec/lib/gitlab/import_export/attribute_configuration_spec.rb
@@ -9,7 +9,7 @@ require 'spec_helper'
# to be included as part of the export, or blacklist them using the import_export.yml configuration file.
# Likewise, new models added to import_export.yml, will need to be added with their correspondent attributes
# to this spec.
-describe 'Import/Export attribute configuration' do
+RSpec.describe 'Import/Export attribute configuration' do
include ConfigurationHelper
let(:safe_attributes_file) { 'spec/lib/gitlab/import_export/safe_model_attributes.yml' }
diff --git a/spec/lib/gitlab/import_export/attributes_finder_spec.rb b/spec/lib/gitlab/import_export/attributes_finder_spec.rb
index 3cbc1375d6e..7f6ebf577af 100644
--- a/spec/lib/gitlab/import_export/attributes_finder_spec.rb
+++ b/spec/lib/gitlab/import_export/attributes_finder_spec.rb
@@ -2,7 +2,7 @@
require 'fast_spec_helper'
-describe Gitlab::ImportExport::AttributesFinder do
+RSpec.describe Gitlab::ImportExport::AttributesFinder do
describe '#find_root' do
subject { described_class.new(config: config).find_root(model_key) }
diff --git a/spec/lib/gitlab/import_export/attributes_permitter_spec.rb b/spec/lib/gitlab/import_export/attributes_permitter_spec.rb
index d6217811b9c..0c1b1cd74bf 100644
--- a/spec/lib/gitlab/import_export/attributes_permitter_spec.rb
+++ b/spec/lib/gitlab/import_export/attributes_permitter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::ImportExport::AttributesPermitter do
+RSpec.describe Gitlab::ImportExport::AttributesPermitter do
let(:yml_config) do
<<-EOF
tree:
diff --git a/spec/lib/gitlab/import_export/avatar_restorer_spec.rb b/spec/lib/gitlab/import_export/avatar_restorer_spec.rb
index 662e1a5eaab..994132239b9 100644
--- a/spec/lib/gitlab/import_export/avatar_restorer_spec.rb
+++ b/spec/lib/gitlab/import_export/avatar_restorer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::ImportExport::AvatarRestorer do
+RSpec.describe Gitlab::ImportExport::AvatarRestorer do
include UploadHelpers
let(:shared) { project.import_export_shared }
diff --git a/spec/lib/gitlab/import_export/avatar_saver_spec.rb b/spec/lib/gitlab/import_export/avatar_saver_spec.rb
index d2349e47c0a..334d930c47c 100644
--- a/spec/lib/gitlab/import_export/avatar_saver_spec.rb
+++ b/spec/lib/gitlab/import_export/avatar_saver_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::ImportExport::AvatarSaver do
+RSpec.describe Gitlab::ImportExport::AvatarSaver do
let(:shared) { project.import_export_shared }
let(:export_path) { "#{Dir.tmpdir}/project_tree_saver_spec" }
let(:project_with_avatar) { create(:project, avatar: fixture_file_upload("spec/fixtures/dk.png", "image/png")) }
diff --git a/spec/lib/gitlab/import_export/base/object_builder_spec.rb b/spec/lib/gitlab/import_export/base/object_builder_spec.rb
index e5242ae0bfc..d560c8ea5a7 100644
--- a/spec/lib/gitlab/import_export/base/object_builder_spec.rb
+++ b/spec/lib/gitlab/import_export/base/object_builder_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::ImportExport::Base::ObjectBuilder do
+RSpec.describe Gitlab::ImportExport::Base::ObjectBuilder do
let(:project) do
create(:project, :repository,
:builds_disabled,
diff --git a/spec/lib/gitlab/import_export/base/relation_factory_spec.rb b/spec/lib/gitlab/import_export/base/relation_factory_spec.rb
index 50d93763ad6..09e6e5a03bb 100644
--- a/spec/lib/gitlab/import_export/base/relation_factory_spec.rb
+++ b/spec/lib/gitlab/import_export/base/relation_factory_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::ImportExport::Base::RelationFactory do
+RSpec.describe Gitlab::ImportExport::Base::RelationFactory do
let(:user) { create(:admin) }
let(:project) { create(:project) }
let(:members_mapper) { double('members_mapper').as_null_object }
diff --git a/spec/lib/gitlab/import_export/command_line_util_spec.rb b/spec/lib/gitlab/import_export/command_line_util_spec.rb
index 8e5e0aefac0..b00a2597681 100644
--- a/spec/lib/gitlab/import_export/command_line_util_spec.rb
+++ b/spec/lib/gitlab/import_export/command_line_util_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::ImportExport::CommandLineUtil do
+RSpec.describe Gitlab::ImportExport::CommandLineUtil do
include ExportFileHelper
let(:path) { "#{Dir.tmpdir}/symlink_test" }
diff --git a/spec/lib/gitlab/import_export/config_spec.rb b/spec/lib/gitlab/import_export/config_spec.rb
index f09a29b84db..40cf75779b6 100644
--- a/spec/lib/gitlab/import_export/config_spec.rb
+++ b/spec/lib/gitlab/import_export/config_spec.rb
@@ -3,7 +3,7 @@
require 'fast_spec_helper'
require 'rspec-parameterized'
-describe Gitlab::ImportExport::Config do
+RSpec.describe Gitlab::ImportExport::Config do
let(:yaml_file) { described_class.new }
describe '#to_h' do
diff --git a/spec/lib/gitlab/import_export/design_repo_restorer_spec.rb b/spec/lib/gitlab/import_export/design_repo_restorer_spec.rb
index 5662b8af280..b311a02833c 100644
--- a/spec/lib/gitlab/import_export/design_repo_restorer_spec.rb
+++ b/spec/lib/gitlab/import_export/design_repo_restorer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::ImportExport::DesignRepoRestorer do
+RSpec.describe Gitlab::ImportExport::DesignRepoRestorer do
include GitHelpers
describe 'bundle a design Git repo' do
diff --git a/spec/lib/gitlab/import_export/design_repo_saver_spec.rb b/spec/lib/gitlab/import_export/design_repo_saver_spec.rb
index bff48e8b52a..2575d209db5 100644
--- a/spec/lib/gitlab/import_export/design_repo_saver_spec.rb
+++ b/spec/lib/gitlab/import_export/design_repo_saver_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::ImportExport::DesignRepoSaver do
+RSpec.describe Gitlab::ImportExport::DesignRepoSaver do
describe 'bundle a design Git repo' do
let_it_be(:user) { create(:user) }
let_it_be(:design) { create(:design, :with_file, versions_count: 1) }
diff --git a/spec/lib/gitlab/import_export/error_spec.rb b/spec/lib/gitlab/import_export/error_spec.rb
index 067f7049097..015133a399b 100644
--- a/spec/lib/gitlab/import_export/error_spec.rb
+++ b/spec/lib/gitlab/import_export/error_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::ImportExport::Error do
+RSpec.describe Gitlab::ImportExport::Error do
describe '.permission_error' do
subject(:error) do
described_class.permission_error(user, importable)
diff --git a/spec/lib/gitlab/import_export/fast_hash_serializer_spec.rb b/spec/lib/gitlab/import_export/fast_hash_serializer_spec.rb
index 916ed692a05..5b6be0b3198 100644
--- a/spec/lib/gitlab/import_export/fast_hash_serializer_spec.rb
+++ b/spec/lib/gitlab/import_export/fast_hash_serializer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::ImportExport::FastHashSerializer do
+RSpec.describe Gitlab::ImportExport::FastHashSerializer do
# FastHashSerializer#execute generates the hash which is not easily accessible
# and includes `JSONBatchRelation` items which are serialized at this point.
# Wrapping the result into JSON generating/parsing is for making
@@ -175,14 +175,6 @@ describe Gitlab::ImportExport::FastHashSerializer do
expect(subject['merge_requests'].first['resource_label_events']).not_to be_empty
end
- it 'saves the correct service type' do
- expect(subject['services'].first['type']).to eq('CustomIssueTrackerService')
- end
-
- it 'saves the properties for a service' do
- expect(subject['services'].first['properties']).to eq('one' => 'value')
- end
-
it 'has project feature' do
project_feature = subject['project_feature']
expect(project_feature).not_to be_empty
diff --git a/spec/lib/gitlab/import_export/file_importer_spec.rb b/spec/lib/gitlab/import_export/file_importer_spec.rb
index 7c54c5f2da1..47485cc7edb 100644
--- a/spec/lib/gitlab/import_export/file_importer_spec.rb
+++ b/spec/lib/gitlab/import_export/file_importer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::ImportExport::FileImporter do
+RSpec.describe Gitlab::ImportExport::FileImporter do
include ExportFileHelper
let(:shared) { Gitlab::ImportExport::Shared.new(nil) }
diff --git a/spec/lib/gitlab/import_export/fork_spec.rb b/spec/lib/gitlab/import_export/fork_spec.rb
index bb79331efac..ef7394053b9 100644
--- a/spec/lib/gitlab/import_export/fork_spec.rb
+++ b/spec/lib/gitlab/import_export/fork_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'forked project import' do
+RSpec.describe 'forked project import' do
include ProjectForksHelper
let(:user) { create(:user) }
diff --git a/spec/lib/gitlab/import_export/group/legacy_tree_restorer_spec.rb b/spec/lib/gitlab/import_export/group/legacy_tree_restorer_spec.rb
index 4c926da1436..bfcd4994995 100644
--- a/spec/lib/gitlab/import_export/group/legacy_tree_restorer_spec.rb
+++ b/spec/lib/gitlab/import_export/group/legacy_tree_restorer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::ImportExport::Group::LegacyTreeRestorer do
+RSpec.describe Gitlab::ImportExport::Group::LegacyTreeRestorer do
include ImportExport::CommonUtil
let(:shared) { Gitlab::ImportExport::Shared.new(group) }
diff --git a/spec/lib/gitlab/import_export/group/legacy_tree_saver_spec.rb b/spec/lib/gitlab/import_export/group/legacy_tree_saver_spec.rb
index a0490c255aa..6b324b952dc 100644
--- a/spec/lib/gitlab/import_export/group/legacy_tree_saver_spec.rb
+++ b/spec/lib/gitlab/import_export/group/legacy_tree_saver_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::ImportExport::Group::LegacyTreeSaver do
+RSpec.describe Gitlab::ImportExport::Group::LegacyTreeSaver do
describe 'saves the group tree into a json object' do
let(:shared) { Gitlab::ImportExport::Shared.new(group) }
let(:group_tree_saver) { described_class.new(group: group, current_user: user, shared: shared) }
diff --git a/spec/lib/gitlab/import_export/group/object_builder_spec.rb b/spec/lib/gitlab/import_export/group/object_builder_spec.rb
index 781670b0aa5..028bd5463a1 100644
--- a/spec/lib/gitlab/import_export/group/object_builder_spec.rb
+++ b/spec/lib/gitlab/import_export/group/object_builder_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::ImportExport::Group::ObjectBuilder do
+RSpec.describe Gitlab::ImportExport::Group::ObjectBuilder do
let(:group) { create(:group) }
let(:base_attributes) do
{
diff --git a/spec/lib/gitlab/import_export/group/relation_factory_spec.rb b/spec/lib/gitlab/import_export/group/relation_factory_spec.rb
index 332648d5c89..eb9a3fa9bd8 100644
--- a/spec/lib/gitlab/import_export/group/relation_factory_spec.rb
+++ b/spec/lib/gitlab/import_export/group/relation_factory_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::ImportExport::Group::RelationFactory do
+RSpec.describe Gitlab::ImportExport::Group::RelationFactory do
let(:group) { create(:group) }
let(:members_mapper) { double('members_mapper').as_null_object }
let(:user) { create(:admin) }
diff --git a/spec/lib/gitlab/import_export/group/tree_restorer_spec.rb b/spec/lib/gitlab/import_export/group/tree_restorer_spec.rb
index 327f36c664e..6cc16ee9cbb 100644
--- a/spec/lib/gitlab/import_export/group/tree_restorer_spec.rb
+++ b/spec/lib/gitlab/import_export/group/tree_restorer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::ImportExport::Group::TreeRestorer do
+RSpec.describe Gitlab::ImportExport::Group::TreeRestorer do
include ImportExport::CommonUtil
describe 'restore group tree' do
diff --git a/spec/lib/gitlab/import_export/group/tree_saver_spec.rb b/spec/lib/gitlab/import_export/group/tree_saver_spec.rb
index 06e8484a3cb..908896e4891 100644
--- a/spec/lib/gitlab/import_export/group/tree_saver_spec.rb
+++ b/spec/lib/gitlab/import_export/group/tree_saver_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::ImportExport::Group::TreeSaver do
+RSpec.describe Gitlab::ImportExport::Group::TreeSaver do
describe 'saves the group tree into a json object' do
let_it_be(:user) { create(:user) }
let_it_be(:group) { setup_groups }
diff --git a/spec/lib/gitlab/import_export/hash_util_spec.rb b/spec/lib/gitlab/import_export/hash_util_spec.rb
index b97c6665d0e..467342e6e96 100644
--- a/spec/lib/gitlab/import_export/hash_util_spec.rb
+++ b/spec/lib/gitlab/import_export/hash_util_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::ImportExport::HashUtil do
+RSpec.describe Gitlab::ImportExport::HashUtil do
let(:stringified_array) { [{ 'test' => 1 }] }
let(:stringified_array_with_date) { [{ 'test_date' => '2016-04-06 06:17:44 +0200' }] }
diff --git a/spec/lib/gitlab/import_export/import_export_equivalence_spec.rb b/spec/lib/gitlab/import_export/import_export_equivalence_spec.rb
index 95df9cd0e6e..18f2e8f80d7 100644
--- a/spec/lib/gitlab/import_export/import_export_equivalence_spec.rb
+++ b/spec/lib/gitlab/import_export/import_export_equivalence_spec.rb
@@ -13,7 +13,7 @@ require 'spec_helper'
# - randomly generated fields like tokens
#
# as these are expected to change between import/export cycles.
-describe Gitlab::ImportExport do
+RSpec.describe Gitlab::ImportExport do
include ImportExport::CommonUtil
include ConfigurationHelper
include ImportExport::ProjectTreeExpectations
diff --git a/spec/lib/gitlab/import_export/import_export_spec.rb b/spec/lib/gitlab/import_export/import_export_spec.rb
index 300ba66ee5b..62b4717fc96 100644
--- a/spec/lib/gitlab/import_export/import_export_spec.rb
+++ b/spec/lib/gitlab/import_export/import_export_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::ImportExport do
+RSpec.describe Gitlab::ImportExport do
describe 'export filename' do
let(:group) { create(:group, :nested) }
let(:project) { create(:project, :public, path: 'project-path', namespace: group) }
diff --git a/spec/lib/gitlab/import_export/import_failure_service_spec.rb b/spec/lib/gitlab/import_export/import_failure_service_spec.rb
index 324328181e4..c8bb067d40c 100644
--- a/spec/lib/gitlab/import_export/import_failure_service_spec.rb
+++ b/spec/lib/gitlab/import_export/import_failure_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::ImportExport::ImportFailureService do
+RSpec.describe Gitlab::ImportExport::ImportFailureService do
let(:importable) { create(:project, :builds_enabled, :issues_disabled, name: 'project', path: 'project') }
let(:label) { create(:label) }
let(:subject) { described_class.new(importable) }
diff --git a/spec/lib/gitlab/import_export/import_test_coverage_spec.rb b/spec/lib/gitlab/import_export/import_test_coverage_spec.rb
index c5a7327332e..9737a0f39fc 100644
--- a/spec/lib/gitlab/import_export/import_test_coverage_spec.rb
+++ b/spec/lib/gitlab/import_export/import_test_coverage_spec.rb
@@ -7,7 +7,7 @@ require 'spec_helper'
# Fixture JSONs we use for testing Import such as
# `spec/fixtures/lib/gitlab/import_export/complex/project.json`
# should include these relations being non-empty.
-describe 'Test coverage of the Project Import' do
+RSpec.describe 'Test coverage of the Project Import' do
include ConfigurationHelper
# `muted_relations` is a technical debt.
diff --git a/spec/lib/gitlab/import_export/importer_spec.rb b/spec/lib/gitlab/import_export/importer_spec.rb
index 494f7e3a00d..dc44296321c 100644
--- a/spec/lib/gitlab/import_export/importer_spec.rb
+++ b/spec/lib/gitlab/import_export/importer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::ImportExport::Importer do
+RSpec.describe Gitlab::ImportExport::Importer do
let(:user) { create(:user) }
let(:test_path) { "#{Dir.tmpdir}/importer_spec" }
let(:shared) { project.import_export_shared }
diff --git a/spec/lib/gitlab/import_export/json/legacy_reader/file_spec.rb b/spec/lib/gitlab/import_export/json/legacy_reader/file_spec.rb
index 99932404fd9..9c7f41cbb89 100644
--- a/spec/lib/gitlab/import_export/json/legacy_reader/file_spec.rb
+++ b/spec/lib/gitlab/import_export/json/legacy_reader/file_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require_relative 'shared_example.rb'
-describe Gitlab::ImportExport::JSON::LegacyReader::File do
+RSpec.describe Gitlab::ImportExport::JSON::LegacyReader::File do
it_behaves_like 'import/export json legacy reader' do
let(:valid_path) { 'spec/fixtures/lib/gitlab/import_export/light/project.json' }
let(:data) { valid_path }
diff --git a/spec/lib/gitlab/import_export/json/legacy_reader/hash_spec.rb b/spec/lib/gitlab/import_export/json/legacy_reader/hash_spec.rb
index e793dc7339d..d0899accf59 100644
--- a/spec/lib/gitlab/import_export/json/legacy_reader/hash_spec.rb
+++ b/spec/lib/gitlab/import_export/json/legacy_reader/hash_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require_relative 'shared_example.rb'
-describe Gitlab::ImportExport::JSON::LegacyReader::Hash do
+RSpec.describe Gitlab::ImportExport::JSON::LegacyReader::Hash do
it_behaves_like 'import/export json legacy reader' do
let(:path) { 'spec/fixtures/lib/gitlab/import_export/light/project.json' }
diff --git a/spec/lib/gitlab/import_export/json/legacy_writer_spec.rb b/spec/lib/gitlab/import_export/json/legacy_writer_spec.rb
index 1f39b26e46a..eb7a2d4aa8b 100644
--- a/spec/lib/gitlab/import_export/json/legacy_writer_spec.rb
+++ b/spec/lib/gitlab/import_export/json/legacy_writer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::ImportExport::JSON::LegacyWriter do
+RSpec.describe Gitlab::ImportExport::JSON::LegacyWriter do
let(:path) { "#{Dir.tmpdir}/legacy_writer_spec/test.json" }
subject do
diff --git a/spec/lib/gitlab/import_export/json/ndjson_reader_spec.rb b/spec/lib/gitlab/import_export/json/ndjson_reader_spec.rb
index 34e8b1ddd59..a347d835428 100644
--- a/spec/lib/gitlab/import_export/json/ndjson_reader_spec.rb
+++ b/spec/lib/gitlab/import_export/json/ndjson_reader_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::ImportExport::JSON::NdjsonReader do
+RSpec.describe Gitlab::ImportExport::JSON::NdjsonReader do
include ImportExport::CommonUtil
let(:fixture) { 'spec/fixtures/lib/gitlab/import_export/light/tree' }
diff --git a/spec/lib/gitlab/import_export/json/ndjson_writer_spec.rb b/spec/lib/gitlab/import_export/json/ndjson_writer_spec.rb
index bae3672474c..0af74dee604 100644
--- a/spec/lib/gitlab/import_export/json/ndjson_writer_spec.rb
+++ b/spec/lib/gitlab/import_export/json/ndjson_writer_spec.rb
@@ -2,7 +2,7 @@
require "spec_helper"
-describe Gitlab::ImportExport::JSON::NdjsonWriter do
+RSpec.describe Gitlab::ImportExport::JSON::NdjsonWriter do
include ImportExport::CommonUtil
let(:path) { "#{Dir.tmpdir}/ndjson_writer_spec/tree" }
diff --git a/spec/lib/gitlab/import_export/json/streaming_serializer_spec.rb b/spec/lib/gitlab/import_export/json/streaming_serializer_spec.rb
index 30f8280fda3..eb6b07ce02f 100644
--- a/spec/lib/gitlab/import_export/json/streaming_serializer_spec.rb
+++ b/spec/lib/gitlab/import_export/json/streaming_serializer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::ImportExport::JSON::StreamingSerializer do
+RSpec.describe Gitlab::ImportExport::JSON::StreamingSerializer do
let_it_be(:user) { create(:user) }
let_it_be(:release) { create(:release) }
let_it_be(:group) { create(:group) }
@@ -61,6 +61,20 @@ describe Gitlab::ImportExport::JSON::StreamingSerializer do
subject.execute
end
+
+ context 'relation ordering' do
+ before do
+ create_list(:issue, 5, project: exportable)
+ end
+
+ it 'orders exported issues by primary key' do
+ expected_issues = exportable.issues.reorder(:id).map(&:to_json)
+
+ expect(json_writer).to receive(:write_relation_array).with(exportable_path, :issues, expected_issues)
+
+ subject.execute
+ end
+ end
end
context 'with single relation' do
diff --git a/spec/lib/gitlab/import_export/legacy_relation_tree_saver_spec.rb b/spec/lib/gitlab/import_export/legacy_relation_tree_saver_spec.rb
index 6562aa5b8a6..454cc74b9d4 100644
--- a/spec/lib/gitlab/import_export/legacy_relation_tree_saver_spec.rb
+++ b/spec/lib/gitlab/import_export/legacy_relation_tree_saver_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::ImportExport::LegacyRelationTreeSaver do
+RSpec.describe Gitlab::ImportExport::LegacyRelationTreeSaver do
let(:exportable) { create(:group) }
let(:relation_tree_saver) { described_class.new }
let(:tree) { {} }
diff --git a/spec/lib/gitlab/import_export/lfs_restorer_spec.rb b/spec/lib/gitlab/import_export/lfs_restorer_spec.rb
index a932dc3ee4e..a9f7fb72612 100644
--- a/spec/lib/gitlab/import_export/lfs_restorer_spec.rb
+++ b/spec/lib/gitlab/import_export/lfs_restorer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::ImportExport::LfsRestorer do
+RSpec.describe Gitlab::ImportExport::LfsRestorer do
include UploadHelpers
let(:export_path) { "#{Dir.tmpdir}/lfs_object_restorer_spec" }
diff --git a/spec/lib/gitlab/import_export/lfs_saver_spec.rb b/spec/lib/gitlab/import_export/lfs_saver_spec.rb
index e9d06573e70..db76eb9538b 100644
--- a/spec/lib/gitlab/import_export/lfs_saver_spec.rb
+++ b/spec/lib/gitlab/import_export/lfs_saver_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::ImportExport::LfsSaver do
+RSpec.describe Gitlab::ImportExport::LfsSaver do
let(:shared) { project.import_export_shared }
let(:export_path) { "#{Dir.tmpdir}/project_tree_saver_spec" }
let(:project) { create(:project) }
diff --git a/spec/lib/gitlab/import_export/members_mapper_spec.rb b/spec/lib/gitlab/import_export/members_mapper_spec.rb
index 61e893bfb3c..9755e322221 100644
--- a/spec/lib/gitlab/import_export/members_mapper_spec.rb
+++ b/spec/lib/gitlab/import_export/members_mapper_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::ImportExport::MembersMapper do
+RSpec.describe Gitlab::ImportExport::MembersMapper do
describe 'map members' do
shared_examples 'imports exported members' do
let(:user) { create(:admin) }
diff --git a/spec/lib/gitlab/import_export/merge_request_parser_spec.rb b/spec/lib/gitlab/import_export/merge_request_parser_spec.rb
index 0cdb3c43992..c558c12f581 100644
--- a/spec/lib/gitlab/import_export/merge_request_parser_spec.rb
+++ b/spec/lib/gitlab/import_export/merge_request_parser_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::ImportExport::MergeRequestParser do
+RSpec.describe Gitlab::ImportExport::MergeRequestParser do
include ProjectForksHelper
let(:user) { create(:user) }
diff --git a/spec/lib/gitlab/import_export/model_configuration_spec.rb b/spec/lib/gitlab/import_export/model_configuration_spec.rb
index cfbfe244988..34591122a97 100644
--- a/spec/lib/gitlab/import_export/model_configuration_spec.rb
+++ b/spec/lib/gitlab/import_export/model_configuration_spec.rb
@@ -5,7 +5,7 @@ require 'spec_helper'
# Part of the test security suite for the Import/Export feature
# Finds if a new model has been added that can potentially be part of the Import/Export
# If it finds a new model, it will show a +failure_message+ with the options available.
-describe 'Import/Export model configuration' do
+RSpec.describe 'Import/Export model configuration' do
include ConfigurationHelper
let(:all_models_yml) { 'spec/lib/gitlab/import_export/all_models.yml' }
diff --git a/spec/lib/gitlab/import_export/project/export_task_spec.rb b/spec/lib/gitlab/import_export/project/export_task_spec.rb
index dc8eb54dc14..1048379a5d6 100644
--- a/spec/lib/gitlab/import_export/project/export_task_spec.rb
+++ b/spec/lib/gitlab/import_export/project/export_task_spec.rb
@@ -2,7 +2,7 @@
require 'rake_helper'
-describe Gitlab::ImportExport::Project::ExportTask do
+RSpec.describe Gitlab::ImportExport::Project::ExportTask do
let_it_be(:username) { 'root' }
let(:namespace_path) { username }
let_it_be(:user) { create(:user, username: username) }
diff --git a/spec/lib/gitlab/import_export/project/import_task_spec.rb b/spec/lib/gitlab/import_export/project/import_task_spec.rb
index 7c11161aaa7..90f4501acdc 100644
--- a/spec/lib/gitlab/import_export/project/import_task_spec.rb
+++ b/spec/lib/gitlab/import_export/project/import_task_spec.rb
@@ -2,7 +2,7 @@
require 'rake_helper'
-describe Gitlab::ImportExport::Project::ImportTask, :request_store do
+RSpec.describe Gitlab::ImportExport::Project::ImportTask, :request_store do
let(:username) { 'root' }
let(:namespace_path) { username }
let!(:user) { create(:user, username: username) }
diff --git a/spec/lib/gitlab/import_export/project/object_builder_spec.rb b/spec/lib/gitlab/import_export/project/object_builder_spec.rb
index c9d1410400a..20d882c82be 100644
--- a/spec/lib/gitlab/import_export/project/object_builder_spec.rb
+++ b/spec/lib/gitlab/import_export/project/object_builder_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::ImportExport::Project::ObjectBuilder do
+RSpec.describe Gitlab::ImportExport::Project::ObjectBuilder do
let!(:group) { create(:group, :private) }
let!(:subgroup) { create(:group, :private, parent: group) }
let!(:project) do
diff --git a/spec/lib/gitlab/import_export/project/relation_factory_spec.rb b/spec/lib/gitlab/import_export/project/relation_factory_spec.rb
index 3339129cb8f..31cf2362628 100644
--- a/spec/lib/gitlab/import_export/project/relation_factory_spec.rb
+++ b/spec/lib/gitlab/import_export/project/relation_factory_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::ImportExport::Project::RelationFactory do
+RSpec.describe Gitlab::ImportExport::Project::RelationFactory do
let(:group) { create(:group) }
let(:project) { create(:project, :repository, group: group) }
let(:members_mapper) { double('members_mapper').as_null_object }
diff --git a/spec/lib/gitlab/import_export/project/tree_restorer_spec.rb b/spec/lib/gitlab/import_export/project/tree_restorer_spec.rb
index 867dc37c5c5..6d5604dc40f 100644
--- a/spec/lib/gitlab/import_export/project/tree_restorer_spec.rb
+++ b/spec/lib/gitlab/import_export/project/tree_restorer_spec.rb
@@ -6,7 +6,7 @@ def match_mr1_note(content_regex)
MergeRequest.find_by(title: 'MR1').notes.select { |n| n.note.match(/#{content_regex}/)}.first
end
-describe Gitlab::ImportExport::Project::TreeRestorer do
+RSpec.describe Gitlab::ImportExport::Project::TreeRestorer do
include ImportExport::CommonUtil
using RSpec::Parameterized::TableSyntax
@@ -291,10 +291,6 @@ describe Gitlab::ImportExport::Project::TreeRestorer do
expect(@project.auto_devops.deploy_strategy).to eq('continuous')
end
- it 'restores the correct service' do
- expect(CustomIssueTrackerService.first).not_to be_nil
- end
-
it 'restores zoom meetings' do
meetings = @project.issues.first.zoom_meetings
@@ -553,8 +549,7 @@ describe Gitlab::ImportExport::Project::TreeRestorer do
labels: 2,
label_with_priorities: 'A project label',
milestones: 1,
- first_issue_labels: 1,
- services: 1
+ first_issue_labels: 1
end
context 'when there is an existing build with build token' do
@@ -637,7 +632,6 @@ describe Gitlab::ImportExport::Project::TreeRestorer do
label_with_priorities: 'A project label',
milestones: 1,
first_issue_labels: 1,
- services: 1,
import_failures: 1
it 'records the failures in the database' do
@@ -757,18 +751,6 @@ describe Gitlab::ImportExport::Project::TreeRestorer do
setup_reader(reader)
end
- it 'does not import any templated services' do
- expect(restored_project_json).to eq(true)
-
- expect(project.services.where(template: true).count).to eq(0)
- end
-
- it 'does not import any instance services' do
- expect(restored_project_json).to eq(true)
-
- expect(project.services.where(instance: true).count).to eq(0)
- end
-
it 'imports labels' do
create(:group_label, name: 'Another label', group: project.group)
@@ -972,7 +954,6 @@ describe Gitlab::ImportExport::Project::TreeRestorer do
label_with_priorities: nil,
milestones: 1,
first_issue_labels: 0,
- services: 0,
import_failures: 1
it 'records the failures in the database' do
diff --git a/spec/lib/gitlab/import_export/project/tree_saver_spec.rb b/spec/lib/gitlab/import_export/project/tree_saver_spec.rb
index 533d1097928..40c103eeda6 100644
--- a/spec/lib/gitlab/import_export/project/tree_saver_spec.rb
+++ b/spec/lib/gitlab/import_export/project/tree_saver_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::ImportExport::Project::TreeSaver do
+RSpec.describe Gitlab::ImportExport::Project::TreeSaver do
let_it_be(:export_path) { "#{Dir.tmpdir}/project_tree_saver_spec" }
let_it_be(:exportable_path) { 'project' }
@@ -223,18 +223,6 @@ describe Gitlab::ImportExport::Project::TreeSaver do
it { is_expected.not_to be_empty }
end
- context 'with services' do
- let(:relation_name) { :services }
-
- it 'saves the correct service type' do
- expect(subject.first['type']).to eq('CustomIssueTrackerService')
- end
-
- it 'saves the properties for a service' do
- expect(subject.first['properties']).to eq('one' => 'value')
- end
- end
-
context 'with project_feature' do
let(:relation_name) { :project_feature }
@@ -453,7 +441,6 @@ describe Gitlab::ImportExport::Project::TreeSaver do
create(:resource_label_event, label: group_label, merge_request: merge_request)
create(:event, :created, target: milestone, project: project, author: user)
- create(:service, project: project, type: 'CustomIssueTrackerService', category: 'issue_tracker', properties: { one: 'value' })
create(:project_custom_attribute, project: project)
create(:project_custom_attribute, project: project)
diff --git a/spec/lib/gitlab/import_export/reader_spec.rb b/spec/lib/gitlab/import_export/reader_spec.rb
index e37ad281eb5..8828c7ceb62 100644
--- a/spec/lib/gitlab/import_export/reader_spec.rb
+++ b/spec/lib/gitlab/import_export/reader_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::ImportExport::Reader do
+RSpec.describe Gitlab::ImportExport::Reader do
let(:shared) { Gitlab::ImportExport::Shared.new(nil) }
describe '#project_tree' do
diff --git a/spec/lib/gitlab/import_export/references_configuration_spec.rb b/spec/lib/gitlab/import_export/references_configuration_spec.rb
index 91cf9f964c0..2934d0059ee 100644
--- a/spec/lib/gitlab/import_export/references_configuration_spec.rb
+++ b/spec/lib/gitlab/import_export/references_configuration_spec.rb
@@ -9,7 +9,7 @@ require 'spec_helper'
# or to be blacklisted by using the import_export.yml configuration file.
# Likewise, new models added to import_export.yml, will need to be added with their correspondent relations
# to this spec.
-describe 'Import/Export Project configuration' do
+RSpec.describe 'Import/Export Project configuration' do
include ConfigurationHelper
where(:relation_path, :relation_name) do
diff --git a/spec/lib/gitlab/import_export/relation_tree_restorer_spec.rb b/spec/lib/gitlab/import_export/relation_tree_restorer_spec.rb
index 8fe419da450..ddc96b83208 100644
--- a/spec/lib/gitlab/import_export/relation_tree_restorer_spec.rb
+++ b/spec/lib/gitlab/import_export/relation_tree_restorer_spec.rb
@@ -9,7 +9,7 @@
require 'spec_helper'
-describe Gitlab::ImportExport::RelationTreeRestorer do
+RSpec.describe Gitlab::ImportExport::RelationTreeRestorer do
include ImportExport::CommonUtil
let(:user) { create(:user) }
diff --git a/spec/lib/gitlab/import_export/repo_restorer_spec.rb b/spec/lib/gitlab/import_export/repo_restorer_spec.rb
index d5839589633..ace4449042e 100644
--- a/spec/lib/gitlab/import_export/repo_restorer_spec.rb
+++ b/spec/lib/gitlab/import_export/repo_restorer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::ImportExport::RepoRestorer do
+RSpec.describe Gitlab::ImportExport::RepoRestorer do
include GitHelpers
describe 'bundle a project Git repo' do
diff --git a/spec/lib/gitlab/import_export/repo_saver_spec.rb b/spec/lib/gitlab/import_export/repo_saver_spec.rb
index a95d661ec3c..73d51000c67 100644
--- a/spec/lib/gitlab/import_export/repo_saver_spec.rb
+++ b/spec/lib/gitlab/import_export/repo_saver_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::ImportExport::RepoSaver do
+RSpec.describe Gitlab::ImportExport::RepoSaver do
describe 'bundle a project Git repo' do
let_it_be(:user) { create(:user) }
let!(:project) { create(:project, :repository) }
diff --git a/spec/lib/gitlab/import_export/safe_model_attributes.yml b/spec/lib/gitlab/import_export/safe_model_attributes.yml
index 0d112bfdb2a..2d313b4dcad 100644
--- a/spec/lib/gitlab/import_export/safe_model_attributes.yml
+++ b/spec/lib/gitlab/import_export/safe_model_attributes.yml
@@ -461,36 +461,6 @@ DeployKey:
- public
- can_push
- last_used_at
-Service:
-- id
-- type
-- title
-- project_id
-- created_at
-- updated_at
-- active
-- properties
-- template
-- instance
-- alert_events
-- push_events
-- issues_events
-- commit_events
-- merge_requests_events
-- tag_push_events
-- note_events
-- pipeline_events
-- job_events
-- comment_on_event_enabled
-- comment_detail
-- category
-- default
-- wiki_page_events
-- confidential_issues_events
-- confidential_note_events
-- deployment_events
-- description
-- inherit_from_id
ProjectHook:
- id
- url
@@ -618,6 +588,7 @@ ProtectedBranch::PushAccessLevel:
- updated_at
- user_id
- group_id
+- deploy_key_id
ProtectedBranch::UnprotectAccessLevel:
- id
- protected_branch_id
@@ -705,6 +676,7 @@ ProjectCiCdSetting:
- group_runners_enabled
ProjectSetting:
- allow_merge_on_skipped_pipeline
+- has_confluence
ProtectedEnvironment:
- id
- project_id
diff --git a/spec/lib/gitlab/import_export/saver_spec.rb b/spec/lib/gitlab/import_export/saver_spec.rb
index 18e9d7da32d..865c7e57b5a 100644
--- a/spec/lib/gitlab/import_export/saver_spec.rb
+++ b/spec/lib/gitlab/import_export/saver_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require 'fileutils'
-describe Gitlab::ImportExport::Saver do
+RSpec.describe Gitlab::ImportExport::Saver do
let!(:project) { create(:project, :public, name: 'project') }
let(:base_path) { "#{Dir.tmpdir}/project_tree_saver_spec" }
let(:export_path) { "#{base_path}/project_tree_saver_spec/export" }
diff --git a/spec/lib/gitlab/import_export/shared_spec.rb b/spec/lib/gitlab/import_export/shared_spec.rb
index 8c16243576d..22f2d4c5077 100644
--- a/spec/lib/gitlab/import_export/shared_spec.rb
+++ b/spec/lib/gitlab/import_export/shared_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require 'fileutils'
-describe Gitlab::ImportExport::Shared do
+RSpec.describe Gitlab::ImportExport::Shared do
let(:project) { build(:project) }
subject { project.import_export_shared }
diff --git a/spec/lib/gitlab/import_export/snippet_repo_restorer_spec.rb b/spec/lib/gitlab/import_export/snippet_repo_restorer_spec.rb
index 43c4b164b2d..fe934cadedd 100644
--- a/spec/lib/gitlab/import_export/snippet_repo_restorer_spec.rb
+++ b/spec/lib/gitlab/import_export/snippet_repo_restorer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::ImportExport::SnippetRepoRestorer do
+RSpec.describe Gitlab::ImportExport::SnippetRepoRestorer do
let_it_be(:user) { create(:user) }
let(:project) { create(:project, namespace: user.namespace) }
@@ -35,6 +35,12 @@ describe Gitlab::ImportExport::SnippetRepoRestorer do
end
end
+ it 'does not call snippet update statistics service' do
+ expect(Snippets::UpdateStatisticsService).not_to receive(:new).with(snippet)
+
+ restorer.restore
+ end
+
context 'when the repository creation fails' do
it 'returns false' do
allow_any_instance_of(Gitlab::BackgroundMigration::BackfillSnippetRepositories).to receive(:perform_by_ids).and_return(nil)
@@ -66,6 +72,10 @@ describe Gitlab::ImportExport::SnippetRepoRestorer do
before do
expect(exporter.save).to be_truthy
+
+ allow_next_instance_of(Snippets::RepositoryValidationService) do |instance|
+ allow(instance).to receive(:execute).and_return(ServiceResponse.success)
+ end
end
context 'when it is valid' do
@@ -115,5 +125,19 @@ describe Gitlab::ImportExport::SnippetRepoRestorer do
end
end
end
+
+ it 'refreshes snippet statistics' do
+ expect(snippet.statistics.commit_count).to be_zero
+ expect(snippet.statistics.file_count).to be_zero
+ expect(snippet.statistics.repository_size).to be_zero
+
+ expect(Snippets::UpdateStatisticsService).to receive(:new).with(snippet).and_call_original
+
+ restorer.restore
+
+ expect(snippet.statistics.commit_count).not_to be_zero
+ expect(snippet.statistics.file_count).not_to be_zero
+ expect(snippet.statistics.repository_size).not_to be_zero
+ end
end
end
diff --git a/spec/lib/gitlab/import_export/snippet_repo_saver_spec.rb b/spec/lib/gitlab/import_export/snippet_repo_saver_spec.rb
index 7ad1ff213a1..323ed9a746e 100644
--- a/spec/lib/gitlab/import_export/snippet_repo_saver_spec.rb
+++ b/spec/lib/gitlab/import_export/snippet_repo_saver_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::ImportExport::SnippetRepoSaver do
+RSpec.describe Gitlab::ImportExport::SnippetRepoSaver do
describe 'bundle a project Git repo' do
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project, namespace: user.namespace) }
diff --git a/spec/lib/gitlab/import_export/snippets_repo_restorer_spec.rb b/spec/lib/gitlab/import_export/snippets_repo_restorer_spec.rb
index ac73462073e..7ca365762b5 100644
--- a/spec/lib/gitlab/import_export/snippets_repo_restorer_spec.rb
+++ b/spec/lib/gitlab/import_export/snippets_repo_restorer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::ImportExport::SnippetsRepoRestorer do
+RSpec.describe Gitlab::ImportExport::SnippetsRepoRestorer do
include GitHelpers
describe 'bundle a snippet Git repo' do
diff --git a/spec/lib/gitlab/import_export/snippets_repo_saver_spec.rb b/spec/lib/gitlab/import_export/snippets_repo_saver_spec.rb
index 5332990a975..8507c46ec83 100644
--- a/spec/lib/gitlab/import_export/snippets_repo_saver_spec.rb
+++ b/spec/lib/gitlab/import_export/snippets_repo_saver_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::ImportExport::SnippetsRepoSaver do
+RSpec.describe Gitlab::ImportExport::SnippetsRepoSaver do
describe 'bundle a project Git repo' do
let_it_be(:user) { create(:user) }
let!(:project) { create(:project) }
diff --git a/spec/lib/gitlab/import_export/uploads_manager_spec.rb b/spec/lib/gitlab/import_export/uploads_manager_spec.rb
index e6d6ba840be..33ad0e12c37 100644
--- a/spec/lib/gitlab/import_export/uploads_manager_spec.rb
+++ b/spec/lib/gitlab/import_export/uploads_manager_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::ImportExport::UploadsManager do
+RSpec.describe Gitlab::ImportExport::UploadsManager do
let(:shared) { project.import_export_shared }
let(:export_path) { "#{Dir.tmpdir}/project_tree_saver_spec" }
let(:project) { create(:project) }
diff --git a/spec/lib/gitlab/import_export/uploads_restorer_spec.rb b/spec/lib/gitlab/import_export/uploads_restorer_spec.rb
index 077ece87b31..864fd8d72fa 100644
--- a/spec/lib/gitlab/import_export/uploads_restorer_spec.rb
+++ b/spec/lib/gitlab/import_export/uploads_restorer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::ImportExport::UploadsRestorer do
+RSpec.describe Gitlab::ImportExport::UploadsRestorer do
describe 'bundle a project Git repo' do
let(:export_path) { "#{Dir.tmpdir}/uploads_saver_spec" }
let(:shared) { project.import_export_shared }
diff --git a/spec/lib/gitlab/import_export/uploads_saver_spec.rb b/spec/lib/gitlab/import_export/uploads_saver_spec.rb
index 8a36caef316..8e9be209f89 100644
--- a/spec/lib/gitlab/import_export/uploads_saver_spec.rb
+++ b/spec/lib/gitlab/import_export/uploads_saver_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::ImportExport::UploadsSaver do
+RSpec.describe Gitlab::ImportExport::UploadsSaver do
describe 'bundle a project Git repo' do
let(:export_path) { "#{Dir.tmpdir}/uploads_saver_spec" }
let(:file) { fixture_file_upload('spec/fixtures/banana_sample.gif', 'image/gif') }
diff --git a/spec/lib/gitlab/import_export/version_checker_spec.rb b/spec/lib/gitlab/import_export/version_checker_spec.rb
index befbd1b4c19..8b39330656f 100644
--- a/spec/lib/gitlab/import_export/version_checker_spec.rb
+++ b/spec/lib/gitlab/import_export/version_checker_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
include ImportExport::CommonUtil
-describe Gitlab::ImportExport::VersionChecker do
+RSpec.describe Gitlab::ImportExport::VersionChecker do
let!(:shared) { Gitlab::ImportExport::Shared.new(nil) }
describe 'bundle a project Git repo' do
diff --git a/spec/lib/gitlab/import_export/wiki_repo_saver_spec.rb b/spec/lib/gitlab/import_export/wiki_repo_saver_spec.rb
index 264272d2026..778d0859bf1 100644
--- a/spec/lib/gitlab/import_export/wiki_repo_saver_spec.rb
+++ b/spec/lib/gitlab/import_export/wiki_repo_saver_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::ImportExport::WikiRepoSaver do
+RSpec.describe Gitlab::ImportExport::WikiRepoSaver do
describe 'bundle a wiki Git repo' do
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project, :wiki_repo) }
diff --git a/spec/lib/gitlab/import_export/wiki_restorer_spec.rb b/spec/lib/gitlab/import_export/wiki_restorer_spec.rb
index 33cd3e55393..6c80c410d07 100644
--- a/spec/lib/gitlab/import_export/wiki_restorer_spec.rb
+++ b/spec/lib/gitlab/import_export/wiki_restorer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::ImportExport::WikiRestorer do
+RSpec.describe Gitlab::ImportExport::WikiRestorer do
describe 'restore a wiki Git repo' do
let!(:project_with_wiki) { create(:project, :wiki_repo) }
let!(:project_without_wiki) { create(:project) }
diff --git a/spec/lib/gitlab/import_formatter_spec.rb b/spec/lib/gitlab/import_formatter_spec.rb
index e9f63ba5777..fbf00ab92d3 100644
--- a/spec/lib/gitlab/import_formatter_spec.rb
+++ b/spec/lib/gitlab/import_formatter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::ImportFormatter do
+RSpec.describe Gitlab::ImportFormatter do
let(:formatter) { Gitlab::ImportFormatter.new }
describe '#comment' do
diff --git a/spec/lib/gitlab/import_sources_spec.rb b/spec/lib/gitlab/import_sources_spec.rb
index 265241dc2af..0dfd8a2ee50 100644
--- a/spec/lib/gitlab/import_sources_spec.rb
+++ b/spec/lib/gitlab/import_sources_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::ImportSources do
+RSpec.describe Gitlab::ImportSources do
describe '.options' do
it 'returns a hash' do
expected =
diff --git a/spec/lib/gitlab/incident_management/pager_duty/incident_issue_description_spec.rb b/spec/lib/gitlab/incident_management/pager_duty/incident_issue_description_spec.rb
new file mode 100644
index 00000000000..9a55e21d031
--- /dev/null
+++ b/spec/lib/gitlab/incident_management/pager_duty/incident_issue_description_spec.rb
@@ -0,0 +1,97 @@
+# frozen_string_literal: true
+
+require 'fast_spec_helper'
+require 'timecop'
+
+RSpec.describe Gitlab::IncidentManagement::PagerDuty::IncidentIssueDescription do
+ describe '#to_s' do
+ let(:markdown_line_break) { ' ' }
+ let(:created_at) { '2017-09-26T15:14:36Z' }
+ let(:assignees) do
+ [{ 'summary' => 'Laura Haley', 'url' => 'https://webdemo.pagerduty.com/users/P553OPV' }]
+ end
+ let(:impacted_services) do
+ [{ 'summary' => 'Production XDB Cluster', 'url' => 'https://webdemo.pagerduty.com/services/PN49J75' }]
+ end
+ let(:incident_payload) do
+ {
+ 'url' => 'https://webdemo.pagerduty.com/incidents/PRORDTY',
+ 'incident_number' => 33,
+ 'title' => 'My new incident',
+ 'status' => 'triggered',
+ 'created_at' => created_at,
+ 'urgency' => 'high',
+ 'incident_key' => 'SOME-KEY',
+ 'assignees' => assignees,
+ 'impacted_services' => impacted_services
+ }
+ end
+
+ subject(:to_s) { described_class.new(incident_payload).to_s }
+
+ it 'returns description' do
+ expect(to_s).to eq(
+ <<~MARKDOWN.chomp
+ **Incident:** [My new incident](https://webdemo.pagerduty.com/incidents/PRORDTY)#{markdown_line_break}
+ **Incident number:** 33#{markdown_line_break}
+ **Urgency:** high#{markdown_line_break}
+ **Status:** triggered#{markdown_line_break}
+ **Incident key:** SOME-KEY#{markdown_line_break}
+ **Created at:** 26 September 2017, 3:14PM (UTC)#{markdown_line_break}
+ **Assignees:** [Laura Haley](https://webdemo.pagerduty.com/users/P553OPV)#{markdown_line_break}
+ **Impacted services:** [Production XDB Cluster](https://webdemo.pagerduty.com/services/PN49J75)
+ MARKDOWN
+ )
+ end
+
+ context 'when created_at is missing' do
+ let(:created_at) { nil }
+
+ it 'description contains current time in UTC' do
+ Timecop.freeze do
+ now = Time.current.utc.strftime('%d %B %Y, %-l:%M%p (%Z)')
+
+ expect(to_s).to include(
+ <<~MARKDOWN.chomp
+ **Created at:** #{now}#{markdown_line_break}
+ MARKDOWN
+ )
+ end
+ end
+ end
+
+ context 'when there are several assignees' do
+ let(:assignees) do
+ [
+ { 'summary' => 'Laura Haley', 'url' => 'https://laura.pagerduty.com' },
+ { 'summary' => 'John Doe', 'url' => 'https://john.pagerduty.com' }
+ ]
+ end
+
+ it 'assignees is a list of links' do
+ expect(to_s).to include(
+ <<~MARKDOWN.chomp
+ **Assignees:** [Laura Haley](https://laura.pagerduty.com), [John Doe](https://john.pagerduty.com)#{markdown_line_break}
+ MARKDOWN
+ )
+ end
+ end
+
+ context 'when there are several impacted services' do
+ let(:impacted_services) do
+ [
+ { 'summary' => 'XDB Cluster', 'url' => 'https://xdb.pagerduty.com' },
+ { 'summary' => 'BRB Cluster', 'url' => 'https://brb.pagerduty.com' }
+ ]
+ end
+
+ it 'impacted services is a list of links' do
+ expect(to_s).to include(
+ <<~MARKDOWN.chomp
+ **Impacted services:** [XDB Cluster](https://xdb.pagerduty.com), [BRB Cluster](https://brb.pagerduty.com)
+ MARKDOWN
+ )
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/incoming_email_spec.rb b/spec/lib/gitlab/incoming_email_spec.rb
index 2dd45d18ee9..19d608cf48e 100644
--- a/spec/lib/gitlab/incoming_email_spec.rb
+++ b/spec/lib/gitlab/incoming_email_spec.rb
@@ -2,7 +2,7 @@
require "spec_helper"
-describe Gitlab::IncomingEmail do
+RSpec.describe Gitlab::IncomingEmail do
describe "self.enabled?" do
context "when reply by email is enabled" do
before do
diff --git a/spec/lib/gitlab/insecure_key_fingerprint_spec.rb b/spec/lib/gitlab/insecure_key_fingerprint_spec.rb
index 8d0422bae9f..2f3489edcd8 100644
--- a/spec/lib/gitlab/insecure_key_fingerprint_spec.rb
+++ b/spec/lib/gitlab/insecure_key_fingerprint_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::InsecureKeyFingerprint do
+RSpec.describe Gitlab::InsecureKeyFingerprint do
let(:key) do
'ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn' \
'1SJejgt4596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qk' \
diff --git a/spec/lib/gitlab/instrumentation/redis_base_spec.rb b/spec/lib/gitlab/instrumentation/redis_base_spec.rb
index 5ea8f00114e..07be0ccf6e9 100644
--- a/spec/lib/gitlab/instrumentation/redis_base_spec.rb
+++ b/spec/lib/gitlab/instrumentation/redis_base_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Instrumentation::RedisBase, :request_store do
+RSpec.describe Gitlab::Instrumentation::RedisBase, :request_store do
let(:instrumentation_class_a) do
stub_const('InstanceA', Class.new(described_class))
end
diff --git a/spec/lib/gitlab/instrumentation/redis_cluster_validator_spec.rb b/spec/lib/gitlab/instrumentation/redis_cluster_validator_spec.rb
new file mode 100644
index 00000000000..2ca7465e775
--- /dev/null
+++ b/spec/lib/gitlab/instrumentation/redis_cluster_validator_spec.rb
@@ -0,0 +1,93 @@
+# frozen_string_literal: true
+
+require 'fast_spec_helper'
+require 'support/helpers/rails_helpers'
+require 'rspec-parameterized'
+
+RSpec.describe Gitlab::Instrumentation::RedisClusterValidator do
+ include RailsHelpers
+
+ describe '.validate!' do
+ using RSpec::Parameterized::TableSyntax
+
+ context 'Rails environments' do
+ where(:env, :should_raise) do
+ 'production' | false
+ 'staging' | false
+ 'development' | true
+ 'test' | true
+ end
+
+ with_them do
+ it do
+ stub_rails_env(env)
+
+ args = [:mget, 'foo', 'bar']
+
+ if should_raise
+ expect { described_class.validate!(args) }
+ .to raise_error(described_class::CrossSlotError)
+ else
+ expect { described_class.validate!(args) }.not_to raise_error
+ end
+ end
+ end
+ end
+
+ where(:command, :arguments, :should_raise) do
+ :rename | %w(foo bar) | true
+ :RENAME | %w(foo bar) | true
+ 'rename' | %w(foo bar) | true
+ 'RENAME' | %w(foo bar) | true
+ :rename | %w(iaa ahy) | false # 'iaa' and 'ahy' hash to the same slot
+ :rename | %w({foo}:1 {foo}:2) | false
+ :rename | %w(foo foo bar) | false # This is not a valid command but should not raise here
+ :mget | %w(foo bar) | true
+ :mget | %w(foo foo bar) | true
+ :mget | %w(foo foo) | false
+ :blpop | %w(foo bar 1) | true
+ :blpop | %w(foo foo 1) | false
+ :mset | %w(foo a bar a) | true
+ :mset | %w(foo a foo a) | false
+ :del | %w(foo bar) | true
+ :del | [%w(foo bar)] | true # Arguments can be a nested array
+ :del | %w(foo foo) | false
+ :hset | %w(foo bar) | false # Not a multi-key command
+ end
+
+ with_them do
+ it do
+ args = [command] + arguments
+
+ if should_raise
+ expect { described_class.validate!(args) }
+ .to raise_error(described_class::CrossSlotError)
+ else
+ expect { described_class.validate!(args) }.not_to raise_error
+ end
+ end
+ end
+ end
+
+ describe '.allow_cross_slot_commands' do
+ it 'does not raise for invalid arguments' do
+ expect do
+ described_class.allow_cross_slot_commands do
+ described_class.validate!([:mget, 'foo', 'bar'])
+ end
+ end.not_to raise_error
+ end
+
+ it 'allows nested invocation' do
+ expect do
+ described_class.allow_cross_slot_commands do
+ described_class.allow_cross_slot_commands do
+ described_class.validate!([:mget, 'foo', 'bar'])
+ end
+
+ described_class.validate!([:mget, 'foo', 'bar'])
+ end
+ end.not_to raise_error
+ end
+ end
+end
diff --git a/spec/lib/gitlab/instrumentation/redis_interceptor_spec.rb b/spec/lib/gitlab/instrumentation/redis_interceptor_spec.rb
index 25506d63091..5b0ad63ee72 100644
--- a/spec/lib/gitlab/instrumentation/redis_interceptor_spec.rb
+++ b/spec/lib/gitlab/instrumentation/redis_interceptor_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require 'rspec-parameterized'
-describe Gitlab::Instrumentation::RedisInterceptor, :clean_gitlab_redis_shared_state, :request_store do
+RSpec.describe Gitlab::Instrumentation::RedisInterceptor, :clean_gitlab_redis_shared_state, :request_store do
using RSpec::Parameterized::TableSyntax
describe 'read and write' do
@@ -42,4 +42,73 @@ describe Gitlab::Instrumentation::RedisInterceptor, :clean_gitlab_redis_shared_s
end
end
end
+
+ describe 'counting' do
+ let(:instrumentation_class) { Gitlab::Redis::SharedState.instrumentation_class }
+
+ it 'counts successful requests' do
+ expect(instrumentation_class).to receive(:instance_count_request).and_call_original
+
+ Gitlab::Redis::SharedState.with { |redis| redis.call(:get, 'foobar') }
+ end
+
+ it 'counts exceptions' do
+ expect(instrumentation_class).to receive(:instance_count_exception)
+ .with(instance_of(Redis::CommandError)).and_call_original
+ expect(instrumentation_class).to receive(:instance_count_request).and_call_original
+
+ expect do
+ Gitlab::Redis::SharedState.with do |redis|
+ redis.call(:auth, 'foo', 'bar')
+ end
+ end.to raise_exception(Redis::CommandError)
+ end
+ end
+
+ describe 'latency' do
+ let(:instrumentation_class) { Gitlab::Redis::SharedState.instrumentation_class }
+
+ describe 'commands in the apdex' do
+ where(:command) do
+ [
+ [[:get, 'foobar']],
+ [%w[GET foobar]]
+ ]
+ end
+
+ with_them do
+ it 'measures requests we want in the apdex' do
+ expect(instrumentation_class).to receive(:instance_observe_duration).with(a_value > 0)
+ .and_call_original
+
+ Gitlab::Redis::SharedState.with { |redis| redis.call(*command) }
+ end
+ end
+ end
+
+ describe 'commands not in the apdex' do
+ where(:command) do
+ [
+ [%w[brpop foobar 0.01]],
+ [%w[blpop foobar 0.01]],
+ [%w[brpoplpush foobar bazqux 0.01]],
+ [%w[bzpopmin foobar 0.01]],
+ [%w[bzpopmax foobar 0.01]],
+ [%w[xread block 1 streams mystream 0-0]],
+ [%w[xreadgroup group mygroup myconsumer block 1 streams foobar 0-0]]
+ ]
+ end
+
+ with_them do
+ it 'skips requests we do not want in the apdex' do
+ expect(instrumentation_class).not_to receive(:instance_observe_duration)
+
+ begin
+ Gitlab::Redis::SharedState.with { |redis| redis.call(*command) }
+ rescue Gitlab::Instrumentation::RedisClusterValidator::CrossSlotError, ::Redis::CommandError
+ end
+ end
+ end
+ end
+ end
end
diff --git a/spec/lib/gitlab/instrumentation/redis_spec.rb b/spec/lib/gitlab/instrumentation/redis_spec.rb
index 8311c4f5bbb..e927f39cae2 100644
--- a/spec/lib/gitlab/instrumentation/redis_spec.rb
+++ b/spec/lib/gitlab/instrumentation/redis_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Instrumentation::Redis do
+RSpec.describe Gitlab::Instrumentation::Redis do
def stub_storages(method, value)
described_class::STORAGES.each do |storage|
allow(storage).to receive(method) { value }
diff --git a/spec/lib/gitlab/instrumentation_helper_spec.rb b/spec/lib/gitlab/instrumentation_helper_spec.rb
index 15d377a16fc..35bbdcdccd6 100644
--- a/spec/lib/gitlab/instrumentation_helper_spec.rb
+++ b/spec/lib/gitlab/instrumentation_helper_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require 'rspec-parameterized'
-describe Gitlab::InstrumentationHelper do
+RSpec.describe Gitlab::InstrumentationHelper do
using RSpec::Parameterized::TableSyntax
describe '.keys' do
diff --git a/spec/lib/gitlab/internal_post_receive/response_spec.rb b/spec/lib/gitlab/internal_post_receive/response_spec.rb
index d90b85a41ed..135596c2de3 100644
--- a/spec/lib/gitlab/internal_post_receive/response_spec.rb
+++ b/spec/lib/gitlab/internal_post_receive/response_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::InternalPostReceive::Response do
+RSpec.describe Gitlab::InternalPostReceive::Response do
subject { described_class.new }
describe '#add_merge_request_urls' do
diff --git a/spec/lib/gitlab/issuable_metadata_spec.rb b/spec/lib/gitlab/issuable_metadata_spec.rb
index 1920cecfc29..3ba1d069bc9 100644
--- a/spec/lib/gitlab/issuable_metadata_spec.rb
+++ b/spec/lib/gitlab/issuable_metadata_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::IssuableMetadata do
+RSpec.describe Gitlab::IssuableMetadata do
let(:user) { create(:user) }
let!(:project) { create(:project, :public, :repository, creator: user, namespace: user.namespace) }
diff --git a/spec/lib/gitlab/issuable_sorter_spec.rb b/spec/lib/gitlab/issuable_sorter_spec.rb
index 486e9539b92..60f62062f04 100644
--- a/spec/lib/gitlab/issuable_sorter_spec.rb
+++ b/spec/lib/gitlab/issuable_sorter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::IssuableSorter do
+RSpec.describe Gitlab::IssuableSorter do
let(:namespace1) { build_stubbed(:namespace, id: 1) }
let(:project1) { build_stubbed(:project, id: 1, namespace: namespace1) }
diff --git a/spec/lib/gitlab/issuables_count_for_state_spec.rb b/spec/lib/gitlab/issuables_count_for_state_spec.rb
index 9380aa53470..1c186a8e6ca 100644
--- a/spec/lib/gitlab/issuables_count_for_state_spec.rb
+++ b/spec/lib/gitlab/issuables_count_for_state_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::IssuablesCountForState do
+RSpec.describe Gitlab::IssuablesCountForState do
let(:finder) do
double(:finder, count_by_state: { opened: 2, closed: 1 })
end
diff --git a/spec/lib/gitlab/jira_import/base_importer_spec.rb b/spec/lib/gitlab/jira_import/base_importer_spec.rb
index cda491393e8..1470bad2c4c 100644
--- a/spec/lib/gitlab/jira_import/base_importer_spec.rb
+++ b/spec/lib/gitlab/jira_import/base_importer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::JiraImport::BaseImporter do
+RSpec.describe Gitlab::JiraImport::BaseImporter do
include JiraServiceHelper
let(:project) { create(:project) }
diff --git a/spec/lib/gitlab/jira_import/handle_labels_service_spec.rb b/spec/lib/gitlab/jira_import/handle_labels_service_spec.rb
index 0eeff180575..4e2c5afb077 100644
--- a/spec/lib/gitlab/jira_import/handle_labels_service_spec.rb
+++ b/spec/lib/gitlab/jira_import/handle_labels_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::JiraImport::HandleLabelsService do
+RSpec.describe Gitlab::JiraImport::HandleLabelsService do
describe '#execute' do
let_it_be(:group) { create(:group) }
let_it_be(:project) { create(:project, group: group) }
diff --git a/spec/lib/gitlab/jira_import/issue_serializer_spec.rb b/spec/lib/gitlab/jira_import/issue_serializer_spec.rb
index ce38a1234cf..4adc4e4d22a 100644
--- a/spec/lib/gitlab/jira_import/issue_serializer_spec.rb
+++ b/spec/lib/gitlab/jira_import/issue_serializer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::JiraImport::IssueSerializer do
+RSpec.describe Gitlab::JiraImport::IssueSerializer do
describe '#execute' do
let_it_be(:group) { create(:group) }
let_it_be(:project) { create(:project, group: group) }
@@ -10,6 +10,7 @@ describe Gitlab::JiraImport::IssueSerializer do
let_it_be(:other_project_label) { create(:label, project: project, title: 'feature') }
let_it_be(:group_label) { create(:group_label, group: group, title: 'dev') }
let_it_be(:current_user) { create(:user) }
+ let_it_be(:user) { create(:user) }
let(:iid) { 5 }
let(:key) { 'PROJECT-5' }
@@ -17,8 +18,8 @@ describe Gitlab::JiraImport::IssueSerializer do
let(:description) { 'basic description' }
let(:created_at) { '2020-01-01 20:00:00' }
let(:updated_at) { '2020-01-10 20:00:00' }
- let(:assignee) { double(attrs: { 'displayName' => 'Solver', 'emailAddress' => 'assignee@example.com' }) }
- let(:reporter) { double(attrs: { 'displayName' => 'Reporter', 'emailAddress' => 'reporter@example.com' }) }
+ let(:assignee) { nil }
+ let(:reporter) { nil }
let(:jira_status) { 'new' }
let(:parent_field) do
@@ -109,11 +110,12 @@ describe Gitlab::JiraImport::IssueSerializer do
end
context 'author' do
- context 'when reporter maps to a valid GitLab user' do
- let!(:user) { create(:user, email: 'reporter@example.com') }
+ let(:reporter) { double(attrs: { 'displayName' => 'Solver', 'accountId' => 'abcd' }) }
+ context 'when reporter maps to a valid GitLab user' do
it 'sets the issue author to the mapped user' do
- project.add_developer(user)
+ expect(Gitlab::JiraImport).to receive(:get_user_mapping).with(project.id, 'abcd')
+ .and_return(user.id)
expect(subject[:author_id]).to eq(user.id)
end
@@ -121,6 +123,9 @@ describe Gitlab::JiraImport::IssueSerializer do
context 'when reporter does not map to a valid Gitlab user' do
it 'defaults the issue author to project creator' do
+ expect(Gitlab::JiraImport).to receive(:get_user_mapping).with(project.id, 'abcd')
+ .and_return(nil)
+
expect(subject[:author_id]).to eq(current_user.id)
end
end
@@ -129,25 +134,30 @@ describe Gitlab::JiraImport::IssueSerializer do
let(:reporter) { nil }
it 'defaults the issue author to project creator' do
+ expect(Gitlab::JiraImport).not_to receive(:get_user_mapping)
+
expect(subject[:author_id]).to eq(current_user.id)
end
end
- context 'when reporter field is missing email address' do
+ context 'when reporter field is missing accountId' do
let(:reporter) { double(attrs: { 'displayName' => 'Reporter' }) }
it 'defaults the issue author to project creator' do
+ expect(Gitlab::JiraImport).not_to receive(:get_user_mapping)
+
expect(subject[:author_id]).to eq(current_user.id)
end
end
end
context 'assignee' do
- context 'when assignee maps to a valid GitLab user' do
- let!(:user) { create(:user, email: 'assignee@example.com') }
+ let(:assignee) { double(attrs: { 'displayName' => 'Solver', 'accountId' => '1234' }) }
+ context 'when assignee maps to a valid GitLab user' do
it 'sets the issue assignees to the mapped user' do
- project.add_developer(user)
+ expect(Gitlab::JiraImport).to receive(:get_user_mapping).with(project.id, '1234')
+ .and_return(user.id)
expect(subject[:assignee_ids]).to eq([user.id])
end
@@ -155,6 +165,9 @@ describe Gitlab::JiraImport::IssueSerializer do
context 'when assignee does not map to a valid GitLab user' do
it 'leaves the assignee empty' do
+ expect(Gitlab::JiraImport).to receive(:get_user_mapping).with(project.id, '1234')
+ .and_return(nil)
+
expect(subject[:assignee_ids]).to be_nil
end
end
@@ -163,14 +176,18 @@ describe Gitlab::JiraImport::IssueSerializer do
let(:assignee) { nil }
it 'leaves the assignee empty' do
+ expect(Gitlab::JiraImport).not_to receive(:get_user_mapping)
+
expect(subject[:assignee_ids]).to be_nil
end
end
- context 'when assginee field is missing email address' do
- let(:assignee) { double(attrs: { 'displayName' => 'Reporter' }) }
+ context 'when assginee field is missing accountId' do
+ let(:assignee) { double(attrs: { 'displayName' => 'Solver' }) }
it 'leaves the assignee empty' do
+ expect(Gitlab::JiraImport).not_to receive(:get_user_mapping)
+
expect(subject[:assignee_ids]).to be_nil
end
end
diff --git a/spec/lib/gitlab/jira_import/issues_importer_spec.rb b/spec/lib/gitlab/jira_import/issues_importer_spec.rb
index 0d790f49450..4a32f0fd3a9 100644
--- a/spec/lib/gitlab/jira_import/issues_importer_spec.rb
+++ b/spec/lib/gitlab/jira_import/issues_importer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::JiraImport::IssuesImporter do
+RSpec.describe Gitlab::JiraImport::IssuesImporter do
include JiraServiceHelper
let_it_be(:user) { create(:user) }
diff --git a/spec/lib/gitlab/jira_import/labels_importer_spec.rb b/spec/lib/gitlab/jira_import/labels_importer_spec.rb
index 19661ff4e73..db98a83cb3c 100644
--- a/spec/lib/gitlab/jira_import/labels_importer_spec.rb
+++ b/spec/lib/gitlab/jira_import/labels_importer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::JiraImport::LabelsImporter do
+RSpec.describe Gitlab::JiraImport::LabelsImporter do
include JiraServiceHelper
let_it_be(:user) { create(:user) }
diff --git a/spec/lib/gitlab/jira_import/metadata_collector_spec.rb b/spec/lib/gitlab/jira_import/metadata_collector_spec.rb
index af479810df0..86863d67f25 100644
--- a/spec/lib/gitlab/jira_import/metadata_collector_spec.rb
+++ b/spec/lib/gitlab/jira_import/metadata_collector_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::JiraImport::MetadataCollector do
+RSpec.describe Gitlab::JiraImport::MetadataCollector do
describe '#execute' do
let(:key) { 'PROJECT-5' }
let(:summary) { 'some title' }
diff --git a/spec/lib/gitlab/jira_import/user_mapper_spec.rb b/spec/lib/gitlab/jira_import/user_mapper_spec.rb
deleted file mode 100644
index c8c8bd3c5b0..00000000000
--- a/spec/lib/gitlab/jira_import/user_mapper_spec.rb
+++ /dev/null
@@ -1,80 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-describe Gitlab::JiraImport::UserMapper do
- let_it_be(:group) { create(:group) }
- let_it_be(:project) { create(:project, group: group) }
- let_it_be(:user) { create(:user, email: 'user@example.com') }
- let_it_be(:email) { create(:email, user: user, email: 'second_email@example.com', confirmed_at: nil) }
-
- let(:jira_user) { { 'acountId' => '1a2b', 'emailAddress' => 'user@example.com' } }
-
- describe '#execute' do
- subject { described_class.new(project, jira_user).execute }
-
- context 'when jira_user is nil' do
- let(:jira_user) { nil }
-
- it 'returns nil' do
- expect(subject).to be_nil
- end
- end
-
- context 'when Gitlab user is not found by email' do
- let(:jira_user) { { 'acountId' => '1a2b', 'emailAddress' => 'other@example.com' } }
-
- it 'returns nil' do
- expect(subject).to be_nil
- end
- end
-
- context 'when jira_user emailAddress is nil' do
- let(:jira_user) { { 'acountId' => '1a2b', 'emailAddress' => nil } }
-
- it 'returns nil' do
- expect(subject).to be_nil
- end
- end
-
- context 'when jira_user emailAddress key is missing' do
- let(:jira_user) { { 'acountId' => '1a2b' } }
-
- it 'returns nil' do
- expect(subject).to be_nil
- end
- end
-
- context 'when found user is not a project member' do
- it 'returns nil' do
- expect(subject).to be_nil
- end
- end
-
- context 'when found user is a project member' do
- it 'returns the found user' do
- project.add_developer(user)
-
- expect(subject).to eq(user)
- end
- end
-
- context 'when user found by unconfirmd secondary address is a project member' do
- let(:jira_user) { { 'acountId' => '1a2b', 'emailAddress' => 'second_email@example.com' } }
-
- it 'returns the found user' do
- project.add_developer(user)
-
- expect(subject).to eq(user)
- end
- end
-
- context 'when user is a group member' do
- it 'returns the found user' do
- group.add_developer(user)
-
- expect(subject).to eq(user)
- end
- end
- end
-end
diff --git a/spec/lib/gitlab/jira_import_spec.rb b/spec/lib/gitlab/jira_import_spec.rb
index 5b95891c97e..c8cecb576da 100644
--- a/spec/lib/gitlab/jira_import_spec.rb
+++ b/spec/lib/gitlab/jira_import_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::JiraImport do
+RSpec.describe Gitlab::JiraImport do
let(:project_id) { 321 }
describe '.validate_project_settings!' do
diff --git a/spec/lib/gitlab/job_waiter_spec.rb b/spec/lib/gitlab/job_waiter_spec.rb
index da6a6a9149b..4d7c838aa3b 100644
--- a/spec/lib/gitlab/job_waiter_spec.rb
+++ b/spec/lib/gitlab/job_waiter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::JobWaiter do
+RSpec.describe Gitlab::JobWaiter do
describe '.notify' do
it 'pushes the jid to the named queue' do
key = 'gitlab:job_waiter:foo'
diff --git a/spec/lib/gitlab/json_cache_spec.rb b/spec/lib/gitlab/json_cache_spec.rb
index 9379499f602..563b3d35823 100644
--- a/spec/lib/gitlab/json_cache_spec.rb
+++ b/spec/lib/gitlab/json_cache_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::JsonCache do
+RSpec.describe Gitlab::JsonCache do
let_it_be(:broadcast_message) { create(:broadcast_message) }
let(:backend) { double('backend').as_null_object }
let(:namespace) { 'geo' }
diff --git a/spec/lib/gitlab/json_logger_spec.rb b/spec/lib/gitlab/json_logger_spec.rb
index 41dafc84ef2..23f7191454a 100644
--- a/spec/lib/gitlab/json_logger_spec.rb
+++ b/spec/lib/gitlab/json_logger_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::JsonLogger do
+RSpec.describe Gitlab::JsonLogger do
subject { described_class.new('/dev/null') }
let(:now) { Time.now }
diff --git a/spec/lib/gitlab/json_spec.rb b/spec/lib/gitlab/json_spec.rb
index ee7c98a5a54..d7671dda323 100644
--- a/spec/lib/gitlab/json_spec.rb
+++ b/spec/lib/gitlab/json_spec.rb
@@ -7,189 +7,404 @@ RSpec.describe Gitlab::Json do
stub_feature_flags(json_wrapper_legacy_mode: true)
end
- describe ".parse" do
- context "legacy_mode is disabled by default" do
- it "parses an object" do
- expect(subject.parse('{ "foo": "bar" }')).to eq({ "foo" => "bar" })
- end
+ shared_examples "json" do
+ describe ".parse" do
+ context "legacy_mode is disabled by default" do
+ it "parses an object" do
+ expect(subject.parse('{ "foo": "bar" }')).to eq({ "foo" => "bar" })
+ end
- it "parses an array" do
- expect(subject.parse('[{ "foo": "bar" }]')).to eq([{ "foo" => "bar" }])
- end
+ it "parses an array" do
+ expect(subject.parse('[{ "foo": "bar" }]')).to eq([{ "foo" => "bar" }])
+ end
- it "parses a string" do
- expect(subject.parse('"foo"', legacy_mode: false)).to eq("foo")
- end
+ it "parses a string" do
+ expect(subject.parse('"foo"', legacy_mode: false)).to eq("foo")
+ end
- it "parses a true bool" do
- expect(subject.parse("true", legacy_mode: false)).to be(true)
- end
+ it "parses a true bool" do
+ expect(subject.parse("true", legacy_mode: false)).to be(true)
+ end
- it "parses a false bool" do
- expect(subject.parse("false", legacy_mode: false)).to be(false)
+ it "parses a false bool" do
+ expect(subject.parse("false", legacy_mode: false)).to be(false)
+ end
end
- end
- context "legacy_mode is enabled" do
- it "parses an object" do
- expect(subject.parse('{ "foo": "bar" }', legacy_mode: true)).to eq({ "foo" => "bar" })
- end
+ context "legacy_mode is enabled" do
+ it "parses an object" do
+ expect(subject.parse('{ "foo": "bar" }', legacy_mode: true)).to eq({ "foo" => "bar" })
+ end
- it "parses an array" do
- expect(subject.parse('[{ "foo": "bar" }]', legacy_mode: true)).to eq([{ "foo" => "bar" }])
- end
+ it "parses an array" do
+ expect(subject.parse('[{ "foo": "bar" }]', legacy_mode: true)).to eq([{ "foo" => "bar" }])
+ end
- it "raises an error on a string" do
- expect { subject.parse('"foo"', legacy_mode: true) }.to raise_error(JSON::ParserError)
- end
+ it "raises an error on a string" do
+ expect { subject.parse('"foo"', legacy_mode: true) }.to raise_error(JSON::ParserError)
+ end
- it "raises an error on a true bool" do
- expect { subject.parse("true", legacy_mode: true) }.to raise_error(JSON::ParserError)
+ it "raises an error on a true bool" do
+ expect { subject.parse("true", legacy_mode: true) }.to raise_error(JSON::ParserError)
+ end
+
+ it "raises an error on a false bool" do
+ expect { subject.parse("false", legacy_mode: true) }.to raise_error(JSON::ParserError)
+ end
end
- it "raises an error on a false bool" do
- expect { subject.parse("false", legacy_mode: true) }.to raise_error(JSON::ParserError)
+ context "feature flag is disabled" do
+ before do
+ stub_feature_flags(json_wrapper_legacy_mode: false)
+ end
+
+ it "parses an object" do
+ expect(subject.parse('{ "foo": "bar" }', legacy_mode: true)).to eq({ "foo" => "bar" })
+ end
+
+ it "parses an array" do
+ expect(subject.parse('[{ "foo": "bar" }]', legacy_mode: true)).to eq([{ "foo" => "bar" }])
+ end
+
+ it "parses a string" do
+ expect(subject.parse('"foo"', legacy_mode: true)).to eq("foo")
+ end
+
+ it "parses a true bool" do
+ expect(subject.parse("true", legacy_mode: true)).to be(true)
+ end
+
+ it "parses a false bool" do
+ expect(subject.parse("false", legacy_mode: true)).to be(false)
+ end
end
end
- context "feature flag is disabled" do
- before do
- stub_feature_flags(json_wrapper_legacy_mode: false)
- end
+ describe ".parse!" do
+ context "legacy_mode is disabled by default" do
+ it "parses an object" do
+ expect(subject.parse!('{ "foo": "bar" }')).to eq({ "foo" => "bar" })
+ end
- it "parses an object" do
- expect(subject.parse('{ "foo": "bar" }', legacy_mode: true)).to eq({ "foo" => "bar" })
- end
+ it "parses an array" do
+ expect(subject.parse!('[{ "foo": "bar" }]')).to eq([{ "foo" => "bar" }])
+ end
- it "parses an array" do
- expect(subject.parse('[{ "foo": "bar" }]', legacy_mode: true)).to eq([{ "foo" => "bar" }])
- end
+ it "parses a string" do
+ expect(subject.parse!('"foo"', legacy_mode: false)).to eq("foo")
+ end
- it "parses a string" do
- expect(subject.parse('"foo"', legacy_mode: true)).to eq("foo")
+ it "parses a true bool" do
+ expect(subject.parse!("true", legacy_mode: false)).to be(true)
+ end
+
+ it "parses a false bool" do
+ expect(subject.parse!("false", legacy_mode: false)).to be(false)
+ end
end
- it "parses a true bool" do
- expect(subject.parse("true", legacy_mode: true)).to be(true)
+ context "legacy_mode is enabled" do
+ it "parses an object" do
+ expect(subject.parse!('{ "foo": "bar" }', legacy_mode: true)).to eq({ "foo" => "bar" })
+ end
+
+ it "parses an array" do
+ expect(subject.parse!('[{ "foo": "bar" }]', legacy_mode: true)).to eq([{ "foo" => "bar" }])
+ end
+
+ it "raises an error on a string" do
+ expect { subject.parse!('"foo"', legacy_mode: true) }.to raise_error(JSON::ParserError)
+ end
+
+ it "raises an error on a true bool" do
+ expect { subject.parse!("true", legacy_mode: true) }.to raise_error(JSON::ParserError)
+ end
+
+ it "raises an error on a false bool" do
+ expect { subject.parse!("false", legacy_mode: true) }.to raise_error(JSON::ParserError)
+ end
end
- it "parses a false bool" do
- expect(subject.parse("false", legacy_mode: true)).to be(false)
+ context "feature flag is disabled" do
+ before do
+ stub_feature_flags(json_wrapper_legacy_mode: false)
+ end
+
+ it "parses an object" do
+ expect(subject.parse!('{ "foo": "bar" }', legacy_mode: true)).to eq({ "foo" => "bar" })
+ end
+
+ it "parses an array" do
+ expect(subject.parse!('[{ "foo": "bar" }]', legacy_mode: true)).to eq([{ "foo" => "bar" }])
+ end
+
+ it "parses a string" do
+ expect(subject.parse!('"foo"', legacy_mode: true)).to eq("foo")
+ end
+
+ it "parses a true bool" do
+ expect(subject.parse!("true", legacy_mode: true)).to be(true)
+ end
+
+ it "parses a false bool" do
+ expect(subject.parse!("false", legacy_mode: true)).to be(false)
+ end
end
end
- end
- describe ".parse!" do
- context "legacy_mode is disabled by default" do
- it "parses an object" do
- expect(subject.parse!('{ "foo": "bar" }')).to eq({ "foo" => "bar" })
+ describe ".dump" do
+ it "dumps an object" do
+ expect(subject.dump({ "foo" => "bar" })).to eq('{"foo":"bar"}')
end
- it "parses an array" do
- expect(subject.parse!('[{ "foo": "bar" }]')).to eq([{ "foo" => "bar" }])
+ it "dumps an array" do
+ expect(subject.dump([{ "foo" => "bar" }])).to eq('[{"foo":"bar"}]')
end
- it "parses a string" do
- expect(subject.parse!('"foo"', legacy_mode: false)).to eq("foo")
+ it "dumps a string" do
+ expect(subject.dump("foo")).to eq('"foo"')
end
- it "parses a true bool" do
- expect(subject.parse!("true", legacy_mode: false)).to be(true)
+ it "dumps a true bool" do
+ expect(subject.dump(true)).to eq("true")
end
- it "parses a false bool" do
- expect(subject.parse!("false", legacy_mode: false)).to be(false)
+ it "dumps a false bool" do
+ expect(subject.dump(false)).to eq("false")
end
end
- context "legacy_mode is enabled" do
- it "parses an object" do
- expect(subject.parse!('{ "foo": "bar" }', legacy_mode: true)).to eq({ "foo" => "bar" })
+ describe ".generate" do
+ let(:obj) do
+ { test: true, "foo.bar" => "baz", is_json: 1, some: [1, 2, 3] }
end
- it "parses an array" do
- expect(subject.parse!('[{ "foo": "bar" }]', legacy_mode: true)).to eq([{ "foo" => "bar" }])
- end
+ it "generates JSON" do
+ expected_string = <<~STR.chomp
+ {"test":true,"foo.bar":"baz","is_json":1,"some":[1,2,3]}
+ STR
- it "raises an error on a string" do
- expect { subject.parse!('"foo"', legacy_mode: true) }.to raise_error(JSON::ParserError)
+ expect(subject.generate(obj)).to eq(expected_string)
end
- it "raises an error on a true bool" do
- expect { subject.parse!("true", legacy_mode: true) }.to raise_error(JSON::ParserError)
+ it "allows you to customise the output" do
+ opts = {
+ indent: " ",
+ space: " ",
+ space_before: " ",
+ object_nl: "\n",
+ array_nl: "\n"
+ }
+
+ json = subject.generate(obj, opts)
+
+ expected_string = <<~STR.chomp
+ {
+ "test" : true,
+ "foo.bar" : "baz",
+ "is_json" : 1,
+ "some" : [
+ 1,
+ 2,
+ 3
+ ]
+ }
+ STR
+
+ expect(json).to eq(expected_string)
end
+ end
- it "raises an error on a false bool" do
- expect { subject.parse!("false", legacy_mode: true) }.to raise_error(JSON::ParserError)
+ describe ".pretty_generate" do
+ let(:obj) do
+ {
+ test: true,
+ "foo.bar" => "baz",
+ is_json: 1,
+ some: [1, 2, 3],
+ more: { test: true },
+ multi_line_empty_array: [],
+ multi_line_empty_obj: {}
+ }
+ end
+
+ it "generates pretty JSON" do
+ expected_string = <<~STR.chomp
+ {
+ "test": true,
+ "foo.bar": "baz",
+ "is_json": 1,
+ "some": [
+ 1,
+ 2,
+ 3
+ ],
+ "more": {
+ "test": true
+ },
+ "multi_line_empty_array": [
+
+ ],
+ "multi_line_empty_obj": {
+ }
+ }
+ STR
+
+ expect(subject.pretty_generate(obj)).to eq(expected_string)
+ end
+
+ it "allows you to customise the output" do
+ opts = {
+ space_before: " "
+ }
+
+ json = subject.pretty_generate(obj, opts)
+
+ expected_string = <<~STR.chomp
+ {
+ "test" : true,
+ "foo.bar" : "baz",
+ "is_json" : 1,
+ "some" : [
+ 1,
+ 2,
+ 3
+ ],
+ "more" : {
+ "test" : true
+ },
+ "multi_line_empty_array" : [
+
+ ],
+ "multi_line_empty_obj" : {
+ }
+ }
+ STR
+
+ expect(json).to eq(expected_string)
end
end
- context "feature flag is disabled" do
+ context "the feature table is missing" do
before do
- stub_feature_flags(json_wrapper_legacy_mode: false)
+ allow(Feature::FlipperFeature).to receive(:table_exists?).and_return(false)
end
- it "parses an object" do
- expect(subject.parse!('{ "foo": "bar" }', legacy_mode: true)).to eq({ "foo" => "bar" })
+ it "skips legacy mode handling" do
+ expect(Feature).not_to receive(:enabled?).with(:json_wrapper_legacy_mode, default_enabled: true)
+
+ subject.send(:handle_legacy_mode!, {})
end
- it "parses an array" do
- expect(subject.parse!('[{ "foo": "bar" }]', legacy_mode: true)).to eq([{ "foo" => "bar" }])
+ it "skips oj feature detection" do
+ expect(Feature).not_to receive(:enabled?).with(:oj_json, default_enabled: true)
+
+ subject.send(:enable_oj?)
end
+ end
- it "parses a string" do
- expect(subject.parse!('"foo"', legacy_mode: true)).to eq("foo")
+ context "the database is missing" do
+ before do
+ allow(Feature::FlipperFeature).to receive(:table_exists?).and_raise(PG::ConnectionBad)
end
- it "parses a true bool" do
- expect(subject.parse!("true", legacy_mode: true)).to be(true)
+ it "still parses json" do
+ expect(subject.parse("{}")).to eq({})
end
- it "parses a false bool" do
- expect(subject.parse!("false", legacy_mode: true)).to be(false)
+ it "still generates json" do
+ expect(subject.dump({})).to eq("{}")
end
end
end
- describe ".dump" do
- it "dumps an object" do
- expect(subject.dump({ "foo" => "bar" })).to eq('{"foo":"bar"}')
+ context "oj gem" do
+ before do
+ stub_feature_flags(oj_json: true)
end
- it "dumps an array" do
- expect(subject.dump([{ "foo" => "bar" }])).to eq('[{"foo":"bar"}]')
- end
+ it_behaves_like "json"
- it "dumps a string" do
- expect(subject.dump("foo")).to eq('"foo"')
+ describe "#enable_oj?" do
+ it "returns true" do
+ expect(subject.enable_oj?).to be(true)
+ end
end
+ end
- it "dumps a true bool" do
- expect(subject.dump(true)).to eq("true")
+ context "json gem" do
+ before do
+ stub_feature_flags(oj_json: false)
end
- it "dumps a false bool" do
- expect(subject.dump(false)).to eq("false")
+ it_behaves_like "json"
+
+ describe "#enable_oj?" do
+ it "returns false" do
+ expect(subject.enable_oj?).to be(false)
+ end
end
end
- describe ".generate" do
- it "delegates to the adapter" do
- args = [{ foo: "bar" }]
+ describe Gitlab::Json::GrapeFormatter do
+ subject { described_class.call(obj, env) }
+
+ let(:obj) { { test: true } }
+ let(:env) { {} }
+ let(:result) { "{\"test\":true}" }
+
+ context "oj is enabled" do
+ before do
+ stub_feature_flags(oj_json: true)
+ end
+
+ context "grape_gitlab_json flag is enabled" do
+ before do
+ stub_feature_flags(grape_gitlab_json: true)
+ end
+
+ it "generates JSON" do
+ expect(subject).to eq(result)
+ end
+
+ it "uses Gitlab::Json" do
+ expect(Gitlab::Json).to receive(:dump).with(obj)
+
+ subject
+ end
+ end
+
+ context "grape_gitlab_json flag is disabled" do
+ before do
+ stub_feature_flags(grape_gitlab_json: false)
+ end
+
+ it "generates JSON" do
+ expect(subject).to eq(result)
+ end
- expect(JSON).to receive(:generate).with(*args)
+ it "uses Grape::Formatter::Json" do
+ expect(Grape::Formatter::Json).to receive(:call).with(obj, env)
- subject.generate(*args)
+ subject
+ end
+ end
end
- end
- describe ".pretty_generate" do
- it "delegates to the adapter" do
- args = [{ foo: "bar" }]
+ context "oj is disabled" do
+ before do
+ stub_feature_flags(oj_json: false)
+ end
- expect(JSON).to receive(:pretty_generate).with(*args)
+ it "generates JSON" do
+ expect(subject).to eq(result)
+ end
+
+ it "uses Grape::Formatter::Json" do
+ expect(Grape::Formatter::Json).to receive(:call).with(obj, env)
- subject.pretty_generate(*args)
+ subject
+ end
end
end
end
diff --git a/spec/lib/gitlab/jwt_authenticatable_spec.rb b/spec/lib/gitlab/jwt_authenticatable_spec.rb
index 0c1c491b308..36bb46cb250 100644
--- a/spec/lib/gitlab/jwt_authenticatable_spec.rb
+++ b/spec/lib/gitlab/jwt_authenticatable_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::JwtAuthenticatable do
+RSpec.describe Gitlab::JwtAuthenticatable do
let(:test_class) do
Class.new do
include Gitlab::JwtAuthenticatable
diff --git a/spec/lib/gitlab/kubernetes/cluster_role_binding_spec.rb b/spec/lib/gitlab/kubernetes/cluster_role_binding_spec.rb
index e1106f7496a..02729d53da8 100644
--- a/spec/lib/gitlab/kubernetes/cluster_role_binding_spec.rb
+++ b/spec/lib/gitlab/kubernetes/cluster_role_binding_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Kubernetes::ClusterRoleBinding do
+RSpec.describe Gitlab::Kubernetes::ClusterRoleBinding do
let(:cluster_role_binding) { described_class.new(name, cluster_role_name, subjects) }
let(:name) { 'cluster-role-binding-name' }
let(:cluster_role_name) { 'cluster-admin' }
diff --git a/spec/lib/gitlab/kubernetes/config_map_spec.rb b/spec/lib/gitlab/kubernetes/config_map_spec.rb
index 0203772e069..2d0d205ffb1 100644
--- a/spec/lib/gitlab/kubernetes/config_map_spec.rb
+++ b/spec/lib/gitlab/kubernetes/config_map_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Kubernetes::ConfigMap do
+RSpec.describe Gitlab::Kubernetes::ConfigMap do
let(:kubeclient) { double('kubernetes client') }
let(:application) { create(:clusters_applications_prometheus) }
let(:config_map) { described_class.new(application.name, application.files) }
diff --git a/spec/lib/gitlab/kubernetes/config_maps/aws_node_auth_spec.rb b/spec/lib/gitlab/kubernetes/config_maps/aws_node_auth_spec.rb
index f701643860a..9c3e41dfbff 100644
--- a/spec/lib/gitlab/kubernetes/config_maps/aws_node_auth_spec.rb
+++ b/spec/lib/gitlab/kubernetes/config_maps/aws_node_auth_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Kubernetes::ConfigMaps::AwsNodeAuth do
+RSpec.describe Gitlab::Kubernetes::ConfigMaps::AwsNodeAuth do
describe '#generate' do
let(:role) { 'arn:aws:iam::123456789012:role/node-instance-role' }
diff --git a/spec/lib/gitlab/kubernetes/default_namespace_spec.rb b/spec/lib/gitlab/kubernetes/default_namespace_spec.rb
index 1fda547f35c..976fe4a0a87 100644
--- a/spec/lib/gitlab/kubernetes/default_namespace_spec.rb
+++ b/spec/lib/gitlab/kubernetes/default_namespace_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Kubernetes::DefaultNamespace do
+RSpec.describe Gitlab::Kubernetes::DefaultNamespace do
let(:generator) { described_class.new(cluster, project: environment.project) }
describe '#from_environment_name' do
diff --git a/spec/lib/gitlab/kubernetes/generic_secret_spec.rb b/spec/lib/gitlab/kubernetes/generic_secret_spec.rb
index fe1d4cc11e6..8b56d76410e 100644
--- a/spec/lib/gitlab/kubernetes/generic_secret_spec.rb
+++ b/spec/lib/gitlab/kubernetes/generic_secret_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Kubernetes::GenericSecret do
+RSpec.describe Gitlab::Kubernetes::GenericSecret do
let(:secret) { described_class.new(name, data, namespace) }
let(:name) { 'example-name' }
let(:data) { 'example-data' }
diff --git a/spec/lib/gitlab/kubernetes/helm/api_spec.rb b/spec/lib/gitlab/kubernetes/helm/api_spec.rb
index 0e4179d5887..dabbab27b13 100644
--- a/spec/lib/gitlab/kubernetes/helm/api_spec.rb
+++ b/spec/lib/gitlab/kubernetes/helm/api_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Kubernetes::Helm::API do
+RSpec.describe Gitlab::Kubernetes::Helm::API do
let(:client) { double('kubernetes client') }
let(:helm) { described_class.new(client) }
let(:gitlab_namespace) { Gitlab::Kubernetes::Helm::NAMESPACE }
diff --git a/spec/lib/gitlab/kubernetes/helm/base_command_spec.rb b/spec/lib/gitlab/kubernetes/helm/base_command_spec.rb
index f9bcb8abdb1..57fafaebf98 100644
--- a/spec/lib/gitlab/kubernetes/helm/base_command_spec.rb
+++ b/spec/lib/gitlab/kubernetes/helm/base_command_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Kubernetes::Helm::BaseCommand do
+RSpec.describe Gitlab::Kubernetes::Helm::BaseCommand do
subject(:base_command) do
test_class.new(rbac)
end
diff --git a/spec/lib/gitlab/kubernetes/helm/certificate_spec.rb b/spec/lib/gitlab/kubernetes/helm/certificate_spec.rb
index 04649353976..b446c5e1149 100644
--- a/spec/lib/gitlab/kubernetes/helm/certificate_spec.rb
+++ b/spec/lib/gitlab/kubernetes/helm/certificate_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe Gitlab::Kubernetes::Helm::Certificate do
+RSpec.describe Gitlab::Kubernetes::Helm::Certificate do
describe '.generate_root' do
subject { described_class.generate_root }
diff --git a/spec/lib/gitlab/kubernetes/helm/delete_command_spec.rb b/spec/lib/gitlab/kubernetes/helm/delete_command_spec.rb
index 2bf8b294821..7b182478cc3 100644
--- a/spec/lib/gitlab/kubernetes/helm/delete_command_spec.rb
+++ b/spec/lib/gitlab/kubernetes/helm/delete_command_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Kubernetes::Helm::DeleteCommand do
+RSpec.describe Gitlab::Kubernetes::Helm::DeleteCommand do
subject(:delete_command) { described_class.new(name: app_name, rbac: rbac, files: files, local_tiller_enabled: local_tiller_enabled) }
let(:app_name) { 'app-name' }
diff --git a/spec/lib/gitlab/kubernetes/helm/init_command_spec.rb b/spec/lib/gitlab/kubernetes/helm/init_command_spec.rb
index 61b8eb30b42..c982a417682 100644
--- a/spec/lib/gitlab/kubernetes/helm/init_command_spec.rb
+++ b/spec/lib/gitlab/kubernetes/helm/init_command_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Kubernetes::Helm::InitCommand do
+RSpec.describe Gitlab::Kubernetes::Helm::InitCommand do
subject(:init_command) { described_class.new(name: application.name, files: files, rbac: rbac, local_tiller_enabled: false) }
let(:application) { create(:clusters_applications_helm) }
diff --git a/spec/lib/gitlab/kubernetes/helm/install_command_spec.rb b/spec/lib/gitlab/kubernetes/helm/install_command_spec.rb
index 6fc91300f5b..aad350256ec 100644
--- a/spec/lib/gitlab/kubernetes/helm/install_command_spec.rb
+++ b/spec/lib/gitlab/kubernetes/helm/install_command_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Kubernetes::Helm::InstallCommand do
+RSpec.describe Gitlab::Kubernetes::Helm::InstallCommand do
subject(:install_command) do
described_class.new(
name: 'app-name',
diff --git a/spec/lib/gitlab/kubernetes/helm/parsers/list_v2_spec.rb b/spec/lib/gitlab/kubernetes/helm/parsers/list_v2_spec.rb
index 0ad5dc189c0..435c296d5f1 100644
--- a/spec/lib/gitlab/kubernetes/helm/parsers/list_v2_spec.rb
+++ b/spec/lib/gitlab/kubernetes/helm/parsers/list_v2_spec.rb
@@ -2,7 +2,7 @@
require 'fast_spec_helper'
-describe Gitlab::Kubernetes::Helm::Parsers::ListV2 do
+RSpec.describe Gitlab::Kubernetes::Helm::Parsers::ListV2 do
let(:valid_file_contents) do
<<~EOF
{
diff --git a/spec/lib/gitlab/kubernetes/helm/patch_command_spec.rb b/spec/lib/gitlab/kubernetes/helm/patch_command_spec.rb
index 8d965a25f84..ea2ade18e37 100644
--- a/spec/lib/gitlab/kubernetes/helm/patch_command_spec.rb
+++ b/spec/lib/gitlab/kubernetes/helm/patch_command_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Kubernetes::Helm::PatchCommand do
+RSpec.describe Gitlab::Kubernetes::Helm::PatchCommand do
let(:files) { { 'ca.pem': 'some file content' } }
let(:repository) { 'https://repository.example.com' }
let(:rbac) { false }
diff --git a/spec/lib/gitlab/kubernetes/helm/pod_spec.rb b/spec/lib/gitlab/kubernetes/helm/pod_spec.rb
index ea32ac96213..54e3289dd25 100644
--- a/spec/lib/gitlab/kubernetes/helm/pod_spec.rb
+++ b/spec/lib/gitlab/kubernetes/helm/pod_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Kubernetes::Helm::Pod do
+RSpec.describe Gitlab::Kubernetes::Helm::Pod do
describe '#generate' do
let(:app) { create(:clusters_applications_prometheus) }
let(:command) { app.install_command }
@@ -32,7 +32,7 @@ describe Gitlab::Kubernetes::Helm::Pod do
it 'generates the appropriate specifications for the container' do
container = subject.generate.spec.containers.first
expect(container.name).to eq('helm')
- expect(container.image).to eq('registry.gitlab.com/gitlab-org/cluster-integration/helm-install-image/releases/2.16.6-kube-1.13.12')
+ expect(container.image).to eq('registry.gitlab.com/gitlab-org/cluster-integration/helm-install-image/releases/2.16.9-kube-1.13.12')
expect(container.env.count).to eq(3)
expect(container.env.map(&:name)).to match_array([:HELM_VERSION, :TILLER_NAMESPACE, :COMMAND_SCRIPT])
expect(container.command).to match_array(["/bin/sh"])
diff --git a/spec/lib/gitlab/kubernetes/helm/reset_command_spec.rb b/spec/lib/gitlab/kubernetes/helm/reset_command_spec.rb
index 3773c428713..8d386d41ad5 100644
--- a/spec/lib/gitlab/kubernetes/helm/reset_command_spec.rb
+++ b/spec/lib/gitlab/kubernetes/helm/reset_command_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Kubernetes::Helm::ResetCommand do
+RSpec.describe Gitlab::Kubernetes::Helm::ResetCommand do
subject(:reset_command) { described_class.new(name: name, rbac: rbac, files: files, local_tiller_enabled: false) }
let(:rbac) { true }
diff --git a/spec/lib/gitlab/kubernetes/kube_client_spec.rb b/spec/lib/gitlab/kubernetes/kube_client_spec.rb
index 32597aa4f5a..a15be42f393 100644
--- a/spec/lib/gitlab/kubernetes/kube_client_spec.rb
+++ b/spec/lib/gitlab/kubernetes/kube_client_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Kubernetes::KubeClient do
+RSpec.describe Gitlab::Kubernetes::KubeClient do
include StubRequests
include KubernetesHelpers
diff --git a/spec/lib/gitlab/kubernetes/kubectl_cmd_spec.rb b/spec/lib/gitlab/kubernetes/kubectl_cmd_spec.rb
index b65d7b9fdc6..e80bb3dfb07 100644
--- a/spec/lib/gitlab/kubernetes/kubectl_cmd_spec.rb
+++ b/spec/lib/gitlab/kubernetes/kubectl_cmd_spec.rb
@@ -2,7 +2,7 @@
require 'fast_spec_helper'
-describe Gitlab::Kubernetes::KubectlCmd do
+RSpec.describe Gitlab::Kubernetes::KubectlCmd do
describe '.delete' do
it 'constructs string properly' do
args = %w(resource_type type --flag-1 --flag-2)
diff --git a/spec/lib/gitlab/kubernetes/namespace_spec.rb b/spec/lib/gitlab/kubernetes/namespace_spec.rb
index 467b10e21b1..5fe4e008469 100644
--- a/spec/lib/gitlab/kubernetes/namespace_spec.rb
+++ b/spec/lib/gitlab/kubernetes/namespace_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Kubernetes::Namespace do
+RSpec.describe Gitlab::Kubernetes::Namespace do
let(:name) { 'a_namespace' }
let(:client) { double('kubernetes client') }
let(:labels) { nil }
diff --git a/spec/lib/gitlab/kubernetes/network_policy_spec.rb b/spec/lib/gitlab/kubernetes/network_policy_spec.rb
index 5a920d78436..a8ca15f998b 100644
--- a/spec/lib/gitlab/kubernetes/network_policy_spec.rb
+++ b/spec/lib/gitlab/kubernetes/network_policy_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Kubernetes::NetworkPolicy do
+RSpec.describe Gitlab::Kubernetes::NetworkPolicy do
let(:policy) do
described_class.new(
name: name,
diff --git a/spec/lib/gitlab/kubernetes/node_spec.rb b/spec/lib/gitlab/kubernetes/node_spec.rb
new file mode 100644
index 00000000000..732bf29bc44
--- /dev/null
+++ b/spec/lib/gitlab/kubernetes/node_spec.rb
@@ -0,0 +1,68 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Kubernetes::Node do
+ include KubernetesHelpers
+
+ describe '#all' do
+ let(:cluster) { create(:cluster, :provided_by_user, :group) }
+ let(:expected_nodes) { [] }
+
+ before do
+ stub_kubeclient_nodes_and_nodes_metrics(cluster.platform.api_url)
+ end
+
+ subject { described_class.new(cluster).all }
+
+ context 'when connection to the cluster is successful' do
+ let(:expected_nodes) { [kube_node.merge(kube_node_metrics)] }
+
+ it { is_expected.to eq(expected_nodes) }
+ end
+
+ context 'when cluster cannot be reached' do
+ before do
+ allow(cluster.kubeclient.core_client).to receive(:discover)
+ .and_raise(SocketError)
+ end
+
+ it { is_expected.to eq(expected_nodes) }
+ end
+
+ context 'when cluster cannot be authenticated to' do
+ before do
+ allow(cluster.kubeclient.core_client).to receive(:discover)
+ .and_raise(OpenSSL::X509::CertificateError.new('Certificate error'))
+ end
+
+ it { is_expected.to eq(expected_nodes) }
+ end
+
+ context 'when Kubeclient::HttpError is raised' do
+ before do
+ allow(cluster.kubeclient.core_client).to receive(:discover)
+ .and_raise(Kubeclient::HttpError.new(403, 'Forbidden', nil))
+ end
+
+ it { is_expected.to eq(expected_nodes) }
+ end
+
+ context 'when an uncategorised error is raised' do
+ before do
+ allow(cluster.kubeclient.core_client).to receive(:discover)
+ .and_raise(StandardError)
+ end
+
+ it { is_expected.to eq(expected_nodes) }
+
+ it 'notifies Sentry' do
+ expect(Gitlab::ErrorTracking).to receive(:track_exception)
+ .with(instance_of(StandardError), hash_including(cluster_id: cluster.id))
+ .once
+
+ subject
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/kubernetes/role_binding_spec.rb b/spec/lib/gitlab/kubernetes/role_binding_spec.rb
index 4c200eb545f..3003fe25301 100644
--- a/spec/lib/gitlab/kubernetes/role_binding_spec.rb
+++ b/spec/lib/gitlab/kubernetes/role_binding_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Kubernetes::RoleBinding, '#generate' do
+RSpec.describe Gitlab::Kubernetes::RoleBinding, '#generate' do
let(:role_name) { 'edit' }
let(:role_kind) { 'ClusterRole' }
let(:namespace) { 'my-namespace' }
diff --git a/spec/lib/gitlab/kubernetes/role_spec.rb b/spec/lib/gitlab/kubernetes/role_spec.rb
index 3a5cd3b6704..acb9b5d4e8e 100644
--- a/spec/lib/gitlab/kubernetes/role_spec.rb
+++ b/spec/lib/gitlab/kubernetes/role_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Kubernetes::Role do
+RSpec.describe Gitlab::Kubernetes::Role do
let(:role) { described_class.new(name: name, namespace: namespace, rules: rules) }
let(:name) { 'example-name' }
let(:namespace) { 'example-namespace' }
diff --git a/spec/lib/gitlab/kubernetes/service_account_spec.rb b/spec/lib/gitlab/kubernetes/service_account_spec.rb
index 0d525966d18..998d1a6d954 100644
--- a/spec/lib/gitlab/kubernetes/service_account_spec.rb
+++ b/spec/lib/gitlab/kubernetes/service_account_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Kubernetes::ServiceAccount do
+RSpec.describe Gitlab::Kubernetes::ServiceAccount do
let(:name) { 'a_service_account' }
let(:namespace_name) { 'a_namespace' }
let(:service_account) { described_class.new(name, namespace_name) }
diff --git a/spec/lib/gitlab/kubernetes/service_account_token_spec.rb b/spec/lib/gitlab/kubernetes/service_account_token_spec.rb
index 0d334bed45f..18423fdb2a3 100644
--- a/spec/lib/gitlab/kubernetes/service_account_token_spec.rb
+++ b/spec/lib/gitlab/kubernetes/service_account_token_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Kubernetes::ServiceAccountToken do
+RSpec.describe Gitlab::Kubernetes::ServiceAccountToken do
let(:name) { 'token-name' }
let(:service_account_name) { 'a_service_account' }
let(:namespace_name) { 'a_namespace' }
diff --git a/spec/lib/gitlab/kubernetes/tls_secret_spec.rb b/spec/lib/gitlab/kubernetes/tls_secret_spec.rb
index 438a0dc79fc..b40ed3c88c9 100644
--- a/spec/lib/gitlab/kubernetes/tls_secret_spec.rb
+++ b/spec/lib/gitlab/kubernetes/tls_secret_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Kubernetes::TlsSecret do
+RSpec.describe Gitlab::Kubernetes::TlsSecret do
let(:secret) { described_class.new(name, cert, key, namespace) }
let(:name) { 'example-name' }
let(:cert) { 'example-cert' }
diff --git a/spec/lib/gitlab/kubernetes_spec.rb b/spec/lib/gitlab/kubernetes_spec.rb
index 40c3e7d0b3c..eff5c019ad0 100644
--- a/spec/lib/gitlab/kubernetes_spec.rb
+++ b/spec/lib/gitlab/kubernetes_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Kubernetes do
+RSpec.describe Gitlab::Kubernetes do
include KubernetesHelpers
include described_class
diff --git a/spec/lib/gitlab/language_data_spec.rb b/spec/lib/gitlab/language_data_spec.rb
index b08150855fe..bb4b0c3855c 100644
--- a/spec/lib/gitlab/language_data_spec.rb
+++ b/spec/lib/gitlab/language_data_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::LanguageData do
+RSpec.describe Gitlab::LanguageData do
describe '#extensions' do
before do
described_class.clear_extensions!
diff --git a/spec/lib/gitlab/language_detection_spec.rb b/spec/lib/gitlab/language_detection_spec.rb
index 770fe2f80db..04ad19a04ec 100644
--- a/spec/lib/gitlab/language_detection_spec.rb
+++ b/spec/lib/gitlab/language_detection_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::LanguageDetection do
+RSpec.describe Gitlab::LanguageDetection do
let_it_be(:project) { create(:project, :repository) }
let_it_be(:ruby) { create(:programming_language, name: 'Ruby') }
let_it_be(:haskell) { create(:programming_language, name: 'Haskell') }
diff --git a/spec/lib/gitlab/lazy_spec.rb b/spec/lib/gitlab/lazy_spec.rb
index 19758a18589..3e929cf200a 100644
--- a/spec/lib/gitlab/lazy_spec.rb
+++ b/spec/lib/gitlab/lazy_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Lazy do
+RSpec.describe Gitlab::Lazy do
let(:dummy) { double(:dummy) }
context 'when not calling any methods' do
diff --git a/spec/lib/gitlab/legacy_github_import/branch_formatter_spec.rb b/spec/lib/gitlab/legacy_github_import/branch_formatter_spec.rb
index e96745f5fbe..1a21ed29ab7 100644
--- a/spec/lib/gitlab/legacy_github_import/branch_formatter_spec.rb
+++ b/spec/lib/gitlab/legacy_github_import/branch_formatter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::LegacyGithubImport::BranchFormatter do
+RSpec.describe Gitlab::LegacyGithubImport::BranchFormatter do
let(:project) { create(:project, :repository) }
let(:commit) { create(:commit, project: project) }
let(:repo) { double }
diff --git a/spec/lib/gitlab/legacy_github_import/client_spec.rb b/spec/lib/gitlab/legacy_github_import/client_spec.rb
index d266b39bd81..0929b90d1f4 100644
--- a/spec/lib/gitlab/legacy_github_import/client_spec.rb
+++ b/spec/lib/gitlab/legacy_github_import/client_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::LegacyGithubImport::Client do
+RSpec.describe Gitlab::LegacyGithubImport::Client do
let(:token) { '123456' }
let(:github_provider) { Settingslogic.new('app_id' => 'asd123', 'app_secret' => 'asd123', 'name' => 'github', 'args' => { 'client_options' => {} }) }
let(:wait_for_rate_limit_reset) { true }
diff --git a/spec/lib/gitlab/legacy_github_import/comment_formatter_spec.rb b/spec/lib/gitlab/legacy_github_import/comment_formatter_spec.rb
index 0f03db312ce..85f7666fe85 100644
--- a/spec/lib/gitlab/legacy_github_import/comment_formatter_spec.rb
+++ b/spec/lib/gitlab/legacy_github_import/comment_formatter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::LegacyGithubImport::CommentFormatter do
+RSpec.describe Gitlab::LegacyGithubImport::CommentFormatter do
let(:client) { double }
let(:project) { create(:project) }
let(:octocat) { double(id: 123456, login: 'octocat', email: 'octocat@example.com') }
diff --git a/spec/lib/gitlab/legacy_github_import/importer_spec.rb b/spec/lib/gitlab/legacy_github_import/importer_spec.rb
index 8cc3fd8efbd..c443944678f 100644
--- a/spec/lib/gitlab/legacy_github_import/importer_spec.rb
+++ b/spec/lib/gitlab/legacy_github_import/importer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::LegacyGithubImport::Importer do
+RSpec.describe Gitlab::LegacyGithubImport::Importer do
shared_examples 'Gitlab::LegacyGithubImport::Importer#execute' do
let(:expected_not_called) { [] }
diff --git a/spec/lib/gitlab/legacy_github_import/issuable_formatter_spec.rb b/spec/lib/gitlab/legacy_github_import/issuable_formatter_spec.rb
index f5bfc379e89..6a51cb6f39d 100644
--- a/spec/lib/gitlab/legacy_github_import/issuable_formatter_spec.rb
+++ b/spec/lib/gitlab/legacy_github_import/issuable_formatter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::LegacyGithubImport::IssuableFormatter do
+RSpec.describe Gitlab::LegacyGithubImport::IssuableFormatter do
let(:raw_data) do
double(number: 42)
end
diff --git a/spec/lib/gitlab/legacy_github_import/issue_formatter_spec.rb b/spec/lib/gitlab/legacy_github_import/issue_formatter_spec.rb
index 9a7a34afbe7..4b1e0d2c144 100644
--- a/spec/lib/gitlab/legacy_github_import/issue_formatter_spec.rb
+++ b/spec/lib/gitlab/legacy_github_import/issue_formatter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::LegacyGithubImport::IssueFormatter do
+RSpec.describe Gitlab::LegacyGithubImport::IssueFormatter do
let(:client) { double }
let!(:project) { create(:project, namespace: create(:namespace, path: 'octocat')) }
let(:octocat) { double(id: 123456, login: 'octocat', email: 'octocat@example.com') }
diff --git a/spec/lib/gitlab/legacy_github_import/label_formatter_spec.rb b/spec/lib/gitlab/legacy_github_import/label_formatter_spec.rb
index e56e2772f6a..ab7c8ea4a58 100644
--- a/spec/lib/gitlab/legacy_github_import/label_formatter_spec.rb
+++ b/spec/lib/gitlab/legacy_github_import/label_formatter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::LegacyGithubImport::LabelFormatter do
+RSpec.describe Gitlab::LegacyGithubImport::LabelFormatter do
let(:project) { create(:project) }
let(:raw) { double(name: 'improvements', color: 'e6e6e6') }
diff --git a/spec/lib/gitlab/legacy_github_import/milestone_formatter_spec.rb b/spec/lib/gitlab/legacy_github_import/milestone_formatter_spec.rb
index f5d71888ac9..2ac79c4f5b8 100644
--- a/spec/lib/gitlab/legacy_github_import/milestone_formatter_spec.rb
+++ b/spec/lib/gitlab/legacy_github_import/milestone_formatter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::LegacyGithubImport::MilestoneFormatter do
+RSpec.describe Gitlab::LegacyGithubImport::MilestoneFormatter do
let(:project) { create(:project) }
let(:created_at) { DateTime.strptime('2011-01-26T19:01:12Z') }
let(:updated_at) { DateTime.strptime('2011-01-27T19:01:12Z') }
diff --git a/spec/lib/gitlab/legacy_github_import/project_creator_spec.rb b/spec/lib/gitlab/legacy_github_import/project_creator_spec.rb
index b0687474c80..02cc2eba4da 100644
--- a/spec/lib/gitlab/legacy_github_import/project_creator_spec.rb
+++ b/spec/lib/gitlab/legacy_github_import/project_creator_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::LegacyGithubImport::ProjectCreator do
+RSpec.describe Gitlab::LegacyGithubImport::ProjectCreator do
let(:user) { create(:user) }
let(:namespace) { create(:group) }
diff --git a/spec/lib/gitlab/legacy_github_import/pull_request_formatter_spec.rb b/spec/lib/gitlab/legacy_github_import/pull_request_formatter_spec.rb
index 622210508b9..3e6b9340d0b 100644
--- a/spec/lib/gitlab/legacy_github_import/pull_request_formatter_spec.rb
+++ b/spec/lib/gitlab/legacy_github_import/pull_request_formatter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::LegacyGithubImport::PullRequestFormatter do
+RSpec.describe Gitlab::LegacyGithubImport::PullRequestFormatter do
let(:client) { double }
let(:project) { create(:project, :repository) }
let(:source_sha) { create(:commit, project: project).id }
diff --git a/spec/lib/gitlab/legacy_github_import/release_formatter_spec.rb b/spec/lib/gitlab/legacy_github_import/release_formatter_spec.rb
index 554be57fbec..73b35d3a4e7 100644
--- a/spec/lib/gitlab/legacy_github_import/release_formatter_spec.rb
+++ b/spec/lib/gitlab/legacy_github_import/release_formatter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::LegacyGithubImport::ReleaseFormatter do
+RSpec.describe Gitlab::LegacyGithubImport::ReleaseFormatter do
let!(:project) { create(:project, namespace: create(:namespace, path: 'octocat')) }
let(:octocat) { double(id: 123456, login: 'octocat') }
let(:created_at) { DateTime.strptime('2011-01-26T19:01:12Z') }
diff --git a/spec/lib/gitlab/legacy_github_import/user_formatter_spec.rb b/spec/lib/gitlab/legacy_github_import/user_formatter_spec.rb
index 919847fe061..34659030020 100644
--- a/spec/lib/gitlab/legacy_github_import/user_formatter_spec.rb
+++ b/spec/lib/gitlab/legacy_github_import/user_formatter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::LegacyGithubImport::UserFormatter do
+RSpec.describe Gitlab::LegacyGithubImport::UserFormatter do
let(:client) { double }
let(:octocat) { double(id: 123456, login: 'octocat', email: 'octocat@example.com') }
diff --git a/spec/lib/gitlab/legacy_github_import/wiki_formatter_spec.rb b/spec/lib/gitlab/legacy_github_import/wiki_formatter_spec.rb
index 639fb9d80eb..df443fa3dbb 100644
--- a/spec/lib/gitlab/legacy_github_import/wiki_formatter_spec.rb
+++ b/spec/lib/gitlab/legacy_github_import/wiki_formatter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::LegacyGithubImport::WikiFormatter do
+RSpec.describe Gitlab::LegacyGithubImport::WikiFormatter do
let(:project) do
create(:project,
namespace: create(:namespace, path: 'gitlabhq'),
diff --git a/spec/lib/gitlab/lets_encrypt/challenge_spec.rb b/spec/lib/gitlab/lets_encrypt/challenge_spec.rb
index fcd92586362..d853275520b 100644
--- a/spec/lib/gitlab/lets_encrypt/challenge_spec.rb
+++ b/spec/lib/gitlab/lets_encrypt/challenge_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ::Gitlab::LetsEncrypt::Challenge do
+RSpec.describe ::Gitlab::LetsEncrypt::Challenge do
include LetsEncryptHelpers
let(:challenge) { described_class.new(acme_challenge_double) }
diff --git a/spec/lib/gitlab/lets_encrypt/client_spec.rb b/spec/lib/gitlab/lets_encrypt/client_spec.rb
index e86de04b5cf..54b9bd3bfba 100644
--- a/spec/lib/gitlab/lets_encrypt/client_spec.rb
+++ b/spec/lib/gitlab/lets_encrypt/client_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ::Gitlab::LetsEncrypt::Client do
+RSpec.describe ::Gitlab::LetsEncrypt::Client do
include LetsEncryptHelpers
let(:client) { described_class.new }
diff --git a/spec/lib/gitlab/lets_encrypt/order_spec.rb b/spec/lib/gitlab/lets_encrypt/order_spec.rb
index 333fce8e36a..419f9e28871 100644
--- a/spec/lib/gitlab/lets_encrypt/order_spec.rb
+++ b/spec/lib/gitlab/lets_encrypt/order_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ::Gitlab::LetsEncrypt::Order do
+RSpec.describe ::Gitlab::LetsEncrypt::Order do
include LetsEncryptHelpers
let(:acme_order) { acme_order_double }
diff --git a/spec/lib/gitlab/lets_encrypt_spec.rb b/spec/lib/gitlab/lets_encrypt_spec.rb
index 2229393fb32..7597359847b 100644
--- a/spec/lib/gitlab/lets_encrypt_spec.rb
+++ b/spec/lib/gitlab/lets_encrypt_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ::Gitlab::LetsEncrypt do
+RSpec.describe ::Gitlab::LetsEncrypt do
include LetsEncryptHelpers
before do
diff --git a/spec/lib/gitlab/lfs_token_spec.rb b/spec/lib/gitlab/lfs_token_spec.rb
index 58a3767b242..9b8b2c1417a 100644
--- a/spec/lib/gitlab/lfs_token_spec.rb
+++ b/spec/lib/gitlab/lfs_token_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::LfsToken, :clean_gitlab_redis_shared_state do
+RSpec.describe Gitlab::LfsToken, :clean_gitlab_redis_shared_state do
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project) }
let_it_be(:deploy_key) { create(:deploy_key) }
diff --git a/spec/lib/gitlab/log_timestamp_formatter_spec.rb b/spec/lib/gitlab/log_timestamp_formatter_spec.rb
index 1a76d02889b..e06baa2324f 100644
--- a/spec/lib/gitlab/log_timestamp_formatter_spec.rb
+++ b/spec/lib/gitlab/log_timestamp_formatter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::LogTimestampFormatter do
+RSpec.describe Gitlab::LogTimestampFormatter do
subject { described_class.new }
let(:formatted_timestamp) { Time.now.utc.iso8601(3) }
diff --git a/spec/lib/gitlab/logging/cloudflare_helper_spec.rb b/spec/lib/gitlab/logging/cloudflare_helper_spec.rb
index 2b73fb7bc1c..87ac438d38d 100644
--- a/spec/lib/gitlab/logging/cloudflare_helper_spec.rb
+++ b/spec/lib/gitlab/logging/cloudflare_helper_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Logging::CloudflareHelper do
+RSpec.describe Gitlab::Logging::CloudflareHelper do
let(:helper) do
Class.new do
include Gitlab::Logging::CloudflareHelper
diff --git a/spec/lib/gitlab/lograge/custom_options_spec.rb b/spec/lib/gitlab/lograge/custom_options_spec.rb
index ebf150d21ef..218007c6e2a 100644
--- a/spec/lib/gitlab/lograge/custom_options_spec.rb
+++ b/spec/lib/gitlab/lograge/custom_options_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Lograge::CustomOptions do
+RSpec.describe Gitlab::Lograge::CustomOptions do
describe '.call' do
let(:params) do
{
@@ -44,18 +44,6 @@ describe Gitlab::Lograge::CustomOptions do
end
end
- context 'with transaction' do
- let(:transaction) { Gitlab::Metrics::WebTransaction.new({}) }
-
- before do
- allow(Gitlab::Metrics::Transaction).to receive(:current).and_return(transaction)
- end
-
- it 'adds db counters' do
- expect(subject).to include(:db_count, :db_write_count, :db_cached_count)
- end
- end
-
it 'adds the user id' do
expect(subject[:user_id]).to eq('test')
end
@@ -77,14 +65,14 @@ describe Gitlab::Lograge::CustomOptions do
end
end
- context 'when correlation_id is overriden' do
+ context 'when correlation_id is overridden' do
let(:correlation_id_key) { Labkit::Correlation::CorrelationId::LOG_KEY }
before do
event_payload[correlation_id_key] = '123456'
end
- it 'sets the overriden value' do
+ it 'sets the overridden value' do
expect(subject[correlation_id_key]).to eq('123456')
end
end
diff --git a/spec/lib/gitlab/loop_helpers_spec.rb b/spec/lib/gitlab/loop_helpers_spec.rb
index 7e59b41d5b9..0535cb6068c 100644
--- a/spec/lib/gitlab/loop_helpers_spec.rb
+++ b/spec/lib/gitlab/loop_helpers_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::LoopHelpers do
+RSpec.describe Gitlab::LoopHelpers do
let(:class_instance) { (Class.new { include ::Gitlab::LoopHelpers }).new }
describe '#loop_until' do
diff --git a/spec/lib/gitlab/mail_room/mail_room_spec.rb b/spec/lib/gitlab/mail_room/mail_room_spec.rb
index 4b09205a181..ab9a9a035f1 100644
--- a/spec/lib/gitlab/mail_room/mail_room_spec.rb
+++ b/spec/lib/gitlab/mail_room/mail_room_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::MailRoom do
+RSpec.describe Gitlab::MailRoom do
let(:default_port) { 143 }
let(:yml_config) do
{
diff --git a/spec/lib/gitlab/manifest_import/manifest_spec.rb b/spec/lib/gitlab/manifest_import/manifest_spec.rb
index c1135f710ea..2e8753b0880 100644
--- a/spec/lib/gitlab/manifest_import/manifest_spec.rb
+++ b/spec/lib/gitlab/manifest_import/manifest_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::ManifestImport::Manifest do
+RSpec.describe Gitlab::ManifestImport::Manifest do
let(:file) { File.open(Rails.root.join('spec/fixtures/aosp_manifest.xml')) }
let(:manifest) { described_class.new(file) }
diff --git a/spec/lib/gitlab/manifest_import/project_creator_spec.rb b/spec/lib/gitlab/manifest_import/project_creator_spec.rb
index a8cfcfb41d3..354acf53b7a 100644
--- a/spec/lib/gitlab/manifest_import/project_creator_spec.rb
+++ b/spec/lib/gitlab/manifest_import/project_creator_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::ManifestImport::ProjectCreator do
+RSpec.describe Gitlab::ManifestImport::ProjectCreator do
let(:group) { create(:group) }
let(:user) { create(:user) }
let(:repository) do
diff --git a/spec/lib/gitlab/markdown_cache/active_record/extension_spec.rb b/spec/lib/gitlab/markdown_cache/active_record/extension_spec.rb
index 5b6c769d6eb..be562d916d3 100644
--- a/spec/lib/gitlab/markdown_cache/active_record/extension_spec.rb
+++ b/spec/lib/gitlab/markdown_cache/active_record/extension_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe Gitlab::MarkdownCache::ActiveRecord::Extension do
+RSpec.describe Gitlab::MarkdownCache::ActiveRecord::Extension do
let(:klass) do
Class.new(ActiveRecord::Base) do
self.table_name = 'issues'
diff --git a/spec/lib/gitlab/markdown_cache/field_data_spec.rb b/spec/lib/gitlab/markdown_cache/field_data_spec.rb
index 393bf85aa43..76d8cbe6b7d 100644
--- a/spec/lib/gitlab/markdown_cache/field_data_spec.rb
+++ b/spec/lib/gitlab/markdown_cache/field_data_spec.rb
@@ -2,7 +2,7 @@
require 'fast_spec_helper'
-describe Gitlab::MarkdownCache::FieldData do
+RSpec.describe Gitlab::MarkdownCache::FieldData do
subject(:field_data) { described_class.new }
before do
diff --git a/spec/lib/gitlab/markdown_cache/redis/extension_spec.rb b/spec/lib/gitlab/markdown_cache/redis/extension_spec.rb
index b6a781de426..3dcb9f160ba 100644
--- a/spec/lib/gitlab/markdown_cache/redis/extension_spec.rb
+++ b/spec/lib/gitlab/markdown_cache/redis/extension_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe Gitlab::MarkdownCache::Redis::Extension, :clean_gitlab_redis_cache do
+RSpec.describe Gitlab::MarkdownCache::Redis::Extension, :clean_gitlab_redis_cache do
let(:klass) do
Class.new do
include CacheMarkdownField
@@ -49,6 +49,31 @@ describe Gitlab::MarkdownCache::Redis::Extension, :clean_gitlab_redis_cache do
expect(thing.cached_markdown_version).to eq(cache_version)
end
+ describe '.preload_markdown_cache!' do
+ before do
+ Gitlab::Redis::Cache.with do |r|
+ r.mapped_hmset(expected_cache_key,
+ title_html: 'hello',
+ description_html: 'world',
+ cached_markdown_version: cache_version)
+ end
+ end
+
+ it 'does not preload the markdown twice' do
+ expect(Gitlab::MarkdownCache::Redis::Store).to receive(:bulk_read).and_call_original
+ expect(Gitlab::Redis::Cache).to receive(:with).twice.and_call_original
+
+ klass.preload_markdown_cache!([thing])
+
+ aggregate_failures do
+ expect(Gitlab::Redis::Cache).not_to receive(:with)
+ expect(thing.title_html).to eq('hello')
+ expect(thing.description_html).to eq('world')
+ expect(thing.cached_markdown_version).to eq(cache_version)
+ end
+ end
+ end
+
describe "#refresh_markdown_cache!" do
it "stores the value in redis" do
expected_results = { "title_html" => "`Hello`",
diff --git a/spec/lib/gitlab/markdown_cache/redis/store_spec.rb b/spec/lib/gitlab/markdown_cache/redis/store_spec.rb
index 95c68e7d491..40ff9a765a6 100644
--- a/spec/lib/gitlab/markdown_cache/redis/store_spec.rb
+++ b/spec/lib/gitlab/markdown_cache/redis/store_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe Gitlab::MarkdownCache::Redis::Store, :clean_gitlab_redis_cache do
+RSpec.describe Gitlab::MarkdownCache::Redis::Store, :clean_gitlab_redis_cache do
let(:storable_class) do
Class.new do
cattr_reader :cached_markdown_fields do
@@ -37,6 +37,23 @@ describe Gitlab::MarkdownCache::Redis::Store, :clean_gitlab_redis_cache do
end
end
+ describe '.bulk_read' do
+ before do
+ store.save(field_1_html: "hello", field_2_html: "world", cached_markdown_version: 1)
+ end
+
+ it 'returns a hash of values from store' do
+ Gitlab::Redis::Cache.with do |redis|
+ expect(redis).to receive(:pipelined).and_call_original
+ end
+
+ results = described_class.bulk_read([storable])
+
+ expect(results[storable.cache_key].value.symbolize_keys)
+ .to eq(field_1_html: "hello", field_2_html: "world", cached_markdown_version: "1")
+ end
+ end
+
describe '#save' do
it 'stores updates to html fields and version' do
values_to_store = { field_1_html: "hello", field_2_html: "world", cached_markdown_version: 1 }
@@ -44,7 +61,7 @@ describe Gitlab::MarkdownCache::Redis::Store, :clean_gitlab_redis_cache do
store.save(values_to_store)
expect(read_values)
- .to eq({ field_1_html: "hello", field_2_html: "world", cached_markdown_version: "1" })
+ .to eq(field_1_html: "hello", field_2_html: "world", cached_markdown_version: "1")
end
end
@@ -54,7 +71,8 @@ describe Gitlab::MarkdownCache::Redis::Store, :clean_gitlab_redis_cache do
store_values(stored_values)
- expect(store.read.symbolize_keys).to eq(stored_values)
+ expect(store.read.symbolize_keys)
+ .to eq(field_1_html: "hello", field_2_html: "world", cached_markdown_version: "1")
end
it 'is mared loaded after reading' do
diff --git a/spec/lib/gitlab/markup_helper_spec.rb b/spec/lib/gitlab/markup_helper_spec.rb
index b93538cae5a..bf5415ba1d7 100644
--- a/spec/lib/gitlab/markup_helper_spec.rb
+++ b/spec/lib/gitlab/markup_helper_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::MarkupHelper do
+RSpec.describe Gitlab::MarkupHelper do
describe '#markup?' do
%w(textile rdoc org creole wiki
mediawiki rst adoc ad asciidoc mdown md markdown).each do |type|
diff --git a/spec/lib/gitlab/metrics/background_transaction_spec.rb b/spec/lib/gitlab/metrics/background_transaction_spec.rb
index 84f405d7369..640bbebf0da 100644
--- a/spec/lib/gitlab/metrics/background_transaction_spec.rb
+++ b/spec/lib/gitlab/metrics/background_transaction_spec.rb
@@ -2,14 +2,23 @@
require 'spec_helper'
-describe Gitlab::Metrics::BackgroundTransaction do
+RSpec.describe Gitlab::Metrics::BackgroundTransaction do
let(:test_worker_class) { double(:class, name: 'TestWorker') }
subject { described_class.new(test_worker_class) }
describe '#label' do
it 'returns labels based on class name' do
- expect(subject.labels).to eq(controller: 'TestWorker', action: 'perform')
+ expect(subject.labels).to eq(controller: 'TestWorker', action: 'perform', feature_category: '')
+ end
+
+ it 'contains only the labels defined for metrics' do
+ expect(subject.labels.keys).to contain_exactly(*described_class.superclass::BASE_LABELS.keys)
+ end
+
+ it 'includes the feature category if there is one' do
+ expect(test_worker_class).to receive(:get_feature_category).and_return('source_code_management')
+ expect(subject.labels).to include(feature_category: 'source_code_management')
end
end
end
diff --git a/spec/lib/gitlab/metrics/dashboard/defaults_spec.rb b/spec/lib/gitlab/metrics/dashboard/defaults_spec.rb
index 420b246b3f5..dd61f8ebc4d 100644
--- a/spec/lib/gitlab/metrics/dashboard/defaults_spec.rb
+++ b/spec/lib/gitlab/metrics/dashboard/defaults_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Metrics::Dashboard::Defaults do
+RSpec.describe Gitlab::Metrics::Dashboard::Defaults do
it { is_expected.to be_const_defined(:DEFAULT_PANEL_TYPE) }
it { is_expected.to be_const_defined(:DEFAULT_PANEL_WEIGHT) }
end
diff --git a/spec/lib/gitlab/metrics/dashboard/finder_spec.rb b/spec/lib/gitlab/metrics/dashboard/finder_spec.rb
index 2703339d89c..60e1e29d4c5 100644
--- a/spec/lib/gitlab/metrics/dashboard/finder_spec.rb
+++ b/spec/lib/gitlab/metrics/dashboard/finder_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Metrics::Dashboard::Finder, :use_clean_rails_memory_store_caching do
+RSpec.describe Gitlab::Metrics::Dashboard::Finder, :use_clean_rails_memory_store_caching do
include MetricsDashboardHelpers
let_it_be(:project) { create(:project) }
@@ -118,7 +118,7 @@ describe Gitlab::Metrics::Dashboard::Finder, :use_clean_rails_memory_store_cachi
end
describe '.find_raw' do
- let(:dashboard) { YAML.load_file(Rails.root.join('config', 'prometheus', 'common_metrics.yml')) }
+ let(:dashboard) { load_dashboard_yaml(File.read(Rails.root.join('config', 'prometheus', 'common_metrics.yml'))) }
let(:params) { {} }
subject { described_class.find_raw(project, **params) }
@@ -132,7 +132,7 @@ describe Gitlab::Metrics::Dashboard::Finder, :use_clean_rails_memory_store_cachi
end
context 'when an existing project dashboard is specified' do
- let(:dashboard) { YAML.safe_load(fixture_file('lib/gitlab/metrics/dashboard/sample_dashboard.yml')) }
+ let(:dashboard) { load_sample_dashboard }
let(:params) { { dashboard_path: '.gitlab/dashboards/test.yml' } }
let(:project) { project_with_dashboard(params[:dashboard_path]) }
@@ -142,7 +142,7 @@ describe Gitlab::Metrics::Dashboard::Finder, :use_clean_rails_memory_store_cachi
describe '.find_all_paths' do
let(:all_dashboard_paths) { described_class.find_all_paths(project) }
- let(:system_dashboard) { { path: system_dashboard_path, display_name: 'Default dashboard', default: true, system_dashboard: true } }
+ let(:system_dashboard) { { path: system_dashboard_path, display_name: 'Default dashboard', default: true, system_dashboard: true, out_of_the_box_dashboard: true } }
it 'includes only the system dashboard by default' do
expect(all_dashboard_paths).to eq([system_dashboard])
@@ -153,7 +153,7 @@ describe Gitlab::Metrics::Dashboard::Finder, :use_clean_rails_memory_store_cachi
let(:project) { project_with_dashboard(dashboard_path) }
it 'includes system and project dashboards' do
- project_dashboard = { path: dashboard_path, display_name: 'test.yml', default: false, system_dashboard: false }
+ project_dashboard = { path: dashboard_path, display_name: 'test.yml', default: false, system_dashboard: false, out_of_the_box_dashboard: false }
expect(all_dashboard_paths).to contain_exactly(system_dashboard, project_dashboard)
end
@@ -165,7 +165,8 @@ describe Gitlab::Metrics::Dashboard::Finder, :use_clean_rails_memory_store_cachi
path: self_monitoring_dashboard_path,
display_name: 'Default dashboard',
default: true,
- system_dashboard: false
+ system_dashboard: false,
+ out_of_the_box_dashboard: true
}
end
let(:dashboard_path) { '.gitlab/dashboards/test.yml' }
@@ -180,7 +181,8 @@ describe Gitlab::Metrics::Dashboard::Finder, :use_clean_rails_memory_store_cachi
path: dashboard_path,
display_name: 'test.yml',
default: false,
- system_dashboard: false
+ system_dashboard: false,
+ out_of_the_box_dashboard: false
}
expect(all_dashboard_paths).to contain_exactly(self_monitoring_dashboard, project_dashboard)
diff --git a/spec/lib/gitlab/metrics/dashboard/processor_spec.rb b/spec/lib/gitlab/metrics/dashboard/processor_spec.rb
index 7250cefb9ff..7f7070dfafb 100644
--- a/spec/lib/gitlab/metrics/dashboard/processor_spec.rb
+++ b/spec/lib/gitlab/metrics/dashboard/processor_spec.rb
@@ -2,10 +2,12 @@
require 'spec_helper'
-describe Gitlab::Metrics::Dashboard::Processor do
+RSpec.describe Gitlab::Metrics::Dashboard::Processor do
+ include MetricsDashboardHelpers
+
let(:project) { build(:project) }
let(:environment) { create(:environment, project: project) }
- let(:dashboard_yml) { YAML.load_file('spec/fixtures/lib/gitlab/metrics/dashboard/sample_dashboard.yml') }
+ let(:dashboard_yml) { load_sample_dashboard }
describe 'process' do
let(:sequence) do
@@ -13,7 +15,7 @@ describe Gitlab::Metrics::Dashboard::Processor do
Gitlab::Metrics::Dashboard::Stages::CommonMetricsInserter,
Gitlab::Metrics::Dashboard::Stages::CustomMetricsInserter,
Gitlab::Metrics::Dashboard::Stages::CustomMetricsDetailsInserter,
- Gitlab::Metrics::Dashboard::Stages::EndpointInserter,
+ Gitlab::Metrics::Dashboard::Stages::MetricEndpointInserter,
Gitlab::Metrics::Dashboard::Stages::Sorter,
Gitlab::Metrics::Dashboard::Stages::AlertsInserter,
Gitlab::Metrics::Dashboard::Stages::PanelIdsInserter,
@@ -98,7 +100,7 @@ describe Gitlab::Metrics::Dashboard::Processor do
let(:sequence) do
[
Gitlab::Metrics::Dashboard::Stages::CommonMetricsInserter,
- Gitlab::Metrics::Dashboard::Stages::EndpointInserter,
+ Gitlab::Metrics::Dashboard::Stages::MetricEndpointInserter,
Gitlab::Metrics::Dashboard::Stages::Sorter
]
end
@@ -202,27 +204,6 @@ describe Gitlab::Metrics::Dashboard::Processor do
it_behaves_like 'errors with message', 'Each "metric" must define one of :query or :query_range'
end
-
- describe 'validating links' do
- context 'when the links contain a blocked url' do
- let(:dashboard_yml_links) do
- [{ 'url' => 'http://1.1.1.1.1' }, { 'url' => 'https://gitlab.com' }]
- end
-
- let(:expected) do
- [{ url: '' }, { url: 'https://gitlab.com' }]
- end
-
- before do
- stub_env('RSPEC_ALLOW_INVALID_URLS', 'false')
- dashboard_yml['links'] = dashboard_yml_links
- end
-
- it 'replaces the blocked url with an empty string' do
- expect(dashboard[:links]).to eq(expected)
- end
- end
- end
end
private
diff --git a/spec/lib/gitlab/metrics/dashboard/service_selector_spec.rb b/spec/lib/gitlab/metrics/dashboard/service_selector_spec.rb
index 245c98cdd00..f3c8209e0b6 100644
--- a/spec/lib/gitlab/metrics/dashboard/service_selector_spec.rb
+++ b/spec/lib/gitlab/metrics/dashboard/service_selector_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Metrics::Dashboard::ServiceSelector do
+RSpec.describe Gitlab::Metrics::Dashboard::ServiceSelector do
include MetricsDashboardHelpers
describe '#call' do
@@ -109,6 +109,46 @@ describe Gitlab::Metrics::Dashboard::ServiceSelector do
it { is_expected.to be Metrics::Dashboard::TransientEmbedService }
end
+
+ context 'when cluster is provided' do
+ let(:arguments) { { cluster: "some cluster" } }
+
+ it { is_expected.to be Metrics::Dashboard::ClusterDashboardService }
+ end
+
+ context 'when cluster is provided and embedded is not true' do
+ let(:arguments) { { cluster: "some cluster", embedded: 'false' } }
+
+ it { is_expected.to be Metrics::Dashboard::ClusterDashboardService }
+ end
+
+ context 'when cluster dashboard_path is provided' do
+ let(:arguments) { { dashboard_path: ::Metrics::Dashboard::ClusterDashboardService::DASHBOARD_PATH } }
+
+ it { is_expected.to be Metrics::Dashboard::ClusterDashboardService }
+ end
+
+ context 'when cluster is provided and embed params' do
+ let(:arguments) do
+ {
+ cluster: "some cluster",
+ embedded: 'true',
+ cluster_type: 'project',
+ format: :json,
+ group: 'Food metrics',
+ title: 'Pizza Consumption',
+ y_label: 'Slice Count'
+ }
+ end
+
+ it { is_expected.to be Metrics::Dashboard::ClusterMetricsEmbedService }
+ end
+
+ context 'when metrics embed is for an alert' do
+ let(:arguments) { { embedded: true, prometheus_alert_id: 5 } }
+
+ it { is_expected.to be Metrics::Dashboard::GitlabAlertEmbedService }
+ end
end
end
end
diff --git a/spec/lib/gitlab/metrics/dashboard/stages/grafana_formatter_spec.rb b/spec/lib/gitlab/metrics/dashboard/stages/grafana_formatter_spec.rb
index 5d4bd4512e3..8a236f72a60 100644
--- a/spec/lib/gitlab/metrics/dashboard/stages/grafana_formatter_spec.rb
+++ b/spec/lib/gitlab/metrics/dashboard/stages/grafana_formatter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Metrics::Dashboard::Stages::GrafanaFormatter do
+RSpec.describe Gitlab::Metrics::Dashboard::Stages::GrafanaFormatter do
include GrafanaApiHelpers
let_it_be(:namespace) { create(:namespace, name: 'foo') }
diff --git a/spec/lib/gitlab/metrics/dashboard/stages/panel_ids_inserter_spec.rb b/spec/lib/gitlab/metrics/dashboard/stages/panel_ids_inserter_spec.rb
index 6124f471e39..7a3a9021f86 100644
--- a/spec/lib/gitlab/metrics/dashboard/stages/panel_ids_inserter_spec.rb
+++ b/spec/lib/gitlab/metrics/dashboard/stages/panel_ids_inserter_spec.rb
@@ -2,7 +2,9 @@
require 'spec_helper'
-describe Gitlab::Metrics::Dashboard::Stages::PanelIdsInserter do
+RSpec.describe Gitlab::Metrics::Dashboard::Stages::PanelIdsInserter do
+ include MetricsDashboardHelpers
+
let(:project) { build_stubbed(:project) }
def fetch_panel_ids(dashboard_hash)
@@ -12,7 +14,7 @@ describe Gitlab::Metrics::Dashboard::Stages::PanelIdsInserter do
describe '#transform!' do
subject(:transform!) { described_class.new(project, dashboard, nil).transform! }
- let(:dashboard) { YAML.safe_load(fixture_file('lib/gitlab/metrics/dashboard/sample_dashboard.yml')).deep_symbolize_keys }
+ let(:dashboard) { load_sample_dashboard.deep_symbolize_keys }
context 'when dashboard panels are present' do
it 'assigns unique ids to each panel using PerformanceMonitoring::PrometheusPanel', :aggregate_failures do
diff --git a/spec/lib/gitlab/metrics/dashboard/stages/url_validator_spec.rb b/spec/lib/gitlab/metrics/dashboard/stages/url_validator_spec.rb
new file mode 100644
index 00000000000..83cf161c4e2
--- /dev/null
+++ b/spec/lib/gitlab/metrics/dashboard/stages/url_validator_spec.rb
@@ -0,0 +1,101 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Metrics::Dashboard::Stages::UrlValidator do
+ let(:project) { build_stubbed(:project) }
+
+ describe '#transform!' do
+ context 'when the links contain a blocked url' do
+ let(:dashboard) do
+ {
+ dashboard: "Test Dashboard",
+ links: [
+ { url: "http://1.1.1.1.1" },
+ { url: "https://gitlab.com" },
+ { url: "http://0.0.0.0" }
+ ],
+ panel_groups: [
+ {
+ group: "Group A",
+ panels: [
+ {
+ title: "Super Chart A1",
+ type: "area-chart",
+ y_label: "y_label",
+ metrics: [
+ {
+ id: "metric_a1",
+ query_range: "query",
+ unit: "unit",
+ label: "Legend Label"
+ }
+ ],
+ links: [
+ { url: "http://1.1.1.1.1" },
+ { url: "https://gitlab.com" },
+ { url: "http://0.0.0.0" }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ end
+
+ let(:expected) do
+ [{ url: '' }, { url: 'https://gitlab.com' }, { url: 'http://0.0.0.0' }]
+ end
+
+ let(:transform!) { described_class.new(project, dashboard, nil).transform! }
+
+ before do
+ stub_env('RSPEC_ALLOW_INVALID_URLS', 'false')
+ stub_application_setting(allow_local_requests_from_web_hooks_and_services: true)
+ end
+
+ context 'dashboard related links' do
+ it 'replaces the blocked url with an empty string' do
+ transform!
+
+ expect(dashboard[:links]).to eq(expected)
+ end
+ end
+
+ context 'chart links' do
+ it 'replaces the blocked url with an empty string' do
+ transform!
+
+ result = dashboard.dig(:panel_groups, 0, :panels, 0, :links)
+ expect(result).to eq(expected)
+ end
+ end
+
+ context 'when local requests are not allowed' do
+ before do
+ stub_application_setting(allow_local_requests_from_web_hooks_and_services: false)
+ end
+
+ let(:expected) do
+ [{ url: '' }, { url: 'https://gitlab.com' }, { url: '' }]
+ end
+
+ it 'replaces the blocked url with an empty string' do
+ transform!
+
+ expect(dashboard[:links]).to eq(expected)
+ end
+ end
+
+ context 'when the links are an array of strings instead of hashes' do
+ before do
+ dashboard[:links] = dashboard[:links].map(&:values)
+ end
+
+ it 'prevents an invalid link definition from erroring out' do
+ expect { transform! }.not_to raise_error
+ end
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/metrics/dashboard/stages/variable_endpoint_inserter_spec.rb b/spec/lib/gitlab/metrics/dashboard/stages/variable_endpoint_inserter_spec.rb
new file mode 100644
index 00000000000..9303ff981fb
--- /dev/null
+++ b/spec/lib/gitlab/metrics/dashboard/stages/variable_endpoint_inserter_spec.rb
@@ -0,0 +1,77 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Metrics::Dashboard::Stages::VariableEndpointInserter do
+ include MetricsDashboardHelpers
+
+ let(:project) { build_stubbed(:project) }
+ let(:environment) { build_stubbed(:environment, project: project) }
+
+ describe '#transform!' do
+ subject(:transform!) { described_class.new(project, dashboard, environment: environment).transform! }
+
+ let(:dashboard) { load_sample_dashboard.deep_symbolize_keys }
+
+ context 'when dashboard variables are present' do
+ it 'assigns prometheus_endpoint_path to metric_label_values variable type' do
+ endpoint_path = Gitlab::Routing.url_helpers.prometheus_api_project_environment_path(
+ project,
+ environment,
+ proxy_path: :series,
+ match: ['backend:haproxy_backend_availability:ratio{env="{{env}}"}']
+ )
+
+ transform!
+
+ expect(
+ dashboard.dig(:templating, :variables, :metric_label_values_variable, :options)
+ ).to include(prometheus_endpoint_path: endpoint_path)
+ end
+
+ it 'does not modify other variable types' do
+ original_text_variable = dashboard[:templating][:variables][:text_variable_full_syntax].deep_dup
+
+ transform!
+
+ expect(dashboard[:templating][:variables][:text_variable_full_syntax]).to eq(original_text_variable)
+ end
+
+ context 'when variable does not have the required series_selector' do
+ it 'adds prometheus_endpoint_path without match parameter' do
+ dashboard[:templating][:variables][:metric_label_values_variable][:options].delete(:series_selector)
+ endpoint_path = Gitlab::Routing.url_helpers.prometheus_api_project_environment_path(
+ project,
+ environment,
+ proxy_path: :series
+ )
+
+ transform!
+
+ expect(
+ dashboard.dig(:templating, :variables, :metric_label_values_variable, :options)
+ ).to include(prometheus_endpoint_path: endpoint_path)
+ end
+ end
+ end
+
+ context 'when no variables are present' do
+ it 'does not fail' do
+ dashboard.delete(:templating)
+
+ expect { transform! }.not_to raise_error
+ end
+ end
+
+ context 'with no environment' do
+ subject(:transform!) { described_class.new(project, dashboard, {}).transform! }
+
+ it 'raises error' do
+ expect { transform! }.to raise_error(
+ Gitlab::Metrics::Dashboard::Errors::DashboardProcessingError,
+ 'Environment is required for Stages::VariableEndpointInserter'
+ )
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/metrics/dashboard/url_spec.rb b/spec/lib/gitlab/metrics/dashboard/url_spec.rb
index 75f9f99c8a6..56556423b05 100644
--- a/spec/lib/gitlab/metrics/dashboard/url_spec.rb
+++ b/spec/lib/gitlab/metrics/dashboard/url_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Metrics::Dashboard::Url do
+RSpec.describe Gitlab::Metrics::Dashboard::Url do
include Gitlab::Routing.url_helpers
describe '#metrics_regex' do
@@ -46,6 +46,35 @@ describe Gitlab::Metrics::Dashboard::Url do
end
end
+ describe '#clusters_regex' do
+ let(:url) do
+ Gitlab::Routing.url_helpers.namespace_project_cluster_url(
+ 'foo',
+ 'bar',
+ '1',
+ group: 'Cluster Health',
+ title: 'Memory Usage',
+ y_label: 'Memory 20(GiB)',
+ anchor: 'title'
+ )
+ end
+
+ let(:expected_params) do
+ {
+ 'url' => url,
+ 'namespace' => 'foo',
+ 'project' => 'bar',
+ 'cluster_id' => '1',
+ 'query' => '?group=Cluster+Health&title=Memory+Usage&y_label=Memory+20%28GiB%29',
+ 'anchor' => '#title'
+ }
+ end
+
+ subject { described_class.clusters_regex }
+
+ it_behaves_like 'regex which matches url when expected'
+ end
+
describe '#grafana_regex' do
let(:url) do
namespace_project_grafana_api_metrics_dashboard_url(
diff --git a/spec/lib/gitlab/metrics/delta_spec.rb b/spec/lib/gitlab/metrics/delta_spec.rb
index 9bb011dc8fc..e768da875c2 100644
--- a/spec/lib/gitlab/metrics/delta_spec.rb
+++ b/spec/lib/gitlab/metrics/delta_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Metrics::Delta do
+RSpec.describe Gitlab::Metrics::Delta do
let(:delta) { described_class.new }
describe '#compared_with' do
diff --git a/spec/lib/gitlab/metrics/elasticsearch_rack_middleware_spec.rb b/spec/lib/gitlab/metrics/elasticsearch_rack_middleware_spec.rb
index 305768ef060..1fbd41bcc88 100644
--- a/spec/lib/gitlab/metrics/elasticsearch_rack_middleware_spec.rb
+++ b/spec/lib/gitlab/metrics/elasticsearch_rack_middleware_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Metrics::ElasticsearchRackMiddleware do
+RSpec.describe Gitlab::Metrics::ElasticsearchRackMiddleware do
let(:app) { double(:app, call: 'app call result') }
let(:middleware) { described_class.new(app) }
let(:env) { {} }
diff --git a/spec/lib/gitlab/metrics/exporter/base_exporter_spec.rb b/spec/lib/gitlab/metrics/exporter/base_exporter_spec.rb
index 47ec69e2f45..e4f85243528 100644
--- a/spec/lib/gitlab/metrics/exporter/base_exporter_spec.rb
+++ b/spec/lib/gitlab/metrics/exporter/base_exporter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Metrics::Exporter::BaseExporter do
+RSpec.describe Gitlab::Metrics::Exporter::BaseExporter do
let(:exporter) { described_class.new }
let(:log_filename) { File.join(Rails.root, 'log', 'sidekiq_exporter.log') }
let(:settings) { double('settings') }
diff --git a/spec/lib/gitlab/metrics/exporter/sidekiq_exporter_spec.rb b/spec/lib/gitlab/metrics/exporter/sidekiq_exporter_spec.rb
index 0b820fdbde9..2c5ef09f799 100644
--- a/spec/lib/gitlab/metrics/exporter/sidekiq_exporter_spec.rb
+++ b/spec/lib/gitlab/metrics/exporter/sidekiq_exporter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Metrics::Exporter::SidekiqExporter do
+RSpec.describe Gitlab::Metrics::Exporter::SidekiqExporter do
let(:exporter) { described_class.new }
after do
diff --git a/spec/lib/gitlab/metrics/exporter/web_exporter_spec.rb b/spec/lib/gitlab/metrics/exporter/web_exporter_spec.rb
index f22993cf057..ce98c807e2e 100644
--- a/spec/lib/gitlab/metrics/exporter/web_exporter_spec.rb
+++ b/spec/lib/gitlab/metrics/exporter/web_exporter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Metrics::Exporter::WebExporter do
+RSpec.describe Gitlab::Metrics::Exporter::WebExporter do
let(:exporter) { described_class.new }
let(:readiness_probe) { exporter.send(:readiness_probe).execute }
diff --git a/spec/lib/gitlab/metrics/instrumentation_spec.rb b/spec/lib/gitlab/metrics/instrumentation_spec.rb
index bf84a476df9..2729fbce974 100644
--- a/spec/lib/gitlab/metrics/instrumentation_spec.rb
+++ b/spec/lib/gitlab/metrics/instrumentation_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Metrics::Instrumentation do
+RSpec.describe Gitlab::Metrics::Instrumentation do
let(:env) { {} }
let(:transaction) { Gitlab::Metrics::WebTransaction.new(env) }
diff --git a/spec/lib/gitlab/metrics/method_call_spec.rb b/spec/lib/gitlab/metrics/method_call_spec.rb
index 035d875258c..42361cbc36a 100644
--- a/spec/lib/gitlab/metrics/method_call_spec.rb
+++ b/spec/lib/gitlab/metrics/method_call_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Metrics::MethodCall do
+RSpec.describe Gitlab::Metrics::MethodCall do
let(:transaction) { double(:transaction, labels: {}) }
let(:method_call) { described_class.new('Foo#bar', :Foo, '#bar', transaction) }
diff --git a/spec/lib/gitlab/metrics/methods_spec.rb b/spec/lib/gitlab/metrics/methods_spec.rb
index 5cf8db55142..3c171680272 100644
--- a/spec/lib/gitlab/metrics/methods_spec.rb
+++ b/spec/lib/gitlab/metrics/methods_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Metrics::Methods do
+RSpec.describe Gitlab::Metrics::Methods do
subject { Class.new { include Gitlab::Metrics::Methods } }
shared_context 'metric' do |metric_type, *args|
diff --git a/spec/lib/gitlab/metrics/prometheus_spec.rb b/spec/lib/gitlab/metrics/prometheus_spec.rb
index e15a063fc9e..2273987551d 100644
--- a/spec/lib/gitlab/metrics/prometheus_spec.rb
+++ b/spec/lib/gitlab/metrics/prometheus_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Metrics::Prometheus, :prometheus do
+RSpec.describe Gitlab::Metrics::Prometheus, :prometheus do
let(:all_metrics) { Gitlab::Metrics }
let(:registry) { all_metrics.registry }
diff --git a/spec/lib/gitlab/metrics/rack_middleware_spec.rb b/spec/lib/gitlab/metrics/rack_middleware_spec.rb
index dd1dbf7a1f4..335e5a490a6 100644
--- a/spec/lib/gitlab/metrics/rack_middleware_spec.rb
+++ b/spec/lib/gitlab/metrics/rack_middleware_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Metrics::RackMiddleware do
+RSpec.describe Gitlab::Metrics::RackMiddleware do
let(:app) { double(:app) }
let(:middleware) { described_class.new(app) }
diff --git a/spec/lib/gitlab/metrics/redis_rack_middleware_spec.rb b/spec/lib/gitlab/metrics/redis_rack_middleware_spec.rb
index f2f36ccad20..a85968dbd43 100644
--- a/spec/lib/gitlab/metrics/redis_rack_middleware_spec.rb
+++ b/spec/lib/gitlab/metrics/redis_rack_middleware_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Metrics::RedisRackMiddleware do
+RSpec.describe Gitlab::Metrics::RedisRackMiddleware do
let(:app) { double(:app) }
let(:middleware) { described_class.new(app) }
let(:env) { {} }
diff --git a/spec/lib/gitlab/metrics/requests_rack_middleware_spec.rb b/spec/lib/gitlab/metrics/requests_rack_middleware_spec.rb
index 6ee8acbf6fd..69b779d36eb 100644
--- a/spec/lib/gitlab/metrics/requests_rack_middleware_spec.rb
+++ b/spec/lib/gitlab/metrics/requests_rack_middleware_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Metrics::RequestsRackMiddleware do
+RSpec.describe Gitlab::Metrics::RequestsRackMiddleware do
let(:app) { double('app') }
subject { described_class.new(app) }
diff --git a/spec/lib/gitlab/metrics/samplers/database_sampler_spec.rb b/spec/lib/gitlab/metrics/samplers/database_sampler_spec.rb
index 087a0bfbac5..b94d19ff227 100644
--- a/spec/lib/gitlab/metrics/samplers/database_sampler_spec.rb
+++ b/spec/lib/gitlab/metrics/samplers/database_sampler_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Metrics::Samplers::DatabaseSampler do
+RSpec.describe Gitlab::Metrics::Samplers::DatabaseSampler do
subject { described_class.new }
describe '#interval' do
diff --git a/spec/lib/gitlab/metrics/samplers/puma_sampler_spec.rb b/spec/lib/gitlab/metrics/samplers/puma_sampler_spec.rb
index df63f2ebe28..214649d3e7e 100644
--- a/spec/lib/gitlab/metrics/samplers/puma_sampler_spec.rb
+++ b/spec/lib/gitlab/metrics/samplers/puma_sampler_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Metrics::Samplers::PumaSampler do
+RSpec.describe Gitlab::Metrics::Samplers::PumaSampler do
subject { described_class.new }
let(:null_metric) { double('null_metric', set: nil, observe: nil) }
diff --git a/spec/lib/gitlab/metrics/samplers/ruby_sampler_spec.rb b/spec/lib/gitlab/metrics/samplers/ruby_sampler_spec.rb
index 9fc8dd10922..59a70ac74a5 100644
--- a/spec/lib/gitlab/metrics/samplers/ruby_sampler_spec.rb
+++ b/spec/lib/gitlab/metrics/samplers/ruby_sampler_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Metrics::Samplers::RubySampler do
+RSpec.describe Gitlab::Metrics::Samplers::RubySampler do
let(:sampler) { described_class.new }
let(:null_metric) { double('null_metric', set: nil, observe: nil) }
diff --git a/spec/lib/gitlab/metrics/samplers/unicorn_sampler_spec.rb b/spec/lib/gitlab/metrics/samplers/unicorn_sampler_spec.rb
index a64aae73d43..9f2180c4170 100644
--- a/spec/lib/gitlab/metrics/samplers/unicorn_sampler_spec.rb
+++ b/spec/lib/gitlab/metrics/samplers/unicorn_sampler_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Metrics::Samplers::UnicornSampler do
+RSpec.describe Gitlab::Metrics::Samplers::UnicornSampler do
subject { described_class.new(1.second) }
describe '#sample' do
diff --git a/spec/lib/gitlab/metrics/sidekiq_middleware_spec.rb b/spec/lib/gitlab/metrics/sidekiq_middleware_spec.rb
index ea9e8fa6795..c66d8b1075c 100644
--- a/spec/lib/gitlab/metrics/sidekiq_middleware_spec.rb
+++ b/spec/lib/gitlab/metrics/sidekiq_middleware_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Metrics::SidekiqMiddleware do
+RSpec.describe Gitlab::Metrics::SidekiqMiddleware do
let(:middleware) { described_class.new }
let(:message) { { 'args' => ['test'], 'enqueued_at' => Time.new(2016, 6, 23, 6, 59).to_f } }
@@ -18,8 +18,20 @@ describe Gitlab::Metrics::SidekiqMiddleware do
middleware.call(worker, message, :test) do
ActiveRecord::Base.connection.execute('SELECT pg_sleep(0.1);')
end
+ end
+
+ it 'prevents database counters from leaking to the next transaction' do
+ worker = double(:worker, class: double(:class, name: 'TestWorker'))
- expect(message).to include(:db_count, :db_write_count, :db_cached_count)
+ 2.times do
+ Gitlab::WithRequestStore.with_request_store do
+ middleware.call(worker, message, :test) do
+ ActiveRecord::Base.connection.execute('SELECT pg_sleep(0.1);')
+ end
+ end
+ end
+
+ expect(message).to include(db_count: 1, db_write_count: 0, db_cached_count: 0)
end
it 'tracks the transaction (for messages without `enqueued_at`)', :aggregate_failures do
@@ -35,19 +47,20 @@ describe Gitlab::Metrics::SidekiqMiddleware do
middleware.call(worker, {}, :test) { nil }
end
- it 'tracks any raised exceptions', :aggregate_failures do
+ it 'tracks any raised exceptions', :aggregate_failures, :request_store do
worker = double(:worker, class: double(:class, name: 'TestWorker'))
expect_any_instance_of(Gitlab::Metrics::Transaction)
- .to receive(:run).and_raise(RuntimeError)
-
- expect_any_instance_of(Gitlab::Metrics::Transaction)
.to receive(:add_event).with(:sidekiq_exception)
- expect { middleware.call(worker, message, :test) }
- .to raise_error(RuntimeError)
+ expect do
+ middleware.call(worker, message, :test) do
+ ActiveRecord::Base.connection.execute('SELECT pg_sleep(0.1);')
+ raise RuntimeError
+ end
+ end.to raise_error(RuntimeError)
- expect(message).to include(:db_count, :db_write_count, :db_cached_count)
+ expect(message).to include(db_count: 1, db_write_count: 0, db_cached_count: 0)
end
end
end
diff --git a/spec/lib/gitlab/metrics/subscribers/action_view_spec.rb b/spec/lib/gitlab/metrics/subscribers/action_view_spec.rb
index 857e54d3432..161527c01aa 100644
--- a/spec/lib/gitlab/metrics/subscribers/action_view_spec.rb
+++ b/spec/lib/gitlab/metrics/subscribers/action_view_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Metrics::Subscribers::ActionView do
+RSpec.describe Gitlab::Metrics::Subscribers::ActionView do
let(:env) { {} }
let(:transaction) { Gitlab::Metrics::WebTransaction.new(env) }
diff --git a/spec/lib/gitlab/metrics/subscribers/active_record_spec.rb b/spec/lib/gitlab/metrics/subscribers/active_record_spec.rb
index a78d048908d..2fd5dd1d83b 100644
--- a/spec/lib/gitlab/metrics/subscribers/active_record_spec.rb
+++ b/spec/lib/gitlab/metrics/subscribers/active_record_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Metrics::Subscribers::ActiveRecord do
+RSpec.describe Gitlab::Metrics::Subscribers::ActiveRecord do
let(:env) { {} }
let(:transaction) { Gitlab::Metrics::WebTransaction.new(env) }
let(:subscriber) { described_class.new }
@@ -28,60 +28,45 @@ describe Gitlab::Metrics::Subscribers::ActiveRecord do
end
describe 'with a current transaction' do
- shared_examples 'read only query' do
- it 'increments only db count value' do
+ shared_examples 'track executed query' do
+ before do
allow(subscriber).to receive(:current_transaction)
- .at_least(:once)
- .and_return(transaction)
-
- expect(transaction).to receive(:increment)
- .with(:db_count, 1)
-
- expect(transaction).not_to receive(:increment)
- .with(:db_cached_count, 1)
-
- expect(transaction).not_to receive(:increment)
- .with(:db_write_count, 1)
-
- subscriber.sql(event)
+ .at_least(:once)
+ .and_return(transaction)
end
- end
-
- shared_examples 'write query' do
- it 'increments db_write_count and db_count value' do
- expect(subscriber).to receive(:current_transaction)
- .at_least(:once)
- .and_return(transaction)
- expect(transaction).to receive(:increment)
- .with(:db_count, 1)
-
- expect(transaction).not_to receive(:increment)
- .with(:db_cached_count, 1)
-
- expect(transaction).to receive(:increment)
- .with(:db_write_count, 1)
+ it 'increments only db count value' do
+ described_class::DB_COUNTERS.each do |counter|
+ if expected_counters[counter] > 0
+ expect(transaction).to receive(:increment).with(counter, 1)
+ else
+ expect(transaction).not_to receive(:increment).with(counter, 1)
+ end
+ end
subscriber.sql(event)
end
- end
- shared_examples 'cached query' do
- it 'increments db_cached_count and db_count value' do
- expect(subscriber).to receive(:current_transaction)
- .at_least(:once)
- .and_return(transaction)
-
- expect(transaction).to receive(:increment)
- .with(:db_count, 1)
-
- expect(transaction).to receive(:increment)
- .with(:db_cached_count, 1)
-
- expect(transaction).not_to receive(:increment)
- .with(:db_write_count, 1)
-
- subscriber.sql(event)
+ context 'when RequestStore is enabled' do
+ it 'caches db count value', :request_store, :aggregate_failures do
+ subscriber.sql(event)
+
+ described_class::DB_COUNTERS.each do |counter|
+ expect(Gitlab::SafeRequestStore[counter].to_i).to eq expected_counters[counter]
+ end
+ end
+
+ it 'prevents db counters from leaking to the next transaction' do
+ 2.times do
+ Gitlab::WithRequestStore.with_request_store do
+ subscriber.sql(event)
+
+ described_class::DB_COUNTERS.each do |counter|
+ expect(Gitlab::SafeRequestStore[counter].to_i).to eq expected_counters[counter]
+ end
+ end
+ end
+ end
end
end
@@ -93,66 +78,96 @@ describe Gitlab::Metrics::Subscribers::ActiveRecord do
subscriber.sql(event)
end
- it_behaves_like 'read only query'
+ context 'with read query' do
+ let(:expected_counters) do
+ {
+ db_count: 1,
+ db_write_count: 0,
+ db_cached_count: 0
+ }
+ end
+
+ it_behaves_like 'track executed query'
- context 'with select for update sql event' do
- let(:payload) { { sql: 'SELECT * FROM users WHERE id = 10 FOR UPDATE' } }
+ context 'with only select' do
+ let(:payload) { { sql: 'WITH active_milestones AS (SELECT COUNT(*), state FROM milestones GROUP BY state) SELECT * FROM active_milestones' } }
- it_behaves_like 'write query'
+ it_behaves_like 'track executed query'
+ end
end
- context 'with common table expression' do
- context 'with insert' do
- let(:payload) { { sql: 'WITH archived_rows AS (SELECT * FROM users WHERE archived = true) INSERT INTO products_log SELECT * FROM archived_rows' } }
+ context 'write query' do
+ let(:expected_counters) do
+ {
+ db_count: 1,
+ db_write_count: 1,
+ db_cached_count: 0
+ }
+ end
+
+ context 'with select for update sql event' do
+ let(:payload) { { sql: 'SELECT * FROM users WHERE id = 10 FOR UPDATE' } }
- it_behaves_like 'write query'
+ it_behaves_like 'track executed query'
end
- context 'with only select' do
- let(:payload) { { sql: 'WITH active_milestones AS (SELECT COUNT(*), state FROM milestones GROUP BY state) SELECT * FROM active_milestones' } }
+ context 'with common table expression' do
+ context 'with insert' do
+ let(:payload) { { sql: 'WITH archived_rows AS (SELECT * FROM users WHERE archived = true) INSERT INTO products_log SELECT * FROM archived_rows' } }
- it_behaves_like 'read only query'
+ it_behaves_like 'track executed query'
+ end
end
- end
- context 'with delete sql event' do
- let(:payload) { { sql: 'DELETE FROM users where id = 10' } }
+ context 'with delete sql event' do
+ let(:payload) { { sql: 'DELETE FROM users where id = 10' } }
- it_behaves_like 'write query'
- end
+ it_behaves_like 'track executed query'
+ end
- context 'with insert sql event' do
- let(:payload) { { sql: 'INSERT INTO project_ci_cd_settings (project_id) SELECT id FROM projects' } }
+ context 'with insert sql event' do
+ let(:payload) { { sql: 'INSERT INTO project_ci_cd_settings (project_id) SELECT id FROM projects' } }
- it_behaves_like 'write query'
- end
+ it_behaves_like 'track executed query'
+ end
- context 'with update sql event' do
- let(:payload) { { sql: 'UPDATE users SET admin = true WHERE id = 10' } }
+ context 'with update sql event' do
+ let(:payload) { { sql: 'UPDATE users SET admin = true WHERE id = 10' } }
- it_behaves_like 'write query'
+ it_behaves_like 'track executed query'
+ end
end
- context 'with cached payload ' do
- let(:payload) do
+ context 'with cached query' do
+ let(:expected_counters) do
{
- sql: 'SELECT * FROM users WHERE id = 10',
- cached: true
+ db_count: 1,
+ db_write_count: 0,
+ db_cached_count: 1
}
end
- it_behaves_like 'cached query'
- end
+ context 'with cached payload ' do
+ let(:payload) do
+ {
+ sql: 'SELECT * FROM users WHERE id = 10',
+ cached: true
+ }
+ end
- context 'with cached payload name' do
- let(:payload) do
- {
- sql: 'SELECT * FROM users WHERE id = 10',
- name: 'CACHE'
- }
+ it_behaves_like 'track executed query'
end
- it_behaves_like 'cached query'
+ context 'with cached payload name' do
+ let(:payload) do
+ {
+ sql: 'SELECT * FROM users WHERE id = 10',
+ name: 'CACHE'
+ }
+ end
+
+ it_behaves_like 'track executed query'
+ end
end
context 'events are internal to Rails or irrelevant' do
@@ -215,4 +230,54 @@ describe Gitlab::Metrics::Subscribers::ActiveRecord do
end
end
end
+
+ describe 'self.db_counter_payload' do
+ before do
+ allow(subscriber).to receive(:current_transaction)
+ .at_least(:once)
+ .and_return(transaction)
+ end
+
+ context 'when RequestStore is enabled', :request_store do
+ context 'when query is executed' do
+ let(:expected_payload) do
+ {
+ db_count: 1,
+ db_cached_count: 0,
+ db_write_count: 0
+ }
+ end
+
+ it 'returns correct payload' do
+ subscriber.sql(event)
+
+ expect(described_class.db_counter_payload).to eq(expected_payload)
+ end
+ end
+
+ context 'when query is not executed' do
+ let(:expected_payload) do
+ {
+ db_count: 0,
+ db_cached_count: 0,
+ db_write_count: 0
+ }
+ end
+
+ it 'returns correct payload' do
+ expect(described_class.db_counter_payload).to eq(expected_payload)
+ end
+ end
+ end
+
+ context 'when RequestStore is disabled' do
+ let(:expected_payload) { {} }
+
+ it 'returns empty payload' do
+ subscriber.sql(event)
+
+ expect(described_class.db_counter_payload).to eq(expected_payload)
+ end
+ end
+ end
end
diff --git a/spec/lib/gitlab/metrics/subscribers/rails_cache_spec.rb b/spec/lib/gitlab/metrics/subscribers/rails_cache_spec.rb
index ab0d89b2683..f7ac719c16a 100644
--- a/spec/lib/gitlab/metrics/subscribers/rails_cache_spec.rb
+++ b/spec/lib/gitlab/metrics/subscribers/rails_cache_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Metrics::Subscribers::RailsCache do
+RSpec.describe Gitlab::Metrics::Subscribers::RailsCache do
let(:env) { {} }
let(:transaction) { Gitlab::Metrics::WebTransaction.new(env) }
let(:subscriber) { described_class.new }
diff --git a/spec/lib/gitlab/metrics/system_spec.rb b/spec/lib/gitlab/metrics/system_spec.rb
index abb6a0096d6..720bd5d79b3 100644
--- a/spec/lib/gitlab/metrics/system_spec.rb
+++ b/spec/lib/gitlab/metrics/system_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Metrics::System do
+RSpec.describe Gitlab::Metrics::System do
context 'when /proc files exist' do
# Fixtures pulled from:
# Linux carbon 5.3.0-7648-generic #41~1586789791~19.10~9593806-Ubuntu SMP Mon Apr 13 17:50:40 UTC x86_64 x86_64 x86_64 GNU/Linux
diff --git a/spec/lib/gitlab/metrics/transaction_spec.rb b/spec/lib/gitlab/metrics/transaction_spec.rb
index 693ec3cb7e7..e64179bd5c1 100644
--- a/spec/lib/gitlab/metrics/transaction_spec.rb
+++ b/spec/lib/gitlab/metrics/transaction_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Metrics::Transaction do
+RSpec.describe Gitlab::Metrics::Transaction do
let(:transaction) { described_class.new }
let(:sensitive_tags) do
@@ -114,15 +114,4 @@ describe Gitlab::Metrics::Transaction do
transaction.set(:meow, 1)
end
end
-
- describe '#get' do
- let(:prometheus_metric) { instance_double(Prometheus::Client::Counter, get: nil) }
-
- it 'gets a metric' do
- expect(described_class).to receive(:fetch_metric).with(:counter, :gitlab_transaction_meow_total).and_return(prometheus_metric)
- expect(prometheus_metric).to receive(:get)
-
- transaction.get(:meow, :counter)
- end
- end
end
diff --git a/spec/lib/gitlab/metrics/web_transaction_spec.rb b/spec/lib/gitlab/metrics/web_transaction_spec.rb
index 47f1bd3bd10..12e98089066 100644
--- a/spec/lib/gitlab/metrics/web_transaction_spec.rb
+++ b/spec/lib/gitlab/metrics/web_transaction_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Metrics::WebTransaction do
+RSpec.describe Gitlab::Metrics::WebTransaction do
let(:env) { {} }
let(:transaction) { described_class.new(env) }
let(:prometheus_metric) { double("prometheus metric") }
@@ -70,6 +70,9 @@ describe Gitlab::Metrics::WebTransaction do
end
describe '#labels' do
+ let(:request) { double(:request, format: double(:format, ref: :html)) }
+ let(:controller_class) { double(:controller_class, name: 'TestController') }
+
context 'when request goes to Grape endpoint' do
before do
route = double(:route, request_method: 'GET', path: '/:version/projects/:id/archive(.:format)')
@@ -77,8 +80,13 @@ describe Gitlab::Metrics::WebTransaction do
env['api.endpoint'] = endpoint
end
+
it 'provides labels with the method and path of the route in the grape endpoint' do
- expect(transaction.labels).to eq({ controller: 'Grape', action: 'GET /projects/:id/archive' })
+ expect(transaction.labels).to eq({ controller: 'Grape', action: 'GET /projects/:id/archive', feature_category: '' })
+ end
+
+ it 'contains only the labels defined for transactions' do
+ expect(transaction.labels.keys).to contain_exactly(*described_class.superclass::BASE_LABELS.keys)
end
it 'does not provide labels if route infos are missing' do
@@ -92,24 +100,25 @@ describe Gitlab::Metrics::WebTransaction do
end
context 'when request goes to ActionController' do
- let(:request) { double(:request, format: double(:format, ref: :html)) }
-
before do
- klass = double(:klass, name: 'TestController')
- controller = double(:controller, class: klass, action_name: 'show', request: request)
+ controller = double(:controller, class: controller_class, action_name: 'show', request: request)
env['action_controller.instance'] = controller
end
it 'tags a transaction with the name and action of a controller' do
- expect(transaction.labels).to eq({ controller: 'TestController', action: 'show' })
+ expect(transaction.labels).to eq({ controller: 'TestController', action: 'show', feature_category: '' })
+ end
+
+ it 'contains only the labels defined for transactions' do
+ expect(transaction.labels.keys).to contain_exactly(*described_class.superclass::BASE_LABELS.keys)
end
context 'when the request content type is not :html' do
let(:request) { double(:request, format: double(:format, ref: :json)) }
it 'appends the mime type to the transaction action' do
- expect(transaction.labels).to eq({ controller: 'TestController', action: 'show.json' })
+ expect(transaction.labels).to eq({ controller: 'TestController', action: 'show.json', feature_category: '' })
end
end
@@ -117,7 +126,14 @@ describe Gitlab::Metrics::WebTransaction do
let(:request) { double(:request, format: double(:format, ref: 'http://example.com')) }
it 'does not append the MIME type to the transaction action' do
- expect(transaction.labels).to eq({ controller: 'TestController', action: 'show' })
+ expect(transaction.labels).to eq({ controller: 'TestController', action: 'show', feature_category: '' })
+ end
+ end
+
+ context 'when the feature category is known' do
+ it 'includes it in the feature category label' do
+ expect(controller_class).to receive(:feature_category_for_action).with('show').and_return(:source_code_management)
+ expect(transaction.labels).to eq({ controller: 'TestController', action: 'show', feature_category: "source_code_management" })
end
end
end
diff --git a/spec/lib/gitlab/metrics_spec.rb b/spec/lib/gitlab/metrics_spec.rb
index 2ebe1958487..bdf72a3c288 100644
--- a/spec/lib/gitlab/metrics_spec.rb
+++ b/spec/lib/gitlab/metrics_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Metrics do
+RSpec.describe Gitlab::Metrics do
include StubENV
describe '.settings' do
diff --git a/spec/lib/gitlab/middleware/basic_health_check_spec.rb b/spec/lib/gitlab/middleware/basic_health_check_spec.rb
index 07fda691ac8..0ca96de38da 100644
--- a/spec/lib/gitlab/middleware/basic_health_check_spec.rb
+++ b/spec/lib/gitlab/middleware/basic_health_check_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Middleware::BasicHealthCheck do
+RSpec.describe Gitlab::Middleware::BasicHealthCheck do
let(:app) { double(:app) }
let(:middleware) { described_class.new(app) }
let(:env) { {} }
diff --git a/spec/lib/gitlab/middleware/go_spec.rb b/spec/lib/gitlab/middleware/go_spec.rb
index 43a489f6df0..1fffef53a82 100644
--- a/spec/lib/gitlab/middleware/go_spec.rb
+++ b/spec/lib/gitlab/middleware/go_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Middleware::Go do
+RSpec.describe Gitlab::Middleware::Go do
let(:app) { double(:app) }
let(:middleware) { described_class.new(app) }
let(:env) do
diff --git a/spec/lib/gitlab/middleware/handle_ip_spoof_attack_error_spec.rb b/spec/lib/gitlab/middleware/handle_ip_spoof_attack_error_spec.rb
index ccfc5e93887..553ee589c62 100644
--- a/spec/lib/gitlab/middleware/handle_ip_spoof_attack_error_spec.rb
+++ b/spec/lib/gitlab/middleware/handle_ip_spoof_attack_error_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Middleware::HandleIpSpoofAttackError do
+RSpec.describe Gitlab::Middleware::HandleIpSpoofAttackError do
let(:spoof_error) { ActionDispatch::RemoteIp::IpSpoofAttackError.new('sensitive') }
let(:standard_error) { StandardError.new('error') }
let(:app) { -> (env) { env.is_a?(Exception) ? raise(env) : env } }
diff --git a/spec/lib/gitlab/middleware/multipart_spec.rb b/spec/lib/gitlab/middleware/multipart_spec.rb
index 705164d5445..3b64fe335e8 100644
--- a/spec/lib/gitlab/middleware/multipart_spec.rb
+++ b/spec/lib/gitlab/middleware/multipart_spec.rb
@@ -4,7 +4,7 @@ require 'spec_helper'
require 'tempfile'
-describe Gitlab::Middleware::Multipart do
+RSpec.describe Gitlab::Middleware::Multipart do
include_context 'multipart middleware context'
RSpec.shared_examples_for 'multipart upload files' do
@@ -232,4 +232,82 @@ describe Gitlab::Middleware::Multipart do
middleware.call(env)
end
end
+
+ describe '#call' do
+ context 'with packages storage' do
+ using RSpec::Parameterized::TableSyntax
+
+ let(:storage_path) { 'shared/packages' }
+
+ RSpec.shared_examples 'allowing the multipart upload' do
+ it 'allows files to be uploaded' do
+ with_tmp_dir('tmp/uploads', storage_path) do |dir, env|
+ allow(Packages::PackageFileUploader).to receive(:root).and_return(File.join(dir, storage_path))
+
+ expect(app).to receive(:call) do |env|
+ expect(get_params(env)['file']).to be_a(::UploadedFile)
+ end
+
+ middleware.call(env)
+ end
+ end
+ end
+
+ RSpec.shared_examples 'not allowing the multipart upload when package upload path is used' do
+ it 'does not allow files to be uploaded' do
+ with_tmp_dir('tmp/uploads', storage_path) do |dir, env|
+ # with_tmp_dir sets the same workhorse_upload_path for all Uploaders,
+ # so we have to prevent JobArtifactUploader and LfsObjectUploader to
+ # allow the tested path
+ allow(JobArtifactUploader).to receive(:workhorse_upload_path).and_return(Dir.tmpdir)
+ allow(LfsObjectUploader).to receive(:workhorse_upload_path).and_return(Dir.tmpdir)
+
+ status, headers, body = middleware.call(env)
+
+ expect(status).to eq(400)
+ expect(headers).to eq({ 'Content-Type' => 'text/plain' })
+ expect(body).to start_with('insecure path used')
+ end
+ end
+ end
+
+ RSpec.shared_examples 'adding package storage to multipart allowed paths' do
+ before do
+ expect(::Packages::PackageFileUploader).to receive(:workhorse_upload_path).and_call_original
+ end
+
+ it_behaves_like 'allowing the multipart upload'
+ end
+
+ RSpec.shared_examples 'not adding package storage to multipart allowed paths' do
+ before do
+ expect(::Packages::PackageFileUploader).not_to receive(:workhorse_upload_path)
+ end
+
+ it_behaves_like 'not allowing the multipart upload when package upload path is used'
+ end
+
+ where(:object_storage_enabled, :direct_upload_enabled, :example_name) do
+ false | true | 'adding package storage to multipart allowed paths'
+ false | false | 'adding package storage to multipart allowed paths'
+ true | true | 'not adding package storage to multipart allowed paths'
+ true | false | 'adding package storage to multipart allowed paths'
+ end
+
+ with_them do
+ before do
+ stub_config(packages: {
+ enabled: true,
+ object_store: {
+ enabled: object_storage_enabled,
+ direct_upload: direct_upload_enabled
+ },
+ storage_path: storage_path
+ })
+ end
+
+ it_behaves_like params[:example_name]
+ end
+ end
+ end
end
diff --git a/spec/lib/gitlab/middleware/rails_queue_duration_spec.rb b/spec/lib/gitlab/middleware/rails_queue_duration_spec.rb
index 4f21bd14122..8f9b0aec9eb 100644
--- a/spec/lib/gitlab/middleware/rails_queue_duration_spec.rb
+++ b/spec/lib/gitlab/middleware/rails_queue_duration_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Middleware::RailsQueueDuration do
+RSpec.describe Gitlab::Middleware::RailsQueueDuration do
let(:app) { double(:app) }
let(:middleware) { described_class.new(app) }
let(:env) { {} }
diff --git a/spec/lib/gitlab/middleware/read_only_spec.rb b/spec/lib/gitlab/middleware/read_only_spec.rb
index c7e9b38e3ca..3bdf2a5077f 100644
--- a/spec/lib/gitlab/middleware/read_only_spec.rb
+++ b/spec/lib/gitlab/middleware/read_only_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Middleware::ReadOnly do
+RSpec.describe Gitlab::Middleware::ReadOnly do
include Rack::Test::Methods
using RSpec::Parameterized::TableSyntax
diff --git a/spec/lib/gitlab/middleware/release_env_spec.rb b/spec/lib/gitlab/middleware/release_env_spec.rb
index 3ca40f4ebd0..ca0ec0b9d83 100644
--- a/spec/lib/gitlab/middleware/release_env_spec.rb
+++ b/spec/lib/gitlab/middleware/release_env_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Middleware::ReleaseEnv do
+RSpec.describe Gitlab::Middleware::ReleaseEnv do
let(:inner_app) { double(:app, call: 'yay') }
let(:app) { described_class.new(inner_app) }
let(:env) { { 'action_controller.instance' => 'something' } }
diff --git a/spec/lib/gitlab/middleware/request_context_spec.rb b/spec/lib/gitlab/middleware/request_context_spec.rb
index 1ed06a97c1e..431f4453e37 100644
--- a/spec/lib/gitlab/middleware/request_context_spec.rb
+++ b/spec/lib/gitlab/middleware/request_context_spec.rb
@@ -4,7 +4,7 @@ require 'rack'
require 'request_store'
require_relative '../../../support/helpers/next_instance_of'
-describe Gitlab::Middleware::RequestContext do
+RSpec.describe Gitlab::Middleware::RequestContext do
include NextInstanceOf
let(:app) { -> (env) {} }
diff --git a/spec/lib/gitlab/middleware/same_site_cookies_spec.rb b/spec/lib/gitlab/middleware/same_site_cookies_spec.rb
index 0cf1028a930..7c5262ca318 100644
--- a/spec/lib/gitlab/middleware/same_site_cookies_spec.rb
+++ b/spec/lib/gitlab/middleware/same_site_cookies_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Middleware::SameSiteCookies do
+RSpec.describe Gitlab::Middleware::SameSiteCookies do
include Rack::Test::Methods
let(:mock_app) do
diff --git a/spec/lib/gitlab/monitor/demo_projects_spec.rb b/spec/lib/gitlab/monitor/demo_projects_spec.rb
index 92024a3f9c1..262c78eb62e 100644
--- a/spec/lib/gitlab/monitor/demo_projects_spec.rb
+++ b/spec/lib/gitlab/monitor/demo_projects_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Monitor::DemoProjects do
+RSpec.describe Gitlab::Monitor::DemoProjects do
describe '#primary_keys' do
subject { described_class.primary_keys }
diff --git a/spec/lib/gitlab/multi_collection_paginator_spec.rb b/spec/lib/gitlab/multi_collection_paginator_spec.rb
index f2049884b83..c7c8f4f969f 100644
--- a/spec/lib/gitlab/multi_collection_paginator_spec.rb
+++ b/spec/lib/gitlab/multi_collection_paginator_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::MultiCollectionPaginator do
+RSpec.describe Gitlab::MultiCollectionPaginator do
subject(:paginator) { described_class.new(Project.all.order(:id), Group.all.order(:id), per_page: 3) }
it 'combines both collections' do
diff --git a/spec/lib/gitlab/multi_destination_logger_spec.rb b/spec/lib/gitlab/multi_destination_logger_spec.rb
index 7acd7906a26..e0d76afd9bf 100644
--- a/spec/lib/gitlab/multi_destination_logger_spec.rb
+++ b/spec/lib/gitlab/multi_destination_logger_spec.rb
@@ -31,7 +31,7 @@ class EmptyLogger < Gitlab::MultiDestinationLogger
end
end
-describe Gitlab::MultiDestinationLogger do
+RSpec.describe Gitlab::MultiDestinationLogger do
after(:all) do
TestLogger.loggers.each do |logger|
log_file_path = "#{Rails.root}/log/#{logger.file_name}"
diff --git a/spec/lib/gitlab/namespaced_session_store_spec.rb b/spec/lib/gitlab/namespaced_session_store_spec.rb
index e177c44ad67..a569c86960c 100644
--- a/spec/lib/gitlab/namespaced_session_store_spec.rb
+++ b/spec/lib/gitlab/namespaced_session_store_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::NamespacedSessionStore do
+RSpec.describe Gitlab::NamespacedSessionStore do
let(:key) { :some_key }
context 'current session' do
diff --git a/spec/lib/gitlab/no_cache_headers_spec.rb b/spec/lib/gitlab/no_cache_headers_spec.rb
index c7a73f0e2dc..513a39978e6 100644
--- a/spec/lib/gitlab/no_cache_headers_spec.rb
+++ b/spec/lib/gitlab/no_cache_headers_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::NoCacheHeaders do
+RSpec.describe Gitlab::NoCacheHeaders do
before do
stub_const('NoCacheTester', Class.new)
NoCacheTester.class_eval do
diff --git a/spec/lib/gitlab/noteable_metadata_spec.rb b/spec/lib/gitlab/noteable_metadata_spec.rb
index b12a1825f04..98c07fc9f62 100644
--- a/spec/lib/gitlab/noteable_metadata_spec.rb
+++ b/spec/lib/gitlab/noteable_metadata_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::NoteableMetadata do
+RSpec.describe Gitlab::NoteableMetadata do
subject { Class.new { include Gitlab::NoteableMetadata }.new }
it 'returns an empty Hash if an empty collection is provided' do
diff --git a/spec/lib/gitlab/null_request_store_spec.rb b/spec/lib/gitlab/null_request_store_spec.rb
index c023dac53ad..f600af2e31f 100644
--- a/spec/lib/gitlab/null_request_store_spec.rb
+++ b/spec/lib/gitlab/null_request_store_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::NullRequestStore do
+RSpec.describe Gitlab::NullRequestStore do
let(:null_store) { described_class.new }
describe '#store' do
diff --git a/spec/lib/gitlab/object_hierarchy_spec.rb b/spec/lib/gitlab/object_hierarchy_spec.rb
index b72aeb6d601..ef2d4fa0cbf 100644
--- a/spec/lib/gitlab/object_hierarchy_spec.rb
+++ b/spec/lib/gitlab/object_hierarchy_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::ObjectHierarchy do
+RSpec.describe Gitlab::ObjectHierarchy do
let!(:parent) { create(:group) }
let!(:child1) { create(:group, parent: parent) }
let!(:child2) { create(:group, parent: child1) }
diff --git a/spec/lib/gitlab/octokit/middleware_spec.rb b/spec/lib/gitlab/octokit/middleware_spec.rb
index 8aa6d17ac9e..bc4d95738c7 100644
--- a/spec/lib/gitlab/octokit/middleware_spec.rb
+++ b/spec/lib/gitlab/octokit/middleware_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Octokit::Middleware do
+RSpec.describe Gitlab::Octokit::Middleware do
let(:app) { double(:app) }
let(:middleware) { described_class.new(app) }
diff --git a/spec/lib/gitlab/omniauth_initializer_spec.rb b/spec/lib/gitlab/omniauth_initializer_spec.rb
index 4afe4545891..a38dffcfce0 100644
--- a/spec/lib/gitlab/omniauth_initializer_spec.rb
+++ b/spec/lib/gitlab/omniauth_initializer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::OmniauthInitializer do
+RSpec.describe Gitlab::OmniauthInitializer do
let(:devise_config) { class_double(Devise) }
subject { described_class.new(devise_config) }
diff --git a/spec/lib/gitlab/omniauth_logging/json_formatter_spec.rb b/spec/lib/gitlab/omniauth_logging/json_formatter_spec.rb
index 36405daed5a..f65b247d5d7 100644
--- a/spec/lib/gitlab/omniauth_logging/json_formatter_spec.rb
+++ b/spec/lib/gitlab/omniauth_logging/json_formatter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::OmniauthLogging::JSONFormatter do
+RSpec.describe Gitlab::OmniauthLogging::JSONFormatter do
it "generates log in json format" do
Timecop.freeze(Time.utc(2019, 12, 04, 9, 10, 11, 123456)) do
expect(subject.call(:info, Time.now, 'omniauth', 'log message'))
diff --git a/spec/lib/gitlab/optimistic_locking_spec.rb b/spec/lib/gitlab/optimistic_locking_spec.rb
index 9dfcb775dfa..0862a9c880e 100644
--- a/spec/lib/gitlab/optimistic_locking_spec.rb
+++ b/spec/lib/gitlab/optimistic_locking_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::OptimisticLocking do
+RSpec.describe Gitlab::OptimisticLocking do
let!(:pipeline) { create(:ci_pipeline) }
let!(:pipeline2) { Ci::Pipeline.find(pipeline.id) }
diff --git a/spec/lib/gitlab/other_markup_spec.rb b/spec/lib/gitlab/other_markup_spec.rb
index b5cf5b0999d..26e60251abb 100644
--- a/spec/lib/gitlab/other_markup_spec.rb
+++ b/spec/lib/gitlab/other_markup_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::OtherMarkup do
+RSpec.describe Gitlab::OtherMarkup do
let(:context) { {} }
context "XSS Checks" do
diff --git a/spec/lib/gitlab/otp_key_rotator_spec.rb b/spec/lib/gitlab/otp_key_rotator_spec.rb
index f5a567d5ea0..e328b190db4 100644
--- a/spec/lib/gitlab/otp_key_rotator_spec.rb
+++ b/spec/lib/gitlab/otp_key_rotator_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::OtpKeyRotator do
+RSpec.describe Gitlab::OtpKeyRotator do
let(:file) { Tempfile.new("otp-key-rotator-test") }
let(:filename) { file.path }
let(:old_key) { Gitlab::Application.secrets.otp_key_base }
diff --git a/spec/lib/gitlab/pages_spec.rb b/spec/lib/gitlab/pages_spec.rb
index 5889689cb81..9f85efd56e6 100644
--- a/spec/lib/gitlab/pages_spec.rb
+++ b/spec/lib/gitlab/pages_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Pages do
+RSpec.describe Gitlab::Pages do
using RSpec::Parameterized::TableSyntax
let(:pages_secret) { SecureRandom.random_bytes(Gitlab::Pages::SECRET_LENGTH) }
diff --git a/spec/lib/gitlab/pagination/keyset/page_spec.rb b/spec/lib/gitlab/pagination/keyset/page_spec.rb
index c5ca27231d8..3f4ef192b5a 100644
--- a/spec/lib/gitlab/pagination/keyset/page_spec.rb
+++ b/spec/lib/gitlab/pagination/keyset/page_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Pagination::Keyset::Page do
+RSpec.describe Gitlab::Pagination::Keyset::Page do
describe '#per_page' do
it 'limits to a maximum of 100 records per page' do
per_page = described_class.new(per_page: 101).per_page
diff --git a/spec/lib/gitlab/pagination/keyset/pager_spec.rb b/spec/lib/gitlab/pagination/keyset/pager_spec.rb
index 3ad1bee7225..a3169142279 100644
--- a/spec/lib/gitlab/pagination/keyset/pager_spec.rb
+++ b/spec/lib/gitlab/pagination/keyset/pager_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Pagination::Keyset::Pager do
+RSpec.describe Gitlab::Pagination::Keyset::Pager do
let(:relation) { Project.all.order(id: :asc) }
let(:request) { double('request', page: page, apply_headers: nil) }
let(:page) { Gitlab::Pagination::Keyset::Page.new(order_by: { id: :asc }, per_page: 3) }
diff --git a/spec/lib/gitlab/pagination/keyset/request_context_spec.rb b/spec/lib/gitlab/pagination/keyset/request_context_spec.rb
index d6d5340f38b..d4255176a4e 100644
--- a/spec/lib/gitlab/pagination/keyset/request_context_spec.rb
+++ b/spec/lib/gitlab/pagination/keyset/request_context_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Pagination::Keyset::RequestContext do
+RSpec.describe Gitlab::Pagination::Keyset::RequestContext do
let(:request) { double('request', params: params) }
describe '#page' do
diff --git a/spec/lib/gitlab/pagination/keyset_spec.rb b/spec/lib/gitlab/pagination/keyset_spec.rb
index 0ac40080872..81dc40b35d5 100644
--- a/spec/lib/gitlab/pagination/keyset_spec.rb
+++ b/spec/lib/gitlab/pagination/keyset_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Pagination::Keyset do
+RSpec.describe Gitlab::Pagination::Keyset do
describe '.available_for_type?' do
subject { described_class }
diff --git a/spec/lib/gitlab/pagination/offset_pagination_spec.rb b/spec/lib/gitlab/pagination/offset_pagination_spec.rb
index 9c7dd385726..be20f0194f7 100644
--- a/spec/lib/gitlab/pagination/offset_pagination_spec.rb
+++ b/spec/lib/gitlab/pagination/offset_pagination_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Pagination::OffsetPagination do
+RSpec.describe Gitlab::Pagination::OffsetPagination do
let(:resource) { Project.all }
let(:custom_port) { 8080 }
let(:incoming_api_projects_url) { "#{Gitlab.config.gitlab.url}:#{custom_port}/api/v4/projects" }
diff --git a/spec/lib/gitlab/patch/action_dispatch_journey_formatter_spec.rb b/spec/lib/gitlab/patch/action_dispatch_journey_formatter_spec.rb
index b1c7f73489d..ca74f7573f3 100644
--- a/spec/lib/gitlab/patch/action_dispatch_journey_formatter_spec.rb
+++ b/spec/lib/gitlab/patch/action_dispatch_journey_formatter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Patch::ActionDispatchJourneyFormatter do
+RSpec.describe Gitlab::Patch::ActionDispatchJourneyFormatter do
let(:group) { create(:group) }
let(:project) { create(:project, namespace: group) }
let(:pipeline) { create(:ci_empty_pipeline, project: project) }
diff --git a/spec/lib/gitlab/patch/draw_route_spec.rb b/spec/lib/gitlab/patch/draw_route_spec.rb
index 4009b903dc3..4d1c7bf9fcf 100644
--- a/spec/lib/gitlab/patch/draw_route_spec.rb
+++ b/spec/lib/gitlab/patch/draw_route_spec.rb
@@ -2,7 +2,7 @@
require 'fast_spec_helper'
-describe Gitlab::Patch::DrawRoute do
+RSpec.describe Gitlab::Patch::DrawRoute do
subject do
Class.new do
include Gitlab::Patch::DrawRoute
diff --git a/spec/lib/gitlab/patch/prependable_spec.rb b/spec/lib/gitlab/patch/prependable_spec.rb
index 255324f89d5..8feab57a8f3 100644
--- a/spec/lib/gitlab/patch/prependable_spec.rb
+++ b/spec/lib/gitlab/patch/prependable_spec.rb
@@ -5,7 +5,7 @@ require 'fast_spec_helper'
# Patching ActiveSupport::Concern
require_relative '../../../../config/initializers/0_as_concern'
-describe Gitlab::Patch::Prependable do
+RSpec.describe Gitlab::Patch::Prependable do
before do
@prepended_modules = []
end
diff --git a/spec/lib/gitlab/path_regex_spec.rb b/spec/lib/gitlab/path_regex_spec.rb
index ac506c49100..7cecc29afa4 100644
--- a/spec/lib/gitlab/path_regex_spec.rb
+++ b/spec/lib/gitlab/path_regex_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::PathRegex do
+RSpec.describe Gitlab::PathRegex do
let(:starting_with_namespace) { %r{^/\*namespace_id/:(project_)?id} }
let(:non_param_parts) { %r{[^:*][a-z\-_/]*} }
let(:any_other_path_part) { %r{[a-z\-_/:]*} }
diff --git a/spec/lib/gitlab/performance_bar/with_top_level_warnings_spec.rb b/spec/lib/gitlab/performance_bar/with_top_level_warnings_spec.rb
index 3b92261f0fe..735f04b3bce 100644
--- a/spec/lib/gitlab/performance_bar/with_top_level_warnings_spec.rb
+++ b/spec/lib/gitlab/performance_bar/with_top_level_warnings_spec.rb
@@ -3,7 +3,7 @@
require 'fast_spec_helper'
require 'rspec-parameterized'
-describe Gitlab::PerformanceBar::WithTopLevelWarnings do
+RSpec.describe Gitlab::PerformanceBar::WithTopLevelWarnings do
using RSpec::Parameterized::TableSyntax
subject { Module.new }
diff --git a/spec/lib/gitlab/performance_bar_spec.rb b/spec/lib/gitlab/performance_bar_spec.rb
index 7b79cc82816..12916c41f0f 100644
--- a/spec/lib/gitlab/performance_bar_spec.rb
+++ b/spec/lib/gitlab/performance_bar_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::PerformanceBar do
+RSpec.describe Gitlab::PerformanceBar do
it { expect(described_class.l1_cache_backend).to eq(Gitlab::ProcessMemoryCache.cache_backend) }
it { expect(described_class.l2_cache_backend).to eq(Rails.cache) }
diff --git a/spec/lib/gitlab/phabricator_import/cache/map_spec.rb b/spec/lib/gitlab/phabricator_import/cache/map_spec.rb
index 4935ef1bd90..0f760852a68 100644
--- a/spec/lib/gitlab/phabricator_import/cache/map_spec.rb
+++ b/spec/lib/gitlab/phabricator_import/cache/map_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::PhabricatorImport::Cache::Map, :clean_gitlab_redis_cache do
+RSpec.describe Gitlab::PhabricatorImport::Cache::Map, :clean_gitlab_redis_cache do
let_it_be(:project) { create(:project) }
let(:redis) { Gitlab::Redis::Cache }
diff --git a/spec/lib/gitlab/phabricator_import/conduit/client_spec.rb b/spec/lib/gitlab/phabricator_import/conduit/client_spec.rb
index 542b3cd060f..dad349f3255 100644
--- a/spec/lib/gitlab/phabricator_import/conduit/client_spec.rb
+++ b/spec/lib/gitlab/phabricator_import/conduit/client_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe Gitlab::PhabricatorImport::Conduit::Client do
+RSpec.describe Gitlab::PhabricatorImport::Conduit::Client do
let(:client) do
described_class.new('https://see-ya-later.phabricator', 'api-token')
end
diff --git a/spec/lib/gitlab/phabricator_import/conduit/maniphest_spec.rb b/spec/lib/gitlab/phabricator_import/conduit/maniphest_spec.rb
index 0d7714649b9..e655a39a28d 100644
--- a/spec/lib/gitlab/phabricator_import/conduit/maniphest_spec.rb
+++ b/spec/lib/gitlab/phabricator_import/conduit/maniphest_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe Gitlab::PhabricatorImport::Conduit::Maniphest do
+RSpec.describe Gitlab::PhabricatorImport::Conduit::Maniphest do
let(:maniphest) do
described_class.new(phabricator_url: 'https://see-ya-later.phabricator', api_token: 'api-token')
end
diff --git a/spec/lib/gitlab/phabricator_import/conduit/response_spec.rb b/spec/lib/gitlab/phabricator_import/conduit/response_spec.rb
index 1ffb811cbc1..c368b349a3c 100644
--- a/spec/lib/gitlab/phabricator_import/conduit/response_spec.rb
+++ b/spec/lib/gitlab/phabricator_import/conduit/response_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe Gitlab::PhabricatorImport::Conduit::Response do
+RSpec.describe Gitlab::PhabricatorImport::Conduit::Response do
let(:response) { described_class.new(Gitlab::Json.parse(fixture_file('phabricator_responses/maniphest.search.json')))}
let(:error_response) { described_class.new(Gitlab::Json.parse(fixture_file('phabricator_responses/auth_failed.json'))) }
@@ -30,7 +30,7 @@ describe Gitlab::PhabricatorImport::Conduit::Response do
body: 'This is no JSON')
expect { described_class.parse!(fake_response) }
- .to raise_error(Gitlab::PhabricatorImport::Conduit::ResponseError, /unexpected token at/)
+ .to raise_error(Gitlab::PhabricatorImport::Conduit::ResponseError, /unexpected character/)
end
it 'returns a parsed response for valid input' do
diff --git a/spec/lib/gitlab/phabricator_import/conduit/tasks_response_spec.rb b/spec/lib/gitlab/phabricator_import/conduit/tasks_response_spec.rb
index 2cc12ee0165..4e56dead5c0 100644
--- a/spec/lib/gitlab/phabricator_import/conduit/tasks_response_spec.rb
+++ b/spec/lib/gitlab/phabricator_import/conduit/tasks_response_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe Gitlab::PhabricatorImport::Conduit::TasksResponse do
+RSpec.describe Gitlab::PhabricatorImport::Conduit::TasksResponse do
let(:conduit_response) do
Gitlab::PhabricatorImport::Conduit::Response
.new(Gitlab::Json.parse(fixture_file('phabricator_responses/maniphest.search.json')))
diff --git a/spec/lib/gitlab/phabricator_import/conduit/user_spec.rb b/spec/lib/gitlab/phabricator_import/conduit/user_spec.rb
index f3928f390bc..d38421c9405 100644
--- a/spec/lib/gitlab/phabricator_import/conduit/user_spec.rb
+++ b/spec/lib/gitlab/phabricator_import/conduit/user_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe Gitlab::PhabricatorImport::Conduit::User do
+RSpec.describe Gitlab::PhabricatorImport::Conduit::User do
let(:user_client) do
described_class.new(phabricator_url: 'https://see-ya-later.phabricator', api_token: 'api-token')
end
diff --git a/spec/lib/gitlab/phabricator_import/conduit/users_response_spec.rb b/spec/lib/gitlab/phabricator_import/conduit/users_response_spec.rb
index 999a986b73c..ebbb2c0598c 100644
--- a/spec/lib/gitlab/phabricator_import/conduit/users_response_spec.rb
+++ b/spec/lib/gitlab/phabricator_import/conduit/users_response_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe Gitlab::PhabricatorImport::Conduit::UsersResponse do
+RSpec.describe Gitlab::PhabricatorImport::Conduit::UsersResponse do
let(:conduit_response) do
Gitlab::PhabricatorImport::Conduit::Response
.new(Gitlab::Json.parse(fixture_file('phabricator_responses/user.search.json')))
diff --git a/spec/lib/gitlab/phabricator_import/importer_spec.rb b/spec/lib/gitlab/phabricator_import/importer_spec.rb
index 2715b785379..e78024c35c1 100644
--- a/spec/lib/gitlab/phabricator_import/importer_spec.rb
+++ b/spec/lib/gitlab/phabricator_import/importer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::PhabricatorImport::Importer do
+RSpec.describe Gitlab::PhabricatorImport::Importer do
it { expect(described_class).to be_async }
it "acts like it's importing repositories" do
diff --git a/spec/lib/gitlab/phabricator_import/issues/importer_spec.rb b/spec/lib/gitlab/phabricator_import/issues/importer_spec.rb
index 02dafd4bb3b..63ba575aea3 100644
--- a/spec/lib/gitlab/phabricator_import/issues/importer_spec.rb
+++ b/spec/lib/gitlab/phabricator_import/issues/importer_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe Gitlab::PhabricatorImport::Issues::Importer do
+RSpec.describe Gitlab::PhabricatorImport::Issues::Importer do
let(:project) { create(:project) }
let(:response) do
diff --git a/spec/lib/gitlab/phabricator_import/issues/task_importer_spec.rb b/spec/lib/gitlab/phabricator_import/issues/task_importer_spec.rb
index 79f11d7fae6..3cb15f08627 100644
--- a/spec/lib/gitlab/phabricator_import/issues/task_importer_spec.rb
+++ b/spec/lib/gitlab/phabricator_import/issues/task_importer_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe Gitlab::PhabricatorImport::Issues::TaskImporter do
+RSpec.describe Gitlab::PhabricatorImport::Issues::TaskImporter do
let_it_be(:project) { create(:project) }
let(:task) do
Gitlab::PhabricatorImport::Representation::Task.new(
diff --git a/spec/lib/gitlab/phabricator_import/project_creator_spec.rb b/spec/lib/gitlab/phabricator_import/project_creator_spec.rb
index 0b6a71290ed..016aa0abe4d 100644
--- a/spec/lib/gitlab/phabricator_import/project_creator_spec.rb
+++ b/spec/lib/gitlab/phabricator_import/project_creator_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe Gitlab::PhabricatorImport::ProjectCreator do
+RSpec.describe Gitlab::PhabricatorImport::ProjectCreator do
let(:user) { create(:user) }
let(:params) do
{ path: 'new-phab-import',
diff --git a/spec/lib/gitlab/phabricator_import/representation/task_spec.rb b/spec/lib/gitlab/phabricator_import/representation/task_spec.rb
index 5603a6961d6..25a52af3a7a 100644
--- a/spec/lib/gitlab/phabricator_import/representation/task_spec.rb
+++ b/spec/lib/gitlab/phabricator_import/representation/task_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::PhabricatorImport::Representation::Task do
+RSpec.describe Gitlab::PhabricatorImport::Representation::Task do
subject(:task) do
described_class.new(
{
diff --git a/spec/lib/gitlab/phabricator_import/representation/user_spec.rb b/spec/lib/gitlab/phabricator_import/representation/user_spec.rb
index f52467a0cf1..f51be0f7d8d 100644
--- a/spec/lib/gitlab/phabricator_import/representation/user_spec.rb
+++ b/spec/lib/gitlab/phabricator_import/representation/user_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::PhabricatorImport::Representation::User do
+RSpec.describe Gitlab::PhabricatorImport::Representation::User do
subject(:user) do
described_class.new(
{
diff --git a/spec/lib/gitlab/phabricator_import/user_finder_spec.rb b/spec/lib/gitlab/phabricator_import/user_finder_spec.rb
index f260e38b7c8..e63ad6e6626 100644
--- a/spec/lib/gitlab/phabricator_import/user_finder_spec.rb
+++ b/spec/lib/gitlab/phabricator_import/user_finder_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::PhabricatorImport::UserFinder, :clean_gitlab_redis_cache do
+RSpec.describe Gitlab::PhabricatorImport::UserFinder, :clean_gitlab_redis_cache do
let(:project) { create(:project, namespace: create(:group)) }
subject(:finder) { described_class.new(project, %w[first-phid second-phid]) }
diff --git a/spec/lib/gitlab/phabricator_import/worker_state_spec.rb b/spec/lib/gitlab/phabricator_import/worker_state_spec.rb
index 51514dd0ffd..4a07e28440f 100644
--- a/spec/lib/gitlab/phabricator_import/worker_state_spec.rb
+++ b/spec/lib/gitlab/phabricator_import/worker_state_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::PhabricatorImport::WorkerState, :clean_gitlab_redis_shared_state do
+RSpec.describe Gitlab::PhabricatorImport::WorkerState, :clean_gitlab_redis_shared_state do
subject(:state) { described_class.new('weird-project-id') }
let(:key) { 'phabricator-import/jobs/project-weird-project-id/job-count' }
diff --git a/spec/lib/gitlab/polling_interval_spec.rb b/spec/lib/gitlab/polling_interval_spec.rb
index 31569b2c51e..bf0eaf80d3b 100644
--- a/spec/lib/gitlab/polling_interval_spec.rb
+++ b/spec/lib/gitlab/polling_interval_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::PollingInterval do
+RSpec.describe Gitlab::PollingInterval do
let(:polling_interval) { described_class }
describe '.set_header' do
diff --git a/spec/lib/gitlab/popen/runner_spec.rb b/spec/lib/gitlab/popen/runner_spec.rb
index de19106eaee..5f72852c63e 100644
--- a/spec/lib/gitlab/popen/runner_spec.rb
+++ b/spec/lib/gitlab/popen/runner_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Popen::Runner do
+RSpec.describe Gitlab::Popen::Runner do
subject { described_class.new }
describe '#run' do
diff --git a/spec/lib/gitlab/popen_spec.rb b/spec/lib/gitlab/popen_spec.rb
index b398381a7e0..891482a5f17 100644
--- a/spec/lib/gitlab/popen_spec.rb
+++ b/spec/lib/gitlab/popen_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Popen do
+RSpec.describe Gitlab::Popen do
let(:path) { Rails.root.join('tmp').to_s }
before do
diff --git a/spec/lib/gitlab/private_commit_email_spec.rb b/spec/lib/gitlab/private_commit_email_spec.rb
index 7b7a0f7c0ca..502cc82fd8d 100644
--- a/spec/lib/gitlab/private_commit_email_spec.rb
+++ b/spec/lib/gitlab/private_commit_email_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::PrivateCommitEmail do
+RSpec.describe Gitlab::PrivateCommitEmail do
let(:hostname) { Gitlab::CurrentSettings.current_application_settings.commit_email_hostname }
let(:id) { 1 }
let(:valid_email) { "#{id}-foo@#{hostname}" }
diff --git a/spec/lib/gitlab/process_memory_cache/helper_spec.rb b/spec/lib/gitlab/process_memory_cache/helper_spec.rb
index 890642b1d5e..27d7fd0bdcf 100644
--- a/spec/lib/gitlab/process_memory_cache/helper_spec.rb
+++ b/spec/lib/gitlab/process_memory_cache/helper_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::ProcessMemoryCache::Helper, :use_clean_rails_memory_store_caching do
+RSpec.describe Gitlab::ProcessMemoryCache::Helper, :use_clean_rails_memory_store_caching do
let(:minimal_test_class) do
Class.new do
include Gitlab::ProcessMemoryCache::Helper
diff --git a/spec/lib/gitlab/profiler_spec.rb b/spec/lib/gitlab/profiler_spec.rb
index 6440f74a49a..89917e515d0 100644
--- a/spec/lib/gitlab/profiler_spec.rb
+++ b/spec/lib/gitlab/profiler_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Profiler do
+RSpec.describe Gitlab::Profiler do
let(:null_logger) { Logger.new('/dev/null') }
let(:private_token) { 'private' }
@@ -229,7 +229,6 @@ describe Gitlab::Profiler do
.map { |(total)| total.to_f }
expect(total_times).to eq(total_times.sort.reverse)
- expect(total_times).not_to eq(total_times.uniq)
end
end
diff --git a/spec/lib/gitlab/project_authorizations_spec.rb b/spec/lib/gitlab/project_authorizations_spec.rb
index 261e44bc5fa..5ff07dcec4f 100644
--- a/spec/lib/gitlab/project_authorizations_spec.rb
+++ b/spec/lib/gitlab/project_authorizations_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::ProjectAuthorizations do
+RSpec.describe Gitlab::ProjectAuthorizations do
def map_access_levels(rows)
rows.each_with_object({}) do |row, hash|
hash[row.project_id] = row.access_level
diff --git a/spec/lib/gitlab/project_search_results_spec.rb b/spec/lib/gitlab/project_search_results_spec.rb
index aa52949ed60..75a3fe06632 100644
--- a/spec/lib/gitlab/project_search_results_spec.rb
+++ b/spec/lib/gitlab/project_search_results_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::ProjectSearchResults do
+RSpec.describe Gitlab::ProjectSearchResults do
include SearchHelpers
let(:user) { create(:user) }
diff --git a/spec/lib/gitlab/project_template_spec.rb b/spec/lib/gitlab/project_template_spec.rb
index 35f79042df0..fa45c605b1b 100644
--- a/spec/lib/gitlab/project_template_spec.rb
+++ b/spec/lib/gitlab/project_template_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::ProjectTemplate do
+RSpec.describe Gitlab::ProjectTemplate do
describe '.all' do
it 'returns all templates' do
expected = %w[
@@ -10,7 +10,7 @@ describe Gitlab::ProjectTemplate do
gomicro gatsby hugo jekyll plainhtml gitbook
hexo sse_middleman nfhugo nfjekyll nfplainhtml
nfgitbook nfhexo salesforcedx serverless_framework
- cluster_management
+ jsonnet cluster_management
]
expect(described_class.all).to be_an(Array)
diff --git a/spec/lib/gitlab/project_transfer_spec.rb b/spec/lib/gitlab/project_transfer_spec.rb
index d54817ea02b..87c4014264f 100644
--- a/spec/lib/gitlab/project_transfer_spec.rb
+++ b/spec/lib/gitlab/project_transfer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::ProjectTransfer do
+RSpec.describe Gitlab::ProjectTransfer do
before do
@root_dir = File.join(Rails.root, "public", "uploads")
@project_transfer = described_class.new
diff --git a/spec/lib/gitlab/prometheus/adapter_spec.rb b/spec/lib/gitlab/prometheus/adapter_spec.rb
index afee95467fa..4762e4ad108 100644
--- a/spec/lib/gitlab/prometheus/adapter_spec.rb
+++ b/spec/lib/gitlab/prometheus/adapter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Prometheus::Adapter do
+RSpec.describe Gitlab::Prometheus::Adapter do
let_it_be(:project) { create(:project) }
let_it_be(:cluster, reload: true) { create(:cluster, :provided_by_user, environment_scope: '*', projects: [project]) }
diff --git a/spec/lib/gitlab/prometheus/additional_metrics_parser_spec.rb b/spec/lib/gitlab/prometheus/additional_metrics_parser_spec.rb
index 3f97a69b5eb..3c7496cabd0 100644
--- a/spec/lib/gitlab/prometheus/additional_metrics_parser_spec.rb
+++ b/spec/lib/gitlab/prometheus/additional_metrics_parser_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Prometheus::AdditionalMetricsParser do
+RSpec.describe Gitlab::Prometheus::AdditionalMetricsParser do
include Prometheus::MetricBuilders
let(:parser_error_class) { Gitlab::Prometheus::ParsingError }
diff --git a/spec/lib/gitlab/prometheus/internal_spec.rb b/spec/lib/gitlab/prometheus/internal_spec.rb
index 884bdcb4e9b..1254610fe32 100644
--- a/spec/lib/gitlab/prometheus/internal_spec.rb
+++ b/spec/lib/gitlab/prometheus/internal_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Prometheus::Internal do
+RSpec.describe Gitlab::Prometheus::Internal do
let(:listen_address) { 'localhost:9090' }
let(:prometheus_settings) do
diff --git a/spec/lib/gitlab/prometheus/metric_group_spec.rb b/spec/lib/gitlab/prometheus/metric_group_spec.rb
index 787f14daf47..a68cdfe5fb2 100644
--- a/spec/lib/gitlab/prometheus/metric_group_spec.rb
+++ b/spec/lib/gitlab/prometheus/metric_group_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Prometheus::MetricGroup do
+RSpec.describe Gitlab::Prometheus::MetricGroup do
describe '.common_metrics' do
let!(:project_metric) { create(:prometheus_metric) }
let!(:common_metric_group_a) { create(:prometheus_metric, :common, group: :aws_elb) }
diff --git a/spec/lib/gitlab/prometheus/queries/additional_metrics_deployment_query_spec.rb b/spec/lib/gitlab/prometheus/queries/additional_metrics_deployment_query_spec.rb
index 55e89395452..8abc944eeb1 100644
--- a/spec/lib/gitlab/prometheus/queries/additional_metrics_deployment_query_spec.rb
+++ b/spec/lib/gitlab/prometheus/queries/additional_metrics_deployment_query_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Prometheus::Queries::AdditionalMetricsDeploymentQuery do
+RSpec.describe Gitlab::Prometheus::Queries::AdditionalMetricsDeploymentQuery do
around do |example|
Timecop.freeze(Time.local(2008, 9, 1, 12, 0, 0)) { example.run }
end
diff --git a/spec/lib/gitlab/prometheus/queries/additional_metrics_environment_query_spec.rb b/spec/lib/gitlab/prometheus/queries/additional_metrics_environment_query_spec.rb
index 95df8880b90..f5911963108 100644
--- a/spec/lib/gitlab/prometheus/queries/additional_metrics_environment_query_spec.rb
+++ b/spec/lib/gitlab/prometheus/queries/additional_metrics_environment_query_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Prometheus::Queries::AdditionalMetricsEnvironmentQuery do
+RSpec.describe Gitlab::Prometheus::Queries::AdditionalMetricsEnvironmentQuery do
around do |example|
Timecop.freeze { example.run }
end
diff --git a/spec/lib/gitlab/prometheus/queries/deployment_query_spec.rb b/spec/lib/gitlab/prometheus/queries/deployment_query_spec.rb
index 4af233291f6..4683c4eae28 100644
--- a/spec/lib/gitlab/prometheus/queries/deployment_query_spec.rb
+++ b/spec/lib/gitlab/prometheus/queries/deployment_query_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Prometheus::Queries::DeploymentQuery do
+RSpec.describe Gitlab::Prometheus::Queries::DeploymentQuery do
let(:environment) { create(:environment, slug: 'environment-slug') }
let(:deployment) { create(:deployment, environment: environment) }
let(:client) { double('prometheus_client') }
diff --git a/spec/lib/gitlab/prometheus/queries/knative_invocation_query_spec.rb b/spec/lib/gitlab/prometheus/queries/knative_invocation_query_spec.rb
index 8eefd22bd29..ff48b9ada90 100644
--- a/spec/lib/gitlab/prometheus/queries/knative_invocation_query_spec.rb
+++ b/spec/lib/gitlab/prometheus/queries/knative_invocation_query_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Prometheus::Queries::KnativeInvocationQuery do
+RSpec.describe Gitlab::Prometheus::Queries::KnativeInvocationQuery do
include PrometheusHelpers
let(:project) { create(:project) }
diff --git a/spec/lib/gitlab/prometheus/queries/matched_metric_query_spec.rb b/spec/lib/gitlab/prometheus/queries/matched_metric_query_spec.rb
index 35034d814bf..117ca798022 100644
--- a/spec/lib/gitlab/prometheus/queries/matched_metric_query_spec.rb
+++ b/spec/lib/gitlab/prometheus/queries/matched_metric_query_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Prometheus::Queries::MatchedMetricQuery do
+RSpec.describe Gitlab::Prometheus::Queries::MatchedMetricQuery do
include Prometheus::MetricBuilders
let(:metric_group_class) { Gitlab::Prometheus::MetricGroup }
diff --git a/spec/lib/gitlab/prometheus/queries/validate_query_spec.rb b/spec/lib/gitlab/prometheus/queries/validate_query_spec.rb
index 9c0170718f4..045c063ab34 100644
--- a/spec/lib/gitlab/prometheus/queries/validate_query_spec.rb
+++ b/spec/lib/gitlab/prometheus/queries/validate_query_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Prometheus::Queries::ValidateQuery do
+RSpec.describe Gitlab::Prometheus::Queries::ValidateQuery do
include PrometheusHelpers
let(:api_url) { 'https://prometheus.example.com' }
diff --git a/spec/lib/gitlab/prometheus/query_variables_spec.rb b/spec/lib/gitlab/prometheus/query_variables_spec.rb
index 7dfa4de35d6..1422d48152a 100644
--- a/spec/lib/gitlab/prometheus/query_variables_spec.rb
+++ b/spec/lib/gitlab/prometheus/query_variables_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Prometheus::QueryVariables do
+RSpec.describe Gitlab::Prometheus::QueryVariables do
describe '.call' do
let(:project) { environment.project }
let(:environment) { create(:environment) }
diff --git a/spec/lib/gitlab/prometheus_client_spec.rb b/spec/lib/gitlab/prometheus_client_spec.rb
index 749192e5795..242a0ced031 100644
--- a/spec/lib/gitlab/prometheus_client_spec.rb
+++ b/spec/lib/gitlab/prometheus_client_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::PrometheusClient do
+RSpec.describe Gitlab::PrometheusClient do
include PrometheusHelpers
subject { described_class.new('https://prometheus.example.com') }
@@ -32,7 +32,7 @@ describe Gitlab::PrometheusClient do
it 'raises error when status code not 200' do
stub_request(:get, subject.health_url).to_return(status: 500, body: '')
- expect { subject.healthy? }.to raise_error(Gitlab::PrometheusClient::Error)
+ expect { subject.healthy? }.to raise_error(Gitlab::PrometheusClient::UnexpectedResponseError)
end
end
@@ -41,41 +41,41 @@ describe Gitlab::PrometheusClient do
# - execute_query: A query call
shared_examples 'failure response' do
context 'when request returns 400 with an error message' do
- it 'raises a Gitlab::PrometheusClient::Error error' do
+ it 'raises a Gitlab::PrometheusClient::QueryError error' do
req_stub = stub_prometheus_request(query_url, status: 400, body: { error: 'bar!' })
expect { execute_query }
- .to raise_error(Gitlab::PrometheusClient::Error, 'bar!')
+ .to raise_error(Gitlab::PrometheusClient::QueryError, 'bar!')
expect(req_stub).to have_been_requested
end
end
context 'when request returns 400 without an error message' do
- it 'raises a Gitlab::PrometheusClient::Error error' do
+ it 'raises a Gitlab::PrometheusClient::QueryError error' do
req_stub = stub_prometheus_request(query_url, status: 400)
expect { execute_query }
- .to raise_error(Gitlab::PrometheusClient::Error, 'Bad data received')
+ .to raise_error(Gitlab::PrometheusClient::QueryError, 'Bad data received')
expect(req_stub).to have_been_requested
end
end
context 'when request returns 500' do
- it 'raises a Gitlab::PrometheusClient::Error error' do
+ it 'raises a Gitlab::PrometheusClient::UnexpectedResponseError error' do
req_stub = stub_prometheus_request(query_url, status: 500, body: { message: 'FAIL!' })
expect { execute_query }
- .to raise_error(Gitlab::PrometheusClient::Error, '500 - {"message":"FAIL!"}')
+ .to raise_error(Gitlab::PrometheusClient::UnexpectedResponseError, '500 - {"message":"FAIL!"}')
expect(req_stub).to have_been_requested
end
end
context 'when request returns non json data' do
- it 'raises a Gitlab::PrometheusClient::Error error' do
+ it 'raises a Gitlab::PrometheusClient::UnexpectedResponseError error' do
req_stub = stub_prometheus_request(query_url, status: 200, body: 'not json')
expect { execute_query }
- .to raise_error(Gitlab::PrometheusClient::Error, 'Parsing response failed')
+ .to raise_error(Gitlab::PrometheusClient::UnexpectedResponseError, 'Parsing response failed')
expect(req_stub).to have_been_requested
end
end
@@ -85,35 +85,35 @@ describe Gitlab::PrometheusClient do
let(:prometheus_url) {"https://prometheus.invalid.example.com/api/v1/query?query=1"}
shared_examples 'exceptions are raised' do
- it 'raises a Gitlab::PrometheusClient::Error error when a SocketError is rescued' do
+ it 'raises a Gitlab::PrometheusClient::ConnectionError error when a SocketError is rescued' do
req_stub = stub_prometheus_request_with_exception(prometheus_url, SocketError)
expect { subject }
- .to raise_error(Gitlab::PrometheusClient::Error, "Can't connect to #{prometheus_url}")
+ .to raise_error(Gitlab::PrometheusClient::ConnectionError, "Can't connect to #{prometheus_url}")
expect(req_stub).to have_been_requested
end
- it 'raises a Gitlab::PrometheusClient::Error error when a SSLError is rescued' do
+ it 'raises a Gitlab::PrometheusClient::ConnectionError error when a SSLError is rescued' do
req_stub = stub_prometheus_request_with_exception(prometheus_url, OpenSSL::SSL::SSLError)
expect { subject }
- .to raise_error(Gitlab::PrometheusClient::Error, "#{prometheus_url} contains invalid SSL data")
+ .to raise_error(Gitlab::PrometheusClient::ConnectionError, "#{prometheus_url} contains invalid SSL data")
expect(req_stub).to have_been_requested
end
- it 'raises a Gitlab::PrometheusClient::Error error when a Gitlab::HTTP::ResponseError is rescued' do
+ it 'raises a Gitlab::PrometheusClient::ConnectionError error when a Gitlab::HTTP::ResponseError is rescued' do
req_stub = stub_prometheus_request_with_exception(prometheus_url, Gitlab::HTTP::ResponseError)
expect { subject }
- .to raise_error(Gitlab::PrometheusClient::Error, "Network connection error")
+ .to raise_error(Gitlab::PrometheusClient::ConnectionError, "Network connection error")
expect(req_stub).to have_been_requested
end
- it 'raises a Gitlab::PrometheusClient::Error error when a Gitlab::HTTP::ResponseError with a code is rescued' do
+ it 'raises a Gitlab::PrometheusClient::ConnectionError error when a Gitlab::HTTP::ResponseError with a code is rescued' do
req_stub = stub_prometheus_request_with_exception(prometheus_url, Gitlab::HTTP::ResponseError.new(code: 400))
expect { subject }
- .to raise_error(Gitlab::PrometheusClient::Error, "Network connection error")
+ .to raise_error(Gitlab::PrometheusClient::ConnectionError, "Network connection error")
expect(req_stub).to have_been_requested
end
end
@@ -400,9 +400,9 @@ describe Gitlab::PrometheusClient do
context "without response code" do
let(:response_error) { Gitlab::HTTP::ResponseError }
- it 'raises PrometheusClient::Error' do
+ it 'raises PrometheusClient::ConnectionError' do
expect { subject.proxy('query', { query: prometheus_query }) }.to(
- raise_error(Gitlab::PrometheusClient::Error, 'Network connection error')
+ raise_error(Gitlab::PrometheusClient::ConnectionError, 'Network connection error')
)
end
end
diff --git a/spec/lib/gitlab/puma_logging/json_formatter_spec.rb b/spec/lib/gitlab/puma_logging/json_formatter_spec.rb
index f7f5b99d5e5..64ace09e01b 100644
--- a/spec/lib/gitlab/puma_logging/json_formatter_spec.rb
+++ b/spec/lib/gitlab/puma_logging/json_formatter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::PumaLogging::JSONFormatter do
+RSpec.describe Gitlab::PumaLogging::JSONFormatter do
it "generate json format with timestamp and pid" do
Timecop.freeze( Time.utc(2019, 12, 04, 9, 10, 11, 123456)) do
expect(subject.call('log message')).to eq "{\"timestamp\":\"2019-12-04T09:10:11.123Z\",\"pid\":#{Process.pid},\"message\":\"log message\"}"
diff --git a/spec/lib/gitlab/push_options_spec.rb b/spec/lib/gitlab/push_options_spec.rb
index fc9e421bea6..8f43943e2d1 100644
--- a/spec/lib/gitlab/push_options_spec.rb
+++ b/spec/lib/gitlab/push_options_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::PushOptions do
+RSpec.describe Gitlab::PushOptions do
describe 'namespace and key validation' do
it 'ignores unrecognised namespaces' do
options = described_class.new(['invalid.key=value'])
diff --git a/spec/lib/gitlab/query_limiting/active_support_subscriber_spec.rb b/spec/lib/gitlab/query_limiting/active_support_subscriber_spec.rb
index 2db6d2fb60f..a8dd482c7b8 100644
--- a/spec/lib/gitlab/query_limiting/active_support_subscriber_spec.rb
+++ b/spec/lib/gitlab/query_limiting/active_support_subscriber_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::QueryLimiting::ActiveSupportSubscriber do
+RSpec.describe Gitlab::QueryLimiting::ActiveSupportSubscriber do
let(:transaction) { instance_double(Gitlab::QueryLimiting::Transaction, increment: true) }
before do
diff --git a/spec/lib/gitlab/query_limiting/middleware_spec.rb b/spec/lib/gitlab/query_limiting/middleware_spec.rb
index f397843df54..cae6df8cdd5 100644
--- a/spec/lib/gitlab/query_limiting/middleware_spec.rb
+++ b/spec/lib/gitlab/query_limiting/middleware_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::QueryLimiting::Middleware do
+RSpec.describe Gitlab::QueryLimiting::Middleware do
describe '#call' do
it 'runs the application with query limiting in place' do
middleware = described_class.new(-> (env) { env })
diff --git a/spec/lib/gitlab/query_limiting/transaction_spec.rb b/spec/lib/gitlab/query_limiting/transaction_spec.rb
index 4e906314b5a..331c3c1d8b0 100644
--- a/spec/lib/gitlab/query_limiting/transaction_spec.rb
+++ b/spec/lib/gitlab/query_limiting/transaction_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::QueryLimiting::Transaction do
+RSpec.describe Gitlab::QueryLimiting::Transaction do
after do
Thread.current[described_class::THREAD_KEY] = nil
end
diff --git a/spec/lib/gitlab/query_limiting_spec.rb b/spec/lib/gitlab/query_limiting_spec.rb
index e9c6bbc35a3..0fcd865567d 100644
--- a/spec/lib/gitlab/query_limiting_spec.rb
+++ b/spec/lib/gitlab/query_limiting_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::QueryLimiting do
+RSpec.describe Gitlab::QueryLimiting do
describe '.enable?' do
it 'returns true in a test environment' do
expect(described_class.enable?).to eq(true)
diff --git a/spec/lib/gitlab/quick_actions/command_definition_spec.rb b/spec/lib/gitlab/quick_actions/command_definition_spec.rb
index 45b710adf07..d63c21954f2 100644
--- a/spec/lib/gitlab/quick_actions/command_definition_spec.rb
+++ b/spec/lib/gitlab/quick_actions/command_definition_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::QuickActions::CommandDefinition do
+RSpec.describe Gitlab::QuickActions::CommandDefinition do
subject { described_class.new(:command) }
describe "#all_names" do
diff --git a/spec/lib/gitlab/quick_actions/dsl_spec.rb b/spec/lib/gitlab/quick_actions/dsl_spec.rb
index 1145a7edc85..f990abfb253 100644
--- a/spec/lib/gitlab/quick_actions/dsl_spec.rb
+++ b/spec/lib/gitlab/quick_actions/dsl_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::QuickActions::Dsl do
+RSpec.describe Gitlab::QuickActions::Dsl do
before :all do
DummyClass = Struct.new(:project) do
include Gitlab::QuickActions::Dsl
diff --git a/spec/lib/gitlab/quick_actions/extractor_spec.rb b/spec/lib/gitlab/quick_actions/extractor_spec.rb
index 6ea597bf01e..f4104b78d5c 100644
--- a/spec/lib/gitlab/quick_actions/extractor_spec.rb
+++ b/spec/lib/gitlab/quick_actions/extractor_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::QuickActions::Extractor do
+RSpec.describe Gitlab::QuickActions::Extractor do
let(:definitions) do
Class.new do
include Gitlab::QuickActions::Dsl
diff --git a/spec/lib/gitlab/quick_actions/spend_time_and_date_separator_spec.rb b/spec/lib/gitlab/quick_actions/spend_time_and_date_separator_spec.rb
index fd149cd1114..0b012bfd970 100644
--- a/spec/lib/gitlab/quick_actions/spend_time_and_date_separator_spec.rb
+++ b/spec/lib/gitlab/quick_actions/spend_time_and_date_separator_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::QuickActions::SpendTimeAndDateSeparator do
+RSpec.describe Gitlab::QuickActions::SpendTimeAndDateSeparator do
subject { described_class }
shared_examples 'arg line with invalid parameters' do
diff --git a/spec/lib/gitlab/quick_actions/substitution_definition_spec.rb b/spec/lib/gitlab/quick_actions/substitution_definition_spec.rb
index d1a44e2feeb..b28ac49b4ea 100644
--- a/spec/lib/gitlab/quick_actions/substitution_definition_spec.rb
+++ b/spec/lib/gitlab/quick_actions/substitution_definition_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::QuickActions::SubstitutionDefinition do
+RSpec.describe Gitlab::QuickActions::SubstitutionDefinition do
let(:content) do
<<EOF
Hello! Let's do this!
diff --git a/spec/lib/gitlab/rate_limit_helpers_spec.rb b/spec/lib/gitlab/rate_limit_helpers_spec.rb
index 5ab79a2bbfe..e7d4c69d47b 100644
--- a/spec/lib/gitlab/rate_limit_helpers_spec.rb
+++ b/spec/lib/gitlab/rate_limit_helpers_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::RateLimitHelpers, :clean_gitlab_redis_shared_state do
+RSpec.describe Gitlab::RateLimitHelpers, :clean_gitlab_redis_shared_state do
let(:limiter_class) do
Class.new do
include ::Gitlab::RateLimitHelpers
diff --git a/spec/lib/gitlab/reactive_cache_set_cache_spec.rb b/spec/lib/gitlab/reactive_cache_set_cache_spec.rb
index a0d8f15ba1b..19fb2ada476 100644
--- a/spec/lib/gitlab/reactive_cache_set_cache_spec.rb
+++ b/spec/lib/gitlab/reactive_cache_set_cache_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::ReactiveCacheSetCache, :clean_gitlab_redis_cache do
+RSpec.describe Gitlab::ReactiveCacheSetCache, :clean_gitlab_redis_cache do
let_it_be(:project) { create(:project) }
let(:cache_prefix) { 'cache_prefix' }
let(:expires_in) { 10.minutes }
diff --git a/spec/lib/gitlab/redis/boolean_spec.rb b/spec/lib/gitlab/redis/boolean_spec.rb
index bfacf0c448b..9c233ba089f 100644
--- a/spec/lib/gitlab/redis/boolean_spec.rb
+++ b/spec/lib/gitlab/redis/boolean_spec.rb
@@ -2,7 +2,7 @@
require "spec_helper"
-describe Gitlab::Redis::Boolean do
+RSpec.describe Gitlab::Redis::Boolean do
subject(:redis_boolean) { described_class.new(bool) }
let(:bool) { true }
diff --git a/spec/lib/gitlab/redis/cache_spec.rb b/spec/lib/gitlab/redis/cache_spec.rb
index 0718998f981..5f73b84288d 100644
--- a/spec/lib/gitlab/redis/cache_spec.rb
+++ b/spec/lib/gitlab/redis/cache_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Redis::Cache do
+RSpec.describe Gitlab::Redis::Cache do
let(:config_file_name) { "config/redis.cache.yml" }
let(:environment_config_file_name) { "GITLAB_REDIS_CACHE_CONFIG_FILE" }
let(:config_old_format_socket) { "spec/fixtures/config/redis_cache_old_format_socket.yml" }
diff --git a/spec/lib/gitlab/redis/queues_spec.rb b/spec/lib/gitlab/redis/queues_spec.rb
index 93207b6f469..8a32c991943 100644
--- a/spec/lib/gitlab/redis/queues_spec.rb
+++ b/spec/lib/gitlab/redis/queues_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Redis::Queues do
+RSpec.describe Gitlab::Redis::Queues do
let(:config_file_name) { "config/redis.queues.yml" }
let(:environment_config_file_name) { "GITLAB_REDIS_QUEUES_CONFIG_FILE" }
let(:config_old_format_socket) { "spec/fixtures/config/redis_queues_old_format_socket.yml" }
diff --git a/spec/lib/gitlab/redis/shared_state_spec.rb b/spec/lib/gitlab/redis/shared_state_spec.rb
index aa61fd99eb5..bd90e4c750d 100644
--- a/spec/lib/gitlab/redis/shared_state_spec.rb
+++ b/spec/lib/gitlab/redis/shared_state_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Redis::SharedState do
+RSpec.describe Gitlab::Redis::SharedState do
let(:config_file_name) { "config/redis.shared_state.yml" }
let(:environment_config_file_name) { "GITLAB_REDIS_SHARED_STATE_CONFIG_FILE" }
let(:config_old_format_socket) { "spec/fixtures/config/redis_shared_state_old_format_socket.yml" }
diff --git a/spec/lib/gitlab/redis/wrapper_spec.rb b/spec/lib/gitlab/redis/wrapper_spec.rb
index 51a36eb062c..283853ee863 100644
--- a/spec/lib/gitlab/redis/wrapper_spec.rb
+++ b/spec/lib/gitlab/redis/wrapper_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Redis::Wrapper do
+RSpec.describe Gitlab::Redis::Wrapper do
let(:config_file_name) { "config/resque.yml" }
let(:environment_config_file_name) { "GITLAB_REDIS_CONFIG_FILE" }
let(:config_old_format_socket) { "spec/fixtures/config/redis_old_format_socket.yml" }
diff --git a/spec/lib/gitlab/reference_counter_spec.rb b/spec/lib/gitlab/reference_counter_spec.rb
index ae7b18ca007..0d0ac75ee22 100644
--- a/spec/lib/gitlab/reference_counter_spec.rb
+++ b/spec/lib/gitlab/reference_counter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::ReferenceCounter, :clean_gitlab_redis_shared_state do
+RSpec.describe Gitlab::ReferenceCounter, :clean_gitlab_redis_shared_state do
let(:reference_counter) { described_class.new('project-1') }
describe '#increase' do
diff --git a/spec/lib/gitlab/reference_extractor_spec.rb b/spec/lib/gitlab/reference_extractor_spec.rb
index dd16f3c6035..0172defc75d 100644
--- a/spec/lib/gitlab/reference_extractor_spec.rb
+++ b/spec/lib/gitlab/reference_extractor_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::ReferenceExtractor do
+RSpec.describe Gitlab::ReferenceExtractor do
let_it_be(:project) { create(:project) }
before do
diff --git a/spec/lib/gitlab/regex_spec.rb b/spec/lib/gitlab/regex_spec.rb
index 2f220272651..7aece6fe697 100644
--- a/spec/lib/gitlab/regex_spec.rb
+++ b/spec/lib/gitlab/regex_spec.rb
@@ -2,7 +2,7 @@
require 'fast_spec_helper'
-describe Gitlab::Regex do
+RSpec.describe Gitlab::Regex do
shared_examples_for 'project/group name regex' do
it { is_expected.to match('gitlab-ce') }
it { is_expected.to match('GitLab CE') }
@@ -262,6 +262,39 @@ describe Gitlab::Regex do
it { is_expected.not_to match('!!()()') }
end
+ describe '.maven_version_regex' do
+ subject { described_class.maven_version_regex }
+
+ it { is_expected.to match('0')}
+ it { is_expected.to match('1') }
+ it { is_expected.to match('03') }
+ it { is_expected.to match('2.0') }
+ it { is_expected.to match('01.2') }
+ it { is_expected.to match('10.2.3-beta')}
+ it { is_expected.to match('1.2-SNAPSHOT') }
+ it { is_expected.to match('20') }
+ it { is_expected.to match('20.3') }
+ it { is_expected.to match('1.2.1') }
+ it { is_expected.to match('1.4.2-12') }
+ it { is_expected.to match('1.2-beta-2') }
+ it { is_expected.to match('12.1.2-2-1') }
+ it { is_expected.to match('1.1-beta-2') }
+ it { is_expected.to match('1.3.350.v20200505-1744') }
+ it { is_expected.to match('2.0.0.v200706041905-7C78EK9E_EkMNfNOd2d8qq') }
+ it { is_expected.to match('1.2-alpha-1-20050205.060708-1') }
+ it { is_expected.to match('703220b4e2cea9592caeb9f3013f6b1e5335c293') }
+ it { is_expected.to match('RELEASE') }
+ it { is_expected.not_to match('..1.2.3') }
+ it { is_expected.not_to match(' 1.2.3') }
+ it { is_expected.not_to match("1.2.3 \r\t") }
+ it { is_expected.not_to match("\r\t 1.2.3") }
+ it { is_expected.not_to match('1./2.3') }
+ it { is_expected.not_to match('1.2.3-4/../../') }
+ it { is_expected.not_to match('1.2.3-4%2e%2e%') }
+ it { is_expected.not_to match('../../../../../1.2.3') }
+ it { is_expected.not_to match('%2e%2e%2f1.2.3') }
+ end
+
describe '.semver_regex' do
subject { described_class.semver_regex }
diff --git a/spec/lib/gitlab/repo_path_spec.rb b/spec/lib/gitlab/repo_path_spec.rb
index 68571b9de20..05f32459164 100644
--- a/spec/lib/gitlab/repo_path_spec.rb
+++ b/spec/lib/gitlab/repo_path_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ::Gitlab::RepoPath do
+RSpec.describe ::Gitlab::RepoPath do
include Gitlab::Routing
let_it_be(:project) { create(:project, :repository) }
diff --git a/spec/lib/gitlab/repository_cache_adapter_spec.rb b/spec/lib/gitlab/repository_cache_adapter_spec.rb
index dba5ffc84c5..3727217203e 100644
--- a/spec/lib/gitlab/repository_cache_adapter_spec.rb
+++ b/spec/lib/gitlab/repository_cache_adapter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::RepositoryCacheAdapter do
+RSpec.describe Gitlab::RepositoryCacheAdapter do
let(:project) { create(:project, :repository) }
let(:repository) { project.repository }
let(:cache) { repository.send(:cache) }
diff --git a/spec/lib/gitlab/repository_cache_spec.rb b/spec/lib/gitlab/repository_cache_spec.rb
index be31be761ad..80285a6c732 100644
--- a/spec/lib/gitlab/repository_cache_spec.rb
+++ b/spec/lib/gitlab/repository_cache_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::RepositoryCache do
+RSpec.describe Gitlab::RepositoryCache do
let_it_be(:project) { create(:project) }
let(:backend) { double('backend').as_null_object }
let(:repository) { project.repository }
diff --git a/spec/lib/gitlab/repository_hash_cache_spec.rb b/spec/lib/gitlab/repository_hash_cache_spec.rb
index 014a2f235b9..ea856c14a77 100644
--- a/spec/lib/gitlab/repository_hash_cache_spec.rb
+++ b/spec/lib/gitlab/repository_hash_cache_spec.rb
@@ -2,7 +2,7 @@
require "spec_helper"
-describe Gitlab::RepositoryHashCache, :clean_gitlab_redis_cache do
+RSpec.describe Gitlab::RepositoryHashCache, :clean_gitlab_redis_cache do
let_it_be(:project) { create(:project) }
let(:repository) { project.repository }
let(:namespace) { "#{repository.full_path}:#{project.id}" }
diff --git a/spec/lib/gitlab/repository_set_cache_spec.rb b/spec/lib/gitlab/repository_set_cache_spec.rb
index b09194e7d0b..024aae49b04 100644
--- a/spec/lib/gitlab/repository_set_cache_spec.rb
+++ b/spec/lib/gitlab/repository_set_cache_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::RepositorySetCache, :clean_gitlab_redis_cache do
+RSpec.describe Gitlab::RepositorySetCache, :clean_gitlab_redis_cache do
let_it_be(:project) { create(:project) }
let(:repository) { project.repository }
let(:namespace) { "#{repository.full_path}:#{project.id}" }
diff --git a/spec/lib/gitlab/repository_size_checker_spec.rb b/spec/lib/gitlab/repository_size_checker_spec.rb
index 61f76d716e5..9b2c02b1190 100644
--- a/spec/lib/gitlab/repository_size_checker_spec.rb
+++ b/spec/lib/gitlab/repository_size_checker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::RepositorySizeChecker do
+RSpec.describe Gitlab::RepositorySizeChecker do
let(:current_size) { 0 }
let(:limit) { 50 }
let(:enabled) { true }
diff --git a/spec/lib/gitlab/repository_size_error_message_spec.rb b/spec/lib/gitlab/repository_size_error_message_spec.rb
index 9e4d19cc572..b6b975143c9 100644
--- a/spec/lib/gitlab/repository_size_error_message_spec.rb
+++ b/spec/lib/gitlab/repository_size_error_message_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::RepositorySizeErrorMessage do
+RSpec.describe Gitlab::RepositorySizeErrorMessage do
let(:checker) do
Gitlab::RepositorySizeChecker.new(
current_size_proc: -> { 15.megabytes },
diff --git a/spec/lib/gitlab/repository_url_builder_spec.rb b/spec/lib/gitlab/repository_url_builder_spec.rb
index a5797146cc5..63cb1d7a9ac 100644
--- a/spec/lib/gitlab/repository_url_builder_spec.rb
+++ b/spec/lib/gitlab/repository_url_builder_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::RepositoryUrlBuilder do
+RSpec.describe Gitlab::RepositoryUrlBuilder do
describe '.build' do
using RSpec::Parameterized::TableSyntax
diff --git a/spec/lib/gitlab/request_context_spec.rb b/spec/lib/gitlab/request_context_spec.rb
index d7af0765d53..b9acfa4a841 100644
--- a/spec/lib/gitlab/request_context_spec.rb
+++ b/spec/lib/gitlab/request_context_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::RequestContext, :request_store do
+RSpec.describe Gitlab::RequestContext, :request_store do
subject { described_class.instance }
before do
diff --git a/spec/lib/gitlab/request_forgery_protection_spec.rb b/spec/lib/gitlab/request_forgery_protection_spec.rb
index b7a3dc16eff..20996dd44b8 100644
--- a/spec/lib/gitlab/request_forgery_protection_spec.rb
+++ b/spec/lib/gitlab/request_forgery_protection_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::RequestForgeryProtection, :allow_forgery_protection do
+RSpec.describe Gitlab::RequestForgeryProtection, :allow_forgery_protection do
let(:csrf_token) { SecureRandom.base64(ActionController::RequestForgeryProtection::AUTHENTICITY_TOKEN_LENGTH) }
let(:env) do
{
diff --git a/spec/lib/gitlab/request_profiler/profile_spec.rb b/spec/lib/gitlab/request_profiler/profile_spec.rb
index a75f3c66156..2e9c75dde87 100644
--- a/spec/lib/gitlab/request_profiler/profile_spec.rb
+++ b/spec/lib/gitlab/request_profiler/profile_spec.rb
@@ -2,7 +2,7 @@
require 'fast_spec_helper'
-describe Gitlab::RequestProfiler::Profile do
+RSpec.describe Gitlab::RequestProfiler::Profile do
let(:profile) { described_class.new(filename) }
describe '.new' do
diff --git a/spec/lib/gitlab/request_profiler_spec.rb b/spec/lib/gitlab/request_profiler_spec.rb
index f157189a72d..4d3b361efcb 100644
--- a/spec/lib/gitlab/request_profiler_spec.rb
+++ b/spec/lib/gitlab/request_profiler_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::RequestProfiler do
+RSpec.describe Gitlab::RequestProfiler do
describe '.profile_token' do
it 'returns a token' do
expect(described_class.profile_token).to be_present
diff --git a/spec/lib/gitlab/route_map_spec.rb b/spec/lib/gitlab/route_map_spec.rb
index d5e70b91fb4..e2c71980068 100644
--- a/spec/lib/gitlab/route_map_spec.rb
+++ b/spec/lib/gitlab/route_map_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::RouteMap do
+RSpec.describe Gitlab::RouteMap do
describe '#initialize' do
context 'when the data is not YAML' do
it 'raises an error' do
diff --git a/spec/lib/gitlab/routing_spec.rb b/spec/lib/gitlab/routing_spec.rb
index 5446d6559fe..304ce57aee3 100644
--- a/spec/lib/gitlab/routing_spec.rb
+++ b/spec/lib/gitlab/routing_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Routing do
+RSpec.describe Gitlab::Routing do
context 'when module is included' do
subject do
Class.new.include(described_class).new
diff --git a/spec/lib/gitlab/rugged_instrumentation_spec.rb b/spec/lib/gitlab/rugged_instrumentation_spec.rb
index d6f3fb9be55..393bb957aba 100644
--- a/spec/lib/gitlab/rugged_instrumentation_spec.rb
+++ b/spec/lib/gitlab/rugged_instrumentation_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::RuggedInstrumentation, :request_store do
+RSpec.describe Gitlab::RuggedInstrumentation, :request_store do
subject { described_class }
describe '.query_time' do
diff --git a/spec/lib/gitlab/runtime_spec.rb b/spec/lib/gitlab/runtime_spec.rb
index 8f920bb2e01..8ed7cc141cd 100644
--- a/spec/lib/gitlab/runtime_spec.rb
+++ b/spec/lib/gitlab/runtime_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Runtime do
+RSpec.describe Gitlab::Runtime do
shared_examples "valid runtime" do |runtime, max_threads|
it "identifies itself" do
expect(subject.identify).to eq(runtime)
@@ -48,18 +48,47 @@ describe Gitlab::Runtime do
before do
stub_const('::Puma', puma_type)
allow(puma_type).to receive_message_chain(:cli_config, :options).and_return(max_threads: 2)
+ stub_env('ACTION_CABLE_IN_APP', 'false')
end
it_behaves_like "valid runtime", :puma, 3
+
+ context "when ActionCable in-app mode is enabled" do
+ before do
+ stub_env('ACTION_CABLE_IN_APP', 'true')
+ stub_env('ACTION_CABLE_WORKER_POOL_SIZE', '3')
+ end
+
+ it_behaves_like "valid runtime", :puma, 6
+ end
+
+ context "when ActionCable standalone is run" do
+ before do
+ stub_const('ACTION_CABLE_SERVER', true)
+ stub_env('ACTION_CABLE_WORKER_POOL_SIZE', '8')
+ end
+
+ it_behaves_like "valid runtime", :puma, 11
+ end
end
context "unicorn" do
before do
stub_const('::Unicorn', Module.new)
stub_const('::Unicorn::HttpServer', Class.new)
+ stub_env('ACTION_CABLE_IN_APP', 'false')
end
it_behaves_like "valid runtime", :unicorn, 1
+
+ context "when ActionCable in-app mode is enabled" do
+ before do
+ stub_env('ACTION_CABLE_IN_APP', 'true')
+ stub_env('ACTION_CABLE_WORKER_POOL_SIZE', '3')
+ end
+
+ it_behaves_like "valid runtime", :unicorn, 4
+ end
end
context "sidekiq" do
@@ -105,17 +134,4 @@ describe Gitlab::Runtime do
it_behaves_like "valid runtime", :rails_runner, 1
end
-
- context "action_cable" do
- before do
- stub_const('ACTION_CABLE_SERVER', true)
- stub_const('::Puma', Module.new)
-
- allow(Gitlab::Application).to receive_message_chain(:config, :action_cable, :worker_pool_size).and_return(8)
- end
-
- it "reports its maximum concurrency based on ActionCable's worker pool size" do
- expect(subject.max_threads).to eq(9)
- end
- end
end
diff --git a/spec/lib/gitlab/safe_request_store_spec.rb b/spec/lib/gitlab/safe_request_store_spec.rb
index def05a3f285..704102ccaee 100644
--- a/spec/lib/gitlab/safe_request_store_spec.rb
+++ b/spec/lib/gitlab/safe_request_store_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::SafeRequestStore do
+RSpec.describe Gitlab::SafeRequestStore do
describe '.store' do
context 'when RequestStore is active', :request_store do
it 'uses RequestStore' do
diff --git a/spec/lib/gitlab/sanitizers/exif_spec.rb b/spec/lib/gitlab/sanitizers/exif_spec.rb
index 58fba673f8e..88ef3ce6aa5 100644
--- a/spec/lib/gitlab/sanitizers/exif_spec.rb
+++ b/spec/lib/gitlab/sanitizers/exif_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Sanitizers::Exif do
+RSpec.describe Gitlab::Sanitizers::Exif do
let(:sanitizer) { described_class.new }
describe '#batch_clean' do
diff --git a/spec/lib/gitlab/sanitizers/svg_spec.rb b/spec/lib/gitlab/sanitizers/svg_spec.rb
index 18fa96a2914..88fd7b78abe 100644
--- a/spec/lib/gitlab/sanitizers/svg_spec.rb
+++ b/spec/lib/gitlab/sanitizers/svg_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Sanitizers::SVG do
+RSpec.describe Gitlab::Sanitizers::SVG do
let(:scrubber) { Gitlab::Sanitizers::SVG::Scrubber.new }
let(:namespace) { double(Nokogiri::XML::Namespace, prefix: 'xlink', href: 'http://www.w3.org/1999/xlink') }
let(:namespaced_attr) { double(Nokogiri::XML::Attr, name: 'href', namespace: namespace, value: '#awesome_id') }
diff --git a/spec/lib/gitlab/search/found_blob_spec.rb b/spec/lib/gitlab/search/found_blob_spec.rb
index ce6a54100a5..8b1c91f689d 100644
--- a/spec/lib/gitlab/search/found_blob_spec.rb
+++ b/spec/lib/gitlab/search/found_blob_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Search::FoundBlob do
+RSpec.describe Gitlab::Search::FoundBlob do
let(:project) { create(:project, :public, :repository) }
describe 'parsing content results' do
diff --git a/spec/lib/gitlab/search/found_wiki_page_spec.rb b/spec/lib/gitlab/search/found_wiki_page_spec.rb
index e8b6728aba5..fc166ad3851 100644
--- a/spec/lib/gitlab/search/found_wiki_page_spec.rb
+++ b/spec/lib/gitlab/search/found_wiki_page_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Search::FoundWikiPage do
+RSpec.describe Gitlab::Search::FoundWikiPage do
let(:project) { create(:project, :public, :repository) }
describe 'policy' do
diff --git a/spec/lib/gitlab/search/query_spec.rb b/spec/lib/gitlab/search/query_spec.rb
index 112e9a59f04..e9601002922 100644
--- a/spec/lib/gitlab/search/query_spec.rb
+++ b/spec/lib/gitlab/search/query_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Search::Query do
+RSpec.describe Gitlab::Search::Query do
let(:query) { 'base filter:wow anotherfilter:noway name:maybe other:mmm leftover' }
let(:subject) do
described_class.new(query) do
diff --git a/spec/lib/gitlab/search_context/builder_spec.rb b/spec/lib/gitlab/search_context/builder_spec.rb
index 1707b54b273..5b4190fc67e 100644
--- a/spec/lib/gitlab/search_context/builder_spec.rb
+++ b/spec/lib/gitlab/search_context/builder_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::SearchContext::Builder, type: :controller do
+RSpec.describe Gitlab::SearchContext::Builder, type: :controller do
controller(ApplicationController) { }
subject(:builder) { described_class.new(controller.view_context) }
diff --git a/spec/lib/gitlab/search_context/controller_concern_spec.rb b/spec/lib/gitlab/search_context/controller_concern_spec.rb
index 16784cafb76..9cf6b8d49aa 100644
--- a/spec/lib/gitlab/search_context/controller_concern_spec.rb
+++ b/spec/lib/gitlab/search_context/controller_concern_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::SearchContext::ControllerConcern, type: :controller do
+RSpec.describe Gitlab::SearchContext::ControllerConcern, type: :controller do
controller(ApplicationController) do
include Gitlab::SearchContext::ControllerConcern
end
diff --git a/spec/lib/gitlab/search_results_spec.rb b/spec/lib/gitlab/search_results_spec.rb
index ab14602a468..61fa61566cd 100644
--- a/spec/lib/gitlab/search_results_spec.rb
+++ b/spec/lib/gitlab/search_results_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::SearchResults do
+RSpec.describe Gitlab::SearchResults do
include ProjectForksHelper
include SearchHelpers
diff --git a/spec/lib/gitlab/serializer/ci/variables_spec.rb b/spec/lib/gitlab/serializer/ci/variables_spec.rb
index 900508420c9..9b0475259fe 100644
--- a/spec/lib/gitlab/serializer/ci/variables_spec.rb
+++ b/spec/lib/gitlab/serializer/ci/variables_spec.rb
@@ -2,7 +2,7 @@
require 'fast_spec_helper'
-describe Gitlab::Serializer::Ci::Variables do
+RSpec.describe Gitlab::Serializer::Ci::Variables do
subject do
described_class.load(described_class.dump(object))
end
diff --git a/spec/lib/gitlab/serializer/pagination_spec.rb b/spec/lib/gitlab/serializer/pagination_spec.rb
index 1e7f441f258..69b5e181769 100644
--- a/spec/lib/gitlab/serializer/pagination_spec.rb
+++ b/spec/lib/gitlab/serializer/pagination_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Serializer::Pagination do
+RSpec.describe Gitlab::Serializer::Pagination do
let(:request) { double(url: "#{Gitlab.config.gitlab.url}:8080/api/v4/projects?#{query.to_query}", query_parameters: query) }
let(:response) { spy('response') }
let(:headers) { spy('headers') }
diff --git a/spec/lib/gitlab/serverless/service_spec.rb b/spec/lib/gitlab/serverless/service_spec.rb
index 6db8b9cd0ba..3400be5b48e 100644
--- a/spec/lib/gitlab/serverless/service_spec.rb
+++ b/spec/lib/gitlab/serverless/service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Serverless::Service do
+RSpec.describe Gitlab::Serverless::Service do
let(:cluster) { create(:cluster) }
let(:environment) { create(:environment) }
let(:attributes) do
diff --git a/spec/lib/gitlab/service_desk_email_spec.rb b/spec/lib/gitlab/service_desk_email_spec.rb
new file mode 100644
index 00000000000..23e2b2ff3cf
--- /dev/null
+++ b/spec/lib/gitlab/service_desk_email_spec.rb
@@ -0,0 +1,59 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::ServiceDeskEmail do
+ describe '.enabled?' do
+ context 'when service_desk_email is enabled and address is set' do
+ before do
+ stub_service_desk_email_setting(enabled: true, address: 'foo')
+ end
+
+ it 'returns true' do
+ expect(described_class.enabled?).to be_truthy
+ end
+ end
+
+ context 'when service_desk_email is disabled' do
+ before do
+ stub_service_desk_email_setting(enabled: false, address: 'foo')
+ end
+
+ it 'returns false' do
+ expect(described_class.enabled?).to be_falsey
+ end
+ end
+
+ context 'when service desk address is not set' do
+ before do
+ stub_service_desk_email_setting(enabled: true, address: nil)
+ end
+
+ it 'returns false' do
+ expect(described_class.enabled?).to be_falsey
+ end
+ end
+ end
+
+ describe '.key_from_address' do
+ context 'when service desk address is set' do
+ before do
+ stub_service_desk_email_setting(address: 'address+%{key}@example.com')
+ end
+
+ it 'returns key' do
+ expect(described_class.key_from_address('address+key@example.com')).to eq('key')
+ end
+ end
+
+ context 'when service desk address is not set' do
+ before do
+ stub_service_desk_email_setting(address: nil)
+ end
+
+ it 'returns nil' do
+ expect(described_class.key_from_address('address+key@example.com')).to be_nil
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/service_desk_spec.rb b/spec/lib/gitlab/service_desk_spec.rb
new file mode 100644
index 00000000000..f554840ec78
--- /dev/null
+++ b/spec/lib/gitlab/service_desk_spec.rb
@@ -0,0 +1,56 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::ServiceDesk do
+ before do
+ allow(Gitlab::IncomingEmail).to receive(:enabled?).and_return(true)
+ allow(Gitlab::IncomingEmail).to receive(:supports_wildcard?).and_return(true)
+ end
+
+ describe 'enabled?' do
+ let_it_be(:project) { create(:project) }
+
+ subject { described_class.enabled?(project: project) }
+
+ it { is_expected.to be_truthy }
+
+ context 'when service desk is not supported' do
+ before do
+ allow(described_class).to receive(:supported?).and_return(false)
+ end
+
+ it { is_expected.to be_falsy }
+ end
+
+ context 'when service desk is disabled for project' do
+ before do
+ project.update!(service_desk_enabled: false)
+ end
+
+ it { is_expected.to be_falsy }
+ end
+ end
+
+ describe 'supported?' do
+ subject { described_class.supported? }
+
+ it { is_expected.to be_truthy }
+
+ context 'when incoming emails are disabled' do
+ before do
+ allow(Gitlab::IncomingEmail).to receive(:enabled?).and_return(false)
+ end
+
+ it { is_expected.to be_falsy }
+ end
+
+ context 'when email key is not supported' do
+ before do
+ allow(Gitlab::IncomingEmail).to receive(:supports_wildcard?).and_return(false)
+ end
+
+ it { is_expected.to be_falsy }
+ end
+ end
+end
diff --git a/spec/lib/gitlab/session_spec.rb b/spec/lib/gitlab/session_spec.rb
index 8db73f0ec7b..de680e8425e 100644
--- a/spec/lib/gitlab/session_spec.rb
+++ b/spec/lib/gitlab/session_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Session do
+RSpec.describe Gitlab::Session do
it 'uses the current thread as a data store' do
Thread.current[:session_storage] = { a: :b }
diff --git a/spec/lib/gitlab/shard_health_cache_spec.rb b/spec/lib/gitlab/shard_health_cache_spec.rb
index f747849b5e9..5c47ac7e9a0 100644
--- a/spec/lib/gitlab/shard_health_cache_spec.rb
+++ b/spec/lib/gitlab/shard_health_cache_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::ShardHealthCache, :clean_gitlab_redis_cache do
+RSpec.describe Gitlab::ShardHealthCache, :clean_gitlab_redis_cache do
let(:shards) { %w(foo bar) }
before do
diff --git a/spec/lib/gitlab/shell_spec.rb b/spec/lib/gitlab/shell_spec.rb
index 1f515cffdbf..b0dc34e8abf 100644
--- a/spec/lib/gitlab/shell_spec.rb
+++ b/spec/lib/gitlab/shell_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require 'stringio'
-describe Gitlab::Shell do
+RSpec.describe Gitlab::Shell do
let_it_be(:project) { create(:project, :repository) }
let(:repository) { project.repository }
let(:gitlab_shell) { described_class.new }
diff --git a/spec/lib/gitlab/sherlock/collection_spec.rb b/spec/lib/gitlab/sherlock/collection_spec.rb
index bdc89c3d3cf..fcf8e6638f8 100644
--- a/spec/lib/gitlab/sherlock/collection_spec.rb
+++ b/spec/lib/gitlab/sherlock/collection_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Sherlock::Collection do
+RSpec.describe Gitlab::Sherlock::Collection do
let(:collection) { described_class.new }
let(:transaction) do
diff --git a/spec/lib/gitlab/sherlock/file_sample_spec.rb b/spec/lib/gitlab/sherlock/file_sample_spec.rb
index b09ba5c62dc..8a1aa51e2d4 100644
--- a/spec/lib/gitlab/sherlock/file_sample_spec.rb
+++ b/spec/lib/gitlab/sherlock/file_sample_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Sherlock::FileSample do
+RSpec.describe Gitlab::Sherlock::FileSample do
let(:sample) { described_class.new(__FILE__, [], 150.4, 2) }
describe '#id' do
diff --git a/spec/lib/gitlab/sherlock/line_profiler_spec.rb b/spec/lib/gitlab/sherlock/line_profiler_spec.rb
index c1997606839..2220a2cafc8 100644
--- a/spec/lib/gitlab/sherlock/line_profiler_spec.rb
+++ b/spec/lib/gitlab/sherlock/line_profiler_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Sherlock::LineProfiler do
+RSpec.describe Gitlab::Sherlock::LineProfiler do
let(:profiler) { described_class.new }
describe '#profile' do
diff --git a/spec/lib/gitlab/sherlock/line_sample_spec.rb b/spec/lib/gitlab/sherlock/line_sample_spec.rb
index b68e8cc0266..db031377787 100644
--- a/spec/lib/gitlab/sherlock/line_sample_spec.rb
+++ b/spec/lib/gitlab/sherlock/line_sample_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Sherlock::LineSample do
+RSpec.describe Gitlab::Sherlock::LineSample do
let(:sample) { described_class.new(150.0, 4) }
describe '#duration' do
diff --git a/spec/lib/gitlab/sherlock/location_spec.rb b/spec/lib/gitlab/sherlock/location_spec.rb
index 7b40c84c2d1..4a8b5dffba2 100644
--- a/spec/lib/gitlab/sherlock/location_spec.rb
+++ b/spec/lib/gitlab/sherlock/location_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Sherlock::Location do
+RSpec.describe Gitlab::Sherlock::Location do
let(:location) { described_class.new(__FILE__, 1) }
describe 'from_ruby_location' do
diff --git a/spec/lib/gitlab/sherlock/middleware_spec.rb b/spec/lib/gitlab/sherlock/middleware_spec.rb
index 8d6e362f622..645bde6681d 100644
--- a/spec/lib/gitlab/sherlock/middleware_spec.rb
+++ b/spec/lib/gitlab/sherlock/middleware_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Sherlock::Middleware do
+RSpec.describe Gitlab::Sherlock::Middleware do
let(:app) { double(:app) }
let(:middleware) { described_class.new(app) }
diff --git a/spec/lib/gitlab/sherlock/query_spec.rb b/spec/lib/gitlab/sherlock/query_spec.rb
index 13c7e6f8f8b..b8dfd082c37 100644
--- a/spec/lib/gitlab/sherlock/query_spec.rb
+++ b/spec/lib/gitlab/sherlock/query_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Sherlock::Query do
+RSpec.describe Gitlab::Sherlock::Query do
let(:started_at) { Time.utc(2015, 1, 1) }
let(:finished_at) { started_at + 5 }
diff --git a/spec/lib/gitlab/sherlock/transaction_spec.rb b/spec/lib/gitlab/sherlock/transaction_spec.rb
index 728c44df4f3..535b0ad4d8a 100644
--- a/spec/lib/gitlab/sherlock/transaction_spec.rb
+++ b/spec/lib/gitlab/sherlock/transaction_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Sherlock::Transaction do
+RSpec.describe Gitlab::Sherlock::Transaction do
let(:transaction) { described_class.new('POST', '/cat_pictures') }
describe '#id' do
diff --git a/spec/lib/gitlab/sidekiq_cluster/cli_spec.rb b/spec/lib/gitlab/sidekiq_cluster/cli_spec.rb
index fa23bf8958c..cf165d1770b 100644
--- a/spec/lib/gitlab/sidekiq_cluster/cli_spec.rb
+++ b/spec/lib/gitlab/sidekiq_cluster/cli_spec.rb
@@ -3,7 +3,7 @@
require 'fast_spec_helper'
require 'rspec-parameterized'
-describe Gitlab::SidekiqCluster::CLI do
+RSpec.describe Gitlab::SidekiqCluster::CLI do
let(:cli) { described_class.new('/dev/null') }
let(:timeout) { described_class::DEFAULT_SOFT_TIMEOUT_SECONDS }
let(:default_options) do
diff --git a/spec/lib/gitlab/sidekiq_cluster_spec.rb b/spec/lib/gitlab/sidekiq_cluster_spec.rb
index 3f466e9f2a3..d625a2a2185 100644
--- a/spec/lib/gitlab/sidekiq_cluster_spec.rb
+++ b/spec/lib/gitlab/sidekiq_cluster_spec.rb
@@ -3,7 +3,7 @@
require 'fast_spec_helper'
require 'rspec-parameterized'
-describe Gitlab::SidekiqCluster do
+RSpec.describe Gitlab::SidekiqCluster do
describe '.trap_signals' do
it 'traps the given signals' do
expect(described_class).to receive(:trap).ordered.with(:INT)
diff --git a/spec/lib/gitlab/sidekiq_config/cli_methods_spec.rb b/spec/lib/gitlab/sidekiq_config/cli_methods_spec.rb
index 7a8aba2d396..01e7c06249a 100644
--- a/spec/lib/gitlab/sidekiq_config/cli_methods_spec.rb
+++ b/spec/lib/gitlab/sidekiq_config/cli_methods_spec.rb
@@ -3,7 +3,7 @@
require 'fast_spec_helper'
require 'rspec-parameterized'
-describe Gitlab::SidekiqConfig::CliMethods do
+RSpec.describe Gitlab::SidekiqConfig::CliMethods do
let(:dummy_root) { '/tmp/' }
describe '.worker_queues' do
diff --git a/spec/lib/gitlab/sidekiq_config/worker_spec.rb b/spec/lib/gitlab/sidekiq_config/worker_spec.rb
index 00343a0264d..05987f95b33 100644
--- a/spec/lib/gitlab/sidekiq_config/worker_spec.rb
+++ b/spec/lib/gitlab/sidekiq_config/worker_spec.rb
@@ -2,7 +2,7 @@
require 'fast_spec_helper'
-describe Gitlab::SidekiqConfig::Worker do
+RSpec.describe Gitlab::SidekiqConfig::Worker do
def create_worker(queue:, **attributes)
namespace = queue.include?(':') && queue.split(':').first
inner_worker = double(
diff --git a/spec/lib/gitlab/sidekiq_config_spec.rb b/spec/lib/gitlab/sidekiq_config_spec.rb
index 66744d07aaa..d216b9d0c18 100644
--- a/spec/lib/gitlab/sidekiq_config_spec.rb
+++ b/spec/lib/gitlab/sidekiq_config_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::SidekiqConfig do
+RSpec.describe Gitlab::SidekiqConfig do
describe '.workers' do
it 'includes all workers' do
worker_classes = described_class.workers.map(&:klass)
diff --git a/spec/lib/gitlab/sidekiq_daemon/memory_killer_spec.rb b/spec/lib/gitlab/sidekiq_daemon/memory_killer_spec.rb
index 45bcc71dfcb..0ff2dbb234a 100644
--- a/spec/lib/gitlab/sidekiq_daemon/memory_killer_spec.rb
+++ b/spec/lib/gitlab/sidekiq_daemon/memory_killer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::SidekiqDaemon::MemoryKiller do
+RSpec.describe Gitlab::SidekiqDaemon::MemoryKiller do
let(:memory_killer) { described_class.new }
let(:pid) { 12345 }
diff --git a/spec/lib/gitlab/sidekiq_daemon/monitor_spec.rb b/spec/lib/gitlab/sidekiq_daemon/monitor_spec.rb
index 3f49ef0e9a7..749c7af6f59 100644
--- a/spec/lib/gitlab/sidekiq_daemon/monitor_spec.rb
+++ b/spec/lib/gitlab/sidekiq_daemon/monitor_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::SidekiqDaemon::Monitor do
+RSpec.describe Gitlab::SidekiqDaemon::Monitor do
let(:monitor) { described_class.new }
describe '#within_job' do
diff --git a/spec/lib/gitlab/sidekiq_logging/deduplication_logger_spec.rb b/spec/lib/gitlab/sidekiq_logging/deduplication_logger_spec.rb
index 3cc5c0bed1b..82f927fe481 100644
--- a/spec/lib/gitlab/sidekiq_logging/deduplication_logger_spec.rb
+++ b/spec/lib/gitlab/sidekiq_logging/deduplication_logger_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::SidekiqLogging::DeduplicationLogger do
+RSpec.describe Gitlab::SidekiqLogging::DeduplicationLogger do
describe '#log_deduplication' do
let(:job) do
{
@@ -18,11 +18,12 @@ describe Gitlab::SidekiqLogging::DeduplicationLogger do
expected_payload = {
'job_status' => 'deduplicated',
'message' => "#{job['class']} JID-#{job['jid']}: deduplicated: a fancy strategy",
- 'deduplication_type' => 'a fancy strategy'
+ 'deduplication.type' => 'a fancy strategy',
+ 'deduplication.options.foo' => :bar
}
expect(Sidekiq.logger).to receive(:info).with(a_hash_including(expected_payload)).and_call_original
- described_class.instance.log(job, "a fancy strategy")
+ described_class.instance.log(job, "a fancy strategy", { foo: :bar })
end
it "does not modify the job" do
diff --git a/spec/lib/gitlab/sidekiq_logging/exception_handler_spec.rb b/spec/lib/gitlab/sidekiq_logging/exception_handler_spec.rb
index a79a0678e2b..5a68b88c02d 100644
--- a/spec/lib/gitlab/sidekiq_logging/exception_handler_spec.rb
+++ b/spec/lib/gitlab/sidekiq_logging/exception_handler_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::SidekiqLogging::ExceptionHandler do
+RSpec.describe Gitlab::SidekiqLogging::ExceptionHandler do
describe '#call' do
let(:job) do
{
diff --git a/spec/lib/gitlab/sidekiq_logging/json_formatter_spec.rb b/spec/lib/gitlab/sidekiq_logging/json_formatter_spec.rb
index 10354147cf9..c879fdea3ad 100644
--- a/spec/lib/gitlab/sidekiq_logging/json_formatter_spec.rb
+++ b/spec/lib/gitlab/sidekiq_logging/json_formatter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::SidekiqLogging::JSONFormatter do
+RSpec.describe Gitlab::SidekiqLogging::JSONFormatter do
let(:message) { 'This is a test' }
let(:now) { Time.now }
let(:timestamp) { now.utc.to_f }
diff --git a/spec/lib/gitlab/sidekiq_logging/structured_logger_spec.rb b/spec/lib/gitlab/sidekiq_logging/structured_logger_spec.rb
index a456f814e78..03ace9a01c7 100644
--- a/spec/lib/gitlab/sidekiq_logging/structured_logger_spec.rb
+++ b/spec/lib/gitlab/sidekiq_logging/structured_logger_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::SidekiqLogging::StructuredLogger do
+RSpec.describe Gitlab::SidekiqLogging::StructuredLogger do
describe '#call' do
let(:timestamp) { Time.iso8601('2018-01-01T12:00:00.000Z') }
let(:created_at) { timestamp - 1.second }
diff --git a/spec/lib/gitlab/sidekiq_middleware/admin_mode/client_spec.rb b/spec/lib/gitlab/sidekiq_middleware/admin_mode/client_spec.rb
index f6449bae8c3..3d9ffb11ae2 100644
--- a/spec/lib/gitlab/sidekiq_middleware/admin_mode/client_spec.rb
+++ b/spec/lib/gitlab/sidekiq_middleware/admin_mode/client_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::SidekiqMiddleware::AdminMode::Client, :do_not_mock_admin_mode, :request_store do
+RSpec.describe Gitlab::SidekiqMiddleware::AdminMode::Client, :do_not_mock_admin_mode, :request_store do
include AdminModeHelper
let(:worker) do
diff --git a/spec/lib/gitlab/sidekiq_middleware/admin_mode/server_spec.rb b/spec/lib/gitlab/sidekiq_middleware/admin_mode/server_spec.rb
index 60475f0e403..20f1e88bcf4 100644
--- a/spec/lib/gitlab/sidekiq_middleware/admin_mode/server_spec.rb
+++ b/spec/lib/gitlab/sidekiq_middleware/admin_mode/server_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::SidekiqMiddleware::AdminMode::Server, :do_not_mock_admin_mode, :request_store do
+RSpec.describe Gitlab::SidekiqMiddleware::AdminMode::Server, :do_not_mock_admin_mode, :request_store do
include AdminModeHelper
let(:worker) do
diff --git a/spec/lib/gitlab/sidekiq_middleware/client_metrics_spec.rb b/spec/lib/gitlab/sidekiq_middleware/client_metrics_spec.rb
index 1d45b70ec3e..f7010b2001a 100644
--- a/spec/lib/gitlab/sidekiq_middleware/client_metrics_spec.rb
+++ b/spec/lib/gitlab/sidekiq_middleware/client_metrics_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::SidekiqMiddleware::ClientMetrics do
+RSpec.describe Gitlab::SidekiqMiddleware::ClientMetrics do
context "with worker attribution" do
subject { described_class.new }
diff --git a/spec/lib/gitlab/sidekiq_middleware/duplicate_jobs/client_spec.rb b/spec/lib/gitlab/sidekiq_middleware/duplicate_jobs/client_spec.rb
index a1e4cbb1e31..98350fb9b8e 100644
--- a/spec/lib/gitlab/sidekiq_middleware/duplicate_jobs/client_spec.rb
+++ b/spec/lib/gitlab/sidekiq_middleware/duplicate_jobs/client_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::SidekiqMiddleware::DuplicateJobs::Client, :clean_gitlab_redis_queues do
+RSpec.describe Gitlab::SidekiqMiddleware::DuplicateJobs::Client, :clean_gitlab_redis_queues do
let(:worker_class) do
Class.new do
def self.name
diff --git a/spec/lib/gitlab/sidekiq_middleware/duplicate_jobs/duplicate_job_spec.rb b/spec/lib/gitlab/sidekiq_middleware/duplicate_jobs/duplicate_job_spec.rb
index 13c86563be7..8ef61d4eae9 100644
--- a/spec/lib/gitlab/sidekiq_middleware/duplicate_jobs/duplicate_job_spec.rb
+++ b/spec/lib/gitlab/sidekiq_middleware/duplicate_jobs/duplicate_job_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::SidekiqMiddleware::DuplicateJobs::DuplicateJob, :clean_gitlab_redis_queues do
+RSpec.describe Gitlab::SidekiqMiddleware::DuplicateJobs::DuplicateJob, :clean_gitlab_redis_queues do
using RSpec::Parameterized::TableSyntax
subject(:duplicate_job) do
diff --git a/spec/lib/gitlab/sidekiq_middleware/duplicate_jobs/server_spec.rb b/spec/lib/gitlab/sidekiq_middleware/duplicate_jobs/server_spec.rb
index 312ebd30a76..3f75d867936 100644
--- a/spec/lib/gitlab/sidekiq_middleware/duplicate_jobs/server_spec.rb
+++ b/spec/lib/gitlab/sidekiq_middleware/duplicate_jobs/server_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::SidekiqMiddleware::DuplicateJobs::Server, :clean_gitlab_redis_queues do
+RSpec.describe Gitlab::SidekiqMiddleware::DuplicateJobs::Server, :clean_gitlab_redis_queues do
let(:worker_class) do
Class.new do
def self.name
diff --git a/spec/lib/gitlab/sidekiq_middleware/duplicate_jobs/strategies/until_executing_spec.rb b/spec/lib/gitlab/sidekiq_middleware/duplicate_jobs/strategies/until_executing_spec.rb
index eb8b0a951a8..77d760d1ae3 100644
--- a/spec/lib/gitlab/sidekiq_middleware/duplicate_jobs/strategies/until_executing_spec.rb
+++ b/spec/lib/gitlab/sidekiq_middleware/duplicate_jobs/strategies/until_executing_spec.rb
@@ -3,7 +3,7 @@
require 'fast_spec_helper'
require 'timecop'
-describe Gitlab::SidekiqMiddleware::DuplicateJobs::Strategies::UntilExecuting do
+RSpec.describe Gitlab::SidekiqMiddleware::DuplicateJobs::Strategies::UntilExecuting do
let(:fake_duplicate_job) do
instance_double(Gitlab::SidekiqMiddleware::DuplicateJobs::DuplicateJob)
end
@@ -40,6 +40,7 @@ describe Gitlab::SidekiqMiddleware::DuplicateJobs::Strategies::UntilExecuting do
allow(fake_duplicate_job).to receive(:scheduled?).and_return(false)
allow(fake_duplicate_job).to receive(:check!).and_return('the jid')
allow(fake_duplicate_job).to receive(:droppable?).and_return(true)
+ allow(fake_duplicate_job).to receive(:options).and_return({})
job_hash = {}
expect(fake_duplicate_job).to receive(:duplicate?).and_return(true)
@@ -102,6 +103,7 @@ describe Gitlab::SidekiqMiddleware::DuplicateJobs::Strategies::UntilExecuting do
allow(fake_duplicate_job).to receive(:scheduled?).and_return(false)
allow(fake_duplicate_job).to receive(:check!).and_return('the jid')
allow(fake_duplicate_job).to receive(:duplicate?).and_return(true)
+ allow(fake_duplicate_job).to receive(:options).and_return({})
allow(fake_duplicate_job).to receive(:existing_jid).and_return('the jid')
allow(fake_duplicate_job).to receive(:droppable?).and_return(true)
end
@@ -119,7 +121,17 @@ describe Gitlab::SidekiqMiddleware::DuplicateJobs::Strategies::UntilExecuting do
fake_logger = instance_double(Gitlab::SidekiqLogging::DeduplicationLogger)
expect(Gitlab::SidekiqLogging::DeduplicationLogger).to receive(:instance).and_return(fake_logger)
- expect(fake_logger).to receive(:log).with(a_hash_including({ 'jid' => 'new jid' }), 'dropped until executing')
+ expect(fake_logger).to receive(:log).with(a_hash_including({ 'jid' => 'new jid' }), 'dropped until executing', {})
+
+ strategy.schedule({ 'jid' => 'new jid' }) {}
+ end
+
+ it 'logs the deduplication options of the worker' do
+ fake_logger = instance_double(Gitlab::SidekiqLogging::DeduplicationLogger)
+
+ expect(Gitlab::SidekiqLogging::DeduplicationLogger).to receive(:instance).and_return(fake_logger)
+ allow(fake_duplicate_job).to receive(:options).and_return({ foo: :bar })
+ expect(fake_logger).to receive(:log).with(a_hash_including({ 'jid' => 'new jid' }), 'dropped until executing', { foo: :bar })
strategy.schedule({ 'jid' => 'new jid' }) {}
end
diff --git a/spec/lib/gitlab/sidekiq_middleware/duplicate_jobs/strategies_spec.rb b/spec/lib/gitlab/sidekiq_middleware/duplicate_jobs/strategies_spec.rb
index 6ecc2a3a5f8..5d37e3cb1ae 100644
--- a/spec/lib/gitlab/sidekiq_middleware/duplicate_jobs/strategies_spec.rb
+++ b/spec/lib/gitlab/sidekiq_middleware/duplicate_jobs/strategies_spec.rb
@@ -2,7 +2,7 @@
require 'fast_spec_helper'
-describe Gitlab::SidekiqMiddleware::DuplicateJobs::Strategies do
+RSpec.describe Gitlab::SidekiqMiddleware::DuplicateJobs::Strategies do
describe '.for' do
it 'returns the right class for `until_executing`' do
expect(described_class.for(:until_executing)).to eq(described_class::UntilExecuting)
diff --git a/spec/lib/gitlab/sidekiq_middleware/extra_done_log_metadata_spec.rb b/spec/lib/gitlab/sidekiq_middleware/extra_done_log_metadata_spec.rb
index 98847885e62..dbab67f5996 100644
--- a/spec/lib/gitlab/sidekiq_middleware/extra_done_log_metadata_spec.rb
+++ b/spec/lib/gitlab/sidekiq_middleware/extra_done_log_metadata_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::SidekiqMiddleware::ExtraDoneLogMetadata do
+RSpec.describe Gitlab::SidekiqMiddleware::ExtraDoneLogMetadata do
# Cannot use Class.new for this as ApplicationWorker will need the class to
# have a name during `included do`.
let(:worker) { AdminEmailWorker.new }
diff --git a/spec/lib/gitlab/sidekiq_middleware/memory_killer_spec.rb b/spec/lib/gitlab/sidekiq_middleware/memory_killer_spec.rb
index b5be43ec96c..0d1616c4aed 100644
--- a/spec/lib/gitlab/sidekiq_middleware/memory_killer_spec.rb
+++ b/spec/lib/gitlab/sidekiq_middleware/memory_killer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::SidekiqMiddleware::MemoryKiller do
+RSpec.describe Gitlab::SidekiqMiddleware::MemoryKiller do
subject { described_class.new }
let(:pid) { 999 }
diff --git a/spec/lib/gitlab/sidekiq_middleware/monitor_spec.rb b/spec/lib/gitlab/sidekiq_middleware/monitor_spec.rb
index 398144025ea..85cddfa7bf1 100644
--- a/spec/lib/gitlab/sidekiq_middleware/monitor_spec.rb
+++ b/spec/lib/gitlab/sidekiq_middleware/monitor_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::SidekiqMiddleware::Monitor do
+RSpec.describe Gitlab::SidekiqMiddleware::Monitor do
let(:monitor) { described_class.new }
describe '#call' do
diff --git a/spec/lib/gitlab/sidekiq_middleware/server_metrics_spec.rb b/spec/lib/gitlab/sidekiq_middleware/server_metrics_spec.rb
index 4b7baea25e8..1b3b108d9ea 100644
--- a/spec/lib/gitlab/sidekiq_middleware/server_metrics_spec.rb
+++ b/spec/lib/gitlab/sidekiq_middleware/server_metrics_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::SidekiqMiddleware::ServerMetrics do
+RSpec.describe Gitlab::SidekiqMiddleware::ServerMetrics do
context "with worker attribution" do
subject { described_class.new }
diff --git a/spec/lib/gitlab/sidekiq_middleware/worker_context/client_spec.rb b/spec/lib/gitlab/sidekiq_middleware/worker_context/client_spec.rb
index 9cb89b1bc10..fff925f8532 100644
--- a/spec/lib/gitlab/sidekiq_middleware/worker_context/client_spec.rb
+++ b/spec/lib/gitlab/sidekiq_middleware/worker_context/client_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::SidekiqMiddleware::WorkerContext::Client do
+RSpec.describe Gitlab::SidekiqMiddleware::WorkerContext::Client do
let(:worker_class) do
Class.new do
def self.name
diff --git a/spec/lib/gitlab/sidekiq_middleware/worker_context/server_spec.rb b/spec/lib/gitlab/sidekiq_middleware/worker_context/server_spec.rb
index fdf643a8ad1..bde19fa7552 100644
--- a/spec/lib/gitlab/sidekiq_middleware/worker_context/server_spec.rb
+++ b/spec/lib/gitlab/sidekiq_middleware/worker_context/server_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::SidekiqMiddleware::WorkerContext::Server do
+RSpec.describe Gitlab::SidekiqMiddleware::WorkerContext::Server do
let(:worker_class) do
Class.new do
def self.name
diff --git a/spec/lib/gitlab/sidekiq_middleware_spec.rb b/spec/lib/gitlab/sidekiq_middleware_spec.rb
index 5ca0abeb132..018821e6c5e 100644
--- a/spec/lib/gitlab/sidekiq_middleware_spec.rb
+++ b/spec/lib/gitlab/sidekiq_middleware_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require 'sidekiq/testing'
-describe Gitlab::SidekiqMiddleware do
+RSpec.describe Gitlab::SidekiqMiddleware do
before do
stub_const('TestWorker', Class.new)
diff --git a/spec/lib/gitlab/sidekiq_queue_spec.rb b/spec/lib/gitlab/sidekiq_queue_spec.rb
index f5be8d9bfed..44ac89c0816 100644
--- a/spec/lib/gitlab/sidekiq_queue_spec.rb
+++ b/spec/lib/gitlab/sidekiq_queue_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::SidekiqQueue, :clean_gitlab_redis_queues do
+RSpec.describe Gitlab::SidekiqQueue, :clean_gitlab_redis_queues do
around do |example|
Sidekiq::Queue.new('authorized_projects').clear
Sidekiq::Testing.disable!(&example)
diff --git a/spec/lib/gitlab/sidekiq_signals_spec.rb b/spec/lib/gitlab/sidekiq_signals_spec.rb
index 10f1bad32cd..2f751839f6a 100644
--- a/spec/lib/gitlab/sidekiq_signals_spec.rb
+++ b/spec/lib/gitlab/sidekiq_signals_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::SidekiqSignals do
+RSpec.describe Gitlab::SidekiqSignals do
describe '.install' do
let(:result) { Hash.new { |h, k| h[k] = 0 } }
let(:int_handler) { -> (_) { result['INT'] += 1 } }
diff --git a/spec/lib/gitlab/sidekiq_status/client_middleware_spec.rb b/spec/lib/gitlab/sidekiq_status/client_middleware_spec.rb
index 1ca8cea66fc..0cf05fb0a5c 100644
--- a/spec/lib/gitlab/sidekiq_status/client_middleware_spec.rb
+++ b/spec/lib/gitlab/sidekiq_status/client_middleware_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::SidekiqStatus::ClientMiddleware do
+RSpec.describe Gitlab::SidekiqStatus::ClientMiddleware do
describe '#call' do
it 'tracks the job in Redis' do
expect(Gitlab::SidekiqStatus).to receive(:set).with('123', Gitlab::SidekiqStatus::DEFAULT_EXPIRATION)
diff --git a/spec/lib/gitlab/sidekiq_status/server_middleware_spec.rb b/spec/lib/gitlab/sidekiq_status/server_middleware_spec.rb
index 40bcb49d1d3..5a0c4cbd1b5 100644
--- a/spec/lib/gitlab/sidekiq_status/server_middleware_spec.rb
+++ b/spec/lib/gitlab/sidekiq_status/server_middleware_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::SidekiqStatus::ServerMiddleware do
+RSpec.describe Gitlab::SidekiqStatus::ServerMiddleware do
describe '#call' do
it 'stops tracking of a job upon completion' do
expect(Gitlab::SidekiqStatus).to receive(:unset).with('123')
diff --git a/spec/lib/gitlab/sidekiq_status_spec.rb b/spec/lib/gitlab/sidekiq_status_spec.rb
index 7b5c75b2f3b..dd5b8856ccd 100644
--- a/spec/lib/gitlab/sidekiq_status_spec.rb
+++ b/spec/lib/gitlab/sidekiq_status_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::SidekiqStatus do
+RSpec.describe Gitlab::SidekiqStatus do
describe '.set', :clean_gitlab_redis_shared_state do
it 'stores the job ID' do
described_class.set('123')
diff --git a/spec/lib/gitlab/sidekiq_versioning/manager_spec.rb b/spec/lib/gitlab/sidekiq_versioning/manager_spec.rb
index a528ce201a2..84161d9236e 100644
--- a/spec/lib/gitlab/sidekiq_versioning/manager_spec.rb
+++ b/spec/lib/gitlab/sidekiq_versioning/manager_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::SidekiqVersioning::Manager do
+RSpec.describe Gitlab::SidekiqVersioning::Manager do
before do
Sidekiq::Manager.prepend described_class
end
diff --git a/spec/lib/gitlab/sidekiq_versioning_spec.rb b/spec/lib/gitlab/sidekiq_versioning_spec.rb
index 11c866894c2..ed9650fc166 100644
--- a/spec/lib/gitlab/sidekiq_versioning_spec.rb
+++ b/spec/lib/gitlab/sidekiq_versioning_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::SidekiqVersioning, :redis do
+RSpec.describe Gitlab::SidekiqVersioning, :redis do
let(:foo_worker) do
Class.new do
def self.name
diff --git a/spec/lib/gitlab/slash_commands/application_help_spec.rb b/spec/lib/gitlab/slash_commands/application_help_spec.rb
index afa63c21584..b82121bf3a8 100644
--- a/spec/lib/gitlab/slash_commands/application_help_spec.rb
+++ b/spec/lib/gitlab/slash_commands/application_help_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::SlashCommands::ApplicationHelp do
+RSpec.describe Gitlab::SlashCommands::ApplicationHelp do
let(:params) { { command: '/gitlab', text: 'help' } }
let(:project) { build(:project) }
diff --git a/spec/lib/gitlab/slash_commands/command_spec.rb b/spec/lib/gitlab/slash_commands/command_spec.rb
index 9849cf78b2f..069577b3846 100644
--- a/spec/lib/gitlab/slash_commands/command_spec.rb
+++ b/spec/lib/gitlab/slash_commands/command_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::SlashCommands::Command do
+RSpec.describe Gitlab::SlashCommands::Command do
let(:project) { create(:project, :repository) }
let(:user) { create(:user) }
let(:chat_name) { double(:chat_name, user: user) }
diff --git a/spec/lib/gitlab/slash_commands/deploy_spec.rb b/spec/lib/gitlab/slash_commands/deploy_spec.rb
index fb9969800a2..36f47c711bc 100644
--- a/spec/lib/gitlab/slash_commands/deploy_spec.rb
+++ b/spec/lib/gitlab/slash_commands/deploy_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::SlashCommands::Deploy do
+RSpec.describe Gitlab::SlashCommands::Deploy do
describe '#execute' do
let(:project) { create(:project, :repository) }
let(:user) { create(:user) }
diff --git a/spec/lib/gitlab/slash_commands/issue_close_spec.rb b/spec/lib/gitlab/slash_commands/issue_close_spec.rb
index c0760ce0ba6..cf6ac2c5267 100644
--- a/spec/lib/gitlab/slash_commands/issue_close_spec.rb
+++ b/spec/lib/gitlab/slash_commands/issue_close_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::SlashCommands::IssueClose do
+RSpec.describe Gitlab::SlashCommands::IssueClose do
describe '#execute' do
let(:issue) { create(:issue, project: project) }
let(:project) { create(:project) }
diff --git a/spec/lib/gitlab/slash_commands/issue_comment_spec.rb b/spec/lib/gitlab/slash_commands/issue_comment_spec.rb
index c6f56d10d1f..f3c3bccfade 100644
--- a/spec/lib/gitlab/slash_commands/issue_comment_spec.rb
+++ b/spec/lib/gitlab/slash_commands/issue_comment_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::SlashCommands::IssueComment do
+RSpec.describe Gitlab::SlashCommands::IssueComment do
describe '#execute' do
let(:project) { create(:project, :public) }
let(:issue) { create(:issue, project: project) }
diff --git a/spec/lib/gitlab/slash_commands/issue_move_spec.rb b/spec/lib/gitlab/slash_commands/issue_move_spec.rb
index 1a45b0e06ba..5fffbb2d4cc 100644
--- a/spec/lib/gitlab/slash_commands/issue_move_spec.rb
+++ b/spec/lib/gitlab/slash_commands/issue_move_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::SlashCommands::IssueMove, service: true do
+RSpec.describe Gitlab::SlashCommands::IssueMove, service: true do
describe '#match' do
shared_examples_for 'move command' do |text_command|
it 'can be parsed to extract the needed fields' do
diff --git a/spec/lib/gitlab/slash_commands/issue_new_spec.rb b/spec/lib/gitlab/slash_commands/issue_new_spec.rb
index 90f0518a63e..c17cee887ee 100644
--- a/spec/lib/gitlab/slash_commands/issue_new_spec.rb
+++ b/spec/lib/gitlab/slash_commands/issue_new_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::SlashCommands::IssueNew do
+RSpec.describe Gitlab::SlashCommands::IssueNew do
describe '#execute' do
let(:project) { create(:project) }
let(:user) { create(:user) }
diff --git a/spec/lib/gitlab/slash_commands/issue_search_spec.rb b/spec/lib/gitlab/slash_commands/issue_search_spec.rb
index a142c8e4c92..8a86aaf67e3 100644
--- a/spec/lib/gitlab/slash_commands/issue_search_spec.rb
+++ b/spec/lib/gitlab/slash_commands/issue_search_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::SlashCommands::IssueSearch do
+RSpec.describe Gitlab::SlashCommands::IssueSearch do
describe '#execute' do
let!(:issue) { create(:issue, project: project, title: 'find me') }
let!(:confidential) { create(:issue, :confidential, project: project, title: 'mepmep find') }
diff --git a/spec/lib/gitlab/slash_commands/issue_show_spec.rb b/spec/lib/gitlab/slash_commands/issue_show_spec.rb
index e53f79dcd86..62628daf24d 100644
--- a/spec/lib/gitlab/slash_commands/issue_show_spec.rb
+++ b/spec/lib/gitlab/slash_commands/issue_show_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::SlashCommands::IssueShow do
+RSpec.describe Gitlab::SlashCommands::IssueShow do
describe '#execute' do
let(:issue) { create(:issue, project: project) }
let(:project) { create(:project) }
diff --git a/spec/lib/gitlab/slash_commands/presenters/access_spec.rb b/spec/lib/gitlab/slash_commands/presenters/access_spec.rb
index 804184a7173..5d62e96971b 100644
--- a/spec/lib/gitlab/slash_commands/presenters/access_spec.rb
+++ b/spec/lib/gitlab/slash_commands/presenters/access_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::SlashCommands::Presenters::Access do
+RSpec.describe Gitlab::SlashCommands::Presenters::Access do
shared_examples_for 'displays an error message' do
it do
expect(subject[:text]).to match(error_message)
diff --git a/spec/lib/gitlab/slash_commands/presenters/deploy_spec.rb b/spec/lib/gitlab/slash_commands/presenters/deploy_spec.rb
index 9c2e9ab982f..c6f188de576 100644
--- a/spec/lib/gitlab/slash_commands/presenters/deploy_spec.rb
+++ b/spec/lib/gitlab/slash_commands/presenters/deploy_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::SlashCommands::Presenters::Deploy do
+RSpec.describe Gitlab::SlashCommands::Presenters::Deploy do
let(:build) { create(:ci_build) }
describe '#present' do
diff --git a/spec/lib/gitlab/slash_commands/presenters/error_spec.rb b/spec/lib/gitlab/slash_commands/presenters/error_spec.rb
index 30ff81510c1..24d6aad1726 100644
--- a/spec/lib/gitlab/slash_commands/presenters/error_spec.rb
+++ b/spec/lib/gitlab/slash_commands/presenters/error_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::SlashCommands::Presenters::Error do
+RSpec.describe Gitlab::SlashCommands::Presenters::Error do
subject { described_class.new('Error').message }
it { is_expected.to be_a(Hash) }
diff --git a/spec/lib/gitlab/slash_commands/presenters/issue_close_spec.rb b/spec/lib/gitlab/slash_commands/presenters/issue_close_spec.rb
index adc13b4ee56..daa859d4de7 100644
--- a/spec/lib/gitlab/slash_commands/presenters/issue_close_spec.rb
+++ b/spec/lib/gitlab/slash_commands/presenters/issue_close_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::SlashCommands::Presenters::IssueClose do
+RSpec.describe Gitlab::SlashCommands::Presenters::IssueClose do
let(:project) { create(:project) }
let(:issue) { create(:issue, project: project) }
let(:attachment) { subject[:attachments].first }
diff --git a/spec/lib/gitlab/slash_commands/presenters/issue_comment_spec.rb b/spec/lib/gitlab/slash_commands/presenters/issue_comment_spec.rb
index 3741563a744..109b4b8fee1 100644
--- a/spec/lib/gitlab/slash_commands/presenters/issue_comment_spec.rb
+++ b/spec/lib/gitlab/slash_commands/presenters/issue_comment_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::SlashCommands::Presenters::IssueComment do
+RSpec.describe Gitlab::SlashCommands::Presenters::IssueComment do
let_it_be(:project) { create(:project) }
let_it_be(:issue) { create(:issue, project: project) }
let_it_be(:note) { create(:note, project: project, noteable: issue) }
diff --git a/spec/lib/gitlab/slash_commands/presenters/issue_move_spec.rb b/spec/lib/gitlab/slash_commands/presenters/issue_move_spec.rb
index 7726c3b6a87..df949154d4c 100644
--- a/spec/lib/gitlab/slash_commands/presenters/issue_move_spec.rb
+++ b/spec/lib/gitlab/slash_commands/presenters/issue_move_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::SlashCommands::Presenters::IssueMove do
+RSpec.describe Gitlab::SlashCommands::Presenters::IssueMove do
let_it_be(:admin) { create(:admin) }
let_it_be(:project, reload: true) { create(:project) }
let_it_be(:other_project) { create(:project) }
diff --git a/spec/lib/gitlab/slash_commands/presenters/issue_new_spec.rb b/spec/lib/gitlab/slash_commands/presenters/issue_new_spec.rb
index f926783fbea..03a94ea5e29 100644
--- a/spec/lib/gitlab/slash_commands/presenters/issue_new_spec.rb
+++ b/spec/lib/gitlab/slash_commands/presenters/issue_new_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::SlashCommands::Presenters::IssueNew do
+RSpec.describe Gitlab::SlashCommands::Presenters::IssueNew do
let(:project) { create(:project) }
let(:issue) { create(:issue, project: project) }
let(:attachment) { subject[:attachments].first }
diff --git a/spec/lib/gitlab/slash_commands/presenters/issue_search_spec.rb b/spec/lib/gitlab/slash_commands/presenters/issue_search_spec.rb
index e1c011133c4..c12e7fa7ee8 100644
--- a/spec/lib/gitlab/slash_commands/presenters/issue_search_spec.rb
+++ b/spec/lib/gitlab/slash_commands/presenters/issue_search_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::SlashCommands::Presenters::IssueSearch do
+RSpec.describe Gitlab::SlashCommands::Presenters::IssueSearch do
let(:project) { create(:project) }
let(:message) { subject[:text] }
diff --git a/spec/lib/gitlab/slash_commands/presenters/issue_show_spec.rb b/spec/lib/gitlab/slash_commands/presenters/issue_show_spec.rb
index 47b9a67f54f..91fd32d2b55 100644
--- a/spec/lib/gitlab/slash_commands/presenters/issue_show_spec.rb
+++ b/spec/lib/gitlab/slash_commands/presenters/issue_show_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::SlashCommands::Presenters::IssueShow do
+RSpec.describe Gitlab::SlashCommands::Presenters::IssueShow do
let(:user) { create(:user, :with_avatar) }
let(:project) { create(:project, creator: user) }
let(:issue) { create(:issue, project: project) }
diff --git a/spec/lib/gitlab/slash_commands/presenters/run_spec.rb b/spec/lib/gitlab/slash_commands/presenters/run_spec.rb
index f3ab01ef6bb..3a775e8f101 100644
--- a/spec/lib/gitlab/slash_commands/presenters/run_spec.rb
+++ b/spec/lib/gitlab/slash_commands/presenters/run_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::SlashCommands::Presenters::Run do
+RSpec.describe Gitlab::SlashCommands::Presenters::Run do
let(:presenter) { described_class.new }
describe '#present' do
diff --git a/spec/lib/gitlab/slash_commands/run_spec.rb b/spec/lib/gitlab/slash_commands/run_spec.rb
index 32a23129e3c..c9ff580d586 100644
--- a/spec/lib/gitlab/slash_commands/run_spec.rb
+++ b/spec/lib/gitlab/slash_commands/run_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::SlashCommands::Run do
+RSpec.describe Gitlab::SlashCommands::Run do
describe '.available?' do
it 'returns true when builds are enabled for the project' do
project = double(:project, builds_enabled?: true)
diff --git a/spec/lib/gitlab/slug/environment_spec.rb b/spec/lib/gitlab/slug/environment_spec.rb
index 7dc583a94b8..f516322b937 100644
--- a/spec/lib/gitlab/slug/environment_spec.rb
+++ b/spec/lib/gitlab/slug/environment_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Slug::Environment do
+RSpec.describe Gitlab::Slug::Environment do
describe '#generate' do
{
"staging-12345678901234567" => "staging-123456789-q517sa",
diff --git a/spec/lib/gitlab/snippet_search_results_spec.rb b/spec/lib/gitlab/snippet_search_results_spec.rb
index a41be0eaa95..e1ae26a4d9e 100644
--- a/spec/lib/gitlab/snippet_search_results_spec.rb
+++ b/spec/lib/gitlab/snippet_search_results_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::SnippetSearchResults do
+RSpec.describe Gitlab::SnippetSearchResults do
include SearchHelpers
let_it_be(:snippet) { create(:snippet, content: 'foo', file_name: 'foo') }
diff --git a/spec/lib/gitlab/sourcegraph_spec.rb b/spec/lib/gitlab/sourcegraph_spec.rb
index ef4008960a9..ad947475f06 100644
--- a/spec/lib/gitlab/sourcegraph_spec.rb
+++ b/spec/lib/gitlab/sourcegraph_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Sourcegraph do
+RSpec.describe Gitlab::Sourcegraph do
let_it_be(:user) { create(:user) }
let(:feature_scope) { true }
diff --git a/spec/lib/gitlab/sql/cte_spec.rb b/spec/lib/gitlab/sql/cte_spec.rb
index e6194924f5a..fdc150cd4b9 100644
--- a/spec/lib/gitlab/sql/cte_spec.rb
+++ b/spec/lib/gitlab/sql/cte_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::SQL::CTE do
+RSpec.describe Gitlab::SQL::CTE do
describe '#to_arel' do
it 'generates an Arel relation for the CTE body' do
relation = User.where(id: 1)
diff --git a/spec/lib/gitlab/sql/glob_spec.rb b/spec/lib/gitlab/sql/glob_spec.rb
index 83eed309ecc..8e2b842add6 100644
--- a/spec/lib/gitlab/sql/glob_spec.rb
+++ b/spec/lib/gitlab/sql/glob_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::SQL::Glob do
+RSpec.describe Gitlab::SQL::Glob do
describe '.to_like' do
it 'matches * as %' do
expect(glob('apple', '*')).to be(true)
diff --git a/spec/lib/gitlab/sql/pattern_spec.rb b/spec/lib/gitlab/sql/pattern_spec.rb
index 38b93913f6d..220ac2ff6da 100644
--- a/spec/lib/gitlab/sql/pattern_spec.rb
+++ b/spec/lib/gitlab/sql/pattern_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::SQL::Pattern do
+RSpec.describe Gitlab::SQL::Pattern do
describe '.to_pattern' do
subject(:to_pattern) { User.to_pattern(query) }
diff --git a/spec/lib/gitlab/sql/recursive_cte_spec.rb b/spec/lib/gitlab/sql/recursive_cte_spec.rb
index b15be56dd6d..02611620989 100644
--- a/spec/lib/gitlab/sql/recursive_cte_spec.rb
+++ b/spec/lib/gitlab/sql/recursive_cte_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::SQL::RecursiveCTE do
+RSpec.describe Gitlab::SQL::RecursiveCTE do
let(:cte) { described_class.new(:cte_name) }
describe '#to_arel' do
diff --git a/spec/lib/gitlab/sql/union_spec.rb b/spec/lib/gitlab/sql/union_spec.rb
index f736614ae53..c8be83c093d 100644
--- a/spec/lib/gitlab/sql/union_spec.rb
+++ b/spec/lib/gitlab/sql/union_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::SQL::Union do
+RSpec.describe Gitlab::SQL::Union do
let(:relation_1) { User.where(email: 'alice@example.com').select(:id) }
let(:relation_2) { User.where(email: 'bob@example.com').select(:id) }
diff --git a/spec/lib/gitlab/ssh_public_key_spec.rb b/spec/lib/gitlab/ssh_public_key_spec.rb
index 08e008c82d9..e1a588a4b7d 100644
--- a/spec/lib/gitlab/ssh_public_key_spec.rb
+++ b/spec/lib/gitlab/ssh_public_key_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::SSHPublicKey, lib: true do
+RSpec.describe Gitlab::SSHPublicKey, lib: true do
let(:key) { attributes_for(:rsa_key_2048)[:key] }
let(:public_key) { described_class.new(key) }
diff --git a/spec/lib/gitlab/static_site_editor/config_spec.rb b/spec/lib/gitlab/static_site_editor/config_spec.rb
index 4cfda83b8f6..b60a6a9b006 100644
--- a/spec/lib/gitlab/static_site_editor/config_spec.rb
+++ b/spec/lib/gitlab/static_site_editor/config_spec.rb
@@ -2,11 +2,14 @@
require 'spec_helper'
-describe Gitlab::StaticSiteEditor::Config do
+RSpec.describe Gitlab::StaticSiteEditor::Config do
subject(:config) { described_class.new(repository, ref, file_path, return_url) }
let_it_be(:namespace) { create(:namespace, name: 'namespace') }
+ let_it_be(:root_group) { create(:group, name: 'group') }
+ let_it_be(:subgroup) { create(:group, name: 'subgroup', parent: root_group) }
let_it_be(:project) { create(:project, :public, :repository, name: 'project', namespace: namespace) }
+ let_it_be(:project_with_subgroup) { create(:project, :public, :repository, name: 'project', group: subgroup) }
let_it_be(:repository) { project.repository }
let(:ref) { 'master' }
@@ -30,6 +33,34 @@ describe Gitlab::StaticSiteEditor::Config do
)
end
+ context 'when namespace is a subgroup' do
+ let(:repository) { project_with_subgroup.repository }
+
+ it 'returns data for the frontend component' do
+ is_expected.to include(
+ namespace: 'group/subgroup',
+ project: 'project',
+ base_url: '/group/subgroup/project/-/sse/master%2FREADME.md'
+ )
+ end
+ end
+
+ context 'when file has .md.erb extension' do
+ let(:file_path) { 'README.md.erb' }
+
+ before do
+ repository.create_file(
+ project.creator,
+ file_path,
+ '',
+ message: 'message',
+ branch_name: 'master'
+ )
+ end
+
+ it { is_expected.to include(is_supported_content: 'true') }
+ end
+
context 'when file path is nested' do
let(:file_path) { 'lib/README.md' }
diff --git a/spec/lib/gitlab/string_placeholder_replacer_spec.rb b/spec/lib/gitlab/string_placeholder_replacer_spec.rb
index 0295bf1265f..8f17bf64005 100644
--- a/spec/lib/gitlab/string_placeholder_replacer_spec.rb
+++ b/spec/lib/gitlab/string_placeholder_replacer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::StringPlaceholderReplacer do
+RSpec.describe Gitlab::StringPlaceholderReplacer do
describe '.render_url' do
it 'returns the nil if the string is blank' do
expect(described_class.replace_string_placeholders(nil, /whatever/)).to be_blank
diff --git a/spec/lib/gitlab/string_range_marker_spec.rb b/spec/lib/gitlab/string_range_marker_spec.rb
index ef9be7cd992..52fab6e3109 100644
--- a/spec/lib/gitlab/string_range_marker_spec.rb
+++ b/spec/lib/gitlab/string_range_marker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::StringRangeMarker do
+RSpec.describe Gitlab::StringRangeMarker do
describe '#mark' do
def mark_diff(rich = nil)
raw = 'abc <def>'
diff --git a/spec/lib/gitlab/string_regex_marker_spec.rb b/spec/lib/gitlab/string_regex_marker_spec.rb
index 2ab1ccc447b..2dadd222820 100644
--- a/spec/lib/gitlab/string_regex_marker_spec.rb
+++ b/spec/lib/gitlab/string_regex_marker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::StringRegexMarker do
+RSpec.describe Gitlab::StringRegexMarker do
describe '#mark' do
context 'with a single occurrence' do
let(:raw) { %{"name": "AFNetworking"} }
diff --git a/spec/lib/gitlab/submodule_links_spec.rb b/spec/lib/gitlab/submodule_links_spec.rb
index 1f2848a29e9..c69326e12be 100644
--- a/spec/lib/gitlab/submodule_links_spec.rb
+++ b/spec/lib/gitlab/submodule_links_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::SubmoduleLinks do
+RSpec.describe Gitlab::SubmoduleLinks do
let(:submodule_item) { double(id: 'hash', path: 'gitlab-foss') }
let(:repo) { double }
let(:links) { described_class.new(repo) }
diff --git a/spec/lib/gitlab/suggestions/commit_message_spec.rb b/spec/lib/gitlab/suggestions/commit_message_spec.rb
index 0774fc80528..1411f64f8b7 100644
--- a/spec/lib/gitlab/suggestions/commit_message_spec.rb
+++ b/spec/lib/gitlab/suggestions/commit_message_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Suggestions::CommitMessage do
+RSpec.describe Gitlab::Suggestions::CommitMessage do
def create_suggestion(file_path, new_line, to_content)
position = Gitlab::Diff::Position.new(old_path: file_path,
new_path: file_path,
diff --git a/spec/lib/gitlab/suggestions/file_suggestion_spec.rb b/spec/lib/gitlab/suggestions/file_suggestion_spec.rb
index 6fbbad017c5..1d25bf6edbd 100644
--- a/spec/lib/gitlab/suggestions/file_suggestion_spec.rb
+++ b/spec/lib/gitlab/suggestions/file_suggestion_spec.rb
@@ -2,8 +2,8 @@
require 'spec_helper'
-describe Gitlab::Suggestions::FileSuggestion do
- def create_suggestion(new_line, to_content)
+RSpec.describe Gitlab::Suggestions::FileSuggestion do
+ def create_suggestion(new_line, to_content, lines_above = 0, lines_below = 0)
position = Gitlab::Diff::Position.new(old_path: file_path,
new_path: file_path,
old_line: nil,
@@ -18,6 +18,8 @@ describe Gitlab::Suggestions::FileSuggestion do
create(:suggestion,
:content_from_repo,
note: diff_note,
+ lines_above: lines_above,
+ lines_below: lines_below,
to_content: to_content)
end
@@ -39,27 +41,9 @@ describe Gitlab::Suggestions::FileSuggestion do
create_suggestion(15, " *** SUGGESTION 2 ***\n")
end
- let(:file_suggestion) { described_class.new }
+ let(:suggestions) { [suggestion1, suggestion2] }
- describe '#add_suggestion' do
- it 'succeeds when adding a suggestion for the same file as the original' do
- file_suggestion.add_suggestion(suggestion1)
-
- expect { file_suggestion.add_suggestion(suggestion2) }.not_to raise_error
- end
-
- it 'raises an error when adding a suggestion for a different file' do
- allow(suggestion2)
- .to(receive_message_chain(:diff_file, :file_path)
- .and_return('path/to/different/file'))
-
- file_suggestion.add_suggestion(suggestion1)
-
- expect { file_suggestion.add_suggestion(suggestion2) }.to(
- raise_error(described_class::SuggestionForDifferentFileError)
- )
- end
- end
+ let(:file_suggestion) { described_class.new(file_path, suggestions) }
describe '#line_conflict' do
def stub_suggestions(line_index_spans)
@@ -175,67 +159,296 @@ describe Gitlab::Suggestions::FileSuggestion do
end
describe '#new_content' do
- it 'returns a blob with the suggestions applied to it' do
- file_suggestion.add_suggestion(suggestion1)
- file_suggestion.add_suggestion(suggestion2)
+ context 'with two suggestions' do
+ let(:suggestions) { [suggestion1, suggestion2] }
- expected_content = <<-CONTENT.strip_heredoc
- require 'fileutils'
- require 'open3'
+ it 'returns a blob with the suggestions applied to it' do
+ expected_content = <<-CONTENT.strip_heredoc
+ require 'fileutils'
+ require 'open3'
- module Popen
- extend self
+ module Popen
+ extend self
- def popen(cmd, path=nil)
- unless cmd.is_a?(Array)
- *** SUGGESTION 1 ***
+ def popen(cmd, path=nil)
+ unless cmd.is_a?(Array)
+ *** SUGGESTION 1 ***
+ end
+
+ path ||= Dir.pwd
+
+ vars = {
+ *** SUGGESTION 2 ***
+ }
+
+ options = {
+ chdir: path
+ }
+
+ unless File.directory?(path)
+ FileUtils.mkdir_p(path)
+ end
+
+ @cmd_output = ""
+ @cmd_status = 0
+
+ Open3.popen3(vars, *cmd, options) do |stdin, stdout, stderr, wait_thr|
+ @cmd_output << stdout.read
+ @cmd_output << stderr.read
+ @cmd_status = wait_thr.value.exitstatus
+ end
+
+ return @cmd_output, @cmd_status
end
+ end
+ CONTENT
- path ||= Dir.pwd
+ expect(file_suggestion.new_content).to eq(expected_content)
+ end
+ end
- vars = {
- *** SUGGESTION 2 ***
- }
+ context 'when no suggestions have been added' do
+ let(:suggestions) { [] }
- options = {
- chdir: path
- }
+ it 'returns an empty string' do
+ expect(file_suggestion.new_content).to eq('')
+ end
+ end
+
+ context 'with multiline suggestions' do
+ let(:suggestions) { [multi_suggestion1, multi_suggestion2, multi_suggestion3] }
+
+ context 'when the previous suggestion increases the line count' do
+ let!(:multi_suggestion1) do
+ create_suggestion(9, " *** SUGGESTION 1 ***\n *** SECOND LINE ***\n *** THIRD LINE ***\n")
+ end
- unless File.directory?(path)
- FileUtils.mkdir_p(path)
+ let!(:multi_suggestion2) do
+ create_suggestion(15, " *** SUGGESTION 2 ***\n *** SECOND LINE ***\n")
+ end
+
+ let!(:multi_suggestion3) do
+ create_suggestion(19, " chdir: *** SUGGESTION 3 ***\n")
+ end
+
+ it 'returns a blob with the suggestions applied to it' do
+ expected_content = <<-CONTENT.strip_heredoc
+ require 'fileutils'
+ require 'open3'
+
+ module Popen
+ extend self
+
+ def popen(cmd, path=nil)
+ unless cmd.is_a?(Array)
+ *** SUGGESTION 1 ***
+ *** SECOND LINE ***
+ *** THIRD LINE ***
+ end
+
+ path ||= Dir.pwd
+
+ vars = {
+ *** SUGGESTION 2 ***
+ *** SECOND LINE ***
+ }
+
+ options = {
+ chdir: *** SUGGESTION 3 ***
+ }
+
+ unless File.directory?(path)
+ FileUtils.mkdir_p(path)
+ end
+
+ @cmd_output = ""
+ @cmd_status = 0
+
+ Open3.popen3(vars, *cmd, options) do |stdin, stdout, stderr, wait_thr|
+ @cmd_output << stdout.read
+ @cmd_output << stderr.read
+ @cmd_status = wait_thr.value.exitstatus
+ end
+
+ return @cmd_output, @cmd_status
end
+ end
+ CONTENT
+
+ expect(file_suggestion.new_content).to eq(expected_content)
+ end
+ end
- @cmd_output = ""
- @cmd_status = 0
+ context 'when the previous suggestion decreases and increases the line count' do
+ let!(:multi_suggestion1) do
+ create_suggestion(9, " *** SUGGESTION 1 ***\n", 1, 1)
+ end
+
+ let!(:multi_suggestion2) do
+ create_suggestion(15, " *** SUGGESTION 2 ***\n *** SECOND LINE ***\n")
+ end
+
+ let!(:multi_suggestion3) do
+ create_suggestion(19, " chdir: *** SUGGESTION 3 ***\n")
+ end
+
+ it 'returns a blob with the suggestions applied to it' do
+ expected_content = <<-CONTENT.strip_heredoc
+ require 'fileutils'
+ require 'open3'
+
+ module Popen
+ extend self
+
+ def popen(cmd, path=nil)
+ *** SUGGESTION 1 ***
- Open3.popen3(vars, *cmd, options) do |stdin, stdout, stderr, wait_thr|
- @cmd_output << stdout.read
- @cmd_output << stderr.read
- @cmd_status = wait_thr.value.exitstatus
+ path ||= Dir.pwd
+
+ vars = {
+ *** SUGGESTION 2 ***
+ *** SECOND LINE ***
+ }
+
+ options = {
+ chdir: *** SUGGESTION 3 ***
+ }
+
+ unless File.directory?(path)
+ FileUtils.mkdir_p(path)
+ end
+
+ @cmd_output = ""
+ @cmd_status = 0
+
+ Open3.popen3(vars, *cmd, options) do |stdin, stdout, stderr, wait_thr|
+ @cmd_output << stdout.read
+ @cmd_output << stderr.read
+ @cmd_status = wait_thr.value.exitstatus
+ end
+
+ return @cmd_output, @cmd_status
end
+ end
+ CONTENT
+
+ expect(file_suggestion.new_content).to eq(expected_content)
+ end
+ end
+
+ context 'when the previous suggestion replaces with the same number of lines' do
+ let!(:multi_suggestion1) do
+ create_suggestion(9, " *** SUGGESTION 1 ***\n *** SECOND LINE ***\n *** THIRD LINE ***\n", 1, 1)
+ end
+
+ let!(:multi_suggestion2) do
+ create_suggestion(15, " *** SUGGESTION 2 ***\n")
+ end
+
+ let!(:multi_suggestion3) do
+ create_suggestion(19, " chdir: *** SUGGESTION 3 ***\n")
+ end
+
+ it 'returns a blob with the suggestions applied to it' do
+ expected_content = <<-CONTENT.strip_heredoc
+ require 'fileutils'
+ require 'open3'
+
+ module Popen
+ extend self
+
+ def popen(cmd, path=nil)
+ *** SUGGESTION 1 ***
+ *** SECOND LINE ***
+ *** THIRD LINE ***
+
+ path ||= Dir.pwd
+
+ vars = {
+ *** SUGGESTION 2 ***
+ }
+
+ options = {
+ chdir: *** SUGGESTION 3 ***
+ }
+
+ unless File.directory?(path)
+ FileUtils.mkdir_p(path)
+ end
+
+ @cmd_output = ""
+ @cmd_status = 0
+
+ Open3.popen3(vars, *cmd, options) do |stdin, stdout, stderr, wait_thr|
+ @cmd_output << stdout.read
+ @cmd_output << stderr.read
+ @cmd_status = wait_thr.value.exitstatus
+ end
- return @cmd_output, @cmd_status
+ return @cmd_output, @cmd_status
+ end
end
+ CONTENT
+
+ expect(file_suggestion.new_content).to eq(expected_content)
end
- CONTENT
+ end
- expect(file_suggestion.new_content).to eq(expected_content)
- end
+ context 'when the previous suggestion replaces multiple lines and the suggestions were applied out of order' do
+ let(:suggestions) { [multi_suggestion1, multi_suggestion3, multi_suggestion2] }
- it 'returns an empty string when no suggestions have been added' do
- expect(file_suggestion.new_content).to eq('')
- end
- end
+ let!(:multi_suggestion1) do
+ create_suggestion(9, " *** SUGGESTION 1 ***\n *** SECOND LINE ***\n *** THIRD LINE ***\n", 1, 1)
+ end
- describe '#file_path' do
- it 'returns the path of the file associated with the suggestions' do
- file_suggestion.add_suggestion(suggestion1)
+ let!(:multi_suggestion3) do
+ create_suggestion(19, " *** SUGGESTION 3 ***\n", 1, 1)
+ end
- expect(file_suggestion.file_path).to eq(file_path)
- end
+ let!(:multi_suggestion2) do
+ create_suggestion(15, " *** SUGGESTION 2 ***\n", 1, 1)
+ end
+
+ it 'returns a blob with the suggestions applied to it' do
+ expected_content = <<-CONTENT.strip_heredoc
+ require 'fileutils'
+ require 'open3'
+
+ module Popen
+ extend self
+
+ def popen(cmd, path=nil)
+ *** SUGGESTION 1 ***
+ *** SECOND LINE ***
+ *** THIRD LINE ***
+
+ path ||= Dir.pwd
+
+ *** SUGGESTION 2 ***
+
+ *** SUGGESTION 3 ***
+
+ unless File.directory?(path)
+ FileUtils.mkdir_p(path)
+ end
+
+ @cmd_output = ""
+ @cmd_status = 0
- it 'returns nil if no suggestions have been added' do
- expect(file_suggestion.file_path).to be(nil)
+ Open3.popen3(vars, *cmd, options) do |stdin, stdout, stderr, wait_thr|
+ @cmd_output << stdout.read
+ @cmd_output << stderr.read
+ @cmd_status = wait_thr.value.exitstatus
+ end
+
+ return @cmd_output, @cmd_status
+ end
+ end
+ CONTENT
+
+ expect(file_suggestion.new_content).to eq(expected_content)
+ end
+ end
end
end
end
diff --git a/spec/lib/gitlab/suggestions/suggestion_set_spec.rb b/spec/lib/gitlab/suggestions/suggestion_set_spec.rb
index 8c61e6c42a6..54d79a9d4ba 100644
--- a/spec/lib/gitlab/suggestions/suggestion_set_spec.rb
+++ b/spec/lib/gitlab/suggestions/suggestion_set_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Suggestions::SuggestionSet do
+RSpec.describe Gitlab::Suggestions::SuggestionSet do
def create_suggestion(file_path, new_line, to_content)
position = Gitlab::Diff::Position.new(old_path: file_path,
new_path: file_path,
@@ -87,11 +87,10 @@ describe Gitlab::Suggestions::SuggestionSet do
it 'returns an array of hashes with proper key/value pairs' do
first_action = suggestion_set.actions.first
- file_path, file_suggestion = suggestion_set
- .send(:suggestions_per_file).first
+ file_suggestion = suggestion_set.send(:suggestions_per_file).first
expect(first_action[:action]).to be('update')
- expect(first_action[:file_path]).to eq(file_path)
+ expect(first_action[:file_path]).to eq(file_suggestion.file_path)
expect(first_action[:content]).to eq(file_suggestion.new_content)
end
end
diff --git a/spec/lib/gitlab/tab_width_spec.rb b/spec/lib/gitlab/tab_width_spec.rb
index 3b5014d27e4..f0efb6ec4a7 100644
--- a/spec/lib/gitlab/tab_width_spec.rb
+++ b/spec/lib/gitlab/tab_width_spec.rb
@@ -2,7 +2,7 @@
require 'fast_spec_helper'
-describe Gitlab::TabWidth, lib: true do
+RSpec.describe Gitlab::TabWidth, lib: true do
describe '.css_class_for_user' do
it 'returns default CSS class when user is nil' do
css_class = described_class.css_class_for_user(nil)
diff --git a/spec/lib/gitlab/tcp_checker_spec.rb b/spec/lib/gitlab/tcp_checker_spec.rb
index 9474e79cc5d..12149576de0 100644
--- a/spec/lib/gitlab/tcp_checker_spec.rb
+++ b/spec/lib/gitlab/tcp_checker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::TcpChecker, :permit_dns do
+RSpec.describe Gitlab::TcpChecker, :permit_dns do
before do
@server = TCPServer.new('localhost', 0)
_, @port, _, @ip = @server.addr
diff --git a/spec/lib/gitlab/template/finders/global_template_finder_spec.rb b/spec/lib/gitlab/template/finders/global_template_finder_spec.rb
index 580da497944..e776284b3e8 100644
--- a/spec/lib/gitlab/template/finders/global_template_finder_spec.rb
+++ b/spec/lib/gitlab/template/finders/global_template_finder_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Template::Finders::GlobalTemplateFinder do
+RSpec.describe Gitlab::Template::Finders::GlobalTemplateFinder do
let(:base_dir) { Dir.mktmpdir }
def create_template!(name_with_category)
diff --git a/spec/lib/gitlab/template/finders/repo_template_finders_spec.rb b/spec/lib/gitlab/template/finders/repo_template_finders_spec.rb
index cd5cde76b73..05f351be702 100644
--- a/spec/lib/gitlab/template/finders/repo_template_finders_spec.rb
+++ b/spec/lib/gitlab/template/finders/repo_template_finders_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Template::Finders::RepoTemplateFinder do
+RSpec.describe Gitlab::Template::Finders::RepoTemplateFinder do
let_it_be(:project) { create(:project, :repository) }
let(:categories) { { 'HTML' => 'html' } }
diff --git a/spec/lib/gitlab/template/gitignore_template_spec.rb b/spec/lib/gitlab/template/gitignore_template_spec.rb
index e8f632889ad..365b579d08e 100644
--- a/spec/lib/gitlab/template/gitignore_template_spec.rb
+++ b/spec/lib/gitlab/template/gitignore_template_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Template::GitignoreTemplate do
+RSpec.describe Gitlab::Template::GitignoreTemplate do
subject { described_class }
describe '.all' do
diff --git a/spec/lib/gitlab/template/gitlab_ci_yml_template_spec.rb b/spec/lib/gitlab/template/gitlab_ci_yml_template_spec.rb
index 52e100768a7..3004de6fe08 100644
--- a/spec/lib/gitlab/template/gitlab_ci_yml_template_spec.rb
+++ b/spec/lib/gitlab/template/gitlab_ci_yml_template_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Template::GitlabCiYmlTemplate do
+RSpec.describe Gitlab::Template::GitlabCiYmlTemplate do
subject { described_class }
describe '.all' do
diff --git a/spec/lib/gitlab/template/issue_template_spec.rb b/spec/lib/gitlab/template/issue_template_spec.rb
index 54e46d3a9ec..4acbdcc4ba6 100644
--- a/spec/lib/gitlab/template/issue_template_spec.rb
+++ b/spec/lib/gitlab/template/issue_template_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Template::IssueTemplate do
+RSpec.describe Gitlab::Template::IssueTemplate do
let(:project) { create(:project, :repository, create_templates: :issue) }
describe '.all' do
diff --git a/spec/lib/gitlab/template/merge_request_template_spec.rb b/spec/lib/gitlab/template/merge_request_template_spec.rb
index bbc184d4dfc..abac0a10e18 100644
--- a/spec/lib/gitlab/template/merge_request_template_spec.rb
+++ b/spec/lib/gitlab/template/merge_request_template_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Template::MergeRequestTemplate do
+RSpec.describe Gitlab::Template::MergeRequestTemplate do
let(:project) { create(:project, :repository, create_templates: :merge_request) }
describe '.all' do
diff --git a/spec/lib/gitlab/themes_spec.rb b/spec/lib/gitlab/themes_spec.rb
index e0278eb9c7f..68ff28becfa 100644
--- a/spec/lib/gitlab/themes_spec.rb
+++ b/spec/lib/gitlab/themes_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Themes, lib: true do
+RSpec.describe Gitlab::Themes, lib: true do
describe '.body_classes' do
it 'returns a space-separated list of class names' do
css = described_class.body_classes
diff --git a/spec/lib/gitlab/throttle_spec.rb b/spec/lib/gitlab/throttle_spec.rb
index e3679a1a721..ca2abe94ad2 100644
--- a/spec/lib/gitlab/throttle_spec.rb
+++ b/spec/lib/gitlab/throttle_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Throttle do
+RSpec.describe Gitlab::Throttle do
describe '.protected_paths_enabled?' do
subject { described_class.protected_paths_enabled? }
diff --git a/spec/lib/gitlab/time_tracking_formatter_spec.rb b/spec/lib/gitlab/time_tracking_formatter_spec.rb
index cfc804c13a7..8bbd1263057 100644
--- a/spec/lib/gitlab/time_tracking_formatter_spec.rb
+++ b/spec/lib/gitlab/time_tracking_formatter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::TimeTrackingFormatter do
+RSpec.describe Gitlab::TimeTrackingFormatter do
describe '#parse' do
subject { described_class.parse(duration_string) }
diff --git a/spec/lib/gitlab/tracking/incident_management_spec.rb b/spec/lib/gitlab/tracking/incident_management_spec.rb
index 6f7e04b7c16..e8131b4eeee 100644
--- a/spec/lib/gitlab/tracking/incident_management_spec.rb
+++ b/spec/lib/gitlab/tracking/incident_management_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Tracking::IncidentManagement do
+RSpec.describe Gitlab::Tracking::IncidentManagement do
describe '.track_from_params' do
shared_examples 'a tracked event' do |label, value = nil|
it 'creates the tracking event with the correct details' do
diff --git a/spec/lib/gitlab/tracking_spec.rb b/spec/lib/gitlab/tracking_spec.rb
index 82828c2dcce..65b6d9c8899 100644
--- a/spec/lib/gitlab/tracking_spec.rb
+++ b/spec/lib/gitlab/tracking_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe Gitlab::Tracking do
+RSpec.describe Gitlab::Tracking do
let(:timestamp) { Time.utc(2017, 3, 22) }
before do
diff --git a/spec/lib/gitlab/tree_summary_spec.rb b/spec/lib/gitlab/tree_summary_spec.rb
index 593b8655e80..4bd08fab60a 100644
--- a/spec/lib/gitlab/tree_summary_spec.rb
+++ b/spec/lib/gitlab/tree_summary_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::TreeSummary do
+RSpec.describe Gitlab::TreeSummary do
using RSpec::Parameterized::TableSyntax
let(:project) { create(:project, :empty_repo) }
@@ -47,15 +47,17 @@ describe Gitlab::TreeSummary do
end
describe '#summarize (entries)' do
- let(:limit) { 2 }
+ let(:limit) { 4 }
custom_files = {
'a.txt' => '',
'b.txt' => '',
- 'directory/c.txt' => ''
+ 'directory/c.txt' => '',
+ ':dir/test.txt' => '',
+ ':file' => ''
}
- let(:project) { create(:project, :custom_repo, files: custom_files) }
+ let!(:project) { create(:project, :custom_repo, files: custom_files) }
let(:commit) { repo.head_commit }
subject(:entries) { summary.summarize.first }
@@ -63,13 +65,16 @@ describe Gitlab::TreeSummary do
it 'summarizes the entries within the window' do
is_expected.to contain_exactly(
a_hash_including(type: :tree, file_name: 'directory'),
- a_hash_including(type: :blob, file_name: 'a.txt')
+ a_hash_including(type: :blob, file_name: 'a.txt'),
+ a_hash_including(type: :blob, file_name: ':file'),
+ a_hash_including(type: :tree, file_name: ':dir')
# b.txt is excluded by the limit
)
end
it 'references the commit and commit path in entries' do
- entry = entries.first
+ # There are 2 trees and the summary is not ordered
+ entry = entries.find { |entry| entry[:commit].id == commit.id }
expected_commit_path = Gitlab::Routing.url_helpers.project_commit_path(project, commit)
expect(entry[:commit]).to be_a(::Commit)
@@ -85,6 +90,14 @@ describe Gitlab::TreeSummary do
end
end
+ context 'in a subdirectory with a pathspec character' do
+ let(:path) { ':dir' }
+
+ it 'summarizes the entries in the subdirectory' do
+ is_expected.to contain_exactly(a_hash_including(type: :blob, file_name: 'test.txt'))
+ end
+ end
+
context 'in a non-existent subdirectory' do
let(:path) { 'tmp' }
@@ -92,7 +105,7 @@ describe Gitlab::TreeSummary do
end
context 'custom offset and limit' do
- let(:offset) { 2 }
+ let(:offset) { 4 }
it 'returns entries from the offset' do
is_expected.to contain_exactly(a_hash_including(type: :blob, file_name: 'b.txt'))
diff --git a/spec/lib/gitlab/untrusted_regexp/ruby_syntax_spec.rb b/spec/lib/gitlab/untrusted_regexp/ruby_syntax_spec.rb
index cdd681a9345..42fc84cf076 100644
--- a/spec/lib/gitlab/untrusted_regexp/ruby_syntax_spec.rb
+++ b/spec/lib/gitlab/untrusted_regexp/ruby_syntax_spec.rb
@@ -4,7 +4,7 @@ require 'fast_spec_helper'
require 'support/shared_examples/lib/gitlab/malicious_regexp_shared_examples'
require 'support/helpers/stub_feature_flags'
-describe Gitlab::UntrustedRegexp::RubySyntax do
+RSpec.describe Gitlab::UntrustedRegexp::RubySyntax do
describe '.matches_syntax?' do
it 'returns true if regexp is valid' do
expect(described_class.matches_syntax?('/some .* thing/'))
diff --git a/spec/lib/gitlab/untrusted_regexp_spec.rb b/spec/lib/gitlab/untrusted_regexp_spec.rb
index 60f14d0277b..aac3d5e27f5 100644
--- a/spec/lib/gitlab/untrusted_regexp_spec.rb
+++ b/spec/lib/gitlab/untrusted_regexp_spec.rb
@@ -3,7 +3,7 @@
require 'fast_spec_helper'
require 'support/shared_examples/lib/gitlab/malicious_regexp_shared_examples'
-describe Gitlab::UntrustedRegexp do
+RSpec.describe Gitlab::UntrustedRegexp do
describe '#initialize' do
subject { described_class.new(pattern) }
diff --git a/spec/lib/gitlab/updated_notes_paginator_spec.rb b/spec/lib/gitlab/updated_notes_paginator_spec.rb
new file mode 100644
index 00000000000..eedc11777d4
--- /dev/null
+++ b/spec/lib/gitlab/updated_notes_paginator_spec.rb
@@ -0,0 +1,57 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::UpdatedNotesPaginator do
+ let(:issue) { create(:issue) }
+
+ let(:project) { issue.project }
+ let(:finder) { NotesFinder.new(user, target: issue, last_fetched_at: last_fetched_at) }
+ let(:user) { issue.author }
+
+ let!(:page_1) { create_list(:note, 2, noteable: issue, project: project, updated_at: 2.days.ago) }
+ let!(:page_2) { [create(:note, noteable: issue, project: project, updated_at: 1.day.ago)] }
+
+ let(:page_1_boundary) { page_1.last.updated_at + NotesFinder::FETCH_OVERLAP }
+
+ around do |example|
+ Timecop.freeze do
+ example.run
+ end
+ end
+
+ before do
+ stub_const("Gitlab::UpdatedNotesPaginator::LIMIT", 2)
+ end
+
+ subject(:paginator) { described_class.new(finder.execute, last_fetched_at: last_fetched_at) }
+
+ describe 'last_fetched_at: start of time' do
+ let(:last_fetched_at) { Time.at(0) }
+
+ it 'calculates the first page of notes', :aggregate_failures do
+ expect(paginator.notes).to match_array(page_1)
+ expect(paginator.metadata).to match(
+ more: true,
+ last_fetched_at: microseconds(page_1_boundary)
+ )
+ end
+ end
+
+ describe 'last_fetched_at: start of final page' do
+ let(:last_fetched_at) { page_1_boundary }
+
+ it 'calculates a final page', :aggregate_failures do
+ expect(paginator.notes).to match_array(page_2)
+ expect(paginator.metadata).to match(
+ more: false,
+ last_fetched_at: microseconds(Time.zone.now)
+ )
+ end
+ end
+
+ # Convert a time to an integer number of microseconds
+ def microseconds(time)
+ (time.to_i * 1_000_000) + time.usec
+ end
+end
diff --git a/spec/lib/gitlab/uploads_transfer_spec.rb b/spec/lib/gitlab/uploads_transfer_spec.rb
index 16560fc8f12..80bbf68e98a 100644
--- a/spec/lib/gitlab/uploads_transfer_spec.rb
+++ b/spec/lib/gitlab/uploads_transfer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::UploadsTransfer do
+RSpec.describe Gitlab::UploadsTransfer do
it 'leaves avatar uploads where they are' do
project_with_avatar = create(:project, :with_avatar)
diff --git a/spec/lib/gitlab/url_blocker_spec.rb b/spec/lib/gitlab/url_blocker_spec.rb
index 08678de87c9..7edfde09864 100644
--- a/spec/lib/gitlab/url_blocker_spec.rb
+++ b/spec/lib/gitlab/url_blocker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::UrlBlocker, :stub_invalid_dns_only do
+RSpec.describe Gitlab::UrlBlocker, :stub_invalid_dns_only do
include StubRequests
describe '#validate!' do
diff --git a/spec/lib/gitlab/url_blockers/domain_whitelist_entry_spec.rb b/spec/lib/gitlab/url_blockers/domain_whitelist_entry_spec.rb
index 34ea6c328e6..58bae109146 100644
--- a/spec/lib/gitlab/url_blockers/domain_whitelist_entry_spec.rb
+++ b/spec/lib/gitlab/url_blockers/domain_whitelist_entry_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::UrlBlockers::DomainWhitelistEntry do
+RSpec.describe Gitlab::UrlBlockers::DomainWhitelistEntry do
let(:domain) { 'www.example.com' }
describe '#initialize' do
diff --git a/spec/lib/gitlab/url_blockers/ip_whitelist_entry_spec.rb b/spec/lib/gitlab/url_blockers/ip_whitelist_entry_spec.rb
index 042d135d265..52f9b31165a 100644
--- a/spec/lib/gitlab/url_blockers/ip_whitelist_entry_spec.rb
+++ b/spec/lib/gitlab/url_blockers/ip_whitelist_entry_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::UrlBlockers::IpWhitelistEntry do
+RSpec.describe Gitlab::UrlBlockers::IpWhitelistEntry do
let(:ipv4) { IPAddr.new('192.168.1.1') }
describe '#initialize' do
diff --git a/spec/lib/gitlab/url_blockers/url_whitelist_spec.rb b/spec/lib/gitlab/url_blockers/url_whitelist_spec.rb
index e43cd819838..7a65516be3c 100644
--- a/spec/lib/gitlab/url_blockers/url_whitelist_spec.rb
+++ b/spec/lib/gitlab/url_blockers/url_whitelist_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::UrlBlockers::UrlWhitelist do
+RSpec.describe Gitlab::UrlBlockers::UrlWhitelist do
include StubRequests
let(:whitelist) { [] }
diff --git a/spec/lib/gitlab/url_builder_spec.rb b/spec/lib/gitlab/url_builder_spec.rb
index e91d17bfbe8..a16ff252bc1 100644
--- a/spec/lib/gitlab/url_builder_spec.rb
+++ b/spec/lib/gitlab/url_builder_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::UrlBuilder do
+RSpec.describe Gitlab::UrlBuilder do
subject { described_class }
describe '#build' do
@@ -87,12 +87,41 @@ describe Gitlab::UrlBuilder do
end
context 'when passing a Snippet' do
- let(:snippet) { build_stubbed(:personal_snippet) }
+ let_it_be(:personal_snippet) { create(:personal_snippet, :repository) }
+ let_it_be(:project_snippet) { create(:project_snippet, :repository) }
+ let(:blob) { snippet.blobs.first }
+ let(:ref) { blob.repository.root_ref }
- it 'returns a raw snippet URL if requested' do
- url = subject.build(snippet, raw: true)
+ context 'for a PersonalSnippet' do
+ let(:snippet) { personal_snippet }
- expect(url).to eq "#{Gitlab.config.gitlab.url}/snippets/#{snippet.id}/raw"
+ it 'returns a raw snippet URL if requested' do
+ url = subject.build(snippet, raw: true)
+
+ expect(url).to eq "#{Gitlab.config.gitlab.url}/snippets/#{snippet.id}/raw"
+ end
+
+ it 'returns a raw snippet blob URL if requested' do
+ url = subject.build(snippet, file: blob.path, ref: ref)
+
+ expect(url).to eq "#{Gitlab.config.gitlab.url}/-/snippets/#{snippet.id}/raw/#{ref}/#{blob.path}"
+ end
+ end
+
+ context 'for a ProjectSnippet' do
+ let(:snippet) { project_snippet }
+
+ it 'returns a raw snippet URL if requested' do
+ url = subject.build(snippet, raw: true)
+
+ expect(url).to eq "#{Gitlab.config.gitlab.url}/#{snippet.project.full_path}/snippets/#{snippet.id}/raw"
+ end
+
+ it 'returns a raw snippet blob URL if requested' do
+ url = subject.build(snippet, file: blob.path, ref: ref)
+
+ expect(url).to eq "#{Gitlab.config.gitlab.url}/#{snippet.project.full_path}/-/snippets/#{snippet.id}/raw/#{ref}/#{blob.path}"
+ end
end
end
diff --git a/spec/lib/gitlab/url_sanitizer_spec.rb b/spec/lib/gitlab/url_sanitizer_spec.rb
index caca22eb98b..aba4ca109a9 100644
--- a/spec/lib/gitlab/url_sanitizer_spec.rb
+++ b/spec/lib/gitlab/url_sanitizer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::UrlSanitizer do
+RSpec.describe Gitlab::UrlSanitizer do
using RSpec::Parameterized::TableSyntax
describe '.sanitize' do
diff --git a/spec/lib/gitlab/usage_data/topology_spec.rb b/spec/lib/gitlab/usage_data/topology_spec.rb
new file mode 100644
index 00000000000..2a7adea261d
--- /dev/null
+++ b/spec/lib/gitlab/usage_data/topology_spec.rb
@@ -0,0 +1,562 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::UsageData::Topology do
+ include UsageDataHelpers
+
+ describe '#topology_usage_data' do
+ subject { described_class.new.topology_usage_data }
+
+ before do
+ # this pins down time shifts when benchmarking durations
+ allow(Process).to receive(:clock_gettime).and_return(0)
+ end
+
+ context 'when embedded Prometheus server is enabled' do
+ before do
+ expect(Gitlab::Prometheus::Internal).to receive(:prometheus_enabled?).and_return(true)
+ expect(Gitlab::Prometheus::Internal).to receive(:uri).and_return('http://prom:9090')
+ end
+
+ context 'tracking node metrics' do
+ it 'contains node level metrics for each instance' do
+ expect_prometheus_api_to(
+ receive_app_request_volume_query,
+ receive_node_memory_query,
+ receive_node_cpu_count_query,
+ receive_node_uname_info_query,
+ receive_node_service_memory_rss_query,
+ receive_node_service_memory_uss_query,
+ receive_node_service_memory_pss_query,
+ receive_node_service_process_count_query,
+ receive_node_service_app_server_workers_query
+ )
+
+ expect(subject[:topology]).to eq({
+ duration_s: 0,
+ application_requests_per_hour: 36,
+ failures: [],
+ nodes: [
+ {
+ node_memory_total_bytes: 512,
+ node_cpus: 8,
+ node_uname_info: {
+ machine: 'x86_64',
+ sysname: 'Linux',
+ release: '4.19.76-linuxkit'
+ },
+ node_services: [
+ {
+ name: 'web',
+ process_count: 10,
+ process_memory_rss: 300,
+ process_memory_uss: 301,
+ process_memory_pss: 302,
+ server: 'puma'
+ },
+ {
+ name: 'sidekiq',
+ process_count: 5,
+ process_memory_rss: 303
+ }
+ ]
+ },
+ {
+ node_memory_total_bytes: 1024,
+ node_cpus: 16,
+ node_uname_info: {
+ machine: 'x86_64',
+ sysname: 'Linux',
+ release: '4.15.0-101-generic'
+ },
+ node_services: [
+ {
+ name: 'sidekiq',
+ process_count: 15,
+ process_memory_rss: 400,
+ process_memory_pss: 401
+ },
+ {
+ name: 'redis',
+ process_count: 1,
+ process_memory_rss: 402
+ },
+ {
+ name: 'registry',
+ process_count: 1
+ },
+ {
+ name: 'web',
+ server: 'unicorn'
+ }
+ ]
+ }
+ ]
+ })
+ end
+ end
+
+ context 'and some node memory metrics are missing' do
+ it 'removes the respective entries and includes the failures' do
+ expect_prometheus_api_to(
+ receive_app_request_volume_query(result: []),
+ receive_node_memory_query(result: []),
+ receive_node_cpu_count_query,
+ receive_node_uname_info_query,
+ receive_node_service_memory_rss_query(result: []),
+ receive_node_service_memory_uss_query(result: []),
+ receive_node_service_memory_pss_query,
+ receive_node_service_process_count_query,
+ receive_node_service_app_server_workers_query(result: [])
+ )
+
+ expect(subject[:topology]).to eq({
+ duration_s: 0,
+ failures: [
+ { 'app_requests' => 'empty_result' },
+ { 'node_memory' => 'empty_result' },
+ { 'service_rss' => 'empty_result' },
+ { 'service_uss' => 'empty_result' },
+ { 'service_workers' => 'empty_result' }
+ ],
+ nodes: [
+ {
+ node_cpus: 16,
+ node_uname_info: {
+ machine: 'x86_64',
+ release: '4.15.0-101-generic',
+ sysname: 'Linux'
+ },
+ node_services: [
+ {
+ name: 'sidekiq',
+ process_count: 15,
+ process_memory_pss: 401
+ },
+ {
+ name: 'redis',
+ process_count: 1
+ },
+ {
+ name: 'registry',
+ process_count: 1
+ }
+ ]
+ },
+ {
+ node_cpus: 8,
+ node_uname_info: {
+ machine: 'x86_64',
+ release: '4.19.76-linuxkit',
+ sysname: 'Linux'
+ },
+ node_services: [
+ {
+ name: 'web',
+ process_count: 10,
+ process_memory_pss: 302
+ },
+ {
+ name: 'sidekiq',
+ process_count: 5
+ }
+ ]
+ }
+ ]
+ })
+ end
+ end
+
+ context 'and services run on the same node but report different instance values' do
+ let(:node_memory_response) do
+ [
+ {
+ 'metric' => { 'instance' => 'localhost:9100' },
+ 'value' => [1000, '512']
+ }
+ ]
+ end
+ let(:node_uname_info_response) do
+ [
+ {
+ "metric" => {
+ "__name__" => "node_uname_info",
+ "domainname" => "(none)",
+ "instance" => "127.0.0.1:9100",
+ "job" => "node_exporter",
+ "machine" => "x86_64",
+ "nodename" => "127.0.0.1",
+ "release" => "4.19.76-linuxkit",
+ "sysname" => "Linux"
+ },
+ "value" => [1592463033.359, "1"]
+ }
+ ]
+ end
+ # The services in this response should all be mapped to localhost i.e. the same node
+ let(:service_memory_response) do
+ [
+ {
+ 'metric' => { 'instance' => 'localhost:8080', 'job' => 'gitlab-rails' },
+ 'value' => [1000, '10']
+ },
+ {
+ 'metric' => { 'instance' => '127.0.0.1:8090', 'job' => 'gitlab-sidekiq' },
+ 'value' => [1000, '11']
+ },
+ {
+ 'metric' => { 'instance' => '0.0.0.0:9090', 'job' => 'prometheus' },
+ 'value' => [1000, '12']
+ },
+ {
+ 'metric' => { 'instance' => '[::1]:1234', 'job' => 'redis' },
+ 'value' => [1000, '13']
+ },
+ {
+ 'metric' => { 'instance' => '[::]:1234', 'job' => 'postgres' },
+ 'value' => [1000, '14']
+ }
+ ]
+ end
+
+ it 'normalizes equivalent instance values and maps them to the same node' do
+ expect_prometheus_api_to(
+ receive_app_request_volume_query(result: []),
+ receive_node_memory_query(result: node_memory_response),
+ receive_node_cpu_count_query(result: []),
+ receive_node_uname_info_query(result: node_uname_info_response),
+ receive_node_service_memory_rss_query(result: service_memory_response),
+ receive_node_service_memory_uss_query(result: []),
+ receive_node_service_memory_pss_query(result: []),
+ receive_node_service_process_count_query(result: []),
+ receive_node_service_app_server_workers_query(result: [])
+ )
+
+ expect(subject[:topology]).to eq({
+ duration_s: 0,
+ failures: [
+ { 'app_requests' => 'empty_result' },
+ { 'node_cpus' => 'empty_result' },
+ { 'service_uss' => 'empty_result' },
+ { 'service_pss' => 'empty_result' },
+ { 'service_process_count' => 'empty_result' },
+ { 'service_workers' => 'empty_result' }
+ ],
+ nodes: [
+ {
+ node_memory_total_bytes: 512,
+ node_uname_info: {
+ machine: 'x86_64',
+ sysname: 'Linux',
+ release: '4.19.76-linuxkit'
+ },
+ node_services: [
+ {
+ name: 'web',
+ process_memory_rss: 10
+ },
+ {
+ name: 'sidekiq',
+ process_memory_rss: 11
+ },
+ {
+ name: 'prometheus',
+ process_memory_rss: 12
+ },
+ {
+ name: 'redis',
+ process_memory_rss: 13
+ },
+ {
+ name: 'postgres',
+ process_memory_rss: 14
+ }
+ ]
+ }
+ ]
+ })
+ end
+ end
+
+ context 'and node metrics are missing but service metrics exist' do
+ it 'still reports service metrics' do
+ expect_prometheus_api_to(
+ receive_app_request_volume_query(result: []),
+ receive_node_memory_query(result: []),
+ receive_node_cpu_count_query(result: []),
+ receive_node_uname_info_query(result: []),
+ receive_node_service_memory_rss_query,
+ receive_node_service_memory_uss_query(result: []),
+ receive_node_service_memory_pss_query(result: []),
+ receive_node_service_process_count_query(result: []),
+ receive_node_service_app_server_workers_query(result: [])
+ )
+
+ expect(subject[:topology]).to eq({
+ duration_s: 0,
+ failures: [
+ { 'app_requests' => 'empty_result' },
+ { 'node_memory' => 'empty_result' },
+ { 'node_cpus' => 'empty_result' },
+ { 'node_uname_info' => 'empty_result' },
+ { 'service_uss' => 'empty_result' },
+ { 'service_pss' => 'empty_result' },
+ { 'service_process_count' => 'empty_result' },
+ { 'service_workers' => 'empty_result' }
+ ],
+ nodes: [
+ {
+ node_services: [
+ {
+ name: 'web',
+ process_memory_rss: 300
+ },
+ {
+ name: 'sidekiq',
+ process_memory_rss: 303
+ }
+ ]
+ },
+ {
+ node_services: [
+ {
+ name: 'sidekiq',
+ process_memory_rss: 400
+ },
+ {
+ name: 'redis',
+ process_memory_rss: 402
+ }
+ ]
+ }
+ ]
+ })
+ end
+ end
+
+ context 'and an error is raised when querying Prometheus' do
+ it 'returns empty result with failures' do
+ expect_prometheus_api_to receive(:query)
+ .at_least(:once)
+ .and_raise(Gitlab::PrometheusClient::ConnectionError)
+
+ expect(subject[:topology]).to eq({
+ duration_s: 0,
+ failures: [
+ { 'app_requests' => 'Gitlab::PrometheusClient::ConnectionError' },
+ { 'node_memory' => 'Gitlab::PrometheusClient::ConnectionError' },
+ { 'node_cpus' => 'Gitlab::PrometheusClient::ConnectionError' },
+ { 'node_uname_info' => 'Gitlab::PrometheusClient::ConnectionError' },
+ { 'service_rss' => 'Gitlab::PrometheusClient::ConnectionError' },
+ { 'service_uss' => 'Gitlab::PrometheusClient::ConnectionError' },
+ { 'service_pss' => 'Gitlab::PrometheusClient::ConnectionError' },
+ { 'service_process_count' => 'Gitlab::PrometheusClient::ConnectionError' },
+ { 'service_workers' => 'Gitlab::PrometheusClient::ConnectionError' }
+ ],
+ nodes: []
+ })
+ end
+ end
+ end
+
+ context 'when embedded Prometheus server is disabled' do
+ it 'returns empty result with no failures' do
+ expect(Gitlab::Prometheus::Internal).to receive(:prometheus_enabled?).and_return(false)
+
+ expect(subject[:topology]).to eq({
+ duration_s: 0,
+ failures: []
+ })
+ end
+ end
+
+ context 'when top-level function raises error' do
+ it 'returns empty result with generic failure' do
+ allow(Gitlab::Prometheus::Internal).to receive(:prometheus_enabled?).and_raise(RuntimeError)
+
+ expect(subject[:topology]).to eq({
+ duration_s: 0,
+ failures: [
+ { 'other' => 'RuntimeError' }
+ ]
+ })
+ end
+ end
+ end
+
+ def receive_app_request_volume_query(result: nil)
+ receive(:query)
+ .with(/gitlab_usage_ping:ops:rate/)
+ .and_return(result || [
+ {
+ 'metric' => { 'component' => 'http_requests', 'service' => 'workhorse' },
+ 'value' => [1000, '0.01']
+ }
+ ])
+ end
+
+ def receive_node_memory_query(result: nil)
+ receive(:query)
+ .with(/node_memory_total_bytes/, an_instance_of(Hash))
+ .and_return(result || [
+ {
+ 'metric' => { 'instance' => 'instance1:8080' },
+ 'value' => [1000, '512']
+ },
+ {
+ 'metric' => { 'instance' => 'instance2:8090' },
+ 'value' => [1000, '1024']
+ }
+ ])
+ end
+
+ def receive_node_cpu_count_query(result: nil)
+ receive(:query)
+ .with(/node_cpus/, an_instance_of(Hash))
+ .and_return(result || [
+ {
+ 'metric' => { 'instance' => 'instance2:8090' },
+ 'value' => [1000, '16']
+ },
+ {
+ 'metric' => { 'instance' => 'instance1:8080' },
+ 'value' => [1000, '8']
+ }
+ ])
+ end
+
+ def receive_node_uname_info_query(result: nil)
+ receive(:query)
+ .with('node_uname_info')
+ .and_return(result || [
+ {
+ "metric" => {
+ "__name__" => "node_uname_info",
+ "domainname" => "(none)",
+ "instance" => "instance1:9100",
+ "job" => "node_exporter",
+ "machine" => "x86_64",
+ "nodename" => "instance1",
+ "release" => "4.19.76-linuxkit",
+ "sysname" => "Linux"
+ },
+ "value" => [1592463033.359, "1"]
+ },
+ {
+ "metric" => {
+ "__name__" => "node_uname_info",
+ "domainname" => "(none)",
+ "instance" => "instance2:9100",
+ "job" => "node_exporter",
+ "machine" => "x86_64",
+ "nodename" => "instance2",
+ "release" => "4.15.0-101-generic",
+ "sysname" => "Linux"
+ },
+ "value" => [1592463033.359, "1"]
+ }
+ ])
+ end
+
+ def receive_node_service_memory_rss_query(result: nil)
+ receive(:query)
+ .with(/process_resident_memory_bytes/, an_instance_of(Hash))
+ .and_return(result || [
+ {
+ 'metric' => { 'instance' => 'instance1:8080', 'job' => 'gitlab-rails' },
+ 'value' => [1000, '300']
+ },
+ {
+ 'metric' => { 'instance' => 'instance1:8090', 'job' => 'gitlab-sidekiq' },
+ 'value' => [1000, '303']
+ },
+ # instance 2: runs a dedicated Sidekiq + Redis (which uses a different metric name)
+ {
+ 'metric' => { 'instance' => 'instance2:8090', 'job' => 'gitlab-sidekiq' },
+ 'value' => [1000, '400']
+ },
+ {
+ 'metric' => { 'instance' => 'instance2:9121', 'job' => 'redis' },
+ 'value' => [1000, '402']
+ }
+ ])
+ end
+
+ def receive_node_service_memory_uss_query(result: nil)
+ receive(:query)
+ .with(/process_unique_memory_bytes/, an_instance_of(Hash))
+ .and_return(result || [
+ {
+ 'metric' => { 'instance' => 'instance1:8080', 'job' => 'gitlab-rails' },
+ 'value' => [1000, '301']
+ }
+ ])
+ end
+
+ def receive_node_service_memory_pss_query(result: nil)
+ receive(:query)
+ .with(/process_proportional_memory_bytes/, an_instance_of(Hash))
+ .and_return(result || [
+ {
+ 'metric' => { 'instance' => 'instance1:8080', 'job' => 'gitlab-rails' },
+ 'value' => [1000, '302']
+ },
+ {
+ 'metric' => { 'instance' => 'instance2:8090', 'job' => 'gitlab-sidekiq' },
+ 'value' => [1000, '401']
+ }
+ ])
+ end
+
+ def receive_node_service_process_count_query(result: nil)
+ receive(:query)
+ .with(/service_process:count/, an_instance_of(Hash))
+ .and_return(result || [
+ # instance 1
+ {
+ 'metric' => { 'instance' => 'instance1:8080', 'job' => 'gitlab-rails' },
+ 'value' => [1000, '10']
+ },
+ {
+ 'metric' => { 'instance' => 'instance1:8090', 'job' => 'gitlab-sidekiq' },
+ 'value' => [1000, '5']
+ },
+ # instance 2
+ {
+ 'metric' => { 'instance' => 'instance2:8090', 'job' => 'gitlab-sidekiq' },
+ 'value' => [1000, '15']
+ },
+ {
+ 'metric' => { 'instance' => 'instance2:9121', 'job' => 'redis' },
+ 'value' => [1000, '1']
+ },
+ {
+ 'metric' => { 'instance' => 'instance2:8080', 'job' => 'registry' },
+ 'value' => [1000, '1']
+ },
+ # unknown service => should be stripped out
+ {
+ 'metric' => { 'instance' => 'instance2:9000', 'job' => 'not-a-gitlab-service' },
+ 'value' => [1000, '42']
+ }
+ ])
+ end
+
+ def receive_node_service_app_server_workers_query(result: nil)
+ receive(:query)
+ .with(/app_server_workers/, an_instance_of(Hash))
+ .and_return(result || [
+ # instance 1
+ {
+ 'metric' => { 'instance' => 'instance1:8080', 'job' => 'gitlab-rails', 'server' => 'puma' },
+ 'value' => [1000, '2']
+ },
+ # instance 2
+ {
+ 'metric' => { 'instance' => 'instance2:8080', 'job' => 'gitlab-rails', 'server' => 'unicorn' },
+ 'value' => [1000, '1']
+ }
+ ])
+ end
+end
diff --git a/spec/lib/gitlab/usage_data_concerns/topology_spec.rb b/spec/lib/gitlab/usage_data_concerns/topology_spec.rb
deleted file mode 100644
index 0428900690c..00000000000
--- a/spec/lib/gitlab/usage_data_concerns/topology_spec.rb
+++ /dev/null
@@ -1,220 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-describe Gitlab::UsageDataConcerns::Topology do
- include UsageDataHelpers
-
- describe '#topology_usage_data' do
- subject { Class.new.extend(described_class).topology_usage_data }
-
- before do
- # this pins down time shifts when benchmarking durations
- allow(Process).to receive(:clock_gettime).and_return(0)
- end
-
- context 'when embedded Prometheus server is enabled' do
- before do
- expect(Gitlab::Prometheus::Internal).to receive(:prometheus_enabled?).and_return(true)
- expect(Gitlab::Prometheus::Internal).to receive(:uri).and_return('http://prom:9090')
- end
-
- it 'contains a topology element' do
- allow_prometheus_queries
-
- expect(subject).to have_key(:topology)
- end
-
- context 'tracking node metrics' do
- it 'contains node level metrics for each instance' do
- expect_prometheus_api_to(
- receive_node_memory_query,
- receive_node_cpu_count_query,
- receive_node_service_memory_query,
- receive_node_service_process_count_query
- )
-
- expect(subject[:topology]).to eq({
- duration_s: 0,
- nodes: [
- {
- node_memory_total_bytes: 512,
- node_cpus: 8,
- node_services: [
- {
- name: 'web',
- process_count: 10,
- process_memory_rss: 300,
- process_memory_uss: 301,
- process_memory_pss: 302
- },
- {
- name: 'sidekiq',
- process_count: 5,
- process_memory_rss: 303
- }
- ]
- },
- {
- node_memory_total_bytes: 1024,
- node_cpus: 16,
- node_services: [
- {
- name: 'sidekiq',
- process_count: 15,
- process_memory_rss: 400,
- process_memory_pss: 401
- },
- {
- name: 'redis',
- process_count: 1,
- process_memory_rss: 402
- }
- ]
- }
- ]
- })
- end
- end
-
- context 'and some node memory metrics are missing' do
- it 'removes the respective entries' do
- expect_prometheus_api_to(
- receive_node_memory_query(result: []),
- receive_node_cpu_count_query,
- receive_node_service_memory_query,
- receive_node_service_process_count_query
- )
-
- keys = subject[:topology][:nodes].flat_map(&:keys)
- expect(keys).not_to include(:node_memory_total_bytes)
- expect(keys).to include(:node_cpus, :node_services)
- end
- end
-
- context 'and no results are found' do
- it 'does not report anything' do
- expect_prometheus_api_to receive(:aggregate).at_least(:once).and_return({})
-
- expect(subject[:topology]).to eq({
- duration_s: 0,
- nodes: []
- })
- end
- end
-
- context 'and a connection error is raised' do
- it 'does not report anything' do
- expect_prometheus_api_to receive(:aggregate).and_raise('Connection failed')
-
- expect(subject[:topology]).to eq({ duration_s: 0 })
- end
- end
- end
-
- context 'when embedded Prometheus server is disabled' do
- it 'does not report anything' do
- expect(Gitlab::Prometheus::Internal).to receive(:prometheus_enabled?).and_return(false)
-
- expect(subject[:topology]).to eq({ duration_s: 0 })
- end
- end
- end
-
- def receive_node_memory_query(result: nil)
- receive(:query)
- .with(/node_memory_MemTotal_bytes/, an_instance_of(Hash))
- .and_return(result || [
- {
- 'metric' => { 'instance' => 'instance1:8080' },
- 'value' => [1000, '512']
- },
- {
- 'metric' => { 'instance' => 'instance2:8090' },
- 'value' => [1000, '1024']
- }
- ])
- end
-
- def receive_node_cpu_count_query(result: nil)
- receive(:query)
- .with(/node_cpu_seconds_total/, an_instance_of(Hash))
- .and_return(result || [
- {
- 'metric' => { 'instance' => 'instance2:8090' },
- 'value' => [1000, '16']
- },
- {
- 'metric' => { 'instance' => 'instance1:8080' },
- 'value' => [1000, '8']
- }
- ])
- end
-
- def receive_node_service_memory_query(result: nil)
- receive(:query)
- .with(/process_.+_memory_bytes/, an_instance_of(Hash))
- .and_return(result || [
- # instance 1: runs Puma + a small Sidekiq
- {
- 'metric' => { 'instance' => 'instance1:8080', 'job' => 'gitlab-rails', '__name__' => 'ruby_process_resident_memory_bytes' },
- 'value' => [1000, '300']
- },
- {
- 'metric' => { 'instance' => 'instance1:8080', 'job' => 'gitlab-rails', '__name__' => 'ruby_process_unique_memory_bytes' },
- 'value' => [1000, '301']
- },
- {
- 'metric' => { 'instance' => 'instance1:8080', 'job' => 'gitlab-rails', '__name__' => 'ruby_process_proportional_memory_bytes' },
- 'value' => [1000, '302']
- },
- {
- 'metric' => { 'instance' => 'instance1:8090', 'job' => 'gitlab-sidekiq', '__name__' => 'ruby_process_resident_memory_bytes' },
- 'value' => [1000, '303']
- },
- # instance 2: runs a dedicated Sidekiq + Redis (which uses a different metric name)
- {
- 'metric' => { 'instance' => 'instance2:8090', 'job' => 'gitlab-sidekiq', '__name__' => 'ruby_process_resident_memory_bytes' },
- 'value' => [1000, '400']
- },
- {
- 'metric' => { 'instance' => 'instance2:8090', 'job' => 'gitlab-sidekiq', '__name__' => 'ruby_process_proportional_memory_bytes' },
- 'value' => [1000, '401']
- },
- {
- 'metric' => { 'instance' => 'instance2:9121', 'job' => 'redis', '__name__' => 'process_resident_memory_bytes' },
- 'value' => [1000, '402']
- }
- ])
- end
-
- def receive_node_service_process_count_query(result: nil)
- receive(:query)
- .with(/process_start_time_seconds/, an_instance_of(Hash))
- .and_return(result || [
- # instance 1
- {
- 'metric' => { 'instance' => 'instance1:8080', 'job' => 'gitlab-rails' },
- 'value' => [1000, '10']
- },
- {
- 'metric' => { 'instance' => 'instance1:8090', 'job' => 'gitlab-sidekiq' },
- 'value' => [1000, '5']
- },
- # instance 2
- {
- 'metric' => { 'instance' => 'instance2:8090', 'job' => 'gitlab-sidekiq' },
- 'value' => [1000, '15']
- },
- {
- 'metric' => { 'instance' => 'instance2:9121', 'job' => 'redis' },
- 'value' => [1000, '1']
- },
- # unknown service => should be stripped out
- {
- 'metric' => { 'instance' => 'instance2:9000', 'job' => 'not-a-gitlab-service' },
- 'value' => [1000, '42']
- }
- ])
- end
-end
diff --git a/spec/lib/gitlab/usage_data_counters/cycle_analytics_counter_spec.rb b/spec/lib/gitlab/usage_data_counters/cycle_analytics_counter_spec.rb
index 71be37692e2..18c21ef7eba 100644
--- a/spec/lib/gitlab/usage_data_counters/cycle_analytics_counter_spec.rb
+++ b/spec/lib/gitlab/usage_data_counters/cycle_analytics_counter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::UsageDataCounters::CycleAnalyticsCounter do
+RSpec.describe Gitlab::UsageDataCounters::CycleAnalyticsCounter do
it_behaves_like 'a redis usage counter', 'CycleAnalytics', :views
it_behaves_like 'a redis usage counter with totals', :cycle_analytics, views: 3
diff --git a/spec/lib/gitlab/usage_data_counters/designs_counter_spec.rb b/spec/lib/gitlab/usage_data_counters/designs_counter_spec.rb
index deaf7ebc7f3..10a09e737f9 100644
--- a/spec/lib/gitlab/usage_data_counters/designs_counter_spec.rb
+++ b/spec/lib/gitlab/usage_data_counters/designs_counter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::UsageDataCounters::DesignsCounter do
+RSpec.describe Gitlab::UsageDataCounters::DesignsCounter do
it_behaves_like 'a redis usage counter', 'Designs', :create
it_behaves_like 'a redis usage counter', 'Designs', :update
it_behaves_like 'a redis usage counter', 'Designs', :delete
diff --git a/spec/lib/gitlab/usage_data_counters/merge_request_counter_spec.rb b/spec/lib/gitlab/usage_data_counters/merge_request_counter_spec.rb
index 4be4a661260..d16c73e9312 100644
--- a/spec/lib/gitlab/usage_data_counters/merge_request_counter_spec.rb
+++ b/spec/lib/gitlab/usage_data_counters/merge_request_counter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::UsageDataCounters::MergeRequestCounter do
+RSpec.describe Gitlab::UsageDataCounters::MergeRequestCounter do
it_behaves_like 'a redis usage counter', 'Merge Request', :create
it_behaves_like 'a redis usage counter with totals', :merge_request, create: 5
diff --git a/spec/lib/gitlab/usage_data_counters/note_counter_spec.rb b/spec/lib/gitlab/usage_data_counters/note_counter_spec.rb
index b385d1b07c7..7e8f0172e06 100644
--- a/spec/lib/gitlab/usage_data_counters/note_counter_spec.rb
+++ b/spec/lib/gitlab/usage_data_counters/note_counter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::UsageDataCounters::NoteCounter, :clean_gitlab_redis_shared_state do
+RSpec.describe Gitlab::UsageDataCounters::NoteCounter, :clean_gitlab_redis_shared_state do
shared_examples 'a note usage counter' do |event, noteable_type|
describe ".count(#{event})" do
it "increments the Note #{event} counter by 1" do
diff --git a/spec/lib/gitlab/usage_data_counters/productivity_analytics_counter_spec.rb b/spec/lib/gitlab/usage_data_counters/productivity_analytics_counter_spec.rb
index f4e92791728..34b2cfcf1de 100644
--- a/spec/lib/gitlab/usage_data_counters/productivity_analytics_counter_spec.rb
+++ b/spec/lib/gitlab/usage_data_counters/productivity_analytics_counter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::UsageDataCounters::ProductivityAnalyticsCounter do
+RSpec.describe Gitlab::UsageDataCounters::ProductivityAnalyticsCounter do
it_behaves_like 'a redis usage counter', 'ProductivityAnalytics', :views
it_behaves_like 'a redis usage counter with totals', :productivity_analytics, views: 3
diff --git a/spec/lib/gitlab/usage_data_counters/redis_counter_spec.rb b/spec/lib/gitlab/usage_data_counters/redis_counter_spec.rb
index c34ac7867ab..be528b081c5 100644
--- a/spec/lib/gitlab/usage_data_counters/redis_counter_spec.rb
+++ b/spec/lib/gitlab/usage_data_counters/redis_counter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::UsageDataCounters::RedisCounter, :clean_gitlab_redis_shared_state do
+RSpec.describe Gitlab::UsageDataCounters::RedisCounter, :clean_gitlab_redis_shared_state do
let(:redis_key) { 'foobar' }
subject { Class.new.extend(described_class) }
diff --git a/spec/lib/gitlab/usage_data_counters/search_counter_spec.rb b/spec/lib/gitlab/usage_data_counters/search_counter_spec.rb
index 35b0f9a67f4..b55e20ba555 100644
--- a/spec/lib/gitlab/usage_data_counters/search_counter_spec.rb
+++ b/spec/lib/gitlab/usage_data_counters/search_counter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::UsageDataCounters::SearchCounter, :clean_gitlab_redis_shared_state do
+RSpec.describe Gitlab::UsageDataCounters::SearchCounter, :clean_gitlab_redis_shared_state do
shared_examples_for 'usage counter with totals' do |counter|
it 'increments counter and returns total count' do
expect(described_class.read(counter)).to eq(0)
diff --git a/spec/lib/gitlab/usage_data_counters/snippet_counter_spec.rb b/spec/lib/gitlab/usage_data_counters/snippet_counter_spec.rb
index 65381ed36d1..6109437da77 100644
--- a/spec/lib/gitlab/usage_data_counters/snippet_counter_spec.rb
+++ b/spec/lib/gitlab/usage_data_counters/snippet_counter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::UsageDataCounters::SnippetCounter do
+RSpec.describe Gitlab::UsageDataCounters::SnippetCounter do
it_behaves_like 'a redis usage counter', 'Snippet', :create
it_behaves_like 'a redis usage counter', 'Snippet', :update
diff --git a/spec/lib/gitlab/usage_data_counters/source_code_counter_spec.rb b/spec/lib/gitlab/usage_data_counters/source_code_counter_spec.rb
index 47077345e0c..73d5b4038f8 100644
--- a/spec/lib/gitlab/usage_data_counters/source_code_counter_spec.rb
+++ b/spec/lib/gitlab/usage_data_counters/source_code_counter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::UsageDataCounters::SourceCodeCounter do
+RSpec.describe Gitlab::UsageDataCounters::SourceCodeCounter do
it_behaves_like 'a redis usage counter', 'Source Code', :pushes
it_behaves_like 'a redis usage counter with totals', :source_code, pushes: 5
diff --git a/spec/lib/gitlab/usage_data_counters/track_unique_actions_spec.rb b/spec/lib/gitlab/usage_data_counters/track_unique_actions_spec.rb
new file mode 100644
index 00000000000..584d8407e79
--- /dev/null
+++ b/spec/lib/gitlab/usage_data_counters/track_unique_actions_spec.rb
@@ -0,0 +1,82 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::UsageDataCounters::TrackUniqueActions, :clean_gitlab_redis_shared_state do
+ subject(:track_unique_events) { described_class }
+
+ let(:time) { Time.zone.now }
+
+ def track_action(params)
+ track_unique_events.track_action(params)
+ end
+
+ def count_unique_events(params)
+ track_unique_events.count_unique_events(params)
+ end
+
+ context 'tracking an event' do
+ context 'when tracking successfully' do
+ context 'when the feature flag and the application setting is enabled' do
+ context 'when the target and the action is valid' do
+ before do
+ stub_feature_flags(described_class::FEATURE_FLAG => true)
+ stub_application_setting(usage_ping_enabled: true)
+ end
+
+ it 'tracks and counts the events as expected' do
+ project = Event::TARGET_TYPES[:project]
+ design = Event::TARGET_TYPES[:design]
+ wiki = Event::TARGET_TYPES[:wiki]
+
+ expect(track_action(event_action: :pushed, event_target: project, author_id: 1)).to be_truthy
+ expect(track_action(event_action: :pushed, event_target: project, author_id: 1)).to be_truthy
+ expect(track_action(event_action: :pushed, event_target: project, author_id: 2)).to be_truthy
+ expect(track_action(event_action: :pushed, event_target: project, author_id: 3)).to be_truthy
+ expect(track_action(event_action: :pushed, event_target: project, author_id: 4, time: time - 3.days)).to be_truthy
+ expect(track_action(event_action: :created, event_target: project, author_id: 5, time: time - 3.days)).to be_truthy
+
+ expect(track_action(event_action: :destroyed, event_target: design, author_id: 3)).to be_truthy
+ expect(track_action(event_action: :created, event_target: design, author_id: 4)).to be_truthy
+ expect(track_action(event_action: :updated, event_target: design, author_id: 5)).to be_truthy
+ expect(track_action(event_action: :pushed, event_target: design, author_id: 6)).to be_truthy
+
+ expect(track_action(event_action: :destroyed, event_target: wiki, author_id: 5)).to be_truthy
+ expect(track_action(event_action: :created, event_target: wiki, author_id: 3)).to be_truthy
+ expect(track_action(event_action: :updated, event_target: wiki, author_id: 4)).to be_truthy
+ expect(track_action(event_action: :pushed, event_target: wiki, author_id: 6)).to be_truthy
+
+ expect(count_unique_events(event_action: described_class::PUSH_ACTION, date_from: time, date_to: Date.today)).to eq(3)
+ expect(count_unique_events(event_action: described_class::PUSH_ACTION, date_from: time - 5.days, date_to: Date.tomorrow)).to eq(4)
+ expect(count_unique_events(event_action: described_class::DESIGN_ACTION, date_from: time - 5.days, date_to: Date.today)).to eq(3)
+ expect(count_unique_events(event_action: described_class::WIKI_ACTION, date_from: time - 5.days, date_to: Date.today)).to eq(3)
+ expect(count_unique_events(event_action: described_class::PUSH_ACTION, date_from: time - 5.days, date_to: time - 2.days)).to eq(1)
+ end
+ end
+ end
+ end
+
+ context 'when tracking unsuccessfully' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:feature_flag, :application_setting, :target, :action) do
+ true | true | Project | :invalid_action
+ false | true | Project | :pushed
+ true | false | Project | :pushed
+ true | true | :invalid_target | :pushed
+ end
+
+ with_them do
+ before do
+ stub_application_setting(usage_ping_enabled: application_setting)
+ stub_feature_flags(described_class::FEATURE_FLAG => feature_flag)
+ end
+
+ it 'returns the expected values' do
+ expect(track_action(event_action: action, event_target: target, author_id: 2)).to be_nil
+ expect(count_unique_events(event_action: described_class::PUSH_ACTION, date_from: time, date_to: Date.today)).to eq(0)
+ end
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/usage_data_counters/web_ide_counter_spec.rb b/spec/lib/gitlab/usage_data_counters/web_ide_counter_spec.rb
index 42abbecead0..b0e5bd18b66 100644
--- a/spec/lib/gitlab/usage_data_counters/web_ide_counter_spec.rb
+++ b/spec/lib/gitlab/usage_data_counters/web_ide_counter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::UsageDataCounters::WebIdeCounter, :clean_gitlab_redis_shared_state do
+RSpec.describe Gitlab::UsageDataCounters::WebIdeCounter, :clean_gitlab_redis_shared_state do
shared_examples 'counter examples' do |event|
it 'increments counter and return the total count' do
expect(described_class.public_send(:total_count, event)).to eq(0)
diff --git a/spec/lib/gitlab/usage_data_counters/wiki_page_counter_spec.rb b/spec/lib/gitlab/usage_data_counters/wiki_page_counter_spec.rb
index 4e8ae35187e..685f6ea0a85 100644
--- a/spec/lib/gitlab/usage_data_counters/wiki_page_counter_spec.rb
+++ b/spec/lib/gitlab/usage_data_counters/wiki_page_counter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::UsageDataCounters::WikiPageCounter do
+RSpec.describe Gitlab::UsageDataCounters::WikiPageCounter do
it_behaves_like 'a redis usage counter', 'Wiki Page', :create
it_behaves_like 'a redis usage counter', 'Wiki Page', :update
it_behaves_like 'a redis usage counter', 'Wiki Page', :delete
diff --git a/spec/lib/gitlab/usage_data_spec.rb b/spec/lib/gitlab/usage_data_spec.rb
index 31176999333..bca2f49eb33 100644
--- a/spec/lib/gitlab/usage_data_spec.rb
+++ b/spec/lib/gitlab/usage_data_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::UsageData, :aggregate_failures do
+RSpec.describe Gitlab::UsageData, :aggregate_failures do
include UsageDataHelpers
before do
@@ -10,7 +10,282 @@ describe Gitlab::UsageData, :aggregate_failures do
stub_object_store_settings
end
- describe '#uncached_data' do
+ describe '.uncached_data' do
+ describe '.usage_activity_by_stage' do
+ it 'includes usage_activity_by_stage data' do
+ expect(described_class.uncached_data).to include(:usage_activity_by_stage)
+ expect(described_class.uncached_data).to include(:usage_activity_by_stage_monthly)
+ end
+
+ it 'clears memoized values' do
+ values = %i(issue_minimum_id issue_maximum_id
+ user_minimum_id user_maximum_id unique_visit_service
+ deployment_minimum_id deployment_maximum_id
+ approval_merge_request_rule_minimum_id
+ approval_merge_request_rule_maximum_id)
+ values.each do |key|
+ expect(described_class).to receive(:clear_memoization).with(key)
+ end
+
+ described_class.uncached_data
+ end
+
+ context 'for configure' do
+ it 'includes accurate usage_activity_by_stage data' do
+ for_defined_days_back do
+ user = create(:user)
+ cluster = create(:cluster, user: user)
+ create(:clusters_applications_cert_manager, :installed, cluster: cluster)
+ create(:clusters_applications_helm, :installed, cluster: cluster)
+ create(:clusters_applications_ingress, :installed, cluster: cluster)
+ create(:clusters_applications_knative, :installed, cluster: cluster)
+ create(:cluster, :disabled, user: user)
+ create(:cluster_provider_gcp, :created)
+ create(:cluster_provider_aws, :created)
+ create(:cluster_platform_kubernetes)
+ create(:cluster, :group, :disabled, user: user)
+ create(:cluster, :group, user: user)
+ create(:cluster, :instance, :disabled, :production_environment)
+ create(:cluster, :instance, :production_environment)
+ create(:cluster, :management_project)
+ end
+
+ expect(described_class.uncached_data[:usage_activity_by_stage][:configure]).to include(
+ clusters_applications_cert_managers: 2,
+ clusters_applications_helm: 2,
+ clusters_applications_ingress: 2,
+ clusters_applications_knative: 2,
+ clusters_management_project: 2,
+ clusters_disabled: 4,
+ clusters_enabled: 12,
+ clusters_platforms_gke: 2,
+ clusters_platforms_eks: 2,
+ clusters_platforms_user: 2,
+ instance_clusters_disabled: 2,
+ instance_clusters_enabled: 2,
+ group_clusters_disabled: 2,
+ group_clusters_enabled: 2,
+ project_clusters_disabled: 2,
+ project_clusters_enabled: 10
+ )
+ expect(described_class.uncached_data[:usage_activity_by_stage_monthly][:configure]).to include(
+ clusters_applications_cert_managers: 1,
+ clusters_applications_helm: 1,
+ clusters_applications_ingress: 1,
+ clusters_applications_knative: 1,
+ clusters_management_project: 1,
+ clusters_disabled: 2,
+ clusters_enabled: 6,
+ clusters_platforms_gke: 1,
+ clusters_platforms_eks: 1,
+ clusters_platforms_user: 1,
+ instance_clusters_disabled: 1,
+ instance_clusters_enabled: 1,
+ group_clusters_disabled: 1,
+ group_clusters_enabled: 1,
+ project_clusters_disabled: 1,
+ project_clusters_enabled: 5
+ )
+ end
+ end
+
+ context 'for create' do
+ it 'include usage_activity_by_stage data' do
+ expect(described_class.uncached_data[:usage_activity_by_stage][:create])
+ .not_to include(
+ :merge_requests_users
+ )
+ end
+
+ it 'includes monthly usage_activity_by_stage data' do
+ expect(described_class.uncached_data[:usage_activity_by_stage_monthly][:create])
+ .to include(
+ :merge_requests_users
+ )
+ end
+
+ it 'includes accurate usage_activity_by_stage data' do
+ for_defined_days_back do
+ user = create(:user)
+ project = create(:project, :repository_private,
+ :test_repo, :remote_mirror, creator: user)
+ create(:merge_request, source_project: project)
+ create(:deploy_key, user: user)
+ create(:key, user: user)
+ create(:project, creator: user, disable_overriding_approvers_per_merge_request: true)
+ create(:project, creator: user, disable_overriding_approvers_per_merge_request: false)
+ create(:remote_mirror, project: project)
+ create(:snippet, author: user)
+ end
+
+ expect(described_class.uncached_data[:usage_activity_by_stage][:create]).to include(
+ deploy_keys: 2,
+ keys: 2,
+ merge_requests: 2,
+ projects_with_disable_overriding_approvers_per_merge_request: 2,
+ projects_without_disable_overriding_approvers_per_merge_request: 4,
+ remote_mirrors: 2,
+ snippets: 2
+ )
+ expect(described_class.uncached_data[:usage_activity_by_stage_monthly][:create]).to include(
+ deploy_keys: 1,
+ keys: 1,
+ merge_requests: 1,
+ projects_with_disable_overriding_approvers_per_merge_request: 1,
+ projects_without_disable_overriding_approvers_per_merge_request: 2,
+ remote_mirrors: 1,
+ snippets: 1
+ )
+ end
+ end
+
+ context 'for manage' do
+ it 'includes accurate usage_activity_by_stage data' do
+ stub_config(
+ omniauth:
+ { providers: omniauth_providers }
+ )
+
+ for_defined_days_back do
+ user = create(:user)
+ create(:event, author: user)
+ create(:group_member, user: user)
+ end
+
+ expect(described_class.uncached_data[:usage_activity_by_stage][:manage]).to include(
+ events: 2,
+ groups: 2,
+ users_created: Gitlab.ee? ? 6 : 5,
+ omniauth_providers: ['google_oauth2']
+ )
+ expect(described_class.uncached_data[:usage_activity_by_stage_monthly][:manage]).to include(
+ events: 1,
+ groups: 1,
+ users_created: Gitlab.ee? ? 4 : 3,
+ omniauth_providers: ['google_oauth2']
+ )
+ end
+
+ def omniauth_providers
+ [
+ OpenStruct.new(name: 'google_oauth2'),
+ OpenStruct.new(name: 'ldapmain'),
+ OpenStruct.new(name: 'group_saml')
+ ]
+ end
+ end
+
+ context 'for monitor' do
+ it 'includes accurate usage_activity_by_stage data' do
+ for_defined_days_back do
+ user = create(:user, dashboard: 'operations')
+ cluster = create(:cluster, user: user)
+ create(:project, creator: user)
+ create(:clusters_applications_prometheus, :installed, cluster: cluster)
+ end
+
+ expect(described_class.uncached_data[:usage_activity_by_stage][:monitor]).to include(
+ clusters: 2,
+ clusters_applications_prometheus: 2,
+ operations_dashboard_default_dashboard: 2
+ )
+ expect(described_class.uncached_data[:usage_activity_by_stage_monthly][:monitor]).to include(
+ clusters: 1,
+ clusters_applications_prometheus: 1,
+ operations_dashboard_default_dashboard: 1
+ )
+ end
+ end
+
+ context 'for plan' do
+ it 'includes accurate usage_activity_by_stage data' do
+ for_defined_days_back do
+ user = create(:user)
+ project = create(:project, creator: user)
+ issue = create(:issue, project: project, author: user)
+ create(:note, project: project, noteable: issue, author: user)
+ create(:todo, project: project, target: issue, author: user)
+ end
+
+ expect(described_class.uncached_data[:usage_activity_by_stage][:plan]).to include(
+ issues: 2,
+ notes: 2,
+ projects: 2,
+ todos: 2
+ )
+ expect(described_class.uncached_data[:usage_activity_by_stage_monthly][:plan]).to include(
+ issues: 1,
+ notes: 1,
+ projects: 1,
+ todos: 1
+ )
+ end
+ end
+
+ context 'for release' do
+ it 'includes accurate usage_activity_by_stage data' do
+ for_defined_days_back do
+ user = create(:user)
+ create(:deployment, :failed, user: user)
+ create(:release, author: user)
+ create(:deployment, :success, user: user)
+ end
+
+ expect(described_class.uncached_data[:usage_activity_by_stage][:release]).to include(
+ deployments: 2,
+ failed_deployments: 2,
+ releases: 2,
+ successful_deployments: 2
+ )
+ expect(described_class.uncached_data[:usage_activity_by_stage_monthly][:release]).to include(
+ deployments: 1,
+ failed_deployments: 1,
+ releases: 1,
+ successful_deployments: 1
+ )
+ end
+ end
+
+ context 'for verify' do
+ it 'includes accurate usage_activity_by_stage data' do
+ for_defined_days_back do
+ user = create(:user)
+ create(:ci_build, user: user)
+ create(:ci_empty_pipeline, source: :external, user: user)
+ create(:ci_empty_pipeline, user: user)
+ create(:ci_pipeline, :auto_devops_source, user: user)
+ create(:ci_pipeline, :repository_source, user: user)
+ create(:ci_pipeline_schedule, owner: user)
+ create(:ci_trigger, owner: user)
+ create(:clusters_applications_runner, :installed)
+ end
+
+ expect(described_class.uncached_data[:usage_activity_by_stage][:verify]).to include(
+ ci_builds: 2,
+ ci_external_pipelines: 2,
+ ci_internal_pipelines: 2,
+ ci_pipeline_config_auto_devops: 2,
+ ci_pipeline_config_repository: 2,
+ ci_pipeline_schedules: 2,
+ ci_pipelines: 2,
+ ci_triggers: 2,
+ clusters_applications_runner: 2
+ )
+ expect(described_class.uncached_data[:usage_activity_by_stage_monthly][:verify]).to include(
+ ci_builds: 1,
+ ci_external_pipelines: 1,
+ ci_internal_pipelines: 1,
+ ci_pipeline_config_auto_devops: 1,
+ ci_pipeline_config_repository: 1,
+ ci_pipeline_schedules: 1,
+ ci_pipelines: 1,
+ ci_triggers: 1,
+ clusters_applications_runner: 1
+ )
+ end
+ end
+ end
+
it 'ensures recorded_at is set before any other usage data calculation' do
%i(alt_usage_data redis_usage_data distinct_count count).each do |method|
expect(described_class).not_to receive(method)
@@ -21,7 +296,7 @@ describe Gitlab::UsageData, :aggregate_failures do
end
end
- describe '#data' do
+ describe '.data' do
let!(:ud) { build(:usage_data) }
before do
@@ -44,7 +319,11 @@ describe Gitlab::UsageData, :aggregate_failures do
expect(UsageDataHelpers::COUNTS_KEYS - count_data.keys).to be_empty
end
- it 'gathers projects data correctly' do
+ it 'gathers usage counts monthly hash' do
+ expect(subject[:counts_monthly]).to be_an(Hash)
+ end
+
+ it 'gathers usage counts correctly' do
count_data = subject[:counts]
expect(count_data[:projects]).to eq(4)
@@ -56,8 +335,6 @@ describe Gitlab::UsageData, :aggregate_failures do
expect(count_data[:jira_imports_projects_count]).to eq(2)
expect(count_data[:jira_imports_total_imported_count]).to eq(3)
expect(count_data[:jira_imports_total_imported_issues_count]).to eq(13)
- expect(count_data[:projects_slack_notifications_active]).to eq(2)
- expect(count_data[:projects_slack_slash_active]).to eq(1)
expect(count_data[:projects_slack_active]).to eq(2)
expect(count_data[:projects_slack_slash_commands_active]).to eq(1)
expect(count_data[:projects_custom_issue_tracker_active]).to eq(1)
@@ -102,7 +379,15 @@ describe Gitlab::UsageData, :aggregate_failures do
expect(count_data[:clusters_applications_elastic_stack]).to eq(1)
expect(count_data[:grafana_integrated_projects]).to eq(2)
expect(count_data[:clusters_applications_jupyter]).to eq(1)
+ expect(count_data[:clusters_applications_cilium]).to eq(1)
expect(count_data[:clusters_management_project]).to eq(1)
+
+ expect(count_data[:deployments]).to eq(4)
+ expect(count_data[:successful_deployments]).to eq(2)
+ expect(count_data[:failed_deployments]).to eq(2)
+ expect(count_data[:snippets]).to eq(6)
+ expect(count_data[:personal_snippets]).to eq(2)
+ expect(count_data[:project_snippets]).to eq(4)
end
it 'gathers object store usage correctly' do
@@ -169,6 +454,10 @@ describe Gitlab::UsageData, :aggregate_failures do
expect { subject }.not_to raise_error
end
+ it 'includes a recording_ce_finished_at timestamp' do
+ expect(subject[:recording_ce_finished_at]).to be_a(Time)
+ end
+
it 'jira usage works when queries time out' do
allow_any_instance_of(ActiveRecord::Relation)
.to receive(:find_in_batches).and_raise(ActiveRecord::StatementInvalid.new(''))
@@ -177,7 +466,24 @@ describe Gitlab::UsageData, :aggregate_failures do
end
end
- describe '#usage_data_counters' do
+ describe '.system_usage_data_monthly' do
+ let!(:ud) { build(:usage_data) }
+
+ subject { described_class.system_usage_data_monthly }
+
+ it 'gathers monthly usage counts correctly' do
+ counts_monthly = subject[:counts_monthly]
+
+ expect(counts_monthly[:deployments]).to eq(2)
+ expect(counts_monthly[:successful_deployments]).to eq(1)
+ expect(counts_monthly[:failed_deployments]).to eq(1)
+ expect(counts_monthly[:snippets]).to eq(3)
+ expect(counts_monthly[:personal_snippets]).to eq(1)
+ expect(counts_monthly[:project_snippets]).to eq(2)
+ end
+ end
+
+ describe '.usage_data_counters' do
subject { described_class.usage_data_counters }
it { is_expected.to all(respond_to :totals) }
@@ -204,7 +510,7 @@ describe Gitlab::UsageData, :aggregate_failures do
end
end
- describe '#license_usage_data' do
+ describe '.license_usage_data' do
subject { described_class.license_usage_data }
it 'gathers license data' do
@@ -216,16 +522,8 @@ describe Gitlab::UsageData, :aggregate_failures do
end
end
- describe '.recording_ce_finished_at' do
- subject { described_class.recording_ce_finish_data }
-
- it 'gathers time ce recording finishes at' do
- expect(subject[:recording_ce_finished_at]).to be_a(Time)
- end
- end
-
context 'when not relying on database records' do
- describe '#features_usage_data_ce' do
+ describe '.features_usage_data_ce' do
subject { described_class.features_usage_data_ce }
it 'gathers feature usage data', :aggregate_failures do
@@ -243,6 +541,20 @@ describe Gitlab::UsageData, :aggregate_failures do
expect(subject[:grafana_link_enabled]).to eq(Gitlab::CurrentSettings.grafana_enabled?)
end
+ context 'with embedded Prometheus' do
+ it 'returns true when embedded Prometheus is enabled' do
+ allow(Gitlab::Prometheus::Internal).to receive(:prometheus_enabled?).and_return(true)
+
+ expect(subject[:prometheus_enabled]).to eq(true)
+ end
+
+ it 'returns false when embedded Prometheus is disabled' do
+ allow(Gitlab::Prometheus::Internal).to receive(:prometheus_enabled?).and_return(false)
+
+ expect(subject[:prometheus_enabled]).to eq(false)
+ end
+ end
+
context 'with embedded grafana' do
it 'returns true when embedded grafana is enabled' do
stub_application_setting(grafana_enabled: true)
@@ -258,7 +570,7 @@ describe Gitlab::UsageData, :aggregate_failures do
end
end
- describe '#components_usage_data' do
+ describe '.components_usage_data' do
subject { described_class.components_usage_data }
it 'gathers basic components usage data' do
@@ -282,7 +594,7 @@ describe Gitlab::UsageData, :aggregate_failures do
end
end
- describe '#app_server_type' do
+ describe '.app_server_type' do
subject { described_class.app_server_type }
it 'successfully identifies runtime and returns the identifier' do
@@ -304,7 +616,7 @@ describe Gitlab::UsageData, :aggregate_failures do
end
end
- describe '#object_store_config' do
+ describe '.object_store_config' do
let(:component) { 'lfs' }
subject { described_class.object_store_config(component) }
@@ -345,7 +657,7 @@ describe Gitlab::UsageData, :aggregate_failures do
end
end
- describe '#object_store_usage_data' do
+ describe '.object_store_usage_data' do
subject { described_class.object_store_usage_data }
it 'fetches object store config of five components' do
@@ -364,7 +676,7 @@ describe Gitlab::UsageData, :aggregate_failures do
end
end
- describe '#cycle_analytics_usage_data' do
+ describe '.cycle_analytics_usage_data' do
subject { described_class.cycle_analytics_usage_data }
it 'works when queries time out in new' do
@@ -382,7 +694,7 @@ describe Gitlab::UsageData, :aggregate_failures do
end
end
- describe '#ingress_modsecurity_usage' do
+ describe '.ingress_modsecurity_usage' do
subject { described_class.ingress_modsecurity_usage }
let(:environment) { create(:environment) }
@@ -514,7 +826,7 @@ describe Gitlab::UsageData, :aggregate_failures do
end
end
- describe '#grafana_embed_usage_data' do
+ describe '.grafana_embed_usage_data' do
subject { described_class.grafana_embed_usage_data }
let(:project) { create(:project) }
@@ -580,7 +892,7 @@ describe Gitlab::UsageData, :aggregate_failures do
end
end
- describe '#merge_requests_usage' do
+ describe '.merge_requests_users' do
let(:time_period) { { created_at: 2.days.ago..Time.current } }
let(:merge_request) { create(:merge_request) }
let(:other_user) { create(:user) }
@@ -597,9 +909,94 @@ describe Gitlab::UsageData, :aggregate_failures do
end
it 'returns the distinct count of users using merge requests (via events table) within the specified time period' do
- expect(described_class.merge_requests_usage(time_period)).to eq(
- merge_requests_users: 2
- )
+ expect(described_class.merge_requests_users(time_period)).to eq(2)
+ end
+ end
+
+ def for_defined_days_back(days: [29, 2])
+ days.each do |n|
+ Timecop.travel(n.days.ago) do
+ yield
+ end
+ end
+ end
+
+ describe '#action_monthly_active_users', :clean_gitlab_redis_shared_state do
+ let(:time_period) { { created_at: 2.days.ago..time } }
+ let(:time) { Time.zone.now }
+
+ before do
+ stub_feature_flags(Gitlab::UsageDataCounters::TrackUniqueActions::FEATURE_FLAG => feature_flag)
+ end
+
+ context 'when the feature flag is enabled' do
+ let(:feature_flag) { true }
+
+ before do
+ counter = Gitlab::UsageDataCounters::TrackUniqueActions
+ project = Event::TARGET_TYPES[:project]
+ wiki = Event::TARGET_TYPES[:wiki]
+ design = Event::TARGET_TYPES[:design]
+
+ counter.track_action(event_action: :pushed, event_target: project, author_id: 1)
+ counter.track_action(event_action: :pushed, event_target: project, author_id: 1)
+ counter.track_action(event_action: :pushed, event_target: project, author_id: 2)
+ counter.track_action(event_action: :pushed, event_target: project, author_id: 3)
+ counter.track_action(event_action: :pushed, event_target: project, author_id: 4, time: time - 3.days)
+ counter.track_action(event_action: :created, event_target: project, author_id: 5, time: time - 3.days)
+ counter.track_action(event_action: :created, event_target: wiki, author_id: 3)
+ counter.track_action(event_action: :created, event_target: design, author_id: 3)
+ end
+
+ it 'returns the distinct count of user actions within the specified time period' do
+ expect(described_class.action_monthly_active_users(time_period)).to eq(
+ {
+ action_monthly_active_users_design_management: 1,
+ action_monthly_active_users_project_repo: 3,
+ action_monthly_active_users_wiki_repo: 1
+ }
+ )
+ end
+ end
+
+ context 'when the feature flag is disabled' do
+ let(:feature_flag) { false }
+
+ it 'returns an empty hash' do
+ expect(described_class.action_monthly_active_users(time_period)).to eq({})
+ end
+ end
+ end
+
+ describe '.analytics_unique_visits_data' do
+ subject { described_class.analytics_unique_visits_data }
+
+ it 'returns the number of unique visits to pages with analytics features' do
+ ::Gitlab::Analytics::UniqueVisits::TARGET_IDS.each do |target_id|
+ expect_any_instance_of(::Gitlab::Analytics::UniqueVisits).to receive(:weekly_unique_visits_for_target).with(target_id).and_return(123)
+ end
+
+ expect_any_instance_of(::Gitlab::Analytics::UniqueVisits).to receive(:weekly_unique_visits_for_any_target).and_return(543)
+
+ expect(subject).to eq({
+ analytics_unique_visits: {
+ 'g_analytics_contribution' => 123,
+ 'g_analytics_insights' => 123,
+ 'g_analytics_issues' => 123,
+ 'g_analytics_productivity' => 123,
+ 'g_analytics_valuestream' => 123,
+ 'p_analytics_pipelines' => 123,
+ 'p_analytics_code_reviews' => 123,
+ 'p_analytics_valuestream' => 123,
+ 'p_analytics_insights' => 123,
+ 'p_analytics_issues' => 123,
+ 'p_analytics_repo' => 123,
+ 'u_analytics_todos' => 123,
+ 'i_analytics_cohorts' => 123,
+ 'i_analytics_dev_ops_score' => 123,
+ 'analytics_unique_visits_for_any_target' => 543
+ }
+ })
end
end
end
diff --git a/spec/lib/gitlab/user_access_snippet_spec.rb b/spec/lib/gitlab/user_access_snippet_spec.rb
index 2e8a0a49a76..4143a3017e8 100644
--- a/spec/lib/gitlab/user_access_snippet_spec.rb
+++ b/spec/lib/gitlab/user_access_snippet_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::UserAccessSnippet do
+RSpec.describe Gitlab::UserAccessSnippet do
subject(:access) { described_class.new(user, snippet: snippet) }
let_it_be(:project) { create(:project, :private) }
diff --git a/spec/lib/gitlab/user_access_spec.rb b/spec/lib/gitlab/user_access_spec.rb
index 78370f0136c..1a81d0127dc 100644
--- a/spec/lib/gitlab/user_access_spec.rb
+++ b/spec/lib/gitlab/user_access_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::UserAccess do
+RSpec.describe Gitlab::UserAccess do
include ProjectForksHelper
let(:access) { described_class.new(user, project: project) }
diff --git a/spec/lib/gitlab/utils/deep_size_spec.rb b/spec/lib/gitlab/utils/deep_size_spec.rb
index 5a155fb6c80..7595fb2c1b0 100644
--- a/spec/lib/gitlab/utils/deep_size_spec.rb
+++ b/spec/lib/gitlab/utils/deep_size_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Utils::DeepSize do
+RSpec.describe Gitlab::Utils::DeepSize do
let(:data) do
{
a: [1, 2, 3],
diff --git a/spec/lib/gitlab/utils/inline_hash_spec.rb b/spec/lib/gitlab/utils/inline_hash_spec.rb
index 867db0b92a5..d1354a7ae0a 100644
--- a/spec/lib/gitlab/utils/inline_hash_spec.rb
+++ b/spec/lib/gitlab/utils/inline_hash_spec.rb
@@ -2,7 +2,7 @@
require 'fast_spec_helper'
-describe Gitlab::Utils::InlineHash do
+RSpec.describe Gitlab::Utils::InlineHash do
describe '.merge_keys' do
subject { described_class.merge_keys(source) }
diff --git a/spec/lib/gitlab/utils/json_size_estimator_spec.rb b/spec/lib/gitlab/utils/json_size_estimator_spec.rb
index ae24e25558a..5fd66caa5e9 100644
--- a/spec/lib/gitlab/utils/json_size_estimator_spec.rb
+++ b/spec/lib/gitlab/utils/json_size_estimator_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Utils::JsonSizeEstimator do
+RSpec.describe Gitlab::Utils::JsonSizeEstimator do
RSpec::Matchers.define :match_json_bytesize_of do |expected|
match do |actual|
actual == expected.to_json.bytesize
diff --git a/spec/lib/gitlab/utils/lazy_attributes_spec.rb b/spec/lib/gitlab/utils/lazy_attributes_spec.rb
index c0005c194c4..dfffe70defb 100644
--- a/spec/lib/gitlab/utils/lazy_attributes_spec.rb
+++ b/spec/lib/gitlab/utils/lazy_attributes_spec.rb
@@ -2,7 +2,7 @@
require 'fast_spec_helper'
require 'active_support/concern'
-describe Gitlab::Utils::LazyAttributes do
+RSpec.describe Gitlab::Utils::LazyAttributes do
subject(:klass) do
Class.new do
include Gitlab::Utils::LazyAttributes
diff --git a/spec/lib/gitlab/utils/log_limited_array_spec.rb b/spec/lib/gitlab/utils/log_limited_array_spec.rb
index a236ab37614..a55a176be48 100644
--- a/spec/lib/gitlab/utils/log_limited_array_spec.rb
+++ b/spec/lib/gitlab/utils/log_limited_array_spec.rb
@@ -2,7 +2,7 @@
require 'fast_spec_helper'
-describe Gitlab::Utils::LogLimitedArray do
+RSpec.describe Gitlab::Utils::LogLimitedArray do
describe '.log_limited_array' do
context 'when the argument is not an array' do
it 'returns an empty array' do
diff --git a/spec/lib/gitlab/utils/markdown_spec.rb b/spec/lib/gitlab/utils/markdown_spec.rb
new file mode 100644
index 00000000000..001ff5bc487
--- /dev/null
+++ b/spec/lib/gitlab/utils/markdown_spec.rb
@@ -0,0 +1,63 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Utils::Markdown do
+ let(:klass) do
+ Class.new do
+ include Gitlab::Utils::Markdown
+ end
+ end
+
+ subject(:object) { klass.new }
+
+ describe '#string_to_anchor' do
+ subject { object.string_to_anchor(string) }
+
+ let(:string) { 'My Header' }
+
+ it 'converts string to anchor' do
+ is_expected.to eq 'my-header'
+ end
+
+ context 'when string has punctuation' do
+ let(:string) { 'My, Header!' }
+
+ it 'removes punctuation' do
+ is_expected.to eq 'my-header'
+ end
+ end
+
+ context 'when string starts and ends with spaces' do
+ let(:string) { ' My Header ' }
+
+ it 'removes extra spaces' do
+ is_expected.to eq 'my-header'
+ end
+ end
+
+ context 'when string has multiple spaces and dashes in the middle' do
+ let(:string) { 'My - - - Header' }
+
+ it 'removes consecutive dashes' do
+ is_expected.to eq 'my-header'
+ end
+ end
+
+ context 'when string contains only digits' do
+ let(:string) { '123' }
+
+ it 'adds anchor prefix' do
+ is_expected.to eq 'anchor-123'
+ end
+ end
+
+ context 'when string is empty' do
+ let(:string) { '' }
+
+ it 'returns an empty string' do
+ is_expected.to eq ''
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/utils/measuring_spec.rb b/spec/lib/gitlab/utils/measuring_spec.rb
index 254f53f7da3..4931ebf26f0 100644
--- a/spec/lib/gitlab/utils/measuring_spec.rb
+++ b/spec/lib/gitlab/utils/measuring_spec.rb
@@ -2,7 +2,7 @@
require 'fast_spec_helper'
-describe Gitlab::Utils::Measuring do
+RSpec.describe Gitlab::Utils::Measuring do
describe '#with_measuring' do
let(:base_log_data) { {} }
let(:result) { "result" }
diff --git a/spec/lib/gitlab/utils/merge_hash_spec.rb b/spec/lib/gitlab/utils/merge_hash_spec.rb
index 72620e549a9..11daa05c9ee 100644
--- a/spec/lib/gitlab/utils/merge_hash_spec.rb
+++ b/spec/lib/gitlab/utils/merge_hash_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe Gitlab::Utils::MergeHash do
+RSpec.describe Gitlab::Utils::MergeHash do
describe '.crush' do
it 'can flatten a hash to each element' do
input = { hello: "world", this: { crushes: ["an entire", "hash"] } }
diff --git a/spec/lib/gitlab/utils/override_spec.rb b/spec/lib/gitlab/utils/override_spec.rb
index e2776efac85..7ba7392df0f 100644
--- a/spec/lib/gitlab/utils/override_spec.rb
+++ b/spec/lib/gitlab/utils/override_spec.rb
@@ -2,7 +2,7 @@
require 'fast_spec_helper'
-describe Gitlab::Utils::Override do
+RSpec.describe Gitlab::Utils::Override do
let(:base) do
Struct.new(:good) do
def self.good
diff --git a/spec/lib/gitlab/utils/safe_inline_hash_spec.rb b/spec/lib/gitlab/utils/safe_inline_hash_spec.rb
index 617845332bc..f7c50140cf7 100644
--- a/spec/lib/gitlab/utils/safe_inline_hash_spec.rb
+++ b/spec/lib/gitlab/utils/safe_inline_hash_spec.rb
@@ -2,7 +2,7 @@
require 'fast_spec_helper'
-describe Gitlab::Utils::SafeInlineHash do
+RSpec.describe Gitlab::Utils::SafeInlineHash do
describe '.merge_keys!' do
let(:source) { { 'foo' => { 'bar' => 'baz' } } }
let(:validator) { instance_double(Gitlab::Utils::DeepSize, valid?: valid) }
diff --git a/spec/lib/gitlab/utils/sanitize_node_link_spec.rb b/spec/lib/gitlab/utils/sanitize_node_link_spec.rb
index dd379f2fe1f..514051b1cc0 100644
--- a/spec/lib/gitlab/utils/sanitize_node_link_spec.rb
+++ b/spec/lib/gitlab/utils/sanitize_node_link_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Utils::SanitizeNodeLink do
+RSpec.describe Gitlab::Utils::SanitizeNodeLink do
let(:klass) do
struct = Struct.new(:value)
struct.include(described_class)
diff --git a/spec/lib/gitlab/utils/strong_memoize_spec.rb b/spec/lib/gitlab/utils/strong_memoize_spec.rb
index 624e799c5e9..d9fa2e516e1 100644
--- a/spec/lib/gitlab/utils/strong_memoize_spec.rb
+++ b/spec/lib/gitlab/utils/strong_memoize_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Utils::StrongMemoize do
+RSpec.describe Gitlab::Utils::StrongMemoize do
let(:klass) do
struct = Struct.new(:value) do
def method_name
diff --git a/spec/lib/gitlab/utils/usage_data_spec.rb b/spec/lib/gitlab/utils/usage_data_spec.rb
index 7de615384c5..7940c9af6ff 100644
--- a/spec/lib/gitlab/utils/usage_data_spec.rb
+++ b/spec/lib/gitlab/utils/usage_data_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Utils::UsageData do
+RSpec.describe Gitlab::Utils::UsageData do
describe '#count' do
let(:relation) { double(:relation) }
@@ -88,13 +88,21 @@ describe Gitlab::Utils::UsageData do
end
context 'when Prometheus is disabled' do
- it 'returns nil' do
+ before do
expect(Gitlab::Prometheus::Internal).to receive(:prometheus_enabled?).and_return(false)
+ end
+ it 'returns nil by default' do
result = described_class.with_prometheus_client { |client| client }
expect(result).to be nil
end
+
+ it 'returns fallback if provided' do
+ result = described_class.with_prometheus_client(fallback: []) { |client| client }
+
+ expect(result).to eq([])
+ end
end
end
@@ -108,4 +116,14 @@ describe Gitlab::Utils::UsageData do
expect(duration).to eq(2)
end
end
+
+ describe '#with_finished_at' do
+ it 'adds a timestamp to the hash yielded by the block' do
+ freeze_time do
+ result = described_class.with_finished_at(:current_time) { { a: 1 } }
+
+ expect(result).to eq(a: 1, current_time: Time.now)
+ end
+ end
+ end
end
diff --git a/spec/lib/gitlab/utils_spec.rb b/spec/lib/gitlab/utils_spec.rb
index 3a2430d1f2d..7a0d40ff0d2 100644
--- a/spec/lib/gitlab/utils_spec.rb
+++ b/spec/lib/gitlab/utils_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Utils do
+RSpec.describe Gitlab::Utils do
delegate :to_boolean, :boolean_to_yes_no, :slugify, :random_string, :which,
:ensure_array_from_string, :to_exclusive_sentence, :bytes_to_megabytes,
:append_path, :check_path_traversal!, :ms_to_round_sec, to: :described_class
@@ -358,4 +358,40 @@ describe Gitlab::Utils do
})
end
end
+
+ describe '.stable_sort_by' do
+ subject(:sorted_list) { described_class.stable_sort_by(list) { |obj| obj[:priority] } }
+
+ context 'when items have the same priority' do
+ let(:list) do
+ [
+ { name: 'obj 1', priority: 1 },
+ { name: 'obj 2', priority: 1 },
+ { name: 'obj 3', priority: 1 }
+ ]
+ end
+
+ it 'does not change order in cases of ties' do
+ expect(sorted_list).to eq(list)
+ end
+ end
+
+ context 'when items have different priorities' do
+ let(:list) do
+ [
+ { name: 'obj 1', priority: 2 },
+ { name: 'obj 2', priority: 1 },
+ { name: 'obj 3', priority: 3 }
+ ]
+ end
+
+ it 'sorts items like the regular sort_by' do
+ expect(sorted_list).to eq([
+ { name: 'obj 2', priority: 1 },
+ { name: 'obj 1', priority: 2 },
+ { name: 'obj 3', priority: 3 }
+ ])
+ end
+ end
+ end
end
diff --git a/spec/lib/gitlab/verify/job_artifacts_spec.rb b/spec/lib/gitlab/verify/job_artifacts_spec.rb
index b50ec1528d4..fde7ebd442e 100644
--- a/spec/lib/gitlab/verify/job_artifacts_spec.rb
+++ b/spec/lib/gitlab/verify/job_artifacts_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Verify::JobArtifacts do
+RSpec.describe Gitlab::Verify::JobArtifacts do
include GitlabVerifyHelpers
it_behaves_like 'Gitlab::Verify::BatchVerifier subclass' do
diff --git a/spec/lib/gitlab/verify/lfs_objects_spec.rb b/spec/lib/gitlab/verify/lfs_objects_spec.rb
index c27c9b6efa1..760162dd627 100644
--- a/spec/lib/gitlab/verify/lfs_objects_spec.rb
+++ b/spec/lib/gitlab/verify/lfs_objects_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Verify::LfsObjects do
+RSpec.describe Gitlab::Verify::LfsObjects do
include GitlabVerifyHelpers
it_behaves_like 'Gitlab::Verify::BatchVerifier subclass' do
diff --git a/spec/lib/gitlab/verify/uploads_spec.rb b/spec/lib/gitlab/verify/uploads_spec.rb
index a3d3f5d46f3..3e5154d5029 100644
--- a/spec/lib/gitlab/verify/uploads_spec.rb
+++ b/spec/lib/gitlab/verify/uploads_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Verify::Uploads do
+RSpec.describe Gitlab::Verify::Uploads do
include GitlabVerifyHelpers
it_behaves_like 'Gitlab::Verify::BatchVerifier subclass' do
diff --git a/spec/lib/gitlab/version_info_spec.rb b/spec/lib/gitlab/version_info_spec.rb
index 8c14b187410..f81e3aa070a 100644
--- a/spec/lib/gitlab/version_info_spec.rb
+++ b/spec/lib/gitlab/version_info_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Gitlab::VersionInfo' do
+RSpec.describe 'Gitlab::VersionInfo' do
before do
@unknown = Gitlab::VersionInfo.new
@v0_0_1 = Gitlab::VersionInfo.new(0, 0, 1)
diff --git a/spec/lib/gitlab/view/presenter/base_spec.rb b/spec/lib/gitlab/view/presenter/base_spec.rb
index e196ab23482..1ab6973e279 100644
--- a/spec/lib/gitlab/view/presenter/base_spec.rb
+++ b/spec/lib/gitlab/view/presenter/base_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::View::Presenter::Base do
+RSpec.describe Gitlab::View::Presenter::Base do
let(:project) { double(:project) }
let(:presenter_class) do
Struct.new(:subject).include(described_class)
diff --git a/spec/lib/gitlab/view/presenter/delegated_spec.rb b/spec/lib/gitlab/view/presenter/delegated_spec.rb
index 0a21cd1358e..27cff970f06 100644
--- a/spec/lib/gitlab/view/presenter/delegated_spec.rb
+++ b/spec/lib/gitlab/view/presenter/delegated_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::View::Presenter::Delegated do
+RSpec.describe Gitlab::View::Presenter::Delegated do
let(:project) { double(:project, user: 'John Doe') }
let(:presenter_class) do
Class.new(described_class)
diff --git a/spec/lib/gitlab/view/presenter/factory_spec.rb b/spec/lib/gitlab/view/presenter/factory_spec.rb
index 7bf3c325019..92986e89b73 100644
--- a/spec/lib/gitlab/view/presenter/factory_spec.rb
+++ b/spec/lib/gitlab/view/presenter/factory_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::View::Presenter::Factory do
+RSpec.describe Gitlab::View::Presenter::Factory do
let(:build) { Ci::Build.new }
describe '#initialize' do
diff --git a/spec/lib/gitlab/view/presenter/simple_spec.rb b/spec/lib/gitlab/view/presenter/simple_spec.rb
index 70e2b170a36..2b764fcccef 100644
--- a/spec/lib/gitlab/view/presenter/simple_spec.rb
+++ b/spec/lib/gitlab/view/presenter/simple_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::View::Presenter::Simple do
+RSpec.describe Gitlab::View::Presenter::Simple do
let(:project) { double(:project, user: 'John Doe') }
let(:presenter_class) do
Class.new(described_class)
diff --git a/spec/lib/gitlab/visibility_level_checker_spec.rb b/spec/lib/gitlab/visibility_level_checker_spec.rb
index fc929d5cbbf..833021a22ca 100644
--- a/spec/lib/gitlab/visibility_level_checker_spec.rb
+++ b/spec/lib/gitlab/visibility_level_checker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::VisibilityLevelChecker do
+RSpec.describe Gitlab::VisibilityLevelChecker do
let(:user) { create(:user) }
let(:project) { create(:project) }
let(:visibility_level_checker) { }
diff --git a/spec/lib/gitlab/visibility_level_spec.rb b/spec/lib/gitlab/visibility_level_spec.rb
index a249b3a235e..2ac343cd1e7 100644
--- a/spec/lib/gitlab/visibility_level_spec.rb
+++ b/spec/lib/gitlab/visibility_level_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::VisibilityLevel do
+RSpec.describe Gitlab::VisibilityLevel do
describe '.level_value' do
it 'converts "public" to integer value' do
expect(described_class.level_value('public')).to eq(Gitlab::VisibilityLevel::PUBLIC)
diff --git a/spec/lib/gitlab/web_ide/config/entry/global_spec.rb b/spec/lib/gitlab/web_ide/config/entry/global_spec.rb
index 04b0752c6fe..3a50667163b 100644
--- a/spec/lib/gitlab/web_ide/config/entry/global_spec.rb
+++ b/spec/lib/gitlab/web_ide/config/entry/global_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::WebIde::Config::Entry::Global do
+RSpec.describe Gitlab::WebIde::Config::Entry::Global do
let(:global) { described_class.new(hash) }
describe '.nodes' do
diff --git a/spec/lib/gitlab/web_ide/config/entry/terminal_spec.rb b/spec/lib/gitlab/web_ide/config/entry/terminal_spec.rb
index 882e389e040..0df0f56f440 100644
--- a/spec/lib/gitlab/web_ide/config/entry/terminal_spec.rb
+++ b/spec/lib/gitlab/web_ide/config/entry/terminal_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::WebIde::Config::Entry::Terminal do
+RSpec.describe Gitlab::WebIde::Config::Entry::Terminal do
let(:entry) { described_class.new(config, with_image_ports: true) }
describe '.nodes' do
diff --git a/spec/lib/gitlab/web_ide/config_spec.rb b/spec/lib/gitlab/web_ide/config_spec.rb
index c1dafd01197..7a9011d03c0 100644
--- a/spec/lib/gitlab/web_ide/config_spec.rb
+++ b/spec/lib/gitlab/web_ide/config_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::WebIde::Config do
+RSpec.describe Gitlab::WebIde::Config do
let(:config) do
described_class.new(yml)
end
diff --git a/spec/lib/gitlab/wiki_file_finder_spec.rb b/spec/lib/gitlab/wiki_file_finder_spec.rb
index aeba081f3d3..7abe92a5a2b 100644
--- a/spec/lib/gitlab/wiki_file_finder_spec.rb
+++ b/spec/lib/gitlab/wiki_file_finder_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::WikiFileFinder do
+RSpec.describe Gitlab::WikiFileFinder do
describe '#find' do
let(:project) { create(:project, :public, :wiki_repo) }
let(:wiki) { build(:project_wiki, project: project) }
diff --git a/spec/lib/gitlab/wiki_pages/front_matter_parser_spec.rb b/spec/lib/gitlab/wiki_pages/front_matter_parser_spec.rb
index 01701589e63..c78103f33f4 100644
--- a/spec/lib/gitlab/wiki_pages/front_matter_parser_spec.rb
+++ b/spec/lib/gitlab/wiki_pages/front_matter_parser_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::WikiPages::FrontMatterParser do
+RSpec.describe Gitlab::WikiPages::FrontMatterParser do
subject(:parser) { described_class.new(raw_content, gate) }
let(:content) { 'This is the content' }
diff --git a/spec/lib/gitlab/with_request_store_spec.rb b/spec/lib/gitlab/with_request_store_spec.rb
index 1ef8d986f96..353ad02fbd8 100644
--- a/spec/lib/gitlab/with_request_store_spec.rb
+++ b/spec/lib/gitlab/with_request_store_spec.rb
@@ -3,7 +3,7 @@
require 'fast_spec_helper'
require 'request_store'
-describe Gitlab::WithRequestStore do
+RSpec.describe Gitlab::WithRequestStore do
let(:fake_class) { Class.new { include Gitlab::WithRequestStore } }
subject(:object) { fake_class.new }
diff --git a/spec/lib/gitlab/workhorse_spec.rb b/spec/lib/gitlab/workhorse_spec.rb
index 53b6f461a48..5cb08ac1e76 100644
--- a/spec/lib/gitlab/workhorse_spec.rb
+++ b/spec/lib/gitlab/workhorse_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Workhorse do
+RSpec.describe Gitlab::Workhorse do
let_it_be(:project) { create(:project, :repository) }
let(:repository) { project.repository }
diff --git a/spec/lib/gitlab/x509/commit_spec.rb b/spec/lib/gitlab/x509/commit_spec.rb
index ac93609b467..a81955b995e 100644
--- a/spec/lib/gitlab/x509/commit_spec.rb
+++ b/spec/lib/gitlab/x509/commit_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe Gitlab::X509::Commit do
+RSpec.describe Gitlab::X509::Commit do
describe '#signature' do
let(:signature) { described_class.new(commit).signature }
diff --git a/spec/lib/gitlab/x509/signature_spec.rb b/spec/lib/gitlab/x509/signature_spec.rb
index cff2fd7748b..ac6f7e49fe0 100644
--- a/spec/lib/gitlab/x509/signature_spec.rb
+++ b/spec/lib/gitlab/x509/signature_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::X509::Signature do
+RSpec.describe Gitlab::X509::Signature do
let(:issuer_attributes) do
{
subject_key_identifier: X509Helpers::User1.issuer_subject_key_identifier,
diff --git a/spec/lib/gitlab/x509/tag_spec.rb b/spec/lib/gitlab/x509/tag_spec.rb
index 4bc9723bd0d..b011ea515de 100644
--- a/spec/lib/gitlab/x509/tag_spec.rb
+++ b/spec/lib/gitlab/x509/tag_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe Gitlab::X509::Tag do
+RSpec.describe Gitlab::X509::Tag do
subject(:signature) { described_class.new(tag).signature }
describe '#signature' do
diff --git a/spec/lib/gitlab/zoom_link_extractor_spec.rb b/spec/lib/gitlab/zoom_link_extractor_spec.rb
index c3d1679d031..363668fb2b5 100644
--- a/spec/lib/gitlab/zoom_link_extractor_spec.rb
+++ b/spec/lib/gitlab/zoom_link_extractor_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::ZoomLinkExtractor do
+RSpec.describe Gitlab::ZoomLinkExtractor do
describe "#links" do
using RSpec::Parameterized::TableSyntax
diff --git a/spec/lib/gitlab_danger_spec.rb b/spec/lib/gitlab_danger_spec.rb
index 8115fbca5e0..49c7a46f321 100644
--- a/spec/lib/gitlab_danger_spec.rb
+++ b/spec/lib/gitlab_danger_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GitlabDanger do
+RSpec.describe GitlabDanger do
let(:gitlab_danger_helper) { nil }
subject { described_class.new(gitlab_danger_helper) }
diff --git a/spec/lib/gitlab_spec.rb b/spec/lib/gitlab_spec.rb
index 84d072a50ec..7c2758bf27e 100644
--- a/spec/lib/gitlab_spec.rb
+++ b/spec/lib/gitlab_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab do
+RSpec.describe Gitlab do
describe '.root' do
it 'returns the root path of the app' do
expect(described_class.root).to eq(Pathname.new(File.expand_path('../..', __dir__)))
diff --git a/spec/lib/google_api/auth_spec.rb b/spec/lib/google_api/auth_spec.rb
index fa4e6288681..eeb99bfbb6c 100644
--- a/spec/lib/google_api/auth_spec.rb
+++ b/spec/lib/google_api/auth_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GoogleApi::Auth do
+RSpec.describe GoogleApi::Auth do
let(:redirect_uri) { 'http://localhost:3000/google_api/authorizations/callback' }
let(:redirect_to) { 'http://localhost:3000/namaspace/project/clusters' }
diff --git a/spec/lib/google_api/cloud_platform/client_spec.rb b/spec/lib/google_api/cloud_platform/client_spec.rb
index bd063648ca1..d217699f79d 100644
--- a/spec/lib/google_api/cloud_platform/client_spec.rb
+++ b/spec/lib/google_api/cloud_platform/client_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GoogleApi::CloudPlatform::Client do
+RSpec.describe GoogleApi::CloudPlatform::Client do
let(:token) { 'token' }
let(:client) { described_class.new(token, nil) }
let(:user_agent_options) { client.instance_eval { user_agent_header } }
diff --git a/spec/lib/grafana/client_spec.rb b/spec/lib/grafana/client_spec.rb
index 699344e940e..c233d0b8445 100644
--- a/spec/lib/grafana/client_spec.rb
+++ b/spec/lib/grafana/client_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Grafana::Client do
+RSpec.describe Grafana::Client do
let(:grafana_url) { 'https://grafanatest.com/-/grafana-project' }
let(:token) { 'test-token' }
diff --git a/spec/lib/grafana/time_window_spec.rb b/spec/lib/grafana/time_window_spec.rb
index e70861658ca..9ee65c6cf20 100644
--- a/spec/lib/grafana/time_window_spec.rb
+++ b/spec/lib/grafana/time_window_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Grafana::TimeWindow do
+RSpec.describe Grafana::TimeWindow do
let(:from) { '1552799400000' }
let(:to) { '1552828200000' }
@@ -32,7 +32,7 @@ describe Grafana::TimeWindow do
end
end
-describe Grafana::RangeWithDefaults do
+RSpec.describe Grafana::RangeWithDefaults do
let(:from) { Grafana::Timestamp.from_ms_since_epoch('1552799400000') }
let(:to) { Grafana::Timestamp.from_ms_since_epoch('1552828200000') }
@@ -78,7 +78,7 @@ describe Grafana::RangeWithDefaults do
end
end
-describe Grafana::Timestamp do
+RSpec.describe Grafana::Timestamp do
let(:timestamp) { Time.at(1552799400) }
around do |example|
diff --git a/spec/lib/grafana/validator_spec.rb b/spec/lib/grafana/validator_spec.rb
index a048a1f3470..b45749ffc95 100644
--- a/spec/lib/grafana/validator_spec.rb
+++ b/spec/lib/grafana/validator_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Grafana::Validator do
+RSpec.describe Grafana::Validator do
let(:grafana_dashboard) { Gitlab::Json.parse(fixture_file('grafana/simplified_dashboard_response.json'), symbolize_names: true) }
let(:datasource) { Gitlab::Json.parse(fixture_file('grafana/datasource_response.json'), symbolize_names: true) }
let(:panel) { grafana_dashboard[:dashboard][:panels].first }
diff --git a/spec/lib/json_web_token/hmac_token_spec.rb b/spec/lib/json_web_token/hmac_token_spec.rb
index f2cbc381967..cf7e5c54f45 100644
--- a/spec/lib/json_web_token/hmac_token_spec.rb
+++ b/spec/lib/json_web_token/hmac_token_spec.rb
@@ -3,7 +3,7 @@
require 'json'
require 'timecop'
-describe JSONWebToken::HMACToken do
+RSpec.describe JSONWebToken::HMACToken do
let(:secret) { 'shh secret squirrel' }
shared_examples 'a valid, non-expired token' do
diff --git a/spec/lib/json_web_token/rsa_token_spec.rb b/spec/lib/json_web_token/rsa_token_spec.rb
index 62fddbb97c7..f19471917c2 100644
--- a/spec/lib/json_web_token/rsa_token_spec.rb
+++ b/spec/lib/json_web_token/rsa_token_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-describe JSONWebToken::RSAToken do
+RSpec.describe JSONWebToken::RSAToken do
let(:rsa_key) do
OpenSSL::PKey::RSA.new <<-eos.strip_heredoc
-----BEGIN RSA PRIVATE KEY-----
diff --git a/spec/lib/json_web_token/token_spec.rb b/spec/lib/json_web_token/token_spec.rb
index ca587a6ebcd..6fb15ae0a9c 100644
--- a/spec/lib/json_web_token/token_spec.rb
+++ b/spec/lib/json_web_token/token_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-describe JSONWebToken::Token do
+RSpec.describe JSONWebToken::Token do
let(:token) { described_class.new }
context 'custom parameters' do
diff --git a/spec/lib/kramdown/parser/atlassian_document_format_spec.rb b/spec/lib/kramdown/parser/atlassian_document_format_spec.rb
new file mode 100644
index 00000000000..f3559503388
--- /dev/null
+++ b/spec/lib/kramdown/parser/atlassian_document_format_spec.rb
@@ -0,0 +1,77 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.context Kramdown::Parser::AtlassianDocumentFormat do
+ let_it_be(:options) { { input: 'AtlassianDocumentFormat', html_tables: true } }
+ let_it_be(:fixtures_path) { 'lib/kramdown/atlassian_document_format' }
+
+ context 'markdown render' do
+ shared_examples 'render elements to markdown' do |base_name|
+ let(:json_file) { "#{base_name}.json" }
+ let(:markdown_file) { "#{base_name}.md" }
+
+ it "renders #{base_name}" do
+ source = fixture_file(File.join(fixtures_path, json_file))
+ target = fixture_file(File.join(fixtures_path, markdown_file))
+ parser = Kramdown::Document.new(source, options)
+
+ expect(parser.to_commonmark).to eq target
+ end
+ end
+
+ it_behaves_like 'render elements to markdown', 'blockquote'
+ it_behaves_like 'render elements to markdown', 'bullet_list'
+ it_behaves_like 'render elements to markdown', 'code_block'
+ it_behaves_like 'render elements to markdown', 'emoji'
+ it_behaves_like 'render elements to markdown', 'hard_break'
+ it_behaves_like 'render elements to markdown', 'heading'
+ it_behaves_like 'render elements to markdown', 'inline_card'
+ it_behaves_like 'render elements to markdown', 'media_group'
+ it_behaves_like 'render elements to markdown', 'media_single'
+ it_behaves_like 'render elements to markdown', 'mention'
+ it_behaves_like 'render elements to markdown', 'ordered_list'
+ it_behaves_like 'render elements to markdown', 'panel'
+ it_behaves_like 'render elements to markdown', 'paragraph'
+ it_behaves_like 'render elements to markdown', 'rule'
+ it_behaves_like 'render elements to markdown', 'table'
+
+ it_behaves_like 'render elements to markdown', 'strong_em_mark'
+ it_behaves_like 'render elements to markdown', 'code_mark'
+ it_behaves_like 'render elements to markdown', 'link_mark'
+ it_behaves_like 'render elements to markdown', 'strike_sup_sub_mark'
+ it_behaves_like 'render elements to markdown', 'underline_text_color_mark'
+
+ it_behaves_like 'render elements to markdown', 'complex_document'
+
+ it 'renders header id to html' do
+ source = fixture_file(File.join(fixtures_path, 'heading.json'))
+ parser = Kramdown::Document.new(source, options)
+
+ expect(parser.to_html).to include('id="header-2"')
+ end
+
+ it 'logs an error with invalid json' do
+ source = fixture_file(File.join(fixtures_path, 'invalid_json.json'))
+
+ expect(Gitlab::AppLogger).to receive(:error).with(/Invalid Atlassian Document Format JSON/)
+ expect(Gitlab::AppLogger).to receive(:error).with(any_args)
+ expect { Kramdown::Document.new(source, options) }.to raise_error(::Kramdown::Error, /Invalid Atlassian Document Format JSON/)
+ end
+
+ it 'logs an error if no valid document node' do
+ source = fixture_file(File.join(fixtures_path, 'invalid_no_doc.json'))
+
+ expect(Gitlab::AppLogger).to receive(:error).with(/Invalid Atlassian Document Format JSON/)
+ expect(Gitlab::AppLogger).to receive(:error).with(any_args)
+ expect { Kramdown::Document.new(source, options) }.to raise_error(::Kramdown::Error, /Invalid Atlassian Document Format JSON/)
+ end
+
+ it 'invalid node gets ignored' do
+ source = fixture_file(File.join(fixtures_path, 'invalid_node_type.json'))
+ parser = Kramdown::Document.new(source, options)
+
+ expect(parser.to_commonmark).to eq "This is a second paragraph\n\n"
+ end
+ end
+end
diff --git a/spec/lib/learn_gitlab_spec.rb b/spec/lib/learn_gitlab_spec.rb
new file mode 100644
index 00000000000..abfd82999c3
--- /dev/null
+++ b/spec/lib/learn_gitlab_spec.rb
@@ -0,0 +1,61 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe LearnGitlab do
+ let_it_be(:current_user) { create(:user) }
+ let_it_be(:learn_gitlab_project) { create(:project, name: LearnGitlab::PROJECT_NAME) }
+ let_it_be(:learn_gitlab_board) { create(:board, project: learn_gitlab_project, name: LearnGitlab::BOARD_NAME) }
+ let_it_be(:learn_gitlab_label) { create(:label, project: learn_gitlab_project, name: LearnGitlab::LABEL_NAME) }
+
+ before do
+ learn_gitlab_project.add_developer(current_user)
+ end
+
+ describe '.available?' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:project, :board, :label, :expected_result) do
+ nil | nil | nil | nil
+ nil | nil | true | nil
+ nil | true | nil | nil
+ nil | true | true | nil
+ true | nil | nil | nil
+ true | nil | true | nil
+ true | true | nil | nil
+ true | true | true | true
+ end
+
+ with_them do
+ before do
+ allow_next_instance_of(described_class) do |learn_gitlab|
+ allow(learn_gitlab).to receive(:project).and_return(project)
+ allow(learn_gitlab).to receive(:board).and_return(board)
+ allow(learn_gitlab).to receive(:label).and_return(label)
+ end
+ end
+
+ subject { described_class.new(current_user).available? }
+
+ it { is_expected.to be expected_result }
+ end
+ end
+
+ describe '.project' do
+ subject { described_class.new(current_user).project }
+
+ it { is_expected.to eq learn_gitlab_project }
+ end
+
+ describe '.board' do
+ subject { described_class.new(current_user).board }
+
+ it { is_expected.to eq learn_gitlab_board }
+ end
+
+ describe '.label' do
+ subject { described_class.new(current_user).label }
+
+ it { is_expected.to eq learn_gitlab_label }
+ end
+end
diff --git a/spec/lib/marginalia_spec.rb b/spec/lib/marginalia_spec.rb
index 2f446694083..a920f598c24 100644
--- a/spec/lib/marginalia_spec.rb
+++ b/spec/lib/marginalia_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Marginalia spec' do
+RSpec.describe 'Marginalia spec' do
class MarginaliaTestController < ActionController::Base
def first_user
User.first
diff --git a/spec/lib/mattermost/client_spec.rb b/spec/lib/mattermost/client_spec.rb
index 5fe35eb5f93..32755d1103c 100644
--- a/spec/lib/mattermost/client_spec.rb
+++ b/spec/lib/mattermost/client_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Mattermost::Client do
+RSpec.describe Mattermost::Client do
let(:user) { build(:user) }
subject { described_class.new(user) }
diff --git a/spec/lib/mattermost/command_spec.rb b/spec/lib/mattermost/command_spec.rb
index f8c451a1522..26d1ec32232 100644
--- a/spec/lib/mattermost/command_spec.rb
+++ b/spec/lib/mattermost/command_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Mattermost::Command do
+RSpec.describe Mattermost::Command do
let(:params) { { 'token' => 'token', team_id: 'abc' } }
before do
diff --git a/spec/lib/mattermost/session_spec.rb b/spec/lib/mattermost/session_spec.rb
index ea12bd76c8d..5110d3cdfa3 100644
--- a/spec/lib/mattermost/session_spec.rb
+++ b/spec/lib/mattermost/session_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Mattermost::Session, type: :request do
+RSpec.describe Mattermost::Session, type: :request do
include ExclusiveLeaseHelpers
include StubRequests
diff --git a/spec/lib/mattermost/team_spec.rb b/spec/lib/mattermost/team_spec.rb
index 2823dab67c9..0870114ca28 100644
--- a/spec/lib/mattermost/team_spec.rb
+++ b/spec/lib/mattermost/team_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Mattermost::Team do
+RSpec.describe Mattermost::Team do
before do
session = Mattermost::Session.new(nil)
session.base_uri = 'http://mattermost.example.com'
diff --git a/spec/lib/microsoft_teams/activity_spec.rb b/spec/lib/microsoft_teams/activity_spec.rb
index 3fad2437f3e..d1eac7204a6 100644
--- a/spec/lib/microsoft_teams/activity_spec.rb
+++ b/spec/lib/microsoft_teams/activity_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe MicrosoftTeams::Activity do
+RSpec.describe MicrosoftTeams::Activity do
subject { described_class.new(title: 'title', subtitle: 'subtitle', text: 'text', image: 'image') }
describe '#prepare' do
diff --git a/spec/lib/microsoft_teams/notifier_spec.rb b/spec/lib/microsoft_teams/notifier_spec.rb
index 25538db159e..c35d7e8420c 100644
--- a/spec/lib/microsoft_teams/notifier_spec.rb
+++ b/spec/lib/microsoft_teams/notifier_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe MicrosoftTeams::Notifier do
+RSpec.describe MicrosoftTeams::Notifier do
subject { described_class.new(webhook_url) }
let(:webhook_url) { 'https://example.gitlab.com/'}
diff --git a/spec/lib/object_storage/direct_upload_spec.rb b/spec/lib/object_storage/direct_upload_spec.rb
index c3890c72852..1c1455e2456 100644
--- a/spec/lib/object_storage/direct_upload_spec.rb
+++ b/spec/lib/object_storage/direct_upload_spec.rb
@@ -2,10 +2,11 @@
require 'spec_helper'
-describe ObjectStorage::DirectUpload do
+RSpec.describe ObjectStorage::DirectUpload do
let(:region) { 'us-east-1' }
let(:path_style) { false }
let(:use_iam_profile) { false }
+ let(:consolidated_settings) { false }
let(:credentials) do
{
provider: 'AWS',
@@ -23,7 +24,7 @@ describe ObjectStorage::DirectUpload do
let(:object_name) { 'tmp/uploads/my-file' }
let(:maximum_size) { 1.gigabyte }
- let(:direct_upload) { described_class.new(credentials, bucket_name, object_name, has_length: has_length, maximum_size: maximum_size) }
+ let(:direct_upload) { described_class.new(credentials, bucket_name, object_name, has_length: has_length, maximum_size: maximum_size, consolidated_settings: consolidated_settings) }
before do
Fog.unmock!
@@ -60,6 +61,38 @@ describe ObjectStorage::DirectUpload do
end
end
+ describe '#get_url' do
+ subject { described_class.new(credentials, bucket_name, object_name, has_length: true) }
+
+ context 'when AWS is used' do
+ it 'calls the proper method' do
+ expect_next_instance_of(::Fog::Storage, credentials) do |connection|
+ expect(connection).to receive(:get_object_url).once
+ end
+
+ subject.get_url
+ end
+ end
+
+ context 'when Google is used' do
+ let(:credentials) do
+ {
+ provider: 'Google',
+ google_storage_access_key_id: 'GOOGLE_ACCESS_KEY_ID',
+ google_storage_secret_access_key: 'GOOGLE_SECRET_ACCESS_KEY'
+ }
+ end
+
+ it 'calls the proper method' do
+ expect_next_instance_of(::Fog::Storage, credentials) do |connection|
+ expect(connection).to receive(:get_object_https_url).once
+ end
+
+ subject.get_url
+ end
+ end
+ end
+
describe '#to_hash' do
subject { direct_upload.to_hash }
@@ -109,6 +142,14 @@ describe ObjectStorage::DirectUpload do
expect(subject[:UseWorkhorseClient]).to eq(use_iam_profile)
end
end
+
+ context 'when consolidated settings are used' do
+ let(:consolidated_settings) { true }
+
+ it 'enables the Workhorse client' do
+ expect(subject[:UseWorkhorseClient]).to be true
+ end
+ end
end
shared_examples 'a valid Google upload' do
diff --git a/spec/lib/omni_auth/strategies/jwt_spec.rb b/spec/lib/omni_auth/strategies/jwt_spec.rb
index 302329cf198..0f4528d4fbe 100644
--- a/spec/lib/omni_auth/strategies/jwt_spec.rb
+++ b/spec/lib/omni_auth/strategies/jwt_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe OmniAuth::Strategies::Jwt do
+RSpec.describe OmniAuth::Strategies::Jwt do
include Rack::Test::Methods
include DeviseHelpers
diff --git a/spec/lib/pager_duty/webhook_payload_parser_spec.rb b/spec/lib/pager_duty/webhook_payload_parser_spec.rb
new file mode 100644
index 00000000000..0010165318d
--- /dev/null
+++ b/spec/lib/pager_duty/webhook_payload_parser_spec.rb
@@ -0,0 +1,80 @@
+# frozen_string_literal: true
+
+require 'fast_spec_helper'
+
+RSpec.describe PagerDuty::WebhookPayloadParser do
+ describe '.call' do
+ let(:fixture_file) do
+ File.read(File.join(File.dirname(__FILE__), '../../fixtures/pager_duty/webhook_incident_trigger.json'))
+ end
+
+ subject(:parse) { described_class.call(payload) }
+
+ context 'when payload is a correct PagerDuty payload' do
+ let(:payload) { Gitlab::Json.parse(fixture_file) }
+
+ it 'returns parsed payload' do
+ is_expected.to eq(
+ [
+ {
+ 'event' => 'incident.trigger',
+ 'incident' => {
+ 'url' => 'https://webdemo.pagerduty.com/incidents/PRORDTY',
+ 'incident_number' => 33,
+ 'title' => 'My new incident',
+ 'status' => 'triggered',
+ 'created_at' => '2017-09-26T15:14:36Z',
+ 'urgency' => 'high',
+ 'incident_key' => nil,
+ 'assignees' => [{
+ 'summary' => 'Laura Haley',
+ 'url' => 'https://webdemo.pagerduty.com/users/P553OPV'
+ }],
+ 'impacted_services' => [{
+ 'summary' => 'Production XDB Cluster',
+ 'url' => 'https://webdemo.pagerduty.com/services/PN49J75'
+ }]
+ }
+ }
+ ]
+ )
+ end
+
+ context 'when assignments summary and html_url are blank' do
+ before do
+ payload['messages'].each do |m|
+ m['incident']['assignments'] = [{ 'assignee' => { 'summary' => '', 'html_url' => '' } }]
+ end
+ end
+
+ it 'returns parsed payload with blank assignees' do
+ assignees = parse.map { |events| events['incident'].slice('assignees') }
+
+ expect(assignees).to eq([{ 'assignees' => [] }])
+ end
+ end
+
+ context 'when impacted_services summary and html_url are blank' do
+ before do
+ payload['messages'].each do |m|
+ m['incident']['impacted_services'] = [{ 'summary' => '', 'html_url' => '' }]
+ end
+ end
+
+ it 'returns parsed payload with blank assignees' do
+ assignees = parse.map { |events| events['incident'].slice('impacted_services') }
+
+ expect(assignees).to eq([{ 'impacted_services' => [] }])
+ end
+ end
+ end
+
+ context 'when payload has no incidents' do
+ let(:payload) { { 'messages' => [{ 'event' => 'incident.trigger' }] } }
+
+ it 'returns payload with blank incidents' do
+ is_expected.to eq([{ 'event' => 'incident.trigger', 'incident' => {} }])
+ end
+ end
+ end
+end
diff --git a/spec/lib/peek/views/bullet_detailed_spec.rb b/spec/lib/peek/views/bullet_detailed_spec.rb
index a482cadc7db..ec2f798a320 100644
--- a/spec/lib/peek/views/bullet_detailed_spec.rb
+++ b/spec/lib/peek/views/bullet_detailed_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Peek::Views::BulletDetailed do
+RSpec.describe Peek::Views::BulletDetailed do
subject { described_class.new }
before do
diff --git a/spec/lib/peek/views/detailed_view_spec.rb b/spec/lib/peek/views/detailed_view_spec.rb
index d8660a55ea9..8d6d9a829ef 100644
--- a/spec/lib/peek/views/detailed_view_spec.rb
+++ b/spec/lib/peek/views/detailed_view_spec.rb
@@ -2,7 +2,7 @@
require 'fast_spec_helper'
-describe Peek::Views::DetailedView, :request_store do
+RSpec.describe Peek::Views::DetailedView, :request_store do
context 'when a class defines thresholds' do
let(:threshold_view) do
Class.new(described_class) do
diff --git a/spec/lib/peek/views/redis_detailed_spec.rb b/spec/lib/peek/views/redis_detailed_spec.rb
index a270c006a43..a757af50dcb 100644
--- a/spec/lib/peek/views/redis_detailed_spec.rb
+++ b/spec/lib/peek/views/redis_detailed_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Peek::Views::RedisDetailed, :request_store do
+RSpec.describe Peek::Views::RedisDetailed, :request_store do
subject { described_class.new }
using RSpec::Parameterized::TableSyntax
diff --git a/spec/lib/peek/views/rugged_spec.rb b/spec/lib/peek/views/rugged_spec.rb
index 39968afed39..31418b5fc81 100644
--- a/spec/lib/peek/views/rugged_spec.rb
+++ b/spec/lib/peek/views/rugged_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Peek::Views::Rugged, :request_store do
+RSpec.describe Peek::Views::Rugged, :request_store do
subject { described_class.new }
let(:project) { create(:project) }
diff --git a/spec/lib/product_analytics/event_params_spec.rb b/spec/lib/product_analytics/event_params_spec.rb
new file mode 100644
index 00000000000..d6c098599d6
--- /dev/null
+++ b/spec/lib/product_analytics/event_params_spec.rb
@@ -0,0 +1,54 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe ProductAnalytics::EventParams do
+ describe '.parse_event_params' do
+ subject { described_class.parse_event_params(raw_event) }
+
+ let(:raw_event) { Gitlab::Json.parse(fixture_file('product_analytics/event.json')) }
+
+ it 'extracts all params from raw event' do
+ expected_params = {
+ project_id: '1',
+ platform: 'web',
+ name_tracker: 'sp',
+ v_tracker: 'js-2.14.0',
+ event_id: 'fbf14096-74ee-47e4-883c-8a0d6cb72e37',
+ domain_userid: '79543c31-cfc3-4479-a737-fafb9333c8ba',
+ domain_sessionid: '54f6d3f3-f4f9-4fdc-87e0-a2c775234c1b',
+ domain_sessionidx: 4,
+ page_url: 'http://example.com/products/1',
+ page_referrer: 'http://example.com/products/1',
+ br_lang: 'en-US',
+ br_cookies: true,
+ os_timezone: 'America/Los_Angeles',
+ doc_charset: 'UTF-8'
+ }
+
+ expect(subject).to include(expected_params)
+ end
+ end
+
+ describe '.has_required_params?' do
+ subject { described_class.has_required_params?(params) }
+
+ context 'aid and eid are present' do
+ let(:params) { { 'aid' => 1, 'eid' => 2 } }
+
+ it { expect(subject).to be_truthy }
+ end
+
+ context 'aid and eid are missing' do
+ let(:params) { {} }
+
+ it { expect(subject).to be_falsey }
+ end
+
+ context 'eid is missing' do
+ let(:params) { { 'aid' => 1 } }
+
+ it { expect(subject).to be_falsey }
+ end
+ end
+end
diff --git a/spec/lib/prometheus/cleanup_multiproc_dir_service_spec.rb b/spec/lib/prometheus/cleanup_multiproc_dir_service_spec.rb
index c7302a1a656..db77a5d42d8 100644
--- a/spec/lib/prometheus/cleanup_multiproc_dir_service_spec.rb
+++ b/spec/lib/prometheus/cleanup_multiproc_dir_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Prometheus::CleanupMultiprocDirService do
+RSpec.describe Prometheus::CleanupMultiprocDirService do
describe '.call' do
subject { described_class.new.execute }
diff --git a/spec/lib/prometheus/pid_provider_spec.rb b/spec/lib/prometheus/pid_provider_spec.rb
index 5a17f25f144..f1d7f2ffff5 100644
--- a/spec/lib/prometheus/pid_provider_spec.rb
+++ b/spec/lib/prometheus/pid_provider_spec.rb
@@ -2,7 +2,7 @@
require 'fast_spec_helper'
-describe Prometheus::PidProvider do
+RSpec.describe Prometheus::PidProvider do
describe '.worker_id' do
subject { described_class.worker_id }
diff --git a/spec/lib/quality/helm3_client_spec.rb b/spec/lib/quality/helm3_client_spec.rb
deleted file mode 100644
index 1144ee9369d..00000000000
--- a/spec/lib/quality/helm3_client_spec.rb
+++ /dev/null
@@ -1,133 +0,0 @@
-# frozen_string_literal: true
-
-require 'fast_spec_helper'
-
-RSpec.describe Quality::Helm3Client do
- let(:namespace) { 'review-apps-ee' }
- let(:release_name) { 'my-release' }
- let(:raw_helm_list_page1) do
- <<~OUTPUT
- [
- {"name":"review-qa-60-reor-1mugd1","namespace":"#{namespace}","revision":1,"updated":"2020-04-03 17:27:10.245952 +0800 +08","status":"failed","chart":"gitlab-1.1.3","app_version":"12.9.2"},
- {"name":"review-7846-fix-s-261vd6","namespace":"#{namespace}","revision":2,"updated":"2020-04-02 17:27:12.245952 +0800 +08","status":"deployed","chart":"gitlab-1.1.3","app_version":"12.9.2"},
- {"name":"review-7867-snowp-lzo3iy","namespace":"#{namespace}","revision":1,"updated":"2020-04-02 15:27:12.245952 +0800 +08","status":"deployed","chart":"gitlab-1.1.3","app_version":"12.9.1"},
- {"name":"review-6709-group-2pzeec","namespace":"#{namespace}","revision":2,"updated":"2020-04-01 21:27:12.245952 +0800 +08","status":"failed","chart":"gitlab-1.1.3","app_version":"12.9.1"}
- ]
- OUTPUT
- end
- let(:raw_helm_list_page2) do
- <<~OUTPUT
- [
- {"name":"review-6709-group-t40qbv","namespace":"#{namespace}","revision":2,"updated":"2020-04-01 11:27:12.245952 +0800 +08","status":"deployed","chart":"gitlab-1.1.3","app_version":"12.9.1"}
- ]
- OUTPUT
- end
- let(:raw_helm_list_empty) do
- <<~OUTPUT
- []
- OUTPUT
- end
-
- subject { described_class.new(namespace: namespace) }
-
- describe '#releases' do
- it 'raises an error if the Helm command fails' do
- expect(Gitlab::Popen).to receive(:popen_with_detail)
- .with([%(helm list --namespace "#{namespace}" --max 256 --offset 0 --output json)])
- .and_return(Gitlab::Popen::Result.new([], '', '', double(success?: false)))
-
- expect { subject.releases.to_a }.to raise_error(described_class::CommandFailedError)
- end
-
- it 'calls helm list with default arguments' do
- expect(Gitlab::Popen).to receive(:popen_with_detail)
- .with([%(helm list --namespace "#{namespace}" --max 256 --offset 0 --output json)])
- .and_return(Gitlab::Popen::Result.new([], '', '', double(success?: true)))
-
- subject.releases.to_a
- end
-
- it 'calls helm list with extra arguments' do
- expect(Gitlab::Popen).to receive(:popen_with_detail)
- .with([%(helm list --namespace "#{namespace}" --max 256 --offset 0 --output json --deployed)])
- .and_return(Gitlab::Popen::Result.new([], '', '', double(success?: true)))
-
- subject.releases(args: ['--deployed']).to_a
- end
-
- it 'returns a list of Release objects' do
- expect(Gitlab::Popen).to receive(:popen_with_detail)
- .with([%(helm list --namespace "#{namespace}" --max 256 --offset 0 --output json --deployed)])
- .and_return(Gitlab::Popen::Result.new([], raw_helm_list_page2, '', double(success?: true)))
- expect(Gitlab::Popen).to receive(:popen_with_detail).ordered
- .and_return(Gitlab::Popen::Result.new([], raw_helm_list_empty, '', double(success?: true)))
-
- releases = subject.releases(args: ['--deployed']).to_a
-
- expect(releases.size).to eq(1)
- expect(releases[0]).to have_attributes(
- name: 'review-6709-group-t40qbv',
- revision: 2,
- last_update: Time.parse('2020-04-01 11:27:12.245952 +0800 +08'),
- status: 'deployed',
- chart: 'gitlab-1.1.3',
- app_version: '12.9.1',
- namespace: namespace
- )
- end
-
- it 'automatically paginates releases' do
- expect(Gitlab::Popen).to receive(:popen_with_detail).ordered
- .with([%(helm list --namespace "#{namespace}" --max 256 --offset 0 --output json)])
- .and_return(Gitlab::Popen::Result.new([], raw_helm_list_page1, '', double(success?: true)))
- expect(Gitlab::Popen).to receive(:popen_with_detail).ordered
- .with([%(helm list --namespace "#{namespace}" --max 256 --offset 256 --output json)])
- .and_return(Gitlab::Popen::Result.new([], raw_helm_list_page2, '', double(success?: true)))
- expect(Gitlab::Popen).to receive(:popen_with_detail).ordered
- .with([%(helm list --namespace "#{namespace}" --max 256 --offset 512 --output json)])
- .and_return(Gitlab::Popen::Result.new([], raw_helm_list_empty, '', double(success?: true)))
- releases = subject.releases.to_a
-
- expect(releases.size).to eq(5)
- expect(releases.last.name).to eq('review-6709-group-t40qbv')
- end
- end
-
- describe '#delete' do
- it 'raises an error if the Helm command fails' do
- expect(Gitlab::Popen).to receive(:popen_with_detail)
- .with([%(helm uninstall --namespace "#{namespace}" #{release_name})])
- .and_return(Gitlab::Popen::Result.new([], '', '', double(success?: false)))
-
- expect { subject.delete(release_name: release_name) }.to raise_error(described_class::CommandFailedError)
- end
-
- it 'calls helm uninstall with default arguments' do
- expect(Gitlab::Popen).to receive(:popen_with_detail)
- .with([%(helm uninstall --namespace "#{namespace}" #{release_name})])
- .and_return(Gitlab::Popen::Result.new([], '', '', double(success?: true)))
-
- expect(subject.delete(release_name: release_name)).to eq('')
- end
-
- context 'with multiple release names' do
- let(:release_name) { %w[my-release my-release-2] }
-
- it 'raises an error if the Helm command fails' do
- expect(Gitlab::Popen).to receive(:popen_with_detail)
- .with([%(helm uninstall --namespace "#{namespace}" #{release_name.join(' ')})])
- .and_return(Gitlab::Popen::Result.new([], '', '', double(success?: false)))
-
- expect { subject.delete(release_name: release_name) }.to raise_error(described_class::CommandFailedError)
- end
-
- it 'calls helm uninstall with multiple release names' do
- expect(Gitlab::Popen).to receive(:popen_with_detail)
- .with([%(helm uninstall --namespace "#{namespace}" #{release_name.join(' ')})])
- .and_return(Gitlab::Popen::Result.new([], '', '', double(success?: true)))
-
- expect(subject.delete(release_name: release_name)).to eq('')
- end
- end
- end
-end
diff --git a/spec/lib/quality/kubernetes_client_spec.rb b/spec/lib/quality/kubernetes_client_spec.rb
deleted file mode 100644
index 1cfee5200f3..00000000000
--- a/spec/lib/quality/kubernetes_client_spec.rb
+++ /dev/null
@@ -1,111 +0,0 @@
-# frozen_string_literal: true
-
-require 'fast_spec_helper'
-
-RSpec.describe Quality::KubernetesClient do
- let(:namespace) { 'review-apps-ee' }
- let(:release_name) { 'my-release' }
- let(:pod_for_release) { "pod-my-release-abcd" }
- let(:raw_resource_names_str) { "NAME\nfoo\n#{pod_for_release}\nbar" }
- let(:raw_resource_names) { raw_resource_names_str.lines.map(&:strip) }
-
- subject { described_class.new(namespace: namespace) }
-
- describe 'RESOURCE_LIST' do
- it 'returns the correct list of resources separated by commas' do
- expect(described_class::RESOURCE_LIST).to eq('ingress,svc,pdb,hpa,deploy,statefulset,job,pod,secret,configmap,pvc,secret,clusterrole,clusterrolebinding,role,rolebinding,sa,crd')
- end
- end
-
- describe '#cleanup' do
- before do
- allow(subject).to receive(:raw_resource_names).and_return(raw_resource_names)
- end
-
- it 'raises an error if the Kubernetes command fails' do
- expect(Gitlab::Popen).to receive(:popen_with_detail)
- .with(["kubectl delete #{described_class::RESOURCE_LIST} " +
- %(--namespace "#{namespace}" --now --ignore-not-found --include-uninitialized --wait=true -l release="#{release_name}")])
- .and_return(Gitlab::Popen::Result.new([], '', '', double(success?: false)))
-
- expect { subject.cleanup(release_name: release_name) }.to raise_error(described_class::CommandFailedError)
- end
-
- it 'calls kubectl with the correct arguments' do
- expect(Gitlab::Popen).to receive(:popen_with_detail)
- .with(["kubectl delete #{described_class::RESOURCE_LIST} " +
- %(--namespace "#{namespace}" --now --ignore-not-found --include-uninitialized --wait=true -l release="#{release_name}")])
- .and_return(Gitlab::Popen::Result.new([], '', '', double(success?: true)))
-
- expect(Gitlab::Popen).to receive(:popen_with_detail)
- .with([%(kubectl delete --namespace "#{namespace}" --ignore-not-found #{pod_for_release})])
- .and_return(Gitlab::Popen::Result.new([], '', '', double(success?: true)))
-
- # We're not verifying the output here, just silencing it
- expect { subject.cleanup(release_name: release_name) }.to output.to_stdout
- end
-
- context 'with multiple releases' do
- let(:release_name) { %w[my-release my-release-2] }
-
- it 'raises an error if the Kubernetes command fails' do
- expect(Gitlab::Popen).to receive(:popen_with_detail)
- .with(["kubectl delete #{described_class::RESOURCE_LIST} " +
- %(--namespace "#{namespace}" --now --ignore-not-found --include-uninitialized --wait=true -l 'release in (#{release_name.join(', ')})')])
- .and_return(Gitlab::Popen::Result.new([], '', '', double(success?: false)))
-
- expect { subject.cleanup(release_name: release_name) }.to raise_error(described_class::CommandFailedError)
- end
-
- it 'calls kubectl with the correct arguments' do
- expect(Gitlab::Popen).to receive(:popen_with_detail)
- .with(["kubectl delete #{described_class::RESOURCE_LIST} " +
- %(--namespace "#{namespace}" --now --ignore-not-found --include-uninitialized --wait=true -l 'release in (#{release_name.join(', ')})')])
- .and_return(Gitlab::Popen::Result.new([], '', '', double(success?: true)))
-
- expect(Gitlab::Popen).to receive(:popen_with_detail)
- .with([%(kubectl delete --namespace "#{namespace}" --ignore-not-found #{pod_for_release})])
- .and_return(Gitlab::Popen::Result.new([], '', '', double(success?: true)))
-
- # We're not verifying the output here, just silencing it
- expect { subject.cleanup(release_name: release_name) }.to output.to_stdout
- end
- end
-
- context 'with `wait: false`' do
- it 'raises an error if the Kubernetes command fails' do
- expect(Gitlab::Popen).to receive(:popen_with_detail)
- .with(["kubectl delete #{described_class::RESOURCE_LIST} " +
- %(--namespace "#{namespace}" --now --ignore-not-found --include-uninitialized --wait=false -l release="#{release_name}")])
- .and_return(Gitlab::Popen::Result.new([], '', '', double(success?: false)))
-
- expect { subject.cleanup(release_name: release_name, wait: false) }.to raise_error(described_class::CommandFailedError)
- end
-
- it 'calls kubectl with the correct arguments' do
- expect(Gitlab::Popen).to receive(:popen_with_detail)
- .with(["kubectl delete #{described_class::RESOURCE_LIST} " +
- %(--namespace "#{namespace}" --now --ignore-not-found --include-uninitialized --wait=false -l release="#{release_name}")])
- .and_return(Gitlab::Popen::Result.new([], '', '', double(success?: true)))
-
- expect(Gitlab::Popen).to receive(:popen_with_detail)
- .with([%(kubectl delete --namespace "#{namespace}" --ignore-not-found #{pod_for_release})])
- .and_return(Gitlab::Popen::Result.new([], '', '', double(success?: true)))
-
- # We're not verifying the output here, just silencing it
- expect { subject.cleanup(release_name: release_name, wait: false) }.to output.to_stdout
- end
- end
- end
-
- describe '#raw_resource_names' do
- it 'calls kubectl to retrieve the resource names' do
- expect(Gitlab::Popen).to receive(:popen_with_detail)
- .with(["kubectl get #{described_class::RESOURCE_LIST} " +
- %(--namespace "#{namespace}" -o name)])
- .and_return(Gitlab::Popen::Result.new([], raw_resource_names_str, '', double(success?: true)))
-
- expect(subject.__send__(:raw_resource_names)).to eq(raw_resource_names)
- end
- end
-end
diff --git a/spec/lib/quality/test_level_spec.rb b/spec/lib/quality/test_level_spec.rb
index ad29c80b07a..0b113e8b63a 100644
--- a/spec/lib/quality/test_level_spec.rb
+++ b/spec/lib/quality/test_level_spec.rb
@@ -28,7 +28,7 @@ RSpec.describe Quality::TestLevel do
context 'when level is migration' do
it 'returns a pattern' do
expect(subject.pattern(:migration))
- .to eq("spec/{migrations}{,/**/}*_spec.rb")
+ .to eq("spec/{migrations,lib/gitlab/background_migration,lib/ee/gitlab/background_migration}{,/**/}*_spec.rb")
end
end
@@ -96,7 +96,7 @@ RSpec.describe Quality::TestLevel do
context 'when level is migration' do
it 'returns a regexp' do
expect(subject.regexp(:migration))
- .to eq(%r{spec/(migrations)})
+ .to eq(%r{spec/(migrations|lib/gitlab/background_migration|lib/ee/gitlab/background_migration)})
end
end
diff --git a/spec/lib/rspec_flaky/config_spec.rb b/spec/lib/rspec_flaky/config_spec.rb
index 13b2219267b..6b148599b67 100644
--- a/spec/lib/rspec_flaky/config_spec.rb
+++ b/spec/lib/rspec_flaky/config_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe RspecFlaky::Config, :aggregate_failures do
+RSpec.describe RspecFlaky::Config, :aggregate_failures do
before do
# Stub these env variables otherwise specs don't behave the same on the CI
stub_env('FLAKY_RSPEC_GENERATE_REPORT', nil)
diff --git a/spec/lib/rspec_flaky/example_spec.rb b/spec/lib/rspec_flaky/example_spec.rb
index 4679dd818db..aaf5ddc6f74 100644
--- a/spec/lib/rspec_flaky/example_spec.rb
+++ b/spec/lib/rspec_flaky/example_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe RspecFlaky::Example do
+RSpec.describe RspecFlaky::Example do
let(:example_attrs) do
{
id: 'spec/foo/bar_spec.rb:2',
diff --git a/spec/lib/rspec_flaky/flaky_example_spec.rb b/spec/lib/rspec_flaky/flaky_example_spec.rb
index d4a1d6c882a..8ac323475d6 100644
--- a/spec/lib/rspec_flaky/flaky_example_spec.rb
+++ b/spec/lib/rspec_flaky/flaky_example_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe RspecFlaky::FlakyExample, :aggregate_failures do
+RSpec.describe RspecFlaky::FlakyExample, :aggregate_failures do
let(:flaky_example_attrs) do
{
example_id: 'spec/foo/bar_spec.rb:2',
diff --git a/spec/lib/rspec_flaky/flaky_examples_collection_spec.rb b/spec/lib/rspec_flaky/flaky_examples_collection_spec.rb
index 2e224cda61b..5718d8211af 100644
--- a/spec/lib/rspec_flaky/flaky_examples_collection_spec.rb
+++ b/spec/lib/rspec_flaky/flaky_examples_collection_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe RspecFlaky::FlakyExamplesCollection, :aggregate_failures do
+RSpec.describe RspecFlaky::FlakyExamplesCollection, :aggregate_failures do
let(:collection_hash) do
{
a: { example_id: 'spec/foo/bar_spec.rb:2' },
diff --git a/spec/lib/rspec_flaky/listener_spec.rb b/spec/lib/rspec_flaky/listener_spec.rb
index 44b8d99b74f..2438ae171d3 100644
--- a/spec/lib/rspec_flaky/listener_spec.rb
+++ b/spec/lib/rspec_flaky/listener_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe RspecFlaky::Listener, :aggregate_failures do
+RSpec.describe RspecFlaky::Listener, :aggregate_failures do
let(:already_flaky_example_uid) { '6e869794f4cfd2badd93eb68719371d1' }
let(:suite_flaky_example_report) do
{
diff --git a/spec/lib/rspec_flaky/report_spec.rb b/spec/lib/rspec_flaky/report_spec.rb
index 37330f39e1c..e735329a8a3 100644
--- a/spec/lib/rspec_flaky/report_spec.rb
+++ b/spec/lib/rspec_flaky/report_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe RspecFlaky::Report, :aggregate_failures do
+RSpec.describe RspecFlaky::Report, :aggregate_failures do
let(:thirty_one_days) { 3600 * 24 * 31 }
let(:collection_hash) do
{
diff --git a/spec/lib/safe_zip/entry_spec.rb b/spec/lib/safe_zip/entry_spec.rb
index be3d46917ee..9929b8073a0 100644
--- a/spec/lib/safe_zip/entry_spec.rb
+++ b/spec/lib/safe_zip/entry_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe SafeZip::Entry do
+RSpec.describe SafeZip::Entry do
let(:target_path) { Dir.mktmpdir('safe-zip') }
let(:directories) { %w(public folder/with/subfolder) }
let(:params) { SafeZip::ExtractParams.new(directories: directories, to: target_path) }
diff --git a/spec/lib/safe_zip/extract_params_spec.rb b/spec/lib/safe_zip/extract_params_spec.rb
index f66d3de89ee..880c4358663 100644
--- a/spec/lib/safe_zip/extract_params_spec.rb
+++ b/spec/lib/safe_zip/extract_params_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe SafeZip::ExtractParams do
+RSpec.describe SafeZip::ExtractParams do
let(:target_path) { Dir.mktmpdir("safe-zip") }
let(:params) { described_class.new(directories: directories, to: target_path) }
let(:directories) { %w(public folder/with/subfolder) }
diff --git a/spec/lib/safe_zip/extract_spec.rb b/spec/lib/safe_zip/extract_spec.rb
index d388135c3fb..30b7e1cdd2c 100644
--- a/spec/lib/safe_zip/extract_spec.rb
+++ b/spec/lib/safe_zip/extract_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe SafeZip::Extract do
+RSpec.describe SafeZip::Extract do
let(:target_path) { Dir.mktmpdir('safe-zip') }
let(:directories) { %w(public) }
let(:object) { described_class.new(archive) }
diff --git a/spec/lib/sentry/api_urls_spec.rb b/spec/lib/sentry/api_urls_spec.rb
index 78455f8d51f..d56b4397e1c 100644
--- a/spec/lib/sentry/api_urls_spec.rb
+++ b/spec/lib/sentry/api_urls_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Sentry::ApiUrls do
+RSpec.describe Sentry::ApiUrls do
let(:sentry_url) { 'https://sentrytest.gitlab.com/api/0/projects/sentry-org/sentry-project/' }
let(:token) { 'test-token' }
let(:issue_id) { '123456' }
diff --git a/spec/lib/sentry/client/event_spec.rb b/spec/lib/sentry/client/event_spec.rb
index 58891895bfa..af1e28d09bb 100644
--- a/spec/lib/sentry/client/event_spec.rb
+++ b/spec/lib/sentry/client/event_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Sentry::Client do
+RSpec.describe Sentry::Client do
include SentryClientHelpers
let(:sentry_url) { 'https://sentrytest.gitlab.com/api/0/projects/sentry-org/sentry-project' }
diff --git a/spec/lib/sentry/client/issue_link_spec.rb b/spec/lib/sentry/client/issue_link_spec.rb
index 293937f6100..fe3abe7cb23 100644
--- a/spec/lib/sentry/client/issue_link_spec.rb
+++ b/spec/lib/sentry/client/issue_link_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Sentry::Client::IssueLink do
+RSpec.describe Sentry::Client::IssueLink do
include SentryClientHelpers
let_it_be(:sentry_url) { 'https://sentrytest.gitlab.com/api/0/projects/sentry-org/sentry-project' }
diff --git a/spec/lib/sentry/client/issue_spec.rb b/spec/lib/sentry/client/issue_spec.rb
index b683ad6d4a9..a6279aeadd2 100644
--- a/spec/lib/sentry/client/issue_spec.rb
+++ b/spec/lib/sentry/client/issue_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Sentry::Client::Issue do
+RSpec.describe Sentry::Client::Issue do
include SentryClientHelpers
let(:token) { 'test-token' }
@@ -234,6 +234,7 @@ describe Sentry::Client::Issue do
:first_release_short_version | [:firstRelease, :shortVersion]
:last_release_short_version | [:lastRelease, :shortVersion]
:first_release_version | [:firstRelease, :version]
+ :last_release_version | [:lastRelease, :version]
end
with_them do
diff --git a/spec/lib/sentry/client/projects_spec.rb b/spec/lib/sentry/client/projects_spec.rb
index 1b5bbb8f81a..ea2c5ccb81e 100644
--- a/spec/lib/sentry/client/projects_spec.rb
+++ b/spec/lib/sentry/client/projects_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Sentry::Client::Projects do
+RSpec.describe Sentry::Client::Projects do
include SentryClientHelpers
let(:sentry_url) { 'https://sentrytest.gitlab.com/api/0/projects/sentry-org/sentry-project' }
diff --git a/spec/lib/sentry/client/repo_spec.rb b/spec/lib/sentry/client/repo_spec.rb
index 524dca8dcf6..956c0b6eee1 100644
--- a/spec/lib/sentry/client/repo_spec.rb
+++ b/spec/lib/sentry/client/repo_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Sentry::Client::Repo do
+RSpec.describe Sentry::Client::Repo do
include SentryClientHelpers
let(:sentry_url) { 'https://sentrytest.gitlab.com/api/0/projects/sentry-org/sentry-project' }
diff --git a/spec/lib/sentry/client_spec.rb b/spec/lib/sentry/client_spec.rb
index e2da4564ca1..cddcb6e98fa 100644
--- a/spec/lib/sentry/client_spec.rb
+++ b/spec/lib/sentry/client_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Sentry::Client do
+RSpec.describe Sentry::Client do
let(:sentry_url) { 'https://sentrytest.gitlab.com/api/0/projects/sentry-org/sentry-project' }
let(:token) { 'test-token' }
diff --git a/spec/lib/sentry/pagination_parser_spec.rb b/spec/lib/sentry/pagination_parser_spec.rb
index 1be6f9f4163..c4ed24827bb 100644
--- a/spec/lib/sentry/pagination_parser_spec.rb
+++ b/spec/lib/sentry/pagination_parser_spec.rb
@@ -1,11 +1,8 @@
# frozen_string_literal: true
require 'fast_spec_helper'
-require 'support/helpers/fixture_helpers'
-
-describe Sentry::PaginationParser do
- include FixtureHelpers
+RSpec.describe Sentry::PaginationParser do
describe '.parse' do
subject { described_class.parse(headers) }
diff --git a/spec/lib/serializers/json_spec.rb b/spec/lib/serializers/json_spec.rb
index dfe85d3f362..7054f98a719 100644
--- a/spec/lib/serializers/json_spec.rb
+++ b/spec/lib/serializers/json_spec.rb
@@ -2,7 +2,7 @@
require 'fast_spec_helper'
-describe Serializers::JSON do
+RSpec.describe Serializers::JSON do
describe '.dump' do
let(:obj) { { key: "value" } }
diff --git a/spec/lib/system_check/app/authorized_keys_permission_check_spec.rb b/spec/lib/system_check/app/authorized_keys_permission_check_spec.rb
index 1a8123c3f0a..a5a3105c125 100644
--- a/spec/lib/system_check/app/authorized_keys_permission_check_spec.rb
+++ b/spec/lib/system_check/app/authorized_keys_permission_check_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe SystemCheck::App::AuthorizedKeysPermissionCheck do
+RSpec.describe SystemCheck::App::AuthorizedKeysPermissionCheck do
subject(:system_check) { described_class.new }
describe '#skip?' do
diff --git a/spec/lib/system_check/app/git_user_default_ssh_config_check_spec.rb b/spec/lib/system_check/app/git_user_default_ssh_config_check_spec.rb
index f132f608ab6..2c996635c36 100644
--- a/spec/lib/system_check/app/git_user_default_ssh_config_check_spec.rb
+++ b/spec/lib/system_check/app/git_user_default_ssh_config_check_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe SystemCheck::App::GitUserDefaultSSHConfigCheck do
+RSpec.describe SystemCheck::App::GitUserDefaultSSHConfigCheck do
let(:username) { '_this_user_will_not_exist_unless_it_is_stubbed' }
let(:base_dir) { Dir.mktmpdir }
let(:home_dir) { File.join(base_dir, "/var/lib/#{username}") }
diff --git a/spec/lib/system_check/app/hashed_storage_all_projects_check_spec.rb b/spec/lib/system_check/app/hashed_storage_all_projects_check_spec.rb
index e5e7f6a4450..14bf9d61ab5 100644
--- a/spec/lib/system_check/app/hashed_storage_all_projects_check_spec.rb
+++ b/spec/lib/system_check/app/hashed_storage_all_projects_check_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require 'rake_helper'
-describe SystemCheck::App::HashedStorageAllProjectsCheck do
+RSpec.describe SystemCheck::App::HashedStorageAllProjectsCheck do
before do
silence_output
end
diff --git a/spec/lib/system_check/app/hashed_storage_enabled_check_spec.rb b/spec/lib/system_check/app/hashed_storage_enabled_check_spec.rb
index d5a0014b791..32a2f409858 100644
--- a/spec/lib/system_check/app/hashed_storage_enabled_check_spec.rb
+++ b/spec/lib/system_check/app/hashed_storage_enabled_check_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require 'rake_helper'
-describe SystemCheck::App::HashedStorageEnabledCheck do
+RSpec.describe SystemCheck::App::HashedStorageEnabledCheck do
before do
silence_output
end
diff --git a/spec/lib/system_check/base_check_spec.rb b/spec/lib/system_check/base_check_spec.rb
index ccb7b483bdc..59b2fc519ae 100644
--- a/spec/lib/system_check/base_check_spec.rb
+++ b/spec/lib/system_check/base_check_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe SystemCheck::BaseCheck do
+RSpec.describe SystemCheck::BaseCheck do
context 'helpers on instance level' do
it 'responds to SystemCheck::Helpers methods' do
expect(subject).to respond_to :fix_and_rerun, :for_more_information, :see_installation_guide_section,
diff --git a/spec/lib/system_check/orphans/namespace_check_spec.rb b/spec/lib/system_check/orphans/namespace_check_spec.rb
index f7491e40438..795dfde9029 100644
--- a/spec/lib/system_check/orphans/namespace_check_spec.rb
+++ b/spec/lib/system_check/orphans/namespace_check_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require 'rake_helper'
-describe SystemCheck::Orphans::NamespaceCheck do
+RSpec.describe SystemCheck::Orphans::NamespaceCheck do
let(:storages) { Gitlab.config.repositories.storages.reject { |key, _| key.eql? 'broken' } }
before do
diff --git a/spec/lib/system_check/orphans/repository_check_spec.rb b/spec/lib/system_check/orphans/repository_check_spec.rb
index a5e06f30e75..2ab30f4802d 100644
--- a/spec/lib/system_check/orphans/repository_check_spec.rb
+++ b/spec/lib/system_check/orphans/repository_check_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require 'rake_helper'
-describe SystemCheck::Orphans::RepositoryCheck do
+RSpec.describe SystemCheck::Orphans::RepositoryCheck do
let(:storages) { Gitlab.config.repositories.storages.reject { |key, _| key.eql? 'broken' } }
before do
diff --git a/spec/lib/system_check/simple_executor_spec.rb b/spec/lib/system_check/simple_executor_spec.rb
index 58f3a7df197..c9a09d86e32 100644
--- a/spec/lib/system_check/simple_executor_spec.rb
+++ b/spec/lib/system_check/simple_executor_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require 'rake_helper'
-describe SystemCheck::SimpleExecutor do
+RSpec.describe SystemCheck::SimpleExecutor do
before do
stub_const('SimpleCheck', Class.new(SystemCheck::BaseCheck))
stub_const('OtherCheck', Class.new(SystemCheck::BaseCheck))
diff --git a/spec/lib/system_check_spec.rb b/spec/lib/system_check_spec.rb
index da1916455ba..5ddb2741c4a 100644
--- a/spec/lib/system_check_spec.rb
+++ b/spec/lib/system_check_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require 'rake_helper'
-describe SystemCheck do
+RSpec.describe SystemCheck do
before do
stub_const('SimpleCheck', Class.new(SystemCheck::BaseCheck))
stub_const('OtherCheck', Class.new(SystemCheck::BaseCheck))
diff --git a/spec/lib/uploaded_file_spec.rb b/spec/lib/uploaded_file_spec.rb
index 39055a2479f..5ff46193b4f 100644
--- a/spec/lib/uploaded_file_spec.rb
+++ b/spec/lib/uploaded_file_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe UploadedFile do
+RSpec.describe UploadedFile do
let(:temp_dir) { Dir.tmpdir }
let(:temp_file) { Tempfile.new(%w[test test], temp_dir) }
diff --git a/spec/mailers/abuse_report_mailer_spec.rb b/spec/mailers/abuse_report_mailer_spec.rb
index fcbffb52849..4eb616722ac 100644
--- a/spec/mailers/abuse_report_mailer_spec.rb
+++ b/spec/mailers/abuse_report_mailer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe AbuseReportMailer do
+RSpec.describe AbuseReportMailer do
include EmailSpec::Matchers
describe '.notify' do
diff --git a/spec/mailers/devise_mailer_spec.rb b/spec/mailers/devise_mailer_spec.rb
new file mode 100644
index 00000000000..4637df9c8a3
--- /dev/null
+++ b/spec/mailers/devise_mailer_spec.rb
@@ -0,0 +1,38 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require 'email_spec'
+
+RSpec.describe DeviseMailer do
+ describe "#confirmation_instructions" do
+ subject { described_class.confirmation_instructions(user, 'faketoken', {}) }
+
+ context "when confirming a new account" do
+ let(:user) { build(:user, created_at: 1.minute.ago, unconfirmed_email: nil) }
+
+ it "shows the expected text" do
+ expect(subject.body.encoded).to have_text "Welcome"
+ expect(subject.body.encoded).not_to have_text user.email
+ end
+ end
+
+ context "when confirming the unconfirmed_email" do
+ let(:user) { build(:user, unconfirmed_email: 'jdoe@example.com') }
+
+ it "shows the expected text" do
+ expect(subject.body.encoded).not_to have_text "Welcome"
+ expect(subject.body.encoded).to have_text user.unconfirmed_email
+ expect(subject.body.encoded).not_to have_text user.email
+ end
+ end
+
+ context "when re-confirming the primary email after a security issue" do
+ let(:user) { build(:user, created_at: 10.days.ago, unconfirmed_email: nil) }
+
+ it "shows the expected text" do
+ expect(subject.body.encoded).not_to have_text "Welcome"
+ expect(subject.body.encoded).to have_text user.email
+ end
+ end
+ end
+end
diff --git a/spec/mailers/email_rejection_mailer_spec.rb b/spec/mailers/email_rejection_mailer_spec.rb
index 2ce2e3e1410..d28f034105a 100644
--- a/spec/mailers/email_rejection_mailer_spec.rb
+++ b/spec/mailers/email_rejection_mailer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe EmailRejectionMailer do
+RSpec.describe EmailRejectionMailer do
include EmailSpec::Matchers
describe '#rejection' do
diff --git a/spec/mailers/emails/auto_devops_spec.rb b/spec/mailers/emails/auto_devops_spec.rb
index dd7c12c3143..950fcfbf4df 100644
--- a/spec/mailers/emails/auto_devops_spec.rb
+++ b/spec/mailers/emails/auto_devops_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Emails::AutoDevops do
+RSpec.describe Emails::AutoDevops do
include EmailSpec::Matchers
describe '#auto_devops_disabled_email' do
diff --git a/spec/mailers/emails/groups_spec.rb b/spec/mailers/emails/groups_spec.rb
index b4746e120e0..01846172440 100644
--- a/spec/mailers/emails/groups_spec.rb
+++ b/spec/mailers/emails/groups_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require 'email_spec'
-describe Emails::Groups do
+RSpec.describe Emails::Groups do
include EmailSpec::Matchers
let(:group) { create(:group) }
diff --git a/spec/mailers/emails/issues_spec.rb b/spec/mailers/emails/issues_spec.rb
index dfd974aa5f3..21e07c0252d 100644
--- a/spec/mailers/emails/issues_spec.rb
+++ b/spec/mailers/emails/issues_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require 'email_spec'
-describe Emails::Issues do
+RSpec.describe Emails::Issues do
include EmailSpec::Matchers
it 'adds email methods to Notify' do
diff --git a/spec/mailers/emails/merge_requests_spec.rb b/spec/mailers/emails/merge_requests_spec.rb
index 541acc47172..477fb16400a 100644
--- a/spec/mailers/emails/merge_requests_spec.rb
+++ b/spec/mailers/emails/merge_requests_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require 'email_spec'
-describe Emails::MergeRequests do
+RSpec.describe Emails::MergeRequests do
include EmailSpec::Matchers
describe "#resolved_all_discussions_email" do
@@ -17,4 +17,20 @@ describe Emails::MergeRequests do
expect(subject).to have_body_text current_user.name
end
end
+
+ describe "#merge_when_pipeline_succeeds_email" do
+ let(:user) { create(:user) }
+ let(:merge_request) { create(:merge_request) }
+ let(:current_user) { create(:user) }
+ let(:project) { create(:project, :repository) }
+ let(:title) { "Merge request #{merge_request.to_reference} was scheduled to merge after pipeline succeeds by #{current_user.name}" }
+
+ subject { Notify.merge_when_pipeline_succeeds_email(user.id, merge_request.id, current_user.id) }
+
+ it "has required details" do
+ expect(subject).to have_content title
+ expect(subject).to have_content merge_request.to_reference
+ expect(subject).to have_content current_user.name
+ end
+ end
end
diff --git a/spec/mailers/emails/pages_domains_spec.rb b/spec/mailers/emails/pages_domains_spec.rb
index 5029a17e4e5..cf17f2e5ebf 100644
--- a/spec/mailers/emails/pages_domains_spec.rb
+++ b/spec/mailers/emails/pages_domains_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require 'email_spec'
-describe Emails::PagesDomains do
+RSpec.describe Emails::PagesDomains do
include EmailSpec::Matchers
include_context 'gitlab email notification'
diff --git a/spec/mailers/emails/pipelines_spec.rb b/spec/mailers/emails/pipelines_spec.rb
index cc901da98dc..f0f23fe2097 100644
--- a/spec/mailers/emails/pipelines_spec.rb
+++ b/spec/mailers/emails/pipelines_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require 'email_spec'
-describe Emails::Pipelines do
+RSpec.describe Emails::Pipelines do
include EmailSpec::Matchers
let_it_be(:project) { create(:project, :repository) }
diff --git a/spec/mailers/emails/profile_spec.rb b/spec/mailers/emails/profile_spec.rb
index cbf42da2085..ee91df360b6 100644
--- a/spec/mailers/emails/profile_spec.rb
+++ b/spec/mailers/emails/profile_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require 'email_spec'
-describe Emails::Profile do
+RSpec.describe Emails::Profile do
include EmailSpec::Matchers
include_context 'gitlab email notification'
diff --git a/spec/mailers/emails/projects_spec.rb b/spec/mailers/emails/projects_spec.rb
index 6c94ed0aa4d..599f62a8113 100644
--- a/spec/mailers/emails/projects_spec.rb
+++ b/spec/mailers/emails/projects_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require 'email_spec'
-describe Emails::Projects do
+RSpec.describe Emails::Projects do
include EmailSpec::Matchers
include_context 'gitlab email notification'
diff --git a/spec/mailers/emails/releases_spec.rb b/spec/mailers/emails/releases_spec.rb
index c614c009434..60e522c7cfa 100644
--- a/spec/mailers/emails/releases_spec.rb
+++ b/spec/mailers/emails/releases_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require 'email_spec'
-describe Emails::Releases do
+RSpec.describe Emails::Releases do
include EmailSpec::Matchers
include_context 'gitlab email notification'
diff --git a/spec/mailers/emails/service_desk_spec.rb b/spec/mailers/emails/service_desk_spec.rb
new file mode 100644
index 00000000000..842f82539cb
--- /dev/null
+++ b/spec/mailers/emails/service_desk_spec.rb
@@ -0,0 +1,188 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require 'email_spec'
+
+RSpec.describe Emails::ServiceDesk do
+ include EmailSpec::Helpers
+ include EmailSpec::Matchers
+ include EmailHelpers
+
+ include_context 'gitlab email notification'
+
+ let_it_be(:user) { create(:user) }
+ let_it_be(:project) { create(:project) }
+ let_it_be(:issue) { create(:issue, project: project) }
+ let(:template) { double(content: template_content) }
+
+ before do
+ stub_const('ServiceEmailClass', Class.new(ApplicationMailer))
+
+ ServiceEmailClass.class_eval do
+ include GitlabRoutingHelper
+ include EmailsHelper
+ include Emails::ServiceDesk
+
+ helper GitlabRoutingHelper
+ helper EmailsHelper
+
+ # this method is implemented in Notify class, we don't need to test it
+ def reply_key
+ 'test-key'
+ end
+
+ # this method is implemented in Notify class, we don't need to test it
+ def sender(author_id, params = {})
+ author_id
+ end
+
+ # this method is implemented in Notify class
+ #
+ # We do not need to test the Notify method, it is already tested in notify_spec
+ def mail_new_thread(issue, options)
+ # we need to rewrite this in order to look up templates in the correct directory
+ self.class.mailer_name = 'notify'
+
+ # this is needed for default layout
+ @unsubscribe_url = 'http://unsubscribe.example.com'
+
+ mail(options)
+ end
+ alias_method :mail_answer_thread, :mail_new_thread
+ end
+ end
+
+ shared_examples 'handle template content' do |template_key|
+ before do
+ expect(Gitlab::Template::ServiceDeskTemplate).to receive(:find)
+ .with(template_key, issue.project)
+ .and_return(template)
+ end
+
+ it 'builds the email correctly' do
+ aggregate_failures do
+ is_expected.to have_referable_subject(issue, include_project: false, reply: reply_in_subject)
+ is_expected.to have_body_text(expected_body)
+ expect(subject.content_type).to include('text/html')
+ end
+ end
+ end
+
+ shared_examples 'read template from repository' do |template_key|
+ let(:template_content) { 'custom text' }
+ let(:issue) { create(:issue, project: project)}
+
+ context 'when a template is in the repository' do
+ let(:project) { create(:project, :custom_repo, files: { ".gitlab/service_desk_templates/#{template_key}.md" => template_content }) }
+
+ it 'uses the text template from the template' do
+ is_expected.to have_body_text(template_content)
+ end
+ end
+
+ context 'when the service_desk_templates directory does not contain correct template' do
+ let(:project) { create(:project, :custom_repo, files: { ".gitlab/service_desk_templates/another_file.md" => template_content }) }
+
+ it 'uses the default template' do
+ is_expected.to have_body_text(default_text)
+ end
+ end
+
+ context 'when the service_desk_templates directory does not exist' do
+ let(:project) { create(:project, :custom_repo, files: { "other_directory/another_file.md" => template_content }) }
+
+ it 'uses the default template' do
+ is_expected.to have_body_text(default_text)
+ end
+ end
+
+ context 'when the project does not have a repo' do
+ let(:project) { create(:project) }
+
+ it 'uses the default template' do
+ is_expected.to have_body_text(default_text)
+ end
+ end
+ end
+
+ describe '.service_desk_thank_you_email' do
+ let_it_be(:reply_in_subject) { true }
+ let_it_be(:default_text) do
+ "Thank you for your support request! We are tracking your request as ticket #{issue.to_reference}, and will respond as soon as we can."
+ end
+
+ subject { ServiceEmailClass.service_desk_thank_you_email(issue.id) }
+
+ it_behaves_like 'read template from repository', 'thank_you'
+
+ context 'handling template markdown' do
+ context 'with a simple text' do
+ let(:template_content) { 'thank you, **your new issue** has been created.' }
+ let(:expected_body) { 'thank you, <strong>your new issue</strong> has been created.' }
+
+ it_behaves_like 'handle template content', 'thank_you'
+ end
+
+ context 'with an issue id and issue path placeholders' do
+ let(:template_content) { 'thank you, **your new issue:** %{ISSUE_ID}, path: %{ISSUE_PATH}' }
+ let(:expected_body) { "thank you, <strong>your new issue:</strong> ##{issue.iid}, path: #{project.full_path}##{issue.iid}" }
+
+ it_behaves_like 'handle template content', 'thank_you'
+ end
+
+ context 'with an issue id placeholder with whitespace' do
+ let(:template_content) { 'thank you, **your new issue:** %{ ISSUE_ID}' }
+ let(:expected_body) { "thank you, <strong>your new issue:</strong> ##{issue.iid}" }
+
+ it_behaves_like 'handle template content', 'thank_you'
+ end
+
+ context 'with unexpected placeholder' do
+ let(:template_content) { 'thank you, **your new issue:** %{this is issue}' }
+ let(:expected_body) { "thank you, <strong>your new issue:</strong> %{this is issue}" }
+
+ it_behaves_like 'handle template content', 'thank_you'
+ end
+ end
+ end
+
+ describe '.service_desk_new_note_email' do
+ let_it_be(:reply_in_subject) { false }
+ let_it_be(:note) { create(:note_on_issue, noteable: issue, project: project) }
+ let_it_be(:default_text) { note.note }
+
+ subject { ServiceEmailClass.service_desk_new_note_email(issue.id, note.id) }
+
+ it_behaves_like 'read template from repository', 'new_note'
+
+ context 'handling template markdown' do
+ context 'with a simple text' do
+ let(:template_content) { 'thank you, **new note on issue** has been created.' }
+ let(:expected_body) { 'thank you, <strong>new note on issue</strong> has been created.' }
+
+ it_behaves_like 'handle template content', 'new_note'
+ end
+
+ context 'with an issue id, issue path and note placeholders' do
+ let(:template_content) { 'thank you, **new note on issue:** %{ISSUE_ID}, path: %{ISSUE_PATH}: %{NOTE_TEXT}' }
+ let(:expected_body) { "thank you, <strong>new note on issue:</strong> ##{issue.iid}, path: #{project.full_path}##{issue.iid}: #{note.note}" }
+
+ it_behaves_like 'handle template content', 'new_note'
+ end
+
+ context 'with an issue id placeholder with whitespace' do
+ let(:template_content) { 'thank you, **new note on issue:** %{ ISSUE_ID}: %{ NOTE_TEXT }' }
+ let(:expected_body) { "thank you, <strong>new note on issue:</strong> ##{issue.iid}: #{note.note}" }
+
+ it_behaves_like 'handle template content', 'new_note'
+ end
+
+ context 'with unexpected placeholder' do
+ let(:template_content) { 'thank you, **new note on issue:** %{this is issue}' }
+ let(:expected_body) { "thank you, <strong>new note on issue:</strong> %{this is issue}" }
+
+ it_behaves_like 'handle template content', 'new_note'
+ end
+ end
+ end
+end
diff --git a/spec/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb
index 8b99cc41a53..e56cd488a52 100644
--- a/spec/mailers/notify_spec.rb
+++ b/spec/mailers/notify_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require 'email_spec'
-describe Notify do
+RSpec.describe Notify do
include EmailSpec::Helpers
include EmailSpec::Matchers
include EmailHelpers
@@ -105,6 +105,7 @@ describe Notify do
it 'contains a link to issue author' do
is_expected.to have_body_text(issue.author_name)
is_expected.to have_body_text 'created an issue'
+ is_expected.to have_link(issue.to_reference, href: project_issue_url(issue.project, issue))
end
it 'contains a link to the issue' do
@@ -467,6 +468,7 @@ describe Notify do
is_expected.to have_body_text(status)
is_expected.to have_body_text(current_user_sanitized)
is_expected.to have_body_text(project_merge_request_path(project, merge_request))
+ is_expected.to have_link(merge_request.to_reference, href: project_merge_request_url(merge_request.target_project, merge_request))
end
end
end
@@ -497,6 +499,7 @@ describe Notify do
is_expected.to have_referable_subject(merge_request, reply: true)
is_expected.to have_body_text('merged')
is_expected.to have_body_text(project_merge_request_path(project, merge_request))
+ is_expected.to have_link(merge_request.to_reference, href: project_merge_request_url(merge_request.target_project, merge_request))
end
end
end
@@ -534,6 +537,7 @@ describe Notify do
is_expected.to have_referable_subject(merge_request, reply: true)
is_expected.to have_body_text(project_merge_request_path(project, merge_request))
is_expected.to have_body_text('due to conflict.')
+ is_expected.to have_link(merge_request.to_reference, href: project_merge_request_url(merge_request.target_project, merge_request))
end
end
end
@@ -567,6 +571,7 @@ describe Notify do
is_expected.to have_referable_subject(merge_request, reply: true)
is_expected.to have_body_text("#{push_user.name} pushed new commits")
is_expected.to have_body_text(project_merge_request_path(project, merge_request))
+ is_expected.to have_link(merge_request.to_reference, href: project_merge_request_url(merge_request.target_project, merge_request))
end
end
end
@@ -1175,9 +1180,7 @@ describe Notify do
context 'when note is not on text' do
before do
- allow_next_instance_of(DiffDiscussion) do |instance|
- allow(instance).to receive(:on_text?).and_return(false)
- end
+ allow(note.discussion).to receive(:on_text?).and_return(false)
end
it 'does not include diffs with character-level highlighting' do
@@ -1248,6 +1251,78 @@ describe Notify do
it_behaves_like 'appearance header and footer not enabled'
end
end
+
+ context 'for service desk issues' do
+ before do
+ issue.update!(service_desk_reply_to: 'service.desk@example.com')
+ end
+
+ def expect_sender(username)
+ sender = subject.header[:from].addrs[0]
+ expect(sender.display_name).to eq(username)
+ expect(sender.address).to eq(gitlab_sender)
+ end
+
+ describe 'thank you email' do
+ subject { described_class.service_desk_thank_you_email(issue.id) }
+
+ it_behaves_like 'an unsubscribeable thread'
+
+ it 'has the correct recipient' do
+ is_expected.to deliver_to('service.desk@example.com')
+ end
+
+ it 'has the correct subject and body' do
+ aggregate_failures do
+ is_expected.to have_referable_subject(issue, include_project: false, reply: true)
+ is_expected.to have_body_text("Thank you for your support request! We are tracking your request as ticket #{issue.to_reference}, and will respond as soon as we can.")
+ end
+ end
+
+ it 'uses service bot name by default' do
+ expect_sender(User.support_bot.name)
+ end
+
+ context 'when custom outgoing name is set' do
+ let_it_be(:settings) { create(:service_desk_setting, project: project, outgoing_name: 'some custom name') }
+
+ it 'uses custom name in "from" header' do
+ expect_sender('some custom name')
+ end
+ end
+
+ context 'when custom outgoing name is empty' do
+ let_it_be(:settings) { create(:service_desk_setting, project: project, outgoing_name: '') }
+
+ it 'uses service bot name' do
+ expect_sender(User.support_bot.name)
+ end
+ end
+ end
+
+ describe 'new note email' do
+ let_it_be(:first_note) { create(:discussion_note_on_issue, note: 'Hello world') }
+
+ subject { described_class.service_desk_new_note_email(issue.id, first_note.id) }
+
+ it_behaves_like 'an unsubscribeable thread'
+
+ it 'has the correct recipient' do
+ is_expected.to deliver_to('service.desk@example.com')
+ end
+
+ it 'uses author\'s name in "from" header' do
+ expect_sender(first_note.author.name)
+ end
+
+ it 'has the correct subject and body' do
+ aggregate_failures do
+ is_expected.to have_referable_subject(issue, include_project: false, reply: true)
+ is_expected.to have_body_text(first_note.note)
+ end
+ end
+ end
+ end
end
context 'for a group' do
diff --git a/spec/mailers/repository_check_mailer_spec.rb b/spec/mailers/repository_check_mailer_spec.rb
index 1fd4d28ca53..8b1bc33d8be 100644
--- a/spec/mailers/repository_check_mailer_spec.rb
+++ b/spec/mailers/repository_check_mailer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe RepositoryCheckMailer do
+RSpec.describe RepositoryCheckMailer do
include EmailSpec::Matchers
describe '.notify' do
diff --git a/spec/migrations/20190924152703_migrate_issue_trackers_data_spec.rb b/spec/migrations/20190924152703_migrate_issue_trackers_data_spec.rb
index dfa4cc21d63..750d3d1fa30 100644
--- a/spec/migrations/20190924152703_migrate_issue_trackers_data_spec.rb
+++ b/spec/migrations/20190924152703_migrate_issue_trackers_data_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20190924152703_migrate_issue_trackers_data.rb')
-describe MigrateIssueTrackersData do
+RSpec.describe MigrateIssueTrackersData do
let(:services) { table(:services) }
let(:migration_class) { Gitlab::BackgroundMigration::MigrateIssueTrackersSensitiveData }
let(:migration_name) { migration_class.to_s.demodulize }
diff --git a/spec/migrations/20191015154408_drop_merge_requests_require_code_owner_approval_from_projects_spec.rb b/spec/migrations/20191015154408_drop_merge_requests_require_code_owner_approval_from_projects_spec.rb
index ac9ff5632eb..e2eacc00e5a 100644
--- a/spec/migrations/20191015154408_drop_merge_requests_require_code_owner_approval_from_projects_spec.rb
+++ b/spec/migrations/20191015154408_drop_merge_requests_require_code_owner_approval_from_projects_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20191015154408_drop_merge_requests_require_code_owner_approval_from_projects.rb')
-describe DropMergeRequestsRequireCodeOwnerApprovalFromProjects do
+RSpec.describe DropMergeRequestsRequireCodeOwnerApprovalFromProjects do
let(:projects_table) { table(:projects) }
subject(:migration) { described_class.new }
diff --git a/spec/migrations/20191125114345_add_admin_mode_protected_path_spec.rb b/spec/migrations/20191125114345_add_admin_mode_protected_path_spec.rb
index 77d8dd002e3..51f798220c3 100644
--- a/spec/migrations/20191125114345_add_admin_mode_protected_path_spec.rb
+++ b/spec/migrations/20191125114345_add_admin_mode_protected_path_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'migrate', '20191125114345_add_admin_mode_protected_path.rb')
-describe AddAdminModeProtectedPath do
+RSpec.describe AddAdminModeProtectedPath do
subject(:migration) { described_class.new }
let(:admin_mode_endpoint) { '/admin/session' }
diff --git a/spec/migrations/20191204114127_delete_legacy_triggers_spec.rb b/spec/migrations/20191204114127_delete_legacy_triggers_spec.rb
index 58061d80f21..07c69872497 100644
--- a/spec/migrations/20191204114127_delete_legacy_triggers_spec.rb
+++ b/spec/migrations/20191204114127_delete_legacy_triggers_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20191204114127_delete_legacy_triggers.rb')
-describe DeleteLegacyTriggers, schema: 2019_11_25_140458 do
+RSpec.describe DeleteLegacyTriggers, schema: 2019_11_25_140458 do
let(:ci_trigger_table) { table(:ci_triggers) }
let(:user) { table(:users).create!(name: 'test', email: 'test@example.com', projects_limit: 1) }
diff --git a/spec/migrations/20200107172020_add_timestamp_softwarelicensespolicy_spec.rb b/spec/migrations/20200107172020_add_timestamp_softwarelicensespolicy_spec.rb
index 7a6b21d485b..1377d5e6ba0 100644
--- a/spec/migrations/20200107172020_add_timestamp_softwarelicensespolicy_spec.rb
+++ b/spec/migrations/20200107172020_add_timestamp_softwarelicensespolicy_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'migrate', '20200107172020_add_timestamp_softwarelicensespolicy.rb')
-describe AddTimestampSoftwarelicensespolicy do
+RSpec.describe AddTimestampSoftwarelicensespolicy do
let(:software_licenses_policy) { table(:software_license_policies) }
let(:projects) { table(:projects) }
let(:licenses) { table(:software_licenses) }
diff --git a/spec/migrations/20200122123016_backfill_project_settings_spec.rb b/spec/migrations/20200122123016_backfill_project_settings_spec.rb
index ce86e94b6d5..0992ddde0c1 100644
--- a/spec/migrations/20200122123016_backfill_project_settings_spec.rb
+++ b/spec/migrations/20200122123016_backfill_project_settings_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20200122123016_backfill_project_settings.rb')
-describe BackfillProjectSettings, :sidekiq, schema: 20200114113341 do
+RSpec.describe BackfillProjectSettings, :sidekiq, schema: 20200114113341 do
let(:projects) { table(:projects) }
let(:namespace) { table(:namespaces).create(name: 'user', path: 'user') }
let(:project) { projects.create(namespace_id: namespace.id) }
diff --git a/spec/migrations/20200123155929_remove_invalid_jira_data_spec.rb b/spec/migrations/20200123155929_remove_invalid_jira_data_spec.rb
index 253e39c1647..cae8e858af9 100644
--- a/spec/migrations/20200123155929_remove_invalid_jira_data_spec.rb
+++ b/spec/migrations/20200123155929_remove_invalid_jira_data_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20200123155929_remove_invalid_jira_data.rb')
-describe RemoveInvalidJiraData do
+RSpec.describe RemoveInvalidJiraData do
let(:jira_tracker_data) { table(:jira_tracker_data) }
let(:services) { table(:services) }
diff --git a/spec/migrations/20200127090233_remove_invalid_issue_tracker_data_spec.rb b/spec/migrations/20200127090233_remove_invalid_issue_tracker_data_spec.rb
index ca7cde08071..a86b5d83a0b 100644
--- a/spec/migrations/20200127090233_remove_invalid_issue_tracker_data_spec.rb
+++ b/spec/migrations/20200127090233_remove_invalid_issue_tracker_data_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20200127090233_remove_invalid_issue_tracker_data.rb')
-describe RemoveInvalidIssueTrackerData do
+RSpec.describe RemoveInvalidIssueTrackerData do
let(:issue_tracker_data) { table(:issue_tracker_data) }
let(:services) { table(:services) }
diff --git a/spec/migrations/20200130145430_reschedule_migrate_issue_trackers_data_spec.rb b/spec/migrations/20200130145430_reschedule_migrate_issue_trackers_data_spec.rb
index 8e3e55f3e19..146302ba650 100644
--- a/spec/migrations/20200130145430_reschedule_migrate_issue_trackers_data_spec.rb
+++ b/spec/migrations/20200130145430_reschedule_migrate_issue_trackers_data_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20200130145430_reschedule_migrate_issue_trackers_data.rb')
-describe RescheduleMigrateIssueTrackersData do
+RSpec.describe RescheduleMigrateIssueTrackersData do
let(:services) { table(:services) }
let(:migration_class) { Gitlab::BackgroundMigration::MigrateIssueTrackersSensitiveData }
let(:migration_name) { migration_class.to_s.demodulize }
diff --git a/spec/migrations/20200313203550_remove_orphaned_chat_names_spec.rb b/spec/migrations/20200313203550_remove_orphaned_chat_names_spec.rb
index fd30ebaa66f..d9ce62fe475 100644
--- a/spec/migrations/20200313203550_remove_orphaned_chat_names_spec.rb
+++ b/spec/migrations/20200313203550_remove_orphaned_chat_names_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20200313203550_remove_orphaned_chat_names.rb')
-describe RemoveOrphanedChatNames, schema: 20200313202430 do
+RSpec.describe RemoveOrphanedChatNames, schema: 20200313202430 do
let(:projects) { table(:projects) }
let(:namespaces) { table(:namespaces) }
let(:services) { table(:services) }
diff --git a/spec/migrations/20200406102120_backfill_deployment_clusters_from_deployments_spec.rb b/spec/migrations/20200406102120_backfill_deployment_clusters_from_deployments_spec.rb
index fcb253677e1..47bc7428286 100644
--- a/spec/migrations/20200406102120_backfill_deployment_clusters_from_deployments_spec.rb
+++ b/spec/migrations/20200406102120_backfill_deployment_clusters_from_deployments_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20200406102120_backfill_deployment_clusters_from_deployments.rb')
-describe BackfillDeploymentClustersFromDeployments, :migration, :sidekiq, schema: 20200227140242 do
+RSpec.describe BackfillDeploymentClustersFromDeployments, :migration, :sidekiq, schema: 20200227140242 do
describe '#up' do
it 'schedules BackfillDeploymentClustersFromDeployments background jobs' do
stub_const("#{described_class}::BATCH_SIZE", 2)
diff --git a/spec/migrations/20200511145545_change_variable_interpolation_format_in_common_metrics_spec.rb b/spec/migrations/20200511145545_change_variable_interpolation_format_in_common_metrics_spec.rb
index f9e8a7ee6e9..e42a448a01e 100644
--- a/spec/migrations/20200511145545_change_variable_interpolation_format_in_common_metrics_spec.rb
+++ b/spec/migrations/20200511145545_change_variable_interpolation_format_in_common_metrics_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20200511145545_change_variable_interpolation_format_in_common_metrics')
-describe ChangeVariableInterpolationFormatInCommonMetrics, :migration do
+RSpec.describe ChangeVariableInterpolationFormatInCommonMetrics, :migration do
let(:prometheus_metrics) { table(:prometheus_metrics) }
let!(:common_metric) do
@@ -23,9 +23,14 @@ describe ChangeVariableInterpolationFormatInCommonMetrics, :migration do
end
it 'updates query to use {{}}' do
- expected_query = 'avg(sum(container_memory_usage_bytes{container_name!="POD",' \
- 'pod_name=~"^{{ci_environment_slug}}-(.*)",namespace="{{kube_namespace}}"})' \
- ' by (job)) without (job) /1024/1024/1024'
+ expected_query = <<~EOS.chomp
+ avg(sum(container_memory_usage_bytes{container!="POD",\
+ pod=~"^{{ci_environment_slug}}-(.*)",namespace="{{kube_namespace}}"}) \
+ by (job)) without (job) /1024/1024/1024 OR \
+ avg(sum(container_memory_usage_bytes{container_name!="POD",\
+ pod_name=~"^{{ci_environment_slug}}-(.*)",namespace="{{kube_namespace}}"}) \
+ by (job)) without (job) /1024/1024/1024
+ EOS
migrate!
diff --git a/spec/migrations/20200526115436_dedup_mr_metrics_spec.rb b/spec/migrations/20200526115436_dedup_mr_metrics_spec.rb
new file mode 100644
index 00000000000..f2698a0f352
--- /dev/null
+++ b/spec/migrations/20200526115436_dedup_mr_metrics_spec.rb
@@ -0,0 +1,68 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require Rails.root.join('db', 'post_migrate', '20200526115436_dedup_mr_metrics')
+
+RSpec.describe DedupMrMetrics, :migration, schema: 20200526013844 do
+ let(:namespaces) { table(:namespaces) }
+ let(:projects) { table(:projects) }
+ let(:merge_requests) { table(:merge_requests) }
+ let(:metrics) { table(:merge_request_metrics) }
+ let(:merge_request_params) { { source_branch: 'x', target_branch: 'y', target_project_id: project.id } }
+
+ let!(:namespace) { namespaces.create(name: 'foo', path: 'foo') }
+ let!(:project) { projects.create!(namespace_id: namespace.id) }
+ let!(:merge_request_1) { merge_requests.create!(merge_request_params) }
+ let!(:merge_request_2) { merge_requests.create!(merge_request_params) }
+ let!(:merge_request_3) { merge_requests.create!(merge_request_params) }
+
+ let!(:duplicated_metrics_1) { metrics.create(merge_request_id: merge_request_1.id, latest_build_started_at: 1.day.ago, first_deployed_to_production_at: 5.days.ago, updated_at: 2.months.ago) }
+ let!(:duplicated_metrics_2) { metrics.create(merge_request_id: merge_request_1.id, latest_build_started_at: Time.now, merged_at: Time.now, updated_at: 1.month.ago) }
+
+ let!(:duplicated_metrics_3) { metrics.create(merge_request_id: merge_request_3.id, diff_size: 30, commits_count: 20, updated_at: 2.months.ago) }
+ let!(:duplicated_metrics_4) { metrics.create(merge_request_id: merge_request_3.id, added_lines: 5, commits_count: nil, updated_at: 1.month.ago) }
+
+ let!(:non_duplicated_metrics) { metrics.create(merge_request_id: merge_request_2.id, latest_build_started_at: 2.days.ago) }
+
+ it 'deduplicates merge_request_metrics table' do
+ expect { migrate! }.to change { metrics.count }.from(5).to(3)
+ end
+
+ it 'merges `duplicated_metrics_1` with `duplicated_metrics_2`' do
+ migrate!
+
+ expect(metrics.where(id: duplicated_metrics_1.id)).not_to exist
+
+ merged_metrics = metrics.find_by(id: duplicated_metrics_2.id)
+
+ expect(merged_metrics).to be_present
+ expect(merged_metrics.latest_build_started_at).to be_like_time(duplicated_metrics_2.latest_build_started_at)
+ expect(merged_metrics.merged_at).to be_like_time(duplicated_metrics_2.merged_at)
+ expect(merged_metrics.first_deployed_to_production_at).to be_like_time(duplicated_metrics_1.first_deployed_to_production_at)
+ end
+
+ it 'merges `duplicated_metrics_3` with `duplicated_metrics_4`' do
+ migrate!
+
+ expect(metrics.where(id: duplicated_metrics_3.id)).not_to exist
+
+ merged_metrics = metrics.find_by(id: duplicated_metrics_4.id)
+
+ expect(merged_metrics).to be_present
+ expect(merged_metrics.diff_size).to eq(duplicated_metrics_3.diff_size)
+ expect(merged_metrics.commits_count).to eq(duplicated_metrics_3.commits_count)
+ expect(merged_metrics.added_lines).to eq(duplicated_metrics_4.added_lines)
+ end
+
+ it 'does not change non duplicated records' do
+ expect { migrate! }.not_to change { non_duplicated_metrics.reload.attributes }
+ end
+
+ it 'does nothing when there are no metrics' do
+ metrics.delete_all
+
+ migrate!
+
+ expect(metrics.count).to eq(0)
+ end
+end
diff --git a/spec/migrations/20200526231421_update_index_approval_rule_name_for_code_owners_rule_type_spec.rb b/spec/migrations/20200526231421_update_index_approval_rule_name_for_code_owners_rule_type_spec.rb
new file mode 100644
index 00000000000..9f26b698158
--- /dev/null
+++ b/spec/migrations/20200526231421_update_index_approval_rule_name_for_code_owners_rule_type_spec.rb
@@ -0,0 +1,175 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require Rails.root.join('db', 'migrate', '20200526231421_update_index_approval_rule_name_for_code_owners_rule_type.rb')
+
+RSpec.describe UpdateIndexApprovalRuleNameForCodeOwnersRuleType do
+ let(:migration) { described_class.new }
+
+ let(:approval_rules) { table(:approval_merge_request_rules) }
+ let(:namespace) { table(:namespaces).create!(name: 'gitlab', path: 'gitlab') }
+
+ let(:project) do
+ table(:projects).create!(
+ namespace_id: namespace.id,
+ name: 'gitlab',
+ path: 'gitlab'
+ )
+ end
+
+ let(:merge_request) do
+ table(:merge_requests).create!(
+ target_project_id: project.id,
+ source_project_id: project.id,
+ target_branch: 'feature',
+ source_branch: 'master'
+ )
+ end
+
+ let(:index_names) do
+ ActiveRecord::Base.connection
+ .indexes(:approval_merge_request_rules)
+ .collect(&:name)
+ end
+
+ def create_sectional_approval_rules
+ approval_rules.create!(
+ merge_request_id: merge_request.id,
+ name: "*.rb",
+ code_owner: true,
+ rule_type: 2,
+ section: "First Section"
+ )
+
+ approval_rules.create!(
+ merge_request_id: merge_request.id,
+ name: "*.rb",
+ code_owner: true,
+ rule_type: 2,
+ section: "Second Section"
+ )
+ end
+
+ def create_two_matching_nil_section_approval_rules
+ 2.times do
+ approval_rules.create!(
+ merge_request_id: merge_request.id,
+ name: "nil_section",
+ code_owner: true,
+ rule_type: 2
+ )
+ end
+ end
+
+ before do
+ approval_rules.delete_all
+ end
+
+ describe "#up" do
+ it "creates the new index and removes the 'legacy' indices" do
+ # Confirm that existing legacy indices prevent duplicate entries
+ #
+ expect { create_sectional_approval_rules }
+ .to raise_exception(ActiveRecord::RecordNotUnique)
+ expect { create_two_matching_nil_section_approval_rules }
+ .to raise_exception(ActiveRecord::RecordNotUnique)
+
+ approval_rules.delete_all
+
+ disable_migrations_output { migrate! }
+
+ # After running the migration, expect `section == nil` rules to still be
+ # blocked by the legacy indices, but sectional rules are allowed.
+ #
+ expect { create_sectional_approval_rules }
+ .to change { approval_rules.count }.by(2)
+ expect { create_two_matching_nil_section_approval_rules }
+ .to raise_exception(ActiveRecord::RecordNotUnique)
+
+ # Attempt to rerun the creation of sectional rules, and see that sectional
+ # rules are unique by section
+ #
+ expect { create_sectional_approval_rules }
+ .to raise_exception(ActiveRecord::RecordNotUnique)
+
+ expect(index_names).to include(
+ described_class::SECTIONAL_INDEX_NAME,
+ described_class::LEGACY_INDEX_NAME_RULE_TYPE,
+ described_class::LEGACY_INDEX_NAME_CODE_OWNERS
+ )
+ end
+ end
+
+ describe "#down" do
+ context "run as FOSS" do
+ before do
+ expect(Gitlab).to receive(:ee?).twice.and_return(false)
+ end
+
+ it "recreates legacy indices, but does not invoke EE-specific code" do
+ disable_migrations_output { migrate! }
+
+ expect(index_names).to include(
+ described_class::SECTIONAL_INDEX_NAME,
+ described_class::LEGACY_INDEX_NAME_RULE_TYPE,
+ described_class::LEGACY_INDEX_NAME_CODE_OWNERS
+ )
+
+ # Since ApprovalMergeRequestRules are EE-specific, we expect none to be
+ # deleted during the migration.
+ #
+ expect { disable_migrations_output { migration.down } }
+ .not_to change { approval_rules.count }
+
+ index_names = ActiveRecord::Base.connection
+ .indexes(:approval_merge_request_rules)
+ .collect(&:name)
+
+ expect(index_names).not_to include(described_class::SECTIONAL_INDEX_NAME)
+ expect(index_names).to include(
+ described_class::LEGACY_INDEX_NAME_RULE_TYPE,
+ described_class::LEGACY_INDEX_NAME_CODE_OWNERS
+ )
+ end
+ end
+
+ context "EE" do
+ it "recreates 'legacy' indices and removes duplicate code owner approval rules" do
+ skip("This test is skipped under FOSS") unless Gitlab.ee?
+
+ disable_migrations_output { migrate! }
+
+ expect { create_sectional_approval_rules }
+ .to change { approval_rules.count }.by(2)
+ expect { create_two_matching_nil_section_approval_rules }
+ .to raise_exception(ActiveRecord::RecordNotUnique)
+
+ expect(MergeRequests::SyncCodeOwnerApprovalRules)
+ .to receive(:new).with(MergeRequest.find(merge_request.id)).once.and_call_original
+
+ # Run the down migration. This will remove the 3 approval rules we create
+ # above, and call MergeRequests::SyncCodeOwnerApprovalRules to recreate
+ # new ones. However, as there is no CODEOWNERS file in this test
+ # context, no approval rules will be created, so we can expect
+ # approval_rules.count to be changed by -3.
+ #
+ expect { disable_migrations_output { migration.down } }
+ .to change { approval_rules.count }.by(-3)
+
+ # Test that the index does not allow us to create the same rules as the
+ # previous sectional index.
+ #
+ expect { create_sectional_approval_rules }
+ .to raise_exception(ActiveRecord::RecordNotUnique)
+ expect { create_two_matching_nil_section_approval_rules }
+ .to raise_exception(ActiveRecord::RecordNotUnique)
+
+ expect(index_names).not_to include(described_class::SECTIONAL_INDEX_NAME)
+ expect(index_names).to include(
+ described_class::LEGACY_INDEX_NAME_RULE_TYPE,
+ described_class::LEGACY_INDEX_NAME_CODE_OWNERS
+ )
+ end
+ end
+ end
+end
diff --git a/spec/migrations/20200703125016_backfill_namespace_settings_spec.rb b/spec/migrations/20200703125016_backfill_namespace_settings_spec.rb
new file mode 100644
index 00000000000..7b84ef9e236
--- /dev/null
+++ b/spec/migrations/20200703125016_backfill_namespace_settings_spec.rb
@@ -0,0 +1,30 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require Rails.root.join('db', 'post_migrate', '20200703125016_backfill_namespace_settings.rb')
+
+RSpec.describe BackfillNamespaceSettings, :sidekiq, schema: 20200703124823 do
+ let(:namespaces) { table(:namespaces) }
+
+ describe '#up' do
+ before do
+ stub_const("#{described_class}::BATCH_SIZE", 2)
+
+ namespaces.create!(id: 1, name: 'test1', path: 'test1')
+ namespaces.create!(id: 2, name: 'test2', path: 'test2')
+ namespaces.create!(id: 3, name: 'test3', path: 'test3')
+ end
+
+ it 'schedules BackfillNamespaceSettings background jobs' do
+ Sidekiq::Testing.fake! do
+ Timecop.freeze do
+ migrate!
+
+ expect(described_class::MIGRATION).to be_scheduled_delayed_migration(2.minutes, 1, 2)
+ expect(described_class::MIGRATION).to be_scheduled_delayed_migration(4.minutes, 3, 3)
+ expect(BackgroundMigrationWorker.jobs.size).to eq(2)
+ end
+ end
+ end
+ end
+end
diff --git a/spec/migrations/20200706035141_adjust_unique_index_alert_management_alerts_spec.rb b/spec/migrations/20200706035141_adjust_unique_index_alert_management_alerts_spec.rb
new file mode 100644
index 00000000000..0068571ad0d
--- /dev/null
+++ b/spec/migrations/20200706035141_adjust_unique_index_alert_management_alerts_spec.rb
@@ -0,0 +1,57 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require Rails.root.join('db', 'migrate', '20200706035141_adjust_unique_index_alert_management_alerts.rb')
+
+RSpec.describe AdjustUniqueIndexAlertManagementAlerts, :migration do
+ let(:migration) { described_class.new }
+ let(:alerts) { AlertManagement::Alert }
+ let(:project) { create_project }
+ let(:other_project) { create_project }
+ let(:resolved_state) { 2 }
+ let(:triggered_state) { 1 }
+ let!(:existing_alert) { create_alert(project, resolved_state, '1234', 1) }
+ let!(:p2_alert) { create_alert(other_project, resolved_state, '1234', 1) }
+ let!(:p2_alert_diff_fingerprint) { create_alert(other_project, resolved_state, '4567', 2) }
+
+ it 'can reverse the migration' do
+ expect(existing_alert.fingerprint).not_to eq(nil)
+ expect(p2_alert.fingerprint).not_to eq(nil)
+ expect(p2_alert_diff_fingerprint.fingerprint).not_to eq(nil)
+
+ migrate!
+
+ # Adding a second alert with the same fingerprint now that we can
+ second_alert = create_alert(project, triggered_state, '1234', 2)
+ expect(alerts.count).to eq(4)
+
+ schema_migrate_down!
+
+ # We keep the alerts, but the oldest ones fingerprint is removed
+ expect(alerts.count).to eq(4)
+ expect(second_alert.reload.fingerprint).not_to eq(nil)
+ expect(p2_alert.fingerprint).not_to eq(nil)
+ expect(p2_alert_diff_fingerprint.fingerprint).not_to eq(nil)
+ expect(existing_alert.reload.fingerprint).to eq(nil)
+ end
+
+ def namespace
+ @namespace ||= table(:namespaces).create!(name: 'foo', path: 'foo')
+ end
+
+ def create_project
+ table(:projects).create!(namespace_id: namespace.id)
+ end
+
+ def create_alert(project, status, fingerprint, iid)
+ params = {
+ title: 'test',
+ started_at: Time.current,
+ iid: iid,
+ project_id: project.id,
+ status: status,
+ fingerprint: fingerprint
+ }
+ table(:alert_management_alerts).create!(params)
+ end
+end
diff --git a/spec/migrations/active_record/schema_spec.rb b/spec/migrations/active_record/schema_spec.rb
index a3348424f47..8199f55f5fc 100644
--- a/spec/migrations/active_record/schema_spec.rb
+++ b/spec/migrations/active_record/schema_spec.rb
@@ -5,7 +5,7 @@ require 'spec_helper'
# Check consistency of db/structure.sql version, migrations' timestamps, and the latest migration timestamp
# stored in the database's schema_migrations table.
-describe ActiveRecord::Schema, schema: :latest do
+RSpec.describe ActiveRecord::Schema, schema: :latest do
let(:all_migrations) do
migrations_paths = %w[db/migrate db/post_migrate]
.map { |path| Rails.root.join(*path, '*') }
@@ -25,6 +25,6 @@ describe ActiveRecord::Schema, schema: :latest do
it 'the schema_migrations table contains all schema versions' do
versions = ActiveRecord::Base.connection.execute('SELECT version FROM schema_migrations ORDER BY version').map { |m| Integer(m['version']) }
- expect(versions).to eq(all_migrations)
+ expect(versions).to match_array(all_migrations)
end
end
diff --git a/spec/migrations/add_default_and_free_plans_spec.rb b/spec/migrations/add_default_and_free_plans_spec.rb
index dffdeb8e71a..75787896999 100644
--- a/spec/migrations/add_default_and_free_plans_spec.rb
+++ b/spec/migrations/add_default_and_free_plans_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'migrate', '20191023152913_add_default_and_free_plans.rb')
-describe AddDefaultAndFreePlans do
+RSpec.describe AddDefaultAndFreePlans do
describe 'migrate' do
let(:plans) { table(:plans) }
diff --git a/spec/migrations/add_default_value_stream_to_groups_with_group_stages_spec.rb b/spec/migrations/add_default_value_stream_to_groups_with_group_stages_spec.rb
new file mode 100644
index 00000000000..31dee29a39b
--- /dev/null
+++ b/spec/migrations/add_default_value_stream_to_groups_with_group_stages_spec.rb
@@ -0,0 +1,44 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require Rails.root.join('db', 'post_migrate', '20200701070435_add_default_value_stream_to_groups_with_group_stages.rb')
+
+RSpec.describe AddDefaultValueStreamToGroupsWithGroupStages, schema: 20200624142207 do
+ let(:groups) { table(:namespaces) }
+ let(:group_stages) { table(:analytics_cycle_analytics_group_stages) }
+ let(:value_streams) { table(:analytics_cycle_analytics_group_value_streams) }
+
+ let!(:group) { groups.create!(name: 'test', path: 'path', type: 'Group') }
+ let!(:group_stage) { group_stages.create!(name: 'test', group_id: group.id, start_event_identifier: 1, end_event_identifier: 2) }
+
+ describe '#up' do
+ it 'creates default value stream record for the group' do
+ migrate!
+
+ group_value_streams = value_streams.where(group_id: group.id)
+ expect(group_value_streams.size).to eq(1)
+
+ value_stream = group_value_streams.first
+ expect(value_stream.name).to eq('default')
+ end
+
+ it 'migrates existing stages to the default value stream' do
+ migrate!
+
+ group_stage.reload
+
+ value_stream = value_streams.find_by(group_id: group.id, name: 'default')
+ expect(group_stage.group_value_stream_id).to eq(value_stream.id)
+ end
+ end
+
+ describe '#down' do
+ it 'sets the group_value_stream_id to nil' do
+ described_class.new.down
+
+ group_stage.reload
+
+ expect(group_stage.group_value_stream_id).to be_nil
+ end
+ end
+end
diff --git a/spec/migrations/add_deploy_token_type_to_deploy_tokens_spec.rb b/spec/migrations/add_deploy_token_type_to_deploy_tokens_spec.rb
index 3bc3d3f8ee2..f1fe27e72a1 100644
--- a/spec/migrations/add_deploy_token_type_to_deploy_tokens_spec.rb
+++ b/spec/migrations/add_deploy_token_type_to_deploy_tokens_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'migrate', '20200122161638_add_deploy_token_type_to_deploy_tokens.rb')
-describe AddDeployTokenTypeToDeployTokens do
+RSpec.describe AddDeployTokenTypeToDeployTokens do
let(:deploy_tokens) { table(:deploy_tokens) }
let(:deploy_token) do
deploy_tokens.create(name: 'token_test',
diff --git a/spec/migrations/add_incident_settings_to_all_existing_projects_spec.rb b/spec/migrations/add_incident_settings_to_all_existing_projects_spec.rb
index 507b1a8d580..dab42c0ffc3 100644
--- a/spec/migrations/add_incident_settings_to_all_existing_projects_spec.rb
+++ b/spec/migrations/add_incident_settings_to_all_existing_projects_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'migrate', '20200609212701_add_incident_settings_to_all_existing_projects.rb')
-describe AddIncidentSettingsToAllExistingProjects, :migration do
+RSpec.describe AddIncidentSettingsToAllExistingProjects, :migration do
let(:project_incident_management_settings) { table(:project_incident_management_settings) }
let(:labels) { table(:labels) }
let(:label_links) { table(:label_links) }
@@ -67,7 +67,7 @@ describe AddIncidentSettingsToAllExistingProjects, :migration do
context 'when project has incident labels' do
before do
issue = issues.create!(project_id: project.id)
- incident_label_attrs = IncidentManagement::CreateIssueService::INCIDENT_LABEL
+ incident_label_attrs = IncidentManagement::CreateIncidentLabelService::LABEL_PROPERTIES
incident_label = labels.create!(project_id: project.id, **incident_label_attrs)
label_links.create!(target_id: issue.id, label_id: incident_label.id, target_type: 'Issue')
end
diff --git a/spec/migrations/add_temporary_partial_index_on_project_id_to_services_spec.rb b/spec/migrations/add_temporary_partial_index_on_project_id_to_services_spec.rb
index ce790b0266c..1be699f5636 100644
--- a/spec/migrations/add_temporary_partial_index_on_project_id_to_services_spec.rb
+++ b/spec/migrations/add_temporary_partial_index_on_project_id_to_services_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20200114112932_add_temporary_partial_index_on_project_id_to_services.rb')
-describe AddTemporaryPartialIndexOnProjectIdToServices do
+RSpec.describe AddTemporaryPartialIndexOnProjectIdToServices do
let(:migration) { described_class.new }
describe '#up' do
diff --git a/spec/migrations/add_unique_constraint_to_approvals_user_id_and_merge_request_id_spec.rb b/spec/migrations/add_unique_constraint_to_approvals_user_id_and_merge_request_id_spec.rb
index 74830ab4ce2..6c04be5fc60 100644
--- a/spec/migrations/add_unique_constraint_to_approvals_user_id_and_merge_request_id_spec.rb
+++ b/spec/migrations/add_unique_constraint_to_approvals_user_id_and_merge_request_id_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20190404143330_add_unique_constraint_to_approvals_user_id_and_merge_request_id.rb')
-describe AddUniqueConstraintToApprovalsUserIdAndMergeRequestId do
+RSpec.describe AddUniqueConstraintToApprovalsUserIdAndMergeRequestId do
let(:migration) { described_class.new }
let(:namespaces) { table(:namespaces) }
let(:projects) { table(:projects) }
diff --git a/spec/migrations/backfill_and_add_not_null_constraint_to_released_at_column_on_releases_table_spec.rb b/spec/migrations/backfill_and_add_not_null_constraint_to_released_at_column_on_releases_table_spec.rb
index 8fceba276ce..9d0ad5863c6 100644
--- a/spec/migrations/backfill_and_add_not_null_constraint_to_released_at_column_on_releases_table_spec.rb
+++ b/spec/migrations/backfill_and_add_not_null_constraint_to_released_at_column_on_releases_table_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'migrate', '20190628185004_backfill_and_add_not_null_constraint_to_released_at_column_on_releases_table.rb')
-describe BackfillAndAddNotNullConstraintToReleasedAtColumnOnReleasesTable do
+RSpec.describe BackfillAndAddNotNullConstraintToReleasedAtColumnOnReleasesTable do
let(:releases) { table(:releases) }
let(:namespaces) { table(:namespaces) }
let(:projects) { table(:projects) }
diff --git a/spec/migrations/backfill_imported_snippet_repositories_spec.rb b/spec/migrations/backfill_imported_snippet_repositories_spec.rb
index c77978b23e4..208bda274e2 100644
--- a/spec/migrations/backfill_imported_snippet_repositories_spec.rb
+++ b/spec/migrations/backfill_imported_snippet_repositories_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20200608072931_backfill_imported_snippet_repositories.rb')
-describe BackfillImportedSnippetRepositories do
+RSpec.describe BackfillImportedSnippetRepositories do
let(:users) { table(:users) }
let(:snippets) { table(:snippets) }
let(:user) { users.create(id: 1, email: 'user@example.com', projects_limit: 10, username: 'test', name: 'Test', state: 'active') }
diff --git a/spec/migrations/backfill_operations_feature_flags_active_spec.rb b/spec/migrations/backfill_operations_feature_flags_active_spec.rb
index c51ed9fea8c..4ec2a4a2a92 100644
--- a/spec/migrations/backfill_operations_feature_flags_active_spec.rb
+++ b/spec/migrations/backfill_operations_feature_flags_active_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'migrate', '20191213184609_backfill_operations_feature_flags_active.rb')
-describe BackfillOperationsFeatureFlagsActive do
+RSpec.describe BackfillOperationsFeatureFlagsActive do
let(:namespaces) { table(:namespaces) }
let(:projects) { table(:projects) }
let(:flags) { table(:operations_feature_flags) }
diff --git a/spec/migrations/backfill_operations_feature_flags_iid_spec.rb b/spec/migrations/backfill_operations_feature_flags_iid_spec.rb
index 4628780787c..bafe5830652 100644
--- a/spec/migrations/backfill_operations_feature_flags_iid_spec.rb
+++ b/spec/migrations/backfill_operations_feature_flags_iid_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20200117194850_backfill_operations_feature_flags_iid.rb')
-describe BackfillOperationsFeatureFlagsIid do
+RSpec.describe BackfillOperationsFeatureFlagsIid do
let(:namespaces) { table(:namespaces) }
let(:projects) { table(:projects) }
let(:flags) { table(:operations_feature_flags) }
diff --git a/spec/migrations/backfill_releases_table_updated_at_and_add_not_null_constraints_to_timestamps_spec.rb b/spec/migrations/backfill_releases_table_updated_at_and_add_not_null_constraints_to_timestamps_spec.rb
index bf9a8154e1e..4c3517bc574 100644
--- a/spec/migrations/backfill_releases_table_updated_at_and_add_not_null_constraints_to_timestamps_spec.rb
+++ b/spec/migrations/backfill_releases_table_updated_at_and_add_not_null_constraints_to_timestamps_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'migrate', '20190920194925_backfill_releases_table_updated_at_and_add_not_null_constraints_to_timestamps.rb')
-describe BackfillReleasesTableUpdatedAtAndAddNotNullConstraintsToTimestamps do
+RSpec.describe BackfillReleasesTableUpdatedAtAndAddNotNullConstraintsToTimestamps do
let(:releases) { table(:releases) }
let(:namespaces) { table(:namespaces) }
let(:projects) { table(:projects) }
diff --git a/spec/migrations/backfill_snippet_repositories_spec.rb b/spec/migrations/backfill_snippet_repositories_spec.rb
index e87bf7376dd..084faa16a37 100644
--- a/spec/migrations/backfill_snippet_repositories_spec.rb
+++ b/spec/migrations/backfill_snippet_repositories_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20200420094444_backfill_snippet_repositories.rb')
-describe BackfillSnippetRepositories do
+RSpec.describe BackfillSnippetRepositories do
let(:users) { table(:users) }
let(:snippets) { table(:snippets) }
let(:user) { users.create(id: 1, email: 'user@example.com', projects_limit: 10, username: 'test', name: 'Test', state: 'active') }
diff --git a/spec/migrations/backfill_status_page_published_incidents_spec.rb b/spec/migrations/backfill_status_page_published_incidents_spec.rb
index ccdc8be4168..2b1ab891038 100644
--- a/spec/migrations/backfill_status_page_published_incidents_spec.rb
+++ b/spec/migrations/backfill_status_page_published_incidents_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20200421195234_backfill_status_page_published_incidents.rb')
-describe BackfillStatusPagePublishedIncidents, :migration do
+RSpec.describe BackfillStatusPagePublishedIncidents, :migration do
subject(:migration) { described_class.new }
describe '#up' do
diff --git a/spec/migrations/backport_enterprise_schema_spec.rb b/spec/migrations/backport_enterprise_schema_spec.rb
index c167301e1e3..b76b53dc259 100644
--- a/spec/migrations/backport_enterprise_schema_spec.rb
+++ b/spec/migrations/backport_enterprise_schema_spec.rb
@@ -4,7 +4,7 @@ require 'spec_helper'
require Rails.root.join('db', 'migrate', '20190402150158_backport_enterprise_schema.rb')
-describe BackportEnterpriseSchema, schema: 20190329085614 do
+RSpec.describe BackportEnterpriseSchema, schema: 20190329085614 do
include MigrationsHelpers
def drop_if_exists(table)
diff --git a/spec/migrations/cap_designs_filename_length_to_new_limit_spec.rb b/spec/migrations/cap_designs_filename_length_to_new_limit_spec.rb
index daa07953cb5..523c8864b63 100644
--- a/spec/migrations/cap_designs_filename_length_to_new_limit_spec.rb
+++ b/spec/migrations/cap_designs_filename_length_to_new_limit_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20200602013901_cap_designs_filename_length_to_new_limit')
-describe CapDesignsFilenameLengthToNewLimit, :migration, schema: 20200528125905 do
+RSpec.describe CapDesignsFilenameLengthToNewLimit, :migration, schema: 20200528125905 do
let(:namespaces) { table(:namespaces) }
let(:projects) { table(:projects) }
let(:issues) { table(:issues) }
diff --git a/spec/migrations/change_outbound_local_requests_whitelist_default_spec.rb b/spec/migrations/change_outbound_local_requests_whitelist_default_spec.rb
index dd45cac4a70..53b02425010 100644
--- a/spec/migrations/change_outbound_local_requests_whitelist_default_spec.rb
+++ b/spec/migrations/change_outbound_local_requests_whitelist_default_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'migrate', '20190725012225_change_outbound_local_requests_whitelist_default.rb')
-describe ChangeOutboundLocalRequestsWhitelistDefault do
+RSpec.describe ChangeOutboundLocalRequestsWhitelistDefault do
let(:application_settings) { table(:application_settings) }
it 'defaults to empty array' do
diff --git a/spec/migrations/change_packages_size_defaults_in_project_statistics_spec.rb b/spec/migrations/change_packages_size_defaults_in_project_statistics_spec.rb
index c36506643de..8614bfea7cb 100644
--- a/spec/migrations/change_packages_size_defaults_in_project_statistics_spec.rb
+++ b/spec/migrations/change_packages_size_defaults_in_project_statistics_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'migrate', '20190516155724_change_packages_size_defaults_in_project_statistics.rb')
-describe ChangePackagesSizeDefaultsInProjectStatistics do
+RSpec.describe ChangePackagesSizeDefaultsInProjectStatistics do
let(:project_statistics) { table(:project_statistics) }
let(:projects) { table(:projects) }
diff --git a/spec/migrations/clean_grafana_url_spec.rb b/spec/migrations/clean_grafana_url_spec.rb
index f6ea88a6f8d..caaf44b884d 100644
--- a/spec/migrations/clean_grafana_url_spec.rb
+++ b/spec/migrations/clean_grafana_url_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'migrate', '20200214085940_clean_grafana_url.rb')
-describe CleanGrafanaUrl do
+RSpec.describe CleanGrafanaUrl do
let(:application_settings_table) { table(:application_settings) }
[
diff --git a/spec/migrations/clean_up_noteable_id_for_notes_on_commits_spec.rb b/spec/migrations/clean_up_noteable_id_for_notes_on_commits_spec.rb
index 602e1c1fe93..531c1dbb76a 100644
--- a/spec/migrations/clean_up_noteable_id_for_notes_on_commits_spec.rb
+++ b/spec/migrations/clean_up_noteable_id_for_notes_on_commits_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20190313092516_clean_up_noteable_id_for_notes_on_commits.rb')
-describe CleanUpNoteableIdForNotesOnCommits do
+RSpec.describe CleanUpNoteableIdForNotesOnCommits do
let(:notes) { table(:notes) }
before do
diff --git a/spec/migrations/cleanup_empty_commit_user_mentions_spec.rb b/spec/migrations/cleanup_empty_commit_user_mentions_spec.rb
index 529fe046e32..da714e7da4c 100644
--- a/spec/migrations/cleanup_empty_commit_user_mentions_spec.rb
+++ b/spec/migrations/cleanup_empty_commit_user_mentions_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20200128133510_cleanup_empty_commit_user_mentions')
-describe CleanupEmptyCommitUserMentions, :migration, :sidekiq do
+RSpec.describe CleanupEmptyCommitUserMentions, :migration, :sidekiq do
let(:users) { table(:users) }
let(:namespaces) { table(:namespaces) }
let(:projects) { table(:projects) }
diff --git a/spec/migrations/cleanup_legacy_artifact_migration_spec.rb b/spec/migrations/cleanup_legacy_artifact_migration_spec.rb
index 29a5c34373c..83b88a0cf1a 100644
--- a/spec/migrations/cleanup_legacy_artifact_migration_spec.rb
+++ b/spec/migrations/cleanup_legacy_artifact_migration_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'migrate', '20190104182041_cleanup_legacy_artifact_migration.rb')
-describe CleanupLegacyArtifactMigration, :redis do
+RSpec.describe CleanupLegacyArtifactMigration, :redis do
let(:migration) { spy('migration') }
context 'when still legacy artifacts exist' do
diff --git a/spec/migrations/cleanup_optimistic_locking_nulls_pt2_fixed_spec.rb b/spec/migrations/cleanup_optimistic_locking_nulls_pt2_fixed_spec.rb
index 2e5e450afc7..7c7f87d4e06 100644
--- a/spec/migrations/cleanup_optimistic_locking_nulls_pt2_fixed_spec.rb
+++ b/spec/migrations/cleanup_optimistic_locking_nulls_pt2_fixed_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20200427064130_cleanup_optimistic_locking_nulls_pt2_fixed.rb')
-describe CleanupOptimisticLockingNullsPt2Fixed, :migration do
+RSpec.describe CleanupOptimisticLockingNullsPt2Fixed, :migration, schema: 20200219193117 do
test_tables = %w(ci_stages ci_builds ci_pipelines).freeze
test_tables.each do |table|
let(table.to_sym) { table(table.to_sym) }
diff --git a/spec/migrations/cleanup_optimistic_locking_nulls_spec.rb b/spec/migrations/cleanup_optimistic_locking_nulls_spec.rb
index 6e541c903ff..bcdcd3e9273 100644
--- a/spec/migrations/cleanup_optimistic_locking_nulls_spec.rb
+++ b/spec/migrations/cleanup_optimistic_locking_nulls_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20200128210353_cleanup_optimistic_locking_nulls')
-describe CleanupOptimisticLockingNulls do
+RSpec.describe CleanupOptimisticLockingNulls do
let(:epics) { table(:epics) }
let(:merge_requests) { table(:merge_requests) }
let(:issues) { table(:issues) }
diff --git a/spec/migrations/cleanup_projects_with_missing_namespace_spec.rb b/spec/migrations/cleanup_projects_with_missing_namespace_spec.rb
index 27c954d2984..cef6e0f470f 100644
--- a/spec/migrations/cleanup_projects_with_missing_namespace_spec.rb
+++ b/spec/migrations/cleanup_projects_with_missing_namespace_spec.rb
@@ -47,7 +47,7 @@ class SchemaVersionFinder
end
end
-describe CleanupProjectsWithMissingNamespace, :migration, schema: SchemaVersionFinder.previous_migration do
+RSpec.describe CleanupProjectsWithMissingNamespace, :migration, schema: SchemaVersionFinder.previous_migration do
let(:projects) { table(:projects) }
let(:namespaces) { table(:namespaces) }
let(:users) { table(:users) }
diff --git a/spec/migrations/confirm_project_bot_users_spec.rb b/spec/migrations/confirm_project_bot_users_spec.rb
new file mode 100644
index 00000000000..11aa08d16a9
--- /dev/null
+++ b/spec/migrations/confirm_project_bot_users_spec.rb
@@ -0,0 +1,84 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require Rails.root.join('db', 'post_migrate', '20200713071042_confirm_project_bot_users.rb')
+
+RSpec.describe ConfirmProjectBotUsers, :migration do
+ let(:users) { table(:users) }
+
+ context 'project bot users that are currently unconfirmed' do
+ let!(:project_bot_1) do
+ create_user!(
+ name: 'bot_1',
+ email: 'bot_1@example.com',
+ created_at: 2.days.ago,
+ user_type: described_class::User::USER_TYPE_PROJECT_BOT
+ )
+ end
+
+ let!(:project_bot_2) do
+ create_user!(
+ name: 'bot_2',
+ email: 'bot_2@example.com',
+ created_at: 4.days.ago,
+ user_type: described_class::User::USER_TYPE_PROJECT_BOT
+ )
+ end
+
+ it 'updates their `confirmed_at` attribute' do
+ expect { migrate! }
+ .to change { project_bot_1.reload.confirmed_at }
+ .and change { project_bot_2.reload.confirmed_at }
+ end
+
+ it 'sets `confirmed_at` to be the same as their `created_at` attribute' do
+ migrate!
+
+ [project_bot_1, project_bot_2].each do |bot|
+ expect(bot.reload.confirmed_at).to eq(bot.created_at)
+ end
+ end
+ end
+
+ context 'project bot users that are currently confirmed' do
+ let!(:confirmed_project_bot) do
+ create_user!(
+ name: 'bot_1',
+ email: 'bot_1@example.com',
+ user_type: described_class::User::USER_TYPE_PROJECT_BOT,
+ confirmed_at: 1.day.ago
+ )
+ end
+
+ it 'does not update their `confirmed_at` attribute' do
+ expect { migrate! }.not_to change { confirmed_project_bot.reload.confirmed_at }
+ end
+ end
+
+ context 'human users that are currently unconfirmed' do
+ let!(:unconfirmed_human) do
+ create_user!(
+ name: 'human',
+ email: 'human@example.com',
+ user_type: nil
+ )
+ end
+
+ it 'does not update their `confirmed_at` attribute' do
+ expect { migrate! }.not_to change { unconfirmed_human.reload.confirmed_at }
+ end
+ end
+
+ private
+
+ def create_user!(name:, email:, user_type:, created_at: Time.now, confirmed_at: nil)
+ users.create!(
+ name: name,
+ email: email,
+ username: name,
+ projects_limit: 0,
+ user_type: user_type,
+ confirmed_at: confirmed_at
+ )
+ end
+end
diff --git a/spec/migrations/create_environment_for_self_monitoring_project_spec.rb b/spec/migrations/create_environment_for_self_monitoring_project_spec.rb
index aee0651dee0..1ba464f1610 100644
--- a/spec/migrations/create_environment_for_self_monitoring_project_spec.rb
+++ b/spec/migrations/create_environment_for_self_monitoring_project_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20200214214934_create_environment_for_self_monitoring_project')
-describe CreateEnvironmentForSelfMonitoringProject do
+RSpec.describe CreateEnvironmentForSelfMonitoringProject do
let(:application_settings_table) { table(:application_settings) }
let(:environments) { table(:environments) }
diff --git a/spec/migrations/delete_internal_ids_where_feature_flags_usage_spec.rb b/spec/migrations/delete_internal_ids_where_feature_flags_usage_spec.rb
index 6eecd0870ed..a58121fb708 100644
--- a/spec/migrations/delete_internal_ids_where_feature_flags_usage_spec.rb
+++ b/spec/migrations/delete_internal_ids_where_feature_flags_usage_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20200117194900_delete_internal_ids_where_feature_flags_usage')
-describe DeleteInternalIdsWhereFeatureFlagsUsage do
+RSpec.describe DeleteInternalIdsWhereFeatureFlagsUsage do
let(:namespaces) { table(:namespaces) }
let(:projects) { table(:projects) }
let(:internal_ids) { table(:internal_ids) }
diff --git a/spec/migrations/delete_template_project_services_spec.rb b/spec/migrations/delete_template_project_services_spec.rb
index 3c6709ec310..aacc4fcfd58 100644
--- a/spec/migrations/delete_template_project_services_spec.rb
+++ b/spec/migrations/delete_template_project_services_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'migrate', '20200305151736_delete_template_project_services.rb')
-describe DeleteTemplateProjectServices, :migration do
+RSpec.describe DeleteTemplateProjectServices, :migration do
let(:services) { table(:services) }
let(:project) { table(:projects).create!(namespace_id: 1) }
diff --git a/spec/migrations/delete_template_services_duplicated_by_type_spec.rb b/spec/migrations/delete_template_services_duplicated_by_type_spec.rb
index 64da0664e2c..5bcbfb7c733 100644
--- a/spec/migrations/delete_template_services_duplicated_by_type_spec.rb
+++ b/spec/migrations/delete_template_services_duplicated_by_type_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'migrate', '20200304160801_delete_template_services_duplicated_by_type.rb')
-describe DeleteTemplateServicesDuplicatedByType do
+RSpec.describe DeleteTemplateServicesDuplicatedByType do
let(:services) { table(:services) }
before do
diff --git a/spec/migrations/delete_user_callout_alerts_moved_spec.rb b/spec/migrations/delete_user_callout_alerts_moved_spec.rb
new file mode 100644
index 00000000000..f6b1a8982fb
--- /dev/null
+++ b/spec/migrations/delete_user_callout_alerts_moved_spec.rb
@@ -0,0 +1,30 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require Rails.root.join('db', 'post_migrate', '20200710102418_delete_user_callout_alerts_moved.rb')
+
+RSpec.describe DeleteUserCalloutAlertsMoved do
+ let(:users) { table(:users) }
+ let(:user_callouts) { table(:user_callouts) }
+ let(:alerts_moved_feature) { described_class::FEATURE_NAME_ALERTS_MOVED }
+ let(:unrelated_feature) { 1 }
+
+ let!(:user1) { users.create!(email: '1', projects_limit: 0) }
+ let!(:user2) { users.create!(email: '2', projects_limit: 0) }
+
+ subject(:migration) { described_class.new }
+
+ before do
+ user_callouts.create!(user_id: user1.id, feature_name: alerts_moved_feature)
+ user_callouts.create!(user_id: user1.id, feature_name: unrelated_feature)
+ user_callouts.create!(user_id: user2.id, feature_name: alerts_moved_feature)
+ end
+
+ describe '#up' do
+ it 'deletes `alerts_moved` user callouts' do
+ migration.up
+
+ expect(user_callouts.all.map(&:feature_name)).to eq([unrelated_feature])
+ end
+ end
+end
diff --git a/spec/migrations/drop_activate_prometheus_services_background_jobs_spec.rb b/spec/migrations/drop_activate_prometheus_services_background_jobs_spec.rb
index a02a0819a7b..b4bbadd199e 100644
--- a/spec/migrations/drop_activate_prometheus_services_background_jobs_spec.rb
+++ b/spec/migrations/drop_activate_prometheus_services_background_jobs_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'migrate', '20200221144534_drop_activate_prometheus_services_background_jobs.rb')
-describe DropActivatePrometheusServicesBackgroundJobs, :sidekiq, :redis, schema: 2020_02_21_144534 do
+RSpec.describe DropActivatePrometheusServicesBackgroundJobs, :sidekiq, :redis, schema: 2020_02_21_144534 do
subject(:migration) { described_class.new }
describe '#up' do
diff --git a/spec/migrations/drop_background_migration_jobs_spec.rb b/spec/migrations/drop_background_migration_jobs_spec.rb
index d9e0561f326..2896f4ca0eb 100644
--- a/spec/migrations/drop_background_migration_jobs_spec.rb
+++ b/spec/migrations/drop_background_migration_jobs_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'migrate', '20200116051619_drop_background_migration_jobs.rb')
-describe DropBackgroundMigrationJobs, :sidekiq, :redis, schema: 2020_01_16_051619 do
+RSpec.describe DropBackgroundMigrationJobs, :sidekiq, :redis, schema: 2020_01_16_051619 do
subject(:migration) { described_class.new }
describe '#up' do
diff --git a/spec/migrations/drop_project_ci_cd_settings_merge_trains_enabled_spec.rb b/spec/migrations/drop_project_ci_cd_settings_merge_trains_enabled_spec.rb
index 9166f626922..3f6aae401be 100644
--- a/spec/migrations/drop_project_ci_cd_settings_merge_trains_enabled_spec.rb
+++ b/spec/migrations/drop_project_ci_cd_settings_merge_trains_enabled_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20191128162854_drop_project_ci_cd_settings_merge_trains_enabled.rb')
-describe DropProjectCiCdSettingsMergeTrainsEnabled do
+RSpec.describe DropProjectCiCdSettingsMergeTrainsEnabled do
let!(:project_ci_cd_setting) { table(:project_ci_cd_settings) }
it 'correctly migrates up and down' do
diff --git a/spec/migrations/encrypt_feature_flags_clients_tokens_spec.rb b/spec/migrations/encrypt_feature_flags_clients_tokens_spec.rb
index 9b139c4b57b..ad83119f324 100644
--- a/spec/migrations/encrypt_feature_flags_clients_tokens_spec.rb
+++ b/spec/migrations/encrypt_feature_flags_clients_tokens_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20190606175050_encrypt_feature_flags_clients_tokens.rb')
-describe EncryptFeatureFlagsClientsTokens do
+RSpec.describe EncryptFeatureFlagsClientsTokens do
let(:migration) { described_class.new }
let(:feature_flags_clients) { table(:operations_feature_flags_clients) }
let(:projects) { table(:projects) }
diff --git a/spec/migrations/encrypt_plaintext_attributes_on_application_settings_spec.rb b/spec/migrations/encrypt_plaintext_attributes_on_application_settings_spec.rb
index fda810d1da9..ff5aa81b5b5 100644
--- a/spec/migrations/encrypt_plaintext_attributes_on_application_settings_spec.rb
+++ b/spec/migrations/encrypt_plaintext_attributes_on_application_settings_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'migrate', '20191120115530_encrypt_plaintext_attributes_on_application_settings.rb')
-describe EncryptPlaintextAttributesOnApplicationSettings do
+RSpec.describe EncryptPlaintextAttributesOnApplicationSettings do
let(:migration) { described_class.new }
let(:application_settings) { table(:application_settings) }
let(:plaintext) { 'secret-token' }
diff --git a/spec/migrations/enqueue_reset_merge_status_second_run_spec.rb b/spec/migrations/enqueue_reset_merge_status_second_run_spec.rb
index d4cf3d15758..f5728534675 100644
--- a/spec/migrations/enqueue_reset_merge_status_second_run_spec.rb
+++ b/spec/migrations/enqueue_reset_merge_status_second_run_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20190620112608_enqueue_reset_merge_status_second_run.rb')
-describe EnqueueResetMergeStatusSecondRun do
+RSpec.describe EnqueueResetMergeStatusSecondRun do
let(:namespaces) { table(:namespaces) }
let(:projects) { table(:projects) }
let(:namespace) { namespaces.create(name: 'gitlab', path: 'gitlab-org') }
diff --git a/spec/migrations/enqueue_reset_merge_status_spec.rb b/spec/migrations/enqueue_reset_merge_status_spec.rb
index 9728ada14ba..683d2caf9ca 100644
--- a/spec/migrations/enqueue_reset_merge_status_spec.rb
+++ b/spec/migrations/enqueue_reset_merge_status_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20190528180441_enqueue_reset_merge_status.rb')
-describe EnqueueResetMergeStatus do
+RSpec.describe EnqueueResetMergeStatus do
let(:namespaces) { table(:namespaces) }
let(:projects) { table(:projects) }
let(:namespace) { namespaces.create(name: 'gitlab', path: 'gitlab-org') }
diff --git a/spec/migrations/fill_file_store_ci_job_artifacts_spec.rb b/spec/migrations/fill_file_store_ci_job_artifacts_spec.rb
index 5435a438824..64ab879d53c 100644
--- a/spec/migrations/fill_file_store_ci_job_artifacts_spec.rb
+++ b/spec/migrations/fill_file_store_ci_job_artifacts_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'migrate', '20200513235532_fill_file_store_ci_job_artifacts.rb')
-describe FillFileStoreCiJobArtifacts do
+RSpec.describe FillFileStoreCiJobArtifacts do
let(:namespaces) { table(:namespaces) }
let(:projects) { table(:projects) }
let(:builds) { table(:ci_builds) }
diff --git a/spec/migrations/fill_file_store_lfs_objects_spec.rb b/spec/migrations/fill_file_store_lfs_objects_spec.rb
index e574eacca35..2a610e5311b 100644
--- a/spec/migrations/fill_file_store_lfs_objects_spec.rb
+++ b/spec/migrations/fill_file_store_lfs_objects_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'migrate', '20200513234502_fill_file_store_lfs_objects.rb')
-describe FillFileStoreLfsObjects do
+RSpec.describe FillFileStoreLfsObjects do
let(:lfs_objects) { table(:lfs_objects) }
let(:oid) { 'b804383982bb89b00e828e3f44c038cc991d3d1768009fc39ba8e2c081b9fb75' }
diff --git a/spec/migrations/fill_productivity_analytics_start_date_spec.rb b/spec/migrations/fill_productivity_analytics_start_date_spec.rb
index 4ae7b0eed24..5d7f0ffba50 100644
--- a/spec/migrations/fill_productivity_analytics_start_date_spec.rb
+++ b/spec/migrations/fill_productivity_analytics_start_date_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'migrate', '20191004081520_fill_productivity_analytics_start_date.rb')
-describe FillProductivityAnalyticsStartDate do
+RSpec.describe FillProductivityAnalyticsStartDate do
let(:settings_table) { table('application_settings') }
let(:metrics_table) { table('merge_request_metrics') }
diff --git a/spec/migrations/fill_store_uploads_spec.rb b/spec/migrations/fill_store_uploads_spec.rb
index 6a2a3c4ea8e..60eaf982c57 100644
--- a/spec/migrations/fill_store_uploads_spec.rb
+++ b/spec/migrations/fill_store_uploads_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'migrate', '20200513235347_fill_store_uploads.rb')
-describe FillStoreUploads do
+RSpec.describe FillStoreUploads do
let(:uploads) { table(:uploads) }
let(:path) { 'uploads/-/system/avatar.jpg' }
diff --git a/spec/migrations/fix_max_pages_size_spec.rb b/spec/migrations/fix_max_pages_size_spec.rb
index 9882cda7fba..b44ce21b858 100644
--- a/spec/migrations/fix_max_pages_size_spec.rb
+++ b/spec/migrations/fix_max_pages_size_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'migrate', '20191213120427_fix_max_pages_size.rb')
-describe FixMaxPagesSize do
+RSpec.describe FixMaxPagesSize do
let(:application_settings) { table(:application_settings) }
let!(:default_setting) { application_settings.create! }
let!(:max_possible_setting) { application_settings.create!(max_pages_size: described_class::MAX_SIZE) }
diff --git a/spec/migrations/fix_null_type_labels_spec.rb b/spec/migrations/fix_null_type_labels_spec.rb
index b098141c5e9..fadc2a5d29e 100644
--- a/spec/migrations/fix_null_type_labels_spec.rb
+++ b/spec/migrations/fix_null_type_labels_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20190131122559_fix_null_type_labels')
-describe FixNullTypeLabels do
+RSpec.describe FixNullTypeLabels do
let(:migration) { described_class.new }
let(:projects) { table(:projects) }
let(:namespaces) { table(:namespaces) }
diff --git a/spec/migrations/fix_pool_repository_source_project_id_spec.rb b/spec/migrations/fix_pool_repository_source_project_id_spec.rb
index 5a878dba6e7..67a75b893ef 100644
--- a/spec/migrations/fix_pool_repository_source_project_id_spec.rb
+++ b/spec/migrations/fix_pool_repository_source_project_id_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'migrate', '20190604184643_fix_pool_repository_source_project_id.rb')
-describe FixPoolRepositorySourceProjectId do
+RSpec.describe FixPoolRepositorySourceProjectId do
let(:projects) { table(:projects) }
let(:pool_repositories) { table(:pool_repositories) }
let(:shards) { table(:shards) }
diff --git a/spec/migrations/fix_projects_without_project_feature_spec.rb b/spec/migrations/fix_projects_without_project_feature_spec.rb
index 01413261008..2a4ba3f3ca5 100644
--- a/spec/migrations/fix_projects_without_project_feature_spec.rb
+++ b/spec/migrations/fix_projects_without_project_feature_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20200127111840_fix_projects_without_project_feature.rb')
-describe FixProjectsWithoutProjectFeature do
+RSpec.describe FixProjectsWithoutProjectFeature do
let(:namespace) { table(:namespaces).create(name: 'gitlab', path: 'gitlab-org') }
let!(:projects) do
diff --git a/spec/migrations/fix_projects_without_prometheus_services_spec.rb b/spec/migrations/fix_projects_without_prometheus_services_spec.rb
index ecfad313f58..73ede2ad90c 100644
--- a/spec/migrations/fix_projects_without_prometheus_services_spec.rb
+++ b/spec/migrations/fix_projects_without_prometheus_services_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20200220115023_fix_projects_without_prometheus_service.rb')
-describe FixProjectsWithoutPrometheusService, :migration do
+RSpec.describe FixProjectsWithoutPrometheusService, :migration do
let(:namespace) { table(:namespaces).create(name: 'gitlab', path: 'gitlab-org') }
let!(:projects) do
diff --git a/spec/migrations/fix_wrong_pages_access_level_spec.rb b/spec/migrations/fix_wrong_pages_access_level_spec.rb
index e0d09add740..9ee5fa783a4 100644
--- a/spec/migrations/fix_wrong_pages_access_level_spec.rb
+++ b/spec/migrations/fix_wrong_pages_access_level_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20190703185326_fix_wrong_pages_access_level.rb')
-describe FixWrongPagesAccessLevel, :sidekiq_might_not_need_inline, schema: 20190628185004 do
+RSpec.describe FixWrongPagesAccessLevel, :sidekiq_might_not_need_inline, schema: 20190628185004 do
using RSpec::Parameterized::TableSyntax
let(:migration_class) { described_class::MIGRATION }
diff --git a/spec/migrations/generate_lets_encrypt_private_key_spec.rb b/spec/migrations/generate_lets_encrypt_private_key_spec.rb
index c0cb39fd519..cad257c18a0 100644
--- a/spec/migrations/generate_lets_encrypt_private_key_spec.rb
+++ b/spec/migrations/generate_lets_encrypt_private_key_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'migrate', '20190524062810_generate_lets_encrypt_private_key.rb')
-describe GenerateLetsEncryptPrivateKey do
+RSpec.describe GenerateLetsEncryptPrivateKey do
describe '#up' do
it 'does not fail' do
expect do
diff --git a/spec/migrations/generate_missing_routes_for_bots_spec.rb b/spec/migrations/generate_missing_routes_for_bots_spec.rb
new file mode 100644
index 00000000000..8af22042350
--- /dev/null
+++ b/spec/migrations/generate_missing_routes_for_bots_spec.rb
@@ -0,0 +1,80 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+require Rails.root.join('db', 'post_migrate', '20200703064117_generate_missing_routes_for_bots.rb')
+
+RSpec.describe GenerateMissingRoutesForBots, :migration do
+ let(:users) { table(:users) }
+ let(:namespaces) { table(:namespaces) }
+ let(:routes) { table(:routes) }
+
+ let(:visual_review_bot) do
+ users.create!(email: 'visual-review-bot@gitlab.com', name: 'GitLab Visual Review Bot', username: 'visual-review-bot', user_type: 3, projects_limit: 5)
+ end
+
+ let(:migration_bot) do
+ users.create!(email: 'migration-bot@gitlab.com', name: 'GitLab Migration Bot', username: 'migration-bot', user_type: 7, projects_limit: 5)
+ end
+
+ let!(:visual_review_bot_namespace) do
+ namespaces.create!(owner_id: visual_review_bot.id, name: visual_review_bot.name, path: visual_review_bot.username)
+ end
+
+ let!(:migration_bot_namespace) do
+ namespaces.create!(owner_id: migration_bot.id, name: migration_bot.name, path: migration_bot.username)
+ end
+
+ context 'for bot users without an existing route' do
+ it 'creates new routes' do
+ expect { migrate! }.to change { routes.count }.by(2)
+ end
+
+ it 'creates new routes with the same path and name as their namespace' do
+ migrate!
+
+ [visual_review_bot, migration_bot].each do |bot|
+ namespace = namespaces.find_by(owner_id: bot.id)
+ route = route_for(namespace: namespace)
+
+ expect(route.path).to eq(namespace.path)
+ expect(route.name).to eq(namespace.name)
+ end
+ end
+ end
+
+ it 'does not create routes for bot users with existing routes' do
+ create_route!(namespace: visual_review_bot_namespace)
+ create_route!(namespace: migration_bot_namespace)
+
+ expect { migrate! }.not_to change { routes.count }
+ end
+
+ it 'does not create routes for human users without an existing route' do
+ human_namespace = create_human_namespace!(name: 'GitLab Human', username: 'human')
+
+ expect { migrate! }.not_to change { route_for(namespace: human_namespace) }
+ end
+
+ it 'does not create route for a bot user with a missing route, if a human user with the same path already exists' do
+ human_namespace = create_human_namespace!(name: visual_review_bot.name, username: visual_review_bot.username)
+ create_route!(namespace: human_namespace)
+
+ expect { migrate! }.not_to change { route_for(namespace: visual_review_bot_namespace) }
+ end
+
+ private
+
+ def create_human_namespace!(name:, username:)
+ human = users.create!(email: 'human@gitlab.com', name: name, username: username, user_type: nil, projects_limit: 5)
+ namespaces.create!(owner_id: human.id, name: human.name, path: human.username)
+ end
+
+ def create_route!(namespace:)
+ routes.create!(path: namespace.path, name: namespace.name, source_id: namespace.id, source_type: 'Namespace')
+ end
+
+ def route_for(namespace:)
+ routes.find_by(source_type: 'Namespace', source_id: namespace.id)
+ end
+end
diff --git a/spec/migrations/insert_project_hooks_plan_limits_spec.rb b/spec/migrations/insert_project_hooks_plan_limits_spec.rb
index e4bdda4cf5e..09fae160a6f 100644
--- a/spec/migrations/insert_project_hooks_plan_limits_spec.rb
+++ b/spec/migrations/insert_project_hooks_plan_limits_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'migrate', '20191216183532_insert_project_hooks_plan_limits.rb')
-describe InsertProjectHooksPlanLimits do
+RSpec.describe InsertProjectHooksPlanLimits do
let(:migration) { described_class.new }
let(:plans) { table(:plans) }
let(:plan_limits) { table(:plan_limits) }
diff --git a/spec/migrations/migrate_auto_dev_ops_domain_to_cluster_domain_spec.rb b/spec/migrations/migrate_auto_dev_ops_domain_to_cluster_domain_spec.rb
index 2fe6f3f62a9..d0abc777326 100644
--- a/spec/migrations/migrate_auto_dev_ops_domain_to_cluster_domain_spec.rb
+++ b/spec/migrations/migrate_auto_dev_ops_domain_to_cluster_domain_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20190204115450_migrate_auto_dev_ops_domain_to_cluster_domain.rb')
-describe MigrateAutoDevOpsDomainToClusterDomain do
+RSpec.describe MigrateAutoDevOpsDomainToClusterDomain do
include MigrationHelpers::ClusterHelpers
let(:migration) { described_class.new }
diff --git a/spec/migrations/migrate_bot_type_to_user_type_spec.rb b/spec/migrations/migrate_bot_type_to_user_type_spec.rb
index 9686aae0cd3..2b85f2a7f69 100644
--- a/spec/migrations/migrate_bot_type_to_user_type_spec.rb
+++ b/spec/migrations/migrate_bot_type_to_user_type_spec.rb
@@ -4,7 +4,7 @@ require 'spec_helper'
require Rails.root.join('db', 'migrate', '20200311074438_migrate_bot_type_to_user_type.rb')
-describe MigrateBotTypeToUserType, :migration do
+RSpec.describe MigrateBotTypeToUserType, :migration do
let(:users) { table(:users) }
it 'updates bots & ignores humans' do
diff --git a/spec/migrations/migrate_code_owner_approval_status_to_protected_branches_in_batches_spec.rb b/spec/migrations/migrate_code_owner_approval_status_to_protected_branches_in_batches_spec.rb
index cda965135b0..70304a4deb2 100644
--- a/spec/migrations/migrate_code_owner_approval_status_to_protected_branches_in_batches_spec.rb
+++ b/spec/migrations/migrate_code_owner_approval_status_to_protected_branches_in_batches_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20190827102026_migrate_code_owner_approval_status_to_protected_branches_in_batches.rb')
-describe MigrateCodeOwnerApprovalStatusToProtectedBranchesInBatches do
+RSpec.describe MigrateCodeOwnerApprovalStatusToProtectedBranchesInBatches do
let(:namespaces) { table(:namespaces) }
let(:projects) { table(:projects) }
let(:protected_branches) { table(:protected_branches) }
diff --git a/spec/migrations/migrate_commit_notes_mentions_to_db_spec.rb b/spec/migrations/migrate_commit_notes_mentions_to_db_spec.rb
index dc40d0865f2..eb7fb7d4fc5 100644
--- a/spec/migrations/migrate_commit_notes_mentions_to_db_spec.rb
+++ b/spec/migrations/migrate_commit_notes_mentions_to_db_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20200128134110_migrate_commit_notes_mentions_to_db')
-describe MigrateCommitNotesMentionsToDb, :migration, :sidekiq do
+RSpec.describe MigrateCommitNotesMentionsToDb, :migration, :sidekiq do
let(:users) { table(:users) }
let(:namespaces) { table(:namespaces) }
let(:projects) { table(:projects) }
diff --git a/spec/migrations/migrate_create_commit_signature_worker_sidekiq_queue_spec.rb b/spec/migrations/migrate_create_commit_signature_worker_sidekiq_queue_spec.rb
index 3d7803b7563..5f7b4755980 100644
--- a/spec/migrations/migrate_create_commit_signature_worker_sidekiq_queue_spec.rb
+++ b/spec/migrations/migrate_create_commit_signature_worker_sidekiq_queue_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20200206091544_migrate_create_commit_signature_worker_sidekiq_queue.rb')
-describe MigrateCreateCommitSignatureWorkerSidekiqQueue, :sidekiq, :redis do
+RSpec.describe MigrateCreateCommitSignatureWorkerSidekiqQueue, :sidekiq, :redis do
include Gitlab::Database::MigrationHelpers
include StubWorker
diff --git a/spec/migrations/migrate_discussion_id_on_promoted_epics_spec.rb b/spec/migrations/migrate_discussion_id_on_promoted_epics_spec.rb
index 0a8975402da..e3119be495d 100644
--- a/spec/migrations/migrate_discussion_id_on_promoted_epics_spec.rb
+++ b/spec/migrations/migrate_discussion_id_on_promoted_epics_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20190715193142_migrate_discussion_id_on_promoted_epics.rb')
-describe MigrateDiscussionIdOnPromotedEpics do
+RSpec.describe MigrateDiscussionIdOnPromotedEpics do
let(:migration_class) { described_class::MIGRATION }
let(:migration_name) { migration_class.to_s.demodulize }
diff --git a/spec/migrations/migrate_k8s_service_integration_spec.rb b/spec/migrations/migrate_k8s_service_integration_spec.rb
index 660e958eb42..a1b2f2ae841 100644
--- a/spec/migrations/migrate_k8s_service_integration_spec.rb
+++ b/spec/migrations/migrate_k8s_service_integration_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20190517153211_migrate_k8s_service_integration.rb')
-describe MigrateK8sServiceIntegration do
+RSpec.describe MigrateK8sServiceIntegration do
context 'template service' do
context 'with namespace' do
let!(:service) do
diff --git a/spec/migrations/migrate_legacy_managed_clusters_to_unmanaged_spec.rb b/spec/migrations/migrate_legacy_managed_clusters_to_unmanaged_spec.rb
index e3462e1d6bd..c8bfeec8049 100644
--- a/spec/migrations/migrate_legacy_managed_clusters_to_unmanaged_spec.rb
+++ b/spec/migrations/migrate_legacy_managed_clusters_to_unmanaged_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20190606163724_migrate_legacy_managed_clusters_to_unmanaged.rb')
-describe MigrateLegacyManagedClustersToUnmanaged do
+RSpec.describe MigrateLegacyManagedClustersToUnmanaged do
let(:cluster_type) { 'project_type' }
let(:created_at) { 1.hour.ago }
diff --git a/spec/migrations/migrate_managed_clusters_with_no_token_to_unmanaged_spec.rb b/spec/migrations/migrate_managed_clusters_with_no_token_to_unmanaged_spec.rb
index 2931fba3eb2..1fc92f6ceea 100644
--- a/spec/migrations/migrate_managed_clusters_with_no_token_to_unmanaged_spec.rb
+++ b/spec/migrations/migrate_managed_clusters_with_no_token_to_unmanaged_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20190613231640_migrate_managed_clusters_with_no_token_to_unmanaged.rb')
-describe MigrateManagedClustersWithNoTokenToUnmanaged do
+RSpec.describe MigrateManagedClustersWithNoTokenToUnmanaged do
let(:cluster_type) { 'project_type' }
let(:created_at) { Date.new(2018, 11, 1).midnight }
diff --git a/spec/migrations/migrate_merge_request_mentions_to_db_spec.rb b/spec/migrations/migrate_merge_request_mentions_to_db_spec.rb
index aef8fd6490b..83388cc6314 100644
--- a/spec/migrations/migrate_merge_request_mentions_to_db_spec.rb
+++ b/spec/migrations/migrate_merge_request_mentions_to_db_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20200211155539_migrate_merge_request_mentions_to_db')
-describe MigrateMergeRequestMentionsToDb, :migration do
+RSpec.describe MigrateMergeRequestMentionsToDb, :migration do
let(:users) { table(:users) }
let(:projects) { table(:projects) }
let(:namespaces) { table(:namespaces) }
diff --git a/spec/migrations/migrate_ops_feature_flags_scopes_target_user_ids_spec.rb b/spec/migrations/migrate_ops_feature_flags_scopes_target_user_ids_spec.rb
index fc5d814a2de..b2c36db2e1d 100644
--- a/spec/migrations/migrate_ops_feature_flags_scopes_target_user_ids_spec.rb
+++ b/spec/migrations/migrate_ops_feature_flags_scopes_target_user_ids_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20191118211629_migrate_ops_feature_flags_scopes_target_user_ids.rb')
-describe MigrateOpsFeatureFlagsScopesTargetUserIds do
+RSpec.describe MigrateOpsFeatureFlagsScopesTargetUserIds do
let(:namespaces) { table(:namespaces) }
let(:projects) { table(:projects) }
let(:flags) { table(:operations_feature_flags) }
diff --git a/spec/migrations/migrate_storage_migrator_sidekiq_queue_spec.rb b/spec/migrations/migrate_storage_migrator_sidekiq_queue_spec.rb
index 557eb52632f..1181c169f57 100644
--- a/spec/migrations/migrate_storage_migrator_sidekiq_queue_spec.rb
+++ b/spec/migrations/migrate_storage_migrator_sidekiq_queue_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20190124200344_migrate_storage_migrator_sidekiq_queue.rb')
-describe MigrateStorageMigratorSidekiqQueue, :redis do
+RSpec.describe MigrateStorageMigratorSidekiqQueue, :redis do
include Gitlab::Database::MigrationHelpers
include StubWorker
diff --git a/spec/migrations/migrate_store_security_reports_sidekiq_queue_spec.rb b/spec/migrations/migrate_store_security_reports_sidekiq_queue_spec.rb
index ddffa036af1..4face37bd66 100644
--- a/spec/migrations/migrate_store_security_reports_sidekiq_queue_spec.rb
+++ b/spec/migrations/migrate_store_security_reports_sidekiq_queue_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20200213220159_migrate_store_security_reports_sidekiq_queue.rb')
-describe MigrateStoreSecurityReportsSidekiqQueue, :redis do
+RSpec.describe MigrateStoreSecurityReportsSidekiqQueue, :redis do
include Gitlab::Database::MigrationHelpers
include StubWorker
diff --git a/spec/migrations/migrate_sync_security_reports_to_report_approval_rules_sidekiq_queue_spec.rb b/spec/migrations/migrate_sync_security_reports_to_report_approval_rules_sidekiq_queue_spec.rb
index 6dfaff06ddb..15c5761bd99 100644
--- a/spec/migrations/migrate_sync_security_reports_to_report_approval_rules_sidekiq_queue_spec.rb
+++ b/spec/migrations/migrate_sync_security_reports_to_report_approval_rules_sidekiq_queue_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20200213220211_migrate_sync_security_reports_to_report_approval_rules_sidekiq_queue.rb')
-describe MigrateSyncSecurityReportsToReportApprovalRulesSidekiqQueue, :redis do
+RSpec.describe MigrateSyncSecurityReportsToReportApprovalRulesSidekiqQueue, :redis do
include Gitlab::Database::MigrationHelpers
include StubWorker
diff --git a/spec/migrations/move_limits_from_plans_spec.rb b/spec/migrations/move_limits_from_plans_spec.rb
index aeb36100205..16f94ba6dbb 100644
--- a/spec/migrations/move_limits_from_plans_spec.rb
+++ b/spec/migrations/move_limits_from_plans_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'migrate', '20191030152934_move_limits_from_plans.rb')
-describe MoveLimitsFromPlans do
+RSpec.describe MoveLimitsFromPlans do
let(:plans) { table(:plans) }
let(:plan_limits) { table(:plan_limits) }
diff --git a/spec/migrations/nullify_users_role_spec.rb b/spec/migrations/nullify_users_role_spec.rb
index b11929ef76f..3cdeb81f362 100644
--- a/spec/migrations/nullify_users_role_spec.rb
+++ b/spec/migrations/nullify_users_role_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20191104142124_nullify_users_role.rb')
-describe NullifyUsersRole do
+RSpec.describe NullifyUsersRole do
let(:users) { table(:users) }
before do
diff --git a/spec/migrations/populate_project_statistics_packages_size_spec.rb b/spec/migrations/populate_project_statistics_packages_size_spec.rb
index c316a4bc8b7..9024406c614 100644
--- a/spec/migrations/populate_project_statistics_packages_size_spec.rb
+++ b/spec/migrations/populate_project_statistics_packages_size_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20190418132125_populate_project_statistics_packages_size.rb')
-describe PopulateProjectStatisticsPackagesSize do
+RSpec.describe PopulateProjectStatisticsPackagesSize do
let(:project_statistics) { table(:project_statistics) }
let(:namespaces) { table(:namespaces) }
let(:projects) { table(:projects) }
diff --git a/spec/migrations/populate_rule_type_on_approval_merge_request_rules_spec.rb b/spec/migrations/populate_rule_type_on_approval_merge_request_rules_spec.rb
index d6362528068..2ac912d7979 100644
--- a/spec/migrations/populate_rule_type_on_approval_merge_request_rules_spec.rb
+++ b/spec/migrations/populate_rule_type_on_approval_merge_request_rules_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20190520201748_populate_rule_type_on_approval_merge_request_rules.rb')
-describe PopulateRuleTypeOnApprovalMergeRequestRules do
+RSpec.describe PopulateRuleTypeOnApprovalMergeRequestRules do
let(:migration) { described_class.new }
describe '#up' do
diff --git a/spec/migrations/remove_additional_application_settings_rows_spec.rb b/spec/migrations/remove_additional_application_settings_rows_spec.rb
index 379fa385b8e..a865e8e8dd7 100644
--- a/spec/migrations/remove_additional_application_settings_rows_spec.rb
+++ b/spec/migrations/remove_additional_application_settings_rows_spec.rb
@@ -4,7 +4,7 @@ require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20200420162730_remove_additional_application_settings_rows.rb')
-describe RemoveAdditionalApplicationSettingsRows do
+RSpec.describe RemoveAdditionalApplicationSettingsRows do
let(:application_settings) { table(:application_settings) }
it 'removes additional rows from application settings' do
diff --git a/spec/migrations/remove_deprecated_jenkins_service_records_spec.rb b/spec/migrations/remove_deprecated_jenkins_service_records_spec.rb
index 9c9abd36203..00fde88c528 100644
--- a/spec/migrations/remove_deprecated_jenkins_service_records_spec.rb
+++ b/spec/migrations/remove_deprecated_jenkins_service_records_spec.rb
@@ -4,7 +4,7 @@ require 'spec_helper'
require Rails.root.join('db', 'migrate', '20200511130129_remove_deprecated_jenkins_service_records.rb')
require Rails.root.join('db', 'post_migrate', '20200511130130_ensure_deprecated_jenkins_service_records_removal.rb')
-shared_examples 'remove DeprecatedJenkinsService records' do
+RSpec.shared_examples 'remove DeprecatedJenkinsService records' do
let(:services) { table(:services) }
before do
@@ -19,10 +19,10 @@ shared_examples 'remove DeprecatedJenkinsService records' do
end
end
-describe RemoveDeprecatedJenkinsServiceRecords, :migration do
+RSpec.describe RemoveDeprecatedJenkinsServiceRecords, :migration do
it_behaves_like 'remove DeprecatedJenkinsService records'
end
-describe EnsureDeprecatedJenkinsServiceRecordsRemoval, :migration do
+RSpec.describe EnsureDeprecatedJenkinsServiceRecordsRemoval, :migration do
it_behaves_like 'remove DeprecatedJenkinsService records'
end
diff --git a/spec/migrations/remove_duplicate_labels_from_project_spec.rb b/spec/migrations/remove_duplicate_labels_from_project_spec.rb
new file mode 100644
index 00000000000..5e287eaa4ed
--- /dev/null
+++ b/spec/migrations/remove_duplicate_labels_from_project_spec.rb
@@ -0,0 +1,238 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require Rails.root.join('db', 'post_migrate', '20200305082754_remove_duplicate_labels_from_project.rb')
+
+RSpec.describe RemoveDuplicateLabelsFromProject do
+ let(:labels_table) { table(:labels) }
+ let(:labels) { labels_table.all }
+ let(:projects_table) { table(:projects) }
+ let(:projects) { projects_table.all }
+ let(:namespaces_table) { table(:namespaces) }
+ let(:namespaces) { namespaces_table.all }
+ let(:backup_labels_table) { table(:backup_labels) }
+ let(:backup_labels) { backup_labels_table.all }
+
+ # all the possible tables with records that may have a relationship with a label
+ let(:analytics_cycle_analytics_group_stages_table) { table(:analytics_cycle_analytics_group_stages) }
+ let(:analytics_cycle_analytics_project_stages_table) { table(:analytics_cycle_analytics_project_stages) }
+ let(:board_labels_table) { table(:board_labels) }
+ let(:label_links_table) { table(:label_links) }
+ let(:label_priorities_table) { table(:label_priorities) }
+ let(:lists_table) { table(:lists) }
+ let(:resource_label_events_table) { table(:resource_label_events) }
+
+ let!(:group_one) { namespaces_table.create!(id: 1, type: 'Group', name: 'group', path: 'group') }
+ let!(:project_one) do
+ projects_table.create!(id: 1, name: 'project', path: 'project',
+ visibility_level: 0, namespace_id: group_one.id)
+ end
+ let(:label_title) { 'bug' }
+ let(:label_color) { 'red' }
+ let(:label_description) { 'nice label' }
+ let(:group_id) { group_one.id }
+ let(:project_id) { project_one.id }
+ let(:other_title) { 'feature' }
+
+ let(:group_label_attributes) do
+ {
+ title: label_title, color: label_color, group_id: group_id, type: 'GroupLabel', template: false, description: label_description
+ }
+ end
+
+ let(:project_label_attributes) do
+ {
+ title: label_title, color: label_color, project_id: project_id, type: 'ProjectLabel', template: false, description: label_description
+ }
+ end
+
+ let(:migration) { described_class.new }
+
+ describe 'removing full duplicates' do
+ context 'when there are no duplicate labels' do
+ let!(:first_label) { labels_table.create!(project_label_attributes.merge(id: 1, title: "a different label")) }
+ let!(:second_label) { labels_table.create!(project_label_attributes.merge(id: 2, title: "a totally different label")) }
+
+ it 'does not remove anything' do
+ expect { migration.up }.not_to change { backup_labels_table.count }
+ end
+
+ it 'restores removed records when rolling back - no change' do
+ migration.up
+
+ expect { migration.down }.not_to change { labels_table.count }
+ end
+ end
+
+ context 'with duplicates with no relationships' do
+ # can't use the activerecord class because the `type` makes it think it has polymorphism and should be/have a ProjectLabel subclass
+ let(:backup_labels) { ApplicationRecord.connection.execute('SELECT * from backup_labels') }
+
+ let!(:first_label) { labels_table.create!(project_label_attributes.merge(id: 1)) }
+ let!(:second_label) { labels_table.create!(project_label_attributes.merge(id: 2)) }
+ let!(:third_label) { labels_table.create!(project_label_attributes.merge(id: 3, title: other_title)) }
+ let!(:fourth_label) { labels_table.create!(project_label_attributes.merge(id: 4, title: other_title)) }
+
+ it 'creates a backup record for each removed record' do
+ expect { migration.up }.to change { backup_labels_table.count }.from(0).to(2)
+ end
+
+ it 'creates the correct backup records with `create` restore_action' do
+ migration.up
+
+ expect(backup_labels.find { |bl| bl["id"] == 2 }).to include(second_label.attributes.merge("restore_action" => described_class::CREATE, "new_title" => nil, "created_at" => anything, "updated_at" => anything))
+ expect(backup_labels.find { |bl| bl["id"] == 4 }).to include(fourth_label.attributes.merge("restore_action" => described_class::CREATE, "new_title" => nil, "created_at" => anything, "updated_at" => anything))
+ end
+
+ it 'deletes all but one' do
+ migration.up
+
+ expect { second_label.reload }.to raise_error(ActiveRecord::RecordNotFound)
+ expect { fourth_label.reload }.to raise_error(ActiveRecord::RecordNotFound)
+ end
+
+ it 'restores removed records on rollback' do
+ second_label_attributes = modified_attributes(second_label)
+ fourth_label_attributes = modified_attributes(fourth_label)
+
+ migration.up
+
+ migration.down
+
+ expect(second_label.attributes).to include(second_label_attributes)
+ expect(fourth_label.attributes).to include(fourth_label_attributes)
+ end
+ end
+
+ context 'two duplicate records, one of which has a relationship' do
+ let!(:first_label) { labels_table.create!(project_label_attributes.merge(id: 1)) }
+ let!(:second_label) { labels_table.create!(project_label_attributes.merge(id: 2)) }
+ let!(:label_priority) { label_priorities_table.create!(label_id: second_label.id, project_id: project_id, priority: 1) }
+
+ it 'does not remove anything' do
+ expect { migration.up }.not_to change { labels_table.count }
+ end
+
+ it 'does not create a backup record with `create` restore_action' do
+ expect { migration.up }.not_to change { backup_labels_table.where(restore_action: described_class::CREATE).count }
+ end
+
+ it 'restores removed records when rolling back - no change' do
+ migration.up
+
+ expect { migration.down }.not_to change { labels_table.count }
+ end
+ end
+
+ context 'multiple duplicates, a subset of which have relationships' do
+ let!(:first_label) { labels_table.create!(project_label_attributes.merge(id: 1)) }
+ let!(:second_label) { labels_table.create!(project_label_attributes.merge(id: 2)) }
+ let!(:label_priority_for_second_label) { label_priorities_table.create!(label_id: second_label.id, project_id: project_id, priority: 1) }
+ let!(:third_label) { labels_table.create!(project_label_attributes.merge(id: 3)) }
+ let!(:fourth_label) { labels_table.create!(project_label_attributes.merge(id: 4)) }
+ let!(:label_priority_for_fourth_label) { label_priorities_table.create!(label_id: fourth_label.id, project_id: project_id, priority: 2) }
+
+ it 'creates a backup record with `create` restore_action for each removed record' do
+ expect { migration.up }.to change { backup_labels_table.where(restore_action: described_class::CREATE).count }.from(0).to(1)
+ end
+
+ it 'creates the correct backup records' do
+ migration.up
+
+ # can't use the activerecord class because the `type` column makes it think it has polymorphism and should be/have a ProjectLabel subclass
+ backup_labels = ApplicationRecord.connection.execute('SELECT * from backup_labels')
+
+ expect(backup_labels.find { |bl| bl["id"] == 3 }).to include(third_label.attributes.merge("restore_action" => described_class::CREATE, "new_title" => nil, "created_at" => anything, "updated_at" => anything))
+ end
+
+ it 'deletes the duplicate record' do
+ migration.up
+
+ expect { first_label.reload }.not_to raise_error
+ expect { second_label.reload }.not_to raise_error
+ expect { third_label.reload }.to raise_error(ActiveRecord::RecordNotFound)
+ end
+
+ it 'restores removed records on rollback' do
+ third_label_attributes = modified_attributes(third_label)
+
+ migration.up
+ migration.down
+
+ expect(third_label.attributes).to include(third_label_attributes)
+ end
+ end
+ end
+
+ describe 'renaming partial duplicates' do
+ # partial duplicates - only project_id and title match. Distinct colour prevents deletion.
+ context 'when there are no duplicate labels' do
+ let!(:first_label) { labels_table.create!(project_label_attributes.merge(id: 1, title: "a unique label", color: 'green')) }
+ let!(:second_label) { labels_table.create!(project_label_attributes.merge(id: 2, title: "a totally different, unique, label", color: 'blue')) }
+
+ it 'does not rename anything' do
+ expect { migration.up }.not_to change { backup_labels_table.count }
+ end
+ end
+
+ context 'with duplicates with no relationships' do
+ let!(:first_label) { labels_table.create!(project_label_attributes.merge(id: 1, color: 'green')) }
+ let!(:second_label) { labels_table.create!(project_label_attributes.merge(id: 2, color: 'blue')) }
+ let!(:third_label) { labels_table.create!(project_label_attributes.merge(id: 3, title: other_title, color: 'purple')) }
+ let!(:fourth_label) { labels_table.create!(project_label_attributes.merge(id: 4, title: other_title, color: 'yellow')) }
+
+ it 'creates a backup record for each renamed record' do
+ expect { migration.up }.to change { backup_labels_table.count }.from(0).to(2)
+ end
+
+ it 'creates the correct backup records with `rename` restore_action' do
+ migration.up
+
+ # can't use the activerecord class because the `type` makes it think it has polymorphism and should be/have a ProjectLabel subclass
+ backup_labels = ApplicationRecord.connection.execute('SELECT * from backup_labels')
+
+ expect(backup_labels.find { |bl| bl["id"] == 2 }).to include(second_label.attributes.merge("restore_action" => described_class::RENAME, "created_at" => anything, "updated_at" => anything))
+ expect(backup_labels.find { |bl| bl["id"] == 4 }).to include(fourth_label.attributes.merge("restore_action" => described_class::RENAME, "created_at" => anything, "updated_at" => anything))
+ end
+
+ it 'modifies the titles of the partial duplicates' do
+ migration.up
+
+ expect(second_label.reload.title).to match(/#{label_title}_duplicate#{second_label.id}$/)
+ expect(fourth_label.reload.title).to match(/#{other_title}_duplicate#{fourth_label.id}$/)
+ end
+
+ it 'restores renamed records on rollback' do
+ second_label_attributes = modified_attributes(second_label)
+ fourth_label_attributes = modified_attributes(fourth_label)
+
+ migration.up
+
+ migration.down
+
+ expect(second_label.reload.attributes).to include(second_label_attributes)
+ expect(fourth_label.reload.attributes).to include(fourth_label_attributes)
+ end
+
+ context 'when the labels have a long title that might overflow' do
+ let(:long_title) { "a" * 255 }
+
+ before do
+ first_label.update_attribute(:title, long_title)
+ second_label.update_attribute(:title, long_title)
+ end
+
+ it 'keeps the length within the limit' do
+ migration.up
+
+ expect(second_label.reload.title).to eq("#{"a" * 244}_duplicate#{second_label.id}")
+ expect(second_label.title.length).to eq 255
+ end
+ end
+ end
+ end
+
+ def modified_attributes(label)
+ label.attributes.except('created_at', 'updated_at')
+ end
+end
diff --git a/spec/migrations/remove_empty_github_service_templates_spec.rb b/spec/migrations/remove_empty_github_service_templates_spec.rb
index 51b29ec6efc..7a77e342efd 100644
--- a/spec/migrations/remove_empty_github_service_templates_spec.rb
+++ b/spec/migrations/remove_empty_github_service_templates_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20191021101942_remove_empty_github_service_templates.rb')
-describe RemoveEmptyGithubServiceTemplates do
+RSpec.describe RemoveEmptyGithubServiceTemplates do
subject(:migration) { described_class.new }
let(:services) do
diff --git a/spec/migrations/remove_gitlab_issue_tracker_service_records_spec.rb b/spec/migrations/remove_gitlab_issue_tracker_service_records_spec.rb
new file mode 100644
index 00000000000..81fa29f4c54
--- /dev/null
+++ b/spec/migrations/remove_gitlab_issue_tracker_service_records_spec.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require Rails.root.join('db', 'post_migrate', '20200623142159_remove_gitlab_issue_tracker_service_records.rb')
+
+RSpec.describe RemoveGitlabIssueTrackerServiceRecords do
+ let(:services) { table(:services) }
+
+ before do
+ 5.times { services.create!(type: 'GitlabIssueTrackerService') }
+ services.create!(type: 'SomeOtherType')
+ end
+
+ it 'removes services records of type GitlabIssueTrackerService', :aggregate_failures do
+ expect { migrate! }.to change { services.count }.from(6).to(1)
+ expect(services.first.type).to eq('SomeOtherType')
+ expect(services.where(type: 'GitlabIssueTrackerService')).to be_empty
+ end
+end
diff --git a/spec/migrations/remove_orphaned_invited_members_spec.rb b/spec/migrations/remove_orphaned_invited_members_spec.rb
index 0ed4c15428a..0474b5362be 100644
--- a/spec/migrations/remove_orphaned_invited_members_spec.rb
+++ b/spec/migrations/remove_orphaned_invited_members_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'migrate', '20200424050250_remove_orphaned_invited_members.rb')
-describe RemoveOrphanedInvitedMembers do
+RSpec.describe RemoveOrphanedInvitedMembers do
let(:members_table) { table(:members) }
let(:users_table) { table(:users) }
let(:namespaces_table) { table(:namespaces) }
diff --git a/spec/migrations/remove_packages_deprecated_dependencies_spec.rb b/spec/migrations/remove_packages_deprecated_dependencies_spec.rb
index 2ba7a3b268b..84c23240af9 100644
--- a/spec/migrations/remove_packages_deprecated_dependencies_spec.rb
+++ b/spec/migrations/remove_packages_deprecated_dependencies_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'migrate', '20200210135504_remove_packages_deprecated_dependencies.rb')
-describe RemovePackagesDeprecatedDependencies do
+RSpec.describe RemovePackagesDeprecatedDependencies do
let(:projects) { table(:projects) }
let(:packages) { table(:packages_packages) }
let(:dependency_links) { table(:packages_dependency_links) }
diff --git a/spec/migrations/remove_security_dashboard_feature_flag_spec.rb b/spec/migrations/remove_security_dashboard_feature_flag_spec.rb
index fa0489526e2..77363aebfe6 100644
--- a/spec/migrations/remove_security_dashboard_feature_flag_spec.rb
+++ b/spec/migrations/remove_security_dashboard_feature_flag_spec.rb
@@ -4,7 +4,7 @@ require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20200214034836_remove_security_dashboard_feature_flag.rb')
-describe RemoveSecurityDashboardFeatureFlag do
+RSpec.describe RemoveSecurityDashboardFeatureFlag do
let(:feature_gates) { table(:feature_gates) }
subject(:migration) { described_class.new }
diff --git a/spec/migrations/rename_security_dashboard_feature_flag_to_instance_security_dashboard_spec.rb b/spec/migrations/rename_security_dashboard_feature_flag_to_instance_security_dashboard_spec.rb
index 07be7a4ad51..83a79ac9795 100644
--- a/spec/migrations/rename_security_dashboard_feature_flag_to_instance_security_dashboard_spec.rb
+++ b/spec/migrations/rename_security_dashboard_feature_flag_to_instance_security_dashboard_spec.rb
@@ -4,7 +4,7 @@ require 'spec_helper'
require Rails.root.join('db', 'migrate', '20200212014653_rename_security_dashboard_feature_flag_to_instance_security_dashboard.rb')
-describe RenameSecurityDashboardFeatureFlagToInstanceSecurityDashboard do
+RSpec.describe RenameSecurityDashboardFeatureFlagToInstanceSecurityDashboard do
let(:feature_gates) { table(:feature_gates) }
subject(:migration) { described_class.new }
diff --git a/spec/migrations/save_instance_administrators_group_id_spec.rb b/spec/migrations/save_instance_administrators_group_id_spec.rb
index 74ced009fa5..cb11cd1653a 100644
--- a/spec/migrations/save_instance_administrators_group_id_spec.rb
+++ b/spec/migrations/save_instance_administrators_group_id_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20200210092405_save_instance_administrators_group_id')
-describe SaveInstanceAdministratorsGroupId do
+RSpec.describe SaveInstanceAdministratorsGroupId do
let(:application_settings_table) { table(:application_settings) }
let(:instance_administrators_group) do
diff --git a/spec/migrations/schedule_backfill_push_rules_id_in_projects_spec.rb b/spec/migrations/schedule_backfill_push_rules_id_in_projects_spec.rb
index 77721eab77d..37a769bbc52 100644
--- a/spec/migrations/schedule_backfill_push_rules_id_in_projects_spec.rb
+++ b/spec/migrations/schedule_backfill_push_rules_id_in_projects_spec.rb
@@ -4,7 +4,7 @@ require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20200325162730_schedule_backfill_push_rules_id_in_projects.rb')
-describe ScheduleBackfillPushRulesIdInProjects do
+RSpec.describe ScheduleBackfillPushRulesIdInProjects do
let(:push_rules) { table(:push_rules) }
it 'adds global rule association to application settings' do
diff --git a/spec/migrations/schedule_calculate_wiki_sizes_spec.rb b/spec/migrations/schedule_calculate_wiki_sizes_spec.rb
new file mode 100644
index 00000000000..39a93f3ed25
--- /dev/null
+++ b/spec/migrations/schedule_calculate_wiki_sizes_spec.rb
@@ -0,0 +1,60 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require Rails.root.join('db', 'post_migrate', '20190527194900_schedule_calculate_wiki_sizes.rb')
+
+RSpec.describe ScheduleCalculateWikiSizes do
+ let(:migration_class) { Gitlab::BackgroundMigration::CalculateWikiSizes }
+ let(:migration_name) { migration_class.to_s.demodulize }
+
+ let(:namespaces) { table(:namespaces) }
+ let(:projects) { table(:projects) }
+ let(:project_statistics) { table(:project_statistics) }
+ let(:namespace) { namespaces.create!(name: 'wiki-migration', path: 'wiki-migration') }
+ let(:project1) { projects.create!(name: 'wiki-project-1', path: 'wiki-project-1', namespace_id: namespace.id) }
+ let(:project2) { projects.create!(name: 'wiki-project-2', path: 'wiki-project-2', namespace_id: namespace.id) }
+ let(:project3) { projects.create!(name: 'wiki-project-3', path: 'wiki-project-3', namespace_id: namespace.id) }
+
+ context 'when missing wiki sizes exist' do
+ let!(:project_statistic1) { project_statistics.create!(id: 1, project_id: project1.id, namespace_id: namespace.id, wiki_size: 1000) }
+ let!(:project_statistic2) { project_statistics.create!(id: 2, project_id: project2.id, namespace_id: namespace.id, wiki_size: nil) }
+ let!(:project_statistic3) { project_statistics.create!(id: 3, project_id: project3.id, namespace_id: namespace.id, wiki_size: nil) }
+
+ it 'schedules a background migration' do
+ Timecop.freeze do
+ migrate!
+
+ expect(migration_name).to be_scheduled_delayed_migration(5.minutes, project_statistic2.id, project_statistic3.id)
+ expect(BackgroundMigrationWorker.jobs.size).to eq 1
+ end
+ end
+
+ it 'calculates missing wiki sizes', :sidekiq_inline do
+ expect(project_statistic2.wiki_size).to be_nil
+ expect(project_statistic3.wiki_size).to be_nil
+
+ migrate!
+
+ expect(project_statistic2.reload.wiki_size).not_to be_nil
+ expect(project_statistic3.reload.wiki_size).not_to be_nil
+ end
+ end
+
+ context 'when missing wiki sizes do not exist' do
+ before do
+ namespace = namespaces.create!(name: 'wiki-migration', path: 'wiki-migration')
+ project = projects.create!(name: 'wiki-project-1', path: 'wiki-project-1', namespace_id: namespace.id)
+ project_statistics.create!(project_id: project.id, namespace_id: 1, wiki_size: 1000)
+ end
+
+ it 'does not schedule a background migration' do
+ Sidekiq::Testing.fake! do
+ Timecop.freeze do
+ migrate!
+
+ expect(BackgroundMigrationWorker.jobs.size).to eq 0
+ end
+ end
+ end
+ end
+end
diff --git a/spec/migrations/schedule_fill_valid_time_for_pages_domain_certificates_spec.rb b/spec/migrations/schedule_fill_valid_time_for_pages_domain_certificates_spec.rb
index 30cb68c742c..a51bc374d5f 100644
--- a/spec/migrations/schedule_fill_valid_time_for_pages_domain_certificates_spec.rb
+++ b/spec/migrations/schedule_fill_valid_time_for_pages_domain_certificates_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20190524073827_schedule_fill_valid_time_for_pages_domain_certificates.rb')
-describe ScheduleFillValidTimeForPagesDomainCertificates do
+RSpec.describe ScheduleFillValidTimeForPagesDomainCertificates do
let(:migration_class) { described_class::MIGRATION }
let(:migration_name) { migration_class.to_s.demodulize }
diff --git a/spec/migrations/schedule_link_lfs_objects_projects_spec.rb b/spec/migrations/schedule_link_lfs_objects_projects_spec.rb
index 055ab3cdd83..04ca18de91f 100644
--- a/spec/migrations/schedule_link_lfs_objects_projects_spec.rb
+++ b/spec/migrations/schedule_link_lfs_objects_projects_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20200310075115_schedule_link_lfs_objects_projects.rb')
-describe ScheduleLinkLfsObjectsProjects, :migration, :sidekiq do
+RSpec.describe ScheduleLinkLfsObjectsProjects, :migration, :sidekiq do
let(:namespaces) { table(:namespaces) }
let(:projects) { table(:projects) }
let(:fork_networks) { table(:fork_networks) }
diff --git a/spec/migrations/schedule_merge_request_assignees_migration_progress_check_spec.rb b/spec/migrations/schedule_merge_request_assignees_migration_progress_check_spec.rb
index bea985fabb1..0a69f49f10d 100644
--- a/spec/migrations/schedule_merge_request_assignees_migration_progress_check_spec.rb
+++ b/spec/migrations/schedule_merge_request_assignees_migration_progress_check_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20190402224749_schedule_merge_request_assignees_migration_progress_check.rb')
-describe ScheduleMergeRequestAssigneesMigrationProgressCheck do
+RSpec.describe ScheduleMergeRequestAssigneesMigrationProgressCheck do
describe '#up' do
it 'schedules MergeRequestAssigneesMigrationProgressCheck background job' do
expect(BackgroundMigrationWorker).to receive(:perform_async)
diff --git a/spec/migrations/schedule_migrate_security_scans_spec.rb b/spec/migrations/schedule_migrate_security_scans_spec.rb
index 29e4e2b5cac..4fd17b20666 100644
--- a/spec/migrations/schedule_migrate_security_scans_spec.rb
+++ b/spec/migrations/schedule_migrate_security_scans_spec.rb
@@ -4,7 +4,7 @@ require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20200217225719_schedule_migrate_security_scans.rb')
# rubocop: disable RSpec/FactoriesInMigrationSpecs
-describe ScheduleMigrateSecurityScans, :sidekiq do
+RSpec.describe ScheduleMigrateSecurityScans, :sidekiq do
let(:migration) { described_class.new }
let(:namespaces) { table(:namespaces) }
let(:projects) { table(:projects) }
diff --git a/spec/migrations/schedule_pages_metadata_migration_spec.rb b/spec/migrations/schedule_pages_metadata_migration_spec.rb
index 748b9fe1cd1..c37e19eb71c 100644
--- a/spec/migrations/schedule_pages_metadata_migration_spec.rb
+++ b/spec/migrations/schedule_pages_metadata_migration_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20191002031332_schedule_pages_metadata_migration')
-describe SchedulePagesMetadataMigration do
+RSpec.describe SchedulePagesMetadataMigration do
let(:namespaces) { table(:namespaces) }
let(:projects) { table(:projects) }
diff --git a/spec/migrations/schedule_populate_merge_request_assignees_table_spec.rb b/spec/migrations/schedule_populate_merge_request_assignees_table_spec.rb
index d778b47179f..8b26cd589fd 100644
--- a/spec/migrations/schedule_populate_merge_request_assignees_table_spec.rb
+++ b/spec/migrations/schedule_populate_merge_request_assignees_table_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20190322132835_schedule_populate_merge_request_assignees_table.rb')
-describe SchedulePopulateMergeRequestAssigneesTable do
+RSpec.describe SchedulePopulateMergeRequestAssigneesTable do
let(:namespaces) { table(:namespaces) }
let(:projects) { table(:projects) }
let(:namespace) { namespaces.create(name: 'gitlab', path: 'gitlab-org') }
diff --git a/spec/migrations/schedule_populate_project_snippet_statistics_spec.rb b/spec/migrations/schedule_populate_project_snippet_statistics_spec.rb
new file mode 100644
index 00000000000..05e9d4d2f79
--- /dev/null
+++ b/spec/migrations/schedule_populate_project_snippet_statistics_spec.rb
@@ -0,0 +1,61 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require Rails.root.join('db', 'post_migrate', '20200709101408_schedule_populate_project_snippet_statistics.rb')
+
+RSpec.describe SchedulePopulateProjectSnippetStatistics do
+ let(:users) { table(:users) }
+ let(:snippets) { table(:snippets) }
+ let(:projects) { table(:projects) }
+ let(:namespaces) { table(:namespaces) }
+ let(:user1) { users.create!(id: 1, email: 'user1@example.com', projects_limit: 10, username: 'test1', name: 'Test1', state: 'active') }
+ let(:user2) { users.create!(id: 2, email: 'user2@example.com', projects_limit: 10, username: 'test2', name: 'Test2', state: 'active') }
+ let(:namespace1) { namespaces.create!(id: 1, owner_id: user1.id, name: 'user1', path: 'user1') }
+ let(:namespace2) { namespaces.create!(id: 2, owner_id: user2.id, name: 'user2', path: 'user2') }
+ let(:project1) { projects.create!(id: 1, namespace_id: namespace1.id) }
+ let(:project2) { projects.create!(id: 2, namespace_id: namespace1.id) }
+ let(:project3) { projects.create!(id: 3, namespace_id: namespace2.id) }
+
+ def create_snippet(id, user_id, project_id, type = 'ProjectSnippet')
+ params = {
+ id: id,
+ type: type,
+ author_id: user_id,
+ project_id: project_id,
+ file_name: 'foo',
+ content: 'bar'
+ }
+
+ snippets.create!(params)
+ end
+
+ it 'correctly schedules background migrations' do
+ # Creating the snippets in different order
+ create_snippet(1, user1.id, project1.id)
+ create_snippet(2, user2.id, project3.id)
+ create_snippet(3, user1.id, project1.id)
+ create_snippet(4, user1.id, project2.id)
+ create_snippet(5, user2.id, project3.id)
+ create_snippet(6, user1.id, project1.id)
+ # Creating a personal snippet to ensure we don't pick it
+ create_snippet(7, user1.id, nil, 'PersonalSnippet')
+
+ stub_const("#{described_class}::BATCH_SIZE", 4)
+
+ Sidekiq::Testing.fake! do
+ Timecop.freeze do
+ migrate!
+
+ aggregate_failures do
+ expect(described_class::MIGRATION)
+ .to be_scheduled_migration([1, 3, 6, 4])
+
+ expect(described_class::MIGRATION)
+ .to be_scheduled_delayed_migration(2.minutes, [2, 5])
+
+ expect(BackgroundMigrationWorker.jobs.size).to eq(2)
+ end
+ end
+ end
+ end
+end
diff --git a/spec/migrations/schedule_populate_user_highest_roles_table_spec.rb b/spec/migrations/schedule_populate_user_highest_roles_table_spec.rb
index 67e0b994265..c7e5c6f30a6 100644
--- a/spec/migrations/schedule_populate_user_highest_roles_table_spec.rb
+++ b/spec/migrations/schedule_populate_user_highest_roles_table_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20200311130802_schedule_populate_user_highest_roles_table.rb')
-describe SchedulePopulateUserHighestRolesTable do
+RSpec.describe SchedulePopulateUserHighestRolesTable do
let(:users) { table(:users) }
def create_user(id, params = {})
diff --git a/spec/migrations/schedule_recalculate_project_authorizations_second_run_spec.rb b/spec/migrations/schedule_recalculate_project_authorizations_second_run_spec.rb
index 4c05f7d57a1..06f1a7e28eb 100644
--- a/spec/migrations/schedule_recalculate_project_authorizations_second_run_spec.rb
+++ b/spec/migrations/schedule_recalculate_project_authorizations_second_run_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20200204113224_schedule_recalculate_project_authorizations_second_run.rb')
-describe ScheduleRecalculateProjectAuthorizationsSecondRun do
+RSpec.describe ScheduleRecalculateProjectAuthorizationsSecondRun do
let(:users_table) { table(:users) }
before do
diff --git a/spec/migrations/schedule_recalculate_project_authorizations_spec.rb b/spec/migrations/schedule_recalculate_project_authorizations_spec.rb
index d30ebf825ef..9519b103284 100644
--- a/spec/migrations/schedule_recalculate_project_authorizations_spec.rb
+++ b/spec/migrations/schedule_recalculate_project_authorizations_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20200204113223_schedule_recalculate_project_authorizations.rb')
-describe ScheduleRecalculateProjectAuthorizations do
+RSpec.describe ScheduleRecalculateProjectAuthorizations do
let(:users_table) { table(:users) }
let(:namespaces_table) { table(:namespaces) }
let(:projects_table) { table(:projects) }
diff --git a/spec/migrations/schedule_recalculate_project_authorizations_third_run_spec.rb b/spec/migrations/schedule_recalculate_project_authorizations_third_run_spec.rb
index 19ba8984224..300bb940dd8 100644
--- a/spec/migrations/schedule_recalculate_project_authorizations_third_run_spec.rb
+++ b/spec/migrations/schedule_recalculate_project_authorizations_third_run_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20200204113225_schedule_recalculate_project_authorizations_third_run.rb')
-describe ScheduleRecalculateProjectAuthorizationsThirdRun do
+RSpec.describe ScheduleRecalculateProjectAuthorizationsThirdRun do
let(:users_table) { table(:users) }
before do
diff --git a/spec/migrations/schedule_sync_issuables_state_id_spec.rb b/spec/migrations/schedule_sync_issuables_state_id_spec.rb
index 408e7e6d19d..ecfebbde348 100644
--- a/spec/migrations/schedule_sync_issuables_state_id_spec.rb
+++ b/spec/migrations/schedule_sync_issuables_state_id_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20190214112022_schedule_sync_issuables_state_id.rb')
-describe ScheduleSyncIssuablesStateId do
+RSpec.describe ScheduleSyncIssuablesStateId do
let(:namespaces) { table(:namespaces) }
let(:projects) { table(:projects) }
let(:merge_requests) { table(:merge_requests) }
diff --git a/spec/migrations/schedule_sync_issuables_state_id_where_nil_spec.rb b/spec/migrations/schedule_sync_issuables_state_id_where_nil_spec.rb
index e26a864b8ba..d23f5b69d22 100644
--- a/spec/migrations/schedule_sync_issuables_state_id_where_nil_spec.rb
+++ b/spec/migrations/schedule_sync_issuables_state_id_where_nil_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20190506135400_schedule_sync_issuables_state_id_where_nil')
-describe ScheduleSyncIssuablesStateIdWhereNil do
+RSpec.describe ScheduleSyncIssuablesStateIdWhereNil do
let(:namespaces) { table(:namespaces) }
let(:projects) { table(:projects) }
let(:merge_requests) { table(:merge_requests) }
diff --git a/spec/migrations/schedule_update_existing_subgroup_to_match_visibility_level_of_parent_spec.rb b/spec/migrations/schedule_update_existing_subgroup_to_match_visibility_level_of_parent_spec.rb
index 098fe68927c..949d8d8794f 100644
--- a/spec/migrations/schedule_update_existing_subgroup_to_match_visibility_level_of_parent_spec.rb
+++ b/spec/migrations/schedule_update_existing_subgroup_to_match_visibility_level_of_parent_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20200110121314_schedule_update_existing_subgroup_to_match_visibility_level_of_parent.rb')
-describe ScheduleUpdateExistingSubgroupToMatchVisibilityLevelOfParent do
+RSpec.describe ScheduleUpdateExistingSubgroupToMatchVisibilityLevelOfParent do
include MigrationHelpers::NamespacesHelpers
let(:migration_class) { described_class::MIGRATION }
let(:migration_name) { migration_class.to_s.demodulize }
diff --git a/spec/migrations/seed_repository_storages_weighted_spec.rb b/spec/migrations/seed_repository_storages_weighted_spec.rb
index 9a68ff5fb2f..fb077a3e345 100644
--- a/spec/migrations/seed_repository_storages_weighted_spec.rb
+++ b/spec/migrations/seed_repository_storages_weighted_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20200526000407_seed_repository_storages_weighted.rb')
-describe SeedRepositoryStoragesWeighted do
+RSpec.describe SeedRepositoryStoragesWeighted do
let(:storages) { { "foo" => {}, "baz" => {} } }
let(:application_settings) do
table(:application_settings).tap do |klass|
diff --git a/spec/migrations/services_remove_temporary_index_on_project_id_spec.rb b/spec/migrations/services_remove_temporary_index_on_project_id_spec.rb
index d4f9969b71b..6cab4c16cfb 100644
--- a/spec/migrations/services_remove_temporary_index_on_project_id_spec.rb
+++ b/spec/migrations/services_remove_temporary_index_on_project_id_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20200203104214_services_remove_temporary_index_on_project_id.rb')
-describe ServicesRemoveTemporaryIndexOnProjectId do
+RSpec.describe ServicesRemoveTemporaryIndexOnProjectId do
let(:migration_instance) { described_class.new }
it 'adds and removes temporary partial index in up and down methods' do
diff --git a/spec/migrations/set_issue_id_for_all_versions_spec.rb b/spec/migrations/set_issue_id_for_all_versions_spec.rb
index ff281947db8..0908d054e70 100644
--- a/spec/migrations/set_issue_id_for_all_versions_spec.rb
+++ b/spec/migrations/set_issue_id_for_all_versions_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'migrate', '20190715043954_set_issue_id_for_all_versions.rb')
-describe SetIssueIdForAllVersions do
+RSpec.describe SetIssueIdForAllVersions do
let(:projects) { table(:projects) }
let(:issues) { table(:issues) }
let(:designs) { table(:design_management_designs) }
diff --git a/spec/migrations/sync_issuables_state_id_spec.rb b/spec/migrations/sync_issuables_state_id_spec.rb
index 4bd30172cbe..dcddbca6a36 100644
--- a/spec/migrations/sync_issuables_state_id_spec.rb
+++ b/spec/migrations/sync_issuables_state_id_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20190911251732_sync_issuables_state_id')
-describe SyncIssuablesStateId do
+RSpec.describe SyncIssuablesStateId do
let(:migration) { described_class.new }
describe '#up' do
diff --git a/spec/migrations/truncate_user_fullname_spec.rb b/spec/migrations/truncate_user_fullname_spec.rb
index a5e6a0a4fce..cb95c222790 100644
--- a/spec/migrations/truncate_user_fullname_spec.rb
+++ b/spec/migrations/truncate_user_fullname_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'migrate', '20190325080727_truncate_user_fullname.rb')
-describe TruncateUserFullname do
+RSpec.describe TruncateUserFullname do
let(:users) { table(:users) }
let(:user_short) { create_user(name: 'abc', email: 'test_short@example.com') }
diff --git a/spec/migrations/unconfirm_wrongfully_verified_emails_spec.rb b/spec/migrations/unconfirm_wrongfully_verified_emails_spec.rb
new file mode 100644
index 00000000000..e93f2cb64de
--- /dev/null
+++ b/spec/migrations/unconfirm_wrongfully_verified_emails_spec.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require Rails.root.join('db', 'post_migrate', '20200615111857_unconfirm_wrongfully_verified_emails.rb')
+
+RSpec.describe UnconfirmWrongfullyVerifiedEmails do
+ before do
+ user = table(:users).create!(name: 'user1', email: 'test1@test.com', projects_limit: 1)
+ table(:emails).create!(email: 'test2@test.com', user_id: user.id)
+ end
+
+ it 'enqueues WrongullyConfirmedEmailUnconfirmer job' do
+ Sidekiq::Testing.fake! do
+ migrate!
+
+ jobs = BackgroundMigrationWorker.jobs
+ expect(jobs.size).to eq(1)
+ expect(jobs.first["args"].first).to eq(Gitlab::BackgroundMigration::WrongfullyConfirmedEmailUnconfirmer.name.demodulize)
+ end
+ end
+end
diff --git a/spec/migrations/update_application_setting_npm_package_requests_forwarding_default_spec.rb b/spec/migrations/update_application_setting_npm_package_requests_forwarding_default_spec.rb
index f9523e0e582..8b241d1b28e 100644
--- a/spec/migrations/update_application_setting_npm_package_requests_forwarding_default_spec.rb
+++ b/spec/migrations/update_application_setting_npm_package_requests_forwarding_default_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'migrate', '20200221105436_update_application_setting_npm_package_requests_forwarding_default.rb')
-describe UpdateApplicationSettingNpmPackageRequestsForwardingDefault do
+RSpec.describe UpdateApplicationSettingNpmPackageRequestsForwardingDefault do
# Create test data - pipeline and CI/CD jobs.
let(:application_settings) { table(:application_settings) }
diff --git a/spec/migrations/update_fingerprint_sha256_within_keys_spec.rb b/spec/migrations/update_fingerprint_sha256_within_keys_spec.rb
index d149ec230a7..7f5ae892391 100644
--- a/spec/migrations/update_fingerprint_sha256_within_keys_spec.rb
+++ b/spec/migrations/update_fingerprint_sha256_within_keys_spec.rb
@@ -4,7 +4,7 @@ require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20200106071113_update_fingerprint_sha256_within_keys.rb')
-describe UpdateFingerprintSha256WithinKeys do
+RSpec.describe UpdateFingerprintSha256WithinKeys do
let(:key_table) { table(:keys) }
describe '#up' do
diff --git a/spec/migrations/update_minimum_password_length_spec.rb b/spec/migrations/update_minimum_password_length_spec.rb
index ed9c85362f5..a4485dfbcea 100644
--- a/spec/migrations/update_minimum_password_length_spec.rb
+++ b/spec/migrations/update_minimum_password_length_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20191205084057_update_minimum_password_length')
-describe UpdateMinimumPasswordLength do
+RSpec.describe UpdateMinimumPasswordLength do
let(:application_settings) { table(:application_settings) }
let(:application_setting) do
application_settings.create!(
diff --git a/spec/migrations/update_routes_for_lost_and_found_group_and_orphaned_projects_spec.rb b/spec/migrations/update_routes_for_lost_and_found_group_and_orphaned_projects_spec.rb
index 103b6f114c4..ffab5c40182 100644
--- a/spec/migrations/update_routes_for_lost_and_found_group_and_orphaned_projects_spec.rb
+++ b/spec/migrations/update_routes_for_lost_and_found_group_and_orphaned_projects_spec.rb
@@ -4,7 +4,7 @@ require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20200602143020_update_routes_for_lost_and_found_group_and_orphaned_projects.rb')
-describe UpdateRoutesForLostAndFoundGroupAndOrphanedProjects, :migration do
+RSpec.describe UpdateRoutesForLostAndFoundGroupAndOrphanedProjects, :migration do
let(:users) { table(:users) }
let(:namespaces) { table(:namespaces) }
let(:members) { table(:members) }
diff --git a/spec/migrations/update_timestamp_softwarelicensespolicy_spec.rb b/spec/migrations/update_timestamp_softwarelicensespolicy_spec.rb
index 918c5fb567f..f55d55f94c7 100644
--- a/spec/migrations/update_timestamp_softwarelicensespolicy_spec.rb
+++ b/spec/migrations/update_timestamp_softwarelicensespolicy_spec.rb
@@ -4,7 +4,7 @@ require 'spec_helper'
require Rails.root.join('db', 'migrate', '20200116175538_update_timestamp_softwarelicensespolicy.rb')
-describe UpdateTimestampSoftwarelicensespolicy do
+RSpec.describe UpdateTimestampSoftwarelicensespolicy do
let(:software_licenses_policy) { table(:software_license_policies) }
let(:projects) { table(:projects) }
let(:licenses) { table(:software_licenses) }
diff --git a/spec/models/ability_spec.rb b/spec/models/ability_spec.rb
index 9ef77da6f43..9206f14fd37 100644
--- a/spec/models/ability_spec.rb
+++ b/spec/models/ability_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ability do
+RSpec.describe Ability do
context 'using a nil subject' do
it 'has no permissions' do
expect(described_class.policy_for(nil, nil)).to be_banned
diff --git a/spec/models/abuse_report_spec.rb b/spec/models/abuse_report_spec.rb
index 2c4fa398636..a97574fa524 100644
--- a/spec/models/abuse_report_spec.rb
+++ b/spec/models/abuse_report_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe AbuseReport do
+RSpec.describe AbuseReport do
let_it_be(:report, reload: true) { create(:abuse_report) }
let_it_be(:user, reload: true) { create(:admin) }
subject { report }
diff --git a/spec/models/alert_management/alert_assignee_spec.rb b/spec/models/alert_management/alert_assignee_spec.rb
index c51a5d543ab..c50a3ec0d01 100644
--- a/spec/models/alert_management/alert_assignee_spec.rb
+++ b/spec/models/alert_management/alert_assignee_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe AlertManagement::AlertAssignee do
+RSpec.describe AlertManagement::AlertAssignee do
describe 'associations' do
it { is_expected.to belong_to(:alert) }
it { is_expected.to belong_to(:assignee) }
diff --git a/spec/models/alert_management/alert_spec.rb b/spec/models/alert_management/alert_spec.rb
index 27b8bb48073..becc5475c15 100644
--- a/spec/models/alert_management/alert_spec.rb
+++ b/spec/models/alert_management/alert_spec.rb
@@ -2,10 +2,12 @@
require 'spec_helper'
-describe AlertManagement::Alert do
+RSpec.describe AlertManagement::Alert do
describe 'associations' do
it { is_expected.to belong_to(:project) }
- it { is_expected.to belong_to(:issue) }
+ it { is_expected.to belong_to(:issue).optional }
+ it { is_expected.to belong_to(:prometheus_alert).optional }
+ it { is_expected.to belong_to(:environment).optional }
it { is_expected.to have_many(:assignees).through(:alert_assignees) }
it { is_expected.to have_many(:notes) }
it { is_expected.to have_many(:ordered_notes) }
@@ -81,21 +83,50 @@ describe AlertManagement::Alert do
end
describe 'fingerprint' do
+ let_it_be(:project) { create(:project) }
let_it_be(:fingerprint) { 'fingerprint' }
- let_it_be(:existing_alert) { create(:alert_management_alert, fingerprint: fingerprint) }
let(:new_alert) { build(:alert_management_alert, fingerprint: fingerprint, project: project) }
subject { new_alert }
context 'adding an alert with the same fingerprint' do
- context 'same project' do
- let(:project) { existing_alert.project }
-
- it { is_expected.not_to be_valid }
+ context 'same project, various states' do
+ using RSpec::Parameterized::TableSyntax
+
+ # We are only validating uniqueness for non-resolved alerts
+ where(:existing_status, :new_status, :valid) do
+ :resolved | :triggered | true
+ :resolved | :acknowledged | true
+ :resolved | :ignored | true
+ :resolved | :resolved | true
+ :triggered | :triggered | false
+ :triggered | :acknowledged | false
+ :triggered | :ignored | false
+ :triggered | :resolved | true
+ :acknowledged | :triggered | false
+ :acknowledged | :acknowledged | false
+ :acknowledged | :ignored | false
+ :acknowledged | :resolved | true
+ :ignored | :triggered | false
+ :ignored | :acknowledged | false
+ :ignored | :ignored | false
+ :ignored | :resolved | true
+ end
+
+ with_them do
+ let!(:existing_alert) { create(:alert_management_alert, existing_status, fingerprint: fingerprint, project: project) }
+ let(:new_alert) { build(:alert_management_alert, new_status, fingerprint: fingerprint, project: project) }
+
+ if params[:valid]
+ it { is_expected.to be_valid }
+ else
+ it { is_expected.to be_invalid }
+ end
+ end
end
context 'different project' do
- let(:project) { create(:project) }
+ let!(:existing_alert) { create(:alert_management_alert, fingerprint: fingerprint) }
it { is_expected.to be_valid }
end
@@ -163,6 +194,15 @@ describe AlertManagement::Alert do
it { is_expected.to contain_exactly(alert_with_fingerprint) }
end
+ describe '.for_environment' do
+ let(:environment) { create(:environment, project: project) }
+ let!(:env_alert) { create(:alert_management_alert, project: project, environment: environment) }
+
+ subject { described_class.for_environment(environment) }
+
+ it { is_expected.to match_array(env_alert) }
+ end
+
describe '.counts_by_status' do
subject { described_class.counts_by_status }
@@ -174,6 +214,51 @@ describe AlertManagement::Alert do
)
end
end
+
+ describe '.counts_by_project_id' do
+ subject { described_class.counts_by_project_id }
+
+ let!(:alert_other_project) { create(:alert_management_alert) }
+
+ it do
+ is_expected.to eq(
+ project.id => 3,
+ alert_other_project.project.id => 1
+ )
+ end
+ end
+
+ describe '.open' do
+ subject { described_class.open }
+
+ let!(:acknowledged_alert) { create(:alert_management_alert, :acknowledged, project: project)}
+
+ it { is_expected.to contain_exactly(acknowledged_alert, triggered_alert) }
+ end
+
+ describe '.not_resolved' do
+ subject { described_class.not_resolved }
+
+ let!(:acknowledged_alert) { create(:alert_management_alert, :acknowledged, project: project) }
+
+ it { is_expected.to contain_exactly(acknowledged_alert, triggered_alert, ignored_alert) }
+ end
+ end
+
+ describe '.last_prometheus_alert_by_project_id' do
+ subject { described_class.last_prometheus_alert_by_project_id }
+
+ let(:project_1) { create(:project) }
+ let!(:alert_1) { create(:alert_management_alert, project: project_1) }
+ let!(:alert_2) { create(:alert_management_alert, project: project_1) }
+
+ let(:project_2) { create(:project) }
+ let!(:alert_3) { create(:alert_management_alert, project: project_2) }
+ let!(:alert_4) { create(:alert_management_alert, project: project_2) }
+
+ it 'returns the latest alert for each project' do
+ expect(subject).to contain_exactly(alert_2, alert_4)
+ end
end
describe '.search' do
@@ -337,4 +422,22 @@ describe AlertManagement::Alert do
expect { subject }.to change { alert.events }.by(1)
end
end
+
+ describe '#present' do
+ context 'when alert is generic' do
+ let(:alert) { build(:alert_management_alert) }
+
+ it 'uses generic alert presenter' do
+ expect(alert.present).to be_kind_of(AlertManagement::AlertPresenter)
+ end
+ end
+
+ context 'when alert is Prometheus specific' do
+ let(:alert) { build(:alert_management_alert, :prometheus) }
+
+ it 'uses Prometheus Alert presenter' do
+ expect(alert.present).to be_kind_of(AlertManagement::PrometheusAlertPresenter)
+ end
+ end
+ end
end
diff --git a/spec/models/alert_management/alert_user_mention_spec.rb b/spec/models/alert_management/alert_user_mention_spec.rb
index cce090a2231..27c3d290dde 100644
--- a/spec/models/alert_management/alert_user_mention_spec.rb
+++ b/spec/models/alert_management/alert_user_mention_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe AlertManagement::AlertUserMention do
+RSpec.describe AlertManagement::AlertUserMention do
describe 'associations' do
it { is_expected.to belong_to(:alert_management_alert) }
it { is_expected.to belong_to(:note) }
diff --git a/spec/models/alerting/project_alerting_setting_spec.rb b/spec/models/alerting/project_alerting_setting_spec.rb
index 40fbe3a6e78..90c5f8313b0 100644
--- a/spec/models/alerting/project_alerting_setting_spec.rb
+++ b/spec/models/alerting/project_alerting_setting_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Alerting::ProjectAlertingSetting do
+RSpec.describe Alerting::ProjectAlertingSetting do
let_it_be(:project) { create(:project) }
subject { create(:project_alerting_setting, project: project) }
diff --git a/spec/models/analytics/cycle_analytics/project_stage_spec.rb b/spec/models/analytics/cycle_analytics/project_stage_spec.rb
index 9850bfde30e..2e024011553 100644
--- a/spec/models/analytics/cycle_analytics/project_stage_spec.rb
+++ b/spec/models/analytics/cycle_analytics/project_stage_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Analytics::CycleAnalytics::ProjectStage do
+RSpec.describe Analytics::CycleAnalytics::ProjectStage do
describe 'associations' do
it { is_expected.to belong_to(:project) }
end
diff --git a/spec/models/appearance_spec.rb b/spec/models/appearance_spec.rb
index 2c32028c3e5..37eddf9a22a 100644
--- a/spec/models/appearance_spec.rb
+++ b/spec/models/appearance_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Appearance do
+RSpec.describe Appearance do
subject { build(:appearance) }
it { include(CacheableAttributes) }
diff --git a/spec/models/application_record_spec.rb b/spec/models/application_record_spec.rb
index 74573d0941c..cc314d9077d 100644
--- a/spec/models/application_record_spec.rb
+++ b/spec/models/application_record_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ApplicationRecord do
+RSpec.describe ApplicationRecord do
describe '#id_in' do
let(:records) { create_list(:user, 3) }
@@ -58,4 +58,11 @@ describe ApplicationRecord do
expect(MergeRequest.underscore).to eq('merge_request')
end
end
+
+ describe '.at_most' do
+ it 'limits the number of records returned' do
+ create_list(:user, 3)
+ expect(User.at_most(2).count).to eq(2)
+ end
+ end
end
diff --git a/spec/models/application_setting/term_spec.rb b/spec/models/application_setting/term_spec.rb
index dd263335b81..82347453437 100644
--- a/spec/models/application_setting/term_spec.rb
+++ b/spec/models/application_setting/term_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ApplicationSetting::Term do
+RSpec.describe ApplicationSetting::Term do
describe 'validations' do
it { is_expected.to validate_presence_of(:terms) }
end
diff --git a/spec/models/application_setting_spec.rb b/spec/models/application_setting_spec.rb
index 96bf19439a1..f618e13ca26 100644
--- a/spec/models/application_setting_spec.rb
+++ b/spec/models/application_setting_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ApplicationSetting do
+RSpec.describe ApplicationSetting do
using RSpec::Parameterized::TableSyntax
subject(:setting) { described_class.create_from_defaults }
diff --git a/spec/models/approval_spec.rb b/spec/models/approval_spec.rb
new file mode 100644
index 00000000000..e2c0d5faa07
--- /dev/null
+++ b/spec/models/approval_spec.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Approval do
+ context 'presence validation' do
+ it { is_expected.to validate_presence_of(:merge_request_id) }
+ it { is_expected.to validate_presence_of(:user_id) }
+ end
+
+ context 'uniqueness validation' do
+ let!(:existing_record) { create(:approval) }
+
+ it { is_expected.to validate_uniqueness_of(:user_id).scoped_to([:merge_request_id]) }
+ end
+end
diff --git a/spec/models/award_emoji_spec.rb b/spec/models/award_emoji_spec.rb
index 8b370e80c9b..f268408c095 100644
--- a/spec/models/award_emoji_spec.rb
+++ b/spec/models/award_emoji_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe AwardEmoji do
+RSpec.describe AwardEmoji do
describe 'Associations' do
it { is_expected.to belong_to(:awardable) }
it { is_expected.to belong_to(:user) }
diff --git a/spec/models/aws/role_spec.rb b/spec/models/aws/role_spec.rb
index d4165567146..612868f6eb0 100644
--- a/spec/models/aws/role_spec.rb
+++ b/spec/models/aws/role_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Aws::Role do
+RSpec.describe Aws::Role do
it { is_expected.to belong_to(:user) }
it { is_expected.to validate_length_of(:role_external_id).is_at_least(1).is_at_most(64) }
diff --git a/spec/models/badge_spec.rb b/spec/models/badge_spec.rb
index fba8f40e99b..f3c95332ca0 100644
--- a/spec/models/badge_spec.rb
+++ b/spec/models/badge_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Badge do
+RSpec.describe Badge do
let(:placeholder_url) { 'http://www.example.com/%{project_path}/%{project_id}/%{default_branch}/%{commit_sha}' }
describe 'validations' do
diff --git a/spec/models/badges/group_badge_spec.rb b/spec/models/badges/group_badge_spec.rb
index c297bc957ea..8913a2e6f17 100644
--- a/spec/models/badges/group_badge_spec.rb
+++ b/spec/models/badges/group_badge_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GroupBadge do
+RSpec.describe GroupBadge do
describe 'associations' do
it { is_expected.to belong_to(:group) }
end
diff --git a/spec/models/badges/project_badge_spec.rb b/spec/models/badges/project_badge_spec.rb
index c0e85d3de87..9b9836129a6 100644
--- a/spec/models/badges/project_badge_spec.rb
+++ b/spec/models/badges/project_badge_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ProjectBadge do
+RSpec.describe ProjectBadge do
let(:placeholder_url) { 'http://www.example.com/%{project_path}/%{project_id}/%{default_branch}/%{commit_sha}' }
describe 'associations' do
diff --git a/spec/models/blob_spec.rb b/spec/models/blob_spec.rb
index c2d6406c3fb..bd4832bd978 100644
--- a/spec/models/blob_spec.rb
+++ b/spec/models/blob_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Blob do
+RSpec.describe Blob do
include FakeBlobHelpers
using RSpec::Parameterized::TableSyntax
diff --git a/spec/models/blob_viewer/base_spec.rb b/spec/models/blob_viewer/base_spec.rb
index 39c7a34f052..682b6dc3b1d 100644
--- a/spec/models/blob_viewer/base_spec.rb
+++ b/spec/models/blob_viewer/base_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe BlobViewer::Base do
+RSpec.describe BlobViewer::Base do
include FakeBlobHelpers
let(:project) { build(:project) }
diff --git a/spec/models/blob_viewer/changelog_spec.rb b/spec/models/blob_viewer/changelog_spec.rb
index b71531ff3c2..5346483cfc8 100644
--- a/spec/models/blob_viewer/changelog_spec.rb
+++ b/spec/models/blob_viewer/changelog_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe BlobViewer::Changelog do
+RSpec.describe BlobViewer::Changelog do
include FakeBlobHelpers
let(:project) { create(:project, :repository) }
diff --git a/spec/models/blob_viewer/composer_json_spec.rb b/spec/models/blob_viewer/composer_json_spec.rb
index a6bb64ba121..8d66e9e951f 100644
--- a/spec/models/blob_viewer/composer_json_spec.rb
+++ b/spec/models/blob_viewer/composer_json_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe BlobViewer::ComposerJson do
+RSpec.describe BlobViewer::ComposerJson do
include FakeBlobHelpers
let(:project) { build_stubbed(:project) }
diff --git a/spec/models/blob_viewer/gemspec_spec.rb b/spec/models/blob_viewer/gemspec_spec.rb
index 291d14e2d72..b6f3e059c7e 100644
--- a/spec/models/blob_viewer/gemspec_spec.rb
+++ b/spec/models/blob_viewer/gemspec_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe BlobViewer::Gemspec do
+RSpec.describe BlobViewer::Gemspec do
include FakeBlobHelpers
let(:project) { build_stubbed(:project) }
diff --git a/spec/models/blob_viewer/gitlab_ci_yml_spec.rb b/spec/models/blob_viewer/gitlab_ci_yml_spec.rb
index e645733e02d..cd885d312dc 100644
--- a/spec/models/blob_viewer/gitlab_ci_yml_spec.rb
+++ b/spec/models/blob_viewer/gitlab_ci_yml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe BlobViewer::GitlabCiYml do
+RSpec.describe BlobViewer::GitlabCiYml do
include FakeBlobHelpers
include RepoHelpers
diff --git a/spec/models/blob_viewer/go_mod_spec.rb b/spec/models/blob_viewer/go_mod_spec.rb
index ba6038533ea..21e84d39a54 100644
--- a/spec/models/blob_viewer/go_mod_spec.rb
+++ b/spec/models/blob_viewer/go_mod_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe BlobViewer::GoMod do
+RSpec.describe BlobViewer::GoMod do
include FakeBlobHelpers
let(:project) { build_stubbed(:project) }
diff --git a/spec/models/blob_viewer/license_spec.rb b/spec/models/blob_viewer/license_spec.rb
index b0426401932..bc970136503 100644
--- a/spec/models/blob_viewer/license_spec.rb
+++ b/spec/models/blob_viewer/license_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe BlobViewer::License do
+RSpec.describe BlobViewer::License do
include FakeBlobHelpers
let(:project) { create(:project, :repository) }
diff --git a/spec/models/blob_viewer/metrics_dashboard_yml_spec.rb b/spec/models/blob_viewer/metrics_dashboard_yml_spec.rb
index f5b8586975d..057f0f32158 100644
--- a/spec/models/blob_viewer/metrics_dashboard_yml_spec.rb
+++ b/spec/models/blob_viewer/metrics_dashboard_yml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe BlobViewer::MetricsDashboardYml do
+RSpec.describe BlobViewer::MetricsDashboardYml do
include FakeBlobHelpers
include RepoHelpers
diff --git a/spec/models/blob_viewer/package_json_spec.rb b/spec/models/blob_viewer/package_json_spec.rb
index 7f7b1dcfcb3..d2e8ab6575f 100644
--- a/spec/models/blob_viewer/package_json_spec.rb
+++ b/spec/models/blob_viewer/package_json_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe BlobViewer::PackageJson do
+RSpec.describe BlobViewer::PackageJson do
include FakeBlobHelpers
let(:project) { build_stubbed(:project) }
diff --git a/spec/models/blob_viewer/podspec_json_spec.rb b/spec/models/blob_viewer/podspec_json_spec.rb
index dd5ed03b77d..61d2602c413 100644
--- a/spec/models/blob_viewer/podspec_json_spec.rb
+++ b/spec/models/blob_viewer/podspec_json_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe BlobViewer::PodspecJson do
+RSpec.describe BlobViewer::PodspecJson do
include FakeBlobHelpers
let(:project) { build_stubbed(:project) }
diff --git a/spec/models/blob_viewer/podspec_spec.rb b/spec/models/blob_viewer/podspec_spec.rb
index 2d9b184c5cb..0a0fbcaebd4 100644
--- a/spec/models/blob_viewer/podspec_spec.rb
+++ b/spec/models/blob_viewer/podspec_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe BlobViewer::Podspec do
+RSpec.describe BlobViewer::Podspec do
include FakeBlobHelpers
let(:project) { build_stubbed(:project) }
diff --git a/spec/models/blob_viewer/readme_spec.rb b/spec/models/blob_viewer/readme_spec.rb
index 89bc5be94fb..4c5f11d55ff 100644
--- a/spec/models/blob_viewer/readme_spec.rb
+++ b/spec/models/blob_viewer/readme_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe BlobViewer::Readme do
+RSpec.describe BlobViewer::Readme do
include FakeBlobHelpers
let(:project) { create(:project, :repository, :wiki_repo) }
diff --git a/spec/models/blob_viewer/route_map_spec.rb b/spec/models/blob_viewer/route_map_spec.rb
index 6c703df5c4c..bb0284d7868 100644
--- a/spec/models/blob_viewer/route_map_spec.rb
+++ b/spec/models/blob_viewer/route_map_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe BlobViewer::RouteMap do
+RSpec.describe BlobViewer::RouteMap do
include FakeBlobHelpers
let(:project) { build_stubbed(:project) }
diff --git a/spec/models/blob_viewer/server_side_spec.rb b/spec/models/blob_viewer/server_side_spec.rb
index f95305abe78..284ac4524f7 100644
--- a/spec/models/blob_viewer/server_side_spec.rb
+++ b/spec/models/blob_viewer/server_side_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe BlobViewer::ServerSide do
+RSpec.describe BlobViewer::ServerSide do
include FakeBlobHelpers
let(:project) { build(:project) }
diff --git a/spec/models/board_group_recent_visit_spec.rb b/spec/models/board_group_recent_visit_spec.rb
index 558be61824f..4d16e1ff839 100644
--- a/spec/models/board_group_recent_visit_spec.rb
+++ b/spec/models/board_group_recent_visit_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe BoardGroupRecentVisit do
+RSpec.describe BoardGroupRecentVisit do
let(:user) { create(:user) }
let(:group) { create(:group) }
let(:board) { create(:board, group: group) }
diff --git a/spec/models/board_project_recent_visit_spec.rb b/spec/models/board_project_recent_visit_spec.rb
index e404fb3bbdb..8e74405fd8c 100644
--- a/spec/models/board_project_recent_visit_spec.rb
+++ b/spec/models/board_project_recent_visit_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe BoardProjectRecentVisit do
+RSpec.describe BoardProjectRecentVisit do
let(:user) { create(:user) }
let(:project) { create(:project) }
let(:board) { create(:board, project: project) }
diff --git a/spec/models/board_spec.rb b/spec/models/board_spec.rb
index 2d5309b4d23..d309b4dbdb9 100644
--- a/spec/models/board_spec.rb
+++ b/spec/models/board_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Board do
+RSpec.describe Board do
let(:project) { create(:project) }
let(:other_project) { create(:project) }
diff --git a/spec/models/broadcast_message_spec.rb b/spec/models/broadcast_message_spec.rb
index 8032f913d86..fc463c6af52 100644
--- a/spec/models/broadcast_message_spec.rb
+++ b/spec/models/broadcast_message_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe BroadcastMessage do
+RSpec.describe BroadcastMessage do
subject { build(:broadcast_message) }
it { is_expected.to be_valid }
diff --git a/spec/models/chat_name_spec.rb b/spec/models/chat_name_spec.rb
index 02594b98665..623e55aad21 100644
--- a/spec/models/chat_name_spec.rb
+++ b/spec/models/chat_name_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ChatName do
+RSpec.describe ChatName do
let_it_be(:chat_name) { create(:chat_name) }
subject { chat_name }
diff --git a/spec/models/chat_team_spec.rb b/spec/models/chat_team_spec.rb
index 107fdaccc68..08fd05324aa 100644
--- a/spec/models/chat_team_spec.rb
+++ b/spec/models/chat_team_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ChatTeam do
+RSpec.describe ChatTeam do
let_it_be(:chat_team) { create(:chat_team) }
subject { chat_team }
diff --git a/spec/models/ci/artifact_blob_spec.rb b/spec/models/ci/artifact_blob_spec.rb
index 99983686670..44f895cc1c5 100644
--- a/spec/models/ci/artifact_blob_spec.rb
+++ b/spec/models/ci/artifact_blob_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::ArtifactBlob do
+RSpec.describe Ci::ArtifactBlob do
let_it_be(:project) { create(:project, :public) }
let_it_be(:build) { create(:ci_build, :artifacts, project: project) }
let(:entry) { build.artifacts_metadata_entry('other_artifacts_0.1.2/another-subdirectory/banana_sample.gif') }
diff --git a/spec/models/ci/bridge_spec.rb b/spec/models/ci/bridge_spec.rb
index 385261e0ee9..3a459e5897a 100644
--- a/spec/models/ci/bridge_spec.rb
+++ b/spec/models/ci/bridge_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::Bridge do
+RSpec.describe Ci::Bridge do
let_it_be(:project) { create(:project) }
let_it_be(:target_project) { create(:project, name: 'project', namespace: create(:namespace, name: 'my')) }
let_it_be(:pipeline) { create(:ci_pipeline, project: project) }
@@ -47,8 +47,8 @@ describe Ci::Bridge do
CI_JOB_NAME CI_JOB_STAGE CI_COMMIT_SHA CI_COMMIT_SHORT_SHA
CI_COMMIT_BEFORE_SHA CI_COMMIT_REF_NAME CI_COMMIT_REF_SLUG
CI_PROJECT_ID CI_PROJECT_NAME CI_PROJECT_PATH
- CI_PROJECT_PATH_SLUG CI_PROJECT_NAMESPACE CI_PIPELINE_IID
- CI_CONFIG_PATH CI_PIPELINE_SOURCE CI_COMMIT_MESSAGE
+ CI_PROJECT_PATH_SLUG CI_PROJECT_NAMESPACE CI_PROJECT_ROOT_NAMESPACE
+ CI_PIPELINE_IID CI_CONFIG_PATH CI_PIPELINE_SOURCE CI_COMMIT_MESSAGE
CI_COMMIT_TITLE CI_COMMIT_DESCRIPTION CI_COMMIT_REF_PROTECTED
]
diff --git a/spec/models/ci/build_dependencies_spec.rb b/spec/models/ci/build_dependencies_spec.rb
index 8f2199ac360..4fa1b3eb5a5 100644
--- a/spec/models/ci/build_dependencies_spec.rb
+++ b/spec/models/ci/build_dependencies_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::BuildDependencies do
+RSpec.describe Ci::BuildDependencies do
let_it_be(:user) { create(:user) }
let_it_be(:project, reload: true) { create(:project, :repository) }
diff --git a/spec/models/ci/build_metadata_spec.rb b/spec/models/ci/build_metadata_spec.rb
index 588e5872cc8..e4d71632957 100644
--- a/spec/models/ci/build_metadata_spec.rb
+++ b/spec/models/ci/build_metadata_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::BuildMetadata do
+RSpec.describe Ci::BuildMetadata do
let_it_be(:user) { create(:user) }
let_it_be(:group) { create(:group) }
let_it_be(:project) { create(:project, :repository, group: group, build_timeout: 2000) }
@@ -92,4 +92,33 @@ describe Ci::BuildMetadata do
end
end
end
+
+ describe 'validations' do
+ context 'when attributes are valid' do
+ it 'returns no errors' do
+ metadata.secrets = {
+ DATABASE_PASSWORD: {
+ vault: {
+ engine: { name: 'kv-v2', path: 'kv-v2' },
+ path: 'production/db',
+ field: 'password'
+ }
+ }
+ }
+
+ expect(metadata).to be_valid
+ end
+ end
+
+ context 'when data is invalid' do
+ it 'returns errors' do
+ metadata.secrets = { DATABASE_PASSWORD: { vault: {} } }
+
+ aggregate_failures do
+ expect(metadata).to be_invalid
+ expect(metadata.errors.full_messages).to eq(["Secrets must be a valid json schema"])
+ end
+ end
+ end
+ end
end
diff --git a/spec/models/ci/build_need_spec.rb b/spec/models/ci/build_need_spec.rb
index d1186fa981d..43cce073918 100644
--- a/spec/models/ci/build_need_spec.rb
+++ b/spec/models/ci/build_need_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::BuildNeed, model: true do
+RSpec.describe Ci::BuildNeed, model: true do
let(:build_need) { build(:ci_build_need) }
it { is_expected.to belong_to(:build) }
@@ -17,4 +17,22 @@ describe Ci::BuildNeed, model: true do
it { expect(described_class.artifacts).to contain_exactly(with_artifacts) }
end
+
+ describe 'BulkInsertSafe' do
+ let(:ci_build) { build(:ci_build) }
+
+ it "bulk inserts from Ci::Build model" do
+ ci_build.needs_attributes = [
+ { name: "build", artifacts: true },
+ { name: "build2", artifacts: true },
+ { name: "build3", artifacts: true }
+ ]
+
+ expect(described_class).to receive(:bulk_insert!).and_call_original
+
+ BulkInsertableAssociations.with_bulk_insert do
+ ci_build.save!
+ end
+ end
+ end
end
diff --git a/spec/models/ci/build_report_result_spec.rb b/spec/models/ci/build_report_result_spec.rb
index 078b0d100a1..e78f602feef 100644
--- a/spec/models/ci/build_report_result_spec.rb
+++ b/spec/models/ci/build_report_result_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::BuildReportResult do
+RSpec.describe Ci::BuildReportResult do
let(:build_report_result) { build(:ci_build_report_result, :with_junit_success) }
describe 'associations' do
diff --git a/spec/models/ci/build_runner_session_spec.rb b/spec/models/ci/build_runner_session_spec.rb
index 3e520407884..601c6ad26f9 100644
--- a/spec/models/ci/build_runner_session_spec.rb
+++ b/spec/models/ci/build_runner_session_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::BuildRunnerSession, model: true do
+RSpec.describe Ci::BuildRunnerSession, model: true do
let!(:build) { create(:ci_build, :with_runner_session) }
let(:url) { 'https://new.example.com' }
diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb
index 6fdd8463329..857b238981b 100644
--- a/spec/models/ci/build_spec.rb
+++ b/spec/models/ci/build_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::Build do
+RSpec.describe Ci::Build do
let_it_be(:user) { create(:user) }
let_it_be(:group, reload: true) { create(:group) }
let_it_be(:project, reload: true) { create(:project, :repository, group: group) }
@@ -1811,6 +1811,50 @@ describe Ci::Build do
end
end
+ describe '.keep_artifacts!' do
+ let!(:build) { create(:ci_build, artifacts_expire_at: Time.current + 7.days) }
+ let!(:builds_for_update) do
+ Ci::Build.where(id: create_list(:ci_build, 3, artifacts_expire_at: Time.current + 7.days).map(&:id))
+ end
+
+ it 'resets expire_at' do
+ builds_for_update.keep_artifacts!
+
+ builds_for_update.each do |build|
+ expect(build.reload.artifacts_expire_at).to be_nil
+ end
+ end
+
+ it 'does not reset expire_at for other builds' do
+ builds_for_update.keep_artifacts!
+
+ expect(build.reload.artifacts_expire_at).to be_present
+ end
+
+ context 'when having artifacts files' do
+ let!(:artifact) { create(:ci_job_artifact, job: build, expire_in: '7 days') }
+ let!(:artifacts_for_update) do
+ builds_for_update.map do |build|
+ create(:ci_job_artifact, job: build, expire_in: '7 days')
+ end
+ end
+
+ it 'resets dependent objects' do
+ builds_for_update.keep_artifacts!
+
+ artifacts_for_update.each do |artifact|
+ expect(artifact.reload.expire_at).to be_nil
+ end
+ end
+
+ it 'does not reset dependent object for other builds' do
+ builds_for_update.keep_artifacts!
+
+ expect(artifact.reload.expire_at).to be_present
+ end
+ end
+ end
+
describe '#keep_artifacts!' do
let(:build) { create(:ci_build, artifacts_expire_at: Time.current + 7.days) }
@@ -2336,6 +2380,7 @@ describe Ci::Build do
{ key: 'CI_PROJECT_PATH', value: project.full_path, public: true, masked: false },
{ key: 'CI_PROJECT_PATH_SLUG', value: project.full_path_slug, public: true, masked: false },
{ key: 'CI_PROJECT_NAMESPACE', value: project.namespace.full_path, public: true, masked: false },
+ { key: 'CI_PROJECT_ROOT_NAMESPACE', value: project.namespace.root_ancestor.path, public: true, masked: false },
{ key: 'CI_PROJECT_URL', value: project.web_url, public: true, masked: false },
{ key: 'CI_PROJECT_VISIBILITY', value: 'private', public: true, masked: false },
{ key: 'CI_PROJECT_REPOSITORY_LANGUAGES', value: project.repository_languages.map(&:name).join(',').downcase, public: true, masked: false },
@@ -2929,19 +2974,6 @@ describe Ci::Build do
it { is_expected.to include(deployment_variable) }
end
- context 'when build has a freeze period' do
- let(:freeze_variable) { { key: 'CI_DEPLOY_FREEZE', value: 'true', masked: false, public: true } }
-
- before do
- expect_next_instance_of(Ci::FreezePeriodStatus) do |freeze_period|
- expect(freeze_period).to receive(:execute)
- .and_return(true)
- end
- end
-
- it { is_expected.to include(freeze_variable) }
- end
-
context 'when project has default CI config path' do
let(:ci_config_path) { { key: 'CI_CONFIG_PATH', value: '.gitlab-ci.yml', public: true, masked: false } }
@@ -3269,17 +3301,6 @@ describe Ci::Build do
expect(build.scoped_variables_hash).not_to include('MY_VAR': 'my value 1')
end
end
-
- context 'when CI instance variables are disabled' do
- before do
- create(:ci_instance_variable, key: 'MY_VAR', value: 'my value 1')
- stub_feature_flags(ci_instance_level_variables: false)
- end
-
- it 'does not include instance level variables' do
- expect(build.scoped_variables_hash).not_to include('MY_VAR': 'my value 1')
- end
- end
end
describe '#any_unmet_prerequisites?' do
@@ -4050,6 +4071,10 @@ describe Ci::Build do
it 'parses blobs and add the results to the terraform report' do
expect { build.collect_terraform_reports!(terraform_reports) }.not_to raise_error
+ terraform_reports.plans.each do |key, hash_value|
+ expect(hash_value.keys).to match_array(%w[create delete job_id job_name job_path update])
+ end
+
expect(terraform_reports.plans).to match(
a_hash_including(
build.id.to_s => a_hash_including(
@@ -4068,9 +4093,19 @@ describe Ci::Build do
create(:ci_job_artifact, :terraform_with_corrupted_data, job: build, project: build.project)
end
- it 'raises an error' do
- expect { build.collect_terraform_reports!(terraform_reports) }.to raise_error(
- Gitlab::Ci::Parsers::Terraform::Tfplan::TfplanParserError
+ it 'adds invalid plan report' do
+ expect { build.collect_terraform_reports!(terraform_reports) }.not_to raise_error
+
+ terraform_reports.plans.each do |key, hash_value|
+ expect(hash_value.keys).to match_array(%w[job_id job_name job_path tf_report_error])
+ end
+
+ expect(terraform_reports.plans).to match(
+ a_hash_including(
+ build.id.to_s => a_hash_including(
+ 'tf_report_error' => :invalid_json_format
+ )
+ )
)
end
end
@@ -4258,15 +4293,15 @@ describe Ci::Build do
end
end
- context 'when `release_steps` feature is required by build' do
+ context 'when `multi_build_steps` feature is required by build' do
before do
expect(build).to receive(:runner_required_feature_names) do
- [:release_steps]
+ [:multi_build_steps]
end
end
context 'when runner provides given feature' do
- let(:runner_features) { { release_steps: true } }
+ let(:runner_features) { { multi_build_steps: true } }
it { is_expected.to be_truthy }
end
diff --git a/spec/models/ci/build_trace_chunk_spec.rb b/spec/models/ci/build_trace_chunk_spec.rb
index 85873847fca..dab523f67ab 100644
--- a/spec/models/ci/build_trace_chunk_spec.rb
+++ b/spec/models/ci/build_trace_chunk_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::BuildTraceChunk, :clean_gitlab_redis_shared_state do
+RSpec.describe Ci::BuildTraceChunk, :clean_gitlab_redis_shared_state do
include ExclusiveLeaseHelpers
let_it_be(:build) { create(:ci_build, :running) }
diff --git a/spec/models/ci/build_trace_chunks/database_spec.rb b/spec/models/ci/build_trace_chunks/database_spec.rb
index eb94d7dae38..245625b8046 100644
--- a/spec/models/ci/build_trace_chunks/database_spec.rb
+++ b/spec/models/ci/build_trace_chunks/database_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::BuildTraceChunks::Database do
+RSpec.describe Ci::BuildTraceChunks::Database do
let(:data_store) { described_class.new }
describe '#available?' do
diff --git a/spec/models/ci/build_trace_chunks/fog_spec.rb b/spec/models/ci/build_trace_chunks/fog_spec.rb
index b8d78bcd069..7ef3018d87b 100644
--- a/spec/models/ci/build_trace_chunks/fog_spec.rb
+++ b/spec/models/ci/build_trace_chunks/fog_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::BuildTraceChunks::Fog do
+RSpec.describe Ci::BuildTraceChunks::Fog do
let(:data_store) { described_class.new }
before do
diff --git a/spec/models/ci/build_trace_chunks/redis_spec.rb b/spec/models/ci/build_trace_chunks/redis_spec.rb
index 6cff33d24fa..c37b8697a4d 100644
--- a/spec/models/ci/build_trace_chunks/redis_spec.rb
+++ b/spec/models/ci/build_trace_chunks/redis_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::BuildTraceChunks::Redis, :clean_gitlab_redis_shared_state do
+RSpec.describe Ci::BuildTraceChunks::Redis, :clean_gitlab_redis_shared_state do
let(:data_store) { described_class.new }
describe '#available?' do
diff --git a/spec/models/ci/build_trace_section_name_spec.rb b/spec/models/ci/build_trace_section_name_spec.rb
index 11e2d27ff79..b220e67d48e 100644
--- a/spec/models/ci/build_trace_section_name_spec.rb
+++ b/spec/models/ci/build_trace_section_name_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::BuildTraceSectionName, model: true do
+RSpec.describe Ci::BuildTraceSectionName, model: true do
subject { build(:ci_build_trace_section_name) }
it { is_expected.to belong_to(:project) }
diff --git a/spec/models/ci/build_trace_section_spec.rb b/spec/models/ci/build_trace_section_spec.rb
index 5bd3a953ec0..640bd202b3a 100644
--- a/spec/models/ci/build_trace_section_spec.rb
+++ b/spec/models/ci/build_trace_section_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::BuildTraceSection, model: true do
+RSpec.describe Ci::BuildTraceSection, model: true do
it { is_expected.to belong_to(:build)}
it { is_expected.to belong_to(:project)}
it { is_expected.to belong_to(:section_name)}
diff --git a/spec/models/ci/build_trace_spec.rb b/spec/models/ci/build_trace_spec.rb
index 2471a6fa827..3beca0565c6 100644
--- a/spec/models/ci/build_trace_spec.rb
+++ b/spec/models/ci/build_trace_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::BuildTrace do
+RSpec.describe Ci::BuildTrace do
let(:build) { build_stubbed(:ci_build) }
let(:state) { nil }
let(:data) { StringIO.new('the-stream') }
@@ -11,7 +11,7 @@ describe Ci::BuildTrace do
Gitlab::Ci::Trace::Stream.new { data }
end
- subject { described_class.new(build: build, stream: stream, state: state, content_format: content_format) }
+ subject { described_class.new(build: build, stream: stream, state: state) }
shared_examples 'delegates methods' do
it { is_expected.to delegate_method(:state).to(:trace) }
@@ -25,29 +25,11 @@ describe Ci::BuildTrace do
it { is_expected.to delegate_method(:complete?).to(:build).with_prefix }
end
- context 'with :json content format' do
- let(:content_format) { :json }
+ it_behaves_like 'delegates methods'
- it_behaves_like 'delegates methods'
-
- it { is_expected.to be_json }
-
- it 'returns formatted trace' do
- expect(subject.trace.lines).to eq([
- { offset: 0, content: [{ text: 'the-stream' }] }
- ])
- end
- end
-
- context 'with :html content format' do
- let(:content_format) { :html }
-
- it_behaves_like 'delegates methods'
-
- it { is_expected.to be_html }
-
- it 'returns formatted trace' do
- expect(subject.trace.html).to eq('<span>the-stream</span>')
- end
+ it 'returns formatted trace' do
+ expect(subject.lines).to eq([
+ { offset: 0, content: [{ text: 'the-stream' }] }
+ ])
end
end
diff --git a/spec/models/ci/daily_build_group_report_result_spec.rb b/spec/models/ci/daily_build_group_report_result_spec.rb
index f2ce1b5775f..059a5b76b9a 100644
--- a/spec/models/ci/daily_build_group_report_result_spec.rb
+++ b/spec/models/ci/daily_build_group_report_result_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::DailyBuildGroupReportResult do
+RSpec.describe Ci::DailyBuildGroupReportResult do
let(:daily_build_group_report_result) { build(:ci_daily_build_group_report_result)}
describe 'associations' do
diff --git a/spec/models/ci/freeze_period_status_spec.rb b/spec/models/ci/freeze_period_status_spec.rb
index b700ec8c45f..831895cb528 100644
--- a/spec/models/ci/freeze_period_status_spec.rb
+++ b/spec/models/ci/freeze_period_status_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe Ci::FreezePeriodStatus do
+RSpec.describe Ci::FreezePeriodStatus do
let(:project) { create :project }
# '0 23 * * 5' == "At 23:00 on Friday."", '0 7 * * 1' == "At 07:00 on Monday.""
let(:friday_2300) { '0 23 * * 5' }
diff --git a/spec/models/ci/group_spec.rb b/spec/models/ci/group_spec.rb
index 868382e3756..dc9aee906ea 100644
--- a/spec/models/ci/group_spec.rb
+++ b/spec/models/ci/group_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::Group do
+RSpec.describe Ci::Group do
let_it_be(:project) { create(:project) }
let!(:jobs) { build_list(:ci_build, 1, :success, project: project) }
diff --git a/spec/models/ci/group_variable_spec.rb b/spec/models/ci/group_variable_spec.rb
index 610db9bf0e5..c8eac4d8765 100644
--- a/spec/models/ci/group_variable_spec.rb
+++ b/spec/models/ci/group_variable_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::GroupVariable do
+RSpec.describe Ci::GroupVariable do
subject { build(:ci_group_variable) }
it_behaves_like "CI variable"
diff --git a/spec/models/ci/instance_variable_spec.rb b/spec/models/ci/instance_variable_spec.rb
index 4d69b7ac2f8..344ba5bfafd 100644
--- a/spec/models/ci/instance_variable_spec.rb
+++ b/spec/models/ci/instance_variable_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::InstanceVariable do
+RSpec.describe Ci::InstanceVariable do
subject { build(:ci_instance_variable) }
it_behaves_like "CI variable"
@@ -15,21 +15,6 @@ describe Ci::InstanceVariable do
subject { build(:ci_instance_variable) }
end
- context 'with instance level variable feature flag disabled' do
- let(:plan_limits) { create(:plan_limits, :default_plan) }
-
- before do
- stub_feature_flags(ci_instance_level_variables_limit: false)
- plan_limits.update(described_class.limit_name => 1)
- create(:ci_instance_variable)
- end
-
- it 'can create new models exceeding the plan limits', :aggregate_failures do
- expect { subject.save }.to change { described_class.count }
- expect(subject.errors[:base]).to be_empty
- end
- end
-
describe '.unprotected' do
subject { described_class.unprotected }
diff --git a/spec/models/ci/job_artifact_spec.rb b/spec/models/ci/job_artifact_spec.rb
index 17e00533ac3..b5f9128b7c5 100644
--- a/spec/models/ci/job_artifact_spec.rb
+++ b/spec/models/ci/job_artifact_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::JobArtifact do
+RSpec.describe Ci::JobArtifact do
let(:artifact) { create(:ci_job_artifact, :archive) }
describe "Associations" do
@@ -110,6 +110,21 @@ describe Ci::JobArtifact do
end
end
+ describe '.associated_file_types_for' do
+ using RSpec::Parameterized::TableSyntax
+
+ subject { Ci::JobArtifact.associated_file_types_for(file_type) }
+
+ where(:file_type, :result) do
+ 'codequality' | %w(codequality)
+ 'quality' | nil
+ end
+
+ with_them do
+ it { is_expected.to eq result }
+ end
+ end
+
describe '.erasable' do
subject { described_class.erasable }
@@ -174,18 +189,6 @@ describe Ci::JobArtifact do
end
end
- describe '.for_ref' do
- let(:first_pipeline) { create(:ci_pipeline, ref: 'first_ref') }
- let(:second_pipeline) { create(:ci_pipeline, ref: 'second_ref', project: first_pipeline.project) }
- let!(:first_artifact) { create(:ci_job_artifact, job: create(:ci_build, pipeline: first_pipeline)) }
- let!(:second_artifact) { create(:ci_job_artifact, job: create(:ci_build, pipeline: second_pipeline)) }
-
- it 'returns job artifacts for a given pipeline ref' do
- expect(described_class.for_ref(first_pipeline.ref, first_pipeline.project.id)).to eq([first_artifact])
- expect(described_class.for_ref(second_pipeline.ref, first_pipeline.project.id)).to eq([second_artifact])
- end
- end
-
describe '.for_job_name' do
it 'returns job artifacts for a given job name' do
first_job = create(:ci_build, name: 'first')
@@ -501,4 +504,100 @@ describe Ci::JobArtifact do
end
end
end
+
+ describe '.file_types' do
+ context 'all file types have corresponding limit' do
+ let_it_be(:plan_limits) { create(:plan_limits) }
+
+ where(:file_type) do
+ described_class.file_types.keys
+ end
+
+ with_them do
+ let(:limit_name) { "#{described_class::PLAN_LIMIT_PREFIX}#{file_type}" }
+
+ it { expect(plan_limits.attributes).to include(limit_name), file_type_limit_failure_message(file_type, limit_name) }
+ end
+ end
+ end
+
+ describe '.max_artifact_size' do
+ let(:build) { create(:ci_build) }
+
+ subject(:max_size) { described_class.max_artifact_size(type: artifact_type, project: build.project) }
+
+ context 'when file type is supported' do
+ let(:project_closest_setting) { 1024 }
+ let(:artifact_type) { 'junit' }
+
+ before do
+ stub_feature_flags(ci_max_artifact_size_per_type: flag_enabled)
+ allow(build.project).to receive(:closest_setting).with(:max_artifacts_size).and_return(project_closest_setting)
+ end
+
+ shared_examples_for 'basing off the project closest setting' do
+ it { is_expected.to eq(project_closest_setting.megabytes.to_i) }
+ end
+
+ shared_examples_for 'basing off the plan limit' do
+ it { is_expected.to eq(max_size_for_type.megabytes.to_i) }
+ end
+
+ context 'and feature flag for custom max size per type is enabled' do
+ let(:flag_enabled) { true }
+ let(:limit_name) { "#{described_class::PLAN_LIMIT_PREFIX}#{artifact_type}" }
+
+ let!(:plan_limits) { create(:plan_limits, :default_plan) }
+
+ context 'and plan limit is disabled for the given artifact type' do
+ before do
+ plan_limits.update!(limit_name => 0)
+ end
+
+ it_behaves_like 'basing off the project closest setting'
+
+ context 'and project closest setting results to zero' do
+ let(:project_closest_setting) { 0 }
+
+ it { is_expected.to eq(0) }
+ end
+ end
+
+ context 'and plan limit is enabled for the given artifact type' do
+ before do
+ plan_limits.update!(limit_name => max_size_for_type)
+ end
+
+ context 'and plan limit is smaller than project setting' do
+ let(:max_size_for_type) { project_closest_setting - 1 }
+
+ it_behaves_like 'basing off the plan limit'
+ end
+
+ context 'and plan limit is smaller than project setting' do
+ let(:max_size_for_type) { project_closest_setting + 1 }
+
+ it_behaves_like 'basing off the project closest setting'
+ end
+ end
+ end
+
+ context 'and feature flag for custom max size per type is disabled' do
+ let(:flag_enabled) { false }
+
+ it_behaves_like 'basing off the project closest setting'
+ end
+ end
+ end
+
+ def file_type_limit_failure_message(type, limit_name)
+ <<~MSG
+ The artifact type `#{type}` is missing its counterpart plan limit which is expected to be named `#{limit_name}`.
+
+ Please refer to https://docs.gitlab.com/ee/development/application_limits.html on how to add new plan limit columns.
+
+ Take note that while existing max size plan limits default to 0, succeeding new limits are recommended to have
+ non-zero default values.
+ MSG
+ end
end
diff --git a/spec/models/ci/job_variable_spec.rb b/spec/models/ci/job_variable_spec.rb
index b94a914c784..4aebd3283f0 100644
--- a/spec/models/ci/job_variable_spec.rb
+++ b/spec/models/ci/job_variable_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::JobVariable do
+RSpec.describe Ci::JobVariable do
subject { build(:ci_job_variable) }
it_behaves_like "CI variable"
diff --git a/spec/models/ci/legacy_stage_spec.rb b/spec/models/ci/legacy_stage_spec.rb
index f503fc10c08..c53f6abb037 100644
--- a/spec/models/ci/legacy_stage_spec.rb
+++ b/spec/models/ci/legacy_stage_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::LegacyStage do
+RSpec.describe Ci::LegacyStage do
let(:stage) { build(:ci_stage) }
let(:pipeline) { stage.pipeline }
let(:stage_name) { stage.name }
diff --git a/spec/models/ci/persistent_ref_spec.rb b/spec/models/ci/persistent_ref_spec.rb
index 89dd9b05331..18552317025 100644
--- a/spec/models/ci/persistent_ref_spec.rb
+++ b/spec/models/ci/persistent_ref_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::PersistentRef do
+RSpec.describe Ci::PersistentRef do
it 'cleans up persistent refs after pipeline finished' do
pipeline = create(:ci_pipeline, :running)
diff --git a/spec/models/ci/pipeline_config_spec.rb b/spec/models/ci/pipeline_config_spec.rb
index 25f514ee5ab..3d033d33df3 100644
--- a/spec/models/ci/pipeline_config_spec.rb
+++ b/spec/models/ci/pipeline_config_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::PipelineConfig, type: :model do
+RSpec.describe Ci::PipelineConfig, type: :model do
it { is_expected.to belong_to(:pipeline) }
it { is_expected.to validate_presence_of(:pipeline) }
diff --git a/spec/models/ci/pipeline_message_spec.rb b/spec/models/ci/pipeline_message_spec.rb
new file mode 100644
index 00000000000..6c97a025625
--- /dev/null
+++ b/spec/models/ci/pipeline_message_spec.rb
@@ -0,0 +1,53 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Ci::PipelineMessage do
+ describe 'validations' do
+ subject { described_class.new(pipeline: pipeline, content: content) }
+
+ let(:pipeline) { create(:ci_pipeline) }
+
+ context 'when message content is longer than the limit' do
+ let(:content) { 'x' * (described_class::MAX_CONTENT_LENGTH + 1) }
+
+ it 'is truncated with ellipsis' do
+ subject.save!
+
+ expect(subject.content).to end_with('x...')
+ expect(subject.content.length).to eq(described_class::MAX_CONTENT_LENGTH)
+ end
+ end
+
+ context 'when message is not present' do
+ let(:content) { '' }
+
+ it 'returns an error' do
+ expect(subject.save).to be_falsey
+ expect(subject.errors[:content]).to be_present
+ end
+ end
+
+ context 'when message content is valid' do
+ let(:content) { 'valid message content' }
+
+ it 'is saved with default error severity' do
+ subject.save!
+
+ expect(subject.content).to eq(content)
+ expect(subject.severity).to eq('error')
+ expect(subject).to be_error
+ end
+
+ it 'is persist the defined severity' do
+ subject.severity = :warning
+
+ subject.save!
+
+ expect(subject.content).to eq(content)
+ expect(subject.severity).to eq('warning')
+ expect(subject).to be_warning
+ end
+ end
+ end
+end
diff --git a/spec/models/ci/pipeline_schedule_spec.rb b/spec/models/ci/pipeline_schedule_spec.rb
index 4ba70552f01..949d5f7bd04 100644
--- a/spec/models/ci/pipeline_schedule_spec.rb
+++ b/spec/models/ci/pipeline_schedule_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::PipelineSchedule do
+RSpec.describe Ci::PipelineSchedule do
subject { build(:ci_pipeline_schedule) }
it { is_expected.to belong_to(:project) }
diff --git a/spec/models/ci/pipeline_schedule_variable_spec.rb b/spec/models/ci/pipeline_schedule_variable_spec.rb
index c96a24d5042..fd6b1d3dce0 100644
--- a/spec/models/ci/pipeline_schedule_variable_spec.rb
+++ b/spec/models/ci/pipeline_schedule_variable_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::PipelineScheduleVariable do
+RSpec.describe Ci::PipelineScheduleVariable do
subject { build(:ci_pipeline_schedule_variable) }
it_behaves_like "CI variable"
diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb
index 782a4206c36..ed2466d6413 100644
--- a/spec/models/ci/pipeline_spec.rb
+++ b/spec/models/ci/pipeline_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::Pipeline, :mailer do
+RSpec.describe Ci::Pipeline, :mailer do
include ProjectForksHelper
include StubRequests
@@ -219,6 +219,50 @@ describe Ci::Pipeline, :mailer do
end
end
+ describe '.outside_pipeline_family' do
+ subject(:outside_pipeline_family) { described_class.outside_pipeline_family(upstream_pipeline) }
+
+ let(:upstream_pipeline) { create(:ci_pipeline, project: project) }
+ let(:child_pipeline) { create(:ci_pipeline, project: project) }
+
+ let!(:other_pipeline) { create(:ci_pipeline, project: project) }
+
+ before do
+ create(:ci_sources_pipeline,
+ source_job: create(:ci_build, pipeline: upstream_pipeline),
+ source_project: project,
+ pipeline: child_pipeline,
+ project: project)
+ end
+
+ it 'only returns pipelines outside pipeline family' do
+ expect(outside_pipeline_family).to contain_exactly(other_pipeline)
+ end
+ end
+
+ describe '.before_pipeline' do
+ subject(:before_pipeline) { described_class.before_pipeline(child_pipeline) }
+
+ let!(:older_other_pipeline) { create(:ci_pipeline, project: project) }
+
+ let!(:upstream_pipeline) { create(:ci_pipeline, project: project) }
+ let!(:child_pipeline) { create(:ci_pipeline, project: project) }
+
+ let!(:other_pipeline) { create(:ci_pipeline, project: project) }
+
+ before do
+ create(:ci_sources_pipeline,
+ source_job: create(:ci_build, pipeline: upstream_pipeline),
+ source_project: project,
+ pipeline: child_pipeline,
+ project: project)
+ end
+
+ it 'only returns older pipelines outside pipeline family' do
+ expect(before_pipeline).to contain_exactly(older_other_pipeline)
+ end
+ end
+
describe '#merge_request?' do
let(:pipeline) { create(:ci_pipeline, merge_request: merge_request) }
let(:merge_request) { create(:merge_request) }
@@ -1488,6 +1532,35 @@ describe Ci::Pipeline, :mailer do
sha: project.commit.sha)
end
+ describe '#lazy_ref_commit' do
+ let(:another) do
+ create(:ci_pipeline,
+ project: project,
+ ref: 'feature',
+ sha: project.commit('feature').sha)
+ end
+
+ let(:unicode) do
+ create(:ci_pipeline,
+ project: project,
+ ref: 'ü/unicode/multi-byte')
+ end
+
+ it 'returns the latest commit for a ref lazily' do
+ expect(project.repository)
+ .to receive(:list_commits_by_ref_name).once
+ .and_call_original
+
+ pipeline.lazy_ref_commit
+ another.lazy_ref_commit
+ unicode.lazy_ref_commit
+
+ expect(pipeline.lazy_ref_commit.id).to eq pipeline.sha
+ expect(another.lazy_ref_commit.id).to eq another.sha
+ expect(unicode.lazy_ref_commit).to be_nil
+ end
+ end
+
describe '#latest?' do
context 'with latest sha' do
it 'returns true' do
@@ -1496,17 +1569,26 @@ describe Ci::Pipeline, :mailer do
end
context 'with a branch name as the ref' do
- it 'looks up commit with the full ref name' do
- expect(pipeline.project).to receive(:commit).with('refs/heads/master').and_call_original
+ it 'looks up a commit for a branch' do
+ expect(pipeline.ref).to eq 'master'
+ expect(pipeline).to be_latest
+ end
+ end
+
+ context 'with a tag name as a ref' do
+ it 'looks up a commit for a tag' do
+ expect(project.repository.branch_names).not_to include 'v1.0.0'
+ pipeline.update(sha: project.commit('v1.0.0').sha, ref: 'v1.0.0', tag: true)
+
+ expect(pipeline).to be_tag
expect(pipeline).to be_latest
end
end
context 'with not latest sha' do
before do
- pipeline.update(
- sha: project.commit("#{project.default_branch}~1").sha)
+ pipeline.update(sha: project.commit("#{project.default_branch}~1").sha)
end
it 'returns false' do
@@ -1932,6 +2014,23 @@ describe Ci::Pipeline, :mailer do
end
end
+ describe '.last_finished_for_ref_id' do
+ let(:project) { create(:project, :repository) }
+ let(:branch) { project.default_branch }
+ let(:ref) { project.ci_refs.take }
+ let(:config_source) { Ci::PipelineEnums.config_sources[:parameter_source] }
+ let!(:pipeline1) { create(:ci_pipeline, :success, project: project, ref: branch) }
+ let!(:pipeline2) { create(:ci_pipeline, :success, project: project, ref: branch) }
+ let!(:pipeline3) { create(:ci_pipeline, :failed, project: project, ref: branch) }
+ let!(:pipeline4) { create(:ci_pipeline, :success, project: project, ref: branch) }
+ let!(:pipeline5) { create(:ci_pipeline, :success, project: project, ref: branch, config_source: config_source) }
+
+ it 'returns the expected pipeline' do
+ result = described_class.last_finished_for_ref_id(ref.id)
+ expect(result).to eq(pipeline4)
+ end
+ end
+
describe '.internal_sources' do
subject { described_class.internal_sources }
@@ -2087,7 +2186,7 @@ describe Ci::Pipeline, :mailer do
it 'raises an exception' do
expect { pipeline.update_legacy_status }
- .to raise_error(HasStatus::UnknownStatusError)
+ .to raise_error(Ci::HasStatus::UnknownStatusError)
end
end
end
@@ -2580,6 +2679,55 @@ describe Ci::Pipeline, :mailer do
end
end
+ describe '#same_family_pipeline_ids' do
+ subject(:same_family_pipeline_ids) { pipeline.same_family_pipeline_ids }
+
+ context 'when pipeline is not child nor parent' do
+ it 'returns just the pipeline id' do
+ expect(same_family_pipeline_ids).to contain_exactly(pipeline.id)
+ end
+ end
+
+ context 'when pipeline is child' do
+ let(:parent) { create(:ci_pipeline, project: pipeline.project) }
+ let(:sibling) { create(:ci_pipeline, project: pipeline.project) }
+
+ before do
+ create(:ci_sources_pipeline,
+ source_job: create(:ci_build, pipeline: parent),
+ source_project: parent.project,
+ pipeline: pipeline,
+ project: pipeline.project)
+
+ create(:ci_sources_pipeline,
+ source_job: create(:ci_build, pipeline: parent),
+ source_project: parent.project,
+ pipeline: sibling,
+ project: sibling.project)
+ end
+
+ it 'returns parent sibling and self ids' do
+ expect(same_family_pipeline_ids).to contain_exactly(parent.id, pipeline.id, sibling.id)
+ end
+ end
+
+ context 'when pipeline is parent' do
+ let(:child) { create(:ci_pipeline, project: pipeline.project) }
+
+ before do
+ create(:ci_sources_pipeline,
+ source_job: create(:ci_build, pipeline: pipeline),
+ source_project: pipeline.project,
+ pipeline: child,
+ project: child.project)
+ end
+
+ it 'returns self and child ids' do
+ expect(same_family_pipeline_ids).to contain_exactly(pipeline.id, child.id)
+ end
+ end
+ end
+
describe '#stuck?' do
before do
create(:ci_build, :pending, pipeline: pipeline)
@@ -2602,6 +2750,28 @@ describe Ci::Pipeline, :mailer do
end
end
+ describe '#add_error_message' do
+ let(:pipeline) { build_stubbed(:ci_pipeline) }
+
+ it 'adds a new pipeline error message' do
+ pipeline.add_error_message('The error message')
+
+ expect(pipeline.messages.map(&:content)).to contain_exactly('The error message')
+ end
+
+ context 'when feature flag ci_store_pipeline_messages is disabled' do
+ before do
+ stub_feature_flags(ci_store_pipeline_messages: false)
+ end
+
+ it ' does not add pipeline error message' do
+ pipeline.add_error_message('The error message')
+
+ expect(pipeline.messages).to be_empty
+ end
+ end
+ end
+
describe '#has_yaml_errors?' do
context 'when yaml_errors is set' do
before do
@@ -2825,6 +2995,16 @@ describe Ci::Pipeline, :mailer do
end
end
+ describe '#batch_lookup_report_artifact_for_file_type' do
+ context 'with code quality report artifact' do
+ let(:pipeline) { create(:ci_pipeline, :with_codequality_report, project: project) }
+
+ it "returns the code quality artifact" do
+ expect(pipeline.batch_lookup_report_artifact_for_file_type(:codequality)).to eq(pipeline.job_artifacts.sample)
+ end
+ end
+ end
+
describe '#latest_report_builds' do
it 'returns build with test artifacts' do
test_build = create(:ci_build, :test_reports, pipeline: pipeline, project: project)
@@ -2891,6 +3071,39 @@ describe Ci::Pipeline, :mailer do
end
end
+ describe '#test_report_summary' do
+ subject { pipeline.test_report_summary }
+
+ context 'when pipeline has multiple builds with report results' do
+ let(:pipeline) { create(:ci_pipeline, :success, project: project) }
+
+ before do
+ create(:ci_build, :success, :report_results, name: 'rspec', pipeline: pipeline, project: project)
+ create(:ci_build, :success, :report_results, name: 'java', pipeline: pipeline, project: project)
+ end
+
+ it 'returns test report summary with collected data', :aggregate_failures do
+ expect(subject.total_time).to be(0.84)
+ expect(subject.total_count).to be(4)
+ expect(subject.success_count).to be(0)
+ expect(subject.failed_count).to be(0)
+ expect(subject.error_count).to be(4)
+ expect(subject.skipped_count).to be(0)
+ end
+ end
+
+ context 'when pipeline does not have any builds with report results' do
+ it 'returns empty test report sumary', :aggregate_failures do
+ expect(subject.total_time).to be(0)
+ expect(subject.total_count).to be(0)
+ expect(subject.success_count).to be(0)
+ expect(subject.failed_count).to be(0)
+ expect(subject.error_count).to be(0)
+ expect(subject.skipped_count).to be(0)
+ end
+ end
+ end
+
describe '#test_reports' do
subject { pipeline.test_reports }
@@ -3069,6 +3282,32 @@ describe Ci::Pipeline, :mailer do
end
end
end
+
+ context 'when transitioning to success' do
+ context 'when feature is enabled' do
+ before do
+ stub_feature_flags(keep_latest_artifacts_for_ref: true)
+ end
+
+ it 'calls the PipelineSuccessUnlockArtifactsWorker' do
+ expect(Ci::PipelineSuccessUnlockArtifactsWorker).to receive(:perform_async).with(pipeline.id)
+
+ pipeline.succeed!
+ end
+ end
+
+ context 'when feature is disabled' do
+ before do
+ stub_feature_flags(keep_latest_artifacts_for_ref: false)
+ end
+
+ it 'does not call the PipelineSuccessUnlockArtifactsWorker' do
+ expect(Ci::PipelineSuccessUnlockArtifactsWorker).not_to receive(:perform_async)
+
+ pipeline.succeed!
+ end
+ end
+ end
end
describe '#default_branch?' do
@@ -3133,8 +3372,8 @@ describe Ci::Pipeline, :mailer do
end
end
- describe '#error_messages' do
- subject { pipeline.error_messages }
+ describe '#full_error_messages' do
+ subject { pipeline.full_error_messages }
before do
pipeline.valid?
diff --git a/spec/models/ci/pipeline_variable_spec.rb b/spec/models/ci/pipeline_variable_spec.rb
index e8c7ce088e2..04fcaab4c2d 100644
--- a/spec/models/ci/pipeline_variable_spec.rb
+++ b/spec/models/ci/pipeline_variable_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::PipelineVariable do
+RSpec.describe Ci::PipelineVariable do
subject { build(:ci_pipeline_variable) }
it_behaves_like "CI variable"
diff --git a/spec/models/ci/processable_spec.rb b/spec/models/ci/processable_spec.rb
index e67f740279b..35764e2bbbe 100644
--- a/spec/models/ci/processable_spec.rb
+++ b/spec/models/ci/processable_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::Processable do
+RSpec.describe Ci::Processable do
let_it_be(:project) { create(:project) }
let_it_be(:pipeline) { create(:ci_pipeline, project: project) }
diff --git a/spec/models/ci/ref_spec.rb b/spec/models/ci/ref_spec.rb
index 3d75cb63141..fd4742a8ad2 100644
--- a/spec/models/ci/ref_spec.rb
+++ b/spec/models/ci/ref_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::Ref do
+RSpec.describe Ci::Ref do
it { is_expected.to belong_to(:project) }
describe '.ensure_for' do
@@ -62,6 +62,35 @@ describe Ci::Ref do
end
end
+ describe '#last_finished_pipeline_id' do
+ let(:pipeline_status) { :running }
+ let(:config_source) { Ci::PipelineEnums.config_sources[:repository_source] }
+ let(:pipeline) { create(:ci_pipeline, pipeline_status, config_source: config_source) }
+ let(:ci_ref) { pipeline.ci_ref }
+
+ context 'when there are no finished pipelines' do
+ it 'returns nil' do
+ expect(ci_ref.last_finished_pipeline_id).to be_nil
+ end
+ end
+
+ context 'when there are finished pipelines' do
+ let(:pipeline_status) { :success }
+
+ it 'returns the pipeline id' do
+ expect(ci_ref.last_finished_pipeline_id).to eq(pipeline.id)
+ end
+
+ context 'when the pipeline is not a ci_source' do
+ let(:config_source) { Ci::PipelineEnums.config_sources[:parameter_source] }
+
+ it 'returns nil' do
+ expect(ci_ref.last_finished_pipeline_id).to be_nil
+ end
+ end
+ end
+ end
+
describe '#update_status_by!' do
subject { ci_ref.update_status_by!(pipeline) }
diff --git a/spec/models/ci/resource_group_spec.rb b/spec/models/ci/resource_group_spec.rb
index ce8b03282bc..9f72d1a82e5 100644
--- a/spec/models/ci/resource_group_spec.rb
+++ b/spec/models/ci/resource_group_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::ResourceGroup do
+RSpec.describe Ci::ResourceGroup do
describe 'validation' do
it 'valids when key includes allowed character' do
resource_group = build(:ci_resource_group, key: 'test')
diff --git a/spec/models/ci/resource_spec.rb b/spec/models/ci/resource_spec.rb
index 27e512e2c45..90f26ef2b31 100644
--- a/spec/models/ci/resource_spec.rb
+++ b/spec/models/ci/resource_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::Resource do
+RSpec.describe Ci::Resource do
describe '.free' do
subject { described_class.free }
diff --git a/spec/models/ci/runner_spec.rb b/spec/models/ci/runner_spec.rb
index 296240b1602..8247ebf1144 100644
--- a/spec/models/ci/runner_spec.rb
+++ b/spec/models/ci/runner_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::Runner do
+RSpec.describe Ci::Runner do
it_behaves_like 'having unique enum values'
describe 'validation' do
@@ -713,6 +713,46 @@ describe Ci::Runner do
end
end
+ describe '#belongs_to_more_than_one_project?' do
+ context 'project runner' do
+ let(:project1) { create(:project) }
+ let(:project2) { create(:project) }
+
+ context 'two projects assigned to runner' do
+ let(:runner) { create(:ci_runner, :project, projects: [project1, project2]) }
+
+ it 'returns true' do
+ expect(runner.belongs_to_more_than_one_project?).to be_truthy
+ end
+ end
+
+ context 'one project assigned to runner' do
+ let(:runner) { create(:ci_runner, :project, projects: [project1]) }
+
+ it 'returns false' do
+ expect(runner.belongs_to_more_than_one_project?).to be_falsey
+ end
+ end
+ end
+
+ context 'group runner' do
+ let(:group) { create(:group) }
+ let(:runner) { create(:ci_runner, :group, groups: [group]) }
+
+ it 'returns false' do
+ expect(runner.belongs_to_more_than_one_project?).to be_falsey
+ end
+ end
+
+ context 'shared runner' do
+ let(:runner) { create(:ci_runner, :instance) }
+
+ it 'returns false' do
+ expect(runner.belongs_to_more_than_one_project?).to be_falsey
+ end
+ end
+ end
+
describe '#has_tags?' do
context 'when runner has tags' do
subject { create(:ci_runner, tag_list: ['tag']) }
diff --git a/spec/models/ci/sources/pipeline_spec.rb b/spec/models/ci/sources/pipeline_spec.rb
index 5023747b487..ccf3140650b 100644
--- a/spec/models/ci/sources/pipeline_spec.rb
+++ b/spec/models/ci/sources/pipeline_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::Sources::Pipeline do
+RSpec.describe Ci::Sources::Pipeline do
it { is_expected.to belong_to(:project) }
it { is_expected.to belong_to(:pipeline) }
diff --git a/spec/models/ci/stage_spec.rb b/spec/models/ci/stage_spec.rb
index a1549532559..3d873a1b9c1 100644
--- a/spec/models/ci/stage_spec.rb
+++ b/spec/models/ci/stage_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::Stage, :models do
+RSpec.describe Ci::Stage, :models do
let_it_be(:pipeline) { create(:ci_empty_pipeline) }
let(:stage) { create(:ci_stage_entity, pipeline: pipeline, project: pipeline.project) }
@@ -172,7 +172,7 @@ describe Ci::Stage, :models do
it 'raises an exception' do
expect { stage.update_legacy_status }
- .to raise_error(HasStatus::UnknownStatusError)
+ .to raise_error(Ci::HasStatus::UnknownStatusError)
end
end
end
diff --git a/spec/models/ci/trigger_request_spec.rb b/spec/models/ci/trigger_request_spec.rb
index d04349bec92..0d462741089 100644
--- a/spec/models/ci/trigger_request_spec.rb
+++ b/spec/models/ci/trigger_request_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::TriggerRequest do
+RSpec.describe Ci::TriggerRequest do
describe 'validation' do
it 'be invalid if saving a variable' do
trigger = build(:ci_trigger_request, variables: { TRIGGER_KEY_1: 'TRIGGER_VALUE_1' } )
diff --git a/spec/models/ci/trigger_spec.rb b/spec/models/ci/trigger_spec.rb
index 5b0815f8156..4ba6c6e50f7 100644
--- a/spec/models/ci/trigger_spec.rb
+++ b/spec/models/ci/trigger_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::Trigger do
+RSpec.describe Ci::Trigger do
let(:project) { create :project }
describe 'associations' do
diff --git a/spec/models/ci/variable_spec.rb b/spec/models/ci/variable_spec.rb
index 810a0ddfd2e..26a7a2596af 100644
--- a/spec/models/ci/variable_spec.rb
+++ b/spec/models/ci/variable_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::Variable do
+RSpec.describe Ci::Variable do
subject { build(:ci_variable) }
it_behaves_like "CI variable"
diff --git a/spec/models/clusters/applications/cert_manager_spec.rb b/spec/models/clusters/applications/cert_manager_spec.rb
index d7fd0d06b05..7ca7f533a27 100644
--- a/spec/models/clusters/applications/cert_manager_spec.rb
+++ b/spec/models/clusters/applications/cert_manager_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Clusters::Applications::CertManager do
+RSpec.describe Clusters::Applications::CertManager do
let(:cert_manager) { create(:clusters_applications_cert_manager) }
include_examples 'cluster application core specs', :clusters_applications_cert_manager
diff --git a/spec/models/clusters/applications/cilium_spec.rb b/spec/models/clusters/applications/cilium_spec.rb
new file mode 100644
index 00000000000..8b01502d5c0
--- /dev/null
+++ b/spec/models/clusters/applications/cilium_spec.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Clusters::Applications::Cilium do
+ let(:cilium) { create(:clusters_applications_cilium) }
+
+ include_examples 'cluster application core specs', :clusters_applications_cilium
+ include_examples 'cluster application status specs', :clusters_applications_cilium
+ include_examples 'cluster application initial status specs'
+
+ describe '#allowed_to_uninstall?' do
+ subject { cilium.allowed_to_uninstall? }
+
+ it { is_expected.to be false }
+ end
+end
diff --git a/spec/models/clusters/applications/crossplane_spec.rb b/spec/models/clusters/applications/crossplane_spec.rb
index ebc675497f4..a41c5f6586b 100644
--- a/spec/models/clusters/applications/crossplane_spec.rb
+++ b/spec/models/clusters/applications/crossplane_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Clusters::Applications::Crossplane do
+RSpec.describe Clusters::Applications::Crossplane do
let(:crossplane) { create(:clusters_applications_crossplane) }
include_examples 'cluster application core specs', :clusters_applications_crossplane
diff --git a/spec/models/clusters/applications/elastic_stack_spec.rb b/spec/models/clusters/applications/elastic_stack_spec.rb
index 50042a4e29a..62123ffa542 100644
--- a/spec/models/clusters/applications/elastic_stack_spec.rb
+++ b/spec/models/clusters/applications/elastic_stack_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Clusters::Applications::ElasticStack do
+RSpec.describe Clusters::Applications::ElasticStack do
include KubernetesHelpers
include_examples 'cluster application core specs', :clusters_applications_elastic_stack
@@ -27,6 +27,20 @@ describe Clusters::Applications::ElasticStack do
expect(subject.preinstall).to be_empty
end
+ context 'within values.yaml' do
+ let(:values_yaml_content) {subject.files[:"values.yaml"]}
+
+ it 'contains the disabled index lifecycle management' do
+ expect(values_yaml_content).to include "setup.ilm.enabled: false"
+ end
+
+ it 'contains daily indices with respective template' do
+ expect(values_yaml_content).to include "index: \"filebeat-%{[agent.version]}-%{+yyyy.MM.dd}\""
+ expect(values_yaml_content).to include "setup.template.name: 'filebeat'"
+ expect(values_yaml_content).to include "setup.template.pattern: 'filebeat-*'"
+ end
+ end
+
context 'on a non rbac enabled cluster' do
before do
elastic_stack.cluster.platform_kubernetes.abac!
diff --git a/spec/models/clusters/applications/fluentd_spec.rb b/spec/models/clusters/applications/fluentd_spec.rb
index 4e9548990ed..be7b4a87947 100644
--- a/spec/models/clusters/applications/fluentd_spec.rb
+++ b/spec/models/clusters/applications/fluentd_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Clusters::Applications::Fluentd do
+RSpec.describe Clusters::Applications::Fluentd do
let(:waf_log_enabled) { true }
let(:cilium_log_enabled) { true }
let(:fluentd) { create(:clusters_applications_fluentd, waf_log_enabled: waf_log_enabled, cilium_log_enabled: cilium_log_enabled) }
diff --git a/spec/models/clusters/applications/helm_spec.rb b/spec/models/clusters/applications/helm_spec.rb
index 87454e1d3e2..6d2ecaa6d47 100644
--- a/spec/models/clusters/applications/helm_spec.rb
+++ b/spec/models/clusters/applications/helm_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Clusters::Applications::Helm do
+RSpec.describe Clusters::Applications::Helm do
include_examples 'cluster application core specs', :clusters_applications_helm
describe '.available' do
diff --git a/spec/models/clusters/applications/ingress_spec.rb b/spec/models/clusters/applications/ingress_spec.rb
index 8aee4eec0d3..d1138f5fa2d 100644
--- a/spec/models/clusters/applications/ingress_spec.rb
+++ b/spec/models/clusters/applications/ingress_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Clusters::Applications::Ingress do
+RSpec.describe Clusters::Applications::Ingress do
let(:ingress) { create(:clusters_applications_ingress) }
it_behaves_like 'having unique enum values'
diff --git a/spec/models/clusters/applications/jupyter_spec.rb b/spec/models/clusters/applications/jupyter_spec.rb
index 937db9217f3..3cf24f1a9ef 100644
--- a/spec/models/clusters/applications/jupyter_spec.rb
+++ b/spec/models/clusters/applications/jupyter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Clusters::Applications::Jupyter do
+RSpec.describe Clusters::Applications::Jupyter do
include_examples 'cluster application core specs', :clusters_applications_jupyter
include_examples 'cluster application status specs', :clusters_applications_jupyter
include_examples 'cluster application version specs', :clusters_applications_jupyter
diff --git a/spec/models/clusters/applications/knative_spec.rb b/spec/models/clusters/applications/knative_spec.rb
index 7ff7644e703..b14161ce8e6 100644
--- a/spec/models/clusters/applications/knative_spec.rb
+++ b/spec/models/clusters/applications/knative_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Clusters::Applications::Knative do
+RSpec.describe Clusters::Applications::Knative do
let(:knative) { create(:clusters_applications_knative) }
include_examples 'cluster application core specs', :clusters_applications_knative
diff --git a/spec/models/clusters/applications/prometheus_spec.rb b/spec/models/clusters/applications/prometheus_spec.rb
index 1ed9e207b6b..1215b38a9a2 100644
--- a/spec/models/clusters/applications/prometheus_spec.rb
+++ b/spec/models/clusters/applications/prometheus_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Clusters::Applications::Prometheus do
+RSpec.describe Clusters::Applications::Prometheus do
include KubernetesHelpers
include StubRequests
diff --git a/spec/models/clusters/applications/runner_spec.rb b/spec/models/clusters/applications/runner_spec.rb
index 6ee6711ec4b..fbabfd25b2f 100644
--- a/spec/models/clusters/applications/runner_spec.rb
+++ b/spec/models/clusters/applications/runner_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Clusters::Applications::Runner do
+RSpec.describe Clusters::Applications::Runner do
let(:ci_runner) { create(:ci_runner) }
include_examples 'cluster application core specs', :clusters_applications_runner
diff --git a/spec/models/clusters/cluster_spec.rb b/spec/models/clusters/cluster_spec.rb
index 4dd74976028..4807957152c 100644
--- a/spec/models/clusters/cluster_spec.rb
+++ b/spec/models/clusters/cluster_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Clusters::Cluster, :use_clean_rails_memory_store_caching do
+RSpec.describe Clusters::Cluster, :use_clean_rails_memory_store_caching do
include ReactiveCachingHelpers
include KubernetesHelpers
@@ -10,6 +10,7 @@ describe Clusters::Cluster, :use_clean_rails_memory_store_caching do
subject { build(:cluster) }
+ it { is_expected.to include_module(HasEnvironmentScope) }
it { is_expected.to belong_to(:user) }
it { is_expected.to belong_to(:management_project).class_name('::Project') }
it { is_expected.to have_many(:cluster_projects) }
@@ -289,6 +290,79 @@ describe Clusters::Cluster, :use_clean_rails_memory_store_caching do
describe 'validations' do
subject { cluster.valid? }
+ context 'when validates unique_environment_scope' do
+ context 'for a project cluster' do
+ let(:project) { create(:project) }
+
+ before do
+ create(:cluster, projects: [project], environment_scope: 'product/*')
+ end
+
+ context 'when identical environment scope exists in project' do
+ let(:cluster) { build(:cluster, projects: [project], environment_scope: 'product/*') }
+
+ it { is_expected.to be_falsey }
+ end
+
+ context 'when identical environment scope does not exist in project' do
+ let(:cluster) { build(:cluster, projects: [project], environment_scope: '*') }
+
+ it { is_expected.to be_truthy }
+ end
+
+ context 'when identical environment scope exists in different project' do
+ let(:project2) { create(:project) }
+ let(:cluster) { build(:cluster, projects: [project2], environment_scope: 'product/*') }
+
+ it { is_expected.to be_truthy }
+ end
+ end
+
+ context 'for a group cluster' do
+ let(:group) { create(:group) }
+
+ before do
+ create(:cluster, cluster_type: :group_type, groups: [group], environment_scope: 'product/*')
+ end
+
+ context 'when identical environment scope exists in group' do
+ let(:cluster) { build(:cluster, cluster_type: :group_type, groups: [group], environment_scope: 'product/*') }
+
+ it { is_expected.to be_falsey }
+ end
+
+ context 'when identical environment scope does not exist in group' do
+ let(:cluster) { build(:cluster, cluster_type: :group_type, groups: [group], environment_scope: '*') }
+
+ it { is_expected.to be_truthy }
+ end
+
+ context 'when identical environment scope exists in different group' do
+ let(:cluster) { build(:cluster, :group, environment_scope: 'product/*') }
+
+ it { is_expected.to be_truthy }
+ end
+ end
+
+ context 'for an instance cluster' do
+ before do
+ create(:cluster, :instance, environment_scope: 'product/*')
+ end
+
+ context 'identical environment scope exists' do
+ let(:cluster) { build(:cluster, :instance, environment_scope: 'product/*') }
+
+ it { is_expected.to be_falsey }
+ end
+
+ context 'identical environment scope does not exist' do
+ let(:cluster) { build(:cluster, :instance, environment_scope: '*') }
+
+ it { is_expected.to be_truthy }
+ end
+ end
+ end
+
context 'when validates name' do
context 'when provided by user' do
let!(:cluster) { build(:cluster, :provided_by_user, name: name) }
@@ -1111,13 +1185,23 @@ describe Clusters::Cluster, :use_clean_rails_memory_store_caching do
context 'cluster is enabled' do
let(:cluster) { create(:cluster, :provided_by_user, :group) }
+ let(:gl_k8s_node_double) { double(Gitlab::Kubernetes::Node) }
+ let(:expected_nodes) { nil }
before do
- stub_kubeclient_nodes_and_nodes_metrics(cluster.platform.api_url)
+ stub_kubeclient_discover(cluster.platform.api_url)
+ allow(Gitlab::Kubernetes::Node).to receive(:new).with(cluster).and_return(gl_k8s_node_double)
+ allow(gl_k8s_node_double).to receive(:all).and_return([])
end
context 'connection to the cluster is successful' do
- it { is_expected.to eq(connection_status: :connected, nodes: [kube_node.merge(kube_node_metrics)]) }
+ before do
+ allow(gl_k8s_node_double).to receive(:all).and_return(expected_nodes)
+ end
+
+ let(:expected_nodes) { [kube_node.merge(kube_node_metrics)] }
+
+ it { is_expected.to eq(connection_status: :connected, nodes: expected_nodes) }
end
context 'cluster cannot be reached' do
@@ -1126,7 +1210,7 @@ describe Clusters::Cluster, :use_clean_rails_memory_store_caching do
.and_raise(SocketError)
end
- it { is_expected.to eq(connection_status: :unreachable, nodes: nil) }
+ it { is_expected.to eq(connection_status: :unreachable, nodes: expected_nodes) }
end
context 'cluster cannot be authenticated to' do
@@ -1135,7 +1219,7 @@ describe Clusters::Cluster, :use_clean_rails_memory_store_caching do
.and_raise(OpenSSL::X509::CertificateError.new("Certificate error"))
end
- it { is_expected.to eq(connection_status: :authentication_failure, nodes: nil) }
+ it { is_expected.to eq(connection_status: :authentication_failure, nodes: expected_nodes) }
end
describe 'Kubeclient::HttpError' do
@@ -1147,18 +1231,18 @@ describe Clusters::Cluster, :use_clean_rails_memory_store_caching do
.and_raise(Kubeclient::HttpError.new(error_code, error_message, nil))
end
- it { is_expected.to eq(connection_status: :authentication_failure, nodes: nil) }
+ it { is_expected.to eq(connection_status: :authentication_failure, nodes: expected_nodes) }
context 'generic timeout' do
let(:error_message) { 'Timed out connecting to server'}
- it { is_expected.to eq(connection_status: :unreachable, nodes: nil) }
+ it { is_expected.to eq(connection_status: :unreachable, nodes: expected_nodes) }
end
context 'gateway timeout' do
let(:error_message) { '504 Gateway Timeout for GET https://kubernetes.example.com/api/v1'}
- it { is_expected.to eq(connection_status: :unreachable, nodes: nil) }
+ it { is_expected.to eq(connection_status: :unreachable, nodes: expected_nodes) }
end
end
@@ -1168,12 +1252,12 @@ describe Clusters::Cluster, :use_clean_rails_memory_store_caching do
.and_raise(StandardError)
end
- it { is_expected.to eq(connection_status: :unknown_failure, nodes: nil) }
+ it { is_expected.to eq(connection_status: :unknown_failure, nodes: expected_nodes) }
it 'notifies Sentry' do
expect(Gitlab::ErrorTracking).to receive(:track_exception)
.with(instance_of(StandardError), hash_including(cluster_id: cluster.id))
- .twice
+ .once
subject
end
diff --git a/spec/models/clusters/clusters_hierarchy_spec.rb b/spec/models/clusters/clusters_hierarchy_spec.rb
index 1957e1fc5ee..5ac561eb2d0 100644
--- a/spec/models/clusters/clusters_hierarchy_spec.rb
+++ b/spec/models/clusters/clusters_hierarchy_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Clusters::ClustersHierarchy do
+RSpec.describe Clusters::ClustersHierarchy do
describe '#base_and_ancestors' do
def base_and_ancestors(clusterable, include_management_project: true)
described_class.new(clusterable, include_management_project: include_management_project).base_and_ancestors
diff --git a/spec/models/clusters/group_spec.rb b/spec/models/clusters/group_spec.rb
index ba145342cb8..3b541c40938 100644
--- a/spec/models/clusters/group_spec.rb
+++ b/spec/models/clusters/group_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Clusters::Group do
+RSpec.describe Clusters::Group do
it { is_expected.to belong_to(:cluster) }
it { is_expected.to belong_to(:group) }
end
diff --git a/spec/models/clusters/platforms/kubernetes_spec.rb b/spec/models/clusters/platforms/kubernetes_spec.rb
index f0e6dd53664..adccc72d13d 100644
--- a/spec/models/clusters/platforms/kubernetes_spec.rb
+++ b/spec/models/clusters/platforms/kubernetes_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Clusters::Platforms::Kubernetes do
+RSpec.describe Clusters::Platforms::Kubernetes do
include KubernetesHelpers
it { is_expected.to belong_to(:cluster) }
@@ -204,6 +204,52 @@ describe Clusters::Platforms::Kubernetes do
end
it { is_expected.to be_an_instance_of(Gitlab::Kubernetes::KubeClient) }
+
+ context 'ca_pem is a single certificate' do
+ let(:ca_pem) { File.read(Rails.root.join('spec/fixtures/clusters/ca_certificate.pem')) }
+ let(:kubernetes) do
+ build(:cluster_platform_kubernetes,
+ :configured,
+ namespace: 'a-namespace',
+ cluster: cluster,
+ ca_pem: ca_pem)
+ end
+
+ it 'adds it to cert_store' do
+ cert = OpenSSL::X509::Certificate.new(ca_pem)
+ cert_store = kubernetes.kubeclient.kubeclient_options[:ssl_options][:cert_store]
+
+ expect(cert_store.verify(cert)).to be true
+ end
+ end
+
+ context 'ca_pem is a chain' do
+ let(:cert_chain) { File.read(Rails.root.join('spec/fixtures/clusters/chain_certificates.pem')) }
+ let(:kubernetes) do
+ build(:cluster_platform_kubernetes,
+ :configured,
+ namespace: 'a-namespace',
+ cluster: cluster,
+ ca_pem: cert_chain)
+ end
+
+ it 'includes chain of certificates' do
+ cert1_file = File.read(Rails.root.join('spec/fixtures/clusters/root_certificate.pem'))
+ cert1 = OpenSSL::X509::Certificate.new(cert1_file)
+
+ cert2_file = File.read(Rails.root.join('spec/fixtures/clusters/intermediate_certificate.pem'))
+ cert2 = OpenSSL::X509::Certificate.new(cert2_file)
+
+ cert3_file = File.read(Rails.root.join('spec/fixtures/clusters/ca_certificate.pem'))
+ cert3 = OpenSSL::X509::Certificate.new(cert3_file)
+
+ cert_store = kubernetes.kubeclient.kubeclient_options[:ssl_options][:cert_store]
+
+ expect(cert_store.verify(cert1)).to be true
+ expect(cert_store.verify(cert2)).to be true
+ expect(cert_store.verify(cert3)).to be true
+ end
+ end
end
describe '#rbac?' do
diff --git a/spec/models/clusters/project_spec.rb b/spec/models/clusters/project_spec.rb
index 671af085d10..e16dfa47898 100644
--- a/spec/models/clusters/project_spec.rb
+++ b/spec/models/clusters/project_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Clusters::Project do
+RSpec.describe Clusters::Project do
it { is_expected.to belong_to(:cluster) }
it { is_expected.to belong_to(:project) }
it { is_expected.to have_many(:kubernetes_namespaces) }
diff --git a/spec/models/clusters/providers/aws_spec.rb b/spec/models/clusters/providers/aws_spec.rb
index 05d6e63288e..3b4a48cc5be 100644
--- a/spec/models/clusters/providers/aws_spec.rb
+++ b/spec/models/clusters/providers/aws_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Clusters::Providers::Aws do
+RSpec.describe Clusters::Providers::Aws do
it { is_expected.to belong_to(:cluster) }
it { is_expected.to validate_length_of(:key_name).is_at_least(1).is_at_most(255) }
diff --git a/spec/models/clusters/providers/gcp_spec.rb b/spec/models/clusters/providers/gcp_spec.rb
index e2fd777d131..ad9ada04875 100644
--- a/spec/models/clusters/providers/gcp_spec.rb
+++ b/spec/models/clusters/providers/gcp_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Clusters::Providers::Gcp do
+RSpec.describe Clusters::Providers::Gcp do
it { is_expected.to belong_to(:cluster) }
it { is_expected.to validate_presence_of(:zone) }
diff --git a/spec/models/commit_collection_spec.rb b/spec/models/commit_collection_spec.rb
index d49b71db5f8..f4e86f3292b 100644
--- a/spec/models/commit_collection_spec.rb
+++ b/spec/models/commit_collection_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe CommitCollection do
+RSpec.describe CommitCollection do
let(:project) { create(:project, :repository) }
let(:commit) { project.commit("c1c67abbaf91f624347bb3ae96eabe3a1b742478") }
@@ -75,6 +75,18 @@ describe CommitCollection do
end
end
+ describe '#with_markdown_cache' do
+ let(:commits) { [commit] }
+ let(:collection) { described_class.new(project, commits) }
+
+ it 'preloads commits cache markdown' do
+ aggregate_failures do
+ expect(Commit).to receive(:preload_markdown_cache!).with(commits)
+ expect(collection.with_markdown_cache).to eq(collection)
+ end
+ end
+ end
+
describe 'enrichment methods' do
let(:gitaly_commit) { commit }
let(:hash_commit) { Commit.from_hash(gitaly_commit.to_hash, project) }
diff --git a/spec/models/commit_range_spec.rb b/spec/models/commit_range_spec.rb
index 245e47fa17b..3fb8708c884 100644
--- a/spec/models/commit_range_spec.rb
+++ b/spec/models/commit_range_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe CommitRange do
+RSpec.describe CommitRange do
describe 'modules' do
subject { described_class }
diff --git a/spec/models/commit_spec.rb b/spec/models/commit_spec.rb
index ddda04faaf1..cfa87b3e39e 100644
--- a/spec/models/commit_spec.rb
+++ b/spec/models/commit_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Commit do
+RSpec.describe Commit do
let_it_be(:project) { create(:project, :public, :repository) }
let_it_be(:personal_snippet) { create(:personal_snippet, :repository) }
let_it_be(:project_snippet) { create(:project_snippet, :repository) }
@@ -675,7 +675,10 @@ eos
end
describe '#work_in_progress?' do
- ['squash! ', 'fixup! ', 'wip: ', 'WIP: ', '[WIP] '].each do |wip_prefix|
+ [
+ 'squash! ', 'fixup! ', 'wip: ', 'WIP: ', '[WIP] ',
+ 'draft: ', 'Draft - ', '[Draft] ', '(draft) ', 'Draft: '
+ ].each do |wip_prefix|
it "detects the '#{wip_prefix}' prefix" do
commit.message = "#{wip_prefix}#{commit.message}"
@@ -689,6 +692,12 @@ eos
expect(commit).to be_work_in_progress
end
+ it "detects WIP for a commit just saying 'draft'" do
+ commit.message = "draft"
+
+ expect(commit).to be_work_in_progress
+ end
+
it "doesn't detect WIP for a commit that begins with 'FIXUP! '" do
commit.message = "FIXUP! #{commit.message}"
diff --git a/spec/models/commit_status_spec.rb b/spec/models/commit_status_spec.rb
index 85fc503a1ca..cd0110a787b 100644
--- a/spec/models/commit_status_spec.rb
+++ b/spec/models/commit_status_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe CommitStatus do
+RSpec.describe CommitStatus do
let_it_be(:project) { create(:project, :repository) }
let_it_be(:pipeline) do
diff --git a/spec/models/commit_with_pipeline_spec.rb b/spec/models/commit_with_pipeline_spec.rb
index e0bb29fec7b..ff451527929 100644
--- a/spec/models/commit_with_pipeline_spec.rb
+++ b/spec/models/commit_with_pipeline_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe CommitWithPipeline do
+RSpec.describe CommitWithPipeline do
let(:project) { create(:project, :public, :repository) }
let(:commit) { described_class.new(project.commit) }
diff --git a/spec/models/compare_spec.rb b/spec/models/compare_spec.rb
index 43c3580bed2..d395aa359e5 100644
--- a/spec/models/compare_spec.rb
+++ b/spec/models/compare_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Compare do
+RSpec.describe Compare do
include RepoHelpers
let(:project) { create(:project, :public, :repository) }
diff --git a/spec/models/concerns/access_requestable_spec.rb b/spec/models/concerns/access_requestable_spec.rb
index 5c1694e3737..24eb3e8a1e6 100644
--- a/spec/models/concerns/access_requestable_spec.rb
+++ b/spec/models/concerns/access_requestable_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe AccessRequestable do
+RSpec.describe AccessRequestable do
describe 'Group' do
describe '#request_access' do
let(:group) { create(:group, :public) }
diff --git a/spec/models/concerns/approvable_base_spec.rb b/spec/models/concerns/approvable_base_spec.rb
new file mode 100644
index 00000000000..8fda8bccf09
--- /dev/null
+++ b/spec/models/concerns/approvable_base_spec.rb
@@ -0,0 +1,34 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe ApprovableBase do
+ describe '#approved_by?' do
+ let(:merge_request) { create(:merge_request) }
+ let(:user) { create(:user) }
+
+ subject { merge_request.approved_by?(user) }
+
+ context 'when a user has not approved' do
+ it 'returns false' do
+ is_expected.to be_falsy
+ end
+ end
+
+ context 'when a user has approved' do
+ let!(:approval) { create(:approval, merge_request: merge_request, user: user) }
+
+ it 'returns false' do
+ is_expected.to be_truthy
+ end
+ end
+
+ context 'when a user is nil' do
+ let(:user) { nil }
+
+ it 'returns false' do
+ is_expected.to be_falsy
+ end
+ end
+ end
+end
diff --git a/spec/models/concerns/atomic_internal_id_spec.rb b/spec/models/concerns/atomic_internal_id_spec.rb
index 93bf7ec10dd..8c3537f1dcc 100644
--- a/spec/models/concerns/atomic_internal_id_spec.rb
+++ b/spec/models/concerns/atomic_internal_id_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe AtomicInternalId do
+RSpec.describe AtomicInternalId do
let(:milestone) { build(:milestone) }
let(:iid) { double('iid', to_i: 42) }
let(:external_iid) { 100 }
diff --git a/spec/models/concerns/avatarable_spec.rb b/spec/models/concerns/avatarable_spec.rb
index 96e867dbc97..8a8eeea39dc 100644
--- a/spec/models/concerns/avatarable_spec.rb
+++ b/spec/models/concerns/avatarable_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Avatarable do
+RSpec.describe Avatarable do
let(:project) { create(:project, :with_avatar) }
let(:gitlab_host) { "https://gitlab.example.com" }
diff --git a/spec/models/concerns/awardable_spec.rb b/spec/models/concerns/awardable_spec.rb
index 29f911fcb04..b5b3772ecb6 100644
--- a/spec/models/concerns/awardable_spec.rb
+++ b/spec/models/concerns/awardable_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Awardable do
+RSpec.describe Awardable do
let!(:issue) { create(:issue) }
let!(:award_emoji) { create(:award_emoji, :downvote, awardable: issue) }
diff --git a/spec/models/concerns/batch_destroy_dependent_associations_spec.rb b/spec/models/concerns/batch_destroy_dependent_associations_spec.rb
index d2373926802..a8fcb714c64 100644
--- a/spec/models/concerns/batch_destroy_dependent_associations_spec.rb
+++ b/spec/models/concerns/batch_destroy_dependent_associations_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe BatchDestroyDependentAssociations do
+RSpec.describe BatchDestroyDependentAssociations do
class TestProject < ActiveRecord::Base
self.table_name = 'projects'
diff --git a/spec/models/concerns/blob_language_from_git_attributes_spec.rb b/spec/models/concerns/blob_language_from_git_attributes_spec.rb
index 4cb8f042b1d..c07ee15e841 100644
--- a/spec/models/concerns/blob_language_from_git_attributes_spec.rb
+++ b/spec/models/concerns/blob_language_from_git_attributes_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe BlobLanguageFromGitAttributes do
+RSpec.describe BlobLanguageFromGitAttributes do
include FakeBlobHelpers
let(:project) { build(:project, :repository) }
diff --git a/spec/models/concerns/blocks_json_serialization_spec.rb b/spec/models/concerns/blocks_json_serialization_spec.rb
index 32870461019..d811b47fa35 100644
--- a/spec/models/concerns/blocks_json_serialization_spec.rb
+++ b/spec/models/concerns/blocks_json_serialization_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe BlocksJsonSerialization do
+RSpec.describe BlocksJsonSerialization do
before do
stub_const('DummyModel', Class.new)
DummyModel.class_eval do
diff --git a/spec/models/concerns/bulk_insert_safe_spec.rb b/spec/models/concerns/bulk_insert_safe_spec.rb
index 07d6cee487f..82b0c00b396 100644
--- a/spec/models/concerns/bulk_insert_safe_spec.rb
+++ b/spec/models/concerns/bulk_insert_safe_spec.rb
@@ -2,57 +2,7 @@
require 'spec_helper'
-describe BulkInsertSafe do
- class BulkInsertItem < ActiveRecord::Base
- include BulkInsertSafe
- include ShaAttribute
-
- validates :name, :enum_value, :secret_value, :sha_value, :jsonb_value, presence: true
-
- ENUM_VALUES = {
- case_1: 1
- }.freeze
-
- sha_attribute :sha_value
-
- enum enum_value: ENUM_VALUES
-
- attr_encrypted :secret_value,
- mode: :per_attribute_iv,
- algorithm: 'aes-256-gcm',
- key: Settings.attr_encrypted_db_key_base_32,
- insecure_mode: false
-
- default_value_for :enum_value, 'case_1'
- default_value_for :secret_value, 'my-secret'
- default_value_for :sha_value, '2fd4e1c67a2d28fced849ee1bb76e7391b93eb12'
- default_value_for :jsonb_value, { "key" => "value" }
-
- def self.valid_list(count)
- Array.new(count) { |n| new(name: "item-#{n}") }
- end
-
- def self.invalid_list(count)
- Array.new(count) { new }
- end
- end
-
- module InheritedUnsafeMethods
- extend ActiveSupport::Concern
-
- included do
- after_save -> { "unsafe" }
- end
- end
-
- module InheritedSafeMethods
- extend ActiveSupport::Concern
-
- included do
- after_initialize -> { "safe" }
- end
- end
-
+RSpec.describe BulkInsertSafe do
before(:all) do
ActiveRecord::Schema.define do
create_table :bulk_insert_items, force: true do |t|
@@ -66,100 +16,155 @@ describe BulkInsertSafe do
t.index :name, unique: true
end
end
-
- BulkInsertItem.reset_column_information
end
after(:all) do
ActiveRecord::Schema.define do
drop_table :bulk_insert_items, force: true
end
+ end
+
+ let_it_be(:bulk_insert_item_class) do
+ Class.new(ActiveRecord::Base) do
+ self.table_name = 'bulk_insert_items'
- BulkInsertItem.reset_column_information
+ include BulkInsertSafe
+ include ShaAttribute
+
+ validates :name, :enum_value, :secret_value, :sha_value, :jsonb_value, presence: true
+
+ sha_attribute :sha_value
+
+ enum enum_value: { case_1: 1 }
+
+ attr_encrypted :secret_value,
+ mode: :per_attribute_iv,
+ algorithm: 'aes-256-gcm',
+ key: Settings.attr_encrypted_db_key_base_32,
+ insecure_mode: false
+
+ default_value_for :enum_value, 'case_1'
+ default_value_for :secret_value, 'my-secret'
+ default_value_for :sha_value, '2fd4e1c67a2d28fced849ee1bb76e7391b93eb12'
+ default_value_for :jsonb_value, { "key" => "value" }
+
+ def self.name
+ 'BulkInsertItem'
+ end
+
+ def self.valid_list(count)
+ Array.new(count) { |n| new(name: "item-#{n}") }
+ end
+
+ def self.invalid_list(count)
+ Array.new(count) { new }
+ end
+ end
end
- describe BulkInsertItem do
- it_behaves_like 'a BulkInsertSafe model', described_class do
- let(:valid_items_for_bulk_insertion) { described_class.valid_list(10) }
- let(:invalid_items_for_bulk_insertion) { described_class.invalid_list(10) }
+ describe 'BulkInsertItem' do
+ it_behaves_like 'a BulkInsertSafe model' do
+ let(:target_class) { bulk_insert_item_class.dup }
+ let(:valid_items_for_bulk_insertion) { target_class.valid_list(10) }
+ let(:invalid_items_for_bulk_insertion) { target_class.invalid_list(10) }
end
context 'when inheriting class methods' do
+ let(:inherited_unsafe_methods_module) do
+ Module.new do
+ extend ActiveSupport::Concern
+
+ included do
+ after_save -> { "unsafe" }
+ end
+ end
+ end
+
+ let(:inherited_safe_methods_module) do
+ Module.new do
+ extend ActiveSupport::Concern
+
+ included do
+ after_initialize -> { "safe" }
+ end
+ end
+ end
+
it 'raises an error when method is not bulk-insert safe' do
- expect { described_class.include(InheritedUnsafeMethods) }
- .to raise_error(described_class::MethodNotAllowedError)
+ expect { bulk_insert_item_class.include(inherited_unsafe_methods_module) }
+ .to raise_error(bulk_insert_item_class::MethodNotAllowedError)
end
it 'does not raise an error when method is bulk-insert safe' do
- expect { described_class.include(InheritedSafeMethods) }.not_to raise_error
+ expect { bulk_insert_item_class.include(inherited_safe_methods_module) }.not_to raise_error
end
end
context 'primary keys' do
it 'raises error if primary keys are set prior to insertion' do
- item = described_class.new(name: 'valid', id: 10)
+ item = bulk_insert_item_class.new(name: 'valid', id: 10)
- expect { described_class.bulk_insert!([item]) }
- .to raise_error(described_class::PrimaryKeySetError)
+ expect { bulk_insert_item_class.bulk_insert!([item]) }
+ .to raise_error(bulk_insert_item_class::PrimaryKeySetError)
end
end
describe '.bulk_insert!' do
it 'inserts items in the given number of batches' do
- items = described_class.valid_list(10)
+ items = bulk_insert_item_class.valid_list(10)
expect(ActiveRecord::InsertAll).to receive(:new).twice.and_call_original
- described_class.bulk_insert!(items, batch_size: 5)
+ bulk_insert_item_class.bulk_insert!(items, batch_size: 5)
end
it 'items can be properly fetched from database' do
- items = described_class.valid_list(10)
+ items = bulk_insert_item_class.valid_list(10)
- described_class.bulk_insert!(items)
+ bulk_insert_item_class.bulk_insert!(items)
- attribute_names = described_class.attribute_names - %w[id created_at updated_at]
- expect(described_class.last(items.size).pluck(*attribute_names)).to eq(
+ attribute_names = bulk_insert_item_class.attribute_names - %w[id created_at updated_at]
+ expect(bulk_insert_item_class.last(items.size).pluck(*attribute_names)).to eq(
items.pluck(*attribute_names))
end
it 'rolls back the transaction when any item is invalid' do
# second batch is bad
- all_items = described_class.valid_list(10) +
- described_class.invalid_list(10)
+ all_items = bulk_insert_item_class.valid_list(10) +
+ bulk_insert_item_class.invalid_list(10)
expect do
- described_class.bulk_insert!(all_items, batch_size: 2) rescue nil
- end.not_to change { described_class.count }
+ bulk_insert_item_class.bulk_insert!(all_items, batch_size: 2) rescue nil
+ end.not_to change { bulk_insert_item_class.count }
end
it 'does nothing and returns an empty array when items are empty' do
- expect(described_class.bulk_insert!([])).to eq([])
- expect(described_class.count).to eq(0)
+ expect(bulk_insert_item_class.bulk_insert!([])).to eq([])
+ expect(bulk_insert_item_class.count).to eq(0)
end
context 'with returns option set' do
context 'when is set to :ids' do
it 'return an array with the primary key values for all inserted records' do
- items = described_class.valid_list(1)
+ items = bulk_insert_item_class.valid_list(1)
- expect(described_class.bulk_insert!(items, returns: :ids)).to contain_exactly(a_kind_of(Integer))
+ expect(bulk_insert_item_class.bulk_insert!(items, returns: :ids)).to contain_exactly(a_kind_of(Integer))
end
end
context 'when is set to nil' do
it 'returns an empty array' do
- items = described_class.valid_list(1)
+ items = bulk_insert_item_class.valid_list(1)
- expect(described_class.bulk_insert!(items, returns: nil)).to eq([])
+ expect(bulk_insert_item_class.bulk_insert!(items, returns: nil)).to eq([])
end
end
context 'when is set to anything else' do
it 'raises an error' do
- items = described_class.valid_list(1)
+ items = bulk_insert_item_class.valid_list(1)
- expect { described_class.bulk_insert!([items], returns: [:id, :name]) }
+ expect { bulk_insert_item_class.bulk_insert!([items], returns: [:id, :name]) }
.to raise_error(ArgumentError, "returns needs to be :ids or nil")
end
end
@@ -167,20 +172,20 @@ describe BulkInsertSafe do
end
context 'when duplicate items are to be inserted' do
- let!(:existing_object) { described_class.create!(name: 'duplicate', secret_value: 'old value') }
- let(:new_object) { described_class.new(name: 'duplicate', secret_value: 'new value') }
+ let!(:existing_object) { bulk_insert_item_class.create!(name: 'duplicate', secret_value: 'old value') }
+ let(:new_object) { bulk_insert_item_class.new(name: 'duplicate', secret_value: 'new value') }
describe '.bulk_insert!' do
context 'when skip_duplicates is set to false' do
it 'raises an exception' do
- expect { described_class.bulk_insert!([new_object], skip_duplicates: false) }
+ expect { bulk_insert_item_class.bulk_insert!([new_object], skip_duplicates: false) }
.to raise_error(ActiveRecord::RecordNotUnique)
end
end
context 'when skip_duplicates is set to true' do
it 'does not update existing object' do
- described_class.bulk_insert!([new_object], skip_duplicates: true)
+ bulk_insert_item_class.bulk_insert!([new_object], skip_duplicates: true)
expect(existing_object.reload.secret_value).to eq('old value')
end
@@ -189,7 +194,7 @@ describe BulkInsertSafe do
describe '.bulk_upsert!' do
it 'updates existing object' do
- described_class.bulk_upsert!([new_object], unique_by: %w[name])
+ bulk_insert_item_class.bulk_upsert!([new_object], unique_by: %w[name])
expect(existing_object.reload.secret_value).to eq('new value')
end
diff --git a/spec/models/concerns/bulk_insertable_associations_spec.rb b/spec/models/concerns/bulk_insertable_associations_spec.rb
index 6359b2c57ef..5a40639e493 100644
--- a/spec/models/concerns/bulk_insertable_associations_spec.rb
+++ b/spec/models/concerns/bulk_insertable_associations_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe BulkInsertableAssociations do
+RSpec.describe BulkInsertableAssociations do
class BulkFoo < ApplicationRecord
include BulkInsertSafe
diff --git a/spec/models/concerns/cache_markdown_field_spec.rb b/spec/models/concerns/cache_markdown_field_spec.rb
index c46ebcf324c..5f8c65e429e 100644
--- a/spec/models/concerns/cache_markdown_field_spec.rb
+++ b/spec/models/concerns/cache_markdown_field_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe CacheMarkdownField, :clean_gitlab_redis_cache do
+RSpec.describe CacheMarkdownField, :clean_gitlab_redis_cache do
let(:ar_class) do
Class.new(ActiveRecord::Base) do
self.table_name = 'issues'
diff --git a/spec/models/concerns/cacheable_attributes_spec.rb b/spec/models/concerns/cacheable_attributes_spec.rb
index 6694b2aba22..f2877bed9cf 100644
--- a/spec/models/concerns/cacheable_attributes_spec.rb
+++ b/spec/models/concerns/cacheable_attributes_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe CacheableAttributes do
+RSpec.describe CacheableAttributes do
let(:minimal_test_class) do
Class.new do
include ActiveModel::Model
diff --git a/spec/models/concerns/case_sensitivity_spec.rb b/spec/models/concerns/case_sensitivity_spec.rb
index 9819f656f0d..521b47c63fd 100644
--- a/spec/models/concerns/case_sensitivity_spec.rb
+++ b/spec/models/concerns/case_sensitivity_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe CaseSensitivity do
+RSpec.describe CaseSensitivity do
describe '.iwhere' do
let(:connection) { ActiveRecord::Base.connection }
let(:model) do
diff --git a/spec/models/concerns/checksummable_spec.rb b/spec/models/concerns/checksummable_spec.rb
index 017077bd297..b469b2e5c18 100644
--- a/spec/models/concerns/checksummable_spec.rb
+++ b/spec/models/concerns/checksummable_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Checksummable do
+RSpec.describe Checksummable do
describe ".hexdigest" do
let(:fake_class) do
Class.new do
diff --git a/spec/models/concerns/chronic_duration_attribute_spec.rb b/spec/models/concerns/chronic_duration_attribute_spec.rb
index e41d75568f7..e6dbf403b63 100644
--- a/spec/models/concerns/chronic_duration_attribute_spec.rb
+++ b/spec/models/concerns/chronic_duration_attribute_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-shared_examples 'ChronicDurationAttribute reader' do
+RSpec.shared_examples 'ChronicDurationAttribute reader' do
it 'contains dynamically created reader method' do
expect(subject.class).to be_public_method_defined(virtual_field)
end
@@ -22,7 +22,7 @@ shared_examples 'ChronicDurationAttribute reader' do
end
end
-shared_examples 'ChronicDurationAttribute writer' do
+RSpec.shared_examples 'ChronicDurationAttribute writer' do
it 'contains dynamically created writer method' do
expect(subject.class).to be_public_method_defined("#{virtual_field}=")
end
@@ -94,7 +94,7 @@ shared_examples 'ChronicDurationAttribute writer' do
end
end
-describe 'ChronicDurationAttribute' do
+RSpec.describe 'ChronicDurationAttribute' do
context 'when default value is not set' do
let(:source_field) {:maximum_timeout}
let(:virtual_field) {:maximum_timeout_human_readable}
@@ -118,7 +118,7 @@ describe 'ChronicDurationAttribute' do
end
end
-describe 'ChronicDurationAttribute - reader' do
+RSpec.describe 'ChronicDurationAttribute - reader' do
let(:source_field) {:timeout}
let(:virtual_field) {:timeout_human_readable}
diff --git a/spec/models/concerns/ci/has_ref_spec.rb b/spec/models/concerns/ci/has_ref_spec.rb
index b98f915018b..69f2fdb21e1 100644
--- a/spec/models/concerns/ci/has_ref_spec.rb
+++ b/spec/models/concerns/ci/has_ref_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::HasRef do
+RSpec.describe Ci::HasRef do
describe '#branch?' do
let(:build) { create(:ci_build) }
diff --git a/spec/models/concerns/ci/has_status_spec.rb b/spec/models/concerns/ci/has_status_spec.rb
new file mode 100644
index 00000000000..fe46b63781d
--- /dev/null
+++ b/spec/models/concerns/ci/has_status_spec.rb
@@ -0,0 +1,411 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Ci::HasStatus do
+ describe '.slow_composite_status' do
+ using RSpec::Parameterized::TableSyntax
+
+ subject { CommitStatus.slow_composite_status(project: nil) }
+
+ shared_examples 'build status summary' do
+ context 'all successful' do
+ let!(:statuses) { Array.new(2) { create(type, status: :success) } }
+
+ it { is_expected.to eq 'success' }
+ end
+
+ context 'at least one failed' do
+ let!(:statuses) do
+ [create(type, status: :success), create(type, status: :failed)]
+ end
+
+ it { is_expected.to eq 'failed' }
+ end
+
+ context 'at least one running' do
+ let!(:statuses) do
+ [create(type, status: :success), create(type, status: :running)]
+ end
+
+ it { is_expected.to eq 'running' }
+ end
+
+ context 'at least one pending' do
+ let!(:statuses) do
+ [create(type, status: :success), create(type, status: :pending)]
+ end
+
+ it { is_expected.to eq 'running' }
+ end
+
+ context 'all waiting for resource' do
+ let!(:statuses) do
+ [create(type, status: :waiting_for_resource), create(type, status: :waiting_for_resource)]
+ end
+
+ it { is_expected.to eq 'waiting_for_resource' }
+ end
+
+ context 'at least one waiting for resource' do
+ let!(:statuses) do
+ [create(type, status: :success), create(type, status: :waiting_for_resource)]
+ end
+
+ it { is_expected.to eq 'waiting_for_resource' }
+ end
+
+ context 'all preparing' do
+ let!(:statuses) do
+ [create(type, status: :preparing), create(type, status: :preparing)]
+ end
+
+ it { is_expected.to eq 'preparing' }
+ end
+
+ context 'at least one preparing' do
+ let!(:statuses) do
+ [create(type, status: :success), create(type, status: :preparing)]
+ end
+
+ it { is_expected.to eq 'preparing' }
+ end
+
+ context 'success and failed but allowed to fail' do
+ let!(:statuses) do
+ [create(type, status: :success),
+ create(type, status: :failed, allow_failure: true)]
+ end
+
+ it { is_expected.to eq 'success' }
+ end
+
+ context 'one failed but allowed to fail' do
+ let!(:statuses) do
+ [create(type, status: :failed, allow_failure: true)]
+ end
+
+ it { is_expected.to eq 'success' }
+ end
+
+ context 'success and canceled' do
+ let!(:statuses) do
+ [create(type, status: :success), create(type, status: :canceled)]
+ end
+
+ it { is_expected.to eq 'canceled' }
+ end
+
+ context 'one failed and one canceled' do
+ let!(:statuses) do
+ [create(type, status: :failed), create(type, status: :canceled)]
+ end
+
+ it { is_expected.to eq 'failed' }
+ end
+
+ context 'one failed but allowed to fail and one canceled' do
+ let!(:statuses) do
+ [create(type, status: :failed, allow_failure: true),
+ create(type, status: :canceled)]
+ end
+
+ it { is_expected.to eq 'canceled' }
+ end
+
+ context 'one running one canceled' do
+ let!(:statuses) do
+ [create(type, status: :running), create(type, status: :canceled)]
+ end
+
+ it { is_expected.to eq 'running' }
+ end
+
+ context 'all canceled' do
+ let!(:statuses) do
+ [create(type, status: :canceled), create(type, status: :canceled)]
+ end
+
+ it { is_expected.to eq 'canceled' }
+ end
+
+ context 'success and canceled but allowed to fail' do
+ let!(:statuses) do
+ [create(type, status: :success),
+ create(type, status: :canceled, allow_failure: true)]
+ end
+
+ it { is_expected.to eq 'success' }
+ end
+
+ context 'one finished and second running but allowed to fail' do
+ let!(:statuses) do
+ [create(type, status: :success),
+ create(type, status: :running, allow_failure: true)]
+ end
+
+ it { is_expected.to eq 'running' }
+ end
+
+ context 'when one status finished and second is still created' do
+ let!(:statuses) do
+ [create(type, status: :success), create(type, status: :created)]
+ end
+
+ it { is_expected.to eq 'running' }
+ end
+
+ context 'when there is a manual status before created status' do
+ let!(:statuses) do
+ [create(type, status: :success),
+ create(type, status: :manual, allow_failure: false),
+ create(type, status: :created)]
+ end
+
+ it { is_expected.to eq 'manual' }
+ end
+
+ context 'when one status is a blocking manual action' do
+ let!(:statuses) do
+ [create(type, status: :failed),
+ create(type, status: :manual, allow_failure: false)]
+ end
+
+ it { is_expected.to eq 'manual' }
+ end
+
+ context 'when one status is a non-blocking manual action' do
+ let!(:statuses) do
+ [create(type, status: :failed),
+ create(type, status: :manual, allow_failure: true)]
+ end
+
+ it { is_expected.to eq 'failed' }
+ end
+ end
+
+ where(:ci_composite_status) do
+ [false, true]
+ end
+
+ with_them do
+ before do
+ stub_feature_flags(ci_composite_status: ci_composite_status)
+ end
+
+ context 'ci build statuses' do
+ let(:type) { :ci_build }
+
+ it_behaves_like 'build status summary'
+ end
+
+ context 'generic commit statuses' do
+ let(:type) { :generic_commit_status }
+
+ it_behaves_like 'build status summary'
+ end
+ end
+ end
+
+ context 'for scope with one status' do
+ shared_examples 'having a job' do |status|
+ %i[ci_build generic_commit_status].each do |type|
+ context "when it's #{status} #{type} job" do
+ let!(:job) { create(type, status) }
+
+ describe ".#{status}" do
+ it 'contains the job' do
+ expect(CommitStatus.public_send(status).all)
+ .to contain_exactly(job)
+ end
+ end
+
+ describe '.relevant' do
+ if status == :created
+ it 'contains nothing' do
+ expect(CommitStatus.relevant.all).to be_empty
+ end
+ else
+ it 'contains the job' do
+ expect(CommitStatus.relevant.all).to contain_exactly(job)
+ end
+ end
+ end
+ end
+ end
+ end
+
+ %i[created waiting_for_resource preparing running pending success
+ failed canceled skipped].each do |status|
+ it_behaves_like 'having a job', status
+ end
+ end
+
+ context 'for scope with more statuses' do
+ shared_examples 'containing the job' do |status|
+ %i[ci_build generic_commit_status].each do |type|
+ context "when it's #{status} #{type} job" do
+ let!(:job) { create(type, status) }
+
+ it 'contains the job' do
+ is_expected.to contain_exactly(job)
+ end
+ end
+ end
+ end
+
+ shared_examples 'not containing the job' do |status|
+ %i[ci_build generic_commit_status].each do |type|
+ context "when it's #{status} #{type} job" do
+ let!(:job) { create(type, status) }
+
+ it 'contains nothing' do
+ is_expected.to be_empty
+ end
+ end
+ end
+ end
+
+ describe '.running_or_pending' do
+ subject { CommitStatus.running_or_pending }
+
+ %i[running pending].each do |status|
+ it_behaves_like 'containing the job', status
+ end
+
+ %i[created failed success].each do |status|
+ it_behaves_like 'not containing the job', status
+ end
+ end
+
+ describe '.alive' do
+ subject { CommitStatus.alive }
+
+ %i[running pending waiting_for_resource preparing created].each do |status|
+ it_behaves_like 'containing the job', status
+ end
+
+ %i[failed success].each do |status|
+ it_behaves_like 'not containing the job', status
+ end
+ end
+
+ describe '.alive_or_scheduled' do
+ subject { CommitStatus.alive_or_scheduled }
+
+ %i[running pending waiting_for_resource preparing created scheduled].each do |status|
+ it_behaves_like 'containing the job', status
+ end
+
+ %i[failed success canceled skipped].each do |status|
+ it_behaves_like 'not containing the job', status
+ end
+ end
+
+ describe '.created_or_pending' do
+ subject { CommitStatus.created_or_pending }
+
+ %i[created pending].each do |status|
+ it_behaves_like 'containing the job', status
+ end
+
+ %i[running failed success].each do |status|
+ it_behaves_like 'not containing the job', status
+ end
+ end
+
+ describe '.finished' do
+ subject { CommitStatus.finished }
+
+ %i[success failed canceled].each do |status|
+ it_behaves_like 'containing the job', status
+ end
+
+ %i[created running pending].each do |status|
+ it_behaves_like 'not containing the job', status
+ end
+ end
+
+ describe '.cancelable' do
+ subject { CommitStatus.cancelable }
+
+ %i[running pending waiting_for_resource preparing created scheduled].each do |status|
+ it_behaves_like 'containing the job', status
+ end
+
+ %i[failed success skipped canceled manual].each do |status|
+ it_behaves_like 'not containing the job', status
+ end
+ end
+
+ describe '.manual' do
+ subject { CommitStatus.manual }
+
+ %i[manual].each do |status|
+ it_behaves_like 'containing the job', status
+ end
+
+ %i[failed success skipped canceled].each do |status|
+ it_behaves_like 'not containing the job', status
+ end
+ end
+
+ describe '.scheduled' do
+ subject { CommitStatus.scheduled }
+
+ %i[scheduled].each do |status|
+ it_behaves_like 'containing the job', status
+ end
+
+ %i[failed success skipped canceled].each do |status|
+ it_behaves_like 'not containing the job', status
+ end
+ end
+ end
+
+ describe '::DEFAULT_STATUS' do
+ it 'is a status created' do
+ expect(described_class::DEFAULT_STATUS).to eq 'created'
+ end
+ end
+
+ describe '::BLOCKED_STATUS' do
+ it 'is a status manual' do
+ expect(described_class::BLOCKED_STATUS).to eq %w[manual scheduled]
+ end
+ end
+
+ describe 'blocked?' do
+ subject { object.blocked? }
+
+ %w[ci_pipeline ci_stage ci_build generic_commit_status].each do |type|
+ let(:object) { build(type, status: status) }
+
+ context 'when status is scheduled' do
+ let(:status) { :scheduled }
+
+ it { is_expected.to be_truthy }
+ end
+
+ context 'when status is manual' do
+ let(:status) { :manual }
+
+ it { is_expected.to be_truthy }
+ end
+
+ context 'when status is created' do
+ let(:status) { :created }
+
+ it { is_expected.to be_falsy }
+ end
+ end
+ end
+
+ describe '.legacy_status_sql' do
+ subject { Ci::Build.legacy_status_sql }
+
+ it 'returns SQL' do
+ puts subject
+ end
+ end
+end
diff --git a/spec/models/concerns/ci/has_variable_spec.rb b/spec/models/concerns/ci/has_variable_spec.rb
index c132fe47c3c..b5390281064 100644
--- a/spec/models/concerns/ci/has_variable_spec.rb
+++ b/spec/models/concerns/ci/has_variable_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::HasVariable do
+RSpec.describe Ci::HasVariable do
subject { build(:ci_variable) }
it { is_expected.to validate_presence_of(:key) }
diff --git a/spec/models/concerns/ci/maskable_spec.rb b/spec/models/concerns/ci/maskable_spec.rb
index 01861b39165..840a08b6060 100644
--- a/spec/models/concerns/ci/maskable_spec.rb
+++ b/spec/models/concerns/ci/maskable_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::Maskable do
+RSpec.describe Ci::Maskable do
let(:variable) { build(:ci_variable) }
describe 'masked value validations' do
diff --git a/spec/models/concerns/delete_with_limit_spec.rb b/spec/models/concerns/delete_with_limit_spec.rb
index 52085f970f3..0259a1ea4fb 100644
--- a/spec/models/concerns/delete_with_limit_spec.rb
+++ b/spec/models/concerns/delete_with_limit_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe DeleteWithLimit do
+RSpec.describe DeleteWithLimit do
describe '.delete_with_limit' do
it 'deletes a limited amount of rows' do
create_list(:web_hook_log, 4)
diff --git a/spec/models/concerns/deployment_platform_spec.rb b/spec/models/concerns/deployment_platform_spec.rb
index 9164c3a75c5..2bb6aa27e21 100644
--- a/spec/models/concerns/deployment_platform_spec.rb
+++ b/spec/models/concerns/deployment_platform_spec.rb
@@ -2,12 +2,247 @@
require 'spec_helper'
-describe DeploymentPlatform do
+RSpec.describe DeploymentPlatform do
let(:project) { create(:project) }
describe '#deployment_platform' do
subject { project.deployment_platform }
+ context 'multiple clusters' do
+ let(:group) { create(:group) }
+ let(:project) { create(:project, group: group) }
+
+ shared_examples 'matching environment scope' do
+ it 'returns environment specific cluster' do
+ is_expected.to eq(cluster.platform_kubernetes)
+ end
+ end
+
+ shared_examples 'not matching environment scope' do
+ it 'returns default cluster' do
+ is_expected.to eq(default_cluster.platform_kubernetes)
+ end
+ end
+
+ context 'multiple clusters use the same management project' do
+ let(:management_project) { create(:project, group: group) }
+
+ let!(:default_cluster) do
+ create(:cluster_for_group, groups: [group], environment_scope: '*', management_project: management_project)
+ end
+
+ let!(:cluster) do
+ create(:cluster_for_group, groups: [group], environment_scope: 'review/*', management_project: management_project)
+ end
+
+ let(:environment) { 'review/name' }
+
+ subject { management_project.deployment_platform(environment: environment) }
+
+ it_behaves_like 'matching environment scope'
+ end
+
+ context 'when project does not have a cluster but has group clusters' do
+ let!(:default_cluster) do
+ create(:cluster, :provided_by_user,
+ cluster_type: :group_type, groups: [group], environment_scope: '*')
+ end
+
+ let!(:cluster) do
+ create(:cluster, :provided_by_user,
+ cluster_type: :group_type, environment_scope: 'review/*', groups: [group])
+ end
+
+ let(:environment) { 'review/name' }
+
+ subject { project.deployment_platform(environment: environment) }
+
+ context 'when environment scope is exactly matched' do
+ before do
+ cluster.update!(environment_scope: 'review/name')
+ end
+
+ it_behaves_like 'matching environment scope'
+ end
+
+ context 'when environment scope is matched by wildcard' do
+ before do
+ cluster.update!(environment_scope: 'review/*')
+ end
+
+ it_behaves_like 'matching environment scope'
+ end
+
+ context 'when environment scope does not match' do
+ before do
+ cluster.update!(environment_scope: 'review/*/special')
+ end
+
+ it_behaves_like 'not matching environment scope'
+ end
+
+ context 'when group belongs to a parent group' do
+ let(:parent_group) { create(:group) }
+ let(:group) { create(:group, parent: parent_group) }
+
+ context 'when parent_group has a cluster with default scope' do
+ let!(:parent_group_cluster) do
+ create(:cluster, :provided_by_user,
+ cluster_type: :group_type, environment_scope: '*', groups: [parent_group])
+ end
+
+ it_behaves_like 'matching environment scope'
+ end
+
+ context 'when parent_group has a cluster that is an exact match' do
+ let!(:parent_group_cluster) do
+ create(:cluster, :provided_by_user,
+ cluster_type: :group_type, environment_scope: 'review/name', groups: [parent_group])
+ end
+
+ it_behaves_like 'matching environment scope'
+ end
+ end
+ end
+
+ context 'with instance clusters' do
+ let!(:default_cluster) do
+ create(:cluster, :provided_by_user, :instance, environment_scope: '*')
+ end
+
+ let!(:cluster) do
+ create(:cluster, :provided_by_user, :instance, environment_scope: 'review/*')
+ end
+
+ let(:environment) { 'review/name' }
+
+ subject { project.deployment_platform(environment: environment) }
+
+ context 'when environment scope is exactly matched' do
+ before do
+ cluster.update!(environment_scope: 'review/name')
+ end
+
+ it_behaves_like 'matching environment scope'
+ end
+
+ context 'when environment scope is matched by wildcard' do
+ before do
+ cluster.update!(environment_scope: 'review/*')
+ end
+
+ it_behaves_like 'matching environment scope'
+ end
+
+ context 'when environment scope does not match' do
+ before do
+ cluster.update!(environment_scope: 'review/*/special')
+ end
+
+ it_behaves_like 'not matching environment scope'
+ end
+ end
+
+ context 'when environment is specified' do
+ let!(:default_cluster) { create(:cluster, :provided_by_user, projects: [project], environment_scope: '*') }
+ let!(:cluster) { create(:cluster, :provided_by_user, environment_scope: 'review/*', projects: [project]) }
+
+ let!(:group_default_cluster) do
+ create(:cluster, :provided_by_user,
+ cluster_type: :group_type, groups: [group], environment_scope: '*')
+ end
+
+ let(:environment) { 'review/name' }
+
+ subject { project.deployment_platform(environment: environment) }
+
+ context 'when environment scope is exactly matched' do
+ before do
+ cluster.update!(environment_scope: 'review/name')
+ end
+
+ it_behaves_like 'matching environment scope'
+ end
+
+ context 'when environment scope is matched by wildcard' do
+ before do
+ cluster.update!(environment_scope: 'review/*')
+ end
+
+ it_behaves_like 'matching environment scope'
+ end
+
+ context 'when environment scope does not match' do
+ before do
+ cluster.update!(environment_scope: 'review/*/special')
+ end
+
+ it_behaves_like 'not matching environment scope'
+ end
+
+ context 'when environment scope has _' do
+ it 'does not treat it as wildcard' do
+ cluster.update!(environment_scope: 'foo_bar/*')
+
+ is_expected.to eq(default_cluster.platform_kubernetes)
+ end
+
+ context 'when environment name contains an underscore' do
+ let(:environment) { 'foo_bar/test' }
+
+ it 'matches literally for _' do
+ cluster.update!(environment_scope: 'foo_bar/*')
+
+ is_expected.to eq(cluster.platform_kubernetes)
+ end
+ end
+ end
+
+ # The environment name and scope cannot have % at the moment,
+ # but we're considering relaxing it and we should also make sure
+ # it doesn't break in case some data sneaked in somehow as we're
+ # not checking this integrity in database level.
+ context 'when environment scope has %' do
+ it 'does not treat it as wildcard' do
+ cluster.update_attribute(:environment_scope, '*%*')
+
+ is_expected.to eq(default_cluster.platform_kubernetes)
+ end
+
+ context 'when environment name contains a percent char' do
+ let(:environment) { 'foo%bar/test' }
+
+ it 'matches literally for %' do
+ cluster.update_attribute(:environment_scope, 'foo%bar/*')
+
+ is_expected.to eq(cluster.platform_kubernetes)
+ end
+ end
+ end
+
+ context 'when perfectly matched cluster exists' do
+ let!(:perfectly_matched_cluster) { create(:cluster, :provided_by_user, projects: [project], environment_scope: 'review/name') }
+
+ it 'returns perfectly matched cluster as highest precedence' do
+ is_expected.to eq(perfectly_matched_cluster.platform_kubernetes)
+ end
+ end
+ end
+
+ context 'with multiple clusters and multiple environments' do
+ let!(:cluster_1) { create(:cluster, :provided_by_user, projects: [project], environment_scope: 'staging/*') }
+ let!(:cluster_2) { create(:cluster, :provided_by_user, projects: [project], environment_scope: 'test/*') }
+
+ let(:environment_1) { 'staging/name' }
+ let(:environment_2) { 'test/name' }
+
+ it 'returns the appropriate cluster' do
+ expect(project.deployment_platform(environment: environment_1)).to eq(cluster_1.platform_kubernetes)
+ expect(project.deployment_platform(environment: environment_2)).to eq(cluster_2.platform_kubernetes)
+ end
+ end
+ end
+
context 'with no Kubernetes configuration on CI/CD, no Kubernetes Service' do
it { is_expected.to be_nil }
end
diff --git a/spec/models/concerns/deprecated_assignee_spec.rb b/spec/models/concerns/deprecated_assignee_spec.rb
index e394de0aa34..630d9ea601f 100644
--- a/spec/models/concerns/deprecated_assignee_spec.rb
+++ b/spec/models/concerns/deprecated_assignee_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe DeprecatedAssignee do
+RSpec.describe DeprecatedAssignee do
let(:user) { create(:user) }
describe '#assignee_id=' do
diff --git a/spec/models/concerns/discussion_on_diff_spec.rb b/spec/models/concerns/discussion_on_diff_spec.rb
index f091861bd41..dd5d422f12d 100644
--- a/spec/models/concerns/discussion_on_diff_spec.rb
+++ b/spec/models/concerns/discussion_on_diff_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe DiscussionOnDiff do
+RSpec.describe DiscussionOnDiff do
subject { create(:diff_note_on_merge_request, line_number: 18).to_discussion }
describe "#truncated_diff_lines" do
diff --git a/spec/models/concerns/each_batch_spec.rb b/spec/models/concerns/each_batch_spec.rb
index ee3d9aea505..3c93c8a7a79 100644
--- a/spec/models/concerns/each_batch_spec.rb
+++ b/spec/models/concerns/each_batch_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe EachBatch do
+RSpec.describe EachBatch do
describe '.each_batch' do
let(:model) do
Class.new(ActiveRecord::Base) do
diff --git a/spec/models/concerns/editable_spec.rb b/spec/models/concerns/editable_spec.rb
index 4a4a3ca5687..1d26629d0aa 100644
--- a/spec/models/concerns/editable_spec.rb
+++ b/spec/models/concerns/editable_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Editable do
+RSpec.describe Editable do
describe '#edited?' do
let(:issue) { create(:issue, last_edited_at: nil) }
let(:edited_issue) { create(:issue, created_at: 3.days.ago, last_edited_at: 2.days.ago) }
diff --git a/spec/models/concerns/expirable_spec.rb b/spec/models/concerns/expirable_spec.rb
index f4f5eab5b86..b20d759fc3f 100644
--- a/spec/models/concerns/expirable_spec.rb
+++ b/spec/models/concerns/expirable_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Expirable do
+RSpec.describe Expirable do
describe 'ProjectMember' do
let(:no_expire) { create(:project_member) }
let(:expire_later) { create(:project_member, expires_at: Time.current + 6.days) }
diff --git a/spec/models/concerns/faster_cache_keys_spec.rb b/spec/models/concerns/faster_cache_keys_spec.rb
index 7830acbae3d..ab6e809b3f7 100644
--- a/spec/models/concerns/faster_cache_keys_spec.rb
+++ b/spec/models/concerns/faster_cache_keys_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe FasterCacheKeys do
+RSpec.describe FasterCacheKeys do
describe '#cache_key' do
it 'returns a String' do
# We're using a fixed string here so it's easier to set an expectation for
diff --git a/spec/models/concerns/featurable_spec.rb b/spec/models/concerns/featurable_spec.rb
index 89720e3652c..cc01820cc97 100644
--- a/spec/models/concerns/featurable_spec.rb
+++ b/spec/models/concerns/featurable_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Featurable do
+RSpec.describe Featurable do
let_it_be(:user) { create(:user) }
let(:project) { create(:project) }
let(:feature_class) { subject.class }
diff --git a/spec/models/concerns/feature_gate_spec.rb b/spec/models/concerns/feature_gate_spec.rb
index 276d3d9e1d5..6106708a32d 100644
--- a/spec/models/concerns/feature_gate_spec.rb
+++ b/spec/models/concerns/feature_gate_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe FeatureGate do
+RSpec.describe FeatureGate do
describe 'User' do
describe '#flipper_id' do
context 'when user is not persisted' do
diff --git a/spec/models/concerns/from_union_spec.rb b/spec/models/concerns/from_union_spec.rb
index 735e14b47ec..9819a6ec3de 100644
--- a/spec/models/concerns/from_union_spec.rb
+++ b/spec/models/concerns/from_union_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe FromUnion do
+RSpec.describe FromUnion do
describe '.from_union' do
let(:model) do
Class.new(ActiveRecord::Base) do
diff --git a/spec/models/concerns/group_descendant_spec.rb b/spec/models/concerns/group_descendant_spec.rb
index 47419770d0f..b29fa910ee6 100644
--- a/spec/models/concerns/group_descendant_spec.rb
+++ b/spec/models/concerns/group_descendant_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GroupDescendant do
+RSpec.describe GroupDescendant do
let(:parent) { create(:group) }
let(:subgroup) { create(:group, parent: parent) }
let(:subsub_group) { create(:group, parent: subgroup) }
diff --git a/spec/models/concerns/has_environment_scope_spec.rb b/spec/models/concerns/has_environment_scope_spec.rb
index a6e1ba59263..0cc997709c9 100644
--- a/spec/models/concerns/has_environment_scope_spec.rb
+++ b/spec/models/concerns/has_environment_scope_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe HasEnvironmentScope do
+RSpec.describe HasEnvironmentScope do
subject { build(:ci_variable) }
it { is_expected.to allow_value('*').for(:environment_scope) }
diff --git a/spec/models/concerns/has_status_spec.rb b/spec/models/concerns/has_status_spec.rb
deleted file mode 100644
index 68047f24ec3..00000000000
--- a/spec/models/concerns/has_status_spec.rb
+++ /dev/null
@@ -1,411 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-describe HasStatus do
- describe '.slow_composite_status' do
- using RSpec::Parameterized::TableSyntax
-
- subject { CommitStatus.slow_composite_status(project: nil) }
-
- shared_examples 'build status summary' do
- context 'all successful' do
- let!(:statuses) { Array.new(2) { create(type, status: :success) } }
-
- it { is_expected.to eq 'success' }
- end
-
- context 'at least one failed' do
- let!(:statuses) do
- [create(type, status: :success), create(type, status: :failed)]
- end
-
- it { is_expected.to eq 'failed' }
- end
-
- context 'at least one running' do
- let!(:statuses) do
- [create(type, status: :success), create(type, status: :running)]
- end
-
- it { is_expected.to eq 'running' }
- end
-
- context 'at least one pending' do
- let!(:statuses) do
- [create(type, status: :success), create(type, status: :pending)]
- end
-
- it { is_expected.to eq 'running' }
- end
-
- context 'all waiting for resource' do
- let!(:statuses) do
- [create(type, status: :waiting_for_resource), create(type, status: :waiting_for_resource)]
- end
-
- it { is_expected.to eq 'waiting_for_resource' }
- end
-
- context 'at least one waiting for resource' do
- let!(:statuses) do
- [create(type, status: :success), create(type, status: :waiting_for_resource)]
- end
-
- it { is_expected.to eq 'waiting_for_resource' }
- end
-
- context 'all preparing' do
- let!(:statuses) do
- [create(type, status: :preparing), create(type, status: :preparing)]
- end
-
- it { is_expected.to eq 'preparing' }
- end
-
- context 'at least one preparing' do
- let!(:statuses) do
- [create(type, status: :success), create(type, status: :preparing)]
- end
-
- it { is_expected.to eq 'preparing' }
- end
-
- context 'success and failed but allowed to fail' do
- let!(:statuses) do
- [create(type, status: :success),
- create(type, status: :failed, allow_failure: true)]
- end
-
- it { is_expected.to eq 'success' }
- end
-
- context 'one failed but allowed to fail' do
- let!(:statuses) do
- [create(type, status: :failed, allow_failure: true)]
- end
-
- it { is_expected.to eq 'success' }
- end
-
- context 'success and canceled' do
- let!(:statuses) do
- [create(type, status: :success), create(type, status: :canceled)]
- end
-
- it { is_expected.to eq 'canceled' }
- end
-
- context 'one failed and one canceled' do
- let!(:statuses) do
- [create(type, status: :failed), create(type, status: :canceled)]
- end
-
- it { is_expected.to eq 'failed' }
- end
-
- context 'one failed but allowed to fail and one canceled' do
- let!(:statuses) do
- [create(type, status: :failed, allow_failure: true),
- create(type, status: :canceled)]
- end
-
- it { is_expected.to eq 'canceled' }
- end
-
- context 'one running one canceled' do
- let!(:statuses) do
- [create(type, status: :running), create(type, status: :canceled)]
- end
-
- it { is_expected.to eq 'running' }
- end
-
- context 'all canceled' do
- let!(:statuses) do
- [create(type, status: :canceled), create(type, status: :canceled)]
- end
-
- it { is_expected.to eq 'canceled' }
- end
-
- context 'success and canceled but allowed to fail' do
- let!(:statuses) do
- [create(type, status: :success),
- create(type, status: :canceled, allow_failure: true)]
- end
-
- it { is_expected.to eq 'success' }
- end
-
- context 'one finished and second running but allowed to fail' do
- let!(:statuses) do
- [create(type, status: :success),
- create(type, status: :running, allow_failure: true)]
- end
-
- it { is_expected.to eq 'running' }
- end
-
- context 'when one status finished and second is still created' do
- let!(:statuses) do
- [create(type, status: :success), create(type, status: :created)]
- end
-
- it { is_expected.to eq 'running' }
- end
-
- context 'when there is a manual status before created status' do
- let!(:statuses) do
- [create(type, status: :success),
- create(type, status: :manual, allow_failure: false),
- create(type, status: :created)]
- end
-
- it { is_expected.to eq 'manual' }
- end
-
- context 'when one status is a blocking manual action' do
- let!(:statuses) do
- [create(type, status: :failed),
- create(type, status: :manual, allow_failure: false)]
- end
-
- it { is_expected.to eq 'manual' }
- end
-
- context 'when one status is a non-blocking manual action' do
- let!(:statuses) do
- [create(type, status: :failed),
- create(type, status: :manual, allow_failure: true)]
- end
-
- it { is_expected.to eq 'failed' }
- end
- end
-
- where(:ci_composite_status) do
- [false, true]
- end
-
- with_them do
- before do
- stub_feature_flags(ci_composite_status: ci_composite_status)
- end
-
- context 'ci build statuses' do
- let(:type) { :ci_build }
-
- it_behaves_like 'build status summary'
- end
-
- context 'generic commit statuses' do
- let(:type) { :generic_commit_status }
-
- it_behaves_like 'build status summary'
- end
- end
- end
-
- context 'for scope with one status' do
- shared_examples 'having a job' do |status|
- %i[ci_build generic_commit_status].each do |type|
- context "when it's #{status} #{type} job" do
- let!(:job) { create(type, status) }
-
- describe ".#{status}" do
- it 'contains the job' do
- expect(CommitStatus.public_send(status).all)
- .to contain_exactly(job)
- end
- end
-
- describe '.relevant' do
- if status == :created
- it 'contains nothing' do
- expect(CommitStatus.relevant.all).to be_empty
- end
- else
- it 'contains the job' do
- expect(CommitStatus.relevant.all).to contain_exactly(job)
- end
- end
- end
- end
- end
- end
-
- %i[created waiting_for_resource preparing running pending success
- failed canceled skipped].each do |status|
- it_behaves_like 'having a job', status
- end
- end
-
- context 'for scope with more statuses' do
- shared_examples 'containing the job' do |status|
- %i[ci_build generic_commit_status].each do |type|
- context "when it's #{status} #{type} job" do
- let!(:job) { create(type, status) }
-
- it 'contains the job' do
- is_expected.to contain_exactly(job)
- end
- end
- end
- end
-
- shared_examples 'not containing the job' do |status|
- %i[ci_build generic_commit_status].each do |type|
- context "when it's #{status} #{type} job" do
- let!(:job) { create(type, status) }
-
- it 'contains nothing' do
- is_expected.to be_empty
- end
- end
- end
- end
-
- describe '.running_or_pending' do
- subject { CommitStatus.running_or_pending }
-
- %i[running pending].each do |status|
- it_behaves_like 'containing the job', status
- end
-
- %i[created failed success].each do |status|
- it_behaves_like 'not containing the job', status
- end
- end
-
- describe '.alive' do
- subject { CommitStatus.alive }
-
- %i[running pending waiting_for_resource preparing created].each do |status|
- it_behaves_like 'containing the job', status
- end
-
- %i[failed success].each do |status|
- it_behaves_like 'not containing the job', status
- end
- end
-
- describe '.alive_or_scheduled' do
- subject { CommitStatus.alive_or_scheduled }
-
- %i[running pending waiting_for_resource preparing created scheduled].each do |status|
- it_behaves_like 'containing the job', status
- end
-
- %i[failed success canceled skipped].each do |status|
- it_behaves_like 'not containing the job', status
- end
- end
-
- describe '.created_or_pending' do
- subject { CommitStatus.created_or_pending }
-
- %i[created pending].each do |status|
- it_behaves_like 'containing the job', status
- end
-
- %i[running failed success].each do |status|
- it_behaves_like 'not containing the job', status
- end
- end
-
- describe '.finished' do
- subject { CommitStatus.finished }
-
- %i[success failed canceled].each do |status|
- it_behaves_like 'containing the job', status
- end
-
- %i[created running pending].each do |status|
- it_behaves_like 'not containing the job', status
- end
- end
-
- describe '.cancelable' do
- subject { CommitStatus.cancelable }
-
- %i[running pending waiting_for_resource preparing created scheduled].each do |status|
- it_behaves_like 'containing the job', status
- end
-
- %i[failed success skipped canceled manual].each do |status|
- it_behaves_like 'not containing the job', status
- end
- end
-
- describe '.manual' do
- subject { CommitStatus.manual }
-
- %i[manual].each do |status|
- it_behaves_like 'containing the job', status
- end
-
- %i[failed success skipped canceled].each do |status|
- it_behaves_like 'not containing the job', status
- end
- end
-
- describe '.scheduled' do
- subject { CommitStatus.scheduled }
-
- %i[scheduled].each do |status|
- it_behaves_like 'containing the job', status
- end
-
- %i[failed success skipped canceled].each do |status|
- it_behaves_like 'not containing the job', status
- end
- end
- end
-
- describe '::DEFAULT_STATUS' do
- it 'is a status created' do
- expect(described_class::DEFAULT_STATUS).to eq 'created'
- end
- end
-
- describe '::BLOCKED_STATUS' do
- it 'is a status manual' do
- expect(described_class::BLOCKED_STATUS).to eq %w[manual scheduled]
- end
- end
-
- describe 'blocked?' do
- subject { object.blocked? }
-
- %w[ci_pipeline ci_stage ci_build generic_commit_status].each do |type|
- let(:object) { build(type, status: status) }
-
- context 'when status is scheduled' do
- let(:status) { :scheduled }
-
- it { is_expected.to be_truthy }
- end
-
- context 'when status is manual' do
- let(:status) { :manual }
-
- it { is_expected.to be_truthy }
- end
-
- context 'when status is created' do
- let(:status) { :created }
-
- it { is_expected.to be_falsy }
- end
- end
- end
-
- describe '.legacy_status_sql' do
- subject { Ci::Build.legacy_status_sql }
-
- it 'returns SQL' do
- puts subject
- end
- end
-end
diff --git a/spec/models/concerns/has_user_type_spec.rb b/spec/models/concerns/has_user_type_spec.rb
index f12eee414f9..9496bb57b8b 100644
--- a/spec/models/concerns/has_user_type_spec.rb
+++ b/spec/models/concerns/has_user_type_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe User do
+RSpec.describe User do
specify 'types consistency checks', :aggregate_failures do
expect(described_class::USER_TYPES.keys)
.to match_array(%w[human ghost alert_bot project_bot support_bot service_user visual_review_bot migration_bot])
diff --git a/spec/models/concerns/ignorable_columns_spec.rb b/spec/models/concerns/ignorable_columns_spec.rb
index 018b1296c62..a5eff154a0b 100644
--- a/spec/models/concerns/ignorable_columns_spec.rb
+++ b/spec/models/concerns/ignorable_columns_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe IgnorableColumns do
+RSpec.describe IgnorableColumns do
let(:record_class) do
Class.new(ApplicationRecord) do
include IgnorableColumns
diff --git a/spec/models/concerns/issuable_spec.rb b/spec/models/concerns/issuable_spec.rb
index 74ee7a87b7b..96d3e2b7b1b 100644
--- a/spec/models/concerns/issuable_spec.rb
+++ b/spec/models/concerns/issuable_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Issuable do
+RSpec.describe Issuable do
include ProjectForksHelper
let(:issuable_class) { Issue }
@@ -416,6 +416,27 @@ describe Issuable do
describe '#to_hook_data' do
let(:builder) { double }
+ context 'when old_associations is empty' do
+ let(:label) { create(:label) }
+
+ before do
+ issue.update!(labels: [label])
+ issue.assignees << user
+ issue.spend_time(duration: 2, user_id: user.id, spent_at: Time.current)
+ expect(Gitlab::HookData::IssuableBuilder)
+ .to receive(:new).with(issue).and_return(builder)
+ end
+
+ it 'delegates to Gitlab::HookData::IssuableBuilder#build and does not set labels, assignees, nor total_time_spent' do
+ expect(builder).to receive(:build).with(
+ user: user,
+ changes: {})
+
+ # In some cases, old_associations is empty, e.g. on a close event
+ issue.to_hook_data(user)
+ end
+ end
+
context 'labels are updated' do
let(:labels) { create_list(:label, 2) }
diff --git a/spec/models/concerns/limitable_spec.rb b/spec/models/concerns/limitable_spec.rb
index ca0a257be7a..753e2a8ee5e 100644
--- a/spec/models/concerns/limitable_spec.rb
+++ b/spec/models/concerns/limitable_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Limitable do
+RSpec.describe Limitable do
let(:minimal_test_class) do
Class.new do
include ActiveModel::Model
diff --git a/spec/models/concerns/loaded_in_group_list_spec.rb b/spec/models/concerns/loaded_in_group_list_spec.rb
index 509811822e0..c37943022ba 100644
--- a/spec/models/concerns/loaded_in_group_list_spec.rb
+++ b/spec/models/concerns/loaded_in_group_list_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe LoadedInGroupList do
+RSpec.describe LoadedInGroupList do
let(:parent) { create(:group) }
subject(:found_group) { Group.with_selects_for_list.find_by(id: parent.id) }
diff --git a/spec/models/concerns/manual_inverse_association_spec.rb b/spec/models/concerns/manual_inverse_association_spec.rb
index ee32e3b165b..1349d2cc680 100644
--- a/spec/models/concerns/manual_inverse_association_spec.rb
+++ b/spec/models/concerns/manual_inverse_association_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ManualInverseAssociation do
+RSpec.describe ManualInverseAssociation do
let(:model) do
Class.new(MergeRequest) do
belongs_to :manual_association, class_name: 'MergeRequestDiff', foreign_key: :latest_merge_request_diff_id
diff --git a/spec/models/concerns/mentionable_spec.rb b/spec/models/concerns/mentionable_spec.rb
index 03fd1c69654..758b5aa2ce4 100644
--- a/spec/models/concerns/mentionable_spec.rb
+++ b/spec/models/concerns/mentionable_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Mentionable do
+RSpec.describe Mentionable do
before do
stub_const('Example', Class.new)
Example.class_eval do
@@ -67,7 +67,7 @@ describe Mentionable do
end
end
-describe Issue, "Mentionable" do
+RSpec.describe Issue, "Mentionable" do
describe '#mentioned_users' do
let!(:user) { create(:user, username: 'stranger') }
let!(:user2) { create(:user, username: 'john') }
@@ -222,7 +222,7 @@ describe Issue, "Mentionable" do
end
end
-describe Commit, 'Mentionable' do
+RSpec.describe Commit, 'Mentionable' do
let(:project) { create(:project, :public, :repository) }
let(:commit) { project.commit }
@@ -291,7 +291,7 @@ describe Commit, 'Mentionable' do
end
end
-describe MergeRequest, 'Mentionable' do
+RSpec.describe MergeRequest, 'Mentionable' do
describe '#store_mentions!' do
it_behaves_like 'mentions in description', :merge_request
it_behaves_like 'mentions in notes', :merge_request do
@@ -312,7 +312,7 @@ describe MergeRequest, 'Mentionable' do
end
end
-describe Snippet, 'Mentionable' do
+RSpec.describe Snippet, 'Mentionable' do
describe '#store_mentions!' do
it_behaves_like 'mentions in description', :project_snippet
it_behaves_like 'mentions in notes', :project_snippet do
@@ -329,7 +329,7 @@ describe Snippet, 'Mentionable' do
end
end
-describe PersonalSnippet, 'Mentionable' do
+RSpec.describe PersonalSnippet, 'Mentionable' do
describe '#store_mentions!' do
it_behaves_like 'mentions in description', :personal_snippet
it_behaves_like 'mentions in notes', :personal_snippet do
@@ -346,7 +346,7 @@ describe PersonalSnippet, 'Mentionable' do
end
end
-describe DesignManagement::Design do
+RSpec.describe DesignManagement::Design do
describe '#store_mentions!' do
it_behaves_like 'mentions in notes', :design do
let(:note) { create(:diff_note_on_design) }
diff --git a/spec/models/concerns/milestoneable_spec.rb b/spec/models/concerns/milestoneable_spec.rb
index 0b19c0542ee..15352a1453c 100644
--- a/spec/models/concerns/milestoneable_spec.rb
+++ b/spec/models/concerns/milestoneable_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Milestoneable do
+RSpec.describe Milestoneable do
let(:user) { create(:user) }
let(:milestone) { create(:milestone, project: project) }
diff --git a/spec/models/concerns/milestoneish_spec.rb b/spec/models/concerns/milestoneish_spec.rb
index 8c43a12aa15..58cd054efd5 100644
--- a/spec/models/concerns/milestoneish_spec.rb
+++ b/spec/models/concerns/milestoneish_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Milestone, 'Milestoneish' do
+RSpec.describe Milestone, 'Milestoneish' do
let(:author) { create(:user) }
let(:assignee) { create(:user) }
let(:non_member) { create(:user) }
diff --git a/spec/models/concerns/noteable_spec.rb b/spec/models/concerns/noteable_spec.rb
index 5c8c5425ca7..bb7374bf46c 100644
--- a/spec/models/concerns/noteable_spec.rb
+++ b/spec/models/concerns/noteable_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Noteable do
+RSpec.describe Noteable do
let!(:active_diff_note1) { create(:diff_note_on_merge_request) }
let(:project) { active_diff_note1.project }
subject { active_diff_note1.noteable }
@@ -262,4 +262,44 @@ describe Noteable do
end
end
end
+
+ describe "#has_any_diff_note_positions?" do
+ let(:source_branch) { "compare-with-merge-head-source" }
+ let(:target_branch) { "compare-with-merge-head-target" }
+ let(:merge_request) { create(:merge_request, source_branch: source_branch, target_branch: target_branch) }
+
+ let!(:note) do
+ path = "files/markdown/ruby-style-guide.md"
+
+ position = Gitlab::Diff::Position.new(
+ old_path: path,
+ new_path: path,
+ new_line: 508,
+ diff_refs: merge_request.diff_refs
+ )
+
+ create(:diff_note_on_merge_request, project: merge_request.project, position: position, noteable: merge_request)
+ end
+
+ before do
+ MergeRequests::MergeToRefService.new(merge_request.project, merge_request.author).execute(merge_request)
+ Discussions::CaptureDiffNotePositionsService.new(merge_request).execute
+ end
+
+ it "returns true when it has diff note positions" do
+ expect(merge_request.has_any_diff_note_positions?).to be(true)
+ end
+
+ it "returns false when it has notes but no diff note positions" do
+ DiffNotePosition.where(note: note).find_each(&:delete)
+
+ expect(merge_request.has_any_diff_note_positions?).to be(false)
+ end
+
+ it "returns false when it has no notes" do
+ merge_request.notes.find_each(&:destroy)
+
+ expect(merge_request.has_any_diff_note_positions?).to be(false)
+ end
+ end
end
diff --git a/spec/models/concerns/optionally_search_spec.rb b/spec/models/concerns/optionally_search_spec.rb
index e1eb4cf8cd2..c8e2e6da51f 100644
--- a/spec/models/concerns/optionally_search_spec.rb
+++ b/spec/models/concerns/optionally_search_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe OptionallySearch do
+RSpec.describe OptionallySearch do
describe '.search' do
let(:model) do
Class.new do
diff --git a/spec/models/concerns/participable_spec.rb b/spec/models/concerns/participable_spec.rb
index 3d5937c4fc6..3376e337dc9 100644
--- a/spec/models/concerns/participable_spec.rb
+++ b/spec/models/concerns/participable_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Participable do
+RSpec.describe Participable do
let(:model) do
Class.new do
include Participable
diff --git a/spec/models/concerns/partitioned_table_spec.rb b/spec/models/concerns/partitioned_table_spec.rb
new file mode 100644
index 00000000000..3343b273ba2
--- /dev/null
+++ b/spec/models/concerns/partitioned_table_spec.rb
@@ -0,0 +1,35 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe PartitionedTable do
+ describe '.partitioned_by' do
+ subject { my_class.partitioned_by(key, strategy: :monthly) }
+
+ let(:key) { :foo }
+
+ let(:my_class) do
+ Class.new do
+ include PartitionedTable
+ end
+ end
+
+ it 'assigns the MonthlyStrategy as the partitioning strategy' do
+ subject
+
+ expect(my_class.partitioning_strategy).to be_a(Gitlab::Database::Partitioning::MonthlyStrategy)
+ end
+
+ it 'passes the partitioning key to the strategy instance' do
+ subject
+
+ expect(my_class.partitioning_strategy.partitioning_key).to eq(key)
+ end
+
+ it 'registers itself with the PartitionCreator' do
+ expect(Gitlab::Database::Partitioning::PartitionCreator).to receive(:register).with(my_class)
+
+ subject
+ end
+ end
+end
diff --git a/spec/models/concerns/presentable_spec.rb b/spec/models/concerns/presentable_spec.rb
index 9db868dd348..871e122e409 100644
--- a/spec/models/concerns/presentable_spec.rb
+++ b/spec/models/concerns/presentable_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Presentable do
+RSpec.describe Presentable do
let(:build) { Ci::Build.new }
describe '#present' do
diff --git a/spec/models/concerns/project_api_compatibility_spec.rb b/spec/models/concerns/project_api_compatibility_spec.rb
index f5722f88aac..7a69406cb71 100644
--- a/spec/models/concerns/project_api_compatibility_spec.rb
+++ b/spec/models/concerns/project_api_compatibility_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ProjectAPICompatibility do
+RSpec.describe ProjectAPICompatibility do
let(:project) { create(:project) }
# git_strategy
diff --git a/spec/models/concerns/project_features_compatibility_spec.rb b/spec/models/concerns/project_features_compatibility_spec.rb
index 8346c4ad4cc..ba70ff563a8 100644
--- a/spec/models/concerns/project_features_compatibility_spec.rb
+++ b/spec/models/concerns/project_features_compatibility_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ProjectFeaturesCompatibility do
+RSpec.describe ProjectFeaturesCompatibility do
let(:project) { create(:project) }
let(:features_enabled) { %w(issues wiki builds merge_requests snippets) }
let(:features) { features_enabled + %w(repository pages) }
diff --git a/spec/models/concerns/prometheus_adapter_spec.rb b/spec/models/concerns/prometheus_adapter_spec.rb
index fdc98ba74b8..e795e2b06cb 100644
--- a/spec/models/concerns/prometheus_adapter_spec.rb
+++ b/spec/models/concerns/prometheus_adapter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe PrometheusAdapter, :use_clean_rails_memory_store_caching do
+RSpec.describe PrometheusAdapter, :use_clean_rails_memory_store_caching do
include PrometheusHelpers
include ReactiveCachingHelpers
diff --git a/spec/models/concerns/protected_ref_access_spec.rb b/spec/models/concerns/protected_ref_access_spec.rb
index f63ad958ed3..750a5eba303 100644
--- a/spec/models/concerns/protected_ref_access_spec.rb
+++ b/spec/models/concerns/protected_ref_access_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ProtectedRefAccess do
+RSpec.describe ProtectedRefAccess do
include ExternalAuthorizationServiceHelpers
subject(:protected_ref_access) do
diff --git a/spec/models/concerns/reactive_caching_spec.rb b/spec/models/concerns/reactive_caching_spec.rb
index cfca383e0b0..b12ad82920f 100644
--- a/spec/models/concerns/reactive_caching_spec.rb
+++ b/spec/models/concerns/reactive_caching_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ReactiveCaching, :use_clean_rails_memory_store_caching do
+RSpec.describe ReactiveCaching, :use_clean_rails_memory_store_caching do
include ExclusiveLeaseHelpers
include ReactiveCachingHelpers
@@ -285,38 +285,30 @@ describe ReactiveCaching, :use_clean_rails_memory_store_caching do
go!
end
- context 'when calculated object size exceeds default reactive_cache_hard_limit' do
- let(:calculation) { -> { 'a' * 2 * 1.megabyte } }
+ context 'when reactive_cache_hard_limit is set' do
+ let(:test_class) { Class.new(cache_class_test) { self.reactive_cache_hard_limit = 1.megabyte } }
+ let(:instance) { test_class.new(666, &calculation) }
+
+ context 'when cache size is over the overridden limit' do
+ let(:calculation) { -> { 'a' * 2 * 1.megabyte } }
- shared_examples 'ExceededReactiveCacheLimit' do
it 'raises ExceededReactiveCacheLimit exception and does not cache new data' do
expect { go! }.to raise_exception(ReactiveCaching::ExceededReactiveCacheLimit)
expect(read_reactive_cache(instance)).not_to eq(calculation.call)
end
- end
- context 'when reactive_cache_hard_limit feature flag is enabled' do
- it_behaves_like 'ExceededReactiveCacheLimit'
-
- context 'when reactive_cache_hard_limit is overridden' do
- let(:test_class) { Class.new(cache_class_test) { self.reactive_cache_hard_limit = 3.megabytes } }
- let(:instance) { test_class.new(666, &calculation) }
+ context 'when reactive_cache_limit_enabled? is overridden to return false' do
+ before do
+ allow(instance).to receive(:reactive_cache_limit_enabled?).and_return(false)
+ end
it_behaves_like 'successful cache'
-
- context 'when cache size is over the overridden limit' do
- let(:calculation) { -> { 'a' * 4 * 1.megabyte } }
-
- it_behaves_like 'ExceededReactiveCacheLimit'
- end
end
end
- context 'when reactive_cache_limit feature flag is disabled' do
- before do
- stub_feature_flags(reactive_cache_limit: false)
- end
+ context 'when cache size is within the overridden limit' do
+ let(:calculation) { -> { 'Smaller than 1Mb reactive_cache_hard_limit' } }
it_behaves_like 'successful cache'
end
@@ -377,7 +369,7 @@ describe ReactiveCaching, :use_clean_rails_memory_store_caching do
it { expect(subject.reactive_cache_refresh_interval).to be_a(ActiveSupport::Duration) }
it { expect(subject.reactive_cache_lifetime).to be_a(ActiveSupport::Duration) }
it { expect(subject.reactive_cache_key).to respond_to(:call) }
- it { expect(subject.reactive_cache_hard_limit).to be_a(Integer) }
+ it { expect(subject.reactive_cache_hard_limit).to be_nil }
it { expect(subject.reactive_cache_worker_finder).to respond_to(:call) }
end
end
diff --git a/spec/models/concerns/redactable_spec.rb b/spec/models/concerns/redactable_spec.rb
index 3f6a2e2410c..bb59e04adf1 100644
--- a/spec/models/concerns/redactable_spec.rb
+++ b/spec/models/concerns/redactable_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Redactable do
+RSpec.describe Redactable do
before do
stub_commonmark_sourcepos_disabled
end
diff --git a/spec/models/concerns/redis_cacheable_spec.rb b/spec/models/concerns/redis_cacheable_spec.rb
index 1cf6afcc167..c270f23defb 100644
--- a/spec/models/concerns/redis_cacheable_spec.rb
+++ b/spec/models/concerns/redis_cacheable_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe RedisCacheable do
+RSpec.describe RedisCacheable do
let(:model) do
Struct.new(:id, :attributes) do
def read_attribute(attribute)
diff --git a/spec/models/concerns/resolvable_discussion_spec.rb b/spec/models/concerns/resolvable_discussion_spec.rb
index 95553fb13a6..c91ddfee944 100644
--- a/spec/models/concerns/resolvable_discussion_spec.rb
+++ b/spec/models/concerns/resolvable_discussion_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Discussion, ResolvableDiscussion do
+RSpec.describe Discussion, ResolvableDiscussion do
subject { described_class.new([first_note, second_note, third_note]) }
let(:first_note) { create(:discussion_note_on_merge_request) }
diff --git a/spec/models/concerns/resolvable_note_spec.rb b/spec/models/concerns/resolvable_note_spec.rb
index 12e50ac807e..69c58a5cfe5 100644
--- a/spec/models/concerns/resolvable_note_spec.rb
+++ b/spec/models/concerns/resolvable_note_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Note, ResolvableNote do
+RSpec.describe Note, ResolvableNote do
let(:project) { create(:project, :repository) }
let(:merge_request) { create(:merge_request, source_project: project) }
diff --git a/spec/models/concerns/routable_spec.rb b/spec/models/concerns/routable_spec.rb
index c891fdcb6b5..15d754861b2 100644
--- a/spec/models/concerns/routable_spec.rb
+++ b/spec/models/concerns/routable_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Group, 'Routable' do
+RSpec.describe Group, 'Routable' do
let!(:group) { create(:group, name: 'foo') }
describe 'Validations' do
@@ -164,7 +164,7 @@ describe Group, 'Routable' do
end
end
-describe Project, 'Routable' do
+RSpec.describe Project, 'Routable' do
describe '#full_path' do
let(:project) { build_stubbed(:project) }
diff --git a/spec/models/concerns/safe_url_spec.rb b/spec/models/concerns/safe_url_spec.rb
index e523e6a15e4..3d38c05bf11 100644
--- a/spec/models/concerns/safe_url_spec.rb
+++ b/spec/models/concerns/safe_url_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe SafeUrl do
+RSpec.describe SafeUrl do
describe '#safe_url' do
let(:safe_url_test_class) do
Class.new do
diff --git a/spec/models/concerns/schedulable_spec.rb b/spec/models/concerns/schedulable_spec.rb
index 38ae2112e01..875c2d80e55 100644
--- a/spec/models/concerns/schedulable_spec.rb
+++ b/spec/models/concerns/schedulable_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Schedulable do
+RSpec.describe Schedulable do
shared_examples 'before_save callback' do
it 'updates next_run_at' do
expect { object.save! }.to change { object.next_run_at }
diff --git a/spec/models/concerns/sha256_attribute_spec.rb b/spec/models/concerns/sha256_attribute_spec.rb
index 213723c2dcb..c247865d77f 100644
--- a/spec/models/concerns/sha256_attribute_spec.rb
+++ b/spec/models/concerns/sha256_attribute_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Sha256Attribute do
+RSpec.describe Sha256Attribute do
let(:model) { Class.new { include Sha256Attribute } }
before do
diff --git a/spec/models/concerns/sha_attribute_spec.rb b/spec/models/concerns/sha_attribute_spec.rb
index 0d4dbfb215e..50748efcda4 100644
--- a/spec/models/concerns/sha_attribute_spec.rb
+++ b/spec/models/concerns/sha_attribute_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ShaAttribute do
+RSpec.describe ShaAttribute do
let(:model) { Class.new { include ShaAttribute } }
before do
diff --git a/spec/models/concerns/sortable_spec.rb b/spec/models/concerns/sortable_spec.rb
index a1fe5c0928d..bbfdaeec64c 100644
--- a/spec/models/concerns/sortable_spec.rb
+++ b/spec/models/concerns/sortable_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Sortable do
+RSpec.describe Sortable do
describe '.order_by' do
let(:arel_table) { Group.arel_table }
let(:relation) { Group.all }
diff --git a/spec/models/concerns/spammable_spec.rb b/spec/models/concerns/spammable_spec.rb
index a8d27e174b7..d4fcb2e99eb 100644
--- a/spec/models/concerns/spammable_spec.rb
+++ b/spec/models/concerns/spammable_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Spammable do
+RSpec.describe Spammable do
let(:issue) { create(:issue, description: 'Test Desc.') }
describe 'Associations' do
diff --git a/spec/models/concerns/stepable_spec.rb b/spec/models/concerns/stepable_spec.rb
index 51356c3eaf6..e442e4f0664 100644
--- a/spec/models/concerns/stepable_spec.rb
+++ b/spec/models/concerns/stepable_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Stepable do
+RSpec.describe Stepable do
let(:described_class) do
Class.new do
include Stepable
diff --git a/spec/models/concerns/strip_attribute_spec.rb b/spec/models/concerns/strip_attribute_spec.rb
index 5c0d1042e06..812f0a015f7 100644
--- a/spec/models/concerns/strip_attribute_spec.rb
+++ b/spec/models/concerns/strip_attribute_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe StripAttribute do
+RSpec.describe StripAttribute do
let(:milestone) { create(:milestone) }
describe ".strip_attributes" do
diff --git a/spec/models/concerns/subscribable_spec.rb b/spec/models/concerns/subscribable_spec.rb
index f189cd7633c..2a43e748e58 100644
--- a/spec/models/concerns/subscribable_spec.rb
+++ b/spec/models/concerns/subscribable_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Subscribable, 'Subscribable' do
+RSpec.describe Subscribable, 'Subscribable' do
let(:project) { create(:project) }
let(:resource) { create(:issue, project: project) }
let(:user_1) { create(:user) }
diff --git a/spec/models/concerns/token_authenticatable_spec.rb b/spec/models/concerns/token_authenticatable_spec.rb
index 36eb8fdaba4..e0e764fc63c 100644
--- a/spec/models/concerns/token_authenticatable_spec.rb
+++ b/spec/models/concerns/token_authenticatable_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-shared_examples 'TokenAuthenticatable' do
+RSpec.shared_examples 'TokenAuthenticatable' do
describe 'dynamically defined methods' do
it { expect(described_class).to respond_to("find_by_#{token_field}") }
it { is_expected.to respond_to("ensure_#{token_field}") }
@@ -11,7 +11,7 @@ shared_examples 'TokenAuthenticatable' do
end
end
-describe User, 'TokenAuthenticatable' do
+RSpec.describe User, 'TokenAuthenticatable' do
let(:token_field) { :feed_token }
it_behaves_like 'TokenAuthenticatable'
@@ -23,7 +23,7 @@ describe User, 'TokenAuthenticatable' do
end
end
-describe ApplicationSetting, 'TokenAuthenticatable' do
+RSpec.describe ApplicationSetting, 'TokenAuthenticatable' do
let(:token_field) { :runners_registration_token }
let(:settings) { described_class.new }
@@ -100,7 +100,7 @@ describe ApplicationSetting, 'TokenAuthenticatable' do
end
end
-describe PersonalAccessToken, 'TokenAuthenticatable' do
+RSpec.describe PersonalAccessToken, 'TokenAuthenticatable' do
shared_examples 'changes personal access token' do
it 'sets new token' do
subject
@@ -205,7 +205,7 @@ describe PersonalAccessToken, 'TokenAuthenticatable' do
end
end
-describe Ci::Build, 'TokenAuthenticatable' do
+RSpec.describe Ci::Build, 'TokenAuthenticatable' do
let(:token_field) { :token }
let(:build) { FactoryBot.build(:ci_build) }
diff --git a/spec/models/concerns/token_authenticatable_strategies/base_spec.rb b/spec/models/concerns/token_authenticatable_strategies/base_spec.rb
index 7332da309d5..bccef9b9554 100644
--- a/spec/models/concerns/token_authenticatable_strategies/base_spec.rb
+++ b/spec/models/concerns/token_authenticatable_strategies/base_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe TokenAuthenticatableStrategies::Base do
+RSpec.describe TokenAuthenticatableStrategies::Base do
let(:instance) { double(:instance) }
let(:field) { double(:field) }
diff --git a/spec/models/concerns/token_authenticatable_strategies/encrypted_spec.rb b/spec/models/concerns/token_authenticatable_strategies/encrypted_spec.rb
index 70f41981b3b..f6b8cf7def4 100644
--- a/spec/models/concerns/token_authenticatable_strategies/encrypted_spec.rb
+++ b/spec/models/concerns/token_authenticatable_strategies/encrypted_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe TokenAuthenticatableStrategies::Encrypted do
+RSpec.describe TokenAuthenticatableStrategies::Encrypted do
let(:model) { double(:model) }
let(:instance) { double(:instance) }
diff --git a/spec/models/concerns/uniquify_spec.rb b/spec/models/concerns/uniquify_spec.rb
index 9ba35702ba6..9b79e4d4154 100644
--- a/spec/models/concerns/uniquify_spec.rb
+++ b/spec/models/concerns/uniquify_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Uniquify do
+RSpec.describe Uniquify do
let(:uniquify) { described_class.new }
describe "#string" do
diff --git a/spec/models/concerns/usage_statistics_spec.rb b/spec/models/concerns/usage_statistics_spec.rb
index f99f0a13317..15ccd08eda9 100644
--- a/spec/models/concerns/usage_statistics_spec.rb
+++ b/spec/models/concerns/usage_statistics_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe UsageStatistics do
+RSpec.describe UsageStatistics do
describe '.distinct_count_by' do
let_it_be(:issue_1) { create(:issue) }
let_it_be(:issue_2) { create(:issue) }
diff --git a/spec/models/concerns/where_composite_spec.rb b/spec/models/concerns/where_composite_spec.rb
index 1c0951d90d0..fb23e6bfe1d 100644
--- a/spec/models/concerns/where_composite_spec.rb
+++ b/spec/models/concerns/where_composite_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe WhereComposite do
+RSpec.describe WhereComposite do
describe '.where_composite' do
let_it_be(:test_table_name) { "test_table_#{SecureRandom.hex(10)}" }
diff --git a/spec/models/concerns/x509_serial_number_attribute_spec.rb b/spec/models/concerns/x509_serial_number_attribute_spec.rb
index 18a1d85204c..88550823748 100644
--- a/spec/models/concerns/x509_serial_number_attribute_spec.rb
+++ b/spec/models/concerns/x509_serial_number_attribute_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe X509SerialNumberAttribute do
+RSpec.describe X509SerialNumberAttribute do
let(:model) { Class.new { include X509SerialNumberAttribute } }
before do
diff --git a/spec/models/container_registry/event_spec.rb b/spec/models/container_registry/event_spec.rb
index 54ff218f2a8..21a3ab5363a 100644
--- a/spec/models/container_registry/event_spec.rb
+++ b/spec/models/container_registry/event_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ContainerRegistry::Event do
+RSpec.describe ContainerRegistry::Event do
using RSpec::Parameterized::TableSyntax
let_it_be(:group) { create(:group, name: 'group') }
diff --git a/spec/models/container_repository_spec.rb b/spec/models/container_repository_spec.rb
index 4f23a905e93..953f92d103b 100644
--- a/spec/models/container_repository_spec.rb
+++ b/spec/models/container_repository_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ContainerRepository do
+RSpec.describe ContainerRepository do
let(:group) { create(:group, name: 'group') }
let(:project) { create(:project, path: 'test', group: group) }
diff --git a/spec/models/custom_emoji_spec.rb b/spec/models/custom_emoji_spec.rb
new file mode 100644
index 00000000000..2b569b6097d
--- /dev/null
+++ b/spec/models/custom_emoji_spec.rb
@@ -0,0 +1,31 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe CustomEmoji do
+ describe 'Associations' do
+ it { is_expected.to belong_to(:namespace) }
+ it { is_expected.to have_db_column(:file) }
+ it { is_expected.to validate_length_of(:name).is_at_most(36) }
+ it { is_expected.to validate_presence_of(:name) }
+ end
+
+ describe 'exclusion of duplicated emoji' do
+ let(:emoji_name) { Gitlab::Emoji.emojis_names.sample }
+
+ it 'disallows emoji names of built-in emoji' do
+ new_emoji = build(:custom_emoji, name: emoji_name)
+
+ expect(new_emoji).not_to be_valid
+ expect(new_emoji.errors.messages).to eq(name: ["#{emoji_name} is already being used for another emoji"])
+ end
+
+ it 'disallows duplicate custom emoji names within namespace' do
+ old_emoji = create(:custom_emoji)
+ new_emoji = build(:custom_emoji, name: old_emoji.name, namespace: old_emoji.namespace)
+
+ expect(new_emoji).not_to be_valid
+ expect(new_emoji.errors.messages).to eq(name: ["has already been taken"])
+ end
+ end
+end
diff --git a/spec/models/cycle_analytics/code_spec.rb b/spec/models/cycle_analytics/code_spec.rb
index f6ab8e0ece6..8900c49a662 100644
--- a/spec/models/cycle_analytics/code_spec.rb
+++ b/spec/models/cycle_analytics/code_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'CycleAnalytics#code' do
+RSpec.describe 'CycleAnalytics#code' do
extend CycleAnalyticsHelpers::TestGeneration
let_it_be(:project) { create(:project, :repository) }
diff --git a/spec/models/cycle_analytics/issue_spec.rb b/spec/models/cycle_analytics/issue_spec.rb
index b4ab763e0e6..9372ef5f0e6 100644
--- a/spec/models/cycle_analytics/issue_spec.rb
+++ b/spec/models/cycle_analytics/issue_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'CycleAnalytics#issue' do
+RSpec.describe 'CycleAnalytics#issue' do
extend CycleAnalyticsHelpers::TestGeneration
let_it_be(:project) { create(:project, :repository) }
diff --git a/spec/models/cycle_analytics/plan_spec.rb b/spec/models/cycle_analytics/plan_spec.rb
index 6765b2e2cbc..364694a11e1 100644
--- a/spec/models/cycle_analytics/plan_spec.rb
+++ b/spec/models/cycle_analytics/plan_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'CycleAnalytics#plan' do
+RSpec.describe 'CycleAnalytics#plan' do
extend CycleAnalyticsHelpers::TestGeneration
let_it_be(:project) { create(:project, :repository) }
diff --git a/spec/models/cycle_analytics/production_spec.rb b/spec/models/cycle_analytics/production_spec.rb
index 2f2bcd63acd..cf4d57d6b73 100644
--- a/spec/models/cycle_analytics/production_spec.rb
+++ b/spec/models/cycle_analytics/production_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'CycleAnalytics#production' do
+RSpec.describe 'CycleAnalytics#production' do
extend CycleAnalyticsHelpers::TestGeneration
let_it_be(:project) { create(:project, :repository) }
diff --git a/spec/models/cycle_analytics/project_level_spec.rb b/spec/models/cycle_analytics/project_level_spec.rb
index bb296351a29..c2d421c03d8 100644
--- a/spec/models/cycle_analytics/project_level_spec.rb
+++ b/spec/models/cycle_analytics/project_level_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe CycleAnalytics::ProjectLevel do
+RSpec.describe CycleAnalytics::ProjectLevel do
let_it_be(:project) { create(:project, :repository) }
let_it_be(:from_date) { 10.days.ago }
let_it_be(:user) { project.owner }
diff --git a/spec/models/cycle_analytics/review_spec.rb b/spec/models/cycle_analytics/review_spec.rb
index 25e8f1441d3..6ebbcebd71d 100644
--- a/spec/models/cycle_analytics/review_spec.rb
+++ b/spec/models/cycle_analytics/review_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'CycleAnalytics#review' do
+RSpec.describe 'CycleAnalytics#review' do
extend CycleAnalyticsHelpers::TestGeneration
let_it_be(:project) { create(:project, :repository) }
diff --git a/spec/models/cycle_analytics/staging_spec.rb b/spec/models/cycle_analytics/staging_spec.rb
index effbc7056cc..024625d229f 100644
--- a/spec/models/cycle_analytics/staging_spec.rb
+++ b/spec/models/cycle_analytics/staging_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'CycleAnalytics#staging' do
+RSpec.describe 'CycleAnalytics#staging' do
extend CycleAnalyticsHelpers::TestGeneration
let_it_be(:project) { create(:project, :repository) }
diff --git a/spec/models/cycle_analytics/test_spec.rb b/spec/models/cycle_analytics/test_spec.rb
index 7e7ba4d9994..7010d69f8a4 100644
--- a/spec/models/cycle_analytics/test_spec.rb
+++ b/spec/models/cycle_analytics/test_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'CycleAnalytics#test' do
+RSpec.describe 'CycleAnalytics#test' do
extend CycleAnalyticsHelpers::TestGeneration
let_it_be(:project) { create(:project, :repository) }
diff --git a/spec/models/deploy_key_spec.rb b/spec/models/deploy_key_spec.rb
index ec6cfb6b826..00114a94b56 100644
--- a/spec/models/deploy_key_spec.rb
+++ b/spec/models/deploy_key_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe DeployKey, :mailer do
+RSpec.describe DeployKey, :mailer do
describe "Associations" do
it { is_expected.to have_many(:deploy_keys_projects) }
it { is_expected.to have_many(:projects) }
diff --git a/spec/models/deploy_keys_project_spec.rb b/spec/models/deploy_keys_project_spec.rb
index 1dbae78a01d..7dd4d3129de 100644
--- a/spec/models/deploy_keys_project_spec.rb
+++ b/spec/models/deploy_keys_project_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe DeployKeysProject do
+RSpec.describe DeployKeysProject do
describe "Associations" do
it { is_expected.to belong_to(:deploy_key) }
it { is_expected.to belong_to(:project) }
@@ -13,6 +13,21 @@ describe DeployKeysProject do
it { is_expected.to validate_presence_of(:deploy_key) }
end
+ describe '.with_deploy_keys' do
+ subject(:scoped_query) { described_class.with_deploy_keys.last }
+
+ it 'includes deploy_keys in query' do
+ project = create(:project)
+ create(:deploy_keys_project, project: project, deploy_key: create(:deploy_key))
+
+ includes_query_count = ActiveRecord::QueryRecorder.new { scoped_query }.count
+ deploy_key_query_count = ActiveRecord::QueryRecorder.new { scoped_query.deploy_key }.count
+
+ expect(includes_query_count).to eq(2)
+ expect(deploy_key_query_count).to eq(0)
+ end
+ end
+
describe "Destroying" do
let(:project) { create(:project) }
subject { create(:deploy_keys_project, project: project) }
diff --git a/spec/models/deploy_token_spec.rb b/spec/models/deploy_token_spec.rb
index 819e2850644..9fd3751be13 100644
--- a/spec/models/deploy_token_spec.rb
+++ b/spec/models/deploy_token_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe DeployToken do
+RSpec.describe DeployToken do
subject(:deploy_token) { create(:deploy_token) }
it { is_expected.to have_many :project_deploy_tokens }
diff --git a/spec/models/deployment_cluster_spec.rb b/spec/models/deployment_cluster_spec.rb
index 8bb09e9a510..dc9cbe4b082 100644
--- a/spec/models/deployment_cluster_spec.rb
+++ b/spec/models/deployment_cluster_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe DeploymentCluster do
+RSpec.describe DeploymentCluster do
let(:cluster) { create(:cluster) }
let(:deployment) { create(:deployment) }
let(:kubernetes_namespace) { 'an-example-namespace' }
diff --git a/spec/models/deployment_merge_request_spec.rb b/spec/models/deployment_merge_request_spec.rb
index fd5be52d47c..29834691fa4 100644
--- a/spec/models/deployment_merge_request_spec.rb
+++ b/spec/models/deployment_merge_request_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe DeploymentMergeRequest do
+RSpec.describe DeploymentMergeRequest do
let(:mr) { create(:merge_request, :merged) }
let(:deployment) { create(:deployment, :success, project: project) }
let(:project) { mr.project }
diff --git a/spec/models/deployment_metrics_spec.rb b/spec/models/deployment_metrics_spec.rb
index 5a4ae0bbe79..d0474777eb7 100644
--- a/spec/models/deployment_metrics_spec.rb
+++ b/spec/models/deployment_metrics_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe DeploymentMetrics do
+RSpec.describe DeploymentMetrics do
describe '#has_metrics?' do
subject { described_class.new(deployment.project, deployment).has_metrics? }
diff --git a/spec/models/deployment_spec.rb b/spec/models/deployment_spec.rb
index ac2a4c9877d..b320390711e 100644
--- a/spec/models/deployment_spec.rb
+++ b/spec/models/deployment_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Deployment do
+RSpec.describe Deployment do
subject { build(:deployment) }
it { is_expected.to belong_to(:project).required }
diff --git a/spec/models/description_version_spec.rb b/spec/models/description_version_spec.rb
index 5ec34c0cde4..7c094f7a0a0 100644
--- a/spec/models/description_version_spec.rb
+++ b/spec/models/description_version_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe DescriptionVersion do
+RSpec.describe DescriptionVersion do
describe 'associations' do
it { is_expected.to belong_to :issue }
it { is_expected.to belong_to :merge_request }
diff --git a/spec/models/design_management/action_spec.rb b/spec/models/design_management/action_spec.rb
index 753c31b1549..59c58191718 100644
--- a/spec/models/design_management/action_spec.rb
+++ b/spec/models/design_management/action_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe DesignManagement::Action do
+RSpec.describe DesignManagement::Action do
describe 'relations' do
it { is_expected.to belong_to(:design) }
it { is_expected.to belong_to(:version) }
diff --git a/spec/models/design_management/design_action_spec.rb b/spec/models/design_management/design_action_spec.rb
index da4ad41dfcb..958b1dd9124 100644
--- a/spec/models/design_management/design_action_spec.rb
+++ b/spec/models/design_management/design_action_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe DesignManagement::DesignAction do
+RSpec.describe DesignManagement::DesignAction do
describe 'validations' do
describe 'the design' do
let(:fail_validation) { raise_error(/design/i) }
diff --git a/spec/models/design_management/design_at_version_spec.rb b/spec/models/design_management/design_at_version_spec.rb
index f6fa8df243c..2c640ee5c2c 100644
--- a/spec/models/design_management/design_at_version_spec.rb
+++ b/spec/models/design_management/design_at_version_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe DesignManagement::DesignAtVersion do
+RSpec.describe DesignManagement::DesignAtVersion do
include DesignManagementTestHelpers
let_it_be(:issue, reload: true) { create(:issue) }
diff --git a/spec/models/design_management/design_collection_spec.rb b/spec/models/design_management/design_collection_spec.rb
index bd48f742042..c5e290da759 100644
--- a/spec/models/design_management/design_collection_spec.rb
+++ b/spec/models/design_management/design_collection_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe DesignManagement::DesignCollection do
+RSpec.describe DesignManagement::DesignCollection do
include DesignManagementTestHelpers
let_it_be(:issue, reload: true) { create(:issue) }
diff --git a/spec/models/design_management/design_spec.rb b/spec/models/design_management/design_spec.rb
index bc1f54f057e..345147390c0 100644
--- a/spec/models/design_management/design_spec.rb
+++ b/spec/models/design_management/design_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe DesignManagement::Design do
+RSpec.describe DesignManagement::Design do
include DesignManagementTestHelpers
let_it_be(:issue) { create(:issue) }
diff --git a/spec/models/design_management/repository_spec.rb b/spec/models/design_management/repository_spec.rb
index 996316eeec9..0115e0c139c 100644
--- a/spec/models/design_management/repository_spec.rb
+++ b/spec/models/design_management/repository_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe DesignManagement::Repository do
+RSpec.describe DesignManagement::Repository do
let(:project) { create(:project) }
let(:repository) { described_class.new(project) }
diff --git a/spec/models/design_management/version_spec.rb b/spec/models/design_management/version_spec.rb
index ab6958ea94a..cd52f4129dc 100644
--- a/spec/models/design_management/version_spec.rb
+++ b/spec/models/design_management/version_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe DesignManagement::Version do
+RSpec.describe DesignManagement::Version do
let_it_be(:issue) { create(:issue) }
describe 'relations' do
diff --git a/spec/models/design_user_mention_spec.rb b/spec/models/design_user_mention_spec.rb
index 03c77c73c8d..944a82c5edf 100644
--- a/spec/models/design_user_mention_spec.rb
+++ b/spec/models/design_user_mention_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe DesignUserMention do
+RSpec.describe DesignUserMention do
describe 'associations' do
it { is_expected.to belong_to(:design) }
it { is_expected.to belong_to(:note) }
diff --git a/spec/models/dev_ops_score/metric_spec.rb b/spec/models/dev_ops_score/metric_spec.rb
index 89212d5ca26..60001d0667d 100644
--- a/spec/models/dev_ops_score/metric_spec.rb
+++ b/spec/models/dev_ops_score/metric_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe DevOpsScore::Metric do
+RSpec.describe DevOpsScore::Metric do
let(:conv_dev_index) { create(:dev_ops_score_metric) }
describe '#percentage_score' do
diff --git a/spec/models/diff_discussion_spec.rb b/spec/models/diff_discussion_spec.rb
index cfeb4382927..26b311fe629 100644
--- a/spec/models/diff_discussion_spec.rb
+++ b/spec/models/diff_discussion_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe DiffDiscussion do
+RSpec.describe DiffDiscussion do
include RepoHelpers
subject { described_class.new([diff_note]) }
diff --git a/spec/models/diff_note_position_spec.rb b/spec/models/diff_note_position_spec.rb
index d93e0af5526..aa9e6b4e824 100644
--- a/spec/models/diff_note_position_spec.rb
+++ b/spec/models/diff_note_position_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe DiffNotePosition, type: :model do
+RSpec.describe DiffNotePosition, type: :model do
describe '.create_or_update_by' do
context 'when a diff note' do
let(:note) { create(:diff_note_on_merge_request) }
diff --git a/spec/models/diff_note_spec.rb b/spec/models/diff_note_spec.rb
index 8bfe2ac7a6c..8a6176bf045 100644
--- a/spec/models/diff_note_spec.rb
+++ b/spec/models/diff_note_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe DiffNote do
+RSpec.describe DiffNote do
include RepoHelpers
let_it_be(:merge_request) { create(:merge_request) }
diff --git a/spec/models/diff_viewer/base_spec.rb b/spec/models/diff_viewer/base_spec.rb
index 0a1c4c5560e..57c62788ee9 100644
--- a/spec/models/diff_viewer/base_spec.rb
+++ b/spec/models/diff_viewer/base_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe DiffViewer::Base do
+RSpec.describe DiffViewer::Base do
include FakeBlobHelpers
let(:project) { create(:project, :repository) }
diff --git a/spec/models/diff_viewer/server_side_spec.rb b/spec/models/diff_viewer/server_side_spec.rb
index 0a14eae26f3..686dd1249be 100644
--- a/spec/models/diff_viewer/server_side_spec.rb
+++ b/spec/models/diff_viewer/server_side_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe DiffViewer::ServerSide do
+RSpec.describe DiffViewer::ServerSide do
let_it_be(:project) { create(:project, :repository) }
let(:commit) { project.commit_by(oid: '570e7b2abdd848b95f2f578043fc23bd6f6fd24d') }
let!(:diff_file) { commit.diffs.diff_file_with_new_path('files/ruby/popen.rb') }
diff --git a/spec/models/discussion_spec.rb b/spec/models/discussion_spec.rb
index 950bdec4d00..021940be0c2 100644
--- a/spec/models/discussion_spec.rb
+++ b/spec/models/discussion_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Discussion do
+RSpec.describe Discussion do
subject { described_class.new([first_note, second_note, third_note]) }
let(:first_note) { create(:diff_note_on_merge_request) }
diff --git a/spec/models/draft_note_spec.rb b/spec/models/draft_note_spec.rb
index b880d3c5b97..64b06bf5c8f 100644
--- a/spec/models/draft_note_spec.rb
+++ b/spec/models/draft_note_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe DraftNote do
+RSpec.describe DraftNote do
include RepoHelpers
let(:project) { create(:project, :repository) }
diff --git a/spec/models/email_spec.rb b/spec/models/email_spec.rb
index f7b194abcee..ffdc621dd4c 100644
--- a/spec/models/email_spec.rb
+++ b/spec/models/email_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Email do
+RSpec.describe Email do
describe 'modules' do
subject { described_class }
diff --git a/spec/models/environment_spec.rb b/spec/models/environment_spec.rb
index b93da518b68..c449a3c3c47 100644
--- a/spec/models/environment_spec.rb
+++ b/spec/models/environment_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Environment, :use_clean_rails_memory_store_caching do
+RSpec.describe Environment, :use_clean_rails_memory_store_caching do
include ReactiveCachingHelpers
using RSpec::Parameterized::TableSyntax
include RepoHelpers
@@ -18,6 +18,7 @@ describe Environment, :use_clean_rails_memory_store_caching do
it { is_expected.to belong_to(:project).required }
it { is_expected.to have_many(:deployments) }
it { is_expected.to have_many(:metrics_dashboard_annotations) }
+ it { is_expected.to have_many(:alert_management_alerts) }
it { is_expected.to delegate_method(:stop_action).to(:last_deployment) }
it { is_expected.to delegate_method(:manual_actions).to(:last_deployment) }
@@ -847,6 +848,20 @@ describe Environment, :use_clean_rails_memory_store_caching do
subject { environment.calculate_reactive_cache }
+ it 'overrides default reactive_cache_hard_limit to 10 Mb' do
+ expect(described_class.reactive_cache_hard_limit).to eq(10.megabyte)
+ end
+
+ it 'overrides reactive_cache_limit_enabled? with a FF' do
+ environment_with_enabled_ff = FactoryBot.build(:environment)
+ environment_with_disabled_ff = FactoryBot.build(:environment)
+
+ stub_feature_flags(reactive_caching_limit_environment: environment_with_enabled_ff.project)
+
+ expect(environment_with_enabled_ff.send(:reactive_cache_limit_enabled?)).to be_truthy
+ expect(environment_with_disabled_ff.send(:reactive_cache_limit_enabled?)).to be_falsey
+ end
+
it 'returns cache data from the deployment platform' do
expect(environment.deployment_platform).to receive(:calculate_reactive_cache_for)
.with(environment).and_return(pods: %w(pod1 pod2))
diff --git a/spec/models/environment_status_spec.rb b/spec/models/environment_status_spec.rb
index 10283b54796..7eefb8f714a 100644
--- a/spec/models/environment_status_spec.rb
+++ b/spec/models/environment_status_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe EnvironmentStatus do
+RSpec.describe EnvironmentStatus do
include ProjectForksHelper
let(:deployment) { create(:deployment, :succeed, :review_app) }
diff --git a/spec/models/error_tracking/project_error_tracking_setting_spec.rb b/spec/models/error_tracking/project_error_tracking_setting_spec.rb
index b564c48a9c1..72ed11f6c74 100644
--- a/spec/models/error_tracking/project_error_tracking_setting_spec.rb
+++ b/spec/models/error_tracking/project_error_tracking_setting_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ErrorTracking::ProjectErrorTrackingSetting do
+RSpec.describe ErrorTracking::ProjectErrorTrackingSetting do
include ReactiveCachingHelpers
include Gitlab::Routing
diff --git a/spec/models/event_collection_spec.rb b/spec/models/event_collection_spec.rb
index 6d1954700bf..aca2a8c3a2f 100644
--- a/spec/models/event_collection_spec.rb
+++ b/spec/models/event_collection_spec.rb
@@ -2,7 +2,9 @@
require 'spec_helper'
-describe EventCollection do
+RSpec.describe EventCollection do
+ include DesignManagementTestHelpers
+
describe '#to_a' do
let_it_be(:group) { create(:group) }
let_it_be(:project) { create(:project_empty_repo, group: group) }
@@ -10,6 +12,10 @@ describe EventCollection do
let_it_be(:user) { create(:user) }
let_it_be(:merge_request) { create(:merge_request) }
+ before do
+ enable_design_management
+ end
+
context 'with project events' do
let_it_be(:push_event_payloads) do
Array.new(9) do
@@ -21,11 +27,13 @@ describe EventCollection do
let_it_be(:merge_request_events) { create_list(:event, 10, :commented, project: project, target: merge_request) }
let_it_be(:closed_issue_event) { create(:closed_issue_event, project: project, author: user) }
let_it_be(:wiki_page_event) { create(:wiki_page_event, project: project) }
+ let_it_be(:design_event) { create(:design_event, project: project) }
let(:push_events) { push_event_payloads.map(&:event) }
it 'returns an Array of events', :aggregate_failures do
most_recent_20_events = [
wiki_page_event,
+ design_event,
closed_issue_event,
*push_events,
*merge_request_events
@@ -36,40 +44,23 @@ describe EventCollection do
expect(events).to match_array(most_recent_20_events)
end
- context 'the wiki_events feature flag is disabled' do
- before do
- stub_feature_flags(wiki_events: false)
- end
-
- it 'omits the wiki page events when using to_a' do
- events = described_class.new(projects).to_a
-
- expect(events).not_to include(wiki_page_event)
- end
-
- it 'omits the wiki page events when using all_project_events' do
- events = described_class.new(projects).all_project_events
+ it 'includes the wiki page events when using to_a' do
+ events = described_class.new(projects).to_a
- expect(events).not_to include(wiki_page_event)
- end
+ expect(events).to include(wiki_page_event)
end
- context 'the wiki_events feature flag is enabled' do
- before do
- stub_feature_flags(wiki_events: true)
- end
-
- it 'includes the wiki page events when using to_a' do
- events = described_class.new(projects).to_a
+ it 'includes the design events' do
+ collection = described_class.new(projects)
- expect(events).to include(wiki_page_event)
- end
+ expect(collection.to_a).to include(design_event)
+ expect(collection.all_project_events).to include(design_event)
+ end
- it 'includes the wiki page events when using all_project_events' do
- events = described_class.new(projects).all_project_events
+ it 'includes the wiki page events when using all_project_events' do
+ events = described_class.new(projects).all_project_events
- expect(events).to include(wiki_page_event)
- end
+ expect(events).to include(wiki_page_event)
end
it 'applies a limit to the number of events' do
@@ -81,7 +72,7 @@ describe EventCollection do
it 'can paginate through events' do
events = described_class.new(projects, offset: 20).to_a
- expect(events.length).to eq(1)
+ expect(events.length).to eq(2)
end
it 'returns an empty Array when crossing the maximum page number' do
diff --git a/spec/models/event_spec.rb b/spec/models/event_spec.rb
index 14066b1e9d2..96baeab6809 100644
--- a/spec/models/event_spec.rb
+++ b/spec/models/event_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Event do
+RSpec.describe Event do
describe "Associations" do
it { is_expected.to belong_to(:project) }
it { is_expected.to belong_to(:target) }
@@ -643,15 +643,6 @@ describe Event do
end
end
- describe '.not_design' do
- it 'does not contain the design events' do
- non_design_events = events.reject(&:design?)
-
- expect(events).not_to match_array(non_design_events)
- expect(described_class.not_design).to match_array(non_design_events)
- end
- end
-
describe '.for_wiki_page' do
it 'only contains the wiki page events' do
wiki_events = events.select(&:wiki_page?)
@@ -661,15 +652,6 @@ describe Event do
end
end
- describe '.not_wiki_page' do
- it 'does not contain the wiki page events' do
- non_wiki_events = events.reject(&:wiki_page?)
-
- expect(events).not_to match_array(non_wiki_events)
- expect(described_class.not_wiki_page).to match_array(non_wiki_events)
- end
- end
-
describe '.for_wiki_meta' do
it 'finds events for a given wiki page metadata object' do
event = events.select(&:wiki_page?).first
diff --git a/spec/models/external_issue_spec.rb b/spec/models/external_issue_spec.rb
index b8d85d49b07..47b13ff50cf 100644
--- a/spec/models/external_issue_spec.rb
+++ b/spec/models/external_issue_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ExternalIssue do
+RSpec.describe ExternalIssue do
let(:project) { double('project', id: 1, to_reference: 'namespace1/project1') }
let(:issue) { described_class.new('EXT-1234', project) }
diff --git a/spec/models/external_pull_request_spec.rb b/spec/models/external_pull_request_spec.rb
index e85d5b2f6c7..e0822fc177a 100644
--- a/spec/models/external_pull_request_spec.rb
+++ b/spec/models/external_pull_request_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ExternalPullRequest do
+RSpec.describe ExternalPullRequest do
let(:project) { create(:project) }
let(:source_branch) { 'the-branch' }
let(:status) { :open }
diff --git a/spec/models/fork_network_member_spec.rb b/spec/models/fork_network_member_spec.rb
index d7a0dd5be65..b34eb7964ca 100644
--- a/spec/models/fork_network_member_spec.rb
+++ b/spec/models/fork_network_member_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ForkNetworkMember do
+RSpec.describe ForkNetworkMember do
describe 'validations' do
it { is_expected.to validate_presence_of(:project) }
it { is_expected.to validate_presence_of(:fork_network) }
diff --git a/spec/models/fork_network_spec.rb b/spec/models/fork_network_spec.rb
index 5ec0f8d6b02..c2ef1fdcb5f 100644
--- a/spec/models/fork_network_spec.rb
+++ b/spec/models/fork_network_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ForkNetwork do
+RSpec.describe ForkNetwork do
include ProjectForksHelper
describe '#add_root_as_member' do
diff --git a/spec/models/generic_commit_status_spec.rb b/spec/models/generic_commit_status_spec.rb
index c8ed898122b..6fe5a1407a9 100644
--- a/spec/models/generic_commit_status_spec.rb
+++ b/spec/models/generic_commit_status_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GenericCommitStatus do
+RSpec.describe GenericCommitStatus do
let(:project) { create(:project) }
let(:pipeline) { create(:ci_pipeline, project: project) }
let(:external_url) { 'http://example.gitlab.com/status' }
diff --git a/spec/models/gpg_key_spec.rb b/spec/models/gpg_key_spec.rb
index b9c914e2506..7ecde04e3df 100644
--- a/spec/models/gpg_key_spec.rb
+++ b/spec/models/gpg_key_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GpgKey do
+RSpec.describe GpgKey do
describe "associations" do
it { is_expected.to belong_to(:user) }
it { is_expected.to have_many(:subkeys) }
diff --git a/spec/models/gpg_key_subkey_spec.rb b/spec/models/gpg_key_subkey_spec.rb
index 5f80cc02924..c1d9e2bde43 100644
--- a/spec/models/gpg_key_subkey_spec.rb
+++ b/spec/models/gpg_key_subkey_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GpgKeySubkey do
+RSpec.describe GpgKeySubkey do
subject { build(:gpg_key_subkey) }
describe 'associations' do
diff --git a/spec/models/grafana_integration_spec.rb b/spec/models/grafana_integration_spec.rb
index 662e8b1dd61..79f102919ac 100644
--- a/spec/models/grafana_integration_spec.rb
+++ b/spec/models/grafana_integration_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GrafanaIntegration do
+RSpec.describe GrafanaIntegration do
describe 'associations' do
it { is_expected.to belong_to(:project) }
end
diff --git a/spec/models/group_custom_attribute_spec.rb b/spec/models/group_custom_attribute_spec.rb
index 7d60c74b62b..1d8afa71377 100644
--- a/spec/models/group_custom_attribute_spec.rb
+++ b/spec/models/group_custom_attribute_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GroupCustomAttribute do
+RSpec.describe GroupCustomAttribute do
describe 'assocations' do
it { is_expected.to belong_to(:group) }
end
diff --git a/spec/models/group_deploy_key_spec.rb b/spec/models/group_deploy_key_spec.rb
index 3ba56c7e504..3fe71cc4699 100644
--- a/spec/models/group_deploy_key_spec.rb
+++ b/spec/models/group_deploy_key_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GroupDeployKey do
+RSpec.describe GroupDeployKey do
it { is_expected.to validate_presence_of(:user) }
it 'is of type DeployKey' do
diff --git a/spec/models/group_group_link_spec.rb b/spec/models/group_group_link_spec.rb
index 54e622b2f22..03cc9d7e64c 100644
--- a/spec/models/group_group_link_spec.rb
+++ b/spec/models/group_group_link_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GroupGroupLink do
+RSpec.describe GroupGroupLink do
let_it_be(:group) { create(:group) }
let_it_be(:shared_group) { create(:group) }
let_it_be(:group_group_link) do
diff --git a/spec/models/group_import_state_spec.rb b/spec/models/group_import_state_spec.rb
index 9d9cb1e8391..4404ef64966 100644
--- a/spec/models/group_import_state_spec.rb
+++ b/spec/models/group_import_state_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GroupImportState do
+RSpec.describe GroupImportState do
describe 'validations' do
let_it_be(:group) { create(:group) }
diff --git a/spec/models/group_label_spec.rb b/spec/models/group_label_spec.rb
index a3a5c631c3d..ec9244d5eb5 100644
--- a/spec/models/group_label_spec.rb
+++ b/spec/models/group_label_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GroupLabel do
+RSpec.describe GroupLabel do
describe 'relationships' do
it { is_expected.to belong_to(:group) }
end
diff --git a/spec/models/group_spec.rb b/spec/models/group_spec.rb
index 93cb6d83489..4184f2d07cc 100644
--- a/spec/models/group_spec.rb
+++ b/spec/models/group_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Group do
+RSpec.describe Group do
let!(:group) { create(:group) }
describe 'associations' do
@@ -1313,4 +1313,231 @@ describe Group do
expect(groups).to contain_exactly(parent_group1, parent_group2, child_group1, child_group2, child_group3)
end
end
+
+ describe '#shared_runners_allowed?' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:shared_runners_enabled, :allow_descendants_override, :expected_shared_runners_allowed) do
+ true | false | true
+ true | true | true
+ false | false | false
+ false | true | true
+ end
+
+ with_them do
+ let!(:group) { create(:group, shared_runners_enabled: shared_runners_enabled, allow_descendants_override_disabled_shared_runners: allow_descendants_override) }
+
+ it 'returns the expected result' do
+ expect(group.shared_runners_allowed?).to eq(expected_shared_runners_allowed)
+ end
+ end
+ end
+
+ describe '#parent_allows_shared_runners?' do
+ context 'when parent group is present' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:shared_runners_enabled, :allow_descendants_override, :expected_shared_runners_allowed) do
+ true | false | true
+ true | true | true
+ false | false | false
+ false | true | true
+ end
+
+ with_them do
+ let!(:parent_group) { create(:group, shared_runners_enabled: shared_runners_enabled, allow_descendants_override_disabled_shared_runners: allow_descendants_override) }
+ let!(:group) { create(:group, parent: parent_group) }
+
+ it 'returns the expected result' do
+ expect(group.parent_allows_shared_runners?).to eq(expected_shared_runners_allowed)
+ end
+ end
+ end
+
+ context 'when parent group is missing' do
+ let!(:group) { create(:group) }
+
+ it 'returns true' do
+ expect(group.parent_allows_shared_runners?).to be_truthy
+ end
+ end
+ end
+
+ describe '#parent_enabled_shared_runners?' do
+ subject { group.parent_enabled_shared_runners? }
+
+ context 'when parent group is present' do
+ context 'When shared Runners are disabled' do
+ let!(:parent_group) { create(:group, :shared_runners_disabled) }
+ let!(:group) { create(:group, parent: parent_group) }
+
+ it { is_expected.to be_falsy }
+ end
+
+ context 'When shared Runners are enabled' do
+ let!(:parent_group) { create(:group) }
+ let!(:group) { create(:group, parent: parent_group) }
+
+ it { is_expected.to be_truthy }
+ end
+ end
+
+ context 'when parent group is missing' do
+ let!(:group) { create(:group) }
+
+ it { is_expected.to be_truthy }
+ end
+ end
+
+ describe '#enable_shared_runners!' do
+ subject { group.enable_shared_runners! }
+
+ context 'group that its ancestors have shared runners disabled' do
+ let_it_be(:parent) { create(:group, :shared_runners_disabled) }
+ let_it_be(:group) { create(:group, :shared_runners_disabled, parent: parent) }
+ let_it_be(:project) { create(:project, shared_runners_enabled: false, group: group) }
+
+ it 'raises error and does not enable shared Runners' do
+ expect { subject }
+ .to raise_error(described_class::UpdateSharedRunnersError, 'Shared Runners disabled for the parent group')
+ .and not_change { parent.reload.shared_runners_enabled }
+ .and not_change { group.reload.shared_runners_enabled }
+ .and not_change { project.reload.shared_runners_enabled }
+ end
+ end
+
+ context 'root group with shared runners disabled' do
+ let_it_be(:group) { create(:group, :shared_runners_disabled) }
+ let_it_be(:sub_group) { create(:group, :shared_runners_disabled, parent: group) }
+ let_it_be(:project) { create(:project, shared_runners_enabled: false, group: sub_group) }
+
+ it 'enables shared Runners only for itself' do
+ expect { subject }
+ .to change { group.reload.shared_runners_enabled }.from(false).to(true)
+ .and not_change { sub_group.reload.shared_runners_enabled }
+ .and not_change { project.reload.shared_runners_enabled }
+ end
+ end
+ end
+
+ describe '#disable_shared_runners!' do
+ let_it_be(:group) { create(:group) }
+ let_it_be(:sub_group) { create(:group, :shared_runners_disabled, :allow_descendants_override_disabled_shared_runners, parent: group) }
+ let_it_be(:sub_group_2) { create(:group, parent: group) }
+ let_it_be(:project) { create(:project, group: group, shared_runners_enabled: true) }
+ let_it_be(:project_2) { create(:project, group: sub_group_2, shared_runners_enabled: true) }
+
+ subject { group.disable_shared_runners! }
+
+ it 'disables shared Runners for all descendant groups and projects' do
+ expect { subject }
+ .to change { group.reload.shared_runners_enabled }.from(true).to(false)
+ .and not_change { group.reload.allow_descendants_override_disabled_shared_runners }
+ .and not_change { sub_group.reload.shared_runners_enabled }
+ .and not_change { sub_group.reload.allow_descendants_override_disabled_shared_runners }
+ .and change { sub_group_2.reload.shared_runners_enabled }.from(true).to(false)
+ .and not_change { sub_group_2.reload.allow_descendants_override_disabled_shared_runners }
+ .and change { project.reload.shared_runners_enabled }.from(true).to(false)
+ .and change { project_2.reload.shared_runners_enabled }.from(true).to(false)
+ end
+ end
+
+ describe '#allow_descendants_override_disabled_shared_runners!' do
+ subject { group.allow_descendants_override_disabled_shared_runners! }
+
+ context 'top level group' do
+ let_it_be(:group) { create(:group, :shared_runners_disabled) }
+ let_it_be(:sub_group) { create(:group, :shared_runners_disabled, parent: group) }
+ let_it_be(:project) { create(:project, shared_runners_enabled: false, group: sub_group) }
+
+ it 'enables allow descendants to override only for itself' do
+ expect { subject }
+ .to change { group.reload.allow_descendants_override_disabled_shared_runners }.from(false).to(true)
+ .and not_change { group.reload.shared_runners_enabled }
+ .and not_change { sub_group.reload.allow_descendants_override_disabled_shared_runners }
+ .and not_change { sub_group.reload.shared_runners_enabled }
+ .and not_change { project.reload.shared_runners_enabled }
+ end
+ end
+
+ context 'group that its ancestors have shared Runners disabled but allows to override' do
+ let_it_be(:parent) { create(:group, :shared_runners_disabled, :allow_descendants_override_disabled_shared_runners) }
+ let_it_be(:group) { create(:group, :shared_runners_disabled, parent: parent) }
+ let_it_be(:project) { create(:project, shared_runners_enabled: false, group: group) }
+
+ it 'enables allow descendants to override' do
+ expect { subject }
+ .to not_change { parent.reload.allow_descendants_override_disabled_shared_runners }
+ .and not_change { parent.reload.shared_runners_enabled }
+ .and change { group.reload.allow_descendants_override_disabled_shared_runners }.from(false).to(true)
+ .and not_change { group.reload.shared_runners_enabled }
+ .and not_change { project.reload.shared_runners_enabled }
+ end
+ end
+
+ context 'when parent does not allow' do
+ let_it_be(:parent) { create(:group, :shared_runners_disabled, allow_descendants_override_disabled_shared_runners: false ) }
+ let_it_be(:group) { create(:group, :shared_runners_disabled, allow_descendants_override_disabled_shared_runners: false, parent: parent) }
+
+ it 'raises error and does not allow descendants to override' do
+ expect { subject }
+ .to raise_error(described_class::UpdateSharedRunnersError, 'Group level shared Runners not allowed')
+ .and not_change { parent.reload.allow_descendants_override_disabled_shared_runners }
+ .and not_change { parent.reload.shared_runners_enabled }
+ .and not_change { group.reload.allow_descendants_override_disabled_shared_runners }
+ .and not_change { group.reload.shared_runners_enabled }
+ end
+ end
+
+ context 'top level group that has shared Runners enabled' do
+ let_it_be(:group) { create(:group, shared_runners_enabled: true) }
+ let_it_be(:sub_group) { create(:group, :shared_runners_disabled, parent: group) }
+ let_it_be(:project) { create(:project, shared_runners_enabled: false, group: sub_group) }
+
+ it 'raises error and does not change config' do
+ expect { subject }
+ .to raise_error(described_class::UpdateSharedRunnersError, 'Shared Runners enabled')
+ .and not_change { group.reload.allow_descendants_override_disabled_shared_runners }
+ .and not_change { group.reload.shared_runners_enabled }
+ .and not_change { sub_group.reload.allow_descendants_override_disabled_shared_runners }
+ .and not_change { sub_group.reload.shared_runners_enabled }
+ .and not_change { project.reload.shared_runners_enabled }
+ end
+ end
+ end
+
+ describe '#disallow_descendants_override_disabled_shared_runners!' do
+ subject { group.disallow_descendants_override_disabled_shared_runners! }
+
+ context 'top level group' do
+ let_it_be(:group) { create(:group, :shared_runners_disabled, :allow_descendants_override_disabled_shared_runners ) }
+ let_it_be(:sub_group) { create(:group, :shared_runners_disabled, :allow_descendants_override_disabled_shared_runners, parent: group) }
+ let_it_be(:project) { create(:project, shared_runners_enabled: true, group: sub_group) }
+
+ it 'disables allow project to override for descendants and disables project shared Runners' do
+ expect { subject }
+ .to not_change { group.reload.shared_runners_enabled }
+ .and change { group.reload.allow_descendants_override_disabled_shared_runners }.from(true).to(false)
+ .and not_change { sub_group.reload.shared_runners_enabled }
+ .and change { sub_group.reload.allow_descendants_override_disabled_shared_runners }.from(true).to(false)
+ .and change { project.reload.shared_runners_enabled }.from(true).to(false)
+ end
+ end
+
+ context 'top level group that has shared Runners enabled' do
+ let_it_be(:group) { create(:group, shared_runners_enabled: true) }
+ let_it_be(:sub_group) { create(:group, :shared_runners_disabled, parent: group) }
+ let_it_be(:project) { create(:project, shared_runners_enabled: false, group: sub_group) }
+
+ it 'results error and does not change config' do
+ expect { subject }
+ .to raise_error(described_class::UpdateSharedRunnersError, 'Shared Runners enabled')
+ .and not_change { group.reload.allow_descendants_override_disabled_shared_runners }
+ .and not_change { group.reload.shared_runners_enabled }
+ .and not_change { sub_group.reload.allow_descendants_override_disabled_shared_runners }
+ .and not_change { sub_group.reload.shared_runners_enabled }
+ .and not_change { project.reload.shared_runners_enabled }
+ end
+ end
+ end
end
diff --git a/spec/models/guest_spec.rb b/spec/models/guest_spec.rb
index 57eb077031c..975b64cb855 100644
--- a/spec/models/guest_spec.rb
+++ b/spec/models/guest_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Guest do
+RSpec.describe Guest do
let_it_be(:public_project, reload: true) { create(:project, :public) }
let_it_be(:private_project) { create(:project, :private) }
let_it_be(:internal_project) { create(:project, :internal) }
diff --git a/spec/models/hooks/active_hook_filter_spec.rb b/spec/models/hooks/active_hook_filter_spec.rb
index 1249c793f7f..1f693ce9fde 100644
--- a/spec/models/hooks/active_hook_filter_spec.rb
+++ b/spec/models/hooks/active_hook_filter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ActiveHookFilter do
+RSpec.describe ActiveHookFilter do
subject(:filter) { described_class.new(hook) }
describe '#matches?' do
diff --git a/spec/models/hooks/project_hook_spec.rb b/spec/models/hooks/project_hook_spec.rb
index ccf8171049d..69fbc4c3b4f 100644
--- a/spec/models/hooks/project_hook_spec.rb
+++ b/spec/models/hooks/project_hook_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ProjectHook do
+RSpec.describe ProjectHook do
describe 'associations' do
it { is_expected.to belong_to :project }
end
diff --git a/spec/models/hooks/service_hook_spec.rb b/spec/models/hooks/service_hook_spec.rb
index 936c2fbad27..f7045d7ac5e 100644
--- a/spec/models/hooks/service_hook_spec.rb
+++ b/spec/models/hooks/service_hook_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ServiceHook do
+RSpec.describe ServiceHook do
describe 'associations' do
it { is_expected.to belong_to :service }
end
diff --git a/spec/models/hooks/system_hook_spec.rb b/spec/models/hooks/system_hook_spec.rb
index 2e836c19e3c..e56d08c1847 100644
--- a/spec/models/hooks/system_hook_spec.rb
+++ b/spec/models/hooks/system_hook_spec.rb
@@ -2,7 +2,7 @@
require "spec_helper"
-describe SystemHook do
+RSpec.describe SystemHook do
context 'default attributes' do
let(:system_hook) { build(:system_hook) }
diff --git a/spec/models/hooks/web_hook_log_spec.rb b/spec/models/hooks/web_hook_log_spec.rb
index 128601794cf..8dd9cf9e84a 100644
--- a/spec/models/hooks/web_hook_log_spec.rb
+++ b/spec/models/hooks/web_hook_log_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe WebHookLog do
+RSpec.describe WebHookLog do
it { is_expected.to belong_to(:web_hook) }
it { is_expected.to serialize(:request_headers).as(Hash) }
diff --git a/spec/models/hooks/web_hook_spec.rb b/spec/models/hooks/web_hook_spec.rb
index 025c11d6407..3fc1ad6eb0d 100644
--- a/spec/models/hooks/web_hook_spec.rb
+++ b/spec/models/hooks/web_hook_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe WebHook do
+RSpec.describe WebHook do
let(:hook) { build(:project_hook) }
describe 'associations' do
diff --git a/spec/models/identity_spec.rb b/spec/models/identity_spec.rb
index 9f120775a3c..696d33b7beb 100644
--- a/spec/models/identity_spec.rb
+++ b/spec/models/identity_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Identity do
+RSpec.describe Identity do
describe 'relations' do
it { is_expected.to belong_to(:user) }
end
diff --git a/spec/models/import_export_upload_spec.rb b/spec/models/import_export_upload_spec.rb
index 18a714f4d98..46a611852ab 100644
--- a/spec/models/import_export_upload_spec.rb
+++ b/spec/models/import_export_upload_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ImportExportUpload do
+RSpec.describe ImportExportUpload do
subject { described_class.new(project: create(:project)) }
shared_examples 'stores the Import/Export file' do |method|
diff --git a/spec/models/import_failure_spec.rb b/spec/models/import_failure_spec.rb
index d286a4ad314..cdef125e890 100644
--- a/spec/models/import_failure_spec.rb
+++ b/spec/models/import_failure_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ImportFailure do
+RSpec.describe ImportFailure do
describe 'Scopes' do
let_it_be(:project) { create(:project) }
let_it_be(:correlation_id) { 'ABC' }
diff --git a/spec/models/incident_management/project_incident_management_setting_spec.rb b/spec/models/incident_management/project_incident_management_setting_spec.rb
index ac3f97e2d89..effd89e970c 100644
--- a/spec/models/incident_management/project_incident_management_setting_spec.rb
+++ b/spec/models/incident_management/project_incident_management_setting_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe IncidentManagement::ProjectIncidentManagementSetting do
+RSpec.describe IncidentManagement::ProjectIncidentManagementSetting do
let_it_be(:project) { create(:project, :repository, create_templates: :issue) }
describe 'Associations' do
@@ -108,4 +108,42 @@ describe IncidentManagement::ProjectIncidentManagementSetting do
it_behaves_like 'no content'
end
end
+
+ describe '#pagerduty_token' do
+ let(:active) { true }
+
+ subject do
+ create(:project_incident_management_setting, project: project, pagerduty_active: active, pagerduty_token: token)
+ end
+
+ context 'when token already set' do
+ let(:token) { SecureRandom.hex }
+
+ it 'reads the token' do
+ expect(subject.pagerduty_token).to eq(token)
+ expect(subject.encrypted_pagerduty_token).not_to be_nil
+ expect(subject.encrypted_pagerduty_token_iv).not_to be_nil
+ end
+ end
+
+ context 'when not set' do
+ let(:token) { nil }
+
+ context 'when PagerDuty webhook is active' do
+ it 'generates a token before validation' do
+ expect(subject).to be_valid
+ expect(subject.pagerduty_token).to match(/\A\h{32}\z/)
+ end
+ end
+
+ context 'when PagerDuty webhook is not active' do
+ let(:active) { false }
+
+ it 'does not generate a token before validation' do
+ expect(subject).to be_valid
+ expect(subject.pagerduty_token).to be_nil
+ end
+ end
+ end
+ end
end
diff --git a/spec/models/instance_configuration_spec.rb b/spec/models/instance_configuration_spec.rb
index 747e9dc2faa..383e548c324 100644
--- a/spec/models/instance_configuration_spec.rb
+++ b/spec/models/instance_configuration_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe InstanceConfiguration do
+RSpec.describe InstanceConfiguration do
context 'without cache' do
describe '#settings' do
describe '#ssh_algorithms_hashes' do
diff --git a/spec/models/integration_spec.rb b/spec/models/integration_spec.rb
index 3042fd15a7b..87ba0f3f7e6 100644
--- a/spec/models/integration_spec.rb
+++ b/spec/models/integration_spec.rb
@@ -3,8 +3,9 @@
require 'spec_helper'
RSpec.describe Integration do
- let(:project_1) { create(:project) }
- let(:project_2) { create(:project) }
+ let!(:project_1) { create(:project) }
+ let!(:project_2) { create(:project) }
+ let!(:project_3) { create(:project) }
let(:instance_integration) { create(:jira_service, :instance) }
before do
@@ -18,4 +19,10 @@ RSpec.describe Integration do
expect(Project.with_custom_integration_for(instance_integration)).to contain_exactly(project_2)
end
end
+
+ describe '#ids_without_integration' do
+ it 'returns projects ids without an integration' do
+ expect(Project.ids_without_integration(instance_integration, 100)).to contain_exactly(project_3.id)
+ end
+ end
end
diff --git a/spec/models/internal_id_spec.rb b/spec/models/internal_id_spec.rb
index 0dfb59cf43a..751e8724872 100644
--- a/spec/models/internal_id_spec.rb
+++ b/spec/models/internal_id_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe InternalId do
+RSpec.describe InternalId do
let(:project) { create(:project) }
let(:usage) { :issues }
let(:issue) { build(:issue, project: project) }
diff --git a/spec/models/issue/metrics_spec.rb b/spec/models/issue/metrics_spec.rb
index dc22d26e2f9..966e4321378 100644
--- a/spec/models/issue/metrics_spec.rb
+++ b/spec/models/issue/metrics_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Issue::Metrics do
+RSpec.describe Issue::Metrics do
let(:project) { create(:project) }
subject { create(:issue, project: project) }
diff --git a/spec/models/issue_assignee_spec.rb b/spec/models/issue_assignee_spec.rb
index 2d59ba15101..df8e91cd133 100644
--- a/spec/models/issue_assignee_spec.rb
+++ b/spec/models/issue_assignee_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe IssueAssignee do
+RSpec.describe IssueAssignee do
let(:issue) { create(:issue) }
subject { issue.issue_assignees.build(assignee: create(:user)) }
@@ -15,4 +15,37 @@ describe IssueAssignee do
describe 'validations' do
it { is_expected.to validate_uniqueness_of(:assignee).scoped_to(:issue_id) }
end
+
+ describe 'scopes' do
+ let_it_be(:user) { create(:user) }
+ let_it_be(:project) { create(:project) }
+ let_it_be(:project_issue) { create(:issue, project: project, assignee_ids: [user.id]) }
+
+ before do
+ issue.update!(assignee_ids: [user.id])
+ end
+
+ context 'in_projects' do
+ it 'returns issue assignees for given project' do
+ expect(IssueAssignee.count).to eq 2
+
+ assignees = IssueAssignee.in_projects([project])
+
+ expect(assignees.count).to eq 1
+ expect(assignees.first.user_id).to eq project_issue.issue_assignees.first.user_id
+ expect(assignees.first.issue_id).to eq project_issue.issue_assignees.first.issue_id
+ end
+ end
+
+ context 'on_issues' do
+ it 'returns issue assignees for given issues' do
+ expect(IssueAssignee.count).to eq 2
+
+ assignees = IssueAssignee.on_issues([project_issue])
+
+ expect(assignees.count).to eq 1
+ expect(assignees.first.issue_id).to eq project_issue.issue_assignees.first.issue_id
+ end
+ end
+ end
end
diff --git a/spec/models/issue_collection_spec.rb b/spec/models/issue_collection_spec.rb
index 7fc635f100f..d67bd8debce 100644
--- a/spec/models/issue_collection_spec.rb
+++ b/spec/models/issue_collection_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe IssueCollection do
+RSpec.describe IssueCollection do
let(:user) { create(:user) }
let(:project) { create(:project) }
let(:issue1) { create(:issue, project: project) }
diff --git a/spec/models/issue_spec.rb b/spec/models/issue_spec.rb
index 291cccd72db..80041d2e859 100644
--- a/spec/models/issue_spec.rb
+++ b/spec/models/issue_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Issue do
+RSpec.describe Issue do
include ExternalAuthorizationServiceHelpers
describe "Associations" do
@@ -95,29 +95,6 @@ describe Issue do
end
end
- describe 'locking' do
- using RSpec::Parameterized::TableSyntax
-
- where(:lock_version) do
- [
- [0],
- ["0"]
- ]
- end
-
- with_them do
- it 'works when an issue has a NULL lock_version' do
- issue = create(:issue)
-
- described_class.where(id: issue.id).update_all('lock_version = NULL')
-
- issue.update!(lock_version: lock_version, title: 'locking test')
-
- expect(issue.reload.title).to eq('locking test')
- end
- end
- end
-
describe '.simple_sorts' do
it 'includes all keys' do
expect(described_class.simple_sorts.keys).to include(
@@ -406,6 +383,22 @@ describe Issue do
end
end
+ describe '#from_service_desk?' do
+ subject { issue.from_service_desk? }
+
+ context 'when issue author is support bot' do
+ let(:issue) { create(:issue, author: ::User.support_bot) }
+
+ it { is_expected.to be_truthy }
+ end
+
+ context 'when issue author is not support bot' do
+ let(:issue) { create(:issue) }
+
+ it { is_expected.to be_falsey }
+ end
+ end
+
describe '#suggested_branch_name' do
let(:repository) { double }
@@ -1002,6 +995,16 @@ describe Issue do
end
end
+ describe '.service_desk' do
+ it 'returns the service desk issue' do
+ service_desk_issue = create(:issue, author: ::User.support_bot)
+ regular_issue = create(:issue)
+
+ expect(described_class.service_desk).to include(service_desk_issue)
+ expect(described_class.service_desk).not_to include(regular_issue)
+ end
+ end
+
it_behaves_like 'throttled touch' do
subject { create(:issue, updated_at: 1.hour.ago) }
end
diff --git a/spec/models/iteration_spec.rb b/spec/models/iteration_spec.rb
index ae14adf9106..ef638330208 100644
--- a/spec/models/iteration_spec.rb
+++ b/spec/models/iteration_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Iteration do
+RSpec.describe Iteration do
let_it_be(:project) { create(:project) }
let_it_be(:group) { create(:group) }
@@ -45,6 +45,14 @@ describe Iteration do
it { is_expected.to be_valid }
end
+ context 'when updated iteration dates overlap with its own dates' do
+ it 'is valid' do
+ existing_iteration.start_date = 5.days.from_now
+
+ expect(existing_iteration).to be_valid
+ end
+ end
+
context 'when dates overlap' do
context 'same group' do
context 'when start_date is in range' do
@@ -138,6 +146,25 @@ describe Iteration do
end
end
+ context 'time scopes' do
+ let_it_be(:project) { create(:project, :empty_repo) }
+ let_it_be(:iteration_1) { create(:iteration, :skip_future_date_validation, project: project, start_date: 3.days.ago, due_date: 1.day.from_now) }
+ let_it_be(:iteration_2) { create(:iteration, :skip_future_date_validation, project: project, start_date: 10.days.ago, due_date: 4.days.ago) }
+ let_it_be(:iteration_3) { create(:iteration, project: project, start_date: 4.days.from_now, due_date: 1.week.from_now) }
+
+ describe 'start_date_passed' do
+ it 'returns iterations where start_date is in the past but due_date is in the future' do
+ expect(described_class.start_date_passed).to contain_exactly(iteration_1)
+ end
+ end
+
+ describe 'due_date_passed' do
+ it 'returns iterations where due date is in the past' do
+ expect(described_class.due_date_passed).to contain_exactly(iteration_2)
+ end
+ end
+ end
+
describe '.within_timeframe' do
let_it_be(:now) { Time.current }
let_it_be(:project) { create(:project, :empty_repo) }
diff --git a/spec/models/jira_import_state_spec.rb b/spec/models/jira_import_state_spec.rb
index d2535636c63..e982b7353ba 100644
--- a/spec/models/jira_import_state_spec.rb
+++ b/spec/models/jira_import_state_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe JiraImportState do
+RSpec.describe JiraImportState do
describe "associations" do
it { is_expected.to belong_to(:project) }
it { is_expected.to belong_to(:user) }
diff --git a/spec/models/key_spec.rb b/spec/models/key_spec.rb
index 8cdedbcdedf..1e14864676c 100644
--- a/spec/models/key_spec.rb
+++ b/spec/models/key_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Key, :mailer do
+RSpec.describe Key, :mailer do
describe "Associations" do
it { is_expected.to belong_to(:user) }
end
diff --git a/spec/models/label_link_spec.rb b/spec/models/label_link_spec.rb
index 7a179dcb419..a95481f3083 100644
--- a/spec/models/label_link_spec.rb
+++ b/spec/models/label_link_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe LabelLink do
+RSpec.describe LabelLink do
it { expect(build(:label_link)).to be_valid }
it { is_expected.to belong_to(:label) }
diff --git a/spec/models/label_note_spec.rb b/spec/models/label_note_spec.rb
index 34560acfa9e..0bf202ce2b1 100644
--- a/spec/models/label_note_spec.rb
+++ b/spec/models/label_note_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe LabelNote do
+RSpec.describe LabelNote do
let_it_be(:project) { create(:project, :repository) }
let_it_be(:user) { create(:user) }
let_it_be(:label) { create(:label, project: project) }
diff --git a/spec/models/label_priority_spec.rb b/spec/models/label_priority_spec.rb
index 1a93468290f..db961d5a4e6 100644
--- a/spec/models/label_priority_spec.rb
+++ b/spec/models/label_priority_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe LabelPriority do
+RSpec.describe LabelPriority do
describe 'relationships' do
it { is_expected.to belong_to(:project) }
it { is_expected.to belong_to(:label) }
diff --git a/spec/models/label_spec.rb b/spec/models/label_spec.rb
index dc878c2d3c0..e1abfd9d8e5 100644
--- a/spec/models/label_spec.rb
+++ b/spec/models/label_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Label do
+RSpec.describe Label do
describe 'modules' do
it { is_expected.to include_module(Referable) }
it { is_expected.to include_module(Subscribable) }
diff --git a/spec/models/legacy_diff_discussion_spec.rb b/spec/models/legacy_diff_discussion_spec.rb
index 49ea319fbd1..4f90fe7d7f0 100644
--- a/spec/models/legacy_diff_discussion_spec.rb
+++ b/spec/models/legacy_diff_discussion_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe LegacyDiffDiscussion do
+RSpec.describe LegacyDiffDiscussion do
subject { create(:legacy_diff_note_on_merge_request).to_discussion }
describe '#reply_attributes' do
diff --git a/spec/models/lfs_download_object_spec.rb b/spec/models/lfs_download_object_spec.rb
index d7522fbb969..d1c323cd177 100644
--- a/spec/models/lfs_download_object_spec.rb
+++ b/spec/models/lfs_download_object_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe LfsDownloadObject do
+RSpec.describe LfsDownloadObject do
let(:oid) { 'cd293be6cea034bd45a0352775a219ef5dc7825ce55d1f7dae9762d80ce64411' }
let(:link) { 'http://www.example.com' }
let(:size) { 1 }
diff --git a/spec/models/lfs_file_lock_spec.rb b/spec/models/lfs_file_lock_spec.rb
index 0a47ded43fb..d3f79c7c7cf 100644
--- a/spec/models/lfs_file_lock_spec.rb
+++ b/spec/models/lfs_file_lock_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe LfsFileLock do
+RSpec.describe LfsFileLock do
let_it_be(:lfs_file_lock, reload: true) { create(:lfs_file_lock) }
subject { lfs_file_lock }
diff --git a/spec/models/lfs_object_spec.rb b/spec/models/lfs_object_spec.rb
index 09a64dabb08..36d45f17392 100644
--- a/spec/models/lfs_object_spec.rb
+++ b/spec/models/lfs_object_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe LfsObject do
+RSpec.describe LfsObject do
context 'scopes' do
describe '.not_existing_in_project' do
it 'contains only lfs objects not linked to the project' do
diff --git a/spec/models/lfs_objects_project_spec.rb b/spec/models/lfs_objects_project_spec.rb
index 31300828a43..71009a6f28f 100644
--- a/spec/models/lfs_objects_project_spec.rb
+++ b/spec/models/lfs_objects_project_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe LfsObjectsProject do
+RSpec.describe LfsObjectsProject do
let_it_be(:project) { create(:project) }
subject do
diff --git a/spec/models/license_template_spec.rb b/spec/models/license_template_spec.rb
index 7037277e580..515f728f515 100644
--- a/spec/models/license_template_spec.rb
+++ b/spec/models/license_template_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe LicenseTemplate do
+RSpec.describe LicenseTemplate do
describe '#content' do
it 'calls a proc exactly once if provided' do
content_proc = -> { 'bar' }
diff --git a/spec/models/list_spec.rb b/spec/models/list_spec.rb
index bc9124e73af..37158584062 100644
--- a/spec/models/list_spec.rb
+++ b/spec/models/list_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe List do
+RSpec.describe List do
it_behaves_like 'having unique enum values'
describe 'relationships' do
diff --git a/spec/models/list_user_preference_spec.rb b/spec/models/list_user_preference_spec.rb
index 10a7bf41f4e..fde0481e301 100644
--- a/spec/models/list_user_preference_spec.rb
+++ b/spec/models/list_user_preference_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ListUserPreference do
+RSpec.describe ListUserPreference do
let_it_be(:user) { create(:user) }
let_it_be(:list) { create(:list) }
diff --git a/spec/models/member_spec.rb b/spec/models/member_spec.rb
index 7c40bb24b56..f155c240fb2 100644
--- a/spec/models/member_spec.rb
+++ b/spec/models/member_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Member do
+RSpec.describe Member do
include ExclusiveLeaseHelpers
using RSpec::Parameterized::TableSyntax
@@ -88,6 +88,28 @@ describe Member do
expect(child_member).to be_valid
end
end
+
+ context 'project bots' do
+ let_it_be(:project_bot) { create(:user, :project_bot) }
+ let(:new_member) { build(:project_member, user_id: project_bot.id) }
+
+ context 'not a member of any group or project' do
+ it 'is valid' do
+ expect(new_member).to be_valid
+ end
+ end
+
+ context 'already member of a project' do
+ before do
+ unrelated_project = create(:project)
+ unrelated_project.add_maintainer(project_bot)
+ end
+
+ it 'is not valid' do
+ expect(new_member).not_to be_valid
+ end
+ end
+ end
end
describe 'Scopes & finders' do
diff --git a/spec/models/members/group_member_spec.rb b/spec/models/members/group_member_spec.rb
index fdb71b7ec7d..9af620e70a5 100644
--- a/spec/models/members/group_member_spec.rb
+++ b/spec/models/members/group_member_spec.rb
@@ -2,52 +2,20 @@
require 'spec_helper'
-describe GroupMember do
+RSpec.describe GroupMember do
context 'scopes' do
- shared_examples '.count_users_by_group_id' do
- it 'counts users by group ID' do
- user_1 = create(:user)
- user_2 = create(:user)
- group_1 = create(:group)
- group_2 = create(:group)
-
- group_1.add_owner(user_1)
- group_1.add_owner(user_2)
- group_2.add_owner(user_1)
-
- expect(described_class.count_users_by_group_id).to eq(group_1.id => 2,
- group_2.id => 1)
- end
- end
-
- describe '.count_users_by_group_id with optimized_count_users_by_group_id feature flag on' do
- before do
- stub_feature_flags(optimized_count_users_by_group_id: true)
- end
-
- it_behaves_like '.count_users_by_group_id'
-
- it 'does not JOIN users' do
- scope = described_class.all
- expect(scope).not_to receive(:joins).with(:user)
-
- scope.count_users_by_group_id
- end
- end
-
- describe '.count_users_by_group_id with optimized_count_users_by_group_id feature flag off' do
- before do
- stub_feature_flags(optimized_count_users_by_group_id: false)
- end
-
- it_behaves_like '.count_users_by_group_id'
-
- it 'does JOIN users' do
- scope = described_class.all
- expect(scope).to receive(:joins).with(:user).and_call_original
-
- scope.count_users_by_group_id
- end
+ it 'counts users by group ID' do
+ user_1 = create(:user)
+ user_2 = create(:user)
+ group_1 = create(:group)
+ group_2 = create(:group)
+
+ group_1.add_owner(user_1)
+ group_1.add_owner(user_2)
+ group_2.add_owner(user_1)
+
+ expect(described_class.count_users_by_group_id).to eq(group_1.id => 2,
+ group_2.id => 1)
end
describe '.of_ldap_type' do
diff --git a/spec/models/members/project_member_spec.rb b/spec/models/members/project_member_spec.rb
index fdb9457b211..f25f8933184 100644
--- a/spec/models/members/project_member_spec.rb
+++ b/spec/models/members/project_member_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ProjectMember do
+RSpec.describe ProjectMember do
describe 'associations' do
it { is_expected.to belong_to(:project).with_foreign_key(:source_id) }
end
diff --git a/spec/models/merge_request/metrics_spec.rb b/spec/models/merge_request/metrics_spec.rb
index bd97cabc11e..4d9e768ecc6 100644
--- a/spec/models/merge_request/metrics_spec.rb
+++ b/spec/models/merge_request/metrics_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe MergeRequest::Metrics do
+RSpec.describe MergeRequest::Metrics do
describe 'associations' do
it { is_expected.to belong_to(:merge_request) }
it { is_expected.to belong_to(:latest_closed_by).class_name('User') }
diff --git a/spec/models/merge_request_assignee_spec.rb b/spec/models/merge_request_assignee_spec.rb
index d6aab15d990..d287392bf7f 100644
--- a/spec/models/merge_request_assignee_spec.rb
+++ b/spec/models/merge_request_assignee_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe MergeRequestAssignee do
+RSpec.describe MergeRequestAssignee do
let(:merge_request) { create(:merge_request) }
subject { merge_request.merge_request_assignees.build(assignee: create(:user)) }
@@ -15,4 +15,26 @@ describe MergeRequestAssignee do
describe 'validations' do
it { is_expected.to validate_uniqueness_of(:assignee).scoped_to(:merge_request_id) }
end
+
+ describe 'scopes' do
+ let_it_be(:user) { create(:user) }
+ let_it_be(:project) { create(:project, :repository) }
+ let_it_be(:project_merge_request) { create(:merge_request, target_project: project, source_project: project, assignee_ids: [user.id]) }
+
+ before do
+ merge_request.update!(assignee_ids: [user.id])
+ end
+
+ context 'in_projects' do
+ it 'returns issue assignees for given project' do
+ expect(MergeRequestAssignee.count).to eq 2
+
+ assignees = MergeRequestAssignee.in_projects([project])
+
+ expect(assignees.count).to eq 1
+ expect(assignees.first.user_id).to eq project_merge_request.merge_request_assignees.first.user_id
+ expect(assignees.first.merge_request_id).to eq project_merge_request.merge_request_assignees.first.merge_request_id
+ end
+ end
+ end
end
diff --git a/spec/models/merge_request_context_commit_diff_file_spec.rb b/spec/models/merge_request_context_commit_diff_file_spec.rb
index 37d44662326..7a098639b57 100644
--- a/spec/models/merge_request_context_commit_diff_file_spec.rb
+++ b/spec/models/merge_request_context_commit_diff_file_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe MergeRequestContextCommitDiffFile do
+RSpec.describe MergeRequestContextCommitDiffFile do
describe 'associations' do
it { is_expected.to belong_to(:merge_request_context_commit) }
end
diff --git a/spec/models/merge_request_context_commit_spec.rb b/spec/models/merge_request_context_commit_spec.rb
index 5a1bf9874ac..29ef2fab9ad 100644
--- a/spec/models/merge_request_context_commit_spec.rb
+++ b/spec/models/merge_request_context_commit_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe MergeRequestContextCommit do
+RSpec.describe MergeRequestContextCommit do
let(:merge_request) { create(:merge_request) }
let(:project) { merge_request.project }
let(:raw_repository) { project.repository.raw_repository }
diff --git a/spec/models/merge_request_diff_commit_spec.rb b/spec/models/merge_request_diff_commit_spec.rb
index 62430b08c5c..5ea0145e60f 100644
--- a/spec/models/merge_request_diff_commit_spec.rb
+++ b/spec/models/merge_request_diff_commit_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe MergeRequestDiffCommit do
+RSpec.describe MergeRequestDiffCommit do
let(:merge_request) { create(:merge_request) }
let(:project) { merge_request.project }
diff --git a/spec/models/merge_request_diff_file_spec.rb b/spec/models/merge_request_diff_file_spec.rb
index 40f7be5dc8f..25971f63338 100644
--- a/spec/models/merge_request_diff_file_spec.rb
+++ b/spec/models/merge_request_diff_file_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe MergeRequestDiffFile do
+RSpec.describe MergeRequestDiffFile do
it_behaves_like 'a BulkInsertSafe model', MergeRequestDiffFile do
let(:valid_items_for_bulk_insertion) { build_list(:merge_request_diff_file, 10) }
let(:invalid_items_for_bulk_insertion) { [] } # class does not have any validations defined
diff --git a/spec/models/merge_request_diff_spec.rb b/spec/models/merge_request_diff_spec.rb
index 0839dde696a..d153ccedf8c 100644
--- a/spec/models/merge_request_diff_spec.rb
+++ b/spec/models/merge_request_diff_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe MergeRequestDiff do
+RSpec.describe MergeRequestDiff do
using RSpec::Parameterized::TableSyntax
include RepoHelpers
@@ -162,7 +162,8 @@ describe MergeRequestDiff do
let(:uploader) { ExternalDiffUploader }
let(:file_store) { uploader::Store::LOCAL }
let(:remote_store) { uploader::Store::REMOTE }
- let(:diff) { create(:merge_request).merge_request_diff }
+ let(:merge_request) { create(:merge_request) }
+ let(:diff) { merge_request.merge_request_diff }
it 'converts from in-database to external file storage' do
expect(diff).not_to be_stored_externally
@@ -177,6 +178,30 @@ describe MergeRequestDiff do
expect(diff.external_diff_store).to eq(file_store)
end
+ it 'safely handles a transaction error when migrating to external storage' do
+ expect(diff).not_to be_stored_externally
+ expect(diff.external_diff).not_to be_exists
+
+ stub_external_diffs_setting(enabled: true)
+
+ expect(diff).not_to receive(:save!)
+ expect(Gitlab::Database)
+ .to receive(:bulk_insert)
+ .with('merge_request_diff_files', anything)
+ .and_raise(ActiveRecord::Rollback)
+
+ expect { diff.migrate_files_to_external_storage! }.not_to change(diff, :merge_request_diff_files)
+
+ diff.reload
+
+ expect(diff).not_to be_stored_externally
+
+ # The diff is written outside of the transaction, which is desirable to
+ # avoid long transaction times when migrating, but it does mean we can
+ # leave the file dangling on failure
+ expect(diff.external_diff).to be_exists
+ end
+
it 'converts from in-database to external object storage' do
expect(diff).not_to be_stored_externally
@@ -209,6 +234,33 @@ describe MergeRequestDiff do
diff.migrate_files_to_external_storage!
end
+
+ context 'diff adds an empty file' do
+ let(:project) { create(:project, :test_repo) }
+ let(:merge_request) do
+ create(
+ :merge_request,
+ source_project: project,
+ target_project: project,
+ source_branch: 'empty-file',
+ target_branch: 'master'
+ )
+ end
+
+ it 'migrates the diff to object storage' do
+ create_file_in_repo(project, 'master', 'empty-file', 'empty-file', '')
+
+ expect(diff).not_to be_stored_externally
+
+ stub_external_diffs_setting(enabled: true)
+ stub_external_diffs_object_storage(uploader, direct_upload: true)
+
+ diff.migrate_files_to_external_storage!
+
+ expect(diff).to be_stored_externally
+ expect(diff.external_diff_store).to eq(remote_store)
+ end
+ end
end
describe '#migrate_files_to_database!' do
@@ -476,7 +528,7 @@ describe MergeRequestDiff do
include_examples 'merge request diffs'
end
- describe 'external diffs always enabled' do
+ describe 'external diffs on disk always enabled' do
before do
stub_external_diffs_setting(enabled: true, when: 'always')
end
@@ -484,6 +536,63 @@ describe MergeRequestDiff do
include_examples 'merge request diffs'
end
+ describe 'external diffs in object storage always enabled' do
+ let(:uploader) { ExternalDiffUploader }
+ let(:remote_store) { uploader::Store::REMOTE }
+
+ subject(:diff) { merge_request.merge_request_diff }
+
+ before do
+ stub_external_diffs_setting(enabled: true, when: 'always')
+ stub_external_diffs_object_storage(uploader, direct_upload: true)
+ end
+
+ # We can't use the full merge request diffs shared examples here because
+ # reading from the fake object store isn't implemented yet
+
+ context 'empty diff' do
+ let(:merge_request) { create(:merge_request, :without_diffs) }
+
+ it 'creates an empty diff' do
+ expect(diff.state).to eq('empty')
+ expect(diff).not_to be_stored_externally
+ end
+ end
+
+ context 'normal diff' do
+ let(:merge_request) { create(:merge_request) }
+
+ it 'creates a diff in object storage' do
+ expect(diff).to be_stored_externally
+ expect(diff.state).to eq('collected')
+ expect(diff.external_diff_store).to eq(remote_store)
+ end
+ end
+
+ context 'diff adding an empty file' do
+ let(:project) { create(:project, :test_repo) }
+ let(:merge_request) do
+ create(
+ :merge_request,
+ source_project: project,
+ target_project: project,
+ source_branch: 'empty-file',
+ target_branch: 'master'
+ )
+ end
+
+ it 'creates a diff in object storage' do
+ create_file_in_repo(project, 'master', 'empty-file', 'empty-file', '')
+
+ diff.reload
+
+ expect(diff).to be_stored_externally
+ expect(diff.state).to eq('collected')
+ expect(diff.external_diff_store).to eq(remote_store)
+ end
+ end
+ end
+
describe 'exernal diffs enabled for outdated diffs' do
before do
stub_external_diffs_setting(enabled: true, when: 'outdated')
diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb
index 582cdc7b419..06febddef0c 100644
--- a/spec/models/merge_request_spec.rb
+++ b/spec/models/merge_request_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe MergeRequest do
+RSpec.describe MergeRequest do
include RepoHelpers
include ProjectForksHelper
include ReactiveCachingHelpers
@@ -12,6 +12,8 @@ describe MergeRequest do
subject { create(:merge_request) }
describe 'associations' do
+ subject { build_stubbed(:merge_request) }
+
it { is_expected.to belong_to(:target_project).class_name('Project') }
it { is_expected.to belong_to(:source_project).class_name('Project') }
it { is_expected.to belong_to(:merge_user).class_name("User") }
@@ -55,29 +57,6 @@ describe MergeRequest do
end
end
- describe 'locking' do
- using RSpec::Parameterized::TableSyntax
-
- where(:lock_version) do
- [
- [0],
- ["0"]
- ]
- end
-
- with_them do
- it 'works when a merge request has a NULL lock_version' do
- merge_request = create(:merge_request)
-
- described_class.where(id: merge_request.id).update_all('lock_version = NULL')
-
- merge_request.update!(lock_version: lock_version, title: 'locking test')
-
- expect(merge_request.reload.title).to eq('locking test')
- end
- end
- end
-
describe '#squash_in_progress?' do
let(:repo_path) do
Gitlab::GitalyClient::StorageSettings.allow_disk_access do
@@ -195,6 +174,8 @@ describe MergeRequest do
end
describe 'validation' do
+ subject { build_stubbed(:merge_request) }
+
it { is_expected.to validate_presence_of(:target_branch) }
it { is_expected.to validate_presence_of(:source_branch) }
@@ -280,6 +261,21 @@ describe MergeRequest do
expect(MergeRequest::Metrics.count).to eq(1)
end
+
+ it 'does not create duplicated metrics records when MR is concurrently updated' do
+ merge_request = create(:merge_request)
+
+ merge_request.metrics.destroy
+
+ instance1 = MergeRequest.find(merge_request.id)
+ instance2 = MergeRequest.find(merge_request.id)
+
+ instance1.ensure_metrics
+ instance2.ensure_metrics
+
+ metrics_records = MergeRequest::Metrics.where(merge_request_id: merge_request.id)
+ expect(metrics_records.size).to eq(1)
+ end
end
end
@@ -1092,13 +1088,43 @@ describe MergeRequest do
end
describe "#work_in_progress?" do
- ['WIP ', 'WIP:', 'WIP: ', '[WIP]', '[WIP] ', ' [WIP] WIP [WIP] WIP: WIP '].each do |wip_prefix|
+ subject { build_stubbed(:merge_request) }
+
+ [
+ 'WIP:', 'WIP: ', '[WIP]', '[WIP] ', ' [WIP] WIP: [WIP] WIP:',
+ 'draft:', 'Draft: ', '[Draft]', '[DRAFT] ', 'Draft - '
+ ].each do |wip_prefix|
it "detects the '#{wip_prefix}' prefix" do
subject.title = "#{wip_prefix}#{subject.title}"
+
expect(subject.work_in_progress?).to eq true
end
end
+ it "detects merge request title just saying 'wip'" do
+ subject.title = "wip"
+
+ expect(subject.work_in_progress?).to eq true
+ end
+
+ it "detects merge request title just saying 'draft'" do
+ subject.title = "draft"
+
+ expect(subject.work_in_progress?).to eq true
+ end
+
+ it 'does not detect WIP in the middle of the title' do
+ subject.title = 'Something with WIP in the middle'
+
+ expect(subject.work_in_progress?).to eq false
+ end
+
+ it 'does not detect Draft in the middle of the title' do
+ subject.title = 'Something with Draft in the middle'
+
+ expect(subject.work_in_progress?).to eq false
+ end
+
it "doesn't detect WIP for words starting with WIP" do
subject.title = "Wipwap #{subject.title}"
expect(subject.work_in_progress?).to eq false
@@ -1115,7 +1141,12 @@ describe MergeRequest do
end
describe "#wipless_title" do
- ['WIP ', 'WIP:', 'WIP: ', '[WIP]', '[WIP] ', '[WIP] WIP [WIP] WIP: WIP '].each do |wip_prefix|
+ subject { build_stubbed(:merge_request) }
+
+ [
+ 'WIP:', 'WIP: ', '[WIP]', '[WIP] ', '[WIP] WIP: [WIP] WIP:',
+ 'draft:', 'Draft: ', '[Draft]', '[DRAFT] ', 'Draft - '
+ ].each do |wip_prefix|
it "removes the '#{wip_prefix}' prefix" do
wipless_title = subject.title
subject.title = "#{wip_prefix}#{subject.title}"
@@ -1133,14 +1164,14 @@ describe MergeRequest do
end
describe "#wip_title" do
- it "adds the WIP: prefix to the title" do
- wip_title = "WIP: #{subject.title}"
+ it "adds the Draft: prefix to the title" do
+ wip_title = "Draft: #{subject.title}"
expect(subject.wip_title).to eq wip_title
end
- it "does not add the WIP: prefix multiple times" do
- wip_title = "WIP: #{subject.title}"
+ it "does not add the Draft: prefix multiple times" do
+ wip_title = "Draft: #{subject.title}"
subject.title = subject.wip_title
subject.title = subject.wip_title
@@ -1170,6 +1201,12 @@ describe MergeRequest do
expect(subject.can_remove_source_branch?(user)).to be_falsey
end
+ it "can't be removed because source project has been deleted" do
+ subject.source_project = nil
+
+ expect(subject.can_remove_source_branch?(user)).to be_falsey
+ end
+
it "can't remove a root ref" do
subject.update(source_branch: 'master', target_branch: 'feature')
@@ -1196,6 +1233,29 @@ describe MergeRequest do
end
end
+ describe "#source_branch_exists?" do
+ let(:merge_request) { subject }
+ let(:repository) { merge_request.source_project.repository }
+
+ context 'when the source project is set' do
+ it 'memoizes the value and returns the result' do
+ expect(repository).to receive(:branch_exists?).once.with(merge_request.source_branch).and_return(true)
+
+ 2.times { expect(merge_request.source_branch_exists?).to eq(true) }
+ end
+ end
+
+ context 'when the source project is not set' do
+ before do
+ merge_request.source_project = nil
+ end
+
+ it 'returns false' do
+ expect(merge_request.source_branch_exists?).to eq(false)
+ end
+ end
+ end
+
describe '#default_merge_commit_message' do
it 'includes merge information as the title' do
request = build(:merge_request, source_branch: 'source', target_branch: 'target')
@@ -2426,7 +2486,7 @@ describe MergeRequest do
context 'when working in progress' do
before do
- subject.title = 'WIP MR'
+ subject.title = '[Draft] MR'
end
it 'returns false' do
diff --git a/spec/models/metrics/dashboard/annotation_spec.rb b/spec/models/metrics/dashboard/annotation_spec.rb
index 3cba31ffdfe..bd4baeb8851 100644
--- a/spec/models/metrics/dashboard/annotation_spec.rb
+++ b/spec/models/metrics/dashboard/annotation_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Metrics::Dashboard::Annotation do
+RSpec.describe Metrics::Dashboard::Annotation do
using RSpec::Parameterized::TableSyntax
describe 'associations' do
diff --git a/spec/models/metrics/users_starred_dashboard_spec.rb b/spec/models/metrics/users_starred_dashboard_spec.rb
index 6cb14ae569e..c89344c0a1c 100644
--- a/spec/models/metrics/users_starred_dashboard_spec.rb
+++ b/spec/models/metrics/users_starred_dashboard_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Metrics::UsersStarredDashboard do
+RSpec.describe Metrics::UsersStarredDashboard do
describe 'associations' do
it { is_expected.to belong_to(:project).inverse_of(:metrics_users_starred_dashboards) }
it { is_expected.to belong_to(:user).inverse_of(:metrics_users_starred_dashboards) }
diff --git a/spec/models/milestone_note_spec.rb b/spec/models/milestone_note_spec.rb
index aad65cf0346..db1a7ca05f8 100644
--- a/spec/models/milestone_note_spec.rb
+++ b/spec/models/milestone_note_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe MilestoneNote do
+RSpec.describe MilestoneNote do
describe '.from_event' do
let(:author) { create(:user) }
let(:project) { create(:project, :repository) }
@@ -11,9 +11,7 @@ describe MilestoneNote do
subject { described_class.from_event(event, resource: noteable, resource_parent: project) }
- it_behaves_like 'a system note', exclude_project: true do
- let(:action) { 'milestone' }
- end
+ it_behaves_like 'a synthetic note', 'milestone'
context 'with a remove milestone event' do
let(:milestone) { create(:milestone) }
diff --git a/spec/models/milestone_release_spec.rb b/spec/models/milestone_release_spec.rb
index 28cec7bbc17..3c781545d8a 100644
--- a/spec/models/milestone_release_spec.rb
+++ b/spec/models/milestone_release_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe MilestoneRelease do
+RSpec.describe MilestoneRelease do
let(:project) { create(:project) }
let(:release) { create(:release, project: project) }
let(:milestone) { create(:milestone, project: project) }
diff --git a/spec/models/milestone_spec.rb b/spec/models/milestone_spec.rb
index 33f84da27f6..b52b035e130 100644
--- a/spec/models/milestone_spec.rb
+++ b/spec/models/milestone_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Milestone do
+RSpec.describe Milestone do
it_behaves_like 'a timebox', :milestone
describe 'MilestoneStruct#serializable_hash' do
diff --git a/spec/models/namespace/root_storage_size_spec.rb b/spec/models/namespace/root_storage_size_spec.rb
deleted file mode 100644
index a8048b7f637..00000000000
--- a/spec/models/namespace/root_storage_size_spec.rb
+++ /dev/null
@@ -1,67 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe Namespace::RootStorageSize, type: :model do
- let(:namespace) { create(:namespace) }
- let(:current_size) { 50.megabytes }
- let(:limit) { 100 }
- let(:model) { described_class.new(namespace) }
- let(:create_statistics) { create(:namespace_root_storage_statistics, namespace: namespace, storage_size: current_size)}
-
- before do
- create_statistics
-
- stub_application_setting(namespace_storage_size_limit: limit)
- end
-
- describe '#above_size_limit?' do
- subject { model.above_size_limit? }
-
- context 'when limit is 0' do
- let(:limit) { 0 }
-
- it { is_expected.to eq(false) }
- end
-
- context 'when below limit' do
- it { is_expected.to eq(false) }
- end
-
- context 'when above limit' do
- let(:current_size) { 101.megabytes }
-
- it { is_expected.to eq(true) }
- end
- end
-
- describe '#usage_ratio' do
- subject { model.usage_ratio }
-
- it { is_expected.to eq(0.5) }
-
- context 'when limit is 0' do
- let(:limit) { 0 }
-
- it { is_expected.to eq(0) }
- end
-
- context 'when there are no root_storage_statistics' do
- let(:create_statistics) { nil }
-
- it { is_expected.to eq(0) }
- end
- end
-
- describe '#current_size' do
- subject { model.current_size }
-
- it { is_expected.to eq(current_size) }
- end
-
- describe '#limit' do
- subject { model.limit }
-
- it { is_expected.to eq(limit.megabytes) }
- end
-end
diff --git a/spec/models/namespace/root_storage_statistics_spec.rb b/spec/models/namespace/root_storage_statistics_spec.rb
index 9e12831a704..ce6f875ee09 100644
--- a/spec/models/namespace/root_storage_statistics_spec.rb
+++ b/spec/models/namespace/root_storage_statistics_spec.rb
@@ -43,6 +43,7 @@ RSpec.describe Namespace::RootStorageStatistics, type: :model do
total_build_artifacts_size = stat1.build_artifacts_size + stat2.build_artifacts_size
total_packages_size = stat1.packages_size + stat2.packages_size
total_storage_size = stat1.storage_size + stat2.storage_size
+ total_snippets_size = stat1.snippets_size + stat2.snippets_size
expect(root_storage_statistics.repository_size).to eq(total_repository_size)
expect(root_storage_statistics.wiki_size).to eq(total_wiki_size)
@@ -50,6 +51,7 @@ RSpec.describe Namespace::RootStorageStatistics, type: :model do
expect(root_storage_statistics.build_artifacts_size).to eq(total_build_artifacts_size)
expect(root_storage_statistics.packages_size).to eq(total_packages_size)
expect(root_storage_statistics.storage_size).to eq(total_storage_size)
+ expect(root_storage_statistics.snippets_size).to eq(total_snippets_size)
end
it 'works when there are no projects' do
@@ -64,10 +66,20 @@ RSpec.describe Namespace::RootStorageStatistics, type: :model do
expect(root_storage_statistics.build_artifacts_size).to eq(0)
expect(root_storage_statistics.packages_size).to eq(0)
expect(root_storage_statistics.storage_size).to eq(0)
+ expect(root_storage_statistics.snippets_size).to eq(0)
+ end
+ end
+
+ shared_examples 'does not include personal snippets' do
+ specify do
+ expect(root_storage_statistics).not_to receive(:from_personal_snippets)
+
+ root_storage_statistics.recalculate!
end
end
it_behaves_like 'data refresh'
+ it_behaves_like 'does not include personal snippets'
context 'with subgroups' do
let(:subgroup1) { create(:group, parent: namespace)}
@@ -77,12 +89,45 @@ RSpec.describe Namespace::RootStorageStatistics, type: :model do
let(:project2) { create(:project, namespace: subgroup2) }
it_behaves_like 'data refresh'
+ it_behaves_like 'does not include personal snippets'
end
context 'with a personal namespace' do
- let(:namespace) { create(:user).namespace }
+ let_it_be(:user) { create(:user) }
+ let(:namespace) { user.namespace }
it_behaves_like 'data refresh'
+
+ context 'when user has personal snippets' do
+ let(:total_project_snippets_size) { stat1.snippets_size + stat2.snippets_size }
+
+ it 'aggregates personal and project snippets size' do
+ # This is just a a snippet authored by other user
+ # to ensure we only pick snippets from the namespace
+ # user
+ create(:personal_snippet, :repository).statistics.refresh!
+
+ snippets = create_list(:personal_snippet, 3, :repository, author: user)
+ snippets.each { |s| s.statistics.refresh! }
+
+ total_personal_snippets_size = snippets.map { |s| s.statistics.repository_size }.sum
+
+ root_storage_statistics.recalculate!
+
+ expect(root_storage_statistics.snippets_size).to eq(total_personal_snippets_size + total_project_snippets_size)
+ end
+
+ context 'when personal snippets do not have statistics' do
+ it 'does not raise any error' do
+ snippets = create_list(:personal_snippet, 2, :repository, author: user)
+ snippets.last.statistics.refresh!
+
+ root_storage_statistics.recalculate!
+
+ expect(root_storage_statistics.snippets_size).to eq(total_project_snippets_size + snippets.last.statistics.repository_size)
+ end
+ end
+ end
end
end
end
diff --git a/spec/models/namespace/traversal_hierarchy_spec.rb b/spec/models/namespace/traversal_hierarchy_spec.rb
new file mode 100644
index 00000000000..71b0e974106
--- /dev/null
+++ b/spec/models/namespace/traversal_hierarchy_spec.rb
@@ -0,0 +1,63 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Namespace::TraversalHierarchy, type: :model do
+ let_it_be(:root, reload: true) { create(:namespace, :with_hierarchy) }
+
+ describe '.for_namespace' do
+ let(:hierarchy) { described_class.for_namespace(namespace) }
+
+ context 'with root group' do
+ let(:namespace) { root }
+
+ it { expect(hierarchy.root).to eq root }
+ end
+
+ context 'with child group' do
+ let(:namespace) { root.children.first.children.first }
+
+ it { expect(hierarchy.root).to eq root }
+ end
+
+ context 'with group outside of hierarchy' do
+ let(:namespace) { create(:namespace) }
+
+ it { expect(hierarchy.root).not_to eq root }
+ end
+ end
+
+ describe '.new' do
+ let(:hierarchy) { described_class.new(namespace) }
+
+ context 'with root group' do
+ let(:namespace) { root }
+
+ it { expect(hierarchy.root).to eq root }
+ end
+
+ context 'with child group' do
+ let(:namespace) { root.children.first }
+
+ it { expect { hierarchy }.to raise_error(StandardError, 'Must specify a root node') }
+ end
+ end
+
+ describe '#incorrect_traversal_ids' do
+ subject { described_class.new(root).incorrect_traversal_ids }
+
+ it { is_expected.to match_array Namespace.all }
+ end
+
+ describe '#sync_traversal_ids!' do
+ let(:hierarchy) { described_class.new(root) }
+
+ before do
+ hierarchy.sync_traversal_ids!
+ root.reload
+ end
+
+ it_behaves_like 'hierarchy with traversal_ids'
+ it { expect(hierarchy.incorrect_traversal_ids).to be_empty }
+ end
+end
diff --git a/spec/models/namespace_setting_spec.rb b/spec/models/namespace_setting_spec.rb
new file mode 100644
index 00000000000..257d78dfa2c
--- /dev/null
+++ b/spec/models/namespace_setting_spec.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe NamespaceSetting, type: :model do
+ it { is_expected.to belong_to(:namespace) }
+end
diff --git a/spec/models/namespace_spec.rb b/spec/models/namespace_spec.rb
index ed7ef8b2b8e..ad4c8448745 100644
--- a/spec/models/namespace_spec.rb
+++ b/spec/models/namespace_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Namespace do
+RSpec.describe Namespace do
include ProjectForksHelper
include GitHelpers
@@ -17,6 +17,8 @@ describe Namespace do
it { is_expected.to have_many :children }
it { is_expected.to have_one :root_storage_statistics }
it { is_expected.to have_one :aggregation_schedule }
+ it { is_expected.to have_one :namespace_settings }
+ it { is_expected.to have_many :custom_emoji }
end
describe 'validations' do
@@ -64,6 +66,36 @@ describe Namespace do
it { expect(group).to be_valid }
end
end
+
+ describe '1 char path length' do
+ it 'does not allow to create one' do
+ namespace = build(:namespace, path: 'j')
+
+ expect(namespace).not_to be_valid
+ expect(namespace.errors[:path].first).to eq('is too short (minimum is 2 characters)')
+ end
+
+ it 'does not allow to update one' do
+ namespace = create(:namespace)
+ namespace.update(path: 'j')
+
+ expect(namespace).not_to be_valid
+ expect(namespace.errors[:path].first).to eq('is too short (minimum is 2 characters)')
+ end
+
+ it 'allows updating other attributes for existing record' do
+ namespace = build(:namespace, path: 'j')
+ namespace.save(validate: false)
+ namespace.reload
+
+ expect(namespace.path).to eq('j')
+
+ namespace.update(name: 'something new')
+
+ expect(namespace).to be_valid
+ expect(namespace.name).to eq('something new')
+ end
+ end
end
describe 'delegate' do
@@ -153,7 +185,8 @@ describe Namespace do
wiki_size: 505,
lfs_objects_size: 202,
build_artifacts_size: 303,
- packages_size: 404))
+ packages_size: 404,
+ snippets_size: 605))
end
let(:project2) do
@@ -164,7 +197,8 @@ describe Namespace do
wiki_size: 50,
lfs_objects_size: 20,
build_artifacts_size: 30,
- packages_size: 40))
+ packages_size: 40,
+ snippets_size: 60))
end
it "sums all project storage counters in the namespace" do
@@ -172,12 +206,13 @@ describe Namespace do
project2
statistics = described_class.with_statistics.find(namespace.id)
- expect(statistics.storage_size).to eq 1665
+ expect(statistics.storage_size).to eq 2330
expect(statistics.repository_size).to eq 111
expect(statistics.wiki_size).to eq 555
expect(statistics.lfs_objects_size).to eq 222
expect(statistics.build_artifacts_size).to eq 333
expect(statistics.packages_size).to eq 444
+ expect(statistics.snippets_size).to eq 665
end
it "correctly handles namespaces without projects" do
@@ -189,6 +224,7 @@ describe Namespace do
expect(statistics.lfs_objects_size).to eq 0
expect(statistics.build_artifacts_size).to eq 0
expect(statistics.packages_size).to eq 0
+ expect(statistics.snippets_size).to eq 0
end
end
@@ -849,8 +885,13 @@ describe Namespace do
end
describe '#root_ancestor' do
+ let!(:root_group) { create(:group) }
+
+ it 'returns root_ancestor for root group without a query' do
+ expect { root_group.root_ancestor }.not_to exceed_query_limit(0)
+ end
+
it 'returns the top most ancestor' do
- root_group = create(:group)
nested_group = create(:group, parent: root_group)
deep_nested_group = create(:group, parent: nested_group)
very_deep_nested_group = create(:group, parent: deep_nested_group)
diff --git a/spec/models/network/graph_spec.rb b/spec/models/network/graph_spec.rb
index 232172fde76..a393aace39c 100644
--- a/spec/models/network/graph_spec.rb
+++ b/spec/models/network/graph_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Network::Graph do
+RSpec.describe Network::Graph do
let(:project) { create(:project, :repository) }
let!(:note_on_commit) { create(:note_on_commit, project: project) }
diff --git a/spec/models/note_diff_file_spec.rb b/spec/models/note_diff_file_spec.rb
index 11108016b8e..1ece1dfea59 100644
--- a/spec/models/note_diff_file_spec.rb
+++ b/spec/models/note_diff_file_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe NoteDiffFile do
+RSpec.describe NoteDiffFile do
describe 'associations' do
it { is_expected.to belong_to(:diff_note) }
end
diff --git a/spec/models/note_spec.rb b/spec/models/note_spec.rb
index af3fdcfaa2e..e6e6a8c35cf 100644
--- a/spec/models/note_spec.rb
+++ b/spec/models/note_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Note do
+RSpec.describe Note do
include RepoHelpers
describe 'associations' do
diff --git a/spec/models/notification_recipient_spec.rb b/spec/models/notification_recipient_spec.rb
index 05aeafaa4d4..8429f577dc6 100644
--- a/spec/models/notification_recipient_spec.rb
+++ b/spec/models/notification_recipient_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe NotificationRecipient do
+RSpec.describe NotificationRecipient do
let(:user) { create(:user) }
let(:project) { create(:project, namespace: user.namespace) }
let(:target) { create(:issue, project: project) }
diff --git a/spec/models/oauth_access_grant_spec.rb b/spec/models/oauth_access_grant_spec.rb
index 955dae906f3..ca67944752d 100644
--- a/spec/models/oauth_access_grant_spec.rb
+++ b/spec/models/oauth_access_grant_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe OauthAccessGrant do
+RSpec.describe OauthAccessGrant do
let(:user) { create(:user) }
let(:application) { create(:oauth_application, owner: user) }
diff --git a/spec/models/oauth_access_token_spec.rb b/spec/models/oauth_access_token_spec.rb
index 0a1c576a5e7..65a7f6410cf 100644
--- a/spec/models/oauth_access_token_spec.rb
+++ b/spec/models/oauth_access_token_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe OauthAccessToken do
+RSpec.describe OauthAccessToken do
let(:user) { create(:user) }
let(:app_one) { create(:oauth_application) }
let(:app_two) { create(:oauth_application) }
diff --git a/spec/models/packages/composer/metadatum_spec.rb b/spec/models/packages/composer/metadatum_spec.rb
new file mode 100644
index 00000000000..ae53532696b
--- /dev/null
+++ b/spec/models/packages/composer/metadatum_spec.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+RSpec.describe Packages::Composer::Metadatum, type: :model do
+ describe 'relationships' do
+ it { is_expected.to belong_to(:package) }
+ end
+
+ describe 'validations' do
+ it { is_expected.to validate_presence_of(:package) }
+ it { is_expected.to validate_presence_of(:target_sha) }
+ it { is_expected.to validate_presence_of(:composer_json) }
+ end
+end
diff --git a/spec/models/packages/conan/file_metadatum_spec.rb b/spec/models/packages/conan/file_metadatum_spec.rb
new file mode 100644
index 00000000000..a66a2813196
--- /dev/null
+++ b/spec/models/packages/conan/file_metadatum_spec.rb
@@ -0,0 +1,106 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Packages::Conan::FileMetadatum, type: :model do
+ describe 'relationships' do
+ it { is_expected.to belong_to(:package_file) }
+ end
+
+ describe 'validations' do
+ let(:package_file) { create(:conan_package_file, :conan_recipe_file) }
+
+ it { is_expected.to validate_presence_of(:package_file) }
+ it { is_expected.to validate_presence_of(:recipe_revision) }
+
+ describe '#recipe_revision' do
+ it { is_expected.to allow_value("0").for(:recipe_revision) }
+ it { is_expected.not_to allow_value(nil).for(:recipe_revision) }
+ end
+
+ describe '#package_revision_for_package_file' do
+ context 'recipe file' do
+ let(:conan_file_metadatum) { build(:conan_file_metadatum, :recipe_file, package_file: package_file) }
+
+ it 'is valid with empty value' do
+ conan_file_metadatum.package_revision = nil
+
+ expect(conan_file_metadatum).to be_valid
+ end
+
+ it 'is invalid with value' do
+ conan_file_metadatum.package_revision = '0'
+
+ expect(conan_file_metadatum).to be_invalid
+ end
+ end
+
+ context 'package file' do
+ let(:conan_file_metadatum) { build(:conan_file_metadatum, :package_file, package_file: package_file) }
+
+ it 'is valid with default value' do
+ conan_file_metadatum.package_revision = '0'
+
+ expect(conan_file_metadatum).to be_valid
+ end
+
+ it 'is invalid with non-default value' do
+ conan_file_metadatum.package_revision = 'foo'
+
+ expect(conan_file_metadatum).to be_invalid
+ end
+ end
+ end
+
+ describe '#conan_package_reference_for_package_file' do
+ context 'recipe file' do
+ let(:conan_file_metadatum) { build(:conan_file_metadatum, :recipe_file, package_file: package_file) }
+
+ it 'is valid with empty value' do
+ conan_file_metadatum.conan_package_reference = nil
+
+ expect(conan_file_metadatum).to be_valid
+ end
+
+ it 'is invalid with value' do
+ conan_file_metadatum.conan_package_reference = '123456789'
+
+ expect(conan_file_metadatum).to be_invalid
+ end
+ end
+
+ context 'package file' do
+ let(:conan_file_metadatum) { build(:conan_file_metadatum, :package_file, package_file: package_file) }
+
+ it 'is valid with acceptable value' do
+ conan_file_metadatum.conan_package_reference = '123456asdf'
+
+ expect(conan_file_metadatum).to be_valid
+ end
+
+ it 'is invalid with invalid value' do
+ conan_file_metadatum.conan_package_reference = 'foo@bar'
+
+ expect(conan_file_metadatum).to be_invalid
+ end
+
+ it 'is invalid when nil' do
+ conan_file_metadatum.conan_package_reference = nil
+
+ expect(conan_file_metadatum).to be_invalid
+ end
+ end
+ end
+
+ describe '#conan_package_type' do
+ it 'validates package of type conan' do
+ package = build('package')
+ package_file = build('package_file', package: package)
+ conan_file_metadatum = build('conan_file_metadatum', package_file: package_file)
+
+ expect(conan_file_metadatum).not_to be_valid
+ expect(conan_file_metadatum.errors.to_a).to contain_exactly('Package type must be Conan')
+ end
+ end
+ end
+end
diff --git a/spec/models/packages/conan/metadatum_spec.rb b/spec/models/packages/conan/metadatum_spec.rb
new file mode 100644
index 00000000000..112f395818b
--- /dev/null
+++ b/spec/models/packages/conan/metadatum_spec.rb
@@ -0,0 +1,90 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Packages::Conan::Metadatum, type: :model do
+ describe 'relationships' do
+ it { is_expected.to belong_to(:package) }
+ end
+
+ describe 'validations' do
+ let(:fifty_one_characters) { 'f_a' * 17}
+
+ it { is_expected.to validate_presence_of(:package) }
+ it { is_expected.to validate_presence_of(:package_username) }
+ it { is_expected.to validate_presence_of(:package_channel) }
+
+ describe '#package_username' do
+ it { is_expected.to allow_value("my-package+username").for(:package_username) }
+ it { is_expected.to allow_value("my_package.username").for(:package_username) }
+ it { is_expected.to allow_value("_my-package.username123").for(:package_username) }
+ it { is_expected.to allow_value("my").for(:package_username) }
+ it { is_expected.not_to allow_value('+my_package').for(:package_username) }
+ it { is_expected.not_to allow_value('.my_package').for(:package_username) }
+ it { is_expected.not_to allow_value('-my_package').for(:package_username) }
+ it { is_expected.not_to allow_value('m').for(:package_username) }
+ it { is_expected.not_to allow_value(fifty_one_characters).for(:package_username) }
+ it { is_expected.not_to allow_value("my/package").for(:package_username) }
+ it { is_expected.not_to allow_value("my(package)").for(:package_username) }
+ it { is_expected.not_to allow_value("my@package").for(:package_username) }
+ end
+
+ describe '#package_channel' do
+ it { is_expected.to allow_value("beta").for(:package_channel) }
+ it { is_expected.to allow_value("stable+1.0").for(:package_channel) }
+ it { is_expected.to allow_value("my").for(:package_channel) }
+ it { is_expected.to allow_value("my_channel.beta").for(:package_channel) }
+ it { is_expected.to allow_value("_my-channel.beta123").for(:package_channel) }
+ it { is_expected.not_to allow_value('+my_channel').for(:package_channel) }
+ it { is_expected.not_to allow_value('.my_channel').for(:package_channel) }
+ it { is_expected.not_to allow_value('-my_channel').for(:package_channel) }
+ it { is_expected.not_to allow_value('m').for(:package_channel) }
+ it { is_expected.not_to allow_value(fifty_one_characters).for(:package_channel) }
+ it { is_expected.not_to allow_value("my/channel").for(:package_channel) }
+ it { is_expected.not_to allow_value("my(channel)").for(:package_channel) }
+ it { is_expected.not_to allow_value("my@channel").for(:package_channel) }
+ end
+
+ describe '#conan_package_type' do
+ it 'will not allow a package with a different package_type' do
+ package = build('package')
+ conan_metadatum = build('conan_metadatum', package: package)
+
+ expect(conan_metadatum).not_to be_valid
+ expect(conan_metadatum.errors.to_a).to include('Package type must be Conan')
+ end
+ end
+ end
+
+ describe '#recipe' do
+ let(:package) { create(:conan_package) }
+
+ it 'returns the recipe' do
+ expect(package.conan_recipe).to eq("#{package.name}/#{package.version}@#{package.conan_metadatum.package_username}/#{package.conan_metadatum.package_channel}")
+ end
+ end
+
+ describe '#recipe_url' do
+ let(:package) { create(:conan_package) }
+
+ it 'returns the recipe url' do
+ expect(package.conan_recipe_path).to eq("#{package.name}/#{package.version}/#{package.conan_metadatum.package_username}/#{package.conan_metadatum.package_channel}")
+ end
+ end
+
+ describe '.package_username_from' do
+ let(:full_path) { 'foo/bar/baz-buz' }
+
+ it 'returns the username formatted package path' do
+ expect(described_class.package_username_from(full_path: full_path)).to eq('foo+bar+baz-buz')
+ end
+ end
+
+ describe '.full_path_from' do
+ let(:username) { 'foo+bar+baz-buz' }
+
+ it 'returns the username formatted package path' do
+ expect(described_class.full_path_from(package_username: username)).to eq('foo/bar/baz-buz')
+ end
+ end
+end
diff --git a/spec/models/packages/dependency_link_spec.rb b/spec/models/packages/dependency_link_spec.rb
new file mode 100644
index 00000000000..d8fde8f5eb3
--- /dev/null
+++ b/spec/models/packages/dependency_link_spec.rb
@@ -0,0 +1,56 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+RSpec.describe Packages::DependencyLink, type: :model do
+ describe 'relationships' do
+ it { is_expected.to belong_to(:package).inverse_of(:dependency_links) }
+ it { is_expected.to belong_to(:dependency).inverse_of(:dependency_links) }
+ it { is_expected.to have_one(:nuget_metadatum).inverse_of(:dependency_link) }
+ end
+
+ describe 'validations' do
+ subject { create(:packages_dependency_link) }
+
+ it { is_expected.to validate_presence_of(:package) }
+ it { is_expected.to validate_presence_of(:dependency) }
+
+ context 'package_id and package_dependency_id uniqueness for dependency_type' do
+ it 'is not valid' do
+ exisiting_link = subject
+ link = build(
+ :packages_dependency_link,
+ package: exisiting_link.package,
+ dependency: exisiting_link.dependency,
+ dependency_type: exisiting_link.dependency_type
+ )
+
+ expect(link).not_to be_valid
+ expect(link.errors.to_a).to include("Dependency type has already been taken")
+ end
+ end
+ end
+
+ context 'with multiple links' do
+ let_it_be(:link1) { create(:packages_dependency_link) }
+ let_it_be(:link2) { create(:packages_dependency_link, dependency: link1.dependency, dependency_type: :devDependencies) }
+ let_it_be(:link3) { create(:packages_dependency_link, dependency: link1.dependency, dependency_type: :bundleDependencies) }
+
+ subject { described_class }
+
+ describe '.with_dependency_type' do
+ it 'returns links of the given type' do
+ expect(subject.with_dependency_type(:bundleDependencies)).to eq([link3])
+ end
+ end
+
+ describe '.for_package' do
+ let_it_be(:link1) { create(:packages_dependency_link) }
+ let_it_be(:link2) { create(:packages_dependency_link, dependency: link1.dependency, dependency_type: :devDependencies) }
+ let_it_be(:link3) { create(:packages_dependency_link, dependency: link1.dependency, dependency_type: :bundleDependencies) }
+
+ it 'returns the link for the given package' do
+ expect(subject.for_package(link1.package)).to eq([link1])
+ end
+ end
+ end
+end
diff --git a/spec/models/packages/dependency_spec.rb b/spec/models/packages/dependency_spec.rb
new file mode 100644
index 00000000000..fa6b0fd1848
--- /dev/null
+++ b/spec/models/packages/dependency_spec.rb
@@ -0,0 +1,113 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+RSpec.describe Packages::Dependency, type: :model do
+ describe 'relationships' do
+ it { is_expected.to have_many(:dependency_links) }
+ end
+
+ describe 'validations' do
+ subject { create(:packages_dependency) }
+
+ it { is_expected.to validate_presence_of(:name) }
+ it { is_expected.to validate_presence_of(:version_pattern) }
+ it { is_expected.to validate_uniqueness_of(:name).scoped_to(:version_pattern) }
+ end
+
+ describe '.ids_for_package_names_and_version_patterns' do
+ let_it_be(:package_dependency1) { create(:packages_dependency, name: 'foo', version_pattern: '~1.0.0') }
+ let_it_be(:package_dependency2) { create(:packages_dependency, name: 'bar', version_pattern: '~2.5.0') }
+ let_it_be(:expected_ids) { [package_dependency1.id, package_dependency2.id] }
+ let(:names_and_version_patterns) { build_names_and_version_patterns(package_dependency1, package_dependency2) }
+ let(:chunk_size) { 50 }
+ let(:rows_limit) { 50 }
+
+ subject { Packages::Dependency.ids_for_package_names_and_version_patterns(names_and_version_patterns, chunk_size, rows_limit) }
+
+ it { is_expected.to match_array(expected_ids) }
+
+ context 'with unknown names' do
+ let(:names_and_version_patterns) { { unknown: '~1.0.0' } }
+
+ it { is_expected.to be_empty }
+ end
+
+ context 'with unknown version patterns' do
+ let(:names_and_version_patterns) { { 'foo' => '~1.0.0beta' } }
+
+ it { is_expected.to be_empty }
+ end
+
+ context 'with a name bigger than column size' do
+ let_it_be(:big_name) { 'a' * (Packages::Dependency::MAX_STRING_LENGTH + 1) }
+ let(:names_and_version_patterns) { build_names_and_version_patterns(package_dependency1, package_dependency2).merge(big_name => '~1.0.0') }
+
+ it { is_expected.to match_array(expected_ids) }
+ end
+
+ context 'with a version pattern bigger than column size' do
+ let_it_be(:big_version_pattern) { 'a' * (Packages::Dependency::MAX_STRING_LENGTH + 1) }
+ let(:names_and_version_patterns) { build_names_and_version_patterns(package_dependency1, package_dependency2).merge('test' => big_version_pattern) }
+
+ it { is_expected.to match_array(expected_ids) }
+ end
+
+ context 'with too big parameter' do
+ let(:size) { (Packages::Dependency::MAX_CHUNKED_QUERIES_COUNT * chunk_size) + 1 }
+ let(:names_and_version_patterns) { Hash[(1..size).map { |v| [v, v] }] }
+
+ it { expect { subject }.to raise_error(ArgumentError, 'Too many names_and_version_patterns') }
+ end
+
+ context 'with parameters size' do
+ let_it_be(:package_dependency3) { create(:packages_dependency, name: 'foo3', version_pattern: '~1.5.3') }
+ let_it_be(:package_dependency4) { create(:packages_dependency, name: 'foo4', version_pattern: '~1.5.4') }
+ let_it_be(:package_dependency5) { create(:packages_dependency, name: 'foo5', version_pattern: '~1.5.5') }
+ let_it_be(:package_dependency6) { create(:packages_dependency, name: 'foo6', version_pattern: '~1.5.6') }
+ let_it_be(:package_dependency7) { create(:packages_dependency, name: 'foo7', version_pattern: '~1.5.7') }
+ let(:expected_ids) { [package_dependency1.id, package_dependency2.id, package_dependency3.id, package_dependency4.id, package_dependency5.id, package_dependency6.id, package_dependency7.id] }
+ let(:names_and_version_patterns) { build_names_and_version_patterns(package_dependency1, package_dependency2, package_dependency3, package_dependency4, package_dependency5, package_dependency6, package_dependency7) }
+
+ context 'above the chunk size' do
+ let(:chunk_size) { 2 }
+
+ it { is_expected.to match_array(expected_ids) }
+ end
+
+ context 'selecting too many rows' do
+ let(:rows_limit) { 2 }
+
+ it { expect { subject }.to raise_error(ArgumentError, 'Too many Dependencies selected') }
+ end
+ end
+ end
+
+ describe '.for_package_names_and_version_patterns' do
+ let_it_be(:package_dependency1) { create(:packages_dependency, name: 'foo', version_pattern: '~1.0.0') }
+ let_it_be(:package_dependency2) { create(:packages_dependency, name: 'bar', version_pattern: '~2.5.0') }
+ let_it_be(:expected_array) { [package_dependency1, package_dependency2] }
+ let(:names_and_version_patterns) { build_names_and_version_patterns(package_dependency1, package_dependency2) }
+
+ subject { Packages::Dependency.for_package_names_and_version_patterns(names_and_version_patterns) }
+
+ it { is_expected.to match_array(expected_array) }
+
+ context 'with unknown names' do
+ let(:names_and_version_patterns) { { unknown: '~1.0.0' } }
+
+ it { is_expected.to be_empty }
+ end
+
+ context 'with unknown version patterns' do
+ let(:names_and_version_patterns) { { 'foo' => '~1.0.0beta' } }
+
+ it { is_expected.to be_empty }
+ end
+ end
+
+ def build_names_and_version_patterns(*package_dependencies)
+ result = Hash.new { |h, dependency| h[dependency.name] = dependency.version_pattern }
+ package_dependencies.each { |dependency| result[dependency] }
+ result
+ end
+end
diff --git a/spec/models/packages/go/module_spec.rb b/spec/models/packages/go/module_spec.rb
new file mode 100644
index 00000000000..03af4cf4b70
--- /dev/null
+++ b/spec/models/packages/go/module_spec.rb
@@ -0,0 +1,59 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Packages::Go::Module, type: :model do
+ before do
+ stub_feature_flags(go_proxy_disable_gomod_validation: false)
+ end
+
+ describe '#path_valid?' do
+ context 'with root path' do
+ let_it_be(:package) { create(:go_module) }
+
+ context 'with major version 0' do
+ it('returns true') { expect(package.path_valid?(0)).to eq(true) }
+ end
+
+ context 'with major version 1' do
+ it('returns true') { expect(package.path_valid?(1)).to eq(true) }
+ end
+
+ context 'with major version 2' do
+ it('returns false') { expect(package.path_valid?(2)).to eq(false) }
+ end
+ end
+
+ context 'with path ./v2' do
+ let_it_be(:package) { create(:go_module, path: '/v2') }
+
+ context 'with major version 0' do
+ it('returns false') { expect(package.path_valid?(0)).to eq(false) }
+ end
+
+ context 'with major version 1' do
+ it('returns false') { expect(package.path_valid?(1)).to eq(false) }
+ end
+
+ context 'with major version 2' do
+ it('returns true') { expect(package.path_valid?(2)).to eq(true) }
+ end
+ end
+ end
+
+ describe '#gomod_valid?' do
+ let_it_be(:package) { create(:go_module) }
+
+ context 'with good gomod' do
+ it('returns true') { expect(package.gomod_valid?("module #{package.name}")).to eq(true) }
+ end
+
+ context 'with bad gomod' do
+ it('returns false') { expect(package.gomod_valid?("module #{package.name}/v2")).to eq(false) }
+ end
+
+ context 'with empty gomod' do
+ it('returns false') { expect(package.gomod_valid?("")).to eq(false) }
+ end
+ end
+end
diff --git a/spec/models/packages/go/module_version_spec.rb b/spec/models/packages/go/module_version_spec.rb
new file mode 100644
index 00000000000..c4c6a07d9e9
--- /dev/null
+++ b/spec/models/packages/go/module_version_spec.rb
@@ -0,0 +1,114 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Packages::Go::ModuleVersion, type: :model do
+ let_it_be(:user) { create :user }
+ let_it_be(:project) { create :project_empty_repo, creator: user, path: 'my-go-lib' }
+ let_it_be(:mod) { create :go_module, project: project }
+
+ before :all do
+ create :go_module_commit, :files, project: project, tag: 'v1.0.0', files: { 'README.md' => 'Hi' }
+ create :go_module_commit, :module, project: project, tag: 'v1.0.1'
+ create :go_module_commit, :package, project: project, tag: 'v1.0.2', path: 'pkg'
+ create :go_module_commit, :module, project: project, tag: 'v1.0.3', name: 'mod'
+ create :go_module_commit, :files, project: project, files: { 'y.go' => "package a\n" }
+ create :go_module_commit, :module, project: project, name: 'v2'
+ create :go_module_commit, :files, project: project, tag: 'v2.0.0', files: { 'v2/x.go' => "package a\n" }
+ end
+
+ shared_examples '#files' do |desc, *entries|
+ it "returns #{desc}" do
+ actual = version.files.map { |x| x }.to_set
+ expect(actual).to eq(entries.to_set)
+ end
+ end
+
+ shared_examples '#archive' do |desc, *entries|
+ it "returns an archive of #{desc}" do
+ expected = entries.map { |e| "#{version.full_name}/#{e}" }.to_set
+
+ actual = Set[]
+ Zip::InputStream.open(StringIO.new(version.archive.string)) do |zip|
+ while (entry = zip.get_next_entry)
+ actual.add(entry.name)
+ end
+ end
+
+ expect(actual).to eq(expected)
+ end
+ end
+
+ describe '#name' do
+ context 'with ref and name specified' do
+ let_it_be(:version) { create :go_module_version, mod: mod, name: 'foobar', commit: project.repository.head_commit, ref: project.repository.find_tag('v1.0.0') }
+ it('returns that name') { expect(version.name).to eq('foobar') }
+ end
+
+ context 'with ref specified and name unspecified' do
+ let_it_be(:version) { create :go_module_version, mod: mod, commit: project.repository.head_commit, ref: project.repository.find_tag('v1.0.0') }
+ it('returns the name of the ref') { expect(version.name).to eq('v1.0.0') }
+ end
+
+ context 'with ref and name unspecified' do
+ let_it_be(:version) { create :go_module_version, mod: mod, commit: project.repository.head_commit }
+ it('returns nil') { expect(version.name).to eq(nil) }
+ end
+ end
+
+ describe '#gomod' do
+ context 'with go.mod missing' do
+ let_it_be(:version) { create :go_module_version, :tagged, mod: mod, name: 'v1.0.0' }
+ it('returns nil') { expect(version.gomod).to eq(nil) }
+ end
+
+ context 'with go.mod present' do
+ let_it_be(:version) { create :go_module_version, :tagged, mod: mod, name: 'v1.0.1' }
+ it('returns the contents of go.mod') { expect(version.gomod).to eq("module #{mod.name}\n") }
+ end
+ end
+
+ describe '#files' do
+ context 'with a root module' do
+ context 'with an empty module path' do
+ let_it_be(:version) { create :go_module_version, :tagged, mod: mod, name: 'v1.0.2' }
+ it_behaves_like '#files', 'all the files', 'README.md', 'go.mod', 'a.go', 'pkg/b.go'
+ end
+ end
+
+ context 'with a root module and a submodule' do
+ context 'with an empty module path' do
+ let_it_be(:version) { create :go_module_version, :tagged, mod: mod, name: 'v1.0.3' }
+ it_behaves_like '#files', 'files excluding the submodule', 'README.md', 'go.mod', 'a.go', 'pkg/b.go'
+ end
+
+ context 'with the submodule\'s path' do
+ let_it_be(:mod) { create :go_module, project: project, path: 'mod' }
+ let_it_be(:version) { create :go_module_version, :tagged, mod: mod, name: 'v1.0.3' }
+ it_behaves_like '#files', 'the submodule\'s files', 'mod/go.mod', 'mod/a.go'
+ end
+ end
+ end
+
+ describe '#archive' do
+ context 'with a root module' do
+ context 'with an empty module path' do
+ let_it_be(:version) { create :go_module_version, :tagged, mod: mod, name: 'v1.0.2' }
+ it_behaves_like '#archive', 'all the files', 'README.md', 'go.mod', 'a.go', 'pkg/b.go'
+ end
+ end
+
+ context 'with a root module and a submodule' do
+ context 'with an empty module path' do
+ let_it_be(:version) { create :go_module_version, :tagged, mod: mod, name: 'v1.0.3' }
+ it_behaves_like '#archive', 'files excluding the submodule', 'README.md', 'go.mod', 'a.go', 'pkg/b.go'
+ end
+
+ context 'with the submodule\'s path' do
+ let_it_be(:mod) { create :go_module, project: project, path: 'mod' }
+ let_it_be(:version) { create :go_module_version, :tagged, mod: mod, name: 'v1.0.3' }
+ it_behaves_like '#archive', 'the submodule\'s files', 'go.mod', 'a.go'
+ end
+ end
+ end
+end
diff --git a/spec/models/packages/maven/metadatum_spec.rb b/spec/models/packages/maven/metadatum_spec.rb
new file mode 100644
index 00000000000..16f6929d710
--- /dev/null
+++ b/spec/models/packages/maven/metadatum_spec.rb
@@ -0,0 +1,40 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+RSpec.describe Packages::Maven::Metadatum, type: :model do
+ describe 'relationships' do
+ it { is_expected.to belong_to(:package) }
+ end
+
+ describe 'validations' do
+ it { is_expected.to validate_presence_of(:package) }
+
+ describe '#app_name' do
+ it { is_expected.to allow_value("my-app").for(:app_name) }
+ it { is_expected.not_to allow_value("my/app").for(:app_name) }
+ it { is_expected.not_to allow_value("my(app)").for(:app_name) }
+ end
+
+ describe '#app_group' do
+ it { is_expected.to allow_value("my.domain.com").for(:app_group) }
+ it { is_expected.not_to allow_value("my/domain/com").for(:app_group) }
+ it { is_expected.not_to allow_value("my(domain)").for(:app_group) }
+ end
+
+ describe '#path' do
+ it { is_expected.to allow_value("my/domain/com/my-app").for(:path) }
+ it { is_expected.to allow_value("my/domain/com/my-app/1.0-SNAPSHOT").for(:path) }
+ it { is_expected.not_to allow_value("my(domain)com.my-app").for(:path) }
+ end
+
+ describe '#maven_package_type' do
+ it 'will not allow a package with a different package_type' do
+ package = build('conan_package')
+ maven_metadatum = build('maven_metadatum', package: package)
+
+ expect(maven_metadatum).not_to be_valid
+ expect(maven_metadatum.errors.to_a).to include('Package type must be Maven')
+ end
+ end
+ end
+end
diff --git a/spec/models/packages/nuget/dependency_link_metadatum_spec.rb b/spec/models/packages/nuget/dependency_link_metadatum_spec.rb
new file mode 100644
index 00000000000..0c03c65028e
--- /dev/null
+++ b/spec/models/packages/nuget/dependency_link_metadatum_spec.rb
@@ -0,0 +1,32 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Packages::Nuget::DependencyLinkMetadatum, type: :model do
+ describe 'relationships' do
+ it { is_expected.to belong_to(:dependency_link) }
+ end
+
+ describe 'validations' do
+ it { is_expected.to validate_presence_of(:dependency_link) }
+ it { is_expected.to validate_presence_of(:target_framework) }
+
+ describe '#ensure_nuget_package_type' do
+ it 'validates package of type nuget' do
+ package = build('conan_package')
+ dependency_link = build('packages_dependency_link', package: package)
+ nuget_metadatum = build('nuget_dependency_link_metadatum', dependency_link: dependency_link)
+
+ expect(nuget_metadatum).not_to be_valid
+ expect(nuget_metadatum.errors.to_a).to contain_exactly('Package type must be NuGet')
+ end
+
+ it 'validates package of type nuget with nil dependency_link' do
+ nuget_metadatum = build('nuget_dependency_link_metadatum', dependency_link: nil)
+
+ expect(nuget_metadatum).not_to be_valid
+ expect(nuget_metadatum.errors.to_a).to contain_exactly("Dependency link can't be blank", 'Package type must be NuGet')
+ end
+ end
+ end
+end
diff --git a/spec/models/packages/nuget/metadatum_spec.rb b/spec/models/packages/nuget/metadatum_spec.rb
new file mode 100644
index 00000000000..c1bc5429500
--- /dev/null
+++ b/spec/models/packages/nuget/metadatum_spec.rb
@@ -0,0 +1,44 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Packages::Nuget::Metadatum, type: :model do
+ describe 'relationships' do
+ it { is_expected.to belong_to(:package).inverse_of(:nuget_metadatum) }
+ end
+
+ describe 'validations' do
+ it { is_expected.to validate_presence_of(:package) }
+
+ %i[license_url project_url icon_url].each do |url|
+ describe "##{url}" do
+ it { is_expected.to allow_value('http://sandbox.com').for(url) }
+ it { is_expected.to allow_value('https://sandbox.com').for(url) }
+ it { is_expected.not_to allow_value('123').for(url) }
+ it { is_expected.not_to allow_value('sandbox.com').for(url) }
+ end
+
+ describe '#ensure_at_least_one_field_supplied' do
+ subject { build(:nuget_metadatum) }
+
+ it 'rejects unfilled metadatum' do
+ subject.attributes = { license_url: nil, project_url: nil, icon_url: nil }
+
+ expect(subject).not_to be_valid
+ expect(subject.errors).to contain_exactly('Nuget metadatum must have at least license_url, project_url or icon_url set')
+ end
+ end
+
+ describe '#ensure_nuget_package_type' do
+ subject { build(:nuget_metadatum) }
+
+ it 'rejects if not linked to a nuget package' do
+ subject.package = build(:npm_package)
+
+ expect(subject).not_to be_valid
+ expect(subject.errors).to contain_exactly('Package type must be NuGet')
+ end
+ end
+ end
+ end
+end
diff --git a/spec/models/packages/package_file_spec.rb b/spec/models/packages/package_file_spec.rb
new file mode 100644
index 00000000000..7758ed4a500
--- /dev/null
+++ b/spec/models/packages/package_file_spec.rb
@@ -0,0 +1,69 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+RSpec.describe Packages::PackageFile, type: :model do
+ describe 'relationships' do
+ it { is_expected.to belong_to(:package) }
+ it { is_expected.to have_one(:conan_file_metadatum) }
+ end
+
+ describe 'validations' do
+ it { is_expected.to validate_presence_of(:package) }
+ end
+
+ context 'with package filenames' do
+ let_it_be(:package_file1) { create(:package_file, :xml, file_name: 'FooBar') }
+ let_it_be(:package_file2) { create(:package_file, :xml, file_name: 'ThisIsATest') }
+
+ describe '.with_file_name' do
+ let(:filename) { 'FooBar' }
+
+ subject { described_class.with_file_name(filename) }
+
+ it { is_expected.to match_array([package_file1]) }
+ end
+
+ describe '.with_file_name_like' do
+ let(:filename) { 'foobar' }
+
+ subject { described_class.with_file_name_like(filename) }
+
+ it { is_expected.to match_array([package_file1]) }
+ end
+ end
+
+ it_behaves_like 'UpdateProjectStatistics' do
+ subject { build(:package_file, :jar, size: 42) }
+
+ before do
+ allow_any_instance_of(Packages::PackageFileUploader).to receive(:size).and_return(42)
+ end
+ end
+
+ describe '.with_conan_package_reference' do
+ let_it_be(:non_matching_package_file) { create(:package_file, :nuget) }
+ let_it_be(:metadatum) { create(:conan_file_metadatum, :package_file) }
+ let_it_be(:reference) { metadatum.conan_package_reference}
+
+ it 'returns matching packages' do
+ expect(described_class.with_conan_package_reference(reference))
+ .to eq([metadatum.package_file])
+ end
+ end
+
+ describe '#update_file_metadata callback' do
+ let_it_be(:package_file) { build(:package_file, :nuget, file_store: nil, size: nil) }
+
+ subject { package_file.save! }
+
+ it 'updates metadata columns' do
+ expect(package_file)
+ .to receive(:update_file_metadata)
+ .and_call_original
+
+ expect { subject }
+ .to change { package_file.file_store }.from(nil).to(::Packages::PackageFileUploader::Store::LOCAL)
+ .and change { package_file.size }.from(nil).to(3513)
+ end
+ end
+end
diff --git a/spec/models/packages/package_spec.rb b/spec/models/packages/package_spec.rb
new file mode 100644
index 00000000000..4170bf595f0
--- /dev/null
+++ b/spec/models/packages/package_spec.rb
@@ -0,0 +1,485 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+RSpec.describe Packages::Package, type: :model do
+ include SortingHelper
+
+ describe 'relationships' do
+ it { is_expected.to belong_to(:project) }
+ it { is_expected.to have_many(:package_files).dependent(:destroy) }
+ it { is_expected.to have_many(:dependency_links).inverse_of(:package) }
+ it { is_expected.to have_many(:tags).inverse_of(:package) }
+ it { is_expected.to have_one(:conan_metadatum).inverse_of(:package) }
+ it { is_expected.to have_one(:maven_metadatum).inverse_of(:package) }
+ it { is_expected.to have_one(:nuget_metadatum).inverse_of(:package) }
+ end
+
+ describe '.with_composer_target' do
+ let!(:package1) { create(:composer_package, :with_metadatum, sha: '123') }
+ let!(:package2) { create(:composer_package, :with_metadatum, sha: '123') }
+ let!(:package3) { create(:composer_package, :with_metadatum, sha: '234') }
+
+ subject { described_class.with_composer_target('123').to_a }
+
+ it 'selects packages with the specified sha' do
+ expect(subject).to include(package1)
+ expect(subject).to include(package2)
+ expect(subject).not_to include(package3)
+ end
+ end
+
+ describe '.sort_by_attribute' do
+ let_it_be(:group) { create(:group, :public) }
+ let_it_be(:project) { create(:project, :public, namespace: group, name: 'project A') }
+ let!(:package1) { create(:npm_package, project: project, version: '3.1.0', name: "@#{project.root_namespace.path}/foo1") }
+ let!(:package2) { create(:nuget_package, project: project, version: '2.0.4') }
+ let(:package3) { create(:maven_package, project: project, version: '1.1.1', name: 'zzz') }
+
+ before do
+ travel_to(1.day.ago) do
+ package3
+ end
+ end
+
+ RSpec.shared_examples 'package sorting by attribute' do |order_by|
+ subject { described_class.where(id: packages.map(&:id)).sort_by_attribute("#{order_by}_#{sort}").to_a }
+
+ context "sorting by #{order_by}" do
+ context 'ascending order' do
+ let(:sort) { 'asc' }
+
+ it { is_expected.to eq packages }
+ end
+
+ context 'descending order' do
+ let(:sort) { 'desc' }
+
+ it { is_expected.to eq packages.reverse }
+ end
+ end
+ end
+
+ it_behaves_like 'package sorting by attribute', 'name' do
+ let(:packages) { [package1, package2, package3] }
+ end
+
+ it_behaves_like 'package sorting by attribute', 'created_at' do
+ let(:packages) { [package3, package1, package2] }
+ end
+
+ it_behaves_like 'package sorting by attribute', 'version' do
+ let(:packages) { [package3, package2, package1] }
+ end
+
+ it_behaves_like 'package sorting by attribute', 'type' do
+ let(:packages) { [package3, package1, package2] }
+ end
+
+ it_behaves_like 'package sorting by attribute', 'project_path' do
+ let(:another_project) { create(:project, :public, namespace: group, name: 'project B') }
+ let!(:package4) { create(:npm_package, project: another_project, version: '3.1.0', name: "@#{project.root_namespace.path}/bar") }
+
+ let(:packages) { [package1, package2, package3, package4] }
+ end
+ end
+
+ describe 'validations' do
+ subject { create(:package) }
+
+ it { is_expected.to validate_presence_of(:project) }
+ it { is_expected.to validate_uniqueness_of(:name).scoped_to(:project_id, :version, :package_type) }
+
+ describe '#name' do
+ it { is_expected.to allow_value("my/domain/com/my-app").for(:name) }
+ it { is_expected.to allow_value("my.app-11.07.2018").for(:name) }
+ it { is_expected.not_to allow_value("my(dom$$$ain)com.my-app").for(:name) }
+
+ context 'conan package' do
+ subject { create(:conan_package) }
+
+ let(:fifty_one_characters) {'f_b' * 17}
+
+ it { is_expected.to allow_value('foo+bar').for(:name) }
+ it { is_expected.to allow_value('foo_bar').for(:name) }
+ it { is_expected.to allow_value('foo.bar').for(:name) }
+ it { is_expected.not_to allow_value(fifty_one_characters).for(:name) }
+ it { is_expected.not_to allow_value('+foobar').for(:name) }
+ it { is_expected.not_to allow_value('.foobar').for(:name) }
+ it { is_expected.not_to allow_value('%foo%bar').for(:name) }
+ end
+ end
+
+ describe '#version' do
+ RSpec.shared_examples 'validating version to be SemVer compliant for' do |factory_name|
+ context "for #{factory_name}" do
+ subject { create(factory_name) }
+
+ it { is_expected.to allow_value('1.2.3').for(:version) }
+ it { is_expected.to allow_value('1.2.3-beta').for(:version) }
+ it { is_expected.to allow_value('1.2.3-alpha.3').for(:version) }
+ it { is_expected.not_to allow_value('1').for(:version) }
+ it { is_expected.not_to allow_value('1.2').for(:version) }
+ it { is_expected.not_to allow_value('1./2.3').for(:version) }
+ it { is_expected.not_to allow_value('../../../../../1.2.3').for(:version) }
+ it { is_expected.not_to allow_value('%2e%2e%2f1.2.3').for(:version) }
+ end
+ end
+
+ context 'conan package' do
+ subject { create(:conan_package) }
+
+ let(:fifty_one_characters) {'1.2' * 17}
+
+ it { is_expected.to allow_value('1.2').for(:version) }
+ it { is_expected.to allow_value('1.2.3-beta').for(:version) }
+ it { is_expected.to allow_value('1.2.3-pre1+build2').for(:version) }
+ it { is_expected.not_to allow_value('1').for(:version) }
+ it { is_expected.not_to allow_value(fifty_one_characters).for(:version) }
+ it { is_expected.not_to allow_value('1./2.3').for(:version) }
+ it { is_expected.not_to allow_value('.1.2.3').for(:version) }
+ it { is_expected.not_to allow_value('+1.2.3').for(:version) }
+ it { is_expected.not_to allow_value('%2e%2e%2f1.2.3').for(:version) }
+ end
+
+ context 'maven package' do
+ subject { create(:maven_package) }
+
+ it { is_expected.to allow_value('0').for(:version) }
+ it { is_expected.to allow_value('1').for(:version) }
+ it { is_expected.to allow_value('10').for(:version) }
+ it { is_expected.to allow_value('1.0').for(:version) }
+ it { is_expected.to allow_value('1.3.350.v20200505-1744').for(:version) }
+ it { is_expected.to allow_value('1.1-beta-2').for(:version) }
+ it { is_expected.to allow_value('1.2-SNAPSHOT').for(:version) }
+ it { is_expected.to allow_value('12.1.2-2-1').for(:version) }
+ it { is_expected.to allow_value('1.2.3..beta').for(:version) }
+ it { is_expected.to allow_value('1.2.3-beta').for(:version) }
+ it { is_expected.to allow_value('10.2.3-beta').for(:version) }
+ it { is_expected.to allow_value('2.0.0.v200706041905-7C78EK9E_EkMNfNOd2d8qq').for(:version) }
+ it { is_expected.to allow_value('1.2-alpha-1-20050205.060708-1').for(:version) }
+ it { is_expected.to allow_value('703220b4e2cea9592caeb9f3013f6b1e5335c293').for(:version) }
+ it { is_expected.to allow_value('RELEASE').for(:version) }
+ it { is_expected.not_to allow_value('..1.2.3').for(:version) }
+ it { is_expected.not_to allow_value(' 1.2.3').for(:version) }
+ it { is_expected.not_to allow_value("1.2.3 \r\t").for(:version) }
+ it { is_expected.not_to allow_value("\r\t 1.2.3").for(:version) }
+ it { is_expected.not_to allow_value('1.2.3-4/../../').for(:version) }
+ it { is_expected.not_to allow_value('1.2.3-4%2e%2e%').for(:version) }
+ it { is_expected.not_to allow_value('../../../../../1.2.3').for(:version) }
+ it { is_expected.not_to allow_value('%2e%2e%2f1.2.3').for(:version) }
+ end
+
+ it_behaves_like 'validating version to be SemVer compliant for', :npm_package
+ it_behaves_like 'validating version to be SemVer compliant for', :nuget_package
+ end
+
+ describe '#package_already_taken' do
+ context 'npm package' do
+ let!(:package) { create(:npm_package) }
+
+ it 'will not allow a package of the same name' do
+ new_package = build(:npm_package, name: package.name)
+
+ expect(new_package).not_to be_valid
+ end
+ end
+
+ context 'maven package' do
+ let!(:package) { create(:maven_package) }
+
+ it 'will allow a package of the same name' do
+ new_package = build(:maven_package, name: package.name)
+
+ expect(new_package).to be_valid
+ end
+ end
+ end
+
+ context "recipe uniqueness for conan packages" do
+ let!(:package) { create('conan_package') }
+
+ it "will allow a conan package with same project, name, version and package_type" do
+ new_package = build('conan_package', project: package.project, name: package.name, version: package.version)
+ new_package.conan_metadatum.package_channel = 'beta'
+ expect(new_package).to be_valid
+ end
+
+ it "will not allow a conan package with same recipe (name, version, metadatum.package_channel, metadatum.package_username, and package_type)" do
+ new_package = build('conan_package', project: package.project, name: package.name, version: package.version)
+ expect(new_package).not_to be_valid
+ expect(new_package.errors.to_a).to include("Package recipe already exists")
+ end
+ end
+
+ Packages::Package.package_types.keys.without('conan').each do |pt|
+ context "project id, name, version and package type uniqueness for package type #{pt}" do
+ let(:package) { create("#{pt}_package") }
+
+ it "will not allow a #{pt} package with same project, name, version and package_type" do
+ new_package = build("#{pt}_package", project: package.project, name: package.name, version: package.version)
+ expect(new_package).not_to be_valid
+ expect(new_package.errors.to_a).to include("Name has already been taken")
+ end
+ end
+ end
+ end
+
+ describe '#destroy' do
+ let(:package) { create(:npm_package) }
+ let(:package_file) { package.package_files.first }
+ let(:project_statistics) { ProjectStatistics.for_project_ids(package.project.id).first }
+
+ it 'affects project statistics' do
+ expect { package.destroy! }
+ .to change { project_statistics.reload.packages_size }
+ .from(package_file.size).to(0)
+ end
+ end
+
+ describe '.by_name_and_file_name' do
+ let!(:package) { create(:npm_package) }
+ let!(:package_file) { package.package_files.first }
+
+ subject { described_class }
+
+ it 'finds a package with correct arguiments' do
+ expect(subject.by_name_and_file_name(package.name, package_file.file_name)).to eq(package)
+ end
+
+ it 'will raise error if not found' do
+ expect { subject.by_name_and_file_name('foo', 'foo-5.5.5.tgz') }.to raise_error(ActiveRecord::RecordNotFound)
+ end
+ end
+
+ context 'version scopes' do
+ let!(:package1) { create(:npm_package, version: '1.0.0') }
+ let!(:package2) { create(:npm_package, version: '1.0.1') }
+ let!(:package3) { create(:npm_package, version: '1.0.1') }
+
+ describe '.last_of_each_version' do
+ subject { described_class.last_of_each_version }
+
+ it 'includes only latest package per version' do
+ is_expected.to include(package1, package3)
+ is_expected.not_to include(package2)
+ end
+ end
+
+ describe '.has_version' do
+ subject { described_class.has_version }
+
+ before do
+ create(:maven_metadatum).package.update!(version: nil)
+ end
+
+ it 'includes only packages with version attribute' do
+ is_expected.to match_array([package1, package2, package3])
+ end
+ end
+
+ describe '.with_version' do
+ subject { described_class.with_version('1.0.1') }
+
+ it 'includes only packages with specified version' do
+ is_expected.to match_array([package2, package3])
+ end
+ end
+
+ describe '.without_version_like' do
+ let(:version_pattern) { '%.0.0%' }
+
+ subject { described_class.without_version_like(version_pattern) }
+
+ it 'includes packages without the version pattern' do
+ is_expected.to match_array([package2, package3])
+ end
+ end
+ end
+
+ context 'conan scopes' do
+ let!(:package) { create(:conan_package) }
+
+ describe '.with_conan_channel' do
+ subject { described_class.with_conan_channel('stable') }
+
+ it 'includes only packages with specified version' do
+ is_expected.to include(package)
+ end
+ end
+
+ describe '.with_conan_username' do
+ subject do
+ described_class.with_conan_username(
+ Packages::Conan::Metadatum.package_username_from(full_path: package.project.full_path)
+ )
+ end
+
+ it 'includes only packages with specified version' do
+ is_expected.to match_array([package])
+ end
+ end
+ end
+
+ describe '.without_nuget_temporary_name' do
+ let!(:package1) { create(:nuget_package) }
+ let!(:package2) { create(:nuget_package, name: Packages::Nuget::CreatePackageService::TEMPORARY_PACKAGE_NAME) }
+
+ subject { described_class.without_nuget_temporary_name }
+
+ it 'does not include nuget temporary packages' do
+ expect(subject).to eq([package1])
+ end
+ end
+
+ describe '.processed' do
+ let!(:package1) { create(:nuget_package) }
+ let!(:package2) { create(:npm_package) }
+ let!(:package3) { create(:nuget_package) }
+
+ subject { described_class.processed }
+
+ it { is_expected.to match_array([package1, package2, package3]) }
+
+ context 'with temporary packages' do
+ let!(:package1) { create(:nuget_package, name: Packages::Nuget::CreatePackageService::TEMPORARY_PACKAGE_NAME) }
+
+ it { is_expected.to match_array([package2, package3]) }
+ end
+ end
+
+ describe '.limit_recent' do
+ let!(:package1) { create(:nuget_package) }
+ let!(:package2) { create(:nuget_package) }
+ let!(:package3) { create(:nuget_package) }
+
+ subject { described_class.limit_recent(2) }
+
+ it { is_expected.to match_array([package3, package2]) }
+ end
+
+ context 'with several packages' do
+ let_it_be(:package1) { create(:nuget_package, name: 'FooBar') }
+ let_it_be(:package2) { create(:nuget_package, name: 'foobar') }
+ let_it_be(:package3) { create(:npm_package) }
+ let_it_be(:package4) { create(:npm_package) }
+
+ describe '.pluck_names' do
+ subject { described_class.pluck_names }
+
+ it { is_expected.to match_array([package1, package2, package3, package4].map(&:name)) }
+ end
+
+ describe '.pluck_versions' do
+ subject { described_class.pluck_versions }
+
+ it { is_expected.to match_array([package1, package2, package3, package4].map(&:version)) }
+ end
+
+ describe '.with_name_like' do
+ subject { described_class.with_name_like(name_term) }
+
+ context 'with downcase name' do
+ let(:name_term) { 'foobar' }
+
+ it { is_expected.to match_array([package1, package2]) }
+ end
+
+ context 'with prefix wildcard' do
+ let(:name_term) { '%ar' }
+
+ it { is_expected.to match_array([package1, package2]) }
+ end
+
+ context 'with suffix wildcard' do
+ let(:name_term) { 'foo%' }
+
+ it { is_expected.to match_array([package1, package2]) }
+ end
+
+ context 'with surrounding wildcards' do
+ let(:name_term) { '%ooba%' }
+
+ it { is_expected.to match_array([package1, package2]) }
+ end
+ end
+
+ describe '.search_by_name' do
+ let(:query) { 'oba' }
+
+ subject { described_class.search_by_name(query) }
+
+ it { is_expected.to match_array([package1, package2]) }
+ end
+ end
+
+ describe '.select_distinct_name' do
+ let_it_be(:nuget_package) { create(:nuget_package) }
+ let_it_be(:nuget_packages) { create_list(:nuget_package, 3, name: nuget_package.name, project: nuget_package.project) }
+ let_it_be(:maven_package) { create(:maven_package) }
+ let_it_be(:maven_packages) { create_list(:maven_package, 3, name: maven_package.name, project: maven_package.project) }
+
+ subject { described_class.select_distinct_name }
+
+ it 'returns only distinct names' do
+ packages = subject
+
+ expect(packages.size).to eq(2)
+ expect(packages.pluck(:name)).to match_array([nuget_package.name, maven_package.name])
+ end
+ end
+
+ describe '#versions' do
+ let_it_be(:project) { create(:project) }
+ let_it_be(:package) { create(:maven_package, project: project) }
+ let_it_be(:package2) { create(:maven_package, project: project) }
+ let_it_be(:package3) { create(:maven_package, project: project, name: 'foo') }
+
+ it 'returns other package versions of the same package name belonging to the project' do
+ expect(package.versions).to contain_exactly(package2)
+ end
+
+ it 'does not return different packages' do
+ expect(package.versions).not_to include(package3)
+ end
+ end
+
+ describe '#pipeline' do
+ let_it_be(:package) { create(:maven_package) }
+
+ context 'package without pipeline' do
+ it 'returns nil if there is no pipeline' do
+ expect(package.pipeline).to be_nil
+ end
+ end
+
+ context 'package with pipeline' do
+ let_it_be(:pipeline) { create(:ci_pipeline) }
+
+ before do
+ package.create_build_info!(pipeline: pipeline)
+ end
+
+ it 'returns the pipeline' do
+ expect(package.pipeline).to eq(pipeline)
+ end
+ end
+ end
+
+ describe '#tag_names' do
+ let_it_be(:package) { create(:nuget_package) }
+
+ subject { package.tag_names }
+
+ it { is_expected.to eq([]) }
+
+ context 'with tags' do
+ let(:tags) { %w(tag1 tag2 tag3) }
+
+ before do
+ tags.each { |t| create(:packages_tag, name: t, package: package) }
+ end
+
+ it { is_expected.to contain_exactly(*tags) }
+ end
+ end
+end
diff --git a/spec/models/packages/pypi/metadatum_spec.rb b/spec/models/packages/pypi/metadatum_spec.rb
new file mode 100644
index 00000000000..2c9893ef8f3
--- /dev/null
+++ b/spec/models/packages/pypi/metadatum_spec.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+RSpec.describe Packages::Pypi::Metadatum, type: :model do
+ describe 'relationships' do
+ it { is_expected.to belong_to(:package) }
+ end
+
+ describe 'validations' do
+ it { is_expected.to validate_presence_of(:package) }
+
+ describe '#pypi_package_type' do
+ it 'will not allow a package with a different package_type' do
+ package = build('package')
+ pypi_metadatum = build('pypi_metadatum', package: package)
+
+ expect(pypi_metadatum).not_to be_valid
+ expect(pypi_metadatum.errors.to_a).to include('Package type must be PyPi')
+ end
+ end
+ end
+end
diff --git a/spec/models/packages/sem_ver_spec.rb b/spec/models/packages/sem_ver_spec.rb
new file mode 100644
index 00000000000..419653dca19
--- /dev/null
+++ b/spec/models/packages/sem_ver_spec.rb
@@ -0,0 +1,42 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Packages::SemVer, type: :model do
+ shared_examples '#parse with a valid semver' do |str, major, minor, patch, prerelease, build|
+ context "with #{str}" do
+ it "returns #{described_class.new(major, minor, patch, prerelease, build, prefixed: true)} with prefix" do
+ expected = described_class.new(major, minor, patch, prerelease, build, prefixed: true)
+ expect(described_class.parse('v' + str, prefixed: true)).to eq(expected)
+ end
+
+ it "returns #{described_class.new(major, minor, patch, prerelease, build)} without prefix" do
+ expected = described_class.new(major, minor, patch, prerelease, build)
+ expect(described_class.parse(str)).to eq(expected)
+ end
+ end
+ end
+
+ shared_examples '#parse with an invalid semver' do |str|
+ context "with #{str}" do
+ it 'returns nil with prefix' do
+ expect(described_class.parse('v' + str, prefixed: true)).to be_nil
+ end
+
+ it 'returns nil without prefix' do
+ expect(described_class.parse(str)).to be_nil
+ end
+ end
+ end
+
+ describe '#parse' do
+ it_behaves_like '#parse with a valid semver', '1.0.0', 1, 0, 0, nil, nil
+ it_behaves_like '#parse with a valid semver', '1.0.0-pre', 1, 0, 0, 'pre', nil
+ it_behaves_like '#parse with a valid semver', '1.0.0+build', 1, 0, 0, nil, 'build'
+ it_behaves_like '#parse with a valid semver', '1.0.0-pre+build', 1, 0, 0, 'pre', 'build'
+ it_behaves_like '#parse with an invalid semver', '01.0.0'
+ it_behaves_like '#parse with an invalid semver', '0.01.0'
+ it_behaves_like '#parse with an invalid semver', '0.0.01'
+ it_behaves_like '#parse with an invalid semver', '1.0.0asdf'
+ end
+end
diff --git a/spec/models/packages/tag_spec.rb b/spec/models/packages/tag_spec.rb
new file mode 100644
index 00000000000..18ec99c3d51
--- /dev/null
+++ b/spec/models/packages/tag_spec.rb
@@ -0,0 +1,62 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+RSpec.describe Packages::Tag, type: :model do
+ let!(:project) { create(:project) }
+ let!(:package) { create(:npm_package, version: '1.0.2', project: project, updated_at: 3.days.ago) }
+
+ describe 'relationships' do
+ it { is_expected.to belong_to(:package).inverse_of(:tags) }
+ end
+
+ describe 'validations' do
+ subject { create(:packages_tag) }
+
+ it { is_expected.to validate_presence_of(:package) }
+ it { is_expected.to validate_presence_of(:name) }
+ end
+
+ describe '.for_packages' do
+ let(:package2) { create(:package, project: project, updated_at: 2.days.ago) }
+ let(:package3) { create(:package, project: project, updated_at: 1.day.ago) }
+ let!(:tag1) { create(:packages_tag, package: package) }
+ let!(:tag2) { create(:packages_tag, package: package2) }
+ let!(:tag3) { create(:packages_tag, package: package3) }
+
+ subject { described_class.for_packages(project.packages) }
+
+ it { is_expected.to match_array([tag1, tag2, tag3]) }
+
+ context 'with too many tags' do
+ before do
+ stub_const('Packages::Tag::FOR_PACKAGES_TAGS_LIMIT', 2)
+ end
+
+ it { is_expected.to match_array([tag2, tag3]) }
+ end
+ end
+
+ describe '.with_name' do
+ let_it_be(:package) { create(:package) }
+ let_it_be(:tag1) { create(:packages_tag, package: package, name: 'tag1') }
+ let_it_be(:tag2) { create(:packages_tag, package: package, name: 'tag2') }
+ let_it_be(:tag3) { create(:packages_tag, package: package, name: 'tag3') }
+ let(:name) { 'tag1' }
+
+ subject { described_class.with_name(name) }
+
+ it { is_expected.to contain_exactly(tag1) }
+
+ context 'with nil name' do
+ let(:name) { nil }
+
+ it { is_expected.to eq([]) }
+ end
+
+ context 'with multiple names' do
+ let(:name) { %w(tag1 tag3) }
+
+ it { is_expected.to contain_exactly(tag1, tag3) }
+ end
+ end
+end
diff --git a/spec/models/pages/lookup_path_spec.rb b/spec/models/pages/lookup_path_spec.rb
index c05d4c82634..38bd9b39a56 100644
--- a/spec/models/pages/lookup_path_spec.rb
+++ b/spec/models/pages/lookup_path_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Pages::LookupPath do
+RSpec.describe Pages::LookupPath do
let(:project) do
instance_double(Project,
id: 12345,
diff --git a/spec/models/pages/virtual_domain_spec.rb b/spec/models/pages/virtual_domain_spec.rb
index a5310738482..38f5f4d2538 100644
--- a/spec/models/pages/virtual_domain_spec.rb
+++ b/spec/models/pages/virtual_domain_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Pages::VirtualDomain do
+RSpec.describe Pages::VirtualDomain do
describe '#certificate and #key pair' do
let(:domain) { nil }
let(:project) { instance_double(Project) }
diff --git a/spec/models/pages_domain_acme_order_spec.rb b/spec/models/pages_domain_acme_order_spec.rb
index 4ffb4fc7389..4a104203e39 100644
--- a/spec/models/pages_domain_acme_order_spec.rb
+++ b/spec/models/pages_domain_acme_order_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe PagesDomainAcmeOrder do
+RSpec.describe PagesDomainAcmeOrder do
using RSpec::Parameterized::TableSyntax
describe '.expired' do
diff --git a/spec/models/pages_domain_spec.rb b/spec/models/pages_domain_spec.rb
index fc7694530d0..d283389e29e 100644
--- a/spec/models/pages_domain_spec.rb
+++ b/spec/models/pages_domain_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe PagesDomain do
+RSpec.describe PagesDomain do
using RSpec::Parameterized::TableSyntax
subject(:pages_domain) { described_class.new }
diff --git a/spec/models/performance_monitoring/prometheus_dashboard_spec.rb b/spec/models/performance_monitoring/prometheus_dashboard_spec.rb
index ef7298c3d8c..61174a7d0c5 100644
--- a/spec/models/performance_monitoring/prometheus_dashboard_spec.rb
+++ b/spec/models/performance_monitoring/prometheus_dashboard_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe PerformanceMonitoring::PrometheusDashboard do
+RSpec.describe PerformanceMonitoring::PrometheusDashboard do
let(:json_content) do
{
"dashboard" => "Dashboard Title",
@@ -50,19 +50,19 @@ describe PerformanceMonitoring::PrometheusDashboard do
context 'dashboard content is missing' do
let(:json_content) { nil }
- it_behaves_like 'validation failed', panel_groups: ["can't be blank"], dashboard: ["can't be blank"]
+ it_behaves_like 'validation failed', panel_groups: ["should be an array of panel_groups objects"], dashboard: ["can't be blank"]
end
context 'dashboard content is NOT a hash' do
let(:json_content) { YAML.safe_load("'test'") }
- it_behaves_like 'validation failed', panel_groups: ["can't be blank"], dashboard: ["can't be blank"]
+ it_behaves_like 'validation failed', panel_groups: ["should be an array of panel_groups objects"], dashboard: ["can't be blank"]
end
context 'content is an array' do
let(:json_content) { [{ "dashboard" => "Dashboard Title" }] }
- it_behaves_like 'validation failed', panel_groups: ["can't be blank"], dashboard: ["can't be blank"]
+ it_behaves_like 'validation failed', panel_groups: ["should be an array of panel_groups objects"], dashboard: ["can't be blank"]
end
context 'dashboard definition is missing panels_groups and dashboard keys' do
@@ -72,7 +72,7 @@ describe PerformanceMonitoring::PrometheusDashboard do
}
end
- it_behaves_like 'validation failed', panel_groups: ["can't be blank"], dashboard: ["can't be blank"]
+ it_behaves_like 'validation failed', panel_groups: ["should be an array of panel_groups objects"], dashboard: ["can't be blank"]
end
context 'group definition is missing panels and group keys' do
@@ -88,7 +88,7 @@ describe PerformanceMonitoring::PrometheusDashboard do
}
end
- it_behaves_like 'validation failed', panels: ["can't be blank"], group: ["can't be blank"]
+ it_behaves_like 'validation failed', panels: ["should be an array of panels objects"], group: ["can't be blank"]
end
context 'panel definition is missing metrics and title keys' do
@@ -110,7 +110,7 @@ describe PerformanceMonitoring::PrometheusDashboard do
}
end
- it_behaves_like 'validation failed', metrics: ["can't be blank"], title: ["can't be blank"]
+ it_behaves_like 'validation failed', metrics: ["should be an array of metrics objects"], title: ["can't be blank"]
end
context 'metrics definition is missing unit, query and query_range keys' do
@@ -180,7 +180,7 @@ describe PerformanceMonitoring::PrometheusDashboard do
describe '.find_for' do
let(:project) { build_stubbed(:project) }
let(:user) { build_stubbed(:user) }
- let(:environment) { build_stubbed(:environment) }
+ let(:environment) { build_stubbed(:environment, project: project) }
let(:path) { ::Metrics::Dashboard::SystemDashboardService::DASHBOARD_PATH }
context 'dashboard has been found' do
diff --git a/spec/models/performance_monitoring/prometheus_metric_spec.rb b/spec/models/performance_monitoring/prometheus_metric_spec.rb
index 83f687aa90e..b5b9cd58aa8 100644
--- a/spec/models/performance_monitoring/prometheus_metric_spec.rb
+++ b/spec/models/performance_monitoring/prometheus_metric_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe PerformanceMonitoring::PrometheusMetric do
+RSpec.describe PerformanceMonitoring::PrometheusMetric do
let(:json_content) do
{
"id" => "metric_of_ages",
diff --git a/spec/models/performance_monitoring/prometheus_panel_group_spec.rb b/spec/models/performance_monitoring/prometheus_panel_group_spec.rb
index ecf7e13a9a3..9e92cb27954 100644
--- a/spec/models/performance_monitoring/prometheus_panel_group_spec.rb
+++ b/spec/models/performance_monitoring/prometheus_panel_group_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe PerformanceMonitoring::PrometheusPanelGroup do
+RSpec.describe PerformanceMonitoring::PrometheusPanelGroup do
let(:json_content) do
{
"group" => "Group Title",
diff --git a/spec/models/performance_monitoring/prometheus_panel_spec.rb b/spec/models/performance_monitoring/prometheus_panel_spec.rb
index 127b9e8183a..c5c6b1fdafd 100644
--- a/spec/models/performance_monitoring/prometheus_panel_spec.rb
+++ b/spec/models/performance_monitoring/prometheus_panel_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe PerformanceMonitoring::PrometheusPanel do
+RSpec.describe PerformanceMonitoring::PrometheusPanel do
let(:json_content) do
{
"max_value" => 1,
diff --git a/spec/models/personal_access_token_spec.rb b/spec/models/personal_access_token_spec.rb
index a3f5eb38511..a39a37b605f 100644
--- a/spec/models/personal_access_token_spec.rb
+++ b/spec/models/personal_access_token_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe PersonalAccessToken do
+RSpec.describe PersonalAccessToken do
subject { described_class }
describe '.build' do
@@ -165,6 +165,7 @@ describe PersonalAccessToken do
let_it_be(:revoked_token) { create(:personal_access_token, revoked: true) }
let_it_be(:valid_token_and_notified) { create(:personal_access_token, expires_at: 2.days.from_now, expire_notification_delivered: true) }
let_it_be(:valid_token) { create(:personal_access_token, expires_at: 2.days.from_now) }
+ let_it_be(:long_expiry_token) { create(:personal_access_token, expires_at: '999999-12-31'.to_date) }
context 'in one day' do
it "doesn't have any tokens" do
@@ -187,10 +188,24 @@ describe PersonalAccessToken do
expect(described_class.without_impersonation).to contain_exactly(personal_access_token)
end
end
+
+ describe 'revoke scopes' do
+ let_it_be(:revoked_token) { create(:personal_access_token, :revoked) }
+ let_it_be(:non_revoked_token) { create(:personal_access_token, revoked: false) }
+ let_it_be(:non_revoked_token2) { create(:personal_access_token, revoked: nil) }
+
+ describe '.revoked' do
+ it { expect(described_class.revoked).to contain_exactly(revoked_token) }
+ end
+
+ describe '.not_revoked' do
+ it { expect(described_class.not_revoked).to contain_exactly(non_revoked_token, non_revoked_token2) }
+ end
+ end
end
describe '.simple_sorts' do
- it 'includes overriden keys' do
+ it 'includes overridden keys' do
expect(described_class.simple_sorts.keys).to include(*%w(expires_at_asc expires_at_desc))
end
end
diff --git a/spec/models/personal_snippet_spec.rb b/spec/models/personal_snippet_spec.rb
index fb96d6e8bc3..10d70fed1ee 100644
--- a/spec/models/personal_snippet_spec.rb
+++ b/spec/models/personal_snippet_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe PersonalSnippet do
+RSpec.describe PersonalSnippet do
describe '#embeddable?' do
[
{ snippet: :public, embeddable: true },
diff --git a/spec/models/plan_limits_spec.rb b/spec/models/plan_limits_spec.rb
index 1366f088623..831fd0dcbc3 100644
--- a/spec/models/plan_limits_spec.rb
+++ b/spec/models/plan_limits_spec.rb
@@ -2,57 +2,217 @@
require 'spec_helper'
-describe PlanLimits do
- let(:plan_limits) { create(:plan_limits) }
- let(:model) { ProjectHook }
- let(:count) { model.count }
+RSpec.describe PlanLimits do
+ let_it_be(:project) { create(:project) }
+ let_it_be(:plan_limits) { create(:plan_limits) }
+ let(:project_hooks_count) { 2 }
before do
- create(:project_hook)
+ create_list(:project_hook, project_hooks_count, project: project)
end
- context 'without plan limits configured' do
- describe '#exceeded?' do
- it 'does not exceed any relation offset' do
- expect(plan_limits.exceeded?(:project_hooks, model)).to be false
- expect(plan_limits.exceeded?(:project_hooks, count)).to be false
+ describe '#exceeded?' do
+ let(:alternate_limit) { double('an alternate limit value') }
+
+ subject(:exceeded_limit) { plan_limits.exceeded?(:project_hooks, limit_subject, alternate_limit: alternate_limit) }
+
+ before do
+ allow(plan_limits).to receive(:limit_for).with(:project_hooks, alternate_limit: alternate_limit).and_return(limit)
+ end
+
+ shared_examples_for 'comparing limits' do
+ context 'when limit for given name results to a disabled value' do
+ let(:limit) { nil }
+
+ it { is_expected.to eq(false) }
+ end
+
+ context 'when limit for given name results to a non-disabled value' do
+ context 'and given count is smaller than limit' do
+ let(:limit) { project_hooks_count + 1 }
+
+ it { is_expected.to eq(false) }
+ end
+
+ context 'and given count is equal to the limit' do
+ let(:limit) { project_hooks_count }
+
+ it { is_expected.to eq(true) }
+ end
+
+ context 'and given count is greater than the limit' do
+ let(:limit) { project_hooks_count - 1 }
+
+ it { is_expected.to eq(true) }
+ end
+ end
+ end
+
+ context 'when given limit subject is an integer' do
+ let(:limit_subject) { project.hooks.count }
+
+ it_behaves_like 'comparing limits'
+ end
+
+ context 'when given limit subject is an ActiveRecord::Relation' do
+ let(:limit_subject) { project.hooks }
+
+ it_behaves_like 'comparing limits'
+ end
+
+ context 'when given limit subject is something else' do
+ let(:limit_subject) { ProjectHook }
+ let(:limit) { 100 }
+
+ it 'raises an error' do
+ expect { exceeded_limit }.to raise_error(ArgumentError)
end
end
end
- context 'with plan limits configured' do
- before do
- plan_limits.update!(project_hooks: 2)
+ describe '#limit_for' do
+ let(:alternate_limit) { nil }
+
+ subject(:limit) { plan_limits.limit_for(:project_hooks, alternate_limit: alternate_limit) }
+
+ context 'when given limit name does not exist' do
+ it 'raises an error' do
+ expect { plan_limits.limit_for(:project_foo) }.to raise_error(described_class::LimitUndefinedError)
+ end
end
- describe '#exceeded?' do
- it 'does not exceed the relation offset' do
- expect(plan_limits.exceeded?(:project_hooks, model)).to be false
- expect(plan_limits.exceeded?(:project_hooks, count)).to be false
+ context 'when given limit name is disabled' do
+ before do
+ plan_limits.update!(project_hooks: 0)
+ end
+
+ it { is_expected.to eq(nil) }
+
+ context 'and alternate_limit is a non-zero integer' do
+ let(:alternate_limit) { 1 }
+
+ it { is_expected.to eq(1) }
+ end
+
+ context 'and alternate_limit is zero' do
+ let(:alternate_limit) { 0 }
+
+ it { is_expected.to eq(nil) }
+ end
+
+ context 'and alternate_limit is a proc that returns non-zero integer' do
+ let(:alternate_limit) { -> { 1 } }
+
+ it { is_expected.to eq(1) }
+ end
+
+ context 'and alternate_limit is a proc that returns zero' do
+ let(:alternate_limit) { -> { 0 } }
+
+ it { is_expected.to eq(nil) }
+ end
+
+ context 'and alternate_limit is a proc that returns nil' do
+ let(:alternate_limit) { -> { nil } }
+
+ it { is_expected.to eq(nil) }
end
end
- context 'with boundary values' do
+ context 'when given limit name is enabled' do
+ let(:plan_limit_value) { 2 }
+
before do
- create(:project_hook)
+ plan_limits.update!(project_hooks: plan_limit_value)
end
- describe '#exceeded?' do
- it 'does exceed the relation offset' do
- expect(plan_limits.exceeded?(:project_hooks, model)).to be true
- expect(plan_limits.exceeded?(:project_hooks, count)).to be true
- end
+ context 'and alternate_limit is a non-zero integer that is bigger than the plan limit' do
+ let(:alternate_limit) { plan_limit_value + 1 }
+
+ it { is_expected.to eq(plan_limit_value) }
+ end
+
+ context 'and alternate_limit is a non-zero integer that is smaller than the plan limit' do
+ let(:alternate_limit) { plan_limit_value - 1 }
+
+ it { is_expected.to eq(alternate_limit) }
+ end
+
+ context 'and alternate_limit is zero' do
+ let(:alternate_limit) { 0 }
+
+ it { is_expected.to eq(plan_limit_value) }
+ end
+
+ context 'and alternate_limit is a proc that returns non-zero integer that is bigger than the plan limit' do
+ let(:alternate_limit) { -> { plan_limit_value + 1 } }
+
+ it { is_expected.to eq(plan_limit_value) }
+ end
+
+ context 'and alternate_limit is a proc that returns non-zero integer that is smaller than the plan limit' do
+ let(:alternate_limit) { -> { plan_limit_value - 1 } }
+
+ it { is_expected.to eq(alternate_limit.call) }
+ end
+
+ context 'and alternate_limit is a proc that returns zero' do
+ let(:alternate_limit) { -> { 0 } }
+
+ it { is_expected.to eq(plan_limit_value) }
+ end
+
+ context 'and alternate_limit is a proc that returns nil' do
+ let(:alternate_limit) { -> { nil } }
+
+ it { is_expected.to eq(plan_limit_value) }
end
end
end
context 'validates default values' do
+ # TODO: For now, these columns have default values set to 0.
+ # Each artifact type listed here have their own matching issues to determine
+ # the actual limit value. In each of those issues, the default value should also be updated to
+ # a non-zero value. Also update existing values of zero to whatever the default value will be.
+ # For a list of the issues, see: https://gitlab.com/gitlab-org/gitlab/-/issues/211378#note_355619970
+ let(:disabled_max_artifact_size_columns) do
+ %w[
+ ci_max_artifact_size_archive
+ ci_max_artifact_size_metadata
+ ci_max_artifact_size_trace
+ ci_max_artifact_size_junit
+ ci_max_artifact_size_sast
+ ci_max_artifact_size_dependency_scanning
+ ci_max_artifact_size_container_scanning
+ ci_max_artifact_size_dast
+ ci_max_artifact_size_codequality
+ ci_max_artifact_size_license_management
+ ci_max_artifact_size_license_scanning
+ ci_max_artifact_size_performance
+ ci_max_artifact_size_browser_performance
+ ci_max_artifact_size_load_performance
+ ci_max_artifact_size_metrics
+ ci_max_artifact_size_metrics_referee
+ ci_max_artifact_size_network_referee
+ ci_max_artifact_size_dotenv
+ ci_max_artifact_size_cobertura
+ ci_max_artifact_size_terraform
+ ci_max_artifact_size_accessibility
+ ci_max_artifact_size_cluster_applications
+ ci_max_artifact_size_secret_detection
+ ci_max_artifact_size_requirements
+ ci_max_artifact_size_coverage_fuzzing
+ ]
+ end
+
let(:columns_with_zero) do
%w[
ci_active_pipelines
ci_pipeline_size
ci_active_jobs
- ]
+ storage_size_limit
+ ] + disabled_max_artifact_size_columns
end
it "has positive values for enabled limits" do
diff --git a/spec/models/plan_spec.rb b/spec/models/plan_spec.rb
index 3f3b8046232..490c6b1bbf7 100644
--- a/spec/models/plan_spec.rb
+++ b/spec/models/plan_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Plan do
+RSpec.describe Plan do
describe '#default?' do
subject { plan.default? }
@@ -14,4 +14,16 @@ describe Plan do
end
end
end
+
+ context 'when updating plan limits' do
+ let(:plan) { described_class.default }
+
+ it { expect(plan).to be_persisted }
+
+ it { expect(plan.actual_limits).not_to be_persisted }
+
+ it 'successfully updates the limits' do
+ expect(plan.actual_limits.update!(ci_instance_level_variables: 100)).to be_truthy
+ end
+ end
end
diff --git a/spec/models/pool_repository_spec.rb b/spec/models/pool_repository_spec.rb
index ae00f9df89e..92b3e41cd18 100644
--- a/spec/models/pool_repository_spec.rb
+++ b/spec/models/pool_repository_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe PoolRepository do
+RSpec.describe PoolRepository do
describe 'associations' do
it { is_expected.to belong_to(:shard) }
it { is_expected.to belong_to(:source_project) }
diff --git a/spec/models/postgresql/replication_slot_spec.rb b/spec/models/postgresql/replication_slot_spec.rb
index d435fccc09a..02a4d783b84 100644
--- a/spec/models/postgresql/replication_slot_spec.rb
+++ b/spec/models/postgresql/replication_slot_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Postgresql::ReplicationSlot do
+RSpec.describe Postgresql::ReplicationSlot do
describe '.in_use?' do
it 'returns true when replication slots are present' do
expect(described_class).to receive(:exists?).and_return(true)
diff --git a/spec/models/product_analytics_event_spec.rb b/spec/models/product_analytics_event_spec.rb
new file mode 100644
index 00000000000..6058df9fa13
--- /dev/null
+++ b/spec/models/product_analytics_event_spec.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+RSpec.describe ProductAnalyticsEvent, type: :model do
+ it { is_expected.to belong_to(:project) }
+ it { expect(described_class).to respond_to(:order_by_time) }
+
+ describe 'validations' do
+ it { is_expected.to validate_presence_of(:project_id) }
+ it { is_expected.to validate_presence_of(:event_id) }
+ it { is_expected.to validate_presence_of(:v_collector) }
+ it { is_expected.to validate_presence_of(:v_etl) }
+ end
+
+ describe '.timerange' do
+ let_it_be(:event_1) { create(:product_analytics_event, collector_tstamp: Time.zone.now - 1.day) }
+ let_it_be(:event_2) { create(:product_analytics_event, collector_tstamp: Time.zone.now - 5.days) }
+ let_it_be(:event_3) { create(:product_analytics_event, collector_tstamp: Time.zone.now - 15.days) }
+
+ it { expect(described_class.timerange(3.days)).to match_array([event_1]) }
+ it { expect(described_class.timerange(7.days)).to match_array([event_1, event_2]) }
+ it { expect(described_class.timerange(30.days)).to match_array([event_1, event_2, event_3]) }
+ end
+end
diff --git a/spec/models/programming_language_spec.rb b/spec/models/programming_language_spec.rb
index b327d360461..f2201eabd1c 100644
--- a/spec/models/programming_language_spec.rb
+++ b/spec/models/programming_language_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ProgrammingLanguage do
+RSpec.describe ProgrammingLanguage do
it { is_expected.to respond_to(:name) }
it { is_expected.to respond_to(:color) }
diff --git a/spec/models/project_authorization_spec.rb b/spec/models/project_authorization_spec.rb
index 6f06fe4e55a..c517fc8be55 100644
--- a/spec/models/project_authorization_spec.rb
+++ b/spec/models/project_authorization_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ProjectAuthorization do
+RSpec.describe ProjectAuthorization do
let(:user) { create(:user) }
let(:project1) { create(:project) }
let(:project2) { create(:project) }
diff --git a/spec/models/project_auto_devops_spec.rb b/spec/models/project_auto_devops_spec.rb
index 5af25ac1437..8313879114f 100644
--- a/spec/models/project_auto_devops_spec.rb
+++ b/spec/models/project_auto_devops_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ProjectAutoDevops do
+RSpec.describe ProjectAutoDevops do
let_it_be(:project) { build(:project) }
it_behaves_like 'having unique enum values'
diff --git a/spec/models/project_ci_cd_setting_spec.rb b/spec/models/project_ci_cd_setting_spec.rb
index ecca371ce4e..698465e854a 100644
--- a/spec/models/project_ci_cd_setting_spec.rb
+++ b/spec/models/project_ci_cd_setting_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ProjectCiCdSetting do
+RSpec.describe ProjectCiCdSetting do
describe 'validations' do
it 'validates default_git_depth is between 0 and 1000 or nil' do
expect(subject).to validate_numericality_of(:default_git_depth)
diff --git a/spec/models/project_custom_attribute_spec.rb b/spec/models/project_custom_attribute_spec.rb
index 80638676b49..25ee1e60819 100644
--- a/spec/models/project_custom_attribute_spec.rb
+++ b/spec/models/project_custom_attribute_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ProjectCustomAttribute do
+RSpec.describe ProjectCustomAttribute do
describe 'assocations' do
it { is_expected.to belong_to(:project) }
end
diff --git a/spec/models/project_daily_statistic_spec.rb b/spec/models/project_daily_statistic_spec.rb
index 86210af15d8..8dbabdb3829 100644
--- a/spec/models/project_daily_statistic_spec.rb
+++ b/spec/models/project_daily_statistic_spec.rb
@@ -2,6 +2,6 @@
require 'spec_helper'
-describe ProjectDailyStatistic do
+RSpec.describe ProjectDailyStatistic do
it { is_expected.to belong_to(:project) }
end
diff --git a/spec/models/project_export_job_spec.rb b/spec/models/project_export_job_spec.rb
index dc39d0e401d..5a2b1443f8b 100644
--- a/spec/models/project_export_job_spec.rb
+++ b/spec/models/project_export_job_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ProjectExportJob, type: :model do
+RSpec.describe ProjectExportJob, type: :model do
let(:project) { create(:project) }
let!(:job1) { create(:project_export_job, project: project, status: 0) }
let!(:job2) { create(:project_export_job, project: project, status: 2) }
diff --git a/spec/models/project_feature_spec.rb b/spec/models/project_feature_spec.rb
index e33ea75bc5d..c927c8fd1f9 100644
--- a/spec/models/project_feature_spec.rb
+++ b/spec/models/project_feature_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ProjectFeature do
+RSpec.describe ProjectFeature do
using RSpec::Parameterized::TableSyntax
let(:project) { create(:project) }
diff --git a/spec/models/project_group_link_spec.rb b/spec/models/project_group_link_spec.rb
index 8ef29e8a876..c925d87170c 100644
--- a/spec/models/project_group_link_spec.rb
+++ b/spec/models/project_group_link_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ProjectGroupLink do
+RSpec.describe ProjectGroupLink do
describe "Associations" do
it { is_expected.to belong_to(:group) }
it { is_expected.to belong_to(:project) }
diff --git a/spec/models/project_import_data_spec.rb b/spec/models/project_import_data_spec.rb
index fe47811f074..50a2ee42084 100644
--- a/spec/models/project_import_data_spec.rb
+++ b/spec/models/project_import_data_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ProjectImportData do
+RSpec.describe ProjectImportData do
describe '#merge_data' do
it 'writes the Hash to the attribute if it is nil' do
row = described_class.new
diff --git a/spec/models/project_import_state_spec.rb b/spec/models/project_import_state_spec.rb
index f3b83c036b5..6a0402d43a8 100644
--- a/spec/models/project_import_state_spec.rb
+++ b/spec/models/project_import_state_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ProjectImportState, type: :model do
+RSpec.describe ProjectImportState, type: :model do
let_it_be(:correlation_id) { 'cid' }
let_it_be(:import_state, refind: true) { create(:import_state, correlation_id_value: correlation_id) }
diff --git a/spec/models/project_label_spec.rb b/spec/models/project_label_spec.rb
index 330aab9f856..f451c2905e6 100644
--- a/spec/models/project_label_spec.rb
+++ b/spec/models/project_label_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ProjectLabel do
+RSpec.describe ProjectLabel do
describe 'relationships' do
it { is_expected.to belong_to(:project) }
end
diff --git a/spec/models/project_metrics_setting_spec.rb b/spec/models/project_metrics_setting_spec.rb
index adfbbbc3a45..6639f9cb208 100644
--- a/spec/models/project_metrics_setting_spec.rb
+++ b/spec/models/project_metrics_setting_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ProjectMetricsSetting do
+RSpec.describe ProjectMetricsSetting do
describe 'Associations' do
it { is_expected.to belong_to(:project) }
end
diff --git a/spec/models/project_repository_spec.rb b/spec/models/project_repository_spec.rb
index c966447fedc..6852ca0097d 100644
--- a/spec/models/project_repository_spec.rb
+++ b/spec/models/project_repository_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ProjectRepository do
+RSpec.describe ProjectRepository do
describe 'associations' do
it { is_expected.to belong_to(:shard) }
it { is_expected.to belong_to(:project) }
diff --git a/spec/models/project_services/alerts_service_spec.rb b/spec/models/project_services/alerts_service_spec.rb
index 4e63ece26d8..db25885c76a 100644
--- a/spec/models/project_services/alerts_service_spec.rb
+++ b/spec/models/project_services/alerts_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe AlertsService do
+RSpec.describe AlertsService do
let_it_be(:project) { create(:project) }
let(:service_params) { { project: project, active: active } }
let(:active) { true }
diff --git a/spec/models/project_services/asana_service_spec.rb b/spec/models/project_services/asana_service_spec.rb
index 8b6f2888c0a..7a6fe4b1537 100644
--- a/spec/models/project_services/asana_service_spec.rb
+++ b/spec/models/project_services/asana_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe AsanaService do
+RSpec.describe AsanaService do
describe 'Associations' do
it { is_expected.to belong_to :project }
it { is_expected.to have_one :service_hook }
diff --git a/spec/models/project_services/assembla_service_spec.rb b/spec/models/project_services/assembla_service_spec.rb
index 2c86c0ec7be..207add6f090 100644
--- a/spec/models/project_services/assembla_service_spec.rb
+++ b/spec/models/project_services/assembla_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe AssemblaService do
+RSpec.describe AssemblaService do
include StubRequests
describe "Associations" do
diff --git a/spec/models/project_services/bamboo_service_spec.rb b/spec/models/project_services/bamboo_service_spec.rb
index c1efa3a4348..4d2474cc56a 100644
--- a/spec/models/project_services/bamboo_service_spec.rb
+++ b/spec/models/project_services/bamboo_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe BambooService, :use_clean_rails_memory_store_caching do
+RSpec.describe BambooService, :use_clean_rails_memory_store_caching do
include ReactiveCachingHelpers
include StubRequests
diff --git a/spec/models/project_services/bugzilla_service_spec.rb b/spec/models/project_services/bugzilla_service_spec.rb
index ab939e0d2f8..560c7c3ee83 100644
--- a/spec/models/project_services/bugzilla_service_spec.rb
+++ b/spec/models/project_services/bugzilla_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe BugzillaService do
+RSpec.describe BugzillaService do
describe 'Associations' do
it { is_expected.to belong_to :project }
it { is_expected.to have_one :service_hook }
@@ -32,49 +32,4 @@ describe BugzillaService do
it { is_expected.not_to validate_presence_of(:new_issue_url) }
end
end
-
- context 'overriding properties' do
- let(:url) { 'http://bugzilla.example.com' }
- let(:access_params) do
- { project_url: url, issues_url: url, new_issue_url: url }
- end
-
- # this will be removed as part of https://gitlab.com/gitlab-org/gitlab/issues/29404
- context 'when data are stored in properties' do
- let(:properties) { access_params.merge(title: title, description: description) }
- let(:service) do
- create(:bugzilla_service, :without_properties_callback, properties: properties)
- end
-
- it_behaves_like 'issue tracker fields'
- end
-
- context 'when data are stored in separated fields' do
- let(:service) do
- create(:bugzilla_service, title: title, description: description, properties: access_params)
- end
-
- it_behaves_like 'issue tracker fields'
- end
-
- context 'when data are stored in both properties and separated fields' do
- let(:properties) { access_params.merge(title: 'wrong title', description: 'wrong description') }
- let(:service) do
- create(:bugzilla_service, :without_properties_callback, title: title, description: description, properties: properties)
- end
-
- it_behaves_like 'issue tracker fields'
- end
-
- context 'when no title & description are set' do
- let(:service) do
- create(:bugzilla_service, properties: access_params)
- end
-
- it 'returns default values' do
- expect(service.title).to eq('Bugzilla')
- expect(service.description).to eq('Bugzilla issue tracker')
- end
- end
- end
end
diff --git a/spec/models/project_services/buildkite_service_spec.rb b/spec/models/project_services/buildkite_service_spec.rb
index 8b6fa36eaa5..ff717a59e7b 100644
--- a/spec/models/project_services/buildkite_service_spec.rb
+++ b/spec/models/project_services/buildkite_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe BuildkiteService, :use_clean_rails_memory_store_caching do
+RSpec.describe BuildkiteService, :use_clean_rails_memory_store_caching do
include ReactiveCachingHelpers
include StubRequests
diff --git a/spec/models/project_services/campfire_service_spec.rb b/spec/models/project_services/campfire_service_spec.rb
index 0d3dd89e93b..ea3990b339b 100644
--- a/spec/models/project_services/campfire_service_spec.rb
+++ b/spec/models/project_services/campfire_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe CampfireService do
+RSpec.describe CampfireService do
include StubRequests
describe 'Associations' do
diff --git a/spec/models/project_services/chat_message/alert_message_spec.rb b/spec/models/project_services/chat_message/alert_message_spec.rb
index a1dd332c005..927c5dffe77 100644
--- a/spec/models/project_services/chat_message/alert_message_spec.rb
+++ b/spec/models/project_services/chat_message/alert_message_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ChatMessage::AlertMessage do
+RSpec.describe ChatMessage::AlertMessage do
subject { described_class.new(args) }
let_it_be(:start_time) { Time.current }
diff --git a/spec/models/project_services/chat_message/base_message_spec.rb b/spec/models/project_services/chat_message/base_message_spec.rb
index 8f80cf0b074..a7ddf230758 100644
--- a/spec/models/project_services/chat_message/base_message_spec.rb
+++ b/spec/models/project_services/chat_message/base_message_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ChatMessage::BaseMessage do
+RSpec.describe ChatMessage::BaseMessage do
let(:base_message) { described_class.new(args) }
let(:args) { { project_url: 'https://gitlab-domain.com' } }
diff --git a/spec/models/project_services/chat_message/deployment_message_spec.rb b/spec/models/project_services/chat_message/deployment_message_spec.rb
index 42c1689db3d..9c361f90ae0 100644
--- a/spec/models/project_services/chat_message/deployment_message_spec.rb
+++ b/spec/models/project_services/chat_message/deployment_message_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ChatMessage::DeploymentMessage do
+RSpec.describe ChatMessage::DeploymentMessage do
describe '#pretext' do
it 'returns a message with the data returned by the deployment data builder' do
environment = create(:environment, name: "myenvironment")
diff --git a/spec/models/project_services/chat_message/issue_message_spec.rb b/spec/models/project_services/chat_message/issue_message_spec.rb
index c4d10be8331..051f4780ba4 100644
--- a/spec/models/project_services/chat_message/issue_message_spec.rb
+++ b/spec/models/project_services/chat_message/issue_message_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ChatMessage::IssueMessage do
+RSpec.describe ChatMessage::IssueMessage do
subject { described_class.new(args) }
let(:args) do
diff --git a/spec/models/project_services/chat_message/merge_message_spec.rb b/spec/models/project_services/chat_message/merge_message_spec.rb
index 6063ef4ecb3..45be5212508 100644
--- a/spec/models/project_services/chat_message/merge_message_spec.rb
+++ b/spec/models/project_services/chat_message/merge_message_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ChatMessage::MergeMessage do
+RSpec.describe ChatMessage::MergeMessage do
subject { described_class.new(args) }
let(:args) do
diff --git a/spec/models/project_services/chat_message/note_message_spec.rb b/spec/models/project_services/chat_message/note_message_spec.rb
index 5e7987dc0f6..6a741365d55 100644
--- a/spec/models/project_services/chat_message/note_message_spec.rb
+++ b/spec/models/project_services/chat_message/note_message_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ChatMessage::NoteMessage do
+RSpec.describe ChatMessage::NoteMessage do
subject { described_class.new(args) }
let(:color) { '#345' }
diff --git a/spec/models/project_services/chat_message/pipeline_message_spec.rb b/spec/models/project_services/chat_message/pipeline_message_spec.rb
index a7171577063..4eb2f57315b 100644
--- a/spec/models/project_services/chat_message/pipeline_message_spec.rb
+++ b/spec/models/project_services/chat_message/pipeline_message_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe ChatMessage::PipelineMessage do
+RSpec.describe ChatMessage::PipelineMessage do
subject { described_class.new(args) }
let(:args) do
diff --git a/spec/models/project_services/chat_message/push_message_spec.rb b/spec/models/project_services/chat_message/push_message_spec.rb
index 9d990508ab2..e3ba4c2aefe 100644
--- a/spec/models/project_services/chat_message/push_message_spec.rb
+++ b/spec/models/project_services/chat_message/push_message_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ChatMessage::PushMessage do
+RSpec.describe ChatMessage::PushMessage do
subject { described_class.new(args) }
let(:args) do
diff --git a/spec/models/project_services/chat_message/wiki_page_message_spec.rb b/spec/models/project_services/chat_message/wiki_page_message_spec.rb
index 1346a43335e..04c9e5934be 100644
--- a/spec/models/project_services/chat_message/wiki_page_message_spec.rb
+++ b/spec/models/project_services/chat_message/wiki_page_message_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ChatMessage::WikiPageMessage do
+RSpec.describe ChatMessage::WikiPageMessage do
subject { described_class.new(args) }
let(:args) do
diff --git a/spec/models/project_services/chat_notification_service_spec.rb b/spec/models/project_services/chat_notification_service_spec.rb
index 1caec5c6eb7..77a1377c138 100644
--- a/spec/models/project_services/chat_notification_service_spec.rb
+++ b/spec/models/project_services/chat_notification_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ChatNotificationService do
+RSpec.describe ChatNotificationService do
describe 'Associations' do
before do
allow(subject).to receive(:activated?).and_return(true)
diff --git a/spec/models/project_services/confluence_service_spec.rb b/spec/models/project_services/confluence_service_spec.rb
new file mode 100644
index 00000000000..5d153b17070
--- /dev/null
+++ b/spec/models/project_services/confluence_service_spec.rb
@@ -0,0 +1,90 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe ConfluenceService do
+ describe 'Associations' do
+ it { is_expected.to belong_to :project }
+ it { is_expected.to have_one :service_hook }
+ end
+
+ describe 'Validations' do
+ before do
+ subject.active = active
+ end
+
+ context 'when service is active' do
+ let(:active) { true }
+
+ it { is_expected.not_to allow_value('https://example.com').for(:confluence_url) }
+ it { is_expected.not_to allow_value('example.com').for(:confluence_url) }
+ it { is_expected.not_to allow_value('foo').for(:confluence_url) }
+ it { is_expected.not_to allow_value('ftp://example.atlassian.net/wiki').for(:confluence_url) }
+ it { is_expected.not_to allow_value('https://example.atlassian.net').for(:confluence_url) }
+ it { is_expected.not_to allow_value('https://.atlassian.net/wiki').for(:confluence_url) }
+ it { is_expected.not_to allow_value('https://example.atlassian.net/wikifoo').for(:confluence_url) }
+ it { is_expected.not_to allow_value('').for(:confluence_url) }
+ it { is_expected.not_to allow_value(nil).for(:confluence_url) }
+ it { is_expected.not_to allow_value('😊').for(:confluence_url) }
+ it { is_expected.to allow_value('https://example.atlassian.net/wiki').for(:confluence_url) }
+ it { is_expected.to allow_value('http://example.atlassian.net/wiki').for(:confluence_url) }
+ it { is_expected.to allow_value('https://example.atlassian.net/wiki/').for(:confluence_url) }
+ it { is_expected.to allow_value('http://example.atlassian.net/wiki/').for(:confluence_url) }
+ it { is_expected.to allow_value('https://example.atlassian.net/wiki/foo').for(:confluence_url) }
+
+ it { is_expected.to validate_presence_of(:confluence_url) }
+ end
+
+ context 'when service is inactive' do
+ let(:active) { false }
+
+ it { is_expected.not_to validate_presence_of(:confluence_url) }
+ it { is_expected.to allow_value('foo').for(:confluence_url) }
+ end
+ end
+
+ describe '#detailed_description' do
+ it 'can correctly return a link to the project wiki when active' do
+ project = create(:project)
+ subject.project = project
+ subject.active = true
+
+ expect(subject.detailed_description).to include(Gitlab::Routing.url_helpers.project_wikis_url(project))
+ end
+
+ context 'when the project wiki is not enabled' do
+ it 'returns nil when both active or inactive', :aggregate_failures do
+ project = create(:project, :wiki_disabled)
+ subject.project = project
+
+ [true, false].each do |active|
+ subject.active = active
+
+ expect(subject.detailed_description).to be_nil
+ end
+ end
+ end
+ end
+
+ describe 'Caching has_confluence on project_settings' do
+ let(:project) { create(:project) }
+
+ subject { project.project_setting.has_confluence? }
+
+ it 'sets the property to true when service is active' do
+ create(:confluence_service, project: project, active: true)
+
+ is_expected.to be(true)
+ end
+
+ it 'sets the property to false when service is not active' do
+ create(:confluence_service, project: project, active: false)
+
+ is_expected.to be(false)
+ end
+
+ it 'creates a project_setting record if one was not already created' do
+ expect { create(:confluence_service) }.to change { ProjectSetting.count }.by(1)
+ end
+ end
+end
diff --git a/spec/models/project_services/custom_issue_tracker_service_spec.rb b/spec/models/project_services/custom_issue_tracker_service_spec.rb
index e749ea6eacc..881ae60a680 100644
--- a/spec/models/project_services/custom_issue_tracker_service_spec.rb
+++ b/spec/models/project_services/custom_issue_tracker_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe CustomIssueTrackerService do
+RSpec.describe CustomIssueTrackerService do
describe 'Associations' do
it { is_expected.to belong_to :project }
it { is_expected.to have_one :service_hook }
@@ -31,66 +31,5 @@ describe CustomIssueTrackerService do
it { is_expected.not_to validate_presence_of(:issues_url) }
it { is_expected.not_to validate_presence_of(:new_issue_url) }
end
-
- context 'title' do
- let(:issue_tracker) { described_class.new(properties: {}) }
-
- it 'sets a default title' do
- issue_tracker.title = nil
-
- expect(issue_tracker.title).to eq('Custom Issue Tracker')
- end
-
- it 'sets the custom title' do
- issue_tracker.title = 'test title'
-
- expect(issue_tracker.title).to eq('test title')
- end
- end
- end
-
- context 'overriding properties' do
- let(:url) { 'http://custom.example.com' }
- let(:access_params) do
- { project_url: url, issues_url: url, new_issue_url: url }
- end
-
- # this will be removed as part of https://gitlab.com/gitlab-org/gitlab/issues/29404
- context 'when data are stored in properties' do
- let(:properties) { access_params.merge(title: title, description: description) }
- let(:service) do
- create(:custom_issue_tracker_service, :without_properties_callback, properties: properties)
- end
-
- it_behaves_like 'issue tracker fields'
- end
-
- context 'when data are stored in separated fields' do
- let(:service) do
- create(:custom_issue_tracker_service, title: title, description: description, properties: access_params)
- end
-
- it_behaves_like 'issue tracker fields'
- end
-
- context 'when data are stored in both properties and separated fields' do
- let(:properties) { access_params.merge(title: 'wrong title', description: 'wrong description') }
- let(:service) do
- create(:custom_issue_tracker_service, :without_properties_callback, title: title, description: description, properties: properties)
- end
-
- it_behaves_like 'issue tracker fields'
- end
-
- context 'when no title & description are set' do
- let(:service) do
- create(:custom_issue_tracker_service, properties: access_params)
- end
-
- it 'returns default values' do
- expect(service.title).to eq('Custom Issue Tracker')
- expect(service.description).to eq('Custom issue tracker')
- end
- end
end
end
diff --git a/spec/models/project_services/data_fields_spec.rb b/spec/models/project_services/data_fields_spec.rb
index 6b388a7222b..9a3042f9f8d 100644
--- a/spec/models/project_services/data_fields_spec.rb
+++ b/spec/models/project_services/data_fields_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe DataFields do
+RSpec.describe DataFields do
let(:url) { 'http://url.com' }
let(:username) { 'username_one' }
let(:properties) do
diff --git a/spec/models/project_services/discord_service_spec.rb b/spec/models/project_services/discord_service_spec.rb
index b5a54676dd7..d4bd08ddeb6 100644
--- a/spec/models/project_services/discord_service_spec.rb
+++ b/spec/models/project_services/discord_service_spec.rb
@@ -2,7 +2,7 @@
require "spec_helper"
-describe DiscordService do
+RSpec.describe DiscordService do
it_behaves_like "chat service", "Discord notifications" do
let(:client) { Discordrb::Webhooks::Client }
let(:client_arguments) { { url: webhook_url } }
diff --git a/spec/models/project_services/drone_ci_service_spec.rb b/spec/models/project_services/drone_ci_service_spec.rb
index 1ee9c5f90c6..9aaf4f7a644 100644
--- a/spec/models/project_services/drone_ci_service_spec.rb
+++ b/spec/models/project_services/drone_ci_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe DroneCiService, :use_clean_rails_memory_store_caching do
+RSpec.describe DroneCiService, :use_clean_rails_memory_store_caching do
include ReactiveCachingHelpers
describe 'associations' do
diff --git a/spec/models/project_services/emails_on_push_service_spec.rb b/spec/models/project_services/emails_on_push_service_spec.rb
index 44db95afc57..6954a72f9c1 100644
--- a/spec/models/project_services/emails_on_push_service_spec.rb
+++ b/spec/models/project_services/emails_on_push_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe EmailsOnPushService do
+RSpec.describe EmailsOnPushService do
describe 'Validations' do
context 'when service is active' do
before do
diff --git a/spec/models/project_services/external_wiki_service_spec.rb b/spec/models/project_services/external_wiki_service_spec.rb
index f8d88a944a5..c6891401a0f 100644
--- a/spec/models/project_services/external_wiki_service_spec.rb
+++ b/spec/models/project_services/external_wiki_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ExternalWikiService do
+RSpec.describe ExternalWikiService do
describe "Associations" do
it { is_expected.to belong_to :project }
it { is_expected.to have_one :service_hook }
diff --git a/spec/models/project_services/flowdock_service_spec.rb b/spec/models/project_services/flowdock_service_spec.rb
index c1ebe69ee66..94a49fb3080 100644
--- a/spec/models/project_services/flowdock_service_spec.rb
+++ b/spec/models/project_services/flowdock_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe FlowdockService do
+RSpec.describe FlowdockService do
describe "Associations" do
it { is_expected.to belong_to :project }
it { is_expected.to have_one :service_hook }
diff --git a/spec/models/project_services/gitlab_issue_tracker_service_spec.rb b/spec/models/project_services/gitlab_issue_tracker_service_spec.rb
index 7f1c6224b7d..a6b7cb05836 100644
--- a/spec/models/project_services/gitlab_issue_tracker_service_spec.rb
+++ b/spec/models/project_services/gitlab_issue_tracker_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GitlabIssueTrackerService do
+RSpec.describe GitlabIssueTrackerService do
describe "Associations" do
it { is_expected.to belong_to :project }
it { is_expected.to have_one :service_hook }
@@ -51,49 +51,4 @@ describe GitlabIssueTrackerService do
end
end
end
-
- context 'overriding properties' do
- let(:url) { 'http://gitlab.example.com' }
- let(:access_params) do
- { project_url: url, issues_url: url, new_issue_url: url }
- end
-
- # this will be removed as part of https://gitlab.com/gitlab-org/gitlab/issues/29404
- context 'when data are stored in properties' do
- let(:properties) { access_params.merge(title: title, description: description) }
- let(:service) do
- create(:gitlab_issue_tracker_service, :without_properties_callback, properties: properties)
- end
-
- it_behaves_like 'issue tracker fields'
- end
-
- context 'when data are stored in separated fields' do
- let(:service) do
- create(:gitlab_issue_tracker_service, title: title, description: description, properties: access_params)
- end
-
- it_behaves_like 'issue tracker fields'
- end
-
- context 'when data are stored in both properties and separated fields' do
- let(:properties) { access_params.merge(title: 'wrong title', description: 'wrong description') }
- let(:service) do
- create(:gitlab_issue_tracker_service, :without_properties_callback, title: title, description: description, properties: properties)
- end
-
- it_behaves_like 'issue tracker fields'
- end
-
- context 'when no title & description are set' do
- let(:service) do
- create(:gitlab_issue_tracker_service, properties: access_params)
- end
-
- it 'returns default values' do
- expect(service.title).to eq('GitLab')
- expect(service.description).to eq('GitLab issue tracker')
- end
- end
- end
end
diff --git a/spec/models/project_services/hangouts_chat_service_spec.rb b/spec/models/project_services/hangouts_chat_service_spec.rb
index 0505ac9b49c..042e32439d1 100644
--- a/spec/models/project_services/hangouts_chat_service_spec.rb
+++ b/spec/models/project_services/hangouts_chat_service_spec.rb
@@ -2,7 +2,7 @@
require "spec_helper"
-describe HangoutsChatService do
+RSpec.describe HangoutsChatService do
it_behaves_like "chat service", "Hangouts Chat" do
let(:client) { HangoutsChat::Sender }
let(:client_arguments) { webhook_url }
diff --git a/spec/models/project_services/hipchat_service_spec.rb b/spec/models/project_services/hipchat_service_spec.rb
index c25edf81352..667e6cc85ab 100644
--- a/spec/models/project_services/hipchat_service_spec.rb
+++ b/spec/models/project_services/hipchat_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe HipchatService do
+RSpec.describe HipchatService do
describe "Associations" do
it { is_expected.to belong_to :project }
it { is_expected.to have_one :service_hook }
diff --git a/spec/models/project_services/irker_service_spec.rb b/spec/models/project_services/irker_service_spec.rb
index 88a93eef214..07963947de8 100644
--- a/spec/models/project_services/irker_service_spec.rb
+++ b/spec/models/project_services/irker_service_spec.rb
@@ -4,7 +4,7 @@ require 'spec_helper'
require 'socket'
require 'json'
-describe IrkerService do
+RSpec.describe IrkerService do
describe 'Associations' do
it { is_expected.to belong_to :project }
it { is_expected.to have_one :service_hook }
diff --git a/spec/models/project_services/issue_tracker_data_spec.rb b/spec/models/project_services/issue_tracker_data_spec.rb
index db617cf0abb..3ddb7d9250f 100644
--- a/spec/models/project_services/issue_tracker_data_spec.rb
+++ b/spec/models/project_services/issue_tracker_data_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe IssueTrackerData do
+RSpec.describe IssueTrackerData do
let(:service) { create(:custom_issue_tracker_service, active: false, properties: {}) }
describe 'Associations' do
diff --git a/spec/models/project_services/issue_tracker_service_spec.rb b/spec/models/project_services/issue_tracker_service_spec.rb
index f1cdee5c4a3..5b12c7330b8 100644
--- a/spec/models/project_services/issue_tracker_service_spec.rb
+++ b/spec/models/project_services/issue_tracker_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe IssueTrackerService do
+RSpec.describe IssueTrackerService do
describe 'Validations' do
let(:project) { create :project }
diff --git a/spec/models/project_services/jira_service_spec.rb b/spec/models/project_services/jira_service_spec.rb
index 20e85f0fd4b..cfc2c920cd2 100644
--- a/spec/models/project_services/jira_service_spec.rb
+++ b/spec/models/project_services/jira_service_spec.rb
@@ -2,11 +2,9 @@
require 'spec_helper'
-describe JiraService do
+RSpec.describe JiraService do
include AssetsHelpers
- let(:title) { 'custom title' }
- let(:description) { 'custom description' }
let(:url) { 'http://jira.example.com' }
let(:api_url) { 'http://api-jira.example.com' }
let(:username) { 'jira-username' }
@@ -93,7 +91,6 @@ describe JiraService do
let(:params) do
{
project: create(:project),
- title: 'custom title', description: 'custom description',
url: url, api_url: api_url,
username: username, password: password,
jira_issue_transition_id: transition_id
@@ -106,19 +103,6 @@ describe JiraService do
expect(subject.properties).to be_nil
end
- it 'sets title correctly' do
- service = subject
-
- expect(service.title).to eq('custom title')
- end
-
- it 'sets service data correctly' do
- service = subject
-
- expect(service.title).to eq('custom title')
- expect(service.description).to eq('custom description')
- end
-
it 'stores data in data_fields correcty' do
service = subject
@@ -209,7 +193,6 @@ describe JiraService do
end
it 'does not reset password if url "changed" to the same url as before' do
- service.title = 'aaaaaa'
service.url = 'http://jira.example.com'
service.save
@@ -318,46 +301,32 @@ describe JiraService do
# this will be removed as part of https://gitlab.com/gitlab-org/gitlab/issues/29404
context 'when data are stored in properties' do
- let(:properties) { data_params.merge(title: title, description: description) }
+ let(:properties) { data_params }
let!(:service) do
create(:jira_service, :without_properties_callback, properties: properties.merge(additional: 'something'))
end
- it_behaves_like 'issue tracker fields'
it_behaves_like 'handles jira fields'
end
context 'when data are stored in separated fields' do
let(:service) do
- create(:jira_service, data_params.merge(properties: {}, title: title, description: description))
+ create(:jira_service, data_params.merge(properties: {}))
end
- it_behaves_like 'issue tracker fields'
it_behaves_like 'handles jira fields'
end
context 'when data are stored in both properties and separated fields' do
- let(:properties) { data_params.merge(title: title, description: description) }
+ let(:properties) { data_params }
let(:service) do
create(:jira_service, :without_properties_callback, active: false, properties: properties).tap do |service|
create(:jira_tracker_data, data_params.merge(service: service))
end
end
- it_behaves_like 'issue tracker fields'
it_behaves_like 'handles jira fields'
end
-
- context 'when no title & description are set' do
- let(:service) do
- create(:jira_service, properties: access_params)
- end
-
- it 'returns default values' do
- expect(service.title).to eq('Jira')
- expect(service.description).to eq(s_('JiraService|Jira issue tracker'))
- end
- end
end
describe '#close_issue' do
@@ -704,59 +673,6 @@ describe JiraService do
end
end
- describe 'description and title' do
- let(:title) { 'Jira One' }
- let(:description) { 'Jira One issue tracker' }
- let(:properties) do
- {
- url: 'http://jira.example.com/web',
- username: 'mic',
- password: 'password',
- title: title,
- description: description
- }
- end
-
- context 'when it is not set' do
- it 'default values are returned' do
- service = create(:jira_service)
-
- expect(service.title).to eq('Jira')
- expect(service.description).to eq(s_('JiraService|Jira issue tracker'))
- end
- end
-
- context 'when it is set in properties' do
- it 'values from properties are returned' do
- service = create(:jira_service, :without_properties_callback, properties: properties)
-
- expect(service.title).to eq(title)
- expect(service.description).to eq(description)
- end
- end
-
- context 'when it is in title & description fields' do
- it 'values from title and description fields are returned' do
- service = create(:jira_service, title: title, description: description)
-
- expect(service.title).to eq(title)
- expect(service.description).to eq(description)
- end
- end
-
- context 'when it is in both properites & title & description fields' do
- it 'values from title and description fields are returned' do
- title2 = 'Jira 2'
- description2 = 'Jira description 2'
-
- service = create(:jira_service, title: title2, description: description2, properties: properties)
-
- expect(service.title).to eq(title2)
- expect(service.description).to eq(description2)
- end
- end
- end
-
describe 'project and issue urls' do
context 'when gitlab.yml was initialized' do
it 'is prepopulated with the settings' do
@@ -808,7 +724,7 @@ describe JiraService do
describe '#new_issue_url' do
it 'handles trailing slashes' do
- expect(service.new_issue_url).to eq('http://jira.test.com/path/secure/CreateIssue.jspa')
+ expect(service.new_issue_url).to eq('http://jira.test.com/path/secure/CreateIssue!default.jspa')
end
end
end
diff --git a/spec/models/project_services/jira_tracker_data_spec.rb b/spec/models/project_services/jira_tracker_data_spec.rb
index 12f6b99e8a7..9e38bced46c 100644
--- a/spec/models/project_services/jira_tracker_data_spec.rb
+++ b/spec/models/project_services/jira_tracker_data_spec.rb
@@ -2,8 +2,8 @@
require 'spec_helper'
-describe JiraTrackerData do
- let(:service) { create(:jira_service, active: false) }
+RSpec.describe JiraTrackerData do
+ let(:service) { build(:jira_service) }
describe 'Associations' do
it { is_expected.to belong_to(:service) }
diff --git a/spec/models/project_services/mattermost_service_spec.rb b/spec/models/project_services/mattermost_service_spec.rb
index 5b974985706..af1944ea77d 100644
--- a/spec/models/project_services/mattermost_service_spec.rb
+++ b/spec/models/project_services/mattermost_service_spec.rb
@@ -2,6 +2,6 @@
require 'spec_helper'
-describe MattermostService do
+RSpec.describe MattermostService do
it_behaves_like "slack or mattermost notifications", "Mattermost"
end
diff --git a/spec/models/project_services/mattermost_slash_commands_service_spec.rb b/spec/models/project_services/mattermost_slash_commands_service_spec.rb
index 836181929e3..4fff3bc56cc 100644
--- a/spec/models/project_services/mattermost_slash_commands_service_spec.rb
+++ b/spec/models/project_services/mattermost_slash_commands_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe MattermostSlashCommandsService do
+RSpec.describe MattermostSlashCommandsService do
it_behaves_like "chat slash commands service"
context 'Mattermost API' do
diff --git a/spec/models/project_services/microsoft_teams_service_spec.rb b/spec/models/project_services/microsoft_teams_service_spec.rb
index 425599c73d4..610feb52827 100644
--- a/spec/models/project_services/microsoft_teams_service_spec.rb
+++ b/spec/models/project_services/microsoft_teams_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe MicrosoftTeamsService do
+RSpec.describe MicrosoftTeamsService do
let(:chat_service) { described_class.new }
let(:webhook_url) { 'https://example.gitlab.com/' }
diff --git a/spec/models/project_services/open_project_service_spec.rb b/spec/models/project_services/open_project_service_spec.rb
index 8e373a31e62..1abaab0ceff 100644
--- a/spec/models/project_services/open_project_service_spec.rb
+++ b/spec/models/project_services/open_project_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe OpenProjectService do
+RSpec.describe OpenProjectService do
describe 'Validations' do
context 'when service is active' do
before do
diff --git a/spec/models/project_services/open_project_tracker_data_spec.rb b/spec/models/project_services/open_project_tracker_data_spec.rb
index 0d387bbf69b..e6a3963ba87 100644
--- a/spec/models/project_services/open_project_tracker_data_spec.rb
+++ b/spec/models/project_services/open_project_tracker_data_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe OpenProjectTrackerData do
+RSpec.describe OpenProjectTrackerData do
describe 'Associations' do
it { is_expected.to belong_to(:service) }
end
diff --git a/spec/models/project_services/packagist_service_spec.rb b/spec/models/project_services/packagist_service_spec.rb
index 53f18a1bdd9..f710385b6e2 100644
--- a/spec/models/project_services/packagist_service_spec.rb
+++ b/spec/models/project_services/packagist_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe PackagistService do
+RSpec.describe PackagistService do
describe "Associations" do
it { is_expected.to belong_to :project }
it { is_expected.to have_one :service_hook }
diff --git a/spec/models/project_services/pipelines_email_service_spec.rb b/spec/models/project_services/pipelines_email_service_spec.rb
index de1edf2099a..9a8386c619e 100644
--- a/spec/models/project_services/pipelines_email_service_spec.rb
+++ b/spec/models/project_services/pipelines_email_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe PipelinesEmailService, :mailer do
+RSpec.describe PipelinesEmailService, :mailer do
let(:pipeline) do
create(:ci_pipeline, :failed,
project: project,
diff --git a/spec/models/project_services/pivotaltracker_service_spec.rb b/spec/models/project_services/pivotaltracker_service_spec.rb
index dde46c82df6..8de85cc7fa5 100644
--- a/spec/models/project_services/pivotaltracker_service_spec.rb
+++ b/spec/models/project_services/pivotaltracker_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe PivotaltrackerService do
+RSpec.describe PivotaltrackerService do
include StubRequests
describe 'Associations' do
diff --git a/spec/models/project_services/prometheus_service_spec.rb b/spec/models/project_services/prometheus_service_spec.rb
index db3cbe23ad3..16837e2b93a 100644
--- a/spec/models/project_services/prometheus_service_spec.rb
+++ b/spec/models/project_services/prometheus_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe PrometheusService, :use_clean_rails_memory_store_caching do
+RSpec.describe PrometheusService, :use_clean_rails_memory_store_caching do
include PrometheusHelpers
include ReactiveCachingHelpers
@@ -23,7 +23,7 @@ describe PrometheusService, :use_clean_rails_memory_store_caching do
# result = { success: false, result: error }
expect(result[:success]).to be_falsy
- expect(result[:result]).to be_instance_of(Gitlab::PrometheusClient::Error)
+ expect(result[:result]).to be_instance_of(Gitlab::PrometheusClient::UnexpectedResponseError)
expect(redirect_req_stub).to have_been_requested
expect(redirected_req_stub).not_to have_been_requested
@@ -262,8 +262,6 @@ describe PrometheusService, :use_clean_rails_memory_store_caching do
service.google_iap_audience_client_id = "IAP_CLIENT_ID.apps.googleusercontent.com"
stub_request(:post, "https://oauth2.googleapis.com/token").to_return(status: 200, body: '{"id_token": "FOO"}', headers: { 'Content-Type': 'application/json; charset=UTF-8' })
-
- stub_feature_flags(prometheus_service_iap_auth: true)
end
it 'includes the authorization header' do
@@ -474,11 +472,7 @@ describe PrometheusService, :use_clean_rails_memory_store_caching do
title: 'API URL',
placeholder: s_('PrometheusService|Prometheus API Base URL, like http://prometheus.example.com/'),
required: true
- }
- ]
- end
- let(:feature_flagged_fields) do
- [
+ },
{
type: 'text',
name: 'google_iap_audience_client_id',
@@ -498,13 +492,7 @@ describe PrometheusService, :use_clean_rails_memory_store_caching do
end
it 'returns fields' do
- stub_feature_flags(prometheus_service_iap_auth: false)
expect(service.fields).to eq(expected_fields)
end
-
- it 'returns fields with feature flag on' do
- stub_feature_flags(prometheus_service_iap_auth: true)
- expect(service.fields).to eq(expected_fields + feature_flagged_fields)
- end
end
end
diff --git a/spec/models/project_services/pushover_service_spec.rb b/spec/models/project_services/pushover_service_spec.rb
index 380f02739bc..b7d3b8987b8 100644
--- a/spec/models/project_services/pushover_service_spec.rb
+++ b/spec/models/project_services/pushover_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe PushoverService do
+RSpec.describe PushoverService do
include StubRequests
describe 'Associations' do
diff --git a/spec/models/project_services/redmine_service_spec.rb b/spec/models/project_services/redmine_service_spec.rb
index 6220d7b1fac..b9be3940d34 100644
--- a/spec/models/project_services/redmine_service_spec.rb
+++ b/spec/models/project_services/redmine_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe RedmineService do
+RSpec.describe RedmineService do
describe 'Associations' do
it { is_expected.to belong_to :project }
it { is_expected.to have_one :service_hook }
@@ -50,49 +50,4 @@ describe RedmineService do
expect(described_class.reference_pattern.match('#123')[:issue]).to eq('123')
end
end
-
- context 'overriding properties' do
- let(:url) { 'http://redmine.example.com' }
- let(:access_params) do
- { project_url: url, issues_url: url, new_issue_url: url }
- end
-
- # this will be removed as part of https://gitlab.com/gitlab-org/gitlab/issues/29404
- context 'when data are stored in properties' do
- let(:properties) { access_params.merge(title: title, description: description) }
- let(:service) do
- create(:redmine_service, :without_properties_callback, properties: properties)
- end
-
- it_behaves_like 'issue tracker fields'
- end
-
- context 'when data are stored in separated fields' do
- let(:service) do
- create(:redmine_service, title: title, description: description, properties: access_params)
- end
-
- it_behaves_like 'issue tracker fields'
- end
-
- context 'when data are stored in both properties and separated fields' do
- let(:properties) { access_params.merge(title: 'wrong title', description: 'wrong description') }
- let(:service) do
- create(:redmine_service, :without_properties_callback, title: title, description: description, properties: properties)
- end
-
- it_behaves_like 'issue tracker fields'
- end
-
- context 'when no title & description are set' do
- let(:service) do
- create(:redmine_service, properties: access_params)
- end
-
- it 'returns default values' do
- expect(service.title).to eq('Redmine')
- expect(service.description).to eq('Redmine issue tracker')
- end
- end
- end
end
diff --git a/spec/models/project_services/slack_service_spec.rb b/spec/models/project_services/slack_service_spec.rb
index 93036ac7ec4..0b35b9e7b30 100644
--- a/spec/models/project_services/slack_service_spec.rb
+++ b/spec/models/project_services/slack_service_spec.rb
@@ -2,6 +2,6 @@
require 'spec_helper'
-describe SlackService do
+RSpec.describe SlackService do
it_behaves_like "slack or mattermost notifications", 'Slack'
end
diff --git a/spec/models/project_services/slack_slash_commands_service_spec.rb b/spec/models/project_services/slack_slash_commands_service_spec.rb
index 8c57907d064..95c87ef01bc 100644
--- a/spec/models/project_services/slack_slash_commands_service_spec.rb
+++ b/spec/models/project_services/slack_slash_commands_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe SlackSlashCommandsService do
+RSpec.describe SlackSlashCommandsService do
it_behaves_like "chat slash commands service"
describe '#trigger' do
diff --git a/spec/models/project_services/teamcity_service_spec.rb b/spec/models/project_services/teamcity_service_spec.rb
index 0dd77b68588..a3fda33664a 100644
--- a/spec/models/project_services/teamcity_service_spec.rb
+++ b/spec/models/project_services/teamcity_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe TeamcityService, :use_clean_rails_memory_store_caching do
+RSpec.describe TeamcityService, :use_clean_rails_memory_store_caching do
include ReactiveCachingHelpers
include StubRequests
diff --git a/spec/models/project_services/unify_circuit_service_spec.rb b/spec/models/project_services/unify_circuit_service_spec.rb
index 51079ea5395..73702aa8471 100644
--- a/spec/models/project_services/unify_circuit_service_spec.rb
+++ b/spec/models/project_services/unify_circuit_service_spec.rb
@@ -2,7 +2,7 @@
require "spec_helper"
-describe UnifyCircuitService do
+RSpec.describe UnifyCircuitService do
it_behaves_like "chat service", "Unify Circuit" do
let(:client_arguments) { webhook_url }
let(:content_key) { :subject }
diff --git a/spec/models/project_services/webex_teams_service_spec.rb b/spec/models/project_services/webex_teams_service_spec.rb
index 38977ef3b7d..bd73d0c93b8 100644
--- a/spec/models/project_services/webex_teams_service_spec.rb
+++ b/spec/models/project_services/webex_teams_service_spec.rb
@@ -2,7 +2,7 @@
require "spec_helper"
-describe WebexTeamsService do
+RSpec.describe WebexTeamsService do
it_behaves_like "chat service", "Webex Teams" do
let(:client_arguments) { webhook_url }
let(:content_key) { :markdown }
diff --git a/spec/models/project_services/youtrack_service_spec.rb b/spec/models/project_services/youtrack_service_spec.rb
index b8fff635e99..4339b44e1de 100644
--- a/spec/models/project_services/youtrack_service_spec.rb
+++ b/spec/models/project_services/youtrack_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe YoutrackService do
+RSpec.describe YoutrackService do
describe 'Associations' do
it { is_expected.to belong_to :project }
it { is_expected.to have_one :service_hook }
@@ -42,49 +42,4 @@ describe YoutrackService do
expect(described_class.reference_pattern.match('yt-123')[:issue]).to eq('yt-123')
end
end
-
- context 'overriding properties' do
- let(:url) { 'http://youtrack.example.com' }
- let(:access_params) do
- { project_url: url, issues_url: url, new_issue_url: url }
- end
-
- # this will be removed as part of https://gitlab.com/gitlab-org/gitlab/issues/29404
- context 'when data are stored in properties' do
- let(:properties) { access_params.merge(title: title, description: description) }
- let(:service) do
- create(:youtrack_service, :without_properties_callback, properties: properties)
- end
-
- it_behaves_like 'issue tracker fields'
- end
-
- context 'when data are stored in separated fields' do
- let(:service) do
- create(:youtrack_service, title: title, description: description, properties: access_params)
- end
-
- it_behaves_like 'issue tracker fields'
- end
-
- context 'when data are stored in both properties and separated fields' do
- let(:properties) { access_params.merge(title: 'wrong title', description: 'wrong description') }
- let(:service) do
- create(:youtrack_service, :without_properties_callback, title: title, description: description, properties: properties)
- end
-
- it_behaves_like 'issue tracker fields'
- end
-
- context 'when no title & description are set' do
- let(:service) do
- create(:youtrack_service, properties: access_params)
- end
-
- it 'returns default values' do
- expect(service.title).to eq('YouTrack')
- expect(service.description).to eq(s_('IssueTracker|YouTrack issue tracker'))
- end
- end
- end
end
diff --git a/spec/models/project_setting_spec.rb b/spec/models/project_setting_spec.rb
index 5cfb932eb2a..5572304d666 100644
--- a/spec/models/project_setting_spec.rb
+++ b/spec/models/project_setting_spec.rb
@@ -2,6 +2,6 @@
require 'spec_helper'
-describe ProjectSetting, type: :model do
+RSpec.describe ProjectSetting, type: :model do
it { is_expected.to belong_to(:project) }
end
diff --git a/spec/models/project_snippet_spec.rb b/spec/models/project_snippet_spec.rb
index c17a24dc7cf..464b9b1da84 100644
--- a/spec/models/project_snippet_spec.rb
+++ b/spec/models/project_snippet_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ProjectSnippet do
+RSpec.describe ProjectSnippet do
describe "Associations" do
it { is_expected.to belong_to(:project) }
end
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index 9ec306d297e..8fdda241719 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Project do
+RSpec.describe Project do
include ProjectForksHelper
include GitHelpers
include ExternalAuthorizationServiceHelpers
@@ -63,6 +63,7 @@ describe Project do
it { is_expected.to have_one(:bugzilla_service) }
it { is_expected.to have_one(:gitlab_issue_tracker_service) }
it { is_expected.to have_one(:external_wiki_service) }
+ it { is_expected.to have_one(:confluence_service) }
it { is_expected.to have_one(:project_feature) }
it { is_expected.to have_one(:project_repository) }
it { is_expected.to have_one(:container_expiration_policy) }
@@ -119,6 +120,8 @@ describe Project do
it { is_expected.to have_many(:metrics_users_starred_dashboards).inverse_of(:project) }
it { is_expected.to have_many(:repository_storage_moves) }
it { is_expected.to have_many(:reviews).inverse_of(:project) }
+ it { is_expected.to have_many(:packages).class_name('Packages::Package') }
+ it { is_expected.to have_many(:package_files).class_name('Packages::PackageFile') }
it_behaves_like 'model with repository' do
let_it_be(:container) { create(:project, :repository, path: 'somewhere') }
@@ -1378,6 +1381,62 @@ describe Project do
end
end
+ describe '.service_desk_enabled' do
+ it 'returns the correct project' do
+ project_with_service_desk_enabled = create(:project)
+ project_with_service_desk_disabled = create(:project, :service_desk_disabled)
+
+ expect(described_class.service_desk_enabled).to include(project_with_service_desk_enabled)
+ expect(described_class.service_desk_enabled).not_to include(project_with_service_desk_disabled)
+ end
+ end
+
+ describe '#service_desk_enabled?' do
+ let_it_be(:namespace) { create(:namespace) }
+
+ subject(:project) { build(:project, :private, namespace: namespace, service_desk_enabled: true) }
+
+ before do
+ allow(Gitlab::IncomingEmail).to receive(:enabled?).and_return(true)
+ allow(Gitlab::IncomingEmail).to receive(:supports_wildcard?).and_return(true)
+ end
+
+ it 'is enabled' do
+ expect(project.service_desk_enabled?).to be_truthy
+ expect(project.service_desk_enabled).to be_truthy
+ end
+ end
+
+ describe '#service_desk_address' do
+ let_it_be(:project) { create(:project, service_desk_enabled: true) }
+
+ before do
+ allow(Gitlab::ServiceDesk).to receive(:enabled?).and_return(true)
+ allow(Gitlab.config.incoming_email).to receive(:enabled).and_return(true)
+ allow(Gitlab.config.incoming_email).to receive(:address).and_return("test+%{key}@mail.com")
+ end
+
+ it 'uses project full path as service desk address key' do
+ expect(project.service_desk_address).to eq("test+#{project.full_path_slug}-#{project.project_id}-issue-@mail.com")
+ end
+ end
+
+ describe '.find_by_service_desk_project_key' do
+ it 'returns the correct project' do
+ project1 = create(:project)
+ project2 = create(:project)
+ create(:service_desk_setting, project: project1, project_key: 'key1')
+ create(:service_desk_setting, project: project2, project_key: 'key2')
+
+ expect(Project.find_by_service_desk_project_key('key1')).to eq(project1)
+ expect(Project.find_by_service_desk_project_key('key2')).to eq(project2)
+ end
+
+ it 'returns nil if there is no project with the key' do
+ expect(Project.find_by_service_desk_project_key('some_key')).to be_nil
+ end
+ end
+
context 'repository storage by default' do
let(:project) { build(:project) }
@@ -1651,6 +1710,14 @@ describe Project do
let(:project_name) { 'group.example.com' }
it { is_expected.to eq("http://group.example.com") }
+
+ context 'mixed case path' do
+ before do
+ project.update!(path: 'Group.example.com')
+ end
+
+ it { is_expected.to eq("http://group.example.com") }
+ end
end
context 'project page' do
@@ -1658,6 +1725,14 @@ describe Project do
let(:project_name) { 'Project' }
it { is_expected.to eq("http://group.example.com/project") }
+
+ context 'mixed case path' do
+ before do
+ project.update!(path: 'Project')
+ end
+
+ it { is_expected.to eq("http://group.example.com/Project") }
+ end
end
end
@@ -2897,28 +2972,73 @@ describe Project do
subject { project.deployment_variables(environment: environment, kubernetes_namespace: namespace) }
- before do
- expect(project).to receive(:deployment_platform).with(environment: environment)
- .and_return(deployment_platform)
- end
+ context 'when the deployment platform is stubbed' do
+ before do
+ expect(project).to receive(:deployment_platform).with(environment: environment)
+ .and_return(deployment_platform)
+ end
+
+ context 'when project has a deployment platform' do
+ let(:platform_variables) { %w(platform variables) }
+ let(:deployment_platform) { double }
+
+ before do
+ expect(deployment_platform).to receive(:predefined_variables)
+ .with(project: project, environment_name: environment, kubernetes_namespace: namespace)
+ .and_return(platform_variables)
+ end
+
+ it { is_expected.to eq platform_variables }
+ end
- context 'when project has no deployment platform' do
- let(:deployment_platform) { nil }
+ context 'when project has no deployment platform' do
+ let(:deployment_platform) { nil }
- it { is_expected.to eq [] }
+ it { is_expected.to eq [] }
+ end
end
- context 'when project has a deployment platform' do
- let(:platform_variables) { %w(platform variables) }
- let(:deployment_platform) { double }
+ context 'when project has a deployment platforms' do
+ let(:project) { create(:project) }
+
+ let!(:default_cluster) do
+ create(:cluster,
+ :not_managed,
+ platform_type: :kubernetes,
+ projects: [project],
+ environment_scope: '*',
+ platform_kubernetes: default_cluster_kubernetes)
+ end
- before do
- expect(deployment_platform).to receive(:predefined_variables)
- .with(project: project, environment_name: environment, kubernetes_namespace: namespace)
- .and_return(platform_variables)
+ let!(:review_env_cluster) do
+ create(:cluster,
+ :not_managed,
+ platform_type: :kubernetes,
+ projects: [project],
+ environment_scope: 'review/*',
+ platform_kubernetes: review_env_cluster_kubernetes)
end
- it { is_expected.to eq platform_variables }
+ let(:default_cluster_kubernetes) { create(:cluster_platform_kubernetes, token: 'default-AAA') }
+ let(:review_env_cluster_kubernetes) { create(:cluster_platform_kubernetes, token: 'review-AAA') }
+
+ context 'when environment name is review/name' do
+ let!(:environment) { create(:environment, project: project, name: 'review/name') }
+
+ it 'returns variables from this service' do
+ expect(project.deployment_variables(environment: 'review/name'))
+ .to include(key: 'KUBE_TOKEN', value: 'review-AAA', public: false, masked: true)
+ end
+ end
+
+ context 'when environment name is other' do
+ let!(:environment) { create(:environment, project: project, name: 'staging/name') }
+
+ it 'returns variables from this service' do
+ expect(project.deployment_variables(environment: 'staging/name'))
+ .to include(key: 'KUBE_TOKEN', value: 'default-AAA', public: false, masked: true)
+ end
+ end
end
end
@@ -3999,7 +4119,7 @@ describe Project do
it 'returns the number of forks' do
project = build(:project)
- expect_any_instance_of(Projects::ForksCountService).to receive(:count).and_return(1)
+ expect_any_instance_of(::Projects::BatchForksCountService).to receive(:refresh_cache_and_retrieve_data).and_return({ project => 1 })
expect(project.forks_count).to eq(1)
end
@@ -4655,6 +4775,7 @@ describe Project do
expect(project).to receive(:refresh_markdown_cache!)
expect(InternalId).to receive(:flush_records!).with(project: project)
expect(DetectRepositoryLanguagesWorker).to receive(:perform_async).with(project.id)
+ expect(project).to receive(:write_repository_config)
project.after_import
end
@@ -4743,6 +4864,36 @@ describe Project do
end
end
+ describe "#default_branch" do
+ context "with an empty repository" do
+ let_it_be(:project) { create(:project_empty_repo) }
+
+ context "Gitlab::CurrentSettings.default_branch_name is unavailable" do
+ before do
+ expect(Gitlab::CurrentSettings)
+ .to receive(:default_branch_name)
+ .and_return(nil)
+ end
+
+ it "returns that value" do
+ expect(project.default_branch).to be_nil
+ end
+ end
+
+ context "Gitlab::CurrentSettings.default_branch_name is available" do
+ before do
+ expect(Gitlab::CurrentSettings)
+ .to receive(:default_branch_name)
+ .and_return('example_branch')
+ end
+
+ it "returns that value" do
+ expect(project.default_branch).to eq("example_branch")
+ end
+ end
+ end
+ end
+
describe '#to_ability_name' do
it 'returns project' do
project = build(:project_empty_repo)
@@ -5886,6 +6037,30 @@ describe Project do
end
end
+ describe '#prometheus_service_active?' do
+ let(:project) { create(:project) }
+
+ subject { project.prometheus_service_active? }
+
+ before do
+ create(:prometheus_service, project: project, manual_configuration: manual_configuration)
+ end
+
+ context 'when project has an activated prometheus service' do
+ let(:manual_configuration) { true }
+
+ it { is_expected.to be_truthy }
+ end
+
+ context 'when project has an inactive prometheus service' do
+ let(:manual_configuration) { false }
+
+ it 'the service is marked as inactive' do
+ expect(subject).to be_falsey
+ end
+ end
+ end
+
describe '#self_monitoring?' do
let_it_be(:project) { create(:project) }
@@ -6025,6 +6200,39 @@ describe Project do
end
end
+ describe '#packages_enabled' do
+ subject { create(:project).packages_enabled }
+
+ it { is_expected.to be true }
+ end
+
+ describe '#package_already_taken?' do
+ let(:namespace) { create(:namespace) }
+ let(:project) { create(:project, :public, namespace: namespace) }
+ let!(:package) { create(:npm_package, project: project, name: "@#{namespace.path}/foo") }
+
+ context 'no package exists with the same name' do
+ it 'returns false' do
+ result = project.package_already_taken?("@#{namespace.path}/bar")
+ expect(result).to be false
+ end
+
+ it 'returns false if it is the project that the package belongs to' do
+ result = project.package_already_taken?("@#{namespace.path}/foo")
+ expect(result).to be false
+ end
+ end
+
+ context 'a package already exists with the same name' do
+ let(:alt_project) { create(:project, :public, namespace: namespace) }
+
+ it 'returns true' do
+ result = alt_project.package_already_taken?("@#{namespace.path}/foo")
+ expect(result).to be true
+ end
+ end
+ end
+
describe '#design_management_enabled?' do
let(:project) { build(:project) }
diff --git a/spec/models/project_statistics_spec.rb b/spec/models/project_statistics_spec.rb
index 4bc6130387a..3659e6b973e 100644
--- a/spec/models/project_statistics_spec.rb
+++ b/spec/models/project_statistics_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ProjectStatistics do
+RSpec.describe ProjectStatistics do
let(:project) { create :project }
let(:statistics) { project.statistics }
@@ -32,7 +32,8 @@ describe ProjectStatistics do
repository_size: 2.exabytes,
wiki_size: 1.exabytes,
lfs_objects_size: 2.exabytes,
- build_artifacts_size: 3.exabytes - 1
+ build_artifacts_size: 2.exabytes - 1,
+ snippets_size: 1.exabyte
)
statistics.reload
@@ -41,8 +42,9 @@ describe ProjectStatistics do
expect(statistics.repository_size).to eq(2.exabytes)
expect(statistics.wiki_size).to eq(1.exabytes)
expect(statistics.lfs_objects_size).to eq(2.exabytes)
- expect(statistics.build_artifacts_size).to eq(3.exabytes - 1)
+ expect(statistics.build_artifacts_size).to eq(2.exabytes - 1)
expect(statistics.storage_size).to eq(8.exabytes - 1)
+ expect(statistics.snippets_size).to eq(1.exabyte)
end
end
@@ -52,23 +54,47 @@ describe ProjectStatistics do
statistics.wiki_size = 6
statistics.lfs_objects_size = 3
statistics.build_artifacts_size = 4
+ statistics.snippets_size = 5
expect(statistics.total_repository_size).to eq 5
end
end
describe '#wiki_size' do
- it "is initialized with not null value" do
+ it 'is initialized with not null value' do
+ expect(statistics.attributes['wiki_size']).to be_zero
+ expect(statistics.wiki_size).to be_zero
+ end
+
+ it 'coerces any nil value to 0' do
+ statistics.update!(wiki_size: nil)
+
+ expect(statistics.attributes['wiki_size']).to be_nil
expect(statistics.wiki_size).to eq 0
end
end
+ describe '#snippets_size' do
+ it 'is initialized with not null value' do
+ expect(statistics.attributes['snippets_size']).to be_zero
+ expect(statistics.snippets_size).to be_zero
+ end
+
+ it 'coerces any nil value to 0' do
+ statistics.update!(snippets_size: nil)
+
+ expect(statistics.attributes['snippets_size']).to be_nil
+ expect(statistics.snippets_size).to eq 0
+ end
+ end
+
describe '#refresh!' do
before do
allow(statistics).to receive(:update_commit_count)
allow(statistics).to receive(:update_repository_size)
allow(statistics).to receive(:update_wiki_size)
allow(statistics).to receive(:update_lfs_objects_size)
+ allow(statistics).to receive(:update_snippets_size)
allow(statistics).to receive(:update_storage_size)
end
@@ -82,6 +108,7 @@ describe ProjectStatistics do
expect(statistics).to have_received(:update_repository_size)
expect(statistics).to have_received(:update_wiki_size)
expect(statistics).to have_received(:update_lfs_objects_size)
+ expect(statistics).to have_received(:update_snippets_size)
end
end
@@ -95,6 +122,7 @@ describe ProjectStatistics do
expect(statistics).not_to have_received(:update_commit_count)
expect(statistics).not_to have_received(:update_repository_size)
expect(statistics).not_to have_received(:update_wiki_size)
+ expect(statistics).not_to have_received(:update_snippets_size)
end
end
@@ -108,9 +136,11 @@ describe ProjectStatistics do
expect(statistics).to have_received(:update_commit_count)
expect(statistics).to have_received(:update_repository_size)
expect(statistics).to have_received(:update_wiki_size)
+ expect(statistics).to have_received(:update_snippets_size)
expect(statistics.repository_size).to eq(0)
expect(statistics.commit_count).to eq(0)
expect(statistics.wiki_size).to eq(0)
+ expect(statistics.snippets_size).to eq(0)
end
end
@@ -130,9 +160,11 @@ describe ProjectStatistics do
expect(statistics).to have_received(:update_commit_count)
expect(statistics).to have_received(:update_repository_size)
expect(statistics).to have_received(:update_wiki_size)
+ expect(statistics).to have_received(:update_snippets_size)
expect(statistics.repository_size).to eq(0)
expect(statistics.commit_count).to eq(0)
expect(statistics.wiki_size).to eq(0)
+ expect(statistics.snippets_size).to eq(0)
end
end
@@ -202,6 +234,33 @@ describe ProjectStatistics do
end
end
+ describe '#update_snippets_size' do
+ before do
+ create_list(:project_snippet, 2, project: project)
+ SnippetStatistics.update_all(repository_size: 10)
+ end
+
+ it 'stores the size of snippets' do
+ # Snippet not associated with the project
+ snippet = create(:project_snippet)
+ snippet.statistics.update!(repository_size: 40)
+
+ statistics.update_snippets_size
+
+ expect(statistics.update_snippets_size).to eq 20
+ end
+
+ context 'when not all snippets has statistics' do
+ it 'stores the size of snippets with statistics' do
+ SnippetStatistics.last.delete
+
+ statistics.update_snippets_size
+
+ expect(statistics.update_snippets_size).to eq 10
+ end
+ end
+ end
+
describe '#update_lfs_objects_size' do
let!(:lfs_object1) { create(:lfs_object, size: 23.megabytes) }
let!(:lfs_object2) { create(:lfs_object, size: 34.megabytes) }
@@ -222,12 +281,13 @@ describe ProjectStatistics do
statistics.update!(
repository_size: 2,
wiki_size: 4,
- lfs_objects_size: 3
+ lfs_objects_size: 3,
+ snippets_size: 2
)
statistics.reload
- expect(statistics.storage_size).to eq 9
+ expect(statistics.storage_size).to eq 11
end
it 'works during wiki_size backfill' do
@@ -241,6 +301,21 @@ describe ProjectStatistics do
expect(statistics.storage_size).to eq 5
end
+
+ context 'when nullable columns are nil' do
+ it 'does not raise any error' do
+ expect do
+ statistics.update!(
+ repository_size: 2,
+ wiki_size: nil,
+ lfs_objects_size: 3,
+ snippets_size: nil
+ )
+ end.not_to raise_error
+
+ expect(statistics.storage_size).to eq 5
+ end
+ end
end
describe '.increment_statistic' do
diff --git a/spec/models/project_team_spec.rb b/spec/models/project_team_spec.rb
index 24652a1d706..34ec856459c 100644
--- a/spec/models/project_team_spec.rb
+++ b/spec/models/project_team_spec.rb
@@ -2,7 +2,7 @@
require "spec_helper"
-describe ProjectTeam do
+RSpec.describe ProjectTeam do
let(:maintainer) { create(:user) }
let(:reporter) { create(:user) }
let(:guest) { create(:user) }
diff --git a/spec/models/project_wiki_spec.rb b/spec/models/project_wiki_spec.rb
index aff2b248642..d9c5fed542e 100644
--- a/spec/models/project_wiki_spec.rb
+++ b/spec/models/project_wiki_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ProjectWiki do
+RSpec.describe ProjectWiki do
it_behaves_like 'wiki model' do
let(:wiki_container) { create(:project, :wiki_repo, namespace: user.namespace) }
let(:wiki_container_without_repo) { create(:project, namespace: user.namespace) }
diff --git a/spec/models/prometheus_alert_event_spec.rb b/spec/models/prometheus_alert_event_spec.rb
index 85e57cb08c3..913ca7db0be 100644
--- a/spec/models/prometheus_alert_event_spec.rb
+++ b/spec/models/prometheus_alert_event_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe PrometheusAlertEvent do
+RSpec.describe PrometheusAlertEvent do
subject { build(:prometheus_alert_event) }
let(:alert) { subject.prometheus_alert }
diff --git a/spec/models/prometheus_alert_spec.rb b/spec/models/prometheus_alert_spec.rb
index 1409cf65fee..7169a34d96f 100644
--- a/spec/models/prometheus_alert_spec.rb
+++ b/spec/models/prometheus_alert_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe PrometheusAlert do
+RSpec.describe PrometheusAlert do
let_it_be(:project) { build(:project) }
let(:metric) { build(:prometheus_metric) }
@@ -33,6 +33,10 @@ describe PrometheusAlert do
describe 'associations' do
it { is_expected.to belong_to(:project) }
it { is_expected.to belong_to(:environment) }
+ it { is_expected.to belong_to(:prometheus_metric) }
+ it { is_expected.to have_many(:prometheus_alert_events) }
+ it { is_expected.to have_many(:related_issues) }
+ it { is_expected.to have_many(:alert_management_alerts) }
end
describe 'project validations' do
diff --git a/spec/models/prometheus_metric_spec.rb b/spec/models/prometheus_metric_spec.rb
index 93abef063cb..f284102b4a9 100644
--- a/spec/models/prometheus_metric_spec.rb
+++ b/spec/models/prometheus_metric_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe PrometheusMetric do
+RSpec.describe PrometheusMetric do
subject { build(:prometheus_metric) }
it_behaves_like 'having unique enum values'
@@ -11,6 +11,7 @@ describe PrometheusMetric do
it { is_expected.to validate_presence_of(:title) }
it { is_expected.to validate_presence_of(:query) }
it { is_expected.to validate_presence_of(:group) }
+ it { is_expected.to validate_uniqueness_of(:identifier).scoped_to(:project_id).allow_nil }
describe 'common metrics' do
using RSpec::Parameterized::TableSyntax
diff --git a/spec/models/protectable_dropdown_spec.rb b/spec/models/protectable_dropdown_spec.rb
index aca3df9fdde..c51197234ca 100644
--- a/spec/models/protectable_dropdown_spec.rb
+++ b/spec/models/protectable_dropdown_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ProtectableDropdown do
+RSpec.describe ProtectableDropdown do
let(:project) { create(:project, :repository) }
let(:subject) { described_class.new(project, :branches) }
diff --git a/spec/models/protected_branch/merge_access_level_spec.rb b/spec/models/protected_branch/merge_access_level_spec.rb
index 39dd586b157..b6c2d527d1b 100644
--- a/spec/models/protected_branch/merge_access_level_spec.rb
+++ b/spec/models/protected_branch/merge_access_level_spec.rb
@@ -2,6 +2,6 @@
require 'spec_helper'
-describe ProtectedBranch::MergeAccessLevel do
+RSpec.describe ProtectedBranch::MergeAccessLevel do
it { is_expected.to validate_inclusion_of(:access_level).in_array([Gitlab::Access::MAINTAINER, Gitlab::Access::DEVELOPER, Gitlab::Access::NO_ACCESS]) }
end
diff --git a/spec/models/protected_branch/push_access_level_spec.rb b/spec/models/protected_branch/push_access_level_spec.rb
index 628c8d29ecd..77fe9814c86 100644
--- a/spec/models/protected_branch/push_access_level_spec.rb
+++ b/spec/models/protected_branch/push_access_level_spec.rb
@@ -2,6 +2,6 @@
require 'spec_helper'
-describe ProtectedBranch::PushAccessLevel do
+RSpec.describe ProtectedBranch::PushAccessLevel do
it { is_expected.to validate_inclusion_of(:access_level).in_array([Gitlab::Access::MAINTAINER, Gitlab::Access::DEVELOPER, Gitlab::Access::NO_ACCESS]) }
end
diff --git a/spec/models/protected_branch_spec.rb b/spec/models/protected_branch_spec.rb
index 30fce1cd5c4..a89f8778780 100644
--- a/spec/models/protected_branch_spec.rb
+++ b/spec/models/protected_branch_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ProtectedBranch do
+RSpec.describe ProtectedBranch do
subject { build_stubbed(:protected_branch) }
describe 'Associations' do
diff --git a/spec/models/protected_tag_spec.rb b/spec/models/protected_tag_spec.rb
index 79120d17d39..7bc62b1d0e7 100644
--- a/spec/models/protected_tag_spec.rb
+++ b/spec/models/protected_tag_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ProtectedTag do
+RSpec.describe ProtectedTag do
describe 'Associations' do
it { is_expected.to belong_to(:project) }
end
diff --git a/spec/models/push_event_payload_spec.rb b/spec/models/push_event_payload_spec.rb
index 6b59ee5ee57..32415ef4719 100644
--- a/spec/models/push_event_payload_spec.rb
+++ b/spec/models/push_event_payload_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe PushEventPayload do
+RSpec.describe PushEventPayload do
it_behaves_like 'having unique enum values'
describe 'saving payloads' do
diff --git a/spec/models/push_event_spec.rb b/spec/models/push_event_spec.rb
index 5c1802669c1..61e31e7c4e3 100644
--- a/spec/models/push_event_spec.rb
+++ b/spec/models/push_event_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe PushEvent do
+RSpec.describe PushEvent do
let(:payload) { PushEventPayload.new }
let(:event) do
diff --git a/spec/models/readme_blob_spec.rb b/spec/models/readme_blob_spec.rb
index 34182fa413f..95622d55254 100644
--- a/spec/models/readme_blob_spec.rb
+++ b/spec/models/readme_blob_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ReadmeBlob do
+RSpec.describe ReadmeBlob do
include FakeBlobHelpers
describe 'policy' do
diff --git a/spec/models/redirect_route_spec.rb b/spec/models/redirect_route_spec.rb
index b9b2873f8f2..c6e35923b89 100644
--- a/spec/models/redirect_route_spec.rb
+++ b/spec/models/redirect_route_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe RedirectRoute do
+RSpec.describe RedirectRoute do
let(:group) { create(:group) }
let!(:redirect_route) { group.redirect_routes.create(path: 'gitlabb') }
diff --git a/spec/models/releases/evidence_spec.rb b/spec/models/releases/evidence_spec.rb
index 927e2e9bbe6..ca5d4b67b59 100644
--- a/spec/models/releases/evidence_spec.rb
+++ b/spec/models/releases/evidence_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Releases::Evidence do
+RSpec.describe Releases::Evidence do
let_it_be(:project) { create(:project) }
let(:release) { create(:release, project: project) }
diff --git a/spec/models/releases/link_spec.rb b/spec/models/releases/link_spec.rb
index 7533d1e6e5c..4dc1e53d59e 100644
--- a/spec/models/releases/link_spec.rb
+++ b/spec/models/releases/link_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Releases::Link do
+RSpec.describe Releases::Link do
let(:release) { create(:release, project: project) }
let(:project) { create(:project) }
diff --git a/spec/models/releases/source_spec.rb b/spec/models/releases/source_spec.rb
index d7af6fd90a6..d10b2140550 100644
--- a/spec/models/releases/source_spec.rb
+++ b/spec/models/releases/source_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Releases::Source do
+RSpec.describe Releases::Source do
let_it_be(:project) { create(:project, :repository, name: 'finance-cal') }
let(:tag_name) { 'v1.0' }
diff --git a/spec/models/remote_mirror_spec.rb b/spec/models/remote_mirror_spec.rb
index 6d163a16e63..ebc9760ab14 100644
--- a/spec/models/remote_mirror_spec.rb
+++ b/spec/models/remote_mirror_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe RemoteMirror, :mailer do
+RSpec.describe RemoteMirror, :mailer do
include GitHelpers
describe 'URL validation' do
diff --git a/spec/models/repository_language_spec.rb b/spec/models/repository_language_spec.rb
index 13a4cd1e7cf..8cf5e17086d 100644
--- a/spec/models/repository_language_spec.rb
+++ b/spec/models/repository_language_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe RepositoryLanguage do
+RSpec.describe RepositoryLanguage do
let(:repository_language) { build(:repository_language) }
describe 'associations' do
diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb
index c698b40a4c0..964cc5a13ca 100644
--- a/spec/models/repository_spec.rb
+++ b/spec/models/repository_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Repository do
+RSpec.describe Repository do
include RepoHelpers
include GitHelpers
@@ -252,6 +252,21 @@ describe Repository do
end
end
end
+
+ context 'with filename with pathspec characters' do
+ let(:filename) { ':wq' }
+ let(:newrev) { project.repository.commit('master').sha }
+
+ before do
+ create_file_in_repo(project, 'master', 'master', filename, 'Test file')
+ end
+
+ subject { repository.last_commit_for_path('master', filename, literal_pathspec: true).id }
+
+ it 'returns a commit SHA' do
+ expect(subject).to eq(newrev)
+ end
+ end
end
describe '#last_commit_id_for_path' do
@@ -276,6 +291,21 @@ describe Repository do
end
end
end
+
+ context 'with filename with pathspec characters' do
+ let(:filename) { ':wq' }
+ let(:newrev) { project.repository.commit('master').sha }
+
+ before do
+ create_file_in_repo(project, 'master', 'master', filename, 'Test file')
+ end
+
+ subject { repository.last_commit_id_for_path('master', filename, literal_pathspec: true) }
+
+ it 'returns a commit SHA' do
+ expect(subject).to eq(newrev)
+ end
+ end
end
describe '#commits' do
@@ -2865,6 +2895,29 @@ describe Repository do
end
end
+ describe '#project' do
+ it 'returns the project for a project snippet' do
+ snippet = create(:project_snippet)
+
+ expect(snippet.repository.project).to be(snippet.project)
+ end
+
+ it 'returns nil for a personal snippet' do
+ snippet = create(:personal_snippet)
+
+ expect(snippet.repository.project).to be_nil
+ end
+
+ it 'returns the container if it is a project' do
+ expect(repository.project).to be(project)
+ end
+
+ it 'returns nil if the container is not a project' do
+ expect(repository).to receive(:container).and_return(Group.new)
+ expect(repository.project).to be_nil
+ end
+ end
+
describe '#submodule_links' do
it 'returns an instance of Gitlab::SubmoduleLinks' do
expect(repository.submodule_links).to be_a(Gitlab::SubmoduleLinks)
diff --git a/spec/models/resource_milestone_event_spec.rb b/spec/models/resource_milestone_event_spec.rb
index 66686ec77d0..76ffb358d80 100644
--- a/spec/models/resource_milestone_event_spec.rb
+++ b/spec/models/resource_milestone_event_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ResourceMilestoneEvent, type: :model do
+RSpec.describe ResourceMilestoneEvent, type: :model do
it_behaves_like 'a resource event'
it_behaves_like 'a resource event for issues'
it_behaves_like 'a resource event for merge requests'
diff --git a/spec/models/resource_state_event_spec.rb b/spec/models/resource_state_event_spec.rb
index 986a13cbd0d..1381b45cf9e 100644
--- a/spec/models/resource_state_event_spec.rb
+++ b/spec/models/resource_state_event_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ResourceStateEvent, type: :model do
+RSpec.describe ResourceStateEvent, type: :model do
subject { build(:resource_state_event, issue: issue) }
let(:issue) { create(:issue) }
diff --git a/spec/models/review_spec.rb b/spec/models/review_spec.rb
index 9dd8b90feee..2683dc93a4b 100644
--- a/spec/models/review_spec.rb
+++ b/spec/models/review_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Review do
+RSpec.describe Review do
describe 'associations' do
it { is_expected.to belong_to(:author).class_name('User').with_foreign_key(:author_id).inverse_of(:reviews) }
it { is_expected.to belong_to(:merge_request).inverse_of(:reviews).touch(false) }
diff --git a/spec/models/route_spec.rb b/spec/models/route_spec.rb
index 20289afbeb5..0f1637016d6 100644
--- a/spec/models/route_spec.rb
+++ b/spec/models/route_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Route do
+RSpec.describe Route do
let(:group) { create(:group, path: 'git_lab', name: 'git_lab') }
let(:route) { group.route }
diff --git a/spec/models/sent_notification_spec.rb b/spec/models/sent_notification_spec.rb
index 087bc957373..aeafb49f8b5 100644
--- a/spec/models/sent_notification_spec.rb
+++ b/spec/models/sent_notification_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe SentNotification do
+RSpec.describe SentNotification do
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project) }
diff --git a/spec/models/sentry_issue_spec.rb b/spec/models/sentry_issue_spec.rb
index b4c1cf57761..33654bf5e1a 100644
--- a/spec/models/sentry_issue_spec.rb
+++ b/spec/models/sentry_issue_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe SentryIssue do
+RSpec.describe SentryIssue do
describe 'associations' do
it { is_expected.to belong_to(:issue) }
end
diff --git a/spec/models/serverless/domain_cluster_spec.rb b/spec/models/serverless/domain_cluster_spec.rb
index f5e1eb304a1..fdae0483c19 100644
--- a/spec/models/serverless/domain_cluster_spec.rb
+++ b/spec/models/serverless/domain_cluster_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ::Serverless::DomainCluster do
+RSpec.describe ::Serverless::DomainCluster do
subject { create(:serverless_domain_cluster) }
describe 'validations' do
diff --git a/spec/models/serverless/domain_spec.rb b/spec/models/serverless/domain_spec.rb
index ba54e05b4e3..f997b28b149 100644
--- a/spec/models/serverless/domain_spec.rb
+++ b/spec/models/serverless/domain_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ::Serverless::Domain do
+RSpec.describe ::Serverless::Domain do
let(:function_name) { 'test-function' }
let(:pages_domain_name) { 'serverless.gitlab.io' }
let(:pages_domain) { create(:pages_domain, :instance_serverless, domain: pages_domain_name) }
diff --git a/spec/models/serverless/function_spec.rb b/spec/models/serverless/function_spec.rb
index 810d4409a34..632f5eba5c3 100644
--- a/spec/models/serverless/function_spec.rb
+++ b/spec/models/serverless/function_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ::Serverless::Function do
+RSpec.describe ::Serverless::Function do
let(:project) { create(:project) }
let(:func) { described_class.new(project, 'test', 'test-ns') }
diff --git a/spec/models/service_desk_setting_spec.rb b/spec/models/service_desk_setting_spec.rb
new file mode 100644
index 00000000000..ca57a5d4087
--- /dev/null
+++ b/spec/models/service_desk_setting_spec.rb
@@ -0,0 +1,37 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe ServiceDeskSetting do
+ describe 'validations' do
+ subject(:service_desk_setting) { create(:service_desk_setting) }
+
+ it { is_expected.to validate_presence_of(:project_id) }
+ it { is_expected.to validate_length_of(:outgoing_name).is_at_most(255) }
+ it { is_expected.to validate_length_of(:project_key).is_at_most(255) }
+ it { is_expected.to allow_value('abc123_').for(:project_key) }
+ it { is_expected.not_to allow_value('abc 12').for(:project_key) }
+ it { is_expected.not_to allow_value('Big val').for(:project_key) }
+
+ describe '.valid_issue_template' do
+ let_it_be(:project) { create(:project, :custom_repo, files: { '.gitlab/issue_templates/service_desk.md' => 'template' }) }
+
+ it 'is not valid if template does not exist' do
+ settings = build(:service_desk_setting, project: project, issue_template_key: 'invalid key')
+
+ expect(settings).not_to be_valid
+ expect(settings.errors[:issue_template_key].first).to eq('is empty or does not exist')
+ end
+
+ it 'is valid if template exists' do
+ settings = build(:service_desk_setting, project: project, issue_template_key: 'service_desk')
+
+ expect(settings).to be_valid
+ end
+ end
+ end
+
+ describe 'associations' do
+ it { is_expected.to belong_to(:project) }
+ end
+end
diff --git a/spec/models/service_spec.rb b/spec/models/service_spec.rb
index 8698a6cf3d3..75bbb074526 100644
--- a/spec/models/service_spec.rb
+++ b/spec/models/service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Service do
+RSpec.describe Service do
describe "Associations" do
it { is_expected.to belong_to :project }
it { is_expected.to have_one :service_hook }
@@ -304,8 +304,6 @@ describe Service do
end
describe 'build issue tracker from an integration' do
- let(:title) { 'custom title' }
- let(:description) { 'custom description' }
let(:url) { 'http://jira.example.com' }
let(:api_url) { 'http://api-jira.example.com' }
let(:username) { 'jira-username' }
@@ -322,8 +320,6 @@ describe Service do
service = described_class.build_from_integration(project.id, integration)
expect(service).to be_active
- expect(service.title).to eq(title)
- expect(service.description).to eq(description)
expect(service.url).to eq(url)
expect(service.api_url).to eq(api_url)
expect(service.username).to eq(username)
@@ -335,7 +331,7 @@ describe Service do
# this will be removed as part of https://gitlab.com/gitlab-org/gitlab/issues/29404
context 'when data are stored in properties' do
- let(:properties) { data_params.merge(title: title, description: description) }
+ let(:properties) { data_params }
let!(:integration) do
create(:jira_service, :without_properties_callback, template: true, properties: properties.merge(additional: 'something'))
end
@@ -345,14 +341,14 @@ describe Service do
context 'when data are stored in separated fields' do
let(:integration) do
- create(:jira_service, :template, data_params.merge(properties: {}, title: title, description: description))
+ create(:jira_service, :template, data_params.merge(properties: {}))
end
it_behaves_like 'service creation from an integration'
end
context 'when data are stored in both properties and separated fields' do
- let(:properties) { data_params.merge(title: title, description: description) }
+ let(:properties) { data_params }
let(:integration) do
create(:jira_service, :without_properties_callback, active: true, template: true, properties: properties).tap do |service|
create(:jira_tracker_data, data_params.merge(service: service))
@@ -390,6 +386,33 @@ describe Service do
end
end
+ describe 'instance' do
+ describe '.instance_for' do
+ let_it_be(:jira_service) { create(:jira_service, :instance) }
+ let_it_be(:slack_service) { create(:slack_service, :instance) }
+
+ subject { described_class.instance_for(type) }
+
+ context 'Hipchat serivce' do
+ let(:type) { 'HipchatService' }
+
+ it { is_expected.to eq(nil) }
+ end
+
+ context 'Jira serivce' do
+ let(:type) { 'JiraService' }
+
+ it { is_expected.to eq(jira_service) }
+ end
+
+ context 'Slack serivce' do
+ let(:type) { 'SlackService' }
+
+ it { is_expected.to eq(slack_service) }
+ end
+ end
+ end
+
describe "{property}_changed?" do
let(:service) do
BambooService.create(
@@ -514,7 +537,6 @@ describe Service do
let(:service) do
GitlabIssueTrackerService.create(
project: create(:project),
- title: 'random title',
project_url: 'http://gitlab.example.com'
)
end
@@ -523,10 +545,6 @@ describe Service do
expect { service }.not_to raise_error
end
- it 'sets title correctly' do
- expect(service.title).to eq('random title')
- end
-
it 'sets data correctly' do
expect(service.data_fields.project_url).to eq('http://gitlab.example.com')
end
diff --git a/spec/models/shard_spec.rb b/spec/models/shard_spec.rb
index 4da86858b54..a9d11f4290c 100644
--- a/spec/models/shard_spec.rb
+++ b/spec/models/shard_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Shard do
+RSpec.describe Shard do
describe '.populate!' do
it 'creates shards based on the config file' do
expect(described_class.all).to be_empty
diff --git a/spec/models/snippet_blob_spec.rb b/spec/models/snippet_blob_spec.rb
index 88441e39d45..19b985f66ee 100644
--- a/spec/models/snippet_blob_spec.rb
+++ b/spec/models/snippet_blob_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe SnippetBlob do
+RSpec.describe SnippetBlob do
let(:snippet) { create(:snippet) }
subject { described_class.new(snippet) }
diff --git a/spec/models/snippet_input_action_collection_spec.rb b/spec/models/snippet_input_action_collection_spec.rb
index ef18ab5a810..3ec206bd031 100644
--- a/spec/models/snippet_input_action_collection_spec.rb
+++ b/spec/models/snippet_input_action_collection_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe SnippetInputActionCollection do
+RSpec.describe SnippetInputActionCollection do
let(:action_name) { 'create' }
let(:action) { { action: action_name, file_path: 'foo', content: 'bar', previous_path: 'foobar' } }
let(:data) { [action, action] }
diff --git a/spec/models/snippet_input_action_spec.rb b/spec/models/snippet_input_action_spec.rb
index 5e379a48171..ca61b80df4c 100644
--- a/spec/models/snippet_input_action_spec.rb
+++ b/spec/models/snippet_input_action_spec.rb
@@ -2,18 +2,18 @@
require 'spec_helper'
-describe SnippetInputAction do
+RSpec.describe SnippetInputAction do
describe 'validations' do
using RSpec::Parameterized::TableSyntax
where(:action, :file_path, :content, :previous_path, :allowed_actions, :is_valid, :invalid_field) do
:create | 'foobar' | 'foobar' | 'foobar' | nil | true | nil
- :move | 'foobar' | 'foobar' | 'foobar' | nil | true | nil
+ :move | 'foobar' | 'foobar' | 'foo1' | nil | true | nil
:delete | 'foobar' | 'foobar' | 'foobar' | nil | true | nil
:update | 'foobar' | 'foobar' | 'foobar' | nil | true | nil
:foo | 'foobar' | 'foobar' | 'foobar' | nil | false | :action
'create' | 'foobar' | 'foobar' | 'foobar' | nil | true | nil
- 'move' | 'foobar' | 'foobar' | 'foobar' | nil | true | nil
+ 'move' | 'foobar' | 'foobar' | 'foo1' | nil | true | nil
'delete' | 'foobar' | 'foobar' | 'foobar' | nil | true | nil
'update' | 'foobar' | 'foobar' | 'foobar' | nil | true | nil
'foo' | 'foobar' | 'foobar' | 'foobar' | nil | false | :action
@@ -21,10 +21,16 @@ describe SnippetInputAction do
'' | 'foobar' | 'foobar' | 'foobar' | nil | false | :action
:move | 'foobar' | 'foobar' | nil | nil | false | :previous_path
:move | 'foobar' | 'foobar' | '' | nil | false | :previous_path
+ :move | 'foobar' | 'foobar' | 'foobar' | nil | false | :file_path
+ :move | nil | 'foobar' | 'foobar' | nil | false | :file_path
+ :move | '' | 'foobar' | 'foobar' | nil | false | :file_path
+ :move | nil | 'foobar' | 'foo1' | nil | false | :file_path
+ :move | 'foobar' | nil | 'foo1' | nil | true | nil
+ :move | 'foobar' | '' | 'foo1' | nil | true | nil
:create | 'foobar' | nil | 'foobar' | nil | false | :content
:create | 'foobar' | '' | 'foobar' | nil | false | :content
- :create | nil | 'foobar' | 'foobar' | nil | false | :file_path
- :create | '' | 'foobar' | 'foobar' | nil | false | :file_path
+ :create | nil | 'foobar' | 'foobar' | nil | true | nil
+ :create | '' | 'foobar' | 'foobar' | nil | true | nil
:update | 'foobar' | nil | 'foobar' | nil | false | :content
:update | 'foobar' | '' | 'foobar' | nil | false | :content
:update | 'other' | 'foobar' | 'foobar' | nil | false | :file_path
diff --git a/spec/models/snippet_repository_spec.rb b/spec/models/snippet_repository_spec.rb
index b86a6f82f07..8c25d713c0a 100644
--- a/spec/models/snippet_repository_spec.rb
+++ b/spec/models/snippet_repository_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe SnippetRepository do
+RSpec.describe SnippetRepository do
let_it_be(:user) { create(:user) }
let(:snippet) { create(:personal_snippet, :repository, author: user) }
let(:snippet_repository) { snippet.snippet_repository }
diff --git a/spec/models/snippet_spec.rb b/spec/models/snippet_spec.rb
index 4d6586c1df4..3f9c6981de1 100644
--- a/spec/models/snippet_spec.rb
+++ b/spec/models/snippet_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Snippet do
+RSpec.describe Snippet do
describe 'modules' do
subject { described_class }
@@ -20,6 +20,7 @@ describe Snippet do
it { is_expected.to have_many(:award_emoji).dependent(:destroy) }
it { is_expected.to have_many(:user_mentions).class_name("SnippetUserMention") }
it { is_expected.to have_one(:snippet_repository) }
+ it { is_expected.to have_one(:statistics).class_name('SnippetStatistics').dependent(:destroy) }
end
describe 'validation' do
@@ -91,6 +92,17 @@ describe Snippet do
end
end
+ describe 'callbacks' do
+ it 'creates snippet statistics when the snippet is created' do
+ snippet = build(:snippet)
+ expect(snippet.statistics).to be_nil
+
+ snippet.save
+
+ expect(snippet.statistics).to be_persisted
+ end
+ end
+
describe '#to_reference' do
context 'when snippet belongs to a project' do
let(:project) { build(:project, name: 'sample-project') }
@@ -750,4 +762,29 @@ describe Snippet do
end
end
end
+
+ describe '#list_files' do
+ let_it_be(:snippet) { create(:snippet, :repository) }
+ let(:ref) { 'test-ref' }
+
+ subject { snippet.list_files(ref) }
+
+ context 'when snippet has a repository' do
+ it 'lists files from the repository with the ref' do
+ expect(snippet.repository).to receive(:ls_files).with(ref)
+
+ subject
+ end
+ end
+
+ context 'when snippet does not have a repository' do
+ before do
+ allow(snippet.repository).to receive(:empty?).and_return(true)
+ end
+
+ it 'returns an empty array' do
+ expect(subject).to eq []
+ end
+ end
+ end
end
diff --git a/spec/models/snippet_statistics_spec.rb b/spec/models/snippet_statistics_spec.rb
new file mode 100644
index 00000000000..ad25bd7b3be
--- /dev/null
+++ b/spec/models/snippet_statistics_spec.rb
@@ -0,0 +1,149 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe SnippetStatistics do
+ let_it_be(:snippet_without_repo) { create(:snippet) }
+ let_it_be(:snippet_with_repo) { create(:snippet, :repository) }
+
+ let(:statistics) { snippet_with_repo.statistics }
+
+ it { is_expected.to belong_to(:snippet) }
+ it { is_expected.to validate_presence_of(:snippet) }
+
+ describe '#update_commit_count' do
+ subject { statistics.update_commit_count }
+
+ it 'updates the count of commits' do
+ commit_count = snippet_with_repo.repository.commit_count
+
+ subject
+
+ expect(statistics.commit_count).to eq commit_count
+ end
+
+ context 'when the snippet does not have a repository' do
+ let(:statistics) { snippet_without_repo.statistics }
+
+ it 'returns 0' do
+ expect(subject).to eq 0
+ expect(statistics.commit_count).to eq 0
+ end
+ end
+ end
+
+ describe '#update_file_count' do
+ subject { statistics.update_file_count }
+
+ it 'updates the count of files' do
+ file_count = snippet_with_repo.repository.ls_files(nil).count
+
+ subject
+
+ expect(statistics.file_count).to eq file_count
+ end
+
+ context 'when the snippet does not have a repository' do
+ let(:statistics) { snippet_without_repo.statistics }
+
+ it 'returns 0' do
+ expect(subject).to eq 0
+ expect(statistics.file_count).to eq 0
+ end
+ end
+ end
+
+ describe '#update_repository_size' do
+ subject { statistics.update_repository_size }
+
+ it 'updates the repository_size' do
+ repository_size = snippet_with_repo.repository.size.megabytes.to_i
+
+ subject
+
+ expect(statistics.repository_size).to eq repository_size
+ end
+
+ context 'when the snippet does not have a repository' do
+ let(:statistics) { snippet_without_repo.statistics }
+
+ it 'returns 0' do
+ expect(subject).to eq 0
+ expect(statistics.repository_size).to eq 0
+ end
+ end
+ end
+
+ describe '#refresh!' do
+ subject { statistics.refresh! }
+
+ it 'retrieves and saves statistic data from repository' do
+ expect(statistics).to receive(:update_commit_count)
+ expect(statistics).to receive(:update_file_count)
+ expect(statistics).to receive(:update_repository_size)
+ expect(statistics).to receive(:save!)
+
+ subject
+ end
+ end
+
+ context 'with a PersonalSnippet' do
+ let!(:snippet) { create(:personal_snippet, :repository) }
+
+ shared_examples 'personal snippet statistics updates' do
+ it 'schedules a namespace statistics worker' do
+ expect(Namespaces::ScheduleAggregationWorker)
+ .to receive(:perform_async).once
+
+ statistics.save!
+ end
+
+ it 'does not try to update project stats' do
+ expect(statistics).not_to receive(:schedule_update_project_statistic)
+
+ statistics.save!
+ end
+ end
+
+ context 'when creating' do
+ let(:statistics) { build(:snippet_statistics, snippet_id: snippet.id, with_data: true) }
+
+ before do
+ snippet.statistics.delete
+ end
+
+ it_behaves_like 'personal snippet statistics updates'
+ end
+
+ context 'when updating' do
+ let(:statistics) { snippet.statistics }
+
+ before do
+ snippet.statistics.repository_size = 123
+ end
+
+ it_behaves_like 'personal snippet statistics updates'
+ end
+ end
+
+ context 'with a ProjectSnippet' do
+ let!(:snippet) { create(:project_snippet) }
+
+ it_behaves_like 'UpdateProjectStatistics' do
+ subject { build(:snippet_statistics, snippet: snippet, id: snippet.id, with_data: true) }
+
+ before do
+ # The shared examples requires the snippet statistics not to be present
+ snippet.statistics.delete
+ snippet.reload
+ end
+ end
+
+ it 'does not call personal snippet callbacks' do
+ expect(snippet.statistics).not_to receive(:update_author_root_storage_statistics)
+ expect(snippet.statistics).to receive(:schedule_update_project_statistic)
+
+ snippet.statistics.update!(repository_size: 123)
+ end
+ end
+end
diff --git a/spec/models/spam_log_spec.rb b/spec/models/spam_log_spec.rb
index 8d0f247b5d6..97a0dc27f17 100644
--- a/spec/models/spam_log_spec.rb
+++ b/spec/models/spam_log_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe SpamLog do
+RSpec.describe SpamLog do
let_it_be(:admin) { create(:admin) }
describe 'associations' do
diff --git a/spec/models/ssh_host_key_spec.rb b/spec/models/ssh_host_key_spec.rb
index a17cd8ba345..4d729d5585f 100644
--- a/spec/models/ssh_host_key_spec.rb
+++ b/spec/models/ssh_host_key_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe SshHostKey do
+RSpec.describe SshHostKey do
using RSpec::Parameterized::TableSyntax
include ReactiveCachingHelpers
diff --git a/spec/models/state_note_spec.rb b/spec/models/state_note_spec.rb
index d3409315e41..bd07af7ceca 100644
--- a/spec/models/state_note_spec.rb
+++ b/spec/models/state_note_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe StateNote do
+RSpec.describe StateNote do
describe '.from_event' do
let_it_be(:author) { create(:user) }
let_it_be(:project) { create(:project, :repository) }
@@ -10,18 +10,62 @@ describe StateNote do
ResourceStateEvent.states.each do |state, _value|
context "with event state #{state}" do
- let_it_be(:event) { create(:resource_state_event, issue: noteable, state: state, created_at: '2020-02-05') }
+ let(:event) { create(:resource_state_event, issue: noteable, state: state, created_at: '2020-02-05') }
subject { described_class.from_event(event, resource: noteable, resource_parent: project) }
- it_behaves_like 'a system note', exclude_project: true do
- let(:action) { state.to_s }
+ it_behaves_like 'a synthetic note', state == 'reopened' ? 'opened' : state
+
+ it 'contains the expected values' do
+ expect(subject.author).to eq(author)
+ expect(subject.created_at).to eq(event.created_at)
+ expect(subject.note).to eq(state)
+ end
+ end
+ end
+
+ context 'with a mentionable source' do
+ subject { described_class.from_event(event, resource: noteable, resource_parent: project) }
+
+ context 'with a commit' do
+ let(:commit) { create(:commit, project: project) }
+ let(:event) { create(:resource_state_event, issue: noteable, state: :closed, created_at: '2020-02-05', source_commit: commit.id) }
+
+ it 'contains the expected values' do
+ expect(subject.author).to eq(author)
+ expect(subject.created_at).to eq(subject.created_at)
+ expect(subject.note).to eq("closed via commit #{commit.id}")
+ end
+ end
+
+ context 'with a merge request' do
+ let(:merge_request) { create(:merge_request, source_project: project) }
+ let(:event) { create(:resource_state_event, issue: noteable, state: :closed, created_at: '2020-02-05', source_merge_request: merge_request) }
+
+ it 'contains the expected values' do
+ expect(subject.author).to eq(author)
+ expect(subject.created_at).to eq(event.created_at)
+ expect(subject.note).to eq("closed via merge request !#{merge_request.iid}")
+ end
+ end
+
+ context 'when closed by error tracking' do
+ let(:event) { create(:resource_state_event, issue: noteable, state: :closed, created_at: '2020-02-05', close_after_error_tracking_resolve: true) }
+
+ it 'contains the expected values' do
+ expect(subject.author).to eq(author)
+ expect(subject.created_at).to eq(event.created_at)
+ expect(subject.note).to eq('resolved the corresponding error and closed the issue.')
end
+ end
+
+ context 'when closed by promotheus alert' do
+ let(:event) { create(:resource_state_event, issue: noteable, state: :closed, created_at: '2020-02-05', close_auto_resolve_prometheus_alert: true) }
it 'contains the expected values' do
expect(subject.author).to eq(author)
expect(subject.created_at).to eq(event.created_at)
- expect(subject.note_html).to eq("<p dir=\"auto\">#{state}</p>")
+ expect(subject.note).to eq('automatically closed this issue because the alert resolved.')
end
end
end
diff --git a/spec/models/subscription_spec.rb b/spec/models/subscription_spec.rb
index 41bd48810b2..be85e6e10f4 100644
--- a/spec/models/subscription_spec.rb
+++ b/spec/models/subscription_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Subscription do
+RSpec.describe Subscription do
describe 'relationships' do
it { is_expected.to belong_to(:project) }
it { is_expected.to belong_to(:subscribable) }
diff --git a/spec/models/suggestion_spec.rb b/spec/models/suggestion_spec.rb
index 2ac3ae0a5ad..6c30bc39c1d 100644
--- a/spec/models/suggestion_spec.rb
+++ b/spec/models/suggestion_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Suggestion do
+RSpec.describe Suggestion do
let(:suggestion) { create(:suggestion) }
describe 'associations' do
@@ -38,26 +38,106 @@ describe Suggestion do
end
describe '#appliable?' do
- context 'when patch is already applied' do
- let(:suggestion) { create(:suggestion, :applied) }
+ let(:suggestion) { build(:suggestion) }
- it 'returns false' do
- expect(suggestion).not_to be_appliable
+ subject(:appliable) { suggestion.appliable? }
+
+ before do
+ allow(suggestion).to receive(:inapplicable_reason).and_return(inapplicable_reason)
+ end
+
+ context 'when inapplicable_reason is nil' do
+ let(:inapplicable_reason) { nil }
+
+ it { is_expected.to be_truthy }
+ end
+
+ context 'when inapplicable_reason is not nil' do
+ let(:inapplicable_reason) { :applied }
+
+ it { is_expected.to be_falsey }
+ end
+ end
+
+ describe '#inapplicable_reason' do
+ let(:merge_request) { create(:merge_request) }
+
+ let!(:note) do
+ create(
+ :diff_note_on_merge_request,
+ project: merge_request.project,
+ noteable: merge_request
+ )
+ end
+
+ let(:suggestion) { build(:suggestion, note: note) }
+
+ subject(:inapplicable_reason) { suggestion.inapplicable_reason }
+
+ context 'when suggestion is already applied' do
+ let(:suggestion) { build(:suggestion, :applied, note: note) }
+
+ it { is_expected.to eq(:applied) }
+ end
+
+ context 'when merge request was merged' do
+ before do
+ merge_request.mark_as_merged!
+ end
+
+ it { is_expected.to eq(:merge_request_merged) }
+ end
+
+ context 'when merge request is closed' do
+ before do
+ merge_request.close!
end
+
+ it { is_expected.to eq(:merge_request_closed) }
end
- context 'when merge request is not opened' do
- let(:merge_request) { create(:merge_request, :merged) }
- let(:note) do
- create(:diff_note_on_merge_request, project: merge_request.project,
- noteable: merge_request)
+ context 'when source branch is deleted' do
+ before do
+ merge_request.project.repository.rm_branch(merge_request.author, merge_request.source_branch)
end
- let(:suggestion) { create(:suggestion, note: note) }
+ it { is_expected.to eq(:source_branch_deleted) }
+ end
- it 'returns false' do
- expect(suggestion).not_to be_appliable
+ context 'when content is outdated' do
+ before do
+ allow(suggestion).to receive(:outdated?).and_return(true)
+ end
+
+ it { is_expected.to eq(:outdated) }
+ end
+
+ context 'when note is outdated' do
+ before do
+ allow(note).to receive(:active?).and_return(false)
end
+
+ it { is_expected.to eq(:outdated) }
+ end
+
+ context 'when applicable' do
+ it { is_expected.to be_nil }
+ end
+ end
+
+ describe '#single_line?' do
+ subject(:single_line) { suggestion.single_line? }
+
+ context 'when suggestion is for a single line' do
+ let(:suggestion) { build(:suggestion, lines_above: 0, lines_below: 0) }
+
+ it { is_expected.to eq(true) }
+ end
+
+ context 'when suggestion is for multiple lines' do
+ let(:suggestion) { build(:suggestion, lines_above: 2, lines_below: 0) }
+
+ it { is_expected.to eq(false) }
end
end
end
diff --git a/spec/models/system_note_metadata_spec.rb b/spec/models/system_note_metadata_spec.rb
index 801f139355b..9a6b57afb97 100644
--- a/spec/models/system_note_metadata_spec.rb
+++ b/spec/models/system_note_metadata_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe SystemNoteMetadata do
+RSpec.describe SystemNoteMetadata do
describe 'associations' do
it { is_expected.to belong_to(:note) }
it { is_expected.to belong_to(:description_version) }
diff --git a/spec/models/term_agreement_spec.rb b/spec/models/term_agreement_spec.rb
index 42a48048b67..98c7a2daadd 100644
--- a/spec/models/term_agreement_spec.rb
+++ b/spec/models/term_agreement_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe TermAgreement do
+RSpec.describe TermAgreement do
describe 'validations' do
it { is_expected.to validate_presence_of(:term) }
it { is_expected.to validate_presence_of(:user) }
diff --git a/spec/models/terraform/state_spec.rb b/spec/models/terraform/state_spec.rb
index 3cd15e23ee2..00e67ad70db 100644
--- a/spec/models/terraform/state_spec.rb
+++ b/spec/models/terraform/state_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Terraform::State do
+RSpec.describe Terraform::State do
subject { create(:terraform_state, :with_file) }
let(:terraform_state_file) { fixture_file('terraform/terraform.tfstate') }
diff --git a/spec/models/todo_spec.rb b/spec/models/todo_spec.rb
index bda89fc01f3..44e81455a67 100644
--- a/spec/models/todo_spec.rb
+++ b/spec/models/todo_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Todo do
+RSpec.describe Todo do
let(:issue) { create(:issue) }
describe 'relationships' do
diff --git a/spec/models/tree_spec.rb b/spec/models/tree_spec.rb
index 7dde8459f9a..1522d836f76 100644
--- a/spec/models/tree_spec.rb
+++ b/spec/models/tree_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Tree do
+RSpec.describe Tree do
let(:repository) { create(:project, :repository).repository }
let(:sha) { repository.root_ref }
diff --git a/spec/models/trending_project_spec.rb b/spec/models/trending_project_spec.rb
index 39f5d686eb4..802f8befbcd 100644
--- a/spec/models/trending_project_spec.rb
+++ b/spec/models/trending_project_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe TrendingProject do
+RSpec.describe TrendingProject do
let(:user) { create(:user) }
let(:public_project1) { create(:project, :public, :repository) }
let(:public_project2) { create(:project, :public, :repository) }
diff --git a/spec/models/upload_spec.rb b/spec/models/upload_spec.rb
index 8a64948d570..18388b4cd83 100644
--- a/spec/models/upload_spec.rb
+++ b/spec/models/upload_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Upload do
+RSpec.describe Upload do
describe 'associations' do
it { is_expected.to belong_to(:model) }
end
diff --git a/spec/models/uploads/fog_spec.rb b/spec/models/uploads/fog_spec.rb
index 72a169280af..899e6f2064c 100644
--- a/spec/models/uploads/fog_spec.rb
+++ b/spec/models/uploads/fog_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Uploads::Fog do
+RSpec.describe Uploads::Fog do
let(:data_store) { described_class.new }
before do
diff --git a/spec/models/uploads/local_spec.rb b/spec/models/uploads/local_spec.rb
index 374c3019edc..d354b252b39 100644
--- a/spec/models/uploads/local_spec.rb
+++ b/spec/models/uploads/local_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Uploads::Local do
+RSpec.describe Uploads::Local do
let(:data_store) { described_class.new }
before do
diff --git a/spec/models/user_agent_detail_spec.rb b/spec/models/user_agent_detail_spec.rb
index 5c28511b446..e3f3d9c342b 100644
--- a/spec/models/user_agent_detail_spec.rb
+++ b/spec/models/user_agent_detail_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe UserAgentDetail do
+RSpec.describe UserAgentDetail do
describe '.submittable?' do
it 'is submittable when not already submitted' do
detail = build(:user_agent_detail)
diff --git a/spec/models/user_callout_spec.rb b/spec/models/user_callout_spec.rb
index a084b1ac662..cdf70dd5190 100644
--- a/spec/models/user_callout_spec.rb
+++ b/spec/models/user_callout_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe UserCallout do
+RSpec.describe UserCallout do
let!(:callout) { create(:user_callout) }
it_behaves_like 'having unique enum values'
diff --git a/spec/models/user_canonical_email_spec.rb b/spec/models/user_canonical_email_spec.rb
index 54a4e968033..8e26f68c09b 100644
--- a/spec/models/user_canonical_email_spec.rb
+++ b/spec/models/user_canonical_email_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe UserCanonicalEmail do
+RSpec.describe UserCanonicalEmail do
it { is_expected.to belong_to(:user) }
describe 'validations' do
diff --git a/spec/models/user_custom_attribute_spec.rb b/spec/models/user_custom_attribute_spec.rb
index d0981b2d771..1a51ad662b0 100644
--- a/spec/models/user_custom_attribute_spec.rb
+++ b/spec/models/user_custom_attribute_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe UserCustomAttribute do
+RSpec.describe UserCustomAttribute do
describe 'assocations' do
it { is_expected.to belong_to(:user) }
end
diff --git a/spec/models/user_detail_spec.rb b/spec/models/user_detail_spec.rb
index 2b2bfff7be2..041af5b9c31 100644
--- a/spec/models/user_detail_spec.rb
+++ b/spec/models/user_detail_spec.rb
@@ -2,13 +2,42 @@
require 'spec_helper'
-describe UserDetail do
+RSpec.describe UserDetail do
it { is_expected.to belong_to(:user) }
describe 'validations' do
- describe 'job_title' do
+ describe '#job_title' do
it { is_expected.not_to validate_presence_of(:job_title) }
it { is_expected.to validate_length_of(:job_title).is_at_most(200) }
end
+
+ describe '#bio' do
+ it { is_expected.to validate_length_of(:bio).is_at_most(255) }
+ end
+ end
+
+ describe '#bio_html' do
+ let(:user) { create(:user, bio: 'some **bio**') }
+
+ subject { user.user_detail.bio_html }
+
+ it 'falls back to #bio when the html representation is missing' do
+ user.user_detail.update!(bio_html: nil)
+
+ expect(subject).to eq(user.user_detail.bio)
+ end
+
+ it 'stores rendered html' do
+ expect(subject).to include('some <strong>bio</strong>')
+ end
+
+ it 'does not try to set the value when the column is not there' do
+ without_bio_html_column = UserDetail.column_names - ['bio_html']
+
+ expect(described_class).to receive(:column_names).at_least(:once).and_return(without_bio_html_column)
+ expect(user.user_detail).not_to receive(:bio_html=)
+
+ subject
+ end
end
end
diff --git a/spec/models/user_highest_role_spec.rb b/spec/models/user_highest_role_spec.rb
index b3c795f6623..3ae672cf7f7 100644
--- a/spec/models/user_highest_role_spec.rb
+++ b/spec/models/user_highest_role_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe UserHighestRole do
+RSpec.describe UserHighestRole do
describe 'associations' do
it { is_expected.to belong_to(:user).required }
end
diff --git a/spec/models/user_interacted_project_spec.rb b/spec/models/user_interacted_project_spec.rb
index 83c66bf1969..2fec8be76e8 100644
--- a/spec/models/user_interacted_project_spec.rb
+++ b/spec/models/user_interacted_project_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe UserInteractedProject do
+RSpec.describe UserInteractedProject do
describe '.track' do
subject { described_class.track(event) }
diff --git a/spec/models/user_mentions/commit_user_mention_spec.rb b/spec/models/user_mentions/commit_user_mention_spec.rb
index ebad3902d6b..91d28241650 100644
--- a/spec/models/user_mentions/commit_user_mention_spec.rb
+++ b/spec/models/user_mentions/commit_user_mention_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe CommitUserMention do
+RSpec.describe CommitUserMention do
describe 'associations' do
it { is_expected.to belong_to(:note) }
end
diff --git a/spec/models/user_mentions/issue_user_mention_spec.rb b/spec/models/user_mentions/issue_user_mention_spec.rb
index ac29f3084b4..6faf598ee36 100644
--- a/spec/models/user_mentions/issue_user_mention_spec.rb
+++ b/spec/models/user_mentions/issue_user_mention_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe IssueUserMention do
+RSpec.describe IssueUserMention do
describe 'associations' do
it { is_expected.to belong_to(:issue) }
it { is_expected.to belong_to(:note) }
diff --git a/spec/models/user_mentions/merge_request_user_mention_spec.rb b/spec/models/user_mentions/merge_request_user_mention_spec.rb
index c5c7cebfaa5..10fcb126965 100644
--- a/spec/models/user_mentions/merge_request_user_mention_spec.rb
+++ b/spec/models/user_mentions/merge_request_user_mention_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe MergeRequestUserMention do
+RSpec.describe MergeRequestUserMention do
describe 'associations' do
it { is_expected.to belong_to(:merge_request) }
it { is_expected.to belong_to(:note) }
diff --git a/spec/models/user_mentions/snippet_user_mention_spec.rb b/spec/models/user_mentions/snippet_user_mention_spec.rb
index 0e34a2dd5a1..0762e731a53 100644
--- a/spec/models/user_mentions/snippet_user_mention_spec.rb
+++ b/spec/models/user_mentions/snippet_user_mention_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe SnippetUserMention do
+RSpec.describe SnippetUserMention do
describe 'associations' do
it { is_expected.to belong_to(:snippet) }
it { is_expected.to belong_to(:note) }
diff --git a/spec/models/user_preference_spec.rb b/spec/models/user_preference_spec.rb
index cf32d4eeca7..27ddaea763d 100644
--- a/spec/models/user_preference_spec.rb
+++ b/spec/models/user_preference_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe UserPreference do
+RSpec.describe UserPreference do
let(:user_preference) { create(:user_preference) }
describe 'notes filters global keys' do
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index dd4b174a38f..fa2e4b63648 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe User do
+RSpec.describe User do
include ProjectForksHelper
include TermsHelper
include ExclusiveLeaseHelpers
@@ -58,6 +58,10 @@ describe User do
it { is_expected.to delegate_method(:job_title).to(:user_detail).allow_nil }
it { is_expected.to delegate_method(:job_title=).to(:user_detail).with_arguments(:args).allow_nil }
+
+ it { is_expected.to delegate_method(:bio).to(:user_detail).allow_nil }
+ it { is_expected.to delegate_method(:bio=).to(:user_detail).with_arguments(:args).allow_nil }
+ it { is_expected.to delegate_method(:bio_html).to(:user_detail).allow_nil }
end
describe 'associations' do
@@ -91,64 +95,28 @@ describe User do
it { is_expected.to have_many(:metrics_users_starred_dashboards).inverse_of(:user) }
it { is_expected.to have_many(:reviews).inverse_of(:author) }
- describe "#bio" do
- it 'syncs bio with `user_details.bio` on create' do
- user = create(:user, bio: 'my bio')
-
- expect(user.bio).to eq(user.user_detail.bio)
+ describe "#user_detail" do
+ it 'does not persist `user_detail` by default' do
+ expect(create(:user).user_detail).not_to be_persisted
end
- context 'when `migrate_bio_to_user_details` feature flag is off' do
- before do
- stub_feature_flags(migrate_bio_to_user_details: false)
- end
-
- it 'does not sync bio with `user_details.bio`' do
- user = create(:user, bio: 'my bio')
+ it 'creates `user_detail` when `bio` is given' do
+ user = create(:user, bio: 'my bio')
- expect(user.bio).to eq('my bio')
- expect(user.user_detail.bio).to eq('')
- end
+ expect(user.user_detail).to be_persisted
+ expect(user.user_detail.bio).to eq('my bio')
end
- it 'syncs bio with `user_details.bio` on update' do
- user = create(:user)
-
- user.update!(bio: 'my bio')
+ it 'delegates `bio` to `user_detail`' do
+ user = create(:user, bio: 'my bio')
expect(user.bio).to eq(user.user_detail.bio)
end
- context 'when `user_details` association already exists' do
- let(:user) { create(:user) }
-
- before do
- create(:user_detail, user: user)
- end
-
- it 'syncs bio with `user_details.bio`' do
- user.update!(bio: 'my bio')
-
- expect(user.bio).to eq(user.user_detail.bio)
- end
-
- it 'falls back to "" when nil is given' do
- user.update!(bio: nil)
-
- expect(user.bio).to eq(nil)
- expect(user.user_detail.bio).to eq('')
- end
-
- # very unlikely scenario
- it 'truncates long bio when syncing to user_details' do
- invalid_bio = 'a' * 256
- truncated_bio = 'a' * 255
-
- user.bio = invalid_bio
- user.save(validate: false)
+ it 'creates `user_detail` when `bio` is first updated' do
+ user = create(:user)
- expect(user.user_detail.bio).to eq(truncated_bio)
- end
+ expect { user.update(bio: 'my bio') }.to change { user.user_detail.persisted? }.from(false).to(true)
end
end
@@ -214,7 +182,7 @@ describe User do
describe 'validations' do
describe 'password' do
- let!(:user) { create(:user) }
+ let!(:user) { build_stubbed(:user) }
before do
allow(Devise).to receive(:password_length).and_return(8..128)
@@ -337,8 +305,6 @@ describe User do
it { is_expected.not_to allow_value(-1).for(:projects_limit) }
it { is_expected.not_to allow_value(Gitlab::Database::MAX_INT_VALUE + 1).for(:projects_limit) }
- it { is_expected.to validate_length_of(:bio).is_at_most(255) }
-
it_behaves_like 'an object with email-formated attributes', :email do
subject { build(:user) }
end
@@ -3745,6 +3711,12 @@ describe User do
expect(user.namespace).not_to be_nil
end
+
+ it 'creates the namespace setting' do
+ user.save!
+
+ expect(user.namespace.namespace_settings).to be_persisted
+ end
end
context 'for an existing user' do
@@ -4634,7 +4606,8 @@ describe User do
[
{ state: 'blocked' },
{ user_type: :ghost },
- { user_type: :alert_bot }
+ { user_type: :alert_bot },
+ { user_type: :support_bot }
]
end
@@ -4688,6 +4661,7 @@ describe User do
where(:user_type, :expected_result) do
'human' | true
'alert_bot' | false
+ 'support_bot' | false
end
with_them do
@@ -4756,19 +4730,44 @@ describe User do
end
end
- describe '#migration_bot' do
- it 'creates the user if it does not exist' do
- expect do
- described_class.migration_bot
- end.to change { User.where(user_type: :migration_bot).count }.by(1)
+ context 'bot users' do
+ shared_examples 'bot users' do |bot_type|
+ it 'creates the user if it does not exist' do
+ expect do
+ described_class.public_send(bot_type)
+ end.to change { User.where(user_type: bot_type).count }.by(1)
+ end
+
+ it 'creates a route for the namespace of the created user' do
+ bot_user = described_class.public_send(bot_type)
+
+ expect(bot_user.namespace.route).to be_present
+ end
+
+ it 'does not create a new user if it already exists' do
+ described_class.public_send(bot_type)
+
+ expect do
+ described_class.public_send(bot_type)
+ end.not_to change { User.count }
+ end
end
- it 'does not create a new user if it already exists' do
- described_class.migration_bot
+ shared_examples 'bot user avatars' do |bot_type, avatar_filename|
+ it 'sets the custom avatar for the created bot' do
+ bot_user = described_class.public_send(bot_type)
- expect do
- described_class.migration_bot
- end.not_to change { User.count }
+ expect(bot_user.avatar.url).to be_present
+ expect(bot_user.avatar.filename).to eq(avatar_filename)
+ end
end
+
+ it_behaves_like 'bot users', :alert_bot
+ it_behaves_like 'bot users', :support_bot
+ it_behaves_like 'bot users', :migration_bot
+ it_behaves_like 'bot users', :ghost
+
+ it_behaves_like 'bot user avatars', :alert_bot, 'alert-bot.png'
+ it_behaves_like 'bot user avatars', :support_bot, 'support-bot.png'
end
end
diff --git a/spec/models/user_status_spec.rb b/spec/models/user_status_spec.rb
index fcc01cdae3d..2c0664bd165 100644
--- a/spec/models/user_status_spec.rb
+++ b/spec/models/user_status_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe UserStatus do
+RSpec.describe UserStatus do
it { is_expected.to validate_presence_of(:user) }
it { is_expected.to allow_value('smirk').for(:emoji) }
diff --git a/spec/models/users_statistics_spec.rb b/spec/models/users_statistics_spec.rb
index 4437a5469c6..b4b7ddb7c63 100644
--- a/spec/models/users_statistics_spec.rb
+++ b/spec/models/users_statistics_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe UsersStatistics do
+RSpec.describe UsersStatistics do
let(:users_statistics) { build(:users_statistics) }
describe 'scopes' do
diff --git a/spec/models/web_ide_terminal_spec.rb b/spec/models/web_ide_terminal_spec.rb
index 4103a26c75a..149fce33f43 100644
--- a/spec/models/web_ide_terminal_spec.rb
+++ b/spec/models/web_ide_terminal_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe WebIdeTerminal do
+RSpec.describe WebIdeTerminal do
let(:build) { create(:ci_build) }
subject { described_class.new(build) }
diff --git a/spec/models/wiki_page/meta_spec.rb b/spec/models/wiki_page/meta_spec.rb
index 0255dd802cf..aaac72cbc68 100644
--- a/spec/models/wiki_page/meta_spec.rb
+++ b/spec/models/wiki_page/meta_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe WikiPage::Meta do
+RSpec.describe WikiPage::Meta do
let_it_be(:project) { create(:project, :wiki_repo) }
let_it_be(:other_project) { create(:project) }
diff --git a/spec/models/wiki_page/slug_spec.rb b/spec/models/wiki_page/slug_spec.rb
index 324dea6b320..cf256c67277 100644
--- a/spec/models/wiki_page/slug_spec.rb
+++ b/spec/models/wiki_page/slug_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe WikiPage::Slug do
+RSpec.describe WikiPage::Slug do
let_it_be(:meta) { create(:wiki_page_meta) }
describe 'Associations' do
diff --git a/spec/models/wiki_page_spec.rb b/spec/models/wiki_page_spec.rb
index 8f2da8ff9a1..a2ca6441f28 100644
--- a/spec/models/wiki_page_spec.rb
+++ b/spec/models/wiki_page_spec.rb
@@ -2,7 +2,7 @@
require "spec_helper"
-describe WikiPage do
+RSpec.describe WikiPage do
let_it_be(:user) { create(:user) }
let(:container) { create(:project, :wiki_repo) }
let(:wiki) { Wiki.for_container(container, user) }
@@ -864,6 +864,24 @@ describe WikiPage do
end
end
+ describe '#diffs' do
+ subject { existing_page }
+
+ it 'returns a diff instance' do
+ diffs = subject.diffs(foo: 'bar')
+
+ expect(diffs).to be_a(Gitlab::Diff::FileCollection::WikiPage)
+ expect(diffs.diffable).to be_a(Commit)
+ expect(diffs.diffable.id).to eq(subject.version.id)
+ expect(diffs.project).to be(subject.wiki)
+ expect(diffs.diff_options).to include(
+ expanded: true,
+ paths: [subject.path],
+ foo: 'bar'
+ )
+ end
+ end
+
private
def get_slugs(page_or_dir)
diff --git a/spec/models/zoom_meeting_spec.rb b/spec/models/zoom_meeting_spec.rb
index 3dad957a1ce..00a0f92e848 100644
--- a/spec/models/zoom_meeting_spec.rb
+++ b/spec/models/zoom_meeting_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ZoomMeeting do
+RSpec.describe ZoomMeeting do
let(:project) { build(:project) }
describe 'Factory' do
diff --git a/spec/policies/alert_management/alert_policy_spec.rb b/spec/policies/alert_management/alert_policy_spec.rb
index 0d7624a0142..3e08d8b4ccc 100644
--- a/spec/policies/alert_management/alert_policy_spec.rb
+++ b/spec/policies/alert_management/alert_policy_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe AlertManagement::AlertPolicy, :models do
+RSpec.describe AlertManagement::AlertPolicy, :models do
let(:alert) { create(:alert_management_alert) }
let(:project) { alert.project }
let(:user) { create(:user) }
diff --git a/spec/policies/application_setting/term_policy_spec.rb b/spec/policies/application_setting/term_policy_spec.rb
index 2b5b9758ec2..00b48402fa6 100644
--- a/spec/policies/application_setting/term_policy_spec.rb
+++ b/spec/policies/application_setting/term_policy_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ApplicationSetting::TermPolicy do
+RSpec.describe ApplicationSetting::TermPolicy do
include TermsHelper
let_it_be(:term) { create(:term) }
diff --git a/spec/policies/award_emoji_policy_spec.rb b/spec/policies/award_emoji_policy_spec.rb
index 2e3693c58d7..bd34a656e12 100644
--- a/spec/policies/award_emoji_policy_spec.rb
+++ b/spec/policies/award_emoji_policy_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe AwardEmojiPolicy do
+RSpec.describe AwardEmojiPolicy do
let(:user) { create(:user) }
let(:award_emoji) { create(:award_emoji, awardable: awardable) }
diff --git a/spec/policies/base_policy_spec.rb b/spec/policies/base_policy_spec.rb
index 67f7452528a..103f2e9bc39 100644
--- a/spec/policies/base_policy_spec.rb
+++ b/spec/policies/base_policy_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe BasePolicy do
+RSpec.describe BasePolicy do
include ExternalAuthorizationServiceHelpers
include AdminModeHelper
diff --git a/spec/policies/blob_policy_spec.rb b/spec/policies/blob_policy_spec.rb
index e48dd751a8f..fc46b25f25c 100644
--- a/spec/policies/blob_policy_spec.rb
+++ b/spec/policies/blob_policy_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe BlobPolicy, :enable_admin_mode do
+RSpec.describe BlobPolicy, :enable_admin_mode do
include_context 'ProjectPolicyTable context'
include ProjectHelpers
using RSpec::Parameterized::TableSyntax
diff --git a/spec/policies/board_policy_spec.rb b/spec/policies/board_policy_spec.rb
index 35eac8a02c4..6940e75ec37 100644
--- a/spec/policies/board_policy_spec.rb
+++ b/spec/policies/board_policy_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe BoardPolicy do
+RSpec.describe BoardPolicy do
let(:user) { create(:user) }
let(:project) { create(:project, :private) }
let(:group) { create(:group, :private) }
diff --git a/spec/policies/ci/build_policy_spec.rb b/spec/policies/ci/build_policy_spec.rb
index 5857369a550..d2547338855 100644
--- a/spec/policies/ci/build_policy_spec.rb
+++ b/spec/policies/ci/build_policy_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::BuildPolicy do
+RSpec.describe Ci::BuildPolicy do
let(:user) { create(:user) }
let(:build) { create(:ci_build, pipeline: pipeline) }
let(:pipeline) { create(:ci_empty_pipeline, project: project) }
diff --git a/spec/policies/ci/pipeline_policy_spec.rb b/spec/policies/ci/pipeline_policy_spec.rb
index 293fe1fc5b9..fcd96bc6653 100644
--- a/spec/policies/ci/pipeline_policy_spec.rb
+++ b/spec/policies/ci/pipeline_policy_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::PipelinePolicy, :models do
+RSpec.describe Ci::PipelinePolicy, :models do
let(:user) { create(:user) }
let(:pipeline) { create(:ci_empty_pipeline, project: project) }
diff --git a/spec/policies/ci/pipeline_schedule_policy_spec.rb b/spec/policies/ci/pipeline_schedule_policy_spec.rb
index d503401f7cf..b455384d17a 100644
--- a/spec/policies/ci/pipeline_schedule_policy_spec.rb
+++ b/spec/policies/ci/pipeline_schedule_policy_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::PipelineSchedulePolicy, :models do
+RSpec.describe Ci::PipelineSchedulePolicy, :models do
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project, :repository) }
let_it_be(:pipeline_schedule, reload: true) { create(:ci_pipeline_schedule, :nightly, project: project) }
diff --git a/spec/policies/ci/trigger_policy_spec.rb b/spec/policies/ci/trigger_policy_spec.rb
index 28e5a2b2cd6..b8b54e57035 100644
--- a/spec/policies/ci/trigger_policy_spec.rb
+++ b/spec/policies/ci/trigger_policy_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::TriggerPolicy do
+RSpec.describe Ci::TriggerPolicy do
let(:user) { create(:user) }
let(:project) { create(:project) }
let(:trigger) { create(:ci_trigger, project: project, owner: create(:user)) }
diff --git a/spec/policies/clusters/cluster_policy_spec.rb b/spec/policies/clusters/cluster_policy_spec.rb
index 26cfc19862a..0b931c6f927 100644
--- a/spec/policies/clusters/cluster_policy_spec.rb
+++ b/spec/policies/clusters/cluster_policy_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Clusters::ClusterPolicy, :models do
+RSpec.describe Clusters::ClusterPolicy, :models do
let(:cluster) { create(:cluster, :project) }
let(:project) { cluster.project }
let(:user) { create(:user) }
diff --git a/spec/policies/clusters/instance_policy_spec.rb b/spec/policies/clusters/instance_policy_spec.rb
index dfe480d7fa4..f90841fc311 100644
--- a/spec/policies/clusters/instance_policy_spec.rb
+++ b/spec/policies/clusters/instance_policy_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Clusters::InstancePolicy do
+RSpec.describe Clusters::InstancePolicy do
let(:user) { create(:user) }
let(:policy) { described_class.new(user, Clusters::Instance.new) }
diff --git a/spec/policies/commit_policy_spec.rb b/spec/policies/commit_policy_spec.rb
index 40183f51e9e..0d3dcc97565 100644
--- a/spec/policies/commit_policy_spec.rb
+++ b/spec/policies/commit_policy_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe CommitPolicy do
+RSpec.describe CommitPolicy do
describe '#rules' do
let(:user) { create(:user) }
let(:commit) { project.repository.head_commit }
diff --git a/spec/policies/concerns/policy_actor_spec.rb b/spec/policies/concerns/policy_actor_spec.rb
index 27db9710a38..7271cbb4a9d 100644
--- a/spec/policies/concerns/policy_actor_spec.rb
+++ b/spec/policies/concerns/policy_actor_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe PolicyActor do
+RSpec.describe PolicyActor do
it 'implements all the methods from user' do
methods = subject.instance_methods
diff --git a/spec/policies/deploy_key_policy_spec.rb b/spec/policies/deploy_key_policy_spec.rb
index 545647e2c67..d84b80a8738 100644
--- a/spec/policies/deploy_key_policy_spec.rb
+++ b/spec/policies/deploy_key_policy_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe DeployKeyPolicy do
+RSpec.describe DeployKeyPolicy do
subject { described_class.new(current_user, deploy_key) }
describe 'updating a deploy_key' do
diff --git a/spec/policies/deploy_keys_project_policy_spec.rb b/spec/policies/deploy_keys_project_policy_spec.rb
index 952da86b7a7..3be55e9238c 100644
--- a/spec/policies/deploy_keys_project_policy_spec.rb
+++ b/spec/policies/deploy_keys_project_policy_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe DeployKeysProjectPolicy do
+RSpec.describe DeployKeysProjectPolicy do
subject { described_class.new(current_user, deploy_key.deploy_keys_project_for(project)) }
describe 'updating a deploy_keys_project' do
diff --git a/spec/policies/deploy_token_policy_spec.rb b/spec/policies/deploy_token_policy_spec.rb
index 43e23ee55ac..f218828052e 100644
--- a/spec/policies/deploy_token_policy_spec.rb
+++ b/spec/policies/deploy_token_policy_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe DeployTokenPolicy do
+RSpec.describe DeployTokenPolicy do
let(:current_user) { create(:user) }
let(:project) { create(:project) }
let(:deploy_token) { create(:deploy_token, projects: [project]) }
diff --git a/spec/policies/design_management/design_policy_spec.rb b/spec/policies/design_management/design_policy_spec.rb
index a566aecc4b7..5dde5f896c9 100644
--- a/spec/policies/design_management/design_policy_spec.rb
+++ b/spec/policies/design_management/design_policy_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe DesignManagement::DesignPolicy do
+RSpec.describe DesignManagement::DesignPolicy do
include DesignManagementTestHelpers
include_context 'ProjectPolicy context'
diff --git a/spec/policies/environment_policy_spec.rb b/spec/policies/environment_policy_spec.rb
index 75fca464ec8..649b1a770c0 100644
--- a/spec/policies/environment_policy_spec.rb
+++ b/spec/policies/environment_policy_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe EnvironmentPolicy do
+RSpec.describe EnvironmentPolicy do
using RSpec::Parameterized::TableSyntax
let(:user) { create(:user) }
diff --git a/spec/policies/global_policy_spec.rb b/spec/policies/global_policy_spec.rb
index e8ba4eed4ec..4954eafe338 100644
--- a/spec/policies/global_policy_spec.rb
+++ b/spec/policies/global_policy_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GlobalPolicy do
+RSpec.describe GlobalPolicy do
include TermsHelper
let_it_be(:project_bot) { create(:user, :project_bot) }
@@ -130,6 +130,24 @@ describe GlobalPolicy do
end
end
+ describe 'using project statistics filters' do
+ context 'regular user' do
+ it { is_expected.not_to be_allowed(:use_project_statistics_filters) }
+ end
+
+ context 'admin' do
+ let(:current_user) { create(:user, :admin) }
+
+ context 'when admin mode is enabled', :enable_admin_mode do
+ it { is_expected.to be_allowed(:use_project_statistics_filters) }
+ end
+
+ context 'when admin mode is disabled' do
+ it { is_expected.to be_disallowed(:use_project_statistics_filters) }
+ end
+ end
+ end
+
shared_examples 'access allowed when terms accepted' do |ability|
it { is_expected.not_to be_allowed(ability) }
diff --git a/spec/policies/group_member_policy_spec.rb b/spec/policies/group_member_policy_spec.rb
index a4f3301a064..4215fa09301 100644
--- a/spec/policies/group_member_policy_spec.rb
+++ b/spec/policies/group_member_policy_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GroupMemberPolicy do
+RSpec.describe GroupMemberPolicy do
let(:guest) { create(:user) }
let(:owner) { create(:user) }
let(:group) { create(:group, :private) }
diff --git a/spec/policies/group_policy_spec.rb b/spec/policies/group_policy_spec.rb
index 6b17a8285a2..733cc9bd9cb 100644
--- a/spec/policies/group_policy_spec.rb
+++ b/spec/policies/group_policy_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GroupPolicy do
+RSpec.describe GroupPolicy do
include_context 'GroupPolicy context'
context 'public group with no user' do
@@ -154,7 +154,7 @@ describe GroupPolicy do
context 'admin' do
let(:current_user) { admin }
- it do
+ specify do
expect_allowed(*read_group_permissions)
expect_allowed(*guest_permissions)
expect_allowed(*reporter_permissions)
@@ -162,6 +162,10 @@ describe GroupPolicy do
expect_allowed(*maintainer_permissions)
expect_allowed(*owner_permissions)
end
+
+ context 'with admin mode', :enable_admin_mode do
+ specify { expect_allowed(*admin_permissions) }
+ end
end
describe 'private nested group use the highest access level from the group and inherited permissions' do
@@ -661,4 +665,61 @@ describe GroupPolicy do
end
end
end
+
+ describe 'design activity' do
+ let_it_be(:group) { create(:group, :public) }
+ let(:current_user) { nil }
+
+ subject { described_class.new(current_user, group) }
+
+ context 'when design management is not available' do
+ it { is_expected.not_to be_allowed(:read_design_activity) }
+
+ context 'even when there are projects in the group' do
+ before do
+ create_list(:project_group_link, 2, group: group)
+ end
+
+ it { is_expected.not_to be_allowed(:read_design_activity) }
+ end
+ end
+
+ context 'when design management is available globally' do
+ include DesignManagementTestHelpers
+
+ before do
+ enable_design_management
+ end
+
+ context 'the group has no projects' do
+ it { is_expected.not_to be_allowed(:read_design_activity) }
+ end
+
+ context 'the group has a project' do
+ let(:project) { create(:project, :public) }
+
+ before do
+ create(:project_group_link, project: project, group: group)
+ end
+
+ it { is_expected.to be_allowed(:read_design_activity) }
+
+ context 'which does not have design management enabled' do
+ before do
+ project.update(lfs_enabled: false)
+ end
+
+ it { is_expected.not_to be_allowed(:read_design_activity) }
+
+ context 'but another project does' do
+ before do
+ create(:project_group_link, project: create(:project, :public), group: group)
+ end
+
+ it { is_expected.to be_allowed(:read_design_activity) }
+ end
+ end
+ end
+ end
+ end
end
diff --git a/spec/policies/identity_provider_policy_spec.rb b/spec/policies/identity_provider_policy_spec.rb
index 52b6d2c89ba..f6b4e15cff9 100644
--- a/spec/policies/identity_provider_policy_spec.rb
+++ b/spec/policies/identity_provider_policy_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe IdentityProviderPolicy do
+RSpec.describe IdentityProviderPolicy do
subject(:policy) { described_class.new(user, provider) }
let(:user) { User.new }
diff --git a/spec/policies/issuable_policy_spec.rb b/spec/policies/issuable_policy_spec.rb
index 18e35308ecd..20eb09e11c9 100644
--- a/spec/policies/issuable_policy_spec.rb
+++ b/spec/policies/issuable_policy_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe IssuablePolicy, models: true do
+RSpec.describe IssuablePolicy, models: true do
let(:user) { create(:user) }
let(:project) { create(:project, :public) }
let(:issue) { create(:issue, project: project) }
diff --git a/spec/policies/issue_policy_spec.rb b/spec/policies/issue_policy_spec.rb
index 9d52079e4be..b3ca37b17c2 100644
--- a/spec/policies/issue_policy_spec.rb
+++ b/spec/policies/issue_policy_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe IssuePolicy do
+RSpec.describe IssuePolicy do
include ExternalAuthorizationServiceHelpers
let(:guest) { create(:user) }
diff --git a/spec/policies/merge_request_policy_spec.rb b/spec/policies/merge_request_policy_spec.rb
index 31ced5db953..2f3cb2e998a 100644
--- a/spec/policies/merge_request_policy_spec.rb
+++ b/spec/policies/merge_request_policy_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe MergeRequestPolicy do
+RSpec.describe MergeRequestPolicy do
include ExternalAuthorizationServiceHelpers
let(:guest) { create(:user) }
@@ -24,6 +24,7 @@ describe MergeRequestPolicy do
mr_perms = %i[create_merge_request_in
create_merge_request_from
read_merge_request
+ approve_merge_request
create_note].freeze
shared_examples_for 'a denied user' do
diff --git a/spec/policies/metrics/dashboard/annotation_policy_spec.rb b/spec/policies/metrics/dashboard/annotation_policy_spec.rb
index 4dc5f4cd0b4..0c59b39ae3e 100644
--- a/spec/policies/metrics/dashboard/annotation_policy_spec.rb
+++ b/spec/policies/metrics/dashboard/annotation_policy_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Metrics::Dashboard::AnnotationPolicy, :models do
+RSpec.describe Metrics::Dashboard::AnnotationPolicy, :models do
shared_examples 'metrics dashboard annotation policy' do
context 'when guest' do
before do
diff --git a/spec/policies/namespace/root_storage_statistics_policy_spec.rb b/spec/policies/namespace/root_storage_statistics_policy_spec.rb
index 8d53050fffb..e6b58bca4a8 100644
--- a/spec/policies/namespace/root_storage_statistics_policy_spec.rb
+++ b/spec/policies/namespace/root_storage_statistics_policy_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Namespace::RootStorageStatisticsPolicy do
+RSpec.describe Namespace::RootStorageStatisticsPolicy do
using RSpec::Parameterized::TableSyntax
describe '#rules' do
diff --git a/spec/policies/namespace_policy_spec.rb b/spec/policies/namespace_policy_spec.rb
index 01162dc0fc4..f2f411e48d6 100644
--- a/spec/policies/namespace_policy_spec.rb
+++ b/spec/policies/namespace_policy_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe NamespacePolicy do
+RSpec.describe NamespacePolicy do
let(:user) { create(:user) }
let(:owner) { create(:user) }
let(:admin) { create(:admin) }
diff --git a/spec/policies/note_policy_spec.rb b/spec/policies/note_policy_spec.rb
index 1e3bd0d9147..a4cc3a1e9af 100644
--- a/spec/policies/note_policy_spec.rb
+++ b/spec/policies/note_policy_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe NotePolicy do
+RSpec.describe NotePolicy do
describe '#rules' do
let(:user) { create(:user) }
let(:project) { create(:project, :public) }
diff --git a/spec/policies/packages/package_policy_spec.rb b/spec/policies/packages/package_policy_spec.rb
new file mode 100644
index 00000000000..13935974b44
--- /dev/null
+++ b/spec/policies/packages/package_policy_spec.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Packages::PackagePolicy do
+ let_it_be(:user) { create(:user) }
+ let_it_be(:project) { create(:project) }
+ let_it_be(:package) { create(:package, project: project) }
+
+ subject(:policy) { described_class.new(user, package) }
+
+ context 'when the user is part of the project' do
+ before do
+ project.add_reporter(user)
+ end
+
+ it 'allows read_package' do
+ expect(policy).to be_allowed(:read_package)
+ end
+ end
+
+ context 'when the user is not part of the project' do
+ it 'disallows read_package for any Package' do
+ expect(policy).to be_disallowed(:read_package)
+ end
+ end
+end
diff --git a/spec/policies/personal_snippet_policy_spec.rb b/spec/policies/personal_snippet_policy_spec.rb
index 5fc48717d86..d546805ce01 100644
--- a/spec/policies/personal_snippet_policy_spec.rb
+++ b/spec/policies/personal_snippet_policy_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
# Snippet visibility scenarios are included in more details in spec/support/snippet_visibility.rb
-describe PersonalSnippetPolicy do
+RSpec.describe PersonalSnippetPolicy do
let(:regular_user) { create(:user) }
let(:external_user) { create(:user, :external) }
let(:admin_user) { create(:user, :admin) }
diff --git a/spec/policies/project_member_policy_spec.rb b/spec/policies/project_member_policy_spec.rb
new file mode 100644
index 00000000000..ab8f8b83e7f
--- /dev/null
+++ b/spec/policies/project_member_policy_spec.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe ProjectMemberPolicy do
+ let(:project) { create(:project) }
+ let(:maintainer_user) { create(:user) }
+ let(:member) { create(:project_member, project: project, user: member_user) }
+
+ subject { described_class.new(maintainer_user, member) }
+
+ before do
+ create(:project_member, :maintainer, project: project, user: maintainer_user)
+ end
+
+ context 'with regular member' do
+ let(:member_user) { create(:user) }
+
+ it { is_expected.to be_allowed(:update_project_member) }
+ it { is_expected.to be_allowed(:destroy_project_member) }
+
+ it { is_expected.not_to be_allowed(:destroy_project_bot_member) }
+ end
+
+ context 'with a bot member' do
+ let(:member_user) { create(:user, :project_bot) }
+
+ it { is_expected.to be_allowed(:destroy_project_bot_member) }
+
+ it { is_expected.not_to be_allowed(:update_project_member) }
+ it { is_expected.not_to be_allowed(:destroy_project_member) }
+ end
+end
diff --git a/spec/policies/project_policy_spec.rb b/spec/policies/project_policy_spec.rb
index 6ec63ba61ca..dc6ed94309b 100644
--- a/spec/policies/project_policy_spec.rb
+++ b/spec/policies/project_policy_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ProjectPolicy do
+RSpec.describe ProjectPolicy do
include ExternalAuthorizationServiceHelpers
include_context 'ProjectPolicy context'
let_it_be(:other_user) { create(:user) }
@@ -30,7 +30,7 @@ describe ProjectPolicy do
admin_issue admin_label admin_list read_commit_status read_build
read_container_image read_pipeline read_environment read_deployment
read_merge_request download_wiki_code read_sentry_issue read_metrics_dashboard_annotation
- metrics_dashboard
+ metrics_dashboard read_confidential_issues
]
end
@@ -46,6 +46,7 @@ describe ProjectPolicy do
resolve_note create_container_image update_container_image destroy_container_image daily_statistics
create_environment update_environment create_deployment update_deployment create_release update_release
create_metrics_dashboard_annotation delete_metrics_dashboard_annotation update_metrics_dashboard_annotation
+ read_terraform_state
]
end
@@ -496,6 +497,33 @@ describe ProjectPolicy do
end
end
+ context 'support bot' do
+ let(:current_user) { User.support_bot }
+
+ subject { described_class.new(current_user, project) }
+
+ context 'with service desk disabled' do
+ it { expect_allowed(:guest_access) }
+ it { expect_disallowed(:create_note, :read_project) }
+ end
+
+ context 'with service desk enabled' do
+ before do
+ allow(project).to receive(:service_desk_enabled?).and_return(true)
+ end
+
+ it { expect_allowed(:reporter_access, :create_note, :read_issue) }
+
+ context 'when issues are protected members only' do
+ before do
+ project.project_feature.update!(issues_access_level: ProjectFeature::PRIVATE)
+ end
+
+ it { expect_allowed(:reporter_access, :create_note, :read_issue) }
+ end
+ end
+ end
+
describe 'read_prometheus_alerts' do
subject { described_class.new(current_user, project) }
@@ -855,6 +883,28 @@ describe ProjectPolicy do
end
end
+ describe 'design permissions' do
+ subject { described_class.new(guest, project) }
+
+ let(:design_permissions) do
+ %i[read_design_activity read_design]
+ end
+
+ context 'when design management is not available' do
+ it { is_expected.not_to be_allowed(*design_permissions) }
+ end
+
+ context 'when design management is available' do
+ include DesignManagementTestHelpers
+
+ before do
+ enable_design_management
+ end
+
+ it { is_expected.to be_allowed(*design_permissions) }
+ end
+ end
+
describe 'read_build_report_results' do
subject { described_class.new(guest, project) }
@@ -892,4 +942,64 @@ describe ProjectPolicy do
it { is_expected.to be_disallowed(:read_build_report_results) }
end
end
+
+ describe 'read_package' do
+ subject { described_class.new(current_user, project) }
+
+ context 'with admin' do
+ let(:current_user) { admin }
+
+ it { is_expected.to be_allowed(:read_package) }
+
+ context 'when repository is disabled' do
+ before do
+ project.project_feature.update(repository_access_level: ProjectFeature::DISABLED)
+ end
+
+ it { is_expected.to be_disallowed(:read_package) }
+ end
+ end
+
+ context 'with owner' do
+ let(:current_user) { owner }
+
+ it { is_expected.to be_allowed(:read_package) }
+ end
+
+ context 'with maintainer' do
+ let(:current_user) { maintainer }
+
+ it { is_expected.to be_allowed(:read_package) }
+ end
+
+ context 'with developer' do
+ let(:current_user) { developer }
+
+ it { is_expected.to be_allowed(:read_package) }
+ end
+
+ context 'with reporter' do
+ let(:current_user) { reporter }
+
+ it { is_expected.to be_allowed(:read_package) }
+ end
+
+ context 'with guest' do
+ let(:current_user) { guest }
+
+ it { is_expected.to be_allowed(:read_package) }
+ end
+
+ context 'with non member' do
+ let(:current_user) { create(:user) }
+
+ it { is_expected.to be_allowed(:read_package) }
+ end
+
+ context 'with anonymous' do
+ let(:current_user) { nil }
+
+ it { is_expected.to be_allowed(:read_package) }
+ end
+ end
end
diff --git a/spec/policies/project_snippet_policy_spec.rb b/spec/policies/project_snippet_policy_spec.rb
index 3864666f587..bdf9eaedbf1 100644
--- a/spec/policies/project_snippet_policy_spec.rb
+++ b/spec/policies/project_snippet_policy_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
# Snippet visibility scenarios are included in more details in spec/support/snippet_visibility.rb
-describe ProjectSnippetPolicy do
+RSpec.describe ProjectSnippetPolicy do
let_it_be(:regular_user) { create(:user) }
let_it_be(:other_user) { create(:user) }
let_it_be(:external_user) { create(:user, :external) }
diff --git a/spec/policies/project_statistics_policy_spec.rb b/spec/policies/project_statistics_policy_spec.rb
index 50dfbf7291b..74630dc38ad 100644
--- a/spec/policies/project_statistics_policy_spec.rb
+++ b/spec/policies/project_statistics_policy_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ProjectStatisticsPolicy do
+RSpec.describe ProjectStatisticsPolicy do
using RSpec::Parameterized::TableSyntax
describe '#rules' do
diff --git a/spec/policies/protected_branch_policy_spec.rb b/spec/policies/protected_branch_policy_spec.rb
index ea7fd093e38..bb6dbff18a0 100644
--- a/spec/policies/protected_branch_policy_spec.rb
+++ b/spec/policies/protected_branch_policy_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ProtectedBranchPolicy do
+RSpec.describe ProtectedBranchPolicy do
let(:user) { create(:user) }
let(:name) { 'feature' }
let(:protected_branch) { create(:protected_branch, name: name) }
diff --git a/spec/policies/releases/source_policy_spec.rb b/spec/policies/releases/source_policy_spec.rb
deleted file mode 100644
index 1bc6d5415d3..00000000000
--- a/spec/policies/releases/source_policy_spec.rb
+++ /dev/null
@@ -1,88 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-describe Releases::SourcePolicy do
- using RSpec::Parameterized::TableSyntax
-
- let(:policy) { described_class.new(user, source) }
-
- let_it_be(:public_user) { create(:user) }
- let_it_be(:guest) { create(:user) }
- let_it_be(:reporter) { create(:user) }
-
- let(:release) { create(:release, project: project) }
- let(:source) { release.sources.first }
-
- shared_examples 'source code access' do
- it "allows access a release's source code" do
- expect(policy).to be_allowed(:read_release_sources)
- end
- end
-
- shared_examples 'no source code access' do
- it "does not allow access a release's source code" do
- expect(policy).to be_disallowed(:read_release_sources)
- end
- end
-
- context 'a private project' do
- let_it_be(:project) { create(:project, :private) }
-
- context 'accessed by a public user' do
- let(:user) { public_user }
-
- it_behaves_like 'no source code access'
- end
-
- context 'accessed by a user with Guest permissions' do
- let(:user) { guest }
-
- before do
- project.add_guest(user)
- end
-
- it_behaves_like 'no source code access'
- end
-
- context 'accessed by a user with Reporter permissions' do
- let(:user) { reporter }
-
- before do
- project.add_reporter(user)
- end
-
- it_behaves_like 'source code access'
- end
- end
-
- context 'a public project' do
- let_it_be(:project) { create(:project, :public) }
-
- context 'accessed by a public user' do
- let(:user) { public_user }
-
- it_behaves_like 'source code access'
- end
-
- context 'accessed by a user with Guest permissions' do
- let(:user) { guest }
-
- before do
- project.add_guest(user)
- end
-
- it_behaves_like 'source code access'
- end
-
- context 'accessed by a user with Reporter permissions' do
- let(:user) { reporter }
-
- before do
- project.add_reporter(user)
- end
-
- it_behaves_like 'source code access'
- end
- end
-end
diff --git a/spec/policies/resource_label_event_policy_spec.rb b/spec/policies/resource_label_event_policy_spec.rb
index 4db2390c818..eff2b0e1af5 100644
--- a/spec/policies/resource_label_event_policy_spec.rb
+++ b/spec/policies/resource_label_event_policy_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ResourceLabelEventPolicy do
+RSpec.describe ResourceLabelEventPolicy do
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project, :private) }
let_it_be(:issue) { create(:issue, project: project) }
diff --git a/spec/policies/todo_policy_spec.rb b/spec/policies/todo_policy_spec.rb
index be6fecd1045..b4876baa504 100644
--- a/spec/policies/todo_policy_spec.rb
+++ b/spec/policies/todo_policy_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe TodoPolicy do
+RSpec.describe TodoPolicy do
let_it_be(:author) { create(:user) }
let_it_be(:user1) { create(:user) }
diff --git a/spec/policies/user_policy_spec.rb b/spec/policies/user_policy_spec.rb
index 63c4bd05836..1cc3581ebdd 100644
--- a/spec/policies/user_policy_spec.rb
+++ b/spec/policies/user_policy_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe UserPolicy do
+RSpec.describe UserPolicy do
let(:current_user) { create(:user) }
let(:user) { create(:user) }
diff --git a/spec/policies/wiki_page_policy_spec.rb b/spec/policies/wiki_page_policy_spec.rb
index 0dedccb6e88..093db9f8374 100644
--- a/spec/policies/wiki_page_policy_spec.rb
+++ b/spec/policies/wiki_page_policy_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe WikiPagePolicy, :enable_admin_mode do
+RSpec.describe WikiPagePolicy, :enable_admin_mode do
include_context 'ProjectPolicyTable context'
include ProjectHelpers
using RSpec::Parameterized::TableSyntax
diff --git a/spec/presenters/alert_management/alert_presenter_spec.rb b/spec/presenters/alert_management/alert_presenter_spec.rb
new file mode 100644
index 00000000000..b1bf7029f3e
--- /dev/null
+++ b/spec/presenters/alert_management/alert_presenter_spec.rb
@@ -0,0 +1,48 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe AlertManagement::AlertPresenter do
+ let_it_be(:project) { create(:project) }
+ let_it_be(:generic_payload) do
+ {
+ 'title' => 'Alert title',
+ 'start_time' => '2020-04-27T10:10:22.265949279Z',
+ 'custom' => { 'param' => 73 }
+ }
+ end
+ let_it_be(:alert) do
+ create(:alert_management_alert, :with_description, :with_host, :with_service, :with_monitoring_tool, project: project, payload: generic_payload)
+ end
+
+ subject(:presenter) { described_class.new(alert) }
+
+ describe '#issue_description' do
+ let(:markdown_line_break) { ' ' }
+
+ it 'returns an alert issue description' do
+ expect(presenter.issue_description).to eq(
+ <<~MARKDOWN.chomp
+ #### Summary
+
+ **Start time:** #{presenter.start_time}#{markdown_line_break}
+ **Severity:** #{presenter.severity}#{markdown_line_break}
+ **Service:** #{alert.service}#{markdown_line_break}
+ **Monitoring tool:** #{alert.monitoring_tool}#{markdown_line_break}
+ **Hosts:** #{alert.hosts.join(' ')}#{markdown_line_break}
+ **Description:** #{alert.description}
+
+ #### Alert Details
+
+ **custom.param:** 73
+ MARKDOWN
+ )
+ end
+ end
+
+ describe '#metrics_dashboard_url' do
+ it 'is not defined' do
+ expect(presenter.metrics_dashboard_url).to be_nil
+ end
+ end
+end
diff --git a/spec/presenters/alert_management/prometheus_alert_presenter_spec.rb b/spec/presenters/alert_management/prometheus_alert_presenter_spec.rb
new file mode 100644
index 00000000000..95246914140
--- /dev/null
+++ b/spec/presenters/alert_management/prometheus_alert_presenter_spec.rb
@@ -0,0 +1,68 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe AlertManagement::PrometheusAlertPresenter do
+ let_it_be(:project) { create(:project) }
+ let_it_be(:payload) do
+ {
+ 'annotations' => {
+ 'title' => 'Alert title',
+ 'gitlab_incident_markdown' => '**`markdown example`**',
+ 'custom annotation' => 'custom annotation value'
+ },
+ 'startsAt' => '2020-04-27T10:10:22.265949279Z',
+ 'generatorURL' => 'http://8d467bd4607a:9090/graph?g0.expr=vector%281%29&g0.tab=1'
+ }
+ end
+ let(:alert) do
+ create(:alert_management_alert, :prometheus, project: project, payload: payload)
+ end
+
+ subject(:presenter) { described_class.new(alert) }
+
+ describe '#issue_description' do
+ let(:markdown_line_break) { ' ' }
+
+ it 'returns an alert issue description' do
+ expect(presenter.issue_description).to eq(
+ <<~MARKDOWN.chomp
+ #### Summary
+
+ **Start time:** #{presenter.start_time}#{markdown_line_break}
+ **Severity:** #{presenter.severity}#{markdown_line_break}
+ **full_query:** `vector(1)`#{markdown_line_break}
+ **Monitoring tool:** Prometheus
+
+ #### Alert Details
+
+ **custom annotation:** custom annotation value
+
+ ---
+
+ **`markdown example`**
+ MARKDOWN
+ )
+ end
+ end
+
+ describe '#metrics_dashboard_url' do
+ subject { presenter.metrics_dashboard_url }
+
+ context 'for a non-prometheus alert' do
+ it { is_expected.to be_nil }
+ end
+
+ context 'for a self-managed prometheus alert' do
+ include_context 'self-managed prometheus alert attributes'
+
+ it { is_expected.to eq(dashboard_url_for_alert) }
+ end
+
+ context 'for a gitlab-managed prometheus alert' do
+ include_context 'gitlab-managed prometheus alert attributes'
+
+ it { is_expected.to eq(dashboard_url_for_alert) }
+ end
+ end
+end
diff --git a/spec/presenters/award_emoji_presenter_spec.rb b/spec/presenters/award_emoji_presenter_spec.rb
index e2ada2a3c93..58ee985f165 100644
--- a/spec/presenters/award_emoji_presenter_spec.rb
+++ b/spec/presenters/award_emoji_presenter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe AwardEmojiPresenter do
+RSpec.describe AwardEmojiPresenter do
let(:emoji_name) { 'thumbsup' }
let(:award_emoji) { build(:award_emoji, name: emoji_name) }
let(:presenter) { described_class.new(award_emoji) }
diff --git a/spec/presenters/blob_presenter_spec.rb b/spec/presenters/blob_presenter_spec.rb
index ee7bfd1256d..bf926ce62b3 100644
--- a/spec/presenters/blob_presenter_spec.rb
+++ b/spec/presenters/blob_presenter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe BlobPresenter, :seed_helper do
+RSpec.describe BlobPresenter, :seed_helper do
let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH, '', 'group/project') }
let(:git_blob) do
diff --git a/spec/presenters/blobs/unfold_presenter_spec.rb b/spec/presenters/blobs/unfold_presenter_spec.rb
index 83004809536..4e9f83e8001 100644
--- a/spec/presenters/blobs/unfold_presenter_spec.rb
+++ b/spec/presenters/blobs/unfold_presenter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Blobs::UnfoldPresenter do
+RSpec.describe Blobs::UnfoldPresenter do
include FakeBlobHelpers
let(:project) { create(:project, :repository) }
diff --git a/spec/presenters/ci/bridge_presenter_spec.rb b/spec/presenters/ci/bridge_presenter_spec.rb
index 2a4c40a7eaa..6291c3426e2 100644
--- a/spec/presenters/ci/bridge_presenter_spec.rb
+++ b/spec/presenters/ci/bridge_presenter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::BridgePresenter do
+RSpec.describe Ci::BridgePresenter do
let_it_be(:project) { create(:project) }
let_it_be(:pipeline) { create(:ci_pipeline, project: project) }
let_it_be(:bridge) { create(:ci_bridge, pipeline: pipeline, status: :failed) }
diff --git a/spec/presenters/ci/build_presenter_spec.rb b/spec/presenters/ci/build_presenter_spec.rb
index 9cf6eb45c63..8d302b242b3 100644
--- a/spec/presenters/ci/build_presenter_spec.rb
+++ b/spec/presenters/ci/build_presenter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::BuildPresenter do
+RSpec.describe Ci::BuildPresenter do
let(:project) { create(:project) }
let(:pipeline) { create(:ci_pipeline, project: project) }
let(:build) { create(:ci_build, pipeline: pipeline) }
diff --git a/spec/presenters/ci/build_runner_presenter_spec.rb b/spec/presenters/ci/build_runner_presenter_spec.rb
index de199d2bff9..ce4c5a2db7d 100644
--- a/spec/presenters/ci/build_runner_presenter_spec.rb
+++ b/spec/presenters/ci/build_runner_presenter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::BuildRunnerPresenter do
+RSpec.describe Ci::BuildRunnerPresenter do
let(:presenter) { described_class.new(build) }
let(:archive) { { paths: ['sample.txt'] } }
diff --git a/spec/presenters/ci/group_variable_presenter_spec.rb b/spec/presenters/ci/group_variable_presenter_spec.rb
index 3b81a425f5b..aaa6410266e 100644
--- a/spec/presenters/ci/group_variable_presenter_spec.rb
+++ b/spec/presenters/ci/group_variable_presenter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::GroupVariablePresenter do
+RSpec.describe Ci::GroupVariablePresenter do
include Gitlab::Routing.url_helpers
let(:group) { create(:group) }
diff --git a/spec/presenters/ci/legacy_stage_presenter_spec.rb b/spec/presenters/ci/legacy_stage_presenter_spec.rb
index ccf4e48de6e..5268ef0f246 100644
--- a/spec/presenters/ci/legacy_stage_presenter_spec.rb
+++ b/spec/presenters/ci/legacy_stage_presenter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::LegacyStagePresenter do
+RSpec.describe Ci::LegacyStagePresenter do
let(:legacy_stage) { create(:ci_stage) }
let(:presenter) { described_class.new(legacy_stage) }
diff --git a/spec/presenters/ci/pipeline_presenter_spec.rb b/spec/presenters/ci/pipeline_presenter_spec.rb
index e8b66682b97..158daad97f5 100644
--- a/spec/presenters/ci/pipeline_presenter_spec.rb
+++ b/spec/presenters/ci/pipeline_presenter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::PipelinePresenter do
+RSpec.describe Ci::PipelinePresenter do
include Gitlab::Routing
let(:user) { create(:user) }
diff --git a/spec/presenters/ci/trigger_presenter_spec.rb b/spec/presenters/ci/trigger_presenter_spec.rb
index 41cb436f928..bac1c94e0b7 100644
--- a/spec/presenters/ci/trigger_presenter_spec.rb
+++ b/spec/presenters/ci/trigger_presenter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::TriggerPresenter do
+RSpec.describe Ci::TriggerPresenter do
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project) }
diff --git a/spec/presenters/ci/variable_presenter_spec.rb b/spec/presenters/ci/variable_presenter_spec.rb
index 70cf2f539b6..30fedf78035 100644
--- a/spec/presenters/ci/variable_presenter_spec.rb
+++ b/spec/presenters/ci/variable_presenter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::VariablePresenter do
+RSpec.describe Ci::VariablePresenter do
include Gitlab::Routing.url_helpers
let(:project) { create(:project) }
diff --git a/spec/presenters/clusterable_presenter_spec.rb b/spec/presenters/clusterable_presenter_spec.rb
index 2c0a7f3e9b2..d19abd4e4d8 100644
--- a/spec/presenters/clusterable_presenter_spec.rb
+++ b/spec/presenters/clusterable_presenter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ClusterablePresenter do
+RSpec.describe ClusterablePresenter do
include Gitlab::Routing.url_helpers
describe '.fabricate' do
diff --git a/spec/presenters/clusters/cluster_presenter_spec.rb b/spec/presenters/clusters/cluster_presenter_spec.rb
index 6a1360807b7..5b75b281297 100644
--- a/spec/presenters/clusters/cluster_presenter_spec.rb
+++ b/spec/presenters/clusters/cluster_presenter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Clusters::ClusterPresenter do
+RSpec.describe Clusters::ClusterPresenter do
include Gitlab::Routing.url_helpers
let(:cluster) { create(:cluster, :provided_by_gcp, :project) }
@@ -249,4 +249,126 @@ describe Clusters::ClusterPresenter do
it { is_expected.to be_truthy }
end
end
+
+ describe '#health_data' do
+ shared_examples 'cluster health data' do
+ let(:user) { create(:user) }
+ let(:cluster_presenter) { cluster.present(current_user: user) }
+
+ let(:clusterable_presenter) do
+ ClusterablePresenter.fabricate(clusterable, current_user: user)
+ end
+
+ subject { cluster_presenter.health_data(clusterable_presenter) }
+
+ it do
+ is_expected.to include('clusters-path': clusterable_presenter.index_path,
+ 'dashboard-endpoint': clusterable_presenter.metrics_dashboard_path(cluster),
+ 'documentation-path': help_page_path('user/project/clusters/index', anchor: 'monitoring-your-kubernetes-cluster-ultimate'),
+ 'add-dashboard-documentation-path': help_page_path('user/project/integrations/prometheus.md', anchor: 'adding-a-new-dashboard-to-your-project'),
+ 'empty-getting-started-svg-path': match_asset_path('/assets/illustrations/monitoring/getting_started.svg'),
+ 'empty-loading-svg-path': match_asset_path('/assets/illustrations/monitoring/loading.svg'),
+ 'empty-no-data-svg-path': match_asset_path('/assets/illustrations/monitoring/no_data.svg'),
+ 'empty-no-data-small-svg-path': match_asset_path('illustrations/chart-empty-state-small.svg'),
+ 'empty-unable-to-connect-svg-path': match_asset_path('/assets/illustrations/monitoring/unable_to_connect.svg'),
+ 'settings-path': '',
+ 'project-path': '',
+ 'tags-path': '')
+ end
+ end
+
+ context 'with project cluster' do
+ let(:cluster) { create(:cluster, :project, :provided_by_gcp) }
+ let(:clusterable) { cluster.project }
+
+ it_behaves_like 'cluster health data'
+ end
+
+ context 'with group cluster' do
+ let(:cluster) { create(:cluster, :group, :provided_by_gcp) }
+ let(:clusterable) { cluster.group }
+
+ it_behaves_like 'cluster health data'
+ end
+ end
+
+ describe '#gitlab_managed_apps_logs_path' do
+ context 'user can read logs' do
+ let(:project) { cluster.project }
+
+ before do
+ project.add_maintainer(user)
+ end
+
+ it 'returns path to logs' do
+ expect(presenter.gitlab_managed_apps_logs_path).to eq k8s_project_logs_path(project, cluster_id: cluster.id, format: :json)
+ end
+
+ context 'cluster has elastic stack application installed' do
+ before do
+ create(:clusters_applications_elastic_stack, :installed, cluster: cluster)
+ end
+
+ it 'returns path to logs' do
+ expect(presenter.gitlab_managed_apps_logs_path).to eq elasticsearch_project_logs_path(project, cluster_id: cluster.id, format: :json)
+ end
+ end
+ end
+
+ context 'group cluster' do
+ let(:cluster) { create(:cluster, cluster_type: :group_type, groups: [group]) }
+ let(:group) { create(:group, name: 'Foo') }
+
+ context 'user can read logs' do
+ before do
+ group.add_maintainer(user)
+ end
+
+ context 'there are projects within group' do
+ let!(:project) { create(:project, namespace: group) }
+
+ it 'returns path to logs' do
+ expect(presenter.gitlab_managed_apps_logs_path).to eq k8s_project_logs_path(project, cluster_id: cluster.id, format: :json)
+ end
+ end
+
+ context 'there are no projects within group' do
+ it 'returns nil' do
+ expect(presenter.gitlab_managed_apps_logs_path).to be_nil
+ end
+ end
+ end
+ end
+
+ context 'instance cluster' do
+ let(:cluster) { create(:cluster, cluster_type: :instance_type) }
+ let!(:project) { create(:project) }
+ let(:user) { create(:admin) }
+
+ before do
+ project.add_maintainer(user)
+ stub_feature_flags(user_mode_in_session: false)
+ end
+
+ context 'user can read logs' do
+ it 'returns path to logs' do
+ expect(presenter.gitlab_managed_apps_logs_path).to eq k8s_project_logs_path(project, cluster_id: cluster.id, format: :json)
+ end
+ end
+ end
+
+ context 'user can NOT read logs' do
+ let(:cluster) { create(:cluster, cluster_type: :instance_type) }
+ let!(:project) { create(:project) }
+
+ before do
+ project.add_developer(user)
+ stub_feature_flags(user_mode_in_session: false)
+ end
+
+ it 'returns nil' do
+ expect(presenter.gitlab_managed_apps_logs_path).to be_nil
+ end
+ end
+ end
end
diff --git a/spec/presenters/commit_presenter_spec.rb b/spec/presenters/commit_presenter_spec.rb
index bc749acfa3a..bc6be07f415 100644
--- a/spec/presenters/commit_presenter_spec.rb
+++ b/spec/presenters/commit_presenter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe CommitPresenter do
+RSpec.describe CommitPresenter do
let(:project) { create(:project, :repository) }
let(:commit) { project.commit }
let(:user) { create(:user) }
diff --git a/spec/presenters/commit_status_presenter_spec.rb b/spec/presenters/commit_status_presenter_spec.rb
index b02497d4c11..4b2441d656e 100644
--- a/spec/presenters/commit_status_presenter_spec.rb
+++ b/spec/presenters/commit_status_presenter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe CommitStatusPresenter do
+RSpec.describe CommitStatusPresenter do
let(:project) { create(:project) }
let(:pipeline) { create(:ci_pipeline, project: project) }
let(:build) { create(:ci_build, pipeline: pipeline) }
diff --git a/spec/presenters/dev_ops_score/metric_presenter_spec.rb b/spec/presenters/dev_ops_score/metric_presenter_spec.rb
index b6eab3f2e74..8b7b2c88578 100644
--- a/spec/presenters/dev_ops_score/metric_presenter_spec.rb
+++ b/spec/presenters/dev_ops_score/metric_presenter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe DevOpsScore::MetricPresenter do
+RSpec.describe DevOpsScore::MetricPresenter do
subject { described_class.new(metric) }
let(:metric) { build(:dev_ops_score_metric) }
diff --git a/spec/presenters/event_presenter_spec.rb b/spec/presenters/event_presenter_spec.rb
index eb94d838370..2d4872ea29e 100644
--- a/spec/presenters/event_presenter_spec.rb
+++ b/spec/presenters/event_presenter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe EventPresenter do
+RSpec.describe EventPresenter do
include Gitlab::Routing.url_helpers
let_it_be(:group) { create(:group) }
diff --git a/spec/presenters/gitlab/blame_presenter_spec.rb b/spec/presenters/gitlab/blame_presenter_spec.rb
index d2a173b557c..b163926154b 100644
--- a/spec/presenters/gitlab/blame_presenter_spec.rb
+++ b/spec/presenters/gitlab/blame_presenter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::BlamePresenter do
+RSpec.describe Gitlab::BlamePresenter do
let(:project) { create(:project, :repository) }
let(:path) { 'files/ruby/popen.rb' }
let(:commit) { project.commit('master') }
diff --git a/spec/presenters/group_clusterable_presenter_spec.rb b/spec/presenters/group_clusterable_presenter_spec.rb
index d40ca856f7b..27360201e81 100644
--- a/spec/presenters/group_clusterable_presenter_spec.rb
+++ b/spec/presenters/group_clusterable_presenter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GroupClusterablePresenter do
+RSpec.describe GroupClusterablePresenter do
include Gitlab::Routing.url_helpers
let(:presenter) { described_class.new(group) }
@@ -94,4 +94,10 @@ describe GroupClusterablePresenter do
it { is_expected.to eq(group_cluster_path(group, cluster)) }
end
+
+ describe '#metrics_dashboard_path' do
+ subject { presenter.metrics_dashboard_path(cluster) }
+
+ it { is_expected.to eq(metrics_dashboard_group_cluster_path(group, cluster)) }
+ end
end
diff --git a/spec/presenters/group_member_presenter_spec.rb b/spec/presenters/group_member_presenter_spec.rb
index 382b1881ab7..6bd3005fbb6 100644
--- a/spec/presenters/group_member_presenter_spec.rb
+++ b/spec/presenters/group_member_presenter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GroupMemberPresenter do
+RSpec.describe GroupMemberPresenter do
let(:user) { double(:user) }
let(:group) { double(:group) }
let(:group_member) { double(:group_member, source: group) }
diff --git a/spec/presenters/instance_clusterable_presenter_spec.rb b/spec/presenters/instance_clusterable_presenter_spec.rb
index 4265e2fcb69..6968e3a4da3 100644
--- a/spec/presenters/instance_clusterable_presenter_spec.rb
+++ b/spec/presenters/instance_clusterable_presenter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe InstanceClusterablePresenter do
+RSpec.describe InstanceClusterablePresenter do
include Gitlab::Routing.url_helpers
let(:presenter) { described_class.new(instance) }
@@ -26,4 +26,10 @@ describe InstanceClusterablePresenter do
it { is_expected.to eq(clear_cache_admin_cluster_path(cluster)) }
end
+
+ describe '#metrics_dashboard_path' do
+ subject { presenter.metrics_dashboard_path(cluster) }
+
+ it { is_expected.to eq(metrics_dashboard_admin_cluster_path(cluster)) }
+ end
end
diff --git a/spec/presenters/issue_presenter_spec.rb b/spec/presenters/issue_presenter_spec.rb
index 4a4caef9d28..f08cd0f2026 100644
--- a/spec/presenters/issue_presenter_spec.rb
+++ b/spec/presenters/issue_presenter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe IssuePresenter do
+RSpec.describe IssuePresenter do
include Gitlab::Routing.url_helpers
let(:user) { create(:user) }
diff --git a/spec/presenters/label_presenter_spec.rb b/spec/presenters/label_presenter_spec.rb
index 9578d017af5..cb6e991bd8e 100644
--- a/spec/presenters/label_presenter_spec.rb
+++ b/spec/presenters/label_presenter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe LabelPresenter do
+RSpec.describe LabelPresenter do
include Gitlab::Routing.url_helpers
let_it_be(:group) { create(:group) }
diff --git a/spec/presenters/merge_request_presenter_spec.rb b/spec/presenters/merge_request_presenter_spec.rb
index f184e767f8c..f1e581efd44 100644
--- a/spec/presenters/merge_request_presenter_spec.rb
+++ b/spec/presenters/merge_request_presenter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe MergeRequestPresenter do
+RSpec.describe MergeRequestPresenter do
let(:resource) { create(:merge_request, source_project: project) }
let(:project) { create(:project) }
let(:user) { create(:user) }
@@ -613,4 +613,22 @@ describe MergeRequestPresenter do
end
end
end
+
+ describe '#api_approvals_path' do
+ subject { described_class.new(resource, current_user: user).api_approvals_path }
+
+ it { is_expected.to eq(expose_path("/api/v4/projects/#{project.id}/merge_requests/#{resource.iid}/approvals")) }
+ end
+
+ describe '#api_approve_path' do
+ subject { described_class.new(resource, current_user: user).api_approve_path }
+
+ it { is_expected.to eq(expose_path("/api/v4/projects/#{project.id}/merge_requests/#{resource.iid}/approve")) }
+ end
+
+ describe '#api_unapprove_path' do
+ subject { described_class.new(resource, current_user: user).api_unapprove_path }
+
+ it { is_expected.to eq(expose_path("/api/v4/projects/#{project.id}/merge_requests/#{resource.iid}/unapprove")) }
+ end
end
diff --git a/spec/presenters/milestone_presenter_spec.rb b/spec/presenters/milestone_presenter_spec.rb
index 3d7b3ad6d78..1f23bb31fda 100644
--- a/spec/presenters/milestone_presenter_spec.rb
+++ b/spec/presenters/milestone_presenter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe MilestonePresenter do
+RSpec.describe MilestonePresenter do
let_it_be(:user) { create(:user) }
let_it_be(:group) { create(:group) }
let_it_be(:milestone) { create(:milestone, group: group) }
diff --git a/spec/presenters/packages/composer/packages_presenter_spec.rb b/spec/presenters/packages/composer/packages_presenter_spec.rb
new file mode 100644
index 00000000000..0445a346180
--- /dev/null
+++ b/spec/presenters/packages/composer/packages_presenter_spec.rb
@@ -0,0 +1,78 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe ::Packages::Composer::PackagesPresenter do
+ using RSpec::Parameterized::TableSyntax
+
+ let_it_be(:package_name) { 'sample-project' }
+ let_it_be(:json) { { 'name' => package_name } }
+ let_it_be(:group) { create(:group) }
+ let_it_be(:project) { create(:project, :custom_repo, files: { 'composer.json' => json.to_json }, group: group) }
+ let_it_be(:package1) { create(:composer_package, :with_metadatum, project: project, name: package_name, version: '1.0.0', json: json) }
+ let_it_be(:package2) { create(:composer_package, :with_metadatum, project: project, name: package_name, version: '2.0.0', json: json) }
+
+ let(:branch) { project.repository.find_branch('master') }
+
+ let(:packages) { [package1, package2] }
+ let(:presenter) { described_class.new(group, packages) }
+
+ describe '#package_versions' do
+ subject { presenter.package_versions }
+
+ def expected_json(package)
+ {
+ 'dist' => {
+ 'reference' => branch.target,
+ 'shasum' => '',
+ 'type' => 'zip',
+ 'url' => "http://localhost/api/v4/projects/#{project.id}/packages/composer/archives/#{package.name}.zip?sha=#{branch.target}"
+ },
+ 'name' => package.name,
+ 'uid' => package.id,
+ 'version' => package.version
+ }
+ end
+
+ it 'returns the packages json' do
+ packages = subject['packages'][package_name]
+
+ expect(packages['1.0.0']).to eq(expected_json(package1))
+ expect(packages['2.0.0']).to eq(expected_json(package2))
+ end
+ end
+
+ describe '#provider' do
+ subject { presenter.provider}
+
+ let(:expected_json) do
+ {
+ 'providers' => {
+ package_name => {
+ 'sha256' => /^\h+$/
+ }
+ }
+ }
+ end
+
+ it 'returns the provider json' do
+ expect(subject).to match(expected_json)
+ end
+ end
+
+ describe '#root' do
+ subject { presenter.root }
+
+ let(:expected_json) do
+ {
+ 'packages' => [],
+ 'provider-includes' => { 'p/%hash%.json' => { 'sha256' => /^\h+$/ } },
+ 'providers-url' => "/api/v4/group/#{group.id}/-/packages/composer/%package%.json"
+ }
+ end
+
+ it 'returns the provider json' do
+ expect(subject).to match(expected_json)
+ end
+ end
+end
diff --git a/spec/presenters/packages/conan/package_presenter_spec.rb b/spec/presenters/packages/conan/package_presenter_spec.rb
new file mode 100644
index 00000000000..3bc649c5da4
--- /dev/null
+++ b/spec/presenters/packages/conan/package_presenter_spec.rb
@@ -0,0 +1,181 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe ::Packages::Conan::PackagePresenter do
+ let_it_be(:user) { create(:user) }
+ let_it_be(:project) { create(:project) }
+ let_it_be(:conan_package_reference) { '123456789'}
+
+ RSpec.shared_examples 'not selecting a package with the wrong type' do
+ context 'with a nuget package with same name and version' do
+ let_it_be(:wrong_package) { create(:nuget_package, name: 'wrong', version: '1.0.0', project: project) }
+
+ let(:recipe) { "#{wrong_package.name}/#{wrong_package.version}" }
+
+ it { is_expected.to be_empty }
+ end
+ end
+
+ describe '#recipe_urls' do
+ subject { described_class.new(recipe, user, project).recipe_urls }
+
+ context 'no existing package' do
+ let(:recipe) { "my-pkg/v1.0.0/#{project.full_path}/stable" }
+
+ it { is_expected.to be_empty }
+ end
+
+ it_behaves_like 'not selecting a package with the wrong type'
+
+ context 'existing package' do
+ let(:package) { create(:conan_package, project: project) }
+ let(:recipe) { package.conan_recipe }
+
+ let(:expected_result) do
+ {
+ "conanfile.py" => "#{Settings.build_base_gitlab_url}/api/v4/packages/conan/v1/files/#{package.conan_recipe_path}/0/export/conanfile.py",
+ "conanmanifest.txt" => "#{Settings.build_base_gitlab_url}/api/v4/packages/conan/v1/files/#{package.conan_recipe_path}/0/export/conanmanifest.txt"
+ }
+ end
+
+ it { is_expected.to eq(expected_result) }
+ end
+ end
+
+ describe '#recipe_snapshot' do
+ subject { described_class.new(recipe, user, project).recipe_snapshot }
+
+ context 'no existing package' do
+ let(:recipe) { "my-pkg/v1.0.0/#{project.full_path}/stable" }
+
+ it { is_expected.to be_empty }
+ end
+
+ it_behaves_like 'not selecting a package with the wrong type'
+
+ context 'existing package' do
+ let(:package) { create(:conan_package, project: project) }
+ let(:recipe) { package.conan_recipe }
+
+ let(:expected_result) do
+ {
+ "conanfile.py" => '12345abcde',
+ "conanmanifest.txt" => '12345abcde'
+ }
+ end
+
+ it { is_expected.to eq(expected_result) }
+ end
+ end
+
+ describe '#package_urls' do
+ let(:reference) { conan_package_reference }
+
+ subject do
+ described_class.new(
+ recipe, user, project, conan_package_reference: reference
+ ).package_urls
+ end
+
+ context 'no existing package' do
+ let(:recipe) { "my-pkg/v1.0.0/#{project.full_path}/stable" }
+
+ it { is_expected.to be_empty }
+ end
+
+ it_behaves_like 'not selecting a package with the wrong type'
+
+ context 'existing package' do
+ let(:package) { create(:conan_package, project: project) }
+ let(:recipe) { package.conan_recipe }
+
+ let(:expected_result) do
+ {
+ "conaninfo.txt" => "#{Settings.build_base_gitlab_url}/api/v4/packages/conan/v1/files/#{package.conan_recipe_path}/0/package/#{conan_package_reference}/0/conaninfo.txt",
+ "conanmanifest.txt" => "#{Settings.build_base_gitlab_url}/api/v4/packages/conan/v1/files/#{package.conan_recipe_path}/0/package/#{conan_package_reference}/0/conanmanifest.txt",
+ "conan_package.tgz" => "#{Settings.build_base_gitlab_url}/api/v4/packages/conan/v1/files/#{package.conan_recipe_path}/0/package/#{conan_package_reference}/0/conan_package.tgz"
+ }
+ end
+
+ it { is_expected.to eq(expected_result) }
+
+ context 'multiple packages with different references' do
+ let(:info_file) { create(:conan_package_file, :conan_package_info, package: package) }
+ let(:manifest_file) { create(:conan_package_file, :conan_package_manifest, package: package) }
+ let(:package_file) { create(:conan_package_file, :conan_package, package: package) }
+ let(:alternative_reference) { 'abcdefghi' }
+
+ before do
+ [info_file, manifest_file, package_file].each do |file|
+ file.conan_file_metadatum.conan_package_reference = alternative_reference
+ file.save
+ end
+ end
+
+ it { is_expected.to eq(expected_result) }
+
+ context 'requesting the alternative reference' do
+ let(:reference) { alternative_reference }
+
+ let(:expected_result) do
+ {
+ "conaninfo.txt" => "#{Settings.build_base_gitlab_url}/api/v4/packages/conan/v1/files/#{package.conan_recipe_path}/0/package/#{alternative_reference}/0/conaninfo.txt",
+ "conanmanifest.txt" => "#{Settings.build_base_gitlab_url}/api/v4/packages/conan/v1/files/#{package.conan_recipe_path}/0/package/#{alternative_reference}/0/conanmanifest.txt",
+ "conan_package.tgz" => "#{Settings.build_base_gitlab_url}/api/v4/packages/conan/v1/files/#{package.conan_recipe_path}/0/package/#{alternative_reference}/0/conan_package.tgz"
+ }
+ end
+
+ it { is_expected.to eq(expected_result) }
+ end
+
+ it 'returns empty if the reference does not exist' do
+ result = described_class.new(
+ recipe, user, project, conan_package_reference: 'doesnotexist'
+ ).package_urls
+
+ expect(result).to eq({})
+ end
+ end
+ end
+ end
+
+ describe '#package_snapshot' do
+ let(:reference) { conan_package_reference }
+
+ subject do
+ described_class.new(
+ recipe, user, project, conan_package_reference: reference
+ ).package_snapshot
+ end
+
+ context 'no existing package' do
+ let(:recipe) { "my-pkg/v1.0.0/#{project.full_path}/stable" }
+
+ it { is_expected.to be_empty }
+ end
+
+ it_behaves_like 'not selecting a package with the wrong type'
+
+ context 'existing package' do
+ let(:package) { create(:conan_package, project: project) }
+ let(:recipe) { package.conan_recipe }
+
+ let(:expected_result) do
+ {
+ "conaninfo.txt" => '12345abcde',
+ "conanmanifest.txt" => '12345abcde',
+ "conan_package.tgz" => '12345abcde'
+ }
+ end
+
+ it { is_expected.to eq(expected_result) }
+
+ context 'when requested with invalid reference' do
+ let(:reference) { 'invalid' }
+
+ it { is_expected.to eq({}) }
+ end
+ end
+ end
+end
diff --git a/spec/presenters/packages/detail/package_presenter_spec.rb b/spec/presenters/packages/detail/package_presenter_spec.rb
new file mode 100644
index 00000000000..34582957364
--- /dev/null
+++ b/spec/presenters/packages/detail/package_presenter_spec.rb
@@ -0,0 +1,98 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe ::Packages::Detail::PackagePresenter do
+ let_it_be(:user) { create(:user) }
+ let_it_be(:project) { create(:project, creator: user) }
+ let_it_be(:package) { create(:npm_package, :with_build, project: project) }
+ let(:presenter) { described_class.new(package) }
+
+ let_it_be(:user_info) { { name: user.name, avatar_url: user.avatar_url } }
+ let!(:expected_package_files) do
+ npm_file = package.package_files.first
+ [{
+ created_at: npm_file.created_at,
+ download_path: npm_file.download_path,
+ file_name: npm_file.file_name,
+ size: npm_file.size
+ }]
+ end
+ let(:pipeline_info) do
+ pipeline = package.build_info.pipeline
+ {
+ created_at: pipeline.created_at,
+ id: pipeline.id,
+ sha: pipeline.sha,
+ ref: pipeline.ref,
+ git_commit_message: pipeline.git_commit_message,
+ user: user_info,
+ project: {
+ name: pipeline.project.name,
+ web_url: pipeline.project.web_url
+ }
+ }
+ end
+ let!(:dependency_links) { [] }
+ let!(:expected_package_details) do
+ {
+ id: package.id,
+ created_at: package.created_at,
+ name: package.name,
+ package_files: expected_package_files,
+ package_type: package.package_type,
+ project_id: package.project_id,
+ tags: package.tags.as_json,
+ updated_at: package.updated_at,
+ version: package.version,
+ dependency_links: dependency_links
+ }
+ end
+
+ context 'detail_view' do
+ context 'with build_info' do
+ let_it_be(:package) { create(:npm_package, :with_build, project: project) }
+ let(:expected_package_details) { super().merge(pipeline: pipeline_info) }
+
+ it 'returns details with pipeline' do
+ expect(presenter.detail_view).to eq expected_package_details
+ end
+ end
+
+ context 'without build info' do
+ let_it_be(:package) { create(:npm_package, project: project) }
+
+ it 'returns details without pipeline' do
+ expect(presenter.detail_view).to eq expected_package_details
+ end
+ end
+
+ context 'with nuget_metadatum' do
+ let_it_be(:package) { create(:nuget_package, project: project) }
+ let_it_be(:nuget_metadatum) { create(:nuget_metadatum, package: package) }
+ let(:expected_package_details) { super().merge(nuget_metadatum: nuget_metadatum) }
+
+ it 'returns nuget_metadatum' do
+ expect(presenter.detail_view).to eq expected_package_details
+ end
+ end
+
+ context 'with dependency_links' do
+ let_it_be(:package) { create(:nuget_package, project: project) }
+ let_it_be(:dependency_link) { create(:packages_dependency_link, package: package) }
+ let_it_be(:nuget_dependency) { create(:nuget_dependency_link_metadatum, dependency_link: dependency_link) }
+ let_it_be(:expected_link) do
+ {
+ name: dependency_link.dependency.name,
+ version_pattern: dependency_link.dependency.version_pattern,
+ target_framework: nuget_dependency.target_framework
+ }
+ end
+ let_it_be(:dependency_links) { [expected_link] }
+
+ it 'returns the correct dependency link' do
+ expect(presenter.detail_view).to eq expected_package_details
+ end
+ end
+ end
+end
diff --git a/spec/presenters/packages/npm/package_presenter_spec.rb b/spec/presenters/packages/npm/package_presenter_spec.rb
new file mode 100644
index 00000000000..0e8cda5bafd
--- /dev/null
+++ b/spec/presenters/packages/npm/package_presenter_spec.rb
@@ -0,0 +1,65 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe ::Packages::Npm::PackagePresenter do
+ let_it_be(:project) { create(:project) }
+ let_it_be(:package_name) { "@#{project.root_namespace.path}/test" }
+ let!(:package1) { create(:npm_package, version: '1.0.4', project: project, name: package_name) }
+ let!(:package2) { create(:npm_package, version: '1.0.6', project: project, name: package_name) }
+ let!(:latest_package) { create(:npm_package, version: '1.0.11', project: project, name: package_name) }
+ let(:packages) { project.packages.npm.with_name(package_name).last_of_each_version }
+ let(:presenter) { described_class.new(package_name, packages) }
+
+ describe '#versions' do
+ subject { presenter.versions }
+
+ context 'for packages without dependencies' do
+ it { is_expected.to be_a(Hash) }
+ it { expect(subject[package1.version]).to match_schema('public_api/v4/packages/npm_package_version') }
+ it { expect(subject[package2.version]).to match_schema('public_api/v4/packages/npm_package_version') }
+
+ described_class::NPM_VALID_DEPENDENCY_TYPES.each do |dependency_type|
+ it { expect(subject.dig(package1.version, dependency_type)).to be nil }
+ it { expect(subject.dig(package2.version, dependency_type)).to be nil }
+ end
+ end
+
+ context 'for packages with dependencies' do
+ described_class::NPM_VALID_DEPENDENCY_TYPES.each do |dependency_type|
+ let!("package_dependency_link_for_#{dependency_type}") { create(:packages_dependency_link, package: package1, dependency_type: dependency_type) }
+ end
+
+ it { is_expected.to be_a(Hash) }
+ it { expect(subject[package1.version]).to match_schema('public_api/v4/packages/npm_package_version') }
+ it { expect(subject[package2.version]).to match_schema('public_api/v4/packages/npm_package_version') }
+ described_class::NPM_VALID_DEPENDENCY_TYPES.each do |dependency_type|
+ it { expect(subject.dig(package1.version, dependency_type.to_s)).to be_any }
+ end
+ end
+ end
+
+ describe '#dist_tags' do
+ subject { presenter.dist_tags }
+
+ context 'for packages without tags' do
+ it { is_expected.to be_a(Hash) }
+ it { expect(subject["latest"]).to eq(latest_package.version) }
+ end
+
+ context 'for packages with tags' do
+ let!(:package_tag1) { create(:packages_tag, package: package1, name: 'release_a') }
+ let!(:package_tag2) { create(:packages_tag, package: package1, name: 'test_release') }
+ let!(:package_tag3) { create(:packages_tag, package: package2, name: 'release_b') }
+ let!(:package_tag4) { create(:packages_tag, package: latest_package, name: 'release_c') }
+ let!(:package_tag5) { create(:packages_tag, package: latest_package, name: 'latest') }
+
+ it { is_expected.to be_a(Hash) }
+ it { expect(subject[package_tag1.name]).to eq(package1.version) }
+ it { expect(subject[package_tag2.name]).to eq(package1.version) }
+ it { expect(subject[package_tag3.name]).to eq(package2.version) }
+ it { expect(subject[package_tag4.name]).to eq(latest_package.version) }
+ it { expect(subject[package_tag5.name]).to eq(latest_package.version) }
+ end
+ end
+end
diff --git a/spec/presenters/packages/nuget/package_metadata_presenter_spec.rb b/spec/presenters/packages/nuget/package_metadata_presenter_spec.rb
new file mode 100644
index 00000000000..d5e7b23d785
--- /dev/null
+++ b/spec/presenters/packages/nuget/package_metadata_presenter_spec.rb
@@ -0,0 +1,52 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Packages::Nuget::PackageMetadataPresenter do
+ include_context 'with expected presenters dependency groups'
+
+ let_it_be(:package) { create(:nuget_package, :with_metadatum) }
+ let_it_be(:tag1) { create(:packages_tag, name: 'tag1', package: package) }
+ let_it_be(:tag2) { create(:packages_tag, name: 'tag2', package: package) }
+ let_it_be(:presenter) { described_class.new(package) }
+
+ describe '#json_url' do
+ let_it_be(:expected_suffix) { "/api/v4/projects/#{package.project_id}/packages/nuget/metadata/#{package.name}/#{package.version}.json" }
+
+ subject { presenter.json_url }
+
+ it { is_expected.to end_with(expected_suffix) }
+ end
+
+ describe '#archive_url' do
+ let_it_be(:expected_suffix) { "/api/v4/projects/#{package.project_id}/packages/nuget/download/#{package.name}/#{package.version}/#{package.package_files.last.file_name}" }
+
+ subject { presenter.archive_url }
+
+ it { is_expected.to end_with(expected_suffix) }
+ end
+
+ describe '#catalog_entry' do
+ subject { presenter.catalog_entry }
+
+ before do
+ create_dependencies_for(package)
+ end
+
+ it 'returns an entry structure' do
+ entry = subject
+
+ expect(entry).to be_a Hash
+ %i[json_url archive_url].each { |field| expect(entry[field]).not_to be_blank }
+ %i[authors summary].each { |field| expect(entry[field]).to be_blank }
+ expect(entry[:dependency_groups]).to eq expected_dependency_groups(package.project_id, package.name, package.version)
+ expect(entry[:package_name]).to eq package.name
+ expect(entry[:package_version]).to eq package.version
+ expect(entry[:tags].split(::Packages::Tag::NUGET_TAGS_SEPARATOR)).to contain_exactly('tag1', 'tag2')
+
+ %i[project_url license_url icon_url].each do |field|
+ expect(entry.dig(:metadatum, field)).to eq(package.nuget_metadatum.send(field))
+ end
+ end
+ end
+end
diff --git a/spec/presenters/packages/nuget/packages_metadata_presenter_spec.rb b/spec/presenters/packages/nuget/packages_metadata_presenter_spec.rb
new file mode 100644
index 00000000000..b2bcdf8f03d
--- /dev/null
+++ b/spec/presenters/packages/nuget/packages_metadata_presenter_spec.rb
@@ -0,0 +1,66 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Packages::Nuget::PackagesMetadataPresenter do
+ include_context 'with expected presenters dependency groups'
+
+ let_it_be(:project) { create(:project) }
+ let_it_be(:packages) { create_list(:nuget_package, 5, :with_metadatum, name: 'Dummy.Package', project: project) }
+ let_it_be(:presenter) { described_class.new(packages) }
+
+ describe '#count' do
+ subject { presenter.count }
+
+ it {is_expected.to eq 1}
+ end
+
+ describe '#items' do
+ let(:tag_names) { %w(tag1 tag2) }
+
+ subject { presenter.items }
+
+ before do
+ packages.each do |pkg|
+ tag_names.each { |tag| create(:packages_tag, package: pkg, name: tag) }
+
+ create_dependencies_for(pkg)
+ end
+ end
+
+ it 'returns an array' do
+ items = subject
+
+ expect(items).to be_a Array
+ expect(items.size).to eq 1
+ end
+
+ it 'returns a summary structure' do
+ item = subject.first
+
+ expect(item).to be_a Hash
+ %i[json_url lower_version upper_version].each { |field| expect(item[field]).not_to be_blank }
+ expect(item[:packages_count]).to eq packages.count
+ expect(item[:packages]).to be_a Array
+ expect(item[:packages].size).to eq packages.count
+ end
+
+ it 'returns the catalog entries' do
+ item = subject.first
+
+ item[:packages].each do |pkg|
+ expect(pkg).to be_a Hash
+ %i[json_url archive_url catalog_entry].each { |field| expect(pkg[field]).not_to be_blank }
+ catalog_entry = pkg[:catalog_entry]
+ %i[json_url archive_url package_name package_version].each { |field| expect(catalog_entry[field]).not_to be_blank }
+ %i[authors summary].each { |field| expect(catalog_entry[field]).to be_blank }
+ expect(catalog_entry[:dependency_groups]).to eq(expected_dependency_groups(project.id, catalog_entry[:package_name], catalog_entry[:package_version]))
+ expect(catalog_entry[:tags].split(::Packages::Tag::NUGET_TAGS_SEPARATOR)).to contain_exactly('tag1', 'tag2')
+
+ %i[project_url license_url icon_url].each do |field|
+ expect(catalog_entry.dig(:metadatum, field)).not_to be_blank
+ end
+ end
+ end
+ end
+end
diff --git a/spec/presenters/packages/nuget/packages_versions_presenter_spec.rb b/spec/presenters/packages/nuget/packages_versions_presenter_spec.rb
new file mode 100644
index 00000000000..36aa28243a4
--- /dev/null
+++ b/spec/presenters/packages/nuget/packages_versions_presenter_spec.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Packages::Nuget::PackagesVersionsPresenter do
+ let_it_be(:packages) { create_list(:nuget_package, 5) }
+ let_it_be(:presenter) { described_class.new(::Packages::Package.all) }
+
+ describe '#versions' do
+ subject { presenter.versions }
+
+ it { is_expected.to match_array(packages.map(&:version).sort) }
+ end
+end
diff --git a/spec/presenters/packages/nuget/search_results_presenter_spec.rb b/spec/presenters/packages/nuget/search_results_presenter_spec.rb
new file mode 100644
index 00000000000..29ec8579dc1
--- /dev/null
+++ b/spec/presenters/packages/nuget/search_results_presenter_spec.rb
@@ -0,0 +1,59 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Packages::Nuget::SearchResultsPresenter do
+ let_it_be(:project) { create(:project) }
+ let_it_be(:package_a) { create(:nuget_package, :with_metadatum, project: project, name: 'DummyPackageA') }
+ let_it_be(:tag1) { create(:packages_tag, package: package_a, name: 'tag1') }
+ let_it_be(:tag2) { create(:packages_tag, package: package_a, name: 'tag2') }
+ let_it_be(:packages_b) { create_list(:nuget_package, 5, project: project, name: 'DummyPackageB') }
+ let_it_be(:packages_c) { create_list(:nuget_package, 5, project: project, name: 'DummyPackageC') }
+ let_it_be(:search_results) { OpenStruct.new(total_count: 3, results: [package_a, packages_b, packages_c].flatten) }
+ let_it_be(:presenter) { described_class.new(search_results) }
+ let(:total_count) { presenter.total_count }
+ let(:data) { presenter.data }
+
+ describe '#total_count' do
+ it 'expects to have 3 total elements' do
+ expect(total_count).to eq(3)
+ end
+ end
+
+ describe '#data' do
+ it 'returns the proper data structure' do
+ expect(data.size).to eq 3
+ pkg_a, pkg_b, pkg_c = data
+ expect_package_result(pkg_a, package_a.name, [package_a.version], %w(tag1 tag2), with_metadatum: true)
+ expect_package_result(pkg_b, packages_b.first.name, packages_b.map(&:version))
+ expect_package_result(pkg_c, packages_c.first.name, packages_c.map(&:version))
+ end
+
+ # rubocop:disable Metrics/AbcSize
+ def expect_package_result(package_json, name, versions, tags = [], with_metadatum: false)
+ expect(package_json[:type]).to eq 'Package'
+ expect(package_json[:authors]).to be_blank
+ expect(package_json[:name]).to eq(name)
+ expect(package_json[:summary]).to be_blank
+ expect(package_json[:total_downloads]).to eq 0
+ expect(package_json[:verified]).to be
+ expect(package_json[:version]).to eq VersionSorter.sort(versions).last # rubocop: disable Style/UnneededSort
+ versions.zip(package_json[:versions]).each do |version, version_json|
+ expect(version_json[:json_url]).to end_with("#{version}.json")
+ expect(version_json[:downloads]).to eq 0
+ expect(version_json[:version]).to eq version
+ end
+
+ if tags.any?
+ expect(package_json[:tags].split(::Packages::Tag::NUGET_TAGS_SEPARATOR)).to contain_exactly(*tags)
+ else
+ expect(package_json[:tags]).to be_blank
+ end
+
+ %i[project_url license_url icon_url].each do |field|
+ expect(package_json.dig(:metadatum, field)).to with_metadatum ? be_present : be_blank
+ end
+ end
+ # rubocop:enable Metrics/AbcSize
+ end
+end
diff --git a/spec/presenters/packages/nuget/service_index_presenter_spec.rb b/spec/presenters/packages/nuget/service_index_presenter_spec.rb
new file mode 100644
index 00000000000..19ef890e19f
--- /dev/null
+++ b/spec/presenters/packages/nuget/service_index_presenter_spec.rb
@@ -0,0 +1,28 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe ::Packages::Nuget::ServiceIndexPresenter do
+ let_it_be(:project) { create(:project) }
+ let_it_be(:presenter) { described_class.new(project) }
+
+ describe '#version' do
+ subject { presenter.version }
+
+ it { is_expected.to eq '3.0.0' }
+ end
+
+ describe '#resources' do
+ subject { presenter.resources }
+
+ it 'has valid resources' do
+ expect(subject.size).to eq 8
+ subject.each do |resource|
+ %i[@id @type comment].each do |field|
+ expect(resource).to have_key(field)
+ expect(resource[field]).to be_a(String)
+ end
+ end
+ end
+ end
+end
diff --git a/spec/presenters/packages/pypi/package_presenter_spec.rb b/spec/presenters/packages/pypi/package_presenter_spec.rb
new file mode 100644
index 00000000000..e4d234a4688
--- /dev/null
+++ b/spec/presenters/packages/pypi/package_presenter_spec.rb
@@ -0,0 +1,49 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe ::Packages::Pypi::PackagePresenter do
+ using RSpec::Parameterized::TableSyntax
+
+ let_it_be(:project) { create(:project) }
+ let_it_be(:package_name) { 'sample-project' }
+ let_it_be(:package1) { create(:pypi_package, project: project, name: package_name, version: '1.0.0') }
+ let_it_be(:package2) { create(:pypi_package, project: project, name: package_name, version: '2.0.0') }
+
+ let(:packages) { [package1, package2] }
+ let(:presenter) { described_class.new(packages, project) }
+
+ describe '#body' do
+ subject { presenter.body}
+
+ shared_examples_for "pypi package presenter" do
+ let(:file) { package.package_files.first }
+ let(:filename) { file.file_name }
+ let(:expected_file) { "<a href=\"http://localhost/api/v4/projects/#{project.id}/packages/pypi/files/#{file.file_sha256}/#{filename}#sha256=#{file.file_sha256}\" data-requires-python=\"#{expected_python_version}\">#{filename}</a><br>" }
+
+ before do
+ package.pypi_metadatum.required_python = python_version
+ end
+
+ it { is_expected.to include expected_file }
+ end
+
+ it_behaves_like "pypi package presenter" do
+ let(:python_version) { '>=2.7' }
+ let(:expected_python_version) { '&gt;=2.7' }
+ let(:package) { package1 }
+ end
+
+ it_behaves_like "pypi package presenter" do
+ let(:python_version) { '"><script>alert(1)</script>' }
+ let(:expected_python_version) { '&quot;&gt;&lt;script&gt;alert(1)&lt;/script&gt;' }
+ let(:package) { package1 }
+ end
+
+ it_behaves_like "pypi package presenter" do
+ let(:python_version) { '>=2.7, !=3.0' }
+ let(:expected_python_version) { '&gt;=2.7, !=3.0' }
+ let(:package) { package2 }
+ end
+ end
+end
diff --git a/spec/presenters/pages_domain_presenter_spec.rb b/spec/presenters/pages_domain_presenter_spec.rb
index 30ce59b7bfb..731279ce5b9 100644
--- a/spec/presenters/pages_domain_presenter_spec.rb
+++ b/spec/presenters/pages_domain_presenter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe PagesDomainPresenter do
+RSpec.describe PagesDomainPresenter do
using RSpec::Parameterized::TableSyntax
include LetsEncryptHelpers
diff --git a/spec/presenters/project_clusterable_presenter_spec.rb b/spec/presenters/project_clusterable_presenter_spec.rb
index b3dad4abde5..b518c63f0ca 100644
--- a/spec/presenters/project_clusterable_presenter_spec.rb
+++ b/spec/presenters/project_clusterable_presenter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ProjectClusterablePresenter do
+RSpec.describe ProjectClusterablePresenter do
include Gitlab::Routing.url_helpers
let(:presenter) { described_class.new(project) }
@@ -94,4 +94,10 @@ describe ProjectClusterablePresenter do
it { is_expected.to eq(project_cluster_path(project, cluster)) }
end
+
+ describe '#metrics_dashboard_path' do
+ subject { presenter.metrics_dashboard_path(cluster) }
+
+ it { is_expected.to eq(metrics_dashboard_project_cluster_path(project, cluster)) }
+ end
end
diff --git a/spec/presenters/project_hook_presenter_spec.rb b/spec/presenters/project_hook_presenter_spec.rb
index 773e8ccf51e..061ec38ae34 100644
--- a/spec/presenters/project_hook_presenter_spec.rb
+++ b/spec/presenters/project_hook_presenter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ProjectHookPresenter do
+RSpec.describe ProjectHookPresenter do
let(:web_hook_log) { create(:web_hook_log) }
let(:project) { web_hook_log.web_hook.project }
let(:web_hook) { web_hook_log.web_hook }
diff --git a/spec/presenters/project_member_presenter_spec.rb b/spec/presenters/project_member_presenter_spec.rb
index 743c89fc7c2..ad45a23c183 100644
--- a/spec/presenters/project_member_presenter_spec.rb
+++ b/spec/presenters/project_member_presenter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ProjectMemberPresenter do
+RSpec.describe ProjectMemberPresenter do
let(:user) { double(:user) }
let(:project) { double(:project) }
let(:project_member) { double(:project_member, source: project) }
diff --git a/spec/presenters/project_presenter_spec.rb b/spec/presenters/project_presenter_spec.rb
index 65ae85ea78f..eb1ff628d14 100644
--- a/spec/presenters/project_presenter_spec.rb
+++ b/spec/presenters/project_presenter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ProjectPresenter do
+RSpec.describe ProjectPresenter do
let(:user) { create(:user) }
let(:project) { create(:project) }
let(:presenter) { described_class.new(project, current_user: user) }
diff --git a/spec/presenters/projects/import_export/project_export_presenter_spec.rb b/spec/presenters/projects/import_export/project_export_presenter_spec.rb
index 052ca36974a..8463d01d95b 100644
--- a/spec/presenters/projects/import_export/project_export_presenter_spec.rb
+++ b/spec/presenters/projects/import_export/project_export_presenter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Projects::ImportExport::ProjectExportPresenter do
+RSpec.describe Projects::ImportExport::ProjectExportPresenter do
let_it_be(:group) { create(:group) }
let_it_be(:project) { create(:project, group: group) }
let_it_be(:user) { create(:user) }
diff --git a/spec/presenters/projects/prometheus/alert_presenter_spec.rb b/spec/presenters/projects/prometheus/alert_presenter_spec.rb
index 8ee5a4d7b3f..89c5438b074 100644
--- a/spec/presenters/projects/prometheus/alert_presenter_spec.rb
+++ b/spec/presenters/projects/prometheus/alert_presenter_spec.rb
@@ -2,7 +2,9 @@
require 'spec_helper'
-describe Projects::Prometheus::AlertPresenter do
+RSpec.describe Projects::Prometheus::AlertPresenter do
+ include Gitlab::Routing.url_helpers
+
let_it_be(:project, reload: true) { create(:project) }
let(:presenter) { described_class.new(alert) }
@@ -14,7 +16,7 @@ describe Projects::Prometheus::AlertPresenter do
let(:metric_id) { gitlab_alert.prometheus_metric_id }
let(:alert) do
- create(:alerting_alert, project: project, metric_id: metric_id)
+ create(:alerting_alert, project: project, metric_id: metric_id, payload: payload)
end
end
@@ -171,7 +173,7 @@ describe Projects::Prometheus::AlertPresenter do
**Start time:** #{presenter.start_time}#{markdown_line_break}
**full_query:** `avg(metric) > 1.0`
- [](#{url})
+ [](#{presenter.metrics_dashboard_url})
MARKDOWN
end
@@ -180,68 +182,30 @@ describe Projects::Prometheus::AlertPresenter do
Timecop.freeze(starts_at) { example.run }
end
+ before do
+ payload.delete('startsAt')
+ end
+
it { is_expected.to eq(expected_markdown) }
end
context 'with a starting time available' do
- before do
- payload['startsAt'] = starts_at
- end
-
it { is_expected.to eq(expected_markdown) }
end
end
context 'for gitlab-managed prometheus alerts' do
- let(:gitlab_alert) { create(:prometheus_alert, project: project) }
- let(:metric_id) { gitlab_alert.prometheus_metric_id }
- let(:env_id) { gitlab_alert.environment_id }
+ include_context 'gitlab-managed prometheus alert attributes'
- before do
- payload['labels'] = { 'gitlab_alert_id' => metric_id }
+ let(:alert) do
+ create(:alerting_alert, project: project, metric_id: prometheus_metric_id, payload: payload)
end
- let(:url) { "http://localhost/#{project.full_path}/prometheus/alerts/#{metric_id}/metrics_dashboard?end=2018-03-12T09%3A36%3A00Z&environment_id=#{env_id}&start=2018-03-12T08%3A36%3A00Z" }
-
it_behaves_like 'markdown with metrics embed'
end
context 'for alerts from a self-managed prometheus' do
- let!(:environment) { create(:environment, project: project, name: 'production') }
- let(:url) { "http://localhost/#{project.full_path}/-/environments/#{environment.id}/metrics_dashboard?embed_json=#{CGI.escape(embed_content.to_json)}&end=2018-03-12T09%3A36%3A00Z&start=2018-03-12T08%3A36%3A00Z" }
-
- let(:title) { 'title' }
- let(:y_label) { 'y_label' }
- let(:query) { 'avg(metric) > 1.0' }
- let(:embed_content) do
- {
- panel_groups: [{
- panels: [{
- type: 'line-graph',
- title: title,
- y_label: y_label,
- metrics: [{ query_range: query }]
- }]
- }]
- }
- end
-
- before do
- # Setup embed time range
- payload['startsAt'] = starts_at
-
- # Setup query
- payload['generatorURL'] = "http://host?g0.expr=#{CGI.escape(query)}"
-
- # Setup environment
- payload['labels'] ||= {}
- payload['labels']['gitlab_environment_name'] = 'production'
-
- # Setup chart title & axis labels
- payload['annotations'] ||= {}
- payload['annotations']['title'] = 'title'
- payload['annotations']['gitlab_y_label'] = 'y_label'
- end
+ include_context 'self-managed prometheus alert attributes'
it_behaves_like 'markdown with metrics embed'
@@ -359,10 +323,7 @@ describe Projects::Prometheus::AlertPresenter do
end
describe '#performance_dashboard_link' do
- let(:expected_link) do
- Gitlab::Routing.url_helpers
- .metrics_project_environment_url(project, alert.environment)
- end
+ let(:expected_link) { metrics_project_environment_url(project, alert.environment) }
subject { presenter.performance_dashboard_link }
@@ -370,10 +331,7 @@ describe Projects::Prometheus::AlertPresenter do
end
describe '#incident_issues_link' do
- let(:expected_link) do
- Gitlab::Routing.url_helpers
- .project_issues_url(project, label_name: described_class::INCIDENT_LABEL_NAME)
- end
+ let(:expected_link) { project_issues_url(project, label_name: described_class::INCIDENT_LABEL_NAME) }
subject { presenter.incident_issues_link }
@@ -413,13 +371,35 @@ describe Projects::Prometheus::AlertPresenter do
end
describe '#performance_dashboard_link' do
- let(:expected_link) do
- Gitlab::Routing.url_helpers.metrics_project_environments_url(project)
- end
+ let(:expected_link) { metrics_project_environments_url(project) }
subject { presenter.performance_dashboard_link }
it { is_expected.to eq(expected_link) }
end
end
+
+ describe '#metrics_dashboard_url' do
+ subject { presenter.metrics_dashboard_url }
+
+ context 'for a non-prometheus alert' do
+ it { is_expected.to be_nil }
+ end
+
+ context 'for a self-managed prometheus alert' do
+ include_context 'self-managed prometheus alert attributes'
+
+ let(:prometheus_payload) { payload }
+
+ it { is_expected.to eq(dashboard_url_for_alert) }
+ end
+
+ context 'for a gitlab-managed prometheus alert' do
+ include_context 'gitlab-managed prometheus alert attributes'
+
+ let(:prometheus_payload) { payload }
+
+ it { is_expected.to eq(dashboard_url_for_alert) }
+ end
+ end
end
diff --git a/spec/presenters/projects/settings/deploy_keys_presenter_spec.rb b/spec/presenters/projects/settings/deploy_keys_presenter_spec.rb
index b9cb60e414f..7a679a03b53 100644
--- a/spec/presenters/projects/settings/deploy_keys_presenter_spec.rb
+++ b/spec/presenters/projects/settings/deploy_keys_presenter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Projects::Settings::DeployKeysPresenter do
+RSpec.describe Projects::Settings::DeployKeysPresenter do
let(:project) { create(:project) }
let(:user) { create(:user) }
diff --git a/spec/presenters/release_presenter_spec.rb b/spec/presenters/release_presenter_spec.rb
index d1f023b8760..5577b3ad2e8 100644
--- a/spec/presenters/release_presenter_spec.rb
+++ b/spec/presenters/release_presenter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ReleasePresenter do
+RSpec.describe ReleasePresenter do
include Gitlab::Routing.url_helpers
let_it_be(:project) { create(:project, :repository) }
@@ -112,4 +112,36 @@ describe ReleasePresenter do
it { is_expected.to be_nil }
end
end
+
+ describe '#assets_count' do
+ subject { presenter.assets_count }
+
+ it 'returns the number of assets associated to the release' do
+ is_expected.to be release.assets_count
+ end
+
+ context 'when a user is not allowed to download release sources' do
+ let(:presenter) { described_class.new(release, current_user: guest) }
+
+ it 'returns the number of all non-source assets associated to the release' do
+ is_expected.to be release.assets_count(except: [:sources])
+ end
+ end
+ end
+
+ describe '#name' do
+ subject { presenter.name }
+
+ it 'returns the release name' do
+ is_expected.to eq release.name
+ end
+
+ context "when a user is not allowed to access any repository information" do
+ let(:presenter) { described_class.new(release, current_user: guest) }
+
+ it 'returns a replacement name to avoid potentially leaking tag information' do
+ is_expected.to eq "Release-#{release.id}"
+ end
+ end
+ end
end
diff --git a/spec/presenters/sentry_error_presenter_spec.rb b/spec/presenters/sentry_error_presenter_spec.rb
index 5f3f1d33b86..af9e7c8a2b2 100644
--- a/spec/presenters/sentry_error_presenter_spec.rb
+++ b/spec/presenters/sentry_error_presenter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe SentryErrorPresenter do
+RSpec.describe SentryErrorPresenter do
let(:error) { build(:detailed_error_tracking_error) }
let(:presenter) { described_class.new(error) }
diff --git a/spec/presenters/service_hook_presenter_spec.rb b/spec/presenters/service_hook_presenter_spec.rb
index bea57768e3e..adef34a882b 100644
--- a/spec/presenters/service_hook_presenter_spec.rb
+++ b/spec/presenters/service_hook_presenter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ServiceHookPresenter do
+RSpec.describe ServiceHookPresenter do
let(:web_hook_log) { create(:web_hook_log, web_hook: service_hook) }
let(:service_hook) { create(:service_hook, service: service) }
let(:service) { create(:drone_ci_service, project: project) }
diff --git a/spec/presenters/snippet_blob_presenter_spec.rb b/spec/presenters/snippet_blob_presenter_spec.rb
index eb7621cc591..7464c0ac15b 100644
--- a/spec/presenters/snippet_blob_presenter_spec.rb
+++ b/spec/presenters/snippet_blob_presenter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe SnippetBlobPresenter do
+RSpec.describe SnippetBlobPresenter do
describe '#rich_data' do
before do
allow_next_instance_of(described_class) do |instance|
@@ -109,22 +109,38 @@ describe SnippetBlobPresenter do
end
describe '#raw_path' do
- subject { described_class.new(snippet.blob).raw_path }
+ let_it_be(:project) { create(:project) }
+ let_it_be(:user) { create(:user) }
+ let_it_be(:personal_snippet) { create(:personal_snippet, :repository, author: user) }
+ let_it_be(:project_snippet) { create(:project_snippet, :repository, project: project, author: user) }
- context 'with ProjectSnippet' do
- let!(:project) { create(:project) }
- let(:snippet) { create(:project_snippet, project: project) }
+ before do
+ project.add_developer(user)
+ end
+
+ subject { described_class.new(snippet.blobs.first, current_user: user).raw_path }
- it 'returns the raw path' do
- expect(subject).to eq "/#{snippet.project.full_path}/snippets/#{snippet.id}/raw"
+ it_behaves_like 'snippet blob raw path'
+
+ context 'with snippet_multiple_files feature disabled' do
+ before do
+ stub_feature_flags(snippet_multiple_files: false)
end
- end
- context 'with PersonalSnippet' do
- let(:snippet) { create(:personal_snippet) }
+ context 'with ProjectSnippet' do
+ let(:snippet) { project_snippet }
- it 'returns the raw path' do
- expect(subject).to eq "/snippets/#{snippet.id}/raw"
+ it 'returns the raw path' do
+ expect(subject).to eq "/#{snippet.project.full_path}/snippets/#{snippet.id}/raw"
+ end
+ end
+
+ context 'with PersonalSnippet' do
+ let(:snippet) { personal_snippet }
+
+ it 'returns the raw path' do
+ expect(subject).to eq "/snippets/#{snippet.id}/raw"
+ end
end
end
end
diff --git a/spec/presenters/snippet_presenter_spec.rb b/spec/presenters/snippet_presenter_spec.rb
index 423e9edc219..98c291bdd02 100644
--- a/spec/presenters/snippet_presenter_spec.rb
+++ b/spec/presenters/snippet_presenter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe SnippetPresenter do
+RSpec.describe SnippetPresenter do
include Gitlab::Routing.url_helpers
let_it_be(:user) { create(:user) }
diff --git a/spec/presenters/tree_entry_presenter_spec.rb b/spec/presenters/tree_entry_presenter_spec.rb
index 0c29fe3e5ff..d29a7a6ab04 100644
--- a/spec/presenters/tree_entry_presenter_spec.rb
+++ b/spec/presenters/tree_entry_presenter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe TreeEntryPresenter do
+RSpec.describe TreeEntryPresenter do
include Gitlab::Routing.url_helpers
let(:project) { create(:project, :repository) }
diff --git a/spec/presenters/web_hook_log_presenter_spec.rb b/spec/presenters/web_hook_log_presenter_spec.rb
index 8812a0ba594..68c8c6e2a1b 100644
--- a/spec/presenters/web_hook_log_presenter_spec.rb
+++ b/spec/presenters/web_hook_log_presenter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe WebHookLogPresenter do
+RSpec.describe WebHookLogPresenter do
include Gitlab::Routing.url_helpers
describe '#details_path' do
diff --git a/spec/rack_servers/puma_spec.rb b/spec/rack_servers/puma_spec.rb
index a4b37905af3..80595b267fa 100644
--- a/spec/rack_servers/puma_spec.rb
+++ b/spec/rack_servers/puma_spec.rb
@@ -5,7 +5,7 @@ require 'spec_helper'
require 'fileutils'
require 'excon'
-describe 'Puma' do
+RSpec.describe 'Puma' do
before(:all) do
project_root = Rails.root.to_s
config_lines = File.read(Rails.root.join('config/puma.example.development.rb'))
diff --git a/spec/rack_servers/unicorn_spec.rb b/spec/rack_servers/unicorn_spec.rb
index 6a02ebcd048..5887b49d269 100644
--- a/spec/rack_servers/unicorn_spec.rb
+++ b/spec/rack_servers/unicorn_spec.rb
@@ -6,7 +6,7 @@ require 'excon'
require 'spec_helper'
-describe 'Unicorn' do
+RSpec.describe 'Unicorn' do
before(:all) do
project_root = File.expand_path('../..', __dir__)
diff --git a/spec/requests/api/access_requests_spec.rb b/spec/requests/api/access_requests_spec.rb
index 52bc81cff18..223d740a004 100644
--- a/spec/requests/api/access_requests_spec.rb
+++ b/spec/requests/api/access_requests_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::AccessRequests do
+RSpec.describe API::AccessRequests do
let_it_be(:maintainer) { create(:user) }
let_it_be(:developer) { create(:user) }
let_it_be(:access_requester) { create(:user) }
diff --git a/spec/requests/api/admin/ci/variables_spec.rb b/spec/requests/api/admin/ci/variables_spec.rb
index 185fde17e1b..812ee93ad21 100644
--- a/spec/requests/api/admin/ci/variables_spec.rb
+++ b/spec/requests/api/admin/ci/variables_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ::API::Admin::Ci::Variables do
+RSpec.describe ::API::Admin::Ci::Variables do
let_it_be(:admin) { create(:admin) }
let_it_be(:user) { create(:user) }
diff --git a/spec/requests/api/admin/instance_clusters_spec.rb b/spec/requests/api/admin/instance_clusters_spec.rb
new file mode 100644
index 00000000000..b68541b5d92
--- /dev/null
+++ b/spec/requests/api/admin/instance_clusters_spec.rb
@@ -0,0 +1,461 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe ::API::Admin::InstanceClusters do
+ include KubernetesHelpers
+
+ let_it_be(:regular_user) { create(:user) }
+ let_it_be(:admin_user) { create(:admin) }
+ let_it_be(:project) { create(:project) }
+ let_it_be(:project_cluster) do
+ create(:cluster, :project, :provided_by_gcp,
+ user: admin_user,
+ projects: [project])
+ end
+ let(:project_cluster_id) { project_cluster.id }
+
+ describe "GET /admin/clusters" do
+ let_it_be(:clusters) do
+ create_list(:cluster, 3, :provided_by_gcp, :instance, :production_environment)
+ end
+
+ context "when authenticated as a non-admin user" do
+ it 'returns 403' do
+ get api('/admin/clusters', regular_user)
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+ end
+
+ context "when authenticated as admin" do
+ before do
+ get api("/admin/clusters", admin_user)
+ end
+
+ it 'returns 200' do
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+
+ it 'includes pagination headers' do
+ expect(response).to include_pagination_headers
+ end
+
+ it 'only returns the instance clusters' do
+ cluster_ids = json_response.map { |cluster| cluster['id'] }
+ expect(cluster_ids).to match_array(clusters.pluck(:id))
+ expect(cluster_ids).not_to include(project_cluster_id)
+ end
+ end
+ end
+
+ describe "GET /admin/clusters/:cluster_id" do
+ let_it_be(:platform_kubernetes) do
+ create(:cluster_platform_kubernetes, :configured)
+ end
+
+ let_it_be(:cluster) do
+ create(:cluster, :instance, :provided_by_gcp, :with_domain,
+ platform_kubernetes: platform_kubernetes,
+ user: admin_user)
+ end
+
+ let(:cluster_id) { cluster.id }
+
+ context "when authenticated as admin" do
+ before do
+ get api("/admin/clusters/#{cluster_id}", admin_user)
+ end
+
+ context "when no cluster associated to the ID" do
+ let(:cluster_id) { 1337 }
+
+ it 'returns 404' do
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+
+ context "when cluster with cluster_id exists" do
+ it 'returns 200' do
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+
+ it 'returns the cluster with cluster_id' do
+ expect(json_response['id']).to eq(cluster.id)
+ end
+
+ it 'returns the cluster information' do
+ expect(json_response['provider_type']).to eq('gcp')
+ expect(json_response['platform_type']).to eq('kubernetes')
+ expect(json_response['environment_scope']).to eq('*')
+ expect(json_response['cluster_type']).to eq('instance_type')
+ expect(json_response['domain']).to eq('example.com')
+ end
+
+ it 'returns kubernetes platform information' do
+ platform = json_response['platform_kubernetes']
+
+ expect(platform['api_url']).to eq('https://kubernetes.example.com')
+ expect(platform['ca_cert']).to be_present
+ end
+
+ it 'returns user information' do
+ user = json_response['user']
+
+ expect(user['id']).to eq(admin_user.id)
+ expect(user['username']).to eq(admin_user.username)
+ end
+
+ it 'returns GCP provider information' do
+ gcp_provider = json_response['provider_gcp']
+
+ expect(gcp_provider['cluster_id']).to eq(cluster.id)
+ expect(gcp_provider['status_name']).to eq('created')
+ expect(gcp_provider['gcp_project_id']).to eq('test-gcp-project')
+ expect(gcp_provider['zone']).to eq('us-central1-a')
+ expect(gcp_provider['machine_type']).to eq('n1-standard-2')
+ expect(gcp_provider['num_nodes']).to eq(3)
+ expect(gcp_provider['endpoint']).to eq('111.111.111.111')
+ end
+
+ context 'when cluster has no provider' do
+ let(:cluster) do
+ create(:cluster, :instance, :provided_by_user, :production_environment)
+ end
+
+ it 'does not include GCP provider info' do
+ expect(json_response['provider_gcp']).not_to be_present
+ end
+ end
+
+ context 'when trying to get a project cluster via the instance cluster endpoint' do
+ it 'returns 404' do
+ get api("/admin/clusters/#{project_cluster_id}", admin_user)
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+ end
+
+ context "when authenticated as a non-admin user" do
+ it 'returns 403' do
+ get api("/admin/clusters/#{cluster_id}", regular_user)
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+ end
+ end
+ end
+
+ describe "POST /admin/clusters/add" do
+ let(:api_url) { 'https://example.com' }
+ let(:authorization_type) { 'rbac' }
+ let(:clusterable) { Clusters::Instance.new }
+
+ let(:platform_kubernetes_attributes) do
+ {
+ api_url: api_url,
+ token: 'sample-token',
+ authorization_type: authorization_type
+ }
+ end
+
+ let(:cluster_params) do
+ {
+ name: 'test-instance-cluster',
+ domain: 'domain.example.com',
+ managed: false,
+ platform_kubernetes_attributes: platform_kubernetes_attributes,
+ clusterable: clusterable
+ }
+ end
+
+ let(:multiple_cluster_params) do
+ {
+ name: 'multiple-instance-cluster',
+ environment_scope: 'staging/*',
+ platform_kubernetes_attributes: platform_kubernetes_attributes
+ }
+ end
+
+ let(:invalid_cluster_params) do
+ {
+ environment_scope: 'production/*',
+ domain: 'domain.example.com',
+ platform_kubernetes_attributes: platform_kubernetes_attributes
+ }
+ end
+
+ context 'authorized user' do
+ before do
+ post api('/admin/clusters/add', admin_user), params: cluster_params
+ end
+
+ context 'with valid params' do
+ it 'responds with 201' do
+ expect(response).to have_gitlab_http_status(:created)
+ end
+
+ it 'creates a new Clusters::Cluster', :aggregate_failures do
+ cluster_result = Clusters::Cluster.find(json_response["id"])
+ platform_kubernetes = cluster_result.platform
+ expect(cluster_result).to be_user
+ expect(cluster_result).to be_kubernetes
+ expect(cluster_result.clusterable).to be_a Clusters::Instance
+ expect(cluster_result.cluster_type).to eq('instance_type')
+ expect(cluster_result.name).to eq('test-instance-cluster')
+ expect(cluster_result.domain).to eq('domain.example.com')
+ expect(cluster_result.environment_scope).to eq('*')
+ expect(cluster_result.enabled).to eq(true)
+ expect(platform_kubernetes.authorization_type).to eq('rbac')
+ expect(cluster_result.managed).to be_falsy
+ expect(platform_kubernetes.api_url).to eq("https://example.com")
+ expect(platform_kubernetes.token).to eq('sample-token')
+ end
+
+ context 'when user does not indicate authorization type' do
+ let(:platform_kubernetes_attributes) do
+ {
+ api_url: api_url,
+ token: 'sample-token'
+ }
+ end
+
+ it 'defaults to RBAC' do
+ cluster_result = Clusters::Cluster.find(json_response['id'])
+
+ expect(cluster_result.platform_kubernetes.rbac?).to be_truthy
+ end
+ end
+
+ context 'when user sets authorization type as ABAC' do
+ let(:authorization_type) { 'abac' }
+
+ it 'creates an ABAC cluster' do
+ cluster_result = Clusters::Cluster.find(json_response['id'])
+
+ expect(cluster_result.platform.abac?).to be_truthy
+ end
+ end
+
+ context 'when an instance cluster already exists' do
+ it 'allows user to add multiple clusters' do
+ post api('/admin/clusters/add', admin_user), params: multiple_cluster_params
+
+ expect(Clusters::Instance.new.clusters.count).to eq(2)
+ end
+ end
+ end
+
+ context 'with invalid params' do
+ context 'when missing a required parameter' do
+ it 'responds with 400' do
+ post api('/admin/clusters/add', admin_user), params: invalid_cluster_params
+ expect(response).to have_gitlab_http_status(:bad_request)
+ expect(json_response['error']).to eql('name is missing')
+ end
+ end
+
+ context 'with a malformed api url' do
+ let(:api_url) { 'invalid_api_url' }
+
+ it 'responds with 400' do
+ expect(response).to have_gitlab_http_status(:bad_request)
+ end
+
+ it 'returns validation errors' do
+ expect(json_response['message']['platform_kubernetes.api_url'].first).to be_present
+ end
+ end
+ end
+ end
+
+ context 'non-authorized user' do
+ it 'responds with 403' do
+ post api('/admin/clusters/add', regular_user), params: cluster_params
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+ end
+ end
+
+ describe 'PUT /admin/clusters/:cluster_id' do
+ let(:api_url) { 'https://example.com' }
+
+ let(:update_params) do
+ {
+ domain: domain,
+ platform_kubernetes_attributes: platform_kubernetes_attributes
+ }
+ end
+
+ let(:domain) { 'new-domain.com' }
+ let(:platform_kubernetes_attributes) { {} }
+
+ let_it_be(:cluster) do
+ create(:cluster, :instance, :provided_by_gcp, domain: 'old-domain.com')
+ end
+
+ context 'authorized user' do
+ before do
+ put api("/admin/clusters/#{cluster.id}", admin_user), params: update_params
+
+ cluster.reload
+ end
+
+ context 'with valid params' do
+ it 'responds with 200' do
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+
+ it 'updates cluster attributes' do
+ expect(cluster.domain).to eq('new-domain.com')
+ end
+ end
+
+ context 'with invalid params' do
+ let(:domain) { 'invalid domain' }
+
+ it 'responds with 400' do
+ expect(response).to have_gitlab_http_status(:bad_request)
+ end
+
+ it 'does not update cluster attributes' do
+ expect(cluster.domain).to eq('old-domain.com')
+ end
+
+ it 'returns validation errors' do
+ expect(json_response['message']['domain'].first).to match('contains invalid characters (valid characters: [a-z0-9\\-])')
+ end
+ end
+
+ context 'with a GCP cluster' do
+ context 'when user tries to change GCP specific fields' do
+ let(:platform_kubernetes_attributes) do
+ {
+ api_url: 'https://new-api-url.com',
+ token: 'new-sample-token'
+ }
+ end
+
+ it 'responds with 400' do
+ expect(response).to have_gitlab_http_status(:bad_request)
+ end
+
+ it 'returns validation error' do
+ expect(json_response['message']['platform_kubernetes.base'].first).to eq(_('Cannot modify managed Kubernetes cluster'))
+ end
+ end
+
+ context 'when user tries to change domain' do
+ let(:domain) { 'new-domain.com' }
+
+ it 'responds with 200' do
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+ end
+ end
+
+ context 'with an user cluster' do
+ let(:api_url) { 'https://new-api-url.com' }
+
+ let(:cluster) do
+ create(:cluster, :instance, :provided_by_user, :production_environment)
+ end
+
+ let(:platform_kubernetes_attributes) do
+ {
+ api_url: api_url,
+ token: 'new-sample-token'
+ }
+ end
+
+ let(:update_params) do
+ {
+ name: 'new-name',
+ platform_kubernetes_attributes: platform_kubernetes_attributes
+ }
+ end
+
+ it 'responds with 200' do
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+
+ it 'updates platform kubernetes attributes' do
+ platform_kubernetes = cluster.platform_kubernetes
+
+ expect(cluster.name).to eq('new-name')
+ expect(platform_kubernetes.api_url).to eq('https://new-api-url.com')
+ expect(platform_kubernetes.token).to eq('new-sample-token')
+ end
+ end
+
+ context 'with a cluster that does not exist' do
+ let(:cluster_id) { 1337 }
+
+ it 'returns 404' do
+ put api("/admin/clusters/#{cluster_id}", admin_user), params: update_params
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+
+ context 'when trying to update a project cluster via the instance cluster endpoint' do
+ it 'returns 404' do
+ put api("/admin/clusters/#{project_cluster_id}", admin_user), params: update_params
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+ end
+
+ context 'non-authorized user' do
+ it 'responds with 403' do
+ put api("/admin/clusters/#{cluster.id}", regular_user), params: update_params
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+ end
+ end
+
+ describe 'DELETE /admin/clusters/:cluster_id' do
+ let(:cluster_params) { { cluster_id: cluster.id } }
+
+ let_it_be(:cluster) do
+ create(:cluster, :instance, :provided_by_gcp)
+ end
+
+ context 'authorized user' do
+ before do
+ delete api("/admin/clusters/#{cluster.id}", admin_user), params: cluster_params
+ end
+
+ it 'responds with 204' do
+ expect(response).to have_gitlab_http_status(:no_content)
+ end
+
+ it 'deletes the cluster' do
+ expect(Clusters::Cluster.exists?(id: cluster.id)).to be_falsy
+ end
+
+ context 'with a cluster that does not exist' do
+ let(:cluster_id) { 1337 }
+
+ it 'returns 404' do
+ delete api("/admin/clusters/#{cluster_id}", admin_user)
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+
+ context 'when trying to update a project cluster via the instance cluster endpoint' do
+ it 'returns 404' do
+ delete api("/admin/clusters/#{project_cluster_id}", admin_user)
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+ end
+
+ context 'non-authorized user' do
+ it 'responds with 403' do
+ delete api("/admin/clusters/#{cluster.id}", regular_user), params: cluster_params
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+ end
+ end
+end
diff --git a/spec/requests/api/admin/sidekiq_spec.rb b/spec/requests/api/admin/sidekiq_spec.rb
index 303b62f4436..3c488816bed 100644
--- a/spec/requests/api/admin/sidekiq_spec.rb
+++ b/spec/requests/api/admin/sidekiq_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Admin::Sidekiq, :clean_gitlab_redis_queues do
+RSpec.describe API::Admin::Sidekiq, :clean_gitlab_redis_queues do
let_it_be(:admin) { create(:admin) }
describe 'DELETE /admin/sidekiq/queues/:queue_name' do
diff --git a/spec/requests/api/api_guard/admin_mode_middleware_spec.rb b/spec/requests/api/api_guard/admin_mode_middleware_spec.rb
index 7175076e56d..4b477f829a7 100644
--- a/spec/requests/api/api_guard/admin_mode_middleware_spec.rb
+++ b/spec/requests/api/api_guard/admin_mode_middleware_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::APIGuard::AdminModeMiddleware, :do_not_mock_admin_mode, :request_store do
+RSpec.describe API::APIGuard::AdminModeMiddleware, :do_not_mock_admin_mode, :request_store do
let(:user) { create(:admin) }
it 'is loaded' do
diff --git a/spec/requests/api/api_spec.rb b/spec/requests/api/api_spec.rb
index 201c0d1796c..bd0426601db 100644
--- a/spec/requests/api/api_spec.rb
+++ b/spec/requests/api/api_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::API do
+RSpec.describe API::API do
include GroupAPIHelpers
describe 'Record user last activity in after hook' do
@@ -36,6 +36,14 @@ describe API::API do
expect(response).to have_gitlab_http_status(:ok)
end
+ it 'does not authorize user for revoked token' do
+ revoked = create(:personal_access_token, :revoked, user: user, scopes: [:read_api])
+
+ get api('/groups', personal_access_token: revoked)
+
+ expect(response).to have_gitlab_http_status(:unauthorized)
+ end
+
it 'does not authorize user for post request' do
params = attributes_for_group_api
diff --git a/spec/requests/api/appearance_spec.rb b/spec/requests/api/appearance_spec.rb
index f8c3db70d16..69176e18d2e 100644
--- a/spec/requests/api/appearance_spec.rb
+++ b/spec/requests/api/appearance_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Appearance, 'Appearance' do
+RSpec.describe API::Appearance, 'Appearance' do
let_it_be(:user) { create(:user) }
let_it_be(:admin) { create(:admin) }
diff --git a/spec/requests/api/applications_spec.rb b/spec/requests/api/applications_spec.rb
index cd341ad134e..63fbf6e32dd 100644
--- a/spec/requests/api/applications_spec.rb
+++ b/spec/requests/api/applications_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Applications, :api do
+RSpec.describe API::Applications, :api do
let(:admin_user) { create(:user, admin: true) }
let(:user) { create(:user, admin: false) }
let!(:application) { create(:application, name: 'another_application', redirect_uri: 'http://other_application.url', scopes: '') }
@@ -74,14 +74,15 @@ describe API::Applications, :api do
expect(json_response['error']).to eq('scopes is missing')
end
- it 'does not allow creating an application with confidential set to nil' do
+ it 'defaults to creating an application with confidential' do
expect do
post api('/applications', admin_user), params: { name: 'application_name', redirect_uri: 'http://application.url', scopes: '', confidential: nil }
- end.not_to change { Doorkeeper::Application.count }
+ end.to change { Doorkeeper::Application.count }.by(1)
- expect(response).to have_gitlab_http_status(:bad_request)
+ expect(response).to have_gitlab_http_status(:created)
expect(json_response).to be_a Hash
- expect(json_response['message']['confidential'].first).to eq('is not included in the list')
+ expect(json_response['callback_url']).to eq('http://application.url')
+ expect(json_response['confidential']).to be true
end
end
diff --git a/spec/requests/api/avatar_spec.rb b/spec/requests/api/avatar_spec.rb
index 45e34b7894b..656a086e550 100644
--- a/spec/requests/api/avatar_spec.rb
+++ b/spec/requests/api/avatar_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Avatar do
+RSpec.describe API::Avatar do
let(:gravatar_service) { double('GravatarService') }
describe 'GET /avatar' do
diff --git a/spec/requests/api/award_emoji_spec.rb b/spec/requests/api/award_emoji_spec.rb
index 543fe970abd..1c825949ae8 100644
--- a/spec/requests/api/award_emoji_spec.rb
+++ b/spec/requests/api/award_emoji_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::AwardEmoji do
+RSpec.describe API::AwardEmoji do
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project) }
let_it_be(:issue) { create(:issue, project: project) }
diff --git a/spec/requests/api/badges_spec.rb b/spec/requests/api/badges_spec.rb
index d7f9b7d010b..99d224cb8e9 100644
--- a/spec/requests/api/badges_spec.rb
+++ b/spec/requests/api/badges_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Badges do
+RSpec.describe API::Badges do
let(:maintainer) { create(:user, username: 'maintainer_user') }
let(:developer) { create(:user) }
let(:access_requester) { create(:user) }
diff --git a/spec/requests/api/boards_spec.rb b/spec/requests/api/boards_spec.rb
index d761b371821..f0d3afd0af7 100644
--- a/spec/requests/api/boards_spec.rb
+++ b/spec/requests/api/boards_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Boards do
+RSpec.describe API::Boards do
let_it_be(:user) { create(:user) }
let_it_be(:non_member) { create(:user) }
let_it_be(:guest) { create(:user) }
diff --git a/spec/requests/api/branches_spec.rb b/spec/requests/api/branches_spec.rb
index f2dc5b1c045..46acd92803f 100644
--- a/spec/requests/api/branches_spec.rb
+++ b/spec/requests/api/branches_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Branches do
+RSpec.describe API::Branches do
let_it_be(:user) { create(:user) }
let(:project) { create(:project, :repository, creator: user, path: 'my.project') }
let(:guest) { create(:user).tap { |u| project.add_guest(u) } }
@@ -17,6 +17,7 @@ describe API::Branches do
before do
project.add_maintainer(user)
project.repository.add_branch(user, 'ends-with.txt', branch_sha)
+ stub_feature_flags(branch_list_keyset_pagination: false)
end
describe "GET /projects/:id/repository/branches" do
@@ -29,16 +30,6 @@ describe API::Branches do
end
end
- it 'returns the repository branches' do
- get api(route, current_user), params: { per_page: 100 }
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(response).to match_response_schema('public_api/v4/branches')
- expect(response).to include_pagination_headers
- branch_names = json_response.map { |x| x['name'] }
- expect(branch_names).to match_array(project.repository.branch_names)
- end
-
def check_merge_status(json_response)
merged, unmerged = json_response.partition { |branch| branch['merged'] }
merged_branches = merged.map { |branch| branch['name'] }
@@ -47,22 +38,107 @@ describe API::Branches do
expect(project.repository.merged_branch_names(unmerged_branches)).to be_empty
end
- it 'determines only a limited number of merged branch names' do
- expect(API::Entities::Branch).to receive(:represent).with(anything, has_up_to_merged_branch_names_count(2)).and_call_original
+ context 'with branch_list_keyset_pagination feature off' do
+ context 'with legacy pagination params' do
+ it 'returns the repository branches' do
+ get api(route, current_user), params: { per_page: 100 }
- get api(route, current_user), params: { per_page: 2 }
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to match_response_schema('public_api/v4/branches')
+ expect(response).to include_pagination_headers
+ branch_names = json_response.map { |x| x['name'] }
+ expect(branch_names).to match_array(project.repository.branch_names)
+ end
- expect(response).to have_gitlab_http_status(:ok)
+ it 'determines only a limited number of merged branch names' do
+ expect(API::Entities::Branch).to receive(:represent).with(anything, has_up_to_merged_branch_names_count(2)).and_call_original
+
+ get api(route, current_user), params: { per_page: 2 }
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response.count).to eq 2
+
+ check_merge_status(json_response)
+ end
- check_merge_status(json_response)
+ it 'merge status matches reality on paginated input' do
+ expected_first_branch_name = project.repository.branches_sorted_by('name')[20].name
+
+ get api(route, current_user), params: { per_page: 20, page: 2 }
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response.count).to eq 20
+ expect(json_response.first['name']).to eq(expected_first_branch_name)
+
+ check_merge_status(json_response)
+ end
+ end
+
+ context 'with gitaly pagination params ' do
+ it 'merge status matches reality on paginated input' do
+ expected_first_branch_name = project.repository.branches_sorted_by('name').first.name
+
+ get api(route, current_user), params: { per_page: 20, page_token: 'feature' }
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response.count).to eq 20
+ expect(json_response.first['name']).to eq(expected_first_branch_name)
+
+ check_merge_status(json_response)
+ end
+ end
end
- it 'merge status matches reality on paginated input' do
- get api(route, current_user), params: { per_page: 20, page: 2 }
+ context 'with branch_list_keyset_pagination feature on' do
+ before do
+ stub_feature_flags(branch_list_keyset_pagination: true)
+ end
- expect(response).to have_gitlab_http_status(:ok)
+ context 'with gitaly pagination params ' do
+ it 'returns the repository branches' do
+ get api(route, current_user), params: { per_page: 100 }
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to match_response_schema('public_api/v4/branches')
+ branch_names = json_response.map { |x| x['name'] }
+ expect(branch_names).to match_array(project.repository.branch_names)
+ end
+
+ it 'determines only a limited number of merged branch names' do
+ expect(API::Entities::Branch).to receive(:represent).with(anything, has_up_to_merged_branch_names_count(2)).and_call_original
+
+ get api(route, current_user), params: { per_page: 2 }
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response.count).to eq 2
+
+ check_merge_status(json_response)
+ end
- check_merge_status(json_response)
+ it 'merge status matches reality on paginated input' do
+ expected_first_branch_name = project.repository.branches_sorted_by('name').drop_while { |b| b.name <= 'feature' }.first.name
+
+ get api(route, current_user), params: { per_page: 20, page_token: 'feature' }
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response.count).to eq 20
+ expect(json_response.first['name']).to eq(expected_first_branch_name)
+
+ check_merge_status(json_response)
+ end
+ end
+
+ context 'with legacy pagination params' do
+ it 'ignores legacy pagination params' do
+ expected_first_branch_name = project.repository.branches_sorted_by('name').first.name
+ get api(route, current_user), params: { per_page: 20, page: 2 }
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response.first['name']).to eq(expected_first_branch_name)
+
+ check_merge_status(json_response)
+ end
+ end
end
context 'when repository is disabled' do
diff --git a/spec/requests/api/broadcast_messages_spec.rb b/spec/requests/api/broadcast_messages_spec.rb
index 9bfbbe0daab..b5b6ce106e5 100644
--- a/spec/requests/api/broadcast_messages_spec.rb
+++ b/spec/requests/api/broadcast_messages_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::BroadcastMessages do
+RSpec.describe API::BroadcastMessages do
let_it_be(:user) { create(:user) }
let_it_be(:admin) { create(:admin) }
let_it_be(:message) { create(:broadcast_message) }
diff --git a/spec/requests/api/ci/pipeline_schedules_spec.rb b/spec/requests/api/ci/pipeline_schedules_spec.rb
new file mode 100644
index 00000000000..e0199b7b51c
--- /dev/null
+++ b/spec/requests/api/ci/pipeline_schedules_spec.rb
@@ -0,0 +1,522 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe API::Ci::PipelineSchedules do
+ let_it_be(:developer) { create(:user) }
+ let_it_be(:user) { create(:user) }
+ let_it_be(:project) { create(:project, :repository, public_builds: false) }
+
+ before do
+ project.add_developer(developer)
+ end
+
+ describe 'GET /projects/:id/pipeline_schedules' do
+ context 'authenticated user with valid permissions' do
+ let(:pipeline_schedule) { create(:ci_pipeline_schedule, project: project, owner: developer) }
+
+ before do
+ pipeline_schedule.pipelines << build(:ci_pipeline, project: project)
+ end
+
+ def create_pipeline_schedules(count)
+ create_list(:ci_pipeline_schedule, count, project: project)
+ .each do |pipeline_schedule|
+ create(:user).tap do |user|
+ project.add_developer(user)
+ pipeline_schedule.update!(owner: user)
+ end
+ pipeline_schedule.pipelines << build(:ci_pipeline, project: project)
+ end
+ end
+
+ it 'returns list of pipeline_schedules' do
+ get api("/projects/#{project.id}/pipeline_schedules", developer)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to include_pagination_headers
+ expect(response).to match_response_schema('pipeline_schedules')
+ end
+
+ it 'avoids N + 1 queries' do
+ # We need at least two users to trigger a preload for that relation.
+ create_pipeline_schedules(1)
+
+ control_count = ActiveRecord::QueryRecorder.new do
+ get api("/projects/#{project.id}/pipeline_schedules", developer)
+ end.count
+
+ create_pipeline_schedules(5)
+
+ expect do
+ get api("/projects/#{project.id}/pipeline_schedules", developer)
+ end.not_to exceed_query_limit(control_count)
+ end
+
+ %w[active inactive].each do |target|
+ context "when scope is #{target}" do
+ before do
+ create(:ci_pipeline_schedule, project: project, active: active?(target))
+ end
+
+ it 'returns matched pipeline schedules' do
+ get api("/projects/#{project.id}/pipeline_schedules", developer), params: { scope: target }
+
+ expect(json_response.map { |r| r['active'] }).to all(eq(active?(target)))
+ end
+ end
+
+ def active?(str)
+ str == 'active'
+ end
+ end
+ end
+
+ context 'authenticated user with invalid permissions' do
+ it 'does not return pipeline_schedules list' do
+ get api("/projects/#{project.id}/pipeline_schedules", user)
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+
+ context 'unauthenticated user' do
+ it 'does not return pipeline_schedules list' do
+ get api("/projects/#{project.id}/pipeline_schedules")
+
+ expect(response).to have_gitlab_http_status(:unauthorized)
+ end
+ end
+ end
+
+ describe 'GET /projects/:id/pipeline_schedules/:pipeline_schedule_id' do
+ let(:pipeline_schedule) { create(:ci_pipeline_schedule, project: project, owner: developer) }
+
+ before do
+ pipeline_schedule.variables << build(:ci_pipeline_schedule_variable)
+ pipeline_schedule.pipelines << build(:ci_pipeline, project: project)
+ end
+
+ context 'authenticated user with valid permissions' do
+ it 'returns pipeline_schedule details' do
+ get api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}", developer)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to match_response_schema('pipeline_schedule')
+ end
+
+ it 'responds with 404 Not Found if requesting non-existing pipeline_schedule' do
+ get api("/projects/#{project.id}/pipeline_schedules/-5", developer)
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+
+ context 'authenticated user with invalid permissions' do
+ it 'does not return pipeline_schedules list' do
+ get api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}", user)
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+
+ context 'authenticated user with insufficient permissions' do
+ before do
+ project.add_guest(user)
+ end
+
+ it 'does not return pipeline_schedules list' do
+ get api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}", user)
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+
+ context 'unauthenticated user' do
+ it 'does not return pipeline_schedules list' do
+ get api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}")
+
+ expect(response).to have_gitlab_http_status(:unauthorized)
+ end
+ end
+ end
+
+ describe 'POST /projects/:id/pipeline_schedules' do
+ let(:params) { attributes_for(:ci_pipeline_schedule) }
+
+ context 'authenticated user with valid permissions' do
+ context 'with required parameters' do
+ it 'creates pipeline_schedule' do
+ expect do
+ post api("/projects/#{project.id}/pipeline_schedules", developer),
+ params: params
+ end.to change { project.pipeline_schedules.count }.by(1)
+
+ expect(response).to have_gitlab_http_status(:created)
+ expect(response).to match_response_schema('pipeline_schedule')
+ expect(json_response['description']).to eq(params[:description])
+ expect(json_response['ref']).to eq(params[:ref])
+ expect(json_response['cron']).to eq(params[:cron])
+ expect(json_response['cron_timezone']).to eq(params[:cron_timezone])
+ expect(json_response['owner']['id']).to eq(developer.id)
+ end
+ end
+
+ context 'without required parameters' do
+ it 'does not create pipeline_schedule' do
+ post api("/projects/#{project.id}/pipeline_schedules", developer)
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ end
+ end
+
+ context 'when cron has validation error' do
+ it 'does not create pipeline_schedule' do
+ post api("/projects/#{project.id}/pipeline_schedules", developer),
+ params: params.merge('cron' => 'invalid-cron')
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ expect(json_response['message']).to have_key('cron')
+ end
+ end
+ end
+
+ context 'authenticated user with invalid permissions' do
+ it 'does not create pipeline_schedule' do
+ post api("/projects/#{project.id}/pipeline_schedules", user), params: params
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+
+ context 'unauthenticated user' do
+ it 'does not create pipeline_schedule' do
+ post api("/projects/#{project.id}/pipeline_schedules"), params: params
+
+ expect(response).to have_gitlab_http_status(:unauthorized)
+ end
+ end
+ end
+
+ describe 'PUT /projects/:id/pipeline_schedules/:pipeline_schedule_id' do
+ let(:pipeline_schedule) do
+ create(:ci_pipeline_schedule, project: project, owner: developer)
+ end
+
+ context 'authenticated user with valid permissions' do
+ it 'updates cron' do
+ put api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}", developer),
+ params: { cron: '1 2 3 4 *' }
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to match_response_schema('pipeline_schedule')
+ expect(json_response['cron']).to eq('1 2 3 4 *')
+ end
+
+ context 'when cron has validation error' do
+ it 'does not update pipeline_schedule' do
+ put api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}", developer),
+ params: { cron: 'invalid-cron' }
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ expect(json_response['message']).to have_key('cron')
+ end
+ end
+ end
+
+ context 'authenticated user with invalid permissions' do
+ it 'does not update pipeline_schedule' do
+ put api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}", user)
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+
+ context 'unauthenticated user' do
+ it 'does not update pipeline_schedule' do
+ put api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}")
+
+ expect(response).to have_gitlab_http_status(:unauthorized)
+ end
+ end
+ end
+
+ describe 'POST /projects/:id/pipeline_schedules/:pipeline_schedule_id/take_ownership' do
+ let(:pipeline_schedule) do
+ create(:ci_pipeline_schedule, project: project, owner: developer)
+ end
+
+ context 'authenticated user with valid permissions' do
+ it 'updates owner' do
+ post api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}/take_ownership", developer)
+
+ expect(response).to have_gitlab_http_status(:created)
+ expect(response).to match_response_schema('pipeline_schedule')
+ end
+ end
+
+ context 'authenticated user with invalid permissions' do
+ it 'does not update owner' do
+ post api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}/take_ownership", user)
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+
+ context 'unauthenticated user' do
+ it 'does not update owner' do
+ post api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}/take_ownership")
+
+ expect(response).to have_gitlab_http_status(:unauthorized)
+ end
+ end
+ end
+
+ describe 'DELETE /projects/:id/pipeline_schedules/:pipeline_schedule_id' do
+ let(:maintainer) { create(:user) }
+
+ let!(:pipeline_schedule) do
+ create(:ci_pipeline_schedule, project: project, owner: developer)
+ end
+
+ before do
+ project.add_maintainer(maintainer)
+ end
+
+ context 'authenticated user with valid permissions' do
+ it 'deletes pipeline_schedule' do
+ expect do
+ delete api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}", maintainer)
+ end.to change { project.pipeline_schedules.count }.by(-1)
+
+ expect(response).to have_gitlab_http_status(:no_content)
+ end
+
+ it 'responds with 404 Not Found if requesting non-existing pipeline_schedule' do
+ delete api("/projects/#{project.id}/pipeline_schedules/-5", maintainer)
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+
+ it_behaves_like '412 response' do
+ let(:request) { api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}", maintainer) }
+ end
+ end
+
+ context 'authenticated user with invalid permissions' do
+ let!(:pipeline_schedule) { create(:ci_pipeline_schedule, project: project, owner: maintainer) }
+
+ it 'does not delete pipeline_schedule' do
+ delete api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}", developer)
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+ end
+
+ context 'unauthenticated user' do
+ it 'does not delete pipeline_schedule' do
+ delete api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}")
+
+ expect(response).to have_gitlab_http_status(:unauthorized)
+ end
+ end
+ end
+
+ describe 'POST /projects/:id/pipeline_schedules/:pipeline_schedule_id/play' do
+ let_it_be(:pipeline_schedule) { create(:ci_pipeline_schedule, project: project) }
+
+ let(:route) { ->(id) { "/projects/#{project.id}/pipeline_schedules/#{id}/play" } }
+
+ context 'authenticated user with `:play_pipeline_schedule` permission' do
+ it 'schedules a pipeline worker' do
+ project.add_developer(developer)
+
+ expect(RunPipelineScheduleWorker)
+ .to receive(:perform_async)
+ .with(pipeline_schedule.id, developer.id)
+ .and_call_original
+ post api(route[pipeline_schedule.id], developer)
+
+ expect(response).to have_gitlab_http_status(:created)
+ end
+
+ it 'renders an error if scheduling failed' do
+ project.add_developer(developer)
+
+ expect(RunPipelineScheduleWorker)
+ .to receive(:perform_async)
+ .with(pipeline_schedule.id, developer.id)
+ .and_return(nil)
+ post api(route[pipeline_schedule.id], developer)
+
+ expect(response).to have_gitlab_http_status(:internal_server_error)
+ end
+ end
+
+ context 'authenticated user with insufficient access' do
+ it 'responds with not found' do
+ project.add_guest(user)
+
+ post api(route[pipeline_schedule.id], user)
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+
+ context 'unauthenticated user' do
+ it 'responds with unauthorized' do
+ post api(route[pipeline_schedule.id])
+
+ expect(response).to have_gitlab_http_status(:unauthorized)
+ end
+ end
+ end
+
+ describe 'POST /projects/:id/pipeline_schedules/:pipeline_schedule_id/variables' do
+ let(:params) { attributes_for(:ci_pipeline_schedule_variable) }
+
+ let_it_be(:pipeline_schedule) do
+ create(:ci_pipeline_schedule, project: project, owner: developer)
+ end
+
+ context 'authenticated user with valid permissions' do
+ context 'with required parameters' do
+ it 'creates pipeline_schedule_variable' do
+ expect do
+ post api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}/variables", developer),
+ params: params.merge(variable_type: 'file')
+ end.to change { pipeline_schedule.variables.count }.by(1)
+
+ expect(response).to have_gitlab_http_status(:created)
+ expect(response).to match_response_schema('pipeline_schedule_variable')
+ expect(json_response['key']).to eq(params[:key])
+ expect(json_response['value']).to eq(params[:value])
+ expect(json_response['variable_type']).to eq('file')
+ end
+ end
+
+ context 'without required parameters' do
+ it 'does not create pipeline_schedule_variable' do
+ post api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}/variables", developer)
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ end
+ end
+
+ context 'when key has validation error' do
+ it 'does not create pipeline_schedule_variable' do
+ post api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}/variables", developer),
+ params: params.merge('key' => '!?!?')
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ expect(json_response['message']).to have_key('key')
+ end
+ end
+ end
+
+ context 'authenticated user with invalid permissions' do
+ it 'does not create pipeline_schedule_variable' do
+ post api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}/variables", user), params: params
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+
+ context 'unauthenticated user' do
+ it 'does not create pipeline_schedule_variable' do
+ post api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}/variables"), params: params
+
+ expect(response).to have_gitlab_http_status(:unauthorized)
+ end
+ end
+ end
+
+ describe 'PUT /projects/:id/pipeline_schedules/:pipeline_schedule_id/variables/:key' do
+ let_it_be(:pipeline_schedule) do
+ create(:ci_pipeline_schedule, project: project, owner: developer)
+ end
+
+ let(:pipeline_schedule_variable) do
+ create(:ci_pipeline_schedule_variable, pipeline_schedule: pipeline_schedule)
+ end
+
+ context 'authenticated user with valid permissions' do
+ it 'updates pipeline_schedule_variable' do
+ put api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}/variables/#{pipeline_schedule_variable.key}", developer),
+ params: { value: 'updated_value', variable_type: 'file' }
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to match_response_schema('pipeline_schedule_variable')
+ expect(json_response['value']).to eq('updated_value')
+ expect(json_response['variable_type']).to eq('file')
+ end
+ end
+
+ context 'authenticated user with invalid permissions' do
+ it 'does not update pipeline_schedule_variable' do
+ put api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}/variables/#{pipeline_schedule_variable.key}", user)
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+
+ context 'unauthenticated user' do
+ it 'does not update pipeline_schedule_variable' do
+ put api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}/variables/#{pipeline_schedule_variable.key}")
+
+ expect(response).to have_gitlab_http_status(:unauthorized)
+ end
+ end
+ end
+
+ describe 'DELETE /projects/:id/pipeline_schedules/:pipeline_schedule_id/variables/:key' do
+ let(:maintainer) { create(:user) }
+
+ let_it_be(:pipeline_schedule) do
+ create(:ci_pipeline_schedule, project: project, owner: developer)
+ end
+
+ let!(:pipeline_schedule_variable) do
+ create(:ci_pipeline_schedule_variable, pipeline_schedule: pipeline_schedule)
+ end
+
+ before do
+ project.add_maintainer(maintainer)
+ end
+
+ context 'authenticated user with valid permissions' do
+ it 'deletes pipeline_schedule_variable' do
+ expect do
+ delete api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}/variables/#{pipeline_schedule_variable.key}", maintainer)
+ end.to change { Ci::PipelineScheduleVariable.count }.by(-1)
+
+ expect(response).to have_gitlab_http_status(:accepted)
+ expect(response).to match_response_schema('pipeline_schedule_variable')
+ end
+
+ it 'responds with 404 Not Found if requesting non-existing pipeline_schedule_variable' do
+ delete api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}/variables/____", maintainer)
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+
+ context 'authenticated user with invalid permissions' do
+ let!(:pipeline_schedule) { create(:ci_pipeline_schedule, project: project, owner: maintainer) }
+
+ it 'does not delete pipeline_schedule_variable' do
+ delete api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}/variables/#{pipeline_schedule_variable.key}", developer)
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+ end
+
+ context 'unauthenticated user' do
+ it 'does not delete pipeline_schedule_variable' do
+ delete api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}/variables/#{pipeline_schedule_variable.key}")
+
+ expect(response).to have_gitlab_http_status(:unauthorized)
+ end
+ end
+ end
+end
diff --git a/spec/requests/api/ci/pipelines_spec.rb b/spec/requests/api/ci/pipelines_spec.rb
new file mode 100644
index 00000000000..c9ca806e2c4
--- /dev/null
+++ b/spec/requests/api/ci/pipelines_spec.rb
@@ -0,0 +1,786 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe API::Ci::Pipelines do
+ let_it_be(:user) { create(:user) }
+ let_it_be(:non_member) { create(:user) }
+
+ # We need to reload as the shared example 'pipelines visibility table' is changing project
+ let_it_be(:project, reload: true) do
+ create(:project, :repository, creator: user)
+ end
+
+ let_it_be(:pipeline) do
+ create(:ci_empty_pipeline, project: project, sha: project.commit.id,
+ ref: project.default_branch, user: user)
+ end
+
+ before do
+ project.add_maintainer(user)
+ end
+
+ describe 'GET /projects/:id/pipelines ' do
+ it_behaves_like 'pipelines visibility table'
+
+ context 'authorized user' do
+ it 'returns project pipelines' do
+ get api("/projects/#{project.id}/pipelines", user)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to include_pagination_headers
+ expect(json_response).to be_an Array
+ expect(json_response.first['sha']).to match(/\A\h{40}\z/)
+ expect(json_response.first['id']).to eq pipeline.id
+ expect(json_response.first['web_url']).to be_present
+ expect(json_response.first.keys).to contain_exactly(*%w[id sha ref status web_url created_at updated_at])
+ end
+
+ context 'when parameter is passed' do
+ %w[running pending].each do |target|
+ context "when scope is #{target}" do
+ before do
+ create(:ci_pipeline, project: project, status: target)
+ end
+
+ it 'returns matched pipelines' do
+ get api("/projects/#{project.id}/pipelines", user), params: { scope: target }
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to include_pagination_headers
+ expect(json_response).not_to be_empty
+ json_response.each { |r| expect(r['status']).to eq(target) }
+ end
+ end
+ end
+
+ context 'when scope is finished' do
+ before do
+ create(:ci_pipeline, project: project, status: 'success')
+ create(:ci_pipeline, project: project, status: 'failed')
+ create(:ci_pipeline, project: project, status: 'canceled')
+ end
+
+ it 'returns matched pipelines' do
+ get api("/projects/#{project.id}/pipelines", user), params: { scope: 'finished' }
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to include_pagination_headers
+ expect(json_response).not_to be_empty
+ json_response.each { |r| expect(r['status']).to be_in(%w[success failed canceled]) }
+ end
+ end
+
+ context 'when scope is branches or tags' do
+ let_it_be(:pipeline_branch) { create(:ci_pipeline, project: project) }
+ let_it_be(:pipeline_tag) { create(:ci_pipeline, project: project, ref: 'v1.0.0', tag: true) }
+
+ context 'when scope is branches' do
+ it 'returns matched pipelines' do
+ get api("/projects/#{project.id}/pipelines", user), params: { scope: 'branches' }
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to include_pagination_headers
+ expect(json_response).not_to be_empty
+ expect(json_response.last['id']).to eq(pipeline_branch.id)
+ end
+ end
+
+ context 'when scope is tags' do
+ it 'returns matched pipelines' do
+ get api("/projects/#{project.id}/pipelines", user), params: { scope: 'tags' }
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to include_pagination_headers
+ expect(json_response).not_to be_empty
+ expect(json_response.last['id']).to eq(pipeline_tag.id)
+ end
+ end
+ end
+
+ context 'when scope is invalid' do
+ it 'returns bad_request' do
+ get api("/projects/#{project.id}/pipelines", user), params: { scope: 'invalid-scope' }
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ end
+ end
+
+ Ci::HasStatus::AVAILABLE_STATUSES.each do |target|
+ context "when status is #{target}" do
+ before do
+ create(:ci_pipeline, project: project, status: target)
+ exception_status = Ci::HasStatus::AVAILABLE_STATUSES - [target]
+ create(:ci_pipeline, project: project, status: exception_status.sample)
+ end
+
+ it 'returns matched pipelines' do
+ get api("/projects/#{project.id}/pipelines", user), params: { status: target }
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to include_pagination_headers
+ expect(json_response).not_to be_empty
+ json_response.each { |r| expect(r['status']).to eq(target) }
+ end
+ end
+ end
+
+ context 'when status is invalid' do
+ it 'returns bad_request' do
+ get api("/projects/#{project.id}/pipelines", user), params: { status: 'invalid-status' }
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ end
+ end
+
+ context 'when ref is specified' do
+ before do
+ create(:ci_pipeline, project: project)
+ end
+
+ context 'when ref exists' do
+ it 'returns matched pipelines' do
+ get api("/projects/#{project.id}/pipelines", user), params: { ref: 'master' }
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to include_pagination_headers
+ expect(json_response).not_to be_empty
+ json_response.each { |r| expect(r['ref']).to eq('master') }
+ end
+ end
+
+ context 'when ref does not exist' do
+ it 'returns empty' do
+ get api("/projects/#{project.id}/pipelines", user), params: { ref: 'invalid-ref' }
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to include_pagination_headers
+ expect(json_response).to be_empty
+ end
+ end
+ end
+
+ context 'when name is specified' do
+ let_it_be(:pipeline) { create(:ci_pipeline, project: project, user: user) }
+
+ context 'when name exists' do
+ it 'returns matched pipelines' do
+ get api("/projects/#{project.id}/pipelines", user), params: { name: user.name }
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to include_pagination_headers
+ expect(json_response.first['id']).to eq(pipeline.id)
+ end
+ end
+
+ context 'when name does not exist' do
+ it 'returns empty' do
+ get api("/projects/#{project.id}/pipelines", user), params: { name: 'invalid-name' }
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to include_pagination_headers
+ expect(json_response).to be_empty
+ end
+ end
+ end
+
+ context 'when username is specified' do
+ let_it_be(:pipeline) { create(:ci_pipeline, project: project, user: user) }
+
+ context 'when username exists' do
+ it 'returns matched pipelines' do
+ get api("/projects/#{project.id}/pipelines", user), params: { username: user.username }
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to include_pagination_headers
+ expect(json_response.first['id']).to eq(pipeline.id)
+ end
+ end
+
+ context 'when username does not exist' do
+ it 'returns empty' do
+ get api("/projects/#{project.id}/pipelines", user), params: { username: 'invalid-username' }
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to include_pagination_headers
+ expect(json_response).to be_empty
+ end
+ end
+ end
+
+ context 'when yaml_errors is specified' do
+ let_it_be(:pipeline1) { create(:ci_pipeline, project: project, yaml_errors: 'Syntax error') }
+ let_it_be(:pipeline2) { create(:ci_pipeline, project: project) }
+
+ context 'when yaml_errors is true' do
+ it 'returns matched pipelines' do
+ get api("/projects/#{project.id}/pipelines", user), params: { yaml_errors: true }
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to include_pagination_headers
+ expect(json_response.first['id']).to eq(pipeline1.id)
+ end
+ end
+
+ context 'when yaml_errors is false' do
+ it 'returns matched pipelines' do
+ get api("/projects/#{project.id}/pipelines", user), params: { yaml_errors: false }
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to include_pagination_headers
+ expect(json_response.first['id']).to eq(pipeline2.id)
+ end
+ end
+
+ context 'when yaml_errors is invalid' do
+ it 'returns bad_request' do
+ get api("/projects/#{project.id}/pipelines", user), params: { yaml_errors: 'invalid-yaml_errors' }
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ end
+ end
+ end
+
+ context 'when updated_at filters are specified' do
+ let_it_be(:pipeline1) { create(:ci_pipeline, project: project, updated_at: 2.days.ago) }
+ let_it_be(:pipeline2) { create(:ci_pipeline, project: project, updated_at: 4.days.ago) }
+ let_it_be(:pipeline3) { create(:ci_pipeline, project: project, updated_at: 1.hour.ago) }
+
+ it 'returns pipelines with last update date in specified datetime range' do
+ get api("/projects/#{project.id}/pipelines", user), params: { updated_before: 1.day.ago, updated_after: 3.days.ago }
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to include_pagination_headers
+ expect(json_response.first['id']).to eq(pipeline1.id)
+ end
+ end
+
+ context 'when order_by and sort are specified' do
+ context 'when order_by user_id' do
+ before do
+ create_list(:user, 3).each do |some_user|
+ create(:ci_pipeline, project: project, user: some_user)
+ end
+ end
+
+ context 'when sort parameter is valid' do
+ it 'sorts as user_id: :desc' do
+ get api("/projects/#{project.id}/pipelines", user), params: { order_by: 'user_id', sort: 'desc' }
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to include_pagination_headers
+ expect(json_response).not_to be_empty
+
+ pipeline_ids = Ci::Pipeline.all.order(user_id: :desc).pluck(:id)
+ expect(json_response.map { |r| r['id'] }).to eq(pipeline_ids)
+ end
+ end
+
+ context 'when sort parameter is invalid' do
+ it 'returns bad_request' do
+ get api("/projects/#{project.id}/pipelines", user), params: { order_by: 'user_id', sort: 'invalid_sort' }
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ end
+ end
+ end
+
+ context 'when order_by is invalid' do
+ it 'returns bad_request' do
+ get api("/projects/#{project.id}/pipelines", user), params: { order_by: 'lock_version', sort: 'asc' }
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ end
+ end
+ end
+ end
+ end
+
+ context 'unauthorized user' do
+ it 'does not return project pipelines' do
+ get api("/projects/#{project.id}/pipelines", non_member)
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ expect(json_response['message']).to eq '404 Project Not Found'
+ expect(json_response).not_to be_an Array
+ end
+ end
+ end
+
+ describe 'POST /projects/:id/pipeline ' do
+ def expect_variables(variables, expected_variables)
+ variables.each_with_index do |variable, index|
+ expected_variable = expected_variables[index]
+
+ expect(variable.key).to eq(expected_variable['key'])
+ expect(variable.value).to eq(expected_variable['value'])
+ expect(variable.variable_type).to eq(expected_variable['variable_type'])
+ end
+ end
+
+ context 'authorized user' do
+ context 'with gitlab-ci.yml' do
+ before do
+ stub_ci_pipeline_to_return_yaml_file
+ end
+
+ it 'creates and returns a new pipeline' do
+ expect do
+ post api("/projects/#{project.id}/pipeline", user), params: { ref: project.default_branch }
+ end.to change { project.ci_pipelines.count }.by(1)
+
+ expect(response).to have_gitlab_http_status(:created)
+ expect(json_response).to be_a Hash
+ expect(json_response['sha']).to eq project.commit.id
+ end
+
+ context 'variables given' do
+ let(:variables) { [{ 'variable_type' => 'file', 'key' => 'UPLOAD_TO_S3', 'value' => 'true' }] }
+
+ it 'creates and returns a new pipeline using the given variables' do
+ expect do
+ post api("/projects/#{project.id}/pipeline", user), params: { ref: project.default_branch, variables: variables }
+ end.to change { project.ci_pipelines.count }.by(1)
+ expect_variables(project.ci_pipelines.last.variables, variables)
+
+ expect(response).to have_gitlab_http_status(:created)
+ expect(json_response).to be_a Hash
+ expect(json_response['sha']).to eq project.commit.id
+ expect(json_response).not_to have_key('variables')
+ end
+ end
+
+ describe 'using variables conditions' do
+ let(:variables) { [{ 'variable_type' => 'env_var', 'key' => 'STAGING', 'value' => 'true' }] }
+
+ before do
+ config = YAML.dump(test: { script: 'test', only: { variables: ['$STAGING'] } })
+ stub_ci_pipeline_yaml_file(config)
+ end
+
+ it 'creates and returns a new pipeline using the given variables' do
+ expect do
+ post api("/projects/#{project.id}/pipeline", user), params: { ref: project.default_branch, variables: variables }
+ end.to change { project.ci_pipelines.count }.by(1)
+ expect_variables(project.ci_pipelines.last.variables, variables)
+
+ expect(response).to have_gitlab_http_status(:created)
+ expect(json_response).to be_a Hash
+ expect(json_response['sha']).to eq project.commit.id
+ expect(json_response).not_to have_key('variables')
+ end
+
+ context 'condition unmatch' do
+ let(:variables) { [{ 'key' => 'STAGING', 'value' => 'false' }] }
+
+ it "doesn't create a job" do
+ expect do
+ post api("/projects/#{project.id}/pipeline", user), params: { ref: project.default_branch }
+ end.not_to change { project.ci_pipelines.count }
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ end
+ end
+ end
+
+ it 'fails when using an invalid ref' do
+ post api("/projects/#{project.id}/pipeline", user), params: { ref: 'invalid_ref' }
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ expect(json_response['message']['base'].first).to eq 'Reference not found'
+ expect(json_response).not_to be_an Array
+ end
+ end
+
+ context 'without gitlab-ci.yml' do
+ context 'without auto devops enabled' do
+ before do
+ project.update!(auto_devops_attributes: { enabled: false })
+ end
+
+ it 'fails to create pipeline' do
+ post api("/projects/#{project.id}/pipeline", user), params: { ref: project.default_branch }
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ expect(json_response['message']['base'].first).to eq 'Missing CI config file'
+ expect(json_response).not_to be_an Array
+ end
+ end
+ end
+ end
+
+ context 'unauthorized user' do
+ it 'does not create pipeline' do
+ post api("/projects/#{project.id}/pipeline", non_member), params: { ref: project.default_branch }
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ expect(json_response['message']).to eq '404 Project Not Found'
+ expect(json_response).not_to be_an Array
+ end
+ end
+ end
+
+ describe 'GET /projects/:id/pipelines/:pipeline_id' do
+ it_behaves_like 'pipelines visibility table' do
+ let(:pipelines_api_path) do
+ "/projects/#{project.id}/pipelines/#{pipeline.id}"
+ end
+
+ let(:api_response) { response_status == 200 ? response : json_response }
+ let(:response_200) { match_response_schema('public_api/v4/pipeline/detail') }
+ end
+
+ context 'authorized user' do
+ it 'exposes known attributes' do
+ get api("/projects/#{project.id}/pipelines/#{pipeline.id}", user)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to match_response_schema('public_api/v4/pipeline/detail')
+ end
+
+ it 'returns project pipelines' do
+ get api("/projects/#{project.id}/pipelines/#{pipeline.id}", user)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response['sha']).to match(/\A\h{40}\z/)
+ end
+
+ it 'returns 404 when it does not exist' do
+ get api("/projects/#{project.id}/pipelines/#{non_existing_record_id}", user)
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ expect(json_response['message']).to eq '404 Not found'
+ expect(json_response['id']).to be nil
+ end
+
+ context 'with coverage' do
+ before do
+ create(:ci_build, coverage: 30, pipeline: pipeline)
+ end
+
+ it 'exposes the coverage' do
+ get api("/projects/#{project.id}/pipelines/#{pipeline.id}", user)
+
+ expect(json_response["coverage"].to_i).to eq(30)
+ end
+ end
+ end
+
+ context 'unauthorized user' do
+ it 'does not return a project pipeline' do
+ get api("/projects/#{project.id}/pipelines/#{pipeline.id}", non_member)
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ expect(json_response['message']).to eq '404 Project Not Found'
+ expect(json_response['id']).to be nil
+ end
+ end
+ end
+
+ describe 'GET /projects/:id/pipelines/latest' do
+ context 'authorized user' do
+ let(:second_branch) { project.repository.branches[2] }
+
+ let!(:second_pipeline) do
+ create(:ci_empty_pipeline, project: project, sha: second_branch.target,
+ ref: second_branch.name, user: user)
+ end
+
+ before do
+ create(:ci_empty_pipeline, project: project, sha: project.commit.parent.id,
+ ref: project.default_branch, user: user)
+ end
+
+ context 'default repository branch' do
+ it 'gets the latest pipleine' do
+ get api("/projects/#{project.id}/pipelines/latest", user)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to match_response_schema('public_api/v4/pipeline/detail')
+ expect(json_response['ref']).to eq(project.default_branch)
+ expect(json_response['sha']).to eq(project.commit.id)
+ end
+ end
+
+ context 'ref parameter' do
+ it 'gets the latest pipleine' do
+ get api("/projects/#{project.id}/pipelines/latest", user), params: { ref: second_branch.name }
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to match_response_schema('public_api/v4/pipeline/detail')
+ expect(json_response['ref']).to eq(second_branch.name)
+ expect(json_response['sha']).to eq(second_branch.target)
+ end
+ end
+ end
+
+ context 'unauthorized user' do
+ it 'does not return a project pipeline' do
+ get api("/projects/#{project.id}/pipelines/#{pipeline.id}", non_member)
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ expect(json_response['message']).to eq '404 Project Not Found'
+ expect(json_response['id']).to be nil
+ end
+ end
+ end
+
+ describe 'GET /projects/:id/pipelines/:pipeline_id/variables' do
+ subject { get api("/projects/#{project.id}/pipelines/#{pipeline.id}/variables", api_user) }
+
+ let(:api_user) { user }
+
+ context 'user is a mantainer' do
+ it 'returns pipeline variables empty' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response).to be_empty
+ end
+
+ context 'with variables' do
+ let!(:variable) { create(:ci_pipeline_variable, pipeline: pipeline, key: 'foo', value: 'bar') }
+
+ it 'returns pipeline variables' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response).to contain_exactly({ "variable_type" => "env_var", "key" => "foo", "value" => "bar" })
+ end
+ end
+ end
+
+ context 'user is a developer' do
+ let(:pipeline_owner_user) { create(:user) }
+ let(:pipeline) { create(:ci_empty_pipeline, project: project, user: pipeline_owner_user) }
+
+ before do
+ project.add_developer(api_user)
+ end
+
+ context 'pipeline created by the developer user' do
+ let(:api_user) { pipeline_owner_user }
+ let!(:variable) { create(:ci_pipeline_variable, pipeline: pipeline, key: 'foo', value: 'bar') }
+
+ it 'returns pipeline variables' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response).to contain_exactly({ "variable_type" => "env_var", "key" => "foo", "value" => "bar" })
+ end
+ end
+
+ context 'pipeline created is not created by the developer user' do
+ let(:api_user) { create(:user) }
+
+ it 'does not return pipeline variables' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+ end
+ end
+
+ context 'user is not a project member' do
+ it 'does not return pipeline variables' do
+ get api("/projects/#{project.id}/pipelines/#{pipeline.id}/variables", non_member)
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ expect(json_response['message']).to eq '404 Project Not Found'
+ end
+ end
+ end
+
+ describe 'DELETE /projects/:id/pipelines/:pipeline_id' do
+ context 'authorized user' do
+ let(:owner) { project.owner }
+
+ it 'destroys the pipeline' do
+ delete api("/projects/#{project.id}/pipelines/#{pipeline.id}", owner)
+
+ expect(response).to have_gitlab_http_status(:no_content)
+ expect { pipeline.reload }.to raise_error(ActiveRecord::RecordNotFound)
+ end
+
+ it 'returns 404 when it does not exist' do
+ delete api("/projects/#{project.id}/pipelines/#{non_existing_record_id}", owner)
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ expect(json_response['message']).to eq '404 Not found'
+ end
+
+ it 'does not log an audit event' do
+ expect { delete api("/projects/#{project.id}/pipelines/#{pipeline.id}", owner) }.not_to change { SecurityEvent.count }
+ end
+
+ context 'when the pipeline has jobs' do
+ let_it_be(:build) { create(:ci_build, project: project, pipeline: pipeline) }
+
+ it 'destroys associated jobs' do
+ delete api("/projects/#{project.id}/pipelines/#{pipeline.id}", owner)
+
+ expect(response).to have_gitlab_http_status(:no_content)
+ expect { build.reload }.to raise_error(ActiveRecord::RecordNotFound)
+ end
+ end
+ end
+
+ context 'unauthorized user' do
+ context 'when user is not member' do
+ it 'returns a 404' do
+ delete api("/projects/#{project.id}/pipelines/#{pipeline.id}", non_member)
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ expect(json_response['message']).to eq '404 Project Not Found'
+ end
+ end
+
+ context 'when user is developer' do
+ let(:developer) { create(:user) }
+
+ before do
+ project.add_developer(developer)
+ end
+
+ it 'returns a 403' do
+ delete api("/projects/#{project.id}/pipelines/#{pipeline.id}", developer)
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ expect(json_response['message']).to eq '403 Forbidden'
+ end
+ end
+ end
+ end
+
+ describe 'POST /projects/:id/pipelines/:pipeline_id/retry' do
+ context 'authorized user' do
+ let_it_be(:pipeline) do
+ create(:ci_pipeline, project: project, sha: project.commit.id,
+ ref: project.default_branch)
+ end
+
+ let_it_be(:build) { create(:ci_build, :failed, pipeline: pipeline) }
+
+ it 'retries failed builds' do
+ expect do
+ post api("/projects/#{project.id}/pipelines/#{pipeline.id}/retry", user)
+ end.to change { pipeline.builds.count }.from(1).to(2)
+
+ expect(response).to have_gitlab_http_status(:created)
+ expect(build.reload.retried?).to be true
+ end
+ end
+
+ context 'unauthorized user' do
+ it 'does not return a project pipeline' do
+ post api("/projects/#{project.id}/pipelines/#{pipeline.id}/retry", non_member)
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ expect(json_response['message']).to eq '404 Project Not Found'
+ expect(json_response['id']).to be nil
+ end
+ end
+ end
+
+ describe 'POST /projects/:id/pipelines/:pipeline_id/cancel' do
+ let_it_be(:pipeline) do
+ create(:ci_empty_pipeline, project: project, sha: project.commit.id,
+ ref: project.default_branch)
+ end
+
+ let_it_be(:build) { create(:ci_build, :running, pipeline: pipeline) }
+
+ context 'authorized user' do
+ it 'retries failed builds', :sidekiq_might_not_need_inline do
+ post api("/projects/#{project.id}/pipelines/#{pipeline.id}/cancel", user)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response['status']).to eq('canceled')
+ end
+ end
+
+ context 'user without proper access rights' do
+ let_it_be(:reporter) { create(:user) }
+
+ before do
+ project.add_reporter(reporter)
+ end
+
+ it 'rejects the action' do
+ post api("/projects/#{project.id}/pipelines/#{pipeline.id}/cancel", reporter)
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ expect(pipeline.reload.status).to eq('pending')
+ end
+ end
+ end
+
+ describe 'GET /projects/:id/pipelines/:pipeline_id/test_report' do
+ context 'authorized user' do
+ subject { get api("/projects/#{project.id}/pipelines/#{pipeline.id}/test_report", user) }
+
+ let(:pipeline) { create(:ci_pipeline, project: project) }
+
+ context 'when feature is enabled' do
+ before do
+ stub_feature_flags(junit_pipeline_view: true)
+ end
+
+ context 'when pipeline does not have a test report' do
+ it 'returns an empty test report' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response['total_count']).to eq(0)
+ end
+ end
+
+ context 'when pipeline has a test report' do
+ let(:pipeline) { create(:ci_pipeline, :with_test_reports, project: project) }
+
+ it 'returns the test report' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response['total_count']).to eq(4)
+ end
+ end
+
+ context 'when pipeline has corrupt test reports' do
+ before do
+ job = create(:ci_build, pipeline: pipeline)
+ create(:ci_job_artifact, :junit_with_corrupted_data, job: job, project: project)
+ end
+
+ it 'returns a suite_error' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response['test_suites'].first['suite_error']).to eq('JUnit XML parsing failed: 1:1: FATAL: Document is empty')
+ end
+ end
+ end
+
+ context 'when feature is disabled' do
+ before do
+ stub_feature_flags(junit_pipeline_view: false)
+ end
+
+ it 'renders empty response' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+ end
+
+ context 'unauthorized user' do
+ it 'does not return project pipelines' do
+ get api("/projects/#{project.id}/pipelines/#{pipeline.id}/test_report", non_member)
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ expect(json_response['message']).to eq '404 Project Not Found'
+ end
+ end
+ end
+end
diff --git a/spec/requests/api/ci/runner_spec.rb b/spec/requests/api/ci/runner_spec.rb
new file mode 100644
index 00000000000..c8718309bf2
--- /dev/null
+++ b/spec/requests/api/ci/runner_spec.rb
@@ -0,0 +1,2474 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state do
+ include StubGitlabCalls
+ include RedisHelpers
+ include WorkhorseHelpers
+
+ let(:registration_token) { 'abcdefg123456' }
+
+ before do
+ stub_feature_flags(ci_enable_live_trace: true)
+ stub_gitlab_calls
+ stub_application_setting(runners_registration_token: registration_token)
+ allow_any_instance_of(::Ci::Runner).to receive(:cache_attributes)
+ end
+
+ describe '/api/v4/runners' do
+ describe 'POST /api/v4/runners' do
+ context 'when no token is provided' do
+ it 'returns 400 error' do
+ post api('/runners')
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ end
+ end
+
+ context 'when invalid token is provided' do
+ it 'returns 403 error' do
+ post api('/runners'), params: { token: 'invalid' }
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+ end
+
+ context 'when valid token is provided' do
+ it 'creates runner with default values' do
+ post api('/runners'), params: { token: registration_token }
+
+ runner = ::Ci::Runner.first
+
+ expect(response).to have_gitlab_http_status(:created)
+ expect(json_response['id']).to eq(runner.id)
+ expect(json_response['token']).to eq(runner.token)
+ expect(runner.run_untagged).to be true
+ expect(runner.active).to be true
+ expect(runner.token).not_to eq(registration_token)
+ expect(runner).to be_instance_type
+ end
+
+ context 'when project token is used' do
+ let(:project) { create(:project) }
+
+ it 'creates project runner' do
+ post api('/runners'), params: { token: project.runners_token }
+
+ expect(response).to have_gitlab_http_status(:created)
+ expect(project.runners.size).to eq(1)
+ runner = ::Ci::Runner.first
+ expect(runner.token).not_to eq(registration_token)
+ expect(runner.token).not_to eq(project.runners_token)
+ expect(runner).to be_project_type
+ end
+ end
+
+ context 'when group token is used' do
+ let(:group) { create(:group) }
+
+ it 'creates a group runner' do
+ post api('/runners'), params: { token: group.runners_token }
+
+ expect(response).to have_gitlab_http_status(:created)
+ expect(group.runners.reload.size).to eq(1)
+ runner = ::Ci::Runner.first
+ expect(runner.token).not_to eq(registration_token)
+ expect(runner.token).not_to eq(group.runners_token)
+ expect(runner).to be_group_type
+ end
+ end
+ end
+
+ context 'when runner description is provided' do
+ it 'creates runner' do
+ post api('/runners'), params: {
+ token: registration_token,
+ description: 'server.hostname'
+ }
+
+ expect(response).to have_gitlab_http_status(:created)
+ expect(::Ci::Runner.first.description).to eq('server.hostname')
+ end
+ end
+
+ context 'when runner tags are provided' do
+ it 'creates runner' do
+ post api('/runners'), params: {
+ token: registration_token,
+ tag_list: 'tag1, tag2'
+ }
+
+ expect(response).to have_gitlab_http_status(:created)
+ expect(::Ci::Runner.first.tag_list.sort).to eq(%w(tag1 tag2))
+ end
+ end
+
+ context 'when option for running untagged jobs is provided' do
+ context 'when tags are provided' do
+ it 'creates runner' do
+ post api('/runners'), params: {
+ token: registration_token,
+ run_untagged: false,
+ tag_list: ['tag']
+ }
+
+ expect(response).to have_gitlab_http_status(:created)
+ expect(::Ci::Runner.first.run_untagged).to be false
+ expect(::Ci::Runner.first.tag_list.sort).to eq(['tag'])
+ end
+ end
+
+ context 'when tags are not provided' do
+ it 'returns 400 error' do
+ post api('/runners'), params: {
+ token: registration_token,
+ run_untagged: false
+ }
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ expect(json_response['message']).to include(
+ 'tags_list' => ['can not be empty when runner is not allowed to pick untagged jobs'])
+ end
+ end
+ end
+
+ context 'when option for locking Runner is provided' do
+ it 'creates runner' do
+ post api('/runners'), params: {
+ token: registration_token,
+ locked: true
+ }
+
+ expect(response).to have_gitlab_http_status(:created)
+ expect(::Ci::Runner.first.locked).to be true
+ end
+ end
+
+ context 'when option for activating a Runner is provided' do
+ context 'when active is set to true' do
+ it 'creates runner' do
+ post api('/runners'), params: {
+ token: registration_token,
+ active: true
+ }
+
+ expect(response).to have_gitlab_http_status(:created)
+ expect(::Ci::Runner.first.active).to be true
+ end
+ end
+
+ context 'when active is set to false' do
+ it 'creates runner' do
+ post api('/runners'), params: {
+ token: registration_token,
+ active: false
+ }
+
+ expect(response).to have_gitlab_http_status(:created)
+ expect(::Ci::Runner.first.active).to be false
+ end
+ end
+ end
+
+ context 'when access_level is provided for Runner' do
+ context 'when access_level is set to ref_protected' do
+ it 'creates runner' do
+ post api('/runners'), params: {
+ token: registration_token,
+ access_level: 'ref_protected'
+ }
+
+ expect(response).to have_gitlab_http_status(:created)
+ expect(::Ci::Runner.first.ref_protected?).to be true
+ end
+ end
+
+ context 'when access_level is set to not_protected' do
+ it 'creates runner' do
+ post api('/runners'), params: {
+ token: registration_token,
+ access_level: 'not_protected'
+ }
+
+ expect(response).to have_gitlab_http_status(:created)
+ expect(::Ci::Runner.first.ref_protected?).to be false
+ end
+ end
+ end
+
+ context 'when maximum job timeout is specified' do
+ it 'creates runner' do
+ post api('/runners'), params: {
+ token: registration_token,
+ maximum_timeout: 9000
+ }
+
+ expect(response).to have_gitlab_http_status(:created)
+ expect(::Ci::Runner.first.maximum_timeout).to eq(9000)
+ end
+
+ context 'when maximum job timeout is empty' do
+ it 'creates runner' do
+ post api('/runners'), params: {
+ token: registration_token,
+ maximum_timeout: ''
+ }
+
+ expect(response).to have_gitlab_http_status(:created)
+ expect(::Ci::Runner.first.maximum_timeout).to be_nil
+ end
+ end
+ end
+
+ %w(name version revision platform architecture).each do |param|
+ context "when info parameter '#{param}' info is present" do
+ let(:value) { "#{param}_value" }
+
+ it "updates provided Runner's parameter" do
+ post api('/runners'), params: {
+ token: registration_token,
+ info: { param => value }
+ }
+
+ expect(response).to have_gitlab_http_status(:created)
+ expect(::Ci::Runner.first.read_attribute(param.to_sym)).to eq(value)
+ end
+ end
+ end
+
+ it "sets the runner's ip_address" do
+ post api('/runners'),
+ params: { token: registration_token },
+ headers: { 'X-Forwarded-For' => '123.111.123.111' }
+
+ expect(response).to have_gitlab_http_status(:created)
+ expect(::Ci::Runner.first.ip_address).to eq('123.111.123.111')
+ end
+ end
+
+ describe 'DELETE /api/v4/runners' do
+ context 'when no token is provided' do
+ it 'returns 400 error' do
+ delete api('/runners')
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ end
+ end
+
+ context 'when invalid token is provided' do
+ it 'returns 403 error' do
+ delete api('/runners'), params: { token: 'invalid' }
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+ end
+
+ context 'when valid token is provided' do
+ let(:runner) { create(:ci_runner) }
+
+ it 'deletes Runner' do
+ delete api('/runners'), params: { token: runner.token }
+
+ expect(response).to have_gitlab_http_status(:no_content)
+ expect(::Ci::Runner.count).to eq(0)
+ end
+
+ it_behaves_like '412 response' do
+ let(:request) { api('/runners') }
+ let(:params) { { token: runner.token } }
+ end
+ end
+ end
+
+ describe 'POST /api/v4/runners/verify' do
+ let(:runner) { create(:ci_runner) }
+
+ context 'when no token is provided' do
+ it 'returns 400 error' do
+ post api('/runners/verify')
+
+ expect(response).to have_gitlab_http_status :bad_request
+ end
+ end
+
+ context 'when invalid token is provided' do
+ it 'returns 403 error' do
+ post api('/runners/verify'), params: { token: 'invalid-token' }
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+ end
+
+ context 'when valid token is provided' do
+ it 'verifies Runner credentials' do
+ post api('/runners/verify'), params: { token: runner.token }
+
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+ end
+ end
+ end
+
+ describe '/api/v4/jobs' do
+ shared_examples 'application context metadata' do |api_route|
+ it 'contains correct context metadata' do
+ # Avoids popping the context from the thread so we can
+ # check its content after the request.
+ allow(Labkit::Context).to receive(:pop)
+
+ send_request
+
+ Labkit::Context.with_context do |context|
+ expected_context = {
+ 'meta.caller_id' => api_route,
+ 'meta.user' => job.user.username,
+ 'meta.project' => job.project.full_path,
+ 'meta.root_namespace' => job.project.full_path_components.first
+ }
+
+ expect(context.to_h).to include(expected_context)
+ end
+ end
+ end
+
+ let(:root_namespace) { create(:namespace) }
+ let(:namespace) { create(:namespace, parent: root_namespace) }
+ let(:project) { create(:project, namespace: namespace, shared_runners_enabled: false) }
+ let(:pipeline) { create(:ci_pipeline, project: project, ref: 'master') }
+ let(:runner) { create(:ci_runner, :project, projects: [project]) }
+ let(:user) { create(:user) }
+ let(:job) do
+ create(:ci_build, :artifacts, :extended_options,
+ pipeline: pipeline, name: 'spinach', stage: 'test', stage_idx: 0)
+ end
+
+ describe 'POST /api/v4/jobs/request' do
+ let!(:last_update) {}
+ let!(:new_update) { }
+ let(:user_agent) { 'gitlab-runner 9.0.0 (9-0-stable; go1.7.4; linux/amd64)' }
+
+ before do
+ job
+ stub_container_registry_config(enabled: false)
+ end
+
+ shared_examples 'no jobs available' do
+ before do
+ request_job
+ end
+
+ context 'when runner sends version in User-Agent' do
+ context 'for stable version' do
+ it 'gives 204 and set X-GitLab-Last-Update' do
+ expect(response).to have_gitlab_http_status(:no_content)
+ expect(response.header).to have_key('X-GitLab-Last-Update')
+ end
+ end
+
+ context 'when last_update is up-to-date' do
+ let(:last_update) { runner.ensure_runner_queue_value }
+
+ it 'gives 204 and set the same X-GitLab-Last-Update' do
+ expect(response).to have_gitlab_http_status(:no_content)
+ expect(response.header['X-GitLab-Last-Update']).to eq(last_update)
+ end
+ end
+
+ context 'when last_update is outdated' do
+ let(:last_update) { runner.ensure_runner_queue_value }
+ let(:new_update) { runner.tick_runner_queue }
+
+ it 'gives 204 and set a new X-GitLab-Last-Update' do
+ expect(response).to have_gitlab_http_status(:no_content)
+ expect(response.header['X-GitLab-Last-Update']).to eq(new_update)
+ end
+ end
+
+ context 'when beta version is sent' do
+ let(:user_agent) { 'gitlab-runner 9.0.0~beta.167.g2b2bacc (master; go1.7.4; linux/amd64)' }
+
+ it { expect(response).to have_gitlab_http_status(:no_content) }
+ end
+
+ context 'when pre-9-0 version is sent' do
+ let(:user_agent) { 'gitlab-ci-multi-runner 1.6.0 (1-6-stable; go1.6.3; linux/amd64)' }
+
+ it { expect(response).to have_gitlab_http_status(:no_content) }
+ end
+
+ context 'when pre-9-0 beta version is sent' do
+ let(:user_agent) { 'gitlab-ci-multi-runner 1.6.0~beta.167.g2b2bacc (master; go1.6.3; linux/amd64)' }
+
+ it { expect(response).to have_gitlab_http_status(:no_content) }
+ end
+ end
+ end
+
+ context 'when no token is provided' do
+ it 'returns 400 error' do
+ post api('/jobs/request')
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ end
+ end
+
+ context 'when invalid token is provided' do
+ it 'returns 403 error' do
+ post api('/jobs/request'), params: { token: 'invalid' }
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+ end
+
+ context 'when valid token is provided' do
+ context 'when Runner is not active' do
+ let(:runner) { create(:ci_runner, :inactive) }
+ let(:update_value) { runner.ensure_runner_queue_value }
+
+ it 'returns 204 error' do
+ request_job
+
+ expect(response).to have_gitlab_http_status(:no_content)
+ expect(response.header['X-GitLab-Last-Update']).to eq(update_value)
+ end
+ end
+
+ context 'when jobs are finished' do
+ before do
+ job.success
+ end
+
+ it_behaves_like 'no jobs available'
+ end
+
+ context 'when other projects have pending jobs' do
+ before do
+ job.success
+ create(:ci_build, :pending)
+ end
+
+ it_behaves_like 'no jobs available'
+ end
+
+ context 'when shared runner requests job for project without shared_runners_enabled' do
+ let(:runner) { create(:ci_runner, :instance) }
+
+ it_behaves_like 'no jobs available'
+ end
+
+ context 'when there is a pending job' do
+ let(:expected_job_info) do
+ { 'name' => job.name,
+ 'stage' => job.stage,
+ 'project_id' => job.project.id,
+ 'project_name' => job.project.name }
+ end
+
+ let(:expected_git_info) do
+ { 'repo_url' => job.repo_url,
+ 'ref' => job.ref,
+ 'sha' => job.sha,
+ 'before_sha' => job.before_sha,
+ 'ref_type' => 'branch',
+ 'refspecs' => ["+refs/pipelines/#{pipeline.id}:refs/pipelines/#{pipeline.id}",
+ "+refs/heads/#{job.ref}:refs/remotes/origin/#{job.ref}"],
+ 'depth' => project.ci_default_git_depth }
+ end
+
+ let(:expected_steps) do
+ [{ 'name' => 'script',
+ 'script' => %w(echo),
+ 'timeout' => job.metadata_timeout,
+ 'when' => 'on_success',
+ 'allow_failure' => false },
+ { 'name' => 'after_script',
+ 'script' => %w(ls date),
+ 'timeout' => job.metadata_timeout,
+ 'when' => 'always',
+ 'allow_failure' => true }]
+ end
+
+ let(:expected_variables) do
+ [{ 'key' => 'CI_JOB_NAME', 'value' => 'spinach', 'public' => true, 'masked' => false },
+ { 'key' => 'CI_JOB_STAGE', 'value' => 'test', 'public' => true, 'masked' => false },
+ { 'key' => 'DB_NAME', 'value' => 'postgres', 'public' => true, 'masked' => false }]
+ end
+
+ let(:expected_artifacts) do
+ [{ 'name' => 'artifacts_file',
+ 'untracked' => false,
+ 'paths' => %w(out/),
+ 'when' => 'always',
+ 'expire_in' => '7d',
+ "artifact_type" => "archive",
+ "artifact_format" => "zip" }]
+ end
+
+ let(:expected_cache) do
+ [{ 'key' => 'cache_key',
+ 'untracked' => false,
+ 'paths' => ['vendor/*'],
+ 'policy' => 'pull-push' }]
+ end
+
+ let(:expected_features) { { 'trace_sections' => true } }
+
+ it 'picks a job' do
+ request_job info: { platform: :darwin }
+
+ expect(response).to have_gitlab_http_status(:created)
+ expect(response.headers['Content-Type']).to eq('application/json')
+ expect(response.headers).not_to have_key('X-GitLab-Last-Update')
+ expect(runner.reload.platform).to eq('darwin')
+ expect(json_response['id']).to eq(job.id)
+ expect(json_response['token']).to eq(job.token)
+ expect(json_response['job_info']).to eq(expected_job_info)
+ expect(json_response['git_info']).to eq(expected_git_info)
+ expect(json_response['image']).to eq({ 'name' => 'ruby:2.7', 'entrypoint' => '/bin/sh', 'ports' => [] })
+ expect(json_response['services']).to eq([{ 'name' => 'postgres', 'entrypoint' => nil,
+ 'alias' => nil, 'command' => nil, 'ports' => [] },
+ { 'name' => 'docker:stable-dind', 'entrypoint' => '/bin/sh',
+ 'alias' => 'docker', 'command' => 'sleep 30', 'ports' => [] }])
+ expect(json_response['steps']).to eq(expected_steps)
+ expect(json_response['artifacts']).to eq(expected_artifacts)
+ expect(json_response['cache']).to eq(expected_cache)
+ expect(json_response['variables']).to include(*expected_variables)
+ expect(json_response['features']).to eq(expected_features)
+ end
+
+ it 'creates persistent ref' do
+ expect_any_instance_of(::Ci::PersistentRef).to receive(:create_ref)
+ .with(job.sha, "refs/#{Repository::REF_PIPELINES}/#{job.commit_id}")
+
+ request_job info: { platform: :darwin }
+
+ expect(response).to have_gitlab_http_status(:created)
+ expect(json_response['id']).to eq(job.id)
+ end
+
+ context 'when job is made for tag' do
+ let!(:job) { create(:ci_build, :tag, pipeline: pipeline, name: 'spinach', stage: 'test', stage_idx: 0) }
+
+ it 'sets branch as ref_type' do
+ request_job
+
+ expect(response).to have_gitlab_http_status(:created)
+ expect(json_response['git_info']['ref_type']).to eq('tag')
+ end
+
+ context 'when GIT_DEPTH is specified' do
+ before do
+ create(:ci_pipeline_variable, key: 'GIT_DEPTH', value: 1, pipeline: pipeline)
+ end
+
+ it 'specifies refspecs' do
+ request_job
+
+ expect(response).to have_gitlab_http_status(:created)
+ expect(json_response['git_info']['refspecs']).to include("+refs/tags/#{job.ref}:refs/tags/#{job.ref}")
+ end
+ end
+
+ context 'when a Gitaly exception is thrown during response' do
+ before do
+ allow_next_instance_of(Ci::BuildRunnerPresenter) do |instance|
+ allow(instance).to receive(:artifacts).and_raise(GRPC::DeadlineExceeded)
+ end
+ end
+
+ it 'fails the job as a scheduler failure' do
+ request_job
+
+ expect(response).to have_gitlab_http_status(:no_content)
+ expect(job.reload.failed?).to be_truthy
+ expect(job.failure_reason).to eq('scheduler_failure')
+ expect(job.runner_id).to eq(runner.id)
+ expect(job.runner_session).to be_nil
+ end
+ end
+
+ context 'when GIT_DEPTH is not specified and there is no default git depth for the project' do
+ before do
+ project.update!(ci_default_git_depth: nil)
+ end
+
+ it 'specifies refspecs' do
+ request_job
+
+ expect(response).to have_gitlab_http_status(:created)
+ expect(json_response['git_info']['refspecs'])
+ .to contain_exactly("+refs/pipelines/#{pipeline.id}:refs/pipelines/#{pipeline.id}",
+ '+refs/tags/*:refs/tags/*',
+ '+refs/heads/*:refs/remotes/origin/*')
+ end
+ end
+ end
+
+ context 'when job filtered by job_age' do
+ let!(:job) { create(:ci_build, :tag, pipeline: pipeline, name: 'spinach', stage: 'test', stage_idx: 0, queued_at: 60.seconds.ago) }
+
+ context 'job is queued less than job_age parameter' do
+ let(:job_age) { 120 }
+
+ it 'gives 204' do
+ request_job(job_age: job_age)
+
+ expect(response).to have_gitlab_http_status(:no_content)
+ end
+ end
+
+ context 'job is queued more than job_age parameter' do
+ let(:job_age) { 30 }
+
+ it 'picks a job' do
+ request_job(job_age: job_age)
+
+ expect(response).to have_gitlab_http_status(:created)
+ end
+ end
+ end
+
+ context 'when job is made for branch' do
+ it 'sets tag as ref_type' do
+ request_job
+
+ expect(response).to have_gitlab_http_status(:created)
+ expect(json_response['git_info']['ref_type']).to eq('branch')
+ end
+
+ context 'when GIT_DEPTH is specified' do
+ before do
+ create(:ci_pipeline_variable, key: 'GIT_DEPTH', value: 1, pipeline: pipeline)
+ end
+
+ it 'specifies refspecs' do
+ request_job
+
+ expect(response).to have_gitlab_http_status(:created)
+ expect(json_response['git_info']['refspecs']).to include("+refs/heads/#{job.ref}:refs/remotes/origin/#{job.ref}")
+ end
+ end
+
+ context 'when GIT_DEPTH is not specified and there is no default git depth for the project' do
+ before do
+ project.update!(ci_default_git_depth: nil)
+ end
+
+ it 'specifies refspecs' do
+ request_job
+
+ expect(response).to have_gitlab_http_status(:created)
+ expect(json_response['git_info']['refspecs'])
+ .to contain_exactly("+refs/pipelines/#{pipeline.id}:refs/pipelines/#{pipeline.id}",
+ '+refs/tags/*:refs/tags/*',
+ '+refs/heads/*:refs/remotes/origin/*')
+ end
+ end
+ end
+
+ context 'when job is for a release' do
+ let!(:job) { create(:ci_build, :release_options, pipeline: pipeline) }
+
+ context 'when `multi_build_steps` is passed by the runner' do
+ it 'exposes release info' do
+ request_job info: { features: { multi_build_steps: true } }
+
+ expect(response).to have_gitlab_http_status(:created)
+ expect(response.headers).not_to have_key('X-GitLab-Last-Update')
+ expect(json_response['steps']).to eq([
+ {
+ "name" => "script",
+ "script" => ["make changelog | tee release_changelog.txt"],
+ "timeout" => 3600,
+ "when" => "on_success",
+ "allow_failure" => false
+ },
+ {
+ "name" => "release",
+ "script" =>
+ ["release-cli create --name \"Release $CI_COMMIT_SHA\" --description \"Created using the release-cli $EXTRA_DESCRIPTION\" --tag-name \"release-$CI_COMMIT_SHA\" --ref \"$CI_COMMIT_SHA\""],
+ "timeout" => 3600,
+ "when" => "on_success",
+ "allow_failure" => false
+ }
+ ])
+ end
+ end
+
+ context 'when `multi_build_steps` is not passed by the runner' do
+ it 'drops the job' do
+ request_job
+
+ expect(response).to have_gitlab_http_status(:no_content)
+ end
+ end
+ end
+
+ context 'when job is made for merge request' do
+ let(:pipeline) { create(:ci_pipeline, source: :merge_request_event, project: project, ref: 'feature', merge_request: merge_request) }
+ let!(:job) { create(:ci_build, pipeline: pipeline, name: 'spinach', ref: 'feature', stage: 'test', stage_idx: 0) }
+ let(:merge_request) { create(:merge_request) }
+
+ it 'sets branch as ref_type' do
+ request_job
+
+ expect(response).to have_gitlab_http_status(:created)
+ expect(json_response['git_info']['ref_type']).to eq('branch')
+ end
+
+ context 'when GIT_DEPTH is specified' do
+ before do
+ create(:ci_pipeline_variable, key: 'GIT_DEPTH', value: 1, pipeline: pipeline)
+ end
+
+ it 'returns the overwritten git depth for merge request refspecs' do
+ request_job
+
+ expect(response).to have_gitlab_http_status(:created)
+ expect(json_response['git_info']['depth']).to eq(1)
+ end
+ end
+ end
+
+ it 'updates runner info' do
+ expect { request_job }.to change { runner.reload.contacted_at }
+ end
+
+ %w(version revision platform architecture).each do |param|
+ context "when info parameter '#{param}' is present" do
+ let(:value) { "#{param}_value" }
+
+ it "updates provided Runner's parameter" do
+ request_job info: { param => value }
+
+ expect(response).to have_gitlab_http_status(:created)
+ expect(runner.reload.read_attribute(param.to_sym)).to eq(value)
+ end
+ end
+ end
+
+ it "sets the runner's ip_address" do
+ post api('/jobs/request'),
+ params: { token: runner.token },
+ headers: { 'User-Agent' => user_agent, 'X-Forwarded-For' => '123.222.123.222' }
+
+ expect(response).to have_gitlab_http_status(:created)
+ expect(runner.reload.ip_address).to eq('123.222.123.222')
+ end
+
+ it "handles multiple X-Forwarded-For addresses" do
+ post api('/jobs/request'),
+ params: { token: runner.token },
+ headers: { 'User-Agent' => user_agent, 'X-Forwarded-For' => '123.222.123.222, 127.0.0.1' }
+
+ expect(response).to have_gitlab_http_status(:created)
+ expect(runner.reload.ip_address).to eq('123.222.123.222')
+ end
+
+ context 'when concurrently updating a job' do
+ before do
+ expect_any_instance_of(::Ci::Build).to receive(:run!)
+ .and_raise(ActiveRecord::StaleObjectError.new(nil, nil))
+ end
+
+ it 'returns a conflict' do
+ request_job
+
+ expect(response).to have_gitlab_http_status(:conflict)
+ expect(response.headers).not_to have_key('X-GitLab-Last-Update')
+ end
+ end
+
+ context 'when project and pipeline have multiple jobs' do
+ let!(:job) { create(:ci_build, :tag, pipeline: pipeline, name: 'spinach', stage: 'test', stage_idx: 0) }
+ let!(:job2) { create(:ci_build, :tag, pipeline: pipeline, name: 'rubocop', stage: 'test', stage_idx: 0) }
+ let!(:test_job) { create(:ci_build, pipeline: pipeline, name: 'deploy', stage: 'deploy', stage_idx: 1) }
+
+ before do
+ job.success
+ job2.success
+ end
+
+ it 'returns dependent jobs' do
+ request_job
+
+ expect(response).to have_gitlab_http_status(:created)
+ expect(json_response['id']).to eq(test_job.id)
+ expect(json_response['dependencies'].count).to eq(2)
+ expect(json_response['dependencies']).to include(
+ { 'id' => job.id, 'name' => job.name, 'token' => job.token },
+ { 'id' => job2.id, 'name' => job2.name, 'token' => job2.token })
+ end
+ end
+
+ context 'when pipeline have jobs with artifacts' do
+ let!(:job) { create(:ci_build, :tag, :artifacts, pipeline: pipeline, name: 'spinach', stage: 'test', stage_idx: 0) }
+ let!(:test_job) { create(:ci_build, pipeline: pipeline, name: 'deploy', stage: 'deploy', stage_idx: 1) }
+
+ before do
+ job.success
+ end
+
+ it 'returns dependent jobs' do
+ request_job
+
+ expect(response).to have_gitlab_http_status(:created)
+ expect(json_response['id']).to eq(test_job.id)
+ expect(json_response['dependencies'].count).to eq(1)
+ expect(json_response['dependencies']).to include(
+ { 'id' => job.id, 'name' => job.name, 'token' => job.token,
+ 'artifacts_file' => { 'filename' => 'ci_build_artifacts.zip', 'size' => 107464 } })
+ end
+ end
+
+ context 'when explicit dependencies are defined' do
+ let!(:job) { create(:ci_build, :tag, pipeline: pipeline, name: 'spinach', stage: 'test', stage_idx: 0) }
+ let!(:job2) { create(:ci_build, :tag, pipeline: pipeline, name: 'rubocop', stage: 'test', stage_idx: 0) }
+ let!(:test_job) do
+ create(:ci_build, pipeline: pipeline, token: 'test-job-token', name: 'deploy',
+ stage: 'deploy', stage_idx: 1,
+ options: { script: ['bash'], dependencies: [job2.name] })
+ end
+
+ before do
+ job.success
+ job2.success
+ end
+
+ it 'returns dependent jobs' do
+ request_job
+
+ expect(response).to have_gitlab_http_status(:created)
+ expect(json_response['id']).to eq(test_job.id)
+ expect(json_response['dependencies'].count).to eq(1)
+ expect(json_response['dependencies'][0]).to include('id' => job2.id, 'name' => job2.name, 'token' => job2.token)
+ end
+ end
+
+ context 'when dependencies is an empty array' do
+ let!(:job) { create(:ci_build, :tag, pipeline: pipeline, name: 'spinach', stage: 'test', stage_idx: 0) }
+ let!(:job2) { create(:ci_build, :tag, pipeline: pipeline, name: 'rubocop', stage: 'test', stage_idx: 0) }
+ let!(:empty_dependencies_job) do
+ create(:ci_build, pipeline: pipeline, token: 'test-job-token', name: 'empty_dependencies_job',
+ stage: 'deploy', stage_idx: 1,
+ options: { script: ['bash'], dependencies: [] })
+ end
+
+ before do
+ job.success
+ job2.success
+ end
+
+ it 'returns an empty array' do
+ request_job
+
+ expect(response).to have_gitlab_http_status(:created)
+ expect(json_response['id']).to eq(empty_dependencies_job.id)
+ expect(json_response['dependencies'].count).to eq(0)
+ end
+ end
+
+ context 'when job has no tags' do
+ before do
+ job.update(tags: [])
+ end
+
+ context 'when runner is allowed to pick untagged jobs' do
+ before do
+ runner.update_column(:run_untagged, true)
+ end
+
+ it 'picks job' do
+ request_job
+
+ expect(response).to have_gitlab_http_status(:created)
+ end
+ end
+
+ context 'when runner is not allowed to pick untagged jobs' do
+ before do
+ runner.update_column(:run_untagged, false)
+ end
+
+ it_behaves_like 'no jobs available'
+ end
+ end
+
+ context 'when triggered job is available' do
+ let(:expected_variables) do
+ [{ 'key' => 'CI_JOB_NAME', 'value' => 'spinach', 'public' => true, 'masked' => false },
+ { 'key' => 'CI_JOB_STAGE', 'value' => 'test', 'public' => true, 'masked' => false },
+ { 'key' => 'CI_PIPELINE_TRIGGERED', 'value' => 'true', 'public' => true, 'masked' => false },
+ { 'key' => 'DB_NAME', 'value' => 'postgres', 'public' => true, 'masked' => false },
+ { 'key' => 'SECRET_KEY', 'value' => 'secret_value', 'public' => false, 'masked' => false },
+ { 'key' => 'TRIGGER_KEY_1', 'value' => 'TRIGGER_VALUE_1', 'public' => false, 'masked' => false }]
+ end
+
+ let(:trigger) { create(:ci_trigger, project: project) }
+ let!(:trigger_request) { create(:ci_trigger_request, pipeline: pipeline, builds: [job], trigger: trigger) }
+
+ before do
+ project.variables << ::Ci::Variable.new(key: 'SECRET_KEY', value: 'secret_value')
+ end
+
+ shared_examples 'expected variables behavior' do
+ it 'returns variables for triggers' do
+ request_job
+
+ expect(response).to have_gitlab_http_status(:created)
+ expect(json_response['variables']).to include(*expected_variables)
+ end
+ end
+
+ context 'when variables are stored in trigger_request' do
+ before do
+ trigger_request.update_attribute(:variables, { TRIGGER_KEY_1: 'TRIGGER_VALUE_1' } )
+ end
+
+ it_behaves_like 'expected variables behavior'
+ end
+
+ context 'when variables are stored in pipeline_variables' do
+ before do
+ create(:ci_pipeline_variable, pipeline: pipeline, key: :TRIGGER_KEY_1, value: 'TRIGGER_VALUE_1')
+ end
+
+ it_behaves_like 'expected variables behavior'
+ end
+ end
+
+ describe 'registry credentials support' do
+ let(:registry_url) { 'registry.example.com:5005' }
+ let(:registry_credentials) do
+ { 'type' => 'registry',
+ 'url' => registry_url,
+ 'username' => 'gitlab-ci-token',
+ 'password' => job.token }
+ end
+
+ context 'when registry is enabled' do
+ before do
+ stub_container_registry_config(enabled: true, host_port: registry_url)
+ end
+
+ it 'sends registry credentials key' do
+ request_job
+
+ expect(json_response).to have_key('credentials')
+ expect(json_response['credentials']).to include(registry_credentials)
+ end
+ end
+
+ context 'when registry is disabled' do
+ before do
+ stub_container_registry_config(enabled: false, host_port: registry_url)
+ end
+
+ it 'does not send registry credentials' do
+ request_job
+
+ expect(json_response).to have_key('credentials')
+ expect(json_response['credentials']).not_to include(registry_credentials)
+ end
+ end
+ end
+
+ describe 'timeout support' do
+ context 'when project specifies job timeout' do
+ let(:project) { create(:project, shared_runners_enabled: false, build_timeout: 1234) }
+
+ it 'contains info about timeout taken from project' do
+ request_job
+
+ expect(response).to have_gitlab_http_status(:created)
+ expect(json_response['runner_info']).to include({ 'timeout' => 1234 })
+ end
+
+ context 'when runner specifies lower timeout' do
+ let(:runner) { create(:ci_runner, :project, maximum_timeout: 1000, projects: [project]) }
+
+ it 'contains info about timeout overridden by runner' do
+ request_job
+
+ expect(response).to have_gitlab_http_status(:created)
+ expect(json_response['runner_info']).to include({ 'timeout' => 1000 })
+ end
+ end
+
+ context 'when runner specifies bigger timeout' do
+ let(:runner) { create(:ci_runner, :project, maximum_timeout: 2000, projects: [project]) }
+
+ it 'contains info about timeout not overridden by runner' do
+ request_job
+
+ expect(response).to have_gitlab_http_status(:created)
+ expect(json_response['runner_info']).to include({ 'timeout' => 1234 })
+ end
+ end
+ end
+ end
+ end
+
+ describe 'port support' do
+ let(:job) { create(:ci_build, pipeline: pipeline, options: options) }
+
+ context 'when job image has ports' do
+ let(:options) do
+ {
+ image: {
+ name: 'ruby',
+ ports: [80]
+ },
+ services: ['mysql']
+ }
+ end
+
+ it 'returns the image ports' do
+ request_job
+
+ expect(response).to have_gitlab_http_status(:created)
+ expect(json_response).to include(
+ 'id' => job.id,
+ 'image' => a_hash_including('name' => 'ruby', 'ports' => [{ 'number' => 80, 'protocol' => 'http', 'name' => 'default_port' }]),
+ 'services' => all(a_hash_including('name' => 'mysql')))
+ end
+ end
+
+ context 'when job services settings has ports' do
+ let(:options) do
+ {
+ image: 'ruby',
+ services: [
+ {
+ name: 'tomcat',
+ ports: [{ number: 8081, protocol: 'http', name: 'custom_port' }]
+ }
+ ]
+ }
+ end
+
+ it 'returns the service ports' do
+ request_job
+
+ expect(response).to have_gitlab_http_status(:created)
+ expect(json_response).to include(
+ 'id' => job.id,
+ 'image' => a_hash_including('name' => 'ruby'),
+ 'services' => all(a_hash_including('name' => 'tomcat', 'ports' => [{ 'number' => 8081, 'protocol' => 'http', 'name' => 'custom_port' }])))
+ end
+ end
+ end
+
+ describe 'a job with excluded artifacts' do
+ context 'when excluded paths are defined' do
+ let(:job) do
+ create(:ci_build, pipeline: pipeline, token: 'test-job-token', name: 'test',
+ stage: 'deploy', stage_idx: 1,
+ options: { artifacts: { paths: ['abc'], exclude: ['cde'] } })
+ end
+
+ context 'when a runner supports this feature' do
+ it 'exposes excluded paths when the feature is enabled' do
+ stub_feature_flags(ci_artifacts_exclude: true)
+
+ request_job info: { features: { artifacts_exclude: true } }
+
+ expect(response).to have_gitlab_http_status(:created)
+ expect(json_response.dig('artifacts').first).to include('exclude' => ['cde'])
+ end
+
+ it 'does not expose excluded paths when the feature is disabled' do
+ stub_feature_flags(ci_artifacts_exclude: false)
+
+ request_job info: { features: { artifacts_exclude: true } }
+
+ expect(response).to have_gitlab_http_status(:created)
+ expect(json_response.dig('artifacts').first).not_to have_key('exclude')
+ end
+ end
+
+ context 'when a runner does not support this feature' do
+ it 'does not expose the build at all' do
+ stub_feature_flags(ci_artifacts_exclude: true)
+
+ request_job
+
+ expect(response).to have_gitlab_http_status(:no_content)
+ end
+ end
+ end
+
+ it 'does not expose excluded paths when these are empty' do
+ request_job
+
+ expect(response).to have_gitlab_http_status(:created)
+ expect(json_response.dig('artifacts').first).not_to have_key('exclude')
+ end
+ end
+
+ def request_job(token = runner.token, **params)
+ new_params = params.merge(token: token, last_update: last_update)
+ post api('/jobs/request'), params: new_params.to_json, headers: { 'User-Agent' => user_agent, 'Content-Type': 'application/json' }
+ end
+ end
+
+ context 'for web-ide job' do
+ let_it_be(:user) { create(:user) }
+ let_it_be(:project) { create(:project, :repository) }
+
+ let(:runner) { create(:ci_runner, :project, projects: [project]) }
+ let(:service) { ::Ci::CreateWebIdeTerminalService.new(project, user, ref: 'master').execute }
+ let(:pipeline) { service[:pipeline] }
+ let(:build) { pipeline.builds.first }
+ let(:job) { {} }
+ let(:config_content) do
+ 'terminal: { image: ruby, services: [mysql], before_script: [ls], tags: [tag-1], variables: { KEY: value } }'
+ end
+
+ before do
+ stub_webide_config_file(config_content)
+ project.add_maintainer(user)
+
+ pipeline
+ end
+
+ context 'when runner has matching tag' do
+ before do
+ runner.update!(tag_list: ['tag-1'])
+ end
+
+ it 'successfully picks job' do
+ request_job
+
+ build.reload
+
+ expect(build).to be_running
+ expect(build.runner).to eq(runner)
+
+ expect(response).to have_gitlab_http_status(:created)
+ expect(json_response).to include(
+ "id" => build.id,
+ "variables" => include("key" => 'KEY', "value" => 'value', "public" => true, "masked" => false),
+ "image" => a_hash_including("name" => 'ruby'),
+ "services" => all(a_hash_including("name" => 'mysql')),
+ "job_info" => a_hash_including("name" => 'terminal', "stage" => 'terminal'))
+ end
+ end
+
+ context 'when runner does not have matching tags' do
+ it 'does not pick a job' do
+ request_job
+
+ build.reload
+
+ expect(build).to be_pending
+ expect(response).to have_gitlab_http_status(:no_content)
+ end
+ end
+
+ def request_job(token = runner.token, **params)
+ post api('/jobs/request'), params: params.merge(token: token)
+ end
+ end
+ end
+
+ describe 'PUT /api/v4/jobs/:id' do
+ let(:job) do
+ create(:ci_build, :pending, :trace_live, pipeline: pipeline, project: project, user: user, runner_id: runner.id)
+ end
+
+ before do
+ job.run!
+ end
+
+ it_behaves_like 'application context metadata', '/api/:version/jobs/:id' do
+ let(:send_request) { update_job(state: 'success') }
+ end
+
+ it 'updates runner info' do
+ expect { update_job(state: 'success') }.to change { runner.reload.contacted_at }
+ end
+
+ context 'when status is given' do
+ it 'mark job as succeeded' do
+ update_job(state: 'success')
+
+ job.reload
+ expect(job).to be_success
+ end
+
+ it 'mark job as failed' do
+ update_job(state: 'failed')
+
+ job.reload
+ expect(job).to be_failed
+ expect(job).to be_unknown_failure
+ end
+
+ context 'when failure_reason is script_failure' do
+ before do
+ update_job(state: 'failed', failure_reason: 'script_failure')
+ job.reload
+ end
+
+ it { expect(job).to be_script_failure }
+ end
+
+ context 'when failure_reason is runner_system_failure' do
+ before do
+ update_job(state: 'failed', failure_reason: 'runner_system_failure')
+ job.reload
+ end
+
+ it { expect(job).to be_runner_system_failure }
+ end
+
+ context 'when failure_reason is unrecognized value' do
+ before do
+ update_job(state: 'failed', failure_reason: 'what_is_this')
+ job.reload
+ end
+
+ it { expect(job).to be_unknown_failure }
+ end
+
+ context 'when failure_reason is job_execution_timeout' do
+ before do
+ update_job(state: 'failed', failure_reason: 'job_execution_timeout')
+ job.reload
+ end
+
+ it { expect(job).to be_job_execution_timeout }
+ end
+
+ context 'when failure_reason is unmet_prerequisites' do
+ before do
+ update_job(state: 'failed', failure_reason: 'unmet_prerequisites')
+ job.reload
+ end
+
+ it { expect(job).to be_unmet_prerequisites }
+ end
+ end
+
+ context 'when trace is given' do
+ it 'creates a trace artifact' do
+ allow(BuildFinishedWorker).to receive(:perform_async).with(job.id) do
+ ArchiveTraceWorker.new.perform(job.id)
+ end
+
+ update_job(state: 'success', trace: 'BUILD TRACE UPDATED')
+
+ job.reload
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(job.trace.raw).to eq 'BUILD TRACE UPDATED'
+ expect(job.job_artifacts_trace.open.read).to eq 'BUILD TRACE UPDATED'
+ end
+
+ context 'when concurrent update of trace is happening' do
+ before do
+ job.trace.write('wb') do
+ update_job(state: 'success', trace: 'BUILD TRACE UPDATED')
+ end
+ end
+
+ it 'returns that operation conflicts' do
+ expect(response).to have_gitlab_http_status(:conflict)
+ end
+ end
+ end
+
+ context 'when no trace is given' do
+ it 'does not override trace information' do
+ update_job
+
+ expect(job.reload.trace.raw).to eq 'BUILD TRACE'
+ end
+
+ context 'when running state is sent' do
+ it 'updates update_at value' do
+ expect { update_job_after_time }.to change { job.reload.updated_at }
+ end
+ end
+
+ context 'when other state is sent' do
+ it "doesn't update update_at value" do
+ expect { update_job_after_time(20.minutes, state: 'success') }.not_to change { job.reload.updated_at }
+ end
+ end
+ end
+
+ context 'when job has been erased' do
+ let(:job) { create(:ci_build, runner_id: runner.id, erased_at: Time.now) }
+
+ it 'responds with forbidden' do
+ update_job
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+ end
+
+ context 'when job has already been finished' do
+ before do
+ job.trace.set('Job failed')
+ job.drop!(:script_failure)
+ end
+
+ it 'does not update job status and job trace' do
+ update_job(state: 'success', trace: 'BUILD TRACE UPDATED')
+
+ job.reload
+ expect(response).to have_gitlab_http_status(:forbidden)
+ expect(response.header['Job-Status']).to eq 'failed'
+ expect(job.trace.raw).to eq 'Job failed'
+ expect(job).to be_failed
+ end
+ end
+
+ def update_job(token = job.token, **params)
+ new_params = params.merge(token: token)
+ put api("/jobs/#{job.id}"), params: new_params
+ end
+
+ def update_job_after_time(update_interval = 20.minutes, state = 'running')
+ Timecop.travel(job.updated_at + update_interval) do
+ update_job(job.token, state: state)
+ end
+ end
+ end
+
+ describe 'PATCH /api/v4/jobs/:id/trace' do
+ let(:job) do
+ create(:ci_build, :running, :trace_live,
+ project: project, user: user, runner_id: runner.id, pipeline: pipeline)
+ end
+ let(:headers) { { API::Helpers::Runner::JOB_TOKEN_HEADER => job.token, 'Content-Type' => 'text/plain' } }
+ let(:headers_with_range) { headers.merge({ 'Content-Range' => '11-20' }) }
+ let(:update_interval) { 10.seconds.to_i }
+
+ before do
+ initial_patch_the_trace
+ end
+
+ it_behaves_like 'application context metadata', '/api/:version/jobs/:id/trace' do
+ let(:send_request) { patch_the_trace }
+ end
+
+ it 'updates runner info' do
+ runner.update!(contacted_at: 1.year.ago)
+
+ expect { patch_the_trace }.to change { runner.reload.contacted_at }
+ end
+
+ context 'when request is valid' do
+ it 'gets correct response' do
+ expect(response).to have_gitlab_http_status(:accepted)
+ expect(job.reload.trace.raw).to eq 'BUILD TRACE appended'
+ expect(response.header).to have_key 'Range'
+ expect(response.header).to have_key 'Job-Status'
+ expect(response.header).to have_key 'X-GitLab-Trace-Update-Interval'
+ end
+
+ context 'when job has been updated recently' do
+ it { expect { patch_the_trace }.not_to change { job.updated_at }}
+
+ it "changes the job's trace" do
+ patch_the_trace
+
+ expect(job.reload.trace.raw).to eq 'BUILD TRACE appended appended'
+ end
+
+ context 'when Runner makes a force-patch' do
+ it { expect { force_patch_the_trace }.not_to change { job.updated_at }}
+
+ it "doesn't change the build.trace" do
+ force_patch_the_trace
+
+ expect(job.reload.trace.raw).to eq 'BUILD TRACE appended'
+ end
+ end
+ end
+
+ context 'when job was not updated recently' do
+ let(:update_interval) { 15.minutes.to_i }
+
+ it { expect { patch_the_trace }.to change { job.updated_at } }
+
+ it 'changes the job.trace' do
+ patch_the_trace
+
+ expect(job.reload.trace.raw).to eq 'BUILD TRACE appended appended'
+ end
+
+ context 'when Runner makes a force-patch' do
+ it { expect { force_patch_the_trace }.to change { job.updated_at } }
+
+ it "doesn't change the job.trace" do
+ force_patch_the_trace
+
+ expect(job.reload.trace.raw).to eq 'BUILD TRACE appended'
+ end
+ end
+ end
+
+ context 'when project for the build has been deleted' do
+ let(:job) do
+ create(:ci_build, :running, :trace_live, runner_id: runner.id, pipeline: pipeline) do |job|
+ job.project.update(pending_delete: true)
+ end
+ end
+
+ it 'responds with forbidden' do
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+ end
+
+ context 'when trace is patched' do
+ before do
+ patch_the_trace
+ end
+
+ it 'has valid trace' do
+ expect(response).to have_gitlab_http_status(:accepted)
+ expect(job.reload.trace.raw).to eq 'BUILD TRACE appended appended'
+ end
+
+ context 'when job is cancelled' do
+ before do
+ job.cancel
+ end
+
+ context 'when trace is patched' do
+ before do
+ patch_the_trace
+ end
+
+ it 'returns Forbidden ' do
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+ end
+ end
+
+ context 'when redis data are flushed' do
+ before do
+ redis_shared_state_cleanup!
+ end
+
+ it 'has empty trace' do
+ expect(job.reload.trace.raw).to eq ''
+ end
+
+ context 'when we perform partial patch' do
+ before do
+ patch_the_trace('hello', headers.merge({ 'Content-Range' => "28-32/5" }))
+ end
+
+ it 'returns an error' do
+ expect(response).to have_gitlab_http_status(:range_not_satisfiable)
+ expect(response.header['Range']).to eq('0-0')
+ end
+ end
+
+ context 'when we resend full trace' do
+ before do
+ patch_the_trace('BUILD TRACE appended appended hello', headers.merge({ 'Content-Range' => "0-34/35" }))
+ end
+
+ it 'succeeds with updating trace' do
+ expect(response).to have_gitlab_http_status(:accepted)
+ expect(job.reload.trace.raw).to eq 'BUILD TRACE appended appended hello'
+ end
+ end
+ end
+ end
+
+ context 'when concurrent update of trace is happening' do
+ before do
+ job.trace.write('wb') do
+ patch_the_trace
+ end
+ end
+
+ it 'returns that operation conflicts' do
+ expect(response).to have_gitlab_http_status(:conflict)
+ end
+ end
+
+ context 'when the job is canceled' do
+ before do
+ job.cancel
+ patch_the_trace
+ end
+
+ it 'receives status in header' do
+ expect(response.header['Job-Status']).to eq 'canceled'
+ end
+ end
+
+ context 'when build trace is being watched' do
+ before do
+ job.trace.being_watched!
+ end
+
+ it 'returns X-GitLab-Trace-Update-Interval as 3' do
+ patch_the_trace
+
+ expect(response).to have_gitlab_http_status(:accepted)
+ expect(response.header['X-GitLab-Trace-Update-Interval']).to eq('3')
+ end
+ end
+
+ context 'when build trace is not being watched' do
+ it 'returns X-GitLab-Trace-Update-Interval as 30' do
+ patch_the_trace
+
+ expect(response).to have_gitlab_http_status(:accepted)
+ expect(response.header['X-GitLab-Trace-Update-Interval']).to eq('30')
+ end
+ end
+ end
+
+ context 'when Runner makes a force-patch' do
+ before do
+ force_patch_the_trace
+ end
+
+ it 'gets correct response' do
+ expect(response).to have_gitlab_http_status(:accepted)
+ expect(job.reload.trace.raw).to eq 'BUILD TRACE appended'
+ expect(response.header).to have_key 'Range'
+ expect(response.header).to have_key 'Job-Status'
+ end
+ end
+
+ context 'when content-range start is too big' do
+ let(:headers_with_range) { headers.merge({ 'Content-Range' => '15-20/6' }) }
+
+ it 'gets 416 error response with range headers' do
+ expect(response).to have_gitlab_http_status(:range_not_satisfiable)
+ expect(response.header).to have_key 'Range'
+ expect(response.header['Range']).to eq '0-11'
+ end
+ end
+
+ context 'when content-range start is too small' do
+ let(:headers_with_range) { headers.merge({ 'Content-Range' => '8-20/13' }) }
+
+ it 'gets 416 error response with range headers' do
+ expect(response).to have_gitlab_http_status(:range_not_satisfiable)
+ expect(response.header).to have_key 'Range'
+ expect(response.header['Range']).to eq '0-11'
+ end
+ end
+
+ context 'when Content-Range header is missing' do
+ let(:headers_with_range) { headers }
+
+ it { expect(response).to have_gitlab_http_status(:bad_request) }
+ end
+
+ context 'when job has been errased' do
+ let(:job) { create(:ci_build, runner_id: runner.id, erased_at: Time.now) }
+
+ it { expect(response).to have_gitlab_http_status(:forbidden) }
+ end
+
+ def patch_the_trace(content = ' appended', request_headers = nil)
+ unless request_headers
+ job.trace.read do |stream|
+ offset = stream.size
+ limit = offset + content.length - 1
+ request_headers = headers.merge({ 'Content-Range' => "#{offset}-#{limit}" })
+ end
+ end
+
+ Timecop.travel(job.updated_at + update_interval) do
+ patch api("/jobs/#{job.id}/trace"), params: content, headers: request_headers
+ job.reload
+ end
+ end
+
+ def initial_patch_the_trace
+ patch_the_trace(' appended', headers_with_range)
+ end
+
+ def force_patch_the_trace
+ 2.times { patch_the_trace('') }
+ end
+ end
+
+ describe 'artifacts' do
+ let(:job) { create(:ci_build, :pending, user: user, project: project, pipeline: pipeline, runner_id: runner.id) }
+ let(:jwt) { JWT.encode({ 'iss' => 'gitlab-workhorse' }, Gitlab::Workhorse.secret, 'HS256') }
+ let(:headers) { { 'GitLab-Workhorse' => '1.0', Gitlab::Workhorse::INTERNAL_API_REQUEST_HEADER => jwt } }
+ let(:headers_with_token) { headers.merge(API::Helpers::Runner::JOB_TOKEN_HEADER => job.token) }
+ let(:file_upload) { fixture_file_upload('spec/fixtures/banana_sample.gif', 'image/gif') }
+ let(:file_upload2) { fixture_file_upload('spec/fixtures/dk.png', 'image/gif') }
+
+ before do
+ stub_artifacts_object_storage
+ job.run!
+ end
+
+ shared_examples_for 'rejecting artifacts that are too large' do
+ let(:filesize) { 100.megabytes.to_i }
+ let(:sample_max_size) { (filesize / 1.megabyte) - 10 } # Set max size to be smaller than file size to trigger error
+
+ shared_examples_for 'failed request' do
+ it 'responds with payload too large error' do
+ send_request
+
+ expect(response).to have_gitlab_http_status(:payload_too_large)
+ end
+ end
+
+ context 'based on plan limit setting' do
+ let(:application_max_size) { sample_max_size + 100 }
+ let(:limit_name) { "#{Ci::JobArtifact::PLAN_LIMIT_PREFIX}archive" }
+
+ before do
+ create(:plan_limits, :default_plan, limit_name => sample_max_size)
+ stub_application_setting(max_artifacts_size: application_max_size)
+ end
+
+ context 'and feature flag ci_max_artifact_size_per_type is enabled' do
+ before do
+ stub_feature_flags(ci_max_artifact_size_per_type: true)
+ end
+
+ it_behaves_like 'failed request'
+ end
+
+ context 'and feature flag ci_max_artifact_size_per_type is disabled' do
+ before do
+ stub_feature_flags(ci_max_artifact_size_per_type: false)
+ end
+
+ it 'bases of project closest setting' do
+ send_request
+
+ expect(response).to have_gitlab_http_status(success_code)
+ end
+ end
+ end
+
+ context 'based on application setting' do
+ before do
+ stub_application_setting(max_artifacts_size: sample_max_size)
+ end
+
+ it_behaves_like 'failed request'
+ end
+
+ context 'based on root namespace setting' do
+ let(:application_max_size) { sample_max_size + 10 }
+
+ before do
+ stub_application_setting(max_artifacts_size: application_max_size)
+ root_namespace.update!(max_artifacts_size: sample_max_size)
+ end
+
+ it_behaves_like 'failed request'
+ end
+
+ context 'based on child namespace setting' do
+ let(:application_max_size) { sample_max_size + 10 }
+ let(:root_namespace_max_size) { sample_max_size + 10 }
+
+ before do
+ stub_application_setting(max_artifacts_size: application_max_size)
+ root_namespace.update!(max_artifacts_size: root_namespace_max_size)
+ namespace.update!(max_artifacts_size: sample_max_size)
+ end
+
+ it_behaves_like 'failed request'
+ end
+
+ context 'based on project setting' do
+ let(:application_max_size) { sample_max_size + 10 }
+ let(:root_namespace_max_size) { sample_max_size + 10 }
+ let(:child_namespace_max_size) { sample_max_size + 10 }
+
+ before do
+ stub_application_setting(max_artifacts_size: application_max_size)
+ root_namespace.update!(max_artifacts_size: root_namespace_max_size)
+ namespace.update!(max_artifacts_size: child_namespace_max_size)
+ project.update!(max_artifacts_size: sample_max_size)
+ end
+
+ it_behaves_like 'failed request'
+ end
+ end
+
+ describe 'POST /api/v4/jobs/:id/artifacts/authorize' do
+ context 'when using token as parameter' do
+ context 'and the artifact is too large' do
+ it_behaves_like 'rejecting artifacts that are too large' do
+ let(:success_code) { :ok }
+ let(:send_request) { authorize_artifacts_with_token_in_params(filesize: filesize) }
+ end
+ end
+
+ context 'posting artifacts to running job' do
+ subject do
+ authorize_artifacts_with_token_in_params
+ end
+
+ it_behaves_like 'application context metadata', '/api/:version/jobs/:id/artifacts/authorize' do
+ let(:send_request) { subject }
+ end
+
+ it 'updates runner info' do
+ expect { subject }.to change { runner.reload.contacted_at }
+ end
+
+ shared_examples 'authorizes local file' do
+ it 'succeeds' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response.media_type).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE)
+ expect(json_response['TempPath']).to eq(JobArtifactUploader.workhorse_local_upload_path)
+ expect(json_response['RemoteObject']).to be_nil
+ end
+ end
+
+ context 'when using local storage' do
+ it_behaves_like 'authorizes local file'
+ end
+
+ context 'when using remote storage' do
+ context 'when direct upload is enabled' do
+ before do
+ stub_artifacts_object_storage(enabled: true, direct_upload: true)
+ end
+
+ it 'succeeds' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response.media_type).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE)
+ expect(json_response).not_to have_key('TempPath')
+ expect(json_response['RemoteObject']).to have_key('ID')
+ expect(json_response['RemoteObject']).to have_key('GetURL')
+ expect(json_response['RemoteObject']).to have_key('StoreURL')
+ expect(json_response['RemoteObject']).to have_key('DeleteURL')
+ expect(json_response['RemoteObject']).to have_key('MultipartUpload')
+ end
+ end
+
+ context 'when direct upload is disabled' do
+ before do
+ stub_artifacts_object_storage(enabled: true, direct_upload: false)
+ end
+
+ it_behaves_like 'authorizes local file'
+ end
+ end
+ end
+ end
+
+ context 'when using token as header' do
+ it 'authorizes posting artifacts to running job' do
+ authorize_artifacts_with_token_in_headers
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response.media_type).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE)
+ expect(json_response['TempPath']).not_to be_nil
+ end
+
+ it 'fails to post too large artifact' do
+ stub_application_setting(max_artifacts_size: 0)
+
+ authorize_artifacts_with_token_in_headers(filesize: 100)
+
+ expect(response).to have_gitlab_http_status(:payload_too_large)
+ end
+ end
+
+ context 'when using runners token' do
+ it 'fails to authorize artifacts posting' do
+ authorize_artifacts(token: job.project.runners_token)
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+ end
+
+ it 'reject requests that did not go through gitlab-workhorse' do
+ headers.delete(Gitlab::Workhorse::INTERNAL_API_REQUEST_HEADER)
+
+ authorize_artifacts
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+
+ context 'authorization token is invalid' do
+ it 'responds with forbidden' do
+ authorize_artifacts(token: 'invalid', filesize: 100 )
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+ end
+
+ context 'authorize uploading of an lsif artifact' do
+ before do
+ stub_feature_flags(code_navigation: job.project)
+ end
+
+ it 'adds ProcessLsif header' do
+ authorize_artifacts_with_token_in_headers(artifact_type: :lsif)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response['ProcessLsif']).to be_truthy
+ end
+
+ it 'adds ProcessLsifReferences header' do
+ authorize_artifacts_with_token_in_headers(artifact_type: :lsif)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response['ProcessLsifReferences']).to be_truthy
+ end
+
+ context 'code_navigation feature flag is disabled' do
+ it 'responds with a forbidden error' do
+ stub_feature_flags(code_navigation: false)
+ authorize_artifacts_with_token_in_headers(artifact_type: :lsif)
+
+ aggregate_failures do
+ expect(response).to have_gitlab_http_status(:forbidden)
+ expect(json_response['ProcessLsif']).to be_falsy
+ expect(json_response['ProcessLsifReferences']).to be_falsy
+ end
+ end
+ end
+
+ context 'code_navigation_references feature flag is disabled' do
+ it 'sets ProcessLsifReferences header to false' do
+ stub_feature_flags(code_navigation_references: false)
+ authorize_artifacts_with_token_in_headers(artifact_type: :lsif)
+
+ aggregate_failures do
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response['ProcessLsif']).to be_truthy
+ expect(json_response['ProcessLsifReferences']).to be_falsy
+ end
+ end
+ end
+ end
+
+ def authorize_artifacts(params = {}, request_headers = headers)
+ post api("/jobs/#{job.id}/artifacts/authorize"), params: params, headers: request_headers
+ end
+
+ def authorize_artifacts_with_token_in_params(params = {}, request_headers = headers)
+ params = params.merge(token: job.token)
+ authorize_artifacts(params, request_headers)
+ end
+
+ def authorize_artifacts_with_token_in_headers(params = {}, request_headers = headers_with_token)
+ authorize_artifacts(params, request_headers)
+ end
+ end
+
+ describe 'POST /api/v4/jobs/:id/artifacts' do
+ it_behaves_like 'application context metadata', '/api/:version/jobs/:id/artifacts' do
+ let(:send_request) do
+ upload_artifacts(file_upload, headers_with_token)
+ end
+ end
+
+ it 'updates runner info' do
+ expect { upload_artifacts(file_upload, headers_with_token) }.to change { runner.reload.contacted_at }
+ end
+
+ context 'when the artifact is too large' do
+ it_behaves_like 'rejecting artifacts that are too large' do
+ # This filesize validation also happens in non remote stored files,
+ # it's just that it's hard to stub the filesize in other cases to be
+ # more than a megabyte.
+ let!(:fog_connection) do
+ stub_artifacts_object_storage(direct_upload: true)
+ end
+ let(:object) do
+ fog_connection.directories.new(key: 'artifacts').files.create(
+ key: 'tmp/uploads/12312300',
+ body: 'content'
+ )
+ end
+ let(:file_upload) { fog_to_uploaded_file(object) }
+ let(:send_request) do
+ upload_artifacts(file_upload, headers_with_token, 'file.remote_id' => '12312300')
+ end
+ let(:success_code) { :created }
+
+ before do
+ allow(object).to receive(:content_length).and_return(filesize)
+ end
+ end
+ end
+
+ context 'when artifacts are being stored inside of tmp path' do
+ before do
+ # by configuring this path we allow to pass temp file from any path
+ allow(JobArtifactUploader).to receive(:workhorse_upload_path).and_return('/')
+ end
+
+ context 'when job has been erased' do
+ let(:job) { create(:ci_build, erased_at: Time.now) }
+
+ before do
+ upload_artifacts(file_upload, headers_with_token)
+ end
+
+ it 'responds with forbidden' do
+ upload_artifacts(file_upload, headers_with_token)
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+ end
+
+ context 'when job is running' do
+ shared_examples 'successful artifacts upload' do
+ it 'updates successfully' do
+ expect(response).to have_gitlab_http_status(:created)
+ end
+ end
+
+ context 'when uses accelerated file post' do
+ context 'for file stored locally' do
+ before do
+ upload_artifacts(file_upload, headers_with_token)
+ end
+
+ it_behaves_like 'successful artifacts upload'
+ end
+
+ context 'for file stored remotely' do
+ let!(:fog_connection) do
+ stub_artifacts_object_storage(direct_upload: true)
+ end
+ let(:object) do
+ fog_connection.directories.new(key: 'artifacts').files.create(
+ key: 'tmp/uploads/12312300',
+ body: 'content'
+ )
+ end
+ let(:file_upload) { fog_to_uploaded_file(object) }
+
+ before do
+ upload_artifacts(file_upload, headers_with_token, 'file.remote_id' => remote_id)
+ end
+
+ context 'when valid remote_id is used' do
+ let(:remote_id) { '12312300' }
+
+ it_behaves_like 'successful artifacts upload'
+ end
+
+ context 'when invalid remote_id is used' do
+ let(:remote_id) { 'invalid id' }
+
+ it 'responds with bad request' do
+ expect(response).to have_gitlab_http_status(:internal_server_error)
+ expect(json_response['message']).to eq("Missing file")
+ end
+ end
+ end
+ end
+
+ context 'when using runners token' do
+ it 'responds with forbidden' do
+ upload_artifacts(file_upload, headers.merge(API::Helpers::Runner::JOB_TOKEN_HEADER => job.project.runners_token))
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+ end
+ end
+
+ context 'when artifacts post request does not contain file' do
+ it 'fails to post artifacts without file' do
+ post api("/jobs/#{job.id}/artifacts"), params: {}, headers: headers_with_token
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ end
+ end
+
+ context 'GitLab Workhorse is not configured' do
+ it 'fails to post artifacts without GitLab-Workhorse' do
+ post api("/jobs/#{job.id}/artifacts"), params: { token: job.token }, headers: {}
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ end
+ end
+
+ context 'Is missing GitLab Workhorse token headers' do
+ let(:jwt) { JWT.encode({ 'iss' => 'invalid-header' }, Gitlab::Workhorse.secret, 'HS256') }
+
+ it 'fails to post artifacts without GitLab-Workhorse' do
+ expect(Gitlab::ErrorTracking).to receive(:track_exception).once
+
+ upload_artifacts(file_upload, headers_with_token)
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+ end
+
+ context 'when setting an expire date' do
+ let(:default_artifacts_expire_in) {}
+ let(:post_data) do
+ { file: file_upload,
+ expire_in: expire_in }
+ end
+
+ before do
+ stub_application_setting(default_artifacts_expire_in: default_artifacts_expire_in)
+
+ upload_artifacts(file_upload, headers_with_token, post_data)
+ end
+
+ context 'when an expire_in is given' do
+ let(:expire_in) { '7 days' }
+
+ it 'updates when specified' do
+ expect(response).to have_gitlab_http_status(:created)
+ expect(job.reload.artifacts_expire_at).to be_within(5.minutes).of(7.days.from_now)
+ end
+ end
+
+ context 'when no expire_in is given' do
+ let(:expire_in) { nil }
+
+ it 'ignores if not specified' do
+ expect(response).to have_gitlab_http_status(:created)
+ expect(job.reload.artifacts_expire_at).to be_nil
+ end
+
+ context 'with application default' do
+ context 'when default is 5 days' do
+ let(:default_artifacts_expire_in) { '5 days' }
+
+ it 'sets to application default' do
+ expect(response).to have_gitlab_http_status(:created)
+ expect(job.reload.artifacts_expire_at).to be_within(5.minutes).of(5.days.from_now)
+ end
+ end
+
+ context 'when default is 0' do
+ let(:default_artifacts_expire_in) { '0' }
+
+ it 'does not set expire_in' do
+ expect(response).to have_gitlab_http_status(:created)
+ expect(job.reload.artifacts_expire_at).to be_nil
+ end
+ end
+ end
+ end
+ end
+
+ context 'posts artifacts file and metadata file' do
+ let!(:artifacts) { file_upload }
+ let!(:artifacts_sha256) { Digest::SHA256.file(artifacts.path).hexdigest }
+ let!(:metadata) { file_upload2 }
+ let!(:metadata_sha256) { Digest::SHA256.file(metadata.path).hexdigest }
+
+ let(:stored_artifacts_file) { job.reload.artifacts_file }
+ let(:stored_metadata_file) { job.reload.artifacts_metadata }
+ let(:stored_artifacts_size) { job.reload.artifacts_size }
+ let(:stored_artifacts_sha256) { job.reload.job_artifacts_archive.file_sha256 }
+ let(:stored_metadata_sha256) { job.reload.job_artifacts_metadata.file_sha256 }
+ let(:file_keys) { post_data.keys }
+ let(:send_rewritten_field) { true }
+
+ before do
+ workhorse_finalize_with_multiple_files(
+ api("/jobs/#{job.id}/artifacts"),
+ method: :post,
+ file_keys: file_keys,
+ params: post_data,
+ headers: headers_with_token,
+ send_rewritten_field: send_rewritten_field
+ )
+ end
+
+ context 'when posts data accelerated by workhorse is correct' do
+ let(:post_data) { { file: artifacts, metadata: metadata } }
+
+ it 'stores artifacts and artifacts metadata' do
+ expect(response).to have_gitlab_http_status(:created)
+ expect(stored_artifacts_file.filename).to eq(artifacts.original_filename)
+ expect(stored_metadata_file.filename).to eq(metadata.original_filename)
+ expect(stored_artifacts_size).to eq(artifacts.size)
+ expect(stored_artifacts_sha256).to eq(artifacts_sha256)
+ expect(stored_metadata_sha256).to eq(metadata_sha256)
+ end
+ end
+
+ context 'with a malicious file.path param' do
+ let(:post_data) { {} }
+ let(:tmp_file) { Tempfile.new('crafted.file.path') }
+ let(:url) { "/jobs/#{job.id}/artifacts?file.path=#{tmp_file.path}" }
+
+ it 'rejects the request' do
+ expect(response).to have_gitlab_http_status(:bad_request)
+ expect(stored_artifacts_size).to be_nil
+ end
+ end
+
+ context 'when workhorse header is missing' do
+ let(:post_data) { { file: artifacts, metadata: metadata } }
+ let(:send_rewritten_field) { false }
+
+ it 'rejects the request' do
+ expect(response).to have_gitlab_http_status(:bad_request)
+ expect(stored_artifacts_size).to be_nil
+ end
+ end
+
+ context 'when there is no artifacts file in post data' do
+ let(:post_data) do
+ { metadata: metadata }
+ end
+
+ it 'is expected to respond with bad request' do
+ expect(response).to have_gitlab_http_status(:bad_request)
+ end
+
+ it 'does not store metadata' do
+ expect(stored_metadata_file).to be_nil
+ end
+ end
+ end
+
+ context 'when artifact_type is archive' do
+ context 'when artifact_format is zip' do
+ let(:params) { { artifact_type: :archive, artifact_format: :zip } }
+
+ it 'stores junit test report' do
+ upload_artifacts(file_upload, headers_with_token, params)
+
+ expect(response).to have_gitlab_http_status(:created)
+ expect(job.reload.job_artifacts_archive).not_to be_nil
+ end
+ end
+
+ context 'when artifact_format is gzip' do
+ let(:params) { { artifact_type: :archive, artifact_format: :gzip } }
+
+ it 'returns an error' do
+ upload_artifacts(file_upload, headers_with_token, params)
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ expect(job.reload.job_artifacts_archive).to be_nil
+ end
+ end
+ end
+
+ context 'when artifact_type is junit' do
+ context 'when artifact_format is gzip' do
+ let(:file_upload) { fixture_file_upload('spec/fixtures/junit/junit.xml.gz') }
+ let(:params) { { artifact_type: :junit, artifact_format: :gzip } }
+
+ it 'stores junit test report' do
+ upload_artifacts(file_upload, headers_with_token, params)
+
+ expect(response).to have_gitlab_http_status(:created)
+ expect(job.reload.job_artifacts_junit).not_to be_nil
+ end
+ end
+
+ context 'when artifact_format is raw' do
+ let(:file_upload) { fixture_file_upload('spec/fixtures/junit/junit.xml.gz') }
+ let(:params) { { artifact_type: :junit, artifact_format: :raw } }
+
+ it 'returns an error' do
+ upload_artifacts(file_upload, headers_with_token, params)
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ expect(job.reload.job_artifacts_junit).to be_nil
+ end
+ end
+ end
+
+ context 'when artifact_type is metrics_referee' do
+ context 'when artifact_format is gzip' do
+ let(:file_upload) { fixture_file_upload('spec/fixtures/referees/metrics_referee.json.gz') }
+ let(:params) { { artifact_type: :metrics_referee, artifact_format: :gzip } }
+
+ it 'stores metrics_referee data' do
+ upload_artifacts(file_upload, headers_with_token, params)
+
+ expect(response).to have_gitlab_http_status(:created)
+ expect(job.reload.job_artifacts_metrics_referee).not_to be_nil
+ end
+ end
+
+ context 'when artifact_format is raw' do
+ let(:file_upload) { fixture_file_upload('spec/fixtures/referees/metrics_referee.json.gz') }
+ let(:params) { { artifact_type: :metrics_referee, artifact_format: :raw } }
+
+ it 'returns an error' do
+ upload_artifacts(file_upload, headers_with_token, params)
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ expect(job.reload.job_artifacts_metrics_referee).to be_nil
+ end
+ end
+ end
+
+ context 'when artifact_type is network_referee' do
+ context 'when artifact_format is gzip' do
+ let(:file_upload) { fixture_file_upload('spec/fixtures/referees/network_referee.json.gz') }
+ let(:params) { { artifact_type: :network_referee, artifact_format: :gzip } }
+
+ it 'stores network_referee data' do
+ upload_artifacts(file_upload, headers_with_token, params)
+
+ expect(response).to have_gitlab_http_status(:created)
+ expect(job.reload.job_artifacts_network_referee).not_to be_nil
+ end
+ end
+
+ context 'when artifact_format is raw' do
+ let(:file_upload) { fixture_file_upload('spec/fixtures/referees/network_referee.json.gz') }
+ let(:params) { { artifact_type: :network_referee, artifact_format: :raw } }
+
+ it 'returns an error' do
+ upload_artifacts(file_upload, headers_with_token, params)
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ expect(job.reload.job_artifacts_network_referee).to be_nil
+ end
+ end
+ end
+
+ context 'when artifact_type is dotenv' do
+ context 'when artifact_format is gzip' do
+ let(:file_upload) { fixture_file_upload('spec/fixtures/build.env.gz') }
+ let(:params) { { artifact_type: :dotenv, artifact_format: :gzip } }
+
+ it 'stores dotenv file' do
+ upload_artifacts(file_upload, headers_with_token, params)
+
+ expect(response).to have_gitlab_http_status(:created)
+ expect(job.reload.job_artifacts_dotenv).not_to be_nil
+ end
+
+ it 'parses dotenv file' do
+ expect do
+ upload_artifacts(file_upload, headers_with_token, params)
+ end.to change { job.job_variables.count }.from(0).to(2)
+ end
+
+ context 'when parse error happens' do
+ let(:file_upload) { fixture_file_upload('spec/fixtures/ci_build_artifacts_metadata.gz') }
+
+ it 'returns an error' do
+ upload_artifacts(file_upload, headers_with_token, params)
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ expect(json_response['message']).to eq('Invalid Format')
+ end
+ end
+ end
+
+ context 'when artifact_format is raw' do
+ let(:file_upload) { fixture_file_upload('spec/fixtures/build.env.gz') }
+ let(:params) { { artifact_type: :dotenv, artifact_format: :raw } }
+
+ it 'returns an error' do
+ upload_artifacts(file_upload, headers_with_token, params)
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ expect(job.reload.job_artifacts_dotenv).to be_nil
+ end
+ end
+ end
+ end
+
+ context 'when artifacts already exist for the job' do
+ let(:params) do
+ {
+ artifact_type: :archive,
+ artifact_format: :zip,
+ 'file.sha256' => uploaded_sha256
+ }
+ end
+
+ let(:existing_sha256) { '0' * 64 }
+
+ let!(:existing_artifact) do
+ create(:ci_job_artifact, :archive, file_sha256: existing_sha256, job: job)
+ end
+
+ context 'when sha256 is the same of the existing artifact' do
+ let(:uploaded_sha256) { existing_sha256 }
+
+ it 'ignores the new artifact' do
+ upload_artifacts(file_upload, headers_with_token, params)
+
+ expect(response).to have_gitlab_http_status(:created)
+ expect(job.reload.job_artifacts_archive).to eq(existing_artifact)
+ end
+ end
+
+ context 'when sha256 is different than the existing artifact' do
+ let(:uploaded_sha256) { '1' * 64 }
+
+ it 'logs and returns an error' do
+ expect(Gitlab::ErrorTracking).to receive(:track_exception)
+
+ upload_artifacts(file_upload, headers_with_token, params)
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ expect(job.reload.job_artifacts_archive).to eq(existing_artifact)
+ end
+ end
+ end
+
+ context 'when object storage throws errors' do
+ let(:params) { { artifact_type: :archive, artifact_format: :zip } }
+
+ it 'does not store artifacts' do
+ allow_next_instance_of(JobArtifactUploader) do |uploader|
+ allow(uploader).to receive(:store!).and_raise(Errno::EIO)
+ end
+
+ upload_artifacts(file_upload, headers_with_token, params)
+
+ expect(response).to have_gitlab_http_status(:service_unavailable)
+ expect(job.reload.job_artifacts_archive).to be_nil
+ end
+ end
+
+ context 'when artifacts are being stored outside of tmp path' do
+ let(:new_tmpdir) { Dir.mktmpdir }
+
+ before do
+ # init before overwriting tmp dir
+ file_upload
+
+ # by configuring this path we allow to pass file from @tmpdir only
+ # but all temporary files are stored in system tmp directory
+ allow(Dir).to receive(:tmpdir).and_return(new_tmpdir)
+ end
+
+ after do
+ FileUtils.remove_entry(new_tmpdir)
+ end
+
+ it 'fails to post artifacts for outside of tmp path' do
+ upload_artifacts(file_upload, headers_with_token)
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ end
+ end
+
+ def upload_artifacts(file, headers = {}, params = {})
+ workhorse_finalize(
+ api("/jobs/#{job.id}/artifacts"),
+ method: :post,
+ file_key: :file,
+ params: params.merge(file: file),
+ headers: headers,
+ send_rewritten_field: true
+ )
+ end
+ end
+
+ describe 'GET /api/v4/jobs/:id/artifacts' do
+ let(:token) { job.token }
+
+ it_behaves_like 'application context metadata', '/api/:version/jobs/:id/artifacts' do
+ let(:send_request) { download_artifact }
+ end
+
+ it 'updates runner info' do
+ expect { download_artifact }.to change { runner.reload.contacted_at }
+ end
+
+ context 'when job has artifacts' do
+ let(:job) { create(:ci_build) }
+ let(:store) { JobArtifactUploader::Store::LOCAL }
+
+ before do
+ create(:ci_job_artifact, :archive, file_store: store, job: job)
+ end
+
+ context 'when using job token' do
+ context 'when artifacts are stored locally' do
+ let(:download_headers) do
+ { 'Content-Transfer-Encoding' => 'binary',
+ 'Content-Disposition' => %q(attachment; filename="ci_build_artifacts.zip"; filename*=UTF-8''ci_build_artifacts.zip) }
+ end
+
+ before do
+ download_artifact
+ end
+
+ it 'download artifacts' do
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response.headers.to_h).to include download_headers
+ end
+ end
+
+ context 'when artifacts are stored remotely' do
+ let(:store) { JobArtifactUploader::Store::REMOTE }
+ let!(:job) { create(:ci_build) }
+
+ context 'when proxy download is being used' do
+ before do
+ download_artifact(direct_download: false)
+ end
+
+ it 'uses workhorse send-url' do
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response.headers.to_h).to include(
+ 'Gitlab-Workhorse-Send-Data' => /send-url:/)
+ end
+ end
+
+ context 'when direct download is being used' do
+ before do
+ download_artifact(direct_download: true)
+ end
+
+ it 'receive redirect for downloading artifacts' do
+ expect(response).to have_gitlab_http_status(:found)
+ expect(response.headers).to include('Location')
+ end
+ end
+ end
+ end
+
+ context 'when using runnners token' do
+ let(:token) { job.project.runners_token }
+
+ before do
+ download_artifact
+ end
+
+ it 'responds with forbidden' do
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+ end
+ end
+
+ context 'when job does not have artifacts' do
+ it 'responds with not found' do
+ download_artifact
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+
+ def download_artifact(params = {}, request_headers = headers)
+ params = params.merge(token: token)
+ job.reload
+
+ get api("/jobs/#{job.id}/artifacts"), params: params, headers: request_headers
+ end
+ end
+ end
+ end
+end
diff --git a/spec/requests/api/ci/runners_spec.rb b/spec/requests/api/ci/runners_spec.rb
new file mode 100644
index 00000000000..670456e5dba
--- /dev/null
+++ b/spec/requests/api/ci/runners_spec.rb
@@ -0,0 +1,1096 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe API::Ci::Runners do
+ let_it_be(:admin) { create(:user, :admin) }
+ let_it_be(:user) { create(:user) }
+ let_it_be(:user2) { create(:user) }
+ let_it_be(:group_guest) { create(:user) }
+ let_it_be(:group_reporter) { create(:user) }
+ let_it_be(:group_developer) { create(:user) }
+ let_it_be(:group_maintainer) { create(:user) }
+
+ let_it_be(:project) { create(:project, creator_id: user.id) }
+ let_it_be(:project2) { create(:project, creator_id: user.id) }
+
+ let_it_be(:group) { create(:group).tap { |group| group.add_owner(user) } }
+ let_it_be(:subgroup) { create(:group, parent: group) }
+
+ let_it_be(:shared_runner, reload: true) { create(:ci_runner, :instance, description: 'Shared runner') }
+ let_it_be(:project_runner, reload: true) { create(:ci_runner, :project, description: 'Project runner', projects: [project]) }
+ let_it_be(:two_projects_runner) { create(:ci_runner, :project, description: 'Two projects runner', projects: [project, project2]) }
+ let_it_be(:group_runner_a) { create(:ci_runner, :group, description: 'Group runner A', groups: [group]) }
+ let_it_be(:group_runner_b) { create(:ci_runner, :group, description: 'Group runner B', groups: [subgroup]) }
+
+ before_all do
+ group.add_guest(group_guest)
+ group.add_reporter(group_reporter)
+ group.add_developer(group_developer)
+ group.add_maintainer(group_maintainer)
+ project.add_maintainer(user)
+ project2.add_maintainer(user)
+ project.add_reporter(user2)
+ end
+
+ describe 'GET /runners' do
+ context 'authorized user' do
+ it 'returns response status and headers' do
+ get api('/runners', user)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to include_pagination_headers
+ end
+
+ it 'returns user available runners' do
+ get api('/runners', user)
+
+ expect(json_response).to match_array [
+ a_hash_including('description' => 'Project runner'),
+ a_hash_including('description' => 'Two projects runner'),
+ a_hash_including('description' => 'Group runner A'),
+ a_hash_including('description' => 'Group runner B')
+ ]
+ end
+
+ it 'filters runners by scope' do
+ create(:ci_runner, :project, :inactive, description: 'Inactive project runner', projects: [project])
+
+ get api('/runners?scope=paused', user)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to include_pagination_headers
+
+ expect(json_response).to match_array [
+ a_hash_including('description' => 'Inactive project runner')
+ ]
+ end
+
+ it 'avoids filtering if scope is invalid' do
+ get api('/runners?scope=unknown', user)
+ expect(response).to have_gitlab_http_status(:bad_request)
+ end
+
+ it 'filters runners by type' do
+ get api('/runners?type=project_type', user)
+
+ expect(json_response).to match_array [
+ a_hash_including('description' => 'Project runner'),
+ a_hash_including('description' => 'Two projects runner')
+ ]
+ end
+
+ it 'does not filter by invalid type' do
+ get api('/runners?type=bogus', user)
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ end
+
+ it 'filters runners by status' do
+ create(:ci_runner, :project, :inactive, description: 'Inactive project runner', projects: [project])
+
+ get api('/runners?status=paused', user)
+
+ expect(json_response).to match_array [
+ a_hash_including('description' => 'Inactive project runner')
+ ]
+ end
+
+ it 'does not filter by invalid status' do
+ get api('/runners?status=bogus', user)
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ end
+
+ it 'filters runners by tag_list' do
+ create(:ci_runner, :project, description: 'Runner tagged with tag1 and tag2', projects: [project], tag_list: %w[tag1 tag2])
+ create(:ci_runner, :project, description: 'Runner tagged with tag2', projects: [project], tag_list: ['tag2'])
+
+ get api('/runners?tag_list=tag1,tag2', user)
+
+ expect(json_response).to match_array [
+ a_hash_including('description' => 'Runner tagged with tag1 and tag2')
+ ]
+ end
+ end
+
+ context 'unauthorized user' do
+ it 'does not return runners' do
+ get api('/runners')
+
+ expect(response).to have_gitlab_http_status(:unauthorized)
+ end
+ end
+ end
+
+ describe 'GET /runners/all' do
+ context 'authorized user' do
+ context 'with admin privileges' do
+ it 'returns response status and headers' do
+ get api('/runners/all', admin)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to include_pagination_headers
+ end
+
+ it 'returns all runners' do
+ get api('/runners/all', admin)
+
+ expect(json_response).to match_array [
+ a_hash_including('description' => 'Project runner'),
+ a_hash_including('description' => 'Two projects runner'),
+ a_hash_including('description' => 'Group runner A'),
+ a_hash_including('description' => 'Group runner B'),
+ a_hash_including('description' => 'Shared runner')
+ ]
+ end
+
+ it 'filters runners by scope' do
+ get api('/runners/all?scope=shared', admin)
+
+ shared = json_response.all? { |r| r['is_shared'] }
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to include_pagination_headers
+ expect(json_response).to be_an Array
+ expect(json_response[0]).to have_key('ip_address')
+ expect(shared).to be_truthy
+ end
+
+ it 'filters runners by scope' do
+ get api('/runners/all?scope=specific', admin)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to include_pagination_headers
+
+ expect(json_response).to match_array [
+ a_hash_including('description' => 'Project runner'),
+ a_hash_including('description' => 'Two projects runner'),
+ a_hash_including('description' => 'Group runner A'),
+ a_hash_including('description' => 'Group runner B')
+ ]
+ end
+
+ it 'avoids filtering if scope is invalid' do
+ get api('/runners/all?scope=unknown', admin)
+ expect(response).to have_gitlab_http_status(:bad_request)
+ end
+
+ it 'filters runners by project type' do
+ get api('/runners/all?type=project_type', admin)
+
+ expect(json_response).to match_array [
+ a_hash_including('description' => 'Project runner'),
+ a_hash_including('description' => 'Two projects runner')
+ ]
+ end
+
+ it 'filters runners by group type' do
+ get api('/runners/all?type=group_type', admin)
+
+ expect(json_response).to match_array [
+ a_hash_including('description' => 'Group runner A'),
+ a_hash_including('description' => 'Group runner B')
+ ]
+ end
+
+ it 'does not filter by invalid type' do
+ get api('/runners/all?type=bogus', admin)
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ end
+
+ it 'filters runners by status' do
+ create(:ci_runner, :project, :inactive, description: 'Inactive project runner', projects: [project])
+
+ get api('/runners/all?status=paused', admin)
+
+ expect(json_response).to match_array [
+ a_hash_including('description' => 'Inactive project runner')
+ ]
+ end
+
+ it 'does not filter by invalid status' do
+ get api('/runners/all?status=bogus', admin)
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ end
+
+ it 'filters runners by tag_list' do
+ create(:ci_runner, :project, description: 'Runner tagged with tag1 and tag2', projects: [project], tag_list: %w[tag1 tag2])
+ create(:ci_runner, :project, description: 'Runner tagged with tag2', projects: [project], tag_list: ['tag2'])
+
+ get api('/runners/all?tag_list=tag1,tag2', admin)
+
+ expect(json_response).to match_array [
+ a_hash_including('description' => 'Runner tagged with tag1 and tag2')
+ ]
+ end
+ end
+
+ context 'without admin privileges' do
+ it 'does not return runners list' do
+ get api('/runners/all', user)
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+ end
+ end
+
+ context 'unauthorized user' do
+ it 'does not return runners' do
+ get api('/runners')
+
+ expect(response).to have_gitlab_http_status(:unauthorized)
+ end
+ end
+ end
+
+ describe 'GET /runners/:id' do
+ context 'admin user' do
+ context 'when runner is shared' do
+ it "returns runner's details" do
+ get api("/runners/#{shared_runner.id}", admin)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response['description']).to eq(shared_runner.description)
+ expect(json_response['maximum_timeout']).to be_nil
+ end
+ end
+
+ context 'when runner is not shared' do
+ context 'when unused runner is present' do
+ let!(:unused_project_runner) { create(:ci_runner, :project, :without_projects) }
+
+ it 'deletes unused runner' do
+ expect do
+ delete api("/runners/#{unused_project_runner.id}", admin)
+
+ expect(response).to have_gitlab_http_status(:no_content)
+ end.to change { ::Ci::Runner.project_type.count }.by(-1)
+ end
+ end
+
+ it "returns runner's details" do
+ get api("/runners/#{project_runner.id}", admin)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response['description']).to eq(project_runner.description)
+ end
+
+ it "returns the project's details for a project runner" do
+ get api("/runners/#{project_runner.id}", admin)
+
+ expect(json_response['projects'].first['id']).to eq(project.id)
+ end
+ end
+
+ it 'returns 404 if runner does not exist' do
+ get api('/runners/0', admin)
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+
+ context "runner project's administrative user" do
+ context 'when runner is not shared' do
+ it "returns runner's details" do
+ get api("/runners/#{project_runner.id}", user)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response['description']).to eq(project_runner.description)
+ end
+ end
+
+ context 'when runner is shared' do
+ it "returns runner's details" do
+ get api("/runners/#{shared_runner.id}", user)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response['description']).to eq(shared_runner.description)
+ end
+ end
+ end
+
+ context 'other authorized user' do
+ it "does not return project runner's details" do
+ get api("/runners/#{project_runner.id}", user2)
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+ end
+
+ context 'unauthorized user' do
+ it "does not return project runner's details" do
+ get api("/runners/#{project_runner.id}")
+
+ expect(response).to have_gitlab_http_status(:unauthorized)
+ end
+ end
+ end
+
+ describe 'PUT /runners/:id' do
+ context 'admin user' do
+ # see https://gitlab.com/gitlab-org/gitlab-foss/issues/48625
+ context 'single parameter update' do
+ it 'runner description' do
+ description = shared_runner.description
+ update_runner(shared_runner.id, admin, description: "#{description}_updated")
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(shared_runner.reload.description).to eq("#{description}_updated")
+ end
+
+ it 'runner active state' do
+ active = shared_runner.active
+ update_runner(shared_runner.id, admin, active: !active)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(shared_runner.reload.active).to eq(!active)
+ end
+
+ it 'runner tag list' do
+ update_runner(shared_runner.id, admin, tag_list: ['ruby2.1', 'pgsql', 'mysql'])
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(shared_runner.reload.tag_list).to include('ruby2.1', 'pgsql', 'mysql')
+ end
+
+ it 'runner untagged flag' do
+ # Ensure tag list is non-empty before setting untagged to false.
+ update_runner(shared_runner.id, admin, tag_list: ['ruby2.1', 'pgsql', 'mysql'])
+ update_runner(shared_runner.id, admin, run_untagged: 'false')
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(shared_runner.reload.run_untagged?).to be(false)
+ end
+
+ it 'runner unlocked flag' do
+ update_runner(shared_runner.id, admin, locked: 'true')
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(shared_runner.reload.locked?).to be(true)
+ end
+
+ it 'runner access level' do
+ update_runner(shared_runner.id, admin, access_level: 'ref_protected')
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(shared_runner.reload.ref_protected?).to be_truthy
+ end
+
+ it 'runner maximum timeout' do
+ update_runner(shared_runner.id, admin, maximum_timeout: 1234)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(shared_runner.reload.maximum_timeout).to eq(1234)
+ end
+
+ it 'fails with no parameters' do
+ put api("/runners/#{shared_runner.id}", admin)
+
+ shared_runner.reload
+ expect(response).to have_gitlab_http_status(:bad_request)
+ end
+ end
+
+ context 'when runner is shared' do
+ it 'updates runner' do
+ description = shared_runner.description
+ active = shared_runner.active
+ runner_queue_value = shared_runner.ensure_runner_queue_value
+
+ update_runner(shared_runner.id, admin, description: "#{description}_updated",
+ active: !active,
+ tag_list: ['ruby2.1', 'pgsql', 'mysql'],
+ run_untagged: 'false',
+ locked: 'true',
+ access_level: 'ref_protected',
+ maximum_timeout: 1234)
+ shared_runner.reload
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(shared_runner.description).to eq("#{description}_updated")
+ expect(shared_runner.active).to eq(!active)
+ expect(shared_runner.tag_list).to include('ruby2.1', 'pgsql', 'mysql')
+ expect(shared_runner.run_untagged?).to be(false)
+ expect(shared_runner.locked?).to be(true)
+ expect(shared_runner.ref_protected?).to be_truthy
+ expect(shared_runner.ensure_runner_queue_value)
+ .not_to eq(runner_queue_value)
+ expect(shared_runner.maximum_timeout).to eq(1234)
+ end
+ end
+
+ context 'when runner is not shared' do
+ it 'updates runner' do
+ description = project_runner.description
+ runner_queue_value = project_runner.ensure_runner_queue_value
+
+ update_runner(project_runner.id, admin, description: 'test')
+ project_runner.reload
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(project_runner.description).to eq('test')
+ expect(project_runner.description).not_to eq(description)
+ expect(project_runner.ensure_runner_queue_value)
+ .not_to eq(runner_queue_value)
+ end
+ end
+
+ it 'returns 404 if runner does not exist' do
+ update_runner(0, admin, description: 'test')
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+
+ def update_runner(id, user, args)
+ put api("/runners/#{id}", user), params: args
+ end
+ end
+
+ context 'authorized user' do
+ context 'when runner is shared' do
+ it 'does not update runner' do
+ put api("/runners/#{shared_runner.id}", user), params: { description: 'test' }
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+ end
+
+ context 'when runner is not shared' do
+ it 'does not update project runner without access to it' do
+ put api("/runners/#{project_runner.id}", user2), params: { description: 'test' }
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+
+ it 'updates project runner with access to it' do
+ description = project_runner.description
+ put api("/runners/#{project_runner.id}", admin), params: { description: 'test' }
+ project_runner.reload
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(project_runner.description).to eq('test')
+ expect(project_runner.description).not_to eq(description)
+ end
+ end
+ end
+
+ context 'unauthorized user' do
+ it 'does not delete project runner' do
+ put api("/runners/#{project_runner.id}")
+
+ expect(response).to have_gitlab_http_status(:unauthorized)
+ end
+ end
+ end
+
+ describe 'DELETE /runners/:id' do
+ context 'admin user' do
+ context 'when runner is shared' do
+ it 'deletes runner' do
+ expect do
+ delete api("/runners/#{shared_runner.id}", admin)
+
+ expect(response).to have_gitlab_http_status(:no_content)
+ end.to change { ::Ci::Runner.instance_type.count }.by(-1)
+ end
+
+ it_behaves_like '412 response' do
+ let(:request) { api("/runners/#{shared_runner.id}", admin) }
+ end
+ end
+
+ context 'when runner is not shared' do
+ it 'deletes used project runner' do
+ expect do
+ delete api("/runners/#{project_runner.id}", admin)
+
+ expect(response).to have_gitlab_http_status(:no_content)
+ end.to change { ::Ci::Runner.project_type.count }.by(-1)
+ end
+ end
+
+ it 'returns 404 if runner does not exist' do
+ delete api('/runners/0', admin)
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+
+ context 'authorized user' do
+ context 'when runner is shared' do
+ it 'does not delete runner' do
+ delete api("/runners/#{shared_runner.id}", user)
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+ end
+
+ context 'when runner is not shared' do
+ it 'does not delete runner without access to it' do
+ delete api("/runners/#{project_runner.id}", user2)
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+
+ it 'does not delete project runner with more than one associated project' do
+ delete api("/runners/#{two_projects_runner.id}", user)
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+
+ it 'deletes project runner for one owned project' do
+ expect do
+ delete api("/runners/#{project_runner.id}", user)
+
+ expect(response).to have_gitlab_http_status(:no_content)
+ end.to change { ::Ci::Runner.project_type.count }.by(-1)
+ end
+
+ it 'does not delete group runner with guest access' do
+ delete api("/runners/#{group_runner_a.id}", group_guest)
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+
+ it 'does not delete group runner with reporter access' do
+ delete api("/runners/#{group_runner_a.id}", group_reporter)
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+
+ it 'does not delete group runner with developer access' do
+ delete api("/runners/#{group_runner_a.id}", group_developer)
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+
+ it 'does not delete group runner with maintainer access' do
+ delete api("/runners/#{group_runner_a.id}", group_maintainer)
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+
+ it 'deletes owned group runner with owner access' do
+ expect do
+ delete api("/runners/#{group_runner_a.id}", user)
+
+ expect(response).to have_gitlab_http_status(:no_content)
+ end.to change { ::Ci::Runner.group_type.count }.by(-1)
+ end
+
+ it 'deletes inherited group runner with owner access' do
+ expect do
+ delete api("/runners/#{group_runner_b.id}", user)
+
+ expect(response).to have_gitlab_http_status(:no_content)
+ end.to change { ::Ci::Runner.group_type.count }.by(-1)
+ end
+
+ it_behaves_like '412 response' do
+ let(:request) { api("/runners/#{project_runner.id}", user) }
+ end
+ end
+ end
+
+ context 'unauthorized user' do
+ it 'does not delete project runner' do
+ delete api("/runners/#{project_runner.id}")
+
+ expect(response).to have_gitlab_http_status(:unauthorized)
+ end
+ end
+ end
+
+ describe 'GET /runners/:id/jobs' do
+ let_it_be(:job_1) { create(:ci_build) }
+ let_it_be(:job_2) { create(:ci_build, :running, runner: shared_runner, project: project) }
+ let_it_be(:job_3) { create(:ci_build, :failed, runner: shared_runner, project: project) }
+ let_it_be(:job_4) { create(:ci_build, :running, runner: project_runner, project: project) }
+ let_it_be(:job_5) { create(:ci_build, :failed, runner: project_runner, project: project) }
+
+ context 'admin user' do
+ context 'when runner exists' do
+ context 'when runner is shared' do
+ it 'return jobs' do
+ get api("/runners/#{shared_runner.id}/jobs", admin)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to include_pagination_headers
+
+ expect(json_response).to be_an(Array)
+ expect(json_response.length).to eq(2)
+ end
+ end
+
+ context 'when runner is specific' do
+ it 'return jobs' do
+ get api("/runners/#{project_runner.id}/jobs", admin)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to include_pagination_headers
+
+ expect(json_response).to be_an(Array)
+ expect(json_response.length).to eq(2)
+ end
+ end
+
+ context 'when valid status is provided' do
+ it 'return filtered jobs' do
+ get api("/runners/#{project_runner.id}/jobs?status=failed", admin)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to include_pagination_headers
+
+ expect(json_response).to be_an(Array)
+ expect(json_response.length).to eq(1)
+ expect(json_response.first).to include('id' => job_5.id)
+ end
+ end
+
+ context 'when valid order_by is provided' do
+ context 'when sort order is not specified' do
+ it 'return jobs in descending order' do
+ get api("/runners/#{project_runner.id}/jobs?order_by=id", admin)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to include_pagination_headers
+
+ expect(json_response).to be_an(Array)
+ expect(json_response.length).to eq(2)
+ expect(json_response.first).to include('id' => job_5.id)
+ end
+ end
+
+ context 'when sort order is specified as asc' do
+ it 'return jobs sorted in ascending order' do
+ get api("/runners/#{project_runner.id}/jobs?order_by=id&sort=asc", admin)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to include_pagination_headers
+
+ expect(json_response).to be_an(Array)
+ expect(json_response.length).to eq(2)
+ expect(json_response.first).to include('id' => job_4.id)
+ end
+ end
+ end
+
+ context 'when invalid status is provided' do
+ it 'return 400' do
+ get api("/runners/#{project_runner.id}/jobs?status=non-existing", admin)
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ end
+ end
+
+ context 'when invalid order_by is provided' do
+ it 'return 400' do
+ get api("/runners/#{project_runner.id}/jobs?order_by=non-existing", admin)
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ end
+ end
+
+ context 'when invalid sort is provided' do
+ it 'return 400' do
+ get api("/runners/#{project_runner.id}/jobs?sort=non-existing", admin)
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ end
+ end
+ end
+
+ context "when runner doesn't exist" do
+ it 'returns 404' do
+ get api('/runners/0/jobs', admin)
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+ end
+
+ context "runner project's administrative user" do
+ context 'when runner exists' do
+ context 'when runner is shared' do
+ it 'returns 403' do
+ get api("/runners/#{shared_runner.id}/jobs", user)
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+ end
+
+ context 'when runner is specific' do
+ it 'return jobs' do
+ get api("/runners/#{project_runner.id}/jobs", user)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to include_pagination_headers
+
+ expect(json_response).to be_an(Array)
+ expect(json_response.length).to eq(2)
+ end
+ end
+
+ context 'when valid status is provided' do
+ it 'return filtered jobs' do
+ get api("/runners/#{project_runner.id}/jobs?status=failed", user)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to include_pagination_headers
+
+ expect(json_response).to be_an(Array)
+ expect(json_response.length).to eq(1)
+ expect(json_response.first).to include('id' => job_5.id)
+ end
+ end
+
+ context 'when invalid status is provided' do
+ it 'return 400' do
+ get api("/runners/#{project_runner.id}/jobs?status=non-existing", user)
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ end
+ end
+ end
+
+ context "when runner doesn't exist" do
+ it 'returns 404' do
+ get api('/runners/0/jobs', user)
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+ end
+
+ context 'other authorized user' do
+ it 'does not return jobs' do
+ get api("/runners/#{project_runner.id}/jobs", user2)
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+ end
+
+ context 'unauthorized user' do
+ it 'does not return jobs' do
+ get api("/runners/#{project_runner.id}/jobs")
+
+ expect(response).to have_gitlab_http_status(:unauthorized)
+ end
+ end
+ end
+
+ shared_examples_for 'unauthorized access to runners list' do
+ context 'authorized user without maintainer privileges' do
+ it "does not return group's runners" do
+ get api("/#{entity_type}/#{entity.id}/runners", user2)
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+ end
+
+ context 'unauthorized user' do
+ it "does not return project's runners" do
+ get api("/#{entity_type}/#{entity.id}/runners")
+
+ expect(response).to have_gitlab_http_status(:unauthorized)
+ end
+ end
+ end
+
+ describe 'GET /projects/:id/runners' do
+ context 'authorized user with maintainer privileges' do
+ it 'returns response status and headers' do
+ get api('/runners/all', admin)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to include_pagination_headers
+ end
+
+ it 'returns all runners' do
+ get api("/projects/#{project.id}/runners", user)
+
+ expect(json_response).to match_array [
+ a_hash_including('description' => 'Project runner'),
+ a_hash_including('description' => 'Two projects runner'),
+ a_hash_including('description' => 'Shared runner')
+ ]
+ end
+
+ it 'filters runners by scope' do
+ get api("/projects/#{project.id}/runners?scope=specific", user)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to include_pagination_headers
+
+ expect(json_response).to match_array [
+ a_hash_including('description' => 'Project runner'),
+ a_hash_including('description' => 'Two projects runner')
+ ]
+ end
+
+ it 'avoids filtering if scope is invalid' do
+ get api("/projects/#{project.id}/runners?scope=unknown", user)
+ expect(response).to have_gitlab_http_status(:bad_request)
+ end
+
+ it 'filters runners by type' do
+ get api("/projects/#{project.id}/runners?type=project_type", user)
+
+ expect(json_response).to match_array [
+ a_hash_including('description' => 'Project runner'),
+ a_hash_including('description' => 'Two projects runner')
+ ]
+ end
+
+ it 'does not filter by invalid type' do
+ get api("/projects/#{project.id}/runners?type=bogus", user)
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ end
+
+ it 'filters runners by status' do
+ create(:ci_runner, :project, :inactive, description: 'Inactive project runner', projects: [project])
+
+ get api("/projects/#{project.id}/runners?status=paused", user)
+
+ expect(json_response).to match_array [
+ a_hash_including('description' => 'Inactive project runner')
+ ]
+ end
+
+ it 'does not filter by invalid status' do
+ get api("/projects/#{project.id}/runners?status=bogus", user)
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ end
+
+ it 'filters runners by tag_list' do
+ create(:ci_runner, :project, description: 'Runner tagged with tag1 and tag2', projects: [project], tag_list: %w[tag1 tag2])
+ create(:ci_runner, :project, description: 'Runner tagged with tag2', projects: [project], tag_list: ['tag2'])
+
+ get api("/projects/#{project.id}/runners?tag_list=tag1,tag2", user)
+
+ expect(json_response).to match_array [
+ a_hash_including('description' => 'Runner tagged with tag1 and tag2')
+ ]
+ end
+ end
+
+ it_behaves_like 'unauthorized access to runners list' do
+ let(:entity_type) { 'projects' }
+ let(:entity) { project }
+ end
+ end
+
+ describe 'GET /groups/:id/runners' do
+ context 'authorized user with maintainer privileges' do
+ it 'returns all runners' do
+ get api("/groups/#{group.id}/runners", user)
+
+ expect(json_response).to match_array([
+ a_hash_including('description' => 'Group runner A')
+ ])
+ end
+
+ context 'filter by type' do
+ it 'returns record when valid and present' do
+ get api("/groups/#{group.id}/runners?type=group_type", user)
+
+ expect(json_response).to match_array([
+ a_hash_including('description' => 'Group runner A')
+ ])
+ end
+
+ it 'returns empty result when type does not match' do
+ get api("/groups/#{group.id}/runners?type=project_type", user)
+
+ expect(json_response).to be_empty
+ end
+
+ it 'does not filter by invalid type' do
+ get api("/groups/#{group.id}/runners?type=bogus", user)
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ end
+ end
+
+ context 'filter runners by status' do
+ it 'returns runners by valid status' do
+ create(:ci_runner, :group, :inactive, description: 'Inactive group runner', groups: [group])
+
+ get api("/groups/#{group.id}/runners?status=paused", user)
+
+ expect(json_response).to match_array([
+ a_hash_including('description' => 'Inactive group runner')
+ ])
+ end
+
+ it 'does not filter by invalid status' do
+ get api("/groups/#{group.id}/runners?status=bogus", user)
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ end
+ end
+
+ it 'filters runners by tag_list' do
+ create(:ci_runner, :group, description: 'Runner tagged with tag1 and tag2', groups: [group], tag_list: %w[tag1 tag2])
+ create(:ci_runner, :group, description: 'Runner tagged with tag2', groups: [group], tag_list: %w[tag1])
+
+ get api("/groups/#{group.id}/runners?tag_list=tag1,tag2", user)
+
+ expect(json_response).to match_array([
+ a_hash_including('description' => 'Runner tagged with tag1 and tag2')
+ ])
+ end
+ end
+
+ it_behaves_like 'unauthorized access to runners list' do
+ let(:entity_type) { 'groups' }
+ let(:entity) { group }
+ end
+ end
+
+ describe 'POST /projects/:id/runners' do
+ context 'authorized user' do
+ let_it_be(:project_runner2) { create(:ci_runner, :project, projects: [project2]) }
+
+ it 'enables specific runner' do
+ expect do
+ post api("/projects/#{project.id}/runners", user), params: { runner_id: project_runner2.id }
+ end.to change { project.runners.count }.by(+1)
+ expect(response).to have_gitlab_http_status(:created)
+ end
+
+ it 'avoids changes when enabling already enabled runner' do
+ expect do
+ post api("/projects/#{project.id}/runners", user), params: { runner_id: project_runner.id }
+ end.to change { project.runners.count }.by(0)
+ expect(response).to have_gitlab_http_status(:bad_request)
+ end
+
+ it 'does not enable locked runner' do
+ project_runner2.update!(locked: true)
+
+ expect do
+ post api("/projects/#{project.id}/runners", user), params: { runner_id: project_runner2.id }
+ end.to change { project.runners.count }.by(0)
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+
+ it 'does not enable shared runner' do
+ post api("/projects/#{project.id}/runners", user), params: { runner_id: shared_runner.id }
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+
+ it 'does not enable group runner' do
+ post api("/projects/#{project.id}/runners", user), params: { runner_id: group_runner_a.id }
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+
+ context 'user is admin' do
+ context 'when project runner is used' do
+ let!(:new_project_runner) { create(:ci_runner, :project) }
+
+ it 'enables any specific runner' do
+ expect do
+ post api("/projects/#{project.id}/runners", admin), params: { runner_id: new_project_runner.id }
+ end.to change { project.runners.count }.by(+1)
+ expect(response).to have_gitlab_http_status(:created)
+ end
+ end
+
+ it 'enables a instance type runner' do
+ expect do
+ post api("/projects/#{project.id}/runners", admin), params: { runner_id: shared_runner.id }
+ end.to change { project.runners.count }.by(1)
+
+ expect(shared_runner.reload).not_to be_instance_type
+ expect(response).to have_gitlab_http_status(:created)
+ end
+ end
+
+ it 'raises an error when no runner_id param is provided' do
+ post api("/projects/#{project.id}/runners", admin)
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ end
+ end
+
+ context 'user is not admin' do
+ let!(:new_project_runner) { create(:ci_runner, :project) }
+
+ it 'does not enable runner without access to' do
+ post api("/projects/#{project.id}/runners", user), params: { runner_id: new_project_runner.id }
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+ end
+
+ context 'authorized user without permissions' do
+ it 'does not enable runner' do
+ post api("/projects/#{project.id}/runners", user2)
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+ end
+
+ context 'unauthorized user' do
+ it 'does not enable runner' do
+ post api("/projects/#{project.id}/runners")
+
+ expect(response).to have_gitlab_http_status(:unauthorized)
+ end
+ end
+ end
+
+ describe 'DELETE /projects/:id/runners/:runner_id' do
+ context 'authorized user' do
+ context 'when runner have more than one associated projects' do
+ it "disables project's runner" do
+ expect do
+ delete api("/projects/#{project.id}/runners/#{two_projects_runner.id}", user)
+
+ expect(response).to have_gitlab_http_status(:no_content)
+ end.to change { project.runners.count }.by(-1)
+ end
+
+ it_behaves_like '412 response' do
+ let(:request) { api("/projects/#{project.id}/runners/#{two_projects_runner.id}", user) }
+ end
+ end
+
+ context 'when runner have one associated projects' do
+ it "does not disable project's runner" do
+ expect do
+ delete api("/projects/#{project.id}/runners/#{project_runner.id}", user)
+ end.to change { project.runners.count }.by(0)
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+ end
+
+ it 'returns 404 is runner is not found' do
+ delete api("/projects/#{project.id}/runners/0", user)
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+
+ context 'authorized user without permissions' do
+ it "does not disable project's runner" do
+ delete api("/projects/#{project.id}/runners/#{project_runner.id}", user2)
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+ end
+
+ context 'unauthorized user' do
+ it "does not disable project's runner" do
+ delete api("/projects/#{project.id}/runners/#{project_runner.id}")
+
+ expect(response).to have_gitlab_http_status(:unauthorized)
+ end
+ end
+ end
+end
diff --git a/spec/requests/api/commit_statuses_spec.rb b/spec/requests/api/commit_statuses_spec.rb
index 0c0bf8b4df0..bec15b788c3 100644
--- a/spec/requests/api/commit_statuses_spec.rb
+++ b/spec/requests/api/commit_statuses_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::CommitStatuses do
+RSpec.describe API::CommitStatuses do
let!(:project) { create(:project, :repository) }
let(:commit) { project.repository.commit }
let(:guest) { create_user(:guest) }
diff --git a/spec/requests/api/commits_spec.rb b/spec/requests/api/commits_spec.rb
index a423c92e2fb..724e3177173 100644
--- a/spec/requests/api/commits_spec.rb
+++ b/spec/requests/api/commits_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require 'mime/types'
-describe API::Commits do
+RSpec.describe API::Commits do
include ProjectForksHelper
let(:user) { create(:user) }
diff --git a/spec/requests/api/composer_packages_spec.rb b/spec/requests/api/composer_packages_spec.rb
new file mode 100644
index 00000000000..d756a7700f6
--- /dev/null
+++ b/spec/requests/api/composer_packages_spec.rb
@@ -0,0 +1,302 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+RSpec.describe API::ComposerPackages do
+ include PackagesManagerApiSpecHelpers
+
+ let_it_be(:user) { create(:user) }
+ let_it_be(:group, reload: true) { create(:group, :public) }
+ let_it_be(:personal_access_token) { create(:personal_access_token, user: user) }
+ let_it_be(:package_name) { 'package-name' }
+ let_it_be(:project, reload: true) { create(:project, :custom_repo, files: { 'composer.json' => { name: package_name }.to_json }, group: group) }
+ let(:headers) { {} }
+
+ describe 'GET /api/v4/group/:id/-/packages/composer/packages' do
+ let(:url) { "/group/#{group.id}/-/packages/composer/packages.json" }
+
+ subject { get api(url), headers: headers }
+
+ context 'without the need for a license' do
+ context 'with valid project' do
+ let!(:package) { create(:composer_package, :with_metadatum, project: project) }
+
+ using RSpec::Parameterized::TableSyntax
+
+ where(:project_visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do
+ 'PUBLIC' | :developer | true | true | 'Composer package index' | :success
+ 'PUBLIC' | :guest | true | true | 'Composer package index' | :success
+ 'PUBLIC' | :developer | true | false | 'Composer package index' | :success
+ 'PUBLIC' | :guest | true | false | 'Composer package index' | :success
+ 'PUBLIC' | :developer | false | true | 'Composer package index' | :success
+ 'PUBLIC' | :guest | false | true | 'Composer package index' | :success
+ 'PUBLIC' | :developer | false | false | 'Composer package index' | :success
+ 'PUBLIC' | :guest | false | false | 'Composer package index' | :success
+ 'PUBLIC' | :anonymous | false | true | 'Composer package index' | :success
+ 'PRIVATE' | :developer | true | true | 'Composer package index' | :success
+ 'PRIVATE' | :guest | true | true | 'Composer package index' | :success
+ 'PRIVATE' | :developer | true | false | 'process Composer api request' | :not_found
+ 'PRIVATE' | :guest | true | false | 'process Composer api request' | :not_found
+ 'PRIVATE' | :developer | false | true | 'process Composer api request' | :not_found
+ 'PRIVATE' | :guest | false | true | 'process Composer api request' | :not_found
+ 'PRIVATE' | :developer | false | false | 'process Composer api request' | :not_found
+ 'PRIVATE' | :guest | false | false | 'process Composer api request' | :not_found
+ 'PRIVATE' | :anonymous | false | true | 'process Composer api request' | :not_found
+ end
+
+ with_them do
+ include_context 'Composer api group access', params[:project_visibility_level], params[:user_role], params[:user_token] do
+ it_behaves_like params[:shared_examples_name], params[:user_role], params[:expected_status], params[:member]
+ end
+ end
+ end
+
+ it_behaves_like 'rejects Composer access with unknown group id'
+ end
+ end
+
+ describe 'GET /api/v4/group/:id/-/packages/composer/p/:sha.json' do
+ let(:sha) { '123' }
+ let(:url) { "/group/#{group.id}/-/packages/composer/p/#{sha}.json" }
+ let!(:package) { create(:composer_package, :with_metadatum, project: project) }
+
+ subject { get api(url), headers: headers }
+
+ context 'without the need for a license' do
+ context 'with valid project' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:project_visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do
+ 'PUBLIC' | :developer | true | true | 'Composer provider index' | :success
+ 'PUBLIC' | :guest | true | true | 'Composer provider index' | :success
+ 'PUBLIC' | :developer | true | false | 'Composer provider index' | :success
+ 'PUBLIC' | :guest | true | false | 'Composer provider index' | :success
+ 'PUBLIC' | :developer | false | true | 'Composer provider index' | :success
+ 'PUBLIC' | :guest | false | true | 'Composer provider index' | :success
+ 'PUBLIC' | :developer | false | false | 'Composer provider index' | :success
+ 'PUBLIC' | :guest | false | false | 'Composer provider index' | :success
+ 'PUBLIC' | :anonymous | false | true | 'Composer provider index' | :success
+ 'PRIVATE' | :developer | true | true | 'Composer provider index' | :success
+ 'PRIVATE' | :guest | true | true | 'Composer empty provider index' | :success
+ 'PRIVATE' | :developer | true | false | 'process Composer api request' | :not_found
+ 'PRIVATE' | :guest | true | false | 'process Composer api request' | :not_found
+ 'PRIVATE' | :developer | false | true | 'process Composer api request' | :not_found
+ 'PRIVATE' | :guest | false | true | 'process Composer api request' | :not_found
+ 'PRIVATE' | :developer | false | false | 'process Composer api request' | :not_found
+ 'PRIVATE' | :guest | false | false | 'process Composer api request' | :not_found
+ 'PRIVATE' | :anonymous | false | true | 'process Composer api request' | :not_found
+ end
+
+ with_them do
+ include_context 'Composer api group access', params[:project_visibility_level], params[:user_role], params[:user_token] do
+ it_behaves_like params[:shared_examples_name], params[:user_role], params[:expected_status], params[:member]
+ end
+ end
+ end
+
+ it_behaves_like 'rejects Composer access with unknown group id'
+ end
+ end
+
+ describe 'GET /api/v4/group/:id/-/packages/composer/*package_name.json' do
+ let(:package_name) { 'foobar' }
+ let(:url) { "/group/#{group.id}/-/packages/composer/#{package_name}.json" }
+
+ subject { get api(url), headers: headers }
+
+ context 'without the need for a license' do
+ context 'with no packages' do
+ include_context 'Composer user type', :developer, true do
+ it_behaves_like 'returning response status', :not_found
+ end
+ end
+
+ context 'with valid project' do
+ using RSpec::Parameterized::TableSyntax
+
+ let!(:package) { create(:composer_package, :with_metadatum, name: package_name, project: project) }
+
+ where(:project_visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do
+ 'PUBLIC' | :developer | true | true | 'Composer package api request' | :success
+ 'PUBLIC' | :guest | true | true | 'Composer package api request' | :success
+ 'PUBLIC' | :developer | true | false | 'Composer package api request' | :success
+ 'PUBLIC' | :guest | true | false | 'Composer package api request' | :success
+ 'PUBLIC' | :developer | false | true | 'Composer package api request' | :success
+ 'PUBLIC' | :guest | false | true | 'Composer package api request' | :success
+ 'PUBLIC' | :developer | false | false | 'Composer package api request' | :success
+ 'PUBLIC' | :guest | false | false | 'Composer package api request' | :success
+ 'PUBLIC' | :anonymous | false | true | 'Composer package api request' | :success
+ 'PRIVATE' | :developer | true | true | 'Composer package api request' | :success
+ 'PRIVATE' | :guest | true | true | 'process Composer api request' | :not_found
+ 'PRIVATE' | :developer | true | false | 'process Composer api request' | :not_found
+ 'PRIVATE' | :guest | true | false | 'process Composer api request' | :not_found
+ 'PRIVATE' | :developer | false | true | 'process Composer api request' | :not_found
+ 'PRIVATE' | :guest | false | true | 'process Composer api request' | :not_found
+ 'PRIVATE' | :developer | false | false | 'process Composer api request' | :not_found
+ 'PRIVATE' | :guest | false | false | 'process Composer api request' | :not_found
+ 'PRIVATE' | :anonymous | false | true | 'process Composer api request' | :not_found
+ end
+
+ with_them do
+ include_context 'Composer api group access', params[:project_visibility_level], params[:user_role], params[:user_token] do
+ it_behaves_like params[:shared_examples_name], params[:user_role], params[:expected_status], params[:member]
+ end
+ end
+ end
+
+ it_behaves_like 'rejects Composer access with unknown group id'
+ end
+ end
+
+ describe 'POST /api/v4/projects/:id/packages/composer' do
+ let(:url) { "/projects/#{project.id}/packages/composer" }
+ let(:params) { {} }
+
+ before(:all) do
+ project.repository.add_tag(user, 'v1.2.99', 'master')
+ end
+
+ subject { post api(url), headers: headers, params: params }
+
+ shared_examples 'composer package publish' do
+ context 'without the need for a license' do
+ context 'with valid project' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:project_visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do
+ 'PUBLIC' | :developer | true | true | 'Composer package creation' | :created
+ 'PUBLIC' | :guest | true | true | 'process Composer api request' | :forbidden
+ 'PUBLIC' | :developer | true | false | 'process Composer api request' | :unauthorized
+ 'PUBLIC' | :guest | true | false | 'process Composer api request' | :unauthorized
+ 'PUBLIC' | :developer | false | true | 'process Composer api request' | :forbidden
+ 'PUBLIC' | :guest | false | true | 'process Composer api request' | :forbidden
+ 'PUBLIC' | :developer | false | false | 'process Composer api request' | :unauthorized
+ 'PUBLIC' | :guest | false | false | 'process Composer api request' | :unauthorized
+ 'PUBLIC' | :anonymous | false | true | 'process Composer api request' | :unauthorized
+ 'PRIVATE' | :developer | true | true | 'Composer package creation' | :created
+ 'PRIVATE' | :guest | true | true | 'process Composer api request' | :forbidden
+ 'PRIVATE' | :developer | true | false | 'process Composer api request' | :unauthorized
+ 'PRIVATE' | :guest | true | false | 'process Composer api request' | :unauthorized
+ 'PRIVATE' | :developer | false | true | 'process Composer api request' | :not_found
+ 'PRIVATE' | :guest | false | true | 'process Composer api request' | :not_found
+ 'PRIVATE' | :developer | false | false | 'process Composer api request' | :unauthorized
+ 'PRIVATE' | :guest | false | false | 'process Composer api request' | :unauthorized
+ 'PRIVATE' | :anonymous | false | true | 'process Composer api request' | :unauthorized
+ end
+
+ with_them do
+ include_context 'Composer api project access', params[:project_visibility_level], params[:user_role], params[:user_token] do
+ it_behaves_like params[:shared_examples_name], params[:user_role], params[:expected_status], params[:member]
+ end
+ end
+ end
+
+ it_behaves_like 'rejects Composer access with unknown project id'
+ end
+ end
+
+ context 'with no tag or branch params' do
+ let(:headers) { build_basic_auth_header(user.username, personal_access_token.token) }
+
+ it_behaves_like 'process Composer api request', :developer, :bad_request
+ end
+
+ context 'with a tag' do
+ context 'with an existing branch' do
+ let(:params) { { tag: 'v1.2.99' } }
+
+ it_behaves_like 'composer package publish'
+ end
+
+ context 'with a non existing tag' do
+ let(:params) { { tag: 'non-existing-tag' } }
+ let(:headers) { build_basic_auth_header(user.username, personal_access_token.token) }
+
+ it_behaves_like 'process Composer api request', :developer, :not_found
+ end
+ end
+
+ context 'with a branch' do
+ context 'with an existing branch' do
+ let(:params) { { branch: 'master' } }
+
+ it_behaves_like 'composer package publish'
+ end
+
+ context 'with a non existing branch' do
+ let(:params) { { branch: 'non-existing-branch' } }
+ let(:headers) { build_basic_auth_header(user.username, personal_access_token.token) }
+
+ it_behaves_like 'process Composer api request', :developer, :not_found
+ end
+ end
+ end
+
+ describe 'GET /api/v4/projects/:id/packages/composer/archives/*package_name?sha=:sha' do
+ let(:sha) { '123' }
+ let(:url) { "/projects/#{project.id}/packages/composer/archives/#{package_name}.zip" }
+ let(:params) { { sha: sha } }
+
+ subject { get api(url), headers: headers, params: params }
+
+ context 'without the need for a license' do
+ context 'with valid project' do
+ let!(:package) { create(:composer_package, :with_metadatum, name: package_name, project: project) }
+
+ context 'when the sha does not match the package name' do
+ let(:sha) { '123' }
+
+ it_behaves_like 'process Composer api request', :anonymous, :not_found
+ end
+
+ context 'when the package name does not match the sha' do
+ let(:branch) { project.repository.find_branch('master') }
+ let(:sha) { branch.target }
+ let(:url) { "/projects/#{project.id}/packages/composer/archives/unexisting-package-name.zip" }
+
+ it_behaves_like 'process Composer api request', :anonymous, :not_found
+ end
+
+ context 'with a match package name and sha' do
+ let(:branch) { project.repository.find_branch('master') }
+ let(:sha) { branch.target }
+
+ using RSpec::Parameterized::TableSyntax
+
+ where(:project_visibility_level, :user_role, :member, :user_token, :expected_status) do
+ 'PUBLIC' | :developer | true | true | :success
+ 'PUBLIC' | :guest | true | true | :success
+ 'PUBLIC' | :developer | true | false | :success
+ 'PUBLIC' | :guest | true | false | :success
+ 'PUBLIC' | :developer | false | true | :success
+ 'PUBLIC' | :guest | false | true | :success
+ 'PUBLIC' | :developer | false | false | :success
+ 'PUBLIC' | :guest | false | false | :success
+ 'PUBLIC' | :anonymous | false | true | :success
+ 'PRIVATE' | :developer | true | true | :success
+ 'PRIVATE' | :guest | true | true | :success
+ 'PRIVATE' | :developer | true | false | :success
+ 'PRIVATE' | :guest | true | false | :success
+ 'PRIVATE' | :developer | false | true | :success
+ 'PRIVATE' | :guest | false | true | :success
+ 'PRIVATE' | :developer | false | false | :success
+ 'PRIVATE' | :guest | false | false | :success
+ 'PRIVATE' | :anonymous | false | true | :success
+ end
+
+ with_them do
+ let(:token) { user_token ? personal_access_token.token : 'wrong' }
+ let(:headers) { user_role == :anonymous ? {} : build_basic_auth_header(user.username, token) }
+
+ before do
+ project.update!(visibility_level: Gitlab::VisibilityLevel.const_get(project_visibility_level, false))
+ end
+
+ it_behaves_like 'process Composer api request', params[:user_role], params[:expected_status], params[:member]
+ end
+ end
+ end
+
+ it_behaves_like 'rejects Composer access with unknown project id'
+ end
+ end
+end
diff --git a/spec/requests/api/conan_packages_spec.rb b/spec/requests/api/conan_packages_spec.rb
new file mode 100644
index 00000000000..1d88eaef79c
--- /dev/null
+++ b/spec/requests/api/conan_packages_spec.rb
@@ -0,0 +1,840 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+RSpec.describe API::ConanPackages do
+ include WorkhorseHelpers
+ include PackagesManagerApiSpecHelpers
+
+ let(:package) { create(:conan_package) }
+ let_it_be(:personal_access_token) { create(:personal_access_token) }
+ let_it_be(:user) { personal_access_token.user }
+ let(:project) { package.project }
+
+ let(:base_secret) { SecureRandom.base64(64) }
+ let(:auth_token) { personal_access_token.token }
+ let(:job) { create(:ci_build, user: user) }
+ let(:job_token) { job.token }
+ let(:deploy_token) { create(:deploy_token, read_package_registry: true, write_package_registry: true) }
+ let(:project_deploy_token) { create(:project_deploy_token, deploy_token: deploy_token, project: project) }
+
+ let(:headers) do
+ { 'HTTP_AUTHORIZATION' => ActionController::HttpAuthentication::Basic.encode_credentials('foo', auth_token) }
+ end
+
+ let(:jwt_secret) do
+ OpenSSL::HMAC.hexdigest(
+ OpenSSL::Digest::SHA256.new,
+ base_secret,
+ Gitlab::ConanToken::HMAC_KEY
+ )
+ end
+
+ before do
+ project.add_developer(user)
+ allow(Settings).to receive(:attr_encrypted_db_key_base).and_return(base_secret)
+ end
+
+ describe 'GET /api/v4/packages/conan/v1/ping' do
+ it 'responds with 401 Unauthorized when no token provided' do
+ get api('/packages/conan/v1/ping')
+
+ expect(response).to have_gitlab_http_status(:unauthorized)
+ end
+
+ it 'responds with 200 OK when valid token is provided' do
+ jwt = build_jwt(personal_access_token)
+ get api('/packages/conan/v1/ping'), headers: build_token_auth_header(jwt.encoded)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response.headers['X-Conan-Server-Capabilities']).to eq("")
+ end
+
+ it 'responds with 200 OK when valid job token is provided' do
+ jwt = build_jwt_from_job(job)
+ get api('/packages/conan/v1/ping'), headers: build_token_auth_header(jwt.encoded)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response.headers['X-Conan-Server-Capabilities']).to eq("")
+ end
+
+ it 'responds with 200 OK when valid deploy token is provided' do
+ jwt = build_jwt_from_deploy_token(deploy_token)
+ get api('/packages/conan/v1/ping'), headers: build_token_auth_header(jwt.encoded)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response.headers['X-Conan-Server-Capabilities']).to eq("")
+ end
+
+ it 'responds with 401 Unauthorized when invalid access token ID is provided' do
+ jwt = build_jwt(double(id: 12345), user_id: personal_access_token.user_id)
+ get api('/packages/conan/v1/ping'), headers: build_token_auth_header(jwt.encoded)
+
+ expect(response).to have_gitlab_http_status(:unauthorized)
+ end
+
+ it 'responds with 401 Unauthorized when invalid user is provided' do
+ jwt = build_jwt(personal_access_token, user_id: 12345)
+ get api('/packages/conan/v1/ping'), headers: build_token_auth_header(jwt.encoded)
+
+ expect(response).to have_gitlab_http_status(:unauthorized)
+ end
+
+ it 'responds with 401 Unauthorized when the provided JWT is signed with different secret' do
+ jwt = build_jwt(personal_access_token, secret: SecureRandom.base64(32))
+ get api('/packages/conan/v1/ping'), headers: build_token_auth_header(jwt.encoded)
+
+ expect(response).to have_gitlab_http_status(:unauthorized)
+ end
+
+ it 'responds with 401 Unauthorized when invalid JWT is provided' do
+ get api('/packages/conan/v1/ping'), headers: build_token_auth_header('invalid-jwt')
+
+ expect(response).to have_gitlab_http_status(:unauthorized)
+ end
+
+ context 'packages feature disabled' do
+ it 'responds with 404 Not Found' do
+ stub_packages_setting(enabled: false)
+ get api('/packages/conan/v1/ping')
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+ end
+
+ describe 'GET /api/v4/packages/conan/v1/conans/search' do
+ before do
+ project.update!(visibility_level: Gitlab::VisibilityLevel::PUBLIC)
+
+ get api('/packages/conan/v1/conans/search'), headers: headers, params: params
+ end
+
+ subject { json_response['results'] }
+
+ context 'returns packages with a matching name' do
+ let(:params) { { q: package.conan_recipe } }
+
+ it { is_expected.to contain_exactly(package.conan_recipe) }
+ end
+
+ context 'returns packages using a * wildcard' do
+ let(:params) { { q: "#{package.name[0, 3]}*" } }
+
+ it { is_expected.to contain_exactly(package.conan_recipe) }
+ end
+
+ context 'does not return non-matching packages' do
+ let(:params) { { q: "foo" } }
+
+ it { is_expected.to be_blank }
+ end
+ end
+
+ describe 'GET /api/v4/packages/conan/v1/users/authenticate' do
+ subject { get api('/packages/conan/v1/users/authenticate'), headers: headers }
+
+ context 'when using invalid token' do
+ let(:auth_token) { 'invalid_token' }
+
+ it 'responds with 401' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:unauthorized)
+ end
+ end
+
+ context 'when valid JWT access token is provided' do
+ it 'responds with 200' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+
+ it 'token has valid validity time' do
+ Timecop.freeze do
+ subject
+
+ payload = JSONWebToken::HMACToken.decode(
+ response.body, jwt_secret).first
+ expect(payload['access_token']).to eq(personal_access_token.id)
+ expect(payload['user_id']).to eq(personal_access_token.user_id)
+
+ duration = payload['exp'] - payload['iat']
+ expect(duration).to eq(1.hour)
+ end
+ end
+ end
+
+ context 'with valid job token' do
+ let(:auth_token) { job_token }
+
+ it 'responds with 200' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+ end
+
+ context 'with valid deploy token' do
+ let(:auth_token) { deploy_token.token }
+
+ it 'responds with 200' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+ end
+ end
+
+ describe 'GET /api/v4/packages/conan/v1/users/check_credentials' do
+ it 'responds with a 200 OK with PAT' do
+ get api('/packages/conan/v1/users/check_credentials'), headers: headers
+
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+
+ context 'with job token' do
+ let(:auth_token) { job_token }
+
+ it 'responds with a 200 OK with job token' do
+ get api('/packages/conan/v1/users/check_credentials'), headers: headers
+
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+ end
+
+ context 'with deploy token' do
+ let(:auth_token) { deploy_token.token }
+
+ it 'responds with a 200 OK with job token' do
+ get api('/packages/conan/v1/users/check_credentials'), headers: headers
+
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+ end
+
+ it 'responds with a 401 Unauthorized when an invalid token is used' do
+ get api('/packages/conan/v1/users/check_credentials'), headers: build_token_auth_header('invalid-token')
+
+ expect(response).to have_gitlab_http_status(:unauthorized)
+ end
+ end
+
+ shared_examples 'rejects invalid recipe' do
+ context 'with invalid recipe path' do
+ let(:recipe_path) { '../../foo++../..' }
+
+ it 'returns 400' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ end
+ end
+ end
+
+ shared_examples 'rejects recipe for invalid project' do
+ context 'with invalid recipe path' do
+ let(:recipe_path) { 'aa/bb/not-existing-project/ccc' }
+
+ it 'returns forbidden' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+ end
+ end
+
+ shared_examples 'rejects recipe for not found package' do
+ context 'with invalid recipe path' do
+ let(:recipe_path) do
+ 'aa/bb/%{project}/ccc' % { project: ::Packages::Conan::Metadatum.package_username_from(full_path: project.full_path) }
+ end
+
+ it 'returns not found' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+ end
+
+ shared_examples 'empty recipe for not found package' do
+ context 'with invalid recipe url' do
+ let(:recipe_path) do
+ 'aa/bb/%{project}/ccc' % { project: ::Packages::Conan::Metadatum.package_username_from(full_path: project.full_path) }
+ end
+
+ it 'returns not found' do
+ allow(::Packages::Conan::PackagePresenter).to receive(:new)
+ .with(
+ 'aa/bb@%{project}/ccc' % { project: ::Packages::Conan::Metadatum.package_username_from(full_path: project.full_path) },
+ user,
+ project,
+ any_args
+ ).and_return(presenter)
+ allow(presenter).to receive(:recipe_snapshot) { {} }
+ allow(presenter).to receive(:package_snapshot) { {} }
+
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response.body).to eq("{}")
+ end
+ end
+ end
+
+ shared_examples 'recipe download_urls' do
+ let(:recipe_path) { package.conan_recipe_path }
+
+ it 'returns the download_urls for the recipe files' do
+ expected_response = {
+ 'conanfile.py' => "#{Settings.gitlab.base_url}/api/v4/packages/conan/v1/files/#{package.conan_recipe_path}/0/export/conanfile.py",
+ 'conanmanifest.txt' => "#{Settings.gitlab.base_url}/api/v4/packages/conan/v1/files/#{package.conan_recipe_path}/0/export/conanmanifest.txt"
+ }
+
+ allow(presenter).to receive(:recipe_urls) { expected_response }
+
+ subject
+
+ expect(json_response).to eq(expected_response)
+ end
+ end
+
+ shared_examples 'package download_urls' do
+ let(:recipe_path) { package.conan_recipe_path }
+
+ it 'returns the download_urls for the package files' do
+ expected_response = {
+ 'conaninfo.txt' => "#{Settings.gitlab.base_url}/api/v4/packages/conan/v1/files/#{package.conan_recipe_path}/0/package/123456789/0/conaninfo.txt",
+ 'conanmanifest.txt' => "#{Settings.gitlab.base_url}/api/v4/packages/conan/v1/files/#{package.conan_recipe_path}/0/package/123456789/0/conanmanifest.txt",
+ 'conan_package.tgz' => "#{Settings.gitlab.base_url}/api/v4/packages/conan/v1/files/#{package.conan_recipe_path}/0/package/123456789/0/conan_package.tgz"
+ }
+
+ allow(presenter).to receive(:package_urls) { expected_response }
+
+ subject
+
+ expect(json_response).to eq(expected_response)
+ end
+ end
+
+ context 'recipe endpoints' do
+ let(:jwt) { build_jwt(personal_access_token) }
+ let(:headers) { build_token_auth_header(jwt.encoded) }
+ let(:conan_package_reference) { '123456789' }
+ let(:presenter) { double('::Packages::Conan::PackagePresenter') }
+
+ before do
+ allow(::Packages::Conan::PackagePresenter).to receive(:new)
+ .with(package.conan_recipe, user, package.project, any_args)
+ .and_return(presenter)
+ end
+
+ describe 'GET /api/v4/packages/conan/v1/conans/:package_name/package_version/:package_username/:package_channel' do
+ let(:recipe_path) { package.conan_recipe_path }
+
+ subject { get api("/packages/conan/v1/conans/#{recipe_path}"), headers: headers }
+
+ it_behaves_like 'rejects invalid recipe'
+ it_behaves_like 'rejects recipe for invalid project'
+ it_behaves_like 'empty recipe for not found package'
+
+ context 'with existing package' do
+ it 'returns a hash of files with their md5 hashes' do
+ expected_response = {
+ 'conanfile.py' => 'md5hash1',
+ 'conanmanifest.txt' => 'md5hash2'
+ }
+
+ allow(presenter).to receive(:recipe_snapshot) { expected_response }
+
+ subject
+
+ expect(json_response).to eq(expected_response)
+ end
+ end
+ end
+
+ describe 'GET /api/v4/packages/conan/v1/conans/:package_name/package_version/:package_username/:package_channel/packages/:conan_package_reference' do
+ let(:recipe_path) { package.conan_recipe_path }
+
+ subject { get api("/packages/conan/v1/conans/#{recipe_path}/packages/#{conan_package_reference}"), headers: headers }
+
+ it_behaves_like 'rejects invalid recipe'
+ it_behaves_like 'rejects recipe for invalid project'
+ it_behaves_like 'empty recipe for not found package'
+
+ context 'with existing package' do
+ it 'returns a hash of md5 values for the files' do
+ expected_response = {
+ 'conaninfo.txt' => "md5hash1",
+ 'conanmanifest.txt' => "md5hash2",
+ 'conan_package.tgz' => "md5hash3"
+ }
+
+ allow(presenter).to receive(:package_snapshot) { expected_response }
+
+ subject
+
+ expect(json_response).to eq(expected_response)
+ end
+ end
+ end
+
+ describe 'GET /api/v4/packages/conan/v1/conans/:package_name/package_version/:package_username/:package_channel/digest' do
+ subject { get api("/packages/conan/v1/conans/#{recipe_path}/digest"), headers: headers }
+
+ it_behaves_like 'rejects invalid recipe'
+ it_behaves_like 'rejects recipe for invalid project'
+ it_behaves_like 'recipe download_urls'
+ end
+
+ describe 'GET /api/v4/packages/conan/v1/conans/:package_name/package_version/:package_username/:package_channel/packages/:conan_package_reference/download_urls' do
+ subject { get api("/packages/conan/v1/conans/#{recipe_path}/packages/#{conan_package_reference}/download_urls"), headers: headers }
+
+ it_behaves_like 'rejects invalid recipe'
+ it_behaves_like 'rejects recipe for invalid project'
+ it_behaves_like 'package download_urls'
+ end
+
+ describe 'GET /api/v4/packages/conan/v1/conans/:package_name/package_version/:package_username/:package_channel/download_urls' do
+ subject { get api("/packages/conan/v1/conans/#{recipe_path}/download_urls"), headers: headers }
+
+ it_behaves_like 'rejects invalid recipe'
+ it_behaves_like 'rejects recipe for invalid project'
+ it_behaves_like 'recipe download_urls'
+ end
+
+ describe 'GET /api/v4/packages/conan/v1/conans/:package_name/package_version/:package_username/:package_channel/packages/:conan_package_reference/digest' do
+ subject { get api("/packages/conan/v1/conans/#{recipe_path}/packages/#{conan_package_reference}/digest"), headers: headers }
+
+ it_behaves_like 'rejects invalid recipe'
+ it_behaves_like 'rejects recipe for invalid project'
+ it_behaves_like 'package download_urls'
+ end
+
+ describe 'POST /api/v4/packages/conan/v1/conans/:package_name/package_version/:package_username/:package_channel/upload_urls' do
+ let(:recipe_path) { package.conan_recipe_path }
+
+ let(:params) do
+ { "conanfile.py": 24,
+ "conanmanifext.txt": 123 }
+ end
+
+ subject { post api("/packages/conan/v1/conans/#{recipe_path}/upload_urls"), params: params, headers: headers }
+
+ it_behaves_like 'rejects invalid recipe'
+
+ it 'returns a set of upload urls for the files requested' do
+ subject
+
+ expected_response = {
+ 'conanfile.py': "#{Settings.gitlab.base_url}/api/v4/packages/conan/v1/files/#{package.conan_recipe_path}/0/export/conanfile.py",
+ 'conanmanifest.txt': "#{Settings.gitlab.base_url}/api/v4/packages/conan/v1/files/#{package.conan_recipe_path}/0/export/conanmanifest.txt"
+ }
+
+ expect(response.body).to eq(expected_response.to_json)
+ end
+ end
+
+ describe 'POST /api/v4/packages/conan/v1/conans/:package_name/package_version/:package_username/:package_channel/packages/:conan_package_reference/upload_urls' do
+ let(:recipe_path) { package.conan_recipe_path }
+
+ let(:params) do
+ { "conaninfo.txt": 24,
+ "conanmanifext.txt": 123,
+ "conan_package.tgz": 523 }
+ end
+
+ subject { post api("/packages/conan/v1/conans/#{recipe_path}/packages/123456789/upload_urls"), params: params, headers: headers }
+
+ it_behaves_like 'rejects invalid recipe'
+
+ it 'returns a set of upload urls for the files requested' do
+ expected_response = {
+ 'conaninfo.txt': "#{Settings.gitlab.base_url}/api/v4/packages/conan/v1/files/#{package.conan_recipe_path}/0/package/123456789/0/conaninfo.txt",
+ 'conanmanifest.txt': "#{Settings.gitlab.base_url}/api/v4/packages/conan/v1/files/#{package.conan_recipe_path}/0/package/123456789/0/conanmanifest.txt",
+ 'conan_package.tgz': "#{Settings.gitlab.base_url}/api/v4/packages/conan/v1/files/#{package.conan_recipe_path}/0/package/123456789/0/conan_package.tgz"
+ }
+
+ subject
+
+ expect(response.body).to eq(expected_response.to_json)
+ end
+ end
+
+ describe 'DELETE /api/v4/packages/conan/v1/conans/:package_name/package_version/:package_username/:package_channel' do
+ let(:recipe_path) { package.conan_recipe_path }
+
+ subject { delete api("/packages/conan/v1/conans/#{recipe_path}"), headers: headers}
+
+ it_behaves_like 'rejects invalid recipe'
+
+ it 'returns unauthorized for users without valid permission' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+
+ context 'with delete permissions' do
+ before do
+ project.add_maintainer(user)
+ end
+
+ it_behaves_like 'a gitlab tracking event', described_class.name, 'delete_package'
+
+ it 'deletes a package' do
+ expect { subject }.to change { Packages::Package.count }.from(2).to(1)
+ end
+ end
+ end
+ end
+
+ context 'file endpoints' do
+ let(:jwt) { build_jwt(personal_access_token) }
+ let(:headers) { build_token_auth_header(jwt.encoded) }
+ let(:recipe_path) { package.conan_recipe_path }
+
+ shared_examples 'denies download with no token' do
+ context 'with no private token' do
+ let(:headers) { {} }
+
+ it 'returns 400' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:unauthorized)
+ end
+ end
+ end
+
+ shared_examples 'a public project with packages' do
+ it 'returns the file' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response.media_type).to eq('application/octet-stream')
+ end
+ end
+
+ shared_examples 'an internal project with packages' do
+ before do
+ project.team.truncate
+ project.update!(visibility_level: Gitlab::VisibilityLevel::INTERNAL)
+ end
+
+ it_behaves_like 'denies download with no token'
+
+ it 'returns the file' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response.media_type).to eq('application/octet-stream')
+ end
+ end
+
+ shared_examples 'a private project with packages' do
+ before do
+ project.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE)
+ end
+
+ it_behaves_like 'denies download with no token'
+
+ it 'returns the file' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response.media_type).to eq('application/octet-stream')
+ end
+
+ it 'denies download when not enough permissions' do
+ project.add_guest(user)
+
+ subject
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+ end
+
+ shared_examples 'a project is not found' do
+ let(:recipe_path) { 'not/package/for/project' }
+
+ it 'returns forbidden' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+ end
+
+ describe 'GET /api/v4/packages/conan/v1/files/:package_name/package_version/:package_username/:package_channel/
+:recipe_revision/export/:file_name' do
+ let(:recipe_file) { package.package_files.find_by(file_name: 'conanfile.py') }
+ let(:metadata) { recipe_file.conan_file_metadatum }
+
+ subject do
+ get api("/packages/conan/v1/files/#{recipe_path}/#{metadata.recipe_revision}/export/#{recipe_file.file_name}"),
+ headers: headers
+ end
+
+ it_behaves_like 'a public project with packages'
+ it_behaves_like 'an internal project with packages'
+ it_behaves_like 'a private project with packages'
+ it_behaves_like 'a project is not found'
+ end
+
+ describe 'GET /api/v4/packages/conan/v1/files/:package_name/package_version/:package_username/:package_channel/
+:recipe_revision/package/:conan_package_reference/:package_revision/:file_name' do
+ let(:package_file) { package.package_files.find_by(file_name: 'conaninfo.txt') }
+ let(:metadata) { package_file.conan_file_metadatum }
+
+ subject do
+ get api("/packages/conan/v1/files/#{recipe_path}/#{metadata.recipe_revision}/package/#{metadata.conan_package_reference}/#{metadata.package_revision}/#{package_file.file_name}"),
+ headers: headers
+ end
+
+ it_behaves_like 'a public project with packages'
+ it_behaves_like 'an internal project with packages'
+ it_behaves_like 'a private project with packages'
+ it_behaves_like 'a project is not found'
+
+ context 'tracking the conan_package.tgz download' do
+ let(:package_file) { package.package_files.find_by(file_name: ::Packages::Conan::FileMetadatum::PACKAGE_BINARY) }
+
+ it_behaves_like 'a gitlab tracking event', described_class.name, 'pull_package'
+ end
+ end
+ end
+
+ context 'file uploads' do
+ let(:jwt) { build_jwt(personal_access_token) }
+ let(:workhorse_token) { JWT.encode({ 'iss' => 'gitlab-workhorse' }, Gitlab::Workhorse.secret, 'HS256') }
+ let(:workhorse_header) { { 'GitLab-Workhorse' => '1.0', Gitlab::Workhorse::INTERNAL_API_REQUEST_HEADER => workhorse_token } }
+ let(:headers_with_token) { build_token_auth_header(jwt.encoded).merge(workhorse_header) }
+ let(:recipe_path) { "foo/bar/#{project.full_path.tr('/', '+')}/baz"}
+
+ shared_examples 'uploads a package file' do
+ context 'with object storage disabled' do
+ context 'without a file from workhorse' do
+ let(:params) { { file: nil } }
+
+ it_behaves_like 'package workhorse uploads'
+
+ it 'rejects the request' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ end
+ end
+
+ context 'without a token' do
+ it 'rejects request without a token' do
+ headers_with_token.delete('HTTP_AUTHORIZATION')
+
+ subject
+
+ expect(response).to have_gitlab_http_status(:unauthorized)
+ end
+ end
+
+ context 'when params from workhorse are correct' do
+ it 'creates package and stores package file' do
+ expect { subject }
+ .to change { project.packages.count }.by(1)
+ .and change { Packages::PackageFile.count }.by(1)
+
+ expect(response).to have_gitlab_http_status(:ok)
+
+ package_file = project.packages.last.package_files.reload.last
+ expect(package_file.file_name).to eq(params[:file].original_filename)
+ end
+
+ it "doesn't attempt to migrate file to object storage" do
+ expect(ObjectStorage::BackgroundMoveWorker).not_to receive(:perform_async)
+
+ subject
+ end
+ end
+ end
+
+ context 'with object storage enabled' do
+ context 'and direct upload enabled' do
+ let!(:fog_connection) do
+ stub_package_file_object_storage(direct_upload: true)
+ end
+
+ let(:tmp_object) do
+ fog_connection.directories.new(key: 'packages').files.create(
+ key: "tmp/uploads/#{file_name}",
+ body: 'content'
+ )
+ end
+
+ let(:fog_file) { fog_to_uploaded_file(tmp_object) }
+
+ ['123123', '../../123123'].each do |remote_id|
+ context "with invalid remote_id: #{remote_id}" do
+ let(:params) do
+ {
+ file: fog_file,
+ 'file.remote_id' => remote_id
+ }
+ end
+
+ it 'responds with status 403' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+ end
+ end
+
+ context 'with valid remote_id' do
+ let(:params) do
+ {
+ file: fog_file,
+ 'file.remote_id' => file_name
+ }
+ end
+
+ it 'creates package and stores package file' do
+ expect { subject }
+ .to change { project.packages.count }.by(1)
+ .and change { Packages::PackageFile.count }.by(1)
+
+ expect(response).to have_gitlab_http_status(:ok)
+
+ package_file = project.packages.last.package_files.reload.last
+ expect(package_file.file_name).to eq(params[:file].original_filename)
+ expect(package_file.file.read).to eq('content')
+ end
+ end
+ end
+
+ it_behaves_like 'background upload schedules a file migration'
+ end
+ end
+
+ shared_examples 'workhorse authorization' do
+ it 'authorizes posting package with a valid token' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response.media_type).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE)
+ end
+
+ it 'rejects request without a valid token' do
+ headers_with_token['HTTP_AUTHORIZATION'] = 'foo'
+
+ subject
+
+ expect(response).to have_gitlab_http_status(:unauthorized)
+ end
+
+ it 'rejects request without a valid permission' do
+ project.add_guest(user)
+
+ subject
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+
+ it 'rejects requests that bypassed gitlab-workhorse' do
+ headers_with_token.delete(Gitlab::Workhorse::INTERNAL_API_REQUEST_HEADER)
+
+ subject
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+
+ context 'when using remote storage' do
+ context 'when direct upload is enabled' do
+ before do
+ stub_package_file_object_storage(enabled: true, direct_upload: true)
+ end
+
+ it 'responds with status 200, location of package remote store and object details' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response.media_type).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE)
+ expect(json_response).not_to have_key('TempPath')
+ expect(json_response['RemoteObject']).to have_key('ID')
+ expect(json_response['RemoteObject']).to have_key('GetURL')
+ expect(json_response['RemoteObject']).to have_key('StoreURL')
+ expect(json_response['RemoteObject']).to have_key('DeleteURL')
+ expect(json_response['RemoteObject']).not_to have_key('MultipartUpload')
+ end
+ end
+
+ context 'when direct upload is disabled' do
+ before do
+ stub_package_file_object_storage(enabled: true, direct_upload: false)
+ end
+
+ it 'handles as a local file' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response.media_type).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE)
+ expect(json_response['TempPath']).to eq(::Packages::PackageFileUploader.workhorse_local_upload_path)
+ expect(json_response['RemoteObject']).to be_nil
+ end
+ end
+ end
+ end
+
+ describe 'PUT /api/v4/packages/conan/v1/files/:package_name/package_version/:package_username/:package_channel/:recipe_revision/export/:file_name/authorize' do
+ subject { put api("/packages/conan/v1/files/#{recipe_path}/0/export/conanfile.py/authorize"), headers: headers_with_token }
+
+ it_behaves_like 'rejects invalid recipe'
+ it_behaves_like 'workhorse authorization'
+ end
+
+ describe 'PUT /api/v4/packages/conan/v1/files/:package_name/package_version/:package_username/:package_channel/:recipe_revision/export/:conan_package_reference/:package_revision/:file_name/authorize' do
+ subject { put api("/packages/conan/v1/files/#{recipe_path}/0/package/123456789/0/conaninfo.txt/authorize"), headers: headers_with_token }
+
+ it_behaves_like 'rejects invalid recipe'
+ it_behaves_like 'workhorse authorization'
+ end
+
+ describe 'PUT /api/v4/packages/conan/v1/files/:package_name/package_version/:package_username/:package_channel/:recipe_revision/export/:file_name' do
+ let(:file_name) { 'conanfile.py' }
+ let(:params) { { file: temp_file(file_name) } }
+
+ subject do
+ workhorse_finalize(
+ "/api/v4/packages/conan/v1/files/#{recipe_path}/0/export/#{file_name}",
+ method: :put,
+ file_key: :file,
+ params: params,
+ headers: headers_with_token
+ )
+ end
+
+ it_behaves_like 'rejects invalid recipe'
+ it_behaves_like 'uploads a package file'
+ end
+
+ describe 'PUT /api/v4/packages/conan/v1/files/:package_name/package_version/:package_username/:package_channel/:recipe_revision/export/:conan_package_reference/:package_revision/:file_name' do
+ let(:file_name) { 'conaninfo.txt' }
+ let(:params) { { file: temp_file(file_name) } }
+
+ subject do
+ workhorse_finalize(
+ "/api/v4/packages/conan/v1/files/#{recipe_path}/0/package/123456789/0/#{file_name}",
+ method: :put,
+ file_key: :file,
+ params: params,
+ headers: headers_with_token
+ )
+ end
+
+ it_behaves_like 'rejects invalid recipe'
+ it_behaves_like 'uploads a package file'
+ context 'tracking the conan_package.tgz upload' do
+ let(:file_name) { ::Packages::Conan::FileMetadatum::PACKAGE_BINARY }
+
+ it_behaves_like 'a gitlab tracking event', described_class.name, 'push_package'
+ end
+ end
+ end
+end
diff --git a/spec/requests/api/container_registry_event_spec.rb b/spec/requests/api/container_registry_event_spec.rb
index 2cdf2656cb7..4d38ddddffd 100644
--- a/spec/requests/api/container_registry_event_spec.rb
+++ b/spec/requests/api/container_registry_event_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::ContainerRegistryEvent do
+RSpec.describe API::ContainerRegistryEvent do
let(:secret_token) { 'secret_token' }
let(:events) { [{ action: 'push' }] }
let(:registry_headers) { { 'Content-Type' => ::API::ContainerRegistryEvent::DOCKER_DISTRIBUTION_EVENTS_V1_JSON } }
diff --git a/spec/requests/api/deploy_keys_spec.rb b/spec/requests/api/deploy_keys_spec.rb
index e8cc6bc71ae..81cef653770 100644
--- a/spec/requests/api/deploy_keys_spec.rb
+++ b/spec/requests/api/deploy_keys_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::DeployKeys do
+RSpec.describe API::DeployKeys do
let(:user) { create(:user) }
let(:maintainer) { create(:user) }
let(:admin) { create(:admin) }
diff --git a/spec/requests/api/deploy_tokens_spec.rb b/spec/requests/api/deploy_tokens_spec.rb
index 2b86d59fbba..8ec4f888e2e 100644
--- a/spec/requests/api/deploy_tokens_spec.rb
+++ b/spec/requests/api/deploy_tokens_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::DeployTokens do
+RSpec.describe API::DeployTokens do
let_it_be(:user) { create(:user) }
let_it_be(:creator) { create(:user) }
let_it_be(:project) { create(:project, creator_id: creator.id) }
diff --git a/spec/requests/api/deployments_spec.rb b/spec/requests/api/deployments_spec.rb
index ef2415a0cde..8113de96ac4 100644
--- a/spec/requests/api/deployments_spec.rb
+++ b/spec/requests/api/deployments_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Deployments do
+RSpec.describe API::Deployments do
let(:user) { create(:user) }
let(:non_member) { create(:user) }
diff --git a/spec/requests/api/discussions_spec.rb b/spec/requests/api/discussions_spec.rb
index cb3efb2cf5f..720ea429c2c 100644
--- a/spec/requests/api/discussions_spec.rb
+++ b/spec/requests/api/discussions_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Discussions do
+RSpec.describe API::Discussions do
let(:user) { create(:user) }
let!(:project) { create(:project, :public, :repository, namespace: user.namespace) }
let(:private_user) { create(:user) }
diff --git a/spec/requests/api/doorkeeper_access_spec.rb b/spec/requests/api/doorkeeper_access_spec.rb
index a25a6485f47..f16cd58bb34 100644
--- a/spec/requests/api/doorkeeper_access_spec.rb
+++ b/spec/requests/api/doorkeeper_access_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'doorkeeper access' do
+RSpec.describe 'doorkeeper access' do
let!(:user) { create(:user) }
let!(:application) { Doorkeeper::Application.create!(name: "MyApp", redirect_uri: "https://app.com", owner: user) }
let!(:token) { Doorkeeper::AccessToken.create! application_id: application.id, resource_owner_id: user.id, scopes: "api" }
diff --git a/spec/requests/api/environments_spec.rb b/spec/requests/api/environments_spec.rb
index 91b3dd93433..b1ac8f9eeec 100644
--- a/spec/requests/api/environments_spec.rb
+++ b/spec/requests/api/environments_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Environments do
+RSpec.describe API::Environments do
let(:user) { create(:user) }
let(:non_member) { create(:user) }
let(:project) { create(:project, :private, :repository, namespace: user.namespace) }
diff --git a/spec/requests/api/error_tracking_spec.rb b/spec/requests/api/error_tracking_spec.rb
index deed9777025..8c9ca1b6a9d 100644
--- a/spec/requests/api/error_tracking_spec.rb
+++ b/spec/requests/api/error_tracking_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::ErrorTracking do
+RSpec.describe API::ErrorTracking do
let_it_be(:user) { create(:user) }
let(:setting) { create(:project_error_tracking_setting) }
let(:project) { setting.project }
diff --git a/spec/requests/api/events_spec.rb b/spec/requests/api/events_spec.rb
index 58a55c2e6d0..6a8d5f91abd 100644
--- a/spec/requests/api/events_spec.rb
+++ b/spec/requests/api/events_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Events do
+RSpec.describe API::Events do
let(:user) { create(:user) }
let(:non_member) { create(:user) }
let(:private_project) { create(:project, :private, creator_id: user.id, namespace: user.namespace) }
diff --git a/spec/requests/api/features_spec.rb b/spec/requests/api/features_spec.rb
index 59a9ed2f77d..2746e777306 100644
--- a/spec/requests/api/features_spec.rb
+++ b/spec/requests/api/features_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Features, stub_feature_flags: false do
+RSpec.describe API::Features, stub_feature_flags: false do
let_it_be(:user) { create(:user) }
let_it_be(:admin) { create(:admin) }
diff --git a/spec/requests/api/files_spec.rb b/spec/requests/api/files_spec.rb
index 198e4f64bcc..b50f63ed67c 100644
--- a/spec/requests/api/files_spec.rb
+++ b/spec/requests/api/files_spec.rb
@@ -2,7 +2,9 @@
require 'spec_helper'
-describe API::Files do
+RSpec.describe API::Files do
+ include RepoHelpers
+
let(:user) { create(:user) }
let!(:project) { create(:project, :repository, namespace: user.namespace ) }
let(:guest) { create(:user) { |u| project.add_guest(u) } }
@@ -183,6 +185,26 @@ describe API::Files do
expect(response.content_type).to eq('application/json')
end
+ context 'with filename with pathspec characters' do
+ let(:file_path) { ':wq' }
+ let(:newrev) { project.repository.commit('master').sha }
+
+ before do
+ create_file_in_repo(project, 'master', 'master', file_path, 'Test file')
+ end
+
+ it 'returns JSON wth commit SHA' do
+ params[:ref] = 'master'
+
+ get api(route(file_path), api_user), params: params
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response['file_path']).to eq(file_path)
+ expect(json_response['file_name']).to eq(file_path)
+ expect(json_response['last_commit_id']).to eq(newrev)
+ end
+ end
+
it 'returns file by commit sha' do
# This file is deleted on HEAD
file_path = "files%2Fjs%2Fcommit%2Ejs%2Ecoffee"
diff --git a/spec/requests/api/freeze_periods_spec.rb b/spec/requests/api/freeze_periods_spec.rb
index 0b7828ebedf..5589d4d543d 100644
--- a/spec/requests/api/freeze_periods_spec.rb
+++ b/spec/requests/api/freeze_periods_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::FreezePeriods do
+RSpec.describe API::FreezePeriods do
let_it_be(:project) { create(:project, :repository, :private) }
let_it_be(:user) { create(:user) }
let_it_be(:admin) { create(:admin) }
diff --git a/spec/requests/api/go_proxy_spec.rb b/spec/requests/api/go_proxy_spec.rb
new file mode 100644
index 00000000000..91e455dac19
--- /dev/null
+++ b/spec/requests/api/go_proxy_spec.rb
@@ -0,0 +1,465 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe API::GoProxy do
+ include PackagesManagerApiSpecHelpers
+
+ let_it_be(:user) { create :user }
+ let_it_be(:project) { create :project_empty_repo, creator: user, path: 'my-go-lib' }
+ let_it_be(:base) { "#{Settings.build_gitlab_go_url}/#{project.full_path}" }
+
+ let_it_be(:oauth) { create :oauth_access_token, scopes: 'api', resource_owner: user }
+ let_it_be(:job) { create :ci_build, user: user }
+ let_it_be(:pa_token) { create :personal_access_token, user: user }
+
+ let_it_be(:modules) do
+ commits = [
+ create(:go_module_commit, :files, project: project, tag: 'v1.0.0', files: { 'README.md' => 'Hi' } ),
+ create(:go_module_commit, :module, project: project, tag: 'v1.0.1' ),
+ create(:go_module_commit, :package, project: project, tag: 'v1.0.2', path: 'pkg' ),
+ create(:go_module_commit, :module, project: project, tag: 'v1.0.3', name: 'mod' ),
+ create(:go_module_commit, :files, project: project, files: { 'y.go' => "package a\n" } ),
+ create(:go_module_commit, :module, project: project, name: 'v2' ),
+ create(:go_module_commit, :files, project: project, tag: 'v2.0.0', files: { 'v2/x.go' => "package a\n" })
+ ]
+
+ { sha: [commits[4].sha, commits[5].sha] }
+ end
+
+ before do
+ project.add_developer(user)
+
+ stub_feature_flags(go_proxy_disable_gomod_validation: false)
+
+ modules
+ end
+
+ shared_examples 'an unavailable resource' do
+ it 'returns not found' do
+ get_resource(user)
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+
+ shared_examples 'a module version list resource' do |*versions, path: ''|
+ let(:module_name) { "#{base}#{path}" }
+ let(:resource) { "list" }
+
+ it "returns #{versions.empty? ? 'nothing' : versions.join(', ')}" do
+ get_resource(user)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response.body.split("\n").to_set).to eq(versions.to_set)
+ end
+ end
+
+ shared_examples 'a missing module version list resource' do |path: ''|
+ let(:module_name) { "#{base}#{path}" }
+ let(:resource) { "list" }
+
+ it_behaves_like 'an unavailable resource'
+ end
+
+ shared_examples 'a module version information resource' do |version, path: ''|
+ let(:module_name) { "#{base}#{path}" }
+ let(:resource) { "#{version}.info" }
+
+ it "returns information for #{version}" do
+ get_resource(user)
+
+ time = project.repository.find_tag(version).dereferenced_target.committed_date
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response).to be_kind_of(Hash)
+ expect(json_response['Version']).to eq(version)
+ expect(json_response['Time']).to eq(time.strftime('%Y-%m-%dT%H:%M:%S.%L%:z'))
+ end
+ end
+
+ shared_examples 'a missing module version information resource' do |version, path: ''|
+ let(:module_name) { "#{base}#{path}" }
+ let(:resource) { "#{version}.info" }
+
+ it_behaves_like 'an unavailable resource'
+ end
+
+ shared_examples 'a module pseudo-version information resource' do |prefix, path: ''|
+ let(:module_name) { "#{base}#{path}" }
+ let(:commit) { project.repository.commit_by(oid: sha) }
+ let(:version) { fmt_pseudo_version prefix, commit }
+ let(:resource) { "#{version}.info" }
+
+ it "returns information for #{prefix}yyyymmddhhmmss-abcdefabcdef" do
+ get_resource(user)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response).to be_kind_of(Hash)
+ expect(json_response['Version']).to eq(version)
+ expect(json_response['Time']).to eq(commit.committed_date.strftime('%Y-%m-%dT%H:%M:%S.%L%:z'))
+ end
+ end
+
+ shared_examples 'a missing module pseudo-version information resource' do |path: ''|
+ let(:module_name) { "#{base}#{path}" }
+ let(:commit) do
+ raise "tried to reference :commit without defining :sha" unless defined?(sha)
+
+ project.repository.commit_by(oid: sha)
+ end
+ let(:resource) { "#{version}.info" }
+
+ it_behaves_like 'an unavailable resource'
+ end
+
+ shared_examples 'a module file resource' do |version, path: ''|
+ let(:module_name) { "#{base}#{path}" }
+ let(:resource) { "#{version}.mod" }
+
+ it "returns #{path}/go.mod from the repo" do
+ get_resource(user)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response.body.split("\n", 2).first).to eq("module #{module_name}")
+ end
+ end
+
+ shared_examples 'a missing module file resource' do |version, path: ''|
+ let(:module_name) { "#{base}#{path}" }
+ let(:resource) { "#{version}.mod" }
+
+ it_behaves_like 'an unavailable resource'
+ end
+
+ shared_examples 'a module archive resource' do |version, entries, path: ''|
+ let(:module_name) { "#{base}#{path}" }
+ let(:resource) { "#{version}.zip" }
+
+ it "returns an archive of #{path.empty? ? '/' : path} @ #{version} from the repo" do
+ get_resource(user)
+
+ expect(response).to have_gitlab_http_status(:ok)
+
+ entries = entries.map { |e| "#{module_name}@#{version}/#{e}" }.to_set
+ actual = Set[]
+ Zip::InputStream.open(StringIO.new(response.body)) do |zip|
+ while (entry = zip.get_next_entry)
+ actual.add(entry.name)
+ end
+ end
+
+ expect(actual).to eq(entries)
+ end
+ end
+
+ describe 'GET /projects/:id/packages/go/*module_name/@v/list' do
+ context 'for the root module' do
+ it_behaves_like 'a module version list resource', 'v1.0.1', 'v1.0.2', 'v1.0.3'
+ end
+
+ context 'for the package' do
+ it_behaves_like 'a module version list resource', path: '/pkg'
+ end
+
+ context 'for the submodule' do
+ it_behaves_like 'a module version list resource', 'v1.0.3', path: '/mod'
+ end
+
+ context 'for the root module v2' do
+ it_behaves_like 'a module version list resource', 'v2.0.0', path: '/v2'
+ end
+
+ context 'with a URL encoded relative path component' do
+ it_behaves_like 'a missing module version list resource', path: '/%2E%2E%2Fxyz'
+ end
+
+ context 'with the feature disabled' do
+ before do
+ stub_feature_flags(go_proxy: false)
+ end
+
+ it_behaves_like 'a missing module version list resource'
+ end
+ end
+
+ describe 'GET /projects/:id/packages/go/*module_name/@v/:module_version.info' do
+ context 'with the root module v1.0.1' do
+ it_behaves_like 'a module version information resource', 'v1.0.1'
+ end
+
+ context 'with the submodule v1.0.3' do
+ it_behaves_like 'a module version information resource', 'v1.0.3', path: '/mod'
+ end
+
+ context 'with the root module v2.0.0' do
+ it_behaves_like 'a module version information resource', 'v2.0.0', path: '/v2'
+ end
+
+ context 'with an invalid path' do
+ it_behaves_like 'a missing module version information resource', 'v1.0.3', path: '/pkg'
+ end
+
+ context 'with an invalid version' do
+ it_behaves_like 'a missing module version information resource', 'v1.0.1', path: '/mod'
+ end
+
+ context 'with a pseudo-version for v1' do
+ it_behaves_like 'a module pseudo-version information resource', 'v1.0.4-0.' do
+ let(:sha) { modules[:sha][0] }
+ end
+ end
+
+ context 'with a pseudo-version for v2' do
+ it_behaves_like 'a module pseudo-version information resource', 'v2.0.0-', path: '/v2' do
+ let(:sha) { modules[:sha][1] }
+ end
+ end
+
+ context 'with a pseudo-version with an invalid timestamp' do
+ it_behaves_like 'a missing module pseudo-version information resource' do
+ let(:version) { "v1.0.4-0.00000000000000-#{modules[:sha][0][0..11]}" }
+ end
+ end
+
+ context 'with a pseudo-version with an invalid commit sha' do
+ it_behaves_like 'a missing module pseudo-version information resource' do
+ let(:sha) { modules[:sha][0] }
+ let(:version) { "v1.0.4-0.#{commit.committed_date.strftime('%Y%m%d%H%M%S')}-000000000000" }
+ end
+ end
+
+ context 'with a pseudo-version with a short commit sha' do
+ it_behaves_like 'a missing module pseudo-version information resource' do
+ let(:sha) { modules[:sha][0] }
+ let(:version) { "v1.0.4-0.#{commit.committed_date.strftime('%Y%m%d%H%M%S')}-#{modules[:sha][0][0..10]}" }
+ end
+ end
+ end
+
+ describe 'GET /projects/:id/packages/go/*module_name/@v/:module_version.mod' do
+ context 'with the root module v1.0.1' do
+ it_behaves_like 'a module file resource', 'v1.0.1'
+ end
+
+ context 'with the submodule v1.0.3' do
+ it_behaves_like 'a module file resource', 'v1.0.3', path: '/mod'
+ end
+
+ context 'with the root module v2.0.0' do
+ it_behaves_like 'a module file resource', 'v2.0.0', path: '/v2'
+ end
+
+ context 'with an invalid path' do
+ it_behaves_like 'a missing module file resource', 'v1.0.3', path: '/pkg'
+ end
+
+ context 'with an invalid version' do
+ it_behaves_like 'a missing module file resource', 'v1.0.1', path: '/mod'
+ end
+ end
+
+ describe 'GET /projects/:id/packages/go/*module_name/@v/:module_version.zip' do
+ context 'with the root module v1.0.1' do
+ it_behaves_like 'a module archive resource', 'v1.0.1', ['README.md', 'go.mod', 'a.go']
+ end
+
+ context 'with the root module v1.0.2' do
+ it_behaves_like 'a module archive resource', 'v1.0.2', ['README.md', 'go.mod', 'a.go', 'pkg/b.go']
+ end
+
+ context 'with the root module v1.0.3' do
+ it_behaves_like 'a module archive resource', 'v1.0.3', ['README.md', 'go.mod', 'a.go', 'pkg/b.go']
+ end
+
+ context 'with the submodule v1.0.3' do
+ it_behaves_like 'a module archive resource', 'v1.0.3', ['go.mod', 'a.go'], path: '/mod'
+ end
+
+ context 'with the root module v2.0.0' do
+ it_behaves_like 'a module archive resource', 'v2.0.0', ['go.mod', 'a.go', 'x.go'], path: '/v2'
+ end
+ end
+
+ context 'with an invalid module directive' do
+ let_it_be(:project) { create :project_empty_repo, :public, creator: user }
+ let_it_be(:base) { "#{Settings.build_gitlab_go_url}/#{project.full_path}" }
+
+ let_it_be(:modules) do
+ create(:go_module_commit, :files, project: project, files: { 'a.go' => "package\a" } )
+ create(:go_module_commit, :files, project: project, tag: 'v1.0.0', files: { 'go.mod' => "module not/a/real/module\n" })
+ create(:go_module_commit, :files, project: project, files: { 'v2/a.go' => "package a\n" } )
+ create(:go_module_commit, :files, project: project, tag: 'v2.0.0', files: { 'v2/go.mod' => "module #{base}\n" } )
+ end
+
+ describe 'GET /projects/:id/packages/go/*module_name/@v/list' do
+ context 'with a completely wrong directive for v1' do
+ it_behaves_like 'a module version list resource'
+ end
+
+ context 'with a directive omitting the suffix for v2' do
+ it_behaves_like 'a module version list resource', path: '/v2'
+ end
+ end
+
+ describe 'GET /projects/:id/packages/go/*module_name/@v/:module_version.info' do
+ context 'with a completely wrong directive for v1' do
+ it_behaves_like 'a missing module version information resource', 'v1.0.0'
+ end
+
+ context 'with a directive omitting the suffix for v2' do
+ it_behaves_like 'a missing module version information resource', 'v2.0.0', path: '/v2'
+ end
+ end
+ end
+
+ context 'with a case sensitive project and versions' do
+ let_it_be(:project) { create :project_empty_repo, :public, creator: user, path: 'MyGoLib' }
+ let_it_be(:base) { "#{Settings.build_gitlab_go_url}/#{project.full_path}" }
+ let_it_be(:base_encoded) { base.gsub(/[A-Z]/) { |s| "!#{s.downcase}"} }
+
+ let_it_be(:modules) do
+ create(:go_module_commit, :files, project: project, files: { 'README.md' => "Hi" })
+ create(:go_module_commit, :module, project: project, tag: 'v1.0.1-prerelease')
+ create(:go_module_commit, :package, project: project, tag: 'v1.0.1-Prerelease', path: 'pkg')
+ end
+
+ describe 'GET /projects/:id/packages/go/*module_name/@v/list' do
+ let(:resource) { "list" }
+
+ context 'with a case encoded path' do
+ it_behaves_like 'a module version list resource', 'v1.0.1-prerelease', 'v1.0.1-Prerelease' do
+ let(:module_name) { base_encoded }
+ end
+ end
+
+ context 'without a case encoded path' do
+ it_behaves_like 'a missing module version list resource' do
+ let(:module_name) { base.downcase }
+ end
+ end
+ end
+
+ describe 'GET /projects/:id/packages/go/*module_name/@v/:module_version.info' do
+ context 'with a case encoded path' do
+ it_behaves_like 'a module version information resource', 'v1.0.1-Prerelease' do
+ let(:module_name) { base_encoded }
+ let(:resource) { "v1.0.1-!prerelease.info" }
+ end
+ end
+
+ context 'without a case encoded path' do
+ it_behaves_like 'a module version information resource', 'v1.0.1-prerelease' do
+ let(:module_name) { base_encoded }
+ let(:resource) { "v1.0.1-prerelease.info" }
+ end
+ end
+ end
+ end
+
+ context 'with a private project' do
+ let(:module_name) { base }
+
+ before do
+ project.update(visibility_level: Gitlab::VisibilityLevel::PRIVATE)
+ end
+
+ describe 'GET /projects/:id/packages/go/*module_name/@v/list' do
+ let(:resource) { "list" }
+
+ it 'returns ok with an oauth token' do
+ get_resource(oauth_access_token: oauth)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+
+ it 'returns ok with a job token' do
+ get_resource(oauth_access_token: job)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+
+ it 'returns ok with a personal access token' do
+ get_resource(personal_access_token: pa_token)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+
+ it 'returns ok with a personal access token and basic authentication' do
+ get_resource(headers: build_basic_auth_header(user.username, pa_token.token))
+
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+
+ it 'returns unauthorized with no authentication' do
+ get_resource
+
+ expect(response).to have_gitlab_http_status(:unauthorized)
+ end
+ end
+ end
+
+ context 'with a public project' do
+ let(:module_name) { base }
+
+ before do
+ project.update(visibility_level: Gitlab::VisibilityLevel::PUBLIC)
+ end
+
+ describe 'GET /projects/:id/packages/go/*module_name/@v/list' do
+ let(:resource) { "list" }
+
+ it 'returns ok with no authentication' do
+ get_resource
+
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+ end
+ end
+
+ context 'with a non-existent project' do
+ def get_resource(user = nil, **params)
+ get api("/projects/not%2fa%2fproject/packages/go/#{base}/@v/list", user, params)
+ end
+
+ describe 'GET /projects/:id/packages/go/*module_name/@v/list' do
+ it 'returns not found with a user' do
+ get_resource(user)
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+
+ it 'returns not found with an oauth token' do
+ get_resource(oauth_access_token: oauth)
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+
+ it 'returns not found with a job token' do
+ get_resource(oauth_access_token: job)
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+
+ it 'returns not found with a personal access token' do
+ get_resource(personal_access_token: pa_token)
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+
+ it 'returns unauthorized with no authentication' do
+ get_resource
+
+ expect(response).to have_gitlab_http_status(:unauthorized)
+ end
+ end
+ end
+
+ def get_resource(user = nil, headers: {}, **params)
+ get api("/projects/#{project.id}/packages/go/#{module_name}/@v/#{resource}", user, params), headers: headers
+ end
+
+ def fmt_pseudo_version(prefix, commit)
+ "#{prefix}#{commit.committed_date.strftime('%Y%m%d%H%M%S')}-#{commit.sha[0..11]}"
+ end
+end
diff --git a/spec/requests/api/graphql/boards/board_lists_query_spec.rb b/spec/requests/api/graphql/boards/board_lists_query_spec.rb
index 3cc1468be02..8a89590c85a 100644
--- a/spec/requests/api/graphql/boards/board_lists_query_spec.rb
+++ b/spec/requests/api/graphql/boards/board_lists_query_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'get board lists' do
+RSpec.describe 'get board lists' do
include GraphqlHelpers
let_it_be(:user) { create(:user) }
diff --git a/spec/requests/api/graphql/boards/boards_query_spec.rb b/spec/requests/api/graphql/boards/boards_query_spec.rb
index a17554aba21..50004e5a8a1 100644
--- a/spec/requests/api/graphql/boards/boards_query_spec.rb
+++ b/spec/requests/api/graphql/boards/boards_query_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'get list of boards' do
+RSpec.describe 'get list of boards' do
include GraphqlHelpers
include_context 'group and project boards query context'
diff --git a/spec/requests/api/graphql/current_user/todos_query_spec.rb b/spec/requests/api/graphql/current_user/todos_query_spec.rb
index 321e1062a96..e298de0df01 100644
--- a/spec/requests/api/graphql/current_user/todos_query_spec.rb
+++ b/spec/requests/api/graphql/current_user/todos_query_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Query current user todos' do
+RSpec.describe 'Query current user todos' do
include GraphqlHelpers
let_it_be(:current_user) { create(:user) }
diff --git a/spec/requests/api/graphql/current_user_query_spec.rb b/spec/requests/api/graphql/current_user_query_spec.rb
index 2b38b8e98ab..dc832b42fa5 100644
--- a/spec/requests/api/graphql/current_user_query_spec.rb
+++ b/spec/requests/api/graphql/current_user_query_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'getting project information' do
+RSpec.describe 'getting project information' do
include GraphqlHelpers
let(:query) do
diff --git a/spec/requests/api/graphql/gitlab_schema_spec.rb b/spec/requests/api/graphql/gitlab_schema_spec.rb
index 266c98d6f08..ee7dba545be 100644
--- a/spec/requests/api/graphql/gitlab_schema_spec.rb
+++ b/spec/requests/api/graphql/gitlab_schema_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'GitlabSchema configurations' do
+RSpec.describe 'GitlabSchema configurations' do
include GraphqlHelpers
let_it_be(:project) { create(:project) }
diff --git a/spec/requests/api/graphql/group/labels_query_spec.rb b/spec/requests/api/graphql/group/labels_query_spec.rb
index 6c34cbadf95..31556ffca30 100644
--- a/spec/requests/api/graphql/group/labels_query_spec.rb
+++ b/spec/requests/api/graphql/group/labels_query_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'getting group label information' do
+RSpec.describe 'getting group label information' do
include GraphqlHelpers
let_it_be(:group) { create(:group, :public) }
diff --git a/spec/requests/api/graphql/group/milestones_spec.rb b/spec/requests/api/graphql/group/milestones_spec.rb
index bad0024e7a3..380eaea17f8 100644
--- a/spec/requests/api/graphql/group/milestones_spec.rb
+++ b/spec/requests/api/graphql/group/milestones_spec.rb
@@ -2,21 +2,22 @@
require 'spec_helper'
-describe 'Milestones through GroupQuery' do
+RSpec.describe 'Milestones through GroupQuery' do
include GraphqlHelpers
let_it_be(:user) { create(:user) }
let_it_be(:now) { Time.now }
- let_it_be(:group) { create(:group) }
- let_it_be(:milestone_1) { create(:milestone, group: group) }
- let_it_be(:milestone_2) { create(:milestone, group: group, state: :closed, start_date: now, due_date: now + 1.day) }
- let_it_be(:milestone_3) { create(:milestone, group: group, start_date: now, due_date: now + 2.days) }
- let_it_be(:milestone_4) { create(:milestone, group: group, state: :closed, start_date: now - 2.days, due_date: now - 1.day) }
- let_it_be(:milestone_from_other_group) { create(:milestone, group: create(:group)) }
-
- let(:milestone_data) { graphql_data['group']['milestones']['edges'] }
describe 'Get list of milestones from a group' do
+ let_it_be(:group) { create(:group) }
+ let_it_be(:milestone_1) { create(:milestone, group: group) }
+ let_it_be(:milestone_2) { create(:milestone, group: group, state: :closed, start_date: now, due_date: now + 1.day) }
+ let_it_be(:milestone_3) { create(:milestone, group: group, start_date: now, due_date: now + 2.days) }
+ let_it_be(:milestone_4) { create(:milestone, group: group, state: :closed, start_date: now - 2.days, due_date: now - 1.day) }
+ let_it_be(:milestone_from_other_group) { create(:milestone, group: create(:group)) }
+
+ let(:milestone_data) { graphql_data['group']['milestones']['edges'] }
+
context 'when the request is correct' do
before do
fetch_milestones(user)
@@ -72,21 +73,6 @@ describe 'Milestones through GroupQuery' do
submilestone_1.to_global_id.to_s, submilestone_2.to_global_id.to_s
)
end
-
- context 'when group_milestone_descendants is disabled' do
- before do
- stub_feature_flags(group_milestone_descendants: false)
- end
-
- it 'ignores descendant milestones' do
- fetch_milestones(user, args)
-
- expect_array_response(
- milestone_1.to_global_id.to_s, milestone_2.to_global_id.to_s,
- milestone_3.to_global_id.to_s, milestone_4.to_global_id.to_s
- )
- end
- end
end
def fetch_milestones(user = nil, args = {})
@@ -120,4 +106,89 @@ describe 'Milestones through GroupQuery' do
node_array(milestone_data, extract_attribute)
end
end
+
+ describe 'ensures each field returns the correct value' do
+ let_it_be(:group) { create(:group) }
+ let_it_be(:project) { create(:project, group: group) }
+ let_it_be(:milestone) { create(:milestone, group: group, start_date: now, due_date: now + 1.day) }
+ let_it_be(:open_issue) { create(:issue, project: project, milestone: milestone) }
+ let_it_be(:closed_issue) { create(:issue, :closed, project: project, milestone: milestone) }
+
+ let(:milestone_query) do
+ %{
+ id
+ title
+ description
+ state
+ webPath
+ dueDate
+ startDate
+ createdAt
+ updatedAt
+ projectMilestone
+ groupMilestone
+ subgroupMilestone
+ }
+ end
+
+ def post_query
+ full_query = graphql_query_for("group",
+ { full_path: group.full_path },
+ [query_graphql_field("milestones", nil, "nodes { #{milestone_query} }")]
+ )
+
+ post_graphql(full_query, current_user: user)
+
+ graphql_data.dig('group', 'milestones', 'nodes', 0)
+ end
+
+ it 'returns correct values for scalar fields' do
+ expect(post_query).to eq({
+ 'id' => global_id_of(milestone),
+ 'title' => milestone.title,
+ 'description' => milestone.description,
+ 'state' => 'active',
+ 'webPath' => milestone_path(milestone),
+ 'dueDate' => milestone.due_date.iso8601,
+ 'startDate' => milestone.start_date.iso8601,
+ 'createdAt' => milestone.created_at.iso8601,
+ 'updatedAt' => milestone.updated_at.iso8601,
+ 'projectMilestone' => false,
+ 'groupMilestone' => true,
+ 'subgroupMilestone' => false
+ })
+ end
+
+ context 'milestone statistics' do
+ let(:milestone_query) do
+ %{
+ stats {
+ totalIssuesCount
+ closedIssuesCount
+ }
+ }
+ end
+
+ it 'returns the correct milestone statistics' do
+ expect(post_query).to eq({
+ 'stats' => {
+ 'totalIssuesCount' => 2,
+ 'closedIssuesCount' => 1
+ }
+ })
+ end
+
+ context 'when the graphql_milestone_stats feature flag is disabled' do
+ before do
+ stub_feature_flags(graphql_milestone_stats: false)
+ end
+
+ it 'returns nil for the stats field' do
+ expect(post_query).to eq({
+ 'stats' => nil
+ })
+ end
+ end
+ end
+ end
end
diff --git a/spec/requests/api/graphql/group_query_spec.rb b/spec/requests/api/graphql/group_query_spec.rb
index c7b537a9923..d99bff2e349 100644
--- a/spec/requests/api/graphql/group_query_spec.rb
+++ b/spec/requests/api/graphql/group_query_spec.rb
@@ -4,7 +4,7 @@ require 'spec_helper'
# Based on spec/requests/api/groups_spec.rb
# Should follow closely in order to ensure all situations are covered
-describe 'getting group information', :do_not_mock_admin_mode do
+RSpec.describe 'getting group information', :do_not_mock_admin_mode do
include GraphqlHelpers
include UploadHelpers
diff --git a/spec/requests/api/graphql/metadata_query_spec.rb b/spec/requests/api/graphql/metadata_query_spec.rb
index 4c56c559cf9..6344ec371c8 100644
--- a/spec/requests/api/graphql/metadata_query_spec.rb
+++ b/spec/requests/api/graphql/metadata_query_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'getting project information' do
+RSpec.describe 'getting project information' do
include GraphqlHelpers
let(:query) { graphql_query_for('metadata', {}, all_graphql_fields_for('Metadata')) }
diff --git a/spec/requests/api/graphql/metrics/dashboard/annotations_spec.rb b/spec/requests/api/graphql/metrics/dashboard/annotations_spec.rb
index cb35411b7a5..c47920087dc 100644
--- a/spec/requests/api/graphql/metrics/dashboard/annotations_spec.rb
+++ b/spec/requests/api/graphql/metrics/dashboard/annotations_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Getting Metrics Dashboard Annotations' do
+RSpec.describe 'Getting Metrics Dashboard Annotations' do
include GraphqlHelpers
let_it_be(:project) { create(:project) }
diff --git a/spec/requests/api/graphql/metrics/dashboard_query_spec.rb b/spec/requests/api/graphql/metrics/dashboard_query_spec.rb
index d9d9ea9ad61..456b0a5dea1 100644
--- a/spec/requests/api/graphql/metrics/dashboard_query_spec.rb
+++ b/spec/requests/api/graphql/metrics/dashboard_query_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Getting Metrics Dashboard' do
+RSpec.describe 'Getting Metrics Dashboard' do
include GraphqlHelpers
let_it_be(:current_user) { create(:user) }
@@ -62,12 +62,12 @@ describe 'Getting Metrics Dashboard' do
context 'invalid dashboard' do
let(:path) { '.gitlab/dashboards/metrics.yml' }
- let(:project) { create(:project, :repository, :custom_repo, namespace: current_user.namespace, files: { path => "---\ndasboard: ''" }) }
+ let(:project) { create(:project, :repository, :custom_repo, namespace: current_user.namespace, files: { path => "---\ndashboard: 'test'" }) }
it 'returns metrics dashboard' do
dashboard = graphql_data.dig('project', 'environments', 'nodes', 0, 'metricsDashboard')
- expect(dashboard).to eql("path" => path, "schemaValidationWarnings" => ["dashboard: can't be blank", "panel_groups: can't be blank"])
+ expect(dashboard).to eql("path" => path, "schemaValidationWarnings" => ["panel_groups: should be an array of panel_groups objects"])
end
end
@@ -78,7 +78,7 @@ describe 'Getting Metrics Dashboard' do
it 'returns metrics dashboard' do
dashboard = graphql_data.dig('project', 'environments', 'nodes', 0, 'metricsDashboard')
- expect(dashboard).to eql("path" => path, "schemaValidationWarnings" => ["dashboard: can't be blank", "panel_groups: can't be blank"])
+ expect(dashboard).to eql("path" => path, "schemaValidationWarnings" => ["dashboard: can't be blank", "panel_groups: should be an array of panel_groups objects"])
end
end
end
diff --git a/spec/requests/api/graphql/multiplexed_queries_spec.rb b/spec/requests/api/graphql/multiplexed_queries_spec.rb
index 9ebb57f6b9c..f79bac6ae3b 100644
--- a/spec/requests/api/graphql/multiplexed_queries_spec.rb
+++ b/spec/requests/api/graphql/multiplexed_queries_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe 'Multiplexed queries' do
+RSpec.describe 'Multiplexed queries' do
include GraphqlHelpers
it 'returns responses for multiple queries' do
diff --git a/spec/requests/api/graphql/mutations/admin/sidekiq_queues/delete_jobs_spec.rb b/spec/requests/api/graphql/mutations/admin/sidekiq_queues/delete_jobs_spec.rb
index a5159da84f3..4ad35e7f0d1 100644
--- a/spec/requests/api/graphql/mutations/admin/sidekiq_queues/delete_jobs_spec.rb
+++ b/spec/requests/api/graphql/mutations/admin/sidekiq_queues/delete_jobs_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Deleting Sidekiq jobs', :clean_gitlab_redis_queues do
+RSpec.describe 'Deleting Sidekiq jobs', :clean_gitlab_redis_queues do
include GraphqlHelpers
let_it_be(:admin) { create(:admin) }
diff --git a/spec/requests/api/graphql/mutations/alert_management/alerts/create_alert_issue_spec.rb b/spec/requests/api/graphql/mutations/alert_management/alerts/create_alert_issue_spec.rb
index 5b5b2ec8788..6141a172253 100644
--- a/spec/requests/api/graphql/mutations/alert_management/alerts/create_alert_issue_spec.rb
+++ b/spec/requests/api/graphql/mutations/alert_management/alerts/create_alert_issue_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Create an alert issue from an alert' do
+RSpec.describe 'Create an alert issue from an alert' do
include GraphqlHelpers
let_it_be(:user) { create(:user) }
diff --git a/spec/requests/api/graphql/mutations/alert_management/alerts/set_assignees_spec.rb b/spec/requests/api/graphql/mutations/alert_management/alerts/set_assignees_spec.rb
index 6663281e093..cd5cefa0a9a 100644
--- a/spec/requests/api/graphql/mutations/alert_management/alerts/set_assignees_spec.rb
+++ b/spec/requests/api/graphql/mutations/alert_management/alerts/set_assignees_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Setting assignees of an alert' do
+RSpec.describe 'Setting assignees of an alert' do
include GraphqlHelpers
let_it_be(:project) { create(:project) }
diff --git a/spec/requests/api/graphql/mutations/alert_management/alerts/todo/create_spec.rb b/spec/requests/api/graphql/mutations/alert_management/alerts/todo/create_spec.rb
new file mode 100644
index 00000000000..e5803f50474
--- /dev/null
+++ b/spec/requests/api/graphql/mutations/alert_management/alerts/todo/create_spec.rb
@@ -0,0 +1,55 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'Creating a todo for the alert' do
+ include GraphqlHelpers
+
+ let_it_be(:user) { create(:user) }
+ let_it_be(:project) { create(:project) }
+ let(:alert) { create(:alert_management_alert, project: project) }
+
+ let(:mutation) do
+ variables = {
+ project_path: project.full_path,
+ iid: alert.iid.to_s
+ }
+ graphql_mutation(:alert_todo_create, variables) do
+ <<~QL
+ clientMutationId
+ errors
+ todo {
+ author {
+ username
+ }
+ }
+ QL
+ end
+ end
+
+ let(:mutation_response) { graphql_mutation_response(:alert_todo_create) }
+
+ before do
+ project.add_developer(user)
+ end
+
+ it 'creates a todo for the current user' do
+ post_graphql_mutation(mutation, current_user: user)
+
+ expect(response).to have_gitlab_http_status(:success)
+ expect(mutation_response['todo']['author']['username']).to eq(user.username)
+ end
+
+ context 'todo already exists' do
+ before do
+ create(:todo, :pending, project: project, user: user, target: alert)
+ end
+
+ it 'surfaces an error' do
+ post_graphql_mutation(mutation, current_user: user)
+
+ expect(response).to have_gitlab_http_status(:success)
+ expect(mutation_response['errors']).to eq(['You already have pending todo for this alert'])
+ end
+ end
+end
diff --git a/spec/requests/api/graphql/mutations/alert_management/alerts/update_alert_status_spec.rb b/spec/requests/api/graphql/mutations/alert_management/alerts/update_alert_status_spec.rb
index 2a470bda689..ff55656a2ae 100644
--- a/spec/requests/api/graphql/mutations/alert_management/alerts/update_alert_status_spec.rb
+++ b/spec/requests/api/graphql/mutations/alert_management/alerts/update_alert_status_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Setting the status of an alert' do
+RSpec.describe 'Setting the status of an alert' do
include GraphqlHelpers
let_it_be(:user) { create(:user) }
diff --git a/spec/requests/api/graphql/mutations/award_emojis/add_spec.rb b/spec/requests/api/graphql/mutations/award_emojis/add_spec.rb
index 83dec7dd3e2..1891300dace 100644
--- a/spec/requests/api/graphql/mutations/award_emojis/add_spec.rb
+++ b/spec/requests/api/graphql/mutations/award_emojis/add_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Adding an AwardEmoji' do
+RSpec.describe 'Adding an AwardEmoji' do
include GraphqlHelpers
let_it_be(:current_user) { create(:user) }
@@ -15,11 +15,11 @@ describe 'Adding an AwardEmoji' do
name: emoji_name
}
- graphql_mutation(:add_award_emoji, variables)
+ graphql_mutation(:award_emoji_add, variables)
end
def mutation_response
- graphql_mutation_response(:add_award_emoji)
+ graphql_mutation_response(:award_emoji_add)
end
shared_examples 'a mutation that does not create an AwardEmoji' do
diff --git a/spec/requests/api/graphql/mutations/award_emojis/remove_spec.rb b/spec/requests/api/graphql/mutations/award_emojis/remove_spec.rb
index a2997db6cae..665b511abb8 100644
--- a/spec/requests/api/graphql/mutations/award_emojis/remove_spec.rb
+++ b/spec/requests/api/graphql/mutations/award_emojis/remove_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Removing an AwardEmoji' do
+RSpec.describe 'Removing an AwardEmoji' do
include GraphqlHelpers
let(:current_user) { create(:user) }
@@ -12,11 +12,11 @@ describe 'Removing an AwardEmoji' do
let(:input) { { awardable_id: GitlabSchema.id_from_object(awardable).to_s, name: emoji_name } }
let(:mutation) do
- graphql_mutation(:remove_award_emoji, input)
+ graphql_mutation(:award_emoji_remove, input)
end
def mutation_response
- graphql_mutation_response(:remove_award_emoji)
+ graphql_mutation_response(:award_emoji_remove)
end
def create_award_emoji(user)
diff --git a/spec/requests/api/graphql/mutations/award_emojis/toggle_spec.rb b/spec/requests/api/graphql/mutations/award_emojis/toggle_spec.rb
index e1180c85c6b..ab4a213fde3 100644
--- a/spec/requests/api/graphql/mutations/award_emojis/toggle_spec.rb
+++ b/spec/requests/api/graphql/mutations/award_emojis/toggle_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Toggling an AwardEmoji' do
+RSpec.describe 'Toggling an AwardEmoji' do
include GraphqlHelpers
let_it_be(:current_user) { create(:user) }
@@ -15,11 +15,11 @@ describe 'Toggling an AwardEmoji' do
name: emoji_name
}
- graphql_mutation(:toggle_award_emoji, variables)
+ graphql_mutation(:award_emoji_toggle, variables)
end
def mutation_response
- graphql_mutation_response(:toggle_award_emoji)
+ graphql_mutation_response(:award_emoji_toggle)
end
shared_examples 'a mutation that does not create or destroy an AwardEmoji' do
diff --git a/spec/requests/api/graphql/mutations/branches/create_spec.rb b/spec/requests/api/graphql/mutations/branches/create_spec.rb
index b3c378ec2bc..082b445bf3e 100644
--- a/spec/requests/api/graphql/mutations/branches/create_spec.rb
+++ b/spec/requests/api/graphql/mutations/branches/create_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Creation of a new branch' do
+RSpec.describe 'Creation of a new branch' do
include GraphqlHelpers
let_it_be(:current_user) { create(:user) }
diff --git a/spec/requests/api/graphql/mutations/commits/create_spec.rb b/spec/requests/api/graphql/mutations/commits/create_spec.rb
index 10a69932948..9e4a96700bb 100644
--- a/spec/requests/api/graphql/mutations/commits/create_spec.rb
+++ b/spec/requests/api/graphql/mutations/commits/create_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Creation of a new commit' do
+RSpec.describe 'Creation of a new commit' do
include GraphqlHelpers
let_it_be(:current_user) { create(:user) }
diff --git a/spec/requests/api/graphql/mutations/container_expiration_policy/update_spec.rb b/spec/requests/api/graphql/mutations/container_expiration_policy/update_spec.rb
index bc256a08f00..bc1b42d68e6 100644
--- a/spec/requests/api/graphql/mutations/container_expiration_policy/update_spec.rb
+++ b/spec/requests/api/graphql/mutations/container_expiration_policy/update_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Updating the container expiration policy' do
+RSpec.describe 'Updating the container expiration policy' do
include GraphqlHelpers
using RSpec::Parameterized::TableSyntax
@@ -48,13 +48,48 @@ describe 'Updating the container expiration policy' do
end
end
- RSpec.shared_examples 'updating the container expiration policy' do
+ RSpec.shared_examples 'rejecting invalid regex for' do |field_name|
+ context "for field #{field_name}" do
+ let_it_be(:invalid_regex) { '*production' }
+ let(:params) do
+ {
+ :project_path => project.full_path,
+ field_name => invalid_regex
+ }
+ end
+
+ it_behaves_like 'returning response status', :success
+
+ it_behaves_like 'not creating the container expiration policy'
+
+ it 'returns an error' do
+ subject
+
+ expect(graphql_errors.size).to eq(1)
+ expect(graphql_errors.first['message']).to include("#{invalid_regex} is an invalid regexp")
+ end
+ end
+ end
+
+ RSpec.shared_examples 'accepting the mutation request updating the container expiration policy' do
it_behaves_like 'updating the container expiration policy attributes', mode: :update, from: { cadence: '1d', keep_n: 10, older_than: '90d' }, to: { cadence: '3month', keep_n: 100, older_than: '14d' }
it_behaves_like 'returning a success'
+
+ it_behaves_like 'rejecting invalid regex for', :name_regex
+ it_behaves_like 'rejecting invalid regex for', :name_regex_keep
+ end
+
+ RSpec.shared_examples 'accepting the mutation request creating the container expiration policy' do
+ it_behaves_like 'creating the container expiration policy'
+
+ it_behaves_like 'returning a success'
+
+ it_behaves_like 'rejecting invalid regex for', :name_regex
+ it_behaves_like 'rejecting invalid regex for', :name_regex_keep
end
- RSpec.shared_examples 'denying access to container expiration policy' do
+ RSpec.shared_examples 'denying the mutation request' do
it_behaves_like 'not creating the container expiration policy'
it_behaves_like 'returning response status', :success
@@ -71,11 +106,11 @@ describe 'Updating the container expiration policy' do
context 'with existing container expiration policy' do
where(:user_role, :shared_examples_name) do
- :maintainer | 'updating the container expiration policy'
- :developer | 'updating the container expiration policy'
- :reporter | 'denying access to container expiration policy'
- :guest | 'denying access to container expiration policy'
- :anonymous | 'denying access to container expiration policy'
+ :maintainer | 'accepting the mutation request updating the container expiration policy'
+ :developer | 'accepting the mutation request updating the container expiration policy'
+ :reporter | 'denying the mutation request'
+ :guest | 'denying the mutation request'
+ :anonymous | 'denying the mutation request'
end
with_them do
@@ -91,11 +126,11 @@ describe 'Updating the container expiration policy' do
let_it_be(:project, reload: true) { create(:project, :without_container_expiration_policy) }
where(:user_role, :shared_examples_name) do
- :maintainer | 'creating the container expiration policy'
- :developer | 'creating the container expiration policy'
- :reporter | 'denying access to container expiration policy'
- :guest | 'denying access to container expiration policy'
- :anonymous | 'denying access to container expiration policy'
+ :maintainer | 'accepting the mutation request creating the container expiration policy'
+ :developer | 'accepting the mutation request creating the container expiration policy'
+ :reporter | 'denying the mutation request'
+ :guest | 'denying the mutation request'
+ :anonymous | 'denying the mutation request'
end
with_them do
diff --git a/spec/requests/api/graphql/mutations/design_management/delete_spec.rb b/spec/requests/api/graphql/mutations/design_management/delete_spec.rb
index 10376305b3e..e329416faee 100644
--- a/spec/requests/api/graphql/mutations/design_management/delete_spec.rb
+++ b/spec/requests/api/graphql/mutations/design_management/delete_spec.rb
@@ -2,7 +2,7 @@
require "spec_helper"
-describe "deleting designs" do
+RSpec.describe "deleting designs" do
include GraphqlHelpers
include DesignManagementTestHelpers
diff --git a/spec/requests/api/graphql/mutations/design_management/upload_spec.rb b/spec/requests/api/graphql/mutations/design_management/upload_spec.rb
index 22adc064406..9a9c7107b20 100644
--- a/spec/requests/api/graphql/mutations/design_management/upload_spec.rb
+++ b/spec/requests/api/graphql/mutations/design_management/upload_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require "spec_helper"
-describe "uploading designs" do
+RSpec.describe "uploading designs" do
include GraphqlHelpers
include DesignManagementTestHelpers
include WorkhorseHelpers
diff --git a/spec/requests/api/graphql/mutations/discussions/toggle_resolve_spec.rb b/spec/requests/api/graphql/mutations/discussions/toggle_resolve_spec.rb
index 95e967c039d..e83da830935 100644
--- a/spec/requests/api/graphql/mutations/discussions/toggle_resolve_spec.rb
+++ b/spec/requests/api/graphql/mutations/discussions/toggle_resolve_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Toggling the resolve status of a discussion' do
+RSpec.describe 'Toggling the resolve status of a discussion' do
include GraphqlHelpers
let_it_be(:project) { create(:project, :public, :repository) }
diff --git a/spec/requests/api/graphql/mutations/issues/set_confidential_spec.rb b/spec/requests/api/graphql/mutations/issues/set_confidential_spec.rb
index 4d0bb59b030..3f804a46992 100644
--- a/spec/requests/api/graphql/mutations/issues/set_confidential_spec.rb
+++ b/spec/requests/api/graphql/mutations/issues/set_confidential_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Setting an issue as confidential' do
+RSpec.describe 'Setting an issue as confidential' do
include GraphqlHelpers
let(:current_user) { create(:user) }
diff --git a/spec/requests/api/graphql/mutations/issues/set_due_date_spec.rb b/spec/requests/api/graphql/mutations/issues/set_due_date_spec.rb
index 1efa9e16233..3dd1225db5a 100644
--- a/spec/requests/api/graphql/mutations/issues/set_due_date_spec.rb
+++ b/spec/requests/api/graphql/mutations/issues/set_due_date_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Setting Due Date of an issue' do
+RSpec.describe 'Setting Due Date of an issue' do
include GraphqlHelpers
let(:current_user) { create(:user) }
diff --git a/spec/requests/api/graphql/mutations/issues/set_locked_spec.rb b/spec/requests/api/graphql/mutations/issues/set_locked_spec.rb
new file mode 100644
index 00000000000..f1d55430e02
--- /dev/null
+++ b/spec/requests/api/graphql/mutations/issues/set_locked_spec.rb
@@ -0,0 +1,55 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'Setting an issue as locked' do
+ include GraphqlHelpers
+
+ let_it_be(:current_user) { create(:user) }
+ let_it_be(:issue) { create(:issue) }
+ let_it_be(:project) { issue.project }
+ let(:input) { { locked: true } }
+
+ let(:mutation) do
+ variables = {
+ project_path: project.full_path,
+ iid: issue.iid.to_s
+ }
+ graphql_mutation(:issue_set_locked, variables.merge(input),
+ <<-QL.strip_heredoc
+ clientMutationId
+ errors
+ issue {
+ iid
+ discussionLocked
+ }
+ QL
+ )
+ end
+
+ def mutation_response
+ graphql_mutation_response(:issue_set_locked)
+ end
+
+ context 'when the user is not allowed to update the issue' do
+ it 'returns an error' do
+ error = "The resource that you are attempting to access does not exist or you don't have permission to perform this action"
+ post_graphql_mutation(mutation, current_user: current_user)
+
+ expect(graphql_errors).to include(a_hash_including('message' => error))
+ end
+ end
+
+ context 'when user is allowed to update the issue' do
+ before do
+ project.add_developer(current_user)
+ end
+
+ it 'updates the issue locked status' do
+ post_graphql_mutation(mutation, current_user: current_user)
+
+ expect(response).to have_gitlab_http_status(:success)
+ expect(mutation_response['issue']['discussionLocked']).to be_truthy
+ end
+ end
+end
diff --git a/spec/requests/api/graphql/mutations/jira_import/import_users_spec.rb b/spec/requests/api/graphql/mutations/jira_import/import_users_spec.rb
index be0d843d5ff..4057aa4ba9e 100644
--- a/spec/requests/api/graphql/mutations/jira_import/import_users_spec.rb
+++ b/spec/requests/api/graphql/mutations/jira_import/import_users_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Importing Jira Users' do
+RSpec.describe 'Importing Jira Users' do
include JiraServiceHelper
include GraphqlHelpers
diff --git a/spec/requests/api/graphql/mutations/jira_import/start_spec.rb b/spec/requests/api/graphql/mutations/jira_import/start_spec.rb
index 296d33aec5d..e7124512ef1 100644
--- a/spec/requests/api/graphql/mutations/jira_import/start_spec.rb
+++ b/spec/requests/api/graphql/mutations/jira_import/start_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Starting a Jira Import' do
+RSpec.describe 'Starting a Jira Import' do
include JiraServiceHelper
include GraphqlHelpers
@@ -14,7 +14,8 @@ describe 'Starting a Jira Import' do
let(:mutation) do
variables = {
jira_project_key: jira_project_key,
- project_path: project_path
+ project_path: project_path,
+ users_mapping: [{ jiraAccountId: 'abc', gitlabId: 5 }]
}
graphql_mutation(:jira_import_start, variables)
diff --git a/spec/requests/api/graphql/mutations/merge_requests/create_spec.rb b/spec/requests/api/graphql/mutations/merge_requests/create_spec.rb
index 5c63f655f1d..d4ac639e226 100644
--- a/spec/requests/api/graphql/mutations/merge_requests/create_spec.rb
+++ b/spec/requests/api/graphql/mutations/merge_requests/create_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Creation of a new merge request' do
+RSpec.describe 'Creation of a new merge request' do
include GraphqlHelpers
let_it_be(:current_user) { create(:user) }
diff --git a/spec/requests/api/graphql/mutations/merge_requests/set_assignees_spec.rb b/spec/requests/api/graphql/mutations/merge_requests/set_assignees_spec.rb
index 8f908b7bf88..97873b01338 100644
--- a/spec/requests/api/graphql/mutations/merge_requests/set_assignees_spec.rb
+++ b/spec/requests/api/graphql/mutations/merge_requests/set_assignees_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Setting assignees of a merge request' do
+RSpec.describe 'Setting assignees of a merge request' do
include GraphqlHelpers
let(:current_user) { create(:user) }
diff --git a/spec/requests/api/graphql/mutations/merge_requests/set_labels_spec.rb b/spec/requests/api/graphql/mutations/merge_requests/set_labels_spec.rb
index 2112ff0dc74..34d347c76fd 100644
--- a/spec/requests/api/graphql/mutations/merge_requests/set_labels_spec.rb
+++ b/spec/requests/api/graphql/mutations/merge_requests/set_labels_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Setting labels of a merge request' do
+RSpec.describe 'Setting labels of a merge request' do
include GraphqlHelpers
let(:current_user) { create(:user) }
diff --git a/spec/requests/api/graphql/mutations/merge_requests/set_locked_spec.rb b/spec/requests/api/graphql/mutations/merge_requests/set_locked_spec.rb
index c45da613591..a1a35bc1dcc 100644
--- a/spec/requests/api/graphql/mutations/merge_requests/set_locked_spec.rb
+++ b/spec/requests/api/graphql/mutations/merge_requests/set_locked_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Setting locked status of a merge request' do
+RSpec.describe 'Setting locked status of a merge request' do
include GraphqlHelpers
let(:current_user) { create(:user) }
diff --git a/spec/requests/api/graphql/mutations/merge_requests/set_milestone_spec.rb b/spec/requests/api/graphql/mutations/merge_requests/set_milestone_spec.rb
index bd558edf9c5..d7e2602bd0a 100644
--- a/spec/requests/api/graphql/mutations/merge_requests/set_milestone_spec.rb
+++ b/spec/requests/api/graphql/mutations/merge_requests/set_milestone_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Setting milestone of a merge request' do
+RSpec.describe 'Setting milestone of a merge request' do
include GraphqlHelpers
let(:current_user) { create(:user) }
diff --git a/spec/requests/api/graphql/mutations/merge_requests/set_subscription_spec.rb b/spec/requests/api/graphql/mutations/merge_requests/set_subscription_spec.rb
index 975735bf246..6b3035fbf48 100644
--- a/spec/requests/api/graphql/mutations/merge_requests/set_subscription_spec.rb
+++ b/spec/requests/api/graphql/mutations/merge_requests/set_subscription_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Setting subscribed status of a merge request' do
+RSpec.describe 'Setting subscribed status of a merge request' do
include GraphqlHelpers
let(:current_user) { create(:user) }
diff --git a/spec/requests/api/graphql/mutations/merge_requests/set_wip_spec.rb b/spec/requests/api/graphql/mutations/merge_requests/set_wip_spec.rb
index 4492c51dbd7..2143abd3031 100644
--- a/spec/requests/api/graphql/mutations/merge_requests/set_wip_spec.rb
+++ b/spec/requests/api/graphql/mutations/merge_requests/set_wip_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Setting WIP status of a merge request' do
+RSpec.describe 'Setting Draft status of a merge request' do
include GraphqlHelpers
let(:current_user) { create(:user) }
@@ -41,39 +41,39 @@ describe 'Setting WIP status of a merge request' do
expect(graphql_errors).not_to be_empty
end
- it 'marks the merge request as WIP' do
+ it 'marks the merge request as Draft' do
post_graphql_mutation(mutation, current_user: current_user)
expect(response).to have_gitlab_http_status(:success)
- expect(mutation_response['mergeRequest']['title']).to start_with('WIP:')
+ expect(mutation_response['mergeRequest']['title']).to start_with('Draft:')
end
- it 'does not do anything if the merge request was already marked `WIP`' do
- merge_request.update!(title: 'wip: hello world')
+ it 'does not do anything if the merge request was already marked `Draft`' do
+ merge_request.update!(title: 'draft: hello world')
post_graphql_mutation(mutation, current_user: current_user)
expect(response).to have_gitlab_http_status(:success)
- expect(mutation_response['mergeRequest']['title']).to start_with('wip:')
+ expect(mutation_response['mergeRequest']['title']).to start_with('draft:')
end
- context 'when passing WIP false as input' do
+ context 'when passing Draft false as input' do
let(:input) { { wip: false } }
- it 'does not do anything if the merge reqeust was not marked wip' do
+ it 'does not do anything if the merge reqeust was not marked draft' do
post_graphql_mutation(mutation, current_user: current_user)
expect(response).to have_gitlab_http_status(:success)
- expect(mutation_response['mergeRequest']['title']).not_to start_with(/wip\:/)
+ expect(mutation_response['mergeRequest']['title']).not_to start_with(/draft\:/)
end
- it 'unmarks the merge request as `WIP`' do
- merge_request.update!(title: 'wip: hello world')
+ it 'unmarks the merge request as `Draft`' do
+ merge_request.update!(title: 'draft: hello world')
post_graphql_mutation(mutation, current_user: current_user)
expect(response).to have_gitlab_http_status(:success)
- expect(mutation_response['mergeRequest']['title']).not_to start_with('/wip\:/')
+ expect(mutation_response['mergeRequest']['title']).not_to start_with('/draft\:/')
end
end
end
diff --git a/spec/requests/api/graphql/mutations/metrics/dashboard/annotations/create_spec.rb b/spec/requests/api/graphql/mutations/metrics/dashboard/annotations/create_spec.rb
index 8568dc8ffc0..0e2da94f0f9 100644
--- a/spec/requests/api/graphql/mutations/metrics/dashboard/annotations/create_spec.rb
+++ b/spec/requests/api/graphql/mutations/metrics/dashboard/annotations/create_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Mutations::Metrics::Dashboard::Annotations::Create do
+RSpec.describe Mutations::Metrics::Dashboard::Annotations::Create do
include GraphqlHelpers
let_it_be(:current_user) { create(:user) }
diff --git a/spec/requests/api/graphql/mutations/metrics/dashboard/annotations/delete_spec.rb b/spec/requests/api/graphql/mutations/metrics/dashboard/annotations/delete_spec.rb
index 217f538c53e..2459a6f3828 100644
--- a/spec/requests/api/graphql/mutations/metrics/dashboard/annotations/delete_spec.rb
+++ b/spec/requests/api/graphql/mutations/metrics/dashboard/annotations/delete_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Mutations::Metrics::Dashboard::Annotations::Delete do
+RSpec.describe Mutations::Metrics::Dashboard::Annotations::Delete do
include GraphqlHelpers
let_it_be(:current_user) { create(:user) }
diff --git a/spec/requests/api/graphql/mutations/notes/create/diff_note_spec.rb b/spec/requests/api/graphql/mutations/notes/create/diff_note_spec.rb
index 4c535434faa..e847c46be1b 100644
--- a/spec/requests/api/graphql/mutations/notes/create/diff_note_spec.rb
+++ b/spec/requests/api/graphql/mutations/notes/create/diff_note_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Adding a DiffNote' do
+RSpec.describe 'Adding a DiffNote' do
include GraphqlHelpers
let_it_be(:current_user) { create(:user) }
diff --git a/spec/requests/api/graphql/mutations/notes/create/image_diff_note_spec.rb b/spec/requests/api/graphql/mutations/notes/create/image_diff_note_spec.rb
index 0bba3e79434..896a398e308 100644
--- a/spec/requests/api/graphql/mutations/notes/create/image_diff_note_spec.rb
+++ b/spec/requests/api/graphql/mutations/notes/create/image_diff_note_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Adding an image DiffNote' do
+RSpec.describe 'Adding an image DiffNote' do
include GraphqlHelpers
let_it_be(:current_user) { create(:user) }
diff --git a/spec/requests/api/graphql/mutations/notes/create/note_spec.rb b/spec/requests/api/graphql/mutations/notes/create/note_spec.rb
index 9a78d44245e..391ced7dc98 100644
--- a/spec/requests/api/graphql/mutations/notes/create/note_spec.rb
+++ b/spec/requests/api/graphql/mutations/notes/create/note_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Adding a Note' do
+RSpec.describe 'Adding a Note' do
include GraphqlHelpers
let_it_be(:current_user) { create(:user) }
@@ -13,7 +13,8 @@ describe 'Adding a Note' do
variables = {
noteable_id: GitlabSchema.id_from_object(noteable).to_s,
discussion_id: (GitlabSchema.id_from_object(discussion).to_s if discussion),
- body: 'Body text'
+ body: 'Body text',
+ confidential: true
}
graphql_mutation(:create_note, variables)
@@ -40,6 +41,7 @@ describe 'Adding a Note' do
post_graphql_mutation(mutation, current_user: current_user)
expect(mutation_response['note']['body']).to eq('Body text')
+ expect(mutation_response['note']['confidential']).to eq(true)
end
describe 'creating Notes in reply to a discussion' do
diff --git a/spec/requests/api/graphql/mutations/notes/destroy_spec.rb b/spec/requests/api/graphql/mutations/notes/destroy_spec.rb
index 337a6e6f6e6..6002a5b5b9d 100644
--- a/spec/requests/api/graphql/mutations/notes/destroy_spec.rb
+++ b/spec/requests/api/graphql/mutations/notes/destroy_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Destroying a Note' do
+RSpec.describe 'Destroying a Note' do
include GraphqlHelpers
let!(:note) { create(:note) }
diff --git a/spec/requests/api/graphql/mutations/notes/update/image_diff_note_spec.rb b/spec/requests/api/graphql/mutations/notes/update/image_diff_note_spec.rb
index 0362fef2d2e..f7be671e5f3 100644
--- a/spec/requests/api/graphql/mutations/notes/update/image_diff_note_spec.rb
+++ b/spec/requests/api/graphql/mutations/notes/update/image_diff_note_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Updating an image DiffNote' do
+RSpec.describe 'Updating an image DiffNote' do
include GraphqlHelpers
using RSpec::Parameterized::TableSyntax
diff --git a/spec/requests/api/graphql/mutations/notes/update/note_spec.rb b/spec/requests/api/graphql/mutations/notes/update/note_spec.rb
index a5c6b72005e..38378310d9f 100644
--- a/spec/requests/api/graphql/mutations/notes/update/note_spec.rb
+++ b/spec/requests/api/graphql/mutations/notes/update/note_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Updating a Note' do
+RSpec.describe 'Updating a Note' do
include GraphqlHelpers
let!(:note) { create(:note, note: original_body) }
diff --git a/spec/requests/api/graphql/mutations/snippets/create_spec.rb b/spec/requests/api/graphql/mutations/snippets/create_spec.rb
index 9052f54b171..e2474e1bcce 100644
--- a/spec/requests/api/graphql/mutations/snippets/create_spec.rb
+++ b/spec/requests/api/graphql/mutations/snippets/create_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Creating a Snippet' do
+RSpec.describe 'Creating a Snippet' do
include GraphqlHelpers
let_it_be(:user) { create(:user) }
@@ -14,9 +14,8 @@ describe 'Creating a Snippet' do
let(:visibility_level) { 'public' }
let(:project_path) { nil }
let(:uploaded_files) { nil }
-
- let(:mutation) do
- variables = {
+ let(:mutation_vars) do
+ {
content: content,
description: description,
visibility_level: visibility_level,
@@ -25,8 +24,10 @@ describe 'Creating a Snippet' do
project_path: project_path,
uploaded_files: uploaded_files
}
+ end
- graphql_mutation(:create_snippet, variables)
+ let(:mutation) do
+ graphql_mutation(:create_snippet, mutation_vars)
end
def mutation_response
@@ -137,6 +138,47 @@ describe 'Creating a Snippet' do
end
end
+ context 'when snippet is created using the files param' do
+ let(:action) { :create }
+ let(:file_1) { { filePath: 'example_file1', content: 'This is the example file 1' }}
+ let(:file_2) { { filePath: 'example_file2', content: 'This is the example file 2' }}
+ let(:actions) { [{ action: action }.merge(file_1), { action: action }.merge(file_2)] }
+ let(:mutation_vars) do
+ {
+ description: description,
+ visibility_level: visibility_level,
+ project_path: project_path,
+ title: title,
+ files: actions
+ }
+ end
+
+ it 'creates the Snippet' do
+ expect do
+ subject
+ end.to change { Snippet.count }.by(1)
+ end
+
+ it 'returns the created Snippet' do
+ subject
+
+ expect(mutation_response['snippet']['title']).to eq(title)
+ expect(mutation_response['snippet']['description']).to eq(description)
+ expect(mutation_response['snippet']['visibilityLevel']).to eq(visibility_level)
+ expect(mutation_response['snippet']['blobs'][0]['plainData']).to match(file_1[:content])
+ expect(mutation_response['snippet']['blobs'][0]['fileName']).to match(file_1[:file_path])
+ expect(mutation_response['snippet']['blobs'][1]['plainData']).to match(file_2[:content])
+ expect(mutation_response['snippet']['blobs'][1]['fileName']).to match(file_2[:file_path])
+ end
+
+ context 'when action is invalid' do
+ let(:file_1) { { filePath: 'example_file1' }}
+
+ it_behaves_like 'a mutation that returns errors in the response', errors: ['Snippet actions have invalid data']
+ it_behaves_like 'does not create snippet'
+ end
+ end
+
context 'when there are ActiveRecord validation errors' do
let(:title) { '' }
diff --git a/spec/requests/api/graphql/mutations/snippets/destroy_spec.rb b/spec/requests/api/graphql/mutations/snippets/destroy_spec.rb
index cb9aeea74b2..8ade72635af 100644
--- a/spec/requests/api/graphql/mutations/snippets/destroy_spec.rb
+++ b/spec/requests/api/graphql/mutations/snippets/destroy_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Destroying a Snippet' do
+RSpec.describe 'Destroying a Snippet' do
include GraphqlHelpers
let(:current_user) { snippet.author }
diff --git a/spec/requests/api/graphql/mutations/snippets/mark_as_spam_spec.rb b/spec/requests/api/graphql/mutations/snippets/mark_as_spam_spec.rb
index 6d4dce3f6f1..97e6ae8fda8 100644
--- a/spec/requests/api/graphql/mutations/snippets/mark_as_spam_spec.rb
+++ b/spec/requests/api/graphql/mutations/snippets/mark_as_spam_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Mark snippet as spam', :do_not_mock_admin_mode do
+RSpec.describe 'Mark snippet as spam', :do_not_mock_admin_mode do
include GraphqlHelpers
let_it_be(:admin) { create(:admin) }
diff --git a/spec/requests/api/graphql/mutations/snippets/update_spec.rb b/spec/requests/api/graphql/mutations/snippets/update_spec.rb
index 968ea5aed52..3b2f9dc0f19 100644
--- a/spec/requests/api/graphql/mutations/snippets/update_spec.rb
+++ b/spec/requests/api/graphql/mutations/snippets/update_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Updating a Snippet' do
+RSpec.describe 'Updating a Snippet' do
include GraphqlHelpers
let_it_be(:original_content) { 'Initial content' }
@@ -16,8 +16,8 @@ describe 'Updating a Snippet' do
let(:current_user) { snippet.author }
let(:snippet_gid) { GitlabSchema.id_from_object(snippet).to_s }
- let(:mutation) do
- variables = {
+ let(:mutation_vars) do
+ {
id: snippet_gid,
content: updated_content,
description: updated_description,
@@ -25,8 +25,9 @@ describe 'Updating a Snippet' do
file_name: updated_file_name,
title: updated_title
}
-
- graphql_mutation(:update_snippet, variables)
+ end
+ let(:mutation) do
+ graphql_mutation(:update_snippet, mutation_vars)
end
def mutation_response
@@ -101,7 +102,6 @@ describe 'Updating a Snippet' do
end
it_behaves_like 'graphql update actions'
-
it_behaves_like 'when the snippet is not found'
end
@@ -148,4 +148,40 @@ describe 'Updating a Snippet' do
it_behaves_like 'when the snippet is not found'
end
+
+ context 'when using the files params' do
+ let!(:snippet) { create(:personal_snippet, :private, :repository) }
+ let(:updated_content) { 'updated_content' }
+ let(:updated_file) { 'CHANGELOG' }
+ let(:deleted_file) { 'README' }
+ let(:mutation_vars) do
+ {
+ id: snippet_gid,
+ files: [
+ { action: :update, filePath: updated_file, content: updated_content },
+ { action: :delete, filePath: deleted_file }
+ ]
+ }
+ end
+
+ it 'updates the Snippet' do
+ blob_to_update = blob_at(updated_file)
+ expect(blob_to_update.data).not_to eq updated_content
+
+ blob_to_delete = blob_at(deleted_file)
+ expect(blob_to_delete).to be_present
+
+ post_graphql_mutation(mutation, current_user: current_user)
+
+ blob_to_update = blob_at(updated_file)
+ expect(blob_to_update.data).to eq updated_content
+
+ blob_to_delete = blob_at(deleted_file)
+ expect(blob_to_delete).to be_nil
+ end
+
+ def blob_at(filename)
+ snippet.repository.blob_at('HEAD', filename)
+ end
+ end
end
diff --git a/spec/requests/api/graphql/mutations/todos/mark_all_done_spec.rb b/spec/requests/api/graphql/mutations/todos/mark_all_done_spec.rb
index 40e085027d7..ed5552f3e30 100644
--- a/spec/requests/api/graphql/mutations/todos/mark_all_done_spec.rb
+++ b/spec/requests/api/graphql/mutations/todos/mark_all_done_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Marking all todos done' do
+RSpec.describe 'Marking all todos done' do
include GraphqlHelpers
let_it_be(:current_user) { create(:user) }
diff --git a/spec/requests/api/graphql/mutations/todos/mark_done_spec.rb b/spec/requests/api/graphql/mutations/todos/mark_done_spec.rb
index fabbb3aeb49..9c4733f6769 100644
--- a/spec/requests/api/graphql/mutations/todos/mark_done_spec.rb
+++ b/spec/requests/api/graphql/mutations/todos/mark_done_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Marking todos done' do
+RSpec.describe 'Marking todos done' do
include GraphqlHelpers
let_it_be(:current_user) { create(:user) }
diff --git a/spec/requests/api/graphql/mutations/todos/restore_spec.rb b/spec/requests/api/graphql/mutations/todos/restore_spec.rb
index faa36c8273a..6dedde56e13 100644
--- a/spec/requests/api/graphql/mutations/todos/restore_spec.rb
+++ b/spec/requests/api/graphql/mutations/todos/restore_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Restoring Todos' do
+RSpec.describe 'Restoring Todos' do
include GraphqlHelpers
let_it_be(:current_user) { create(:user) }
diff --git a/spec/requests/api/graphql/namespace/projects_spec.rb b/spec/requests/api/graphql/namespace/projects_spec.rb
index 2a95b99572f..0b634e6b689 100644
--- a/spec/requests/api/graphql/namespace/projects_spec.rb
+++ b/spec/requests/api/graphql/namespace/projects_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'getting projects' do
+RSpec.describe 'getting projects' do
include GraphqlHelpers
let(:group) { create(:group) }
diff --git a/spec/requests/api/graphql/namespace/root_storage_statistics_spec.rb b/spec/requests/api/graphql/namespace/root_storage_statistics_spec.rb
index f7e28043930..44e68c59248 100644
--- a/spec/requests/api/graphql/namespace/root_storage_statistics_spec.rb
+++ b/spec/requests/api/graphql/namespace/root_storage_statistics_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'rendering namespace statistics' do
+RSpec.describe 'rendering namespace statistics' do
include GraphqlHelpers
let(:namespace) { user.namespace }
diff --git a/spec/requests/api/graphql/project/alert_management/alert/assignees_spec.rb b/spec/requests/api/graphql/project/alert_management/alert/assignees_spec.rb
index 4c048caaeee..dd001a73349 100644
--- a/spec/requests/api/graphql/project/alert_management/alert/assignees_spec.rb
+++ b/spec/requests/api/graphql/project/alert_management/alert/assignees_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'getting Alert Management Alert Assignees' do
+RSpec.describe 'getting Alert Management Alert Assignees' do
include GraphqlHelpers
let_it_be(:project) { create(:project) }
diff --git a/spec/requests/api/graphql/project/alert_management/alert/metrics_dashboard_url_spec.rb b/spec/requests/api/graphql/project/alert_management/alert/metrics_dashboard_url_spec.rb
new file mode 100644
index 00000000000..352a94cfc1d
--- /dev/null
+++ b/spec/requests/api/graphql/project/alert_management/alert/metrics_dashboard_url_spec.rb
@@ -0,0 +1,62 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'getting Alert Management Alert Assignees' do
+ include GraphqlHelpers
+
+ let_it_be(:project) { create(:project) }
+ let_it_be(:current_user) { create(:user) }
+
+ let(:fields) do
+ <<~QUERY
+ nodes {
+ iid
+ metricsDashboardUrl
+ }
+ QUERY
+ end
+
+ let(:graphql_query) do
+ graphql_query_for(
+ 'project',
+ { 'fullPath' => project.full_path },
+ query_graphql_field('alertManagementAlerts', {}, fields)
+ )
+ end
+
+ let(:alerts) { graphql_data.dig('project', 'alertManagementAlerts', 'nodes') }
+ let(:first_alert) { alerts.first }
+
+ before do
+ project.add_developer(current_user)
+ end
+
+ context 'with self-managed prometheus payload' do
+ include_context 'self-managed prometheus alert attributes'
+
+ before do
+ create(:alert_management_alert, :prometheus, project: project, payload: payload)
+ end
+
+ it 'includes the correct metrics dashboard url' do
+ post_graphql(graphql_query, current_user: current_user)
+
+ expect(first_alert).to include('metricsDashboardUrl' => dashboard_url_for_alert)
+ end
+ end
+
+ context 'with gitlab-managed prometheus payload' do
+ include_context 'gitlab-managed prometheus alert attributes'
+
+ before do
+ create(:alert_management_alert, :prometheus, project: project, payload: payload, prometheus_alert: prometheus_alert)
+ end
+
+ it 'includes the correct metrics dashboard url' do
+ post_graphql(graphql_query, current_user: current_user)
+
+ expect(first_alert).to include('metricsDashboardUrl' => dashboard_url_for_alert)
+ end
+ end
+end
diff --git a/spec/requests/api/graphql/project/alert_management/alert/notes_spec.rb b/spec/requests/api/graphql/project/alert_management/alert/notes_spec.rb
index df6bfa8c97b..1350cba119b 100644
--- a/spec/requests/api/graphql/project/alert_management/alert/notes_spec.rb
+++ b/spec/requests/api/graphql/project/alert_management/alert/notes_spec.rb
@@ -2,15 +2,15 @@
require 'spec_helper'
-describe 'getting Alert Management Alert Notes' do
+RSpec.describe 'getting Alert Management Alert Notes' do
include GraphqlHelpers
let_it_be(:project) { create(:project) }
let_it_be(:current_user) { create(:user) }
let_it_be(:first_alert) { create(:alert_management_alert, project: project, assignees: [current_user]) }
let_it_be(:second_alert) { create(:alert_management_alert, project: project) }
- let_it_be(:first_system_note) { create(:note_on_alert, noteable: first_alert, project: project) }
- let_it_be(:second_system_note) { create(:note_on_alert, noteable: first_alert, project: project) }
+ let_it_be(:first_system_note) { create(:note_on_alert, :with_system_note_metadata, noteable: first_alert, project: project) }
+ let_it_be(:second_system_note) { create(:note_on_alert, :with_system_note_metadata, noteable: first_alert, project: project) }
let(:params) { {} }
@@ -21,6 +21,8 @@ describe 'getting Alert Management Alert Notes' do
notes {
nodes {
id
+ body
+ systemNoteIconName
}
}
}
@@ -44,7 +46,17 @@ describe 'getting Alert Management Alert Notes' do
project.add_developer(current_user)
end
- it 'returns the notes ordered by createdAt' do
+ it 'includes expected data' do
+ post_graphql(query, current_user: current_user)
+
+ expect(first_notes_result.first).to include(
+ 'id' => first_system_note.to_global_id.to_s,
+ 'systemNoteIconName' => 'git-merge',
+ 'body' => first_system_note.note
+ )
+ end
+
+ it 'returns the notes ordered by createdAt with sufficient content' do
post_graphql(query, current_user: current_user)
expect(first_notes_result.length).to eq(2)
@@ -64,4 +76,18 @@ describe 'getting Alert Management Alert Notes' do
expect { post_graphql(query, current_user: current_user) }.not_to exceed_query_limit(base_count)
expect(alerts_result.length).to eq(3)
end
+
+ context 'for non-system notes' do
+ let_it_be(:user_note) { create(:note_on_alert, noteable: second_alert, project: project) }
+
+ it 'includes expected data' do
+ post_graphql(query, current_user: current_user)
+
+ expect(second_notes_result.first).to include(
+ 'id' => user_note.to_global_id.to_s,
+ 'systemNoteIconName' => nil,
+ 'body' => user_note.note
+ )
+ end
+ end
end
diff --git a/spec/requests/api/graphql/project/alert_management/alert_status_counts_spec.rb b/spec/requests/api/graphql/project/alert_management/alert_status_counts_spec.rb
index a0d1ff7efc5..b62215f43fb 100644
--- a/spec/requests/api/graphql/project/alert_management/alert_status_counts_spec.rb
+++ b/spec/requests/api/graphql/project/alert_management/alert_status_counts_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe 'getting Alert Management Alert counts by status' do
+RSpec.describe 'getting Alert Management Alert counts by status' do
include GraphqlHelpers
let_it_be(:project) { create(:project, :repository) }
diff --git a/spec/requests/api/graphql/project/alert_management/alerts_spec.rb b/spec/requests/api/graphql/project/alert_management/alerts_spec.rb
index c591895f295..f050c6873f3 100644
--- a/spec/requests/api/graphql/project/alert_management/alerts_spec.rb
+++ b/spec/requests/api/graphql/project/alert_management/alerts_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe 'getting Alert Management Alerts' do
+RSpec.describe 'getting Alert Management Alerts' do
include GraphqlHelpers
let_it_be(:payload) { { 'custom' => { 'alert' => 'payload' } } }
@@ -73,12 +73,13 @@ describe 'getting Alert Management Alerts' do
'endedAt' => nil,
'details' => { 'custom.alert' => 'payload' },
'createdAt' => triggered_alert.created_at.strftime('%Y-%m-%dT%H:%M:%SZ'),
- 'updatedAt' => triggered_alert.updated_at.strftime('%Y-%m-%dT%H:%M:%SZ')
+ 'updatedAt' => triggered_alert.updated_at.strftime('%Y-%m-%dT%H:%M:%SZ'),
+ 'metricsDashboardUrl' => nil
)
expect(second_alert).to include(
'iid' => resolved_alert.iid.to_s,
- 'issueIid' => nil,
+ 'issueIid' => resolved_alert.issue_iid.to_s,
'status' => 'RESOLVED',
'endedAt' => resolved_alert.ended_at.strftime('%Y-%m-%dT%H:%M:%SZ')
)
@@ -109,14 +110,14 @@ describe 'getting Alert Management Alerts' do
it_behaves_like 'a working graphql query'
it 'sorts in the correct order' do
- expect(iids).to eq [resolved_alert.iid.to_s, triggered_alert.iid.to_s]
+ expect(iids).to eq [triggered_alert.iid.to_s, resolved_alert.iid.to_s]
end
context 'ascending order' do
let(:params) { 'sort: SEVERITY_ASC' }
it 'sorts in the correct order' do
- expect(iids).to eq [triggered_alert.iid.to_s, resolved_alert.iid.to_s]
+ expect(iids).to eq [resolved_alert.iid.to_s, triggered_alert.iid.to_s]
end
end
end
diff --git a/spec/requests/api/graphql/project/base_service_spec.rb b/spec/requests/api/graphql/project/base_service_spec.rb
index 8199f331fbf..4dfc242da80 100644
--- a/spec/requests/api/graphql/project/base_service_spec.rb
+++ b/spec/requests/api/graphql/project/base_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'query Jira service' do
+RSpec.describe 'query Jira service' do
include GraphqlHelpers
let_it_be(:current_user) { create(:user) }
diff --git a/spec/requests/api/graphql/project/container_expiration_policy_spec.rb b/spec/requests/api/graphql/project/container_expiration_policy_spec.rb
index d0563f9ff05..b064e4d43e9 100644
--- a/spec/requests/api/graphql/project/container_expiration_policy_spec.rb
+++ b/spec/requests/api/graphql/project/container_expiration_policy_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe 'getting a repository in a project' do
+RSpec.describe 'getting a repository in a project' do
include GraphqlHelpers
let_it_be(:project) { create(:project) }
diff --git a/spec/requests/api/graphql/project/error_tracking/sentry_detailed_error_request_spec.rb b/spec/requests/api/graphql/project/error_tracking/sentry_detailed_error_request_spec.rb
index a1f9fa1f10c..b2b42137acf 100644
--- a/spec/requests/api/graphql/project/error_tracking/sentry_detailed_error_request_spec.rb
+++ b/spec/requests/api/graphql/project/error_tracking/sentry_detailed_error_request_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe 'getting a detailed sentry error' do
+RSpec.describe 'getting a detailed sentry error' do
include GraphqlHelpers
let_it_be(:project) { create(:project, :repository) }
diff --git a/spec/requests/api/graphql/project/error_tracking/sentry_errors_request_spec.rb b/spec/requests/api/graphql/project/error_tracking/sentry_errors_request_spec.rb
index 06a0bfc0d32..cd84ce9cb96 100644
--- a/spec/requests/api/graphql/project/error_tracking/sentry_errors_request_spec.rb
+++ b/spec/requests/api/graphql/project/error_tracking/sentry_errors_request_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe 'sentry errors requests' do
+RSpec.describe 'sentry errors requests' do
include GraphqlHelpers
let_it_be(:project) { create(:project, :repository) }
let_it_be(:project_setting) { create(:project_error_tracking_setting, project: project) }
diff --git a/spec/requests/api/graphql/project/grafana_integration_spec.rb b/spec/requests/api/graphql/project/grafana_integration_spec.rb
index c9bc6c1a68e..688959e622d 100644
--- a/spec/requests/api/graphql/project/grafana_integration_spec.rb
+++ b/spec/requests/api/graphql/project/grafana_integration_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe 'Getting Grafana Integration' do
+RSpec.describe 'Getting Grafana Integration' do
include GraphqlHelpers
let_it_be(:project) { create(:project, :repository) }
diff --git a/spec/requests/api/graphql/project/issue/design_collection/version_spec.rb b/spec/requests/api/graphql/project/issue/design_collection/version_spec.rb
index 04f445b4318..1b654e660e3 100644
--- a/spec/requests/api/graphql/project/issue/design_collection/version_spec.rb
+++ b/spec/requests/api/graphql/project/issue/design_collection/version_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Query.project(fullPath).issue(iid).designCollection.version(sha)' do
+RSpec.describe 'Query.project(fullPath).issue(iid).designCollection.version(sha)' do
include GraphqlHelpers
include DesignManagementTestHelpers
diff --git a/spec/requests/api/graphql/project/issue/design_collection/versions_spec.rb b/spec/requests/api/graphql/project/issue/design_collection/versions_spec.rb
index 18787bf925d..640ac95cd86 100644
--- a/spec/requests/api/graphql/project/issue/design_collection/versions_spec.rb
+++ b/spec/requests/api/graphql/project/issue/design_collection/versions_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Getting versions related to an issue' do
+RSpec.describe 'Getting versions related to an issue' do
include GraphqlHelpers
include DesignManagementTestHelpers
diff --git a/spec/requests/api/graphql/project/issue/designs/designs_spec.rb b/spec/requests/api/graphql/project/issue/designs/designs_spec.rb
index b6fd0d91bda..e47c025f8b2 100644
--- a/spec/requests/api/graphql/project/issue/designs/designs_spec.rb
+++ b/spec/requests/api/graphql/project/issue/designs/designs_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Getting designs related to an issue' do
+RSpec.describe 'Getting designs related to an issue' do
include GraphqlHelpers
include DesignManagementTestHelpers
diff --git a/spec/requests/api/graphql/project/issue/designs/notes_spec.rb b/spec/requests/api/graphql/project/issue/designs/notes_spec.rb
index 0207bb9123a..ae5c8363d0f 100644
--- a/spec/requests/api/graphql/project/issue/designs/notes_spec.rb
+++ b/spec/requests/api/graphql/project/issue/designs/notes_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Getting designs related to an issue' do
+RSpec.describe 'Getting designs related to an issue' do
include GraphqlHelpers
include DesignManagementTestHelpers
diff --git a/spec/requests/api/graphql/project/issue/notes_spec.rb b/spec/requests/api/graphql/project/issue/notes_spec.rb
index bfc89434370..97f5261ef1d 100644
--- a/spec/requests/api/graphql/project/issue/notes_spec.rb
+++ b/spec/requests/api/graphql/project/issue/notes_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'getting notes for an issue' do
+RSpec.describe 'getting notes for an issue' do
include GraphqlHelpers
let(:noteable) { create(:issue) }
diff --git a/spec/requests/api/graphql/project/issue_spec.rb b/spec/requests/api/graphql/project/issue_spec.rb
index 92d2f9d0d31..5f368833181 100644
--- a/spec/requests/api/graphql/project/issue_spec.rb
+++ b/spec/requests/api/graphql/project/issue_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Query.project(fullPath).issue(iid)' do
+RSpec.describe 'Query.project(fullPath).issue(iid)' do
include GraphqlHelpers
let_it_be(:project) { create(:project) }
diff --git a/spec/requests/api/graphql/project/issues_spec.rb b/spec/requests/api/graphql/project/issues_spec.rb
index 3128f527356..cdfff2f50d4 100644
--- a/spec/requests/api/graphql/project/issues_spec.rb
+++ b/spec/requests/api/graphql/project/issues_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'getting an issue list for a project' do
+RSpec.describe 'getting an issue list for a project' do
include GraphqlHelpers
let(:project) { create(:project, :repository, :public) }
diff --git a/spec/requests/api/graphql/project/jira_import_spec.rb b/spec/requests/api/graphql/project/jira_import_spec.rb
index 7be14696963..814965262b6 100644
--- a/spec/requests/api/graphql/project/jira_import_spec.rb
+++ b/spec/requests/api/graphql/project/jira_import_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'query Jira import data' do
+RSpec.describe 'query Jira import data' do
include GraphqlHelpers
let_it_be(:current_user) { create(:user) }
diff --git a/spec/requests/api/graphql/project/jira_projects_spec.rb b/spec/requests/api/graphql/project/jira_projects_spec.rb
index d67c89f18c9..d5f59711ab1 100644
--- a/spec/requests/api/graphql/project/jira_projects_spec.rb
+++ b/spec/requests/api/graphql/project/jira_projects_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'query Jira projects' do
+RSpec.describe 'query Jira projects' do
include GraphqlHelpers
let_it_be(:current_user) { create(:user) }
@@ -80,34 +80,6 @@ describe 'query Jira projects' do
it_behaves_like 'fetches first project'
end
-
- context 'with before cursor' do
- let(:projects_query) { 'projects(before: "Mg==", first: 1)' }
-
- it_behaves_like 'fetches first project'
- end
-
- context 'with after cursor' do
- let(:projects_query) { 'projects(after: "MA==", first: 1)' }
-
- it_behaves_like 'fetches first project'
- end
- end
-
- context 'with valid but inexistent after cursor' do
- let(:projects_query) { 'projects(after: "MTk==")' }
-
- it 'retuns empty list of jira projects' do
- expect(jira_projects.size).to eq(0)
- end
- end
-
- context 'with invalid after cursor' do
- let(:projects_query) { 'projects(after: "invalid==")' }
-
- it 'treats the invalid cursor as no cursor and returns list of jira projects' do
- expect(jira_projects.size).to eq(2)
- end
end
end
end
diff --git a/spec/requests/api/graphql/project/jira_service_spec.rb b/spec/requests/api/graphql/project/jira_service_spec.rb
index 4ac598b789f..905a669bf0d 100644
--- a/spec/requests/api/graphql/project/jira_service_spec.rb
+++ b/spec/requests/api/graphql/project/jira_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'query Jira service' do
+RSpec.describe 'query Jira service' do
include GraphqlHelpers
let_it_be(:current_user) { create(:user) }
diff --git a/spec/requests/api/graphql/project/labels_query_spec.rb b/spec/requests/api/graphql/project/labels_query_spec.rb
index ecc43e0a3db..eeaaaaee575 100644
--- a/spec/requests/api/graphql/project/labels_query_spec.rb
+++ b/spec/requests/api/graphql/project/labels_query_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'getting project label information' do
+RSpec.describe 'getting project label information' do
include GraphqlHelpers
let_it_be(:project) { create(:project, :public) }
diff --git a/spec/requests/api/graphql/project/merge_request/diff_notes_spec.rb b/spec/requests/api/graphql/project/merge_request/diff_notes_spec.rb
index c616310a72c..dd16b052e0e 100644
--- a/spec/requests/api/graphql/project/merge_request/diff_notes_spec.rb
+++ b/spec/requests/api/graphql/project/merge_request/diff_notes_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'getting notes for a merge request' do
+RSpec.describe 'getting notes for a merge request' do
include GraphqlHelpers
let_it_be(:noteable) { create(:merge_request) }
diff --git a/spec/requests/api/graphql/project/merge_request_spec.rb b/spec/requests/api/graphql/project/merge_request_spec.rb
index 643532bf2e2..c39358a2db1 100644
--- a/spec/requests/api/graphql/project/merge_request_spec.rb
+++ b/spec/requests/api/graphql/project/merge_request_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'getting merge request information nested in a project' do
+RSpec.describe 'getting merge request information nested in a project' do
include GraphqlHelpers
let(:project) { create(:project, :repository, :public) }
@@ -43,6 +43,59 @@ describe 'getting merge request information nested in a project' do
expect(merge_request_graphql_data['author']['username']).to eq(merge_request.author.username)
end
+ it 'includes diff stats' do
+ be_natural = an_instance_of(Integer).and(be >= 0)
+
+ post_graphql(query, current_user: current_user)
+
+ sums = merge_request_graphql_data['diffStats'].reduce([0, 0, 0]) do |(a, d, c), node|
+ a_, d_ = node.values_at('additions', 'deletions')
+ [a + a_, d + d_, c + a_ + d_]
+ end
+
+ expect(merge_request_graphql_data).to include(
+ 'diffStats' => all(a_hash_including('path' => String, 'additions' => be_natural, 'deletions' => be_natural)),
+ 'diffStatsSummary' => a_hash_including(
+ 'fileCount' => merge_request.diff_stats.count,
+ 'additions' => be_natural,
+ 'deletions' => be_natural,
+ 'changes' => be_natural
+ )
+ )
+
+ # diff_stats is consistent with summary
+ expect(merge_request_graphql_data['diffStatsSummary']
+ .values_at('additions', 'deletions', 'changes')).to eq(sums)
+
+ # diff_stats_summary is internally consistent
+ expect(merge_request_graphql_data['diffStatsSummary']
+ .values_at('additions', 'deletions').sum)
+ .to eq(merge_request_graphql_data.dig('diffStatsSummary', 'changes'))
+ .and be_positive
+ end
+
+ context 'requesting a specific diff stat' do
+ let(:diff_stat) { merge_request.diff_stats.first }
+
+ let(:query) do
+ graphql_query_for(:project, { full_path: project.full_path },
+ query_graphql_field(:merge_request, { iid: merge_request.iid.to_s }, [
+ query_graphql_field(:diff_stats, { path: diff_stat.path }, all_graphql_fields_for('DiffStats'))
+ ])
+ )
+ end
+
+ it 'includes only the requested stats' do
+ post_graphql(query, current_user: current_user)
+
+ expect(merge_request_graphql_data).to include(
+ 'diffStats' => contain_exactly(
+ a_hash_including('path' => diff_stat.path, 'additions' => diff_stat.additions, 'deletions' => diff_stat.deletions)
+ )
+ )
+ end
+ end
+
it 'includes correct mergedAt value when merged' do
time = 1.week.ago
merge_request.mark_as_merged
diff --git a/spec/requests/api/graphql/project/merge_requests_spec.rb b/spec/requests/api/graphql/project/merge_requests_spec.rb
index 49fdfe29874..e2255fdb048 100644
--- a/spec/requests/api/graphql/project/merge_requests_spec.rb
+++ b/spec/requests/api/graphql/project/merge_requests_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'getting merge request listings nested in a project' do
+RSpec.describe 'getting merge request listings nested in a project' do
include GraphqlHelpers
let_it_be(:project) { create(:project, :repository, :public) }
diff --git a/spec/requests/api/graphql/project/packages_spec.rb b/spec/requests/api/graphql/project/packages_spec.rb
new file mode 100644
index 00000000000..88f97f9256b
--- /dev/null
+++ b/spec/requests/api/graphql/project/packages_spec.rb
@@ -0,0 +1,69 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'getting a package list for a project' do
+ include GraphqlHelpers
+
+ let_it_be(:project) { create(:project) }
+ let_it_be(:current_user) { create(:user) }
+ let_it_be(:package) { create(:package, project: project) }
+ let(:packages_data) { graphql_data['project']['packages']['edges'] }
+
+ let(:fields) do
+ <<~QUERY
+ edges {
+ node {
+ #{all_graphql_fields_for('packages'.classify)}
+ }
+ }
+ QUERY
+ end
+
+ let(:query) do
+ graphql_query_for(
+ 'project',
+ { 'fullPath' => project.full_path },
+ query_graphql_field('packages', {}, fields)
+ )
+ end
+
+ context 'without the need for a license' do
+ context 'when user has access to the project' do
+ before do
+ project.add_reporter(current_user)
+ post_graphql(query, current_user: current_user)
+ end
+
+ it_behaves_like 'a working graphql query'
+
+ it 'returns packages successfully' do
+ expect(packages_data[0]['node']['name']).to eq package.name
+ end
+ end
+
+ context 'when the user does not have access to the project/packages' do
+ before do
+ post_graphql(query, current_user: current_user)
+ end
+
+ it_behaves_like 'a working graphql query'
+
+ it 'returns nil' do
+ expect(graphql_data['project']).to be_nil
+ end
+ end
+
+ context 'when the user is not autenthicated' do
+ before do
+ post_graphql(query)
+ end
+
+ it_behaves_like 'a working graphql query'
+
+ it 'returns nil' do
+ expect(graphql_data['project']).to be_nil
+ end
+ end
+ end
+end
diff --git a/spec/requests/api/graphql/project/pipeline_spec.rb b/spec/requests/api/graphql/project/pipeline_spec.rb
index bed9a18577f..57b9de25c3d 100644
--- a/spec/requests/api/graphql/project/pipeline_spec.rb
+++ b/spec/requests/api/graphql/project/pipeline_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'getting pipeline information nested in a project' do
+RSpec.describe 'getting pipeline information nested in a project' do
include GraphqlHelpers
let(:project) { create(:project, :repository, :public) }
diff --git a/spec/requests/api/graphql/project/project_statistics_spec.rb b/spec/requests/api/graphql/project/project_statistics_spec.rb
index 05dd5d36c26..c226b10ab51 100644
--- a/spec/requests/api/graphql/project/project_statistics_spec.rb
+++ b/spec/requests/api/graphql/project/project_statistics_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'rendering project statistics' do
+RSpec.describe 'rendering project statistics' do
include GraphqlHelpers
let(:project) { create(:project) }
diff --git a/spec/requests/api/graphql/project/release_spec.rb b/spec/requests/api/graphql/project/release_spec.rb
index f8624a97a2b..f9c19d9747d 100644
--- a/spec/requests/api/graphql/project/release_spec.rb
+++ b/spec/requests/api/graphql/project/release_spec.rb
@@ -1,206 +1,374 @@
# frozen_string_literal: true
require 'spec_helper'
-require 'pp'
-describe 'Query.project(fullPath).release(tagName)' do
+RSpec.describe 'Query.project(fullPath).release(tagName)' do
include GraphqlHelpers
include Presentable
- let_it_be(:project) { create(:project, :repository) }
- let_it_be(:milestone_1) { create(:milestone, project: project) }
- let_it_be(:milestone_2) { create(:milestone, project: project) }
- let_it_be(:release) { create(:release, :with_evidence, project: project, milestones: [milestone_1, milestone_2]) }
- let_it_be(:release_link_1) { create(:release_link, release: release) }
- let_it_be(:release_link_2) { create(:release_link, release: release) }
let_it_be(:developer) { create(:user) }
+ let_it_be(:guest) { create(:user) }
+ let_it_be(:reporter) { create(:user) }
+ let_it_be(:stranger) { create(:user) }
- let(:current_user) { developer }
+ let(:params_for_issues_and_mrs) { { scope: 'all', state: 'opened', release_tag: release.tag } }
+ let(:post_query) { post_graphql(query, current_user: current_user) }
+ let(:path_prefix) { %w[project release] }
+ let(:data) { graphql_data.dig(*path) }
def query(rq = release_fields)
graphql_query_for(:project, { fullPath: project.full_path },
query_graphql_field(:release, { tagName: release.tag }, rq))
end
- let(:post_query) { post_graphql(query, current_user: current_user) }
- let(:path_prefix) { %w[project release] }
-
- let(:data) { graphql_data.dig(*path) }
-
before do
- project.add_developer(developer)
+ stub_default_url_options(host: 'www.example.com')
end
- describe 'scalar fields' do
- let(:path) { path_prefix }
- let(:release_fields) do
- query_graphql_field(%{
- tagName
- tagPath
- description
- descriptionHtml
- name
- createdAt
- releasedAt
- })
+ shared_examples 'full access to the release field' do
+ describe 'scalar fields' do
+ let(:path) { path_prefix }
+
+ let(:release_fields) do
+ query_graphql_field(%{
+ tagName
+ tagPath
+ description
+ descriptionHtml
+ name
+ createdAt
+ releasedAt
+ })
+ end
+
+ before do
+ post_query
+ end
+
+ it 'finds all release data' do
+ expect(data).to eq({
+ 'tagName' => release.tag,
+ 'tagPath' => project_tag_path(project, release.tag),
+ 'description' => release.description,
+ 'descriptionHtml' => release.description_html,
+ 'name' => release.name,
+ 'createdAt' => release.created_at.iso8601,
+ 'releasedAt' => release.released_at.iso8601
+ })
+ end
end
- before do
- post_query
+ describe 'milestones' do
+ let(:path) { path_prefix + %w[milestones nodes] }
+
+ let(:release_fields) do
+ query_graphql_field(:milestones, nil, 'nodes { id title }')
+ end
+
+ it 'finds all milestones associated to a release' do
+ post_query
+
+ expected = release.milestones.map do |milestone|
+ { 'id' => global_id_of(milestone), 'title' => milestone.title }
+ end
+
+ expect(data).to match_array(expected)
+ end
end
- it 'finds all release data' do
- expect(data).to eq({
- 'tagName' => release.tag,
- 'tagPath' => project_tag_path(project, release.tag),
- 'description' => release.description,
- 'descriptionHtml' => release.description_html,
- 'name' => release.name,
- 'createdAt' => release.created_at.iso8601,
- 'releasedAt' => release.released_at.iso8601
- })
+ describe 'author' do
+ let(:path) { path_prefix + %w[author] }
+
+ let(:release_fields) do
+ query_graphql_field(:author, nil, 'id username')
+ end
+
+ it 'finds the author of the release' do
+ post_query
+
+ expect(data).to eq(
+ 'id' => global_id_of(release.author),
+ 'username' => release.author.username
+ )
+ end
end
- end
- describe 'milestones' do
- let(:path) { path_prefix + %w[milestones nodes] }
- let(:release_fields) do
- query_graphql_field(:milestones, nil, 'nodes { id title }')
+ describe 'commit' do
+ let(:path) { path_prefix + %w[commit] }
+
+ let(:release_fields) do
+ query_graphql_field(:commit, nil, 'sha')
+ end
+
+ it 'finds the commit associated with the release' do
+ post_query
+
+ expect(data).to eq('sha' => release.commit.sha)
+ end
end
- it 'finds all milestones associated to a release' do
- post_query
+ describe 'assets' do
+ describe 'count' do
+ let(:path) { path_prefix + %w[assets] }
+
+ let(:release_fields) do
+ query_graphql_field(:assets, nil, 'count')
+ end
+
+ it 'returns the number of assets associated to the release' do
+ post_query
+
+ expect(data).to eq('count' => release.sources.size + release.links.size)
+ end
+ end
+
+ describe 'links' do
+ let(:path) { path_prefix + %w[assets links nodes] }
- expected = release.milestones.map do |milestone|
- { 'id' => global_id_of(milestone), 'title' => milestone.title }
+ let(:release_fields) do
+ query_graphql_field(:assets, nil,
+ query_graphql_field(:links, nil, 'nodes { id name url external }'))
+ end
+
+ it 'finds all release links' do
+ post_query
+
+ expected = release.links.map do |link|
+ {
+ 'id' => global_id_of(link),
+ 'name' => link.name,
+ 'url' => link.url,
+ 'external' => link.external?
+ }
+ end
+
+ expect(data).to match_array(expected)
+ end
end
- expect(data).to match_array(expected)
+ describe 'sources' do
+ let(:path) { path_prefix + %w[assets sources nodes] }
+
+ let(:release_fields) do
+ query_graphql_field(:assets, nil,
+ query_graphql_field(:sources, nil, 'nodes { format url }'))
+ end
+
+ it 'finds all release sources' do
+ post_query
+
+ expected = release.sources.map do |source|
+ {
+ 'format' => source.format,
+ 'url' => source.url
+ }
+ end
+
+ expect(data).to match_array(expected)
+ end
+ end
+ end
+
+ describe 'links' do
+ let(:path) { path_prefix + %w[links] }
+
+ let(:release_fields) do
+ query_graphql_field(:links, nil, %{
+ selfUrl
+ mergeRequestsUrl
+ issuesUrl
+ })
+ end
+
+ it 'finds all release links' do
+ post_query
+
+ expect(data).to eq(
+ 'selfUrl' => project_release_url(project, release),
+ 'mergeRequestsUrl' => project_merge_requests_url(project, params_for_issues_and_mrs),
+ 'issuesUrl' => project_issues_url(project, params_for_issues_and_mrs)
+ )
+ end
+ end
+
+ describe 'evidences' do
+ let(:path) { path_prefix + %w[evidences] }
+
+ let(:release_fields) do
+ query_graphql_field(:evidences, nil, 'nodes { id sha filepath collectedAt }')
+ end
+
+ it 'finds all evidence fields' do
+ post_query
+
+ evidence = release.evidences.first.present
+
+ expect(data["nodes"].first).to eq(
+ 'id' => global_id_of(evidence),
+ 'sha' => evidence.sha,
+ 'filepath' => evidence.filepath,
+ 'collectedAt' => evidence.collected_at.utc.iso8601
+ )
+ end
+ end
+ end
+
+ shared_examples 'no access to the release field' do
+ describe 'repository-related fields' do
+ let(:path) { path_prefix }
+
+ let(:release_fields) do
+ query_graphql_field('description')
+ end
+
+ before do
+ post_query
+ end
+
+ it 'returns nil' do
+ expect(data).to eq(nil)
+ end
end
end
- describe 'author' do
- let(:path) { path_prefix + %w[author] }
+ shared_examples 'access to editUrl' do
+ let(:path) { path_prefix + %w[links] }
+
let(:release_fields) do
- query_graphql_field(:author, nil, 'id username')
+ query_graphql_field(:links, nil, 'editUrl')
end
- it 'finds the author of the release' do
+ before do
post_query
+ end
- expect(data).to eq({
- 'id' => global_id_of(release.author),
- 'username' => release.author.username
- })
+ it 'returns editUrl' do
+ expect(data).to eq('editUrl' => edit_project_release_url(project, release))
end
end
- describe 'commit' do
- let(:path) { path_prefix + %w[commit] }
+ shared_examples 'no access to editUrl' do
+ let(:path) { path_prefix + %w[links] }
+
let(:release_fields) do
- query_graphql_field(:commit, nil, 'sha')
+ query_graphql_field(:links, nil, 'editUrl')
end
- it 'finds the commit associated with the release' do
+ before do
post_query
+ end
- expect(data).to eq({ 'sha' => release.commit.sha })
+ it 'does not return editUrl' do
+ expect(data).to eq('editUrl' => nil)
end
end
- describe 'assets' do
- describe 'assetsCount' do
- let(:path) { path_prefix + %w[assets] }
- let(:release_fields) do
- query_graphql_field(:assets, nil, 'assetsCount')
+ describe "ensures that the correct data is returned based on the project's visibility and the user's access level" do
+ context 'when the project is private' do
+ let_it_be(:project) { create(:project, :repository, :private) }
+ let_it_be(:milestone_1) { create(:milestone, project: project) }
+ let_it_be(:milestone_2) { create(:milestone, project: project) }
+ let_it_be(:release) { create(:release, :with_evidence, project: project, milestones: [milestone_1, milestone_2]) }
+ let_it_be(:release_link_1) { create(:release_link, release: release) }
+ let_it_be(:release_link_2) { create(:release_link, release: release) }
+
+ before_all do
+ project.add_developer(developer)
+ project.add_guest(guest)
+ project.add_reporter(reporter)
end
- it 'returns the number of assets associated to the release' do
- post_query
+ context 'when the user is not logged in' do
+ let(:current_user) { stranger }
- expect(data).to eq({ 'assetsCount' => release.sources.size + release.links.size })
+ it_behaves_like 'no access to the release field'
end
- end
- describe 'links' do
- let(:path) { path_prefix + %w[assets links nodes] }
- let(:release_fields) do
- query_graphql_field(:assets, nil,
- query_graphql_field(:links, nil, 'nodes { id name url external }'))
+ context 'when the user has Guest permissions' do
+ let(:current_user) { guest }
+
+ it_behaves_like 'no access to the release field'
end
- it 'finds all release links' do
- post_query
+ context 'when the user has Reporter permissions' do
+ let(:current_user) { reporter }
- expected = release.links.map do |link|
- {
- 'id' => global_id_of(link),
- 'name' => link.name,
- 'url' => link.url,
- 'external' => link.external?
- }
- end
+ it_behaves_like 'full access to the release field'
+ it_behaves_like 'no access to editUrl'
+ end
- expect(data).to match_array(expected)
+ context 'when the user has Developer permissions' do
+ let(:current_user) { developer }
+
+ it_behaves_like 'full access to the release field'
+ it_behaves_like 'access to editUrl'
end
end
- describe 'sources' do
- let(:path) { path_prefix + %w[assets sources nodes] }
- let(:release_fields) do
- query_graphql_field(:assets, nil,
- query_graphql_field(:sources, nil, 'nodes { format url }'))
+ context 'when the project is public' do
+ let_it_be(:project) { create(:project, :repository, :public) }
+ let_it_be(:milestone_1) { create(:milestone, project: project) }
+ let_it_be(:milestone_2) { create(:milestone, project: project) }
+ let_it_be(:release) { create(:release, :with_evidence, project: project, milestones: [milestone_1, milestone_2]) }
+ let_it_be(:release_link_1) { create(:release_link, release: release) }
+ let_it_be(:release_link_2) { create(:release_link, release: release) }
+
+ before_all do
+ project.add_developer(developer)
+ project.add_guest(guest)
+ project.add_reporter(reporter)
end
- it 'finds all release sources' do
- post_query
+ context 'when the user is not logged in' do
+ let(:current_user) { stranger }
- expected = release.sources.map do |source|
- {
- 'format' => source.format,
- 'url' => source.url
- }
- end
+ it_behaves_like 'full access to the release field'
+ it_behaves_like 'no access to editUrl'
+ end
- expect(data).to match_array(expected)
+ context 'when the user has Guest permissions' do
+ let(:current_user) { guest }
+
+ it_behaves_like 'full access to the release field'
+ it_behaves_like 'no access to editUrl'
end
- end
- describe 'evidences' do
- let(:path) { path_prefix + %w[evidences] }
- let(:release_fields) do
- query_graphql_field(:evidences, nil, 'nodes { id sha filepath collectedAt }')
+ context 'when the user has Reporter permissions' do
+ let(:current_user) { reporter }
+
+ it_behaves_like 'full access to the release field'
+ it_behaves_like 'no access to editUrl'
end
- context 'for a developer' do
- it 'finds all evidence fields' do
- post_query
+ context 'when the user has Reporter permissions' do
+ let(:current_user) { reporter }
- evidence = release.evidences.first.present
- expected = {
- 'id' => global_id_of(evidence),
- 'sha' => evidence.sha,
- 'filepath' => evidence.filepath,
- 'collectedAt' => evidence.collected_at.utc.iso8601
- }
+ it_behaves_like 'full access to the release field'
+ end
- expect(data["nodes"].first).to eq(expected)
- end
+ context 'when the user has Developer permissions' do
+ let(:current_user) { developer }
+
+ it_behaves_like 'full access to the release field'
+ it_behaves_like 'access to editUrl'
end
+ end
+ end
- context 'for a guest' do
- let(:current_user) { create :user }
+ describe 'ensures that the release data can be contolled by a feature flag' do
+ context 'when the graphql_release_data feature flag is disabled' do
+ let_it_be(:project) { create(:project, :repository, :public) }
+ let_it_be(:release) { create(:release, project: project) }
- before do
- project.add_guest(current_user)
- end
+ let(:current_user) { developer }
- it 'denies access' do
- post_query
+ before do
+ stub_feature_flags(graphql_release_data: false)
- expect(data['node']).to be_nil
- end
+ project.add_developer(developer)
end
+
+ it_behaves_like 'no access to the release field'
end
end
end
diff --git a/spec/requests/api/graphql/project/releases_spec.rb b/spec/requests/api/graphql/project/releases_spec.rb
new file mode 100644
index 00000000000..7e418bbaa5b
--- /dev/null
+++ b/spec/requests/api/graphql/project/releases_spec.rb
@@ -0,0 +1,284 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'Query.project(fullPath).releases()' do
+ include GraphqlHelpers
+
+ let_it_be(:stranger) { create(:user) }
+ let_it_be(:guest) { create(:user) }
+ let_it_be(:reporter) { create(:user) }
+ let_it_be(:developer) { create(:user) }
+
+ let(:query) do
+ graphql_query_for(:project, { fullPath: project.full_path },
+ %{
+ releases {
+ nodes {
+ tagName
+ tagPath
+ name
+ commit {
+ sha
+ }
+ assets {
+ count
+ sources {
+ nodes {
+ url
+ }
+ }
+ }
+ evidences {
+ nodes {
+ sha
+ }
+ }
+ links {
+ selfUrl
+ mergeRequestsUrl
+ issuesUrl
+ }
+ }
+ }
+ })
+ end
+
+ let(:params_for_issues_and_mrs) { { scope: 'all', state: 'opened', release_tag: release.tag } }
+ let(:post_query) { post_graphql(query, current_user: current_user) }
+
+ let(:data) { graphql_data.dig('project', 'releases', 'nodes', 0) }
+
+ before do
+ stub_default_url_options(host: 'www.example.com')
+ end
+
+ shared_examples 'full access to all repository-related fields' do
+ describe 'repository-related fields' do
+ before do
+ post_query
+ end
+
+ it 'returns data for fields that are protected in private projects' do
+ expected_sources = release.sources.map do |s|
+ { 'url' => s.url }
+ end
+
+ expected_evidences = release.evidences.map do |e|
+ { 'sha' => e.sha }
+ end
+
+ expect(data).to eq(
+ 'tagName' => release.tag,
+ 'tagPath' => project_tag_path(project, release.tag),
+ 'name' => release.name,
+ 'commit' => {
+ 'sha' => release.commit.sha
+ },
+ 'assets' => {
+ 'count' => release.assets_count,
+ 'sources' => {
+ 'nodes' => expected_sources
+ }
+ },
+ 'evidences' => {
+ 'nodes' => expected_evidences
+ },
+ 'links' => {
+ 'selfUrl' => project_release_url(project, release),
+ 'mergeRequestsUrl' => project_merge_requests_url(project, params_for_issues_and_mrs),
+ 'issuesUrl' => project_issues_url(project, params_for_issues_and_mrs)
+ }
+ )
+ end
+ end
+ end
+
+ shared_examples 'no access to any repository-related fields' do
+ describe 'repository-related fields' do
+ before do
+ post_query
+ end
+
+ it 'does not return data for fields that expose repository information' do
+ expect(data).to eq(
+ 'tagName' => nil,
+ 'tagPath' => nil,
+ 'name' => "Release-#{release.id}",
+ 'commit' => nil,
+ 'assets' => {
+ 'count' => release.assets_count(except: [:sources]),
+ 'sources' => {
+ 'nodes' => []
+ }
+ },
+ 'evidences' => {
+ 'nodes' => []
+ },
+ 'links' => nil
+ )
+ end
+ end
+ end
+
+ # editUrl is tested separately becuase its permissions
+ # are slightly different than other release fields
+ shared_examples 'access to editUrl' do
+ let(:query) do
+ graphql_query_for(:project, { fullPath: project.full_path },
+ %{
+ releases {
+ nodes {
+ links {
+ editUrl
+ }
+ }
+ }
+ })
+ end
+
+ before do
+ post_query
+ end
+
+ it 'returns editUrl' do
+ expect(data).to eq(
+ 'links' => {
+ 'editUrl' => edit_project_release_url(project, release)
+ }
+ )
+ end
+ end
+
+ shared_examples 'no access to editUrl' do
+ let(:query) do
+ graphql_query_for(:project, { fullPath: project.full_path },
+ %{
+ releases {
+ nodes {
+ links {
+ editUrl
+ }
+ }
+ }
+ })
+ end
+
+ before do
+ post_query
+ end
+
+ it 'does not return editUrl' do
+ expect(data).to eq(
+ 'links' => {
+ 'editUrl' => nil
+ }
+ )
+ end
+ end
+
+ shared_examples 'no access to any release data' do
+ before do
+ post_query
+ end
+
+ it 'returns nil' do
+ expect(data).to eq(nil)
+ end
+ end
+
+ describe "ensures that the correct data is returned based on the project's visibility and the user's access level" do
+ context 'when the project is private' do
+ let_it_be(:project) { create(:project, :repository, :private) }
+ let_it_be(:release) { create(:release, :with_evidence, project: project) }
+
+ before_all do
+ project.add_guest(guest)
+ project.add_reporter(reporter)
+ project.add_developer(developer)
+ end
+
+ context 'when the user is not logged in' do
+ let(:current_user) { stranger }
+
+ it_behaves_like 'no access to any release data'
+ end
+
+ context 'when the user has Guest permissions' do
+ let(:current_user) { guest }
+
+ it_behaves_like 'no access to any repository-related fields'
+ end
+
+ context 'when the user has Reporter permissions' do
+ let(:current_user) { reporter }
+
+ it_behaves_like 'full access to all repository-related fields'
+ it_behaves_like 'no access to editUrl'
+ end
+
+ context 'when the user has Developer permissions' do
+ let(:current_user) { developer }
+
+ it_behaves_like 'full access to all repository-related fields'
+ it_behaves_like 'access to editUrl'
+ end
+ end
+
+ context 'when the project is public' do
+ let_it_be(:project) { create(:project, :repository, :public) }
+ let_it_be(:release) { create(:release, :with_evidence, project: project) }
+
+ before_all do
+ project.add_guest(guest)
+ project.add_reporter(reporter)
+ project.add_developer(developer)
+ end
+
+ context 'when the user is not logged in' do
+ let(:current_user) { stranger }
+
+ it_behaves_like 'full access to all repository-related fields'
+ it_behaves_like 'no access to editUrl'
+ end
+
+ context 'when the user has Guest permissions' do
+ let(:current_user) { guest }
+
+ it_behaves_like 'full access to all repository-related fields'
+ it_behaves_like 'no access to editUrl'
+ end
+
+ context 'when the user has Reporter permissions' do
+ let(:current_user) { reporter }
+
+ it_behaves_like 'full access to all repository-related fields'
+ it_behaves_like 'no access to editUrl'
+ end
+
+ context 'when the user has Developer permissions' do
+ let(:current_user) { developer }
+
+ it_behaves_like 'full access to all repository-related fields'
+ it_behaves_like 'access to editUrl'
+ end
+ end
+ end
+
+ describe 'ensures that the release data can be contolled by a feature flag' do
+ context 'when the graphql_release_data feature flag is disabled' do
+ let_it_be(:project) { create(:project, :repository, :public) }
+ let_it_be(:release) { create(:release, project: project) }
+
+ let(:current_user) { developer }
+
+ before do
+ stub_feature_flags(graphql_release_data: false)
+
+ project.add_developer(developer)
+ end
+
+ it_behaves_like 'no access to any release data'
+ end
+ end
+end
diff --git a/spec/requests/api/graphql/project/repository_spec.rb b/spec/requests/api/graphql/project/repository_spec.rb
index 261433a3d6a..bd719a69647 100644
--- a/spec/requests/api/graphql/project/repository_spec.rb
+++ b/spec/requests/api/graphql/project/repository_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe 'getting a repository in a project' do
+RSpec.describe 'getting a repository in a project' do
include GraphqlHelpers
let(:project) { create(:project, :repository) }
diff --git a/spec/requests/api/graphql/project/tree/tree_spec.rb b/spec/requests/api/graphql/project/tree/tree_spec.rb
index 94128cc21ee..bce63d57c38 100644
--- a/spec/requests/api/graphql/project/tree/tree_spec.rb
+++ b/spec/requests/api/graphql/project/tree/tree_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe 'getting a tree in a project' do
+RSpec.describe 'getting a tree in a project' do
include GraphqlHelpers
let(:project) { create(:project, :repository) }
diff --git a/spec/requests/api/graphql/project_query_spec.rb b/spec/requests/api/graphql/project_query_spec.rb
index 9a88b47eea6..b115030afbc 100644
--- a/spec/requests/api/graphql/project_query_spec.rb
+++ b/spec/requests/api/graphql/project_query_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'getting project information' do
+RSpec.describe 'getting project information' do
include GraphqlHelpers
let(:project) { create(:project, :repository) }
diff --git a/spec/requests/api/graphql/query_spec.rb b/spec/requests/api/graphql/query_spec.rb
index 26b4c6eafd7..6bd0703c121 100644
--- a/spec/requests/api/graphql/query_spec.rb
+++ b/spec/requests/api/graphql/query_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Query' do
+RSpec.describe 'Query' do
include GraphqlHelpers
let_it_be(:project) { create(:project) }
diff --git a/spec/requests/api/graphql/read_only_spec.rb b/spec/requests/api/graphql/read_only_spec.rb
index 1d28a71258d..ce8a3f6ef5c 100644
--- a/spec/requests/api/graphql/read_only_spec.rb
+++ b/spec/requests/api/graphql/read_only_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Requests on a read-only node' do
+RSpec.describe 'Requests on a read-only node' do
include GraphqlHelpers
before do
diff --git a/spec/requests/api/graphql/tasks/task_completion_status_spec.rb b/spec/requests/api/graphql/tasks/task_completion_status_spec.rb
index c47406ea534..5f4d2aec718 100644
--- a/spec/requests/api/graphql/tasks/task_completion_status_spec.rb
+++ b/spec/requests/api/graphql/tasks/task_completion_status_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'getting task completion status information' do
+RSpec.describe 'getting task completion status information' do
include GraphqlHelpers
description_0_done = '- [ ] task 1\n- [ ] task 2'
diff --git a/spec/requests/api/graphql/user/group_member_query_spec.rb b/spec/requests/api/graphql/user/group_member_query_spec.rb
index 022ee79297c..3a16d962214 100644
--- a/spec/requests/api/graphql/user/group_member_query_spec.rb
+++ b/spec/requests/api/graphql/user/group_member_query_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'GroupMember' do
+RSpec.describe 'GroupMember' do
include GraphqlHelpers
let_it_be(:member) { create(:group_member, :developer) }
diff --git a/spec/requests/api/graphql/user/project_member_query_spec.rb b/spec/requests/api/graphql/user/project_member_query_spec.rb
index 397d2872189..0790e148caf 100644
--- a/spec/requests/api/graphql/user/project_member_query_spec.rb
+++ b/spec/requests/api/graphql/user/project_member_query_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'ProjectMember' do
+RSpec.describe 'ProjectMember' do
include GraphqlHelpers
let_it_be(:member) { create(:project_member, :developer) }
diff --git a/spec/requests/api/graphql/user_query_spec.rb b/spec/requests/api/graphql/user_query_spec.rb
index 5ac94bc7323..7ba1788a9ef 100644
--- a/spec/requests/api/graphql/user_query_spec.rb
+++ b/spec/requests/api/graphql/user_query_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'getting user information' do
+RSpec.describe 'getting user information' do
include GraphqlHelpers
let(:query) do
diff --git a/spec/requests/api/graphql/user_spec.rb b/spec/requests/api/graphql/user_spec.rb
index 097c75b3541..d2d6b1fca66 100644
--- a/spec/requests/api/graphql/user_spec.rb
+++ b/spec/requests/api/graphql/user_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'User' do
+RSpec.describe 'User' do
include GraphqlHelpers
let_it_be(:current_user) { create(:user) }
diff --git a/spec/requests/api/graphql/users_spec.rb b/spec/requests/api/graphql/users_spec.rb
index 1e6d73cbd7d..91ac206676b 100644
--- a/spec/requests/api/graphql/users_spec.rb
+++ b/spec/requests/api/graphql/users_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Users' do
+RSpec.describe 'Users' do
include GraphqlHelpers
let_it_be(:current_user) { create(:user, created_at: 1.day.ago) }
diff --git a/spec/requests/api/graphql_spec.rb b/spec/requests/api/graphql_spec.rb
index 84be5ab0951..ff1a5aa1540 100644
--- a/spec/requests/api/graphql_spec.rb
+++ b/spec/requests/api/graphql_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe 'GraphQL' do
+RSpec.describe 'GraphQL' do
include GraphqlHelpers
let(:query) { graphql_query_for('echo', 'text' => 'Hello world' ) }
diff --git a/spec/requests/api/group_boards_spec.rb b/spec/requests/api/group_boards_spec.rb
index a9083f82f25..6ce8b766807 100644
--- a/spec/requests/api/group_boards_spec.rb
+++ b/spec/requests/api/group_boards_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::GroupBoards do
+RSpec.describe API::GroupBoards do
let_it_be(:user) { create(:user) }
let_it_be(:non_member) { create(:user) }
let_it_be(:guest) { create(:user) }
diff --git a/spec/requests/api/group_clusters_spec.rb b/spec/requests/api/group_clusters_spec.rb
index fade54f6b11..068af1485e2 100644
--- a/spec/requests/api/group_clusters_spec.rb
+++ b/spec/requests/api/group_clusters_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::GroupClusters do
+RSpec.describe API::GroupClusters do
include KubernetesHelpers
let(:current_user) { create(:user) }
@@ -266,29 +266,51 @@ describe API::GroupClusters do
end
end
- context 'when user tries to add multiple clusters' do
+ context 'non-authorized user' do
before do
- create(:cluster, :provided_by_gcp, :group,
- groups: [group])
-
- post api("/groups/#{group.id}/clusters/user", current_user), params: cluster_params
+ post api("/groups/#{group.id}/clusters/user", developer_user), params: cluster_params
end
- it 'responds with 400' do
- expect(response).to have_gitlab_http_status(:bad_request)
- expect(json_response['message']['base'].first).to eq(_('Instance does not support multiple Kubernetes clusters'))
+ it 'responds with 403' do
+ expect(response).to have_gitlab_http_status(:forbidden)
+
+ expect(json_response['message']).to eq('403 Forbidden')
end
end
+ end
- context 'non-authorized user' do
+ describe 'PUT /groups/:id/clusters/:cluster_id' do
+ let(:api_url) { 'https://kubernetes.example.com' }
+
+ let(:platform_kubernetes_attributes) do
+ {
+ api_url: api_url,
+ token: 'sample-token'
+ }
+ end
+
+ let(:cluster_params) do
+ {
+ name: 'test-cluster',
+ environment_scope: 'test/*',
+ platform_kubernetes_attributes: platform_kubernetes_attributes
+ }
+ end
+
+ context 'when another cluster exists' do
before do
- post api("/groups/#{group.id}/clusters/user", developer_user), params: cluster_params
+ create(:cluster, :provided_by_gcp, :group,
+ groups: [group])
+
+ post api("/groups/#{group.id}/clusters/user", current_user), params: cluster_params
end
- it 'responds with 403' do
- expect(response).to have_gitlab_http_status(:forbidden)
+ it 'responds with 201' do
+ expect(response).to have_gitlab_http_status(:created)
+ end
- expect(json_response['message']).to eq('403 Forbidden')
+ it 'allows multiple clusters to be associated to group' do
+ expect(group.reload.clusters.count).to eq(2)
end
end
end
diff --git a/spec/requests/api/group_container_repositories_spec.rb b/spec/requests/api/group_container_repositories_spec.rb
index 9f439bb2167..3128becae6d 100644
--- a/spec/requests/api/group_container_repositories_spec.rb
+++ b/spec/requests/api/group_container_repositories_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::GroupContainerRepositories do
+RSpec.describe API::GroupContainerRepositories do
let_it_be(:group) { create(:group, :private) }
let_it_be(:project) { create(:project, :private, group: group) }
let_it_be(:reporter) { create(:user) }
diff --git a/spec/requests/api/group_export_spec.rb b/spec/requests/api/group_export_spec.rb
index 9dd7797c768..50a1e9d0c3d 100644
--- a/spec/requests/api/group_export_spec.rb
+++ b/spec/requests/api/group_export_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::GroupExport do
+RSpec.describe API::GroupExport do
let_it_be(:group) { create(:group) }
let_it_be(:user) { create(:user) }
@@ -33,6 +33,10 @@ describe API::GroupExport do
context 'group_import_export feature flag enabled' do
before do
stub_feature_flags(group_import_export: true)
+
+ allow(Gitlab::ApplicationRateLimiter)
+ .to receive(:increment)
+ .and_return(0)
end
context 'when export file exists' do
@@ -87,7 +91,7 @@ describe API::GroupExport do
before do
allow(Gitlab::ApplicationRateLimiter)
.to receive(:increment)
- .and_return(Gitlab::ApplicationRateLimiter.rate_limits[:group_download_export][:threshold] + 1)
+ .and_return(Gitlab::ApplicationRateLimiter.rate_limits[:group_download_export][:threshold].call + 1)
end
it 'throttles the endpoint' do
@@ -162,7 +166,7 @@ describe API::GroupExport do
allow(Gitlab::ApplicationRateLimiter)
.to receive(:increment)
- .and_return(Gitlab::ApplicationRateLimiter.rate_limits[:group_export][:threshold] + 1)
+ .and_return(Gitlab::ApplicationRateLimiter.rate_limits[:group_export][:threshold].call + 1)
end
it 'throttles the endpoint' do
diff --git a/spec/requests/api/group_import_spec.rb b/spec/requests/api/group_import_spec.rb
index b60a1b3f119..ad67f737725 100644
--- a/spec/requests/api/group_import_spec.rb
+++ b/spec/requests/api/group_import_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::GroupImport do
+RSpec.describe API::GroupImport do
include WorkhorseHelpers
let_it_be(:user) { create(:user) }
@@ -122,6 +122,7 @@ describe API::GroupImport do
before do
allow_next_instance_of(Group) do |group|
allow(group).to receive(:persisted?).and_return(false)
+ allow(group).to receive(:save).and_return(false)
end
end
diff --git a/spec/requests/api/group_labels_spec.rb b/spec/requests/api/group_labels_spec.rb
index 715c1255cb3..f965a845bbe 100644
--- a/spec/requests/api/group_labels_spec.rb
+++ b/spec/requests/api/group_labels_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::GroupLabels do
+RSpec.describe API::GroupLabels do
let(:user) { create(:user) }
let(:group) { create(:group) }
let(:subgroup) { create(:group, parent: group) }
diff --git a/spec/requests/api/group_milestones_spec.rb b/spec/requests/api/group_milestones_spec.rb
index 3e9b6246434..2b361f2b503 100644
--- a/spec/requests/api/group_milestones_spec.rb
+++ b/spec/requests/api/group_milestones_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::GroupMilestones do
+RSpec.describe API::GroupMilestones do
let(:user) { create(:user) }
let(:group) { create(:group, :private) }
let(:project) { create(:project, namespace: group) }
diff --git a/spec/requests/api/group_packages_spec.rb b/spec/requests/api/group_packages_spec.rb
new file mode 100644
index 00000000000..7c7e8da3fb1
--- /dev/null
+++ b/spec/requests/api/group_packages_spec.rb
@@ -0,0 +1,147 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe API::GroupPackages do
+ let_it_be(:group) { create(:group, :public) }
+ let_it_be(:project) { create(:project, :public, namespace: group, name: 'project A') }
+ let_it_be(:user) { create(:user) }
+
+ subject { get api(url) }
+
+ describe 'GET /groups/:id/packages' do
+ let(:url) { "/groups/#{group.id}/packages" }
+ let(:package_schema) { 'public_api/v4/packages/group_packages' }
+
+ context 'without the need for a license' do
+ context 'with sorting' do
+ let_it_be(:package1) { create(:npm_package, project: project, version: '3.1.0', name: "@#{project.root_namespace.path}/foo1") }
+ let_it_be(:package2) { create(:nuget_package, project: project, version: '2.0.4') }
+ let(:package3) { create(:maven_package, project: project, version: '1.1.1', name: 'zzz') }
+
+ before do
+ travel_to(1.day.ago) do
+ package3
+ end
+ end
+
+ context 'without sorting params' do
+ let(:packages) { [package3, package1, package2] }
+
+ it 'sorts by created_at asc' do
+ subject
+
+ expect(json_response.map { |package| package['id'] }).to eq(packages.map(&:id))
+ end
+ end
+
+ it_behaves_like 'package sorting', 'name' do
+ let(:packages) { [package1, package2, package3] }
+ end
+
+ it_behaves_like 'package sorting', 'created_at' do
+ let(:packages) { [package3, package1, package2] }
+ end
+
+ it_behaves_like 'package sorting', 'version' do
+ let(:packages) { [package3, package2, package1] }
+ end
+
+ it_behaves_like 'package sorting', 'type' do
+ let(:packages) { [package3, package1, package2] }
+ end
+
+ it_behaves_like 'package sorting', 'project_path' do
+ let(:another_project) { create(:project, :public, namespace: group, name: 'project B') }
+ let!(:package4) { create(:npm_package, project: another_project, version: '3.1.0', name: "@#{project.root_namespace.path}/bar") }
+
+ let(:packages) { [package1, package2, package3, package4] }
+ end
+ end
+
+ context 'with private group' do
+ let!(:package1) { create(:package, project: project) }
+ let!(:package2) { create(:package, project: project) }
+
+ let(:group) { create(:group, :private) }
+ let(:subgroup) { create(:group, :private, parent: group) }
+ let(:project) { create(:project, :private, namespace: group) }
+ let(:subproject) { create(:project, :private, namespace: subgroup) }
+
+ context 'with unauthenticated user' do
+ it_behaves_like 'rejects packages access', :group, :no_type, :not_found
+ end
+
+ context 'with authenticated user' do
+ subject { get api(url, user) }
+
+ it_behaves_like 'returns packages', :group, :owner
+ it_behaves_like 'returns packages', :group, :maintainer
+ it_behaves_like 'returns packages', :group, :developer
+ it_behaves_like 'rejects packages access', :group, :reporter, :forbidden
+ it_behaves_like 'rejects packages access', :group, :guest, :forbidden
+
+ context 'with subgroup' do
+ let(:subgroup) { create(:group, :private, parent: group) }
+ let(:subproject) { create(:project, :private, namespace: subgroup) }
+ let!(:package3) { create(:npm_package, project: subproject) }
+
+ it_behaves_like 'returns packages with subgroups', :group, :owner
+ it_behaves_like 'returns packages with subgroups', :group, :maintainer
+ it_behaves_like 'returns packages with subgroups', :group, :developer
+ it_behaves_like 'rejects packages access', :group, :reporter, :forbidden
+ it_behaves_like 'rejects packages access', :group, :guest, :forbidden
+
+ context 'excluding subgroup' do
+ let(:url) { "/groups/#{group.id}/packages?exclude_subgroups=true" }
+
+ it_behaves_like 'returns packages', :group, :owner
+ it_behaves_like 'returns packages', :group, :maintainer
+ it_behaves_like 'returns packages', :group, :developer
+ it_behaves_like 'rejects packages access', :group, :reporter, :forbidden
+ it_behaves_like 'rejects packages access', :group, :guest, :forbidden
+ end
+ end
+ end
+ end
+
+ context 'with public group' do
+ let_it_be(:package1) { create(:package, project: project) }
+ let_it_be(:package2) { create(:package, project: project) }
+
+ context 'with unauthenticated user' do
+ it_behaves_like 'returns packages', :group, :no_type
+ end
+
+ context 'with authenticated user' do
+ subject { get api(url, user) }
+
+ it_behaves_like 'returns packages', :group, :owner
+ it_behaves_like 'returns packages', :group, :maintainer
+ it_behaves_like 'returns packages', :group, :developer
+ it_behaves_like 'returns packages', :group, :reporter
+ it_behaves_like 'returns packages', :group, :guest
+ end
+ end
+
+ context 'with pagination params' do
+ let_it_be(:package1) { create(:package, project: project) }
+ let_it_be(:package2) { create(:package, project: project) }
+ let_it_be(:package3) { create(:npm_package, project: project) }
+ let_it_be(:package4) { create(:npm_package, project: project) }
+
+ it_behaves_like 'returns paginated packages'
+ end
+
+ it_behaves_like 'filters on each package_type', is_project: false
+
+ context 'does not accept non supported package_type value' do
+ include_context 'package filter context'
+
+ let(:url) { group_filter_url(:type, 'foo') }
+
+ it_behaves_like 'returning response status', :bad_request
+ end
+ end
+ end
+end
diff --git a/spec/requests/api/group_variables_spec.rb b/spec/requests/api/group_variables_spec.rb
index a5b48985df5..c6d6ae1615b 100644
--- a/spec/requests/api/group_variables_spec.rb
+++ b/spec/requests/api/group_variables_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::GroupVariables do
+RSpec.describe API::GroupVariables do
let(:group) { create(:group) }
let(:user) { create(:user) }
diff --git a/spec/requests/api/groups_spec.rb b/spec/requests/api/groups_spec.rb
index 9a449499576..fac9f4dfe00 100644
--- a/spec/requests/api/groups_spec.rb
+++ b/spec/requests/api/groups_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Groups do
+RSpec.describe API::Groups do
include GroupAPIHelpers
include UploadHelpers
@@ -15,6 +15,7 @@ describe API::Groups do
let_it_be(:project1) { create(:project, namespace: group1) }
let_it_be(:project2) { create(:project, namespace: group2) }
let_it_be(:project3) { create(:project, namespace: group1, path: 'test', visibility_level: Gitlab::VisibilityLevel::PRIVATE) }
+ let_it_be(:archived_project) { create(:project, namespace: group1, archived: true) }
before do
group1.add_owner(user1)
@@ -184,11 +185,12 @@ describe API::Groups do
it "includes statistics if requested" do
attributes = {
- storage_size: 1158,
+ storage_size: 2392,
repository_size: 123,
wiki_size: 456,
lfs_objects_size: 234,
- build_artifacts_size: 345
+ build_artifacts_size: 345,
+ snippets_size: 1234
}.stringify_keys
exposed_attributes = attributes.dup
exposed_attributes['job_artifacts_size'] = exposed_attributes.delete('build_artifacts_size')
@@ -470,7 +472,7 @@ describe API::Groups do
expect(json_response['shared_with_groups'][0]['group_access_level']).to eq(link.group_access)
expect(json_response['shared_with_groups'][0]).to have_key('expires_at')
expect(json_response['projects']).to be_an Array
- expect(json_response['projects'].length).to eq(2)
+ expect(json_response['projects'].length).to eq(3)
expect(json_response['shared_projects']).to be_an Array
expect(json_response['shared_projects'].length).to eq(1)
expect(json_response['shared_projects'][0]['id']).to eq(project.id)
@@ -695,7 +697,7 @@ describe API::Groups do
expect(json_response['parent_id']).to eq(nil)
expect(json_response['created_at']).to be_present
expect(json_response['projects']).to be_an Array
- expect(json_response['projects'].length).to eq(2)
+ expect(json_response['projects'].length).to eq(3)
expect(json_response['shared_projects']).to be_an Array
expect(json_response['shared_projects'].length).to eq(0)
expect(json_response['default_branch_protection']).to eq(::Gitlab::Access::MAINTAINER_PROJECT_ACCESS)
@@ -821,20 +823,51 @@ describe API::Groups do
expect(response).to have_gitlab_http_status(:ok)
expect(response).to include_pagination_headers
- expect(json_response.length).to eq(2)
+ expect(json_response.length).to eq(3)
project_names = json_response.map { |proj| proj['name'] }
- expect(project_names).to match_array([project1.name, project3.name])
+ expect(project_names).to match_array([project1.name, project3.name, archived_project.name])
expect(json_response.first['visibility']).to be_present
end
+ context 'and using archived' do
+ it "returns the group's archived projects" do
+ get api("/groups/#{group1.id}/projects?archived=true", user1)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to include_pagination_headers
+ expect(json_response).to be_an Array
+ expect(json_response.length).to eq(Project.public_or_visible_to_user(user1).where(archived: true).size)
+ expect(json_response.map { |project| project['id'] }).to include(archived_project.id)
+ end
+
+ it "returns the group's non-archived projects" do
+ get api("/groups/#{group1.id}/projects?archived=false", user1)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to include_pagination_headers
+ expect(json_response).to be_an Array
+ expect(json_response.length).to eq(Project.public_or_visible_to_user(user1).where(archived: false).size)
+ expect(json_response.map { |project| project['id'] }).not_to include(archived_project.id)
+ end
+
+ it "returns all of the group's projects" do
+ get api("/groups/#{group1.id}/projects", user1)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to include_pagination_headers
+ expect(json_response).to be_an Array
+ expect(json_response.map { |project| project['id'] }).to contain_exactly(*Project.public_or_visible_to_user(user1).pluck(:id))
+ end
+ end
+
it "returns the group's projects with simple representation" do
get api("/groups/#{group1.id}/projects", user1), params: { simple: true }
expect(response).to have_gitlab_http_status(:ok)
expect(response).to include_pagination_headers
- expect(json_response.length).to eq(2)
+ expect(json_response.length).to eq(3)
project_names = json_response.map { |proj| proj['name'] }
- expect(project_names).to match_array([project1.name, project3.name])
+ expect(project_names).to match_array([project1.name, project3.name, archived_project.name])
expect(json_response.first['visibility']).not_to be_present
end
@@ -860,7 +893,7 @@ describe API::Groups do
expect(response).to have_gitlab_http_status(:ok)
expect(response).to include_pagination_headers
expect(json_response).to be_an(Array)
- expect(json_response.length).to eq(2)
+ expect(json_response.length).to eq(3)
end
it "returns projects including those in subgroups" do
@@ -873,7 +906,7 @@ describe API::Groups do
expect(response).to have_gitlab_http_status(:ok)
expect(response).to include_pagination_headers
expect(json_response).to be_an(Array)
- expect(json_response.length).to eq(4)
+ expect(json_response.length).to eq(5)
end
it "does not return a non existing group" do
@@ -958,7 +991,7 @@ describe API::Groups do
expect(response).to have_gitlab_http_status(:ok)
expect(response).to include_pagination_headers
project_names = json_response.map { |proj| proj['name'] }
- expect(project_names).to match_array([project1.name, project3.name])
+ expect(project_names).to match_array([project1.name, project3.name, archived_project.name])
end
it 'does not return a non existing group' do
diff --git a/spec/requests/api/helpers_spec.rb b/spec/requests/api/helpers_spec.rb
index d65c89f48ea..12cd5ace84e 100644
--- a/spec/requests/api/helpers_spec.rb
+++ b/spec/requests/api/helpers_spec.rb
@@ -4,7 +4,7 @@ require 'spec_helper'
require 'raven/transports/dummy'
require_relative '../../../config/initializers/sentry'
-describe API::Helpers do
+RSpec.describe API::Helpers do
include API::APIGuard::HelperMethods
include described_class
include TermsHelper
diff --git a/spec/requests/api/import_bitbucket_server_spec.rb b/spec/requests/api/import_bitbucket_server_spec.rb
new file mode 100644
index 00000000000..5828dab3080
--- /dev/null
+++ b/spec/requests/api/import_bitbucket_server_spec.rb
@@ -0,0 +1,218 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe API::ImportBitbucketServer do
+ let(:base_uri) { "https://test:7990" }
+ let(:user) { create(:user) }
+ let(:token) { "asdasd12345" }
+ let(:secret) { "sekrettt" }
+ let(:project_key) { 'TES' }
+ let(:repo_slug) { 'vim' }
+ let(:repo) { { name: 'vim' } }
+
+ describe "POST /import/bitbucket_server" do
+ context 'with no optional parameters' do
+ let_it_be(:project) { create(:project) }
+ let(:client) { double(BitbucketServer::Client) }
+
+ before do
+ Grape::Endpoint.before_each do |endpoint|
+ allow(endpoint).to receive(:client).and_return(client.as_null_object)
+ allow(client).to receive(:repo).with(project_key, repo_slug).and_return(double(name: repo_slug))
+ end
+ end
+
+ after do
+ Grape::Endpoint.before_each nil
+ end
+
+ it 'returns 201 response when the project is imported successfully' do
+ allow(Gitlab::BitbucketServerImport::ProjectCreator)
+ .to receive(:new).with(project_key, repo_slug, anything, repo_slug, user.namespace, user, anything)
+ .and_return(double(execute: project))
+
+ post api("/import/bitbucket_server", user), params: {
+ bitbucket_server_url: base_uri,
+ bitbucket_server_username: user,
+ personal_access_token: token,
+ bitbucket_server_project: project_key,
+ bitbucket_server_repo: repo_slug
+ }
+
+ expect(response).to have_gitlab_http_status(:created)
+ expect(json_response).to be_a Hash
+ expect(json_response['name']).to eq(project.name)
+ end
+ end
+
+ context 'with a new project name' do
+ let_it_be(:project) { create(:project, name: 'new-name') }
+ let(:client) { instance_double(BitbucketServer::Client) }
+
+ before do
+ Grape::Endpoint.before_each do |endpoint|
+ allow(endpoint).to receive(:client).and_return(client)
+ allow(client).to receive(:repo).with(project_key, repo_slug).and_return(double(name: repo_slug))
+ end
+ end
+
+ after do
+ Grape::Endpoint.before_each nil
+ end
+
+ it 'returns 201 response when the project is imported successfully with a new project name' do
+ allow(Gitlab::BitbucketServerImport::ProjectCreator)
+ .to receive(:new).with(project_key, repo_slug, anything, project.name, user.namespace, user, anything)
+ .and_return(double(execute: project))
+
+ post api("/import/bitbucket_server", user), params: {
+ bitbucket_server_url: base_uri,
+ bitbucket_server_username: user,
+ personal_access_token: token,
+ bitbucket_server_project: project_key,
+ bitbucket_server_repo: repo_slug,
+ new_name: 'new-name'
+ }
+
+ expect(response).to have_gitlab_http_status(:created)
+ expect(json_response).to be_a Hash
+ expect(json_response['name']).to eq('new-name')
+ end
+ end
+
+ context 'with an invalid URL' do
+ let_it_be(:project) { create(:project, name: 'new-name') }
+ let(:client) { instance_double(BitbucketServer::Client) }
+
+ before do
+ Grape::Endpoint.before_each do |endpoint|
+ allow(endpoint).to receive(:client).and_return(client)
+ allow(client).to receive(:repo).with(project_key, repo_slug).and_return(double(name: repo_slug))
+ end
+ end
+
+ after do
+ Grape::Endpoint.before_each nil
+ end
+
+ it 'returns 400 response due to a blcoked URL' do
+ allow(Gitlab::BitbucketServerImport::ProjectCreator)
+ .to receive(:new).with(project_key, repo_slug, anything, project.name, user.namespace, user, anything)
+ .and_return(double(execute: project))
+
+ allow(Gitlab::UrlBlocker)
+ .to receive(:blocked_url?)
+ .and_return(true)
+ post api("/import/bitbucket_server", user), params: {
+ bitbucket_server_url: base_uri,
+ bitbucket_server_username: user,
+ personal_access_token: token,
+ bitbucket_server_project: project_key,
+ bitbucket_server_repo: repo_slug,
+ new_name: 'new-name'
+ }
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ end
+ end
+
+ context 'with a new namespace' do
+ let(:bitbucket_client) { instance_double(BitbucketServer::Client) }
+
+ before do
+ Grape::Endpoint.before_each do |endpoint|
+ allow(endpoint).to receive(:client).and_return(bitbucket_client)
+ repo = double(name: repo_slug, full_path: "/other-namespace/#{repo_slug}")
+ allow(bitbucket_client).to receive(:repo).with(project_key, repo_slug).and_return(repo)
+ end
+ end
+
+ after do
+ Grape::Endpoint.before_each nil
+ end
+
+ it 'returns 201 response when the project is imported successfully to a new namespace' do
+ allow(Gitlab::BitbucketServerImport::ProjectCreator)
+ .to receive(:new).with(project_key, repo_slug, anything, repo_slug, an_instance_of(Group), user, anything)
+ .and_return(double(execute: create(:project, name: repo_slug)))
+
+ post api("/import/bitbucket_server", user), params: {
+ bitbucket_server_url: base_uri,
+ bitbucket_server_username: user,
+ personal_access_token: token,
+ bitbucket_server_project: project_key,
+ bitbucket_server_repo: repo_slug,
+ new_namespace: 'new-namespace'
+ }
+
+ expect(response).to have_gitlab_http_status(:created)
+ expect(json_response).to be_a Hash
+ expect(json_response['full_path']).not_to eq("/#{user.namespace}/#{repo_slug}")
+ end
+ end
+
+ context 'with a private inaccessible namespace' do
+ let(:bitbucket_client) { instance_double(BitbucketServer::Client) }
+ let(:project) { create(:project, import_type: 'bitbucket', creator_id: user.id, import_source: 'asd/vim', namespace: 'private-group/vim') }
+
+ before do
+ Grape::Endpoint.before_each do |endpoint|
+ allow(endpoint).to receive(:client).and_return(bitbucket_client)
+ repo = double(name: repo_slug, full_path: "/private-group/#{repo_slug}")
+ allow(bitbucket_client).to receive(:repo).with(project_key, repo_slug).and_return(repo)
+ end
+ end
+
+ after do
+ Grape::Endpoint.before_each nil
+ end
+
+ it 'returns 401 response when user can not create projects in the chosen namespace' do
+ allow(Gitlab::BitbucketServerImport::ProjectCreator)
+ .to receive(:new).with(project_key, repo_slug, anything, repo_slug, an_instance_of(Group), user, anything)
+ .and_return(double(execute: build(:project)))
+
+ other_namespace = create(:group, :private, name: 'private-group')
+
+ post api("/import/bitbucket_server", user), params: {
+ bitbucket_server_url: base_uri,
+ bitbucket_server_username: user,
+ personal_access_token: token,
+ bitbucket_server_project: project_key,
+ bitbucket_server_repo: repo_slug,
+ new_namespace: other_namespace.name
+ }
+ expect(response).to have_gitlab_http_status(:unauthorized)
+ end
+ end
+
+ context 'with an inaccessible bitbucket server instance' do
+ let(:bitbucket_client) { instance_double(BitbucketServer::Client) }
+ let(:project) { create(:project, import_type: 'bitbucket', creator_id: user.id, import_source: 'asd/vim', namespace: 'private-group/vim') }
+
+ before do
+ Grape::Endpoint.before_each do |endpoint|
+ allow(endpoint).to receive(:client).and_return(bitbucket_client)
+ allow(bitbucket_client).to receive(:repo).with(project_key, repo_slug).and_raise(::BitbucketServer::Connection::ConnectionError)
+ end
+ end
+
+ after do
+ Grape::Endpoint.before_each nil
+ end
+
+ it 'raises a connection error' do
+ post api("/import/bitbucket_server", user), params: {
+ bitbucket_server_url: base_uri,
+ bitbucket_server_username: user,
+ personal_access_token: token,
+ bitbucket_server_project: project_key,
+ bitbucket_server_repo: repo_slug,
+ new_namespace: 'new-namespace'
+ }
+ expect(response).to have_gitlab_http_status(:bad_request)
+ end
+ end
+ end
+end
diff --git a/spec/requests/api/import_github_spec.rb b/spec/requests/api/import_github_spec.rb
index f33436df40e..f026314f7a8 100644
--- a/spec/requests/api/import_github_spec.rb
+++ b/spec/requests/api/import_github_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::ImportGithub do
+RSpec.describe API::ImportGithub do
let(:token) { "asdasd12345" }
let(:provider) { :github }
let(:access_params) { { github_access_token: token } }
@@ -26,6 +26,10 @@ describe API::ImportGithub do
end
end
+ after do
+ Grape::Endpoint.before_each nil
+ end
+
it 'rejects requests when Github Importer is disabled' do
stub_application_setting(import_sources: nil)
diff --git a/spec/requests/api/internal/base_spec.rb b/spec/requests/api/internal/base_spec.rb
index aa5e2367a2b..7d219954e9d 100644
--- a/spec/requests/api/internal/base_spec.rb
+++ b/spec/requests/api/internal/base_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Internal::Base do
+RSpec.describe API::Internal::Base do
let_it_be(:user, reload: true) { create(:user) }
let_it_be(:project, reload: true) { create(:project, :repository, :wiki_repo) }
let_it_be(:personal_snippet) { create(:personal_snippet, :repository, author: user) }
@@ -467,21 +467,6 @@ describe API::Internal::Base do
expect(json_response["git_config_options"]).to include("uploadpack.allowFilter=true")
expect(json_response["git_config_options"]).to include("uploadpack.allowAnySHA1InWant=true")
end
-
- context 'when gitaly_upload_pack_filter feature flag is disabled' do
- before do
- stub_feature_flags(gitaly_upload_pack_filter: false)
- end
-
- it 'returns only maxInputSize and not partial clone git config' do
- push(key, project)
-
- expect(json_response["git_config_options"]).to be_present
- expect(json_response["git_config_options"]).to include("receive.maxInputSize=1048576")
- expect(json_response["git_config_options"]).not_to include("uploadpack.allowFilter=true")
- expect(json_response["git_config_options"]).not_to include("uploadpack.allowAnySHA1InWant=true")
- end
- end
end
context 'when receive_max_input_size is empty' do
@@ -496,18 +481,6 @@ describe API::Internal::Base do
expect(json_response["git_config_options"]).to include("uploadpack.allowFilter=true")
expect(json_response["git_config_options"]).to include("uploadpack.allowAnySHA1InWant=true")
end
-
- context 'when gitaly_upload_pack_filter feature flag is disabled' do
- before do
- stub_feature_flags(gitaly_upload_pack_filter: false)
- end
-
- it 'returns an empty git config' do
- push(key, project)
-
- expect(json_response["git_config_options"]).to be_empty
- end
- end
end
end
diff --git a/spec/requests/api/internal/pages_spec.rb b/spec/requests/api/internal/pages_spec.rb
index fecf15c29c2..48fc95b6574 100644
--- a/spec/requests/api/internal/pages_spec.rb
+++ b/spec/requests/api/internal/pages_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Internal::Pages do
+RSpec.describe API::Internal::Pages do
let(:auth_headers) do
jwt_token = JWT.encode({ 'iss' => 'gitlab-pages' }, Gitlab::Pages.secret, 'HS256')
{ Gitlab::Pages::INTERNAL_API_REQUEST_HEADER => jwt_token }
diff --git a/spec/requests/api/issues/get_group_issues_spec.rb b/spec/requests/api/issues/get_group_issues_spec.rb
index 5c925d2a32e..b53fac3679d 100644
--- a/spec/requests/api/issues/get_group_issues_spec.rb
+++ b/spec/requests/api/issues/get_group_issues_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Issues do
+RSpec.describe API::Issues do
let_it_be(:user2) { create(:user) }
let_it_be(:admin) { create(:user, :admin) }
let_it_be(:non_member) { create(:user) }
diff --git a/spec/requests/api/issues/get_project_issues_spec.rb b/spec/requests/api/issues/get_project_issues_spec.rb
index 4a728c81215..7ff07bf580d 100644
--- a/spec/requests/api/issues/get_project_issues_spec.rb
+++ b/spec/requests/api/issues/get_project_issues_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Issues do
+RSpec.describe API::Issues do
let_it_be(:user) { create(:user) }
let_it_be(:project, reload: true) { create(:project, :public, :repository, creator_id: user.id, namespace: user.namespace) }
let_it_be(:private_mrs_project) do
diff --git a/spec/requests/api/issues/issues_spec.rb b/spec/requests/api/issues/issues_spec.rb
index 315396c89c3..519bea22501 100644
--- a/spec/requests/api/issues/issues_spec.rb
+++ b/spec/requests/api/issues/issues_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Issues do
+RSpec.describe API::Issues do
let_it_be(:user) { create(:user) }
let_it_be(:project, reload: true) { create(:project, :public, :repository, creator_id: user.id, namespace: user.namespace) }
let_it_be(:private_mrs_project) do
@@ -886,4 +886,53 @@ describe API::Issues do
include_examples 'time tracking endpoints', 'issue'
end
+
+ describe 'PUT /projects/:id/issues/:issue_iid/reorder' do
+ let_it_be(:project) { create(:project) }
+ let_it_be(:issue1) { create(:issue, project: project, relative_position: 10) }
+ let_it_be(:issue2) { create(:issue, project: project, relative_position: 20) }
+ let_it_be(:issue3) { create(:issue, project: project, relative_position: 30) }
+
+ context 'when user has access' do
+ before do
+ project.add_developer(user)
+ end
+
+ context 'with valid params' do
+ it 'reorders issues and returns a successful 200 response' do
+ put api("/projects/#{project.id}/issues/#{issue1.iid}/reorder", user), params: { move_after_id: issue2.id, move_before_id: issue3.id }
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(issue1.reload.relative_position)
+ .to be_between(issue2.reload.relative_position, issue3.reload.relative_position)
+ end
+ end
+
+ context 'with invalid params' do
+ it 'returns a unprocessable entity 422 response for invalid move ids' do
+ put api("/projects/#{project.id}/issues/#{issue1.iid}/reorder", user), params: { move_after_id: issue2.id, move_before_id: non_existing_record_id }
+
+ expect(response).to have_gitlab_http_status(:unprocessable_entity)
+ end
+
+ it 'returns a not found 404 response for invalid issue id' do
+ put api("/projects/#{project.id}/issues/#{non_existing_record_iid}/reorder", user), params: { move_after_id: issue2.id, move_before_id: issue3.id }
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+ end
+
+ context 'with unauthorized user' do
+ before do
+ project.add_guest(user)
+ end
+
+ it 'responds with 403 forbidden' do
+ put api("/projects/#{project.id}/issues/#{issue1.iid}/reorder", user), params: { move_after_id: issue2.id, move_before_id: issue3.id }
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+ end
+ end
end
diff --git a/spec/requests/api/issues/post_projects_issues_spec.rb b/spec/requests/api/issues/post_projects_issues_spec.rb
index 2e1e5d3204e..e2f1bb2cd1a 100644
--- a/spec/requests/api/issues/post_projects_issues_spec.rb
+++ b/spec/requests/api/issues/post_projects_issues_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Issues do
+RSpec.describe API::Issues do
let_it_be(:user) { create(:user) }
let_it_be(:project, reload: true) do
create(:project, :public, creator_id: user.id, namespace: user.namespace)
diff --git a/spec/requests/api/issues/put_projects_issues_spec.rb b/spec/requests/api/issues/put_projects_issues_spec.rb
index 62a4d3b48b2..dac721cbea0 100644
--- a/spec/requests/api/issues/put_projects_issues_spec.rb
+++ b/spec/requests/api/issues/put_projects_issues_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Issues do
+RSpec.describe API::Issues do
let_it_be(:user) { create(:user) }
let_it_be(:owner) { create(:owner) }
let(:user2) { create(:user) }
diff --git a/spec/requests/api/jobs_spec.rb b/spec/requests/api/jobs_spec.rb
index 18b5c00d64f..53c57931d36 100644
--- a/spec/requests/api/jobs_spec.rb
+++ b/spec/requests/api/jobs_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Jobs do
+RSpec.describe API::Jobs do
include HttpIOHelpers
shared_examples 'a job with artifacts and trace' do |result_is_array: true|
@@ -36,9 +36,9 @@ describe API::Jobs do
end
let_it_be(:pipeline, reload: true) do
- create(:ci_empty_pipeline, project: project,
- sha: project.commit.id,
- ref: project.default_branch)
+ create(:ci_pipeline, project: project,
+ sha: project.commit.id,
+ ref: project.default_branch)
end
let!(:job) do
diff --git a/spec/requests/api/keys_spec.rb b/spec/requests/api/keys_spec.rb
index 089ee22982c..49b8f4a8520 100644
--- a/spec/requests/api/keys_spec.rb
+++ b/spec/requests/api/keys_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Keys do
+RSpec.describe API::Keys do
let(:user) { create(:user) }
let(:admin) { create(:admin) }
let(:key) { create(:key, user: user, expires_at: 1.day.from_now) }
diff --git a/spec/requests/api/labels_spec.rb b/spec/requests/api/labels_spec.rb
index 697f22e5f29..fc674fca9b2 100644
--- a/spec/requests/api/labels_spec.rb
+++ b/spec/requests/api/labels_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Labels do
+RSpec.describe API::Labels do
def put_labels_api(route_type, user, spec_params, request_params = {})
if route_type == :deprecated
put api("/projects/#{project.id}/labels", user),
diff --git a/spec/requests/api/lint_spec.rb b/spec/requests/api/lint_spec.rb
index 71c2619d898..4c60c8bd2a3 100644
--- a/spec/requests/api/lint_spec.rb
+++ b/spec/requests/api/lint_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Lint do
+RSpec.describe API::Lint do
describe 'POST /ci/lint' do
context 'with valid .gitlab-ci.yaml content' do
let(:yaml_content) do
diff --git a/spec/requests/api/markdown_spec.rb b/spec/requests/api/markdown_spec.rb
index 53e43430b1f..35d91963ac9 100644
--- a/spec/requests/api/markdown_spec.rb
+++ b/spec/requests/api/markdown_spec.rb
@@ -2,7 +2,7 @@
require "spec_helper"
-describe API::Markdown do
+RSpec.describe API::Markdown do
describe "POST /markdown" do
let(:user) {} # No-op. It gets overwritten in the contexts below.
diff --git a/spec/requests/api/maven_packages_spec.rb b/spec/requests/api/maven_packages_spec.rb
new file mode 100644
index 00000000000..189d6a4c1a4
--- /dev/null
+++ b/spec/requests/api/maven_packages_spec.rb
@@ -0,0 +1,569 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+RSpec.describe API::MavenPackages do
+ include WorkhorseHelpers
+
+ let_it_be(:group) { create(:group) }
+ let_it_be(:user) { create(:user) }
+ let_it_be(:project, reload: true) { create(:project, :public, namespace: group) }
+ let_it_be(:package, reload: true) { create(:maven_package, project: project, name: project.full_path) }
+ let_it_be(:maven_metadatum, reload: true) { package.maven_metadatum }
+ let_it_be(:package_file) { package.package_files.with_file_name_like('%.xml').first }
+ let_it_be(:jar_file) { package.package_files.with_file_name_like('%.jar').first }
+ let_it_be(:personal_access_token) { create(:personal_access_token, user: user) }
+ let_it_be(:job) { create(:ci_build, user: user) }
+ let_it_be(:deploy_token) { create(:deploy_token, read_package_registry: true, write_package_registry: true) }
+ let_it_be(:project_deploy_token) { create(:project_deploy_token, deploy_token: deploy_token, project: project) }
+
+ let(:workhorse_token) { JWT.encode({ 'iss' => 'gitlab-workhorse' }, Gitlab::Workhorse.secret, 'HS256') }
+ let(:headers) { { 'GitLab-Workhorse' => '1.0', Gitlab::Workhorse::INTERNAL_API_REQUEST_HEADER => workhorse_token } }
+ let(:headers_with_token) { headers.merge('Private-Token' => personal_access_token.token) }
+
+ let(:headers_with_deploy_token) do
+ headers.merge(
+ Gitlab::Auth::AuthFinders::DEPLOY_TOKEN_HEADER => deploy_token.token
+ )
+ end
+
+ let(:version) { '1.0-SNAPSHOT' }
+
+ before do
+ project.add_developer(user)
+ end
+
+ shared_examples 'tracking the file download event' do
+ context 'with jar file' do
+ let_it_be(:package_file) { jar_file }
+
+ it_behaves_like 'a gitlab tracking event', described_class.name, 'pull_package'
+ end
+ end
+
+ shared_examples 'processing HEAD requests' do
+ subject { head api(url) }
+
+ before do
+ allow_any_instance_of(::Packages::PackageFileUploader).to receive(:fog_credentials).and_return(object_storage_credentials)
+ stub_package_file_object_storage(enabled: object_storage_enabled)
+ end
+
+ context 'with object storage enabled' do
+ let(:object_storage_enabled) { true }
+
+ before do
+ allow_any_instance_of(::Packages::PackageFileUploader).to receive(:file_storage?).and_return(false)
+ end
+
+ context 'non AWS provider' do
+ let(:object_storage_credentials) { { provider: 'Google' } }
+
+ it 'does not generated a signed url for head' do
+ expect_any_instance_of(Fog::AWS::Storage::Files).not_to receive(:head_url)
+
+ subject
+ end
+ end
+
+ context 'with AWS provider' do
+ let(:object_storage_credentials) { { provider: 'AWS', aws_access_key_id: 'test', aws_secret_access_key: 'test' } }
+
+ it 'generates a signed url for head' do
+ expect_any_instance_of(Fog::AWS::Storage::Files).to receive(:head_url).and_call_original
+
+ subject
+ end
+ end
+ end
+
+ context 'with object storage disabled' do
+ let(:object_storage_enabled) { false }
+ let(:object_storage_credentials) { {} }
+
+ it 'does not generate a signed url for head' do
+ expect_any_instance_of(Fog::AWS::Storage::Files).not_to receive(:head_url)
+
+ subject
+ end
+ end
+ end
+
+ shared_examples 'downloads with a deploy token' do
+ it 'allows download with deploy token' do
+ download_file(
+ package_file.file_name,
+ {},
+ Gitlab::Auth::AuthFinders::DEPLOY_TOKEN_HEADER => deploy_token.token
+ )
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response.media_type).to eq('application/octet-stream')
+ end
+ end
+
+ shared_examples 'downloads with a job token' do
+ it 'allows download with job token' do
+ download_file(package_file.file_name, job_token: job.token)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response.media_type).to eq('application/octet-stream')
+ end
+ end
+
+ describe 'GET /api/v4/packages/maven/*path/:file_name' do
+ context 'a public project' do
+ subject { download_file(package_file.file_name) }
+
+ it_behaves_like 'tracking the file download event'
+
+ it 'returns the file' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response.media_type).to eq('application/octet-stream')
+ end
+
+ it 'returns sha1 of the file' do
+ download_file(package_file.file_name + '.sha1')
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response.media_type).to eq('text/plain')
+ expect(response.body).to eq(package_file.file_sha1)
+ end
+ end
+
+ context 'internal project' do
+ before do
+ project.team.truncate
+ project.update!(visibility_level: Gitlab::VisibilityLevel::INTERNAL)
+ end
+
+ subject { download_file_with_token(package_file.file_name) }
+
+ it_behaves_like 'tracking the file download event'
+
+ it 'returns the file' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response.media_type).to eq('application/octet-stream')
+ end
+
+ it 'denies download when no private token' do
+ download_file(package_file.file_name)
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+
+ it_behaves_like 'downloads with a job token'
+
+ it_behaves_like 'downloads with a deploy token'
+ end
+
+ context 'private project' do
+ subject { download_file_with_token(package_file.file_name) }
+
+ before do
+ project.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE)
+ end
+
+ it_behaves_like 'tracking the file download event'
+
+ it 'returns the file' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response.media_type).to eq('application/octet-stream')
+ end
+
+ it 'denies download when not enough permissions' do
+ project.add_guest(user)
+
+ subject
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+
+ it 'denies download when no private token' do
+ download_file(package_file.file_name)
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+
+ it_behaves_like 'downloads with a job token'
+
+ it_behaves_like 'downloads with a deploy token'
+ end
+
+ context 'project name is different from a package name' do
+ before do
+ maven_metadatum.update!(path: "wrong_name/#{package.version}")
+ end
+
+ it 'rejects request' do
+ download_file(package_file.file_name)
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+ end
+
+ def download_file(file_name, params = {}, request_headers = headers)
+ get api("/packages/maven/#{maven_metadatum.path}/#{file_name}"), params: params, headers: request_headers
+ end
+
+ def download_file_with_token(file_name, params = {}, request_headers = headers_with_token)
+ download_file(file_name, params, request_headers)
+ end
+ end
+
+ describe 'HEAD /api/v4/packages/maven/*path/:file_name' do
+ let(:url) { "/packages/maven/#{package.maven_metadatum.path}/#{package_file.file_name}" }
+
+ it_behaves_like 'processing HEAD requests'
+ end
+
+ describe 'GET /api/v4/groups/:id/-/packages/maven/*path/:file_name' do
+ before do
+ project.team.truncate
+ group.add_developer(user)
+ end
+
+ context 'a public project' do
+ subject { download_file(package_file.file_name) }
+
+ it_behaves_like 'tracking the file download event'
+
+ it 'returns the file' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response.media_type).to eq('application/octet-stream')
+ end
+
+ it 'returns sha1 of the file' do
+ download_file(package_file.file_name + '.sha1')
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response.media_type).to eq('text/plain')
+ expect(response.body).to eq(package_file.file_sha1)
+ end
+ end
+
+ context 'internal project' do
+ before do
+ group.group_member(user).destroy
+ project.update!(visibility_level: Gitlab::VisibilityLevel::INTERNAL)
+ end
+
+ subject { download_file_with_token(package_file.file_name) }
+
+ it_behaves_like 'tracking the file download event'
+
+ it 'returns the file' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response.media_type).to eq('application/octet-stream')
+ end
+
+ it 'denies download when no private token' do
+ download_file(package_file.file_name)
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+
+ it_behaves_like 'downloads with a job token'
+
+ it_behaves_like 'downloads with a deploy token'
+ end
+
+ context 'private project' do
+ before do
+ project.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE)
+ end
+
+ subject { download_file_with_token(package_file.file_name) }
+
+ it_behaves_like 'tracking the file download event'
+
+ it 'returns the file' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response.media_type).to eq('application/octet-stream')
+ end
+
+ it 'denies download when not enough permissions' do
+ group.add_guest(user)
+
+ subject
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+
+ it 'denies download when no private token' do
+ download_file(package_file.file_name)
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+
+ it_behaves_like 'downloads with a job token'
+
+ it_behaves_like 'downloads with a deploy token'
+ end
+
+ def download_file(file_name, params = {}, request_headers = headers)
+ get api("/groups/#{group.id}/-/packages/maven/#{maven_metadatum.path}/#{file_name}"), params: params, headers: request_headers
+ end
+
+ def download_file_with_token(file_name, params = {}, request_headers = headers_with_token)
+ download_file(file_name, params, request_headers)
+ end
+ end
+
+ describe 'HEAD /api/v4/groups/:id/-/packages/maven/*path/:file_name' do
+ let(:url) { "/groups/#{group.id}/-/packages/maven/#{package.maven_metadatum.path}/#{package_file.file_name}" }
+
+ it_behaves_like 'processing HEAD requests'
+ end
+
+ describe 'GET /api/v4/projects/:id/packages/maven/*path/:file_name' do
+ context 'a public project' do
+ subject { download_file(package_file.file_name) }
+
+ it_behaves_like 'tracking the file download event'
+
+ it 'returns the file' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response.media_type).to eq('application/octet-stream')
+ end
+
+ it 'returns sha1 of the file' do
+ download_file(package_file.file_name + '.sha1')
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response.media_type).to eq('text/plain')
+ expect(response.body).to eq(package_file.file_sha1)
+ end
+ end
+
+ context 'private project' do
+ before do
+ project.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE)
+ end
+
+ subject { download_file_with_token(package_file.file_name) }
+
+ it_behaves_like 'tracking the file download event'
+
+ it 'returns the file' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response.media_type).to eq('application/octet-stream')
+ end
+
+ it 'denies download when not enough permissions' do
+ project.add_guest(user)
+
+ subject
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+
+ it 'denies download when no private token' do
+ download_file(package_file.file_name)
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+
+ it_behaves_like 'downloads with a job token'
+
+ it_behaves_like 'downloads with a deploy token'
+ end
+
+ def download_file(file_name, params = {}, request_headers = headers)
+ get api("/projects/#{project.id}/packages/maven/" \
+ "#{maven_metadatum.path}/#{file_name}"), params: params, headers: request_headers
+ end
+
+ def download_file_with_token(file_name, params = {}, request_headers = headers_with_token)
+ download_file(file_name, params, request_headers)
+ end
+ end
+
+ describe 'HEAD /api/v4/projects/:id/packages/maven/*path/:file_name' do
+ let(:url) { "/projects/#{project.id}/packages/maven/#{package.maven_metadatum.path}/#{package_file.file_name}" }
+
+ it_behaves_like 'processing HEAD requests'
+ end
+
+ describe 'PUT /api/v4/projects/:id/packages/maven/*path/:file_name/authorize' do
+ it 'rejects a malicious request' do
+ put api("/projects/#{project.id}/packages/maven/com/example/my-app/#{version}/%2e%2e%2F.ssh%2Fauthorized_keys/authorize"), params: {}, headers: headers_with_token
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ end
+
+ it 'authorizes posting package with a valid token' do
+ authorize_upload_with_token
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response.media_type).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE)
+ expect(json_response['TempPath']).not_to be_nil
+ end
+
+ it 'rejects request without a valid token' do
+ headers_with_token['Private-Token'] = 'foo'
+
+ authorize_upload_with_token
+
+ expect(response).to have_gitlab_http_status(:unauthorized)
+ end
+
+ it 'rejects request without a valid permission' do
+ project.add_guest(user)
+
+ authorize_upload_with_token
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+
+ it 'rejects requests that did not go through gitlab-workhorse' do
+ headers.delete(Gitlab::Workhorse::INTERNAL_API_REQUEST_HEADER)
+
+ authorize_upload_with_token
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+
+ it 'authorizes upload with job token' do
+ authorize_upload(job_token: job.token)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+
+ it 'authorizes upload with deploy token' do
+ authorize_upload({}, headers_with_deploy_token)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+
+ def authorize_upload(params = {}, request_headers = headers)
+ put api("/projects/#{project.id}/packages/maven/com/example/my-app/#{version}/maven-metadata.xml/authorize"), params: params, headers: request_headers
+ end
+
+ def authorize_upload_with_token(params = {}, request_headers = headers_with_token)
+ authorize_upload(params, request_headers)
+ end
+ end
+
+ describe 'PUT /api/v4/projects/:id/packages/maven/*path/:file_name' do
+ let(:workhorse_token) { JWT.encode({ 'iss' => 'gitlab-workhorse' }, Gitlab::Workhorse.secret, 'HS256') }
+ let(:workhorse_header) { { 'GitLab-Workhorse' => '1.0', Gitlab::Workhorse::INTERNAL_API_REQUEST_HEADER => workhorse_token } }
+ let(:send_rewritten_field) { true }
+ let(:file_upload) { fixture_file_upload('spec/fixtures/packages/maven/my-app-1.0-20180724.124855-1.jar') }
+
+ before do
+ # by configuring this path we allow to pass temp file from any path
+ allow(Packages::PackageFileUploader).to receive(:workhorse_upload_path).and_return('/')
+ end
+
+ it 'rejects requests without a file from workhorse' do
+ upload_file_with_token
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ end
+
+ it 'rejects request without a token' do
+ upload_file
+
+ expect(response).to have_gitlab_http_status(:unauthorized)
+ end
+
+ context 'without workhorse rewritten field' do
+ let(:send_rewritten_field) { false }
+
+ it 'rejects the request' do
+ upload_file_with_token
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ end
+ end
+
+ context 'when params from workhorse are correct' do
+ let(:params) { { file: file_upload } }
+
+ it 'rejects a malicious request' do
+ put api("/projects/#{project.id}/packages/maven/com/example/my-app/#{version}/%2e%2e%2f.ssh%2fauthorized_keys"), params: params, headers: headers_with_token
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ end
+
+ context 'without workhorse header' do
+ let(:workhorse_header) { {} }
+
+ subject { upload_file_with_token(params) }
+
+ it_behaves_like 'package workhorse uploads'
+ end
+
+ context 'event tracking' do
+ subject { upload_file_with_token(params) }
+
+ it_behaves_like 'a gitlab tracking event', described_class.name, 'push_package'
+ end
+
+ it 'creates package and stores package file' do
+ expect { upload_file_with_token(params) }.to change { project.packages.count }.by(1)
+ .and change { Packages::Maven::Metadatum.count }.by(1)
+ .and change { Packages::PackageFile.count }.by(1)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(jar_file.file_name).to eq(file_upload.original_filename)
+ end
+
+ it 'allows upload with job token' do
+ upload_file(params.merge(job_token: job.token))
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(project.reload.packages.last.build_info.pipeline).to eq job.pipeline
+ end
+
+ it 'allows upload with deploy token' do
+ upload_file(params, headers_with_deploy_token)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+
+ context 'version is not correct' do
+ let(:version) { '$%123' }
+
+ it 'rejects request' do
+ expect { upload_file_with_token(params) }.not_to change { project.packages.count }
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ expect(json_response['message']).to include('Validation failed')
+ end
+ end
+ end
+
+ def upload_file(params = {}, request_headers = headers)
+ url = "/projects/#{project.id}/packages/maven/com/example/my-app/#{version}/my-app-1.0-20180724.124855-1.jar"
+ workhorse_finalize(
+ api(url),
+ method: :put,
+ file_key: :file,
+ params: params,
+ headers: request_headers,
+ send_rewritten_field: send_rewritten_field
+ )
+ end
+
+ def upload_file_with_token(params = {}, request_headers = headers_with_token)
+ upload_file(params, request_headers)
+ end
+ end
+end
diff --git a/spec/requests/api/members_spec.rb b/spec/requests/api/members_spec.rb
index 0ecef26c27a..23889912d7a 100644
--- a/spec/requests/api/members_spec.rb
+++ b/spec/requests/api/members_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Members do
+RSpec.describe API::Members do
let(:maintainer) { create(:user, username: 'maintainer_user') }
let(:developer) { create(:user) }
let(:access_requester) { create(:user) }
@@ -321,6 +321,26 @@ describe API::Members do
expect(response).to have_gitlab_http_status(:bad_request)
end
end
+
+ context 'adding project bot' do
+ let_it_be(:project_bot) { create(:user, :project_bot) }
+
+ before do
+ unrelated_project = create(:project)
+ unrelated_project.add_maintainer(project_bot)
+ end
+
+ it 'returns 400' do
+ expect do
+ post api("/#{source_type.pluralize}/#{source.id}/members", maintainer),
+ params: { user_id: project_bot.id, access_level: Member::DEVELOPER }
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ expect(json_response['message']['user_id']).to(
+ include('project bots cannot be added to other groups / projects'))
+ end.not_to change { project.members.count }
+ end
+ end
end
shared_examples 'PUT /:source_type/:id/members/:user_id' do |source_type|
@@ -461,8 +481,34 @@ describe API::Members do
end
end
- it_behaves_like 'POST /:source_type/:id/members', 'project' do
- let(:source) { project }
+ describe 'POST /projects/:id/members' do
+ it_behaves_like 'POST /:source_type/:id/members', 'project' do
+ let(:source) { project }
+ end
+
+ context 'adding owner to project' do
+ it 'returns 403' do
+ expect do
+ post api("/projects/#{project.id}/members", maintainer),
+ params: { user_id: stranger.id, access_level: Member::OWNER }
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ end.not_to change { project.members.count }
+ end
+ end
+
+ context 'remove bot from project' do
+ it 'returns a 403 forbidden' do
+ project_bot = create(:user, :project_bot)
+ create(:project_member, project: project, user: project_bot)
+
+ expect do
+ delete api("/projects/#{project.id}/members/#{project_bot.id}", maintainer)
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end.not_to change { project.members.count }
+ end
+ end
end
it_behaves_like 'POST /:source_type/:id/members', 'group' do
@@ -484,15 +530,4 @@ describe API::Members do
it_behaves_like 'DELETE /:source_type/:id/members/:user_id', 'group' do
let(:source) { group }
end
-
- context 'Adding owner to project' do
- it 'returns 403' do
- expect do
- post api("/projects/#{project.id}/members", maintainer),
- params: { user_id: stranger.id, access_level: Member::OWNER }
-
- expect(response).to have_gitlab_http_status(:bad_request)
- end.to change { project.members.count }.by(0)
- end
- end
end
diff --git a/spec/requests/api/merge_request_approvals_spec.rb b/spec/requests/api/merge_request_approvals_spec.rb
new file mode 100644
index 00000000000..fad5c3fb60e
--- /dev/null
+++ b/spec/requests/api/merge_request_approvals_spec.rb
@@ -0,0 +1,84 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe API::MergeRequestApprovals do
+ let_it_be(:user) { create(:user) }
+ let_it_be(:project) { create(:project, :public, :repository, creator: user, namespace: user.namespace) }
+ let_it_be(:approver) { create :user }
+ let_it_be(:group) { create :group }
+
+ let(:merge_request) { create(:merge_request, :simple, author: user, source_project: project) }
+
+ describe 'GET :id/merge_requests/:merge_request_iid/approvals' do
+ it 'retrieves the approval status' do
+ project.add_developer(approver)
+ project.add_developer(create(:user))
+
+ create(:approval, user: approver, merge_request: merge_request)
+
+ get api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/approvals", user)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+ end
+
+ describe 'POST :id/merge_requests/:merge_request_iid/approve' do
+ context 'as a valid approver' do
+ let_it_be(:approver) { create(:user) }
+
+ before do
+ project.add_developer(approver)
+ project.add_developer(create(:user))
+ end
+
+ def approve(extra_params = {})
+ post api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/approve", approver), params: extra_params
+ end
+
+ context 'when the sha param is not set' do
+ it 'approves the merge request' do
+ approve
+
+ expect(response).to have_gitlab_http_status(:created)
+ end
+ end
+
+ context 'when the sha param is correct' do
+ it 'approves the merge request' do
+ approve(sha: merge_request.diff_head_sha)
+
+ expect(response).to have_gitlab_http_status(:created)
+ end
+ end
+
+ context 'when the sha param is incorrect' do
+ it 'does not approve the merge request' do
+ approve(sha: merge_request.diff_head_sha.reverse)
+
+ expect(response).to have_gitlab_http_status(:conflict)
+ expect(merge_request.approvals).to be_empty
+ end
+ end
+ end
+ end
+
+ describe 'POST :id/merge_requests/:merge_request_iid/unapprove' do
+ context 'as a user who has approved the merge request' do
+ it 'unapproves the merge request' do
+ unapprover = create(:user)
+
+ project.add_developer(approver)
+ project.add_developer(unapprover)
+ project.add_developer(create(:user))
+
+ create(:approval, user: approver, merge_request: merge_request)
+ create(:approval, user: unapprover, merge_request: merge_request)
+
+ post api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/unapprove", unapprover)
+
+ expect(response).to have_gitlab_http_status(:created)
+ end
+ end
+ end
+end
diff --git a/spec/requests/api/merge_request_diffs_spec.rb b/spec/requests/api/merge_request_diffs_spec.rb
index d00bc4a6dde..3f41a7a034d 100644
--- a/spec/requests/api/merge_request_diffs_spec.rb
+++ b/spec/requests/api/merge_request_diffs_spec.rb
@@ -2,7 +2,7 @@
require "spec_helper"
-describe API::MergeRequestDiffs, 'MergeRequestDiffs' do
+RSpec.describe API::MergeRequestDiffs, 'MergeRequestDiffs' do
let!(:user) { create(:user) }
let!(:merge_request) { create(:merge_request, importing: true) }
let!(:project) { merge_request.target_project }
diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb
index 7a0077f853a..68f1a0f1ba1 100644
--- a/spec/requests/api/merge_requests_spec.rb
+++ b/spec/requests/api/merge_requests_spec.rb
@@ -2,7 +2,7 @@
require "spec_helper"
-describe API::MergeRequests do
+RSpec.describe API::MergeRequests do
include ProjectForksHelper
let(:base_time) { Time.now }
@@ -425,6 +425,73 @@ describe API::MergeRequests do
end
end
+ context 'NOT params' do
+ let(:merge_request2) do
+ create(
+ :merge_request,
+ :simple,
+ milestone: milestone,
+ author: user,
+ assignees: [user],
+ merge_request_context_commits: [merge_request_context_commit],
+ source_project: project,
+ target_project: project,
+ source_branch: 'what',
+ title: "What",
+ created_at: base_time
+ )
+ end
+
+ before do
+ create(:label_link, label: label, target: merge_request)
+ create(:label_link, label: label2, target: merge_request2)
+ end
+
+ it 'returns merge requests without any of the labels given', :aggregate_failures do
+ get api(endpoint_path, user), params: { not: { labels: ["#{label.title}, #{label2.title}"] } }
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response).to be_an(Array)
+ expect(json_response.length).to eq(3)
+ json_response.each do |mr|
+ expect(mr['labels']).not_to include(label2.title, label.title)
+ end
+ end
+
+ it 'returns merge requests without any of the milestones given', :aggregate_failures do
+ get api(endpoint_path, user), params: { not: { milestone: milestone.title } }
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response).to be_an(Array)
+ expect(json_response.length).to eq(4)
+ json_response.each do |mr|
+ expect(mr['milestone']).not_to eq(milestone.title)
+ end
+ end
+
+ it 'returns merge requests without the author given', :aggregate_failures do
+ get api(endpoint_path, user), params: { not: { author_id: user2.id } }
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response).to be_an(Array)
+ expect(json_response.length).to eq(5)
+ json_response.each do |mr|
+ expect(mr['author']['id']).not_to eq(user2.id)
+ end
+ end
+
+ it 'returns merge requests without the assignee given', :aggregate_failures do
+ get api(endpoint_path, user), params: { not: { assignee_id: user2.id } }
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response).to be_an(Array)
+ expect(json_response.length).to eq(5)
+ json_response.each do |mr|
+ expect(mr['assignee']['id']).not_to eq(user2.id)
+ end
+ end
+ end
+
context 'source_branch param' do
it 'returns merge requests with the given source branch' do
get api(endpoint_path, user), params: { source_branch: merge_request_closed.source_branch, state: 'all' }
@@ -1930,7 +1997,7 @@ describe API::MergeRequests do
it "updates the MR's squash attribute" do
expect do
put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/merge", user), params: { squash: true }
- end.to change { merge_request.reload.squash }
+ end.to change { merge_request.reload.squash_on_merge? }
expect(response).to have_gitlab_http_status(:ok)
end
diff --git a/spec/requests/api/metrics/dashboard/annotations_spec.rb b/spec/requests/api/metrics/dashboard/annotations_spec.rb
index 6377ef2435a..07de2925ee2 100644
--- a/spec/requests/api/metrics/dashboard/annotations_spec.rb
+++ b/spec/requests/api/metrics/dashboard/annotations_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Metrics::Dashboard::Annotations do
+RSpec.describe API::Metrics::Dashboard::Annotations do
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project, :private, :repository, namespace: user.namespace) }
let_it_be(:environment) { create(:environment, project: project) }
diff --git a/spec/requests/api/metrics/user_starred_dashboards_spec.rb b/spec/requests/api/metrics/user_starred_dashboards_spec.rb
index 8f9394a0e20..533dff05f27 100644
--- a/spec/requests/api/metrics/user_starred_dashboards_spec.rb
+++ b/spec/requests/api/metrics/user_starred_dashboards_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Metrics::UserStarredDashboards do
+RSpec.describe API::Metrics::UserStarredDashboards do
let_it_be(:user) { create(:user) }
let_it_be(:dashboard_yml) { fixture_file('lib/gitlab/metrics/dashboard/sample_dashboard.yml') }
let_it_be(:dashboard) { '.gitlab/dashboards/find&seek.yml' }
diff --git a/spec/requests/api/namespaces_spec.rb b/spec/requests/api/namespaces_spec.rb
index 68fffc638df..2ac76d469d5 100644
--- a/spec/requests/api/namespaces_spec.rb
+++ b/spec/requests/api/namespaces_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Namespaces do
+RSpec.describe API::Namespaces do
let(:admin) { create(:admin) }
let(:user) { create(:user) }
let!(:group1) { create(:group, name: 'group.one') }
diff --git a/spec/requests/api/notes_spec.rb b/spec/requests/api/notes_spec.rb
index 797dd3bb4e2..1510d31a1a6 100644
--- a/spec/requests/api/notes_spec.rb
+++ b/spec/requests/api/notes_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Notes do
+RSpec.describe API::Notes do
let!(:user) { create(:user) }
let!(:project) { create(:project, :public) }
let(:private_user) { create(:user) }
diff --git a/spec/requests/api/notification_settings_spec.rb b/spec/requests/api/notification_settings_spec.rb
index 2dfde4c8ec9..73cb4948524 100644
--- a/spec/requests/api/notification_settings_spec.rb
+++ b/spec/requests/api/notification_settings_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::NotificationSettings do
+RSpec.describe API::NotificationSettings do
let(:user) { create(:user) }
let!(:group) { create(:group) }
let!(:project) { create(:project, :public, creator_id: user.id, namespace: group) }
diff --git a/spec/requests/api/npm_packages_spec.rb b/spec/requests/api/npm_packages_spec.rb
new file mode 100644
index 00000000000..98a1ca978a8
--- /dev/null
+++ b/spec/requests/api/npm_packages_spec.rb
@@ -0,0 +1,550 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe API::NpmPackages do
+ include PackagesManagerApiSpecHelpers
+
+ let_it_be(:user) { create(:user) }
+ let_it_be(:group) { create(:group) }
+ let_it_be(:project, reload: true) { create(:project, :public, namespace: group) }
+ let_it_be(:package, reload: true) { create(:npm_package, project: project) }
+ let_it_be(:token) { create(:oauth_access_token, scopes: 'api', resource_owner: user) }
+ let_it_be(:personal_access_token) { create(:personal_access_token, user: user) }
+ let_it_be(:job) { create(:ci_build, user: user) }
+ let_it_be(:deploy_token) { create(:deploy_token, read_package_registry: true, write_package_registry: true) }
+ let_it_be(:project_deploy_token) { create(:project_deploy_token, deploy_token: deploy_token, project: project) }
+
+ before do
+ project.add_developer(user)
+ end
+
+ shared_examples 'a package that requires auth' do
+ it 'returns the package info with oauth token' do
+ get_package_with_token(package)
+
+ expect_a_valid_package_response
+ end
+
+ it 'returns the package info with job token' do
+ get_package_with_job_token(package)
+
+ expect_a_valid_package_response
+ end
+
+ it 'denies request without oauth token' do
+ get_package(package)
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+
+ it 'returns the package info with deploy token' do
+ get_package_with_deploy_token(package)
+
+ expect_a_valid_package_response
+ end
+ end
+
+ describe 'GET /api/v4/packages/npm/*package_name' do
+ let_it_be(:package_dependency_link1) { create(:packages_dependency_link, package: package, dependency_type: :dependencies) }
+ let_it_be(:package_dependency_link2) { create(:packages_dependency_link, package: package, dependency_type: :devDependencies) }
+ let_it_be(:package_dependency_link3) { create(:packages_dependency_link, package: package, dependency_type: :bundleDependencies) }
+ let_it_be(:package_dependency_link4) { create(:packages_dependency_link, package: package, dependency_type: :peerDependencies) }
+
+ shared_examples 'returning the npm package info' do
+ it 'returns the package info' do
+ get_package(package)
+
+ expect_a_valid_package_response
+ end
+ end
+
+ shared_examples 'returning forbidden for unknown package' do
+ context 'with an unknown package' do
+ it 'returns forbidden' do
+ get api("/packages/npm/unknown")
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+ end
+ end
+
+ context 'a public project' do
+ it_behaves_like 'returning the npm package info'
+
+ context 'with application setting enabled' do
+ before do
+ stub_application_setting(npm_package_requests_forwarding: true)
+ end
+
+ it_behaves_like 'returning the npm package info'
+
+ context 'with unknown package' do
+ it 'returns a redirect' do
+ get api("/packages/npm/unknown")
+
+ expect(response).to have_gitlab_http_status(:found)
+ expect(response.headers['Location']).to eq('https://registry.npmjs.org/unknown')
+ end
+ end
+ end
+
+ context 'with application setting disabled' do
+ before do
+ stub_application_setting(npm_package_requests_forwarding: false)
+ end
+
+ it_behaves_like 'returning the npm package info'
+
+ it_behaves_like 'returning forbidden for unknown package'
+ end
+
+ context 'project path with a dot' do
+ before do
+ project.update!(path: 'foo.bar')
+ end
+
+ it_behaves_like 'returning the npm package info'
+ end
+ end
+
+ context 'internal project' do
+ before do
+ project.team.truncate
+ project.update!(visibility_level: Gitlab::VisibilityLevel::INTERNAL)
+ end
+
+ it_behaves_like 'a package that requires auth'
+ end
+
+ context 'private project' do
+ before do
+ project.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE)
+ end
+
+ it_behaves_like 'a package that requires auth'
+
+ it 'denies request when not enough permissions' do
+ project.add_guest(user)
+
+ get_package_with_token(package)
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+ end
+
+ def get_package(package, params = {}, headers = {})
+ get api("/packages/npm/#{package.name}"), params: params, headers: headers
+ end
+
+ def get_package_with_token(package, params = {})
+ get_package(package, params.merge(access_token: token.token))
+ end
+
+ def get_package_with_job_token(package, params = {})
+ get_package(package, params.merge(job_token: job.token))
+ end
+
+ def get_package_with_deploy_token(package, params = {})
+ get_package(package, {}, build_token_auth_header(deploy_token.token))
+ end
+ end
+
+ describe 'GET /api/v4/projects/:id/packages/npm/*package_name/-/*file_name' do
+ let_it_be(:package_file) { package.package_files.first }
+
+ shared_examples 'a package file that requires auth' do
+ it 'returns the file with an access token' do
+ get_file_with_token(package_file)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response.media_type).to eq('application/octet-stream')
+ end
+
+ it 'returns the file with a job token' do
+ get_file_with_job_token(package_file)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response.media_type).to eq('application/octet-stream')
+ end
+
+ it 'denies download with no token' do
+ get_file(package_file)
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+
+ context 'a public project' do
+ subject { get_file(package_file) }
+
+ it 'returns the file with no token needed' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response.media_type).to eq('application/octet-stream')
+ end
+
+ it_behaves_like 'a gitlab tracking event', described_class.name, 'pull_package'
+ end
+
+ context 'private project' do
+ before do
+ project.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE)
+ end
+
+ it_behaves_like 'a package file that requires auth'
+
+ it 'denies download when not enough permissions' do
+ project.add_guest(user)
+
+ get_file_with_token(package_file)
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+ end
+
+ context 'internal project' do
+ before do
+ project.update!(visibility_level: Gitlab::VisibilityLevel::INTERNAL)
+ end
+
+ it_behaves_like 'a package file that requires auth'
+ end
+
+ def get_file(package_file, params = {})
+ get api("/projects/#{project.id}/packages/npm/" \
+ "#{package_file.package.name}/-/#{package_file.file_name}"), params: params
+ end
+
+ def get_file_with_token(package_file, params = {})
+ get_file(package_file, params.merge(access_token: token.token))
+ end
+
+ def get_file_with_job_token(package_file, params = {})
+ get_file(package_file, params.merge(job_token: job.token))
+ end
+ end
+
+ describe 'PUT /api/v4/projects/:id/packages/npm/:package_name' do
+ RSpec.shared_examples 'handling invalid record with 400 error' do
+ it 'handles an ActiveRecord::RecordInvalid exception with 400 error' do
+ expect { upload_package_with_token(package_name, params) }
+ .not_to change { project.packages.count }
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ end
+ end
+
+ context 'when params are correct' do
+ context 'invalid package record' do
+ context 'unscoped package' do
+ let(:package_name) { 'my_unscoped_package' }
+ let(:params) { upload_params(package_name: package_name) }
+
+ it_behaves_like 'handling invalid record with 400 error'
+
+ context 'with empty versions' do
+ let(:params) { upload_params(package_name: package_name).merge!(versions: {}) }
+
+ it 'throws a 400 error' do
+ expect { upload_package_with_token(package_name, params) }
+ .not_to change { project.packages.count }
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ end
+ end
+ end
+
+ context 'invalid package name' do
+ let(:package_name) { "@#{group.path}/my_inv@@lid_package_name" }
+ let(:params) { upload_params(package_name: package_name) }
+
+ it_behaves_like 'handling invalid record with 400 error'
+ end
+
+ context 'invalid package version' do
+ using RSpec::Parameterized::TableSyntax
+
+ let(:package_name) { "@#{group.path}/my_package_name" }
+
+ where(:version) do
+ [
+ '1',
+ '1.2',
+ '1./2.3',
+ '../../../../../1.2.3',
+ '%2e%2e%2f1.2.3'
+ ]
+ end
+
+ with_them do
+ let(:params) { upload_params(package_name: package_name, package_version: version) }
+
+ it_behaves_like 'handling invalid record with 400 error'
+ end
+ end
+ end
+
+ context 'scoped package' do
+ let(:package_name) { "@#{group.path}/my_package_name" }
+ let(:params) { upload_params(package_name: package_name) }
+
+ context 'with access token' do
+ subject { upload_package_with_token(package_name, params) }
+
+ it_behaves_like 'a gitlab tracking event', described_class.name, 'push_package'
+
+ it 'creates npm package with file' do
+ expect { subject }
+ .to change { project.packages.count }.by(1)
+ .and change { Packages::PackageFile.count }.by(1)
+ .and change { Packages::Tag.count }.by(1)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+ end
+
+ it 'creates npm package with file with job token' do
+ expect { upload_package_with_job_token(package_name, params) }
+ .to change { project.packages.count }.by(1)
+ .and change { Packages::PackageFile.count }.by(1)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+
+ context 'with an authenticated job token' do
+ let!(:job) { create(:ci_build, user: user) }
+
+ before do
+ Grape::Endpoint.before_each do |endpoint|
+ expect(endpoint).to receive(:current_authenticated_job) { job }
+ end
+ end
+
+ after do
+ Grape::Endpoint.before_each nil
+ end
+
+ it 'creates the package metadata' do
+ upload_package_with_token(package_name, params)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(project.reload.packages.find(json_response['id']).build_info.pipeline).to eq job.pipeline
+ end
+ end
+ end
+
+ context 'package creation fails' do
+ let(:package_name) { "@#{group.path}/my_package_name" }
+ let(:params) { upload_params(package_name: package_name) }
+
+ it 'returns an error if the package already exists' do
+ create(:npm_package, project: project, version: '1.0.1', name: "@#{group.path}/my_package_name")
+ expect { upload_package_with_token(package_name, params) }
+ .not_to change { project.packages.count }
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+ end
+
+ context 'with dependencies' do
+ let(:package_name) { "@#{group.path}/my_package_name" }
+ let(:params) { upload_params(package_name: package_name, file: 'npm/payload_with_duplicated_packages.json') }
+
+ it 'creates npm package with file and dependencies' do
+ expect { upload_package_with_token(package_name, params) }
+ .to change { project.packages.count }.by(1)
+ .and change { Packages::PackageFile.count }.by(1)
+ .and change { Packages::Dependency.count}.by(4)
+ .and change { Packages::DependencyLink.count}.by(6)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+
+ context 'with existing dependencies' do
+ before do
+ name = "@#{group.path}/existing_package"
+ upload_package_with_token(name, upload_params(package_name: name, file: 'npm/payload_with_duplicated_packages.json'))
+ end
+
+ it 'reuses them' do
+ expect { upload_package_with_token(package_name, params) }
+ .to change { project.packages.count }.by(1)
+ .and change { Packages::PackageFile.count }.by(1)
+ .and not_change { Packages::Dependency.count}
+ .and change { Packages::DependencyLink.count}.by(6)
+ end
+ end
+ end
+ end
+
+ def upload_package(package_name, params = {})
+ put api("/projects/#{project.id}/packages/npm/#{package_name.sub('/', '%2f')}"), params: params
+ end
+
+ def upload_package_with_token(package_name, params = {})
+ upload_package(package_name, params.merge(access_token: token.token))
+ end
+
+ def upload_package_with_job_token(package_name, params = {})
+ upload_package(package_name, params.merge(job_token: job.token))
+ end
+
+ def upload_params(package_name:, package_version: '1.0.1', file: 'npm/payload.json')
+ Gitlab::Json.parse(fixture_file("packages/#{file}")
+ .gsub('@root/npm-test', package_name)
+ .gsub('1.0.1', package_version))
+ end
+ end
+
+ describe 'GET /api/v4/packages/npm/-/package/*package_name/dist-tags' do
+ let_it_be(:package_tag1) { create(:packages_tag, package: package) }
+ let_it_be(:package_tag2) { create(:packages_tag, package: package) }
+
+ let(:package_name) { package.name }
+ let(:url) { "/packages/npm/-/package/#{package_name}/dist-tags" }
+
+ subject { get api(url) }
+
+ context 'without the need for a license' do
+ context 'with public project' do
+ context 'with authenticated user' do
+ subject { get api(url, personal_access_token: personal_access_token) }
+
+ it_behaves_like 'returns package tags', :maintainer
+ it_behaves_like 'returns package tags', :developer
+ it_behaves_like 'returns package tags', :reporter
+ it_behaves_like 'returns package tags', :guest
+ end
+
+ context 'with unauthenticated user' do
+ it_behaves_like 'returns package tags', :no_type
+ end
+ end
+
+ context 'with private project' do
+ before do
+ project.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE)
+ end
+
+ context 'with authenticated user' do
+ subject { get api(url, personal_access_token: personal_access_token) }
+
+ it_behaves_like 'returns package tags', :maintainer
+ it_behaves_like 'returns package tags', :developer
+ it_behaves_like 'returns package tags', :reporter
+ it_behaves_like 'rejects package tags access', :guest, :forbidden
+ end
+
+ context 'with unauthenticated user' do
+ it_behaves_like 'rejects package tags access', :no_type, :forbidden
+ end
+ end
+ end
+ end
+
+ describe 'PUT /api/v4/packages/npm/-/package/*package_name/dist-tags/:tag' do
+ let_it_be(:tag_name) { 'test' }
+
+ let(:package_name) { package.name }
+ let(:version) { package.version }
+ let(:url) { "/packages/npm/-/package/#{package_name}/dist-tags/#{tag_name}" }
+
+ subject { put api(url), env: { 'api.request.body': version } }
+
+ context 'without the need for a license' do
+ context 'with public project' do
+ context 'with authenticated user' do
+ subject { put api(url, personal_access_token: personal_access_token), env: { 'api.request.body': version } }
+
+ it_behaves_like 'create package tag', :maintainer
+ it_behaves_like 'create package tag', :developer
+ it_behaves_like 'rejects package tags access', :reporter, :forbidden
+ it_behaves_like 'rejects package tags access', :guest, :forbidden
+ end
+
+ context 'with unauthenticated user' do
+ it_behaves_like 'rejects package tags access', :no_type, :unauthorized
+ end
+ end
+
+ context 'with private project' do
+ before do
+ project.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE)
+ end
+
+ context 'with authenticated user' do
+ subject { put api(url, personal_access_token: personal_access_token), env: { 'api.request.body': version } }
+
+ it_behaves_like 'create package tag', :maintainer
+ it_behaves_like 'create package tag', :developer
+ it_behaves_like 'rejects package tags access', :reporter, :forbidden
+ it_behaves_like 'rejects package tags access', :guest, :forbidden
+ end
+
+ context 'with unauthenticated user' do
+ it_behaves_like 'rejects package tags access', :no_type, :unauthorized
+ end
+ end
+ end
+ end
+
+ describe 'DELETE /api/v4/packages/npm/-/package/*package_name/dist-tags/:tag' do
+ let_it_be(:package_tag) { create(:packages_tag, package: package) }
+
+ let(:package_name) { package.name }
+ let(:tag_name) { package_tag.name }
+ let(:url) { "/packages/npm/-/package/#{package_name}/dist-tags/#{tag_name}" }
+
+ subject { delete api(url) }
+
+ context 'without the need for a license' do
+ context 'with public project' do
+ context 'with authenticated user' do
+ subject { delete api(url, personal_access_token: personal_access_token) }
+
+ it_behaves_like 'delete package tag', :maintainer
+ it_behaves_like 'rejects package tags access', :developer, :forbidden
+ it_behaves_like 'rejects package tags access', :reporter, :forbidden
+ it_behaves_like 'rejects package tags access', :guest, :forbidden
+ end
+
+ context 'with unauthenticated user' do
+ it_behaves_like 'rejects package tags access', :no_type, :unauthorized
+ end
+ end
+
+ context 'with private project' do
+ before do
+ project.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE)
+ end
+
+ context 'with authenticated user' do
+ subject { delete api(url, personal_access_token: personal_access_token) }
+
+ it_behaves_like 'delete package tag', :maintainer
+ it_behaves_like 'rejects package tags access', :developer, :forbidden
+ it_behaves_like 'rejects package tags access', :reporter, :forbidden
+ it_behaves_like 'rejects package tags access', :guest, :forbidden
+ end
+
+ context 'with unauthenticated user' do
+ it_behaves_like 'rejects package tags access', :no_type, :unauthorized
+ end
+ end
+ end
+ end
+
+ def expect_a_valid_package_response
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response.media_type).to eq('application/json')
+ expect(response).to match_response_schema('public_api/v4/packages/npm_package')
+ expect(json_response['name']).to eq(package.name)
+ expect(json_response['versions'][package.version]).to match_schema('public_api/v4/packages/npm_package_version')
+ ::Packages::Npm::PackagePresenter::NPM_VALID_DEPENDENCY_TYPES.each do |dependency_type|
+ expect(json_response.dig('versions', package.version, dependency_type.to_s)).to be_any
+ end
+ expect(json_response['dist-tags']).to match_schema('public_api/v4/packages/npm_package_tags')
+ end
+end
diff --git a/spec/requests/api/nuget_packages_spec.rb b/spec/requests/api/nuget_packages_spec.rb
new file mode 100644
index 00000000000..43aa65d1f76
--- /dev/null
+++ b/spec/requests/api/nuget_packages_spec.rb
@@ -0,0 +1,482 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+RSpec.describe API::NugetPackages do
+ include WorkhorseHelpers
+ include PackagesManagerApiSpecHelpers
+
+ let_it_be(:user) { create(:user) }
+ let_it_be(:project, reload: true) { create(:project, :public) }
+ let_it_be(:personal_access_token) { create(:personal_access_token, user: user) }
+ let_it_be(:deploy_token) { create(:deploy_token, read_package_registry: true, write_package_registry: true) }
+ let_it_be(:project_deploy_token) { create(:project_deploy_token, deploy_token: deploy_token, project: project) }
+
+ describe 'GET /api/v4/projects/:id/packages/nuget' do
+ let(:url) { "/projects/#{project.id}/packages/nuget/index.json" }
+
+ subject { get api(url) }
+
+ context 'without the need for a license' do
+ context 'with valid project' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:project_visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do
+ 'PUBLIC' | :developer | true | true | 'process nuget service index request' | :success
+ 'PUBLIC' | :guest | true | true | 'process nuget service index request' | :success
+ 'PUBLIC' | :developer | true | false | 'process nuget service index request' | :success
+ 'PUBLIC' | :guest | true | false | 'process nuget service index request' | :success
+ 'PUBLIC' | :developer | false | true | 'process nuget service index request' | :success
+ 'PUBLIC' | :guest | false | true | 'process nuget service index request' | :success
+ 'PUBLIC' | :developer | false | false | 'process nuget service index request' | :success
+ 'PUBLIC' | :guest | false | false | 'process nuget service index request' | :success
+ 'PUBLIC' | :anonymous | false | true | 'process nuget service index request' | :success
+ 'PRIVATE' | :developer | true | true | 'process nuget service index request' | :success
+ 'PRIVATE' | :guest | true | true | 'rejects nuget packages access' | :forbidden
+ 'PRIVATE' | :developer | true | false | 'rejects nuget packages access' | :unauthorized
+ 'PRIVATE' | :guest | true | false | 'rejects nuget packages access' | :unauthorized
+ 'PRIVATE' | :developer | false | true | 'rejects nuget packages access' | :not_found
+ 'PRIVATE' | :guest | false | true | 'rejects nuget packages access' | :not_found
+ 'PRIVATE' | :developer | false | false | 'rejects nuget packages access' | :unauthorized
+ 'PRIVATE' | :guest | false | false | 'rejects nuget packages access' | :unauthorized
+ 'PRIVATE' | :anonymous | false | true | 'rejects nuget packages access' | :unauthorized
+ end
+
+ with_them do
+ let(:token) { user_token ? personal_access_token.token : 'wrong' }
+ let(:headers) { user_role == :anonymous ? {} : build_basic_auth_header(user.username, token) }
+
+ subject { get api(url), headers: headers }
+
+ before do
+ project.update!(visibility_level: Gitlab::VisibilityLevel.const_get(project_visibility_level, false))
+ end
+
+ it_behaves_like params[:shared_examples_name], params[:user_role], params[:expected_status], params[:member]
+ end
+ end
+
+ it_behaves_like 'deploy token for package GET requests'
+
+ it_behaves_like 'rejects nuget access with unknown project id'
+
+ it_behaves_like 'rejects nuget access with invalid project id'
+ end
+ end
+
+ describe 'PUT /api/v4/projects/:id/packages/nuget/authorize' do
+ let_it_be(:workhorse_token) { JWT.encode({ 'iss' => 'gitlab-workhorse' }, Gitlab::Workhorse.secret, 'HS256') }
+ let_it_be(:workhorse_header) { { 'GitLab-Workhorse' => '1.0', Gitlab::Workhorse::INTERNAL_API_REQUEST_HEADER => workhorse_token } }
+ let(:url) { "/projects/#{project.id}/packages/nuget/authorize" }
+ let(:headers) { {} }
+
+ subject { put api(url), headers: headers }
+
+ context 'without the need for a license' do
+ context 'with valid project' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:project_visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do
+ 'PUBLIC' | :developer | true | true | 'process nuget workhorse authorization' | :success
+ 'PUBLIC' | :guest | true | true | 'rejects nuget packages access' | :forbidden
+ 'PUBLIC' | :developer | true | false | 'rejects nuget packages access' | :unauthorized
+ 'PUBLIC' | :guest | true | false | 'rejects nuget packages access' | :unauthorized
+ 'PUBLIC' | :developer | false | true | 'rejects nuget packages access' | :forbidden
+ 'PUBLIC' | :guest | false | true | 'rejects nuget packages access' | :forbidden
+ 'PUBLIC' | :developer | false | false | 'rejects nuget packages access' | :unauthorized
+ 'PUBLIC' | :guest | false | false | 'rejects nuget packages access' | :unauthorized
+ 'PUBLIC' | :anonymous | false | true | 'rejects nuget packages access' | :unauthorized
+ 'PRIVATE' | :developer | true | true | 'process nuget workhorse authorization' | :success
+ 'PRIVATE' | :guest | true | true | 'rejects nuget packages access' | :forbidden
+ 'PRIVATE' | :developer | true | false | 'rejects nuget packages access' | :unauthorized
+ 'PRIVATE' | :guest | true | false | 'rejects nuget packages access' | :unauthorized
+ 'PRIVATE' | :developer | false | true | 'rejects nuget packages access' | :not_found
+ 'PRIVATE' | :guest | false | true | 'rejects nuget packages access' | :not_found
+ 'PRIVATE' | :developer | false | false | 'rejects nuget packages access' | :unauthorized
+ 'PRIVATE' | :guest | false | false | 'rejects nuget packages access' | :unauthorized
+ 'PRIVATE' | :anonymous | false | true | 'rejects nuget packages access' | :unauthorized
+ end
+
+ with_them do
+ let(:token) { user_token ? personal_access_token.token : 'wrong' }
+ let(:user_headers) { user_role == :anonymous ? {} : build_basic_auth_header(user.username, token) }
+ let(:headers) { user_headers.merge(workhorse_header) }
+
+ before do
+ project.update!(visibility_level: Gitlab::VisibilityLevel.const_get(project_visibility_level, false))
+ end
+
+ it_behaves_like params[:shared_examples_name], params[:user_role], params[:expected_status], params[:member]
+ end
+ end
+
+ it_behaves_like 'deploy token for package uploads'
+
+ it_behaves_like 'rejects nuget access with unknown project id'
+
+ it_behaves_like 'rejects nuget access with invalid project id'
+ end
+ end
+
+ describe 'PUT /api/v4/projects/:id/packages/nuget' do
+ let(:workhorse_token) { JWT.encode({ 'iss' => 'gitlab-workhorse' }, Gitlab::Workhorse.secret, 'HS256') }
+ let(:workhorse_header) { { 'GitLab-Workhorse' => '1.0', Gitlab::Workhorse::INTERNAL_API_REQUEST_HEADER => workhorse_token } }
+ let_it_be(:file_name) { 'package.nupkg' }
+ let(:url) { "/projects/#{project.id}/packages/nuget" }
+ let(:headers) { {} }
+ let(:params) { { package: temp_file(file_name) } }
+ let(:file_key) { :package }
+ let(:send_rewritten_field) { true }
+
+ subject do
+ workhorse_finalize(
+ api(url),
+ method: :put,
+ file_key: file_key,
+ params: params,
+ headers: headers,
+ send_rewritten_field: send_rewritten_field
+ )
+ end
+
+ context 'without the need for a license' do
+ context 'with valid project' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:project_visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do
+ 'PUBLIC' | :developer | true | true | 'process nuget upload' | :created
+ 'PUBLIC' | :guest | true | true | 'rejects nuget packages access' | :forbidden
+ 'PUBLIC' | :developer | true | false | 'rejects nuget packages access' | :unauthorized
+ 'PUBLIC' | :guest | true | false | 'rejects nuget packages access' | :unauthorized
+ 'PUBLIC' | :developer | false | true | 'rejects nuget packages access' | :forbidden
+ 'PUBLIC' | :guest | false | true | 'rejects nuget packages access' | :forbidden
+ 'PUBLIC' | :developer | false | false | 'rejects nuget packages access' | :unauthorized
+ 'PUBLIC' | :guest | false | false | 'rejects nuget packages access' | :unauthorized
+ 'PUBLIC' | :anonymous | false | true | 'rejects nuget packages access' | :unauthorized
+ 'PRIVATE' | :developer | true | true | 'process nuget upload' | :created
+ 'PRIVATE' | :guest | true | true | 'rejects nuget packages access' | :forbidden
+ 'PRIVATE' | :developer | true | false | 'rejects nuget packages access' | :unauthorized
+ 'PRIVATE' | :guest | true | false | 'rejects nuget packages access' | :unauthorized
+ 'PRIVATE' | :developer | false | true | 'rejects nuget packages access' | :not_found
+ 'PRIVATE' | :guest | false | true | 'rejects nuget packages access' | :not_found
+ 'PRIVATE' | :developer | false | false | 'rejects nuget packages access' | :unauthorized
+ 'PRIVATE' | :guest | false | false | 'rejects nuget packages access' | :unauthorized
+ 'PRIVATE' | :anonymous | false | true | 'rejects nuget packages access' | :unauthorized
+ end
+
+ with_them do
+ let(:token) { user_token ? personal_access_token.token : 'wrong' }
+ let(:user_headers) { user_role == :anonymous ? {} : build_basic_auth_header(user.username, token) }
+ let(:headers) { user_headers.merge(workhorse_header) }
+
+ before do
+ project.update!(visibility_level: Gitlab::VisibilityLevel.const_get(project_visibility_level, false))
+ end
+
+ it_behaves_like params[:shared_examples_name], params[:user_role], params[:expected_status], params[:member]
+ end
+ end
+
+ it_behaves_like 'deploy token for package uploads'
+
+ it_behaves_like 'rejects nuget access with unknown project id'
+
+ it_behaves_like 'rejects nuget access with invalid project id'
+ end
+ end
+
+ describe 'GET /api/v4/projects/:id/packages/nuget/metadata/*package_name/index' do
+ include_context 'with expected presenters dependency groups'
+
+ let_it_be(:package_name) { 'Dummy.Package' }
+ let_it_be(:packages) { create_list(:nuget_package, 5, :with_metadatum, name: package_name, project: project) }
+ let_it_be(:tags) { packages.each { |pkg| create(:packages_tag, package: pkg, name: 'test') } }
+ let(:url) { "/projects/#{project.id}/packages/nuget/metadata/#{package_name}/index.json" }
+
+ subject { get api(url) }
+
+ before do
+ packages.each { |pkg| create_dependencies_for(pkg) }
+ end
+
+ context 'without the need for license' do
+ context 'with valid project' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:project_visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do
+ 'PUBLIC' | :developer | true | true | 'process nuget metadata request at package name level' | :success
+ 'PUBLIC' | :guest | true | true | 'process nuget metadata request at package name level' | :success
+ 'PUBLIC' | :developer | true | false | 'process nuget metadata request at package name level' | :success
+ 'PUBLIC' | :guest | true | false | 'process nuget metadata request at package name level' | :success
+ 'PUBLIC' | :developer | false | true | 'process nuget metadata request at package name level' | :success
+ 'PUBLIC' | :guest | false | true | 'process nuget metadata request at package name level' | :success
+ 'PUBLIC' | :developer | false | false | 'process nuget metadata request at package name level' | :success
+ 'PUBLIC' | :guest | false | false | 'process nuget metadata request at package name level' | :success
+ 'PUBLIC' | :anonymous | false | true | 'process nuget metadata request at package name level' | :success
+ 'PRIVATE' | :developer | true | true | 'process nuget metadata request at package name level' | :success
+ 'PRIVATE' | :guest | true | true | 'rejects nuget packages access' | :forbidden
+ 'PRIVATE' | :developer | true | false | 'rejects nuget packages access' | :unauthorized
+ 'PRIVATE' | :guest | true | false | 'rejects nuget packages access' | :unauthorized
+ 'PRIVATE' | :developer | false | true | 'rejects nuget packages access' | :not_found
+ 'PRIVATE' | :guest | false | true | 'rejects nuget packages access' | :not_found
+ 'PRIVATE' | :developer | false | false | 'rejects nuget packages access' | :unauthorized
+ 'PRIVATE' | :guest | false | false | 'rejects nuget packages access' | :unauthorized
+ 'PRIVATE' | :anonymous | false | true | 'rejects nuget packages access' | :unauthorized
+ end
+
+ with_them do
+ let(:token) { user_token ? personal_access_token.token : 'wrong' }
+ let(:headers) { user_role == :anonymous ? {} : build_basic_auth_header(user.username, token) }
+
+ subject { get api(url), headers: headers }
+
+ before do
+ project.update!(visibility_level: Gitlab::VisibilityLevel.const_get(project_visibility_level, false))
+ end
+
+ it_behaves_like params[:shared_examples_name], params[:user_role], params[:expected_status], params[:member]
+ end
+
+ it_behaves_like 'deploy token for package GET requests'
+
+ it_behaves_like 'rejects nuget access with unknown project id'
+
+ it_behaves_like 'rejects nuget access with invalid project id'
+ end
+ end
+ end
+
+ describe 'GET /api/v4/projects/:id/packages/nuget/metadata/*package_name/*package_version' do
+ include_context 'with expected presenters dependency groups'
+
+ let_it_be(:package_name) { 'Dummy.Package' }
+ let_it_be(:package) { create(:nuget_package, :with_metadatum, name: 'Dummy.Package', project: project) }
+ let_it_be(:tag) { create(:packages_tag, package: package, name: 'test') }
+ let(:url) { "/projects/#{project.id}/packages/nuget/metadata/#{package_name}/#{package.version}.json" }
+
+ subject { get api(url) }
+
+ before do
+ create_dependencies_for(package)
+ end
+
+ context 'without the need for a license' do
+ context 'with valid project' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:project_visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do
+ 'PUBLIC' | :developer | true | true | 'process nuget metadata request at package name and package version level' | :success
+ 'PUBLIC' | :guest | true | true | 'process nuget metadata request at package name and package version level' | :success
+ 'PUBLIC' | :developer | true | false | 'process nuget metadata request at package name and package version level' | :success
+ 'PUBLIC' | :guest | true | false | 'process nuget metadata request at package name and package version level' | :success
+ 'PUBLIC' | :developer | false | true | 'process nuget metadata request at package name and package version level' | :success
+ 'PUBLIC' | :guest | false | true | 'process nuget metadata request at package name and package version level' | :success
+ 'PUBLIC' | :developer | false | false | 'process nuget metadata request at package name and package version level' | :success
+ 'PUBLIC' | :guest | false | false | 'process nuget metadata request at package name and package version level' | :success
+ 'PUBLIC' | :anonymous | false | true | 'process nuget metadata request at package name and package version level' | :success
+ 'PRIVATE' | :developer | true | true | 'process nuget metadata request at package name and package version level' | :success
+ 'PRIVATE' | :guest | true | true | 'rejects nuget packages access' | :forbidden
+ 'PRIVATE' | :developer | true | false | 'rejects nuget packages access' | :unauthorized
+ 'PRIVATE' | :guest | true | false | 'rejects nuget packages access' | :unauthorized
+ 'PRIVATE' | :developer | false | true | 'rejects nuget packages access' | :not_found
+ 'PRIVATE' | :guest | false | true | 'rejects nuget packages access' | :not_found
+ 'PRIVATE' | :developer | false | false | 'rejects nuget packages access' | :unauthorized
+ 'PRIVATE' | :guest | false | false | 'rejects nuget packages access' | :unauthorized
+ 'PRIVATE' | :anonymous | false | true | 'rejects nuget packages access' | :unauthorized
+ end
+
+ with_them do
+ let(:token) { user_token ? personal_access_token.token : 'wrong' }
+ let(:headers) { user_role == :anonymous ? {} : build_basic_auth_header(user.username, token) }
+
+ subject { get api(url), headers: headers }
+
+ before do
+ project.update!(visibility_level: Gitlab::VisibilityLevel.const_get(project_visibility_level, false))
+ end
+
+ it_behaves_like params[:shared_examples_name], params[:user_role], params[:expected_status], params[:member]
+ end
+ end
+
+ it_behaves_like 'deploy token for package GET requests'
+
+ context 'with invalid package name' do
+ let_it_be(:package_name) { 'Unkown' }
+
+ it_behaves_like 'rejects nuget packages access', :developer, :not_found
+ end
+ end
+ end
+
+ describe 'GET /api/v4/projects/:id/packages/nuget/download/*package_name/index' do
+ let_it_be(:package_name) { 'Dummy.Package' }
+ let_it_be(:packages) { create_list(:nuget_package, 5, name: package_name, project: project) }
+ let(:url) { "/projects/#{project.id}/packages/nuget/download/#{package_name}/index.json" }
+
+ subject { get api(url) }
+
+ context 'without the need for a license' do
+ context 'with valid project' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:project_visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do
+ 'PUBLIC' | :developer | true | true | 'process nuget download versions request' | :success
+ 'PUBLIC' | :guest | true | true | 'process nuget download versions request' | :success
+ 'PUBLIC' | :developer | true | false | 'process nuget download versions request' | :success
+ 'PUBLIC' | :guest | true | false | 'process nuget download versions request' | :success
+ 'PUBLIC' | :developer | false | true | 'process nuget download versions request' | :success
+ 'PUBLIC' | :guest | false | true | 'process nuget download versions request' | :success
+ 'PUBLIC' | :developer | false | false | 'process nuget download versions request' | :success
+ 'PUBLIC' | :guest | false | false | 'process nuget download versions request' | :success
+ 'PUBLIC' | :anonymous | false | true | 'process nuget download versions request' | :success
+ 'PRIVATE' | :developer | true | true | 'process nuget download versions request' | :success
+ 'PRIVATE' | :guest | true | true | 'rejects nuget packages access' | :forbidden
+ 'PRIVATE' | :developer | true | false | 'rejects nuget packages access' | :unauthorized
+ 'PRIVATE' | :guest | true | false | 'rejects nuget packages access' | :unauthorized
+ 'PRIVATE' | :developer | false | true | 'rejects nuget packages access' | :not_found
+ 'PRIVATE' | :guest | false | true | 'rejects nuget packages access' | :not_found
+ 'PRIVATE' | :developer | false | false | 'rejects nuget packages access' | :unauthorized
+ 'PRIVATE' | :guest | false | false | 'rejects nuget packages access' | :unauthorized
+ 'PRIVATE' | :anonymous | false | true | 'rejects nuget packages access' | :unauthorized
+ end
+
+ with_them do
+ let(:token) { user_token ? personal_access_token.token : 'wrong' }
+ let(:headers) { user_role == :anonymous ? {} : build_basic_auth_header(user.username, token) }
+
+ subject { get api(url), headers: headers }
+
+ before do
+ project.update!(visibility_level: Gitlab::VisibilityLevel.const_get(project_visibility_level, false))
+ end
+
+ it_behaves_like params[:shared_examples_name], params[:user_role], params[:expected_status], params[:member]
+ end
+ end
+
+ it_behaves_like 'deploy token for package GET requests'
+
+ it_behaves_like 'rejects nuget access with unknown project id'
+
+ it_behaves_like 'rejects nuget access with invalid project id'
+ end
+ end
+
+ describe 'GET /api/v4/projects/:id/packages/nuget/download/*package_name/*package_version/*package_filename' do
+ let_it_be(:package_name) { 'Dummy.Package' }
+ let_it_be(:package) { create(:nuget_package, project: project, name: package_name) }
+
+ let(:url) { "/projects/#{project.id}/packages/nuget/download/#{package.name}/#{package.version}/#{package.name}.#{package.version}.nupkg" }
+
+ subject { get api(url) }
+
+ context 'without the need for a license' do
+ context 'with valid project' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:project_visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do
+ 'PUBLIC' | :developer | true | true | 'process nuget download content request' | :success
+ 'PUBLIC' | :guest | true | true | 'process nuget download content request' | :success
+ 'PUBLIC' | :developer | true | false | 'process nuget download content request' | :success
+ 'PUBLIC' | :guest | true | false | 'process nuget download content request' | :success
+ 'PUBLIC' | :developer | false | true | 'process nuget download content request' | :success
+ 'PUBLIC' | :guest | false | true | 'process nuget download content request' | :success
+ 'PUBLIC' | :developer | false | false | 'process nuget download content request' | :success
+ 'PUBLIC' | :guest | false | false | 'process nuget download content request' | :success
+ 'PUBLIC' | :anonymous | false | true | 'process nuget download content request' | :success
+ 'PRIVATE' | :developer | true | true | 'process nuget download content request' | :success
+ 'PRIVATE' | :guest | true | true | 'rejects nuget packages access' | :forbidden
+ 'PRIVATE' | :developer | true | false | 'rejects nuget packages access' | :unauthorized
+ 'PRIVATE' | :guest | true | false | 'rejects nuget packages access' | :unauthorized
+ 'PRIVATE' | :developer | false | true | 'rejects nuget packages access' | :not_found
+ 'PRIVATE' | :guest | false | true | 'rejects nuget packages access' | :not_found
+ 'PRIVATE' | :developer | false | false | 'rejects nuget packages access' | :unauthorized
+ 'PRIVATE' | :guest | false | false | 'rejects nuget packages access' | :unauthorized
+ 'PRIVATE' | :anonymous | false | true | 'rejects nuget packages access' | :unauthorized
+ end
+
+ with_them do
+ let(:token) { user_token ? personal_access_token.token : 'wrong' }
+ let(:headers) { user_role == :anonymous ? {} : build_basic_auth_header(user.username, token) }
+
+ subject { get api(url), headers: headers }
+
+ before do
+ project.update!(visibility_level: Gitlab::VisibilityLevel.const_get(project_visibility_level, false))
+ end
+
+ it_behaves_like params[:shared_examples_name], params[:user_role], params[:expected_status], params[:member]
+ end
+ end
+
+ it_behaves_like 'deploy token for package GET requests'
+
+ it_behaves_like 'rejects nuget access with unknown project id'
+
+ it_behaves_like 'rejects nuget access with invalid project id'
+ end
+ end
+
+ describe 'GET /api/v4/projects/:id/packages/nuget/query' do
+ let_it_be(:package_a) { create(:nuget_package, :with_metadatum, name: 'Dummy.PackageA', project: project) }
+ let_it_be(:tag) { create(:packages_tag, package: package_a, name: 'test') }
+ let_it_be(:packages_b) { create_list(:nuget_package, 5, name: 'Dummy.PackageB', project: project) }
+ let_it_be(:packages_c) { create_list(:nuget_package, 5, name: 'Dummy.PackageC', project: project) }
+ let_it_be(:package_d) { create(:nuget_package, name: 'Dummy.PackageD', version: '5.0.5-alpha', project: project) }
+ let_it_be(:package_e) { create(:nuget_package, name: 'Foo.BarE', project: project) }
+ let(:search_term) { 'uMmy' }
+ let(:take) { 26 }
+ let(:skip) { 0 }
+ let(:include_prereleases) { true }
+ let(:query_parameters) { { q: search_term, take: take, skip: skip, prerelease: include_prereleases } }
+ let(:url) { "/projects/#{project.id}/packages/nuget/query?#{query_parameters.to_query}" }
+
+ subject { get api(url) }
+
+ context 'without the need for a license' do
+ context 'with valid project' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:project_visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do
+ 'PUBLIC' | :developer | true | true | 'process nuget search request' | :success
+ 'PUBLIC' | :guest | true | true | 'process nuget search request' | :success
+ 'PUBLIC' | :developer | true | false | 'process nuget search request' | :success
+ 'PUBLIC' | :guest | true | false | 'process nuget search request' | :success
+ 'PUBLIC' | :developer | false | true | 'process nuget search request' | :success
+ 'PUBLIC' | :guest | false | true | 'process nuget search request' | :success
+ 'PUBLIC' | :developer | false | false | 'process nuget search request' | :success
+ 'PUBLIC' | :guest | false | false | 'process nuget search request' | :success
+ 'PUBLIC' | :anonymous | false | true | 'process nuget search request' | :success
+ 'PRIVATE' | :developer | true | true | 'process nuget search request' | :success
+ 'PRIVATE' | :guest | true | true | 'rejects nuget packages access' | :forbidden
+ 'PRIVATE' | :developer | true | false | 'rejects nuget packages access' | :unauthorized
+ 'PRIVATE' | :guest | true | false | 'rejects nuget packages access' | :unauthorized
+ 'PRIVATE' | :developer | false | true | 'rejects nuget packages access' | :not_found
+ 'PRIVATE' | :guest | false | true | 'rejects nuget packages access' | :not_found
+ 'PRIVATE' | :developer | false | false | 'rejects nuget packages access' | :unauthorized
+ 'PRIVATE' | :guest | false | false | 'rejects nuget packages access' | :unauthorized
+ 'PRIVATE' | :anonymous | false | true | 'rejects nuget packages access' | :unauthorized
+ end
+
+ with_them do
+ let(:token) { user_token ? personal_access_token.token : 'wrong' }
+ let(:headers) { user_role == :anonymous ? {} : build_basic_auth_header(user.username, token) }
+
+ subject { get api(url), headers: headers }
+
+ before do
+ project.update!(visibility_level: Gitlab::VisibilityLevel.const_get(project_visibility_level, false))
+ end
+
+ it_behaves_like params[:shared_examples_name], params[:user_role], params[:expected_status], params[:member]
+ end
+ end
+
+ it_behaves_like 'deploy token for package GET requests'
+
+ it_behaves_like 'rejects nuget access with unknown project id'
+
+ it_behaves_like 'rejects nuget access with invalid project id'
+ end
+ end
+end
diff --git a/spec/requests/api/oauth_tokens_spec.rb b/spec/requests/api/oauth_tokens_spec.rb
index 5e775841f12..f5971054b3c 100644
--- a/spec/requests/api/oauth_tokens_spec.rb
+++ b/spec/requests/api/oauth_tokens_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'OAuth tokens' do
+RSpec.describe 'OAuth tokens' do
include HttpBasicAuthHelpers
context 'Resource Owner Password Credentials' do
diff --git a/spec/requests/api/package_files_spec.rb b/spec/requests/api/package_files_spec.rb
new file mode 100644
index 00000000000..11170066d6e
--- /dev/null
+++ b/spec/requests/api/package_files_spec.rb
@@ -0,0 +1,81 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe API::PackageFiles do
+ let(:user) { create(:user) }
+ let(:project) { create(:project, :public) }
+ let(:package) { create(:maven_package, project: project) }
+
+ before do
+ project.add_developer(user)
+ end
+
+ describe 'GET /projects/:id/packages/:package_id/package_files' do
+ let(:url) { "/projects/#{project.id}/packages/#{package.id}/package_files" }
+
+ context 'without the need for a license' do
+ context 'project is public' do
+ it 'returns 200' do
+ get api(url)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+
+ it 'returns 404 if package does not exist' do
+ get api("/projects/#{project.id}/packages/0/package_files")
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+
+ context 'project is private' do
+ let(:project) { create(:project, :private) }
+
+ it 'returns 404 for non authenticated user' do
+ get api(url)
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+
+ it 'returns 404 for a user without access to the project' do
+ project.team.truncate
+
+ get api(url, user)
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+
+ it 'returns 200 and valid response schema' do
+ get api(url, user)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to match_response_schema('public_api/v4/packages/package_files')
+ end
+ end
+
+ context 'with pagination params' do
+ let(:per_page) { 2 }
+ let!(:package_file_1) { package.package_files[0] }
+ let!(:package_file_2) { package.package_files[1] }
+ let!(:package_file_3) { package.package_files[2] }
+
+ context 'when viewing the first page' do
+ it 'returns first 2 packages' do
+ get api(url, user), params: { page: 1, per_page: per_page }
+
+ expect_paginated_array_response([package_file_1.id, package_file_2.id])
+ end
+ end
+
+ context 'viewing the second page' do
+ it 'returns the last package' do
+ get api(url, user), params: { page: 2, per_page: per_page }
+
+ expect_paginated_array_response([package_file_3.id])
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/spec/requests/api/pages/internal_access_spec.rb b/spec/requests/api/pages/internal_access_spec.rb
index ee55d1c54b7..c894a2d3ca4 100644
--- a/spec/requests/api/pages/internal_access_spec.rb
+++ b/spec/requests/api/pages/internal_access_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe "Internal Project Pages Access" do
+RSpec.describe "Internal Project Pages Access" do
using RSpec::Parameterized::TableSyntax
include AccessMatchers
diff --git a/spec/requests/api/pages/pages_spec.rb b/spec/requests/api/pages/pages_spec.rb
index 62d43ecff16..53e732928ff 100644
--- a/spec/requests/api/pages/pages_spec.rb
+++ b/spec/requests/api/pages/pages_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Pages do
+RSpec.describe API::Pages do
let_it_be(:project) { create(:project, path: 'my.project', pages_https_only: false) }
let_it_be(:admin) { create(:admin) }
let_it_be(:user) { create(:user) }
diff --git a/spec/requests/api/pages/private_access_spec.rb b/spec/requests/api/pages/private_access_spec.rb
index 146c6a389f3..ea5db691b14 100644
--- a/spec/requests/api/pages/private_access_spec.rb
+++ b/spec/requests/api/pages/private_access_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe "Private Project Pages Access" do
+RSpec.describe "Private Project Pages Access" do
using RSpec::Parameterized::TableSyntax
include AccessMatchers
diff --git a/spec/requests/api/pages/public_access_spec.rb b/spec/requests/api/pages/public_access_spec.rb
index 7d929e2a287..ae73cee91d5 100644
--- a/spec/requests/api/pages/public_access_spec.rb
+++ b/spec/requests/api/pages/public_access_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe "Public Project Pages Access" do
+RSpec.describe "Public Project Pages Access" do
using RSpec::Parameterized::TableSyntax
include AccessMatchers
diff --git a/spec/requests/api/pages_domains_spec.rb b/spec/requests/api/pages_domains_spec.rb
index 8c411233b27..b6838a39257 100644
--- a/spec/requests/api/pages_domains_spec.rb
+++ b/spec/requests/api/pages_domains_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::PagesDomains do
+RSpec.describe API::PagesDomains do
let_it_be(:project) { create(:project, path: 'my.project', pages_https_only: false) }
let_it_be(:user) { create(:user) }
let_it_be(:admin) { create(:admin) }
diff --git a/spec/requests/api/pipeline_schedules_spec.rb b/spec/requests/api/pipeline_schedules_spec.rb
deleted file mode 100644
index 98eaf36b14e..00000000000
--- a/spec/requests/api/pipeline_schedules_spec.rb
+++ /dev/null
@@ -1,522 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-describe API::PipelineSchedules do
- let_it_be(:developer) { create(:user) }
- let_it_be(:user) { create(:user) }
- let_it_be(:project) { create(:project, :repository, public_builds: false) }
-
- before do
- project.add_developer(developer)
- end
-
- describe 'GET /projects/:id/pipeline_schedules' do
- context 'authenticated user with valid permissions' do
- let(:pipeline_schedule) { create(:ci_pipeline_schedule, project: project, owner: developer) }
-
- before do
- pipeline_schedule.pipelines << build(:ci_pipeline, project: project)
- end
-
- def create_pipeline_schedules(count)
- create_list(:ci_pipeline_schedule, count, project: project)
- .each do |pipeline_schedule|
- create(:user).tap do |user|
- project.add_developer(user)
- pipeline_schedule.update(owner: user)
- end
- pipeline_schedule.pipelines << build(:ci_pipeline, project: project)
- end
- end
-
- it 'returns list of pipeline_schedules' do
- get api("/projects/#{project.id}/pipeline_schedules", developer)
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(response).to include_pagination_headers
- expect(response).to match_response_schema('pipeline_schedules')
- end
-
- it 'avoids N + 1 queries' do
- # We need at least two users to trigger a preload for that relation.
- create_pipeline_schedules(1)
-
- control_count = ActiveRecord::QueryRecorder.new do
- get api("/projects/#{project.id}/pipeline_schedules", developer)
- end.count
-
- create_pipeline_schedules(5)
-
- expect do
- get api("/projects/#{project.id}/pipeline_schedules", developer)
- end.not_to exceed_query_limit(control_count)
- end
-
- %w[active inactive].each do |target|
- context "when scope is #{target}" do
- before do
- create(:ci_pipeline_schedule, project: project, active: active?(target))
- end
-
- it 'returns matched pipeline schedules' do
- get api("/projects/#{project.id}/pipeline_schedules", developer), params: { scope: target }
-
- expect(json_response.map { |r| r['active'] }).to all(eq(active?(target)))
- end
- end
-
- def active?(str)
- str == 'active'
- end
- end
- end
-
- context 'authenticated user with invalid permissions' do
- it 'does not return pipeline_schedules list' do
- get api("/projects/#{project.id}/pipeline_schedules", user)
-
- expect(response).to have_gitlab_http_status(:not_found)
- end
- end
-
- context 'unauthenticated user' do
- it 'does not return pipeline_schedules list' do
- get api("/projects/#{project.id}/pipeline_schedules")
-
- expect(response).to have_gitlab_http_status(:unauthorized)
- end
- end
- end
-
- describe 'GET /projects/:id/pipeline_schedules/:pipeline_schedule_id' do
- let(:pipeline_schedule) { create(:ci_pipeline_schedule, project: project, owner: developer) }
-
- before do
- pipeline_schedule.variables << build(:ci_pipeline_schedule_variable)
- pipeline_schedule.pipelines << build(:ci_pipeline, project: project)
- end
-
- context 'authenticated user with valid permissions' do
- it 'returns pipeline_schedule details' do
- get api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}", developer)
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(response).to match_response_schema('pipeline_schedule')
- end
-
- it 'responds with 404 Not Found if requesting non-existing pipeline_schedule' do
- get api("/projects/#{project.id}/pipeline_schedules/-5", developer)
-
- expect(response).to have_gitlab_http_status(:not_found)
- end
- end
-
- context 'authenticated user with invalid permissions' do
- it 'does not return pipeline_schedules list' do
- get api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}", user)
-
- expect(response).to have_gitlab_http_status(:not_found)
- end
- end
-
- context 'authenticated user with insufficient permissions' do
- before do
- project.add_guest(user)
- end
-
- it 'does not return pipeline_schedules list' do
- get api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}", user)
-
- expect(response).to have_gitlab_http_status(:not_found)
- end
- end
-
- context 'unauthenticated user' do
- it 'does not return pipeline_schedules list' do
- get api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}")
-
- expect(response).to have_gitlab_http_status(:unauthorized)
- end
- end
- end
-
- describe 'POST /projects/:id/pipeline_schedules' do
- let(:params) { attributes_for(:ci_pipeline_schedule) }
-
- context 'authenticated user with valid permissions' do
- context 'with required parameters' do
- it 'creates pipeline_schedule' do
- expect do
- post api("/projects/#{project.id}/pipeline_schedules", developer),
- params: params
- end.to change { project.pipeline_schedules.count }.by(1)
-
- expect(response).to have_gitlab_http_status(:created)
- expect(response).to match_response_schema('pipeline_schedule')
- expect(json_response['description']).to eq(params[:description])
- expect(json_response['ref']).to eq(params[:ref])
- expect(json_response['cron']).to eq(params[:cron])
- expect(json_response['cron_timezone']).to eq(params[:cron_timezone])
- expect(json_response['owner']['id']).to eq(developer.id)
- end
- end
-
- context 'without required parameters' do
- it 'does not create pipeline_schedule' do
- post api("/projects/#{project.id}/pipeline_schedules", developer)
-
- expect(response).to have_gitlab_http_status(:bad_request)
- end
- end
-
- context 'when cron has validation error' do
- it 'does not create pipeline_schedule' do
- post api("/projects/#{project.id}/pipeline_schedules", developer),
- params: params.merge('cron' => 'invalid-cron')
-
- expect(response).to have_gitlab_http_status(:bad_request)
- expect(json_response['message']).to have_key('cron')
- end
- end
- end
-
- context 'authenticated user with invalid permissions' do
- it 'does not create pipeline_schedule' do
- post api("/projects/#{project.id}/pipeline_schedules", user), params: params
-
- expect(response).to have_gitlab_http_status(:not_found)
- end
- end
-
- context 'unauthenticated user' do
- it 'does not create pipeline_schedule' do
- post api("/projects/#{project.id}/pipeline_schedules"), params: params
-
- expect(response).to have_gitlab_http_status(:unauthorized)
- end
- end
- end
-
- describe 'PUT /projects/:id/pipeline_schedules/:pipeline_schedule_id' do
- let(:pipeline_schedule) do
- create(:ci_pipeline_schedule, project: project, owner: developer)
- end
-
- context 'authenticated user with valid permissions' do
- it 'updates cron' do
- put api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}", developer),
- params: { cron: '1 2 3 4 *' }
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(response).to match_response_schema('pipeline_schedule')
- expect(json_response['cron']).to eq('1 2 3 4 *')
- end
-
- context 'when cron has validation error' do
- it 'does not update pipeline_schedule' do
- put api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}", developer),
- params: { cron: 'invalid-cron' }
-
- expect(response).to have_gitlab_http_status(:bad_request)
- expect(json_response['message']).to have_key('cron')
- end
- end
- end
-
- context 'authenticated user with invalid permissions' do
- it 'does not update pipeline_schedule' do
- put api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}", user)
-
- expect(response).to have_gitlab_http_status(:not_found)
- end
- end
-
- context 'unauthenticated user' do
- it 'does not update pipeline_schedule' do
- put api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}")
-
- expect(response).to have_gitlab_http_status(:unauthorized)
- end
- end
- end
-
- describe 'POST /projects/:id/pipeline_schedules/:pipeline_schedule_id/take_ownership' do
- let(:pipeline_schedule) do
- create(:ci_pipeline_schedule, project: project, owner: developer)
- end
-
- context 'authenticated user with valid permissions' do
- it 'updates owner' do
- post api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}/take_ownership", developer)
-
- expect(response).to have_gitlab_http_status(:created)
- expect(response).to match_response_schema('pipeline_schedule')
- end
- end
-
- context 'authenticated user with invalid permissions' do
- it 'does not update owner' do
- post api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}/take_ownership", user)
-
- expect(response).to have_gitlab_http_status(:not_found)
- end
- end
-
- context 'unauthenticated user' do
- it 'does not update owner' do
- post api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}/take_ownership")
-
- expect(response).to have_gitlab_http_status(:unauthorized)
- end
- end
- end
-
- describe 'DELETE /projects/:id/pipeline_schedules/:pipeline_schedule_id' do
- let(:maintainer) { create(:user) }
-
- let!(:pipeline_schedule) do
- create(:ci_pipeline_schedule, project: project, owner: developer)
- end
-
- before do
- project.add_maintainer(maintainer)
- end
-
- context 'authenticated user with valid permissions' do
- it 'deletes pipeline_schedule' do
- expect do
- delete api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}", maintainer)
- end.to change { project.pipeline_schedules.count }.by(-1)
-
- expect(response).to have_gitlab_http_status(:no_content)
- end
-
- it 'responds with 404 Not Found if requesting non-existing pipeline_schedule' do
- delete api("/projects/#{project.id}/pipeline_schedules/-5", maintainer)
-
- expect(response).to have_gitlab_http_status(:not_found)
- end
-
- it_behaves_like '412 response' do
- let(:request) { api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}", maintainer) }
- end
- end
-
- context 'authenticated user with invalid permissions' do
- let!(:pipeline_schedule) { create(:ci_pipeline_schedule, project: project, owner: maintainer) }
-
- it 'does not delete pipeline_schedule' do
- delete api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}", developer)
-
- expect(response).to have_gitlab_http_status(:forbidden)
- end
- end
-
- context 'unauthenticated user' do
- it 'does not delete pipeline_schedule' do
- delete api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}")
-
- expect(response).to have_gitlab_http_status(:unauthorized)
- end
- end
- end
-
- describe 'POST /projects/:id/pipeline_schedules/:pipeline_schedule_id/play' do
- let_it_be(:pipeline_schedule) { create(:ci_pipeline_schedule, project: project) }
-
- let(:route) { ->(id) { "/projects/#{project.id}/pipeline_schedules/#{id}/play" } }
-
- context 'authenticated user with `:play_pipeline_schedule` permission' do
- it 'schedules a pipeline worker' do
- project.add_developer(developer)
-
- expect(RunPipelineScheduleWorker)
- .to receive(:perform_async)
- .with(pipeline_schedule.id, developer.id)
- .and_call_original
- post api(route[pipeline_schedule.id], developer)
-
- expect(response).to have_gitlab_http_status(:created)
- end
-
- it 'renders an error if scheduling failed' do
- project.add_developer(developer)
-
- expect(RunPipelineScheduleWorker)
- .to receive(:perform_async)
- .with(pipeline_schedule.id, developer.id)
- .and_return(nil)
- post api(route[pipeline_schedule.id], developer)
-
- expect(response).to have_gitlab_http_status(:internal_server_error)
- end
- end
-
- context 'authenticated user with insufficient access' do
- it 'responds with not found' do
- project.add_guest(user)
-
- post api(route[pipeline_schedule.id], user)
-
- expect(response).to have_gitlab_http_status(:not_found)
- end
- end
-
- context 'unauthenticated user' do
- it 'responds with unauthorized' do
- post api(route[pipeline_schedule.id])
-
- expect(response).to have_gitlab_http_status(:unauthorized)
- end
- end
- end
-
- describe 'POST /projects/:id/pipeline_schedules/:pipeline_schedule_id/variables' do
- let(:params) { attributes_for(:ci_pipeline_schedule_variable) }
-
- let_it_be(:pipeline_schedule) do
- create(:ci_pipeline_schedule, project: project, owner: developer)
- end
-
- context 'authenticated user with valid permissions' do
- context 'with required parameters' do
- it 'creates pipeline_schedule_variable' do
- expect do
- post api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}/variables", developer),
- params: params.merge(variable_type: 'file')
- end.to change { pipeline_schedule.variables.count }.by(1)
-
- expect(response).to have_gitlab_http_status(:created)
- expect(response).to match_response_schema('pipeline_schedule_variable')
- expect(json_response['key']).to eq(params[:key])
- expect(json_response['value']).to eq(params[:value])
- expect(json_response['variable_type']).to eq('file')
- end
- end
-
- context 'without required parameters' do
- it 'does not create pipeline_schedule_variable' do
- post api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}/variables", developer)
-
- expect(response).to have_gitlab_http_status(:bad_request)
- end
- end
-
- context 'when key has validation error' do
- it 'does not create pipeline_schedule_variable' do
- post api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}/variables", developer),
- params: params.merge('key' => '!?!?')
-
- expect(response).to have_gitlab_http_status(:bad_request)
- expect(json_response['message']).to have_key('key')
- end
- end
- end
-
- context 'authenticated user with invalid permissions' do
- it 'does not create pipeline_schedule_variable' do
- post api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}/variables", user), params: params
-
- expect(response).to have_gitlab_http_status(:not_found)
- end
- end
-
- context 'unauthenticated user' do
- it 'does not create pipeline_schedule_variable' do
- post api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}/variables"), params: params
-
- expect(response).to have_gitlab_http_status(:unauthorized)
- end
- end
- end
-
- describe 'PUT /projects/:id/pipeline_schedules/:pipeline_schedule_id/variables/:key' do
- let_it_be(:pipeline_schedule) do
- create(:ci_pipeline_schedule, project: project, owner: developer)
- end
-
- let(:pipeline_schedule_variable) do
- create(:ci_pipeline_schedule_variable, pipeline_schedule: pipeline_schedule)
- end
-
- context 'authenticated user with valid permissions' do
- it 'updates pipeline_schedule_variable' do
- put api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}/variables/#{pipeline_schedule_variable.key}", developer),
- params: { value: 'updated_value', variable_type: 'file' }
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(response).to match_response_schema('pipeline_schedule_variable')
- expect(json_response['value']).to eq('updated_value')
- expect(json_response['variable_type']).to eq('file')
- end
- end
-
- context 'authenticated user with invalid permissions' do
- it 'does not update pipeline_schedule_variable' do
- put api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}/variables/#{pipeline_schedule_variable.key}", user)
-
- expect(response).to have_gitlab_http_status(:not_found)
- end
- end
-
- context 'unauthenticated user' do
- it 'does not update pipeline_schedule_variable' do
- put api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}/variables/#{pipeline_schedule_variable.key}")
-
- expect(response).to have_gitlab_http_status(:unauthorized)
- end
- end
- end
-
- describe 'DELETE /projects/:id/pipeline_schedules/:pipeline_schedule_id/variables/:key' do
- let(:maintainer) { create(:user) }
-
- let_it_be(:pipeline_schedule) do
- create(:ci_pipeline_schedule, project: project, owner: developer)
- end
-
- let!(:pipeline_schedule_variable) do
- create(:ci_pipeline_schedule_variable, pipeline_schedule: pipeline_schedule)
- end
-
- before do
- project.add_maintainer(maintainer)
- end
-
- context 'authenticated user with valid permissions' do
- it 'deletes pipeline_schedule_variable' do
- expect do
- delete api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}/variables/#{pipeline_schedule_variable.key}", maintainer)
- end.to change { Ci::PipelineScheduleVariable.count }.by(-1)
-
- expect(response).to have_gitlab_http_status(:accepted)
- expect(response).to match_response_schema('pipeline_schedule_variable')
- end
-
- it 'responds with 404 Not Found if requesting non-existing pipeline_schedule_variable' do
- delete api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}/variables/____", maintainer)
-
- expect(response).to have_gitlab_http_status(:not_found)
- end
- end
-
- context 'authenticated user with invalid permissions' do
- let!(:pipeline_schedule) { create(:ci_pipeline_schedule, project: project, owner: maintainer) }
-
- it 'does not delete pipeline_schedule_variable' do
- delete api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}/variables/#{pipeline_schedule_variable.key}", developer)
-
- expect(response).to have_gitlab_http_status(:forbidden)
- end
- end
-
- context 'unauthenticated user' do
- it 'does not delete pipeline_schedule_variable' do
- delete api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}/variables/#{pipeline_schedule_variable.key}")
-
- expect(response).to have_gitlab_http_status(:unauthorized)
- end
- end
- end
-end
diff --git a/spec/requests/api/pipelines_spec.rb b/spec/requests/api/pipelines_spec.rb
deleted file mode 100644
index f57223f1de5..00000000000
--- a/spec/requests/api/pipelines_spec.rb
+++ /dev/null
@@ -1,786 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-describe API::Pipelines do
- let_it_be(:user) { create(:user) }
- let_it_be(:non_member) { create(:user) }
-
- # We need to reload as the shared example 'pipelines visibility table' is changing project
- let_it_be(:project, reload: true) do
- create(:project, :repository, creator: user)
- end
-
- let_it_be(:pipeline) do
- create(:ci_empty_pipeline, project: project, sha: project.commit.id,
- ref: project.default_branch, user: user)
- end
-
- before do
- project.add_maintainer(user)
- end
-
- describe 'GET /projects/:id/pipelines ' do
- it_behaves_like 'pipelines visibility table'
-
- context 'authorized user' do
- it 'returns project pipelines' do
- get api("/projects/#{project.id}/pipelines", user)
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(response).to include_pagination_headers
- expect(json_response).to be_an Array
- expect(json_response.first['sha']).to match(/\A\h{40}\z/)
- expect(json_response.first['id']).to eq pipeline.id
- expect(json_response.first['web_url']).to be_present
- expect(json_response.first.keys).to contain_exactly(*%w[id sha ref status web_url created_at updated_at])
- end
-
- context 'when parameter is passed' do
- %w[running pending].each do |target|
- context "when scope is #{target}" do
- before do
- create(:ci_pipeline, project: project, status: target)
- end
-
- it 'returns matched pipelines' do
- get api("/projects/#{project.id}/pipelines", user), params: { scope: target }
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(response).to include_pagination_headers
- expect(json_response).not_to be_empty
- json_response.each { |r| expect(r['status']).to eq(target) }
- end
- end
- end
-
- context 'when scope is finished' do
- before do
- create(:ci_pipeline, project: project, status: 'success')
- create(:ci_pipeline, project: project, status: 'failed')
- create(:ci_pipeline, project: project, status: 'canceled')
- end
-
- it 'returns matched pipelines' do
- get api("/projects/#{project.id}/pipelines", user), params: { scope: 'finished' }
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(response).to include_pagination_headers
- expect(json_response).not_to be_empty
- json_response.each { |r| expect(r['status']).to be_in(%w[success failed canceled]) }
- end
- end
-
- context 'when scope is branches or tags' do
- let_it_be(:pipeline_branch) { create(:ci_pipeline, project: project) }
- let_it_be(:pipeline_tag) { create(:ci_pipeline, project: project, ref: 'v1.0.0', tag: true) }
-
- context 'when scope is branches' do
- it 'returns matched pipelines' do
- get api("/projects/#{project.id}/pipelines", user), params: { scope: 'branches' }
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(response).to include_pagination_headers
- expect(json_response).not_to be_empty
- expect(json_response.last['id']).to eq(pipeline_branch.id)
- end
- end
-
- context 'when scope is tags' do
- it 'returns matched pipelines' do
- get api("/projects/#{project.id}/pipelines", user), params: { scope: 'tags' }
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(response).to include_pagination_headers
- expect(json_response).not_to be_empty
- expect(json_response.last['id']).to eq(pipeline_tag.id)
- end
- end
- end
-
- context 'when scope is invalid' do
- it 'returns bad_request' do
- get api("/projects/#{project.id}/pipelines", user), params: { scope: 'invalid-scope' }
-
- expect(response).to have_gitlab_http_status(:bad_request)
- end
- end
-
- HasStatus::AVAILABLE_STATUSES.each do |target|
- context "when status is #{target}" do
- before do
- create(:ci_pipeline, project: project, status: target)
- exception_status = HasStatus::AVAILABLE_STATUSES - [target]
- create(:ci_pipeline, project: project, status: exception_status.sample)
- end
-
- it 'returns matched pipelines' do
- get api("/projects/#{project.id}/pipelines", user), params: { status: target }
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(response).to include_pagination_headers
- expect(json_response).not_to be_empty
- json_response.each { |r| expect(r['status']).to eq(target) }
- end
- end
- end
-
- context 'when status is invalid' do
- it 'returns bad_request' do
- get api("/projects/#{project.id}/pipelines", user), params: { status: 'invalid-status' }
-
- expect(response).to have_gitlab_http_status(:bad_request)
- end
- end
-
- context 'when ref is specified' do
- before do
- create(:ci_pipeline, project: project)
- end
-
- context 'when ref exists' do
- it 'returns matched pipelines' do
- get api("/projects/#{project.id}/pipelines", user), params: { ref: 'master' }
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(response).to include_pagination_headers
- expect(json_response).not_to be_empty
- json_response.each { |r| expect(r['ref']).to eq('master') }
- end
- end
-
- context 'when ref does not exist' do
- it 'returns empty' do
- get api("/projects/#{project.id}/pipelines", user), params: { ref: 'invalid-ref' }
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(response).to include_pagination_headers
- expect(json_response).to be_empty
- end
- end
- end
-
- context 'when name is specified' do
- let_it_be(:pipeline) { create(:ci_pipeline, project: project, user: user) }
-
- context 'when name exists' do
- it 'returns matched pipelines' do
- get api("/projects/#{project.id}/pipelines", user), params: { name: user.name }
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(response).to include_pagination_headers
- expect(json_response.first['id']).to eq(pipeline.id)
- end
- end
-
- context 'when name does not exist' do
- it 'returns empty' do
- get api("/projects/#{project.id}/pipelines", user), params: { name: 'invalid-name' }
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(response).to include_pagination_headers
- expect(json_response).to be_empty
- end
- end
- end
-
- context 'when username is specified' do
- let_it_be(:pipeline) { create(:ci_pipeline, project: project, user: user) }
-
- context 'when username exists' do
- it 'returns matched pipelines' do
- get api("/projects/#{project.id}/pipelines", user), params: { username: user.username }
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(response).to include_pagination_headers
- expect(json_response.first['id']).to eq(pipeline.id)
- end
- end
-
- context 'when username does not exist' do
- it 'returns empty' do
- get api("/projects/#{project.id}/pipelines", user), params: { username: 'invalid-username' }
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(response).to include_pagination_headers
- expect(json_response).to be_empty
- end
- end
- end
-
- context 'when yaml_errors is specified' do
- let_it_be(:pipeline1) { create(:ci_pipeline, project: project, yaml_errors: 'Syntax error') }
- let_it_be(:pipeline2) { create(:ci_pipeline, project: project) }
-
- context 'when yaml_errors is true' do
- it 'returns matched pipelines' do
- get api("/projects/#{project.id}/pipelines", user), params: { yaml_errors: true }
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(response).to include_pagination_headers
- expect(json_response.first['id']).to eq(pipeline1.id)
- end
- end
-
- context 'when yaml_errors is false' do
- it 'returns matched pipelines' do
- get api("/projects/#{project.id}/pipelines", user), params: { yaml_errors: false }
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(response).to include_pagination_headers
- expect(json_response.first['id']).to eq(pipeline2.id)
- end
- end
-
- context 'when yaml_errors is invalid' do
- it 'returns bad_request' do
- get api("/projects/#{project.id}/pipelines", user), params: { yaml_errors: 'invalid-yaml_errors' }
-
- expect(response).to have_gitlab_http_status(:bad_request)
- end
- end
- end
-
- context 'when updated_at filters are specified' do
- let_it_be(:pipeline1) { create(:ci_pipeline, project: project, updated_at: 2.days.ago) }
- let_it_be(:pipeline2) { create(:ci_pipeline, project: project, updated_at: 4.days.ago) }
- let_it_be(:pipeline3) { create(:ci_pipeline, project: project, updated_at: 1.hour.ago) }
-
- it 'returns pipelines with last update date in specified datetime range' do
- get api("/projects/#{project.id}/pipelines", user), params: { updated_before: 1.day.ago, updated_after: 3.days.ago }
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(response).to include_pagination_headers
- expect(json_response.first['id']).to eq(pipeline1.id)
- end
- end
-
- context 'when order_by and sort are specified' do
- context 'when order_by user_id' do
- before do
- create_list(:user, 3).each do |some_user|
- create(:ci_pipeline, project: project, user: some_user)
- end
- end
-
- context 'when sort parameter is valid' do
- it 'sorts as user_id: :desc' do
- get api("/projects/#{project.id}/pipelines", user), params: { order_by: 'user_id', sort: 'desc' }
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(response).to include_pagination_headers
- expect(json_response).not_to be_empty
-
- pipeline_ids = Ci::Pipeline.all.order(user_id: :desc).pluck(:id)
- expect(json_response.map { |r| r['id'] }).to eq(pipeline_ids)
- end
- end
-
- context 'when sort parameter is invalid' do
- it 'returns bad_request' do
- get api("/projects/#{project.id}/pipelines", user), params: { order_by: 'user_id', sort: 'invalid_sort' }
-
- expect(response).to have_gitlab_http_status(:bad_request)
- end
- end
- end
-
- context 'when order_by is invalid' do
- it 'returns bad_request' do
- get api("/projects/#{project.id}/pipelines", user), params: { order_by: 'lock_version', sort: 'asc' }
-
- expect(response).to have_gitlab_http_status(:bad_request)
- end
- end
- end
- end
- end
-
- context 'unauthorized user' do
- it 'does not return project pipelines' do
- get api("/projects/#{project.id}/pipelines", non_member)
-
- expect(response).to have_gitlab_http_status(:not_found)
- expect(json_response['message']).to eq '404 Project Not Found'
- expect(json_response).not_to be_an Array
- end
- end
- end
-
- describe 'POST /projects/:id/pipeline ' do
- def expect_variables(variables, expected_variables)
- variables.each_with_index do |variable, index|
- expected_variable = expected_variables[index]
-
- expect(variable.key).to eq(expected_variable['key'])
- expect(variable.value).to eq(expected_variable['value'])
- expect(variable.variable_type).to eq(expected_variable['variable_type'])
- end
- end
-
- context 'authorized user' do
- context 'with gitlab-ci.yml' do
- before do
- stub_ci_pipeline_to_return_yaml_file
- end
-
- it 'creates and returns a new pipeline' do
- expect do
- post api("/projects/#{project.id}/pipeline", user), params: { ref: project.default_branch }
- end.to change { project.ci_pipelines.count }.by(1)
-
- expect(response).to have_gitlab_http_status(:created)
- expect(json_response).to be_a Hash
- expect(json_response['sha']).to eq project.commit.id
- end
-
- context 'variables given' do
- let(:variables) { [{ 'variable_type' => 'file', 'key' => 'UPLOAD_TO_S3', 'value' => 'true' }] }
-
- it 'creates and returns a new pipeline using the given variables' do
- expect do
- post api("/projects/#{project.id}/pipeline", user), params: { ref: project.default_branch, variables: variables }
- end.to change { project.ci_pipelines.count }.by(1)
- expect_variables(project.ci_pipelines.last.variables, variables)
-
- expect(response).to have_gitlab_http_status(:created)
- expect(json_response).to be_a Hash
- expect(json_response['sha']).to eq project.commit.id
- expect(json_response).not_to have_key('variables')
- end
- end
-
- describe 'using variables conditions' do
- let(:variables) { [{ 'variable_type' => 'env_var', 'key' => 'STAGING', 'value' => 'true' }] }
-
- before do
- config = YAML.dump(test: { script: 'test', only: { variables: ['$STAGING'] } })
- stub_ci_pipeline_yaml_file(config)
- end
-
- it 'creates and returns a new pipeline using the given variables' do
- expect do
- post api("/projects/#{project.id}/pipeline", user), params: { ref: project.default_branch, variables: variables }
- end.to change { project.ci_pipelines.count }.by(1)
- expect_variables(project.ci_pipelines.last.variables, variables)
-
- expect(response).to have_gitlab_http_status(:created)
- expect(json_response).to be_a Hash
- expect(json_response['sha']).to eq project.commit.id
- expect(json_response).not_to have_key('variables')
- end
-
- context 'condition unmatch' do
- let(:variables) { [{ 'key' => 'STAGING', 'value' => 'false' }] }
-
- it "doesn't create a job" do
- expect do
- post api("/projects/#{project.id}/pipeline", user), params: { ref: project.default_branch }
- end.not_to change { project.ci_pipelines.count }
-
- expect(response).to have_gitlab_http_status(:bad_request)
- end
- end
- end
-
- it 'fails when using an invalid ref' do
- post api("/projects/#{project.id}/pipeline", user), params: { ref: 'invalid_ref' }
-
- expect(response).to have_gitlab_http_status(:bad_request)
- expect(json_response['message']['base'].first).to eq 'Reference not found'
- expect(json_response).not_to be_an Array
- end
- end
-
- context 'without gitlab-ci.yml' do
- context 'without auto devops enabled' do
- before do
- project.update!(auto_devops_attributes: { enabled: false })
- end
-
- it 'fails to create pipeline' do
- post api("/projects/#{project.id}/pipeline", user), params: { ref: project.default_branch }
-
- expect(response).to have_gitlab_http_status(:bad_request)
- expect(json_response['message']['base'].first).to eq 'Missing CI config file'
- expect(json_response).not_to be_an Array
- end
- end
- end
- end
-
- context 'unauthorized user' do
- it 'does not create pipeline' do
- post api("/projects/#{project.id}/pipeline", non_member), params: { ref: project.default_branch }
-
- expect(response).to have_gitlab_http_status(:not_found)
- expect(json_response['message']).to eq '404 Project Not Found'
- expect(json_response).not_to be_an Array
- end
- end
- end
-
- describe 'GET /projects/:id/pipelines/:pipeline_id' do
- it_behaves_like 'pipelines visibility table' do
- let(:pipelines_api_path) do
- "/projects/#{project.id}/pipelines/#{pipeline.id}"
- end
-
- let(:api_response) { response_status == 200 ? response : json_response }
- let(:response_200) { match_response_schema('public_api/v4/pipeline/detail') }
- end
-
- context 'authorized user' do
- it 'exposes known attributes' do
- get api("/projects/#{project.id}/pipelines/#{pipeline.id}", user)
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(response).to match_response_schema('public_api/v4/pipeline/detail')
- end
-
- it 'returns project pipelines' do
- get api("/projects/#{project.id}/pipelines/#{pipeline.id}", user)
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(json_response['sha']).to match(/\A\h{40}\z/)
- end
-
- it 'returns 404 when it does not exist' do
- get api("/projects/#{project.id}/pipelines/#{non_existing_record_id}", user)
-
- expect(response).to have_gitlab_http_status(:not_found)
- expect(json_response['message']).to eq '404 Not found'
- expect(json_response['id']).to be nil
- end
-
- context 'with coverage' do
- before do
- create(:ci_build, coverage: 30, pipeline: pipeline)
- end
-
- it 'exposes the coverage' do
- get api("/projects/#{project.id}/pipelines/#{pipeline.id}", user)
-
- expect(json_response["coverage"].to_i).to eq(30)
- end
- end
- end
-
- context 'unauthorized user' do
- it 'does not return a project pipeline' do
- get api("/projects/#{project.id}/pipelines/#{pipeline.id}", non_member)
-
- expect(response).to have_gitlab_http_status(:not_found)
- expect(json_response['message']).to eq '404 Project Not Found'
- expect(json_response['id']).to be nil
- end
- end
- end
-
- describe 'GET /projects/:id/pipelines/latest' do
- context 'authorized user' do
- let(:second_branch) { project.repository.branches[2] }
-
- let!(:second_pipeline) do
- create(:ci_empty_pipeline, project: project, sha: second_branch.target,
- ref: second_branch.name, user: user)
- end
-
- before do
- create(:ci_empty_pipeline, project: project, sha: project.commit.parent.id,
- ref: project.default_branch, user: user)
- end
-
- context 'default repository branch' do
- it 'gets the latest pipleine' do
- get api("/projects/#{project.id}/pipelines/latest", user)
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(response).to match_response_schema('public_api/v4/pipeline/detail')
- expect(json_response['ref']).to eq(project.default_branch)
- expect(json_response['sha']).to eq(project.commit.id)
- end
- end
-
- context 'ref parameter' do
- it 'gets the latest pipleine' do
- get api("/projects/#{project.id}/pipelines/latest", user), params: { ref: second_branch.name }
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(response).to match_response_schema('public_api/v4/pipeline/detail')
- expect(json_response['ref']).to eq(second_branch.name)
- expect(json_response['sha']).to eq(second_branch.target)
- end
- end
- end
-
- context 'unauthorized user' do
- it 'does not return a project pipeline' do
- get api("/projects/#{project.id}/pipelines/#{pipeline.id}", non_member)
-
- expect(response).to have_gitlab_http_status(:not_found)
- expect(json_response['message']).to eq '404 Project Not Found'
- expect(json_response['id']).to be nil
- end
- end
- end
-
- describe 'GET /projects/:id/pipelines/:pipeline_id/variables' do
- subject { get api("/projects/#{project.id}/pipelines/#{pipeline.id}/variables", api_user) }
-
- let(:api_user) { user }
-
- context 'user is a mantainer' do
- it 'returns pipeline variables empty' do
- subject
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(json_response).to be_empty
- end
-
- context 'with variables' do
- let!(:variable) { create(:ci_pipeline_variable, pipeline: pipeline, key: 'foo', value: 'bar') }
-
- it 'returns pipeline variables' do
- subject
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(json_response).to contain_exactly({ "variable_type" => "env_var", "key" => "foo", "value" => "bar" })
- end
- end
- end
-
- context 'user is a developer' do
- let(:pipeline_owner_user) { create(:user) }
- let(:pipeline) { create(:ci_empty_pipeline, project: project, user: pipeline_owner_user) }
-
- before do
- project.add_developer(api_user)
- end
-
- context 'pipeline created by the developer user' do
- let(:api_user) { pipeline_owner_user }
- let!(:variable) { create(:ci_pipeline_variable, pipeline: pipeline, key: 'foo', value: 'bar') }
-
- it 'returns pipeline variables' do
- subject
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(json_response).to contain_exactly({ "variable_type" => "env_var", "key" => "foo", "value" => "bar" })
- end
- end
-
- context 'pipeline created is not created by the developer user' do
- let(:api_user) { create(:user) }
-
- it 'does not return pipeline variables' do
- subject
-
- expect(response).to have_gitlab_http_status(:forbidden)
- end
- end
- end
-
- context 'user is not a project member' do
- it 'does not return pipeline variables' do
- get api("/projects/#{project.id}/pipelines/#{pipeline.id}/variables", non_member)
-
- expect(response).to have_gitlab_http_status(:not_found)
- expect(json_response['message']).to eq '404 Project Not Found'
- end
- end
- end
-
- describe 'DELETE /projects/:id/pipelines/:pipeline_id' do
- context 'authorized user' do
- let(:owner) { project.owner }
-
- it 'destroys the pipeline' do
- delete api("/projects/#{project.id}/pipelines/#{pipeline.id}", owner)
-
- expect(response).to have_gitlab_http_status(:no_content)
- expect { pipeline.reload }.to raise_error(ActiveRecord::RecordNotFound)
- end
-
- it 'returns 404 when it does not exist' do
- delete api("/projects/#{project.id}/pipelines/#{non_existing_record_id}", owner)
-
- expect(response).to have_gitlab_http_status(:not_found)
- expect(json_response['message']).to eq '404 Not found'
- end
-
- it 'does not log an audit event' do
- expect { delete api("/projects/#{project.id}/pipelines/#{pipeline.id}", owner) }.not_to change { SecurityEvent.count }
- end
-
- context 'when the pipeline has jobs' do
- let_it_be(:build) { create(:ci_build, project: project, pipeline: pipeline) }
-
- it 'destroys associated jobs' do
- delete api("/projects/#{project.id}/pipelines/#{pipeline.id}", owner)
-
- expect(response).to have_gitlab_http_status(:no_content)
- expect { build.reload }.to raise_error(ActiveRecord::RecordNotFound)
- end
- end
- end
-
- context 'unauthorized user' do
- context 'when user is not member' do
- it 'returns a 404' do
- delete api("/projects/#{project.id}/pipelines/#{pipeline.id}", non_member)
-
- expect(response).to have_gitlab_http_status(:not_found)
- expect(json_response['message']).to eq '404 Project Not Found'
- end
- end
-
- context 'when user is developer' do
- let(:developer) { create(:user) }
-
- before do
- project.add_developer(developer)
- end
-
- it 'returns a 403' do
- delete api("/projects/#{project.id}/pipelines/#{pipeline.id}", developer)
-
- expect(response).to have_gitlab_http_status(:forbidden)
- expect(json_response['message']).to eq '403 Forbidden'
- end
- end
- end
- end
-
- describe 'POST /projects/:id/pipelines/:pipeline_id/retry' do
- context 'authorized user' do
- let_it_be(:pipeline) do
- create(:ci_pipeline, project: project, sha: project.commit.id,
- ref: project.default_branch)
- end
-
- let_it_be(:build) { create(:ci_build, :failed, pipeline: pipeline) }
-
- it 'retries failed builds' do
- expect do
- post api("/projects/#{project.id}/pipelines/#{pipeline.id}/retry", user)
- end.to change { pipeline.builds.count }.from(1).to(2)
-
- expect(response).to have_gitlab_http_status(:created)
- expect(build.reload.retried?).to be true
- end
- end
-
- context 'unauthorized user' do
- it 'does not return a project pipeline' do
- post api("/projects/#{project.id}/pipelines/#{pipeline.id}/retry", non_member)
-
- expect(response).to have_gitlab_http_status(:not_found)
- expect(json_response['message']).to eq '404 Project Not Found'
- expect(json_response['id']).to be nil
- end
- end
- end
-
- describe 'POST /projects/:id/pipelines/:pipeline_id/cancel' do
- let_it_be(:pipeline) do
- create(:ci_empty_pipeline, project: project, sha: project.commit.id,
- ref: project.default_branch)
- end
-
- let_it_be(:build) { create(:ci_build, :running, pipeline: pipeline) }
-
- context 'authorized user' do
- it 'retries failed builds', :sidekiq_might_not_need_inline do
- post api("/projects/#{project.id}/pipelines/#{pipeline.id}/cancel", user)
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(json_response['status']).to eq('canceled')
- end
- end
-
- context 'user without proper access rights' do
- let_it_be(:reporter) { create(:user) }
-
- before do
- project.add_reporter(reporter)
- end
-
- it 'rejects the action' do
- post api("/projects/#{project.id}/pipelines/#{pipeline.id}/cancel", reporter)
-
- expect(response).to have_gitlab_http_status(:forbidden)
- expect(pipeline.reload.status).to eq('pending')
- end
- end
- end
-
- describe 'GET /projects/:id/pipelines/:pipeline_id/test_report' do
- context 'authorized user' do
- subject { get api("/projects/#{project.id}/pipelines/#{pipeline.id}/test_report", user) }
-
- let(:pipeline) { create(:ci_pipeline, project: project) }
-
- context 'when feature is enabled' do
- before do
- stub_feature_flags(junit_pipeline_view: true)
- end
-
- context 'when pipeline does not have a test report' do
- it 'returns an empty test report' do
- subject
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(json_response['total_count']).to eq(0)
- end
- end
-
- context 'when pipeline has a test report' do
- let(:pipeline) { create(:ci_pipeline, :with_test_reports, project: project) }
-
- it 'returns the test report' do
- subject
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(json_response['total_count']).to eq(4)
- end
- end
-
- context 'when pipeline has corrupt test reports' do
- before do
- job = create(:ci_build, pipeline: pipeline)
- create(:ci_job_artifact, :junit_with_corrupted_data, job: job, project: project)
- end
-
- it 'returns a suite_error' do
- subject
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(json_response['test_suites'].first['suite_error']).to eq('JUnit XML parsing failed: 1:1: FATAL: Document is empty')
- end
- end
- end
-
- context 'when feature is disabled' do
- before do
- stub_feature_flags(junit_pipeline_view: false)
- end
-
- it 'renders empty response' do
- subject
-
- expect(response).to have_gitlab_http_status(:not_found)
- end
- end
- end
-
- context 'unauthorized user' do
- it 'does not return project pipelines' do
- get api("/projects/#{project.id}/pipelines/#{pipeline.id}/test_report", non_member)
-
- expect(response).to have_gitlab_http_status(:not_found)
- expect(json_response['message']).to eq '404 Project Not Found'
- end
- end
- end
-end
diff --git a/spec/requests/api/project_clusters_spec.rb b/spec/requests/api/project_clusters_spec.rb
index ed899e830e1..ff35e380476 100644
--- a/spec/requests/api/project_clusters_spec.rb
+++ b/spec/requests/api/project_clusters_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::ProjectClusters do
+RSpec.describe API::ProjectClusters do
include KubernetesHelpers
let_it_be(:current_user) { create(:user) }
@@ -40,7 +40,7 @@ describe API::ProjectClusters do
expect(response).to include_pagination_headers
end
- it 'onlies include authorized clusters' do
+ it 'only includes authorized clusters' do
cluster_ids = json_response.map { |cluster| cluster['id'] }
expect(response).to have_gitlab_http_status(:ok)
@@ -258,29 +258,52 @@ describe API::ProjectClusters do
end
end
- context 'when user tries to add multiple clusters' do
+ context 'non-authorized user' do
before do
- create(:cluster, :provided_by_gcp, :project,
- projects: [project])
-
- post api("/projects/#{project.id}/clusters/user", current_user), params: cluster_params
+ post api("/projects/#{project.id}/clusters/user", developer_user), params: cluster_params
end
- it 'responds with 400' do
- expect(response).to have_gitlab_http_status(:bad_request)
- expect(json_response['message']['base'].first)
- .to eq(_('Instance does not support multiple Kubernetes clusters'))
+ it 'responds with 403' do
+ expect(response).to have_gitlab_http_status(:forbidden)
+ expect(json_response['message']).to eq('403 Forbidden')
end
end
+ end
- context 'non-authorized user' do
+ describe 'POST /projects/:id/clusters/user with multiple clusters' do
+ let(:api_url) { 'https://kubernetes.example.com' }
+ let(:namespace) { project.path }
+
+ let(:platform_kubernetes_attributes) do
+ {
+ api_url: api_url,
+ token: 'sample-token',
+ namespace: namespace
+ }
+ end
+
+ let(:cluster_params) do
+ {
+ name: 'test-cluster',
+ environment_scope: 'production/*',
+ platform_kubernetes_attributes: platform_kubernetes_attributes
+ }
+ end
+
+ context 'when another cluster exists' do
before do
- post api("/projects/#{project.id}/clusters/user", developer_user), params: cluster_params
+ create(:cluster, :provided_by_gcp, :project,
+ projects: [project])
+
+ post api("/projects/#{project.id}/clusters/user", current_user), params: cluster_params
end
- it 'responds with 403' do
- expect(response).to have_gitlab_http_status(:forbidden)
- expect(json_response['message']).to eq('403 Forbidden')
+ it 'responds with 201' do
+ expect(response).to have_gitlab_http_status(:created)
+ end
+
+ it 'allows multiple clusters to be associated to project' do
+ expect(project.reload.clusters.count).to eq(2)
end
end
end
diff --git a/spec/requests/api/project_container_repositories_spec.rb b/spec/requests/api/project_container_repositories_spec.rb
index 471fc99117b..6cf0619cde4 100644
--- a/spec/requests/api/project_container_repositories_spec.rb
+++ b/spec/requests/api/project_container_repositories_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::ProjectContainerRepositories do
+RSpec.describe API::ProjectContainerRepositories do
include ExclusiveLeaseHelpers
let_it_be(:project) { create(:project, :private) }
diff --git a/spec/requests/api/project_events_spec.rb b/spec/requests/api/project_events_spec.rb
index f65c62f9402..f3e592f9796 100644
--- a/spec/requests/api/project_events_spec.rb
+++ b/spec/requests/api/project_events_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::ProjectEvents do
+RSpec.describe API::ProjectEvents do
let(:user) { create(:user) }
let(:non_member) { create(:user) }
let(:private_project) { create(:project, :private, creator_id: user.id, namespace: user.namespace) }
diff --git a/spec/requests/api/project_export_spec.rb b/spec/requests/api/project_export_spec.rb
index 58034322a13..d7ba3b4e158 100644
--- a/spec/requests/api/project_export_spec.rb
+++ b/spec/requests/api/project_export_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::ProjectExport, :clean_gitlab_redis_cache do
+RSpec.describe API::ProjectExport, :clean_gitlab_redis_cache do
let_it_be(:project) { create(:project) }
let_it_be(:project_none) { create(:project) }
let_it_be(:project_started) { create(:project) }
@@ -237,7 +237,7 @@ describe API::ProjectExport, :clean_gitlab_redis_cache do
before do
allow(Gitlab::ApplicationRateLimiter)
.to receive(:increment)
- .and_return(Gitlab::ApplicationRateLimiter.rate_limits[:project_download_export][:threshold] + 1)
+ .and_return(Gitlab::ApplicationRateLimiter.rate_limits[:project_download_export][:threshold].call + 1)
end
it 'prevents requesting project export' do
@@ -362,7 +362,7 @@ describe API::ProjectExport, :clean_gitlab_redis_cache do
before do
allow(Gitlab::ApplicationRateLimiter)
.to receive(:increment)
- .and_return(Gitlab::ApplicationRateLimiter.rate_limits[:project_export][:threshold] + 1)
+ .and_return(Gitlab::ApplicationRateLimiter.rate_limits[:project_export][:threshold].call + 1)
end
it 'prevents requesting project export' do
diff --git a/spec/requests/api/project_hooks_spec.rb b/spec/requests/api/project_hooks_spec.rb
index 4474f2f0577..8ab90e26a51 100644
--- a/spec/requests/api/project_hooks_spec.rb
+++ b/spec/requests/api/project_hooks_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::ProjectHooks, 'ProjectHooks' do
+RSpec.describe API::ProjectHooks, 'ProjectHooks' do
let(:user) { create(:user) }
let(:user3) { create(:user) }
let!(:project) { create(:project, creator_id: user.id, namespace: user.namespace) }
diff --git a/spec/requests/api/project_import_spec.rb b/spec/requests/api/project_import_spec.rb
index 563acd0ece4..a6ae636996e 100644
--- a/spec/requests/api/project_import_spec.rb
+++ b/spec/requests/api/project_import_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::ProjectImport do
+RSpec.describe API::ProjectImport do
include WorkhorseHelpers
let(:user) { create(:user) }
diff --git a/spec/requests/api/project_milestones_spec.rb b/spec/requests/api/project_milestones_spec.rb
index c5911d51706..b238949ce47 100644
--- a/spec/requests/api/project_milestones_spec.rb
+++ b/spec/requests/api/project_milestones_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::ProjectMilestones do
+RSpec.describe API::ProjectMilestones do
let(:user) { create(:user) }
let!(:project) { create(:project, namespace: user.namespace ) }
let!(:closed_milestone) { create(:closed_milestone, project: project, title: 'version1', description: 'closed milestone') }
diff --git a/spec/requests/api/project_packages_spec.rb b/spec/requests/api/project_packages_spec.rb
new file mode 100644
index 00000000000..0ece3bff8f9
--- /dev/null
+++ b/spec/requests/api/project_packages_spec.rb
@@ -0,0 +1,272 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe API::ProjectPackages do
+ let(:user) { create(:user) }
+ let_it_be(:project) { create(:project, :public) }
+ let!(:package1) { create(:npm_package, project: project, version: '3.1.0', name: "@#{project.root_namespace.path}/foo1") }
+ let(:package_url) { "/projects/#{project.id}/packages/#{package1.id}" }
+ let!(:package2) { create(:nuget_package, project: project, version: '2.0.4') }
+ let!(:another_package) { create(:npm_package) }
+ let(:no_package_url) { "/projects/#{project.id}/packages/0" }
+ let(:wrong_package_url) { "/projects/#{project.id}/packages/#{another_package.id}" }
+
+ describe 'GET /projects/:id/packages' do
+ let(:url) { "/projects/#{project.id}/packages" }
+ let(:package_schema) { 'public_api/v4/packages/packages' }
+
+ subject { get api(url) }
+
+ context 'without the need for a license' do
+ context 'project is public' do
+ it_behaves_like 'returns packages', :project, :no_type
+ end
+
+ context 'project is private' do
+ let(:project) { create(:project, :private) }
+
+ context 'for unauthenticated user' do
+ it_behaves_like 'rejects packages access', :project, :no_type, :not_found
+ end
+
+ context 'for authenticated user' do
+ subject { get api(url, user) }
+
+ it_behaves_like 'returns packages', :project, :maintainer
+ it_behaves_like 'returns packages', :project, :developer
+ it_behaves_like 'returns packages', :project, :reporter
+ it_behaves_like 'rejects packages access', :project, :no_type, :not_found
+ it_behaves_like 'rejects packages access', :project, :guest, :forbidden
+
+ context 'user is a maintainer' do
+ before do
+ project.add_maintainer(user)
+ end
+
+ it 'returns the destroy url' do
+ subject
+
+ expect(json_response.first['_links']).to include('delete_api_path')
+ end
+ end
+ end
+ end
+
+ context 'with pagination params' do
+ let!(:package3) { create(:maven_package, project: project) }
+ let!(:package4) { create(:maven_package, project: project) }
+
+ context 'with pagination params' do
+ let!(:package3) { create(:npm_package, project: project) }
+ let!(:package4) { create(:npm_package, project: project) }
+
+ it_behaves_like 'returns paginated packages'
+ end
+ end
+
+ context 'with sorting' do
+ let(:package3) { create(:maven_package, project: project, version: '1.1.1', name: 'zzz') }
+
+ before do
+ travel_to(1.day.ago) do
+ package3
+ end
+ end
+
+ it_behaves_like 'package sorting', 'name' do
+ let(:packages) { [package1, package2, package3] }
+ end
+
+ it_behaves_like 'package sorting', 'created_at' do
+ let(:packages) { [package3, package1, package2] }
+ end
+
+ it_behaves_like 'package sorting', 'version' do
+ let(:packages) { [package3, package2, package1] }
+ end
+
+ it_behaves_like 'package sorting', 'type' do
+ let(:packages) { [package3, package1, package2] }
+ end
+ end
+
+ it_behaves_like 'filters on each package_type', is_project: true
+
+ context 'filtering on package_name' do
+ include_context 'package filter context'
+
+ it 'returns the named package' do
+ url = package_filter_url(:name, 'nuget')
+ get api(url, user)
+
+ expect(json_response.length).to eq(1)
+ expect(json_response.first['name']).to include(package2.name)
+ end
+ end
+ end
+ end
+
+ describe 'GET /projects/:id/packages/:package_id' do
+ subject { get api(package_url, user) }
+
+ shared_examples 'no destroy url' do
+ it 'returns no destroy url' do
+ subject
+
+ expect(json_response['_links']).not_to include('delete_api_path')
+ end
+ end
+
+ shared_examples 'destroy url' do
+ it 'returns destroy url' do
+ subject
+
+ expect(json_response['_links']['delete_api_path']).to be_present
+ end
+ end
+
+ context 'without the need for a license' do
+ context 'project is public' do
+ it 'returns 200 and the package information' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to match_response_schema('public_api/v4/packages/package')
+ end
+
+ it 'returns 404 when the package does not exist' do
+ get api(no_package_url, user)
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+
+ it 'returns 404 for the package from a different project' do
+ get api(wrong_package_url, user)
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+
+ it_behaves_like 'no destroy url'
+ end
+
+ context 'project is private' do
+ let(:project) { create(:project, :private) }
+
+ it 'returns 404 for non authenticated user' do
+ get api(package_url)
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+
+ it 'returns 404 for a user without access to the project' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+
+ context 'user is a developer' do
+ before do
+ project.add_developer(user)
+ end
+
+ it 'returns 200 and the package information' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to match_response_schema('public_api/v4/packages/package')
+ end
+
+ it_behaves_like 'no destroy url'
+ end
+
+ context 'user is a maintainer' do
+ before do
+ project.add_maintainer(user)
+ end
+
+ it_behaves_like 'destroy url'
+ end
+
+ context 'with pipeline' do
+ let!(:package1) { create(:npm_package, :with_build, project: project) }
+
+ it 'returns the pipeline info' do
+ project.add_developer(user)
+
+ get api(package_url, user)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to match_response_schema('public_api/v4/packages/package_with_build')
+ end
+ end
+ end
+ end
+ end
+
+ describe 'DELETE /projects/:id/packages/:package_id' do
+ context 'without the need for a license' do
+ context 'project is public' do
+ it 'returns 403 for non authenticated user' do
+ delete api(package_url)
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+
+ it 'returns 403 for a user without access to the project' do
+ delete api(package_url, user)
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+ end
+
+ context 'project is private' do
+ let(:project) { create(:project, :private) }
+
+ it 'returns 404 for non authenticated user' do
+ delete api(package_url)
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+
+ it 'returns 404 for a user without access to the project' do
+ delete api(package_url, user)
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+
+ it 'returns 404 when the package does not exist' do
+ project.add_maintainer(user)
+
+ delete api(no_package_url, user)
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+
+ it 'returns 404 for the package from a different project' do
+ project.add_maintainer(user)
+
+ delete api(wrong_package_url, user)
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+
+ it 'returns 403 for a user without enough permissions' do
+ project.add_developer(user)
+
+ delete api(package_url, user)
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+
+ it 'returns 204' do
+ project.add_maintainer(user)
+
+ delete api(package_url, user)
+
+ expect(response).to have_gitlab_http_status(:no_content)
+ end
+ end
+ end
+ end
+end
diff --git a/spec/requests/api/project_repository_storage_moves_spec.rb b/spec/requests/api/project_repository_storage_moves_spec.rb
index 40966e31d0d..4c9e058ef13 100644
--- a/spec/requests/api/project_repository_storage_moves_spec.rb
+++ b/spec/requests/api/project_repository_storage_moves_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::ProjectRepositoryStorageMoves do
+RSpec.describe API::ProjectRepositoryStorageMoves do
include AccessMatchersForRequest
let_it_be(:user) { create(:admin) }
diff --git a/spec/requests/api/project_snapshots_spec.rb b/spec/requests/api/project_snapshots_spec.rb
index a54f317782b..f23e374407b 100644
--- a/spec/requests/api/project_snapshots_spec.rb
+++ b/spec/requests/api/project_snapshots_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::ProjectSnapshots do
+RSpec.describe API::ProjectSnapshots do
include WorkhorseHelpers
let(:project) { create(:project) }
diff --git a/spec/requests/api/project_snippets_spec.rb b/spec/requests/api/project_snippets_spec.rb
index 22189dc3299..fbb0e3e109f 100644
--- a/spec/requests/api/project_snippets_spec.rb
+++ b/spec/requests/api/project_snippets_spec.rb
@@ -2,7 +2,9 @@
require 'spec_helper'
-describe API::ProjectSnippets do
+RSpec.describe API::ProjectSnippets do
+ include SnippetHelpers
+
let_it_be(:project) { create(:project, :public) }
let_it_be(:user) { create(:user) }
let_it_be(:admin) { create(:admin) }
@@ -84,19 +86,22 @@ describe API::ProjectSnippets do
end
describe 'GET /projects/:project_id/snippets/:id' do
- let(:user) { create(:user) }
- let(:snippet) { create(:project_snippet, :public, :repository, project: project) }
+ let_it_be(:user) { create(:user) }
+ let_it_be(:snippet) { create(:project_snippet, :public, :repository, project: project) }
it 'returns snippet json' do
get api("/projects/#{project.id}/snippets/#{snippet.id}", user)
- expect(response).to have_gitlab_http_status(:ok)
+ aggregate_failures do
+ expect(response).to have_gitlab_http_status(:ok)
- expect(json_response['title']).to eq(snippet.title)
- expect(json_response['description']).to eq(snippet.description)
- expect(json_response['file_name']).to eq(snippet.file_name_on_repo)
- expect(json_response['ssh_url_to_repo']).to eq(snippet.ssh_url_to_repo)
- expect(json_response['http_url_to_repo']).to eq(snippet.http_url_to_repo)
+ expect(json_response['title']).to eq(snippet.title)
+ expect(json_response['description']).to eq(snippet.description)
+ expect(json_response['file_name']).to eq(snippet.file_name_on_repo)
+ expect(json_response['files']).to eq(snippet.blobs.map { |blob| snippet_blob_file(blob) } )
+ expect(json_response['ssh_url_to_repo']).to eq(snippet.ssh_url_to_repo)
+ expect(json_response['http_url_to_repo']).to eq(snippet.http_url_to_repo)
+ end
end
it 'returns 404 for invalid snippet id' do
@@ -111,6 +116,10 @@ describe API::ProjectSnippets do
let(:request) { get api("/projects/#{project_no_snippets.id}/snippets/123", user) }
end
end
+
+ it_behaves_like 'snippet_multiple_files feature disabled' do
+ subject { get api("/projects/#{project.id}/snippets/#{snippet.id}", user) }
+ end
end
describe 'POST /projects/:project_id/snippets/' do
@@ -443,7 +452,7 @@ describe API::ProjectSnippets do
get api("/projects/#{snippet.project.id}/snippets/#{snippet.id}/raw", admin)
expect(response).to have_gitlab_http_status(:ok)
- expect(response.content_type).to eq 'text/plain'
+ expect(response.media_type).to eq 'text/plain'
end
it 'returns 404 for invalid snippet id' do
@@ -465,4 +474,12 @@ describe API::ProjectSnippets do
subject { get api("/projects/#{snippet.project.id}/snippets/#{snippet.id}/raw", snippet.author) }
end
end
+
+ describe 'GET /projects/:project_id/snippets/:id/files/:ref/:file_path/raw' do
+ let_it_be(:snippet) { create(:project_snippet, :repository, author: admin, project: project) }
+
+ it_behaves_like 'raw snippet files' do
+ let(:api_path) { "/projects/#{snippet.project.id}/snippets/#{snippet_id}/files/#{ref}/#{file_path}/raw" }
+ end
+ end
end
diff --git a/spec/requests/api/project_statistics_spec.rb b/spec/requests/api/project_statistics_spec.rb
index 89809a97b96..5f0cac403aa 100644
--- a/spec/requests/api/project_statistics_spec.rb
+++ b/spec/requests/api/project_statistics_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::ProjectStatistics do
+RSpec.describe API::ProjectStatistics do
let_it_be(:developer) { create(:user) }
let_it_be(:public_project) { create(:project, :public) }
diff --git a/spec/requests/api/project_templates_spec.rb b/spec/requests/api/project_templates_spec.rb
index caeb465080e..59b2b09f0bf 100644
--- a/spec/requests/api/project_templates_spec.rb
+++ b/spec/requests/api/project_templates_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::ProjectTemplates do
+RSpec.describe API::ProjectTemplates do
let_it_be(:public_project) { create(:project, :public, path: 'path.with.dot') }
let_it_be(:private_project) { create(:project, :private) }
let_it_be(:developer) { create(:user) }
diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb
index c3f29ec47a9..76b0c04e32d 100644
--- a/spec/requests/api/projects_spec.rb
+++ b/spec/requests/api/projects_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-shared_examples 'languages and percentages JSON response' do
+RSpec.shared_examples 'languages and percentages JSON response' do
let(:expected_languages) { project.repository.languages.map { |language| language.values_at(:label, :value)}.to_h }
before do
@@ -46,7 +46,7 @@ shared_examples 'languages and percentages JSON response' do
end
end
-describe API::Projects do
+RSpec.describe API::Projects do
include ProjectForksHelper
let(:user) { create(:user) }
@@ -254,7 +254,10 @@ describe API::Projects do
expect(response).to have_gitlab_http_status(:ok)
expect(response).to include_pagination_headers
expect(json_response).to be_an Array
- expect(json_response.first).to include 'statistics'
+
+ statistics = json_response.first['statistics']
+ expect(statistics).to be_present
+ expect(statistics).to include('commit_count', 'storage_size', 'repository_size', 'wiki_size', 'lfs_objects_size', 'job_artifacts_size', 'snippets_size')
end
it "does not include license by default" do
@@ -584,6 +587,85 @@ describe API::Projects do
end
end
+ context 'sorting by project statistics' do
+ %w(repository_size storage_size wiki_size).each do |order_by|
+ context "sorting by #{order_by}" do
+ before do
+ ProjectStatistics.update_all(order_by => 100)
+ project4.statistics.update_columns(order_by => 10)
+ project.statistics.update_columns(order_by => 200)
+ end
+
+ context 'admin user' do
+ let(:current_user) { admin }
+
+ context "when sorting by #{order_by} ascendingly" do
+ it 'returns a properly sorted list of projects' do
+ get api('/projects', current_user), params: { order_by: order_by, sort: :asc }
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to include_pagination_headers
+ expect(json_response).to be_an Array
+ expect(json_response.first['id']).to eq(project4.id)
+ end
+ end
+
+ context "when sorting by #{order_by} descendingly" do
+ it 'returns a properly sorted list of projects' do
+ get api('/projects', current_user), params: { order_by: order_by, sort: :desc }
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to include_pagination_headers
+ expect(json_response).to be_an Array
+ expect(json_response.first['id']).to eq(project.id)
+ end
+ end
+ end
+
+ context 'non-admin user' do
+ let(:current_user) { user }
+ let(:projects) { [public_project, project, project2, project3] }
+
+ it 'returns projects ordered normally' do
+ get api('/projects', current_user), params: { order_by: order_by }
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to include_pagination_headers
+ expect(json_response).to be_an Array
+ expect(json_response.map { |project| project['id'] }).to eq(projects.map(&:id).reverse)
+ end
+ end
+ end
+ end
+ end
+
+ context 'filtering by repository_storage' do
+ before do
+ [project, project3].each { |proj| proj.update_columns(repository_storage: 'nfs-11') }
+ # Since we don't actually have Gitaly configured with an nfs-11 storage, an error would be raised
+ # when we present the projects in a response, as we ask Gitaly for stuff like default branch and Gitaly
+ # is not configured for a nfs-11 storage. So we trick Rails into thinking the storage for these projects
+ # is still default (in reality, it is).
+ allow_any_instance_of(Project).to receive(:repository_storage).and_return('default')
+ end
+
+ context 'admin user' do
+ it_behaves_like 'projects response' do
+ let(:filter) { { repository_storage: 'nfs-11' } }
+ let(:current_user) { admin }
+ let(:projects) { [project, project3] }
+ end
+ end
+
+ context 'non-admin user' do
+ it_behaves_like 'projects response' do
+ let(:filter) { { repository_storage: 'nfs-11' } }
+ let(:current_user) { user }
+ let(:projects) { [public_project, project, project2, project3] }
+ end
+ end
+ end
+
context 'with keyset pagination' do
let(:current_user) { user }
let(:projects) { [public_project, project, project2, project3] }
@@ -1846,6 +1928,13 @@ describe API::Projects do
end
end
end
+
+ it 'exposes service desk attributes' do
+ get api("/projects/#{project.id}", user)
+
+ expect(json_response).to have_key 'service_desk_enabled'
+ expect(json_response).to have_key 'service_desk_address'
+ end
end
describe 'GET /projects/:id/users' do
@@ -2133,7 +2222,7 @@ describe API::Projects do
expect(json_response['expires_at']).to eq(expires_at.to_s)
end
- it 'updates project authorization' do
+ it 'updates project authorization', :sidekiq_inline do
expect do
post api("/projects/#{project.id}/share", user), params: { group_id: group.id, group_access: Gitlab::Access::DEVELOPER }
end.to(
@@ -2590,6 +2679,26 @@ describe API::Projects do
end
end
end
+
+ context 'when updating service desk' do
+ subject { put(api("/projects/#{project.id}", user), params: { service_desk_enabled: true }) }
+
+ before do
+ project.update!(service_desk_enabled: false)
+
+ allow(::Gitlab::IncomingEmail).to receive(:enabled?).and_return(true)
+ end
+
+ it 'returns 200' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+
+ it 'enables the service_desk' do
+ expect { subject }.to change { project.reload.service_desk_enabled }.to(true)
+ end
+ end
end
describe 'POST /projects/:id/archive' do
diff --git a/spec/requests/api/protected_branches_spec.rb b/spec/requests/api/protected_branches_spec.rb
index 9203e0ec819..8bcd493eb1f 100644
--- a/spec/requests/api/protected_branches_spec.rb
+++ b/spec/requests/api/protected_branches_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::ProtectedBranches do
+RSpec.describe API::ProtectedBranches do
let(:user) { create(:user) }
let!(:project) { create(:project, :repository) }
let(:protected_name) { 'feature' }
diff --git a/spec/requests/api/protected_tags_spec.rb b/spec/requests/api/protected_tags_spec.rb
index 3bc8ecbee73..cc7261dafc9 100644
--- a/spec/requests/api/protected_tags_spec.rb
+++ b/spec/requests/api/protected_tags_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::ProtectedTags do
+RSpec.describe API::ProtectedTags do
let(:user) { create(:user) }
let!(:project) { create(:project, :repository) }
let(:project2) { create(:project, path: 'project2', namespace: user.namespace) }
diff --git a/spec/requests/api/pypi_packages_spec.rb b/spec/requests/api/pypi_packages_spec.rb
new file mode 100644
index 00000000000..b4e83c8caab
--- /dev/null
+++ b/spec/requests/api/pypi_packages_spec.rb
@@ -0,0 +1,259 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+RSpec.describe API::PypiPackages do
+ include WorkhorseHelpers
+ include PackagesManagerApiSpecHelpers
+
+ let_it_be(:user) { create(:user) }
+ let_it_be(:project, reload: true) { create(:project, :public) }
+ let_it_be(:personal_access_token) { create(:personal_access_token, user: user) }
+ let_it_be(:deploy_token) { create(:deploy_token, read_package_registry: true, write_package_registry: true) }
+ let_it_be(:project_deploy_token) { create(:project_deploy_token, deploy_token: deploy_token, project: project) }
+
+ describe 'GET /api/v4/projects/:id/packages/pypi/simple/:package_name' do
+ let_it_be(:package) { create(:pypi_package, project: project) }
+ let(:url) { "/projects/#{project.id}/packages/pypi/simple/#{package.name}" }
+
+ subject { get api(url) }
+
+ context 'without the need for a license' do
+ context 'with valid project' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:project_visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do
+ 'PUBLIC' | :developer | true | true | 'PyPi package versions' | :success
+ 'PUBLIC' | :guest | true | true | 'PyPi package versions' | :success
+ 'PUBLIC' | :developer | true | false | 'PyPi package versions' | :success
+ 'PUBLIC' | :guest | true | false | 'PyPi package versions' | :success
+ 'PUBLIC' | :developer | false | true | 'PyPi package versions' | :success
+ 'PUBLIC' | :guest | false | true | 'PyPi package versions' | :success
+ 'PUBLIC' | :developer | false | false | 'PyPi package versions' | :success
+ 'PUBLIC' | :guest | false | false | 'PyPi package versions' | :success
+ 'PUBLIC' | :anonymous | false | true | 'PyPi package versions' | :success
+ 'PRIVATE' | :developer | true | true | 'PyPi package versions' | :success
+ 'PRIVATE' | :guest | true | true | 'process PyPi api request' | :forbidden
+ 'PRIVATE' | :developer | true | false | 'process PyPi api request' | :unauthorized
+ 'PRIVATE' | :guest | true | false | 'process PyPi api request' | :unauthorized
+ 'PRIVATE' | :developer | false | true | 'process PyPi api request' | :not_found
+ 'PRIVATE' | :guest | false | true | 'process PyPi api request' | :not_found
+ 'PRIVATE' | :developer | false | false | 'process PyPi api request' | :unauthorized
+ 'PRIVATE' | :guest | false | false | 'process PyPi api request' | :unauthorized
+ 'PRIVATE' | :anonymous | false | true | 'process PyPi api request' | :unauthorized
+ end
+
+ with_them do
+ let(:token) { user_token ? personal_access_token.token : 'wrong' }
+ let(:headers) { user_role == :anonymous ? {} : build_basic_auth_header(user.username, token) }
+
+ subject { get api(url), headers: headers }
+
+ before do
+ project.update!(visibility_level: Gitlab::VisibilityLevel.const_get(project_visibility_level, false))
+ end
+
+ it_behaves_like params[:shared_examples_name], params[:user_role], params[:expected_status], params[:member]
+ end
+ end
+
+ it_behaves_like 'deploy token for package GET requests'
+
+ it_behaves_like 'rejects PyPI access with unknown project id'
+ end
+ end
+
+ describe 'POST /api/v4/projects/:id/packages/pypi/authorize' do
+ let_it_be(:workhorse_token) { JWT.encode({ 'iss' => 'gitlab-workhorse' }, Gitlab::Workhorse.secret, 'HS256') }
+ let_it_be(:workhorse_header) { { 'GitLab-Workhorse' => '1.0', Gitlab::Workhorse::INTERNAL_API_REQUEST_HEADER => workhorse_token } }
+ let(:url) { "/projects/#{project.id}/packages/pypi/authorize" }
+ let(:headers) { {} }
+
+ subject { post api(url), headers: headers }
+
+ context 'without the need for a license' do
+ context 'with valid project' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:project_visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do
+ 'PUBLIC' | :developer | true | true | 'process PyPi api request' | :success
+ 'PUBLIC' | :guest | true | true | 'process PyPi api request' | :forbidden
+ 'PUBLIC' | :developer | true | false | 'process PyPi api request' | :unauthorized
+ 'PUBLIC' | :guest | true | false | 'process PyPi api request' | :unauthorized
+ 'PUBLIC' | :developer | false | true | 'process PyPi api request' | :forbidden
+ 'PUBLIC' | :guest | false | true | 'process PyPi api request' | :forbidden
+ 'PUBLIC' | :developer | false | false | 'process PyPi api request' | :unauthorized
+ 'PUBLIC' | :guest | false | false | 'process PyPi api request' | :unauthorized
+ 'PUBLIC' | :anonymous | false | true | 'process PyPi api request' | :unauthorized
+ 'PRIVATE' | :developer | true | true | 'process PyPi api request' | :success
+ 'PRIVATE' | :guest | true | true | 'process PyPi api request' | :forbidden
+ 'PRIVATE' | :developer | true | false | 'process PyPi api request' | :unauthorized
+ 'PRIVATE' | :guest | true | false | 'process PyPi api request' | :unauthorized
+ 'PRIVATE' | :developer | false | true | 'process PyPi api request' | :not_found
+ 'PRIVATE' | :guest | false | true | 'process PyPi api request' | :not_found
+ 'PRIVATE' | :developer | false | false | 'process PyPi api request' | :unauthorized
+ 'PRIVATE' | :guest | false | false | 'process PyPi api request' | :unauthorized
+ 'PRIVATE' | :anonymous | false | true | 'process PyPi api request' | :unauthorized
+ end
+
+ with_them do
+ let(:token) { user_token ? personal_access_token.token : 'wrong' }
+ let(:user_headers) { user_role == :anonymous ? {} : build_basic_auth_header(user.username, token) }
+ let(:headers) { user_headers.merge(workhorse_header) }
+
+ before do
+ project.update!(visibility_level: Gitlab::VisibilityLevel.const_get(project_visibility_level, false))
+ end
+
+ it_behaves_like params[:shared_examples_name], params[:user_role], params[:expected_status], params[:member]
+ end
+ end
+
+ it_behaves_like 'deploy token for package uploads'
+
+ it_behaves_like 'rejects PyPI access with unknown project id'
+ end
+ end
+
+ describe 'POST /api/v4/projects/:id/packages/pypi' do
+ let(:workhorse_token) { JWT.encode({ 'iss' => 'gitlab-workhorse' }, Gitlab::Workhorse.secret, 'HS256') }
+ let(:workhorse_header) { { 'GitLab-Workhorse' => '1.0', Gitlab::Workhorse::INTERNAL_API_REQUEST_HEADER => workhorse_token } }
+ let_it_be(:file_name) { 'package.whl' }
+ let(:url) { "/projects/#{project.id}/packages/pypi" }
+ let(:headers) { {} }
+ let(:base_params) { { requires_python: '>=3.7', version: '1.0.0', name: 'sample-project', sha256_digest: '123' } }
+ let(:params) { base_params.merge(content: temp_file(file_name)) }
+ let(:send_rewritten_field) { true }
+
+ subject do
+ workhorse_finalize(
+ api(url),
+ method: :post,
+ file_key: :content,
+ params: params,
+ headers: headers,
+ send_rewritten_field: send_rewritten_field
+ )
+ end
+
+ context 'without the need for a license' do
+ context 'with valid project' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:project_visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do
+ 'PUBLIC' | :developer | true | true | 'PyPi package creation' | :created
+ 'PUBLIC' | :guest | true | true | 'process PyPi api request' | :forbidden
+ 'PUBLIC' | :developer | true | false | 'process PyPi api request' | :unauthorized
+ 'PUBLIC' | :guest | true | false | 'process PyPi api request' | :unauthorized
+ 'PUBLIC' | :developer | false | true | 'process PyPi api request' | :forbidden
+ 'PUBLIC' | :guest | false | true | 'process PyPi api request' | :forbidden
+ 'PUBLIC' | :developer | false | false | 'process PyPi api request' | :unauthorized
+ 'PUBLIC' | :guest | false | false | 'process PyPi api request' | :unauthorized
+ 'PUBLIC' | :anonymous | false | true | 'process PyPi api request' | :unauthorized
+ 'PRIVATE' | :developer | true | true | 'process PyPi api request' | :created
+ 'PRIVATE' | :guest | true | true | 'process PyPi api request' | :forbidden
+ 'PRIVATE' | :developer | true | false | 'process PyPi api request' | :unauthorized
+ 'PRIVATE' | :guest | true | false | 'process PyPi api request' | :unauthorized
+ 'PRIVATE' | :developer | false | true | 'process PyPi api request' | :not_found
+ 'PRIVATE' | :guest | false | true | 'process PyPi api request' | :not_found
+ 'PRIVATE' | :developer | false | false | 'process PyPi api request' | :unauthorized
+ 'PRIVATE' | :guest | false | false | 'process PyPi api request' | :unauthorized
+ 'PRIVATE' | :anonymous | false | true | 'process PyPi api request' | :unauthorized
+ end
+
+ with_them do
+ let(:token) { user_token ? personal_access_token.token : 'wrong' }
+ let(:user_headers) { user_role == :anonymous ? {} : build_basic_auth_header(user.username, token) }
+ let(:headers) { user_headers.merge(workhorse_header) }
+
+ before do
+ project.update!(visibility_level: Gitlab::VisibilityLevel.const_get(project_visibility_level, false))
+ end
+
+ it_behaves_like params[:shared_examples_name], params[:user_role], params[:expected_status], params[:member]
+ end
+ end
+
+ context 'with an invalid package' do
+ let(:token) { personal_access_token.token }
+ let(:user_headers) { build_basic_auth_header(user.username, token) }
+ let(:headers) { user_headers.merge(workhorse_header) }
+
+ before do
+ params[:name] = '.$/@!^*'
+ project.add_developer(user)
+ end
+
+ it_behaves_like 'returning response status', :bad_request
+ end
+
+ it_behaves_like 'deploy token for package uploads'
+
+ it_behaves_like 'rejects PyPI access with unknown project id'
+ end
+ end
+
+ describe 'GET /api/v4/projects/:id/packages/pypi/files/:sha256/*file_identifier' do
+ let_it_be(:package_name) { 'Dummy-Package' }
+ let_it_be(:package) { create(:pypi_package, project: project, name: package_name, version: '1.0.0') }
+
+ let(:url) { "/projects/#{project.id}/packages/pypi/files/#{package.package_files.first.file_sha256}/#{package_name}-1.0.0.tar.gz" }
+
+ subject { get api(url) }
+
+ context 'without the need for a license' do
+ context 'with valid project' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:project_visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do
+ 'PUBLIC' | :developer | true | true | 'PyPi package download' | :success
+ 'PUBLIC' | :guest | true | true | 'PyPi package download' | :success
+ 'PUBLIC' | :developer | true | false | 'PyPi package download' | :success
+ 'PUBLIC' | :guest | true | false | 'PyPi package download' | :success
+ 'PUBLIC' | :developer | false | true | 'PyPi package download' | :success
+ 'PUBLIC' | :guest | false | true | 'PyPi package download' | :success
+ 'PUBLIC' | :developer | false | false | 'PyPi package download' | :success
+ 'PUBLIC' | :guest | false | false | 'PyPi package download' | :success
+ 'PUBLIC' | :anonymous | false | true | 'PyPi package download' | :success
+ 'PRIVATE' | :developer | true | true | 'PyPi package download' | :success
+ 'PRIVATE' | :guest | true | true | 'PyPi package download' | :success
+ 'PRIVATE' | :developer | true | false | 'PyPi package download' | :success
+ 'PRIVATE' | :guest | true | false | 'PyPi package download' | :success
+ 'PRIVATE' | :developer | false | true | 'PyPi package download' | :success
+ 'PRIVATE' | :guest | false | true | 'PyPi package download' | :success
+ 'PRIVATE' | :developer | false | false | 'PyPi package download' | :success
+ 'PRIVATE' | :guest | false | false | 'PyPi package download' | :success
+ 'PRIVATE' | :anonymous | false | true | 'PyPi package download' | :success
+ end
+
+ with_them do
+ let(:token) { user_token ? personal_access_token.token : 'wrong' }
+ let(:headers) { user_role == :anonymous ? {} : build_basic_auth_header(user.username, token) }
+
+ subject { get api(url), headers: headers }
+
+ before do
+ project.update!(visibility_level: Gitlab::VisibilityLevel.const_get(project_visibility_level, false))
+ end
+
+ it_behaves_like params[:shared_examples_name], params[:user_role], params[:expected_status], params[:member]
+ end
+ end
+
+ context 'with deploy token headers' do
+ let(:headers) { build_basic_auth_header(deploy_token.username, deploy_token.token) }
+
+ context 'valid token' do
+ it_behaves_like 'returning response status', :success
+ end
+
+ context 'invalid token' do
+ let(:headers) { build_basic_auth_header('foo', 'bar') }
+
+ it_behaves_like 'returning response status', :success
+ end
+ end
+
+ it_behaves_like 'rejects PyPI access with unknown project id'
+ end
+ end
+end
diff --git a/spec/requests/api/release/links_spec.rb b/spec/requests/api/release/links_spec.rb
index cf2043ecc74..82d0d64eba4 100644
--- a/spec/requests/api/release/links_spec.rb
+++ b/spec/requests/api/release/links_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Release::Links do
+RSpec.describe API::Release::Links do
let(:project) { create(:project, :repository, :private) }
let(:maintainer) { create(:user) }
let(:reporter) { create(:user) }
diff --git a/spec/requests/api/releases_spec.rb b/spec/requests/api/releases_spec.rb
index f4cb7f25990..5e8353d74c3 100644
--- a/spec/requests/api/releases_spec.rb
+++ b/spec/requests/api/releases_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Releases do
+RSpec.describe API::Releases do
let(:project) { create(:project, :repository, :private) }
let(:maintainer) { create(:user) }
let(:reporter) { create(:user) }
diff --git a/spec/requests/api/remote_mirrors_spec.rb b/spec/requests/api/remote_mirrors_spec.rb
index 3029b8443b0..436efb708fd 100644
--- a/spec/requests/api/remote_mirrors_spec.rb
+++ b/spec/requests/api/remote_mirrors_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::RemoteMirrors do
+RSpec.describe API::RemoteMirrors do
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project, :repository, :remote_mirror) }
let_it_be(:developer) { create(:user) { |u| project.add_developer(u) } }
diff --git a/spec/requests/api/repositories_spec.rb b/spec/requests/api/repositories_spec.rb
index 55243e83017..36707f32d04 100644
--- a/spec/requests/api/repositories_spec.rb
+++ b/spec/requests/api/repositories_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require 'mime/types'
-describe API::Repositories do
+RSpec.describe API::Repositories do
include RepoHelpers
include WorkhorseHelpers
@@ -227,7 +227,8 @@ describe API::Repositories do
end
describe "GET /projects/:id/repository/archive(.:format)?:sha" do
- let(:route) { "/projects/#{project.id}/repository/archive" }
+ let(:project_id) { CGI.escape(project.full_path) }
+ let(:route) { "/projects/#{project_id}/repository/archive" }
before do
allow(::Gitlab::ApplicationRateLimiter).to receive(:throttled?).and_return(false)
@@ -246,7 +247,7 @@ describe API::Repositories do
end
it 'returns the repository archive archive.zip' do
- get api("/projects/#{project.id}/repository/archive.zip", user)
+ get api("/projects/#{project_id}/repository/archive.zip", user)
expect(response).to have_gitlab_http_status(:ok)
@@ -257,7 +258,7 @@ describe API::Repositories do
end
it 'returns the repository archive archive.tar.bz2' do
- get api("/projects/#{project.id}/repository/archive.tar.bz2", user)
+ get api("/projects/#{project_id}/repository/archive.tar.bz2", user)
expect(response).to have_gitlab_http_status(:ok)
@@ -277,7 +278,7 @@ describe API::Repositories do
it 'rate limits user when thresholds hit' do
allow(::Gitlab::ApplicationRateLimiter).to receive(:throttled?).and_return(true)
- get api("/projects/#{project.id}/repository/archive.tar.bz2", user)
+ get api("/projects/#{project_id}/repository/archive.tar.bz2", user)
expect(response).to have_gitlab_http_status(:too_many_requests)
end
@@ -302,6 +303,13 @@ describe API::Repositories do
end
end
+ context 'when unauthenticated and project path has dots' do
+ it_behaves_like 'repository archive' do
+ let(:project) { create(:project, :public, :repository, path: 'path.with.dot') }
+ let(:current_user) { nil }
+ end
+ end
+
context 'when unauthenticated', 'and project is private' do
it_behaves_like '404 response' do
let(:request) { get api(route) }
diff --git a/spec/requests/api/resource_label_events_spec.rb b/spec/requests/api/resource_label_events_spec.rb
index 7619399458a..a4a70d89812 100644
--- a/spec/requests/api/resource_label_events_spec.rb
+++ b/spec/requests/api/resource_label_events_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::ResourceLabelEvents do
+RSpec.describe API::ResourceLabelEvents do
let_it_be(:user) { create(:user) }
let_it_be(:project, reload: true) { create(:project, :public, namespace: user.namespace) }
let_it_be(:label) { create(:label, project: project) }
diff --git a/spec/requests/api/resource_milestone_events_spec.rb b/spec/requests/api/resource_milestone_events_spec.rb
index b2e92fde5ee..5c81c2180d7 100644
--- a/spec/requests/api/resource_milestone_events_spec.rb
+++ b/spec/requests/api/resource_milestone_events_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::ResourceMilestoneEvents do
+RSpec.describe API::ResourceMilestoneEvents do
let!(:user) { create(:user) }
let!(:project) { create(:project, :public, namespace: user.namespace) }
let!(:milestone) { create(:milestone, project: project) }
diff --git a/spec/requests/api/resource_state_events_spec.rb b/spec/requests/api/resource_state_events_spec.rb
new file mode 100644
index 00000000000..46ca9874395
--- /dev/null
+++ b/spec/requests/api/resource_state_events_spec.rb
@@ -0,0 +1,105 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe API::ResourceStateEvents do
+ let_it_be(:user) { create(:user) }
+ let_it_be(:project, reload: true) { create(:project, :public, namespace: user.namespace) }
+
+ before_all do
+ project.add_developer(user)
+ end
+
+ shared_examples 'resource_state_events API' do |parent_type, eventable_type, id_name|
+ describe "GET /#{parent_type}/:id/#{eventable_type}/:noteable_id/resource_state_events" do
+ let!(:event) { create_event }
+
+ it "returns an array of resource state events" do
+ url = "/#{parent_type}/#{parent.id}/#{eventable_type}/#{eventable[id_name]}/resource_state_events"
+ get api(url, user)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to include_pagination_headers
+ expect(json_response).to be_an Array
+ expect(json_response.first['id']).to eq(event.id)
+ expect(json_response.first['state']).to eq(event.state.to_s)
+ end
+
+ it "returns a 404 error when eventable id not found" do
+ get api("/#{parent_type}/#{parent.id}/#{eventable_type}/#{non_existing_record_id}/resource_state_events", user)
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+
+ it "returns 404 when not authorized" do
+ parent.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE)
+ private_user = create(:user)
+
+ get api("/#{parent_type}/#{parent.id}/#{eventable_type}/#{eventable[id_name]}/resource_state_events", private_user)
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+
+ describe "GET /#{parent_type}/:id/#{eventable_type}/:noteable_id/resource_state_events/:event_id" do
+ let!(:event) { create_event }
+
+ it "returns a resource state event by id" do
+ get api("/#{parent_type}/#{parent.id}/#{eventable_type}/#{eventable[id_name]}/resource_state_events/#{event.id}", user)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response['id']).to eq(event.id)
+ expect(json_response['state']).to eq(event.state.to_s)
+ end
+
+ it "returns 404 when not authorized" do
+ parent.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE)
+ private_user = create(:user)
+
+ get api("/#{parent_type}/#{parent.id}/#{eventable_type}/#{eventable[id_name]}/resource_state_events/#{event.id}", private_user)
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+
+ it "returns a 404 error if resource state event not found" do
+ get api("/#{parent_type}/#{parent.id}/#{eventable_type}/#{eventable[id_name]}/resource_state_events/#{non_existing_record_id}", user)
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+
+ describe 'pagination' do
+ # https://gitlab.com/gitlab-org/gitlab/-/issues/220192
+ it 'returns the second page' do
+ create_event
+ event2 = create_event
+
+ get api("/#{parent_type}/#{parent.id}/#{eventable_type}/#{eventable[id_name]}/resource_state_events?page=2&per_page=1", user)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to include_pagination_headers
+ expect(response.headers['X-Total']).to eq '2'
+ expect(json_response.count).to eq(1)
+ expect(json_response.first['id']).to eq(event2.id)
+ end
+ end
+
+ def create_event(state: :opened)
+ create(:resource_state_event, eventable.class.name.underscore => eventable, state: state)
+ end
+ end
+
+ context 'when eventable is an Issue' do
+ it_behaves_like 'resource_state_events API', 'projects', 'issues', 'iid' do
+ let(:parent) { project }
+ let(:eventable) { create(:issue, project: project, author: user) }
+ end
+ end
+
+ context 'when eventable is a Merge Request' do
+ it_behaves_like 'resource_state_events API', 'projects', 'merge_requests', 'iid' do
+ let(:parent) { project }
+ let(:eventable) { create(:merge_request, source_project: project, target_project: project, author: user) }
+ end
+ end
+end
diff --git a/spec/requests/api/runner_spec.rb b/spec/requests/api/runner_spec.rb
deleted file mode 100644
index 774615757b9..00000000000
--- a/spec/requests/api/runner_spec.rb
+++ /dev/null
@@ -1,2375 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-describe API::Runner, :clean_gitlab_redis_shared_state do
- include StubGitlabCalls
- include RedisHelpers
- include WorkhorseHelpers
-
- let(:registration_token) { 'abcdefg123456' }
-
- before do
- stub_feature_flags(ci_enable_live_trace: true)
- stub_gitlab_calls
- stub_application_setting(runners_registration_token: registration_token)
- allow_any_instance_of(Ci::Runner).to receive(:cache_attributes)
- end
-
- describe '/api/v4/runners' do
- describe 'POST /api/v4/runners' do
- context 'when no token is provided' do
- it 'returns 400 error' do
- post api('/runners')
-
- expect(response).to have_gitlab_http_status(:bad_request)
- end
- end
-
- context 'when invalid token is provided' do
- it 'returns 403 error' do
- post api('/runners'), params: { token: 'invalid' }
-
- expect(response).to have_gitlab_http_status(:forbidden)
- end
- end
-
- context 'when valid token is provided' do
- it 'creates runner with default values' do
- post api('/runners'), params: { token: registration_token }
-
- runner = Ci::Runner.first
-
- expect(response).to have_gitlab_http_status(:created)
- expect(json_response['id']).to eq(runner.id)
- expect(json_response['token']).to eq(runner.token)
- expect(runner.run_untagged).to be true
- expect(runner.active).to be true
- expect(runner.token).not_to eq(registration_token)
- expect(runner).to be_instance_type
- end
-
- context 'when project token is used' do
- let(:project) { create(:project) }
-
- it 'creates project runner' do
- post api('/runners'), params: { token: project.runners_token }
-
- expect(response).to have_gitlab_http_status(:created)
- expect(project.runners.size).to eq(1)
- runner = Ci::Runner.first
- expect(runner.token).not_to eq(registration_token)
- expect(runner.token).not_to eq(project.runners_token)
- expect(runner).to be_project_type
- end
- end
-
- context 'when group token is used' do
- let(:group) { create(:group) }
-
- it 'creates a group runner' do
- post api('/runners'), params: { token: group.runners_token }
-
- expect(response).to have_gitlab_http_status(:created)
- expect(group.runners.reload.size).to eq(1)
- runner = Ci::Runner.first
- expect(runner.token).not_to eq(registration_token)
- expect(runner.token).not_to eq(group.runners_token)
- expect(runner).to be_group_type
- end
- end
- end
-
- context 'when runner description is provided' do
- it 'creates runner' do
- post api('/runners'), params: {
- token: registration_token,
- description: 'server.hostname'
- }
-
- expect(response).to have_gitlab_http_status(:created)
- expect(Ci::Runner.first.description).to eq('server.hostname')
- end
- end
-
- context 'when runner tags are provided' do
- it 'creates runner' do
- post api('/runners'), params: {
- token: registration_token,
- tag_list: 'tag1, tag2'
- }
-
- expect(response).to have_gitlab_http_status(:created)
- expect(Ci::Runner.first.tag_list.sort).to eq(%w(tag1 tag2))
- end
- end
-
- context 'when option for running untagged jobs is provided' do
- context 'when tags are provided' do
- it 'creates runner' do
- post api('/runners'), params: {
- token: registration_token,
- run_untagged: false,
- tag_list: ['tag']
- }
-
- expect(response).to have_gitlab_http_status(:created)
- expect(Ci::Runner.first.run_untagged).to be false
- expect(Ci::Runner.first.tag_list.sort).to eq(['tag'])
- end
- end
-
- context 'when tags are not provided' do
- it 'returns 400 error' do
- post api('/runners'), params: {
- token: registration_token,
- run_untagged: false
- }
-
- expect(response).to have_gitlab_http_status(:bad_request)
- expect(json_response['message']).to include(
- 'tags_list' => ['can not be empty when runner is not allowed to pick untagged jobs'])
- end
- end
- end
-
- context 'when option for locking Runner is provided' do
- it 'creates runner' do
- post api('/runners'), params: {
- token: registration_token,
- locked: true
- }
-
- expect(response).to have_gitlab_http_status(:created)
- expect(Ci::Runner.first.locked).to be true
- end
- end
-
- context 'when option for activating a Runner is provided' do
- context 'when active is set to true' do
- it 'creates runner' do
- post api('/runners'), params: {
- token: registration_token,
- active: true
- }
-
- expect(response).to have_gitlab_http_status(:created)
- expect(Ci::Runner.first.active).to be true
- end
- end
-
- context 'when active is set to false' do
- it 'creates runner' do
- post api('/runners'), params: {
- token: registration_token,
- active: false
- }
-
- expect(response).to have_gitlab_http_status(:created)
- expect(Ci::Runner.first.active).to be false
- end
- end
- end
-
- context 'when access_level is provided for Runner' do
- context 'when access_level is set to ref_protected' do
- it 'creates runner' do
- post api('/runners'), params: {
- token: registration_token,
- access_level: 'ref_protected'
- }
-
- expect(response).to have_gitlab_http_status(:created)
- expect(Ci::Runner.first.ref_protected?).to be true
- end
- end
-
- context 'when access_level is set to not_protected' do
- it 'creates runner' do
- post api('/runners'), params: {
- token: registration_token,
- access_level: 'not_protected'
- }
-
- expect(response).to have_gitlab_http_status(:created)
- expect(Ci::Runner.first.ref_protected?).to be false
- end
- end
- end
-
- context 'when maximum job timeout is specified' do
- it 'creates runner' do
- post api('/runners'), params: {
- token: registration_token,
- maximum_timeout: 9000
- }
-
- expect(response).to have_gitlab_http_status(:created)
- expect(Ci::Runner.first.maximum_timeout).to eq(9000)
- end
-
- context 'when maximum job timeout is empty' do
- it 'creates runner' do
- post api('/runners'), params: {
- token: registration_token,
- maximum_timeout: ''
- }
-
- expect(response).to have_gitlab_http_status(:created)
- expect(Ci::Runner.first.maximum_timeout).to be_nil
- end
- end
- end
-
- %w(name version revision platform architecture).each do |param|
- context "when info parameter '#{param}' info is present" do
- let(:value) { "#{param}_value" }
-
- it "updates provided Runner's parameter" do
- post api('/runners'), params: {
- token: registration_token,
- info: { param => value }
- }
-
- expect(response).to have_gitlab_http_status(:created)
- expect(Ci::Runner.first.read_attribute(param.to_sym)).to eq(value)
- end
- end
- end
-
- it "sets the runner's ip_address" do
- post api('/runners'),
- params: { token: registration_token },
- headers: { 'X-Forwarded-For' => '123.111.123.111' }
-
- expect(response).to have_gitlab_http_status(:created)
- expect(Ci::Runner.first.ip_address).to eq('123.111.123.111')
- end
- end
-
- describe 'DELETE /api/v4/runners' do
- context 'when no token is provided' do
- it 'returns 400 error' do
- delete api('/runners')
-
- expect(response).to have_gitlab_http_status(:bad_request)
- end
- end
-
- context 'when invalid token is provided' do
- it 'returns 403 error' do
- delete api('/runners'), params: { token: 'invalid' }
-
- expect(response).to have_gitlab_http_status(:forbidden)
- end
- end
-
- context 'when valid token is provided' do
- let(:runner) { create(:ci_runner) }
-
- it 'deletes Runner' do
- delete api('/runners'), params: { token: runner.token }
-
- expect(response).to have_gitlab_http_status(:no_content)
- expect(Ci::Runner.count).to eq(0)
- end
-
- it_behaves_like '412 response' do
- let(:request) { api('/runners') }
- let(:params) { { token: runner.token } }
- end
- end
- end
-
- describe 'POST /api/v4/runners/verify' do
- let(:runner) { create(:ci_runner) }
-
- context 'when no token is provided' do
- it 'returns 400 error' do
- post api('/runners/verify')
-
- expect(response).to have_gitlab_http_status :bad_request
- end
- end
-
- context 'when invalid token is provided' do
- it 'returns 403 error' do
- post api('/runners/verify'), params: { token: 'invalid-token' }
-
- expect(response).to have_gitlab_http_status(:forbidden)
- end
- end
-
- context 'when valid token is provided' do
- it 'verifies Runner credentials' do
- post api('/runners/verify'), params: { token: runner.token }
-
- expect(response).to have_gitlab_http_status(:ok)
- end
- end
- end
- end
-
- describe '/api/v4/jobs' do
- shared_examples 'application context metadata' do |api_route|
- it 'contains correct context metadata' do
- # Avoids popping the context from the thread so we can
- # check its content after the request.
- allow(Labkit::Context).to receive(:pop)
-
- send_request
-
- Labkit::Context.with_context do |context|
- expected_context = {
- 'meta.caller_id' => api_route,
- 'meta.user' => job.user.username,
- 'meta.project' => job.project.full_path,
- 'meta.root_namespace' => job.project.full_path_components.first
- }
-
- expect(context.to_h).to include(expected_context)
- end
- end
- end
-
- let(:root_namespace) { create(:namespace) }
- let(:namespace) { create(:namespace, parent: root_namespace) }
- let(:project) { create(:project, namespace: namespace, shared_runners_enabled: false) }
- let(:pipeline) { create(:ci_pipeline, project: project, ref: 'master') }
- let(:runner) { create(:ci_runner, :project, projects: [project]) }
- let(:user) { create(:user) }
- let(:job) do
- create(:ci_build, :artifacts, :extended_options,
- pipeline: pipeline, name: 'spinach', stage: 'test', stage_idx: 0)
- end
-
- describe 'POST /api/v4/jobs/request' do
- let!(:last_update) {}
- let!(:new_update) { }
- let(:user_agent) { 'gitlab-runner 9.0.0 (9-0-stable; go1.7.4; linux/amd64)' }
-
- before do
- job
- stub_container_registry_config(enabled: false)
- end
-
- shared_examples 'no jobs available' do
- before do
- request_job
- end
-
- context 'when runner sends version in User-Agent' do
- context 'for stable version' do
- it 'gives 204 and set X-GitLab-Last-Update' do
- expect(response).to have_gitlab_http_status(:no_content)
- expect(response.header).to have_key('X-GitLab-Last-Update')
- end
- end
-
- context 'when last_update is up-to-date' do
- let(:last_update) { runner.ensure_runner_queue_value }
-
- it 'gives 204 and set the same X-GitLab-Last-Update' do
- expect(response).to have_gitlab_http_status(:no_content)
- expect(response.header['X-GitLab-Last-Update']).to eq(last_update)
- end
- end
-
- context 'when last_update is outdated' do
- let(:last_update) { runner.ensure_runner_queue_value }
- let(:new_update) { runner.tick_runner_queue }
-
- it 'gives 204 and set a new X-GitLab-Last-Update' do
- expect(response).to have_gitlab_http_status(:no_content)
- expect(response.header['X-GitLab-Last-Update']).to eq(new_update)
- end
- end
-
- context 'when beta version is sent' do
- let(:user_agent) { 'gitlab-runner 9.0.0~beta.167.g2b2bacc (master; go1.7.4; linux/amd64)' }
-
- it { expect(response).to have_gitlab_http_status(:no_content) }
- end
-
- context 'when pre-9-0 version is sent' do
- let(:user_agent) { 'gitlab-ci-multi-runner 1.6.0 (1-6-stable; go1.6.3; linux/amd64)' }
-
- it { expect(response).to have_gitlab_http_status(:no_content) }
- end
-
- context 'when pre-9-0 beta version is sent' do
- let(:user_agent) { 'gitlab-ci-multi-runner 1.6.0~beta.167.g2b2bacc (master; go1.6.3; linux/amd64)' }
-
- it { expect(response).to have_gitlab_http_status(:no_content) }
- end
- end
- end
-
- context 'when no token is provided' do
- it 'returns 400 error' do
- post api('/jobs/request')
-
- expect(response).to have_gitlab_http_status(:bad_request)
- end
- end
-
- context 'when invalid token is provided' do
- it 'returns 403 error' do
- post api('/jobs/request'), params: { token: 'invalid' }
-
- expect(response).to have_gitlab_http_status(:forbidden)
- end
- end
-
- context 'when valid token is provided' do
- context 'when Runner is not active' do
- let(:runner) { create(:ci_runner, :inactive) }
- let(:update_value) { runner.ensure_runner_queue_value }
-
- it 'returns 204 error' do
- request_job
-
- expect(response).to have_gitlab_http_status(:no_content)
- expect(response.header['X-GitLab-Last-Update']).to eq(update_value)
- end
- end
-
- context 'when jobs are finished' do
- before do
- job.success
- end
-
- it_behaves_like 'no jobs available'
- end
-
- context 'when other projects have pending jobs' do
- before do
- job.success
- create(:ci_build, :pending)
- end
-
- it_behaves_like 'no jobs available'
- end
-
- context 'when shared runner requests job for project without shared_runners_enabled' do
- let(:runner) { create(:ci_runner, :instance) }
-
- it_behaves_like 'no jobs available'
- end
-
- context 'when there is a pending job' do
- let(:expected_job_info) do
- { 'name' => job.name,
- 'stage' => job.stage,
- 'project_id' => job.project.id,
- 'project_name' => job.project.name }
- end
-
- let(:expected_git_info) do
- { 'repo_url' => job.repo_url,
- 'ref' => job.ref,
- 'sha' => job.sha,
- 'before_sha' => job.before_sha,
- 'ref_type' => 'branch',
- 'refspecs' => ["+refs/pipelines/#{pipeline.id}:refs/pipelines/#{pipeline.id}",
- "+refs/heads/#{job.ref}:refs/remotes/origin/#{job.ref}"],
- 'depth' => project.ci_default_git_depth }
- end
-
- let(:expected_steps) do
- [{ 'name' => 'script',
- 'script' => %w(echo),
- 'timeout' => job.metadata_timeout,
- 'when' => 'on_success',
- 'allow_failure' => false },
- { 'name' => 'after_script',
- 'script' => %w(ls date),
- 'timeout' => job.metadata_timeout,
- 'when' => 'always',
- 'allow_failure' => true }]
- end
-
- let(:expected_variables) do
- [{ 'key' => 'CI_JOB_NAME', 'value' => 'spinach', 'public' => true, 'masked' => false },
- { 'key' => 'CI_JOB_STAGE', 'value' => 'test', 'public' => true, 'masked' => false },
- { 'key' => 'DB_NAME', 'value' => 'postgres', 'public' => true, 'masked' => false }]
- end
-
- let(:expected_artifacts) do
- [{ 'name' => 'artifacts_file',
- 'untracked' => false,
- 'paths' => %w(out/),
- 'when' => 'always',
- 'expire_in' => '7d',
- "artifact_type" => "archive",
- "artifact_format" => "zip" }]
- end
-
- let(:expected_cache) do
- [{ 'key' => 'cache_key',
- 'untracked' => false,
- 'paths' => ['vendor/*'],
- 'policy' => 'pull-push' }]
- end
-
- let(:expected_features) { { 'trace_sections' => true } }
-
- it 'picks a job' do
- request_job info: { platform: :darwin }
-
- expect(response).to have_gitlab_http_status(:created)
- expect(response.headers).not_to have_key('X-GitLab-Last-Update')
- expect(runner.reload.platform).to eq('darwin')
- expect(json_response['id']).to eq(job.id)
- expect(json_response['token']).to eq(job.token)
- expect(json_response['job_info']).to eq(expected_job_info)
- expect(json_response['git_info']).to eq(expected_git_info)
- expect(json_response['image']).to eq({ 'name' => 'ruby:2.7', 'entrypoint' => '/bin/sh', 'ports' => [] })
- expect(json_response['services']).to eq([{ 'name' => 'postgres', 'entrypoint' => nil,
- 'alias' => nil, 'command' => nil, 'ports' => [] },
- { 'name' => 'docker:stable-dind', 'entrypoint' => '/bin/sh',
- 'alias' => 'docker', 'command' => 'sleep 30', 'ports' => [] }])
- expect(json_response['steps']).to eq(expected_steps)
- expect(json_response['artifacts']).to eq(expected_artifacts)
- expect(json_response['cache']).to eq(expected_cache)
- expect(json_response['variables']).to include(*expected_variables)
- expect(json_response['features']).to eq(expected_features)
- end
-
- it 'creates persistent ref' do
- expect_any_instance_of(Ci::PersistentRef).to receive(:create_ref)
- .with(job.sha, "refs/#{Repository::REF_PIPELINES}/#{job.commit_id}")
-
- request_job info: { platform: :darwin }
-
- expect(response).to have_gitlab_http_status(:created)
- expect(json_response['id']).to eq(job.id)
- end
-
- context 'when job is made for tag' do
- let!(:job) { create(:ci_build, :tag, pipeline: pipeline, name: 'spinach', stage: 'test', stage_idx: 0) }
-
- it 'sets branch as ref_type' do
- request_job
-
- expect(response).to have_gitlab_http_status(:created)
- expect(json_response['git_info']['ref_type']).to eq('tag')
- end
-
- context 'when GIT_DEPTH is specified' do
- before do
- create(:ci_pipeline_variable, key: 'GIT_DEPTH', value: 1, pipeline: pipeline)
- end
-
- it 'specifies refspecs' do
- request_job
-
- expect(response).to have_gitlab_http_status(:created)
- expect(json_response['git_info']['refspecs']).to include("+refs/tags/#{job.ref}:refs/tags/#{job.ref}")
- end
- end
-
- context 'when GIT_DEPTH is not specified and there is no default git depth for the project' do
- before do
- project.update!(ci_default_git_depth: nil)
- end
-
- it 'specifies refspecs' do
- request_job
-
- expect(response).to have_gitlab_http_status(:created)
- expect(json_response['git_info']['refspecs'])
- .to contain_exactly("+refs/pipelines/#{pipeline.id}:refs/pipelines/#{pipeline.id}",
- '+refs/tags/*:refs/tags/*',
- '+refs/heads/*:refs/remotes/origin/*')
- end
- end
- end
-
- context 'when job filtered by job_age' do
- let!(:job) { create(:ci_build, :tag, pipeline: pipeline, name: 'spinach', stage: 'test', stage_idx: 0, queued_at: 60.seconds.ago) }
-
- context 'job is queued less than job_age parameter' do
- let(:job_age) { 120 }
-
- it 'gives 204' do
- request_job(job_age: job_age)
-
- expect(response).to have_gitlab_http_status(:no_content)
- end
- end
-
- context 'job is queued more than job_age parameter' do
- let(:job_age) { 30 }
-
- it 'picks a job' do
- request_job(job_age: job_age)
-
- expect(response).to have_gitlab_http_status(:created)
- end
- end
- end
-
- context 'when job is made for branch' do
- it 'sets tag as ref_type' do
- request_job
-
- expect(response).to have_gitlab_http_status(:created)
- expect(json_response['git_info']['ref_type']).to eq('branch')
- end
-
- context 'when GIT_DEPTH is specified' do
- before do
- create(:ci_pipeline_variable, key: 'GIT_DEPTH', value: 1, pipeline: pipeline)
- end
-
- it 'specifies refspecs' do
- request_job
-
- expect(response).to have_gitlab_http_status(:created)
- expect(json_response['git_info']['refspecs']).to include("+refs/heads/#{job.ref}:refs/remotes/origin/#{job.ref}")
- end
- end
-
- context 'when GIT_DEPTH is not specified and there is no default git depth for the project' do
- before do
- project.update!(ci_default_git_depth: nil)
- end
-
- it 'specifies refspecs' do
- request_job
-
- expect(response).to have_gitlab_http_status(:created)
- expect(json_response['git_info']['refspecs'])
- .to contain_exactly("+refs/pipelines/#{pipeline.id}:refs/pipelines/#{pipeline.id}",
- '+refs/tags/*:refs/tags/*',
- '+refs/heads/*:refs/remotes/origin/*')
- end
- end
- end
-
- context 'when job is for a release' do
- let!(:job) { create(:ci_build, :release_options, pipeline: pipeline) }
-
- context 'when `release_steps` is passed by the runner' do
- it 'exposes release info' do
- request_job info: { features: { release_steps: true } }
-
- expect(response).to have_gitlab_http_status(:created)
- expect(response.headers).not_to have_key('X-GitLab-Last-Update')
- expect(json_response['steps']).to eq([
- {
- "name" => "script",
- "script" => ["make changelog | tee release_changelog.txt"],
- "timeout" => 3600,
- "when" => "on_success",
- "allow_failure" => false
- },
- {
- "name" => "release",
- "script" =>
- "release-cli create --ref \"$CI_COMMIT_SHA\" --name \"Release $CI_COMMIT_SHA\" --tag-name \"release-$CI_COMMIT_SHA\" --description \"Created using the release-cli $EXTRA_DESCRIPTION\"",
- "timeout" => 3600,
- "when" => "on_success",
- "allow_failure" => false
- }
- ])
- end
- end
-
- context 'when `release_steps` is not passed by the runner' do
- it 'drops the job' do
- request_job
-
- expect(response).to have_gitlab_http_status(:no_content)
- end
- end
- end
-
- context 'when job is made for merge request' do
- let(:pipeline) { create(:ci_pipeline, source: :merge_request_event, project: project, ref: 'feature', merge_request: merge_request) }
- let!(:job) { create(:ci_build, pipeline: pipeline, name: 'spinach', ref: 'feature', stage: 'test', stage_idx: 0) }
- let(:merge_request) { create(:merge_request) }
-
- it 'sets branch as ref_type' do
- request_job
-
- expect(response).to have_gitlab_http_status(:created)
- expect(json_response['git_info']['ref_type']).to eq('branch')
- end
-
- context 'when GIT_DEPTH is specified' do
- before do
- create(:ci_pipeline_variable, key: 'GIT_DEPTH', value: 1, pipeline: pipeline)
- end
-
- it 'returns the overwritten git depth for merge request refspecs' do
- request_job
-
- expect(response).to have_gitlab_http_status(:created)
- expect(json_response['git_info']['depth']).to eq(1)
- end
- end
- end
-
- it 'updates runner info' do
- expect { request_job }.to change { runner.reload.contacted_at }
- end
-
- %w(version revision platform architecture).each do |param|
- context "when info parameter '#{param}' is present" do
- let(:value) { "#{param}_value" }
-
- it "updates provided Runner's parameter" do
- request_job info: { param => value }
-
- expect(response).to have_gitlab_http_status(:created)
- expect(runner.reload.read_attribute(param.to_sym)).to eq(value)
- end
- end
- end
-
- it "sets the runner's ip_address" do
- post api('/jobs/request'),
- params: { token: runner.token },
- headers: { 'User-Agent' => user_agent, 'X-Forwarded-For' => '123.222.123.222' }
-
- expect(response).to have_gitlab_http_status(:created)
- expect(runner.reload.ip_address).to eq('123.222.123.222')
- end
-
- it "handles multiple X-Forwarded-For addresses" do
- post api('/jobs/request'),
- params: { token: runner.token },
- headers: { 'User-Agent' => user_agent, 'X-Forwarded-For' => '123.222.123.222, 127.0.0.1' }
-
- expect(response).to have_gitlab_http_status(:created)
- expect(runner.reload.ip_address).to eq('123.222.123.222')
- end
-
- context 'when concurrently updating a job' do
- before do
- expect_any_instance_of(Ci::Build).to receive(:run!)
- .and_raise(ActiveRecord::StaleObjectError.new(nil, nil))
- end
-
- it 'returns a conflict' do
- request_job
-
- expect(response).to have_gitlab_http_status(:conflict)
- expect(response.headers).not_to have_key('X-GitLab-Last-Update')
- end
- end
-
- context 'when project and pipeline have multiple jobs' do
- let!(:job) { create(:ci_build, :tag, pipeline: pipeline, name: 'spinach', stage: 'test', stage_idx: 0) }
- let!(:job2) { create(:ci_build, :tag, pipeline: pipeline, name: 'rubocop', stage: 'test', stage_idx: 0) }
- let!(:test_job) { create(:ci_build, pipeline: pipeline, name: 'deploy', stage: 'deploy', stage_idx: 1) }
-
- before do
- job.success
- job2.success
- end
-
- it 'returns dependent jobs' do
- request_job
-
- expect(response).to have_gitlab_http_status(:created)
- expect(json_response['id']).to eq(test_job.id)
- expect(json_response['dependencies'].count).to eq(2)
- expect(json_response['dependencies']).to include(
- { 'id' => job.id, 'name' => job.name, 'token' => job.token },
- { 'id' => job2.id, 'name' => job2.name, 'token' => job2.token })
- end
- end
-
- context 'when pipeline have jobs with artifacts' do
- let!(:job) { create(:ci_build, :tag, :artifacts, pipeline: pipeline, name: 'spinach', stage: 'test', stage_idx: 0) }
- let!(:test_job) { create(:ci_build, pipeline: pipeline, name: 'deploy', stage: 'deploy', stage_idx: 1) }
-
- before do
- job.success
- end
-
- it 'returns dependent jobs' do
- request_job
-
- expect(response).to have_gitlab_http_status(:created)
- expect(json_response['id']).to eq(test_job.id)
- expect(json_response['dependencies'].count).to eq(1)
- expect(json_response['dependencies']).to include(
- { 'id' => job.id, 'name' => job.name, 'token' => job.token,
- 'artifacts_file' => { 'filename' => 'ci_build_artifacts.zip', 'size' => 107464 } })
- end
- end
-
- context 'when explicit dependencies are defined' do
- let!(:job) { create(:ci_build, :tag, pipeline: pipeline, name: 'spinach', stage: 'test', stage_idx: 0) }
- let!(:job2) { create(:ci_build, :tag, pipeline: pipeline, name: 'rubocop', stage: 'test', stage_idx: 0) }
- let!(:test_job) do
- create(:ci_build, pipeline: pipeline, token: 'test-job-token', name: 'deploy',
- stage: 'deploy', stage_idx: 1,
- options: { script: ['bash'], dependencies: [job2.name] })
- end
-
- before do
- job.success
- job2.success
- end
-
- it 'returns dependent jobs' do
- request_job
-
- expect(response).to have_gitlab_http_status(:created)
- expect(json_response['id']).to eq(test_job.id)
- expect(json_response['dependencies'].count).to eq(1)
- expect(json_response['dependencies'][0]).to include('id' => job2.id, 'name' => job2.name, 'token' => job2.token)
- end
- end
-
- context 'when dependencies is an empty array' do
- let!(:job) { create(:ci_build, :tag, pipeline: pipeline, name: 'spinach', stage: 'test', stage_idx: 0) }
- let!(:job2) { create(:ci_build, :tag, pipeline: pipeline, name: 'rubocop', stage: 'test', stage_idx: 0) }
- let!(:empty_dependencies_job) do
- create(:ci_build, pipeline: pipeline, token: 'test-job-token', name: 'empty_dependencies_job',
- stage: 'deploy', stage_idx: 1,
- options: { script: ['bash'], dependencies: [] })
- end
-
- before do
- job.success
- job2.success
- end
-
- it 'returns an empty array' do
- request_job
-
- expect(response).to have_gitlab_http_status(:created)
- expect(json_response['id']).to eq(empty_dependencies_job.id)
- expect(json_response['dependencies'].count).to eq(0)
- end
- end
-
- context 'when job has no tags' do
- before do
- job.update(tags: [])
- end
-
- context 'when runner is allowed to pick untagged jobs' do
- before do
- runner.update_column(:run_untagged, true)
- end
-
- it 'picks job' do
- request_job
-
- expect(response).to have_gitlab_http_status(:created)
- end
- end
-
- context 'when runner is not allowed to pick untagged jobs' do
- before do
- runner.update_column(:run_untagged, false)
- end
-
- it_behaves_like 'no jobs available'
- end
- end
-
- context 'when triggered job is available' do
- let(:expected_variables) do
- [{ 'key' => 'CI_JOB_NAME', 'value' => 'spinach', 'public' => true, 'masked' => false },
- { 'key' => 'CI_JOB_STAGE', 'value' => 'test', 'public' => true, 'masked' => false },
- { 'key' => 'CI_PIPELINE_TRIGGERED', 'value' => 'true', 'public' => true, 'masked' => false },
- { 'key' => 'DB_NAME', 'value' => 'postgres', 'public' => true, 'masked' => false },
- { 'key' => 'SECRET_KEY', 'value' => 'secret_value', 'public' => false, 'masked' => false },
- { 'key' => 'TRIGGER_KEY_1', 'value' => 'TRIGGER_VALUE_1', 'public' => false, 'masked' => false }]
- end
-
- let(:trigger) { create(:ci_trigger, project: project) }
- let!(:trigger_request) { create(:ci_trigger_request, pipeline: pipeline, builds: [job], trigger: trigger) }
-
- before do
- project.variables << Ci::Variable.new(key: 'SECRET_KEY', value: 'secret_value')
- end
-
- shared_examples 'expected variables behavior' do
- it 'returns variables for triggers' do
- request_job
-
- expect(response).to have_gitlab_http_status(:created)
- expect(json_response['variables']).to include(*expected_variables)
- end
- end
-
- context 'when variables are stored in trigger_request' do
- before do
- trigger_request.update_attribute(:variables, { TRIGGER_KEY_1: 'TRIGGER_VALUE_1' } )
- end
-
- it_behaves_like 'expected variables behavior'
- end
-
- context 'when variables are stored in pipeline_variables' do
- before do
- create(:ci_pipeline_variable, pipeline: pipeline, key: :TRIGGER_KEY_1, value: 'TRIGGER_VALUE_1')
- end
-
- it_behaves_like 'expected variables behavior'
- end
- end
-
- describe 'registry credentials support' do
- let(:registry_url) { 'registry.example.com:5005' }
- let(:registry_credentials) do
- { 'type' => 'registry',
- 'url' => registry_url,
- 'username' => 'gitlab-ci-token',
- 'password' => job.token }
- end
-
- context 'when registry is enabled' do
- before do
- stub_container_registry_config(enabled: true, host_port: registry_url)
- end
-
- it 'sends registry credentials key' do
- request_job
-
- expect(json_response).to have_key('credentials')
- expect(json_response['credentials']).to include(registry_credentials)
- end
- end
-
- context 'when registry is disabled' do
- before do
- stub_container_registry_config(enabled: false, host_port: registry_url)
- end
-
- it 'does not send registry credentials' do
- request_job
-
- expect(json_response).to have_key('credentials')
- expect(json_response['credentials']).not_to include(registry_credentials)
- end
- end
- end
-
- describe 'timeout support' do
- context 'when project specifies job timeout' do
- let(:project) { create(:project, shared_runners_enabled: false, build_timeout: 1234) }
-
- it 'contains info about timeout taken from project' do
- request_job
-
- expect(response).to have_gitlab_http_status(:created)
- expect(json_response['runner_info']).to include({ 'timeout' => 1234 })
- end
-
- context 'when runner specifies lower timeout' do
- let(:runner) { create(:ci_runner, :project, maximum_timeout: 1000, projects: [project]) }
-
- it 'contains info about timeout overridden by runner' do
- request_job
-
- expect(response).to have_gitlab_http_status(:created)
- expect(json_response['runner_info']).to include({ 'timeout' => 1000 })
- end
- end
-
- context 'when runner specifies bigger timeout' do
- let(:runner) { create(:ci_runner, :project, maximum_timeout: 2000, projects: [project]) }
-
- it 'contains info about timeout not overridden by runner' do
- request_job
-
- expect(response).to have_gitlab_http_status(:created)
- expect(json_response['runner_info']).to include({ 'timeout' => 1234 })
- end
- end
- end
- end
- end
-
- describe 'port support' do
- let(:job) { create(:ci_build, pipeline: pipeline, options: options) }
-
- context 'when job image has ports' do
- let(:options) do
- {
- image: {
- name: 'ruby',
- ports: [80]
- },
- services: ['mysql']
- }
- end
-
- it 'returns the image ports' do
- request_job
-
- expect(response).to have_gitlab_http_status(:created)
- expect(json_response).to include(
- 'id' => job.id,
- 'image' => a_hash_including('name' => 'ruby', 'ports' => [{ 'number' => 80, 'protocol' => 'http', 'name' => 'default_port' }]),
- 'services' => all(a_hash_including('name' => 'mysql')))
- end
- end
-
- context 'when job services settings has ports' do
- let(:options) do
- {
- image: 'ruby',
- services: [
- {
- name: 'tomcat',
- ports: [{ number: 8081, protocol: 'http', name: 'custom_port' }]
- }
- ]
- }
- end
-
- it 'returns the service ports' do
- request_job
-
- expect(response).to have_gitlab_http_status(:created)
- expect(json_response).to include(
- 'id' => job.id,
- 'image' => a_hash_including('name' => 'ruby'),
- 'services' => all(a_hash_including('name' => 'tomcat', 'ports' => [{ 'number' => 8081, 'protocol' => 'http', 'name' => 'custom_port' }])))
- end
- end
- end
-
- describe 'a job with excluded artifacts' do
- context 'when excluded paths are defined' do
- let(:job) do
- create(:ci_build, pipeline: pipeline, token: 'test-job-token', name: 'test',
- stage: 'deploy', stage_idx: 1,
- options: { artifacts: { paths: ['abc'], exclude: ['cde'] } })
- end
-
- context 'when a runner supports this feature' do
- it 'exposes excluded paths when the feature is enabled' do
- stub_feature_flags(ci_artifacts_exclude: true)
-
- request_job info: { features: { artifacts_exclude: true } }
-
- expect(response).to have_gitlab_http_status(:created)
- expect(json_response.dig('artifacts').first).to include('exclude' => ['cde'])
- end
-
- it 'does not expose excluded paths when the feature is disabled' do
- stub_feature_flags(ci_artifacts_exclude: false)
-
- request_job info: { features: { artifacts_exclude: true } }
-
- expect(response).to have_gitlab_http_status(:created)
- expect(json_response.dig('artifacts').first).not_to have_key('exclude')
- end
- end
-
- context 'when a runner does not support this feature' do
- it 'does not expose the build at all' do
- stub_feature_flags(ci_artifacts_exclude: true)
-
- request_job
-
- expect(response).to have_gitlab_http_status(:no_content)
- end
- end
- end
-
- it 'does not expose excluded paths when these are empty' do
- request_job
-
- expect(response).to have_gitlab_http_status(:created)
- expect(json_response.dig('artifacts').first).not_to have_key('exclude')
- end
- end
-
- def request_job(token = runner.token, **params)
- new_params = params.merge(token: token, last_update: last_update)
- post api('/jobs/request'), params: new_params, headers: { 'User-Agent' => user_agent }
- end
- end
-
- context 'for web-ide job' do
- let_it_be(:user) { create(:user) }
- let_it_be(:project) { create(:project, :repository) }
-
- let(:runner) { create(:ci_runner, :project, projects: [project]) }
- let(:service) { Ci::CreateWebIdeTerminalService.new(project, user, ref: 'master').execute }
- let(:pipeline) { service[:pipeline] }
- let(:build) { pipeline.builds.first }
- let(:job) { {} }
- let(:config_content) do
- 'terminal: { image: ruby, services: [mysql], before_script: [ls], tags: [tag-1], variables: { KEY: value } }'
- end
-
- before do
- stub_webide_config_file(config_content)
- project.add_maintainer(user)
-
- pipeline
- end
-
- context 'when runner has matching tag' do
- before do
- runner.update!(tag_list: ['tag-1'])
- end
-
- it 'successfully picks job' do
- request_job
-
- build.reload
-
- expect(build).to be_running
- expect(build.runner).to eq(runner)
-
- expect(response).to have_gitlab_http_status(:created)
- expect(json_response).to include(
- "id" => build.id,
- "variables" => include("key" => 'KEY', "value" => 'value', "public" => true, "masked" => false),
- "image" => a_hash_including("name" => 'ruby'),
- "services" => all(a_hash_including("name" => 'mysql')),
- "job_info" => a_hash_including("name" => 'terminal', "stage" => 'terminal'))
- end
- end
-
- context 'when runner does not have matching tags' do
- it 'does not pick a job' do
- request_job
-
- build.reload
-
- expect(build).to be_pending
- expect(response).to have_gitlab_http_status(:no_content)
- end
- end
-
- def request_job(token = runner.token, **params)
- post api('/jobs/request'), params: params.merge(token: token)
- end
- end
- end
-
- describe 'PUT /api/v4/jobs/:id' do
- let(:job) do
- create(:ci_build, :pending, :trace_live, pipeline: pipeline, project: project, user: user, runner_id: runner.id)
- end
-
- before do
- job.run!
- end
-
- it_behaves_like 'application context metadata', '/api/:version/jobs/:id' do
- let(:send_request) { update_job(state: 'success') }
- end
-
- it 'updates runner info' do
- expect { update_job(state: 'success') }.to change { runner.reload.contacted_at }
- end
-
- context 'when status is given' do
- it 'mark job as succeeded' do
- update_job(state: 'success')
-
- job.reload
- expect(job).to be_success
- end
-
- it 'mark job as failed' do
- update_job(state: 'failed')
-
- job.reload
- expect(job).to be_failed
- expect(job).to be_unknown_failure
- end
-
- context 'when failure_reason is script_failure' do
- before do
- update_job(state: 'failed', failure_reason: 'script_failure')
- job.reload
- end
-
- it { expect(job).to be_script_failure }
- end
-
- context 'when failure_reason is runner_system_failure' do
- before do
- update_job(state: 'failed', failure_reason: 'runner_system_failure')
- job.reload
- end
-
- it { expect(job).to be_runner_system_failure }
- end
-
- context 'when failure_reason is unrecognized value' do
- before do
- update_job(state: 'failed', failure_reason: 'what_is_this')
- job.reload
- end
-
- it { expect(job).to be_unknown_failure }
- end
-
- context 'when failure_reason is job_execution_timeout' do
- before do
- update_job(state: 'failed', failure_reason: 'job_execution_timeout')
- job.reload
- end
-
- it { expect(job).to be_job_execution_timeout }
- end
-
- context 'when failure_reason is unmet_prerequisites' do
- before do
- update_job(state: 'failed', failure_reason: 'unmet_prerequisites')
- job.reload
- end
-
- it { expect(job).to be_unmet_prerequisites }
- end
- end
-
- context 'when trace is given' do
- it 'creates a trace artifact' do
- allow(BuildFinishedWorker).to receive(:perform_async).with(job.id) do
- ArchiveTraceWorker.new.perform(job.id)
- end
-
- update_job(state: 'success', trace: 'BUILD TRACE UPDATED')
-
- job.reload
- expect(response).to have_gitlab_http_status(:ok)
- expect(job.trace.raw).to eq 'BUILD TRACE UPDATED'
- expect(job.job_artifacts_trace.open.read).to eq 'BUILD TRACE UPDATED'
- end
-
- context 'when concurrent update of trace is happening' do
- before do
- job.trace.write('wb') do
- update_job(state: 'success', trace: 'BUILD TRACE UPDATED')
- end
- end
-
- it 'returns that operation conflicts' do
- expect(response).to have_gitlab_http_status(:conflict)
- end
- end
- end
-
- context 'when no trace is given' do
- it 'does not override trace information' do
- update_job
-
- expect(job.reload.trace.raw).to eq 'BUILD TRACE'
- end
-
- context 'when running state is sent' do
- it 'updates update_at value' do
- expect { update_job_after_time }.to change { job.reload.updated_at }
- end
- end
-
- context 'when other state is sent' do
- it "doesn't update update_at value" do
- expect { update_job_after_time(20.minutes, state: 'success') }.not_to change { job.reload.updated_at }
- end
- end
- end
-
- context 'when job has been erased' do
- let(:job) { create(:ci_build, runner_id: runner.id, erased_at: Time.now) }
-
- it 'responds with forbidden' do
- update_job
-
- expect(response).to have_gitlab_http_status(:forbidden)
- end
- end
-
- context 'when job has already been finished' do
- before do
- job.trace.set('Job failed')
- job.drop!(:script_failure)
- end
-
- it 'does not update job status and job trace' do
- update_job(state: 'success', trace: 'BUILD TRACE UPDATED')
-
- job.reload
- expect(response).to have_gitlab_http_status(:forbidden)
- expect(response.header['Job-Status']).to eq 'failed'
- expect(job.trace.raw).to eq 'Job failed'
- expect(job).to be_failed
- end
- end
-
- def update_job(token = job.token, **params)
- new_params = params.merge(token: token)
- put api("/jobs/#{job.id}"), params: new_params
- end
-
- def update_job_after_time(update_interval = 20.minutes, state = 'running')
- Timecop.travel(job.updated_at + update_interval) do
- update_job(job.token, state: state)
- end
- end
- end
-
- describe 'PATCH /api/v4/jobs/:id/trace' do
- let(:job) do
- create(:ci_build, :running, :trace_live,
- project: project, user: user, runner_id: runner.id, pipeline: pipeline)
- end
- let(:headers) { { API::Helpers::Runner::JOB_TOKEN_HEADER => job.token, 'Content-Type' => 'text/plain' } }
- let(:headers_with_range) { headers.merge({ 'Content-Range' => '11-20' }) }
- let(:update_interval) { 10.seconds.to_i }
-
- before do
- initial_patch_the_trace
- end
-
- it_behaves_like 'application context metadata', '/api/:version/jobs/:id/trace' do
- let(:send_request) { patch_the_trace }
- end
-
- it 'updates runner info' do
- runner.update!(contacted_at: 1.year.ago)
-
- expect { patch_the_trace }.to change { runner.reload.contacted_at }
- end
-
- context 'when request is valid' do
- it 'gets correct response' do
- expect(response).to have_gitlab_http_status(:accepted)
- expect(job.reload.trace.raw).to eq 'BUILD TRACE appended'
- expect(response.header).to have_key 'Range'
- expect(response.header).to have_key 'Job-Status'
- expect(response.header).to have_key 'X-GitLab-Trace-Update-Interval'
- end
-
- context 'when job has been updated recently' do
- it { expect { patch_the_trace }.not_to change { job.updated_at }}
-
- it "changes the job's trace" do
- patch_the_trace
-
- expect(job.reload.trace.raw).to eq 'BUILD TRACE appended appended'
- end
-
- context 'when Runner makes a force-patch' do
- it { expect { force_patch_the_trace }.not_to change { job.updated_at }}
-
- it "doesn't change the build.trace" do
- force_patch_the_trace
-
- expect(job.reload.trace.raw).to eq 'BUILD TRACE appended'
- end
- end
- end
-
- context 'when job was not updated recently' do
- let(:update_interval) { 15.minutes.to_i }
-
- it { expect { patch_the_trace }.to change { job.updated_at } }
-
- it 'changes the job.trace' do
- patch_the_trace
-
- expect(job.reload.trace.raw).to eq 'BUILD TRACE appended appended'
- end
-
- context 'when Runner makes a force-patch' do
- it { expect { force_patch_the_trace }.to change { job.updated_at } }
-
- it "doesn't change the job.trace" do
- force_patch_the_trace
-
- expect(job.reload.trace.raw).to eq 'BUILD TRACE appended'
- end
- end
- end
-
- context 'when project for the build has been deleted' do
- let(:job) do
- create(:ci_build, :running, :trace_live, runner_id: runner.id, pipeline: pipeline) do |job|
- job.project.update(pending_delete: true)
- end
- end
-
- it 'responds with forbidden' do
- expect(response).to have_gitlab_http_status(:forbidden)
- end
- end
-
- context 'when trace is patched' do
- before do
- patch_the_trace
- end
-
- it 'has valid trace' do
- expect(response).to have_gitlab_http_status(:accepted)
- expect(job.reload.trace.raw).to eq 'BUILD TRACE appended appended'
- end
-
- context 'when job is cancelled' do
- before do
- job.cancel
- end
-
- context 'when trace is patched' do
- before do
- patch_the_trace
- end
-
- it 'returns Forbidden ' do
- expect(response).to have_gitlab_http_status(:forbidden)
- end
- end
- end
-
- context 'when redis data are flushed' do
- before do
- redis_shared_state_cleanup!
- end
-
- it 'has empty trace' do
- expect(job.reload.trace.raw).to eq ''
- end
-
- context 'when we perform partial patch' do
- before do
- patch_the_trace('hello', headers.merge({ 'Content-Range' => "28-32/5" }))
- end
-
- it 'returns an error' do
- expect(response).to have_gitlab_http_status(:range_not_satisfiable)
- expect(response.header['Range']).to eq('0-0')
- end
- end
-
- context 'when we resend full trace' do
- before do
- patch_the_trace('BUILD TRACE appended appended hello', headers.merge({ 'Content-Range' => "0-34/35" }))
- end
-
- it 'succeeds with updating trace' do
- expect(response).to have_gitlab_http_status(:accepted)
- expect(job.reload.trace.raw).to eq 'BUILD TRACE appended appended hello'
- end
- end
- end
- end
-
- context 'when concurrent update of trace is happening' do
- before do
- job.trace.write('wb') do
- patch_the_trace
- end
- end
-
- it 'returns that operation conflicts' do
- expect(response).to have_gitlab_http_status(:conflict)
- end
- end
-
- context 'when the job is canceled' do
- before do
- job.cancel
- patch_the_trace
- end
-
- it 'receives status in header' do
- expect(response.header['Job-Status']).to eq 'canceled'
- end
- end
-
- context 'when build trace is being watched' do
- before do
- job.trace.being_watched!
- end
-
- it 'returns X-GitLab-Trace-Update-Interval as 3' do
- patch_the_trace
-
- expect(response).to have_gitlab_http_status(:accepted)
- expect(response.header['X-GitLab-Trace-Update-Interval']).to eq('3')
- end
- end
-
- context 'when build trace is not being watched' do
- it 'returns X-GitLab-Trace-Update-Interval as 30' do
- patch_the_trace
-
- expect(response).to have_gitlab_http_status(:accepted)
- expect(response.header['X-GitLab-Trace-Update-Interval']).to eq('30')
- end
- end
- end
-
- context 'when Runner makes a force-patch' do
- before do
- force_patch_the_trace
- end
-
- it 'gets correct response' do
- expect(response).to have_gitlab_http_status(:accepted)
- expect(job.reload.trace.raw).to eq 'BUILD TRACE appended'
- expect(response.header).to have_key 'Range'
- expect(response.header).to have_key 'Job-Status'
- end
- end
-
- context 'when content-range start is too big' do
- let(:headers_with_range) { headers.merge({ 'Content-Range' => '15-20/6' }) }
-
- it 'gets 416 error response with range headers' do
- expect(response).to have_gitlab_http_status(:range_not_satisfiable)
- expect(response.header).to have_key 'Range'
- expect(response.header['Range']).to eq '0-11'
- end
- end
-
- context 'when content-range start is too small' do
- let(:headers_with_range) { headers.merge({ 'Content-Range' => '8-20/13' }) }
-
- it 'gets 416 error response with range headers' do
- expect(response).to have_gitlab_http_status(:range_not_satisfiable)
- expect(response.header).to have_key 'Range'
- expect(response.header['Range']).to eq '0-11'
- end
- end
-
- context 'when Content-Range header is missing' do
- let(:headers_with_range) { headers }
-
- it { expect(response).to have_gitlab_http_status(:bad_request) }
- end
-
- context 'when job has been errased' do
- let(:job) { create(:ci_build, runner_id: runner.id, erased_at: Time.now) }
-
- it { expect(response).to have_gitlab_http_status(:forbidden) }
- end
-
- def patch_the_trace(content = ' appended', request_headers = nil)
- unless request_headers
- job.trace.read do |stream|
- offset = stream.size
- limit = offset + content.length - 1
- request_headers = headers.merge({ 'Content-Range' => "#{offset}-#{limit}" })
- end
- end
-
- Timecop.travel(job.updated_at + update_interval) do
- patch api("/jobs/#{job.id}/trace"), params: content, headers: request_headers
- job.reload
- end
- end
-
- def initial_patch_the_trace
- patch_the_trace(' appended', headers_with_range)
- end
-
- def force_patch_the_trace
- 2.times { patch_the_trace('') }
- end
- end
-
- describe 'artifacts' do
- let(:job) { create(:ci_build, :pending, user: user, project: project, pipeline: pipeline, runner_id: runner.id) }
- let(:jwt) { JWT.encode({ 'iss' => 'gitlab-workhorse' }, Gitlab::Workhorse.secret, 'HS256') }
- let(:headers) { { 'GitLab-Workhorse' => '1.0', Gitlab::Workhorse::INTERNAL_API_REQUEST_HEADER => jwt } }
- let(:headers_with_token) { headers.merge(API::Helpers::Runner::JOB_TOKEN_HEADER => job.token) }
- let(:file_upload) { fixture_file_upload('spec/fixtures/banana_sample.gif', 'image/gif') }
- let(:file_upload2) { fixture_file_upload('spec/fixtures/dk.png', 'image/gif') }
-
- before do
- stub_artifacts_object_storage
- job.run!
- end
-
- describe 'POST /api/v4/jobs/:id/artifacts/authorize' do
- context 'when using token as parameter' do
- context 'posting artifacts to running job' do
- subject do
- authorize_artifacts_with_token_in_params
- end
-
- it_behaves_like 'application context metadata', '/api/:version/jobs/:id/artifacts/authorize' do
- let(:send_request) { subject }
- end
-
- it 'updates runner info' do
- expect { subject }.to change { runner.reload.contacted_at }
- end
-
- shared_examples 'authorizes local file' do
- it 'succeeds' do
- subject
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(response.media_type).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE)
- expect(json_response['TempPath']).to eq(JobArtifactUploader.workhorse_local_upload_path)
- expect(json_response['RemoteObject']).to be_nil
- end
- end
-
- context 'when using local storage' do
- it_behaves_like 'authorizes local file'
- end
-
- context 'when using remote storage' do
- context 'when direct upload is enabled' do
- before do
- stub_artifacts_object_storage(enabled: true, direct_upload: true)
- end
-
- it 'succeeds' do
- subject
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(response.media_type).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE)
- expect(json_response).not_to have_key('TempPath')
- expect(json_response['RemoteObject']).to have_key('ID')
- expect(json_response['RemoteObject']).to have_key('GetURL')
- expect(json_response['RemoteObject']).to have_key('StoreURL')
- expect(json_response['RemoteObject']).to have_key('DeleteURL')
- expect(json_response['RemoteObject']).to have_key('MultipartUpload')
- end
- end
-
- context 'when direct upload is disabled' do
- before do
- stub_artifacts_object_storage(enabled: true, direct_upload: false)
- end
-
- it_behaves_like 'authorizes local file'
- end
- end
- end
-
- context 'when artifact is too large' do
- let(:sample_max_size) { 100 }
-
- shared_examples_for 'rejecting too large artifacts' do
- it 'fails to post' do
- authorize_artifacts_with_token_in_params(filesize: sample_max_size.megabytes.to_i)
-
- expect(response).to have_gitlab_http_status(:payload_too_large)
- end
- end
-
- context 'based on application setting' do
- before do
- stub_application_setting(max_artifacts_size: sample_max_size)
- end
-
- it_behaves_like 'rejecting too large artifacts'
- end
-
- context 'based on root namespace setting' do
- before do
- stub_application_setting(max_artifacts_size: 200)
- root_namespace.update!(max_artifacts_size: sample_max_size)
- end
-
- it_behaves_like 'rejecting too large artifacts'
- end
-
- context 'based on child namespace setting' do
- before do
- stub_application_setting(max_artifacts_size: 200)
- root_namespace.update!(max_artifacts_size: 200)
- namespace.update!(max_artifacts_size: sample_max_size)
- end
-
- it_behaves_like 'rejecting too large artifacts'
- end
-
- context 'based on project setting' do
- before do
- stub_application_setting(max_artifacts_size: 200)
- root_namespace.update!(max_artifacts_size: 200)
- namespace.update!(max_artifacts_size: 200)
- project.update!(max_artifacts_size: sample_max_size)
- end
-
- it_behaves_like 'rejecting too large artifacts'
- end
- end
- end
-
- context 'when using token as header' do
- it 'authorizes posting artifacts to running job' do
- authorize_artifacts_with_token_in_headers
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(response.media_type).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE)
- expect(json_response['TempPath']).not_to be_nil
- end
-
- it 'fails to post too large artifact' do
- stub_application_setting(max_artifacts_size: 0)
-
- authorize_artifacts_with_token_in_headers(filesize: 100)
-
- expect(response).to have_gitlab_http_status(:payload_too_large)
- end
- end
-
- context 'when using runners token' do
- it 'fails to authorize artifacts posting' do
- authorize_artifacts(token: job.project.runners_token)
-
- expect(response).to have_gitlab_http_status(:forbidden)
- end
- end
-
- it 'reject requests that did not go through gitlab-workhorse' do
- headers.delete(Gitlab::Workhorse::INTERNAL_API_REQUEST_HEADER)
-
- authorize_artifacts
-
- expect(response).to have_gitlab_http_status(:forbidden)
- end
-
- context 'authorization token is invalid' do
- it 'responds with forbidden' do
- authorize_artifacts(token: 'invalid', filesize: 100 )
-
- expect(response).to have_gitlab_http_status(:forbidden)
- end
- end
-
- context 'authorize uploading of an lsif artifact' do
- before do
- stub_feature_flags(code_navigation: job.project)
- end
-
- it 'adds ProcessLsif header' do
- authorize_artifacts_with_token_in_headers(artifact_type: :lsif)
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(json_response['ProcessLsif']).to be_truthy
- end
-
- it 'fails to authorize too large artifact' do
- authorize_artifacts_with_token_in_headers(artifact_type: :lsif, filesize: 30.megabytes)
-
- expect(response).to have_gitlab_http_status(:payload_too_large)
- end
-
- context 'code_navigation feature flag is disabled' do
- it 'does not add ProcessLsif header' do
- stub_feature_flags(code_navigation: false)
-
- authorize_artifacts_with_token_in_headers(artifact_type: :lsif)
-
- expect(response).to have_gitlab_http_status(:forbidden)
- end
- end
- end
-
- def authorize_artifacts(params = {}, request_headers = headers)
- post api("/jobs/#{job.id}/artifacts/authorize"), params: params, headers: request_headers
- end
-
- def authorize_artifacts_with_token_in_params(params = {}, request_headers = headers)
- params = params.merge(token: job.token)
- authorize_artifacts(params, request_headers)
- end
-
- def authorize_artifacts_with_token_in_headers(params = {}, request_headers = headers_with_token)
- authorize_artifacts(params, request_headers)
- end
- end
-
- describe 'POST /api/v4/jobs/:id/artifacts' do
- it_behaves_like 'application context metadata', '/api/:version/jobs/:id/artifacts' do
- let(:send_request) do
- upload_artifacts(file_upload, headers_with_token)
- end
- end
-
- it 'updates runner info' do
- expect { upload_artifacts(file_upload, headers_with_token) }.to change { runner.reload.contacted_at }
- end
-
- context 'when artifacts are being stored inside of tmp path' do
- before do
- # by configuring this path we allow to pass temp file from any path
- allow(JobArtifactUploader).to receive(:workhorse_upload_path).and_return('/')
- end
-
- context 'when job has been erased' do
- let(:job) { create(:ci_build, erased_at: Time.now) }
-
- before do
- upload_artifacts(file_upload, headers_with_token)
- end
-
- it 'responds with forbidden' do
- upload_artifacts(file_upload, headers_with_token)
-
- expect(response).to have_gitlab_http_status(:forbidden)
- end
- end
-
- context 'when job is running' do
- shared_examples 'successful artifacts upload' do
- it 'updates successfully' do
- expect(response).to have_gitlab_http_status(:created)
- end
- end
-
- context 'when uses accelerated file post' do
- context 'for file stored locally' do
- before do
- upload_artifacts(file_upload, headers_with_token)
- end
-
- it_behaves_like 'successful artifacts upload'
- end
-
- context 'for file stored remotely' do
- let!(:fog_connection) do
- stub_artifacts_object_storage(direct_upload: true)
- end
- let(:object) do
- fog_connection.directories.new(key: 'artifacts').files.create(
- key: 'tmp/uploads/12312300',
- body: 'content'
- )
- end
- let(:file_upload) { fog_to_uploaded_file(object) }
-
- before do
- upload_artifacts(file_upload, headers_with_token, 'file.remote_id' => remote_id)
- end
-
- context 'when valid remote_id is used' do
- let(:remote_id) { '12312300' }
-
- it_behaves_like 'successful artifacts upload'
- end
-
- context 'when invalid remote_id is used' do
- let(:remote_id) { 'invalid id' }
-
- it 'responds with bad request' do
- expect(response).to have_gitlab_http_status(:internal_server_error)
- expect(json_response['message']).to eq("Missing file")
- end
- end
- end
- end
-
- context 'when using runners token' do
- it 'responds with forbidden' do
- upload_artifacts(file_upload, headers.merge(API::Helpers::Runner::JOB_TOKEN_HEADER => job.project.runners_token))
-
- expect(response).to have_gitlab_http_status(:forbidden)
- end
- end
- end
-
- context 'when artifacts file is too large' do
- it 'fails to post too large artifact' do
- stub_application_setting(max_artifacts_size: 0)
-
- upload_artifacts(file_upload, headers_with_token)
-
- expect(response).to have_gitlab_http_status(:payload_too_large)
- end
- end
-
- context 'when artifacts post request does not contain file' do
- it 'fails to post artifacts without file' do
- post api("/jobs/#{job.id}/artifacts"), params: {}, headers: headers_with_token
-
- expect(response).to have_gitlab_http_status(:bad_request)
- end
- end
-
- context 'GitLab Workhorse is not configured' do
- it 'fails to post artifacts without GitLab-Workhorse' do
- post api("/jobs/#{job.id}/artifacts"), params: { token: job.token }, headers: {}
-
- expect(response).to have_gitlab_http_status(:bad_request)
- end
- end
-
- context 'Is missing GitLab Workhorse token headers' do
- let(:jwt) { JWT.encode({ 'iss' => 'invalid-header' }, Gitlab::Workhorse.secret, 'HS256') }
-
- it 'fails to post artifacts without GitLab-Workhorse' do
- expect(Gitlab::ErrorTracking).to receive(:track_exception).once
-
- upload_artifacts(file_upload, headers_with_token)
-
- expect(response).to have_gitlab_http_status(:forbidden)
- end
- end
-
- context 'when setting an expire date' do
- let(:default_artifacts_expire_in) {}
- let(:post_data) do
- { file: file_upload,
- expire_in: expire_in }
- end
-
- before do
- stub_application_setting(default_artifacts_expire_in: default_artifacts_expire_in)
-
- upload_artifacts(file_upload, headers_with_token, post_data)
- end
-
- context 'when an expire_in is given' do
- let(:expire_in) { '7 days' }
-
- it 'updates when specified' do
- expect(response).to have_gitlab_http_status(:created)
- expect(job.reload.artifacts_expire_at).to be_within(5.minutes).of(7.days.from_now)
- end
- end
-
- context 'when no expire_in is given' do
- let(:expire_in) { nil }
-
- it 'ignores if not specified' do
- expect(response).to have_gitlab_http_status(:created)
- expect(job.reload.artifacts_expire_at).to be_nil
- end
-
- context 'with application default' do
- context 'when default is 5 days' do
- let(:default_artifacts_expire_in) { '5 days' }
-
- it 'sets to application default' do
- expect(response).to have_gitlab_http_status(:created)
- expect(job.reload.artifacts_expire_at).to be_within(5.minutes).of(5.days.from_now)
- end
- end
-
- context 'when default is 0' do
- let(:default_artifacts_expire_in) { '0' }
-
- it 'does not set expire_in' do
- expect(response).to have_gitlab_http_status(:created)
- expect(job.reload.artifacts_expire_at).to be_nil
- end
- end
- end
- end
- end
-
- context 'posts artifacts file and metadata file' do
- let!(:artifacts) { file_upload }
- let!(:artifacts_sha256) { Digest::SHA256.file(artifacts.path).hexdigest }
- let!(:metadata) { file_upload2 }
- let!(:metadata_sha256) { Digest::SHA256.file(metadata.path).hexdigest }
-
- let(:stored_artifacts_file) { job.reload.artifacts_file }
- let(:stored_metadata_file) { job.reload.artifacts_metadata }
- let(:stored_artifacts_size) { job.reload.artifacts_size }
- let(:stored_artifacts_sha256) { job.reload.job_artifacts_archive.file_sha256 }
- let(:stored_metadata_sha256) { job.reload.job_artifacts_metadata.file_sha256 }
- let(:file_keys) { post_data.keys }
- let(:send_rewritten_field) { true }
-
- before do
- workhorse_finalize_with_multiple_files(
- api("/jobs/#{job.id}/artifacts"),
- method: :post,
- file_keys: file_keys,
- params: post_data,
- headers: headers_with_token,
- send_rewritten_field: send_rewritten_field
- )
- end
-
- context 'when posts data accelerated by workhorse is correct' do
- let(:post_data) { { file: artifacts, metadata: metadata } }
-
- it 'stores artifacts and artifacts metadata' do
- expect(response).to have_gitlab_http_status(:created)
- expect(stored_artifacts_file.filename).to eq(artifacts.original_filename)
- expect(stored_metadata_file.filename).to eq(metadata.original_filename)
- expect(stored_artifacts_size).to eq(artifacts.size)
- expect(stored_artifacts_sha256).to eq(artifacts_sha256)
- expect(stored_metadata_sha256).to eq(metadata_sha256)
- end
- end
-
- context 'with a malicious file.path param' do
- let(:post_data) { {} }
- let(:tmp_file) { Tempfile.new('crafted.file.path') }
- let(:url) { "/jobs/#{job.id}/artifacts?file.path=#{tmp_file.path}" }
-
- it 'rejects the request' do
- expect(response).to have_gitlab_http_status(:bad_request)
- expect(stored_artifacts_size).to be_nil
- end
- end
-
- context 'when workhorse header is missing' do
- let(:post_data) { { file: artifacts, metadata: metadata } }
- let(:send_rewritten_field) { false }
-
- it 'rejects the request' do
- expect(response).to have_gitlab_http_status(:bad_request)
- expect(stored_artifacts_size).to be_nil
- end
- end
-
- context 'when there is no artifacts file in post data' do
- let(:post_data) do
- { metadata: metadata }
- end
-
- it 'is expected to respond with bad request' do
- expect(response).to have_gitlab_http_status(:bad_request)
- end
-
- it 'does not store metadata' do
- expect(stored_metadata_file).to be_nil
- end
- end
- end
-
- context 'when artifact_type is archive' do
- context 'when artifact_format is zip' do
- let(:params) { { artifact_type: :archive, artifact_format: :zip } }
-
- it 'stores junit test report' do
- upload_artifacts(file_upload, headers_with_token, params)
-
- expect(response).to have_gitlab_http_status(:created)
- expect(job.reload.job_artifacts_archive).not_to be_nil
- end
- end
-
- context 'when artifact_format is gzip' do
- let(:params) { { artifact_type: :archive, artifact_format: :gzip } }
-
- it 'returns an error' do
- upload_artifacts(file_upload, headers_with_token, params)
-
- expect(response).to have_gitlab_http_status(:bad_request)
- expect(job.reload.job_artifacts_archive).to be_nil
- end
- end
- end
-
- context 'when artifact_type is junit' do
- context 'when artifact_format is gzip' do
- let(:file_upload) { fixture_file_upload('spec/fixtures/junit/junit.xml.gz') }
- let(:params) { { artifact_type: :junit, artifact_format: :gzip } }
-
- it 'stores junit test report' do
- upload_artifacts(file_upload, headers_with_token, params)
-
- expect(response).to have_gitlab_http_status(:created)
- expect(job.reload.job_artifacts_junit).not_to be_nil
- end
- end
-
- context 'when artifact_format is raw' do
- let(:file_upload) { fixture_file_upload('spec/fixtures/junit/junit.xml.gz') }
- let(:params) { { artifact_type: :junit, artifact_format: :raw } }
-
- it 'returns an error' do
- upload_artifacts(file_upload, headers_with_token, params)
-
- expect(response).to have_gitlab_http_status(:bad_request)
- expect(job.reload.job_artifacts_junit).to be_nil
- end
- end
- end
-
- context 'when artifact_type is metrics_referee' do
- context 'when artifact_format is gzip' do
- let(:file_upload) { fixture_file_upload('spec/fixtures/referees/metrics_referee.json.gz') }
- let(:params) { { artifact_type: :metrics_referee, artifact_format: :gzip } }
-
- it 'stores metrics_referee data' do
- upload_artifacts(file_upload, headers_with_token, params)
-
- expect(response).to have_gitlab_http_status(:created)
- expect(job.reload.job_artifacts_metrics_referee).not_to be_nil
- end
- end
-
- context 'when artifact_format is raw' do
- let(:file_upload) { fixture_file_upload('spec/fixtures/referees/metrics_referee.json.gz') }
- let(:params) { { artifact_type: :metrics_referee, artifact_format: :raw } }
-
- it 'returns an error' do
- upload_artifacts(file_upload, headers_with_token, params)
-
- expect(response).to have_gitlab_http_status(:bad_request)
- expect(job.reload.job_artifacts_metrics_referee).to be_nil
- end
- end
- end
-
- context 'when artifact_type is network_referee' do
- context 'when artifact_format is gzip' do
- let(:file_upload) { fixture_file_upload('spec/fixtures/referees/network_referee.json.gz') }
- let(:params) { { artifact_type: :network_referee, artifact_format: :gzip } }
-
- it 'stores network_referee data' do
- upload_artifacts(file_upload, headers_with_token, params)
-
- expect(response).to have_gitlab_http_status(:created)
- expect(job.reload.job_artifacts_network_referee).not_to be_nil
- end
- end
-
- context 'when artifact_format is raw' do
- let(:file_upload) { fixture_file_upload('spec/fixtures/referees/network_referee.json.gz') }
- let(:params) { { artifact_type: :network_referee, artifact_format: :raw } }
-
- it 'returns an error' do
- upload_artifacts(file_upload, headers_with_token, params)
-
- expect(response).to have_gitlab_http_status(:bad_request)
- expect(job.reload.job_artifacts_network_referee).to be_nil
- end
- end
- end
-
- context 'when artifact_type is dotenv' do
- context 'when artifact_format is gzip' do
- let(:file_upload) { fixture_file_upload('spec/fixtures/build.env.gz') }
- let(:params) { { artifact_type: :dotenv, artifact_format: :gzip } }
-
- it 'stores dotenv file' do
- upload_artifacts(file_upload, headers_with_token, params)
-
- expect(response).to have_gitlab_http_status(:created)
- expect(job.reload.job_artifacts_dotenv).not_to be_nil
- end
-
- it 'parses dotenv file' do
- expect do
- upload_artifacts(file_upload, headers_with_token, params)
- end.to change { job.job_variables.count }.from(0).to(2)
- end
-
- context 'when parse error happens' do
- let(:file_upload) { fixture_file_upload('spec/fixtures/ci_build_artifacts_metadata.gz') }
-
- it 'returns an error' do
- upload_artifacts(file_upload, headers_with_token, params)
-
- expect(response).to have_gitlab_http_status(:bad_request)
- expect(json_response['message']).to eq('Invalid Format')
- end
- end
- end
-
- context 'when artifact_format is raw' do
- let(:file_upload) { fixture_file_upload('spec/fixtures/build.env.gz') }
- let(:params) { { artifact_type: :dotenv, artifact_format: :raw } }
-
- it 'returns an error' do
- upload_artifacts(file_upload, headers_with_token, params)
-
- expect(response).to have_gitlab_http_status(:bad_request)
- expect(job.reload.job_artifacts_dotenv).to be_nil
- end
- end
- end
- end
-
- context 'when artifacts already exist for the job' do
- let(:params) do
- {
- artifact_type: :archive,
- artifact_format: :zip,
- 'file.sha256' => uploaded_sha256
- }
- end
-
- let(:existing_sha256) { '0' * 64 }
-
- let!(:existing_artifact) do
- create(:ci_job_artifact, :archive, file_sha256: existing_sha256, job: job)
- end
-
- context 'when sha256 is the same of the existing artifact' do
- let(:uploaded_sha256) { existing_sha256 }
-
- it 'ignores the new artifact' do
- upload_artifacts(file_upload, headers_with_token, params)
-
- expect(response).to have_gitlab_http_status(:created)
- expect(job.reload.job_artifacts_archive).to eq(existing_artifact)
- end
- end
-
- context 'when sha256 is different than the existing artifact' do
- let(:uploaded_sha256) { '1' * 64 }
-
- it 'logs and returns an error' do
- expect(Gitlab::ErrorTracking).to receive(:track_exception)
-
- upload_artifacts(file_upload, headers_with_token, params)
-
- expect(response).to have_gitlab_http_status(:bad_request)
- expect(job.reload.job_artifacts_archive).to eq(existing_artifact)
- end
- end
- end
-
- context 'when object storage throws errors' do
- let(:params) { { artifact_type: :archive, artifact_format: :zip } }
-
- it 'does not store artifacts' do
- allow_next_instance_of(JobArtifactUploader) do |uploader|
- allow(uploader).to receive(:store!).and_raise(Errno::EIO)
- end
-
- upload_artifacts(file_upload, headers_with_token, params)
-
- expect(response).to have_gitlab_http_status(:service_unavailable)
- expect(job.reload.job_artifacts_archive).to be_nil
- end
- end
-
- context 'when artifacts are being stored outside of tmp path' do
- let(:new_tmpdir) { Dir.mktmpdir }
-
- before do
- # init before overwriting tmp dir
- file_upload
-
- # by configuring this path we allow to pass file from @tmpdir only
- # but all temporary files are stored in system tmp directory
- allow(Dir).to receive(:tmpdir).and_return(new_tmpdir)
- end
-
- after do
- FileUtils.remove_entry(new_tmpdir)
- end
-
- it' "fails to post artifacts for outside of tmp path"' do
- upload_artifacts(file_upload, headers_with_token)
-
- expect(response).to have_gitlab_http_status(:bad_request)
- end
- end
-
- def upload_artifacts(file, headers = {}, params = {})
- workhorse_finalize(
- api("/jobs/#{job.id}/artifacts"),
- method: :post,
- file_key: :file,
- params: params.merge(file: file),
- headers: headers,
- send_rewritten_field: true
- )
- end
- end
-
- describe 'GET /api/v4/jobs/:id/artifacts' do
- let(:token) { job.token }
-
- it_behaves_like 'application context metadata', '/api/:version/jobs/:id/artifacts' do
- let(:send_request) { download_artifact }
- end
-
- it 'updates runner info' do
- expect { download_artifact }.to change { runner.reload.contacted_at }
- end
-
- context 'when job has artifacts' do
- let(:job) { create(:ci_build) }
- let(:store) { JobArtifactUploader::Store::LOCAL }
-
- before do
- create(:ci_job_artifact, :archive, file_store: store, job: job)
- end
-
- context 'when using job token' do
- context 'when artifacts are stored locally' do
- let(:download_headers) do
- { 'Content-Transfer-Encoding' => 'binary',
- 'Content-Disposition' => %q(attachment; filename="ci_build_artifacts.zip"; filename*=UTF-8''ci_build_artifacts.zip) }
- end
-
- before do
- download_artifact
- end
-
- it 'download artifacts' do
- expect(response).to have_gitlab_http_status(:ok)
- expect(response.headers.to_h).to include download_headers
- end
- end
-
- context 'when artifacts are stored remotely' do
- let(:store) { JobArtifactUploader::Store::REMOTE }
- let!(:job) { create(:ci_build) }
-
- context 'when proxy download is being used' do
- before do
- download_artifact(direct_download: false)
- end
-
- it 'uses workhorse send-url' do
- expect(response).to have_gitlab_http_status(:ok)
- expect(response.headers.to_h).to include(
- 'Gitlab-Workhorse-Send-Data' => /send-url:/)
- end
- end
-
- context 'when direct download is being used' do
- before do
- download_artifact(direct_download: true)
- end
-
- it 'receive redirect for downloading artifacts' do
- expect(response).to have_gitlab_http_status(:found)
- expect(response.headers).to include('Location')
- end
- end
- end
- end
-
- context 'when using runnners token' do
- let(:token) { job.project.runners_token }
-
- before do
- download_artifact
- end
-
- it 'responds with forbidden' do
- expect(response).to have_gitlab_http_status(:forbidden)
- end
- end
- end
-
- context 'when job does not have artifacts' do
- it 'responds with not found' do
- download_artifact
-
- expect(response).to have_gitlab_http_status(:not_found)
- end
- end
-
- def download_artifact(params = {}, request_headers = headers)
- params = params.merge(token: token)
- job.reload
-
- get api("/jobs/#{job.id}/artifacts"), params: params, headers: request_headers
- end
- end
- end
- end
-end
diff --git a/spec/requests/api/runners_spec.rb b/spec/requests/api/runners_spec.rb
deleted file mode 100644
index 67c258260bf..00000000000
--- a/spec/requests/api/runners_spec.rb
+++ /dev/null
@@ -1,1096 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-describe API::Runners do
- let_it_be(:admin) { create(:user, :admin) }
- let_it_be(:user) { create(:user) }
- let_it_be(:user2) { create(:user) }
- let_it_be(:group_guest) { create(:user) }
- let_it_be(:group_reporter) { create(:user) }
- let_it_be(:group_developer) { create(:user) }
- let_it_be(:group_maintainer) { create(:user) }
-
- let_it_be(:project) { create(:project, creator_id: user.id) }
- let_it_be(:project2) { create(:project, creator_id: user.id) }
-
- let_it_be(:group) { create(:group).tap { |group| group.add_owner(user) } }
- let_it_be(:subgroup) { create(:group, parent: group) }
-
- let_it_be(:shared_runner, reload: true) { create(:ci_runner, :instance, description: 'Shared runner') }
- let_it_be(:project_runner, reload: true) { create(:ci_runner, :project, description: 'Project runner', projects: [project]) }
- let_it_be(:two_projects_runner) { create(:ci_runner, :project, description: 'Two projects runner', projects: [project, project2]) }
- let_it_be(:group_runner_a) { create(:ci_runner, :group, description: 'Group runner A', groups: [group]) }
- let_it_be(:group_runner_b) { create(:ci_runner, :group, description: 'Group runner B', groups: [subgroup]) }
-
- before_all do
- group.add_guest(group_guest)
- group.add_reporter(group_reporter)
- group.add_developer(group_developer)
- group.add_maintainer(group_maintainer)
- project.add_maintainer(user)
- project2.add_maintainer(user)
- project.add_reporter(user2)
- end
-
- describe 'GET /runners' do
- context 'authorized user' do
- it 'returns response status and headers' do
- get api('/runners', user)
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(response).to include_pagination_headers
- end
-
- it 'returns user available runners' do
- get api('/runners', user)
-
- expect(json_response).to match_array [
- a_hash_including('description' => 'Project runner'),
- a_hash_including('description' => 'Two projects runner'),
- a_hash_including('description' => 'Group runner A'),
- a_hash_including('description' => 'Group runner B')
- ]
- end
-
- it 'filters runners by scope' do
- create(:ci_runner, :project, :inactive, description: 'Inactive project runner', projects: [project])
-
- get api('/runners?scope=paused', user)
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(response).to include_pagination_headers
-
- expect(json_response).to match_array [
- a_hash_including('description' => 'Inactive project runner')
- ]
- end
-
- it 'avoids filtering if scope is invalid' do
- get api('/runners?scope=unknown', user)
- expect(response).to have_gitlab_http_status(:bad_request)
- end
-
- it 'filters runners by type' do
- get api('/runners?type=project_type', user)
-
- expect(json_response).to match_array [
- a_hash_including('description' => 'Project runner'),
- a_hash_including('description' => 'Two projects runner')
- ]
- end
-
- it 'does not filter by invalid type' do
- get api('/runners?type=bogus', user)
-
- expect(response).to have_gitlab_http_status(:bad_request)
- end
-
- it 'filters runners by status' do
- create(:ci_runner, :project, :inactive, description: 'Inactive project runner', projects: [project])
-
- get api('/runners?status=paused', user)
-
- expect(json_response).to match_array [
- a_hash_including('description' => 'Inactive project runner')
- ]
- end
-
- it 'does not filter by invalid status' do
- get api('/runners?status=bogus', user)
-
- expect(response).to have_gitlab_http_status(:bad_request)
- end
-
- it 'filters runners by tag_list' do
- create(:ci_runner, :project, description: 'Runner tagged with tag1 and tag2', projects: [project], tag_list: %w[tag1 tag2])
- create(:ci_runner, :project, description: 'Runner tagged with tag2', projects: [project], tag_list: ['tag2'])
-
- get api('/runners?tag_list=tag1,tag2', user)
-
- expect(json_response).to match_array [
- a_hash_including('description' => 'Runner tagged with tag1 and tag2')
- ]
- end
- end
-
- context 'unauthorized user' do
- it 'does not return runners' do
- get api('/runners')
-
- expect(response).to have_gitlab_http_status(:unauthorized)
- end
- end
- end
-
- describe 'GET /runners/all' do
- context 'authorized user' do
- context 'with admin privileges' do
- it 'returns response status and headers' do
- get api('/runners/all', admin)
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(response).to include_pagination_headers
- end
-
- it 'returns all runners' do
- get api('/runners/all', admin)
-
- expect(json_response).to match_array [
- a_hash_including('description' => 'Project runner'),
- a_hash_including('description' => 'Two projects runner'),
- a_hash_including('description' => 'Group runner A'),
- a_hash_including('description' => 'Group runner B'),
- a_hash_including('description' => 'Shared runner')
- ]
- end
-
- it 'filters runners by scope' do
- get api('/runners/all?scope=shared', admin)
-
- shared = json_response.all? { |r| r['is_shared'] }
- expect(response).to have_gitlab_http_status(:ok)
- expect(response).to include_pagination_headers
- expect(json_response).to be_an Array
- expect(json_response[0]).to have_key('ip_address')
- expect(shared).to be_truthy
- end
-
- it 'filters runners by scope' do
- get api('/runners/all?scope=specific', admin)
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(response).to include_pagination_headers
-
- expect(json_response).to match_array [
- a_hash_including('description' => 'Project runner'),
- a_hash_including('description' => 'Two projects runner'),
- a_hash_including('description' => 'Group runner A'),
- a_hash_including('description' => 'Group runner B')
- ]
- end
-
- it 'avoids filtering if scope is invalid' do
- get api('/runners/all?scope=unknown', admin)
- expect(response).to have_gitlab_http_status(:bad_request)
- end
-
- it 'filters runners by project type' do
- get api('/runners/all?type=project_type', admin)
-
- expect(json_response).to match_array [
- a_hash_including('description' => 'Project runner'),
- a_hash_including('description' => 'Two projects runner')
- ]
- end
-
- it 'filters runners by group type' do
- get api('/runners/all?type=group_type', admin)
-
- expect(json_response).to match_array [
- a_hash_including('description' => 'Group runner A'),
- a_hash_including('description' => 'Group runner B')
- ]
- end
-
- it 'does not filter by invalid type' do
- get api('/runners/all?type=bogus', admin)
-
- expect(response).to have_gitlab_http_status(:bad_request)
- end
-
- it 'filters runners by status' do
- create(:ci_runner, :project, :inactive, description: 'Inactive project runner', projects: [project])
-
- get api('/runners/all?status=paused', admin)
-
- expect(json_response).to match_array [
- a_hash_including('description' => 'Inactive project runner')
- ]
- end
-
- it 'does not filter by invalid status' do
- get api('/runners/all?status=bogus', admin)
-
- expect(response).to have_gitlab_http_status(:bad_request)
- end
-
- it 'filters runners by tag_list' do
- create(:ci_runner, :project, description: 'Runner tagged with tag1 and tag2', projects: [project], tag_list: %w[tag1 tag2])
- create(:ci_runner, :project, description: 'Runner tagged with tag2', projects: [project], tag_list: ['tag2'])
-
- get api('/runners/all?tag_list=tag1,tag2', admin)
-
- expect(json_response).to match_array [
- a_hash_including('description' => 'Runner tagged with tag1 and tag2')
- ]
- end
- end
-
- context 'without admin privileges' do
- it 'does not return runners list' do
- get api('/runners/all', user)
-
- expect(response).to have_gitlab_http_status(:forbidden)
- end
- end
- end
-
- context 'unauthorized user' do
- it 'does not return runners' do
- get api('/runners')
-
- expect(response).to have_gitlab_http_status(:unauthorized)
- end
- end
- end
-
- describe 'GET /runners/:id' do
- context 'admin user' do
- context 'when runner is shared' do
- it "returns runner's details" do
- get api("/runners/#{shared_runner.id}", admin)
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(json_response['description']).to eq(shared_runner.description)
- expect(json_response['maximum_timeout']).to be_nil
- end
- end
-
- context 'when runner is not shared' do
- context 'when unused runner is present' do
- let!(:unused_project_runner) { create(:ci_runner, :project, :without_projects) }
-
- it 'deletes unused runner' do
- expect do
- delete api("/runners/#{unused_project_runner.id}", admin)
-
- expect(response).to have_gitlab_http_status(:no_content)
- end.to change { Ci::Runner.project_type.count }.by(-1)
- end
- end
-
- it "returns runner's details" do
- get api("/runners/#{project_runner.id}", admin)
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(json_response['description']).to eq(project_runner.description)
- end
-
- it "returns the project's details for a project runner" do
- get api("/runners/#{project_runner.id}", admin)
-
- expect(json_response['projects'].first['id']).to eq(project.id)
- end
- end
-
- it 'returns 404 if runner does not exists' do
- get api('/runners/0', admin)
-
- expect(response).to have_gitlab_http_status(:not_found)
- end
- end
-
- context "runner project's administrative user" do
- context 'when runner is not shared' do
- it "returns runner's details" do
- get api("/runners/#{project_runner.id}", user)
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(json_response['description']).to eq(project_runner.description)
- end
- end
-
- context 'when runner is shared' do
- it "returns runner's details" do
- get api("/runners/#{shared_runner.id}", user)
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(json_response['description']).to eq(shared_runner.description)
- end
- end
- end
-
- context 'other authorized user' do
- it "does not return project runner's details" do
- get api("/runners/#{project_runner.id}", user2)
-
- expect(response).to have_gitlab_http_status(:forbidden)
- end
- end
-
- context 'unauthorized user' do
- it "does not return project runner's details" do
- get api("/runners/#{project_runner.id}")
-
- expect(response).to have_gitlab_http_status(:unauthorized)
- end
- end
- end
-
- describe 'PUT /runners/:id' do
- context 'admin user' do
- # see https://gitlab.com/gitlab-org/gitlab-foss/issues/48625
- context 'single parameter update' do
- it 'runner description' do
- description = shared_runner.description
- update_runner(shared_runner.id, admin, description: "#{description}_updated")
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(shared_runner.reload.description).to eq("#{description}_updated")
- end
-
- it 'runner active state' do
- active = shared_runner.active
- update_runner(shared_runner.id, admin, active: !active)
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(shared_runner.reload.active).to eq(!active)
- end
-
- it 'runner tag list' do
- update_runner(shared_runner.id, admin, tag_list: ['ruby2.1', 'pgsql', 'mysql'])
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(shared_runner.reload.tag_list).to include('ruby2.1', 'pgsql', 'mysql')
- end
-
- it 'runner untagged flag' do
- # Ensure tag list is non-empty before setting untagged to false.
- update_runner(shared_runner.id, admin, tag_list: ['ruby2.1', 'pgsql', 'mysql'])
- update_runner(shared_runner.id, admin, run_untagged: 'false')
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(shared_runner.reload.run_untagged?).to be(false)
- end
-
- it 'runner unlocked flag' do
- update_runner(shared_runner.id, admin, locked: 'true')
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(shared_runner.reload.locked?).to be(true)
- end
-
- it 'runner access level' do
- update_runner(shared_runner.id, admin, access_level: 'ref_protected')
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(shared_runner.reload.ref_protected?).to be_truthy
- end
-
- it 'runner maximum timeout' do
- update_runner(shared_runner.id, admin, maximum_timeout: 1234)
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(shared_runner.reload.maximum_timeout).to eq(1234)
- end
-
- it 'fails with no parameters' do
- put api("/runners/#{shared_runner.id}", admin)
-
- shared_runner.reload
- expect(response).to have_gitlab_http_status(:bad_request)
- end
- end
-
- context 'when runner is shared' do
- it 'updates runner' do
- description = shared_runner.description
- active = shared_runner.active
- runner_queue_value = shared_runner.ensure_runner_queue_value
-
- update_runner(shared_runner.id, admin, description: "#{description}_updated",
- active: !active,
- tag_list: ['ruby2.1', 'pgsql', 'mysql'],
- run_untagged: 'false',
- locked: 'true',
- access_level: 'ref_protected',
- maximum_timeout: 1234)
- shared_runner.reload
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(shared_runner.description).to eq("#{description}_updated")
- expect(shared_runner.active).to eq(!active)
- expect(shared_runner.tag_list).to include('ruby2.1', 'pgsql', 'mysql')
- expect(shared_runner.run_untagged?).to be(false)
- expect(shared_runner.locked?).to be(true)
- expect(shared_runner.ref_protected?).to be_truthy
- expect(shared_runner.ensure_runner_queue_value)
- .not_to eq(runner_queue_value)
- expect(shared_runner.maximum_timeout).to eq(1234)
- end
- end
-
- context 'when runner is not shared' do
- it 'updates runner' do
- description = project_runner.description
- runner_queue_value = project_runner.ensure_runner_queue_value
-
- update_runner(project_runner.id, admin, description: 'test')
- project_runner.reload
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(project_runner.description).to eq('test')
- expect(project_runner.description).not_to eq(description)
- expect(project_runner.ensure_runner_queue_value)
- .not_to eq(runner_queue_value)
- end
- end
-
- it 'returns 404 if runner does not exists' do
- update_runner(0, admin, description: 'test')
-
- expect(response).to have_gitlab_http_status(:not_found)
- end
-
- def update_runner(id, user, args)
- put api("/runners/#{id}", user), params: args
- end
- end
-
- context 'authorized user' do
- context 'when runner is shared' do
- it 'does not update runner' do
- put api("/runners/#{shared_runner.id}", user), params: { description: 'test' }
-
- expect(response).to have_gitlab_http_status(:forbidden)
- end
- end
-
- context 'when runner is not shared' do
- it 'does not update project runner without access to it' do
- put api("/runners/#{project_runner.id}", user2), params: { description: 'test' }
-
- expect(response).to have_gitlab_http_status(:forbidden)
- end
-
- it 'updates project runner with access to it' do
- description = project_runner.description
- put api("/runners/#{project_runner.id}", admin), params: { description: 'test' }
- project_runner.reload
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(project_runner.description).to eq('test')
- expect(project_runner.description).not_to eq(description)
- end
- end
- end
-
- context 'unauthorized user' do
- it 'does not delete project runner' do
- put api("/runners/#{project_runner.id}")
-
- expect(response).to have_gitlab_http_status(:unauthorized)
- end
- end
- end
-
- describe 'DELETE /runners/:id' do
- context 'admin user' do
- context 'when runner is shared' do
- it 'deletes runner' do
- expect do
- delete api("/runners/#{shared_runner.id}", admin)
-
- expect(response).to have_gitlab_http_status(:no_content)
- end.to change { Ci::Runner.instance_type.count }.by(-1)
- end
-
- it_behaves_like '412 response' do
- let(:request) { api("/runners/#{shared_runner.id}", admin) }
- end
- end
-
- context 'when runner is not shared' do
- it 'deletes used project runner' do
- expect do
- delete api("/runners/#{project_runner.id}", admin)
-
- expect(response).to have_gitlab_http_status(:no_content)
- end.to change { Ci::Runner.project_type.count }.by(-1)
- end
- end
-
- it 'returns 404 if runner does not exists' do
- delete api('/runners/0', admin)
-
- expect(response).to have_gitlab_http_status(:not_found)
- end
- end
-
- context 'authorized user' do
- context 'when runner is shared' do
- it 'does not delete runner' do
- delete api("/runners/#{shared_runner.id}", user)
- expect(response).to have_gitlab_http_status(:forbidden)
- end
- end
-
- context 'when runner is not shared' do
- it 'does not delete runner without access to it' do
- delete api("/runners/#{project_runner.id}", user2)
- expect(response).to have_gitlab_http_status(:forbidden)
- end
-
- it 'does not delete project runner with more than one associated project' do
- delete api("/runners/#{two_projects_runner.id}", user)
- expect(response).to have_gitlab_http_status(:forbidden)
- end
-
- it 'deletes project runner for one owned project' do
- expect do
- delete api("/runners/#{project_runner.id}", user)
-
- expect(response).to have_gitlab_http_status(:no_content)
- end.to change { Ci::Runner.project_type.count }.by(-1)
- end
-
- it 'does not delete group runner with guest access' do
- delete api("/runners/#{group_runner_a.id}", group_guest)
-
- expect(response).to have_gitlab_http_status(:forbidden)
- end
-
- it 'does not delete group runner with reporter access' do
- delete api("/runners/#{group_runner_a.id}", group_reporter)
-
- expect(response).to have_gitlab_http_status(:forbidden)
- end
-
- it 'does not delete group runner with developer access' do
- delete api("/runners/#{group_runner_a.id}", group_developer)
-
- expect(response).to have_gitlab_http_status(:forbidden)
- end
-
- it 'does not delete group runner with maintainer access' do
- delete api("/runners/#{group_runner_a.id}", group_maintainer)
-
- expect(response).to have_gitlab_http_status(:forbidden)
- end
-
- it 'deletes owned group runner with owner access' do
- expect do
- delete api("/runners/#{group_runner_a.id}", user)
-
- expect(response).to have_gitlab_http_status(:no_content)
- end.to change { Ci::Runner.group_type.count }.by(-1)
- end
-
- it 'deletes inherited group runner with owner access' do
- expect do
- delete api("/runners/#{group_runner_b.id}", user)
-
- expect(response).to have_gitlab_http_status(:no_content)
- end.to change { Ci::Runner.group_type.count }.by(-1)
- end
-
- it_behaves_like '412 response' do
- let(:request) { api("/runners/#{project_runner.id}", user) }
- end
- end
- end
-
- context 'unauthorized user' do
- it 'does not delete project runner' do
- delete api("/runners/#{project_runner.id}")
-
- expect(response).to have_gitlab_http_status(:unauthorized)
- end
- end
- end
-
- describe 'GET /runners/:id/jobs' do
- let_it_be(:job_1) { create(:ci_build) }
- let_it_be(:job_2) { create(:ci_build, :running, runner: shared_runner, project: project) }
- let_it_be(:job_3) { create(:ci_build, :failed, runner: shared_runner, project: project) }
- let_it_be(:job_4) { create(:ci_build, :running, runner: project_runner, project: project) }
- let_it_be(:job_5) { create(:ci_build, :failed, runner: project_runner, project: project) }
-
- context 'admin user' do
- context 'when runner exists' do
- context 'when runner is shared' do
- it 'return jobs' do
- get api("/runners/#{shared_runner.id}/jobs", admin)
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(response).to include_pagination_headers
-
- expect(json_response).to be_an(Array)
- expect(json_response.length).to eq(2)
- end
- end
-
- context 'when runner is specific' do
- it 'return jobs' do
- get api("/runners/#{project_runner.id}/jobs", admin)
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(response).to include_pagination_headers
-
- expect(json_response).to be_an(Array)
- expect(json_response.length).to eq(2)
- end
- end
-
- context 'when valid status is provided' do
- it 'return filtered jobs' do
- get api("/runners/#{project_runner.id}/jobs?status=failed", admin)
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(response).to include_pagination_headers
-
- expect(json_response).to be_an(Array)
- expect(json_response.length).to eq(1)
- expect(json_response.first).to include('id' => job_5.id)
- end
- end
-
- context 'when valid order_by is provided' do
- context 'when sort order is not specified' do
- it 'return jobs in descending order' do
- get api("/runners/#{project_runner.id}/jobs?order_by=id", admin)
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(response).to include_pagination_headers
-
- expect(json_response).to be_an(Array)
- expect(json_response.length).to eq(2)
- expect(json_response.first).to include('id' => job_5.id)
- end
- end
-
- context 'when sort order is specified as asc' do
- it 'return jobs sorted in ascending order' do
- get api("/runners/#{project_runner.id}/jobs?order_by=id&sort=asc", admin)
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(response).to include_pagination_headers
-
- expect(json_response).to be_an(Array)
- expect(json_response.length).to eq(2)
- expect(json_response.first).to include('id' => job_4.id)
- end
- end
- end
-
- context 'when invalid status is provided' do
- it 'return 400' do
- get api("/runners/#{project_runner.id}/jobs?status=non-existing", admin)
-
- expect(response).to have_gitlab_http_status(:bad_request)
- end
- end
-
- context 'when invalid order_by is provided' do
- it 'return 400' do
- get api("/runners/#{project_runner.id}/jobs?order_by=non-existing", admin)
-
- expect(response).to have_gitlab_http_status(:bad_request)
- end
- end
-
- context 'when invalid sort is provided' do
- it 'return 400' do
- get api("/runners/#{project_runner.id}/jobs?sort=non-existing", admin)
-
- expect(response).to have_gitlab_http_status(:bad_request)
- end
- end
- end
-
- context "when runner doesn't exist" do
- it 'returns 404' do
- get api('/runners/0/jobs', admin)
-
- expect(response).to have_gitlab_http_status(:not_found)
- end
- end
- end
-
- context "runner project's administrative user" do
- context 'when runner exists' do
- context 'when runner is shared' do
- it 'returns 403' do
- get api("/runners/#{shared_runner.id}/jobs", user)
-
- expect(response).to have_gitlab_http_status(:forbidden)
- end
- end
-
- context 'when runner is specific' do
- it 'return jobs' do
- get api("/runners/#{project_runner.id}/jobs", user)
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(response).to include_pagination_headers
-
- expect(json_response).to be_an(Array)
- expect(json_response.length).to eq(2)
- end
- end
-
- context 'when valid status is provided' do
- it 'return filtered jobs' do
- get api("/runners/#{project_runner.id}/jobs?status=failed", user)
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(response).to include_pagination_headers
-
- expect(json_response).to be_an(Array)
- expect(json_response.length).to eq(1)
- expect(json_response.first).to include('id' => job_5.id)
- end
- end
-
- context 'when invalid status is provided' do
- it 'return 400' do
- get api("/runners/#{project_runner.id}/jobs?status=non-existing", user)
-
- expect(response).to have_gitlab_http_status(:bad_request)
- end
- end
- end
-
- context "when runner doesn't exist" do
- it 'returns 404' do
- get api('/runners/0/jobs', user)
-
- expect(response).to have_gitlab_http_status(:not_found)
- end
- end
- end
-
- context 'other authorized user' do
- it 'does not return jobs' do
- get api("/runners/#{project_runner.id}/jobs", user2)
-
- expect(response).to have_gitlab_http_status(:forbidden)
- end
- end
-
- context 'unauthorized user' do
- it 'does not return jobs' do
- get api("/runners/#{project_runner.id}/jobs")
-
- expect(response).to have_gitlab_http_status(:unauthorized)
- end
- end
- end
-
- shared_examples_for 'unauthorized access to runners list' do
- context 'authorized user without maintainer privileges' do
- it "does not return group's runners" do
- get api("/#{entity_type}/#{entity.id}/runners", user2)
-
- expect(response).to have_gitlab_http_status(:forbidden)
- end
- end
-
- context 'unauthorized user' do
- it "does not return project's runners" do
- get api("/#{entity_type}/#{entity.id}/runners")
-
- expect(response).to have_gitlab_http_status(:unauthorized)
- end
- end
- end
-
- describe 'GET /projects/:id/runners' do
- context 'authorized user with maintainer privileges' do
- it 'returns response status and headers' do
- get api('/runners/all', admin)
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(response).to include_pagination_headers
- end
-
- it 'returns all runners' do
- get api("/projects/#{project.id}/runners", user)
-
- expect(json_response).to match_array [
- a_hash_including('description' => 'Project runner'),
- a_hash_including('description' => 'Two projects runner'),
- a_hash_including('description' => 'Shared runner')
- ]
- end
-
- it 'filters runners by scope' do
- get api("/projects/#{project.id}/runners?scope=specific", user)
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(response).to include_pagination_headers
-
- expect(json_response).to match_array [
- a_hash_including('description' => 'Project runner'),
- a_hash_including('description' => 'Two projects runner')
- ]
- end
-
- it 'avoids filtering if scope is invalid' do
- get api("/projects/#{project.id}/runners?scope=unknown", user)
- expect(response).to have_gitlab_http_status(:bad_request)
- end
-
- it 'filters runners by type' do
- get api("/projects/#{project.id}/runners?type=project_type", user)
-
- expect(json_response).to match_array [
- a_hash_including('description' => 'Project runner'),
- a_hash_including('description' => 'Two projects runner')
- ]
- end
-
- it 'does not filter by invalid type' do
- get api("/projects/#{project.id}/runners?type=bogus", user)
-
- expect(response).to have_gitlab_http_status(:bad_request)
- end
-
- it 'filters runners by status' do
- create(:ci_runner, :project, :inactive, description: 'Inactive project runner', projects: [project])
-
- get api("/projects/#{project.id}/runners?status=paused", user)
-
- expect(json_response).to match_array [
- a_hash_including('description' => 'Inactive project runner')
- ]
- end
-
- it 'does not filter by invalid status' do
- get api("/projects/#{project.id}/runners?status=bogus", user)
-
- expect(response).to have_gitlab_http_status(:bad_request)
- end
-
- it 'filters runners by tag_list' do
- create(:ci_runner, :project, description: 'Runner tagged with tag1 and tag2', projects: [project], tag_list: %w[tag1 tag2])
- create(:ci_runner, :project, description: 'Runner tagged with tag2', projects: [project], tag_list: ['tag2'])
-
- get api("/projects/#{project.id}/runners?tag_list=tag1,tag2", user)
-
- expect(json_response).to match_array [
- a_hash_including('description' => 'Runner tagged with tag1 and tag2')
- ]
- end
- end
-
- it_behaves_like 'unauthorized access to runners list' do
- let(:entity_type) { 'projects' }
- let(:entity) { project }
- end
- end
-
- describe 'GET /groups/:id/runners' do
- context 'authorized user with maintainer privileges' do
- it 'returns all runners' do
- get api("/groups/#{group.id}/runners", user)
-
- expect(json_response).to match_array([
- a_hash_including('description' => 'Group runner A')
- ])
- end
-
- context 'filter by type' do
- it 'returns record when valid and present' do
- get api("/groups/#{group.id}/runners?type=group_type", user)
-
- expect(json_response).to match_array([
- a_hash_including('description' => 'Group runner A')
- ])
- end
-
- it 'returns empty result when type does not match' do
- get api("/groups/#{group.id}/runners?type=project_type", user)
-
- expect(json_response).to be_empty
- end
-
- it 'does not filter by invalid type' do
- get api("/groups/#{group.id}/runners?type=bogus", user)
-
- expect(response).to have_gitlab_http_status(:bad_request)
- end
- end
-
- context 'filter runners by status' do
- it 'returns runners by valid status' do
- create(:ci_runner, :group, :inactive, description: 'Inactive group runner', groups: [group])
-
- get api("/groups/#{group.id}/runners?status=paused", user)
-
- expect(json_response).to match_array([
- a_hash_including('description' => 'Inactive group runner')
- ])
- end
-
- it 'does not filter by invalid status' do
- get api("/groups/#{group.id}/runners?status=bogus", user)
-
- expect(response).to have_gitlab_http_status(:bad_request)
- end
- end
-
- it 'filters runners by tag_list' do
- create(:ci_runner, :group, description: 'Runner tagged with tag1 and tag2', groups: [group], tag_list: %w[tag1 tag2])
- create(:ci_runner, :group, description: 'Runner tagged with tag2', groups: [group], tag_list: %w[tag1])
-
- get api("/groups/#{group.id}/runners?tag_list=tag1,tag2", user)
-
- expect(json_response).to match_array([
- a_hash_including('description' => 'Runner tagged with tag1 and tag2')
- ])
- end
- end
-
- it_behaves_like 'unauthorized access to runners list' do
- let(:entity_type) { 'groups' }
- let(:entity) { group }
- end
- end
-
- describe 'POST /projects/:id/runners' do
- context 'authorized user' do
- let_it_be(:project_runner2) { create(:ci_runner, :project, projects: [project2]) }
-
- it 'enables specific runner' do
- expect do
- post api("/projects/#{project.id}/runners", user), params: { runner_id: project_runner2.id }
- end.to change { project.runners.count }.by(+1)
- expect(response).to have_gitlab_http_status(:created)
- end
-
- it 'avoids changes when enabling already enabled runner' do
- expect do
- post api("/projects/#{project.id}/runners", user), params: { runner_id: project_runner.id }
- end.to change { project.runners.count }.by(0)
- expect(response).to have_gitlab_http_status(:bad_request)
- end
-
- it 'does not enable locked runner' do
- project_runner2.update(locked: true)
-
- expect do
- post api("/projects/#{project.id}/runners", user), params: { runner_id: project_runner2.id }
- end.to change { project.runners.count }.by(0)
-
- expect(response).to have_gitlab_http_status(:forbidden)
- end
-
- it 'does not enable shared runner' do
- post api("/projects/#{project.id}/runners", user), params: { runner_id: shared_runner.id }
-
- expect(response).to have_gitlab_http_status(:forbidden)
- end
-
- it 'does not enable group runner' do
- post api("/projects/#{project.id}/runners", user), params: { runner_id: group_runner_a.id }
-
- expect(response).to have_gitlab_http_status(:forbidden)
- end
-
- context 'user is admin' do
- context 'when project runner is used' do
- let!(:new_project_runner) { create(:ci_runner, :project) }
-
- it 'enables any specific runner' do
- expect do
- post api("/projects/#{project.id}/runners", admin), params: { runner_id: new_project_runner.id }
- end.to change { project.runners.count }.by(+1)
- expect(response).to have_gitlab_http_status(:created)
- end
- end
-
- it 'enables a instance type runner' do
- expect do
- post api("/projects/#{project.id}/runners", admin), params: { runner_id: shared_runner.id }
- end.to change { project.runners.count }.by(1)
-
- expect(shared_runner.reload).not_to be_instance_type
- expect(response).to have_gitlab_http_status(:created)
- end
- end
-
- it 'raises an error when no runner_id param is provided' do
- post api("/projects/#{project.id}/runners", admin)
-
- expect(response).to have_gitlab_http_status(:bad_request)
- end
- end
-
- context 'user is not admin' do
- let!(:new_project_runner) { create(:ci_runner, :project) }
-
- it 'does not enable runner without access to' do
- post api("/projects/#{project.id}/runners", user), params: { runner_id: new_project_runner.id }
-
- expect(response).to have_gitlab_http_status(:forbidden)
- end
- end
-
- context 'authorized user without permissions' do
- it 'does not enable runner' do
- post api("/projects/#{project.id}/runners", user2)
-
- expect(response).to have_gitlab_http_status(:forbidden)
- end
- end
-
- context 'unauthorized user' do
- it 'does not enable runner' do
- post api("/projects/#{project.id}/runners")
-
- expect(response).to have_gitlab_http_status(:unauthorized)
- end
- end
- end
-
- describe 'DELETE /projects/:id/runners/:runner_id' do
- context 'authorized user' do
- context 'when runner have more than one associated projects' do
- it "disables project's runner" do
- expect do
- delete api("/projects/#{project.id}/runners/#{two_projects_runner.id}", user)
-
- expect(response).to have_gitlab_http_status(:no_content)
- end.to change { project.runners.count }.by(-1)
- end
-
- it_behaves_like '412 response' do
- let(:request) { api("/projects/#{project.id}/runners/#{two_projects_runner.id}", user) }
- end
- end
-
- context 'when runner have one associated projects' do
- it "does not disable project's runner" do
- expect do
- delete api("/projects/#{project.id}/runners/#{project_runner.id}", user)
- end.to change { project.runners.count }.by(0)
- expect(response).to have_gitlab_http_status(:forbidden)
- end
- end
-
- it 'returns 404 is runner is not found' do
- delete api("/projects/#{project.id}/runners/0", user)
-
- expect(response).to have_gitlab_http_status(:not_found)
- end
- end
-
- context 'authorized user without permissions' do
- it "does not disable project's runner" do
- delete api("/projects/#{project.id}/runners/#{project_runner.id}", user2)
-
- expect(response).to have_gitlab_http_status(:forbidden)
- end
- end
-
- context 'unauthorized user' do
- it "does not disable project's runner" do
- delete api("/projects/#{project.id}/runners/#{project_runner.id}")
-
- expect(response).to have_gitlab_http_status(:unauthorized)
- end
- end
- end
-end
diff --git a/spec/requests/api/search_spec.rb b/spec/requests/api/search_spec.rb
index a02d804ee9b..1a93be98a67 100644
--- a/spec/requests/api/search_spec.rb
+++ b/spec/requests/api/search_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Search do
+RSpec.describe API::Search do
let_it_be(:user) { create(:user) }
let_it_be(:group) { create(:group) }
let_it_be(:project, reload: true) { create(:project, :wiki_repo, :public, name: 'awesome project', group: group) }
diff --git a/spec/requests/api/services_spec.rb b/spec/requests/api/services_spec.rb
index 53265574e6a..5528a0c094f 100644
--- a/spec/requests/api/services_spec.rb
+++ b/spec/requests/api/services_spec.rb
@@ -2,7 +2,7 @@
require "spec_helper"
-describe API::Services do
+RSpec.describe API::Services do
let_it_be(:user) { create(:user) }
let_it_be(:user2) { create(:user) }
diff --git a/spec/requests/api/settings_spec.rb b/spec/requests/api/settings_spec.rb
index e6dd1fecb69..602aacb6ced 100644
--- a/spec/requests/api/settings_spec.rb
+++ b/spec/requests/api/settings_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Settings, 'Settings' do
+RSpec.describe API::Settings, 'Settings' do
let(:user) { create(:user) }
let_it_be(:admin) { create(:admin) }
@@ -62,14 +62,14 @@ describe API::Settings, 'Settings' do
default_projects_limit: 3,
default_project_creation: 2,
password_authentication_enabled_for_web: false,
- repository_storages: ['custom'],
+ repository_storages: 'custom',
plantuml_enabled: true,
plantuml_url: 'http://plantuml.example.com',
sourcegraph_enabled: true,
sourcegraph_url: 'https://sourcegraph.com',
sourcegraph_public_only: false,
default_snippet_visibility: 'internal',
- restricted_visibility_levels: ['public'],
+ restricted_visibility_levels: 'public',
default_artifacts_expire_in: '2 days',
help_page_text: 'custom help text',
help_page_hide_commercial_content: true,
@@ -94,7 +94,9 @@ describe API::Settings, 'Settings' do
issues_create_limit: 300,
raw_blob_request_limit: 300,
spam_check_endpoint_enabled: true,
- spam_check_endpoint_url: 'https://example.com/spam_check'
+ spam_check_endpoint_url: 'https://example.com/spam_check',
+ disabled_oauth_sign_in_sources: 'unknown',
+ import_sources: 'github,bitbucket'
}
expect(response).to have_gitlab_http_status(:ok)
@@ -135,6 +137,8 @@ describe API::Settings, 'Settings' do
expect(json_response['raw_blob_request_limit']).to eq(300)
expect(json_response['spam_check_endpoint_enabled']).to be_truthy
expect(json_response['spam_check_endpoint_url']).to eq('https://example.com/spam_check')
+ expect(json_response['disabled_oauth_sign_in_sources']).to eq([])
+ expect(json_response['import_sources']).to match_array(%w(github bitbucket))
end
end
diff --git a/spec/requests/api/sidekiq_metrics_spec.rb b/spec/requests/api/sidekiq_metrics_spec.rb
index 705ae29d5d8..23ac2ea5c0b 100644
--- a/spec/requests/api/sidekiq_metrics_spec.rb
+++ b/spec/requests/api/sidekiq_metrics_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::SidekiqMetrics do
+RSpec.describe API::SidekiqMetrics do
let(:admin) { create(:user, :admin) }
describe 'GET sidekiq/*' do
diff --git a/spec/requests/api/snippets_spec.rb b/spec/requests/api/snippets_spec.rb
index c12c95ae2e0..e676eb94337 100644
--- a/spec/requests/api/snippets_spec.rb
+++ b/spec/requests/api/snippets_spec.rb
@@ -2,14 +2,16 @@
require 'spec_helper'
-describe API::Snippets do
+RSpec.describe API::Snippets do
+ include SnippetHelpers
+
let_it_be(:user) { create(:user) }
describe 'GET /snippets/' do
it 'returns snippets available' do
- public_snippet = create(:personal_snippet, :public, author: user)
- private_snippet = create(:personal_snippet, :private, author: user)
- internal_snippet = create(:personal_snippet, :internal, author: user)
+ public_snippet = create(:personal_snippet, :repository, :public, author: user)
+ private_snippet = create(:personal_snippet, :repository, :private, author: user)
+ internal_snippet = create(:personal_snippet, :repository, :internal, author: user)
get api("/snippets/", user)
@@ -22,6 +24,7 @@ describe API::Snippets do
private_snippet.id)
expect(json_response.last).to have_key('web_url')
expect(json_response.last).to have_key('raw_url')
+ expect(json_response.last).to have_key('files')
expect(json_response.last).to have_key('visibility')
end
@@ -59,32 +62,33 @@ describe API::Snippets do
end
describe 'GET /snippets/public' do
- let!(:other_user) { create(:user) }
- let!(:public_snippet) { create(:personal_snippet, :public, author: user) }
- let!(:private_snippet) { create(:personal_snippet, :private, author: user) }
- let!(:internal_snippet) { create(:personal_snippet, :internal, author: user) }
- let!(:public_snippet_other) { create(:personal_snippet, :public, author: other_user) }
- let!(:private_snippet_other) { create(:personal_snippet, :private, author: other_user) }
- let!(:internal_snippet_other) { create(:personal_snippet, :internal, author: other_user) }
- let!(:public_snippet_project) { create(:project_snippet, :public, author: user) }
- let!(:private_snippet_project) { create(:project_snippet, :private, author: user) }
- let!(:internal_snippet_project) { create(:project_snippet, :internal, author: user) }
+ let_it_be(:other_user) { create(:user) }
+ let_it_be(:public_snippet) { create(:personal_snippet, :repository, :public, author: user) }
+ let_it_be(:private_snippet) { create(:personal_snippet, :repository, :private, author: user) }
+ let_it_be(:internal_snippet) { create(:personal_snippet, :repository, :internal, author: user) }
+ let_it_be(:public_snippet_other) { create(:personal_snippet, :repository, :public, author: other_user) }
+ let_it_be(:private_snippet_other) { create(:personal_snippet, :repository, :private, author: other_user) }
+ let_it_be(:internal_snippet_other) { create(:personal_snippet, :repository, :internal, author: other_user) }
+ let_it_be(:public_snippet_project) { create(:project_snippet, :repository, :public, author: user) }
+ let_it_be(:private_snippet_project) { create(:project_snippet, :repository, :private, author: user) }
+ let_it_be(:internal_snippet_project) { create(:project_snippet, :repository, :internal, author: user) }
it 'returns all snippets with public visibility from all users' do
get api("/snippets/public", user)
- expect(response).to have_gitlab_http_status(:ok)
- expect(response).to include_pagination_headers
- expect(json_response).to be_an Array
- expect(json_response.map { |snippet| snippet['id']} ).to contain_exactly(
- public_snippet.id,
- public_snippet_other.id)
- expect(json_response.map { |snippet| snippet['web_url']} ).to contain_exactly(
- "http://localhost/snippets/#{public_snippet.id}",
- "http://localhost/snippets/#{public_snippet_other.id}")
- expect(json_response.map { |snippet| snippet['raw_url']} ).to contain_exactly(
- "http://localhost/snippets/#{public_snippet.id}/raw",
- "http://localhost/snippets/#{public_snippet_other.id}/raw")
+ aggregate_failures do
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to include_pagination_headers
+ expect(json_response).to be_an Array
+ expect(json_response.map { |snippet| snippet['id']} ).to contain_exactly(
+ public_snippet.id,
+ public_snippet_other.id)
+ expect(json_response.map { |snippet| snippet['web_url']} ).to contain_exactly(
+ "http://localhost/snippets/#{public_snippet.id}",
+ "http://localhost/snippets/#{public_snippet_other.id}")
+ expect(json_response[0]['files'].first).to eq snippet_blob_file(public_snippet_other.blobs.first)
+ expect(json_response[1]['files'].first).to eq snippet_blob_file(public_snippet.blobs.first)
+ end
end
end
@@ -102,13 +106,8 @@ describe API::Snippets do
get api("/snippets/#{snippet.id}/raw", author)
expect(response).to have_gitlab_http_status(:ok)
- expect(response.content_type).to eq 'text/plain'
- end
-
- it 'forces attachment content disposition' do
- get api("/snippets/#{snippet.id}/raw", author)
-
- expect(headers['Content-Disposition']).to match(/^attachment/)
+ expect(response.media_type).to eq 'text/plain'
+ expect(headers['Content-Disposition']).to match(/^inline/)
end
it 'returns 404 for invalid snippet id' do
@@ -141,56 +140,88 @@ describe API::Snippets do
end
end
+ describe 'GET /snippets/:id/files/:ref/:file_path/raw' do
+ let_it_be(:snippet) { create(:personal_snippet, :repository, :private) }
+
+ it_behaves_like 'raw snippet files' do
+ let(:api_path) { "/snippets/#{snippet_id}/files/#{ref}/#{file_path}/raw" }
+ end
+ end
+
describe 'GET /snippets/:id' do
let_it_be(:admin) { create(:user, :admin) }
let_it_be(:author) { create(:user) }
let_it_be(:private_snippet) { create(:personal_snippet, :repository, :private, author: author) }
let_it_be(:internal_snippet) { create(:personal_snippet, :repository, :internal, author: author) }
+ let(:snippet) { private_snippet }
- it 'requires authentication' do
- get api("/snippets/#{private_snippet.id}", nil)
+ subject { get api("/snippets/#{snippet.id}", user) }
- expect(response).to have_gitlab_http_status(:unauthorized)
+ it 'hides private snippets from an ordinary user' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:not_found)
end
- it 'returns snippet json' do
- get api("/snippets/#{private_snippet.id}", author)
+ context 'without a user' do
+ let(:user) { nil }
- expect(response).to have_gitlab_http_status(:ok)
+ it 'requires authentication' do
+ subject
- expect(json_response['title']).to eq(private_snippet.title)
- expect(json_response['description']).to eq(private_snippet.description)
- expect(json_response['file_name']).to eq(private_snippet.file_name_on_repo)
- expect(json_response['visibility']).to eq(private_snippet.visibility)
- expect(json_response['ssh_url_to_repo']).to eq(private_snippet.ssh_url_to_repo)
- expect(json_response['http_url_to_repo']).to eq(private_snippet.http_url_to_repo)
+ expect(response).to have_gitlab_http_status(:unauthorized)
+ end
end
- it 'shows private snippets to an admin' do
- get api("/snippets/#{private_snippet.id}", admin)
+ context 'with the author' do
+ let(:user) { author }
- expect(response).to have_gitlab_http_status(:ok)
- end
+ it 'returns snippet json' do
+ subject
- it 'hides private snippets from an ordinary user' do
- get api("/snippets/#{private_snippet.id}", user)
+ expect(response).to have_gitlab_http_status(:ok)
- expect(response).to have_gitlab_http_status(:not_found)
+ expect(json_response['title']).to eq(private_snippet.title)
+ expect(json_response['description']).to eq(private_snippet.description)
+ expect(json_response['file_name']).to eq(private_snippet.file_name_on_repo)
+ expect(json_response['files']).to eq(private_snippet.blobs.map { |blob| snippet_blob_file(blob) })
+ expect(json_response['visibility']).to eq(private_snippet.visibility)
+ expect(json_response['ssh_url_to_repo']).to eq(private_snippet.ssh_url_to_repo)
+ expect(json_response['http_url_to_repo']).to eq(private_snippet.http_url_to_repo)
+ end
end
- it 'shows internal snippets to an ordinary user' do
- get api("/snippets/#{internal_snippet.id}", user)
+ context 'with an admin' do
+ let(:user) { admin }
- expect(response).to have_gitlab_http_status(:ok)
+ it 'shows private snippets to an admin' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+
+ it 'returns 404 for invalid snippet id' do
+ private_snippet.destroy
+
+ subject
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ expect(json_response['message']).to eq('404 Snippet Not Found')
+ end
end
- it 'returns 404 for invalid snippet id' do
- private_snippet.destroy
+ context 'with an internal snippet' do
+ let(:snippet) { internal_snippet }
- get api("/snippets/#{private_snippet.id}", admin)
+ it 'shows internal snippets to an ordinary user' do
+ subject
- expect(response).to have_gitlab_http_status(:not_found)
- expect(json_response['message']).to eq('404 Snippet Not Found')
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+ end
+
+ it_behaves_like 'snippet_multiple_files feature disabled' do
+ let(:user) { author }
end
end
@@ -221,6 +252,7 @@ describe API::Snippets do
expect(json_response['title']).to eq(params[:title])
expect(json_response['description']).to eq(params[:description])
expect(json_response['file_name']).to eq(params[:file_name])
+ expect(json_response['files']).to eq(snippet.blobs.map { |blob| snippet_blob_file(blob) })
expect(json_response['visibility']).to eq(params[:visibility])
end
@@ -251,6 +283,10 @@ describe API::Snippets do
it_behaves_like 'snippet creation'
+ it_behaves_like 'snippet_multiple_files feature disabled' do
+ let(:snippet) { Snippet.find(json_response["id"]) }
+ end
+
context 'with an external user' do
let(:user) { create(:user, :external) }
diff --git a/spec/requests/api/statistics_spec.rb b/spec/requests/api/statistics_spec.rb
index 5aea5c225a0..eab97b6916e 100644
--- a/spec/requests/api/statistics_spec.rb
+++ b/spec/requests/api/statistics_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Statistics, 'Statistics' do
+RSpec.describe API::Statistics, 'Statistics' do
include ProjectForksHelper
tables_to_analyze = %w[
projects
diff --git a/spec/requests/api/submodules_spec.rb b/spec/requests/api/submodules_spec.rb
index 2604dc18005..6b141d6d036 100644
--- a/spec/requests/api/submodules_spec.rb
+++ b/spec/requests/api/submodules_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Submodules do
+RSpec.describe API::Submodules do
let(:user) { create(:user) }
let!(:project) { create(:project, :repository, namespace: user.namespace ) }
let(:guest) { create(:user) { |u| project.add_guest(u) } }
diff --git a/spec/requests/api/suggestions_spec.rb b/spec/requests/api/suggestions_spec.rb
index ffb8c811622..34d3c54d700 100644
--- a/spec/requests/api/suggestions_spec.rb
+++ b/spec/requests/api/suggestions_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Suggestions do
+RSpec.describe API::Suggestions do
let(:project) { create(:project, :repository) }
let(:user) { create(:user) }
diff --git a/spec/requests/api/system_hooks_spec.rb b/spec/requests/api/system_hooks_spec.rb
index 609aa615d33..01b46053d52 100644
--- a/spec/requests/api/system_hooks_spec.rb
+++ b/spec/requests/api/system_hooks_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::SystemHooks do
+RSpec.describe API::SystemHooks do
include StubRequests
let(:user) { create(:user) }
diff --git a/spec/requests/api/tags_spec.rb b/spec/requests/api/tags_spec.rb
index 694802ce1b8..b029c0f5793 100644
--- a/spec/requests/api/tags_spec.rb
+++ b/spec/requests/api/tags_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Tags do
+RSpec.describe API::Tags do
let(:user) { create(:user) }
let(:guest) { create(:user).tap { |u| project.add_guest(u) } }
let(:project) { create(:project, :repository, creator: user, path: 'my.project') }
diff --git a/spec/requests/api/task_completion_status_spec.rb b/spec/requests/api/task_completion_status_spec.rb
index 4dd1e27bd4b..97ce858ba12 100644
--- a/spec/requests/api/task_completion_status_spec.rb
+++ b/spec/requests/api/task_completion_status_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'task completion status response' do
+RSpec.describe 'task completion status response' do
let_it_be(:user) { create(:user) }
let_it_be(:project) do
create(:project, :public, creator_id: user.id, namespace: user.namespace)
diff --git a/spec/requests/api/templates_spec.rb b/spec/requests/api/templates_spec.rb
index fae338b4ca3..e1c5bfd82c4 100644
--- a/spec/requests/api/templates_spec.rb
+++ b/spec/requests/api/templates_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Templates do
+RSpec.describe API::Templates do
context 'the Template Entity' do
before do
get api('/templates/gitignores/Ruby')
diff --git a/spec/requests/api/terraform/state_spec.rb b/spec/requests/api/terraform/state_spec.rb
index ec9db5566e3..c6cba39314b 100644
--- a/spec/requests/api/terraform/state_spec.rb
+++ b/spec/requests/api/terraform/state_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Terraform::State do
+RSpec.describe API::Terraform::State do
include HttpBasicAuthHelpers
let_it_be(:project) { create(:project) }
@@ -59,10 +59,11 @@ describe API::Terraform::State do
context 'with developer permissions' do
let(:current_user) { developer }
- it 'returns forbidden if the user cannot access the state' do
+ it 'returns terraform state belonging to a project of given state name' do
request
- expect(response).to have_gitlab_http_status(:forbidden)
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response.body).to eq(state.file.read)
end
end
end
@@ -94,10 +95,11 @@ describe API::Terraform::State do
context 'with developer permissions' do
let(:job) { create(:ci_build, project: project, user: developer) }
- it 'returns forbidden if the user cannot access the state' do
+ it 'returns terraform state belonging to a project of given state name' do
request
- expect(response).to have_gitlab_http_status(:forbidden)
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response.body).to eq(state.file.read)
end
end
end
@@ -235,9 +237,43 @@ describe API::Terraform::State do
expect(response).to have_gitlab_http_status(:ok)
end
+
+ context 'state is already locked' do
+ before do
+ state.update!(lock_xid: 'locked', locked_by_user: current_user)
+ end
+
+ it 'returns an error' do
+ request
+
+ expect(response).to have_gitlab_http_status(:conflict)
+ end
+ end
+
+ context 'user does not have permission to lock the state' do
+ let(:current_user) { developer }
+
+ it 'returns an error' do
+ request
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+ end
end
describe 'DELETE /projects/:id/terraform/state/:name/lock' do
+ let(:params) do
+ {
+ ID: lock_id,
+ Version: '0.1',
+ Operation: 'OperationTypePlan',
+ Info: '',
+ Who: "#{current_user.username}",
+ Created: Time.now.utc.iso8601(6),
+ Path: ''
+ }
+ end
+
before do
state.lock_xid = '123-456'
state.save!
@@ -246,7 +282,7 @@ describe API::Terraform::State do
subject(:request) { delete api("#{state_path}/lock"), headers: auth_header, params: params }
context 'with the correct lock id' do
- let(:params) { { ID: '123-456' } }
+ let(:lock_id) { '123-456' }
it 'removes the terraform state lock' do
request
@@ -266,7 +302,7 @@ describe API::Terraform::State do
end
context 'with an incorrect lock id' do
- let(:params) { { ID: '456-789' } }
+ let(:lock_id) { '456-789' }
it 'returns an error' do
request
@@ -276,7 +312,7 @@ describe API::Terraform::State do
end
context 'with a longer than 255 character lock id' do
- let(:params) { { ID: '0' * 256 } }
+ let(:lock_id) { '0' * 256 }
it 'returns an error' do
request
@@ -284,5 +320,16 @@ describe API::Terraform::State do
expect(response).to have_gitlab_http_status(:bad_request)
end
end
+
+ context 'user does not have permission to unlock the state' do
+ let(:lock_id) { '123-456' }
+ let(:current_user) { developer }
+
+ it 'returns an error' do
+ request
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+ end
end
end
diff --git a/spec/requests/api/todos_spec.rb b/spec/requests/api/todos_spec.rb
index 0bdc71a30e9..dfd0e13d84c 100644
--- a/spec/requests/api/todos_spec.rb
+++ b/spec/requests/api/todos_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Todos do
+RSpec.describe API::Todos do
let_it_be(:group) { create(:group) }
let_it_be(:project_1) { create(:project, :repository, group: group) }
let_it_be(:project_2) { create(:project) }
diff --git a/spec/requests/api/triggers_spec.rb b/spec/requests/api/triggers_spec.rb
index 19b01cb7913..c51358bf659 100644
--- a/spec/requests/api/triggers_spec.rb
+++ b/spec/requests/api/triggers_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Triggers do
+RSpec.describe API::Triggers do
let_it_be(:user) { create(:user) }
let_it_be(:user2) { create(:user) }
diff --git a/spec/requests/api/user_counts_spec.rb b/spec/requests/api/user_counts_spec.rb
index 688dfe11115..94e25d647fc 100644
--- a/spec/requests/api/user_counts_spec.rb
+++ b/spec/requests/api/user_counts_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::UserCounts do
+RSpec.describe API::UserCounts do
let(:user) { create(:user) }
let(:project) { create(:project, :public) }
diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb
index e780f67bcab..17f9112c1d5 100644
--- a/spec/requests/api/users_spec.rb
+++ b/spec/requests/api/users_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Users, :do_not_mock_admin_mode do
+RSpec.describe API::Users, :do_not_mock_admin_mode do
let_it_be(:admin) { create(:admin) }
let_it_be(:user, reload: true) { create(:user, username: 'user.with.dot') }
let_it_be(:key) { create(:key, user: user) }
@@ -910,6 +910,14 @@ describe API::Users, :do_not_mock_admin_mode do
expect(user.reload.bio).to eq('')
end
+ it 'updates user with nil bio' do
+ put api("/users/#{user.id}", admin), params: { bio: nil }
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response['bio']).to eq('')
+ expect(user.reload.bio).to eq('')
+ end
+
it "updates user with new password and forces reset on next login" do
put api("/users/#{user.id}", admin), params: { password: '12345678' }
diff --git a/spec/requests/api/variables_spec.rb b/spec/requests/api/variables_spec.rb
index f209a1d2e6e..7bb73e9664b 100644
--- a/spec/requests/api/variables_spec.rb
+++ b/spec/requests/api/variables_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Variables do
+RSpec.describe API::Variables do
let(:user) { create(:user) }
let(:user2) { create(:user) }
let!(:project) { create(:project, creator_id: user.id) }
@@ -54,6 +54,59 @@ describe API::Variables do
expect(response).to have_gitlab_http_status(:not_found)
end
+
+ context 'when there are two variables with the same key on different env' do
+ let!(:var1) { create(:ci_variable, project: project, key: 'key1', environment_scope: 'staging') }
+ let!(:var2) { create(:ci_variable, project: project, key: 'key1', environment_scope: 'production') }
+
+ context 'when filter[environment_scope] is not passed' do
+ context 'FF ci_variables_api_filter_environment_scope is enabled' do
+ it 'returns 409' do
+ get api("/projects/#{project.id}/variables/key1", user)
+
+ expect(response).to have_gitlab_http_status(:conflict)
+ end
+ end
+
+ context 'FF ci_variables_api_filter_environment_scope is disabled' do
+ before do
+ stub_feature_flags(ci_variables_api_filter_environment_scope: false)
+ end
+
+ it 'returns random one' do
+ get api("/projects/#{project.id}/variables/key1", user)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response['key']).to eq('key1')
+ end
+ end
+ end
+
+ context 'when filter[environment_scope] is passed' do
+ it 'returns the variable' do
+ get api("/projects/#{project.id}/variables/key1", user), params: { 'filter[environment_scope]': 'production' }
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response['value']).to eq(var2.value)
+ end
+ end
+
+ context 'when wrong filter[environment_scope] is passed' do
+ it 'returns not_found' do
+ get api("/projects/#{project.id}/variables/key1", user), params: { 'filter[environment_scope]': 'invalid' }
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+
+ context 'when there is only one variable with provided key' do
+ it 'returns not_found' do
+ get api("/projects/#{project.id}/variables/#{variable.key}", user), params: { 'filter[environment_scope]': 'invalid' }
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+ end
+ end
end
context 'authorized user with invalid permissions' do
@@ -173,6 +226,52 @@ describe API::Variables do
expect(response).to have_gitlab_http_status(:not_found)
end
+
+ context 'when there are two variables with the same key on different env' do
+ let!(:var1) { create(:ci_variable, project: project, key: 'key1', environment_scope: 'staging') }
+ let!(:var2) { create(:ci_variable, project: project, key: 'key1', environment_scope: 'production') }
+
+ context 'when filter[environment_scope] is not passed' do
+ context 'FF ci_variables_api_filter_environment_scope is enabled' do
+ it 'returns 409' do
+ get api("/projects/#{project.id}/variables/key1", user)
+
+ expect(response).to have_gitlab_http_status(:conflict)
+ end
+ end
+
+ context 'FF ci_variables_api_filter_environment_scope is disabled' do
+ before do
+ stub_feature_flags(ci_variables_api_filter_environment_scope: false)
+ end
+
+ it 'updates random one' do
+ put api("/projects/#{project.id}/variables/key1", user), params: { value: 'new_val' }
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response['value']).to eq('new_val')
+ end
+ end
+ end
+
+ context 'when filter[environment_scope] is passed' do
+ it 'updates the variable' do
+ put api("/projects/#{project.id}/variables/key1", user), params: { value: 'new_val', 'filter[environment_scope]': 'production' }
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(var1.reload.value).not_to eq('new_val')
+ expect(var2.reload.value).to eq('new_val')
+ end
+ end
+
+ context 'when wrong filter[environment_scope] is passed' do
+ it 'returns not_found' do
+ put api("/projects/#{project.id}/variables/key1", user), params: { value: 'new_val', 'filter[environment_scope]': 'invalid' }
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+ end
end
context 'authorized user with invalid permissions' do
@@ -207,6 +306,56 @@ describe API::Variables do
expect(response).to have_gitlab_http_status(:not_found)
end
+
+ context 'when there are two variables with the same key on different env' do
+ let!(:var1) { create(:ci_variable, project: project, key: 'key1', environment_scope: 'staging') }
+ let!(:var2) { create(:ci_variable, project: project, key: 'key1', environment_scope: 'production') }
+
+ context 'when filter[environment_scope] is not passed' do
+ context 'FF ci_variables_api_filter_environment_scope is enabled' do
+ it 'returns 409' do
+ get api("/projects/#{project.id}/variables/key1", user)
+
+ expect(response).to have_gitlab_http_status(:conflict)
+ end
+ end
+
+ context 'FF ci_variables_api_filter_environment_scope is disabled' do
+ before do
+ stub_feature_flags(ci_variables_api_filter_environment_scope: false)
+ end
+
+ it 'deletes random one' do
+ expect do
+ delete api("/projects/#{project.id}/variables/key1", user), params: { 'filter[environment_scope]': 'production' }
+
+ expect(response).to have_gitlab_http_status(:no_content)
+ end.to change {project.variables.count}.by(-1)
+ end
+ end
+ end
+
+ context 'when filter[environment_scope] is passed' do
+ it 'deletes the variable' do
+ expect do
+ delete api("/projects/#{project.id}/variables/key1", user), params: { 'filter[environment_scope]': 'production' }
+
+ expect(response).to have_gitlab_http_status(:no_content)
+ end.to change {project.variables.count}.by(-1)
+
+ expect(var1.reload).to be_present
+ expect { var2.reload }.to raise_error(ActiveRecord::RecordNotFound)
+ end
+ end
+
+ context 'when wrong filter[environment_scope] is passed' do
+ it 'returns not_found' do
+ delete api("/projects/#{project.id}/variables/key1", user), params: { 'filter[environment_scope]': 'invalid' }
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+ end
end
context 'authorized user with invalid permissions' do
diff --git a/spec/requests/api/version_spec.rb b/spec/requests/api/version_spec.rb
index 9eb8c03e273..a0a0f66c8d1 100644
--- a/spec/requests/api/version_spec.rb
+++ b/spec/requests/api/version_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Version do
+RSpec.describe API::Version do
shared_examples_for 'GET /version' do
context 'when unauthenticated' do
it 'returns authentication error' do
diff --git a/spec/requests/api/wikis_spec.rb b/spec/requests/api/wikis_spec.rb
index 43a5cb446bb..f271f8aa853 100644
--- a/spec/requests/api/wikis_spec.rb
+++ b/spec/requests/api/wikis_spec.rb
@@ -12,7 +12,7 @@ require 'spec_helper'
# - maintainer
# because they are 3 edge cases of using wiki pages.
-describe API::Wikis do
+RSpec.describe API::Wikis do
include WorkhorseHelpers
let(:user) { create(:user) }
@@ -21,178 +21,10 @@ describe API::Wikis do
let(:payload) { { content: 'content', format: 'rdoc', title: 'title' } }
let(:expected_keys_with_content) { %w(content format slug title) }
let(:expected_keys_without_content) { %w(format slug title) }
+ let(:wiki) { project_wiki }
- shared_examples_for 'returns list of wiki pages' do
- context 'when wiki has pages' do
- let!(:pages) do
- [create(:wiki_page, wiki: project_wiki, title: 'page1', content: 'content of page1'),
- create(:wiki_page, wiki: project_wiki, title: 'page2.with.dot', content: 'content of page2')]
- end
-
- it 'returns the list of wiki pages without content' do
- get api(url, user)
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(json_response.size).to eq(2)
-
- json_response.each_with_index do |page, index|
- expect(page.keys).to match_array(expected_keys_without_content)
- expect(page['slug']).to eq(pages[index].slug)
- expect(page['title']).to eq(pages[index].title)
- end
- end
-
- it 'returns the list of wiki pages with content' do
- get api(url, user), params: { with_content: 1 }
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(json_response.size).to eq(2)
-
- json_response.each_with_index do |page, index|
- expect(page.keys).to match_array(expected_keys_with_content)
- expect(page['content']).to eq(pages[index].content)
- expect(page['slug']).to eq(pages[index].slug)
- expect(page['title']).to eq(pages[index].title)
- end
- end
- end
-
- it 'return the empty list of wiki pages' do
- get api(url, user)
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(json_response.size).to eq(0)
- end
- end
-
- shared_examples_for 'returns wiki page' do
- it 'returns the wiki page' do
- expect(response).to have_gitlab_http_status(:ok)
- expect(json_response.size).to eq(4)
- expect(json_response.keys).to match_array(expected_keys_with_content)
- expect(json_response['content']).to eq(page.content)
- expect(json_response['slug']).to eq(page.slug)
- expect(json_response['title']).to eq(page.title)
- end
- end
-
- shared_examples_for 'creates wiki page' do
- it 'creates the wiki page' do
- post(api(url, user), params: payload)
-
- expect(response).to have_gitlab_http_status(:created)
- expect(json_response.size).to eq(4)
- expect(json_response.keys).to match_array(expected_keys_with_content)
- expect(json_response['content']).to eq(payload[:content])
- expect(json_response['slug']).to eq(payload[:title].tr(' ', '-'))
- expect(json_response['title']).to eq(payload[:title])
- expect(json_response['rdoc']).to eq(payload[:rdoc])
- end
-
- [:title, :content].each do |part|
- it "responds with validation error on empty #{part}" do
- payload.delete(part)
-
- post(api(url, user), params: payload)
-
- expect(response).to have_gitlab_http_status(:bad_request)
- expect(json_response.size).to eq(1)
- expect(json_response['error']).to eq("#{part} is missing")
- end
- end
- end
-
- shared_examples_for 'updates wiki page' do
- it 'updates the wiki page' do
- put(api(url, user), params: payload)
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(json_response.size).to eq(4)
- expect(json_response.keys).to match_array(expected_keys_with_content)
- expect(json_response['content']).to eq(payload[:content])
- expect(json_response['slug']).to eq(payload[:title].tr(' ', '-'))
- expect(json_response['title']).to eq(payload[:title])
- end
-
- [:title, :content, :format].each do |part|
- it "updates with wiki with missing #{part}" do
- payload.delete(part)
-
- put(api(url, user), params: payload)
-
- expect(response).to have_gitlab_http_status(:ok)
- end
- end
- end
-
- shared_examples_for '403 Forbidden' do
- it 'returns 403 Forbidden' do
- expect(response).to have_gitlab_http_status(:forbidden)
- expect(json_response.size).to eq(1)
- expect(json_response['message']).to eq('403 Forbidden')
- end
- end
-
- shared_examples_for '404 Wiki Page Not Found' do
- it 'returns 404 Wiki Page Not Found' do
- expect(response).to have_gitlab_http_status(:not_found)
- expect(json_response.size).to eq(1)
- expect(json_response['message']).to eq('404 Wiki Page Not Found')
- end
- end
-
- shared_examples_for '404 Project Not Found' do
- it 'returns 404 Project Not Found' do
- expect(response).to have_gitlab_http_status(:not_found)
- expect(json_response.size).to eq(1)
- expect(json_response['message']).to eq('404 Project Not Found')
- end
- end
-
- shared_examples_for '204 No Content' do
- it 'returns 204 No Content' do
- expect(response).to have_gitlab_http_status(:no_content)
- end
- end
-
- shared_examples_for 'uploads wiki attachment' do
- it 'pushes attachment to the wiki repository' do
- allow(SecureRandom).to receive(:hex).and_return('fixed_hex')
-
- workhorse_post_with_file(api(url, user), file_key: :file, params: payload)
-
- expect(response).to have_gitlab_http_status(:created)
- expect(json_response).to eq result_hash.deep_stringify_keys
- end
-
- it 'responds with validation error on empty file' do
- payload.delete(:file)
-
- post(api(url, user), params: payload)
-
- expect(response).to have_gitlab_http_status(:bad_request)
- expect(json_response.size).to eq(1)
- expect(json_response['error']).to eq('file is missing')
- end
-
- it 'responds with validation error on invalid temp file' do
- payload[:file] = { tempfile: '/etc/hosts' }
-
- post(api(url, user), params: payload)
-
- expect(response).to have_gitlab_http_status(:bad_request)
- expect(json_response.size).to eq(1)
- expect(json_response['error']).to eq('file is invalid')
- end
-
- it 'is backward compatible with regular multipart uploads' do
- allow(SecureRandom).to receive(:hex).and_return('fixed_hex')
-
- post(api(url, user), params: payload)
-
- expect(response).to have_gitlab_http_status(:created)
- expect(json_response).to eq result_hash.deep_stringify_keys
- end
+ shared_examples_for 'wiki API 404 Project Not Found' do
+ include_examples 'wiki API 404 Not Found', 'Project'
end
describe 'GET /projects/:id/wikis' do
@@ -206,7 +38,7 @@ describe API::Wikis do
get api(url)
end
- include_examples '404 Project Not Found'
+ include_examples 'wiki API 404 Project Not Found'
end
context 'when user is developer' do
@@ -216,7 +48,7 @@ describe API::Wikis do
get api(url, user)
end
- include_examples '403 Forbidden'
+ include_examples 'wiki API 403 Forbidden'
end
context 'when user is maintainer' do
@@ -226,7 +58,7 @@ describe API::Wikis do
get api(url, user)
end
- include_examples '403 Forbidden'
+ include_examples 'wiki API 403 Forbidden'
end
end
@@ -238,7 +70,7 @@ describe API::Wikis do
get api(url)
end
- include_examples '404 Project Not Found'
+ include_examples 'wiki API 404 Project Not Found'
end
context 'when user is developer' do
@@ -246,7 +78,7 @@ describe API::Wikis do
project.add_developer(user)
end
- include_examples 'returns list of wiki pages'
+ include_examples 'wikis API returns list of wiki pages'
end
context 'when user is maintainer' do
@@ -254,7 +86,7 @@ describe API::Wikis do
project.add_maintainer(user)
end
- include_examples 'returns list of wiki pages'
+ include_examples 'wikis API returns list of wiki pages'
end
end
@@ -266,7 +98,7 @@ describe API::Wikis do
get api(url)
end
- include_examples '404 Project Not Found'
+ include_examples 'wiki API 404 Project Not Found'
end
context 'when user is developer' do
@@ -274,7 +106,7 @@ describe API::Wikis do
project.add_developer(user)
end
- include_examples 'returns list of wiki pages'
+ include_examples 'wikis API returns list of wiki pages'
end
context 'when user is maintainer' do
@@ -282,7 +114,7 @@ describe API::Wikis do
project.add_maintainer(user)
end
- include_examples 'returns list of wiki pages'
+ include_examples 'wikis API returns list of wiki pages'
end
end
end
@@ -299,7 +131,7 @@ describe API::Wikis do
get api(url)
end
- include_examples '404 Project Not Found'
+ include_examples 'wiki API 404 Project Not Found'
end
context 'when user is developer' do
@@ -309,7 +141,7 @@ describe API::Wikis do
get api(url, user)
end
- include_examples '403 Forbidden'
+ include_examples 'wiki API 403 Forbidden'
end
context 'when user is maintainer' do
@@ -319,7 +151,7 @@ describe API::Wikis do
get api(url, user)
end
- include_examples '403 Forbidden'
+ include_examples 'wiki API 403 Forbidden'
end
end
@@ -331,7 +163,7 @@ describe API::Wikis do
get api(url)
end
- include_examples '404 Project Not Found'
+ include_examples 'wiki API 404 Project Not Found'
end
context 'when user is developer' do
@@ -340,12 +172,12 @@ describe API::Wikis do
get api(url, user)
end
- include_examples 'returns wiki page'
+ include_examples 'wikis API returns wiki page'
context 'when page is not existing' do
let(:url) { "/projects/#{project.id}/wikis/unknown" }
- include_examples '404 Wiki Page Not Found'
+ include_examples 'wiki API 404 Wiki Page Not Found'
end
end
@@ -356,12 +188,12 @@ describe API::Wikis do
get api(url, user)
end
- include_examples 'returns wiki page'
+ include_examples 'wikis API returns wiki page'
context 'when page is not existing' do
let(:url) { "/projects/#{project.id}/wikis/unknown" }
- include_examples '404 Wiki Page Not Found'
+ include_examples 'wiki API 404 Wiki Page Not Found'
end
end
end
@@ -374,7 +206,7 @@ describe API::Wikis do
get api(url)
end
- include_examples '404 Project Not Found'
+ include_examples 'wiki API 404 Project Not Found'
end
context 'when user is developer' do
@@ -384,12 +216,12 @@ describe API::Wikis do
get api(url, user)
end
- include_examples 'returns wiki page'
+ include_examples 'wikis API returns wiki page'
context 'when page is not existing' do
let(:url) { "/projects/#{project.id}/wikis/unknown" }
- include_examples '404 Wiki Page Not Found'
+ include_examples 'wiki API 404 Wiki Page Not Found'
end
end
@@ -400,12 +232,12 @@ describe API::Wikis do
get api(url, user)
end
- include_examples 'returns wiki page'
+ include_examples 'wikis API returns wiki page'
context 'when page is not existing' do
let(:url) { "/projects/#{project.id}/wikis/unknown" }
- include_examples '404 Wiki Page Not Found'
+ include_examples 'wiki API 404 Wiki Page Not Found'
end
end
end
@@ -423,7 +255,7 @@ describe API::Wikis do
post(api(url), params: payload)
end
- include_examples '404 Project Not Found'
+ include_examples 'wiki API 404 Project Not Found'
end
context 'when user is developer' do
@@ -432,7 +264,7 @@ describe API::Wikis do
post(api(url, user), params: payload)
end
- include_examples '403 Forbidden'
+ include_examples 'wiki API 403 Forbidden'
end
context 'when user is maintainer' do
@@ -441,7 +273,7 @@ describe API::Wikis do
post(api(url, user), params: payload)
end
- include_examples '403 Forbidden'
+ include_examples 'wiki API 403 Forbidden'
end
end
@@ -453,7 +285,7 @@ describe API::Wikis do
post(api(url), params: payload)
end
- include_examples '404 Project Not Found'
+ include_examples 'wiki API 404 Project Not Found'
end
context 'when user is developer' do
@@ -461,7 +293,7 @@ describe API::Wikis do
project.add_developer(user)
end
- include_examples 'creates wiki page'
+ include_examples 'wikis API creates wiki page'
end
context 'when user is maintainer' do
@@ -469,7 +301,7 @@ describe API::Wikis do
project.add_maintainer(user)
end
- include_examples 'creates wiki page'
+ include_examples 'wikis API creates wiki page'
end
end
@@ -481,7 +313,7 @@ describe API::Wikis do
post(api(url), params: payload)
end
- include_examples '404 Project Not Found'
+ include_examples 'wiki API 404 Project Not Found'
end
context 'when user is developer' do
@@ -489,7 +321,7 @@ describe API::Wikis do
project.add_developer(user)
end
- include_examples 'creates wiki page'
+ include_examples 'wikis API creates wiki page'
end
context 'when user is maintainer' do
@@ -497,7 +329,7 @@ describe API::Wikis do
project.add_maintainer(user)
end
- include_examples 'creates wiki page'
+ include_examples 'wikis API creates wiki page'
end
end
end
@@ -515,7 +347,7 @@ describe API::Wikis do
put(api(url), params: payload)
end
- include_examples '404 Project Not Found'
+ include_examples 'wiki API 404 Project Not Found'
end
context 'when user is developer' do
@@ -525,7 +357,7 @@ describe API::Wikis do
put(api(url, user), params: payload)
end
- include_examples '403 Forbidden'
+ include_examples 'wiki API 403 Forbidden'
end
context 'when user is maintainer' do
@@ -535,7 +367,7 @@ describe API::Wikis do
put(api(url, user), params: payload)
end
- include_examples '403 Forbidden'
+ include_examples 'wiki API 403 Forbidden'
end
end
@@ -547,7 +379,7 @@ describe API::Wikis do
put(api(url), params: payload)
end
- include_examples '404 Project Not Found'
+ include_examples 'wiki API 404 Project Not Found'
end
context 'when user is developer' do
@@ -555,7 +387,7 @@ describe API::Wikis do
project.add_developer(user)
end
- include_examples 'updates wiki page'
+ include_examples 'wikis API updates wiki page'
context 'when page is not existing' do
let(:url) { "/projects/#{project.id}/wikis/unknown" }
@@ -564,7 +396,7 @@ describe API::Wikis do
put(api(url, user), params: payload)
end
- include_examples '404 Wiki Page Not Found'
+ include_examples 'wiki API 404 Wiki Page Not Found'
end
end
@@ -573,7 +405,7 @@ describe API::Wikis do
project.add_maintainer(user)
end
- include_examples 'updates wiki page'
+ include_examples 'wikis API updates wiki page'
context 'when page is not existing' do
let(:url) { "/projects/#{project.id}/wikis/unknown" }
@@ -582,7 +414,7 @@ describe API::Wikis do
put(api(url, user), params: payload)
end
- include_examples '404 Wiki Page Not Found'
+ include_examples 'wiki API 404 Wiki Page Not Found'
end
end
end
@@ -595,7 +427,7 @@ describe API::Wikis do
put(api(url), params: payload)
end
- include_examples '404 Project Not Found'
+ include_examples 'wiki API 404 Project Not Found'
end
context 'when user is developer' do
@@ -603,7 +435,7 @@ describe API::Wikis do
project.add_developer(user)
end
- include_examples 'updates wiki page'
+ include_examples 'wikis API updates wiki page'
context 'when page is not existing' do
let(:url) { "/projects/#{project.id}/wikis/unknown" }
@@ -612,7 +444,7 @@ describe API::Wikis do
put(api(url, user), params: payload)
end
- include_examples '404 Wiki Page Not Found'
+ include_examples 'wiki API 404 Wiki Page Not Found'
end
end
@@ -621,7 +453,7 @@ describe API::Wikis do
project.add_maintainer(user)
end
- include_examples 'updates wiki page'
+ include_examples 'wikis API updates wiki page'
context 'when page is not existing' do
let(:url) { "/projects/#{project.id}/wikis/unknown" }
@@ -630,7 +462,7 @@ describe API::Wikis do
put(api(url, user), params: payload)
end
- include_examples '404 Wiki Page Not Found'
+ include_examples 'wiki API 404 Wiki Page Not Found'
end
end
end
@@ -638,7 +470,7 @@ describe API::Wikis do
context 'when wiki belongs to a group project' do
let(:project) { create(:project, :wiki_repo, namespace: group) }
- include_examples 'updates wiki page'
+ include_examples 'wikis API updates wiki page'
end
end
@@ -654,7 +486,7 @@ describe API::Wikis do
delete(api(url))
end
- include_examples '404 Project Not Found'
+ include_examples 'wiki API 404 Project Not Found'
end
context 'when user is developer' do
@@ -664,7 +496,7 @@ describe API::Wikis do
delete(api(url, user))
end
- include_examples '403 Forbidden'
+ include_examples 'wiki API 403 Forbidden'
end
context 'when user is maintainer' do
@@ -674,7 +506,7 @@ describe API::Wikis do
delete(api(url, user))
end
- include_examples '403 Forbidden'
+ include_examples 'wiki API 403 Forbidden'
end
end
@@ -686,7 +518,7 @@ describe API::Wikis do
delete(api(url))
end
- include_examples '404 Project Not Found'
+ include_examples 'wiki API 404 Project Not Found'
end
context 'when user is developer' do
@@ -696,7 +528,7 @@ describe API::Wikis do
delete(api(url, user))
end
- include_examples '403 Forbidden'
+ include_examples 'wiki API 403 Forbidden'
end
context 'when user is maintainer' do
@@ -706,7 +538,7 @@ describe API::Wikis do
delete(api(url, user))
end
- include_examples '204 No Content'
+ include_examples 'wiki API 204 No Content'
end
end
@@ -718,7 +550,7 @@ describe API::Wikis do
delete(api(url))
end
- include_examples '404 Project Not Found'
+ include_examples 'wiki API 404 Project Not Found'
end
context 'when user is developer' do
@@ -728,7 +560,7 @@ describe API::Wikis do
delete(api(url, user))
end
- include_examples '403 Forbidden'
+ include_examples 'wiki API 403 Forbidden'
end
context 'when user is maintainer' do
@@ -738,12 +570,12 @@ describe API::Wikis do
delete(api(url, user))
end
- include_examples '204 No Content'
+ include_examples 'wiki API 204 No Content'
context 'when page is not existing' do
let(:url) { "/projects/#{project.id}/wikis/unknown" }
- include_examples '404 Wiki Page Not Found'
+ include_examples 'wiki API 404 Wiki Page Not Found'
end
end
end
@@ -755,7 +587,7 @@ describe API::Wikis do
delete(api(url, user))
end
- include_examples '204 No Content'
+ include_examples 'wiki API 204 No Content'
end
end
@@ -783,7 +615,7 @@ describe API::Wikis do
post(api(url), params: payload)
end
- include_examples '404 Project Not Found'
+ include_examples 'wiki API 404 Project Not Found'
end
context 'when user is developer' do
@@ -792,7 +624,7 @@ describe API::Wikis do
post(api(url, user), params: payload)
end
- include_examples '403 Forbidden'
+ include_examples 'wiki API 403 Forbidden'
end
context 'when user is maintainer' do
@@ -801,7 +633,7 @@ describe API::Wikis do
post(api(url, user), params: payload)
end
- include_examples '403 Forbidden'
+ include_examples 'wiki API 403 Forbidden'
end
end
@@ -813,7 +645,7 @@ describe API::Wikis do
post(api(url), params: payload)
end
- include_examples '404 Project Not Found'
+ include_examples 'wiki API 404 Project Not Found'
end
context 'when user is developer' do
@@ -821,7 +653,7 @@ describe API::Wikis do
project.add_developer(user)
end
- include_examples 'uploads wiki attachment'
+ include_examples 'wiki API uploads wiki attachment'
end
context 'when user is maintainer' do
@@ -829,7 +661,7 @@ describe API::Wikis do
project.add_maintainer(user)
end
- include_examples 'uploads wiki attachment'
+ include_examples 'wiki API uploads wiki attachment'
end
end
@@ -841,7 +673,7 @@ describe API::Wikis do
post(api(url), params: payload)
end
- include_examples '404 Project Not Found'
+ include_examples 'wiki API 404 Project Not Found'
end
context 'when user is developer' do
@@ -849,7 +681,7 @@ describe API::Wikis do
project.add_developer(user)
end
- include_examples 'uploads wiki attachment'
+ include_examples 'wiki API uploads wiki attachment'
end
context 'when user is maintainer' do
@@ -857,7 +689,7 @@ describe API::Wikis do
project.add_maintainer(user)
end
- include_examples 'uploads wiki attachment'
+ include_examples 'wiki API uploads wiki attachment'
end
end
end
diff --git a/spec/requests/boards/lists_controller_spec.rb b/spec/requests/boards/lists_controller_spec.rb
index 7451ad93efd..4d9f1dace4d 100644
--- a/spec/requests/boards/lists_controller_spec.rb
+++ b/spec/requests/boards/lists_controller_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Boards::ListsController do
+RSpec.describe Boards::ListsController do
describe '#index' do
let(:board) { create(:board) }
let(:user) { board.project.owner }
diff --git a/spec/requests/git_http_spec.rb b/spec/requests/git_http_spec.rb
index 183a3545cf2..d387c6df4cf 100644
--- a/spec/requests/git_http_spec.rb
+++ b/spec/requests/git_http_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Git HTTP requests' do
+RSpec.describe 'Git HTTP requests' do
include ProjectForksHelper
include TermsHelper
include GitHttpHelpers
diff --git a/spec/requests/groups/milestones_controller_spec.rb b/spec/requests/groups/milestones_controller_spec.rb
index 1c6743dc678..2c4d97ec4c3 100644
--- a/spec/requests/groups/milestones_controller_spec.rb
+++ b/spec/requests/groups/milestones_controller_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Groups::MilestonesController do
+RSpec.describe Groups::MilestonesController do
context 'N+1 DB queries' do
let(:user) { create(:user) }
let!(:public_group) { create(:group, :public) }
diff --git a/spec/requests/groups/registry/repositories_controller_spec.rb b/spec/requests/groups/registry/repositories_controller_spec.rb
index ab59b006be7..89cbd3e4100 100644
--- a/spec/requests/groups/registry/repositories_controller_spec.rb
+++ b/spec/requests/groups/registry/repositories_controller_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Groups::Registry::RepositoriesController do
+RSpec.describe Groups::Registry::RepositoriesController do
let_it_be(:group, reload: true) { create(:group) }
let_it_be(:user) { create(:user) }
diff --git a/spec/requests/health_controller_spec.rb b/spec/requests/health_controller_spec.rb
index 6ee716e0e89..592a57bc637 100644
--- a/spec/requests/health_controller_spec.rb
+++ b/spec/requests/health_controller_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe HealthController do
+RSpec.describe HealthController do
include StubENV
let(:token) { Gitlab::CurrentSettings.health_check_access_token }
@@ -129,6 +129,40 @@ describe HealthController do
expect(response).to have_gitlab_http_status(:service_unavailable)
expect(response.headers['X-GitLab-Custom-Error']).to eq(1)
end
+
+ context 'when DB is not accessible and connection raises an exception' do
+ before do
+ expect(Gitlab::HealthChecks::DbCheck)
+ .to receive(:readiness)
+ .and_raise(PG::ConnectionBad, 'could not connect to server')
+ end
+
+ it 'responds with 500 including the exception info' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:internal_server_error)
+ expect(response.headers['X-GitLab-Custom-Error']).to eq(1)
+ expect(json_response).to eq(
+ { 'status' => 'failed', 'message' => 'PG::ConnectionBad : could not connect to server' })
+ end
+ end
+
+ context 'when any exception happens during the probing' do
+ before do
+ expect(Gitlab::HealthChecks::Redis::RedisCheck)
+ .to receive(:readiness)
+ .and_raise(::Redis::CannotConnectError, 'Redis down')
+ end
+
+ it 'responds with 500 including the exception info' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:internal_server_error)
+ expect(response.headers['X-GitLab-Custom-Error']).to eq(1)
+ expect(json_response).to eq(
+ { 'status' => 'failed', 'message' => 'Redis::CannotConnectError : Redis down' })
+ end
+ end
end
end
diff --git a/spec/requests/import/gitlab_groups_controller_spec.rb b/spec/requests/import/gitlab_groups_controller_spec.rb
index 35f2bf0c2f7..4125c5c7c7a 100644
--- a/spec/requests/import/gitlab_groups_controller_spec.rb
+++ b/spec/requests/import/gitlab_groups_controller_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Import::GitlabGroupsController do
+RSpec.describe Import::GitlabGroupsController do
include WorkhorseHelpers
let(:import_path) { "#{Dir.tmpdir}/gitlab_groups_controller_spec" }
@@ -16,6 +16,8 @@ describe Import::GitlabGroupsController do
expect(import_export).to receive(:storage_path).and_return(import_path)
end
+ allow(Gitlab::ApplicationRateLimiter).to receive(:throttled?).and_return(false)
+
stub_uploads_object_storage(ImportExportUploader)
end
diff --git a/spec/requests/import/gitlab_projects_controller_spec.rb b/spec/requests/import/gitlab_projects_controller_spec.rb
index f16755e9766..c1ac5a9f2c8 100644
--- a/spec/requests/import/gitlab_projects_controller_spec.rb
+++ b/spec/requests/import/gitlab_projects_controller_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Import::GitlabProjectsController do
+RSpec.describe Import::GitlabProjectsController do
include WorkhorseHelpers
let(:workhorse_token) { JWT.encode({ 'iss' => 'gitlab-workhorse' }, Gitlab::Workhorse.secret, 'HS256') }
diff --git a/spec/requests/jwt_controller_spec.rb b/spec/requests/jwt_controller_spec.rb
index 617587e2fa6..fe6c0f0a556 100644
--- a/spec/requests/jwt_controller_spec.rb
+++ b/spec/requests/jwt_controller_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe JwtController do
+RSpec.describe JwtController do
include_context 'parsed logs'
let(:service) { double(execute: {}) }
diff --git a/spec/requests/lfs_http_spec.rb b/spec/requests/lfs_http_spec.rb
index f3fa5e36fec..f7771c7b0f9 100644
--- a/spec/requests/lfs_http_spec.rb
+++ b/spec/requests/lfs_http_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe 'Git LFS API and storage' do
+RSpec.describe 'Git LFS API and storage' do
include LfsHttpHelpers
include ProjectForksHelper
include WorkhorseHelpers
diff --git a/spec/requests/lfs_locks_api_spec.rb b/spec/requests/lfs_locks_api_spec.rb
index 41cf1a80205..34e345cb1cf 100644
--- a/spec/requests/lfs_locks_api_spec.rb
+++ b/spec/requests/lfs_locks_api_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Git LFS File Locking API' do
+RSpec.describe 'Git LFS File Locking API' do
include WorkhorseHelpers
let(:project) { create(:project) }
diff --git a/spec/requests/oauth_tokens_spec.rb b/spec/requests/oauth_tokens_spec.rb
index bb1c25d686e..6d944bbc783 100644
--- a/spec/requests/oauth_tokens_spec.rb
+++ b/spec/requests/oauth_tokens_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'OAuth Tokens requests' do
+RSpec.describe 'OAuth Tokens requests' do
let(:user) { create :user }
let(:application) { create :oauth_application, scopes: 'api' }
diff --git a/spec/requests/openid_connect_spec.rb b/spec/requests/openid_connect_spec.rb
index 785ab98a3d0..7b682d76150 100644
--- a/spec/requests/openid_connect_spec.rb
+++ b/spec/requests/openid_connect_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'OpenID Connect requests' do
+RSpec.describe 'OpenID Connect requests' do
let(:user) do
create(
:user,
@@ -146,8 +146,16 @@ describe 'OpenID Connect requests' do
expect(@payload['auth_time']).to eq user.current_sign_in_at.to_i
end
+ it 'has public email in email claim' do
+ expect(@payload['email']).to eq(user.public_email)
+ end
+
+ it 'has true in email_verified claim' do
+ expect(@payload['email_verified']).to eq(true)
+ end
+
it 'does not include any unknown properties' do
- expect(@payload.keys).to eq %w[iss sub aud exp iat auth_time sub_legacy]
+ expect(@payload.keys).to eq %w[iss sub aud exp iat auth_time sub_legacy email email_verified]
end
end
@@ -211,5 +219,20 @@ describe 'OpenID Connect requests' do
expect(json_response['email_verified']).to eq(true)
end
end
+
+ context 'ID token payload' do
+ before do
+ request_access_token!
+ @payload = JSON::JWT.decode(json_response['id_token'], :skip_verification)
+ end
+
+ it 'has private email in email claim' do
+ expect(@payload['email']).to eq(user.email)
+ end
+
+ it 'has true in email_verified claim' do
+ expect(@payload['email_verified']).to eq(true)
+ end
+ end
end
end
diff --git a/spec/requests/product_analytics/collector_app_attack_spec.rb b/spec/requests/product_analytics/collector_app_attack_spec.rb
new file mode 100644
index 00000000000..6f86e39c295
--- /dev/null
+++ b/spec/requests/product_analytics/collector_app_attack_spec.rb
@@ -0,0 +1,41 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'ProductAnalytics::CollectorApp throttle' do
+ include RackAttackSpecHelpers
+
+ include_context 'rack attack cache store'
+
+ let(:project1) { create(:project) }
+ let(:project2) { create(:project) }
+
+ before do
+ allow(ProductAnalyticsEvent).to receive(:create).and_return(true)
+ end
+
+ context 'per application id' do
+ let(:params) do
+ {
+ aid: project1.id,
+ eid: SecureRandom.uuid
+ }
+ end
+
+ it 'throttles the endpoint' do
+ # Allow requests under the rate limit.
+ 100.times do
+ expect_ok { get '/-/collector/i', params: params }
+ end
+
+ # Ensure its not related to ip address
+ random_next_ip
+
+ # Reject request over the limit
+ expect_rejection { get '/-/collector/i', params: params }
+
+ # But allows request for different aid
+ expect_ok { get '/-/collector/i', params: params.merge(aid: project2.id) }
+ end
+ end
+end
diff --git a/spec/requests/product_analytics/collector_app_spec.rb b/spec/requests/product_analytics/collector_app_spec.rb
new file mode 100644
index 00000000000..0491c2564f0
--- /dev/null
+++ b/spec/requests/product_analytics/collector_app_spec.rb
@@ -0,0 +1,73 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'ProductAnalytics::CollectorApp' do
+ let_it_be(:project) { create(:project) }
+ let(:params) { {} }
+
+ subject { get '/-/collector/i', params: params }
+
+ RSpec.shared_examples 'not found' do
+ it 'repond with 404' do
+ expect { subject }.not_to change { ProductAnalyticsEvent.count }
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+
+ context 'correct event params' do
+ let(:params) do
+ {
+ aid: project.id,
+ p: 'web',
+ tna: 'sp',
+ tv: 'js-2.14.0',
+ eid: SecureRandom.uuid,
+ duid: SecureRandom.uuid,
+ sid: SecureRandom.uuid,
+ vid: 4,
+ url: 'http://example.com/products/1',
+ refr: 'http://example.com/products/1',
+ lang: 'en-US',
+ cookie: true,
+ tz: 'America/Los_Angeles',
+ cs: 'UTF-8'
+ }
+ end
+
+ it 'repond with 200' do
+ expect { subject }.to change { ProductAnalyticsEvent.count }.by(1)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+
+ context 'feature disabled' do
+ before do
+ stub_feature_flags(product_analytics: false)
+ end
+
+ it_behaves_like 'not found'
+ end
+ end
+
+ context 'empty event params' do
+ it_behaves_like 'not found'
+ end
+
+ context 'invalid project id in params' do
+ let(:params) do
+ {
+ aid: '-1',
+ p: 'web',
+ tna: 'sp',
+ tv: 'js-2.14.0',
+ eid: SecureRandom.uuid,
+ duid: SecureRandom.uuid,
+ sid: SecureRandom.uuid
+ }
+ end
+
+ it_behaves_like 'not found'
+ end
+end
diff --git a/spec/requests/profiles/notifications_controller_spec.rb b/spec/requests/profiles/notifications_controller_spec.rb
index 0b2741677ab..d60cee00aef 100644
--- a/spec/requests/profiles/notifications_controller_spec.rb
+++ b/spec/requests/profiles/notifications_controller_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'view user notifications' do
+RSpec.describe 'view user notifications' do
let(:user) do
create(:user) do |user|
user.emails.create(email: 'original@example.com', confirmed_at: Time.current)
diff --git a/spec/requests/projects/cycle_analytics_events_spec.rb b/spec/requests/projects/cycle_analytics_events_spec.rb
index 773f243e733..8c3058d405c 100644
--- a/spec/requests/projects/cycle_analytics_events_spec.rb
+++ b/spec/requests/projects/cycle_analytics_events_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'value stream analytics events' do
+RSpec.describe 'value stream analytics events' do
let(:user) { create(:user) }
let(:project) { create(:project, :repository, public_builds: false) }
let(:issue) { create(:issue, project: project, created_at: 2.days.ago) }
diff --git a/spec/requests/projects/incident_management/pagerduty_incidents_spec.rb b/spec/requests/projects/incident_management/pagerduty_incidents_spec.rb
new file mode 100644
index 00000000000..c246aacb4c7
--- /dev/null
+++ b/spec/requests/projects/incident_management/pagerduty_incidents_spec.rb
@@ -0,0 +1,36 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'PagerDuty webhook' do
+ let_it_be(:project) { create(:project) }
+
+ describe 'POST /incidents/pagerduty' do
+ let(:payload) { Gitlab::Json.parse(fixture_file('pager_duty/webhook_incident_trigger.json')) }
+ let(:webhook_processor_class) { ::IncidentManagement::PagerDuty::ProcessWebhookService }
+ let(:webhook_processor) { instance_double(webhook_processor_class) }
+
+ def make_request
+ headers = { 'Content-Type' => 'application/json' }
+ post project_incidents_pagerduty_url(project, token: 'VALID-TOKEN'), params: payload.to_json, headers: headers
+ end
+
+ before do
+ allow(webhook_processor_class).to receive(:new).and_return(webhook_processor)
+ allow(webhook_processor).to receive(:execute).and_return(ServiceResponse.success(http_status: :accepted))
+ end
+
+ it 'calls PagerDuty webhook processor with correct parameters' do
+ make_request
+
+ expect(webhook_processor_class).to have_received(:new).with(project, nil, payload)
+ expect(webhook_processor).to have_received(:execute).with('VALID-TOKEN')
+ end
+
+ it 'responds with 202 Accepted' do
+ make_request
+
+ expect(response).to have_gitlab_http_status(:accepted)
+ end
+ end
+end
diff --git a/spec/requests/projects/merge_requests/creations_spec.rb b/spec/requests/projects/merge_requests/creations_spec.rb
index d192e1bca7f..0a3e663444f 100644
--- a/spec/requests/projects/merge_requests/creations_spec.rb
+++ b/spec/requests/projects/merge_requests/creations_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'merge requests creations' do
+RSpec.describe 'merge requests creations' do
describe 'GET /:namespace/:project/merge_requests/new' do
include ProjectForksHelper
diff --git a/spec/requests/projects/merge_requests_discussions_spec.rb b/spec/requests/projects/merge_requests_discussions_spec.rb
index 94e9c81bc3b..6ec586ed22c 100644
--- a/spec/requests/projects/merge_requests_discussions_spec.rb
+++ b/spec/requests/projects/merge_requests_discussions_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'merge requests discussions' do
+RSpec.describe 'merge requests discussions' do
# Further tests can be found at merge_requests_controller_spec.rb
describe 'GET /:namespace/:project/-/merge_requests/:iid/discussions' do
let(:project) { create(:project, :repository) }
diff --git a/spec/requests/projects/metrics_dashboard_spec.rb b/spec/requests/projects/metrics_dashboard_spec.rb
new file mode 100644
index 00000000000..ab35788387c
--- /dev/null
+++ b/spec/requests/projects/metrics_dashboard_spec.rb
@@ -0,0 +1,85 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'metrics dashboard page' do
+ let_it_be(:project) { create(:project) }
+ let_it_be(:environment) { create(:environment, project: project) }
+ let_it_be(:environment2) { create(:environment, project: project) }
+ let_it_be(:user) { project.owner }
+
+ before do
+ project.add_developer(user)
+ login_as(user)
+ end
+
+ describe 'GET /:namespace/:project/-/metrics' do
+ it 'returns 200' do
+ send_request
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+
+ it 'assigns environment' do
+ send_request
+ expect(assigns(:environment).id).to eq(environment.id)
+ end
+ end
+
+ describe 'GET /:namespace/:project/-/metrics?environment=:environment.id' do
+ it 'returns 200' do
+ send_request(environment: environment2.id)
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+
+ it 'assigns query param environment' do
+ send_request(environment: environment2.id)
+ expect(assigns(:environment).id).to eq(environment2.id)
+ end
+
+ context 'when query param environment does not exist' do
+ it 'responds with 404' do
+ send_request(environment: 99)
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+ end
+
+ describe 'GET /:namespace/:project/-/metrics/:dashboard_path' do
+ let(:dashboard_path) { '.gitlab/dashboards/dashboard_path.yml' }
+
+ it 'returns 200' do
+ send_request(dashboard_path: dashboard_path)
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+
+ it 'assigns environment' do
+ send_request(dashboard_path: dashboard_path)
+ expect(assigns(:environment).id).to eq(environment.id)
+ end
+ end
+
+ describe 'GET :/namespace/:project/-/metrics/:dashboard_path?environment=:environment.id' do
+ let(:dashboard_path) { '.gitlab/dashboards/dashboard_path.yml' }
+
+ it 'returns 200' do
+ send_request(dahboard_path: dashboard_path, environment: environment.id)
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+
+ it 'assigns query param environment' do
+ send_request(dashboard_path: dashboard_path, environment: environment2.id)
+ expect(assigns(:environment).id).to eq(environment2.id)
+ end
+
+ context 'when query param environment does not exist' do
+ it 'responds with 404' do
+ send_request(dashboard_path: dashboard_path, environment: 99)
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+ end
+
+ def send_request(params = {})
+ get namespace_project_metrics_dashboard_path(namespace_id: project.namespace, project_id: project, **params)
+ end
+end
diff --git a/spec/requests/projects/uploads_spec.rb b/spec/requests/projects/uploads_spec.rb
index aca4644289d..de5ef36be7e 100644
--- a/spec/requests/projects/uploads_spec.rb
+++ b/spec/requests/projects/uploads_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'File uploads' do
+RSpec.describe 'File uploads' do
include WorkhorseHelpers
let(:project) { create(:project, :public, :repository) }
diff --git a/spec/requests/rack_attack_global_spec.rb b/spec/requests/rack_attack_global_spec.rb
index 175c5dd0088..106515a6b13 100644
--- a/spec/requests/rack_attack_global_spec.rb
+++ b/spec/requests/rack_attack_global_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Rack Attack global throttles' do
+RSpec.describe 'Rack Attack global throttles' do
include RackAttackSpecHelpers
let(:settings) { Gitlab::CurrentSettings.current_application_settings }
diff --git a/spec/requests/request_profiler_spec.rb b/spec/requests/request_profiler_spec.rb
index 36ccfc6b400..7f9999bf3d2 100644
--- a/spec/requests/request_profiler_spec.rb
+++ b/spec/requests/request_profiler_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Request Profiler' do
+RSpec.describe 'Request Profiler' do
let(:user) { create(:user) }
shared_examples 'profiling a request' do |profile_type, extension|
diff --git a/spec/requests/self_monitoring_project_spec.rb b/spec/requests/self_monitoring_project_spec.rb
index 6a0258c349f..5844a27da17 100644
--- a/spec/requests/self_monitoring_project_spec.rb
+++ b/spec/requests/self_monitoring_project_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Self-Monitoring project requests' do
+RSpec.describe 'Self-Monitoring project requests' do
let(:admin) { create(:admin) }
describe 'POST #create_self_monitoring_project' do
diff --git a/spec/requests/sessions_spec.rb b/spec/requests/sessions_spec.rb
index 6697700c37d..95df181b7b0 100644
--- a/spec/requests/sessions_spec.rb
+++ b/spec/requests/sessions_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Sessions' do
+RSpec.describe 'Sessions' do
context 'authentication', :allow_forgery_protection do
let(:user) { create(:user) }
diff --git a/spec/requests/user_activity_spec.rb b/spec/requests/user_activity_spec.rb
index b24760d475b..6f0726dbdc9 100644
--- a/spec/requests/user_activity_spec.rb
+++ b/spec/requests/user_activity_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Update of user activity' do
+RSpec.describe 'Update of user activity' do
let(:user) { create(:user, last_activity_on: nil) }
before do
diff --git a/spec/requests/user_avatar_spec.rb b/spec/requests/user_avatar_spec.rb
index 66c7ce4d533..1397741af18 100644
--- a/spec/requests/user_avatar_spec.rb
+++ b/spec/requests/user_avatar_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Loading a user avatar' do
+RSpec.describe 'Loading a user avatar' do
let(:user) { create(:user, :with_avatar) }
context 'when logged in' do
diff --git a/spec/requests/user_spoofs_ip_spec.rb b/spec/requests/user_spoofs_ip_spec.rb
index 8da15665132..833dae78529 100644
--- a/spec/requests/user_spoofs_ip_spec.rb
+++ b/spec/requests/user_spoofs_ip_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'User spoofs their IP' do
+RSpec.describe 'User spoofs their IP' do
it 'raises a 400 error' do
get '/nonexistent', headers: { 'Client-Ip' => '1.2.3.4', 'X-Forwarded-For' => '5.6.7.8' }
diff --git a/spec/routing/admin/serverless/domains_controller_routing_spec.rb b/spec/routing/admin/serverless/domains_controller_routing_spec.rb
index 18c0db6add1..60b60809f4d 100644
--- a/spec/routing/admin/serverless/domains_controller_routing_spec.rb
+++ b/spec/routing/admin/serverless/domains_controller_routing_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Admin::Serverless::DomainsController do
+RSpec.describe Admin::Serverless::DomainsController do
it 'routes to #index' do
expect(get: '/admin/serverless/domains').to route_to('admin/serverless/domains#index')
end
diff --git a/spec/routing/admin_routing_spec.rb b/spec/routing/admin_routing_spec.rb
index 25216b0c712..13e371ad68a 100644
--- a/spec/routing/admin_routing_spec.rb
+++ b/spec/routing/admin_routing_spec.rb
@@ -11,7 +11,7 @@ require 'spec_helper'
# admin_user GET /admin/users/:id(.:format) admin/users#show
# PUT /admin/users/:id(.:format) admin/users#update
# DELETE /admin/users/:id(.:format) admin/users#destroy
-describe Admin::UsersController, "routing" do
+RSpec.describe Admin::UsersController, "routing" do
it "to #block" do
expect(put("/admin/users/1/block")).to route_to('admin/users#block', id: '1')
end
@@ -58,7 +58,7 @@ end
# admin_project GET /admin/projects/:id(.:format) admin/projects#show {id: /[^\/]+/}
# PUT /admin/projects/:id(.:format) admin/projects#update {id: /[^\/]+/}
# DELETE /admin/projects/:id(.:format) admin/projects#destroy {id: /[^\/]+/}
-describe Admin::ProjectsController, "routing" do
+RSpec.describe Admin::ProjectsController, "routing" do
it "to #index" do
expect(get("/admin/projects")).to route_to('admin/projects#index')
end
@@ -75,7 +75,7 @@ end
# admin_hook DELETE /admin/hooks/:id(.:format) admin/hooks#destroy
# PUT /admin/hooks/:id(.:format) admin/hooks#update
# edit_admin_hook GET /admin/hooks/:id(.:format) admin/hooks#edit
-describe Admin::HooksController, "routing" do
+RSpec.describe Admin::HooksController, "routing" do
it "to #test" do
expect(post("/admin/hooks/1/test")).to route_to('admin/hooks#test', id: '1')
end
@@ -103,7 +103,7 @@ end
# admin_hook_hook_log_retry POST /admin/hooks/:hook_id/hook_logs/:id/retry(.:format) admin/hook_logs#retry
# admin_hook_hook_log GET /admin/hooks/:hook_id/hook_logs/:id(.:format) admin/hook_logs#show
-describe Admin::HookLogsController, 'routing' do
+RSpec.describe Admin::HookLogsController, 'routing' do
it 'to #retry' do
expect(post('/admin/hooks/1/hook_logs/1/retry')).to route_to('admin/hook_logs#retry', hook_id: '1', id: '1')
end
@@ -114,27 +114,27 @@ describe Admin::HookLogsController, 'routing' do
end
# admin_background_jobs GET /admin/background_jobs(.:format) admin/background_jobs#show
-describe Admin::BackgroundJobsController, "routing" do
+RSpec.describe Admin::BackgroundJobsController, "routing" do
it "to #show" do
expect(get("/admin/background_jobs")).to route_to('admin/background_jobs#show')
end
end
# admin_root /admin(.:format) admin/dashboard#index
-describe Admin::DashboardController, "routing" do
+RSpec.describe Admin::DashboardController, "routing" do
it "to #index" do
expect(get("/admin")).to route_to('admin/dashboard#index')
end
end
# admin_health_check GET /admin/health_check(.:format) admin/health_check#show
-describe Admin::HealthCheckController, "routing" do
+RSpec.describe Admin::HealthCheckController, "routing" do
it "to #show" do
expect(get("/admin/health_check")).to route_to('admin/health_check#show')
end
end
-describe Admin::GroupsController, "routing" do
+RSpec.describe Admin::GroupsController, "routing" do
let(:name) { 'complex.group-namegit' }
it "to #index" do
@@ -151,7 +151,7 @@ describe Admin::GroupsController, "routing" do
end
end
-describe Admin::SessionsController, "routing" do
+RSpec.describe Admin::SessionsController, "routing" do
it "to #new" do
expect(get("/admin/session/new")).to route_to('admin/sessions#new')
end
diff --git a/spec/routing/environments_spec.rb b/spec/routing/environments_spec.rb
index 46d4f31dd31..5ba02c384e2 100644
--- a/spec/routing/environments_spec.rb
+++ b/spec/routing/environments_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'environments routing' do
+RSpec.describe 'environments routing' do
let(:project) { create(:project) }
let(:environment) do
diff --git a/spec/routing/git_http_routing_spec.rb b/spec/routing/git_http_routing_spec.rb
index af14e5f81cb..e5216d99eb9 100644
--- a/spec/routing/git_http_routing_spec.rb
+++ b/spec/routing/git_http_routing_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'git_http routing' do
+RSpec.describe 'git_http routing' do
include RSpec::Rails::RequestExampleGroup
describe 'wiki.git routing', 'routing' do
diff --git a/spec/routing/group_routing_spec.rb b/spec/routing/group_routing_spec.rb
index 2a8454a276d..f4d5f899519 100644
--- a/spec/routing/group_routing_spec.rb
+++ b/spec/routing/group_routing_spec.rb
@@ -2,8 +2,8 @@
require 'spec_helper'
-describe "Groups", "routing" do
- let(:group_path) { 'complex.group-namegit' }
+RSpec.shared_examples 'groups routing' do
+ let(:group_path) { 'projects.abc123' }
let!(:group) { create(:group, path: group_path) }
it "to #show" do
@@ -56,3 +56,23 @@ describe "Groups", "routing" do
expect(get('/groups/gitlabhq/-/boards')).to route_to('groups/boards#index', group_id: 'gitlabhq')
end
end
+
+RSpec.describe "Groups", "routing" do
+ context 'complex group path with dot' do
+ include_examples 'groups routing' do
+ let(:group_path) { 'complex.group-namegit' }
+ end
+ end
+
+ context 'group path starting with help' do
+ include_examples 'groups routing' do
+ let(:group_path) { 'help.abc123' }
+ end
+ end
+
+ context 'group path starting with projects' do
+ include_examples 'groups routing' do
+ let(:group_path) { 'projects.abc123' }
+ end
+ end
+end
diff --git a/spec/routing/import_routing_spec.rb b/spec/routing/import_routing_spec.rb
index 7e78a1c0cd2..15d2f32de78 100644
--- a/spec/routing/import_routing_spec.rb
+++ b/spec/routing/import_routing_spec.rb
@@ -23,7 +23,7 @@ require 'spec_helper'
# let(:actions) { [:index] }
# let(:controller) { 'issues' }
# end
-shared_examples 'importer routing' do
+RSpec.shared_examples 'importer routing' do
let(:except_actions) { [] }
let(:is_realtime) { false }
@@ -62,7 +62,7 @@ end
# realtime_changes_import_github GET /import/github/realtime_changes(.:format) import/github#jobs
# import_github POST /import/github(.:format) import/github#create
# new_import_github GET /import/github/new(.:format) import/github#new
-describe Import::GithubController, 'routing' do
+RSpec.describe Import::GithubController, 'routing' do
it_behaves_like 'importer routing' do
let(:provider) { 'github' }
let(:is_realtime) { true }
@@ -78,7 +78,7 @@ end
# realtime_changes_import_gitea GET /import/gitea/realtime_changes(.:format) import/gitea#jobs
# import_gitea POST /import/gitea(.:format) import/gitea#create
# new_import_gitea GET /import/gitea/new(.:format) import/gitea#new
-describe Import::GiteaController, 'routing' do
+RSpec.describe Import::GiteaController, 'routing' do
it_behaves_like 'importer routing' do
let(:except_actions) { [:callback] }
let(:provider) { 'gitea' }
@@ -90,25 +90,39 @@ describe Import::GiteaController, 'routing' do
end
end
-# status_import_gitlab GET /import/gitlab/status(.:format) import/gitlab#status
-# callback_import_gitlab GET /import/gitlab/callback(.:format) import/gitlab#callback
-# jobs_import_gitlab GET /import/gitlab/jobs(.:format) import/gitlab#jobs
-# import_gitlab POST /import/gitlab(.:format) import/gitlab#create
-describe Import::GitlabController, 'routing' do
+# status_import_gitlab GET /import/gitlab/status(.:format) import/gitlab#status
+# callback_import_gitlab GET /import/gitlab/callback(.:format) import/gitlab#callback
+# realtime_changes_import_gitlab GET /import/gitlab/realtime_changes(.:format) import/gitlab#realtime_changes
+# import_gitlab POST /import/gitlab(.:format) import/gitlab#create
+RSpec.describe Import::GitlabController, 'routing' do
it_behaves_like 'importer routing' do
let(:except_actions) { [:new] }
let(:provider) { 'gitlab' }
+ let(:is_realtime) { true }
end
end
-# status_import_bitbucket GET /import/bitbucket/status(.:format) import/bitbucket#status
-# callback_import_bitbucket GET /import/bitbucket/callback(.:format) import/bitbucket#callback
-# jobs_import_bitbucket GET /import/bitbucket/jobs(.:format) import/bitbucket#jobs
-# import_bitbucket POST /import/bitbucket(.:format) import/bitbucket#create
-describe Import::BitbucketController, 'routing' do
+# status_import_bitbucket GET /import/bitbucket/status(.:format) import/bitbucket#status
+# callback_import_bitbucket GET /import/bitbucket/callback(.:format) import/bitbucket#callback
+# realtime_changes_import_bitbucket GET /import/bitbucket/realtime_changes(.:format) import/bitbucket#realtime_changes
+# import_bitbucket POST /import/bitbucket(.:format) import/bitbucket#create
+RSpec.describe Import::BitbucketController, 'routing' do
it_behaves_like 'importer routing' do
let(:except_actions) { [:new] }
let(:provider) { 'bitbucket' }
+ let(:is_realtime) { true }
+ end
+end
+
+# status_import_bitbucket_server GET /import/bitbucket_server/status(.:format) import/bitbucket_server#status
+# callback_import_bitbucket_server GET /import/bitbucket_server/callback(.:format) import/bitbucket_server#callback
+# realtime_changes_import_bitbucket_server GET /import/bitbucket_server/realtime_changes(.:format) import/bitbucket_server#realtime_changes
+# new_import_bitbucket_server GET /import/bitbucket_server/new(.:format) import/bitbucket_server#new
+# import_bitbucket_server POST /import/bitbucket_server(.:format) import/bitbucket_server#create
+RSpec.describe Import::BitbucketServerController, 'routing' do
+ it_behaves_like 'importer routing' do
+ let(:provider) { 'bitbucket_server' }
+ let(:is_realtime) { true }
end
end
@@ -119,7 +133,7 @@ end
# create_user_map_import_google_code POST /import/google_code/user_map(.:format) import/google_code#create_user_map
# import_google_code POST /import/google_code(.:format) import/google_code#create
# new_import_google_code GET /import/google_code/new(.:format) import/google_code#new
-describe Import::GoogleCodeController, 'routing' do
+RSpec.describe Import::GoogleCodeController, 'routing' do
it_behaves_like 'importer routing' do
let(:except_actions) { [:callback] }
let(:provider) { 'google_code' }
@@ -138,17 +152,18 @@ describe Import::GoogleCodeController, 'routing' do
end
end
-# status_import_fogbugz GET /import/fogbugz/status(.:format) import/fogbugz#status
-# callback_import_fogbugz POST /import/fogbugz/callback(.:format) import/fogbugz#callback
-# jobs_import_fogbugz GET /import/fogbugz/jobs(.:format) import/fogbugz#jobs
-# new_user_map_import_fogbugz GET /import/fogbugz/user_map(.:format) import/fogbugz#new_user_map
-# create_user_map_import_fogbugz POST /import/fogbugz/user_map(.:format) import/fogbugz#create_user_map
-# import_fogbugz POST /import/fogbugz(.:format) import/fogbugz#create
-# new_import_fogbugz GET /import/fogbugz/new(.:format) import/fogbugz#new
-describe Import::FogbugzController, 'routing' do
+# status_import_fogbugz GET /import/fogbugz/status(.:format) import/fogbugz#status
+# callback_import_fogbugz POST /import/fogbugz/callback(.:format) import/fogbugz#callback
+# realtime_changes_import_fogbugz GET /import/fogbugz/realtime_changes(.:format) import/fogbugz#realtime_changes
+# new_user_map_import_fogbugz GET /import/fogbugz/user_map(.:format) import/fogbugz#new_user_map
+# create_user_map_import_fogbugz POST /import/fogbugz/user_map(.:format) import/fogbugz#create_user_map
+# import_fogbugz POST /import/fogbugz(.:format) import/fogbugz#create
+# new_import_fogbugz GET /import/fogbugz/new(.:format) import/fogbugz#new
+RSpec.describe Import::FogbugzController, 'routing' do
it_behaves_like 'importer routing' do
let(:except_actions) { [:callback] }
let(:provider) { 'fogbugz' }
+ let(:is_realtime) { true }
end
it 'to #callback' do
@@ -167,7 +182,7 @@ end
# import_gitlab_project POST /import/gitlab_project(.:format) import/gitlab_projects#create
# POST /import/gitlab_project(.:format) import/gitlab_projects#create
# new_import_gitlab_project GET /import/gitlab_project/new(.:format) import/gitlab_projects#new
-describe Import::GitlabProjectsController, 'routing' do
+RSpec.describe Import::GitlabProjectsController, 'routing' do
it 'to #create' do
expect(post('/import/gitlab_project')).to route_to('import/gitlab_projects#create')
end
@@ -179,7 +194,7 @@ end
# new_import_phabricator GET /import/phabricator/new(.:format) import/phabricator#new
# import_phabricator POST /import/phabricator(.:format) import/phabricator#create
-describe Import::PhabricatorController, 'routing' do
+RSpec.describe Import::PhabricatorController, 'routing' do
it 'to #create' do
expect(post("/import/phabricator")).to route_to("import/phabricator#create")
end
diff --git a/spec/routing/instance_statistics_routing_spec.rb b/spec/routing/instance_statistics_routing_spec.rb
index 48a3ac4695c..7793c5cce71 100644
--- a/spec/routing/instance_statistics_routing_spec.rb
+++ b/spec/routing/instance_statistics_routing_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Instance Statistics', 'routing' do
+RSpec.describe 'Instance Statistics', 'routing' do
include RSpec::Rails::RequestExampleGroup
it "routes '/-/instance_statistics' to dev ops score" do
diff --git a/spec/routing/notifications_routing_spec.rb b/spec/routing/notifications_routing_spec.rb
index 8c2b29aabcb..f545badcdfa 100644
--- a/spec/routing/notifications_routing_spec.rb
+++ b/spec/routing/notifications_routing_spec.rb
@@ -2,7 +2,7 @@
require "spec_helper"
-describe "notifications routing" do
+RSpec.describe "notifications routing" do
it "routes to #show" do
expect(get("/profile/notifications")).to route_to("profiles/notifications#show")
end
diff --git a/spec/routing/openid_connect_spec.rb b/spec/routing/openid_connect_spec.rb
index fc170f8986c..b5291953730 100644
--- a/spec/routing/openid_connect_spec.rb
+++ b/spec/routing/openid_connect_spec.rb
@@ -6,7 +6,7 @@ require 'spec_helper'
# jwks GET /-/jwks(.:format) doorkeeper/openid_connect/discovery#keys
# oauth_discovery_provider GET /.well-known/openid-configuration(.:format) doorkeeper/openid_connect/discovery#provider
# oauth_discovery_webfinger GET /.well-known/webfinger(.:format) doorkeeper/openid_connect/discovery#webfinger
-describe Doorkeeper::OpenidConnect::DiscoveryController, 'routing' do
+RSpec.describe Doorkeeper::OpenidConnect::DiscoveryController, 'routing' do
it "to #provider" do
expect(get('/.well-known/openid-configuration')).to route_to('doorkeeper/openid_connect/discovery#provider')
end
@@ -26,7 +26,7 @@ end
# oauth_userinfo GET /oauth/userinfo(.:format) doorkeeper/openid_connect/userinfo#show
# POST /oauth/userinfo(.:format) doorkeeper/openid_connect/userinfo#show
-describe Doorkeeper::OpenidConnect::UserinfoController, 'routing' do
+RSpec.describe Doorkeeper::OpenidConnect::UserinfoController, 'routing' do
it "to #show" do
expect(get('/oauth/userinfo')).to route_to('doorkeeper/openid_connect/userinfo#show')
end
diff --git a/spec/routing/project_routing_spec.rb b/spec/routing/project_routing_spec.rb
index 6150a9637c7..87091da0c84 100644
--- a/spec/routing/project_routing_spec.rb
+++ b/spec/routing/project_routing_spec.rb
@@ -2,72 +2,14 @@
require 'spec_helper'
-describe 'project routing' do
+RSpec.describe 'project routing' do
+ let(:base_params) { { namespace_id: 'gitlab', project_id: 'gitlabhq' } }
+
before do
allow(Project).to receive(:find_by_full_path).and_return(false)
allow(Project).to receive(:find_by_full_path).with('gitlab/gitlabhq', any_args).and_return(true)
end
- # Shared examples for a resource inside a Project
- #
- # By default it tests all the default REST actions: index, create, new, edit,
- # show, update, and destroy. You can remove actions by customizing the
- # `actions` variable.
- #
- # It also expects a `controller` variable to be available which defines both
- # the path to the resource as well as the controller name.
- #
- # Examples
- #
- # # Default behavior
- # it_behaves_like 'RESTful project resources' do
- # let(:controller) { 'issues' }
- # end
- #
- # # Customizing actions
- # it_behaves_like 'RESTful project resources' do
- # let(:actions) { [:index] }
- # let(:controller) { 'issues' }
- # end
- #
- # # Different controller name and path
- # it_behaves_like 'RESTful project resources' do
- # let(:controller) { 'pages_domains' }
- # let(:controller_path) { 'pages/domains' }
- # end
- shared_examples 'RESTful project resources' do
- let(:actions) { [:index, :create, :new, :edit, :show, :update, :destroy] }
- let(:controller_path) { controller }
-
- it 'to #index' do
- expect(get("/gitlab/gitlabhq/#{controller_path}")).to route_to("projects/#{controller}#index", namespace_id: 'gitlab', project_id: 'gitlabhq') if actions.include?(:index)
- end
-
- it 'to #create' do
- expect(post("/gitlab/gitlabhq/#{controller_path}")).to route_to("projects/#{controller}#create", namespace_id: 'gitlab', project_id: 'gitlabhq') if actions.include?(:create)
- end
-
- it 'to #new' do
- expect(get("/gitlab/gitlabhq/#{controller_path}/new")).to route_to("projects/#{controller}#new", namespace_id: 'gitlab', project_id: 'gitlabhq') if actions.include?(:new)
- end
-
- it 'to #edit' do
- expect(get("/gitlab/gitlabhq/#{controller_path}/1/edit")).to route_to("projects/#{controller}#edit", namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1') if actions.include?(:edit)
- end
-
- it 'to #show' do
- expect(get("/gitlab/gitlabhq/#{controller_path}/1")).to route_to("projects/#{controller}#show", namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1') if actions.include?(:show)
- end
-
- it 'to #update' do
- expect(put("/gitlab/gitlabhq/#{controller_path}/1")).to route_to("projects/#{controller}#update", namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1') if actions.include?(:update)
- end
-
- it 'to #destroy' do
- expect(delete("/gitlab/gitlabhq/#{controller_path}/1")).to route_to("projects/#{controller}#destroy", namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1') if actions.include?(:destroy)
- end
- end
-
# projects POST /projects(.:format) projects#create
# new_project GET /projects/new(.:format) projects#new
# files_project GET /:id/files(.:format) projects#files
@@ -77,6 +19,10 @@ describe 'project routing' do
# DELETE /:id(.:format) projects#destroy
# preview_markdown_project POST /:id/preview_markdown(.:format) projects#preview_markdown
describe ProjectsController, 'routing' do
+ it 'to #index' do
+ expect(get('/projects')).to route_to('projects#index')
+ end
+
it 'to #create' do
expect(post('/projects')).to route_to('projects#create')
end
@@ -145,25 +91,19 @@ describe 'project routing' do
end
end
- # pages_project_wikis GET /:project_id/wikis/pages(.:format) projects/wikis#pages
- # history_project_wiki GET /:project_id/wikis/:id/history(.:format) projects/wikis#history
- # project_wikis POST /:project_id/wikis(.:format) projects/wikis#create
- # edit_project_wiki GET /:project_id/wikis/:id/edit(.:format) projects/wikis#edit
- # project_wiki GET /:project_id/wikis/:id(.:format) projects/wikis#show
- # DELETE /:project_id/wikis/:id(.:format) projects/wikis#destroy
+ # project_wikis_git_access GET /:project_id/-/wikis/git_access(.:format) projects/wikis#git_access
+ # project_wikis_pages GET /:project_id/-/wikis/pages(.:format) projects/wikis#pages
+ # project_wikis_new GET /:project_id/-/wikis/new(.:format) projects/wikis#new
+ # POST /:project_id/-/wikis(.:format) projects/wikis#create
+ # project_wiki_edit GET /:project_id/-/wikis/*id/edit projects/wikis#edit
+ # project_wiki_history GET /:project_id/-/wikis/*id/history projects/wikis#history
+ # project_wiki_preview_markdown POST /:project_id/-/wikis/*id/preview_markdown projects/wikis#preview_markdown
+ # project_wiki GET /:project_id/-/wikis/*id projects/wikis#show
+ # PUT /:project_id/-/wikis/*id projects/wikis#update
+ # DELETE /:project_id/-/wikis/*id projects/wikis#destroy
describe Projects::WikisController, 'routing' do
- it 'to #pages' do
- expect(get('/gitlab/gitlabhq/-/wikis/pages')).to route_to('projects/wikis#pages', namespace_id: 'gitlab', project_id: 'gitlabhq')
- end
-
- it 'to #history' do
- expect(get('/gitlab/gitlabhq/-/wikis/1/history')).to route_to('projects/wikis#history', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1')
- end
-
- it_behaves_like 'RESTful project resources' do
- let(:actions) { [:create, :edit, :show, :destroy] }
- let(:controller) { 'wikis' }
- let(:controller_path) { '/-/wikis' }
+ it_behaves_like 'wiki routing' do
+ let(:base_path) { '/gitlab/gitlabhq/-/wikis' }
end
it_behaves_like 'redirecting a legacy project path', "/gitlab/gitlabhq/wikis", "/gitlab/gitlabhq/-/wikis"
@@ -242,10 +182,9 @@ describe 'project routing' do
# project_deploy_key PATCH /:project_id/deploy_keys/:id(.:format) deploy_keys#update
# DELETE /:project_id/deploy_keys/:id(.:format) deploy_keys#destroy
describe Projects::DeployKeysController, 'routing' do
- it_behaves_like 'RESTful project resources' do
- let(:actions) { [:index, :new, :create, :edit, :update] }
- let(:controller) { 'deploy_keys' }
- let(:controller_path) { '/-/deploy_keys' }
+ it_behaves_like 'resource routing' do
+ let(:actions) { %i[index new create edit update] }
+ let(:base_path) { '/gitlab/gitlabhq/-/deploy_keys' }
end
end
@@ -253,10 +192,9 @@ describe 'project routing' do
# POST /:project_id/protected_branches(.:format) protected_branches#create
# project_protected_branch DELETE /:project_id/protected_branches/:id(.:format) protected_branches#destroy
describe Projects::ProtectedBranchesController, 'routing' do
- it_behaves_like 'RESTful project resources' do
- let(:actions) { [:index, :create, :destroy] }
- let(:controller) { 'protected_branches' }
- let(:controller_path) { '/-/protected_branches' }
+ it_behaves_like 'resource routing' do
+ let(:actions) { %i[index create destroy] }
+ let(:base_path) { '/gitlab/gitlabhq/-/protected_branches' }
end
end
@@ -316,10 +254,9 @@ describe 'project routing' do
expect(get('/gitlab/gitlabhq/-/merge_requests/1/diffs')).to route_to('projects/merge_requests#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1', tab: 'diffs')
end
- it_behaves_like 'RESTful project resources' do
- let(:controller) { 'merge_requests' }
- let(:actions) { [:index, :edit, :show, :update] }
- let(:controller_path) { '/-/merge_requests' }
+ it_behaves_like 'resource routing' do
+ let(:actions) { %i[index edit show update] }
+ let(:base_path) { '/gitlab/gitlabhq/-/merge_requests' }
end
it_behaves_like 'redirecting a legacy project path', "/gitlab/gitlabhq/merge_requests", "/gitlab/gitlabhq/-/merge_requests"
@@ -407,6 +344,10 @@ describe 'project routing' do
it 'to #destroy' do
expect(delete('/gitlab/gitlabhq/snippets/1')).to route_to('projects/snippets#destroy', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1')
end
+
+ it 'to #show from scope routing' do
+ expect(get('/gitlab/gitlabhq/-/snippets/1')).to route_to('projects/snippets#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1')
+ end
end
# test_project_hook POST /:project_id/hooks/:id/test(.:format) hooks#test
@@ -420,9 +361,9 @@ describe 'project routing' do
expect(post('/gitlab/gitlabhq/hooks/1/test')).to route_to('projects/hooks#test', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1')
end
- it_behaves_like 'RESTful project resources' do
- let(:actions) { [:index, :create, :destroy, :edit, :update] }
- let(:controller) { 'hooks' }
+ it_behaves_like 'resource routing' do
+ let(:actions) { %i[index create destroy edit update] }
+ let(:base_path) { '/gitlab/gitlabhq/hooks' }
end
end
@@ -457,10 +398,9 @@ describe 'project routing' do
# POST /:project_id/commits(.:format) commits#create
# project_commit GET /:project_id/commits/:id(.:format) commits#show
describe Projects::CommitsController, 'routing' do
- it_behaves_like 'RESTful project resources' do
- let(:actions) { [:show] }
- let(:controller) { 'commits' }
- let(:controller_path) { '/-/commits' }
+ it_behaves_like 'resource routing' do
+ let(:actions) { %i[show] }
+ let(:base_path) { '/gitlab/gitlabhq/-/commits' }
end
it 'to #show' do
@@ -477,10 +417,9 @@ describe 'project routing' do
# PUT /:project_id/project_members/:id(.:format) project_members#update
# DELETE /:project_id/project_members/:id(.:format) project_members#destroy
describe Projects::ProjectMembersController, 'routing' do
- it_behaves_like 'RESTful project resources' do
- let(:actions) { [:index, :create, :update, :destroy] }
- let(:controller) { 'project_members' }
- let(:controller_path) { '/-/project_members' }
+ it_behaves_like 'resource routing' do
+ let(:actions) { %i[index create update destroy] }
+ let(:base_path) { '/gitlab/gitlabhq/-/project_members' }
end
end
@@ -493,10 +432,9 @@ describe 'project routing' do
# DELETE /:project_id/milestones/:id(.:format) milestones#destroy
# promote_project_milestone POST /:project_id/milestones/:id/promote milestones#promote
describe Projects::MilestonesController, 'routing' do
- it_behaves_like 'RESTful project resources' do
- let(:controller) { 'milestones' }
- let(:actions) { [:index, :create, :new, :edit, :show, :update] }
- let(:controller_path) { '/-/milestones' }
+ it_behaves_like 'resource routing' do
+ let(:actions) { %i[index create new edit show update] }
+ let(:base_path) { '/gitlab/gitlabhq/-/milestones' }
end
it 'to #promote' do
@@ -526,10 +464,9 @@ describe 'project routing' do
expect(post('/gitlab/gitlabhq/-/issues/bulk_update')).to route_to('projects/issues#bulk_update', namespace_id: 'gitlab', project_id: 'gitlabhq')
end
- it_behaves_like 'RESTful project resources' do
- let(:controller) { 'issues' }
- let(:actions) { [:index, :create, :new, :edit, :show, :update] }
- let(:controller_path) { '/-/issues' }
+ it_behaves_like 'resource routing' do
+ let(:actions) { %i[index create new edit show update] }
+ let(:base_path) { '/gitlab/gitlabhq/-/issues' }
end
it_behaves_like 'redirecting a legacy project path', "/gitlab/gitlabhq/issues", "/gitlab/gitlabhq/-/issues"
@@ -550,9 +487,9 @@ describe 'project routing' do
)
end
- it_behaves_like 'RESTful project resources' do
- let(:actions) { [:create, :destroy] }
- let(:controller) { 'notes' }
+ it_behaves_like 'resource routing' do
+ let(:actions) { %i[create destroy] }
+ let(:base_path) { '/gitlab/gitlabhq/notes' }
end
end
@@ -741,10 +678,10 @@ describe 'project routing' do
end
describe Projects::PagesDomainsController, 'routing' do
- it_behaves_like 'RESTful project resources' do
- let(:actions) { [:show, :new, :create, :destroy] }
- let(:controller) { 'pages_domains' }
- let(:controller_path) { 'pages/domains' }
+ it_behaves_like 'resource routing' do
+ let(:actions) { %i[show new create destroy] }
+ let(:base_path) { '/gitlab/gitlabhq/pages/domains' }
+ let(:id) { 'my.domain.com' }
end
it 'to #destroy with a valid domain name' do
@@ -778,6 +715,12 @@ describe 'project routing' do
end
end
+ describe Projects::Settings::OperationsController, 'routing' do
+ it 'to #reset_alerting_token' do
+ expect(post('/gitlab/gitlabhq/-/settings/operations/reset_alerting_token')).to route_to('projects/settings/operations#reset_alerting_token', namespace_id: 'gitlab', project_id: 'gitlabhq')
+ end
+ end
+
describe Projects::Settings::RepositoryController, 'routing' do
it 'to #show' do
expect(get('/gitlab/gitlabhq/-/settings/repository')).to route_to('projects/settings/repository#show', namespace_id: 'gitlab', project_id: 'gitlabhq')
@@ -872,4 +815,12 @@ describe 'project routing' do
expect(get('/gitlab/gitlabhq/-/design_management/designs/1/c6f00aa50b80887ada30a6fe517670be9f8f9ece/resized_image/small')).to route_to('application#route_not_found', unmatched_route: 'gitlab/gitlabhq/-/design_management/designs/1/c6f00aa50b80887ada30a6fe517670be9f8f9ece/resized_image/small')
end
end
+
+ describe Projects::Snippets::BlobsController, "routing" do
+ it "to #raw" do
+ expect(get('/gitlab/gitlabhq/-/snippets/1/raw/master/lib/version.rb'))
+ .to route_to('projects/snippets/blobs#raw', namespace_id: 'gitlab',
+ project_id: 'gitlabhq', snippet_id: '1', ref: 'master', path: 'lib/version.rb')
+ end
+ end
end
diff --git a/spec/routing/routing_spec.rb b/spec/routing/routing_spec.rb
index 9c3d17f7d8f..1218ae30781 100644
--- a/spec/routing/routing_spec.rb
+++ b/spec/routing/routing_spec.rb
@@ -9,7 +9,7 @@ require 'spec_helper'
# user_snippets GET /users/:username/snippets(.:format)
# user_calendar GET /users/:username/calendar(.:format)
# user_calendar_activities GET /users/:username/calendar_activities(.:format)
-describe UsersController, "routing" do
+RSpec.describe UsersController, "routing" do
it "to #show" do
allow_any_instance_of(::Constraints::UserUrlConstrainer).to receive(:matches?).and_return(true)
@@ -32,6 +32,13 @@ describe UsersController, "routing" do
expect(get("/users/User/snippets")).to route_to('users#snippets', username: 'User')
end
+ # get all the ssh-keys of a user
+ it "to #get_keys" do
+ allow_any_instance_of(::Constraints::UserUrlConstrainer).to receive(:matches?).and_return(true)
+
+ expect(get("/User.keys")).to route_to('users#ssh_keys', username: 'User')
+ end
+
it "to #calendar" do
expect(get("/users/User/calendar")).to route_to('users#calendar', username: 'User')
end
@@ -42,7 +49,7 @@ describe UsersController, "routing" do
end
# search GET /search(.:format) search#show
-describe SearchController, "routing" do
+RSpec.describe SearchController, "routing" do
it "to #show" do
expect(get("/search")).to route_to('search#show')
end
@@ -50,7 +57,7 @@ end
# gitlab_api /api API::API
# /:path Grack
-describe "Mounted Apps", "routing" do
+RSpec.describe "Mounted Apps", "routing" do
it "to API" do
expect(get("/api/issues")).to be_routable
end
@@ -67,7 +74,7 @@ end
# snippet GET /snippets/:id(.:format) snippets#show
# PUT /snippets/:id(.:format) snippets#update
# DELETE /snippets/:id(.:format) snippets#destroy
-describe SnippetsController, "routing" do
+RSpec.describe SnippetsController, "routing" do
it "to #raw" do
expect(get("/snippets/1/raw")).to route_to('snippets#raw', id: '1')
end
@@ -99,13 +106,17 @@ describe SnippetsController, "routing" do
it "to #destroy" do
expect(delete("/snippets/1")).to route_to('snippets#destroy', id: '1')
end
+
+ it 'to #show from scope routing' do
+ expect(get("/-/snippets/1")).to route_to('snippets#show', id: '1')
+ end
end
# help GET /help(.:format) help#index
# help_page GET /help/*path(.:format) help#show
# help_shortcuts GET /help/shortcuts(.:format) help#shortcuts
# help_ui GET /help/ui(.:format) help#ui
-describe HelpController, "routing" do
+RSpec.describe HelpController, "routing" do
it "to #index" do
expect(get("/help")).to route_to('help#index')
end
@@ -132,7 +143,7 @@ end
# profile_token GET /profile/token(.:format) profile#token
# profile GET /profile(.:format) profile#show
# profile_update PUT /profile/update(.:format) profile#update
-describe ProfilesController, "routing" do
+RSpec.describe ProfilesController, "routing" do
it "to #account" do
expect(get("/profile/account")).to route_to('profiles/accounts#show')
end
@@ -153,7 +164,7 @@ end
# profile_preferences GET /profile/preferences(.:format) profiles/preferences#show
# PATCH /profile/preferences(.:format) profiles/preferences#update
# PUT /profile/preferences(.:format) profiles/preferences#update
-describe Profiles::PreferencesController, 'routing' do
+RSpec.describe Profiles::PreferencesController, 'routing' do
it 'to #show' do
expect(get('/profile/preferences')).to route_to('profiles/preferences#show')
end
@@ -170,7 +181,7 @@ end
# key GET /keys/:id(.:format) keys#show
# PUT /keys/:id(.:format) keys#update
# DELETE /keys/:id(.:format) keys#destroy
-describe Profiles::KeysController, "routing" do
+RSpec.describe Profiles::KeysController, "routing" do
it "to #index" do
expect(get("/profile/keys")).to route_to('profiles/keys#index')
end
@@ -186,19 +197,12 @@ describe Profiles::KeysController, "routing" do
it "to #destroy" do
expect(delete("/profile/keys/1")).to route_to('profiles/keys#destroy', id: '1')
end
-
- # get all the ssh-keys of a user
- it "to #get_keys" do
- allow_any_instance_of(::Constraints::UserUrlConstrainer).to receive(:matches?).and_return(true)
-
- expect(get("/foo.keys")).to route_to('profiles/keys#get_keys', username: 'foo')
- end
end
# emails GET /emails(.:format) emails#index
# POST /keys(.:format) emails#create
# DELETE /keys/:id(.:format) keys#destroy
-describe Profiles::EmailsController, "routing" do
+RSpec.describe Profiles::EmailsController, "routing" do
it "to #index" do
expect(get("/profile/emails")).to route_to('profiles/emails#index')
end
@@ -213,7 +217,7 @@ describe Profiles::EmailsController, "routing" do
end
# profile_avatar DELETE /profile/avatar(.:format) profiles/avatars#destroy
-describe Profiles::AvatarsController, "routing" do
+RSpec.describe Profiles::AvatarsController, "routing" do
it "to #destroy" do
expect(delete("/profile/avatar")).to route_to('profiles/avatars#destroy')
end
@@ -222,7 +226,7 @@ end
# dashboard GET /dashboard(.:format) dashboard#show
# dashboard_issues GET /dashboard/issues(.:format) dashboard#issues
# dashboard_merge_requests GET /dashboard/merge_requests(.:format) dashboard#merge_requests
-describe DashboardController, "routing" do
+RSpec.describe DashboardController, "routing" do
it "to #index" do
expect(get("/dashboard")).to route_to('dashboard/projects#index')
end
@@ -241,13 +245,13 @@ describe DashboardController, "routing" do
end
# root / root#show
-describe RootController, 'routing' do
+RSpec.describe RootController, 'routing' do
it 'to #index' do
expect(get('/')).to route_to('root#index')
end
end
-describe "Authentication", "routing" do
+RSpec.describe "Authentication", "routing" do
it "GET /users/sign_in" do
expect(get("/users/sign_in")).to route_to('sessions#new')
end
@@ -304,7 +308,7 @@ describe "Authentication", "routing" do
end
end
-describe HealthCheckController, 'routing' do
+RSpec.describe HealthCheckController, 'routing' do
it 'to #index' do
expect(get('/health_check')).to route_to('health_check#index')
end
@@ -314,7 +318,7 @@ describe HealthCheckController, 'routing' do
end
end
-describe InvitesController, 'routing' do
+RSpec.describe InvitesController, 'routing' do
let_it_be(:member) { create(:project_member, :invited) }
it 'to #show' do
@@ -326,7 +330,7 @@ describe InvitesController, 'routing' do
end
end
-describe AbuseReportsController, 'routing' do
+RSpec.describe AbuseReportsController, 'routing' do
let_it_be(:user) { create(:user) }
it 'to #new' do
@@ -338,14 +342,14 @@ describe AbuseReportsController, 'routing' do
end
end
-describe SentNotificationsController, 'routing' do
+RSpec.describe SentNotificationsController, 'routing' do
it 'to #unsubscribe' do
expect(get("/-/sent_notifications/4bee17d4a63ed60cf5db53417e9aeb4c/unsubscribe"))
.to route_to('sent_notifications#unsubscribe', id: '4bee17d4a63ed60cf5db53417e9aeb4c')
end
end
-describe AutocompleteController, 'routing' do
+RSpec.describe AutocompleteController, 'routing' do
it 'to #users' do
expect(get("/-/autocomplete/users")).to route_to('autocomplete#users')
end
@@ -368,3 +372,10 @@ describe AutocompleteController, 'routing' do
expect(get("/autocomplete/award_emojis")).to route_to('autocomplete#award_emojis')
end
end
+
+RSpec.describe Snippets::BlobsController, "routing" do
+ it "to #raw" do
+ expect(get('/-/snippets/1/raw/master/lib/version.rb'))
+ .to route_to('snippets/blobs#raw', snippet_id: '1', ref: 'master', path: 'lib/version.rb')
+ end
+end
diff --git a/spec/routing/uploads_routing_spec.rb b/spec/routing/uploads_routing_spec.rb
index f94ae81eeb5..d1ddf8a6d6a 100644
--- a/spec/routing/uploads_routing_spec.rb
+++ b/spec/routing/uploads_routing_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Uploads', 'routing' do
+RSpec.describe 'Uploads', 'routing' do
it 'allows creating uploads for personal snippets' do
expect(post('/uploads/personal_snippet?id=1')).to route_to(
controller: 'uploads',
diff --git a/spec/rubocop/code_reuse_helpers_spec.rb b/spec/rubocop/code_reuse_helpers_spec.rb
index 2720141aad2..574a4a85a34 100644
--- a/spec/rubocop/code_reuse_helpers_spec.rb
+++ b/spec/rubocop/code_reuse_helpers_spec.rb
@@ -1,11 +1,11 @@
# frozen_string_literal: true
-require 'spec_helper'
+require 'fast_spec_helper'
require 'rubocop'
require 'parser/current'
require_relative '../../rubocop/code_reuse_helpers'
-describe RuboCop::CodeReuseHelpers do
+RSpec.describe RuboCop::CodeReuseHelpers do
def parse_source(source, path = 'foo.rb')
buffer = Parser::Source::Buffer.new(path)
buffer.source = source
@@ -69,13 +69,13 @@ describe RuboCop::CodeReuseHelpers do
describe '#in_finder?' do
it 'returns true for a node in the finders directory' do
- node = parse_source('10', Rails.root.join('app', 'finders', 'foo.rb'))
+ node = parse_source('10', rails_root_join('app', 'finders', 'foo.rb'))
expect(cop.in_finder?(node)).to eq(true)
end
it 'returns false for a node outside the finders directory' do
- node = parse_source('10', Rails.root.join('app', 'foo', 'foo.rb'))
+ node = parse_source('10', rails_root_join('app', 'foo', 'foo.rb'))
expect(cop.in_finder?(node)).to eq(false)
end
@@ -83,13 +83,13 @@ describe RuboCop::CodeReuseHelpers do
describe '#in_model?' do
it 'returns true for a node in the models directory' do
- node = parse_source('10', Rails.root.join('app', 'models', 'foo.rb'))
+ node = parse_source('10', rails_root_join('app', 'models', 'foo.rb'))
expect(cop.in_model?(node)).to eq(true)
end
it 'returns false for a node outside the models directory' do
- node = parse_source('10', Rails.root.join('app', 'foo', 'foo.rb'))
+ node = parse_source('10', rails_root_join('app', 'foo', 'foo.rb'))
expect(cop.in_model?(node)).to eq(false)
end
@@ -97,13 +97,13 @@ describe RuboCop::CodeReuseHelpers do
describe '#in_service_class?' do
it 'returns true for a node in the services directory' do
- node = parse_source('10', Rails.root.join('app', 'services', 'foo.rb'))
+ node = parse_source('10', rails_root_join('app', 'services', 'foo.rb'))
expect(cop.in_service_class?(node)).to eq(true)
end
it 'returns false for a node outside the services directory' do
- node = parse_source('10', Rails.root.join('app', 'foo', 'foo.rb'))
+ node = parse_source('10', rails_root_join('app', 'foo', 'foo.rb'))
expect(cop.in_service_class?(node)).to eq(false)
end
@@ -111,13 +111,13 @@ describe RuboCop::CodeReuseHelpers do
describe '#in_presenter?' do
it 'returns true for a node in the presenters directory' do
- node = parse_source('10', Rails.root.join('app', 'presenters', 'foo.rb'))
+ node = parse_source('10', rails_root_join('app', 'presenters', 'foo.rb'))
expect(cop.in_presenter?(node)).to eq(true)
end
it 'returns false for a node outside the presenters directory' do
- node = parse_source('10', Rails.root.join('app', 'foo', 'foo.rb'))
+ node = parse_source('10', rails_root_join('app', 'foo', 'foo.rb'))
expect(cop.in_presenter?(node)).to eq(false)
end
@@ -125,13 +125,13 @@ describe RuboCop::CodeReuseHelpers do
describe '#in_serializer?' do
it 'returns true for a node in the serializers directory' do
- node = parse_source('10', Rails.root.join('app', 'serializers', 'foo.rb'))
+ node = parse_source('10', rails_root_join('app', 'serializers', 'foo.rb'))
expect(cop.in_serializer?(node)).to eq(true)
end
it 'returns false for a node outside the serializers directory' do
- node = parse_source('10', Rails.root.join('app', 'foo', 'foo.rb'))
+ node = parse_source('10', rails_root_join('app', 'foo', 'foo.rb'))
expect(cop.in_serializer?(node)).to eq(false)
end
@@ -139,13 +139,13 @@ describe RuboCop::CodeReuseHelpers do
describe '#in_worker?' do
it 'returns true for a node in the workers directory' do
- node = parse_source('10', Rails.root.join('app', 'workers', 'foo.rb'))
+ node = parse_source('10', rails_root_join('app', 'workers', 'foo.rb'))
expect(cop.in_worker?(node)).to eq(true)
end
it 'returns false for a node outside the workers directory' do
- node = parse_source('10', Rails.root.join('app', 'foo', 'foo.rb'))
+ node = parse_source('10', rails_root_join('app', 'foo', 'foo.rb'))
expect(cop.in_worker?(node)).to eq(false)
end
@@ -153,13 +153,13 @@ describe RuboCop::CodeReuseHelpers do
describe '#in_api?' do
it 'returns true for a node in the API directory' do
- node = parse_source('10', Rails.root.join('lib', 'api', 'foo.rb'))
+ node = parse_source('10', rails_root_join('lib', 'api', 'foo.rb'))
expect(cop.in_api?(node)).to eq(true)
end
it 'returns false for a node outside the API directory' do
- node = parse_source('10', Rails.root.join('lib', 'foo', 'foo.rb'))
+ node = parse_source('10', rails_root_join('lib', 'foo', 'foo.rb'))
expect(cop.in_api?(node)).to eq(false)
end
@@ -167,21 +167,21 @@ describe RuboCop::CodeReuseHelpers do
describe '#in_directory?' do
it 'returns true for a directory in the CE app/ directory' do
- node = parse_source('10', Rails.root.join('app', 'models', 'foo.rb'))
+ node = parse_source('10', rails_root_join('app', 'models', 'foo.rb'))
expect(cop.in_directory?(node, 'models')).to eq(true)
end
it 'returns true for a directory in the EE app/ directory' do
node =
- parse_source('10', Rails.root.join('ee', 'app', 'models', 'foo.rb'))
+ parse_source('10', rails_root_join('ee', 'app', 'models', 'foo.rb'))
expect(cop.in_directory?(node, 'models')).to eq(true)
end
it 'returns false for a directory in the lib/ directory' do
node =
- parse_source('10', Rails.root.join('lib', 'models', 'foo.rb'))
+ parse_source('10', rails_root_join('lib', 'models', 'foo.rb'))
expect(cop.in_directory?(node, 'models')).to eq(false)
end
diff --git a/spec/rubocop/cop/active_record_association_reload_spec.rb b/spec/rubocop/cop/active_record_association_reload_spec.rb
index d9c8069f0c3..79053a79c5a 100644
--- a/spec/rubocop/cop/active_record_association_reload_spec.rb
+++ b/spec/rubocop/cop/active_record_association_reload_spec.rb
@@ -1,10 +1,10 @@
# frozen_string_literal: true
-require 'spec_helper'
+require 'fast_spec_helper'
require 'rubocop'
require_relative '../../../rubocop/cop/active_record_association_reload'
-describe RuboCop::Cop::ActiveRecordAssociationReload do
+RSpec.describe RuboCop::Cop::ActiveRecordAssociationReload, type: :rubocop do
include CopHelper
subject(:cop) { described_class.new }
diff --git a/spec/rubocop/cop/api/grape_api_instance_spec.rb b/spec/rubocop/cop/api/grape_api_instance_spec.rb
new file mode 100644
index 00000000000..74f175cb707
--- /dev/null
+++ b/spec/rubocop/cop/api/grape_api_instance_spec.rb
@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+
+require 'fast_spec_helper'
+require 'rubocop'
+require_relative '../../../../rubocop/cop/api/grape_api_instance'
+
+RSpec.describe RuboCop::Cop::API::GrapeAPIInstance do
+ include CopHelper
+
+ subject(:cop) { described_class.new }
+
+ it 'adds an offense when inheriting from Grape::API' do
+ inspect_source(<<~CODE)
+ class SomeAPI < Grape::API
+ end
+ CODE
+
+ expect(cop.offenses.size).to eq(1)
+ end
+
+ it 'does not add an offense when inheriting from Grape::API::Instance' do
+ inspect_source(<<~CODE)
+ class SomeAPI < Grape::API::Instance
+ end
+ CODE
+
+ expect(cop.offenses.size).to be_zero
+ end
+end
diff --git a/spec/rubocop/cop/api/grape_array_missing_coerce_spec.rb b/spec/rubocop/cop/api/grape_array_missing_coerce_spec.rb
new file mode 100644
index 00000000000..c7bb8255398
--- /dev/null
+++ b/spec/rubocop/cop/api/grape_array_missing_coerce_spec.rb
@@ -0,0 +1,62 @@
+# frozen_string_literal: true
+
+require 'fast_spec_helper'
+require 'rubocop'
+require_relative '../../../../rubocop/cop/api/grape_array_missing_coerce'
+
+RSpec.describe RuboCop::Cop::API::GrapeArrayMissingCoerce do
+ include CopHelper
+
+ subject(:cop) { described_class.new }
+
+ it 'adds an offense with a required parameter' do
+ inspect_source(<<~CODE)
+ class SomeAPI < Grape::API::Instance
+ params do
+ requires :values, type: Array[String]
+ end
+ end
+ CODE
+
+ expect(cop.offenses.size).to eq(1)
+ end
+
+ it 'adds an offense with an optional parameter' do
+ inspect_source(<<~CODE)
+ class SomeAPI < Grape::API::Instance
+ params do
+ optional :values, type: Array[String]
+ end
+ end
+ CODE
+
+ expect(cop.offenses.size).to eq(1)
+ end
+
+ it 'does not add an offense' do
+ inspect_source(<<~CODE)
+ class SomeAPI < Grape::API::Instance
+ params do
+ requires :values, type: Array[String], coerce_with: ->(val) { val.split(',').map(&:strip) }
+ requires :milestone, type: String, desc: 'Milestone title'
+ optional :assignee_id, types: [Integer, String], integer_none_any: true,
+ desc: 'Return issues which are assigned to the user with the given ID'
+ end
+ end
+ CODE
+
+ expect(cop.offenses.size).to be_zero
+ end
+
+ it 'does not add an offense for unrelated classes' do
+ inspect_source(<<~CODE)
+ class SomeClass
+ params do
+ requires :values, type: Array[String]
+ end
+ end
+ CODE
+
+ expect(cop.offenses.size).to be_zero
+ end
+end
diff --git a/spec/rubocop/cop/avoid_break_from_strong_memoize_spec.rb b/spec/rubocop/cop/avoid_break_from_strong_memoize_spec.rb
index feb85c354ef..bc9db9cafec 100644
--- a/spec/rubocop/cop/avoid_break_from_strong_memoize_spec.rb
+++ b/spec/rubocop/cop/avoid_break_from_strong_memoize_spec.rb
@@ -5,7 +5,7 @@ require 'rubocop'
require 'rubocop/rspec/support'
require_relative '../../../rubocop/cop/avoid_break_from_strong_memoize'
-describe RuboCop::Cop::AvoidBreakFromStrongMemoize do
+RSpec.describe RuboCop::Cop::AvoidBreakFromStrongMemoize do
include CopHelper
subject(:cop) { described_class.new }
diff --git a/spec/rubocop/cop/avoid_keyword_arguments_in_sidekiq_workers_spec.rb b/spec/rubocop/cop/avoid_keyword_arguments_in_sidekiq_workers_spec.rb
index 11d63d8e0ee..851493e004e 100644
--- a/spec/rubocop/cop/avoid_keyword_arguments_in_sidekiq_workers_spec.rb
+++ b/spec/rubocop/cop/avoid_keyword_arguments_in_sidekiq_workers_spec.rb
@@ -1,11 +1,11 @@
# frozen_string_literal: true
-require 'spec_helper'
+require 'fast_spec_helper'
require 'rubocop'
require 'rubocop/rspec/support'
require_relative '../../../rubocop/cop/avoid_keyword_arguments_in_sidekiq_workers'
-describe RuboCop::Cop::AvoidKeywordArgumentsInSidekiqWorkers do
+RSpec.describe RuboCop::Cop::AvoidKeywordArgumentsInSidekiqWorkers, type: :rubocop do
include CopHelper
subject(:cop) { described_class.new }
diff --git a/spec/rubocop/cop/avoid_return_from_blocks_spec.rb b/spec/rubocop/cop/avoid_return_from_blocks_spec.rb
index 919cd3d98f3..9e571bf96b9 100644
--- a/spec/rubocop/cop/avoid_return_from_blocks_spec.rb
+++ b/spec/rubocop/cop/avoid_return_from_blocks_spec.rb
@@ -5,7 +5,7 @@ require 'rubocop'
require 'rubocop/rspec/support'
require_relative '../../../rubocop/cop/avoid_return_from_blocks'
-describe RuboCop::Cop::AvoidReturnFromBlocks do
+RSpec.describe RuboCop::Cop::AvoidReturnFromBlocks do
include CopHelper
subject(:cop) { described_class.new }
diff --git a/spec/rubocop/cop/avoid_route_redirect_leading_slash_spec.rb b/spec/rubocop/cop/avoid_route_redirect_leading_slash_spec.rb
index 207c3420fbd..78bc859beda 100644
--- a/spec/rubocop/cop/avoid_route_redirect_leading_slash_spec.rb
+++ b/spec/rubocop/cop/avoid_route_redirect_leading_slash_spec.rb
@@ -1,10 +1,10 @@
# frozen_string_literal: true
-require 'spec_helper'
+require 'fast_spec_helper'
require 'rubocop'
require_relative '../../../rubocop/cop/avoid_route_redirect_leading_slash'
-describe RuboCop::Cop::AvoidRouteRedirectLeadingSlash do
+RSpec.describe RuboCop::Cop::AvoidRouteRedirectLeadingSlash, type: :rubocop do
include CopHelper
subject(:cop) { described_class.new }
diff --git a/spec/rubocop/cop/ban_catch_throw_spec.rb b/spec/rubocop/cop/ban_catch_throw_spec.rb
index b4c277fc429..1d0ccb6d262 100644
--- a/spec/rubocop/cop/ban_catch_throw_spec.rb
+++ b/spec/rubocop/cop/ban_catch_throw_spec.rb
@@ -1,13 +1,13 @@
# frozen_string_literal: true
-require 'spec_helper'
+require 'fast_spec_helper'
require 'rubocop'
require 'rubocop/rspec/support'
require_relative '../../../rubocop/cop/ban_catch_throw'
-describe RuboCop::Cop::BanCatchThrow do
+RSpec.describe RuboCop::Cop::BanCatchThrow, type: :rubocop do
include CopHelper
subject(:cop) { described_class.new }
diff --git a/spec/rubocop/cop/code_reuse/active_record_spec.rb b/spec/rubocop/cop/code_reuse/active_record_spec.rb
index 0f3d886e4b8..25eca185f26 100644
--- a/spec/rubocop/cop/code_reuse/active_record_spec.rb
+++ b/spec/rubocop/cop/code_reuse/active_record_spec.rb
@@ -1,11 +1,10 @@
# frozen_string_literal: true
-require 'spec_helper'
+require 'fast_spec_helper'
require 'rubocop'
-require 'rubocop/rspec/support'
require_relative '../../../../rubocop/cop/code_reuse/active_record'
-describe RuboCop::Cop::CodeReuse::ActiveRecord do
+RSpec.describe RuboCop::Cop::CodeReuse::ActiveRecord, type: :rubocop do
include CopHelper
subject(:cop) { described_class.new }
@@ -46,7 +45,7 @@ describe RuboCop::Cop::CodeReuse::ActiveRecord do
end
it 'does not flag the use of ActiveRecord models in a model' do
- path = Rails.root.join('app', 'models', 'foo.rb').to_s
+ path = rails_root_join('app', 'models', 'foo.rb').to_s
expect_no_offenses(<<~SOURCE, path)
def foo
@@ -56,7 +55,7 @@ describe RuboCop::Cop::CodeReuse::ActiveRecord do
end
it 'does not flag the use of ActiveRecord models in a spec' do
- path = Rails.root.join('spec', 'foo_spec.rb').to_s
+ path = rails_root_join('spec', 'foo_spec.rb').to_s
expect_no_offenses(<<~SOURCE, path)
def foo
@@ -66,10 +65,7 @@ describe RuboCop::Cop::CodeReuse::ActiveRecord do
end
it 'does not flag the use of ActiveRecord models in a background migration' do
- path = Rails
- .root
- .join('lib', 'gitlab', 'background_migration', 'foo.rb')
- .to_s
+ path = rails_root_join('lib', 'gitlab', 'background_migration', 'foo.rb').to_s
expect_no_offenses(<<~SOURCE, path)
def foo
@@ -79,7 +75,7 @@ describe RuboCop::Cop::CodeReuse::ActiveRecord do
end
it 'does not flag the use of ActiveRecord models in lib/gitlab/database' do
- path = Rails.root.join('lib', 'gitlab', 'database', 'foo.rb').to_s
+ path = rails_root_join('lib', 'gitlab', 'database', 'foo.rb').to_s
expect_no_offenses(<<~SOURCE, path)
def foo
diff --git a/spec/rubocop/cop/code_reuse/finder_spec.rb b/spec/rubocop/cop/code_reuse/finder_spec.rb
index b04e053a4c3..1935d825b19 100644
--- a/spec/rubocop/cop/code_reuse/finder_spec.rb
+++ b/spec/rubocop/cop/code_reuse/finder_spec.rb
@@ -1,11 +1,11 @@
# frozen_string_literal: true
-require 'spec_helper'
+require 'fast_spec_helper'
require 'rubocop'
require 'rubocop/rspec/support'
require_relative '../../../../rubocop/cop/code_reuse/finder'
-describe RuboCop::Cop::CodeReuse::Finder do
+RSpec.describe RuboCop::Cop::CodeReuse::Finder, type: :rubocop do
include CopHelper
subject(:cop) { described_class.new }
diff --git a/spec/rubocop/cop/code_reuse/presenter_spec.rb b/spec/rubocop/cop/code_reuse/presenter_spec.rb
index 4fe72619273..1366018ec12 100644
--- a/spec/rubocop/cop/code_reuse/presenter_spec.rb
+++ b/spec/rubocop/cop/code_reuse/presenter_spec.rb
@@ -1,11 +1,11 @@
# frozen_string_literal: true
-require 'spec_helper'
+require 'fast_spec_helper'
require 'rubocop'
require 'rubocop/rspec/support'
require_relative '../../../../rubocop/cop/code_reuse/presenter'
-describe RuboCop::Cop::CodeReuse::Presenter do
+RSpec.describe RuboCop::Cop::CodeReuse::Presenter, type: :rubocop do
include CopHelper
subject(:cop) { described_class.new }
diff --git a/spec/rubocop/cop/code_reuse/serializer_spec.rb b/spec/rubocop/cop/code_reuse/serializer_spec.rb
index 4530b15eed7..d4341cc0367 100644
--- a/spec/rubocop/cop/code_reuse/serializer_spec.rb
+++ b/spec/rubocop/cop/code_reuse/serializer_spec.rb
@@ -1,11 +1,11 @@
# frozen_string_literal: true
-require 'spec_helper'
+require 'fast_spec_helper'
require 'rubocop'
require 'rubocop/rspec/support'
require_relative '../../../../rubocop/cop/code_reuse/serializer'
-describe RuboCop::Cop::CodeReuse::Serializer do
+RSpec.describe RuboCop::Cop::CodeReuse::Serializer, type: :rubocop do
include CopHelper
subject(:cop) { described_class.new }
diff --git a/spec/rubocop/cop/code_reuse/service_class_spec.rb b/spec/rubocop/cop/code_reuse/service_class_spec.rb
index 7b8d82f332e..b018e743230 100644
--- a/spec/rubocop/cop/code_reuse/service_class_spec.rb
+++ b/spec/rubocop/cop/code_reuse/service_class_spec.rb
@@ -1,11 +1,11 @@
# frozen_string_literal: true
-require 'spec_helper'
+require 'fast_spec_helper'
require 'rubocop'
require 'rubocop/rspec/support'
require_relative '../../../../rubocop/cop/code_reuse/service_class'
-describe RuboCop::Cop::CodeReuse::ServiceClass do
+RSpec.describe RuboCop::Cop::CodeReuse::ServiceClass, type: :rubocop do
include CopHelper
subject(:cop) { described_class.new }
diff --git a/spec/rubocop/cop/code_reuse/worker_spec.rb b/spec/rubocop/cop/code_reuse/worker_spec.rb
index 97acaeb7643..1f502e554c4 100644
--- a/spec/rubocop/cop/code_reuse/worker_spec.rb
+++ b/spec/rubocop/cop/code_reuse/worker_spec.rb
@@ -1,11 +1,11 @@
# frozen_string_literal: true
-require 'spec_helper'
+require 'fast_spec_helper'
require 'rubocop'
require 'rubocop/rspec/support'
require_relative '../../../../rubocop/cop/code_reuse/worker'
-describe RuboCop::Cop::CodeReuse::Worker do
+RSpec.describe RuboCop::Cop::CodeReuse::Worker, type: :rubocop do
include CopHelper
subject(:cop) { described_class.new }
@@ -31,7 +31,7 @@ describe RuboCop::Cop::CodeReuse::Worker do
.and_return(true)
expect_offense(<<~SOURCE)
- class Foo < Grape::API
+ class Foo < Grape::API::Instance
resource :projects do
get '/' do
FooWorker.perform_async
diff --git a/spec/rubocop/cop/default_scope_spec.rb b/spec/rubocop/cop/default_scope_spec.rb
index 9520915f900..617a7f63497 100644
--- a/spec/rubocop/cop/default_scope_spec.rb
+++ b/spec/rubocop/cop/default_scope_spec.rb
@@ -1,11 +1,11 @@
# frozen_string_literal: true
-require 'spec_helper'
+require 'fast_spec_helper'
require 'rubocop'
require 'rubocop/rspec/support'
require_relative '../../../rubocop/cop/default_scope'
-describe RuboCop::Cop::DefaultScope do
+RSpec.describe RuboCop::Cop::DefaultScope, type: :rubocop do
include CopHelper
subject(:cop) { described_class.new }
diff --git a/spec/rubocop/cop/destroy_all_spec.rb b/spec/rubocop/cop/destroy_all_spec.rb
index d06c0b2f3cf..3220d44ea2b 100644
--- a/spec/rubocop/cop/destroy_all_spec.rb
+++ b/spec/rubocop/cop/destroy_all_spec.rb
@@ -1,11 +1,11 @@
# frozen_string_literal: true
-require 'spec_helper'
+require 'fast_spec_helper'
require 'rubocop'
require 'rubocop/rspec/support'
require_relative '../../../rubocop/cop/destroy_all'
-describe RuboCop::Cop::DestroyAll do
+RSpec.describe RuboCop::Cop::DestroyAll, type: :rubocop do
include CopHelper
subject(:cop) { described_class.new }
diff --git a/spec/rubocop/cop/filename_length_spec.rb b/spec/rubocop/cop/filename_length_spec.rb
index b1cc845787a..381218c61ed 100644
--- a/spec/rubocop/cop/filename_length_spec.rb
+++ b/spec/rubocop/cop/filename_length_spec.rb
@@ -5,7 +5,7 @@ require 'rubocop'
require 'rubocop/rspec/support'
require_relative '../../../rubocop/cop/filename_length'
-describe RuboCop::Cop::FilenameLength, type: :rubocop do
+RSpec.describe RuboCop::Cop::FilenameLength, type: :rubocop do
subject(:cop) { described_class.new }
it 'does not flag files with names 100 characters long' do
diff --git a/spec/rubocop/cop/gitlab/bulk_insert_spec.rb b/spec/rubocop/cop/gitlab/bulk_insert_spec.rb
index 937c709218f..2766e4f1982 100644
--- a/spec/rubocop/cop/gitlab/bulk_insert_spec.rb
+++ b/spec/rubocop/cop/gitlab/bulk_insert_spec.rb
@@ -1,11 +1,11 @@
# frozen_string_literal: true
-require 'spec_helper'
+require 'fast_spec_helper'
require 'rubocop'
require 'rubocop/rspec/support'
require_relative '../../../../rubocop/cop/gitlab/bulk_insert'
-describe RuboCop::Cop::Gitlab::BulkInsert do
+RSpec.describe RuboCop::Cop::Gitlab::BulkInsert, type: :rubocop do
include CopHelper
subject(:cop) { described_class.new }
diff --git a/spec/rubocop/cop/gitlab/change_timezone_spec.rb b/spec/rubocop/cop/gitlab/change_timezone_spec.rb
index 1e4b4048cf4..1ec5f28e811 100644
--- a/spec/rubocop/cop/gitlab/change_timezone_spec.rb
+++ b/spec/rubocop/cop/gitlab/change_timezone_spec.rb
@@ -1,11 +1,11 @@
# frozen_string_literal: true
-require 'spec_helper'
+require 'fast_spec_helper'
require 'rubocop'
require 'rubocop/rspec/support'
require_relative '../../../../rubocop/cop/gitlab/change_timzone'
-describe RuboCop::Cop::Gitlab::ChangeTimezone do
+RSpec.describe RuboCop::Cop::Gitlab::ChangeTimezone, type: :rubocop do
include CopHelper
subject(:cop) { described_class.new }
diff --git a/spec/rubocop/cop/gitlab/const_get_inherit_false_spec.rb b/spec/rubocop/cop/gitlab/const_get_inherit_false_spec.rb
index bf0434e7afe..97ed6b743f9 100644
--- a/spec/rubocop/cop/gitlab/const_get_inherit_false_spec.rb
+++ b/spec/rubocop/cop/gitlab/const_get_inherit_false_spec.rb
@@ -1,11 +1,11 @@
# frozen_string_literal: true
-require 'spec_helper'
+require 'fast_spec_helper'
require 'rubocop'
require 'rubocop/rspec/support'
require_relative '../../../../rubocop/cop/gitlab/const_get_inherit_false'
-describe RuboCop::Cop::Gitlab::ConstGetInheritFalse do
+RSpec.describe RuboCop::Cop::Gitlab::ConstGetInheritFalse, type: :rubocop do
include CopHelper
subject(:cop) { described_class.new }
diff --git a/spec/rubocop/cop/gitlab/duplicate_spec_location_spec.rb b/spec/rubocop/cop/gitlab/duplicate_spec_location_spec.rb
index 3a0a74a4713..1ed21e44290 100644
--- a/spec/rubocop/cop/gitlab/duplicate_spec_location_spec.rb
+++ b/spec/rubocop/cop/gitlab/duplicate_spec_location_spec.rb
@@ -2,12 +2,10 @@
require 'fast_spec_helper'
require 'rubocop'
-require 'rubocop/rspec/support'
require_relative '../../../../rubocop/cop/gitlab/duplicate_spec_location'
-describe RuboCop::Cop::Gitlab::DuplicateSpecLocation do
- include RuboCop::RSpec::ExpectOffense
+RSpec.describe RuboCop::Cop::Gitlab::DuplicateSpecLocation, type: :rubocop do
include CopHelper
subject(:cop) { described_class.new }
diff --git a/spec/rubocop/cop/gitlab/finder_with_find_by_spec.rb b/spec/rubocop/cop/gitlab/finder_with_find_by_spec.rb
index 7af98b66218..30ee422f420 100644
--- a/spec/rubocop/cop/gitlab/finder_with_find_by_spec.rb
+++ b/spec/rubocop/cop/gitlab/finder_with_find_by_spec.rb
@@ -1,13 +1,13 @@
# frozen_string_literal: true
-require 'spec_helper'
+require 'fast_spec_helper'
require 'rubocop'
require 'rubocop/rspec/support'
require_relative '../../../../rubocop/cop/gitlab/finder_with_find_by'
-describe RuboCop::Cop::Gitlab::FinderWithFindBy do
+RSpec.describe RuboCop::Cop::Gitlab::FinderWithFindBy, type: :rubocop do
include CopHelper
subject(:cop) { described_class.new }
diff --git a/spec/rubocop/cop/gitlab/httparty_spec.rb b/spec/rubocop/cop/gitlab/httparty_spec.rb
index 42da97679ec..379365096ba 100644
--- a/spec/rubocop/cop/gitlab/httparty_spec.rb
+++ b/spec/rubocop/cop/gitlab/httparty_spec.rb
@@ -1,11 +1,11 @@
# frozen_string_literal: true
-require 'spec_helper'
+require 'fast_spec_helper'
require 'rubocop'
require 'rubocop/rspec/support'
require_relative '../../../../rubocop/cop/gitlab/httparty'
-describe RuboCop::Cop::Gitlab::HTTParty do # rubocop:disable RSpec/FilePath
+RSpec.describe RuboCop::Cop::Gitlab::HTTParty, type: :rubocop do # rubocop:disable RSpec/FilePath
include CopHelper
subject(:cop) { described_class.new }
diff --git a/spec/rubocop/cop/gitlab/json_spec.rb b/spec/rubocop/cop/gitlab/json_spec.rb
index d64f60c8583..6f5ec07ffb1 100644
--- a/spec/rubocop/cop/gitlab/json_spec.rb
+++ b/spec/rubocop/cop/gitlab/json_spec.rb
@@ -1,11 +1,11 @@
# frozen_string_literal: true
-require 'spec_helper'
+require 'fast_spec_helper'
require 'rubocop'
require 'rubocop/rspec/support'
require_relative '../../../../rubocop/cop/gitlab/json'
-describe RuboCop::Cop::Gitlab::Json do
+RSpec.describe RuboCop::Cop::Gitlab::Json, type: :rubocop do
include CopHelper
subject(:cop) { described_class.new }
diff --git a/spec/rubocop/cop/gitlab/module_with_instance_variables_spec.rb b/spec/rubocop/cop/gitlab/module_with_instance_variables_spec.rb
index 9cb55ced1fa..3d22201c92e 100644
--- a/spec/rubocop/cop/gitlab/module_with_instance_variables_spec.rb
+++ b/spec/rubocop/cop/gitlab/module_with_instance_variables_spec.rb
@@ -1,11 +1,11 @@
# frozen_string_literal: true
-require 'spec_helper'
+require 'fast_spec_helper'
require 'rubocop'
require 'rubocop/rspec/support'
require_relative '../../../../rubocop/cop/gitlab/module_with_instance_variables'
-describe RuboCop::Cop::Gitlab::ModuleWithInstanceVariables do
+RSpec.describe RuboCop::Cop::Gitlab::ModuleWithInstanceVariables, type: :rubocop do
include CopHelper
subject(:cop) { described_class.new }
diff --git a/spec/rubocop/cop/gitlab/predicate_memoization_spec.rb b/spec/rubocop/cop/gitlab/predicate_memoization_spec.rb
index ae9466368d2..ebe984b189d 100644
--- a/spec/rubocop/cop/gitlab/predicate_memoization_spec.rb
+++ b/spec/rubocop/cop/gitlab/predicate_memoization_spec.rb
@@ -1,11 +1,11 @@
# frozen_string_literal: true
-require 'spec_helper'
+require 'fast_spec_helper'
require 'rubocop'
require 'rubocop/rspec/support'
require_relative '../../../../rubocop/cop/gitlab/predicate_memoization'
-describe RuboCop::Cop::Gitlab::PredicateMemoization do
+RSpec.describe RuboCop::Cop::Gitlab::PredicateMemoization, type: :rubocop do
include CopHelper
subject(:cop) { described_class.new }
diff --git a/spec/rubocop/cop/gitlab/rails_logger_spec.rb b/spec/rubocop/cop/gitlab/rails_logger_spec.rb
index f0158ddcc5c..0583079136b 100644
--- a/spec/rubocop/cop/gitlab/rails_logger_spec.rb
+++ b/spec/rubocop/cop/gitlab/rails_logger_spec.rb
@@ -1,11 +1,11 @@
# frozen_string_literal: true
-require 'spec_helper'
+require 'fast_spec_helper'
require 'rubocop'
require 'rubocop/rspec/support'
require_relative '../../../../rubocop/cop/gitlab/rails_logger'
-describe RuboCop::Cop::Gitlab::RailsLogger do
+RSpec.describe RuboCop::Cop::Gitlab::RailsLogger, type: :rubocop do
include CopHelper
subject(:cop) { described_class.new }
diff --git a/spec/rubocop/cop/gitlab/union_spec.rb b/spec/rubocop/cop/gitlab/union_spec.rb
index f0544fdb66e..571dcc4eeb4 100644
--- a/spec/rubocop/cop/gitlab/union_spec.rb
+++ b/spec/rubocop/cop/gitlab/union_spec.rb
@@ -1,11 +1,11 @@
# frozen_string_literal: true
-require 'spec_helper'
+require 'fast_spec_helper'
require 'rubocop'
require 'rubocop/rspec/support'
require_relative '../../../../rubocop/cop/gitlab/union'
-describe RuboCop::Cop::Gitlab::Union do
+RSpec.describe RuboCop::Cop::Gitlab::Union, type: :rubocop do
include CopHelper
subject(:cop) { described_class.new }
diff --git a/spec/rubocop/cop/graphql/authorize_types_spec.rb b/spec/rubocop/cop/graphql/authorize_types_spec.rb
index 98797a780e0..df637a26ec5 100644
--- a/spec/rubocop/cop/graphql/authorize_types_spec.rb
+++ b/spec/rubocop/cop/graphql/authorize_types_spec.rb
@@ -2,92 +2,68 @@
require 'fast_spec_helper'
require 'rubocop'
-require 'rubocop/rspec/support'
+
require_relative '../../../../rubocop/cop/graphql/authorize_types'
-describe RuboCop::Cop::Graphql::AuthorizeTypes do
- include RuboCop::RSpec::ExpectOffense
+RSpec.describe RuboCop::Cop::Graphql::AuthorizeTypes, type: :rubocop do
include CopHelper
subject(:cop) { described_class.new }
- context 'when NOT in a type folder' do
- before do
- allow(cop).to receive(:in_type?).and_return(false)
- end
-
- it 'does not add an offense even though there is no authorize call' do
- expect_no_offenses(<<~TYPE.strip)
- module Types
- class AType < BaseObject
- field :a_thing
- field :another_thing
- end
- end
- TYPE
- end
- end
-
- context 'when in a type folder' do
- before do
- allow(cop).to receive(:in_type?).and_return(true)
- end
-
- it 'adds an offense when there is no authorize call' do
- inspect_source(<<~TYPE)
- module Types
- class AType < BaseObject
- field :a_thing
- field :another_thing
- end
+ it 'adds an offense when there is no authorize call' do
+ inspect_source(<<~TYPE)
+ module Types
+ class AType < BaseObject
+ field :a_thing
+ field :another_thing
end
- TYPE
+ end
+ TYPE
- expect(cop.offenses.size).to eq 1
- end
+ expect(cop.offenses.size).to eq 1
+ end
- it 'does not add an offense for classes that have an authorize call' do
- expect_no_offenses(<<~TYPE.strip)
- module Types
- class AType < BaseObject
- graphql_name 'ATypeName'
+ it 'does not add an offense for classes that have an authorize call' do
+ expect_no_offenses(<<~TYPE.strip)
+ module Types
+ class AType < BaseObject
+ graphql_name 'ATypeName'
- authorize :an_ability, :second_ability
+ authorize :an_ability, :second_ability
- field :a_thing
- end
+ field :a_thing
end
- TYPE
- end
+ end
+ TYPE
+ end
- it 'does not add an offense for classes that only have an authorize call' do
- expect_no_offenses(<<~TYPE.strip)
- module Types
- class AType < SuperClassWithFields
- authorize :an_ability
- end
+ it 'does not add an offense for classes that only have an authorize call' do
+ expect_no_offenses(<<~TYPE.strip)
+ module Types
+ class AType < SuperClassWithFields
+ authorize :an_ability
end
- TYPE
- end
+ end
+ TYPE
+ end
- it 'does not add an offense for base types' do
- expect_no_offenses(<<~TYPE)
- module Types
- class AType < BaseEnum
- field :a_thing
- end
+ it 'does not add an offense for base types' do
+ expect_no_offenses(<<~TYPE)
+ module Types
+ class AType < BaseEnum
+ field :a_thing
end
- TYPE
- end
+ end
+ TYPE
+ end
- it 'does not add an offense for Enums' do
- expect_no_offenses(<<~TYPE)
- module Types
- class ATypeEnum < AnotherEnum
- field :a_thing
- end
+ it 'does not add an offense for Enums' do
+ expect_no_offenses(<<~TYPE)
+ module Types
+ class ATypeEnum < AnotherEnum
+ field :a_thing
end
- TYPE
- end
+ end
+ TYPE
end
end
diff --git a/spec/rubocop/cop/graphql/descriptions_spec.rb b/spec/rubocop/cop/graphql/descriptions_spec.rb
index 8cfdc05172d..3b29cd2fbee 100644
--- a/spec/rubocop/cop/graphql/descriptions_spec.rb
+++ b/spec/rubocop/cop/graphql/descriptions_spec.rb
@@ -2,11 +2,9 @@
require 'fast_spec_helper'
require 'rubocop'
-require 'rubocop/rspec/support'
require_relative '../../../../rubocop/cop/graphql/descriptions'
-describe RuboCop::Cop::Graphql::Descriptions do
- include RuboCop::RSpec::ExpectOffense
+RSpec.describe RuboCop::Cop::Graphql::Descriptions, type: :rubocop do
include CopHelper
subject(:cop) { described_class.new }
diff --git a/spec/rubocop/cop/group_public_or_visible_to_user_spec.rb b/spec/rubocop/cop/group_public_or_visible_to_user_spec.rb
index 8e027ad59f7..4e725deaafd 100644
--- a/spec/rubocop/cop/group_public_or_visible_to_user_spec.rb
+++ b/spec/rubocop/cop/group_public_or_visible_to_user_spec.rb
@@ -1,11 +1,11 @@
# frozen_string_literal: true
-require 'spec_helper'
+require 'fast_spec_helper'
require 'rubocop'
require 'rubocop/rspec/support'
require_relative '../../../rubocop/cop/group_public_or_visible_to_user'
-describe RuboCop::Cop::GroupPublicOrVisibleToUser do
+RSpec.describe RuboCop::Cop::GroupPublicOrVisibleToUser, type: :rubocop do
include CopHelper
subject(:cop) { described_class.new }
diff --git a/spec/rubocop/cop/ignored_columns_spec.rb b/spec/rubocop/cop/ignored_columns_spec.rb
index 64437765018..ec5499bbdff 100644
--- a/spec/rubocop/cop/ignored_columns_spec.rb
+++ b/spec/rubocop/cop/ignored_columns_spec.rb
@@ -1,11 +1,11 @@
# frozen_string_literal: true
-require 'spec_helper'
+require 'fast_spec_helper'
require 'rubocop'
require 'rubocop/rspec/support'
require_relative '../../../rubocop/cop/ignored_columns'
-describe RuboCop::Cop::IgnoredColumns do
+RSpec.describe RuboCop::Cop::IgnoredColumns, type: :rubocop do
include CopHelper
subject(:cop) { described_class.new }
diff --git a/spec/rubocop/cop/include_sidekiq_worker_spec.rb b/spec/rubocop/cop/include_sidekiq_worker_spec.rb
index 39965646aff..8d056c6a13e 100644
--- a/spec/rubocop/cop/include_sidekiq_worker_spec.rb
+++ b/spec/rubocop/cop/include_sidekiq_worker_spec.rb
@@ -1,13 +1,13 @@
# frozen_string_literal: true
-require 'spec_helper'
+require 'fast_spec_helper'
require 'rubocop'
require 'rubocop/rspec/support'
require_relative '../../../rubocop/cop/include_sidekiq_worker'
-describe RuboCop::Cop::IncludeSidekiqWorker do
+RSpec.describe RuboCop::Cop::IncludeSidekiqWorker, type: :rubocop do
include CopHelper
subject(:cop) { described_class.new }
diff --git a/spec/rubocop/cop/inject_enterprise_edition_module_spec.rb b/spec/rubocop/cop/inject_enterprise_edition_module_spec.rb
index f047baa3bc2..767ed994542 100644
--- a/spec/rubocop/cop/inject_enterprise_edition_module_spec.rb
+++ b/spec/rubocop/cop/inject_enterprise_edition_module_spec.rb
@@ -1,11 +1,11 @@
# frozen_string_literal: true
-require 'spec_helper'
+require 'fast_spec_helper'
require 'rubocop'
require 'rubocop/rspec/support'
require_relative '../../../rubocop/cop/inject_enterprise_edition_module'
-describe RuboCop::Cop::InjectEnterpriseEditionModule do
+RSpec.describe RuboCop::Cop::InjectEnterpriseEditionModule, type: :rubocop do
include CopHelper
subject(:cop) { described_class.new }
diff --git a/spec/rubocop/cop/line_break_around_conditional_block_spec.rb b/spec/rubocop/cop/line_break_around_conditional_block_spec.rb
index d09de4c6614..0a26ef49e35 100644
--- a/spec/rubocop/cop/line_break_around_conditional_block_spec.rb
+++ b/spec/rubocop/cop/line_break_around_conditional_block_spec.rb
@@ -1,11 +1,11 @@
# frozen_string_literal: true
-require 'spec_helper'
+require 'fast_spec_helper'
require 'rubocop'
require 'rubocop/rspec/support'
require_relative '../../../rubocop/cop/line_break_around_conditional_block'
-describe RuboCop::Cop::LineBreakAroundConditionalBlock do
+RSpec.describe RuboCop::Cop::LineBreakAroundConditionalBlock, type: :rubocop do
include CopHelper
subject(:cop) { described_class.new }
diff --git a/spec/rubocop/cop/migration/add_column_with_default_spec.rb b/spec/rubocop/cop/migration/add_column_with_default_spec.rb
index 5d4fc59fb95..50af344e0d4 100644
--- a/spec/rubocop/cop/migration/add_column_with_default_spec.rb
+++ b/spec/rubocop/cop/migration/add_column_with_default_spec.rb
@@ -1,13 +1,10 @@
# frozen_string_literal: true
-require 'spec_helper'
-
+require 'fast_spec_helper'
require 'rubocop'
-require 'rubocop/rspec/support'
-
require_relative '../../../../rubocop/cop/migration/add_column_with_default'
-describe RuboCop::Cop::Migration::AddColumnWithDefault do
+RSpec.describe RuboCop::Cop::Migration::AddColumnWithDefault, type: :rubocop do
include CopHelper
let(:cop) { described_class.new }
diff --git a/spec/rubocop/cop/migration/add_columns_to_wide_tables_spec.rb b/spec/rubocop/cop/migration/add_columns_to_wide_tables_spec.rb
index 5b179168eab..6ae4fb21126 100644
--- a/spec/rubocop/cop/migration/add_columns_to_wide_tables_spec.rb
+++ b/spec/rubocop/cop/migration/add_columns_to_wide_tables_spec.rb
@@ -1,11 +1,10 @@
# frozen_string_literal: true
-require 'spec_helper'
+require 'fast_spec_helper'
require 'rubocop'
-
require_relative '../../../../rubocop/cop/migration/add_columns_to_wide_tables'
-describe RuboCop::Cop::Migration::AddColumnsToWideTables do
+RSpec.describe RuboCop::Cop::Migration::AddColumnsToWideTables, type: :rubocop do
include CopHelper
let(:cop) { described_class.new }
diff --git a/spec/rubocop/cop/migration/add_concurrent_foreign_key_spec.rb b/spec/rubocop/cop/migration/add_concurrent_foreign_key_spec.rb
index dfc3898af24..b43d44dba65 100644
--- a/spec/rubocop/cop/migration/add_concurrent_foreign_key_spec.rb
+++ b/spec/rubocop/cop/migration/add_concurrent_foreign_key_spec.rb
@@ -1,13 +1,10 @@
# frozen_string_literal: true
-require 'spec_helper'
-
+require 'fast_spec_helper'
require 'rubocop'
-require 'rubocop/rspec/support'
-
require_relative '../../../../rubocop/cop/migration/add_concurrent_foreign_key'
-describe RuboCop::Cop::Migration::AddConcurrentForeignKey do
+RSpec.describe RuboCop::Cop::Migration::AddConcurrentForeignKey, type: :rubocop do
include CopHelper
let(:cop) { described_class.new }
diff --git a/spec/rubocop/cop/migration/add_concurrent_index_spec.rb b/spec/rubocop/cop/migration/add_concurrent_index_spec.rb
index 9812e64216f..cef5295830c 100644
--- a/spec/rubocop/cop/migration/add_concurrent_index_spec.rb
+++ b/spec/rubocop/cop/migration/add_concurrent_index_spec.rb
@@ -1,13 +1,10 @@
# frozen_string_literal: true
-require 'spec_helper'
-
+require 'fast_spec_helper'
require 'rubocop'
-require 'rubocop/rspec/support'
-
require_relative '../../../../rubocop/cop/migration/add_concurrent_index'
-describe RuboCop::Cop::Migration::AddConcurrentIndex do
+RSpec.describe RuboCop::Cop::Migration::AddConcurrentIndex, type: :rubocop do
include CopHelper
subject(:cop) { described_class.new }
diff --git a/spec/rubocop/cop/migration/add_index_spec.rb b/spec/rubocop/cop/migration/add_index_spec.rb
index ca1aadb381b..6bb78a7f3c7 100644
--- a/spec/rubocop/cop/migration/add_index_spec.rb
+++ b/spec/rubocop/cop/migration/add_index_spec.rb
@@ -1,13 +1,10 @@
# frozen_string_literal: true
-require 'spec_helper'
-
+require 'fast_spec_helper'
require 'rubocop'
-require 'rubocop/rspec/support'
-
require_relative '../../../../rubocop/cop/migration/add_index'
-describe RuboCop::Cop::Migration::AddIndex do
+RSpec.describe RuboCop::Cop::Migration::AddIndex, type: :rubocop do
include CopHelper
subject(:cop) { described_class.new }
diff --git a/spec/rubocop/cop/migration/add_limit_to_text_columns_spec.rb b/spec/rubocop/cop/migration/add_limit_to_text_columns_spec.rb
index 39ca9ace73d..5f0ca419548 100644
--- a/spec/rubocop/cop/migration/add_limit_to_text_columns_spec.rb
+++ b/spec/rubocop/cop/migration/add_limit_to_text_columns_spec.rb
@@ -1,12 +1,10 @@
# frozen_string_literal: true
-require 'spec_helper'
+require 'fast_spec_helper'
require 'rubocop'
-require 'rubocop/rspec/support'
-
require_relative '../../../../rubocop/cop/migration/add_limit_to_text_columns'
-describe RuboCop::Cop::Migration::AddLimitToTextColumns do
+RSpec.describe RuboCop::Cop::Migration::AddLimitToTextColumns, type: :rubocop do
include CopHelper
subject(:cop) { described_class.new }
diff --git a/spec/rubocop/cop/migration/add_reference_spec.rb b/spec/rubocop/cop/migration/add_reference_spec.rb
index 03348ecc744..fab70d74b55 100644
--- a/spec/rubocop/cop/migration/add_reference_spec.rb
+++ b/spec/rubocop/cop/migration/add_reference_spec.rb
@@ -1,13 +1,10 @@
# frozen_string_literal: true
-require 'spec_helper'
-
+require 'fast_spec_helper'
require 'rubocop'
-require 'rubocop/rspec/support'
-
require_relative '../../../../rubocop/cop/migration/add_reference'
-describe RuboCop::Cop::Migration::AddReference do
+RSpec.describe RuboCop::Cop::Migration::AddReference, type: :rubocop do
include CopHelper
let(:cop) { described_class.new }
diff --git a/spec/rubocop/cop/migration/add_timestamps_spec.rb b/spec/rubocop/cop/migration/add_timestamps_spec.rb
index a3314d878e5..c18b6b06ded 100644
--- a/spec/rubocop/cop/migration/add_timestamps_spec.rb
+++ b/spec/rubocop/cop/migration/add_timestamps_spec.rb
@@ -1,13 +1,10 @@
# frozen_string_literal: true
-require 'spec_helper'
-
+require 'fast_spec_helper'
require 'rubocop'
-require 'rubocop/rspec/support'
-
require_relative '../../../../rubocop/cop/migration/add_timestamps'
-describe RuboCop::Cop::Migration::AddTimestamps do
+RSpec.describe RuboCop::Cop::Migration::AddTimestamps, type: :rubocop do
include CopHelper
subject(:cop) { described_class.new }
diff --git a/spec/rubocop/cop/migration/datetime_spec.rb b/spec/rubocop/cop/migration/datetime_spec.rb
index e3023406dce..c5f6fd503ec 100644
--- a/spec/rubocop/cop/migration/datetime_spec.rb
+++ b/spec/rubocop/cop/migration/datetime_spec.rb
@@ -1,13 +1,10 @@
# frozen_string_literal: true
-require 'spec_helper'
-
+require 'fast_spec_helper'
require 'rubocop'
-require 'rubocop/rspec/support'
-
require_relative '../../../../rubocop/cop/migration/datetime'
-describe RuboCop::Cop::Migration::Datetime do
+RSpec.describe RuboCop::Cop::Migration::Datetime, type: :rubocop do
include CopHelper
subject(:cop) { described_class.new }
diff --git a/spec/rubocop/cop/migration/drop_table_spec.rb b/spec/rubocop/cop/migration/drop_table_spec.rb
index 4fe7fc8c5a5..44a1106ba62 100644
--- a/spec/rubocop/cop/migration/drop_table_spec.rb
+++ b/spec/rubocop/cop/migration/drop_table_spec.rb
@@ -7,7 +7,7 @@ require 'rubocop/rspec/support'
require_relative '../../../../rubocop/cop/migration/drop_table'
-describe RuboCop::Cop::Migration::DropTable do
+RSpec.describe RuboCop::Cop::Migration::DropTable do
include CopHelper
subject(:cop) { described_class.new }
@@ -17,20 +17,70 @@ describe RuboCop::Cop::Migration::DropTable do
allow(cop).to receive(:in_deployment_migration?).and_return(true)
end
- it 'registers an offense' do
- expect_offense(<<~PATTERN)
- def change
- drop_table :table
- ^^^^^^^^^^ #{described_class::MSG}
-
- add_column(:users, :username, :text)
+ context 'with drop_table DSL method' do
+ context 'when in down method' do
+ it 'does not register an offense' do
+ expect_no_offenses(<<~PATTERN)
+ def down
+ drop_table :table
+ end
+ PATTERN
+ end
+ end
- execute "DROP TABLE table"
- ^^^^^^^ #{described_class::MSG}
+ context 'when in up method' do
+ it 'registers an offense' do
+ expect_offense(<<~PATTERN)
+ def up
+ drop_table :table
+ ^^^^^^^^^^ #{described_class::MSG}
+ end
+ PATTERN
+ end
+ end
- execute "CREATE UNIQUE INDEX email_index ON users (email);"
+ context 'when in change method' do
+ it 'registers an offense' do
+ expect_offense(<<~PATTERN)
+ def change
+ drop_table :table
+ ^^^^^^^^^^ #{described_class::MSG}
+ end
+ PATTERN
end
- PATTERN
+ end
+ end
+
+ context 'with DROP TABLE SQL literal' do
+ it 'does not register an offense' do
+ expect_no_offenses(<<~PATTERN)
+ def down
+ execute "DROP TABLE table"
+ end
+ PATTERN
+ end
+ end
+
+ context 'when in up method' do
+ it 'registers an offense' do
+ expect_offense(<<~PATTERN)
+ def up
+ execute "DROP TABLE table"
+ ^^^^^^^ #{described_class::MSG}
+ end
+ PATTERN
+ end
+ end
+
+ context 'when in change method' do
+ it 'registers an offense' do
+ expect_offense(<<~PATTERN)
+ def change
+ execute "DROP TABLE table"
+ ^^^^^^^ #{described_class::MSG}
+ end
+ PATTERN
+ end
end
end
diff --git a/spec/rubocop/cop/migration/hash_index_spec.rb b/spec/rubocop/cop/migration/hash_index_spec.rb
index e8b05a94653..3d26ea41d08 100644
--- a/spec/rubocop/cop/migration/hash_index_spec.rb
+++ b/spec/rubocop/cop/migration/hash_index_spec.rb
@@ -1,13 +1,10 @@
# frozen_string_literal: true
-require 'spec_helper'
-
+require 'fast_spec_helper'
require 'rubocop'
-require 'rubocop/rspec/support'
-
require_relative '../../../../rubocop/cop/migration/hash_index'
-describe RuboCop::Cop::Migration::HashIndex do
+RSpec.describe RuboCop::Cop::Migration::HashIndex, type: :rubocop do
include CopHelper
subject(:cop) { described_class.new }
diff --git a/spec/rubocop/cop/migration/prevent_strings_spec.rb b/spec/rubocop/cop/migration/prevent_strings_spec.rb
index d0e97874aed..6882dca1926 100644
--- a/spec/rubocop/cop/migration/prevent_strings_spec.rb
+++ b/spec/rubocop/cop/migration/prevent_strings_spec.rb
@@ -1,12 +1,10 @@
# frozen_string_literal: true
-require 'spec_helper'
+require 'fast_spec_helper'
require 'rubocop'
-require 'rubocop/rspec/support'
-
require_relative '../../../../rubocop/cop/migration/prevent_strings'
-describe RuboCop::Cop::Migration::PreventStrings do
+RSpec.describe RuboCop::Cop::Migration::PreventStrings, type: :rubocop do
include CopHelper
subject(:cop) { described_class.new }
diff --git a/spec/rubocop/cop/migration/remove_column_spec.rb b/spec/rubocop/cop/migration/remove_column_spec.rb
index bc2fa04ce64..7ef5556d8d7 100644
--- a/spec/rubocop/cop/migration/remove_column_spec.rb
+++ b/spec/rubocop/cop/migration/remove_column_spec.rb
@@ -1,13 +1,10 @@
# frozen_string_literal: true
-require 'spec_helper'
-
+require 'fast_spec_helper'
require 'rubocop'
-require 'rubocop/rspec/support'
-
require_relative '../../../../rubocop/cop/migration/remove_column'
-describe RuboCop::Cop::Migration::RemoveColumn do
+RSpec.describe RuboCop::Cop::Migration::RemoveColumn, type: :rubocop do
include CopHelper
subject(:cop) { described_class.new }
diff --git a/spec/rubocop/cop/migration/remove_concurrent_index_spec.rb b/spec/rubocop/cop/migration/remove_concurrent_index_spec.rb
index 9de4c756f12..f70febb571d 100644
--- a/spec/rubocop/cop/migration/remove_concurrent_index_spec.rb
+++ b/spec/rubocop/cop/migration/remove_concurrent_index_spec.rb
@@ -1,13 +1,10 @@
# frozen_string_literal: true
-require 'spec_helper'
-
+require 'fast_spec_helper'
require 'rubocop'
-require 'rubocop/rspec/support'
-
require_relative '../../../../rubocop/cop/migration/remove_concurrent_index'
-describe RuboCop::Cop::Migration::RemoveConcurrentIndex do
+RSpec.describe RuboCop::Cop::Migration::RemoveConcurrentIndex, type: :rubocop do
include CopHelper
subject(:cop) { described_class.new }
diff --git a/spec/rubocop/cop/migration/remove_index_spec.rb b/spec/rubocop/cop/migration/remove_index_spec.rb
index d343d27484a..cc82306a0f5 100644
--- a/spec/rubocop/cop/migration/remove_index_spec.rb
+++ b/spec/rubocop/cop/migration/remove_index_spec.rb
@@ -1,13 +1,10 @@
# frozen_string_literal: true
-require 'spec_helper'
-
+require 'fast_spec_helper'
require 'rubocop'
-require 'rubocop/rspec/support'
-
require_relative '../../../../rubocop/cop/migration/remove_index'
-describe RuboCop::Cop::Migration::RemoveIndex do
+RSpec.describe RuboCop::Cop::Migration::RemoveIndex, type: :rubocop do
include CopHelper
subject(:cop) { described_class.new }
diff --git a/spec/rubocop/cop/migration/safer_boolean_column_spec.rb b/spec/rubocop/cop/migration/safer_boolean_column_spec.rb
index b27a4cd4f80..013f2edc5e9 100644
--- a/spec/rubocop/cop/migration/safer_boolean_column_spec.rb
+++ b/spec/rubocop/cop/migration/safer_boolean_column_spec.rb
@@ -1,13 +1,10 @@
# frozen_string_literal: true
-require 'spec_helper'
-
+require 'fast_spec_helper'
require 'rubocop'
-require 'rubocop/rspec/support'
-
require_relative '../../../../rubocop/cop/migration/safer_boolean_column'
-describe RuboCop::Cop::Migration::SaferBooleanColumn do
+RSpec.describe RuboCop::Cop::Migration::SaferBooleanColumn, type: :rubocop do
include CopHelper
subject(:cop) { described_class.new }
diff --git a/spec/rubocop/cop/migration/schedule_async_spec.rb b/spec/rubocop/cop/migration/schedule_async_spec.rb
index 3453f1c51cc..a7246dfa73a 100644
--- a/spec/rubocop/cop/migration/schedule_async_spec.rb
+++ b/spec/rubocop/cop/migration/schedule_async_spec.rb
@@ -7,7 +7,7 @@ require 'rubocop/rspec/support'
require_relative '../../../../rubocop/cop/migration/schedule_async'
-describe RuboCop::Cop::Migration::ScheduleAsync do
+RSpec.describe RuboCop::Cop::Migration::ScheduleAsync do
include CopHelper
let(:cop) { described_class.new }
diff --git a/spec/rubocop/cop/migration/timestamps_spec.rb b/spec/rubocop/cop/migration/timestamps_spec.rb
index d03c75e7cfc..14b3cb36cf8 100644
--- a/spec/rubocop/cop/migration/timestamps_spec.rb
+++ b/spec/rubocop/cop/migration/timestamps_spec.rb
@@ -1,13 +1,10 @@
# frozen_string_literal: true
-require 'spec_helper'
-
+require 'fast_spec_helper'
require 'rubocop'
-require 'rubocop/rspec/support'
-
require_relative '../../../../rubocop/cop/migration/timestamps'
-describe RuboCop::Cop::Migration::Timestamps do
+RSpec.describe RuboCop::Cop::Migration::Timestamps, type: :rubocop do
include CopHelper
subject(:cop) { described_class.new }
diff --git a/spec/rubocop/cop/migration/update_column_in_batches_spec.rb b/spec/rubocop/cop/migration/update_column_in_batches_spec.rb
index f72efaf2eb2..5d96e8048bf 100644
--- a/spec/rubocop/cop/migration/update_column_in_batches_spec.rb
+++ b/spec/rubocop/cop/migration/update_column_in_batches_spec.rb
@@ -7,7 +7,7 @@ require 'rubocop/rspec/support'
require_relative '../../../../rubocop/cop/migration/update_column_in_batches'
-describe RuboCop::Cop::Migration::UpdateColumnInBatches do
+RSpec.describe RuboCop::Cop::Migration::UpdateColumnInBatches do
let(:cop) { described_class.new }
let(:tmp_rails_root) { Rails.root.join('tmp', 'rails_root') }
let(:migration_code) do
diff --git a/spec/rubocop/cop/migration/with_lock_retries_disallowed_method_spec.rb b/spec/rubocop/cop/migration/with_lock_retries_disallowed_method_spec.rb
index 48570c1c8d8..11e4d784617 100644
--- a/spec/rubocop/cop/migration/with_lock_retries_disallowed_method_spec.rb
+++ b/spec/rubocop/cop/migration/with_lock_retries_disallowed_method_spec.rb
@@ -1,13 +1,10 @@
# frozen_string_literal: true
-require 'spec_helper'
-
+require 'fast_spec_helper'
require 'rubocop'
-require 'rubocop/rspec/support'
-
require_relative '../../../../rubocop/cop/migration/with_lock_retries_disallowed_method'
-describe RuboCop::Cop::Migration::WithLockRetriesDisallowedMethod do
+RSpec.describe RuboCop::Cop::Migration::WithLockRetriesDisallowedMethod, type: :rubocop do
include CopHelper
subject(:cop) { described_class.new }
diff --git a/spec/rubocop/cop/migration/with_lock_retries_with_change_spec.rb b/spec/rubocop/cop/migration/with_lock_retries_with_change_spec.rb
index 75a1f939a9f..93b96f3a20e 100644
--- a/spec/rubocop/cop/migration/with_lock_retries_with_change_spec.rb
+++ b/spec/rubocop/cop/migration/with_lock_retries_with_change_spec.rb
@@ -1,13 +1,10 @@
# frozen_string_literal: true
-require 'spec_helper'
-
+require 'fast_spec_helper'
require 'rubocop'
-require 'rubocop/rspec/support'
-
require_relative '../../../../rubocop/cop/migration/with_lock_retries_with_change'
-describe RuboCop::Cop::Migration::WithLockRetriesWithChange do
+RSpec.describe RuboCop::Cop::Migration::WithLockRetriesWithChange, type: :rubocop do
include CopHelper
subject(:cop) { described_class.new }
diff --git a/spec/rubocop/cop/performance/ar_count_each_spec.rb b/spec/rubocop/cop/performance/ar_count_each_spec.rb
index 534fa55dd45..33b667dd52f 100644
--- a/spec/rubocop/cop/performance/ar_count_each_spec.rb
+++ b/spec/rubocop/cop/performance/ar_count_each_spec.rb
@@ -1,11 +1,11 @@
# frozen_string_literal: true
require 'fast_spec_helper'
+require 'rubocop'
require_relative '../../../../rubocop/cop/performance/ar_count_each.rb'
-describe RuboCop::Cop::Performance::ARCountEach, type: :rubocop do
+RSpec.describe RuboCop::Cop::Performance::ARCountEach, type: :rubocop do
include CopHelper
- include ExpectOffense
subject(:cop) { described_class.new }
diff --git a/spec/rubocop/cop/performance/ar_exists_and_present_blank_spec.rb b/spec/rubocop/cop/performance/ar_exists_and_present_blank_spec.rb
index da44004f947..972f3c52153 100644
--- a/spec/rubocop/cop/performance/ar_exists_and_present_blank_spec.rb
+++ b/spec/rubocop/cop/performance/ar_exists_and_present_blank_spec.rb
@@ -1,11 +1,11 @@
# frozen_string_literal: true
require 'fast_spec_helper'
+require 'rubocop'
require_relative '../../../../rubocop/cop/performance/ar_exists_and_present_blank.rb'
-describe RuboCop::Cop::Performance::ARExistsAndPresentBlank, type: :rubocop do
+RSpec.describe RuboCop::Cop::Performance::ARExistsAndPresentBlank, type: :rubocop do
include CopHelper
- include ExpectOffense
subject(:cop) { described_class.new }
diff --git a/spec/rubocop/cop/performance/readlines_each_spec.rb b/spec/rubocop/cop/performance/readlines_each_spec.rb
index e71aaaf3056..2f222f1dc00 100644
--- a/spec/rubocop/cop/performance/readlines_each_spec.rb
+++ b/spec/rubocop/cop/performance/readlines_each_spec.rb
@@ -1,11 +1,11 @@
# frozen_string_literal: true
require 'fast_spec_helper'
+require 'rubocop'
require_relative '../../../../rubocop/cop/performance/readlines_each'
-describe RuboCop::Cop::Performance::ReadlinesEach, type: :rubocop do
+RSpec.describe RuboCop::Cop::Performance::ReadlinesEach, type: :rubocop do
include CopHelper
- include ExpectOffense
subject(:cop) { described_class.new }
diff --git a/spec/rubocop/cop/prefer_class_methods_over_module_spec.rb b/spec/rubocop/cop/prefer_class_methods_over_module_spec.rb
index 4739f0e6c47..bed8d331209 100644
--- a/spec/rubocop/cop/prefer_class_methods_over_module_spec.rb
+++ b/spec/rubocop/cop/prefer_class_methods_over_module_spec.rb
@@ -1,11 +1,11 @@
# frozen_string_literal: true
-require 'spec_helper'
+require 'fast_spec_helper'
require 'rubocop'
require 'rubocop/rspec/support'
require_relative '../../../rubocop/cop/prefer_class_methods_over_module'
-describe RuboCop::Cop::PreferClassMethodsOverModule do
+RSpec.describe RuboCop::Cop::PreferClassMethodsOverModule, type: :rubocop do
include CopHelper
subject(:cop) { described_class.new }
diff --git a/spec/rubocop/cop/project_path_helper_spec.rb b/spec/rubocop/cop/project_path_helper_spec.rb
index 1b69030c798..0e5b0b10ae6 100644
--- a/spec/rubocop/cop/project_path_helper_spec.rb
+++ b/spec/rubocop/cop/project_path_helper_spec.rb
@@ -1,13 +1,13 @@
# frozen_string_literal: true
-require 'spec_helper'
+require 'fast_spec_helper'
require 'rubocop'
require 'rubocop/rspec/support'
require_relative '../../../rubocop/cop/project_path_helper'
-describe RuboCop::Cop::ProjectPathHelper do
+RSpec.describe RuboCop::Cop::ProjectPathHelper, type: :rubocop do
include CopHelper
subject(:cop) { described_class.new }
diff --git a/spec/rubocop/cop/put_group_routes_under_scope_spec.rb b/spec/rubocop/cop/put_group_routes_under_scope_spec.rb
index c77412f91b4..2e577c9c578 100644
--- a/spec/rubocop/cop/put_group_routes_under_scope_spec.rb
+++ b/spec/rubocop/cop/put_group_routes_under_scope_spec.rb
@@ -1,10 +1,10 @@
# frozen_string_literal: true
-require 'spec_helper'
+require 'fast_spec_helper'
require 'rubocop'
require_relative '../../../rubocop/cop/put_group_routes_under_scope'
-describe RuboCop::Cop::PutGroupRoutesUnderScope do
+RSpec.describe RuboCop::Cop::PutGroupRoutesUnderScope, type: :rubocop do
include CopHelper
subject(:cop) { described_class.new }
diff --git a/spec/rubocop/cop/put_project_routes_under_scope_spec.rb b/spec/rubocop/cop/put_project_routes_under_scope_spec.rb
index 80ac4cc52e9..66e9044c453 100644
--- a/spec/rubocop/cop/put_project_routes_under_scope_spec.rb
+++ b/spec/rubocop/cop/put_project_routes_under_scope_spec.rb
@@ -1,10 +1,10 @@
# frozen_string_literal: true
-require 'spec_helper'
+require 'fast_spec_helper'
require 'rubocop'
require_relative '../../../rubocop/cop/put_project_routes_under_scope'
-describe RuboCop::Cop::PutProjectRoutesUnderScope do
+RSpec.describe RuboCop::Cop::PutProjectRoutesUnderScope, type: :rubocop do
include CopHelper
subject(:cop) { described_class.new }
diff --git a/spec/rubocop/cop/qa/ambiguous_page_object_name_spec.rb b/spec/rubocop/cop/qa/ambiguous_page_object_name_spec.rb
index 8ee720af9a5..484b5fad473 100644
--- a/spec/rubocop/cop/qa/ambiguous_page_object_name_spec.rb
+++ b/spec/rubocop/cop/qa/ambiguous_page_object_name_spec.rb
@@ -1,13 +1,13 @@
# frozen_string_literal: true
-require 'spec_helper'
+require 'fast_spec_helper'
require 'rubocop'
require 'rubocop/rspec/support'
require_relative '../../../../rubocop/cop/qa/ambiguous_page_object_name'
-describe RuboCop::Cop::QA::AmbiguousPageObjectName do
+RSpec.describe RuboCop::Cop::QA::AmbiguousPageObjectName, type: :rubocop do
include CopHelper
let(:source_file) { 'qa/page.rb' }
diff --git a/spec/rubocop/cop/qa/element_with_pattern_spec.rb b/spec/rubocop/cop/qa/element_with_pattern_spec.rb
index fee390caa9f..0e599701531 100644
--- a/spec/rubocop/cop/qa/element_with_pattern_spec.rb
+++ b/spec/rubocop/cop/qa/element_with_pattern_spec.rb
@@ -1,13 +1,13 @@
# frozen_string_literal: true
-require 'spec_helper'
+require 'fast_spec_helper'
require 'rubocop'
require 'rubocop/rspec/support'
require_relative '../../../../rubocop/cop/qa/element_with_pattern'
-describe RuboCop::Cop::QA::ElementWithPattern do
+RSpec.describe RuboCop::Cop::QA::ElementWithPattern, type: :rubocop do
include CopHelper
let(:source_file) { 'qa/page.rb' }
diff --git a/spec/rubocop/cop/rspec/any_instance_of_spec.rb b/spec/rubocop/cop/rspec/any_instance_of_spec.rb
index b16f8ac189c..11c0f109850 100644
--- a/spec/rubocop/cop/rspec/any_instance_of_spec.rb
+++ b/spec/rubocop/cop/rspec/any_instance_of_spec.rb
@@ -1,10 +1,10 @@
# frozen_string_literal: true
-require 'spec_helper'
+require 'fast_spec_helper'
require_relative '../../../../rubocop/cop/rspec/any_instance_of'
-describe RuboCop::Cop::RSpec::AnyInstanceOf do
+RSpec.describe RuboCop::Cop::RSpec::AnyInstanceOf, type: :rubocop do
include CopHelper
subject(:cop) { described_class.new }
diff --git a/spec/rubocop/cop/rspec/be_success_matcher_spec.rb b/spec/rubocop/cop/rspec/be_success_matcher_spec.rb
index 12aa7d1643e..a16cd8b634f 100644
--- a/spec/rubocop/cop/rspec/be_success_matcher_spec.rb
+++ b/spec/rubocop/cop/rspec/be_success_matcher_spec.rb
@@ -1,10 +1,10 @@
# frozen_string_literal: true
-require 'spec_helper'
+require 'fast_spec_helper'
require_relative '../../../../rubocop/cop/rspec/be_success_matcher'
-describe RuboCop::Cop::RSpec::BeSuccessMatcher do
+RSpec.describe RuboCop::Cop::RSpec::BeSuccessMatcher, type: :rubocop do
include CopHelper
let(:source_file) { 'spec/foo_spec.rb' }
diff --git a/spec/rubocop/cop/rspec/env_assignment_spec.rb b/spec/rubocop/cop/rspec/env_assignment_spec.rb
index 1c7cfb5c827..72ad584dd6f 100644
--- a/spec/rubocop/cop/rspec/env_assignment_spec.rb
+++ b/spec/rubocop/cop/rspec/env_assignment_spec.rb
@@ -1,13 +1,13 @@
# frozen_string_literal: true
-require 'spec_helper'
+require 'fast_spec_helper'
require 'rubocop'
require 'rubocop/rspec/support'
require_relative '../../../../rubocop/cop/rspec/env_assignment'
-describe RuboCop::Cop::RSpec::EnvAssignment do
+RSpec.describe RuboCop::Cop::RSpec::EnvAssignment, type: :rubocop do
include CopHelper
offense_call_single_quotes_key = %(ENV['FOO'] = 'bar').freeze
diff --git a/spec/rubocop/cop/rspec/factories_in_migration_specs_spec.rb b/spec/rubocop/cop/rspec/factories_in_migration_specs_spec.rb
index 20013519db4..1bb93402a5b 100644
--- a/spec/rubocop/cop/rspec/factories_in_migration_specs_spec.rb
+++ b/spec/rubocop/cop/rspec/factories_in_migration_specs_spec.rb
@@ -1,13 +1,13 @@
# frozen_string_literal: true
-require 'spec_helper'
+require 'fast_spec_helper'
require 'rubocop'
require 'rubocop/rspec/support'
require_relative '../../../../rubocop/cop/rspec/factories_in_migration_specs'
-describe RuboCop::Cop::RSpec::FactoriesInMigrationSpecs do
+RSpec.describe RuboCop::Cop::RSpec::FactoriesInMigrationSpecs, type: :rubocop do
include CopHelper
subject(:cop) { described_class.new }
diff --git a/spec/rubocop/cop/rspec/have_gitlab_http_status_spec.rb b/spec/rubocop/cop/rspec/have_gitlab_http_status_spec.rb
index 4aa45e66ca7..f6040350dc0 100644
--- a/spec/rubocop/cop/rspec/have_gitlab_http_status_spec.rb
+++ b/spec/rubocop/cop/rspec/have_gitlab_http_status_spec.rb
@@ -8,7 +8,7 @@ require 'rubocop/rspec/support'
require_relative '../../../../rubocop/cop/rspec/have_gitlab_http_status'
-describe RuboCop::Cop::RSpec::HaveGitlabHttpStatus do
+RSpec.describe RuboCop::Cop::RSpec::HaveGitlabHttpStatus do
include CopHelper
using RSpec::Parameterized::TableSyntax
diff --git a/spec/rubocop/cop/rspec/modify_sidekiq_middleware_spec.rb b/spec/rubocop/cop/rspec/modify_sidekiq_middleware_spec.rb
index d3d323b6643..ca47bba4264 100644
--- a/spec/rubocop/cop/rspec/modify_sidekiq_middleware_spec.rb
+++ b/spec/rubocop/cop/rspec/modify_sidekiq_middleware_spec.rb
@@ -4,9 +4,8 @@ require 'fast_spec_helper'
require 'rubocop'
require_relative '../../../../rubocop/cop/rspec/modify_sidekiq_middleware'
-describe RuboCop::Cop::RSpec::ModifySidekiqMiddleware, type: :rubocop do
+RSpec.describe RuboCop::Cop::RSpec::ModifySidekiqMiddleware, type: :rubocop do
include CopHelper
- include ExpectOffense
subject(:cop) { described_class.new }
diff --git a/spec/rubocop/cop/rspec/top_level_describe_path_spec.rb b/spec/rubocop/cop/rspec/top_level_describe_path_spec.rb
index ee6b6d39cb4..92a76bde243 100644
--- a/spec/rubocop/cop/rspec/top_level_describe_path_spec.rb
+++ b/spec/rubocop/cop/rspec/top_level_describe_path_spec.rb
@@ -1,14 +1,10 @@
# frozen_string_literal: true
require 'fast_spec_helper'
-
require 'rubocop'
-require 'rubocop/rspec/support'
-
require_relative '../../../../rubocop/cop/rspec/top_level_describe_path'
-describe RuboCop::Cop::RSpec::TopLevelDescribePath do
- include RuboCop::RSpec::ExpectOffense
+RSpec.describe RuboCop::Cop::RSpec::TopLevelDescribePath, type: :rubocop do
include CopHelper
subject(:cop) { described_class.new }
diff --git a/spec/rubocop/cop/ruby_interpolation_in_translation_spec.rb b/spec/rubocop/cop/ruby_interpolation_in_translation_spec.rb
index 7bd50866577..938ddc7fb6a 100644
--- a/spec/rubocop/cop/ruby_interpolation_in_translation_spec.rb
+++ b/spec/rubocop/cop/ruby_interpolation_in_translation_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'spec_helper'
+require 'fast_spec_helper'
require 'rubocop'
require 'rubocop/rspec/support'
@@ -9,7 +9,7 @@ require_relative '../../../rubocop/cop/ruby_interpolation_in_translation'
# Disabling interpolation check as we deliberately want to have #{} in strings.
# rubocop:disable Lint/InterpolationCheck
-describe RuboCop::Cop::RubyInterpolationInTranslation do
+RSpec.describe RuboCop::Cop::RubyInterpolationInTranslation, type: :rubocop do
subject(:cop) { described_class.new }
it 'does not add an offence for a regular messages' do
diff --git a/spec/rubocop/cop/safe_params_spec.rb b/spec/rubocop/cop/safe_params_spec.rb
index 4f02b8e9008..c4f683a41bf 100644
--- a/spec/rubocop/cop/safe_params_spec.rb
+++ b/spec/rubocop/cop/safe_params_spec.rb
@@ -1,11 +1,11 @@
# frozen_string_literal: true
-require 'spec_helper'
+require 'fast_spec_helper'
require 'rubocop'
require 'rubocop/rspec/support'
require_relative '../../../rubocop/cop/safe_params'
-describe RuboCop::Cop::SafeParams do
+RSpec.describe RuboCop::Cop::SafeParams, type: :rubocop do
include CopHelper
subject(:cop) { described_class.new }
diff --git a/spec/rubocop/cop/scalability/bulk_perform_with_context_spec.rb b/spec/rubocop/cop/scalability/bulk_perform_with_context_spec.rb
index 61603d0100e..48964ab76e0 100644
--- a/spec/rubocop/cop/scalability/bulk_perform_with_context_spec.rb
+++ b/spec/rubocop/cop/scalability/bulk_perform_with_context_spec.rb
@@ -4,9 +4,8 @@ require 'fast_spec_helper'
require 'rubocop'
require_relative '../../../../rubocop/cop/scalability/bulk_perform_with_context'
-describe RuboCop::Cop::Scalability::BulkPerformWithContext, type: :rubocop do
+RSpec.describe RuboCop::Cop::Scalability::BulkPerformWithContext, type: :rubocop do
include CopHelper
- include ExpectOffense
subject(:cop) { described_class.new }
diff --git a/spec/rubocop/cop/scalability/cron_worker_context_spec.rb b/spec/rubocop/cop/scalability/cron_worker_context_spec.rb
index e917d33b1e5..4b4ee7258f1 100644
--- a/spec/rubocop/cop/scalability/cron_worker_context_spec.rb
+++ b/spec/rubocop/cop/scalability/cron_worker_context_spec.rb
@@ -4,9 +4,8 @@ require 'fast_spec_helper'
require 'rubocop'
require_relative '../../../../rubocop/cop/scalability/cron_worker_context'
-describe RuboCop::Cop::Scalability::CronWorkerContext, type: :rubocop do
+RSpec.describe RuboCop::Cop::Scalability::CronWorkerContext, type: :rubocop do
include CopHelper
- include ExpectOffense
subject(:cop) { described_class.new }
diff --git a/spec/rubocop/cop/scalability/file_uploads_spec.rb b/spec/rubocop/cop/scalability/file_uploads_spec.rb
index b0be9ac2b51..ed826728681 100644
--- a/spec/rubocop/cop/scalability/file_uploads_spec.rb
+++ b/spec/rubocop/cop/scalability/file_uploads_spec.rb
@@ -4,9 +4,8 @@ require 'fast_spec_helper'
require 'rubocop'
require_relative '../../../../rubocop/cop/scalability/file_uploads'
-describe RuboCop::Cop::Scalability::FileUploads, type: :rubocop do
+RSpec.describe RuboCop::Cop::Scalability::FileUploads, type: :rubocop do
include CopHelper
- include ExpectOffense
subject(:cop) { described_class.new }
diff --git a/spec/rubocop/cop/scalability/idempotent_worker_spec.rb b/spec/rubocop/cop/scalability/idempotent_worker_spec.rb
index 73cacc984e9..9197cc954f5 100644
--- a/spec/rubocop/cop/scalability/idempotent_worker_spec.rb
+++ b/spec/rubocop/cop/scalability/idempotent_worker_spec.rb
@@ -4,9 +4,8 @@ require 'fast_spec_helper'
require 'rubocop'
require_relative '../../../../rubocop/cop/scalability/idempotent_worker'
-describe RuboCop::Cop::Scalability::IdempotentWorker, type: :rubocop do
+RSpec.describe RuboCop::Cop::Scalability::IdempotentWorker, type: :rubocop do
include CopHelper
- include ExpectOffense
subject(:cop) { described_class.new }
diff --git a/spec/rubocop/cop/sidekiq_options_queue_spec.rb b/spec/rubocop/cop/sidekiq_options_queue_spec.rb
index c10fd7bd32b..e76265d426c 100644
--- a/spec/rubocop/cop/sidekiq_options_queue_spec.rb
+++ b/spec/rubocop/cop/sidekiq_options_queue_spec.rb
@@ -1,13 +1,13 @@
# frozen_string_literal: true
-require 'spec_helper'
+require 'fast_spec_helper'
require 'rubocop'
require 'rubocop/rspec/support'
require_relative '../../../rubocop/cop/sidekiq_options_queue'
-describe RuboCop::Cop::SidekiqOptionsQueue do
+RSpec.describe RuboCop::Cop::SidekiqOptionsQueue, type: :rubocop do
include CopHelper
subject(:cop) { described_class.new }
diff --git a/spec/rubocop/cop/static_translation_definition_spec.rb b/spec/rubocop/cop/static_translation_definition_spec.rb
index b85f9da9b4e..b6c9f6a25df 100644
--- a/spec/rubocop/cop/static_translation_definition_spec.rb
+++ b/spec/rubocop/cop/static_translation_definition_spec.rb
@@ -1,13 +1,13 @@
# frozen_string_literal: true
-require 'spec_helper'
+require 'fast_spec_helper'
require 'rubocop'
-require 'rubocop/rspec/support'
+require 'rspec-parameterized'
require_relative '../../../rubocop/cop/static_translation_definition'
-describe RuboCop::Cop::StaticTranslationDefinition do
+RSpec.describe RuboCop::Cop::StaticTranslationDefinition, type: :rubocop do
include CopHelper
using RSpec::Parameterized::TableSyntax
diff --git a/spec/rubocop/migration_helpers_spec.rb b/spec/rubocop/migration_helpers_spec.rb
index 73ced8c58da..f0be21c9d70 100644
--- a/spec/rubocop/migration_helpers_spec.rb
+++ b/spec/rubocop/migration_helpers_spec.rb
@@ -6,7 +6,7 @@ require 'rspec-parameterized'
require_relative '../../rubocop/migration_helpers'
-describe RuboCop::MigrationHelpers do
+RSpec.describe RuboCop::MigrationHelpers do
using RSpec::Parameterized::TableSyntax
subject(:fake_cop) { Class.new { include RuboCop::MigrationHelpers }.new }
diff --git a/spec/rubocop/qa_helpers_spec.rb b/spec/rubocop/qa_helpers_spec.rb
index 26e4c1ca6f0..786b9c78952 100644
--- a/spec/rubocop/qa_helpers_spec.rb
+++ b/spec/rubocop/qa_helpers_spec.rb
@@ -1,10 +1,11 @@
# frozen_string_literal: true
-require 'spec_helper'
+require 'fast_spec_helper'
require 'rubocop'
+require 'parser/current'
require_relative '../../rubocop/qa_helpers'
-describe RuboCop::QAHelpers do
+RSpec.describe RuboCop::QAHelpers, type: :rubocop do
def parse_source(source, path = 'foo.rb')
buffer = Parser::Source::Buffer.new(path)
buffer.source = source
@@ -23,13 +24,13 @@ describe RuboCop::QAHelpers do
describe '#in_qa_file?' do
it 'returns true for a node in the qa/ directory' do
- node = parse_source('10', Rails.root.join('qa', 'qa', 'page', 'dashboard', 'groups.rb'))
+ node = parse_source('10', rails_root_join('qa', 'qa', 'page', 'dashboard', 'groups.rb'))
expect(cop.in_qa_file?(node)).to eq(true)
end
it 'returns false for a node outside the qa/ directory' do
- node = parse_source('10', Rails.root.join('app', 'foo', 'foo.rb'))
+ node = parse_source('10', rails_root_join('app', 'foo', 'foo.rb'))
expect(cop.in_qa_file?(node)).to eq(false)
end
diff --git a/spec/serializers/accessibility_error_entity_spec.rb b/spec/serializers/accessibility_error_entity_spec.rb
index e9bfabb7aa8..afbff15a195 100644
--- a/spec/serializers/accessibility_error_entity_spec.rb
+++ b/spec/serializers/accessibility_error_entity_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe AccessibilityErrorEntity do
+RSpec.describe AccessibilityErrorEntity do
let(:entity) { described_class.new(accessibility_error) }
describe '#as_json' do
diff --git a/spec/serializers/accessibility_reports_comparer_entity_spec.rb b/spec/serializers/accessibility_reports_comparer_entity_spec.rb
index ed2c17de640..3024974710e 100644
--- a/spec/serializers/accessibility_reports_comparer_entity_spec.rb
+++ b/spec/serializers/accessibility_reports_comparer_entity_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe AccessibilityReportsComparerEntity do
+RSpec.describe AccessibilityReportsComparerEntity do
let(:entity) { described_class.new(comparer) }
let(:comparer) { Gitlab::Ci::Reports::AccessibilityReportsComparer.new(base_report, head_report) }
let(:base_report) { Gitlab::Ci::Reports::AccessibilityReports.new }
diff --git a/spec/serializers/accessibility_reports_comparer_serializer_spec.rb b/spec/serializers/accessibility_reports_comparer_serializer_spec.rb
index 37dc760fdec..ef56f5b6b6b 100644
--- a/spec/serializers/accessibility_reports_comparer_serializer_spec.rb
+++ b/spec/serializers/accessibility_reports_comparer_serializer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe AccessibilityReportsComparerSerializer do
+RSpec.describe AccessibilityReportsComparerSerializer do
let(:project) { double(:project) }
let(:serializer) { described_class.new(project: project).represent(comparer) }
let(:comparer) { Gitlab::Ci::Reports::AccessibilityReportsComparer.new(base_report, head_report) }
diff --git a/spec/serializers/analytics_build_entity_spec.rb b/spec/serializers/analytics_build_entity_spec.rb
index dfa16075d20..20bd017d1cf 100644
--- a/spec/serializers/analytics_build_entity_spec.rb
+++ b/spec/serializers/analytics_build_entity_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe AnalyticsBuildEntity do
+RSpec.describe AnalyticsBuildEntity do
let(:entity) do
described_class.new(build, request: double)
end
diff --git a/spec/serializers/analytics_build_serializer_spec.rb b/spec/serializers/analytics_build_serializer_spec.rb
index 04a387fd353..94a42ba4ffa 100644
--- a/spec/serializers/analytics_build_serializer_spec.rb
+++ b/spec/serializers/analytics_build_serializer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe AnalyticsBuildSerializer do
+RSpec.describe AnalyticsBuildSerializer do
let(:resource) { create(:ci_build) }
subject { described_class.new.represent(resource) }
diff --git a/spec/serializers/analytics_issue_entity_spec.rb b/spec/serializers/analytics_issue_entity_spec.rb
index 555efe136e6..2518eec8c23 100644
--- a/spec/serializers/analytics_issue_entity_spec.rb
+++ b/spec/serializers/analytics_issue_entity_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe AnalyticsIssueEntity do
+RSpec.describe AnalyticsIssueEntity do
let(:user) { create(:user) }
let(:entity_hash) do
{
diff --git a/spec/serializers/analytics_issue_serializer_spec.rb b/spec/serializers/analytics_issue_serializer_spec.rb
index 9b29739a8f2..5d268fe514b 100644
--- a/spec/serializers/analytics_issue_serializer_spec.rb
+++ b/spec/serializers/analytics_issue_serializer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe AnalyticsIssueSerializer do
+RSpec.describe AnalyticsIssueSerializer do
subject do
described_class
.new(entity: :merge_request)
diff --git a/spec/serializers/analytics_merge_request_serializer_spec.rb b/spec/serializers/analytics_merge_request_serializer_spec.rb
index c82eb28a28b..adf7321c738 100644
--- a/spec/serializers/analytics_merge_request_serializer_spec.rb
+++ b/spec/serializers/analytics_merge_request_serializer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe AnalyticsMergeRequestSerializer do
+RSpec.describe AnalyticsMergeRequestSerializer do
subject do
described_class
.new(entity: :merge_request)
diff --git a/spec/serializers/analytics_stage_serializer_spec.rb b/spec/serializers/analytics_stage_serializer_spec.rb
index 1f1a0180b1f..0f2de262188 100644
--- a/spec/serializers/analytics_stage_serializer_spec.rb
+++ b/spec/serializers/analytics_stage_serializer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe AnalyticsStageSerializer do
+RSpec.describe AnalyticsStageSerializer do
subject do
described_class.new.represent(resource)
end
diff --git a/spec/serializers/analytics_summary_serializer_spec.rb b/spec/serializers/analytics_summary_serializer_spec.rb
index ed126720a55..cd8be07827d 100644
--- a/spec/serializers/analytics_summary_serializer_spec.rb
+++ b/spec/serializers/analytics_summary_serializer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe AnalyticsSummarySerializer do
+RSpec.describe AnalyticsSummarySerializer do
subject do
described_class.new.represent(resource)
end
diff --git a/spec/serializers/blob_entity_spec.rb b/spec/serializers/blob_entity_spec.rb
index 3cd967ed44c..b8c8c4c17de 100644
--- a/spec/serializers/blob_entity_spec.rb
+++ b/spec/serializers/blob_entity_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe BlobEntity do
+RSpec.describe BlobEntity do
let(:user) { create(:user) }
let(:project) { create(:project, :repository) }
let(:blob) { project.commit('master').diffs.diff_files.first.blob }
diff --git a/spec/serializers/board_serializer_spec.rb b/spec/serializers/board_serializer_spec.rb
index 8a633e46316..9e6d5a93d53 100644
--- a/spec/serializers/board_serializer_spec.rb
+++ b/spec/serializers/board_serializer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe BoardSerializer do
+RSpec.describe BoardSerializer do
let(:resource) { create(:board) }
let(:json_entity) do
described_class.new
diff --git a/spec/serializers/build_action_entity_spec.rb b/spec/serializers/build_action_entity_spec.rb
index 7cd1fdcda22..75ae244db83 100644
--- a/spec/serializers/build_action_entity_spec.rb
+++ b/spec/serializers/build_action_entity_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe BuildActionEntity do
+RSpec.describe BuildActionEntity do
let(:job) { create(:ci_build, name: 'test_job') }
let(:request) { double('request') }
let(:user) { create(:user) }
diff --git a/spec/serializers/build_artifact_entity_spec.rb b/spec/serializers/build_artifact_entity_spec.rb
index afa2aa3d254..02c172d723f 100644
--- a/spec/serializers/build_artifact_entity_spec.rb
+++ b/spec/serializers/build_artifact_entity_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe BuildArtifactEntity do
+RSpec.describe BuildArtifactEntity do
let(:job) { create(:ci_build) }
let(:artifact) { create(:ci_job_artifact, :codequality, expire_at: 1.hour.from_now, job: job) }
diff --git a/spec/serializers/build_details_entity_spec.rb b/spec/serializers/build_details_entity_spec.rb
index 92917f6ea25..ef6472e07a0 100644
--- a/spec/serializers/build_details_entity_spec.rb
+++ b/spec/serializers/build_details_entity_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe BuildDetailsEntity do
+RSpec.describe BuildDetailsEntity do
include ProjectForksHelper
let_it_be(:user) { create(:admin) }
diff --git a/spec/serializers/build_serializer_spec.rb b/spec/serializers/build_serializer_spec.rb
index c7bb6864361..f3584beb39b 100644
--- a/spec/serializers/build_serializer_spec.rb
+++ b/spec/serializers/build_serializer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe BuildSerializer do
+RSpec.describe BuildSerializer do
let(:user) { create(:user) }
let(:serializer) do
diff --git a/spec/serializers/build_trace_entity_spec.rb b/spec/serializers/build_trace_entity_spec.rb
index bafead04a51..82bd56caaac 100644
--- a/spec/serializers/build_trace_entity_spec.rb
+++ b/spec/serializers/build_trace_entity_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe BuildTraceEntity do
+RSpec.describe BuildTraceEntity do
let(:build) { build_stubbed(:ci_build) }
let(:request) { double('request') }
@@ -13,7 +13,7 @@ describe BuildTraceEntity do
end
let(:build_trace) do
- Ci::BuildTrace.new(build: build, stream: stream, content_format: content_format, state: nil)
+ Ci::BuildTrace.new(build: build, stream: stream, state: nil)
end
let(:entity) do
@@ -22,42 +22,24 @@ describe BuildTraceEntity do
subject { entity.as_json }
- shared_examples 'includes build and trace metadata' do
- it 'includes build attributes' do
- expect(subject[:id]).to eq(build.id)
- expect(subject[:status]).to eq(build.status)
- expect(subject[:complete]).to eq(build.complete?)
- end
-
- it 'includes trace metadata' do
- expect(subject).to include(:state)
- expect(subject).to include(:append)
- expect(subject).to include(:truncated)
- expect(subject).to include(:offset)
- expect(subject).to include(:size)
- expect(subject).to include(:total)
- end
+ it 'includes build attributes' do
+ expect(subject[:id]).to eq(build.id)
+ expect(subject[:status]).to eq(build.status)
+ expect(subject[:complete]).to eq(build.complete?)
end
- context 'when content format is :json' do
- let(:content_format) { :json }
-
- it_behaves_like 'includes build and trace metadata'
-
- it 'includes the trace content in json' do
- expect(subject[:lines]).to eq([
- { offset: 0, content: [{ text: 'the-trace' }] }
- ])
- end
+ it 'includes trace metadata' do
+ expect(subject).to include(:state)
+ expect(subject).to include(:append)
+ expect(subject).to include(:truncated)
+ expect(subject).to include(:offset)
+ expect(subject).to include(:size)
+ expect(subject).to include(:total)
end
- context 'when content format is :html' do
- let(:content_format) { :html }
-
- it_behaves_like 'includes build and trace metadata'
-
- it 'includes the trace content in json' do
- expect(subject[:html]).to eq('<span>the-trace</span>')
- end
+ it 'includes the trace content in json' do
+ expect(subject[:lines]).to eq([
+ { offset: 0, content: [{ text: 'the-trace' }] }
+ ])
end
end
diff --git a/spec/serializers/ci/dag_job_entity_spec.rb b/spec/serializers/ci/dag_job_entity_spec.rb
index eaaf39d6bfc..5e2b186186f 100644
--- a/spec/serializers/ci/dag_job_entity_spec.rb
+++ b/spec/serializers/ci/dag_job_entity_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::DagJobEntity do
+RSpec.describe Ci::DagJobEntity do
let_it_be(:request) { double(:request) }
let(:job) { create(:ci_build, name: 'dag_job') }
@@ -11,10 +11,18 @@ describe Ci::DagJobEntity do
describe '#as_json' do
subject { entity.as_json }
+ RSpec.shared_examples "matches schema" do
+ it "matches schema" do
+ expect(subject.to_json).to match_schema('entities/dag_job')
+ end
+ end
+
it 'contains the name' do
expect(subject[:name]).to eq 'dag_job'
end
+ it_behaves_like "matches schema"
+
context 'when job is stage scheduled' do
it 'contains the name scheduling_type' do
expect(subject[:scheduling_type]).to eq 'stage'
@@ -23,6 +31,8 @@ describe Ci::DagJobEntity do
it 'does not expose needs' do
expect(subject).not_to include(:needs)
end
+
+ it_behaves_like "matches schema"
end
context 'when job is dag scheduled' do
@@ -32,18 +42,24 @@ describe Ci::DagJobEntity do
expect(subject[:scheduling_type]).to eq 'dag'
end
+ it_behaves_like "matches schema"
+
context 'when job has needs' do
let!(:need) { create(:ci_build_need, build: job, name: 'compile') }
it 'exposes the array of needs' do
expect(subject[:needs]).to eq ['compile']
end
+
+ it_behaves_like "matches schema"
end
context 'when job has empty needs' do
it 'exposes an empty array of needs' do
expect(subject[:needs]).to eq []
end
+
+ it_behaves_like "matches schema"
end
end
end
diff --git a/spec/serializers/ci/dag_job_group_entity_spec.rb b/spec/serializers/ci/dag_job_group_entity_spec.rb
index a25723894fd..5a75c04efe5 100644
--- a/spec/serializers/ci/dag_job_group_entity_spec.rb
+++ b/spec/serializers/ci/dag_job_group_entity_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::DagJobGroupEntity do
+RSpec.describe Ci::DagJobGroupEntity do
let_it_be(:request) { double(:request) }
let_it_be(:pipeline) { create(:ci_pipeline) }
let_it_be(:stage) { create(:ci_stage, pipeline: pipeline) }
@@ -31,6 +31,10 @@ describe Ci::DagJobGroupEntity do
expect(exposed_jobs.size).to eq 1
expect(exposed_jobs.first.fetch(:name)).to eq 'test'
end
+
+ it 'matches schema' do
+ expect(subject.to_json).to match_schema('entities/dag_job_group')
+ end
end
context 'when group contains multiple parallel jobs' do
@@ -53,6 +57,10 @@ describe Ci::DagJobGroupEntity do
expect(exposed_jobs.first.fetch(:name)).to eq 'test 1/2'
expect(exposed_jobs.last.fetch(:name)).to eq 'test 2/2'
end
+
+ it 'matches schema' do
+ expect(subject.to_json).to match_schema('entities/dag_job_group')
+ end
end
end
end
diff --git a/spec/serializers/ci/dag_pipeline_entity_spec.rb b/spec/serializers/ci/dag_pipeline_entity_spec.rb
index fab8798effc..e1703b09f97 100644
--- a/spec/serializers/ci/dag_pipeline_entity_spec.rb
+++ b/spec/serializers/ci/dag_pipeline_entity_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::DagPipelineEntity do
+RSpec.describe Ci::DagPipelineEntity do
let_it_be(:request) { double(:request) }
let(:pipeline) { create(:ci_pipeline) }
@@ -11,12 +11,20 @@ describe Ci::DagPipelineEntity do
describe '#as_json' do
subject { entity.as_json }
+ RSpec.shared_examples "matches schema" do
+ it 'matches schema' do
+ expect(subject.to_json).to match_schema('entities/dag_pipeline')
+ end
+ end
+
context 'when pipeline is empty' do
it 'contains stages' do
expect(subject).to include(:stages)
expect(subject[:stages]).to be_empty
end
+
+ it_behaves_like "matches schema"
end
context 'when pipeline has jobs' do
@@ -30,6 +38,8 @@ describe Ci::DagPipelineEntity do
expect(stages.size).to eq 3
expect(stages.map { |s| s[:name] }).to contain_exactly('build', 'test', 'deploy')
end
+
+ it_behaves_like "matches schema"
end
context 'when pipeline has parallel jobs, DAG needs and GenericCommitStatus' do
@@ -138,6 +148,8 @@ describe Ci::DagPipelineEntity do
expect(subject.fetch(:stages)[2].fetch(:name)).to eq 'deploy'
expect(subject.fetch(:stages)[2]).to eq expected_result.fetch(:stages)[2]
end
+
+ it_behaves_like "matches schema"
end
end
end
diff --git a/spec/serializers/ci/dag_pipeline_serializer_spec.rb b/spec/serializers/ci/dag_pipeline_serializer_spec.rb
index abf895c3e77..856f6760d5d 100644
--- a/spec/serializers/ci/dag_pipeline_serializer_spec.rb
+++ b/spec/serializers/ci/dag_pipeline_serializer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::DagPipelineSerializer do
+RSpec.describe Ci::DagPipelineSerializer do
describe '#represent' do
subject { described_class.new.represent(pipeline) }
@@ -13,5 +13,9 @@ describe Ci::DagPipelineSerializer do
expect(subject[:stages]).to be_present
expect(subject[:stages].size).to eq 1
end
+
+ it 'matches schema' do
+ expect(subject.to_json).to match_schema('entities/dag_pipeline')
+ end
end
end
diff --git a/spec/serializers/ci/dag_stage_entity_spec.rb b/spec/serializers/ci/dag_stage_entity_spec.rb
index 5c6aa7faee4..0262ccdac68 100644
--- a/spec/serializers/ci/dag_stage_entity_spec.rb
+++ b/spec/serializers/ci/dag_stage_entity_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::DagStageEntity do
+RSpec.describe Ci::DagStageEntity do
let_it_be(:pipeline) { create(:ci_pipeline) }
let_it_be(:request) { double(:request) }
@@ -27,5 +27,9 @@ describe Ci::DagStageEntity do
expect(job_group[:size]).to eq 1
expect(job_group[:jobs]).not_to be_empty
end
+
+ it "matches schema" do
+ expect(subject.to_json).to match_schema('entities/dag_stage')
+ end
end
end
diff --git a/spec/serializers/ci/daily_build_group_report_result_entity_spec.rb b/spec/serializers/ci/daily_build_group_report_result_entity_spec.rb
index cc35b3bc8b8..f468acdcc64 100644
--- a/spec/serializers/ci/daily_build_group_report_result_entity_spec.rb
+++ b/spec/serializers/ci/daily_build_group_report_result_entity_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::DailyBuildGroupReportResultEntity do
+RSpec.describe Ci::DailyBuildGroupReportResultEntity do
let(:report_result) { double(date: '2020-05-20', group_name: 'rspec', data: { 'coverage' => 79.1 }) }
let(:entity) { described_class.new(report_result, param_type: param_type) }
let(:param_type) { 'coverage' }
diff --git a/spec/serializers/ci/daily_build_group_report_result_serializer_spec.rb b/spec/serializers/ci/daily_build_group_report_result_serializer_spec.rb
index 4a781971ae0..69bf599c0dd 100644
--- a/spec/serializers/ci/daily_build_group_report_result_serializer_spec.rb
+++ b/spec/serializers/ci/daily_build_group_report_result_serializer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::DailyBuildGroupReportResultSerializer do
+RSpec.describe Ci::DailyBuildGroupReportResultSerializer do
let(:report_result) do
[
double(date: '2020-05-20', group_name: 'rspec', data: { 'coverage' => 79.1 }),
diff --git a/spec/serializers/ci/group_variable_entity_spec.rb b/spec/serializers/ci/group_variable_entity_spec.rb
new file mode 100644
index 00000000000..a7e12905924
--- /dev/null
+++ b/spec/serializers/ci/group_variable_entity_spec.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Ci::GroupVariableEntity do
+ let(:variable) { create(:ci_group_variable) }
+ let(:entity) { described_class.new(variable) }
+
+ describe '#as_json' do
+ subject { entity.as_json }
+
+ it 'contains required fields' do
+ expect(subject).to include(:id, :key, :value, :protected, :variable_type)
+ end
+ end
+end
diff --git a/spec/serializers/ci/variable_entity_spec.rb b/spec/serializers/ci/variable_entity_spec.rb
new file mode 100644
index 00000000000..38da0b16bbd
--- /dev/null
+++ b/spec/serializers/ci/variable_entity_spec.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Ci::VariableEntity do
+ let(:variable) { create(:ci_variable) }
+ let(:entity) { described_class.new(variable) }
+
+ describe '#as_json' do
+ subject { entity.as_json }
+
+ it 'contains required fields' do
+ expect(subject).to include(:id, :key, :value, :protected, :environment_scope, :variable_type)
+ end
+ end
+end
diff --git a/spec/serializers/cluster_application_entity_spec.rb b/spec/serializers/cluster_application_entity_spec.rb
index b81bdaa0d72..aa2bb25b17c 100644
--- a/spec/serializers/cluster_application_entity_spec.rb
+++ b/spec/serializers/cluster_application_entity_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ClusterApplicationEntity do
+RSpec.describe ClusterApplicationEntity do
describe '#as_json' do
let(:application) { build(:clusters_applications_helm, version: '0.1.1') }
diff --git a/spec/serializers/cluster_entity_spec.rb b/spec/serializers/cluster_entity_spec.rb
index 16247eef655..223d37b6acd 100644
--- a/spec/serializers/cluster_entity_spec.rb
+++ b/spec/serializers/cluster_entity_spec.rb
@@ -2,9 +2,14 @@
require 'spec_helper'
-describe ClusterEntity do
+RSpec.describe ClusterEntity do
+ include Gitlab::Routing.url_helpers
+
describe '#as_json' do
- subject { described_class.new(cluster).as_json }
+ let(:user) { nil }
+ let(:request) { EntityRequest.new({ current_user: user }) }
+
+ subject { described_class.new(cluster, request: request).as_json }
context 'when provider type is gcp' do
let(:cluster) { create(:cluster, :instance, provider_type: :gcp, provider_gcp: provider) }
@@ -40,7 +45,7 @@ describe ClusterEntity do
context 'when no application has been installed' do
let(:cluster) { create(:cluster, :instance) }
- subject { described_class.new(cluster).as_json[:applications]}
+ subject { described_class.new(cluster, request: request).as_json[:applications]}
it 'contains helm as not_installable' do
expect(subject).not_to be_empty
@@ -50,5 +55,28 @@ describe ClusterEntity do
expect(helm[:status]).to eq(:not_installable)
end
end
+
+ context 'gitlab_managed_apps_logs_path' do
+ let(:cluster) { create(:cluster, :project) }
+ let(:user) { create(:user) }
+
+ subject { described_class.new(cluster, request: request).as_json }
+
+ before do
+ allow_next_instance_of(Clusters::ClusterPresenter) do |presenter|
+ allow(presenter).to receive(:show_path).and_return(nil)
+ end
+ end
+
+ it 'return projects log explorer path' do
+ log_explorer_path = project_logs_path(cluster.project, cluster_id: cluster.id)
+
+ expect_next_instance_of(Clusters::ClusterPresenter, cluster, current_user: user) do |presenter|
+ expect(presenter).to receive(:gitlab_managed_apps_logs_path).and_return(log_explorer_path)
+ end
+
+ expect(subject[:gitlab_managed_apps_logs_path]).to eq(log_explorer_path)
+ end
+ end
end
end
diff --git a/spec/serializers/cluster_serializer_spec.rb b/spec/serializers/cluster_serializer_spec.rb
index b7d7307d40b..ea1cf6ff59a 100644
--- a/spec/serializers/cluster_serializer_spec.rb
+++ b/spec/serializers/cluster_serializer_spec.rb
@@ -2,17 +2,18 @@
require 'spec_helper'
-describe ClusterSerializer do
+RSpec.describe ClusterSerializer do
let(:cluster) { create(:cluster, :project, provider_type: :user) }
describe '#represent_list' do
- subject { described_class.new.represent_list(cluster).keys }
+ subject { described_class.new(current_user: nil).represent_list(cluster).keys }
it 'serializes attrs correctly' do
is_expected.to contain_exactly(
:cluster_type,
:enabled,
:environment_scope,
+ :gitlab_managed_apps_logs_path,
:name,
:nodes,
:path,
@@ -22,7 +23,7 @@ describe ClusterSerializer do
end
describe '#represent_status' do
- subject { described_class.new.represent_status(cluster).keys }
+ subject { described_class.new(current_user: nil).represent_status(cluster).keys }
context 'when provider type is gcp and cluster is errored' do
let(:cluster) do
diff --git a/spec/serializers/commit_entity_spec.rb b/spec/serializers/commit_entity_spec.rb
index 6abe8504b93..e2ea63893a4 100644
--- a/spec/serializers/commit_entity_spec.rb
+++ b/spec/serializers/commit_entity_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe CommitEntity do
+RSpec.describe CommitEntity do
let(:signature_html) { 'TEST' }
let(:entity) do
diff --git a/spec/serializers/container_repositories_serializer_spec.rb b/spec/serializers/container_repositories_serializer_spec.rb
index 382778389b3..a0d08a8ba44 100644
--- a/spec/serializers/container_repositories_serializer_spec.rb
+++ b/spec/serializers/container_repositories_serializer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ContainerRepositoriesSerializer do
+RSpec.describe ContainerRepositoriesSerializer do
let(:user) { create(:user) }
let(:project) { create(:project) }
let(:resource) { create(:container_repository, name: 'image', project: project) }
diff --git a/spec/serializers/container_repository_entity_spec.rb b/spec/serializers/container_repository_entity_spec.rb
index 1f85c6e6a46..43969c63471 100644
--- a/spec/serializers/container_repository_entity_spec.rb
+++ b/spec/serializers/container_repository_entity_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ContainerRepositoryEntity do
+RSpec.describe ContainerRepositoryEntity do
let_it_be(:project) { create(:project) }
let_it_be(:user) { create(:user) }
let_it_be(:repository) { create(:container_repository, project: project) }
diff --git a/spec/serializers/container_tag_entity_spec.rb b/spec/serializers/container_tag_entity_spec.rb
index 8440e56f08f..8e47a6269bc 100644
--- a/spec/serializers/container_tag_entity_spec.rb
+++ b/spec/serializers/container_tag_entity_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ContainerTagEntity do
+RSpec.describe ContainerTagEntity do
let_it_be(:project) { create(:project) }
let_it_be(:user) { create(:user) }
let_it_be(:repository) { create(:container_repository, name: 'image', project: project) }
diff --git a/spec/serializers/deploy_key_entity_spec.rb b/spec/serializers/deploy_key_entity_spec.rb
index 0dbbf0de59b..3404d27a23c 100644
--- a/spec/serializers/deploy_key_entity_spec.rb
+++ b/spec/serializers/deploy_key_entity_spec.rb
@@ -2,15 +2,16 @@
require 'spec_helper'
-describe DeployKeyEntity do
+RSpec.describe DeployKeyEntity do
include RequestAwareEntity
let(:user) { create(:user) }
let(:project) { create(:project, :internal)}
let(:project_private) { create(:project, :private)}
let(:deploy_key) { create(:deploy_key) }
+ let(:options) { { user: user } }
- let(:entity) { described_class.new(deploy_key, user: user) }
+ let(:entity) { described_class.new(deploy_key, options) }
before do
project.deploy_keys << deploy_key
@@ -74,4 +75,42 @@ describe DeployKeyEntity do
it { expect(entity_public.as_json).to include(can_edit: true) }
end
end
+
+ describe 'with_owner option' do
+ it 'does not return an owner payload when it is set to false' do
+ options[:with_owner] = false
+
+ payload = entity.as_json
+
+ expect(payload[:owner]).not_to be_present
+ end
+
+ describe 'when with_owner is set to true' do
+ before do
+ options[:with_owner] = true
+ end
+
+ it 'returns an owner payload' do
+ payload = entity.as_json
+
+ expect(payload[:owner]).to be_present
+ expect(payload[:owner].keys).to include(:id, :name, :username, :avatar_url)
+ end
+
+ it 'does not return an owner if current_user cannot read the owner' do
+ allow(Ability).to receive(:allowed?).and_call_original
+ allow(Ability).to receive(:allowed?).with(options[:user], :read_user, deploy_key.user).and_return(false)
+
+ payload = entity.as_json
+
+ expect(payload[:owner]).to be_nil
+ end
+ end
+ end
+
+ it 'does not return an owner payload with_owner option not passed in' do
+ payload = entity.as_json
+
+ expect(payload[:owner]).not_to be_present
+ end
end
diff --git a/spec/serializers/deployment_cluster_entity_spec.rb b/spec/serializers/deployment_cluster_entity_spec.rb
index b22a93fcec7..95f2f8ce6fc 100644
--- a/spec/serializers/deployment_cluster_entity_spec.rb
+++ b/spec/serializers/deployment_cluster_entity_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe DeploymentClusterEntity do
+RSpec.describe DeploymentClusterEntity do
describe '#as_json' do
subject { described_class.new(deployment, request: request).as_json }
diff --git a/spec/serializers/deployment_entity_spec.rb b/spec/serializers/deployment_entity_spec.rb
index 7abe74fae8f..27673b905d3 100644
--- a/spec/serializers/deployment_entity_spec.rb
+++ b/spec/serializers/deployment_entity_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe DeploymentEntity do
+RSpec.describe DeploymentEntity do
let(:user) { developer }
let(:developer) { create(:user) }
let(:reporter) { create(:user) }
diff --git a/spec/serializers/deployment_serializer_spec.rb b/spec/serializers/deployment_serializer_spec.rb
index 67fccaa3f25..cfd43227b18 100644
--- a/spec/serializers/deployment_serializer_spec.rb
+++ b/spec/serializers/deployment_serializer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe DeploymentSerializer do
+RSpec.describe DeploymentSerializer do
let_it_be(:project) { create(:project, :repository) }
let_it_be(:user) { create(:user, email: project.commit.author_email) }
let(:resource) { create(:deployment, project: project, sha: project.commit.id) }
diff --git a/spec/serializers/detailed_status_entity_spec.rb b/spec/serializers/detailed_status_entity_spec.rb
index a4b51f1e02e..33ecb4572a8 100644
--- a/spec/serializers/detailed_status_entity_spec.rb
+++ b/spec/serializers/detailed_status_entity_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe DetailedStatusEntity do
+RSpec.describe DetailedStatusEntity do
let(:entity) { described_class.new(status) }
let(:status) do
diff --git a/spec/serializers/diff_file_base_entity_spec.rb b/spec/serializers/diff_file_base_entity_spec.rb
index 1fd697970de..153f854aa58 100644
--- a/spec/serializers/diff_file_base_entity_spec.rb
+++ b/spec/serializers/diff_file_base_entity_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe DiffFileBaseEntity do
+RSpec.describe DiffFileBaseEntity do
let(:project) { create(:project, :repository) }
let(:repository) { project.repository }
let(:entity) { described_class.new(diff_file, options).as_json }
@@ -40,10 +40,6 @@ describe DiffFileBaseEntity do
let(:options) { { request: EntityRequest.new(current_user: create(:user)), merge_request: merge_request } }
let(:params) { {} }
- before do
- stub_feature_flags(web_ide_default: false)
- end
-
shared_examples 'a diff file edit path to the source branch' do
it do
expect(entity[:edit_path]).to eq(Gitlab::Routing.url_helpers.project_edit_blob_path(project, File.join(merge_request.source_branch, diff_file.new_path), params))
diff --git a/spec/serializers/diff_file_entity_spec.rb b/spec/serializers/diff_file_entity_spec.rb
index e3ecd72b275..bebe2e2dfb5 100644
--- a/spec/serializers/diff_file_entity_spec.rb
+++ b/spec/serializers/diff_file_entity_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe DiffFileEntity do
+RSpec.describe DiffFileEntity do
include RepoHelpers
let_it_be(:project) { create(:project, :repository) }
diff --git a/spec/serializers/diff_line_entity_spec.rb b/spec/serializers/diff_line_entity_spec.rb
index 2549f64bcd3..862b06c800a 100644
--- a/spec/serializers/diff_line_entity_spec.rb
+++ b/spec/serializers/diff_line_entity_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe DiffLineEntity do
+RSpec.describe DiffLineEntity do
include RepoHelpers
let(:code) { 'hello world' }
diff --git a/spec/serializers/diff_line_serializer_spec.rb b/spec/serializers/diff_line_serializer_spec.rb
index bdfcb8e2459..b91f15211de 100644
--- a/spec/serializers/diff_line_serializer_spec.rb
+++ b/spec/serializers/diff_line_serializer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe DiffLineSerializer do
+RSpec.describe DiffLineSerializer do
let(:line) { Gitlab::Diff::Line.new('hello world', 'new', 1, nil, 1) }
let(:serializer) { described_class.new.represent(line) }
diff --git a/spec/serializers/diff_viewer_entity_spec.rb b/spec/serializers/diff_viewer_entity_spec.rb
index 76d2728c597..53601fcff61 100644
--- a/spec/serializers/diff_viewer_entity_spec.rb
+++ b/spec/serializers/diff_viewer_entity_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe DiffViewerEntity do
+RSpec.describe DiffViewerEntity do
include RepoHelpers
let(:project) { create(:project, :repository) }
diff --git a/spec/serializers/diffs_entity_spec.rb b/spec/serializers/diffs_entity_spec.rb
index 435d8a6aff2..7c59e4aed83 100644
--- a/spec/serializers/diffs_entity_spec.rb
+++ b/spec/serializers/diffs_entity_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe DiffsEntity do
+RSpec.describe DiffsEntity do
let(:user) { create(:user) }
let(:project) { create(:project, :repository) }
let(:request) { EntityRequest.new(project: project, current_user: user) }
diff --git a/spec/serializers/diffs_metadata_entity_spec.rb b/spec/serializers/diffs_metadata_entity_spec.rb
index 3ed2b7c9452..8ed47569b75 100644
--- a/spec/serializers/diffs_metadata_entity_spec.rb
+++ b/spec/serializers/diffs_metadata_entity_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe DiffsMetadataEntity do
+RSpec.describe DiffsMetadataEntity do
let(:user) { create(:user) }
let(:project) { create(:project, :repository) }
let(:request) { EntityRequest.new(project: project, current_user: user) }
diff --git a/spec/serializers/discussion_diff_file_entity_spec.rb b/spec/serializers/discussion_diff_file_entity_spec.rb
index 101ac918a98..05438450d78 100644
--- a/spec/serializers/discussion_diff_file_entity_spec.rb
+++ b/spec/serializers/discussion_diff_file_entity_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe DiscussionDiffFileEntity do
+RSpec.describe DiscussionDiffFileEntity do
include RepoHelpers
let(:project) { create(:project, :repository) }
diff --git a/spec/serializers/discussion_entity_spec.rb b/spec/serializers/discussion_entity_spec.rb
index b441fd08b98..306a4fa43a9 100644
--- a/spec/serializers/discussion_entity_spec.rb
+++ b/spec/serializers/discussion_entity_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe DiscussionEntity do
+RSpec.describe DiscussionEntity do
include RepoHelpers
let(:user) { create(:user) }
diff --git a/spec/serializers/entity_date_helper_spec.rb b/spec/serializers/entity_date_helper_spec.rb
index 6f99074c3a2..a8c338675e2 100644
--- a/spec/serializers/entity_date_helper_spec.rb
+++ b/spec/serializers/entity_date_helper_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe EntityDateHelper do
+RSpec.describe EntityDateHelper do
let(:date_helper_class) { Class.new { include EntityDateHelper }.new }
it 'converts 0 seconds' do
diff --git a/spec/serializers/entity_request_spec.rb b/spec/serializers/entity_request_spec.rb
index 947c4b165d3..43a53b57c97 100644
--- a/spec/serializers/entity_request_spec.rb
+++ b/spec/serializers/entity_request_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe EntityRequest do
+RSpec.describe EntityRequest do
subject do
described_class.new(user: 'user', project: 'some project')
end
diff --git a/spec/serializers/environment_entity_spec.rb b/spec/serializers/environment_entity_spec.rb
index 36e971c467a..6232a0d2973 100644
--- a/spec/serializers/environment_entity_spec.rb
+++ b/spec/serializers/environment_entity_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe EnvironmentEntity do
+RSpec.describe EnvironmentEntity do
include Gitlab::Routing.url_helpers
let(:request) { double('request') }
diff --git a/spec/serializers/environment_serializer_spec.rb b/spec/serializers/environment_serializer_spec.rb
index 304457d83a1..1e3980b7720 100644
--- a/spec/serializers/environment_serializer_spec.rb
+++ b/spec/serializers/environment_serializer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe EnvironmentSerializer do
+RSpec.describe EnvironmentSerializer do
let(:user) { create(:user) }
let(:project) { create(:project) }
diff --git a/spec/serializers/environment_status_entity_spec.rb b/spec/serializers/environment_status_entity_spec.rb
index 11455c57677..a940c4b465e 100644
--- a/spec/serializers/environment_status_entity_spec.rb
+++ b/spec/serializers/environment_status_entity_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe EnvironmentStatusEntity do
+RSpec.describe EnvironmentStatusEntity do
let(:user) { create(:user) }
let(:request) { double('request', project: project) }
diff --git a/spec/serializers/evidences/evidence_entity_spec.rb b/spec/serializers/evidences/evidence_entity_spec.rb
index fa13bd21edd..8ec0422fea2 100644
--- a/spec/serializers/evidences/evidence_entity_spec.rb
+++ b/spec/serializers/evidences/evidence_entity_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Evidences::EvidenceEntity do
+RSpec.describe Evidences::EvidenceEntity do
let_it_be(:project) { create(:project) }
let(:release) { create(:release, project: project) }
let(:evidence) { build(:evidence, release: release) }
diff --git a/spec/serializers/evidences/evidence_serializer_spec.rb b/spec/serializers/evidences/evidence_serializer_spec.rb
index 5322f6a43fc..73d0700b7cf 100644
--- a/spec/serializers/evidences/evidence_serializer_spec.rb
+++ b/spec/serializers/evidences/evidence_serializer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Evidences::EvidenceSerializer do
+RSpec.describe Evidences::EvidenceSerializer do
it 'represents an EvidenceEntity entity' do
expect(described_class.entity_class).to eq(Evidences::EvidenceEntity)
end
diff --git a/spec/serializers/evidences/issue_entity_spec.rb b/spec/serializers/evidences/issue_entity_spec.rb
index 915df986887..71392d0abd9 100644
--- a/spec/serializers/evidences/issue_entity_spec.rb
+++ b/spec/serializers/evidences/issue_entity_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Evidences::IssueEntity do
+RSpec.describe Evidences::IssueEntity do
let(:entity) { described_class.new(build(:issue)) }
subject { entity.as_json }
diff --git a/spec/serializers/evidences/milestone_entity_spec.rb b/spec/serializers/evidences/milestone_entity_spec.rb
index 68eb12093da..337ce966763 100644
--- a/spec/serializers/evidences/milestone_entity_spec.rb
+++ b/spec/serializers/evidences/milestone_entity_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Evidences::MilestoneEntity do
+RSpec.describe Evidences::MilestoneEntity do
let(:milestone) { build(:milestone) }
let(:entity) { described_class.new(milestone) }
diff --git a/spec/serializers/evidences/project_entity_spec.rb b/spec/serializers/evidences/project_entity_spec.rb
index 01c160425a8..d5cb1a53a7e 100644
--- a/spec/serializers/evidences/project_entity_spec.rb
+++ b/spec/serializers/evidences/project_entity_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Evidences::ProjectEntity do
+RSpec.describe Evidences::ProjectEntity do
let(:entity) { described_class.new(build(:project)) }
subject { entity.as_json }
diff --git a/spec/serializers/evidences/release_entity_spec.rb b/spec/serializers/evidences/release_entity_spec.rb
index 8e2be748169..63e25efc65f 100644
--- a/spec/serializers/evidences/release_entity_spec.rb
+++ b/spec/serializers/evidences/release_entity_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Evidences::ReleaseEntity do
+RSpec.describe Evidences::ReleaseEntity do
let(:release) { build(:release) }
let(:entity) { described_class.new(release) }
diff --git a/spec/serializers/evidences/release_serializer_spec.rb b/spec/serializers/evidences/release_serializer_spec.rb
index a0dbf50137c..c93e0cd7c19 100644
--- a/spec/serializers/evidences/release_serializer_spec.rb
+++ b/spec/serializers/evidences/release_serializer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Evidences::ReleaseSerializer do
+RSpec.describe Evidences::ReleaseSerializer do
it 'represents an Evidence::ReleaseEntity entity' do
expect(described_class.entity_class).to eq(Evidences::ReleaseEntity)
end
diff --git a/spec/serializers/fork_namespace_entity_spec.rb b/spec/serializers/fork_namespace_entity_spec.rb
new file mode 100644
index 00000000000..7ce6b77da44
--- /dev/null
+++ b/spec/serializers/fork_namespace_entity_spec.rb
@@ -0,0 +1,70 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe ForkNamespaceEntity do
+ include Gitlab::Routing.url_helpers
+ include ProjectForksHelper
+
+ let_it_be(:user) { create(:user) }
+ let_it_be(:project) { create(:project) }
+
+ let(:namespace) { create(:group, :with_avatar, description: 'test') }
+ let(:entity) { described_class.new(namespace, current_user: user, project: project) }
+
+ subject(:json) { entity.as_json }
+
+ before do
+ project.add_maintainer(user)
+ end
+
+ it 'renders json' do
+ is_expected.not_to be_nil
+ end
+
+ %w[id
+ name
+ description
+ markdown_description
+ visibility
+ full_name
+ created_at
+ updated_at
+ avatar_url].each do |attribute|
+ it "includes #{attribute}" do
+ expect(json[attribute.to_sym]).to be_present
+ end
+ end
+
+ it 'exposes path for forking project to the namespace' do
+ expect(json[:fork_path]).to eq project_forks_path(project, namespace_key: namespace.id)
+ end
+
+ it 'exposes forked_project_path when fork exists in namespace' do
+ namespace.add_maintainer(user)
+ fork_in_namespace = fork_project(project, user, namespace: namespace)
+
+ expect(json[:forked_project_path]).to eql project_path(fork_in_namespace)
+ end
+
+ it 'exposes relative path to the namespace' do
+ expect(json[:relative_path]).to eql polymorphic_path(namespace)
+ end
+
+ it 'exposes human readable permission level' do
+ namespace.add_developer(user)
+ expect(json[:permission]).to eql 'Developer'
+ end
+
+ it 'sets can_create_project to true when user can create projects in namespace' do
+ allow(user).to receive(:can?).with(:create_projects, namespace).and_return(true)
+
+ expect(json[:can_create_project]).to be true
+ end
+
+ it 'sets can_create_project to false when user is not allowed create projects in namespace' do
+ allow(user).to receive(:can?).with(:create_projects, namespace).and_return(false)
+
+ expect(json[:can_create_project]).to be false
+ end
+end
diff --git a/spec/serializers/fork_namespace_serializer_spec.rb b/spec/serializers/fork_namespace_serializer_spec.rb
new file mode 100644
index 00000000000..6793b900af6
--- /dev/null
+++ b/spec/serializers/fork_namespace_serializer_spec.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe ForkNamespaceSerializer do
+ it 'represents ForkNamespaceEntity entities' do
+ expect(described_class.entity_class).to eq(ForkNamespaceEntity)
+ end
+end
diff --git a/spec/serializers/group_child_entity_spec.rb b/spec/serializers/group_child_entity_spec.rb
index cae263e7fd5..7f330da44a7 100644
--- a/spec/serializers/group_child_entity_spec.rb
+++ b/spec/serializers/group_child_entity_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GroupChildEntity do
+RSpec.describe GroupChildEntity do
include ExternalAuthorizationServiceHelpers
include Gitlab::Routing.url_helpers
diff --git a/spec/serializers/group_child_serializer_spec.rb b/spec/serializers/group_child_serializer_spec.rb
index 92f083ed23e..63582019d32 100644
--- a/spec/serializers/group_child_serializer_spec.rb
+++ b/spec/serializers/group_child_serializer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GroupChildSerializer do
+RSpec.describe GroupChildSerializer do
let(:request) { double('request') }
let(:user) { create(:user) }
diff --git a/spec/serializers/group_variable_entity_spec.rb b/spec/serializers/group_variable_entity_spec.rb
deleted file mode 100644
index e6b51e0d626..00000000000
--- a/spec/serializers/group_variable_entity_spec.rb
+++ /dev/null
@@ -1,16 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-describe GroupVariableEntity do
- let(:variable) { create(:ci_group_variable) }
- let(:entity) { described_class.new(variable) }
-
- describe '#as_json' do
- subject { entity.as_json }
-
- it 'contains required fields' do
- expect(subject).to include(:id, :key, :value, :protected, :variable_type)
- end
- end
-end
diff --git a/spec/serializers/import/bitbucket_provider_repo_entity_spec.rb b/spec/serializers/import/bitbucket_provider_repo_entity_spec.rb
index ed3ef26db65..2a0386b779f 100644
--- a/spec/serializers/import/bitbucket_provider_repo_entity_spec.rb
+++ b/spec/serializers/import/bitbucket_provider_repo_entity_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Import::BitbucketProviderRepoEntity do
+RSpec.describe Import::BitbucketProviderRepoEntity do
let(:repo_data) do
{
'name' => 'repo_name',
diff --git a/spec/serializers/import/bitbucket_server_provider_repo_entity_spec.rb b/spec/serializers/import/bitbucket_server_provider_repo_entity_spec.rb
index 9891809cc67..6e00d608d9a 100644
--- a/spec/serializers/import/bitbucket_server_provider_repo_entity_spec.rb
+++ b/spec/serializers/import/bitbucket_server_provider_repo_entity_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Import::BitbucketServerProviderRepoEntity do
+RSpec.describe Import::BitbucketServerProviderRepoEntity do
let(:repo_data) do
{
'name' => 'test',
diff --git a/spec/serializers/import/fogbugz_provider_repo_entity_spec.rb b/spec/serializers/import/fogbugz_provider_repo_entity_spec.rb
index b9029b67aab..748ddd2a108 100644
--- a/spec/serializers/import/fogbugz_provider_repo_entity_spec.rb
+++ b/spec/serializers/import/fogbugz_provider_repo_entity_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Import::FogbugzProviderRepoEntity do
+RSpec.describe Import::FogbugzProviderRepoEntity do
let(:provider_url) { 'https://demo.fogbugz.com/' }
let(:repo_data) do
{
diff --git a/spec/serializers/import/githubish_provider_repo_entity_spec.rb b/spec/serializers/import/githubish_provider_repo_entity_spec.rb
index c6a07b2d64a..b8292f71714 100644
--- a/spec/serializers/import/githubish_provider_repo_entity_spec.rb
+++ b/spec/serializers/import/githubish_provider_repo_entity_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Import::GithubishProviderRepoEntity do
+RSpec.describe Import::GithubishProviderRepoEntity do
let(:provider_url) { 'https://github.com/' }
let(:repo) do
{
diff --git a/spec/serializers/import/gitlab_provider_repo_entity_spec.rb b/spec/serializers/import/gitlab_provider_repo_entity_spec.rb
index 3f862c16fe2..25e445d336c 100644
--- a/spec/serializers/import/gitlab_provider_repo_entity_spec.rb
+++ b/spec/serializers/import/gitlab_provider_repo_entity_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Import::GitlabProviderRepoEntity do
+RSpec.describe Import::GitlabProviderRepoEntity do
let(:repo_data) do
{
'id' => 1,
diff --git a/spec/serializers/import/provider_repo_serializer_spec.rb b/spec/serializers/import/provider_repo_serializer_spec.rb
index 9bf55e6c65d..430bad151d3 100644
--- a/spec/serializers/import/provider_repo_serializer_spec.rb
+++ b/spec/serializers/import/provider_repo_serializer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Import::ProviderRepoSerializer do
+RSpec.describe Import::ProviderRepoSerializer do
using RSpec::Parameterized::TableSyntax
describe '#represent' do
diff --git a/spec/serializers/issuable_sidebar_extras_entity_spec.rb b/spec/serializers/issuable_sidebar_extras_entity_spec.rb
index a1a7c554b49..f49b9acfd5d 100644
--- a/spec/serializers/issuable_sidebar_extras_entity_spec.rb
+++ b/spec/serializers/issuable_sidebar_extras_entity_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe IssuableSidebarExtrasEntity do
+RSpec.describe IssuableSidebarExtrasEntity do
let(:user) { create(:user) }
let(:project) { create(:project, :repository) }
let(:resource) { create(:issue, project: project) }
diff --git a/spec/serializers/issue_board_entity_spec.rb b/spec/serializers/issue_board_entity_spec.rb
index d013b27369b..e60a063b9eb 100644
--- a/spec/serializers/issue_board_entity_spec.rb
+++ b/spec/serializers/issue_board_entity_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe IssueBoardEntity do
+RSpec.describe IssueBoardEntity do
let_it_be(:project) { create(:project) }
let_it_be(:resource) { create(:issue, project: project) }
let_it_be(:user) { create(:user) }
diff --git a/spec/serializers/issue_entity_spec.rb b/spec/serializers/issue_entity_spec.rb
index a1868b2631b..5c5ac184778 100644
--- a/spec/serializers/issue_entity_spec.rb
+++ b/spec/serializers/issue_entity_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe IssueEntity do
+RSpec.describe IssueEntity do
let(:project) { create(:project) }
let(:resource) { create(:issue, project: project) }
let(:user) { create(:user) }
diff --git a/spec/serializers/issue_serializer_spec.rb b/spec/serializers/issue_serializer_spec.rb
index e25becc4709..a51297d6d80 100644
--- a/spec/serializers/issue_serializer_spec.rb
+++ b/spec/serializers/issue_serializer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe IssueSerializer do
+RSpec.describe IssueSerializer do
let(:resource) { create(:issue) }
let(:user) { create(:user) }
let(:json_entity) do
diff --git a/spec/serializers/job_artifact_report_entity_spec.rb b/spec/serializers/job_artifact_report_entity_spec.rb
index 3cd12f0e9fe..1aaaad544ba 100644
--- a/spec/serializers/job_artifact_report_entity_spec.rb
+++ b/spec/serializers/job_artifact_report_entity_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe JobArtifactReportEntity do
+RSpec.describe JobArtifactReportEntity do
let(:report) { create(:ci_job_artifact, :codequality) }
let(:entity) { described_class.new(report, request: double) }
diff --git a/spec/serializers/job_entity_spec.rb b/spec/serializers/job_entity_spec.rb
index 60843f1a599..02262be9511 100644
--- a/spec/serializers/job_entity_spec.rb
+++ b/spec/serializers/job_entity_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe JobEntity do
+RSpec.describe JobEntity do
let(:user) { create(:user) }
let(:job) { create(:ci_build) }
let(:project) { job.project }
diff --git a/spec/serializers/label_serializer_spec.rb b/spec/serializers/label_serializer_spec.rb
index 7e1bb2cc127..ae1466b16e5 100644
--- a/spec/serializers/label_serializer_spec.rb
+++ b/spec/serializers/label_serializer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe LabelSerializer do
+RSpec.describe LabelSerializer do
let(:user) { create(:user) }
let(:serializer) do
diff --git a/spec/serializers/lfs_file_lock_entity_spec.rb b/spec/serializers/lfs_file_lock_entity_spec.rb
index 4ffffad7d5a..5869941c920 100644
--- a/spec/serializers/lfs_file_lock_entity_spec.rb
+++ b/spec/serializers/lfs_file_lock_entity_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe LfsFileLockEntity do
+RSpec.describe LfsFileLockEntity do
let(:user) { create(:user) }
let(:resource) { create(:lfs_file_lock, user: user) }
diff --git a/spec/serializers/merge_request_basic_entity_spec.rb b/spec/serializers/merge_request_basic_entity_spec.rb
index 53ba66a79ac..1cddd87e917 100644
--- a/spec/serializers/merge_request_basic_entity_spec.rb
+++ b/spec/serializers/merge_request_basic_entity_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe MergeRequestBasicEntity do
+RSpec.describe MergeRequestBasicEntity do
let(:resource) { build(:merge_request) }
subject do
diff --git a/spec/serializers/merge_request_diff_entity_spec.rb b/spec/serializers/merge_request_diff_entity_spec.rb
index 2e3b0d092fe..542ef6cb3c3 100644
--- a/spec/serializers/merge_request_diff_entity_spec.rb
+++ b/spec/serializers/merge_request_diff_entity_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe MergeRequestDiffEntity do
+RSpec.describe MergeRequestDiffEntity do
let_it_be(:project) { create(:project, :repository) }
let(:request) { EntityRequest.new(project: project) }
let(:merge_request) { create(:merge_request_with_diffs, target_project: project, source_project: project) }
diff --git a/spec/serializers/merge_request_for_pipeline_entity_spec.rb b/spec/serializers/merge_request_for_pipeline_entity_spec.rb
index b39fbce8c3a..ed66716974a 100644
--- a/spec/serializers/merge_request_for_pipeline_entity_spec.rb
+++ b/spec/serializers/merge_request_for_pipeline_entity_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe MergeRequestForPipelineEntity do
+RSpec.describe MergeRequestForPipelineEntity do
let(:project) { create(:project, :repository) }
let(:user) { create(:user) }
let(:request) { EntityRequest.new(project: project) }
diff --git a/spec/serializers/merge_request_poll_cached_widget_entity_spec.rb b/spec/serializers/merge_request_poll_cached_widget_entity_spec.rb
index 9f96e5711a4..51564de6041 100644
--- a/spec/serializers/merge_request_poll_cached_widget_entity_spec.rb
+++ b/spec/serializers/merge_request_poll_cached_widget_entity_spec.rb
@@ -2,8 +2,9 @@
require 'spec_helper'
-describe MergeRequestPollCachedWidgetEntity do
+RSpec.describe MergeRequestPollCachedWidgetEntity do
include ProjectForksHelper
+ using RSpec::Parameterized::TableSyntax
let(:project) { create :project, :repository }
let(:resource) { create(:merge_request, source_project: project, target_project: project) }
@@ -181,6 +182,27 @@ describe MergeRequestPollCachedWidgetEntity do
end
end
+ describe 'squash defaults for projects' do
+ where(:squash_option, :value, :default, :readonly) do
+ 'always' | true | true | true
+ 'never' | false | false | true
+ 'default_on' | false | true | false
+ 'default_off' | false | false | false
+ end
+
+ with_them do
+ before do
+ project.project_setting.update!(squash_option: squash_option)
+ end
+
+ it 'the key reflects the correct value' do
+ expect(subject[:squash_on_merge]).to eq(value)
+ expect(subject[:squash_enabled_by_default]).to eq(default)
+ expect(subject[:squash_readonly]).to eq(readonly)
+ end
+ end
+ end
+
describe 'attributes for squash commit message' do
context 'when merge request is mergeable' do
before do
diff --git a/spec/serializers/merge_request_poll_widget_entity_spec.rb b/spec/serializers/merge_request_poll_widget_entity_spec.rb
index 4b3bfc99c88..f0493699209 100644
--- a/spec/serializers/merge_request_poll_widget_entity_spec.rb
+++ b/spec/serializers/merge_request_poll_widget_entity_spec.rb
@@ -2,8 +2,9 @@
require 'spec_helper'
-describe MergeRequestPollWidgetEntity do
+RSpec.describe MergeRequestPollWidgetEntity do
include ProjectForksHelper
+ using RSpec::Parameterized::TableSyntax
let(:project) { create :project, :repository }
let(:resource) { create(:merge_request, source_project: project, target_project: project) }
@@ -171,6 +172,27 @@ describe MergeRequestPollWidgetEntity do
end
end
+ describe 'squash defaults for projects' do
+ where(:squash_option, :value, :default, :readonly) do
+ 'always' | true | true | true
+ 'never' | false | false | true
+ 'default_on' | false | true | false
+ 'default_off' | false | false | false
+ end
+
+ with_them do
+ before do
+ project.project_setting.update!(squash_option: squash_option)
+ end
+
+ it 'the key reflects the correct value' do
+ expect(subject[:squash_on_merge]).to eq(value)
+ expect(subject[:squash_enabled_by_default]).to eq(default)
+ expect(subject[:squash_readonly]).to eq(readonly)
+ end
+ end
+ end
+
context 'when head pipeline is finished' do
before do
create(:ci_pipeline, :success, project: project,
diff --git a/spec/serializers/merge_request_serializer_spec.rb b/spec/serializers/merge_request_serializer_spec.rb
index 90b3efae412..a2ce1ed4ac2 100644
--- a/spec/serializers/merge_request_serializer_spec.rb
+++ b/spec/serializers/merge_request_serializer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe MergeRequestSerializer do
+RSpec.describe MergeRequestSerializer do
let_it_be(:user) { create(:user) }
let_it_be(:resource) { create(:merge_request, description: "Description") }
diff --git a/spec/serializers/merge_request_sidebar_basic_entity_spec.rb b/spec/serializers/merge_request_sidebar_basic_entity_spec.rb
index b2db57801ea..e950e6d6592 100644
--- a/spec/serializers/merge_request_sidebar_basic_entity_spec.rb
+++ b/spec/serializers/merge_request_sidebar_basic_entity_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe MergeRequestSidebarBasicEntity do
+RSpec.describe MergeRequestSidebarBasicEntity do
let(:project) { create :project, :repository }
let(:merge_request) { create(:merge_request, source_project: project, target_project: project) }
let(:user) { create(:user) }
diff --git a/spec/serializers/merge_request_user_entity_spec.rb b/spec/serializers/merge_request_user_entity_spec.rb
index 47b9b0a57ab..8d6f066481e 100644
--- a/spec/serializers/merge_request_user_entity_spec.rb
+++ b/spec/serializers/merge_request_user_entity_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe MergeRequestUserEntity do
+RSpec.describe MergeRequestUserEntity do
let(:user) { create(:user) }
let(:project) { create(:project, :repository) }
let(:request) { EntityRequest.new(project: project, current_user: user) }
diff --git a/spec/serializers/merge_request_widget_commit_entity_spec.rb b/spec/serializers/merge_request_widget_commit_entity_spec.rb
index ce83978c49a..0d13308f27d 100644
--- a/spec/serializers/merge_request_widget_commit_entity_spec.rb
+++ b/spec/serializers/merge_request_widget_commit_entity_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe MergeRequestWidgetCommitEntity do
+RSpec.describe MergeRequestWidgetCommitEntity do
let(:project) { create(:project, :repository) }
let(:commit) { project.commit }
let(:request) { double('request') }
diff --git a/spec/serializers/merge_request_widget_entity_spec.rb b/spec/serializers/merge_request_widget_entity_spec.rb
index 039fb311bfc..aaee47fb981 100644
--- a/spec/serializers/merge_request_widget_entity_spec.rb
+++ b/spec/serializers/merge_request_widget_entity_spec.rb
@@ -2,11 +2,12 @@
require 'spec_helper'
-describe MergeRequestWidgetEntity do
+RSpec.describe MergeRequestWidgetEntity do
include ProjectForksHelper
let(:project) { create :project, :repository }
let(:resource) { create(:merge_request, source_project: project, target_project: project) }
+ let(:pipeline) { create(:ci_empty_pipeline, project: project) }
let(:user) { create(:user) }
let(:request) { double('request', current_user: user, project: project) }
@@ -53,6 +54,42 @@ describe MergeRequestWidgetEntity do
.to eq("/#{resource.project.full_path}/-/merge_requests/#{resource.iid}.diff")
end
+ it 'has blob path data' do
+ allow(resource).to receive_messages(
+ base_pipeline: pipeline,
+ head_pipeline: pipeline
+ )
+
+ expect(subject).to include(:blob_path)
+ expect(subject[:blob_path]).to include(:base_path)
+ expect(subject[:blob_path]).to include(:head_path)
+ end
+
+ describe 'codequality report artifacts', :request_store do
+ before do
+ project.add_developer(user)
+
+ allow(resource).to receive_messages(
+ base_pipeline: pipeline,
+ head_pipeline: pipeline
+ )
+ end
+
+ context "with report artifacts" do
+ let(:pipeline) { create(:ci_pipeline, :with_codequality_report, project: project) }
+
+ it "has data entry" do
+ expect(subject).to include(:codeclimate)
+ end
+ end
+
+ context "without artifacts" do
+ it "does not have data entry" do
+ expect(subject).not_to include(:codeclimate)
+ end
+ end
+ end
+
describe 'merge_request_add_ci_config_path' do
let!(:project_auto_devops) { create(:project_auto_devops, :disabled, project: project) }
@@ -155,6 +192,36 @@ describe MergeRequestWidgetEntity do
expect(subject[:merge_request_add_ci_config_path]).to be_nil
end
end
+
+ context 'when merge request is merged' do
+ before do
+ resource.mark_as_merged!
+ end
+
+ it 'returns a blank ci config path' do
+ expect(subject[:merge_request_add_ci_config_path]).to be_nil
+ end
+ end
+
+ context 'when merge request is closed' do
+ before do
+ resource.close!
+ end
+
+ it 'returns a blank ci config path' do
+ expect(subject[:merge_request_add_ci_config_path]).to be_nil
+ end
+ end
+
+ context 'when source branch does not exist' do
+ before do
+ resource.source_project.repository.rm_branch(user, resource.source_branch)
+ end
+
+ it 'returns a blank ci config path' do
+ expect(subject[:merge_request_add_ci_config_path]).to be_nil
+ end
+ end
end
context 'when user does not have permissions' do
diff --git a/spec/serializers/move_to_project_entity_spec.rb b/spec/serializers/move_to_project_entity_spec.rb
index ac495eadb68..a14bc3ae622 100644
--- a/spec/serializers/move_to_project_entity_spec.rb
+++ b/spec/serializers/move_to_project_entity_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe MoveToProjectEntity do
+RSpec.describe MoveToProjectEntity do
describe '#as_json' do
let(:project) { build(:project, id: 1) }
diff --git a/spec/serializers/move_to_project_serializer_spec.rb b/spec/serializers/move_to_project_serializer_spec.rb
index 841ac969eeb..60bcca3269f 100644
--- a/spec/serializers/move_to_project_serializer_spec.rb
+++ b/spec/serializers/move_to_project_serializer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe MoveToProjectSerializer do
+RSpec.describe MoveToProjectSerializer do
describe '#represent' do
it 'includes the name and name with namespace' do
project = build(:project, id: 1)
diff --git a/spec/serializers/namespace_basic_entity_spec.rb b/spec/serializers/namespace_basic_entity_spec.rb
index d3d666d57aa..8b69af5696a 100644
--- a/spec/serializers/namespace_basic_entity_spec.rb
+++ b/spec/serializers/namespace_basic_entity_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe NamespaceBasicEntity do
+RSpec.describe NamespaceBasicEntity do
let_it_be(:group) { create(:group) }
let(:entity) do
described_class.represent(group)
diff --git a/spec/serializers/namespace_serializer_spec.rb b/spec/serializers/namespace_serializer_spec.rb
index 6e5bdd8c52d..0c4b099ef60 100644
--- a/spec/serializers/namespace_serializer_spec.rb
+++ b/spec/serializers/namespace_serializer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe NamespaceSerializer do
+RSpec.describe NamespaceSerializer do
it 'represents NamespaceBasicEntity entities' do
expect(described_class.entity_class).to eq(NamespaceBasicEntity)
end
diff --git a/spec/serializers/note_entity_spec.rb b/spec/serializers/note_entity_spec.rb
index f37fffb5048..19438e69a10 100644
--- a/spec/serializers/note_entity_spec.rb
+++ b/spec/serializers/note_entity_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe NoteEntity do
+RSpec.describe NoteEntity do
include Gitlab::Routing
let(:request) { double('request', current_user: user, noteable: note.noteable) }
diff --git a/spec/serializers/paginated_diff_entity_spec.rb b/spec/serializers/paginated_diff_entity_spec.rb
index a6b83cb4286..30360b00537 100644
--- a/spec/serializers/paginated_diff_entity_spec.rb
+++ b/spec/serializers/paginated_diff_entity_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe PaginatedDiffEntity do
+RSpec.describe PaginatedDiffEntity do
let(:user) { create(:user) }
let(:request) { double('request', current_user: user) }
let(:merge_request) { create(:merge_request, :with_diffs) }
diff --git a/spec/serializers/pipeline_details_entity_spec.rb b/spec/serializers/pipeline_details_entity_spec.rb
index 849dab102c2..35ce7c7175c 100644
--- a/spec/serializers/pipeline_details_entity_spec.rb
+++ b/spec/serializers/pipeline_details_entity_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe PipelineDetailsEntity do
+RSpec.describe PipelineDetailsEntity do
let_it_be(:user) { create(:user) }
let(:request) { double('request') }
@@ -157,20 +157,30 @@ describe PipelineDetailsEntity do
context 'when pipeline triggered other pipeline' do
let(:pipeline) { create(:ci_empty_pipeline) }
- let(:build) { create(:ci_build, pipeline: pipeline) }
+ let(:build) { create(:ci_build, name: 'child', stage: 'test', pipeline: pipeline) }
+ let(:bridge) { create(:ci_bridge, name: 'cross-project', stage: 'build', pipeline: pipeline) }
+ let(:child_pipeline) { create(:ci_pipeline, project: pipeline.project) }
+ let(:cross_project_pipeline) { create(:ci_pipeline) }
before do
- create(:ci_sources_pipeline, source_job: build)
- create(:ci_sources_pipeline, source_job: build)
+ create(:ci_sources_pipeline, source_job: build, pipeline: child_pipeline)
+ create(:ci_sources_pipeline, source_job: bridge, pipeline: cross_project_pipeline)
end
- it 'contains an information about depedent pipeline' do
+ it 'contains an information about dependent pipeline', :aggregate_failures do
expect(subject[:triggered]).to be_a(Array)
expect(subject[:triggered].length).to eq(2)
expect(subject[:triggered].first[:path]).not_to be_nil
expect(subject[:triggered].first[:details]).not_to be_nil
expect(subject[:triggered].first[:details][:status]).not_to be_nil
expect(subject[:triggered].first[:project]).not_to be_nil
+
+ source_jobs = subject[:triggered]
+ .index_by { |pipeline| pipeline[:id] }
+ .transform_values { |pipeline| pipeline.fetch(:source_job) }
+
+ expect(source_jobs[cross_project_pipeline.id][:name]).to eq('cross-project')
+ expect(source_jobs[child_pipeline.id][:name]).to eq('child')
end
end
end
diff --git a/spec/serializers/pipeline_entity_spec.rb b/spec/serializers/pipeline_entity_spec.rb
index 914f42054bd..e638b14765b 100644
--- a/spec/serializers/pipeline_entity_spec.rb
+++ b/spec/serializers/pipeline_entity_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe PipelineEntity do
+RSpec.describe PipelineEntity do
include Gitlab::Routing
let_it_be(:project) { create(:project) }
@@ -261,5 +261,29 @@ describe PipelineEntity do
end
end
end
+
+ context 'when pipeline has build report results' do
+ let(:pipeline) { create(:ci_pipeline, :with_report_results, project: project, user: user) }
+
+ context 'when feature is enabled' do
+ before do
+ stub_feature_flags(build_report_summary: true)
+ end
+
+ it 'exposes tests total count' do
+ expect(subject[:tests_total_count]).to eq(2)
+ end
+ end
+
+ context 'when feature is disabled' do
+ before do
+ stub_feature_flags(build_report_summary: false)
+ end
+
+ it 'do not expose tests total count' do
+ expect(subject).not_to include(:tests_total_count)
+ end
+ end
+ end
end
end
diff --git a/spec/serializers/pipeline_serializer_spec.rb b/spec/serializers/pipeline_serializer_spec.rb
index c8f25423f85..c1386ac4eb2 100644
--- a/spec/serializers/pipeline_serializer_spec.rb
+++ b/spec/serializers/pipeline_serializer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe PipelineSerializer do
+RSpec.describe PipelineSerializer do
let_it_be(:project) { create(:project, :repository) }
let_it_be(:user) { create(:user) }
@@ -155,11 +155,25 @@ describe PipelineSerializer do
it 'verifies number of queries', :request_store do
recorded = ActiveRecord::QueryRecorder.new { subject }
- expected_queries = Gitlab.ee? ? 43 : 40
+ expected_queries = Gitlab.ee? ? 46 : 43
expect(recorded.count).to be_within(2).of(expected_queries)
expect(recorded.cached_count).to eq(0)
end
+
+ context 'with the :build_report_summary flag turned off' do
+ before do
+ stub_feature_flags(build_report_summary: false)
+ end
+
+ it 'verifies number of queries', :request_store do
+ recorded = ActiveRecord::QueryRecorder.new { subject }
+ expected_queries = Gitlab.ee? ? 43 : 40
+
+ expect(recorded.count).to be_within(2).of(expected_queries)
+ expect(recorded.cached_count).to eq(0)
+ end
+ end
end
context 'with different refs' do
@@ -176,7 +190,48 @@ describe PipelineSerializer do
# pipeline. With the same ref this check is cached but if refs are
# different then there is an extra query per ref
# https://gitlab.com/gitlab-org/gitlab-foss/issues/46368
- expected_queries = Gitlab.ee? ? 46 : 43
+ expected_queries = Gitlab.ee? ? 49 : 46
+
+ expect(recorded.count).to be_within(2).of(expected_queries)
+ expect(recorded.cached_count).to eq(0)
+ end
+
+ context 'with the :build_report_summary flag turned off' do
+ before do
+ stub_feature_flags(build_report_summary: false)
+ end
+
+ it 'verifies number of queries', :request_store do
+ recorded = ActiveRecord::QueryRecorder.new { subject }
+ expected_queries = Gitlab.ee? ? 46 : 43
+
+ expect(recorded.count).to be_within(2).of(expected_queries)
+ expect(recorded.cached_count).to eq(0)
+ end
+ end
+ end
+
+ context 'with triggered pipelines' do
+ let(:ref) { 'feature' }
+
+ before do
+ pipeline_1 = create(:ci_pipeline)
+ build_1 = create(:ci_build, pipeline: pipeline_1)
+ create(:ci_sources_pipeline, source_job: build_1)
+
+ pipeline_2 = create(:ci_pipeline)
+ build_2 = create(:ci_build, pipeline: pipeline_2)
+ create(:ci_sources_pipeline, source_job: build_2)
+ end
+
+ it 'verifies number of queries', :request_store do
+ recorded = ActiveRecord::QueryRecorder.new { subject }
+
+ # 99 queries by default + 2 related to preloading
+ # :source_pipeline and :source_job
+ # Existing numbers are high and require performance optimization
+ # https://gitlab.com/gitlab-org/gitlab/-/issues/225156
+ expected_queries = Gitlab.ee? ? 101 : 92
expect(recorded.count).to be_within(2).of(expected_queries)
expect(recorded.cached_count).to eq(0)
diff --git a/spec/serializers/project_import_entity_spec.rb b/spec/serializers/project_import_entity_spec.rb
index 9ccae798091..1481d4122ae 100644
--- a/spec/serializers/project_import_entity_spec.rb
+++ b/spec/serializers/project_import_entity_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ProjectImportEntity do
+RSpec.describe ProjectImportEntity do
include ImportHelper
let_it_be(:project) { create(:project, import_status: :started, import_source: 'namespace/project') }
diff --git a/spec/serializers/project_mirror_entity_spec.rb b/spec/serializers/project_mirror_entity_spec.rb
index 0d64199ecf6..7ed530ed9e8 100644
--- a/spec/serializers/project_mirror_entity_spec.rb
+++ b/spec/serializers/project_mirror_entity_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ProjectMirrorEntity do
+RSpec.describe ProjectMirrorEntity do
let(:project) { create(:project, :repository, :remote_mirror) }
let(:entity) { described_class.new(project) }
diff --git a/spec/serializers/project_mirror_serializer_spec.rb b/spec/serializers/project_mirror_serializer_spec.rb
index b50c2267ced..0d83e25e27b 100644
--- a/spec/serializers/project_mirror_serializer_spec.rb
+++ b/spec/serializers/project_mirror_serializer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ProjectMirrorSerializer do
+RSpec.describe ProjectMirrorSerializer do
it 'represents ProjectMirror entities' do
expect(described_class.entity_class).to eq(ProjectMirrorEntity)
end
diff --git a/spec/serializers/project_note_entity_spec.rb b/spec/serializers/project_note_entity_spec.rb
index 469bf2b86de..aaf7764a123 100644
--- a/spec/serializers/project_note_entity_spec.rb
+++ b/spec/serializers/project_note_entity_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ProjectNoteEntity do
+RSpec.describe ProjectNoteEntity do
include Gitlab::Routing
let(:request) { double('request', current_user: user, noteable: note.noteable) }
diff --git a/spec/serializers/project_serializer_spec.rb b/spec/serializers/project_serializer_spec.rb
index ef3dd1dc15e..4bf0657129f 100644
--- a/spec/serializers/project_serializer_spec.rb
+++ b/spec/serializers/project_serializer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ProjectSerializer do
+RSpec.describe ProjectSerializer do
let_it_be(:project) { create(:project) }
let(:provider_url) { 'http://provider.com' }
diff --git a/spec/serializers/prometheus_alert_entity_spec.rb b/spec/serializers/prometheus_alert_entity_spec.rb
index 2b6d8b62c4d..aeee8de2a5b 100644
--- a/spec/serializers/prometheus_alert_entity_spec.rb
+++ b/spec/serializers/prometheus_alert_entity_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe PrometheusAlertEntity do
+RSpec.describe PrometheusAlertEntity do
let(:user) { create(:user) }
let(:prometheus_alert) { create(:prometheus_alert) }
let(:request) { double('prometheus_alert', current_user: user) }
diff --git a/spec/serializers/remote_mirror_entity_spec.rb b/spec/serializers/remote_mirror_entity_spec.rb
index 27472c46436..4cbf87e4d67 100644
--- a/spec/serializers/remote_mirror_entity_spec.rb
+++ b/spec/serializers/remote_mirror_entity_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe RemoteMirrorEntity do
+RSpec.describe RemoteMirrorEntity do
let(:project) { create(:project, :repository, :remote_mirror, url: "https://test:password@gitlab.com") }
let(:remote_mirror) { project.remote_mirrors.first }
let(:entity) { described_class.new(remote_mirror) }
diff --git a/spec/serializers/request_aware_entity_spec.rb b/spec/serializers/request_aware_entity_spec.rb
index 8ddb35f5f61..d5e87f0a618 100644
--- a/spec/serializers/request_aware_entity_spec.rb
+++ b/spec/serializers/request_aware_entity_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe RequestAwareEntity do
+RSpec.describe RequestAwareEntity do
subject do
Class.new.include(described_class).new
end
diff --git a/spec/serializers/review_app_setup_entity_spec.rb b/spec/serializers/review_app_setup_entity_spec.rb
index 19949fa9282..0893d7ee47f 100644
--- a/spec/serializers/review_app_setup_entity_spec.rb
+++ b/spec/serializers/review_app_setup_entity_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ReviewAppSetupEntity do
+RSpec.describe ReviewAppSetupEntity do
let_it_be(:user) { create(:admin) }
let(:project) { create(:project) }
let(:presenter) { ProjectPresenter.new(project, current_user: user) }
diff --git a/spec/serializers/runner_entity_spec.rb b/spec/serializers/runner_entity_spec.rb
index 11a6aba431b..84c7d1720e2 100644
--- a/spec/serializers/runner_entity_spec.rb
+++ b/spec/serializers/runner_entity_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe RunnerEntity do
+RSpec.describe RunnerEntity do
let(:project) { create(:project) }
let(:runner) { create(:ci_runner, :project, projects: [project]) }
let(:entity) { described_class.new(runner, request: request, current_user: user) }
diff --git a/spec/serializers/serverless/domain_entity_spec.rb b/spec/serializers/serverless/domain_entity_spec.rb
index bdf0ccb176c..4d73a0f651a 100644
--- a/spec/serializers/serverless/domain_entity_spec.rb
+++ b/spec/serializers/serverless/domain_entity_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Serverless::DomainEntity do
+RSpec.describe Serverless::DomainEntity do
describe '#as_json' do
let(:domain) { create(:pages_domain, :instance_serverless) }
diff --git a/spec/serializers/service_event_entity_spec.rb b/spec/serializers/service_event_entity_spec.rb
index fc11263807b..09bb8bca43b 100644
--- a/spec/serializers/service_event_entity_spec.rb
+++ b/spec/serializers/service_event_entity_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ServiceEventEntity do
+RSpec.describe ServiceEventEntity do
let(:request) { double('request') }
subject { described_class.new(event, request: request, service: service).as_json }
diff --git a/spec/serializers/service_field_entity_spec.rb b/spec/serializers/service_field_entity_spec.rb
index 277890d143a..f10639dfa1b 100644
--- a/spec/serializers/service_field_entity_spec.rb
+++ b/spec/serializers/service_field_entity_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ServiceFieldEntity do
+RSpec.describe ServiceFieldEntity do
let(:request) { double('request') }
subject { described_class.new(field, request: request, service: service).as_json }
@@ -55,12 +55,12 @@ describe ServiceFieldEntity do
end
context 'EmailsOnPush Service' do
- let(:service) { create(:emails_on_push_service) }
+ let(:service) { create(:emails_on_push_service, send_from_committer_email: '1') }
context 'field with type checkbox' do
let(:field) { service.global_fields.find { |field| field[:name] == 'send_from_committer_email' } }
- it 'exposes correct attributes' do
+ it 'exposes correct attributes and casts value to Boolean' do
expected_hash = {
type: 'checkbox',
name: 'send_from_committer_email',
@@ -68,7 +68,7 @@ describe ServiceFieldEntity do
placeholder: nil,
required: nil,
choices: nil,
- value: true
+ value: 'true'
}
is_expected.to include(expected_hash)
diff --git a/spec/serializers/stage_entity_spec.rb b/spec/serializers/stage_entity_spec.rb
index fb08d6994ae..b977d5d33aa 100644
--- a/spec/serializers/stage_entity_spec.rb
+++ b/spec/serializers/stage_entity_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe StageEntity do
+RSpec.describe StageEntity do
let(:pipeline) { create(:ci_pipeline) }
let(:request) { double('request') }
let(:user) { create(:user) }
diff --git a/spec/serializers/stage_serializer_spec.rb b/spec/serializers/stage_serializer_spec.rb
index aae17cfbcb9..0b5e87dc95b 100644
--- a/spec/serializers/stage_serializer_spec.rb
+++ b/spec/serializers/stage_serializer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe StageSerializer do
+RSpec.describe StageSerializer do
let(:project) { create(:project, :repository) }
let(:user) { create(:user) }
let(:resource) { create(:ci_stage_entity) }
diff --git a/spec/serializers/suggestion_entity_spec.rb b/spec/serializers/suggestion_entity_spec.rb
index d282a7f9c7a..b133c3fb82e 100644
--- a/spec/serializers/suggestion_entity_spec.rb
+++ b/spec/serializers/suggestion_entity_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe SuggestionEntity do
+RSpec.describe SuggestionEntity do
include RepoHelpers
let(:user) { create(:user) }
@@ -13,10 +13,119 @@ describe SuggestionEntity do
subject { entity.as_json }
it 'exposes correct attributes' do
- expect(subject.keys).to match_array([:id, :appliable, :applied, :diff_lines, :current_user])
+ expect(subject.keys).to match_array([:id, :appliable, :applied, :diff_lines, :current_user, :inapplicable_reason])
end
it 'exposes current user abilities' do
expect(subject[:current_user]).to include(:can_apply)
end
+
+ describe 'inapplicable_reason' do
+ let(:inapplicable_reason) { subject[:inapplicable_reason] }
+
+ before do
+ allow(Ability).to receive(:allowed?).and_call_original
+
+ allow(Ability)
+ .to receive(:allowed?)
+ .with(user, :apply_suggestion, suggestion)
+ .and_return(can_apply_suggestion)
+ end
+
+ context 'when user can apply suggestion' do
+ let(:can_apply_suggestion) { true }
+
+ before do
+ allow(suggestion).to receive(:appliable?).and_return(appliable)
+ end
+
+ context 'and suggestion is appliable' do
+ let(:appliable) { true }
+
+ it 'returns nil' do
+ expect(inapplicable_reason).to be_nil
+ end
+ end
+
+ context 'but suggestion is not applicable' do
+ let(:appliable) { false }
+
+ before do
+ allow(suggestion).to receive(:inapplicable_reason).and_return(reason)
+ end
+
+ context 'and merge request was merged' do
+ let(:reason) { :merge_request_merged }
+
+ it 'returns appropriate message' do
+ expect(inapplicable_reason).to eq("This merge request was merged. To apply this suggestion, edit this file directly.")
+ end
+ end
+
+ context 'and source branch was deleted' do
+ let(:reason) { :source_branch_deleted }
+
+ it 'returns appropriate message' do
+ expect(inapplicable_reason).to eq("Can't apply as the source branch was deleted.")
+ end
+ end
+
+ context 'and merge request is closed' do
+ let(:reason) { :merge_request_closed }
+
+ it 'returns appropriate message' do
+ expect(inapplicable_reason).to eq("This merge request is closed. To apply this suggestion, edit this file directly.")
+ end
+ end
+
+ context 'and suggestion is outdated' do
+ let(:reason) { :outdated }
+
+ before do
+ allow(suggestion).to receive(:single_line?).and_return(single_line)
+ end
+
+ context 'and suggestion is for a single line' do
+ let(:single_line) { true }
+
+ it 'returns appropriate message' do
+ expect(inapplicable_reason).to eq("Can't apply as this line was changed in a more recent version.")
+ end
+ end
+
+ context 'and suggestion is for multiple lines' do
+ let(:single_line) { false }
+
+ it 'returns appropriate message' do
+ expect(inapplicable_reason).to eq("Can't apply as these lines were changed in a more recent version.")
+ end
+ end
+ end
+
+ context 'and suggestion has the same content' do
+ let(:reason) { :same_content }
+
+ it 'returns appropriate message' do
+ expect(inapplicable_reason).to eq("This suggestion already matches its content.")
+ end
+ end
+
+ context 'and suggestion is inapplicable for other reasons' do
+ let(:reason) { :some_other_reason }
+
+ it 'returns default message' do
+ expect(inapplicable_reason).to eq("Can't apply this suggestion.")
+ end
+ end
+ end
+ end
+
+ context 'when user cannot apply suggestion' do
+ let(:can_apply_suggestion) { false }
+
+ it 'returns appropriate message' do
+ expect(inapplicable_reason).to eq("You don't have write access to the source branch.")
+ end
+ end
+ end
end
diff --git a/spec/serializers/test_case_entity_spec.rb b/spec/serializers/test_case_entity_spec.rb
index 9f1822ff581..bd2a1b0fb98 100644
--- a/spec/serializers/test_case_entity_spec.rb
+++ b/spec/serializers/test_case_entity_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe TestCaseEntity do
+RSpec.describe TestCaseEntity do
include TestReportsHelper
let(:entity) { described_class.new(test_case) }
diff --git a/spec/serializers/test_report_entity_spec.rb b/spec/serializers/test_report_entity_spec.rb
index 5913d1c0208..549b14c04dc 100644
--- a/spec/serializers/test_report_entity_spec.rb
+++ b/spec/serializers/test_report_entity_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe TestReportEntity do
+RSpec.describe TestReportEntity do
let(:pipeline) { create(:ci_pipeline, :with_test_reports) }
let(:entity) { described_class.new(pipeline.test_reports) }
diff --git a/spec/serializers/test_report_summary_entity_spec.rb b/spec/serializers/test_report_summary_entity_spec.rb
new file mode 100644
index 00000000000..fcac9af5c23
--- /dev/null
+++ b/spec/serializers/test_report_summary_entity_spec.rb
@@ -0,0 +1,31 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe TestReportSummaryEntity do
+ let(:pipeline) { create(:ci_pipeline, :with_report_results) }
+ let(:entity) { described_class.new(pipeline.test_report_summary) }
+
+ describe '#as_json' do
+ subject(:as_json) { entity.as_json }
+
+ it 'contains the total time' do
+ expect(as_json).to include(:total_time)
+ end
+
+ it 'contains the counts' do
+ expect(as_json).to include(:total_count, :success_count, :failed_count, :skipped_count, :error_count)
+ end
+
+ context 'when summary has test suites' do
+ it 'contains the test suites' do
+ expect(as_json).to include(:test_suites)
+ expect(as_json[:test_suites].count).to eq(1)
+ end
+
+ it 'contains build_ids' do
+ expect(as_json[:test_suites].first).to include(:build_ids)
+ end
+ end
+ end
+end
diff --git a/spec/serializers/test_reports_comparer_entity_spec.rb b/spec/serializers/test_reports_comparer_entity_spec.rb
index e7dabc67325..3f88438ccde 100644
--- a/spec/serializers/test_reports_comparer_entity_spec.rb
+++ b/spec/serializers/test_reports_comparer_entity_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe TestReportsComparerEntity do
+RSpec.describe TestReportsComparerEntity do
include TestReportsHelper
let(:entity) { described_class.new(comparer) }
diff --git a/spec/serializers/test_reports_comparer_serializer_spec.rb b/spec/serializers/test_reports_comparer_serializer_spec.rb
index 0d833afe9e4..f9c37f49039 100644
--- a/spec/serializers/test_reports_comparer_serializer_spec.rb
+++ b/spec/serializers/test_reports_comparer_serializer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe TestReportsComparerSerializer do
+RSpec.describe TestReportsComparerSerializer do
include TestReportsHelper
let(:project) { double(:project) }
diff --git a/spec/serializers/test_suite_comparer_entity_spec.rb b/spec/serializers/test_suite_comparer_entity_spec.rb
index 9790777a570..882991a6208 100644
--- a/spec/serializers/test_suite_comparer_entity_spec.rb
+++ b/spec/serializers/test_suite_comparer_entity_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe TestSuiteComparerEntity do
+RSpec.describe TestSuiteComparerEntity do
include TestReportsHelper
let(:entity) { described_class.new(comparer) }
diff --git a/spec/serializers/test_suite_entity_spec.rb b/spec/serializers/test_suite_entity_spec.rb
index bd88d235013..83d3086ea6b 100644
--- a/spec/serializers/test_suite_entity_spec.rb
+++ b/spec/serializers/test_suite_entity_spec.rb
@@ -2,36 +2,46 @@
require 'spec_helper'
-describe TestSuiteEntity do
- let(:pipeline) { create(:ci_pipeline, :with_test_reports) }
+RSpec.describe TestSuiteEntity do
+ let(:pipeline) { create(:ci_pipeline, :with_test_reports) }
let(:test_suite) { pipeline.test_reports.test_suites.each_value.first }
- let(:entity) { described_class.new(test_suite) }
+ let(:user) { create(:user) }
+ let(:request) { double('request', current_user: user) }
- describe '#as_json' do
- subject(:as_json) { entity.as_json }
+ subject { described_class.new(test_suite, request: request).as_json }
+
+ context 'when details option is not present' do
+ it 'does not expose suite error and test cases', :aggregate_failures do
+ expect(subject).not_to include(:test_cases)
+ expect(subject).not_to include(:suite_error)
+ end
+ end
+
+ context 'when details option is present' do
+ subject { described_class.new(test_suite, request: request, details: true).as_json }
it 'contains the suite name' do
- expect(as_json[:name]).to be_present
+ expect(subject[:name]).to be_present
end
it 'contains the total time' do
- expect(as_json[:total_time]).to be_present
+ expect(subject[:total_time]).to be_present
end
it 'contains the counts' do
- expect(as_json[:total_count]).to eq(4)
- expect(as_json[:success_count]).to eq(2)
- expect(as_json[:failed_count]).to eq(2)
- expect(as_json[:skipped_count]).to eq(0)
- expect(as_json[:error_count]).to eq(0)
+ expect(subject[:total_count]).to eq(4)
+ expect(subject[:success_count]).to eq(2)
+ expect(subject[:failed_count]).to eq(2)
+ expect(subject[:skipped_count]).to eq(0)
+ expect(subject[:error_count]).to eq(0)
end
it 'contains the test cases' do
- expect(as_json[:test_cases].count).to eq(4)
+ expect(subject[:test_cases].count).to eq(4)
end
it 'contains an empty error message' do
- expect(as_json[:suite_error]).to be_nil
+ expect(subject[:suite_error]).to be_nil
end
context 'with a suite error' do
@@ -40,27 +50,27 @@ describe TestSuiteEntity do
end
it 'contains the suite name' do
- expect(as_json[:name]).to be_present
+ expect(subject[:name]).to be_present
end
it 'contains the total time' do
- expect(as_json[:total_time]).to be_present
+ expect(subject[:total_time]).to be_present
end
it 'returns all the counts as 0' do
- expect(as_json[:total_count]).to eq(0)
- expect(as_json[:success_count]).to eq(0)
- expect(as_json[:failed_count]).to eq(0)
- expect(as_json[:skipped_count]).to eq(0)
- expect(as_json[:error_count]).to eq(0)
+ expect(subject[:total_count]).to eq(0)
+ expect(subject[:success_count]).to eq(0)
+ expect(subject[:failed_count]).to eq(0)
+ expect(subject[:skipped_count]).to eq(0)
+ expect(subject[:error_count]).to eq(0)
end
it 'returns no test cases' do
- expect(as_json[:test_cases]).to be_empty
+ expect(subject[:test_cases]).to be_empty
end
it 'returns a suite error' do
- expect(as_json[:suite_error]).to eq('a really bad error')
+ expect(subject[:suite_error]).to eq('a really bad error')
end
end
end
diff --git a/spec/serializers/test_suite_summary_entity_spec.rb b/spec/serializers/test_suite_summary_entity_spec.rb
new file mode 100644
index 00000000000..d26592bc60e
--- /dev/null
+++ b/spec/serializers/test_suite_summary_entity_spec.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe TestSuiteSummaryEntity do
+ let(:pipeline) { create(:ci_pipeline, :with_report_results) }
+ let(:entity) { described_class.new(pipeline.test_report_summary.total) }
+
+ describe '#as_json' do
+ subject(:as_json) { entity.as_json }
+
+ it 'contains the total time' do
+ expect(as_json).to include(:total_time)
+ end
+
+ it 'contains the counts' do
+ expect(as_json).to include(:total_count, :success_count, :failed_count, :skipped_count, :error_count)
+ end
+
+ it 'contains the build_ids' do
+ expect(as_json).to include(:build_ids)
+ end
+ end
+end
diff --git a/spec/serializers/trigger_variable_entity_spec.rb b/spec/serializers/trigger_variable_entity_spec.rb
index f5a21f943d8..e90bfc24f9f 100644
--- a/spec/serializers/trigger_variable_entity_spec.rb
+++ b/spec/serializers/trigger_variable_entity_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe TriggerVariableEntity do
+RSpec.describe TriggerVariableEntity do
let(:project) { create(:project) }
let(:request) { double('request') }
let(:user) { create(:user) }
diff --git a/spec/serializers/user_entity_spec.rb b/spec/serializers/user_entity_spec.rb
index 71107daf6ac..e9d86fe7282 100644
--- a/spec/serializers/user_entity_spec.rb
+++ b/spec/serializers/user_entity_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe UserEntity do
+RSpec.describe UserEntity do
include Gitlab::Routing
let(:entity) { described_class.new(user) }
diff --git a/spec/serializers/user_serializer_spec.rb b/spec/serializers/user_serializer_spec.rb
index 2e4a8c644fe..d54f33b6a23 100644
--- a/spec/serializers/user_serializer_spec.rb
+++ b/spec/serializers/user_serializer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe UserSerializer do
+RSpec.describe UserSerializer do
let(:user1) { create(:user) }
let(:user2) { create(:user) }
diff --git a/spec/serializers/variable_entity_spec.rb b/spec/serializers/variable_entity_spec.rb
deleted file mode 100644
index 3cb18dab314..00000000000
--- a/spec/serializers/variable_entity_spec.rb
+++ /dev/null
@@ -1,16 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-describe VariableEntity do
- let(:variable) { create(:ci_variable) }
- let(:entity) { described_class.new(variable) }
-
- describe '#as_json' do
- subject { entity.as_json }
-
- it 'contains required fields' do
- expect(subject).to include(:id, :key, :value, :protected, :environment_scope, :variable_type)
- end
- end
-end
diff --git a/spec/serializers/web_ide_terminal_entity_spec.rb b/spec/serializers/web_ide_terminal_entity_spec.rb
index e163afa14ed..a6f108bf25e 100644
--- a/spec/serializers/web_ide_terminal_entity_spec.rb
+++ b/spec/serializers/web_ide_terminal_entity_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe WebIdeTerminalEntity do
+RSpec.describe WebIdeTerminalEntity do
let(:build) { create(:ci_build) }
let(:entity) { described_class.new(WebIdeTerminal.new(build)) }
diff --git a/spec/serializers/web_ide_terminal_serializer_spec.rb b/spec/serializers/web_ide_terminal_serializer_spec.rb
index 01133deaf84..7ddc4bfedc8 100644
--- a/spec/serializers/web_ide_terminal_serializer_spec.rb
+++ b/spec/serializers/web_ide_terminal_serializer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe WebIdeTerminalSerializer do
+RSpec.describe WebIdeTerminalSerializer do
let(:build) { create(:ci_build) }
subject { described_class.new.represent(WebIdeTerminal.new(build)) }
diff --git a/spec/services/access_token_validation_service_spec.rb b/spec/services/access_token_validation_service_spec.rb
index b2a8da6c4c6..2bf74d64dc9 100644
--- a/spec/services/access_token_validation_service_spec.rb
+++ b/spec/services/access_token_validation_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe AccessTokenValidationService do
+RSpec.describe AccessTokenValidationService do
describe ".include_any_scope?" do
let(:request) { double("request") }
diff --git a/spec/services/alert_management/alerts/todo/create_service_spec.rb b/spec/services/alert_management/alerts/todo/create_service_spec.rb
new file mode 100644
index 00000000000..e3d9de8b4df
--- /dev/null
+++ b/spec/services/alert_management/alerts/todo/create_service_spec.rb
@@ -0,0 +1,84 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe AlertManagement::Alerts::Todo::CreateService do
+ let_it_be(:user) { create(:user) }
+ let_it_be(:alert) { create(:alert_management_alert) }
+
+ let(:current_user) { user }
+
+ describe '#execute' do
+ subject(:result) { AlertManagement::Alerts::Todo::CreateService.new(alert, current_user).execute }
+
+ shared_examples 'permissions error' do
+ it 'returns an error', :aggregate_failures do
+ expect(result.error?).to be(true)
+ expect(result.message).to eq('You have insufficient permissions to create a Todo for this alert')
+ expect(result.payload[:todo]).to be(nil)
+ expect(result.payload[:alert]).to be(alert)
+ end
+ end
+
+ context 'when the user is anonymous' do
+ let(:current_user) { nil }
+
+ it_behaves_like 'permissions error'
+ end
+
+ context 'when the user does not have permission' do
+ it_behaves_like 'permissions error'
+ end
+
+ context 'when user has permission' do
+ before do
+ alert.project.add_developer(user)
+ end
+
+ it 'creates a todo' do
+ expect { result }.to change { Todo.count }.by(1)
+ end
+
+ it 'returns the alert and todo in the payload', :aggregate_failures do
+ expect(result.success?).to be(true)
+ expect(result.payload[:alert][:id]).to be(alert.id)
+ expect(result.payload[:todo][:id]).to be(Todo.last.id)
+ end
+
+ context 'when the user has a marked todo for the alert' do
+ let_it_be(:todo_params) do
+ { project: alert.project,
+ target: alert,
+ user: user,
+ action: Todo::MARKED }
+ end
+
+ context 'when todo is pending' do
+ before_all do
+ create(:todo, :pending, **todo_params)
+ end
+
+ it 'does not create a todo' do
+ expect { result }.not_to change { Todo.count }
+ end
+
+ it 'returns an error', :aggregate_failures do
+ expect(result.error?).to be(true)
+ expect(result.message).to be('You already have pending todo for this alert')
+ expect(result.payload[:todo]).to be(nil)
+ expect(result.payload[:alert]).to be(alert)
+ end
+ end
+
+ context 'when todo is done' do
+ before do
+ create(:todo, :done, **todo_params)
+ end
+
+ it { expect(result.success?).to be(true) }
+ it { expect { result }.to change { Todo.count }.by(1) }
+ end
+ end
+ end
+ end
+end
diff --git a/spec/services/alert_management/alerts/update_service_spec.rb b/spec/services/alert_management/alerts/update_service_spec.rb
index e185e67c5cf..91b02325bad 100644
--- a/spec/services/alert_management/alerts/update_service_spec.rb
+++ b/spec/services/alert_management/alerts/update_service_spec.rb
@@ -2,11 +2,12 @@
require 'spec_helper'
-describe AlertManagement::Alerts::UpdateService do
+RSpec.describe AlertManagement::Alerts::UpdateService do
let_it_be(:user_with_permissions) { create(:user) }
+ let_it_be(:other_user_with_permissions) { create(:user) }
let_it_be(:user_without_permissions) { create(:user) }
- let_it_be(:alert, reload: true) { create(:alert_management_alert) }
- let_it_be(:project) { alert.project }
+ let_it_be(:project) { create(:project) }
+ let_it_be(:alert, reload: true) { create(:alert_management_alert, :triggered, project: project) }
let(:current_user) { user_with_permissions }
let(:params) { {} }
@@ -15,119 +16,225 @@ describe AlertManagement::Alerts::UpdateService do
before_all do
project.add_developer(user_with_permissions)
+ project.add_developer(other_user_with_permissions)
end
describe '#execute' do
+ shared_examples 'does not add a todo' do
+ specify { expect { response }.not_to change(Todo, :count) }
+ end
+
+ shared_examples 'does not add a system note' do
+ specify { expect { response }.not_to change(Note, :count) }
+ end
+
+ shared_examples 'adds a system note' do
+ specify { expect { response }.to change { alert.reload.notes.count }.by(1) }
+ end
+
+ shared_examples 'error response' do |message|
+ it_behaves_like 'does not add a todo'
+ it_behaves_like 'does not add a system note'
+
+ it 'has an informative message' do
+ expect(response).to be_error
+ expect(response.message).to eq(message)
+ end
+ end
+
subject(:response) { service.execute }
context 'when the current_user is nil' do
let(:current_user) { nil }
- it 'results in an error' do
- expect(response).to be_error
- expect(response.message).to eq('You have no permissions')
- end
+ it_behaves_like 'error response', 'You have no permissions'
end
- context 'when user does not have permission to update alerts' do
+ context 'when current_user does not have permission to update alerts' do
let(:current_user) { user_without_permissions }
- it 'results in an error' do
- expect(response).to be_error
- expect(response.message).to eq('You have no permissions')
- end
+ it_behaves_like 'error response', 'You have no permissions'
end
context 'when no parameters are included' do
- it 'results in an error' do
- expect(response).to be_error
- expect(response.message).to eq('Please provide attributes to update')
- end
+ it_behaves_like 'error response', 'Please provide attributes to update'
end
- context 'when an error occures during update' do
+ context 'when an error occurs during update' do
let(:params) { { title: nil } }
- it 'results in an error' do
- expect { response }.not_to change { alert.reload.notes.count }
- expect(response).to be_error
- expect(response.message).to eq("Title can't be blank")
- end
+ it_behaves_like 'error response', "Title can't be blank"
end
- context 'when a model attribute is included without assignees' do
- let(:params) { { title: 'This is an updated alert.' } }
+ shared_examples 'title update' do
+ it_behaves_like 'does not add a todo'
+ it_behaves_like 'does not add a system note'
it 'updates the attribute' do
original_title = alert.title
- expect { response }.to change { alert.title }.from(original_title).to(params[:title])
+ expect { response }.to change { alert.title }.from(original_title).to(expected_title)
expect(response).to be_success
end
+ end
- it 'skips adding a todo' do
- expect { response }.not_to change(Todo, :count)
- end
+ context 'when a model attribute is included without assignees' do
+ let(:params) { { title: 'This is an updated alert.' } }
+ let(:expected_title) { params[:title] }
+
+ it_behaves_like 'title update'
+ end
+
+ context 'when alert is resolved and another existing open alert' do
+ let!(:alert) { create(:alert_management_alert, :resolved, project: project) }
+ let!(:existing_alert) { create(:alert_management_alert, :triggered, project: project) }
+
+ let(:params) { { title: 'This is an updated alert.' } }
+ let(:expected_title) { params[:title] }
+
+ it_behaves_like 'title update'
end
context 'when assignees are included' do
- let(:params) { { assignees: [user_with_permissions] } }
+ shared_examples 'adds a todo' do
+ let(:assignee) { expected_assignees.first }
- after do
- alert.assignees = []
+ specify do
+ expect { response }.to change { assignee.reload.todos.count }.by(1)
+ expect(assignee.todos.last.author).to eq(current_user)
+ end
end
- it 'assigns the user' do
- expect { response }.to change { alert.reload.assignees }.from([]).to(params[:assignees])
- expect(response).to be_success
+ shared_examples 'successful assignment' do
+ it_behaves_like 'adds a system note'
+ it_behaves_like 'adds a todo'
+
+ after do
+ alert.assignees = []
+ end
+
+ specify do
+ expect { response }.to change { alert.reload.assignees }.from([]).to(expected_assignees)
+ expect(response).to be_success
+ end
+ end
+
+ let(:expected_assignees) { params[:assignees] }
+
+ context 'when the assignee is the current user' do
+ let(:params) { { assignees: [current_user] } }
+
+ it_behaves_like 'successful assignment'
end
- it 'creates a system note for the assignment' do
- expect { response }.to change { alert.reload.notes.count }.by(1)
+ context 'when the assignee has read permissions' do
+ let(:params) { { assignees: [other_user_with_permissions] } }
+
+ it_behaves_like 'successful assignment'
end
- it 'adds a todo' do
- expect { response }.to change { Todo.where(user: user_with_permissions).count }.by(1)
+ context 'when the assignee does not have read permissions' do
+ let(:params) { { assignees: [user_without_permissions] } }
+
+ it_behaves_like 'error response', 'Assignee has no permissions'
end
- context 'when current user is not the assignee' do
- let(:assignee_user) { create(:user) }
- let(:params) { { assignees: [assignee_user] } }
+ context 'when user is already assigned' do
+ let(:params) { { assignees: [user_with_permissions] } }
- it 'skips adding todo for assignee without permission to read alert' do
- expect { response }.not_to change(Todo, :count)
+ before do
+ alert.assignees << user_with_permissions
end
- context 'when assignee has read permission' do
- before do
- project.add_developer(assignee_user)
- end
+ it_behaves_like 'does not add a system note'
+ # TODO: We should not add another todo in this scenario
+ it_behaves_like 'adds a todo'
+ end
- it 'adds a todo' do
- response
+ context 'with multiple users included' do
+ let(:params) { { assignees: [user_with_permissions, user_without_permissions] } }
+ let(:expected_assignees) { [user_with_permissions] }
- expect(Todo.first.author).to eq(current_user)
- end
+ it_behaves_like 'successful assignment'
+ end
+ end
+
+ context 'when a status is included' do
+ let(:params) { { status: new_status } }
+ let(:new_status) { AlertManagement::Alert::STATUSES[:acknowledged] }
+
+ it 'successfully changes the status' do
+ expect { response }.to change { alert.acknowledged? }.to(true)
+ expect(response).to be_success
+ expect(response.payload[:alert]).to eq(alert)
+ end
+
+ it_behaves_like 'adds a system note'
+
+ context 'with unknown status' do
+ let(:new_status) { -1 }
+
+ it_behaves_like 'error response', 'Invalid status'
+ end
+
+ context 'with resolving status' do
+ let(:new_status) { AlertManagement::Alert::STATUSES[:resolved] }
+
+ it 'changes the status' do
+ expect { response }.to change { alert.resolved? }.to(true)
+ end
+
+ it "resolves the current user's related todos" do
+ todo = create(:todo, :pending, target: alert, user: current_user, project: alert.project)
+
+ expect { response }.to change { todo.reload.state }.from('pending').to('done')
+ end
+ end
+
+ context 'with an opening status and existing open alert' do
+ let_it_be(:alert) { create(:alert_management_alert, :resolved, :with_fingerprint, project: project) }
+ let_it_be(:existing_alert) { create(:alert_management_alert, :triggered, fingerprint: alert.fingerprint, project: project) }
+ let_it_be(:url) { Gitlab::Routing.url_helpers.details_project_alert_management_path(project, existing_alert) }
+ let_it_be(:link) { ActionController::Base.helpers.link_to(_('alert'), url) }
+
+ let(:message) do
+ "An #{link} with the same fingerprint is already open. " \
+ 'To change the status of this alert, resolve the linked alert.'
end
- context 'when current_user is nil' do
- let(:current_user) { nil }
+ it_behaves_like 'does not add a todo'
+ it_behaves_like 'does not add a system note'
+
+ it 'has an informative message' do
+ expect(response).to be_error
+ expect(response.message).to eq(message)
+ end
- it 'skips adding todo if current_user is nil' do
- project.add_developer(assignee_user)
+ context 'fingerprints are blank' do
+ let_it_be(:alert) { create(:alert_management_alert, :resolved, project: project, fingerprint: nil) }
+ let_it_be(:existing_alert) { create(:alert_management_alert, :triggered, fingerprint: alert.fingerprint, project: project) }
- expect { response }.not_to change(Todo, :count)
+ it 'successfully changes the status' do
+ expect { response }.to change { alert.acknowledged? }.to(true)
+ expect(response).to be_success
+ expect(response.payload[:alert]).to eq(alert)
end
+
+ it_behaves_like 'adds a system note'
end
end
- context 'with multiple users included' do
- let(:params) { { assignees: [user_with_permissions, user_without_permissions] } }
+ context 'two existing closed alerts' do
+ let_it_be(:alert) { create(:alert_management_alert, :resolved, :with_fingerprint, project: project) }
+ let_it_be(:existing_alert) { create(:alert_management_alert, :resolved, fingerprint: alert.fingerprint, project: project) }
- it 'assigns the first permissioned user' do
- expect { response }.to change { alert.reload.assignees }.from([]).to([user_with_permissions])
+ it 'successfully changes the status' do
+ expect { response }.to change { alert.acknowledged? }.to(true)
expect(response).to be_success
+ expect(response.payload[:alert]).to eq(alert)
end
+
+ it_behaves_like 'adds a system note'
end
end
end
diff --git a/spec/services/alert_management/create_alert_issue_service_spec.rb b/spec/services/alert_management/create_alert_issue_service_spec.rb
index 9bc8b731dc1..a8f2b4ee09c 100644
--- a/spec/services/alert_management/create_alert_issue_service_spec.rb
+++ b/spec/services/alert_management/create_alert_issue_service_spec.rb
@@ -4,19 +4,18 @@ require 'spec_helper'
RSpec.describe AlertManagement::CreateAlertIssueService do
let_it_be(:user) { create(:user) }
- let_it_be(:project) { create(:project) }
+ let_it_be(:group) { create(:group) }
+ let_it_be(:project) { create(:project, group: group) }
let_it_be(:payload) do
{
- 'annotations' => {
- 'title' => 'Alert title'
- },
'startsAt' => '2020-04-27T10:10:22.265949279Z',
'generatorURL' => 'http://8d467bd4607a:9090/graph?g0.expr=vector%281%29&g0.tab=1'
}
end
let_it_be(:generic_alert, reload: true) { create(:alert_management_alert, :triggered, project: project, payload: payload) }
- let_it_be(:prometheus_alert) { create(:alert_management_alert, :triggered, :prometheus, project: project, payload: payload) }
+ let_it_be(:prometheus_alert, reload: true) { create(:alert_management_alert, :triggered, :prometheus, project: project, payload: payload) }
let(:alert) { generic_alert }
+ let(:alert_presenter) { alert.present }
let(:created_issue) { Issue.last! }
describe '#execute' do
@@ -29,7 +28,7 @@ RSpec.describe AlertManagement::CreateAlertIssueService do
.and_return(can_create)
end
- shared_examples 'creating an alert' do
+ shared_examples 'creating an alert issue' do
it 'creates an issue' do
expect { execute }.to change { project.issues.count }.by(1)
end
@@ -48,11 +47,27 @@ RSpec.describe AlertManagement::CreateAlertIssueService do
expect(alert.reload.issue_id).to eq(created_issue.id)
end
- it 'sets issue author to the current user' do
+ it 'creates a system note' do
+ expect { execute }.to change { alert.reload.notes.count }.by(1)
+ end
+ end
+
+ shared_examples 'setting an issue attributes' do
+ before do
execute
+ end
+ it 'sets issue author to the current user' do
expect(created_issue.author).to eq(user)
end
+
+ it 'sets the issue title' do
+ expect(created_issue.title).to eq(alert.title)
+ end
+
+ it 'sets the issue description' do
+ expect(created_issue.description).to include(alert_presenter.issue_summary_markdown.strip)
+ end
end
context 'when a user is allowed to create an issue' do
@@ -69,27 +84,33 @@ RSpec.describe AlertManagement::CreateAlertIssueService do
context 'when the alert is prometheus alert' do
let(:alert) { prometheus_alert }
+ let(:issue) { subject.payload[:issue] }
- it_behaves_like 'creating an alert'
+ it_behaves_like 'creating an alert issue'
+ it_behaves_like 'setting an issue attributes'
+ it_behaves_like 'create alert issue sets issue labels'
end
context 'when the alert is generic' do
let(:alert) { generic_alert }
+ let(:issue) { subject.payload[:issue] }
- it_behaves_like 'creating an alert'
+ it_behaves_like 'creating an alert issue'
+ it_behaves_like 'setting an issue attributes'
+ it_behaves_like 'create alert issue sets issue labels'
end
context 'when issue cannot be created' do
- let(:alert) { prometheus_alert }
+ let(:alert) { generic_alert }
before do
- # set invalid payload for Prometheus alert
- alert.update!(payload: {})
+ # Invalid alert
+ alert.update_columns(title: '')
end
it 'has an unsuccessful status' do
expect(execute).to be_error
- expect(execute.message).to eq('invalid alert')
+ expect(execute.message).to eq("Title can't be blank")
end
end
diff --git a/spec/services/alert_management/process_prometheus_alert_service_spec.rb b/spec/services/alert_management/process_prometheus_alert_service_spec.rb
index 5b4da5e9077..0ce88f6b5b7 100644
--- a/spec/services/alert_management/process_prometheus_alert_service_spec.rb
+++ b/spec/services/alert_management/process_prometheus_alert_service_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
RSpec.describe AlertManagement::ProcessPrometheusAlertService do
- let_it_be(:project) { create(:project) }
+ let_it_be(:project) { create(:project, :repository) }
before do
allow(ProjectServiceWorker).to receive(:perform_async)
@@ -35,26 +35,31 @@ RSpec.describe AlertManagement::ProcessPrometheusAlertService do
}
end
- context 'when Prometheus alert status is firing' do
- let(:status) { 'firing' }
+ let(:status) { 'firing' }
+ context 'when Prometheus alert status is firing' do
context 'when alert with the same fingerprint already exists' do
- let!(:alert) { create(:alert_management_alert, :resolved, project: project, fingerprint: parsed_alert.gitlab_fingerprint) }
+ let!(:alert) { create(:alert_management_alert, project: project, fingerprint: parsed_alert.gitlab_fingerprint) }
+
+ it_behaves_like 'adds an alert management alert event'
+
+ context 'existing alert is resolved' do
+ let!(:alert) { create(:alert_management_alert, :resolved, project: project, fingerprint: parsed_alert.gitlab_fingerprint) }
- it 'increases alert events count' do
- expect { execute }.to change { alert.reload.events }.by(1)
+ it_behaves_like 'creates an alert management alert'
end
- context 'when status can be changed' do
- it 'changes status to triggered' do
- expect { execute }.to change { alert.reload.triggered? }.to(true)
- end
+ context 'existing alert is ignored' do
+ let!(:alert) { create(:alert_management_alert, :ignored, project: project, fingerprint: parsed_alert.gitlab_fingerprint) }
+
+ it_behaves_like 'adds an alert management alert event'
end
- it 'does not executes the alert service hooks' do
- expect(alert).not_to receive(:execute_services)
+ context 'two existing alerts, one resolved one open' do
+ let!(:resolved_alert) { create(:alert_management_alert, :resolved, project: project, fingerprint: parsed_alert.gitlab_fingerprint) }
+ let!(:alert) { create(:alert_management_alert, project: project, fingerprint: parsed_alert.gitlab_fingerprint) }
- subject
+ it_behaves_like 'adds an alert management alert event'
end
context 'when status change did not succeed' do
@@ -73,23 +78,11 @@ RSpec.describe AlertManagement::ProcessPrometheusAlertService do
execute
end
end
-
- it { is_expected.to be_success }
end
context 'when alert does not exist' do
context 'when alert can be created' do
- it 'creates a new alert' do
- expect { execute }.to change { AlertManagement::Alert.where(project: project).count }.by(1)
- end
-
- it 'executes the alert service hooks' do
- slack_service = create(:service, type: 'SlackService', project: project, alert_events: true, active: true)
-
- subject
-
- expect(ProjectServiceWorker).to have_received(:perform_async).with(slack_service.id, an_instance_of(Hash))
- end
+ it_behaves_like 'creates an alert management alert'
end
context 'when alert cannot be created' do
@@ -123,6 +116,31 @@ RSpec.describe AlertManagement::ProcessPrometheusAlertService do
it 'resolves an existing alert' do
expect { execute }.to change { alert.reload.resolved? }.to(true)
end
+
+ [true, false].each do |state_tracking_enabled|
+ context 'existing issue' do
+ before do
+ stub_feature_flags(track_resource_state_change_events: state_tracking_enabled)
+ end
+
+ let!(:alert) { create(:alert_management_alert, :with_issue, project: project, fingerprint: parsed_alert.gitlab_fingerprint) }
+
+ it 'closes the issue' do
+ issue = alert.issue
+
+ expect { execute }
+ .to change { issue.reload.state }
+ .from('opened')
+ .to('closed')
+ end
+
+ if state_tracking_enabled
+ specify { expect { execute }.to change(ResourceStateEvent, :count).by(1) }
+ else
+ specify { expect { execute }.to change(Note, :count).by(1) }
+ end
+ end
+ end
end
context 'when status change did not succeed' do
@@ -144,6 +162,33 @@ RSpec.describe AlertManagement::ProcessPrometheusAlertService do
it { is_expected.to be_success }
end
+
+ context 'environment given' do
+ let(:environment) { create(:environment, project: project) }
+
+ it 'sets the environment' do
+ payload['labels']['gitlab_environment_name'] = environment.name
+ execute
+
+ alert = project.alert_management_alerts.last
+
+ expect(alert.environment).to eq(environment)
+ end
+ end
+
+ context 'prometheus alert given' do
+ let(:prometheus_alert) { create(:prometheus_alert, project: project) }
+
+ it 'sets the prometheus alert and environment' do
+ payload['labels']['gitlab_alert_id'] = prometheus_alert.prometheus_metric_id
+ execute
+
+ alert = project.alert_management_alerts.last
+
+ expect(alert.prometheus_alert).to eq(prometheus_alert)
+ expect(alert.environment).to eq(prometheus_alert.environment)
+ end
+ end
end
context 'when alert payload is invalid' do
diff --git a/spec/services/alert_management/update_alert_status_service_spec.rb b/spec/services/alert_management/update_alert_status_service_spec.rb
deleted file mode 100644
index b287d0d1614..00000000000
--- a/spec/services/alert_management/update_alert_status_service_spec.rb
+++ /dev/null
@@ -1,66 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-describe AlertManagement::UpdateAlertStatusService do
- let(:project) { alert.project }
- let_it_be(:user) { build(:user) }
-
- let_it_be(:alert, reload: true) do
- create(:alert_management_alert, :triggered)
- end
-
- let(:service) { described_class.new(alert, user, new_status) }
-
- describe '#execute' do
- shared_examples 'update failure' do |error_message|
- it 'returns an error' do
- expect(response).to be_error
- expect(response.message).to eq(error_message)
- expect(response.payload[:alert]).to eq(alert)
- end
-
- it 'does not update the status' do
- expect { response }.not_to change { alert.status }
- end
- end
-
- let(:new_status) { Types::AlertManagement::StatusEnum.values['ACKNOWLEDGED'].value }
- let(:can_update) { true }
-
- subject(:response) { service.execute }
-
- before do
- allow(user).to receive(:can?)
- .with(:update_alert_management_alert, project)
- .and_return(can_update)
- end
-
- it 'returns success' do
- expect(response).to be_success
- expect(response.payload[:alert]).to eq(alert)
- end
-
- it 'updates the status' do
- expect { response }.to change { alert.acknowledged? }.to(true)
- end
-
- context 'when user has no permissions' do
- let(:can_update) { false }
-
- include_examples 'update failure', _('You have no permissions')
- end
-
- context 'with no status' do
- let(:new_status) { nil }
-
- include_examples 'update failure', _('Invalid status')
- end
-
- context 'with unknown status' do
- let(:new_status) { -1 }
-
- include_examples 'update failure', _('Invalid status')
- end
- end
-end
diff --git a/spec/services/application_settings/update_service_spec.rb b/spec/services/application_settings/update_service_spec.rb
index 3a37cbc3522..e5060fa2eeb 100644
--- a/spec/services/application_settings/update_service_spec.rb
+++ b/spec/services/application_settings/update_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ApplicationSettings::UpdateService do
+RSpec.describe ApplicationSettings::UpdateService do
include ExternalAuthorizationServiceHelpers
let(:application_settings) { create(:application_setting) }
diff --git a/spec/services/applications/create_service_spec.rb b/spec/services/applications/create_service_spec.rb
index c8134087fa1..58ac723ee55 100644
--- a/spec/services/applications/create_service_spec.rb
+++ b/spec/services/applications/create_service_spec.rb
@@ -2,7 +2,7 @@
require "spec_helper"
-describe ::Applications::CreateService do
+RSpec.describe ::Applications::CreateService do
include TestRequestHelpers
let(:user) { create(:user) }
diff --git a/spec/services/audit_event_service_spec.rb b/spec/services/audit_event_service_spec.rb
index dc86735805c..530d3469481 100644
--- a/spec/services/audit_event_service_spec.rb
+++ b/spec/services/audit_event_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe AuditEventService do
+RSpec.describe AuditEventService do
let(:project) { create(:project) }
let(:user) { create(:user, :with_sign_ins) }
let(:project_member) { create(:project_member, user: user) }
@@ -17,6 +17,7 @@ describe AuditEventService do
it 'creates an event and logs to a file' do
expect(service).to receive(:file_logger).and_return(logger)
expect(logger).to receive(:info).with(author_id: user.id,
+ author_name: user.name,
entity_id: project.id,
entity_type: "Project",
action: :destroy)
@@ -35,6 +36,7 @@ describe AuditEventService do
})
expect(service).to receive(:file_logger).and_return(logger)
expect(logger).to receive(:info).with(author_id: user.id,
+ author_name: user.name,
entity_type: 'Project',
entity_id: project.id,
from: 'true',
@@ -56,6 +58,7 @@ describe AuditEventService do
it 'logs security event to file' do
expect(service).to receive(:file_logger).and_return(logger)
expect(logger).to receive(:info).with(author_id: user.id,
+ author_name: user.name,
entity_type: 'Project',
entity_id: project.id,
action: :destroy)
diff --git a/spec/services/auth/container_registry_authentication_service_spec.rb b/spec/services/auth/container_registry_authentication_service_spec.rb
index 70eb35f0826..8d58c4b27e1 100644
--- a/spec/services/auth/container_registry_authentication_service_spec.rb
+++ b/spec/services/auth/container_registry_authentication_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Auth::ContainerRegistryAuthenticationService do
+RSpec.describe Auth::ContainerRegistryAuthenticationService do
let(:current_project) { nil }
let(:current_user) { nil }
let(:current_params) { {} }
diff --git a/spec/services/authorized_project_update/periodic_recalculate_service_spec.rb b/spec/services/authorized_project_update/periodic_recalculate_service_spec.rb
index 020056da36e..c776e013fdf 100644
--- a/spec/services/authorized_project_update/periodic_recalculate_service_spec.rb
+++ b/spec/services/authorized_project_update/periodic_recalculate_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe AuthorizedProjectUpdate::PeriodicRecalculateService do
+RSpec.describe AuthorizedProjectUpdate::PeriodicRecalculateService do
subject(:service) { described_class.new }
describe '#execute' do
diff --git a/spec/services/authorized_project_update/project_create_service_spec.rb b/spec/services/authorized_project_update/project_create_service_spec.rb
index 5b3e36af766..891800bfb87 100644
--- a/spec/services/authorized_project_update/project_create_service_spec.rb
+++ b/spec/services/authorized_project_update/project_create_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe AuthorizedProjectUpdate::ProjectCreateService do
+RSpec.describe AuthorizedProjectUpdate::ProjectCreateService do
let_it_be(:group_parent) { create(:group, :private) }
let_it_be(:group) { create(:group, :private, parent: group_parent) }
let_it_be(:group_child) { create(:group, :private, parent: group) }
diff --git a/spec/services/authorized_project_update/project_group_link_create_service_spec.rb b/spec/services/authorized_project_update/project_group_link_create_service_spec.rb
new file mode 100644
index 00000000000..d30d9f1e766
--- /dev/null
+++ b/spec/services/authorized_project_update/project_group_link_create_service_spec.rb
@@ -0,0 +1,190 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe AuthorizedProjectUpdate::ProjectGroupLinkCreateService do
+ let_it_be(:group_parent) { create(:group, :private) }
+ let_it_be(:group) { create(:group, :private, parent: group_parent) }
+ let_it_be(:group_child) { create(:group, :private, parent: group) }
+
+ let_it_be(:parent_group_user) { create(:user) }
+ let_it_be(:group_user) { create(:user) }
+
+ let_it_be(:project) { create(:project, :private, group: create(:group, :private)) }
+
+ let(:access_level) { Gitlab::Access::MAINTAINER }
+
+ subject(:service) { described_class.new(project, group) }
+
+ describe '#perform' do
+ context 'direct group members' do
+ before do
+ create(:group_member, access_level: access_level, group: group, user: group_user)
+ ProjectAuthorization.delete_all
+ end
+
+ it 'creates project authorization' do
+ expect { service.execute }.to(
+ change { ProjectAuthorization.count }.from(0).to(1))
+
+ project_authorization = ProjectAuthorization.where(
+ project_id: project.id,
+ user_id: group_user.id,
+ access_level: access_level)
+
+ expect(project_authorization).to exist
+ end
+ end
+
+ context 'inherited group members' do
+ before do
+ create(:group_member, access_level: access_level, group: group_parent, user: parent_group_user)
+ ProjectAuthorization.delete_all
+ end
+
+ it 'creates project authorization' do
+ expect { service.execute }.to(
+ change { ProjectAuthorization.count }.from(0).to(1))
+
+ project_authorization = ProjectAuthorization.where(
+ project_id: project.id,
+ user_id: parent_group_user.id,
+ access_level: access_level)
+ expect(project_authorization).to exist
+ end
+ end
+
+ context 'membership overrides' do
+ before do
+ create(:group_member, access_level: Gitlab::Access::REPORTER, group: group_parent, user: group_user)
+ create(:group_member, access_level: Gitlab::Access::DEVELOPER, group: group, user: group_user)
+ ProjectAuthorization.delete_all
+ end
+
+ it 'creates project authorization' do
+ expect { service.execute }.to(
+ change { ProjectAuthorization.count }.from(0).to(1))
+
+ project_authorization = ProjectAuthorization.where(
+ project_id: project.id,
+ user_id: group_user.id,
+ access_level: Gitlab::Access::DEVELOPER)
+ expect(project_authorization).to exist
+ end
+ end
+
+ context 'no group member' do
+ it 'does not create project authorization' do
+ expect { service.execute }.not_to(
+ change { ProjectAuthorization.count }.from(0))
+ end
+ end
+
+ context 'unapproved access requests' do
+ before do
+ create(:group_member, :guest, :access_request, user: group_user, group: group)
+ end
+
+ it 'does not create project authorization' do
+ expect { service.execute }.not_to(
+ change { ProjectAuthorization.count }.from(0))
+ end
+ end
+
+ context 'project has more users than BATCH_SIZE' do
+ let(:batch_size) { 2 }
+ let(:users) { create_list(:user, batch_size + 1 ) }
+
+ before do
+ stub_const("#{described_class.name}::BATCH_SIZE", batch_size)
+
+ users.each do |user|
+ create(:group_member, access_level: access_level, group: group_parent, user: user)
+ end
+
+ ProjectAuthorization.delete_all
+ end
+
+ it 'bulk creates project authorizations in batches' do
+ users.each_slice(batch_size) do |batch|
+ attributes = batch.map do |user|
+ { user_id: user.id, project_id: project.id, access_level: access_level }
+ end
+
+ expect(ProjectAuthorization).to(
+ receive(:insert_all).with(array_including(attributes)).and_call_original)
+ end
+
+ expect { service.execute }.to(
+ change { ProjectAuthorization.count }.from(0).to(batch_size + 1))
+ end
+ end
+
+ context 'users have existing project authorizations' do
+ before do
+ create(:group_member, access_level: access_level, group: group, user: group_user)
+ ProjectAuthorization.delete_all
+
+ create(:project_authorization, user_id: group_user.id,
+ project_id: project.id,
+ access_level: existing_access_level)
+ end
+
+ context 'when access level is the same' do
+ let(:existing_access_level) { access_level }
+
+ it 'does not create project authorization' do
+ project_authorization = ProjectAuthorization.where(
+ project_id: project.id,
+ user_id: group_user.id,
+ access_level: existing_access_level)
+
+ expect(ProjectAuthorization).not_to receive(:insert_all)
+
+ expect { service.execute }.not_to(
+ change { project_authorization.reload.exists? }.from(true))
+ end
+ end
+
+ context 'when existing access level is lower' do
+ let(:existing_access_level) { Gitlab::Access::DEVELOPER }
+
+ it 'creates new project authorization' do
+ project_authorization = ProjectAuthorization.where(
+ project_id: project.id,
+ user_id: group_user.id,
+ access_level: access_level)
+
+ expect { service.execute }.to(
+ change { project_authorization.reload.exists? }.from(false).to(true))
+ end
+
+ it 'deletes previous project authorization' do
+ project_authorization = ProjectAuthorization.where(
+ project_id: project.id,
+ user_id: group_user.id,
+ access_level: existing_access_level)
+
+ expect { service.execute }.to(
+ change { project_authorization.reload.exists? }.from(true).to(false))
+ end
+ end
+
+ context 'when existing access level is higher' do
+ let(:existing_access_level) { Gitlab::Access::OWNER }
+
+ it 'does not create project authorization' do
+ project_authorization = ProjectAuthorization.where(
+ project_id: project.id,
+ user_id: group_user.id,
+ access_level: existing_access_level)
+
+ expect(ProjectAuthorization).not_to receive(:insert_all)
+
+ expect { service.execute }.not_to(
+ change { project_authorization.reload.exists? }.from(true))
+ end
+ end
+ end
+ end
+end
diff --git a/spec/services/authorized_project_update/recalculate_for_user_range_service_spec.rb b/spec/services/authorized_project_update/recalculate_for_user_range_service_spec.rb
index 28cbda6f4fd..a4637b6ba1c 100644
--- a/spec/services/authorized_project_update/recalculate_for_user_range_service_spec.rb
+++ b/spec/services/authorized_project_update/recalculate_for_user_range_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe AuthorizedProjectUpdate::RecalculateForUserRangeService do
+RSpec.describe AuthorizedProjectUpdate::RecalculateForUserRangeService do
describe '#execute' do
let_it_be(:users) { create_list(:user, 2) }
diff --git a/spec/services/auto_merge/base_service_spec.rb b/spec/services/auto_merge/base_service_spec.rb
index e08e1d670bf..98fa6012089 100644
--- a/spec/services/auto_merge/base_service_spec.rb
+++ b/spec/services/auto_merge/base_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe AutoMerge::BaseService do
+RSpec.describe AutoMerge::BaseService do
let(:project) { create(:project) }
let(:user) { create(:user) }
let(:service) { described_class.new(project, user, params) }
@@ -51,7 +51,7 @@ describe AutoMerge::BaseService do
expect(merge_request.merge_params['commit_message']).to eq("Merge branch 'patch-12' into 'master'")
expect(merge_request.merge_params['sha']).to eq('200fcc9c260f7219eaf0daba87d818f0922c5b18')
expect(merge_request.merge_params['should_remove_source_branch']).to eq(false)
- expect(merge_request.squash).to eq(false)
+ expect(merge_request.squash_on_merge?).to eq(false)
expect(merge_request.merge_params['squash_commit_message']).to eq('Update README.md')
end
end
diff --git a/spec/services/auto_merge/merge_when_pipeline_succeeds_service_spec.rb b/spec/services/auto_merge/merge_when_pipeline_succeeds_service_spec.rb
index b6e8d3c636a..3bf59f6a2d1 100644
--- a/spec/services/auto_merge/merge_when_pipeline_succeeds_service_spec.rb
+++ b/spec/services/auto_merge/merge_when_pipeline_succeeds_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe AutoMerge::MergeWhenPipelineSucceedsService do
+RSpec.describe AutoMerge::MergeWhenPipelineSucceedsService do
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project, :repository) }
@@ -69,6 +69,7 @@ describe AutoMerge::MergeWhenPipelineSucceedsService do
before do
allow(merge_request)
.to receive_messages(head_pipeline: pipeline, actual_head_pipeline: pipeline)
+ expect(MailScheduler::NotificationServiceWorker).to receive(:perform_async).with('merge_when_pipeline_succeeds', merge_request, user).once
service.execute(merge_request)
end
@@ -90,6 +91,18 @@ describe AutoMerge::MergeWhenPipelineSucceedsService do
end
end
+ context 'without feature enabled' do
+ it 'does not send notification' do
+ stub_feature_flags(mwps_notification: false)
+
+ allow(merge_request)
+ .to receive_messages(head_pipeline: pipeline, actual_head_pipeline: pipeline)
+ expect(MailScheduler::NotificationServiceWorker).not_to receive(:perform_async)
+
+ service.execute(merge_request)
+ end
+ end
+
context 'already approved' do
let(:service) { described_class.new(project, user, should_remove_source_branch: true) }
let(:build) { create(:ci_build, ref: mr_merge_if_green_enabled.source_branch) }
@@ -106,6 +119,7 @@ describe AutoMerge::MergeWhenPipelineSucceedsService do
it 'updates the merge params' do
expect(SystemNoteService).not_to receive(:merge_when_pipeline_succeeds)
+ expect(MailScheduler::NotificationServiceWorker).not_to receive(:perform_async).with('merge_when_pipeline_succeeds', any_args)
service.execute(mr_merge_if_green_enabled)
expect(mr_merge_if_green_enabled.merge_params).to have_key('should_remove_source_branch')
diff --git a/spec/services/auto_merge_service_spec.rb b/spec/services/auto_merge_service_spec.rb
index bab69fb4aa3..eab95973e1b 100644
--- a/spec/services/auto_merge_service_spec.rb
+++ b/spec/services/auto_merge_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe AutoMergeService do
+RSpec.describe AutoMergeService do
let_it_be(:project) { create(:project, :repository) }
let_it_be(:user) { create(:user) }
let(:service) { described_class.new(project, user) }
diff --git a/spec/services/award_emojis/add_service_spec.rb b/spec/services/award_emojis/add_service_spec.rb
index 4bcb5fa039f..85c39015614 100644
--- a/spec/services/award_emojis/add_service_spec.rb
+++ b/spec/services/award_emojis/add_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe AwardEmojis::AddService do
+RSpec.describe AwardEmojis::AddService do
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project) }
let_it_be(:awardable) { create(:note, project: project) }
diff --git a/spec/services/award_emojis/collect_user_emoji_service_spec.rb b/spec/services/award_emojis/collect_user_emoji_service_spec.rb
index a0dea31b403..bf5aa0eb9ef 100644
--- a/spec/services/award_emojis/collect_user_emoji_service_spec.rb
+++ b/spec/services/award_emojis/collect_user_emoji_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe AwardEmojis::CollectUserEmojiService do
+RSpec.describe AwardEmojis::CollectUserEmojiService do
describe '#execute' do
it 'returns an Array containing the awarded emoji names' do
user = create(:user)
diff --git a/spec/services/award_emojis/destroy_service_spec.rb b/spec/services/award_emojis/destroy_service_spec.rb
index f411345560e..2aba078b638 100644
--- a/spec/services/award_emojis/destroy_service_spec.rb
+++ b/spec/services/award_emojis/destroy_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe AwardEmojis::DestroyService do
+RSpec.describe AwardEmojis::DestroyService do
let_it_be(:user) { create(:user) }
let_it_be(:awardable) { create(:note) }
let_it_be(:project) { awardable.project }
diff --git a/spec/services/award_emojis/toggle_service_spec.rb b/spec/services/award_emojis/toggle_service_spec.rb
index 069bdfcb99f..a7feeed50c6 100644
--- a/spec/services/award_emojis/toggle_service_spec.rb
+++ b/spec/services/award_emojis/toggle_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe AwardEmojis::ToggleService do
+RSpec.describe AwardEmojis::ToggleService do
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project, :public) }
let_it_be(:awardable) { create(:note, project: project) }
diff --git a/spec/services/base_container_service_spec.rb b/spec/services/base_container_service_spec.rb
index 47cfb387e25..1de79eec702 100644
--- a/spec/services/base_container_service_spec.rb
+++ b/spec/services/base_container_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe BaseContainerService do
+RSpec.describe BaseContainerService do
let(:project) { Project.new }
let(:user) { User.new }
diff --git a/spec/services/base_count_service_spec.rb b/spec/services/base_count_service_spec.rb
index 275bec9982d..18cab2e8e9a 100644
--- a/spec/services/base_count_service_spec.rb
+++ b/spec/services/base_count_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe BaseCountService, :use_clean_rails_memory_store_caching do
+RSpec.describe BaseCountService, :use_clean_rails_memory_store_caching do
let(:service) { described_class.new }
describe '#relation_for_count' do
diff --git a/spec/services/boards/create_service_spec.rb b/spec/services/boards/create_service_spec.rb
index 7d4fb04c6c0..f6a9f0903ce 100644
--- a/spec/services/boards/create_service_spec.rb
+++ b/spec/services/boards/create_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Boards::CreateService do
+RSpec.describe Boards::CreateService do
describe '#execute' do
context 'when board parent is a project' do
let(:parent) { create(:project) }
diff --git a/spec/services/boards/issues/create_service_spec.rb b/spec/services/boards/issues/create_service_spec.rb
index 3520630dd83..9a6b48c13bf 100644
--- a/spec/services/boards/issues/create_service_spec.rb
+++ b/spec/services/boards/issues/create_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Boards::Issues::CreateService do
+RSpec.describe Boards::Issues::CreateService do
describe '#execute' do
let(:project) { create(:project) }
let(:board) { create(:board, project: project) }
diff --git a/spec/services/boards/issues/list_service_spec.rb b/spec/services/boards/issues/list_service_spec.rb
index c46ab004af6..29b49db42f9 100644
--- a/spec/services/boards/issues/list_service_spec.rb
+++ b/spec/services/boards/issues/list_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Boards::Issues::ListService do
+RSpec.describe Boards::Issues::ListService do
describe '#execute' do
context 'when parent is a project' do
let(:user) { create(:user) }
diff --git a/spec/services/boards/issues/move_service_spec.rb b/spec/services/boards/issues/move_service_spec.rb
index b9ebbc30c1a..01a3ec72987 100644
--- a/spec/services/boards/issues/move_service_spec.rb
+++ b/spec/services/boards/issues/move_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Boards::Issues::MoveService do
+RSpec.describe Boards::Issues::MoveService do
describe '#execute' do
context 'when parent is a project' do
let(:user) { create(:user) }
diff --git a/spec/services/boards/list_service_spec.rb b/spec/services/boards/list_service_spec.rb
index 4eb023907fa..7c94332a78d 100644
--- a/spec/services/boards/list_service_spec.rb
+++ b/spec/services/boards/list_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Boards::ListService do
+RSpec.describe Boards::ListService do
describe '#execute' do
context 'when board parent is a project' do
let(:parent) { create(:project) }
diff --git a/spec/services/boards/lists/create_service_spec.rb b/spec/services/boards/lists/create_service_spec.rb
index 295ec2c8156..f3d4e62eeca 100644
--- a/spec/services/boards/lists/create_service_spec.rb
+++ b/spec/services/boards/lists/create_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Boards::Lists::CreateService do
+RSpec.describe Boards::Lists::CreateService do
describe '#execute' do
shared_examples 'creating board lists' do
let(:user) { create(:user) }
diff --git a/spec/services/boards/lists/destroy_service_spec.rb b/spec/services/boards/lists/destroy_service_spec.rb
index b936ef3837f..4c512b96065 100644
--- a/spec/services/boards/lists/destroy_service_spec.rb
+++ b/spec/services/boards/lists/destroy_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Boards::Lists::DestroyService do
+RSpec.describe Boards::Lists::DestroyService do
describe '#execute' do
context 'when board parent is a project' do
let(:project) { create(:project) }
diff --git a/spec/services/boards/lists/generate_service_spec.rb b/spec/services/boards/lists/generate_service_spec.rb
index 77b42392470..9597c8e0f54 100644
--- a/spec/services/boards/lists/generate_service_spec.rb
+++ b/spec/services/boards/lists/generate_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Boards::Lists::GenerateService do
+RSpec.describe Boards::Lists::GenerateService do
describe '#execute' do
let(:project) { create(:project) }
let(:board) { create(:board, project: project) }
diff --git a/spec/services/boards/lists/list_service_spec.rb b/spec/services/boards/lists/list_service_spec.rb
index 2535f339495..3d71c467e96 100644
--- a/spec/services/boards/lists/list_service_spec.rb
+++ b/spec/services/boards/lists/list_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Boards::Lists::ListService do
+RSpec.describe Boards::Lists::ListService do
let(:user) { create(:user) }
describe '#execute' do
diff --git a/spec/services/boards/lists/move_service_spec.rb b/spec/services/boards/lists/move_service_spec.rb
index f8fc70ef2d6..2861fc48b4d 100644
--- a/spec/services/boards/lists/move_service_spec.rb
+++ b/spec/services/boards/lists/move_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Boards::Lists::MoveService do
+RSpec.describe Boards::Lists::MoveService do
describe '#execute' do
context 'when board parent is a project' do
let(:project) { create(:project) }
diff --git a/spec/services/boards/lists/update_service_spec.rb b/spec/services/boards/lists/update_service_spec.rb
index 243e0fc50ad..cdc7784469a 100644
--- a/spec/services/boards/lists/update_service_spec.rb
+++ b/spec/services/boards/lists/update_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Boards::Lists::UpdateService do
+RSpec.describe Boards::Lists::UpdateService do
let(:user) { create(:user) }
let!(:list) { create(:list, board: board, position: 0) }
diff --git a/spec/services/boards/visits/create_service_spec.rb b/spec/services/boards/visits/create_service_spec.rb
index 203c287f396..a9a8754825b 100644
--- a/spec/services/boards/visits/create_service_spec.rb
+++ b/spec/services/boards/visits/create_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Boards::Visits::CreateService do
+RSpec.describe Boards::Visits::CreateService do
describe '#execute' do
let(:user) { create(:user) }
diff --git a/spec/services/branches/create_service_spec.rb b/spec/services/branches/create_service_spec.rb
index 072a86d17fc..b682a3f26ec 100644
--- a/spec/services/branches/create_service_spec.rb
+++ b/spec/services/branches/create_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Branches::CreateService do
+RSpec.describe Branches::CreateService do
subject(:service) { described_class.new(project, user) }
let_it_be(:project) { create(:project_empty_repo) }
diff --git a/spec/services/branches/delete_merged_service_spec.rb b/spec/services/branches/delete_merged_service_spec.rb
index 5c87f156ec7..2cf0f53c8c3 100644
--- a/spec/services/branches/delete_merged_service_spec.rb
+++ b/spec/services/branches/delete_merged_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Branches::DeleteMergedService do
+RSpec.describe Branches::DeleteMergedService do
include ProjectForksHelper
subject(:service) { described_class.new(project, project.owner) }
diff --git a/spec/services/branches/delete_service_spec.rb b/spec/services/branches/delete_service_spec.rb
index 2219416d94d..f1e7c9340b1 100644
--- a/spec/services/branches/delete_service_spec.rb
+++ b/spec/services/branches/delete_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Branches::DeleteService do
+RSpec.describe Branches::DeleteService do
let(:project) { create(:project, :repository) }
let(:repository) { project.repository }
let(:user) { create(:user) }
@@ -10,6 +10,10 @@ describe Branches::DeleteService do
subject(:service) { described_class.new(project, user) }
shared_examples 'a deleted branch' do |branch_name|
+ before do
+ allow(Ci::RefDeleteUnlockArtifactsWorker).to receive(:perform_async)
+ end
+
it 'removes the branch' do
expect(branch_exists?(branch_name)).to be true
@@ -18,6 +22,12 @@ describe Branches::DeleteService do
expect(result.status).to eq :success
expect(branch_exists?(branch_name)).to be false
end
+
+ it 'calls the RefDeleteUnlockArtifactsWorker' do
+ expect(Ci::RefDeleteUnlockArtifactsWorker).to receive(:perform_async).with(project.id, user.id, "refs/heads/#{branch_name}")
+
+ service.execute(branch_name)
+ end
end
describe '#execute' do
diff --git a/spec/services/branches/diverging_commit_counts_service_spec.rb b/spec/services/branches/diverging_commit_counts_service_spec.rb
index 370da773ab2..34a2b81c831 100644
--- a/spec/services/branches/diverging_commit_counts_service_spec.rb
+++ b/spec/services/branches/diverging_commit_counts_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Branches::DivergingCommitCountsService do
+RSpec.describe Branches::DivergingCommitCountsService do
let(:project) { create(:project, :repository) }
let(:repository) { project.repository }
diff --git a/spec/services/branches/validate_new_service_spec.rb b/spec/services/branches/validate_new_service_spec.rb
index 6d5078d3ccb..02127c8c10d 100644
--- a/spec/services/branches/validate_new_service_spec.rb
+++ b/spec/services/branches/validate_new_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Branches::ValidateNewService do
+RSpec.describe Branches::ValidateNewService do
let(:project) { create(:project, :repository) }
subject(:service) { described_class.new(project) }
diff --git a/spec/services/bulk_push_event_payload_service_spec.rb b/spec/services/bulk_push_event_payload_service_spec.rb
index 661c3540aa0..381c735c003 100644
--- a/spec/services/bulk_push_event_payload_service_spec.rb
+++ b/spec/services/bulk_push_event_payload_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe BulkPushEventPayloadService do
+RSpec.describe BulkPushEventPayloadService do
let(:event) { create(:push_event) }
let(:push_data) do
diff --git a/spec/services/chat_names/authorize_user_service_spec.rb b/spec/services/chat_names/authorize_user_service_spec.rb
index 7f32948daad..b0bb741564d 100644
--- a/spec/services/chat_names/authorize_user_service_spec.rb
+++ b/spec/services/chat_names/authorize_user_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ChatNames::AuthorizeUserService do
+RSpec.describe ChatNames::AuthorizeUserService do
describe '#execute' do
subject { described_class.new(service, params) }
diff --git a/spec/services/chat_names/find_user_service_spec.rb b/spec/services/chat_names/find_user_service_spec.rb
index 9d26f98cd56..a29b243ad2c 100644
--- a/spec/services/chat_names/find_user_service_spec.rb
+++ b/spec/services/chat_names/find_user_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ChatNames::FindUserService, :clean_gitlab_redis_shared_state do
+RSpec.describe ChatNames::FindUserService, :clean_gitlab_redis_shared_state do
describe '#execute' do
let(:service) { create(:service) }
diff --git a/spec/services/ci/archive_trace_service_spec.rb b/spec/services/ci/archive_trace_service_spec.rb
index ba94013b574..07ea314debc 100644
--- a/spec/services/ci/archive_trace_service_spec.rb
+++ b/spec/services/ci/archive_trace_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::ArchiveTraceService, '#execute' do
+RSpec.describe Ci::ArchiveTraceService, '#execute' do
subject { described_class.new.execute(job, worker_name: ArchiveTraceWorker.name) }
context 'when job is finished' do
diff --git a/spec/services/ci/build_report_result_service_spec.rb b/spec/services/ci/build_report_result_service_spec.rb
index dbdfc774314..3c1ef5301fc 100644
--- a/spec/services/ci/build_report_result_service_spec.rb
+++ b/spec/services/ci/build_report_result_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::BuildReportResultService do
+RSpec.describe Ci::BuildReportResultService do
describe "#execute" do
subject(:build_report_result) { described_class.new.execute(build) }
diff --git a/spec/services/ci/cancel_user_pipelines_service_spec.rb b/spec/services/ci/cancel_user_pipelines_service_spec.rb
index b18bf48a50a..12117051b64 100644
--- a/spec/services/ci/cancel_user_pipelines_service_spec.rb
+++ b/spec/services/ci/cancel_user_pipelines_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::CancelUserPipelinesService do
+RSpec.describe Ci::CancelUserPipelinesService do
describe '#execute' do
let(:user) { create(:user) }
diff --git a/spec/services/ci/compare_accessibility_reports_service_spec.rb b/spec/services/ci/compare_accessibility_reports_service_spec.rb
index aee1fd14bc5..6903a633eeb 100644
--- a/spec/services/ci/compare_accessibility_reports_service_spec.rb
+++ b/spec/services/ci/compare_accessibility_reports_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::CompareAccessibilityReportsService do
+RSpec.describe Ci::CompareAccessibilityReportsService do
let(:service) { described_class.new(project) }
let(:project) { create(:project, :repository) }
diff --git a/spec/services/ci/compare_test_reports_service_spec.rb b/spec/services/ci/compare_test_reports_service_spec.rb
index 46f4d2d42ff..7d31db73b6a 100644
--- a/spec/services/ci/compare_test_reports_service_spec.rb
+++ b/spec/services/ci/compare_test_reports_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::CompareTestReportsService do
+RSpec.describe Ci::CompareTestReportsService do
let(:service) { described_class.new(project) }
let(:project) { create(:project, :repository) }
diff --git a/spec/services/ci/create_cross_project_pipeline_service_spec.rb b/spec/services/ci/create_cross_project_pipeline_service_spec.rb
index 9e2497854bc..1aabdb85afd 100644
--- a/spec/services/ci/create_cross_project_pipeline_service_spec.rb
+++ b/spec/services/ci/create_cross_project_pipeline_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::CreateCrossProjectPipelineService, '#execute' do
+RSpec.describe Ci::CreateCrossProjectPipelineService, '#execute' do
let_it_be(:user) { create(:user) }
let(:upstream_project) { create(:project, :repository) }
let_it_be(:downstream_project) { create(:project, :repository) }
diff --git a/spec/services/ci/create_job_artifacts_service_spec.rb b/spec/services/ci/create_job_artifacts_service_spec.rb
index 4d49923a184..3f5cf079025 100644
--- a/spec/services/ci/create_job_artifacts_service_spec.rb
+++ b/spec/services/ci/create_job_artifacts_service_spec.rb
@@ -2,9 +2,9 @@
require 'spec_helper'
-describe Ci::CreateJobArtifactsService do
+RSpec.describe Ci::CreateJobArtifactsService do
let_it_be(:project) { create(:project) }
- let(:service) { described_class.new(project) }
+ let(:service) { described_class.new(job) }
let(:job) { create(:ci_build, project: project) }
let(:artifacts_sha256) { '0' * 64 }
let(:metadata_file) { nil }
@@ -17,7 +17,7 @@ describe Ci::CreateJobArtifactsService do
{
'artifact_type' => 'archive',
'artifact_format' => 'zip'
- }
+ }.with_indifferent_access
end
def file_to_upload(path, params = {})
@@ -28,27 +28,7 @@ describe Ci::CreateJobArtifactsService do
end
describe '#execute' do
- subject { service.execute(job, artifacts_file, params, metadata_file: metadata_file) }
-
- context 'locking' do
- let(:old_job) { create(:ci_build, pipeline: create(:ci_pipeline, project: job.project, ref: job.ref)) }
- let!(:latest_artifact) { create(:ci_job_artifact, job: old_job, locked: true) }
- let!(:other_artifact) { create(:ci_job_artifact, locked: true) }
-
- it 'locks the new artifact' do
- subject
-
- expect(Ci::JobArtifact.last).to have_attributes(locked: true)
- end
-
- it 'unlocks all other artifacts for the same ref' do
- expect { subject }.to change { latest_artifact.reload.locked }.from(true).to(false)
- end
-
- it 'does not unlock artifacts for other refs' do
- expect { subject }.not_to change { other_artifact.reload.locked }.from(true)
- end
- end
+ subject { service.execute(artifacts_file, params, metadata_file: metadata_file) }
context 'when artifacts file is uploaded' do
it 'saves artifact for the given type' do
@@ -150,7 +130,7 @@ describe Ci::CreateJobArtifactsService do
{
'artifact_type' => 'dotenv',
'artifact_format' => 'gzip'
- }
+ }.with_indifferent_access
end
it 'calls parse service' do
@@ -186,7 +166,7 @@ describe Ci::CreateJobArtifactsService do
{
'artifact_type' => 'cluster_applications',
'artifact_format' => 'gzip'
- }
+ }.with_indifferent_access
end
it 'calls cluster applications parse service' do
diff --git a/spec/services/ci/create_pipeline_service/cache_spec.rb b/spec/services/ci/create_pipeline_service/cache_spec.rb
index 4e0567132ff..614e46f1b1a 100644
--- a/spec/services/ci/create_pipeline_service/cache_spec.rb
+++ b/spec/services/ci/create_pipeline_service/cache_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::CreatePipelineService do
+RSpec.describe Ci::CreatePipelineService do
context 'cache' do
let(:user) { create(:admin) }
let(:ref) { 'refs/heads/master' }
diff --git a/spec/services/ci/create_pipeline_service/creation_errors_and_warnings_spec.rb b/spec/services/ci/create_pipeline_service/creation_errors_and_warnings_spec.rb
new file mode 100644
index 00000000000..16205529f1c
--- /dev/null
+++ b/spec/services/ci/create_pipeline_service/creation_errors_and_warnings_spec.rb
@@ -0,0 +1,114 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Ci::CreatePipelineService do
+ describe 'creation errors and warnings' do
+ let_it_be(:user) { create(:admin) }
+ let_it_be(:project) { create(:project, :repository, creator: user) }
+
+ let(:ref) { 'refs/heads/master' }
+ let(:source) { :push }
+ let(:service) { described_class.new(project, user, { ref: ref }) }
+ let(:pipeline) { service.execute(source) }
+
+ before do
+ stub_ci_pipeline_yaml_file(config)
+ stub_feature_flags(ci_raise_job_rules_without_workflow_rules_warning: true)
+ end
+
+ context 'when created successfully' do
+ context 'when warnings are raised' do
+ let(:config) do
+ <<~YAML
+ test:
+ script: rspec
+ rules:
+ - if: '$CI_COMMIT_BRANCH'
+ YAML
+ end
+
+ it 'contains only warnings' do
+ expect(pipeline.error_messages.map(&:content)).to be_empty
+
+ expect(pipeline.warning_messages.map(&:content)).to contain_exactly(
+ 'jobs:test uses `rules` without defining `workflow:rules`'
+ )
+ end
+
+ context 'when feature flag is disabled for the particular warning' do
+ before do
+ stub_feature_flags(ci_raise_job_rules_without_workflow_rules_warning: false)
+ end
+
+ it 'does not contain warnings' do
+ expect(pipeline.error_messages.map(&:content)).to be_empty
+
+ expect(pipeline.warning_messages.map(&:content)).to be_empty
+ end
+ end
+ end
+
+ context 'when no warnings are raised' do
+ let(:config) do
+ <<~YAML
+ test:
+ script: rspec
+ YAML
+ end
+
+ it 'contains no warnings' do
+ expect(pipeline.error_messages).to be_empty
+
+ expect(pipeline.warning_messages).to be_empty
+ end
+ end
+ end
+
+ context 'when failed to create the pipeline' do
+ context 'when warnings are raised' do
+ let(:config) do
+ <<~YAML
+ build:
+ stage: build
+ script: echo
+ needs: [test]
+ test:
+ stage: test
+ script: echo
+ rules:
+ - if: '$CI_COMMIT_BRANCH'
+ YAML
+ end
+
+ it 'contains both errors and warnings' do
+ error_message = 'build job: need test is not defined in prior stages'
+ warning_message = 'jobs:test uses `rules` without defining `workflow:rules`'
+
+ expect(pipeline.yaml_errors).to eq(error_message)
+ expect(pipeline.error_messages.map(&:content)).to contain_exactly(error_message)
+ expect(pipeline.errors.full_messages).to contain_exactly(error_message)
+
+ expect(pipeline.warning_messages.map(&:content)).to contain_exactly(warning_message)
+ end
+ end
+
+ context 'when no warnings are raised' do
+ let(:config) do
+ <<~YAML
+ invalid: yaml
+ YAML
+ end
+
+ it 'contains only errors' do
+ error_message = 'root config contains unknown keys: invalid'
+ expect(pipeline.yaml_errors).to eq(error_message)
+ expect(pipeline.error_messages.map(&:content)).to contain_exactly(error_message)
+ expect(pipeline.errors.full_messages).to contain_exactly(error_message)
+
+ expect(pipeline.warning_messages).to be_empty
+ end
+ end
+ end
+ end
+end
diff --git a/spec/services/ci/create_pipeline_service/custom_config_content_spec.rb b/spec/services/ci/create_pipeline_service/custom_config_content_spec.rb
index 5980260a08a..122870e0f3a 100644
--- a/spec/services/ci/create_pipeline_service/custom_config_content_spec.rb
+++ b/spec/services/ci/create_pipeline_service/custom_config_content_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe Ci::CreatePipelineService do
+RSpec.describe Ci::CreatePipelineService do
let_it_be(:project) { create(:project, :repository) }
let_it_be(:user) { create(:admin) }
let(:ref) { 'refs/heads/master' }
diff --git a/spec/services/ci/create_pipeline_service/needs_spec.rb b/spec/services/ci/create_pipeline_service/needs_spec.rb
index 17b9cf80cc1..915dc46d664 100644
--- a/spec/services/ci/create_pipeline_service/needs_spec.rb
+++ b/spec/services/ci/create_pipeline_service/needs_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::CreatePipelineService do
+RSpec.describe Ci::CreatePipelineService do
context 'needs' do
let_it_be(:user) { create(:admin) }
let_it_be(:project) { create(:project, :repository, creator: user) }
diff --git a/spec/services/ci/create_pipeline_service/parameter_content_spec.rb b/spec/services/ci/create_pipeline_service/parameter_content_spec.rb
new file mode 100644
index 00000000000..5157574ea04
--- /dev/null
+++ b/spec/services/ci/create_pipeline_service/parameter_content_spec.rb
@@ -0,0 +1,62 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Ci::CreatePipelineService do
+ let_it_be(:project) { create(:project, :repository) }
+ let_it_be(:user) { create(:admin) }
+ let(:service) { described_class.new(project, user, { ref: 'refs/heads/master' }) }
+ let(:content) do
+ <<~EOY
+ ---
+ stages:
+ - dast
+
+ variables:
+ DAST_VERSION: 1
+ SECURE_ANALYZERS_PREFIX: "registry.gitlab.com/gitlab-org/security-products/analyzers"
+
+ dast:
+ stage: dast
+ image:
+ name: "$SECURE_ANALYZERS_PREFIX/dast:$DAST_VERSION"
+ variables:
+ GIT_STRATEGY: none
+ script:
+ - /analyze
+ EOY
+ end
+
+ describe '#execute' do
+ context 'when source is a dangling build' do
+ subject { service.execute(:ondemand_dast_scan, content: content) }
+
+ context 'parameter config content' do
+ it 'creates a pipeline' do
+ expect(subject).to be_persisted
+ end
+
+ it 'creates builds with the correct names' do
+ expect(subject.builds.pluck(:name)).to match_array %w[dast]
+ end
+
+ it 'creates stages with the correct names' do
+ expect(subject.stages.pluck(:name)).to match_array %w[dast]
+ end
+
+ it 'sets the correct config source' do
+ expect(subject.config_source).to eq 'parameter_source'
+ end
+ end
+ end
+
+ context 'when source is not a dangling build' do
+ subject { service.execute(:web, content: content) }
+
+ it 'raises an exception' do
+ klass = Gitlab::Ci::Pipeline::Chain::Config::Content::Parameter::UnsupportedSourceError
+ expect { subject }.to raise_error(klass)
+ end
+ end
+ end
+end
diff --git a/spec/services/ci/create_pipeline_service/parent_child_pipeline_spec.rb b/spec/services/ci/create_pipeline_service/parent_child_pipeline_spec.rb
index a76e83f2d60..016a5dfd18b 100644
--- a/spec/services/ci/create_pipeline_service/parent_child_pipeline_spec.rb
+++ b/spec/services/ci/create_pipeline_service/parent_child_pipeline_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::CreatePipelineService, '#execute' do
+RSpec.describe Ci::CreatePipelineService, '#execute' do
let_it_be(:project) { create(:project, :repository) }
let_it_be(:user) { create(:user) }
let(:ref_name) { 'master' }
diff --git a/spec/services/ci/create_pipeline_service/pre_post_stages_spec.rb b/spec/services/ci/create_pipeline_service/pre_post_stages_spec.rb
index 2b11b98f58c..00a2dd74968 100644
--- a/spec/services/ci/create_pipeline_service/pre_post_stages_spec.rb
+++ b/spec/services/ci/create_pipeline_service/pre_post_stages_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe Ci::CreatePipelineService do
+RSpec.describe Ci::CreatePipelineService do
describe '.pre/.post stages' do
let_it_be(:user) { create(:admin) }
let_it_be(:project) { create(:project, :repository, creator: user) }
diff --git a/spec/services/ci/create_pipeline_service/rules_spec.rb b/spec/services/ci/create_pipeline_service/rules_spec.rb
index 713d230731b..1a1fa6e8f5d 100644
--- a/spec/services/ci/create_pipeline_service/rules_spec.rb
+++ b/spec/services/ci/create_pipeline_service/rules_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe Ci::CreatePipelineService do
+RSpec.describe Ci::CreatePipelineService do
let(:user) { create(:admin) }
let(:ref) { 'refs/heads/master' }
let(:source) { :push }
diff --git a/spec/services/ci/create_pipeline_service_spec.rb b/spec/services/ci/create_pipeline_service_spec.rb
index b9456d5fcd4..9dc518be996 100644
--- a/spec/services/ci/create_pipeline_service_spec.rb
+++ b/spec/services/ci/create_pipeline_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::CreatePipelineService do
+RSpec.describe Ci::CreatePipelineService do
include ProjectForksHelper
let_it_be(:project, reload: true) { create(:project, :repository) }
@@ -80,7 +80,7 @@ describe Ci::CreatePipelineService do
it 'records pipeline size in a prometheus histogram' do
histogram = spy('pipeline size histogram')
- allow(Gitlab::Ci::Pipeline::Chain::Metrics)
+ allow(Gitlab::Ci::Pipeline::Metrics)
.to receive(:new).and_return(histogram)
execute_service
@@ -194,6 +194,7 @@ describe Ci::CreatePipelineService do
expect(head_pipeline).to be_persisted
expect(head_pipeline.yaml_errors).to be_present
+ expect(head_pipeline.messages).to be_present
expect(merge_request.reload.head_pipeline).to eq head_pipeline
end
end
@@ -511,7 +512,7 @@ describe Ci::CreatePipelineService do
it 'pull it from Auto-DevOps' do
pipeline = execute_service
expect(pipeline).to be_auto_devops_source
- expect(pipeline.builds.map(&:name)).to match_array(%w[test code_quality build])
+ expect(pipeline.builds.map(&:name)).to match_array(%w[build code_quality eslint-sast test])
end
end
@@ -1683,6 +1684,12 @@ describe Ci::CreatePipelineService do
expect(pipeline).to be_persisted
expect(pipeline.builds.pluck(:name)).to contain_exactly("build_a", "test_a")
end
+
+ it 'bulk inserts all needs' do
+ expect(Ci::BuildNeed).to receive(:bulk_insert!).and_call_original
+
+ expect(pipeline).to be_persisted
+ end
end
context 'when pipeline on feature is created' do
@@ -1695,6 +1702,7 @@ describe Ci::CreatePipelineService do
expect(pipeline).to be_persisted
expect(pipeline.builds).to be_empty
expect(pipeline.yaml_errors).to eq("test_a: needs 'build_a'")
+ expect(pipeline.messages.pluck(:content)).to contain_exactly("test_a: needs 'build_a'")
expect(pipeline.errors[:base]).to contain_exactly("test_a: needs 'build_a'")
end
end
@@ -1706,6 +1714,7 @@ describe Ci::CreatePipelineService do
expect(pipeline).not_to be_persisted
expect(pipeline.builds).to be_empty
expect(pipeline.yaml_errors).to be_nil
+ expect(pipeline.messages).not_to be_empty
expect(pipeline.errors[:base]).to contain_exactly("test_a: needs 'build_a'")
end
end
@@ -2205,6 +2214,83 @@ describe Ci::CreatePipelineService do
expect(find_job('job-7').when).to eq('on_failure')
end
end
+
+ context 'with deploy freeze period `if:` clause' do
+ # '0 23 * * 5' == "At 23:00 on Friday."", '0 7 * * 1' == "At 07:00 on Monday.""
+ let!(:freeze_period) { create(:ci_freeze_period, project: project, freeze_start: '0 23 * * 5', freeze_end: '0 7 * * 1') }
+
+ context 'with 2 jobs' do
+ let(:config) do
+ <<-EOY
+ stages:
+ - test
+ - deploy
+
+ test-job:
+ script:
+ - echo 'running TEST stage'
+
+ deploy-job:
+ stage: deploy
+ script:
+ - echo 'running DEPLOY stage'
+ rules:
+ - if: $CI_DEPLOY_FREEZE == null
+ EOY
+ end
+
+ context 'when outside freeze period' do
+ it 'creates two jobs' do
+ Timecop.freeze(2020, 4, 10, 22, 59) do
+ expect(pipeline).to be_persisted
+ expect(build_names).to contain_exactly('test-job', 'deploy-job')
+ end
+ end
+ end
+
+ context 'when inside freeze period' do
+ it 'creates one job' do
+ Timecop.freeze(2020, 4, 10, 23, 1) do
+ expect(pipeline).to be_persisted
+ expect(build_names).to contain_exactly('test-job')
+ end
+ end
+ end
+ end
+
+ context 'with 1 job' do
+ let(:config) do
+ <<-EOY
+ stages:
+ - deploy
+
+ deploy-job:
+ stage: deploy
+ script:
+ - echo 'running DEPLOY stage'
+ rules:
+ - if: $CI_DEPLOY_FREEZE == null
+ EOY
+ end
+
+ context 'when outside freeze period' do
+ it 'creates two jobs' do
+ Timecop.freeze(2020, 4, 10, 22, 59) do
+ expect(pipeline).to be_persisted
+ expect(build_names).to contain_exactly('deploy-job')
+ end
+ end
+ end
+
+ context 'when inside freeze period' do
+ it 'does not create the pipeline' do
+ Timecop.freeze(2020, 4, 10, 23, 1) do
+ expect(pipeline).not_to be_persisted
+ end
+ end
+ end
+ end
+ end
end
end
diff --git a/spec/services/ci/create_web_ide_terminal_service_spec.rb b/spec/services/ci/create_web_ide_terminal_service_spec.rb
index 2cc67c7cd1d..c1c94e30018 100644
--- a/spec/services/ci/create_web_ide_terminal_service_spec.rb
+++ b/spec/services/ci/create_web_ide_terminal_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::CreateWebIdeTerminalService do
+RSpec.describe Ci::CreateWebIdeTerminalService do
let_it_be(:project) { create(:project, :repository) }
let_it_be(:user) { create(:user) }
let(:ref) { 'master' }
diff --git a/spec/services/ci/daily_build_group_report_result_service_spec.rb b/spec/services/ci/daily_build_group_report_result_service_spec.rb
index f0b72b8fd86..7d181a5c2ba 100644
--- a/spec/services/ci/daily_build_group_report_result_service_spec.rb
+++ b/spec/services/ci/daily_build_group_report_result_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::DailyBuildGroupReportResultService, '#execute' do
+RSpec.describe Ci::DailyBuildGroupReportResultService, '#execute' do
let!(:pipeline) { create(:ci_pipeline, created_at: '2020-02-06 00:01:10') }
let!(:rspec_job) { create(:ci_build, pipeline: pipeline, name: '3/3 rspec', coverage: 80) }
let!(:karma_job) { create(:ci_build, pipeline: pipeline, name: '2/2 karma', coverage: 90) }
diff --git a/spec/services/ci/destroy_expired_job_artifacts_service_spec.rb b/spec/services/ci/destroy_expired_job_artifacts_service_spec.rb
index 4b9f12d8fdf..79443f16276 100644
--- a/spec/services/ci/destroy_expired_job_artifacts_service_spec.rb
+++ b/spec/services/ci/destroy_expired_job_artifacts_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::DestroyExpiredJobArtifactsService, :clean_gitlab_redis_shared_state do
+RSpec.describe Ci::DestroyExpiredJobArtifactsService, :clean_gitlab_redis_shared_state do
include ExclusiveLeaseHelpers
describe '.execute' do
@@ -14,7 +14,7 @@ describe Ci::DestroyExpiredJobArtifactsService, :clean_gitlab_redis_shared_state
context 'when artifact is expired' do
context 'when artifact is not locked' do
before do
- artifact.update!(locked: false)
+ artifact.job.pipeline.unlocked!
end
it 'destroys job artifact' do
@@ -24,7 +24,7 @@ describe Ci::DestroyExpiredJobArtifactsService, :clean_gitlab_redis_shared_state
context 'when artifact is locked' do
before do
- artifact.update!(locked: true)
+ artifact.job.pipeline.artifacts_locked!
end
it 'does not destroy job artifact' do
diff --git a/spec/services/ci/destroy_pipeline_service_spec.rb b/spec/services/ci/destroy_pipeline_service_spec.rb
index bff2b3179fb..23cbe683d2f 100644
--- a/spec/services/ci/destroy_pipeline_service_spec.rb
+++ b/spec/services/ci/destroy_pipeline_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ::Ci::DestroyPipelineService do
+RSpec.describe ::Ci::DestroyPipelineService do
let(:project) { create(:project, :repository) }
let!(:pipeline) { create(:ci_pipeline, :success, project: project, sha: project.commit.id) }
diff --git a/spec/services/ci/ensure_stage_service_spec.rb b/spec/services/ci/ensure_stage_service_spec.rb
index 8a270d77bae..3ede214cdd4 100644
--- a/spec/services/ci/ensure_stage_service_spec.rb
+++ b/spec/services/ci/ensure_stage_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::EnsureStageService, '#execute' do
+RSpec.describe Ci::EnsureStageService, '#execute' do
let_it_be(:project) { create(:project) }
let_it_be(:user) { create(:user) }
diff --git a/spec/services/ci/expire_pipeline_cache_service_spec.rb b/spec/services/ci/expire_pipeline_cache_service_spec.rb
index 2962e9dd31e..b5d664947de 100644
--- a/spec/services/ci/expire_pipeline_cache_service_spec.rb
+++ b/spec/services/ci/expire_pipeline_cache_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::ExpirePipelineCacheService do
+RSpec.describe Ci::ExpirePipelineCacheService do
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project) }
let_it_be(:pipeline) { create(:ci_pipeline, project: project) }
diff --git a/spec/services/ci/external_pull_requests/create_pipeline_service_spec.rb b/spec/services/ci/external_pull_requests/create_pipeline_service_spec.rb
index 5048f2b71b3..e2bdfae27f0 100644
--- a/spec/services/ci/external_pull_requests/create_pipeline_service_spec.rb
+++ b/spec/services/ci/external_pull_requests/create_pipeline_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::ExternalPullRequests::CreatePipelineService do
+RSpec.describe Ci::ExternalPullRequests::CreatePipelineService do
describe '#execute' do
let_it_be(:project) { create(:project, :auto_devops, :repository) }
let_it_be(:user) { create(:user) }
diff --git a/spec/services/ci/extract_sections_from_build_trace_service_spec.rb b/spec/services/ci/extract_sections_from_build_trace_service_spec.rb
index 03c67c611fe..c6ffcdcc6a8 100644
--- a/spec/services/ci/extract_sections_from_build_trace_service_spec.rb
+++ b/spec/services/ci/extract_sections_from_build_trace_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::ExtractSectionsFromBuildTraceService, '#execute' do
+RSpec.describe Ci::ExtractSectionsFromBuildTraceService, '#execute' do
let(:user) { create(:user) }
let(:project) { create(:project) }
let(:build) { create(:ci_build, project: project) }
diff --git a/spec/services/ci/find_exposed_artifacts_service_spec.rb b/spec/services/ci/find_exposed_artifacts_service_spec.rb
index 16e23253c34..287f5c4b929 100644
--- a/spec/services/ci/find_exposed_artifacts_service_spec.rb
+++ b/spec/services/ci/find_exposed_artifacts_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::FindExposedArtifactsService do
+RSpec.describe Ci::FindExposedArtifactsService do
include Gitlab::Routing
let(:metadata) do
diff --git a/spec/services/ci/generate_coverage_reports_service_spec.rb b/spec/services/ci/generate_coverage_reports_service_spec.rb
index b64b682a00b..a3ed2eec713 100644
--- a/spec/services/ci/generate_coverage_reports_service_spec.rb
+++ b/spec/services/ci/generate_coverage_reports_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::GenerateCoverageReportsService do
+RSpec.describe Ci::GenerateCoverageReportsService do
let(:service) { described_class.new(project) }
let(:project) { create(:project, :repository) }
diff --git a/spec/services/ci/generate_terraform_reports_service_spec.rb b/spec/services/ci/generate_terraform_reports_service_spec.rb
index 008ecf17b3e..25bf96035b2 100644
--- a/spec/services/ci/generate_terraform_reports_service_spec.rb
+++ b/spec/services/ci/generate_terraform_reports_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::GenerateTerraformReportsService do
+RSpec.describe Ci::GenerateTerraformReportsService do
let_it_be(:project) { create(:project, :repository) }
describe '#execute' do
@@ -33,19 +33,36 @@ describe Ci::GenerateTerraformReportsService do
end
context 'when head pipeline has corrupted terraform reports' do
- it 'returns status and error message' do
+ it 'returns a report with error messages' do
build = create(:ci_build, pipeline: merge_request.head_pipeline, project: project)
create(:ci_job_artifact, :terraform_with_corrupted_data, job: build, project: project)
result = subject.execute(nil, merge_request.head_pipeline)
expect(result).to match(
- status: :error,
- status_reason: 'An error occurred while fetching terraform reports.',
+ status: :parsed,
+ data: match(
+ a_hash_including(build.id.to_s => hash_including(
+ 'tf_report_error' => :invalid_json_format
+ ))
+ ),
key: an_instance_of(Array)
)
end
end
+
+ context 'when head pipeline is corrupted' do
+ it 'returns status and error message' do
+ result = subject.execute(nil, nil)
+
+ expect(result).to match(
+ a_hash_including(
+ status: :error,
+ status_reason: 'An error occurred while fetching terraform reports.'
+ )
+ )
+ end
+ end
end
describe '#latest?' do
diff --git a/spec/services/ci/parse_dotenv_artifact_service_spec.rb b/spec/services/ci/parse_dotenv_artifact_service_spec.rb
index fc4131d262b..a5f01187a83 100644
--- a/spec/services/ci/parse_dotenv_artifact_service_spec.rb
+++ b/spec/services/ci/parse_dotenv_artifact_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::ParseDotenvArtifactService do
+RSpec.describe Ci::ParseDotenvArtifactService do
let_it_be(:project) { create(:project) }
let_it_be(:pipeline) { create(:ci_pipeline, project: project) }
let(:build) { create(:ci_build, pipeline: pipeline, project: project) }
diff --git a/spec/services/ci/pipeline_bridge_status_service_spec.rb b/spec/services/ci/pipeline_bridge_status_service_spec.rb
index 7e79d222349..584b23bb3aa 100644
--- a/spec/services/ci/pipeline_bridge_status_service_spec.rb
+++ b/spec/services/ci/pipeline_bridge_status_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::PipelineBridgeStatusService do
+RSpec.describe Ci::PipelineBridgeStatusService do
let(:user) { build(:user) }
let_it_be(:project) { create(:project) }
let(:pipeline) { build(:ci_pipeline, project: project) }
diff --git a/spec/services/ci/pipeline_processing/atomic_processing_service/status_collection_spec.rb b/spec/services/ci/pipeline_processing/atomic_processing_service/status_collection_spec.rb
index de3c7713ac8..7868629d34d 100644
--- a/spec/services/ci/pipeline_processing/atomic_processing_service/status_collection_spec.rb
+++ b/spec/services/ci/pipeline_processing/atomic_processing_service/status_collection_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::PipelineProcessing::AtomicProcessingService::StatusCollection do
+RSpec.describe Ci::PipelineProcessing::AtomicProcessingService::StatusCollection do
using RSpec::Parameterized::TableSyntax
let_it_be(:pipeline) { create(:ci_pipeline) }
diff --git a/spec/services/ci/pipeline_processing/atomic_processing_service_spec.rb b/spec/services/ci/pipeline_processing/atomic_processing_service_spec.rb
index 3b66ecff196..a10a333b462 100644
--- a/spec/services/ci/pipeline_processing/atomic_processing_service_spec.rb
+++ b/spec/services/ci/pipeline_processing/atomic_processing_service_spec.rb
@@ -4,7 +4,7 @@ require 'spec_helper'
require_relative 'shared_processing_service.rb'
require_relative 'shared_processing_service_tests_with_yaml.rb'
-describe Ci::PipelineProcessing::AtomicProcessingService do
+RSpec.describe Ci::PipelineProcessing::AtomicProcessingService do
before do
stub_feature_flags(ci_atomic_processing: true)
diff --git a/spec/services/ci/pipeline_processing/legacy_processing_service_spec.rb b/spec/services/ci/pipeline_processing/legacy_processing_service_spec.rb
index fd491bf461b..569a6d62dc1 100644
--- a/spec/services/ci/pipeline_processing/legacy_processing_service_spec.rb
+++ b/spec/services/ci/pipeline_processing/legacy_processing_service_spec.rb
@@ -4,7 +4,7 @@ require 'spec_helper'
require_relative 'shared_processing_service.rb'
require_relative 'shared_processing_service_tests_with_yaml.rb'
-describe Ci::PipelineProcessing::LegacyProcessingService do
+RSpec.describe Ci::PipelineProcessing::LegacyProcessingService do
before do
stub_feature_flags(ci_atomic_processing: false)
end
diff --git a/spec/services/ci/pipeline_processing/shared_processing_service.rb b/spec/services/ci/pipeline_processing/shared_processing_service.rb
index 29fa43001ae..224066885b6 100644
--- a/spec/services/ci/pipeline_processing/shared_processing_service.rb
+++ b/spec/services/ci/pipeline_processing/shared_processing_service.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-shared_examples 'Pipeline Processing Service' do
+RSpec.shared_examples 'Pipeline Processing Service' do
let(:user) { create(:user) }
let(:project) { create(:project) }
diff --git a/spec/services/ci/pipeline_processing/shared_processing_service_tests_with_yaml.rb b/spec/services/ci/pipeline_processing/shared_processing_service_tests_with_yaml.rb
index 93f83f0ea3b..17d254ba48e 100644
--- a/spec/services/ci/pipeline_processing/shared_processing_service_tests_with_yaml.rb
+++ b/spec/services/ci/pipeline_processing/shared_processing_service_tests_with_yaml.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-shared_context 'Pipeline Processing Service Tests With Yaml' do
+RSpec.shared_context 'Pipeline Processing Service Tests With Yaml' do
where(:test_file_path) do
Dir.glob(Rails.root.join('spec/services/ci/pipeline_processing/test_cases/*.yml'))
end
diff --git a/spec/services/ci/pipeline_schedule_service_spec.rb b/spec/services/ci/pipeline_schedule_service_spec.rb
index 867ed0acc0d..65bbd13c5e7 100644
--- a/spec/services/ci/pipeline_schedule_service_spec.rb
+++ b/spec/services/ci/pipeline_schedule_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::PipelineScheduleService do
+RSpec.describe Ci::PipelineScheduleService do
let(:project) { create(:project) }
let(:user) { create(:user) }
let(:service) { described_class.new(project, user) }
diff --git a/spec/services/ci/pipeline_trigger_service_spec.rb b/spec/services/ci/pipeline_trigger_service_spec.rb
index 44ce1ff699b..18fab9623ec 100644
--- a/spec/services/ci/pipeline_trigger_service_spec.rb
+++ b/spec/services/ci/pipeline_trigger_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::PipelineTriggerService do
+RSpec.describe Ci::PipelineTriggerService do
let(:project) { create(:project, :repository) }
before do
diff --git a/spec/services/ci/play_build_service_spec.rb b/spec/services/ci/play_build_service_spec.rb
index cf39f3da4fe..c9ecbad3167 100644
--- a/spec/services/ci/play_build_service_spec.rb
+++ b/spec/services/ci/play_build_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::PlayBuildService, '#execute' do
+RSpec.describe Ci::PlayBuildService, '#execute' do
let(:user) { create(:user, developer_projects: [project]) }
let(:project) { create(:project) }
let(:pipeline) { create(:ci_pipeline, project: project) }
diff --git a/spec/services/ci/play_manual_stage_service_spec.rb b/spec/services/ci/play_manual_stage_service_spec.rb
index e2946111a13..e30ec8bfda5 100644
--- a/spec/services/ci/play_manual_stage_service_spec.rb
+++ b/spec/services/ci/play_manual_stage_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::PlayManualStageService, '#execute' do
+RSpec.describe Ci::PlayManualStageService, '#execute' do
let(:current_user) { create(:user) }
let(:pipeline) { create(:ci_pipeline, user: current_user) }
let(:project) { pipeline.project }
diff --git a/spec/services/ci/prepare_build_service_spec.rb b/spec/services/ci/prepare_build_service_spec.rb
index 02928b58ff8..f75cb322fe9 100644
--- a/spec/services/ci/prepare_build_service_spec.rb
+++ b/spec/services/ci/prepare_build_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::PrepareBuildService do
+RSpec.describe Ci::PrepareBuildService do
describe '#execute' do
let(:build) { create(:ci_build, :preparing) }
diff --git a/spec/services/ci/process_build_service_spec.rb b/spec/services/ci/process_build_service_spec.rb
index abc5c18a523..a6e8732f5ff 100644
--- a/spec/services/ci/process_build_service_spec.rb
+++ b/spec/services/ci/process_build_service_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe Ci::ProcessBuildService, '#execute' do
+RSpec.describe Ci::ProcessBuildService, '#execute' do
let(:user) { create(:user) }
let(:project) { create(:project) }
diff --git a/spec/services/ci/process_pipeline_service_spec.rb b/spec/services/ci/process_pipeline_service_spec.rb
index 40ae1c4029b..a7889f0644d 100644
--- a/spec/services/ci/process_pipeline_service_spec.rb
+++ b/spec/services/ci/process_pipeline_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::ProcessPipelineService do
+RSpec.describe Ci::ProcessPipelineService do
let(:user) { create(:user) }
let(:project) { create(:project) }
@@ -10,38 +10,52 @@ describe Ci::ProcessPipelineService do
create(:ci_empty_pipeline, ref: 'master', project: project)
end
+ subject { described_class.new(pipeline) }
+
before do
stub_ci_pipeline_to_return_yaml_file
-
stub_not_protect_default_branch
project.add_developer(user)
end
- context 'updates a list of retried builds' do
- subject { described_class.retried.order(:id) }
+ describe 'processing events counter' do
+ let(:metrics) { double('pipeline metrics') }
+ let(:counter) { double('events counter') }
+
+ before do
+ allow(subject)
+ .to receive(:metrics).and_return(metrics)
+ allow(metrics)
+ .to receive(:pipeline_processing_events_counter)
+ .and_return(counter)
+ end
+
+ it 'increments processing events counter' do
+ expect(counter).to receive(:increment)
+
+ subject.execute
+ end
+ end
+ describe 'updating a list of retried builds' do
let!(:build_retried) { create_build('build') }
let!(:build) { create_build('build') }
let!(:test) { create_build('test') }
it 'returns unique statuses' do
- process_pipeline
+ subject.execute
expect(all_builds.latest).to contain_exactly(build, test)
expect(all_builds.retried).to contain_exactly(build_retried)
end
- end
-
- def process_pipeline
- described_class.new(pipeline).execute
- end
- def create_build(name, **opts)
- create(:ci_build, :created, pipeline: pipeline, name: name, **opts)
- end
+ def create_build(name, **opts)
+ create(:ci_build, :created, pipeline: pipeline, name: name, **opts)
+ end
- def all_builds
- pipeline.builds.order(:stage_idx, :id)
+ def all_builds
+ pipeline.builds.order(:stage_idx, :id)
+ end
end
end
diff --git a/spec/services/ci/register_job_service_spec.rb b/spec/services/ci/register_job_service_spec.rb
index c0f854df9b7..921f5ba4c7e 100644
--- a/spec/services/ci/register_job_service_spec.rb
+++ b/spec/services/ci/register_job_service_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
module Ci
- describe RegisterJobService do
+ RSpec.describe RegisterJobService do
let_it_be(:group) { create(:group) }
let_it_be(:project, reload: true) { create(:project, group: group, shared_runners_enabled: false, group_runners_enabled: false) }
let_it_be(:pipeline) { create(:ci_pipeline, project: project) }
@@ -109,12 +109,14 @@ module Ci
end
context 'shared runner' do
- let(:build) { execute(shared_runner) }
+ let(:response) { described_class.new(shared_runner).execute }
+ let(:build) { response.build }
it { expect(build).to be_kind_of(Build) }
it { expect(build).to be_valid }
it { expect(build).to be_running }
it { expect(build.runner).to eq(shared_runner) }
+ it { expect(Gitlab::Json.parse(response.build_json)['id']).to eq(build.id) }
end
context 'specific runner' do
@@ -356,13 +358,8 @@ module Ci
end
context 'runner feature set is verified' do
- let!(:pending_job) { create(:ci_build, :pending, pipeline: pipeline) }
-
- before do
- expect_any_instance_of(Ci::Build).to receive(:runner_required_feature_names) do
- [:runner_required_feature]
- end
- end
+ let(:options) { { artifacts: { reports: { junit: "junit.xml" } } } }
+ let!(:pending_job) { create(:ci_build, :pending, pipeline: pipeline, options: options) }
subject { execute(specific_runner, params) }
@@ -378,7 +375,7 @@ module Ci
context 'when feature is supported by runner' do
let(:params) do
- { info: { features: { runner_required_feature: true } } }
+ { info: { features: { upload_multiple_artifacts: true } } }
end
it 'does pick job' do
diff --git a/spec/services/ci/resource_groups/assign_resource_from_resource_group_service_spec.rb b/spec/services/ci/resource_groups/assign_resource_from_resource_group_service_spec.rb
index 50d312647ae..6c69a7f3b11 100644
--- a/spec/services/ci/resource_groups/assign_resource_from_resource_group_service_spec.rb
+++ b/spec/services/ci/resource_groups/assign_resource_from_resource_group_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::ResourceGroups::AssignResourceFromResourceGroupService do
+RSpec.describe Ci::ResourceGroups::AssignResourceFromResourceGroupService do
let_it_be(:project) { create(:project) }
let_it_be(:user) { create(:user) }
let(:service) { described_class.new(project, user) }
diff --git a/spec/services/ci/retry_build_service_spec.rb b/spec/services/ci/retry_build_service_spec.rb
index 90c53d4a346..5a245415b32 100644
--- a/spec/services/ci/retry_build_service_spec.rb
+++ b/spec/services/ci/retry_build_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::RetryBuildService do
+RSpec.describe Ci::RetryBuildService do
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project, :repository) }
let_it_be(:pipeline) do
@@ -33,13 +33,13 @@ describe Ci::RetryBuildService do
job_artifacts_sast job_artifacts_secret_detection job_artifacts_dependency_scanning
job_artifacts_container_scanning job_artifacts_dast
job_artifacts_license_management job_artifacts_license_scanning
- job_artifacts_performance job_artifacts_lsif
- job_artifacts_terraform job_artifacts_cluster_applications
+ job_artifacts_performance job_artifacts_browser_performance job_artifacts_load_performance
+ job_artifacts_lsif job_artifacts_terraform job_artifacts_cluster_applications
job_artifacts_codequality job_artifacts_metrics scheduled_at
job_variables waiting_for_resource_at job_artifacts_metrics_referee
job_artifacts_network_referee job_artifacts_dotenv
job_artifacts_cobertura needs job_artifacts_accessibility
- job_artifacts_requirements].freeze
+ job_artifacts_requirements job_artifacts_coverage_fuzzing].freeze
ignore_accessors =
%i[type lock_version target_url base_tags trace_sections
@@ -279,25 +279,16 @@ describe Ci::RetryBuildService do
end
end
- context 'when scheduling_type of build is nil' do
+ context 'when build has needs' do
before do
- build.update_columns(scheduling_type: nil)
+ create(:ci_build_need, build: build, name: 'build1')
+ create(:ci_build_need, build: build, name: 'build2')
end
- context 'when build has not needs' do
- it 'sets scheduling_type as :stage' do
- expect(new_build.scheduling_type).to eq('stage')
- end
- end
+ it 'bulk inserts all needs' do
+ expect(Ci::BuildNeed).to receive(:bulk_insert!).and_call_original
- context 'when build has needs' do
- before do
- create(:ci_build_need, build: build)
- end
-
- it 'sets scheduling_type as :dag' do
- expect(new_build.scheduling_type).to eq('dag')
- end
+ new_build
end
end
end
diff --git a/spec/services/ci/retry_pipeline_service_spec.rb b/spec/services/ci/retry_pipeline_service_spec.rb
index 8e85e68d4fc..fa46d6c4d1d 100644
--- a/spec/services/ci/retry_pipeline_service_spec.rb
+++ b/spec/services/ci/retry_pipeline_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::RetryPipelineService, '#execute' do
+RSpec.describe Ci::RetryPipelineService, '#execute' do
include ProjectForksHelper
let(:user) { create(:user) }
diff --git a/spec/services/ci/run_scheduled_build_service_spec.rb b/spec/services/ci/run_scheduled_build_service_spec.rb
index 43d110cbc8f..27d25e88944 100644
--- a/spec/services/ci/run_scheduled_build_service_spec.rb
+++ b/spec/services/ci/run_scheduled_build_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::RunScheduledBuildService do
+RSpec.describe Ci::RunScheduledBuildService do
let(:user) { create(:user) }
let(:project) { create(:project) }
let(:pipeline) { create(:ci_pipeline, project: project) }
diff --git a/spec/services/ci/stop_environments_service_spec.rb b/spec/services/ci/stop_environments_service_spec.rb
index ebbe6c37b87..5a0b7f23556 100644
--- a/spec/services/ci/stop_environments_service_spec.rb
+++ b/spec/services/ci/stop_environments_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::StopEnvironmentsService do
+RSpec.describe Ci::StopEnvironmentsService do
include CreateEnvironmentsHelpers
let(:project) { create(:project, :private, :repository) }
diff --git a/spec/services/ci/unlock_artifacts_service_spec.rb b/spec/services/ci/unlock_artifacts_service_spec.rb
new file mode 100644
index 00000000000..8d289a867ba
--- /dev/null
+++ b/spec/services/ci/unlock_artifacts_service_spec.rb
@@ -0,0 +1,97 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Ci::UnlockArtifactsService do
+ describe '#execute' do
+ subject(:execute) { described_class.new(pipeline.project, pipeline.user).execute(ci_ref, before_pipeline) }
+
+ before do
+ stub_const("#{described_class}::BATCH_SIZE", 1)
+ end
+
+ [true, false].each do |tag|
+ context "when tag is #{tag}" do
+ let(:ref) { 'master' }
+ let(:ref_path) { tag ? "#{::Gitlab::Git::TAG_REF_PREFIX}#{ref}" : "#{::Gitlab::Git::BRANCH_REF_PREFIX}#{ref}" }
+ let(:ci_ref) { create(:ci_ref, ref_path: ref_path) }
+
+ let!(:old_unlocked_pipeline) { create(:ci_pipeline, ref: ref, tag: tag, project: ci_ref.project, locked: :unlocked) }
+ let!(:older_pipeline) { create(:ci_pipeline, ref: ref, tag: tag, project: ci_ref.project, locked: :artifacts_locked) }
+ let!(:older_ambiguous_pipeline) { create(:ci_pipeline, ref: ref, tag: !tag, project: ci_ref.project, locked: :artifacts_locked) }
+ let!(:pipeline) { create(:ci_pipeline, ref: ref, tag: tag, project: ci_ref.project, locked: :artifacts_locked) }
+ let!(:child_pipeline) { create(:ci_pipeline, ref: ref, tag: tag, project: ci_ref.project, locked: :artifacts_locked) }
+ let!(:newer_pipeline) { create(:ci_pipeline, ref: ref, tag: tag, project: ci_ref.project, locked: :artifacts_locked) }
+ let!(:other_ref_pipeline) { create(:ci_pipeline, ref: 'other_ref', tag: tag, project: ci_ref.project, locked: :artifacts_locked) }
+
+ before do
+ create(:ci_sources_pipeline,
+ source_job: create(:ci_build, pipeline: pipeline),
+ source_project: ci_ref.project,
+ pipeline: child_pipeline,
+ project: ci_ref.project)
+ end
+
+ context 'when running on a ref before a pipeline' do
+ let(:before_pipeline) { pipeline }
+
+ it 'unlocks artifacts from older pipelines' do
+ expect { execute }.to change { older_pipeline.reload.locked }.from('artifacts_locked').to('unlocked')
+ end
+
+ it 'does not unlock artifacts for tag or branch with same name as ref' do
+ expect { execute }.not_to change { older_ambiguous_pipeline.reload.locked }.from('artifacts_locked')
+ end
+
+ it 'does not unlock artifacts from newer pipelines' do
+ expect { execute }.not_to change { newer_pipeline.reload.locked }.from('artifacts_locked')
+ end
+
+ it 'does not lock artifacts from old unlocked pipelines' do
+ expect { execute }.not_to change { old_unlocked_pipeline.reload.locked }.from('unlocked')
+ end
+
+ it 'does not unlock artifacts from the same pipeline' do
+ expect { execute }.not_to change { pipeline.reload.locked }.from('artifacts_locked')
+ end
+
+ it 'does not unlock artifacts for other refs' do
+ expect { execute }.not_to change { other_ref_pipeline.reload.locked }.from('artifacts_locked')
+ end
+
+ it 'does not unlock artifacts for child pipeline' do
+ expect { execute }.not_to change { child_pipeline.reload.locked }.from('artifacts_locked')
+ end
+ end
+
+ context 'when running on just the ref' do
+ let(:before_pipeline) { nil }
+
+ it 'unlocks artifacts from older pipelines' do
+ expect { execute }.to change { older_pipeline.reload.locked }.from('artifacts_locked').to('unlocked')
+ end
+
+ it 'unlocks artifacts from newer pipelines' do
+ expect { execute }.to change { newer_pipeline.reload.locked }.from('artifacts_locked').to('unlocked')
+ end
+
+ it 'unlocks artifacts from the same pipeline' do
+ expect { execute }.to change { pipeline.reload.locked }.from('artifacts_locked').to('unlocked')
+ end
+
+ it 'does not unlock artifacts for tag or branch with same name as ref' do
+ expect { execute }.not_to change { older_ambiguous_pipeline.reload.locked }.from('artifacts_locked')
+ end
+
+ it 'does not lock artifacts from old unlocked pipelines' do
+ expect { execute }.not_to change { old_unlocked_pipeline.reload.locked }.from('unlocked')
+ end
+
+ it 'does not unlock artifacts for other refs' do
+ expect { execute }.not_to change { other_ref_pipeline.reload.locked }.from('artifacts_locked')
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/spec/services/ci/update_build_queue_service_spec.rb b/spec/services/ci/update_build_queue_service_spec.rb
index 522dd1ba1c2..0f4c0fa5ecb 100644
--- a/spec/services/ci/update_build_queue_service_spec.rb
+++ b/spec/services/ci/update_build_queue_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::UpdateBuildQueueService do
+RSpec.describe Ci::UpdateBuildQueueService do
let(:project) { create(:project, :repository) }
let(:build) { create(:ci_build, pipeline: pipeline) }
let(:pipeline) { create(:ci_pipeline, project: project) }
diff --git a/spec/services/ci/update_instance_variables_service_spec.rb b/spec/services/ci/update_instance_variables_service_spec.rb
index 93f6e5d3ea8..f235d006e34 100644
--- a/spec/services/ci/update_instance_variables_service_spec.rb
+++ b/spec/services/ci/update_instance_variables_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::UpdateInstanceVariablesService do
+RSpec.describe Ci::UpdateInstanceVariablesService do
let(:params) { { variables_attributes: variables_attributes } }
subject { described_class.new(params) }
diff --git a/spec/services/ci/update_runner_service_spec.rb b/spec/services/ci/update_runner_service_spec.rb
index abe575eebc8..cad9e893335 100644
--- a/spec/services/ci/update_runner_service_spec.rb
+++ b/spec/services/ci/update_runner_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::UpdateRunnerService do
+RSpec.describe Ci::UpdateRunnerService do
let(:runner) { create(:ci_runner) }
describe '#update' do
diff --git a/spec/services/ci/web_ide_config_service_spec.rb b/spec/services/ci/web_ide_config_service_spec.rb
index 7522103ccb7..437b468cec8 100644
--- a/spec/services/ci/web_ide_config_service_spec.rb
+++ b/spec/services/ci/web_ide_config_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::WebIdeConfigService do
+RSpec.describe Ci::WebIdeConfigService do
let_it_be(:project) { create(:project, :repository) }
let_it_be(:user) { create(:user) }
let(:sha) { 'sha' }
diff --git a/spec/services/clusters/applications/check_ingress_ip_address_service_spec.rb b/spec/services/clusters/applications/check_ingress_ip_address_service_spec.rb
index 84bca76e69b..605d9e67ab6 100644
--- a/spec/services/clusters/applications/check_ingress_ip_address_service_spec.rb
+++ b/spec/services/clusters/applications/check_ingress_ip_address_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Clusters::Applications::CheckIngressIpAddressService do
+RSpec.describe Clusters::Applications::CheckIngressIpAddressService do
include ExclusiveLeaseHelpers
let(:application) { create(:clusters_applications_ingress, :installed) }
diff --git a/spec/services/clusters/applications/check_installation_progress_service_spec.rb b/spec/services/clusters/applications/check_installation_progress_service_spec.rb
index 4b8db405101..13f7cd62002 100644
--- a/spec/services/clusters/applications/check_installation_progress_service_spec.rb
+++ b/spec/services/clusters/applications/check_installation_progress_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Clusters::Applications::CheckInstallationProgressService, '#execute' do
+RSpec.describe Clusters::Applications::CheckInstallationProgressService, '#execute' do
RESCHEDULE_PHASES = Gitlab::Kubernetes::Pod::PHASES - [Gitlab::Kubernetes::Pod::SUCCEEDED, Gitlab::Kubernetes::Pod::FAILED].freeze
let(:application) { create(:clusters_applications_helm, :installing) }
diff --git a/spec/services/clusters/applications/check_uninstall_progress_service_spec.rb b/spec/services/clusters/applications/check_uninstall_progress_service_spec.rb
index 9dede1947f8..4b8893429cf 100644
--- a/spec/services/clusters/applications/check_uninstall_progress_service_spec.rb
+++ b/spec/services/clusters/applications/check_uninstall_progress_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Clusters::Applications::CheckUninstallProgressService do
+RSpec.describe Clusters::Applications::CheckUninstallProgressService do
reschedule_phases = Gitlab::Kubernetes::Pod::PHASES - [Gitlab::Kubernetes::Pod::SUCCEEDED, Gitlab::Kubernetes::Pod::FAILED].freeze
let(:application) { create(:clusters_applications_prometheus, :uninstalling) }
diff --git a/spec/services/clusters/applications/check_upgrade_progress_service_spec.rb b/spec/services/clusters/applications/check_upgrade_progress_service_spec.rb
index 29ee897454a..dbde8cec9b9 100644
--- a/spec/services/clusters/applications/check_upgrade_progress_service_spec.rb
+++ b/spec/services/clusters/applications/check_upgrade_progress_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Clusters::Applications::CheckUpgradeProgressService do
+RSpec.describe Clusters::Applications::CheckUpgradeProgressService do
reschedule_phashes = ::Gitlab::Kubernetes::Pod::PHASES -
[::Gitlab::Kubernetes::Pod::SUCCEEDED, ::Gitlab::Kubernetes::Pod::FAILED, ::Gitlab].freeze
diff --git a/spec/services/clusters/applications/create_service_spec.rb b/spec/services/clusters/applications/create_service_spec.rb
index 0b48af408e1..f93ae2c62f3 100644
--- a/spec/services/clusters/applications/create_service_spec.rb
+++ b/spec/services/clusters/applications/create_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Clusters::Applications::CreateService do
+RSpec.describe Clusters::Applications::CreateService do
include TestRequestHelpers
let(:cluster) { create(:cluster, :project, :provided_by_gcp) }
diff --git a/spec/services/clusters/applications/destroy_service_spec.rb b/spec/services/clusters/applications/destroy_service_spec.rb
index 8d9dc6a0f11..7306256e68e 100644
--- a/spec/services/clusters/applications/destroy_service_spec.rb
+++ b/spec/services/clusters/applications/destroy_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Clusters::Applications::DestroyService, '#execute' do
+RSpec.describe Clusters::Applications::DestroyService, '#execute' do
let(:cluster) { create(:cluster, :project, :provided_by_gcp) }
let(:user) { create(:user) }
let(:params) { { application: 'prometheus' } }
diff --git a/spec/services/clusters/applications/install_service_spec.rb b/spec/services/clusters/applications/install_service_spec.rb
index 2441cc595a3..d34b4dd943c 100644
--- a/spec/services/clusters/applications/install_service_spec.rb
+++ b/spec/services/clusters/applications/install_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Clusters::Applications::InstallService do
+RSpec.describe Clusters::Applications::InstallService do
describe '#execute' do
let(:application) { create(:clusters_applications_helm, :scheduled) }
let!(:install_command) { application.install_command }
diff --git a/spec/services/clusters/applications/patch_service_spec.rb b/spec/services/clusters/applications/patch_service_spec.rb
index dc9843a5116..281da62b80b 100644
--- a/spec/services/clusters/applications/patch_service_spec.rb
+++ b/spec/services/clusters/applications/patch_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Clusters::Applications::PatchService do
+RSpec.describe Clusters::Applications::PatchService do
describe '#execute' do
let(:application) { create(:clusters_applications_knative, :scheduled) }
let!(:update_command) { application.update_command }
diff --git a/spec/services/clusters/applications/prometheus_config_service_spec.rb b/spec/services/clusters/applications/prometheus_config_service_spec.rb
index b9032e665ec..7399f250248 100644
--- a/spec/services/clusters/applications/prometheus_config_service_spec.rb
+++ b/spec/services/clusters/applications/prometheus_config_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Clusters::Applications::PrometheusConfigService do
+RSpec.describe Clusters::Applications::PrometheusConfigService do
include Gitlab::Routing.url_helpers
let_it_be(:project) { create(:project) }
diff --git a/spec/services/clusters/applications/prometheus_health_check_service_spec.rb b/spec/services/clusters/applications/prometheus_health_check_service_spec.rb
index 5c4127e4938..fc5a80688e6 100644
--- a/spec/services/clusters/applications/prometheus_health_check_service_spec.rb
+++ b/spec/services/clusters/applications/prometheus_health_check_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Clusters::Applications::PrometheusHealthCheckService, '#execute' do
+RSpec.describe Clusters::Applications::PrometheusHealthCheckService, '#execute' do
let(:service) { described_class.new(cluster) }
subject { service.execute }
diff --git a/spec/services/clusters/applications/prometheus_update_service_spec.rb b/spec/services/clusters/applications/prometheus_update_service_spec.rb
index 078b01d2777..076ff0210c9 100644
--- a/spec/services/clusters/applications/prometheus_update_service_spec.rb
+++ b/spec/services/clusters/applications/prometheus_update_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Clusters::Applications::PrometheusUpdateService do
+RSpec.describe Clusters::Applications::PrometheusUpdateService do
describe '#execute' do
let(:project) { create(:project) }
let(:environment) { create(:environment, project: project) }
diff --git a/spec/services/clusters/applications/schedule_update_service_spec.rb b/spec/services/clusters/applications/schedule_update_service_spec.rb
index eb1006ce8e0..f559fb1b7aa 100644
--- a/spec/services/clusters/applications/schedule_update_service_spec.rb
+++ b/spec/services/clusters/applications/schedule_update_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Clusters::Applications::ScheduleUpdateService do
+RSpec.describe Clusters::Applications::ScheduleUpdateService do
describe '#execute' do
let(:project) { create(:project) }
diff --git a/spec/services/clusters/applications/uninstall_service_spec.rb b/spec/services/clusters/applications/uninstall_service_spec.rb
index 6d7f0478b20..50d7e82c47e 100644
--- a/spec/services/clusters/applications/uninstall_service_spec.rb
+++ b/spec/services/clusters/applications/uninstall_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Clusters::Applications::UninstallService, '#execute' do
+RSpec.describe Clusters::Applications::UninstallService, '#execute' do
let(:application) { create(:clusters_applications_prometheus, :scheduled) }
let(:service) { described_class.new(application) }
let(:helm_client) { instance_double(Gitlab::Kubernetes::Helm::API) }
diff --git a/spec/services/clusters/applications/update_service_spec.rb b/spec/services/clusters/applications/update_service_spec.rb
index 4676951faff..4c05a12a4a1 100644
--- a/spec/services/clusters/applications/update_service_spec.rb
+++ b/spec/services/clusters/applications/update_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Clusters::Applications::UpdateService do
+RSpec.describe Clusters::Applications::UpdateService do
include TestRequestHelpers
let(:cluster) { create(:cluster, :project, :provided_by_gcp) }
diff --git a/spec/services/clusters/applications/upgrade_service_spec.rb b/spec/services/clusters/applications/upgrade_service_spec.rb
index 86fb06375f1..22fbb7ca6e3 100644
--- a/spec/services/clusters/applications/upgrade_service_spec.rb
+++ b/spec/services/clusters/applications/upgrade_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Clusters::Applications::UpgradeService do
+RSpec.describe Clusters::Applications::UpgradeService do
describe '#execute' do
let(:application) { create(:clusters_applications_helm, :scheduled) }
let!(:install_command) { application.install_command }
diff --git a/spec/services/clusters/aws/authorize_role_service_spec.rb b/spec/services/clusters/aws/authorize_role_service_spec.rb
index 3ef332558a2..530268340b7 100644
--- a/spec/services/clusters/aws/authorize_role_service_spec.rb
+++ b/spec/services/clusters/aws/authorize_role_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Clusters::Aws::AuthorizeRoleService do
+RSpec.describe Clusters::Aws::AuthorizeRoleService do
let(:user) { create(:user) }
let(:credentials) { instance_double(Aws::Credentials) }
let(:credentials_service) { instance_double(Clusters::Aws::FetchCredentialsService, execute: credentials) }
diff --git a/spec/services/clusters/aws/fetch_credentials_service_spec.rb b/spec/services/clusters/aws/fetch_credentials_service_spec.rb
index 9194947c67f..a0e63d96a5c 100644
--- a/spec/services/clusters/aws/fetch_credentials_service_spec.rb
+++ b/spec/services/clusters/aws/fetch_credentials_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Clusters::Aws::FetchCredentialsService do
+RSpec.describe Clusters::Aws::FetchCredentialsService do
describe '#execute' do
let(:user) { create(:user) }
let(:provider) { create(:cluster_provider_aws, region: 'ap-southeast-2') }
diff --git a/spec/services/clusters/aws/finalize_creation_service_spec.rb b/spec/services/clusters/aws/finalize_creation_service_spec.rb
index 8d7341483e3..6b0cb86eff0 100644
--- a/spec/services/clusters/aws/finalize_creation_service_spec.rb
+++ b/spec/services/clusters/aws/finalize_creation_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Clusters::Aws::FinalizeCreationService do
+RSpec.describe Clusters::Aws::FinalizeCreationService do
describe '#execute' do
let(:provider) { create(:cluster_provider_aws, :creating) }
let(:platform) { provider.cluster.platform_kubernetes }
diff --git a/spec/services/clusters/aws/provision_service_spec.rb b/spec/services/clusters/aws/provision_service_spec.rb
index 15571c64e13..529e1d26575 100644
--- a/spec/services/clusters/aws/provision_service_spec.rb
+++ b/spec/services/clusters/aws/provision_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Clusters::Aws::ProvisionService do
+RSpec.describe Clusters::Aws::ProvisionService do
describe '#execute' do
let(:provider) { create(:cluster_provider_aws) }
diff --git a/spec/services/clusters/aws/verify_provision_status_service_spec.rb b/spec/services/clusters/aws/verify_provision_status_service_spec.rb
index b62b0875bf3..b9a58b97842 100644
--- a/spec/services/clusters/aws/verify_provision_status_service_spec.rb
+++ b/spec/services/clusters/aws/verify_provision_status_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Clusters::Aws::VerifyProvisionStatusService do
+RSpec.describe Clusters::Aws::VerifyProvisionStatusService do
describe '#execute' do
let(:provider) { create(:cluster_provider_aws) }
diff --git a/spec/services/clusters/build_kubernetes_namespace_service_spec.rb b/spec/services/clusters/build_kubernetes_namespace_service_spec.rb
index 36c05469542..4ee933374f6 100644
--- a/spec/services/clusters/build_kubernetes_namespace_service_spec.rb
+++ b/spec/services/clusters/build_kubernetes_namespace_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Clusters::BuildKubernetesNamespaceService do
+RSpec.describe Clusters::BuildKubernetesNamespaceService do
let(:cluster) { create(:cluster, :project, :provided_by_gcp) }
let(:environment) { create(:environment) }
let(:project) { environment.project }
diff --git a/spec/services/clusters/build_service_spec.rb b/spec/services/clusters/build_service_spec.rb
index f3e852726f4..c7a64435d3b 100644
--- a/spec/services/clusters/build_service_spec.rb
+++ b/spec/services/clusters/build_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Clusters::BuildService do
+RSpec.describe Clusters::BuildService do
describe '#execute' do
subject { described_class.new(cluster_subject).execute }
diff --git a/spec/services/clusters/cleanup/app_service_spec.rb b/spec/services/clusters/cleanup/app_service_spec.rb
index 14bfca02fee..ba1be7448a4 100644
--- a/spec/services/clusters/cleanup/app_service_spec.rb
+++ b/spec/services/clusters/cleanup/app_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Clusters::Cleanup::AppService do
+RSpec.describe Clusters::Cleanup::AppService do
describe '#execute' do
let!(:cluster) { create(:cluster, :project, :cleanup_uninstalling_applications, provider_type: :gcp) }
let(:service) { described_class.new(cluster) }
diff --git a/spec/services/clusters/cleanup/project_namespace_service_spec.rb b/spec/services/clusters/cleanup/project_namespace_service_spec.rb
index 22e29cc57d1..761ad8dd8c8 100644
--- a/spec/services/clusters/cleanup/project_namespace_service_spec.rb
+++ b/spec/services/clusters/cleanup/project_namespace_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Clusters::Cleanup::ProjectNamespaceService do
+RSpec.describe Clusters::Cleanup::ProjectNamespaceService do
describe '#execute' do
subject { service.execute }
diff --git a/spec/services/clusters/cleanup/service_account_service_spec.rb b/spec/services/clusters/cleanup/service_account_service_spec.rb
index ecaf0da9fa3..6fe3d0c286e 100644
--- a/spec/services/clusters/cleanup/service_account_service_spec.rb
+++ b/spec/services/clusters/cleanup/service_account_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Clusters::Cleanup::ServiceAccountService do
+RSpec.describe Clusters::Cleanup::ServiceAccountService do
describe '#execute' do
subject { service.execute }
diff --git a/spec/services/clusters/create_service_spec.rb b/spec/services/clusters/create_service_spec.rb
index 3dd25be2a3d..6e252bee7c0 100644
--- a/spec/services/clusters/create_service_spec.rb
+++ b/spec/services/clusters/create_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Clusters::CreateService do
+RSpec.describe Clusters::CreateService do
let(:access_token) { 'xxx' }
let(:project) { create(:project) }
let(:user) { create(:user) }
@@ -53,13 +53,54 @@ describe Clusters::CreateService do
include_context 'valid cluster create params'
let!(:cluster) { create(:cluster, :provided_by_gcp, :production_environment, projects: [project]) }
- it 'does not create a cluster' do
- expect(ClusterProvisionWorker).not_to receive(:perform_async)
- expect { subject }.to raise_error(ArgumentError).and change { Clusters::Cluster.count }.by(0)
+ it 'creates another cluster' do
+ expect(ClusterProvisionWorker).to receive(:perform_async)
+ expect { subject }.to change { Clusters::Cluster.count }.by(1)
end
end
end
+ context 'when another cluster exists' do
+ let!(:cluster) { create(:cluster, :provided_by_gcp, :production_environment, projects: [project]) }
+
+ context 'when correct params' do
+ let(:params) do
+ {
+ name: 'test-cluster',
+ provider_type: :gcp,
+ provider_gcp_attributes: {
+ gcp_project_id: 'gcp-project',
+ zone: 'us-central1-a',
+ num_nodes: 1,
+ machine_type: 'machine_type-a',
+ legacy_abac: 'true'
+ },
+ clusterable: project
+ }
+ end
+
+ include_examples 'create cluster service success'
+ end
+
+ context 'when invalid params' do
+ let(:params) do
+ {
+ name: 'test-cluster',
+ provider_type: :gcp,
+ provider_gcp_attributes: {
+ gcp_project_id: '!!!!!!!',
+ zone: 'us-central1-a',
+ num_nodes: 1,
+ machine_type: 'machine_type-a'
+ },
+ clusterable: project
+ }
+ end
+
+ include_examples 'create cluster service error'
+ end
+ end
+
context 'when params includes :management_project_id' do
subject(:cluster) { described_class.new(user, params).execute(access_token: access_token) }
diff --git a/spec/services/clusters/destroy_service_spec.rb b/spec/services/clusters/destroy_service_spec.rb
index 43ebf8f499e..76d9cc34b5d 100644
--- a/spec/services/clusters/destroy_service_spec.rb
+++ b/spec/services/clusters/destroy_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Clusters::DestroyService do
+RSpec.describe Clusters::DestroyService do
describe '#execute' do
subject { described_class.new(cluster.user, params).execute(cluster) }
diff --git a/spec/services/clusters/gcp/fetch_operation_service_spec.rb b/spec/services/clusters/gcp/fetch_operation_service_spec.rb
index 23da8004a7d..990cc745382 100644
--- a/spec/services/clusters/gcp/fetch_operation_service_spec.rb
+++ b/spec/services/clusters/gcp/fetch_operation_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Clusters::Gcp::FetchOperationService do
+RSpec.describe Clusters::Gcp::FetchOperationService do
include GoogleApi::CloudPlatformHelpers
describe '#execute' do
diff --git a/spec/services/clusters/gcp/finalize_creation_service_spec.rb b/spec/services/clusters/gcp/finalize_creation_service_spec.rb
index 4d1548c9786..be362dc6e23 100644
--- a/spec/services/clusters/gcp/finalize_creation_service_spec.rb
+++ b/spec/services/clusters/gcp/finalize_creation_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Clusters::Gcp::FinalizeCreationService, '#execute' do
+RSpec.describe Clusters::Gcp::FinalizeCreationService, '#execute' do
include GoogleApi::CloudPlatformHelpers
include KubernetesHelpers
diff --git a/spec/services/clusters/gcp/provision_service_spec.rb b/spec/services/clusters/gcp/provision_service_spec.rb
index dfd15690a1f..c5778db6001 100644
--- a/spec/services/clusters/gcp/provision_service_spec.rb
+++ b/spec/services/clusters/gcp/provision_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Clusters::Gcp::ProvisionService do
+RSpec.describe Clusters::Gcp::ProvisionService do
include GoogleApi::CloudPlatformHelpers
describe '#execute' do
diff --git a/spec/services/clusters/gcp/verify_provision_status_service_spec.rb b/spec/services/clusters/gcp/verify_provision_status_service_spec.rb
index 584f9b8367f..ccb4b3b6c15 100644
--- a/spec/services/clusters/gcp/verify_provision_status_service_spec.rb
+++ b/spec/services/clusters/gcp/verify_provision_status_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Clusters::Gcp::VerifyProvisionStatusService do
+RSpec.describe Clusters::Gcp::VerifyProvisionStatusService do
include GoogleApi::CloudPlatformHelpers
describe '#execute' do
diff --git a/spec/services/clusters/kubernetes/configure_istio_ingress_service_spec.rb b/spec/services/clusters/kubernetes/configure_istio_ingress_service_spec.rb
index e9f7f015293..b4402aadc88 100644
--- a/spec/services/clusters/kubernetes/configure_istio_ingress_service_spec.rb
+++ b/spec/services/clusters/kubernetes/configure_istio_ingress_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Clusters::Kubernetes::ConfigureIstioIngressService, '#execute' do
+RSpec.describe Clusters::Kubernetes::ConfigureIstioIngressService, '#execute' do
include KubernetesHelpers
let(:cluster) { create(:cluster, :project, :provided_by_gcp) }
diff --git a/spec/services/clusters/kubernetes/create_or_update_namespace_service_spec.rb b/spec/services/clusters/kubernetes/create_or_update_namespace_service_spec.rb
index 6d8b1617c17..ee10c59390e 100644
--- a/spec/services/clusters/kubernetes/create_or_update_namespace_service_spec.rb
+++ b/spec/services/clusters/kubernetes/create_or_update_namespace_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Clusters::Kubernetes::CreateOrUpdateNamespaceService, '#execute' do
+RSpec.describe Clusters::Kubernetes::CreateOrUpdateNamespaceService, '#execute' do
include KubernetesHelpers
let(:cluster) { create(:cluster, :project, :provided_by_gcp) }
diff --git a/spec/services/clusters/kubernetes/create_or_update_service_account_service_spec.rb b/spec/services/clusters/kubernetes/create_or_update_service_account_service_spec.rb
index 4bcd5c6933e..f3fa6c2c0bb 100644
--- a/spec/services/clusters/kubernetes/create_or_update_service_account_service_spec.rb
+++ b/spec/services/clusters/kubernetes/create_or_update_service_account_service_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe Clusters::Kubernetes::CreateOrUpdateServiceAccountService do
+RSpec.describe Clusters::Kubernetes::CreateOrUpdateServiceAccountService do
include KubernetesHelpers
let(:api_url) { 'http://111.111.111.111' }
diff --git a/spec/services/clusters/kubernetes/fetch_kubernetes_token_service_spec.rb b/spec/services/clusters/kubernetes/fetch_kubernetes_token_service_spec.rb
index fa4b6e497e5..c4daae9dbf0 100644
--- a/spec/services/clusters/kubernetes/fetch_kubernetes_token_service_spec.rb
+++ b/spec/services/clusters/kubernetes/fetch_kubernetes_token_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Clusters::Kubernetes::FetchKubernetesTokenService do
+RSpec.describe Clusters::Kubernetes::FetchKubernetesTokenService do
include KubernetesHelpers
describe '#execute' do
diff --git a/spec/services/clusters/kubernetes_spec.rb b/spec/services/clusters/kubernetes_spec.rb
index 09cc304debe..12af63890fc 100644
--- a/spec/services/clusters/kubernetes_spec.rb
+++ b/spec/services/clusters/kubernetes_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Clusters::Kubernetes do
+RSpec.describe Clusters::Kubernetes do
it { is_expected.to be_const_defined(:GITLAB_SERVICE_ACCOUNT_NAME) }
it { is_expected.to be_const_defined(:GITLAB_SERVICE_ACCOUNT_NAMESPACE) }
it { is_expected.to be_const_defined(:GITLAB_ADMIN_TOKEN_NAME) }
diff --git a/spec/services/clusters/management/create_project_service_spec.rb b/spec/services/clusters/management/create_project_service_spec.rb
index b7764b7840c..5d8cc71faa4 100644
--- a/spec/services/clusters/management/create_project_service_spec.rb
+++ b/spec/services/clusters/management/create_project_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Clusters::Management::CreateProjectService do
+RSpec.describe Clusters::Management::CreateProjectService do
let(:cluster) { create(:cluster, :project) }
let(:current_user) { create(:user) }
diff --git a/spec/services/clusters/management/validate_management_project_permissions_service_spec.rb b/spec/services/clusters/management/validate_management_project_permissions_service_spec.rb
index 1bcebe2e2ac..a21c378d3d1 100644
--- a/spec/services/clusters/management/validate_management_project_permissions_service_spec.rb
+++ b/spec/services/clusters/management/validate_management_project_permissions_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Clusters::Management::ValidateManagementProjectPermissionsService do
+RSpec.describe Clusters::Management::ValidateManagementProjectPermissionsService do
describe '#execute' do
subject { described_class.new(user).execute(cluster, management_project_id) }
diff --git a/spec/services/clusters/parse_cluster_applications_artifact_service_spec.rb b/spec/services/clusters/parse_cluster_applications_artifact_service_spec.rb
index bb0b107eba6..3b155d95345 100644
--- a/spec/services/clusters/parse_cluster_applications_artifact_service_spec.rb
+++ b/spec/services/clusters/parse_cluster_applications_artifact_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Clusters::ParseClusterApplicationsArtifactService do
+RSpec.describe Clusters::ParseClusterApplicationsArtifactService do
let_it_be(:project) { create(:project) }
let_it_be(:user) { create(:user) }
@@ -120,90 +120,9 @@ describe Clusters::ParseClusterApplicationsArtifactService do
end
end
- context 'release is missing' do
- let(:fixture) { 'spec/fixtures/helm/helm_list_v2_prometheus_missing.json.gz' }
- let(:file) { fixture_file_upload(Rails.root.join(fixture)) }
- let(:artifact) { create(:ci_job_artifact, :cluster_applications, job: job, file: file) }
-
- context 'application does not exist' do
- it 'does not create or destroy an application' do
- expect do
- described_class.new(job, user).execute(artifact)
- end.not_to change(Clusters::Applications::Prometheus, :count)
- end
- end
-
- context 'application exists' do
- before do
- create(:clusters_applications_prometheus, :installed, cluster: cluster)
- end
-
- it 'marks the application as uninstalled' do
- described_class.new(job, user).execute(artifact)
-
- cluster.application_prometheus.reload
- expect(cluster.application_prometheus).to be_uninstalled
- end
- end
- end
-
- context 'release is deployed' do
- let(:fixture) { 'spec/fixtures/helm/helm_list_v2_prometheus_deployed.json.gz' }
- let(:file) { fixture_file_upload(Rails.root.join(fixture)) }
- let(:artifact) { create(:ci_job_artifact, :cluster_applications, job: job, file: file) }
-
- context 'application does not exist' do
- it 'creates an application and marks it as installed' do
- expect do
- described_class.new(job, user).execute(artifact)
- end.to change(Clusters::Applications::Prometheus, :count)
-
- expect(cluster.application_prometheus).to be_persisted
- expect(cluster.application_prometheus).to be_installed
- end
- end
-
- context 'application exists' do
- before do
- create(:clusters_applications_prometheus, :errored, cluster: cluster)
- end
-
- it 'marks the application as installed' do
- described_class.new(job, user).execute(artifact)
-
- expect(cluster.application_prometheus).to be_installed
- end
- end
- end
-
- context 'release is failed' do
- let(:fixture) { 'spec/fixtures/helm/helm_list_v2_prometheus_failed.json.gz' }
- let(:file) { fixture_file_upload(Rails.root.join(fixture)) }
- let(:artifact) { create(:ci_job_artifact, :cluster_applications, job: job, file: file) }
-
- context 'application does not exist' do
- it 'creates an application and marks it as errored' do
- expect do
- described_class.new(job, user).execute(artifact)
- end.to change(Clusters::Applications::Prometheus, :count)
-
- expect(cluster.application_prometheus).to be_persisted
- expect(cluster.application_prometheus).to be_errored
- expect(cluster.application_prometheus.status_reason).to eq('Helm release failed to install')
- end
- end
-
- context 'application exists' do
- before do
- create(:clusters_applications_prometheus, :installed, cluster: cluster)
- end
-
- it 'marks the application as errored' do
- described_class.new(job, user).execute(artifact)
-
- expect(cluster.application_prometheus).to be_errored
- expect(cluster.application_prometheus.status_reason).to eq('Helm release failed to install')
- end
+ Clusters::ParseClusterApplicationsArtifactService::RELEASE_NAMES.each do |release_name|
+ context release_name do
+ include_examples 'parse cluster applications artifact', release_name
end
end
end
diff --git a/spec/services/clusters/update_service_spec.rb b/spec/services/clusters/update_service_spec.rb
index 5a7726eded8..e496ccd5c23 100644
--- a/spec/services/clusters/update_service_spec.rb
+++ b/spec/services/clusters/update_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Clusters::UpdateService do
+RSpec.describe Clusters::UpdateService do
include KubernetesHelpers
describe '#execute' do
diff --git a/spec/services/cohorts_service_spec.rb b/spec/services/cohorts_service_spec.rb
index b2f82a1153c..dce8d4f80f2 100644
--- a/spec/services/cohorts_service_spec.rb
+++ b/spec/services/cohorts_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe CohortsService do
+RSpec.describe CohortsService do
describe '#execute' do
def month_start(months_ago)
months_ago.months.ago.beginning_of_month.to_date
diff --git a/spec/services/commits/cherry_pick_service_spec.rb b/spec/services/commits/cherry_pick_service_spec.rb
index 3b797b8ac02..8fad5164b77 100644
--- a/spec/services/commits/cherry_pick_service_spec.rb
+++ b/spec/services/commits/cherry_pick_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Commits::CherryPickService do
+RSpec.describe Commits::CherryPickService do
let(:project) { create(:project, :repository) }
# * ddd0f15ae83993f5cb66a927a28673882e99100b (HEAD -> master, origin/master, origin/HEAD) Merge branch 'po-fix-test-en
# |\
diff --git a/spec/services/commits/commit_patch_service_spec.rb b/spec/services/commits/commit_patch_service_spec.rb
index f4fcec2fbc2..c8c0cbe23b2 100644
--- a/spec/services/commits/commit_patch_service_spec.rb
+++ b/spec/services/commits/commit_patch_service_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe Commits::CommitPatchService do
+RSpec.describe Commits::CommitPatchService do
describe '#execute' do
let(:patches) do
patches_folder = Rails.root.join('spec/fixtures/patchfiles')
diff --git a/spec/services/commits/tag_service_spec.rb b/spec/services/commits/tag_service_spec.rb
index 82377a8dace..dd742ebe469 100644
--- a/spec/services/commits/tag_service_spec.rb
+++ b/spec/services/commits/tag_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Commits::TagService do
+RSpec.describe Commits::TagService do
let(:project) { create(:project, :repository) }
let(:user) { create(:user) }
diff --git a/spec/services/compare_service_spec.rb b/spec/services/compare_service_spec.rb
index f6d8eb348d0..e96a7f2f4f4 100644
--- a/spec/services/compare_service_spec.rb
+++ b/spec/services/compare_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe CompareService do
+RSpec.describe CompareService do
let(:project) { create(:project, :repository) }
let(:user) { create(:user) }
let(:service) { described_class.new(project, 'feature') }
diff --git a/spec/services/concerns/exclusive_lease_guard_spec.rb b/spec/services/concerns/exclusive_lease_guard_spec.rb
index a38facc7520..d54ba6abadd 100644
--- a/spec/services/concerns/exclusive_lease_guard_spec.rb
+++ b/spec/services/concerns/exclusive_lease_guard_spec.rb
@@ -51,7 +51,7 @@ RSpec.describe ExclusiveLeaseGuard, :clean_gitlab_redis_shared_state do
it 'does not call internal_method but logs error', :aggregate_failures do
expect(subject).not_to receive(:internal_method)
- expect(Gitlab::AppLogger).to receive(:error).with('Cannot obtain an exclusive lease. There must be another instance already in execution.')
+ expect(Gitlab::AppLogger).to receive(:error).with("Cannot obtain an exclusive lease for #{subject.class.name}. There must be another instance already in execution.")
subject.call
end
diff --git a/spec/services/concerns/merge_requests/assigns_merge_params_spec.rb b/spec/services/concerns/merge_requests/assigns_merge_params_spec.rb
index 9cf7f354191..5b1e8fca31b 100644
--- a/spec/services/concerns/merge_requests/assigns_merge_params_spec.rb
+++ b/spec/services/concerns/merge_requests/assigns_merge_params_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe MergeRequests::AssignsMergeParams do
+RSpec.describe MergeRequests::AssignsMergeParams do
it 'raises an error when used from an instance that does not respond to #current_user' do
define_class = -> { Class.new { include MergeRequests::AssignsMergeParams }.new }
diff --git a/spec/services/container_expiration_policies/update_service_spec.rb b/spec/services/container_expiration_policies/update_service_spec.rb
index ec178f3830f..d4b6715ae86 100644
--- a/spec/services/container_expiration_policies/update_service_spec.rb
+++ b/spec/services/container_expiration_policies/update_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ContainerExpirationPolicies::UpdateService do
+RSpec.describe ContainerExpirationPolicies::UpdateService do
using RSpec::Parameterized::TableSyntax
let_it_be(:project, reload: true) { create(:project) }
diff --git a/spec/services/container_expiration_policy_service_spec.rb b/spec/services/container_expiration_policy_service_spec.rb
index 97715b990ef..dfce51d73ad 100644
--- a/spec/services/container_expiration_policy_service_spec.rb
+++ b/spec/services/container_expiration_policy_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ContainerExpirationPolicyService do
+RSpec.describe ContainerExpirationPolicyService do
let_it_be(:user) { create(:user) }
let_it_be(:container_expiration_policy) { create(:container_expiration_policy, :runnable) }
let(:project) { container_expiration_policy.project }
diff --git a/spec/services/deploy_keys/collect_keys_service_spec.rb b/spec/services/deploy_keys/collect_keys_service_spec.rb
new file mode 100644
index 00000000000..3442e5e456a
--- /dev/null
+++ b/spec/services/deploy_keys/collect_keys_service_spec.rb
@@ -0,0 +1,58 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe DeployKeys::CollectKeysService do
+ let_it_be(:user) { create(:user) }
+ let_it_be(:project) { create(:project, :private) }
+
+ subject { DeployKeys::CollectKeysService.new(project, user) }
+
+ before do
+ project&.add_developer(user)
+ end
+
+ context 'when no project is passed in' do
+ let(:project) { nil }
+
+ it 'returns an empty Array' do
+ expect(subject.execute).to be_empty
+ end
+ end
+
+ context 'when no user is passed in' do
+ let(:user) { nil }
+
+ it 'returns an empty Array' do
+ expect(subject.execute).to be_empty
+ end
+ end
+
+ context 'when a project is passed in' do
+ let_it_be(:deploy_keys_project) { create(:deploy_keys_project, :write_access, project: project) }
+ let_it_be(:deploy_key) { deploy_keys_project.deploy_key }
+
+ it 'only returns deploy keys with write access' do
+ create(:deploy_keys_project, project: project)
+
+ expect(subject.execute).to contain_exactly(deploy_key)
+ end
+
+ it 'returns deploy keys only for this project' do
+ other_project = create(:project)
+ create(:deploy_keys_project, :write_access, project: other_project)
+
+ expect(subject.execute).to contain_exactly(deploy_key)
+ end
+ end
+
+ context 'when the user cannot read the project' do
+ before do
+ project.members.delete_all
+ end
+
+ it 'returns an empty Array' do
+ expect(subject.execute).to be_empty
+ end
+ end
+end
diff --git a/spec/services/deploy_keys/create_service_spec.rb b/spec/services/deploy_keys/create_service_spec.rb
index a55f1561194..2e3318236f5 100644
--- a/spec/services/deploy_keys/create_service_spec.rb
+++ b/spec/services/deploy_keys/create_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe DeployKeys::CreateService do
+RSpec.describe DeployKeys::CreateService do
let(:user) { create(:user) }
let(:params) { attributes_for(:deploy_key) }
diff --git a/spec/services/deployments/after_create_service_spec.rb b/spec/services/deployments/after_create_service_spec.rb
index 5a69ffd8b9c..3287eed03b7 100644
--- a/spec/services/deployments/after_create_service_spec.rb
+++ b/spec/services/deployments/after_create_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Deployments::AfterCreateService do
+RSpec.describe Deployments::AfterCreateService do
let(:user) { create(:user) }
let(:project) { create(:project, :repository) }
let(:options) { { name: 'production' } }
diff --git a/spec/services/deployments/create_service_spec.rb b/spec/services/deployments/create_service_spec.rb
index 6ab1f8635f7..d1f977c28d3 100644
--- a/spec/services/deployments/create_service_spec.rb
+++ b/spec/services/deployments/create_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Deployments::CreateService do
+RSpec.describe Deployments::CreateService do
let(:user) { create(:user) }
describe '#execute' do
diff --git a/spec/services/deployments/link_merge_requests_service_spec.rb b/spec/services/deployments/link_merge_requests_service_spec.rb
index aa2cecbf897..e2ac2273b8c 100644
--- a/spec/services/deployments/link_merge_requests_service_spec.rb
+++ b/spec/services/deployments/link_merge_requests_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Deployments::LinkMergeRequestsService do
+RSpec.describe Deployments::LinkMergeRequestsService do
let(:project) { create(:project, :repository) }
# * ddd0f15 Merge branch 'po-fix-test-env-path' into 'master'
diff --git a/spec/services/deployments/older_deployments_drop_service_spec.rb b/spec/services/deployments/older_deployments_drop_service_spec.rb
index 4c9bcf90533..6152a95cc3c 100644
--- a/spec/services/deployments/older_deployments_drop_service_spec.rb
+++ b/spec/services/deployments/older_deployments_drop_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Deployments::OlderDeploymentsDropService do
+RSpec.describe Deployments::OlderDeploymentsDropService do
let(:environment) { create(:environment) }
let(:deployment) { create(:deployment, environment: environment) }
let(:service) { described_class.new(deployment) }
diff --git a/spec/services/deployments/update_service_spec.rb b/spec/services/deployments/update_service_spec.rb
index 471e90de467..16b24d0dee8 100644
--- a/spec/services/deployments/update_service_spec.rb
+++ b/spec/services/deployments/update_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Deployments::UpdateService do
+RSpec.describe Deployments::UpdateService do
let(:deploy) { create(:deployment) }
describe '#execute' do
diff --git a/spec/services/design_management/delete_designs_service_spec.rb b/spec/services/design_management/delete_designs_service_spec.rb
index bf5d6b443e6..ace63b6e59c 100644
--- a/spec/services/design_management/delete_designs_service_spec.rb
+++ b/spec/services/design_management/delete_designs_service_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe DesignManagement::DeleteDesignsService do
+RSpec.describe DesignManagement::DeleteDesignsService do
include DesignManagementTestHelpers
let_it_be(:project) { create(:project) }
diff --git a/spec/services/design_management/design_user_notes_count_service_spec.rb b/spec/services/design_management/design_user_notes_count_service_spec.rb
index 62211a4dd0f..37806d3461c 100644
--- a/spec/services/design_management/design_user_notes_count_service_spec.rb
+++ b/spec/services/design_management/design_user_notes_count_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe DesignManagement::DesignUserNotesCountService, :use_clean_rails_memory_store_caching do
+RSpec.describe DesignManagement::DesignUserNotesCountService, :use_clean_rails_memory_store_caching do
let_it_be(:design) { create(:design, :with_file) }
subject { described_class.new(design) }
diff --git a/spec/services/design_management/generate_image_versions_service_spec.rb b/spec/services/design_management/generate_image_versions_service_spec.rb
index cd021c8d7d3..631eec97e5a 100644
--- a/spec/services/design_management/generate_image_versions_service_spec.rb
+++ b/spec/services/design_management/generate_image_versions_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe DesignManagement::GenerateImageVersionsService do
+RSpec.describe DesignManagement::GenerateImageVersionsService do
let_it_be(:project) { create(:project) }
let_it_be(:issue) { create(:issue, project: project) }
let_it_be(:version) { create(:design, :with_lfs_file, issue: issue).versions.first }
diff --git a/spec/services/design_management/save_designs_service_spec.rb b/spec/services/design_management/save_designs_service_spec.rb
index 3be3ac9daca..24639632566 100644
--- a/spec/services/design_management/save_designs_service_spec.rb
+++ b/spec/services/design_management/save_designs_service_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe DesignManagement::SaveDesignsService do
+RSpec.describe DesignManagement::SaveDesignsService do
include DesignManagementTestHelpers
include ConcurrentHelpers
diff --git a/spec/services/discussions/capture_diff_note_position_service_spec.rb b/spec/services/discussions/capture_diff_note_position_service_spec.rb
index bc71e170e92..0913ddd8ef2 100644
--- a/spec/services/discussions/capture_diff_note_position_service_spec.rb
+++ b/spec/services/discussions/capture_diff_note_position_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Discussions::CaptureDiffNotePositionService do
+RSpec.describe Discussions::CaptureDiffNotePositionService do
subject { described_class.new(note.noteable, paths) }
context 'image note on diff' do
diff --git a/spec/services/discussions/capture_diff_note_positions_service_spec.rb b/spec/services/discussions/capture_diff_note_positions_service_spec.rb
index 7b1e207f3eb..dede5a4c354 100644
--- a/spec/services/discussions/capture_diff_note_positions_service_spec.rb
+++ b/spec/services/discussions/capture_diff_note_positions_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Discussions::CaptureDiffNotePositionsService do
+RSpec.describe Discussions::CaptureDiffNotePositionsService do
context 'when merge request has a discussion' do
let(:source_branch) { 'compare-with-merge-head-source' }
let(:target_branch) { 'compare-with-merge-head-target' }
diff --git a/spec/services/discussions/resolve_service_spec.rb b/spec/services/discussions/resolve_service_spec.rb
index 7461934b455..5ff0d535b46 100644
--- a/spec/services/discussions/resolve_service_spec.rb
+++ b/spec/services/discussions/resolve_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Discussions::ResolveService do
+RSpec.describe Discussions::ResolveService do
describe '#execute' do
let_it_be(:project) { create(:project, :repository) }
let_it_be(:user) { create(:user, developer_projects: [project]) }
diff --git a/spec/services/discussions/update_diff_position_service_spec.rb b/spec/services/discussions/update_diff_position_service_spec.rb
index 60ec83e9062..85020e95c83 100644
--- a/spec/services/discussions/update_diff_position_service_spec.rb
+++ b/spec/services/discussions/update_diff_position_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Discussions::UpdateDiffPositionService do
+RSpec.describe Discussions::UpdateDiffPositionService do
let(:project) { create(:project, :repository) }
let(:current_user) { project.owner }
let(:create_commit) { project.commit("913c66a37b4a45b9769037c55c2d238bd0942d2e") }
diff --git a/spec/services/draft_notes/create_service_spec.rb b/spec/services/draft_notes/create_service_spec.rb
index 8f244ed386b..f0291067777 100644
--- a/spec/services/draft_notes/create_service_spec.rb
+++ b/spec/services/draft_notes/create_service_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe DraftNotes::CreateService do
+RSpec.describe DraftNotes::CreateService do
let(:merge_request) { create(:merge_request) }
let(:project) { merge_request.target_project }
let(:user) { merge_request.author }
diff --git a/spec/services/draft_notes/destroy_service_spec.rb b/spec/services/draft_notes/destroy_service_spec.rb
index d0bf88dcdbe..f725f08f3c7 100644
--- a/spec/services/draft_notes/destroy_service_spec.rb
+++ b/spec/services/draft_notes/destroy_service_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe DraftNotes::DestroyService do
+RSpec.describe DraftNotes::DestroyService do
let(:merge_request) { create(:merge_request) }
let(:project) { merge_request.target_project }
let(:user) { merge_request.author }
diff --git a/spec/services/draft_notes/publish_service_spec.rb b/spec/services/draft_notes/publish_service_spec.rb
index 4ebae2f9aa2..ae0c8113904 100644
--- a/spec/services/draft_notes/publish_service_spec.rb
+++ b/spec/services/draft_notes/publish_service_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe DraftNotes::PublishService do
+RSpec.describe DraftNotes::PublishService do
include RepoHelpers
let(:merge_request) { create(:merge_request) }
@@ -237,7 +237,8 @@ describe DraftNotes::PublishService do
it 'resolves the thread' do
publish(draft: draft_note)
- expect(note.discussion.resolved?).to be true
+ # discussion is memoized and reload doesn't clear the memoization
+ expect(Note.find(note.id).discussion.resolved?).to be true
end
it 'sends notifications if all threads are resolved' do
diff --git a/spec/services/emails/confirm_service_spec.rb b/spec/services/emails/confirm_service_spec.rb
index 973d2731b2f..935a673f548 100644
--- a/spec/services/emails/confirm_service_spec.rb
+++ b/spec/services/emails/confirm_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Emails::ConfirmService do
+RSpec.describe Emails::ConfirmService do
let(:user) { create(:user) }
subject(:service) { described_class.new(user) }
diff --git a/spec/services/emails/create_service_spec.rb b/spec/services/emails/create_service_spec.rb
index 23c2f53dca0..1396a1fce30 100644
--- a/spec/services/emails/create_service_spec.rb
+++ b/spec/services/emails/create_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Emails::CreateService do
+RSpec.describe Emails::CreateService do
let(:user) { create(:user) }
let(:opts) { { email: 'new@email.com', user: user } }
diff --git a/spec/services/emails/destroy_service_spec.rb b/spec/services/emails/destroy_service_spec.rb
index 9e14a13aa4f..f8407be41e7 100644
--- a/spec/services/emails/destroy_service_spec.rb
+++ b/spec/services/emails/destroy_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Emails::DestroyService do
+RSpec.describe Emails::DestroyService do
let!(:user) { create(:user) }
let!(:email) { create(:email, user: user) }
diff --git a/spec/services/environments/auto_stop_service_spec.rb b/spec/services/environments/auto_stop_service_spec.rb
index b34d15889d3..8e56c7e642c 100644
--- a/spec/services/environments/auto_stop_service_spec.rb
+++ b/spec/services/environments/auto_stop_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Environments::AutoStopService, :clean_gitlab_redis_shared_state do
+RSpec.describe Environments::AutoStopService, :clean_gitlab_redis_shared_state do
include CreateEnvironmentsHelpers
include ExclusiveLeaseHelpers
diff --git a/spec/services/environments/reset_auto_stop_service_spec.rb b/spec/services/environments/reset_auto_stop_service_spec.rb
index 53a20dd906e..cab1bf2cc26 100644
--- a/spec/services/environments/reset_auto_stop_service_spec.rb
+++ b/spec/services/environments/reset_auto_stop_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Environments::ResetAutoStopService do
+RSpec.describe Environments::ResetAutoStopService do
let_it_be(:project) { create(:project) }
let_it_be(:developer) { create(:user).tap { |user| project.add_developer(user) } }
let_it_be(:reporter) { create(:user).tap { |user| project.add_reporter(user) } }
diff --git a/spec/services/error_tracking/base_service_spec.rb b/spec/services/error_tracking/base_service_spec.rb
index 68deb2e2a73..ffbda37d417 100644
--- a/spec/services/error_tracking/base_service_spec.rb
+++ b/spec/services/error_tracking/base_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ErrorTracking::BaseService do
+RSpec.describe ErrorTracking::BaseService do
describe '#compose_response' do
let(:project) { double('project') }
let(:user) { double('user') }
diff --git a/spec/services/error_tracking/issue_details_service_spec.rb b/spec/services/error_tracking/issue_details_service_spec.rb
index 66b8988f8e3..1954640a512 100644
--- a/spec/services/error_tracking/issue_details_service_spec.rb
+++ b/spec/services/error_tracking/issue_details_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ErrorTracking::IssueDetailsService do
+RSpec.describe ErrorTracking::IssueDetailsService do
include_context 'sentry error tracking context'
subject { described_class.new(project, user, params) }
diff --git a/spec/services/error_tracking/issue_latest_event_service_spec.rb b/spec/services/error_tracking/issue_latest_event_service_spec.rb
index 078d7511850..b7560762ae4 100644
--- a/spec/services/error_tracking/issue_latest_event_service_spec.rb
+++ b/spec/services/error_tracking/issue_latest_event_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ErrorTracking::IssueLatestEventService do
+RSpec.describe ErrorTracking::IssueLatestEventService do
include_context 'sentry error tracking context'
subject { described_class.new(project, user) }
diff --git a/spec/services/error_tracking/issue_update_service_spec.rb b/spec/services/error_tracking/issue_update_service_spec.rb
index a13d42ec141..9ed24038ed8 100644
--- a/spec/services/error_tracking/issue_update_service_spec.rb
+++ b/spec/services/error_tracking/issue_update_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ErrorTracking::IssueUpdateService do
+RSpec.describe ErrorTracking::IssueUpdateService do
include_context 'sentry error tracking context'
let(:arguments) { { issue_id: non_existing_record_id, status: 'resolved' } }
diff --git a/spec/services/error_tracking/list_issues_service_spec.rb b/spec/services/error_tracking/list_issues_service_spec.rb
index 5f6e071e10d..518f2a80826 100644
--- a/spec/services/error_tracking/list_issues_service_spec.rb
+++ b/spec/services/error_tracking/list_issues_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ErrorTracking::ListIssuesService do
+RSpec.describe ErrorTracking::ListIssuesService do
include_context 'sentry error tracking context'
let(:params) { { search_term: 'something', sort: 'last_seen', cursor: 'some-cursor' } }
diff --git a/spec/services/error_tracking/list_projects_service_spec.rb b/spec/services/error_tracking/list_projects_service_spec.rb
index 565610c64ac..8bc632349fa 100644
--- a/spec/services/error_tracking/list_projects_service_spec.rb
+++ b/spec/services/error_tracking/list_projects_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ErrorTracking::ListProjectsService do
+RSpec.describe ErrorTracking::ListProjectsService do
let_it_be(:user) { create(:user) }
let_it_be(:project, reload: true) { create(:project) }
diff --git a/spec/services/event_create_service_spec.rb b/spec/services/event_create_service_spec.rb
index 73c089334ed..d10ed7d6640 100644
--- a/spec/services/event_create_service_spec.rb
+++ b/spec/services/event_create_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe EventCreateService do
+RSpec.describe EventCreateService do
let(:service) { described_class.new }
let_it_be(:user, reload: true) { create :user }
@@ -16,7 +16,6 @@ describe EventCreateService do
it "creates new event" do
expect { service.open_issue(issue, issue.author) }.to change { Event.count }
- expect { service.open_issue(issue, issue.author) }.to change { ResourceStateEvent.count }
end
end
@@ -27,7 +26,6 @@ describe EventCreateService do
it "creates new event" do
expect { service.close_issue(issue, issue.author) }.to change { Event.count }
- expect { service.close_issue(issue, issue.author) }.to change { ResourceStateEvent.count }
end
end
@@ -38,7 +36,6 @@ describe EventCreateService do
it "creates new event" do
expect { service.reopen_issue(issue, issue.author) }.to change { Event.count }
- expect { service.reopen_issue(issue, issue.author) }.to change { ResourceStateEvent.count }
end
end
end
@@ -51,7 +48,6 @@ describe EventCreateService do
it "creates new event" do
expect { service.open_mr(merge_request, merge_request.author) }.to change { Event.count }
- expect { service.open_mr(merge_request, merge_request.author) }.to change { ResourceStateEvent.count }
end
end
@@ -62,7 +58,6 @@ describe EventCreateService do
it "creates new event" do
expect { service.close_mr(merge_request, merge_request.author) }.to change { Event.count }
- expect { service.close_mr(merge_request, merge_request.author) }.to change { ResourceStateEvent.count }
end
end
@@ -73,7 +68,6 @@ describe EventCreateService do
it "creates new event" do
expect { service.merge_mr(merge_request, merge_request.author) }.to change { Event.count }
- expect { service.merge_mr(merge_request, merge_request.author) }.to change { ResourceStateEvent.count }
end
end
@@ -84,7 +78,18 @@ describe EventCreateService do
it "creates new event" do
expect { service.reopen_mr(merge_request, merge_request.author) }.to change { Event.count }
- expect { service.reopen_mr(merge_request, merge_request.author) }.to change { ResourceStateEvent.count }
+ end
+ end
+
+ describe '#approve_mr' do
+ let(:merge_request) { create(:merge_request) }
+
+ it { expect(service.approve_mr(merge_request, user)).to be_truthy }
+
+ it 'creates new event' do
+ service.approve_mr(merge_request, user)
+
+ change { Event.approved_action.where(target: merge_request).count }.by(1)
end
end
end
@@ -161,7 +166,7 @@ describe EventCreateService do
end
end
- describe '#wiki_event' do
+ describe '#wiki_event', :clean_gitlab_redis_shared_state do
let_it_be(:user) { create(:user) }
let_it_be(:wiki_page) { create(:wiki_page) }
let_it_be(:meta) { create(:wiki_page_meta, :for_wiki_page, wiki_page: wiki_page) }
@@ -181,6 +186,16 @@ describe EventCreateService do
)
end
+ it 'records the event in the event counter' do
+ stub_feature_flags(Gitlab::UsageDataCounters::TrackUniqueActions::FEATURE_FLAG => true)
+ counter_class = Gitlab::UsageDataCounters::TrackUniqueActions
+ tracking_params = { event_action: counter_class::WIKI_ACTION, date_from: Date.yesterday, date_to: Date.today }
+
+ expect { event }
+ .to change { counter_class.count_unique_events(tracking_params) }
+ .from(0).to(1)
+ end
+
it 'is idempotent', :aggregate_failures do
expect { event }.to change(Event, :count).by(1)
duplicate = nil
@@ -188,16 +203,6 @@ describe EventCreateService do
expect(duplicate).to eq(event)
end
-
- context 'the feature is disabled' do
- before do
- stub_feature_flags(wiki_events: false)
- end
-
- it 'does not create the event' do
- expect { event }.not_to change(Event, :count)
- end
- end
end
end
@@ -229,6 +234,16 @@ describe EventCreateService do
subject { service.push(project, user, push_data) }
it_behaves_like 'service for creating a push event', PushEventPayloadService
+
+ it 'records the event in the event counter' do
+ stub_feature_flags(Gitlab::UsageDataCounters::TrackUniqueActions::FEATURE_FLAG => true)
+ counter_class = Gitlab::UsageDataCounters::TrackUniqueActions
+ tracking_params = { event_action: counter_class::PUSH_ACTION, date_from: Date.yesterday, date_to: Date.today }
+
+ expect { subject }
+ .to change { counter_class.count_unique_events(tracking_params) }
+ .from(0).to(1)
+ end
end
describe '#bulk_push', :clean_gitlab_redis_shared_state do
@@ -243,6 +258,16 @@ describe EventCreateService do
subject { service.bulk_push(project, user, push_data) }
it_behaves_like 'service for creating a push event', BulkPushEventPayloadService
+
+ it 'records the event in the event counter' do
+ stub_feature_flags(Gitlab::UsageDataCounters::TrackUniqueActions::FEATURE_FLAG => true)
+ counter_class = Gitlab::UsageDataCounters::TrackUniqueActions
+ tracking_params = { event_action: counter_class::PUSH_ACTION, date_from: Date.yesterday, date_to: Date.today }
+
+ expect { subject }
+ .to change { counter_class.count_unique_events(tracking_params) }
+ .from(0).to(1)
+ end
end
describe 'Project' do
@@ -261,31 +286,10 @@ describe EventCreateService do
end
end
- describe 'design events' do
+ describe 'design events', :clean_gitlab_redis_shared_state do
let_it_be(:design) { create(:design, project: project) }
let_it_be(:author) { user }
- shared_examples 'feature flag gated multiple event creation' do
- context 'the feature flag is off' do
- before do
- stub_feature_flags(design_activity_events: false)
- end
-
- specify { expect(result).to be_empty }
- specify { expect { result }.not_to change { Event.count } }
- specify { expect { result }.not_to exceed_query_limit(0) }
- end
-
- context 'the feature flag is enabled for a single project' do
- before do
- stub_feature_flags(design_activity_events: project)
- end
-
- specify { expect(result).not_to be_empty }
- specify { expect { result }.to change { Event.count }.by(1) }
- end
- end
-
describe '#save_designs' do
let_it_be(:updated) { create_list(:design, 5) }
let_it_be(:created) { create_list(:design, 3) }
@@ -310,8 +314,14 @@ describe EventCreateService do
expect(events.map(&:design)).to match_array(updated)
end
- it_behaves_like 'feature flag gated multiple event creation' do
- let(:project) { created.first.project }
+ it 'records the event in the event counter' do
+ stub_feature_flags(Gitlab::UsageDataCounters::TrackUniqueActions::FEATURE_FLAG => true)
+ counter_class = Gitlab::UsageDataCounters::TrackUniqueActions
+ tracking_params = { event_action: counter_class::DESIGN_ACTION, date_from: Date.yesterday, date_to: Date.today }
+
+ expect { result }
+ .to change { counter_class.count_unique_events(tracking_params) }
+ .from(0).to(1)
end
end
@@ -332,8 +342,14 @@ describe EventCreateService do
expect(events.map(&:design)).to match_array(designs)
end
- it_behaves_like 'feature flag gated multiple event creation' do
- let(:project) { designs.first.project }
+ it 'records the event in the event counter' do
+ stub_feature_flags(Gitlab::UsageDataCounters::TrackUniqueActions::FEATURE_FLAG => true)
+ counter_class = Gitlab::UsageDataCounters::TrackUniqueActions
+ tracking_params = { event_action: counter_class::DESIGN_ACTION, date_from: Date.yesterday, date_to: Date.today }
+
+ expect { result }
+ .to change { counter_class.count_unique_events(tracking_params) }
+ .from(0).to(1)
end
end
end
diff --git a/spec/services/events/render_service_spec.rb b/spec/services/events/render_service_spec.rb
index a623a05a56d..24a3b9abe14 100644
--- a/spec/services/events/render_service_spec.rb
+++ b/spec/services/events/render_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Events::RenderService do
+RSpec.describe Events::RenderService do
describe '#execute' do
let!(:note) { build(:note) }
let!(:event) { build(:event, target: note, project: note.project) }
diff --git a/spec/services/files/create_service_spec.rb b/spec/services/files/create_service_spec.rb
index 195f56a2909..3b3dbd1fcfe 100644
--- a/spec/services/files/create_service_spec.rb
+++ b/spec/services/files/create_service_spec.rb
@@ -2,7 +2,7 @@
require "spec_helper"
-describe Files::CreateService do
+RSpec.describe Files::CreateService do
let(:project) { create(:project, :repository) }
let(:repository) { project.repository }
let(:user) { create(:user, :commit_email) }
diff --git a/spec/services/files/delete_service_spec.rb b/spec/services/files/delete_service_spec.rb
index b849def06fc..17e4645fde6 100644
--- a/spec/services/files/delete_service_spec.rb
+++ b/spec/services/files/delete_service_spec.rb
@@ -2,7 +2,7 @@
require "spec_helper"
-describe Files::DeleteService do
+RSpec.describe Files::DeleteService do
subject { described_class.new(project, user, commit_params) }
let(:project) { create(:project, :repository) }
diff --git a/spec/services/files/multi_service_spec.rb b/spec/services/files/multi_service_spec.rb
index 0f51c72019e..6a5c7d2749d 100644
--- a/spec/services/files/multi_service_spec.rb
+++ b/spec/services/files/multi_service_spec.rb
@@ -2,7 +2,7 @@
require "spec_helper"
-describe Files::MultiService do
+RSpec.describe Files::MultiService do
subject { described_class.new(project, user, commit_params) }
let(:project) { create(:project, :repository) }
diff --git a/spec/services/files/update_service_spec.rb b/spec/services/files/update_service_spec.rb
index 37869b176ef..84d78b4c2bc 100644
--- a/spec/services/files/update_service_spec.rb
+++ b/spec/services/files/update_service_spec.rb
@@ -2,7 +2,7 @@
require "spec_helper"
-describe Files::UpdateService do
+RSpec.describe Files::UpdateService do
subject { described_class.new(project, user, commit_params) }
let(:project) { create(:project, :repository) }
diff --git a/spec/services/git/base_hooks_service_spec.rb b/spec/services/git/base_hooks_service_spec.rb
index 07ce560bd88..661c77b56bb 100644
--- a/spec/services/git/base_hooks_service_spec.rb
+++ b/spec/services/git/base_hooks_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Git::BaseHooksService do
+RSpec.describe Git::BaseHooksService do
include RepoHelpers
include GitHelpers
diff --git a/spec/services/git/branch_hooks_service_spec.rb b/spec/services/git/branch_hooks_service_spec.rb
index 908b9772c40..7f22af8bfc6 100644
--- a/spec/services/git/branch_hooks_service_spec.rb
+++ b/spec/services/git/branch_hooks_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Git::BranchHooksService do
+RSpec.describe Git::BranchHooksService do
include RepoHelpers
include ProjectForksHelper
diff --git a/spec/services/git/branch_push_service_spec.rb b/spec/services/git/branch_push_service_spec.rb
index 6ecc1a62ff3..6ccf2d03e4a 100644
--- a/spec/services/git/branch_push_service_spec.rb
+++ b/spec/services/git/branch_push_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Git::BranchPushService, services: true do
+RSpec.describe Git::BranchPushService, services: true do
include RepoHelpers
let_it_be(:user) { create(:user) }
@@ -635,6 +635,37 @@ describe Git::BranchPushService, services: true do
end
end
+ describe 'artifacts' do
+ context 'create branch' do
+ let(:oldrev) { blankrev }
+
+ it 'does nothing' do
+ expect(::Ci::RefDeleteUnlockArtifactsWorker).not_to receive(:perform_async)
+
+ execute_service(project, user, oldrev: oldrev, newrev: newrev, ref: ref)
+ end
+ end
+
+ context 'update branch' do
+ it 'does nothing' do
+ expect(::Ci::RefDeleteUnlockArtifactsWorker).not_to receive(:perform_async)
+
+ execute_service(project, user, oldrev: oldrev, newrev: newrev, ref: ref)
+ end
+ end
+
+ context 'delete branch' do
+ let(:newrev) { blankrev }
+
+ it 'unlocks artifacts' do
+ expect(::Ci::RefDeleteUnlockArtifactsWorker)
+ .to receive(:perform_async).with(project.id, user.id, "refs/heads/#{branch}")
+
+ execute_service(project, user, oldrev: oldrev, newrev: newrev, ref: ref)
+ end
+ end
+ end
+
describe 'Hooks' do
context 'run on a branch' do
it 'delegates to Git::BranchHooksService' do
diff --git a/spec/services/git/process_ref_changes_service_spec.rb b/spec/services/git/process_ref_changes_service_spec.rb
index 924e913a9ec..c2fb40a0ed0 100644
--- a/spec/services/git/process_ref_changes_service_spec.rb
+++ b/spec/services/git/process_ref_changes_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Git::ProcessRefChangesService do
+RSpec.describe Git::ProcessRefChangesService do
let(:project) { create(:project, :repository) }
let(:user) { project.owner }
let(:params) { { changes: git_changes } }
diff --git a/spec/services/git/tag_hooks_service_spec.rb b/spec/services/git/tag_hooks_service_spec.rb
index 094ccd8c9f0..4443c46a414 100644
--- a/spec/services/git/tag_hooks_service_spec.rb
+++ b/spec/services/git/tag_hooks_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Git::TagHooksService, :service do
+RSpec.describe Git::TagHooksService, :service do
let(:user) { create(:user) }
let(:project) { create(:project, :repository) }
diff --git a/spec/services/git/tag_push_service_spec.rb b/spec/services/git/tag_push_service_spec.rb
index 9688041c08c..87dbf79a245 100644
--- a/spec/services/git/tag_push_service_spec.rb
+++ b/spec/services/git/tag_push_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Git::TagPushService do
+RSpec.describe Git::TagPushService do
include RepoHelpers
include GitHelpers
@@ -10,9 +10,11 @@ describe Git::TagPushService do
let(:project) { create(:project, :repository) }
let(:service) { described_class.new(project, user, change: { oldrev: oldrev, newrev: newrev, ref: ref }) }
- let(:oldrev) { Gitlab::Git::BLANK_SHA }
+ let(:blankrev) { Gitlab::Git::BLANK_SHA }
+ let(:oldrev) { blankrev }
let(:newrev) { "8a2a6eb295bb170b34c24c76c49ed0e9b2eaf34b" } # gitlab-test: git rev-parse refs/tags/v1.1.0
- let(:ref) { 'refs/tags/v1.1.0' }
+ let(:tag) { 'v1.1.0' }
+ let(:ref) { "refs/tags/#{tag}" }
describe "Push tags" do
subject do
@@ -58,4 +60,35 @@ describe Git::TagPushService do
end
end
end
+
+ describe 'artifacts' do
+ context 'create tag' do
+ let(:oldrev) { blankrev }
+
+ it 'does nothing' do
+ expect(::Ci::RefDeleteUnlockArtifactsWorker).not_to receive(:perform_async)
+
+ service.execute
+ end
+ end
+
+ context 'update tag' do
+ it 'does nothing' do
+ expect(::Ci::RefDeleteUnlockArtifactsWorker).not_to receive(:perform_async)
+
+ service.execute
+ end
+ end
+
+ context 'delete tag' do
+ let(:newrev) { blankrev }
+
+ it 'unlocks artifacts' do
+ expect(::Ci::RefDeleteUnlockArtifactsWorker)
+ .to receive(:perform_async).with(project.id, user.id, "refs/tags/#{tag}")
+
+ service.execute
+ end
+ end
+ end
end
diff --git a/spec/services/git/wiki_push_service/change_spec.rb b/spec/services/git/wiki_push_service/change_spec.rb
index 4da3f0fc738..3616bf62b20 100644
--- a/spec/services/git/wiki_push_service/change_spec.rb
+++ b/spec/services/git/wiki_push_service/change_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Git::WikiPushService::Change do
+RSpec.describe Git::WikiPushService::Change do
subject { described_class.new(project_wiki, change, raw_change) }
let(:project_wiki) { double('ProjectWiki') }
diff --git a/spec/services/git/wiki_push_service_spec.rb b/spec/services/git/wiki_push_service_spec.rb
index b2234c81c24..f338b7a5709 100644
--- a/spec/services/git/wiki_push_service_spec.rb
+++ b/spec/services/git/wiki_push_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Git::WikiPushService, services: true do
+RSpec.describe Git::WikiPushService, services: true do
include RepoHelpers
let_it_be(:key_id) { create(:key, user: current_user).shell_id }
@@ -247,14 +247,6 @@ describe Git::WikiPushService, services: true do
end
end
- context 'the wiki_events feature is disabled' do
- before do
- stub_feature_flags(wiki_events: false)
- end
-
- it_behaves_like 'a no-op push'
- end
-
context 'the wiki_events_on_git_push feature is disabled' do
before do
stub_feature_flags(wiki_events_on_git_push: false)
diff --git a/spec/services/gpg_keys/create_service_spec.rb b/spec/services/gpg_keys/create_service_spec.rb
index 8dfc9f19439..9ac56355b4b 100644
--- a/spec/services/gpg_keys/create_service_spec.rb
+++ b/spec/services/gpg_keys/create_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GpgKeys::CreateService do
+RSpec.describe GpgKeys::CreateService do
let(:user) { create(:user) }
let(:params) { attributes_for(:gpg_key) }
diff --git a/spec/services/gpg_keys/destroy_service_spec.rb b/spec/services/gpg_keys/destroy_service_spec.rb
new file mode 100644
index 00000000000..b9aa3e351c9
--- /dev/null
+++ b/spec/services/gpg_keys/destroy_service_spec.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe GpgKeys::DestroyService do
+ let(:user) { create(:user) }
+
+ subject { described_class.new(user) }
+
+ it 'destroys the GPG key' do
+ gpg_key = create(:gpg_key)
+
+ expect { subject.execute(gpg_key) }.to change(GpgKey, :count).by(-1)
+ end
+end
diff --git a/spec/services/grafana/proxy_service_spec.rb b/spec/services/grafana/proxy_service_spec.rb
index 8cb7210524a..7ddc31d45d9 100644
--- a/spec/services/grafana/proxy_service_spec.rb
+++ b/spec/services/grafana/proxy_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Grafana::ProxyService do
+RSpec.describe Grafana::ProxyService do
include ReactiveCachingHelpers
let_it_be(:project) { create(:project) }
diff --git a/spec/services/gravatar_service_spec.rb b/spec/services/gravatar_service_spec.rb
index 9ce1df0f76f..a6418b02f78 100644
--- a/spec/services/gravatar_service_spec.rb
+++ b/spec/services/gravatar_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GravatarService do
+RSpec.describe GravatarService do
describe '#execute' do
let(:url) { 'http://example.com/avatar?hash=%{hash}&size=%{size}&email=%{email}&username=%{username}' }
diff --git a/spec/services/groups/auto_devops_service_spec.rb b/spec/services/groups/auto_devops_service_spec.rb
index 63fbdc70c1b..3d89ee96823 100644
--- a/spec/services/groups/auto_devops_service_spec.rb
+++ b/spec/services/groups/auto_devops_service_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe Groups::AutoDevopsService, '#execute' do
+RSpec.describe Groups::AutoDevopsService, '#execute' do
let_it_be(:group) { create(:group) }
let_it_be(:user) { create(:user) }
let(:group_params) { { auto_devops_enabled: '0' } }
diff --git a/spec/services/groups/create_service_spec.rb b/spec/services/groups/create_service_spec.rb
index c0e876cce33..fc877f45a39 100644
--- a/spec/services/groups/create_service_spec.rb
+++ b/spec/services/groups/create_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Groups::CreateService, '#execute' do
+RSpec.describe Groups::CreateService, '#execute' do
let!(:user) { create(:user) }
let!(:group_params) { { path: "group_path", visibility_level: Gitlab::VisibilityLevel::PUBLIC } }
@@ -129,4 +129,13 @@ describe Groups::CreateService, '#execute' do
expect { subject }.to change { ChatTeam.count }.from(0).to(1)
end
end
+
+ describe 'creating a setting record' do
+ let(:service) { described_class.new(user, group_params) }
+
+ it 'create the settings record connected to the group' do
+ group = subject
+ expect(group.namespace_settings).to be_persisted
+ end
+ end
end
diff --git a/spec/services/groups/deploy_tokens/create_service_spec.rb b/spec/services/groups/deploy_tokens/create_service_spec.rb
index 20c609bc828..0c28075f998 100644
--- a/spec/services/groups/deploy_tokens/create_service_spec.rb
+++ b/spec/services/groups/deploy_tokens/create_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Groups::DeployTokens::CreateService do
+RSpec.describe Groups::DeployTokens::CreateService do
it_behaves_like 'a deploy token creation service' do
let(:entity) { create(:group) }
let(:deploy_token_class) { GroupDeployToken }
diff --git a/spec/services/groups/deploy_tokens/destroy_service_spec.rb b/spec/services/groups/deploy_tokens/destroy_service_spec.rb
index d4ef5963558..28e60b12993 100644
--- a/spec/services/groups/deploy_tokens/destroy_service_spec.rb
+++ b/spec/services/groups/deploy_tokens/destroy_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Groups::DeployTokens::DestroyService do
+RSpec.describe Groups::DeployTokens::DestroyService do
it_behaves_like 'a deploy token deletion service' do
let_it_be(:entity) { create(:group) }
let_it_be(:deploy_token_class) { GroupDeployToken }
diff --git a/spec/services/groups/destroy_service_spec.rb b/spec/services/groups/destroy_service_spec.rb
index bf639153b99..31afdba8192 100644
--- a/spec/services/groups/destroy_service_spec.rb
+++ b/spec/services/groups/destroy_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Groups::DestroyService do
+RSpec.describe Groups::DestroyService do
include DatabaseConnectionHelpers
let!(:user) { create(:user) }
diff --git a/spec/services/groups/group_links/create_service_spec.rb b/spec/services/groups/group_links/create_service_spec.rb
index 36faa69577e..bca03863d1e 100644
--- a/spec/services/groups/group_links/create_service_spec.rb
+++ b/spec/services/groups/group_links/create_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Groups::GroupLinks::CreateService, '#execute' do
+RSpec.describe Groups::GroupLinks::CreateService, '#execute' do
let(:parent_group_user) { create(:user) }
let(:group_user) { create(:user) }
let(:child_group_user) { create(:user) }
diff --git a/spec/services/groups/group_links/destroy_service_spec.rb b/spec/services/groups/group_links/destroy_service_spec.rb
index 8989f024262..22fe8a1d58b 100644
--- a/spec/services/groups/group_links/destroy_service_spec.rb
+++ b/spec/services/groups/group_links/destroy_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Groups::GroupLinks::DestroyService, '#execute' do
+RSpec.describe Groups::GroupLinks::DestroyService, '#execute' do
let(:user) { create(:user) }
let_it_be(:group) { create(:group, :private) }
diff --git a/spec/services/groups/group_links/update_service_spec.rb b/spec/services/groups/group_links/update_service_spec.rb
index 446364c9799..e4ff83d7926 100644
--- a/spec/services/groups/group_links/update_service_spec.rb
+++ b/spec/services/groups/group_links/update_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Groups::GroupLinks::UpdateService, '#execute' do
+RSpec.describe Groups::GroupLinks::UpdateService, '#execute' do
let(:user) { create(:user) }
let_it_be(:group) { create(:group, :private) }
diff --git a/spec/services/groups/import_export/export_service_spec.rb b/spec/services/groups/import_export/export_service_spec.rb
index ea49b26cc7c..690bcb94556 100644
--- a/spec/services/groups/import_export/export_service_spec.rb
+++ b/spec/services/groups/import_export/export_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Groups::ImportExport::ExportService do
+RSpec.describe Groups::ImportExport::ExportService do
describe '#async_execute' do
let(:user) { create(:user) }
let(:group) { create(:group) }
diff --git a/spec/services/groups/import_export/import_service_spec.rb b/spec/services/groups/import_export/import_service_spec.rb
index 1f7eaccbdbd..4aac602a6da 100644
--- a/spec/services/groups/import_export/import_service_spec.rb
+++ b/spec/services/groups/import_export/import_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Groups::ImportExport::ImportService do
+RSpec.describe Groups::ImportExport::ImportService do
describe '#async_execute' do
let_it_be(:user) { create(:user) }
let_it_be(:group) { create(:group) }
diff --git a/spec/services/groups/nested_create_service_spec.rb b/spec/services/groups/nested_create_service_spec.rb
index b30392c1b12..a43c1d8d9c3 100644
--- a/spec/services/groups/nested_create_service_spec.rb
+++ b/spec/services/groups/nested_create_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Groups::NestedCreateService do
+RSpec.describe Groups::NestedCreateService do
let(:user) { create(:user) }
subject(:service) { described_class.new(user, params) }
diff --git a/spec/services/groups/transfer_service_spec.rb b/spec/services/groups/transfer_service_spec.rb
index d7f6bececfe..fa254bba6a9 100644
--- a/spec/services/groups/transfer_service_spec.rb
+++ b/spec/services/groups/transfer_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Groups::TransferService do
+RSpec.describe Groups::TransferService do
let(:user) { create(:user) }
let(:new_parent_group) { create(:group, :public) }
let!(:group_member) { create(:group_member, :owner, group: group, user: user) }
diff --git a/spec/services/groups/update_service_spec.rb b/spec/services/groups/update_service_spec.rb
index b17d78505d1..25c79d9e600 100644
--- a/spec/services/groups/update_service_spec.rb
+++ b/spec/services/groups/update_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Groups::UpdateService do
+RSpec.describe Groups::UpdateService do
let!(:user) { create(:user) }
let!(:private_group) { create(:group, :private) }
let!(:internal_group) { create(:group, :internal) }
diff --git a/spec/services/groups/update_shared_runners_service_spec.rb b/spec/services/groups/update_shared_runners_service_spec.rb
new file mode 100644
index 00000000000..9fd8477a455
--- /dev/null
+++ b/spec/services/groups/update_shared_runners_service_spec.rb
@@ -0,0 +1,230 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Groups::UpdateSharedRunnersService do
+ let(:user) { create(:user) }
+ let(:group) { create(:group) }
+ let(:params) { {} }
+
+ describe '#execute' do
+ subject { described_class.new(group, user, params).execute }
+
+ context 'when current_user is not the group owner' do
+ let_it_be(:group) { create(:group) }
+
+ let(:params) { { shared_runners_enabled: '0' } }
+
+ before do
+ group.add_maintainer(user)
+ end
+
+ it 'results error and does not call any method' do
+ expect(group).not_to receive(:enable_shared_runners!)
+ expect(group).not_to receive(:disable_shared_runners!)
+ expect(group).not_to receive(:allow_descendants_override_disabled_shared_runners!)
+ expect(group).not_to receive(:disallow_descendants_override_disabled_shared_runners!)
+
+ expect(subject[:status]).to eq(:error)
+ expect(subject[:message]).to eq('Operation not allowed')
+ expect(subject[:http_status]).to eq(403)
+ end
+ end
+
+ context 'when current_user is the group owner' do
+ before do
+ group.add_owner(user)
+ end
+
+ context 'enable shared Runners' do
+ where(:desired_params) do
+ ['1', true]
+ end
+
+ with_them do
+ let(:params) { { shared_runners_enabled: desired_params } }
+
+ context 'group that its ancestors have shared runners disabled' do
+ let_it_be(:parent) { create(:group, :shared_runners_disabled) }
+ let_it_be(:group) { create(:group, :shared_runners_disabled, parent: parent) }
+
+ it 'results error' do
+ expect(subject[:status]).to eq(:error)
+ expect(subject[:message]).to eq('Shared Runners disabled for the parent group')
+ end
+ end
+
+ context 'root group with shared runners disabled' do
+ let_it_be(:group) { create(:group, :shared_runners_disabled) }
+
+ it 'receives correct method and succeeds' do
+ expect(group).to receive(:enable_shared_runners!)
+ expect(group).not_to receive(:disable_shared_runners!)
+ expect(group).not_to receive(:allow_descendants_override_disabled_shared_runners!)
+ expect(group).not_to receive(:disallow_descendants_override_disabled_shared_runners!)
+
+ expect(subject[:status]).to eq(:success)
+ end
+ end
+ end
+ end
+
+ context 'disable shared Runners' do
+ let_it_be(:group) { create(:group) }
+
+ where(:desired_params) do
+ ['0', false]
+ end
+
+ with_them do
+ let(:params) { { shared_runners_enabled: desired_params } }
+
+ it 'receives correct method and succeeds' do
+ expect(group).to receive(:disable_shared_runners!)
+ expect(group).not_to receive(:enable_shared_runners!)
+ expect(group).not_to receive(:allow_descendants_override_disabled_shared_runners!)
+ expect(group).not_to receive(:disallow_descendants_override_disabled_shared_runners!)
+
+ expect(subject[:status]).to eq(:success)
+ end
+ end
+ end
+
+ context 'allow descendants to override' do
+ where(:desired_params) do
+ ['1', true]
+ end
+
+ with_them do
+ let(:params) { { allow_descendants_override_disabled_shared_runners: desired_params } }
+
+ context 'top level group' do
+ let_it_be(:group) { create(:group, :shared_runners_disabled) }
+
+ it 'receives correct method and succeeds' do
+ expect(group).to receive(:allow_descendants_override_disabled_shared_runners!)
+ expect(group).not_to receive(:disallow_descendants_override_disabled_shared_runners!)
+ expect(group).not_to receive(:enable_shared_runners!)
+ expect(group).not_to receive(:disable_shared_runners!)
+
+ expect(subject[:status]).to eq(:success)
+ end
+ end
+
+ context 'when parent does not allow' do
+ let_it_be(:parent) { create(:group, :shared_runners_disabled, allow_descendants_override_disabled_shared_runners: false ) }
+ let_it_be(:group) { create(:group, :shared_runners_disabled, allow_descendants_override_disabled_shared_runners: false, parent: parent) }
+
+ it 'results error' do
+ expect(subject[:status]).to eq(:error)
+ expect(subject[:message]).to eq('Group level shared Runners not allowed')
+ end
+ end
+ end
+ end
+
+ context 'disallow descendants to override' do
+ where(:desired_params) do
+ ['0', false]
+ end
+
+ with_them do
+ let(:params) { { allow_descendants_override_disabled_shared_runners: desired_params } }
+
+ context 'top level group' do
+ let_it_be(:group) { create(:group, :shared_runners_disabled, :allow_descendants_override_disabled_shared_runners ) }
+
+ it 'receives correct method and succeeds' do
+ expect(group).to receive(:disallow_descendants_override_disabled_shared_runners!)
+ expect(group).not_to receive(:allow_descendants_override_disabled_shared_runners!)
+ expect(group).not_to receive(:enable_shared_runners!)
+ expect(group).not_to receive(:disable_shared_runners!)
+
+ expect(subject[:status]).to eq(:success)
+ end
+ end
+
+ context 'top level group that has shared Runners enabled' do
+ let_it_be(:group) { create(:group, shared_runners_enabled: true) }
+
+ it 'results error' do
+ expect(subject[:status]).to eq(:error)
+ expect(subject[:message]).to eq('Shared Runners enabled')
+ end
+ end
+ end
+ end
+
+ context 'both params are present' do
+ context 'shared_runners_enabled: 1 and allow_descendants_override_disabled_shared_runners' do
+ let_it_be(:group) { create(:group, :shared_runners_disabled) }
+ let_it_be(:sub_group) { create(:group, :shared_runners_disabled, parent: group) }
+ let_it_be(:project) { create(:project, shared_runners_enabled: false, group: sub_group) }
+
+ where(:allow_descendants_override) do
+ ['1', true, '0', false]
+ end
+
+ with_them do
+ let(:params) { { shared_runners_enabled: '1', allow_descendants_override_disabled_shared_runners: allow_descendants_override } }
+
+ it 'results in an error because shared Runners are enabled' do
+ expect { subject }
+ .to not_change { group.reload.shared_runners_enabled }
+ .and not_change { sub_group.reload.shared_runners_enabled }
+ .and not_change { project.reload.shared_runners_enabled }
+ .and not_change { group.reload.allow_descendants_override_disabled_shared_runners }
+ .and not_change { sub_group.reload.allow_descendants_override_disabled_shared_runners }
+ expect(subject[:status]).to eq(:error)
+ expect(subject[:message]).to eq('Cannot set shared_runners_enabled to true and allow_descendants_override_disabled_shared_runners')
+ end
+ end
+ end
+
+ context 'shared_runners_enabled: 0 and allow_descendants_override_disabled_shared_runners: 0' do
+ let_it_be(:group) { create(:group, :allow_descendants_override_disabled_shared_runners) }
+ let_it_be(:sub_group) { create(:group, :shared_runners_disabled, :allow_descendants_override_disabled_shared_runners, parent: group) }
+ let_it_be(:sub_group_2) { create(:group, parent: group) }
+ let_it_be(:project) { create(:project, group: group, shared_runners_enabled: true) }
+ let_it_be(:project_2) { create(:project, group: sub_group_2, shared_runners_enabled: true) }
+
+ let(:params) { { shared_runners_enabled: '0', allow_descendants_override_disabled_shared_runners: '0' } }
+
+ it 'disables shared Runners and disable allow_descendants_override_disabled_shared_runners' do
+ expect { subject }
+ .to change { group.reload.shared_runners_enabled }.from(true).to(false)
+ .and change { group.reload.allow_descendants_override_disabled_shared_runners }.from(true).to(false)
+ .and not_change { sub_group.reload.shared_runners_enabled }
+ .and change { sub_group.reload.allow_descendants_override_disabled_shared_runners }.from(true).to(false)
+ .and change { sub_group_2.reload.shared_runners_enabled }.from(true).to(false)
+ .and not_change { sub_group_2.reload.allow_descendants_override_disabled_shared_runners }
+ .and change { project.reload.shared_runners_enabled }.from(true).to(false)
+ .and change { project_2.reload.shared_runners_enabled }.from(true).to(false)
+ end
+ end
+
+ context 'shared_runners_enabled: 0 and allow_descendants_override_disabled_shared_runners: 1' do
+ let_it_be(:group) { create(:group) }
+ let_it_be(:sub_group) { create(:group, :shared_runners_disabled, parent: group) }
+ let_it_be(:sub_group_2) { create(:group, parent: group) }
+ let_it_be(:project) { create(:project, group: group, shared_runners_enabled: true) }
+ let_it_be(:project_2) { create(:project, group: sub_group_2, shared_runners_enabled: true) }
+
+ let(:params) { { shared_runners_enabled: '0', allow_descendants_override_disabled_shared_runners: '1' } }
+
+ it 'disables shared Runners and enable allow_descendants_override_disabled_shared_runners only for itself' do
+ expect { subject }
+ .to change { group.reload.shared_runners_enabled }.from(true).to(false)
+ .and change { group.reload.allow_descendants_override_disabled_shared_runners }.from(false).to(true)
+ .and not_change { sub_group.reload.shared_runners_enabled }
+ .and not_change { sub_group.reload.allow_descendants_override_disabled_shared_runners }
+ .and change { sub_group_2.reload.shared_runners_enabled }.from(true).to(false)
+ .and not_change { sub_group_2.reload.allow_descendants_override_disabled_shared_runners }
+ .and change { project.reload.shared_runners_enabled }.from(true).to(false)
+ .and change { project_2.reload.shared_runners_enabled }.from(true).to(false)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/spec/services/import/bitbucket_server_service_spec.rb b/spec/services/import/bitbucket_server_service_spec.rb
new file mode 100644
index 00000000000..c548e87b040
--- /dev/null
+++ b/spec/services/import/bitbucket_server_service_spec.rb
@@ -0,0 +1,113 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Import::BitbucketServerService do
+ let_it_be(:user) { create(:user) }
+ let(:base_uri) { "https://test:7990" }
+ let(:token) { "asdasd12345" }
+ let(:secret) { "sekrettt" }
+ let(:project_key) { 'TES' }
+ let(:repo_slug) { 'vim' }
+ let(:repo) do
+ {
+ name: 'vim',
+ description: 'test',
+ visibility_level: Gitlab::VisibilityLevel::PUBLIC,
+ browse_url: 'http://repo.com/repo/repo',
+ clone_url: 'http://repo.com/repo/repo.git'
+ }
+ end
+
+ let(:client) { double(BitbucketServer::Client) }
+
+ let(:credentials) { { base_uri: base_uri, user: user, password: token } }
+ let(:params) { { bitbucket_server_url: base_uri, bitbucket_server_username: user, personal_access_token: token, bitbucket_server_project: project_key, bitbucket_server_repo: repo_slug } }
+
+ subject { described_class.new(client, user, params) }
+
+ before do
+ allow(subject).to receive(:authorized?).and_return(true)
+ end
+
+ context 'when no repo is found' do
+ before do
+ allow(subject).to receive(:authorized?).and_return(true)
+ allow(client).to receive(:repo).and_return(nil)
+ end
+
+ it 'returns an error' do
+ result = subject.execute(credentials)
+
+ expect(result).to include(
+ message: "Project #{project_key}/#{repo_slug} could not be found",
+ status: :error,
+ http_status: :unprocessable_entity
+ )
+ end
+ end
+
+ context 'when user is unauthorized' do
+ before do
+ allow(subject).to receive(:authorized?).and_return(false)
+ end
+
+ it 'returns an error' do
+ result = subject.execute(credentials)
+
+ expect(result).to include(
+ message: "You don't have permissions to create this project",
+ status: :error,
+ http_status: :unauthorized
+ )
+ end
+ end
+
+ context 'verify url' do
+ shared_examples 'denies local request' do
+ before do
+ allow(client).to receive(:repo).with(project_key, repo_slug).and_return(double(repo))
+ end
+
+ it 'does not allow requests' do
+ result = subject.execute(credentials)
+ expect(result[:status]).to eq(:error)
+ expect(result[:message]).to include("Invalid URL:")
+ end
+ end
+
+ context 'when host is localhost' do
+ before do
+ allow(subject).to receive(:url).and_return('https://localhost:3000')
+ end
+
+ include_examples 'denies local request'
+ end
+
+ context 'when host is on local network' do
+ before do
+ allow(subject).to receive(:url).and_return('https://192.168.0.191')
+ end
+
+ include_examples 'denies local request'
+ end
+
+ context 'when host is ftp protocol' do
+ before do
+ allow(subject).to receive(:url).and_return('ftp://testing')
+ end
+
+ include_examples 'denies local request'
+ end
+ end
+
+ it 'raises an exception for unknown error causes' do
+ exception = StandardError.new('Not Implemented')
+
+ allow(client).to receive(:repo).and_raise(exception)
+
+ expect(Gitlab::Import::Logger).not_to receive(:error)
+
+ expect { subject.execute(credentials) }.to raise_error(exception)
+ end
+end
diff --git a/spec/services/import/github_service_spec.rb b/spec/services/import/github_service_spec.rb
index 461b17e0e33..266ff309662 100644
--- a/spec/services/import/github_service_spec.rb
+++ b/spec/services/import/github_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Import::GithubService do
+RSpec.describe Import::GithubService do
let_it_be(:user) { create(:user) }
let_it_be(:token) { 'complex-token' }
let_it_be(:access_params) { { github_access_token: 'github-complex-token' } }
diff --git a/spec/services/import_export_clean_up_service_spec.rb b/spec/services/import_export_clean_up_service_spec.rb
index 9f811f56f50..4101b13adf9 100644
--- a/spec/services/import_export_clean_up_service_spec.rb
+++ b/spec/services/import_export_clean_up_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ImportExportCleanUpService do
+RSpec.describe ImportExportCleanUpService do
describe '#execute' do
let(:service) { described_class.new }
diff --git a/spec/services/incident_management/create_incident_label_service_spec.rb b/spec/services/incident_management/create_incident_label_service_spec.rb
new file mode 100644
index 00000000000..2f11bcf397e
--- /dev/null
+++ b/spec/services/incident_management/create_incident_label_service_spec.rb
@@ -0,0 +1,58 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe IncidentManagement::CreateIncidentLabelService do
+ let_it_be(:project) { create(:project, :private) }
+ let_it_be(:user) { User.alert_bot }
+ let(:service) { described_class.new(project, user) }
+
+ subject(:execute) { service.execute }
+
+ describe 'execute' do
+ let(:title) { described_class::LABEL_PROPERTIES[:title] }
+ let(:color) { described_class::LABEL_PROPERTIES[:color] }
+ let(:description) { described_class::LABEL_PROPERTIES[:description] }
+
+ shared_examples 'existing label' do
+ it 'returns the existing label' do
+ expect { execute }.not_to change(Label, :count)
+
+ expect(execute).to be_success
+ expect(execute.payload).to eq(label: label)
+ end
+ end
+
+ shared_examples 'new label' do
+ it 'creates a new label' do
+ expect { execute }.to change(Label, :count).by(1)
+
+ label = project.reload.labels.last
+ expect(execute).to be_success
+ expect(execute.payload).to eq(label: label)
+ expect(label.title).to eq(title)
+ expect(label.color).to eq(color)
+ expect(label.description).to eq(description)
+ end
+ end
+
+ context 'with predefined project label' do
+ it_behaves_like 'existing label' do
+ let!(:label) { create(:label, project: project, title: title) }
+ end
+ end
+
+ context 'with predefined group label' do
+ let(:project) { create(:project, group: group) }
+ let(:group) { create(:group) }
+
+ it_behaves_like 'existing label' do
+ let!(:label) { create(:group_label, group: group, title: title) }
+ end
+ end
+
+ context 'without label' do
+ it_behaves_like 'new label'
+ end
+ end
+end
diff --git a/spec/services/incident_management/create_issue_service_spec.rb b/spec/services/incident_management/create_issue_service_spec.rb
index 5a3721f00b8..dab9a149458 100644
--- a/spec/services/incident_management/create_issue_service_spec.rb
+++ b/spec/services/incident_management/create_issue_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe IncidentManagement::CreateIssueService do
+RSpec.describe IncidentManagement::CreateIssueService do
let(:project) { create(:project, :repository, :private) }
let_it_be(:user) { User.alert_bot }
let(:service) { described_class.new(project, alert_payload) }
@@ -199,80 +199,7 @@ describe IncidentManagement::CreateIssueService do
end
describe "label `incident`" do
- let(:title) { 'incident' }
- let(:color) { '#CC0033' }
- let(:description) do
- <<~DESCRIPTION.chomp
- Denotes a disruption to IT services and \
- the associated issues require immediate attention
- DESCRIPTION
- end
-
- shared_examples 'existing label' do
- it 'adds the existing label' do
- expect { subject }.not_to change(Label, :count)
-
- expect(issue.labels).to eq([label])
- end
- end
-
- shared_examples 'new label' do
- it 'adds newly created label' do
- expect { subject }.to change(Label, :count).by(1)
-
- label = project.reload.labels.last
- expect(issue.labels).to eq([label])
- expect(label.title).to eq(title)
- expect(label.color).to eq(color)
- expect(label.description).to eq(description)
- end
- end
-
- context 'with predefined project label' do
- it_behaves_like 'existing label' do
- let!(:label) { create(:label, project: project, title: title) }
- end
- end
-
- context 'with predefined group label' do
- let(:project) { create(:project, group: group) }
- let(:group) { create(:group) }
-
- it_behaves_like 'existing label' do
- let!(:label) { create(:group_label, group: group, title: title) }
- end
- end
-
- context 'without label' do
- it_behaves_like 'new label'
- end
-
- context 'with duplicate labels', issue: 'https://gitlab.com/gitlab-org/gitlab-foss/issues/65042' do
- before do
- # Replicate race condition to create duplicates
- build(:label, project: project, title: title).save!(validate: false)
- build(:label, project: project, title: title).save!(validate: false)
- end
-
- it 'create an issue without labels' do
- # Verify we have duplicates
- expect(project.labels.size).to eq(2)
- expect(project.labels.map(&:title)).to all(eq(title))
-
- message = <<~MESSAGE.chomp
- Cannot create incident issue with labels ["#{title}"] for \
- "#{project.full_name}": Labels is invalid.
- Retrying without labels.
- MESSAGE
-
- expect(service)
- .to receive(:log_info)
- .with(message)
-
- expect(subject).to include(status: :success)
- expect(issue.labels).to be_empty
- end
- end
+ it_behaves_like 'create alert issue sets issue labels'
end
end
@@ -281,22 +208,12 @@ describe IncidentManagement::CreateIssueService do
setting.update!(create_issue: false)
end
- context 'when skip_settings_check is false (default)' do
- it 'returns an error' do
- expect(service)
- .to receive(:log_error)
- .with(error_message('setting disabled'))
+ it 'returns an error' do
+ expect(service)
+ .to receive(:log_error)
+ .with(error_message('setting disabled'))
- expect(subject).to eq(status: :error, message: 'setting disabled')
- end
- end
-
- context 'when skip_settings_check is true' do
- subject { service.execute(skip_settings_check: true) }
-
- it 'creates an issue' do
- expect { subject }.to change(Issue, :count).by(1)
- end
+ expect(subject).to eq(status: :error, message: 'setting disabled')
end
end
diff --git a/spec/services/incident_management/pager_duty/create_incident_issue_service_spec.rb b/spec/services/incident_management/pager_duty/create_incident_issue_service_spec.rb
new file mode 100644
index 00000000000..cf43ed2411d
--- /dev/null
+++ b/spec/services/incident_management/pager_duty/create_incident_issue_service_spec.rb
@@ -0,0 +1,104 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe IncidentManagement::PagerDuty::CreateIncidentIssueService do
+ let_it_be(:project, reload: true) { create(:project) }
+ let_it_be(:user) { User.alert_bot }
+ let(:webhook_payload) { Gitlab::Json.parse(fixture_file('pager_duty/webhook_incident_trigger.json')) }
+ let(:parsed_payload) { ::PagerDuty::WebhookPayloadParser.call(webhook_payload) }
+ let(:incident_payload) { parsed_payload.first['incident'] }
+
+ subject(:execute) { described_class.new(project, incident_payload).execute }
+
+ describe '#execute' do
+ context 'when pagerduty_webhook feature enabled' do
+ before do
+ stub_feature_flags(pagerduty_webhook: project)
+ end
+
+ context 'when PagerDuty webhook setting is active' do
+ let_it_be(:incident_management_setting) { create(:project_incident_management_setting, project: project, pagerduty_active: true) }
+
+ context 'when issue can be created' do
+ it 'creates a new issue' do
+ expect { execute }.to change(Issue, :count).by(1)
+ end
+
+ it 'responds with success' do
+ response = execute
+
+ expect(response).to be_success
+ expect(response.payload[:issue]).to be_kind_of(Issue)
+ end
+
+ it 'the issue author is Alert bot' do
+ expect(execute.payload[:issue].author).to eq(User.alert_bot)
+ end
+
+ it 'issue has a correct title' do
+ expect(execute.payload[:issue].title).to eq(incident_payload['title'])
+ end
+
+ it 'issue has a correct description' do
+ markdown_line_break = ' '
+
+ expect(execute.payload[:issue].description).to eq(
+ <<~MARKDOWN.chomp
+ **Incident:** [My new incident](https://webdemo.pagerduty.com/incidents/PRORDTY)#{markdown_line_break}
+ **Incident number:** 33#{markdown_line_break}
+ **Urgency:** high#{markdown_line_break}
+ **Status:** triggered#{markdown_line_break}
+ **Incident key:** #{markdown_line_break}
+ **Created at:** 26 September 2017, 3:14PM (UTC)#{markdown_line_break}
+ **Assignees:** [Laura Haley](https://webdemo.pagerduty.com/users/P553OPV)#{markdown_line_break}
+ **Impacted services:** [Production XDB Cluster](https://webdemo.pagerduty.com/services/PN49J75)
+ MARKDOWN
+ )
+ end
+ end
+
+ context 'when the payload does not contain a title' do
+ let(:incident_payload) { {} }
+
+ it 'does not create a GitLab issue' do
+ expect { execute }.not_to change(Issue, :count)
+ end
+
+ it 'responds with error' do
+ expect(execute).to be_error
+ expect(execute.message).to eq("Title can't be blank")
+ end
+ end
+ end
+
+ context 'when PagerDuty webhook setting is not active' do
+ let_it_be(:incident_management_setting) { create(:project_incident_management_setting, project: project, pagerduty_active: false) }
+
+ it 'does not create a GitLab issue' do
+ expect { execute }.not_to change(Issue, :count)
+ end
+
+ it 'responds with forbidden' do
+ expect(execute).to be_error
+ expect(execute.http_status).to eq(:forbidden)
+ end
+ end
+ end
+
+ context 'when pagerduty_webhook feature disabled' do
+ before do
+ stub_feature_flags(pagerduty_webhook: false)
+ end
+
+ it 'does not create a GitLab issue' do
+ expect { execute }.not_to change(Issue, :count)
+ end
+
+ it 'responds with forbidden' do
+ expect(execute).to be_error
+ expect(execute.http_status).to eq(:forbidden)
+ end
+ end
+ end
+end
diff --git a/spec/services/incident_management/pager_duty/process_webhook_service_spec.rb b/spec/services/incident_management/pager_duty/process_webhook_service_spec.rb
new file mode 100644
index 00000000000..11ce8388427
--- /dev/null
+++ b/spec/services/incident_management/pager_duty/process_webhook_service_spec.rb
@@ -0,0 +1,148 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe IncidentManagement::PagerDuty::ProcessWebhookService do
+ let_it_be(:project, reload: true) { create(:project) }
+
+ describe '#execute' do
+ shared_examples 'does not process incidents' do
+ it 'does not process incidents' do
+ expect(::IncidentManagement::PagerDuty::ProcessIncidentWorker).not_to receive(:perform_async)
+
+ execute
+ end
+ end
+
+ let(:webhook_payload) { Gitlab::Json.parse(fixture_file('pager_duty/webhook_incident_trigger.json')) }
+ let(:token) { nil }
+
+ subject(:execute) { described_class.new(project, nil, webhook_payload).execute(token) }
+
+ context 'when pagerduty_webhook feature is enabled' do
+ before do
+ stub_feature_flags(pagerduty_webhook: project)
+ end
+
+ context 'when PagerDuty webhook setting is active' do
+ let_it_be(:incident_management_setting) { create(:project_incident_management_setting, project: project, pagerduty_active: true) }
+
+ context 'when token is valid' do
+ let(:token) { incident_management_setting.pagerduty_token }
+
+ context 'when webhook payload has acceptable size' do
+ it 'responds with Accepted' do
+ result = execute
+
+ expect(result).to be_success
+ expect(result.http_status).to eq(:accepted)
+ end
+
+ it 'processes issues' do
+ incident_payload = ::PagerDuty::WebhookPayloadParser.call(webhook_payload).first['incident']
+
+ expect(::IncidentManagement::PagerDuty::ProcessIncidentWorker)
+ .to receive(:perform_async)
+ .with(project.id, incident_payload)
+ .once
+
+ execute
+ end
+ end
+
+ context 'when webhook payload is too big' do
+ let(:deep_size) { instance_double(Gitlab::Utils::DeepSize, valid?: false) }
+
+ before do
+ allow(Gitlab::Utils::DeepSize)
+ .to receive(:new)
+ .with(webhook_payload, max_size: described_class::PAGER_DUTY_PAYLOAD_SIZE_LIMIT)
+ .and_return(deep_size)
+ end
+
+ it 'responds with Bad Request' do
+ result = execute
+
+ expect(result).to be_error
+ expect(result.http_status).to eq(:bad_request)
+ end
+
+ it_behaves_like 'does not process incidents'
+ end
+
+ context 'when webhook payload is blank' do
+ let(:webhook_payload) { nil }
+
+ it 'responds with Accepted' do
+ result = execute
+
+ expect(result).to be_success
+ expect(result.http_status).to eq(:accepted)
+ end
+
+ it_behaves_like 'does not process incidents'
+ end
+ end
+
+ context 'when token is invalid' do
+ let(:token) { 'invalid-token' }
+
+ it 'responds with Unauthorized' do
+ result = execute
+
+ expect(result).to be_error
+ expect(result.http_status).to eq(:unauthorized)
+ end
+
+ it_behaves_like 'does not process incidents'
+ end
+ end
+
+ context 'when both tokens are nil' do
+ let_it_be(:incident_management_setting) { create(:project_incident_management_setting, project: project, pagerduty_active: false) }
+ let(:token) { nil }
+
+ before do
+ incident_management_setting.update_column(:pagerduty_active, true)
+ end
+
+ it 'responds with Unauthorized' do
+ result = execute
+
+ expect(result).to be_error
+ expect(result.http_status).to eq(:unauthorized)
+ end
+
+ it_behaves_like 'does not process incidents'
+ end
+
+ context 'when PagerDuty webhook setting is not active' do
+ let_it_be(:incident_management_setting) { create(:project_incident_management_setting, project: project, pagerduty_active: false) }
+
+ it 'responds with Forbidden' do
+ result = execute
+
+ expect(result).to be_error
+ expect(result.http_status).to eq(:forbidden)
+ end
+
+ it_behaves_like 'does not process incidents'
+ end
+ end
+
+ context 'when pagerduty_webhook feature is disabled' do
+ before do
+ stub_feature_flags(pagerduty_webhook: false)
+ end
+
+ it 'responds with Forbidden' do
+ result = execute
+
+ expect(result).to be_error
+ expect(result.http_status).to eq(:forbidden)
+ end
+
+ it_behaves_like 'does not process incidents'
+ end
+ end
+end
diff --git a/spec/services/integrations/test/project_service_spec.rb b/spec/services/integrations/test/project_service_spec.rb
index fdb43ca345a..dd603765d59 100644
--- a/spec/services/integrations/test/project_service_spec.rb
+++ b/spec/services/integrations/test/project_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Integrations::Test::ProjectService do
+RSpec.describe Integrations::Test::ProjectService do
let(:user) { double('user') }
describe '#execute' do
diff --git a/spec/services/issuable/bulk_update_service_spec.rb b/spec/services/issuable/bulk_update_service_spec.rb
index c791c454d70..168a80a97c0 100644
--- a/spec/services/issuable/bulk_update_service_spec.rb
+++ b/spec/services/issuable/bulk_update_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Issuable::BulkUpdateService do
+RSpec.describe Issuable::BulkUpdateService do
let(:user) { create(:user) }
let(:project) { create(:project, :repository, namespace: user.namespace) }
@@ -18,8 +18,8 @@ describe Issuable::BulkUpdateService do
it 'succeeds' do
result = bulk_update(issuables, milestone_id: milestone.id)
- expect(result[:success]).to be_truthy
- expect(result[:count]).to eq(issuables.count)
+ expect(result.success?).to be_truthy
+ expect(result.payload[:count]).to eq(issuables.count)
end
it 'updates the issuables milestone' do
@@ -121,8 +121,8 @@ describe Issuable::BulkUpdateService do
it 'succeeds and returns the correct number of issues updated' do
result = bulk_update(issues, state_event: 'close')
- expect(result[:success]).to be_truthy
- expect(result[:count]).to eq(issues.count)
+ expect(result.success?).to be_truthy
+ expect(result.payload[:count]).to eq(issues.count)
end
it 'closes all the issues passed' do
@@ -139,8 +139,8 @@ describe Issuable::BulkUpdateService do
it 'succeeds and returns the correct number of issues updated' do
result = bulk_update(issues, state_event: 'reopen')
- expect(result[:success]).to be_truthy
- expect(result[:count]).to eq(issues.count)
+ expect(result.success?).to be_truthy
+ expect(result.payload[:count]).to eq(issues.count)
end
it 'reopens all the issues passed' do
@@ -161,8 +161,8 @@ describe Issuable::BulkUpdateService do
result = bulk_update(merge_request, assignee_ids: [user.id, new_assignee.id])
- expect(result[:success]).to be_truthy
- expect(result[:count]).to eq(1)
+ expect(result.success?).to be_truthy
+ expect(result.payload[:count]).to eq(1)
end
it 'updates the assignee to the user ID passed' do
@@ -199,8 +199,8 @@ describe Issuable::BulkUpdateService do
result = bulk_update(issue, assignee_ids: [new_assignee.id])
- expect(result[:success]).to be_truthy
- expect(result[:count]).to eq(1)
+ expect(result.success?).to be_truthy
+ expect(result.payload[:count]).to eq(1)
end
it 'updates the assignee to the user ID passed' do
@@ -273,8 +273,8 @@ describe Issuable::BulkUpdateService do
issue2 = create(:issue, project: create(:project))
result = bulk_update([issue1, issue2], assignee_ids: [user.id])
- expect(result[:success]).to be_truthy
- expect(result[:count]).to eq(1)
+ expect(result.success?).to be_truthy
+ expect(result.payload[:count]).to eq(1)
expect(issue1.reload.assignees).to eq([user])
expect(issue2.reload.assignees).to be_empty
@@ -332,8 +332,8 @@ describe Issuable::BulkUpdateService do
milestone = create(:milestone, group: group)
result = bulk_update([issue1, issue2, issue3], milestone_id: milestone.id)
- expect(result[:success]).to be_truthy
- expect(result[:count]).to eq(2)
+ expect(result.success?).to be_truthy
+ expect(result.payload[:count]).to eq(2)
expect(issue1.reload.milestone).to eq(milestone)
expect(issue2.reload.milestone).to be_nil
diff --git a/spec/services/issuable/clone/attributes_rewriter_spec.rb b/spec/services/issuable/clone/attributes_rewriter_spec.rb
index fb520f828fa..372e6d480e3 100644
--- a/spec/services/issuable/clone/attributes_rewriter_spec.rb
+++ b/spec/services/issuable/clone/attributes_rewriter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Issuable::Clone::AttributesRewriter do
+RSpec.describe Issuable::Clone::AttributesRewriter do
let(:user) { create(:user) }
let(:group) { create(:group) }
let(:project1) { create(:project, :public, group: group) }
diff --git a/spec/services/issuable/clone/content_rewriter_spec.rb b/spec/services/issuable/clone/content_rewriter_spec.rb
index 3479c20862a..f39439b7c2f 100644
--- a/spec/services/issuable/clone/content_rewriter_spec.rb
+++ b/spec/services/issuable/clone/content_rewriter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Issuable::Clone::ContentRewriter do
+RSpec.describe Issuable::Clone::ContentRewriter do
let(:user) { create(:user) }
let(:group) { create(:group) }
let(:project1) { create(:project, :public, group: group) }
diff --git a/spec/services/issuable/common_system_notes_service_spec.rb b/spec/services/issuable/common_system_notes_service_spec.rb
index 771e7ca42c9..daf4f68208e 100644
--- a/spec/services/issuable/common_system_notes_service_spec.rb
+++ b/spec/services/issuable/common_system_notes_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Issuable::CommonSystemNotesService do
+RSpec.describe Issuable::CommonSystemNotesService do
let_it_be(:project) { create(:project) }
let_it_be(:user) { create(:user) }
@@ -43,23 +43,23 @@ describe Issuable::CommonSystemNotesService do
it_behaves_like 'system note creation', {}, 'changed milestone'
end
- context 'with merge requests WIP note' do
- context 'adding WIP note' do
+ context 'with merge requests Draft note' do
+ context 'adding Draft note' do
let(:issuable) { create(:merge_request, title: "merge request") }
- it_behaves_like 'system note creation', { title: "WIP merge request" }, 'marked as a **Work In Progress**'
+ it_behaves_like 'system note creation', { title: "Draft: merge request" }, 'marked as a **Work In Progress**'
context 'and changing title' do
before do
- issuable.update_attribute(:title, "WIP changed title")
+ issuable.update_attribute(:title, "Draft: changed title")
end
- it_behaves_like 'WIP notes creation', 'marked'
+ it_behaves_like 'draft notes creation', 'marked'
end
end
- context 'removing WIP note' do
- let(:issuable) { create(:merge_request, title: "WIP merge request") }
+ context 'removing Draft note' do
+ let(:issuable) { create(:merge_request, title: "Draft: merge request") }
it_behaves_like 'system note creation', { title: "merge request" }, 'unmarked as a **Work In Progress**'
@@ -68,7 +68,7 @@ describe Issuable::CommonSystemNotesService do
issuable.update_attribute(:title, "changed title")
end
- it_behaves_like 'WIP notes creation', 'unmarked'
+ it_behaves_like 'draft notes creation', 'unmarked'
end
end
end
diff --git a/spec/services/issuable/destroy_service_spec.rb b/spec/services/issuable/destroy_service_spec.rb
index dd6a966c145..8d62932f986 100644
--- a/spec/services/issuable/destroy_service_spec.rb
+++ b/spec/services/issuable/destroy_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Issuable::DestroyService do
+RSpec.describe Issuable::DestroyService do
let(:user) { create(:user) }
let(:project) { create(:project, :public) }
diff --git a/spec/services/issues/build_service_spec.rb b/spec/services/issues/build_service_spec.rb
index 140b78f9b7a..68b226b02da 100644
--- a/spec/services/issues/build_service_spec.rb
+++ b/spec/services/issues/build_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper.rb'
-describe Issues::BuildService do
+RSpec.describe Issues::BuildService do
let(:project) { create(:project, :repository) }
let(:user) { create(:user) }
diff --git a/spec/services/issues/close_service_spec.rb b/spec/services/issues/close_service_spec.rb
index 78eba565de4..6678d831775 100644
--- a/spec/services/issues/close_service_spec.rb
+++ b/spec/services/issues/close_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Issues::CloseService do
+RSpec.describe Issues::CloseService do
let(:project) { create(:project, :repository) }
let(:user) { create(:user, email: "user@example.com") }
let(:user2) { create(:user, email: "user2@example.com") }
diff --git a/spec/services/issues/create_service_spec.rb b/spec/services/issues/create_service_spec.rb
index bb02941576a..fdf2326b75e 100644
--- a/spec/services/issues/create_service_spec.rb
+++ b/spec/services/issues/create_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Issues::CreateService do
+RSpec.describe Issues::CreateService do
let(:project) { create(:project) }
let(:user) { create(:user) }
@@ -284,7 +284,9 @@ describe Issues::CreateService do
end
end
- it_behaves_like 'new issuable record that supports quick actions'
+ it_behaves_like 'issuable record that supports quick actions' do
+ let(:issuable) { described_class.new(project, user, params).execute }
+ end
context 'Quick actions' do
context 'with assignee and milestone in params and command' do
diff --git a/spec/services/issues/duplicate_service_spec.rb b/spec/services/issues/duplicate_service_spec.rb
index 41a151b0ca1..78e030e6ac7 100644
--- a/spec/services/issues/duplicate_service_spec.rb
+++ b/spec/services/issues/duplicate_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Issues::DuplicateService do
+RSpec.describe Issues::DuplicateService do
let(:user) { create(:user) }
let(:canonical_project) { create(:project) }
let(:duplicate_project) { create(:project) }
diff --git a/spec/services/issues/export_csv_service_spec.rb b/spec/services/issues/export_csv_service_spec.rb
index 419e29d92a8..76381fe525b 100644
--- a/spec/services/issues/export_csv_service_spec.rb
+++ b/spec/services/issues/export_csv_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Issues::ExportCsvService do
+RSpec.describe Issues::ExportCsvService do
let_it_be(:user) { create(:user) }
let(:group) { create(:group) }
let(:project) { create(:project, :public, group: group) }
diff --git a/spec/services/issues/import_csv_service_spec.rb b/spec/services/issues/import_csv_service_spec.rb
index 92b88489af9..cc3e1d23a74 100644
--- a/spec/services/issues/import_csv_service_spec.rb
+++ b/spec/services/issues/import_csv_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Issues::ImportCsvService do
+RSpec.describe Issues::ImportCsvService do
let(:project) { create(:project) }
let(:user) { create(:user) }
diff --git a/spec/services/issues/move_service_spec.rb b/spec/services/issues/move_service_spec.rb
index a449541f459..8929907a179 100644
--- a/spec/services/issues/move_service_spec.rb
+++ b/spec/services/issues/move_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Issues::MoveService do
+RSpec.describe Issues::MoveService do
let(:user) { create(:user) }
let(:author) { create(:user) }
let(:title) { 'Some issue' }
@@ -210,4 +210,49 @@ describe Issues::MoveService do
end
end
end
+
+ context 'updating sent notifications' do
+ let!(:old_issue_notification_1) { create(:sent_notification, project: old_issue.project, noteable: old_issue) }
+ let!(:old_issue_notification_2) { create(:sent_notification, project: old_issue.project, noteable: old_issue) }
+ let!(:other_issue_notification) { create(:sent_notification, project: old_issue.project) }
+
+ include_context 'user can move issue'
+
+ context 'when issue is from service desk' do
+ before do
+ allow(old_issue).to receive(:from_service_desk?).and_return(true)
+ end
+
+ it 'updates moved issue sent notifications' do
+ new_issue = move_service.execute(old_issue, new_project)
+
+ old_issue_notification_1.reload
+ old_issue_notification_2.reload
+ expect(old_issue_notification_1.project_id).to eq(new_issue.project_id)
+ expect(old_issue_notification_1.noteable_id).to eq(new_issue.id)
+ expect(old_issue_notification_2.project_id).to eq(new_issue.project_id)
+ expect(old_issue_notification_2.noteable_id).to eq(new_issue.id)
+ end
+
+ it 'does not update other issues sent notifications' do
+ expect do
+ move_service.execute(old_issue, new_project)
+ other_issue_notification.reload
+ end.not_to change { other_issue_notification.noteable_id }
+ end
+ end
+
+ context 'when issue is not from service desk' do
+ it 'does not update sent notifications' do
+ move_service.execute(old_issue, new_project)
+
+ old_issue_notification_1.reload
+ old_issue_notification_2.reload
+ expect(old_issue_notification_1.project_id).to eq(old_issue.project_id)
+ expect(old_issue_notification_1.noteable_id).to eq(old_issue.id)
+ expect(old_issue_notification_2.project_id).to eq(old_issue.project_id)
+ expect(old_issue_notification_2.noteable_id).to eq(old_issue.id)
+ end
+ end
+ end
end
diff --git a/spec/services/issues/referenced_merge_requests_service_spec.rb b/spec/services/issues/referenced_merge_requests_service_spec.rb
index 2c5af11d2e6..bf7a4c97e48 100644
--- a/spec/services/issues/referenced_merge_requests_service_spec.rb
+++ b/spec/services/issues/referenced_merge_requests_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper.rb'
-describe Issues::ReferencedMergeRequestsService do
+RSpec.describe Issues::ReferencedMergeRequestsService do
def create_referencing_mr(attributes = {})
create(:merge_request, attributes).tap do |merge_request|
create(:note, :system, project: project, noteable: issue, author: user, note: merge_request.to_reference(full: true))
diff --git a/spec/services/issues/related_branches_service_spec.rb b/spec/services/issues/related_branches_service_spec.rb
index 9f72e499414..d79132d98db 100644
--- a/spec/services/issues/related_branches_service_spec.rb
+++ b/spec/services/issues/related_branches_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Issues::RelatedBranchesService do
+RSpec.describe Issues::RelatedBranchesService do
let_it_be(:developer) { create(:user) }
let_it_be(:issue) { create(:issue) }
let(:user) { developer }
diff --git a/spec/services/issues/reopen_service_spec.rb b/spec/services/issues/reopen_service_spec.rb
index ca878ee947a..f7416203259 100644
--- a/spec/services/issues/reopen_service_spec.rb
+++ b/spec/services/issues/reopen_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Issues::ReopenService do
+RSpec.describe Issues::ReopenService do
let(:project) { create(:project) }
let(:issue) { create(:issue, :closed, project: project) }
diff --git a/spec/services/issues/reorder_service_spec.rb b/spec/services/issues/reorder_service_spec.rb
index 6d72d698b1d..b6ad488a48c 100644
--- a/spec/services/issues/reorder_service_spec.rb
+++ b/spec/services/issues/reorder_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Issues::ReorderService do
+RSpec.describe Issues::ReorderService do
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project) }
let_it_be(:group) { create(:group) }
diff --git a/spec/services/issues/resolve_discussions_spec.rb b/spec/services/issues/resolve_discussions_spec.rb
index ec6624db6fc..a541d92feb2 100644
--- a/spec/services/issues/resolve_discussions_spec.rb
+++ b/spec/services/issues/resolve_discussions_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper.rb'
-describe Issues::ResolveDiscussions do
+RSpec.describe Issues::ResolveDiscussions do
let(:project) { create(:project, :repository) }
let(:user) { create(:user) }
diff --git a/spec/services/issues/update_service_spec.rb b/spec/services/issues/update_service_spec.rb
index 33ae2682d01..77bd540e22f 100644
--- a/spec/services/issues/update_service_spec.rb
+++ b/spec/services/issues/update_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Issues::UpdateService, :mailer do
+RSpec.describe Issues::UpdateService, :mailer do
let_it_be(:user) { create(:user) }
let_it_be(:user2) { create(:user) }
let_it_be(:user3) { create(:user) }
@@ -866,5 +866,10 @@ describe Issues::UpdateService, :mailer do
end
end
end
+
+ it_behaves_like 'issuable record that supports quick actions' do
+ let(:existing_issue) { create(:issue, project: project) }
+ let(:issuable) { described_class.new(project, user, params).execute(existing_issue) }
+ end
end
end
diff --git a/spec/services/issues/zoom_link_service_spec.rb b/spec/services/issues/zoom_link_service_spec.rb
index 3fb1eae361a..56aec4fe564 100644
--- a/spec/services/issues/zoom_link_service_spec.rb
+++ b/spec/services/issues/zoom_link_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Issues::ZoomLinkService do
+RSpec.describe Issues::ZoomLinkService do
let_it_be(:user) { create(:user) }
let_it_be(:issue) { create(:issue) }
diff --git a/spec/services/jira/requests/projects/list_service_spec.rb b/spec/services/jira/requests/projects/list_service_spec.rb
new file mode 100644
index 00000000000..51e67dd821d
--- /dev/null
+++ b/spec/services/jira/requests/projects/list_service_spec.rb
@@ -0,0 +1,83 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Jira::Requests::Projects::ListService do
+ let(:jira_service) { create(:jira_service) }
+ let(:params) { {} }
+
+ describe '#execute' do
+ let(:service) { described_class.new(jira_service, params) }
+
+ subject { service.execute }
+
+ context 'without jira_service' do
+ before do
+ jira_service.update!(active: false)
+ end
+
+ it 'returns an error response' do
+ expect(subject.error?).to be_truthy
+ expect(subject.message).to eq('Jira service not configured.')
+ end
+ end
+
+ context 'when jira_service is nil' do
+ let(:jira_service) { nil }
+
+ it 'returns an error response' do
+ expect(subject.error?).to be_truthy
+ expect(subject.message).to eq('Jira service not configured.')
+ end
+ end
+
+ context 'with jira_service' do
+ context 'when validations and params are ok' do
+ let(:client) { double(options: { site: 'https://jira.example.com' }) }
+
+ before do
+ expect(service).to receive(:client).at_least(:once).and_return(client)
+ end
+
+ context 'when the request to Jira returns an error' do
+ before do
+ expect(client).to receive(:get).and_raise(Timeout::Error)
+ end
+
+ it 'returns an error response' do
+ expect(subject.error?).to be_truthy
+ expect(subject.message).to eq('Jira request error: Timeout::Error')
+ end
+ end
+
+ context 'when the request does not return any values' do
+ before do
+ expect(client).to receive(:get).and_return([])
+ end
+
+ it 'returns a paylod with no projects returned' do
+ payload = subject.payload
+
+ expect(subject.success?).to be_truthy
+ expect(payload[:projects]).to be_empty
+ expect(payload[:is_last]).to be_truthy
+ end
+ end
+
+ context 'when the request returns values' do
+ before do
+ expect(client).to receive(:get).and_return([{ "key" => 'project1' }, { "key" => 'project2' }])
+ end
+
+ it 'returns a paylod with jira projets' do
+ payload = subject.payload
+
+ expect(subject.success?).to be_truthy
+ expect(payload[:projects].map(&:key)).to eq(%w(project1 project2))
+ expect(payload[:is_last]).to be_truthy
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/spec/services/jira/requests/projects_spec.rb b/spec/services/jira/requests/projects_spec.rb
deleted file mode 100644
index f7b9aa7c00c..00000000000
--- a/spec/services/jira/requests/projects_spec.rb
+++ /dev/null
@@ -1,95 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-describe Jira::Requests::Projects do
- let(:jira_service) { create(:jira_service) }
- let(:params) { {} }
-
- describe '#execute' do
- let(:service) { described_class.new(jira_service, params) }
-
- subject { service.execute }
-
- context 'without jira_service' do
- before do
- jira_service.update!(active: false)
- end
-
- it 'returns an error response' do
- expect(subject.error?).to be_truthy
- expect(subject.message).to eq('Jira service not configured.')
- end
- end
-
- context 'when jira_service is nil' do
- let(:jira_service) { nil }
-
- it 'returns an error response' do
- expect(subject.error?).to be_truthy
- expect(subject.message).to eq('Jira service not configured.')
- end
- end
-
- context 'with jira_service' do
- context 'when limit is invalid' do
- let(:params) { { limit: 0 } }
-
- it 'returns a paylod with no projects returned' do
- expect(subject.payload[:projects]).to be_empty
- end
- end
-
- context 'when validations and params are ok' do
- let(:client) { double(options: { site: 'https://jira.example.com' }) }
-
- before do
- expect(service).to receive(:client).at_least(:once).and_return(client)
- end
-
- context 'when the request to Jira returns an error' do
- before do
- expect(client).to receive(:get).and_raise(Timeout::Error)
- end
-
- it 'returns an error response' do
- expect(subject.error?).to be_truthy
- expect(subject.message).to eq('Jira request error: Timeout::Error')
- end
- end
-
- context 'when the request does not return any values' do
- before do
- expect(client).to receive(:get).and_return({ 'someKey' => 'value' })
- end
-
- it 'returns a paylod with no projects returned' do
- payload = subject.payload
-
- expect(subject.success?).to be_truthy
- expect(payload[:projects]).to be_empty
- expect(payload[:is_last]).to be_truthy
- end
- end
-
- context 'when the request returns values' do
- before do
- expect(client).to receive(:get).and_return(
- { 'values' => %w(project1 project2), 'isLast' => false }
- )
- expect(JIRA::Resource::Project).to receive(:build).with(client, 'project1').and_return('jira_project1')
- expect(JIRA::Resource::Project).to receive(:build).with(client, 'project2').and_return('jira_project2')
- end
-
- it 'returns a paylod with jira projets' do
- payload = subject.payload
-
- expect(subject.success?).to be_truthy
- expect(payload[:projects]).to eq(%w(jira_project1 jira_project2))
- expect(payload[:is_last]).to be_falsey
- end
- end
- end
- end
- end
-end
diff --git a/spec/services/jira_import/start_import_service_spec.rb b/spec/services/jira_import/start_import_service_spec.rb
index 9dc8cdb1475..a10928355ef 100644
--- a/spec/services/jira_import/start_import_service_spec.rb
+++ b/spec/services/jira_import/start_import_service_spec.rb
@@ -2,14 +2,21 @@
require 'spec_helper'
-describe JiraImport::StartImportService do
+RSpec.describe JiraImport::StartImportService do
include JiraServiceHelper
let_it_be(:user) { create(:user) }
let_it_be(:project, reload: true) { create(:project) }
let(:key) { 'KEY' }
+ let(:mapping) do
+ [
+ { jira_account_id: 'abc', gitlab_id: 12 },
+ { jira_account_id: 'def', gitlab_id: nil },
+ { jira_account_id: nil, gitlab_id: 1 }
+ ]
+ end
- subject { described_class.new(user, project, key).execute }
+ subject { described_class.new(user, project, key, mapping).execute }
context 'when an error is returned from the project validation' do
before do
@@ -37,7 +44,7 @@ describe JiraImport::StartImportService do
context 'when correct data provided' do
let(:fake_key) { 'some-key' }
- subject { described_class.new(user, project, fake_key).execute }
+ subject { described_class.new(user, project, fake_key, mapping).execute }
context 'when import is already running' do
let_it_be(:jira_import_state) { create(:jira_import_state, :started, project: project) }
@@ -62,35 +69,68 @@ describe JiraImport::StartImportService do
end
context 'when everything is ok' do
- it 'returns success response' do
- expect(subject).to be_a(ServiceResponse)
- expect(subject).to be_success
- end
+ context 'with complete mapping' do
+ before do
+ expect(Gitlab::JiraImport).to receive(:cache_users_mapping).with(project.id, { 'abc' => 12 })
+ end
- it 'schedules Jira import' do
- subject
+ it 'returns success response' do
+ expect(subject).to be_a(ServiceResponse)
+ expect(subject).to be_success
+ end
- expect(project.latest_jira_import).to be_scheduled
- end
+ it 'schedules Jira import' do
+ subject
- it 'creates Jira import data', :aggregate_failures do
- jira_import = subject.payload[:import_data]
+ expect(project.latest_jira_import).to be_scheduled
+ end
+
+ it 'creates Jira import data', :aggregate_failures do
+ jira_import = subject.payload[:import_data]
+
+ expect(jira_import.jira_project_xid).to eq(0)
+ expect(jira_import.jira_project_name).to eq(fake_key)
+ expect(jira_import.jira_project_key).to eq(fake_key)
+ expect(jira_import.user).to eq(user)
+ end
+
+ it 'creates Jira import label' do
+ expect { subject }.to change { Label.count }.by(1)
+ end
+
+ it 'creates Jira label title with correct number' do
+ jira_import = subject.payload[:import_data]
+ label_title = "jira-import::#{jira_import.jira_project_key}-1"
- expect(jira_import.jira_project_xid).to eq(0)
- expect(jira_import.jira_project_name).to eq(fake_key)
- expect(jira_import.jira_project_key).to eq(fake_key)
- expect(jira_import.user).to eq(user)
+ expect(jira_import.label.title).to eq(label_title)
+ end
end
- it 'creates Jira import label' do
- expect { subject }.to change { Label.count }.by(1)
+ context 'when mapping is nil' do
+ let(:mapping) { nil }
+
+ it 'returns success response' do
+ expect(Gitlab::JiraImport).not_to receive(:cache_users_mapping)
+
+ expect(subject).to be_a(ServiceResponse)
+ expect(subject).to be_success
+ end
end
- it 'creates Jira label title with correct number' do
- jira_import = subject.payload[:import_data]
- label_title = "jira-import::#{jira_import.jira_project_key}-1"
+ context 'when no mapping value is complete' do
+ let(:mapping) do
+ [
+ { jira_account_id: 'def', gitlab_id: nil },
+ { jira_account_id: nil, gitlab_id: 1 }
+ ]
+ end
- expect(jira_import.label.title).to eq(label_title)
+ it 'returns success response' do
+ expect(Gitlab::JiraImport).not_to receive(:cache_users_mapping)
+
+ expect(subject).to be_a(ServiceResponse)
+ expect(subject).to be_success
+ end
end
end
diff --git a/spec/services/jira_import/users_importer_spec.rb b/spec/services/jira_import/users_importer_spec.rb
index 28ce5f1b44b..64cdc70f612 100644
--- a/spec/services/jira_import/users_importer_spec.rb
+++ b/spec/services/jira_import/users_importer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe JiraImport::UsersImporter do
+RSpec.describe JiraImport::UsersImporter do
include JiraServiceHelper
let_it_be(:user) { create(:user) }
diff --git a/spec/services/jira_import/users_mapper_spec.rb b/spec/services/jira_import/users_mapper_spec.rb
index 75dbc41aa2e..e5e8279a6fb 100644
--- a/spec/services/jira_import/users_mapper_spec.rb
+++ b/spec/services/jira_import/users_mapper_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe JiraImport::UsersMapper do
+RSpec.describe JiraImport::UsersMapper do
let_it_be(:project) { create(:project) }
subject { described_class.new(project, jira_users).execute }
@@ -29,9 +29,9 @@ describe JiraImport::UsersMapper do
# mapping is tracked in https://gitlab.com/gitlab-org/gitlab/-/issues/219023
let(:mapped_users) do
[
- { jira_account_id: 'abcd', jira_display_name: 'user1', jira_email: nil, gitlab_id: nil },
- { jira_account_id: 'efg', jira_display_name: nil, jira_email: nil, gitlab_id: nil },
- { jira_account_id: 'hij', jira_display_name: 'user3', jira_email: 'user3@example.com', gitlab_id: nil }
+ { jira_account_id: 'abcd', jira_display_name: 'user1', jira_email: nil, gitlab_id: nil, gitlab_username: nil, gitlab_name: nil },
+ { jira_account_id: 'efg', jira_display_name: nil, jira_email: nil, gitlab_id: nil, gitlab_username: nil, gitlab_name: nil },
+ { jira_account_id: 'hij', jira_display_name: 'user3', jira_email: 'user3@example.com', gitlab_id: nil, gitlab_username: nil, gitlab_name: nil }
]
end
diff --git a/spec/services/keys/create_service_spec.rb b/spec/services/keys/create_service_spec.rb
index 1f8b402cf08..1dbe383ad8e 100644
--- a/spec/services/keys/create_service_spec.rb
+++ b/spec/services/keys/create_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Keys::CreateService do
+RSpec.describe Keys::CreateService do
let(:user) { create(:user) }
let(:params) { attributes_for(:key) }
diff --git a/spec/services/keys/destroy_service_spec.rb b/spec/services/keys/destroy_service_spec.rb
index ca4bbd50c03..59ce4a941c7 100644
--- a/spec/services/keys/destroy_service_spec.rb
+++ b/spec/services/keys/destroy_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Keys::DestroyService do
+RSpec.describe Keys::DestroyService do
let(:user) { create(:user) }
subject { described_class.new(user) }
diff --git a/spec/services/keys/last_used_service_spec.rb b/spec/services/keys/last_used_service_spec.rb
index c675df39f4d..82b6b05975b 100644
--- a/spec/services/keys/last_used_service_spec.rb
+++ b/spec/services/keys/last_used_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Keys::LastUsedService do
+RSpec.describe Keys::LastUsedService do
describe '#execute', :clean_gitlab_redis_shared_state do
it 'updates the key when it has not been used recently' do
key = create(:key, last_used_at: 1.year.ago)
diff --git a/spec/services/labels/available_labels_service_spec.rb b/spec/services/labels/available_labels_service_spec.rb
index 56826257d6f..9912f2cf469 100644
--- a/spec/services/labels/available_labels_service_spec.rb
+++ b/spec/services/labels/available_labels_service_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe Labels::AvailableLabelsService do
+RSpec.describe Labels::AvailableLabelsService do
let(:user) { create(:user) }
let(:project) { create(:project, :public, group: group) }
let(:group) { create(:group) }
diff --git a/spec/services/labels/create_service_spec.rb b/spec/services/labels/create_service_spec.rb
index f057c4401e7..7a31a5a7cae 100644
--- a/spec/services/labels/create_service_spec.rb
+++ b/spec/services/labels/create_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Labels::CreateService do
+RSpec.describe Labels::CreateService do
describe '#execute' do
let(:project) { create(:project) }
let(:group) { create(:group) }
diff --git a/spec/services/labels/find_or_create_service_spec.rb b/spec/services/labels/find_or_create_service_spec.rb
index 438d895392b..aa9eb0e6a0d 100644
--- a/spec/services/labels/find_or_create_service_spec.rb
+++ b/spec/services/labels/find_or_create_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Labels::FindOrCreateService do
+RSpec.describe Labels::FindOrCreateService do
describe '#execute' do
let(:group) { create(:group) }
let(:project) { create(:project, namespace: group) }
diff --git a/spec/services/labels/promote_service_spec.rb b/spec/services/labels/promote_service_spec.rb
index d86281b751c..7674ec36331 100644
--- a/spec/services/labels/promote_service_spec.rb
+++ b/spec/services/labels/promote_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Labels::PromoteService do
+RSpec.describe Labels::PromoteService do
describe '#execute' do
let!(:user) { create(:user) }
diff --git a/spec/services/labels/transfer_service_spec.rb b/spec/services/labels/transfer_service_spec.rb
index a2a9c8dddf2..2c0c82ed976 100644
--- a/spec/services/labels/transfer_service_spec.rb
+++ b/spec/services/labels/transfer_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Labels::TransferService do
+RSpec.describe Labels::TransferService do
describe '#execute' do
let_it_be(:user) { create(:admin) }
diff --git a/spec/services/labels/update_service_spec.rb b/spec/services/labels/update_service_spec.rb
index 045e8af1135..af2403656af 100644
--- a/spec/services/labels/update_service_spec.rb
+++ b/spec/services/labels/update_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Labels::UpdateService do
+RSpec.describe Labels::UpdateService do
describe '#execute' do
let(:project) { create(:project) }
diff --git a/spec/services/lfs/file_transformer_spec.rb b/spec/services/lfs/file_transformer_spec.rb
index 13d9c369c42..e87c80b4c6c 100644
--- a/spec/services/lfs/file_transformer_spec.rb
+++ b/spec/services/lfs/file_transformer_spec.rb
@@ -2,7 +2,7 @@
require "spec_helper"
-describe Lfs::FileTransformer do
+RSpec.describe Lfs::FileTransformer do
let(:project) { create(:project, :repository, :wiki_repo) }
let(:repository) { project.repository }
let(:file_content) { 'Test file content' }
diff --git a/spec/services/lfs/lock_file_service_spec.rb b/spec/services/lfs/lock_file_service_spec.rb
index 2bd62b96083..b3a121866c8 100644
--- a/spec/services/lfs/lock_file_service_spec.rb
+++ b/spec/services/lfs/lock_file_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Lfs::LockFileService do
+RSpec.describe Lfs::LockFileService do
let(:project) { create(:project) }
let(:current_user) { create(:user) }
diff --git a/spec/services/lfs/locks_finder_service_spec.rb b/spec/services/lfs/locks_finder_service_spec.rb
index fdc60e2c03f..1167212eb69 100644
--- a/spec/services/lfs/locks_finder_service_spec.rb
+++ b/spec/services/lfs/locks_finder_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Lfs::LocksFinderService do
+RSpec.describe Lfs::LocksFinderService do
let(:project) { create(:project) }
let(:user) { create(:user) }
let(:params) { {} }
diff --git a/spec/services/lfs/unlock_file_service_spec.rb b/spec/services/lfs/unlock_file_service_spec.rb
index 1334b074e84..7ab269f897a 100644
--- a/spec/services/lfs/unlock_file_service_spec.rb
+++ b/spec/services/lfs/unlock_file_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Lfs::UnlockFileService do
+RSpec.describe Lfs::UnlockFileService do
let(:project) { create(:project) }
let(:current_user) { create(:user) }
let(:lock_author) { create(:user) }
diff --git a/spec/services/members/approve_access_request_service_spec.rb b/spec/services/members/approve_access_request_service_spec.rb
index 5bbceac3dd0..e6a94fdaf84 100644
--- a/spec/services/members/approve_access_request_service_spec.rb
+++ b/spec/services/members/approve_access_request_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Members::ApproveAccessRequestService do
+RSpec.describe Members::ApproveAccessRequestService do
let(:project) { create(:project, :public) }
let(:group) { create(:group, :public) }
let(:current_user) { create(:user) }
diff --git a/spec/services/members/create_service_spec.rb b/spec/services/members/create_service_spec.rb
index 674fe0f666e..00b5ff59e48 100644
--- a/spec/services/members/create_service_spec.rb
+++ b/spec/services/members/create_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Members::CreateService do
+RSpec.describe Members::CreateService do
let(:project) { create(:project) }
let(:user) { create(:user) }
let(:project_user) { create(:user) }
diff --git a/spec/services/members/destroy_service_spec.rb b/spec/services/members/destroy_service_spec.rb
index 73ac0bd7716..13e7b4c1006 100644
--- a/spec/services/members/destroy_service_spec.rb
+++ b/spec/services/members/destroy_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Members::DestroyService do
+RSpec.describe Members::DestroyService do
let(:current_user) { create(:user) }
let(:member_user) { create(:user) }
let(:group) { create(:group, :public) }
@@ -25,6 +25,7 @@ describe Members::DestroyService do
before do
type = member.is_a?(GroupMember) ? 'Group' : 'Project'
expect(TodosDestroyer::EntityLeaveWorker).to receive(:perform_in).with(Todo::WAIT_FOR_DELETE, member.user_id, member.source_id, type)
+ expect(MembersDestroyer::UnassignIssuablesWorker).to receive(:perform_async).with(member.user_id, member.source_id, type) if opts[:unassign_issuables]
end
it 'destroys the member' do
@@ -56,12 +57,23 @@ describe Members::DestroyService do
expect(member_user.todos_pending_count).to be(1)
expect(member_user.todos_done_count).to be(1)
- described_class.new(current_user).execute(member, opts)
+ service = described_class.new(current_user)
+
+ if opts[:unassign_issuables]
+ expect(service).to receive(:enqueue_unassign_issuables).with(member)
+ end
+
+ service.execute(member, opts)
expect(member_user.assigned_open_merge_requests_count).to be(0)
expect(member_user.assigned_open_issues_count).to be(0)
expect(member_user.todos_pending_count).to be(0)
expect(member_user.todos_done_count).to be(0)
+
+ unless opts[:unassign_issuables]
+ expect(member_user.assigned_merge_requests.opened.count).to be(1)
+ expect(member_user.assigned_issues.opened.count).to be(1)
+ end
end
end
@@ -100,7 +112,7 @@ describe Members::DestroyService do
it_behaves_like 'a service raising Gitlab::Access::AccessDeniedError'
it_behaves_like 'a service destroying a member with access' do
- let(:opts) { { skip_authorization: true } }
+ let(:opts) { { skip_authorization: true, unassign_issuables: true } }
end
end
@@ -114,7 +126,7 @@ describe Members::DestroyService do
it_behaves_like 'a service raising Gitlab::Access::AccessDeniedError'
it_behaves_like 'a service destroying a member with access' do
- let(:opts) { { skip_authorization: true } }
+ let(:opts) { { skip_authorization: true, unassign_issuables: true } }
end
end
end
@@ -133,6 +145,31 @@ describe Members::DestroyService do
end
it_behaves_like 'a service destroying a member with access'
+
+ context 'unassign issuables' do
+ it_behaves_like 'a service destroying a member with access' do
+ let(:opts) { { unassign_issuables: true } }
+ end
+ end
+ end
+
+ context 'with a project bot member' do
+ let(:member) { group_project.members.find_by(user_id: member_user.id) }
+ let(:member_user) { create(:user, :project_bot) }
+
+ before do
+ group_project.add_maintainer(member_user)
+ end
+
+ context 'when the destroy_bot flag is true' do
+ it_behaves_like 'a service destroying a member with access' do
+ let(:opts) { { destroy_bot: true } }
+ end
+ end
+
+ context 'when the destroy_bot flag is not specified' do
+ it_behaves_like 'a service raising Gitlab::Access::AccessDeniedError'
+ end
end
context 'with a group member' do
@@ -143,6 +180,12 @@ describe Members::DestroyService do
end
it_behaves_like 'a service destroying a member with access'
+
+ context 'unassign issuables' do
+ it_behaves_like 'a service destroying a member with access' do
+ let(:opts) { { unassign_issuables: true } }
+ end
+ end
end
end
end
diff --git a/spec/services/members/request_access_service_spec.rb b/spec/services/members/request_access_service_spec.rb
index a0f7ae91bdb..69eea2aea4b 100644
--- a/spec/services/members/request_access_service_spec.rb
+++ b/spec/services/members/request_access_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Members::RequestAccessService do
+RSpec.describe Members::RequestAccessService do
let(:user) { create(:user) }
shared_examples 'a service raising Gitlab::Access::AccessDeniedError' do
diff --git a/spec/services/members/unassign_issuables_service_spec.rb b/spec/services/members/unassign_issuables_service_spec.rb
new file mode 100644
index 00000000000..3f7ccb7bab3
--- /dev/null
+++ b/spec/services/members/unassign_issuables_service_spec.rb
@@ -0,0 +1,66 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Members::UnassignIssuablesService do
+ let_it_be(:group) { create(:group, :private) }
+ let_it_be(:project) { create(:project, group: group) }
+ let_it_be(:user, reload: true) { create(:user) }
+ let_it_be(:assigned_issue1, reload: true) { create(:issue, project: project, assignees: [user]) }
+ let_it_be(:assigned_issue2, reload: true) { create(:issue, project: project, assignees: [user]) }
+
+ let!(:assigned_merge_request1) { create(:merge_request, :simple, :closed, target_project: project, source_project: project, assignees: [user], title: 'Test1') }
+ let!(:assigned_merge_request2) { create(:merge_request, :simple, :opened, target_project: project, source_project: project, assignees: [user], title: 'Test2') }
+
+ describe '#execute' do
+ RSpec.shared_examples 'un-assigning issuables' do |issue_count, mr_count, open_issue_count, open_mr_count|
+ it 'removes issuable assignments', :aggregate_failures do
+ expect(user.assigned_issues.count).to eq(issue_count)
+ expect(user.assigned_merge_requests.count).to eq(mr_count)
+
+ subject
+
+ expect(user.assigned_issues.count).to eq(0)
+ expect(user.assigned_merge_requests.count).to eq(0)
+ end
+
+ it 'invalidates user cache', :aggregate_failures, :clean_gitlab_redis_cache do
+ expect(user.assigned_open_merge_requests_count).to eq(open_mr_count)
+ expect(user.assigned_open_issues_count).to eq(open_issue_count)
+
+ subject
+
+ expect(user.assigned_open_merge_requests_count).to eq(0)
+ expect(user.assigned_open_issues_count).to eq(0)
+ end
+ end
+
+ context 'when a user leaves a project' do
+ before do
+ project.add_maintainer(user)
+ end
+
+ subject { described_class.new(user, project).execute }
+
+ it_behaves_like 'un-assigning issuables', 2, 2, 2, 1
+ end
+
+ context 'when a user leaves a group' do
+ let_it_be(:project2) { create(:project, group: group) }
+
+ let_it_be(:assigned_issue3, reload: true) { create(:issue, project: project2, assignees: [user]) }
+ let_it_be(:assigned_issue4, reload: true) { create(:issue, project: project2, assignees: [user]) }
+
+ let!(:assigned_merge_request3) { create(:merge_request, :simple, :closed, target_project: project2, source_project: project2, assignees: [user], title: 'Test1') }
+ let!(:assigned_merge_request4) { create(:merge_request, :simple, :opened, target_project: project2, source_project: project2, assignees: [user], title: 'Test2') }
+
+ before do
+ group.add_maintainer(user)
+ end
+
+ subject { described_class.new(user, group).execute }
+
+ it_behaves_like 'un-assigning issuables', 4, 4, 4, 2
+ end
+ end
+end
diff --git a/spec/services/members/update_service_spec.rb b/spec/services/members/update_service_spec.rb
index a8b28127df2..f510916558b 100644
--- a/spec/services/members/update_service_spec.rb
+++ b/spec/services/members/update_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Members::UpdateService do
+RSpec.describe Members::UpdateService do
let(:project) { create(:project, :public) }
let(:group) { create(:group, :public) }
let(:current_user) { create(:user) }
diff --git a/spec/services/merge_requests/add_context_service_spec.rb b/spec/services/merge_requests/add_context_service_spec.rb
index d4e95c2f1ea..58ed91218d1 100644
--- a/spec/services/merge_requests/add_context_service_spec.rb
+++ b/spec/services/merge_requests/add_context_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe MergeRequests::AddContextService do
+RSpec.describe MergeRequests::AddContextService do
let(:project) { create(:project, :repository) }
let(:admin) { create(:admin) }
let(:merge_request) { create(:merge_request, source_project: project, target_project: project, author: admin) }
diff --git a/spec/services/merge_requests/add_todo_when_build_fails_service_spec.rb b/spec/services/merge_requests/add_todo_when_build_fails_service_spec.rb
index 0cec1e7be22..3c81ad6722d 100644
--- a/spec/services/merge_requests/add_todo_when_build_fails_service_spec.rb
+++ b/spec/services/merge_requests/add_todo_when_build_fails_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe MergeRequests::AddTodoWhenBuildFailsService do
+RSpec.describe MergeRequests::AddTodoWhenBuildFailsService do
let(:user) { create(:user) }
let(:project) { create(:project, :repository) }
let(:sha) { '1234567890abcdef1234567890abcdef12345678' }
diff --git a/spec/services/merge_requests/after_create_service_spec.rb b/spec/services/merge_requests/after_create_service_spec.rb
index 4aefe5f7dae..840b7bc0a1c 100644
--- a/spec/services/merge_requests/after_create_service_spec.rb
+++ b/spec/services/merge_requests/after_create_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe MergeRequests::AfterCreateService do
+RSpec.describe MergeRequests::AfterCreateService do
let_it_be(:merge_request) { create(:merge_request) }
subject(:after_create_service) do
diff --git a/spec/services/merge_requests/approval_service_spec.rb b/spec/services/merge_requests/approval_service_spec.rb
new file mode 100644
index 00000000000..124501f17d5
--- /dev/null
+++ b/spec/services/merge_requests/approval_service_spec.rb
@@ -0,0 +1,74 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe MergeRequests::ApprovalService do
+ describe '#execute' do
+ let(:user) { create(:user) }
+ let(:merge_request) { create(:merge_request) }
+ let(:project) { merge_request.project }
+ let!(:todo) { create(:todo, user: user, project: project, target: merge_request) }
+
+ subject(:service) { described_class.new(project, user) }
+
+ before do
+ project.add_developer(user)
+ end
+
+ context 'with invalid approval' do
+ before do
+ allow(merge_request.approvals).to receive(:new).and_return(double(save: false))
+ end
+
+ it 'does not create an approval note' do
+ expect(SystemNoteService).not_to receive(:approve_mr)
+
+ service.execute(merge_request)
+ end
+
+ it 'does not mark pending todos as done' do
+ service.execute(merge_request)
+
+ expect(todo.reload).to be_pending
+ end
+ end
+
+ context 'with valid approval' do
+ it 'creates an approval note and marks pending todos as done' do
+ expect(SystemNoteService).to receive(:approve_mr).with(merge_request, user)
+ expect(merge_request.approvals).to receive(:reset)
+
+ service.execute(merge_request)
+
+ expect(todo.reload).to be_done
+ end
+
+ it 'creates approve MR event' do
+ expect_next_instance_of(EventCreateService) do |instance|
+ expect(instance).to receive(:approve_mr)
+ .with(merge_request, user)
+ end
+
+ service.execute(merge_request)
+ end
+
+ context 'with remaining approvals' do
+ it 'fires an approval webhook' do
+ expect(service).to receive(:execute_hooks).with(merge_request, 'approved')
+
+ service.execute(merge_request)
+ end
+ end
+ end
+
+ context 'user cannot update the merge request' do
+ before do
+ project.add_guest(user)
+ end
+
+ it 'does not update approvals' do
+ expect { service.execute(merge_request) }.not_to change { merge_request.approvals.size }
+ end
+ end
+ end
+end
diff --git a/spec/services/merge_requests/assign_issues_service_spec.rb b/spec/services/merge_requests/assign_issues_service_spec.rb
index c0b57b9092d..6398e8c533e 100644
--- a/spec/services/merge_requests/assign_issues_service_spec.rb
+++ b/spec/services/merge_requests/assign_issues_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe MergeRequests::AssignIssuesService do
+RSpec.describe MergeRequests::AssignIssuesService do
let(:user) { create(:user) }
let(:project) { create(:project, :public, :repository) }
let(:issue) { create(:issue, project: project) }
diff --git a/spec/services/merge_requests/build_service_spec.rb b/spec/services/merge_requests/build_service_spec.rb
index 9b358839c06..f99be26927d 100644
--- a/spec/services/merge_requests/build_service_spec.rb
+++ b/spec/services/merge_requests/build_service_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe MergeRequests::BuildService do
+RSpec.describe MergeRequests::BuildService do
using RSpec::Parameterized::TableSyntax
include RepoHelpers
include ProjectForksHelper
@@ -189,8 +189,8 @@ describe MergeRequests::BuildService do
it_behaves_like 'allows the merge request to be created'
- it 'adds a WIP prefix to the merge request title' do
- expect(merge_request.title).to eq('WIP: Feature branch')
+ it 'adds a Draft prefix to the merge request title' do
+ expect(merge_request.title).to eq('Draft: Feature branch')
end
end
diff --git a/spec/services/merge_requests/close_service_spec.rb b/spec/services/merge_requests/close_service_spec.rb
index 0e51de48fb1..e518e439a84 100644
--- a/spec/services/merge_requests/close_service_spec.rb
+++ b/spec/services/merge_requests/close_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe MergeRequests::CloseService do
+RSpec.describe MergeRequests::CloseService do
let(:user) { create(:user) }
let(:user2) { create(:user) }
let(:guest) { create(:user) }
diff --git a/spec/services/merge_requests/conflicts/list_service_spec.rb b/spec/services/merge_requests/conflicts/list_service_spec.rb
index 13d69307084..14133731e37 100644
--- a/spec/services/merge_requests/conflicts/list_service_spec.rb
+++ b/spec/services/merge_requests/conflicts/list_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe MergeRequests::Conflicts::ListService do
+RSpec.describe MergeRequests::Conflicts::ListService do
describe '#can_be_resolved_in_ui?' do
def create_merge_request(source_branch, target_branch = 'conflict-start')
create(:merge_request, source_branch: source_branch, target_branch: target_branch, merge_status: :unchecked) do |mr|
@@ -30,6 +30,7 @@ describe MergeRequests::Conflicts::ListService do
it 'returns a falsey value when one of the MR branches is missing' do
merge_request = create_merge_request('conflict-resolvable')
merge_request.project.repository.rm_branch(merge_request.author, 'conflict-resolvable')
+ merge_request.clear_memoized_source_branch_exists
expect(conflicts_service(merge_request).can_be_resolved_in_ui?).to be_falsey
end
diff --git a/spec/services/merge_requests/conflicts/resolve_service_spec.rb b/spec/services/merge_requests/conflicts/resolve_service_spec.rb
index 74f20094081..c4d50124ca9 100644
--- a/spec/services/merge_requests/conflicts/resolve_service_spec.rb
+++ b/spec/services/merge_requests/conflicts/resolve_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe MergeRequests::Conflicts::ResolveService do
+RSpec.describe MergeRequests::Conflicts::ResolveService do
include ProjectForksHelper
let(:user) { create(:user) }
let(:project) { create(:project, :public, :repository) }
diff --git a/spec/services/merge_requests/create_from_issue_service_spec.rb b/spec/services/merge_requests/create_from_issue_service_spec.rb
index fb1bb308170..fa70ad8c559 100644
--- a/spec/services/merge_requests/create_from_issue_service_spec.rb
+++ b/spec/services/merge_requests/create_from_issue_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe MergeRequests::CreateFromIssueService do
+RSpec.describe MergeRequests::CreateFromIssueService do
include ProjectForksHelper
let(:project) { create(:project, :repository) }
@@ -163,10 +163,10 @@ describe MergeRequests::CreateFromIssueService do
expect(result[:merge_request].milestone_id).to eq(milestone_id)
end
- it 'sets the merge request title to: "WIP: Resolves "$issue-title"' do
+ it 'sets the merge request title to: "Draft: Resolves "$issue-title"' do
result = service.execute
- expect(result[:merge_request].title).to eq("WIP: Resolve \"#{issue.title}\"")
+ expect(result[:merge_request].title).to eq("Draft: Resolve \"#{issue.title}\"")
end
end
@@ -193,10 +193,10 @@ describe MergeRequests::CreateFromIssueService do
it_behaves_like 'a service that creates a merge request from an issue'
- it 'sets the merge request title to: "WIP: $issue-branch-name', :sidekiq_might_not_need_inline do
+ it 'sets the merge request title to: "Draft: $issue-branch-name', :sidekiq_might_not_need_inline do
result = service.execute
- expect(result[:merge_request].title).to eq("WIP: #{issue.to_branch_name.titleize.humanize}")
+ expect(result[:merge_request].title).to eq("Draft: #{issue.to_branch_name.titleize.humanize}")
end
end
end
diff --git a/spec/services/merge_requests/create_pipeline_service_spec.rb b/spec/services/merge_requests/create_pipeline_service_spec.rb
index 9eb28759061..db46bd37eea 100644
--- a/spec/services/merge_requests/create_pipeline_service_spec.rb
+++ b/spec/services/merge_requests/create_pipeline_service_spec.rb
@@ -2,10 +2,13 @@
require 'spec_helper'
-describe MergeRequests::CreatePipelineService do
+RSpec.describe MergeRequests::CreatePipelineService do
+ include ProjectForksHelper
+
let_it_be(:project) { create(:project, :repository) }
let_it_be(:user) { create(:user) }
- let(:service) { described_class.new(project, user, params) }
+ let(:service) { described_class.new(project, actor, params) }
+ let(:actor) { user }
let(:params) { {} }
before do
@@ -26,11 +29,13 @@ describe MergeRequests::CreatePipelineService do
let(:merge_request) do
create(:merge_request,
source_branch: 'feature',
- source_project: project,
+ source_project: source_project,
target_branch: 'master',
target_project: project)
end
+ let(:source_project) { project }
+
it 'creates a detached merge request pipeline' do
expect { subject }.to change { Ci::Pipeline.count }.by(1)
@@ -42,6 +47,50 @@ describe MergeRequests::CreatePipelineService do
expect(subject.source).to eq('merge_request_event')
end
+ context 'with fork merge request' do
+ let_it_be(:forked_project) { fork_project(project, nil, repository: true, target_project: create(:project, :private, :repository)) }
+ let(:source_project) { forked_project }
+
+ context 'when actor has permission to create pipelines in target project' do
+ let(:actor) { user }
+
+ it 'creates a pipeline in the target project' do
+ expect(subject.project).to eq(project)
+ end
+
+ context 'when ci_allow_to_create_merge_request_pipelines_in_target_project feature flag is disabled' do
+ before do
+ stub_feature_flags(ci_allow_to_create_merge_request_pipelines_in_target_project: false)
+ end
+
+ it 'creates a pipeline in the source project' do
+ expect(subject.project).to eq(source_project)
+ end
+ end
+ end
+
+ context 'when actor has permission to create pipelines in forked project' do
+ let(:actor) { fork_user }
+ let(:fork_user) { create(:user) }
+
+ before do
+ source_project.add_developer(fork_user)
+ end
+
+ it 'creates a pipeline in the source project' do
+ expect(subject.project).to eq(source_project)
+ end
+ end
+
+ context 'when actor does not have permission to create pipelines' do
+ let(:actor) { create(:user) }
+
+ it 'returns nothing' do
+ expect(subject.full_error_messages).to include('Insufficient permissions to create a new pipeline')
+ end
+ end
+ end
+
context 'when service is called multiple times' do
it 'creates a pipeline once' do
expect do
diff --git a/spec/services/merge_requests/create_service_spec.rb b/spec/services/merge_requests/create_service_spec.rb
index bb40c399b6e..a8661f027e8 100644
--- a/spec/services/merge_requests/create_service_spec.rb
+++ b/spec/services/merge_requests/create_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe MergeRequests::CreateService, :clean_gitlab_redis_shared_state do
+RSpec.describe MergeRequests::CreateService, :clean_gitlab_redis_shared_state do
include ProjectForksHelper
let(:project) { create(:project, :repository) }
@@ -216,11 +216,12 @@ describe MergeRequests::CreateService, :clean_gitlab_redis_shared_state do
target_project.add_maintainer(user)
end
- it 'create legacy detached merge request pipeline for fork merge request' do
+ it 'create detached merge request pipeline for fork merge request' do
merge_request.reload
- expect(merge_request.actual_head_pipeline)
- .to be_legacy_detached_merge_request_pipeline
+ head_pipeline = merge_request.actual_head_pipeline
+ expect(head_pipeline).to be_detached_merge_request_pipeline
+ expect(head_pipeline.project).to eq(target_project)
end
end
@@ -339,13 +340,14 @@ describe MergeRequests::CreateService, :clean_gitlab_redis_shared_state do
end
end
- it_behaves_like 'new issuable record that supports quick actions' do
+ it_behaves_like 'issuable record that supports quick actions' do
let(:default_params) do
{
source_branch: 'feature',
target_branch: 'master'
}
end
+ let(:issuable) { described_class.new(project, user, params).execute }
end
context 'Quick actions' do
diff --git a/spec/services/merge_requests/delete_non_latest_diffs_service_spec.rb b/spec/services/merge_requests/delete_non_latest_diffs_service_spec.rb
index 2adf808619d..377615bbc6f 100644
--- a/spec/services/merge_requests/delete_non_latest_diffs_service_spec.rb
+++ b/spec/services/merge_requests/delete_non_latest_diffs_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe MergeRequests::DeleteNonLatestDiffsService, :clean_gitlab_redis_shared_state do
+RSpec.describe MergeRequests::DeleteNonLatestDiffsService, :clean_gitlab_redis_shared_state do
let(:merge_request) { create(:merge_request) }
let!(:subject) { described_class.new(merge_request) }
diff --git a/spec/services/merge_requests/ff_merge_service_spec.rb b/spec/services/merge_requests/ff_merge_service_spec.rb
index 415b351e13a..c3da02273a4 100644
--- a/spec/services/merge_requests/ff_merge_service_spec.rb
+++ b/spec/services/merge_requests/ff_merge_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe MergeRequests::FfMergeService do
+RSpec.describe MergeRequests::FfMergeService do
let(:user) { create(:user) }
let(:user2) { create(:user) }
let(:merge_request) do
diff --git a/spec/services/merge_requests/get_urls_service_spec.rb b/spec/services/merge_requests/get_urls_service_spec.rb
index 8cc627b64d9..053752626dc 100644
--- a/spec/services/merge_requests/get_urls_service_spec.rb
+++ b/spec/services/merge_requests/get_urls_service_spec.rb
@@ -2,7 +2,7 @@
require "spec_helper"
-describe MergeRequests::GetUrlsService do
+RSpec.describe MergeRequests::GetUrlsService do
include ProjectForksHelper
let(:project) { create(:project, :public, :repository) }
diff --git a/spec/services/merge_requests/link_lfs_objects_service_spec.rb b/spec/services/merge_requests/link_lfs_objects_service_spec.rb
index f07cf13e4f2..c1765e3a2ab 100644
--- a/spec/services/merge_requests/link_lfs_objects_service_spec.rb
+++ b/spec/services/merge_requests/link_lfs_objects_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe MergeRequests::LinkLfsObjectsService, :sidekiq_inline do
+RSpec.describe MergeRequests::LinkLfsObjectsService, :sidekiq_inline do
include ProjectForksHelper
include RepoHelpers
diff --git a/spec/services/merge_requests/merge_orchestration_service_spec.rb b/spec/services/merge_requests/merge_orchestration_service_spec.rb
index c50f20d7703..67dbb5a1a01 100644
--- a/spec/services/merge_requests/merge_orchestration_service_spec.rb
+++ b/spec/services/merge_requests/merge_orchestration_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe MergeRequests::MergeOrchestrationService do
+RSpec.describe MergeRequests::MergeOrchestrationService do
let_it_be(:maintainer) { create(:user) }
let(:merge_params) { { sha: merge_request.diff_head_sha } }
let(:user) { maintainer }
diff --git a/spec/services/merge_requests/merge_service_spec.rb b/spec/services/merge_requests/merge_service_spec.rb
index 2274d917527..11e341994f7 100644
--- a/spec/services/merge_requests/merge_service_spec.rb
+++ b/spec/services/merge_requests/merge_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe MergeRequests::MergeService do
+RSpec.describe MergeRequests::MergeService do
let_it_be(:user) { create(:user) }
let_it_be(:user2) { create(:user) }
let(:merge_request) { create(:merge_request, :simple, author: user2, assignees: [user2]) }
@@ -360,6 +360,25 @@ describe MergeRequests::MergeService do
expect(Gitlab::AppLogger).to have_received(:error).with(a_string_matching(error_message))
end
+ context 'when squashing is required' do
+ before do
+ merge_request.update!(source_branch: 'master', target_branch: 'feature')
+ merge_request.target_project.project_setting.squash_always!
+ end
+
+ it 'raises an error if squashing is not done' do
+ error_message = 'requires squashing commits'
+
+ service.execute(merge_request)
+
+ expect(merge_request).to be_open
+
+ expect(merge_request.merge_commit_sha).to be_nil
+ expect(merge_request.merge_error).to include(error_message)
+ expect(Gitlab::AppLogger).to have_received(:error).with(a_string_matching(error_message))
+ end
+ end
+
context 'when squashing' do
before do
merge_request.update!(source_branch: 'master', target_branch: 'feature')
diff --git a/spec/services/merge_requests/merge_to_ref_service_spec.rb b/spec/services/merge_requests/merge_to_ref_service_spec.rb
index 596d46f3c43..b482e8d6724 100644
--- a/spec/services/merge_requests/merge_to_ref_service_spec.rb
+++ b/spec/services/merge_requests/merge_to_ref_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe MergeRequests::MergeToRefService do
+RSpec.describe MergeRequests::MergeToRefService do
shared_examples_for 'MergeService for target ref' do
it 'target_ref has the same state of target branch' do
repo = merge_request.target_project.repository
diff --git a/spec/services/merge_requests/mergeability_check_service_spec.rb b/spec/services/merge_requests/mergeability_check_service_spec.rb
index 45519ddf3d3..543da46f883 100644
--- a/spec/services/merge_requests/mergeability_check_service_spec.rb
+++ b/spec/services/merge_requests/mergeability_check_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe MergeRequests::MergeabilityCheckService, :clean_gitlab_redis_shared_state do
+RSpec.describe MergeRequests::MergeabilityCheckService, :clean_gitlab_redis_shared_state do
shared_examples_for 'unmergeable merge request' do
it 'updates or keeps merge status as cannot_be_merged' do
subject
diff --git a/spec/services/merge_requests/migrate_external_diffs_service_spec.rb b/spec/services/merge_requests/migrate_external_diffs_service_spec.rb
index 233b944624f..6ea8626ba73 100644
--- a/spec/services/merge_requests/migrate_external_diffs_service_spec.rb
+++ b/spec/services/merge_requests/migrate_external_diffs_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe MergeRequests::MigrateExternalDiffsService do
+RSpec.describe MergeRequests::MigrateExternalDiffsService do
let(:merge_request) { create(:merge_request) }
let(:diff) { merge_request.merge_request_diff }
diff --git a/spec/services/merge_requests/post_merge_service_spec.rb b/spec/services/merge_requests/post_merge_service_spec.rb
index fff6ddf3928..a51a896ca96 100644
--- a/spec/services/merge_requests/post_merge_service_spec.rb
+++ b/spec/services/merge_requests/post_merge_service_spec.rb
@@ -2,11 +2,13 @@
require 'spec_helper'
-describe MergeRequests::PostMergeService do
+RSpec.describe MergeRequests::PostMergeService do
let(:user) { create(:user) }
let(:merge_request) { create(:merge_request, assignees: [user]) }
let(:project) { merge_request.project }
+ subject { described_class.new(project, user).execute(merge_request) }
+
before do
project.add_maintainer(user)
end
@@ -19,10 +21,7 @@ describe MergeRequests::PostMergeService do
project.open_merge_requests_count
merge_request.update!(state: 'merged')
- service = described_class.new(project, user, {})
-
- expect { service.execute(merge_request) }
- .to change { project.open_merge_requests_count }.from(1).to(0)
+ expect { subject }.to change { project.open_merge_requests_count }.from(1).to(0)
end
it 'updates metrics' do
@@ -35,7 +34,7 @@ describe MergeRequests::PostMergeService do
expect(metrics_service).to receive(:merge)
- described_class.new(project, user, {}).execute(merge_request)
+ subject
end
it 'deletes non-latest diffs' do
@@ -45,7 +44,7 @@ describe MergeRequests::PostMergeService do
.to receive(:new).with(merge_request)
.and_return(diff_removal_service)
- described_class.new(project, user, {}).execute(merge_request)
+ subject
expect(diff_removal_service).to have_received(:execute)
end
@@ -56,21 +55,63 @@ describe MergeRequests::PostMergeService do
issue = create(:issue, project: project)
allow(merge_request).to receive(:visible_closing_issues_for).and_return([issue])
- expect_next_instance_of(Issues::CloseService) do |service|
- allow(service).to receive(:execute).with(issue, commit: merge_request).and_raise(RuntimeError)
+ expect_next_instance_of(Issues::CloseService) do |close_service|
+ allow(close_service).to receive(:execute).with(issue, commit: merge_request).and_raise(RuntimeError)
end
- expect { described_class.new(project, user).execute(merge_request) }.to raise_error(RuntimeError)
+ expect { subject }.to raise_error(RuntimeError)
expect(merge_request.reload).to be_merged
end
it 'clean up environments for the merge request' do
- expect_next_instance_of(Ci::StopEnvironmentsService) do |service|
- expect(service).to receive(:execute_for_merge_request).with(merge_request)
+ expect_next_instance_of(Ci::StopEnvironmentsService) do |stop_environment_service|
+ expect(stop_environment_service).to receive(:execute_for_merge_request).with(merge_request)
end
- described_class.new(project, user).execute(merge_request)
+ subject
+ end
+
+ context 'when the merge request has review apps' do
+ it 'cancels all review app deployments' do
+ pipeline = create(:ci_pipeline,
+ source: :merge_request_event,
+ merge_request: merge_request,
+ project: project,
+ sha: merge_request.diff_head_sha,
+ merge_requests_as_head_pipeline: [merge_request])
+
+ review_env_a = create(:environment, project: project, state: :available, name: 'review/a')
+ review_env_b = create(:environment, project: project, state: :available, name: 'review/b')
+ review_env_c = create(:environment, project: project, state: :stopped, name: 'review/c')
+ deploy_env = create(:environment, project: project, state: :available, name: 'deploy')
+
+ review_job_a1 = create(:ci_build, :with_deployment, :start_review_app,
+ pipeline: pipeline, project: project, environment: review_env_a.name)
+ review_job_a2 = create(:ci_build, :with_deployment, :start_review_app,
+ pipeline: pipeline, project: project, environment: review_env_a.name)
+ finished_review_job_a = create(:ci_build, :with_deployment, :start_review_app,
+ pipeline: pipeline, project: project, status: :success, environment: review_env_a.name)
+ review_job_b1 = create(:ci_build, :with_deployment, :start_review_app,
+ pipeline: pipeline, project: project, environment: review_env_b.name)
+ review_job_b2 = create(:ci_build, :start_review_app,
+ pipeline: pipeline, project: project, environment: review_env_b.name)
+ review_job_c1 = create(:ci_build, :with_deployment, :start_review_app,
+ pipeline: pipeline, project: project, environment: review_env_c.name)
+ deploy_job = create(:ci_build, :with_deployment, :deploy_to_production,
+ pipeline: pipeline, project: project, environment: deploy_env.name)
+
+ subject
+
+ expect(review_job_a1.reload.canceled?).to be true
+ expect(review_job_a2.reload.canceled?).to be true
+ expect(finished_review_job_a.reload.status).to eq "success"
+ expect(finished_review_job_a.reload.canceled?).to be false
+ expect(review_job_b1.reload.canceled?).to be true
+ expect(review_job_b2.reload.canceled?).to be false
+ expect(review_job_c1.reload.canceled?).to be false
+ expect(deploy_job.reload.canceled?).to be false
+ end
end
end
end
diff --git a/spec/services/merge_requests/push_options_handler_service_spec.rb b/spec/services/merge_requests/push_options_handler_service_spec.rb
index 420c8513c72..55f92d6bd0a 100644
--- a/spec/services/merge_requests/push_options_handler_service_spec.rb
+++ b/spec/services/merge_requests/push_options_handler_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe MergeRequests::PushOptionsHandlerService do
+RSpec.describe MergeRequests::PushOptionsHandlerService do
include ProjectForksHelper
let(:user) { create(:user) }
diff --git a/spec/services/merge_requests/pushed_branches_service_spec.rb b/spec/services/merge_requests/pushed_branches_service_spec.rb
index 7b5d505f4d9..6e9c77bd3b6 100644
--- a/spec/services/merge_requests/pushed_branches_service_spec.rb
+++ b/spec/services/merge_requests/pushed_branches_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe MergeRequests::PushedBranchesService do
+RSpec.describe MergeRequests::PushedBranchesService do
let(:project) { create(:project) }
let!(:service) { described_class.new(project, nil, changes: pushed_branches) }
diff --git a/spec/services/merge_requests/rebase_service_spec.rb b/spec/services/merge_requests/rebase_service_spec.rb
index 69d555f838d..2e525f2ed01 100644
--- a/spec/services/merge_requests/rebase_service_spec.rb
+++ b/spec/services/merge_requests/rebase_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe MergeRequests::RebaseService do
+RSpec.describe MergeRequests::RebaseService do
include ProjectForksHelper
let(:user) { create(:user) }
diff --git a/spec/services/merge_requests/refresh_service_spec.rb b/spec/services/merge_requests/refresh_service_spec.rb
index e60ff6eb98a..18c4cef7087 100644
--- a/spec/services/merge_requests/refresh_service_spec.rb
+++ b/spec/services/merge_requests/refresh_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe MergeRequests::RefreshService do
+RSpec.describe MergeRequests::RefreshService do
include ProjectForksHelper
include ProjectHelpers
@@ -225,12 +225,13 @@ describe MergeRequests::RefreshService do
context 'when service runs on forked project' do
let(:project) { @fork_project }
- it 'creates legacy detached merge request pipeline for fork merge request', :sidekiq_might_not_need_inline do
+ it 'creates detached merge request pipeline for fork merge request', :sidekiq_inline do
expect { subject }
.to change { @fork_merge_request.pipelines_for_merge_request.count }.by(1)
- expect(@fork_merge_request.pipelines_for_merge_request.first)
- .to be_legacy_detached_merge_request_pipeline
+ merge_request_pipeline = @fork_merge_request.pipelines_for_merge_request.first
+ expect(merge_request_pipeline).to be_detached_merge_request_pipeline
+ expect(merge_request_pipeline.project).to eq(@project)
end
end
diff --git a/spec/services/merge_requests/reload_diffs_service_spec.rb b/spec/services/merge_requests/reload_diffs_service_spec.rb
index d2444af1b0f..3d5b65207e6 100644
--- a/spec/services/merge_requests/reload_diffs_service_spec.rb
+++ b/spec/services/merge_requests/reload_diffs_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe MergeRequests::ReloadDiffsService, :use_clean_rails_memory_store_caching do
+RSpec.describe MergeRequests::ReloadDiffsService, :use_clean_rails_memory_store_caching do
let(:current_user) { create(:user) }
let(:merge_request) { create(:merge_request) }
let(:subject) { described_class.new(merge_request, current_user) }
@@ -34,10 +34,8 @@ describe MergeRequests::ReloadDiffsService, :use_clean_rails_memory_store_cachin
context 'cache clearing' do
it 'clears the cache for older diffs on the merge request' do
- old_diff = merge_request.merge_request_diff
- old_cache_key = old_diff.diffs_collection.cache_key
-
- expect_any_instance_of(Redis).to receive(:del).with(old_cache_key).and_call_original
+ expect_any_instance_of(Redis).to receive(:del).once.and_call_original
+ expect(Rails.cache).to receive(:delete).once.and_call_original
subject.execute
end
diff --git a/spec/services/merge_requests/remove_approval_service_spec.rb b/spec/services/merge_requests/remove_approval_service_spec.rb
new file mode 100644
index 00000000000..40da928e832
--- /dev/null
+++ b/spec/services/merge_requests/remove_approval_service_spec.rb
@@ -0,0 +1,46 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe MergeRequests::RemoveApprovalService do
+ describe '#execute' do
+ let(:user) { create(:user) }
+ let(:project) { create(:project) }
+ let(:merge_request) { create(:merge_request, source_project: project) }
+ let!(:existing_approval) { create(:approval, merge_request: merge_request) }
+
+ subject(:service) { described_class.new(project, user) }
+
+ def execute!
+ service.execute(merge_request)
+ end
+
+ before do
+ project.add_developer(user)
+ end
+
+ context 'with a user who has approved' do
+ let!(:approval) { create(:approval, user: user, merge_request: merge_request) }
+
+ it 'removes the approval' do
+ expect { execute! }.to change { merge_request.approvals.size }.from(2).to(1)
+ end
+
+ it 'creates an unapproval note and triggers web hook' do
+ expect(service).to receive(:execute_hooks).with(merge_request, 'unapproved')
+ expect(SystemNoteService).to receive(:unapprove_mr)
+
+ execute!
+ end
+ end
+
+ context 'with a user who has not approved' do
+ it 'does not create an unapproval note and triggers web hook' do
+ expect(service).not_to receive(:execute_hooks)
+ expect(SystemNoteService).not_to receive(:unapprove_mr)
+
+ execute!
+ end
+ end
+ end
+end
diff --git a/spec/services/merge_requests/reopen_service_spec.rb b/spec/services/merge_requests/reopen_service_spec.rb
index 3807c44b01f..0066834180e 100644
--- a/spec/services/merge_requests/reopen_service_spec.rb
+++ b/spec/services/merge_requests/reopen_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe MergeRequests::ReopenService do
+RSpec.describe MergeRequests::ReopenService do
let(:user) { create(:user) }
let(:user2) { create(:user) }
let(:guest) { create(:user) }
diff --git a/spec/services/merge_requests/resolved_discussion_notification_service_spec.rb b/spec/services/merge_requests/resolved_discussion_notification_service_spec.rb
index 29896db58ac..874cf66659a 100644
--- a/spec/services/merge_requests/resolved_discussion_notification_service_spec.rb
+++ b/spec/services/merge_requests/resolved_discussion_notification_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe MergeRequests::ResolvedDiscussionNotificationService do
+RSpec.describe MergeRequests::ResolvedDiscussionNotificationService do
let(:merge_request) { create(:merge_request) }
let(:user) { create(:user) }
let(:project) { merge_request.project }
diff --git a/spec/services/merge_requests/squash_service_spec.rb b/spec/services/merge_requests/squash_service_spec.rb
index a53314ed737..1ec1dc0f6eb 100644
--- a/spec/services/merge_requests/squash_service_spec.rb
+++ b/spec/services/merge_requests/squash_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe MergeRequests::SquashService do
+RSpec.describe MergeRequests::SquashService do
include GitHelpers
let(:service) { described_class.new(project, user, { merge_request: merge_request }) }
@@ -131,6 +131,42 @@ describe MergeRequests::SquashService do
include_examples 'the squash succeeds'
end
+ context 'when squashing is disabled by default on the project' do
+ # Squashing is disabled by default, but it should still allow you
+ # to squash-and-merge if selected through the UI
+ let(:merge_request) { merge_request_with_only_new_files }
+
+ before do
+ merge_request.project.project_setting.squash_default_off!
+ end
+
+ include_examples 'the squash succeeds'
+ end
+
+ context 'when squashing is forbidden on the project' do
+ let(:merge_request) { merge_request_with_only_new_files }
+
+ before do
+ merge_request.project.project_setting.squash_never!
+ end
+
+ it 'raises a squash error' do
+ expect(service.execute).to match(
+ status: :error,
+ message: a_string_including('does not allow squashing commits when merge requests are accepted'))
+ end
+ end
+
+ context 'when squashing is enabled by default on the project' do
+ let(:merge_request) { merge_request_with_only_new_files }
+
+ before do
+ merge_request.project.project_setting.squash_always!
+ end
+
+ include_examples 'the squash succeeds'
+ end
+
context 'when squashing with files too large to display' do
let(:merge_request) { merge_request_with_large_files }
diff --git a/spec/services/merge_requests/update_service_spec.rb b/spec/services/merge_requests/update_service_spec.rb
index 2b934b24757..c3433c8c9d2 100644
--- a/spec/services/merge_requests/update_service_spec.rb
+++ b/spec/services/merge_requests/update_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe MergeRequests::UpdateService, :mailer do
+RSpec.describe MergeRequests::UpdateService, :mailer do
include ProjectForksHelper
let(:group) { create(:group, :public) }
@@ -737,5 +737,10 @@ describe MergeRequests::UpdateService, :mailer do
.to change { merge_request.reload.force_remove_source_branch? }.from(nil).to(true)
end
end
+
+ it_behaves_like 'issuable record that supports quick actions' do
+ let(:existing_merge_request) { create(:merge_request, source_project: project) }
+ let(:issuable) { described_class.new(project, user, params).execute(existing_merge_request) }
+ end
end
end
diff --git a/spec/services/metrics/dashboard/annotations/create_service_spec.rb b/spec/services/metrics/dashboard/annotations/create_service_spec.rb
index 7dabca3c860..c3fe7238047 100644
--- a/spec/services/metrics/dashboard/annotations/create_service_spec.rb
+++ b/spec/services/metrics/dashboard/annotations/create_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Metrics::Dashboard::Annotations::CreateService do
+RSpec.describe Metrics::Dashboard::Annotations::CreateService do
let_it_be(:user) { create(:user) }
let(:description) { 'test annotation' }
let(:dashboard_path) { 'config/prometheus/common_metrics.yml' }
diff --git a/spec/services/metrics/dashboard/annotations/delete_service_spec.rb b/spec/services/metrics/dashboard/annotations/delete_service_spec.rb
index 95825db6902..ec2bd3772bf 100644
--- a/spec/services/metrics/dashboard/annotations/delete_service_spec.rb
+++ b/spec/services/metrics/dashboard/annotations/delete_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Metrics::Dashboard::Annotations::DeleteService do
+RSpec.describe Metrics::Dashboard::Annotations::DeleteService do
let(:user) { create(:user) }
let(:service_instance) { described_class.new(user, annotation) }
diff --git a/spec/services/metrics/dashboard/clone_dashboard_service_spec.rb b/spec/services/metrics/dashboard/clone_dashboard_service_spec.rb
index 3d26ab2ede5..4a226fe386c 100644
--- a/spec/services/metrics/dashboard/clone_dashboard_service_spec.rb
+++ b/spec/services/metrics/dashboard/clone_dashboard_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Metrics::Dashboard::CloneDashboardService, :use_clean_rails_memory_store_caching do
+RSpec.describe Metrics::Dashboard::CloneDashboardService, :use_clean_rails_memory_store_caching do
include MetricsDashboardHelpers
let_it_be(:user) { create(:user) }
@@ -81,7 +81,22 @@ describe Metrics::Dashboard::CloneDashboardService, :use_clean_rails_memory_stor
allow(::Gitlab::Metrics::Dashboard::Processor).to receive(:new).and_return(double(process: file_content_hash))
end
- it_behaves_like 'valid dashboard cloning process', ::Metrics::Dashboard::SystemDashboardService::DASHBOARD_PATH, [::Gitlab::Metrics::Dashboard::Stages::CommonMetricsInserter, ::Gitlab::Metrics::Dashboard::Stages::CustomMetricsInserter, ::Gitlab::Metrics::Dashboard::Stages::Sorter]
+ it_behaves_like 'valid dashboard cloning process', ::Metrics::Dashboard::SystemDashboardService::DASHBOARD_PATH,
+ [
+ ::Gitlab::Metrics::Dashboard::Stages::CommonMetricsInserter,
+ ::Gitlab::Metrics::Dashboard::Stages::CustomMetricsInserter,
+ ::Gitlab::Metrics::Dashboard::Stages::Sorter
+ ]
+
+ it_behaves_like 'valid dashboard cloning process', ::Metrics::Dashboard::ClusterDashboardService::DASHBOARD_PATH,
+ [
+ ::Gitlab::Metrics::Dashboard::Stages::CommonMetricsInserter,
+ ::Gitlab::Metrics::Dashboard::Stages::Sorter
+ ]
+
+ it_behaves_like 'valid dashboard cloning process',
+ ::Metrics::Dashboard::SelfMonitoringDashboardService::DASHBOARD_PATH,
+ [::Gitlab::Metrics::Dashboard::Stages::CustomMetricsInserter]
context 'selected branch already exists' do
let(:branch) { 'existing_branch' }
diff --git a/spec/services/metrics/dashboard/cluster_dashboard_service_spec.rb b/spec/services/metrics/dashboard/cluster_dashboard_service_spec.rb
new file mode 100644
index 00000000000..f2e32d5eb35
--- /dev/null
+++ b/spec/services/metrics/dashboard/cluster_dashboard_service_spec.rb
@@ -0,0 +1,59 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Metrics::Dashboard::ClusterDashboardService, :use_clean_rails_memory_store_caching do
+ include MetricsDashboardHelpers
+
+ let_it_be(:user) { create(:user) }
+ let_it_be(:cluster_project) { create(:cluster_project) }
+ let_it_be(:cluster) { cluster_project.cluster }
+ let_it_be(:project) { cluster_project.project }
+
+ before do
+ project.add_maintainer(user)
+ end
+
+ describe '.valid_params?' do
+ let(:params) { { cluster: cluster, embedded: 'false' } }
+
+ subject { described_class.valid_params?(params) }
+
+ it { is_expected.to be_truthy }
+
+ context 'with matching dashboard_path' do
+ let(:params) { { dashboard_path: ::Metrics::Dashboard::ClusterDashboardService::DASHBOARD_PATH } }
+
+ it { is_expected.to be_truthy }
+ end
+
+ context 'missing cluster without dashboard_path' do
+ let(:params) { {} }
+
+ it { is_expected.to be_falsey }
+ end
+ end
+
+ describe '#get_dashboard' do
+ let(:service_params) { [project, user, { cluster: cluster, cluster_type: :project }] }
+ let(:service_call) { subject.get_dashboard }
+
+ subject { described_class.new(*service_params) }
+
+ it_behaves_like 'valid dashboard service response'
+ it_behaves_like 'caches the unprocessed dashboard for subsequent calls'
+ it_behaves_like 'refreshes cache when dashboard_version is changed'
+
+ it_behaves_like 'dashboard_version contains SHA256 hash of dashboard file content' do
+ let(:dashboard_path) { described_class::DASHBOARD_PATH }
+ let(:dashboard_version) { subject.send(:dashboard_version) }
+ end
+
+ context 'when called with a non-system dashboard' do
+ let(:dashboard_path) { 'garbage/dashboard/path' }
+
+ # We want to always return the cluster dashboard.
+ it_behaves_like 'valid dashboard service response'
+ end
+ end
+end
diff --git a/spec/services/metrics/dashboard/cluster_metrics_embed_service_spec.rb b/spec/services/metrics/dashboard/cluster_metrics_embed_service_spec.rb
new file mode 100644
index 00000000000..e80911d6265
--- /dev/null
+++ b/spec/services/metrics/dashboard/cluster_metrics_embed_service_spec.rb
@@ -0,0 +1,72 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Metrics::Dashboard::ClusterMetricsEmbedService, :use_clean_rails_memory_store_caching do
+ include MetricsDashboardHelpers
+ using RSpec::Parameterized::TableSyntax
+
+ let_it_be(:user) { create(:user) }
+ let_it_be(:cluster_project) { create(:cluster_project) }
+ let_it_be(:cluster) { cluster_project.cluster }
+ let_it_be(:project) { cluster_project.project }
+
+ before do
+ project.add_maintainer(user)
+ end
+
+ describe '.valid_params?' do
+ let(:valid_params) { { cluster: 1, embedded: 'true', group: 'hello', title: 'world', y_label: 'countries' } }
+
+ subject { described_class }
+
+ it { expect(subject.valid_params?(valid_params)).to be_truthy }
+
+ context 'missing all params' do
+ let(:params) { {} }
+
+ it { expect(subject.valid_params?(params)).to be_falsy }
+ end
+
+ [:cluster, :embedded, :group, :title, :y_label].each do |param_key|
+ it 'returns false with missing param' do
+ params = valid_params.except(param_key)
+
+ expect(subject.valid_params?(params)).to be_falsy
+ end
+ end
+ end
+
+ describe '#get_dashboard' do
+ let(:service_params) do
+ [
+ project,
+ user,
+ {
+ cluster: cluster,
+ cluster_type: :project,
+ embedded: 'true',
+ group: 'Cluster Health',
+ title: 'CPU Usage',
+ y_label: 'CPU (cores)'
+ }
+ ]
+ end
+ let(:service_call) { described_class.new(*service_params).get_dashboard }
+ let(:panel_groups) { service_call[:dashboard][:panel_groups] }
+ let(:panel) { panel_groups.first[:panels].first }
+
+ it_behaves_like 'valid embedded dashboard service response'
+ it_behaves_like 'caches the unprocessed dashboard for subsequent calls'
+
+ it 'returns one panel' do
+ expect(panel_groups.size).to eq 1
+ expect(panel_groups.first[:panels].size).to eq 1
+ end
+
+ it 'returns panel by title and y_label' do
+ expect(panel[:title]).to eq(service_params.last[:title])
+ expect(panel[:y_label]).to eq(service_params.last[:y_label])
+ end
+ end
+end
diff --git a/spec/services/metrics/dashboard/custom_dashboard_service_spec.rb b/spec/services/metrics/dashboard/custom_dashboard_service_spec.rb
index 4966b83bbab..d4391ecb6b9 100644
--- a/spec/services/metrics/dashboard/custom_dashboard_service_spec.rb
+++ b/spec/services/metrics/dashboard/custom_dashboard_service_spec.rb
@@ -2,24 +2,31 @@
require 'spec_helper'
-describe Metrics::Dashboard::CustomDashboardService, :use_clean_rails_memory_store_caching do
+RSpec.describe Metrics::Dashboard::CustomDashboardService, :use_clean_rails_memory_store_caching do
include MetricsDashboardHelpers
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project) }
let_it_be(:environment) { create(:environment, project: project) }
+ let(:dashboard_path) { '.gitlab/dashboards/test.yml' }
+ let(:service_params) { [project, user, { environment: environment, dashboard_path: dashboard_path }] }
+
+ subject { described_class.new(*service_params) }
+
before do
project.add_maintainer(user)
end
+ describe '#raw_dashboard' do
+ let(:project) { project_with_dashboard(dashboard_path) }
+
+ it_behaves_like '#raw_dashboard raises error if dashboard loading fails'
+ end
+
describe '#get_dashboard' do
- let(:dashboard_path) { '.gitlab/dashboards/test.yml' }
- let(:service_params) { [project, user, { environment: environment, dashboard_path: dashboard_path }] }
let(:service_call) { subject.get_dashboard }
- subject { described_class.new(*service_params) }
-
context 'when the dashboard does not exist' do
it_behaves_like 'misconfigured dashboard service response', :not_found
@@ -92,7 +99,8 @@ describe Metrics::Dashboard::CustomDashboardService, :use_clean_rails_memory_sto
path: dashboard_path,
display_name: 'test.yml',
default: false,
- system_dashboard: false
+ system_dashboard: false,
+ out_of_the_box_dashboard: false
}]
)
end
diff --git a/spec/services/metrics/dashboard/custom_metric_embed_service_spec.rb b/spec/services/metrics/dashboard/custom_metric_embed_service_spec.rb
index 1a9ddc87ab0..a5f7c2ab8ab 100644
--- a/spec/services/metrics/dashboard/custom_metric_embed_service_spec.rb
+++ b/spec/services/metrics/dashboard/custom_metric_embed_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Metrics::Dashboard::CustomMetricEmbedService do
+RSpec.describe Metrics::Dashboard::CustomMetricEmbedService do
include MetricsDashboardHelpers
let_it_be(:project, reload: true) { build(:project) }
diff --git a/spec/services/metrics/dashboard/default_embed_service_spec.rb b/spec/services/metrics/dashboard/default_embed_service_spec.rb
index 8e32316433d..2ce10eac026 100644
--- a/spec/services/metrics/dashboard/default_embed_service_spec.rb
+++ b/spec/services/metrics/dashboard/default_embed_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Metrics::Dashboard::DefaultEmbedService, :use_clean_rails_memory_store_caching do
+RSpec.describe Metrics::Dashboard::DefaultEmbedService, :use_clean_rails_memory_store_caching do
include MetricsDashboardHelpers
let_it_be(:project) { build(:project) }
diff --git a/spec/services/metrics/dashboard/dynamic_embed_service_spec.rb b/spec/services/metrics/dashboard/dynamic_embed_service_spec.rb
index ee75284b4ce..72b356be60f 100644
--- a/spec/services/metrics/dashboard/dynamic_embed_service_spec.rb
+++ b/spec/services/metrics/dashboard/dynamic_embed_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Metrics::Dashboard::DynamicEmbedService, :use_clean_rails_memory_store_caching do
+RSpec.describe Metrics::Dashboard::DynamicEmbedService, :use_clean_rails_memory_store_caching do
include MetricsDashboardHelpers
let_it_be(:project) { build(:project) }
diff --git a/spec/services/metrics/dashboard/gitlab_alert_embed_service_spec.rb b/spec/services/metrics/dashboard/gitlab_alert_embed_service_spec.rb
index a66150be42c..29c941826b5 100644
--- a/spec/services/metrics/dashboard/gitlab_alert_embed_service_spec.rb
+++ b/spec/services/metrics/dashboard/gitlab_alert_embed_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Metrics::Dashboard::GitlabAlertEmbedService do
+RSpec.describe Metrics::Dashboard::GitlabAlertEmbedService do
include MetricsDashboardHelpers
let_it_be(:alert) { create(:prometheus_alert) }
diff --git a/spec/services/metrics/dashboard/grafana_metric_embed_service_spec.rb b/spec/services/metrics/dashboard/grafana_metric_embed_service_spec.rb
index 3547e1f0f8c..ee3c55cb642 100644
--- a/spec/services/metrics/dashboard/grafana_metric_embed_service_spec.rb
+++ b/spec/services/metrics/dashboard/grafana_metric_embed_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Metrics::Dashboard::GrafanaMetricEmbedService do
+RSpec.describe Metrics::Dashboard::GrafanaMetricEmbedService do
include MetricsDashboardHelpers
include ReactiveCachingHelpers
include GrafanaApiHelpers
@@ -182,7 +182,7 @@ describe Metrics::Dashboard::GrafanaMetricEmbedService do
end
end
-describe Metrics::Dashboard::GrafanaUidParser do
+RSpec.describe Metrics::Dashboard::GrafanaUidParser do
let_it_be(:grafana_integration) { create(:grafana_integration) }
let_it_be(:project) { grafana_integration.project }
@@ -213,7 +213,7 @@ describe Metrics::Dashboard::GrafanaUidParser do
end
end
-describe Metrics::Dashboard::DatasourceNameParser do
+RSpec.describe Metrics::Dashboard::DatasourceNameParser do
include GrafanaApiHelpers
let(:grafana_url) { valid_grafana_dashboard_link('https://gitlab.grafana.net') }
diff --git a/spec/services/metrics/dashboard/pod_dashboard_service_spec.rb b/spec/services/metrics/dashboard/pod_dashboard_service_spec.rb
index 1e62a5504a9..ae0e38a04b2 100644
--- a/spec/services/metrics/dashboard/pod_dashboard_service_spec.rb
+++ b/spec/services/metrics/dashboard/pod_dashboard_service_spec.rb
@@ -2,17 +2,26 @@
require 'spec_helper'
-describe Metrics::Dashboard::PodDashboardService, :use_clean_rails_memory_store_caching do
+RSpec.describe Metrics::Dashboard::PodDashboardService, :use_clean_rails_memory_store_caching do
include MetricsDashboardHelpers
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project) }
let_it_be(:environment) { create(:environment, project: project) }
+ let(:dashboard_path) { described_class::DASHBOARD_PATH }
+ let(:service_params) { [project, user, { environment: environment, dashboard_path: dashboard_path }] }
+
before do
project.add_maintainer(user)
end
+ subject { described_class.new(*service_params) }
+
+ describe '#raw_dashboard' do
+ it_behaves_like '#raw_dashboard raises error if dashboard loading fails'
+ end
+
describe '.valid_params?' do
let(:params) { { dashboard_path: described_class::DASHBOARD_PATH } }
@@ -34,14 +43,15 @@ describe Metrics::Dashboard::PodDashboardService, :use_clean_rails_memory_store_
end
describe '#get_dashboard' do
- let(:dashboard_path) { described_class::DASHBOARD_PATH }
- let(:service_params) { [project, user, { environment: environment, dashboard_path: dashboard_path }] }
let(:service_call) { subject.get_dashboard }
- subject { described_class.new(*service_params) }
-
it_behaves_like 'valid dashboard service response'
it_behaves_like 'caches the unprocessed dashboard for subsequent calls'
+ it_behaves_like 'refreshes cache when dashboard_version is changed'
it_behaves_like 'updates gitlab_metrics_dashboard_processing_time_ms metric'
+
+ it_behaves_like 'dashboard_version contains SHA256 hash of dashboard file content' do
+ let(:dashboard_version) { subject.send(:dashboard_version) }
+ end
end
end
diff --git a/spec/services/metrics/dashboard/self_monitoring_dashboard_service_spec.rb b/spec/services/metrics/dashboard/self_monitoring_dashboard_service_spec.rb
index 6c9a380a470..aea3e41a013 100644
--- a/spec/services/metrics/dashboard/self_monitoring_dashboard_service_spec.rb
+++ b/spec/services/metrics/dashboard/self_monitoring_dashboard_service_spec.rb
@@ -2,20 +2,29 @@
require 'spec_helper'
-describe Metrics::Dashboard::SelfMonitoringDashboardService, :use_clean_rails_memory_store_caching do
+RSpec.describe Metrics::Dashboard::SelfMonitoringDashboardService, :use_clean_rails_memory_store_caching do
include MetricsDashboardHelpers
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project) }
let_it_be(:environment) { create(:environment, project: project) }
+ let(:service_params) { [project, user, { environment: environment }] }
+
before do
project.add_maintainer(user)
stub_application_setting(self_monitoring_project_id: project.id)
end
+ subject do
+ described_class.new(service_params)
+ end
+
+ describe '#raw_dashboard' do
+ it_behaves_like '#raw_dashboard raises error if dashboard loading fails'
+ end
+
describe '#get_dashboard' do
- let(:service_params) { [project, user, { environment: environment }] }
let(:service_call) { subject.get_dashboard }
subject { described_class.new(*service_params) }
@@ -23,7 +32,13 @@ describe Metrics::Dashboard::SelfMonitoringDashboardService, :use_clean_rails_me
it_behaves_like 'valid dashboard service response'
it_behaves_like 'raises error for users with insufficient permissions'
it_behaves_like 'caches the unprocessed dashboard for subsequent calls'
+ it_behaves_like 'refreshes cache when dashboard_version is changed'
it_behaves_like 'updates gitlab_metrics_dashboard_processing_time_ms metric'
+
+ it_behaves_like 'dashboard_version contains SHA256 hash of dashboard file content' do
+ let(:dashboard_path) { described_class::DASHBOARD_PATH }
+ let(:dashboard_version) { subject.send(:dashboard_version) }
+ end
end
describe '.all_dashboard_paths' do
@@ -35,7 +50,8 @@ describe Metrics::Dashboard::SelfMonitoringDashboardService, :use_clean_rails_me
path: described_class::DASHBOARD_PATH,
display_name: described_class::DASHBOARD_NAME,
default: true,
- system_dashboard: false
+ system_dashboard: false,
+ out_of_the_box_dashboard: true
}]
)
end
diff --git a/spec/services/metrics/dashboard/system_dashboard_service_spec.rb b/spec/services/metrics/dashboard/system_dashboard_service_spec.rb
index 7d58501ae3f..ced7c29b507 100644
--- a/spec/services/metrics/dashboard/system_dashboard_service_spec.rb
+++ b/spec/services/metrics/dashboard/system_dashboard_service_spec.rb
@@ -2,29 +2,39 @@
require 'spec_helper'
-describe Metrics::Dashboard::SystemDashboardService, :use_clean_rails_memory_store_caching do
+RSpec.describe Metrics::Dashboard::SystemDashboardService, :use_clean_rails_memory_store_caching do
include MetricsDashboardHelpers
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project) }
let_it_be(:environment) { create(:environment, project: project) }
+ let(:dashboard_path) { described_class::DASHBOARD_PATH }
+ let(:service_params) { [project, user, { environment: environment, dashboard_path: dashboard_path }] }
+
+ subject { described_class.new(*service_params) }
+
before do
project.add_maintainer(user)
end
+ describe '#raw_dashboard' do
+ it_behaves_like '#raw_dashboard raises error if dashboard loading fails'
+ end
+
describe '#get_dashboard' do
- let(:dashboard_path) { described_class::DASHBOARD_PATH }
- let(:service_params) { [project, user, { environment: environment, dashboard_path: dashboard_path }] }
let(:service_call) { subject.get_dashboard }
- subject { described_class.new(*service_params) }
-
it_behaves_like 'valid dashboard service response'
it_behaves_like 'raises error for users with insufficient permissions'
it_behaves_like 'caches the unprocessed dashboard for subsequent calls'
+ it_behaves_like 'refreshes cache when dashboard_version is changed'
it_behaves_like 'updates gitlab_metrics_dashboard_processing_time_ms metric'
+ it_behaves_like 'dashboard_version contains SHA256 hash of dashboard file content' do
+ let(:dashboard_version) { subject.send(:dashboard_version) }
+ end
+
context 'when called with a non-system dashboard' do
let(:dashboard_path) { 'garbage/dashboard/path' }
@@ -42,7 +52,8 @@ describe Metrics::Dashboard::SystemDashboardService, :use_clean_rails_memory_sto
path: described_class::DASHBOARD_PATH,
display_name: described_class::DASHBOARD_NAME,
default: true,
- system_dashboard: true
+ system_dashboard: true,
+ out_of_the_box_dashboard: true
}]
)
end
diff --git a/spec/services/metrics/dashboard/transient_embed_service_spec.rb b/spec/services/metrics/dashboard/transient_embed_service_spec.rb
index 125fff7c23c..3fd0c97d909 100644
--- a/spec/services/metrics/dashboard/transient_embed_service_spec.rb
+++ b/spec/services/metrics/dashboard/transient_embed_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Metrics::Dashboard::TransientEmbedService, :use_clean_rails_memory_store_caching do
+RSpec.describe Metrics::Dashboard::TransientEmbedService, :use_clean_rails_memory_store_caching do
let_it_be(:project) { build(:project) }
let_it_be(:user) { create(:user) }
let_it_be(:environment) { create(:environment, project: project) }
diff --git a/spec/services/metrics/dashboard/update_dashboard_service_spec.rb b/spec/services/metrics/dashboard/update_dashboard_service_spec.rb
index fce027688d9..148005480ea 100644
--- a/spec/services/metrics/dashboard/update_dashboard_service_spec.rb
+++ b/spec/services/metrics/dashboard/update_dashboard_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Metrics::Dashboard::UpdateDashboardService, :use_clean_rails_memory_store_caching do
+RSpec.describe Metrics::Dashboard::UpdateDashboardService, :use_clean_rails_memory_store_caching do
include MetricsDashboardHelpers
let_it_be(:user) { create(:user) }
diff --git a/spec/services/metrics/sample_metrics_service_spec.rb b/spec/services/metrics/sample_metrics_service_spec.rb
index 3b4f7cb8062..b94345500f0 100644
--- a/spec/services/metrics/sample_metrics_service_spec.rb
+++ b/spec/services/metrics/sample_metrics_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Metrics::SampleMetricsService do
+RSpec.describe Metrics::SampleMetricsService do
describe 'query' do
let(:range_start) { '2019-12-02T23:31:45.000Z' }
let(:range_end) { '2019-12-03T00:01:45.000Z' }
diff --git a/spec/services/metrics/users_starred_dashboards/create_service_spec.rb b/spec/services/metrics/users_starred_dashboards/create_service_spec.rb
index eac4965ba44..910b556b8dd 100644
--- a/spec/services/metrics/users_starred_dashboards/create_service_spec.rb
+++ b/spec/services/metrics/users_starred_dashboards/create_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Metrics::UsersStarredDashboards::CreateService do
+RSpec.describe Metrics::UsersStarredDashboards::CreateService do
let_it_be(:user) { create(:user) }
let(:dashboard_path) { 'config/prometheus/common_metrics.yml' }
let(:service_instance) { described_class.new(user, project, dashboard_path) }
diff --git a/spec/services/metrics/users_starred_dashboards/delete_service_spec.rb b/spec/services/metrics/users_starred_dashboards/delete_service_spec.rb
index 68a2fef5931..5cdffe681eb 100644
--- a/spec/services/metrics/users_starred_dashboards/delete_service_spec.rb
+++ b/spec/services/metrics/users_starred_dashboards/delete_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Metrics::UsersStarredDashboards::DeleteService do
+RSpec.describe Metrics::UsersStarredDashboards::DeleteService do
subject(:service_instance) { described_class.new(user, project, dashboard_path) }
let_it_be(:user) { create(:user) }
diff --git a/spec/services/milestones/close_service_spec.rb b/spec/services/milestones/close_service_spec.rb
index 55e705063b2..53751b40667 100644
--- a/spec/services/milestones/close_service_spec.rb
+++ b/spec/services/milestones/close_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Milestones::CloseService do
+RSpec.describe Milestones::CloseService do
let(:user) { create(:user) }
let(:project) { create(:project) }
let(:milestone) { create(:milestone, title: "Milestone v1.2", project: project) }
diff --git a/spec/services/milestones/closed_issues_count_service_spec.rb b/spec/services/milestones/closed_issues_count_service_spec.rb
index b86eede2e22..a3865d08972 100644
--- a/spec/services/milestones/closed_issues_count_service_spec.rb
+++ b/spec/services/milestones/closed_issues_count_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Milestones::ClosedIssuesCountService, :use_clean_rails_memory_store_caching do
+RSpec.describe Milestones::ClosedIssuesCountService, :use_clean_rails_memory_store_caching do
let(:project) { create(:project) }
let(:milestone) { create(:milestone, project: project) }
diff --git a/spec/services/milestones/create_service_spec.rb b/spec/services/milestones/create_service_spec.rb
index 97f6e947539..93ca4ff653f 100644
--- a/spec/services/milestones/create_service_spec.rb
+++ b/spec/services/milestones/create_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Milestones::CreateService do
+RSpec.describe Milestones::CreateService do
let(:project) { create(:project) }
let(:user) { create(:user) }
diff --git a/spec/services/milestones/destroy_service_spec.rb b/spec/services/milestones/destroy_service_spec.rb
index 4f16421c39f..66c5c504c64 100644
--- a/spec/services/milestones/destroy_service_spec.rb
+++ b/spec/services/milestones/destroy_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Milestones::DestroyService do
+RSpec.describe Milestones::DestroyService do
let(:user) { create(:user) }
let(:project) { create(:project, :repository) }
let(:milestone) { create(:milestone, title: 'Milestone v1.0', project: project) }
diff --git a/spec/services/milestones/find_or_create_service_spec.rb b/spec/services/milestones/find_or_create_service_spec.rb
index ae3def30982..1bcaf578441 100644
--- a/spec/services/milestones/find_or_create_service_spec.rb
+++ b/spec/services/milestones/find_or_create_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Milestones::FindOrCreateService do
+RSpec.describe Milestones::FindOrCreateService do
describe '#execute' do
subject(:service) { described_class.new(project, user, params) }
diff --git a/spec/services/milestones/issues_count_service_spec.rb b/spec/services/milestones/issues_count_service_spec.rb
index 22aea884424..c944055e4e7 100644
--- a/spec/services/milestones/issues_count_service_spec.rb
+++ b/spec/services/milestones/issues_count_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Milestones::IssuesCountService, :use_clean_rails_memory_store_caching do
+RSpec.describe Milestones::IssuesCountService, :use_clean_rails_memory_store_caching do
let(:project) { create(:project) }
let(:milestone) { create(:milestone, project: project) }
diff --git a/spec/services/milestones/promote_service_spec.rb b/spec/services/milestones/promote_service_spec.rb
index fa893b86cdb..f0a34241c74 100644
--- a/spec/services/milestones/promote_service_spec.rb
+++ b/spec/services/milestones/promote_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Milestones::PromoteService do
+RSpec.describe Milestones::PromoteService do
let(:group) { create(:group) }
let(:project) { create(:project, namespace: group) }
let(:user) { create(:user) }
diff --git a/spec/services/milestones/transfer_service_spec.rb b/spec/services/milestones/transfer_service_spec.rb
index 9f94d2d320b..4a626fe688a 100644
--- a/spec/services/milestones/transfer_service_spec.rb
+++ b/spec/services/milestones/transfer_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Milestones::TransferService do
+RSpec.describe Milestones::TransferService do
describe '#execute' do
subject(:service) { described_class.new(user, old_group, project) }
diff --git a/spec/services/milestones/update_service_spec.rb b/spec/services/milestones/update_service_spec.rb
index 3b91442c0ba..85fd89c11ac 100644
--- a/spec/services/milestones/update_service_spec.rb
+++ b/spec/services/milestones/update_service_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe Milestones::UpdateService do
+RSpec.describe Milestones::UpdateService do
let(:project) { create(:project) }
let(:user) { build(:user) }
let(:milestone) { create(:milestone, project: project) }
diff --git a/spec/services/namespaces/check_storage_size_service_spec.rb b/spec/services/namespaces/check_storage_size_service_spec.rb
deleted file mode 100644
index e192f897cf9..00000000000
--- a/spec/services/namespaces/check_storage_size_service_spec.rb
+++ /dev/null
@@ -1,165 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-describe Namespaces::CheckStorageSizeService, '#execute' do
- let(:namespace) { build_stubbed(:namespace) }
- let(:user) { build(:user, namespace: namespace) }
- let(:service) { described_class.new(namespace, user) }
- let(:current_size) { 150.megabytes }
- let(:limit) { 100.megabytes }
-
- subject(:response) { service.execute }
-
- before do
- allow(namespace).to receive(:root_ancestor).and_return(namespace)
-
- root_storage_size = instance_double("RootStorageSize",
- current_size: current_size,
- limit: limit,
- usage_ratio: limit == 0 ? 0 : current_size.to_f / limit.to_f,
- above_size_limit?: current_size > limit
- )
-
- expect(Namespace::RootStorageSize).to receive(:new).and_return(root_storage_size)
- end
-
- context 'feature flag' do
- it 'is successful when disabled' do
- stub_feature_flags(namespace_storage_limit: false)
-
- expect(response).to be_success
- end
-
- it 'errors when enabled' do
- stub_feature_flags(namespace_storage_limit: true)
-
- expect(response).to be_error
- end
-
- it 'is successful when feature flag is activated for another namespace' do
- stub_feature_flags(namespace_storage_limit: build(:namespace))
-
- expect(response).to be_success
- end
-
- it 'errors when feature flag is activated for the current namespace' do
- stub_feature_flags(namespace_storage_limit: namespace)
-
- expect(response).to be_error
- expect(response.message).to be_present
- end
- end
-
- context 'when limit is set to 0' do
- let(:limit) { 0 }
-
- it 'is successful and has no payload' do
- expect(response).to be_success
- expect(response.payload).to be_empty
- end
- end
-
- context 'when current size is below threshold' do
- let(:current_size) { 10.megabytes }
-
- it 'is successful and has no payload' do
- expect(response).to be_success
- expect(response.payload).to be_empty
- end
- end
-
- context 'when not admin of the namespace' do
- let(:other_namespace) { build_stubbed(:namespace) }
-
- subject(:response) { described_class.new(other_namespace, user).execute }
-
- before do
- allow(other_namespace).to receive(:root_ancestor).and_return(other_namespace)
- end
-
- it 'errors and has no payload' do
- expect(response).to be_error
- expect(response.payload).to be_empty
- end
- end
-
- context 'when providing the child namespace' do
- let(:namespace) { build_stubbed(:group) }
- let(:child_namespace) { build_stubbed(:group, parent: namespace) }
-
- subject(:response) { described_class.new(child_namespace, user).execute }
-
- before do
- allow(child_namespace).to receive(:root_ancestor).and_return(namespace)
- namespace.add_owner(user)
- end
-
- it 'uses the root namespace' do
- expect(response).to be_error
- end
- end
-
- describe 'payload alert_level' do
- subject { service.execute.payload[:alert_level] }
-
- context 'when above info threshold' do
- let(:current_size) { 50.megabytes }
-
- it { is_expected.to eq(:info) }
- end
-
- context 'when above warning threshold' do
- let(:current_size) { 75.megabytes }
-
- it { is_expected.to eq(:warning) }
- end
-
- context 'when above alert threshold' do
- let(:current_size) { 95.megabytes }
-
- it { is_expected.to eq(:alert) }
- end
-
- context 'when above error threshold' do
- let(:current_size) { 100.megabytes }
-
- it { is_expected.to eq(:error) }
- end
- end
-
- describe 'payload explanation_message' do
- subject(:response) { service.execute.payload[:explanation_message] }
-
- context 'when above limit' do
- let(:current_size) { 110.megabytes }
-
- it 'returns message with read-only warning' do
- expect(response).to include("#{namespace.name} is now read-only")
- end
- end
-
- context 'when below limit' do
- let(:current_size) { 60.megabytes }
-
- it { is_expected.to include('If you reach 100% storage capacity') }
- end
- end
-
- describe 'payload usage_message' do
- let(:current_size) { 60.megabytes }
-
- subject(:response) { service.execute.payload[:usage_message] }
-
- it 'returns current usage information' do
- expect(response).to include("60 MB of 100 MB")
- expect(response).to include("60%")
- end
- end
-
- describe 'payload root_namespace' do
- subject(:response) { service.execute.payload[:root_namespace] }
-
- it { is_expected.to eq(namespace) }
- end
-end
diff --git a/spec/services/namespaces/statistics_refresher_service_spec.rb b/spec/services/namespaces/statistics_refresher_service_spec.rb
index 1fa0a794edd..d3379e843ec 100644
--- a/spec/services/namespaces/statistics_refresher_service_spec.rb
+++ b/spec/services/namespaces/statistics_refresher_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Namespaces::StatisticsRefresherService, '#execute' do
+RSpec.describe Namespaces::StatisticsRefresherService, '#execute' do
let(:group) { create(:group) }
let(:projects) { create_list(:project, 5, namespace: group) }
let(:service) { described_class.new }
diff --git a/spec/services/note_summary_spec.rb b/spec/services/note_summary_spec.rb
index 038e0cdb703..38174748b19 100644
--- a/spec/services/note_summary_spec.rb
+++ b/spec/services/note_summary_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe NoteSummary do
+RSpec.describe NoteSummary do
let(:project) { build(:project) }
let(:noteable) { build(:issue) }
let(:user) { build(:user) }
diff --git a/spec/services/notes/build_service_spec.rb b/spec/services/notes/build_service_spec.rb
index 984658cbd19..90548cf9a99 100644
--- a/spec/services/notes/build_service_spec.rb
+++ b/spec/services/notes/build_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Notes::BuildService do
+RSpec.describe Notes::BuildService do
let(:note) { create(:discussion_note_on_issue) }
let(:project) { note.project }
let(:author) { note.author }
diff --git a/spec/services/notes/create_service_spec.rb b/spec/services/notes/create_service_spec.rb
index 39d6fd26e31..fd824621db7 100644
--- a/spec/services/notes/create_service_spec.rb
+++ b/spec/services/notes/create_service_spec.rb
@@ -2,12 +2,12 @@
require 'spec_helper'
-describe Notes::CreateService do
+RSpec.describe Notes::CreateService do
let_it_be(:project) { create(:project, :repository) }
let_it_be(:issue) { create(:issue, project: project) }
let_it_be(:user) { create(:user) }
let(:opts) do
- { note: 'Awesome comment', noteable_type: 'Issue', noteable_id: issue.id }
+ { note: 'Awesome comment', noteable_type: 'Issue', noteable_id: issue.id, confidential: true }
end
describe '#execute' do
diff --git a/spec/services/notes/destroy_service_spec.rb b/spec/services/notes/destroy_service_spec.rb
index 258e5c68265..d1076f77cec 100644
--- a/spec/services/notes/destroy_service_spec.rb
+++ b/spec/services/notes/destroy_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Notes::DestroyService do
+RSpec.describe Notes::DestroyService do
let_it_be(:project) { create(:project, :public) }
let_it_be(:issue) { create(:issue, project: project) }
let(:user) { issue.author }
diff --git a/spec/services/notes/post_process_service_spec.rb b/spec/services/notes/post_process_service_spec.rb
index d564cacd2d8..07ef08d36c4 100644
--- a/spec/services/notes/post_process_service_spec.rb
+++ b/spec/services/notes/post_process_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Notes::PostProcessService do
+RSpec.describe Notes::PostProcessService do
let(:project) { create(:project) }
let(:issue) { create(:issue, project: project) }
let(:user) { create(:user) }
diff --git a/spec/services/notes/quick_actions_service_spec.rb b/spec/services/notes/quick_actions_service_spec.rb
index 7eea2a7afc6..d20824efaaa 100644
--- a/spec/services/notes/quick_actions_service_spec.rb
+++ b/spec/services/notes/quick_actions_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Notes::QuickActionsService do
+RSpec.describe Notes::QuickActionsService do
shared_context 'note on noteable' do
let(:project) { create(:project, :repository) }
let(:maintainer) { create(:user).tap { |u| project.add_maintainer(u) } }
diff --git a/spec/services/notes/render_service_spec.rb b/spec/services/notes/render_service_spec.rb
index ad69721d876..09cd7dc572b 100644
--- a/spec/services/notes/render_service_spec.rb
+++ b/spec/services/notes/render_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Notes::RenderService do
+RSpec.describe Notes::RenderService do
describe '#execute' do
it 'renders a Note' do
note = double(:note)
diff --git a/spec/services/notes/resolve_service_spec.rb b/spec/services/notes/resolve_service_spec.rb
index c98384c226e..1c5b308aed1 100644
--- a/spec/services/notes/resolve_service_spec.rb
+++ b/spec/services/notes/resolve_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Notes::ResolveService do
+RSpec.describe Notes::ResolveService do
let(:merge_request) { create(:merge_request) }
let(:note) { create(:diff_note_on_merge_request, noteable: merge_request, project: merge_request.project) }
let(:user) { merge_request.author }
diff --git a/spec/services/notes/update_service_spec.rb b/spec/services/notes/update_service_spec.rb
index ab28e08ec83..70dea99de4a 100644
--- a/spec/services/notes/update_service_spec.rb
+++ b/spec/services/notes/update_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Notes::UpdateService do
+RSpec.describe Notes::UpdateService do
let(:group) { create(:group, :public) }
let(:project) { create(:project, :public, group: group) }
let(:private_group) { create(:group, :private) }
@@ -59,6 +59,45 @@ describe Notes::UpdateService do
end
end
+ context 'setting confidentiality' do
+ let(:opts) { { confidential: true } }
+
+ context 'simple note' do
+ it 'updates the confidentiality' do
+ expect { update_note(opts) }.to change { note.reload.confidential }.from(nil).to(true)
+ end
+ end
+
+ context 'discussion notes' do
+ let(:note) { create(:discussion_note, project: project, noteable: issue, author: user, note: "Old note #{user2.to_reference}") }
+ let!(:response_note_1) { create(:discussion_note, project: project, noteable: issue, in_reply_to: note) }
+ let!(:response_note_2) { create(:discussion_note, project: project, noteable: issue, in_reply_to: note, confidential: false) }
+ let!(:other_note) { create(:note, project: project, noteable: issue) }
+
+ context 'when updating the root note' do
+ it 'updates the confidentiality of the root note and all the responses' do
+ update_note(opts)
+
+ expect(note.reload.confidential).to be_truthy
+ expect(response_note_1.reload.confidential).to be_truthy
+ expect(response_note_2.reload.confidential).to be_truthy
+ expect(other_note.reload.confidential).to be_falsey
+ end
+ end
+
+ context 'when updating one of the response notes' do
+ it 'updates only the confidentiality of the note that is being updated' do
+ Notes::UpdateService.new(project, user, opts).execute(response_note_1)
+
+ expect(note.reload.confidential).to be_falsey
+ expect(response_note_1.reload.confidential).to be_truthy
+ expect(response_note_2.reload.confidential).to be_falsey
+ expect(other_note.reload.confidential).to be_falsey
+ end
+ end
+ end
+ end
+
context 'todos' do
shared_examples 'does not update todos' do
it 'keep todos' do
diff --git a/spec/services/notification_recipients/build_service_spec.rb b/spec/services/notification_recipients/build_service_spec.rb
index e203093623d..5c8add250c2 100644
--- a/spec/services/notification_recipients/build_service_spec.rb
+++ b/spec/services/notification_recipients/build_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe NotificationRecipients::BuildService do
+RSpec.describe NotificationRecipients::BuildService do
let(:service) { described_class }
let(:assignee) { create(:user) }
let(:project) { create(:project, :public) }
diff --git a/spec/services/notification_recipients/builder/default_spec.rb b/spec/services/notification_recipients/builder/default_spec.rb
index 307ca40248e..d25410235c2 100644
--- a/spec/services/notification_recipients/builder/default_spec.rb
+++ b/spec/services/notification_recipients/builder/default_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe NotificationRecipients::Builder::Default do
+RSpec.describe NotificationRecipients::Builder::Default do
describe '#build!' do
let_it_be(:group) { create(:group, :public) }
let_it_be(:project) { create(:project, :public, group: group).tap { |p| p.add_developer(project_watcher) } }
diff --git a/spec/services/notification_recipients/builder/new_note_spec.rb b/spec/services/notification_recipients/builder/new_note_spec.rb
index f88e8b2dfb0..7d2a4f682c5 100644
--- a/spec/services/notification_recipients/builder/new_note_spec.rb
+++ b/spec/services/notification_recipients/builder/new_note_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe NotificationRecipients::Builder::NewNote do
+RSpec.describe NotificationRecipients::Builder::NewNote do
describe '#notification_recipients' do
let_it_be(:group) { create(:group, :public) }
let_it_be(:project) { create(:project, :public, group: group) }
diff --git a/spec/services/notification_service_spec.rb b/spec/services/notification_service_spec.rb
index 3c1c3e2dfc3..2fe7a46de4b 100644
--- a/spec/services/notification_service_spec.rb
+++ b/spec/services/notification_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe NotificationService, :mailer do
+RSpec.describe NotificationService, :mailer do
include EmailSpec::Matchers
include ExternalAuthorizationServiceHelpers
include NotificationHelpers
@@ -343,6 +343,79 @@ describe NotificationService, :mailer do
end
end
+ context 'on service desk issue' do
+ before do
+ allow(Notify).to receive(:service_desk_new_note_email)
+ .with(Integer, Integer).and_return(mailer)
+
+ allow(::Gitlab::IncomingEmail).to receive(:enabled?) { true }
+ allow(::Gitlab::IncomingEmail).to receive(:supports_wildcard?) { true }
+ end
+
+ let(:subject) { NotificationService.new }
+ let(:mailer) { double(deliver_later: true) }
+
+ def should_email!
+ expect(Notify).to receive(:service_desk_new_note_email)
+ .with(issue.id, note.id)
+ end
+
+ def should_not_email!
+ expect(Notify).not_to receive(:service_desk_new_note_email)
+ end
+
+ def execute!
+ subject.new_note(note)
+ end
+
+ def self.it_should_email!
+ it 'sends the email' do
+ should_email!
+ execute!
+ end
+ end
+
+ def self.it_should_not_email!
+ it 'doesn\'t send the email' do
+ should_not_email!
+ execute!
+ end
+ end
+
+ let(:issue) { create(:issue, author: User.support_bot) }
+ let(:project) { issue.project }
+ let(:note) { create(:note, noteable: issue, project: project) }
+
+ context 'a non-service-desk issue' do
+ it_should_not_email!
+ end
+
+ context 'a service-desk issue' do
+ before do
+ issue.update!(service_desk_reply_to: 'service.desk@example.com')
+ project.update!(service_desk_enabled: true)
+ end
+
+ it_should_email!
+
+ context 'where the project has disabled the feature' do
+ before do
+ project.update(service_desk_enabled: false)
+ end
+
+ it_should_not_email!
+ end
+
+ context 'when the support bot has unsubscribed' do
+ before do
+ issue.unsubscribe(User.support_bot, project)
+ end
+
+ it_should_not_email!
+ end
+ end
+ end
+
describe 'new note on issue in project that belongs to a group' do
before do
note.project.namespace_id = group.id
@@ -1950,6 +2023,26 @@ describe NotificationService, :mailer do
let(:notification_trigger) { notification.resolve_all_discussions(merge_request, @u_disabled) }
end
end
+
+ describe '#merge_when_pipeline_succeeds' do
+ it 'send notification that merge will happen when pipeline succeeds' do
+ notification.merge_when_pipeline_succeeds(merge_request, assignee)
+ should_email(merge_request.author)
+ should_email(@u_watcher)
+ should_email(@subscriber)
+ end
+
+ it_behaves_like 'participating notifications' do
+ let(:participant) { create(:user, username: 'user-participant') }
+ let(:issuable) { merge_request }
+ let(:notification_trigger) { notification.merge_when_pipeline_succeeds(merge_request, @u_disabled) }
+ end
+
+ it_behaves_like 'project emails are disabled' do
+ let(:notification_target) { merge_request }
+ let(:notification_trigger) { notification.merge_when_pipeline_succeeds(merge_request, @u_disabled) }
+ end
+ end
end
describe 'Projects', :deliver_mails_inline do
diff --git a/spec/services/packages/composer/composer_json_service_spec.rb b/spec/services/packages/composer/composer_json_service_spec.rb
new file mode 100644
index 00000000000..3996fcea679
--- /dev/null
+++ b/spec/services/packages/composer/composer_json_service_spec.rb
@@ -0,0 +1,39 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+RSpec.describe Packages::Composer::ComposerJsonService do
+ describe '#execute' do
+ let(:branch) { project.repository.find_branch('master') }
+ let(:target) { branch.target }
+
+ subject { described_class.new(project, target).execute }
+
+ context 'with an existing file' do
+ let(:project) { create(:project, :custom_repo, files: { 'composer.json' => json } ) }
+
+ context 'with a valid file' do
+ let(:json) { '{ "name": "package-name"}' }
+
+ it 'returns the parsed json' do
+ expect(subject).to eq({ 'name' => 'package-name' })
+ end
+ end
+
+ context 'with an invalid file' do
+ let(:json) { '{ name": "package-name"}' }
+
+ it 'raises an error' do
+ expect { subject }.to raise_error(/Invalid/)
+ end
+ end
+ end
+
+ context 'without the composer.json file' do
+ let(:project) { create(:project, :repository) }
+
+ it 'raises an error' do
+ expect { subject }.to raise_error(/not found/)
+ end
+ end
+ end
+end
diff --git a/spec/services/packages/composer/create_package_service_spec.rb b/spec/services/packages/composer/create_package_service_spec.rb
new file mode 100644
index 00000000000..3f9da31cf6e
--- /dev/null
+++ b/spec/services/packages/composer/create_package_service_spec.rb
@@ -0,0 +1,97 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+RSpec.describe Packages::Composer::CreatePackageService do
+ include PackagesManagerApiSpecHelpers
+
+ let_it_be(:package_name) { 'composer-package-name' }
+ let_it_be(:json) { { name: package_name }.to_json }
+ let_it_be(:project) { create(:project, :custom_repo, files: { 'composer.json' => json } ) }
+ let_it_be(:user) { create(:user) }
+ let(:params) do
+ {
+ branch: branch,
+ tag: tag
+ }
+ end
+
+ describe '#execute' do
+ let(:tag) { nil }
+ let(:branch) { nil }
+
+ subject { described_class.new(project, user, params).execute }
+
+ let(:created_package) { Packages::Package.composer.last }
+
+ context 'without an existing package' do
+ context 'with a branch' do
+ let(:branch) { project.repository.find_branch('master') }
+
+ it 'creates the package' do
+ expect { subject }
+ .to change { Packages::Package.composer.count }.by(1)
+ .and change { Packages::Composer::Metadatum.count }.by(1)
+
+ expect(created_package.name).to eq package_name
+ expect(created_package.version).to eq 'dev-master'
+ expect(created_package.composer_metadatum.target_sha).to eq branch.target
+ expect(created_package.composer_metadatum.composer_json.to_json).to eq json
+ end
+ end
+
+ context 'with a tag' do
+ let(:tag) { project.repository.find_tag('v1.2.3') }
+
+ before do
+ project.repository.add_tag(user, 'v1.2.3', 'master')
+ end
+
+ it 'creates the package' do
+ expect { subject }
+ .to change { Packages::Package.composer.count }.by(1)
+ .and change { Packages::Composer::Metadatum.count }.by(1)
+
+ expect(created_package.name).to eq package_name
+ expect(created_package.version).to eq '1.2.3'
+ end
+ end
+ end
+
+ context 'with an existing package' do
+ let(:branch) { project.repository.find_branch('master') }
+
+ context 'belonging to the same project' do
+ before do
+ described_class.new(project, user, params).execute
+ end
+
+ it 'does not create a new package' do
+ expect { subject }
+ .to change { Packages::Package.composer.count }.by(0)
+ .and change { Packages::Composer::Metadatum.count }.by(0)
+ end
+ end
+
+ context 'belonging to another project' do
+ let(:other_project) { create(:project) }
+ let!(:other_package) { create(:composer_package, name: package_name, version: 'dev-master', project: other_project) }
+
+ it 'fails with an error' do
+ expect { subject }
+ .to raise_error(/is already taken/)
+ end
+ end
+
+ context 'same name but of different type' do
+ let(:other_project) { create(:project) }
+ let!(:other_package) { create(:package, name: package_name, version: 'dev-master', project: other_project) }
+
+ it 'creates the package' do
+ expect { subject }
+ .to change { Packages::Package.composer.count }.by(1)
+ .and change { Packages::Composer::Metadatum.count }.by(1)
+ end
+ end
+ end
+ end
+end
diff --git a/spec/services/packages/composer/version_parser_service_spec.rb b/spec/services/packages/composer/version_parser_service_spec.rb
new file mode 100644
index 00000000000..904c75ab0a1
--- /dev/null
+++ b/spec/services/packages/composer/version_parser_service_spec.rb
@@ -0,0 +1,31 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+RSpec.describe Packages::Composer::VersionParserService do
+ let_it_be(:params) { {} }
+
+ describe '#execute' do
+ using RSpec::Parameterized::TableSyntax
+
+ subject { described_class.new(tag_name: tagname, branch_name: branchname).execute }
+
+ where(:tagname, :branchname, :expected_version) do
+ nil | 'master' | 'dev-master'
+ nil | 'my-feature' | 'dev-my-feature'
+ nil | 'v1' | '1.x-dev'
+ nil | 'v1.x' | '1.x-dev'
+ nil | 'v1.7.x' | '1.7.x-dev'
+ nil | 'v1.7' | '1.7.x-dev'
+ nil | '1.7.x' | '1.7.x-dev'
+ 'v1.0.0' | nil | '1.0.0'
+ 'v1.0' | nil | '1.0'
+ '1.0' | nil | '1.0'
+ '1.0.2' | nil | '1.0.2'
+ '1.0.2-beta2' | nil | '1.0.2-beta2'
+ end
+
+ with_them do
+ it { is_expected.to eq expected_version }
+ end
+ end
+end
diff --git a/spec/services/packages/conan/create_package_file_service_spec.rb b/spec/services/packages/conan/create_package_file_service_spec.rb
new file mode 100644
index 00000000000..0e9cbba5fc1
--- /dev/null
+++ b/spec/services/packages/conan/create_package_file_service_spec.rb
@@ -0,0 +1,130 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+RSpec.describe Packages::Conan::CreatePackageFileService do
+ include WorkhorseHelpers
+
+ let_it_be(:package) { create(:conan_package) }
+
+ describe '#execute' do
+ let(:file_name) { 'foo.tgz' }
+
+ subject { described_class.new(package, file, params) }
+
+ shared_examples 'a valid package_file' do
+ let(:params) do
+ {
+ file_name: file_name,
+ 'file.md5': '12345',
+ 'file.sha1': '54321',
+ 'file.size': '128',
+ 'file.type': 'txt',
+ recipe_revision: '0',
+ package_revision: '0',
+ conan_package_reference: '123456789',
+ conan_file_type: :package_file
+ }.with_indifferent_access
+ end
+
+ it 'creates a new package file' do
+ package_file = subject.execute
+
+ expect(package_file).to be_valid
+ expect(package_file.file_name).to eq(file_name)
+ expect(package_file.file_md5).to eq('12345')
+ expect(package_file.size).to eq(128)
+ expect(package_file.conan_file_metadatum).to be_valid
+ expect(package_file.conan_file_metadatum.recipe_revision).to eq('0')
+ expect(package_file.conan_file_metadatum.package_revision).to eq('0')
+ expect(package_file.conan_file_metadatum.conan_package_reference).to eq('123456789')
+ expect(package_file.conan_file_metadatum.conan_file_type).to eq('package_file')
+ expect(package_file.file.read).to eq('content')
+ end
+ end
+
+ shared_examples 'a valid recipe_file' do
+ let(:params) do
+ {
+ file_name: file_name,
+ 'file.md5': '12345',
+ 'file.sha1': '54321',
+ 'file.size': '128',
+ 'file.type': 'txt',
+ recipe_revision: '0',
+ conan_file_type: :recipe_file
+ }.with_indifferent_access
+ end
+
+ it 'creates a new recipe file' do
+ package_file = subject.execute
+
+ expect(package_file).to be_valid
+ expect(package_file.file_name).to eq(file_name)
+ expect(package_file.file_md5).to eq('12345')
+ expect(package_file.size).to eq(128)
+ expect(package_file.conan_file_metadatum).to be_valid
+ expect(package_file.conan_file_metadatum.recipe_revision).to eq('0')
+ expect(package_file.conan_file_metadatum.package_revision).to be_nil
+ expect(package_file.conan_file_metadatum.conan_package_reference).to be_nil
+ expect(package_file.conan_file_metadatum.conan_file_type).to eq('recipe_file')
+ expect(package_file.file.read).to eq('content')
+ end
+ end
+
+ context 'with temp file' do
+ let!(:file) do
+ upload_path = ::Packages::PackageFileUploader.workhorse_local_upload_path
+ file_path = upload_path + '/' + file_name
+
+ FileUtils.mkdir_p(upload_path)
+ File.write(file_path, 'content')
+
+ UploadedFile.new(file_path, filename: File.basename(file_path))
+ end
+
+ before do
+ allow_any_instance_of(Packages::PackageFileUploader).to receive(:size).and_return(128)
+ end
+
+ it_behaves_like 'a valid package_file'
+ it_behaves_like 'a valid recipe_file'
+ end
+
+ context 'with remote file' do
+ let!(:fog_connection) do
+ stub_package_file_object_storage(direct_upload: true)
+ end
+
+ before do
+ allow_any_instance_of(Packages::PackageFileUploader).to receive(:size).and_return(128)
+ end
+
+ let(:tmp_object) do
+ fog_connection.directories.new(key: 'packages').files.create(
+ key: "tmp/uploads/#{file_name}",
+ body: 'content'
+ )
+ end
+
+ let(:file) { fog_to_uploaded_file(tmp_object) }
+
+ it_behaves_like 'a valid package_file'
+ it_behaves_like 'a valid recipe_file'
+ end
+
+ context 'file is missing' do
+ let(:file) { nil }
+ let(:params) do
+ {
+ file_name: file_name,
+ recipe_revision: '0',
+ conan_file_type: :recipe_file
+ }
+ end
+
+ it 'raises an error' do
+ expect { subject.execute }.to raise_error(ActiveRecord::RecordInvalid)
+ end
+ end
+ end
+end
diff --git a/spec/services/packages/conan/create_package_service_spec.rb b/spec/services/packages/conan/create_package_service_spec.rb
new file mode 100644
index 00000000000..f8068f6e57b
--- /dev/null
+++ b/spec/services/packages/conan/create_package_service_spec.rb
@@ -0,0 +1,48 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+RSpec.describe Packages::Conan::CreatePackageService do
+ let_it_be(:project) { create(:project) }
+ let_it_be(:user) { create(:user) }
+
+ subject { described_class.new(project, user, params) }
+
+ describe '#execute' do
+ context 'valid params' do
+ let(:params) do
+ {
+ package_name: 'my-pkg',
+ package_version: '1.0.0',
+ package_username: ::Packages::Conan::Metadatum.package_username_from(full_path: project.full_path),
+ package_channel: 'stable'
+ }
+ end
+
+ it 'creates a new package' do
+ package = subject.execute
+
+ expect(package).to be_valid
+ expect(package.name).to eq(params[:package_name])
+ expect(package.version).to eq(params[:package_version])
+ expect(package.package_type).to eq('conan')
+ expect(package.conan_metadatum.package_username).to eq(params[:package_username])
+ expect(package.conan_metadatum.package_channel).to eq(params[:package_channel])
+ end
+ end
+
+ context 'invalid params' do
+ let(:params) do
+ {
+ package_name: 'my-pkg',
+ package_version: '1.0.0',
+ package_username: 'foo/bar',
+ package_channel: 'stable'
+ }
+ end
+
+ it 'fails' do
+ expect { subject.execute }.to raise_exception(ActiveRecord::RecordInvalid)
+ end
+ end
+ end
+end
diff --git a/spec/services/packages/conan/search_service_spec.rb b/spec/services/packages/conan/search_service_spec.rb
new file mode 100644
index 00000000000..39d284ee088
--- /dev/null
+++ b/spec/services/packages/conan/search_service_spec.rb
@@ -0,0 +1,74 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Packages::Conan::SearchService do
+ let_it_be(:user) { create(:user) }
+ let_it_be(:project) { create(:project, :public) }
+ let!(:conan_package) { create(:conan_package, project: project) }
+ let!(:conan_package2) { create(:conan_package, project: project) }
+
+ subject { described_class.new(user, query: query) }
+
+ before do
+ project.add_developer(user)
+ end
+
+ describe '#execute' do
+ context 'with wildcard' do
+ let(:partial_name) { conan_package.name.first[0, 3] }
+ let(:query) { "#{partial_name}*" }
+
+ it 'makes a wildcard query' do
+ result = subject.execute
+
+ expect(result.status).to eq :success
+ expect(result.payload).to eq(results: [conan_package.conan_recipe, conan_package2.conan_recipe])
+ end
+ end
+
+ context 'with only wildcard' do
+ let(:query) { '*' }
+
+ it 'returns empty' do
+ result = subject.execute
+
+ expect(result.status).to eq :success
+ expect(result.payload).to eq(results: [])
+ end
+ end
+
+ context 'with no wildcard' do
+ let(:query) { conan_package.name }
+
+ it 'makes a search using the beginning of the recipe' do
+ result = subject.execute
+
+ expect(result.status).to eq :success
+ expect(result.payload).to eq(results: [conan_package.conan_recipe])
+ end
+ end
+
+ context 'with full recipe match' do
+ let(:query) { conan_package.conan_recipe }
+
+ it 'makes an exact search' do
+ result = subject.execute
+
+ expect(result.status).to eq :success
+ expect(result.payload).to eq(results: [conan_package.conan_recipe])
+ end
+ end
+
+ context 'with malicious query' do
+ let(:query) { 'DROP TABLE foo;' }
+
+ it 'returns empty' do
+ result = subject.execute
+
+ expect(result.status).to eq :success
+ expect(result.payload).to eq(results: [])
+ end
+ end
+ end
+end
diff --git a/spec/services/packages/create_dependency_service_spec.rb b/spec/services/packages/create_dependency_service_spec.rb
new file mode 100644
index 00000000000..00e5e5c6d96
--- /dev/null
+++ b/spec/services/packages/create_dependency_service_spec.rb
@@ -0,0 +1,113 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+RSpec.describe Packages::CreateDependencyService do
+ describe '#execute' do
+ let_it_be(:namespace) {create(:namespace)}
+ let_it_be(:version) { '1.0.1' }
+ let_it_be(:package_name) { "@#{namespace.path}/my-app".freeze }
+
+ context 'when packages are published' do
+ let(:json_file) { 'packages/npm/payload.json' }
+ let(:params) do
+ Gitlab::Json.parse(fixture_file(json_file)
+ .gsub('@root/npm-test', package_name)
+ .gsub('1.0.1', version))
+ .with_indifferent_access
+ end
+ let(:package_version) { params[:versions].each_key.first }
+ let(:dependencies) { params[:versions][package_version] }
+ let(:package) { create(:npm_package) }
+ let(:dependency_names) { package.dependency_links.flat_map(&:dependency).map(&:name).sort }
+ let(:dependency_link_types) { package.dependency_links.map(&:dependency_type).sort }
+
+ subject { described_class.new(package, dependencies).execute }
+
+ it 'creates dependencies and links' do
+ expect(Packages::Dependency)
+ .to receive(:ids_for_package_names_and_version_patterns)
+ .once
+ .and_call_original
+
+ expect { subject }
+ .to change { Packages::Dependency.count }.by(1)
+ .and change { Packages::DependencyLink.count }.by(1)
+ expect(dependency_names).to match_array(%w(express))
+ expect(dependency_link_types).to match_array(%w(dependencies))
+ end
+
+ context 'with repeated packages' do
+ let(:json_file) { 'packages/npm/payload_with_duplicated_packages.json' }
+
+ it 'creates dependencies and links' do
+ expect(Packages::Dependency)
+ .to receive(:ids_for_package_names_and_version_patterns)
+ .exactly(4).times
+ .and_call_original
+
+ expect { subject }
+ .to change { Packages::Dependency.count }.by(4)
+ .and change { Packages::DependencyLink.count }.by(6)
+ expect(dependency_names).to match_array(%w(d3 d3 d3 dagre-d3 dagre-d3 express))
+ expect(dependency_link_types).to match_array(%w(bundleDependencies dependencies dependencies devDependencies devDependencies peerDependencies))
+ end
+ end
+
+ context 'with dependencies bulk insert conflicts' do
+ let_it_be(:rows) { [{ name: 'express', version_pattern: '^4.16.4' }] }
+
+ it 'creates dependences and links' do
+ original_bulk_insert = ::Gitlab::Database.method(:bulk_insert)
+ expect(::Gitlab::Database)
+ .to receive(:bulk_insert) do |table, rows, return_ids: false, disable_quote: [], on_conflict: nil|
+ call_count = table == Packages::Dependency.table_name ? 2 : 1
+ call_count.times { original_bulk_insert.call(table, rows, return_ids: return_ids, disable_quote: disable_quote, on_conflict: on_conflict) }
+ end.twice
+ expect(Packages::Dependency)
+ .to receive(:ids_for_package_names_and_version_patterns)
+ .twice
+ .and_call_original
+
+ expect { subject }
+ .to change { Packages::Dependency.count }.by(1)
+ .and change { Packages::DependencyLink.count }.by(1)
+ expect(dependency_names).to match_array(%w(express))
+ expect(dependency_link_types).to match_array(%w(dependencies))
+ end
+ end
+
+ context 'with existing dependencies' do
+ let(:other_package) { create(:npm_package) }
+
+ before do
+ described_class.new(other_package, dependencies).execute
+ end
+
+ it 'reuses them' do
+ expect { subject }
+ .to not_change { Packages::Dependency.count }
+ .and change { Packages::DependencyLink.count }.by(1)
+ end
+ end
+
+ context 'with a dependency not described with a hash' do
+ let(:invalid_dependencies) { dependencies.tap { |d| d['bundleDependencies'] = false } }
+
+ subject { described_class.new(package, invalid_dependencies).execute }
+
+ it 'creates dependencies and links' do
+ expect(Packages::Dependency)
+ .to receive(:ids_for_package_names_and_version_patterns)
+ .once
+ .and_call_original
+
+ expect { subject }
+ .to change { Packages::Dependency.count }.by(1)
+ .and change { Packages::DependencyLink.count }.by(1)
+ expect(dependency_names).to match_array(%w(express))
+ expect(dependency_link_types).to match_array(%w(dependencies))
+ end
+ end
+ end
+ end
+end
diff --git a/spec/services/packages/create_package_file_service_spec.rb b/spec/services/packages/create_package_file_service_spec.rb
new file mode 100644
index 00000000000..93dde54916a
--- /dev/null
+++ b/spec/services/packages/create_package_file_service_spec.rb
@@ -0,0 +1,38 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+RSpec.describe Packages::CreatePackageFileService do
+ let(:package) { create(:maven_package) }
+
+ describe '#execute' do
+ context 'with valid params' do
+ let(:params) do
+ {
+ file: Tempfile.new,
+ file_name: 'foo.jar'
+ }
+ end
+
+ it 'creates a new package file' do
+ package_file = described_class.new(package, params).execute
+
+ expect(package_file).to be_valid
+ expect(package_file.file_name).to eq('foo.jar')
+ end
+ end
+
+ context 'file is missing' do
+ let(:params) do
+ {
+ file_name: 'foo.jar'
+ }
+ end
+
+ it 'raises an error' do
+ service = described_class.new(package, params)
+
+ expect { service.execute }.to raise_error(ActiveRecord::RecordInvalid)
+ end
+ end
+ end
+end
diff --git a/spec/services/packages/maven/create_package_service_spec.rb b/spec/services/packages/maven/create_package_service_spec.rb
new file mode 100644
index 00000000000..bfdf62008ba
--- /dev/null
+++ b/spec/services/packages/maven/create_package_service_spec.rb
@@ -0,0 +1,77 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+RSpec.describe Packages::Maven::CreatePackageService do
+ let(:project) { create(:project) }
+ let(:user) { create(:user) }
+ let(:app_name) { 'my-app' }
+ let(:version) { '1.0-SNAPSHOT' }
+ let(:path) { "my/company/app/#{app_name}" }
+ let(:path_with_version) { "#{path}/#{version}" }
+
+ describe '#execute' do
+ subject(:package) { described_class.new(project, user, params).execute }
+
+ context 'with version' do
+ let(:params) do
+ {
+ path: path_with_version,
+ name: path,
+ version: version
+ }
+ end
+
+ it 'creates a new package with metadatum' do
+ expect(package).to be_valid
+ expect(package.name).to eq(path)
+ expect(package.version).to eq(version)
+ expect(package.package_type).to eq('maven')
+ expect(package.maven_metadatum).to be_valid
+ expect(package.maven_metadatum.path).to eq(path_with_version)
+ expect(package.maven_metadatum.app_group).to eq('my.company.app')
+ expect(package.maven_metadatum.app_name).to eq(app_name)
+ expect(package.maven_metadatum.app_version).to eq(version)
+ end
+
+ it_behaves_like 'assigns build to package'
+ end
+
+ context 'without version' do
+ let(:params) do
+ {
+ path: path,
+ name: path,
+ version: nil
+ }
+ end
+
+ it 'creates a new package with metadatum' do
+ package = described_class.new(project, user, params).execute
+
+ expect(package).to be_valid
+ expect(package.name).to eq(path)
+ expect(package.version).to be nil
+ expect(package.maven_metadatum).to be_valid
+ expect(package.maven_metadatum.path).to eq(path)
+ expect(package.maven_metadatum.app_group).to eq('my.company.app')
+ expect(package.maven_metadatum.app_name).to eq(app_name)
+ expect(package.maven_metadatum.app_version).to be nil
+ end
+ end
+
+ context 'path is missing' do
+ let(:params) do
+ {
+ name: path,
+ version: version
+ }
+ end
+
+ it 'raises an error' do
+ service = described_class.new(project, user, params)
+
+ expect { service.execute }.to raise_error(ActiveRecord::RecordInvalid)
+ end
+ end
+ end
+end
diff --git a/spec/services/packages/maven/find_or_create_package_service_spec.rb b/spec/services/packages/maven/find_or_create_package_service_spec.rb
new file mode 100644
index 00000000000..c9441324216
--- /dev/null
+++ b/spec/services/packages/maven/find_or_create_package_service_spec.rb
@@ -0,0 +1,38 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+RSpec.describe Packages::Maven::FindOrCreatePackageService do
+ let_it_be(:project) { create(:project) }
+ let_it_be(:user) { create(:user) }
+ let_it_be(:app_name) { 'my-app' }
+ let_it_be(:version) { '1.0-SNAPSHOT' }
+ let_it_be(:path) { "my/company/app/#{app_name}" }
+ let_it_be(:path_with_version) { "#{path}/#{version}" }
+ let_it_be(:params) do
+ {
+ path: path_with_version,
+ name: path,
+ version: version
+ }
+ end
+
+ describe '#execute' do
+ subject { described_class.new(project, user, params).execute }
+
+ context 'without any existing package' do
+ it 'creates a package' do
+ expect { subject }.to change { Packages::Package.count }.by(1)
+ end
+ end
+
+ context 'with an existing package' do
+ let_it_be(:existing_package) { create(:maven_package, name: path, version: version, project: project) }
+
+ it { is_expected.to eq existing_package }
+ it "doesn't create a new package" do
+ expect { subject }
+ .to not_change { Packages::Package.count }
+ end
+ end
+ end
+end
diff --git a/spec/services/packages/npm/create_package_service_spec.rb b/spec/services/packages/npm/create_package_service_spec.rb
new file mode 100644
index 00000000000..25bbbf82bec
--- /dev/null
+++ b/spec/services/packages/npm/create_package_service_spec.rb
@@ -0,0 +1,96 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+RSpec.describe Packages::Npm::CreatePackageService do
+ let(:namespace) {create(:namespace)}
+ let(:project) { create(:project, namespace: namespace) }
+ let(:user) { create(:user) }
+ let(:version) { '1.0.1' }
+
+ let(:params) do
+ Gitlab::Json.parse(fixture_file('packages/npm/payload.json')
+ .gsub('@root/npm-test', package_name)
+ .gsub('1.0.1', version)).with_indifferent_access
+ .merge!(override)
+ end
+ let(:override) { {} }
+ let(:package_name) { "@#{namespace.path}/my-app".freeze }
+
+ subject { described_class.new(project, user, params).execute }
+
+ shared_examples 'valid package' do
+ it 'creates a package' do
+ expect { subject }
+ .to change { Packages::Package.count }.by(1)
+ .and change { Packages::Package.npm.count }.by(1)
+ .and change { Packages::Tag.count }.by(1)
+ end
+
+ it { is_expected.to be_valid }
+
+ it 'creates a package with name and version' do
+ package = subject
+
+ expect(package.name).to eq(package_name)
+ expect(package.version).to eq(version)
+ end
+
+ it { expect(subject.name).to eq(package_name) }
+ it { expect(subject.version).to eq(version) }
+ end
+
+ describe '#execute' do
+ context 'scoped package' do
+ it_behaves_like 'valid package'
+
+ it_behaves_like 'assigns build to package'
+ end
+
+ context 'invalid package name' do
+ let(:package_name) { "@#{namespace.path}/my-group/my-app".freeze }
+
+ it { expect { subject }.to raise_error(ActiveRecord::RecordInvalid) }
+ end
+
+ context 'package already exists' do
+ let(:package_name) { "@#{namespace.path}/my_package" }
+ let!(:existing_package) { create(:npm_package, project: project, name: package_name, version: '1.0.1') }
+
+ it { expect(subject[:http_status]).to eq 403 }
+ it { expect(subject[:message]).to be 'Package already exists.' }
+ end
+
+ context 'with incorrect namespace' do
+ let(:package_name) { '@my_other_namespace/my-app' }
+
+ it 'raises a RecordInvalid error' do
+ expect { subject }.to raise_error(ActiveRecord::RecordInvalid)
+ end
+ end
+
+ context 'with empty versions' do
+ let(:override) { { versions: {} } }
+
+ it { expect(subject[:http_status]).to eq 400 }
+ it { expect(subject[:message]).to eq 'Version is empty.' }
+ end
+
+ context 'with invalid versions' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:version) do
+ [
+ '1',
+ '1.2',
+ '1./2.3',
+ '../../../../../1.2.3',
+ '%2e%2e%2f1.2.3'
+ ]
+ end
+
+ with_them do
+ it { expect { subject }.to raise_error(ActiveRecord::RecordInvalid, 'Validation failed: Version is invalid') }
+ end
+ end
+ end
+end
diff --git a/spec/services/packages/npm/create_tag_service_spec.rb b/spec/services/packages/npm/create_tag_service_spec.rb
new file mode 100644
index 00000000000..e7a784068fa
--- /dev/null
+++ b/spec/services/packages/npm/create_tag_service_spec.rb
@@ -0,0 +1,53 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+RSpec.describe Packages::Npm::CreateTagService do
+ let(:package) { create(:npm_package) }
+ let(:tag_name) { 'test-tag' }
+
+ describe '#execute' do
+ subject { described_class.new(package, tag_name).execute }
+
+ shared_examples 'it creates the tag' do
+ it { expect { subject }.to change { Packages::Tag.count }.by(1) }
+ it { expect(subject.name).to eq(tag_name) }
+ it 'adds tag to the package' do
+ tag = subject
+ expect(package.reload.tags).to match_array([tag])
+ end
+ end
+
+ context 'with no existing tag name' do
+ it_behaves_like 'it creates the tag'
+ end
+
+ context 'with exisiting tag name' do
+ let!(:package_tag2) { create(:packages_tag, package: package2, name: tag_name) }
+
+ context 'on package with different name' do
+ let!(:package2) { create(:npm_package, project: package.project) }
+
+ it_behaves_like 'it creates the tag'
+ end
+
+ context 'on different package type' do
+ let!(:package2) { create(:conan_package, project: package.project, name: 'conan_package_name', version: package.version) }
+
+ it_behaves_like 'it creates the tag'
+ end
+
+ context 'on same package with different version' do
+ let!(:package2) { create(:npm_package, project: package.project, name: package.name, version: '5.0.0-testing') }
+
+ it { expect { subject }.to not_change { Packages::Tag.count } }
+ it { expect(subject.name).to eq(tag_name) }
+
+ it 'adds tag to the package' do
+ tag = subject
+ expect(package.reload.tags).to match_array([tag])
+ expect(package2.reload.tags).to be_empty
+ end
+ end
+ end
+ end
+end
diff --git a/spec/services/packages/nuget/create_dependency_service_spec.rb b/spec/services/packages/nuget/create_dependency_service_spec.rb
new file mode 100644
index 00000000000..268c8837e25
--- /dev/null
+++ b/spec/services/packages/nuget/create_dependency_service_spec.rb
@@ -0,0 +1,76 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+RSpec.describe Packages::Nuget::CreateDependencyService do
+ let_it_be(:package, reload: true) { create(:nuget_package) }
+
+ describe '#execute' do
+ RSpec.shared_examples 'creating dependencies, links and nuget metadata for' do |expected_dependency_names, dependency_count, dependency_link_count|
+ let(:dependencies_with_metadata) { dependencies.select { |dep| dep[:target_framework].present? } }
+
+ it 'creates dependencies, links and nuget metadata' do
+ expect { subject }
+ .to change { Packages::Dependency.count }.by(dependency_count)
+ .and change { Packages::DependencyLink.count }.by(dependency_link_count)
+ .and change { Packages::Nuget::DependencyLinkMetadatum.count }.by(dependencies_with_metadata.size)
+ expect(expected_dependency_names).to contain_exactly(*dependency_names)
+ expect(package.dependency_links.map(&:dependency_type).uniq).to contain_exactly('dependencies')
+
+ dependencies_with_metadata.each do |dependency|
+ name = dependency[:name]
+ version_pattern = service.send(:version_or_empty_string, dependency[:version])
+ metadatum = package.dependency_links.joins(:dependency)
+ .find_by(packages_dependencies: { name: name, version_pattern: version_pattern })
+ .nuget_metadatum
+ expect(metadatum.target_framework).to eq dependency[:target_framework]
+ end
+ end
+ end
+
+ let_it_be(:dependencies) do
+ [
+ { name: 'Moqi', version: '2.5.6' },
+ { name: 'Castle.Core' },
+ { name: 'Test.Dependency', version: '2.3.7', target_framework: '.NETStandard2.0' },
+ { name: 'Newtonsoft.Json', version: '12.0.3', target_framework: '.NETStandard2.0' }
+ ]
+ end
+
+ let(:dependency_names) { package.dependency_links.flat_map(&:dependency).map(&:name) }
+ let(:service) { described_class.new(package, dependencies) }
+
+ subject { service.execute }
+
+ it_behaves_like 'creating dependencies, links and nuget metadata for', %w(Castle.Core Moqi Newtonsoft.Json Test.Dependency), 4, 4
+
+ context 'with existing dependencies' do
+ let_it_be(:exisiting_dependency) { create(:packages_dependency, name: 'Moqi', version_pattern: '2.5.6') }
+
+ it_behaves_like 'creating dependencies, links and nuget metadata for', %w(Castle.Core Moqi Newtonsoft.Json Test.Dependency), 3, 4
+ end
+
+ context 'with dependencies with no target framework' do
+ let_it_be(:dependencies) do
+ [
+ { name: 'Moqi', version: '2.5.6' },
+ { name: 'Castle.Core' },
+ { name: 'Test.Dependency', version: '2.3.7' },
+ { name: 'Newtonsoft.Json', version: '12.0.3' }
+ ]
+ end
+
+ it_behaves_like 'creating dependencies, links and nuget metadata for', %w(Castle.Core Moqi Newtonsoft.Json Test.Dependency), 4, 4
+ end
+
+ context 'with empty dependencies' do
+ let_it_be(:dependencies) { [] }
+
+ it 'is a no op' do
+ expect(service).not_to receive(:create_dependency_links)
+ expect(service).not_to receive(:create_dependency_link_metadata)
+
+ subject
+ end
+ end
+ end
+end
diff --git a/spec/services/packages/nuget/create_package_service_spec.rb b/spec/services/packages/nuget/create_package_service_spec.rb
new file mode 100644
index 00000000000..1579b42d9ad
--- /dev/null
+++ b/spec/services/packages/nuget/create_package_service_spec.rb
@@ -0,0 +1,34 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+RSpec.describe Packages::Nuget::CreatePackageService do
+ let_it_be(:project) { create(:project) }
+ let_it_be(:user) { create(:user) }
+ let_it_be(:params) { {} }
+
+ describe '#execute' do
+ subject { described_class.new(project, user, params).execute }
+
+ it 'creates the package' do
+ expect { subject }.to change { Packages::Package.count }.by(1)
+ package = Packages::Package.last
+
+ expect(package).to be_valid
+ expect(package.name).to eq(Packages::Nuget::CreatePackageService::TEMPORARY_PACKAGE_NAME)
+ expect(package.version).to start_with(Packages::Nuget::CreatePackageService::PACKAGE_VERSION)
+ expect(package.package_type).to eq('nuget')
+ end
+
+ it 'can create two packages in a row' do
+ expect { subject }.to change { Packages::Package.count }.by(1)
+ expect { described_class.new(project, user, params).execute }.to change { Packages::Package.count }.by(1)
+
+ package = Packages::Package.last
+
+ expect(package).to be_valid
+ expect(package.name).to eq(Packages::Nuget::CreatePackageService::TEMPORARY_PACKAGE_NAME)
+ expect(package.version).to start_with(Packages::Nuget::CreatePackageService::PACKAGE_VERSION)
+ expect(package.package_type).to eq('nuget')
+ end
+ end
+end
diff --git a/spec/services/packages/nuget/metadata_extraction_service_spec.rb b/spec/services/packages/nuget/metadata_extraction_service_spec.rb
new file mode 100644
index 00000000000..39fc0f9e6a1
--- /dev/null
+++ b/spec/services/packages/nuget/metadata_extraction_service_spec.rb
@@ -0,0 +1,106 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Packages::Nuget::MetadataExtractionService do
+ let(:package_file) { create(:nuget_package).package_files.first }
+ let(:service) { described_class.new(package_file.id) }
+
+ describe '#execute' do
+ subject { service.execute }
+
+ context 'with valid package file id' do
+ expected_metadata = {
+ package_name: 'DummyProject.DummyPackage',
+ package_version: '1.0.0',
+ package_dependencies: [
+ {
+ name: 'Newtonsoft.Json',
+ target_framework: '.NETCoreApp3.0',
+ version: '12.0.3'
+ }
+ ],
+ package_tags: []
+ }
+
+ it { is_expected.to eq(expected_metadata) }
+ end
+
+ context 'with nuspec file' do
+ before do
+ allow(service).to receive(:nuspec_file).and_return(fixture_file(nuspec_filepath))
+ end
+
+ context 'with dependencies' do
+ let(:nuspec_filepath) { 'packages/nuget/with_dependencies.nuspec' }
+
+ it { is_expected.to have_key(:package_dependencies) }
+
+ it 'extracts dependencies' do
+ dependencies = subject[:package_dependencies]
+
+ expect(dependencies).to include(name: 'Moqi', version: '2.5.6')
+ expect(dependencies).to include(name: 'Castle.Core')
+ expect(dependencies).to include(name: 'Test.Dependency', version: '2.3.7', target_framework: '.NETStandard2.0')
+ expect(dependencies).to include(name: 'Newtonsoft.Json', version: '12.0.3', target_framework: '.NETStandard2.0')
+ end
+ end
+
+ context 'with a nuspec file with metadata' do
+ let(:nuspec_filepath) { 'packages/nuget/with_metadata.nuspec' }
+
+ it { expect(subject[:package_tags].sort).to eq(%w(foo bar test tag1 tag2 tag3 tag4 tag5).sort) }
+ end
+ end
+
+ context 'with a nuspec file with metadata' do
+ let_it_be(:nuspec_filepath) { 'packages/nuget/with_metadata.nuspec' }
+
+ before do
+ allow(service).to receive(:nuspec_file).and_return(fixture_file(nuspec_filepath))
+ end
+
+ it { expect(subject[:license_url]).to eq('https://opensource.org/licenses/MIT') }
+ it { expect(subject[:project_url]).to eq('https://gitlab.com/gitlab-org/gitlab') }
+ it { expect(subject[:icon_url]).to eq('https://opensource.org/files/osi_keyhole_300X300_90ppi_0.png') }
+ end
+
+ context 'with invalid package file id' do
+ let(:package_file) { OpenStruct.new(id: 555) }
+
+ it { expect { subject }.to raise_error(::Packages::Nuget::MetadataExtractionService::ExtractionError, 'invalid package file') }
+ end
+
+ context 'linked to a non nuget package' do
+ before do
+ package_file.package.maven!
+ end
+
+ it { expect { subject }.to raise_error(::Packages::Nuget::MetadataExtractionService::ExtractionError, 'invalid package file') }
+ end
+
+ context 'with a 0 byte package file id' do
+ before do
+ allow_any_instance_of(Packages::PackageFileUploader).to receive(:size).and_return(0)
+ end
+
+ it { expect { subject }.to raise_error(::Packages::Nuget::MetadataExtractionService::ExtractionError, 'invalid package file') }
+ end
+
+ context 'without the nuspec file' do
+ before do
+ allow_any_instance_of(Zip::File).to receive(:glob).and_return([])
+ end
+
+ it { expect { subject }.to raise_error(::Packages::Nuget::MetadataExtractionService::ExtractionError, 'nuspec file not found') }
+ end
+
+ context 'with a too big nuspec file' do
+ before do
+ allow_any_instance_of(Zip::File).to receive(:glob).and_return([OpenStruct.new(size: 6.megabytes)])
+ end
+
+ it { expect { subject }.to raise_error(::Packages::Nuget::MetadataExtractionService::ExtractionError, 'nuspec file too big') }
+ end
+ end
+end
diff --git a/spec/services/packages/nuget/search_service_spec.rb b/spec/services/packages/nuget/search_service_spec.rb
new file mode 100644
index 00000000000..d163e7087e4
--- /dev/null
+++ b/spec/services/packages/nuget/search_service_spec.rb
@@ -0,0 +1,116 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+RSpec.describe Packages::Nuget::SearchService do
+ let_it_be(:project) { create(:project) }
+ let_it_be(:package_a) { create(:nuget_package, project: project, name: 'DummyPackageA') }
+ let_it_be(:packages_b) { create_list(:nuget_package, 5, project: project, name: 'DummyPackageB') }
+ let_it_be(:packages_c) { create_list(:nuget_package, 5, project: project, name: 'DummyPackageC') }
+ let_it_be(:package_d) { create(:nuget_package, project: project, name: 'FooBarD') }
+ let_it_be(:other_package_a) { create(:nuget_package, name: 'DummyPackageA') }
+ let_it_be(:other_package_a) { create(:nuget_package, name: 'DummyPackageB') }
+ let(:search_term) { 'ummy' }
+ let(:per_page) { 5 }
+ let(:padding) { 0 }
+ let(:include_prerelease_versions) { true }
+ let(:options) { { include_prerelease_versions: include_prerelease_versions, per_page: per_page, padding: padding } }
+
+ describe '#execute' do
+ subject { described_class.new(project, search_term, options).execute }
+
+ it { expect_search_results 3, package_a, packages_b, packages_c }
+
+ context 'with a smaller per page count' do
+ let(:per_page) { 2 }
+
+ it { expect_search_results 3, package_a, packages_b }
+ end
+
+ context 'with 0 per page count' do
+ let(:per_page) { 0 }
+
+ it { expect_search_results 3, [] }
+ end
+
+ context 'with a negative per page count' do
+ let(:per_page) { -1 }
+
+ it { expect { subject }.to raise_error(ArgumentError, 'negative per_page') }
+ end
+
+ context 'with a padding' do
+ let(:padding) { 2 }
+
+ it { expect_search_results 3, packages_c }
+ end
+
+ context 'with a too big padding' do
+ let(:padding) { 5 }
+
+ it { expect_search_results 3, [] }
+ end
+
+ context 'with a negative padding' do
+ let(:padding) { -1 }
+
+ it { expect { subject }.to raise_error(ArgumentError, 'negative padding') }
+ end
+
+ context 'with search term' do
+ let(:search_term) { 'umm' }
+
+ it { expect_search_results 3, package_a, packages_b, packages_c }
+ end
+
+ context 'with nil search term' do
+ let(:search_term) { nil }
+
+ it { expect_search_results 4, package_a, packages_b, packages_c, package_d }
+ end
+
+ context 'with empty search term' do
+ let(:search_term) { '' }
+
+ it { expect_search_results 4, package_a, packages_b, packages_c, package_d }
+ end
+
+ context 'with prefix search term' do
+ let(:search_term) { 'dummy' }
+
+ it { expect_search_results 3, package_a, packages_b, packages_c }
+ end
+
+ context 'with suffix search term' do
+ let(:search_term) { 'packagec' }
+
+ it { expect_search_results 1, packages_c }
+ end
+
+ context 'with pre release packages' do
+ let_it_be(:package_e) { create(:nuget_package, project: project, name: 'DummyPackageE', version: '3.2.1-alpha') }
+
+ context 'including them' do
+ it { expect_search_results 4, package_a, packages_b, packages_c, package_e }
+ end
+
+ context 'excluding them' do
+ let(:include_prerelease_versions) { false }
+
+ it { expect_search_results 3, package_a, packages_b, packages_c }
+
+ context 'when mixed with release versions' do
+ let_it_be(:package_e_release) { create(:nuget_package, project: project, name: 'DummyPackageE', version: '3.2.1') }
+
+ it { expect_search_results 4, package_a, packages_b, packages_c, package_e_release }
+ end
+ end
+ end
+
+ def expect_search_results(total_count, *results)
+ search = subject
+
+ expect(search.total_count).to eq total_count
+ expect(search.results).to match_array(Array.wrap(results).flatten)
+ end
+ end
+end
diff --git a/spec/services/packages/nuget/sync_metadatum_service_spec.rb b/spec/services/packages/nuget/sync_metadatum_service_spec.rb
new file mode 100644
index 00000000000..32093c48b76
--- /dev/null
+++ b/spec/services/packages/nuget/sync_metadatum_service_spec.rb
@@ -0,0 +1,57 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Packages::Nuget::SyncMetadatumService do
+ let_it_be(:package, reload: true) { create(:nuget_package) }
+ let_it_be(:metadata) do
+ {
+ project_url: 'https://test.org/test',
+ license_url: 'https://test.org/MIT',
+ icon_url: 'https://test.org/icon.png'
+ }
+ end
+
+ let(:service) { described_class.new(package, metadata) }
+ let(:nuget_metadatum) { package.nuget_metadatum }
+
+ describe '#execute' do
+ subject { service.execute }
+
+ RSpec.shared_examples 'saving metadatum attributes' do
+ it 'saves nuget metadatum' do
+ subject
+
+ metadata.each do |attribute, expected_value|
+ expect(nuget_metadatum.send(attribute)).to eq(expected_value)
+ end
+ end
+ end
+
+ it 'creates a nuget metadatum' do
+ expect { subject }
+ .to change { package.nuget_metadatum.present? }.from(false).to(true)
+ end
+
+ it_behaves_like 'saving metadatum attributes'
+
+ context 'with exisiting nuget metadatum' do
+ let_it_be(:package) { create(:nuget_package, :with_metadatum) }
+
+ it 'does not create a nuget metadatum' do
+ expect { subject }.to change { ::Packages::Nuget::Metadatum.count }.by(0)
+ end
+
+ it_behaves_like 'saving metadatum attributes'
+
+ context 'with empty metadata' do
+ let_it_be(:metadata) { {} }
+
+ it 'destroys the nuget metadatum' do
+ expect { subject }
+ .to change { package.reload.nuget_metadatum.present? }.from(true).to(false)
+ end
+ end
+ end
+ end
+end
diff --git a/spec/services/packages/nuget/update_package_from_metadata_service_spec.rb b/spec/services/packages/nuget/update_package_from_metadata_service_spec.rb
new file mode 100644
index 00000000000..b7c780c1ee2
--- /dev/null
+++ b/spec/services/packages/nuget/update_package_from_metadata_service_spec.rb
@@ -0,0 +1,237 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Packages::Nuget::UpdatePackageFromMetadataService, :clean_gitlab_redis_shared_state do
+ include ExclusiveLeaseHelpers
+
+ let(:package) { create(:nuget_package) }
+ let(:package_file) { package.package_files.first }
+ let(:service) { described_class.new(package_file) }
+ let(:package_name) { 'DummyProject.DummyPackage' }
+ let(:package_version) { '1.0.0' }
+ let(:package_file_name) { 'dummyproject.dummypackage.1.0.0.nupkg' }
+
+ RSpec.shared_examples 'raising an' do |error_class|
+ it "raises an #{error_class}" do
+ expect { subject }.to raise_error(error_class)
+ end
+ end
+
+ describe '#execute' do
+ subject { service.execute }
+
+ before do
+ stub_package_file_object_storage(enabled: true, direct_upload: true)
+ end
+
+ RSpec.shared_examples 'taking the lease' do
+ before do
+ allow(service).to receive(:lease_release?).and_return(false)
+ end
+
+ it 'takes the lease' do
+ expect(service).to receive(:try_obtain_lease).and_call_original
+
+ subject
+
+ expect(service.exclusive_lease.exists?).to be_truthy
+ end
+ end
+
+ RSpec.shared_examples 'not updating the package if the lease is taken' do
+ context 'without obtaining the exclusive lease' do
+ let(:lease_key) { "packages:nuget:update_package_from_metadata_service:package:#{package_id}" }
+ let(:metadata) { { package_name: package_name, package_version: package_version } }
+ let(:package_from_package_file) { package_file.package }
+
+ before do
+ stub_exclusive_lease_taken(lease_key, timeout: 1.hour)
+ # to allow the above stub, we need to stub the metadata function as the
+ # original implementation will try to get an exclusive lease on the
+ # file in object storage
+ allow(service).to receive(:metadata).and_return(metadata)
+ end
+
+ it 'does not update the package' do
+ expect(service).to receive(:try_obtain_lease).and_call_original
+
+ expect { subject }
+ .to change { ::Packages::Package.count }.by(0)
+ .and change { Packages::DependencyLink.count }.by(0)
+ expect(package_file.reload.file_name).not_to eq(package_file_name)
+ expect(package_file.package.reload.name).not_to eq(package_name)
+ expect(package_file.package.version).not_to eq(package_version)
+ end
+ end
+ end
+
+ context 'with no existing package' do
+ let(:package_id) { package.id }
+
+ it 'updates package and package file' do
+ expect { subject }
+ .to change { ::Packages::Package.count }.by(1)
+ .and change { Packages::Dependency.count }.by(1)
+ .and change { Packages::DependencyLink.count }.by(1)
+ .and change { ::Packages::Nuget::Metadatum.count }.by(0)
+
+ expect(package.reload.name).to eq(package_name)
+ expect(package.version).to eq(package_version)
+ expect(package_file.reload.file_name).to eq(package_file_name)
+ # hard reset needed to properly reload package_file.file
+ expect(Packages::PackageFile.find(package_file.id).file.size).not_to eq 0
+ end
+
+ it_behaves_like 'taking the lease'
+
+ it_behaves_like 'not updating the package if the lease is taken'
+ end
+
+ context 'with existing package' do
+ let!(:existing_package) { create(:nuget_package, project: package.project, name: package_name, version: package_version) }
+ let(:package_id) { existing_package.id }
+
+ it 'link existing package and updates package file' do
+ expect(service).to receive(:try_obtain_lease).and_call_original
+
+ expect { subject }
+ .to change { ::Packages::Package.count }.by(-1)
+ .and change { Packages::Dependency.count }.by(0)
+ .and change { Packages::DependencyLink.count }.by(0)
+ .and change { Packages::Nuget::DependencyLinkMetadatum.count }.by(0)
+ .and change { ::Packages::Nuget::Metadatum.count }.by(0)
+ expect(package_file.reload.file_name).to eq(package_file_name)
+ expect(package_file.package).to eq(existing_package)
+ end
+
+ it_behaves_like 'taking the lease'
+
+ it_behaves_like 'not updating the package if the lease is taken'
+ end
+
+ context 'with a nuspec file with metadata' do
+ let(:nuspec_filepath) { 'packages/nuget/with_metadata.nuspec' }
+ let(:expected_tags) { %w(foo bar test tag1 tag2 tag3 tag4 tag5) }
+
+ before do
+ allow_any_instance_of(Packages::Nuget::MetadataExtractionService)
+ .to receive(:nuspec_file)
+ .and_return(fixture_file(nuspec_filepath))
+ end
+
+ it 'creates tags' do
+ expect(service).to receive(:try_obtain_lease).and_call_original
+ expect { subject }.to change { ::Packages::Tag.count }.by(8)
+ expect(package.reload.tags.map(&:name)).to contain_exactly(*expected_tags)
+ end
+
+ context 'with existing package and tags' do
+ let!(:existing_package) { create(:nuget_package, project: package.project, name: 'DummyProject.WithMetadata', version: '1.2.3') }
+ let!(:tag1) { create(:packages_tag, package: existing_package, name: 'tag1') }
+ let!(:tag2) { create(:packages_tag, package: existing_package, name: 'tag2') }
+ let!(:tag3) { create(:packages_tag, package: existing_package, name: 'tag_not_in_metadata') }
+
+ it 'creates tags and deletes those not in metadata' do
+ expect(service).to receive(:try_obtain_lease).and_call_original
+ expect { subject }.to change { ::Packages::Tag.count }.by(5)
+ expect(existing_package.tags.map(&:name)).to contain_exactly(*expected_tags)
+ end
+ end
+
+ it 'creates nuget metadatum' do
+ expect { subject }
+ .to change { ::Packages::Package.count }.by(1)
+ .and change { ::Packages::Nuget::Metadatum.count }.by(1)
+
+ metadatum = package_file.reload.package.nuget_metadatum
+ expect(metadatum.license_url).to eq('https://opensource.org/licenses/MIT')
+ expect(metadatum.project_url).to eq('https://gitlab.com/gitlab-org/gitlab')
+ expect(metadatum.icon_url).to eq('https://opensource.org/files/osi_keyhole_300X300_90ppi_0.png')
+ end
+
+ context 'with too long url' do
+ let_it_be(:too_long_url) { "http://localhost/#{'bananas' * 50}" }
+
+ let(:metadata) { { package_name: package_name, package_version: package_version, license_url: too_long_url } }
+
+ before do
+ allow(service).to receive(:metadata).and_return(metadata)
+ end
+
+ it_behaves_like 'raising an', ::Packages::Nuget::UpdatePackageFromMetadataService::InvalidMetadataError
+ end
+ end
+
+ context 'with nuspec file with dependencies' do
+ let(:nuspec_filepath) { 'packages/nuget/with_dependencies.nuspec' }
+ let(:package_name) { 'Test.Package' }
+ let(:package_version) { '3.5.2' }
+ let(:package_file_name) { 'test.package.3.5.2.nupkg' }
+
+ before do
+ allow_any_instance_of(Packages::Nuget::MetadataExtractionService)
+ .to receive(:nuspec_file)
+ .and_return(fixture_file(nuspec_filepath))
+ end
+
+ it 'updates package and package file' do
+ expect { subject }
+ .to change { ::Packages::Package.count }.by(1)
+ .and change { Packages::Dependency.count }.by(4)
+ .and change { Packages::DependencyLink.count }.by(4)
+ .and change { Packages::Nuget::DependencyLinkMetadatum.count }.by(2)
+
+ expect(package.reload.name).to eq(package_name)
+ expect(package.version).to eq(package_version)
+ expect(package_file.reload.file_name).to eq(package_file_name)
+ # hard reset needed to properly reload package_file.file
+ expect(Packages::PackageFile.find(package_file.id).file.size).not_to eq 0
+ end
+ end
+
+ context 'with package file not containing a nuspec file' do
+ before do
+ allow_any_instance_of(Zip::File).to receive(:glob).and_return([])
+ end
+
+ it_behaves_like 'raising an', ::Packages::Nuget::MetadataExtractionService::ExtractionError
+ end
+
+ context 'with package file with a blank package name' do
+ before do
+ allow(service).to receive(:package_name).and_return('')
+ end
+
+ it_behaves_like 'raising an', ::Packages::Nuget::UpdatePackageFromMetadataService::InvalidMetadataError
+ end
+
+ context 'with package file with a blank package version' do
+ before do
+ allow(service).to receive(:package_version).and_return('')
+ end
+
+ it_behaves_like 'raising an', ::Packages::Nuget::UpdatePackageFromMetadataService::InvalidMetadataError
+ end
+
+ context 'with an invalid package version' do
+ invalid_versions = [
+ '555',
+ '1.2',
+ '1./2.3',
+ '../../../../../1.2.3',
+ '%2e%2e%2f1.2.3'
+ ]
+
+ invalid_versions.each do |invalid_version|
+ it "raises an error for version #{invalid_version}" do
+ allow(service).to receive(:package_version).and_return(invalid_version)
+
+ expect { subject }.to raise_error(ActiveRecord::RecordInvalid, 'Validation failed: Version is invalid')
+ expect(package_file.file_name).not_to include(invalid_version)
+ expect(package_file.file.file.path).not_to include(invalid_version)
+ end
+ end
+ end
+ end
+end
diff --git a/spec/services/packages/pypi/create_package_service_spec.rb b/spec/services/packages/pypi/create_package_service_spec.rb
new file mode 100644
index 00000000000..250b43d1f75
--- /dev/null
+++ b/spec/services/packages/pypi/create_package_service_spec.rb
@@ -0,0 +1,83 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+RSpec.describe Packages::Pypi::CreatePackageService do
+ include PackagesManagerApiSpecHelpers
+
+ let_it_be(:project) { create(:project) }
+ let_it_be(:user) { create(:user) }
+ let_it_be(:params) do
+ {
+ name: 'foo',
+ version: '1.0',
+ content: temp_file('foo.tgz'),
+ requires_python: '>=2.7',
+ sha256_digest: '123',
+ md5_digest: '567'
+ }
+ end
+
+ describe '#execute' do
+ subject { described_class.new(project, user, params).execute }
+
+ let(:created_package) { Packages::Package.pypi.last }
+
+ context 'without an existing package' do
+ it 'creates the package' do
+ expect { subject }.to change { Packages::Package.pypi.count }.by(1)
+
+ expect(created_package.name).to eq 'foo'
+ expect(created_package.version).to eq '1.0'
+
+ expect(created_package.pypi_metadatum.required_python).to eq '>=2.7'
+ expect(created_package.package_files.size).to eq 1
+ expect(created_package.package_files.first.file_name).to eq 'foo.tgz'
+ expect(created_package.package_files.first.file_sha256).to eq '123'
+ expect(created_package.package_files.first.file_md5).to eq '567'
+ end
+ end
+
+ context 'with an existing package' do
+ before do
+ described_class.new(project, user, params).execute
+ end
+
+ context 'with an existing file' do
+ before do
+ params[:content] = temp_file('foo.tgz')
+ params[:sha256_digest] = 'abc'
+ params[:md5_digest] = 'def'
+ end
+
+ it 'replaces the file' do
+ expect { subject }
+ .to change { Packages::Package.pypi.count }.by(0)
+ .and change { Packages::PackageFile.count }.by(1)
+
+ expect(created_package.package_files.size).to eq 2
+ expect(created_package.package_files.first.file_name).to eq 'foo.tgz'
+ expect(created_package.package_files.first.file_sha256).to eq '123'
+ expect(created_package.package_files.first.file_md5).to eq '567'
+ expect(created_package.package_files.last.file_name).to eq 'foo.tgz'
+ expect(created_package.package_files.last.file_sha256).to eq 'abc'
+ expect(created_package.package_files.last.file_md5).to eq 'def'
+ end
+ end
+
+ context 'without an existing file' do
+ before do
+ params[:content] = temp_file('another.tgz')
+ end
+
+ it 'adds the file' do
+ expect { subject }
+ .to change { Packages::Package.pypi.count }.by(0)
+ .and change { Packages::PackageFile.count }.by(1)
+
+ expect(created_package.package_files.size).to eq 2
+ expect(created_package.package_files.map(&:file_name).sort).to eq ['another.tgz', 'foo.tgz']
+ end
+ end
+ end
+ end
+end
diff --git a/spec/services/packages/remove_tag_service_spec.rb b/spec/services/packages/remove_tag_service_spec.rb
new file mode 100644
index 00000000000..084635824e5
--- /dev/null
+++ b/spec/services/packages/remove_tag_service_spec.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+RSpec.describe Packages::RemoveTagService do
+ let!(:package_tag) { create(:packages_tag) }
+
+ describe '#execute' do
+ subject { described_class.new(package_tag).execute }
+
+ context 'with existing tag' do
+ it { expect { subject }.to change { Packages::Tag.count }.by(-1) }
+ end
+
+ context 'with nil' do
+ subject { described_class.new(nil) }
+
+ it { expect { subject }.to raise_error(ArgumentError) }
+ end
+ end
+end
diff --git a/spec/services/packages/update_tags_service_spec.rb b/spec/services/packages/update_tags_service_spec.rb
new file mode 100644
index 00000000000..4a122d1c718
--- /dev/null
+++ b/spec/services/packages/update_tags_service_spec.rb
@@ -0,0 +1,59 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+RSpec.describe Packages::UpdateTagsService do
+ let_it_be(:package, reload: true) { create(:nuget_package) }
+
+ let(:tags) { %w(test-tag tag1 tag2 tag3) }
+ let(:service) { described_class.new(package, tags) }
+
+ describe '#execute' do
+ subject { service.execute }
+
+ RSpec.shared_examples 'updating tags' do |tags_count|
+ it 'updates a tag' do
+ expect { subject }.to change { Packages::Tag.count }.by(tags_count)
+ expect(package.reload.tags.map(&:name)).to contain_exactly(*tags)
+ end
+ end
+
+ it_behaves_like 'updating tags', 4
+
+ context 'with an existing tag' do
+ before do
+ create(:packages_tag, package: package2, name: 'test-tag')
+ end
+
+ context 'on the same package' do
+ let_it_be(:package2) { package }
+
+ it_behaves_like 'updating tags', 3
+
+ context 'with different name' do
+ before do
+ create(:packages_tag, package: package2, name: 'to_be_destroyed')
+ end
+
+ it_behaves_like 'updating tags', 2
+ end
+ end
+
+ context 'on a different package' do
+ let_it_be(:package2) { create(:nuget_package) }
+
+ it_behaves_like 'updating tags', 4
+ end
+ end
+
+ context 'with empty tags' do
+ let(:tags) { [] }
+
+ it 'is a no op' do
+ expect(package).not_to receive(:tags)
+ expect(::Gitlab::Database).not_to receive(:bulk_insert)
+
+ subject
+ end
+ end
+ end
+end
diff --git a/spec/services/pages/delete_services_spec.rb b/spec/services/pages/delete_services_spec.rb
index c253f294e80..f6d4694b4dd 100644
--- a/spec/services/pages/delete_services_spec.rb
+++ b/spec/services/pages/delete_services_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Pages::DeleteService do
+RSpec.describe Pages::DeleteService do
let_it_be(:project) { create(:project, path: "my.project")}
let_it_be(:admin) { create(:admin) }
let_it_be(:domain) { create(:pages_domain, project: project) }
diff --git a/spec/services/pages_domains/create_acme_order_service_spec.rb b/spec/services/pages_domains/create_acme_order_service_spec.rb
index d59aa9b979e..35b2cc56973 100644
--- a/spec/services/pages_domains/create_acme_order_service_spec.rb
+++ b/spec/services/pages_domains/create_acme_order_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe PagesDomains::CreateAcmeOrderService do
+RSpec.describe PagesDomains::CreateAcmeOrderService do
include LetsEncryptHelpers
let(:pages_domain) { create(:pages_domain) }
diff --git a/spec/services/pages_domains/obtain_lets_encrypt_certificate_service_spec.rb b/spec/services/pages_domains/obtain_lets_encrypt_certificate_service_spec.rb
index 22fcc6b9a79..4d489d7fe4b 100644
--- a/spec/services/pages_domains/obtain_lets_encrypt_certificate_service_spec.rb
+++ b/spec/services/pages_domains/obtain_lets_encrypt_certificate_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe PagesDomains::ObtainLetsEncryptCertificateService do
+RSpec.describe PagesDomains::ObtainLetsEncryptCertificateService do
include LetsEncryptHelpers
let(:pages_domain) { create(:pages_domain, :without_certificate, :without_key) }
diff --git a/spec/services/pages_domains/retry_acme_order_service_spec.rb b/spec/services/pages_domains/retry_acme_order_service_spec.rb
index 0185f10864c..601de24e766 100644
--- a/spec/services/pages_domains/retry_acme_order_service_spec.rb
+++ b/spec/services/pages_domains/retry_acme_order_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe PagesDomains::RetryAcmeOrderService do
+RSpec.describe PagesDomains::RetryAcmeOrderService do
let(:domain) { create(:pages_domain, auto_ssl_enabled: true, auto_ssl_failed: true) }
let(:service) { described_class.new(domain) }
diff --git a/spec/services/personal_access_tokens/create_service_spec.rb b/spec/services/personal_access_tokens/create_service_spec.rb
index 9190434b96a..475ade95948 100644
--- a/spec/services/personal_access_tokens/create_service_spec.rb
+++ b/spec/services/personal_access_tokens/create_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe PersonalAccessTokens::CreateService do
+RSpec.describe PersonalAccessTokens::CreateService do
describe '#execute' do
context 'with valid params' do
it 'creates personal access token record' do
diff --git a/spec/services/personal_access_tokens/last_used_service_spec.rb b/spec/services/personal_access_tokens/last_used_service_spec.rb
new file mode 100644
index 00000000000..6fc74e27dd9
--- /dev/null
+++ b/spec/services/personal_access_tokens/last_used_service_spec.rb
@@ -0,0 +1,47 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe PersonalAccessTokens::LastUsedService do
+ describe '#execute' do
+ subject { described_class.new(personal_access_token).execute }
+
+ context 'when the personal access token has not been used recently' do
+ let_it_be(:personal_access_token) { create(:personal_access_token, last_used_at: 1.year.ago) }
+
+ it 'updates the last_used_at timestamp' do
+ expect { subject }.to change { personal_access_token.last_used_at }
+ end
+
+ it 'does not run on read-only GitLab instances' do
+ allow(::Gitlab::Database).to receive(:read_only?).and_return(true)
+
+ expect { subject }.not_to change { personal_access_token.last_used_at }
+ end
+ end
+
+ context 'when the personal access token has been used recently' do
+ let_it_be(:personal_access_token) { create(:personal_access_token, last_used_at: 1.minute.ago) }
+
+ it 'does not update the last_used_at timestamp' do
+ expect { subject }.not_to change { personal_access_token.last_used_at }
+ end
+ end
+
+ context 'when the last_used_at timestamp is nil' do
+ let_it_be(:personal_access_token) { create(:personal_access_token, last_used_at: nil) }
+
+ it 'updates the last_used_at timestamp' do
+ expect { subject }.to change { personal_access_token.last_used_at }
+ end
+ end
+
+ context 'when not a personal access token' do
+ let_it_be(:personal_access_token) { create(:oauth_access_token) }
+
+ it 'does not execute' do
+ expect(subject).to be_nil
+ end
+ end
+ end
+end
diff --git a/spec/services/pod_logs/base_service_spec.rb b/spec/services/pod_logs/base_service_spec.rb
index bc4989b59d9..6f7731fda3a 100644
--- a/spec/services/pod_logs/base_service_spec.rb
+++ b/spec/services/pod_logs/base_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ::PodLogs::BaseService do
+RSpec.describe ::PodLogs::BaseService do
include KubernetesHelpers
let_it_be(:cluster) { create(:cluster, :provided_by_gcp, environment_scope: '*') }
diff --git a/spec/services/pod_logs/elasticsearch_service_spec.rb b/spec/services/pod_logs/elasticsearch_service_spec.rb
index 8060d07461a..9431e47c6f2 100644
--- a/spec/services/pod_logs/elasticsearch_service_spec.rb
+++ b/spec/services/pod_logs/elasticsearch_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ::PodLogs::ElasticsearchService do
+RSpec.describe ::PodLogs::ElasticsearchService do
let_it_be(:cluster) { create(:cluster, :provided_by_gcp, environment_scope: '*') }
let(:namespace) { 'autodevops-deploy-9-production' }
diff --git a/spec/services/pod_logs/kubernetes_service_spec.rb b/spec/services/pod_logs/kubernetes_service_spec.rb
index a1f7645323b..3e31ff15c1b 100644
--- a/spec/services/pod_logs/kubernetes_service_spec.rb
+++ b/spec/services/pod_logs/kubernetes_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ::PodLogs::KubernetesService do
+RSpec.describe ::PodLogs::KubernetesService do
include KubernetesHelpers
let_it_be(:cluster) { create(:cluster, :provided_by_gcp, environment_scope: '*') }
diff --git a/spec/services/post_receive_service_spec.rb b/spec/services/post_receive_service_spec.rb
index 25f4122f134..c726e1851a7 100644
--- a/spec/services/post_receive_service_spec.rb
+++ b/spec/services/post_receive_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe PostReceiveService do
+RSpec.describe PostReceiveService do
include Gitlab::Routing
let_it_be(:user) { create(:user) }
@@ -166,41 +166,6 @@ describe PostReceiveService do
expect(subject).to include(build_alert_message(message))
end
end
-
- context 'storage size limit alerts' do
- let(:check_storage_size_response) { ServiceResponse.success }
-
- before do
- expect_next_instance_of(Namespaces::CheckStorageSizeService, project.namespace, user) do |check_storage_size_service|
- expect(check_storage_size_service).to receive(:execute).and_return(check_storage_size_response)
- end
- end
-
- context 'when there is no payload' do
- it 'adds no alert' do
- expect(subject.size).to eq(1)
- end
- end
-
- context 'when there is payload' do
- let(:check_storage_size_response) do
- ServiceResponse.success(
- payload: {
- alert_level: :info,
- usage_message: "Usage",
- explanation_message: "Explanation"
- }
- )
- end
-
- it 'adds an alert' do
- response = subject
-
- expect(response.size).to eq(2)
- expect(response).to include(build_alert_message("##### INFO #####\nUsage\nExplanation"))
- end
- end
- end
end
context 'with PersonalSnippet' do
diff --git a/spec/services/preview_markdown_service_spec.rb b/spec/services/preview_markdown_service_spec.rb
index d25e9958831..2509d1300b3 100644
--- a/spec/services/preview_markdown_service_spec.rb
+++ b/spec/services/preview_markdown_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe PreviewMarkdownService do
+RSpec.describe PreviewMarkdownService do
let(:user) { create(:user) }
let(:project) { create(:project, :repository) }
diff --git a/spec/services/projects/after_import_service_spec.rb b/spec/services/projects/after_import_service_spec.rb
index 82f654cea10..a109348ea19 100644
--- a/spec/services/projects/after_import_service_spec.rb
+++ b/spec/services/projects/after_import_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Projects::AfterImportService do
+RSpec.describe Projects::AfterImportService do
include GitHelpers
subject { described_class.new(project) }
@@ -72,6 +72,26 @@ describe Projects::AfterImportService do
end
end
+ context 'when housekeeping service lease is taken' do
+ let(:exception) { Projects::HousekeepingService::LeaseTaken.new }
+
+ it 'logs the error message' do
+ allow_next_instance_of(Projects::HousekeepingService) do |instance|
+ expect(instance).to receive(:execute).and_raise(exception)
+ end
+
+ expect(Gitlab::Import::Logger).to receive(:info).with(
+ {
+ message: 'Project housekeeping failed',
+ project_full_path: project.full_path,
+ project_id: project.id,
+ 'error.message' => exception.to_s
+ }).and_call_original
+
+ subject.execute
+ end
+ end
+
context 'when after import action throw retriable exception one time' do
let(:exception) { GRPC::DeadlineExceeded.new }
diff --git a/spec/services/projects/after_rename_service_spec.rb b/spec/services/projects/after_rename_service_spec.rb
index b81dd3d7e3f..52136b37c66 100644
--- a/spec/services/projects/after_rename_service_spec.rb
+++ b/spec/services/projects/after_rename_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Projects::AfterRenameService do
+RSpec.describe Projects::AfterRenameService do
let(:rugged_config) { rugged_repo(project.repository).config }
let(:legacy_storage) { Storage::LegacyProject.new(project) }
let(:hashed_storage) { Storage::Hashed.new(project) }
diff --git a/spec/services/projects/alerting/notify_service_spec.rb b/spec/services/projects/alerting/notify_service_spec.rb
index 2f8c2049f85..123b0bad2a8 100644
--- a/spec/services/projects/alerting/notify_service_spec.rb
+++ b/spec/services/projects/alerting/notify_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Projects::Alerting::NotifyService do
+RSpec.describe Projects::Alerting::NotifyService do
let_it_be(:project, reload: true) { create(:project) }
before do
@@ -21,7 +21,7 @@ describe Projects::Alerting::NotifyService do
it 'processes issues' do
expect(IncidentManagement::ProcessAlertWorker)
.to receive(:perform_async)
- .with(project.id, kind_of(Hash), kind_of(Integer))
+ .with(nil, nil, kind_of(Integer))
.once
Sidekiq::Testing.inline! do
@@ -64,12 +64,6 @@ describe Projects::Alerting::NotifyService do
end
end
- shared_examples 'NotifyService does not create alert' do
- it 'does not create alert' do
- expect { subject }.not_to change(AlertManagement::Alert, :count)
- end
- end
-
describe '#execute' do
let(:token) { 'invalid-token' }
let(:starts_at) { Time.current.change(usec: 0) }
@@ -107,60 +101,64 @@ describe Projects::Alerting::NotifyService do
end
context 'with valid payload' do
+ shared_examples 'assigns the alert properties' do
+ it 'ensure that created alert has all data properly assigned' do
+ subject
+
+ expect(last_alert_attributes).to match(
+ project_id: project.id,
+ title: payload_raw.fetch(:title),
+ started_at: Time.zone.parse(payload_raw.fetch(:start_time)),
+ severity: payload_raw.fetch(:severity),
+ status: AlertManagement::Alert::STATUSES[:triggered],
+ events: 1,
+ hosts: payload_raw.fetch(:hosts),
+ payload: payload_raw.with_indifferent_access,
+ issue_id: nil,
+ description: payload_raw.fetch(:description),
+ monitoring_tool: payload_raw.fetch(:monitoring_tool),
+ service: payload_raw.fetch(:service),
+ fingerprint: Digest::SHA1.hexdigest(fingerprint),
+ ended_at: nil,
+ prometheus_alert_id: nil,
+ environment_id: nil
+ )
+ end
+ end
+
let(:last_alert_attributes) do
AlertManagement::Alert.last.attributes
.except('id', 'iid', 'created_at', 'updated_at')
.with_indifferent_access
end
- it 'creates AlertManagement::Alert' do
- expect { subject }.to change(AlertManagement::Alert, :count).by(1)
- end
-
- it 'created alert has all data properly assigned' do
- subject
-
- expect(last_alert_attributes).to match(
- project_id: project.id,
- title: payload_raw.fetch(:title),
- started_at: Time.zone.parse(payload_raw.fetch(:start_time)),
- severity: payload_raw.fetch(:severity),
- status: AlertManagement::Alert::STATUSES[:triggered],
- events: 1,
- hosts: payload_raw.fetch(:hosts),
- payload: payload_raw.with_indifferent_access,
- issue_id: nil,
- description: payload_raw.fetch(:description),
- monitoring_tool: payload_raw.fetch(:monitoring_tool),
- service: payload_raw.fetch(:service),
- fingerprint: Digest::SHA1.hexdigest(fingerprint),
- ended_at: nil
- )
- end
-
- it 'executes the alert service hooks' do
- slack_service = create(:service, type: 'SlackService', project: project, alert_events: true, active: true)
- subject
-
- expect(ProjectServiceWorker).to have_received(:perform_async).with(slack_service.id, an_instance_of(Hash))
- end
+ it_behaves_like 'creates an alert management alert'
+ it_behaves_like 'assigns the alert properties'
context 'existing alert with same fingerprint' do
let(:fingerprint_sha) { Digest::SHA1.hexdigest(fingerprint) }
- let!(:existing_alert) { create(:alert_management_alert, project: project, fingerprint: fingerprint_sha) }
+ let!(:alert) { create(:alert_management_alert, project: project, fingerprint: fingerprint_sha) }
+
+ it_behaves_like 'adds an alert management alert event'
+
+ context 'existing alert is resolved' do
+ let!(:alert) { create(:alert_management_alert, :resolved, project: project, fingerprint: fingerprint_sha) }
- it 'does not create AlertManagement::Alert' do
- expect { subject }.not_to change(AlertManagement::Alert, :count)
+ it_behaves_like 'creates an alert management alert'
+ it_behaves_like 'assigns the alert properties'
end
- it 'increments the existing alert count' do
- expect { subject }.to change { existing_alert.reload.events }.from(1).to(2)
+ context 'existing alert is ignored' do
+ let!(:alert) { create(:alert_management_alert, :ignored, project: project, fingerprint: fingerprint_sha) }
+
+ it_behaves_like 'adds an alert management alert event'
end
- it 'does not executes the alert service hooks' do
- subject
+ context 'two existing alerts, one resolved one open' do
+ let!(:resolved_existing_alert) { create(:alert_management_alert, :resolved, project: project, fingerprint: fingerprint_sha) }
+ let!(:alert) { create(:alert_management_alert, project: project, fingerprint: fingerprint_sha) }
- expect(ProjectServiceWorker).not_to have_received(:perform_async)
+ it_behaves_like 'adds an alert management alert event'
end
end
@@ -172,9 +170,7 @@ describe Projects::Alerting::NotifyService do
}
end
- it 'creates AlertManagement::Alert' do
- expect { subject }.to change(AlertManagement::Alert, :count).by(1)
- end
+ it_behaves_like 'creates an alert management alert'
it 'created alert has all data properly assigned' do
subject
@@ -193,7 +189,9 @@ describe Projects::Alerting::NotifyService do
monitoring_tool: nil,
service: nil,
fingerprint: nil,
- ended_at: nil
+ ended_at: nil,
+ prometheus_alert_id: nil,
+ environment_id: nil
)
end
end
@@ -214,19 +212,19 @@ describe Projects::Alerting::NotifyService do
end
it_behaves_like 'does not process incident issues due to error', http_status: :bad_request
- it_behaves_like 'NotifyService does not create alert'
+ it_behaves_like 'does not an create alert management alert'
end
context 'when alert already exists' do
let(:fingerprint_sha) { Digest::SHA1.hexdigest(fingerprint) }
- let!(:existing_alert) { create(:alert_management_alert, project: project, fingerprint: fingerprint_sha) }
+ let!(:alert) { create(:alert_management_alert, project: project, fingerprint: fingerprint_sha) }
context 'when existing alert does not have an associated issue' do
it_behaves_like 'processes incident issues'
end
context 'when existing alert has an associated issue' do
- let!(:existing_alert) { create(:alert_management_alert, :with_issue, project: project, fingerprint: fingerprint_sha) }
+ let!(:alert) { create(:alert_management_alert, :with_issue, project: project, fingerprint: fingerprint_sha) }
it_behaves_like 'does not process incident issues'
end
@@ -242,14 +240,14 @@ describe Projects::Alerting::NotifyService do
context 'with invalid token' do
it_behaves_like 'does not process incident issues due to error', http_status: :unauthorized
- it_behaves_like 'NotifyService does not create alert'
+ it_behaves_like 'does not an create alert management alert'
end
context 'with deactivated Alerts Service' do
let!(:alerts_service) { create(:alerts_service, :inactive, project: project) }
it_behaves_like 'does not process incident issues due to error', http_status: :forbidden
- it_behaves_like 'NotifyService does not create alert'
+ it_behaves_like 'does not an create alert management alert'
end
end
end
diff --git a/spec/services/projects/auto_devops/disable_service_spec.rb b/spec/services/projects/auto_devops/disable_service_spec.rb
index fb1ab3f9949..1f161990fb2 100644
--- a/spec/services/projects/auto_devops/disable_service_spec.rb
+++ b/spec/services/projects/auto_devops/disable_service_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe Projects::AutoDevops::DisableService, '#execute' do
+RSpec.describe Projects::AutoDevops::DisableService, '#execute' do
let(:project) { create(:project, :repository, :auto_devops) }
let(:auto_devops) { project.auto_devops }
diff --git a/spec/services/projects/autocomplete_service_spec.rb b/spec/services/projects/autocomplete_service_spec.rb
index b625653bc77..336aa37096a 100644
--- a/spec/services/projects/autocomplete_service_spec.rb
+++ b/spec/services/projects/autocomplete_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Projects::AutocompleteService do
+RSpec.describe Projects::AutocompleteService do
describe '#issues' do
describe 'confidential issues' do
let(:author) { create(:user) }
diff --git a/spec/services/projects/batch_open_issues_count_service_spec.rb b/spec/services/projects/batch_open_issues_count_service_spec.rb
index 8cb0ce03fba..82d50604309 100644
--- a/spec/services/projects/batch_open_issues_count_service_spec.rb
+++ b/spec/services/projects/batch_open_issues_count_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Projects::BatchOpenIssuesCountService do
+RSpec.describe Projects::BatchOpenIssuesCountService do
let!(:project_1) { create(:project) }
let!(:project_2) { create(:project) }
diff --git a/spec/services/projects/cleanup_service_spec.rb b/spec/services/projects/cleanup_service_spec.rb
index 5c246854eb7..528f31456a9 100644
--- a/spec/services/projects/cleanup_service_spec.rb
+++ b/spec/services/projects/cleanup_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Projects::CleanupService do
+RSpec.describe Projects::CleanupService do
let(:project) { create(:project, :repository, bfg_object_map: fixture_file_upload('spec/fixtures/bfg_object_map.txt')) }
let(:object_map) { project.bfg_object_map }
diff --git a/spec/services/projects/container_repository/cleanup_tags_service_spec.rb b/spec/services/projects/container_repository/cleanup_tags_service_spec.rb
index 11ea7d51673..2c708e75a25 100644
--- a/spec/services/projects/container_repository/cleanup_tags_service_spec.rb
+++ b/spec/services/projects/container_repository/cleanup_tags_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Projects::ContainerRepository::CleanupTagsService do
+RSpec.describe Projects::ContainerRepository::CleanupTagsService do
let_it_be(:user) { create(:user) }
let_it_be(:project, reload: true) { create(:project, :private) }
let_it_be(:repository) { create(:container_repository, :root, project: project) }
diff --git a/spec/services/projects/container_repository/delete_tags_service_spec.rb b/spec/services/projects/container_repository/delete_tags_service_spec.rb
index e17e4b6f7c9..3d065deefdf 100644
--- a/spec/services/projects/container_repository/delete_tags_service_spec.rb
+++ b/spec/services/projects/container_repository/delete_tags_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Projects::ContainerRepository::DeleteTagsService do
+RSpec.describe Projects::ContainerRepository::DeleteTagsService do
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project, :private) }
let_it_be(:repository) { create(:container_repository, :root, project: project) }
@@ -20,6 +20,31 @@ describe Projects::ContainerRepository::DeleteTagsService do
tags: %w(latest A Ba Bb C D E))
end
+ RSpec.shared_examples 'logging a success response' do
+ it 'logs an info message' do
+ expect(service).to receive(:log_info).with(
+ service_class: 'Projects::ContainerRepository::DeleteTagsService',
+ message: 'deleted tags',
+ container_repository_id: repository.id,
+ deleted_tags_count: tags.size
+ )
+
+ subject
+ end
+ end
+
+ RSpec.shared_examples 'logging an error response' do |message: 'could not delete tags'|
+ it 'logs an error message' do
+ expect(service).to receive(:log_error).with(
+ service_class: 'Projects::ContainerRepository::DeleteTagsService',
+ message: message,
+ container_repository_id: repository.id
+ )
+
+ subject
+ end
+ end
+
describe '#execute' do
let(:tags) { %w[A] }
@@ -47,11 +72,8 @@ describe Projects::ContainerRepository::DeleteTagsService do
let_it_be(:tags) { %w[A Ba] }
it 'deletes the tags by name' do
- stub_request(:delete, "http://registry.gitlab/v2/#{repository.path}/tags/reference/A")
- .to_return(status: 200, body: "")
-
- stub_request(:delete, "http://registry.gitlab/v2/#{repository.path}/tags/reference/Ba")
- .to_return(status: 200, body: "")
+ stub_delete_reference_request('A')
+ stub_delete_reference_request('Ba')
expect_delete_tag_by_name('A')
expect_delete_tag_by_name('Ba')
@@ -60,26 +82,29 @@ describe Projects::ContainerRepository::DeleteTagsService do
end
it 'succeeds when tag delete returns 404' do
- stub_request(:delete, "http://registry.gitlab/v2/#{repository.path}/tags/reference/A")
- .to_return(status: 200, body: "")
-
- stub_request(:delete, "http://registry.gitlab/v2/#{repository.path}/tags/reference/Ba")
- .to_return(status: 404, body: "")
+ stub_delete_reference_request('A')
+ stub_delete_reference_request('Ba', 404)
is_expected.to include(status: :success)
end
+ it_behaves_like 'logging a success response' do
+ before do
+ stub_delete_reference_request('A')
+ stub_delete_reference_request('Ba')
+ end
+ end
+
context 'with failures' do
context 'when the delete request fails' do
before do
- stub_request(:delete, "http://registry.gitlab/v2/#{repository.path}/tags/reference/A")
- .to_return(status: 500, body: "")
-
- stub_request(:delete, "http://registry.gitlab/v2/#{repository.path}/tags/reference/Ba")
- .to_return(status: 500, body: "")
+ stub_delete_reference_request('A', 500)
+ stub_delete_reference_request('Ba', 500)
end
it { is_expected.to include(status: :error) }
+
+ it_behaves_like 'logging an error response'
end
end
end
@@ -104,19 +129,35 @@ describe Projects::ContainerRepository::DeleteTagsService do
end
end
end
+
context 'and the feature is disabled' do
+ let_it_be(:tags) { %w[A Ba] }
+
before do
stub_feature_flags(container_registry_fast_tag_delete: false)
+ stub_upload("{\n \"config\": {\n }\n}", 'sha256:4435000728ee66e6a80e55637fc22725c256b61de344a2ecdeaac6bdb36e8bc3')
+ stub_put_manifest_request('A')
+ stub_put_manifest_request('Ba')
end
it 'fallbacks to slow delete' do
expect(service).not_to receive(:fast_delete)
- expect(service).to receive(:slow_delete).with(repository, tags)
+ expect(service).to receive(:slow_delete).with(repository, tags).and_call_original
+
+ expect_delete_tag_by_digest('sha256:dummy')
subject
end
+
+ it_behaves_like 'logging a success response' do
+ before do
+ allow(service).to receive(:slow_delete).and_call_original
+ expect_delete_tag_by_digest('sha256:dummy')
+ end
+ end
end
end
+
context 'when the registry does not support fast delete' do
let_it_be(:project) { create(:project, :private) }
let_it_be(:repository) { create(:container_repository, :root, project: project) }
@@ -155,11 +196,8 @@ describe Projects::ContainerRepository::DeleteTagsService do
it 'deletes the tags using a dummy image' do
stub_upload("{\n \"config\": {\n }\n}", 'sha256:4435000728ee66e6a80e55637fc22725c256b61de344a2ecdeaac6bdb36e8bc3')
- stub_request(:put, "http://registry.gitlab/v2/#{repository.path}/manifests/A")
- .to_return(status: 200, body: "", headers: { 'docker-content-digest' => 'sha256:dummy' })
-
- stub_request(:put, "http://registry.gitlab/v2/#{repository.path}/manifests/Ba")
- .to_return(status: 200, body: "", headers: { 'docker-content-digest' => 'sha256:dummy' })
+ stub_put_manifest_request('A')
+ stub_put_manifest_request('Ba')
expect_delete_tag_by_digest('sha256:dummy')
@@ -169,11 +207,8 @@ describe Projects::ContainerRepository::DeleteTagsService do
it 'succeeds when tag delete returns 404' do
stub_upload("{\n \"config\": {\n }\n}", 'sha256:4435000728ee66e6a80e55637fc22725c256b61de344a2ecdeaac6bdb36e8bc3')
- stub_request(:put, "http://registry.gitlab/v2/#{repository.path}/manifests/A")
- .to_return(status: 200, body: "", headers: { 'docker-content-digest' => 'sha256:dummy' })
-
- stub_request(:put, "http://registry.gitlab/v2/#{repository.path}/manifests/Ba")
- .to_return(status: 200, body: "", headers: { 'docker-content-digest' => 'sha256:dummy' })
+ stub_put_manifest_request('A')
+ stub_put_manifest_request('Ba')
stub_request(:delete, "http://registry.gitlab/v2/#{repository.path}/manifests/sha256:dummy")
.to_return(status: 404, body: "", headers: {})
@@ -181,6 +216,15 @@ describe Projects::ContainerRepository::DeleteTagsService do
is_expected.to include(status: :success)
end
+ it_behaves_like 'logging a success response' do
+ before do
+ stub_upload("{\n \"config\": {\n }\n}", 'sha256:4435000728ee66e6a80e55637fc22725c256b61de344a2ecdeaac6bdb36e8bc3')
+ stub_put_manifest_request('A')
+ stub_put_manifest_request('Ba')
+ expect_delete_tag_by_digest('sha256:dummy')
+ end
+ end
+
context 'with failures' do
context 'when the dummy manifest generation fails' do
before do
@@ -188,23 +232,23 @@ describe Projects::ContainerRepository::DeleteTagsService do
end
it { is_expected.to include(status: :error) }
+
+ it_behaves_like 'logging an error response', message: 'could not generate manifest'
end
context 'when updating the tags fails' do
before do
stub_upload("{\n \"config\": {\n }\n}", 'sha256:4435000728ee66e6a80e55637fc22725c256b61de344a2ecdeaac6bdb36e8bc3')
- stub_request(:put, "http://registry.gitlab/v2/#{repository.path}/manifests/A")
- .to_return(status: 500, body: "", headers: { 'docker-content-digest' => 'sha256:dummy' })
-
- stub_request(:put, "http://registry.gitlab/v2/#{repository.path}/manifests/Ba")
- .to_return(status: 500, body: "", headers: { 'docker-content-digest' => 'sha256:dummy' })
+ stub_put_manifest_request('A', 500)
+ stub_put_manifest_request('Ba', 500)
stub_request(:delete, "http://registry.gitlab/v2/#{repository.path}/manifests/sha256:4435000728ee66e6a80e55637fc22725c256b61de344a2ecdeaac6bdb36e8bc3")
.to_return(status: 200, body: "", headers: {})
end
it { is_expected.to include(status: :error) }
+ it_behaves_like 'logging an error response'
end
end
end
@@ -214,6 +258,16 @@ describe Projects::ContainerRepository::DeleteTagsService do
private
+ def stub_delete_reference_request(tag, status = 200)
+ stub_request(:delete, "http://registry.gitlab/v2/#{repository.path}/tags/reference/#{tag}")
+ .to_return(status: status, body: '')
+ end
+
+ def stub_put_manifest_request(tag, status = 200, headers = { 'docker-content-digest' => 'sha256:dummy' })
+ stub_request(:put, "http://registry.gitlab/v2/#{repository.path}/manifests/#{tag}")
+ .to_return(status: status, body: '', headers: headers)
+ end
+
def stub_tag_digest(tag, digest)
stub_request(:head, "http://registry.gitlab/v2/#{repository.path}/manifests/#{tag}")
.to_return(status: 200, body: "", headers: { 'docker-content-digest' => digest })
diff --git a/spec/services/projects/container_repository/destroy_service_spec.rb b/spec/services/projects/container_repository/destroy_service_spec.rb
index 753b7540d7f..20e75d94e05 100644
--- a/spec/services/projects/container_repository/destroy_service_spec.rb
+++ b/spec/services/projects/container_repository/destroy_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Projects::ContainerRepository::DestroyService do
+RSpec.describe Projects::ContainerRepository::DestroyService do
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project, :private) }
diff --git a/spec/services/projects/count_service_spec.rb b/spec/services/projects/count_service_spec.rb
index e345b508f53..11b2b57a277 100644
--- a/spec/services/projects/count_service_spec.rb
+++ b/spec/services/projects/count_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Projects::CountService do
+RSpec.describe Projects::CountService do
let(:project) { build(:project, id: 1) }
let(:service) { described_class.new(project) }
diff --git a/spec/services/projects/create_from_template_service_spec.rb b/spec/services/projects/create_from_template_service_spec.rb
index 0b4772e8f02..7e23daabcd3 100644
--- a/spec/services/projects/create_from_template_service_spec.rb
+++ b/spec/services/projects/create_from_template_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Projects::CreateFromTemplateService do
+RSpec.describe Projects::CreateFromTemplateService do
let(:user) { create(:user) }
let(:template_name) { 'rails' }
let(:project_params) do
diff --git a/spec/services/projects/create_service_spec.rb b/spec/services/projects/create_service_spec.rb
index e70ee05ed31..9eb7cacbbcb 100644
--- a/spec/services/projects/create_service_spec.rb
+++ b/spec/services/projects/create_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Projects::CreateService, '#execute' do
+RSpec.describe Projects::CreateService, '#execute' do
include ExternalAuthorizationServiceHelpers
include GitHelpers
@@ -240,13 +240,21 @@ describe Projects::CreateService, '#execute' do
end
context 'import data' do
- it 'stores import data and URL' do
- import_data = { data: { 'test' => 'some data' } }
- project = create_project(user, { name: 'test', import_url: 'http://import-url', import_data: import_data })
+ let(:import_data) { { data: { 'test' => 'some data' } } }
+ let(:imported_project) { create_project(user, { name: 'test', import_url: 'http://import-url', import_data: import_data }) }
+
+ it 'does not write repository config' do
+ expect_next_instance_of(Project) do |project|
+ expect(project).not_to receive(:write_repository_config)
+ end
- expect(project.import_data).to be_persisted
- expect(project.import_data.data).to eq(import_data[:data])
- expect(project.import_url).to eq('http://import-url')
+ imported_project
+ end
+
+ it 'stores import data and URL' do
+ expect(imported_project.import_data).to be_persisted
+ expect(imported_project.import_data.data).to eq(import_data[:data])
+ expect(imported_project.import_url).to eq('http://import-url')
end
end
@@ -438,14 +446,35 @@ describe Projects::CreateService, '#execute' do
end
context 'when readme initialization is requested' do
- it 'creates README.md' do
+ let(:project) { create_project(user, opts) }
+
+ before do
opts[:initialize_with_readme] = '1'
+ end
- project = create_project(user, opts)
+ shared_examples 'creates README.md' do
+ it { expect(project.repository.commit_count).to be(1) }
+ it { expect(project.repository.readme.name).to eql('README.md') }
+ it { expect(project.repository.readme.data).to include('# GitLab') }
+ end
- expect(project.repository.commit_count).to be(1)
- expect(project.repository.readme.name).to eql('README.md')
- expect(project.repository.readme.data).to include('# GitLab')
+ it_behaves_like 'creates README.md'
+
+ context 'and a default_branch_name is specified' do
+ before do
+ allow(Gitlab::CurrentSettings)
+ .to receive(:default_branch_name)
+ .and_return('example_branch')
+ end
+
+ it_behaves_like 'creates README.md'
+
+ it 'creates README.md within the specified branch rather than master' do
+ branches = project.repository.branches
+
+ expect(branches.size).to eq(1)
+ expect(branches.collect(&:name)).to contain_exactly('example_branch')
+ end
end
end
@@ -647,10 +676,6 @@ describe Projects::CreateService, '#execute' do
end
it 'updates authorization for current_user' do
- expect(Users::RefreshAuthorizedProjectsService).to(
- receive(:new).with(user).and_call_original
- )
-
project = create_project(user, opts)
expect(
@@ -682,10 +707,6 @@ describe Projects::CreateService, '#execute' do
end
it 'updates authorization for current_user' do
- expect(Users::RefreshAuthorizedProjectsService).to(
- receive(:new).with(user).and_call_original
- )
-
project = create_project(user, opts)
expect(
diff --git a/spec/services/projects/deploy_tokens/create_service_spec.rb b/spec/services/projects/deploy_tokens/create_service_spec.rb
index 5c3ada8af4e..831dbc06588 100644
--- a/spec/services/projects/deploy_tokens/create_service_spec.rb
+++ b/spec/services/projects/deploy_tokens/create_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Projects::DeployTokens::CreateService do
+RSpec.describe Projects::DeployTokens::CreateService do
it_behaves_like 'a deploy token creation service' do
let(:entity) { create(:project) }
let(:deploy_token_class) { ProjectDeployToken }
diff --git a/spec/services/projects/deploy_tokens/destroy_service_spec.rb b/spec/services/projects/deploy_tokens/destroy_service_spec.rb
index 24407f46615..edb2345aa6c 100644
--- a/spec/services/projects/deploy_tokens/destroy_service_spec.rb
+++ b/spec/services/projects/deploy_tokens/destroy_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Projects::DeployTokens::DestroyService do
+RSpec.describe Projects::DeployTokens::DestroyService do
it_behaves_like 'a deploy token deletion service' do
let_it_be(:entity) { create(:project) }
let_it_be(:deploy_token_class) { ProjectDeployToken }
diff --git a/spec/services/projects/destroy_rollback_service_spec.rb b/spec/services/projects/destroy_rollback_service_spec.rb
index 8facf17dc45..f63939337b8 100644
--- a/spec/services/projects/destroy_rollback_service_spec.rb
+++ b/spec/services/projects/destroy_rollback_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Projects::DestroyRollbackService do
+RSpec.describe Projects::DestroyRollbackService do
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project, :repository, namespace: user.namespace) }
let(:repository) { project.repository }
diff --git a/spec/services/projects/destroy_service_spec.rb b/spec/services/projects/destroy_service_spec.rb
index 58c40d04fe9..56b19c33ece 100644
--- a/spec/services/projects/destroy_service_spec.rb
+++ b/spec/services/projects/destroy_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Projects::DestroyService do
+RSpec.describe Projects::DestroyService do
include ProjectForksHelper
let_it_be(:user) { create(:user) }
diff --git a/spec/services/projects/detect_repository_languages_service_spec.rb b/spec/services/projects/detect_repository_languages_service_spec.rb
index 76600b0e77c..cf4c7a5024d 100644
--- a/spec/services/projects/detect_repository_languages_service_spec.rb
+++ b/spec/services/projects/detect_repository_languages_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Projects::DetectRepositoryLanguagesService, :clean_gitlab_redis_shared_state do
+RSpec.describe Projects::DetectRepositoryLanguagesService, :clean_gitlab_redis_shared_state do
let_it_be(:project, reload: true) { create(:project, :repository) }
subject { described_class.new(project) }
diff --git a/spec/services/projects/download_service_spec.rb b/spec/services/projects/download_service_spec.rb
index 06efc2ff825..0f743eaa7f5 100644
--- a/spec/services/projects/download_service_spec.rb
+++ b/spec/services/projects/download_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Projects::DownloadService do
+RSpec.describe Projects::DownloadService do
describe 'File service' do
before do
@user = create(:user)
diff --git a/spec/services/projects/enable_deploy_key_service_spec.rb b/spec/services/projects/enable_deploy_key_service_spec.rb
index 64de373d7f6..f297ec374cf 100644
--- a/spec/services/projects/enable_deploy_key_service_spec.rb
+++ b/spec/services/projects/enable_deploy_key_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Projects::EnableDeployKeyService do
+RSpec.describe Projects::EnableDeployKeyService do
let(:deploy_key) { create(:deploy_key, public: true) }
let(:project) { create(:project) }
let(:user) { project.creator}
diff --git a/spec/services/projects/fetch_statistics_increment_service_spec.rb b/spec/services/projects/fetch_statistics_increment_service_spec.rb
index fcfb138aad6..16121a42c39 100644
--- a/spec/services/projects/fetch_statistics_increment_service_spec.rb
+++ b/spec/services/projects/fetch_statistics_increment_service_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
module Projects
- describe FetchStatisticsIncrementService do
+ RSpec.describe FetchStatisticsIncrementService do
let(:project) { create(:project) }
describe '#execute' do
diff --git a/spec/services/projects/fork_service_spec.rb b/spec/services/projects/fork_service_spec.rb
index 112a41c773b..c49aa42b147 100644
--- a/spec/services/projects/fork_service_spec.rb
+++ b/spec/services/projects/fork_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Projects::ForkService do
+RSpec.describe Projects::ForkService do
include ProjectForksHelper
shared_examples 'forks count cache refresh' do
@@ -10,6 +10,7 @@ describe Projects::ForkService do
expect(from_project.forks_count).to be_zero
fork_project(from_project, to_user)
+ BatchLoader::Executor.clear_current
expect(from_project.forks_count).to eq(1)
end
@@ -327,7 +328,7 @@ describe Projects::ForkService do
destination_storage_name: 'test_second_storage'
)
Projects::UpdateRepositoryStorageService.new(storage_move).execute
- fork_after_move = fork_project(project)
+ fork_after_move = fork_project(project.reload)
pool_repository_before_move = PoolRepository.joins(:shard)
.find_by(source_project: project, shards: { name: 'default' })
pool_repository_after_move = PoolRepository.joins(:shard)
@@ -405,6 +406,7 @@ describe Projects::ForkService do
expect(fork_from_project.forks_count).to be_zero
subject.execute(fork_to_project)
+ BatchLoader::Executor.clear_current
expect(fork_from_project.forks_count).to eq(1)
end
diff --git a/spec/services/projects/forks_count_service_spec.rb b/spec/services/projects/forks_count_service_spec.rb
index 21a75eafc7a..31662f78973 100644
--- a/spec/services/projects/forks_count_service_spec.rb
+++ b/spec/services/projects/forks_count_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Projects::ForksCountService, :use_clean_rails_memory_store_caching do
+RSpec.describe Projects::ForksCountService, :use_clean_rails_memory_store_caching do
let(:project) { build(:project) }
subject { described_class.new(project) }
diff --git a/spec/services/projects/git_deduplication_service_spec.rb b/spec/services/projects/git_deduplication_service_spec.rb
index 9e6279da7de..b98db5bc41b 100644
--- a/spec/services/projects/git_deduplication_service_spec.rb
+++ b/spec/services/projects/git_deduplication_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Projects::GitDeduplicationService do
+RSpec.describe Projects::GitDeduplicationService do
include ExclusiveLeaseHelpers
let(:pool) { create(:pool_repository, :ready) }
@@ -139,7 +139,7 @@ describe Projects::GitDeduplicationService do
end
it 'fails when a lease is already out' do
- expect(service).to receive(:log_error).with('Cannot obtain an exclusive lease. There must be another instance already in execution.')
+ expect(service).to receive(:log_error).with("Cannot obtain an exclusive lease for #{service.class.name}. There must be another instance already in execution.")
service.execute
end
diff --git a/spec/services/projects/gitlab_projects_import_service_spec.rb b/spec/services/projects/gitlab_projects_import_service_spec.rb
index 1662d4577aa..09d093a9916 100644
--- a/spec/services/projects/gitlab_projects_import_service_spec.rb
+++ b/spec/services/projects/gitlab_projects_import_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Projects::GitlabProjectsImportService do
+RSpec.describe Projects::GitlabProjectsImportService do
let_it_be(:namespace) { create(:namespace) }
let(:path) { 'test-path' }
let(:file) { fixture_file_upload('spec/fixtures/project_export.tar.gz') }
diff --git a/spec/services/projects/group_links/create_service_spec.rb b/spec/services/projects/group_links/create_service_spec.rb
index 22f7c8bdcb4..6468e3007c2 100644
--- a/spec/services/projects/group_links/create_service_spec.rb
+++ b/spec/services/projects/group_links/create_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Projects::GroupLinks::CreateService, '#execute' do
+RSpec.describe Projects::GroupLinks::CreateService, '#execute' do
let_it_be(:user) { create :user }
let_it_be(:group) { create :group }
let_it_be(:project) { create :project }
@@ -23,7 +23,7 @@ describe Projects::GroupLinks::CreateService, '#execute' do
expect { subject.execute(group) }.to change { project.project_group_links.count }.from(0).to(1)
end
- it 'updates authorization' do
+ it 'updates authorization', :sidekiq_inline do
expect { subject.execute(group) }.to(
change { Ability.allowed?(user, :read_project, project) }
.from(false).to(true))
@@ -36,4 +36,50 @@ describe Projects::GroupLinks::CreateService, '#execute' do
it 'returns error if user is not allowed to share with a group' do
expect { subject.execute(create(:group)) }.not_to change { project.project_group_links.count }
end
+
+ context 'with specialized_project_authorization_workers' do
+ let_it_be(:other_user) { create(:user) }
+
+ before do
+ group.add_developer(other_user)
+ end
+
+ it 'schedules authorization update for users with access to group' do
+ expect(AuthorizedProjectsWorker).not_to(
+ receive(:bulk_perform_async)
+ )
+ expect(AuthorizedProjectUpdate::ProjectGroupLinkCreateWorker).to(
+ receive(:perform_async).and_call_original
+ )
+ expect(AuthorizedProjectUpdate::UserRefreshWithLowUrgencyWorker).to(
+ receive(:bulk_perform_in)
+ .with(1.hour,
+ array_including([user.id], [other_user.id]),
+ batch_delay: 30.seconds, batch_size: 100)
+ .and_call_original
+ )
+
+ subject.execute(group)
+ end
+
+ context 'when feature is disabled' do
+ before do
+ stub_feature_flags(specialized_project_authorization_project_share_worker: false)
+ end
+
+ it 'uses AuthorizedProjectsWorker' do
+ expect(AuthorizedProjectsWorker).to(
+ receive(:bulk_perform_async).with(array_including([user.id], [other_user.id])).and_call_original
+ )
+ expect(AuthorizedProjectUpdate::ProjectCreateWorker).not_to(
+ receive(:perform_async)
+ )
+ expect(AuthorizedProjectUpdate::UserRefreshWithLowUrgencyWorker).not_to(
+ receive(:bulk_perform_in)
+ )
+
+ subject.execute(group)
+ end
+ end
+ end
end
diff --git a/spec/services/projects/group_links/destroy_service_spec.rb b/spec/services/projects/group_links/destroy_service_spec.rb
index 0a8c9580e70..459b79b2d7d 100644
--- a/spec/services/projects/group_links/destroy_service_spec.rb
+++ b/spec/services/projects/group_links/destroy_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Projects::GroupLinks::DestroyService, '#execute' do
+RSpec.describe Projects::GroupLinks::DestroyService, '#execute' do
let_it_be(:user) { create :user }
let_it_be(:project) { create(:project, :private) }
let_it_be(:group) { create(:group) }
diff --git a/spec/services/projects/group_links/update_service_spec.rb b/spec/services/projects/group_links/update_service_spec.rb
index 5be2ae1e0f7..053c5eb611e 100644
--- a/spec/services/projects/group_links/update_service_spec.rb
+++ b/spec/services/projects/group_links/update_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Projects::GroupLinks::UpdateService, '#execute' do
+RSpec.describe Projects::GroupLinks::UpdateService, '#execute' do
let_it_be(:user) { create :user }
let_it_be(:group) { create :group }
let_it_be(:project) { create :project }
diff --git a/spec/services/projects/hashed_storage/base_attachment_service_spec.rb b/spec/services/projects/hashed_storage/base_attachment_service_spec.rb
index 070dd5fc1b8..5e1b6f2e404 100644
--- a/spec/services/projects/hashed_storage/base_attachment_service_spec.rb
+++ b/spec/services/projects/hashed_storage/base_attachment_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Projects::HashedStorage::BaseAttachmentService do
+RSpec.describe Projects::HashedStorage::BaseAttachmentService do
let(:project) { create(:project, :repository, storage_version: 0, skip_disk_validation: true) }
subject(:service) { described_class.new(project: project, old_disk_path: project.full_path, logger: nil) }
diff --git a/spec/services/projects/hashed_storage/migrate_attachments_service_spec.rb b/spec/services/projects/hashed_storage/migrate_attachments_service_spec.rb
index 7c7e188a12d..c8f24c6ce00 100644
--- a/spec/services/projects/hashed_storage/migrate_attachments_service_spec.rb
+++ b/spec/services/projects/hashed_storage/migrate_attachments_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Projects::HashedStorage::MigrateAttachmentsService do
+RSpec.describe Projects::HashedStorage::MigrateAttachmentsService do
subject(:service) { described_class.new(project: project, old_disk_path: project.full_path, logger: nil) }
let(:project) { create(:project, :repository, storage_version: 1, skip_disk_validation: true) }
diff --git a/spec/services/projects/hashed_storage/migrate_repository_service_spec.rb b/spec/services/projects/hashed_storage/migrate_repository_service_spec.rb
index f1eaf8324e0..e03e75653ff 100644
--- a/spec/services/projects/hashed_storage/migrate_repository_service_spec.rb
+++ b/spec/services/projects/hashed_storage/migrate_repository_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Projects::HashedStorage::MigrateRepositoryService do
+RSpec.describe Projects::HashedStorage::MigrateRepositoryService do
include GitHelpers
let(:gitlab_shell) { Gitlab::Shell.new }
diff --git a/spec/services/projects/hashed_storage/migration_service_spec.rb b/spec/services/projects/hashed_storage/migration_service_spec.rb
index 0a7975305dc..ef96c17dd85 100644
--- a/spec/services/projects/hashed_storage/migration_service_spec.rb
+++ b/spec/services/projects/hashed_storage/migration_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Projects::HashedStorage::MigrationService do
+RSpec.describe Projects::HashedStorage::MigrationService do
let(:project) { create(:project, :empty_repo, :wiki_repo, :legacy_storage) }
let(:logger) { double }
let!(:project_attachment) { build(:file_uploader, project: project) }
diff --git a/spec/services/projects/hashed_storage/rollback_attachments_service_spec.rb b/spec/services/projects/hashed_storage/rollback_attachments_service_spec.rb
index 54695e6e48f..d4cb46c82ad 100644
--- a/spec/services/projects/hashed_storage/rollback_attachments_service_spec.rb
+++ b/spec/services/projects/hashed_storage/rollback_attachments_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Projects::HashedStorage::RollbackAttachmentsService do
+RSpec.describe Projects::HashedStorage::RollbackAttachmentsService do
subject(:service) { described_class.new(project: project, old_disk_path: project.disk_path, logger: nil) }
let(:project) { create(:project, :repository, skip_disk_validation: true) }
diff --git a/spec/services/projects/hashed_storage/rollback_repository_service_spec.rb b/spec/services/projects/hashed_storage/rollback_repository_service_spec.rb
index 1c0f446d9cf..f2b1ce30a54 100644
--- a/spec/services/projects/hashed_storage/rollback_repository_service_spec.rb
+++ b/spec/services/projects/hashed_storage/rollback_repository_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Projects::HashedStorage::RollbackRepositoryService, :clean_gitlab_redis_shared_state do
+RSpec.describe Projects::HashedStorage::RollbackRepositoryService, :clean_gitlab_redis_shared_state do
include GitHelpers
let(:gitlab_shell) { Gitlab::Shell.new }
diff --git a/spec/services/projects/hashed_storage/rollback_service_spec.rb b/spec/services/projects/hashed_storage/rollback_service_spec.rb
index e6b7daba99e..0bd63f2da2a 100644
--- a/spec/services/projects/hashed_storage/rollback_service_spec.rb
+++ b/spec/services/projects/hashed_storage/rollback_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Projects::HashedStorage::RollbackService do
+RSpec.describe Projects::HashedStorage::RollbackService do
let(:project) { create(:project, :empty_repo, :wiki_repo) }
let(:logger) { double }
let!(:project_attachment) { build(:file_uploader, project: project) }
diff --git a/spec/services/projects/housekeeping_service_spec.rb b/spec/services/projects/housekeeping_service_spec.rb
index 98a27a71c26..18871f010f8 100644
--- a/spec/services/projects/housekeeping_service_spec.rb
+++ b/spec/services/projects/housekeeping_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Projects::HousekeepingService do
+RSpec.describe Projects::HousekeepingService do
subject { described_class.new(project) }
let_it_be(:project) { create(:project, :repository) }
diff --git a/spec/services/projects/import_error_filter_spec.rb b/spec/services/projects/import_error_filter_spec.rb
index 312b658de89..fd31cd52cc4 100644
--- a/spec/services/projects/import_error_filter_spec.rb
+++ b/spec/services/projects/import_error_filter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Projects::ImportErrorFilter do
+RSpec.describe Projects::ImportErrorFilter do
it 'filters any full paths' do
message = 'Error importing into /my/folder Permission denied @ unlink_internal - /var/opt/gitlab/gitlab-rails/shared/a/b/c/uploads/file'
diff --git a/spec/services/projects/import_export/export_service_spec.rb b/spec/services/projects/import_export/export_service_spec.rb
index 19891341311..111c1264777 100644
--- a/spec/services/projects/import_export/export_service_spec.rb
+++ b/spec/services/projects/import_export/export_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Projects::ImportExport::ExportService do
+RSpec.describe Projects::ImportExport::ExportService do
describe '#execute' do
let!(:user) { create(:user) }
let(:project) { create(:project) }
diff --git a/spec/services/projects/import_service_spec.rb b/spec/services/projects/import_service_spec.rb
index ca6750b373d..92e18b6cb46 100644
--- a/spec/services/projects/import_service_spec.rb
+++ b/spec/services/projects/import_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Projects::ImportService do
+RSpec.describe Projects::ImportService do
let!(:project) { create(:project) }
let(:user) { project.creator }
diff --git a/spec/services/projects/lfs_pointers/lfs_download_link_list_service_spec.rb b/spec/services/projects/lfs_pointers/lfs_download_link_list_service_spec.rb
index 99d35fdc7f7..66a450bd734 100644
--- a/spec/services/projects/lfs_pointers/lfs_download_link_list_service_spec.rb
+++ b/spec/services/projects/lfs_pointers/lfs_download_link_list_service_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe Projects::LfsPointers::LfsDownloadLinkListService do
+RSpec.describe Projects::LfsPointers::LfsDownloadLinkListService do
let(:import_url) { 'http://www.gitlab.com/demo/repo.git' }
let(:lfs_endpoint) { "#{import_url}/info/lfs/objects/batch" }
let!(:project) { create(:project, import_url: import_url) }
diff --git a/spec/services/projects/lfs_pointers/lfs_download_service_spec.rb b/spec/services/projects/lfs_pointers/lfs_download_service_spec.rb
index 496d1fe67f2..a606371099d 100644
--- a/spec/services/projects/lfs_pointers/lfs_download_service_spec.rb
+++ b/spec/services/projects/lfs_pointers/lfs_download_service_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe Projects::LfsPointers::LfsDownloadService do
+RSpec.describe Projects::LfsPointers::LfsDownloadService do
include StubRequests
let(:project) { create(:project) }
diff --git a/spec/services/projects/lfs_pointers/lfs_import_service_spec.rb b/spec/services/projects/lfs_pointers/lfs_import_service_spec.rb
index 016028a96bf..b36b0b8d6b2 100644
--- a/spec/services/projects/lfs_pointers/lfs_import_service_spec.rb
+++ b/spec/services/projects/lfs_pointers/lfs_import_service_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe Projects::LfsPointers::LfsImportService do
+RSpec.describe Projects::LfsPointers::LfsImportService do
let(:project) { create(:project) }
let(:user) { project.creator }
let(:import_url) { 'http://www.gitlab.com/demo/repo.git' }
diff --git a/spec/services/projects/lfs_pointers/lfs_link_service_spec.rb b/spec/services/projects/lfs_pointers/lfs_link_service_spec.rb
index b64662f3782..d59f5dbae19 100644
--- a/spec/services/projects/lfs_pointers/lfs_link_service_spec.rb
+++ b/spec/services/projects/lfs_pointers/lfs_link_service_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe Projects::LfsPointers::LfsLinkService do
+RSpec.describe Projects::LfsPointers::LfsLinkService do
let!(:project) { create(:project, lfs_enabled: true) }
let!(:lfs_objects_project) { create_list(:lfs_objects_project, 2, project: project) }
let(:new_oids) { { 'oid1' => 123, 'oid2' => 125 } }
diff --git a/spec/services/projects/lfs_pointers/lfs_object_download_list_service_spec.rb b/spec/services/projects/lfs_pointers/lfs_object_download_list_service_spec.rb
index e94d8a85987..0799a33f856 100644
--- a/spec/services/projects/lfs_pointers/lfs_object_download_list_service_spec.rb
+++ b/spec/services/projects/lfs_pointers/lfs_object_download_list_service_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe Projects::LfsPointers::LfsObjectDownloadListService do
+RSpec.describe Projects::LfsPointers::LfsObjectDownloadListService do
let(:import_url) { 'http://www.gitlab.com/demo/repo.git' }
let(:default_endpoint) { "#{import_url}/info/lfs/objects/batch"}
let(:group) { create(:group, lfs_enabled: true)}
diff --git a/spec/services/projects/move_access_service_spec.rb b/spec/services/projects/move_access_service_spec.rb
index efa34c84522..de3871414af 100644
--- a/spec/services/projects/move_access_service_spec.rb
+++ b/spec/services/projects/move_access_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Projects::MoveAccessService do
+RSpec.describe Projects::MoveAccessService do
let(:user) { create(:user) }
let(:group) { create(:group) }
let(:project_with_access) { create(:project, namespace: user.namespace) }
diff --git a/spec/services/projects/move_deploy_keys_projects_service_spec.rb b/spec/services/projects/move_deploy_keys_projects_service_spec.rb
index a5d28fb0fbf..e69b4dd4fc7 100644
--- a/spec/services/projects/move_deploy_keys_projects_service_spec.rb
+++ b/spec/services/projects/move_deploy_keys_projects_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Projects::MoveDeployKeysProjectsService do
+RSpec.describe Projects::MoveDeployKeysProjectsService do
let!(:user) { create(:user) }
let!(:project_with_deploy_keys) { create(:project, namespace: user.namespace) }
let!(:target_project) { create(:project, namespace: user.namespace) }
diff --git a/spec/services/projects/move_forks_service_spec.rb b/spec/services/projects/move_forks_service_spec.rb
index 8f9f048d5ff..7d3637b7758 100644
--- a/spec/services/projects/move_forks_service_spec.rb
+++ b/spec/services/projects/move_forks_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Projects::MoveForksService do
+RSpec.describe Projects::MoveForksService do
include ProjectForksHelper
let!(:user) { create(:user) }
diff --git a/spec/services/projects/move_lfs_objects_projects_service_spec.rb b/spec/services/projects/move_lfs_objects_projects_service_spec.rb
index 114509229c5..b73286fba9a 100644
--- a/spec/services/projects/move_lfs_objects_projects_service_spec.rb
+++ b/spec/services/projects/move_lfs_objects_projects_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Projects::MoveLfsObjectsProjectsService do
+RSpec.describe Projects::MoveLfsObjectsProjectsService do
let!(:user) { create(:user) }
let!(:project_with_lfs_objects) { create(:project, namespace: user.namespace) }
let!(:target_project) { create(:project, namespace: user.namespace) }
diff --git a/spec/services/projects/move_notification_settings_service_spec.rb b/spec/services/projects/move_notification_settings_service_spec.rb
index 54d85404bf6..7c9f1dd30d2 100644
--- a/spec/services/projects/move_notification_settings_service_spec.rb
+++ b/spec/services/projects/move_notification_settings_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Projects::MoveNotificationSettingsService do
+RSpec.describe Projects::MoveNotificationSettingsService do
let(:user) { create(:user) }
let(:project_with_notifications) { create(:project, namespace: user.namespace) }
let(:target_project) { create(:project, namespace: user.namespace) }
diff --git a/spec/services/projects/move_project_authorizations_service_spec.rb b/spec/services/projects/move_project_authorizations_service_spec.rb
index fe3ba31c881..a37b4d807a0 100644
--- a/spec/services/projects/move_project_authorizations_service_spec.rb
+++ b/spec/services/projects/move_project_authorizations_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Projects::MoveProjectAuthorizationsService do
+RSpec.describe Projects::MoveProjectAuthorizationsService do
let!(:user) { create(:user) }
let(:project_with_users) { create(:project, namespace: user.namespace) }
let(:target_project) { create(:project, namespace: user.namespace) }
diff --git a/spec/services/projects/move_project_group_links_service_spec.rb b/spec/services/projects/move_project_group_links_service_spec.rb
index 6140d679929..196a8f2b339 100644
--- a/spec/services/projects/move_project_group_links_service_spec.rb
+++ b/spec/services/projects/move_project_group_links_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Projects::MoveProjectGroupLinksService do
+RSpec.describe Projects::MoveProjectGroupLinksService do
let!(:user) { create(:user) }
let(:project_with_groups) { create(:project, namespace: user.namespace) }
let(:target_project) { create(:project, namespace: user.namespace) }
diff --git a/spec/services/projects/move_project_members_service_spec.rb b/spec/services/projects/move_project_members_service_spec.rb
index bdd5cd6a87a..f14f00e3866 100644
--- a/spec/services/projects/move_project_members_service_spec.rb
+++ b/spec/services/projects/move_project_members_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Projects::MoveProjectMembersService do
+RSpec.describe Projects::MoveProjectMembersService do
let!(:user) { create(:user) }
let(:project_with_users) { create(:project, namespace: user.namespace) }
let(:target_project) { create(:project, namespace: user.namespace) }
diff --git a/spec/services/projects/move_users_star_projects_service_spec.rb b/spec/services/projects/move_users_star_projects_service_spec.rb
index cde188f9f5f..0f766ebd0ec 100644
--- a/spec/services/projects/move_users_star_projects_service_spec.rb
+++ b/spec/services/projects/move_users_star_projects_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Projects::MoveUsersStarProjectsService do
+RSpec.describe Projects::MoveUsersStarProjectsService do
let!(:user) { create(:user) }
let!(:project_with_stars) { create(:project, namespace: user.namespace) }
let!(:target_project) { create(:project, namespace: user.namespace) }
diff --git a/spec/services/projects/open_issues_count_service_spec.rb b/spec/services/projects/open_issues_count_service_spec.rb
index c1d49befeb9..c739fea5ecf 100644
--- a/spec/services/projects/open_issues_count_service_spec.rb
+++ b/spec/services/projects/open_issues_count_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Projects::OpenIssuesCountService, :use_clean_rails_memory_store_caching do
+RSpec.describe Projects::OpenIssuesCountService, :use_clean_rails_memory_store_caching do
let(:project) { create(:project) }
subject { described_class.new(project) }
diff --git a/spec/services/projects/open_merge_requests_count_service_spec.rb b/spec/services/projects/open_merge_requests_count_service_spec.rb
index 7d848f9f2c3..6caef181e77 100644
--- a/spec/services/projects/open_merge_requests_count_service_spec.rb
+++ b/spec/services/projects/open_merge_requests_count_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Projects::OpenMergeRequestsCountService, :use_clean_rails_memory_store_caching do
+RSpec.describe Projects::OpenMergeRequestsCountService, :use_clean_rails_memory_store_caching do
let_it_be(:project) { create(:project) }
subject { described_class.new(project) }
diff --git a/spec/services/projects/operations/update_service_spec.rb b/spec/services/projects/operations/update_service_spec.rb
index f4d62b48fe5..3cfc9844d65 100644
--- a/spec/services/projects/operations/update_service_spec.rb
+++ b/spec/services/projects/operations/update_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Projects::Operations::UpdateService do
+RSpec.describe Projects::Operations::UpdateService do
let_it_be(:user) { create(:user) }
let_it_be(:project, refind: true) { create(:project) }
diff --git a/spec/services/projects/overwrite_project_service_spec.rb b/spec/services/projects/overwrite_project_service_spec.rb
index def39ad3789..e4495da9807 100644
--- a/spec/services/projects/overwrite_project_service_spec.rb
+++ b/spec/services/projects/overwrite_project_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Projects::OverwriteProjectService do
+RSpec.describe Projects::OverwriteProjectService do
include ProjectForksHelper
let(:user) { create(:user) }
diff --git a/spec/services/projects/participants_service_spec.rb b/spec/services/projects/participants_service_spec.rb
index f4a04159db4..33a3e37a2d2 100644
--- a/spec/services/projects/participants_service_spec.rb
+++ b/spec/services/projects/participants_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Projects::ParticipantsService do
+RSpec.describe Projects::ParticipantsService do
describe '#groups' do
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project, :public) }
diff --git a/spec/services/projects/prometheus/alerts/create_events_service_spec.rb b/spec/services/projects/prometheus/alerts/create_events_service_spec.rb
deleted file mode 100644
index 61236b5bbdb..00000000000
--- a/spec/services/projects/prometheus/alerts/create_events_service_spec.rb
+++ /dev/null
@@ -1,312 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-describe Projects::Prometheus::Alerts::CreateEventsService do
- let(:user) { create(:user) }
- let_it_be(:project) { create(:project) }
- let(:metric) { create(:prometheus_metric, project: project) }
- let(:service) { described_class.new(project, user, alerts_payload) }
-
- shared_examples 'events persisted' do |expected_count|
- subject { service.execute }
-
- it 'returns proper amount of created events' do
- expect(subject.size).to eq(expected_count)
- end
-
- it 'increments event count' do
- expect { subject }.to change { PrometheusAlertEvent.count }.to(expected_count)
- end
- end
-
- shared_examples 'no events persisted' do
- subject { service.execute }
-
- it 'returns no created events' do
- expect(subject).to be_empty
- end
-
- it 'does not change event count' do
- expect { subject }.not_to change { PrometheusAlertEvent.count }
- end
- end
-
- shared_examples 'self managed events persisted' do
- subject { service.execute }
-
- it 'returns created events' do
- expect(subject).not_to be_empty
- end
-
- it 'does change self managed event count' do
- expect { subject }.to change { SelfManagedPrometheusAlertEvent.count }
- end
- end
-
- context 'with valid alerts_payload' do
- let!(:alert) { create(:prometheus_alert, prometheus_metric: metric, project: project) }
-
- let(:events) { service.execute }
-
- context 'with a firing payload' do
- let(:started_at) { truncate_to_second(Time.current) }
- let(:firing_event) { alert_payload(status: 'firing', started_at: started_at) }
- let(:alerts_payload) { { 'alerts' => [firing_event] } }
-
- it_behaves_like 'events persisted', 1
-
- it 'returns created event' do
- event = events.first
-
- expect(event).to be_firing
- expect(event.started_at).to eq(started_at)
- expect(event.ended_at).to be_nil
- end
-
- context 'with 2 different firing events' do
- let(:another_firing_event) { alert_payload(status: 'firing', started_at: started_at + 1) }
- let(:alerts_payload) { { 'alerts' => [firing_event, another_firing_event] } }
-
- it_behaves_like 'events persisted', 2
- end
-
- context 'with already persisted firing event' do
- before do
- service.execute
- end
-
- it_behaves_like 'no events persisted'
- end
-
- context 'with duplicate payload' do
- let(:alerts_payload) { { 'alerts' => [firing_event, firing_event] } }
-
- it_behaves_like 'events persisted', 1
- end
- end
-
- context 'with a resolved payload' do
- let(:started_at) { truncate_to_second(Time.current) }
- let(:ended_at) { started_at + 1 }
- let(:resolved_event) { alert_payload(status: 'resolved', started_at: started_at, ended_at: ended_at) }
- let(:alerts_payload) { { 'alerts' => [resolved_event] } }
- let(:payload_key) { Gitlab::Alerting::Alert.new(project: project, payload: resolved_event).gitlab_fingerprint }
-
- context 'with a matching firing event' do
- before do
- create(:prometheus_alert_event,
- prometheus_alert: alert,
- payload_key: payload_key,
- started_at: started_at)
- end
-
- it 'does not create an additional event' do
- expect { service.execute }.not_to change { PrometheusAlertEvent.count }
- end
-
- it 'marks firing event as `resolved`' do
- expect(events.size).to eq(1)
-
- event = events.first
- expect(event).to be_resolved
- expect(event.started_at).to eq(started_at)
- expect(event.ended_at).to eq(ended_at)
- end
-
- context 'with duplicate payload' do
- let(:alerts_payload) { { 'alerts' => [resolved_event, resolved_event] } }
-
- it 'does not create an additional event' do
- expect { service.execute }.not_to change { PrometheusAlertEvent.count }
- end
-
- it 'marks firing event as `resolved` only once' do
- expect(events.size).to eq(1)
- end
- end
- end
-
- context 'without a matching firing event' do
- context 'due to payload_key' do
- let(:payload_key) { 'some other payload_key' }
-
- before do
- create(:prometheus_alert_event,
- prometheus_alert: alert,
- payload_key: payload_key,
- started_at: started_at)
- end
-
- it_behaves_like 'no events persisted'
- end
-
- context 'due to status' do
- before do
- create(:prometheus_alert_event, :resolved,
- prometheus_alert: alert,
- started_at: started_at)
- end
-
- it_behaves_like 'no events persisted'
- end
- end
-
- context 'with already resolved event' do
- before do
- service.execute
- end
-
- it_behaves_like 'no events persisted'
- end
- end
-
- context 'with a metric from another project' do
- let(:another_project) { create(:project) }
- let(:metric) { create(:prometheus_metric, project: another_project) }
- let(:alerts_payload) { { 'alerts' => [alert_payload] } }
-
- let!(:alert) do
- create(:prometheus_alert,
- prometheus_metric: metric,
- project: another_project)
- end
-
- it_behaves_like 'no events persisted'
- end
- end
-
- context 'with invalid payload' do
- let(:alert) { create(:prometheus_alert, prometheus_metric: metric, project: project) }
-
- describe '`alerts` key' do
- context 'is missing' do
- let(:alerts_payload) { {} }
-
- it_behaves_like 'no events persisted'
- end
-
- context 'is nil' do
- let(:alerts_payload) { { 'alerts' => nil } }
-
- it_behaves_like 'no events persisted'
- end
-
- context 'is empty' do
- let(:alerts_payload) { { 'alerts' => [] } }
-
- it_behaves_like 'no events persisted'
- end
-
- context 'is not a Hash' do
- let(:alerts_payload) { { 'alerts' => [:not_a_hash] } }
-
- it_behaves_like 'no events persisted'
- end
-
- describe '`status`' do
- context 'is missing' do
- let(:alerts_payload) { { 'alerts' => [alert_payload(status: nil)] } }
-
- it_behaves_like 'no events persisted'
- end
-
- context 'is invalid' do
- let(:alerts_payload) { { 'alerts' => [alert_payload(status: 'invalid')] } }
-
- it_behaves_like 'no events persisted'
- end
- end
-
- describe '`started_at`' do
- context 'is missing' do
- let(:alerts_payload) { { 'alerts' => [alert_payload(started_at: nil)] } }
-
- it_behaves_like 'no events persisted'
- end
-
- context 'is invalid' do
- let(:alerts_payload) { { 'alerts' => [alert_payload(started_at: 'invalid date')] } }
-
- it_behaves_like 'no events persisted'
- end
- end
-
- describe '`ended_at`' do
- context 'is missing and status is resolved' do
- let(:alerts_payload) { { 'alerts' => [alert_payload(ended_at: nil, status: 'resolved')] } }
-
- it_behaves_like 'no events persisted'
- end
-
- context 'is invalid and status is resolved' do
- let(:alerts_payload) { { 'alerts' => [alert_payload(ended_at: 'invalid date', status: 'resolved')] } }
-
- it_behaves_like 'no events persisted'
- end
- end
-
- describe '`labels`' do
- describe '`gitlab_alert_id`' do
- context 'is missing' do
- let(:alerts_payload) { { 'alerts' => [alert_payload(gitlab_alert_id: nil)] } }
-
- it_behaves_like 'no events persisted'
- end
-
- context 'is missing but title is given' do
- let(:alerts_payload) { { 'alerts' => [alert_payload(gitlab_alert_id: nil, title: 'alert')] } }
-
- it_behaves_like 'self managed events persisted'
- end
-
- context 'is missing and environment name is given' do
- let(:environment) { create(:environment, project: project) }
- let(:alerts_payload) { { 'alerts' => [alert_payload(gitlab_alert_id: nil, title: 'alert', environment: environment.name)] } }
-
- it_behaves_like 'self managed events persisted'
-
- it 'associates the environment to the alert event' do
- service.execute
-
- expect(SelfManagedPrometheusAlertEvent.last.environment).to eq environment
- end
- end
-
- context 'is invalid' do
- let(:alerts_payload) { { 'alerts' => [alert_payload(gitlab_alert_id: '-1')] } }
-
- it_behaves_like 'no events persisted'
- end
- end
- end
- end
- end
-
- private
-
- def alert_payload(status: 'firing', started_at: Time.current, ended_at: Time.current, gitlab_alert_id: alert.prometheus_metric_id, title: nil, environment: nil)
- payload = {}
-
- payload['status'] = status if status
- payload['startsAt'] = utc_rfc3339(started_at) if started_at
- payload['endsAt'] = utc_rfc3339(ended_at) if ended_at
- payload['labels'] = {}
- payload['labels']['gitlab_alert_id'] = gitlab_alert_id.to_s if gitlab_alert_id
- payload['labels']['alertname'] = title if title
- payload['labels']['gitlab_environment_name'] = environment if environment
-
- payload
- end
-
- # Example: 2018-09-27T18:25:31.079079416Z
- def utc_rfc3339(date)
- date.utc.rfc3339
- rescue
- date
- end
-
- def truncate_to_second(date)
- date.change(usec: 0)
- end
-end
diff --git a/spec/services/projects/prometheus/alerts/create_service_spec.rb b/spec/services/projects/prometheus/alerts/create_service_spec.rb
index 50c776df734..c0bc9336558 100644
--- a/spec/services/projects/prometheus/alerts/create_service_spec.rb
+++ b/spec/services/projects/prometheus/alerts/create_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Projects::Prometheus::Alerts::CreateService do
+RSpec.describe Projects::Prometheus::Alerts::CreateService do
let_it_be(:project) { create(:project) }
let_it_be(:user) { create(:user) }
diff --git a/spec/services/projects/prometheus/alerts/destroy_service_spec.rb b/spec/services/projects/prometheus/alerts/destroy_service_spec.rb
index 7205ace8308..573711051b7 100644
--- a/spec/services/projects/prometheus/alerts/destroy_service_spec.rb
+++ b/spec/services/projects/prometheus/alerts/destroy_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Projects::Prometheus::Alerts::DestroyService do
+RSpec.describe Projects::Prometheus::Alerts::DestroyService do
let_it_be(:project) { create(:project) }
let_it_be(:user) { create(:user) }
let_it_be(:alert) { create(:prometheus_alert, project: project) }
diff --git a/spec/services/projects/prometheus/alerts/notify_service_spec.rb b/spec/services/projects/prometheus/alerts/notify_service_spec.rb
index 95acedb1e76..aae257e3e3a 100644
--- a/spec/services/projects/prometheus/alerts/notify_service_spec.rb
+++ b/spec/services/projects/prometheus/alerts/notify_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Projects::Prometheus::Alerts::NotifyService do
+RSpec.describe Projects::Prometheus::Alerts::NotifyService do
include PrometheusHelpers
let_it_be(:project, reload: true) { create(:project) }
@@ -36,48 +36,8 @@ describe Projects::Prometheus::Alerts::NotifyService do
end
end
- shared_examples 'processes incident issues' do |amount|
- let(:create_incident_service) { spy }
-
- it 'processes issues' do
- expect(IncidentManagement::ProcessPrometheusAlertWorker)
- .to receive(:perform_async)
- .with(project.id, kind_of(Hash))
- .exactly(amount).times
-
- Sidekiq::Testing.inline! do
- expect(subject).to be_success
- end
- end
- end
-
- shared_examples 'does not process incident issues' do
- it 'does not process issues' do
- expect(IncidentManagement::ProcessPrometheusAlertWorker)
- .not_to receive(:perform_async)
-
- expect(subject).to be_success
- end
- end
-
- shared_examples 'persists events' do
- let(:create_events_service) { spy }
-
- it 'persists events' do
- expect(Projects::Prometheus::Alerts::CreateEventsService)
- .to receive(:new)
- .and_return(create_events_service)
-
- expect(create_events_service)
- .to receive(:execute)
-
- expect(subject).to be_success
- end
- end
-
shared_examples 'notifies alerts' do
it_behaves_like 'sends notification email'
- it_behaves_like 'persists events'
end
shared_examples 'no notifications' do |http_status:|
@@ -102,6 +62,41 @@ describe Projects::Prometheus::Alerts::NotifyService do
let(:payload_alert_firing) { payload_raw['alerts'].first }
let(:token) { 'token' }
+ context 'with environment specific clusters' do
+ let(:prd_cluster) do
+ cluster
+ end
+
+ let(:stg_cluster) do
+ create(:cluster, :provided_by_user, projects: [project], enabled: true, environment_scope: 'stg/*')
+ end
+
+ let(:stg_environment) do
+ create(:environment, project: project, name: 'stg/1')
+ end
+
+ let(:alert_firing) do
+ create(:prometheus_alert, project: project, environment: stg_environment)
+ end
+
+ before do
+ create(:clusters_applications_prometheus, :installed,
+ cluster: prd_cluster, alert_manager_token: token)
+ create(:clusters_applications_prometheus, :installed,
+ cluster: stg_cluster, alert_manager_token: nil)
+ end
+
+ context 'without token' do
+ let(:token_input) { nil }
+
+ it_behaves_like 'notifies alerts'
+ end
+
+ context 'with token' do
+ it_behaves_like 'no notifications', http_status: :unauthorized
+ end
+ end
+
context 'with project specific cluster' do
using RSpec::Parameterized::TableSyntax
@@ -222,8 +217,6 @@ describe Projects::Prometheus::Alerts::NotifyService do
context 'when incident_management_setting does not exist' do
let!(:setting) { nil }
- it_behaves_like 'persists events'
-
it 'does not send notification email', :sidekiq_might_not_need_inline do
expect_any_instance_of(NotificationService)
.not_to receive(:async)
@@ -241,8 +234,6 @@ describe Projects::Prometheus::Alerts::NotifyService do
create(:project_incident_management_setting, send_email: false, project: project)
end
- it_behaves_like 'persists events'
-
it 'does not send notification' do
expect(NotificationService).not_to receive(:new)
@@ -276,45 +267,6 @@ describe Projects::Prometheus::Alerts::NotifyService do
end
end
end
-
- context 'process incident issues' do
- before do
- create(:prometheus_service, project: project)
- create(:project_alerting_setting, project: project, token: token)
- end
-
- context 'with create_issue setting enabled' do
- before do
- setting.update!(create_issue: true)
- end
-
- it_behaves_like 'processes incident issues', 2
-
- context 'multiple firing alerts' do
- let(:payload_raw) do
- prometheus_alert_payload(firing: [alert_firing, alert_firing], resolved: [])
- end
-
- it_behaves_like 'processes incident issues', 2
- end
-
- context 'without firing alerts' do
- let(:payload_raw) do
- prometheus_alert_payload(firing: [], resolved: [alert_resolved])
- end
-
- it_behaves_like 'processes incident issues', 1
- end
- end
-
- context 'with create_issue setting disabled' do
- before do
- setting.update!(create_issue: false)
- end
-
- it_behaves_like 'does not process incident issues'
- end
- end
end
context 'with invalid payload' do
@@ -345,13 +297,6 @@ describe Projects::Prometheus::Alerts::NotifyService do
subject
end
-
- it 'does not process issues' do
- expect(IncidentManagement::ProcessPrometheusAlertWorker)
- .not_to receive(:perform_async)
-
- subject
- end
end
end
diff --git a/spec/services/projects/prometheus/alerts/update_service_spec.rb b/spec/services/projects/prometheus/alerts/update_service_spec.rb
index 8a99c2679f7..e831d001838 100644
--- a/spec/services/projects/prometheus/alerts/update_service_spec.rb
+++ b/spec/services/projects/prometheus/alerts/update_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Projects::Prometheus::Alerts::UpdateService do
+RSpec.describe Projects::Prometheus::Alerts::UpdateService do
let_it_be(:project) { create(:project) }
let_it_be(:user) { create(:user) }
let_it_be(:environment) { create(:environment, project: project) }
diff --git a/spec/services/projects/prometheus/metrics/destroy_service_spec.rb b/spec/services/projects/prometheus/metrics/destroy_service_spec.rb
index 81fce82cf46..17cc88b27b6 100644
--- a/spec/services/projects/prometheus/metrics/destroy_service_spec.rb
+++ b/spec/services/projects/prometheus/metrics/destroy_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Projects::Prometheus::Metrics::DestroyService do
+RSpec.describe Projects::Prometheus::Metrics::DestroyService do
let(:metric) { create(:prometheus_metric) }
subject { described_class.new(metric) }
diff --git a/spec/services/projects/prometheus/metrics/update_service_spec.rb b/spec/services/projects/prometheus/metrics/update_service_spec.rb
index a53c6ae37cd..bf87093150c 100644
--- a/spec/services/projects/prometheus/metrics/update_service_spec.rb
+++ b/spec/services/projects/prometheus/metrics/update_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Projects::Prometheus::Metrics::UpdateService do
+RSpec.describe Projects::Prometheus::Metrics::UpdateService do
let(:metric) { create(:prometheus_metric) }
it 'updates the prometheus metric' do
diff --git a/spec/services/projects/propagate_service_template_spec.rb b/spec/services/projects/propagate_service_template_spec.rb
index ddc27c037f8..266bf2cc213 100644
--- a/spec/services/projects/propagate_service_template_spec.rb
+++ b/spec/services/projects/propagate_service_template_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Projects::PropagateServiceTemplate do
+RSpec.describe Projects::PropagateServiceTemplate do
describe '.propagate' do
let!(:service_template) do
PushoverService.create(
diff --git a/spec/services/projects/protect_default_branch_service_spec.rb b/spec/services/projects/protect_default_branch_service_spec.rb
index c0b819ab17b..a485a64ca35 100644
--- a/spec/services/projects/protect_default_branch_service_spec.rb
+++ b/spec/services/projects/protect_default_branch_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Projects::ProtectDefaultBranchService do
+RSpec.describe Projects::ProtectDefaultBranchService do
let(:service) { described_class.new(project) }
let(:project) { create(:project) }
diff --git a/spec/services/projects/repository_languages_service_spec.rb b/spec/services/projects/repository_languages_service_spec.rb
index 46c5095327d..cb61a7a1a3e 100644
--- a/spec/services/projects/repository_languages_service_spec.rb
+++ b/spec/services/projects/repository_languages_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Projects::RepositoryLanguagesService do
+RSpec.describe Projects::RepositoryLanguagesService do
let(:service) { described_class.new(project, project.owner) }
context 'when detected_repository_languages flag is set' do
diff --git a/spec/services/projects/transfer_service_spec.rb b/spec/services/projects/transfer_service_spec.rb
index 0e2431c0e44..72426a6f6ec 100644
--- a/spec/services/projects/transfer_service_spec.rb
+++ b/spec/services/projects/transfer_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Projects::TransferService do
+RSpec.describe Projects::TransferService do
include GitHelpers
let(:user) { create(:user) }
diff --git a/spec/services/projects/unlink_fork_service_spec.rb b/spec/services/projects/unlink_fork_service_spec.rb
index a6bdc69cdca..6a2c55a5e55 100644
--- a/spec/services/projects/unlink_fork_service_spec.rb
+++ b/spec/services/projects/unlink_fork_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Projects::UnlinkForkService, :use_clean_rails_memory_store_caching do
+RSpec.describe Projects::UnlinkForkService, :use_clean_rails_memory_store_caching do
include ProjectForksHelper
subject { described_class.new(forked_project, user) }
@@ -53,6 +53,7 @@ describe Projects::UnlinkForkService, :use_clean_rails_memory_store_caching do
expect(source.forks_count).to eq(1)
subject.execute
+ BatchLoader::Executor.clear_current
expect(source.forks_count).to be_zero
end
@@ -146,6 +147,7 @@ describe Projects::UnlinkForkService, :use_clean_rails_memory_store_caching do
expect(project.forks_count).to eq(2)
subject.execute
+ BatchLoader::Executor.clear_current
expect(project.forks_count).to be_zero
end
@@ -212,6 +214,7 @@ describe Projects::UnlinkForkService, :use_clean_rails_memory_store_caching do
expect(forked_project.forks_count).to eq(1)
subject.execute
+ BatchLoader::Executor.clear_current
expect(project.forks_count).to eq(1)
expect(forked_project.forks_count).to eq(0)
diff --git a/spec/services/projects/update_pages_configuration_service_spec.rb b/spec/services/projects/update_pages_configuration_service_spec.rb
index 363d3df0f84..c4c9fc779fa 100644
--- a/spec/services/projects/update_pages_configuration_service_spec.rb
+++ b/spec/services/projects/update_pages_configuration_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Projects::UpdatePagesConfigurationService do
+RSpec.describe Projects::UpdatePagesConfigurationService do
let(:project) { create(:project) }
let(:service) { described_class.new(project) }
diff --git a/spec/services/projects/update_pages_service_spec.rb b/spec/services/projects/update_pages_service_spec.rb
index 29c3c300d1b..2e02cb56668 100644
--- a/spec/services/projects/update_pages_service_spec.rb
+++ b/spec/services/projects/update_pages_service_spec.rb
@@ -2,7 +2,7 @@
require "spec_helper"
-describe Projects::UpdatePagesService do
+RSpec.describe Projects::UpdatePagesService do
let_it_be(:project, refind: true) { create(:project, :repository) }
let_it_be(:pipeline) { create(:ci_pipeline, project: project, sha: project.commit('HEAD').sha) }
let(:build) { create(:ci_build, pipeline: pipeline, ref: 'HEAD') }
diff --git a/spec/services/projects/update_remote_mirror_service_spec.rb b/spec/services/projects/update_remote_mirror_service_spec.rb
index 418973fb0a6..f0a8074f46c 100644
--- a/spec/services/projects/update_remote_mirror_service_spec.rb
+++ b/spec/services/projects/update_remote_mirror_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Projects::UpdateRemoteMirrorService do
+RSpec.describe Projects::UpdateRemoteMirrorService do
let(:project) { create(:project, :repository) }
let(:remote_project) { create(:forked_project_with_submodules) }
let(:remote_mirror) { create(:remote_mirror, project: project, enabled: true) }
diff --git a/spec/services/projects/update_repository_storage_service_spec.rb b/spec/services/projects/update_repository_storage_service_spec.rb
index e37580e7367..57e02c26b71 100644
--- a/spec/services/projects/update_repository_storage_service_spec.rb
+++ b/spec/services/projects/update_repository_storage_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Projects::UpdateRepositoryStorageService do
+RSpec.describe Projects::UpdateRepositoryStorageService do
include Gitlab::ShellAdapter
subject { described_class.new(repository_storage_move) }
@@ -37,14 +37,15 @@ describe Projects::UpdateRepositoryStorageService do
project.repository.path_to_repo
end
- expect(project_repository_double).to receive(:create_repository)
- .and_return(true)
expect(project_repository_double).to receive(:replicate)
.with(project.repository.raw)
expect(project_repository_double).to receive(:checksum)
.and_return(checksum)
+ expect(GitlabShellWorker).to receive(:perform_async).with(:mv_repository, 'default', anything, anything)
+ .and_call_original
result = subject.execute
+ project.reload
expect(result).to be_success
expect(project).not_to be_repository_read_only
@@ -70,8 +71,6 @@ describe Projects::UpdateRepositoryStorageService do
allow(Gitlab::GitalyClient).to receive(:filesystem_id).with('default').and_call_original
allow(Gitlab::GitalyClient).to receive(:filesystem_id).with('test_second_storage').and_return(SecureRandom.uuid)
- expect(project_repository_double).to receive(:create_repository)
- .and_return(true)
expect(project_repository_double).to receive(:replicate)
.with(project.repository.raw)
.and_raise(Gitlab::Git::CommandError)
@@ -90,8 +89,6 @@ describe Projects::UpdateRepositoryStorageService do
allow(Gitlab::GitalyClient).to receive(:filesystem_id).with('default').and_call_original
allow(Gitlab::GitalyClient).to receive(:filesystem_id).with('test_second_storage').and_return(SecureRandom.uuid)
- expect(project_repository_double).to receive(:create_repository)
- .and_return(true)
expect(project_repository_double).to receive(:replicate)
.with(project.repository.raw)
expect(project_repository_double).to receive(:checksum)
@@ -113,20 +110,43 @@ describe Projects::UpdateRepositoryStorageService do
allow(Gitlab::GitalyClient).to receive(:filesystem_id).with('default').and_call_original
allow(Gitlab::GitalyClient).to receive(:filesystem_id).with('test_second_storage').and_return(SecureRandom.uuid)
- expect(project_repository_double).to receive(:create_repository)
- .and_return(true)
expect(project_repository_double).to receive(:replicate)
.with(project.repository.raw)
expect(project_repository_double).to receive(:checksum)
.and_return(checksum)
result = subject.execute
+ project.reload
expect(result).to be_success
expect(project.repository_storage).to eq('test_second_storage')
expect(project.reload_pool_repository).to be_nil
end
end
+
+ context 'when the repository move is finished' do
+ let(:repository_storage_move) { create(:project_repository_storage_move, :finished, project: project, destination_storage_name: destination) }
+
+ it 'is idempotent' do
+ expect do
+ result = subject.execute
+
+ expect(result).to be_success
+ end.not_to change(repository_storage_move, :state)
+ end
+ end
+
+ context 'when the repository move is failed' do
+ let(:repository_storage_move) { create(:project_repository_storage_move, :failed, project: project, destination_storage_name: destination) }
+
+ it 'is idempotent' do
+ expect do
+ result = subject.execute
+
+ expect(result).to be_success
+ end.not_to change(repository_storage_move, :state)
+ end
+ end
end
context 'with wiki repository' do
diff --git a/spec/services/projects/update_service_spec.rb b/spec/services/projects/update_service_spec.rb
index 8a17884f641..6620ee6e697 100644
--- a/spec/services/projects/update_service_spec.rb
+++ b/spec/services/projects/update_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Projects::UpdateService do
+RSpec.describe Projects::UpdateService do
include ExternalAuthorizationServiceHelpers
include ProjectForksHelper
diff --git a/spec/services/projects/update_statistics_service_spec.rb b/spec/services/projects/update_statistics_service_spec.rb
index 8534853fbc7..92e97186be3 100644
--- a/spec/services/projects/update_statistics_service_spec.rb
+++ b/spec/services/projects/update_statistics_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Projects::UpdateStatisticsService do
+RSpec.describe Projects::UpdateStatisticsService do
let(:service) { described_class.new(project, nil, statistics: statistics)}
let(:statistics) { %w(repository_size) }
diff --git a/spec/services/prometheus/create_default_alerts_service_spec.rb b/spec/services/prometheus/create_default_alerts_service_spec.rb
index a28c38491de..e149161d881 100644
--- a/spec/services/prometheus/create_default_alerts_service_spec.rb
+++ b/spec/services/prometheus/create_default_alerts_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Prometheus::CreateDefaultAlertsService do
+RSpec.describe Prometheus::CreateDefaultAlertsService do
let_it_be(:project) { create(:project, :repository) }
let(:instance) { described_class.new(project: project) }
let(:expected_alerts) { described_class::DEFAULT_ALERTS }
diff --git a/spec/services/prometheus/proxy_service_spec.rb b/spec/services/prometheus/proxy_service_spec.rb
index bd451ff00a1..f22ea361fde 100644
--- a/spec/services/prometheus/proxy_service_spec.rb
+++ b/spec/services/prometheus/proxy_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Prometheus::ProxyService do
+RSpec.describe Prometheus::ProxyService do
include ReactiveCachingHelpers
let_it_be(:project) { create(:project) }
diff --git a/spec/services/prometheus/proxy_variable_substitution_service_spec.rb b/spec/services/prometheus/proxy_variable_substitution_service_spec.rb
index 2435dda07b4..d8c1fdffb98 100644
--- a/spec/services/prometheus/proxy_variable_substitution_service_spec.rb
+++ b/spec/services/prometheus/proxy_variable_substitution_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Prometheus::ProxyVariableSubstitutionService do
+RSpec.describe Prometheus::ProxyVariableSubstitutionService do
describe '#execute' do
let_it_be(:environment) { create(:environment) }
diff --git a/spec/services/protected_branches/create_service_spec.rb b/spec/services/protected_branches/create_service_spec.rb
index 82d24ec43f6..986322e4d87 100644
--- a/spec/services/protected_branches/create_service_spec.rb
+++ b/spec/services/protected_branches/create_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ProtectedBranches::CreateService do
+RSpec.describe ProtectedBranches::CreateService do
let(:project) { create(:project) }
let(:user) { project.owner }
let(:params) do
diff --git a/spec/services/protected_branches/destroy_service_spec.rb b/spec/services/protected_branches/destroy_service_spec.rb
index 3287eb9a59b..98d31147754 100644
--- a/spec/services/protected_branches/destroy_service_spec.rb
+++ b/spec/services/protected_branches/destroy_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ProtectedBranches::DestroyService do
+RSpec.describe ProtectedBranches::DestroyService do
let(:protected_branch) { create(:protected_branch) }
let(:project) { protected_branch.project }
let(:user) { project.owner }
diff --git a/spec/services/protected_branches/update_service_spec.rb b/spec/services/protected_branches/update_service_spec.rb
index 7967ff81075..fdfbdf2e6ae 100644
--- a/spec/services/protected_branches/update_service_spec.rb
+++ b/spec/services/protected_branches/update_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ProtectedBranches::UpdateService do
+RSpec.describe ProtectedBranches::UpdateService do
let(:protected_branch) { create(:protected_branch) }
let(:project) { protected_branch.project }
let(:user) { project.owner }
diff --git a/spec/services/protected_tags/create_service_spec.rb b/spec/services/protected_tags/create_service_spec.rb
index e58a539eb6f..e85a43eb51c 100644
--- a/spec/services/protected_tags/create_service_spec.rb
+++ b/spec/services/protected_tags/create_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ProtectedTags::CreateService do
+RSpec.describe ProtectedTags::CreateService do
let(:project) { create(:project) }
let(:user) { project.owner }
let(:params) do
diff --git a/spec/services/protected_tags/destroy_service_spec.rb b/spec/services/protected_tags/destroy_service_spec.rb
index 52d1d1caa34..fbd1452a8d1 100644
--- a/spec/services/protected_tags/destroy_service_spec.rb
+++ b/spec/services/protected_tags/destroy_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ProtectedTags::DestroyService do
+RSpec.describe ProtectedTags::DestroyService do
let(:protected_tag) { create(:protected_tag) }
let(:project) { protected_tag.project }
let(:user) { project.owner }
diff --git a/spec/services/protected_tags/update_service_spec.rb b/spec/services/protected_tags/update_service_spec.rb
index ca5109aca9c..ed151ca2347 100644
--- a/spec/services/protected_tags/update_service_spec.rb
+++ b/spec/services/protected_tags/update_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ProtectedTags::UpdateService do
+RSpec.describe ProtectedTags::UpdateService do
let(:protected_tag) { create(:protected_tag) }
let(:project) { protected_tag.project }
let(:user) { project.owner }
diff --git a/spec/services/push_event_payload_service_spec.rb b/spec/services/push_event_payload_service_spec.rb
index 855b10c0259..de2bec21a3c 100644
--- a/spec/services/push_event_payload_service_spec.rb
+++ b/spec/services/push_event_payload_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe PushEventPayloadService do
+RSpec.describe PushEventPayloadService do
let(:event) { create(:push_event) }
describe '#execute' do
diff --git a/spec/services/quick_actions/interpret_service_spec.rb b/spec/services/quick_actions/interpret_service_spec.rb
index 1bd402e38be..57e32b1aea9 100644
--- a/spec/services/quick_actions/interpret_service_spec.rb
+++ b/spec/services/quick_actions/interpret_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe QuickActions::InterpretService do
+RSpec.describe QuickActions::InterpretService do
let(:project) { create(:project, :public) }
let(:developer) { create(:user) }
let(:developer2) { create(:user) }
@@ -792,7 +792,7 @@ describe QuickActions::InterpretService do
let(:issuable) { issue }
end
- it_behaves_like 'assign command', :quarantine do
+ it_behaves_like 'assign command', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/27989' do
let(:content) { "/assign @#{developer.username} @#{developer2.username}" }
let(:issuable) { merge_request }
end
diff --git a/spec/services/quick_actions/target_service_spec.rb b/spec/services/quick_actions/target_service_spec.rb
index 0aeb29cbeec..d960678f809 100644
--- a/spec/services/quick_actions/target_service_spec.rb
+++ b/spec/services/quick_actions/target_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe QuickActions::TargetService do
+RSpec.describe QuickActions::TargetService do
let(:project) { create(:project) }
let(:user) { create(:user) }
let(:service) { described_class.new(project, user) }
diff --git a/spec/services/releases/create_evidence_service_spec.rb b/spec/services/releases/create_evidence_service_spec.rb
index caa36a6b21d..818d20f0468 100644
--- a/spec/services/releases/create_evidence_service_spec.rb
+++ b/spec/services/releases/create_evidence_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Releases::CreateEvidenceService do
+RSpec.describe Releases::CreateEvidenceService do
let_it_be(:project) { create(:project) }
let(:release) { create(:release, project: project) }
let(:service) { described_class.new(release) }
diff --git a/spec/services/releases/create_service_spec.rb b/spec/services/releases/create_service_spec.rb
index 4e3d9d5f108..3c0698aa203 100644
--- a/spec/services/releases/create_service_spec.rb
+++ b/spec/services/releases/create_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Releases::CreateService do
+RSpec.describe Releases::CreateService do
let(:project) { create(:project, :repository) }
let(:user) { create(:user) }
let(:tag_name) { project.repository.tag_names.first }
diff --git a/spec/services/releases/destroy_service_spec.rb b/spec/services/releases/destroy_service_spec.rb
index 9d027767cd2..bc5bff0b31d 100644
--- a/spec/services/releases/destroy_service_spec.rb
+++ b/spec/services/releases/destroy_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Releases::DestroyService do
+RSpec.describe Releases::DestroyService do
let(:project) { create(:project, :repository) }
let(:mainatiner) { create(:user) }
let(:repoter) { create(:user) }
diff --git a/spec/services/releases/update_service_spec.rb b/spec/services/releases/update_service_spec.rb
index 7f1849e39a4..00544b820cb 100644
--- a/spec/services/releases/update_service_spec.rb
+++ b/spec/services/releases/update_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Releases::UpdateService do
+RSpec.describe Releases::UpdateService do
let(:project) { create(:project, :repository) }
let(:user) { create(:user) }
let(:new_name) { 'A new name' }
diff --git a/spec/services/repositories/destroy_rollback_service_spec.rb b/spec/services/repositories/destroy_rollback_service_spec.rb
index c3cdae17de7..9cc41a4c7f8 100644
--- a/spec/services/repositories/destroy_rollback_service_spec.rb
+++ b/spec/services/repositories/destroy_rollback_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Repositories::DestroyRollbackService do
+RSpec.describe Repositories::DestroyRollbackService do
let_it_be(:user) { create(:user) }
let!(:project) { create(:project, :repository, namespace: user.namespace) }
let(:repository) { project.repository }
diff --git a/spec/services/repositories/destroy_service_spec.rb b/spec/services/repositories/destroy_service_spec.rb
index 9c2694483c1..30ec84b44e7 100644
--- a/spec/services/repositories/destroy_service_spec.rb
+++ b/spec/services/repositories/destroy_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Repositories::DestroyService do
+RSpec.describe Repositories::DestroyService do
let_it_be(:user) { create(:user) }
let!(:project) { create(:project, :repository, namespace: user.namespace) }
let(:repository) { project.repository }
@@ -34,6 +34,21 @@ describe Repositories::DestroyService do
project.touch
end
+ context 'on a read-only instance' do
+ before do
+ allow(Gitlab::Database).to receive(:read_only?).and_return(true)
+ end
+
+ it 'schedules the repository deletion' do
+ expect(Repositories::ShellDestroyService).to receive(:new).with(repository).and_call_original
+
+ expect(GitlabShellWorker).to receive(:perform_in)
+ .with(Repositories::ShellDestroyService::REPO_REMOVAL_DELAY, :remove_repository, project.repository_storage, remove_path)
+
+ subject
+ end
+ end
+
it 'removes the repository', :sidekiq_inline do
subject
diff --git a/spec/services/repositories/shell_destroy_service_spec.rb b/spec/services/repositories/shell_destroy_service_spec.rb
index 9419977f6fe..9020ef7b209 100644
--- a/spec/services/repositories/shell_destroy_service_spec.rb
+++ b/spec/services/repositories/shell_destroy_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Repositories::ShellDestroyService do
+RSpec.describe Repositories::ShellDestroyService do
let_it_be(:user) { create(:user) }
let!(:project) { create(:project, :repository, namespace: user.namespace) }
let(:path) { project.repository.disk_path }
diff --git a/spec/services/repository_archive_clean_up_service_spec.rb b/spec/services/repository_archive_clean_up_service_spec.rb
index 80b177a0174..c6d673fb1b5 100644
--- a/spec/services/repository_archive_clean_up_service_spec.rb
+++ b/spec/services/repository_archive_clean_up_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe RepositoryArchiveCleanUpService do
+RSpec.describe RepositoryArchiveCleanUpService do
subject(:service) { described_class.new }
describe '#execute (new archive locations)' do
diff --git a/spec/services/reset_project_cache_service_spec.rb b/spec/services/reset_project_cache_service_spec.rb
index a4db4481c36..3e79270da8d 100644
--- a/spec/services/reset_project_cache_service_spec.rb
+++ b/spec/services/reset_project_cache_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ResetProjectCacheService do
+RSpec.describe ResetProjectCacheService do
let(:project) { create(:project) }
let(:user) { create(:user) }
diff --git a/spec/services/resource_access_tokens/create_service_spec.rb b/spec/services/resource_access_tokens/create_service_spec.rb
index 57e7e4e66de..f22c379cd30 100644
--- a/spec/services/resource_access_tokens/create_service_spec.rb
+++ b/spec/services/resource_access_tokens/create_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ResourceAccessTokens::CreateService do
+RSpec.describe ResourceAccessTokens::CreateService do
subject { described_class.new(user, resource, params).execute }
let_it_be(:user) { create(:user) }
@@ -45,6 +45,27 @@ describe ResourceAccessTokens::CreateService do
expect(access_token.user.reload.user_type).to eq("#{resource_type}_bot")
end
+ context 'email confirmation status' do
+ shared_examples_for 'creates a user that has their email confirmed' do
+ it 'creates a user that has their email confirmed' do
+ response = subject
+ access_token = response.payload[:access_token]
+
+ expect(access_token.user.reload.confirmed?).to eq(true)
+ end
+ end
+
+ context 'when created by an admin' do
+ it_behaves_like 'creates a user that has their email confirmed' do
+ let(:user) { create(:admin) }
+ end
+ end
+
+ context 'when created by a non-admin' do
+ it_behaves_like 'creates a user that has their email confirmed'
+ end
+ end
+
context 'bot name' do
context 'when no value is passed' do
it 'uses default value' do
diff --git a/spec/services/resource_access_tokens/revoke_service_spec.rb b/spec/services/resource_access_tokens/revoke_service_spec.rb
index 3ce82745b9e..ffc06d770f8 100644
--- a/spec/services/resource_access_tokens/revoke_service_spec.rb
+++ b/spec/services/resource_access_tokens/revoke_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ResourceAccessTokens::RevokeService do
+RSpec.describe ResourceAccessTokens::RevokeService do
subject { described_class.new(user, resource, access_token).execute }
let_it_be(:user) { create(:user) }
diff --git a/spec/services/resource_events/change_labels_service_spec.rb b/spec/services/resource_events/change_labels_service_spec.rb
index 2b987b7fec9..efee185669e 100644
--- a/spec/services/resource_events/change_labels_service_spec.rb
+++ b/spec/services/resource_events/change_labels_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ResourceEvents::ChangeLabelsService do
+RSpec.describe ResourceEvents::ChangeLabelsService do
let_it_be(:project) { create(:project) }
let_it_be(:author) { create(:user) }
let(:resource) { create(:issue, project: project) }
diff --git a/spec/services/resource_events/change_milestone_service_spec.rb b/spec/services/resource_events/change_milestone_service_spec.rb
index dec01d0db8d..9c0f9420f7a 100644
--- a/spec/services/resource_events/change_milestone_service_spec.rb
+++ b/spec/services/resource_events/change_milestone_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ResourceEvents::ChangeMilestoneService do
+RSpec.describe ResourceEvents::ChangeMilestoneService do
[:issue, :merge_request].each do |issuable|
it_behaves_like 'a milestone events creator' do
let(:resource) { create(issuable) }
diff --git a/spec/services/resource_events/change_state_service_spec.rb b/spec/services/resource_events/change_state_service_spec.rb
index e5d2a4ab11e..5b5379b241b 100644
--- a/spec/services/resource_events/change_state_service_spec.rb
+++ b/spec/services/resource_events/change_state_service_spec.rb
@@ -2,38 +2,95 @@
require 'spec_helper'
-describe ResourceEvents::ChangeStateService do
+RSpec.describe ResourceEvents::ChangeStateService do
let_it_be(:project) { create(:project) }
let_it_be(:user) { create(:user) }
let(:issue) { create(:issue, project: project) }
let(:merge_request) { create(:merge_request, source_project: project) }
+ let(:source_commit) { create(:commit, project: project) }
+ let(:source_merge_request) { create(:merge_request, source_project: project, target_project: project, target_branch: 'foo') }
- describe '#execute' do
- context 'when resource is an issue' do
- %w[opened reopened closed locked].each do |state|
- it "creates the expected event if issue has #{state} state" do
- described_class.new(user: user, resource: issue).execute(state)
+ shared_examples 'a state event' do
+ %w[opened reopened closed locked].each do |state|
+ it "creates the expected event if resource has #{state} state" do
+ described_class.new(user: user, resource: resource).execute(status: state, mentionable_source: source)
+
+ event = resource.resource_state_events.last
- event = issue.resource_state_events.last
- expect(event.issue).to eq(issue)
+ if resource.is_a?(Issue)
+ expect(event.issue).to eq(resource)
expect(event.merge_request).to be_nil
- expect(event.state).to eq(state)
+ elsif resource.is_a?(MergeRequest)
+ expect(event.issue).to be_nil
+ expect(event.merge_request).to eq(resource)
end
+
+ expect(event.state).to eq(state)
+
+ expect_event_source(event, source)
end
end
+ end
- context 'when resource is a merge request' do
- %w[opened reopened closed locked merged].each do |state|
- it "creates the expected event if merge request has #{state} state" do
- described_class.new(user: user, resource: merge_request).execute(state)
+ describe '#execute' do
+ context 'when resource is an Issue' do
+ context 'when no source is given' do
+ it_behaves_like 'a state event' do
+ let(:resource) { issue }
+ let(:source) { nil }
+ end
+ end
- event = merge_request.resource_state_events.last
- expect(event.issue).to be_nil
- expect(event.merge_request).to eq(merge_request)
- expect(event.state).to eq(state)
+ context 'when source commit is given' do
+ it_behaves_like 'a state event' do
+ let(:resource) { issue }
+ let(:source) { source_commit }
+ end
+ end
+
+ context 'when source merge request is given' do
+ it_behaves_like 'a state event' do
+ let(:resource) { issue }
+ let(:source) { source_merge_request }
end
end
end
+
+ context 'when resource is a MergeRequest' do
+ context 'when no source is given' do
+ it_behaves_like 'a state event' do
+ let(:resource) { merge_request }
+ let(:source) { nil }
+ end
+ end
+
+ context 'when source commit is given' do
+ it_behaves_like 'a state event' do
+ let(:resource) { merge_request }
+ let(:source) { source_commit }
+ end
+ end
+
+ context 'when source merge request is given' do
+ it_behaves_like 'a state event' do
+ let(:resource) { merge_request }
+ let(:source) { source_merge_request }
+ end
+ end
+ end
+ end
+
+ def expect_event_source(event, source)
+ if source.is_a?(MergeRequest)
+ expect(event.source_commit).to be_nil
+ expect(event.source_merge_request).to eq(source)
+ elsif source.is_a?(Commit)
+ expect(event.source_commit).to eq(source.id)
+ expect(event.source_merge_request).to be_nil
+ else
+ expect(event.source_merge_request).to be_nil
+ expect(event.source_commit).to be_nil
+ end
end
end
diff --git a/spec/services/resource_events/merge_into_notes_service_spec.rb b/spec/services/resource_events/merge_into_notes_service_spec.rb
index 2664a27244d..6209294f4ce 100644
--- a/spec/services/resource_events/merge_into_notes_service_spec.rb
+++ b/spec/services/resource_events/merge_into_notes_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ResourceEvents::MergeIntoNotesService do
+RSpec.describe ResourceEvents::MergeIntoNotesService do
def create_event(params)
event_params = { action: :add, label: label, issue: resource,
user: user }
@@ -61,7 +61,7 @@ describe ResourceEvents::MergeIntoNotesService do
event = create_event(created_at: 1.day.ago)
notes = described_class.new(resource, user,
- last_fetched_at: 2.days.ago.to_i).execute
+ last_fetched_at: 2.days.ago).execute
expect(notes.count).to eq 1
expect(notes.first.discussion_id).to eq event.discussion_id
diff --git a/spec/services/resource_events/synthetic_label_notes_builder_service_spec.rb b/spec/services/resource_events/synthetic_label_notes_builder_service_spec.rb
index 41902bc1da1..cb42ad5b617 100644
--- a/spec/services/resource_events/synthetic_label_notes_builder_service_spec.rb
+++ b/spec/services/resource_events/synthetic_label_notes_builder_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ResourceEvents::SyntheticLabelNotesBuilderService do
+RSpec.describe ResourceEvents::SyntheticLabelNotesBuilderService do
describe '#execute' do
let!(:user) { create(:user) }
diff --git a/spec/services/resource_events/synthetic_milestone_notes_builder_service_spec.rb b/spec/services/resource_events/synthetic_milestone_notes_builder_service_spec.rb
index e98b8bd00dc..5e3afeabee7 100644
--- a/spec/services/resource_events/synthetic_milestone_notes_builder_service_spec.rb
+++ b/spec/services/resource_events/synthetic_milestone_notes_builder_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ResourceEvents::SyntheticMilestoneNotesBuilderService do
+RSpec.describe ResourceEvents::SyntheticMilestoneNotesBuilderService do
describe '#execute' do
let_it_be(:user) { create(:user) }
let_it_be(:issue) { create(:issue, author: user) }
diff --git a/spec/services/search/global_service_spec.rb b/spec/services/search/global_service_spec.rb
index 0f829df90b3..90ad18e5571 100644
--- a/spec/services/search/global_service_spec.rb
+++ b/spec/services/search/global_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Search::GlobalService do
+RSpec.describe Search::GlobalService do
let(:user) { create(:user) }
let(:internal_user) { create(:user) }
diff --git a/spec/services/search/group_service_spec.rb b/spec/services/search/group_service_spec.rb
index cfb672753b8..d3026d158d4 100644
--- a/spec/services/search/group_service_spec.rb
+++ b/spec/services/search/group_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Search::GroupService do
+RSpec.describe Search::GroupService do
shared_examples_for 'group search' do
context 'finding projects by name' do
let(:user) { create(:user) }
diff --git a/spec/services/search/snippet_service_spec.rb b/spec/services/search/snippet_service_spec.rb
index cb2bb0c43fd..ceaf3d055bf 100644
--- a/spec/services/search/snippet_service_spec.rb
+++ b/spec/services/search/snippet_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Search::SnippetService do
+RSpec.describe Search::SnippetService do
let_it_be(:author) { create(:author) }
let_it_be(:project) { create(:project, :public) }
diff --git a/spec/services/search_service_spec.rb b/spec/services/search_service_spec.rb
index 0333eb85fb6..52aef73ac77 100644
--- a/spec/services/search_service_spec.rb
+++ b/spec/services/search_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe SearchService do
+RSpec.describe SearchService do
let_it_be(:user) { create(:user) }
let_it_be(:accessible_group) { create(:group, :private) }
diff --git a/spec/services/serverless/associate_domain_service_spec.rb b/spec/services/serverless/associate_domain_service_spec.rb
index 3d1a878bcf5..3b5231989bc 100644
--- a/spec/services/serverless/associate_domain_service_spec.rb
+++ b/spec/services/serverless/associate_domain_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Serverless::AssociateDomainService do
+RSpec.describe Serverless::AssociateDomainService do
subject { described_class.new(knative, pages_domain_id: pages_domain_id, creator: creator) }
let(:sdc) { create(:serverless_domain_cluster, pages_domain: create(:pages_domain, :instance_serverless)) }
diff --git a/spec/services/service_desk_settings/update_service_spec.rb b/spec/services/service_desk_settings/update_service_spec.rb
new file mode 100644
index 00000000000..8b920d536b4
--- /dev/null
+++ b/spec/services/service_desk_settings/update_service_spec.rb
@@ -0,0 +1,46 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+RSpec.describe ServiceDeskSettings::UpdateService do
+ describe '#execute' do
+ let_it_be(:settings) { create(:service_desk_setting, outgoing_name: 'original name') }
+ let_it_be(:user) { create(:user) }
+
+ context 'with valid params' do
+ let(:params) { { outgoing_name: 'some name', project_key: 'foo' } }
+
+ it 'updates service desk settings' do
+ result = described_class.new(settings.project, user, params).execute
+
+ expect(result[:status]).to eq :success
+ expect(settings.reload.outgoing_name).to eq 'some name'
+ expect(settings.reload.project_key).to eq 'foo'
+ end
+
+ context 'when service_desk_custom_address is disabled' do
+ before do
+ stub_feature_flags(service_desk_custom_address: false)
+ end
+
+ it 'ignores project_key parameter' do
+ result = described_class.new(settings.project, user, params).execute
+
+ expect(result[:status]).to eq :success
+ expect(settings.reload.project_key).to be_nil
+ end
+ end
+ end
+
+ context 'with invalid params' do
+ let(:params) { { outgoing_name: 'x' * 256 } }
+
+ it 'does not update service desk settings' do
+ result = described_class.new(settings.project, user, params).execute
+
+ expect(result[:status]).to eq :error
+ expect(result[:message]).to eq 'Outgoing name is too long (maximum is 255 characters)'
+ expect(settings.reload.outgoing_name).to eq 'original name'
+ end
+ end
+ end
+end
diff --git a/spec/services/service_response_spec.rb b/spec/services/service_response_spec.rb
index 2c944a63ebb..986b26e67d7 100644
--- a/spec/services/service_response_spec.rb
+++ b/spec/services/service_response_spec.rb
@@ -4,7 +4,7 @@ require 'fast_spec_helper'
ActiveSupport::Dependencies.autoload_paths << 'app/services'
-describe ServiceResponse do
+RSpec.describe ServiceResponse do
describe '.success' do
it 'creates a successful response without a message' do
expect(described_class.success).to be_success
diff --git a/spec/services/snippets/bulk_destroy_service_spec.rb b/spec/services/snippets/bulk_destroy_service_spec.rb
index 6e5623e575f..8a6250a8b45 100644
--- a/spec/services/snippets/bulk_destroy_service_spec.rb
+++ b/spec/services/snippets/bulk_destroy_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Snippets::BulkDestroyService do
+RSpec.describe Snippets::BulkDestroyService do
let_it_be(:project) { create(:project) }
let(:user) { create(:user) }
let!(:personal_snippet) { create(:personal_snippet, :repository, author: user) }
diff --git a/spec/services/snippets/count_service_spec.rb b/spec/services/snippets/count_service_spec.rb
index 4137e65dcca..5ce637d0bac 100644
--- a/spec/services/snippets/count_service_spec.rb
+++ b/spec/services/snippets/count_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Snippets::CountService do
+RSpec.describe Snippets::CountService do
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project, :public) }
diff --git a/spec/services/snippets/create_service_spec.rb b/spec/services/snippets/create_service_spec.rb
index fa8cbc87563..62eef00b67f 100644
--- a/spec/services/snippets/create_service_spec.rb
+++ b/spec/services/snippets/create_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Snippets::CreateService do
+RSpec.describe Snippets::CreateService do
describe '#execute' do
let_it_be(:user) { create(:user) }
let_it_be(:admin) { create(:user, :admin) }
@@ -177,10 +177,8 @@ describe Snippets::CreateService do
end
it 'returns a generic error' do
- response = subject
-
- expect(response).to be_error
- expect(response.payload[:snippet].errors[:repository]).to eq ['Error creating the snippet']
+ expect(subject).to be_error
+ expect(snippet.errors[:repository]).to eq ['Error creating the snippet']
end
end
@@ -230,15 +228,15 @@ describe Snippets::CreateService do
end
end
- shared_examples 'when snippet_files param is present' do
+ shared_examples 'when snippet_actions param is present' do
let(:file_path) { 'snippet_file_path.rb' }
let(:content) { 'snippet_content' }
- let(:snippet_files) { [{ action: 'create', file_path: file_path, content: content }] }
+ let(:snippet_actions) { [{ action: 'create', file_path: file_path, content: content }] }
let(:base_opts) do
{
title: 'Test snippet',
visibility_level: Gitlab::VisibilityLevel::PRIVATE,
- snippet_files: snippet_files
+ snippet_actions: snippet_actions
}
end
@@ -250,7 +248,7 @@ describe Snippets::CreateService do
end
it 'commit the files to the repository' do
- subject
+ expect(subject).to be_success
blob = snippet.repository.blob_at('master', file_path)
@@ -261,28 +259,42 @@ describe Snippets::CreateService do
let(:extra_opts) { { content: 'foo', file_name: 'path' } }
it 'a validation error is raised' do
- response = subject
- snippet = response.payload[:snippet]
-
- expect(response).to be_error
+ expect(subject).to be_error
expect(snippet.errors.full_messages_for(:content)).to eq ['Content and snippet files cannot be used together']
expect(snippet.errors.full_messages_for(:file_name)).to eq ['File name and snippet files cannot be used together']
expect(snippet.repository.exists?).to be_falsey
end
end
- context 'when snippet_files param is invalid' do
- let(:snippet_files) { [{ action: 'invalid_action', file_path: 'snippet_file_path.rb', content: 'snippet_content' }] }
+ context 'when snippet_actions param is invalid' do
+ let(:snippet_actions) { [{ action: 'invalid_action', file_path: 'snippet_file_path.rb', content: 'snippet_content' }] }
it 'a validation error is raised' do
- response = subject
- snippet = response.payload[:snippet]
+ expect(subject).to be_error
+ expect(snippet.errors.full_messages_for(:snippet_actions)).to eq ['Snippet actions have invalid data']
+ expect(snippet.repository.exists?).to be_falsey
+ end
+ end
- expect(response).to be_error
- expect(snippet.errors.full_messages_for(:snippet_files)).to eq ['Snippet files have invalid data']
+ context 'when snippet_actions contain an action different from "create"' do
+ let(:snippet_actions) { [{ action: 'delete', file_path: 'snippet_file_path.rb' }] }
+
+ it 'a validation error is raised' do
+ expect(subject).to be_error
+ expect(snippet.errors.full_messages_for(:snippet_actions)).to eq ['Snippet actions have invalid data']
expect(snippet.repository.exists?).to be_falsey
end
end
+
+ context 'when "create" operation does not have file_path or is empty' do
+ let(:snippet_actions) { [{ action: 'create', content: content }, { action: 'create', content: content, file_path: '' }] }
+
+ it 'generates the file path for the files' do
+ expect(subject).to be_success
+ expect(snippet.repository.blob_at('master', 'snippetfile1.txt').data).to eq content
+ expect(snippet.repository.blob_at('master', 'snippetfile2.txt').data).to eq content
+ end
+ end
end
context 'when ProjectSnippet' do
@@ -299,7 +311,7 @@ describe Snippets::CreateService do
it_behaves_like 'an error service response when save fails'
it_behaves_like 'creates repository and files'
it_behaves_like 'after_save callback to store_mentions', ProjectSnippet
- it_behaves_like 'when snippet_files param is present'
+ it_behaves_like 'when snippet_actions param is present'
context 'when uploaded files are passed to the service' do
let(:extra_opts) { { files: ['foo'] } }
@@ -326,7 +338,7 @@ describe Snippets::CreateService do
it_behaves_like 'an error service response when save fails'
it_behaves_like 'creates repository and files'
it_behaves_like 'after_save callback to store_mentions', PersonalSnippet
- it_behaves_like 'when snippet_files param is present'
+ it_behaves_like 'when snippet_actions param is present'
context 'when the snippet description contains files' do
include FileMoverHelpers
diff --git a/spec/services/snippets/destroy_service_spec.rb b/spec/services/snippets/destroy_service_spec.rb
index 840dc11a740..e53d00b9ca1 100644
--- a/spec/services/snippets/destroy_service_spec.rb
+++ b/spec/services/snippets/destroy_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Snippets::DestroyService do
+RSpec.describe Snippets::DestroyService do
let_it_be(:project) { create(:project) }
let_it_be(:user) { create(:user) }
let_it_be(:other_user) { create(:user) }
@@ -105,6 +105,26 @@ describe Snippets::DestroyService do
it_behaves_like 'a successful destroy'
it_behaves_like 'deletes the snippet repository'
+
+ context 'project statistics' do
+ before do
+ snippet.statistics.refresh!
+ end
+
+ it 'updates stats after deletion' do
+ expect(project.reload.statistics.snippets_size).not_to be_zero
+
+ subject
+
+ expect(project.reload.statistics.snippets_size).to be_zero
+ end
+
+ it 'schedules a namespace statistics update' do
+ expect(Namespaces::ScheduleAggregationWorker).to receive(:perform_async).with(project.namespace_id).once
+
+ subject
+ end
+ end
end
context 'when user is not able to admin_project_snippet' do
@@ -122,6 +142,12 @@ describe Snippets::DestroyService do
it_behaves_like 'a successful destroy'
it_behaves_like 'deletes the snippet repository'
+
+ it 'schedules a namespace statistics update' do
+ expect(Namespaces::ScheduleAggregationWorker).to receive(:perform_async).with(author.namespace_id)
+
+ subject
+ end
end
context 'when user is not able to admin_personal_snippet' do
diff --git a/spec/services/snippets/repository_validation_service_spec.rb b/spec/services/snippets/repository_validation_service_spec.rb
index 1c139d8c223..e2a0d0faa18 100644
--- a/spec/services/snippets/repository_validation_service_spec.rb
+++ b/spec/services/snippets/repository_validation_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Snippets::RepositoryValidationService do
+RSpec.describe Snippets::RepositoryValidationService do
describe '#execute' do
let_it_be(:user) { create(:user) }
let_it_be(:snippet) { create(:personal_snippet, :empty_repo, author: user) }
diff --git a/spec/services/snippets/update_service_spec.rb b/spec/services/snippets/update_service_spec.rb
index 7e6441ad2f9..66dddcc49de 100644
--- a/spec/services/snippets/update_service_spec.rb
+++ b/spec/services/snippets/update_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Snippets::UpdateService do
+RSpec.describe Snippets::UpdateService do
describe '#execute' do
let_it_be(:user) { create(:user) }
let_it_be(:admin) { create :user, admin: true }
@@ -302,22 +302,22 @@ describe Snippets::UpdateService do
end
end
- shared_examples 'when snippet_files param is present' do
+ shared_examples 'when snippet_actions param is present' do
let(:file_path) { 'CHANGELOG' }
let(:content) { 'snippet_content' }
let(:new_title) { 'New title' }
- let(:snippet_files) { [{ action: 'update', previous_path: file_path, file_path: file_path, content: content }] }
+ let(:snippet_actions) { [{ action: 'update', previous_path: file_path, file_path: file_path, content: content }] }
let(:base_opts) do
{
title: new_title,
- snippet_files: snippet_files
+ snippet_actions: snippet_actions
}
end
it 'updates a snippet with the provided attributes' do
file_path = 'foo'
- snippet_files[0][:action] = 'move'
- snippet_files[0][:file_path] = file_path
+ snippet_actions[0][:action] = 'move'
+ snippet_actions[0][:file_path] = file_path
response = subject
snippet = response.payload[:snippet]
@@ -328,7 +328,7 @@ describe Snippets::UpdateService do
expect(snippet.content).to eq(content)
end
- it 'commit the files to the repository' do
+ it 'commits the files to the repository' do
subject
blob = snippet.repository.blob_at('master', file_path)
@@ -349,15 +349,27 @@ describe Snippets::UpdateService do
end
end
- context 'when snippet_files param is invalid' do
- let(:snippet_files) { [{ action: 'invalid_action' }] }
+ context 'when snippet_file content is not present' do
+ let(:snippet_actions) { [{ action: :move, previous_path: file_path, file_path: 'new_file_path' }] }
+
+ it 'does not update snippet content' do
+ content = snippet.content
+
+ expect(subject).to be_success
+
+ expect(snippet.reload.content).to eq content
+ end
+ end
+
+ context 'when snippet_actions param is invalid' do
+ let(:snippet_actions) { [{ action: 'invalid_action' }] }
it 'raises a validation error' do
response = subject
snippet = response.payload[:snippet]
expect(response).to be_error
- expect(snippet.errors.full_messages_for(:snippet_files)).to eq ['Snippet files have invalid data']
+ expect(snippet.errors.full_messages_for(:snippet_actions)).to eq ['Snippet actions have invalid data']
end
end
@@ -376,6 +388,226 @@ describe Snippets::UpdateService do
expect(snippet.content).to eq(content)
end
end
+
+ context 'commit actions' do
+ let(:new_path) { 'created_new_file' }
+ let(:base_opts) { { snippet_actions: snippet_actions } }
+
+ shared_examples 'returns an error' do |error_msg|
+ specify do
+ response = subject
+
+ expect(response).to be_error
+ expect(response.message).to eq error_msg
+ end
+ end
+
+ context 'update action' do
+ let(:snippet_actions) { [{ action: :update, file_path: file_path, content: content }] }
+
+ it 'updates the file content' do
+ expect(subject).to be_success
+
+ blob = blob(file_path)
+
+ expect(blob.data).to eq content
+ end
+
+ context 'when previous_path is present' do
+ let(:snippet_actions) { [{ action: :update, previous_path: file_path, file_path: file_path, content: content }] }
+
+ it 'updates the file content' do
+ expect(subject).to be_success
+
+ blob = blob(file_path)
+
+ expect(blob.data).to eq content
+ end
+ end
+
+ context 'when content is not present' do
+ let(:snippet_actions) { [{ action: :update, file_path: file_path }] }
+
+ it_behaves_like 'returns an error', 'Snippet actions have invalid data'
+ end
+
+ context 'when file_path does not exist' do
+ let(:snippet_actions) { [{ action: :update, file_path: 'makeup_name', content: content }] }
+
+ it_behaves_like 'returns an error', 'Repository Error updating the snippet'
+ end
+ end
+
+ context 'move action' do
+ context 'when file_path and previous_path are the same' do
+ let(:snippet_actions) { [{ action: :move, previous_path: file_path, file_path: file_path }] }
+
+ it_behaves_like 'returns an error', 'Snippet actions have invalid data'
+ end
+
+ context 'when file_path and previous_path are different' do
+ let(:snippet_actions) { [{ action: :move, previous_path: file_path, file_path: new_path }] }
+
+ it 'renames the file' do
+ old_blob = blob(file_path)
+
+ expect(subject).to be_success
+
+ blob = blob(new_path)
+
+ expect(blob).to be_present
+ expect(blob.data).to eq old_blob.data
+ end
+ end
+
+ context 'when previous_path does not exist' do
+ let(:snippet_actions) { [{ action: :move, previous_path: 'makeup_name', file_path: new_path }] }
+
+ it_behaves_like 'returns an error', 'Repository Error updating the snippet'
+ end
+
+ context 'when user wants to rename the file and update content' do
+ let(:snippet_actions) { [{ action: :move, previous_path: file_path, file_path: new_path, content: content }] }
+
+ it 'performs both operations' do
+ expect(subject).to be_success
+
+ blob = blob(new_path)
+
+ expect(blob).to be_present
+ expect(blob.data).to eq content
+ end
+ end
+ end
+
+ context 'delete action' do
+ let(:snippet_actions) { [{ action: :delete, file_path: file_path }] }
+
+ shared_examples 'deletes the file' do
+ specify do
+ old_blob = blob(file_path)
+ expect(old_blob).to be_present
+
+ expect(subject).to be_success
+ expect(blob(file_path)).to be_nil
+ end
+ end
+
+ it_behaves_like 'deletes the file'
+
+ context 'when previous_path is present and same as file_path' do
+ let(:snippet_actions) { [{ action: :delete, previous_path: file_path, file_path: file_path }] }
+
+ it_behaves_like 'deletes the file'
+ end
+
+ context 'when previous_path is present and is different from file_path' do
+ let(:snippet_actions) { [{ action: :delete, previous_path: 'foo', file_path: file_path }] }
+
+ it_behaves_like 'deletes the file'
+ end
+
+ context 'when content is present' do
+ let(:snippet_actions) { [{ action: :delete, file_path: file_path, content: 'foo' }] }
+
+ it_behaves_like 'deletes the file'
+ end
+
+ context 'when file_path does not exist' do
+ let(:snippet_actions) { [{ action: :delete, file_path: 'makeup_name' }] }
+
+ it_behaves_like 'returns an error', 'Repository Error updating the snippet'
+ end
+ end
+
+ context 'create action' do
+ let(:snippet_actions) { [{ action: :create, file_path: new_path, content: content }] }
+
+ it 'creates the file' do
+ expect(subject).to be_success
+
+ blob = blob(new_path)
+ expect(blob).to be_present
+ expect(blob.data).to eq content
+ end
+
+ context 'when content is not present' do
+ let(:snippet_actions) { [{ action: :create, file_path: new_path }] }
+
+ it_behaves_like 'returns an error', 'Snippet actions have invalid data'
+ end
+
+ context 'when file_path is not present or empty' do
+ let(:snippet_actions) { [{ action: :create, content: content }, { action: :create, file_path: '', content: content }] }
+
+ it 'generates the file path for the files' do
+ expect(blob('snippetfile1.txt')).to be_nil
+ expect(blob('snippetfile2.txt')).to be_nil
+
+ expect(subject).to be_success
+
+ expect(blob('snippetfile1.txt').data).to eq content
+ expect(blob('snippetfile2.txt').data).to eq content
+ end
+ end
+
+ context 'when file_path already exists in the repository' do
+ let(:snippet_actions) { [{ action: :create, file_path: file_path, content: content }] }
+
+ it_behaves_like 'returns an error', 'Repository Error updating the snippet'
+ end
+
+ context 'when previous_path is present' do
+ let(:snippet_actions) { [{ action: :create, previous_path: 'foo', file_path: new_path, content: content }] }
+
+ it 'creates the file' do
+ expect(subject).to be_success
+
+ blob = blob(new_path)
+ expect(blob).to be_present
+ expect(blob.data).to eq content
+ end
+ end
+ end
+
+ context 'combination of actions' do
+ let(:delete_file_path) { 'CHANGELOG' }
+ let(:create_file_path) { 'created_new_file' }
+ let(:update_file_path) { 'LICENSE' }
+ let(:move_previous_path) { 'VERSION' }
+ let(:move_file_path) { 'VERSION_new' }
+
+ let(:snippet_actions) do
+ [
+ { action: :create, file_path: create_file_path, content: content },
+ { action: :update, file_path: update_file_path, content: content },
+ { action: :delete, file_path: delete_file_path },
+ { action: :move, previous_path: move_previous_path, file_path: move_file_path, content: content }
+ ]
+ end
+
+ it 'performs all operations' do
+ expect(subject).to be_success
+
+ expect(blob(delete_file_path)).to be_nil
+
+ created_blob = blob(create_file_path)
+ expect(created_blob.data).to eq content
+
+ updated_blob = blob(update_file_path)
+ expect(updated_blob.data).to eq content
+
+ expect(blob(move_previous_path)).to be_nil
+
+ moved_blob = blob(move_file_path)
+ expect(moved_blob.data).to eq content
+ end
+ end
+
+ def blob(path)
+ snippet.repository.blob_at('master', path)
+ end
+ end
end
shared_examples 'only file_name is present' do
@@ -446,7 +678,7 @@ describe Snippets::UpdateService do
it_behaves_like 'updates repository content'
it_behaves_like 'commit operation fails'
it_behaves_like 'committable attributes'
- it_behaves_like 'when snippet_files param is present'
+ it_behaves_like 'when snippet_actions param is present'
it_behaves_like 'only file_name is present'
it_behaves_like 'only content is present'
it_behaves_like 'snippets spam check is performed' do
@@ -473,7 +705,7 @@ describe Snippets::UpdateService do
it_behaves_like 'updates repository content'
it_behaves_like 'commit operation fails'
it_behaves_like 'committable attributes'
- it_behaves_like 'when snippet_files param is present'
+ it_behaves_like 'when snippet_actions param is present'
it_behaves_like 'only file_name is present'
it_behaves_like 'only content is present'
it_behaves_like 'snippets spam check is performed' do
diff --git a/spec/services/snippets/update_statistics_service_spec.rb b/spec/services/snippets/update_statistics_service_spec.rb
new file mode 100644
index 00000000000..27ae054676a
--- /dev/null
+++ b/spec/services/snippets/update_statistics_service_spec.rb
@@ -0,0 +1,86 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Snippets::UpdateStatisticsService do
+ describe '#execute' do
+ subject { described_class.new(snippet).execute }
+
+ shared_examples 'updates statistics' do
+ it 'returns a successful response' do
+ expect(subject).to be_success
+ end
+
+ it 'expires statistics cache' do
+ expect(snippet.repository).to receive(:expire_statistics_caches)
+
+ subject
+ end
+
+ context 'when snippet statistics does not exist' do
+ it 'creates snippet statistics' do
+ snippet.statistics.delete
+ snippet.reload
+
+ expect do
+ subject
+ end.to change(SnippetStatistics, :count).by(1)
+
+ expect(snippet.statistics.commit_count).not_to be_zero
+ expect(snippet.statistics.file_count).not_to be_zero
+ expect(snippet.statistics.repository_size).not_to be_zero
+ end
+ end
+
+ context 'when snippet statistics exists' do
+ it 'updates snippet statistics' do
+ expect(snippet.statistics.commit_count).to be_zero
+ expect(snippet.statistics.file_count).to be_zero
+ expect(snippet.statistics.repository_size).to be_zero
+
+ subject
+
+ expect(snippet.statistics.commit_count).not_to be_zero
+ expect(snippet.statistics.file_count).not_to be_zero
+ expect(snippet.statistics.repository_size).not_to be_zero
+ end
+ end
+
+ context 'when snippet does not have a repository' do
+ it 'returns an error response' do
+ expect(snippet).to receive(:repository_exists?).and_return(false)
+
+ expect(subject).to be_error
+ end
+ end
+
+ it 'schedules a namespace storage statistics update' do
+ expect(Namespaces::ScheduleAggregationWorker)
+ .to receive(:perform_async).once
+
+ subject
+ end
+ end
+
+ context 'with PersonalSnippet' do
+ let!(:snippet) { create(:personal_snippet, :repository) }
+
+ it_behaves_like 'updates statistics'
+ end
+
+ context 'with ProjectSnippet' do
+ let!(:snippet) { create(:project_snippet, :repository) }
+ let(:project_statistics) { snippet.project.statistics }
+
+ it_behaves_like 'updates statistics'
+
+ it 'updates projects statistics "snippets_size"' do
+ expect(project_statistics.snippets_size).to be_zero
+
+ subject
+
+ expect(snippet.reload.statistics.repository_size).to eq project_statistics.reload.snippets_size
+ end
+ end
+ end
+end
diff --git a/spec/services/spam/akismet_service_spec.rb b/spec/services/spam/akismet_service_spec.rb
index 413b43d0156..f75b0216b78 100644
--- a/spec/services/spam/akismet_service_spec.rb
+++ b/spec/services/spam/akismet_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Spam::AkismetService do
+RSpec.describe Spam::AkismetService do
let(:fake_akismet_client) { double(:akismet_client) }
let_it_be(:text) { "Would you like to buy some tinned meat product?" }
diff --git a/spec/services/spam/ham_service_spec.rb b/spec/services/spam/ham_service_spec.rb
index 9848f48def2..c947de6cf92 100644
--- a/spec/services/spam/ham_service_spec.rb
+++ b/spec/services/spam/ham_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Spam::HamService do
+RSpec.describe Spam::HamService do
let_it_be(:user) { create(:user) }
let!(:spam_log) { create(:spam_log, user: user, submitted_as_ham: false) }
let(:fake_akismet_service) { double(:akismet_service) }
diff --git a/spec/services/spam/mark_as_spam_service_spec.rb b/spec/services/spam/mark_as_spam_service_spec.rb
index 9978005279a..308a66c3a48 100644
--- a/spec/services/spam/mark_as_spam_service_spec.rb
+++ b/spec/services/spam/mark_as_spam_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Spam::MarkAsSpamService do
+RSpec.describe Spam::MarkAsSpamService do
let(:user_agent_detail) { build(:user_agent_detail) }
let(:spammable) { build(:issue, user_agent_detail: user_agent_detail) }
let(:fake_akismet_service) { double(:akismet_service, submit_spam: true) }
diff --git a/spec/services/spam/spam_action_service_spec.rb b/spec/services/spam/spam_action_service_spec.rb
index 7b6b65c82b1..abb8e49ec52 100644
--- a/spec/services/spam/spam_action_service_spec.rb
+++ b/spec/services/spam/spam_action_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Spam::SpamActionService do
+RSpec.describe Spam::SpamActionService do
include_context 'includes Spam constants'
let(:fake_ip) { '1.2.3.4' }
diff --git a/spec/services/spam/spam_verdict_service_spec.rb b/spec/services/spam/spam_verdict_service_spec.rb
index f6d9cd96da5..d775e1bdfb5 100644
--- a/spec/services/spam/spam_verdict_service_spec.rb
+++ b/spec/services/spam/spam_verdict_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Spam::SpamVerdictService do
+RSpec.describe Spam::SpamVerdictService do
include_context 'includes Spam constants'
let(:fake_ip) { '1.2.3.4' }
@@ -27,7 +27,7 @@ describe Spam::SpamVerdictService do
before do
allow(service).to receive(:akismet_verdict).and_return(nil)
- allow(service).to receive(:spam_verdict_verdict).and_return(nil)
+ allow(service).to receive(:external_verdict).and_return(nil)
end
context 'if all services return nil' do
@@ -62,7 +62,7 @@ describe Spam::SpamVerdictService do
context 'and they are supported' do
before do
allow(service).to receive(:akismet_verdict).and_return(DISALLOW)
- allow(service).to receive(:spam_verdict).and_return(BLOCK_USER)
+ allow(service).to receive(:external_verdict).and_return(BLOCK_USER)
end
it 'renders the more restrictive verdict' do
@@ -73,18 +73,7 @@ describe Spam::SpamVerdictService do
context 'and one is supported' do
before do
allow(service).to receive(:akismet_verdict).and_return('nonsense')
- allow(service).to receive(:spam_verdict).and_return(BLOCK_USER)
- end
-
- it 'renders the more restrictive verdict' do
- expect(subject).to eq BLOCK_USER
- end
- end
-
- context 'and one is supported' do
- before do
- allow(service).to receive(:akismet_verdict).and_return('nonsense')
- allow(service).to receive(:spam_verdict).and_return(BLOCK_USER)
+ allow(service).to receive(:external_verdict).and_return(BLOCK_USER)
end
it 'renders the more restrictive verdict' do
@@ -95,7 +84,7 @@ describe Spam::SpamVerdictService do
context 'and none are supported' do
before do
allow(service).to receive(:akismet_verdict).and_return('nonsense')
- allow(service).to receive(:spam_verdict).and_return('rubbish')
+ allow(service).to receive(:external_verdict).and_return('rubbish')
end
it 'renders the more restrictive verdict' do
@@ -160,8 +149,8 @@ describe Spam::SpamVerdictService do
end
end
- describe '#spam_verdict' do
- subject { service.send(:spam_verdict) }
+ describe '#external_verdict' do
+ subject { service.send(:external_verdict) }
context 'if a Spam Check endpoint enabled and set to a URL' do
let(:spam_check_body) { {} }
@@ -192,8 +181,8 @@ describe Spam::SpamVerdictService do
context 'the verdict is an unexpected string' do
let(:verdict) { 'this is fine' }
- it 'returns nil' do
- expect(subject).to be_nil
+ it 'returns the string' do
+ expect(subject).to eq verdict
end
end
@@ -209,7 +198,7 @@ describe Spam::SpamVerdictService do
let(:verdict) { '' }
it 'returns nil' do
- expect(subject).to be_nil
+ expect(subject).to eq verdict
end
end
diff --git a/spec/services/submit_usage_ping_service_spec.rb b/spec/services/submit_usage_ping_service_spec.rb
index 981ea0dbec1..4885ef99c13 100644
--- a/spec/services/submit_usage_ping_service_spec.rb
+++ b/spec/services/submit_usage_ping_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe SubmitUsagePingService do
+RSpec.describe SubmitUsagePingService do
include StubRequests
include UsageDataHelpers
diff --git a/spec/services/submodules/update_service_spec.rb b/spec/services/submodules/update_service_spec.rb
index 47b31d4bcbf..e7f92d5ba28 100644
--- a/spec/services/submodules/update_service_spec.rb
+++ b/spec/services/submodules/update_service_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe Submodules::UpdateService do
+RSpec.describe Submodules::UpdateService do
let(:project) { create(:project, :repository) }
let(:repository) { project.repository }
let(:user) { create(:user, :commit_email) }
diff --git a/spec/services/suggestions/apply_service_spec.rb b/spec/services/suggestions/apply_service_spec.rb
index 678e2129181..aa9caf35987 100644
--- a/spec/services/suggestions/apply_service_spec.rb
+++ b/spec/services/suggestions/apply_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Suggestions::ApplyService do
+RSpec.describe Suggestions::ApplyService do
include ProjectForksHelper
def build_position(**optional_args)
diff --git a/spec/services/suggestions/create_service_spec.rb b/spec/services/suggestions/create_service_spec.rb
index d95f9e3349b..54e7c5cc127 100644
--- a/spec/services/suggestions/create_service_spec.rb
+++ b/spec/services/suggestions/create_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Suggestions::CreateService do
+RSpec.describe Suggestions::CreateService do
let(:project_with_repo) { create(:project, :repository) }
let(:merge_request) do
create(:merge_request, source_project: project_with_repo,
diff --git a/spec/services/suggestions/outdate_service_spec.rb b/spec/services/suggestions/outdate_service_spec.rb
index bcc627013d8..e8891f88548 100644
--- a/spec/services/suggestions/outdate_service_spec.rb
+++ b/spec/services/suggestions/outdate_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Suggestions::OutdateService do
+RSpec.describe Suggestions::OutdateService do
describe '#execute' do
let(:merge_request) { create(:merge_request) }
let(:project) { merge_request.target_project }
diff --git a/spec/services/system_hooks_service_spec.rb b/spec/services/system_hooks_service_spec.rb
index d72e5cc2b16..bdc40a92e91 100644
--- a/spec/services/system_hooks_service_spec.rb
+++ b/spec/services/system_hooks_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe SystemHooksService do
+RSpec.describe SystemHooksService do
let(:user) { create(:user) }
let(:project) { create(:project) }
let(:project_member) { create(:project_member) }
diff --git a/spec/services/system_note_service_spec.rb b/spec/services/system_note_service_spec.rb
index 66f9b5d092f..58fa772fefb 100644
--- a/spec/services/system_note_service_spec.rb
+++ b/spec/services/system_note_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe SystemNoteService do
+RSpec.describe SystemNoteService do
include Gitlab::Routing
include RepoHelpers
include AssetsHelpers
@@ -661,4 +661,48 @@ describe SystemNoteService do
described_class.design_discussion_added(discussion_note)
end
end
+
+ describe '.approve_mr' do
+ it 'calls MergeRequestsService' do
+ expect_next_instance_of(::SystemNotes::MergeRequestsService) do |service|
+ expect(service).to receive(:approve_mr)
+ end
+
+ described_class.approve_mr(noteable, author)
+ end
+ end
+
+ describe '.unapprove_mr' do
+ it 'calls MergeRequestsService' do
+ expect_next_instance_of(::SystemNotes::MergeRequestsService) do |service|
+ expect(service).to receive(:unapprove_mr)
+ end
+
+ described_class.unapprove_mr(noteable, author)
+ end
+ end
+
+ describe '.change_alert_status' do
+ let(:alert) { build(:alert_management_alert) }
+
+ it 'calls AlertManagementService' do
+ expect_next_instance_of(SystemNotes::AlertManagementService) do |service|
+ expect(service).to receive(:change_alert_status).with(alert)
+ end
+
+ described_class.change_alert_status(alert, author)
+ end
+ end
+
+ describe '.new_alert_issue' do
+ let(:alert) { build(:alert_management_alert, :with_issue) }
+
+ it 'calls AlertManagementService' do
+ expect_next_instance_of(SystemNotes::AlertManagementService) do |service|
+ expect(service).to receive(:new_alert_issue).with(alert, alert.issue)
+ end
+
+ described_class.new_alert_issue(alert, alert.issue, author)
+ end
+ end
end
diff --git a/spec/services/system_notes/alert_management_service_spec.rb b/spec/services/system_notes/alert_management_service_spec.rb
new file mode 100644
index 00000000000..403763d5fd9
--- /dev/null
+++ b/spec/services/system_notes/alert_management_service_spec.rb
@@ -0,0 +1,35 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe ::SystemNotes::AlertManagementService do
+ let_it_be(:author) { create(:user) }
+ let_it_be(:project) { create(:project, :repository) }
+ let_it_be(:noteable) { create(:alert_management_alert, :with_issue, :acknowledged, project: project) }
+
+ describe '#change_alert_status' do
+ subject { described_class.new(noteable: noteable, project: project, author: author).change_alert_status(noteable) }
+
+ it_behaves_like 'a system note' do
+ let(:action) { 'status' }
+ end
+
+ it 'has the appropriate message' do
+ expect(subject.note).to eq("changed the status to **Acknowledged**")
+ end
+ end
+
+ describe '#new_alert_issue' do
+ let_it_be(:issue) { noteable.issue }
+
+ subject { described_class.new(noteable: noteable, project: project, author: author).new_alert_issue(noteable, issue) }
+
+ it_behaves_like 'a system note' do
+ let(:action) { 'alert_issue_added' }
+ end
+
+ it 'has the appropriate message' do
+ expect(subject.note).to eq("created issue #{issue.to_reference(project)} for this alert")
+ end
+ end
+end
diff --git a/spec/services/system_notes/base_service_spec.rb b/spec/services/system_notes/base_service_spec.rb
index 96788b05829..efb165f8e4c 100644
--- a/spec/services/system_notes/base_service_spec.rb
+++ b/spec/services/system_notes/base_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe SystemNotes::BaseService do
+RSpec.describe SystemNotes::BaseService do
let(:noteable) { double }
let(:project) { double }
let(:author) { double }
diff --git a/spec/services/system_notes/commit_service_spec.rb b/spec/services/system_notes/commit_service_spec.rb
index 5839a17e4a0..bd6b3ec953a 100644
--- a/spec/services/system_notes/commit_service_spec.rb
+++ b/spec/services/system_notes/commit_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe SystemNotes::CommitService do
+RSpec.describe SystemNotes::CommitService do
let_it_be(:group) { create(:group) }
let_it_be(:project) { create(:project, :repository, group: group) }
let_it_be(:author) { create(:user) }
diff --git a/spec/services/system_notes/design_management_service_spec.rb b/spec/services/system_notes/design_management_service_spec.rb
index 08511e62341..6267ad2aaad 100644
--- a/spec/services/system_notes/design_management_service_spec.rb
+++ b/spec/services/system_notes/design_management_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe SystemNotes::DesignManagementService do
+RSpec.describe SystemNotes::DesignManagementService do
let(:project) { create(:project) }
let(:issue) { create(:issue, project: project) }
diff --git a/spec/services/system_notes/issuables_service_spec.rb b/spec/services/system_notes/issuables_service_spec.rb
index c3b3c877583..1b5b26d90da 100644
--- a/spec/services/system_notes/issuables_service_spec.rb
+++ b/spec/services/system_notes/issuables_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ::SystemNotes::IssuablesService do
+RSpec.describe ::SystemNotes::IssuablesService do
include ProjectForksHelper
let_it_be(:group) { create(:group) }
@@ -161,7 +161,9 @@ describe ::SystemNotes::IssuablesService do
let(:status) { 'reopened' }
let(:source) { nil }
- it { is_expected.to be_nil }
+ it 'does not change note count' do
+ expect { subject }.not_to change { Note.count }
+ end
end
context 'with status reopened' do
@@ -660,25 +662,67 @@ describe ::SystemNotes::IssuablesService do
describe '#close_after_error_tracking_resolve' do
subject { service.close_after_error_tracking_resolve }
- it_behaves_like 'a system note' do
- let(:action) { 'closed' }
+ context 'when state tracking is enabled' do
+ before do
+ stub_feature_flags(track_resource_state_change_events: true)
+ end
+
+ it 'creates the expected state event' do
+ subject
+
+ event = ResourceStateEvent.last
+
+ expect(event.close_after_error_tracking_resolve).to eq(true)
+ expect(event.state).to eq('closed')
+ end
end
- it 'creates the expected system note' do
- expect(subject.note)
+ context 'when state tracking is disabled' do
+ before do
+ stub_feature_flags(track_resource_state_change_events: false)
+ end
+
+ it_behaves_like 'a system note' do
+ let(:action) { 'closed' }
+ end
+
+ it 'creates the expected system note' do
+ expect(subject.note)
.to eq('resolved the corresponding error and closed the issue.')
+ end
end
end
describe '#auto_resolve_prometheus_alert' do
subject { service.auto_resolve_prometheus_alert }
- it_behaves_like 'a system note' do
- let(:action) { 'closed' }
+ context 'when state tracking is enabled' do
+ before do
+ stub_feature_flags(track_resource_state_change_events: true)
+ end
+
+ it 'creates the expected state event' do
+ subject
+
+ event = ResourceStateEvent.last
+
+ expect(event.close_auto_resolve_prometheus_alert).to eq(true)
+ expect(event.state).to eq('closed')
+ end
end
- it 'creates the expected system note' do
- expect(subject.note).to eq('automatically closed this issue because the alert resolved.')
+ context 'when state tracking is disabled' do
+ before do
+ stub_feature_flags(track_resource_state_change_events: false)
+ end
+
+ it_behaves_like 'a system note' do
+ let(:action) { 'closed' }
+ end
+
+ it 'creates the expected system note' do
+ expect(subject.note).to eq('automatically closed this issue because the alert resolved.')
+ end
end
end
end
diff --git a/spec/services/system_notes/merge_requests_service_spec.rb b/spec/services/system_notes/merge_requests_service_spec.rb
index 13d6367a585..067e1cef64d 100644
--- a/spec/services/system_notes/merge_requests_service_spec.rb
+++ b/spec/services/system_notes/merge_requests_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ::SystemNotes::MergeRequestsService do
+RSpec.describe ::SystemNotes::MergeRequestsService do
include Gitlab::Routing
let_it_be(:group) { create(:group) }
@@ -52,8 +52,8 @@ describe ::SystemNotes::MergeRequestsService do
end
describe '.handle_merge_request_wip' do
- context 'adding wip note' do
- let(:noteable) { create(:merge_request, source_project: project, title: 'WIP Lorem ipsum') }
+ context 'adding draft note' do
+ let(:noteable) { create(:merge_request, source_project: project, title: 'Draft: Lorem ipsum') }
subject { service.handle_merge_request_wip }
@@ -261,4 +261,18 @@ describe ::SystemNotes::MergeRequestsService do
expect(subject.commit_id).to eq(commit_sha)
end
end
+
+ describe '#approve_mr' do
+ subject { described_class.new(noteable: noteable, project: project, author: author).approve_mr }
+
+ it_behaves_like 'a system note' do
+ let(:action) { 'approved' }
+ end
+
+ context 'when merge request approved' do
+ it 'sets the note text' do
+ expect(subject.note).to eq "approved this merge request"
+ end
+ end
+ end
end
diff --git a/spec/services/system_notes/time_tracking_service_spec.rb b/spec/services/system_notes/time_tracking_service_spec.rb
index 7e3e6a75cdf..f671e66cdcd 100644
--- a/spec/services/system_notes/time_tracking_service_spec.rb
+++ b/spec/services/system_notes/time_tracking_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ::SystemNotes::TimeTrackingService do
+RSpec.describe ::SystemNotes::TimeTrackingService do
let_it_be(:author) { create(:user) }
let_it_be(:project) { create(:project, :repository) }
diff --git a/spec/services/system_notes/zoom_service_spec.rb b/spec/services/system_notes/zoom_service_spec.rb
index 435cdb5748e..986324c9664 100644
--- a/spec/services/system_notes/zoom_service_spec.rb
+++ b/spec/services/system_notes/zoom_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ::SystemNotes::ZoomService do
+RSpec.describe ::SystemNotes::ZoomService do
let_it_be(:project) { create(:project, :repository) }
let_it_be(:author) { create(:user) }
diff --git a/spec/services/tags/create_service_spec.rb b/spec/services/tags/create_service_spec.rb
index e505960d3c7..b1c6623308e 100644
--- a/spec/services/tags/create_service_spec.rb
+++ b/spec/services/tags/create_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Tags::CreateService do
+RSpec.describe Tags::CreateService do
let(:project) { create(:project, :repository) }
let(:repository) { project.repository }
let(:user) { create(:user) }
diff --git a/spec/services/tags/destroy_service_spec.rb b/spec/services/tags/destroy_service_spec.rb
index b46bd77eafe..6160f337552 100644
--- a/spec/services/tags/destroy_service_spec.rb
+++ b/spec/services/tags/destroy_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Tags::DestroyService do
+RSpec.describe Tags::DestroyService do
let(:project) { create(:project, :repository) }
let(:repository) { project.repository }
let(:user) { create(:user) }
@@ -11,6 +11,10 @@ describe Tags::DestroyService do
describe '#execute' do
subject { service.execute(tag_name) }
+ before do
+ allow(Ci::RefDeleteUnlockArtifactsWorker).to receive(:perform_async)
+ end
+
it 'removes the tag' do
expect(repository).to receive(:before_remove_tag)
expect(service).to receive(:success)
@@ -18,6 +22,12 @@ describe Tags::DestroyService do
service.execute('v1.1.0')
end
+ it 'calls the RefDeleteUnlockArtifactsWorker' do
+ expect(Ci::RefDeleteUnlockArtifactsWorker).to receive(:perform_async).with(project.id, user.id, 'refs/tags/v1.1.0')
+
+ service.execute('v1.1.0')
+ end
+
context 'when there is an associated release on the tag' do
let(:tag) { repository.tags.first }
let(:tag_name) { tag.name }
diff --git a/spec/services/task_list_toggle_service_spec.rb b/spec/services/task_list_toggle_service_spec.rb
index 82a5446dcb8..276f2ae435e 100644
--- a/spec/services/task_list_toggle_service_spec.rb
+++ b/spec/services/task_list_toggle_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe TaskListToggleService do
+RSpec.describe TaskListToggleService do
let(:markdown) do
<<-EOT.strip_heredoc
* [ ] Task 1
diff --git a/spec/services/terraform/remote_state_handler_spec.rb b/spec/services/terraform/remote_state_handler_spec.rb
index f4e1831b2e8..c47367feb14 100644
--- a/spec/services/terraform/remote_state_handler_spec.rb
+++ b/spec/services/terraform/remote_state_handler_spec.rb
@@ -2,9 +2,12 @@
require 'spec_helper'
-describe Terraform::RemoteStateHandler do
+RSpec.describe Terraform::RemoteStateHandler do
let_it_be(:project) { create(:project) }
- let_it_be(:user) { create(:user) }
+ let_it_be(:developer) { create(:user, developer_projects: [project]) }
+ let_it_be(:maintainer) { create(:user, maintainer_projects: [project]) }
+
+ let_it_be(:user) { maintainer }
describe '#find_with_lock' do
context 'without a state name' do
@@ -34,33 +37,6 @@ describe Terraform::RemoteStateHandler do
end
end
- describe '#create_or_find!' do
- it 'requires passing a state name' do
- handler = described_class.new(project, user)
-
- expect { handler.create_or_find! }.to raise_error(ArgumentError)
- end
-
- it 'allows to create states with same name in different projects' do
- project_b = create(:project)
-
- state_a = described_class.new(project, user, name: 'my-state').create_or_find!
- state_b = described_class.new(project_b, user, name: 'my-state').create_or_find!
-
- expect(state_a).to be_persisted
- expect(state_b).to be_persisted
- expect(state_a.id).not_to eq state_b.id
- end
-
- it 'loads the same state upon subsequent call in the project scope' do
- state_a = described_class.new(project, user, name: 'my-state').create_or_find!
- state_b = described_class.new(project, user, name: 'my-state').create_or_find!
-
- expect(state_a).to be_persisted
- expect(state_a.id).to eq state_b.id
- end
- end
-
context 'when state locking is not being used' do
subject { described_class.new(project, user, name: 'my-state') }
@@ -74,7 +50,7 @@ describe Terraform::RemoteStateHandler do
end
it 'returns the state object itself' do
- state = subject.create_or_find!
+ state = subject.handle_with_lock
expect(state.name).to eq 'my-state'
end
@@ -89,10 +65,9 @@ describe Terraform::RemoteStateHandler do
context 'when using locking' do
describe '#handle_with_lock' do
- it 'handles a locked state using exclusive read lock' do
- handler = described_class
- .new(project, user, name: 'new-state', lock_id: 'abc-abc')
+ subject(:handler) { described_class.new(project, user, name: 'new-state', lock_id: 'abc-abc') }
+ it 'handles a locked state using exclusive read lock' do
handler.lock!
state = handler.handle_with_lock do |state|
@@ -101,20 +76,35 @@ describe Terraform::RemoteStateHandler do
expect(state.name).to eq 'new-name'
end
- end
- it 'raises exception if lock has not been acquired before' do
- handler = described_class
- .new(project, user, name: 'new-state', lock_id: 'abc-abc')
+ it 'raises exception if lock has not been acquired before' do
+ expect { handler.handle_with_lock }
+ .to raise_error(described_class::StateLockedError)
+ end
+
+ context 'user does not have permission to modify state' do
+ let(:user) { developer }
- expect { handler.handle_with_lock }
- .to raise_error(described_class::StateLockedError)
+ it 'raises an exception' do
+ expect { handler.handle_with_lock }
+ .to raise_error(described_class::UnauthorizedError)
+ end
+ end
end
describe '#lock!' do
- it 'allows to lock state if it does not exist yet' do
- handler = described_class.new(project, user, name: 'new-state', lock_id: 'abc-abc')
+ let(:lock_id) { 'abc-abc' }
+
+ subject(:handler) do
+ described_class.new(
+ project,
+ user,
+ name: 'new-state',
+ lock_id: lock_id
+ )
+ end
+ it 'allows to lock state if it does not exist yet' do
state = handler.lock!
expect(state).to be_persisted
@@ -122,22 +112,61 @@ describe Terraform::RemoteStateHandler do
end
it 'allows to lock state if it exists and is not locked' do
- state = described_class.new(project, user, name: 'new-state').create_or_find!
- handler = described_class.new(project, user, name: 'new-state', lock_id: 'abc-abc')
+ state = create(:terraform_state, project: project, name: 'new-state')
handler.lock!
- expect(state.reload.lock_xid).to eq 'abc-abc'
+ expect(state.reload.lock_xid).to eq lock_id
expect(state).to be_locked
end
it 'raises an exception when trying to unlocked state locked by someone else' do
- described_class.new(project, user, name: 'new-state', lock_id: 'abc-abc').lock!
-
- handler = described_class.new(project, user, name: 'new-state', lock_id: '12a-23f')
+ described_class.new(project, user, name: 'new-state', lock_id: '12a-23f').lock!
expect { handler.lock! }.to raise_error(described_class::StateLockedError)
end
end
+
+ describe '#unlock!' do
+ let(:lock_id) { 'abc-abc' }
+
+ subject(:handler) do
+ described_class.new(
+ project,
+ user,
+ name: 'new-state',
+ lock_id: lock_id
+ )
+ end
+
+ before do
+ create(:terraform_state, :locked, project: project, name: 'new-state', lock_xid: 'abc-abc')
+ end
+
+ it 'unlocks the state' do
+ state = handler.unlock!
+
+ expect(state.lock_xid).to be_nil
+ end
+
+ context 'with no lock ID (force-unlock)' do
+ let(:lock_id) { }
+
+ it 'unlocks the state' do
+ state = handler.unlock!
+
+ expect(state.lock_xid).to be_nil
+ end
+ end
+
+ context 'with different lock ID' do
+ let(:lock_id) { 'other' }
+
+ it 'raises an exception' do
+ expect { handler.unlock! }
+ .to raise_error(described_class::StateLockedError)
+ end
+ end
+ end
end
end
diff --git a/spec/services/test_hooks/project_service_spec.rb b/spec/services/test_hooks/project_service_spec.rb
index 3c5bc0d85f2..e4cc3a2d652 100644
--- a/spec/services/test_hooks/project_service_spec.rb
+++ b/spec/services/test_hooks/project_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe TestHooks::ProjectService do
+RSpec.describe TestHooks::ProjectService do
let(:current_user) { create(:user) }
describe '#execute' do
diff --git a/spec/services/test_hooks/system_service_spec.rb b/spec/services/test_hooks/system_service_spec.rb
index 8a86b14a2a1..34dd2173b09 100644
--- a/spec/services/test_hooks/system_service_spec.rb
+++ b/spec/services/test_hooks/system_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe TestHooks::SystemService do
+RSpec.describe TestHooks::SystemService do
let(:current_user) { create(:user) }
describe '#execute' do
diff --git a/spec/services/todo_service_spec.rb b/spec/services/todo_service_spec.rb
index f6e1608acbe..b187025eb11 100644
--- a/spec/services/todo_service_spec.rb
+++ b/spec/services/todo_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe TodoService do
+RSpec.describe TodoService do
let(:author) { create(:user) }
let(:assignee) { create(:user) }
let(:non_member) { create(:user) }
diff --git a/spec/services/todos/destroy/confidential_issue_service_spec.rb b/spec/services/todos/destroy/confidential_issue_service_spec.rb
index 9f7e656f7d3..ddce45e7968 100644
--- a/spec/services/todos/destroy/confidential_issue_service_spec.rb
+++ b/spec/services/todos/destroy/confidential_issue_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Todos::Destroy::ConfidentialIssueService do
+RSpec.describe Todos::Destroy::ConfidentialIssueService do
let(:project) { create(:project, :public) }
let(:user) { create(:user) }
let(:author) { create(:user) }
diff --git a/spec/services/todos/destroy/entity_leave_service_spec.rb b/spec/services/todos/destroy/entity_leave_service_spec.rb
index 45e3bf381fb..ccafe3bb7a8 100644
--- a/spec/services/todos/destroy/entity_leave_service_spec.rb
+++ b/spec/services/todos/destroy/entity_leave_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Todos::Destroy::EntityLeaveService do
+RSpec.describe Todos::Destroy::EntityLeaveService do
let(:group) { create(:group, :private) }
let(:project) { create(:project, group: group) }
let(:user) { create(:user) }
diff --git a/spec/services/todos/destroy/group_private_service_spec.rb b/spec/services/todos/destroy/group_private_service_spec.rb
index 7dd495847b3..30d02cb7400 100644
--- a/spec/services/todos/destroy/group_private_service_spec.rb
+++ b/spec/services/todos/destroy/group_private_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Todos::Destroy::GroupPrivateService do
+RSpec.describe Todos::Destroy::GroupPrivateService do
let(:group) { create(:group, :public) }
let(:project) { create(:project, group: group) }
let(:user) { create(:user) }
diff --git a/spec/services/todos/destroy/private_features_service_spec.rb b/spec/services/todos/destroy/private_features_service_spec.rb
index dfe9f42e8b1..6dbd7574b80 100644
--- a/spec/services/todos/destroy/private_features_service_spec.rb
+++ b/spec/services/todos/destroy/private_features_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Todos::Destroy::PrivateFeaturesService do
+RSpec.describe Todos::Destroy::PrivateFeaturesService do
let(:project) { create(:project, :public) }
let(:user) { create(:user) }
let(:another_user) { create(:user) }
diff --git a/spec/services/todos/destroy/project_private_service_spec.rb b/spec/services/todos/destroy/project_private_service_spec.rb
index 7c0c76b6c29..1d1c010535d 100644
--- a/spec/services/todos/destroy/project_private_service_spec.rb
+++ b/spec/services/todos/destroy/project_private_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Todos::Destroy::ProjectPrivateService do
+RSpec.describe Todos::Destroy::ProjectPrivateService do
let(:group) { create(:group, :public) }
let(:project) { create(:project, :public, group: group) }
let(:user) { create(:user) }
diff --git a/spec/services/update_container_registry_info_service_spec.rb b/spec/services/update_container_registry_info_service_spec.rb
new file mode 100644
index 00000000000..740e53b0472
--- /dev/null
+++ b/spec/services/update_container_registry_info_service_spec.rb
@@ -0,0 +1,115 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe UpdateContainerRegistryInfoService do
+ let_it_be(:application_settings) { Gitlab::CurrentSettings }
+ let_it_be(:api_url) { 'http://registry.gitlab' }
+
+ describe '#execute' do
+ before do
+ stub_access_token
+ stub_container_registry_config(enabled: true, api_url: api_url)
+ end
+
+ subject { described_class.new.execute }
+
+ shared_examples 'invalid config' do
+ it 'does not update the application settings' do
+ expect(application_settings).not_to receive(:update!)
+
+ subject
+ end
+
+ it 'does not raise an error' do
+ expect { subject }.not_to raise_error
+ end
+ end
+
+ context 'when container registry is disabled' do
+ before do
+ stub_container_registry_config(enabled: false)
+ end
+
+ it_behaves_like 'invalid config'
+ end
+
+ context 'when container registry api_url is blank' do
+ before do
+ stub_container_registry_config(api_url: '')
+ end
+
+ it_behaves_like 'invalid config'
+ end
+
+ context 'when creating a registry client instance' do
+ let(:token) { 'foo' }
+ let(:client) { ContainerRegistry::Client.new(api_url, token: token) }
+
+ before do
+ stub_registry_info({})
+ end
+
+ it 'uses a token with no access permissions' do
+ expect(Auth::ContainerRegistryAuthenticationService)
+ .to receive(:access_token).with([], []).and_return(token)
+ expect(ContainerRegistry::Client)
+ .to receive(:new).with(api_url, token: token).and_return(client)
+
+ subject
+ end
+ end
+
+ context 'when unabled to detect the container registry type' do
+ it 'sets the application settings to their defaults' do
+ stub_registry_info({})
+
+ subject
+
+ application_settings.reload
+ expect(application_settings.container_registry_vendor).to be_blank
+ expect(application_settings.container_registry_version).to be_blank
+ expect(application_settings.container_registry_features).to eq([])
+ end
+ end
+
+ context 'when able to detect the container registry type' do
+ context 'when using the GitLab container registry' do
+ it 'updates application settings accordingly' do
+ stub_registry_info(vendor: 'gitlab', version: '2.9.1-gitlab', features: %w[a,b,c])
+
+ subject
+
+ application_settings.reload
+ expect(application_settings.container_registry_vendor).to eq('gitlab')
+ expect(application_settings.container_registry_version).to eq('2.9.1-gitlab')
+ expect(application_settings.container_registry_features).to eq(%w[a,b,c])
+ end
+ end
+
+ context 'when using a third-party container registry' do
+ it 'updates application settings accordingly' do
+ stub_registry_info(vendor: 'other', version: nil, features: nil)
+
+ subject
+
+ application_settings.reload
+ expect(application_settings.container_registry_vendor).to eq('other')
+ expect(application_settings.container_registry_version).to be_blank
+ expect(application_settings.container_registry_features).to eq([])
+ end
+ end
+ end
+ end
+
+ def stub_access_token
+ allow(Auth::ContainerRegistryAuthenticationService)
+ .to receive(:access_token).with([], []).and_return('foo')
+ end
+
+ def stub_registry_info(output)
+ allow_next_instance_of(ContainerRegistry::Client) do |client|
+ allow(client).to receive(:registry_info).and_return(output)
+ end
+ end
+end
diff --git a/spec/services/update_merge_request_metrics_service_spec.rb b/spec/services/update_merge_request_metrics_service_spec.rb
index 1aaf5e712f9..a07fcee91e4 100644
--- a/spec/services/update_merge_request_metrics_service_spec.rb
+++ b/spec/services/update_merge_request_metrics_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe MergeRequestMetricsService do
+RSpec.describe MergeRequestMetricsService do
let(:metrics) { create(:merge_request).metrics }
describe '#merge' do
diff --git a/spec/services/upload_service_spec.rb b/spec/services/upload_service_spec.rb
index 504e61f9903..89a28e6a098 100644
--- a/spec/services/upload_service_spec.rb
+++ b/spec/services/upload_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe UploadService do
+RSpec.describe UploadService do
describe 'File service' do
before do
@user = create(:user)
diff --git a/spec/services/user_project_access_changed_service_spec.rb b/spec/services/user_project_access_changed_service_spec.rb
index e5ecdd123f7..070782992e7 100644
--- a/spec/services/user_project_access_changed_service_spec.rb
+++ b/spec/services/user_project_access_changed_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe UserProjectAccessChangedService do
+RSpec.describe UserProjectAccessChangedService do
describe '#execute' do
it 'schedules the user IDs' do
expect(AuthorizedProjectsWorker).to receive(:bulk_perform_and_wait)
diff --git a/spec/services/users/activity_service_spec.rb b/spec/services/users/activity_service_spec.rb
index 5f068a2033c..4bbf6a2bcb8 100644
--- a/spec/services/users/activity_service_spec.rb
+++ b/spec/services/users/activity_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Users::ActivityService do
+RSpec.describe Users::ActivityService do
include ExclusiveLeaseHelpers
let(:user) { create(:user, last_activity_on: last_activity_on) }
diff --git a/spec/services/users/block_service_spec.rb b/spec/services/users/block_service_spec.rb
index c3a65a08c0d..e170a5494aa 100644
--- a/spec/services/users/block_service_spec.rb
+++ b/spec/services/users/block_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Users::BlockService do
+RSpec.describe Users::BlockService do
let(:current_user) { create(:admin) }
subject(:service) { described_class.new(current_user) }
diff --git a/spec/services/users/build_service_spec.rb b/spec/services/users/build_service_spec.rb
index 7588be833ae..c14fdb35bfa 100644
--- a/spec/services/users/build_service_spec.rb
+++ b/spec/services/users/build_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Users::BuildService do
+RSpec.describe Users::BuildService do
describe '#execute' do
let(:params) do
{ name: 'John Doe', username: 'jduser', email: 'jd@example.com', password: 'mydummypass' }
diff --git a/spec/services/users/create_service_spec.rb b/spec/services/users/create_service_spec.rb
index c783a1403df..69d2d6ca9ff 100644
--- a/spec/services/users/create_service_spec.rb
+++ b/spec/services/users/create_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Users::CreateService do
+RSpec.describe Users::CreateService do
describe '#execute' do
let(:admin_user) { create(:admin) }
diff --git a/spec/services/users/destroy_service_spec.rb b/spec/services/users/destroy_service_spec.rb
index 3db5e66fe05..ff919257b3c 100644
--- a/spec/services/users/destroy_service_spec.rb
+++ b/spec/services/users/destroy_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Users::DestroyService do
+RSpec.describe Users::DestroyService do
describe "Deletes a user and all their personal projects" do
let!(:user) { create(:user) }
let!(:admin) { create(:admin) }
diff --git a/spec/services/users/keys_count_service_spec.rb b/spec/services/users/keys_count_service_spec.rb
index 83af051e728..aff267cce5e 100644
--- a/spec/services/users/keys_count_service_spec.rb
+++ b/spec/services/users/keys_count_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Users::KeysCountService, :use_clean_rails_memory_store_caching do
+RSpec.describe Users::KeysCountService, :use_clean_rails_memory_store_caching do
let(:user) { create(:user) }
subject { described_class.new(user) }
diff --git a/spec/services/users/last_push_event_service_spec.rb b/spec/services/users/last_push_event_service_spec.rb
index 424e9e2f8ef..5b755db407f 100644
--- a/spec/services/users/last_push_event_service_spec.rb
+++ b/spec/services/users/last_push_event_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Users::LastPushEventService do
+RSpec.describe Users::LastPushEventService do
let(:user) { build(:user, id: 1) }
let(:project) { build(:project, id: 2) }
let(:event) { build(:push_event, id: 3, author: user, project: project) }
diff --git a/spec/services/users/migrate_to_ghost_user_service_spec.rb b/spec/services/users/migrate_to_ghost_user_service_spec.rb
index c2a793b2368..c9c8f9a74d3 100644
--- a/spec/services/users/migrate_to_ghost_user_service_spec.rb
+++ b/spec/services/users/migrate_to_ghost_user_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Users::MigrateToGhostUserService do
+RSpec.describe Users::MigrateToGhostUserService do
let!(:user) { create(:user) }
let!(:project) { create(:project, :repository) }
let(:service) { described_class.new(user) }
diff --git a/spec/services/users/refresh_authorized_projects_service_spec.rb b/spec/services/users/refresh_authorized_projects_service_spec.rb
index d7ba7f5f69e..e45cb05a6c5 100644
--- a/spec/services/users/refresh_authorized_projects_service_spec.rb
+++ b/spec/services/users/refresh_authorized_projects_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Users::RefreshAuthorizedProjectsService do
+RSpec.describe Users::RefreshAuthorizedProjectsService do
include ExclusiveLeaseHelpers
# We're using let! here so that any expectations for the service class are not
diff --git a/spec/services/users/repair_ldap_blocked_service_spec.rb b/spec/services/users/repair_ldap_blocked_service_spec.rb
index bf80cc79d62..b33dcb92f45 100644
--- a/spec/services/users/repair_ldap_blocked_service_spec.rb
+++ b/spec/services/users/repair_ldap_blocked_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Users::RepairLdapBlockedService do
+RSpec.describe Users::RepairLdapBlockedService do
let(:user) { create(:omniauth_user, provider: 'ldapmain', state: 'ldap_blocked') }
let(:identity) { user.ldap_identity }
diff --git a/spec/services/users/respond_to_terms_service_spec.rb b/spec/services/users/respond_to_terms_service_spec.rb
index d840706e8a5..1997dcd0e04 100644
--- a/spec/services/users/respond_to_terms_service_spec.rb
+++ b/spec/services/users/respond_to_terms_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Users::RespondToTermsService do
+RSpec.describe Users::RespondToTermsService do
let(:user) { create(:user) }
let(:term) { create(:term) }
diff --git a/spec/services/users/set_status_service_spec.rb b/spec/services/users/set_status_service_spec.rb
index 554f5e9dc5e..54489adceb0 100644
--- a/spec/services/users/set_status_service_spec.rb
+++ b/spec/services/users/set_status_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Users::SetStatusService do
+RSpec.describe Users::SetStatusService do
let(:current_user) { create(:user) }
subject(:service) { described_class.new(current_user, params) }
diff --git a/spec/services/users/signup_service_spec.rb b/spec/services/users/signup_service_spec.rb
index 7d3cd614142..cc234309817 100644
--- a/spec/services/users/signup_service_spec.rb
+++ b/spec/services/users/signup_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Users::SignupService do
+RSpec.describe Users::SignupService do
let(:user) { create(:user, setup_for_company: true) }
describe '#execute' do
diff --git a/spec/services/users/update_canonical_email_service_spec.rb b/spec/services/users/update_canonical_email_service_spec.rb
index 68ba1b75b6c..1dead13d338 100644
--- a/spec/services/users/update_canonical_email_service_spec.rb
+++ b/spec/services/users/update_canonical_email_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Users::UpdateCanonicalEmailService do
+RSpec.describe Users::UpdateCanonicalEmailService do
let(:other_email) { "differentaddress@includeddomain.com" }
before do
diff --git a/spec/services/users/update_highest_member_role_service_spec.rb b/spec/services/users/update_highest_member_role_service_spec.rb
index 8063abffc2a..89ddd635bb6 100644
--- a/spec/services/users/update_highest_member_role_service_spec.rb
+++ b/spec/services/users/update_highest_member_role_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Users::UpdateHighestMemberRoleService do
+RSpec.describe Users::UpdateHighestMemberRoleService do
let(:user) { create(:user) }
let(:execute_service) { described_class.new(user).execute }
diff --git a/spec/services/users/update_service_spec.rb b/spec/services/users/update_service_spec.rb
index 8e13e7d9c0c..274c44394f3 100644
--- a/spec/services/users/update_service_spec.rb
+++ b/spec/services/users/update_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Users::UpdateService do
+RSpec.describe Users::UpdateService do
let(:user) { create(:user) }
describe '#execute' do
diff --git a/spec/services/verify_pages_domain_service_spec.rb b/spec/services/verify_pages_domain_service_spec.rb
index 3f08ae84c14..29ad85a16ce 100644
--- a/spec/services/verify_pages_domain_service_spec.rb
+++ b/spec/services/verify_pages_domain_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe VerifyPagesDomainService do
+RSpec.describe VerifyPagesDomainService do
using RSpec::Parameterized::TableSyntax
include EmailHelpers
diff --git a/spec/services/web_hook_service_spec.rb b/spec/services/web_hook_service_spec.rb
index 4a917ecdbb5..2be481c5b62 100644
--- a/spec/services/web_hook_service_spec.rb
+++ b/spec/services/web_hook_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe WebHookService do
+RSpec.describe WebHookService do
include StubRequests
let(:project) { create(:project) }
diff --git a/spec/services/wiki_pages/base_service_spec.rb b/spec/services/wiki_pages/base_service_spec.rb
index fede86a5192..6ccc796014c 100644
--- a/spec/services/wiki_pages/base_service_spec.rb
+++ b/spec/services/wiki_pages/base_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe WikiPages::BaseService do
+RSpec.describe WikiPages::BaseService do
let(:project) { double('project') }
let(:user) { double('user') }
diff --git a/spec/services/wiki_pages/create_service_spec.rb b/spec/services/wiki_pages/create_service_spec.rb
index 2a17805110e..44b57088319 100644
--- a/spec/services/wiki_pages/create_service_spec.rb
+++ b/spec/services/wiki_pages/create_service_spec.rb
@@ -2,6 +2,6 @@
require 'spec_helper'
-describe WikiPages::CreateService do
+RSpec.describe WikiPages::CreateService do
it_behaves_like 'WikiPages::CreateService#execute', :project
end
diff --git a/spec/services/wiki_pages/destroy_service_spec.rb b/spec/services/wiki_pages/destroy_service_spec.rb
index b6fee1fd896..9384ea1cd43 100644
--- a/spec/services/wiki_pages/destroy_service_spec.rb
+++ b/spec/services/wiki_pages/destroy_service_spec.rb
@@ -2,6 +2,6 @@
require 'spec_helper'
-describe WikiPages::DestroyService do
+RSpec.describe WikiPages::DestroyService do
it_behaves_like 'WikiPages::DestroyService#execute', :project
end
diff --git a/spec/services/wiki_pages/event_create_service_spec.rb b/spec/services/wiki_pages/event_create_service_spec.rb
index c725c67d7a7..abf3bcb4c4d 100644
--- a/spec/services/wiki_pages/event_create_service_spec.rb
+++ b/spec/services/wiki_pages/event_create_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe WikiPages::EventCreateService do
+RSpec.describe WikiPages::EventCreateService do
let_it_be(:project) { create(:project) }
let_it_be(:user) { create(:user) }
@@ -14,21 +14,6 @@ describe WikiPages::EventCreateService do
let(:action) { :created }
let(:response) { subject.execute(slug, page, action) }
- context 'feature flag is not enabled' do
- before do
- stub_feature_flags(wiki_events: false)
- end
-
- it 'does not error' do
- expect(response).to be_success
- .and have_attributes(message: /No event created/)
- end
-
- it 'does not create an event' do
- expect { response }.not_to change(Event, :count)
- end
- end
-
context 'the user is nil' do
subject { described_class.new(nil) }
diff --git a/spec/services/wiki_pages/update_service_spec.rb b/spec/services/wiki_pages/update_service_spec.rb
index ac629a96f9a..33ac98e764d 100644
--- a/spec/services/wiki_pages/update_service_spec.rb
+++ b/spec/services/wiki_pages/update_service_spec.rb
@@ -2,6 +2,6 @@
require 'spec_helper'
-describe WikiPages::UpdateService do
+RSpec.describe WikiPages::UpdateService do
it_behaves_like 'WikiPages::UpdateService#execute', :project
end
diff --git a/spec/services/wikis/create_attachment_service_spec.rb b/spec/services/wikis/create_attachment_service_spec.rb
index 4adfaa24874..50cb9ac111c 100644
--- a/spec/services/wikis/create_attachment_service_spec.rb
+++ b/spec/services/wikis/create_attachment_service_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe Wikis::CreateAttachmentService do
+RSpec.describe Wikis::CreateAttachmentService do
let(:container) { create(:project, :wiki_repo) }
let(:user) { create(:user) }
let(:file_name) { 'filename.txt' }
diff --git a/spec/services/x509_certificate_revoke_service_spec.rb b/spec/services/x509_certificate_revoke_service_spec.rb
index c2b2576904c..adad3281c13 100644
--- a/spec/services/x509_certificate_revoke_service_spec.rb
+++ b/spec/services/x509_certificate_revoke_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe X509CertificateRevokeService do
+RSpec.describe X509CertificateRevokeService do
describe '#execute' do
let(:service) { described_class.new }
let!(:x509_signature_1) { create(:x509_commit_signature, x509_certificate: x509_certificate, verification_status: :verified ) }
diff --git a/spec/sidekiq/cron/job_gem_dependency_spec.rb b/spec/sidekiq/cron/job_gem_dependency_spec.rb
index 20347b4d306..2924c86c8d0 100644
--- a/spec/sidekiq/cron/job_gem_dependency_spec.rb
+++ b/spec/sidekiq/cron/job_gem_dependency_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Sidekiq::Cron::Job do
+RSpec.describe Sidekiq::Cron::Job do
describe 'cron jobs' do
context 'when Fugit depends on ZoTime or EoTime' do
before do
diff --git a/spec/simplecov_env.rb b/spec/simplecov_env.rb
index c5b8a6db605..17b76205d9e 100644
--- a/spec/simplecov_env.rb
+++ b/spec/simplecov_env.rb
@@ -1,6 +1,7 @@
# frozen_string_literal: true
require 'simplecov'
+require 'simplecov-cobertura'
require 'active_support/core_ext/numeric/time'
require_relative '../lib/gitlab/utils'
@@ -12,10 +13,19 @@ module SimpleCovEnv
configure_profile
configure_job
+ configure_formatter
SimpleCov.start
end
+ def configure_formatter
+ SimpleCov.formatters = SimpleCov::Formatter::MultiFormatter.new([
+ SimpleCov::Formatter::SimpleFormatter,
+ SimpleCov::Formatter::HTMLFormatter,
+ SimpleCov::Formatter::CoberturaFormatter
+ ])
+ end
+
def configure_job
SimpleCov.configure do
if ENV['CI_JOB_NAME']
@@ -37,28 +47,33 @@ module SimpleCovEnv
def configure_profile
SimpleCov.configure do
load_profile 'test_frameworks'
- track_files '{app,lib}/**/*.rb'
+ track_files '{app,config/initializers,config/initializers_before_autoloader,db/post_migrate,haml_lint,lib,rubocop,tooling}/**/*.rb'
add_filter '/vendor/ruby/'
- add_filter 'app/controllers/sherlock/'
- add_filter 'config/initializers/'
- add_filter 'db/fixtures/'
- add_filter 'lib/gitlab/sidekiq_middleware/'
- add_filter 'lib/system_check/'
+ add_filter '/app/controllers/sherlock/'
+ add_filter '/bin/'
+ add_filter 'db/fixtures/' # Matches EE files as well
+ add_filter '/lib/gitlab/sidekiq_middleware/'
+ add_filter '/lib/system_check/'
- add_group 'Controllers', 'app/controllers'
- add_group 'Finders', 'app/finders'
- add_group 'Helpers', 'app/helpers'
- add_group 'Libraries', 'lib'
- add_group 'Mailers', 'app/mailers'
- add_group 'Models', 'app/models'
- add_group 'Policies', 'app/policies'
- add_group 'Presenters', 'app/presenters'
- add_group 'Serializers', 'app/serializers'
- add_group 'Services', 'app/services'
- add_group 'Uploaders', 'app/uploaders'
- add_group 'Validators', 'app/validators'
- add_group 'Workers', %w(app/jobs app/workers)
+ add_group 'Channels', 'app/channels' # Matches EE files as well
+ add_group 'Controllers', 'app/controllers' # Matches EE files as well
+ add_group 'Finders', 'app/finders' # Matches EE files as well
+ add_group 'GraphQL', 'app/graphql' # Matches EE files as well
+ add_group 'Helpers', 'app/helpers' # Matches EE files as well
+ add_group 'Mailers', 'app/mailers' # Matches EE files as well
+ add_group 'Models', 'app/models' # Matches EE files as well
+ add_group 'Policies', 'app/policies' # Matches EE files as well
+ add_group 'Presenters', 'app/presenters' # Matches EE files as well
+ add_group 'Serializers', 'app/serializers' # Matches EE files as well
+ add_group 'Services', 'app/services' # Matches EE files as well
+ add_group 'Uploaders', 'app/uploaders' # Matches EE files as well
+ add_group 'Validators', 'app/validators' # Matches EE files as well
+ add_group 'Workers', %w[app/jobs app/workers] # Matches EE files as well
+ add_group 'Initializers', %w[config/initializers config/initializers_before_autoloader] # Matches EE files as well
+ add_group 'Migrations', %w[db/migrate db/optional_migrations db/post_migrate] # Matches EE files as well
+ add_group 'Libraries', %w[/lib /ee/lib]
+ add_group 'Tooling', %w[/haml_lint /rubocop /tooling]
merge_timeout 365.days
end
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index 84de5119505..ed3211a9c87 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -103,7 +103,6 @@ RSpec.configure do |config|
config.include ActiveJob::TestHelper
config.include ActiveSupport::Testing::TimeHelpers
config.include CycleAnalyticsHelpers
- config.include ExpectOffense
config.include FactoryBot::Syntax::Methods
config.include FixtureHelpers
config.include NonExistingRecordsHelpers
@@ -156,6 +155,9 @@ RSpec.configure do |config|
config.before(:suite) do
Timecop.safe_mode = true
TestEnv.init
+
+ # Reload all feature flags definitions
+ Feature.register_definitions
end
config.after(:all) do
@@ -332,6 +334,8 @@ RSpec.configure do |config|
Ability.allowed?(*args)
end
end
+
+ config.disable_monkey_patching!
end
ActiveRecord::Migration.maintain_test_schema!
diff --git a/spec/support/capybara.rb b/spec/support/capybara.rb
index 38f9ccf23f5..66fce4fddf1 100644
--- a/spec/support/capybara.rb
+++ b/spec/support/capybara.rb
@@ -9,6 +9,9 @@ require 'selenium-webdriver'
# Give CI some extra time
timeout = ENV['CI'] || ENV['CI_SERVER'] ? 60 : 30
+# Support running Capybara on a specific port to allow saving commonly used pages
+Capybara.server_port = ENV['CAPYBARA_PORT'] if ENV['CAPYBARA_PORT']
+
# Define an error class for JS console messages
JSConsoleError = Class.new(StandardError)
@@ -153,11 +156,20 @@ RSpec.configure do |config|
# fixed. If we raised the `JSException` the fixed test would be marked as
# failed again.
if example.exception && !example.exception.is_a?(RSpec::Core::Pending::PendingExampleFixedError)
- console = page.driver.browser.manage.logs.get(:browser)&.reject { |log| log.message =~ JS_CONSOLE_FILTER }
-
- if console.present?
- message = "Unexpected browser console output:\n" + console.map(&:message).join("\n")
- raise JSConsoleError, message
+ begin
+ console = page.driver.browser.manage.logs.get(:browser)&.reject { |log| log.message =~ JS_CONSOLE_FILTER }
+
+ if console.present?
+ message = "Unexpected browser console output:\n" + console.map(&:message).join("\n")
+ raise JSConsoleError, message
+ end
+ rescue Selenium::WebDriver::Error::WebDriverError => error
+ if error.message =~ /unknown command: session\/[0-9a-zA-Z]+(?:\/se)?\/log/
+ message = "Unable to access Chrome javascript console logs. You may be using an outdated version of ChromeDriver."
+ raise JSConsoleError, message
+ else
+ raise error
+ end
end
end
diff --git a/spec/support/controllers/project_import_rate_limiter_shared_examples.rb b/spec/support/controllers/project_import_rate_limiter_shared_examples.rb
index 9a543dd00d4..66d753a4010 100644
--- a/spec/support/controllers/project_import_rate_limiter_shared_examples.rb
+++ b/spec/support/controllers/project_import_rate_limiter_shared_examples.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-shared_examples 'project import rate limiter' do
+RSpec.shared_examples 'project import rate limiter' do
let(:user) { create(:user) }
before do
diff --git a/spec/support/helpers/expect_offense.rb b/spec/support/helpers/expect_offense.rb
deleted file mode 100644
index 76301fe19ff..00000000000
--- a/spec/support/helpers/expect_offense.rb
+++ /dev/null
@@ -1,22 +0,0 @@
-# frozen_string_literal: true
-
-require 'rubocop/rspec/support'
-
-# https://github.com/backus/rubocop-rspec/blob/master/spec/support/expect_offense.rb
-# rubocop-rspec gem extension of RuboCop's ExpectOffense module.
-#
-# This mixin is the same as rubocop's ExpectOffense except the default
-# filename ends with `_spec.rb`
-module ExpectOffense
- include RuboCop::RSpec::ExpectOffense
-
- DEFAULT_FILENAME = 'example_spec.rb'.freeze
-
- def expect_offense(source, filename = DEFAULT_FILENAME)
- super
- end
-
- def expect_no_offenses(source, filename = DEFAULT_FILENAME)
- super
- end
-end
diff --git a/spec/support/helpers/fast_rails_root.rb b/spec/support/helpers/fast_rails_root.rb
new file mode 100644
index 00000000000..1510fe0825c
--- /dev/null
+++ b/spec/support/helpers/fast_rails_root.rb
@@ -0,0 +1,10 @@
+# frozen_string_literal: true
+
+# For specs which don't load Rails, provide a path to Rails root
+module FastRailsRoot
+ RAILS_ROOT = File.absolute_path("#{__dir__}/../../..")
+
+ def rails_root_join(*args)
+ File.join(RAILS_ROOT, *args)
+ end
+end
diff --git a/spec/support/helpers/filtered_search_helpers.rb b/spec/support/helpers/filtered_search_helpers.rb
index 99a5e043825..1847a8f8a06 100644
--- a/spec/support/helpers/filtered_search_helpers.rb
+++ b/spec/support/helpers/filtered_search_helpers.rb
@@ -45,9 +45,8 @@ module FilteredSearchHelpers
all_count = open_count + closed_count
expect(page).to have_issuable_counts(open: open_count, closed: closed_count, all: all_count)
- page.within '.issues-list' do
- expect(page).to have_selector('.issue', count: open_count)
- end
+
+ expect(page).to have_selector('.issue', count: open_count)
end
# Enables input to be added character by character
diff --git a/spec/support/helpers/jira_service_helper.rb b/spec/support/helpers/jira_service_helper.rb
index 198bedfe3bc..9072c41fe66 100644
--- a/spec/support/helpers/jira_service_helper.rb
+++ b/spec/support/helpers/jira_service_helper.rb
@@ -5,14 +5,13 @@ module JiraServiceHelper
JIRA_API = JIRA_URL + "/rest/api/2"
def jira_service_settings
- title = "Jira tracker"
url = JIRA_URL
username = 'jira-user'
password = 'my-secret-password'
jira_issue_transition_id = '1'
jira_tracker.update(
- title: title, url: url, username: username, password: password,
+ url: url, username: username, password: password,
jira_issue_transition_id: jira_issue_transition_id, active: true
)
end
diff --git a/spec/support/helpers/metrics_dashboard_helpers.rb b/spec/support/helpers/metrics_dashboard_helpers.rb
index b8a641d5911..b2dd8ead7dd 100644
--- a/spec/support/helpers/metrics_dashboard_helpers.rb
+++ b/spec/support/helpers/metrics_dashboard_helpers.rb
@@ -7,6 +7,12 @@ module MetricsDashboardHelpers
create(:project, :custom_repo, files: { dashboard_path => dashboard_yml })
end
+ def project_with_dashboard_namespace(dashboard_path, dashboard_yml = nil)
+ dashboard_yml ||= fixture_file('lib/gitlab/metrics/dashboard/sample_dashboard.yml')
+
+ create(:project, :custom_repo, namespace: namespace, path: 'monitor-project', files: { dashboard_path => dashboard_yml })
+ end
+
def delete_project_dashboard(project, user, dashboard_path)
project.repository.delete_file(
user,
@@ -18,6 +24,14 @@ module MetricsDashboardHelpers
project.repository.refresh_method_caches([:metrics_dashboard])
end
+ def load_sample_dashboard
+ load_dashboard_yaml(fixture_file('lib/gitlab/metrics/dashboard/sample_dashboard.yml'))
+ end
+
+ def load_dashboard_yaml(data)
+ ::Gitlab::Config::Loader::Yaml.new(data).load_raw!
+ end
+
def system_dashboard_path
Metrics::Dashboard::SystemDashboardService::DASHBOARD_PATH
end
diff --git a/spec/support/helpers/notification_helpers.rb b/spec/support/helpers/notification_helpers.rb
index b3e0e7d811b..887d68de4e1 100644
--- a/spec/support/helpers/notification_helpers.rb
+++ b/spec/support/helpers/notification_helpers.rb
@@ -38,26 +38,26 @@ module NotificationHelpers
end
def expect_delivery_jobs_count(count)
- expect(ActionMailer::DeliveryJob).to have_been_enqueued.exactly(count).times
+ expect(ActionMailer::MailDeliveryJob).to have_been_enqueued.exactly(count).times
end
def expect_no_delivery_jobs
- expect(ActionMailer::DeliveryJob).not_to have_been_enqueued
+ expect(ActionMailer::MailDeliveryJob).not_to have_been_enqueued
end
def expect_any_delivery_jobs
- expect(ActionMailer::DeliveryJob).to have_been_enqueued.at_least(:once)
+ expect(ActionMailer::MailDeliveryJob).to have_been_enqueued.at_least(:once)
end
def have_enqueued_email(*args, mailer: "Notify", mail: "", delivery: "deliver_now")
- have_enqueued_job(ActionMailer::DeliveryJob).with(mailer, mail, delivery, *args)
+ have_enqueued_job(ActionMailer::MailDeliveryJob).with(mailer, mail, delivery, args: args)
end
def expect_enqueud_email(*args, mailer: "Notify", mail: "", delivery: "deliver_now")
- expect(ActionMailer::DeliveryJob).to have_been_enqueued.with(mailer, mail, delivery, *args)
+ expect(ActionMailer::MailDeliveryJob).to have_been_enqueued.with(mailer, mail, delivery, args: args)
end
def expect_not_enqueud_email(*args, mailer: "Notify", mail: "")
- expect(ActionMailer::DeliveryJob).not_to have_been_enqueued.with(mailer, mail, *args, any_args)
+ expect(ActionMailer::MailDeliveryJob).not_to have_been_enqueued.with(mailer, mail, args: any_args)
end
end
diff --git a/spec/support/helpers/packages_manager_api_spec_helper.rb b/spec/support/helpers/packages_manager_api_spec_helper.rb
new file mode 100644
index 00000000000..e5a690e1680
--- /dev/null
+++ b/spec/support/helpers/packages_manager_api_spec_helper.rb
@@ -0,0 +1,46 @@
+# frozen_string_literal: true
+
+module PackagesManagerApiSpecHelpers
+ def build_auth_headers(value)
+ { 'HTTP_AUTHORIZATION' => value }
+ end
+
+ def build_basic_auth_header(username, password)
+ build_auth_headers(ActionController::HttpAuthentication::Basic.encode_credentials(username, password))
+ end
+
+ def build_token_auth_header(token)
+ build_auth_headers("Bearer #{token}")
+ end
+
+ def build_jwt(personal_access_token, secret: jwt_secret, user_id: nil)
+ JSONWebToken::HMACToken.new(secret).tap do |jwt|
+ jwt['access_token'] = personal_access_token.id
+ jwt['user_id'] = user_id || personal_access_token.user_id
+ end
+ end
+
+ def build_jwt_from_job(job, secret: jwt_secret)
+ JSONWebToken::HMACToken.new(secret).tap do |jwt|
+ jwt['access_token'] = job.token
+ jwt['user_id'] = job.user.id
+ end
+ end
+
+ def build_jwt_from_deploy_token(deploy_token, secret: jwt_secret)
+ JSONWebToken::HMACToken.new(secret).tap do |jwt|
+ jwt['access_token'] = deploy_token.token
+ jwt['user_id'] = deploy_token.username
+ end
+ end
+
+ def temp_file(package_tmp)
+ upload_path = ::Packages::PackageFileUploader.workhorse_local_upload_path
+ file_path = "#{upload_path}/#{package_tmp}"
+
+ FileUtils.mkdir_p(upload_path)
+ File.write(file_path, 'test')
+
+ UploadedFile.new(file_path, filename: File.basename(file_path))
+ end
+end
diff --git a/spec/support/helpers/partitioning_helpers.rb b/spec/support/helpers/partitioning_helpers.rb
index 98a13915d76..8981fea04d5 100644
--- a/spec/support/helpers/partitioning_helpers.rb
+++ b/spec/support/helpers/partitioning_helpers.rb
@@ -9,13 +9,36 @@ module PartitioningHelpers
end
def expect_range_partition_of(partition_name, table_name, min_value, max_value)
- definition = find_partition_definition(partition_name)
+ definition = find_partition_definition(partition_name, schema: Gitlab::Database::DYNAMIC_PARTITIONS_SCHEMA)
expect(definition).not_to be_nil
expect(definition['base_table']).to eq(table_name.to_s)
expect(definition['condition']).to eq("FOR VALUES FROM (#{min_value}) TO (#{max_value})")
end
+ def expect_total_partitions(table_name, count, schema: Gitlab::Database::DYNAMIC_PARTITIONS_SCHEMA)
+ partitions = find_partitions(table_name, schema: schema)
+
+ expect(partitions.size).to eq(count)
+ end
+
+ def expect_range_partitions_for(table_name, partitions)
+ partitions.each do |suffix, (min_value, max_value)|
+ partition_name = "#{table_name}_#{suffix}"
+ expect_range_partition_of(partition_name, table_name, min_value, max_value)
+ end
+
+ expect_total_partitions(table_name, partitions.size, schema: Gitlab::Database::DYNAMIC_PARTITIONS_SCHEMA)
+ end
+
+ def expect_hash_partition_of(partition_name, table_name, modulus, remainder)
+ definition = find_partition_definition(partition_name, schema: Gitlab::Database::STATIC_PARTITIONS_SCHEMA)
+
+ expect(definition).not_to be_nil
+ expect(definition['base_table']).to eq(table_name.to_s)
+ expect(definition['condition']).to eq("FOR VALUES WITH (modulus #{modulus}, remainder #{remainder})")
+ end
+
private
def find_partitioned_columns(table)
@@ -40,7 +63,7 @@ module PartitioningHelpers
SQL
end
- def find_partition_definition(partition)
+ def find_partition_definition(partition, schema: )
connection.select_one(<<~SQL)
select
parent_class.relname as base_table,
@@ -48,7 +71,24 @@ module PartitioningHelpers
from pg_class
inner join pg_inherits i on pg_class.oid = inhrelid
inner join pg_class parent_class on parent_class.oid = inhparent
- where pg_class.relname = '#{partition}' and pg_class.relispartition;
+ inner join pg_namespace ON pg_namespace.oid = pg_class.relnamespace
+ where pg_namespace.nspname = '#{schema}'
+ and pg_class.relname = '#{partition}'
+ and pg_class.relispartition
+ SQL
+ end
+
+ def find_partitions(partition, schema: Gitlab::Database::DYNAMIC_PARTITIONS_SCHEMA)
+ connection.select_rows(<<~SQL)
+ select
+ pg_class.relname
+ from pg_class
+ inner join pg_inherits i on pg_class.oid = inhrelid
+ inner join pg_class parent_class on parent_class.oid = inhparent
+ inner join pg_namespace ON pg_namespace.oid = pg_class.relnamespace
+ where pg_namespace.nspname = '#{schema}'
+ and parent_class.relname = '#{partition}'
+ and pg_class.relispartition
SQL
end
end
diff --git a/spec/support/helpers/rack_attack_spec_helpers.rb b/spec/support/helpers/rack_attack_spec_helpers.rb
index e0cedb5a57b..65082ec690f 100644
--- a/spec/support/helpers/rack_attack_spec_helpers.rb
+++ b/spec/support/helpers/rack_attack_spec_helpers.rb
@@ -30,4 +30,16 @@ module RackAttackSpecHelpers
expect(response).to have_gitlab_http_status(:too_many_requests)
end
+
+ def expect_ok(&block)
+ yield
+
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+
+ def random_next_ip
+ allow_next_instance_of(Rack::Attack::Request) do |instance|
+ allow(instance).to receive(:ip).and_return(FFaker::Internet.ip_v4_address)
+ end
+ end
end
diff --git a/spec/support/helpers/reference_parser_helpers.rb b/spec/support/helpers/reference_parser_helpers.rb
index 9084265b587..e65cb8c96db 100644
--- a/spec/support/helpers/reference_parser_helpers.rb
+++ b/spec/support/helpers/reference_parser_helpers.rb
@@ -10,7 +10,7 @@ module ReferenceParserHelpers
expect(result[:not_visible].count).to eq(not_visible_count)
end
- shared_examples 'no project N+1 queries' do
+ RSpec.shared_examples 'no project N+1 queries' do
it 'avoids N+1 queries in #nodes_visible_to_user', :request_store do
context = Banzai::RenderContext.new(project, user)
@@ -28,7 +28,7 @@ module ReferenceParserHelpers
end
end
- shared_examples 'no N+1 queries' do
+ RSpec.shared_examples 'no N+1 queries' do
it_behaves_like 'no project N+1 queries'
it 'avoids N+1 queries in #records_for_nodes', :request_store do
diff --git a/spec/support/helpers/snippet_helpers.rb b/spec/support/helpers/snippet_helpers.rb
new file mode 100644
index 00000000000..de64ad7d3e2
--- /dev/null
+++ b/spec/support/helpers/snippet_helpers.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+
+module SnippetHelpers
+ def sign_in_as(user)
+ sign_in(public_send(user)) if user
+ end
+
+ def snippet_blob_file(blob)
+ {
+ "path" => blob.path,
+ "raw_url" => gitlab_raw_snippet_blob_url(blob.container, blob.path)
+ }
+ end
+end
diff --git a/spec/support/helpers/stub_configuration.rb b/spec/support/helpers/stub_configuration.rb
index 6a832ca97d1..e19f230d8df 100644
--- a/spec/support/helpers/stub_configuration.rb
+++ b/spec/support/helpers/stub_configuration.rb
@@ -113,6 +113,14 @@ module StubConfiguration
allow(Gitlab.config.rack_attack.git_basic_auth).to receive_messages(to_settings(messages))
end
+ def stub_service_desk_email_setting(messages)
+ allow(::Gitlab.config.service_desk_email).to receive_messages(to_settings(messages))
+ end
+
+ def stub_packages_setting(messages)
+ allow(::Gitlab.config.packages).to receive_messages(to_settings(messages))
+ end
+
private
# Modifies stubbed messages to also stub possible predicate versions
diff --git a/spec/support/helpers/stub_object_storage.rb b/spec/support/helpers/stub_object_storage.rb
index b473cdaefc1..6056359d026 100644
--- a/spec/support/helpers/stub_object_storage.rb
+++ b/spec/support/helpers/stub_object_storage.rb
@@ -1,6 +1,25 @@
# frozen_string_literal: true
module StubObjectStorage
+ def stub_packages_object_storage(**params)
+ stub_object_storage_uploader(config: ::Gitlab.config.packages.object_store,
+ uploader: ::Packages::PackageFileUploader,
+ remote_directory: 'packages',
+ **params)
+ end
+
+ def stub_dependency_proxy_object_storage(**params)
+ stub_object_storage_uploader(config: ::Gitlab.config.dependency_proxy.object_store,
+ uploader: ::DependencyProxy::FileUploader,
+ remote_directory: 'dependency_proxy',
+ **params)
+ end
+
+ def stub_object_storage_pseudonymizer
+ stub_object_storage(connection_params: Pseudonymizer::Uploader.object_store_credentials,
+ remote_directory: Pseudonymizer::Uploader.remote_directory)
+ end
+
def stub_object_storage_uploader(
config:,
uploader:,
@@ -73,7 +92,7 @@ module StubObjectStorage
def stub_terraform_state_object_storage(uploader = described_class, **params)
stub_object_storage_uploader(config: Gitlab.config.terraform_state.object_store,
uploader: uploader,
- remote_directory: 'terraform_state',
+ remote_directory: 'terraform',
**params)
end
@@ -89,8 +108,3 @@ module StubObjectStorage
EOS
end
end
-
-require_relative '../../../ee/spec/support/helpers/ee/stub_object_storage' if
- Dir.exist?("#{__dir__}/../../../ee")
-
-StubObjectStorage.prepend_if_ee('EE::StubObjectStorage')
diff --git a/spec/support/helpers/test_env.rb b/spec/support/helpers/test_env.rb
index 130650b7e2e..f787aedf7aa 100644
--- a/spec/support/helpers/test_env.rb
+++ b/spec/support/helpers/test_env.rb
@@ -6,6 +6,8 @@ module TestEnv
ComponentFailedToInstallError = Class.new(StandardError)
+ SHA_REGEX = /\A[0-9a-f]{5,40}\z/i.freeze
+
# When developing the seed repository, comment out the branch you will modify.
BRANCH_SHA = {
'signed-commits' => '6101e87',
@@ -29,6 +31,10 @@ module TestEnv
'gitattributes' => '5a62481',
'expand-collapse-diffs' => '4842455',
'symlink-expand-diff' => '81e6355',
+ 'diff-files-symlink-to-image' => '8cfca84',
+ 'diff-files-image-to-symlink' => '3e94fda',
+ 'diff-files-symlink-to-text' => '689815e',
+ 'diff-files-text-to-symlink' => '5e2c270',
'expand-collapse-files' => '025db92',
'expand-collapse-lines' => '238e82d',
'pages-deploy' => '7897d5b',
@@ -165,8 +171,9 @@ module TestEnv
task: "gitlab:gitaly:install[#{install_gitaly_args}]") do
Gitlab::SetupHelper::Gitaly.create_configuration(gitaly_dir, { 'default' => repos_path }, force: true)
Gitlab::SetupHelper::Praefect.create_configuration(gitaly_dir, { 'praefect' => repos_path }, force: true)
- start_gitaly(gitaly_dir)
end
+
+ start_gitaly(gitaly_dir)
end
def gitaly_socket_path
@@ -459,7 +466,6 @@ module TestEnv
end
def component_timed_setup(component, install_dir:, version:, task:)
- puts "\n==> Setting up #{component}..."
start = Time.now
ensure_component_dir_name_is_correct!(component, install_dir)
@@ -468,22 +474,22 @@ module TestEnv
return if File.exist?(install_dir) && ci?
if component_needs_update?(install_dir, version)
+ puts "\n==> Setting up #{component}..."
# Cleanup the component entirely to ensure we start fresh
FileUtils.rm_rf(install_dir)
unless system('rake', task)
raise ComponentFailedToInstallError
end
- end
- yield if block_given?
+ yield if block_given?
+ puts " #{component} set up in #{Time.now - start} seconds...\n"
+ end
rescue ComponentFailedToInstallError
puts "\n#{component} failed to install, cleaning up #{install_dir}!\n"
FileUtils.rm_rf(install_dir)
exit 1
- ensure
- puts " #{component} set up in #{Time.now - start} seconds...\n"
end
def ci?
@@ -504,6 +510,8 @@ module TestEnv
# Allow local overrides of the component for tests during development
return false if Rails.env.test? && File.symlink?(component_folder)
+ return false if component_matches_git_sha?(component_folder, expected_version)
+
version = File.read(File.join(component_folder, 'VERSION')).strip
# Notice that this will always yield true when using branch versions
@@ -513,6 +521,16 @@ module TestEnv
rescue Errno::ENOENT
true
end
+
+ def component_matches_git_sha?(component_folder, expected_version)
+ # Not a git SHA, so return early
+ return false unless expected_version =~ SHA_REGEX
+
+ sha, exit_status = Gitlab::Popen.popen(%W(#{Gitlab.config.git.bin_path} rev-parse HEAD), component_folder)
+ return false if exit_status != 0
+
+ expected_version == sha.chomp
+ end
end
require_relative('../../../ee/spec/support/helpers/ee/test_env') if Gitlab.ee?
diff --git a/spec/support/helpers/trigger_helpers.rb b/spec/support/helpers/trigger_helpers.rb
index fa4f499b900..67c62cf4869 100644
--- a/spec/support/helpers/trigger_helpers.rb
+++ b/spec/support/helpers/trigger_helpers.rb
@@ -27,7 +27,10 @@ module TriggerHelpers
expected_timing, expected_events = fires_on.first
expect(timing).to eq(expected_timing.to_s)
expect(events).to match_array(Array.wrap(expected_events))
- expect(definition).to eq("execute procedure #{fn_name}()")
+
+ # TODO: Update CREATE TRIGGER syntax to use EXECUTE FUNCTION
+ # https://gitlab.com/gitlab-org/gitlab/-/issues/227089
+ expect(definition).to match(%r{execute (?:procedure|function) #{fn_name}()})
end
private
diff --git a/spec/support/helpers/usage_data_helpers.rb b/spec/support/helpers/usage_data_helpers.rb
index f6c415a75bc..a4f40a4af0a 100644
--- a/spec/support/helpers/usage_data_helpers.rb
+++ b/spec/support/helpers/usage_data_helpers.rb
@@ -78,7 +78,6 @@ module UsageDataHelpers
labels
lfs_objects
merge_requests
- merge_requests_users
milestone_lists
milestones
notes
@@ -89,8 +88,6 @@ module UsageDataHelpers
projects_jira_active
projects_jira_server_active
projects_jira_cloud_active
- projects_slack_notifications_active
- projects_slack_slash_active
projects_slack_active
projects_slack_slash_commands_active
projects_custom_issue_tracker_active
@@ -231,6 +228,15 @@ module UsageDataHelpers
def allow_prometheus_queries
allow_next_instance_of(Gitlab::PrometheusClient) do |client|
allow(client).to receive(:aggregate).and_return({})
+ allow(client).to receive(:query).and_return({})
+ end
+ end
+
+ def for_defined_days_back(days: [29, 2])
+ days.each do |n|
+ Timecop.travel(n.days.ago) do
+ yield
+ end
end
end
end
diff --git a/spec/support/matchers/background_migrations_matchers.rb b/spec/support/matchers/background_migrations_matchers.rb
index 8735dac8b2a..0144a044f6c 100644
--- a/spec/support/matchers/background_migrations_matchers.rb
+++ b/spec/support/matchers/background_migrations_matchers.rb
@@ -10,7 +10,8 @@ RSpec::Matchers.define :be_scheduled_delayed_migration do |delay, *expected|
failure_message do |migration|
"Migration `#{migration}` with args `#{expected.inspect}` " \
- 'not scheduled in expected time!'
+ "not scheduled in expected time! Expected any of `#{BackgroundMigrationWorker.jobs.map { |j| j['args'] }}` to be `#{[migration, expected]}` " \
+ "and any of `#{BackgroundMigrationWorker.jobs.map { |j| j['at'].to_i }}` to be `#{delay.to_i + Time.now.to_i}` (`#{delay.to_i}` + `#{Time.now.to_i}`)."
end
end
diff --git a/spec/support/matchers/jsonb_matchers.rb b/spec/support/matchers/jsonb_matchers.rb
new file mode 100644
index 00000000000..823888708f3
--- /dev/null
+++ b/spec/support/matchers/jsonb_matchers.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+
+RSpec::Matchers.define :validate_jsonb_schema do |jsonb_columns|
+ match do |actual|
+ next true if jsonb_columns.blank?
+
+ expect(actual.validators).to include(a_kind_of(JsonSchemaValidator))
+ end
+
+ failure_message do
+ <<~FAILURE_MESSAGE
+ Expected #{actual.name} to validate the schema of #{jsonb_columns.join(', ')}.
+
+ Use JsonSchemaValidator in your model when using a jsonb column.
+ See doc/development/migration_style_guide.html#storing-json-in-database for more information.
+
+ To fix this, please add `validates :#{jsonb_columns.first}, json_schema: { filename: "filename" }` in your model file, for example:
+
+ class #{actual.name}
+ validates :#{jsonb_columns.first}, json_schema: { filename: "filename" }
+ end
+ FAILURE_MESSAGE
+ end
+end
diff --git a/spec/support/rspec.rb b/spec/support/rspec.rb
index 7d011c5eb95..861b57c9efa 100644
--- a/spec/support/rspec.rb
+++ b/spec/support/rspec.rb
@@ -4,7 +4,8 @@ require_relative "helpers/stub_configuration"
require_relative "helpers/stub_metrics"
require_relative "helpers/stub_object_storage"
require_relative "helpers/stub_env"
-require_relative "helpers/expect_offense"
+require_relative "helpers/fast_rails_root"
+require 'rubocop/rspec/support'
RSpec.configure do |config|
config.mock_with :rspec
@@ -14,6 +15,7 @@ RSpec.configure do |config|
config.include StubMetrics
config.include StubObjectStorage
config.include StubENV
+ config.include FastRailsRoot
- config.include ExpectOffense, type: :rubocop
+ config.include RuboCop::RSpec::ExpectOffense, type: :rubocop
end
diff --git a/spec/support/services/clusters/create_service_shared.rb b/spec/support/services/clusters/create_service_shared.rb
index 31aee08baec..f8a58a828ce 100644
--- a/spec/support/services/clusters/create_service_shared.rb
+++ b/spec/support/services/clusters/create_service_shared.rb
@@ -1,6 +1,7 @@
# frozen_string_literal: true
RSpec.shared_context 'valid cluster create params' do
+ let(:clusterable) { Clusters::Instance.new }
let(:params) do
{
name: 'test-cluster',
@@ -11,12 +12,14 @@ RSpec.shared_context 'valid cluster create params' do
num_nodes: 1,
machine_type: 'machine_type-a',
legacy_abac: 'true'
- }
+ },
+ clusterable: clusterable
}
end
end
RSpec.shared_context 'invalid cluster create params' do
+ let(:clusterable) { Clusters::Instance.new }
let(:params) do
{
name: 'test-cluster',
@@ -26,7 +29,9 @@ RSpec.shared_context 'invalid cluster create params' do
zone: 'us-central1-a',
num_nodes: 1,
machine_type: 'machine_type-a'
- }
+ },
+ clusterable: clusterable
+
}
end
end
diff --git a/spec/support/services/issuable_create_service_slash_commands_shared_examples.rb b/spec/support/services/issuable_create_service_slash_commands_shared_examples.rb
deleted file mode 100644
index 3d45fe06134..00000000000
--- a/spec/support/services/issuable_create_service_slash_commands_shared_examples.rb
+++ /dev/null
@@ -1,73 +0,0 @@
-# frozen_string_literal: true
-
-# Specifications for behavior common to all objects with executable attributes.
-# It can take a `default_params`.
-
-RSpec.shared_examples 'new issuable record that supports quick actions' do
- let!(:project) { create(:project, :repository) }
- let(:user) { create(:user).tap { |u| project.add_maintainer(u) } }
- let(:assignee) { create(:user) }
- let!(:milestone) { create(:milestone, project: project) }
- let!(:labels) { create_list(:label, 3, project: project) }
- let(:base_params) { { title: 'My issuable title' } }
- let(:params) { base_params.merge(defined?(default_params) ? default_params : {}).merge(example_params) }
- let(:issuable) { described_class.new(project, user, params).execute }
-
- before do
- project.add_maintainer(assignee)
- end
-
- context 'with labels in command only' do
- let(:example_params) do
- {
- description: "/label ~#{labels.first.name} ~#{labels.second.name}\n/unlabel ~#{labels.third.name}"
- }
- end
-
- it 'attaches labels to issuable' do
- expect(issuable).to be_persisted
- expect(issuable.label_ids).to match_array([labels.first.id, labels.second.id])
- end
- end
-
- context 'with labels in params and command' do
- let(:example_params) do
- {
- label_ids: [labels.second.id],
- description: "/label ~#{labels.first.name}\n/unlabel ~#{labels.third.name}"
- }
- end
-
- it 'attaches all labels to issuable' do
- expect(issuable).to be_persisted
- expect(issuable.label_ids).to match_array([labels.first.id, labels.second.id])
- end
- end
-
- context 'with assignee and milestone in command only' do
- let(:example_params) do
- {
- description: %(/assign @#{assignee.username}\n/milestone %"#{milestone.name}")
- }
- end
-
- it 'assigns and sets milestone to issuable' do
- expect(issuable).to be_persisted
- expect(issuable.assignees).to eq([assignee])
- expect(issuable.milestone).to eq(milestone)
- end
- end
-
- describe '/close' do
- let(:example_params) do
- {
- description: '/close'
- }
- end
-
- it 'returns an open issue' do
- expect(issuable).to be_persisted
- expect(issuable).to be_open
- end
- end
-end
diff --git a/spec/support/services/issuable_description_quick_actions_shared_examples.rb b/spec/support/services/issuable_description_quick_actions_shared_examples.rb
new file mode 100644
index 00000000000..1970301e4c9
--- /dev/null
+++ b/spec/support/services/issuable_description_quick_actions_shared_examples.rb
@@ -0,0 +1,62 @@
+# frozen_string_literal: true
+
+# Specifications for behavior common to all objects with executable attributes.
+# It can take a `default_params`.
+
+RSpec.shared_examples 'issuable record that supports quick actions' do
+ let_it_be(:project) { create(:project, :repository) }
+ let_it_be(:user) { create(:user) }
+ let_it_be(:assignee) { create(:user) }
+ let_it_be(:milestone) { create(:milestone, project: project) }
+ let_it_be(:labels) { create_list(:label, 3, project: project) }
+
+ let(:base_params) { { title: 'My issuable title' } }
+ let(:params) { base_params.merge(defined?(default_params) ? default_params : {}).merge(example_params) }
+
+ before_all do
+ project.add_maintainer(user)
+ project.add_maintainer(assignee)
+ end
+
+ before do
+ issuable.reload
+ end
+
+ context 'with labels in command only' do
+ let(:example_params) do
+ {
+ description: "/label ~#{labels.first.name} ~#{labels.second.name}\n/unlabel ~#{labels.third.name}"
+ }
+ end
+
+ it 'attaches labels to issuable' do
+ expect(issuable.label_ids).to match_array([labels.first.id, labels.second.id])
+ end
+ end
+
+ context 'with labels in params and command' do
+ let(:example_params) do
+ {
+ label_ids: [labels.second.id],
+ description: "/label ~#{labels.first.name}\n/unlabel ~#{labels.third.name}"
+ }
+ end
+
+ it 'attaches all labels to issuable' do
+ expect(issuable.label_ids).to match_array([labels.first.id, labels.second.id])
+ end
+ end
+
+ context 'with assignee and milestone in command only' do
+ let(:example_params) do
+ {
+ description: %(/assign @#{assignee.username}\n/milestone %"#{milestone.name}")
+ }
+ end
+
+ it 'assigns and sets milestone to issuable' do
+ expect(issuable.assignees).to eq([assignee])
+ expect(issuable.milestone).to eq(milestone)
+ end
+ end
+end
diff --git a/spec/support/shared_contexts/cache_allowed_users_in_namespace_shared_context.rb b/spec/support/shared_contexts/cache_allowed_users_in_namespace_shared_context.rb
index 04f49e94647..e3c1d0afa53 100644
--- a/spec/support/shared_contexts/cache_allowed_users_in_namespace_shared_context.rb
+++ b/spec/support/shared_contexts/cache_allowed_users_in_namespace_shared_context.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-shared_examples 'allowed user IDs are cached' do
+RSpec.shared_examples 'allowed user IDs are cached' do
it 'caches the allowed user IDs in cache', :use_clean_rails_memory_store_caching do
expect do
expect(described_class.l1_cache_backend).to receive(:fetch).and_call_original
diff --git a/spec/support/shared_contexts/design_management_shared_contexts.rb b/spec/support/shared_contexts/design_management_shared_contexts.rb
index 2866effb3a8..3ff6a521338 100644
--- a/spec/support/shared_contexts/design_management_shared_contexts.rb
+++ b/spec/support/shared_contexts/design_management_shared_contexts.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-shared_context 'four designs in three versions' do
+RSpec.shared_context 'four designs in three versions' do
include DesignManagementTestHelpers
let_it_be(:issue) { create(:issue) }
diff --git a/spec/support/shared_contexts/features/error_tracking_shared_context.rb b/spec/support/shared_contexts/features/error_tracking_shared_context.rb
index 102cf7c9b11..1f4eb3a6df9 100644
--- a/spec/support/shared_contexts/features/error_tracking_shared_context.rb
+++ b/spec/support/shared_contexts/features/error_tracking_shared_context.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-shared_context 'sentry error tracking context feature' do
+RSpec.shared_context 'sentry error tracking context feature' do
include ReactiveCachingHelpers
let_it_be(:project) { create(:project) }
diff --git a/spec/support/shared_contexts/finders/merge_requests_finder_shared_contexts.rb b/spec/support/shared_contexts/finders/merge_requests_finder_shared_contexts.rb
index 2b8daa80ab4..07b6b98222f 100644
--- a/spec/support/shared_contexts/finders/merge_requests_finder_shared_contexts.rb
+++ b/spec/support/shared_contexts/finders/merge_requests_finder_shared_contexts.rb
@@ -23,12 +23,12 @@ RSpec.shared_context 'MergeRequestsFinder multiple projects with merge requests
# We cannot use `let_it_be` here otherwise we get:
# Failure/Error: allow(RepositoryForkWorker).to receive(:perform_async).and_return(true)
# The use of doubles or partial doubles from rspec-mocks outside of the per-test lifecycle is not supported.
- let(:project2) do
+ let!(:project2) do
allow_gitaly_n_plus_1 do
fork_project(project1, user)
end
end
- let(:project3) do
+ let!(:project3) do
allow_gitaly_n_plus_1 do
fork_project(project1, user).tap do |project|
project.update!(archived: true)
@@ -45,6 +45,9 @@ RSpec.shared_context 'MergeRequestsFinder multiple projects with merge requests
allow_gitaly_n_plus_1 { create(:project, group: subgroup) }
end
+ let!(:label) { create(:label, project: project1) }
+ let!(:label2) { create(:label, project: project1) }
+
let!(:merge_request1) do
create(:merge_request, assignees: [user], author: user,
source_project: project2, target_project: project1,
@@ -72,6 +75,9 @@ RSpec.shared_context 'MergeRequestsFinder multiple projects with merge requests
title: '[WIP]')
end
+ let!(:label_link) { create(:label_link, label: label, target: merge_request2) }
+ let!(:label_link2) { create(:label_link, label: label2, target: merge_request3) }
+
before do
project1.add_maintainer(user)
project2.add_developer(user)
diff --git a/spec/support/shared_contexts/issuable/merge_request_shared_context.rb b/spec/support/shared_contexts/issuable/merge_request_shared_context.rb
index 05ffb934c34..79fc42e73c7 100644
--- a/spec/support/shared_contexts/issuable/merge_request_shared_context.rb
+++ b/spec/support/shared_contexts/issuable/merge_request_shared_context.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-shared_context 'merge request show action' do
+RSpec.shared_context 'merge request show action' do
include Devise::Test::ControllerHelpers
include ProjectForksHelper
diff --git a/spec/support/shared_contexts/issuable/project_shared_context.rb b/spec/support/shared_contexts/issuable/project_shared_context.rb
index 6dbf3154977..5e5f6f2b7a6 100644
--- a/spec/support/shared_contexts/issuable/project_shared_context.rb
+++ b/spec/support/shared_contexts/issuable/project_shared_context.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-shared_context 'project show action' do
+RSpec.shared_context 'project show action' do
let(:project) { create(:project, :repository) }
let(:issue) { create(:issue, project: project, author: user) }
let(:user) { create(:user) }
diff --git a/spec/support/shared_contexts/navbar_structure_context.rb b/spec/support/shared_contexts/navbar_structure_context.rb
index 79b5ff44d4f..d9a72f2b54a 100644
--- a/spec/support/shared_contexts/navbar_structure_context.rb
+++ b/spec/support/shared_contexts/navbar_structure_context.rb
@@ -42,6 +42,7 @@ RSpec.shared_context 'project navbar structure' do
_('List'),
_('Boards'),
_('Labels'),
+ _('Service Desk'),
_('Milestones')
]
},
diff --git a/spec/support/shared_contexts/policies/group_policy_shared_context.rb b/spec/support/shared_contexts/policies/group_policy_shared_context.rb
index a0d54666dff..4b0c7afab6d 100644
--- a/spec/support/shared_contexts/policies/group_policy_shared_context.rb
+++ b/spec/support/shared_contexts/policies/group_policy_shared_context.rb
@@ -18,7 +18,7 @@ RSpec.shared_context 'GroupPolicy context' do
]
end
let(:read_group_permissions) { %i[read_label read_list read_milestone read_board] }
- let(:reporter_permissions) { %i[admin_label read_container_image read_metrics_dashboard_annotation] }
+ let(:reporter_permissions) { %i[admin_label read_container_image read_metrics_dashboard_annotation read_prometheus] }
let(:developer_permissions) { %i[admin_milestone create_metrics_dashboard_annotation delete_metrics_dashboard_annotation update_metrics_dashboard_annotation] }
let(:maintainer_permissions) do
%i[
@@ -38,6 +38,7 @@ RSpec.shared_context 'GroupPolicy context' do
:update_default_branch_protection
].compact
end
+ let(:admin_permissions) { %i[read_confidential_issues] }
before_all do
group.add_guest(guest)
diff --git a/spec/support/shared_contexts/presenters/nuget_shared_context.rb b/spec/support/shared_contexts/presenters/nuget_shared_context.rb
new file mode 100644
index 00000000000..dd381db5a8b
--- /dev/null
+++ b/spec/support/shared_contexts/presenters/nuget_shared_context.rb
@@ -0,0 +1,41 @@
+# frozen_string_literal: true
+
+RSpec.shared_context 'with expected presenters dependency groups' do
+ def expected_dependency_groups(project_id, package_name, package_version)
+ [
+ {
+ id: "http://localhost/api/v4/projects/#{project_id}/packages/nuget/metadata/#{package_name}/#{package_version}.json#dependencyGroup/.netstandard2.0",
+ target_framework: '.NETStandard2.0',
+ type: 'PackageDependencyGroup',
+ dependencies: [
+ {
+ id: "http://localhost/api/v4/projects/#{project_id}/packages/nuget/metadata/#{package_name}/#{package_version}.json#dependencyGroup/.netstandard2.0/newtonsoft.json",
+ range: '12.0.3',
+ name: 'Newtonsoft.Json',
+ type: 'PackageDependency'
+ }
+ ]
+ },
+ {
+ id: "http://localhost/api/v4/projects/#{project_id}/packages/nuget/metadata/#{package_name}/#{package_version}.json#dependencyGroup",
+ type: 'PackageDependencyGroup',
+ dependencies: [
+ {
+ id: "http://localhost/api/v4/projects/#{project_id}/packages/nuget/metadata/#{package_name}/#{package_version}.json#dependencyGroup/castle.core",
+ range: '4.4.1',
+ name: 'Castle.Core',
+ type: 'PackageDependency'
+ }
+ ]
+ }
+ ]
+ end
+
+ def create_dependencies_for(package)
+ dependency1 = Packages::Dependency.find_by(name: 'Newtonsoft.Json', version_pattern: '12.0.3') || create(:packages_dependency, name: 'Newtonsoft.Json', version_pattern: '12.0.3')
+ dependency2 = Packages::Dependency.find_by(name: 'Castle.Core', version_pattern: '4.4.1') || create(:packages_dependency, name: 'Castle.Core', version_pattern: '4.4.1')
+
+ create(:packages_dependency_link, :with_nuget_metadatum, package: package, dependency: dependency1)
+ create(:packages_dependency_link, package: package, dependency: dependency2)
+ end
+end
diff --git a/spec/support/shared_contexts/project_service_jira_context.rb b/spec/support/shared_contexts/project_service_jira_context.rb
new file mode 100644
index 00000000000..4ca5c99323a
--- /dev/null
+++ b/spec/support/shared_contexts/project_service_jira_context.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+RSpec.shared_context 'project service Jira context' do
+ let(:url) { 'http://jira.example.com' }
+ let(:test_url) { 'http://jira.example.com/rest/api/2/serverInfo' }
+
+ def fill_form(disable: false)
+ click_active_toggle if disable
+
+ fill_in 'service_url', with: url
+ fill_in 'service_username', with: 'username'
+ fill_in 'service_password', with: 'password'
+ fill_in 'service_jira_issue_transition_id', with: '25'
+ end
+end
diff --git a/spec/support/shared_contexts/project_service_shared_context.rb b/spec/support/shared_contexts/project_service_shared_context.rb
index 5b0dd26bd7b..a72d4901b72 100644
--- a/spec/support/shared_contexts/project_service_shared_context.rb
+++ b/spec/support/shared_contexts/project_service_shared_context.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-shared_context 'project service activation' do
+RSpec.shared_context 'project service activation' do
let(:project) { create(:project) }
let(:user) { create(:user) }
diff --git a/spec/support/shared_contexts/prometheus/alert_shared_context.rb b/spec/support/shared_contexts/prometheus/alert_shared_context.rb
new file mode 100644
index 00000000000..330d2c4515f
--- /dev/null
+++ b/spec/support/shared_contexts/prometheus/alert_shared_context.rb
@@ -0,0 +1,76 @@
+# frozen_string_literal: true
+
+# These contexts expect a `project` to be defined.
+# It is expected that these contexts are used to create an
+# alert.
+RSpec.shared_context 'self-managed prometheus alert attributes' do
+ let_it_be(:environment) { create(:environment, project: project, name: 'production') }
+
+ let(:starts_at) { '2018-03-12T09:06:00Z' }
+ let(:title) { 'title' }
+ let(:y_label) { 'y_label' }
+ let(:query) { 'avg(metric) > 1.0' }
+
+ let(:embed_content) do
+ {
+ panel_groups: [{
+ panels: [{
+ type: 'line-graph',
+ title: title,
+ y_label: y_label,
+ metrics: [{ query_range: query }]
+ }]
+ }]
+ }.to_json
+ end
+
+ let(:payload) do
+ {
+ 'startsAt' => starts_at,
+ 'generatorURL' => "http://host?g0.expr=#{CGI.escape(query)}",
+ 'labels' => {
+ 'gitlab_environment_name' => 'production'
+ },
+ 'annotations' => {
+ 'title' => title,
+ 'gitlab_y_label' => y_label
+ }
+ }
+ end
+
+ let(:dashboard_url_for_alert) do
+ Gitlab::Routing.url_helpers.metrics_dashboard_project_environment_url(
+ project,
+ environment,
+ embed_json: embed_content,
+ embedded: true,
+ end: '2018-03-12T09:36:00Z',
+ start: '2018-03-12T08:36:00Z'
+ )
+ end
+end
+
+RSpec.shared_context 'gitlab-managed prometheus alert attributes' do
+ let_it_be(:prometheus_alert) { create(:prometheus_alert, project: project) }
+ let(:prometheus_metric_id) { prometheus_alert.prometheus_metric_id }
+
+ let(:payload) do
+ {
+ 'startsAt' => '2018-03-12T09:06:00Z',
+ 'labels' => {
+ 'gitlab_alert_id' => prometheus_metric_id
+ }
+ }
+ end
+
+ let(:dashboard_url_for_alert) do
+ Gitlab::Routing.url_helpers.metrics_dashboard_project_prometheus_alert_url(
+ project,
+ prometheus_metric_id,
+ environment_id: prometheus_alert.environment_id,
+ embedded: true,
+ end: '2018-03-12T09:36:00Z',
+ start: '2018-03-12T08:36:00Z'
+ )
+ end
+end
diff --git a/spec/support/shared_contexts/requests/api/graphql/jira_import/jira_projects_context.rb b/spec/support/shared_contexts/requests/api/graphql/jira_import/jira_projects_context.rb
index f0722beb3ed..7f150bed43d 100644
--- a/spec/support/shared_contexts/requests/api/graphql/jira_import/jira_projects_context.rb
+++ b/spec/support/shared_contexts/requests/api/graphql/jira_import/jira_projects_context.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-shared_context 'jira projects request context' do
+RSpec.shared_context 'jira projects request context' do
let(:url) { 'https://jira.example.com' }
let(:username) { 'jira-username' }
let(:password) { 'jira-password' }
@@ -74,6 +74,48 @@ shared_context 'jira projects request context' do
}'
end
+ let_it_be(:all_jira_projects_json) do
+ '[{
+ "expand": "description,lead,issueTypes,url,projectKeys,permissions,insight",
+ "self": "https://gitlab-jira.atlassian.net/rest/api/2/project/10000",
+ "id": "10000",
+ "key": "EX",
+ "name": "Example",
+ "avatarUrls": {
+ "48x48": "https://gitlab-jira.atlassian.net/secure/projectavatar?pid=10000&avatarId=10425",
+ "24x24": "https://gitlab-jira.atlassian.net/secure/projectavatar?size=small&s=small&pid=10000&avatarId=10425",
+ "16x16": "https://gitlab-jira.atlassian.net/secure/projectavatar?size=xsmall&s=xsmall&pid=10000&avatarId=10425",
+ "32x32": "https://gitlab-jira.atlassian.net/secure/projectavatar?size=medium&s=medium&pid=10000&avatarId=10425"
+ },
+ "projectTypeKey": "software",
+ "simplified": false,
+ "style": "classic",
+ "isPrivate": false,
+ "properties": {
+ }
+ },
+ {
+ "expand": "description,lead,issueTypes,url,projectKeys,permissions,insight",
+ "self": "https://gitlab-jira.atlassian.net/rest/api/2/project/10001",
+ "id": "10001",
+ "key": "ABC",
+ "name": "Alphabetical",
+ "avatarUrls": {
+ "48x48": "https://gitlab-jira.atlassian.net/secure/projectavatar?pid=10001&avatarId=10405",
+ "24x24": "https://gitlab-jira.atlassian.net/secure/projectavatar?size=small&s=small&pid=10001&avatarId=10405",
+ "16x16": "https://gitlab-jira.atlassian.net/secure/projectavatar?size=xsmall&s=xsmall&pid=10001&avatarId=10405",
+ "32x32": "https://gitlab-jira.atlassian.net/secure/projectavatar?size=medium&s=medium&pid=10001&avatarId=10405"
+ },
+ "projectTypeKey": "software",
+ "simplified": true,
+ "style": "next-gen",
+ "isPrivate": false,
+ "properties": {
+ },
+ "entityId": "14935009-f8aa-481e-94bc-f7251f320b0e",
+ "uuid": "14935009-f8aa-481e-94bc-f7251f320b0e"
+ }]'
+ end
let_it_be(:empty_jira_projects_json) do
'{
"self": "https://your-domain.atlassian.net/rest/api/2/project/search?startAt=0&maxResults=2",
@@ -86,10 +128,32 @@ shared_context 'jira projects request context' do
}'
end
+ let(:server_info_json) do
+ '{
+ "baseUrl": "https://gitlab-jira.atlassian.net",
+ "version": "1001.0.0-SNAPSHOT",
+ "versionNumbers": [
+ 1001,
+ 0,
+ 0
+ ],
+ "deploymentType": "Cloud",
+ "buildNumber": 100128,
+ "buildDate": "2020-06-03T01:58:44.000-0700",
+ "serverTime": "2020-06-04T06:15:13.686-0700",
+ "scmInfo": "e736ab140ddb281c7cf5dcf9062c9ce2c08b3c1c",
+ "serverTitle": "Jira",
+ "defaultLocale": {
+ "locale": "en_US"
+ }
+ }'
+ end
+
let(:test_url) { "#{url}/rest/api/2/project/search?maxResults=50&query=&startAt=0" }
let(:start_at_20_url) { "#{url}/rest/api/2/project/search?maxResults=50&query=&startAt=20" }
let(:start_at_1_url) { "#{url}/rest/api/2/project/search?maxResults=50&query=&startAt=1" }
let(:max_results_1_url) { "#{url}/rest/api/2/project/search?maxResults=1&query=&startAt=0" }
+ let(:all_projects_url) { "#{url}/rest/api/2/project" }
before do
WebMock.stub_request(:get, test_url).with(basic_auth: [username, password])
@@ -100,5 +164,9 @@ shared_context 'jira projects request context' do
.to_return(body: jira_projects_json, headers: { "Content-Type": "application/json" })
WebMock.stub_request(:get, max_results_1_url).with(basic_auth: [username, password])
.to_return(body: jira_projects_json, headers: { "Content-Type": "application/json" })
+ WebMock.stub_request(:get, all_projects_url).with(basic_auth: [username, password])
+ .to_return(body: all_jira_projects_json, headers: { "Content-Type": "application/json" })
+ WebMock.stub_request(:get, 'https://jira.example.com/rest/api/2/serverInfo')
+ .to_return(status: 200, body: server_info_json, headers: {})
end
end
diff --git a/spec/support/shared_contexts/sentry_error_tracking_shared_context.rb b/spec/support/shared_contexts/sentry_error_tracking_shared_context.rb
index f06de53f0c1..3453f954c9d 100644
--- a/spec/support/shared_contexts/sentry_error_tracking_shared_context.rb
+++ b/spec/support/shared_contexts/sentry_error_tracking_shared_context.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-shared_context 'sentry error tracking context' do
+RSpec.shared_context 'sentry error tracking context' do
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project) }
diff --git a/spec/support/shared_contexts/services_shared_context.rb b/spec/support/shared_contexts/services_shared_context.rb
index bf4eac8f324..899b43ade01 100644
--- a/spec/support/shared_contexts/services_shared_context.rb
+++ b/spec/support/shared_contexts/services_shared_context.rb
@@ -12,6 +12,8 @@ Service.available_services_names.each do |service|
service_attrs_list.inject({}) do |hash, k|
if k =~ /^(token*|.*_token|.*_key)/
hash.merge!(k => 'secrettoken')
+ elsif service == 'confluence' && k == :confluence_url
+ hash.merge!(k => 'https://example.atlassian.net/wiki')
elsif k =~ /^(.*_url|url|webhook)/
hash.merge!(k => "http://example.com")
elsif service_klass.method_defined?("#{k}?")
diff --git a/spec/support/shared_contexts/spam_constants.rb b/spec/support/shared_contexts/spam_constants.rb
index 32371f4b92f..813f9d00123 100644
--- a/spec/support/shared_contexts/spam_constants.rb
+++ b/spec/support/shared_contexts/spam_constants.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-shared_context 'includes Spam constants' do
+RSpec.shared_context 'includes Spam constants' do
before do
stub_const('CONDITIONAL_ALLOW', Spam::SpamConstants::CONDITIONAL_ALLOW)
stub_const('DISALLOW', Spam::SpamConstants::DISALLOW)
diff --git a/spec/support/shared_examples/controllers/import_controller_new_import_ui_shared_examples.rb b/spec/support/shared_examples/controllers/import_controller_new_import_ui_shared_examples.rb
deleted file mode 100644
index 88ad1f6cde2..00000000000
--- a/spec/support/shared_examples/controllers/import_controller_new_import_ui_shared_examples.rb
+++ /dev/null
@@ -1,36 +0,0 @@
-# frozen_string_literal: true
-
-RSpec.shared_examples 'import controller with new_import_ui feature flag' do
- include ImportSpecHelper
-
- context 'with new_import_ui feature flag enabled' do
- let(:group) { create(:group) }
-
- before do
- stub_feature_flags(new_import_ui: true)
- group.add_owner(user)
- end
-
- it "returns variables for json request" do
- project = create(:project, import_type: provider_name, creator_id: user.id)
- stub_client(client_repos_field => [repo])
-
- get :status, format: :json
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(json_response.dig("imported_projects", 0, "id")).to eq(project.id)
- expect(json_response.dig("provider_repos", 0, "id")).to eq(repo_id)
- expect(json_response.dig("namespaces", 0, "id")).to eq(group.id)
- end
-
- it "does not show already added project" do
- project = create(:project, import_type: provider_name, namespace: user.namespace, import_status: :finished, import_source: import_source)
- stub_client(client_repos_field => [repo])
-
- get :status, format: :json
-
- expect(json_response.dig("imported_projects", 0, "id")).to eq(project.id)
- expect(json_response.dig("provider_repos")).to eq([])
- end
- end
-end
diff --git a/spec/support/shared_examples/controllers/import_controller_status_shared_examples.rb b/spec/support/shared_examples/controllers/import_controller_status_shared_examples.rb
new file mode 100644
index 00000000000..ecb9abc5c46
--- /dev/null
+++ b/spec/support/shared_examples/controllers/import_controller_status_shared_examples.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'import controller status' do
+ include ImportSpecHelper
+
+ let(:group) { create(:group) }
+
+ before do
+ group.add_owner(user)
+ end
+
+ it "returns variables for json request" do
+ project = create(:project, import_type: provider_name, creator_id: user.id)
+ stub_client(client_repos_field => [repo])
+
+ get :status, format: :json
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response.dig("imported_projects", 0, "id")).to eq(project.id)
+ expect(json_response.dig("provider_repos", 0, "id")).to eq(repo_id)
+ expect(json_response.dig("namespaces", 0, "id")).to eq(group.id)
+ end
+
+ it "does not show already added project" do
+ project = create(:project, import_type: provider_name, namespace: user.namespace, import_status: :finished, import_source: import_source)
+ stub_client(client_repos_field => [repo])
+
+ get :status, format: :json
+
+ expect(json_response.dig("imported_projects", 0, "id")).to eq(project.id)
+ expect(json_response.dig("provider_repos")).to eq([])
+ end
+end
diff --git a/spec/support/shared_examples/controllers/known_sign_in_shared_examples.rb b/spec/support/shared_examples/controllers/known_sign_in_shared_examples.rb
index 60abb76acec..7f26155f9d6 100644
--- a/spec/support/shared_examples/controllers/known_sign_in_shared_examples.rb
+++ b/spec/support/shared_examples/controllers/known_sign_in_shared_examples.rb
@@ -9,13 +9,38 @@ RSpec.shared_examples 'known sign in' do
user.update!(current_sign_in_ip: ip)
end
- context 'with a valid post' do
- context 'when remote IP does not match user last sign in IP' do
- before do
- stub_user_ip('127.0.0.1')
- stub_remote_ip('169.0.0.1')
- end
+ def stub_cookie(value = user.id)
+ cookies.encrypted[KnownSignIn::KNOWN_SIGN_IN_COOKIE] = {
+ value: value, expires: KnownSignIn::KNOWN_SIGN_IN_COOKIE_EXPIRY
+ }
+ end
+
+ context 'when the remote IP and the last sign in IP match' do
+ before do
+ stub_user_ip('169.0.0.1')
+ stub_remote_ip('169.0.0.1')
+ end
+
+ it 'does not notify the user' do
+ expect(NotificationService).not_to receive(:new)
+ post_action
+ end
+
+ it 'sets/updates the encrypted cookie' do
+ post_action
+
+ expect(cookies.encrypted[KnownSignIn::KNOWN_SIGN_IN_COOKIE]).to eq(user.id)
+ end
+ end
+
+ context 'when the remote IP and the last sign in IP do not match' do
+ before do
+ stub_user_ip('127.0.0.1')
+ stub_remote_ip('169.0.0.1')
+ end
+
+ context 'when the cookie is not previously set' do
it 'notifies the user' do
expect_next_instance_of(NotificationService) do |instance|
expect(instance).to receive(:unknown_sign_in)
@@ -23,37 +48,68 @@ RSpec.shared_examples 'known sign in' do
post_action
end
- end
-
- context 'when remote IP matches an active session' do
- before do
- existing_sessions = ActiveSession.session_ids_for_user(user.id)
- existing_sessions.each { |sessions| ActiveSession.destroy(user, sessions) }
- stub_user_ip('169.0.0.1')
- stub_remote_ip('127.0.0.1')
+ it 'sets the encrypted cookie' do
+ post_action
- ActiveSession.set(user, request)
+ expect(cookies.encrypted[KnownSignIn::KNOWN_SIGN_IN_COOKIE]).to eq(user.id)
end
+ end
- it 'does not notify the user' do
- expect_any_instance_of(NotificationService).not_to receive(:unknown_sign_in)
+ it 'notifies the user when the cookie is expired' do
+ stub_cookie
+
+ Timecop.freeze((KnownSignIn::KNOWN_SIGN_IN_COOKIE_EXPIRY + 1.day).from_now) do
+ expect_next_instance_of(NotificationService) do |instance|
+ expect(instance).to receive(:unknown_sign_in)
+ end
post_action
end
end
- context 'when remote IP address matches last sign in IP' do
+ context 'when notify_on_unknown_sign_in global setting is false' do
before do
- stub_user_ip('127.0.0.1')
- stub_remote_ip('127.0.0.1')
+ stub_application_setting(notify_on_unknown_sign_in: false)
end
it 'does not notify the user' do
- expect_any_instance_of(NotificationService).not_to receive(:unknown_sign_in)
+ expect(NotificationService).not_to receive(:new)
+
+ post_action
+ end
+ it 'does not set a cookie' do
post_action
+
+ expect(cookies.encrypted[KnownSignIn::KNOWN_SIGN_IN_COOKIE]).to be_nil
+ end
+ end
+
+ it 'notifies the user when the cookie is for another user' do
+ stub_cookie(create(:user).id)
+
+ expect_next_instance_of(NotificationService) do |instance|
+ expect(instance).to receive(:unknown_sign_in)
end
+
+ post_action
+ end
+
+ it 'does not notify the user when remote IP matches an active session' do
+ ActiveSession.set(user, request)
+
+ expect(NotificationService).not_to receive(:new)
+
+ post_action
+ end
+
+ it 'does not notify the user when the cookie is present and not expired' do
+ stub_cookie
+
+ expect(NotificationService).not_to receive(:new)
+
+ post_action
end
end
end
diff --git a/spec/support/shared_examples/controllers/metrics/dashboard/prometheus_api_proxy_shared_examples.rb b/spec/support/shared_examples/controllers/metrics/dashboard/prometheus_api_proxy_shared_examples.rb
new file mode 100644
index 00000000000..94cd6971f7c
--- /dev/null
+++ b/spec/support/shared_examples/controllers/metrics/dashboard/prometheus_api_proxy_shared_examples.rb
@@ -0,0 +1,147 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples_for 'metrics dashboard prometheus api proxy' do
+ let(:service_params) { [proxyable, 'GET', 'query', expected_params] }
+ let(:service_result) { { status: :success, body: prometheus_body } }
+ let(:prometheus_proxy_service) { instance_double(Prometheus::ProxyService) }
+ let(:proxyable_params) do
+ {
+ id: proxyable.id.to_s
+ }
+ end
+ let(:expected_params) do
+ ActionController::Parameters.new(
+ prometheus_proxy_params(
+ proxy_path: 'query',
+ controller: described_class.controller_path,
+ action: 'prometheus_proxy'
+ )
+ ).permit!
+ end
+
+ before do
+ allow_next_instance_of(Prometheus::ProxyService, *service_params) do |proxy_service|
+ allow(proxy_service).to receive(:execute).and_return(service_result)
+ end
+ end
+
+ context 'with valid requests' do
+ context 'with success result' do
+ let(:prometheus_body) { '{"status":"success"}' }
+ let(:prometheus_json_body) { Gitlab::Json.parse(prometheus_body) }
+
+ it 'returns prometheus response' do
+ get :prometheus_proxy, params: prometheus_proxy_params
+
+ expect(Prometheus::ProxyService).to have_received(:new).with(*service_params)
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response).to eq(prometheus_json_body)
+ end
+
+ context 'with nil query' do
+ let(:params_without_query) do
+ prometheus_proxy_params.except(:query)
+ end
+
+ before do
+ expected_params.delete(:query)
+ end
+
+ it 'does not raise error' do
+ get :prometheus_proxy, params: params_without_query
+
+ expect(Prometheus::ProxyService).to have_received(:new).with(*service_params)
+ end
+ end
+ end
+
+ context 'with nil result' do
+ let(:service_result) { nil }
+
+ it 'returns 204 no_content' do
+ get :prometheus_proxy, params: prometheus_proxy_params
+
+ expect(json_response['status']).to eq(_('processing'))
+ expect(json_response['message']).to eq(_('Not ready yet. Try again later.'))
+ expect(response).to have_gitlab_http_status(:no_content)
+ end
+ end
+
+ context 'with 404 result' do
+ let(:service_result) { { http_status: 404, status: :success, body: '{"body": "value"}' } }
+
+ it 'returns body' do
+ get :prometheus_proxy, params: prometheus_proxy_params
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ expect(json_response['body']).to eq('value')
+ end
+ end
+
+ context 'with error result' do
+ context 'with http_status' do
+ let(:service_result) do
+ { http_status: :service_unavailable, status: :error, message: 'error message' }
+ end
+
+ it 'sets the http response status code' do
+ get :prometheus_proxy, params: prometheus_proxy_params
+
+ expect(response).to have_gitlab_http_status(:service_unavailable)
+ expect(json_response['status']).to eq('error')
+ expect(json_response['message']).to eq('error message')
+ end
+ end
+
+ context 'without http_status' do
+ let(:service_result) { { status: :error, message: 'error message' } }
+
+ it 'returns bad_request' do
+ get :prometheus_proxy, params: prometheus_proxy_params
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ expect(json_response['status']).to eq('error')
+ expect(json_response['message']).to eq('error message')
+ end
+ end
+ end
+ end
+
+ context 'with inappropriate requests' do
+ let(:prometheus_body) { nil }
+
+ context 'without correct permissions' do
+ let(:user2) { create(:user) }
+
+ before do
+ sign_out(user)
+ sign_in(user2)
+ end
+
+ it 'returns 404' do
+ get :prometheus_proxy, params: prometheus_proxy_params
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+ end
+
+ context 'with invalid proxyable id' do
+ let(:prometheus_body) { nil }
+
+ it 'returns 404' do
+ get :prometheus_proxy, params: prometheus_proxy_params(id: proxyable.id + 1)
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+
+ private
+
+ def prometheus_proxy_params(params = {})
+ {
+ proxy_path: 'query',
+ query: '1'
+ }.merge(proxyable_params).merge(params)
+ end
+end
diff --git a/spec/support/shared_examples/controllers/metrics_dashboard_shared_examples.rb b/spec/support/shared_examples/controllers/metrics_dashboard_shared_examples.rb
new file mode 100644
index 00000000000..cb8f6721d66
--- /dev/null
+++ b/spec/support/shared_examples/controllers/metrics_dashboard_shared_examples.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples_for 'GET #metrics_dashboard correctly formatted response' do
+ it 'returns a json object with the correct keys' do
+ get :metrics_dashboard, params: metrics_dashboard_req_params, format: :json
+
+ # Exclude `all_dashboards` to handle separately, at spec/controllers/projects/environments_controller_spec.rb:565
+ # because `all_dashboards` key is not part of expected shared behavior
+ found_keys = json_response.keys - ['all_dashboards']
+
+ expect(response).to have_gitlab_http_status(status_code)
+ expect(found_keys).to contain_exactly(*expected_keys)
+ end
+end
+
+RSpec.shared_examples_for 'GET #metrics_dashboard for dashboard' do |dashboard_name|
+ let(:expected_keys) { %w(dashboard status metrics_data) }
+ let(:status_code) { :ok }
+
+ it_behaves_like 'GET #metrics_dashboard correctly formatted response'
+
+ it 'returns correct dashboard' do
+ get :metrics_dashboard, params: metrics_dashboard_req_params, format: :json
+
+ expect(json_response['dashboard']['dashboard']).to eq(dashboard_name)
+ end
+end
diff --git a/spec/support/shared_examples/controllers/namespace_storage_limit_alert_shared_examples.rb b/spec/support/shared_examples/controllers/namespace_storage_limit_alert_shared_examples.rb
deleted file mode 100644
index 7885eb6c1f8..00000000000
--- a/spec/support/shared_examples/controllers/namespace_storage_limit_alert_shared_examples.rb
+++ /dev/null
@@ -1,53 +0,0 @@
-# frozen_string_literal: true
-
-RSpec.shared_examples 'namespace storage limit alert' do
- let(:alert_level) { :info }
-
- before do
- allow_next_instance_of(Namespaces::CheckStorageSizeService, namespace, user) do |check_storage_size_service|
- expect(check_storage_size_service).to receive(:execute).and_return(
- ServiceResponse.success(
- payload: {
- alert_level: alert_level,
- usage_message: "Usage",
- explanation_message: "Explanation",
- root_namespace: namespace
- }
- )
- )
- end
-
- allow(controller).to receive(:current_user).and_return(user)
- end
-
- render_views
-
- it 'does render' do
- subject
-
- expect(response.body).to match(/Explanation/)
- expect(response.body).to have_css('.js-namespace-storage-alert-dismiss')
- end
-
- context 'when alert_level is error' do
- let(:alert_level) { :error }
-
- it 'does not render a dismiss button' do
- subject
-
- expect(response.body).not_to have_css('.js-namespace-storage-alert-dismiss')
- end
- end
-
- context 'when cookie is set' do
- before do
- cookies["hide_storage_limit_alert_#{namespace.id}_info"] = 'true'
- end
-
- it 'does not render alert' do
- subject
-
- expect(response.body).not_to match(/Explanation/)
- end
- end
-end
diff --git a/spec/support/shared_examples/controllers/snippet_blob_shared_examples.rb b/spec/support/shared_examples/controllers/snippet_blob_shared_examples.rb
new file mode 100644
index 00000000000..c3e8f807afb
--- /dev/null
+++ b/spec/support/shared_examples/controllers/snippet_blob_shared_examples.rb
@@ -0,0 +1,49 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'raw snippet blob' do
+ context 'with valid params' do
+ before do
+ subject
+ end
+
+ it 'delivers file with correct Workhorse headers' do
+ expect(response.header['Content-Type']).to eq('text/plain; charset=utf-8')
+ expect(response.header[Gitlab::Workhorse::DETECT_HEADER]).to eq 'true'
+ expect(response.header[Gitlab::Workhorse::SEND_DATA_HEADER]).to start_with('git-blob:')
+ end
+
+ it 'responds with status 200' do
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+ end
+
+ context 'with invalid file path' do
+ let(:filepath) { 'doesnotexist' }
+
+ it_behaves_like 'returning response status', :not_found
+ end
+
+ context 'with invalid ref' do
+ let(:ref) { 'doesnotexist' }
+
+ it_behaves_like 'returning response status', :not_found
+ end
+
+ it_behaves_like 'content disposition headers'
+end
+
+RSpec.shared_examples 'raw snippet without repository' do |unauthorized_status|
+ context 'when authorized' do
+ it 'returns a 422' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:unprocessable_entity)
+ end
+ end
+
+ context 'when unauthorized' do
+ let(:visibility) { :private }
+
+ it_behaves_like 'returning response status', unauthorized_status
+ end
+end
diff --git a/spec/support/shared_examples/controllers/snippets_sort_order_shared_examples.rb b/spec/support/shared_examples/controllers/snippets_sort_order_shared_examples.rb
new file mode 100644
index 00000000000..aa4d78b23f4
--- /dev/null
+++ b/spec/support/shared_examples/controllers/snippets_sort_order_shared_examples.rb
@@ -0,0 +1,41 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'snippets sort order' do
+ let(:params) { {} }
+ let(:sort_argument) { {} }
+ let(:sort_params) { params.merge(sort_argument)}
+
+ before do
+ sign_in(user)
+
+ stub_snippet_counter
+ end
+
+ subject { get :index, params: sort_params }
+
+ context 'when no sort param is provided' do
+ it 'calls SnippetsFinder with updated_at sort option' do
+ expect(SnippetsFinder).to receive(:new).with(user,
+ hash_including(sort: 'updated_desc')).and_call_original
+
+ subject
+ end
+ end
+
+ context 'when sort param is provided' do
+ let(:order) { 'created_desc' }
+ let(:sort_argument) { { sort: order } }
+
+ it 'calls SnippetsFinder with the given sort param' do
+ expect(SnippetsFinder).to receive(:new).with(user,
+ hash_including(sort: order)).and_call_original
+
+ subject
+ end
+ end
+
+ def stub_snippet_counter
+ allow(Snippets::CountService)
+ .to receive(:new).and_return(double(:count_service, execute: {}))
+ end
+end
diff --git a/spec/support/shared_examples/controllers/unique_visits_shared_examples.rb b/spec/support/shared_examples/controllers/unique_visits_shared_examples.rb
new file mode 100644
index 00000000000..90588756eb0
--- /dev/null
+++ b/spec/support/shared_examples/controllers/unique_visits_shared_examples.rb
@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'tracking unique visits' do |method|
+ it 'tracks unique visit if the format is HTML' do
+ expect_any_instance_of(Gitlab::Analytics::UniqueVisits).to receive(:track_visit).with(instance_of(String), target_id)
+
+ get method, params: request_params, format: :html
+ end
+
+ it 'tracks unique visit if DNT is not enabled' do
+ expect_any_instance_of(Gitlab::Analytics::UniqueVisits).to receive(:track_visit).with(instance_of(String), target_id)
+ request.headers['DNT'] = '0'
+
+ get method, params: request_params, format: :html
+ end
+
+ it 'does not track unique visit if DNT is enabled' do
+ expect_any_instance_of(Gitlab::Analytics::UniqueVisits).not_to receive(:track_visit)
+ request.headers['DNT'] = '1'
+
+ get method, params: request_params, format: :html
+ end
+
+ it 'does not track unique visit if the format is JSON' do
+ expect_any_instance_of(Gitlab::Analytics::UniqueVisits).not_to receive(:track_visit)
+
+ get method, params: request_params, format: :json
+ end
+end
diff --git a/spec/support/shared_examples/controllers/wiki_actions_shared_examples.rb b/spec/support/shared_examples/controllers/wiki_actions_shared_examples.rb
index b5f2c0d07bf..4df3139d56e 100644
--- a/spec/support/shared_examples/controllers/wiki_actions_shared_examples.rb
+++ b/spec/support/shared_examples/controllers/wiki_actions_shared_examples.rb
@@ -104,6 +104,35 @@ RSpec.shared_examples 'wiki controller actions' do
end
end
+ describe 'GET #diff' do
+ context 'when commit exists' do
+ it 'renders the diff' do
+ get :diff, params: routing_params.merge(id: wiki_title, version_id: wiki.repository.commit.id)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to render_template('shared/wikis/diff')
+ expect(assigns(:diffs)).to be_a(Gitlab::Diff::FileCollection::Base)
+ expect(assigns(:diff_notes_disabled)).to be(true)
+ end
+ end
+
+ context 'when commit does not exist' do
+ it 'returns a 404 error' do
+ get :diff, params: routing_params.merge(id: wiki_title, version_id: 'invalid')
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+
+ context 'when page does not exist' do
+ it 'returns a 404 error' do
+ get :diff, params: routing_params.merge(id: 'invalid')
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+ end
+
describe 'GET #show' do
render_views
@@ -118,6 +147,7 @@ RSpec.shared_examples 'wiki controller actions' do
subject
expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to render_template('shared/wikis/show')
expect(assigns(:page).title).to eq(wiki_title)
expect(assigns(:sidebar_wiki_entries)).to contain_exactly(an_instance_of(WikiPage))
expect(assigns(:sidebar_limited)).to be(false)
@@ -130,6 +160,7 @@ RSpec.shared_examples 'wiki controller actions' do
subject
expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to render_template('shared/wikis/show')
expect(flash[:notice]).to eq(_('The content of this page is not encoded in UTF-8. Edits can only be made via the Git repository.'))
end
end
@@ -138,19 +169,37 @@ RSpec.shared_examples 'wiki controller actions' do
context 'when the page does not exist' do
let(:id) { 'does not exist' }
- before do
- subject
- end
+ context 'when the user can create pages' do
+ before do
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to render_template('shared/wikis/edit')
+ end
+
+ it 'builds a new wiki page with the id as the title' do
+ expect(assigns(:page).title).to eq(id)
+ end
+
+ context 'when a random_title param is present' do
+ let(:random_title) { true }
- it 'builds a new wiki page with the id as the title' do
- expect(assigns(:page).title).to eq(id)
+ it 'builds a new wiki page with no title' do
+ expect(assigns(:page).title).to be_empty
+ end
+ end
end
- context 'when a random_title param is present' do
- let(:random_title) { true }
+ context 'when the user cannot create pages' do
+ before do
+ sign_out(:user)
+ end
- it 'builds a new wiki page with no title' do
- expect(assigns(:page).title).to be_empty
+ it 'shows the empty state' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to render_template('shared/wikis/empty')
end
end
end
@@ -166,6 +215,7 @@ RSpec.shared_examples 'wiki controller actions' do
it 'delivers the file with the correct headers' do
subject
+ expect(response).to have_gitlab_http_status(:ok)
expect(response.headers['Content-Disposition']).to match(/^inline/)
expect(response.headers[Gitlab::Workhorse::DETECT_HEADER]).to eq('true')
expect(response.cache_control[:public]).to be(false)
@@ -179,12 +229,31 @@ RSpec.shared_examples 'wiki controller actions' do
it 'renders json in a correct format' do
post :preview_markdown, params: routing_params.merge(id: 'page/path', text: '*Markdown* text')
+ expect(response).to have_gitlab_http_status(:ok)
expect(json_response.keys).to match_array(%w(body references))
end
end
- describe 'GET #edit' do
- subject { get(:edit, params: routing_params.merge(id: wiki_title)) }
+ shared_examples 'edit action' do
+ context 'when the page does not exist' do
+ let(:id_param) { 'invalid' }
+
+ it 'redirects to show' do
+ subject
+
+ expect(response).to redirect_to_wiki(wiki, 'invalid')
+ end
+ end
+
+ context 'when id param is blank' do
+ let(:id_param) { ' ' }
+
+ it 'redirects to the home page' do
+ subject
+
+ expect(response).to redirect_to_wiki(wiki, 'home')
+ end
+ end
context 'when page content encoding is invalid' do
it 'redirects to show' do
@@ -208,6 +277,14 @@ RSpec.shared_examples 'wiki controller actions' do
expect(response).to redirect_to_wiki(wiki, page)
end
end
+ end
+
+ describe 'GET #edit' do
+ let(:id_param) { wiki_title }
+
+ subject { get(:edit, params: routing_params.merge(id: id_param)) }
+
+ it_behaves_like 'edit action'
context 'when page content encoding is valid' do
render_views
@@ -224,23 +301,17 @@ RSpec.shared_examples 'wiki controller actions' do
describe 'PATCH #update' do
let(:new_title) { 'New title' }
let(:new_content) { 'New content' }
+ let(:id_param) { wiki_title }
subject do
patch(:update,
params: routing_params.merge(
- id: wiki_title,
+ id: id_param,
wiki: { title: new_title, content: new_content }
))
end
- context 'when page content encoding is invalid' do
- it 'redirects to show' do
- allow(controller).to receive(:valid_encoding?).and_return(false)
-
- subject
- expect(response).to redirect_to_wiki(wiki, wiki.list_pages.first)
- end
- end
+ it_behaves_like 'edit action'
context 'when page content encoding is valid' do
render_views
diff --git a/spec/support/shared_examples/create_alert_issue_shared_examples.rb b/spec/support/shared_examples/create_alert_issue_shared_examples.rb
new file mode 100644
index 00000000000..9f4e1c4335a
--- /dev/null
+++ b/spec/support/shared_examples/create_alert_issue_shared_examples.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'create alert issue sets issue labels' do
+ let(:title) { IncidentManagement::CreateIncidentLabelService::LABEL_PROPERTIES[:title] }
+ let!(:label) { create(:label, project: project, title: title) }
+ let(:label_service) { instance_double(IncidentManagement::CreateIncidentLabelService, execute: label_service_response) }
+
+ before do
+ allow(IncidentManagement::CreateIncidentLabelService).to receive(:new).with(project, user).and_return(label_service)
+ end
+
+ context 'when create incident label responds with success' do
+ let(:label_service_response) { ServiceResponse.success(payload: { label: label }) }
+
+ it 'adds label to issue' do
+ expect(issue.labels).to eq([label])
+ end
+ end
+
+ context 'when create incident label responds with error' do
+ let(:label_service_response) { ServiceResponse.error(payload: { label: label }, message: 'label error') }
+
+ it 'creates an issue without labels' do
+ expect(issue.labels).to be_empty
+ end
+ end
+end
diff --git a/spec/support/shared_examples/features/discussion_comments_shared_example.rb b/spec/support/shared_examples/features/discussion_comments_shared_example.rb
index 6007798c290..9fc5d8933e5 100644
--- a/spec/support/shared_examples/features/discussion_comments_shared_example.rb
+++ b/spec/support/shared_examples/features/discussion_comments_shared_example.rb
@@ -266,7 +266,7 @@ RSpec.shared_examples 'thread comments' do |resource_name|
end
end
- it 'has "Comment" selected when opening the menu', quarantine: 'https://gitlab.com/gitlab-org/gitlab/issues/196825' do
+ it 'has "Comment" selected when opening the menu', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/196825' do
find(toggle_selector).click
find("#{menu_selector} li", match: :first)
diff --git a/spec/support/shared_examples/features/editable_merge_request_shared_examples.rb b/spec/support/shared_examples/features/editable_merge_request_shared_examples.rb
index 964c80007b0..487c38da7da 100644
--- a/spec/support/shared_examples/features/editable_merge_request_shared_examples.rb
+++ b/spec/support/shared_examples/features/editable_merge_request_shared_examples.rb
@@ -53,13 +53,17 @@ RSpec.shared_examples 'an editable merge request' do
find('#merge_request_description').native.send_keys('')
fill_in 'merge_request_description', with: user.to_reference[0..4]
- wait_for_requests
-
page.within('.atwho-view') do
expect(page).to have_content(user2.name)
end
end
+ it 'description has quick action autocomplete', :js do
+ find('#merge_request_description').native.send_keys('/')
+
+ expect(page).to have_selector('.atwho-container')
+ end
+
it 'has class js-quick-submit in form' do
expect(page).to have_selector('.js-quick-submit')
end
diff --git a/spec/support/shared_examples/features/error_tracking_shared_example.rb b/spec/support/shared_examples/features/error_tracking_shared_example.rb
index 1cd05b22ae9..ae7d62f31a2 100644
--- a/spec/support/shared_examples/features/error_tracking_shared_example.rb
+++ b/spec/support/shared_examples/features/error_tracking_shared_example.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-shared_examples 'error tracking index page' do
+RSpec.shared_examples 'error tracking index page' do
it 'renders the error index page', quarantine: { issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/217810' } do
within('div.js-title-container') do
expect(page).to have_content(project.namespace.name)
@@ -33,7 +33,7 @@ shared_examples 'error tracking index page' do
end
end
-shared_examples 'expanded stack trace context' do |selected_line: nil, expected_line: 1|
+RSpec.shared_examples 'expanded stack trace context' do |selected_line: nil, expected_line: 1|
it 'expands the stack trace context', quarantine: { issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/217810' } do
within('div.stacktrace') do
find("div.file-holder:nth-child(#{selected_line}) svg.ic-chevron-right").click if selected_line
@@ -48,7 +48,7 @@ shared_examples 'expanded stack trace context' do |selected_line: nil, expected_
end
end
-shared_examples 'error tracking show page' do
+RSpec.shared_examples 'error tracking show page' do
it 'renders the error details', quarantine: { issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/217810' } do
content = page.find(".content")
nav = page.find("nav.breadcrumbs")
diff --git a/spec/support/shared_examples/features/master_manages_access_requests_shared_example.rb b/spec/support/shared_examples/features/master_manages_access_requests_shared_example.rb
index 98010150e65..00ce690d2e3 100644
--- a/spec/support/shared_examples/features/master_manages_access_requests_shared_example.rb
+++ b/spec/support/shared_examples/features/master_manages_access_requests_shared_example.rb
@@ -35,7 +35,12 @@ RSpec.shared_examples 'Maintainer manages access requests' do
expect_visible_access_request(entity, user)
- accept_confirm { click_on 'Deny access' }
+ # Open modal
+ click_on 'Deny access request'
+
+ expect(page).not_to have_field "Also unassign this user from related issues and merge requests"
+
+ click_on 'Deny access request'
expect_no_visible_access_request(entity, user)
expect(page).not_to have_content user.name
diff --git a/spec/support/shared_examples/graphql/jira_import/jira_import_resolver_shared_examples.rb b/spec/support/shared_examples/graphql/jira_import/jira_import_resolver_shared_examples.rb
index 2b96010477c..b2047f1d32c 100644
--- a/spec/support/shared_examples/graphql/jira_import/jira_import_resolver_shared_examples.rb
+++ b/spec/support/shared_examples/graphql/jira_import/jira_import_resolver_shared_examples.rb
@@ -1,12 +1,12 @@
# frozen_string_literal: true
-shared_examples 'no Jira import data present' do
+RSpec.shared_examples 'no Jira import data present' do
it 'returns none' do
expect(resolve_imports).to eq JiraImportState.none
end
end
-shared_examples 'no Jira import access' do
+RSpec.shared_examples 'no Jira import access' do
it 'raises error' do
expect do
resolve_imports
diff --git a/spec/support/shared_examples/graphql/mutation_shared_examples.rb b/spec/support/shared_examples/graphql/mutation_shared_examples.rb
index 022d41c0bdd..86d2bb6c747 100644
--- a/spec/support/shared_examples/graphql/mutation_shared_examples.rb
+++ b/spec/support/shared_examples/graphql/mutation_shared_examples.rb
@@ -7,13 +7,23 @@
#
# There must be a method or let called `mutation` defined that executes
# the mutation.
-RSpec.shared_examples 'a mutation that returns top-level errors' do |errors:|
+RSpec.shared_examples 'a mutation that returns top-level errors' do |errors: []|
+ let(:match_errors) { eq(errors) }
+
it do
post_graphql_mutation(mutation, current_user: current_user)
error_messages = graphql_errors.map { |e| e['message'] }
- expect(error_messages).to eq(errors)
+ expect(error_messages).to match_errors
+ end
+end
+
+RSpec.shared_examples 'an invalid argument to the mutation' do |argument_name:|
+ it_behaves_like 'a mutation that returns top-level errors' do
+ let(:match_errors) do
+ contain_exactly(include("invalid value for #{GraphqlHelpers.fieldnamerize(argument_name)}"))
+ end
end
end
diff --git a/spec/support/shared_examples/graphql/projects/services_resolver_shared_examples.rb b/spec/support/shared_examples/graphql/projects/services_resolver_shared_examples.rb
index 4bed322564a..94b7ed1618d 100644
--- a/spec/support/shared_examples/graphql/projects/services_resolver_shared_examples.rb
+++ b/spec/support/shared_examples/graphql/projects/services_resolver_shared_examples.rb
@@ -1,12 +1,12 @@
# frozen_string_literal: true
-shared_examples 'no project services' do
+RSpec.shared_examples 'no project services' do
it 'returns empty collection' do
expect(resolve_services).to eq []
end
end
-shared_examples 'cannot access project services' do
+RSpec.shared_examples 'cannot access project services' do
it 'raises error' do
expect do
resolve_services
diff --git a/spec/support/shared_examples/graphql/resolves_issuable_shared_examples.rb b/spec/support/shared_examples/graphql/resolves_issuable_shared_examples.rb
index 58cd3d21f66..67d1c2a8254 100644
--- a/spec/support/shared_examples/graphql/resolves_issuable_shared_examples.rb
+++ b/spec/support/shared_examples/graphql/resolves_issuable_shared_examples.rb
@@ -1,7 +1,12 @@
# frozen_string_literal: true
RSpec.shared_examples 'resolving an issuable in GraphQL' do |type|
- subject { mutation.resolve_issuable(type: type, parent_path: parent.full_path, iid: issuable.iid) }
+ include GraphqlHelpers
+
+ let(:parent_path) { parent.full_path }
+ let(:iid) { issuable.iid }
+
+ subject(:result) { mutation.resolve_issuable(type: type, parent_path: parent_path, iid: iid) }
context 'when user has access' do
before do
@@ -9,37 +14,23 @@ RSpec.shared_examples 'resolving an issuable in GraphQL' do |type|
end
it 'resolves issuable by iid' do
- result = type == :merge_request ? subject.sync : subject
expect(result).to eq(issuable)
end
- it 'uses the correct Resolver to resolve issuable' do
- resolver_class = "Resolvers::#{type.to_s.classify.pluralize}Resolver".constantize
- resolve_method = type == :epic ? :resolve_group : :resolve_project
- resolved_parent = mutation.send(resolve_method, full_path: parent.full_path)
-
- allow(mutation).to receive(resolve_method)
- .with(full_path: parent.full_path)
- .and_return(resolved_parent)
-
- expect(resolver_class.single).to receive(:new)
- .with(object: resolved_parent, context: context, field: nil)
- .and_call_original
-
- subject
- end
-
- it 'returns nil if issuable is not found' do
- result = mutation.resolve_issuable(type: type, parent_path: parent.full_path, iid: "100")
- result = result.respond_to?(:sync) ? result.sync : result
+ context 'the IID does not refer to a valid issuable' do
+ let(:iid) { '100' }
- expect(result).to be_nil
+ it 'returns nil' do
+ expect(result).to be_nil
+ end
end
- it 'returns nil if parent path is not present' do
- result = mutation.resolve_issuable(type: type, parent_path: "", iid: issuable.iid)
+ context 'the parent path is not present' do
+ let(:parent_path) { '' }
- expect(result).to be_nil
+ it 'returns nil' do
+ expect(result).to be_nil
+ end
end
end
end
diff --git a/spec/support/shared_examples/helm_commands_shared_examples.rb b/spec/support/shared_examples/helm_commands_shared_examples.rb
index f0624fbf29f..0a94c6648cc 100644
--- a/spec/support/shared_examples/helm_commands_shared_examples.rb
+++ b/spec/support/shared_examples/helm_commands_shared_examples.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-shared_examples 'helm command generator' do
+RSpec.shared_examples 'helm command generator' do
describe '#generate_script' do
let(:helm_setup) do
<<~EOS
@@ -14,7 +14,7 @@ shared_examples 'helm command generator' do
end
end
-shared_examples 'helm command' do
+RSpec.shared_examples 'helm command' do
describe '#rbac?' do
subject { command.rbac? }
diff --git a/spec/support/shared_examples/lib/gitlab/background_migration/mentions_migration_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/background_migration/mentions_migration_shared_examples.rb
index a40c38106e2..af65b61021c 100644
--- a/spec/support/shared_examples/lib/gitlab/background_migration/mentions_migration_shared_examples.rb
+++ b/spec/support/shared_examples/lib/gitlab/background_migration/mentions_migration_shared_examples.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-shared_examples 'resource mentions migration' do |migration_class, resource_class|
+RSpec.shared_examples 'resource mentions migration' do |migration_class, resource_class|
it 'migrates resource mentions' do
join = migration_class::JOIN
conditions = migration_class::QUERY_CONDITIONS
@@ -21,7 +21,7 @@ shared_examples 'resource mentions migration' do |migration_class, resource_clas
end
end
-shared_examples 'resource notes mentions migration' do |migration_class, resource_class|
+RSpec.shared_examples 'resource notes mentions migration' do |migration_class, resource_class|
it 'migrates mentions from note' do
join = migration_class::JOIN
conditions = migration_class::QUERY_CONDITIONS
@@ -56,7 +56,7 @@ shared_examples 'resource notes mentions migration' do |migration_class, resourc
end
end
-shared_examples 'schedules resource mentions migration' do |resource_class, is_for_notes|
+RSpec.shared_examples 'schedules resource mentions migration' do |resource_class, is_for_notes|
before do
stub_const("#{described_class.name}::BATCH_SIZE", 1)
end
diff --git a/spec/support/shared_examples/lib/gitlab/cycle_analytics/base_stage_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/cycle_analytics/base_stage_shared_examples.rb
index 14292f70228..d76089d56dd 100644
--- a/spec/support/shared_examples/lib/gitlab/cycle_analytics/base_stage_shared_examples.rb
+++ b/spec/support/shared_examples/lib/gitlab/cycle_analytics/base_stage_shared_examples.rb
@@ -4,7 +4,7 @@ require 'spec_helper'
ISSUES_MEDIAN = 30.minutes.to_i
-shared_examples 'base stage' do
+RSpec.shared_examples 'base stage' do
let(:stage) { described_class.new(options: { project: double }) }
before do
@@ -35,7 +35,7 @@ shared_examples 'base stage' do
end
end
-shared_examples 'calculate #median with date range' do
+RSpec.shared_examples 'calculate #median with date range' do
context 'when valid date range is given' do
before do
stage_options[:from] = 5.days.ago
@@ -55,7 +55,7 @@ shared_examples 'calculate #median with date range' do
end
end
-shared_examples 'Gitlab::Analytics::CycleAnalytics::DataCollector backend examples' do
+RSpec.shared_examples 'Gitlab::Analytics::CycleAnalytics::DataCollector backend examples' do
let(:stage_params) { Gitlab::Analytics::CycleAnalytics::DefaultStages.send("params_for_#{stage_name}_stage").merge(project: project) }
let(:stage) { Analytics::CycleAnalytics::ProjectStage.new(stage_params) }
let(:data_collector) { Gitlab::Analytics::CycleAnalytics::DataCollector.new(stage: stage, params: { from: stage_options[:from], current_user: project.creator }) }
diff --git a/spec/support/shared_examples/lib/gitlab/cycle_analytics/default_query_config_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/cycle_analytics/default_query_config_shared_examples.rb
index c053af010b3..4f648b27ea2 100644
--- a/spec/support/shared_examples/lib/gitlab/cycle_analytics/default_query_config_shared_examples.rb
+++ b/spec/support/shared_examples/lib/gitlab/cycle_analytics/default_query_config_shared_examples.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-shared_examples 'default query config' do
+RSpec.shared_examples 'default query config' do
let(:project) { create(:project) }
let(:event) { described_class.new(stage: stage_name, options: { from: 1.day.ago, project: project }) }
diff --git a/spec/support/shared_examples/lib/gitlab/cycle_analytics/event_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/cycle_analytics/event_shared_examples.rb
index a00359ce979..d0e41605e00 100644
--- a/spec/support/shared_examples/lib/gitlab/cycle_analytics/event_shared_examples.rb
+++ b/spec/support/shared_examples/lib/gitlab/cycle_analytics/event_shared_examples.rb
@@ -8,6 +8,7 @@ RSpec.shared_examples_for 'cycle analytics event' do
it { expect(described_class.identifier).to be_a_kind_of(Symbol) }
it { expect(instance.object_type.ancestors).to include(ApplicationRecord) }
it { expect(instance).to respond_to(:timestamp_projection) }
+ it { expect(instance.column_list).to be_a_kind_of(Array) }
describe '#apply_query_customization' do
it 'expects an ActiveRecord::Relation object as argument and returns a modified version of it' do
diff --git a/spec/support/shared_examples/lib/gitlab/diff_file_collections_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/diff_file_collections_shared_examples.rb
index a1cdd054f32..e43ce936b90 100644
--- a/spec/support/shared_examples/lib/gitlab/diff_file_collections_shared_examples.rb
+++ b/spec/support/shared_examples/lib/gitlab/diff_file_collections_shared_examples.rb
@@ -10,7 +10,7 @@ RSpec.shared_examples 'diff statistics' do |test_include_stats_flag: true|
end
end
- context 'when should request diff stats' do
+ context 'when include_stats is true' do
it 'Repository#diff_stats is called' do
expect(diffable.project.repository)
.to receive(:diff_stats)
@@ -59,43 +59,87 @@ RSpec.shared_examples 'unfoldable diff' do
end
RSpec.shared_examples 'cacheable diff collection' do
- let(:cache) { instance_double(Gitlab::Diff::HighlightCache) }
+ let(:highlight_cache) { instance_double(Gitlab::Diff::HighlightCache, write_if_empty: true, clear: nil, decorate: nil) }
+ let(:stats_cache) { instance_double(Gitlab::Diff::StatsCache, read: nil, write_if_empty: true, clear: nil) }
before do
- expect(Gitlab::Diff::HighlightCache).to receive(:new).with(subject) { cache }
+ expect(Gitlab::Diff::HighlightCache).to receive(:new).with(subject) { highlight_cache }
end
describe '#write_cache' do
+ before do
+ expect(Gitlab::Diff::StatsCache).to receive(:new).with(cachable_key: diffable.cache_key) { stats_cache }
+ end
+
it 'calls Gitlab::Diff::HighlightCache#write_if_empty' do
- expect(cache).to receive(:write_if_empty).once
+ expect(highlight_cache).to receive(:write_if_empty).once
+
+ subject.write_cache
+ end
+
+ it 'calls Gitlab::Diff::StatsCache#write_if_empty with diff stats' do
+ diff_stats = Gitlab::Git::DiffStatsCollection.new([])
+
+ expect(diffable.project.repository)
+ .to receive(:diff_stats).and_return(diff_stats)
+
+ expect(stats_cache).to receive(:write_if_empty).once.with(diff_stats)
subject.write_cache
end
end
describe '#clear_cache' do
+ before do
+ expect(Gitlab::Diff::StatsCache).to receive(:new).with(cachable_key: diffable.cache_key) { stats_cache }
+ end
+
it 'calls Gitlab::Diff::HighlightCache#clear' do
- expect(cache).to receive(:clear).once
+ expect(highlight_cache).to receive(:clear).once
subject.clear_cache
end
- end
- describe '#cache_key' do
- it 'calls Gitlab::Diff::HighlightCache#key' do
- expect(cache).to receive(:key).once
+ it 'calls Gitlab::Diff::StatsCache#clear' do
+ expect(stats_cache).to receive(:clear).once
- subject.cache_key
+ subject.clear_cache
end
end
describe '#diff_files' do
+ before do
+ expect(Gitlab::Diff::StatsCache).to receive(:new).with(cachable_key: diffable.cache_key) { stats_cache }
+ end
+
it 'calls Gitlab::Diff::HighlightCache#decorate' do
- expect(cache).to receive(:decorate)
+ expect(highlight_cache).to receive(:decorate)
.with(instance_of(Gitlab::Diff::File))
.exactly(cacheable_files_count).times
subject.diff_files
end
+
+ context 'when there are stats cached' do
+ before do
+ allow(stats_cache).to receive(:read).and_return(Gitlab::Git::DiffStatsCollection.new([]))
+ end
+
+ it 'does not make a diff stats rpc call' do
+ expect(diffable.project.repository).not_to receive(:diff_stats)
+
+ subject.diff_files
+ end
+ end
+
+ context 'when there are no stats cached' do
+ it 'makes a diff stats rpc call' do
+ expect(diffable.project.repository)
+ .to receive(:diff_stats)
+ .with(diffable.diff_refs.base_sha, diffable.diff_refs.head_sha)
+
+ subject.diff_files
+ end
+ end
end
end
diff --git a/spec/support/shared_examples/lib/gitlab/gl_repository_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/gl_repository_shared_examples.rb
index 97f4341340d..28137530038 100644
--- a/spec/support/shared_examples/lib/gitlab/gl_repository_shared_examples.rb
+++ b/spec/support/shared_examples/lib/gitlab/gl_repository_shared_examples.rb
@@ -1,12 +1,12 @@
# frozen_string_literal: true
RSpec.shared_examples 'parsing gl_repository identifier' do
- subject { described_class.new(identifier) }
+ subject { described_class.parse(identifier) }
it 'returns correct information' do
- aggregate_failures do
- expect(subject.repo_type).to eq(expected_type)
- expect(subject.fetch_container!).to eq(expected_container)
- end
+ expect(subject).to have_attributes(
+ repo_type: expected_type,
+ container: expected_container
+ )
end
end
diff --git a/spec/support/shared_examples/lib/gitlab/import/stuck_import_job_workers_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/import/stuck_import_job_workers_shared_examples.rb
index 06ea540706a..222390cf9cd 100644
--- a/spec/support/shared_examples/lib/gitlab/import/stuck_import_job_workers_shared_examples.rb
+++ b/spec/support/shared_examples/lib/gitlab/import/stuck_import_job_workers_shared_examples.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-shared_examples 'stuck import job detection' do
+RSpec.shared_examples 'stuck import job detection' do
context 'when the job has completed' do
context 'when the import status was already updated' do
before do
diff --git a/spec/support/shared_examples/lib/gitlab/jira_import/base_importer_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/jira_import/base_importer_shared_examples.rb
index 85dcc053447..b1788bb5912 100644
--- a/spec/support/shared_examples/lib/gitlab/jira_import/base_importer_shared_examples.rb
+++ b/spec/support/shared_examples/lib/gitlab/jira_import/base_importer_shared_examples.rb
@@ -1,5 +1,5 @@
# frozen_string_literal: true
-shared_examples 'raise exception if not implemented' do
+RSpec.shared_examples 'raise exception if not implemented' do
it { expect { described_class.new(project).imported_items_cache_key }.not_to raise_error }
end
diff --git a/spec/support/shared_examples/lib/wikis_api_examples.rb b/spec/support/shared_examples/lib/wikis_api_examples.rb
new file mode 100644
index 00000000000..2e4c667d37e
--- /dev/null
+++ b/spec/support/shared_examples/lib/wikis_api_examples.rb
@@ -0,0 +1,174 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples_for 'wikis API returns list of wiki pages' do
+ context 'when wiki has pages' do
+ let!(:pages) do
+ [create(:wiki_page, wiki: wiki, title: 'page1', content: 'content of page1'),
+ create(:wiki_page, wiki: wiki, title: 'page2.with.dot', content: 'content of page2')]
+ end
+
+ it 'returns the list of wiki pages without content' do
+ get api(url, user)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response.size).to eq(2)
+
+ json_response.each_with_index do |page, index|
+ expect(page.keys).to match_array(expected_keys_without_content)
+ expect(page['slug']).to eq(pages[index].slug)
+ expect(page['title']).to eq(pages[index].title)
+ end
+ end
+
+ it 'returns the list of wiki pages with content' do
+ get api(url, user), params: { with_content: 1 }
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response.size).to eq(2)
+
+ json_response.each_with_index do |page, index|
+ expect(page.keys).to match_array(expected_keys_with_content)
+ expect(page['content']).to eq(pages[index].content)
+ expect(page['slug']).to eq(pages[index].slug)
+ expect(page['title']).to eq(pages[index].title)
+ end
+ end
+ end
+
+ it 'return the empty list of wiki pages' do
+ get api(url, user)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response.size).to eq(0)
+ end
+end
+
+RSpec.shared_examples_for 'wikis API returns wiki page' do
+ it 'returns the wiki page' do
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response.size).to eq(4)
+ expect(json_response.keys).to match_array(expected_keys_with_content)
+ expect(json_response['content']).to eq(page.content)
+ expect(json_response['slug']).to eq(page.slug)
+ expect(json_response['title']).to eq(page.title)
+ end
+end
+
+RSpec.shared_examples_for 'wikis API creates wiki page' do
+ it 'creates the wiki page' do
+ post(api(url, user), params: payload)
+
+ expect(response).to have_gitlab_http_status(:created)
+ expect(json_response.size).to eq(4)
+ expect(json_response.keys).to match_array(expected_keys_with_content)
+ expect(json_response['content']).to eq(payload[:content])
+ expect(json_response['slug']).to eq(payload[:title].tr(' ', '-'))
+ expect(json_response['title']).to eq(payload[:title])
+ expect(json_response['rdoc']).to eq(payload[:rdoc])
+ end
+
+ [:title, :content].each do |part|
+ it "responds with validation error on empty #{part}" do
+ payload.delete(part)
+
+ post(api(url, user), params: payload)
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ expect(json_response.size).to eq(1)
+ expect(json_response['error']).to eq("#{part} is missing")
+ end
+ end
+end
+
+RSpec.shared_examples_for 'wikis API updates wiki page' do
+ it 'updates the wiki page' do
+ put(api(url, user), params: payload)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response.size).to eq(4)
+ expect(json_response.keys).to match_array(expected_keys_with_content)
+ expect(json_response['content']).to eq(payload[:content])
+ expect(json_response['slug']).to eq(payload[:title].tr(' ', '-'))
+ expect(json_response['title']).to eq(payload[:title])
+ end
+
+ [:title, :content, :format].each do |part|
+ it "updates with wiki with missing #{part}" do
+ payload.delete(part)
+
+ put(api(url, user), params: payload)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+ end
+end
+
+RSpec.shared_examples_for 'wiki API 403 Forbidden' do
+ it 'returns 403 Forbidden' do
+ expect(response).to have_gitlab_http_status(:forbidden)
+ expect(json_response.size).to eq(1)
+ expect(json_response['message']).to eq('403 Forbidden')
+ end
+end
+
+RSpec.shared_examples_for 'wiki API 404 Wiki Page Not Found' do
+ it 'returns 404 Wiki Page Not Found' do
+ expect(response).to have_gitlab_http_status(:not_found)
+ expect(json_response.size).to eq(1)
+ expect(json_response['message']).to eq('404 Wiki Page Not Found')
+ end
+end
+
+RSpec.shared_examples_for 'wiki API 404 Not Found' do |what|
+ it "returns 404 #{what} Not Found" do
+ expect(response).to have_gitlab_http_status(:not_found)
+ expect(json_response.size).to eq(1)
+ expect(json_response['message']).to eq("404 #{what} Not Found")
+ end
+end
+
+RSpec.shared_examples_for 'wiki API 204 No Content' do
+ it 'returns 204 No Content' do
+ expect(response).to have_gitlab_http_status(:no_content)
+ end
+end
+
+RSpec.shared_examples_for 'wiki API uploads wiki attachment' do
+ it 'pushes attachment to the wiki repository' do
+ allow(SecureRandom).to receive(:hex).and_return('fixed_hex')
+
+ workhorse_post_with_file(api(url, user), file_key: :file, params: payload)
+
+ expect(response).to have_gitlab_http_status(:created)
+ expect(json_response).to eq result_hash.deep_stringify_keys
+ end
+
+ it 'responds with validation error on empty file' do
+ payload.delete(:file)
+
+ post(api(url, user), params: payload)
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ expect(json_response.size).to eq(1)
+ expect(json_response['error']).to eq('file is missing')
+ end
+
+ it 'responds with validation error on invalid temp file' do
+ payload[:file] = { tempfile: '/etc/hosts' }
+
+ post(api(url, user), params: payload)
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ expect(json_response.size).to eq(1)
+ expect(json_response['error']).to eq('file is invalid')
+ end
+
+ it 'is backward compatible with regular multipart uploads' do
+ allow(SecureRandom).to receive(:hex).and_return('fixed_hex')
+
+ post(api(url, user), params: payload)
+
+ expect(response).to have_gitlab_http_status(:created)
+ expect(json_response).to eq result_hash.deep_stringify_keys
+ end
+end
diff --git a/spec/support/shared_examples/models/cluster_application_status_shared_examples.rb b/spec/support/shared_examples/models/cluster_application_status_shared_examples.rb
index 0efa5e56199..f80ca235220 100644
--- a/spec/support/shared_examples/models/cluster_application_status_shared_examples.rb
+++ b/spec/support/shared_examples/models/cluster_application_status_shared_examples.rb
@@ -88,16 +88,6 @@ RSpec.shared_examples 'cluster application status specs' do |application_name|
end
end
- it 'sets the correct version of the application' do
- subject.update!(version: '0.0.0')
-
- subject.make_installed!
-
- subject.reload
-
- expect(subject.version).to eq(subject.class.const_get(:VERSION, false))
- end
-
context 'application is updating' do
subject { create(application_name, :updating) }
@@ -146,16 +136,6 @@ RSpec.shared_examples 'cluster application status specs' do |application_name|
end
end
end
-
- it 'updates the version of the application' do
- subject.update!(version: '0.0.0')
-
- subject.make_installed!
-
- subject.reload
-
- expect(subject.version).to eq(subject.class.const_get(:VERSION, false))
- end
end
end
diff --git a/spec/support/shared_examples/models/cluster_application_version_shared_examples.rb b/spec/support/shared_examples/models/cluster_application_version_shared_examples.rb
index cf7010c48c2..ed2e4fee2de 100644
--- a/spec/support/shared_examples/models/cluster_application_version_shared_examples.rb
+++ b/spec/support/shared_examples/models/cluster_application_version_shared_examples.rb
@@ -19,4 +19,32 @@ RSpec.shared_examples 'cluster application version specs' do |application_name|
it { is_expected.to be_falsey }
end
end
+
+ describe '#make_installed' do
+ subject { create(application_name, :installing) }
+
+ it 'sets the correct version of the application' do
+ subject.update!(version: '0.0.0')
+
+ subject.make_installed!
+
+ subject.reload
+
+ expect(subject.version).to eq(subject.class.const_get(:VERSION, false))
+ end
+
+ context 'application is updating' do
+ subject { create(application_name, :updating) }
+
+ it 'updates the version of the application' do
+ subject.update!(version: '0.0.0')
+
+ subject.make_installed!
+
+ subject.reload
+
+ expect(subject.version).to eq(subject.class.const_get(:VERSION, false))
+ end
+ end
+ end
end
diff --git a/spec/support/shared_examples/models/concerns/bulk_insert_safe_shared_examples.rb b/spec/support/shared_examples/models/concerns/bulk_insert_safe_shared_examples.rb
index 7bcd6191f1d..3db5d7a8d7d 100644
--- a/spec/support/shared_examples/models/concerns/bulk_insert_safe_shared_examples.rb
+++ b/spec/support/shared_examples/models/concerns/bulk_insert_safe_shared_examples.rb
@@ -7,17 +7,17 @@ RSpec.shared_examples 'a BulkInsertSafe model' do |klass|
let(:target_class) { klass.dup }
# We consider all callbacks unsafe for bulk insertions unless we have explicitly
- # whitelisted them (esp. anything related to :save, :create, :commit etc.)
- let(:callback_method_blacklist) do
+ # allowed them (especially anything related to :save, :create, :commit, etc.)
+ let(:unsafe_callbacks) do
ActiveRecord::Callbacks::CALLBACKS.reject do |callback|
cb_name = callback.to_s.gsub(/(before_|after_|around_)/, '').to_sym
- BulkInsertSafe::CALLBACK_NAME_WHITELIST.include?(cb_name)
+ BulkInsertSafe::ALLOWED_CALLBACKS.include?(cb_name)
end.to_set
end
context 'when calling class methods directly' do
it 'raises an error when method is not bulk-insert safe' do
- callback_method_blacklist.each do |m|
+ unsafe_callbacks.each do |m|
expect { target_class.send(m, nil) }.to(
raise_error(BulkInsertSafe::MethodNotAllowedError),
"Expected call to #{m} to raise an error, but it didn't"
@@ -26,7 +26,7 @@ RSpec.shared_examples 'a BulkInsertSafe model' do |klass|
end
it 'does not raise an error when method is bulk-insert safe' do
- BulkInsertSafe::CALLBACK_NAME_WHITELIST.each do |name|
+ BulkInsertSafe::ALLOWED_CALLBACKS.each do |name|
expect { target_class.set_callback(name) {} }.not_to raise_error
end
end
diff --git a/spec/support/shared_examples/models/jira_import_state_shared_examples.rb b/spec/support/shared_examples/models/jira_import_state_shared_examples.rb
index f4643375c8e..1999f0f3c49 100644
--- a/spec/support/shared_examples/models/jira_import_state_shared_examples.rb
+++ b/spec/support/shared_examples/models/jira_import_state_shared_examples.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-shared_examples 'multiple running imports not allowed' do
+RSpec.shared_examples 'multiple running imports not allowed' do
it 'returns not valid' do
new_import = build(:jira_import_state, project: project)
@@ -9,21 +9,21 @@ shared_examples 'multiple running imports not allowed' do
end
end
-shared_examples 'in progress' do |status|
+RSpec.shared_examples 'in progress' do |status|
it 'returns true' do
jira_import_state = build(:jira_import_state, status: status)
expect(jira_import_state).to be_in_progress
end
end
-shared_examples 'not in progress' do |status|
+RSpec.shared_examples 'not in progress' do |status|
it 'returns false' do
jira_import_state = build(:jira_import_state, status: status)
expect(jira_import_state).not_to be_in_progress
end
end
-shared_examples 'can transition' do |states|
+RSpec.shared_examples 'can transition' do |states|
states.each do |state|
it 'returns true' do
expect(jira_import.send(state)).to be true
@@ -31,7 +31,7 @@ shared_examples 'can transition' do |states|
end
end
-shared_examples 'cannot transition' do |states|
+RSpec.shared_examples 'cannot transition' do |states|
states.each do |state|
it 'returns false' do
expect(jira_import.send(state)).to be false
diff --git a/spec/support/shared_examples/models/note_access_check_shared_examples.rb b/spec/support/shared_examples/models/note_access_check_shared_examples.rb
index 3bafad202f6..44edafe9091 100644
--- a/spec/support/shared_examples/models/note_access_check_shared_examples.rb
+++ b/spec/support/shared_examples/models/note_access_check_shared_examples.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-shared_examples 'users with note access' do
+RSpec.shared_examples 'users with note access' do
it 'returns true' do
users.each do |user|
expect(note.system_note_with_references_visible_for?(user)).to be_truthy
@@ -9,7 +9,7 @@ shared_examples 'users with note access' do
end
end
-shared_examples 'users without note access' do
+RSpec.shared_examples 'users without note access' do
it 'returns false' do
users.each do |user|
expect(note.system_note_with_references_visible_for?(user)).to be_falsy
diff --git a/spec/support/shared_examples/models/services_fields_shared_examples.rb b/spec/support/shared_examples/models/services_fields_shared_examples.rb
deleted file mode 100644
index cb36f74460d..00000000000
--- a/spec/support/shared_examples/models/services_fields_shared_examples.rb
+++ /dev/null
@@ -1,31 +0,0 @@
-# frozen_string_literal: true
-
-RSpec.shared_examples 'issue tracker fields' do
- let(:title) { 'custom title' }
- let(:description) { 'custom description' }
- let(:url) { 'http://issue_tracker.example.com' }
-
- context 'when data are stored in the properties' do
- describe '#update' do
- before do
- service.update(title: 'new_title', description: 'new description')
- end
-
- it 'removes title and description from properties' do
- expect(service.reload.properties).not_to include('title', 'description')
- end
-
- it 'stores title & description in services table' do
- expect(service.read_attribute(:title)).to eq('new_title')
- expect(service.read_attribute(:description)).to eq('new description')
- end
- end
-
- describe 'reading fields' do
- it 'returns correct values' do
- expect(service.title).to eq(title)
- expect(service.description).to eq(description)
- end
- end
- end
-end
diff --git a/spec/support/shared_examples/models/synthetic_note_shared_examples.rb b/spec/support/shared_examples/models/synthetic_note_shared_examples.rb
new file mode 100644
index 00000000000..a41ade2950a
--- /dev/null
+++ b/spec/support/shared_examples/models/synthetic_note_shared_examples.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'a synthetic note' do |action|
+ it_behaves_like 'a system note', exclude_project: true do
+ let(:action) { action }
+ end
+
+ describe '#discussion_id' do
+ before do
+ allow(event).to receive(:discussion_id).and_return('foobar42')
+ end
+
+ it 'returns the expected discussion id' do
+ expect(subject.discussion_id(nil)).to eq('foobar42')
+ end
+ end
+end
diff --git a/spec/support/shared_examples/namespaces/hierarchy_examples.rb b/spec/support/shared_examples/namespaces/hierarchy_examples.rb
new file mode 100644
index 00000000000..d5754f47be2
--- /dev/null
+++ b/spec/support/shared_examples/namespaces/hierarchy_examples.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'hierarchy with traversal_ids' do
+ # A convenient null node to represent the parent of root.
+ let(:null_node) { double(traversal_ids: []) }
+
+ # Walk the tree to assert that the current_node's traversal_id is always
+ # present and equal to it's parent's traversal_ids plus it's own ID.
+ def validate_traversal_ids(current_node, parent = null_node)
+ expect(current_node.traversal_ids).to be_present
+ expect(current_node.traversal_ids).to eq parent.traversal_ids + [current_node.id]
+
+ current_node.children.each do |child|
+ validate_traversal_ids(child, current_node)
+ end
+ end
+
+ it 'will be valid' do
+ validate_traversal_ids(root)
+ end
+end
diff --git a/spec/support/shared_examples/policies/namespace_policy_shared_examples.rb b/spec/support/shared_examples/policies/namespace_policy_shared_examples.rb
new file mode 100644
index 00000000000..ddec1ba5e3d
--- /dev/null
+++ b/spec/support/shared_examples/policies/namespace_policy_shared_examples.rb
@@ -0,0 +1,34 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'update namespace limit policy' do
+ describe 'update_subscription_limit' do
+ using RSpec::Parameterized::TableSyntax
+
+ let(:policy) { :update_subscription_limit }
+
+ where(:role, :is_com, :allowed) do
+ :user | true | false
+ :owner | true | false
+ :admin | true | true
+ :user | false | false
+ :owner | false | false
+ :admin | false | false
+ end
+
+ with_them do
+ let(:current_user) { build_stubbed(role) }
+
+ before do
+ allow(Gitlab).to receive(:com?).and_return(is_com)
+ end
+
+ context 'when admin mode enabled', :enable_admin_mode do
+ it { is_expected.to(allowed ? be_allowed(policy) : be_disallowed(policy)) }
+ end
+
+ context 'when admin mode disabled' do
+ it { is_expected.to be_disallowed(policy) }
+ end
+ end
+ end
+end
diff --git a/spec/support/shared_examples/policies/project_policy_shared_examples.rb b/spec/support/shared_examples/policies/project_policy_shared_examples.rb
index 4dd0152e3d1..f8526ec68dc 100644
--- a/spec/support/shared_examples/policies/project_policy_shared_examples.rb
+++ b/spec/support/shared_examples/policies/project_policy_shared_examples.rb
@@ -41,6 +41,28 @@ RSpec.shared_examples 'archived project policies' do
end
end
+RSpec.shared_examples 'project private features with read_all_resources ability' do
+ subject { described_class.new(user, project) }
+
+ before do
+ project.project_feature.update!(
+ repository_access_level: ProjectFeature::PRIVATE,
+ merge_requests_access_level: ProjectFeature::PRIVATE,
+ builds_access_level: ProjectFeature::PRIVATE
+ )
+ end
+
+ [:public, :internal, :private].each do |visibility|
+ context "for #{visibility} projects" do
+ let(:project) { create(:project, visibility, namespace: owner.namespace) }
+
+ it 'allows the download_code ability' do
+ expect_allowed(:download_code)
+ end
+ end
+ end
+end
+
RSpec.shared_examples 'project policies as anonymous' do
context 'abilities for public projects' do
context 'when a project has pending invites' do
@@ -231,6 +253,12 @@ RSpec.shared_examples 'project policies as admin with admin mode' do
let(:regular_abilities) { owner_permissions }
end
end
+
+ context 'abilities for all project visibility', :enable_admin_mode do
+ it_behaves_like 'project private features with read_all_resources ability' do
+ let(:user) { admin }
+ end
+ end
end
RSpec.shared_examples 'project policies as admin without admin mode' do
diff --git a/spec/support/shared_examples/quick_actions/issue/create_merge_request_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issue/create_merge_request_quick_action_shared_examples.rb
index 159660e7d1d..910805dbdea 100644
--- a/spec/support/shared_examples/quick_actions/issue/create_merge_request_quick_action_shared_examples.rb
+++ b/spec/support/shared_examples/quick_actions/issue/create_merge_request_quick_action_shared_examples.rb
@@ -47,7 +47,7 @@ RSpec.shared_examples 'create_merge_request quick action' do
expect(created_mr.source_branch).to eq(issue.to_branch_name)
visit project_merge_request_path(project, created_mr)
- expect(page).to have_content %{WIP: Resolve "#{issue.title}"}
+ expect(page).to have_content %{Draft: Resolve "#{issue.title}"}
end
it 'creates a merge request using the given branch name' do
@@ -60,7 +60,7 @@ RSpec.shared_examples 'create_merge_request quick action' do
expect(created_mr.source_branch).to eq(branch_name)
visit project_merge_request_path(project, created_mr)
- expect(page).to have_content %{WIP: Resolve "#{issue.title}"}
+ expect(page).to have_content %{Draft: Resolve "#{issue.title}"}
end
end
end
diff --git a/spec/support/shared_examples/quick_actions/merge_request/merge_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/merge_request/merge_quick_action_shared_examples.rb
index e0edbc5637a..258d9ab85e4 100644
--- a/spec/support/shared_examples/quick_actions/merge_request/merge_quick_action_shared_examples.rb
+++ b/spec/support/shared_examples/quick_actions/merge_request/merge_quick_action_shared_examples.rb
@@ -1,68 +1,85 @@
# frozen_string_literal: true
RSpec.shared_examples 'merge quick action' do
- context 'when the current user can merge the MR' do
+ context 'when updating the description' do
before do
sign_in(user)
- visit project_merge_request_path(project, merge_request)
+ visit edit_project_merge_request_path(project, merge_request)
end
- it 'merges the MR', :sidekiq_might_not_need_inline do
- add_note("/merge")
-
- expect(page).to have_content 'Merged this merge request.'
+ it 'merges the MR', :sidekiq_inline do
+ fill_in('Description', with: '/merge')
+ click_button('Save changes')
+ expect(page).to have_content('Merged')
expect(merge_request.reload).to be_merged
end
+ end
- context 'when auto merge is avialable' do
+ context 'when creating a new note' do
+ context 'when the current user can merge the MR' do
before do
- create(:ci_pipeline, :detached_merge_request_pipeline,
- project: project, merge_request: merge_request)
- merge_request.update_head_pipeline
+ sign_in(user)
+ visit project_merge_request_path(project, merge_request)
end
- it 'schedules to merge the MR' do
+ it 'merges the MR', :sidekiq_inline do
add_note("/merge")
- expect(page).to have_content "Scheduled to merge this merge request (Merge when pipeline succeeds)."
+ expect(page).to have_content 'Merged this merge request.'
- expect(merge_request.reload).to be_auto_merge_enabled
- expect(merge_request.reload).not_to be_merged
+ expect(merge_request.reload).to be_merged
end
- end
- end
- context 'when the head diff changes in the meanwhile' do
- before do
- merge_request.source_branch = 'another_branch'
- merge_request.save
- sign_in(user)
- visit project_merge_request_path(project, merge_request)
- end
+ context 'when auto merge is available' do
+ before do
+ create(:ci_pipeline, :detached_merge_request_pipeline,
+ project: project, merge_request: merge_request)
+ merge_request.update_head_pipeline
+ end
- it 'does not merge the MR' do
- add_note("/merge")
+ it 'schedules to merge the MR' do
+ add_note("/merge")
- expect(page).not_to have_content 'Your commands have been executed!'
+ expect(page).to have_content "Scheduled to merge this merge request (Merge when pipeline succeeds)."
- expect(merge_request.reload).not_to be_merged
+ expect(merge_request.reload).to be_auto_merge_enabled
+ expect(merge_request.reload).not_to be_merged
+ end
+ end
end
- end
- context 'when the current user cannot merge the MR' do
- before do
- project.add_guest(guest)
- sign_in(guest)
- visit project_merge_request_path(project, merge_request)
+ context 'when the head diff changes in the meanwhile' do
+ before do
+ merge_request.source_branch = 'another_branch'
+ merge_request.save
+ sign_in(user)
+ visit project_merge_request_path(project, merge_request)
+ end
+
+ it 'does not merge the MR' do
+ add_note("/merge")
+
+ expect(page).not_to have_content 'Your commands have been executed!'
+
+ expect(merge_request.reload).not_to be_merged
+ end
end
- it 'does not merge the MR' do
- add_note("/merge")
+ context 'when the current user cannot merge the MR' do
+ before do
+ project.add_guest(guest)
+ sign_in(guest)
+ visit project_merge_request_path(project, merge_request)
+ end
+
+ it 'does not merge the MR' do
+ add_note("/merge")
- expect(page).not_to have_content 'Your commands have been executed!'
+ expect(page).not_to have_content 'Your commands have been executed!'
- expect(merge_request.reload).not_to be_merged
+ expect(merge_request.reload).not_to be_merged
+ end
end
end
end
diff --git a/spec/support/shared_examples/requests/api/composer_packages_shared_examples.rb b/spec/support/shared_examples/requests/api/composer_packages_shared_examples.rb
new file mode 100644
index 00000000000..5257980d7df
--- /dev/null
+++ b/spec/support/shared_examples/requests/api/composer_packages_shared_examples.rb
@@ -0,0 +1,138 @@
+# frozen_string_literal: true
+
+RSpec.shared_context 'Composer user type' do |user_type, add_member|
+ before do
+ group.send("add_#{user_type}", user) if add_member && user_type != :anonymous
+ project.send("add_#{user_type}", user) if add_member && user_type != :anonymous
+ end
+end
+
+RSpec.shared_examples 'Composer package index' do |user_type, status, add_member = true|
+ include_context 'Composer user type', user_type, add_member do
+ it 'returns the package index' do
+ subject
+
+ expect(response).to have_gitlab_http_status(status)
+ expect(response).to match_response_schema('public_api/v4/packages/composer/index')
+ end
+ end
+end
+
+RSpec.shared_examples 'Composer empty provider index' do |user_type, status, add_member = true|
+ include_context 'Composer user type', user_type, add_member do
+ it 'returns the package index' do
+ subject
+
+ expect(response).to have_gitlab_http_status(status)
+ expect(response).to match_response_schema('public_api/v4/packages/composer/provider')
+ expect(json_response['providers']).to eq({})
+ end
+ end
+end
+
+RSpec.shared_examples 'Composer provider index' do |user_type, status, add_member = true|
+ include_context 'Composer user type', user_type, add_member do
+ it 'returns the package index' do
+ subject
+
+ expect(response).to have_gitlab_http_status(status)
+ expect(response).to match_response_schema('public_api/v4/packages/composer/provider')
+ expect(json_response['providers']).to include(package.name)
+ end
+ end
+end
+
+RSpec.shared_examples 'Composer package api request' do |user_type, status, add_member = true|
+ include_context 'Composer user type', user_type, add_member do
+ it 'returns the package index' do
+ subject
+
+ expect(response).to have_gitlab_http_status(status)
+ expect(response).to match_response_schema('public_api/v4/packages/composer/package')
+ expect(json_response['packages']).to include(package.name)
+ expect(json_response['packages'][package.name]).to include(package.version)
+ end
+ end
+end
+
+RSpec.shared_examples 'Composer package creation' do |user_type, status, add_member = true|
+ context "for user type #{user_type}" do
+ before do
+ group.send("add_#{user_type}", user) if add_member && user_type != :anonymous
+ project.send("add_#{user_type}", user) if add_member && user_type != :anonymous
+ end
+
+ it 'creates package files' do
+ expect { subject }
+ .to change { project.packages.composer.count }.by(1)
+
+ expect(response).to have_gitlab_http_status(status)
+ end
+ it_behaves_like 'a gitlab tracking event', described_class.name, 'register_package'
+ end
+end
+
+RSpec.shared_examples 'process Composer api request' do |user_type, status, add_member = true|
+ context "for user type #{user_type}" do
+ before do
+ group.send("add_#{user_type}", user) if add_member && user_type != :anonymous
+ project.send("add_#{user_type}", user) if add_member && user_type != :anonymous
+ end
+
+ it_behaves_like 'returning response status', status
+ end
+end
+
+RSpec.shared_context 'Composer auth headers' do |user_role, user_token|
+ let(:token) { user_token ? personal_access_token.token : 'wrong' }
+ let(:headers) { user_role == :anonymous ? {} : build_basic_auth_header(user.username, token) }
+end
+
+RSpec.shared_context 'Composer api project access' do |project_visibility_level, user_role, user_token|
+ include_context 'Composer auth headers', user_role, user_token do
+ before do
+ project.update!(visibility_level: Gitlab::VisibilityLevel.const_get(project_visibility_level, false))
+ end
+ end
+end
+
+RSpec.shared_context 'Composer api group access' do |project_visibility_level, user_role, user_token|
+ include_context 'Composer auth headers', user_role, user_token do
+ before do
+ project.update!(visibility_level: Gitlab::VisibilityLevel.const_get(project_visibility_level, false))
+ group.update!(visibility_level: Gitlab::VisibilityLevel.const_get(project_visibility_level, false))
+ end
+ end
+end
+
+RSpec.shared_examples 'rejects Composer access with unknown group id' do
+ context 'with an unknown group' do
+ let(:group) { double(id: non_existing_record_id) }
+
+ context 'as anonymous' do
+ it_behaves_like 'process Composer api request', :anonymous, :not_found
+ end
+
+ context 'as authenticated user' do
+ subject { get api(url), headers: build_basic_auth_header(user.username, personal_access_token.token) }
+
+ it_behaves_like 'process Composer api request', :anonymous, :not_found
+ end
+ end
+end
+
+RSpec.shared_examples 'rejects Composer access with unknown project id' do
+ context 'with an unknown project' do
+ let(:project) { double(id: non_existing_record_id) }
+
+ context 'as anonymous' do
+ it_behaves_like 'process Composer api request', :anonymous, :not_found
+ end
+
+ context 'as authenticated user' do
+ subject { get api(url), headers: build_basic_auth_header(user.username, personal_access_token.token) }
+
+ it_behaves_like 'process Composer api request', :anonymous, :not_found
+ end
+ end
+end
diff --git a/spec/support/shared_examples/requests/api/graphql/projects/services_shared_examples.rb b/spec/support/shared_examples/requests/api/graphql/projects/services_shared_examples.rb
index 246f1850c3c..da1caef63ba 100644
--- a/spec/support/shared_examples/requests/api/graphql/projects/services_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/graphql/projects/services_shared_examples.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-shared_examples 'unauthorized users cannot read services' do
+RSpec.shared_examples 'unauthorized users cannot read services' do
before do
post_graphql(query, current_user: current_user)
end
diff --git a/spec/support/shared_examples/requests/api/notes_shared_examples.rb b/spec/support/shared_examples/requests/api/notes_shared_examples.rb
index 60ed61269df..a34c48a5ba4 100644
--- a/spec/support/shared_examples/requests/api/notes_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/notes_shared_examples.rb
@@ -132,6 +132,16 @@ RSpec.shared_examples 'noteable API' do |parent_type, noteable_type, id_name|
expect(response).to have_gitlab_http_status(:created)
expect(json_response['body']).to eq('hi!')
+ expect(json_response['confidential']).to be_falsey
+ expect(json_response['author']['username']).to eq(user.username)
+ end
+
+ it "creates a confidential note if confidential is set to true" do
+ post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes", user), params: { body: 'hi!', confidential: true }
+
+ expect(response).to have_gitlab_http_status(:created)
+ expect(json_response['body']).to eq('hi!')
+ expect(json_response['confidential']).to be_truthy
expect(json_response['author']['username']).to eq(user.username)
end
diff --git a/spec/support/shared_examples/requests/api/nuget_packages_shared_examples.rb b/spec/support/shared_examples/requests/api/nuget_packages_shared_examples.rb
new file mode 100644
index 00000000000..8d8483cae72
--- /dev/null
+++ b/spec/support/shared_examples/requests/api/nuget_packages_shared_examples.rb
@@ -0,0 +1,408 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'rejects nuget packages access' do |user_type, status, add_member = true|
+ context "for user type #{user_type}" do
+ before do
+ project.send("add_#{user_type}", user) if add_member && user_type != :anonymous
+ end
+
+ it_behaves_like 'returning response status', status
+
+ if status == :unauthorized
+ it 'has the correct response header' do
+ subject
+
+ expect(response.headers['Www-Authenticate: Basic realm']).to eq 'GitLab Packages Registry'
+ end
+ end
+ end
+end
+
+RSpec.shared_examples 'process nuget service index request' do |user_type, status, add_member = true|
+ context "for user type #{user_type}" do
+ before do
+ project.send("add_#{user_type}", user) if add_member && user_type != :anonymous
+ end
+
+ it_behaves_like 'returning response status', status
+
+ it_behaves_like 'a gitlab tracking event', described_class.name, 'nuget_service_index'
+
+ it 'returns a valid json response' do
+ subject
+
+ expect(response.media_type).to eq('application/json')
+ expect(json_response).to match_schema('public_api/v4/packages/nuget/service_index')
+ expect(json_response).to be_a(Hash)
+ end
+
+ context 'with invalid format' do
+ let(:url) { "/projects/#{project.id}/packages/nuget/index.xls" }
+
+ it_behaves_like 'rejects nuget packages access', :anonymous, :not_found
+ end
+ end
+end
+
+RSpec.shared_examples 'returning nuget metadata json response with json schema' do |json_schema|
+ it 'returns a valid json response' do
+ subject
+
+ expect(response.media_type).to eq('application/json')
+ expect(json_response).to match_schema(json_schema)
+ expect(json_response).to be_a(Hash)
+ end
+end
+
+RSpec.shared_examples 'process nuget metadata request at package name level' do |user_type, status, add_member = true|
+ context "for user type #{user_type}" do
+ before do
+ project.send("add_#{user_type}", user) if add_member && user_type != :anonymous
+ end
+
+ it_behaves_like 'returning response status', status
+
+ it_behaves_like 'returning nuget metadata json response with json schema', 'public_api/v4/packages/nuget/packages_metadata'
+
+ context 'with invalid format' do
+ let(:url) { "/projects/#{project.id}/packages/nuget/metadata/#{package_name}/index.xls" }
+
+ it_behaves_like 'rejects nuget packages access', :anonymous, :not_found
+ end
+
+ context 'with lower case package name' do
+ let_it_be(:package_name) { 'dummy.package' }
+
+ it_behaves_like 'returning response status', status
+
+ it_behaves_like 'returning nuget metadata json response with json schema', 'public_api/v4/packages/nuget/packages_metadata'
+ end
+ end
+end
+
+RSpec.shared_examples 'process nuget metadata request at package name and package version level' do |user_type, status, add_member = true|
+ context "for user type #{user_type}" do
+ before do
+ project.send("add_#{user_type}", user) if add_member && user_type != :anonymous
+ end
+
+ it_behaves_like 'returning response status', status
+
+ it_behaves_like 'returning nuget metadata json response with json schema', 'public_api/v4/packages/nuget/package_metadata'
+
+ context 'with invalid format' do
+ let(:url) { "/projects/#{project.id}/packages/nuget/metadata/#{package_name}/#{package.version}.xls" }
+
+ it_behaves_like 'rejects nuget packages access', :anonymous, :not_found
+ end
+
+ context 'with lower case package name' do
+ let_it_be(:package_name) { 'dummy.package' }
+
+ it_behaves_like 'returning response status', status
+
+ it_behaves_like 'returning nuget metadata json response with json schema', 'public_api/v4/packages/nuget/package_metadata'
+ end
+ end
+end
+
+RSpec.shared_examples 'process nuget workhorse authorization' do |user_type, status, add_member = true|
+ context "for user type #{user_type}" do
+ before do
+ project.send("add_#{user_type}", user) if add_member && user_type != :anonymous
+ end
+
+ it_behaves_like 'returning response status', status
+
+ it 'has the proper content type' do
+ subject
+
+ expect(response.media_type).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE)
+ end
+
+ context 'with a request that bypassed gitlab-workhorse' do
+ let(:headers) do
+ build_basic_auth_header(user.username, personal_access_token.token)
+ .merge(workhorse_header)
+ .tap { |h| h.delete(Gitlab::Workhorse::INTERNAL_API_REQUEST_HEADER) }
+ end
+
+ before do
+ project.add_maintainer(user)
+ end
+
+ it_behaves_like 'returning response status', :forbidden
+ end
+ end
+end
+
+RSpec.shared_examples 'process nuget upload' do |user_type, status, add_member = true|
+ RSpec.shared_examples 'creates nuget package files' do
+ it 'creates package files' do
+ expect(::Packages::Nuget::ExtractionWorker).to receive(:perform_async).once
+ expect { subject }
+ .to change { project.packages.count }.by(1)
+ .and change { Packages::PackageFile.count }.by(1)
+ expect(response).to have_gitlab_http_status(status)
+
+ package_file = project.packages.last.package_files.reload.last
+ expect(package_file.file_name).to eq('package.nupkg')
+ end
+ end
+
+ context "for user type #{user_type}" do
+ before do
+ project.send("add_#{user_type}", user) if add_member && user_type != :anonymous
+ end
+
+ context 'with object storage disabled' do
+ before do
+ stub_package_file_object_storage(enabled: false)
+ end
+
+ context 'without a file from workhorse' do
+ let(:send_rewritten_field) { false }
+
+ it_behaves_like 'returning response status', :bad_request
+ end
+
+ context 'with correct params' do
+ it_behaves_like 'package workhorse uploads'
+ it_behaves_like 'creates nuget package files'
+ it_behaves_like 'a gitlab tracking event', described_class.name, 'push_package'
+ end
+ end
+
+ context 'with object storage enabled' do
+ let(:tmp_object) do
+ fog_connection.directories.new(key: 'packages').files.create(
+ key: "tmp/uploads/#{file_name}",
+ body: 'content'
+ )
+ end
+ let(:fog_file) { fog_to_uploaded_file(tmp_object) }
+ let(:params) { { package: fog_file, 'package.remote_id' => file_name } }
+
+ context 'and direct upload enabled' do
+ let(:fog_connection) do
+ stub_package_file_object_storage(direct_upload: true)
+ end
+
+ it_behaves_like 'creates nuget package files'
+
+ ['123123', '../../123123'].each do |remote_id|
+ context "with invalid remote_id: #{remote_id}" do
+ let(:params) do
+ {
+ package: fog_file,
+ 'package.remote_id' => remote_id
+ }
+ end
+
+ it_behaves_like 'returning response status', :forbidden
+ end
+ end
+
+ context 'with crafted package.path param' do
+ let(:crafted_file) { Tempfile.new('nuget.crafted.package.path') }
+ let(:url) { "/projects/#{project.id}/packages/nuget?package.path=#{crafted_file.path}" }
+ let(:params) { { file: temp_file(file_name) } }
+ let(:file_key) { :file }
+
+ it 'does not create a package file' do
+ expect { subject }.to change { ::Packages::PackageFile.count }.by(0)
+ end
+
+ it_behaves_like 'returning response status', :bad_request
+ end
+ end
+
+ context 'and direct upload disabled' do
+ context 'and background upload disabled' do
+ let(:fog_connection) do
+ stub_package_file_object_storage(direct_upload: false, background_upload: false)
+ end
+
+ it_behaves_like 'creates nuget package files'
+ end
+
+ context 'and background upload enabled' do
+ let(:fog_connection) do
+ stub_package_file_object_storage(direct_upload: false, background_upload: true)
+ end
+
+ it_behaves_like 'creates nuget package files'
+ end
+ end
+ end
+
+ it_behaves_like 'background upload schedules a file migration'
+ end
+end
+
+RSpec.shared_examples 'process nuget download versions request' do |user_type, status, add_member = true|
+ RSpec.shared_examples 'returns a valid nuget download versions json response' do
+ it 'returns a valid json response' do
+ subject
+
+ expect(response.media_type).to eq('application/json')
+ expect(json_response).to match_schema('public_api/v4/packages/nuget/download_versions')
+ expect(json_response).to be_a(Hash)
+ expect(json_response['versions']).to match_array(packages.map(&:version).sort)
+ end
+ end
+
+ context "for user type #{user_type}" do
+ before do
+ project.send("add_#{user_type}", user) if add_member && user_type != :anonymous
+ end
+
+ it_behaves_like 'returning response status', status
+
+ it_behaves_like 'returns a valid nuget download versions json response'
+
+ context 'with invalid format' do
+ let(:url) { "/projects/#{project.id}/packages/nuget/download/#{package_name}/index.xls" }
+
+ it_behaves_like 'rejects nuget packages access', :anonymous, :not_found
+ end
+
+ context 'with lower case package name' do
+ let_it_be(:package_name) { 'dummy.package' }
+
+ it_behaves_like 'returning response status', status
+
+ it_behaves_like 'returns a valid nuget download versions json response'
+ end
+ end
+end
+
+RSpec.shared_examples 'process nuget download content request' do |user_type, status, add_member = true|
+ context "for user type #{user_type}" do
+ before do
+ project.send("add_#{user_type}", user) if add_member && user_type != :anonymous
+ end
+
+ it_behaves_like 'returning response status', status
+
+ it_behaves_like 'a gitlab tracking event', described_class.name, 'pull_package'
+
+ it 'returns a valid package archive' do
+ subject
+
+ expect(response.media_type).to eq('application/octet-stream')
+ end
+
+ context 'with invalid format' do
+ let(:url) { "/projects/#{project.id}/packages/nuget/download/#{package.name}/#{package.version}/#{package.name}.#{package.version}.xls" }
+
+ it_behaves_like 'rejects nuget packages access', :anonymous, :not_found
+ end
+
+ context 'with lower case package name' do
+ let_it_be(:package_name) { 'dummy.package' }
+
+ it_behaves_like 'returning response status', status
+
+ it 'returns a valid package archive' do
+ subject
+
+ expect(response.media_type).to eq('application/octet-stream')
+ end
+ end
+ end
+end
+
+RSpec.shared_examples 'process nuget search request' do |user_type, status, add_member = true|
+ RSpec.shared_examples 'returns a valid json search response' do |status, total_hits, versions|
+ it_behaves_like 'returning response status', status
+
+ it 'returns a valid json response' do
+ subject
+
+ expect(response.media_type).to eq('application/json')
+ expect(json_response).to be_a(Hash)
+ expect(json_response).to match_schema('public_api/v4/packages/nuget/search')
+ expect(json_response['totalHits']).to eq total_hits
+ expect(json_response['data'].map { |e| e['versions'].size }).to match_array(versions)
+ end
+ end
+
+ context "for user type #{user_type}" do
+ before do
+ project.send("add_#{user_type}", user) if add_member && user_type != :anonymous
+ end
+
+ it_behaves_like 'returns a valid json search response', status, 4, [1, 5, 5, 1]
+
+ it_behaves_like 'a gitlab tracking event', described_class.name, 'search_package'
+
+ context 'with skip set to 2' do
+ let(:skip) { 2 }
+
+ it_behaves_like 'returns a valid json search response', status, 4, [5, 1]
+ end
+
+ context 'with take set to 2' do
+ let(:take) { 2 }
+
+ it_behaves_like 'returns a valid json search response', status, 4, [1, 5]
+ end
+
+ context 'without prereleases' do
+ let(:include_prereleases) { false }
+
+ it_behaves_like 'returns a valid json search response', status, 3, [1, 5, 5]
+ end
+
+ context 'with empty search term' do
+ let(:search_term) { '' }
+
+ it_behaves_like 'returns a valid json search response', status, 5, [1, 5, 5, 1, 1]
+ end
+
+ context 'with nil search term' do
+ let(:search_term) { nil }
+
+ it_behaves_like 'returns a valid json search response', status, 5, [1, 5, 5, 1, 1]
+ end
+ end
+end
+
+RSpec.shared_examples 'rejects nuget access with invalid project id' do
+ context 'with a project id with invalid integers' do
+ using RSpec::Parameterized::TableSyntax
+
+ let(:project) { OpenStruct.new(id: id) }
+
+ where(:id, :status) do
+ '/../' | :unauthorized
+ '' | :not_found
+ '%20' | :unauthorized
+ '%2e%2e%2f' | :unauthorized
+ 'NaN' | :unauthorized
+ 00002345 | :unauthorized
+ 'anything25' | :unauthorized
+ end
+
+ with_them do
+ it_behaves_like 'rejects nuget packages access', :anonymous, params[:status]
+ end
+ end
+end
+
+RSpec.shared_examples 'rejects nuget access with unknown project id' do
+ context 'with an unknown project' do
+ let(:project) { OpenStruct.new(id: 1234567890) }
+
+ context 'as anonymous' do
+ it_behaves_like 'rejects nuget packages access', :anonymous, :unauthorized
+ end
+
+ context 'as authenticated user' do
+ subject { get api(url), headers: build_basic_auth_header(user.username, personal_access_token.token) }
+
+ it_behaves_like 'rejects nuget packages access', :anonymous, :not_found
+ end
+ end
+end
diff --git a/spec/support/shared_examples/requests/api/packages_shared_examples.rb b/spec/support/shared_examples/requests/api/packages_shared_examples.rb
new file mode 100644
index 00000000000..ec15d7a4d2e
--- /dev/null
+++ b/spec/support/shared_examples/requests/api/packages_shared_examples.rb
@@ -0,0 +1,43 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'deploy token for package GET requests' do
+ context 'with deploy token headers' do
+ let(:headers) { build_basic_auth_header(deploy_token.username, deploy_token.token) }
+
+ subject { get api(url), headers: headers }
+
+ before do
+ project.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE)
+ end
+
+ context 'valid token' do
+ it_behaves_like 'returning response status', :success
+ end
+
+ context 'invalid token' do
+ let(:headers) { build_basic_auth_header(deploy_token.username, 'bar') }
+
+ it_behaves_like 'returning response status', :unauthorized
+ end
+ end
+end
+
+RSpec.shared_examples 'deploy token for package uploads' do
+ context 'with deploy token headers' do
+ let(:headers) { build_basic_auth_header(deploy_token.username, deploy_token.token).merge(workhorse_header) }
+
+ before do
+ project.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE)
+ end
+
+ context 'valid token' do
+ it_behaves_like 'returning response status', :success
+ end
+
+ context 'invalid token' do
+ let(:headers) { build_basic_auth_header(deploy_token.username, 'bar').merge(workhorse_header) }
+
+ it_behaves_like 'returning response status', :unauthorized
+ end
+ end
+end
diff --git a/spec/support/shared_examples/requests/api/packages_tags_shared_examples.rb b/spec/support/shared_examples/requests/api/packages_tags_shared_examples.rb
new file mode 100644
index 00000000000..a371d380f47
--- /dev/null
+++ b/spec/support/shared_examples/requests/api/packages_tags_shared_examples.rb
@@ -0,0 +1,185 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'rejects package tags access' do |user_type, status|
+ context "for user type #{user_type}" do
+ before do
+ project.send("add_#{user_type}", user) unless user_type == :no_type
+ end
+
+ it_behaves_like 'returning response status', status
+ end
+end
+
+RSpec.shared_examples 'returns package tags' do |user_type|
+ using RSpec::Parameterized::TableSyntax
+
+ before do
+ stub_application_setting(npm_package_requests_forwarding: false)
+ project.send("add_#{user_type}", user) unless user_type == :no_type
+ end
+
+ it_behaves_like 'returning response status', :success
+
+ it 'returns a valid json response' do
+ subject
+
+ expect(response.media_type).to eq('application/json')
+ expect(json_response).to be_a(Hash)
+ end
+
+ it 'returns two package tags' do
+ subject
+
+ expect(json_response).to match_schema('public_api/v4/packages/npm_package_tags')
+ expect(json_response.length).to eq(3) # two tags + latest (auto added)
+ expect(json_response[package_tag1.name]).to eq(package.version)
+ expect(json_response[package_tag2.name]).to eq(package.version)
+ expect(json_response['latest']).to eq(package.version)
+ end
+
+ context 'with invalid package name' do
+ where(:package_name, :status) do
+ '%20' | :bad_request
+ nil | :forbidden
+ end
+
+ with_them do
+ it_behaves_like 'returning response status', params[:status]
+ end
+ end
+end
+
+RSpec.shared_examples 'create package tag' do |user_type|
+ using RSpec::Parameterized::TableSyntax
+
+ before do
+ project.send("add_#{user_type}", user) unless user_type == :no_type
+ end
+
+ it_behaves_like 'returning response status', :no_content
+
+ it 'creates the package tag' do
+ expect { subject }.to change { Packages::Tag.count }.by(1)
+
+ last_tag = Packages::Tag.last
+ expect(last_tag.name).to eq(tag_name)
+ expect(last_tag.package).to eq(package)
+ end
+
+ it 'returns a valid response' do
+ subject
+
+ expect(response.body).to be_empty
+ end
+
+ context 'with already existing tag' do
+ let_it_be(:package2) { create(:npm_package, project: project, name: package.name, version: '5.5.55') }
+ let_it_be(:tag) { create(:packages_tag, package: package2, name: tag_name) }
+
+ it_behaves_like 'returning response status', :no_content
+
+ it 'reuses existing tag' do
+ expect(package.tags).to be_empty
+ expect(package2.tags).to eq([tag])
+ expect { subject }.to not_change { Packages::Tag.count }
+ expect(package.reload.tags).to eq([tag])
+ expect(package2.reload.tags).to be_empty
+ end
+
+ it 'returns a valid response' do
+ subject
+
+ expect(response.body).to be_empty
+ end
+ end
+
+ context 'with invalid package name' do
+ where(:package_name, :status) do
+ 'unknown' | :forbidden
+ '' | :not_found
+ '%20' | :bad_request
+ end
+
+ with_them do
+ it_behaves_like 'returning response status', params[:status]
+ end
+ end
+
+ context 'with invalid tag name' do
+ where(:tag_name, :status) do
+ '' | :not_found
+ '%20' | :bad_request
+ end
+
+ with_them do
+ it_behaves_like 'returning response status', params[:status]
+ end
+ end
+
+ context 'with invalid version' do
+ where(:version, :status) do
+ ' ' | :bad_request
+ '' | :bad_request
+ nil | :bad_request
+ end
+
+ with_them do
+ it_behaves_like 'returning response status', params[:status]
+ end
+ end
+end
+
+RSpec.shared_examples 'delete package tag' do |user_type|
+ using RSpec::Parameterized::TableSyntax
+
+ before do
+ project.send("add_#{user_type}", user) unless user_type == :no_type
+ end
+
+ context "for #{user_type} user" do
+ it_behaves_like 'returning response status', :no_content
+
+ it 'returns a valid response' do
+ subject
+
+ expect(response.body).to be_empty
+ end
+
+ it 'destroy the package tag' do
+ expect(package.tags).to eq([package_tag])
+ expect { subject }.to change { Packages::Tag.count }.by(-1)
+ expect(package.reload.tags).to be_empty
+ end
+
+ context 'with tag from other package' do
+ let(:package2) { create(:npm_package, project: project) }
+ let(:package_tag) { create(:packages_tag, package: package2) }
+
+ it_behaves_like 'returning response status', :not_found
+ end
+
+ context 'with invalid package name' do
+ where(:package_name, :status) do
+ 'unknown' | :forbidden
+ '' | :not_found
+ '%20' | :bad_request
+ end
+
+ with_them do
+ it_behaves_like 'returning response status', params[:status]
+ end
+ end
+
+ context 'with invalid tag name' do
+ where(:tag_name, :status) do
+ 'unknown' | :not_found
+ '' | :not_found
+ '%20' | :bad_request
+ end
+
+ with_them do
+ it_behaves_like 'returning response status', params[:status]
+ end
+ end
+ end
+end
diff --git a/spec/support/shared_examples/requests/api/pypi_packages_shared_examples.rb b/spec/support/shared_examples/requests/api/pypi_packages_shared_examples.rb
new file mode 100644
index 00000000000..fcc166ac87d
--- /dev/null
+++ b/spec/support/shared_examples/requests/api/pypi_packages_shared_examples.rb
@@ -0,0 +1,152 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'PyPi package creation' do |user_type, status, add_member = true|
+ RSpec.shared_examples 'creating pypi package files' do
+ it 'creates package files' do
+ expect { subject }
+ .to change { project.packages.pypi.count }.by(1)
+ .and change { Packages::PackageFile.count }.by(1)
+ .and change { Packages::Pypi::Metadatum.count }.by(1)
+ expect(response).to have_gitlab_http_status(status)
+
+ package = project.reload.packages.pypi.last
+
+ expect(package.name).to eq params[:name]
+ expect(package.version).to eq params[:version]
+ expect(package.pypi_metadatum.required_python).to eq params[:requires_python]
+ end
+ end
+
+ context "for user type #{user_type}" do
+ before do
+ project.send("add_#{user_type}", user) if add_member && user_type != :anonymous
+ end
+
+ it_behaves_like 'creating pypi package files'
+
+ context 'with object storage disabled' do
+ before do
+ stub_package_file_object_storage(enabled: false)
+ end
+
+ context 'without a file from workhorse' do
+ let(:send_rewritten_field) { false }
+
+ it_behaves_like 'returning response status', :bad_request
+ end
+
+ context 'with correct params' do
+ it_behaves_like 'package workhorse uploads'
+ it_behaves_like 'creating pypi package files'
+ it_behaves_like 'a gitlab tracking event', described_class.name, 'push_package'
+ end
+ end
+
+ context 'with object storage enabled' do
+ let(:tmp_object) do
+ fog_connection.directories.new(key: 'packages').files.create(
+ key: "tmp/uploads/#{file_name}",
+ body: 'content'
+ )
+ end
+ let(:fog_file) { fog_to_uploaded_file(tmp_object) }
+ let(:params) { base_params.merge(content: fog_file, 'content.remote_id' => file_name) }
+
+ context 'and direct upload enabled' do
+ let(:fog_connection) do
+ stub_package_file_object_storage(direct_upload: true)
+ end
+
+ it_behaves_like 'creating pypi package files'
+
+ ['123123', '../../123123'].each do |remote_id|
+ context "with invalid remote_id: #{remote_id}" do
+ let(:params) { base_params.merge(content: fog_file, 'content.remote_id' => remote_id) }
+
+ it_behaves_like 'returning response status', :forbidden
+ end
+ end
+ end
+
+ context 'and direct upload disabled' do
+ context 'and background upload disabled' do
+ let(:fog_connection) do
+ stub_package_file_object_storage(direct_upload: false, background_upload: false)
+ end
+
+ it_behaves_like 'creating pypi package files'
+ end
+
+ context 'and background upload enabled' do
+ let(:fog_connection) do
+ stub_package_file_object_storage(direct_upload: false, background_upload: true)
+ end
+
+ it_behaves_like 'creating pypi package files'
+ end
+ end
+ end
+
+ it_behaves_like 'background upload schedules a file migration'
+ end
+end
+
+RSpec.shared_examples 'PyPi package versions' do |user_type, status, add_member = true|
+ context "for user type #{user_type}" do
+ before do
+ project.send("add_#{user_type}", user) if add_member && user_type != :anonymous
+ end
+
+ it 'returns the package listing' do
+ subject
+
+ expect(response.body).to match(package.package_files.first.file_name)
+ end
+
+ it_behaves_like 'returning response status', status
+ it_behaves_like 'a gitlab tracking event', described_class.name, 'list_package'
+ end
+end
+
+RSpec.shared_examples 'PyPi package download' do |user_type, status, add_member = true|
+ context "for user type #{user_type}" do
+ before do
+ project.send("add_#{user_type}", user) if add_member && user_type != :anonymous
+ end
+
+ it 'returns the package listing' do
+ subject
+
+ expect(response.body).to eq(File.open(package.package_files.first.file.path, "rb").read)
+ end
+
+ it_behaves_like 'returning response status', status
+ it_behaves_like 'a gitlab tracking event', described_class.name, 'pull_package'
+ end
+end
+
+RSpec.shared_examples 'process PyPi api request' do |user_type, status, add_member = true|
+ context "for user type #{user_type}" do
+ before do
+ project.send("add_#{user_type}", user) if add_member && user_type != :anonymous
+ end
+
+ it_behaves_like 'returning response status', status
+ end
+end
+
+RSpec.shared_examples 'rejects PyPI access with unknown project id' do
+ context 'with an unknown project' do
+ let(:project) { OpenStruct.new(id: 1234567890) }
+
+ context 'as anonymous' do
+ it_behaves_like 'process PyPi api request', :anonymous, :not_found
+ end
+
+ context 'as authenticated user' do
+ subject { get api(url), headers: build_basic_auth_header(user.username, personal_access_token.token) }
+
+ it_behaves_like 'process PyPi api request', :anonymous, :not_found
+ end
+ end
+end
diff --git a/spec/support/shared_examples/requests/api/resource_milestone_events_api_shared_examples.rb b/spec/support/shared_examples/requests/api/resource_milestone_events_api_shared_examples.rb
index bca51dab353..d21a9f419fd 100644
--- a/spec/support/shared_examples/requests/api/resource_milestone_events_api_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/resource_milestone_events_api_shared_examples.rb
@@ -16,6 +16,29 @@ RSpec.shared_examples 'resource_milestone_events API' do |parent_type, eventable
expect(json_response.first['action']).to eq(event.action)
end
+ context 'when there is an event with a milestone which is not visible for requesting user' do
+ let!(:private_project) { create(:project, :private) }
+ let!(:private_milestone) { create(:milestone, project: private_project) }
+
+ let!(:other_user) { create(:user) }
+
+ it 'returns the expected events' do
+ create_event(private_milestone)
+
+ get api("/#{parent_type}/#{parent.id}/#{eventable_type}/#{eventable[id_name]}/resource_milestone_events", other_user)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to include_pagination_headers
+ expect(response.headers['X-Total']).to eq('1')
+ expect(json_response).to be_an Array
+ expect(json_response.size).to eq(1)
+
+ expect(json_response.first['id']).to eq(event.id)
+ expect(json_response.first['milestone']['id']).to eq(event.milestone.id)
+ expect(json_response.first['action']).to eq(event.action)
+ end
+ end
+
it "returns a 404 error when eventable id not found" do
get api("/#{parent_type}/#{parent.id}/#{eventable_type}/#{non_existing_record_id}/resource_milestone_events", user)
@@ -60,6 +83,20 @@ RSpec.shared_examples 'resource_milestone_events API' do |parent_type, eventable
end
end
+ describe 'pagination' do
+ let!(:event1) { create_event(milestone) }
+ let!(:event2) { create_event(milestone) }
+
+ # https://gitlab.com/gitlab-org/gitlab/-/issues/220192
+ it 'returns the second page' do
+ get api("/#{parent_type}/#{parent.id}/#{eventable_type}/#{eventable[id_name]}/resource_milestone_events?page=2&per_page=1", user)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response.count).to eq(1)
+ expect(json_response.first['id']).to eq(event2.id)
+ end
+ end
+
def create_event(milestone, action: :add)
create(:resource_milestone_event, eventable.class.name.underscore => eventable, milestone: milestone, action: action)
end
diff --git a/spec/support/shared_examples/requests/api/snippets_shared_examples.rb b/spec/support/shared_examples/requests/api/snippets_shared_examples.rb
new file mode 100644
index 00000000000..cfbb84dd099
--- /dev/null
+++ b/spec/support/shared_examples/requests/api/snippets_shared_examples.rb
@@ -0,0 +1,79 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'raw snippet files' do
+ let_it_be(:unauthorized_user) { create(:user) }
+ let(:snippet_id) { snippet.id }
+ let(:user) { snippet.author }
+ let(:file_path) { '%2Egitattributes' }
+ let(:ref) { 'master' }
+
+ context 'with no user' do
+ it 'requires authentication' do
+ get api(api_path)
+
+ expect(response).to have_gitlab_http_status(:unauthorized)
+ end
+ end
+
+ shared_examples 'not found' do
+ it 'returns 404' do
+ get api(api_path, user)
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ expect(json_response['message']).to eq('404 Snippet Not Found')
+ end
+ end
+
+ context 'when not authorized' do
+ let(:user) { unauthorized_user }
+
+ it_behaves_like 'not found'
+ end
+
+ context 'with an invalid snippet ID' do
+ let(:snippet_id) { 'invalid' }
+
+ it_behaves_like 'not found'
+ end
+
+ context 'with valid params' do
+ it 'returns the raw file info' do
+ expect(Gitlab::Workhorse).to receive(:send_git_blob).and_call_original
+
+ get api(api_path, user)
+
+ aggregate_failures do
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response.media_type).to eq 'text/plain'
+ expect(response.header[Gitlab::Workhorse::DETECT_HEADER]).to eq 'true'
+ expect(response.header[Gitlab::Workhorse::SEND_DATA_HEADER]).to start_with('git-blob:')
+ expect(response.header['Content-Disposition']).to match 'filename=".gitattributes"'
+ end
+ end
+ end
+
+ context 'with invalid params' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:file_path, :ref, :status, :key, :message) do
+ '%2Egitattributes' | 'invalid-ref' | :not_found | 'message' | '404 Reference Not Found'
+ '%2Egitattributes' | nil | :not_found | 'error' | '404 Not Found'
+ '%2Egitattributes' | '' | :not_found | 'error' | '404 Not Found'
+
+ 'doesnotexist.rb' | 'master' | :not_found | 'message' | '404 File Not Found'
+ '/does/not/exist.rb' | 'master' | :not_found | 'error' | '404 Not Found'
+ '%2E%2E%2Fetc%2Fpasswd' | 'master' | :bad_request | 'error' | 'file_path should be a valid file path'
+ '%2Fetc%2Fpasswd' | 'master' | :bad_request | 'error' | 'file_path should be a valid file path'
+ '../../etc/passwd' | 'master' | :not_found | 'error' | '404 Not Found'
+ end
+
+ with_them do
+ before do
+ get api(api_path, user)
+ end
+
+ it { expect(response).to have_gitlab_http_status(status) }
+ it { expect(json_response[key]).to eq(message) }
+ end
+ end
+end
diff --git a/spec/support/shared_examples/requests/snippet_shared_examples.rb b/spec/support/shared_examples/requests/snippet_shared_examples.rb
index f830f957174..644abb191a6 100644
--- a/spec/support/shared_examples/requests/snippet_shared_examples.rb
+++ b/spec/support/shared_examples/requests/snippet_shared_examples.rb
@@ -74,18 +74,14 @@ RSpec.shared_examples 'update with repository actions' do
end
end
-RSpec.shared_examples 'snippet response without repository URLs' do
- it 'skip inclusion of repository URLs' do
- expect(json_response).not_to have_key('ssh_url_to_repo')
- expect(json_response).not_to have_key('http_url_to_repo')
- end
-end
-
RSpec.shared_examples 'snippet blob content' do
it 'returns content from repository' do
+ expect(Gitlab::Workhorse).to receive(:send_git_blob).and_call_original
+
subject
- expect(response.body).to eq(snippet.blobs.first.data)
+ expect(response.header[Gitlab::Workhorse::DETECT_HEADER]).to eq 'true'
+ expect(response.header[Gitlab::Workhorse::SEND_DATA_HEADER]).to start_with('git-blob:')
end
context 'when snippet repository is empty' do
@@ -98,3 +94,15 @@ RSpec.shared_examples 'snippet blob content' do
end
end
end
+
+RSpec.shared_examples 'snippet_multiple_files feature disabled' do
+ before do
+ stub_feature_flags(snippet_multiple_files: false)
+
+ subject
+ end
+
+ it 'does not return files attributes' do
+ expect(json_response).not_to have_key('files')
+ end
+end
diff --git a/spec/support/shared_examples/resource_events.rb b/spec/support/shared_examples/resource_events.rb
index 66f5e760c37..c0158f9b24b 100644
--- a/spec/support/shared_examples/resource_events.rb
+++ b/spec/support/shared_examples/resource_events.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-shared_examples 'a resource event' do
+RSpec.shared_examples 'a resource event' do
let_it_be(:user1) { create(:user) }
let_it_be(:user2) { create(:user) }
@@ -54,7 +54,7 @@ shared_examples 'a resource event' do
end
end
-shared_examples 'a resource event for issues' do
+RSpec.shared_examples 'a resource event for issues' do
let_it_be(:user1) { create(:user) }
let_it_be(:user2) { create(:user) }
@@ -101,9 +101,19 @@ shared_examples 'a resource event for issues' do
expect(events).to be_empty
end
end
+
+ if described_class.method_defined?(:issuable)
+ describe '#issuable' do
+ let_it_be(:event1) { create(described_class.name.underscore.to_sym, issue: issue2) }
+
+ it 'returns the expected issuable' do
+ expect(event1.issuable).to eq(issue2)
+ end
+ end
+ end
end
-shared_examples 'a resource event for merge requests' do
+RSpec.shared_examples 'a resource event for merge requests' do
let_it_be(:user1) { create(:user) }
let_it_be(:user2) { create(:user) }
@@ -132,4 +142,14 @@ shared_examples 'a resource event for merge requests' do
expect(events).to be_empty
end
end
+
+ if described_class.method_defined?(:issuable)
+ describe '#issuable' do
+ let_it_be(:event1) { create(described_class.name.underscore.to_sym, merge_request: merge_request2) }
+
+ it 'returns the expected issuable' do
+ expect(event1.issuable).to eq(merge_request2)
+ end
+ end
+ end
end
diff --git a/spec/support/shared_examples/routing/resource_routing_shared_examples.rb b/spec/support/shared_examples/routing/resource_routing_shared_examples.rb
new file mode 100644
index 00000000000..b98901a57ea
--- /dev/null
+++ b/spec/support/shared_examples/routing/resource_routing_shared_examples.rb
@@ -0,0 +1,73 @@
+# frozen_string_literal: true
+
+# Shared examples for resource routes.
+#
+# By default it tests all the default REST actions: index, create, new, edit,
+# show, update, and destroy. You can remove actions by customizing the
+# `actions` variable.
+#
+# The subject is expected to be an instance of the controller under test.
+#
+# It also expects a `base_path` variable to be available which defines the
+# base path of the controller, and a `base_params` variable which
+# defines the route params the base path maps to.
+#
+# Examples
+#
+# # Default behavior
+# describe Projects::CommitsController, 'routing' do
+# it_behaves_like 'resource routing' do
+# let(:base_path) { '/gitlab/gitlabhq/-/commits' }
+# let(:base_params) { { namespace_id: 'gitlab', project_id: 'gitlabhq' } }
+# end
+# end
+#
+# # Customizing actions
+# it_behaves_like 'resource routing' do
+# let(:base_path) { '/gitlab/gitlabhq/-/commits' }
+#
+# # Specify default actions
+# let(:actions) { [:index] }
+#
+# # Add custom actions by passing a hash with action names
+# # as keys, and the HTTP method and path as values.
+# let(:additional_actions) do
+# {
+# preview_markdown: [:post, '/:id/preview_markdown'],
+# }
+# end
+# end
+RSpec.shared_examples 'resource routing' do
+ let(:controller) { described_class.controller_path }
+ let(:id) { '123' }
+
+ let(:default_actions) do
+ {
+ index: [:get, ''],
+ show: [:get, '/:id'],
+ new: [:get, '/new'],
+ create: [:post, ''],
+ edit: [:get, '/:id/edit'],
+ update: [:put, '/:id'],
+ destroy: [:delete, '/:id']
+ }
+ end
+
+ let(:actions) { default_actions.keys }
+ let(:additional_actions) { {} }
+
+ it 'routes resource actions', :aggregate_failures do
+ selected_actions = default_actions.slice(*actions).merge(additional_actions)
+
+ selected_actions.each do |action, (method, action_path)|
+ expected_params = base_params.merge(controller: controller.to_s, action: action.to_s)
+
+ if action_path.include?(':id')
+ action_path = action_path.sub(':id', id)
+ expected_params[:id] = id
+ end
+
+ expect(public_send(method, "#{base_path}#{action_path}")).to route_to(expected_params)
+ end
+ end
+end
diff --git a/spec/support/shared_examples/routing/wiki_routing_shared_examples.rb b/spec/support/shared_examples/routing/wiki_routing_shared_examples.rb
new file mode 100644
index 00000000000..9289934677e
--- /dev/null
+++ b/spec/support/shared_examples/routing/wiki_routing_shared_examples.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'wiki routing' do
+ it_behaves_like 'resource routing' do
+ let(:id) { 'directory/page' }
+ let(:actions) { %i[show new create edit update destroy] }
+ let(:additional_actions) do
+ {
+ pages: [:get, '/pages'],
+ history: [:get, '/:id/history'],
+ git_access: [:get, '/git_access'],
+ preview_markdown: [:post, '/:id/preview_markdown']
+ }
+ end
+ end
+
+ it 'redirects the base path to the home page', type: :request do
+ expect(get(base_path)).to redirect_to("#{base_path}/home")
+ end
+end
diff --git a/spec/support/shared_examples/services/alert_management_shared_examples.rb b/spec/support/shared_examples/services/alert_management_shared_examples.rb
new file mode 100644
index 00000000000..a1354a8099b
--- /dev/null
+++ b/spec/support/shared_examples/services/alert_management_shared_examples.rb
@@ -0,0 +1,41 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'creates an alert management alert' do
+ it { is_expected.to be_success }
+
+ it 'creates AlertManagement::Alert' do
+ expect { subject }.to change(AlertManagement::Alert, :count).by(1)
+ end
+
+ it 'executes the alert service hooks' do
+ slack_service = create(:service, type: 'SlackService', project: project, alert_events: true, active: true)
+
+ subject
+
+ expect(ProjectServiceWorker).to have_received(:perform_async).with(slack_service.id, an_instance_of(Hash))
+ end
+end
+
+RSpec.shared_examples 'does not an create alert management alert' do
+ it 'does not create alert' do
+ expect { subject }.not_to change(AlertManagement::Alert, :count)
+ end
+end
+
+RSpec.shared_examples 'adds an alert management alert event' do
+ it { is_expected.to be_success }
+
+ it 'does not create an alert' do
+ expect { subject }.not_to change(AlertManagement::Alert, :count)
+ end
+
+ it 'increases alert events count' do
+ expect { subject }.to change { alert.reload.events }.by(1)
+ end
+
+ it 'does not executes the alert service hooks' do
+ expect(alert).not_to receive(:execute_services)
+
+ subject
+ end
+end
diff --git a/spec/support/shared_examples/services/clusters/parse_cluster_applications_artifact_shared_examples.rb b/spec/support/shared_examples/services/clusters/parse_cluster_applications_artifact_shared_examples.rb
new file mode 100644
index 00000000000..cbe20928f98
--- /dev/null
+++ b/spec/support/shared_examples/services/clusters/parse_cluster_applications_artifact_shared_examples.rb
@@ -0,0 +1,89 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'parse cluster applications artifact' do |release_name|
+ let(:application_class) { Clusters::Cluster::APPLICATIONS[release_name] }
+ let(:cluster_application) { cluster.public_send("application_#{release_name}") }
+ let(:file) { fixture_file_upload(Rails.root.join(fixture)) }
+ let(:artifact) { create(:ci_job_artifact, :cluster_applications, job: job, file: file) }
+
+ context 'release is missing' do
+ let(:fixture) { "spec/fixtures/helm/helm_list_v2_#{release_name}_missing.json.gz" }
+
+ context 'application does not exist' do
+ it 'does not create or destroy an application' do
+ expect do
+ described_class.new(job, user).execute(artifact)
+ end.not_to change(application_class, :count)
+ end
+ end
+
+ context 'application exists' do
+ before do
+ create("clusters_applications_#{release_name}".to_sym, :installed, cluster: cluster)
+ end
+
+ it 'marks the application as uninstalled' do
+ described_class.new(job, user).execute(artifact)
+
+ cluster_application.reload
+ expect(cluster_application).to be_uninstalled
+ end
+ end
+ end
+
+ context 'release is deployed' do
+ let(:fixture) { "spec/fixtures/helm/helm_list_v2_#{release_name}_deployed.json.gz" }
+
+ context 'application does not exist' do
+ it 'creates an application and marks it as installed' do
+ expect do
+ described_class.new(job, user).execute(artifact)
+ end.to change(application_class, :count)
+
+ expect(cluster_application).to be_persisted
+ expect(cluster_application).to be_installed
+ end
+ end
+
+ context 'application exists' do
+ before do
+ create("clusters_applications_#{release_name}".to_sym, :errored, cluster: cluster)
+ end
+
+ it 'marks the application as installed' do
+ described_class.new(job, user).execute(artifact)
+
+ expect(cluster_application).to be_installed
+ end
+ end
+ end
+
+ context 'release is failed' do
+ let(:fixture) { "spec/fixtures/helm/helm_list_v2_#{release_name}_failed.json.gz" }
+
+ context 'application does not exist' do
+ it 'creates an application and marks it as errored' do
+ expect do
+ described_class.new(job, user).execute(artifact)
+ end.to change(application_class, :count)
+
+ expect(cluster_application).to be_persisted
+ expect(cluster_application).to be_errored
+ expect(cluster_application.status_reason).to eq('Helm release failed to install')
+ end
+ end
+
+ context 'application exists' do
+ before do
+ create("clusters_applications_#{release_name}".to_sym, :installed, cluster: cluster)
+ end
+
+ it 'marks the application as errored' do
+ described_class.new(job, user).execute(artifact)
+
+ expect(cluster_application).to be_errored
+ expect(cluster_application.status_reason).to eq('Helm release failed to install')
+ end
+ end
+ end
+end
diff --git a/spec/support/shared_examples/services/common_system_notes_shared_examples.rb b/spec/support/shared_examples/services/common_system_notes_shared_examples.rb
index 4ce3e32d774..20856b05de6 100644
--- a/spec/support/shared_examples/services/common_system_notes_shared_examples.rb
+++ b/spec/support/shared_examples/services/common_system_notes_shared_examples.rb
@@ -17,10 +17,10 @@ RSpec.shared_examples 'system note creation' do |update_params, note_text|
end
end
-RSpec.shared_examples 'WIP notes creation' do |wip_action|
+RSpec.shared_examples 'draft notes creation' do |wip_action|
subject { described_class.new(project, user).execute(issuable, old_labels: []) }
- it 'creates WIP toggle and title change notes' do
+ it 'creates Draft toggle and title change notes' do
expect { subject }.to change { Note.count }.from(0).to(2)
expect(Note.first.note).to match("#{wip_action} as a **Work In Progress**")
diff --git a/spec/support/shared_examples/services/jira_import/start_import_service_shared_examples.rb b/spec/support/shared_examples/services/jira_import/start_import_service_shared_examples.rb
index c5e56ed3539..8fd76f7cb1f 100644
--- a/spec/support/shared_examples/services/jira_import/start_import_service_shared_examples.rb
+++ b/spec/support/shared_examples/services/jira_import/start_import_service_shared_examples.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-shared_examples 'responds with error' do |message|
+RSpec.shared_examples 'responds with error' do |message|
it 'returns error' do
expect(subject).to be_a(ServiceResponse)
expect(subject).to be_error
diff --git a/spec/support/shared_examples/services/metrics/dashboard_shared_examples.rb b/spec/support/shared_examples/services/metrics/dashboard_shared_examples.rb
index 5dd1badbefc..c8fabfe30b9 100644
--- a/spec/support/shared_examples/services/metrics/dashboard_shared_examples.rb
+++ b/spec/support/shared_examples/services/metrics/dashboard_shared_examples.rb
@@ -29,14 +29,43 @@ RSpec.shared_examples 'valid dashboard service response' do
end
RSpec.shared_examples 'caches the unprocessed dashboard for subsequent calls' do
- it do
- expect(YAML).to receive(:safe_load).once.and_call_original
+ specify do
+ expect_next_instance_of(::Gitlab::Config::Loader::Yaml) do |loader|
+ expect(loader).to receive(:load_raw!).once.and_call_original
+ end
described_class.new(*service_params).get_dashboard
described_class.new(*service_params).get_dashboard
end
end
+# This spec is applicable for predefined/out-of-the-box dashboard services.
+RSpec.shared_examples 'refreshes cache when dashboard_version is changed' do
+ specify do
+ allow_next_instance_of(described_class) do |service|
+ allow(service).to receive(:dashboard_version).and_return('1', '2')
+ end
+
+ expect(File).to receive(:read).twice.and_call_original
+
+ service = described_class.new(*service_params)
+
+ service.get_dashboard
+ service.get_dashboard
+ end
+end
+
+# This spec is applicable for predefined/out-of-the-box dashboard services.
+# This shared_example requires the following variables to be defined:
+# dashboard_path: Relative path to the dashboard, ex: 'config/prometheus/common_metrics.yml'
+# dashboard_version: The version string used in the cache_key.
+RSpec.shared_examples 'dashboard_version contains SHA256 hash of dashboard file content' do
+ specify do
+ dashboard = File.read(Rails.root.join(dashboard_path))
+ expect(Digest::SHA256.hexdigest(dashboard)).to eq(dashboard_version)
+ end
+end
+
RSpec.shared_examples 'valid embedded dashboard service response' do
let(:dashboard_schema) { Gitlab::Json.parse(fixture_file('lib/gitlab/metrics/dashboard/schemas/embedded_dashboard.json')) }
@@ -128,3 +157,50 @@ RSpec.shared_examples 'updates gitlab_metrics_dashboard_processing_time_ms metri
expect(metric.get(labels)).to be > 0
end
end
+
+RSpec.shared_examples '#raw_dashboard raises error if dashboard loading fails' do
+ context 'when yaml is too large' do
+ before do
+ allow_next_instance_of(::Gitlab::Config::Loader::Yaml) do |loader|
+ allow(loader).to receive(:load_raw!)
+ .and_raise(Gitlab::Config::Loader::Yaml::DataTooLargeError, 'The parsed YAML is too big')
+ end
+ end
+
+ it 'raises error' do
+ expect { subject.raw_dashboard }.to raise_error(
+ Gitlab::Metrics::Dashboard::Errors::LayoutError,
+ 'The parsed YAML is too big'
+ )
+ end
+ end
+
+ context 'when yaml loader returns error' do
+ before do
+ allow_next_instance_of(::Gitlab::Config::Loader::Yaml) do |loader|
+ allow(loader).to receive(:load_raw!)
+ .and_raise(Gitlab::Config::Loader::FormatError, 'Invalid configuration format')
+ end
+ end
+
+ it 'raises error' do
+ expect { subject.raw_dashboard }.to raise_error(
+ Gitlab::Metrics::Dashboard::Errors::LayoutError,
+ 'Invalid yaml'
+ )
+ end
+ end
+
+ context 'when yaml is not a hash' do
+ before do
+ allow_next_instance_of(::Gitlab::Config::Loader::Yaml) do |loader|
+ allow(loader).to receive(:load_raw!)
+ .and_raise(Gitlab::Config::Loader::Yaml::NotHashError, 'Invalid configuration format')
+ end
+ end
+
+ it 'returns nil' do
+ expect(subject.raw_dashboard).to eq({})
+ end
+ end
+end
diff --git a/spec/support/shared_examples/services/packages_shared_examples.rb b/spec/support/shared_examples/services/packages_shared_examples.rb
new file mode 100644
index 00000000000..45a4c2bb151
--- /dev/null
+++ b/spec/support/shared_examples/services/packages_shared_examples.rb
@@ -0,0 +1,193 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'assigns build to package' do
+ context 'with build info' do
+ let(:job) { create(:ci_build, user: user) }
+ let(:params) { super().merge(build: job) }
+
+ it 'assigns the pipeline to the package' do
+ package = subject
+
+ expect(package.build_info).to be_present
+ expect(package.build_info.pipeline).to eq job.pipeline
+ end
+ end
+end
+
+RSpec.shared_examples 'returns packages' do |container_type, user_type|
+ context "for #{user_type}" do
+ before do
+ send(container_type)&.send("add_#{user_type}", user) unless user_type == :no_type
+ end
+
+ it 'returns success response' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:success)
+ end
+
+ it 'returns a valid response schema' do
+ subject
+
+ expect(response).to match_response_schema(package_schema)
+ end
+
+ it 'returns two packages' do
+ subject
+
+ expect(json_response.length).to eq(2)
+ expect(json_response.map { |package| package['id'] }).to contain_exactly(package1.id, package2.id)
+ end
+ end
+end
+
+RSpec.shared_examples 'returns packages with subgroups' do |container_type, user_type|
+ context "with subgroups for #{user_type}" do
+ before do
+ send(container_type)&.send("add_#{user_type}", user) unless user_type == :no_type
+ end
+
+ it 'returns success response' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:success)
+ end
+
+ it 'returns a valid response schema' do
+ subject
+
+ expect(response).to match_response_schema(package_schema)
+ end
+
+ it 'returns three packages' do
+ subject
+
+ expect(json_response.length).to eq(3)
+ expect(json_response.map { |package| package['id'] }).to contain_exactly(package1.id, package2.id, package3.id)
+ end
+ end
+end
+
+RSpec.shared_examples 'package sorting' do |order_by|
+ subject { get api(url), params: { sort: sort, order_by: order_by } }
+
+ context "sorting by #{order_by}" do
+ context 'ascending order' do
+ let(:sort) { 'asc' }
+
+ it 'returns the sorted packages' do
+ subject
+
+ expect(json_response.map { |package| package['id'] }).to eq(packages.map(&:id))
+ end
+ end
+
+ context 'descending order' do
+ let(:sort) { 'desc' }
+
+ it 'returns the sorted packages' do
+ subject
+
+ expect(json_response.map { |package| package['id'] }).to eq(packages.reverse.map(&:id))
+ end
+ end
+ end
+end
+
+RSpec.shared_examples 'rejects packages access' do |container_type, user_type, status|
+ context "for #{user_type}" do
+ before do
+ send(container_type)&.send("add_#{user_type}", user) unless user_type == :no_type
+ end
+
+ it_behaves_like 'returning response status', status
+ end
+end
+
+RSpec.shared_examples 'returns paginated packages' do
+ let(:per_page) { 2 }
+
+ context 'when viewing the first page' do
+ let(:page) { 1 }
+
+ it 'returns first 2 packages' do
+ get api(url, user), params: { page: page, per_page: per_page }
+
+ expect_paginated_array_response([package1.id, package2.id])
+ end
+ end
+
+ context 'when viewing the second page' do
+ let(:page) { 2 }
+
+ it 'returns first 2 packages' do
+ get api(url, user), params: { page: page, per_page: per_page }
+
+ expect_paginated_array_response([package3.id, package4.id])
+ end
+ end
+end
+
+RSpec.shared_examples 'background upload schedules a file migration' do
+ context 'background upload enabled' do
+ before do
+ stub_package_file_object_storage(background_upload: true)
+ end
+
+ it 'schedules migration of file to object storage' do
+ expect(ObjectStorage::BackgroundMoveWorker).to receive(:perform_async).with('Packages::PackageFileUploader', 'Packages::PackageFile', :file, kind_of(Numeric))
+
+ subject
+ end
+ end
+end
+
+RSpec.shared_context 'package filter context' do
+ def package_filter_url(filter, param)
+ "/projects/#{project.id}/packages?package_#{filter}=#{param}"
+ end
+
+ def group_filter_url(filter, param)
+ "/groups/#{group.id}/packages?package_#{filter}=#{param}"
+ end
+end
+
+RSpec.shared_examples 'filters on each package_type' do |is_project: false|
+ include_context 'package filter context'
+
+ let_it_be(:package1) { create(:conan_package, project: project) }
+ let_it_be(:package2) { create(:maven_package, project: project) }
+ let_it_be(:package3) { create(:npm_package, project: project) }
+ let_it_be(:package4) { create(:nuget_package, project: project) }
+ let_it_be(:package5) { create(:pypi_package, project: project) }
+ let_it_be(:package6) { create(:composer_package, project: project) }
+
+ Packages::Package.package_types.keys.each do |package_type|
+ context "for package type #{package_type}" do
+ let(:url) { is_project ? package_filter_url(:type, package_type) : group_filter_url(:type, package_type) }
+
+ subject { get api(url, user) }
+
+ it "returns #{package_type} packages" do
+ subject
+
+ expect(json_response.length).to eq(1)
+ expect(json_response.map { |package| package['package_type'] }).to contain_exactly(package_type)
+ end
+ end
+ end
+end
+
+RSpec.shared_examples 'package workhorse uploads' do
+ context 'without a workhorse header' do
+ let(:workhorse_token) { JWT.encode({ 'iss' => 'invalid header' }, Gitlab::Workhorse.secret, 'HS256') }
+
+ it_behaves_like 'returning response status', :forbidden
+
+ it 'logs an error' do
+ expect(Gitlab::ErrorTracking).to receive(:track_exception).once
+
+ subject
+ end
+ end
+end
diff --git a/spec/support/shared_examples/services/projects/update_repository_storage_service_shared_examples.rb b/spec/support/shared_examples/services/projects/update_repository_storage_service_shared_examples.rb
index 0e6ecf49cd0..2ddbdebdb97 100644
--- a/spec/support/shared_examples/services/projects/update_repository_storage_service_shared_examples.rb
+++ b/spec/support/shared_examples/services/projects/update_repository_storage_service_shared_examples.rb
@@ -25,19 +25,18 @@ RSpec.shared_examples 'moves repository to another storage' do |repository_type|
allow(Gitlab::GitalyClient).to receive(:filesystem_id).with('default').and_call_original
allow(Gitlab::GitalyClient).to receive(:filesystem_id).with('test_second_storage').and_return(SecureRandom.uuid)
- allow(project_repository_double).to receive(:create_repository)
- .and_return(true)
allow(project_repository_double).to receive(:replicate)
.with(project.repository.raw)
allow(project_repository_double).to receive(:checksum)
.and_return(project_repository_checksum)
- allow(repository_double).to receive(:create_repository)
- .and_return(true)
allow(repository_double).to receive(:replicate)
.with(repository.raw)
allow(repository_double).to receive(:checksum)
.and_return(repository_checksum)
+
+ expect(GitlabShellWorker).to receive(:perform_async).with(:mv_repository, 'default', anything, anything)
+ .twice.and_call_original
end
it "moves the project and its #{repository_type} repository to the new storage and unmarks the repository as read only" do
@@ -48,6 +47,7 @@ RSpec.shared_examples 'moves repository to another storage' do |repository_type|
old_repository_path = repository.full_path
result = subject.execute
+ project.reload
expect(result).to be_success
expect(project).not_to be_repository_read_only
@@ -101,15 +101,11 @@ RSpec.shared_examples 'moves repository to another storage' do |repository_type|
it 'unmarks the repository as read-only without updating the repository storage' do
allow(Gitlab::GitalyClient).to receive(:filesystem_id).with('default').and_call_original
allow(Gitlab::GitalyClient).to receive(:filesystem_id).with('test_second_storage').and_return(SecureRandom.uuid)
- allow(project_repository_double).to receive(:create_repository)
- .and_return(true)
allow(project_repository_double).to receive(:replicate)
.with(project.repository.raw)
allow(project_repository_double).to receive(:checksum)
.and_return(project_repository_checksum)
- allow(repository_double).to receive(:create_repository)
- .and_return(true)
allow(repository_double).to receive(:replicate)
.with(repository.raw)
.and_raise(Gitlab::Git::CommandError)
@@ -128,15 +124,11 @@ RSpec.shared_examples 'moves repository to another storage' do |repository_type|
it 'unmarks the repository as read-only without updating the repository storage' do
allow(Gitlab::GitalyClient).to receive(:filesystem_id).with('default').and_call_original
allow(Gitlab::GitalyClient).to receive(:filesystem_id).with('test_second_storage').and_return(SecureRandom.uuid)
- allow(project_repository_double).to receive(:create_repository)
- .and_return(true)
allow(project_repository_double).to receive(:replicate)
.with(project.repository.raw)
allow(project_repository_double).to receive(:checksum)
.and_return(project_repository_checksum)
- allow(repository_double).to receive(:create_repository)
- .and_return(true)
allow(repository_double).to receive(:replicate)
.with(repository.raw)
allow(repository_double).to receive(:checksum)
diff --git a/spec/support/shared_examples/services/resource_events/change_milestone_service_shared_examples.rb b/spec/support/shared_examples/services/resource_events/change_milestone_service_shared_examples.rb
index c5f84e205cf..ef41c2fcc13 100644
--- a/spec/support/shared_examples/services/resource_events/change_milestone_service_shared_examples.rb
+++ b/spec/support/shared_examples/services/resource_events/change_milestone_service_shared_examples.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-shared_examples 'a milestone events creator' do
+RSpec.shared_examples 'a milestone events creator' do
let_it_be(:user) { create(:user) }
let(:created_at_time) { Time.utc(2019, 12, 30) }
diff --git a/spec/support/shared_examples/services/wiki_pages/create_service_shared_examples.rb b/spec/support/shared_examples/services/wiki_pages/create_service_shared_examples.rb
index efcb83a34af..ebe78c299a5 100644
--- a/spec/support/shared_examples/services/wiki_pages/create_service_shared_examples.rb
+++ b/spec/support/shared_examples/services/wiki_pages/create_service_shared_examples.rb
@@ -63,16 +63,6 @@ RSpec.shared_examples 'WikiPages::CreateService#execute' do |container_type|
include_examples 'correct event created'
end
- context 'the feature is disabled' do
- before do
- stub_feature_flags(wiki_events: false)
- end
-
- it 'does not record the activity' do
- expect { service.execute }.not_to change(Event, :count)
- end
- end
-
context 'when the options are bad' do
let(:page_title) { '' }
diff --git a/spec/support/shared_examples/services/wiki_pages/destroy_service_shared_examples.rb b/spec/support/shared_examples/services/wiki_pages/destroy_service_shared_examples.rb
index 1231c012c31..db1b50fdf3c 100644
--- a/spec/support/shared_examples/services/wiki_pages/destroy_service_shared_examples.rb
+++ b/spec/support/shared_examples/services/wiki_pages/destroy_service_shared_examples.rb
@@ -37,14 +37,4 @@ RSpec.shared_examples 'WikiPages::DestroyService#execute' do |container_type|
expect { service.execute(nil) }.not_to change { counter.read(:delete) }
end
-
- context 'the feature is disabled' do
- before do
- stub_feature_flags(wiki_events: false)
- end
-
- it 'does not record the activity' do
- expect { service.execute(page) }.not_to change(Event, :count)
- end
- end
end
diff --git a/spec/support/shared_examples/services/wiki_pages/update_service_shared_examples.rb b/spec/support/shared_examples/services/wiki_pages/update_service_shared_examples.rb
index 77354fec069..0191a6dfbc9 100644
--- a/spec/support/shared_examples/services/wiki_pages/update_service_shared_examples.rb
+++ b/spec/support/shared_examples/services/wiki_pages/update_service_shared_examples.rb
@@ -67,16 +67,6 @@ RSpec.shared_examples 'WikiPages::UpdateService#execute' do |container_type|
include_examples 'adds activity event'
end
- context 'the feature is disabled' do
- before do
- stub_feature_flags(wiki_events: false)
- end
-
- it 'does not record the activity' do
- expect { service.execute(page) }.not_to change(Event, :count)
- end
- end
-
context 'when the options are bad' do
let(:page_title) { '' }
diff --git a/spec/support/shared_examples/snippet_blob_shared_examples.rb b/spec/support/shared_examples/snippet_blob_shared_examples.rb
new file mode 100644
index 00000000000..ba97688d017
--- /dev/null
+++ b/spec/support/shared_examples/snippet_blob_shared_examples.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.shared_examples 'snippet blob raw path' do
+ let(:blob) { snippet.blobs.first }
+ let(:ref) { blob.repository.root_ref }
+
+ context 'for PersonalSnippets' do
+ let(:snippet) { personal_snippet }
+
+ it 'returns the raw personal snippet blob path' do
+ expect(subject).to eq("/-/snippets/#{snippet.id}/raw/#{ref}/#{blob.path}")
+ end
+ end
+
+ context 'for ProjectSnippets' do
+ let(:snippet) { project_snippet }
+
+ it 'returns the raw project snippet blob path' do
+ expect(subject).to eq("/#{snippet.project.full_path}/-/snippets/#{snippet.id}/raw/#{ref}/#{blob.path}")
+ end
+ end
+end
diff --git a/spec/support/shared_examples/uploaders/upload_type_shared_examples.rb b/spec/support/shared_examples/uploaders/upload_type_shared_examples.rb
index e58723324d3..9e0104e1410 100644
--- a/spec/support/shared_examples/uploaders/upload_type_shared_examples.rb
+++ b/spec/support/shared_examples/uploaders/upload_type_shared_examples.rb
@@ -2,7 +2,7 @@
# @param path [String] the path to file to upload. E.g. File.join('spec', 'fixtures', 'sanitized.svg')
# @param uploader [CarrierWave::Uploader::Base] uploader to handle the upload.
-shared_examples 'denied carrierwave upload' do
+RSpec.shared_examples 'denied carrierwave upload' do
it 'will deny upload' do
fixture_file = fixture_file_upload(path)
expect { uploader.cache!(fixture_file) }.to raise_exception(CarrierWave::IntegrityError)
@@ -11,7 +11,7 @@ end
# @param path [String] the path to file to upload. E.g. File.join('spec', 'fixtures', 'sanitized.svg')
# @param uploader [CarrierWave::Uploader::Base] uploader to handle the upload.
-shared_examples 'accepted carrierwave upload' do
+RSpec.shared_examples 'accepted carrierwave upload' do
let(:fixture_file) { fixture_file_upload(path) }
before do
@@ -30,7 +30,7 @@ end
# @param path [String] the path to file to upload. E.g. File.join('spec', 'fixtures', 'sanitized.svg')
# @param uploader [CarrierWave::Uploader::Base] uploader to handle the upload.
# @param content_type [String] the upload file content type after cache
-shared_examples 'upload with content type' do |content_type|
+RSpec.shared_examples 'upload with content type' do |content_type|
let(:fixture_file) { fixture_file_upload(path, content_type) }
it 'will not change upload file content type' do
diff --git a/spec/support/shared_examples/views/pipeline_status_changes_email.rb b/spec/support/shared_examples/views/pipeline_status_changes_email.rb
index 15b4ce9c44e..698f11c2216 100644
--- a/spec/support/shared_examples/views/pipeline_status_changes_email.rb
+++ b/spec/support/shared_examples/views/pipeline_status_changes_email.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-shared_examples 'pipeline status changes email' do
+RSpec.shared_examples 'pipeline status changes email' do
include Devise::Test::ControllerHelpers
let(:user) { create(:user, developer_projects: [project]) }
diff --git a/spec/support/shared_examples/views/plain_text_email.rb b/spec/support/shared_examples/views/plain_text_email.rb
new file mode 100644
index 00000000000..23f9262b446
--- /dev/null
+++ b/spec/support/shared_examples/views/plain_text_email.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'renders plain text email correctly' do
+ it 'renders the email without HTML links' do
+ render
+
+ expect(rendered).to have_no_selector('a')
+ end
+end
diff --git a/spec/support/shared_examples/workers/gitlab/jira_import/jira_import_workers_shared_examples.rb b/spec/support/shared_examples/workers/gitlab/jira_import/jira_import_workers_shared_examples.rb
index ae8c82cb67c..2cca76c8fe3 100644
--- a/spec/support/shared_examples/workers/gitlab/jira_import/jira_import_workers_shared_examples.rb
+++ b/spec/support/shared_examples/workers/gitlab/jira_import/jira_import_workers_shared_examples.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-shared_examples 'include import workers modules' do
+RSpec.shared_examples 'include import workers modules' do
it { expect(described_class).to include_module(ApplicationWorker) }
it { expect(described_class).to include_module(Gitlab::JiraImport::QueueOptions) }
@@ -12,7 +12,7 @@ shared_examples 'include import workers modules' do
end
end
-shared_examples 'does not advance to next stage' do
+RSpec.shared_examples 'does not advance to next stage' do
it 'does not advance to next stage' do
expect(Gitlab::JiraImport::AdvanceStageWorker).not_to receive(:perform_async)
@@ -20,7 +20,7 @@ shared_examples 'does not advance to next stage' do
end
end
-shared_examples 'cannot do Jira import' do
+RSpec.shared_examples 'cannot do Jira import' do
it 'does not advance to next stage' do
worker = described_class.new
expect(worker).not_to receive(:import)
@@ -29,7 +29,7 @@ shared_examples 'cannot do Jira import' do
end
end
-shared_examples 'advance to next stage' do |next_stage|
+RSpec.shared_examples 'advance to next stage' do |next_stage|
let(:job_waiter) { Gitlab::JobWaiter.new(2, 'some-job-key') }
it "advances to #{next_stage} stage" do
diff --git a/spec/support_specs/helpers/active_record/query_recorder_spec.rb b/spec/support_specs/helpers/active_record/query_recorder_spec.rb
index d15fbb5d4c3..f968f511a2a 100644
--- a/spec/support_specs/helpers/active_record/query_recorder_spec.rb
+++ b/spec/support_specs/helpers/active_record/query_recorder_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ActiveRecord::QueryRecorder do
+RSpec.describe ActiveRecord::QueryRecorder do
before do
stub_const('TestQueries', Class.new(ActiveRecord::Base))
diff --git a/spec/support_specs/helpers/graphql_helpers_spec.rb b/spec/support_specs/helpers/graphql_helpers_spec.rb
index bfc71f519cf..bc777621674 100644
--- a/spec/support_specs/helpers/graphql_helpers_spec.rb
+++ b/spec/support_specs/helpers/graphql_helpers_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GraphqlHelpers do
+RSpec.describe GraphqlHelpers do
include GraphqlHelpers
describe '.graphql_mutation' do
diff --git a/spec/support_specs/helpers/stub_feature_flags_spec.rb b/spec/support_specs/helpers/stub_feature_flags_spec.rb
index e90a7f68a90..8d5f16751ae 100644
--- a/spec/support_specs/helpers/stub_feature_flags_spec.rb
+++ b/spec/support_specs/helpers/stub_feature_flags_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe StubFeatureFlags do
+RSpec.describe StubFeatureFlags do
let(:feature_name) { :test_feature }
describe '#stub_feature_flags' do
diff --git a/spec/support_specs/matchers/exceed_query_limit_helpers_spec.rb b/spec/support_specs/matchers/exceed_query_limit_helpers_spec.rb
index 4a711b43d9a..15b846f28cb 100644
--- a/spec/support_specs/matchers/exceed_query_limit_helpers_spec.rb
+++ b/spec/support_specs/matchers/exceed_query_limit_helpers_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ExceedQueryLimitHelpers do
+RSpec.describe ExceedQueryLimitHelpers do
before do
stub_const('TestQueries', Class.new(ActiveRecord::Base))
stub_const('TestMatcher', Class.new)
diff --git a/spec/tasks/cache/clear/redis_spec.rb b/spec/tasks/cache/clear/redis_spec.rb
index 4f597988763..d2de068f254 100644
--- a/spec/tasks/cache/clear/redis_spec.rb
+++ b/spec/tasks/cache/clear/redis_spec.rb
@@ -2,7 +2,7 @@
require 'rake_helper'
-describe 'clearing redis cache' do
+RSpec.describe 'clearing redis cache' do
before do
Rake.application.rake_require 'tasks/cache'
end
diff --git a/spec/tasks/config_lint_spec.rb b/spec/tasks/config_lint_spec.rb
index c6c11d76388..f5a382989ea 100644
--- a/spec/tasks/config_lint_spec.rb
+++ b/spec/tasks/config_lint_spec.rb
@@ -3,7 +3,7 @@
require 'rake_helper'
Rake.application.rake_require 'tasks/config_lint'
-describe ConfigLint do
+RSpec.describe ConfigLint do
let(:files) { ['lib/support/fake.sh'] }
it 'errors out if any bash scripts have errors' do
@@ -15,7 +15,7 @@ describe ConfigLint do
end
end
-describe 'config_lint rake task' do
+RSpec.describe 'config_lint rake task' do
before do
# Prevent `system` from actually being called
allow(Kernel).to receive(:system).and_return(true)
diff --git a/spec/tasks/gitlab/artifacts/check_rake_spec.rb b/spec/tasks/gitlab/artifacts/check_rake_spec.rb
index 04015f0e21a..d1d02ab9bc5 100644
--- a/spec/tasks/gitlab/artifacts/check_rake_spec.rb
+++ b/spec/tasks/gitlab/artifacts/check_rake_spec.rb
@@ -2,7 +2,7 @@
require 'rake_helper'
-describe 'gitlab:artifacts rake tasks' do
+RSpec.describe 'gitlab:artifacts rake tasks' do
describe 'check' do
let!(:artifact) { create(:ci_job_artifact, :archive, :correct_checksum) }
diff --git a/spec/tasks/gitlab/artifacts/migrate_rake_spec.rb b/spec/tasks/gitlab/artifacts/migrate_rake_spec.rb
index 9ee00b4297b..94a8da9478a 100644
--- a/spec/tasks/gitlab/artifacts/migrate_rake_spec.rb
+++ b/spec/tasks/gitlab/artifacts/migrate_rake_spec.rb
@@ -2,7 +2,7 @@
require 'rake_helper'
-describe 'gitlab:artifacts namespace rake task' do
+RSpec.describe 'gitlab:artifacts namespace rake task' do
before(:context) do
Rake.application.rake_require 'tasks/gitlab/artifacts/migrate'
end
diff --git a/spec/tasks/gitlab/backup_rake_spec.rb b/spec/tasks/gitlab/backup_rake_spec.rb
index febc18e497a..661ce8e74d2 100644
--- a/spec/tasks/gitlab/backup_rake_spec.rb
+++ b/spec/tasks/gitlab/backup_rake_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require 'rake'
-describe 'gitlab:app namespace rake task', :delete do
+RSpec.describe 'gitlab:app namespace rake task', :delete do
let(:enable_registry) { true }
def tars_glob
diff --git a/spec/tasks/gitlab/check_rake_spec.rb b/spec/tasks/gitlab/check_rake_spec.rb
index e3e2a22add9..7956b5c57e6 100644
--- a/spec/tasks/gitlab/check_rake_spec.rb
+++ b/spec/tasks/gitlab/check_rake_spec.rb
@@ -2,7 +2,7 @@
require 'rake_helper'
-describe 'check.rake' do
+RSpec.describe 'check.rake' do
before do
Rake.application.rake_require 'tasks/gitlab/check'
diff --git a/spec/tasks/gitlab/cleanup_rake_spec.rb b/spec/tasks/gitlab/cleanup_rake_spec.rb
index 8db18895c24..dc460611169 100644
--- a/spec/tasks/gitlab/cleanup_rake_spec.rb
+++ b/spec/tasks/gitlab/cleanup_rake_spec.rb
@@ -2,7 +2,7 @@
require 'rake_helper'
-describe 'gitlab:cleanup rake tasks' do
+RSpec.describe 'gitlab:cleanup rake tasks' do
before do
Rake.application.rake_require 'tasks/gitlab/cleanup'
end
diff --git a/spec/tasks/gitlab/container_registry_rake_spec.rb b/spec/tasks/gitlab/container_registry_rake_spec.rb
index 181d5c8b7c8..b83ff567126 100644
--- a/spec/tasks/gitlab/container_registry_rake_spec.rb
+++ b/spec/tasks/gitlab/container_registry_rake_spec.rb
@@ -2,25 +2,19 @@
require 'rake_helper'
-describe 'gitlab:container_registry namespace rake tasks' do
- let_it_be(:application_settings) { Gitlab::CurrentSettings }
+RSpec.describe 'gitlab:container_registry namespace rake tasks' do
let_it_be(:api_url) { 'http://registry.gitlab' }
before :all do
Rake.application.rake_require 'tasks/gitlab/container_registry'
end
- describe 'configure' do
- before do
- stub_access_token
- stub_container_registry_config(enabled: true, api_url: api_url)
- end
-
+ describe '#configure' do
subject { run_rake_task('gitlab:container_registry:configure') }
shared_examples 'invalid config' do
- it 'does not update the application settings' do
- expect(application_settings).not_to receive(:update!)
+ it 'does not call UpdateContainerRegistryInfoService' do
+ expect_any_instance_of(UpdateContainerRegistryInfoService).not_to receive(:execute)
subject
end
@@ -30,7 +24,7 @@ describe 'gitlab:container_registry namespace rake tasks' do
end
it 'prints a warning message' do
- expect { subject }.to output(/Registry is not enabled or registry api url is not present./).to_stdout
+ expect { subject }.to output("Registry is not enabled or registry api url is not present.\n").to_stdout
end
end
@@ -50,74 +44,18 @@ describe 'gitlab:container_registry namespace rake tasks' do
it_behaves_like 'invalid config'
end
- context 'when creating a registry client instance' do
- let(:token) { 'foo' }
- let(:client) { ContainerRegistry::Client.new(api_url, token: token) }
-
+ context 'when container registry is enabled and api_url is not blank' do
before do
- stub_registry_info({})
- end
-
- it 'uses a token with no access permissions' do
- expect(Auth::ContainerRegistryAuthenticationService)
- .to receive(:access_token).with([], []).and_return(token)
- expect(ContainerRegistry::Client)
- .to receive(:new).with(api_url, token: token).and_return(client)
-
- run_rake_task('gitlab:container_registry:configure')
+ stub_container_registry_config(enabled: true, api_url: api_url)
end
- end
-
- context 'when unabled to detect the container registry type' do
- it 'fails and raises an error message' do
- stub_registry_info({})
-
- run_rake_task('gitlab:container_registry:configure')
-
- application_settings.reload
- expect(application_settings.container_registry_vendor).to be_blank
- expect(application_settings.container_registry_version).to be_blank
- expect(application_settings.container_registry_features).to eq([])
- end
- end
- context 'when able to detect the container registry type' do
- context 'when using the GitLab container registry' do
- it 'updates application settings accordingly' do
- stub_registry_info(vendor: 'gitlab', version: '2.9.1-gitlab', features: %w[a,b,c])
-
- run_rake_task('gitlab:container_registry:configure')
-
- application_settings.reload
- expect(application_settings.container_registry_vendor).to eq('gitlab')
- expect(application_settings.container_registry_version).to eq('2.9.1-gitlab')
- expect(application_settings.container_registry_features).to eq(%w[a,b,c])
+ it 'calls UpdateContainerRegistryInfoService' do
+ expect_next_instance_of(UpdateContainerRegistryInfoService) do |service|
+ expect(service).to receive(:execute)
end
- end
- context 'when using a third-party container registry' do
- it 'updates application settings accordingly' do
- stub_registry_info(vendor: 'other', version: nil, features: nil)
-
- run_rake_task('gitlab:container_registry:configure')
-
- application_settings.reload
- expect(application_settings.container_registry_vendor).to eq('other')
- expect(application_settings.container_registry_version).to be_blank
- expect(application_settings.container_registry_features).to eq([])
- end
+ subject
end
end
end
-
- def stub_access_token
- allow(Auth::ContainerRegistryAuthenticationService)
- .to receive(:access_token).with([], []).and_return('foo')
- end
-
- def stub_registry_info(output)
- allow_next_instance_of(ContainerRegistry::Client) do |client|
- allow(client).to receive(:registry_info).and_return(output)
- end
- end
end
diff --git a/spec/tasks/gitlab/db_rake_spec.rb b/spec/tasks/gitlab/db_rake_spec.rb
index c3da5af5439..a78506803be 100644
--- a/spec/tasks/gitlab/db_rake_spec.rb
+++ b/spec/tasks/gitlab/db_rake_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require 'rake'
-describe 'gitlab:db namespace rake task' do
+RSpec.describe 'gitlab:db namespace rake task' do
before :all do
Rake.application.rake_require 'active_record/railties/databases'
Rake.application.rake_require 'tasks/seed_fu'
diff --git a/spec/tasks/gitlab/external_diffs_rake_spec.rb b/spec/tasks/gitlab/external_diffs_rake_spec.rb
new file mode 100644
index 00000000000..66e555734b3
--- /dev/null
+++ b/spec/tasks/gitlab/external_diffs_rake_spec.rb
@@ -0,0 +1,34 @@
+# frozen_string_literal: true
+
+require 'rake_helper'
+
+RSpec.describe 'gitlab:external_diffs rake tasks' do
+ before do
+ Rake.application.rake_require 'tasks/gitlab/external_diffs'
+ end
+
+ describe 'force_object_storage task' do
+ it 'forces externally stored merge request diffs to object storage' do
+ db = create(:merge_request).merge_request_diff
+ file = create(:merge_request).merge_request_diff.tap { |o| o.update_columns(stored_externally: true, external_diff_store: 1) }
+ object = create(:merge_request).merge_request_diff.tap { |o| o.update_columns(stored_externally: true, external_diff_store: 2) }
+
+ run_rake_task('gitlab:external_diffs:force_object_storage')
+
+ expect(db.reload).not_to be_stored_externally
+ expect(file.reload).to be_stored_externally
+ expect(object.reload).to be_stored_externally
+
+ expect(file.external_diff_store).to eq(2)
+ expect(object.external_diff_store).to eq(2)
+ end
+
+ it 'limits batches according to BATCH_SIZE, START_ID, and END_ID' do
+ stub_env('START_ID' => 'foo', 'END_ID' => 'bar', 'BATCH_SIZE' => 'baz')
+
+ expect(MergeRequestDiff).to receive(:in_batches).with(start: 'foo', finish: 'bar', of: 'baz')
+
+ run_rake_task('gitlab:external_diffs:force_object_storage')
+ end
+ end
+end
diff --git a/spec/tasks/gitlab/generate_sample_prometheus_data_spec.rb b/spec/tasks/gitlab/generate_sample_prometheus_data_spec.rb
index 7620047624a..a8effef2d7b 100644
--- a/spec/tasks/gitlab/generate_sample_prometheus_data_spec.rb
+++ b/spec/tasks/gitlab/generate_sample_prometheus_data_spec.rb
@@ -2,7 +2,7 @@
require 'rake_helper'
-describe 'gitlab:generate_sample_prometheus_data rake task' do
+RSpec.describe 'gitlab:generate_sample_prometheus_data rake task' do
let(:cluster) { create(:cluster, :provided_by_user, :project) }
let(:environment) { create(:environment, project: cluster.project) }
let(:sample_query_file) { File.join(Rails.root, Metrics::SampleMetricsService::DIRECTORY, 'test_query_result.yml') }
diff --git a/spec/tasks/gitlab/git_rake_spec.rb b/spec/tasks/gitlab/git_rake_spec.rb
index b8156e55ec7..4bc1b460d9b 100644
--- a/spec/tasks/gitlab/git_rake_spec.rb
+++ b/spec/tasks/gitlab/git_rake_spec.rb
@@ -2,7 +2,7 @@
require 'rake_helper'
-describe 'gitlab:git rake tasks' do
+RSpec.describe 'gitlab:git rake tasks' do
let(:base_path) { 'tmp/tests/default_storage' }
let!(:project) { create(:project, :repository) }
diff --git a/spec/tasks/gitlab/gitaly_rake_spec.rb b/spec/tasks/gitlab/gitaly_rake_spec.rb
index d9fdc183bfe..c59370a7a32 100644
--- a/spec/tasks/gitlab/gitaly_rake_spec.rb
+++ b/spec/tasks/gitlab/gitaly_rake_spec.rb
@@ -2,7 +2,7 @@
require 'rake_helper'
-describe 'gitlab:gitaly namespace rake task' do
+RSpec.describe 'gitlab:gitaly namespace rake task' do
before :all do
Rake.application.rake_require 'tasks/gitlab/gitaly'
end
diff --git a/spec/tasks/gitlab/info_rake_spec.rb b/spec/tasks/gitlab/info_rake_spec.rb
index 8d6b3985380..941f3429017 100644
--- a/spec/tasks/gitlab/info_rake_spec.rb
+++ b/spec/tasks/gitlab/info_rake_spec.rb
@@ -2,7 +2,7 @@
require 'rake_helper'
-describe 'gitlab:env:info' do
+RSpec.describe 'gitlab:env:info' do
before do
Rake.application.rake_require 'tasks/gitlab/info'
diff --git a/spec/tasks/gitlab/ldap_rake_spec.rb b/spec/tasks/gitlab/ldap_rake_spec.rb
index bbc3f625088..275fcb98dd0 100644
--- a/spec/tasks/gitlab/ldap_rake_spec.rb
+++ b/spec/tasks/gitlab/ldap_rake_spec.rb
@@ -2,7 +2,7 @@
require 'rake_helper'
-describe 'gitlab:ldap:rename_provider rake task' do
+RSpec.describe 'gitlab:ldap:rename_provider rake task' do
it 'completes without error' do
Rake.application.rake_require 'tasks/gitlab/ldap'
stub_warn_user_is_not_gitlab
diff --git a/spec/tasks/gitlab/lfs/check_rake_spec.rb b/spec/tasks/gitlab/lfs/check_rake_spec.rb
index 3d698efdcb6..fd1b6d010e4 100644
--- a/spec/tasks/gitlab/lfs/check_rake_spec.rb
+++ b/spec/tasks/gitlab/lfs/check_rake_spec.rb
@@ -2,7 +2,7 @@
require 'rake_helper'
-describe 'gitlab:lfs rake tasks' do
+RSpec.describe 'gitlab:lfs rake tasks' do
describe 'check' do
let!(:lfs_object) { create(:lfs_object, :with_file, :correct_oid) }
diff --git a/spec/tasks/gitlab/lfs/migrate_rake_spec.rb b/spec/tasks/gitlab/lfs/migrate_rake_spec.rb
index fc7be0eebcd..a59da615b94 100644
--- a/spec/tasks/gitlab/lfs/migrate_rake_spec.rb
+++ b/spec/tasks/gitlab/lfs/migrate_rake_spec.rb
@@ -2,7 +2,7 @@
require 'rake_helper'
-describe 'gitlab:lfs namespace rake task' do
+RSpec.describe 'gitlab:lfs namespace rake task' do
before :all do
Rake.application.rake_require 'tasks/gitlab/lfs/migrate'
end
diff --git a/spec/tasks/gitlab/packages/migrate_rake_spec.rb b/spec/tasks/gitlab/packages/migrate_rake_spec.rb
new file mode 100644
index 00000000000..0a296eb0808
--- /dev/null
+++ b/spec/tasks/gitlab/packages/migrate_rake_spec.rb
@@ -0,0 +1,39 @@
+# frozen_string_literal: true
+
+require 'rake_helper'
+
+RSpec.describe 'gitlab:packages namespace rake task' do
+ before :all do
+ Rake.application.rake_require 'tasks/gitlab/packages/migrate'
+ end
+
+ describe 'migrate' do
+ let(:local) { ObjectStorage::Store::LOCAL }
+ let(:remote) { ObjectStorage::Store::REMOTE }
+ let!(:package_file) { create(:package_file, :pom, file_store: local) }
+
+ def packages_migrate
+ run_rake_task('gitlab:packages:migrate')
+ end
+
+ context 'object storage disabled' do
+ before do
+ stub_packages_object_storage(enabled: false)
+ end
+
+ it "doesn't migrate files" do
+ expect { packages_migrate }.to raise_error('Object store is disabled for packages feature')
+ end
+ end
+
+ context 'object storage enabled' do
+ before do
+ stub_packages_object_storage
+ end
+
+ it 'migrates local file to object storage' do
+ expect { packages_migrate }.to change { package_file.reload.file_store }.from(local).to(remote)
+ end
+ end
+ end
+end
diff --git a/spec/tasks/gitlab/praefect_rake_spec.rb b/spec/tasks/gitlab/praefect_rake_spec.rb
index d986a778c8c..c67dba110c7 100644
--- a/spec/tasks/gitlab/praefect_rake_spec.rb
+++ b/spec/tasks/gitlab/praefect_rake_spec.rb
@@ -2,7 +2,7 @@
require 'rake_helper'
-describe 'gitlab:praefect:replicas' do
+RSpec.describe 'gitlab:praefect:replicas' do
before do
Rake.application.rake_require 'tasks/gitlab/praefect'
end
diff --git a/spec/tasks/gitlab/seed/group_seed_rake_spec.rb b/spec/tasks/gitlab/seed/group_seed_rake_spec.rb
index ecf4e9575ab..0b69615eebc 100644
--- a/spec/tasks/gitlab/seed/group_seed_rake_spec.rb
+++ b/spec/tasks/gitlab/seed/group_seed_rake_spec.rb
@@ -2,7 +2,7 @@
require 'rake_helper'
-describe 'gitlab:seed:group_seed rake task' do
+RSpec.describe 'gitlab:seed:group_seed rake task' do
let(:username) { 'group_seed' }
let!(:user) { create(:user, username: username) }
let(:task_params) { [2, username] }
diff --git a/spec/tasks/gitlab/shell_rake_spec.rb b/spec/tasks/gitlab/shell_rake_spec.rb
index 08b3fea0c80..a929daddb67 100644
--- a/spec/tasks/gitlab/shell_rake_spec.rb
+++ b/spec/tasks/gitlab/shell_rake_spec.rb
@@ -2,7 +2,7 @@
require 'rake_helper'
-describe 'gitlab:shell rake tasks' do
+RSpec.describe 'gitlab:shell rake tasks' do
before do
Rake.application.rake_require 'tasks/gitlab/shell'
diff --git a/spec/tasks/gitlab/snippets_rake_spec.rb b/spec/tasks/gitlab/snippets_rake_spec.rb
index c4bb8d7897c..f21922e14b8 100644
--- a/spec/tasks/gitlab/snippets_rake_spec.rb
+++ b/spec/tasks/gitlab/snippets_rake_spec.rb
@@ -2,7 +2,7 @@
require 'rake_helper'
-describe 'gitlab:snippets namespace rake task' do
+RSpec.describe 'gitlab:snippets namespace rake task' do
let_it_be(:user) { create(:user)}
let_it_be(:migrated) { create(:personal_snippet, :repository, author: user) }
let(:non_migrated) { create_list(:personal_snippet, 3, author: user) }
diff --git a/spec/tasks/gitlab/storage_rake_spec.rb b/spec/tasks/gitlab/storage_rake_spec.rb
index 226d3aaab4e..abd44adbdcc 100644
--- a/spec/tasks/gitlab/storage_rake_spec.rb
+++ b/spec/tasks/gitlab/storage_rake_spec.rb
@@ -2,7 +2,7 @@
require 'rake_helper'
-describe 'rake gitlab:storage:*' do
+RSpec.describe 'rake gitlab:storage:*' do
before do
Rake.application.rake_require 'tasks/gitlab/storage'
diff --git a/spec/tasks/gitlab/task_helpers_spec.rb b/spec/tasks/gitlab/task_helpers_spec.rb
index 8e6872f4d6f..2921913319b 100644
--- a/spec/tasks/gitlab/task_helpers_spec.rb
+++ b/spec/tasks/gitlab/task_helpers_spec.rb
@@ -6,7 +6,7 @@ class TestHelpersTest
include Gitlab::TaskHelpers
end
-describe Gitlab::TaskHelpers do
+RSpec.describe Gitlab::TaskHelpers do
subject { TestHelpersTest.new }
let(:repo) { 'https://gitlab.com/gitlab-org/gitlab-test.git' }
diff --git a/spec/tasks/gitlab/update_templates_rake_spec.rb b/spec/tasks/gitlab/update_templates_rake_spec.rb
index 14b4ad5e3d8..25151a2f3ae 100644
--- a/spec/tasks/gitlab/update_templates_rake_spec.rb
+++ b/spec/tasks/gitlab/update_templates_rake_spec.rb
@@ -2,7 +2,7 @@
require 'rake_helper'
-describe 'gitlab:update_project_templates rake task' do
+RSpec.describe 'gitlab:update_project_templates rake task' do
let!(:tmpdir) { Dir.mktmpdir }
before do
diff --git a/spec/tasks/gitlab/uploads/check_rake_spec.rb b/spec/tasks/gitlab/uploads/check_rake_spec.rb
index 91f0cedb246..4c6f16dbcf9 100644
--- a/spec/tasks/gitlab/uploads/check_rake_spec.rb
+++ b/spec/tasks/gitlab/uploads/check_rake_spec.rb
@@ -2,7 +2,7 @@
require 'rake_helper'
-describe 'gitlab:uploads rake tasks' do
+RSpec.describe 'gitlab:uploads rake tasks' do
describe 'check' do
let!(:upload) { create(:upload, path: Rails.root.join('spec/fixtures/banana_sample.gif')) }
diff --git a/spec/tasks/gitlab/uploads/migrate_rake_spec.rb b/spec/tasks/gitlab/uploads/migrate_rake_spec.rb
index 49026cd74f9..7f0f5c6767d 100644
--- a/spec/tasks/gitlab/uploads/migrate_rake_spec.rb
+++ b/spec/tasks/gitlab/uploads/migrate_rake_spec.rb
@@ -2,7 +2,7 @@
require 'rake_helper'
-describe 'gitlab:uploads:migrate and migrate_to_local rake tasks' do
+RSpec.describe 'gitlab:uploads:migrate and migrate_to_local rake tasks' do
let(:model_class) { nil }
let(:uploader_class) { nil }
let(:mounted_as) { nil }
diff --git a/spec/tasks/gitlab/web_hook_rake_spec.rb b/spec/tasks/gitlab/web_hook_rake_spec.rb
index ee3c7e034f3..3e18ce5ab29 100644
--- a/spec/tasks/gitlab/web_hook_rake_spec.rb
+++ b/spec/tasks/gitlab/web_hook_rake_spec.rb
@@ -2,7 +2,7 @@
require 'rake_helper'
-describe 'gitlab:web_hook namespace rake tasks' do
+RSpec.describe 'gitlab:web_hook namespace rake tasks' do
let_it_be(:group, refind: true) { create(:group) }
let_it_be(:project1, reload: true) { create(:project, namespace: group) }
let_it_be(:project2, reload: true) { create(:project, namespace: group) }
diff --git a/spec/tasks/gitlab/workhorse_rake_spec.rb b/spec/tasks/gitlab/workhorse_rake_spec.rb
index 139652ac258..2efbf0febac 100644
--- a/spec/tasks/gitlab/workhorse_rake_spec.rb
+++ b/spec/tasks/gitlab/workhorse_rake_spec.rb
@@ -2,7 +2,7 @@
require 'rake_helper'
-describe 'gitlab:workhorse namespace rake task' do
+RSpec.describe 'gitlab:workhorse namespace rake task' do
before :all do
Rake.application.rake_require 'tasks/gitlab/workhorse'
end
diff --git a/spec/tasks/gitlab/x509/update_rake_spec.rb b/spec/tasks/gitlab/x509/update_rake_spec.rb
index 8c62c6c1728..93e97ab38ad 100644
--- a/spec/tasks/gitlab/x509/update_rake_spec.rb
+++ b/spec/tasks/gitlab/x509/update_rake_spec.rb
@@ -2,7 +2,7 @@
require 'rake_helper'
-describe 'gitlab:x509 namespace rake task' do
+RSpec.describe 'gitlab:x509 namespace rake task' do
before :all do
Rake.application.rake_require 'tasks/gitlab/x509/update'
end
diff --git a/spec/tasks/migrate/schema_check_rake_spec.rb b/spec/tasks/migrate/schema_check_rake_spec.rb
index 1097a43cd8a..aebb3a6551f 100644
--- a/spec/tasks/migrate/schema_check_rake_spec.rb
+++ b/spec/tasks/migrate/schema_check_rake_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require 'rake'
-describe 'schema_version_check rake task' do
+RSpec.describe 'schema_version_check rake task' do
include StubENV
before :all do
diff --git a/spec/tasks/tokens_spec.rb b/spec/tasks/tokens_spec.rb
index 9c69155056a..13fcd37b426 100644
--- a/spec/tasks/tokens_spec.rb
+++ b/spec/tasks/tokens_spec.rb
@@ -2,7 +2,7 @@
require 'rake_helper'
-describe 'tokens rake tasks' do
+RSpec.describe 'tokens rake tasks' do
let!(:user) { create(:user) }
before do
diff --git a/spec/tooling/lib/tooling/helm3_client_spec.rb b/spec/tooling/lib/tooling/helm3_client_spec.rb
new file mode 100644
index 00000000000..f12bae051f0
--- /dev/null
+++ b/spec/tooling/lib/tooling/helm3_client_spec.rb
@@ -0,0 +1,133 @@
+# frozen_string_literal: true
+
+require_relative '../../../../tooling/lib/tooling/helm3_client'
+
+RSpec.describe Tooling::Helm3Client do
+ let(:namespace) { 'review-apps' }
+ let(:release_name) { 'my-release' }
+ let(:raw_helm_list_page1) do
+ <<~OUTPUT
+ [
+ {"name":"review-qa-60-reor-1mugd1","namespace":"#{namespace}","revision":1,"updated":"2020-04-03 17:27:10.245952 +0800 +08","status":"failed","chart":"gitlab-1.1.3","app_version":"12.9.2"},
+ {"name":"review-7846-fix-s-261vd6","namespace":"#{namespace}","revision":2,"updated":"2020-04-02 17:27:12.245952 +0800 +08","status":"deployed","chart":"gitlab-1.1.3","app_version":"12.9.2"},
+ {"name":"review-7867-snowp-lzo3iy","namespace":"#{namespace}","revision":1,"updated":"2020-04-02 15:27:12.245952 +0800 +08","status":"deployed","chart":"gitlab-1.1.3","app_version":"12.9.1"},
+ {"name":"review-6709-group-2pzeec","namespace":"#{namespace}","revision":2,"updated":"2020-04-01 21:27:12.245952 +0800 +08","status":"failed","chart":"gitlab-1.1.3","app_version":"12.9.1"}
+ ]
+ OUTPUT
+ end
+ let(:raw_helm_list_page2) do
+ <<~OUTPUT
+ [
+ {"name":"review-6709-group-t40qbv","namespace":"#{namespace}","revision":2,"updated":"2020-04-01 11:27:12.245952 +0800 +08","status":"deployed","chart":"gitlab-1.1.3","app_version":"12.9.1"}
+ ]
+ OUTPUT
+ end
+ let(:raw_helm_list_empty) do
+ <<~OUTPUT
+ []
+ OUTPUT
+ end
+
+ subject { described_class.new(namespace: namespace) }
+
+ describe '#releases' do
+ it 'raises an error if the Helm command fails' do
+ expect(Gitlab::Popen).to receive(:popen_with_detail)
+ .with([%(helm list --namespace "#{namespace}" --max 256 --offset 0 --output json)])
+ .and_return(Gitlab::Popen::Result.new([], '', '', double(success?: false)))
+
+ expect { subject.releases.to_a }.to raise_error(described_class::CommandFailedError)
+ end
+
+ it 'calls helm list with default arguments' do
+ expect(Gitlab::Popen).to receive(:popen_with_detail)
+ .with([%(helm list --namespace "#{namespace}" --max 256 --offset 0 --output json)])
+ .and_return(Gitlab::Popen::Result.new([], '', '', double(success?: true)))
+
+ subject.releases.to_a
+ end
+
+ it 'calls helm list with extra arguments' do
+ expect(Gitlab::Popen).to receive(:popen_with_detail)
+ .with([%(helm list --namespace "#{namespace}" --max 256 --offset 0 --output json --deployed)])
+ .and_return(Gitlab::Popen::Result.new([], '', '', double(success?: true)))
+
+ subject.releases(args: ['--deployed']).to_a
+ end
+
+ it 'returns a list of Release objects' do
+ expect(Gitlab::Popen).to receive(:popen_with_detail)
+ .with([%(helm list --namespace "#{namespace}" --max 256 --offset 0 --output json --deployed)])
+ .and_return(Gitlab::Popen::Result.new([], raw_helm_list_page2, '', double(success?: true)))
+ expect(Gitlab::Popen).to receive(:popen_with_detail).ordered
+ .and_return(Gitlab::Popen::Result.new([], raw_helm_list_empty, '', double(success?: true)))
+
+ releases = subject.releases(args: ['--deployed']).to_a
+
+ expect(releases.size).to eq(1)
+ expect(releases[0]).to have_attributes(
+ name: 'review-6709-group-t40qbv',
+ revision: 2,
+ last_update: Time.parse('2020-04-01 11:27:12.245952 +0800 +08'),
+ status: 'deployed',
+ chart: 'gitlab-1.1.3',
+ app_version: '12.9.1',
+ namespace: namespace
+ )
+ end
+
+ it 'automatically paginates releases' do
+ expect(Gitlab::Popen).to receive(:popen_with_detail).ordered
+ .with([%(helm list --namespace "#{namespace}" --max 256 --offset 0 --output json)])
+ .and_return(Gitlab::Popen::Result.new([], raw_helm_list_page1, '', double(success?: true)))
+ expect(Gitlab::Popen).to receive(:popen_with_detail).ordered
+ .with([%(helm list --namespace "#{namespace}" --max 256 --offset 256 --output json)])
+ .and_return(Gitlab::Popen::Result.new([], raw_helm_list_page2, '', double(success?: true)))
+ expect(Gitlab::Popen).to receive(:popen_with_detail).ordered
+ .with([%(helm list --namespace "#{namespace}" --max 256 --offset 512 --output json)])
+ .and_return(Gitlab::Popen::Result.new([], raw_helm_list_empty, '', double(success?: true)))
+ releases = subject.releases.to_a
+
+ expect(releases.size).to eq(5)
+ expect(releases.last.name).to eq('review-6709-group-t40qbv')
+ end
+ end
+
+ describe '#delete' do
+ it 'raises an error if the Helm command fails' do
+ expect(Gitlab::Popen).to receive(:popen_with_detail)
+ .with([%(helm uninstall --namespace "#{namespace}" #{release_name})])
+ .and_return(Gitlab::Popen::Result.new([], '', '', double(success?: false)))
+
+ expect { subject.delete(release_name: release_name) }.to raise_error(described_class::CommandFailedError)
+ end
+
+ it 'calls helm uninstall with default arguments' do
+ expect(Gitlab::Popen).to receive(:popen_with_detail)
+ .with([%(helm uninstall --namespace "#{namespace}" #{release_name})])
+ .and_return(Gitlab::Popen::Result.new([], '', '', double(success?: true)))
+
+ expect(subject.delete(release_name: release_name)).to eq('')
+ end
+
+ context 'with multiple release names' do
+ let(:release_name) { %w[my-release my-release-2] }
+
+ it 'raises an error if the Helm command fails' do
+ expect(Gitlab::Popen).to receive(:popen_with_detail)
+ .with([%(helm uninstall --namespace "#{namespace}" #{release_name.join(' ')})])
+ .and_return(Gitlab::Popen::Result.new([], '', '', double(success?: false)))
+
+ expect { subject.delete(release_name: release_name) }.to raise_error(described_class::CommandFailedError)
+ end
+
+ it 'calls helm uninstall with multiple release names' do
+ expect(Gitlab::Popen).to receive(:popen_with_detail)
+ .with([%(helm uninstall --namespace "#{namespace}" #{release_name.join(' ')})])
+ .and_return(Gitlab::Popen::Result.new([], '', '', double(success?: true)))
+
+ expect(subject.delete(release_name: release_name)).to eq('')
+ end
+ end
+ end
+end
diff --git a/spec/tooling/lib/tooling/kubernetes_client_spec.rb b/spec/tooling/lib/tooling/kubernetes_client_spec.rb
new file mode 100644
index 00000000000..fdd56aa0189
--- /dev/null
+++ b/spec/tooling/lib/tooling/kubernetes_client_spec.rb
@@ -0,0 +1,111 @@
+# frozen_string_literal: true
+
+require_relative '../../../../tooling/lib/tooling/kubernetes_client'
+
+RSpec.describe Tooling::KubernetesClient do
+ let(:namespace) { 'review-apps' }
+ let(:release_name) { 'my-release' }
+ let(:pod_for_release) { "pod-my-release-abcd" }
+ let(:raw_resource_names_str) { "NAME\nfoo\n#{pod_for_release}\nbar" }
+ let(:raw_resource_names) { raw_resource_names_str.lines.map(&:strip) }
+
+ subject { described_class.new(namespace: namespace) }
+
+ describe 'RESOURCE_LIST' do
+ it 'returns the correct list of resources separated by commas' do
+ expect(described_class::RESOURCE_LIST).to eq('ingress,svc,pdb,hpa,deploy,statefulset,job,pod,secret,configmap,pvc,secret,clusterrole,clusterrolebinding,role,rolebinding,sa,crd')
+ end
+ end
+
+ describe '#cleanup' do
+ before do
+ allow(subject).to receive(:raw_resource_names).and_return(raw_resource_names)
+ end
+
+ it 'raises an error if the Kubernetes command fails' do
+ expect(Gitlab::Popen).to receive(:popen_with_detail)
+ .with(["kubectl delete #{described_class::RESOURCE_LIST} " +
+ %(--namespace "#{namespace}" --now --ignore-not-found --include-uninitialized --wait=true -l release="#{release_name}")])
+ .and_return(Gitlab::Popen::Result.new([], '', '', double(success?: false)))
+
+ expect { subject.cleanup(release_name: release_name) }.to raise_error(described_class::CommandFailedError)
+ end
+
+ it 'calls kubectl with the correct arguments' do
+ expect(Gitlab::Popen).to receive(:popen_with_detail)
+ .with(["kubectl delete #{described_class::RESOURCE_LIST} " +
+ %(--namespace "#{namespace}" --now --ignore-not-found --include-uninitialized --wait=true -l release="#{release_name}")])
+ .and_return(Gitlab::Popen::Result.new([], '', '', double(success?: true)))
+
+ expect(Gitlab::Popen).to receive(:popen_with_detail)
+ .with([%(kubectl delete --namespace "#{namespace}" --ignore-not-found #{pod_for_release})])
+ .and_return(Gitlab::Popen::Result.new([], '', '', double(success?: true)))
+
+ # We're not verifying the output here, just silencing it
+ expect { subject.cleanup(release_name: release_name) }.to output.to_stdout
+ end
+
+ context 'with multiple releases' do
+ let(:release_name) { %w[my-release my-release-2] }
+
+ it 'raises an error if the Kubernetes command fails' do
+ expect(Gitlab::Popen).to receive(:popen_with_detail)
+ .with(["kubectl delete #{described_class::RESOURCE_LIST} " +
+ %(--namespace "#{namespace}" --now --ignore-not-found --include-uninitialized --wait=true -l 'release in (#{release_name.join(', ')})')])
+ .and_return(Gitlab::Popen::Result.new([], '', '', double(success?: false)))
+
+ expect { subject.cleanup(release_name: release_name) }.to raise_error(described_class::CommandFailedError)
+ end
+
+ it 'calls kubectl with the correct arguments' do
+ expect(Gitlab::Popen).to receive(:popen_with_detail)
+ .with(["kubectl delete #{described_class::RESOURCE_LIST} " +
+ %(--namespace "#{namespace}" --now --ignore-not-found --include-uninitialized --wait=true -l 'release in (#{release_name.join(', ')})')])
+ .and_return(Gitlab::Popen::Result.new([], '', '', double(success?: true)))
+
+ expect(Gitlab::Popen).to receive(:popen_with_detail)
+ .with([%(kubectl delete --namespace "#{namespace}" --ignore-not-found #{pod_for_release})])
+ .and_return(Gitlab::Popen::Result.new([], '', '', double(success?: true)))
+
+ # We're not verifying the output here, just silencing it
+ expect { subject.cleanup(release_name: release_name) }.to output.to_stdout
+ end
+ end
+
+ context 'with `wait: false`' do
+ it 'raises an error if the Kubernetes command fails' do
+ expect(Gitlab::Popen).to receive(:popen_with_detail)
+ .with(["kubectl delete #{described_class::RESOURCE_LIST} " +
+ %(--namespace "#{namespace}" --now --ignore-not-found --include-uninitialized --wait=false -l release="#{release_name}")])
+ .and_return(Gitlab::Popen::Result.new([], '', '', double(success?: false)))
+
+ expect { subject.cleanup(release_name: release_name, wait: false) }.to raise_error(described_class::CommandFailedError)
+ end
+
+ it 'calls kubectl with the correct arguments' do
+ expect(Gitlab::Popen).to receive(:popen_with_detail)
+ .with(["kubectl delete #{described_class::RESOURCE_LIST} " +
+ %(--namespace "#{namespace}" --now --ignore-not-found --include-uninitialized --wait=false -l release="#{release_name}")])
+ .and_return(Gitlab::Popen::Result.new([], '', '', double(success?: true)))
+
+ expect(Gitlab::Popen).to receive(:popen_with_detail)
+ .with([%(kubectl delete --namespace "#{namespace}" --ignore-not-found #{pod_for_release})])
+ .and_return(Gitlab::Popen::Result.new([], '', '', double(success?: true)))
+
+ # We're not verifying the output here, just silencing it
+ expect { subject.cleanup(release_name: release_name, wait: false) }.to output.to_stdout
+ end
+ end
+ end
+
+ describe '#raw_resource_names' do
+ it 'calls kubectl to retrieve the resource names' do
+ expect(Gitlab::Popen).to receive(:popen_with_detail)
+ .with(["kubectl get #{described_class::RESOURCE_LIST} " +
+ %(--namespace "#{namespace}" -o name)])
+ .and_return(Gitlab::Popen::Result.new([], raw_resource_names_str, '', double(success?: true)))
+
+ expect(subject.__send__(:raw_resource_names)).to eq(raw_resource_names)
+ end
+ end
+end
diff --git a/spec/tooling/lib/tooling/test_file_finder_spec.rb b/spec/tooling/lib/tooling/test_file_finder_spec.rb
index 9c33f20877b..64b55b9b1d6 100644
--- a/spec/tooling/lib/tooling/test_file_finder_spec.rb
+++ b/spec/tooling/lib/tooling/test_file_finder_spec.rb
@@ -3,7 +3,7 @@
require_relative '../../../../tooling/lib/tooling/test_file_finder'
RSpec.describe Tooling::TestFileFinder do
- subject { Tooling::TestFileFinder.new(file) }
+ subject { described_class.new(file) }
describe '#test_files' do
context 'when given non .rb files' do
@@ -88,6 +88,78 @@ RSpec.describe Tooling::TestFileFinder do
end
end
+ context 'when given a factory file' do
+ let(:file) { 'spec/factories/users.rb' }
+
+ it 'returns spec/factories_spec.rb file' do
+ expect(subject.test_files).to contain_exactly('spec/factories_spec.rb')
+ end
+ end
+
+ context 'when given an ee factory file' do
+ let(:file) { 'ee/spec/factories/users.rb' }
+
+ it 'returns spec/factories_spec.rb file' do
+ expect(subject.test_files).to contain_exactly('spec/factories_spec.rb')
+ end
+ end
+
+ context 'when given db/structure.sql' do
+ let(:file) { 'db/structure.sql' }
+
+ it 'returns spec/db/schema_spec.rb' do
+ expect(subject.test_files).to contain_exactly('spec/db/schema_spec.rb')
+ end
+ end
+
+ context 'when given an initializer' do
+ let(:file) { 'config/initializers/action_mailer_hooks.rb' }
+
+ it 'returns the matching initializer spec' do
+ expect(subject.test_files).to contain_exactly('spec/initializers/action_mailer_hooks_spec.rb')
+ end
+ end
+
+ context 'when given a haml view' do
+ let(:file) { 'app/views/admin/users/_user.html.haml' }
+
+ it 'returns the matching view spec' do
+ expect(subject.test_files).to contain_exactly('spec/views/admin/users/_user.html.haml_spec.rb')
+ end
+ end
+
+ context 'when given a haml view in ee/' do
+ let(:file) { 'ee/app/views/admin/users/_user.html.haml' }
+
+ it 'returns the matching view spec' do
+ expect(subject.test_files).to contain_exactly('ee/spec/views/admin/users/_user.html.haml_spec.rb')
+ end
+ end
+
+ context 'when given a migration file' do
+ let(:file) { 'db/migrate/20191023152913_add_default_and_free_plans.rb' }
+
+ it 'returns the matching migration spec' do
+ test_files = %w[
+ spec/migrations/add_default_and_free_plans_spec.rb
+ spec/migrations/20191023152913_add_default_and_free_plans_spec.rb
+ ]
+ expect(subject.test_files).to contain_exactly(*test_files)
+ end
+ end
+
+ context 'when given a post-migration file' do
+ let(:file) { 'db/post_migrate/20200608072931_backfill_imported_snippet_repositories.rb' }
+
+ it 'returns the matching migration spec' do
+ test_files = %w[
+ spec/migrations/backfill_imported_snippet_repositories_spec.rb
+ spec/migrations/20200608072931_backfill_imported_snippet_repositories_spec.rb
+ ]
+ expect(subject.test_files).to contain_exactly(*test_files)
+ end
+ end
+
context 'with foss_test_only: true' do
subject { Tooling::TestFileFinder.new(file, foss_test_only: true) }
diff --git a/spec/uploaders/attachment_uploader_spec.rb b/spec/uploaders/attachment_uploader_spec.rb
index d5a92b9b317..05cffff1f1a 100644
--- a/spec/uploaders/attachment_uploader_spec.rb
+++ b/spec/uploaders/attachment_uploader_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe AttachmentUploader do
+RSpec.describe AttachmentUploader do
let(:note) { create(:note, :with_attachment) }
let(:uploader) { note.attachment }
let(:upload) { create(:upload, :attachment_upload, model: uploader.model) }
diff --git a/spec/uploaders/avatar_uploader_spec.rb b/spec/uploaders/avatar_uploader_spec.rb
index 142ee557afa..1fadd9425ef 100644
--- a/spec/uploaders/avatar_uploader_spec.rb
+++ b/spec/uploaders/avatar_uploader_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe AvatarUploader do
+RSpec.describe AvatarUploader do
let(:model) { build_stubbed(:user) }
let(:uploader) { described_class.new(model, :avatar) }
let(:upload) { create(:upload, model: model) }
diff --git a/spec/uploaders/content_type_whitelist_spec.rb b/spec/uploaders/content_type_whitelist_spec.rb
index 32d030cdfee..cf7463369ab 100644
--- a/spec/uploaders/content_type_whitelist_spec.rb
+++ b/spec/uploaders/content_type_whitelist_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ContentTypeWhitelist do
+RSpec.describe ContentTypeWhitelist do
let_it_be(:model) { build_stubbed(:user) }
let!(:uploader) do
stub_const('DummyUploader', Class.new(CarrierWave::Uploader::Base))
diff --git a/spec/uploaders/design_management/design_v432x230_uploader_spec.rb b/spec/uploaders/design_management/design_v432x230_uploader_spec.rb
index 8c62b6ad6a8..b3a106ef94b 100644
--- a/spec/uploaders/design_management/design_v432x230_uploader_spec.rb
+++ b/spec/uploaders/design_management/design_v432x230_uploader_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe DesignManagement::DesignV432x230Uploader do
+RSpec.describe DesignManagement::DesignV432x230Uploader do
include CarrierWave::Test::Matchers
let(:model) { create(:design_action, :with_image_v432x230) }
diff --git a/spec/uploaders/external_diff_uploader_spec.rb b/spec/uploaders/external_diff_uploader_spec.rb
index ee11085d54e..ee23c1e36b7 100644
--- a/spec/uploaders/external_diff_uploader_spec.rb
+++ b/spec/uploaders/external_diff_uploader_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ExternalDiffUploader do
+RSpec.describe ExternalDiffUploader do
let(:diff) { create(:merge_request).merge_request_diff }
let(:path) { Gitlab.config.external_diffs.storage_path }
diff --git a/spec/uploaders/favicon_uploader_spec.rb b/spec/uploaders/favicon_uploader_spec.rb
index 0f5941b3f0a..6bff3ff8a14 100644
--- a/spec/uploaders/favicon_uploader_spec.rb
+++ b/spec/uploaders/favicon_uploader_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe FaviconUploader do
+RSpec.describe FaviconUploader do
let_it_be(:model) { build_stubbed(:user) }
let_it_be(:uploader) { described_class.new(model, :favicon) }
diff --git a/spec/uploaders/file_mover_spec.rb b/spec/uploaders/file_mover_spec.rb
index 474515b537c..0f7496f17d5 100644
--- a/spec/uploaders/file_mover_spec.rb
+++ b/spec/uploaders/file_mover_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe FileMover do
+RSpec.describe FileMover do
include FileMoverHelpers
let(:user) { create(:user) }
diff --git a/spec/uploaders/file_uploader_spec.rb b/spec/uploaders/file_uploader_spec.rb
index 629c84778b9..9f1d276d092 100644
--- a/spec/uploaders/file_uploader_spec.rb
+++ b/spec/uploaders/file_uploader_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe FileUploader do
+RSpec.describe FileUploader do
let(:group) { create(:group, name: 'awesome') }
let(:project) { create(:project, :legacy_storage, namespace: group, name: 'project') }
let(:uploader) { described_class.new(project, :avatar) }
diff --git a/spec/uploaders/gitlab_uploader_spec.rb b/spec/uploaders/gitlab_uploader_spec.rb
index 80efdb88585..72845b47a53 100644
--- a/spec/uploaders/gitlab_uploader_spec.rb
+++ b/spec/uploaders/gitlab_uploader_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require 'carrierwave/storage/fog'
-describe GitlabUploader do
+RSpec.describe GitlabUploader do
let(:uploader_class) { Class.new(described_class) }
subject { uploader_class.new(double) }
diff --git a/spec/uploaders/import_export_uploader_spec.rb b/spec/uploaders/import_export_uploader_spec.rb
index 33cab911f86..b1fdcf067c6 100644
--- a/spec/uploaders/import_export_uploader_spec.rb
+++ b/spec/uploaders/import_export_uploader_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ImportExportUploader do
+RSpec.describe ImportExportUploader do
let(:model) { build_stubbed(:import_export_upload) }
let(:upload) { create(:upload, model: model) }
let(:import_export_upload) { ImportExportUpload.new }
diff --git a/spec/uploaders/job_artifact_uploader_spec.rb b/spec/uploaders/job_artifact_uploader_spec.rb
index a03cf3b9dea..9ccf216d1fc 100644
--- a/spec/uploaders/job_artifact_uploader_spec.rb
+++ b/spec/uploaders/job_artifact_uploader_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe JobArtifactUploader do
+RSpec.describe JobArtifactUploader do
let(:store) { described_class::Store::LOCAL }
let(:job_artifact) { create(:ci_job_artifact, file_store: store) }
let(:uploader) { described_class.new(job_artifact, :file) }
diff --git a/spec/uploaders/lfs_object_uploader_spec.rb b/spec/uploaders/lfs_object_uploader_spec.rb
index 1041e13d34f..d1a3fb243ac 100644
--- a/spec/uploaders/lfs_object_uploader_spec.rb
+++ b/spec/uploaders/lfs_object_uploader_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe LfsObjectUploader do
+RSpec.describe LfsObjectUploader do
let(:lfs_object) { create(:lfs_object, :with_file) }
let(:uploader) { described_class.new(lfs_object, :file) }
let(:path) { Gitlab.config.lfs.storage_path }
diff --git a/spec/uploaders/namespace_file_uploader_spec.rb b/spec/uploaders/namespace_file_uploader_spec.rb
index bc8d6a33e85..99bf4e130f2 100644
--- a/spec/uploaders/namespace_file_uploader_spec.rb
+++ b/spec/uploaders/namespace_file_uploader_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe NamespaceFileUploader do
+RSpec.describe NamespaceFileUploader do
let(:group) { build_stubbed(:group) }
let(:uploader) { described_class.new(group) }
let(:upload) { create(:upload, :namespace_upload, model: group) }
diff --git a/spec/uploaders/object_storage_spec.rb b/spec/uploaders/object_storage_spec.rb
index f42d581ece4..694aafe5ed5 100644
--- a/spec/uploaders/object_storage_spec.rb
+++ b/spec/uploaders/object_storage_spec.rb
@@ -18,7 +18,7 @@ class Implementation < GitlabUploader
end
end
-describe ObjectStorage do
+RSpec.describe ObjectStorage do
let(:uploader_class) { Implementation }
let(:object) { build_stubbed(:user) }
let(:uploader) { uploader_class.new(object, :file) }
diff --git a/spec/uploaders/packages/package_file_uploader_spec.rb b/spec/uploaders/packages/package_file_uploader_spec.rb
new file mode 100644
index 00000000000..1fe65649d7a
--- /dev/null
+++ b/spec/uploaders/packages/package_file_uploader_spec.rb
@@ -0,0 +1,45 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+RSpec.describe Packages::PackageFileUploader do
+ let(:package_file) { create(:package_file, :xml) }
+ let(:uploader) { described_class.new(package_file, :file) }
+ let(:path) { Gitlab.config.packages.storage_path }
+
+ subject { uploader }
+
+ it_behaves_like "builds correct paths",
+ store_dir: %r[\h{2}/\h{2}],
+ cache_dir: %r[/packages/tmp/cache],
+ work_dir: %r[/packages/tmp/work]
+
+ context 'object store is remote' do
+ before do
+ stub_packages_object_storage
+ end
+
+ include_context 'with storage', described_class::Store::REMOTE
+
+ it_behaves_like "builds correct paths",
+ store_dir: %r[\h{2}/\h{2}]
+ end
+
+ describe 'remote file' do
+ let(:package_file) { create(:package_file, :object_storage, :xml) }
+
+ context 'with object storage enabled' do
+ before do
+ stub_packages_object_storage
+ end
+
+ it 'can store file remotely' do
+ allow(ObjectStorage::BackgroundMoveWorker).to receive(:perform_async)
+
+ package_file
+
+ expect(package_file.file_store).to eq(described_class::Store::REMOTE)
+ expect(package_file.file.path).not_to be_blank
+ end
+ end
+ end
+end
diff --git a/spec/uploaders/personal_file_uploader_spec.rb b/spec/uploaders/personal_file_uploader_spec.rb
index c211ec3607c..d2eae5d7a54 100644
--- a/spec/uploaders/personal_file_uploader_spec.rb
+++ b/spec/uploaders/personal_file_uploader_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe PersonalFileUploader do
+RSpec.describe PersonalFileUploader do
let(:model) { create(:personal_snippet) }
let(:uploader) { described_class.new(model) }
let(:upload) { create(:upload, :personal_snippet_upload) }
diff --git a/spec/uploaders/records_uploads_spec.rb b/spec/uploaders/records_uploads_spec.rb
index 1a3c416c74a..c1f5f962d77 100644
--- a/spec/uploaders/records_uploads_spec.rb
+++ b/spec/uploaders/records_uploads_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe RecordsUploads do
+RSpec.describe RecordsUploads do
let!(:uploader) do
stub_const('RecordsUploadsExampleUploader', Class.new(GitlabUploader))
diff --git a/spec/uploaders/terraform/state_uploader_spec.rb b/spec/uploaders/terraform/state_uploader_spec.rb
index cbcb6298eca..dadfdf6e93f 100644
--- a/spec/uploaders/terraform/state_uploader_spec.rb
+++ b/spec/uploaders/terraform/state_uploader_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Terraform::StateUploader do
+RSpec.describe Terraform::StateUploader do
subject { terraform_state.file }
let(:terraform_state) { create(:terraform_state, :with_file) }
diff --git a/spec/uploaders/uploader_helper_spec.rb b/spec/uploaders/uploader_helper_spec.rb
index 7bc6caf8224..e62e9b3c14b 100644
--- a/spec/uploaders/uploader_helper_spec.rb
+++ b/spec/uploaders/uploader_helper_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe UploaderHelper do
+RSpec.describe UploaderHelper do
let(:uploader) do
example_uploader = Class.new(CarrierWave::Uploader::Base) do
include UploaderHelper
diff --git a/spec/uploaders/workers/object_storage/background_move_worker_spec.rb b/spec/uploaders/workers/object_storage/background_move_worker_spec.rb
index 38b70d33993..a481939ed7a 100644
--- a/spec/uploaders/workers/object_storage/background_move_worker_spec.rb
+++ b/spec/uploaders/workers/object_storage/background_move_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ObjectStorage::BackgroundMoveWorker do
+RSpec.describe ObjectStorage::BackgroundMoveWorker do
let(:local) { ObjectStorage::Store::LOCAL }
let(:remote) { ObjectStorage::Store::REMOTE }
diff --git a/spec/uploaders/workers/object_storage/migrate_uploads_worker_spec.rb b/spec/uploaders/workers/object_storage/migrate_uploads_worker_spec.rb
index 7bf8512a6fd..ef5459ce788 100644
--- a/spec/uploaders/workers/object_storage/migrate_uploads_worker_spec.rb
+++ b/spec/uploaders/workers/object_storage/migrate_uploads_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ObjectStorage::MigrateUploadsWorker do
+RSpec.describe ObjectStorage::MigrateUploadsWorker do
let(:model_class) { Project }
let(:uploads) { Upload.all }
let(:to_store) { ObjectStorage::Store::REMOTE }
diff --git a/spec/validators/addressable_url_validator_spec.rb b/spec/validators/addressable_url_validator_spec.rb
index 46b1bebb074..394ffc7bbea 100644
--- a/spec/validators/addressable_url_validator_spec.rb
+++ b/spec/validators/addressable_url_validator_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe AddressableUrlValidator do
+RSpec.describe AddressableUrlValidator do
let!(:badge) { build(:badge, link_url: 'http://www.example.com') }
let(:validator) { described_class.new(validator_options.reverse_merge(attributes: [:link_url])) }
diff --git a/spec/validators/array_members_validator_spec.rb b/spec/validators/array_members_validator_spec.rb
new file mode 100644
index 00000000000..ff8f0da7651
--- /dev/null
+++ b/spec/validators/array_members_validator_spec.rb
@@ -0,0 +1,69 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe ArrayMembersValidator do
+ using RSpec::Parameterized::TableSyntax
+
+ child_class = Class.new
+
+ subject(:test_class) do
+ Class.new do
+ include ActiveModel::Model
+ include ActiveModel::Validations
+ attr_accessor :children
+ validates :children, array_members: { member_class: child_class }
+ end
+ end
+
+ where(:children, :is_valid) do
+ [child_class.new] | true
+ [Class.new.new] | false
+ [child_class.new, Class.new.new] | false
+ [] | false
+ child_class.new | false
+ [Class.new(child_class).new] | false
+ end
+
+ with_them do
+ it 'only accepts valid children nodes' do
+ expect(test_class.new(children: children).valid?).to eq(is_valid)
+ end
+ end
+
+ context 'validation message' do
+ subject(:test_class) do
+ Class.new do
+ include ActiveModel::Model
+ include ActiveModel::Validations
+ attr_accessor :children
+ end
+ end
+
+ context 'with default object name' do
+ it 'uses attribute name', :aggregate_failures do
+ test_class.class_eval do
+ validates :children, array_members: { member_class: child_class }
+ end
+
+ object = test_class.new(children: [])
+
+ expect(object.valid?).to be_falsey
+ expect(object.errors.messages).to eql(children: ['should be an array of children objects'])
+ end
+ end
+
+ context 'with custom object name' do
+ it 'uses that name', :aggregate_failures do
+ test_class.class_eval do
+ validates :children, array_members: { member_class: child_class, object_name: 'test' }
+ end
+
+ object = test_class.new(children: [])
+
+ expect(object.valid?).to be_falsey
+ expect(object.errors.messages).to eql(children: ['should be an array of test objects'])
+ end
+ end
+ end
+end
diff --git a/spec/validators/branch_filter_validator_spec.rb b/spec/validators/branch_filter_validator_spec.rb
index 957d1dd99bb..2d869fa674d 100644
--- a/spec/validators/branch_filter_validator_spec.rb
+++ b/spec/validators/branch_filter_validator_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe BranchFilterValidator do
+RSpec.describe BranchFilterValidator do
let(:validator) { described_class.new(attributes: [:push_events_branch_filter]) }
let(:hook) { build(:project_hook) }
diff --git a/spec/validators/color_validator_spec.rb b/spec/validators/color_validator_spec.rb
index e5a38ac9372..bd77b3df182 100644
--- a/spec/validators/color_validator_spec.rb
+++ b/spec/validators/color_validator_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ColorValidator do
+RSpec.describe ColorValidator do
using RSpec::Parameterized::TableSyntax
subject do
diff --git a/spec/validators/cron_freeze_period_timezone_validator_spec.rb b/spec/validators/cron_freeze_period_timezone_validator_spec.rb
index d283b89fa54..ee2541eb858 100644
--- a/spec/validators/cron_freeze_period_timezone_validator_spec.rb
+++ b/spec/validators/cron_freeze_period_timezone_validator_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe CronFreezePeriodTimezoneValidator do
+RSpec.describe CronFreezePeriodTimezoneValidator do
using RSpec::Parameterized::TableSyntax
subject { create :ci_freeze_period }
diff --git a/spec/validators/cron_validator_spec.rb b/spec/validators/cron_validator_spec.rb
index d6605610402..dff3b506b89 100644
--- a/spec/validators/cron_validator_spec.rb
+++ b/spec/validators/cron_validator_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe CronValidator do
+RSpec.describe CronValidator do
subject do
Class.new do
include ActiveModel::Model
diff --git a/spec/validators/devise_email_validator_spec.rb b/spec/validators/devise_email_validator_spec.rb
index 1dbf3f66cfd..29a008f858a 100644
--- a/spec/validators/devise_email_validator_spec.rb
+++ b/spec/validators/devise_email_validator_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe DeviseEmailValidator do
+RSpec.describe DeviseEmailValidator do
let!(:user) { build(:user, public_email: 'test@example.com') }
subject { validator.validate(user) }
diff --git a/spec/validators/js_regex_validator_spec.rb b/spec/validators/js_regex_validator_spec.rb
index dcc5e7ed4e8..d34ab78b44f 100644
--- a/spec/validators/js_regex_validator_spec.rb
+++ b/spec/validators/js_regex_validator_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe JsRegexValidator do
+RSpec.describe JsRegexValidator do
describe '#validates_each' do
using RSpec::Parameterized::TableSyntax
diff --git a/spec/validators/json_schema_validator_spec.rb b/spec/validators/json_schema_validator_spec.rb
index 3e3a9c0e6a8..83eb0e2f3dd 100644
--- a/spec/validators/json_schema_validator_spec.rb
+++ b/spec/validators/json_schema_validator_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe JsonSchemaValidator do
+RSpec.describe JsonSchemaValidator do
describe '#validates_each' do
let(:build_report_result) { build(:ci_build_report_result, :with_junit_success) }
diff --git a/spec/validators/named_ecdsa_key_validator_spec.rb b/spec/validators/named_ecdsa_key_validator_spec.rb
index 044c5b84a56..07326b7c55a 100644
--- a/spec/validators/named_ecdsa_key_validator_spec.rb
+++ b/spec/validators/named_ecdsa_key_validator_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe NamedEcdsaKeyValidator do
+RSpec.describe NamedEcdsaKeyValidator do
let(:validator) { described_class.new(attributes: [:key]) }
let!(:domain) { build(:pages_domain) }
diff --git a/spec/validators/namespace_path_validator_spec.rb b/spec/validators/namespace_path_validator_spec.rb
index bc9834dbd2a..25940bba49a 100644
--- a/spec/validators/namespace_path_validator_spec.rb
+++ b/spec/validators/namespace_path_validator_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe NamespacePathValidator do
+RSpec.describe NamespacePathValidator do
let(:validator) { described_class.new(attributes: [:path]) }
describe '.valid_path?' do
diff --git a/spec/validators/project_path_validator_spec.rb b/spec/validators/project_path_validator_spec.rb
index d6046f7214b..b92eb9106f6 100644
--- a/spec/validators/project_path_validator_spec.rb
+++ b/spec/validators/project_path_validator_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ProjectPathValidator do
+RSpec.describe ProjectPathValidator do
let(:validator) { described_class.new(attributes: [:path]) }
describe '.valid_path?' do
diff --git a/spec/validators/public_url_validator_spec.rb b/spec/validators/public_url_validator_spec.rb
index c81232d9b78..4fa6ad0cd6f 100644
--- a/spec/validators/public_url_validator_spec.rb
+++ b/spec/validators/public_url_validator_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe PublicUrlValidator do
+RSpec.describe PublicUrlValidator do
include_examples 'url validator examples', AddressableUrlValidator::DEFAULT_OPTIONS[:schemes]
include_examples 'public url validator examples', allow_local_requests_from_web_hooks_and_services: true
end
diff --git a/spec/validators/qualified_domain_array_validator_spec.rb b/spec/validators/qualified_domain_array_validator_spec.rb
index 664048c7544..788e007970a 100644
--- a/spec/validators/qualified_domain_array_validator_spec.rb
+++ b/spec/validators/qualified_domain_array_validator_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe QualifiedDomainArrayValidator do
+RSpec.describe QualifiedDomainArrayValidator do
let(:qualified_domain_array_validator_test_class) do
Class.new do
include ActiveModel::Validations
diff --git a/spec/validators/sha_validator_spec.rb b/spec/validators/sha_validator_spec.rb
index 0a76570f65e..cf2c7a72883 100644
--- a/spec/validators/sha_validator_spec.rb
+++ b/spec/validators/sha_validator_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ShaValidator do
+RSpec.describe ShaValidator do
let(:validator) { described_class.new(attributes: [:base_commit_sha]) }
let!(:merge_diff) { build(:merge_request_diff) }
diff --git a/spec/validators/system_hook_url_validator_spec.rb b/spec/validators/system_hook_url_validator_spec.rb
index 02384bbd1ce..445c0c7f67a 100644
--- a/spec/validators/system_hook_url_validator_spec.rb
+++ b/spec/validators/system_hook_url_validator_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe SystemHookUrlValidator do
+RSpec.describe SystemHookUrlValidator do
include_examples 'url validator examples', AddressableUrlValidator::DEFAULT_OPTIONS[:schemes]
include_examples 'public url validator examples', allow_local_requests_from_system_hooks: true
end
diff --git a/spec/validators/variable_duplicates_validator_spec.rb b/spec/validators/variable_duplicates_validator_spec.rb
index f48ebee7e0e..acc47ff225f 100644
--- a/spec/validators/variable_duplicates_validator_spec.rb
+++ b/spec/validators/variable_duplicates_validator_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe VariableDuplicatesValidator do
+RSpec.describe VariableDuplicatesValidator do
let(:validator) { described_class.new(attributes: [:variables], **options) }
describe '#validate_each' do
diff --git a/spec/validators/x509_certificate_credentials_validator_spec.rb b/spec/validators/x509_certificate_credentials_validator_spec.rb
index 2a5a322622f..9baa31c7257 100644
--- a/spec/validators/x509_certificate_credentials_validator_spec.rb
+++ b/spec/validators/x509_certificate_credentials_validator_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe X509CertificateCredentialsValidator do
+RSpec.describe X509CertificateCredentialsValidator do
let(:certificate_data) { File.read('spec/fixtures/x509_certificate.crt') }
let(:pkey_data) { File.read('spec/fixtures/x509_certificate_pk.key') }
diff --git a/spec/views/admin/application_settings/_eks.html.haml_spec.rb b/spec/views/admin/application_settings/_eks.html.haml_spec.rb
index 52434557d3a..2f45eaadc9f 100644
--- a/spec/views/admin/application_settings/_eks.html.haml_spec.rb
+++ b/spec/views/admin/application_settings/_eks.html.haml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'admin/application_settings/_eks' do
+RSpec.describe 'admin/application_settings/_eks' do
let_it_be(:admin) { create(:admin) }
let(:page) { Capybara::Node::Simple.new(rendered) }
diff --git a/spec/views/admin/application_settings/_repository_storage.html.haml_spec.rb b/spec/views/admin/application_settings/_repository_storage.html.haml_spec.rb
index 63236dbb0c4..2915fe1964f 100644
--- a/spec/views/admin/application_settings/_repository_storage.html.haml_spec.rb
+++ b/spec/views/admin/application_settings/_repository_storage.html.haml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'admin/application_settings/_repository_storage.html.haml' do
+RSpec.describe 'admin/application_settings/_repository_storage.html.haml' do
let(:app_settings) { create(:application_setting) }
let(:repository_storages_weighted_attributes) { [:repository_storages_weighted_default, :repository_storages_weighted_mepmep, :repository_storages_weighted_foobar]}
let(:repository_storages_weighted) do
diff --git a/spec/views/admin/application_settings/general.html.haml_spec.rb b/spec/views/admin/application_settings/general.html.haml_spec.rb
index d8ca5dd1b49..5343847d755 100644
--- a/spec/views/admin/application_settings/general.html.haml_spec.rb
+++ b/spec/views/admin/application_settings/general.html.haml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'admin/application_settings/general.html.haml' do
+RSpec.describe 'admin/application_settings/general.html.haml' do
let(:app_settings) { build(:application_setting) }
let(:user) { create(:admin) }
diff --git a/spec/views/admin/application_settings/repository.html.haml_spec.rb b/spec/views/admin/application_settings/repository.html.haml_spec.rb
new file mode 100644
index 00000000000..b110bc277ac
--- /dev/null
+++ b/spec/views/admin/application_settings/repository.html.haml_spec.rb
@@ -0,0 +1,46 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'admin/application_settings/repository.html.haml' do
+ let(:app_settings) { build(:application_setting) }
+ let(:user) { create(:admin) }
+
+ before do
+ assign(:application_setting, app_settings)
+ allow(view).to receive(:current_user).and_return(user)
+ end
+
+ describe 'default initial branch name' do
+ context 'when the feature flag is disabled' do
+ before do
+ stub_feature_flags(global_default_branch_name: false)
+ end
+
+ it 'does not show the setting section' do
+ render
+
+ expect(rendered).not_to have_css("#js-default-branch-name")
+ end
+ end
+
+ context 'when the feature flag is enabled' do
+ before do
+ stub_feature_flags(global_default_branch_name: true)
+ end
+
+ it 'has the setting section' do
+ render
+
+ expect(rendered).to have_css("#js-default-branch-name")
+ end
+
+ it 'renders the correct setting section content' do
+ render
+
+ expect(rendered).to have_content("Default initial branch name")
+ expect(rendered).to have_content("Set the default name of the initial branch when creating new repositories through the user interface.")
+ end
+ end
+ end
+end
diff --git a/spec/views/admin/dashboard/index.html.haml_spec.rb b/spec/views/admin/dashboard/index.html.haml_spec.rb
index 93fedde6e96..569a20e8f08 100644
--- a/spec/views/admin/dashboard/index.html.haml_spec.rb
+++ b/spec/views/admin/dashboard/index.html.haml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'admin/dashboard/index.html.haml' do
+RSpec.describe 'admin/dashboard/index.html.haml' do
include Devise::Test::ControllerHelpers
before do
diff --git a/spec/views/admin/sessions/new.html.haml_spec.rb b/spec/views/admin/sessions/new.html.haml_spec.rb
index b52ad0f9505..94870f0bdba 100644
--- a/spec/views/admin/sessions/new.html.haml_spec.rb
+++ b/spec/views/admin/sessions/new.html.haml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'admin/sessions/new.html.haml' do
+RSpec.describe 'admin/sessions/new.html.haml' do
let(:user) { create(:admin) }
before do
diff --git a/spec/views/admin/sessions/two_factor.html.haml_spec.rb b/spec/views/admin/sessions/two_factor.html.haml_spec.rb
index 2c061c7707b..9c5ff9925c1 100644
--- a/spec/views/admin/sessions/two_factor.html.haml_spec.rb
+++ b/spec/views/admin/sessions/two_factor.html.haml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'admin/sessions/two_factor.html.haml' do
+RSpec.describe 'admin/sessions/two_factor.html.haml' do
before do
allow(view).to receive(:current_user).and_return(user)
end
diff --git a/spec/views/admin/users/_user.html.haml_spec.rb b/spec/views/admin/users/_user.html.haml_spec.rb
index de5a291a6f8..aed05e4ea9b 100644
--- a/spec/views/admin/users/_user.html.haml_spec.rb
+++ b/spec/views/admin/users/_user.html.haml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'admin/users/_user.html.haml' do
+RSpec.describe 'admin/users/_user.html.haml' do
before do
allow(view).to receive(:user).and_return(user)
end
diff --git a/spec/views/ci/status/_badge.html.haml_spec.rb b/spec/views/ci/status/_badge.html.haml_spec.rb
index 59db828a0c7..6cbd9a61e98 100644
--- a/spec/views/ci/status/_badge.html.haml_spec.rb
+++ b/spec/views/ci/status/_badge.html.haml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'ci/status/_badge' do
+RSpec.describe 'ci/status/_badge' do
let(:user) { create(:user) }
let(:project) { create(:project, :private) }
let(:pipeline) { create(:ci_pipeline, project: project) }
diff --git a/spec/views/ci/status/_icon.html.haml_spec.rb b/spec/views/ci/status/_icon.html.haml_spec.rb
index 626159fc512..d0579734451 100644
--- a/spec/views/ci/status/_icon.html.haml_spec.rb
+++ b/spec/views/ci/status/_icon.html.haml_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe 'ci/status/_icon' do
+RSpec.describe 'ci/status/_icon' do
let(:user) { create(:user) }
let(:project) { create(:project, :private) }
let(:pipeline) { create(:ci_pipeline, project: project) }
diff --git a/spec/views/dashboard/projects/_blank_state_admin_welcome.haml_spec.rb b/spec/views/dashboard/projects/_blank_state_admin_welcome.haml_spec.rb
index bc92278bb22..9c064596f09 100644
--- a/spec/views/dashboard/projects/_blank_state_admin_welcome.haml_spec.rb
+++ b/spec/views/dashboard/projects/_blank_state_admin_welcome.haml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'dashboard/projects/_blank_state_admin_welcome.html.haml' do
+RSpec.describe 'dashboard/projects/_blank_state_admin_welcome.html.haml' do
let(:user) { create(:admin) }
before do
diff --git a/spec/views/dashboard/projects/_nav.html.haml_spec.rb b/spec/views/dashboard/projects/_nav.html.haml_spec.rb
index 61b6bfef120..a592c4e44d2 100644
--- a/spec/views/dashboard/projects/_nav.html.haml_spec.rb
+++ b/spec/views/dashboard/projects/_nav.html.haml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'dashboard/projects/_nav.html.haml' do
+RSpec.describe 'dashboard/projects/_nav.html.haml' do
it 'highlights All tab by default' do
render
diff --git a/spec/views/devise/sessions/new.html.haml_spec.rb b/spec/views/devise/sessions/new.html.haml_spec.rb
index 27bd683bbf0..b5c69f4f04d 100644
--- a/spec/views/devise/sessions/new.html.haml_spec.rb
+++ b/spec/views/devise/sessions/new.html.haml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'devise/sessions/new' do
+RSpec.describe 'devise/sessions/new' do
describe 'ldap' do
include LdapHelpers
diff --git a/spec/views/devise/shared/_signin_box.html.haml_spec.rb b/spec/views/devise/shared/_signin_box.html.haml_spec.rb
index dfd8c315e50..8b1af1866dc 100644
--- a/spec/views/devise/shared/_signin_box.html.haml_spec.rb
+++ b/spec/views/devise/shared/_signin_box.html.haml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'devise/shared/_signin_box' do
+RSpec.describe 'devise/shared/_signin_box' do
describe 'Crowd form' do
before do
stub_devise
diff --git a/spec/views/errors/access_denied.html.haml_spec.rb b/spec/views/errors/access_denied.html.haml_spec.rb
index b2e82847ab9..276d94111c6 100644
--- a/spec/views/errors/access_denied.html.haml_spec.rb
+++ b/spec/views/errors/access_denied.html.haml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'errors/access_denied' do
+RSpec.describe 'errors/access_denied' do
it 'does not fail to render when there is no message provided' do
expect { render }.not_to raise_error
end
diff --git a/spec/views/events/event/_push.html.haml_spec.rb b/spec/views/events/event/_push.html.haml_spec.rb
index d33a8aa86fc..f4d3258ff67 100644
--- a/spec/views/events/event/_push.html.haml_spec.rb
+++ b/spec/views/events/event/_push.html.haml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'events/event/_push.html.haml' do
+RSpec.describe 'events/event/_push.html.haml' do
let(:event) { build_stubbed(:push_event) }
context 'with a branch' do
diff --git a/spec/views/groups/_home_panel.html.haml_spec.rb b/spec/views/groups/_home_panel.html.haml_spec.rb
index 8960dfa67db..b8168b20450 100644
--- a/spec/views/groups/_home_panel.html.haml_spec.rb
+++ b/spec/views/groups/_home_panel.html.haml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'groups/_home_panel' do
+RSpec.describe 'groups/_home_panel' do
let(:group) { create(:group) }
before do
diff --git a/spec/views/groups/edit.html.haml_spec.rb b/spec/views/groups/edit.html.haml_spec.rb
index 9fc850841ee..83623ea7bb4 100644
--- a/spec/views/groups/edit.html.haml_spec.rb
+++ b/spec/views/groups/edit.html.haml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'groups/edit.html.haml' do
+RSpec.describe 'groups/edit.html.haml' do
include Devise::Test::ControllerHelpers
describe '"Share with group lock" setting' do
diff --git a/spec/views/help/index.html.haml_spec.rb b/spec/views/help/index.html.haml_spec.rb
index 3831ddacb72..c59790a346e 100644
--- a/spec/views/help/index.html.haml_spec.rb
+++ b/spec/views/help/index.html.haml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'help/index' do
+RSpec.describe 'help/index' do
include StubVersion
describe 'version information' do
diff --git a/spec/views/help/instance_configuration.html.haml_spec.rb b/spec/views/help/instance_configuration.html.haml_spec.rb
index 81d569b5cf4..7b431bb4180 100644
--- a/spec/views/help/instance_configuration.html.haml_spec.rb
+++ b/spec/views/help/instance_configuration.html.haml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'help/instance_configuration' do
+RSpec.describe 'help/instance_configuration' do
describe 'General Sections:' do
let(:instance_configuration) { build(:instance_configuration)}
let(:settings) { instance_configuration.settings }
diff --git a/spec/views/help/show.html.haml_spec.rb b/spec/views/help/show.html.haml_spec.rb
index 539c647c1d3..ab303919673 100644
--- a/spec/views/help/show.html.haml_spec.rb
+++ b/spec/views/help/show.html.haml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'help/show' do
+RSpec.describe 'help/show' do
describe 'Markdown rendering' do
before do
assign(:path, 'ssh/README')
diff --git a/spec/views/import/gitlab_projects/new.html.haml_spec.rb b/spec/views/import/gitlab_projects/new.html.haml_spec.rb
index 17636c99cbc..c09c798f487 100644
--- a/spec/views/import/gitlab_projects/new.html.haml_spec.rb
+++ b/spec/views/import/gitlab_projects/new.html.haml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'import/gitlab_projects/new.html.haml' do
+RSpec.describe 'import/gitlab_projects/new.html.haml' do
include Devise::Test::ControllerHelpers
let(:namespace) { build_stubbed(:namespace) }
diff --git a/spec/views/layouts/_head.html.haml_spec.rb b/spec/views/layouts/_head.html.haml_spec.rb
index 7011fa23327..25fcbeb61df 100644
--- a/spec/views/layouts/_head.html.haml_spec.rb
+++ b/spec/views/layouts/_head.html.haml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'layouts/_head' do
+RSpec.describe 'layouts/_head' do
include StubConfiguration
before do
diff --git a/spec/views/layouts/application.html.haml_spec.rb b/spec/views/layouts/application.html.haml_spec.rb
index 4270bbf1924..679d0b1ff60 100644
--- a/spec/views/layouts/application.html.haml_spec.rb
+++ b/spec/views/layouts/application.html.haml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'layouts/application' do
+RSpec.describe 'layouts/application' do
let(:user) { create(:user) }
before do
diff --git a/spec/views/layouts/header/_new_dropdown.haml_spec.rb b/spec/views/layouts/header/_new_dropdown.haml_spec.rb
index 1a04ffed103..cf33ec9884b 100644
--- a/spec/views/layouts/header/_new_dropdown.haml_spec.rb
+++ b/spec/views/layouts/header/_new_dropdown.haml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'layouts/header/_new_dropdown' do
+RSpec.describe 'layouts/header/_new_dropdown' do
let(:user) { create(:user) }
context 'group-specific links' do
diff --git a/spec/views/layouts/nav/sidebar/_admin.html.haml_spec.rb b/spec/views/layouts/nav/sidebar/_admin.html.haml_spec.rb
index 2f8a75a81c8..d1e756422d5 100644
--- a/spec/views/layouts/nav/sidebar/_admin.html.haml_spec.rb
+++ b/spec/views/layouts/nav/sidebar/_admin.html.haml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'layouts/nav/sidebar/_admin' do
+RSpec.describe 'layouts/nav/sidebar/_admin' do
shared_examples 'page has active tab' do |title|
it "activates #{title} tab" do
render
diff --git a/spec/views/layouts/nav/sidebar/_group.html.haml_spec.rb b/spec/views/layouts/nav/sidebar/_group.html.haml_spec.rb
index 24b66a0e767..b3c8450fb48 100644
--- a/spec/views/layouts/nav/sidebar/_group.html.haml_spec.rb
+++ b/spec/views/layouts/nav/sidebar/_group.html.haml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'layouts/nav/sidebar/_group' do
+RSpec.describe 'layouts/nav/sidebar/_group' do
let(:group) { create(:group) }
before do
diff --git a/spec/views/layouts/nav/sidebar/_instance_statistics.html.haml_spec.rb b/spec/views/layouts/nav/sidebar/_instance_statistics.html.haml_spec.rb
index 7f7f5637035..d3b57f6dfcf 100644
--- a/spec/views/layouts/nav/sidebar/_instance_statistics.html.haml_spec.rb
+++ b/spec/views/layouts/nav/sidebar/_instance_statistics.html.haml_spec.rb
@@ -2,6 +2,6 @@
require 'spec_helper'
-describe 'layouts/nav/sidebar/_instance_statistics' do
+RSpec.describe 'layouts/nav/sidebar/_instance_statistics' do
it_behaves_like 'has nav sidebar'
end
diff --git a/spec/views/layouts/nav/sidebar/_profile.html.haml_spec.rb b/spec/views/layouts/nav/sidebar/_profile.html.haml_spec.rb
index 6b820ab0b4c..0f6dcf8e57f 100644
--- a/spec/views/layouts/nav/sidebar/_profile.html.haml_spec.rb
+++ b/spec/views/layouts/nav/sidebar/_profile.html.haml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'layouts/nav/sidebar/_profile' do
+RSpec.describe 'layouts/nav/sidebar/_profile' do
let(:user) { create(:user) }
before do
diff --git a/spec/views/layouts/nav/sidebar/_project.html.haml_spec.rb b/spec/views/layouts/nav/sidebar/_project.html.haml_spec.rb
index 881ea818cb0..bf0bf63e164 100644
--- a/spec/views/layouts/nav/sidebar/_project.html.haml_spec.rb
+++ b/spec/views/layouts/nav/sidebar/_project.html.haml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'layouts/nav/sidebar/_project' do
+RSpec.describe 'layouts/nav/sidebar/_project' do
let(:project) { create(:project, :repository) }
before do
@@ -76,7 +76,7 @@ describe 'layouts/nav/sidebar/_project' do
it 'does not show the wiki tab' do
render
- expect(rendered).not_to have_link('Wiki', href: wiki_path(project.wiki))
+ expect(rendered).not_to have_link('Wiki')
end
end
end
@@ -109,6 +109,38 @@ describe 'layouts/nav/sidebar/_project' do
end
end
+ describe 'confluence tab' do
+ let!(:service) { create(:confluence_service, project: project, active: active) }
+
+ before do
+ render
+ end
+
+ context 'when the Confluence integration is active' do
+ let(:active) { true }
+
+ it 'shows the Confluence tab' do
+ expect(rendered).to have_link('Confluence', href: project_wikis_confluence_path(project))
+ end
+
+ it 'does not show the GitLab wiki tab' do
+ expect(rendered).not_to have_link('Wiki')
+ end
+ end
+
+ context 'when it is disabled' do
+ let(:active) { false }
+
+ it 'does not show the Confluence tab' do
+ expect(rendered).not_to have_link('Confluence')
+ end
+
+ it 'shows the GitLab wiki tab' do
+ expect(rendered).to have_link('Wiki', href: wiki_path(project.wiki))
+ end
+ end
+ end
+
describe 'ci/cd settings tab' do
before do
project.update!(archived: project_archived)
diff --git a/spec/views/notify/changed_milestone_email.html.haml_spec.rb b/spec/views/notify/changed_milestone_email.html.haml_spec.rb
index 194b58840a3..50a06683409 100644
--- a/spec/views/notify/changed_milestone_email.html.haml_spec.rb
+++ b/spec/views/notify/changed_milestone_email.html.haml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'notify/changed_milestone_email.html.haml' do
+RSpec.describe 'notify/changed_milestone_email.html.haml' do
let(:milestone) { create(:milestone, title: 'some-milestone') }
let(:milestone_link) { milestone_url(milestone) }
diff --git a/spec/views/notify/pipeline_failed_email.html.haml_spec.rb b/spec/views/notify/pipeline_failed_email.html.haml_spec.rb
index 80dc14b523d..b41933f9c36 100644
--- a/spec/views/notify/pipeline_failed_email.html.haml_spec.rb
+++ b/spec/views/notify/pipeline_failed_email.html.haml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'notify/pipeline_failed_email.html.haml' do
+RSpec.describe 'notify/pipeline_failed_email.html.haml' do
it_behaves_like 'pipeline status changes email' do
let(:title) { 'Your pipeline has failed' }
let(:status) { :failed }
diff --git a/spec/views/notify/pipeline_failed_email.text.erb_spec.rb b/spec/views/notify/pipeline_failed_email.text.erb_spec.rb
index 9a4cea408a6..a5c31632b0d 100644
--- a/spec/views/notify/pipeline_failed_email.text.erb_spec.rb
+++ b/spec/views/notify/pipeline_failed_email.text.erb_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe 'notify/pipeline_failed_email.text.erb' do
+RSpec.describe 'notify/pipeline_failed_email.text.erb' do
include Devise::Test::ControllerHelpers
let(:user) { create(:user, developer_projects: [project]) }
diff --git a/spec/views/notify/pipeline_fixed_email.html.haml_spec.rb b/spec/views/notify/pipeline_fixed_email.html.haml_spec.rb
index 382fc5ecdd3..2b3b08f8e8c 100644
--- a/spec/views/notify/pipeline_fixed_email.html.haml_spec.rb
+++ b/spec/views/notify/pipeline_fixed_email.html.haml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'notify/pipeline_fixed_email.html.haml' do
+RSpec.describe 'notify/pipeline_fixed_email.html.haml' do
it_behaves_like 'pipeline status changes email' do
let(:title) { 'Your pipeline has been fixed!' }
let(:status) { :success }
diff --git a/spec/views/notify/pipeline_fixed_email.text.erb_spec.rb b/spec/views/notify/pipeline_fixed_email.text.erb_spec.rb
index ec540dc3f77..8640998acaa 100644
--- a/spec/views/notify/pipeline_fixed_email.text.erb_spec.rb
+++ b/spec/views/notify/pipeline_fixed_email.text.erb_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'notify/pipeline_fixed_email.text.erb' do
+RSpec.describe 'notify/pipeline_fixed_email.text.erb' do
it_behaves_like 'pipeline status changes email' do
let(:title) { 'Your pipeline has been fixed!' }
let(:status) { :success }
diff --git a/spec/views/notify/pipeline_success_email.html.haml_spec.rb b/spec/views/notify/pipeline_success_email.html.haml_spec.rb
index 417909fd67b..51ea7ef5066 100644
--- a/spec/views/notify/pipeline_success_email.html.haml_spec.rb
+++ b/spec/views/notify/pipeline_success_email.html.haml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'notify/pipeline_success_email.html.haml' do
+RSpec.describe 'notify/pipeline_success_email.html.haml' do
it_behaves_like 'pipeline status changes email' do
let(:title) { 'Your pipeline has passed' }
let(:status) { :success }
diff --git a/spec/views/notify/pipeline_success_email.text.erb_spec.rb b/spec/views/notify/pipeline_success_email.text.erb_spec.rb
index 4a914cab85e..3acf4dd3e26 100644
--- a/spec/views/notify/pipeline_success_email.text.erb_spec.rb
+++ b/spec/views/notify/pipeline_success_email.text.erb_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'notify/pipeline_success_email.text.erb' do
+RSpec.describe 'notify/pipeline_success_email.text.erb' do
it_behaves_like 'pipeline status changes email' do
let(:title) { 'Your pipeline has passed' }
let(:status) { :success }
diff --git a/spec/views/notify/push_to_merge_request_email.text.haml_spec.rb b/spec/views/notify/push_to_merge_request_email.text.haml_spec.rb
new file mode 100644
index 00000000000..ce402533496
--- /dev/null
+++ b/spec/views/notify/push_to_merge_request_email.text.haml_spec.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+RSpec.describe 'notify/push_to_merge_request_email.text.haml' do
+ let(:user) { create(:user, developer_projects: [project]) }
+ let(:project) { create(:project, :repository) }
+ let(:merge_request) { create(:merge_request, :simple, source_project: project) }
+ let(:new_commits) { project.repository.commits_between('be93687618e4b132087f430a4d8fc3a609c9b77c', '54fcc214b94e78d7a41a9a8fe6d87a5e59500e51') }
+
+ before do
+ assign(:updated_by_user, user)
+ assign(:project, project)
+ assign(:merge_request, merge_request)
+ assign(:existing_commits, [])
+ assign(:new_commits, new_commits)
+ end
+
+ it_behaves_like 'renders plain text email correctly'
+end
diff --git a/spec/views/profiles/preferences/show.html.haml_spec.rb b/spec/views/profiles/preferences/show.html.haml_spec.rb
index 2e50e329cfd..5acfbfb9db1 100644
--- a/spec/views/profiles/preferences/show.html.haml_spec.rb
+++ b/spec/views/profiles/preferences/show.html.haml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'profiles/preferences/show' do
+RSpec.describe 'profiles/preferences/show' do
using RSpec::Parameterized::TableSyntax
let_it_be(:user) { build(:user) }
diff --git a/spec/views/profiles/show.html.haml_spec.rb b/spec/views/profiles/show.html.haml_spec.rb
index 14e6feed3ab..daa1d20e6b1 100644
--- a/spec/views/profiles/show.html.haml_spec.rb
+++ b/spec/views/profiles/show.html.haml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'profiles/show' do
+RSpec.describe 'profiles/show' do
let(:user) { create(:user) }
before do
diff --git a/spec/views/projects/_home_panel.html.haml_spec.rb b/spec/views/projects/_home_panel.html.haml_spec.rb
index 9956144b601..548dba7874a 100644
--- a/spec/views/projects/_home_panel.html.haml_spec.rb
+++ b/spec/views/projects/_home_panel.html.haml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'projects/_home_panel' do
+RSpec.describe 'projects/_home_panel' do
include ProjectForksHelper
context 'notifications' do
diff --git a/spec/views/projects/blob/_viewer.html.haml_spec.rb b/spec/views/projects/blob/_viewer.html.haml_spec.rb
index a798a72fa76..893cfec1491 100644
--- a/spec/views/projects/blob/_viewer.html.haml_spec.rb
+++ b/spec/views/projects/blob/_viewer.html.haml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'projects/blob/_viewer.html.haml' do
+RSpec.describe 'projects/blob/_viewer.html.haml' do
include FakeBlobHelpers
let(:project) { build(:project) }
diff --git a/spec/views/projects/buttons/_dropdown.html.haml_spec.rb b/spec/views/projects/buttons/_dropdown.html.haml_spec.rb
index 2bc1de040d5..fc9d7c3ea91 100644
--- a/spec/views/projects/buttons/_dropdown.html.haml_spec.rb
+++ b/spec/views/projects/buttons/_dropdown.html.haml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'projects/buttons/_dropdown' do
+RSpec.describe 'projects/buttons/_dropdown' do
let(:user) { create(:user) }
context 'user with all abilities' do
diff --git a/spec/views/projects/ci/lints/show.html.haml_spec.rb b/spec/views/projects/ci/lints/show.html.haml_spec.rb
index 8c3cf04bae6..bcfb952ca66 100644
--- a/spec/views/projects/ci/lints/show.html.haml_spec.rb
+++ b/spec/views/projects/ci/lints/show.html.haml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'projects/ci/lints/show' do
+RSpec.describe 'projects/ci/lints/show' do
include Devise::Test::ControllerHelpers
let(:project) { create(:project, :repository) }
let(:config_processor) { Gitlab::Ci::YamlProcessor.new(YAML.dump(content)) }
diff --git a/spec/views/projects/clusters/clusters/gcp/_form.html.haml_spec.rb b/spec/views/projects/clusters/clusters/gcp/_form.html.haml_spec.rb
index 1cb2f9a4301..bf5cb6fb25d 100644
--- a/spec/views/projects/clusters/clusters/gcp/_form.html.haml_spec.rb
+++ b/spec/views/projects/clusters/clusters/gcp/_form.html.haml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'clusters/clusters/gcp/_form' do
+RSpec.describe 'clusters/clusters/gcp/_form' do
let(:admin) { create(:admin) }
let(:environment) { create(:environment) }
let(:gcp_cluster) { create(:cluster, :provided_by_gcp) }
diff --git a/spec/views/projects/commit/_commit_box.html.haml_spec.rb b/spec/views/projects/commit/_commit_box.html.haml_spec.rb
index 07c530670d2..9c97696493e 100644
--- a/spec/views/projects/commit/_commit_box.html.haml_spec.rb
+++ b/spec/views/projects/commit/_commit_box.html.haml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'projects/commit/_commit_box.html.haml' do
+RSpec.describe 'projects/commit/_commit_box.html.haml' do
let(:user) { create(:user) }
let(:project) { create(:project, :repository) }
diff --git a/spec/views/projects/commit/branches.html.haml_spec.rb b/spec/views/projects/commit/branches.html.haml_spec.rb
index 0fe7165a790..f1064be3047 100644
--- a/spec/views/projects/commit/branches.html.haml_spec.rb
+++ b/spec/views/projects/commit/branches.html.haml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'projects/commit/branches.html.haml' do
+RSpec.describe 'projects/commit/branches.html.haml' do
let(:project) { create(:project, :repository) }
before do
diff --git a/spec/views/projects/commit/show.html.haml_spec.rb b/spec/views/projects/commit/show.html.haml_spec.rb
index af28a8f9193..c2970abbe9f 100644
--- a/spec/views/projects/commit/show.html.haml_spec.rb
+++ b/spec/views/projects/commit/show.html.haml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'projects/commit/show.html.haml' do
+RSpec.describe 'projects/commit/show.html.haml' do
let(:project) { create(:project, :repository) }
let(:commit) { project.commit }
diff --git a/spec/views/projects/commits/_commit.html.haml_spec.rb b/spec/views/projects/commits/_commit.html.haml_spec.rb
index 1c01a7f9a9c..898d3baae19 100644
--- a/spec/views/projects/commits/_commit.html.haml_spec.rb
+++ b/spec/views/projects/commits/_commit.html.haml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'projects/commits/_commit.html.haml' do
+RSpec.describe 'projects/commits/_commit.html.haml' do
let(:template) { 'projects/commits/commit.html.haml' }
let(:project) { create(:project, :repository) }
let(:commit) { project.repository.commit(ref) }
diff --git a/spec/views/projects/deployments/_confirm_rollback_modal_spec.html_spec.rb b/spec/views/projects/deployments/_confirm_rollback_modal_spec.html_spec.rb
index 9168bc8e833..2fb7b6187eb 100644
--- a/spec/views/projects/deployments/_confirm_rollback_modal_spec.html_spec.rb
+++ b/spec/views/projects/deployments/_confirm_rollback_modal_spec.html_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'projects/deployments/_confirm_rollback_modal' do
+RSpec.describe 'projects/deployments/_confirm_rollback_modal' do
let(:environment) { create(:environment, :with_review_app) }
let(:deployments) { environment.deployments }
let(:project) { environment.project }
diff --git a/spec/views/projects/diffs/_stats.html.haml_spec.rb b/spec/views/projects/diffs/_stats.html.haml_spec.rb
index eb853596f98..f0580b50349 100644
--- a/spec/views/projects/diffs/_stats.html.haml_spec.rb
+++ b/spec/views/projects/diffs/_stats.html.haml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'projects/diffs/_stats.html.haml' do
+RSpec.describe 'projects/diffs/_stats.html.haml' do
let(:project) { create(:project, :repository) }
let(:commit) { project.commit('570e7b2abdd848b95f2f578043fc23bd6f6fd24d') }
diff --git a/spec/views/projects/diffs/_viewer.html.haml_spec.rb b/spec/views/projects/diffs/_viewer.html.haml_spec.rb
index 27f271bb178..305c44190b4 100644
--- a/spec/views/projects/diffs/_viewer.html.haml_spec.rb
+++ b/spec/views/projects/diffs/_viewer.html.haml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'projects/diffs/_viewer.html.haml' do
+RSpec.describe 'projects/diffs/_viewer.html.haml' do
include FakeBlobHelpers
let(:project) { create(:project, :repository) }
diff --git a/spec/views/projects/edit.html.haml_spec.rb b/spec/views/projects/edit.html.haml_spec.rb
index 0c0f74a41f0..b44d07d2ee4 100644
--- a/spec/views/projects/edit.html.haml_spec.rb
+++ b/spec/views/projects/edit.html.haml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'projects/edit' do
+RSpec.describe 'projects/edit' do
include Devise::Test::ControllerHelpers
include ProjectForksHelper
diff --git a/spec/views/projects/environments/terminal.html.haml_spec.rb b/spec/views/projects/environments/terminal.html.haml_spec.rb
index b0b08a84f8d..bfb88f0f72b 100644
--- a/spec/views/projects/environments/terminal.html.haml_spec.rb
+++ b/spec/views/projects/environments/terminal.html.haml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'projects/environments/terminal' do
+RSpec.describe 'projects/environments/terminal' do
let!(:environment) { create(:environment, :with_review_app) }
before do
diff --git a/spec/views/projects/imports/new.html.haml_spec.rb b/spec/views/projects/imports/new.html.haml_spec.rb
index 6cca369b9f6..edf9eadf924 100644
--- a/spec/views/projects/imports/new.html.haml_spec.rb
+++ b/spec/views/projects/imports/new.html.haml_spec.rb
@@ -2,7 +2,7 @@
require "spec_helper"
-describe "projects/imports/new.html.haml" do
+RSpec.describe "projects/imports/new.html.haml" do
let(:user) { create(:user) }
context 'when import fails' do
diff --git a/spec/views/projects/issues/_related_branches.html.haml_spec.rb b/spec/views/projects/issues/_related_branches.html.haml_spec.rb
index 6c9bbaea38c..ba6f7068024 100644
--- a/spec/views/projects/issues/_related_branches.html.haml_spec.rb
+++ b/spec/views/projects/issues/_related_branches.html.haml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'projects/issues/_related_branches' do
+RSpec.describe 'projects/issues/_related_branches' do
include Devise::Test::ControllerHelpers
let(:pipeline) { build(:ci_pipeline, :success) }
diff --git a/spec/views/projects/issues/import_csv/_button.html.haml_spec.rb b/spec/views/projects/issues/import_csv/_button.html.haml_spec.rb
index 440edd376e0..8bc0a00d71c 100644
--- a/spec/views/projects/issues/import_csv/_button.html.haml_spec.rb
+++ b/spec/views/projects/issues/import_csv/_button.html.haml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'projects/issues/import_csv/_button' do
+RSpec.describe 'projects/issues/import_csv/_button' do
include Devise::Test::ControllerHelpers
context 'when the user does not have edit permissions' do
diff --git a/spec/views/projects/issues/show.html.haml_spec.rb b/spec/views/projects/issues/show.html.haml_spec.rb
index 60a541916e9..b2d208f038a 100644
--- a/spec/views/projects/issues/show.html.haml_spec.rb
+++ b/spec/views/projects/issues/show.html.haml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'projects/issues/show' do
+RSpec.describe 'projects/issues/show' do
include_context 'project show action'
context 'when the issue is closed' do
@@ -23,7 +23,7 @@ describe 'projects/issues/show' do
project.add_developer(user)
end
- it 'shows "Closed (moved)" if an issue has been moved' do
+ it 'shows "Closed (moved)" if an issue has been moved and closed' do
render
expect(rendered).to have_selector('.status-box-issue-closed:not(.hidden)', text: 'Closed (moved)')
@@ -41,6 +41,14 @@ describe 'projects/issues/show' do
expect(rendered).to have_selector("a[href=\"#{issue_path(new_issue)}\"]", text: 'moved')
end
+
+ it 'does not show "closed (moved)" if an issue has been moved and reopened (not closed)' do
+ allow(issue).to receive(:closed?).and_return(false)
+
+ render
+
+ expect(rendered).not_to have_selector('.status-box-issue-closed:not(.hidden)', text: 'Closed (moved)')
+ end
end
context 'when user cannot see moved issue' do
diff --git a/spec/views/projects/jobs/_build.html.haml_spec.rb b/spec/views/projects/jobs/_build.html.haml_spec.rb
index 681df77ea99..4256df9ce9a 100644
--- a/spec/views/projects/jobs/_build.html.haml_spec.rb
+++ b/spec/views/projects/jobs/_build.html.haml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'projects/ci/jobs/_build' do
+RSpec.describe 'projects/ci/jobs/_build' do
include Devise::Test::ControllerHelpers
let(:project) { create(:project, :repository) }
diff --git a/spec/views/projects/jobs/_generic_commit_status.html.haml_spec.rb b/spec/views/projects/jobs/_generic_commit_status.html.haml_spec.rb
index f193ce0a552..9be2a6f0ce4 100644
--- a/spec/views/projects/jobs/_generic_commit_status.html.haml_spec.rb
+++ b/spec/views/projects/jobs/_generic_commit_status.html.haml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'projects/generic_commit_statuses/_generic_commit_status.html.haml' do
+RSpec.describe 'projects/generic_commit_statuses/_generic_commit_status.html.haml' do
include Devise::Test::ControllerHelpers
let(:project) { create(:project, :repository) }
diff --git a/spec/views/projects/jobs/show.html.haml_spec.rb b/spec/views/projects/jobs/show.html.haml_spec.rb
index 903a16d9e53..83a00135629 100644
--- a/spec/views/projects/jobs/show.html.haml_spec.rb
+++ b/spec/views/projects/jobs/show.html.haml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'projects/jobs/show' do
+RSpec.describe 'projects/jobs/show' do
let(:user) { create(:user) }
let(:project) { create(:project, :repository) }
let(:build) { create(:ci_build, pipeline: pipeline) }
diff --git a/spec/views/projects/merge_requests/_commits.html.haml_spec.rb b/spec/views/projects/merge_requests/_commits.html.haml_spec.rb
index 755a40a7e4c..fd77c4eb372 100644
--- a/spec/views/projects/merge_requests/_commits.html.haml_spec.rb
+++ b/spec/views/projects/merge_requests/_commits.html.haml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'projects/merge_requests/_commits.html.haml', :sidekiq_might_not_need_inline do
+RSpec.describe 'projects/merge_requests/_commits.html.haml', :sidekiq_might_not_need_inline do
include Devise::Test::ControllerHelpers
include ProjectForksHelper
diff --git a/spec/views/projects/merge_requests/creations/_new_submit.html.haml_spec.rb b/spec/views/projects/merge_requests/creations/_new_submit.html.haml_spec.rb
index f7db4a4d614..038a94fe7c3 100644
--- a/spec/views/projects/merge_requests/creations/_new_submit.html.haml_spec.rb
+++ b/spec/views/projects/merge_requests/creations/_new_submit.html.haml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'projects/merge_requests/creations/_new_submit.html.haml' do
+RSpec.describe 'projects/merge_requests/creations/_new_submit.html.haml' do
let(:merge_request) { create(:merge_request) }
let!(:pipeline) { create(:ci_empty_pipeline) }
diff --git a/spec/views/projects/merge_requests/diffs/_diffs.html.haml_spec.rb b/spec/views/projects/merge_requests/diffs/_diffs.html.haml_spec.rb
index a82ef3c04b5..7cdc817d784 100644
--- a/spec/views/projects/merge_requests/diffs/_diffs.html.haml_spec.rb
+++ b/spec/views/projects/merge_requests/diffs/_diffs.html.haml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'projects/merge_requests/diffs/_diffs.html.haml' do
+RSpec.describe 'projects/merge_requests/diffs/_diffs.html.haml' do
include Devise::Test::ControllerHelpers
let(:user) { create(:user) }
diff --git a/spec/views/projects/merge_requests/edit.html.haml_spec.rb b/spec/views/projects/merge_requests/edit.html.haml_spec.rb
index 74d9067076c..55a74dc8229 100644
--- a/spec/views/projects/merge_requests/edit.html.haml_spec.rb
+++ b/spec/views/projects/merge_requests/edit.html.haml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'projects/merge_requests/edit.html.haml' do
+RSpec.describe 'projects/merge_requests/edit.html.haml' do
include Devise::Test::ControllerHelpers
include ProjectForksHelper
diff --git a/spec/views/projects/merge_requests/show.html.haml_spec.rb b/spec/views/projects/merge_requests/show.html.haml_spec.rb
index e0acf5d1507..32819fc2cb0 100644
--- a/spec/views/projects/merge_requests/show.html.haml_spec.rb
+++ b/spec/views/projects/merge_requests/show.html.haml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'projects/merge_requests/show.html.haml' do
+RSpec.describe 'projects/merge_requests/show.html.haml' do
before do
allow(view).to receive(:experiment_enabled?).and_return(false)
end
diff --git a/spec/views/projects/notes/_more_actions_dropdown.html.haml_spec.rb b/spec/views/projects/notes/_more_actions_dropdown.html.haml_spec.rb
index a07523a4423..3776af9e757 100644
--- a/spec/views/projects/notes/_more_actions_dropdown.html.haml_spec.rb
+++ b/spec/views/projects/notes/_more_actions_dropdown.html.haml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'projects/notes/_more_actions_dropdown' do
+RSpec.describe 'projects/notes/_more_actions_dropdown' do
let(:author_user) { create(:user) }
let(:not_author_user) { create(:user) }
diff --git a/spec/views/projects/pages/show.html.haml_spec.rb b/spec/views/projects/pages/show.html.haml_spec.rb
index 63b66616f31..fac46d08f0c 100644
--- a/spec/views/projects/pages/show.html.haml_spec.rb
+++ b/spec/views/projects/pages/show.html.haml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'projects/pages/show' do
+RSpec.describe 'projects/pages/show' do
include LetsEncryptHelpers
let(:project) { create(:project, :repository) }
diff --git a/spec/views/projects/pages_domains/show.html.haml_spec.rb b/spec/views/projects/pages_domains/show.html.haml_spec.rb
index 2de82a63560..d2abe3dfa56 100644
--- a/spec/views/projects/pages_domains/show.html.haml_spec.rb
+++ b/spec/views/projects/pages_domains/show.html.haml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'projects/pages_domains/show' do
+RSpec.describe 'projects/pages_domains/show' do
let(:project) { create(:project, :repository) }
before do
diff --git a/spec/views/projects/pipeline_schedules/_pipeline_schedule.html.haml_spec.rb b/spec/views/projects/pipeline_schedules/_pipeline_schedule.html.haml_spec.rb
index daf799ec13f..e650e183bc8 100644
--- a/spec/views/projects/pipeline_schedules/_pipeline_schedule.html.haml_spec.rb
+++ b/spec/views/projects/pipeline_schedules/_pipeline_schedule.html.haml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'projects/pipeline_schedules/_pipeline_schedule' do
+RSpec.describe 'projects/pipeline_schedules/_pipeline_schedule' do
let(:owner) { create(:user) }
let(:maintainer) { create(:user) }
let(:project) { create(:project) }
diff --git a/spec/views/projects/pipelines/_stage.html.haml_spec.rb b/spec/views/projects/pipelines/_stage.html.haml_spec.rb
index 591602859c3..c8f6784a0f6 100644
--- a/spec/views/projects/pipelines/_stage.html.haml_spec.rb
+++ b/spec/views/projects/pipelines/_stage.html.haml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'projects/pipelines/_stage' do
+RSpec.describe 'projects/pipelines/_stage' do
let(:project) { create(:project, :repository) }
let(:pipeline) { create(:ci_pipeline, project: project) }
let(:stage) { build(:ci_stage, pipeline: pipeline) }
@@ -54,7 +54,7 @@ describe 'projects/pipelines/_stage' do
context 'when there are multiple builds' do
before do
- HasStatus::AVAILABLE_STATUSES.each do |status|
+ Ci::HasStatus::AVAILABLE_STATUSES.each do |status|
create_build(status)
end
end
@@ -62,7 +62,7 @@ describe 'projects/pipelines/_stage' do
it 'shows them in order' do
render
- expect(rendered).to have_text(HasStatus::ORDERED_STATUSES.join(" "))
+ expect(rendered).to have_text(Ci::HasStatus::ORDERED_STATUSES.join(" "))
end
def create_build(status)
diff --git a/spec/views/projects/services/_form.haml_spec.rb b/spec/views/projects/services/_form.haml_spec.rb
index 720e0aaf450..f5c4e79a082 100644
--- a/spec/views/projects/services/_form.haml_spec.rb
+++ b/spec/views/projects/services/_form.haml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'projects/services/_form' do
+RSpec.describe 'projects/services/_form' do
let(:project) { create(:redmine_project) }
let(:user) { create(:admin) }
diff --git a/spec/views/projects/services/edit.html.haml_spec.rb b/spec/views/projects/services/edit.html.haml_spec.rb
index 12e1cda2c00..785af6a5344 100644
--- a/spec/views/projects/services/edit.html.haml_spec.rb
+++ b/spec/views/projects/services/edit.html.haml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'projects/services/edit' do
+RSpec.describe 'projects/services/edit' do
let(:service) { create(:drone_ci_service, project: project) }
let(:project) { create(:project) }
diff --git a/spec/views/projects/settings/ci_cd/_autodevops_form.html.haml_spec.rb b/spec/views/projects/settings/ci_cd/_autodevops_form.html.haml_spec.rb
index d25860ab301..b3f59a59ac8 100644
--- a/spec/views/projects/settings/ci_cd/_autodevops_form.html.haml_spec.rb
+++ b/spec/views/projects/settings/ci_cd/_autodevops_form.html.haml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'projects/settings/ci_cd/_autodevops_form' do
+RSpec.describe 'projects/settings/ci_cd/_autodevops_form' do
let(:project) { create(:project, :repository) }
before do
diff --git a/spec/views/projects/settings/operations/show.html.haml_spec.rb b/spec/views/projects/settings/operations/show.html.haml_spec.rb
index 7d6faae0f5a..1f0b0ea76bf 100644
--- a/spec/views/projects/settings/operations/show.html.haml_spec.rb
+++ b/spec/views/projects/settings/operations/show.html.haml_spec.rb
@@ -2,9 +2,16 @@
require 'spec_helper'
-describe 'projects/settings/operations/show' do
- let(:project) { create(:project) }
- let(:user) { create(:user) }
+RSpec.describe 'projects/settings/operations/show' do
+ let_it_be(:project) { create(:project) }
+ let_it_be(:user) { create(:user) }
+
+ let(:operations_show_locals) do
+ {
+ prometheus_service: project.find_or_initialize_service('prometheus'),
+ alerts_service: project.find_or_initialize_service('alerts')
+ }
+ end
before do
assign :project, project
@@ -20,13 +27,13 @@ describe 'projects/settings/operations/show' do
allow(view).to receive(:incident_management_available?) { false }
end
- let!(:error_tracking_setting) do
+ let_it_be(:error_tracking_setting) do
create(:project_error_tracking_setting, project: project)
end
context 'Settings page ' do
it 'renders the Operations Settings page' do
- render template: "projects/settings/operations/show", locals: { prometheus_service: project.find_or_initialize_service('prometheus') }
+ render template: 'projects/settings/operations/show', locals: operations_show_locals
expect(rendered).to have_content _('Error Tracking')
expect(rendered).to have_content _('To link Sentry to GitLab, enter your Sentry URL and Auth Token')
diff --git a/spec/views/projects/show.html.haml_spec.rb b/spec/views/projects/show.html.haml_spec.rb
deleted file mode 100644
index c28260f422c..00000000000
--- a/spec/views/projects/show.html.haml_spec.rb
+++ /dev/null
@@ -1,41 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-describe 'projects/show.html.haml' do
- include Devise::Test::ControllerHelpers
-
- let(:user) { create(:admin) }
- let(:project) { create(:project, :repository) }
-
- before do
- presented_project = project.present(current_user: user)
-
- allow(presented_project).to receive(:default_view).and_return('customize_workflow')
- allow(controller).to receive(:current_user).and_return(user)
-
- assign(:project, presented_project)
- end
-
- context 'commit signatures' do
- context 'with vue tree view enabled' do
- it 'are not rendered via js-signature-container' do
- render
-
- expect(rendered).not_to have_css('.js-signature-container')
- end
- end
-
- context 'with vue tree view disabled' do
- before do
- stub_feature_flags(vue_file_list: false)
- end
-
- it 'rendered via js-signature-container' do
- render
-
- expect(rendered).to have_css('.js-signature-container')
- end
- end
- end
-end
diff --git a/spec/views/projects/tags/index.html.haml_spec.rb b/spec/views/projects/tags/index.html.haml_spec.rb
index 08fbada9c9c..4d501b82238 100644
--- a/spec/views/projects/tags/index.html.haml_spec.rb
+++ b/spec/views/projects/tags/index.html.haml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'projects/tags/index.html.haml' do
+RSpec.describe 'projects/tags/index.html.haml' do
let(:project) { create(:project, :repository) }
let(:tags) { TagsFinder.new(project.repository, {}).execute }
let(:git_tag) { project.repository.tags.last }
diff --git a/spec/views/projects/tree/_tree_header.html.haml_spec.rb b/spec/views/projects/tree/_tree_header.html.haml_spec.rb
deleted file mode 100644
index 69ad331f880..00000000000
--- a/spec/views/projects/tree/_tree_header.html.haml_spec.rb
+++ /dev/null
@@ -1,55 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-describe 'projects/tree/_tree_header' do
- let(:project) { create(:project, :repository) }
- let(:current_user) { create(:user) }
- let(:repository) { project.repository }
-
- before do
- stub_feature_flags(vue_file_list: false)
-
- assign(:project, project)
- assign(:repository, repository)
- assign(:id, File.join('master', ''))
- assign(:ref, 'master')
-
- allow(view).to receive(:current_user).and_return(current_user)
- allow(view).to receive(:can_collaborate_with_project?) { true }
- end
-
- it 'renders the WebIDE button when user can collaborate but not create fork or MR' do
- allow(view).to receive(:can?) { false }
-
- render
-
- expect(rendered).to have_link('Web IDE')
- end
-
- it 'renders the WebIDE button when user can create fork and can open MR in project' do
- allow(view).to receive(:can?) { true }
-
- render
-
- expect(rendered).to have_link('Web IDE')
- end
-
- it 'opens a popup confirming a fork if the user can create fork/MR but cannot collaborate with the project' do
- allow(view).to receive(:can?) { true }
- allow(view).to receive(:can_collaborate_with_project?) { false }
-
- render
-
- expect(rendered).to have_link('Web IDE', href: '#modal-confirm-fork')
- end
-
- it 'does not render the WebIDE button when user cannot collaborate or create mr' do
- allow(view).to receive(:can?) { false }
- allow(view).to receive(:can_collaborate_with_project?) { false }
-
- render
-
- expect(rendered).not_to have_link('Web IDE')
- end
-end
diff --git a/spec/views/projects/tree/_tree_row.html.haml_spec.rb b/spec/views/projects/tree/_tree_row.html.haml_spec.rb
index 864272fc146..43a37934afd 100644
--- a/spec/views/projects/tree/_tree_row.html.haml_spec.rb
+++ b/spec/views/projects/tree/_tree_row.html.haml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'projects/tree/_tree_row' do
+RSpec.describe 'projects/tree/_tree_row' do
let(:project) { create(:project, :repository) }
let(:repository) { project.repository }
diff --git a/spec/views/projects/tree/show.html.haml_spec.rb b/spec/views/projects/tree/show.html.haml_spec.rb
index 8c6b229247d..bdf9b08d8f5 100644
--- a/spec/views/projects/tree/show.html.haml_spec.rb
+++ b/spec/views/projects/tree/show.html.haml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'projects/tree/show' do
+RSpec.describe 'projects/tree/show' do
include Devise::Test::ControllerHelpers
let(:project) { create(:project, :repository) }
@@ -39,26 +39,4 @@ describe 'projects/tree/show' do
expect(rendered).to have_css('.js-project-refs-dropdown .dropdown-toggle-text', text: ref)
end
end
-
- context 'commit signatures' do
- context 'with vue tree view disabled' do
- before do
- stub_feature_flags(vue_file_list: false)
- end
-
- it 'rendered via js-signature-container' do
- render
-
- expect(rendered).to have_css('.js-signature-container')
- end
- end
-
- context 'with vue tree view enabled' do
- it 'are not rendered via js-signature-container' do
- render
-
- expect(rendered).not_to have_css('.js-signature-container')
- end
- end
- end
end
diff --git a/spec/views/search/_filter.html.haml_spec.rb b/spec/views/search/_filter.html.haml_spec.rb
index d2cd636f8c6..eb32528e3c7 100644
--- a/spec/views/search/_filter.html.haml_spec.rb
+++ b/spec/views/search/_filter.html.haml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'search/_filter' do
+RSpec.describe 'search/_filter' do
context 'when the search page is opened' do
it 'displays the correct elements' do
render
diff --git a/spec/views/search/_form.html.haml_spec.rb b/spec/views/search/_form.html.haml_spec.rb
index 69f40895d86..073a39e4ed6 100644
--- a/spec/views/search/_form.html.haml_spec.rb
+++ b/spec/views/search/_form.html.haml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'search/_form' do
+RSpec.describe 'search/_form' do
context 'when the search page is opened' do
it 'displays the correct elements' do
render
diff --git a/spec/views/search/_results.html.haml_spec.rb b/spec/views/search/_results.html.haml_spec.rb
index 3243758c650..cd7a3559538 100644
--- a/spec/views/search/_results.html.haml_spec.rb
+++ b/spec/views/search/_results.html.haml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'search/_results' do
+RSpec.describe 'search/_results' do
before do
controller.params[:action] = 'show'
diff --git a/spec/views/search/show.html.haml_spec.rb b/spec/views/search/show.html.haml_spec.rb
index 483b913f2cc..9ddfe08c8f3 100644
--- a/spec/views/search/show.html.haml_spec.rb
+++ b/spec/views/search/show.html.haml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'search/show' do
+RSpec.describe 'search/show' do
let(:search_term) { nil }
before do
diff --git a/spec/views/shared/_label_row.html.haml_spec.rb b/spec/views/shared/_label_row.html.haml_spec.rb
index 0764f8480c8..1e2ed41bafc 100644
--- a/spec/views/shared/_label_row.html.haml_spec.rb
+++ b/spec/views/shared/_label_row.html.haml_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe 'shared/_label_row.html.haml' do
+RSpec.describe 'shared/_label_row.html.haml' do
label_types = {
'project label': :label,
'group label': :group_label
diff --git a/spec/views/shared/milestones/_issuable.html.haml_spec.rb b/spec/views/shared/milestones/_issuable.html.haml_spec.rb
index 6e81fec79d4..46f79f60bbe 100644
--- a/spec/views/shared/milestones/_issuable.html.haml_spec.rb
+++ b/spec/views/shared/milestones/_issuable.html.haml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'shared/milestones/_issuable.html.haml' do
+RSpec.describe 'shared/milestones/_issuable.html.haml' do
let_it_be(:project) { create(:project) }
let_it_be(:user) { create(:user) }
let_it_be(:milestone) { create(:milestone, project: project) }
diff --git a/spec/views/shared/milestones/_issuables.html.haml_spec.rb b/spec/views/shared/milestones/_issuables.html.haml_spec.rb
index f77c14a687b..70ab6914580 100644
--- a/spec/views/shared/milestones/_issuables.html.haml_spec.rb
+++ b/spec/views/shared/milestones/_issuables.html.haml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'shared/milestones/_issuables.html.haml' do
+RSpec.describe 'shared/milestones/_issuables.html.haml' do
let(:issuables_size) { 100 }
before do
diff --git a/spec/views/shared/milestones/_top.html.haml_spec.rb b/spec/views/shared/milestones/_top.html.haml_spec.rb
index 2d72e278706..1aa971709f1 100644
--- a/spec/views/shared/milestones/_top.html.haml_spec.rb
+++ b/spec/views/shared/milestones/_top.html.haml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'shared/milestones/_top.html.haml' do
+RSpec.describe 'shared/milestones/_top.html.haml' do
let_it_be(:group) { create(:group) }
let(:project) { create(:project, group: group) }
let(:milestone) { create(:milestone, project: project) }
diff --git a/spec/views/shared/notes/_form.html.haml_spec.rb b/spec/views/shared/notes/_form.html.haml_spec.rb
index d354c2f0100..b7bad4c5d78 100644
--- a/spec/views/shared/notes/_form.html.haml_spec.rb
+++ b/spec/views/shared/notes/_form.html.haml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'shared/notes/_form' do
+RSpec.describe 'shared/notes/_form' do
include Devise::Test::ControllerHelpers
let(:user) { create(:user) }
diff --git a/spec/views/shared/projects/_list.html.haml_spec.rb b/spec/views/shared/projects/_list.html.haml_spec.rb
index d6043921fc8..037f988257b 100644
--- a/spec/views/shared/projects/_list.html.haml_spec.rb
+++ b/spec/views/shared/projects/_list.html.haml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'shared/projects/_list' do
+RSpec.describe 'shared/projects/_list' do
let(:group) { create(:group) }
before do
diff --git a/spec/views/shared/projects/_project.html.haml_spec.rb b/spec/views/shared/projects/_project.html.haml_spec.rb
index 8c3b8768469..62f23338c48 100644
--- a/spec/views/shared/projects/_project.html.haml_spec.rb
+++ b/spec/views/shared/projects/_project.html.haml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'shared/projects/_project.html.haml' do
+RSpec.describe 'shared/projects/_project.html.haml' do
let_it_be(:project) { create(:project) }
before do
diff --git a/spec/views/shared/runners/show.html.haml_spec.rb b/spec/views/shared/runners/show.html.haml_spec.rb
index 5e92928b143..5e2812eb48a 100644
--- a/spec/views/shared/runners/show.html.haml_spec.rb
+++ b/spec/views/shared/runners/show.html.haml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'shared/runners/show.html.haml' do
+RSpec.describe 'shared/runners/show.html.haml' do
include PageLayoutHelper
let(:runner) do
diff --git a/spec/workers/admin_email_worker_spec.rb b/spec/workers/admin_email_worker_spec.rb
index f72b932423f..1a5cb90bc17 100644
--- a/spec/workers/admin_email_worker_spec.rb
+++ b/spec/workers/admin_email_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe AdminEmailWorker do
+RSpec.describe AdminEmailWorker do
subject(:worker) { described_class.new }
describe '.perform' do
diff --git a/spec/workers/archive_trace_worker_spec.rb b/spec/workers/archive_trace_worker_spec.rb
index 44f7be15201..a9f256b1b3b 100644
--- a/spec/workers/archive_trace_worker_spec.rb
+++ b/spec/workers/archive_trace_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ArchiveTraceWorker do
+RSpec.describe ArchiveTraceWorker do
describe '#perform' do
subject { described_class.new.perform(job&.id) }
diff --git a/spec/workers/authorized_keys_worker_spec.rb b/spec/workers/authorized_keys_worker_spec.rb
index 4f1dde0bfc0..50236f9ea7b 100644
--- a/spec/workers/authorized_keys_worker_spec.rb
+++ b/spec/workers/authorized_keys_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe AuthorizedKeysWorker do
+RSpec.describe AuthorizedKeysWorker do
let(:worker) { described_class.new }
describe '#perform' do
diff --git a/spec/workers/authorized_project_update/periodic_recalculate_worker_spec.rb b/spec/workers/authorized_project_update/periodic_recalculate_worker_spec.rb
index fcd073953b6..2d633828ae3 100644
--- a/spec/workers/authorized_project_update/periodic_recalculate_worker_spec.rb
+++ b/spec/workers/authorized_project_update/periodic_recalculate_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe AuthorizedProjectUpdate::PeriodicRecalculateWorker do
+RSpec.describe AuthorizedProjectUpdate::PeriodicRecalculateWorker do
describe '#perform' do
it 'calls AuthorizedProjectUpdate::PeriodicRecalculateService' do
expect_next_instance_of(AuthorizedProjectUpdate::PeriodicRecalculateService) do |service|
diff --git a/spec/workers/authorized_project_update/project_create_worker_spec.rb b/spec/workers/authorized_project_update/project_create_worker_spec.rb
index 5ebfb60bc79..5226ab30de7 100644
--- a/spec/workers/authorized_project_update/project_create_worker_spec.rb
+++ b/spec/workers/authorized_project_update/project_create_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe AuthorizedProjectUpdate::ProjectCreateWorker do
+RSpec.describe AuthorizedProjectUpdate::ProjectCreateWorker do
let_it_be(:group) { create(:group, :private) }
let_it_be(:group_project) { create(:project, group: group) }
let_it_be(:group_user) { create(:user) }
@@ -27,7 +27,7 @@ describe AuthorizedProjectUpdate::ProjectCreateWorker do
context 'idempotence' do
before do
- create(:group_member, access_level: Gitlab::Access::MAINTAINER, group: group, user: group_user)
+ create(:group_member, access_level: access_level, group: group, user: group_user)
ProjectAuthorization.delete_all
end
diff --git a/spec/workers/authorized_project_update/project_group_link_create_worker_spec.rb b/spec/workers/authorized_project_update/project_group_link_create_worker_spec.rb
new file mode 100644
index 00000000000..7c4ad4ce641
--- /dev/null
+++ b/spec/workers/authorized_project_update/project_group_link_create_worker_spec.rb
@@ -0,0 +1,52 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe AuthorizedProjectUpdate::ProjectGroupLinkCreateWorker do
+ let_it_be(:group) { create(:group, :private) }
+ let_it_be(:group_project) { create(:project, group: group) }
+ let_it_be(:shared_with_group) { create(:group, :private) }
+ let_it_be(:user) { create(:user) }
+
+ let(:access_level) { Gitlab::Access::MAINTAINER }
+
+ subject(:worker) { described_class.new }
+
+ it 'calls AuthorizedProjectUpdate::ProjectCreateService' do
+ expect_next_instance_of(AuthorizedProjectUpdate::ProjectGroupLinkCreateService) do |service|
+ expect(service).to(receive(:execute))
+ end
+
+ worker.perform(group_project.id, shared_with_group.id)
+ end
+
+ it 'returns ServiceResponse.success' do
+ result = worker.perform(group_project.id, shared_with_group.id)
+
+ expect(result.success?).to be_truthy
+ end
+
+ context 'idempotence' do
+ before do
+ create(:group_member, group: shared_with_group, user: user, access_level: access_level)
+ create(:project_group_link, project: group_project, group: shared_with_group)
+ ProjectAuthorization.delete_all
+ end
+
+ include_examples 'an idempotent worker' do
+ let(:job_args) { [group_project.id, shared_with_group.id] }
+
+ it 'creates project authorization' do
+ subject
+
+ project_authorization = ProjectAuthorization.where(
+ project_id: group_project.id,
+ user_id: user.id,
+ access_level: access_level)
+
+ expect(project_authorization).to exist
+ expect(ProjectAuthorization.count).to eq(1)
+ end
+ end
+ end
+end
diff --git a/spec/workers/authorized_project_update/user_refresh_over_user_range_worker_spec.rb b/spec/workers/authorized_project_update/user_refresh_over_user_range_worker_spec.rb
index 5d1c405dfd0..c49e4c453bf 100644
--- a/spec/workers/authorized_project_update/user_refresh_over_user_range_worker_spec.rb
+++ b/spec/workers/authorized_project_update/user_refresh_over_user_range_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe AuthorizedProjectUpdate::UserRefreshOverUserRangeWorker do
+RSpec.describe AuthorizedProjectUpdate::UserRefreshOverUserRangeWorker do
let(:start_user_id) { 42 }
let(:end_user_id) { 4242 }
diff --git a/spec/workers/authorized_project_update/user_refresh_with_low_urgency_worker_spec.rb b/spec/workers/authorized_project_update/user_refresh_with_low_urgency_worker_spec.rb
index fa029dae0fa..bd16eeb4712 100644
--- a/spec/workers/authorized_project_update/user_refresh_with_low_urgency_worker_spec.rb
+++ b/spec/workers/authorized_project_update/user_refresh_with_low_urgency_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe AuthorizedProjectUpdate::UserRefreshWithLowUrgencyWorker do
+RSpec.describe AuthorizedProjectUpdate::UserRefreshWithLowUrgencyWorker do
it 'is labeled as low urgency' do
expect(described_class.get_urgency).to eq(:low)
end
diff --git a/spec/workers/authorized_projects_worker_spec.rb b/spec/workers/authorized_projects_worker_spec.rb
index 93f22471c56..fbfde77be97 100644
--- a/spec/workers/authorized_projects_worker_spec.rb
+++ b/spec/workers/authorized_projects_worker_spec.rb
@@ -2,6 +2,6 @@
require 'spec_helper'
-describe AuthorizedProjectsWorker do
+RSpec.describe AuthorizedProjectsWorker do
it_behaves_like "refreshes user's project authorizations"
end
diff --git a/spec/workers/auto_devops/disable_worker_spec.rb b/spec/workers/auto_devops/disable_worker_spec.rb
index 53113f286ba..239f4b09f5c 100644
--- a/spec/workers/auto_devops/disable_worker_spec.rb
+++ b/spec/workers/auto_devops/disable_worker_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe AutoDevops::DisableWorker, '#perform' do
+RSpec.describe AutoDevops::DisableWorker, '#perform' do
let(:user) { create(:user, developer_projects: [project]) }
let(:project) { create(:project, :repository, :auto_devops) }
let(:auto_devops) { project.auto_devops }
diff --git a/spec/workers/auto_merge_process_worker_spec.rb b/spec/workers/auto_merge_process_worker_spec.rb
index 616727ce5ca..00d27d9c2b5 100644
--- a/spec/workers/auto_merge_process_worker_spec.rb
+++ b/spec/workers/auto_merge_process_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe AutoMergeProcessWorker do
+RSpec.describe AutoMergeProcessWorker do
describe '#perform' do
subject { described_class.new.perform(merge_request&.id) }
diff --git a/spec/workers/background_migration_worker_spec.rb b/spec/workers/background_migration_worker_spec.rb
index 2b2ffc9f5b9..15e93d62c7d 100644
--- a/spec/workers/background_migration_worker_spec.rb
+++ b/spec/workers/background_migration_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe BackgroundMigrationWorker, :clean_gitlab_redis_shared_state do
+RSpec.describe BackgroundMigrationWorker, :clean_gitlab_redis_shared_state do
let(:worker) { described_class.new }
describe '.minimum_interval' do
diff --git a/spec/workers/build_coverage_worker_spec.rb b/spec/workers/build_coverage_worker_spec.rb
index 25686ae68ca..4beb6e5972f 100644
--- a/spec/workers/build_coverage_worker_spec.rb
+++ b/spec/workers/build_coverage_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe BuildCoverageWorker do
+RSpec.describe BuildCoverageWorker do
describe '#perform' do
context 'when build exists' do
let!(:build) { create(:ci_build) }
diff --git a/spec/workers/build_finished_worker_spec.rb b/spec/workers/build_finished_worker_spec.rb
index 849563d9608..e7f7ae84621 100644
--- a/spec/workers/build_finished_worker_spec.rb
+++ b/spec/workers/build_finished_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe BuildFinishedWorker do
+RSpec.describe BuildFinishedWorker do
subject { described_class.new.perform(build.id) }
describe '#perform' do
diff --git a/spec/workers/build_hooks_worker_spec.rb b/spec/workers/build_hooks_worker_spec.rb
index 59b252a8be3..aefbd7e590e 100644
--- a/spec/workers/build_hooks_worker_spec.rb
+++ b/spec/workers/build_hooks_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe BuildHooksWorker do
+RSpec.describe BuildHooksWorker do
describe '#perform' do
context 'when build exists' do
let!(:build) { create(:ci_build) }
diff --git a/spec/workers/build_success_worker_spec.rb b/spec/workers/build_success_worker_spec.rb
index f4ff8cb15f8..0583d79ed46 100644
--- a/spec/workers/build_success_worker_spec.rb
+++ b/spec/workers/build_success_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe BuildSuccessWorker do
+RSpec.describe BuildSuccessWorker do
describe '#perform' do
subject { described_class.new.perform(build.id) }
diff --git a/spec/workers/build_trace_sections_worker_spec.rb b/spec/workers/build_trace_sections_worker_spec.rb
index 97fc0a2da0c..1c84b0083fb 100644
--- a/spec/workers/build_trace_sections_worker_spec.rb
+++ b/spec/workers/build_trace_sections_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe BuildTraceSectionsWorker do
+RSpec.describe BuildTraceSectionsWorker do
describe '#perform' do
context 'when build exists' do
let!(:build) { create(:ci_build) }
diff --git a/spec/workers/chat_notification_worker_spec.rb b/spec/workers/chat_notification_worker_spec.rb
index e4dccf2bf6b..a20a136d197 100644
--- a/spec/workers/chat_notification_worker_spec.rb
+++ b/spec/workers/chat_notification_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ChatNotificationWorker do
+RSpec.describe ChatNotificationWorker do
let(:worker) { described_class.new }
let(:chat_build) do
create(:ci_build, pipeline: create(:ci_pipeline, source: :chat))
diff --git a/spec/workers/ci/archive_traces_cron_worker_spec.rb b/spec/workers/ci/archive_traces_cron_worker_spec.rb
index 789e83783bb..14abe819587 100644
--- a/spec/workers/ci/archive_traces_cron_worker_spec.rb
+++ b/spec/workers/ci/archive_traces_cron_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::ArchiveTracesCronWorker do
+RSpec.describe Ci::ArchiveTracesCronWorker do
subject { described_class.new.perform }
let(:finished_at) { 1.day.ago }
diff --git a/spec/workers/ci/build_prepare_worker_spec.rb b/spec/workers/ci/build_prepare_worker_spec.rb
index 9f76696ee66..b2c74a920ea 100644
--- a/spec/workers/ci/build_prepare_worker_spec.rb
+++ b/spec/workers/ci/build_prepare_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::BuildPrepareWorker do
+RSpec.describe Ci::BuildPrepareWorker do
subject { described_class.new.perform(build_id) }
context 'build exists' do
diff --git a/spec/workers/ci/build_report_result_worker_spec.rb b/spec/workers/ci/build_report_result_worker_spec.rb
index 290a98366b4..aedfa70c8b8 100644
--- a/spec/workers/ci/build_report_result_worker_spec.rb
+++ b/spec/workers/ci/build_report_result_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::BuildReportResultWorker do
+RSpec.describe Ci::BuildReportResultWorker do
subject { described_class.new.perform(build_id) }
context 'when build exists' do
diff --git a/spec/workers/ci/build_schedule_worker_spec.rb b/spec/workers/ci/build_schedule_worker_spec.rb
index 647f9763fed..f8b4efc562b 100644
--- a/spec/workers/ci/build_schedule_worker_spec.rb
+++ b/spec/workers/ci/build_schedule_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::BuildScheduleWorker do
+RSpec.describe Ci::BuildScheduleWorker do
subject { described_class.new.perform(build.id) }
context 'when build is found' do
diff --git a/spec/workers/ci/create_cross_project_pipeline_worker_spec.rb b/spec/workers/ci/create_cross_project_pipeline_worker_spec.rb
index 492f5e812ee..95dcf5624cc 100644
--- a/spec/workers/ci/create_cross_project_pipeline_worker_spec.rb
+++ b/spec/workers/ci/create_cross_project_pipeline_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::CreateCrossProjectPipelineWorker do
+RSpec.describe Ci::CreateCrossProjectPipelineWorker do
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project) }
let_it_be(:pipeline) { create(:ci_pipeline, project: project) }
diff --git a/spec/workers/ci/daily_build_group_report_results_worker_spec.rb b/spec/workers/ci/daily_build_group_report_results_worker_spec.rb
index d9706982a62..e13c6311e46 100644
--- a/spec/workers/ci/daily_build_group_report_results_worker_spec.rb
+++ b/spec/workers/ci/daily_build_group_report_results_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::DailyBuildGroupReportResultsWorker do
+RSpec.describe Ci::DailyBuildGroupReportResultsWorker do
describe '#perform' do
let!(:pipeline) { create(:ci_pipeline) }
diff --git a/spec/workers/ci/pipeline_bridge_status_worker_spec.rb b/spec/workers/ci/pipeline_bridge_status_worker_spec.rb
index a6129b2cf93..6ec5eb0e639 100644
--- a/spec/workers/ci/pipeline_bridge_status_worker_spec.rb
+++ b/spec/workers/ci/pipeline_bridge_status_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::PipelineBridgeStatusWorker do
+RSpec.describe Ci::PipelineBridgeStatusWorker do
describe '#perform' do
subject { described_class.new.perform(pipeline_id) }
diff --git a/spec/workers/ci/pipeline_success_unlock_artifacts_worker_spec.rb b/spec/workers/ci/pipeline_success_unlock_artifacts_worker_spec.rb
new file mode 100644
index 00000000000..cb2cf58d50b
--- /dev/null
+++ b/spec/workers/ci/pipeline_success_unlock_artifacts_worker_spec.rb
@@ -0,0 +1,67 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Ci::PipelineSuccessUnlockArtifactsWorker do
+ describe '#perform' do
+ subject(:perform) { described_class.new.perform(pipeline_id) }
+
+ include_examples 'an idempotent worker' do
+ subject(:idempotent_perform) { perform_multiple(pipeline.id, exec_times: 2) }
+
+ let!(:older_pipeline) do
+ create(:ci_pipeline, :success, :with_job, locked: :artifacts_locked).tap do |pipeline|
+ create(:ci_job_artifact, job: pipeline.builds.first)
+ end
+ end
+
+ let!(:pipeline) do
+ create(:ci_pipeline, :success, :with_job, ref: older_pipeline.ref, tag: older_pipeline.tag, project: older_pipeline.project, locked: :unlocked).tap do |pipeline|
+ create(:ci_job_artifact, job: pipeline.builds.first)
+ end
+ end
+
+ it 'unlocks the artifacts from older pipelines' do
+ expect { idempotent_perform }.to change { older_pipeline.reload.locked }.from('artifacts_locked').to('unlocked')
+ end
+ end
+
+ context 'when pipeline exists' do
+ let(:pipeline) { create(:ci_pipeline, :success, :with_job) }
+ let(:pipeline_id) { pipeline.id }
+
+ context 'when pipeline has artifacts' do
+ before do
+ create(:ci_job_artifact, job: pipeline.builds.first)
+ end
+
+ it 'calls the service' do
+ service = spy(Ci::UnlockArtifactsService)
+ expect(Ci::UnlockArtifactsService).to receive(:new).and_return(service)
+
+ perform
+
+ expect(service).to have_received(:execute)
+ end
+ end
+
+ context 'when pipeline does not have artifacts' do
+ it 'does not call service' do
+ expect(Ci::UnlockArtifactsService).not_to receive(:new)
+
+ perform
+ end
+ end
+ end
+
+ context 'when pipeline does not exist' do
+ let(:pipeline_id) { non_existing_record_id }
+
+ it 'does not call service' do
+ expect(Ci::UnlockArtifactsService).not_to receive(:new)
+
+ perform
+ end
+ end
+ end
+end
diff --git a/spec/workers/ci/ref_delete_unlock_artifacts_worker_spec.rb b/spec/workers/ci/ref_delete_unlock_artifacts_worker_spec.rb
new file mode 100644
index 00000000000..d9f2dd326dd
--- /dev/null
+++ b/spec/workers/ci/ref_delete_unlock_artifacts_worker_spec.rb
@@ -0,0 +1,78 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Ci::RefDeleteUnlockArtifactsWorker do
+ describe '#perform' do
+ subject(:perform) { described_class.new.perform(project_id, user_id, ref) }
+
+ let(:ref) { 'refs/heads/master' }
+
+ let(:project) { create(:project) }
+
+ include_examples 'an idempotent worker' do
+ subject(:idempotent_perform) { perform_multiple([project_id, user_id, ref], exec_times: 2) }
+
+ let(:project_id) { project.id }
+ let(:user_id) { project.creator.id }
+
+ let(:pipeline) { create(:ci_pipeline, ref: 'master', project: project, locked: :artifacts_locked) }
+
+ it 'unlocks the artifacts from older pipelines' do
+ expect { idempotent_perform }.to change { pipeline.reload.locked }.from('artifacts_locked').to('unlocked')
+ end
+ end
+
+ context 'when project exists' do
+ let(:project_id) { project.id }
+
+ context 'when user exists' do
+ let(:user_id) { project.creator.id }
+
+ context 'when ci ref exists' do
+ before do
+ create(:ci_ref, ref_path: ref)
+ end
+
+ it 'calls the service' do
+ service = spy(Ci::UnlockArtifactsService)
+ expect(Ci::UnlockArtifactsService).to receive(:new).and_return(service)
+
+ perform
+
+ expect(service).to have_received(:execute)
+ end
+ end
+
+ context 'when ci ref does not exist' do
+ it 'does not call the service' do
+ expect(Ci::UnlockArtifactsService).not_to receive(:new)
+
+ perform
+ end
+ end
+ end
+
+ context 'when user does not exist' do
+ let(:user_id) { non_existing_record_id }
+
+ it 'does not call service' do
+ expect(Ci::UnlockArtifactsService).not_to receive(:new)
+
+ perform
+ end
+ end
+ end
+
+ context 'when project does not exist' do
+ let(:project_id) { non_existing_record_id }
+ let(:user_id) { project.creator.id }
+
+ it 'does not call service' do
+ expect(Ci::UnlockArtifactsService).not_to receive(:new)
+
+ perform
+ end
+ end
+ end
+end
diff --git a/spec/workers/ci/resource_groups/assign_resource_from_resource_group_worker_spec.rb b/spec/workers/ci/resource_groups/assign_resource_from_resource_group_worker_spec.rb
index 634d932121e..f9914a7cecb 100644
--- a/spec/workers/ci/resource_groups/assign_resource_from_resource_group_worker_spec.rb
+++ b/spec/workers/ci/resource_groups/assign_resource_from_resource_group_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::ResourceGroups::AssignResourceFromResourceGroupWorker do
+RSpec.describe Ci::ResourceGroups::AssignResourceFromResourceGroupWorker do
let(:worker) { described_class.new }
describe '#perform' do
diff --git a/spec/workers/cleanup_container_repository_worker_spec.rb b/spec/workers/cleanup_container_repository_worker_spec.rb
index 1228c2c2d9c..0545f7a35e4 100644
--- a/spec/workers/cleanup_container_repository_worker_spec.rb
+++ b/spec/workers/cleanup_container_repository_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe CleanupContainerRepositoryWorker, :clean_gitlab_redis_shared_state do
+RSpec.describe CleanupContainerRepositoryWorker, :clean_gitlab_redis_shared_state do
let(:repository) { create(:container_repository) }
let(:project) { repository.project }
let(:user) { project.owner }
diff --git a/spec/workers/cluster_configure_istio_worker_spec.rb b/spec/workers/cluster_configure_istio_worker_spec.rb
index 0f02d428ced..5d949fde973 100644
--- a/spec/workers/cluster_configure_istio_worker_spec.rb
+++ b/spec/workers/cluster_configure_istio_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ClusterConfigureIstioWorker do
+RSpec.describe ClusterConfigureIstioWorker do
describe '#perform' do
shared_examples 'configure istio service' do
it 'configures istio' do
diff --git a/spec/workers/cluster_provision_worker_spec.rb b/spec/workers/cluster_provision_worker_spec.rb
index 608639331fd..2d6ca4ab7e3 100644
--- a/spec/workers/cluster_provision_worker_spec.rb
+++ b/spec/workers/cluster_provision_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ClusterProvisionWorker do
+RSpec.describe ClusterProvisionWorker do
describe '#perform' do
context 'when provider type is gcp' do
let(:cluster) { create(:cluster, provider_type: :gcp, provider_gcp: provider) }
diff --git a/spec/workers/cluster_update_app_worker_spec.rb b/spec/workers/cluster_update_app_worker_spec.rb
index 5391c194679..c24f40024fd 100644
--- a/spec/workers/cluster_update_app_worker_spec.rb
+++ b/spec/workers/cluster_update_app_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ClusterUpdateAppWorker do
+RSpec.describe ClusterUpdateAppWorker do
include ExclusiveLeaseHelpers
let_it_be(:project) { create(:project) }
@@ -12,7 +12,7 @@ describe ClusterUpdateAppWorker do
subject { described_class.new }
around do |example|
- Timecop.freeze(Time.now) { example.run }
+ Timecop.freeze(Time.current) { example.run }
end
before do
@@ -22,11 +22,11 @@ describe ClusterUpdateAppWorker do
describe '#perform' do
context 'when the application last_update_started_at is higher than the time the job was scheduled in' do
it 'does nothing' do
- application = create(:clusters_applications_prometheus, :updated, last_update_started_at: Time.now)
+ application = create(:clusters_applications_prometheus, :updated, last_update_started_at: Time.current)
expect(prometheus_update_service).not_to receive(:execute)
- expect(subject.perform(application.name, application.id, project.id, Time.now - 5.minutes)).to be_nil
+ expect(subject.perform(application.name, application.id, project.id, Time.current - 5.minutes)).to be_nil
end
end
@@ -34,7 +34,7 @@ describe ClusterUpdateAppWorker do
it 'returns nil' do
application = create(:clusters_applications_prometheus, :updating)
- expect(subject.perform(application.name, application.id, project.id, Time.now)).to be_nil
+ expect(subject.perform(application.name, application.id, project.id, Time.current)).to be_nil
end
end
@@ -43,7 +43,7 @@ describe ClusterUpdateAppWorker do
expect(prometheus_update_service).to receive(:execute)
- subject.perform(application.name, application.id, project.id, Time.now)
+ subject.perform(application.name, application.id, project.id, Time.current)
end
context 'with exclusive lease' do
@@ -60,7 +60,7 @@ describe ClusterUpdateAppWorker do
it 'does not allow same app to be updated concurrently by same project' do
expect(Clusters::Applications::PrometheusUpdateService).not_to receive(:new)
- subject.perform(application.name, application.id, project.id, Time.now)
+ subject.perform(application.name, application.id, project.id, Time.current)
end
it 'does not allow same app to be updated concurrently by different project', :aggregate_failures do
@@ -68,7 +68,7 @@ describe ClusterUpdateAppWorker do
expect(Clusters::Applications::PrometheusUpdateService).not_to receive(:new)
- subject.perform(application.name, application.id, project1.id, Time.now)
+ subject.perform(application.name, application.id, project1.id, Time.current)
end
it 'allows different app to be updated concurrently by same project' do
@@ -80,7 +80,7 @@ describe ClusterUpdateAppWorker do
expect(Clusters::Applications::PrometheusUpdateService).to receive(:new)
.with(application2, project)
- subject.perform(application2.name, application2.id, project.id, Time.now)
+ subject.perform(application2.name, application2.id, project.id, Time.current)
end
it 'allows different app to be updated by different project', :aggregate_failures do
@@ -94,7 +94,7 @@ describe ClusterUpdateAppWorker do
expect(Clusters::Applications::PrometheusUpdateService).to receive(:new)
.with(application2, project2)
- subject.perform(application2.name, application2.id, project2.id, Time.now)
+ subject.perform(application2.name, application2.id, project2.id, Time.current)
end
end
end
diff --git a/spec/workers/cluster_wait_for_app_update_worker_spec.rb b/spec/workers/cluster_wait_for_app_update_worker_spec.rb
index f1206bd85cb..b7f7622a0e6 100644
--- a/spec/workers/cluster_wait_for_app_update_worker_spec.rb
+++ b/spec/workers/cluster_wait_for_app_update_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ClusterWaitForAppUpdateWorker do
+RSpec.describe ClusterWaitForAppUpdateWorker do
let(:check_upgrade_progress_service) { spy }
before do
diff --git a/spec/workers/cluster_wait_for_ingress_ip_address_worker_spec.rb b/spec/workers/cluster_wait_for_ingress_ip_address_worker_spec.rb
index a9ffdfb085e..7a42c988a92 100644
--- a/spec/workers/cluster_wait_for_ingress_ip_address_worker_spec.rb
+++ b/spec/workers/cluster_wait_for_ingress_ip_address_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ClusterWaitForIngressIpAddressWorker do
+RSpec.describe ClusterWaitForIngressIpAddressWorker do
describe '#perform' do
let(:service) { instance_double(Clusters::Applications::CheckIngressIpAddressService, execute: true) }
let(:application) { instance_double(Clusters::Applications::Ingress) }
diff --git a/spec/workers/clusters/applications/activate_service_worker_spec.rb b/spec/workers/clusters/applications/activate_service_worker_spec.rb
index 09f4c536edf..c157c57888e 100644
--- a/spec/workers/clusters/applications/activate_service_worker_spec.rb
+++ b/spec/workers/clusters/applications/activate_service_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Clusters::Applications::ActivateServiceWorker, '#perform' do
+RSpec.describe Clusters::Applications::ActivateServiceWorker, '#perform' do
context 'cluster exists' do
describe 'prometheus service' do
let(:service_name) { 'prometheus' }
diff --git a/spec/workers/clusters/applications/check_prometheus_health_worker_spec.rb b/spec/workers/clusters/applications/check_prometheus_health_worker_spec.rb
index a09b9ec4165..5a37031a55a 100644
--- a/spec/workers/clusters/applications/check_prometheus_health_worker_spec.rb
+++ b/spec/workers/clusters/applications/check_prometheus_health_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Clusters::Applications::CheckPrometheusHealthWorker, '#perform' do
+RSpec.describe Clusters::Applications::CheckPrometheusHealthWorker, '#perform' do
subject { described_class.new.perform }
it 'triggers health service' do
diff --git a/spec/workers/clusters/applications/deactivate_service_worker_spec.rb b/spec/workers/clusters/applications/deactivate_service_worker_spec.rb
index 809843a1bc8..18cceaaf3b1 100644
--- a/spec/workers/clusters/applications/deactivate_service_worker_spec.rb
+++ b/spec/workers/clusters/applications/deactivate_service_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Clusters::Applications::DeactivateServiceWorker, '#perform' do
+RSpec.describe Clusters::Applications::DeactivateServiceWorker, '#perform' do
context 'cluster exists' do
describe 'prometheus service' do
let(:service_name) { 'prometheus' }
diff --git a/spec/workers/clusters/applications/wait_for_uninstall_app_worker_spec.rb b/spec/workers/clusters/applications/wait_for_uninstall_app_worker_spec.rb
index aaf5c9defc4..0191a2898b2 100644
--- a/spec/workers/clusters/applications/wait_for_uninstall_app_worker_spec.rb
+++ b/spec/workers/clusters/applications/wait_for_uninstall_app_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Clusters::Applications::WaitForUninstallAppWorker, '#perform' do
+RSpec.describe Clusters::Applications::WaitForUninstallAppWorker, '#perform' do
let(:app) { create(:clusters_applications_helm) }
let(:app_name) { app.name }
let(:app_id) { app.id }
diff --git a/spec/workers/clusters/cleanup/app_worker_spec.rb b/spec/workers/clusters/cleanup/app_worker_spec.rb
index 29c00db8079..661468f037f 100644
--- a/spec/workers/clusters/cleanup/app_worker_spec.rb
+++ b/spec/workers/clusters/cleanup/app_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Clusters::Cleanup::AppWorker do
+RSpec.describe Clusters::Cleanup::AppWorker do
describe '#perform' do
subject { worker_instance.perform(cluster.id) }
diff --git a/spec/workers/clusters/cleanup/project_namespace_worker_spec.rb b/spec/workers/clusters/cleanup/project_namespace_worker_spec.rb
index 8b6f22e9a61..b9219586a0b 100644
--- a/spec/workers/clusters/cleanup/project_namespace_worker_spec.rb
+++ b/spec/workers/clusters/cleanup/project_namespace_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Clusters::Cleanup::ProjectNamespaceWorker do
+RSpec.describe Clusters::Cleanup::ProjectNamespaceWorker do
describe '#perform' do
context 'when cluster.cleanup_status is cleanup_removing_project_namespaces' do
let!(:cluster) { create(:cluster, :with_environments, :cleanup_removing_project_namespaces) }
diff --git a/spec/workers/clusters/cleanup/service_account_worker_spec.rb b/spec/workers/clusters/cleanup/service_account_worker_spec.rb
index 9af53dd63c1..dabc32a0ccd 100644
--- a/spec/workers/clusters/cleanup/service_account_worker_spec.rb
+++ b/spec/workers/clusters/cleanup/service_account_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Clusters::Cleanup::ServiceAccountWorker do
+RSpec.describe Clusters::Cleanup::ServiceAccountWorker do
describe '#perform' do
let!(:cluster) { create(:cluster, :cleanup_removing_service_account) }
diff --git a/spec/workers/concerns/application_worker_spec.rb b/spec/workers/concerns/application_worker_spec.rb
index 087a36d2bd0..a18b83f199b 100644
--- a/spec/workers/concerns/application_worker_spec.rb
+++ b/spec/workers/concerns/application_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ApplicationWorker do
+RSpec.describe ApplicationWorker do
let_it_be(:worker) do
Class.new do
def self.name
diff --git a/spec/workers/concerns/cluster_queue_spec.rb b/spec/workers/concerns/cluster_queue_spec.rb
index 732d55dfbde..c03ca9cea48 100644
--- a/spec/workers/concerns/cluster_queue_spec.rb
+++ b/spec/workers/concerns/cluster_queue_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ClusterQueue do
+RSpec.describe ClusterQueue do
let(:worker) do
Class.new do
def self.name
diff --git a/spec/workers/concerns/cronjob_queue_spec.rb b/spec/workers/concerns/cronjob_queue_spec.rb
index 0cea67bf116..d1ad5c65ea3 100644
--- a/spec/workers/concerns/cronjob_queue_spec.rb
+++ b/spec/workers/concerns/cronjob_queue_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe CronjobQueue do
+RSpec.describe CronjobQueue do
let(:worker) do
Class.new do
def self.name
diff --git a/spec/workers/concerns/gitlab/github_import/object_importer_spec.rb b/spec/workers/concerns/gitlab/github_import/object_importer_spec.rb
index 51b685b5792..d0cbc6b35e2 100644
--- a/spec/workers/concerns/gitlab/github_import/object_importer_spec.rb
+++ b/spec/workers/concerns/gitlab/github_import/object_importer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GithubImport::ObjectImporter do
+RSpec.describe Gitlab::GithubImport::ObjectImporter do
let(:worker) do
Class.new do
def self.name
diff --git a/spec/workers/concerns/gitlab/github_import/queue_spec.rb b/spec/workers/concerns/gitlab/github_import/queue_spec.rb
index d262bc2e05c..beca221b593 100644
--- a/spec/workers/concerns/gitlab/github_import/queue_spec.rb
+++ b/spec/workers/concerns/gitlab/github_import/queue_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GithubImport::Queue do
+RSpec.describe Gitlab::GithubImport::Queue do
it 'sets the Sidekiq options for the worker' do
worker = Class.new do
def self.name
diff --git a/spec/workers/concerns/gitlab/github_import/rescheduling_methods_spec.rb b/spec/workers/concerns/gitlab/github_import/rescheduling_methods_spec.rb
index c4f6ddf9aca..09d64fe50bd 100644
--- a/spec/workers/concerns/gitlab/github_import/rescheduling_methods_spec.rb
+++ b/spec/workers/concerns/gitlab/github_import/rescheduling_methods_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GithubImport::ReschedulingMethods do
+RSpec.describe Gitlab::GithubImport::ReschedulingMethods do
let(:worker) do
Class.new { include(Gitlab::GithubImport::ReschedulingMethods) }.new
end
diff --git a/spec/workers/concerns/gitlab/github_import/stage_methods_spec.rb b/spec/workers/concerns/gitlab/github_import/stage_methods_spec.rb
index f9081a875b5..b7635748498 100644
--- a/spec/workers/concerns/gitlab/github_import/stage_methods_spec.rb
+++ b/spec/workers/concerns/gitlab/github_import/stage_methods_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GithubImport::StageMethods do
+RSpec.describe Gitlab::GithubImport::StageMethods do
let(:project) { create(:project) }
let(:worker) do
Class.new { include(Gitlab::GithubImport::StageMethods) }.new
diff --git a/spec/workers/concerns/gitlab/notify_upon_death_spec.rb b/spec/workers/concerns/gitlab/notify_upon_death_spec.rb
index 1c75ac99227..dd0a1cadc9c 100644
--- a/spec/workers/concerns/gitlab/notify_upon_death_spec.rb
+++ b/spec/workers/concerns/gitlab/notify_upon_death_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::NotifyUponDeath do
+RSpec.describe Gitlab::NotifyUponDeath do
let(:worker_class) do
Class.new do
include Sidekiq::Worker
diff --git a/spec/workers/concerns/pipeline_background_queue_spec.rb b/spec/workers/concerns/pipeline_background_queue_spec.rb
index 78ceafb359f..77c7e7440c5 100644
--- a/spec/workers/concerns/pipeline_background_queue_spec.rb
+++ b/spec/workers/concerns/pipeline_background_queue_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe PipelineBackgroundQueue do
+RSpec.describe PipelineBackgroundQueue do
let(:worker) do
Class.new do
def self.name
diff --git a/spec/workers/concerns/pipeline_queue_spec.rb b/spec/workers/concerns/pipeline_queue_spec.rb
index eedfceb8bf0..6c1ac2052e4 100644
--- a/spec/workers/concerns/pipeline_queue_spec.rb
+++ b/spec/workers/concerns/pipeline_queue_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe PipelineQueue do
+RSpec.describe PipelineQueue do
let(:worker) do
Class.new do
def self.name
diff --git a/spec/workers/concerns/project_export_options_spec.rb b/spec/workers/concerns/project_export_options_spec.rb
deleted file mode 100644
index 985afaaf11e..00000000000
--- a/spec/workers/concerns/project_export_options_spec.rb
+++ /dev/null
@@ -1,41 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-describe ProjectExportOptions do
- let(:project) { create(:project) }
- let(:project_export_job) { create(:project_export_job, project: project, jid: '123', status: 1) }
- let(:job) { { 'args' => [project.owner.id, project.id, nil, nil], 'jid' => '123' } }
- let(:worker_class) do
- Class.new do
- include Sidekiq::Worker
- include ProjectExportOptions
- end
- end
-
- it 'sets default retry limit' do
- expect(worker_class.sidekiq_options['retry']).to eq(ProjectExportOptions::EXPORT_RETRY_COUNT)
- end
-
- it 'sets default status expiration' do
- expect(worker_class.sidekiq_options['status_expiration']).to eq(StuckExportJobsWorker::EXPORT_JOBS_EXPIRATION)
- end
-
- describe '.sidekiq_retries_exhausted' do
- it 'marks status as failed' do
- expect { worker_class.sidekiq_retries_exhausted_block.call(job) }.to change { project_export_job.reload.status }.from(1).to(3)
- end
-
- context 'when status update fails' do
- before do
- project_export_job.update(status: 2)
- end
-
- it 'logs an error' do
- expect(Sidekiq.logger).to receive(:error).with("Failed to set Job #{job['jid']} for project #{project.id} to failed state")
-
- worker_class.sidekiq_retries_exhausted_block.call(job)
- end
- end
- end
-end
diff --git a/spec/workers/concerns/project_import_options_spec.rb b/spec/workers/concerns/project_import_options_spec.rb
index c56dcc5ed82..85a26ddb0cb 100644
--- a/spec/workers/concerns/project_import_options_spec.rb
+++ b/spec/workers/concerns/project_import_options_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ProjectImportOptions do
+RSpec.describe ProjectImportOptions do
let(:project) { create(:project, :import_started) }
let(:job) { { 'args' => [project.id, nil, nil], 'jid' => '123' } }
let(:worker_class) do
diff --git a/spec/workers/concerns/reenqueuer_spec.rb b/spec/workers/concerns/reenqueuer_spec.rb
index b28f83d211b..df0724045c1 100644
--- a/spec/workers/concerns/reenqueuer_spec.rb
+++ b/spec/workers/concerns/reenqueuer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Reenqueuer do
+RSpec.describe Reenqueuer do
include ExclusiveLeaseHelpers
let_it_be(:worker_class) do
@@ -93,7 +93,7 @@ describe Reenqueuer do
end
end
-describe Reenqueuer::ReenqueuerSleeper do
+RSpec.describe Reenqueuer::ReenqueuerSleeper do
let_it_be(:dummy_class) do
Class.new do
include Reenqueuer::ReenqueuerSleeper
diff --git a/spec/workers/concerns/repository_check_queue_spec.rb b/spec/workers/concerns/repository_check_queue_spec.rb
index 55ed71f124c..ae377c09b37 100644
--- a/spec/workers/concerns/repository_check_queue_spec.rb
+++ b/spec/workers/concerns/repository_check_queue_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe RepositoryCheckQueue do
+RSpec.describe RepositoryCheckQueue do
let(:worker) do
Class.new do
def self.name
diff --git a/spec/workers/concerns/waitable_worker_spec.rb b/spec/workers/concerns/waitable_worker_spec.rb
index e7ef96c685d..5d08d38380a 100644
--- a/spec/workers/concerns/waitable_worker_spec.rb
+++ b/spec/workers/concerns/waitable_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe WaitableWorker do
+RSpec.describe WaitableWorker do
let(:worker) do
Class.new do
def self.name
diff --git a/spec/workers/concerns/worker_context_spec.rb b/spec/workers/concerns/worker_context_spec.rb
index 4e8c81c57dc..3de37b99aba 100644
--- a/spec/workers/concerns/worker_context_spec.rb
+++ b/spec/workers/concerns/worker_context_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe WorkerContext do
+RSpec.describe WorkerContext do
let(:worker) do
Class.new do
def self.name
diff --git a/spec/workers/container_expiration_policy_worker_spec.rb b/spec/workers/container_expiration_policy_worker_spec.rb
index b15a28dcdca..868eb6b192e 100644
--- a/spec/workers/container_expiration_policy_worker_spec.rb
+++ b/spec/workers/container_expiration_policy_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ContainerExpirationPolicyWorker do
+RSpec.describe ContainerExpirationPolicyWorker do
include ExclusiveLeaseHelpers
subject { described_class.new.perform }
diff --git a/spec/workers/create_commit_signature_worker_spec.rb b/spec/workers/create_commit_signature_worker_spec.rb
index fd5d99b3265..d283ff5b732 100644
--- a/spec/workers/create_commit_signature_worker_spec.rb
+++ b/spec/workers/create_commit_signature_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe CreateCommitSignatureWorker do
+RSpec.describe CreateCommitSignatureWorker do
let(:project) { create(:project, :repository) }
let(:commits) { project.repository.commits('HEAD', limit: 3).commits }
let(:commit_shas) { commits.map(&:id) }
diff --git a/spec/workers/create_evidence_worker_spec.rb b/spec/workers/create_evidence_worker_spec.rb
index b8c622f7d1d..c700c086163 100644
--- a/spec/workers/create_evidence_worker_spec.rb
+++ b/spec/workers/create_evidence_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe CreateEvidenceWorker do
+RSpec.describe CreateEvidenceWorker do
let(:project) { create(:project, :repository) }
let(:release) { create(:release, project: project) }
let(:pipeline) { create(:ci_empty_pipeline, sha: release.sha, project: project) }
diff --git a/spec/workers/create_note_diff_file_worker_spec.rb b/spec/workers/create_note_diff_file_worker_spec.rb
index e35aaa7d593..4c1df8ade06 100644
--- a/spec/workers/create_note_diff_file_worker_spec.rb
+++ b/spec/workers/create_note_diff_file_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe CreateNoteDiffFileWorker do
+RSpec.describe CreateNoteDiffFileWorker do
describe '#perform' do
let(:diff_note) { create(:diff_note_on_merge_request) }
diff --git a/spec/workers/create_pipeline_worker_spec.rb b/spec/workers/create_pipeline_worker_spec.rb
index 62a17da80c0..6a3729fa28a 100644
--- a/spec/workers/create_pipeline_worker_spec.rb
+++ b/spec/workers/create_pipeline_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe CreatePipelineWorker do
+RSpec.describe CreatePipelineWorker do
describe '#perform' do
let(:worker) { described_class.new }
diff --git a/spec/workers/delete_container_repository_worker_spec.rb b/spec/workers/delete_container_repository_worker_spec.rb
index 8c40611a959..b8363a2f81a 100644
--- a/spec/workers/delete_container_repository_worker_spec.rb
+++ b/spec/workers/delete_container_repository_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe DeleteContainerRepositoryWorker do
+RSpec.describe DeleteContainerRepositoryWorker do
let(:registry) { create(:container_repository) }
let(:project) { registry.project }
let(:user) { project.owner }
diff --git a/spec/workers/delete_diff_files_worker_spec.rb b/spec/workers/delete_diff_files_worker_spec.rb
index 9f8b20df48e..b3b01588e0b 100644
--- a/spec/workers/delete_diff_files_worker_spec.rb
+++ b/spec/workers/delete_diff_files_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe DeleteDiffFilesWorker do
+RSpec.describe DeleteDiffFilesWorker do
describe '#perform' do
let(:merge_request) { create(:merge_request) }
let(:merge_request_diff) { merge_request.merge_request_diff }
diff --git a/spec/workers/delete_merged_branches_worker_spec.rb b/spec/workers/delete_merged_branches_worker_spec.rb
index 3eaeb7e0797..861ca111b92 100644
--- a/spec/workers/delete_merged_branches_worker_spec.rb
+++ b/spec/workers/delete_merged_branches_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe DeleteMergedBranchesWorker do
+RSpec.describe DeleteMergedBranchesWorker do
subject(:worker) { described_class.new }
let(:project) { create(:project, :repository) }
diff --git a/spec/workers/delete_user_worker_spec.rb b/spec/workers/delete_user_worker_spec.rb
index c963b886e62..52f2c692b8c 100644
--- a/spec/workers/delete_user_worker_spec.rb
+++ b/spec/workers/delete_user_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe DeleteUserWorker do
+RSpec.describe DeleteUserWorker do
let!(:user) { create(:user) }
let!(:current_user) { create(:user) }
diff --git a/spec/workers/deployments/finished_worker_spec.rb b/spec/workers/deployments/finished_worker_spec.rb
index 2961ff599c3..9b4bd78c03a 100644
--- a/spec/workers/deployments/finished_worker_spec.rb
+++ b/spec/workers/deployments/finished_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Deployments::FinishedWorker do
+RSpec.describe Deployments::FinishedWorker do
let(:worker) { described_class.new }
describe '#perform' do
diff --git a/spec/workers/deployments/success_worker_spec.rb b/spec/workers/deployments/success_worker_spec.rb
index 7f2816d7535..7c21a3147a7 100644
--- a/spec/workers/deployments/success_worker_spec.rb
+++ b/spec/workers/deployments/success_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Deployments::SuccessWorker do
+RSpec.describe Deployments::SuccessWorker do
subject { described_class.new.perform(deployment&.id) }
context 'when successful deployment' do
diff --git a/spec/workers/design_management/new_version_worker_spec.rb b/spec/workers/design_management/new_version_worker_spec.rb
index ef7cd8de108..4d57c46487e 100644
--- a/spec/workers/design_management/new_version_worker_spec.rb
+++ b/spec/workers/design_management/new_version_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe DesignManagement::NewVersionWorker do
+RSpec.describe DesignManagement::NewVersionWorker do
describe '#perform' do
let(:worker) { described_class.new }
diff --git a/spec/workers/detect_repository_languages_worker_spec.rb b/spec/workers/detect_repository_languages_worker_spec.rb
index 84af49050d4..217e16bd155 100644
--- a/spec/workers/detect_repository_languages_worker_spec.rb
+++ b/spec/workers/detect_repository_languages_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe DetectRepositoryLanguagesWorker do
+RSpec.describe DetectRepositoryLanguagesWorker do
let_it_be(:project) { create(:project) }
subject { described_class.new }
diff --git a/spec/workers/email_receiver_worker_spec.rb b/spec/workers/email_receiver_worker_spec.rb
index f8a31fcdee6..8bf7f3f552d 100644
--- a/spec/workers/email_receiver_worker_spec.rb
+++ b/spec/workers/email_receiver_worker_spec.rb
@@ -2,7 +2,7 @@
require "spec_helper"
-describe EmailReceiverWorker, :mailer do
+RSpec.describe EmailReceiverWorker, :mailer do
let(:raw_message) { fixture_file('emails/valid_reply.eml') }
context "when reply by email is enabled" do
diff --git a/spec/workers/emails_on_push_worker_spec.rb b/spec/workers/emails_on_push_worker_spec.rb
index 0f87df89c29..fac463b4dd4 100644
--- a/spec/workers/emails_on_push_worker_spec.rb
+++ b/spec/workers/emails_on_push_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe EmailsOnPushWorker, :mailer do
+RSpec.describe EmailsOnPushWorker, :mailer do
include RepoHelpers
include EmailSpec::Matchers
diff --git a/spec/workers/environments/auto_stop_cron_worker_spec.rb b/spec/workers/environments/auto_stop_cron_worker_spec.rb
index 6773637d4a7..1e86597d288 100644
--- a/spec/workers/environments/auto_stop_cron_worker_spec.rb
+++ b/spec/workers/environments/auto_stop_cron_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Environments::AutoStopCronWorker do
+RSpec.describe Environments::AutoStopCronWorker do
subject { worker.perform }
let(:worker) { described_class.new }
diff --git a/spec/workers/error_tracking_issue_link_worker_spec.rb b/spec/workers/error_tracking_issue_link_worker_spec.rb
index 701d54b72f0..5be568c2dad 100644
--- a/spec/workers/error_tracking_issue_link_worker_spec.rb
+++ b/spec/workers/error_tracking_issue_link_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ErrorTrackingIssueLinkWorker do
+RSpec.describe ErrorTrackingIssueLinkWorker do
let_it_be(:error_tracking) { create(:project_error_tracking_setting) }
let_it_be(:project) { error_tracking.project }
let_it_be(:issue) { create(:issue, project: project) }
diff --git a/spec/workers/every_sidekiq_worker_spec.rb b/spec/workers/every_sidekiq_worker_spec.rb
index 195783c74df..3bb9db07ff3 100644
--- a/spec/workers/every_sidekiq_worker_spec.rb
+++ b/spec/workers/every_sidekiq_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Every Sidekiq worker' do
+RSpec.describe 'Every Sidekiq worker' do
let(:workers_without_defaults) do
Gitlab::SidekiqConfig.workers - Gitlab::SidekiqConfig::DEFAULT_WORKERS
end
@@ -19,7 +19,7 @@ describe 'Every Sidekiq worker' do
file_worker_queues = Gitlab::SidekiqConfig.worker_queues.to_set
worker_queues = Gitlab::SidekiqConfig.workers.map(&:queue).to_set
- worker_queues << ActionMailer::DeliveryJob.new.queue_name
+ worker_queues << ActionMailer::MailDeliveryJob.new.queue_name
worker_queues << 'default'
missing_from_file = worker_queues - file_worker_queues
@@ -53,7 +53,7 @@ describe 'Every Sidekiq worker' do
# All Sidekiq worker classes should declare a valid `feature_category`
# or explicitly be excluded with the `feature_category_not_owned!` annotation.
- # Please see doc/development/sidekiq_style_guide.md#Feature-Categorization for more details.
+ # Please see doc/development/sidekiq_style_guide.md#feature-categorization for more details.
it 'has a feature_category or feature_category_not_owned! attribute', :aggregate_failures do
workers_without_defaults.each do |worker|
expect(worker.get_feature_category).to be_a(Symbol), "expected #{worker.inspect} to declare a feature_category or feature_category_not_owned!"
@@ -62,7 +62,7 @@ describe 'Every Sidekiq worker' do
# All Sidekiq worker classes should declare a valid `feature_category`.
# The category should match a value in `config/feature_categories.yml`.
- # Please see doc/development/sidekiq_style_guide.md#Feature-Categorization for more details.
+ # Please see doc/development/sidekiq_style_guide.md#feature-categorization for more details.
it 'has a feature_category that maps to a value in feature_categories.yml', :aggregate_failures do
workers_with_feature_categories = workers_without_defaults
.select(&:get_feature_category)
diff --git a/spec/workers/expire_build_artifacts_worker_spec.rb b/spec/workers/expire_build_artifacts_worker_spec.rb
index 06561e94fb7..995f37daf17 100644
--- a/spec/workers/expire_build_artifacts_worker_spec.rb
+++ b/spec/workers/expire_build_artifacts_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ExpireBuildArtifactsWorker do
+RSpec.describe ExpireBuildArtifactsWorker do
let(:worker) { described_class.new }
describe '#perform' do
diff --git a/spec/workers/expire_build_instance_artifacts_worker_spec.rb b/spec/workers/expire_build_instance_artifacts_worker_spec.rb
index 335fa5e6c98..38318447b5f 100644
--- a/spec/workers/expire_build_instance_artifacts_worker_spec.rb
+++ b/spec/workers/expire_build_instance_artifacts_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ExpireBuildInstanceArtifactsWorker do
+RSpec.describe ExpireBuildInstanceArtifactsWorker do
include RepoHelpers
let(:worker) { described_class.new }
@@ -32,7 +32,7 @@ describe ExpireBuildInstanceArtifactsWorker do
context 'with not yet expired artifacts' do
let_it_be(:build) do
- create(:ci_build, :artifacts, artifacts_expire_at: Time.now + 7.days)
+ create(:ci_build, :artifacts, artifacts_expire_at: Time.current + 7.days)
end
it 'does not expire' do
diff --git a/spec/workers/expire_job_cache_worker_spec.rb b/spec/workers/expire_job_cache_worker_spec.rb
index 062926cf7aa..b4f8f56563b 100644
--- a/spec/workers/expire_job_cache_worker_spec.rb
+++ b/spec/workers/expire_job_cache_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ExpireJobCacheWorker do
+RSpec.describe ExpireJobCacheWorker do
let_it_be(:pipeline) { create(:ci_empty_pipeline) }
let(:project) { pipeline.project }
diff --git a/spec/workers/expire_pipeline_cache_worker_spec.rb b/spec/workers/expire_pipeline_cache_worker_spec.rb
index 61ea22fbd32..fb6ee67311c 100644
--- a/spec/workers/expire_pipeline_cache_worker_spec.rb
+++ b/spec/workers/expire_pipeline_cache_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ExpirePipelineCacheWorker do
+RSpec.describe ExpirePipelineCacheWorker do
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project) }
let_it_be(:pipeline) { create(:ci_pipeline, project: project) }
diff --git a/spec/workers/export_csv_worker_spec.rb b/spec/workers/export_csv_worker_spec.rb
index 87285b6264a..1a5b17ee35b 100644
--- a/spec/workers/export_csv_worker_spec.rb
+++ b/spec/workers/export_csv_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ExportCsvWorker do
+RSpec.describe ExportCsvWorker do
let(:user) { create(:user) }
let(:project) { create(:project, creator: user) }
diff --git a/spec/workers/external_service_reactive_caching_worker_spec.rb b/spec/workers/external_service_reactive_caching_worker_spec.rb
index 45cce71b75b..907894d9b9a 100644
--- a/spec/workers/external_service_reactive_caching_worker_spec.rb
+++ b/spec/workers/external_service_reactive_caching_worker_spec.rb
@@ -2,6 +2,6 @@
require 'spec_helper'
-describe ExternalServiceReactiveCachingWorker do
+RSpec.describe ExternalServiceReactiveCachingWorker do
it_behaves_like 'reactive cacheable worker'
end
diff --git a/spec/workers/file_hook_worker_spec.rb b/spec/workers/file_hook_worker_spec.rb
index 1a7e753fc4a..c171dc37e5f 100644
--- a/spec/workers/file_hook_worker_spec.rb
+++ b/spec/workers/file_hook_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe FileHookWorker do
+RSpec.describe FileHookWorker do
include RepoHelpers
let(:filename) { 'my_file_hook.rb' }
diff --git a/spec/workers/git_garbage_collect_worker_spec.rb b/spec/workers/git_garbage_collect_worker_spec.rb
index 64ad4ba7eb6..cb6396e2859 100644
--- a/spec/workers/git_garbage_collect_worker_spec.rb
+++ b/spec/workers/git_garbage_collect_worker_spec.rb
@@ -4,7 +4,7 @@ require 'fileutils'
require 'spec_helper'
-describe GitGarbageCollectWorker do
+RSpec.describe GitGarbageCollectWorker do
include GitHelpers
let(:project) { create(:project, :repository) }
diff --git a/spec/workers/gitlab/github_import/advance_stage_worker_spec.rb b/spec/workers/gitlab/github_import/advance_stage_worker_spec.rb
index b1647d8c7df..4e8261f61c4 100644
--- a/spec/workers/gitlab/github_import/advance_stage_worker_spec.rb
+++ b/spec/workers/gitlab/github_import/advance_stage_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GithubImport::AdvanceStageWorker, :clean_gitlab_redis_shared_state do
+RSpec.describe Gitlab::GithubImport::AdvanceStageWorker, :clean_gitlab_redis_shared_state do
let(:project) { create(:project) }
let(:import_state) { create(:import_state, project: project, jid: '123') }
let(:worker) { described_class.new }
diff --git a/spec/workers/gitlab/github_import/import_diff_note_worker_spec.rb b/spec/workers/gitlab/github_import/import_diff_note_worker_spec.rb
index 42d69ff6166..211eba993f7 100644
--- a/spec/workers/gitlab/github_import/import_diff_note_worker_spec.rb
+++ b/spec/workers/gitlab/github_import/import_diff_note_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GithubImport::ImportDiffNoteWorker do
+RSpec.describe Gitlab::GithubImport::ImportDiffNoteWorker do
let(:worker) { described_class.new }
describe '#import' do
diff --git a/spec/workers/gitlab/github_import/import_issue_worker_spec.rb b/spec/workers/gitlab/github_import/import_issue_worker_spec.rb
index 06a573e16b7..1d285790ee2 100644
--- a/spec/workers/gitlab/github_import/import_issue_worker_spec.rb
+++ b/spec/workers/gitlab/github_import/import_issue_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GithubImport::ImportIssueWorker do
+RSpec.describe Gitlab::GithubImport::ImportIssueWorker do
let(:worker) { described_class.new }
describe '#import' do
diff --git a/spec/workers/gitlab/github_import/import_note_worker_spec.rb b/spec/workers/gitlab/github_import/import_note_worker_spec.rb
index 5110c3ff11b..618aca4ff0a 100644
--- a/spec/workers/gitlab/github_import/import_note_worker_spec.rb
+++ b/spec/workers/gitlab/github_import/import_note_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GithubImport::ImportNoteWorker do
+RSpec.describe Gitlab::GithubImport::ImportNoteWorker do
let(:worker) { described_class.new }
describe '#import' do
diff --git a/spec/workers/gitlab/github_import/import_pull_request_worker_spec.rb b/spec/workers/gitlab/github_import/import_pull_request_worker_spec.rb
index d46e381fc51..0f5df302d56 100644
--- a/spec/workers/gitlab/github_import/import_pull_request_worker_spec.rb
+++ b/spec/workers/gitlab/github_import/import_pull_request_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GithubImport::ImportPullRequestWorker do
+RSpec.describe Gitlab::GithubImport::ImportPullRequestWorker do
let(:worker) { described_class.new }
describe '#import' do
diff --git a/spec/workers/gitlab/github_import/refresh_import_jid_worker_spec.rb b/spec/workers/gitlab/github_import/refresh_import_jid_worker_spec.rb
index fa4ded8e42f..3a8b585fa77 100644
--- a/spec/workers/gitlab/github_import/refresh_import_jid_worker_spec.rb
+++ b/spec/workers/gitlab/github_import/refresh_import_jid_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GithubImport::RefreshImportJidWorker do
+RSpec.describe Gitlab::GithubImport::RefreshImportJidWorker do
let(:worker) { described_class.new }
describe '.perform_in_the_future' do
diff --git a/spec/workers/gitlab/github_import/stage/finish_import_worker_spec.rb b/spec/workers/gitlab/github_import/stage/finish_import_worker_spec.rb
index 35a856802c2..c821e0aaa09 100644
--- a/spec/workers/gitlab/github_import/stage/finish_import_worker_spec.rb
+++ b/spec/workers/gitlab/github_import/stage/finish_import_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GithubImport::Stage::FinishImportWorker do
+RSpec.describe Gitlab::GithubImport::Stage::FinishImportWorker do
let(:project) { create(:project) }
let(:worker) { described_class.new }
diff --git a/spec/workers/gitlab/github_import/stage/import_base_data_worker_spec.rb b/spec/workers/gitlab/github_import/stage/import_base_data_worker_spec.rb
index 0c7fc2a164e..f68d0838501 100644
--- a/spec/workers/gitlab/github_import/stage/import_base_data_worker_spec.rb
+++ b/spec/workers/gitlab/github_import/stage/import_base_data_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GithubImport::Stage::ImportBaseDataWorker do
+RSpec.describe Gitlab::GithubImport::Stage::ImportBaseDataWorker do
let(:project) { create(:project) }
let(:import_state) { create(:import_state, project: project) }
let(:worker) { described_class.new }
diff --git a/spec/workers/gitlab/github_import/stage/import_issues_and_diff_notes_worker_spec.rb b/spec/workers/gitlab/github_import/stage/import_issues_and_diff_notes_worker_spec.rb
index 5d96f562c30..f2a28ec40b8 100644
--- a/spec/workers/gitlab/github_import/stage/import_issues_and_diff_notes_worker_spec.rb
+++ b/spec/workers/gitlab/github_import/stage/import_issues_and_diff_notes_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GithubImport::Stage::ImportIssuesAndDiffNotesWorker do
+RSpec.describe Gitlab::GithubImport::Stage::ImportIssuesAndDiffNotesWorker do
let(:project) { create(:project) }
let(:worker) { described_class.new }
diff --git a/spec/workers/gitlab/github_import/stage/import_lfs_objects_worker_spec.rb b/spec/workers/gitlab/github_import/stage/import_lfs_objects_worker_spec.rb
index e7c9dabb292..103d55890c4 100644
--- a/spec/workers/gitlab/github_import/stage/import_lfs_objects_worker_spec.rb
+++ b/spec/workers/gitlab/github_import/stage/import_lfs_objects_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GithubImport::Stage::ImportLfsObjectsWorker do
+RSpec.describe Gitlab::GithubImport::Stage::ImportLfsObjectsWorker do
let(:project) { create(:project) }
let(:worker) { described_class.new }
diff --git a/spec/workers/gitlab/github_import/stage/import_notes_worker_spec.rb b/spec/workers/gitlab/github_import/stage/import_notes_worker_spec.rb
index 90590a45900..73b19239f4a 100644
--- a/spec/workers/gitlab/github_import/stage/import_notes_worker_spec.rb
+++ b/spec/workers/gitlab/github_import/stage/import_notes_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GithubImport::Stage::ImportNotesWorker do
+RSpec.describe Gitlab::GithubImport::Stage::ImportNotesWorker do
let(:project) { create(:project) }
let(:worker) { described_class.new }
diff --git a/spec/workers/gitlab/github_import/stage/import_pull_requests_worker_spec.rb b/spec/workers/gitlab/github_import/stage/import_pull_requests_worker_spec.rb
index 15d485f1018..0acbca7032c 100644
--- a/spec/workers/gitlab/github_import/stage/import_pull_requests_worker_spec.rb
+++ b/spec/workers/gitlab/github_import/stage/import_pull_requests_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GithubImport::Stage::ImportPullRequestsWorker do
+RSpec.describe Gitlab::GithubImport::Stage::ImportPullRequestsWorker do
let(:project) { create(:project) }
let(:import_state) { create(:import_state, project: project) }
let(:worker) { described_class.new }
diff --git a/spec/workers/gitlab/github_import/stage/import_repository_worker_spec.rb b/spec/workers/gitlab/github_import/stage/import_repository_worker_spec.rb
index 3a8fe73622a..bc51a44e057 100644
--- a/spec/workers/gitlab/github_import/stage/import_repository_worker_spec.rb
+++ b/spec/workers/gitlab/github_import/stage/import_repository_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GithubImport::Stage::ImportRepositoryWorker do
+RSpec.describe Gitlab::GithubImport::Stage::ImportRepositoryWorker do
let(:project) { double(:project, id: 4) }
let(:worker) { described_class.new }
diff --git a/spec/workers/gitlab/import/stuck_project_import_jobs_worker_spec.rb b/spec/workers/gitlab/import/stuck_project_import_jobs_worker_spec.rb
index 5afc5717b82..510c41cba21 100644
--- a/spec/workers/gitlab/import/stuck_project_import_jobs_worker_spec.rb
+++ b/spec/workers/gitlab/import/stuck_project_import_jobs_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Import::StuckProjectImportJobsWorker do
+RSpec.describe Gitlab::Import::StuckProjectImportJobsWorker do
let(:worker) { described_class.new }
describe 'with scheduled import_status' do
diff --git a/spec/workers/gitlab/jira_import/import_issue_worker_spec.rb b/spec/workers/gitlab/jira_import/import_issue_worker_spec.rb
index 2de609761e2..4a4ef5700fa 100644
--- a/spec/workers/gitlab/jira_import/import_issue_worker_spec.rb
+++ b/spec/workers/gitlab/jira_import/import_issue_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::JiraImport::ImportIssueWorker do
+RSpec.describe Gitlab::JiraImport::ImportIssueWorker do
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project) }
let_it_be(:jira_issue_label_1) { create(:label, project: project) }
diff --git a/spec/workers/gitlab/jira_import/stage/finish_import_worker_spec.rb b/spec/workers/gitlab/jira_import/stage/finish_import_worker_spec.rb
index 084302be7d8..23a764bd972 100644
--- a/spec/workers/gitlab/jira_import/stage/finish_import_worker_spec.rb
+++ b/spec/workers/gitlab/jira_import/stage/finish_import_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::JiraImport::Stage::FinishImportWorker do
+RSpec.describe Gitlab::JiraImport::Stage::FinishImportWorker do
let_it_be(:project) { create(:project) }
let_it_be(:worker) { described_class.new }
diff --git a/spec/workers/gitlab/jira_import/stage/import_attachments_worker_spec.rb b/spec/workers/gitlab/jira_import/stage/import_attachments_worker_spec.rb
index 34981d974cd..28da93b8d00 100644
--- a/spec/workers/gitlab/jira_import/stage/import_attachments_worker_spec.rb
+++ b/spec/workers/gitlab/jira_import/stage/import_attachments_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::JiraImport::Stage::ImportAttachmentsWorker do
+RSpec.describe Gitlab::JiraImport::Stage::ImportAttachmentsWorker do
let_it_be(:project) { create(:project, import_type: 'jira') }
describe 'modules' do
diff --git a/spec/workers/gitlab/jira_import/stage/import_issues_worker_spec.rb b/spec/workers/gitlab/jira_import/stage/import_issues_worker_spec.rb
index 40f6cf75412..f82f6ccd9d6 100644
--- a/spec/workers/gitlab/jira_import/stage/import_issues_worker_spec.rb
+++ b/spec/workers/gitlab/jira_import/stage/import_issues_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::JiraImport::Stage::ImportIssuesWorker do
+RSpec.describe Gitlab::JiraImport::Stage::ImportIssuesWorker do
include JiraServiceHelper
let_it_be(:user) { create(:user) }
diff --git a/spec/workers/gitlab/jira_import/stage/import_labels_worker_spec.rb b/spec/workers/gitlab/jira_import/stage/import_labels_worker_spec.rb
index 1215b41bd9f..0b7a35a92e2 100644
--- a/spec/workers/gitlab/jira_import/stage/import_labels_worker_spec.rb
+++ b/spec/workers/gitlab/jira_import/stage/import_labels_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::JiraImport::Stage::ImportLabelsWorker do
+RSpec.describe Gitlab::JiraImport::Stage::ImportLabelsWorker do
include JiraServiceHelper
let_it_be(:user) { create(:user) }
diff --git a/spec/workers/gitlab/jira_import/stage/import_notes_worker_spec.rb b/spec/workers/gitlab/jira_import/stage/import_notes_worker_spec.rb
index a0a9ad6f695..2502bbf1df4 100644
--- a/spec/workers/gitlab/jira_import/stage/import_notes_worker_spec.rb
+++ b/spec/workers/gitlab/jira_import/stage/import_notes_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::JiraImport::Stage::ImportNotesWorker do
+RSpec.describe Gitlab::JiraImport::Stage::ImportNotesWorker do
let_it_be(:project) { create(:project, import_type: 'jira') }
describe 'modules' do
diff --git a/spec/workers/gitlab/jira_import/stage/start_import_worker_spec.rb b/spec/workers/gitlab/jira_import/stage/start_import_worker_spec.rb
index a4fc761accf..7066e6e912f 100644
--- a/spec/workers/gitlab/jira_import/stage/start_import_worker_spec.rb
+++ b/spec/workers/gitlab/jira_import/stage/start_import_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::JiraImport::Stage::StartImportWorker do
+RSpec.describe Gitlab::JiraImport::Stage::StartImportWorker do
let_it_be(:project) { create(:project, import_type: 'jira') }
let_it_be(:jid) { '12345678' }
let(:worker) { described_class.new }
diff --git a/spec/workers/gitlab/jira_import/stuck_jira_import_jobs_worker_spec.rb b/spec/workers/gitlab/jira_import/stuck_jira_import_jobs_worker_spec.rb
index fae52cec2b4..8271af4db2f 100644
--- a/spec/workers/gitlab/jira_import/stuck_jira_import_jobs_worker_spec.rb
+++ b/spec/workers/gitlab/jira_import/stuck_jira_import_jobs_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ::Gitlab::JiraImport::StuckJiraImportJobsWorker do
+RSpec.describe ::Gitlab::JiraImport::StuckJiraImportJobsWorker do
let_it_be(:current_user) { create(:user) }
let_it_be(:project) { create(:project) }
let(:worker) { described_class.new }
diff --git a/spec/workers/gitlab/phabricator_import/base_worker_spec.rb b/spec/workers/gitlab/phabricator_import/base_worker_spec.rb
index d46d908a3e3..18fa484aa7a 100644
--- a/spec/workers/gitlab/phabricator_import/base_worker_spec.rb
+++ b/spec/workers/gitlab/phabricator_import/base_worker_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe Gitlab::PhabricatorImport::BaseWorker do
+RSpec.describe Gitlab::PhabricatorImport::BaseWorker do
let(:subclass) do
# Creating an anonymous class for a worker is complicated, as we generate the
# queue name from the class name.
diff --git a/spec/workers/gitlab/phabricator_import/import_tasks_worker_spec.rb b/spec/workers/gitlab/phabricator_import/import_tasks_worker_spec.rb
index 1e38ef8aaa5..221b6202166 100644
--- a/spec/workers/gitlab/phabricator_import/import_tasks_worker_spec.rb
+++ b/spec/workers/gitlab/phabricator_import/import_tasks_worker_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe Gitlab::PhabricatorImport::ImportTasksWorker do
+RSpec.describe Gitlab::PhabricatorImport::ImportTasksWorker do
describe '#perform' do
it 'calls the correct importer' do
project = create(:project, :import_started, import_url: "https://the.phab.ulr")
diff --git a/spec/workers/gitlab_shell_worker_spec.rb b/spec/workers/gitlab_shell_worker_spec.rb
index 0e63f48d3e8..c46ef87333a 100644
--- a/spec/workers/gitlab_shell_worker_spec.rb
+++ b/spec/workers/gitlab_shell_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GitlabShellWorker do
+RSpec.describe GitlabShellWorker do
let(:worker) { described_class.new }
describe '#perform' do
diff --git a/spec/workers/gitlab_usage_ping_worker_spec.rb b/spec/workers/gitlab_usage_ping_worker_spec.rb
index 198daf40493..05d6f2e585b 100644
--- a/spec/workers/gitlab_usage_ping_worker_spec.rb
+++ b/spec/workers/gitlab_usage_ping_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GitlabUsagePingWorker do
+RSpec.describe GitlabUsagePingWorker do
subject { described_class.new }
it 'delegates to SubmitUsagePingService' do
diff --git a/spec/workers/group_destroy_worker_spec.rb b/spec/workers/group_destroy_worker_spec.rb
index 90a4150a31a..ab3dd19dec1 100644
--- a/spec/workers/group_destroy_worker_spec.rb
+++ b/spec/workers/group_destroy_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GroupDestroyWorker do
+RSpec.describe GroupDestroyWorker do
let(:group) { create(:group) }
let(:user) { create(:admin) }
let!(:project) { create(:project, namespace: group) }
diff --git a/spec/workers/group_export_worker_spec.rb b/spec/workers/group_export_worker_spec.rb
index 350777df912..5697e66b7d1 100644
--- a/spec/workers/group_export_worker_spec.rb
+++ b/spec/workers/group_export_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GroupExportWorker do
+RSpec.describe GroupExportWorker do
let!(:user) { create(:user) }
let!(:group) { create(:group) }
diff --git a/spec/workers/group_import_worker_spec.rb b/spec/workers/group_import_worker_spec.rb
index 324a5fa6978..fb2d49c21af 100644
--- a/spec/workers/group_import_worker_spec.rb
+++ b/spec/workers/group_import_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GroupImportWorker do
+RSpec.describe GroupImportWorker do
let!(:user) { create(:user) }
let!(:group) { create(:group) }
diff --git a/spec/workers/hashed_storage/migrator_worker_spec.rb b/spec/workers/hashed_storage/migrator_worker_spec.rb
index ac76a306f43..e014297756e 100644
--- a/spec/workers/hashed_storage/migrator_worker_spec.rb
+++ b/spec/workers/hashed_storage/migrator_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe HashedStorage::MigratorWorker do
+RSpec.describe HashedStorage::MigratorWorker do
subject(:worker) { described_class.new }
let(:projects) { create_list(:project, 2, :legacy_storage, :empty_repo) }
diff --git a/spec/workers/hashed_storage/project_migrate_worker_spec.rb b/spec/workers/hashed_storage/project_migrate_worker_spec.rb
index 4b1b5f84fd3..fd460888932 100644
--- a/spec/workers/hashed_storage/project_migrate_worker_spec.rb
+++ b/spec/workers/hashed_storage/project_migrate_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe HashedStorage::ProjectMigrateWorker, :clean_gitlab_redis_shared_state do
+RSpec.describe HashedStorage::ProjectMigrateWorker, :clean_gitlab_redis_shared_state do
include ExclusiveLeaseHelpers
let(:migration_service) { ::Projects::HashedStorage::MigrationService }
diff --git a/spec/workers/hashed_storage/project_rollback_worker_spec.rb b/spec/workers/hashed_storage/project_rollback_worker_spec.rb
index d833553c0ec..fc89ac728b1 100644
--- a/spec/workers/hashed_storage/project_rollback_worker_spec.rb
+++ b/spec/workers/hashed_storage/project_rollback_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe HashedStorage::ProjectRollbackWorker, :clean_gitlab_redis_shared_state do
+RSpec.describe HashedStorage::ProjectRollbackWorker, :clean_gitlab_redis_shared_state do
include ExclusiveLeaseHelpers
describe '#perform' do
diff --git a/spec/workers/hashed_storage/rollbacker_worker_spec.rb b/spec/workers/hashed_storage/rollbacker_worker_spec.rb
index 55fc4fb0fe1..46cca068273 100644
--- a/spec/workers/hashed_storage/rollbacker_worker_spec.rb
+++ b/spec/workers/hashed_storage/rollbacker_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe HashedStorage::RollbackerWorker do
+RSpec.describe HashedStorage::RollbackerWorker do
subject(:worker) { described_class.new }
let(:projects) { create_list(:project, 2, :empty_repo) }
diff --git a/spec/workers/import_issues_csv_worker_spec.rb b/spec/workers/import_issues_csv_worker_spec.rb
index 03944cfb05d..c5420b00e8a 100644
--- a/spec/workers/import_issues_csv_worker_spec.rb
+++ b/spec/workers/import_issues_csv_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ImportIssuesCsvWorker do
+RSpec.describe ImportIssuesCsvWorker do
let(:project) { create(:project) }
let(:user) { create(:user) }
let(:upload) { create(:upload) }
diff --git a/spec/workers/incident_management/pager_duty/process_incident_worker_spec.rb b/spec/workers/incident_management/pager_duty/process_incident_worker_spec.rb
new file mode 100644
index 00000000000..e2be91516b9
--- /dev/null
+++ b/spec/workers/incident_management/pager_duty/process_incident_worker_spec.rb
@@ -0,0 +1,57 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe IncidentManagement::PagerDuty::ProcessIncidentWorker do
+ let_it_be(:project) { create(:project) }
+ let_it_be(:incident_management_setting) { create(:project_incident_management_setting, project: project, pagerduty_active: true) }
+
+ describe '#perform' do
+ subject(:perform) { described_class.new.perform(project.id, incident_payload) }
+
+ context 'with valid incident payload' do
+ let(:incident_payload) do
+ {
+ 'url' => 'https://webdemo.pagerduty.com/incidents/PRORDTY',
+ 'incident_number' => 33,
+ 'title' => 'My new incident',
+ 'status' => 'triggered',
+ 'created_at' => '2017-09-26T15:14:36Z',
+ 'urgency' => 'high',
+ 'incident_key' => nil,
+ 'assignees' => [{
+ 'summary' => 'Laura Haley', 'url' => 'https://webdemo.pagerduty.com/users/P553OPV'
+ }],
+ 'impacted_services' => [{
+ 'summary' => 'Production XDB Cluster', 'url' => 'https://webdemo.pagerduty.com/services/PN49J75'
+ }]
+ }
+ end
+
+ it 'creates a GitLab issue' do
+ expect { perform }.to change(Issue, :count).by(1)
+ end
+ end
+
+ context 'with invalid incident payload' do
+ let(:incident_payload) { {} }
+
+ before do
+ allow(Gitlab::AppLogger).to receive(:warn).and_call_original
+ end
+
+ it 'does not create a GitLab issue' do
+ expect { perform }.not_to change(Issue, :count)
+ end
+
+ it 'logs a warning' do
+ perform
+
+ expect(Gitlab::AppLogger).to have_received(:warn).with(
+ message: 'Cannot create issue for PagerDuty incident',
+ issue_errors: "Title can't be blank"
+ )
+ end
+ end
+ end
+end
diff --git a/spec/workers/incident_management/process_alert_worker_spec.rb b/spec/workers/incident_management/process_alert_worker_spec.rb
index 0470552d933..75696d15ab8 100644
--- a/spec/workers/incident_management/process_alert_worker_spec.rb
+++ b/spec/workers/incident_management/process_alert_worker_spec.rb
@@ -2,40 +2,36 @@
require 'spec_helper'
-describe IncidentManagement::ProcessAlertWorker do
+RSpec.describe IncidentManagement::ProcessAlertWorker do
let_it_be(:project) { create(:project) }
let_it_be(:settings) { create(:project_incident_management_setting, project: project, create_issue: true) }
describe '#perform' do
- let(:alert_management_alert_id) { nil }
- let(:alert_payload) do
- {
- 'annotations' => { 'title' => 'title' },
- 'startsAt' => Time.now.rfc3339
- }
- end
-
- let(:created_issue) { Issue.last }
+ let_it_be(:started_at) { Time.now.rfc3339 }
+ let_it_be(:payload) { { 'title' => 'title', 'start_time' => started_at } }
+ let_it_be(:parsed_payload) { Gitlab::Alerting::NotificationPayloadParser.call(payload, project) }
+ let_it_be(:alert) { create(:alert_management_alert, project: project, payload: payload, started_at: started_at) }
+ let(:created_issue) { Issue.last! }
- subject { described_class.new.perform(project.id, alert_payload, alert_management_alert_id) }
+ subject { described_class.new.perform(nil, nil, alert.id) }
before do
allow(IncidentManagement::CreateIssueService)
- .to receive(:new).with(project, alert_payload)
+ .to receive(:new).with(alert.project, parsed_payload)
.and_call_original
end
it 'creates an issue' do
expect(IncidentManagement::CreateIssueService)
- .to receive(:new).with(project, alert_payload)
+ .to receive(:new).with(alert.project, parsed_payload)
expect { subject }.to change { Issue.count }.by(1)
end
- context 'with invalid project' do
- let(:invalid_project_id) { non_existing_record_id }
+ context 'with invalid alert' do
+ let(:invalid_alert_id) { non_existing_record_id }
- subject { described_class.new.perform(invalid_project_id, alert_payload) }
+ subject { described_class.new.perform(nil, nil, invalid_alert_id) }
it 'does not create issues' do
expect(IncidentManagement::CreateIssueService).not_to receive(:new)
@@ -44,16 +40,8 @@ describe IncidentManagement::ProcessAlertWorker do
end
end
- context 'when alert_management_alert_id is present' do
- let!(:alert) { create(:alert_management_alert, project: project) }
- let(:alert_management_alert_id) { alert.id }
-
+ context 'with valid alert' do
before do
- allow(AlertManagement::Alert)
- .to receive(:find_by_id)
- .with(alert_management_alert_id)
- .and_return(alert)
-
allow(Gitlab::AppLogger).to receive(:warn).and_call_original
end
@@ -69,24 +57,24 @@ describe IncidentManagement::ProcessAlertWorker do
expect(Gitlab::AppLogger).not_to have_received(:warn)
end
- end
- context 'when alert cannot be updated' do
- let(:alert) { create(:alert_management_alert, :with_validation_errors, project: project) }
+ context 'when alert cannot be updated' do
+ let_it_be(:alert) { create(:alert_management_alert, :with_validation_errors, project: project, payload: payload) }
- it 'updates AlertManagement::Alert#issue_id' do
- expect { subject }.not_to change { alert.reload.issue_id }
- end
+ it 'updates AlertManagement::Alert#issue_id' do
+ expect { subject }.not_to change { alert.reload.issue_id }
+ end
- it 'logs a warning' do
- subject
+ it 'logs a warning' do
+ subject
- expect(Gitlab::AppLogger).to have_received(:warn).with(
- message: 'Cannot link an Issue with Alert',
- issue_id: created_issue.id,
- alert_id: alert_management_alert_id,
- alert_errors: { hosts: ['hosts array is over 255 chars'] }
- )
+ expect(Gitlab::AppLogger).to have_received(:warn).with(
+ message: 'Cannot link an Issue with Alert',
+ issue_id: created_issue.id,
+ alert_id: alert.id,
+ alert_errors: { hosts: ['hosts array is over 255 chars'] }
+ )
+ end
end
end
end
diff --git a/spec/workers/incident_management/process_prometheus_alert_worker_spec.rb b/spec/workers/incident_management/process_prometheus_alert_worker_spec.rb
index c9ea96df5c2..c294892a66f 100644
--- a/spec/workers/incident_management/process_prometheus_alert_worker_spec.rb
+++ b/spec/workers/incident_management/process_prometheus_alert_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe IncidentManagement::ProcessPrometheusAlertWorker do
+RSpec.describe IncidentManagement::ProcessPrometheusAlertWorker do
describe '#perform' do
let_it_be(:project) { create(:project) }
let_it_be(:prometheus_alert) { create(:prometheus_alert, project: project) }
@@ -19,137 +19,9 @@ describe IncidentManagement::ProcessPrometheusAlertWorker do
}.with_indifferent_access
end
- it 'creates an issue' do
+ it 'does nothing' do
expect { subject.perform(project.id, alert_params) }
- .to change(Issue, :count)
- .by(1)
- end
-
- it 'relates issue to an event' do
- expect { subject.perform(project.id, alert_params) }
- .to change(prometheus_alert.related_issues, :count)
- .from(0)
- .to(1)
- end
-
- context 'resolved event' do
- let(:issue) { create(:issue, project: project) }
-
- before do
- prometheus_alert_event.related_issues << issue
- prometheus_alert_event.resolve
- end
-
- it 'does not create an issue' do
- expect { subject.perform(project.id, alert_params) }
- .not_to change(Issue, :count)
- end
-
- it 'closes the existing issue' do
- expect { subject.perform(project.id, alert_params) }
- .to change { issue.reload.state }
- .from('opened')
- .to('closed')
- end
-
- it 'leaves a system note on the issue' do
- expect(SystemNoteService)
- .to receive(:auto_resolve_prometheus_alert)
-
- subject.perform(project.id, alert_params)
- end
- end
-
- context 'when project could not be found' do
- let(:non_existing_project_id) { non_existing_record_id }
-
- it 'does not create an issue' do
- expect { subject.perform(non_existing_project_id, alert_params) }
- .not_to change(Issue, :count)
- end
-
- it 'does not relate issue to an event' do
- expect { subject.perform(non_existing_project_id, alert_params) }
- .not_to change(prometheus_alert.related_issues, :count)
- end
- end
-
- context 'when event could not be found' do
- before do
- alert_params[:labels][:gitlab_alert_id] = non_existing_record_id
- end
-
- it 'does not create an issue' do
- expect { subject.perform(project.id, alert_params) }
- .not_to change(Issue, :count)
- end
-
- it 'does not relate issue to an event' do
- expect { subject.perform(project.id, alert_params) }
- .not_to change(prometheus_alert.related_issues, :count)
- end
- end
-
- context 'when issue could not be created' do
- before do
- allow_next_instance_of(IncidentManagement::CreateIssueService) do |instance|
- allow(instance).to receive(:execute).and_return( { error: true } )
- end
- end
-
- it 'does not relate issue to an event' do
- expect { subject.perform(project.id, alert_params) }
- .not_to change(prometheus_alert.related_issues, :count)
- end
- end
-
- context 'self-managed alert' do
- let(:alert_name) { 'alert' }
- let(:starts_at) { Time.now.rfc3339 }
-
- let!(:prometheus_alert_event) do
- create(:self_managed_prometheus_alert_event, project: project, payload_key: payload_key)
- end
-
- let(:alert_params) do
- {
- startsAt: starts_at,
- generatorURL: 'http://localhost:9090/graph?g0.expr=vector%281%29&g0.tab=1',
- labels: {
- alertname: alert_name
- }
- }.with_indifferent_access
- end
-
- it 'creates an issue' do
- expect { subject.perform(project.id, alert_params) }
- .to change(Issue, :count)
- .by(1)
- end
-
- it 'relates issue to an event' do
- expect { subject.perform(project.id, alert_params) }
- .to change(prometheus_alert_event.related_issues, :count)
- .from(0)
- .to(1)
- end
-
- context 'when event could not be found' do
- before do
- alert_params[:generatorURL] = 'http://somethingelse.com'
- end
-
- it 'creates an issue' do
- expect { subject.perform(project.id, alert_params) }
- .to change(Issue, :count)
- .by(1)
- end
-
- it 'does not relate issue to an event' do
- expect { subject.perform(project.id, alert_params) }
- .not_to change(prometheus_alert.related_issues, :count)
- end
- end
+ .not_to change(Issue, :count)
end
end
end
diff --git a/spec/workers/invalid_gpg_signature_update_worker_spec.rb b/spec/workers/invalid_gpg_signature_update_worker_spec.rb
index 4f727469ea8..25c48b55cbb 100644
--- a/spec/workers/invalid_gpg_signature_update_worker_spec.rb
+++ b/spec/workers/invalid_gpg_signature_update_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe InvalidGpgSignatureUpdateWorker do
+RSpec.describe InvalidGpgSignatureUpdateWorker do
context 'when GpgKey is found' do
it 'calls NotificationService.new.run' do
gpg_key = create(:gpg_key)
diff --git a/spec/workers/irker_worker_spec.rb b/spec/workers/irker_worker_spec.rb
index 6b58c04d909..aa1f1d2fe1d 100644
--- a/spec/workers/irker_worker_spec.rb
+++ b/spec/workers/irker_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe IrkerWorker, '#perform' do
+RSpec.describe IrkerWorker, '#perform' do
let_it_be(:project) { create(:project, :repository) }
let_it_be(:user) { create(:user) }
let_it_be(:push_data) { HashWithIndifferentAccess.new(Gitlab::DataBuilder::Push.build_sample(project, user)) }
diff --git a/spec/workers/issue_due_scheduler_worker_spec.rb b/spec/workers/issue_due_scheduler_worker_spec.rb
index 61ad8330840..aecff4a3d93 100644
--- a/spec/workers/issue_due_scheduler_worker_spec.rb
+++ b/spec/workers/issue_due_scheduler_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe IssueDueSchedulerWorker do
+RSpec.describe IssueDueSchedulerWorker do
describe '#perform' do
it 'schedules one MailScheduler::IssueDueWorker per project with open issues due tomorrow' do
project1 = create(:project)
diff --git a/spec/workers/mail_scheduler/issue_due_worker_spec.rb b/spec/workers/mail_scheduler/issue_due_worker_spec.rb
index fa17775e9f2..c03cc0bda61 100644
--- a/spec/workers/mail_scheduler/issue_due_worker_spec.rb
+++ b/spec/workers/mail_scheduler/issue_due_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe MailScheduler::IssueDueWorker do
+RSpec.describe MailScheduler::IssueDueWorker do
describe '#perform' do
let(:worker) { described_class.new }
let(:project) { create(:project) }
diff --git a/spec/workers/mail_scheduler/notification_service_worker_spec.rb b/spec/workers/mail_scheduler/notification_service_worker_spec.rb
index ac95a6d7e43..ff4a1646d09 100644
--- a/spec/workers/mail_scheduler/notification_service_worker_spec.rb
+++ b/spec/workers/mail_scheduler/notification_service_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe MailScheduler::NotificationServiceWorker do
+RSpec.describe MailScheduler::NotificationServiceWorker do
let(:worker) { described_class.new }
let(:method) { 'new_key' }
diff --git a/spec/workers/members_destroyer/unassign_issuables_worker_spec.rb b/spec/workers/members_destroyer/unassign_issuables_worker_spec.rb
new file mode 100644
index 00000000000..2a325be1225
--- /dev/null
+++ b/spec/workers/members_destroyer/unassign_issuables_worker_spec.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe MembersDestroyer::UnassignIssuablesWorker do
+ let_it_be(:group) { create(:group, :private) }
+ let_it_be(:user, reload: true) { create(:user) }
+
+ context 'when unsupported membership source entity' do
+ it 'exits early and logs error' do
+ params = { message: "SomeEntity is not a supported entity.", entity_type: 'SomeEntity', entity_id: group.id, user_id: user.id }
+
+ expect(Sidekiq.logger).to receive(:error).with(params)
+
+ described_class.new.perform(user.id, group.id, 'SomeEntity')
+ end
+ end
+
+ it "calls the Members::UnassignIssuablesService with the params it was given" do
+ service = double
+
+ expect(Members::UnassignIssuablesService).to receive(:new).with(user, group).and_return(service)
+ expect(service).to receive(:execute)
+
+ described_class.new.perform(user.id, group.id, 'Group')
+ end
+end
diff --git a/spec/workers/merge_request_mergeability_check_worker_spec.rb b/spec/workers/merge_request_mergeability_check_worker_spec.rb
index 8909af1f685..0349de5cbb3 100644
--- a/spec/workers/merge_request_mergeability_check_worker_spec.rb
+++ b/spec/workers/merge_request_mergeability_check_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe MergeRequestMergeabilityCheckWorker do
+RSpec.describe MergeRequestMergeabilityCheckWorker do
subject { described_class.new }
describe '#perform' do
diff --git a/spec/workers/merge_worker_spec.rb b/spec/workers/merge_worker_spec.rb
index dc98c9836fa..97e8aeb616e 100644
--- a/spec/workers/merge_worker_spec.rb
+++ b/spec/workers/merge_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe MergeWorker do
+RSpec.describe MergeWorker do
describe "remove source branch" do
let!(:merge_request) { create(:merge_request, source_branch: "markdown") }
let!(:source_project) { merge_request.source_project }
diff --git a/spec/workers/metrics/dashboard/prune_old_annotations_worker_spec.rb b/spec/workers/metrics/dashboard/prune_old_annotations_worker_spec.rb
index bab5a5d8740..d93612afe37 100644
--- a/spec/workers/metrics/dashboard/prune_old_annotations_worker_spec.rb
+++ b/spec/workers/metrics/dashboard/prune_old_annotations_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Metrics::Dashboard::PruneOldAnnotationsWorker do
+RSpec.describe Metrics::Dashboard::PruneOldAnnotationsWorker do
let_it_be(:now) { DateTime.parse('2020-06-02T00:12:00Z') }
let_it_be(:two_weeks_old_annotation) { create(:metrics_dashboard_annotation, starting_at: now.advance(weeks: -2)) }
let_it_be(:one_day_old_annotation) { create(:metrics_dashboard_annotation, starting_at: now.advance(days: -1)) }
diff --git a/spec/workers/metrics/dashboard/schedule_annotations_prune_worker_spec.rb b/spec/workers/metrics/dashboard/schedule_annotations_prune_worker_spec.rb
index bfe6fe3a90e..e0a5a8fd448 100644
--- a/spec/workers/metrics/dashboard/schedule_annotations_prune_worker_spec.rb
+++ b/spec/workers/metrics/dashboard/schedule_annotations_prune_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Metrics::Dashboard::ScheduleAnnotationsPruneWorker do
+RSpec.describe Metrics::Dashboard::ScheduleAnnotationsPruneWorker do
describe '#perform' do
it 'schedules annotations prune job with default cut off date' do
expect(Metrics::Dashboard::PruneOldAnnotationsWorker).to receive(:perform_async)
diff --git a/spec/workers/migrate_external_diffs_worker_spec.rb b/spec/workers/migrate_external_diffs_worker_spec.rb
index 88d48cad14b..86d4680acbe 100644
--- a/spec/workers/migrate_external_diffs_worker_spec.rb
+++ b/spec/workers/migrate_external_diffs_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe MigrateExternalDiffsWorker do
+RSpec.describe MigrateExternalDiffsWorker do
let(:worker) { described_class.new }
let(:diff) { create(:merge_request).merge_request_diff }
diff --git a/spec/workers/namespaceless_project_destroy_worker_spec.rb b/spec/workers/namespaceless_project_destroy_worker_spec.rb
index ceea7c8d8f5..ef396bc7fbb 100644
--- a/spec/workers/namespaceless_project_destroy_worker_spec.rb
+++ b/spec/workers/namespaceless_project_destroy_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe NamespacelessProjectDestroyWorker do
+RSpec.describe NamespacelessProjectDestroyWorker do
include ProjectForksHelper
subject { described_class.new }
diff --git a/spec/workers/namespaces/prune_aggregation_schedules_worker_spec.rb b/spec/workers/namespaces/prune_aggregation_schedules_worker_spec.rb
index b069b080531..11f2501cbe3 100644
--- a/spec/workers/namespaces/prune_aggregation_schedules_worker_spec.rb
+++ b/spec/workers/namespaces/prune_aggregation_schedules_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Namespaces::PruneAggregationSchedulesWorker, '#perform', :clean_gitlab_redis_shared_state do
+RSpec.describe Namespaces::PruneAggregationSchedulesWorker, '#perform', :clean_gitlab_redis_shared_state do
include ExclusiveLeaseHelpers
let(:namespaces) { create_list(:namespace, 5, :with_aggregation_schedule) }
diff --git a/spec/workers/namespaces/root_statistics_worker_spec.rb b/spec/workers/namespaces/root_statistics_worker_spec.rb
index 910a5b23e17..0c6e3e89973 100644
--- a/spec/workers/namespaces/root_statistics_worker_spec.rb
+++ b/spec/workers/namespaces/root_statistics_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Namespaces::RootStatisticsWorker, '#perform' do
+RSpec.describe Namespaces::RootStatisticsWorker, '#perform' do
let(:group) { create(:group, :with_aggregation_schedule) }
subject(:worker) { described_class.new }
diff --git a/spec/workers/namespaces/schedule_aggregation_worker_spec.rb b/spec/workers/namespaces/schedule_aggregation_worker_spec.rb
index 2f4c7f8bc07..f2fe53d6112 100644
--- a/spec/workers/namespaces/schedule_aggregation_worker_spec.rb
+++ b/spec/workers/namespaces/schedule_aggregation_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Namespaces::ScheduleAggregationWorker, '#perform', :clean_gitlab_redis_shared_state do
+RSpec.describe Namespaces::ScheduleAggregationWorker, '#perform', :clean_gitlab_redis_shared_state do
let(:group) { create(:group) }
subject(:worker) { described_class.new }
diff --git a/spec/workers/new_issue_worker_spec.rb b/spec/workers/new_issue_worker_spec.rb
index 1584e9d5302..6386af8d253 100644
--- a/spec/workers/new_issue_worker_spec.rb
+++ b/spec/workers/new_issue_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe NewIssueWorker do
+RSpec.describe NewIssueWorker do
describe '#perform' do
let(:worker) { described_class.new }
diff --git a/spec/workers/new_merge_request_worker_spec.rb b/spec/workers/new_merge_request_worker_spec.rb
index fe22226903f..37449540db5 100644
--- a/spec/workers/new_merge_request_worker_spec.rb
+++ b/spec/workers/new_merge_request_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe NewMergeRequestWorker do
+RSpec.describe NewMergeRequestWorker do
describe '#perform' do
let(:worker) { described_class.new }
diff --git a/spec/workers/new_note_worker_spec.rb b/spec/workers/new_note_worker_spec.rb
index 57269355180..21f10fa5bfb 100644
--- a/spec/workers/new_note_worker_spec.rb
+++ b/spec/workers/new_note_worker_spec.rb
@@ -2,7 +2,7 @@
require "spec_helper"
-describe NewNoteWorker do
+RSpec.describe NewNoteWorker do
context 'when Note found' do
let(:note) { create(:note) }
diff --git a/spec/workers/new_release_worker_spec.rb b/spec/workers/new_release_worker_spec.rb
deleted file mode 100644
index de4e1bac48f..00000000000
--- a/spec/workers/new_release_worker_spec.rb
+++ /dev/null
@@ -1,17 +0,0 @@
-# frozen_string_literal: true
-
-# TODO: Worker can be removed in 13.2:
-# https://gitlab.com/gitlab-org/gitlab/-/issues/218231
-require 'spec_helper'
-
-describe NewReleaseWorker do
- let(:release) { create(:release) }
-
- it 'sends a new release notification' do
- expect_next_instance_of(NotificationService) do |instance|
- expect(instance).to receive(:send_new_release_notifications).with(release)
- end
-
- described_class.new.perform(release.id)
- end
-end
diff --git a/spec/workers/object_pool/create_worker_spec.rb b/spec/workers/object_pool/create_worker_spec.rb
index 06416489472..4ec409bdf47 100644
--- a/spec/workers/object_pool/create_worker_spec.rb
+++ b/spec/workers/object_pool/create_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ObjectPool::CreateWorker do
+RSpec.describe ObjectPool::CreateWorker do
let(:pool) { create(:pool_repository, :scheduled) }
subject { described_class.new }
diff --git a/spec/workers/object_pool/destroy_worker_spec.rb b/spec/workers/object_pool/destroy_worker_spec.rb
index 52d457b4b71..130a666a42e 100644
--- a/spec/workers/object_pool/destroy_worker_spec.rb
+++ b/spec/workers/object_pool/destroy_worker_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-describe ObjectPool::DestroyWorker do
+RSpec.describe ObjectPool::DestroyWorker do
describe '#perform' do
context 'when no pool is in the database' do
it "doesn't raise an error" do
diff --git a/spec/workers/object_pool/join_worker_spec.rb b/spec/workers/object_pool/join_worker_spec.rb
index 906bc22c8d2..335c45e14e0 100644
--- a/spec/workers/object_pool/join_worker_spec.rb
+++ b/spec/workers/object_pool/join_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ObjectPool::JoinWorker do
+RSpec.describe ObjectPool::JoinWorker do
let(:pool) { create(:pool_repository, :ready) }
let(:project) { pool.source_project }
let(:repository) { project.repository }
diff --git a/spec/workers/packages/nuget/extraction_worker_spec.rb b/spec/workers/packages/nuget/extraction_worker_spec.rb
new file mode 100644
index 00000000000..35b5f1baed5
--- /dev/null
+++ b/spec/workers/packages/nuget/extraction_worker_spec.rb
@@ -0,0 +1,94 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Packages::Nuget::ExtractionWorker, type: :worker do
+ describe '#perform' do
+ let!(:package) { create(:nuget_package) }
+ let(:package_file) { package.package_files.first }
+ let(:package_file_id) { package_file.id }
+
+ let_it_be(:package_name) { 'DummyProject.DummyPackage' }
+ let_it_be(:package_version) { '1.0.0' }
+
+ subject { described_class.new.perform(package_file_id) }
+
+ context 'with valid package file' do
+ it 'updates package and package file' do
+ expect { subject }
+ .to not_change { Packages::Package.count }
+ .and not_change { Packages::PackageFile.count }
+ end
+
+ context 'with exisiting package' do
+ let!(:existing_package) { create(:nuget_package, project: package.project, name: package_name, version: package_version) }
+
+ it 'reuses existing package and updates package file' do
+ expect { subject }
+ .to change { Packages::Package.count }.by(-1)
+ .and change { existing_package.reload.package_files.count }.by(1)
+ .and not_change { Packages::PackageFile.count }
+ end
+ end
+ end
+
+ context 'with invalid package file id' do
+ let(:package_file_id) { 5555 }
+
+ it "doesn't update package and package file" do
+ expect { subject }
+ .to not_change { package.reload.name }
+ .and not_change { package.version }
+ .and not_change { package_file.reload.file_name }
+ end
+ end
+
+ context 'with package file not containing a nuspec file' do
+ before do
+ allow_any_instance_of(Zip::File).to receive(:glob).and_return([])
+ end
+
+ it 'removes the package and the package file' do
+ expect(Gitlab::ErrorTracking).to receive(:log_exception).with(
+ instance_of(::Packages::Nuget::MetadataExtractionService::ExtractionError),
+ project_id: package.project_id
+ )
+ expect { subject }
+ .to change { Packages::Package.count }.by(-1)
+ .and change { Packages::PackageFile.count }.by(-1)
+ end
+ end
+
+ context 'with package file with a blank package name' do
+ before do
+ allow_any_instance_of(::Packages::Nuget::UpdatePackageFromMetadataService).to receive(:package_name).and_return('')
+ end
+
+ it 'removes the package and the package file' do
+ expect(Gitlab::ErrorTracking).to receive(:log_exception).with(
+ instance_of(::Packages::Nuget::UpdatePackageFromMetadataService::InvalidMetadataError),
+ project_id: package.project_id
+ )
+ expect { subject }
+ .to change { Packages::Package.count }.by(-1)
+ .and change { Packages::PackageFile.count }.by(-1)
+ end
+ end
+
+ context 'with package file with a blank package version' do
+ before do
+ allow_any_instance_of(::Packages::Nuget::UpdatePackageFromMetadataService).to receive(:package_version).and_return('')
+ end
+
+ it 'removes the package and the package file' do
+ expect(Gitlab::ErrorTracking).to receive(:log_exception).with(
+ instance_of(::Packages::Nuget::UpdatePackageFromMetadataService::InvalidMetadataError),
+ project_id: package.project_id
+ )
+ expect { subject }
+ .to change { Packages::Package.count }.by(-1)
+ .and change { Packages::PackageFile.count }.by(-1)
+ end
+ end
+ end
+end
diff --git a/spec/workers/pages_domain_removal_cron_worker_spec.rb b/spec/workers/pages_domain_removal_cron_worker_spec.rb
index 2408ad54189..f152d019de6 100644
--- a/spec/workers/pages_domain_removal_cron_worker_spec.rb
+++ b/spec/workers/pages_domain_removal_cron_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe PagesDomainRemovalCronWorker do
+RSpec.describe PagesDomainRemovalCronWorker do
subject(:worker) { described_class.new }
describe '#perform' do
diff --git a/spec/workers/pages_domain_ssl_renewal_cron_worker_spec.rb b/spec/workers/pages_domain_ssl_renewal_cron_worker_spec.rb
index 1349a80029b..7c745e51df5 100644
--- a/spec/workers/pages_domain_ssl_renewal_cron_worker_spec.rb
+++ b/spec/workers/pages_domain_ssl_renewal_cron_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe PagesDomainSslRenewalCronWorker do
+RSpec.describe PagesDomainSslRenewalCronWorker do
include LetsEncryptHelpers
subject(:worker) { described_class.new }
diff --git a/spec/workers/pages_domain_ssl_renewal_worker_spec.rb b/spec/workers/pages_domain_ssl_renewal_worker_spec.rb
index a35965f49b2..f8149b23a08 100644
--- a/spec/workers/pages_domain_ssl_renewal_worker_spec.rb
+++ b/spec/workers/pages_domain_ssl_renewal_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe PagesDomainSslRenewalWorker do
+RSpec.describe PagesDomainSslRenewalWorker do
include LetsEncryptHelpers
subject(:worker) { described_class.new }
diff --git a/spec/workers/pages_domain_verification_cron_worker_spec.rb b/spec/workers/pages_domain_verification_cron_worker_spec.rb
index 6dd6c33f5fe..01eaf984c90 100644
--- a/spec/workers/pages_domain_verification_cron_worker_spec.rb
+++ b/spec/workers/pages_domain_verification_cron_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe PagesDomainVerificationCronWorker do
+RSpec.describe PagesDomainVerificationCronWorker do
subject(:worker) { described_class.new }
describe '#perform', :sidekiq do
diff --git a/spec/workers/pages_domain_verification_worker_spec.rb b/spec/workers/pages_domain_verification_worker_spec.rb
index f51ac1f4323..74b9730f7c1 100644
--- a/spec/workers/pages_domain_verification_worker_spec.rb
+++ b/spec/workers/pages_domain_verification_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe PagesDomainVerificationWorker do
+RSpec.describe PagesDomainVerificationWorker do
subject(:worker) { described_class.new }
let(:domain) { create(:pages_domain) }
diff --git a/spec/workers/partition_creation_worker_spec.rb b/spec/workers/partition_creation_worker_spec.rb
new file mode 100644
index 00000000000..50ed9c901c1
--- /dev/null
+++ b/spec/workers/partition_creation_worker_spec.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+require "spec_helper"
+
+RSpec.describe PartitionCreationWorker do
+ describe '#perform' do
+ let(:creator) { double(create_partitions: nil) }
+
+ before do
+ allow(Gitlab::Database::Partitioning::PartitionCreator).to receive(:new).and_return(creator)
+ end
+
+ it 'delegates to PartitionCreator' do
+ expect(creator).to receive(:create_partitions)
+
+ described_class.new.perform
+ end
+ end
+end
diff --git a/spec/workers/pipeline_hooks_worker_spec.rb b/spec/workers/pipeline_hooks_worker_spec.rb
index 60df08f40da..7c75cdc8823 100644
--- a/spec/workers/pipeline_hooks_worker_spec.rb
+++ b/spec/workers/pipeline_hooks_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe PipelineHooksWorker do
+RSpec.describe PipelineHooksWorker do
describe '#perform' do
context 'when pipeline exists' do
let(:pipeline) { create(:ci_pipeline) }
diff --git a/spec/workers/pipeline_metrics_worker_spec.rb b/spec/workers/pipeline_metrics_worker_spec.rb
index 6beecbcd114..c73b84e26a6 100644
--- a/spec/workers/pipeline_metrics_worker_spec.rb
+++ b/spec/workers/pipeline_metrics_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe PipelineMetricsWorker do
+RSpec.describe PipelineMetricsWorker do
let(:project) { create(:project, :repository) }
let!(:merge_request) do
@@ -18,7 +18,7 @@ describe PipelineMetricsWorker do
ref: 'master',
sha: project.repository.commit('master').id,
started_at: 1.hour.ago,
- finished_at: Time.now)
+ finished_at: Time.current)
end
let(:status) { 'pending' }
diff --git a/spec/workers/pipeline_notification_worker_spec.rb b/spec/workers/pipeline_notification_worker_spec.rb
index f2cc2b56236..9a15864173c 100644
--- a/spec/workers/pipeline_notification_worker_spec.rb
+++ b/spec/workers/pipeline_notification_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe PipelineNotificationWorker, :mailer do
+RSpec.describe PipelineNotificationWorker, :mailer do
let_it_be(:pipeline) { create(:ci_pipeline) }
describe '#execute' do
diff --git a/spec/workers/pipeline_process_worker_spec.rb b/spec/workers/pipeline_process_worker_spec.rb
index 8260d7ac320..a6e6b505a38 100644
--- a/spec/workers/pipeline_process_worker_spec.rb
+++ b/spec/workers/pipeline_process_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe PipelineProcessWorker do
+RSpec.describe PipelineProcessWorker do
describe '#perform' do
context 'when pipeline exists' do
let(:pipeline) { create(:ci_pipeline) }
diff --git a/spec/workers/pipeline_schedule_worker_spec.rb b/spec/workers/pipeline_schedule_worker_spec.rb
index 758cfa82d5d..f59d8ad4615 100644
--- a/spec/workers/pipeline_schedule_worker_spec.rb
+++ b/spec/workers/pipeline_schedule_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe PipelineScheduleWorker do
+RSpec.describe PipelineScheduleWorker do
include ExclusiveLeaseHelpers
subject { described_class.new.perform }
@@ -33,7 +33,7 @@ describe PipelineScheduleWorker do
expect(Ci::Pipeline.last).to be_schedule
pipeline_schedule.reload
- expect(pipeline_schedule.next_run_at).to be > Time.now
+ expect(pipeline_schedule.next_run_at).to be > Time.current
expect(pipeline_schedule).to eq(project.ci_pipelines.last.pipeline_schedule)
expect(pipeline_schedule).to be_active
end
diff --git a/spec/workers/pipeline_update_ci_ref_status_worker_service_spec.rb b/spec/workers/pipeline_update_ci_ref_status_worker_service_spec.rb
index 3fe8aa55142..4e7af16c63d 100644
--- a/spec/workers/pipeline_update_ci_ref_status_worker_service_spec.rb
+++ b/spec/workers/pipeline_update_ci_ref_status_worker_service_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
# NOTE: This class is unused and to be removed in 13.1~
-describe PipelineUpdateCiRefStatusWorker do
+RSpec.describe PipelineUpdateCiRefStatusWorker do
let(:worker) { described_class.new }
let(:pipeline) { create(:ci_pipeline) }
diff --git a/spec/workers/pipeline_update_worker_spec.rb b/spec/workers/pipeline_update_worker_spec.rb
index 187298034cc..c5c1cc0eefd 100644
--- a/spec/workers/pipeline_update_worker_spec.rb
+++ b/spec/workers/pipeline_update_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe PipelineUpdateWorker do
+RSpec.describe PipelineUpdateWorker do
describe '#perform' do
context 'when pipeline exists' do
let(:pipeline) { create(:ci_pipeline) }
@@ -12,6 +12,14 @@ describe PipelineUpdateWorker do
described_class.new.perform(pipeline.id)
end
+
+ include_examples 'an idempotent worker' do
+ let(:job_args) { [pipeline.id] }
+
+ it 'sets pipeline status to skipped' do
+ expect { subject }.to change { pipeline.reload.status }.from('pending').to('skipped')
+ end
+ end
end
context 'when pipeline does not exist' do
diff --git a/spec/workers/post_receive_spec.rb b/spec/workers/post_receive_spec.rb
index 18e06332eb3..f64ee4aa2f7 100644
--- a/spec/workers/post_receive_spec.rb
+++ b/spec/workers/post_receive_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe PostReceive do
+RSpec.describe PostReceive do
let(:changes) { "123456 789012 refs/heads/tést\n654321 210987 refs/tags/tag" }
let(:wrongly_encoded_changes) { changes.encode("ISO-8859-1").force_encoding("UTF-8") }
let(:base64_changes) { Base64.encode64(wrongly_encoded_changes) }
@@ -428,7 +428,12 @@ describe PostReceive do
it 'expires the status cache' do
expect(snippet.repository).to receive(:empty?).and_return(true)
expect(snippet.repository).to receive(:expire_status_cache)
- expect(snippet.repository).to receive(:expire_statistics_caches)
+
+ perform
+ end
+
+ it 'updates snippet statistics' do
+ expect(Snippets::UpdateStatisticsService).to receive(:new).with(snippet).and_call_original
perform
end
diff --git a/spec/workers/process_commit_worker_spec.rb b/spec/workers/process_commit_worker_spec.rb
index d247668ac76..a33ee6e1da5 100644
--- a/spec/workers/process_commit_worker_spec.rb
+++ b/spec/workers/process_commit_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ProcessCommitWorker do
+RSpec.describe ProcessCommitWorker do
let(:worker) { described_class.new }
let(:user) { create(:user) }
let(:project) { create(:project, :public, :repository) }
@@ -200,9 +200,9 @@ describe ProcessCommitWorker do
it 'parses date strings into Time instances' do
commit = worker.build_commit(project,
id: '123',
- authored_date: Time.now.to_s)
+ authored_date: Time.current.to_s)
- expect(commit.authored_date).to be_an_instance_of(Time)
+ expect(commit.authored_date).to be_a_kind_of(Time)
end
end
end
diff --git a/spec/workers/project_cache_worker_spec.rb b/spec/workers/project_cache_worker_spec.rb
index fa02762d716..0f91f7af255 100644
--- a/spec/workers/project_cache_worker_spec.rb
+++ b/spec/workers/project_cache_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ProjectCacheWorker do
+RSpec.describe ProjectCacheWorker do
include ExclusiveLeaseHelpers
let(:worker) { described_class.new }
diff --git a/spec/workers/project_daily_statistics_worker_spec.rb b/spec/workers/project_daily_statistics_worker_spec.rb
index 8640add99e5..fa9d938acca 100644
--- a/spec/workers/project_daily_statistics_worker_spec.rb
+++ b/spec/workers/project_daily_statistics_worker_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe ProjectDailyStatisticsWorker, '#perform' do
+RSpec.describe ProjectDailyStatisticsWorker, '#perform' do
let(:worker) { described_class.new }
let(:project) { create(:project) }
diff --git a/spec/workers/project_destroy_worker_spec.rb b/spec/workers/project_destroy_worker_spec.rb
index ec40900a5b7..00a4ddac29f 100644
--- a/spec/workers/project_destroy_worker_spec.rb
+++ b/spec/workers/project_destroy_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ProjectDestroyWorker do
+RSpec.describe ProjectDestroyWorker do
let(:project) { create(:project, :repository, pending_delete: true) }
let(:path) do
Gitlab::GitalyClient::StorageSettings.allow_disk_access do
diff --git a/spec/workers/project_export_worker_spec.rb b/spec/workers/project_export_worker_spec.rb
index 4c49939d34e..1f54b6766a4 100644
--- a/spec/workers/project_export_worker_spec.rb
+++ b/spec/workers/project_export_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ProjectExportWorker do
+RSpec.describe ProjectExportWorker do
let!(:user) { create(:user) }
let!(:project) { create(:project) }
@@ -69,4 +69,14 @@ describe ProjectExportWorker do
end
end
end
+
+ describe 'sidekiq options' do
+ it 'disables retry' do
+ expect(described_class.sidekiq_options['retry']).to eq(false)
+ end
+
+ it 'sets default status expiration' do
+ expect(described_class.sidekiq_options['status_expiration']).to eq(StuckExportJobsWorker::EXPORT_JOBS_EXPIRATION)
+ end
+ end
end
diff --git a/spec/workers/project_service_worker_spec.rb b/spec/workers/project_service_worker_spec.rb
index 56934f122e4..c638b7472ff 100644
--- a/spec/workers/project_service_worker_spec.rb
+++ b/spec/workers/project_service_worker_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe ProjectServiceWorker, '#perform' do
+RSpec.describe ProjectServiceWorker, '#perform' do
let(:worker) { described_class.new }
let(:service) { JiraService.new }
diff --git a/spec/workers/project_update_repository_storage_worker_spec.rb b/spec/workers/project_update_repository_storage_worker_spec.rb
index 98856480b21..f75bb3d1642 100644
--- a/spec/workers/project_update_repository_storage_worker_spec.rb
+++ b/spec/workers/project_update_repository_storage_worker_spec.rb
@@ -1,9 +1,8 @@
# frozen_string_literal: true
require 'spec_helper'
-require 'securerandom'
-describe ProjectUpdateRepositoryStorageWorker do
+RSpec.describe ProjectUpdateRepositoryStorageWorker do
let(:project) { create(:project, :repository) }
subject { described_class.new }
diff --git a/spec/workers/prometheus/create_default_alerts_worker_spec.rb b/spec/workers/prometheus/create_default_alerts_worker_spec.rb
index 1b1867d5bb6..105fa0415d9 100644
--- a/spec/workers/prometheus/create_default_alerts_worker_spec.rb
+++ b/spec/workers/prometheus/create_default_alerts_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Prometheus::CreateDefaultAlertsWorker do
+RSpec.describe Prometheus::CreateDefaultAlertsWorker do
let_it_be(:project) { create(:project) }
let(:worker) { described_class.new }
let(:logger) { worker.send(:logger) }
diff --git a/spec/workers/propagate_integration_worker_spec.rb b/spec/workers/propagate_integration_worker_spec.rb
index e49869a38e9..a0fdd37b3c0 100644
--- a/spec/workers/propagate_integration_worker_spec.rb
+++ b/spec/workers/propagate_integration_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe PropagateIntegrationWorker do
+RSpec.describe PropagateIntegrationWorker do
describe '#perform' do
let(:integration) do
PushoverService.create(
diff --git a/spec/workers/propagate_service_template_worker_spec.rb b/spec/workers/propagate_service_template_worker_spec.rb
index fb4ced77832..4cba313a23f 100644
--- a/spec/workers/propagate_service_template_worker_spec.rb
+++ b/spec/workers/propagate_service_template_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe PropagateServiceTemplateWorker do
+RSpec.describe PropagateServiceTemplateWorker do
include ExclusiveLeaseHelpers
describe '#perform' do
diff --git a/spec/workers/prune_old_events_worker_spec.rb b/spec/workers/prune_old_events_worker_spec.rb
index 14235bde070..c1ba9128475 100644
--- a/spec/workers/prune_old_events_worker_spec.rb
+++ b/spec/workers/prune_old_events_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe PruneOldEventsWorker do
+RSpec.describe PruneOldEventsWorker do
describe '#perform' do
let(:user) { create(:user) }
diff --git a/spec/workers/prune_web_hook_logs_worker_spec.rb b/spec/workers/prune_web_hook_logs_worker_spec.rb
index e57334967fd..6cd7a54ac7a 100644
--- a/spec/workers/prune_web_hook_logs_worker_spec.rb
+++ b/spec/workers/prune_web_hook_logs_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe PruneWebHookLogsWorker do
+RSpec.describe PruneWebHookLogsWorker do
describe '#perform' do
before do
hook = create(:project_hook)
diff --git a/spec/workers/reactive_caching_worker_spec.rb b/spec/workers/reactive_caching_worker_spec.rb
index dcb804a7e6e..63b26817a7a 100644
--- a/spec/workers/reactive_caching_worker_spec.rb
+++ b/spec/workers/reactive_caching_worker_spec.rb
@@ -2,6 +2,6 @@
require 'spec_helper'
-describe ReactiveCachingWorker do
+RSpec.describe ReactiveCachingWorker do
it_behaves_like 'reactive cacheable worker'
end
diff --git a/spec/workers/rebase_worker_spec.rb b/spec/workers/rebase_worker_spec.rb
index 578b8cf7451..9246b283be5 100644
--- a/spec/workers/rebase_worker_spec.rb
+++ b/spec/workers/rebase_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe RebaseWorker, '#perform' do
+RSpec.describe RebaseWorker, '#perform' do
include ProjectForksHelper
context 'when rebasing an MR from a fork where upstream has protected branches' do
diff --git a/spec/workers/remote_mirror_notification_worker_spec.rb b/spec/workers/remote_mirror_notification_worker_spec.rb
index f0fb46f84d9..c6fd614fdea 100644
--- a/spec/workers/remote_mirror_notification_worker_spec.rb
+++ b/spec/workers/remote_mirror_notification_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe RemoteMirrorNotificationWorker, :mailer do
+RSpec.describe RemoteMirrorNotificationWorker, :mailer do
let_it_be(:project) { create(:project, :repository, :remote_mirror) }
let_it_be(:mirror) { project.remote_mirrors.first }
diff --git a/spec/workers/remove_expired_group_links_worker_spec.rb b/spec/workers/remove_expired_group_links_worker_spec.rb
index b637802cd78..91031768632 100644
--- a/spec/workers/remove_expired_group_links_worker_spec.rb
+++ b/spec/workers/remove_expired_group_links_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe RemoveExpiredGroupLinksWorker do
+RSpec.describe RemoveExpiredGroupLinksWorker do
describe '#perform' do
context 'ProjectGroupLinks' do
let!(:expired_project_group_link) { create(:project_group_link, expires_at: 1.hour.ago) }
diff --git a/spec/workers/remove_expired_members_worker_spec.rb b/spec/workers/remove_expired_members_worker_spec.rb
index 69a5725bb35..cbdd5a68698 100644
--- a/spec/workers/remove_expired_members_worker_spec.rb
+++ b/spec/workers/remove_expired_members_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe RemoveExpiredMembersWorker do
+RSpec.describe RemoveExpiredMembersWorker do
let(:worker) { described_class.new }
describe '#perform' do
diff --git a/spec/workers/remove_unreferenced_lfs_objects_worker_spec.rb b/spec/workers/remove_unreferenced_lfs_objects_worker_spec.rb
index 0e21933a9a5..e716d4806d3 100644
--- a/spec/workers/remove_unreferenced_lfs_objects_worker_spec.rb
+++ b/spec/workers/remove_unreferenced_lfs_objects_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe RemoveUnreferencedLfsObjectsWorker do
+RSpec.describe RemoveUnreferencedLfsObjectsWorker do
let(:worker) { described_class.new }
describe '#perform' do
diff --git a/spec/workers/repository_check/batch_worker_spec.rb b/spec/workers/repository_check/batch_worker_spec.rb
index 12e7d1879d0..643b55af573 100644
--- a/spec/workers/repository_check/batch_worker_spec.rb
+++ b/spec/workers/repository_check/batch_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe RepositoryCheck::BatchWorker do
+RSpec.describe RepositoryCheck::BatchWorker do
let(:shard_name) { 'default' }
subject { described_class.new }
diff --git a/spec/workers/repository_check/clear_worker_spec.rb b/spec/workers/repository_check/clear_worker_spec.rb
index 7ad9e287204..b5f09e8a05f 100644
--- a/spec/workers/repository_check/clear_worker_spec.rb
+++ b/spec/workers/repository_check/clear_worker_spec.rb
@@ -2,12 +2,12 @@
require 'spec_helper'
-describe RepositoryCheck::ClearWorker do
+RSpec.describe RepositoryCheck::ClearWorker do
it 'clears repository check columns' do
project = create(:project)
project.update_columns(
last_repository_check_failed: true,
- last_repository_check_at: Time.now
+ last_repository_check_at: Time.current
)
described_class.new.perform
diff --git a/spec/workers/repository_check/dispatch_worker_spec.rb b/spec/workers/repository_check/dispatch_worker_spec.rb
index e4bb0bf4046..5e1bc76ec8e 100644
--- a/spec/workers/repository_check/dispatch_worker_spec.rb
+++ b/spec/workers/repository_check/dispatch_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe RepositoryCheck::DispatchWorker do
+RSpec.describe RepositoryCheck::DispatchWorker do
subject { described_class.new }
it 'does nothing when repository checks are disabled' do
diff --git a/spec/workers/repository_check/single_repository_worker_spec.rb b/spec/workers/repository_check/single_repository_worker_spec.rb
index 43998f912ef..28e3f43d374 100644
--- a/spec/workers/repository_check/single_repository_worker_spec.rb
+++ b/spec/workers/repository_check/single_repository_worker_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require 'fileutils'
-describe RepositoryCheck::SingleRepositoryWorker do
+RSpec.describe RepositoryCheck::SingleRepositoryWorker do
subject(:worker) { described_class.new }
it 'skips when the project has no push events' do
diff --git a/spec/workers/repository_cleanup_worker_spec.rb b/spec/workers/repository_cleanup_worker_spec.rb
index e58664cf22a..41bfeabb7f3 100644
--- a/spec/workers/repository_cleanup_worker_spec.rb
+++ b/spec/workers/repository_cleanup_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe RepositoryCleanupWorker do
+RSpec.describe RepositoryCleanupWorker do
let(:project) { create(:project) }
let(:user) { create(:user) }
diff --git a/spec/workers/repository_fork_worker_spec.rb b/spec/workers/repository_fork_worker_spec.rb
index 7209c40646f..0f2d4eb4b65 100644
--- a/spec/workers/repository_fork_worker_spec.rb
+++ b/spec/workers/repository_fork_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe RepositoryForkWorker do
+RSpec.describe RepositoryForkWorker do
include ProjectForksHelper
describe 'modules' do
diff --git a/spec/workers/repository_import_worker_spec.rb b/spec/workers/repository_import_worker_spec.rb
index 507098582c9..a2c19debdfd 100644
--- a/spec/workers/repository_import_worker_spec.rb
+++ b/spec/workers/repository_import_worker_spec.rb
@@ -2,13 +2,7 @@
require 'spec_helper'
-describe RepositoryImportWorker do
- describe 'modules' do
- it 'includes ProjectImportOptions' do
- expect(described_class).to include_module(ProjectImportOptions)
- end
- end
-
+RSpec.describe RepositoryImportWorker do
describe '#perform' do
let(:project) { create(:project, :import_scheduled) }
let(:import_state) { project.import_state }
diff --git a/spec/workers/repository_remove_remote_worker_spec.rb b/spec/workers/repository_remove_remote_worker_spec.rb
index afac9212fab..7d66131f34e 100644
--- a/spec/workers/repository_remove_remote_worker_spec.rb
+++ b/spec/workers/repository_remove_remote_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe RepositoryRemoveRemoteWorker do
+RSpec.describe RepositoryRemoveRemoteWorker do
include ExclusiveLeaseHelpers
include GitHelpers
@@ -32,7 +32,7 @@ describe RepositoryRemoveRemoteWorker do
expect(subject)
.to receive(:log_error)
- .with('Cannot obtain an exclusive lease. There must be another instance already in execution.')
+ .with("Cannot obtain an exclusive lease for #{subject.class.name}. There must be another instance already in execution.")
subject.perform(project.id, remote_name)
end
diff --git a/spec/workers/repository_update_remote_mirror_worker_spec.rb b/spec/workers/repository_update_remote_mirror_worker_spec.rb
index 7bc499d480d..37eed57cf2e 100644
--- a/spec/workers/repository_update_remote_mirror_worker_spec.rb
+++ b/spec/workers/repository_update_remote_mirror_worker_spec.rb
@@ -2,14 +2,14 @@
require 'spec_helper'
-describe RepositoryUpdateRemoteMirrorWorker, :clean_gitlab_redis_shared_state do
+RSpec.describe RepositoryUpdateRemoteMirrorWorker, :clean_gitlab_redis_shared_state do
subject { described_class.new }
let(:remote_mirror) { create(:remote_mirror) }
- let(:scheduled_time) { Time.now - 5.minutes }
+ let(:scheduled_time) { Time.current - 5.minutes }
around do |example|
- Timecop.freeze(Time.now) { example.run }
+ Timecop.freeze(Time.current) { example.run }
end
def expect_mirror_service_to_return(mirror, result, tries = 0)
@@ -26,7 +26,7 @@ describe RepositoryUpdateRemoteMirrorWorker, :clean_gitlab_redis_shared_state do
end
it 'does not do anything if the mirror was already updated' do
- remote_mirror.update(last_update_started_at: Time.now, update_status: :finished)
+ remote_mirror.update(last_update_started_at: Time.current, update_status: :finished)
expect(Projects::UpdateRemoteMirrorService).not_to receive(:new)
@@ -48,7 +48,7 @@ describe RepositoryUpdateRemoteMirrorWorker, :clean_gitlab_redis_shared_state do
expect_next_instance_of(Projects::UpdateRemoteMirrorService) do |service|
expect(service).to receive(:execute).with(remote_mirror, 1).and_raise('Unexpected!')
end
- expect { subject.perform(remote_mirror.id, Time.now, 1) }.to raise_error('Unexpected!')
+ expect { subject.perform(remote_mirror.id, Time.current, 1) }.to raise_error('Unexpected!')
lease = Gitlab::ExclusiveLease.new("#{described_class.name}:#{remote_mirror.id}", timeout: 1.second)
diff --git a/spec/workers/run_pipeline_schedule_worker_spec.rb b/spec/workers/run_pipeline_schedule_worker_spec.rb
index afeee4bac73..4999909934b 100644
--- a/spec/workers/run_pipeline_schedule_worker_spec.rb
+++ b/spec/workers/run_pipeline_schedule_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe RunPipelineScheduleWorker do
+RSpec.describe RunPipelineScheduleWorker do
describe '#perform' do
let_it_be(:project) { create(:project) }
let_it_be(:user) { create(:user) }
diff --git a/spec/workers/schedule_migrate_external_diffs_worker_spec.rb b/spec/workers/schedule_migrate_external_diffs_worker_spec.rb
index 9d6fecc9f4e..09a0124f6e0 100644
--- a/spec/workers/schedule_migrate_external_diffs_worker_spec.rb
+++ b/spec/workers/schedule_migrate_external_diffs_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ScheduleMigrateExternalDiffsWorker do
+RSpec.describe ScheduleMigrateExternalDiffsWorker do
include ExclusiveLeaseHelpers
let(:worker) { described_class.new }
diff --git a/spec/workers/self_monitoring_project_create_worker_spec.rb b/spec/workers/self_monitoring_project_create_worker_spec.rb
index 00c288bdc46..b618b8ede99 100644
--- a/spec/workers/self_monitoring_project_create_worker_spec.rb
+++ b/spec/workers/self_monitoring_project_create_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe SelfMonitoringProjectCreateWorker do
+RSpec.describe SelfMonitoringProjectCreateWorker do
describe '#perform' do
let(:service_class) { Gitlab::DatabaseImporters::SelfMonitoring::Project::CreateService }
let(:service) { instance_double(service_class) }
diff --git a/spec/workers/self_monitoring_project_delete_worker_spec.rb b/spec/workers/self_monitoring_project_delete_worker_spec.rb
index 3685c73513e..9a53fe59a40 100644
--- a/spec/workers/self_monitoring_project_delete_worker_spec.rb
+++ b/spec/workers/self_monitoring_project_delete_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe SelfMonitoringProjectDeleteWorker do
+RSpec.describe SelfMonitoringProjectDeleteWorker do
let_it_be(:jid) { 'b5b28910d97563e58c2fe55f' }
let_it_be(:data_key) { "self_monitoring_delete_result:#{jid}" }
diff --git a/spec/workers/service_desk_email_receiver_worker_spec.rb b/spec/workers/service_desk_email_receiver_worker_spec.rb
new file mode 100644
index 00000000000..d3bfa51348e
--- /dev/null
+++ b/spec/workers/service_desk_email_receiver_worker_spec.rb
@@ -0,0 +1,53 @@
+# frozen_string_literal: true
+
+require "spec_helper"
+
+RSpec.describe ServiceDeskEmailReceiverWorker, :mailer do
+ describe '#perform' do
+ let(:worker) { described_class.new }
+ let(:email) { fixture_file('emails/service_desk_custom_address.eml') }
+
+ context 'when service_desk_email config is enabled' do
+ before do
+ stub_service_desk_email_setting(enabled: true, address: 'foo')
+ end
+
+ it 'does not ignore the email' do
+ expect(Gitlab::Email::ServiceDeskReceiver).to receive(:new)
+
+ worker.perform(email)
+ end
+
+ context 'when service desk receiver raises an exception' do
+ before do
+ allow_next_instance_of(Gitlab::Email::ServiceDeskReceiver) do |receiver|
+ allow(receiver).to receive(:find_handler).and_return(nil)
+ end
+ end
+
+ it 'sends a rejection email' do
+ perform_enqueued_jobs do
+ worker.perform(email)
+ end
+
+ reply = ActionMailer::Base.deliveries.last
+ expect(reply).not_to be_nil
+ expect(reply.to).to eq(['jake@adventuretime.ooo'])
+ expect(reply.subject).to include('Rejected')
+ end
+ end
+ end
+
+ context 'when service_desk_email config is disabled' do
+ before do
+ stub_service_desk_email_setting(enabled: false, address: 'foo')
+ end
+
+ it 'ignores the email' do
+ expect(Gitlab::Email::ServiceDeskReceiver).not_to receive(:new)
+
+ worker.perform(email)
+ end
+ end
+ end
+end
diff --git a/spec/workers/stage_update_worker_spec.rb b/spec/workers/stage_update_worker_spec.rb
index dc7158cfd2f..75b324a9e0a 100644
--- a/spec/workers/stage_update_worker_spec.rb
+++ b/spec/workers/stage_update_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe StageUpdateWorker do
+RSpec.describe StageUpdateWorker do
describe '#perform' do
context 'when stage exists' do
let(:stage) { create(:ci_stage_entity) }
diff --git a/spec/workers/stuck_ci_jobs_worker_spec.rb b/spec/workers/stuck_ci_jobs_worker_spec.rb
index c994a5dcb78..b96d506771d 100644
--- a/spec/workers/stuck_ci_jobs_worker_spec.rb
+++ b/spec/workers/stuck_ci_jobs_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe StuckCiJobsWorker do
+RSpec.describe StuckCiJobsWorker do
include ExclusiveLeaseHelpers
let!(:runner) { create :ci_runner }
diff --git a/spec/workers/stuck_export_jobs_worker_spec.rb b/spec/workers/stuck_export_jobs_worker_spec.rb
index fc5758fdadf..cbc7adc8e3f 100644
--- a/spec/workers/stuck_export_jobs_worker_spec.rb
+++ b/spec/workers/stuck_export_jobs_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe StuckExportJobsWorker do
+RSpec.describe StuckExportJobsWorker do
let(:worker) { described_class.new }
shared_examples 'project export job detection' do
diff --git a/spec/workers/stuck_import_jobs_worker_spec.rb b/spec/workers/stuck_import_jobs_worker_spec.rb
deleted file mode 100644
index f8d7f8747d5..00000000000
--- a/spec/workers/stuck_import_jobs_worker_spec.rb
+++ /dev/null
@@ -1,27 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-describe StuckImportJobsWorker do
- let(:worker) { described_class.new }
-
- describe 'with scheduled import_status' do
- it_behaves_like 'stuck import job detection' do
- let(:import_state) { create(:project, :import_scheduled).import_state }
-
- before do
- import_state.update(jid: '123')
- end
- end
- end
-
- describe 'with started import_status' do
- it_behaves_like 'stuck import job detection' do
- let(:import_state) { create(:project, :import_started).import_state }
-
- before do
- import_state.update(jid: '123')
- end
- end
- end
-end
diff --git a/spec/workers/stuck_merge_jobs_worker_spec.rb b/spec/workers/stuck_merge_jobs_worker_spec.rb
index 8ceaf1fc555..bade2e1ca1b 100644
--- a/spec/workers/stuck_merge_jobs_worker_spec.rb
+++ b/spec/workers/stuck_merge_jobs_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe StuckMergeJobsWorker do
+RSpec.describe StuckMergeJobsWorker do
describe 'perform' do
let(:worker) { described_class.new }
diff --git a/spec/workers/system_hook_push_worker_spec.rb b/spec/workers/system_hook_push_worker_spec.rb
index 890a622d11a..43a3f8e3e19 100644
--- a/spec/workers/system_hook_push_worker_spec.rb
+++ b/spec/workers/system_hook_push_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe SystemHookPushWorker do
+RSpec.describe SystemHookPushWorker do
include RepoHelpers
subject { described_class.new }
diff --git a/spec/workers/todos_destroyer/confidential_issue_worker_spec.rb b/spec/workers/todos_destroyer/confidential_issue_worker_spec.rb
index 0907e2768ba..86202fac1ed 100644
--- a/spec/workers/todos_destroyer/confidential_issue_worker_spec.rb
+++ b/spec/workers/todos_destroyer/confidential_issue_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe TodosDestroyer::ConfidentialIssueWorker do
+RSpec.describe TodosDestroyer::ConfidentialIssueWorker do
let(:service) { double }
it "calls the Todos::Destroy::ConfidentialIssueService with issue_id parameter" do
diff --git a/spec/workers/todos_destroyer/entity_leave_worker_spec.rb b/spec/workers/todos_destroyer/entity_leave_worker_spec.rb
index cb14fac0910..db3b0252056 100644
--- a/spec/workers/todos_destroyer/entity_leave_worker_spec.rb
+++ b/spec/workers/todos_destroyer/entity_leave_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe TodosDestroyer::EntityLeaveWorker do
+RSpec.describe TodosDestroyer::EntityLeaveWorker do
it "calls the Todos::Destroy::EntityLeaveService with the params it was given" do
service = double
diff --git a/spec/workers/todos_destroyer/group_private_worker_spec.rb b/spec/workers/todos_destroyer/group_private_worker_spec.rb
index d9a240136d5..4903edd4bbe 100644
--- a/spec/workers/todos_destroyer/group_private_worker_spec.rb
+++ b/spec/workers/todos_destroyer/group_private_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe TodosDestroyer::GroupPrivateWorker do
+RSpec.describe TodosDestroyer::GroupPrivateWorker do
it "calls the Todos::Destroy::GroupPrivateService with the params it was given" do
service = double
diff --git a/spec/workers/todos_destroyer/private_features_worker_spec.rb b/spec/workers/todos_destroyer/private_features_worker_spec.rb
index abd04acc3bd..f346a004670 100644
--- a/spec/workers/todos_destroyer/private_features_worker_spec.rb
+++ b/spec/workers/todos_destroyer/private_features_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe TodosDestroyer::PrivateFeaturesWorker do
+RSpec.describe TodosDestroyer::PrivateFeaturesWorker do
it "calls the Todos::Destroy::PrivateFeaturesService with the params it was given" do
service = double
diff --git a/spec/workers/todos_destroyer/project_private_worker_spec.rb b/spec/workers/todos_destroyer/project_private_worker_spec.rb
index c1bb0438ec3..4e54fbdb275 100644
--- a/spec/workers/todos_destroyer/project_private_worker_spec.rb
+++ b/spec/workers/todos_destroyer/project_private_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe TodosDestroyer::ProjectPrivateWorker do
+RSpec.describe TodosDestroyer::ProjectPrivateWorker do
it "calls the Todos::Destroy::ProjectPrivateService with the params it was given" do
service = double
diff --git a/spec/workers/trending_projects_worker_spec.rb b/spec/workers/trending_projects_worker_spec.rb
index 6e524085662..1f1e312e457 100644
--- a/spec/workers/trending_projects_worker_spec.rb
+++ b/spec/workers/trending_projects_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe TrendingProjectsWorker do
+RSpec.describe TrendingProjectsWorker do
describe '#perform' do
it 'refreshes the trending projects' do
expect(TrendingProject).to receive(:refresh!)
diff --git a/spec/workers/update_container_registry_info_worker_spec.rb b/spec/workers/update_container_registry_info_worker_spec.rb
new file mode 100644
index 00000000000..ace9e55cbce
--- /dev/null
+++ b/spec/workers/update_container_registry_info_worker_spec.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe UpdateContainerRegistryInfoWorker do
+ describe '#perform' do
+ it 'calls UpdateContainerRegistryInfoService' do
+ expect_next_instance_of(UpdateContainerRegistryInfoService) do |service|
+ expect(service).to receive(:execute)
+ end
+
+ subject.perform
+ end
+ end
+end
diff --git a/spec/workers/update_external_pull_requests_worker_spec.rb b/spec/workers/update_external_pull_requests_worker_spec.rb
index afac0357b2d..80f22470977 100644
--- a/spec/workers/update_external_pull_requests_worker_spec.rb
+++ b/spec/workers/update_external_pull_requests_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe UpdateExternalPullRequestsWorker do
+RSpec.describe UpdateExternalPullRequestsWorker do
describe '#perform' do
let_it_be(:project) { create(:project, import_source: 'tanuki/repository') }
let_it_be(:user) { create(:user) }
diff --git a/spec/workers/update_head_pipeline_for_merge_request_worker_spec.rb b/spec/workers/update_head_pipeline_for_merge_request_worker_spec.rb
index 8fe3f27c8b1..e6f4f415987 100644
--- a/spec/workers/update_head_pipeline_for_merge_request_worker_spec.rb
+++ b/spec/workers/update_head_pipeline_for_merge_request_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe UpdateHeadPipelineForMergeRequestWorker do
+RSpec.describe UpdateHeadPipelineForMergeRequestWorker do
describe '#perform' do
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project, :repository) }
diff --git a/spec/workers/update_highest_role_worker_spec.rb b/spec/workers/update_highest_role_worker_spec.rb
index 3f377208a62..19512fb0cfc 100644
--- a/spec/workers/update_highest_role_worker_spec.rb
+++ b/spec/workers/update_highest_role_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe UpdateHighestRoleWorker, :clean_gitlab_redis_shared_state do
+RSpec.describe UpdateHighestRoleWorker, :clean_gitlab_redis_shared_state do
include ExclusiveLeaseHelpers
let(:worker) { described_class.new }
diff --git a/spec/workers/update_merge_requests_worker_spec.rb b/spec/workers/update_merge_requests_worker_spec.rb
index 14a363f9d59..fb12086c2f4 100644
--- a/spec/workers/update_merge_requests_worker_spec.rb
+++ b/spec/workers/update_merge_requests_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe UpdateMergeRequestsWorker do
+RSpec.describe UpdateMergeRequestsWorker do
include RepoHelpers
let(:project) { create(:project, :repository) }
diff --git a/spec/workers/update_project_statistics_worker_spec.rb b/spec/workers/update_project_statistics_worker_spec.rb
index 191075f1a52..1f840e363ea 100644
--- a/spec/workers/update_project_statistics_worker_spec.rb
+++ b/spec/workers/update_project_statistics_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe UpdateProjectStatisticsWorker do
+RSpec.describe UpdateProjectStatisticsWorker do
let(:worker) { described_class.new }
let(:project) { create(:project, :repository) }
let(:statistics) { %w(repository_size) }
diff --git a/spec/workers/upload_checksum_worker_spec.rb b/spec/workers/upload_checksum_worker_spec.rb
index 44e9e4f048d..75d7509b6e6 100644
--- a/spec/workers/upload_checksum_worker_spec.rb
+++ b/spec/workers/upload_checksum_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe UploadChecksumWorker do
+RSpec.describe UploadChecksumWorker do
describe '#perform' do
subject { described_class.new }
diff --git a/spec/workers/users/create_statistics_worker_spec.rb b/spec/workers/users/create_statistics_worker_spec.rb
index 3b2b72a832d..e3f082313a0 100644
--- a/spec/workers/users/create_statistics_worker_spec.rb
+++ b/spec/workers/users/create_statistics_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Users::CreateStatisticsWorker do
+RSpec.describe Users::CreateStatisticsWorker do
describe '#perform' do
subject { described_class.new.perform }
diff --git a/spec/workers/wait_for_cluster_creation_worker_spec.rb b/spec/workers/wait_for_cluster_creation_worker_spec.rb
index b21a9b612af..9079dff1afe 100644
--- a/spec/workers/wait_for_cluster_creation_worker_spec.rb
+++ b/spec/workers/wait_for_cluster_creation_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe WaitForClusterCreationWorker do
+RSpec.describe WaitForClusterCreationWorker do
describe '#perform' do
context 'when provider type is gcp' do
let(:cluster) { create(:cluster, provider_type: :gcp, provider_gcp: provider) }
diff --git a/spec/workers/x509_certificate_revoke_worker_spec.rb b/spec/workers/x509_certificate_revoke_worker_spec.rb
index 1e0cbf61267..392cb52d084 100644
--- a/spec/workers/x509_certificate_revoke_worker_spec.rb
+++ b/spec/workers/x509_certificate_revoke_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe X509CertificateRevokeWorker do
+RSpec.describe X509CertificateRevokeWorker do
describe '#perform' do
context 'with a revoked certificate' do
subject { described_class.new }
diff --git a/spec/workers/x509_issuer_crl_check_worker_spec.rb b/spec/workers/x509_issuer_crl_check_worker_spec.rb
index f052812b86b..5564147d274 100644
--- a/spec/workers/x509_issuer_crl_check_worker_spec.rb
+++ b/spec/workers/x509_issuer_crl_check_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe X509IssuerCrlCheckWorker do
+RSpec.describe X509IssuerCrlCheckWorker do
subject(:worker) { described_class.new }
let(:project) { create(:project, :public, :repository) }
diff --git a/symbol/icons.svg b/symbol/icons.svg
deleted file mode 100644
index 433bd2aca0d..00000000000
--- a/symbol/icons.svg
+++ /dev/null
@@ -1 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><symbol viewBox="0 0 1792 1792" id="clock_o" xmlns="http://www.w3.org/2000/svg"><path d="M1024 544v448q0 14-9 23t-23 9H672q-14 0-23-9t-9-23v-64q0-14 9-23t23-9h224V544q0-14 9-23t23-9h64q14 0 23 9t9 23zm416 352q0-148-73-273t-198-198-273-73-273 73-198 198-73 273 73 273 198 198 273 73 273-73 198-198 73-273zm224 0q0 209-103 385.5T1281.5 1561 896 1664t-385.5-103T231 1281.5 128 896t103-385.5T510.5 231 896 128t385.5 103T1561 510.5 1664 896z"/></symbol><symbol viewBox="0 0 36 18" id="commit" xmlns="http://www.w3.org/2000/svg"><path d="M34 7h-7.2c-.9-4-4.5-7-8.8-7s-7.9 3-8.8 7H2C.9 7 0 7.9 0 9s.9 2 2 2h7.2c.9 4 4.5 7 8.8 7s7.9-3 8.8-7H34c1.1 0 2-.9 2-2s-.9-2-2-2m-16 7c-2.8 0-5-2.2-5-5s2.2-5 5-5 5 2.2 5 5-2.2 5-5 5"/></symbol><symbol viewBox="0 0 16 16" id="project" xmlns="http://www.w3.org/2000/svg"><path d="M8.462 2.177l-.038.044a.505.505 0 0 0 .038-.044zm-.787 0a.5.5 0 0 0 .038.043l-.038-.043zM3.706 7h8.725L8.069 2.585 3.706 7zM7 13.369V12a1 1 0 0 1 2 0v1.369h3V9H4v4.369h3zM14 9v4.836c0 .833-.657 1.533-1.5 1.533h-9c-.843 0-1.5-.7-1.5-1.533V9h-.448a1.1 1.1 0 0 1-.783-1.873L6.934.887a1.5 1.5 0 0 1 2.269 0l6.165 6.24A1.1 1.1 0 0 1 14.585 9H14z"/></symbol></svg> \ No newline at end of file
diff --git a/symbol/sprite.symbol.html b/symbol/sprite.symbol.html
deleted file mode 100644
index a2289014093..00000000000
--- a/symbol/sprite.symbol.html
+++ /dev/null
@@ -1,177 +0,0 @@
-<!DOCTYPE html>
-<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
- <head>
- <meta charset="utf-8"/>
- <meta http-equiv="X-UA-Compatible" content="IE=Edge"/>
- <script src="https://rawgit.com/jonathantneal/svg4everybody/master/dist/svg4everybody.js"></script>
- <script>svg4everybody();</script>
- <title>SVG &lt;symbol&gt; sprite preview | svg-sprite</title>
- <style>@charset "UTF-8";body{padding:0;margin:0;color:#666;background:#fafafa;font-family:Arial,Helvetica,sans-serif;font-size:1em;line-height:1.4}header{display:block;padding:3em 3em 2em 3em;background-color:#fff}header p{margin:2em 0 0 0}section{border-top:1px solid #eee;padding:2em 3em 0 3em}section ul{margin:0;padding:0}section li{display:inline;display:inline-block;background-color:#fff;position:relative;margin:0 2em 2em 0;vertical-align:top;border:1px solid #ccc;padding:1em 1em 3em 1em;cursor:default}.icon-box{margin:0;width:144px;height:144px;position:relative;background:#ccc url("data:image/gif;base64,R0lGODlhDAAMAIAAAMzMzP///yH/C1hNUCBEYXRhWE1QPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS4wLWMwNjEgNjQuMTQwOTQ5LCAyMDEwLzEyLzA3LTEwOjU3OjAxICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOnhtcE1NPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvbW0vIiB4bWxuczpzdFJlZj0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL3NUeXBlL1Jlc291cmNlUmVmIyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ1M1LjEgV2luZG93cyIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDozQjk4OTI0MUY5NTIxMUUyQkJDMEI5NEFEM0Y1QTYwQyIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDozQjk4OTI0MkY5NTIxMUUyQkJDMEI5NEFEM0Y1QTYwQyI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOjNCOTg5MjNGRjk1MjExRTJCQkMwQjk0QUQzRjVBNjBDIiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOjNCOTg5MjQwRjk1MjExRTJCQkMwQjk0QUQzRjVBNjBDIi8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+Af/+/fz7+vn49/b19PPy8fDv7u3s6+rp6Ofm5eTj4uHg397d3Nva2djX1tXU09LR0M/OzczLysnIx8bFxMPCwcC/vr28u7q5uLe2tbSzsrGwr66trKuqqainpqWko6KhoJ+enZybmpmYl5aVlJOSkZCPjo2Mi4qJiIeGhYSDgoGAf359fHt6eXh3dnV0c3JxcG9ubWxramloZ2ZlZGNiYWBfXl1cW1pZWFdWVVRTUlFQT05NTEtKSUhHRkVEQ0JBQD8+PTw7Ojk4NzY1NDMyMTAvLi0sKyopKCcmJSQjIiEgHx4dHBsaGRgXFhUUExIREA8ODQwLCgkIBwYFBAMCAQAAIfkEAAAAAAAsAAAAAAwADAAAAhaEH6mHmmzcgzJAUG/NVGrfOZ8YLlABADs=") top left repeat;border:1px solid #ccc;display:table-cell;vertical-align:middle;text-align:center}.icon{display:inline;display:inline-block}h1{margin-top:0}h2{margin:0;padding:0;font-size:1em;font-weight:normal;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;position:absolute;left:1em;right:1em;bottom:1em}footer{display:block;margin:0;padding:0 3em 3em 3em}footer p{margin:0;font-size:.7em}footer a{color:#0f7595;margin-left:0}</style>
-
-<!--
-
-Sprite shape dimensions
-====================================================================================================
-You will need to set the sprite shape dimensions via CSS when you use them as inline SVG, otherwise
-they would become a huge 100% in size. You may use the following dimension classes for doing so.
-They might well be outsourced to an external stylesheet of course.
-
--->
-
-<style type="text/css">
- .svg-clock_o-dims { width: 200px; height: 200px; }
- .svg-commit-dims { width: 36px; height: 18px; }
- .svg-project-dims { width: 16px; height: 16px; }
-</style>
-<!--
-====================================================================================================
--->
-
- </head>
- <body>
-
-<!--
-
-Inline <symbol> SVG sprite
-====================================================================================================
-This is an inlined version of the generated SVG sprite. The single images may be <use>d everywhere
-below within this document. Please see
-
- https://github.com/jkphl/svg-sprite/blob/master/docs/configuration.md#defs--symbol-mode
-
-for further details on how to create this embeddable sprite variant.
-
--->
-
-<svg width="0" height="0" style="position:absolute">
- <symbol viewBox="0 0 1792 1792" id="clock_o" xmlns="http://www.w3.org/2000/svg"><path d="M1024 544v448q0 14-9 23t-23 9H672q-14 0-23-9t-9-23v-64q0-14 9-23t23-9h224V544q0-14 9-23t23-9h64q14 0 23 9t9 23zm416 352q0-148-73-273t-198-198-273-73-273 73-198 198-73 273 73 273 198 198 273 73 273-73 198-198 73-273zm224 0q0 209-103 385.5T1281.5 1561 896 1664t-385.5-103T231 1281.5 128 896t103-385.5T510.5 231 896 128t385.5 103T1561 510.5 1664 896z"/></symbol>
- <symbol viewBox="0 0 36 18" id="commit" xmlns="http://www.w3.org/2000/svg"><path d="M34 7h-7.2c-.9-4-4.5-7-8.8-7s-7.9 3-8.8 7H2C.9 7 0 7.9 0 9s.9 2 2 2h7.2c.9 4 4.5 7 8.8 7s7.9-3 8.8-7H34c1.1 0 2-.9 2-2s-.9-2-2-2m-16 7c-2.8 0-5-2.2-5-5s2.2-5 5-5 5 2.2 5 5-2.2 5-5 5"/></symbol>
- <symbol viewBox="0 0 16 16" id="project" xmlns="http://www.w3.org/2000/svg"><path d="M8.462 2.177l-.038.044a.505.505 0 0 0 .038-.044zm-.787 0a.5.5 0 0 0 .038.043l-.038-.043zM3.706 7h8.725L8.069 2.585 3.706 7zM7 13.369V12a1 1 0 0 1 2 0v1.369h3V9H4v4.369h3zM14 9v4.836c0 .833-.657 1.533-1.5 1.533h-9c-.843 0-1.5-.7-1.5-1.533V9h-.448a1.1 1.1 0 0 1-.783-1.873L6.934.887a1.5 1.5 0 0 1 2.269 0l6.165 6.24A1.1 1.1 0 0 1 14.585 9H14z"/></symbol>
-</svg>
-
-<!--
-====================================================================================================
--->
-
- <header>
- <h1>SVG <code>&lt;symbol&gt;</code> sprite preview</h1>
- <p>This preview features two methods of using the generated sprite in conjunction with inline SVG. Please have a look at the HTML source for further details and be aware of the following constraints:</p>
- <ul>
- <li>Your browser has to <a href="http://caniuse.com/#feat=svg-html5" target="_blank">support inline SVG</a> for these techniques to work.</li>
- <li>The embedded sprite (A) slightly differs from the generated external one. Please <a href="https://github.com/jkphl/svg-sprite/blob/master/docs/configuration.md#defs--symbol-mode" target="_blank">see the documentation</a> for details on how to create such an embeddable sprite.</li>
- <li>Internet Explorer up to version 11 doesn't support external sprites for use with inline SVG. For IE 9-11, you may polyfill this functionality with <a href="https://github.com/jonathantneal/svg4everybody" target="_blank">SVG for Everybody</a>.</li>
- </ul>
- </header>
- <section>
-
-<!--
-
-A) Inline SVG with embedded sprite
-====================================================================================================
-These SVG images make use of fragment identifiers (IDs) and are extracted out of the inline sprite
-embedded above. They may be styled via CSS.
-
--->
-
- <h3>A) Inline SVG with embedded sprite</h3>
- <ul>
-
- <li title="clock_o">
- <div class="icon-box">
-
- <!-- clock_o -->
- <svg class="svg-clock_o-dims">
- <use xlink:href="#clock_o"></use>
- </svg>
-
- </div>
- <h2>clock_o</h2>
- </li>
- <li title="commit">
- <div class="icon-box">
-
- <!-- commit -->
- <svg class="svg-commit-dims">
- <use xlink:href="#commit"></use>
- </svg>
-
- </div>
- <h2>commit</h2>
- </li>
- <li title="project">
- <div class="icon-box">
-
- <!-- project -->
- <svg class="svg-project-dims">
- <use xlink:href="#project"></use>
- </svg>
-
- </div>
- <h2>project</h2>
- </li>
- </ul>
-
-<!--
-====================================================================================================
--->
-
- </section>
- <section>
-
-<!--
-
-B) Inline SVG with external sprite (IE 9-11 with polyfill only)
-====================================================================================================
-These SVG images make use of an URL + fragment identifiers (IDs) and refer to the regular external
-SVG sprite. They may be styled via CSS. (IE 9-11 with polyfill only)
-
--->
-
- <h3>B) Inline SVG with external sprite (IE 9-11 with polyfill only)</h3>
- <ul>
-
- <li title="clock_o">
- <div class="icon-box">
-
- <!-- clock_o -->
- <svg class="svg-clock_o-dims">
- <use xlink:href="icons.svg#clock_o"></use>
- </svg>
-
- </div>
- <h2>clock_o</h2>
- </li>
- <li title="commit">
- <div class="icon-box">
-
- <!-- commit -->
- <svg class="svg-commit-dims">
- <use xlink:href="icons.svg#commit"></use>
- </svg>
-
- </div>
- <h2>commit</h2>
- </li>
- <li title="project">
- <div class="icon-box">
-
- <!-- project -->
- <svg class="svg-project-dims">
- <use xlink:href="icons.svg#project"></use>
- </svg>
-
- </div>
- <h2>project</h2>
- </li>
- </ul>
-
-<!--
-====================================================================================================
--->
-
- </section>
- <footer>
- <p>Generated at Fri, 25 Aug 2017 12:38:01 GMT by <a href="https://github.com/jkphl/svg-sprite" target="_blank">svg-sprite</a>.</p>
- </footer>
- </body>
-</html>
diff --git a/tooling/lib/tooling/helm3_client.rb b/tooling/lib/tooling/helm3_client.rb
new file mode 100644
index 00000000000..802ff9b9661
--- /dev/null
+++ b/tooling/lib/tooling/helm3_client.rb
@@ -0,0 +1,111 @@
+# frozen_string_literal: true
+
+require 'time'
+require 'json'
+require_relative '../../../lib/gitlab/popen' unless defined?(Gitlab::Popen)
+require_relative '../../../lib/gitlab/json' unless defined?(Gitlab::Json)
+
+module Tooling
+ class Helm3Client
+ CommandFailedError = Class.new(StandardError)
+
+ attr_reader :namespace
+
+ RELEASE_JSON_ATTRIBUTES = %w[name revision updated status chart app_version namespace].freeze
+ PAGINATION_SIZE = 256 # Default helm list pagination size
+
+ Release = Struct.new(:name, :revision, :last_update, :status, :chart, :app_version, :namespace) do
+ def revision
+ @revision ||= self[:revision].to_i
+ end
+
+ def last_update
+ @last_update ||= Time.parse(self[:last_update])
+ end
+ end
+
+ # A single page of data and the corresponding page number.
+ Page = Struct.new(:releases, :number)
+
+ def initialize(namespace:)
+ @namespace = namespace
+ end
+
+ def releases(args: [])
+ each_release(args)
+ end
+
+ def delete(release_name:)
+ run_command([
+ 'uninstall',
+ %(--namespace "#{namespace}"),
+ release_name
+ ])
+ end
+
+ private
+
+ def run_command(command)
+ final_command = ['helm', *command].join(' ')
+ puts "Running command: `#{final_command}`" # rubocop:disable Rails/Output
+
+ result = Gitlab::Popen.popen_with_detail([final_command])
+
+ if result.status.success?
+ result.stdout.chomp.freeze
+ else
+ raise CommandFailedError, "The `#{final_command}` command failed (status: #{result.status}) with the following error:\n#{result.stderr}"
+ end
+ end
+
+ def raw_releases(page, args = [])
+ command = [
+ 'list',
+ %(--namespace "#{namespace}"),
+ %(--max #{PAGINATION_SIZE}),
+ %(--offset #{PAGINATION_SIZE * page}),
+ %(--output json),
+ *args
+ ]
+ releases = Gitlab::Json.parse(run_command(command))
+
+ releases.map do |release|
+ Release.new(*release.values_at(*RELEASE_JSON_ATTRIBUTES))
+ end
+ rescue ::JSON::ParserError => ex
+ puts "Ignoring this JSON parsing error: #{ex}" # rubocop:disable Rails/Output
+ []
+ end
+
+ # Fetches data from Helm and yields a Page object for every page
+ # of data, without loading all of them into memory.
+ #
+ # method - The Octokit method to use for getting the data.
+ # args - Arguments to pass to the `helm list` command.
+ def each_releases_page(args, &block)
+ return to_enum(__method__, args) unless block_given?
+
+ page = 0
+ final_args = args.dup
+
+ begin
+ collection = raw_releases(page, final_args)
+
+ yield Page.new(collection, page += 1)
+ end while collection.any?
+ end
+
+ # Iterates over all of the releases.
+ #
+ # args - Any arguments to pass to the `helm list` command.
+ def each_release(args, &block)
+ return to_enum(__method__, args) unless block_given?
+
+ each_releases_page(args) do |page|
+ page.releases.each do |release|
+ yield release
+ end
+ end
+ end
+ end
+end
diff --git a/tooling/lib/tooling/kubernetes_client.rb b/tooling/lib/tooling/kubernetes_client.rb
new file mode 100644
index 00000000000..14b96addf87
--- /dev/null
+++ b/tooling/lib/tooling/kubernetes_client.rb
@@ -0,0 +1,86 @@
+# frozen_string_literal: true
+
+require_relative '../../../lib/gitlab/popen' unless defined?(Gitlab::Popen)
+require_relative '../../../lib/gitlab/json' unless defined?(Gitlab::JSON)
+
+module Tooling
+ class KubernetesClient
+ RESOURCE_LIST = 'ingress,svc,pdb,hpa,deploy,statefulset,job,pod,secret,configmap,pvc,secret,clusterrole,clusterrolebinding,role,rolebinding,sa,crd'
+ CommandFailedError = Class.new(StandardError)
+
+ attr_reader :namespace
+
+ def initialize(namespace:)
+ @namespace = namespace
+ end
+
+ def cleanup(release_name:, wait: true)
+ delete_by_selector(release_name: release_name, wait: wait)
+ delete_by_matching_name(release_name: release_name)
+ end
+
+ private
+
+ def delete_by_selector(release_name:, wait:)
+ selector = case release_name
+ when String
+ %(-l release="#{release_name}")
+ when Array
+ %(-l 'release in (#{release_name.join(', ')})')
+ else
+ raise ArgumentError, 'release_name must be a string or an array'
+ end
+
+ command = [
+ 'delete',
+ RESOURCE_LIST,
+ %(--namespace "#{namespace}"),
+ '--now',
+ '--ignore-not-found',
+ '--include-uninitialized',
+ %(--wait=#{wait}),
+ selector
+ ]
+
+ run_command(command)
+ end
+
+ def delete_by_matching_name(release_name:)
+ resource_names = raw_resource_names
+ command = [
+ 'delete',
+ %(--namespace "#{namespace}"),
+ '--ignore-not-found'
+ ]
+
+ Array(release_name).each do |release|
+ resource_names
+ .select { |resource_name| resource_name.include?(release) }
+ .each { |matching_resource| run_command(command + [matching_resource]) }
+ end
+ end
+
+ def raw_resource_names
+ command = [
+ 'get',
+ RESOURCE_LIST,
+ %(--namespace "#{namespace}"),
+ '-o name'
+ ]
+ run_command(command).lines.map(&:strip)
+ end
+
+ def run_command(command)
+ final_command = ['kubectl', *command].join(' ')
+ puts "Running command: `#{final_command}`" # rubocop:disable Rails/Output
+
+ result = Gitlab::Popen.popen_with_detail([final_command])
+
+ if result.status.success?
+ result.stdout.chomp.freeze
+ else
+ raise CommandFailedError, "The `#{final_command}` command failed (status: #{result.status}) with the following error:\n#{result.stderr}"
+ end
+ end
+ end
+end
diff --git a/tooling/lib/tooling/test_file_finder.rb b/tooling/lib/tooling/test_file_finder.rb
index 4cf7ad35922..cf5de190c4a 100644
--- a/tooling/lib/tooling/test_file_finder.rb
+++ b/tooling/lib/tooling/test_file_finder.rb
@@ -12,7 +12,7 @@ module Tooling
end
def test_files
- impacted_tests = ee_impact | non_ee_impact
+ impacted_tests = ee_impact | non_ee_impact | either_impact
impacted_tests.impact(@file)
end
@@ -23,20 +23,22 @@ module Tooling
class ImpactedTestFile
attr_reader :pattern_matchers
- def initialize
+ def initialize(prefix: nil)
@pattern_matchers = {}
+ @prefix = prefix
yield self if block_given?
end
def associate(pattern, &block)
- @pattern_matchers[pattern] = block
+ @pattern_matchers[%r{^#{@prefix}#{pattern}}] = block
end
def impact(file)
@pattern_matchers.each_with_object(Set.new) do |(pattern, block), result|
if (match = pattern.match(file))
- result << block.call(match)
+ test_files = block.call(match)
+ result.merge(Array(test_files))
end
end.to_a
end
@@ -54,24 +56,38 @@ module Tooling
end
def ee_impact
- ImpactedTestFile.new do |impact|
+ ImpactedTestFile.new(prefix: EE_PREFIX) do |impact|
unless foss_test_only
- impact.associate(%r{^#{EE_PREFIX}app/(.+)\.rb$}) { |match| "#{EE_PREFIX}spec/#{match[1]}_spec.rb" }
- impact.associate(%r{^#{EE_PREFIX}app/(.*/)ee/(.+)\.rb$}) { |match| "#{EE_PREFIX}spec/#{match[1]}#{match[2]}_spec.rb" }
- impact.associate(%r{^#{EE_PREFIX}lib/(.+)\.rb$}) { |match| "#{EE_PREFIX}spec/lib/#{match[1]}_spec.rb" }
- impact.associate(%r{^#{EE_PREFIX}spec/(.+)_spec.rb$}) { |match| match[0] }
+ impact.associate(%r{app/(.+)\.rb$}) { |match| "#{EE_PREFIX}spec/#{match[1]}_spec.rb" }
+ impact.associate(%r{app/(.*/)ee/(.+)\.rb$}) { |match| "#{EE_PREFIX}spec/#{match[1]}#{match[2]}_spec.rb" }
+ impact.associate(%r{lib/(.+)\.rb$}) { |match| "#{EE_PREFIX}spec/lib/#{match[1]}_spec.rb" }
end
- impact.associate(%r{^#{EE_PREFIX}(?!spec)(.*/)ee/(.+)\.rb$}) { |match| "spec/#{match[1]}#{match[2]}_spec.rb" }
- impact.associate(%r{^#{EE_PREFIX}spec/(.*/)ee/(.+)\.rb$}) { |match| "spec/#{match[1]}#{match[2]}.rb" }
+ impact.associate(%r{(?!spec)(.*/)ee/(.+)\.rb$}) { |match| "spec/#{match[1]}#{match[2]}_spec.rb" }
+ impact.associate(%r{spec/(.*/)ee/(.+)\.rb$}) { |match| "spec/#{match[1]}#{match[2]}.rb" }
end
end
def non_ee_impact
ImpactedTestFile.new do |impact|
- impact.associate(%r{^app/(.+)\.rb$}) { |match| "spec/#{match[1]}_spec.rb" }
- impact.associate(%r{^(tooling/)?lib/(.+)\.rb$}) { |match| "spec/#{match[1]}lib/#{match[2]}_spec.rb" }
- impact.associate(%r{^spec/(.+)_spec.rb$}) { |match| match[0] }
+ impact.associate(%r{app/(.+)\.rb$}) { |match| "spec/#{match[1]}_spec.rb" }
+ impact.associate(%r{(tooling/)?lib/(.+)\.rb$}) { |match| "spec/#{match[1]}lib/#{match[2]}_spec.rb" }
+ impact.associate(%r{config/initializers/(.+)\.rb$}) { |match| "spec/initializers/#{match[1]}_spec.rb" }
+ impact.associate('db/structure.sql') { 'spec/db/schema_spec.rb' }
+ impact.associate(%r{db/(?:post_)?migrate/([0-9]+)_(.+)\.rb$}) do |match|
+ [
+ "spec/migrations/#{match[2]}_spec.rb",
+ "spec/migrations/#{match[1]}_#{match[2]}_spec.rb"
+ ]
+ end
+ end
+ end
+
+ def either_impact
+ ImpactedTestFile.new(prefix: %r{^(?<prefix>#{EE_PREFIX})?}) do |impact|
+ impact.associate(%r{app/views/(?<view>.+)\.haml$}) { |match| "#{match[:prefix]}spec/views/#{match[:view]}.haml_spec.rb" }
+ impact.associate(%r{spec/(.+)_spec\.rb$}) { |match| match[0] }
+ impact.associate(%r{spec/factories/.+\.rb$}) { 'spec/factories_spec.rb' }
end
end
end
diff --git a/tooling/overcommit/Gemfile b/tooling/overcommit/Gemfile
index 120cb1ad8d0..615da316fd5 100644
--- a/tooling/overcommit/Gemfile
+++ b/tooling/overcommit/Gemfile
@@ -4,6 +4,6 @@
source 'https://rubygems.org'
gem 'overcommit'
-gem 'gitlab-styles', '~> 4.2.0', require: false
+gem 'gitlab-styles', '~> 4.3.0', require: false
gem 'scss_lint', '~> 0.56.0', require: false
gem 'haml_lint', '~> 0.34.0', require: false
diff --git a/tooling/overcommit/Gemfile.lock b/tooling/overcommit/Gemfile.lock
index 7ab10f741ed..d3a98855d96 100644
--- a/tooling/overcommit/Gemfile.lock
+++ b/tooling/overcommit/Gemfile.lock
@@ -11,7 +11,7 @@ GEM
childprocess (3.0.0)
concurrent-ruby (1.1.6)
ffi (1.12.2)
- gitlab-styles (4.2.0)
+ gitlab-styles (4.3.0)
rubocop (~> 0.82.0)
rubocop-gitlab-security (~> 0.1.0)
rubocop-performance (~> 1.5.2)
@@ -83,7 +83,7 @@ PLATFORMS
ruby
DEPENDENCIES
- gitlab-styles (~> 4.2.0)
+ gitlab-styles (~> 4.3.0)
haml_lint (~> 0.34.0)
overcommit
scss_lint (~> 0.56.0)
diff --git a/vendor/elastic_stack/values.yaml b/vendor/elastic_stack/values.yaml
index 21352dd35e2..a6c9fdd39a4 100644
--- a/vendor/elastic_stack/values.yaml
+++ b/vendor/elastic_stack/values.yaml
@@ -14,8 +14,12 @@ filebeat:
filebeatConfig:
filebeat.yml: |
output.file.enabled: false
+ setup.ilm.enabled: false
+ setup.template.name: 'filebeat'
+ setup.template.pattern: 'filebeat-*'
output.elasticsearch:
hosts: ["http://elastic-stack-elasticsearch-master:9200"]
+ index: "filebeat-%{[agent.version]}-%{+yyyy.MM.dd}"
filebeat.inputs:
- type: container
paths:
diff --git a/vendor/gitignore/C++.gitignore b/vendor/gitignore/C++.gitignore
index 259148fa18f..259148fa18f 100755..100644
--- a/vendor/gitignore/C++.gitignore
+++ b/vendor/gitignore/C++.gitignore
diff --git a/vendor/gitignore/Java.gitignore b/vendor/gitignore/Java.gitignore
index a1c2a238a96..a1c2a238a96 100755..100644
--- a/vendor/gitignore/Java.gitignore
+++ b/vendor/gitignore/Java.gitignore
diff --git a/vendor/licenses.csv b/vendor/licenses.csv
index c8b305d767d..67db6318b05 100644
--- a/vendor/licenses.csv
+++ b/vendor/licenses.csv
@@ -227,7 +227,6 @@ chunky_png,1.3.5,MIT
cipher-base,1.0.4,MIT
citrus,3.0.2,MIT
class-utils,0.3.6,MIT
-classlist-polyfill,1.2.0,Unlicense
cli-cursor,2.1.0,MIT
cli-width,2.1.0,ISC
clipboard,1.7.1,MIT
@@ -447,7 +446,6 @@ follow-redirects,1.2.6,MIT
font-awesome-rails,4.7.0.1,"MIT,SIL Open Font License"
for-in,1.0.2,MIT
formatador,0.2.5,MIT
-formdata-polyfill,3.0.11,MIT
forwarded,0.1.2,MIT
fragment-cache,0.2.1,MIT
fresh,0.5.2,MIT
@@ -1046,7 +1044,6 @@ strip-json-comments,2.0.1,MIT
style-loader,0.23.0,MIT
supports-color,2.0.0,MIT
supports-color,5.5.0,MIT
-svg4everybody,2.1.9,CC0-1.0
symbol-observable,1.2.0,MIT
sys-filesystem,1.1.6,Artistic 2.0
tapable,1.1.0,MIT
diff --git a/vendor/project_templates/jsonnet.tar.gz b/vendor/project_templates/jsonnet.tar.gz
new file mode 100644
index 00000000000..8da4227530a
--- /dev/null
+++ b/vendor/project_templates/jsonnet.tar.gz
Binary files differ
diff --git a/vendor/project_templates/learn_gitlab.tar.gz b/vendor/project_templates/learn_gitlab.tar.gz
index 249f25bc85c..0eca66b064a 100644
--- a/vendor/project_templates/learn_gitlab.tar.gz
+++ b/vendor/project_templates/learn_gitlab.tar.gz
Binary files differ
diff --git a/yarn.lock b/yarn.lock
index 5a6ab803b70..797107e8272 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -756,7 +756,15 @@
"@babel/types" "^7.4.4"
esutils "^2.0.2"
-"@babel/runtime@^7.8.4":
+"@babel/runtime-corejs3@^7.10.2":
+ version "7.10.2"
+ resolved "https://registry.yarnpkg.com/@babel/runtime-corejs3/-/runtime-corejs3-7.10.2.tgz#3511797ddf9a3d6f3ce46b99cc835184817eaa4e"
+ integrity sha512-+a2M/u7r15o3dV1NEizr9bRi+KUVnrs/qYxF0Z06DAPx/4VCWaz1WA7EcbE+uqGgt39lp5akWGmHsTseIkHkHg==
+ dependencies:
+ core-js-pure "^3.0.0"
+ regenerator-runtime "^0.13.4"
+
+"@babel/runtime@^7.10.2", "@babel/runtime@^7.8.4":
version "7.10.2"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.10.2.tgz#d103f21f2602497d38348a32e008637d506db839"
integrity sha512-6sF3uQw2ivImfVIl62RZ7MXhO2tap69WeWK57vAaimT6AZbE4FbqjdEJIN1UqoD6wI6B+1n9UiagafH1sxjOtg==
@@ -835,15 +843,15 @@
eslint-plugin-vue "^6.2.1"
vue-eslint-parser "^7.0.0"
-"@gitlab/svgs@1.140.0":
- version "1.140.0"
- resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-1.140.0.tgz#593f1f65b0df57c3399fcfb9f472f59aa64da074"
- integrity sha512-6gANJGi2QkpvOgFTMcY3SIwEqhO69i6R3jU4BSskkVziwDdAWxGonln22a4Iu//Iv0NrsFDpAA0jIVfnJzw0iA==
+"@gitlab/svgs@1.152.0":
+ version "1.152.0"
+ resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-1.152.0.tgz#663c9a5f073f59b66f4241ef2d3fea2205846905"
+ integrity sha512-daZHOBVAwjsU6n60IycanoO/JymfQ36vrr46OUdWjHdp0ATYrgh+01LcxiSNLdlyndIRqHWGtwmuilokM9q6Vg==
-"@gitlab/ui@17.0.1":
- version "17.0.1"
- resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-17.0.1.tgz#daf036dfdc095f94123c80c3fb1ab5fe4dcbf95b"
- integrity sha512-JSUGruV6oploADF0Sc0BBY43Des3utU9iWCnR8BAmttKFXFFNUKwTf908yZPGJtfnVyjJkVioOCOYkvUZ0jngg==
+"@gitlab/ui@17.33.0":
+ version "17.33.0"
+ resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-17.33.0.tgz#d0b3e8bf268fe9a28e97a6aa71b5a02059078206"
+ integrity sha512-WszO9zRhO5EkbLrpujPMDgrHIymkSGXRRwg32TzqASSrl4WymdgSpGYVXdTu95c1n3n7pngkjOEdh/90aM89FQ==
dependencies:
"@babel/standalone" "^7.0.0"
"@gitlab/vue-toasted" "^1.3.0"
@@ -1138,25 +1146,30 @@
dependencies:
defer-to-connect "^1.0.1"
-"@toast-ui/editor@^2.0.1":
- version "2.0.1"
- resolved "https://registry.yarnpkg.com/@toast-ui/editor/-/editor-2.0.1.tgz#749e5be1f02f42ded51488d1575ab1c19ca59952"
- integrity sha512-TC481O/zP37boY6H6oVN6KLVMY7yrU8zQu+3xqZ71V3Sr6D2XyaGb2Xub9XqTdqzBmzsf7y4Gi+EXO0IQ3rGVA==
+"@testing-library/dom@^7.16.2":
+ version "7.16.2"
+ resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-7.16.2.tgz#f7a20b5548817e5c7ed26077913372d977be90af"
+ integrity sha512-4fT5l5L+5gfNhUZVCg0wnSszbRJ7A1ZHEz32v7OzH3mcY5lUsK++brI3IB2L9F5zO4kSDc2TRGEVa8v2hgl9vA==
+ dependencies:
+ "@babel/runtime" "^7.10.2"
+ aria-query "^4.0.2"
+ dom-accessibility-api "^0.4.5"
+ pretty-format "^25.5.0"
+
+"@toast-ui/editor@^2.2.0":
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/@toast-ui/editor/-/editor-2.2.0.tgz#77fd790c6ae876d5de738bc022d6ebc5c84a6feb"
+ integrity sha512-WiqrY7OeCOS08NlznJobCwtxOWJC/5my8QefHCKTZyX9/70kkojcnyQ8aoiQQ5kIfGUJ6dKt6/JuKD5OOib+bQ==
dependencies:
"@types/codemirror" "0.0.71"
codemirror "^5.48.4"
-"@toast-ui/vue-editor@^2.0.1":
- version "2.0.1"
- resolved "https://registry.yarnpkg.com/@toast-ui/vue-editor/-/vue-editor-2.0.1.tgz#c9c8c8da4c0a67b9fbc4240464388c67d72a0c22"
- integrity sha512-sGsApl0n+GVAZbmPA+tTrq9rmmyh2mRgCgg2/mu1/lN7S4vPv/nQH8KXxLG9Y6hG2+kgelqz6wvbOCdzlM/HmQ==
+"@toast-ui/vue-editor@^2.2.0":
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/@toast-ui/vue-editor/-/vue-editor-2.2.0.tgz#bae8e6e6c0a7d6fb40a4f6b8e616aada3923118d"
+ integrity sha512-z8q60tEIfrIOk1fQitRg56ZxztOUyp2A1gLlTVuTpFNts21lTsMfFcUNdZsAivWUN6ToQu4qP8Bz80h9FZBLBg==
dependencies:
- "@toast-ui/editor" "^2.0.1"
-
-"@types/anymatch@*":
- version "1.3.0"
- resolved "https://registry.yarnpkg.com/@types/anymatch/-/anymatch-1.3.0.tgz#d1d55958d1fccc5527d4aba29fc9c4b942f563ff"
- integrity sha512-7WcbyctkE8GTzogDb0ulRAEw7v8oIS54ft9mQTU7PfM0hp5e+8kpa+HeQ7IQrFbKtJXBKcZ4bh+Em9dTw5L6AQ==
+ "@toast-ui/editor" "^2.2.0"
"@types/babel__core@^7.1.0":
version "7.1.2"
@@ -1267,11 +1280,6 @@
resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-1.0.1.tgz#0a851d3bd96498fa25c33ab7278ed3bd65f06c3e"
integrity sha512-l42BggppR6zLmpfU6fq9HEa2oGPEI8yrSPL3GITjfRInppYFahObbIQOQK3UGxEnyQpltZLaPe75046NOZQikw==
-"@types/tapable@*":
- version "1.0.4"
- resolved "https://registry.yarnpkg.com/@types/tapable/-/tapable-1.0.4.tgz#b4ffc7dc97b498c969b360a41eee247f82616370"
- integrity sha512-78AdXtlhpCHT0K3EytMpn4JNxaf5tbqbLcbIRoQIHzpTIyjpxLQKRoxU55ujBXAtg3Nl2h/XWvfDa9dsMOd0pQ==
-
"@types/tern@*":
version "0.23.3"
resolved "https://registry.yarnpkg.com/@types/tern/-/tern-0.23.3.tgz#4b54538f04a88c9ff79de1f6f94f575a7f339460"
@@ -1279,13 +1287,6 @@
dependencies:
"@types/estree" "*"
-"@types/uglify-js@*":
- version "3.0.4"
- resolved "https://registry.yarnpkg.com/@types/uglify-js/-/uglify-js-3.0.4.tgz#96beae23df6f561862a830b4288a49e86baac082"
- integrity sha512-SudIN9TRJ+v8g5pTG8RRCqfqTMNqgWCKKd3vtynhGzkIIjxaicNAMuY5TRadJ6tzDu3Dotf3ngaMILtmOdmWEQ==
- dependencies:
- source-map "^0.6.1"
-
"@types/unist@*", "@types/unist@^2.0.0":
version "2.0.3"
resolved "https://registry.yarnpkg.com/@types/unist/-/unist-2.0.3.tgz#9c088679876f374eb5983f150d4787aa6fb32d7e"
@@ -1308,17 +1309,6 @@
"@types/unist" "*"
"@types/vfile-message" "*"
-"@types/webpack@^4.4.19":
- version "4.4.23"
- resolved "https://registry.yarnpkg.com/@types/webpack/-/webpack-4.4.23.tgz#059d6f4598cfd65ddee0e2db38317ef989696712"
- integrity sha512-WswyG+2mRg0ul/ytPpCSWo+kOlVVPW/fKCBEVwqmPVC/2ffWEwhsCEQgnFbWDf8EWId2qGcpL623EjLfNTRk9A==
- dependencies:
- "@types/anymatch" "*"
- "@types/node" "*"
- "@types/tapable" "*"
- "@types/uglify-js" "*"
- source-map "^0.6.0"
-
"@types/yargs-parser@*":
version "15.0.0"
resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-15.0.0.tgz#cb3f9f741869e20cce330ffbeb9271590483882d"
@@ -1742,7 +1732,7 @@ ansi-styles@^3.2.0, ansi-styles@^3.2.1:
dependencies:
color-convert "^1.9.0"
-ansi-styles@^4.1.0:
+ansi-styles@^4.0.0, ansi-styles@^4.1.0:
version "4.2.1"
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.2.1.tgz#90ae75c424d008d2624c5bf29ead3177ebfcf359"
integrity sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==
@@ -1882,6 +1872,14 @@ argparse@^1.0.7:
dependencies:
sprintf-js "~1.0.2"
+aria-query@^4.0.2:
+ version "4.2.0"
+ resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-4.2.0.tgz#3774158e138a84c4790b58afc17e09711071d888"
+ integrity sha512-tpVyXGt6gJVTVwCmu8qgBkDHhvtQ/es80y6J8ziybiwQU/x+LnCy+v8p9CWOTHv3i1BMnH5/IfzMpuTcFPCMQA==
+ dependencies:
+ "@babel/runtime" "^7.10.2"
+ "@babel/runtime-corejs3" "^7.10.2"
+
arr-diff@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-4.0.0.tgz#d6461074febfec71e7e15235761a329a5dc7c520"
@@ -2829,11 +2827,6 @@ class-utils@^0.3.5:
isobject "^3.0.0"
static-extend "^0.1.1"
-classlist-polyfill@^1.2.0:
- version "1.2.0"
- resolved "https://registry.yarnpkg.com/classlist-polyfill/-/classlist-polyfill-1.2.0.tgz#935bc2dfd9458a876b279617514638bcaa964a2e"
- integrity sha1-k1vC39lFiodrJ5YXUUY4vKqWSi4=
-
clean-css@^4.1.6, clean-css@^4.2.1:
version "4.2.1"
resolved "https://registry.yarnpkg.com/clean-css/-/clean-css-4.2.1.tgz#2d411ef76b8569b6d0c84068dabe85b0aa5e5c17"
@@ -3261,6 +3254,11 @@ core-js-compat@^3.6.2:
browserslist "^4.8.3"
semver "7.0.0"
+core-js-pure@^3.0.0:
+ version "3.6.5"
+ resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.6.5.tgz#c79e75f5e38dbc85a662d91eea52b8256d53b813"
+ integrity sha512-lacdXOimsiD0QyNf9BC/mxivNJ/ybBGJXQFKzRekp1WTHoVUWsUHEn+2T8GJAzzIhyOuXA+gOxCVN3l+5PLPUA==
+
core-js@^3.1.3, core-js@^3.6.4:
version "3.6.4"
resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.6.4.tgz#440a83536b458114b9cb2ac1580ba377dc470647"
@@ -4069,6 +4067,11 @@ document-register-element@1.14.3:
dependencies:
lightercollective "^0.3.0"
+dom-accessibility-api@^0.4.5:
+ version "0.4.5"
+ resolved "https://registry.yarnpkg.com/dom-accessibility-api/-/dom-accessibility-api-0.4.5.tgz#d9c1cefa89f509d8cf132ab5d250004d755e76e3"
+ integrity sha512-HcPDilI95nKztbVikaN2vzwvmv0sE8Y2ZJFODy/m15n7mGXLeOKGiys9qWVbFbh+aq/KYj2lqMLybBOkYAEXqg==
+
dom-event-types@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/dom-event-types/-/dom-event-types-1.0.0.tgz#5830a0a29e1bf837fe50a70cd80a597232813cae"
@@ -5157,11 +5160,6 @@ format@^0.2.2:
resolved "https://registry.yarnpkg.com/format/-/format-0.2.2.tgz#d6170107e9efdc4ed30c9dc39016df942b5cb58b"
integrity sha1-1hcBB+nv3E7TDJ3DkBbflCtctYs=
-formdata-polyfill@^3.0.19:
- version "3.0.19"
- resolved "https://registry.yarnpkg.com/formdata-polyfill/-/formdata-polyfill-3.0.19.tgz#72f517db3a646a5dd8c31af0edf111fd8f1e4cee"
- integrity sha512-nRSp8nniopIOCLZOUE2omwnUvmRH6VEdKm52rLTne8XBsW7hMMBUiOjuxUPoBsiK0CatKmxArh+Svt2s7R66JQ==
-
forwarded@~0.1.2:
version "0.1.2"
resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.2.tgz#98c23dab1175657b8c0573e8ceccd91b0ff18c84"
@@ -6122,11 +6120,16 @@ ip@^1.1.0, ip@^1.1.5:
resolved "https://registry.yarnpkg.com/ip/-/ip-1.1.5.tgz#bdded70114290828c0a039e72ef25f5aaec4354a"
integrity sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=
-ipaddr.js@1.9.0, ipaddr.js@^1.9.0:
+ipaddr.js@1.9.0:
version "1.9.0"
resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.0.tgz#37df74e430a0e47550fe54a2defe30d8acd95f65"
integrity sha512-M4Sjn6N/+O6/IXSJseKqHoFc+5FdGJ22sXqnjTpdZweHK64MzEPAyQZyEU3R/KRv2GLoa7nNtg/C2Ev6m7z+eA==
+ipaddr.js@^1.9.0, ipaddr.js@^1.9.1:
+ version "1.9.1"
+ resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3"
+ integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==
+
is-absolute-url@^3.0.3:
version "3.0.3"
resolved "https://registry.yarnpkg.com/is-absolute-url/-/is-absolute-url-3.0.3.tgz#96c6a22b6a23929b11ea0afb1836c36ad4a5d698"
@@ -8196,11 +8199,6 @@ mississippi@^3.0.0:
stream-each "^1.1.0"
through2 "^2.0.0"
-mitt@^1.2.0:
- version "1.2.0"
- resolved "https://registry.yarnpkg.com/mitt/-/mitt-1.2.0.tgz#cb24e6569c806e31bd4e3995787fe38a04fdf90d"
- integrity sha512-r6lj77KlwqLhIUku9UWYes7KJtsczvolZkzp8hbaDPPaE24OmWl5s539Mytlj22siEQKosZ26qCBgda2PKwoJw==
-
mixin-deep@^1.2.0:
version "1.3.2"
resolved "https://registry.yarnpkg.com/mixin-deep/-/mixin-deep-1.3.2.tgz#1120b43dc359a785dce65b55b82e257ccf479566"
@@ -8221,17 +8219,24 @@ moment-mini@^2.22.1:
resolved "https://registry.yarnpkg.com/moment-mini/-/moment-mini-2.22.1.tgz#bc32d73e43a4505070be6b53494b17623183420d"
integrity sha512-OUCkHOz7ehtNMYuZjNciXUfwTuz8vmF1MTbAy59ebf+ZBYZO5/tZKuChVWCX+uDo+4idJBpGltNfV8st+HwsGw==
-monaco-editor-webpack-plugin@^1.7.0:
- version "1.7.0"
- resolved "https://registry.yarnpkg.com/monaco-editor-webpack-plugin/-/monaco-editor-webpack-plugin-1.7.0.tgz#920cbeecca25f15d70d568a7e11b0ba4daf1ae83"
- integrity sha512-oItymcnlL14Sjd7EF7q+CMhucfwR/2BxsqrXIBrWL6LQplFfAfV+grLEQRmVHeGSBZ/Gk9ptzfueXnWcoEcFuA==
+monaco-editor-webpack-plugin@^1.9.0:
+ version "1.9.0"
+ resolved "https://registry.yarnpkg.com/monaco-editor-webpack-plugin/-/monaco-editor-webpack-plugin-1.9.0.tgz#5b547281b9f404057dc5d8c5722390df9ac90be6"
+ integrity sha512-tOiiToc94E1sb50BgZ8q8WK/bxus77SRrwCqIpAB5er3cpX78SULbEBY4YPOB8kDolOzKRt30WIHG/D6gz69Ww==
dependencies:
- "@types/webpack" "^4.4.19"
+ loader-utils "^1.2.3"
-monaco-editor@0.18.1, monaco-editor@^0.18.1:
- version "0.18.1"
- resolved "https://registry.yarnpkg.com/monaco-editor/-/monaco-editor-0.18.1.tgz#ced7c305a23109875feeaf395a504b91f6358cfc"
- integrity sha512-fmL+RFZ2Hrezy+X/5ZczQW51LUmvzfcqOurnkCIRFTyjdVjzR7JvENzI6+VKBJzJdPh6EYL4RoWl92b2Hrk9fw==
+monaco-editor@0.20.0, monaco-editor@^0.20.0:
+ version "0.20.0"
+ resolved "https://registry.yarnpkg.com/monaco-editor/-/monaco-editor-0.20.0.tgz#5d5009343a550124426cb4d965a4d27a348b4dea"
+ integrity sha512-hkvf4EtPJRMQlPC3UbMoRs0vTAFAYdzFQ+gpMb8A+9znae1c43q8Mab9iVsgTcg/4PNiLGGn3SlDIa8uvK1FIQ==
+
+monaco-yaml@^2.4.0:
+ version "2.4.0"
+ resolved "https://registry.yarnpkg.com/monaco-yaml/-/monaco-yaml-2.4.0.tgz#027307a231d809c416babf1cf89b4c1bb940e55d"
+ integrity sha512-ElUS6uBqEjA2/o2gLuNdnqWSAAQXh8ISr1kwlFErm3t5IXO74TNfS3gnjO6Kv9TXS7LImjGfgPAZei7o8zNTHw==
+ optionalDependencies:
+ prettier "^1.19.1"
mousetrap@^1.4.6:
version "1.4.6"
@@ -9356,11 +9361,16 @@ prettier@1.16.3:
resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.16.3.tgz#8c62168453badef702f34b45b6ee899574a6a65d"
integrity sha512-kn/GU6SMRYPxUakNXhpP0EedT/KmaPzr0H5lIsDogrykbaxOpOfAFfk5XA7DZrJyMAv1wlMV3CPcZruGXVVUZw==
-prettier@1.18.2, prettier@^1.18.2:
+prettier@1.18.2:
version "1.18.2"
resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.18.2.tgz#6823e7c5900017b4bd3acf46fe9ac4b4d7bda9ea"
integrity sha512-OeHeMc0JhFE9idD4ZdtNibzY0+TPHSpSSb9h8FqtP+YnoZZ1sl8Vc9b1sasjfymH3SonAF4QcA2+mzHPhMvIiw==
+prettier@^1.18.2, prettier@^1.19.1:
+ version "1.19.1"
+ resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.19.1.tgz#f7d7f5ff8a9cd872a7be4ca142095956a60797cb"
+ integrity sha512-s7PoyDv/II1ObgQunCbB9PdLmUcBZcnWOcxDh7O0N/UwDEsHyqkW+Qh28jW+mVuCdx7gLB0BotYI1Y6uI9iyew==
+
pretty-format@^24.8.0:
version "24.8.0"
resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-24.8.0.tgz#8dae7044f58db7cb8be245383b565a963e3c27f2"
@@ -9371,6 +9381,16 @@ pretty-format@^24.8.0:
ansi-styles "^3.2.0"
react-is "^16.8.4"
+pretty-format@^25.5.0:
+ version "25.5.0"
+ resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-25.5.0.tgz#7873c1d774f682c34b8d48b6743a2bf2ac55791a"
+ integrity sha512-kbo/kq2LQ/A/is0PQwsEHM7Ca6//bGPPvU6UnsdDRSKTWxT/ru/xb88v4BJf6a69H+uTytOEsTusT9ksd/1iWQ==
+ dependencies:
+ "@jest/types" "^25.5.0"
+ ansi-regex "^5.0.0"
+ ansi-styles "^4.0.0"
+ react-is "^16.12.0"
+
pretty@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/pretty/-/pretty-2.0.0.tgz#adbc7960b7bbfe289a557dc5f737619a220d06a5"
@@ -9717,10 +9737,10 @@ rc@^1.2.8, rc@~1.2.7:
minimist "^1.2.0"
strip-json-comments "~2.0.1"
-react-is@^16.8.4:
- version "16.8.6"
- resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.8.6.tgz#5bbc1e2d29141c9fbdfed456343fe2bc430a6a16"
- integrity sha512-aUk3bHfZ2bRSVFFbbeVS4i+lNPZr3/WM5jT2J5omUVV1zzcs1nAaf3l51ctA5FFvCRbhrH0bdAsRRQddFJZPtA==
+react-is@^16.12.0, react-is@^16.8.4:
+ version "16.13.1"
+ resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
+ integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
read-pkg-up@^1.0.1:
version "1.0.1"
@@ -11176,11 +11196,6 @@ svg-tags@^1.0.0:
resolved "https://registry.yarnpkg.com/svg-tags/-/svg-tags-1.0.0.tgz#58f71cee3bd519b59d4b2a843b6c7de64ac04764"
integrity sha1-WPcc7jvVGbWdSyqEO2x95krAR2Q=
-svg4everybody@2.1.9:
- version "2.1.9"
- resolved "https://registry.yarnpkg.com/svg4everybody/-/svg4everybody-2.1.9.tgz#5bd9f6defc133859a044646d4743fabc28db7e2d"
- integrity sha1-W9n23vwTOFmgRGRtR0P6vCjbfi0=
-
swagger-ui-dist@^3.26.2:
version "3.26.2"
resolved "https://registry.yarnpkg.com/swagger-ui-dist/-/swagger-ui-dist-3.26.2.tgz#22c700906c8911b1c9956da6c3fca371dba6219f"
@@ -11507,10 +11522,10 @@ tr46@^2.0.2:
dependencies:
punycode "^2.1.1"
-tributejs@4.1.3:
- version "4.1.3"
- resolved "https://registry.yarnpkg.com/tributejs/-/tributejs-4.1.3.tgz#2e1be7d9a1e403ed4c394f91d859812267e4691c"
- integrity sha512-+VUqyi8p7tCdaqCINCWHf95E2hJFMIML180BhplTpXNooz3E2r96AONXI9qO2Ru6Ugp7MsMPJjB+rnBq+hAmzA==
+tributejs@5.1.3:
+ version "5.1.3"
+ resolved "https://registry.yarnpkg.com/tributejs/-/tributejs-5.1.3.tgz#980600fc72865be5868893078b4bfde721129eae"
+ integrity sha512-B5CXihaVzXw+1UHhNFyAwUTMDk1EfoLP5Tj1VhD9yybZ1I8DZJEv8tZ1l0RJo0t0tk9ZhR8eG5tEsaCvRigmdQ==
trim-newlines@^1.0.0:
version "1.0.0"
@@ -11706,11 +11721,6 @@ underscore@~1.8.3:
resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.8.3.tgz#4f3fb53b106e6097fcf9cb4109f2a5e9bdfa5022"
integrity sha1-Tz+1OxBuYJf8+ctBCfKl6b36UCI=
-unfetch@^4.1.0:
- version "4.1.0"
- resolved "https://registry.yarnpkg.com/unfetch/-/unfetch-4.1.0.tgz#6ec2dd0de887e58a4dee83a050ded80ffc4137db"
- integrity sha512-crP/n3eAPUJxZXM9T80/yv0YhkTEx2K1D3h7D1AJM6fzsWZrxdyRuLN0JH/dkZh1LNH8LxCnBzoPFCPbb2iGpg==
-
unherit@^1.0.4:
version "1.1.1"
resolved "https://registry.yarnpkg.com/unherit/-/unherit-1.1.1.tgz#132748da3e88eab767e08fabfbb89c5e9d28628c"